pax_global_header00006660000000000000000000000064132430605020014505gustar00rootroot0000000000000052 comment=d1b8bb94f6012090fa17aabd9252cac26a81353f trunk-2018.02b/000077500000000000000000000000001324306050200131665ustar00rootroot00000000000000trunk-2018.02b/.gitattributes000066400000000000000000000000621324306050200160570ustar00rootroot00000000000000# Ignore all diffs in line endings * -crlf trunk-2018.02b/.gitignore000066400000000000000000000012741324306050200151620ustar00rootroot00000000000000tags .project VERSION yade.1 debian/changelog debian/control doc/epydoc doc/sphinx/WallStresses doc/sphinx/_build/ doc/sphinx/_publications.bib doc/sphinx/abc.xml doc/sphinx/modules.rst doc/sphinx/publications.rst doc/sphinx/references.rst doc/sphinx/yade.eudoxos.rst doc/sphinx/yade.export.rst doc/sphinx/yade.linterpolation.rst doc/sphinx/yade.log.rst doc/sphinx/yade.pack.rst doc/sphinx/yade.plot.rst doc/sphinx/yade.post2d.rst doc/sphinx/yade.qt.rst doc/sphinx/yade.timing.rst doc/sphinx/yade.utils.rst doc/sphinx/yade.wrapper.rst doc/sphinx/yade.ymport.rst doc/sphinx/yade.polyhedra_utils.rst doc/sphinx/yade.gridpfacet.rst .kdev4/ *.kdev4 *.pyc *~ .idea trunk.iml .vscode Session??.vim *.swp trunk-2018.02b/.mailmap000066400000000000000000000054701324306050200146150ustar00rootroot00000000000000# sorted by last name # Robert Caulk robcaulk # Bruno Chareyre Bruno Chareyre Bruno Chareyre bchareyre # Jerome Duriez Jerome Duriez Jerome Duriez jduriez Jerome Duriez jduriez Jerome Duriez jduriez # Alexander Eulitz Kubeu # Anton Gladky Anton Gladky # Christian Jakob christian # François Kneib Francois Kneib François Kneib François Kneib François Kneib Francois François Kneib françois # Janek Kozicki Janek Kozicki Janek Kozicki Janek Kozicki # Donia Marzougui Donia Donia Marzougui Donia Donia Marzougui Donia Marzougui Donia Marzougui dmarzougui # Raphaël Maurin Raphaël Maurin Raphaël Maurin raphm1 # Luc Scholtes Luc Scholtes lucScholtes Luc Scholtes scholtes Luc Scholtes luc scholtes # Luc Sibille Luc Sibille Luc Sibille lsibille # Thomas Sweijen T Sweijen # Jan Stránský Jan Stransky Jan Stránský Jan Stránský <_honzik@centrum.cz> Jan Stránský Jan Stránský # Chao Yuan cyuan Chao Yuan cyuanLaptop Chao Yuan Thinkpad trunk-2018.02b/.travis.yml000066400000000000000000000023751324306050200153060ustar00rootroot00000000000000language: cpp dist: trusty compiler: - gcc virtualenv: system_site_packages: true notifications: email: recipients: - yade-dev@lists.launchpad.net - gladk@debian.org on_success: change on_failure: always before_script: - sudo bash -c 'echo "deb http://www.yade-dem.org/packages/ trusty/" >> /etc/apt/sources.list' - wget -O - http://www.yade-dem.org/packages/yadedev_pub.gpg | sudo apt-key add - - sudo apt-get update -qq - sudo apt-get install -y cmake debhelper freeglut3-dev help2man ipython libboost-all-dev libbz2-dev libcgal-dev libeigen3-dev libgl1-mesa-dev libgts-dev libloki-dev libmetis-dev libopenblas-dev libqglviewer-dev libsuitesparse-dev libvtk5-dev libxi-dev libxmu-dev pyqt4-dev-tools python-all-dev python-argparse python-gts python-imaging python-matplotlib python-minieigen python-numpy python-qt4 python-support python-tk python-xlib zlib1g-dev dvipng graphviz libjs-jquery lmodern python-bibtex python-sphinx texlive-fonts-recommended texlive-latex-extra texlive-latex-recommended texlive-pictures texlive-xetex tipa - mkdir build - cd build - cmake -DCMAKE_INSTALL_PREFIX=./../inst -DSUFFIX=-trunk .. script: - make - make install - ./../inst/bin/yade-trunk --test - ./../inst/bin/yade-trunk --check trunk-2018.02b/CMakeLists.txt000066400000000000000000000754531324306050200157440ustar00rootroot00000000000000# The YADE has the following parameters to configure: # CMAKE_INSTALL_PREFIX: path, where Yade will be installed (/usr/local by default) # LIBRARY_OUTPUT_PATH: path to install libraries (lib by default) # DEBUG: compile in debug-mode (OFF by default) # CMAKE_VERBOSE_MAKEFILE: output additional information during compiling (OFF by default) # SUFFIX: suffix, added after binary-names (version number by default) # NOSUFFIX: do not add a suffix after binary-name (OFF by default) # YADE_VERSION: explicitely set version number (is defined from git-directory by default) # ENABLE_GUI: enable GUI option (ON by default) # ENABLE_CGAL: enable CGAL option (ON by default) # ENABLE_VTK: enable VTK-export option (ON by default) # ENABLE_OPENMP: enable OpenMP-parallelizing option (ON by default) # ENABLE_GTS: enable GTS-option (ON by default) # ENABLE_GL2PS: enable GL2PS-option (ON by default) # ENABLE_LINSOLV: enable LINSOLV-option (ON by default) # ENABLE_PFVFLOW: enable PFVFLOW-option, FlowEngine (ON by default) # ENABLE_TWOPHASEFLOW: enable TWOPHASEFLOW-option, TwoPhaseFlowEngine (ON by default) # ENABLE_LBMFLOW: enable LBMFLOW-option, LBM_ENGINE (ON by default) # ENABLE_SPH: enable SPH-option, Smoothed Particle Hydrodynamics (OFF by default) # ENABLE_LIQMIGRATION: enable LIQMIGRATION-option, see [Mani2013] for details (OFF by default) # ENABLE_MASK_ARBITRARY: enable MASK_ARBITRARY option (OFF by default) # ENABLE_PROFILING: enable profiling, e.g. shows some more metrics, which can define bottlenecks of the code (OFF by default) # ENABLE_POTENTIAL_PARTICLES: enable potential particles option (OFF by default) # ENABLE_DEFORM: enable constant volume deformation engine (OFF by default) # ENABLE_OAR: generate a script for oar-based task scheduler (OFF by default) # runtimePREFIX: used for packaging, when install directory is not the same is runtime directory (/usr/local by default) # CHUNKSIZE: set >1, if you want several sources to be compiled at once. Increases compilation speed and RAM-consumption during it (1 by default) # VECTORIZE: enables vectorization and alignment in Eigen3 library, experimental (OFF by default) # USE_QT5: use QT5 for GUI (ON by default) project(Yade C CXX) cmake_minimum_required(VERSION 2.8.11) set(CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cMake") INCLUDE(FindPythonInterp) INCLUDE(FindOpenMP) INCLUDE(FindQt4) INCLUDE(FindPkgConfig) INCLUDE(GetVersion) INCLUDE(FindOpenGL) INCLUDE(FindGTS) INCLUDE(FindGL2PS) INCLUDE(FindCGAL) INCLUDE(FindNumPy) INCLUDE(FindLoki) INCLUDE(FindPythonModule) INCLUDE(GNUInstallDirs) INCLUDE_DIRECTORIES (${CMAKE_SOURCE_DIR}) #=========================================================== # HACK!!! If the version of gcc is 4.8 or greater, we add -ftrack-macro-expansion=0 # and -save-temps into compiler to reduce the memory consumption during compilation. # See http://bugs.debian.org/726009 for more information # Can be removed later, if gcc fixes its regression # Taken from http://stackoverflow.com/questions/4058565/check-gcc-minor-in-cmake EXECUTE_PROCESS(COMMAND ${CMAKE_C_COMPILER} -dumpversion OUTPUT_VARIABLE GCC_VERSION) IF (GCC_VERSION VERSION_GREATER 4.8 OR GCC_VERSION VERSION_EQUAL 4.8) MESSAGE(STATUS "GCC Version >= 4.8. Adding -ftrack-macro-expansion=0 and -save-temps") ADD_DEFINITIONS("-ftrack-macro-expansion=0 -save-temps") ENDIF() #=========================================================== IF ("${CMAKE_CXX_COMPILER} ${CMAKE_CXX_COMPILER_ARG1}" MATCHES ".*clang") ADD_DEFINITIONS("-ftemplate-depth-512 -fstack-protector ") ELSE() IF (GCC_VERSION VERSION_GREATER 4.9 OR GCC_VERSION VERSION_EQUAL 4.9) MESSAGE(STATUS "GCC Version >= 4.9. Adding -fstack-protector-strong") ADD_DEFINITIONS("-fstack-protector-strong") ELSE() ADD_DEFINITIONS("-fstack-protector") ENDIF() ENDIF() SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fPIC -O2 --param=ssp-buffer-size=4 -Wformat -Wformat-security -Werror=format-security -Wall -std=c++11") IF (DEBUG) SET(CMAKE_VERBOSE_MAKEFILE 1) SET(CMAKE_BUILD_TYPE Debug) ADD_DEFINITIONS("-DYADE_DEBUG -g") ELSE (DEBUG) ADD_DEFINITIONS("-DNDEBUG") ENDIF (DEBUG) #=========================================================== # Add possibility to use local boost installation (e.g. -DLocalBoost=1.46.1) IF ( NOT LocalBoost ) SET(LocalBoost "1.47.0") # Minimal required Boost version ENDIF ( NOT LocalBoost ) FIND_PACKAGE(Boost ${LocalBoost} COMPONENTS python thread filesystem iostreams regex serialization system date_time REQUIRED) INCLUDE_DIRECTORIES (${Boost_INCLUDE_DIRS}) # for checking purpose MESSAGE("-- Boost_VERSION: " ${Boost_VERSION}) MESSAGE("-- Boost_LIB_VERSION: " ${Boost_LIB_VERSION}) MESSAGE("-- Boost_INCLUDE_DIRS: " ${Boost_INCLUDE_DIRS}) MESSAGE("-- Boost_LIBRARIES: " ${Boost_LIBRARIES}) ADD_DEFINITIONS(-DBOOST_MATH_DISABLE_FLOAT128=1) #=========================================================== FIND_PACKAGE(NumPy REQUIRED) INCLUDE_DIRECTORIES(${NUMPY_INCLUDE_DIRS}) FIND_PACKAGE(Loki REQUIRED) INCLUDE_DIRECTORIES(${LOKI_INCLUDE_DIR}) FIND_PACKAGE(Eigen3 REQUIRED) FIND_PACKAGE(BZip2 REQUIRED) FIND_PACKAGE(ZLIB REQUIRED) SET(Python_ADDITIONAL_VERSIONS 2.7) FIND_PACKAGE(PythonLibs REQUIRED) INCLUDE_DIRECTORIES(${PYTHON_INCLUDE_PATH}) #=========================================================== SET(DEFAULT ON CACHE INTERNAL "Default value for enabled by default options") SET(LINKLIBS "") SET(CONFIGURED_FEATS "") SET(DISABLED_FEATS "") OPTION(ENABLE_VTK "Enable VTK" ${DEFAULT}) OPTION(ENABLE_OPENMP "Enable OpenMP" ${DEFAULT}) OPTION(ENABLE_GTS "Enable GTS" ${DEFAULT}) OPTION(ENABLE_GUI "Enable GUI" ${DEFAULT}) OPTION(ENABLE_CGAL "Enable CGAL" ${DEFAULT}) OPTION(ENABLE_GL2PS "Enable GL2PS" ${DEFAULT}) OPTION(ENABLE_LINSOLV "Enable direct solver for the flow engines (experimental)" ${DEFAULT}) OPTION(ENABLE_PFVFLOW "Enable one-phase flow engines" ${DEFAULT}) OPTION(ENABLE_TWOPHASEFLOW "Enable two-phase flow engines" ${DEFAULT}) OPTION(ENABLE_SPH "Enable SPH" OFF) OPTION(ENABLE_LBMFLOW "Enable LBM engine (very experimental)" ON) OPTION(ENABLE_LIQMIGRATION "Enable liquid control (very experimental), see [Mani2013] for details" OFF) OPTION(ENABLE_MASK_ARBITRARY "Enable arbitrary precision of bitmask variables (only Body::groupMask yet implemented) (experimental). Use -DMASK_ARBITRARY_SIZE=int to set number of used bits (256 by default)" OFF) OPTION(ENABLE_PROFILING "Enable profiling, e.g. shows some more metrics, which can define bottlenecks of the code (OFF by default)" OFF) OPTION(ENABLE_POTENTIAL_PARTICLES "Enable potential particles" OFF) OPTION(ENABLE_OAR "Generate script for oar-based task scheduler" OFF) OPTION(USE_QT5 "USE Qt5 for GUI" ON) OPTION(ENABLE_POTENTIAL_BLOCKS "Enable PotentialBlocks" OFF) OPTION(ENABLE_DEFORM "Enable Deformation Engine" OFF) OPTION(CHOLMOD_GPU "Enable GPU acceleration flow engine direct solver (experimental)" OFF) #=========================================================== # Use Eigen3 by default IF (EIGEN3_FOUND) INCLUDE_DIRECTORIES(${EIGEN3_INCLUDE_DIR}) MESSAGE(STATUS "Found Eigen3, version: ${EIGEN3_VERSION}") # Minimal required version 3.2.1 IF ((${EIGEN3_MAJOR_VERSION} LESS 2) OR ((${EIGEN3_MAJOR_VERSION} EQUAL 2) AND (${EIGEN3_MINOR_VERSION} LESS 1))) MESSAGE(FATAL_ERROR "Minimal required Eigen3 version is 3.2.1, please update Eigen3!") ENDIF ((${EIGEN3_MAJOR_VERSION} LESS 2) OR ((${EIGEN3_MAJOR_VERSION} EQUAL 2) AND (${EIGEN3_MINOR_VERSION} LESS 1))) IF (NOT VECTORIZE) MESSAGE(STATUS "Disable vectorization") ADD_DEFINITIONS("-DEIGEN_DONT_VECTORIZE -DEIGEN_DONT_ALIGN -DEIGEN_DISABLE_UNALIGNED_ARRAY_ASSERT") ELSE (NOT VECTORIZE) MESSAGE(STATUS "Enable vectorization") ENDIF (NOT VECTORIZE) ENDIF(EIGEN3_FOUND) #=========================================================== INCLUDE_DIRECTORIES(${BZIP2_INCLUDE_DIR}) INCLUDE_DIRECTORIES(${ZLIB_INCLUDE_DIRS}) SET(LINKLIBS "${LINKLIBS};${BZIP2_LIBRARIES};${ZLIB_LIBRARIES};") #=========================================================== IF((Boost_MAJOR_VERSION EQUAL 1) OR (Boost_MAJOR_VERSION GREATER 1) AND ((Boost_MINOR_VERSION EQUAL 53) OR (Boost_MINOR_VERSION GREATER 53))) SET(CONFIGURED_FEATS "${CONFIGURED_FEATS} Odeint") ADD_DEFINITIONS("-DYADE_ODEINT") ELSE((Boost_MAJOR_VERSION EQUAL 1) OR (Boost_MAJOR_VERSION GREATER 1) AND ((Boost_MINOR_VERSION EQUAL 53) OR (Boost_MINOR_VERSION GREATER 53))) SET(DISABLED_FEATS "${DISABLED_FEATS} Odeint") MESSAGE(STATUS "Boost Odeint can be enabled, only if Boost>=1.53 is used") ENDIF((Boost_MAJOR_VERSION EQUAL 1) OR (Boost_MAJOR_VERSION GREATER 1) AND ((Boost_MINOR_VERSION EQUAL 53) OR (Boost_MINOR_VERSION GREATER 53))) #=========================================================== IF(ENABLE_VTK) FIND_PACKAGE(VTK COMPONENTS vtkCommonCore vtkIOImage vtkIOXML vtkFiltersCore vtkImagingCore vtkRenderingCore vtkImagingGeneral vtkImagingHybrid) IF(VTK_FOUND) INCLUDE_DIRECTORIES(${VTK_INCLUDE_DIRS}) LINK_DIRECTORIES( ${VTK_LIBRARY_DIRS} ) SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -DYADE_VTK") MESSAGE(STATUS "Found VTK") SET(CONFIGURED_FEATS "${CONFIGURED_FEATS} VTK") ELSE(VTK_FOUND) MESSAGE(STATUS "VTK NOT found") SET(ENABLE_VTK OFF) SET(DISABLED_FEATS "${DISABLED_FEATS} VTK") ENDIF(VTK_FOUND) ELSE(ENABLE_VTK) SET(DISABLED_FEATS "${DISABLED_FEATS} VTK") ENDIF(ENABLE_VTK) #=========================================================== IF(ENABLE_OPENMP) FIND_PACKAGE(OpenMP) IF(OPENMP_FOUND) SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -DYADE_OPENMP ${OpenMP_CXX_FLAGS}") MESSAGE(STATUS "Found OpenMP") SET(CONFIGURED_FEATS "${CONFIGURED_FEATS} OpenMP") ELSE(OPENMP_FOUND) MESSAGE(STATUS "OpenMP NOT found") SET(ENABLE_OPENMP OFF) SET(DISABLED_FEATS "${DISABLED_FEATS} OPENMP") ENDIF(OPENMP_FOUND) ELSE(ENABLE_OPENMP) SET(DISABLED_FEATS "${DISABLED_FEATS} OPENMP") ENDIF(ENABLE_OPENMP) #=========================================================== IF(ENABLE_GTS) FIND_PACKAGE(GTS) FIND_PACKAGE(glib2) IF(GTS_FOUND AND GLIB2_FOUND) SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -DYADE_GTS ${CMAKE_GTS_CXX_FLAGS}") SET(CMAKE_LD_FLAGS "${CMAKE_LD_FLAGS} ${GTS_LIBRARIES}") INCLUDE_DIRECTORIES(${GTS_INCLUDE_DIR}) INCLUDE_DIRECTORIES(${GLIB2_INCLUDE_DIRS}) MESSAGE(STATUS "Found GTS") SET(CONFIGURED_FEATS "${CONFIGURED_FEATS} GTS") ELSE(GTS_FOUND AND GLIB2_FOUND) MESSAGE(STATUS "GTS NOT found") SET(DISABLED_FEATS "${DISABLED_FEATS} GTS") SET(ENABLE_GTS OFF) ENDIF(GTS_FOUND AND GLIB2_FOUND) ELSE(ENABLE_GTS) SET(DISABLED_FEATS "${DISABLED_FEATS} GTS") ENDIF(ENABLE_GTS) #=========================================================== IF(ENABLE_GUI) FIND_PACKAGE(OpenGL) FIND_PACKAGE(GLUT) FIND_PACKAGE(glib2) IF(USE_QT5) FIND_PACKAGE(QGLVIEWER-qt5 REQUIRED) FIND_PACKAGE(Qt5 CONFIG REQUIRED Widgets Xml OpenGL) SET(CMAKE_AUTOMOC ON) FIND_PACKAGE(Qt5Widgets) IF(Qt5Widgets_FOUND AND OPENGL_FOUND AND GLUT_FOUND AND GLIB2_FOUND AND QGLVIEWER_FOUND) SET(GUI_LIBS ${GLUT_LIBRARY} ${QGLVIEWER_LIBRARIES}) SET(GUI_SRC_LIB "lib/opengl/GLUtils.cpp") SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -DYADE_OPENGL -DYADE_QT5") MESSAGE(STATUS "Found GUI-Qt5-LIBS") SET(CONFIGURED_FEATS "${CONFIGURED_FEATS} GUI-Qt5") ELSE(Qt5Widgets_FOUND AND OPENGL_FOUND AND GLUT_FOUND AND GLIB2_FOUND AND QGLVIEWER_FOUND) MESSAGE(STATUS "GUI-Qt5-LIBS NOT found") SET(DISABLED_FEATS "${DISABLED_FEATS} GUI-Qt5") SET(ENABLE_GUI OFF) ENDIF(Qt5Widgets_FOUND AND OPENGL_FOUND AND GLUT_FOUND AND GLIB2_FOUND AND QGLVIEWER_FOUND) ELSE(USE_QT5) # Use Qt4 FIND_PACKAGE(QGLVIEWER-qt4 REQUIRED) FIND_PACKAGE(Qt4 COMPONENTS QtCore QtGui QtOpenGL) IF(QT4_FOUND AND OPENGL_FOUND AND GLUT_FOUND AND GLIB2_FOUND AND QGLVIEWER_FOUND) SET(GUI_LIBS ${GLUT_LIBRARY} ${OPENGL_LIBRARY} ${QGLVIEWER_LIBRARIES}) SET(GUI_SRC_LIB "lib/opengl/GLUtils.cpp") SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -DYADE_OPENGL -DYADE_QT4") INCLUDE_DIRECTORIES(${GLIB2_INCLUDE_DIRS}) INCLUDE_DIRECTORIES(${QT_INCLUDES}) MESSAGE(STATUS "Found GUI-LIBS") SET(CONFIGURED_FEATS "${CONFIGURED_FEATS} GUI") ELSE(QT4_FOUND AND OPENGL_FOUND AND GLUT_FOUND AND GLIB2_FOUND AND QGLVIEWER_FOUND) MESSAGE(STATUS "GUI-LIBS NOT found") SET(DISABLED_FEATS "${DISABLED_FEATS} GUI") SET(ENABLE_GUI OFF) ENDIF(QT4_FOUND AND OPENGL_FOUND AND GLUT_FOUND AND GLIB2_FOUND AND QGLVIEWER_FOUND) ENDIF(USE_QT5) ELSE(ENABLE_GUI) SET(DISABLED_FEATS "${DISABLED_FEATS} GUI") ENDIF(ENABLE_GUI) #=========================================================== # This one will automatically enable CGAL IF(ENABLE_PFVFLOW OR ENABLE_TWOPHASEFLOW) IF (NOT ENABLE_CGAL) MESSAGE(STATUS "PFVFLOW and TWOPHASEFLOW depends on CGAL, attempting to turn ENABLE_CGAL ON") SET(ENABLE_CGAL ON) ENDIF (NOT ENABLE_CGAL) ENDIF(ENABLE_PFVFLOW OR ENABLE_TWOPHASEFLOW) #=========================================================== IF(ENABLE_CGAL) INCLUDE(FindGMP) FIND_PACKAGE(CGAL) FIND_PACKAGE(GMP) IF(CGAL_FOUND AND GMP_FOUND AND (NOT("${CMAKE_CXX_COMPILER} ${CMAKE_CXX_COMPILER_ARG1}" MATCHES ".*clang"))) #Check for clang should be removed, when CGAL will be compilable by clang FILE(GLOB CGAL_SRC_LIB "lib/triangulation/*.cpp") SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${CGAL_DEFINITIONS} -DYADE_CGAL") SET(LINKLIBS "${LINKLIBS};${CGAL_LIBRARIES};${GMP_LIBRARIES};${GMPXX_LIBRARIES};") MESSAGE(STATUS "Found CGAL") SET(CONFIGURED_FEATS "${CONFIGURED_FEATS} CGAL") ADD_DEFINITIONS("-DCGAL_DISABLE_ROUNDING_MATH_CHECK -frounding-math") IF(ENABLE_PFVFLOW) SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -DFLOW_ENGINE") SET(CONFIGURED_FEATS "${CONFIGURED_FEATS} PFVFLOW") ELSE(ENABLE_PFVFLOW) SET(DISABLED_FEATS "${DISABLED_FEATS} PFVFLOW") ENDIF(ENABLE_PFVFLOW) IF(ENABLE_TWOPHASEFLOW) SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -DTWOPHASEFLOW") SET(CONFIGURED_FEATS "${CONFIGURED_FEATS} TWOPHASEFLOW") ELSE(ENABLE_TWOPHASEFLOW) SET(DISABLED_FEATS "${DISABLED_FEATS} TWOPHASEFLOW") ENDIF(ENABLE_TWOPHASEFLOW) ELSE(CGAL_FOUND AND GMP_FOUND AND (NOT("${CMAKE_CXX_COMPILER} ${CMAKE_CXX_COMPILER_ARG1}" MATCHES ".*clang"))) MESSAGE(STATUS "CGAL NOT found") SET(DISABLED_FEATS "${DISABLED_FEATS} CGAL") SET(ENABLE_CGAL OFF) IF(ENABLE_PFVFLOW) SET(DISABLED_FEATS "${DISABLED_FEATS} PFVFLOW TWOPHASEFLOW") MESSAGE(STATUS "CGAL NOT found: PFVFLOW and TWOPHASEFLOW disabled") ENDIF(ENABLE_PFVFLOW) ENDIF(CGAL_FOUND AND GMP_FOUND AND (NOT("${CMAKE_CXX_COMPILER} ${CMAKE_CXX_COMPILER_ARG1}" MATCHES ".*clang"))) ELSE(ENABLE_CGAL) SET(DISABLED_FEATS "${DISABLED_FEATS} CGAL") ENDIF(ENABLE_CGAL) #=========================================================== IF(ENABLE_LINSOLV) FIND_PACKAGE(Cholmod) FIND_PACKAGE(OpenBlas) FIND_PACKAGE(Metis) IF(CHOLMOD_FOUND AND OPENBLAS_FOUND AND METIS_FOUND AND CGAL_FOUND) SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${CGAL_DEFINITIONS} -DLINSOLV -DFLOW_ENGINE") SET(LINKLIBS "${LINKLIBS};${CHOLMOD_LIBRARIES};${AMD_LIBRARY};${CAMD_LIBRARY};${COLAMD_LIBRARY};${CCOLAMD_LIBRARY};${OPENBLAS_LIBRARY};${METIS_LIBRARY};${SUITESPARSE_LIBRARY}") INCLUDE_DIRECTORIES(${METIS_INCLUDE_DIR} ${CHOLMOD_INCLUDE_DIR}) MESSAGE(STATUS "Found Cholmod in " ${CHOLMOD_LIBRARIES}) MESSAGE(STATUS "Found OpenBlas in " ${OPENBLAS_LIBRARY}) MESSAGE(STATUS "Found Metis in " ${METIS_LIBRARY}) SET(CONFIGURED_FEATS "${CONFIGURED_FEATS} LINSOLV") IF(CHOLMOD_GPU) FIND_PACKAGE(CuBlas) FIND_PACKAGE(Lapack) IF(CUBLAS_FOUND AND LAPACK_FOUND) ADD_DEFINITIONS("-DPFV_GPU") SET(LINKLIBS "${LINKLIBS};${CUBLAS_LIBRARY};${CUDART_LIBRARY};${LAPACK_LIBRARY}") MESSAGE(STATUS "Found CuBlas in " ${CUBLAS_LIBRARY}) MESSAGE(STATUS "Found Lapack in " ${LAPACK_LIBRARY}) SET(CONFIGURED_FEATS "${CONFIGURED_FEATS} CHOLMOD_GPU") ELSE(CUBLAS_FOUND AND LAPACK_FOUND) MESSAGE(STATUS "Missing dependency for CHOLMOD_GPU, disabled") SET(DISABLED_FEATS "${DISABLED_FEATS} CHOLMOD_GPU") SET(CHOLMOD_GPU OFF) ENDIF(CUBLAS_FOUND AND LAPACK_FOUND) ELSE(CHOLMOD_GPU) SET(DISABLED_FEATS "${DISABLED_FEATS} CHOLMOD_GPU") ENDIF(CHOLMOD_GPU) ELSE(CHOLMOD_FOUND AND OPENBLAS_FOUND AND METIS_FOUND AND CGAL_FOUND) MESSAGE(STATUS "Missing dependency for LINSOLV, disabled") SET(DISABLED_FEATS "${DISABLED_FEATS} LINSOLV") SET(ENABLE_LINSOLV OFF) ENDIF(CHOLMOD_FOUND AND OPENBLAS_FOUND AND METIS_FOUND AND CGAL_FOUND) ELSE(ENABLE_LINSOLV) SET(DISABLED_FEATS "${DISABLED_FEATS} LINSOLV") ENDIF(ENABLE_LINSOLV) #=============================================== IF(ENABLE_SPH) ADD_DEFINITIONS("-DYADE_SPH") SET(CONFIGURED_FEATS "${CONFIGURED_FEATS} SPH") ELSE(ENABLE_SPH) SET(DISABLED_FEATS "${DISABLED_FEATS} SPH") ENDIF(ENABLE_SPH) #=============================================== IF(ENABLE_DEFORM) ADD_DEFINITIONS("-DYADE_DEFORM") SET(CONFIGURED_FEATS "${CONFIGURED_FEATS} DEFORM") ELSE(ENABLE_DEFORM) SET(DISABLED_FEATS "${DISABLED_FEATS} DEFORM") ENDIF(ENABLE_DEFORM) #=============================================== IF(ENABLE_LIQMIGRATION) ADD_DEFINITIONS("-DYADE_LIQMIGRATION") SET(CONFIGURED_FEATS "${CONFIGURED_FEATS} LIQMIGRATION") ELSE(ENABLE_LIQMIGRATION) SET(DISABLED_FEATS "${DISABLED_FEATS} LIQMIGRATION") ENDIF(ENABLE_LIQMIGRATION) #=============================================== IF(ENABLE_GL2PS) FIND_PACKAGE(GL2PS) IF(GL2PS_FOUND) INCLUDE_DIRECTORIES(${GL2PS_INCLUDE_DIR}) SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -DYADE_GL2PS") SET(LINKLIBS "${LINKLIBS};${GL2PS_LIBRARIES};") MESSAGE(STATUS "Found GL2PS") SET(CONFIGURED_FEATS "${CONFIGURED_FEATS} GL2PS") ELSE(GL2PS_FOUND) MESSAGE(STATUS "GL2PS NOT found") SET(DISABLED_FEATS "${DISABLED_FEATS} GL2PS") SET(ENABLE_GL2PS OFF) ENDIF(GL2PS_FOUND) ELSE(ENABLE_GL2PS) SET(DISABLED_FEATS "${DISABLED_FEATS} GL2PS") ENDIF(ENABLE_GL2PS) INCLUDE_DIRECTORIES(${PYTHON_INCLUDE_DIRS}) INCLUDE_DIRECTORIES(${CMAKE_BINARY_DIR}) #=========================================================== IF(ENABLE_LBMFLOW) SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -DLBM_ENGINE") SET(CONFIGURED_FEATS "${CONFIGURED_FEATS} LBMFLOW") MESSAGE("LBMFLOW is still experimental, building and running LBM engine are at your own risk!") ELSE(ENABLE_LBMFLOW) SET(DISABLED_FEATS "${DISABLED_FEATS} LBMFLOW") ENDIF(ENABLE_LBMFLOW) #=============================================== IF(ENABLE_MASK_ARBITRARY) ADD_DEFINITIONS("-DYADE_MASK_ARBITRARY") SET(CONFIGURED_FEATS "${CONFIGURED_FEATS} MASK_ARBITRARY") IF(NOT MASK_ARBITRARY_SIZE) SET(MASK_ARBITRARY_SIZE "256") ENDIF(NOT MASK_ARBITRARY_SIZE) ADD_DEFINITIONS(-DYADE_MASK_ARBITRARY_SIZE=${MASK_ARBITRARY_SIZE}) MESSAGE("MASK_ARBITRARY_SIZE = ${MASK_ARBITRARY_SIZE}") ELSE(ENABLE_MASK_ARBITRARY) SET(DISABLED_FEATS "${DISABLED_FEATS} MASK_ARBITRARY") ENDIF(ENABLE_MASK_ARBITRARY) #=========================================================== IF(ENABLE_PROFILING) SET(CONFIGURED_FEATS "${CONFIGURED_FEATS} PROFILING") ADD_DEFINITIONS("-DUSE_TIMING_DELTAS -DISC_TIMING") ELSE(ENABLE_PROFILING) SET(DISABLED_FEATS "${DISABLED_FEATS} PROFILING") ENDIF(ENABLE_PROFILING) #=========================================================== IF(ENABLE_POTENTIAL_PARTICLES) FIND_PACKAGE(OpenBlas REQUIRED) FIND_PACKAGE(LAPACK REQUIRED) IF(OPENBLAS_FOUND AND LAPACK_FOUND) ADD_DEFINITIONS("-DYADE_POTENTIAL_PARTICLES") SET(LINKLIBS "${LINKLIBS};${OPENBLAS_LIBRARY};${LAPACK_LIBRARY}") MESSAGE(STATUS "Found OpenBlas") MESSAGE(STATUS "Found Lapack") SET(CONFIGURED_FEATS "${CONFIGURED_FEATS} PotentialParticles") ELSE(OPENBLAS_FOUND AND LAPACK_FOUND) MESSAGE(STATUS "Missing dependency for PotentialParticles, disabled") SET(DISABLED_FEATS "${DISABLED_FEATS} PotentialParticles") SET(ENABLE_POTENTIAL_PARTICLES OFF) ENDIF(OPENBLAS_FOUND AND LAPACK_FOUND) ELSE(ENABLE_POTENTIAL_PARTICLES) SET(DISABLED_FEATS "${DISABLED_FEATS} PotentialParticles") ENDIF(ENABLE_POTENTIAL_PARTICLES) #=========================================================== #=========================================================== IF(ENABLE_POTENTIAL_BLOCKS) INCLUDE(FindCLP) FIND_PACKAGE(OpenBlas REQUIRED) FIND_PACKAGE(LAPACK REQUIRED) IF(CLP_FOUND AND OPENBLAS_FOUND AND LAPACK_FOUND) ADD_DEFINITIONS("-DYADE_POTENTIAL_BLOCKS") INCLUDE_DIRECTORIES(${CLP_INCLUDE_DIR} ${CLP2_INCLUDE_DIR} ) SET(LINKLIBS "${LINKLIBS};${CLP_LIBRARY};${CLP2_LIBRARY};${CLP3_LIBRARY};${OPENBLAS_LIBRARY};${LAPACK_LIBRARY}") MESSAGE(STATUS "Found CLP") SET(CONFIGURED_FEATS "${CONFIGURED_FEATS} PotentialBlocks") ELSE(CLP_FOUND AND OPENBLAS_FOUND AND LAPACK_FOUND) MESSAGE(STATUS "CLP NOT found") SET(DISABLED_FEATS "${DISABLED_FEATS} PotentialBlocks") SET(ENABLE_POTENTIAL_BLOCKS OFF) ENDIF(CLP_FOUND AND OPENBLAS_FOUND AND LAPACK_FOUND) ELSE(ENABLE_POTENTIAL_BLOCKS) SET(DISABLED_FEATS "${DISABLED_FEATS} PotentialBlocks") ENDIF(ENABLE_POTENTIAL_BLOCKS) #=========================================================== IF (INSTALL_PREFIX) SET(CMAKE_INSTALL_PREFIX ${INSTALL_PREFIX}) MESSAGE(WARNING "Use CMAKE_INSTALL_PREFIX option instead of INSTALL_PREFIX! It will be removed soon.") ENDIF (INSTALL_PREFIX) IF (CMAKE_INSTALL_PREFIX) MESSAGE("Yade will be installed to ${CMAKE_INSTALL_PREFIX}") ELSE (CMAKE_INSTALL_PREFIX) MESSAGE("Yade will be installed to default path ${CMAKE_INSTALL_PREFIX}, if you want to override it use -DCMAKE_INSTALL_PREFIX option.") ENDIF (CMAKE_INSTALL_PREFIX) IF (NOT SUFFIX) SET (SUFFIX "-${YADE_VERSION}") ENDIF (NOT SUFFIX) IF(NOSUFFIX) #For packaging SET (SUFFIX "") ENDIF(NOSUFFIX) #For packaging IF(NOT LIBRARY_OUTPUT_PATH) #For packaging SET (LIBRARY_OUTPUT_PATH ${CMAKE_INSTALL_LIBDIR}) ENDIF(NOT LIBRARY_OUTPUT_PATH) #For packaging IF (NOT runtimePREFIX) SET (runtimePREFIX ${CMAKE_INSTALL_PREFIX}) ENDIF (NOT runtimePREFIX) MESSAGE (STATUS "Suffix is set to " ${SUFFIX}) MESSAGE (STATUS "LIBRARY_OUTPUT_PATH is set to " ${LIBRARY_OUTPUT_PATH}) MESSAGE (STATUS "runtimePREFIX is set to " ${runtimePREFIX}) #=========================================================== SET(YADE_LIB_PATH ${CMAKE_INSTALL_PREFIX}/${LIBRARY_OUTPUT_PATH}/yade${SUFFIX}) SET(YADE_EXEC_PATH ${CMAKE_INSTALL_PREFIX}/${CMAKE_INSTALL_BINDIR}) SET(YADE_PY_PATH ${YADE_LIB_PATH}/py) SET(YADE_DOC_PATH ${CMAKE_INSTALL_PREFIX}/${CMAKE_INSTALL_DATAROOTDIR}/doc/yade${SUFFIX}) SET(YADE_MAN_PATH ${CMAKE_INSTALL_PREFIX}/${CMAKE_INSTALL_MANDIR}) SET(CMAKE_SKIP_BUILD_RPATH FALSE) SET(CMAKE_BUILD_WITH_INSTALL_RPATH FALSE) SET(CMAKE_INSTALL_RPATH "${YADE_LIB_PATH};${YADE_PY_PATH};${YADE_PY_PATH}/yade/;${YADE_PY_PATH}/yade/qt;${YADE_PY_PATH}/gts") SET(CMAKE_INSTALL_RPATH_USE_LINK_PATH TRUE) #=========================================================== IF(ENABLE_GUI) ADD_SUBDIRECTORY("${CMAKE_SOURCE_DIR}/gui") ENDIF(ENABLE_GUI) ADD_SUBDIRECTORY("${CMAKE_SOURCE_DIR}/py") #=========================================================== FILE(GLOB SRC_CORE "core/*.cpp") FILE(GLOB_RECURSE SRC_PKG "pkg/*.cpp") FILE(GLOB SRC_LIB "lib/*.cpp") SET(SRC_LIB "${SRC_LIB};lib/base/Math.cpp;lib/factory/ClassFactory.cpp;lib/factory/DynLibManager.cpp") SET(SRC_LIB "${SRC_LIB};lib/serialization/Serializable.cpp;lib/pyutil/gil.cpp;core/main/pyboot.cpp;${GUI_SRC_LIB};${CGAL_SRC_LIB}") IF(ENABLE_POTENTIAL_PARTICLES) SET(SRC_LIB "${SRC_LIB};lib/computational-geometry/MarchingCube.cpp") ENDIF(ENABLE_POTENTIAL_PARTICLES) #=========================================================== IF (CHUNKSIZE) INCLUDE(CombineSources) COMBINE_SOURCES(${CMAKE_BINARY_DIR}/core "${SRC_CORE}" ${CHUNKSIZE}) FILE(GLOB SRC_CORE_COMBINED "${CMAKE_BINARY_DIR}/core.*.cpp") COMBINE_SOURCES(${CMAKE_BINARY_DIR}/pkg "${SRC_PKG}" ${CHUNKSIZE}) FILE(GLOB SRC_PKG_COMBINED "${CMAKE_BINARY_DIR}/pkg.*.cpp") COMBINE_SOURCES(${CMAKE_BINARY_DIR}/lib "${SRC_LIB}" ${CHUNKSIZE}) FILE(GLOB SRC_LIB_COMBINED "${CMAKE_BINARY_DIR}/lib.*.cpp") ADD_LIBRARY(yade SHARED ${SRC_LIB_COMBINED} ${SRC_CORE_COMBINED} ${SRC_PKG_COMBINED}) ELSE (CHUNKSIZE) ADD_LIBRARY(yade SHARED ${SRC_CORE} ${SRC_PKG} ${SRC_LIB}) ENDIF (CHUNKSIZE) #=========================================================== find_python_module(minieigen REQUIRED) find_python_module(Tkinter REQUIRED) #=========================================================== ADD_LIBRARY(boot SHARED ${CMAKE_CURRENT_SOURCE_DIR}/core/main/pyboot.cpp) SET_TARGET_PROPERTIES(boot PROPERTIES PREFIX "" LINK_FLAGS "-Wl,--as-needed" ) TARGET_LINK_LIBRARIES(yade ${Boost_LIBRARIES} ${PYTHON_LIBRARIES} ${LINKLIBS} -lrt) SET_TARGET_PROPERTIES(yade PROPERTIES LINK_FLAGS "-Wl,--as-needed" ) TARGET_LINK_LIBRARIES(boot yade) IF(ENABLE_VTK) IF(${VTK_MAJOR_VERSION} GREATER 5) TARGET_LINK_LIBRARIES(yade ${VTK_LIBRARIES}) ADD_DEFINITIONS("-DYADE_VTK6") MESSAGE(STATUS "VTK version >5 is found") ELSE(${VTK_MAJOR_VERSION} GREATER 5) TARGET_LINK_LIBRARIES(yade vtkHybrid) ENDIF(${VTK_MAJOR_VERSION} GREATER 5) ENDIF(ENABLE_VTK) IF(ENABLE_GUI) TARGET_LINK_LIBRARIES(yade _GLViewer ${GUI_LIBS}) ENDIF(ENABLE_GUI) #==================================== #Back compatibility with scons SET (realVersion ${YADE_VERSION}) SET (version ${YADE_VERSION}) SET (pyExecutable ${PYTHON_EXECUTABLE}) SET (profile "default") SET (sourceRoot "${CMAKE_CURRENT_SOURCE_DIR}") #==================================== CONFIGURE_FILE(core/main/yade-batch.in "${CMAKE_BINARY_DIR}/bins/yade${SUFFIX}-batch") IF(ENABLE_OAR) CONFIGURE_FILE(core/main/yade-oar.in "${CMAKE_BINARY_DIR}/bins/yade${SUFFIX}-oar") ENDIF(ENABLE_OAR) CONFIGURE_FILE(core/main/main.py.in "${CMAKE_BINARY_DIR}/bins/yade${SUFFIX}") CONFIGURE_FILE(py/config.py.in "${CMAKE_BINARY_DIR}/config.py") CONFIGURE_FILE(py/__init__.py.in "${CMAKE_BINARY_DIR}/__init__.py") #=========================================================== # Create header files for PFV from FlowEngine.hpp.in-template. # All @TEMPLATE_FLOW_NAME@ are replacing by a given names SET (TEMPLATE_FLOW_NAMES DFNFlowEngineT DummyFlowEngineT FlowEngineT FlowEngine_PeriodicInfo SoluteFlowEngineT UnsaturatedEngineT TwoPhaseFlowEngineT) FOREACH(TF ${TEMPLATE_FLOW_NAMES}) SET (TEMPLATE_FLOW_NAME ${TF}) CONFIGURE_FILE(pkg/pfv/FlowEngine.hpp.in "${CMAKE_BINARY_DIR}/pkg/pfv/FlowEngine_${TF}.hpp" @ONLY) CONFIGURE_FILE(pkg/pfv/FlowEngine.ipp.in "${CMAKE_BINARY_DIR}/pkg/pfv/FlowEngine_${TF}.ipp" @ONLY) ENDFOREACH(TF) INCLUDE_DIRECTORIES("${CMAKE_BINARY_DIR}/pkg/pfv/") #=========================================================== INSTALL(PROGRAMS "${CMAKE_BINARY_DIR}/bins/yade${SUFFIX}-batch" DESTINATION ${YADE_EXEC_PATH}/) IF(ENABLE_OAR) INSTALL(PROGRAMS "${CMAKE_BINARY_DIR}/bins/yade${SUFFIX}-oar" DESTINATION ${YADE_EXEC_PATH}/) ENDIF(ENABLE_OAR) INSTALL(PROGRAMS "${CMAKE_BINARY_DIR}/bins/yade${SUFFIX}" DESTINATION ${YADE_EXEC_PATH}/) INSTALL(FILES "${CMAKE_BINARY_DIR}/config.py" DESTINATION ${YADE_PY_PATH}/yade/) INSTALL(FILES "${CMAKE_BINARY_DIR}/__init__.py" DESTINATION ${YADE_PY_PATH}/yade/) FILE(GLOB filesPYChecks "${CMAKE_CURRENT_SOURCE_DIR}/scripts/checks-and-tests/checks/*.py") INSTALL(FILES ${filesPYChecks} DESTINATION ${YADE_PY_PATH}/yade/tests/checks) FILE(GLOB filesPYChecksData "${CMAKE_CURRENT_SOURCE_DIR}/scripts/checks-and-tests/checks/data/*") INSTALL(FILES ${filesPYChecksData} DESTINATION ${YADE_PY_PATH}/yade/tests/checks/data) INSTALL(FILES "${CMAKE_CURRENT_SOURCE_DIR}/doc/yade-logo-note.png" DESTINATION "${YADE_DOC_PATH}/img") INSTALL(TARGETS boot DESTINATION "${YADE_PY_PATH}/yade/") INSTALL(TARGETS yade DESTINATION ${YADE_LIB_PATH}) #=========================================================== MESSAGE(STATUS "===========================================================") MESSAGE(STATUS "Yade configured with following features:${CONFIGURED_FEATS}") MESSAGE(STATUS "Disabled features:${DISABLED_FEATS}") IF (DEBUG) MESSAGE(STATUS "Debug build") SET (debugbuild " (debug build)") ELSE (DEBUG) MESSAGE(STATUS "Optimized build") ENDIF (DEBUG) IF (CHUNKSIZE) MESSAGE(STATUS "CHUNKSIZE is set to " ${CHUNKSIZE}) ENDIF (CHUNKSIZE) MESSAGE(STATUS "===========================================================") #=========================================================== #Building doc ADD_CUSTOM_TARGET(doc) FILE(MAKE_DIRECTORY ${CMAKE_BINARY_DIR}/doc) FILE(MAKE_DIRECTORY ${CMAKE_BINARY_DIR}/doc/sphinx) ADD_CUSTOM_COMMAND( TARGET doc PRE_BUILD COMMAND rm -rf ${CMAKE_BINARY_DIR}/doc/sphinx/_build COMMAND cp -r ${CMAKE_CURRENT_SOURCE_DIR}/doc/* ${CMAKE_BINARY_DIR}/doc COMMAND PYTHONPATH=${CMAKE_BINARY_DIR}/doc/sphinx ${YADE_EXEC_PATH}/yade${SUFFIX} yadeSphinx.py WORKING_DIRECTORY ${CMAKE_BINARY_DIR}/doc/sphinx DEPENDS ${YADE_EXEC_PATH}/yade${SUFFIX} ) ADD_CUSTOM_COMMAND( TARGET doc POST_BUILD COMMAND xelatex Yade.tex COMMAND xelatex Yade.tex WORKING_DIRECTORY ${CMAKE_BINARY_DIR}/doc/sphinx/_build/latex DEPENDS ${YADE_EXEC_PATH}/yade${SUFFIX} ${CMAKE_BINARY_DIR}/doc/sphinx/_build/latex/Yade.tex ) ADD_CUSTOM_COMMAND( TARGET doc POST_BUILD COMMAND rm -rf ${YADE_DOC_PATH}/html COMMAND mv ${CMAKE_BINARY_DIR}/doc/sphinx/_build/html ${YADE_DOC_PATH}/html COMMAND mv ${CMAKE_BINARY_DIR}/doc/sphinx/_build/latex/Yade.pdf ${YADE_DOC_PATH}/ WORKING_DIRECTORY ${CMAKE_BINARY_DIR}/doc/sphinx/_build/latex DEPENDS ${YADE_EXEC_PATH}/yade${SUFFIX} ${CMAKE_BINARY_DIR}/doc/sphinx/_build/latex/Yade.tex ) ADD_CUSTOM_COMMAND( TARGET doc POST_BUILD COMMAND mv ${CMAKE_BINARY_DIR}/doc/sphinx/_build/epub/Yade.epub ${YADE_DOC_PATH}/ || true WORKING_DIRECTORY ${CMAKE_BINARY_DIR}/doc/sphinx/_build/latex DEPENDS ${YADE_EXEC_PATH}/yade${SUFFIX} ${CMAKE_BINARY_DIR}/doc/sphinx/_build/latex/Yade.tex ) #=========================================================== #Building manpage ADD_CUSTOM_TARGET(manpage) ADD_CUSTOM_COMMAND( TARGET manpage POST_BUILD COMMAND help2man ${YADE_EXEC_PATH}/yade${SUFFIX} > yade${SUFFIX}.1 COMMAND help2man ${YADE_EXEC_PATH}/yade${SUFFIX}-batch > yade${SUFFIX}-batch.1 COMMAND mkdir -p ${YADE_MAN_PATH} COMMAND mv *.1 ${YADE_MAN_PATH} WORKING_DIRECTORY ${CMAKE_BINARY_DIR} DEPENDS ${YADE_EXEC_PATH}/yade${SUFFIX} /usr/bin/help2man ) #=========================================================== #Execute standard checks ADD_CUSTOM_TARGET(check) ADD_CUSTOM_COMMAND( TARGET check POST_BUILD COMMAND ${YADE_EXEC_PATH}/yade${SUFFIX} --test COMMAND ${YADE_EXEC_PATH}/yade${SUFFIX} --check WORKING_DIRECTORY ${CMAKE_BINARY_DIR} DEPENDS ${YADE_EXEC_PATH}/yade${SUFFIX} ) #=========================================================== trunk-2018.02b/ChangeLog000066400000000000000000024653101324306050200147530ustar00rootroot00000000000000================================================== yade-2018.02b Tue, Feb 20 18:55:30 2018 +0100 Anton Gladky (4): Remove RELEASE file Do not globally import polyhedra_utils in ymport.py Simplify and fix the checkPolyhedraCrush Add RELEASE file Bruno Chareyre (1): fix cmake warning + effectively enable TWOPHASEFLOW Janek Kozicki (3): Update .gitignore and .mailmap Fix warnings that I have during compilaation. Initial fix for cgal 4.11. There are some things still to check: Jerome Duriez (4): Doc: typo (missing parenthesis) in Ig2_Sphere_Sphere_ScGeom Doc: precising IPhysFunctor docstring Doc: typo in Programmer s manual Doc: other typos in Programmer s manual Robert Caulk (2): Fix DEM-PFV-check.py failure. Enable CPU usage of direct cholmod solver (useSolver=4) Add support for older versions of SuiteSparse ================================================== yade-2018.02a Wed, Feb 7 20:06:30 2018 +0100 Anton Gladky (15): Remove RELEASE file. Initialize gui in IPython 5. Add dependency on python-pyqt5.qtsvg. Fix PDF-compilation with sphinx>=1.4.9 Remove comented lines in CMakeLists Test commit number two. Do not enable LINSOLV if CGAL not found Fix missing AND operator in prevoius commit Extend polyhedra breakage model by Mohr-Coulomb-Weibull failure criterion. Add examples for the PolyhedraBreak model Add one more Yade-Paper Update .gitignore Make newly added labels consistent with other labels. One more thesis, where Yade is mentioned Add RELEASE file Bruno Chareyre (15): Fix https://bugs.launchpad.net/yade/+bug/1666339 (thx Robert) +5 journal papers +2 journal papers + Christian Jakob's PhD in references Put brackets in the right place (thx Robert). +4 journal papers 1+ journal paper by V. Lapcevic Add editor to Yade workshop proceedings (me!) missing comma (sorry) turn ERROR into WARNING when ISCollider does not find spheres to determine verletDist +2 journal papers +2 journal papers +11 conference papers add missing year in a bibtex entry Fix kineticEnergy() for clumps Chao Yuan (6): add porosity in cell info. fix compile error. add functions to get pore throat radius (by cells or spheres). -split computeEffectiveRadius() for obtaining reff by pos and radius; -clean redundant functions. -fix setPoreThroatRadius -add setPoreBodyRadius. Francois Kneib (1): Fix typo in installation.rst doc. Jan Stránský (7): fixed a typo in micro-stress example replaced L3Geom by ScGeom in tutorials Added subengines of CombinedKinematicEngine to labeled engines fixed a bug in ScGeom::getIncidentVel with avoidGranularRatcheting=False and O.periodic=True Updated Body::intrs doc, fixed typo in comment fixed a bug in ymport.UNVReader fixed one Polyhedra python method name Jerome Duriez (9): Articles .bib entries: fixes https://bugs.launchpad.net/yade/+bug/1671045, plus update of entries data capillaryLaplaceYoung scripts: update of reference labels +1 master thesis A new post-processing engine to measure capillary stresses using alternate expressions than getCapillaryStress() Implementation improvement of new MeasureCapStress engine JCFpm model: code comment precised (https://answers.launchpad.net/yade/+question/644244) -1 check test (regarding NormalInelastic contact law which is about to be erased) -1 contact law (Law2_ScGeom6D_NormalInelasticityPhys_NormalInelasticity and other NormalInelasticPM classes) Typos corrected in installation.rst Martin Haustein (1): Add constant volume deformation engine. Robert Caulk (4): Editorial change fix typo Fix typos Roll breakOccurred comments back 1 commit T Sweijen (12): Dynamic version of two-phase flow,interpolation of saturation field when meshing, merging of tetrahedra to find pore units OMerge branch 'master' of https://github.com/yade/trunk provide access to per pore partial solid surface update of Dynamic Two-Phase Flow engine, removing old definitions, update comments, and added several averaging procedures for water pressure Function to track deforming pore units and to couple this to dynamic two-phase flow + cleaning of functions Minor fix for updating volume change fix for savePoreNetwork function - to avoid odd locations of cells along boundary fix of comments and cleaning up of redundant lines. Also, inclusion of initial and boundary conditions for dynamic imbibition Fix for mergeCells() function, to allow for using it more than once Update of initial conditions to allow for primary and secondary drainage/imbibition. Addition of compatibility check of boundary conditions. Removing some redundant functions. Fix in TPF Engine for simulations with Deformation + optimalization for TPF + deformation simulations, without swelling update of soluteFlowEngine, including a fundamental fix in equations and a function to export average concentration William Chèvremont (25): Adding Electrostatic interaction Adding Electrostatic interaction (forgot cpp file) Fix the electrostatic law Merge branch 'master' of https://github.com/yade/trunk Correction of ElectrostaticPhys Adding lubrication force w/o FlowEngine Merge branch 'master' of https://github.com/yade/trunk Merge branch 'master' of https://github.com/yade/trunk ElectrostaticMat derive from CohFrictMat instead of FrictMat Merge branch 'master' of https://github.com/yade/trunk Fix lubrication laws set more realistic default values for ElectrostaticMat Lubrication can process other physics Adding yade-oar and Lubrification resolution by implicit method Revert "Adding yade-oar and Lubrification resolution by implicit method" Revert "Revert "Adding yade-oar and Lubrification resolution by implicit method"" Merge branch 'master' of https://github.com/yade/trunk fix lubrication fix plot Matrix3 suffix (yz instead of yx) Merge branch 'master' of https://github.com/yade/trunk Fix CMakeList Test Fix warnings Debug lubrification Insert other resolution method bchareyre (59): fix the flipCell() function fix compile error flipping periodic cell -> small/doc fix move PBC example scripts cleaning examples documentation on changing the boundary conditions in FlowEngine and derivatives Robert Caulk's guide to cloud computing inserted in doc return a consistent set of constrictions in the periodic case (some constrictions were previsously skipped inconsistenty) re-apply the fix of rev 26efbec to getConstrictionFull() fix a compile warning fix an indentation problem in shinx document - hopefully fixing the buildbot fix unicode warning (documentation build) add a reference url fix compiler warning small fix in getConstriction() + a typo new logic for interating on facets in periodic triangulations (following ref. Rev.26efbec and followers) new data 'crackArea' added to DFNFlow cells and updated automatically - first step toward solving the artificial compressibility of fluids in cracks #define DFNFLOW commented out small updates in doc, fixing compile errors and some warnings add exemple script for two-phase flow with 2PFV technique, related to Yuan and Chareyre (2017) extended version of ForceEngine with its own fluid solver (by J. Chauchat) move HydroForceEngine to independent source files. Minor changes in authorship and inline comments Critical bugfix for collision detection in periodic boundary conditions. Bounds lists were left partially unordered, then some interactions were never detected (my toughest yade debugging until now). Minors & cosmetic. fix feature NewtonIntegrator::densityScaling Introduce a tolerance on overlap determination (in periodic BC only), avoiding false negative due to round-off errors. (usage in a separate commit). A simpler and faster version of spatialOverlapPeri. Only one call to floor(), ~20% speedup for ISCollider::action. Tolerance introduced to fix detection failures. Keep the values of sorted bounds in the [0,size] range in ISCollider. small scale optimization in ISCollider Fix collision detection again (amending commit c7c8e6f62d452c8). Don't move bounds below the starting point of the sort, else partial (only) ordering can occure. Don't start blinking the 0-body when opening inspector + make it possible to un-highlight before/after closing the inspector by selecting empty space. Fix miscommit in d2211bd747d74eaea569ac88e. remove a function that was declared but undefined (leading to runtime error) cite recent reference on timestep TwoPhaseFlowEngine::getNeighbors now returning less than 4 neighbor cells if some of them are infinite (normally only one) a function + python wrap for invading one single pore and updating the clusters fix cluster invasion/update (thx Son for spotting the bug) fix doctest's target following b5ca352 fix doctest in addData (re-apply c011decc36) fix doctest indentation (after 36c016bd) TesselationWrapper wrapping CGAL's AlphaShapes for defining an alpha-contour remove function definitions from FlowEngine.hpp preliminary version of 'extended' alpha shape contour further extension and debugging of alphaShape code nearly working version of the extended alpha-contour small fix proper calculation of alphaCaps areas in TesselationWrapper If FlowEngine::meshUpdateInterval<0 do not update the mesh at fixed intervals fix a compile warning - probably leading to undefined behavior apply d71813b for the periodic flavor of FlowEngine re-insert the comment on Qt4->Qt5 since it may still help (partial revert of 1c06b6ad9d2) Remove the declaration of (redundant and undefined) FlowEngine::currentTess, while there is already Network::currentTess. Fix https://bugs.launchpad.net/yade/+bug/1732210. Use a relevant upperbound (i.e. 0.5*period) for the the maximum body size in periodic boundary conditions. Update the position of the clump members relative to the clump itself ("local" coordinates) when applying growParticles(). It is no longer necessary to perform additional operations (updateProperties()) after growing clumps. make checkClumpHopper.py less time demanding fix bug/1291977 and a typo fix some compile warnings in TwoPhaseFlowEngine enable TWOPHASEFLOW by default jduriez (15): Email address changes Capillary script solveLiqBridge.m: change in attributes order (with small consequences), plus other minor changes Capillary scripts: removing one useless test, and including commented time measurement commands Capillary scripts: Introducing .py versions for 2 (out of 3) .m files. Plus minor change in one .m file Doc: precising Collider.avoidSelfInteractionMask +2 book/proceedings chapters (and formatting changes) Doc of Shop::aabbExtrema precised Doc typos and small improvements in VTK post-processing descriptions Small doc changes in TriaxialStressController.particlesVolume and porosity Doc: minor change in package installation (distro name) Doc: typo in user manual Misleading comment removed in UnsaturatedEngine.cpp Doc: typo correction in TriaxialStressController.stressMask grids examples: moving qt.View() at the end of the scripts so that the window view is correctly scaled with respect to the model Doc of Ig2_Sphere_PFacet_ScGridCoGeom updated: replacing inexistant ScPFaceCoGeom with ScGridCoGeom, and Facet with PFacet, fixes 4. of https://bugs.launchpad.net/yade/+bug/1746037 luc scholtes (6): update of the JCFPM particle model with new or adapted functionalities to record number of cracks and associated released energy. An example script will be provided. add possibility to define residual friction angle (broken contacts) different from friction angle (bonded contacts) some cleaning and clarifying of crack aperture calculation update of file. mistakes in previous commit update due to mistake. Thank you Robert update due to mistake. Thank you Robert raphm1 (10): HydroForceEngine: add an optional input parameter to account for convective acceleration The input convective acceleration term take the form of a depth dependent vector yade-article.bib: add a journal paper Add proceedings Add two functions getStressProfile_contact: to evaluate the stress profile considering only the contact contribution getDepthProfile_center: to evaluate solid depth profile (solid velocity and volume fraction) considering particles as points Fix error from commit e4f601d Add 2 references and 1 paper HydroForceEngine: update the 1D RANS fluid coupling Modify the formulation of the fluid resolution in order to merge it with the existing variables of HydroForceEngine. Fix errors in the fluid momentum balance resolution. Clarify the mesh definition. Evaluate all the components necessary to solve the equations inside the fluidModel function. Introduce the possibility to account for fluid lateral wall friction in the equations. Make it possible for the user to access only the fluid resolution without introducing any particles or launching a DEM simulation. Update the documentation. HydroForceEngine: new examples and validations Introduce a new sedimentTransport script, which make use of the fluid coupling (1D RANS) Complete the sedimentTransport example script and add a post-processing example. Add validations for the fluid resolution without particles, considering logarithmic law and poiseuille flow. Add a file commenting the different examples and validation scripts. HydroForceEngine: small modifications HydroForceEngine: fix errors in the fluid resolution remche (4): Fixing clang compilation Adding path for CGAL ans GMP include dir to cmake (for exotic installations) Merge branch 'master' of https://github.com/yade/trunk Adding the correct include dir for CGAL robcaulk (15): added tmux command tip Fixed bug with multithread+compressible flow and addressed backgroundAction() FIXME for eigenSolve() fixed inheritence problem when LINSOLV=OFF Added option to let new bond breaks in JCFpmPhys control retriangulation in FlowEngine add fracture interpolation functionality GPU accelerated PFV solver GPU acceleration guide Avoid compiling suitesparse GPU solver if -DCHOLMOD_GPU=OFF improve GPU doc Merge branch 'master' of github.com:yade/trunk improve GPU acceleration guide add the ability to threshold trickpermeability to 2 or more facets, default behavior should remain same as before this commit fix variable naming inconsistencies Merge branch 'master' of github.com:yade/trunk fix multithreading issue with imposed fluxes williamchevremont (1): Typo ================================================== yade-2017.01a Thu, Jan 19 22:07:25 2017 +0100 Anton Gladky (20): Remove RELEASE file. Add some notes about how to make releases. Make Yade compatible with VTK7. Use C++11 constructions for InteractionLoop. Optimize using of ndarray. Partly fix webpage help show. LP:1598174 Final fix for LP:1598174 Add missing dependency on python-pyqt5.qtwebkit. Fix load/save in GUI-QT5. Drop unused typedef. Disable LAW2_SCGEOM_CAPILLARYPHYS_Capillarity1 by default. Remove some deprecated scripts. Increase the size of force container. +1 paper. Provide a proper functioning with IPython5. Add -fext-numeric-literals to fix FTBFS against boost_1.62 Fix yade with IPython5. Update distro names in documentation Add missing header include Add RELEASE file. Bruno Chareyre (12): +2 articles (thanks to Bettina Suhr) + 1 proceedings (thanks to Bettina Suhr) Update citing_yade.bib revert the revert ca0e29ac57 list authors of the doc as in doi:10.5281/zenodo.34073 a note on using distcc and ccache minor editing of installation page Master thesis Morales2012a Introduce Network::surfaceSolidThroatInPore() for partial solid surfaces per half-throat make the recursion conditional for invasion in multiphase flow push a file missing in commit e91ff135b7 Yade bibtex: "and others" invalid for less than 9 authors Chao Yuan (4): Add cell label for phase cluster. NW-res label 0, W-res label 1, disconnected w-clusters start from 2. label can be saved in savePhaseVtk. fix comment on label. clean UnsaturatedEngine, move all functions to TwoPhaseFlowEngine. Revert DryingEngine. Francois Kneib (1): Fix O.engines in examples/cylinders/cylinderconnection-roots.py Jan Stránský (11): fixed a small bug in shear force computation in CPM model Added PolyhedraSplitter example Changed L3Geom to ScGeom in tutorial example scripts fixed a minor compilation issue in ConcretePM Fixed call of _utils.interactionAnglesHistogram with default arduments making all Law2::go virtual Matrix_computeUnitaryPositive, changed computeThinX to computeFullX flags User's manual, added how to visualize force chain in Paraview UnstructuredGrid: fiexd little bug in manual setting of facet position from vertices added pack.inHalfSpace and pack.inConvexPolygon + example script fixed another little bug in utils.UnstructuredGrid Jerome Duriez (11): Doc clarification for TriaxialStressController.externalWork (http://www.mail-archive.com/yade-dev@lists.launchpad.net/msg12083.html) Doc of utils.readParamsFromTable() hopefully improved Friendlier BibTex entries for 2015 doc. To avoid Smilauer and et al in manuscripts Doc Getting help section updated to reflect current practice Including interface orientation data in CapillaryPhys code Capillary scripts commit (http://www.mail-archive.com/yade-dev@lists.launchpad.net/msg12103.html). Located in a new examples/capillaryLaplaceYoung folder intended to illustrates capillary Law2. With one new simulation script example and another old one, moved here Typo and minor corrections of previous capillary commit Capillary law2: erasing when necessary sphere-non sphere interactions (bug LP: #1628273) + minor changes Capillary law2: restricting test shape. Plus minor change in capillaryLaplaceYoung/README text file Doc: small updates in PartialEngine and KinematicEngines References .bib files: including new entries Raphael Maurin (4): HydroForceEngine: add a condition to ignore particles fixed on the sideWalls. Add an example for the use of HydroForceEngine HydroForceEngine: new fluctuation model for the example script + small correction Add new simple discrete random walk fluctuation model, to be used with the new example script fluidizedBed.py Change the condition for applying the fluid force from p>0 to p>=0, which is more appropriate Add two example scripts for the use of HydroForceEngine One simple example to get familiar with the application of buoyancy (buoyantParticles.py). One example simulating a fluidized bed configuration to get familiar with both the application of a fluid velocity profile and of turbulent fluctuation with the DRW models (fluidizedBed.py) bchareyre (19): fix invalid pointer, thanks Jerome (https://lists.launchpad.net/yade-dev/msg12791.html) (doc) clarify the meaning of TriaxialStressController::externalWork fix 1 compile warning reordering bib references remove latex/multicolumn from sphinx conf.py (compile error on some systems) Law2_ScGeom_CapillaryPhys_Capillarity: cache getClassIndex() for use in a loop fix wrong access to a Network::boundary by index FlowEngine: enable the definition of continuum scale permeability independent of particle sizes Introduce DryingEngine (two phase flow with convection in one phase) Implement Darcy scale permeability in FlowEngine, introduce alphaShapes (commented), some code cleaning and smallfixes (FlowEngine) optionaly include/skip bounding cells when calculating the average pore pressure fix negative volumes issue in triangulation cells, consistentlyy remove the includeBoundary option (a workaround) in averageVelocity TwoPhaseFlowEngine: better integration of fluid clusters merging PhaseCluster code with r3975 fix compile warnings small optimization in Clump::updateProperties update phaseCluster attributes: volume, interfacial area, etc. + various smallfixes in the cluster labelling further improvement of the cluster labeling in multiphase model 2PFLOW engine: remove useless int from getInterfaces()'s parameters booncw (2): Pushing Potential Blocks based on CWBoonEtAl2012 Computers&Geotechnics/CWBoon 2013 PhD thesis, but using CLP as linear programming Solver. CLP can be downloaded from Synaptic Package Manager: coinor-clp, coinor-libclp-dev, coinor-libclp1, coinor-libosi1v5. To enable this, set ENABLE_POTENTIAL_BLOCKS to ON in CMakeLists.txt" revising Potential Blocks to the latest version to run slope analysis raphm1 (1): ViscoelasticPM : fix resolution problem in find_cn_from_en function Change the value of the initial perturbation for the resolution of the equation to obtain the damping from the restitution coefficient. Allows for a much wider range of resolution in terms of mass, stiffness and restitution coefficient. Put a limiter to avoid division by zero and force the loop to print the warning message if "error" goes to nan (no warning in these cases previously) ================================================== yade-2016.06a Tue, Jun 14 20:30:53 2016 +0200 Anton Gladky (88): Remove release. Clean some unused macroses (old wm3-stuff) Remove some global headers Raise minimal boost version to 1.47 Add -DNDEBUG if compiling in release mode Tiny code refactoring Clean some warnings and old stuff. Return removed header back. Fix warning in newer matplotlib. Drop old commented stuff in python-scrips. Remove flags and preSteps from scene Remove deprecated findBoundDispatcherInEnginesIfNoFunctorsAndWarn Remove PISC_DEBUG. Fix typo in installation sections of doce. Thanks to Jan. Export normal and viscous part of the visco-elastic contact Remove confusing part in installation part of documentation regarding Qt5. Fix typo in CMakeLists Tiny updates of examples of LudingPM. Fix compilation warnings. Update formatting in SpherePack Fix an order of calculation of c in SpherePack Fix some compilation warnings. Fix cappitalization typo in Lapack (case-sensitive in this case). Respect VTK6 in PotentialParticles. Add .travis.yml for CI. Remove version restriction on sphinx. Use trusty as build-env. Add -y parameter to apt-get install. Fix numpy import on travis. Fix segfault during save/load of CapViscModels Add one more check-script (capillary models) Fix typo in check-script. Fix crash in Ig2_Facet_Polyhedra_PolyhedraGeom vtkExporter: increase number of leading zeros to 8 Move implementation of methods of ForceContainer in cpp. Split implementation of ForceContainer in parallel and serial. Add zero-forces to the youngest body after simulation load. Closes LP:1560171 Drop parallel execution in replaceByClumps. Closes LP:1559098 Add import of polyhedras from the file. Add an opportunity to shift, scale and rotate imported polyhedrons Minor fixes in Polyhedra_splitter. Fix division by zero crash in Polyhedra Add CGAL_DISABLE_ROUNDING_MATH_CHECK, -frounding-math when CGAL is enabled. Fix some formatting issues. Non-invasive fixes in polyhedra_splitter. Get max-min coefs in polyhedra_splitter simpler. Use tuple as a parameter for SplitPolyhedraDouble Add check script for polyhedra crash. Use explicitly -DNDEBUG instead of CMAKE_CXX_FLAGS_RELEASE Check isnan in some coordinates before calling CGAL. Minor formatting fixes in Polyhedra. Add check-script for save/load of clumps. Disable checkPolyhedraCrush. It is unstable now. Fix checkPolyhedraCrush Remove polyhedra_utils import from "ymport" Add prefix std:: to isnan and isinf. Update checkPolyhedraCrush Update files for ppa-infrastructure. Undef NDEBUG in all polyhedra files. Add pause for checkSaveLoadClumps to escape race condition. Use variadic arguments in DynLibDispatcher. Fix crash in polyhedra, if maxFs=0. Fix checkPolyhedraCrush (remove qt) Remove unused functionality in Dispatcher. Set Qt5 by default. Add mathcmaker for normalCohesion and shearCohesion. Split QGLViewer.cmake on qt4 and qt5 versions. Add .gitattributes to ignore line endings. Migrate buildppa to python3. Replace some "#define"(s) by functions. Remove some machine-specific items in documentation. Add unit test for matchmaker. Replace vector by unordered_map data structure in Matchmaker. Minor fix in documentation. Replace some "defines" by functions. Remove numpy.hpp. Fix bug in polyhedra_splitter. Some cosmetic fixes in core-files. Enable C++14. Revert 2dc99 and ed8dcb. Use unordered_map instead of map for MapId2IntrT Use unordered_map only for Boost >= 1.56 Back to C++11. Revert using unordered_map in Body. Scale mass and inertia in spheresModify. Reduce size of flags in Bounds of InsertionSortCollider Fix bug in MatchMaker. Add RELEASE file. Bruno Chareyre (16): add labels to default engines for easier showcases fix ambiguous syntax, fixing https://bugs.launchpad.net/bugs/1514477 introduce 2nd edition of the documentation and contact for consuting (in homepage) update the webpage about citation with 2nd edition, add 1st edition as an external reference, update pdf metadata fix one DOI of the 2nd ed. documentation Add missing toctree's for the citable book versions fix typo in installation page fixed DOI fix some hyperlinks pointing to the old geo.hmg.inpg.fr declare mask attribute for FlowEngine, fixes a compile error use FlowEngine's mask consistently implement standalone plastic-shear energy counter in CohesiveFrictional Law2 define constrictions with consistent cell's ids (see https://answers.launchpad.net/yade/+question/288118) fix a compile error update boost version in prerequisites (installation page) Documentation: include unp in the equations of Law2_ScGeom6D_CohFrictPhys_CohesionMoment Burak Er (1): Implement deformable elements ChiaWeng Boon (1): Commit Potential Particles: non-spherical particles for DEM Francois Kneib (8): TesselationWrapper : add a security that prevents user to access the strain tensor of a particle (aka deformation) outside its boundaries. Fix a tensor access in local displacement calculation. Fix a bug in Tesselationwrapper: one of the bounding planes was not rightly positionned. Fix in TesselationWrapper: allow the tesselation to compute right triangulation in the boundaries of a periodic boundaries conditions simulation. Add 5 setters for bodies pos,vel,ori,angVel,color to workaround the memory leak bug when assigning attributes from python (see https://bugs.launchpad.net/yade/+bug/1041084). They are not supposed to stay in the code for a long period (remove them when the bug is solved), but are useful in sims that massively change values of python/c++ binded attributes. Simplify one line in TesselationWrapper. Fix the debug build that failed to compile due to a mistake in pkg/dem/Polyhedra.cpp Fix the inspector GUI for Qt5, thanks to Mark S. Bentley. Jan Stránský (30): Merge pull request #47 from booncw/master Potential particles modification Merge branch 'master' of github.com:yade/trunk Potential particles modifications added forgotten file from previous commit Modified python interface for O.forces.addF(...,permanent=True) and addT(...,permanent=True) fixed some warnings during docs build Added frictAngle MatchMaker for Ip2_FrictMat_CpmMat_FrictPhys set b.aspherical=True in utils.polyhedron and utils.tetraPoly Polyhedra modification Added a material model for mortar layer Polyhedra.setVertices improvement, preventing memory leaks (question #290947 and bug #1570679) Interaction::reset explicitly resets also its functorCache Fixed a strange bug in Law2_PolyhedraGeom_PolyhedraPhys_Volumetric and shearForce Added useRef parameters to VTKExporter.exportPolyhedra and VTKExporter.exportInteractions Modified MortarMat Merge branch 'master' of github.com:yade/trunk fixed computation of shearForce in Law2_PolyhedraGeom_PolyhedraPhys_Volumetric added and modified examples for MortarMat Merge branch 'master' of github.com:yade/trunk added MatchMaker for E in Ip2_CmpMat_CpmMat_CpmPhys Fixed a bug in pack.inSphere, related to question #292846 Merge branch 'master' of github.com:yade/trunk VTKRecorder, added orientation for box export (question #293635) Modification of CPM Fixed ymport.textPolyhedra function (bug #1582775) added x_y_z_r_attrs format to ymport.textExt Added an example for generation of 'agglomerate' packing Fixed Law2_Cpm default parameter to keep backward compatibility (question #294657) Better default value from previous commit Jerome Duriez (12): +1 journal paper Incorporating previous +1 paper in JCFpm doc + another journal paper makeCloud doc changes with the hope to make it clearer for all users Doc of TriaxStressController.porosity precised Doc improvements of previous commit improved Last doc improvement of previous commit normalForce and shearForce convention (sustained by #2) precised in docs fabricTensor() now ok for non-periodic simulations. revertSign attribute removed as well getSpheres*() functions: getting rid of dynamic test, see http://www.mail-archive.com/yade-dev@lists.launchpad.net/msg11956.html fabricTensor(): unify the behavior regarding boundary interactions whether split=0 or 1: they are now disregarded in both cases fabricTensor(): re introducing all kind of interactions in the loop, with the new possibility defining a cutoff. See http://www.mail-archive.com/yade-dev@lists.launchpad.net/msg11982.html Klaus Thoeni (23): fix some typos and add some more details fix Anton's previous commit (make doc was not working) fix documentation for Note some polishing gts shift and scale apparently only take three float values, hence changed Merge branch 'master' of github.com:yade/trunk move grid specific functions in separate module and add some more doc add some more examples, show same functionality as with chained cylinders update doc add Grid and pFacet references Initial version of the PFacet implementation (contributed by Anna Effeindzourou) Examples for creating pfacets update tests according to new gridpfacet module remove postLoad function as not used, this fixes as well serialization problem in --test better handling of double contacts, get gid of function ScGem goOneWay improved documentation add definition for cylinder and cylinderConnection basen on gridConnection, add warning for chainedCylinder usage examples for cylinder and cylinderConnection example with many cylinders and 2 pFacets QT5 is default now correct some typos similar to example in chained-cylinder-roots.py add reference Raphael Maurin (6): HydroForceEngine: modify the turbulent fluctuations formulation and add a new DRW model The turbulent fluctuations model takes now the form of a function of HydroForceEngine and is not anymore called from a flag. In addition, an alternative DRW model has been added. HydroForceEngine: adapt the averageProfile function for bi-disperse mixtures. Evaluate the streamwise, spanwise and wall-normal average particle velocity for the two defined types of particles. HydroForceEngine: modify turbulent fluctuation model. Add a fluctuation along the spanwise (y) direction. Reset the fluctuation to zero when the particle is out of the flow (fix a bug) Comment a bit more the turbulentFluctuation function HydroForceEngine, hpp file completing the last commit Add references add a link in the publications bchareyre (1): compute external work correctly in TriaxialStressController ================================================== yade-1.20.0 Fri, Oct 9 21:20:00 2015 +0200 Anton Gladky (57): Remove RELEASE file. Simplify definition of build flags Fix some warnings during compilation. For clang use -fstack-protector instead of -fstack-protector-strong Ignore project files of idea IDE Refactoring of Math.hpp Make minieigen external package mandatory. Add -fstack-protector-strong only for gcc >=4.9 Some warning fixes. Add ENABLE_PROFILING option Set some metric-measures in ViscElCap. Revert adding frounding-math flag Replace INSTALL_PREFIX by CMAKE_INSTALL_PREFIX Remove deprecated in Yade BOOST_PYTHON_FUNCTION_OVERLOADS macros. Fix some more compilation warnings. Fix some more compilation warnings. Minor fix in CMakeLists.txt Fix hexagonal packing, should not be undesired overlap any more. Make Serializable.hpp more readable. Remove deprecated parameters. Move STLReader into STLImporter +1 master thesis at TU Freiberg. Fix crash by export-VTK of interactions after body removal. Split description of ViscoelasticPM to make it more readable. Let exist interactions between clumpMembers of the same clump. Skip force calculation in SPH-clump Add warning about CGAL in Ubuntu 14.04 Trusty Add ISC_TIMING pre preprocessor directive. Drop support of Ubuntu 12.04 Precise Merge pull request #46 from timpovall/master Non invasive refactoring of InsertionSortCollider Revert previous commit. Add a second option to fix the compilation with CGAL on 14.04 Remove deprecated code with very old boost. Implement viscous damping for capillary phase. Fix SPH force calculation between clump members. Fix compilation with QGLViewer>=2.6.3 Prepare Qt5-build. Fix compilation against gqlviewer-qt4. Fix check-script +1 conference First steps in Qt5. Prepare Qt5 build. Update python files due to Qt5. Some more updates toward Qt5. Qt5-migration is alsmot finished. Fix Qt5 compilation. Fix crash in Qt5. Fix QThread issue. Add information about compilation against Qt5. Add missing function in TesselationWrapper Remove some unused headers and defs. Fix compilation if openmp is disabled Fix compilation introduced in last commit. Add talk, hold in Particles 2015 Add hourglass example Add RELEASE file. Bruno Chareyre (15): Save relative rotations in ScGeom6D (unmark "nosave") since it is required for reloading correctly. CohesiveFrictionalInteractions: removed "nosave" flags for contact moments +1 journal paper +1 journal paper add a virtual function returning rotational stiffness of the interactions - returning zero if not overloaded account for the rotational stiffness of interactions in GlobalStiffnessTimeStepper apply the spin of the velocity gradient on particles in periodic BCs small typo Added book chapter Installation: http://www.yade-dem.org/packages is required for an external library (eigen or cgal? not sure now) more accurate defintion of fluctuational velocity/spin for kinetic energy in periodic BCs correct value of volume for id<6 in TesselationWrapper when O.bodies does not have bounding objects fix segfault when InteractionContainer::found() access out of bounds add function has() to O.interactions to check if (id1,id2) exists + fix doc of O.interactions remove useless int parameter of O.interactions.all(), instead filter non-real interactions optionaly based on bool Chao Yuan (3): -add getPotentialPendularSpheresPair() function. -set pore throat radius between two fictious cells negative in invadeBoundary=true mode. add a new version of capillary law. (pushed by Caroline) Francois Kneib (2): New viewer feature : display tori instead of spheres for 2D simulations. So if the viewer looks toward the right axis, on can see circles instead of disks. Modifications in pkg/common/Gl1_Primitives.*pp 3 new parameters in Gl1_Sphere : circleView, circleRelThickness, circleAllowedRotationAxis. A new condition to enable circleView. The code is based on initGlutGlList() method for spheres. Add an example script for the tori DISPLAY feature. Jan Stránský (4): fixed compilation warning added Polyhedra.setVertices function, which also updates internal variables (shape.v=... does not) Added Facet.setVertices function to prevent memory leaks (see bug 1041084, #8) corrected bug in utils.UnstructuredGrid.updateElements (question #267761) Janek Kozicki (2): Fix bug in InteractionContainer::eraseNonReal Fix: CGAL ERROR: assertion violation! Jerome Duriez (5): Add missing space in error message Doc of sphSph attribute of plotDirections precized Introduction of surface tension as an attribute, and comments about code objects related to capillary files Addition in doc of Omega.engines to state it is = to O.engines in python Removal of python O.engines reference Raphael Maurin (9): getStressProfile: output format modification + add calculation of granular temperature and kinetic stress tensor Law2_ScGeom_ViscElPhys_Basic: add a new formulation + check Add a new formulation of the law when imposing in the material young, poisson, and normal restitution coefficient. The effective spring constants ks and kn are evaluated "classically" with the young modulus and the poisson's ratio. The specificity of the formulation stands in the fact that it is the restitution coefficient of the contact (en = 2*en1*en2/(en1+en2)) that is imposed, and not the damping constant. To achieve this, the damping constant of the contact is evaluated resolving numerically the analytical expression 21 of [Schwager2007] (with a minus sign, there is a mistake in the article) with a Newton-Raphson algorithm (This was made by Francois Kneib). This does not seem to affect the calculation time. With this formulation, there is no tangential viscous damping, and it is usually not possible to access value of normal restitution coefficient lower than 0.1 (in that case the numerical resolution does not converged in the maximum 15 iteration allowed in the code) Together with the new formulation, add a short check script to verify that the imposed restitution coefficient is the one obtained. Law2_ScGeom_ViscElPhys_Basic: complete the documentation Add a more precise documentation of the law integrating the last commit. I didn't complete the formulation associated with Pournin2001 (precising en,es,tc) as I am not using it and I do not know it much. Fix compilation error in the documentation of Law2_ScGeom_ViscElPhys_Basic Error inserted in commit d47b8574e8fcc2dc09b926404f08081f2194cfde Add a function in shop to evaluate average depth profiles of particle velocity (x,y,z components), and solid volume fraction. HydroForceEngine: switch averageProfile function into a method + modify documentation Tutorial examples: remove all deprecated 'utils.' and GravityEngine in the example scripts Modify getDepthProfile to possibly average on spheres of selected radius only HydroForceEngine: modify averageProfile function to handle bi-disperse sample Add evaluation of the average solid volume fraction and drag force depth profiles considering the two particle size (taken as input) as independent. Timothy Povall (1): Removed line calling FindVTK.cmake, as this file no longer exists thomassweijen (1): Updates towards merging of both capillarity codes ================================================== yade-1.14.0 Tue, 21 Apr 2015 22:36:55 +0200 Anton Gladky (36): Remove RELEASE file. Fix typo in SPH. Move SPH-body parameters into body->state. Add one more kernel function in SPH. Change sign in calculation of viscosity in SPH Simplify Lucy kernel function Remove Cs parameter from VTK-export (sph only). Implement second variant of BSpline kernel function. Fix typo in Bspline kernel functio (sph). Fix date typo in Changelog Move Vf and Vmin from Body`s parameter to Body->state. Replace struct intReal by standard std::pair Prevent returning of a real value from void-function. Fix probably a wrong using of if-condition. Minor fix of using of std::abs function. Drop some if-conditions, which have always constant value. Drop default inclusion of DFNFLOW. Add some missing #ifdef YADE_CGAL. Fix check-script for LIQMIGRATION. Drop -frounding-math compiler flag from FindCGAL. Drop embedded floating_point_utilities_v3. Reintroduce floating_point_utilities_v3. Add missing files from last commit. Add LiqMigrEnabled parameter. Fix compilation with GCC-5. (Closes: #778190) +2 Project works Add an opportunity to list works, other than master thesis. Fix body unselecting. (Closes: LP:1423130) Fix MatplotlibDeprecationWarning: The use of 0 Remove DirSearchYade, MicroMacroAnalysis and someFile.pdf Clean current directory after --check Check file existance before its moving and removing. Add information, how to be notidied after each commit. Add an opportunity to set frictAngl through MatchMaker in ViscEl Add an option coordNumber to be exported in VTK. Add one more master thesis. Bruno Chareyre (28): FlowEngine: set cell's id in periodic triangulations + couple smallfix fix bad bugs in DFNFlowEngine following uninitialized 'isClump' use the dedicated solver in DFNFlowEngine some code documentation FlowEngine: discard blocked cells completely when assembling the permeability matrix #undef DFNFLOW FlowEngine: enhanced setter macro, detect changes in imposed pressure and update the linear system accordingly explicit error message when trying to interpolate capillary forces out-of-range of the data files fix save/load QGL camera state via text files Documentation. Define sign convention globaly in TriaxialStressController docstring, not repeated in attributes documentation bib reference of 1st workshop fix mistakes in conferences.bib +13 journal papers using yade +14 conference papers small update in the publication page fix and sort some bib references + change the title of the conference section +1 journal paper +1 conference paper +1 journal paper and fix alphabetic ordering of a few items FlowEngine safely impose flux at every iteration without remeshing + documentation implement blockHook for PeriodicFlowEngine as well Add .mailmap file defining git alias for multiple adress emails sphinx '..only::' directive removing some parts that break in latex integrate Ning's FEMxDEM doc in yade's doc add references in relation with FEMxDEM + couple fixes in *.bib remove FEMxDEM readme, replace by FEMxDEM.rst for inclusion in doc remove '..only:' from sphinx files - not yet supported on older sphinx then breaking the html build | update bib's Update introduction.rst Chao Yuan (7): move computePoreThroatRadius() to 2PFlow -move initialization() to 2PF -fix inconsistency in invasionSingleCell and getMaxImbibitionPC; clean code; remove savePhaseVTK in Unsat -remove cleanInterfaceWithinPore() in Unsat. fix inconsistency of side boundary cell pressure of closeBC drainage. Add some comments on FlowEngine(by bruno). Fix error in computing cell->info.solidLine Christian Jakob (10): fix force and torque vtk output for boxes temporary fix for pfv vtk output when vertex ids were not zero-based fix fusion detection for hertz model in capillary law reduce error message spam for capillary law remove unused testing function in capillary law fix previous commit for case (fusionDetection && hertz model) in capillary law (re-initiate bodiesMenisciiList at first real contact) and add some comments improve/fix documentation about clumps insert link to user manual in documentation of NewtonIntegrator update DEM background doc for clumps some code cleaning in clump methods Francois Kneib (1): Add the Yubuntu (live-usb with Xubuntu and Yade) alternative for installation. Jan Stránský (10): PolyhedraMat and PolyhedraPhys are now inherited from FrictMat and FrictPhys, modified related examples Added Ig2_Sphere_Polyhedra_ScGeom Added examples for Ig2_Sphere_Polyhedra_ScGeom, added ymport.ele function Added documentation of Ig2_Sphere_Polyhedra_ScGeom Allowed Polyhedra-Wall interaction if Wall.sense==0 fixed Cpm for Sphere-Box interactions added export.textPolyhedra function added Elias2014 article to yade-articles.bib add attrs parameter to export.textPolyhedra modified Clump::updateProprties to handel non-spherical bodies, added associated example box inertia computation correction Jerome Duriez (19): Sign convention change for fCap, to be consistent with global framework. Plus other minor changes. As discussed in http://www.mail-archive.com/yade-dev@lists.launchpad.net/msg10868.html and http://www.mail-archive.com/yade-dev@lists.launchpad.net/msg10877.html Doc hyperlinks, and erase of commented lines Many changes towards a more consistent sign convention throughout the code for stresses and strains. Should fix e.g. https://bugs.launchpad.net/yade/+bug/1381282. The goal is to know directly the meaning of a strain or stress value each time one is encountered. Relying on the classical Continuum Mechanics convention (editorial choice...). A global announcement is planned in a couple of days, in case some errors appear in the meantime. Other changes regarding sign convention. Add of deprecation warnings (existing in the code and at execution) in the doc. Fix of script-session2.py previously broken See https://bugs.launchpad.net/yade/+bug/1394942 answer # 3 Minor doc fixes Anecdotic doc change, see http://www.mail-archive.com/yade-dev@lists.launchpad.net/msg10972.html Add of O.thisScene() function to know in which scene we are Add of sphSph parameter in plotDirections() to take into account only sph-sph interactions in polar histograms. Seems it does not modify really the plots, but it is easier to figure out with the parameter TSC engine: internal compaction now may consider isotropic tensile cases. See http://www.mail-archive.com/yade-dev@lists.launchpad.net/msg11047.html Removal of parallel loops in capillary Law2: needs maybe more verification, and improvements for fusion detection Typo in the doc (missing space) Correction of minor inconsistency between comment and command Extra parenthesis removed in doc Doc of capillary law2, concerning fusionDetection, precised (see https://lists.launchpad.net/yade-dev/msg11792.html) Doc typos Parallelization of one loop of Law2_ScGeom_CapillaryPhys_Capillarity (see http://www.mail-archive.com/yade-dev@lists.launchpad.net/msg11238.html) and some comments Typo in ScGeom, precision about twist doc (axis) in ScGeom6D Minor fix of previous commit (hyperlink) Klaus Thoeni (2): update references update some links and typos Luc Scholtes (3): changes for DFNFlow + corrections in JCFpm associated with the use of the never erase flag minor changes in DFN Flow add references Luc Sibille (5): Add 2 article references in yade-articles.bib correct a proceeding reference correct reference key in 1 proceeding fix missing comas after key references add fragile parameter to CohFrictMat and redefine unpMax CohFrictPhys Ning GUO (2): Ning Guo's FEMxDEM package added to trunk Example REV generation for FEMxDEM Raphael Maurin (6): add some references in yade-conference.bib Switch a private argument to public in HydroforceEngine. HydroForceEngine: minor fix + add an averaging function Minor fix : modify some default value of the parameters, remove unecessary parameters. Add a 1D averaging function which evaluate the average depth profiles of the solid volume fraction, the solid velocity, and the drag force. The averaging is weighted by the volume occupied by the particles in the layer considered. Add new function to compute stress tensor depth profile. Compute and return the stress tensor for each cell of height dz, between two limit points given by the user. The stress tensor computed include contributions from both particles inertia and Love-Weber stress tensor. This last take into account only the part of the branch vector contained in the cell. Minor fix in getStressProfile. The order of the argument in _utils.cpp was not the right one. HydroForceEngine: small modifications in the averaging function. Perform the averaging also on the y and z component of the particle velocity, instead of only x before. T Sweijen (7): update for dynamic two-phase flow small correction add initSolver() delta t for dynamic two-phase flow add function to get the pore body radius add one get functions for pore volume and rename that of inscribed sphere add one get functions for pore volume and rename that of inscribed sphere ================================================== yade-1.12.0 Mon, 20 Oct 2014 21:22:00 +0200 Anton Gladky (27): Remove RELEASE file. Use toleranceWarning and toleranceCritical for DEM-PFV check. Fix crash after clumps removing. Closes LP:1354433 Add checkClumpHopper autotest. Fix crash after removal of the whole clump. Fix calculation of massR in ViscoelasticPM Fix cs calculation in ViscoelasticPM Fix ViscElPM one more time. Create an array of clump`s memberIds to remove. Return fictional value from deprecated functions. Respect returnin value in computeForceTorqueViscEl Update Schwager2007 URL Add one more paper Merge pull request #44 from fifthguy/master Add firstIterRun parameter to PyRunner. Use system call "sphinx-build" instead of python module. Fix compilation error in PeriodicFlow.hpp Set python versions explicitly. Move function declarations of _utils.cpp into .hpp Fix runtime error in debug mode and without openmp. Drop including "yade/" folder in cpp and hpp files. Fix docs-generation. SPH-code refactoring. Use only vtkCommonCore vtkIOImage vtkIOXML components for VTK. Use Python 2.7 only Reintroduce viscosity in SPH-modell. Add RELEASE file. Bruno Chareyre (33): -include UnsaturatedEngine to start Chao's work fix a few mistakes and make a real (not empty) test function - replace hand-defined positions by scene's positions Merge branch 'master' of https://github.com/yade/trunk into chaoUnsat remove pack.particleSD and variants, unmaintained and deprecated by makeCloud (functions kept temporarily but returning error) fix https://bugs.launchpad.net/yade/+bug/1362090 fix https://bugs.launchpad.net/yade/+bug/1308074 fix https://bugs.launchpad.net/yade/+bug/1368591 + remove a useless test remove a temporary fix for https://bugs.launchpad.net/yade/+bug/923929, after better fix in https://github.com/yade/trunk/commit/4ea76ad6e47ac5074a389ad61712a0840e8560a5, thanks Anton parallel removal of old interactions replace LOG_ERROR by LOG_WARN for notifying deprecation of GravityEngine improve falling back to 1-thread in parrallel collider (fix https://bugs.launchpad.net/yade/+bug/1368591) fix missing brackets resulting in wrong permeability in periodic PFV point to Bourrier2013 for a partial explanation of the CohesiveFrictional contact law fix doc regarding requestErase() - https://bugs.launchpad.net/yade/+bug/1370736, thanks Jan selective blocking of cells of the mesh in FlowEngines (preliminary steps) remove empty file PFV: don't skip perm calculation for blocked cells, yet fix filename remove cpp with wrong filename reset id of erased bodies; enabling this: b=Body(); O.bodies.erase(O.bodies.append(b)); O.bodies.append(b) turn DeprecationWarning (not displayed) into UserWarning (displayed) since the error messages where cryptic no indexing of blocked cells in PFV decrease verbosity of blocking PFV cells DFNFlow unblock cells as fractures reach them + additional attributes in JCFPM split resetNetwork and resetLinearSystem + add more getter/setter split TwoPhaseFlowEngine in hpp/cpp for inclusion in child engines #ifdef guard for openmp function don't overwrite imposed fluid pressure when initializing the values (more flexible) + a function returning barycenter + improved "locate" for interpolation increased flexibility of imposing fluid pressure in FlowEngine's specialized getter/setter's for TwoPhaseFlowEngine + no re-indexing of cells in UnsatEngine better #def/#ifdef logic for TwoPhaseFlowEngine rename cellCenter->cellBarycenter (missing in prev. commit) Chao Yuan (86): - my first work on drainage simulation Commit the first working code for drainage - make the FAR constant more flexible (a parameter in addBoundaries) -add poreRadius -a test commit to escape big mess... Merge github.com:yade/trunk into chaoUnsat -add saveLatticeNode functions for generating axial-normal slice with "0" and "1" -add isWaterReservoir, isAirReservoir. -add another version for drainage. -delete unnecessary recursion for invadeSingleCell2() Merge github.com:yade/trunk into chaoUnsat Merge github.com:yade/trunk into chaoUnsat -add UnsatCellInfo, UnsatVertexInfo clean code -add temp function for pore connection Merge github.com:yade/trunk into chaoUnsat -update boundary attributes -a backup for laptop -add UnsatVertexInfo(void) for future use -test commit -test commit Merge remote-tracking branch 'origin/chaoUnsat' into chaoUnsat Merge github.com:yade/trunk into chaoUnsat -clean code add capillaryCellVolume in cellinfo, optimize getSaturation() -replace cell->info().p with isAir/WaterReservoir -add solidLine in cell info. partly code for force. clean code, fix mistake on Facet_Force. a test version of computing fluid force. fix solidLine[i][j] when facetNFictious case(1). -fix noCache. -fix solidLine[i][j] when facetNFictious case(2). -clean code. fix reservoir attr. change boundcells.isWaterReservoir=true when finish drainage. clean code. add temp test func. add cell->info().trapCapP; fix pressure calculation for trapped phase. clean code. Merge github.com:yade/trunk into chaoUnsat clean code.add action() add getSaturation2() for mode 2. Merge github.com:yade/trunk into chaoUnsat borrow saveVTK from FlowEngine normalize two invade modes. make computeForce optional;a bakcup a temporary save, change waterReservoir=bound[2], add invadeBounday option -make invade from boundary optional.(default false) -fix Line_Solid_Pore() in Network. Merge github.com:yade/trunk into chaoUnsat -big change, inherit from FlowEngine. remove old files clean code fix force calculation. add compute specific interficial area, lots of bugs... fix computeCellInterfacialArea with fictious vertex -test. no big change. Merge github.com:yade/trunk into chaoUnsat Merge github.com:yade/trunk into chaoUnsat Merge github.com:yade/trunk into chaoUnsat -fix ‘python’ declared bug -clean some info in debug -fix core dump in computerForcePoreForceWithCache, currentTes shoule be solver->T[solver->currentTes],NOT solver->T[currentTes] Merge github.com:yade/trunk into chaoUnsat Merge github.com:yade/trunk into chaoUnsat -update with PFV -use bndCondValue to mark reservoir. -fix reservoirs determination; fix invade(), Pw can be negative (mode1) -change invade rule, use bndCondValue to determine invasion. reservoirInfo depends on bndCondValue; merge isInvadeBoundary.(mode1) -change invade rule for mode2. merge some functions -fix getWindowsSaturations. -add pore radius checking funcs(tmp) -rename variables for computePoreRadius(); clean code. -clean code Merge github.com:yade/trunk into chaoUnsat Merge github.com:yade/trunk into chaoUnsat -add debugOut to test isInvadeBoundary=True isPhaseTrapped=True -insert debugOut. (core dump fixed by ulimit -s 16000) Merge github.com:yade/trunk into chaoUnsat This is TwoPhaseFlowEngine (alpha version) yeah! Merge github.com:yade/trunk into chaoUnsat -merge TwoPhaseFlowEngine, add more cell infos. Merge branch 'master' of github.com:yade/trunk -add savePhaseVtk. rename function. -remove check cell.index Merge branch 'master' of github.com:yade/trunk comment #define TWOPHASEFLOW Christian Jakob (6): insert updatePorosity option in TriaxialStressController fix long line in github doc small fix of a warning in NewtonIntegrator minor fix in doc introduction.rst include fluid stiffness for TSC for undrained flow condition fix fluidStiffness for TSC when FlowEngine is deactivated Dominik Boemer (1): Add check-script for ViscoElasticPM. Francois (1): Correct contact tracking bugs for grids. They essencially came from the new logic of laws that have to return booleans. Jan Stransky (7): Added force and torque export to VTKRecorder (question #252413) fixed typo in utils.polyhedron function enable periodic simulations with Polhedron and Tetra rename PolyhedraVolumetricLaw -> Law2_PolyhedraGeom_PolyhedraPhys_Veolumetric deleted vtk output of force for boxes (bug #1376734) added Polyhedra::GetSurfaces function Added volumePower attribute to Law2_PolyhedraGeom_PolyhedraPhys_Volumetric, Polyhedra code small cleanup Janek Kozicki (2): Fix bug in 'inspect' that made editing values annoying. Merge doc/sphinx/references.bib into doc/references.bib Jerome Duriez (3): Re-write of "Yade on GitHub" wiki page in sphinx doc. Anecdotic commit to keep a track (in code comment) of a useful link for JCFpm paraview analysis Removal of very last wiki-link github-related in rst files Raphael Maurin (1): New force engine to couple Yade with a 1D RANS code. Add a new force engine applying the main hydrodynamical forces in function of a 1D average fluid velocity vector which depends only on the depth. The engine is calculating at each time step the drag, lift and buoyant forces for each particle. Complete the references for the documentation associated to the engine. T Sweijen (1): Merge two-phase flow engine Thinkpad (1): -a test commit from laptop cyuan (7): a small change for check reservoir boundingCells. -add temp function for generating sample windows, calculating responding saturation... -add savePhaseVtk Merge github.com:yade/trunk into chaoUnsat Merge github.com:yade/trunk into chaoUnsat -replace abs, max, min by std::abs, std::max, std::min Merge github.com:yade/trunk into chaoUnsat cyuanLaptop (1): -change debugOut fifthguy (1): Fixed a bug that appeared when running the uniaxial-post.py script from examples. Edited py/post2d.py to capture exception while importing Vector3 from minieigen. jduriez (12): Correction of an error using psd() with monodisperse packings (false = False in python). Add of an explanatory message Few changes in capillary doc: assumption of null wetting angle, hyperlinks, and some comments in source code One forgotten change during previous commit about TriaxialStressController <-> 3DTriaxialEngine ? https://github.com/yade/trunk/commit/77bc6d75b780bcbad5bdadc5bb3645badb009cd9 Adding mention of different save files kinds. Tried to comment differences, please correct if I was wrong Typos and precisions in capillary doc porosity() function infers now a volume value for non-periodic cases, rather than throwing an error. Positiv volume values eventually passed as argument override this inferred value porosity doc changed according to previous commit getStress() considers now an adequate volume value for non-periodic case (values passed as parameters may still also be taken into account) Re-introducing change from https://github.com/yade/trunk/commit/ab91b3cf8654f6d563dede10184e784a8ce84219, reverted since, without reason Improvement of https://github.com/yade/trunk/commit/05599b23808991dbfef6b85400c7ddd32be594e0 regarding volume computation in getStress for non periodic cases Typo in CapillaryPhys doc Parallelization of interaction loops in Law2_..._Capillarity (which is in fact not a LawFunctor handled by InteractionLoop). Moreover adopting the FOREACH iterator for the non-parallel flavour. ================================================== yade-1.11.0 Mon, 4 Aug 2014 18:54:00 +0200 Alexander Eulitz (9): small corrections of messed up indentations another indentation fixed rearranged compilation and installing procedure so that hints for multicore compilation are placed directly after the make command typo fix fixed --cores implementation and improved arg-parse argument documentation. improved doc of betan and betas replacing damping coefficient betan and betas by damping ratio betan and betas rearranged indentation of examplarily folder structure for yade complation Added hint about make errors during compilation when user does not posses root priviledges. Anton Gladky (58): Delete RELEASE file. Add YADE_PTR_DYN_CAST to define dynamic_cast. Remove all "using namespace std;" Fix compilation with disabled openmp. Roll back last 2 commits. Remove most of "using namespace std;" Replace math.h by cmath in includes. Remove some more "using namespace std;" Remove "using namespace std;" everywhere. Tests are failing. Replace abs by std::abs. Clean up in header inclusions. First stage of C++11 implementing. Add missing header. Remove isIndexable and isFactorable. Use boost::shared_ptr for C++11 as well. Drop Qt3 workaround Enable LBM by default. Enable C++11 by default. Do not consider Eigen3 like a feature. Capitalize feature list. Use double precision for VTKREcorder. Do not use -DTYPEOF, use explicit decltype Move definitions of YADE_PTR_CAST YADE_CAST YADE_PTR_DYN_CAST into Math.hpp Remove -std=c++0x definition for pygts. Join [CohFrict]Mat, Phys and Ip2_. Move Ip2_*_MindlinCapillaryPhys into HertzMindlin Move Ip2_*_CapillaryPhys. into CapillaryPhys. Move Ip2_FrictMat_* into FrictPhys Move all NormalInelastic files into NormalInelasticPM Join all InelastCohFrict files into InelastCohFrictPM. Join all KinemC*Engine.* into KinemC__Engine.* Set C++11 only for CXX-files, not C Update LICENSE file, does not need exception any more Cut off some header inclusions. Disable reusing of removed body ids. Fix compilation. Replace nullptr by reset(). Join all Bo1_* into Bo1_Aabb Remove some more .cpp-files. Move many Gl1_* into Gl1_Primitives. Remove some more small cpp-files. Remove some warnings. Remove LBMbody.cpp and LBMlink.cpp. Remove Indexable.cpp Exclude Indexable.cpp from CMakeLists.txt Redirect --test output into stdout Modify slightly description of save-loadVars Change stderr to stdout in greeting message. Update Vaclav`s occupation in sphinx-doc Cut long line in HertzMindlin.hpp Move some YADE_PLUGINS into common.cpp Fix most of compilation warnings. Return false in BubbleMat, if no penetration. Fix and enable DEM-PFV-check.py. Replace L3eom by SCgeom in stl and gts examples. Use global interpreter lock (GIL) in OpenGLRenderer. Update mtTkinter version. Remove NEWS file (outdated). Bruno Chareyre (9): Revert "Revert "Set minimal required Eigen3 version 3.2.1"" / Thanks Anton for uploading the backport replace abs by std::abs (complement of https://github.com/yade/trunk/commit/1997c194c0aa759cae101a3dd0a559fcf049b29f) +2 conf papers Law2 return bool - fix https://bugs.launchpad.net/yade/+bug/1324190 add missing 'return bool' in some Law2's Revert "implement more accurate porosity calculation..." (temporary required in order to revert 915fd94606af6 without conflict)" Revert "fix typos and indents; cut long lines.. - please no formatting commits (+there is nothing wrong in long lines). Please reapply the documentation part." make growParticles fatser for clumps use Shop::growParticles in TriaxialStressController (fix https://bugs.launchpad.net/yade/+bug/1351275) Christian Jakob (5): fix typos and indents; cut long lines in TriaxialStressController; bit more documentation of stressMask implement more accurate porosity calculation for clumps in TriaxialStressController; inserted updateClumpProperties command in Triax tutorial Merge branch 'master' of github.com:yade/trunk make PFV compatible with clumps (clumps are treated as spheres with equivalent radius - valid for nearly spherical clumps) reapply 9e512fd50083 Francois (1): Simplify the contact detection in Ig2_GridConnection_GridConnection_GridCoGridCoGeom. Jan Stransky (3): fixed bug in export.VTKExporter.exportInteractions (question #250922) modification of utils.UnstructuredMesh, added utils.polyhedron fixed bug in MASK_ARBITRARY feature Jerome Duriez (7): Correction of some broken sphinx links: yade._utils => yade.utils Removal of a double : Improvement of previous commit: there was no extra : but a missing blank line Export principal stresses and directions in VTKRecorder Some shortening in VTKRecorder, mainly according to http://www.mail-archive.com/yade-dev@lists.launchpad.net/msg10370.html Revert sign convention in bstresses in VTKRecorder (tension=positiv now) Slight modif in JCFpm doc. To better describe the logic of the code. Kubeu (1): replaced "root" from last commit Nolan Dyck (2): Update BubbleMat.hpp and BubbleMat.cpp Update bubble example script. ================================================== yade-1.10.0 Wed, 25 Jun 2014 19:35:00 +0200 Anton Gladky (47): Delete Release file. Add orientation parameter to ymport.textClumps Resize ForceContainer after each body insert Added assert to get*Unsynced function Better fix for ForceContainer-size-change Remove extra/floating_point_utilities_v3 Remove some 'using namespace boost' Remove some more `using namespace boost`. Remove all remaining `using namespace boost` Fix compilation in DEBIG-mode. Link against libboost_date. Fix LP:1322274 Set the mask to a clump the same as the first member of it. Revert removal of embedded floating_point_utilities_v3. Add particleconserve parameter to LiqControl. Replace features by CONFIGURED_FEATS. Prevent first empty item in features. Add checkLiquidMigration autotest. Better check for DEM-PFV-check.py Add missing header. Add --as-needed flag to yade-lib. Comment some lines, where defined unused variables. Minor fix in documentation of viscoelastic. At every sync check, whether ForceContainer is large enough. Disable parallel code in conditionalyEraseNonReal Skip interactions, where one of body isClump Fix doc-compilation for IPython >=2 Enable vectorization in eigen3-lib Do not use Eigen3 in parallel mode. Recommend minimal Eigen3 version 3.2.1. Set minimal required Eigen3 version 3.2.1 Add NOVECTORIZE option Prevent adding liqVol-parameters into VTK-files Prevent crash in liqVolIterBody Disable vectorization by default. Replace Quaternion.Identity by Quaternion((1,0,0),0) In state pos_set and ori_set use passing by value. Add addLiqInter function to set liquid "properly" Liquid migration model, code refactoring. Add script to changes commit`s author`s names. Fix typo in installation. PFV code refactoring. Fix typo in previous commit. Fix templates names. Clean in ymport.stl Remove service-messages in CMakeLists Add missed #pragma once Add RELEASE file. Bruno Chareyre (11): CohFritMat documentation document Ip2_CohFrictMat more make the parallel collider warning more explicit regarding turning collider.ompThreads=1 CohesiveFrictional contacts: make them elastic only if strength<0 (previous condition was <=0) + Lingran's elastic energy of contact moments fix fluidForce=0 after remeshing + allow constant positions for DFNFlowEngine make FlowEngine::setPositionsBuffer virtual missing declaration of function fix default attribute in DFNFlow CohesiveFrictionalContactLaw does not warn about incremental formulation of rolling/twisting moments when the moments are elastic fix bug in FlowEngine::updateBCs() (reported by Luc Scholtes, thanks) Revert "Set minimal required Eigen3 version 3.2.1" Christian Jakob (2): remove relicts of Dem3Dof and code cleaning increase maximum length of char filename in saveVtk method for FlowEngine Francois (1): Update the documentation for Law2_ScGridCoGeom_NormalInelasticityPhys. At this moment it's not done, but it will be fixed and come with a reference article during 2014. Jan Stransky (4): Body::groupMask may be optionally boost::python::long_, subsequently modified code where necessary fixed bug in yade.export.exportFacetsAsMesh (thanks Jan Havelka for reporting) added mask_t type for bitmask variables to be int or optionally std::bitset of fixed (yet arbitrary) size. Added bitset to/from python long conversion. Added MASK_ARBITRARY cmake option. Subsequent changes in other files corrected git warning in CMakeLists.txt Jerome Duriez (6): Correction of mispelling in example script name of one Law2 Minor changes in JCFpm Important change in Kinem.. engines. Both changes in position and velocities of boundary bodies were still existing, while now changes in position should be handled by NewtonIntegrator JCFpm : doc of JCFpmPhys.FnMax precised (minor change). And formulation of kn for onJoint interactions according to JCFpmPhys.crossSection (rather than mean of surfaces) to be consistent with the whole formulation ("major" change : this may impact the results of previous scripts depending on psd and how much the mechanical behaviour of joint is important) Doc of VTKRecorder.skipFacetIntr precised so that it better corresponds to code Extra s in previous commit removed.. Klaus Thoeni (4): remove old code improve output and remove first iteration in calculation velocity estimation add definition for functor order set number of iterations to be run correctly (thanks Alex) ================================================== yade-1.09.0 Sun, 18 May 2014 17:32:07 +0200 Alexander Eulitz (1): corrected a little bug in description of yadedaily-installation and added example and folder structure for compilation Anton Gladky (97): Remove RELEASE-file. Fix setting of wall's color. +1 reference. Replace some bazaar-links by github. Update installation instructions. Update INSTALL file. Fix version definition for IPython>1.0.0 Fix warning for IPython > 1.0.0 Add radiusTopInner, radiusBottomInner to facetCone and facetCylinder. Increase number of steps in checkWeight.py Prevent returning a reference to local temp object. Prevent binding a reference member to a temp. value. Remove notice on the main page about daily packages on Launchpad. Some more changes on the main page. Add Lambert formulation into capillary models. Use enum instead of string to keep capyllar type. Update config of mini-dinstall. Do not ignore k* and c* parameters if mass==0 Add billiard example script. Introduce YADE_ODEINT feature Implement binary-arch only builds for yadedaily. Fix split delimiter Fix library name for new openblas. Output some additional messages, when DEBUG mode is enabled. Fix compilation with libqglviewer>=2.5.1 Fix compilation with libqglviewer>=2.5.1 Add informational message, if VTK6 is found. Disable some debug-messages, because they break compilation. Check, whether manipulatedFrame exist, if not - create it. Set mouse parameters, only if frame is created. Better fix for QGLviewer next version. Fix compilation. Move all force and torque calculation into separate function Move capillary functions out of ViscEl-classes. Implement Soulie capillary model. Add an example to test different capillary models. Deprecate getViscoelasticFromSpheresInteraction. Update examples and test-scripts due to deprecating of getViscoelasticFromSpheresInteraction. Update Warning about last changes. Use MatchMaker to set tc, et and en in ViscEl model Update URL in getViscoelastic Fix compilation in ViscElCap. Minor description update. Replace isnan by isfinite to let the numbers be 0. Remove last capillar-parameters from ViscEl. Remove massMultiply paramter Fix compilation. Implement experimental SPH-model Add gradients and laplacian of the kernel functions. Implement some more kernel functions. Remove duplicated code. Normalize parameters of kernel functions. Produce meaningful error message. Add Cs parameters to particles, visualize them in VTK. Export in VTK some more SPH-parameters. Get a coordination number from body. Use bool return type for force calculation. Add watercolumn example for SPH. Add example script to test different kernel functions. Add README to examples SPH. Fix conflict. Fix eigen3 inclusions. Add one more example for sph. Split all capillary function into smaller one. Add YADE_SPH macroses to fix FTBFS, when SPH disabled Minor fix (replace #ifdef by #endif) Add an opportunity to change liquid volume during simulation. Disable temporarly Cs calculation in SPH. Fix interaction removal in VeiscoElPM Store an information about active liquid contacts. Add -= operator to OpenMPAccumulator. Fix compilation with clang, when openmp is switched off. Add OpenMPVector for thread-safe vector work. Minor fix in installation section of documentation. Export current value from PIDController. Add a note in installation section of docs. Do not link libloki and boost_date_time. Fix scrolling direction for libQGLviewer > 2.5.0 One more minor fix in zooming. Implement Liquid Migration model (experimental). Replace LIQCONTROL by LIQMIGRATION. Fix compilation with clang. Set capillary parameters to 0. instead of -1. Fix Id export in ymport.textClumps Remove YADE_DEPREC_DOF_LIST prepocessor. Add blockedMovement parameter to state of body Add conveyor example. Minor fix in documentation/installation. Add mask parameter to NewtonIntegrator Replace png-pictures by xpm. Fix the beginning camera position in GUI. Remove integrateInertia=False from packs.py. Add discretization parameter to appendClumped. Do not use getTorqueUnsynced and forces.getForceUnsynced in clumps Make addForceTorqueFromMembers more readable. Revert last change in Newtonintegrator. One more revert in NewtonIntegrator. Bruno Chareyre (60): avoid crash when Vh==NULL in FlowEngine. The sphere is ignored in the flow problem, otherwise FlowEngine runs as usual. Merge branch 'master' of github.com:yade/trunk remove redundant "from yade import *" in example scripts, as it mess-up scopes small change in PFV checkTest fix a bug in ISCollider that would create interactions with unbounded bodies remove deprecated code linked to queuing requestErase'd interactions in an older version of the collider Merge pull request #39 from lsibille/master Merge pull request #38 from Kubeu/patch-4 Merge pull request #37 from Kubeu/patch-3 Merge pull request #36 from Kubeu/patch-2 Better behavior of QGLViewer for shift+select and moving bodies (fixes LP bug 806469) let libparmetis be used as an alternative of libmetis Add hideBody/showBody to selectively display bodies in the 3D view Merge pull request #40 from burak-er/master Parallel InsertionSortCollider fix compilation issues for ENABLE_OPENMP=OFF some renaming and code cleaning in PFV code some renaming and code cleaning in PFV code (part2) PFV: First step in refactoring templates dummy engine for showing how to derive from FlowEngine + a preliminary version of DFNFlow make FlowEngine a template yade class Periodic version of flow engine in separate source files PFV: simplify (c++ wise) value assignement to Info types. More refactoring. Final step of code cleaning. fix a bug in PFV code (fictious vetices not defined correctly) minor changes in FlowEngine remove a useless data member fix compile error due to dirty hack (cpp's included by another cpp) Added documentation for changing the velocity gradient of periodic cell TriaxialStressController: reference sample sizes can be modified by users (was previously read-only) ignore *~ files in git status update biblio references Hack YADE_CLASS macro to make it work with c++ templates (introduces a macro for pyClass name different from c++ class name) new YADE_CLASS_PYCLASS_... macro, missing in previous commit update bibliographic reference in a script introduce CellInfo::getInfo() for more generic interpolation between triangulations better detection of changes in Cell::velGrad fix the example script periodic-simple-shear.py - introduce SoluteFlowEngine (from Thomas Sweijen) - move Flow classes to a separate folder fix include paths after moving files to pkg/pfv fix include path pkg/pfv Bugfixes in PeriodicFlowEngine, as discussed with Donia remove useless FlowBoundingSphere.cpp fix https://bugs.launchpad.net/yade/+bug/1301443 undef flag, so SoluteFlow doesn't break compilation on buildbot. update about openblas+openmp on 12.04 initialize CellInfo members (avoids hardly detectable bugs) + remove artifact facets from loop when cell1==cell2 remove an inacurate reference, introduce another one fix bad link to capillary files clean of commented blocks Merge pull request #43 from bchareyre/pc fix gravity checkScript (es -> 'et') enable run/pause from QGLViewer (retun key) + fix compile warning remove empty file add FlowEngine::doInterpolate to force interpolation during mesh swap Add user defined boundary condition to FlowEngine add authors in files headers -fix bibliographic link add missing #ifdef, breaking compilation without LINSOLV + better volume computation fix compile error in 298f0b6 switch to 1-thread colliding when problem detected in parallel run (fix https://bugs.launchpad.net/yade/+bug/1314736) Burak Er (1): added an adaptive Runge Kutta integrator scheme. Also, added an example using this integrator. Christian Jakob (15): allow user to set an output folder for PFV-based saveVtk() method Merge branch 'master' of github.com:yade/trunk fix compilation bug in InsertionSortCollider.hpp fix a bug in inertia tensor calculation for clumps if integrateInertia=True extend/fix replaceByClumps() method for multiple overlapping clump templates absorb bool integrateInertia from Clump::updateProperties by int discretization Merge branch 'master' of github.com:yade/trunk Parallelize replaceByClumps(): drastically reduce generation time of clump replacement if discretization>0 fix bugs in Clump::updateProperties, fixes https://bugs.launchpad.net/yade/+bug/1273172 introduce updateClumpProperties() method in yadeWrapper - can be used e.g. when clumps are imported in yade update of O.bodies.erase(): it can erase clump members too when erasing a clump Merge branch 'master' of github.com:yade/trunk remove typenames from Polyhedra.cpp (fixes compiler error) let ymport.textClumps() return all ids from members and clumps fix some indents Francois (2): Undo modifications about ViscElMat into Ip2_FrictMat_FrictMat_FrictPhys. The young modulus and poisson ratio have to be set for both materials to perform a contact between a frictMat body and a viscElMat body. Add a comment for that. Fix 2 bugs linked to the Grids : - in utils.py : remove a factor 2 in the calculation of the bending stiffness kr. - in Grid.cpp : fix a bug that occured in the Sphere-GridConnection contact. The contact was not detected in a very specific case : if the sphere was next to a node and the node goes from a convex (angle>180°) form to a concav (angle<180°) form. Jan Stransky (13): added vtk export of polyhedral particles improvement of previous commit (vtk polyhedra export) added example of paraview sphere "solid sections" improvement of export.textExt function to support arbitrary quantity export added O.stopAtTime (question246284) chenged return type of O.stopAtTime from previous commit fixed some errors and warnings for documentation build one more little info inside code export.VTKExporter: improved comments, fixed bug (thanks Jan Havelka for reporting), updated corresponding example script code cleanup in ConcretePM, fixed one more bug in export.VTKExporter added exportContactPoints function to exporter.VTKExporter, updated example script fixed export.VTKExporter.exportContactPoints for nonexisting interactions Polyhedra implementation improvement (Contributed by Jan Elias) modification of utils.calm function (bug 1318513) Jerome Duriez (15): Change in the doc of ymport.text/textExt to better reflect the behaviour A doc sentence for pericell recorder in VTKRecorder Typo in doc of CohFric Law2 : the shear adhesion is a_s, not a_n Typos and link syntax in doc of CohFrictPhys Typo correction in porosity() doc : porosity instead of poro sity (in 2 words) Use of correct version of requestErase() in JCFpm (https://bugs.launchpad.net/yade/+bug/1273775) Small changes in JCFpm IP logic : it is now possible to have new contacts that are always cohesive, and "poisson"=0 is possible (=> ks = 0 rather than infinity...) Small improvement in JCFPM doc about the cracks file, and typo in user.rst Mistake in hyperlink previous commit Copy paste mistake in State and typo UniaxialStrainer doc Proposal of correction of UniaxialStrainer, in relation with https://bugs.launchpad.net/yade/+bug/1300167 Add of an external reference, about the "derivation" of bodyStressTensor Correction of copy-paste mistake in doc of Bound Hyperlink about sortAxis in InsertionSortCollider doc Minor changes (not affecting results) in these scripts towards an easier understanding Klaus Thoeni (2): Correct shear stiffness averaging for Ip2_FrictMat_FrictMat_ViscoFrictPhys new simple contact law with normal viscose damping which allows to specify kn and ks/kn in Ip2 Kubeu (3): Added Antons hint to first install external depencies of Yade before following installation instructions small typo fix typofix Luc Sibille (10): add test on alphaKtw to allow nil value of twisting stiffness engine to compute a fluid flow with the lattice Boltzmann method LBM Definition of the basic elements used by the LBM engine definition of the compilation option ENABLE_LBMFLOW to make the compilation of the LBM engine optional, this option is disable by default remove all LBM file Merge remote-tracking branch 'upstream/master' definition of the LBM engine inside a lbm directory, with license GPLv2 definition of the compilation option ENABLE_LBMFLOW to make the compilation of the LBM engine optional, this option is disable by default A buoyancy example for the DEM-LBM coupling Merge branch 'master' of github.com:yade/trunk ================================================== yade-1.07.0 Fri, 10 Jan 2014 21:23:33 +0100 Anton Gladky (37): Remove RELEASE file. Add -ftrack-macro-expansion=0, if gcc>=4.8 Provide VTK6-support. Use ADD_DEFINITIONS instead of adding those directly in CXX_FLAGS. Remove information about yade-stable from Readme. Add BicyclePedalEngine as a new kinematic motion. Add -save-temps if GCC-4.8 is used. Split Grid into Grid and Grid_GUI. Add information about missing packages in documentation and external PPA. Closes: LP:1250928 Add notice about using libqglviewer-qt4-dev instead of libqglviewer-dev on older Ubuntu versions. Add script to create PPA-packages. (Not finished) Use othermirror for some ubuntu versions. Add stl-gts example. Add qt.View() to clumps-example. Add one more check-script to check the functionality of ViscoElastic PM. Consider massMultiply-parameter, calculating parameters in ViscoElasticPM. Add functions to save and load clumps. Set the higher pripority in sys.path to self-compiled modules. Closes LP:1254708 Fix volume calculation. Closes LP:1261415 Set the higher pripority in sys.path to self-compiled modules in yade-batch. Update scripts for PPA. Add configuration files, needed for PPA. Minor update of PPA scripts. Update information about yadedaily prebuilt packages. Add support for qglviewer>=2.5.0. Open GUI in packs/packs.py after 1 step to show all elements. Raise warning, if no spheres are produced by regular* commands. Update information about daily-packages. Set one more paramter -ftemplate-depth-512 for clang Prevent attraction forces in ViscPM due to viscosity Add some more steps to checkWeight-script. Minor formatting in references. Remove cout accidentally added in a prev. commit. Minor change of setFromTwoVectors in utils.py. Rename createtar.py to buildppa.py. Minor fix in docs. Add RELEASE-file. Bruno Chareyre (17): a function to increase the size of a single sphere (~>Thomas swelling) easier manipulation of state files in TesselationWrapper restore the python wrapping of utils.growParticles() as it was before fb02a74 (changed by mistake, sorry Christian) give unique identifiers to trangulation's cells FlowEngine::nCells retruns the number of finite cells; FlowEngine::getVertices returns the vertices of a cell defined by its id make cmake output more consistent when missing dependency for LinSolv Fix the behavior of FlowEngine.updateTriangulation=True FlowEngine: increment a counter correctly small changes in the installation section regarding suitesparse and related libs installation.rst: smallfix in indentation remove a not necessary sorting in FlowEngine, as it triggers a critical bug in the STL (http://gcc.gnu.org/bugzilla/show_bug.cgi?id=58800) correct target values of the DEM-PFV checktest Fix paths to online and packaged documentation (fix url error with GUI's hyperlinks) DEM Background chapter: a note on the semantic strain vs. displacement for contact kinematics Merge branch 'master' of github.com:yade/trunk fix undefined "key" in triax-tutorial/script-session1.py +1 published paper Christian Jakob (1): small fix in an example script tetra/oneTetra.py Francois (3): Fix missing REGISTER_CLASS_INDEX(...) into ViscElPhys. This macro is essential for a right law dispatch ! Corrected a minor mistake in code comments : k=2*r*E Allow the contact between (coh)frictMat and viscElMat as frictPhys. Almost everything was done by inheritance, just had to convert stiffnesses to modulus and modulus to stiffnesses to ensure material compatibility. Note that for the moment the timeStepper cannot handle this kind of simulations -> will be fixed soon. Jan Stransky (4): Modified error message if not yade.runtime.hasDisplay (bug #1241817) fixed SyntaxError from previous commit uncomment and modify utils.forcesOnCoordPlane function corrected mistake in examples/test/triax.py Jerome Duriez (15): - Introducing (uncommenting in fact) tens/shearBreakRel variables in JCFpmState : relative part of broken interactions per body (instead of absolute number). - Few changes in some doc of JCFpm variables. Quite important changes in JCFpm code Modifying JCFpm example scripts so that they still work after previous commit Re-put some commented lines, after discussion of this thread (https://lists.launchpad.net/yade-dev/msg10185.html), thanks Anton. The comments solution is (at the moment ?) finally still used to avoid putting a new variable in VTKRecorder.. A script example for JCFpm : two rock parts with one joint in the middle, like in laboratory experiments Some forgotten corrections in some example scripts, so that they still work after changes of today in JCFpm Some corrections in the JCFpm doc (hyperlinks...) (Hopefully) Fixing some rst links in DEM Background doc Some changes in the doc of getStress() function, to be consistent with the source code (https://answers.launchpad.net/yade/+question/239919) Merge branch 'master' of github.com:yade/trunk Idem as previous commit Restoring the 2d version of growParticle previously erased by mistake.. Some hyperlinks in JCFpm doc corrected Typos and hyperlinks corrected in Peri3dController doc Correction of a link in TesselationWrapper() doc Klaus Thoeni (1): add CGAL link and delete libgmp3 from install list Raphael Maurin (1): Change in the calculation of the normal and tangential stiffness and damping. The change affects only the behavior when one of the two parameters is zero, e.g. for two particles with different stiffness k1 and k2, the contact stiffness will always be k = k1*k2/(k1+k2). Before it gave the same except when k2 = 0 (respectively k1 = 0), where it gave k = k1 (resp. k=k2). This is done to ensure continuity in the behavior when one of the two parameter tend to zero. Add a function contactParameterCalculation in Ip2_ViscElMat_ViscElMat_ViscElPhys to avoid code duplication. ================================================== yade-1.05.0 Mon, Oct 28 19:49:48 2013 +0200 Anton Gladky (19): Add system-component of boost to be linked. Fix typo in equations in getViscoelasticFromSpheresInteraction (documentation). Thanks to Medack (TU Freiberg). Explicitly link BZip2-library Explicitly link ZLIB-library Update installation docs, add libbz2-dev zlib1g-dev. Fix LudingPM. Rename variables in LudingPM (Theta->Delta). Fix k2-calculation in LudingPM. Update script for LudingPM. Split gui/qt4/GLViewer.cpp. Remove pkg/dem/DomainLimiter.* Recover DomainLimiter (LawTester should be moved somewhere). Remove some warnings. Update numpy_boost.hpp from svn. Remove executes bit on py, cpp and h-files. Split Shop.cpp on Shop_01.cpp and Shop_02.cpp Fix compilation. Remove stable-PPA reference from documentation. Replace all libboost-*-dev by libboost-all-dev. Christian Jakob (19): complete description of clump methods in users manual fix a bug in inertia tensor approximation scheme fix some links in users manual and Scene doc fix description of default material in users manual fixing/updating refs and links in users manual - part 1 removed SpherePadder description from users manual fixing/updating refs and links in users manual - part 2 (finished) adapt buoyancy example with new O.forces.addF() method and make it look nicer update in Clump::updateProperties: included new bool integrateInertia and int discretization and adapt clump(), appendClumpend(), addToClump(), releaseFromClump(), replaceByClumps() and growParticles() create links to some example scripts in wrapper increase initialization speed of examples/packs/packs.py (fix https://bugs.launchpad.net/yade/+bug/1229783) try to fix (still broken) refs in users manual Merge branch 'master' of github.com:yade/trunk make getRoundness() more flexible: empty excludeList is no more needed as input argument; adapt replaceByClumps-example.py and associated part in users manual small fix in users manual fix a link in TesselationWrapper fix a link in TesselationWrapper (second try) fix buoyancy example for clumps removed typenames from Polyhedra.cpp, make gcc < version 4.7 happy Donia (4): Save the relative velocities of particles. Compute edgesSurfaces if viscousShear is true. Save the interactions only between spheres into a vector. get the shear and normal viscous stress in each interaction. Jan Stransky (10): marginal modifications Merge branch 'master' of github.com:yade/trunk Added periodic cell VTK export (to VTKRecorder and export.VTKExporter) Merge branch 'master' of github.com:yade/trunk added forgotten example file fixed bug in export.VTKExporter from one previous commits Merge branch 'master' of github.com:yade/trunk Polyhedra implementation + examples (Contributed by Jan Elias). adding examples with tetrehadron modeled by new Polyhedron class CombinedKinematicEngine will not call its 'dead' subengines (question #237437) Jerome Duriez (6): Correction of typo in comment Proposal of new (similar but more compact) examples using JCFpm classes. A choice can be made one day after some feedback ? Some changes in JCFpm documentation (add of external references, and removal of cross reference towards the CFpm model which does not exist anymore). In cpp, some commented lines to add (in the future ?) some post-pro features that could maybe break current saves of (existing ??) current users Commiting changes discussed briefly in https://lists.launchpad.net/yade-dev/msg09979.html. Mainly written by L. Scholtes, they allow to vizualize with paraview some features of JCFpm classes. It adds then 2 recorders, "jcfpm" and "cracks", that are separated mainly for historical reasons.. They are anyway now documented Add of pylab.ion() in plotNumInteractionsHistogram() and plotDirections() so that yade does not hang anymore after launch of these functions (even after closing the plot window, here). Still thanks to Vaclav ! (http://stackoverflow.com/questions/9753885/pylab-matplotlib-show-waits-until-window-closes) Anecdotic changes in an example script Klaus Thoeni (2): resolve problem with mask in GridConnection ignore local kdevelope specific files on git ================================================== yade-1.00.0 Sun, Sep 29 23:10:48 2013 +0200 Anton Gladky (64): Remove release file. Add documentation, how to render particle with ParaView`s PointSprite plugin. Fix spelling errors. Add Changelog. Add different capillar models into ViscoElasticPM. Add links to equations, consider not only monodisperse particles. Fix s parameter for Willet-formulation of capillar. Add one more critical-calculation to capillar model. Cleanings in viscoelastic capillar modell. Move capillar calculation into another function not to overload VPM. Modify Weigart`s model. Update equations for Weigart`s capillar model. Update capillar equations and names of schemas. Move references to a references.bib. Add comment on liquid bridges. Fix some warnings (clang). Fix compilation. Implement rotational resistance in ViscoElasticPM. Add massMultiply-parameter to ViscoElasticPM to have an opportunity to set kn, kt, cn and ct without multiplication on mass. Implement Rabinovich`s capillar model. Fix wrong calculation of the average rotational resistance. Split ViscoelasticPM on capillar and dry parts. Prevent devision by 0 in capillar Rabinovich model. Prepare for flowengine compilation. Merge libplugins, libsupport and libcore in libyade. Fix FindCholmod.cmake. Fix compilation of FlowBoundingSphere.ipp Replace Cholmod-Feature by LinSolv. Minor fix in CMakeList (FlowEnginge). Implement PID controller to have a servo-engine. Fix tests due to failing clumps-autotests. Use combinedengine for ServoPIDController. Fix hopefully clumps-autotests. Add LudingPM. Merge branch 'ndyck' Add an example for LudingPM. Use libyade again. Revert Bruno`s revert. Fix configuration issue with CHUNKSIZE>0. Drop RockPM. Remove ParticleSizeDistrbutionRPMRecorder. Remove CohesiveStateRPMRecorder. Show PFVflow and LinSolv in disabled features, if they are disabled. Include loki-library dirs and link against them. Fixes compilation on HPC TU Freiberg. Set LinSolv and PFVFLOW ON by default. Add recommended build-dependency for LinSolv and PFVFlow into documentation. Replace ifndef/define-constructions by "#pragma once" in lib/triangulation, where it is possible. Minor fix in capillary models in ViscoelasticPM. Add [Pournin2001] into the description of ViscoelasticPM. Minor fix in calculation s_crit in ViscoelasticPM. Add information about github-hosting into the main page. Add a notification to the screen, whether the current build is debugbuild. Remove explicit linking of libstdcxx. Use skipScripts variable to skip some scripts on check-tests. Skip temporarly DEM-PFV-check.py to let daily-builds packages be built. Remove kde-files from the trunk. Minor fix in installation section of documentation. Remove Fedora list package from documentation. The list is unsupported. Add Antypov2011 reference and some links in Hertz-Mindlin model. Fix some compilation warnings (GCC). Replace libqglviewer-qt4-dev on libqglviewer-dev in installation section. Fix errors in ForceContainer, detected by clang. Fix compilation error. Add export into LIGGGHTS-format. 1.00.0 Bruno Chareyre (68): +1 journal article Add the color of the periodic cell a registered attribute. - fix a bug that would let the capillary tables empty when postLoad was not triggered Add function getCapillaryStress(), equivalent of getStress() but for capillary forces -code cleaning fix a latex equation FlowEngine: exclude corner cases when listing constrictions sizes along the void path +1 article -fix url of an article +4 journal papers +1 journal article - add "LINSOLV" source code for DEM-PFV coupling with direct sparse solvers add strainRate attribute to TriaxialStressController + fix bug in example script Revert "Merge libplugins, libsupport and libcore in libyade." Cause: the new cmake system breaks buildbot and does not work for chunkSize>0 - add getSceneAsString() to pass scenes between instances without going through the hard drive fix CMakeList for chunkSize>=1 - enable load/save from/to python strings with O.sceneToString and O.stringToScene (for communication between parallel instances of yade) -fix the return type of stringToScene() clean the flow code (part 1) cleaning the flow code (smallfix of part 1) cleaning the flow code (part2) cleaning the flow code (part3 and last) -first example script for the fluid coupling with the DEM-PFV method - new conference and journal papers fixed reference +1 PhD thesis - more documentation of TesselationWrapper, with a section in user manual and pointer to published equations. git ignore changes to .gitignore undo previous change in gitignore, not the right way to fix Turn a LOG_DEBUG into LOG_ERROR/return, to escape segfault. (ref: https://bugs.launchpad.net/bugs/1208878) Add an optional argument to O.forces.f() and O.forces.t() to get correct forces on clumps. doc improvement and more cross-links for TesselationWrapper and FlowEngine -enable user defined forces on bodies applying permanently (with updated user manual) +1 conference paper -workaround https://bugs.launchpad.net/yade/+bug/1183402#3, allowing startup imports with optional argument example: $yade -lscipy.interpolate -fix '-l' command line option (no import lib was crashing) fix uninitialized member of ForceContainer fix compile warnings (unused variables) - smallfixes in documentation fix FlowEngine docstring fix duplicate bib reference small fix in bib entries remove duplicate bib reference commit small fix and update in bib files make default FlowEngine::permeabilityFactor less absurd (crashing by default sucks) regression (check-)test for FlowEngine remove Dem3Dof form Yade (also eudoxos module, mostly based on Dem3Dof) - remove Dem3Dof from documentation remove eudoxos from build system (+ remove from VTKRecorder, that was left behind) remove Dem3Dof from a docstring remove Dem3Dof from alias various fixes and improvements in checkTests - increase verbosity of DEM-PFV checkTest remove some debugging code left in chekList.py make sure FlowEngine has been compiled before trying the checkTest DEM-PFV, as suggested by Klaus make the DEM-PFV checktest fully determinist with a data file for initial positions new attributes in FlowEngine to access low level cholmod/metis data \#include in FlowEngine.cpp (why is it not needed on lucid?!) add #ifdef LINSOLV guards in appropriate places in case someone (e.g. buildbot) compiles without cholmod one more #ifdef LINSOLV guard add a cmake path for metis.h on wheezy Return a warning at startup if X rendering is unavailable despite gui is required. User manual: correct the meaning of body.dynamic + remove utils. prefixes Typo fix in prog.rst Fix sphinx warning (no :gui: role) yadeSphinx.py: remove eudoxos module from the build list -remove empty section of doc: External modules revert 3d7ca8577 (doc/current hyperlink), see also https://lists.launchpad.net/yade-dev/msg10035.html. Chiara Modenese (1): Prevent the normal force become negative when viscous damping is applied. Christian Jakob (23): added new example showing implementation of buoyancy small fix in new example from previous commit small fix of description in new buoyancy example include integration scheme for inertia tensors of clumps; inertia/masses/volumes of clumps are updated automatically by call of updateProperties method; removed adaptClumpMasses method and example script Merge branch 'master' of github.com:yade/trunk make getRoundness() faster shorten the code of Clump.cpp: switched getClumpVolumeAndAdaptInertia() method into updateProperties() small fix of a comment small fix in an example script Merge branch 'master' of github.com:yade/trunk remove unused #include in Clump.cpp limit number of cubes for clump inertia integration scheme to 1000000; avoids worst case scenario with R_clump_member(s)< iterator 2. Clean an horrible block of code (not only ugly, it was also slow) - introduce a position buffer for faster updates of volumes Use retriangulation conditions more consistently. The solvers are not swapped before a given fraction of the permutation interval now, to reduce overheads. small change in the initialization logic, now defining the two position buffers if "first" make ompThread an attribute of Engine, as it likely to be used more and more in the future. It is then inherited by InteractionLoop and ParallelEngine Parallelize volume updates and the computation of fluid forces fix a typo introduced in previous commit, giving compile error in FlowEngine remove traces of the SpherePadder module, which is broken and unmaintained (with author's agreement) make grids periodic use EpsVolPercent_RTRG consistently in periodic flow: negative value is ignored, just like the non-periodic case Adapt setContactProperties to pass argument in radians to Shop::setContactFriction -fix conversion mistake (radian/degree) update variable names to denote the unit of friction angle (rad), and make this explicit in the documentation - include elasticity in the normal lubrication force - fix ompThreads assignment in flow engine, resulting in numThreads=-1 by default, then "libgomp: Out of memory" Fix the F9-F12 keys binding for IPython>=0.12 -add the correct prefactor in normal lubrication relation -overlap the two triangulations during one iteration in order to have the "previous" forces defined in the normal lubrication equation Update the documentation of the ompThreads attribute. - fix again the offset of ids in SpherePack.toSimulation (previous fix was assuming two bodies per clump) - fix https://bugs.launchpad.net/yade/+bug/923929 again, as shown by François it could still happen in some cases (see example in the bug report) - remove the check/throw when generating aperiodic 2D packings, since the algorithm actually support that. Define O.engine with a default list at startup, with example script and notes in the documentation. remove utils prefix - Updates and improvements in the triax-tutorial material - smallfix in a note in introduction.rst fixed version of the triax script for clumps Remove first iteration form "--performance" option measurements -transfer the normal lubrication force correctly when retriangulating -doc fix -fix the sign of the normal lubrication force in the periodic case fix compile warning (actually fix the functions as well, as they were not returning results at all!) - doc fix -remove remaining debug lines periodic update of publications fix a typo in bibtex file, +1 ref - one fix in articles, yet more references in conferences fix/add Tran's reference - use "orientation" parameter in utils.box (fix bug/1111514) - improved version of TriaxialStressController, controling not onlt stress but also strain rate - making the interface more similar (but still not exactly like) PeriTriaxController. - TriaxialCompressionEngine and ThreeDTriaxialEngine become almost useless - reflected in the example script which now uses only TriaxialStressController -micro-fix (no need to define an initial timestep) -code cleaning - fix the default value of orientation in utils.box. [1,0,0,0] is NOT a quaternion. -add a few python bindings to the periodic engine for manipulations of the internal tesselation -fix the detection of (not) already computed tesselation -make volume() functional also when PeriodicFlow is actually in use uncouple the compilation of flow code and linsolv, so that it is possible to effectively compile the flow engines without cholmod remove useless "sigma_iso" and "isAxisymetric" members from TTController - make an old engine deprecated, not compiled now, to be removed later more #ifdef guards, to allow flow engine without linsolv remove sigma_iso from the triax example script fix syntax error in a doc string (blocking the buildbot https://yade-dem.org/buildbot/builders/yade-full/builds/1741/steps/shell_3/logs/stdio) -deprecation warnings in ThreeD and TriaxialCompression engines -change the default value of FlowEngine::pressureForce, a flow engine is supposed to compute a flow by default... smallfix in example script - 3 new references - add "Open Source Discrete Element Method" explicitely in the home page, google has to find us nicer documentation of equations for stiffness definition remove useless #define - ship a copy of ipython_directive.py from ipython/HEAD for ipython 0.13 ipython version was checked in two different places, forgot the second one. Still no confirmation of the fix. - some lines comited by mistake, sorry for that. - escape crash on non-iterable __doc__ (some qt class) - define new attributes needed for ipython_directives.py >= 0.13 - fix existing *.rst to use strictly 3 blank spaces in multiline directives (i.e. " ...:") - reference name Tran2012a appearing twice -> renaming Tran2012b - re-introduce the @supress of Christian (removed during experiments) Christian Jakob (21): add addToClump() method in yadeWrapper improvements in clump logic - part 1 Improvements in clump logic - part 2 Added new example script, that show usage of addToClump() and releaseFromClump() Improvements in clump logic - part 3 + 4. shorten the code of replaceByClumps() add a check of python input for replaceByClumps() method further improvements in clump logic some fixes in documentation of wrapper and state fix documentation of clumpTemplate in utils.py Merge remote branch 'upstream/master' let replaceByClumps return a list of tuples with new clump ids and associated sphere ids Merge remote branch 'upstream/master' avgNumInteractions now takes care of clumps some improvements for previous commit doc fix in utils.py and exclude unused Clump.hpp in _utils.cpp updated documentation of clumps in user manual and small fixes in doc in yadeWrapper Merge branch 'master' of github.com:yade/trunk bug fix in replaceByClumps, that could lead to set negative masses on bodies and shorten the code yet another doc improvement in wrapper and user manual concerning replaceByClumps method replaced errors by warnings in addToClump and releaseFromClump in wrapper Donia Marzougui (5): Fix the computation of viscous forces by using the correct positions of bodies (updated in buildTriangulation), and fix some variable names. Remove fictious spheres from the computation of edge surfaces (employed in the computation of viscous forces) Correct the computation of the viscous and normal stress per particle. Correct the sign of some parameters in the computation of the viscous forces. Correct the normal lubrication force. Francois Kneib (24): Continue the implementation of grids : - add GridConnexion Aabb - add GridNode-GridNode Ig2 functor : Ig2_GridNode_GridNode_GridNodeGeom6D - add GridNode-GridNode contact geometry : GridNodeGeom6D - add an example script : examples/grids/Simple_Grid_Falling.py - update utils.py (minor change) Add the possibility of avoiding the contact detection between a group of particle. -> the vector "avoidSelfInteractionMasks" contains a list of masks : all particles having the same mask will not collide if this mask is inside this list. -> the "mayCollide" method now check this too. -> avoidSelfInteractionMasks can be set in python with a list of int. Add the Sphere-GridConnection frictionnal contact : -> new contact geometry : ScGridCoGeom -> new Ig2 : Ig2_Sphere_GridConnection_ScGridCoGeom -> new Law2 : Law2_ScGridCoGeom_FrictPhys_CundallStrack -> update utils.py to handle the new contact type (using avoidSelfInteractionMasks) Merge branch 'master' of github.com:yade/trunk Add the contact following and avoid multiple contact when a sphere is sliding on a grid. -Grids : Fix some minor bugs in Ig2_Sphere_GridConnection_ScGridCoGeom::go(...). -Collider : Change the vector of masks "avoidSelfInteractionMasks" to a single mask "avoidSelfInteractionMask". Create and correct documentation. Grids : - modify the rendering. - Apply correct DEM laws in utils.py to set right kn, ks, kr, and ktw. Now the grid behavior no longer depends on the spacial discretization you make. Modified setContactFriction method. Now it has to be used with an angle in radian unit, no longer in degrees. Try to fix documentation in this files, everywere "yref" appears in the online documentation. Fix Grid periodicity, and allow periodic sphere-grid contacts. Disable a boring warning into the grid construction. Fix a bug in SpherePack.toSimulation. There was a big problem when you tried to add clumped SpherePack (with makeClumpCloud() for example). Corrected toSimulation() (for the last time I hope :-) ). Fix a bug in growParticles(...) method. Growing a clump is now really homothetic. Before that, growing clumps seemed to be homothetic but at the next timestep the original position of members was reseted. Add Inelastic ChainedCylinder behavior law (by Ignacio Olmedo). Create inelastic material and related physics, Ip2 and Law2. Example script and complete documentation will come soon. Add the ability to save to a file a SpherePack containing some Clumps. Into SpherePack : now the toFile method writes a fifth column containing the clump number (-1 if the sphere isn't clumped). The fromFile method uses the new information to append clumped Spheres. Into Shop : loadSpheresFromFile method is modified to read and return the fifth column. Old files with 4 columns are still compatible. Into CapillaryTriaxialTest and CohesiveTriaxialTest : Shop::loadSpheresFromFile is sometimes used here, so remove the clumpId information to match the "BasicSphere" typedef. getStress(...) method : exclude gridNode-gridNode contacts for the stress computing. growParticles(...) method : avoid growing gridNode and gridConnection. Fix some Grids bugs. A minor spelling correction. Add a little class summary and explanations into Grid.hpp. Fix a bug into utils.py when creating a Grid in a non-periodic scene. In the sphinx documentation, add the https GitHub read-only method : git clone https://github.com/yade/trunk.git Avoid GridNode-GridNode contact stiffnesses (kn and ks) to be changed when the growParticles(...) method is used. (GridNodes are not concerned by this method). Add a missing space in the dependencies code lines. François Kneib (1): Correct a remaining bug into the toSimulation() python function. Jan Stransky (9): little modifications added example script for CombinedKinematicEngine little modifications of ConcretePM modification of ConcretePM, improved utils.intrsOfEachBody function improved yade executable: optparse->argparse, yade executable is now loadable as python module modified yade executable, fixing bug 1134422 complete optparse->argparse, --generate-manpage should work improvement of ConcretePM, remover warnings in pack module (randomDensePack and randomPeriPack functions) modified ConcretePM, corrected Peri3dController related examples Jan Stránský (1): minor changes françois (1): makeClumpCloud(...) now takes a "seed" parameter to generate always the same pack. ================================================== yade-0.90.0 Sat, Oct 13 15:16:55 2012 +0200 Anton Gladky (142): Remove RELEASE file. Fix cmake-file to compile. Fix paths in core-files. Update CMakeLists.txt. Fix pygts module. Fix standalone compilation of InteractionLoop.hpp. Enable pymodules on cmake-build. Add version definition to cmake-build. Fix suffix and runtimeprefix in cmake-build. Fix compilation error with gcc-4.7 Fix facetBox generation with height=0. Fix permissions on main.py.in file. Fix installation of binaries in cmake-build. Remove log4cxx from scons-file. Fix binary installation at cmake-build. Remove an explicit defining and importing of listdcxx. Install some py-files for cmake-build. Install glviewer in cmake-build. Fix path install in cmake-build. Update CmakeLists. Return explicite defining of libstdcxx. Causes error. Use rpath for lib-installation. Fix linking of core-libraries. Fix some more linkings in cmake-build. Reinitilize interactions after adding a body. Closes LP:1001194 Move gui-cmake to gui-folder Move cmake for python modules to py-folder. Separate python modules in cmake-build Fix some liking in cmake-build Some fixes in cmake-build. Add autotest to check bug LP:1001194 Give the newly created body an id, which is the size of bodyContainer. Remove lowestFree. Nicer fix for LP:1001194 Add scene->doSort variable. One more fix for LP:1001194. Thanks to Bruno. Fix linking of opengl in cmake-build Fix vtk-linking in cmake-build Install pack.py for cmake-build Add notice about python-demgengeo Add URLs on python-demgengeo package. Fix links. Fix some more linking problems in cmake-build Fix plugin search path in cmake-build Cosmetic changes in cmake-build Fix performance tests. Fix overlinking in cmale-build Make vtk mandatory. Fix -batch binary in cmake-build Partly fix QT in cmake-build. Still not functional yet. Return vtk-feature, but not use it Fix gui in cmake-build. Minor fix in cmake-build Add 2 dependencies to install-section for cmake-build. Enable "options" for cmake-build. Remove technical output during cmake-build. Fix compilation error during non-gui cmake-build Revert vtk-feature. Not mandatory again. Add debug-build to cmake. Use Predicate functions, only when gts is enabled. Place -lrt flag into the right place. Fix rpath issue Remove combine_sources function. Fix header definitions in openmp-accu Fix check-test during no_gts build Fix import libstdcxx on old systems Display information about disabled features Add NOSUFFIX option for packaging. Add lrt to linking on cmake-build Fix linking problem of cgal libraries in cmake-build Fix "typo" in VTKRecorder. Thanks to Vaclav. Remove path to python in some scripts. Move all main libs to usr/lib/yade folder Move minieigen to py-folder Add doSort-option to InsertionSortCollider for forced resorting of interactions. Minor fix in cmake-build Link miniEigen with boost-python libs. Thanks to Klaus Thoeni. Fix version definition for git and bzr. Enable use of custom Boost-libraries. Thanks to Klaus Thoeni. Try to fix cgal compilation. Add possibility to set the lib-path. Fix libpath, if it is non-standard. Fix CGAL-feature for cmake-build. Fix libpath with scons. Replace LIBPATHINST by LIBRARY_OUTPUT_PATH. Fix version definition and generation of pdf. Change Version option to YADE_VERSION. Add option description into CMakeList file. Start to update installation documentation. Start to update documentation on installation. Describe compilation and installation processes with cmake. Minor fix in instalaltion section. Remove scons from documentation. Remove compilation instruction for Gentoo. Check, whether newly added bodies to a clump are already members of the existing clump. Closes LP:1017367 Fix handling ipython 0.13 version. Return CHUNKSIZE option. Update documentation on installation. Better fix for ipython-versions. Hopefully the ipython-interface will not be broken in a future. Remove some bzr-notes from documentation. Remove scons. Minor fix in documentation. Add chainId parameter to body. Implement stopAtTime parameter. Add iterBorn and timeBorn parameters to interactions. Rename chainId to chain. Add python-wrapper to utils.facet Remove timeBorn option in interactions. Remove debug and rebuild options from docs and starting scripts (not functional with cmake) Fix execution error. Use vtkQuad for exporting boxes in vtk-format. (Closes: LP:1026474) Let to use relative paths in INSTALL_PREFIX variable. Remove clumps, if no bodies are present there. Fixes LP:1031644. Remove clump members from clumps during body erase. Move Clump.* files to core/ Add "make doc" command for easier documentation build. Fix some warnings. Add bodiesHandling.facetsDimensions function to get parameters of group of facets. Replace the variables "max" and "min" on "minVal" and "maxVal" in spheresPackDimensions to escape coincidence withi function names Add README.rst file for github Add getBodyIdsContacts to get the list of bodies, contacting with the checked one. Keep pointers of interactions for all bodies, not only where otherId>Id. Add ENABLE_FIXBUGINTRS option. Fix an error in interaction container. Add checkIntrs flag into body to escape useless checks of interaction. Fix segfault with FIXBUGINTRS enbled. Add regression test to check the erasing of bodies, which are in contact. One more step to fix bug of facet-sphere mass interaction. Add function to disable some interactions, if the sphere contacts with several facaets of the same chain. Prevent errors in facetCylinder, if height=0.0 Move fixbugintrs stuff from Newtonintegrator to shop.cpp and constitutive law. Move fixbugintrs stuff into viscoelasticpm, for a quicker compilation. Integrate some ideas into RockPM Some minor fixes in eigen-macroses. Remove testcode for fixing LP:850864 Fix serialize-template for matrix6r. Fix version read from RELEASE file. Fix version number output at Yade start. Fix some compilation warnings. Fix warning due to mix class/struct definition. Honor the flags, which are set in environment. Fix compilation warning »_POSIX_C_SOURCE« redefiniert." Disable CGAL, if compilator is clang. Add information, how to compile Yade with Clang-compiler. Fix batch-mode on platforms, where cpuinfo is not available. Remove rebuild-option from batch-mode. Remove old testing macros in VTKRecorder. Remove accidentally added file in examples. Add normalVel parameter for SpheresFactory to set explicitely direction of velocity for newly generated particles. Backward compatibility is saved. Add RELEASE file for 0.90.0 version. Bruno Chareyre (61): - Direct solver used for periodic BCs and/or compressible fluid - saveVTK for periodic BCs - fix rare but possible junk permeability values (define max/min) -FlowEngine uses boost::thread to precompute triangulation and Cholesky factor while yade is running - interplate using barycenters of old trangulation, instead of voro-center of new triangulation, avoids inconsistencies - make sure voidVolume is positive (FIXME: hardcoded number a.t.m.) - normalize the definition of minimum void volume -add a simple getStress function (periodic or not) move an insert in a more logical place A new contact law adding creep (Standard Material Model) to CundallStrack -missing in previous commit restore the option to NOT homomDeform in periodic BCs (may be rarely used, e.g. when veru big bodies are part of the simulation) restore the option to NOT homoDeform (see previous commit too) - let PeriTriaxController accept very large bodies in the simulation. This change makes it possible to simulate bi-periodic BCs with 2 large planes bounding one coordinate. Example script comming soon. - remove junk output left after debugging work - compatibility of the 6D cohesive-moment law with periodic BCs. - add a simpler way to turn contacts cohesive one by one via an Ip::flag -fix force application in cohesive law (was breaking chained cylinder and sent buildbot error) replace Dem3Dof by ScGeom where CundallStrack is used -remove a series of contact laws as discussed in https://blueprints.launchpad.net/yade/+spec/cleancontactlaws remove contact laws (again), as discussed in https://blueprints.launchpad.net/yade/+spec/cleancontactlaws - Implement multithread solving for the periodic flow - Start implementing CHOLMOD solver using Eigen3 support - Fix flow engine compilation with cmake (some variable with multiple definitions breaking per-file compilation) - CHOLMOD handling in cmake - be quiet when cholmod is disabled remove some "cerr" -reset initCohesion flag for each contact after initializing (else it's rested at each step...) -add functions to export network constrictions and normal and track a point in the triangulation using secondary flow engine (Bruno+Wenrui) - better parameter (vector > 3 doubles) - use cell handles vector instead of CGAL's cell iterators for faster loops - make mirror permeabilities i->j vs. j->i exactly equal in the periodic cases to avoid non-positive definite matrices - fix a few mistakes in permeabillity min/max bounding (permeabilityFactor was not included, and the min was way too high, changing 50% of the values) - make multithread run possible also in the periodic case smallfix in documentation Fix compile warnings. - add export functions for writing system matrix to files reverts commit afa59f17d59a90d3049b9c51c41d28e29fc9081e, which messed up flow code. -fix conflict Document Tesselation wrapper a bit more. Add a complete example script. small optimizations in PeriodicFlow, start implementing matrix-based froce computation (commented blocks) documentation here and there Documentation documentaion fix - a unique function for fluid velocity in cells for both periodic and non-periodic - make fluid velocity computed correctly even on boundary cells with imposed pressure - the boundary are now included in computation average velocity in the simulation - A new function to get total flux through a boundary - Simplify the usage of GSTiemeStepper: no need to set dt at all as soon as GST is present, and setting dt manualy will not disable the timestepper. - Introduce density scaling example script for timestepper and density scaling Small change in the logic of cylinder-cylinder interactions sort exemple scripts documentation fix adapt the interface to the new timestepper logic doc hyperlink for density scaling fix compile error (sorry, it was working with chunksize>1) - remove a loop in Newton as suggested by Anton (Thanks) - update example script - adjustents of strain rate in ex. script Still smallfixes in density scaling Revert "Fix the computation of the shear lubrication force and add the torque of the viscous shear force applied on particles." This commit erased some of the previous changes. Please Donia double-check what you commit everytime. fix two compile warnings The \` are displayed correctly by sphinx. Get them back (at the price of many warnings?...) - remove some "invertSign" in stress functions - add a utils::function to change friction without the need to instantiate a TTCompression engine - make use of Shop::setContactFriction() make warning conditional or it will flood my terminal... example script for cohesive and/or rotational law some updates in this example script make autostop possible again (but still disabled by default) -enable 0-thickness bounding boxes example script for triax+clumps -A new function to grow particles and clumps cleaning script clean sources Chiara Modenese (3): Check the body existence before creating an interaction. (Closes: LP:803774) Add a new entry to yade-conference.bib. Add some entries into yade-conferences.bib Christian Jakob (9): improvements in Law2_ScGeom_CapillaryPhys_Capillarity Fix memory leak in capillary law and make isBroken flag compatible with fusionDetection include calm function fix mask bug in calm function export boxes with vtkrecorder (not tested, boxesCells missing) export boxes with vtkrecorder (visualization works, but is not ok) fix uniax.py and periodic.py example scripts fix and improvements of some example scripts delete unneeded file Donia (2): Fix the computation of the shear torque. Fix the computation of the torque and rename some variables. Donia Marzougui (12): Add spheres' rotation to the computation of the viscous shear force. Compute the viscous shear bulk stress. small fix in min Y Merge branch 'master' of github.com:yade/trunk Add the normal and tangential lubrication force Compute the viscous shear stress for each body Add rotation of spheres in the computation of velocity, Compute the viscous and normal stresses applied on each body, add the normal and shear lubrication forces Fix the names and tests of forces and stresses applied on particles Merge branch 'master' of github.com:yade/trunk Fix the computation of the shear lubrication force and add the torque of the viscous shear force applied on particles. Fix the computation of the shear lubrication force and add the torque of the shear viscous force applied on particles. Fix the computation of the shear lubrication force. Fix the computation of the normal lubrication force remove the computation of some characteristics lengths for two fictious spheres. Francois Kneib (5): Add the contact between two different ChainedCylinders. -> add new contact geometry : "ChCylGeom6D" -> modified "Ig2_ChainedCylinder_ChainedCylinder_ScGeom6D()" to instantiate the new contact geometry when it is necessary -> add new law : "Law2_ChCylGeom6D_CohFrictPhys_CohesionMoment()" -> add new CohFrictMat parameters : "CohFrictMat.cylCylNormalCoh" and "CohFrictMat.cylCylShearCoh". They are used to set the cohesion between two different ChainedCylinders ("CohFrictMat.normalCohesion" and "CohFrictMat.shearCohesion" was already used inside a ChainedCylinder). Fix bugs into "Ig2_Sphere_ChainedCylinder_CylScGeom6D" and "Ig2_Sphere_ChainedCylinder_CylScGeom" Fix an ScGeom6D bug : declare the cylCyl boolean variable as hidden in the YADE_CLASS_BASE_DOC_ATTRS_INIT_CTOR_PY instead of in the ScGeom6D class core. Merge branch 'master' of github.com:yade/trunk -python function for handling grid objetcs Create new classes to add grid simulations. Grid are made of GridNodes and GridConnection (two new shapes inheriting from Sphere) Jan Stránský (15): added periodic cell's deformation and strain measures improvement of Cell documentation Cell class improvement made stressTensorOfPeriodicCell deprecated Fixed bug in export.VTKExporter, corrected some docstrings little more fixes in export module docstrings Added Law2_ScGeom_CpmPhys_Cpm class other modifications of CpmMat related classes Added Ip2_FrictMat_CpmMat_FrictPhys functor corrected mistake from previous commit Fixed compilation issues in ConcretePM.cpp reported by Anton Added utils.setNewVerticesOfFacet function improved documentation (corrected some warnings and errors during building docs, added inherited-members directive) improvement of ymport.unv and export.VTKExporter add example file for previous commit fixed small bug in export.VTKExporter.exportFacetsAsMesh add geom.facetSphere another bug fix of export.VTKExporter.exportFacetsAsMesh Klaus Thoeni (1): - add possibility to use local boost installation (e.g. -DLocalBoost=1.46.1) ================================================== yade-0.80.0 Wed, May 2 21:05:12 2012 +0200 Anton Gladky (81): Add forgotten format to ymport.textExt Replace python-numeric on python-numpy in installation section of documentation Fix --test Add check on body existance in GlobalStiffnessTimeStepper. Fixes LP:891540 Fix closeGap for side walls of cylinders and cones Return back fix from r2961. Bruno, please, make a check before commit. Add VTK-5.8 to header search path Fix compilation with VTK-5.8. Remove vtksqlite. Make the code workable with ipython 0.11 Hopefully fix compilation error on clean environment. Do not let PWaveTimeStep return "infinity" Add maxMass parameter to Spheresfactory Update description of maxMass Fix compilation with boost >=1.47 DomainLimiter: * Delete spheres only * Add mass calculation * Add volume calculation * Add mask-parameter Add mass-parameter to VTKRecorder. Add eigen2-option into scons to force compiling with eigen2. Fix eigen2 compiling, when "eigen2=False" by default. Check newly created spheres on intersection with spheres, which were created in one iteration in SpheresFactory. Hopefully fixes "jumping sphere" problem, discussed here [1] Minor changes for ipython 0.11 and 0.12 Add speed-parameter to Scene and Omega Fix calculation speed show in batch-system Fix prediction of finish time in batch-mode. Fix compilation warnings on Ubuntu Precise. Use gravity inside of NewtonIntegrator in examples and in tests. Return the average speed of calculation from the last 10 iterations instead of current speed for the last step. Hopefully makes prediction of finish-time in batch-mode more accurate. 1. Fix a compilation error with eigen2 2. Add a variable to define number of steps, which are taken into account by speed calculation. Rename .bzrignore to .gitignore. Merge pull request #1 from gladk/master Start to update main sphinx page. Merge pull request #2 from gladk/master Fix --check and --performance tests after moving testing scripts. Add deleted by mistake update of main sphinx page. Update of the main sphinx page. Fix version defininition with git. Ignore *.pyc files. Merge pull request #4 from jakob-ifgt/master Fix version number for gits. Caused error during doc-build. ResetRandomPosition should be a child of PeriodicEngine, not GlobalEngine. Fix some examples. Reenable gts-scripts. Let facetBunker.hBunker be zero. Add parameters body::iterBorn and body::timeBorn to track the moment, when the particle was added to simulation. Fix link to a wiki-page. Add libboost-program-options-dev to installation section. Add gtk2-engines-pixbuf to installation section. Escapes "Gtk-WARNING **: Im Modulpfad »pixmap« konnte keine Themen-Engine gefunden werden". Escape warning due to ipython 0.12. Do not output a false backtrace with ipython >= 0.10 due to log4cxx crash. Add python-bibtex to installation section of documentation. Remove useless check of ipython version. Now all systems must have >=10. Do not redeclare cundallInteractionPhysics and mindlinInteractionPhysics in local scopes, as they should be global. Fixes crash LP:966186. Add gmp for linking, when cgal-feature is activated. Fixes LP:968188 Fix unicode characters. Parse Yade.tex and remove empty verbatim-blocks. Happens on Precise. Add ipython_directive for ipython 0.12. Some adjustments to build docs on Precise-systems. Prevent cross-importing of yade modules. Closes LP:980000 Make nice output in ipython 0.12 like it was in 0.11. Make main.py.in more readable. Remove accidentally added ipython_directive012 extensions for all ipython versions. Remove log4cxx support and yade.log module. Fix compilation error on Debian Sid due to changed API of python-git module. Minor update of examples. Add isActive parameter to Interactions. Merge branch 'master' of github.com:yade/trunk Let isActive flag be changeble. Remove "never_use_this_one" option from scons. Remove "sphere-padder" option from scons. Fix autotest "rotation matrix from quaternion". Remove subdomains feature. Fix compilation warnings. Initialize angle* variables. Add geom.facetParallelepiped function. Remove executable-flag from scripts. Remove executable line in some examples (it cannot be in /usr/local folder on all machines). Fix some typos. Change default value in Real-variables from NULL to -1.0. Fox compilation warnings. Fix compilation warnings. Fix some typos. Add RELEASE file for 0.80.0 version. Bruno Chareyre (72): - (failed) attempt to fix the documentation of export.py - fix doc generation for the export module. Still some fixes needed concerning doc convention (wrong parameter declaration for some functions) - makeCloud can now generate spheres on a plane (see https://answers.launchpad.net/yade/+question/175606) remove unused bool "is2D". - makeCloud handles periodic 2D clouds with uniform distribution and psd - yet more fixes. The auto-setting of cell.size was not correct (typo in components) - code cleaning - Documentation of the capillary law (warn users on distanceFactor and neverErase flags, as in http://www.mail-archive.com/yade-dev@lists.launchpad.net/msg07391.html. - 2 additional references - make body iterators jump empty slots, as suggested and discussed here: http://www.mail-archive.com/yade-dev@lists.launchpad.net/msg07406.html - revert the (now useless) fix of r2961, similar tests can be removed in many places after the change in pointer logic is well tested. - remove a script, as agreed by Christian (https://answers.launchpad.net/yade/+question/178830) - add/use the "neverErase" flag in Hertz laws. - back to ordinary vector<>:iterator for bodyContainer - smart body pointers again, also fixing the erase(0) case inside begin(). - remove the if(body) test in GSTS loop. - attempt to fix https://bugs.launchpad.net/yade/+bug/897237 - small fix in the theoretical background Should finally fix https://bugs.launchpad.net/yade/+bug/897237 - capillary model: nullify Fcap and volume when P changes from something to zero - remove the if(!b) test in a few important places (proof of concept + don't waste time doing the same test twice) - fix wrong shape type testing that was giving wronf solid volume. - update publications - fix one bug mentionned in https://bugs.launchpad.net/yade/+bug/901146 (makeCloud with undefined num and psd) - more articles - normalize psdCumm2 after the scaling, not before (since the scaling is determined from absolute psdCumm2). - make it possible to include bodies larger than period conditionally, using allowLargerThanPeriod flag (as discussed in https://answers.launchpad.net/yade/+question/181411, thanks Giulia for the scipt) - document undocumented parameters of bodies creation (in utils.sphere) - enable interaction of two larger-than period bodies (e.g. cylinder vs. box) in addition to previous commit. - point to the "allowBiggerThanPeriod" flag in the LOG_FATAL message. - improvements in TW interface so that one can save and load "state" files to be used for defining deformations (this will actually be used by E. Ando for analyzing experimental data!) - code cleaning - make deformation analysis possible for arbitrary simulations (no longer depends on TriaxialEngine for defining the bounds) - fixed undefined variable when Triaxial engine is not present - erase bodies before adding spheres from file - Fix typo (thanks for pointing out Klaus, actually the "i" was correct: elastic-plastic means that the law is incrementaly elastic OR plastic, while elasto-plastic laws can be both at the same time) - big reorganization of the triangulation lib, which becomes a template library. Usage of different "info" classes for different engines is now possible, so that TWrapper doesn't have to instantiate useless variables inherited from the flow problem, and vice-versa. Will also worj for periodic flow model coming soon. - general code cleaning and removal of old files - a few hyperlinks fixed in *.bib's - remove custom dependencies breaking compilation Merge the "collide" branch to trunk (branch history at https://code.launchpad.net/~bruno-chareyre/yade/collide2) - missing reference update - remove the velocityBins lines in tests since velocityBins are removed from trunk - Draft version of the periodic fluid model from Donia Marzougui smallfix in flow model - fix a bracket mistake triggering ghosts definition after each insertion in periodic::triangulate() - add tests on boundary ids (<0?) before doing anything on them. - account for sweepLength in bounds comparisons of Collider::probeVolume() - removed unused file - Collider: fix the probe function (account for current body's position + typo in one comparison) - Newton: remove the useless "force" from damping2nd parameters, don't apply meanfield vel when not periodic (only diff is it avoids a multiplication of null matrix and vector), make gravity attribute work correctly - GravityEngine: add LOG_ERROR on deprecation (LOG_WARNING are not displayed in prebuilt package right?), better use Newton::gravity to save cpu time. - Clump.hpp: fix the template function that was using the old saveMaximaVelocity, use the new saveMaximaDisplacement instead - PeriIsoCompressor: never pause() in some c++ code, since it resuults in stuck GUI without a chance to run more timesteps - dispatching: don't make bboxes larger for static bodies - gravity : deprec. warning - collider: try and fix Anton's crash (https://lists.launchpad.net/yade-dev/msg08256.html and next in the thread) - Newton: workaround the resetForce bug in OMP build (https://bugs.launchpad.net/yade/+bug/923929) - small changes left behind in a previous commit (fix the "undefined IPCells") problem - include periodic PeriodicFlow in the triangulation lib - Compute the critical timestep correctly when clumps are present. - make the "smoothing" of strain rate evolution configurable via a registered attribute (default=unchanged behavior) - tell more explicitely that Young/Poisson is interpreted differently in different functors in their respective doc - make the LWStress function return pyList instead of tuple - documentation in makeCloud - default stiffness are zero, not NaN - TWrapper: python binding for local deformations - Flow: fixes in the periodic BCs - Flow: improvements in BCs handling and imposed fluxes, toward per-step update of the RHS + code cleaning - Periodic flow: support for optimized solvers (not in trunk) by introducing cells indexes - Periodic flow: optimization of periodicity by precomputed Hsize[k]*gradP products - SpherePack: more code documentation in makeCloud - missing in previous commit (sorry) - fix periodicity of interactions when large bodies are present. Demo script comming soon. - implement the compatibility between periodic fluid model and large bodies in PBCs using the "boundary" objects - Make unbalancedForce() account for Newton::gravity (and my first git commit!) - mirroring bzr3038 + bzr3039 - example script for large bodies in periodic BCs compile error; remove warnings; add last diffs from Donia Smallfix in index.html - fix doc string in the SpherePack.toSimulation function (crashing my terminal...) - update one reference Merge branch 'master' of github.com:yade/trunk - fix bug https://answers.launchpad.net/yade/+question/192152 in SpherePack.toSimulation() - fix a documentation bug (https://bugs.launchpad.net/yade/+bug/978234) fix a warning in DomainLimiter make periodic triax engine ScGeom friendly (a few scripts to be fixed in next commits) - remove reversedForces flag and/or use ScGeom in the example scripts for periodicity Merge branch 'master' of github.com:yade/trunk - add the possibility of updating only the RHS in the fluid problem, without rebuilding the triangulation+matrix Fix bugs introduced by incomplete addition of Real dt in some functions signature (messing virtual inheritance) Merge branch 'master' of github.com:yade/trunk Fix the problem of null pressure measured after retriangulation. Bind a measurePressure() method for the periodic flow engine allow BCs update without retriangulation via Flow::updateBCs() Fix a bug in PeriodicVertexInfo::ghostShift Merge branch 'master' of github.com:yade/trunk Fix a few bugs discovered with Luc: Fluid forces splitted between particles and their images in PBC, and others. Implement the updateBCs feature for PBCs. Enable pressure interpolation in periodic coupling The documentation now reflects the recent changes in collider's logic. Christian Jakob (11): Law2_ScGeom_CapillaryPhys_Capillarity can now be used with Ip2_FrictMat_FrictMat_MindlinCapillaryPhys for hertz model Add forgotten files Added new example script (examples/generate-psd-with-poro-example.py) Fixed examples in example folder, new folder called not-working move all model examples to example folder, fixed all examples, that are not in test folder moving checks and tests to scripts/checks-and-tests and fixing some examples in examples/test moving checks and tests to scripts/checks-and-tests and fixing some examples in examples/test moving checks and tests to scripts/checks-and-tests and fixing some examples in examples/test fixed all examples Improvements in Law2_ScGeom_CapillaryPhys_Capillarity.cpp (included isBroken flag and updated checkFusion) make fusionDetection and isBroken flag compatible Francois Kneib (4): Added the interaction between cohesive sphere and cohesive chained cylinder. Created Ig2_Sphere_ChainedCylinder_CylScGeom6D and Law2_CylScGeom6D_CohFrictPhys_CohesionMoment in Cylinder.cpp. Created CylScGeom6D in CylScGeom6D.cpp. Moved CylScGeom from Cylinder.cpp to CylScGeom6D.cpp Interaction test between a cohesive sphere and a cylinder. Fixed some bugs in Ig2_Sphere_ChainedCylinder_CylScGeom6D() and Ig2_Sphere_ChainedCylinder_CylScGeom(). Now the contact is rightly followed when a sphere move along a chained cylinder. It works with frictional cohesive or frictional contacts. Fixed some bugs in Ig2_Sphere_ChainedCylinder_CylScGeom6D() and Ig2_Sphere_ChainedCylinder_CylScGeom(). Now the contact is rightly followed when a sphere move along a chained cylinder. It works with frictional cohesive and frictional contacts. Jan Stránský (6): 1) modification of CpmState class 2) some changes in py/pack/pack.py Added Matrix6 Added damageTensor to CpmState Fixed bug 922744 (export module) Fixed SpherePack and _getMemoizeDb incompatibility with periodic boundary conditions Corrected assert(Matrix3) in Math.hpp O and wrapper.Omega made part of __builtin__ (equivalent to global variables). Should solve problem of question 182459 Omaga can contain more Scene instances Fix of previous commit Klaus Thoeni (3): 1. delete line which was commented out by Anton 2. derive WirePhys from NormShearPhys in order to use implemented functions which just support NormShearPhys (NormPhys is not supported, however, in the future if more contact laws with normal force only are implemented it might be better to introduce a boolean or so) 1. fix some typos 2. derive WirePhys from FrictPhys 3. compute limitFactor for WirePM 4. add limitFactor and normal force of wires to wpm in VTKRecorder - add capability to deactivate the damping in NewtonIntegrator for individual particle via O.bodies[id].state.isDamped=False - use this new property in examples/WireMatPM/wirecontacttest.py Luc Scholtes (7): add compressibility to fluid flow scheme delete obsolete line in flowEngine.hpp resolve bug 983888 add compressibility to periodic flow engine cleaning pack.py clean FlowBoundingSphere.ipp file Apply changes from Luc Sholtes. Luc Sibille (1): Update some publication references Rémi Cailletaud (4): Added google analytics script to sphinx doc Build infrastructure - removed r0calcul8 from builbot nodes (hardware issues) buildbot now builds two times, with eigen2 and eigen3 now uses xvfb for doc generation (for qt part) buildbot home on slaves changed reenabling r0calcul8 ================================================== yade-0.70.0 Sun, Oct 23 16:49:34 2011 +0200 Anton Gladky (129): 1. Prediction of simulation finish time is added to HTTP-batch server. 1. Fix of a previous commmit. Wrong prediction time. 1. HarmonicMotionEngine is added with example. 1. HarmonicMotionEngine update. 1. HarmonicRotationEngine is added including example in regular-sphere-pack.py 2. TranslationEngine is parallelized with OpenMP. 3. Translation and Rotation engines are working with Dynamic-bodies properly (temporal solution). 1. Regression tests for translation and rotation engines are added (not fully tested yet, but "all tests passed") 1. Changes from 0.60 (debian packaging). 1. scripts/debian-prep creates orig.tar.gz and removes .bzr directory 1. debian-prep creates version number with "1" at the end 1. Backporting changed from 0.60 branch 1. qt4 is "on" by default 2. Syntax fix in documentation. 3. export.text is disabled in regular-sphere-pack example 1. Initial yade.spec is added for RPM packaging 1. Fix bug in VTKRecorder (Thanks, Vaclav) 2. Cohesive-chain test disabled (please, fix it, who can...), https://yade-dem.org/buildbot/builders/yade-full/builds/127 1. Changes in .spec file 1. Some preparation to dpkg ver3. 1. Deleted isDynamic from Translation Engine 1. Commented SubdomainBalancer packs.py to make it workable with default compilation. 1. Partly fixes 692759 1. debian/copyright is changed according to debian requirements. 2. control-template is changed to make the package "lintian clean" 1. Fixed warnings in packs.py->utils.py 2. Created geom.py module for generating geometrical forms from facets. 3. utils.facetBox and utils.facetCylinder have been moved to geom.facetBox and geom.facetCylinder respectively. Backward compatibility is saved. Examples are fixed. 4. closeGap in utils.facetCylinder and utils.facetCone works now only when |anglRange|>math.pi 5. geom.facetBunker is added with corresponding example in pack.py 1. python-imaging is added to installation section of sphinx-docs as well as to debian-dependency 2. geom.facetHelix and facetPolygon are added with examples 1. Closes #702296, if somebody uses rotationengines, please, update! 1. regression tests of kinematic engines are updated and activated. 1. Updated regression tests. Rotation engines. 1. HelixEngine updated, dublicated code deleted. 2. HelixEngine is added to regression tests. 1. Extra Quaternionr q() definition is deleted from RotationEngine (thanks, Bruno) 1. Shop::getStressForEachBody() is now returning stresses for FrictPhys+ScGeom combination; not only NormShearPhys+Dem3DofGeom 1. Added debian/watch to track new yade versions. 2. Some small modifications in debian/control-template to escape lintian warnings. 1. vtk-5.6 path is added to SConstruct to make it compilable with Debain experimental 2. Fixing spelling mistakes in documentation. 3. debian/ -directory changes. 1. Fix lintian warnings in manpages. 1. jquery.js in documentation is now symlink to a packaged jquery, if it is available. 1. Debian-directory cleaning and modifying (checked with lintian -IE --pedantic, no warnings at all). 1. openmp-feature is added into the debian-build options. 1. Hopefully fixes previous error on building PDF (not checked yet). 1. One more try to fix tex-failure. 1. Missing files and licenses are added to debian/copyright 1. yade-support plugin is added to lib/SConscript as described here https://bugs.launchpad.net/yade/+bug/707966/comments/2 1. Hopefully fixes bug 707966, additional libraries are added for linking in shared libraries (vtk*) 1. Small fixes in debian/copyright to escape lintian-warnings. 1. debian/ is moved to scripts directory 2. scripts/debian-prep scripts copies ./scripts/debian/ to ./debian/ 1. Regression test of yade.plot.addAutoData() is partly disabled is due to https://bugs.launchpad.net/yade/+bug/715739 1. Build-script and buildbot-config are moved to scripts/build-infrastructure 1. Updated installation instructions 2. Updated spec-file for RPM-packaging 1. Added a patch to escape -rpath (an ugly and "very" experimental) 1. Fixed Tutorial link in documentation (taken from https://www.yade-dem.org/sphinx/tutorial.html) 2. Some small fixes in documentation 3. Changes in spec-file for RPM-packaging 1. LOG_INFO and LOG_WARN are disabled when log4cxx feature is disabled. 1. Added shop.py. Like shop.cpp but in python not to make yade.utils too crowd 2. Added yade.geom and yade.shop to sphinx 3. Some fixes in docs 4. yade.shop.spheresPackDimensions is added with corresponding example in packs.py 1. scripts/debian-prep does not insert now distribution name into the version number. Update of spec-file for RPM packaging 1. vtkNetCDF was deleted from vtk-libs due to absence in libvtk5.6-dev. Seems functionality is not corrupted. 1. python-sphinx dependency for Debian is changed to >=1.069 1. Added python-imaging to Debian packaging rules. 2. Fix spelling in documentation. 3. Debian-Template yade-doc file is fixed (0.60 version was hard-coded) 1. Update installation information in documentation. Fix in installation documentation 1. RPM-spec file update 2. Old yade.spec was moved from the root directory to scripts/RPM_packaging 1. Fixed some docs 2. shop.spheresPackDimensions can accept now a list of bodies, not only the list of their id's 1. yade.shop module is renamed to yade.bodiesHandling to get more adequate name 2. added bodiesHandling.spheresModify to modify a pack of spheres. Corresponding example is in examples/packs/packs.py 3. added bodiesHandling.sphereDuplicate to make a copy of sphere 1. Renaming shop.py to bodiesHandling.py 1. ForceRecorder renamed to ForceTorqueRecorder 2. TorqueRecorder is added 1. Changed yade.spec file. Seems, working now. INSTALL_PREFIX option is added to CMakeLists.txt CHUNKSIZE option is added to CMakeLists LIB_EIGEN_PATH is added to CMakeLists.txt Typo fix in previous commit Fix formula displaying in utils.getViscoelasticFromSpheresInteraction "mask" parameter is added to GravitiEngines to make it more flexible. 1. ForceRecorder.totalForce and TorqueRecorder.totalTorque are now accessible from python interface 2. Fix in TorqueRecorder: getTorque is added 1. Fix TorqueRecorder calculation utils.shiftBodies function is added to move bodies without velocity update. 1. in utils.sumTorques and utils.sumForces py::tuple has been changed to py::list. I think it is more convinient. 2. added RuntimeError() to checkTestTriax.py. Bruno, are you agree? 1. Added new checkWeight test to --check scripts 2. It is not neccessary now to add the script name into the checkList.py, just drop a new script into the scripts/test/checks directory 1. Updated build-farm script (building Debian and Ubuntu packages for different distributions). 2. Updated installation section of documentation (Gentoo installation). 1. Some changes in installation section (Gentoo) 2. Check tests are now running like --test, returning error code if something went wrong. 1. Performance test is added. Initial script. 1. Small changes in installation section. 2. voxelPorosityTriaxial doctests are transformed to a simple source code demonstration due to results instability (candidate to --checks section http://www.mail-archive.com/yade-dev@lists.launchpad.net/msg06635.html ) 3. Added initial sqlite database of performances on different machines. 1. Fixes bug #724396. Thanks Sergei and Vaclav. 1. Make trunk compilable with Eigen3 (coming soon). No performance test results yet. 1. Color output for --performance tests is added. 1. Fix doc generation of the previous commit. 2. Added a link to revision archive on yade-dem.org Color output in --performance tests is deleted. Add Eigen3 transparent support: if eigen3 persists in the system, it will be used by default, otherwise - eigen2 Fix Eigen3-Eigen2 detection variables. Fixes 748943. Thanks Bruno for pointing out Add "stop" button at the batch-panel Fix spelling errors: allows to... 1. Replace "subscribedBodies" to "ids" almost everywhere in the code. 2. Fix a couple of examples. 1. Move Dragengine from SphereFactory to ForceEngine. 2. Fixes in ForceEngine (crashed, when force applied to non existing body) Add LinearDragEngine 1. Fix a crash, which appears, when force is read from non-existing body. 2. Fix warning in CohesiveFrictionalContactLaw 1. --check tests are now copying into the libDir/py/yade/tests/checks folder to make it available in packaged yade version. 2. Rename "dataTriax" folder to "data" to make this folder and for other tests suitable. Added syncSizesOfContainers to forceContainer::reset() for the case of not using NewtonIntegrator in scripts Remove PressTestEngine, because python-variant works much better Fix an error, when spheresPackDimensions tried to get states of non-existent bodies Skip empty ids in KinematicEngine Fixes nans in ViscoelasticPM, when kn=0 or ks=0. Fixes 804650 Fixes crash, causing by saving-loading simulation with erased bodies. Fixes #803774 Add one more --check scripts to check GravityEngine and NewtonIntegrator Change the number of steps for checkGravity.py Add O.wait() at the end of checkGravity-test. Hopefully will fix the buildbot-error. Add utils.getSpheresMass Add mask-option to SpheresFactory Add ids-parameter to SphereFactory to get the ids of newly created bodies Add utils.psd(). First try Add mask-parameter to utils.getSpheresVolume and utils.getSpheresMass Fix (hopefully) simulation abort, when an interaction::erase is trying to delete an interaction, where one of bodies is already removed from simulation (id2) 1. Add PSD-support to SpheresFactory (not finished, but already working) 2. Add exception in utils.psd(), if all particles have the same diameter Simplify utils.psd() Add forgotten PSDuse-variable initialization in SpheresFactory Add comment on doubtful check of b2 in InteractionContainer::erase Simplify PSD-input data for SpheresFactory Fix file-creation error in yade-batch, when name of log-file is longer, than 255 symbols. The name cuts off now. Add exactDiam flag to SpheresFactory 1. Add example of SpheresFactory into packs.py 2. Add stopIfFailed flag to SpheresFactory. -Remove dublicating item in references Fix some errors during doc-generation Fix crash in capillary law (removed bodies were not checked) Add mask-parameter for spheresPackDimensions Fix mask-check in several places Fix in spheresPackDimensions. The bug appeared, when only mask was inputted Add mask-parameter to spheresModify. Fix a default parameter for ids in spheresPackDimensions Add some more parameters to spheresPackDimensions 1. Remove "consider" parameter from textExp function 2. Add "mask" parameter into textExp function 3. Add one more format for output. Delete all corresponding iterations explicitely, when the body is erased Fix compilation error during debug-mode Move not-working examples into the corresponding folder. Update news Fix link to installation page 0.70.0 Bruno Chareyre (130): - Rename Cohesive law functor with "6D" as in "ScGeom6D" - Same for chanedCylinder's Ig2 functor. - update scripts. - Fix compile crashers due to bad include paths. - Keep robust kernel the default. - re-enable cohesive chain test - declare deprecated names (part of r2543) - revert experimental parameters previously commited by mistake in cylinder script - Make TriaxialTest behave correctly by default - as it used to, i.e. generate the correct number of particles in a cube of size 1. - disable the last comparison on velocities (see https://lists.launchpad.net/yade-dev/msg06178.html) - add the mandatory "bool periodic" parameter for makeCloud. - Prepare linear solver usage in FlowEngine. - Some cleaning. - Add inexes to triangulation cells fro vectorization. - remove a warning in Tesselation.cpp - fix FlowBonding sphere for first iteration and a few other things - make GaussSeidel virtual in FBS - handle different solvers. - small optimization on dt divisions. - FlowEngine : rewrite updateVolume so that cell volumes are not computed twice. - Remove irrelevant remark in SCPE - Some missing "6D" here and ther in decorations (could affect only devirtualized build) - Implement tensile plasticity and add the corresponding parameters in CohFrictPhys. - recent commits broke or modified something in cohesive law. The reg. test points it out. - restore default CohFrict behaviour - PeriTriax : remove the constant-distance approximation in stress definition, using wrapped coordinates instead of radii. - Get back standalone energy tracing in ECL. - Cylinders : handle cylinder-sphere contacts with a fictious state representing interpolated motion between nodes and passed to ScGeom - Cylinders : implement CundallStrack for interaction between 3 bodies (1 body + 2 nodes) - Cylinders : a number of fixes - no more bugs it seems - Cylinders : handle zero-sized element for terminating chains - Cylinders : update scripts; add example script chained-cylinder-roots.py, with cylinders, spheres, and boxes - Ig2_Box_Sphere_ScGeom : small class name fixes ("6D") - utils.py : adapt cylinders linking - ElasticContactLaw : don't use Body::byId when possible - ScGeom6D : put quaternion normalization between #ifdef guards (debug only) - clean the script. - recommit 2608 with fixed reg test (making ratcheting correction negligeable with dist=.999999999999999999*size) - also fix the compile errors - r2608 again, sorry. Local tests are invalidated by last changes in setDynamic, but the commit is correct. - finish implementation of contacts tracking on chained cylinders. - some cleaning and doc update Fix : mistake in prev. commit while cleaning cylinder code. - recommit 2617 without the autoformating from kbibtex. - Doc : give precise references for triaxial algorithms. - Deeper integration of isDynamic semantic : skip velocity integration, damping, DOFs blocking, and exact spherical rotations, when !isDynamic. 0-density bodies should be safe. - Partial DOFs blocking is not implemented yet. - ratcheting documentation : improve and unify source and inline versions. - As in r2615 : skip velocity update, damping, DOFs blocking, and exact spherical rotations, when !isDynamic. 0-density bodies should be safe. This is a merging r2615 with the per-DOF blocking and clump handling from r2621. - updated FIXME, it seems we do the same thing twice -Fix kinetic energy definition. - Fix stripes display - Add secondary light and register light parameters - define specular exponent for smoother display - define and use GL display lists - review and update of documentation in a series of class - adapt TSC and TCE to new non-dynamic behaviour (more changes and cleaning planned) - remove prevNormal and frictionAngle from FrictPhys - Add temporary FrictPhysTransitory for compatibility of old laws - Rename Ip2_2xCohFrictMat_CohFrictPhys for names consistency, as suggested by Janek -As in r2615 and r2622 (both reverted), and partialy in r2626 : skip acceleration, per-dof blocking, damping, and velocity update for bodies and clumps in case of DOF_ALL blocking (per DOF operations are useless in such case). Please don't revert unless there is a reason to compute DOF_ALL's acceleration and it is guaranteed bug-free (see list). Discussion more than welcome, of course. - Fix one mistake in previous commit (incrementing angVel even for asphericals), sorry. - Move clump damping after the foreach(member) loop, so that it will damp the total acceleration at clump's level. - Don't damp clump members. Instead, compute undamped acceleration and damp it at the clump's level. - Similar mechanism implemented for DOF's blocking : compute unblocked accelerations and block at the end (remember blocking one DOF triggers tests and divisions). - Still possible optimizations (especially for spherical rotations and translations), see https://blueprints.launchpad.net/yade/+spec/clump-forces. - One FIXME : aspherical damping is not correct a.t.m. since it uses torque where angAccel should be provided (suggested fix in sources). - Probably fixes a few inconsistencies in previous commits. - Missing in last commit (fix builbot failure) -Fix sphere rendering for closing/opening qtview (closing was deleting existing display lists, new view was empty) -Make quality apply for striped spheres as well -Make it possible to turn duplicated spheres on/off in periodic BC -Separate GLUT and striped spheres functions for clarity -Optimize stripes rendering with GL_TRIANGLE_STRIP - A checkTest for triaxial simulations. - Add a function (and python wrapper) computing the exact mean stress in each sphere. -fix compile error in prev commit. - Integrate check tests in yade options (see README). - Handle PBCs in Shop::getStressLWForEachBody (not tested) - uncouple Hsize and trsf, add setters for attributes Cell::trsf and Cell::refSize, update all scripts. (see http://www.mail-archive.com/yade-dev@lists.launchpad.net/msg06101.html and next in thread). - Fix some temporary stuff left behind (safe but useless) + get back one line of comment removed by mistake. - update periodicity documentation - rename Hsize->hSize to be consistent with names convention. - remove a duplicated ref and add a CGAL ref - one file left behind in last commit, sorry. - Add resetTrsf() to set trsf without side effects. - minor updates of attributes doc to be consistent with recent changes. -Don't maintain refSize in setHsize, in hope that we will not drag it forever, as suggested in https://lists.launchpad.net/yade-dev/msg06836.html. - Fic classname and give working example. - update bodyStressTensor doc. makecloud update (recommit after fix) - Fix psd transformation from mass distrib to number distrib. - Make sure the function will always return the number of particles required by the user as soon as it is psecified. - If num is specified, generate sizes the deterministic way by descending order. - make porosity parameter optional and give it a default value. - update doc and script. - make Cell.size read/write for faster hSize setting. - improve and simplify documentation (the older method is no longer documented) small typo in property name ("sizes" instead of "size") - Move older or redundant interface between #ifdef CELL_BACKW_COMPAT guards. - Replace refSize by current size in PeriTriax - forget adding the "s" in "cell.sizes", since existing scripts are already using "cell.size" -> fix sphinx doc about this. - some more Cell doc. After typing the paragraph on display, I realize w'd better not update refHSize when trsf=s-------------- This line and the following will be ignored -------------- - some more cell doc. As discussed on the list: - Don't update refHSize when trsf is defined - Update it when hSize (like before) or size are assigned - use current size in PeriEngine - remove unneccesary cell attributes - replace setBox by size - update doc (atributes and header) consistently - Forgot to save Cell.h before committing : updated header doc. Yet more cleaning in cell (sorry). - remove FrictPhysTransitory - A few changes in Cell as discussed in the list. - Updated doc. - Attempt to fix buildbot output in triaxCheckTest (LOG_INFO are disabled by default on my machine but the buildbot has a different behavior it seems, which gives lots of junk lines) in the log. - Add a script demonstrating use of inclined initial cell and reseting trsf - Small doc or formatting fixes here and there - add hSize parameter to makeCloud so that it can generate arbitrary parallelepipeds (previously working only by luck on a few special geometries) - documentation - usage in script, which uses now more funny shape (try it!) - Preparation for citation (wiki won't accept text file uploads => commiting to trunk) - Include Yade citation page in doc, and add links in different places. - Add 'InCollection' keyword in bib2rst - One missing file + updated links to individual pdf's in pub.rst.in Missing in previous commits (the most important file...) - Fixing a few links. - more fixes... - A typo left in index.html. Changes in this file are not reflected in documentation built localy, and erasing the _built dir breaks sphinx builds. It makes changes in sphinx main difficult. -link to discussion of citation model - run "xelatex Yade.tex" twice, so that the content table is generated correctly. - Small fix in makeCloud (hSize not passed correctly in recursive calls) Format check tests output better. Small fix in unbalancedForce doc. - per-point imposed pressure mechanism. - per-point imposed pressure mechanism - one fix in permeabilites along boundaries - remove save_vtk flag and add python wrapping - Add functions for constrictions sizes statistics. - Remove a few compile warnings. -fix accents and reference name in Catalano's entries -Update clumps forces and torques in Newton, as in https://blueprints.launchpad.net/yade/+spec/clump-forces. -Update Shop::unbalancedForce accordingly -Remove the old unbalancedForce code from TSController and make it point to the Shop version. - re-fix fluid area definition - some cleaning here and there (Donia+Bruno) - fix fluidArea correction (S0) - Optimal computations by storing fluid/total ratio in cells info - handle changes in cell's volumes sign - add converters from Eigen vectors to CGAL vectors - small fixes in references - cell volume update was disabled -> enable again - Fix the behavior of bodies moved with the mouse with respect to the new isDynamic behaviour. - The velocity is now defined correctly so that contacts on the moved object are computed correctly - use same ordering convention for boundaries in triax and in aabbBoxes. - removed useless variable - correct initialisation of compression axis - Set default values of walls ids - Fix cells volume functions - Add a vertex accessor Tesselation.vertex(body_id) - Various improvements - fix the action condition (was it never tested?) - invert x and y axis for getting stress (the convention changed in r2830) A section on devpt tools and check tests in programmer manual. - doc smallfix - improved section on regression test - fix (in fact workaround...) bad display of double minus (--) in sphinx. - typos End a sentence... (thanks Jerome) - more explicit notes on citing (I realized it was not clear while reviewing a paper citing yade ;-)) - again small fixes in the "development tools" section - remove many warnings - handle viscous shear force gracefully - don't exit the checkList when one checkTest fails, we want all of them tested in all cases - move unstable test out of checks - add Ohloh widget to main page - small fixes in the example script (so that it actually works). - add setImposedPressure that let the p value be modified -shift returned p index - import pack and utils at startup Make accents more bibtex-user-friendly in bibtex entries. typos fixing - fix problems again, sorry - standardize the first page of yade doc and add metadata to the pdf, so that online scanners will identify it correctly (not title:"Smilauer", author: "Catalano"...) - fix a few references - Feng Chen PhD + paper (thanks Feng!) - some small updates - fix typo (thanks Remi) - change sections ordering - an attempt to fix builbot-side compile error in the html version of "formulation" chapter (compile ok locally) - A few fixes on deprecated attributes. Small fix in shear stiffness averaging (thanks Emanuele) code cleaning only (including example usage of stringstream for Ema) - One more entry in PhDs (Jerier) - fix documentation (missing factor in an equation) - fix bug 2) found by chiara (https://answers.launchpad.net/yade/+question/169689) - link to libgmpcxx explicitey if cgal in features. Solves runtime undefined symbol on natty. - Include the material from the school in Grenoble in a subfolder of /scripts Remove files added by mistake. - Add material from the "discrete mechanics" school in Grenoble 2011. - fix the "homoDeform" bug https://bugs.launchpad.net/yade/+bug/836867, and adapt regression tests so they don't test homoDeform=1 - add gravity to Newton - tune front page of the documentation to be more google-scholar-friendly - include gravity a bit differently in Newton, so that it's reflected in scene->forces and accounted in the unbalanced force definition. - one more PhD done with Yade - remove Ohloh widget since it's never clicked and suggest an underestimated number of users - small addition in TSC documentation - fix Newton::gravity usage. Weight is g*mass, not just g... master thesis on yade-fluid coupling uploaded on wiki and added to references. - fix typo - remove the bibtex entry "Yade DEM" without authorship. It could only be misleading and make people use inadequate reference. - fix https://bugs.launchpad.net/yade/+bug/693505 - don't crash the TSRecorder when bodies are removed Chiara Modenese (19): 1. Move alphas parameters to FrictMat as discussed with Bruno (harmonic-average of them is introduced in Ip2 functor). 2. Forgot one inequality when plasticity is applied (maybe I do not like tests so much :-) Sorry, anyway). - Adjust definition of maximum strength for rolling resistance (thanks to Bruno for suggestion). - Get rid of prevNormal (MindlinPhys inherits again from FrictPhys) - Small changes to HM (I added the code for the moment rotation but using quaternions, work in progress to use the incremental formulation instead of that) - Add incremental formulation for the moment rotation (bending only) to HM law (it is very similar to the code for the shear part as written in ScGeom - it has been tested but feedbacks are welcome). PS Let me know if you are interested to have it also in "Law2_ScGeom6D_CohFrictPhys_CohesionMoment" where actually the total formulation is already present. @Bruno: maybe in ScGeom we could add this incremental formulation too? - Add relAngVel function to ScGeom (a similar version can be called from python as done for incidentVel) @Bruno: I would like to add the incremental formulation to CFLaw, how do you suggest to do that? Maybe I can use a bool something like (useIncrementalForm)? - Add the possibility to calculate bending and twisting moments using the incremental formulation in both CFLaw and HMLaw; - Add maximum elastic torsional moment in CFLaw (it should be computed using normal force, I will see how to compute it somehow); - Fix mistake in mindlin.py example script. - Add a seed parameter to makeCloud, particleSD and particleSD2 functions. The parameter is used to initialize the random number generator. - Add calculation of fabric tensor for periodic cell as according to Satake (bib is updated too). The tensor can also be spit into two different parts each of them related to strong and weak contact forces respectively. - Small changes in HM. - Calculation of fabric tensor takes care of the sign of normal force (I think this is the way to do it if we have cohesive forces but I will check again in the existing literature). Also the tensor can be split into two parts according to the mean contact force or a threshold value if this is going to be specified (thanks Vincent for suggestion). - Add a function to HMLaw which computes the ratio of sliding contacts. - Adjust the sign convention in the computation of the fabric tensor. Now the tensor can also be split into tensile and compressive part (simply setting the threshold value equal to 0 - added documentation about that). - Rename (for the fabric tensor function only) compressionPositive to revertSign (that is the meaning indeed - work in progress to use the scene flag and make automatic distinction whichever law is used). - Average stress tensor of periodic cell can also be spit into two contributions, given a threshold value (mean force by default). - Forgot the reference (sorry). Fix doc. - Add formulation for determination of time step for elastic spheres according to Rayleigh wave speed. Fix small bug in HM (only in case of viscous damping). - Wrap Eigen function to get eigenvalues and eigenvectors of a Matrix3r in py - Small fix in HM - For now, add 2d version of particleSD (discrete distribution) - for other type of distributions (like the one generated by makeCloud), something similar can be done. - Fix color map in shop (thanks Vaclav). - Fix bug in Periodic Boundary. scene->cell->prevVelGrad was never updated. It was equivalent to apply homoDeform=2 in place of homoDeform=3. (Thanks, Vaclav) - fix bug 1) as discussed in (https://answers.launchpad.net/yade/+question/169689) Donia Marzougui (1): - Add viscous shear force definition (disabled by default) (Donia+Bruno) Emanuele Catalano (19): - Retriangulation controlled by a volumetric deformation threshold - Substituted pkg-dem in pkg/dem (and similar) to included files - Removed #define flow_engine line from def_type, definition is moved to the scons profile - Reorganized debuggin' - Found and corrected a error in boundary creator - Changed names of .h files - new function for adding bounding planes - Update flow code. - Fixed compilation errors - Fluid velocity calculation takes into account of facets velocities - Update flow code - Update flow code - Correction on facet fluid/solid surfaces calculation - Fixed calculation of fluid velocity in cells - re-fix fluidArea correction (S0) by Donia - Fix error in Volume_cell function. - Fix retriangulation issue - Fixed two warnings in FlowEngine - Changed link to deprecated utils.encodeVideoFromFrames with utils.makeVideo (hope i did it properly, first time I modify docs) - Reordering new function for viscous force calculation - Added computation of average cell permeability for visualization - Removed correction on smallest permeabilities - Porosity computation neglects boundaries - New functions ImposeFlux() and GetFlux() for one-point fluid injection - FlowEngine does not depend on TriaxialCompressionEngine anymore - Clean of useless variables - Consolidation files writers move to python scripts (Flow functions MeasurePorePressure and MeasurePressureProfile do what is needed) - Removed superfluous variables - Fix segfault due to uninitialised normal to walls - Add function to measure slice-averaged pressure values Jan Stránský (5): Little changes in CpmStateUpdater 1. Changes in ConcretePM.cpp 2. added yade.ymport.unv function 3. added yade.export.VTKExporter class 4. added method outer to Vector3 in Python Small changes in yade.ymport.unv and corresponding example Change of CpmMat.relDuctility to CpmMat.crackOpening (making Cpm model more particle size independent) rotation of facets taken into account in yade.export.VTKExporter Janek Kozicki (10): plot.py: added `legendPosition` and `legendPositionSecondary` so we can decide where the legend should be placed, by default it is 'upper left' and 'upper right' Added yade.utils.voxelPorosity and yade.utils.voxelPorosityTriaxial. It allows calculating porosity at any arbitrary sub-volume of a whole sample. See included documentation. small documentation fix standalone example almost works. I still have some problem with sample randomness, and exact answers. Added another variation of simple scene - this one is for energy tracking added nice legend to the plots more flexibility, few more parameters on the top of the script. better default parameters, use dict(...) to read O.energy.items() last correction for simple scene energy tracking example - I forgot to commit changes in CohesiveFrictionalContactLaw but in fact maybe better if those methods for calculating elastic normal & shear energy would be moved somewhere else, maybe to shop? I'm not sure. fix warning, add plastic dissipation tracking Jerome Duriez (17): Details Fixes https://bugs.launchpad.net/yade/+bug/672473 (an error in the script finally...) Details changes in comments of Vector3r forcesOnPlane(..), after http://www.mail-archive.com/yade-users@lists.launchpad.net/msg02610.html. Hoping you agree, Vaclav (if not, do not hesitate to revert !) Few typos corrected in doc, and pack.py. Changes in "normalInelasticity" files suggested by http://www.mail-archive.com/yade-dev@lists.launchpad.net/msg06011.html Fixes https://bugs.launchpad.net/yade/+bug/706937 Minor changes - typos in programmer doc - removal of useless backward compability in system.py - removal or english translations of comments in the three c++ files - some changes in normalInelasticity-test - add of a check version of this script - add of a sentence about these check tests in prog.rst - changes of deprecated function in tutorial scripts - typo in some other rst - add of a note about plot.plot in plot.py - beginning changes in SimpleShear preprocessor - initialization of many variables in Law2_Normal.... - relative difference considered in check test, instead of absolute one - add of some hyperlinks in doc - changes in scripts. At the moment I can run ~20 times the proposed checkTest without fails (did not make more than ~20). Fails occured when interaction disappeared. Wait however a bit... - still small changes in doc mainly - add of a "by" in doc - re let work the .py, modifiying definition of state.blockedDOFS - maintaining of SimpleShear FileGenerator - checkTestNormalInelasticity.py again among check tests. I launched it more than 20 times without crash here Let spheres of SimpleShear be wiser (reducing GSTS->defaultDt) - changes in simpleShear.py - correction of small error in doc (I think) Few changes in doc (sphinx and Sconstruct's one) after https://answers.launchpad.net/yade/+question/169042 - In Law2... add of a requestErase of the interactions with un<0, which was missing. Could help https://bugs.launchpad.net/yade/+bug/810056 ? Klaus Thoeni (11): New implementation of a particle model for wires and rockfall meshes (initial state) 1. Fixed problem in postLoad (regression tests should work now) 2. Add some examples for testing the new WirePM with 2 particles update scripts for the WireMatPM 1. implement packing for the WireMatPM 2. add some examples for the WireMatPM 1. correct affiliation in doc/sphinx/conf.py 2. derive WirePM from FrictMat 3. add value which indicates how far interactions are from normal failing (currently just used in WirePM) 1. move value which indicates how far interactions are from normal failing to WirePhys since only WirePM is using it 1. update WirePM scripts in scripts/test/WireMatPM 2. add a simple example for interaction of FrictMat and WireMat: examples/WireMatPM/wirecontacttest.py 1. add some more comments 1. add some more comments 2. correct some typos 1. fix problem (segmentation fault) for save/load simulations with WireMat 2. correct some typos 1. update scripts 2. add unbalancedForce to diagram in wirecontacttest.py Rémi Cailletaud (5): Added buildbot master.cfg file in scripts dir - removed log4cxx from build options beacause of wrong exit status (https://bugs.launchpad.net/yade/+bug/410250) buildbot now creates a source targz Build infrastructure : buildot script modified after an update of nagios probes modifiied buildbot configuration to fix tarball build Sergei Dorofeenko (15): fix Bug 672623; fix some examples merge with trunk 1.Add silent switcher to NozzleFactory; 2.Fix examples merge with trunk 1.Fix RotationEngine for new meaning non-dynamic bodies 2.Fix STLImporter 3.!!! Set aspherical=False for a facets. Need more consideration. 4.!!! blockedDOFs for leapfrogAsphericalRotate need more consideration. fix bug [Bug 692452] 1. ViscoelasticPM: release from prevNormal; 2.Small fixes merge with trunk 1. Rename NozzleFactory to SpheresFactory; add derived CircularFactory and QuadroFactory. Example is scripts/test/spheresFactory.py 2. small fixes fix examples 1. Rename QuadroFactory to BoxFactory; some fixing SpheresFactory 2. Rename facet->nf to facet->normal; access to the facet's normal from python 3. Fix examples typo fix and more typo fix fix bug #721107 1.Add utils.trackPerfomance; 2.Small fix Unknown (2): Modify CohFrict* classes as discussed with Bruno on the list. 1. Move alphas parameters to FrictMat as discussed with Bruno (harmonic-average of them is introduced in Ip2 functor). 2. Forgot one inequality when plasticity is applied (maybe I do not like tests so much :-) Sorry, anyway). Václav Šmilauer (75): 1. Fix Clump compilation in debug mode small fixes in scons 1. Replace includes {lib,pkg}-* with {lib,pkg}/*; compiles (no full compilation tested) 1. Implement an initial version of generating loose clump packings of predefined configuration with SpherePack (TODO: do not recompute clump properties when adding them using toSimulation for each of them separately, but use those precomputed on given configurations). See scripts/test/clumpPack.py 2. Make selection of clump member highlight the whole clump in OpenGL 2. Add Dong2010 article to articles done with yade (authors contacted for fulltext) 1. Fix particle highlighting bug introduced lastly 2. Enhance the SpherePack.makeClumpCloud routine to be much more efficient (uses bounding spheres) 3. Add Ig2_Wall_Sphere_ScGeom 4. Enhance scripts/test/clumpPack.py Some experiments with cmake 1. Add optional MatchMaker to Ip2_FrictMat_FrictMat_FrictPhys to decide how to compute contact friction angle. 1. Fix include paths pkg-common to pkg/common etc, merge other changes. 1. Allow O.engines to be modified inside the loop from python 2. Fix include dir creation (thanks, buildbot) 3. Fix Ip2_FrictMat_FrictMat_FrictPhys failure (inversed conditional) 4. Add tests for substepping, modification of O.engines and Engine.dead 5. Remove O.initializers 1. Add energy tracking to GravityEngine, NewtonIntegrator, PeriIsoCompressor (the last one not tested yet) 2. Update scripts/test/energy.py 3. DISABLE cohesive-chain test, since it was already broken by previous Bruno's commits 1. Add detailed docs for plot.saveDataTxt 2. Do not add dissipated energy if zero in Law2_ScGeom_FrictPhys_CundallStrack 3. Set friction to zero in scripts/test/energy.py 1. Do not enable draggable legent if not supported by matplotlib (thansk, buildbot) 1. Fix erroneous assert in the integrator (energy & clumps) 2. Fix bug in distance computation in SpherePack.makeClumpCloud 3. Make scripts/test/energy.py use clumps (and no friction) to have rotations, as Bruno suggested. 1. Fix chunkSize=1 builds (bug introduced by changing include paths) 2. Add octree visualization engine (for debugging clump computation) 3. Make editor of sequence of serializables resize more intelligently 1. Simplify InteractionContainer, move interactions inside bodies (NOT useful for getting all interactions of a body) 2. Advance with cmake a little bit (nothing functional yet) 3. Remove YADE_REQUIRE_FEATURE, use plain #ifdefs instead (empty files will be compiled, oh well) 4. Remove a few files that were apparently not compiled for a very long time. 1. Fix compilation pb with cgal (sorry, cgal does not compile with clang, so does not get much coverage from me). Thanks to Ema for reporting. 1. Initial subdomain support, which includes NewtonIntegrator, GravityEngine, InteractionLoop; new SubdomainOptimizer to put bodies to subdomains. 1. SubdomainOptimizer now splits domain and adjusts later so that there is similar number of particles in all domains 2. Add --cores option, defining CPU affinity; based on some tests, it actually hurts performance... :-| 3. #define likely/unlikely (like in Linux kernel) and use it in a few conditions in loops to help the compiler 4. Fix a strainge numerical "bug" in InsertionSortCollider where the same id has inversion with itself (min>max?) 5. Add pack.inParallelepiped predicate (not yet tested) 6. pack.filterSpherePack now returns SpherePack (the other routines are still to be made consistent...) -- see https://lists.launchpad.net/yade-users/msg03887.html 1. Backport doc generation crash (picked from subdomains) Remove extra asserts to make Anton happier (sorry, forgotten stuff) 1. Add timing statistics to yade-batch 2. Make cmake closer to functional build 3. Fix xmlrpc error in the window between job start and xmlrpc-readiness 4. Add a few options to batch, make cpu affinity optional 5. Add subdomain mask to OpenGLRenderer 6. add unlikely to some conditionals in loops 1. Fix requestErase of erased interaction (thanks to Anton for reporting) Fix INteractionContainer bug, enhance the timing table for batch. 1. Assume cache line size 64 bytes in OpenMPAccumulator, if sysconf reports zero (should fix startup FPU exception reported by Giulia) 1. Merge the subdomain branch, enable subdomains feature to enable 2. Delete some obsolete code in scons 1. Add access to clump member from python 2. Fix Clump i/o (clumpId was not saved) 3. Rename regular-sphere-pack to packs 4. Fix a few bugs (thanks, Anton) 5. Fix SubdomainOptimizer behavior in some corner cases 6. Add some checks to qt4 1. Fix L3Geom, add Sphere+Wall interactions, plus some benchmark scripts 2. Add NewtonIntegrator.kinSplit, to track translational and rotational energy separately 3. Add --randomize option to batch, for running jobs in arbitrary order 4. Make Body::setDynamic() (un)block State.blockedDOFs (for future compat) 5. Add Serializable::cast static casting method 6. Several fixes in utils, plot etc 1. Add MatchMaker(Real) ctor 2. Adjust timing report format for batch (precision) 1. Fixes in L3Geom, L6Geom & friends 2. Add scripts/test/beam-l6geom.py to demonstrate L6Geom 3. Fix LawTester so that ti works with all 6 dofs reliably now 1. Add Gl1_L6Geom, and a few params to Gl1_L3Geom as well 2. Add movie to scripts/test/beam-l6geom.py 1. Remove some obsolete files, update INSTALL and NEWS 2. Make qt._GLViewer properly documented with sphinx Fix compilation error introduced lastly, sorry. 1. yade-batch can run a single job (without parameter table) 2. Avoid some exceptions in plot 1. Make the i/o regression test quiet by adding quiet flag to saveTmp/loadTmp 2. Facet has NaN vertices by default, and Facet.postLoad returns without warning in such case 3. LawTester now has most data as Vector6r instead of 2xVector3r. The syntax changed (backwards-compat with warnings, sorry), path should be disPath, ptOurs, ptGeom, rotOurs, rotGeom is now in uGeom and uTest 4. Add Vector6.head() and Vector6.tail() to return first/second triplet as Vector3 in python (corresponds to start<3>() and end<3>() in eigen2 and head<3>()/tail<3>() in eigen3) 5. Make the qt4 interface display Vector6 properly 1. Fix batch script (thanks, Anton) 2. Fix MatchMaker docscring format 1. Add support for ScGeom6D to LawTester (not tested...) 1. Fix orientation of rotations for ScGeom6D in LawTester 1. Remove Body::flags/FLAG_DYNAMIC, setDynamic now merely sets state->blockedDOFs=State::DOF_ALL etc. 2. Remove some deprecated code chunks 1. Add documentation for Ig2_Sphere_Sphere_L3Geom_Inc 2. Add regression tests for PBC & L3Geom 3. Fix syntax in other docs as necessary 1. Add docs for LawTester, and fix docs elsewhere as well. 1. remove devirt-functors (unused) 2. rename SubdomainOptimizer to SubdomainBalancer 3. remove Dem3DofGeom::scaleDisplacementT (unused) 4. remove Law2_ScGeom_CpmPhys_Cpm (malfunctioning, unused) 5. remove SConscript-mono (unused) 1. Fix NewtonIntegrator (not skipping particles with all DOFs blocked) 2. Disable Translation/RotationEngine tests, non-dynamic bodies changed their meaning and the tests fail. 1. Make batch accept multiple scripts on the command line (see --help) 2. Change syntax for blockedDOFs to be 'xyZ' rather than ['x','y','rz'] (assigning is backwards-compatible with warning) 3. Remove rests of devirt-functors; I thought I've done it already... 1. Create abstract KinematicEngine, allow kinematic engine composition with + (thanks to Jan for suggesting that) 2. Move all KinematicEngine's to pkg/common/KinematicEngines.{hpp,cpp} 3. Change accels in GLViewer so that x,X,y,Y,z,Z make rhe respective axis point upwards etc 4. Change g/G to enable/disable grid (or selectively enable if pressed multiple times) 5. Remove button images in the qt4 ui, use unicode symbols instead 6. Make batch referesh once in 30s by default instead of 5s to avoid congestion on slow uplinks 1. Add forgotten KinematicEngines.{c,h}pp files 2. Remove GroupRelationData & mgpost (is now at https://code.launchpad.net/~yade-dev/yade/mgpost) 1. Fix accidental typo, sorry... 1. Make middle-click paste path to variable in the ui 2. Clean up newton integrator; aspherical integration not called for things with 0 in inertia to avoid nans; this also fixed the problem of inverted normal with Wall 1. Fix NewtonIntegrator for clumps, blockedDOFs &c; msg will be sent to yade-dev 2. Make static attributes properly hyperlinked and documented in the ui 3. Fix yref in L3Geom 1. Clean up NewtonIntegrator following the discussion on the list; adds 1st order damping for aspherical particles; comments welcome. 2. Add Body.intrs() in python 1. Fix an error in Law2_L3Geom_FrictPhys_ElPerfPl (forces were applied at the step the interaction broke); results are now identical to ScGeom/CundallStrack (in the normal sense at least) 2. Add the possibility to specify y-data as functions returning y-data names themselves (see scripts/tests/energy.py) 3. Merge 2 consecutive loops in NewtonIntegrator for clumps 4. Rename plot.legendPosition, plot.legendPositionSecondary to plot.legendLoc (tuple of 2 values), to make it consistent with matplotlib terminology (pylab.legend(...,loc=...)) 1. utils.{sphere,facet,wall} funcs take fixed=True|False now (instead of dynamic=False|True, which gives warning now and will be removed) 2. Make space in the 3d recenter scene 3. x,y,z (X,Y,Z) now set up axis (see help of the 3d), grid is toggled with g only 4. Remove Shop::defaults and few other obsolete funcs 5. Fix bug in torsionweighting in LawTester 6. Rename Ig2_*_L3Geom_Inc to Ig2_*_L3Geom, update .py scripts 7. Rationalize Ig2_*_L3Geom functors, add Sphere+Facet 1. Add some data so that Hertz-Mindlin will work with L3Geom 2. Automatically compute "mass" of cell in PeriTriaxController, if not setFocus 3. Fix auto-plotting of energies in case the simulation is reloaded 4. Fix middle-click on attributes in inspector 1. set color_cycle in matplotlib only when it is supported (fixes test failure) 1. Fix i386 (?) live plot issue 1. Add function checking argument types inside FUNCTOR1D and FUNCTOR2D (requires to include full dispatched type definition in headers) 2. Reorganize the integrator so that its logic is understandable. Functionality not changed. 3. Move most clump code from integrator to clump methods 4. Automtically used InsertionSortCollider::verletDist if possible (used to be sweepLength) 5. Add --refresh param to batch, controlling refresh rate of web summary, logs etc (30s by default) 6. Fix gnuplot export with callable data specifiers 7. Put BodyCallback in #ifdef, since buggy; will be removed shortly, unless gets fixed. 8. Fix unbalanced force computation for clumps. 9. Remove accel and angAccel from state, since it is only used locally in the integrator. Add missing includes, thanks to Klaus for reporting. 1. Better integration of NewtonIntegrator into Clump, avoiding linking cycle with chunkSize=1 1. Fix State.rot() mistake (was returning displacement instead) 2. Add qt.SnapshoeEngine.plot for adding snapshot name to plot.imgData automatically 3. Add plot.savePlotSequence for making movies with plots 4. Rename utils.makeVideo bps to kbps (is really kb/s, not b/s) 5. Rename OpenGLRenderer.displayGhosts to ghosts (shorter, sufficient -- sorry, Bruno) Fix cache line size in OpenMPArrayAccumulator 1. Unbreak the periodic cell. Add the dresden course to sphinx docs. 1. Add a more straightforward SpherePack.ParticleSD2 method, adjust the tutorial 1. Add scene flags about local coordinate systems and force sign convention (not yet used) 2. Fix many things around L3Geom (most importantly, re-normalization was never done, resulting in instabilities), merge code for different particle shapes in a general function handleSpheresLikeContact 3. Fix incorrect import of gts surface due to LC_NUMERIC 4. Add OpenGL-related stuff to yade-support, which might fix linking issues with natty 5. Move Cell::invTrsf and Cell::refSize to private part, so that it 1. Make yade and yade-batch run single-threaded simulations 2. Add warning when specifying -j/--threads/--cores when lacking OpenMP support 1. Change Cell behavior as explained on the list -- trsf can be changed freely, without side-effects; assigning refSize is deprecated (use new O.cell.setBox(...) function for that) 2. SpherePack.toSimulation sets identity transformation, but rotated hSize (if requested) 3. PeriTriaxController uses norms of hSize columns (with inverse transformaiton) as reference lengths ;; TODO (?): it should check that hSize is diagonal, otherwise it will probably behave erroneously 1. Revert some "cleanups" in Cell (LOL, Bruno, we are overwriting each other's work again :-)) 2. Add some regression tests of PBC 3. Make plot.addData aceept vectors and matrices, creating columns for individual components 4. Make compilation of SpherePadder only optional (feature sphere-padder) 1. Add plot.autoAddData with docs. A limitation is that "=" should not be in names of data columns anymore. 2. Add Strong/weak fabric distinction to Gl1_NormPhys (not tested) 3. Degrade VelocityBin's initialization message to debug 4. Add .items() method to EnergyTracker, for fast conversion to list of tuples. 1. Fix plots with line specifiers 2. Fix peri-triax example (sweepDist->verletDist) 1. An attempt to fix OpenMPArrayAccumulator initialization (not tested) 1. Update affiliation 1. Fix OpenMPArrayAccumulator initial zeroing, re-enable plot test 2. Add Matrix3 ctor from Quaternion and vice versa in Python 3. Fix some bugs in L3Geom initial axes orientation 4. Plots now display pointed triangles instead of circles to mark current position (and orientation); plot part after the current point is transparent Nicer fix for 721107 (should work) oops, sorry 1. Invalidate persistent collider data when interactions are cleared (not tested yet) 2. Add renderer pointer to scene, so that functors can find display attributes 3. Gl1_NormPhys honors displacement scaling now 4. Fix scalarOnColorScale bug 5. Add RadialForceEngine, fix AxialGravityEngine fix error in last commit ================================================== yade-0.60.0 Sun, Nov 7 09:55:43 2010 +0100 Anton Gladky (54): 1. utils.facetCylinder and utils.facetBox orientations are changed according to https://lists.launchpad.net/yade-dev/msg04886.html 1. Orientation in ymport module is changed according to https://lists.launchpad.net/yade-dev/msg04886.html 1. Some small fixes 1. Docs small modifications 1. Some bibtex references added from WIKI 1. Some references into bibtex-format migration 1. Moving references to bibtex format is finished. 1. Engine for particle size distribution analyzes is added. (not finished) 1. Engine for PSD-analyze is finished, need testing. 1. Added forgotten include, caused a compilation errorin 2331 1. Changed warning message in case, when installer cant find libraries. http://www.mail-archive.com/yade-users@lists.launchpad.net/msg02202.html 2. Installation instructions were moved from Wiki to Sphinx Documentation. regular-sphere-pack.py fix. It works again. 1. Comments to ParticleSizeDistrbutionRPMRecorder are added. 1. RockPM update. RockPM fix 1. Recorder has new feature "addIterNum" which adds Iteration number to the end of file name. Off by default. 2. Clean ParticleSizeDistrbutionRPMRecorder Fix compillation error Packages names needed for qt4 are added to 'installation' section of documentation. 1. Analyze of specimen diametr is added to PSD engine. VTK and Rock were updated relatively. 1. Added materialId analyze to PSD-engine 2. Changed chmod of CohesiveFrictionalPM 1. Some modifications in PSD-recorder for material analyze 1. Added updating volume specimen variable in rpmState 1. Small but necessary fix One more field added to PSD engine 1. 626409 fix. Thanks to Sergei for spotting that. 1. Fix export.textExt function according to http://www.mail-archive.com/yade-users@lists.launchpad.net/msg02413.html Fix compilation warning in SpherePack::particleSD 1. Fixes issue, described in http://www.mail-archive.com/yade-dev@lists.launchpad.net/msg05009.html 1. Some changes for compilation in Fedora 1. One more fix for compiling in Fedora 1. Some small fixes in debian/ 2. Warning fixes in shop.cpp 1. debian-prep now can accept RELEASE file without version number to create yade-bzr version 1. Changed required version of python-sphinx in debian/control-template to make the trunk builded in Maverick. 1. Fixed wrong MaxDiam determinition, when specimen consists on 1 particle. 2. Added maxX, maxY and maxZ parameter into outputed psd-file for geometry analyze. 1. chunkSize is changed in debian/rules from 2 to 1 1. Fixes an issue, indicated here http://www.mail-archive.com/yade-dev@lists.launchpad.net/msg05162.html ; combined-files in SConstruct during Debian build. 1. Changes in debian/rules due to new conception of debug-build http://www.mail-archive.com/yade-dev@lists.launchpad.net/msg05182.html 1. Changes in debian/rules http://www.mail-archive.com/yade-dev@lists.launchpad.net/msg05185.html 1. scripts/debian now takes the name of person from DEBFULLNAME and DEBEMAIL environment varaibles. 1. Added initial scripts/build-fabric 2. scripts/debian-prep now smoothly handles absenting DEBEMAIL and DBFULLNAME variables 1. build-fabric now builds packages for maverick and lucid for 2 platforms "automatically" Fixed an error in debian-prep 1. build-bot with the main functionality is reasy for testing. 1. Yade menu should appear in respective sections in main menu in Debian-based systems (not tested yet) 2. build-farm should build packages without root priveleges 1. Returning "sudo " to build-farm 1. Command-line parameters are added to build-farm script 1. Fixed pixmap copying in debian/rules 1. Warning blocked in DomainLimiter.cpp:63 1. Some notes on Eigen3 migration 1. Eigen3 compiles now (seems). Vaclav, sorry, I needed to change your template, because it gave an error on Eigen3. 1. Again some changes for Eigen3 compilation 1. Yade now compiles with Eigen3 and with OpenGL feature enabled 1. In PSD-engine wrong maxX, ..Y and ..Z calculation fixed 0.60.0 Bruno Chareyre (45): Local sphinx build : I made it! :) Fixing the command in readme. - give path more clearly, the previous commit was assuming same relative path as mine. revert yade path - Add files for kdev4. - Fix ambiguities in contact geometry and time-step parts. - Add stress getter and python wrapper (replacing reverted r2314). - Add definition of work input from boundaries (resp. macro strain) in TriaxialStressController (resp. PeriTriaxController). - Remove useless spheresVolume (redundant with TriaxialStressController::spheresVolume) - Some cleaning in ScGeom. - Implement precomputed quantities (#define IGCACHE for compiling them). - Experiment precomputation with ElasticContactLaw (#ifdef IGCACHE), other laws should not be impacted yet whatever IGCACHE. - Make authors consistent (Galizzi never touched ElasticContactLaw). - Remove some debugging/commented code left in 2336, sorry. - Update authors again (forgot to "save" before commit...) - More changes in stability section. (we need PFC manual in bibtex references...) - More small fixes. - miniEigen : memory leak source suspected (FIXME added) - ScGeom : #define IGCACHE by default, and keep behaviour unchanged for older functions (still some cleaning to do in this class, after all Law2's have been adapted). - CohFrictLaw : Eigen is returning nan quaternions after trivial operations, it is workarounded in the law. - cylinder : a new shape, first step in experiments for tracking interactions jumping over chained elements. - utils.py : a demo script for chained cylinders - make the example more fun. - Revert last doc change and fix the original. (please don't modify documentation if you are not sure. I can't double-check all commits.) - Implement the distinction between node orientation (integrated in Newton) and beam orientation (always oriented as line between adjacent node). It fixes the starcases appearing in display for large stretch. - Fix path to fulltext. - restore Gl1_ChainedCylinder - kdev4 : I failoed to commit this file apparently. - Cylinder, update contact point consistently with current segment definition. - Fix the poisson=0 case (was returning undefined Ks for division by 0...) - This commit enables wire-like behaviour without moment when ks=0 - Last change in contact plane makes script unstable. Restore stable (but wrong?...) equations. - Cylinder : fix segment orientation, use correct contact point, and clean code. - ScGeom : last step before removing old code, some functors adapted (others are using even older duplicated code apparently) - Small sphinx fixes. - Use precomputed geometry everywhere in functors and remove older code. - Make Dem3DofGeom inherit from ScGeom rather than GenericSphereContact and remove (comment) the latest. - Missing in last commit sorry. - Revert r2365 as a whole (and r2367). - All changes in ScGeom and related functors will be commited again when I have time. - restore virgin yade.kdev4 and add kdev files to bzrignore. - publications update. Restore the part of r2367 concerning ScGeom (+ some more improvments). - restore project file for kdevelop3. -Update functor->momentLaw in the global engine. - More documention in the 2 constitutive laws. - Some code cleaning and test redundancy fix in CohFrictPhys.cpp+CohesiveFrictionalContactLaw.cpp. - adds references cited in the previous commit. - Include a comment on "welltestedness" of ElasticContactLaw. - rename classes : Law2_ScGeom_CohFrictPhys_ElasticPlastic -> Law2_ScGeom_CohFrictPhys_CohesionMoment Law2_Dem3DofGeom_FrictPhys_Basic -> Law2_Dem3DofGeom_FrictPhys_CundallStrack Law2_ScGeom_FrictPhys_Basic -> Law2_ScGeom_FrictPhys_CundallStrack - remove useless attributes, - fix class name in TTest. - Add new IG class ScGeom6D precomputing relative rotations, and associated Ig functors. - Update related classes (CFLaw) - add kdev4 configuration for starting debug from kdevelop -Includes a regression test on dynamic beam flexion. -Move cohesion and moment parameters from functors to bodies and interactions, and define them in Ip2_2xCohFrictMat_... - Fix cylinder reg. test with respect to r2509(2510). - Add output for nan quaternion products #ifdef Q_DEBUG. - Document some flags in Ip2_2xCohFrictMat_CohFrictPhys.hpp. - more debug info for quaternions with nan angle. - Convert ScGeom to ScGeom6D in Ig2_Sphere_Sphere_ScGeom6D::go(), using ScGeom::operator= - Optimize distance check for existing interactions in Ig2_Sphere_Sphere_ScGeom::go() - Implement Ig2_Box_Sphere_ScGeom6D (same conversion to ScGeom6D) - Adapt CohesiveTriaxialTest. - Fix a typo (and test some launchpad+bazar logging feature...) - Add python wrapping for python use of TesselationWrapper (+ a few fixes and formating) - add example commands in docstring. - Remove scene pointer from TW function parameters - sphere-cylinder Ig2 functor (some old uncommited code - still bugged) - TW : really check if body geometry is sphere in the insertion loop (instead of isDynamic). - FE : use the faster insert(begin,end) from TW for triangulation, and move area/volume definition to addBoundary(). Chiara Modenese (22): 1) Add contact damping into HM law. 2) Add functions getIncidentVel (both for periodic and non-periodic case) and rotateShear to ScGeom. 3) Add py script to demonstrate and test the effect of contact damping. Rename rotateShear() to rotate() in ScGeom. 1. Add Engine::dead attribute; if set, the engine will not be run at all (unless explicitly called via () from python or ::action from c++). Useful for temporarily (de)activating engines without having to modify O.engines 2. PeriodicPythonRunner renamed to PyRunner as it is used so often. 3. Add Cell.getVolume() and utils.porosity 4. Check for force&torque NaNs in NewtonIntegrator in debug builds 5. Add --rebuild option which will launch scons before running the simulation itself 6. Some fixes in the HertzMindlin law 7. The qt4 interface will try to open packaged docs, then docs in the source dir and online docs as the last resort. 8. Some fixes in PeriTriaxController (the servo-mechanism) and probably a bug fix in TriaxialStressController (will ask Bruno on the list to confirm the correctness) 1. Add an alternative implementation of PSD-based particle generation; likely to be merged to makeCloud in the future (micro-fix for a spurious startup message) 1. Improved summary http page for yade-multi, includes (clickable) plots and hyperlink to the log (with auth cookie removed) 2. Deprecate PartialEngine::subsribedBodies in favor of ids 3. Remove triangulation lib from ctags dirs (so that tag Facet goes to yade::Facet) 4. The qt controller computes simulation speed locally now 5. Fixes in Hertz-Mindlin 6. Add an alternative SpherePack::particleSD function 7. Make axis labels, highlighted axes etc settable in yade.plot 8. Add yade.plot.saveDataTxt 9. Fix plot names with spaces (so that multiple plots with the same x-axis can be specified) 1. The mindlin law now prevents granular ratcheting by default. 2. Ig2_Sphere_Sphere_ScGeom has now avoidGranularRatcheting bool (true by default, which was the hardcoded value till now as well); add documentation from the source to the real doc, add a few references on that. 3. SpherePack no longer saves vectors as tuples; this also fixes memoization issues with pack.random{Dense,Peri}Pack 4. Add Law2_ScGeom_MindlinPhys_HertzWithLinearShear used for testing differences between linear and non-linear law. (Antons changes) 1. angleRange parameter is added to facetCylinder to create "semi-cylinders", "quarter-cylinders" etc. 2. Prepare Sconstruct for compiling in Fedora (partly) 1. Rename yade-multi to yade-batch; documentation updated 2. Make batch save plots when all jobs are done 3. Add tags d.id and id.d (description and id put together) 1. Re-apply removal of Mathr, sorry Anton, the fault was local. 1. Avoid adding useless lines of nans in plot for unknown variables, add only their column instead 2. Save job figure as soon as the job finishes in the batch 3. Fi O.tags['id.d'] and O.tags['d.id'] tag order 1. Make batch commands clickable; fix encoding issues when the batch page sends job script/table 2. add utils.maxOverlapRatio for sphere-sphere contacts 3. add reinitialization counter to InsertionSortCollider 4. Add VTKRecorder.ascii so that saved XML data might be human-readable (false by default) 1. Resurrect SnapshotEngine 2. Add Gl1_NormPhys for displaying interaction network (http://beta.arcig.cz/~eudoxos/temp/Gl1_NormPhys.avi) 3. Make VTKRecroder work nicely with periodic boundary conditions (repeating interaction on both sides if it crosses the cell boundary using a few hacks) 4. Add Shop::normalShearStressTensors for computing (once more) stress tensor, but normal and shear contributions separately 5. Allow specify particle counts for SpherePack.particleSD 6. utils.makeVideo now uses mencoder (and works, unlike the older gstreamer-based thing, which would not work anymore) 7. Add Matrix3.diagonal() 8. Integrate regression tests suite in the main program (yade --test) 9. Set plot.scientific==True only if the version of pylab installed supports it (thanks to Anton for reporting) 1. Add scripts/test/force-network-video.py 2. Rename attribute flags (Attr::readonly, Attr::triggerPostLoad etc) 3. Detect whether display is available and store it in yade.runtime.hasDisplay (false by default); that restores the behavior of yade.qt, which raises ImportError at no display, and makes yade.plot consistent with tha variable as well 4. Fix total running time for batches 5. Do not send 0 refresh for finished job files (refreshes as frequently as possible, whereas no refresh was desired) 6. Debian package depends on mencoder now 7. qt.SnapshotEngine open the view automatically if needed (not very reliable, though) 8. Add scripts/test/force-network-video.py 9. Fixes in the OpenGLRenderer that avoid crashes; drawWithNames should draw shapes in the same place as draw witout names, making the selection consistent with what is seen (periodic boundaries etc) 10. Move {ScGeom,Dem3DofGeom}::contactPoint to GenericSpheresContact 11. Initial (not yet functional) implementation of the partial slip solution for Hertz-Mindlin (Mindlin-Deresiewitz) 1. Introduce a class for returning or computing scalar values based on combination of 2 ids. [experimental, don't use or ask questions about it now] 1. Rename InteractionDispatchers to InteractionLoop; backwards-compat for python as usual, typedef with deprecated warning in c++; update all scripts and docs to reflect that. 2. Add NozzleFactory, which might be in the future generalized to a generic factory object and a numebr of derived classes with different parameters (such as factory area shape, specification of sphere parameters etc); see scripts/test/shots.py 3. Fix many crashers in the OpenGL code; unbreak particle selection 4. Finalize the MatchMaker class, with convertor from python floats to specify fixed value. 5. MatchMaker is used to specify different coefficients of restitution for different material couples in Ip2_FrictMat_FrictMat_MindlinPhys; viscous constants moved away from Law2_ScGeom_MindlinPhys_Mindlin into MindlinPhys instead. 6. Fix compilation errors caused by unintelligedn 3rd-party Combine builder to scons -- save sombined files to an external file, force refresh of all combined files if the md5 changes. Does not increase compilation time. 7. Add DomainLimiter engine for deleting particles that go ouside some domain (see scripts/test/shots.py) 8. Add DragForceApplier (not yet tested); thans to Stefano for the suggestion. 1. Add #pragma once to MatchMaker (thanks, Remi) 2. Remove a few useless scripts. 1. Make the debug build installed in the same directory as the release build; it is compiled with debug=1 (or True); there is only a single yade executable for both, and the debug plugins are loaded with --debug. I tried to adjust packaging rules a bit, I hope it will not introduce additional errors. --debug and --rebuild work fine together. 2. Add --debug option to yade-batch, which runs yade with --debug. 1. Add LawTester class for prescribing displacements on interactions precisely, see scripts/test/law-test.py 2. plot now displays the last point as a bullet 3. Rename Interaction{Geometry,Physics} to I{Geom,Phys}; rename related classes as well (functors etc) 4. Rename Interaction::interaction{Geometry,Physics} to Interaction::{geom,phys} 5. Add Vector3.normalized() to the eigen wrapper 6. Add framework for arbitrary rendering hooks (GLDrawExtra) run from OpenGLRenderer at the end of the rendering routine (not yet fully tested) 1. Do not return Attr::hidden attributes in dict(); that makes them invisible in the UI as well. 1. Fix batch script with debug builds, thanks to Anton for reporting. 1. Fix a few malformed formulas (my bad) so that LaTeX does not choke on them. Emanuele Catalano (12): - Wide code maintainance - Removed isSuperior/Inferior/Lateral attribute, inside/fictious remain, Localize function removed - FlowEngine is able to receive as input boundary conditions (flow/pressure) to be applied to walls - Oedometer test can be performed via python scripting - A new force calculation scheme (facet scheme) had been implemented - Forgot def_types.h in previous commit (sorry Tieng!) - Added function to compute average cell velocity - Added function to apply sinusoidal external fluid pressures - Solved retriangulation problems in seabed simulations - Code maintenance - Solved compilation errors - Added average_cell and average_grain velocity computation - Updated vtk_file creator function - Moved functions and variables related to geometry in Network.cpp/.h - Update other files to these changes - Code maintenance - Commented flow_engine definition, sorry Sergei - Really solved compilation problem with 'cgal' feature, sorry again - Update flowEngine files - Added missing #ifdef FlowEngine condition - Fixed compilation error Jan Stránský (3): 1. add function yade.utils.stressTensorOfPeriodicCell 2. removed BoundDispatcher from pack.randomDensePack 1. correct some misprints and do some actualization in user's manual. 2. correct mistakes in Shop::stressTensorOfPeriodicCell() 1. modification of Vector6 2. change of Peri3dController (+docs and examples) Janek Kozicki (11): making TriaxialStressController more python friendly. revert last commit fix two typos, replace Real strain[3] with Vector3r strain fix two small typos in docs, add spheresVolume python wrapper for TriaxialStressController. (I need it to calculate void_ratio=voids/sphresVolume) Sphinx documentation for plotting + color plotting. Added missing comment in TriaxialCompressionEngine Let TriaxialStressController calculate sphereVolume for clumps correctly. A trivial fix for exceptions when drawing data that is being modified right now refer to matplotlib manual remove duplicated part about pyplot Added pdf figs (not .svg, they are 5MB big!). added info about TeX usage of plot.labels. small fixes Added a throw condition if two spheres have zero distance. Works in #ifdef YADE_DEBUG only. Jerome Duriez (7): - Re-write of SimpleShear PreProcessor with YADE_CLASS_... macro - Creation of KinemSimpleShearBox, new class containing the features common to the various Kinem...Engines, and from which these Engines inherit now. - Replace of .isDynamic (deprecated) by .dynamic in some python scripts - the script simpleShear.py (renamed from SimpleShear.py) allows now to illustrate/test the various Kinem...Engines - Documentation typos/formatting in few files Few modifications in the doc of NormalInelasticityLaw (class Law2_ScGeom_NormalInelasticityPhys_NormalInelasticity), following the request done http://www.mail-archive.com/yade-dev@lists.launchpad.net/msg05207.html. Not sure it is perfect, but hope it is at least better... Re-write of "NormalInelasticityLaw" with use of ScGeom6D for what concerns rotations. => Rename of the corresponding Law2... Luc Scholtes (1): publication list update Sergei Dorofeenko (6): Viscoelastic basic particle model: move mass of particles from material to interaction Fix bug666246. See https://bugs.launchpad.net/yade/+bug/666246 merge with trunk Fix STLImporter; fix some examples 1. Fix ViscElBasic law to handle periodicity; 2. Add some regression tests Fix bug in Ip2_ViscElMat_ViscElMat_ViscElPhys Václav Šmilauer (88): 1. Docs update 2. Remove Dispatcher::add(string) (only accept functor objects instead) 1. Update documentation (backported to 0.50) 2. Remove the long deprecated object['attribute'] syntax. 1. Update docs 2. add some yade publications in bibtex 1. Port packaging changes from the 0.50 branch 1. Remove dem6dof related classes 2. add a few more publications to the bibtex db 1. Remove old io code in Omega 2. Update yade.eudoxos 3. Fix bug in pack.randomDensePack loading memoized scaled periodic packing from (backported to 0.50 as well) 1. Fix some spiral projection functions 2. Add manpage generation to yade and yade-multi 3. Add manpages to debian packaging, including alternatives (man yade -> man yade-bzr2284 etc) 1. Bugs in Cell (!!! no yet fixed: see scripts/test/peri8.py !!!) 2. Add optional devirtualized functors (experimental) 3. Adjust Peri3dController for large strains and test (modulo the cell bug) 4. doc fixes 1. Fix multiple authors syntax in bibtex (multiple authors MUST be separated by "and", not by comma!!!) 2. Add doctests of all modules, fix where failing 3. Add SpherePack.toSimulation Minor documentation fixes. 1. Split publications in articles, conferences, theses 2. Fix problem with scons 2.0, now it works out of the box 3. Add a better error message for VelocityBins with NaN velocity 1. Add show Paraview documentation to the postprocessing section 2. Fix at least partially the rotated cell bug in shift2 (https://bugs.launchpad.net/yade/+bug/601866), still some issues elsewhere, though. 1. Fix stale potential interactions -- we've been dragging this bug since april, I will backport it to 0.50. You are strongly recommended to update!!! The bug was, in the end, not in the collider, but in the container; it had <= instead of >= in one condition. Sorry. Fix Facet AABB computation in periodic scenes (another important update!) 1. LIVE PLOTTING (thanks to Janek for the idea and the suggestion of mtTkinter). Report bugs. 1. Fix InteractionGeometryDispatcher (in case someone still uses it). 1. Move triaxial documentation from the wiki to sphinx 1. Make utils.saveVars, utils.loadVars and utils.readParamsFromTable synthesize modules in yade.params.* to avoid name clashes in builtins (see docs and user's manual, you have to adapt your code if you use those) 2. Rewrite the installation page in sphinx (sorry, Anton, I hope it is a little more readable now) 3. Fix several undefined xrefs in the docs. 4. Disable wm3-compatibility in eigen wrapper (Vector3.UNIT_X etc) 5. Rename utils.chCylinder to utils.chainedCylinder, update docs there (sorry, Bruno, but chCylinder is really not a self-explanatory name) 1. Fix dispatch-torture scripts/test/dispatcher-torture.py 2. Remove wm3-compat code 3. Escape \d in the doc (must be \\d in c literals) 1. Make GL dispatchers proper classes exposed to python 2. Make State and derived classes indexable 3. Update scripts/test/dispatcher-torture.py 1. Fix compilation error due to free-standing function out (remember: NO FREE FUNCTIONS, everything must be inside classes, otherwise there is the risk of name clashes.) 1. Fix the GL dispatch (was enumerating child classes non-recursively, and your Gl1_ChainedCylinder did not inherit directly from GlShapeFunctor) 2. Do not use '|||' to separate plot axes, use None instead (||| is deprecated) 3. Move the Gl1_ChainedCylinder functor inside #ifdef YADE_OPENGL (please pay attention to that) 1. Add noqt3 feature (still keeps OpenGL available), run qt4 app at the beginning (emits warning) 2. Make GLUtils independent of QGLViewer 3. Add some metadata to attribute docstrings 4. Add scripts to render scene to pyQGLViewer and to show basic serialization interface in PyQt4. 5. Make static attributes non-static in python (enables docstrings) 1. Make static attributes documented (xrefs still not working, though) 2. Add section on the Material-State association to the Programmer's manual 3. Fix syntax error in qt4-attributes.py Fix docs 1. Do not uselessly pass Scene* pointer to Law2 functors (already in LawFunctor) 2. Attempt at Law2_ScGeom_CpmPhys_Cpm (not tested) 1. Add the possibility of custom constructors to python (not a hack as it used to be) 2. Add cached functors to explicitly created interactions (not saved, though) 3. Improve the ScGeom-based Cpm law 4. Change interface of StepDisplacer to specify mov and rot separately (old interface with deltaSe3 works, but warns) 5. Add scripts/test/cpm-dem3dof-scgeom.py to show some differences between the 2 geometry formulations. 1. Turn many fatal warnings in UniaxialStrainer to exceptions, remove some guess-logic 2. Automatically disable live plots in batches. 3. the concrete uniaxial test now has the scGeom switch 1. Some VTK recorder fixes. 2. cpm fixes (untested) 1. Save interaction as vtkPolyData rather than vtkUnstructuredGrid (to allow the Tube filter to operate on it). 1. add noShow to uytils.plotDirections 2. Some fixes for ScGeom+Cpm (still problematic) 1. Preliminary version of PyQt4 interface (3d viewer flickers, reason unknown). Config checks largely absent. 1. Remove linkStrategy option. Specifying chunkSize=1 provides the per-plugin linkage. 2. Add qt4 feature, which conflicts qt3. Qt3 must be now explicitly defined. QT4DIR must be defined (defaults to /usr/share/qt4, which works for debian) 3. Add checks for python modules and prettify boost checks. 4. Remove a lot of legacy code, so that the qt4 build avoids yade::serialization completely 5. Add live inspection and modification of arbitrary sequences, for a showcase see the Inspect button on the simulation tab (Body/Interaction inspection will be rather easy as well) 6. Add common handling of number arrays (Vector3, Matrix3, Quaternion) 7. Add to-python converters for vector> and list (only needed in Dispatchers, avoids tome exceptions) 8. Rename OpenGLRenderingEngine to OpenGLRenderer Path hotfix. 1. Dispatchers cleanup (will continue) 1. Remake inheritance tree for Dispatchers, so that functors are really of the right type. Involves some macro ugliness. 2. Add rudimentary (but working) inspection interface for engines, bodies, interactions 3. Fix compilation without qt3 (will be removed soon, completely) (forgotten file) 1. Make types and attributes in all editors clickable 2. Add editor for Se3, Vector3i, Vector2, Vector2i 3. Fix bugs in eigen's wrapper for Vector2i and Vector3i 1. Fix static properties of Eigen objects (return values, not references - see http://www.mail-archive.com/yade-users@lists.launchpad.net/msg02339.html) 1. SpherePack.makeCloud can distribute grains based on their mass (rather than count) along arbitrary PSD or uniformly if rMean is given (the default behavior is unchanged) 2. SpherePack.psd() function for generating particle size distribution, either based on particle count or their volume (mass) 3. A few fixes in the UI 4. Fix facet "coincident vertices" warning 1. Add script demonstrating mass-based and size-based particle size distributions (PSD) and how to specify and arbitrary function for PSD when generating random loose packing with SpherePack.makeCloud 1. Disable (experimental and malfunctioning) multiblock files with vtk<5.2 (did not exist) 1. Remove qt3 interface 2. Remove yade::serialization and related otherwise useless files 3. Remove lib/loki, use the official Loki library (in /usr/include/loki, package libloki-dev) instead, except for (highly incompatible) Singleton, which was moved to lib/base. merge noqt3 branch 1. Body flags instead of bools + isBounded/setBounded flag. 2. Move body_id_t to Body.hpp 1. Change body_id_t to Body::id_t 2. Assign Body.bounded in py/utils.py funcs (in prep for making BoundDispatcher 1d) 1. Make Bo1 functors really 1D 2. add Body::isBounded/setBounded so that there is a way to ask for a body to not have bound created by BoundDispatcher 3. Change bool Body::dynamic into a bit inside Body::flags 4. Change body_id_t to Body::id_t since boost::serialization can handle class-defined types gracefully 1. Integrate BoundDispatcher into Collider, its python ctor takes list of BoundFunctors now 2. Warning and help if BoundDispatcher is used directly (more precisely: if Collider has no BoundFunctors defined and there is BoundDispatcher in O.engines) 3. Remove Body(id,mask) ctor as it bypasses default value initializers 4. Adjust preprocessors to the previous item 1. Remove BoundDispatcher from O.engines in examples, scripts and sphinx (accidental typos possible, sorry) 1. Remove outputFilename from FileGenerator, pass that as argument directly; remove message as FileGenerator's member; FileGenerator is no longer ThreadWorker (was not used) 2. Remove all variables named 'rootBody' in generators (detail: make SeqSerializable number nicer) 1. Rename some stuff in Omega and Scene to make it consistent with python: Scene::currentIteration->Scene::iter Scene::getSimulationTime()->Scene::time Scene::getComputation...Time()->Scene::getComputationTime() Scene::stopAtIteration->Scene::stopAtIter etc. 2. Fix bug with per-class compilation (thanks to Sega for reporting) 1. Adjust docs for changes in the last commit 2. Add version requirement for python-sphinx to debian/control-template (Anton: that's the reason why the build fails in maverick; I uploaded 1.0~hg... into yade-users/external so it should build fine in a few hours; there is no use uploading 0.6 which is in maverick already) 1. replace {pre,post}ProcessAttributes by {pre,post}{Save,Load} (not virtual) 2. change attribute specification adding and additional attribute flags (read-only from python, not saved, should call postLoad after changing value from python) 3. Macros cleanup in Serializable 4. Support clang for compilation (more than 2x faster) https://yade-dem.org/wiki/Compilation_with_LLVM/clang (documentation not yet update to reflect those changes fully, sorry) 1. Add sub-stepping functionality (step by engines rather than by whole iterations) 2. Expose Scene in python properly (not very well tested yet...) 3. Update documentation about attribute flags 4. Remove some 3rd party files no longer needed 1. Add foce, torque etc display in the body tab of the inspector 2. Make force, torque etc access from python not require thread synchronization (sums up threads on demand) 1. When disabling sub-stepping when the last step is done only partially, all remaining substeps are run at the next step. 1. Fix bugs related to 3d views (centering, secondary views, closing views). Thanks to Sega for reporting. 1. Consistently use SUFFIX for what is the same for debug and non-debug version, and libDir for plugin installation directory. Hopefully that cleans the mess we've had. That should also fix startup problems with --debug that Anton reported 2. Do not #define PREFIX and SUFFIX as macros, since they are not used in the c++ code anymore (and should not be) 1. Link to the standard libstdc++.so.6 (which is instaleld everywhere) instead of libstdc++.so, which is only in the devel package of gcc 2. Full support for compilation with clang (packaged for lucid and maverick now), as explained at https://www.yade-dem.org/wiki/Compilation_with_LLVM/clang#Building_yade (openmp and optimizations do not work) 3. Add function to ScGeom to compute incident velocity from python. 1. Fix QT4CXX key error, thanks to Anton for reporting. 1. Fix startup error with debug-only builds. Sorry, Anton. 1. Enhance the gl-drawer for LawTester. 1. Fix both rotation and shear in LawTester. Signs are now consistent are correspond to displacements as defined on classical beam (x is the axis, y and z are in the shear plane) 1. Clear erro message when running debug-only build without --debug 1. Make number in qt4 show the left-most part if the text field is narrower, rather than the left part 2. Run the debug version if optimized is not available, even without --debug 3. Add the possibility of running the monolithig compilation (mono=True); not fully functional yet. 4. Add warning when using march with clang, which leads to crashes (optimizations are not the cause, in the end) 1. Flattening the file hierarchy, as mentioned previously. No changes to #include paths so far. 2. Change the directory where to install headers to not include yade version 1. Add OpenGLRenderer to DomainLimiter, that should fix the linking issue reported by Janek. 1. Fix debian linkage with MatchMaker (hopefully) 2. Add preliminary and experimental L3Geom class (do not use, do not ask etc -- yet). 1. Make forgotten class members public (sorry, Anton) 1. Enhance LawTester to work with L3Geom, fix some bugs 2. Add uN and uT references inside L3Geom, for respective components of the deformation vector 1. Add Vector6r wrapper (both c++ and python) 2. Fix prefix bug in initialization braking debian packages 3. Fix renames in py/test which made regression tests fail 4. Update LawTester and L3Geom for 6 dofs (not yet tested, please do not use now) 1. Add menu entry for the dbg package (yes, it really does not show up, dunno why either) 2. Add plasticity to Law2_L3Geom_FrictPhys_ElPerfPl, add convenience function to apply local forces 1. Clean up Vector6 and friends 2. Create Matrix_computeUnitaryPositive template to be compat between eigen2 and eigen3 (in the future) 1. undefine pi in MindlinPhys 2. Add isAspherical() flag to Body 3. Adjust kineticEnergy with PBC (not yet fully tested) 4. Make createInteraction work with PBC 5. Fix getIncidentVelocity with PBC and !avoidGranularRatcheting 6. Add some regression tests related to Cell (not yet fully done) 1. syntax hotfix for batch -- thanks, Ema. 1. Fix kinetic energy in PBC 2. Add tests to check PBC: Ek, incident velocity with ScGeom (with and without ratcheting), homothetic resize 3. Make it possible to set O.dt=0, for reasons of testing. 1. Deprecate NewtonIntegrator::homotheticCellResize in favor of Cell::homoDeform (compatibility interface with warning) 2. Add the possibility of homothetically changing positions rather than velocities; avoid the meanfield/fluctuation velocity jazz at the expense of vel not being time derivative of pos 3. Add PBC tests for the previous variant 4. Add defThreads arg to scons, which, if specified, gives default number of threads for simulations, if not overridden with -j 5. Fix a few example scripts, more work ahead come (please help!) 6. Add regression tests for saving/loading yade objects; on maverick, this makes the unregistered class to re-surface (!!) 1. Update the dispatch-torture script. 1. Fix laoding of unitialized LawTester 1. Disable debug messages from ScGeom in both debug and non-debug (!!) builds 1. Fix boost::sertialization class export (changed slightly in boost 1.42, which broke our case); that also solves the issues at maverick 2. Add framework for tracking energies (will be documented at some point), added to Law2_ScGeom_FrictPhys_CundallStrack, see scripts/test/energy.py 3. Convert energy trackers in Hertz-Mindlin to OpenMPAccumulator 4. Make OpenMPAccumulator align storage so that each thread uses one cache line (should be faster) 5. Make OpenMPAccumulator not require zero value pointer (is handled via ZeroInitializer template in lib/base/Math.hpp 6. Make OpenMPAccumulator work transparently with python (it can be used as a regular attribute now), make it boost::serializable as well; it appears transparently as a number in python. 7. Add OpenMPArrayAccumulator, for linear array of values (used in EnergyTracker) 8. Make deprecated attributes with non-g++ compilers (clang); previously, only g++>=4.3 was supported 9. Fix a few example script (not completed) 1. (hopefully) fix build with boost::serialization<1.42 broken by previous commit. 1. Add warning for missing ForceResetter in NewtonIntegrator (based step forces were last reset) 2. Rename *Spiral* to *Helix* (suggested by Jan, thanks!) 3. Generalize the post2d module to handle interactions (not just particles) 1. Fix intiialization problem in Shop::kineticEnergy (thanks, Anton) 2. Several enhancements of the post2d module 3. Fix a few crashers with deleted particles. 4. Improve utils.facetCylinder 1. Add HdapsGravityEngine which reads acceleration from real accelerometer in thinkpads (toy engine) 2. Fix bug in qt4 interface where multi-number entries (Vector3r etc) might have some values zeroed due to wrong order of initial update and signal connects. 1. Make clumps subclass of Shape (instead of Body) 2. Favicon in the batch web interface 3. Fix for clang compilation 1. Fix examples, delete a few other ones 2. Fix a bug in post2d ================================================== yade-0.50.0 Wed, Jun 9 08:30:02 2010 +0200 Anton Gladky (77): Added an example of VTK-recorder using. Thanks Sergei Doroffenko aka Sega Changes in facetBox function to get other facet normals Added import_mesh_geometry function to import mesh files into facets Function import_LSMGenGeo_geometry is added to import into the simulation .geo files, generated by LSMGenGeo libraries. Example is added in ./scripts/test/regular-sphere-pack.py and ./scripts/test/genCylLSM.py Fixes import_LSMGenGeo_geometry and its example Adaptation RockPM and PressTestEngine for material and state classes PressTestEngine is temporarily disabled as it causes YADE crash on start 1. TranslationEngine and PressTestEngines are activated 2. regular-sphere-pack.py is updated according to new material class 1.VTKRecorder is activated, except REC_CPM 1. scale factor is added to ymport.gengeo 1. Trivial change scale factor in regular-sphere-pack.py CohesiveStateRPMRecorder base templates are added. This Engine is for calculating cohesive contacts in the RPM model. 1. Created yade.ymport.gengeo() function for importing LSMGenGeo geometry directly to the YADE simulation without intermediate files. See ./scripts/test/genCylLSM.py 2. yade.ymport.gengeo() (import .geo-files function) was renamed to yade.ymport.gengeoFile() 3. CohesiveStateRPMRecorder is now working. (thanks Vaclav) 1. "Patch" to fix 490223 1. CohesiveStateRPMRecorder now derives from the Recorder 1. REC_INTR in VTK changed to save interaction data not only for CPM model. Very trivial fix in utils.py 1. In utils.facetBox added 'Defence from zero dimensions' 2. regular-sphere-pack.py is updated due to new names 1. Added moveTo und scale parameters for ymport.gmsh function Some syntax fixes: moveTo has been renamed to shift in utils.py and ymport.py ymport.gmsh function was renamed to ymport.mesh one ymport.mesh was renamed back to ymport.gmsh Trivial bug fix 318439 1. gmsh.ymport function is fixes to be more universal 2. "Bad" bug #505783 fix Fix bug 505783 1. Added utils.facetCylinder function to create arbitrarily-aligned cylinder composed of facets 2. utils.facetCylinder example is added to ./scripts/tests/regular-sphere-pack.py 3. RotationEngine example is added to ./scripts/tests/regular-sphere-pack.py 1. utils.facetBox() and utils.facetCylinder() function description where changed according to Epytext format. utils.facetBox() and utils.facetCylinder() description has been formatted to reStructuredText-format. 1. vtk-feature is on by default now 2. Added eigen library like a feature 1. Eigen check library fix. Thanks Bruno 1. Added some more Eigen headers to check in SConstruct 2. Commented the string in py/SConscript which made an error during compiling 3. Added yadeEigen.hpp to make a wrapper for Eigen 1. yadeEigen.hpp is added 1. Some changes for Eigen migration 2. TranslationEngineEigen to test Eigen library 1. Some changes in utils.facetCylinder function 1. Convert a RockPM class to YADE_CLASS_BASE_DOC_ATTRS 1. Convert Recorder class to YADE_CLASS_BASE_DOC_ATTRS 1. Removed TranslationEngineEigen 2. TranslationEngine and PressTestEngine were updated according to a new register standard 2. In SConstruct were added some more Eigen headers to check. 1. YADE_CLASS_BASE_DOC_ATTRDECL_CTOR_PY for CohesiveStateRPMRecorder is added, but not activated. 1. ForceRecorder added. 2. Some minor changes. initRun=true added to ForceRecorder 1. spheresToFile() has been fixed and moved to export file. Do we need "consider=lambda id: True" there? 2. ymport.ascii() was renamed to spheresFromFile; wenjieFormat was deleted there. The function is rewritten, comments are available now. 3. Added export.spheresToFile() and ymport.ascii() examples to regular-sphere-pack.py. 4. In timing.py InteractionDispatcher->InteractionDispatchers 5. Some default values in scons were changed to make YADE compilable with "standard" Ubuntu machine with 4GB RAM: chunkSize=7, jobs=2 Rotate function is added to manualWrap RotationEngine parallelized GravityEngine parallelized Some scons changes. Vaclav`s patch 1. penetrationDepth() is added to DemXDofGeom 1. penetrationDepth() declaration fixed. Thanks to Vaclav. 1. NewtonIntegrator - fixes of normalizing null-vectors 2. penetrationDepth() has been deleted from DemXDofGeom Adaptation to Eigen Library Previous commit correction. Thanks to Vaclav. Fix compilation error Orientation parameter is added for ymport.gmsh() function Small changes in ymport.gmsh() Some changes in utils.ymport module due to new Sphinx requirements. Some examples clean Examples cleaning Trivial genCylLSM.py fix Ubuntu 10.04LTS has GMSH version, where medit-mesh format is changed. It fixes wrong import of newly formatted files. Back compatibility is saved. 1. Quaterion.Rotate() wrapper is added 2. mill.py fixed 1. Examples cleaning 2. Code adopting to Eigen library `materialID` property is added to VTKRecorder VTKRecorder changes: 1. clumpIds recorder changed to clumpId 2. materialID recorder changed to materialId 3. ids recorder changed to id 4. Syntax cleaning 5. Default parameter 'all' is added subscribedBodies parameter is added to VTKRecorder Some fixes in VTKRecorder 1. Parameter groupMask is added to utils.sphere and utils.facet 2. VTKRecorder. subscribedBodies mechanism was changed to VtkRecorder changes: 1. Velocities of particles are now exported as Vector3 and Length component separately. 2. Added 'force' parameter for facets and spheres (sorry, not good tested yet) Force calculation mechanism is changed in VTKRecorder (Thanks to Vaclav). Experimental. VTKRecorder. force->stress. But only for Dem3DofGeom now 1. Some warnings were commented (please, don't leave unused variables!) 2. In Shop created getStressForEachBody method to clean up VTKRecorder a little bit (not tested yet, but compiles) 1. VTKRecorder is static now. 2. function ymport.testExt and export.testExt are added Comment parameter is added to export.textExt groupMask parameter of bodies is now exported to VTK-output as `mask` Blocking the crashing string in ElasticContactLaw in ./examples/rotatingCylinder.py encoding is fixed Some changes in examples 1. Fixes crash in VTKRecorder, when some bodies are erased 0.50.0 Boon Chiaweng (17): BasicPM.cpp hpp BasicPMTest.py in pkg/dem/meta BasicPM @ pkg/dem/meta BasicPM @ pkg/dem BasicPM @ pkg/dem meta local commit local commit Removed BasicPM.cpp and added CundallStrack.cpp. It is a duplication of Law2_Dem3Dof_Elastic_Elastic but written in meta style like RockPM. It is therefore cleaner and more friendly to a beginner. Comment local commit local local committing files which were not uploaded in last commit committing files which were not uploaded in last commit local Cohesionless Moment Rotation LLaw as implemented by Plassiard et al (2009). Verified changed Cohesionless Rotation Law to be cleaner added instructions on how to use ContactLaw with TriaxialTest Bruno Chareyre (102): A flag "neverErase" is added to the contact law to define if it can erase interactions. This flag can be turned on when another constitutive law (e.g. capillaryCohesiveLaw) is in charge of erasing interactions. - fix wrong usage of "neverErase" in TTWater - synchronise versions of ECLaw Fix the path in the warning. Please, never make me do that again!! A (little) bit of cleaning. Fix warnings. And one more back. - fix segfault in test.cpp (testing the triangulation functions) - adapt the collider to state/material State/Mat fix. Fixed : 2 "unused variable" warnings. - fix the bug on bodies type in CFTriaxialTest - rename CohesiveFrictionalBodyParameters => CohesiveFrictionalMat - fix the bias in cloud generation (try and position a sphere of constant radius) - some more cleanup of the code - update of the documentation Add the missing #include AABB Code using the velocity gradient to define periodic deformations (experimental) + script to test it. Needs compilation with #defined VELGRAD (currently needs uncommenting "#define VELGRAD" in 4 files in total). Fix some warnings. -Restore default behaviour even with #define VELGRAD, restore default homotheticCellResize=0 and register it. All py scripts should work whatever VELGRAD, except periodic-triax-velgrad.py (throw without VELGRAD for homotheticCellResize=1). -Clean code -Fix mail adress E.C. Make non-proportional scripts really work with VELGRAD : -Update Hsize when refsize is modified once (this is ugly! needs a cleaner way) -Adapt size control by periodic engines. Sorry for this new change in Cell.h. It will not change for a while now, next commits will be in engines. Add a new periodic compressor engine (candidate for replacing/merging with PeriTriaxController). Most important changes : - stress control using mass - defines the full stress tensor (not only normal stress for each axis) - defines strain with logarithm for comparison with goals and postprocessings - merge stiffness/inertia control in PeriTriaxEngine and remove periEngine - show usage of inertia (commented out) in py script A funny script, and no crash! -Fix the loading path, this example shows how shear create rotations. -A commented suggestion for dispatchers -Get back r1932 in PIC -Fix a wrong assignment of max vels in StressController that was causing non-isotropic compression (introduced a while ago when Luc Si. implemented 3D independant controls probably) Optional porosity in makeCloud, giving a value discards rMean (avoid guessing rMean when you know size and number). -Fix messed comment (save before commit!). Comments. - Rename Poison -> KsOnKn in some preprocessors. Not project-wide : I don't know what laws are used in PHYSPAR'ed files. - some cleaning in TT code - Add CGAL excpetion in GNU GPL license. rename KsOnKn -> KsDivKn. Revert weird changes from JFJ in TWrapper interface (min/max should not be mandatory in any functions!). I managed to remove the variable while adding the comment. Glad nobody noticed yet... - Remove old files - add insert(Scene& scene) to tesselation wrapper (faster) - move TWrapper out of namespace CGT and make it a YADE plugin - move TWrapper sources to pkd-dem/globalEngine, keep different (uncompiled) versions in lib for possible out of tree builds - some more documentation in TWrapper.hpp - missing files in previous commit - fix a member's name in TW - Fix : old names used in #includes and code - Add a function KLA::DefToFile() writing the triangulation and deformations to a file - Use it in MMAnalyser. - Fix read/write details in TriaxialState and few other small fixes - MMA is now fully operational for strain maps - switch some spaces/endlines Fix compile errors in VTKWritter and KLA. - Fix size range in makeCloud. It was generating an interval twice smaller than expected. Update the documentation. Used with uniform distribution, makeCloud now generates radii between mean-rRelFuzz and mean+rRelFuzz. 1. Remove lib/triangulation/TesselationWrapper. No need to keep in Yade files used only for out-of-tree builds + it was confusing to have the same file names twice. 2. Reformat vtk output. 3. Implement more methods to control the input/output of TesselationWrapper, access MicroMacroAnalyser methods via TWrapper interface, return all data in a python array (still commented). 1- move subdivideTriangle in the glNewList section. No reason to do that elsewhere. 2- Suggest using glList even when stripes=false (commented code) for better performance. Both changes affected by this bug for now : https://bugs.launchpad.net/yade/+bug/509084. - Remove the part of the code that was only a hack to display bodies with broken contacts, and remove some spaces. - Uncomment the line assigning deformation values in python::dict get(). - Add MATRIX3R_TO_NUMPY macro in numpy.hpp -Add missing members declarations (forgot to commit the header with a previous commit) Fix a compile error when openMP is not in features. - Global update of the triangulation lib. - Fix some compile errors. - Re-implement arbitrary boudaries ids. - Generalize boundary conditions. - Clean the code. -disable the independant GL display. - Fix compile errors with cgal in features. - Fix compile error (forgot this one in lastr commit) Flow: - Implement bz2 read/write for TriaxialStates - 3rd method for hydraulic force and some optimizations in FlowBS (Ema, I'll let you remove unused code) - new data in cell_info - Mostly some formatting. - Compute the new tesselation in Interpolate(). - remove files not used in yade. - Comment the includes pointing to out of tree files to prevent compile errors with implicit link. - Fix the bug (r2025 probably) in GSTimeStepper (test of line 28). - Register timeSteppers. - numpy : comment and new line at EOF - TW : fix a warning. - PTCollider : remove a "using namespace std" in the header. Some changes in the TCE behaviour. Save the state before stopping simulation, and start compression for reloaded files with autoCompression=true. Please keep intact the behaviour even when the code looks strange, there are reasons behind. I ran 1 week simulations, expecting saved xml at the end, but got nothing... I've included a fews comments about this. - A few more comments and code cleaning. - Fix an error in max_vel's definition. - Register attributes of the stress controller and of derived classes. - Make them "global" engines instead of "partial" (and replace "applyCondition" by "action") - Add a function computing the solid volume of a sphere packing in Shop. - py wrapping for getSpheresVolume (usage : O.getSpheresVolume) - Missing in previous commit and creating compile error, sorry for that. - Some documentation and code cleaning. - Fix syntax error + small changes in the documentation. Fix compile error ifndef YADE_OPENPM. -Register and code cleaning. -Link to the wiki page for general instructions and files download. -One more initialisation in ctor (strain). - Fix : wrong assignnment of friction for boxes. compactFrictionDeg was used in place of boxFrictionDegree. No idea when it was introduced. - Register TriaxialTest (yes!!). - Remove useless transition returning a LOG_ERROR. - More registering! - Remove unused unregistered engine. - Register, rename, clean code, doccument some attributes. - New names in Attic files (follow-up of r2156, more to come). - Add a "cohesiveFrictional" functor for usage in interaction dispatching (functionally replace the global engine CohesiveFrictionalContactLaw, which is still here and used in cohesive preprocessor). - Register "cohesiveFrictional" classes and rename them. - Some cleaning and a fix in the law for brittle failure. -Use new names. - Some fixes in triangulation. Python array still crashing in TW. - Use new names. - Remove SCG_SCHEAR, clean code and documentation a little bit more. - Take "fuzz" into account for the definition of rMean in the generated distribution. - Update the link to capillary files. - Make the radius of fictious sphere equal to the one of real sphere in box-sphere geometry. - Implement energy tracing in ElasticContactLaw - Remove some "#ifdef SCG_SHEAR" - Remove equilibriumDistance and initialEquilibriumDistance from FrictPhys and inheriting code. - Remove rotationBlocked from TriaxialTest. - Fix documentation (backticks in pkg/dem/PreProcessor/TriaxialTest.hpp, pkg/dem/Engine/PartialEngine/TriaxialStressController.hpp, pkg/dem/Engine/PartialEngine/TriaxialCompressionEngine.hpp). - Fix cohesiveFrictional crasher - Initialise saveSImulation correctly in TCE - Remove initialKn/Ks assignment Ip2_2xCohFrictMat_CohFrictPhys - Put back Vector3r::Zero() py wrapping for shop::getSpheresVolume(). Used e.g. for computing porosity in a periodic cell. - Use Shop::kineticEnergy in the recorder (adds the rotational term). - Fix strainRate comparisons for the case dynCell=true. - Simplify the code, removing cellGrow totaly and using gradVel everywhere instead (its time derivative). - Rmk : I suspect some of those changes have been commited before, then reverted, but I couldn't really spot when/why. Actually, the revert attempt broke the "dynCell" behavior. This commit should not change anything for dynCell=false. Let me now if you see a difference. - Fix the distance correction in plastic slip. - Compile error (sorry) : matrix(i,k) instead of matrix[i][k]... (couldn't we overload simply define [][] operator for Eigen matrices?) - Simplify equations inside plastic condition of Dem3Dof (1 sqrt instead of 3, less norm()), add a new function that uses a multiplier instead of maxDisp to take advantage of this new formulation (Vaclav, could you review and tell if we should merge maxDisp/multiplier in one single function with a bool? I didn't want to break any other part so I didn't touch the previous function). - Fix a wrong equation in shop::unbalanced force (max is for body force, not contact force). - hardcode tensor product in PeriIso, as this is instanciating a matrix and it is done for each contact at each step. - Remove one more norm(), return 0 for compatibility. - Fix the plastic dissipation equation in ElasticContactLaw and make plasticDissipation a OpenMPaccumulator. - Fix the name scaleDisplacementT(Real multiplier), handle the case force=0 correctly (thanks Vaclav). - Fix mail adress. - Implement scaleDisplacementT(multiplier) for all functors derived from Dem3DOF. This function is only used in Law2_Dem3DOFGeom_FrictPhys_basic at the moment. - Fix the undefined scene pointer in ElasticContactLaw. - Register TTWater correctly. - Update documentation of some classes, reformat a little, and fix (again) some email adresses. 1 - stress definition in PeriIsoCompressor was wrong for ScGeom (sign mistake due to double-reverse); Missing files in r2249. This commit complete the implementation of periodicity for incremental formulation, with updated relative velocity across periods. It has been tested carefully in periodic triaxial tests. 1. updateShearForce is renamed rotateAndGetShear and DOES NOT update shear force any more : this is Law2's job. Two versions are available, for resp. periodic and non-periodic case so that people don't have to worry about the additional "shift" in function parameters. 2. put the line fs+=ks*dus in laws using the former ScGeom::updateShearForce. 3. Put back Hsize in Cell (I need that each time I write a line in periodicity, really, please don't remove it). 4. The rest is details (fix wm3 incompatibilies, formating, documentation, etc.). - Fix compile error, sorry. - Fix Wm3 build in Dem3DofGeom_FacetSphere.cpp, the Real*Vector had not been reverted in that file. - Workaround https://bugs.launchpad.net/bugs/585898. - Implement Cundall-style positions scaling when (homotheticCellResize=2). - Rename Water=>Capillary (more accurate) - Register CFTTest. Chiara Modenese (5): Added constitutive law with Hertz formulation for contact stiffnesses Make penetrationDepth directly accessible through python Removing variable timeStepOutputInterval from the TT, it was useless. Add tangential values for the stiffnesses in HM law so that GSTS can be used (anyway contact forces are computed from the secant stiffness values to avoid numerical approximations). Avoid to store trialFs to the physics functor as it is already referenced. Correct a banal mistake in failure criterion. - Small changes in HM to handle PBC if scene is periodic (same logic as in ElasticContactLaw.cpp) Emanuele Catalano (16): Add code for flow problem resolution. Updated flow engine files. Made some correction in flowengine files. - Made corrections on flow code - Class registration via new macros introduced Few corrections in flow files. Forgot to delete some line. - Modified the type of some function in FlowBoundingSphere - Adapted FlowEngine to that modifications - Adjusted the use of "currentTes" in FlowEngine - Introduced timing information from engines and functors - Reorganized the code and cleaned from useless stuff - Verified properly re-triangulation - Made corrections on Gauss-Seidel break criterion - Assigned more severe tolerance to permeability computation - Fixed some error due to pointer scene* deletion - Added functions to write Mplotlib files - Adjustments to the code - Fixed retriangulation problems - Flow Code Maintenance - Introduced local permeabilities' correction agent - Fixed retriangulation problems - Walls proper identification is no more ID dependent - New output files for fluid pressure and settlement evolution - Fixed some warning came out with new compiler version - Fixed errors in walls' identification Janek Kozicki (4): fixes #571674 don't segfault when TriaxialStressController is facing clumps Scene.hpp -> remove obsolete comment QtGUIGenerator -> wider view on vector fields TriaxialStateRecorder and TriaxialCompressionEngine -> it was segfaulting with clumps: fix it. was not compiling with wm3 Jerome Duriez (26): Beginning of adapting code about simple shear (to train with bzr almost) - There are still indeed the YADE_REQUIRE_FEATURE(PHYS_PAR) lines which prevent these files to be compiled. - There is only one change that will be detected by your news compilings : change of a line in ElasticContactLaw. I corrected a link towards an URL corresponding to "old wiki" Renaming of all (normally) linked with this famous "ContactLaw1". Thanks to Vaclav for his scripts ! Dplt de SimpleShear hors de attic Re-activation (suppression de YADE_REQ...(PHYS_PAR) ) de RockJointPhys, et RockJointLawRelationships - "SimpleShear" was re-introduced in pkg/, instead of attic/ (Last ?) renaming of classes linked with "my" contact law. The normal inelasticity is now emphasized in the name, because it is what is specific to these classes. NB : this contact law takes also into account a moment transfer. Re-writing of classes CinemCNCEngine, NormalInelastictiyLaw (and those linked : NormalInelasticityPhys and Ip2_2xCohFrictMat_NormalInelasticityPhys) according to the macros replacing former REGISTER_ATTRIBUTES... Move of the line YADE_PLUGIN((...)) of CinemCNCEngine : it was before the "include...", and for Emmanuele it did not compile thus. Moved after the "include". Re-write of CinemKNCEngine according to new macros. Anecdotic changes in CinemCNCEngine Suppressing of commented lines Renaming of Cinem...Engine in an english way : - CNC = Constant Normal Load => CNL - KNC = Constant Normal Stifness => CNS - DNC = Constant Normal Displacement => CND - DTC = Constant Tangential Displacement => CTD - Correct writing of Disp2DPropLoadEngine : in order to perform tests on a shear box, piloted by dispNormal + k*dispTangential = 0 Compilation enabled by the way. - Minor syntax changes of doc of KinemCNSEngine - Suppression of CinemCisEngine, which even me almost never used. - Add of KinemCTDEngine: performs constant tangential displacement (=oedometrical) compressions on the simple shear box - move of KinemCND from pkg/common to pkg/dem. This Engine does not use really any DEM-specific concept, but I guess it will always be used in DEM simulations, so I thought it will be more adequate in dem/ - modify of corresponding "include"... - rewrite of KinemCNDEngine.hpp, with "good" macros - Use of YADE_CLASS_BASE_DOC_* macro family in KinemCNDENgine (it was not in r2082, contrary to what was previously said) - Rename of NormalInelasticityLaw into Law2_ScGeom_NormalInelasticityPhys_NormalInelasticity (I accept sincerely any proposal of shorter file names...) In fact it is not yet a functor, but I want to try to write it in a more proper way... - Corresponding changes in affected files - Details in other files - Rename of NormalInelasticityLaw into Law2_ScGeom_NormalInelasticityPhys_NormalInelasticity (I accept sincerely any proposal of shorter file names...) In fact it is not yet a functor, but I want to try to write it in a more proper way... - Corresponding changes in affected files - Details in other files ANECDOTIC COMMIT - fix typo in Engine.hpp : "fron" corrected by "from" I commit this alone, with no other changes, to make clear to everyone that this change to a central part of Yade is really anecdotic... - the code concerning the inelastic normal law is now in files named NormalInelasticityLaw.* instead of Law2_ScGeom_NormalInelasticityPhys_NormalInelasticity.* The class itself has still the same name (Law2...), but I changed the name of the files for practical reasons, as it seem other people do (files HertzMindlin, CohesiveFrictionalPM for ex). Tell me if this is problematic Re-write of CohesiveFrictionalMat with YADE_BASE... macro. It is not one of "my" files but I wanted to use it with Python, so I did it. I hope noone will be offensed. - Add of a script NormalInelasticityTest to test ... the NormalInelasticityLaw (Law2_ScGeom_NormalInelasticityPhys_NormalInelasticity) Moreover my python problems I had difficulties to obtain "perfect results". I think it is linked to the way un is computed in Ig2_Sphere_Sphere_ScGeom (see for example this "shift2"). Why not making penetrationDepth directly accessible through python ?? - Change of Law2_ScGeom_NormalInelasticityPhys_NormalInelasticity into a real functor instead of an Engine (compilation checked, and that's all) - Run of NormalInelasticityTest, now Law2...NormalInelasticity is a functor => same results as before it seems - Change of .Length() in .norm() in other scripts, as .Length() seems to be deprecated - One more change of .Length() in .norm() - check in NormalInelasticityTest.py that the curves are perfect ! **** "My" files ***** - Law2_ScGeom_NormalInelasticityPhys_NormalInelasticity is now a functor as it should. - Related changes in few files - The script NormalInelasticityTest tests now the tangential component also, and it will normaly soon check the MomentLaw. It illustrates also the various ways of controlling disp$ Fix of https://bugs.launchpad.net/yade/+bug/589516 (sorry, the compilation did not crash on my computer when I tried) - Cleaning changes in NormalInelasticityFiles... =>Add of a new Material storing the info for differences between loading and unloading (related changes) Luc Scholtes (13): - correction of thickness computation in TriaxialStressController - submission of an updated version of cohesiveFrictionalContactLaw called cohesiveFrictionalPM (more details in CohesiveFrictionalPM.hpp). Tried to follow the new rules for variables declaration with documentation... NEW TRY to add the updated version of cohesiveFrictionalContactLaw called cohesiveFrictionalPM (see revision 2068) SORRY FOR THE REDUNDANCY... updated comments in cohesiveFrictionalPM.hpp to be consistent with the computation correction of few bugs in CohesiveFrictionalPM fix almost all of the classes related to CapillaryCohesiveLaw (except TriaxialtestWater) to suit YADE_CLASS_BASE_DOC_* macros. Everything compiles but the efficiency of the code needs to be tested. This version fixes a bug which produced FATAL ERROR when 2100 version was launched. SORRY. Removed CapillaryRecorder and ContactStressRecorder. Renamed SimpleElasticRelationshipsWater to Ip2_FrictMat_FrictMat_CapillaryLawPhys (can probably be removed as it is identical to Ip2_Frictmat_FrictMat_FrictPhys...) and CapillaryCohesiveLaw to CapillaryLaw. Modified all Capillary related files. some updates of capillary files. Rk: all must be tested! -updates for capillary files (doc in Law2_..._Capillarity and changes in CapillaryStressRecorder)\n\n -add a feature to CohesiveFrictionalPM (creation of CFpmState to stock number of broken bonds) fix compiling error in previous rev (but some warnings linked to TriaxialStressController.hpp) new line added at the end of CohesiveFrictionalPM to avoid corresponding warning + cleaning some updates in CohesiveFrictionalPM. Unfortunately, the law still does not work in macroscale simulations... CohesiveFrictionalPM update - add normalization of a quaternion + other few changes Luc Sibille (2): I added the (updated) ThreeDTriaxialEngine, and it is also to see if we managed to make bzr working through an ssh tunnel - update of ThreeDTriaxialEngine with YADE_CLASS_BASE_DOC_ATTRS_CTOR_PY \n - modified ThreeDTriaxialEngine to be able to perform internal compaction (growing particles) \n - modified TriaxialStateRecorder to be able to use it with ThreeDTriaxialEngine Sergei Dorofeenko (37): 1. virtual facets don't have a BoundingVolume (flag noBoundingVolume in utils.facet) 2. new engine ResetRandomPosition (another name?). This engine reset the body position to a random position in specifed (by virtual facets) region. 3. example of using of ResetRandomPosition engine: scripts/test/ResetRandomPosition.py Fix [Bug 485209] 1. adapt VTKRecorder to body removing 2. fix bug with colon separated paths in a profiles. fix bug in scons.profile*: now CPPPATH and LIBPATH is a simple string (not a list) with colon separated paths. Adaptation of viscoelastic model classes and some others to new material/state classes: 1. Add a meta package ViscoelasticPM, which now contains all classes for viscoelastic model. 2. Fix STLImporter, getViscoelasticFromSpheresInteraction 3. Fix bug in utils.sphere, utils._commonBodySetup Adaptation examples/STLImporterTest.py for new viscoelastic classes. Adaptation NewtonDampedLaw to material/state classes Add to NewtonDampedLaw a modified leap-frog algorithm for accurate integration of rotation equation. This is intermideate commit: new algo seems to work, but needs some coupling with previous code. merge clump branch with trunk Add some tests for clumps Fix bug with import facets from stl small optimization of acc.rot.alg; fix velocity and add angVel in VTKRecorder." 1. handle DOFs for new rotation alg.; 2. add new clump-hopper-viscoelastic script merge with clump branch: 1. handle DOFs for new rotation alg.; 2. add new clump-hopper-viscoelastic script; 3. small optimization of acc.rot.alg; fix velocity and add angVel in VTKRecorder. Intergate new acc.rot.alg. to code. Intermediate commit. Rename prevAngMom to angMom revert NewtonDampedLaw to r1817 and rename prevAngMom to angMom Integrate new rotate alg. into code. Intermideate commit integrate a new acc.rot.alg to NewtonDampledLaw framework remove log_trace from NewtonDampedLaw::lfRigidBodyRotate Integrate new rotate integrator into the NewtonDampedLaw framework NewtonDampledLaw: rename functions and remove duplicate code as suggest Vaclav (thanks!) 1. NewtonDampedLaw: rename functions and remove duplicate code as Vaclav suggest (thanks!) 2. Add spheres ids to VTKRecorder 1.fix link with GLU for QGLViewer; 2.fix header for Shop; 3.Add clumpids to VTKRecorder Update ResetRandomPosition STLImporter returns a vector imported facets instead of adding them directly utils._commonBodySetup does not reset body id. small update examples/STLImportedTest.py Fix bug #531394 refixing bug #531394 Reanimation of some examples; fix bug in ymport.stl fix bug in utils.py; consistent signs in ViscoelasticPM fix typo small modify of STLImporterTest.py Fix shear direction damping as Chiara Modenese suggest. And more fixing damping shear direction yet more fixing ViscoelasticPM Add doc for utils.getViscoelasticFromSpheresInteraction Unknown (5): 1. Make all paths given to scons on the command line COLON separated rather than space separated. BACKWARDS INCOMPATIBLE if you used scons in scripts (doesn't affect saved profiles). (Rationale: impossible to save the option in shell and later use it due to quoting issues) 2. Optionally set PATH as argument to scons (you can use scons PATH=$PATH as well) 3. Enhance VTKRecorder to record interactions andsome data on them. 1. Fixes in scons for qt path (hopefully OK now); remove VTKINCDIR, use CPPPATH which alread exists 2. Improve yade-multi web interface (keep it running if --http-wait specified; show total time at the end) 3. Fixed in VTKRecorder and CPM 1. Enhance yade-multi to query running jobs for status (percentage, step number etc), to keep connections, allow more fine-grained thread number control 2. Add some more cpm data (like sigma, sigmaMean, tau) 3. Enhance vtkrecorder to save those cpm data 4. Don't set matplotlib backend in yade.plot automatically, since ti breaks in headless environments 5. Add O.tags.has_key A typo in SConstruct. getClassIndexStatic() member turned to public. Vincent Richefeu (2): - Increase DISTANCE_MAX - use gcc-4.0 (4.3 doesn't work correctly on mac snow leopard) Rewrite KinematicLocalisationAnalyser::DefToFile for vtk format. Václav Šmilauer (264): 1. Hopefully fix vtk libs in SConstruct (?) 2. Add compress parameter to VTKRecorder (doesn't seem to make any difference in the output files, however...?) 3. Add CPM damage to VTKRecorder 4. Move spiralProject to Shop, to be usable from within c++ 5. Add a 2 functions to compute 2d and 3d stresses to yade.eudoxos 1. Enhance VTKRecorder to save more CPM data 2. Add confinement storage to CpmMat; CpmStateUpdater now computes that. 3. Add method to get aabb of Gauss-smoothed domain. 1. Update examples/concrete/uniax.py and add CpmPhysDamageColorizer to renamed classes dictionary with warning. 1. Use colon as separator to all path parameters to scons (PATH, CPPPATH etc), to avoid shell escaping insaneness. NOT BACKWARDS COMPATIBLE if you call scons from scripts with multiple paths. Scons.profile-* files will work. 2. Add PATH parameter to scons, for things like pkg-config that we call during the configure stage (you can propagate whole path by saying scons PATH=$PATH) 3. Extend VTKRecorder to record interactions as well; add some cpm-related parameters. 4. avgStress is now Vector3r 5. Fix openGL-less builds (missing parenthesis in core/SConscript) 1. Remove VTKINCDIR (duplicates already existing CPPPATH) 2. Fix handling of QTDIR (hopefully?) 3. Yade multi now displays total time at the end, takes --jobs (long option for -j), and the http server keeps running as long as the summary page is requested if run with --http-wait 4. Fix computation of sxx, syy, szz in cpm Yade-multi, VTKRecorder enhancements, &c 1. Fix compilation on nfs (unable to delete .nfs* files) 2. remove debug message from the info socket 3. Make SpiralEngine assign velocities to (nondynamic) bodies so that when velocityBins are used, contacts are not missed. 1. Add example of generating randomly-packed tunnel-like scene (6 lines of python code). 2. Add explicit check for physics dispatchers in InteractionDispatcher even in the optimized build. Fix what seems like bug in is2is4scg... 1. Set timeout on infosocket 2. Add velocity bins to uniax.py example 1. New yade.post2d module for 2d post-processing (raw/smooth scalar/vector field plots), with docs and examples 2. Add epydoc brief documentation to all python modules 3. Change constructor of GaussAverage in python to take always the relThreshold parameter 1. New BodyVector written from scratch: supports body removal, intelligently allocates new ids at insertion. Is now default; a few pecent faster than BodyRedirectionVector which will be removed after a while. 2. Remove BodyAssocVector 3. Remove mass-spring for good 4. Add body removal support to InsertionSortCollider 5. Add empty body support to NewtonsDampedLaw, GravityEngine, OpenGLRenderingEngine and a few others 6. Wrap body removal from python 7. scripts/test/remove-body.py to demonstrate continuous dynamic body addition/removal. 1. Remove InteractionHashMap Add 'deprecated' feature which guards code pending removal. To compile those, you have to add 'deprecated' to features. 1. Add infinite axis-aligned planes (Walls), interacting with spheres; I would like to deprecate Boxes soon. scripts/test/wall.py show the functionality, utils.wall create ready-made body. 2. Add Walls to triaxial test as an option to facets and boxes. 3. Unbreak ef2_Spheres_Elastic_ElasticLaw (passing neverErase broke virtual function resolution -> exception) 4. Make Simulation controller always open, closing it quits yade. Generator accessible from controller; player must be accessed from controller by opening generator, closing it (where by main window apprears), then opening player. 5. Change prototype for InteractingGeometry functors, to get some information about the openGL context (scene radius and center, ATM). 1. Remove all deprecated classes as described in the last commit. 2. Start flagging code needing GeometricalModel by YADE_REQUIRE_FEATURE(shape). Once it compiles without shape, affected classes will be removed. Remove GeometricalModel from everywhere, unless you compile with the 'shape' features. Many classes had to be disabled; those who care about them are expected to fix them so that they don't rely on GeometricalModel. I will post on the mailing list on how to do that. Trivial fix. But shape feature is no longer supported, fix such things yourself next time, if you need them. 1. For each interaction, save when it was last seen by the collider. Adapt SpatialQuickSortCollider for that (remove Interaction::cycle), it saves 2 loops over interactions. The deletion is triggered in InteractionDispatcher/InteractionGeometryDispatcher afterwards. This infrastructure is neede for the grid collider. 1. Flatten file hierarchy a little bit. 2. Add MetaEngine{1,2}D and EngineUnit{1,2}D to the MetaEngine.hpp and EngineUnit.hpp instead of having them in separate headers. Remove 'deprecated' features from SCons files since they're not in the codebase anymore. 1. Change syntax of python docstrings to restructured text instead of epytext (for future compatibility) 2. Workaround CPPPATH error saving option 3. Make SpatialQuickSortCollider skip boundless bodies 4. Fix rendering bodies with names (i.e. selectables) Initial commit of new Material and State classes that will replace PhysicalParameters in the future. No changes in functionality. 1. Fix crash when removing bodies with velocityBins and MetaInteractingGeometry2AABB 2. Add option to draw normals for InteractingFacet (off by default); see scripts/test/remove-body.py (commented) Fix compile error introduced in last commit. Rollback point for adding YADE_REQUIRE_FEATURE(physpar) everywhere. rollback point 2 Build core only OK (no gui,python,extra) NewtonsDampedLaw, Clumps, SpatialQuickSortCollider compile. Checkpoint before replacing PhysicalParametersMetaEngine and friends REMOVED PhysicalParameters, add Material and State classes. DISABLED many (most) plugins, I ask the respective authors/users to fix them. 1. A few small leftovers. 1. Initialize dnesity to 4800 in CpmMat 2. Add warning if passing obsolete physParamsClass to utils.{sphere,facet,box,wall} 3. Add Material.cpp to ensure virtual functions work properly (not sure if they didn't, but now they do) 4. Fix uniax.py 1. shared materials; see scripts/test/facet-sphere.py and also py/tests/omega.py. 2. added some regression tests for Omega 3. python iterator over O.bodies silently skips erased bodies now 4. finally rename all MetaBody::transientInteractions to MetaBody::interactions 1. Fix uninitialized memebrs in Material (thans Anton) 2. Add equivalent of dispSe3 to OpenGLRenderingEngine directly 3. Fix pack.py 1. Make the optimized build default (as per Sega's suggestion); it also sets linkStrategy to monolithic (is that OK?) 2. Add some vtk dirs to default CPPPATH 3. Fix periodic boundaries in OpenGL & remove some YADE_SHAPE stuff from there. 4. Fix some warnings 5. Fix pack.randomDensePack **kw 6. Adapt examples/concrete/uniax.py to shared materials 1. Add Material::newAssocState creating State instance that is supposed to go along with given material; wrapped in python. It is used by utils.{sphere,facet,box,wall} functions now (hence by most or all body-construction things in python) 2. Fix py/post2d.py and examples/concrete/uniax-post.py 3. Fix crash for material-less bodies in scripts/test/regular-sphere-pack.py 1. Move import functions from utils to yade.ymport (sorry, import is python keyword and cannot be used ;-) ). yade.utils contains proxy function that warn and call the implementation in ymport. 2. Added virtual book Material::stateTypeOk(State*). 2. MetaBody checks that material--state couples are OK at the first run (like initializers, but not listed among initializers...); throws on error. Added this to regression tests. 3. O.step() now calls MetaBody::moveToNextTimeStep() directly, instead of spawning worker for 1 iteration. 4. Adapt stl import test to yade.ymport (the facets look weird, though?) (forgotten: remove prototypes for incrementCurrentIteration and incrementSimulationTime that are no longer used from Omega.hpp) Regression check that NULL state throws (if there is a material) as well. 1. Merge PeriodicInsertionSortCollider into InsertionSortCollider (periodicity detected automatically); PeriIsoCompressor moved to a separate file. 2. Initialize GranularMat properties (such as posisson't ratio) to arbitrary but non-zero values (avoids NaN's and disappearing bodies, bug #488100) 3. Re-enable clump logic in NewtonsDampedLaw (was #ifdef'd out), fixes bug #488100 4. Change prototypes of Shop::sphere and other methods, to take advantage of shared materials. 5. Change Collider::probeBoundingVolume prototype to return vector instead of storing it in the collider itself. 1. Enable velocityBins in pack.randomDensePack for periodic compression, speedup of about 13% (quite small?) 1. Fix Bruno's compile error pkg/dem/meta/Shop.cpp:281: error: operands to ?: have different types 'boost::shared_ptr' and 'boost::shared_ptr' 2. Fix warning's from out-of-order initializer in State. 1. add #error to DataRecorder so that people don't use it, with explanation. 2. Add Recorder, deriving from PeriodicEngine and taking care of opening file. 3. Derive TriaxialStateRecorder from Recorder. (Anton: do the same for CohesiveStateRPMRecorder, I don't want to edit it since the version would probably diverge if you work on it now) 4. Fix PositionOrientationRecorder (is that one being used at all?) 5. Rewrite Singleton (to work accross different translation units) 6. Remove qt3 from 'exclude'. Enabling opengl requires qt3 automatically now. 7. Remove lib/sqlite3x, which was only used in noew-removed SQLiteRecorder 8. Experimental main in python (not fully functionaly for now) 1. Re-enable temporarily commented code. 1. Re-enable PeriodicPythonEngine and a few other YADE_REQUIRE_FEATURE(PYTHON) that I overlooked 2. Make the python main program work with plugins (RTLD_GLOBAL in both DynLibManager and python); doesn't work with openmp and opengl (investigation ongoing), looks promising otherwise. The program is called yade-trunk-dbg-py and such; not all options of the c++ main is implemented (yet?). 3. (In previous commit) Remove loading preferences.xml, just use QtGUI if possible and enabled, otherwise "only" python console. 1. Remove rests of Preferences. 2. Remove HUP emergency saving (was disabled since long anyway) 1. Add chunkSize option that determines how many plugins will be compiled together. (orthogonal to linkStrategy, that one determines how many will be linked to together). Should avoid issue with monolithic link on machines with less RAM; allows parallel compilation of plugins on machines with lots of RAM. Defaults to 20. 1. Re-enable crash warning in pyboot.cpp 1. Remove implementation of global functions from Tetra.hpp to Tetra.cpp (gave errors on linking multiple objects including that same header.) 2. Add Body().geom as alias for (now deprecated?) Body().mold Make YADE_DEBUG work as expected (set debug leverl with log4cxx); avoid possible crash when logging during early log4cxx initialization. 1. Change prototype for InteractionGeometryMetaEngine to receive const State& instead of const Se3r&. Add const Vector3r& shift2 for peroioditiy (ZERO by default). 1. Fix Sega's and Anton's bug which was introduced in r1820 (keeping return from older version, instead of continue :-| ) 1. Rename EngineUnits to Functors, MetaEngines to Dispatchers (only the 4 types that we will keep). Python scripts should not be affected, except for raising deprecation warnings for the old names 2. Fix corner cases in deprecated names logic in python 1. Rename MetaBody to World (it had to be done at some point, regardless of what the name is) 2. Add Engine::world, set before every call in moveToNextTimestep 3. Change names of a few related methods 1. Fix "rootBody" in FileGenerator saver. 1. Add scan&replace scons builder 2. Get libstdc++ path from the compiler, embed it in main.py; it runs fine 3. Fix temporary filename in Omega (forgotten dir separator) (doesn't compile, will fix in a few minutes; need to transfer between computers now) cleanup of python boot code 1. Add yade.system module containing most startup things 2. yade-trunk-py usable now; startup is _much_ faster than the c++ main :-) (lazy linkage?). If everything works well, the c++ main will be deprecated soon (though still functional) 1. Add YADE_LINK_EXTRA_LIB so that plugin can request to be linked with additional library (used in MicroMacroAnalyser) 2. Add -frounding-math when compiling with CGAL 3. Lot of reorganization of python code. Yade is now fully usable (it seems) with the python main (yade-trunk-py), and also lodable from pure python 1. Fix assertion failure in NewtonsDampedLaw triggered by clumps in debug mode (typo) 2. Fix periodicity for Dem3Dof classes 3. Don't put things to __builtins__ in python, from yade import * will import wrappers as necessary into current global scope instead. 4. Expose Omega as yade.wrapper.O instance in the c++ code directly. 1. Error out if adding the same body twice from python 2. First steps for user tutorial (please do not edit now) 1. Move TesselationWrapper to lib/triangulation. 2. Main program (e.g. yade-trunk) is now in python; the old is yade-trunk-cxx. Most functionality should be the same, but please do report undesired behavior. 3. Adjust ipython configuration to be more friendly. 4. Improve default log4cxx output format 5. rename yade.PythonTCPServer to yade.remote 6. Remove annoying "INFO: sanitize failed" warnings 7. SimulationController now closes on escape or any function key. 1. Rename World to Scene, as Bruno suggested. It feels better, we are no gods to create worlds... 2. Rename BoundingVolume to Bound; BoundingVolumeDispatcher to BoundDispatcher; dtto for Functor 3. The 'shape' feature was renamed to 'geometricalmodel' feature to not conflict with the following: 4. Rename InteractingGeometry to Shape 5. Rename ef2_*_Sphere_Dem3DofGeom to Ig2_*_Sphere_Dem3DofGeom 6. Rename ConstitutiveLaw to LawFunctor, ConstitutiveLawDispatcher to LawDispatcher 1. Fix many warnings in various parts of python code related to renaming. 1. Rename data members to follow types: Body::boundingVolume → Body::bound, Body::interactingGeometry → Body::shape. Old names are still present as references with deprecation warning by the compiler. Regression tests passing. 1. make realVersion to be bzr revision 2. Re-enable Controller at startup always 3. Always rebuild python template files invisibly, since they wouldn't spot revision change otherwise (very fast) 1. Fix the plot example. 1. Add PeriTriaxController, with sample script in scripts/test/periodic-triax.py; works perfectly 2. Fix stress sign in PeriIsoCompressor (summing abs stresses still broken) 3. Handle periodic boundary moves with velocityBins 4. Rename ncb to scene in NewtonsDampedLaw (gradual change) 1. Fix (hopefully) an issue with log4cxx 0.9 reported by build robot. (Python 2.5 fix for robot) 1. Enhance PeriTriaxController for controlling strain/stress in periodic simulations; stress and strains appear to be physically correct now (however, see https://bugs.launchpad.net/yade/+bug/493102) 2. Add examples/concrete/periodic.py, showing uniaxial/biaxial periodic loading. Results are awesome. 3. Fix stiffness computation for Cpm. 4. Add (unpolished) pack.randomPeriPack for generating periodic packings. 5. Add some sphinx doc files (no contents, just skeleton). 1. Change prototype to InterationGeometryFunctor (see https://www.yade-dem.org/index.php/API_Changes) 1. Write some docs 1. Fix PeriTriaxController example 1. Refactor parameter table support (one reader class) 2. Fix yade-multi, add detection of regular exit with log4cxx crash at exit 3. Add --nice option to main.py 4. Make O.exitNoBacktrace output either "Yade: normal exit." (magic for OK exit, detected by yade-multi for instance) or "Yade: error exit". Fix reading parameters from table (thanks Anton for patience) Hopefully fix yade-multi https://bugs.launchpad.net/yade/+bug/493686 now? Please confirm. 1. Add Scene* Functor::scene, update it from all dispatchers at every step. 2. Add NewtonsDampedLaw::homotheticCellResize (0,1,2 for disabled, velocity or position update) (not tested at all) 3. Start removing Scene* ncb and Scene* rootBody etc from Engine::action as I stumble accross them. Please do the same, Engine::scene can be always used instead. 1. Renames as discussed on yade-dev: NewtonsDampedLaw -> NewtonIntegrator, StandAloneEngine -> GlobalEngine, DeusExMachina -> PartialEngine. Regression tests ok. 2. Rename the confusing scons 'pretty' option to 'brief'. Profiles will be updated automatically, both options are valid for now. 1. Finish renaming DeusExMachina -> PartialEngine 1. Enable search engine in doxygen. 1. Fix https://bugs.launchpad.net/bugs/495358 (seems like concurrent access with Anton ;-) ) 2. Add mask check to the collider 3. Initialize Body::id with Body::ID_NONE rather than -1 4. First attempt at doctest (for utils.sphere) 1. Don't call CreateIndex in Materia::Material (see https://bugs.launchpad.net/yade/+bug/495437) 2. Add dipl and rot methods to body, for querying displacement and rotation relative to reference position and orientation (moved from the python wrapper). 3. Add .classIndices to return list of class indices from the instance up to the top-level indexable from python. 1. Rename Sphere (as GeometricalModel) to SphereModel (to facilitate InteractingSphere -> Sphere soon) 2. Initialized density to 1000 in Material, to avoid weird explosions 3. Add scripts/tests/triax-basic.py, a TriaxialTest in python 1. A few renames: Facet → FacetModel, Box → BoxModel, GLDrawSphere → GLDrawSphereModel, GLDrawBox → GLDrawBoxModel 2. Remove shadowVolume functors, since they were not used anywhere in OpenGLRenderingEngine 1. Rename GLDraw* to Gl1_* 2. Scene no longer inherits from Body, some of its properties are "emulated" (bound, shape). Not sure if Shape is really needed, perhaps for periodicity things etc in one place? It can be in the class itself just as well. 3. Remove MetaInteractingGeometry2AABB from all files (no longer needed); backwards-compatibility with python assured. Hotfix for twice-renamed Bo1_SceneShape_Aabb. 1. Add some introspection interfaces to Indexables and dispatchers, as needed for Programmer's manual (https://yade-dem.org/sphinx/prog.html#multiple-dispatch), remove dead code, some cleanups. 1. Move lib/triangulation stuff to CGT namespace as suggested by Bruno in https://lists.launchpad.net/yade-dev/msg02658.html 1. Rename SpheresContactGeometry to ScGeom 2. Rename corresponding functors: InteractingBox2InteractingSphere4SpheresContactGeometry -> Ig2_Box_Sphere_ScGeom InteractingFacet2InteractingSphere4SpheresContactGeometry -> Ig2_Facet_Sphere_ScGeom InteractingSphere2InteractingSphere4SpheresContactGeometry -> Ig2_Sphere_Sphere_ScGeom 1. Rename InteractingBox -> Box, InteractingSphere->Sphere, InteractingFacet->Facet. 1. Fix renaming conflict (InteractingSphere->Sphere shadowed by older Sphere->SphereModel) 1. use stderr for standard startup messages 2. add scripts/test/dispatch-torture.py for visualizing dispatch matrices in html. Add support HTML.py module from the web. 3. call cleanupTemps if exiting; we should leave cruft in /tmp/yade-* only if crashing before trying to exit (unusual) 1. Make MetaInteractingGeometry2AABB warning more descriptive. 1. Remove Scene::{cellMin,cellMax}, use just Scene::cellSize; update all related code and scripts 2. Change python interface: Omega.periodicCell=Vector3 (only size, no more min/max) 3. Add (not yet used) core/Cell.hpp header. Will be accessible as Scene::cell, once used. 1. call centerScene() in GLViewer ctor, bind C to centerScene if no body is selected (was deactivated most likely by mistake, bzr annotate thinks it was me...!?). This should fix bug reported by Chiara for TriaxialTest with UpperCorner=Vector3(12,12,12) Fix missing import in py/post2d.py 1. Use Scene::cell instead of Scene::cellSize; update all code 2. Have O.cellSize (instead of O.periodicCell; add wrapper with warning) and add O.cellShear 3. Add scripts/test/periodic-shear.py (purely for showing the display things, no interactions etc yet!) Do pending renames: * AABB → Aabb * Interacting{Box,Sphere,Facet}2AABB → Bo1_{Box,Sphere,Facet}_Aabb * Wall2AABB → Bo1_Wall_Aabb * GLDrawAABB → Gl1_Aabb 1. Finish implementation of sheared periodic space. 2. Fix bug which might have caused missed asymetric (Facet - Sphere, for instance) in periodic space, when interaction order was reversed. 3. Add possiblity to pass a callable to utils.spheres' material parameter, to avoid shared material properties when called indirectly (pac.randomDensePack) 4. Remove Aabb::{center,halfSize}, since they were used only rarely and were redundant. 1. Remove abstract RenderingEngine 2. Remove SceneShape and related classes 1. Add python2.5 relative module import workaround (hope it works) 2. Move some python accessors from yadeWrapper to their respective classes 3. Half work on periodic cell scaling in the OpenGL renderer 1. Add ctor priorities, but only for gcc >= 4.3 (for early logging and plugins; necessary for the next point) 2. Register Functor core class in the factory (will be done for the rest soon, see https://bugs.launchpad.net/yade/+bug/498337) 3. Wrap Matrix3 class in python, write unit tests for it. 4. Re-enable ForceEngine and FacetTopologyAnalyzer 5. Update a few scripts in test/scripts 1. Implement arbitrary strain for periodic cell (see scripts/test/periodic-shear.py). Most code adapted accordingly. 2. ClassFactory now logs to cerr if YADE_DEBUG env var is defined, not otherwise. This avoids initialization order of plugins vs. ClassFactory itself 1. Fix weird behavior reported by Luc at https://lists.launchpad.net/yade-dev/msg02794.html (numerical issue) 2. Fix crash at body selection (wrong dispatcher arguments) 3. Add warnings about what Luc observed, should it happen again. 1. Allow spaces after YADE_REQUIRE_FEATURE (before parenthesis) and around its argument inside parentheses. 1. Fix https://bugs.launchpad.net/yade/+bug/499697 (sorry, Luc) 1. Rename BexContainer to ForceContainer, O.bex to O.forces etc. Mostly backwards-compatible in python. 2. Add Shop::flipCell which changes shear without affecting interactions (by integral steps), demonstrated in scripts/test/periodic-shear.py (not yet fully tested, but doesn't crash ;-) ) [BROKEN due to recent VELGRAD changed] 3. Add InteractionsContainer::eraseNonReal (Fix unsignificant hasShear mistake in Cell, clean old code there) 1. Make python interaction iterator return only real interactions. (TODO: regression test & check scripts for compat) 1. Add cell strain rate integration to the Scene loop. 2. Remove Cell::Hsize and friends, remove VELGRAD; unify both approaches. 3. Update some scripts, fix Shop::flipCell (demo in scripts/test/periodic-triax-velgrad.py) 1. Use Cell::trsf instead of Cell::strain (which really meant trsf-Id) 2. NewtonIntegrator now precomputes cellTrsfInc for the current step, instead of using Cell::getTrsfInc getting increment from the previous step (!?) 3. Update scripts and other code. 1. fix size computation in cell 2. Save history at exit, incremental history search with arrows up/down 3. Don't return false in Ig2_Box_Sphere_ScGeom for existing intr 1. Separate rotation&shear from scaling in cell. Fix possible error (but also possibly not?) error in collider. Spheres' Aabb's are still probably not correctly enlarged. 1. Forgotten new file. 1. Re-enable error check in InsertionSortCollider, make the explanation clearer 2. Fix sphere's Aabb (hopefully) (Bruno, could you check that?) 3. Worker thread now catches exception from simulation and O.wait() (or O.run()) will rethrow it; avoids crashes from exceptions. 1. Preliminary attempt at https://blueprints.launchpad.net/yade/+spec/c++-numpy-arrays (for postprocessing and similar), example in eudoxos.testNumpy 2. SpherePack::makeCloud can be asked for negative number of spheres (= as much as possible) 1. May fixes for errors found when running larger simulation. 2. Move material properties from Ip2_CpmMat_CpmMat_CpmPhys to CpmMat itself. 3. Some reverts in PeriIsoCompressor (log strain and usage of refSize) 1. Add clone function to python objects, taking optinal python dictionary with changed attributes 2. Fix PeriIsoCompressor for examples/concrete/periodic.py 3. GaussAverage relThreshold argument is now optional in python, as it is in c++ (again) 1. Fix examples/concrete/periodic.py 2. Fix (partially) scaling of periodic cell resize in OpenGL Implement https://blueprints.launchpad.net/yade/+spec/sphere-gl-stripes (weird lighting things). Must be enabled via "Gl1_Sphere(stripes=True)" in python. 1. Fix SpherePack().makeCloud() in python 1. Remove some old docs 2. Attempt at update-alternatives 3. packaging updates 1. FIx core class registration in the factory (fixes inheritance info in python for such classes) 2. Fix debian packaging, use update-alternatives Bulk of consistency renames (hopefully last for long time to come): * ElasticMat → ElastMat * GranularMat → FrictMat * NormalInteraction → NormPhys * NormalShearInteraction → NormShearPhys * ElasticContactInteraction → FrictPhys * SimpleElasticRelationships → Ip2_FrictMat_FrictMat_FrictPhys * ef2_Spheres_Elastic_ElasticLaw → Law2_ScGeom_FrictPhys_Basic * Law2_Dem3Dof_Elastic_Elastic → Law2_Dem3Dof_FrictPhys_Basic 1. Document PeriodicEngine (doxygen comment in the header) 2. Fix runtimePREFIX vs. PREFIX in yade-multi (only affects packages, not regular builds) 1. Fix epydoc generation with python main (thansk Anton for th yade-cxx hint!) 2. Expose O.tmpFilename returning unique filename in temporary dir that is deleted at exit 3. Do not import yade.plot at startup, since it initialized matplotlib to TkAgg, which needs $DISPLAY (and fails later if no $DISPLAY exists, i.e. for headless simulations) 4. Fix shear stress computation in CpmStateUpdater 1. Fix numpy_boost and TesselationWrapper 2. Start moving python registration code inside classes themselves. That should allow proper class hierarchy in python as well as (faster) object.attribute access instead of hacky object['attribute']. Please report errors you might encounter. 3. Resurrect Dem6DofGeom, doesn't work for now. 1. Add new-style python wrapper to most classes that were previously wrapped in yadeWrapper.cpp. Let me know if this breaks something for you. 1. Add YADE_CLASS_BASE_DOC_ATTRS and YADE_CLASS_BASE_DOC_ATTRS_PY macros for python wrapping, including documentation (e.g. https://yade-dem.org/sphinx/yade.wrapper.html#yade.wrapper.Body). All classes should be converted in the future. 2. Remove global functions (diagDiv, componentMinVector, ...) from the wm3 wrapper 1. Add new python wrapper to a few more classes. 2. Make python's executable have real python path in the #! line at the beginning (that is, the same python interpreter used for running scons) 1. Fix body_id_t typedef (add includes) 2. Check body id when accessing ForceContainer from python 3. Override prefix in config.py already if YADE_PREFIX is set 1. Fix python docstring options for boost <=1.35 (hopefully) 1. Classes now warn at startup if they don't register &document attributes with YADE_CLASS_BASE_DOC_ATTRS & friends. 2. Add YADE_CLASS_BASE_DOC_ATTRDECL_CTOR_PY macro that declares, documents and initializes data members. 3. Add NaN to lib/base/yadeWm3Extra.hpp, which is std::numeric_limits::signalingNaN() 4. Fix syntax in a few python modules, away from the old obj['attr'] to obj.attr 1. Add detection & mention workaround for https://bugs.launchpad.net/ubuntu/+source/boost1.38/+bug/457688 1. Convert a few more classes to YADE_CLASS_BASE_DOC_ATTRS 1. Re-disable CInem{CNC,KNC}Engines (sorry, Jerome) 2. don't compile cxx main anymore; delete the executable from installation if present 3. Add help for --threads equali with OMP_NUM_THREADS 1. Moved all (most) unused files to attic/* (such as those that have YADE_REQUIRE_FEATURE(PHYSPAR);). We will not keep them there indefinitely (they are in the history), though. If you want to re-enable some classes, fix them and move them back (using bzr mv) to pkg/*. If I inconsciously moved some file that was being used, please fix it yourself (it is unlikely, though). Get rid of physpas a geometricalshape relicts in non-attic code. 1. Fix class registration macro without attributes (remove "cap" in Rpm LawFunctor as well) 2. Remove StretchPeriodicEngine, it was probably too complicated to be really used; remove yade.plot data reduction which was relying on StretchPeriodicEngine 3. Properly expose PeriodicEngine, PythonPeriodicRunner, VTKRecorder, FileGenerator to python 1. Fix python registration of CohesiveStateRPMRecorder (YADE_PLUGIN was missing for PeriodicEngine and Recorder, it seems). Thanks Anton. 1. Wrap many classes using YADE_CLASS_BASE_DOC_ATTRDECL_CTOR_PY and friends. 2. "Fix" problems with features=all (is rejected now, features must be written by hand) 3. Add |ystatic| sphinx role (for static data memebers) 4. Rename GLDrawCpmPhys to Gl1_CpmPhys 1. Wrap yet more classes with YADE_CLASS_BASE_DOC_ATTRS and friends. I strongly ask people to do the same for "their" classes (like Bruno for TriaxialTest and so on); I will not and it might make those classes unfunctional in the future. 2. Add documentation here and there, where it was lacking 1. Remove wm3 includes from everywhere, use lib/base/Math.hpp instead (in preparation to the eigen switch). Regression tests passed. 1. Remove a few forgotten files. 1. Fix empty display (improperly initialized drawMask) 1. Catch io exceptions in the gui 2. Detect io error and throw in IOFormatManager 3. Set config dir at startup, enabling use of logging.conf etc 4. Fix display issues we had. When not running script/xml from command line, yade doesn't display the controller (press F12 to have it). Let me know if this is OK or not. 5. Fix OpenGL initialization code. 1. Add bin with 0 interactions to _utils.bodyNumInteractionsHistogram, hereby fixing utils.avgNumInteractions as well (thanks, Luc) 1. Update comments in scripts/simple-scene-plot.py (thanks, Chiara) Document registration macros (will move to Programmer's manual later) (Add 2 more hyperlinks to ElasticContactLaw, once it serves as example for docs) 1. Remove some garbage 2. Make code partially compile with the wm3-eigen glue. (more fixes for wm3-eigen compat) 1. Fix compilation for per-class builds 2. Add manual wrapper for wm3 classes instead of the auto-generated one, to ease transition to eigen. Let me know if it broke something. Fix compilation error introduced in last commit; sorry. Fix setting # of OpenMP threads via -j switch 1. do not build miniWm3 at all if not needed 1. Document a few classes. Register TimeStepper with YADE_PLUGIN as it should be (that's the reason it wasn't reported as not being registered with YADE_BASE_DOC_* -- thanks Bruno for pointing that out) 1. Add #ifndef YADE_* feature handling to implicit build scanner (till now, it was only supporting #ifdef YADE_*) rename sphere{From,To}File to {ymport,export}.text for consistency. 1. Add scene->forces.sync() to several places Fix typo (Scene::force vs. Scene::forces). Sorry. Thanks to Chiara for reporting. 1. Add no-alignment macros to compiler command line directly, this fixes compilation with eigen, including the tt.py test and scripts/regression-testss.py 1. Fix plugin registration for CinemCNCEngine, remove some cruft, fix formatting, add hyperlinks. 1. Fix quaternion/vector initializers in NormalInelasticityPhys 2. Fix some eigen related macros 1. Add eudoxos.InteractionLocator and eudoxos.IntrSmooth3 for 3d smoothing of arbitrary quantities defined on interactions. 2. Adjust some docstring formatting 1. Doc fixes 2. Change prototype for Funtor::getFunctor, since it doesn't seem to work from python now (perhaps it's that Functors don't inherit from Functor in python universe, actually?!) 3. Fix some stuff with class indices for GenericSpheresContact. (It doesn't seem to help, though, either) 1. Fix instruction set (march) for debian packages 2. chunkSize=∞ if <= 0 1. Add docs at several places 2. Update debian/rules to run tests 3. Add funnel.py scripts/test/CundallStrackTest.py 4. Add more formats to SnapshotEngine (don't seem to work, though) 1. Add IntrCallback and BodyCallback (not yet really documented), for hooking into interaction loop in InteractionDispatchers and body loop in NewtonIntegrator (suggested by Bruno). Two real callbacks are defined, to compute summary (hence also mean) forces on interactions and bodies. Sample script is scripts/test/callbacks.py, but currently it crashes for reasons beyond my comprehension. 2. Remove Scene* ncb and similar stuff from everywhere, since all Engines have scene declared in the base Engine class and don't need it pass as arguments. Later, the prototype will be changed (Engine::action(void) etc). 3. Add docs here and there. 1. Eninge::action and Engine::isActivated no longer takes Scene* pointer. It must be set beforehand by the caller (done e.g. in moveToNextTimeStep). Please check for suspicious crashes. 2. Remove PartialEngine::applyCondition, replace by action just like other engines (https://blueprints.launchpad.net/yade/+spec/remove-partial-engine-apply-condition) 3. Resurrect JumpChangeSe3 engine 4. add py/_extraDocs.py for documentation of c++ classes/function written conveniently in python strings rather than c++ string literals. 5. Fix bug in Dem3DofGeom_SphereSphere, introduced when avoiding normalization of (0,0,0) vector; is OK now. 1. devirtualize BodyContainer and InteractionContainer (they use implementation from BodyVector and InteractionVecMap). There is some speedup. I ask people (you) to run some simulation with the older and current version and report what they observe on the ML. I had more than 2x faster in some cases (in debug mode, though). 2. Fix chunkSize=-1 linkage (is like setting it to infinity) 1. Set docstring format for yade.qt 1. Code cleanups, docs 2. Make sure we are able to compile with boost::serialization (loading/saving works now as well, but no compression yet; about 3x faster for saving and even more for loading) 3. Enable boost-serialization feature for debian builds 1. Don't create python proxy classes for those not using YADE_CLASS_BASE_DOC_* 2. Remove few deprecated things like O.bex and such. 3. Warn about object['attribute'] syntax 4. Add yade --update option, which will do in-place replace for deprecated class names. Run on scripts/test/*.py 5. Wrap ResetRandomPosition and PositionOrientationRecorder 6. Remove some olden scripts 1. Move SpherePadder from extra to py/pack/SpherePadder 2. Add py/pack directory, to avoid too many files in py 3. Other small fixes 1. Fix crasher in Ip2_FrictMat_FrictMat_FrictPhys (??) 2. Cleanup python code 3. Add feature gl2ps, hitting 'v' in the GL view will save PDF (seems to freeze on large scenes, but works nice on small ones) 4. Add special syntax for deprecated attributes. For testing, renamed Shape::diffuseColor to Shape::color. 1. Fix gl2ps, works even for larger scenes now. 1. Rename many attributes in OpenGLRenderingEngine, using the deprecated attribute framework 2. Add pickling support to Serializables (useless) 3. Fixes of docstrings here and there 1. Disable deprecated attributes handling for gcc<=4.2, which gives errors on that code. 1. Use BSP sort in gl2ps only if len(O.bodies)<100 2. Remove ClumpMemberMover, no longer used anywhere 3. Fix pack.regularHexa bug, reported by Nasibeh http://www.mail-archive.com/yade-users@lists.launchpad.net/msg01424.html 1. Uniformize accumulator interface for open and openmp-less builds. 1. Fix compilation of deprecated attributes for gcc 4.3 (thanks Jerome for reporting) 2. Re-add facet rendering code. 3. Add docs to predicates. 1. TriaixalStressController: originally compilation fix, but Luc was faster; Bruno, I tried to replace on c-array with boost::array; if that works, you could use it for other c-arrays as well 2. Add yield surface query function from python to Law2_..._Cpm 3. Make plot support label translation via yade.plot.labels 4. make chunkSize very small for debian; add strip for the optimized build 1. Fix action(Scene*) vs. action() virtuals (mass replace in attic as well), in TranslationEngine. Thanks to Nasibeh Moradi for reporting. 1. Add doc/references.bib, which can be references from docstring using [Author2008]_ syntax (note the underscore). 2. Added a few bibliography items to test, and changed relevant docstrings -- see e.g. https://www.yade-dem.org/sphinx/yade.wrapper.html#yade.wrapper.NewtonIntegrator.damping Add doc to SimpleViscoelasticPhys, add Jean-Francois' paper to both bibtex and SpherePadder's docs 1. add axis parameter to utils.uniaxialTestFeatures, to force strain direction 1. Import yade.plot when showing graph 2. Fix bibtex entry 3. Handle unseen interaction in InteractionDispatchers differently than with requestErase, as that will not work with certain colliders 4. Add from yade import utils to utils.py docstrings, and show full utils.sphere instead of sphere in session transcripts (thanks to Jerome for pointing that out) 1. Add toy grid-based collider (very slow) 1. Rename viscoelastic classes as discussed on the mailing list 2. FIx some docstrings. Fix weird crash http://www.mail-archive.com/yade-users@lists.launchpad.net/msg01482.html in glGetError() (disable error check) 1. Reorganize engines a little bit, as discussed in http://www.mail-archive.com/yade-dev@lists.launchpad.net/msg03337.html and other messages in the thread (trivial to change, though) 2. Small fixes at different places 1. Fix https://bugs.launchpad.net/yade/+bug/539562 (thanks, Luc) 2. Fix import warning in yade.remote Fix monolithic build (#pragma once for BoundaryController and FieldApplier), sorry 1. Add importing native SpherePadder meshes 2. Fix LSMGenGeo interaction with our own type Sphere (in yade itself and in SpherePadder (HACK!!) 3. Fix sample scripts for GenGeo and SpherePadder 4. Allow both gengeo and GenGeo imports 5. Fix ymport.gengeo (shift vs. moveto) 1. Start filling NEWS for upcoming 0.5: do the same if you have some big things in mind 2. Fix hyperlinks in Gl1_Sphere doc 1. Make python return Vector2r, Vector2r, Vector3i, Vector3r, Quaternionr and Matrix3r by reference rather than by value; fixes http://www.mail-archive.com/yade-dev@lists.launchpad.net/msg03406.html and was introduced by fixing https://bugs.launchpad.net/yade/+bug/539562. See explanation in Serializable.hpp. 1. Replace the ugliest code I've ever written by something more sensible. Still ashamed, though, please forgive me. 1. Completely remove HydraulicForceEngine which didn't exist in sources (removed by Bruno in previous commit) 2. Experimental binary io using boost::serialization 3. Some function in yade.eudoxos 1. Some preliminary work to make boost:;serialization work (MUCH faster) 1. Fix calling {pre,post}ProcessAttribute multiple times with boost::serialization. Fix unchanged name (Bruno, do you use scripts/rename-class.py, right? https://www.yade-dem.org/sphinx/prog.html#renaming-class) 1. Skip yade::ObjectIO for boost::serialization-less builds. (thanks Nasibeh for reporting) Fix Jerome's problem with boost 1.35 (don't compile code uselessly) 1. Fix cached values for cell and facet when modified from python 2. Fix table parsing for lines with 1 character onlyDynamic 1. Clarify error message i Law2 functor is not found (thanks, Chiara) 2. Make static attributes initialized by values provided in YADE_CLASS_BASE_DOC_STATICATTRS 3. documentation fixes here and there 1. Fix yade.plot broken in last commit 2. adjust interpolating-force example script 1. Adjust interpolating-force.py script; present for Chia 1. Fix boost::serialization and postProcessAttributes 2. Add scripts/test/serialization-benchmark.py for comparing serialization time/size (http://www.mail-archive.com/yade-dev@lists.launchpad.net/msg03496.html) 3. Fix bug #557124 (hopefully) 4. Don't call postProcessAttributes on Facet in utils.facet, as that is called automatically when setting vertices now. Some eigen compatibility stuff Some more replaces After some text replaces (IDENTITY etc); removed MarchingCube 1. Replace things for eigen compatibility. See https://www.yade-dem.org/wiki/Wm3%E2%86%92Eigen for details. Code should not be changed functionally. Report if it is. 2. Remove PositionOrientationRecorder from everywhere. 3. Make the timestep entry behave differently: only when enter is pressed, it will change timestep. Fixes bug #394687 1. Forgotten fixes. REALLY fix bad end iterator on InteractionContainer (since r2092!!). UPDATE your code, this makes simulations little wrong (some interaction might be processed twice in one step). (Remove unnecessary aseerts in InteractionContainer.) 1. Remove some ToAxisAngle and FromAxisAngle as per https://www.yade-dem.org/wiki/Wm3%E2%86%92Eigen 2. Fix NewtonIntegrator using [i] on quaternions. 2. Add docs for VTKRecorder 1. Make controller a little more compact 2. JumpChangeSe3->StepDisplacer (does it sound ok, Chia?), MomentEngine->TorqueEngine 3. Fix a few docs 4. Disable building of CapillaryPressureEngine, it cannot work. 1. Change Wm3 interface so that it is eigen-compatible. Please let me know if there are regressions. 2. Fix body selection with python when no onBodySelect callback is defined 1. Some garbage removal 2. Fix assertion in NewtonIntegrator 1. Fixes for Eigen compatibility (compile with features eigen,nowm3) 1. Add forgotten file 1. Add docs for kineticEnergy 2. Complete wrapper for Eigen in Python (will be renamed from miniWm3Wrap to yade.math or similar later), passes scripts/regression-test.py now 1. Rename miniWm3Wrap module to miniEigen. Let me know in case of regressions (shouldn't be, unless you imported that module directly) 2. Add documentation to the eigen wrapper, document operators (https://www.yade-dem.org/sphinx/external.html#module-miniEigen) 3. Add custom converters from sequences for all supported vector types 1. Fix bug with facet import Luc reported (thanks) 2. Add None to separate y1 and y2 axis (instead of |||, which will still work, but is not documented anymore) 3. Add the possibility to plot multiple x axes with the save variable by adding spaces to the name in yade.plots 4. Rename TestWm3Wrapper to TestEigenWrapper 1. Add backward-compat to Cpm model if from external files 2. Complete the eigen wrapper 3. Fix some docstrings. Fix inplace operators. 1. Add Pournin article to references.bib 2. Fix some docstrings (still not clear) Fix DOI for Luc's article 1. Make yade compile with eigen by default. Use 'wm3' feature to compile with Wm3 instead (beware, you still need eigen installed) 2. Disable the possibility of ocmpiling with external wm3. 3. Add Identity, Ones, Zero, Unit{X,Y,Z} to old wm3 wrapper so that they can be used in python. 1. Fix O.tags with keys with spaces (historical relics) 1. Add forgotten ctor with kwargs to classes with static attrs (such as GL functors) 2. Update scripts/test/Dem3DofGeom.py 1. Fix facet wall vertices addition in TriaxialTest. 1. Fix O.tags tag update. Thanks to Anton for spotting the problem. 1. Change O.dt semantics (negative value activates TimeStepper) 2. Rename O.usesTimeStepper to O.dynDt 3. Remove several Omega wrapper functions that really pertain to Scene, adapt code accordingly. 4. Remove bunch of useless files from attic (mostly seem to have equivalents in our tree, or are obsolete WRT code structure) 1. Change O.dt as per http://www.mail-archive.com/yade-users@lists.launchpad.net/msg01767.html + update docs 1. Remove some old garbage code 2. Make Material::id read-only from python 1. Forgotten Scene #include in StepDisplacer, sorry. 1. Partial fix for clump eigen decomposition 1. Fix apparent clumps problems, spheres in the dogbone were getting through. 1. Rename groupMask to mask, change its default value to match that of Body::Body ctor (Bodies with 0 mask would not collide!!!!!!!!!!!!) and add to to waoll and box as well (please keep the interface the same for all 4: sphere, facet, wall, box). 1. Remove old sphinx docs before adding new ones. 1. Merge sphinx docs from https://launchpad.net/~eudoxos/+junk/ydoc/ 1. remove sdecGroupMask from functors (handled by the collider already) -- thanks to Chiara for pointing it out. Fix compilation error (Bruno, you must have been terribly tired today) 1. add explicit qt dependencies. 1. Fix scene pointer in CohesiveFrictionalContactLaw 2. add doc to utils.avgNumContacts. 1. Fix timestepper selection 2. Disable debuggin in Eigen (for higher performance with debug builds) 1. compile with package QGLViewer, if installed (package libqglviewer-qt3-dev) 1. Add Peri3dController, done by Jan and me during a rainy day 2. Implement memoizeDb for pack.randomPeriPack 3. add polar decomposition to Matrix3 in python 4. make the 3d view update even if there are no bodies 1. Fix bibliography path in generated docs 2. Fix bug #582679 hopefully (compile error with clean builds) 3. Add import gts to gts related function in yade.pack, to make the error message more informative 4. Rewrite scripts/debian-prep in python instead of shell 5. Add qglviewer to some versions in debian/control-template 1. Remove pause time from omega, avoids negative realtime 2. Fix some scripts in scripts/test 1. Avoid recompilation of the UI when already compiled 1. Tentative fix for the clumps bug 1. Re-enable PeriIsoCompressor and PeriTriaxController 1. Update docs for Peri3dController 2. Fix compiler warning 1. Adjust debian-related packaging files 2. Remove NullGUI for good 3. Remove non-DISPATCH_CACHE code (not used) 1. Fix a non-critical typo in the script 1. packaging fixes 1. Remove attic (use bzr co lp:yade -r2280 to get it) 2. Remove QGLViewer 3. Remove miniWm3 1. Remove wm3 sources 2. Remove #ifdefs pertaining to Wm3 1. make Body::isDynamic a method (and Body::setDynamic to change it), in preparation towards making dynamic equivalent to State::blockDOFs==State::DOF_ALL 2. make YADE_BOOST_SERIALIZATION and YADE_SERIALIZE_USING_BOOST #defined in all builds (will be removed later once the switch is complete) 1. Replace wm3-compatibility functions (component{Min,Max}Vector, angleAxisFromQuat, diag{Mult,Div}) with their eigen equivalents. 1. Fix stl import with different material ================================================== yade-0.20.2 Sat, Sep 19 21:41:52 2009 +0200 Anton Gladky (34): 1. This is my first test commit 2. Added some initial files for mining rocks classes simulation 1. Some changes in RockPM, still does not work good 1. Some small changes on RockPM.* files 2. Test SVN_2_email 1. Test 2. RockPM.* updated Changes on RockPM.* files 1. RockPM, first variant of "destruction mechanism" 1. Deleted euclid.py 1. delete euclid.py (fised Sconscript) 1. In utils.py alignedFacetBox has been changed to facetBox. It is now possible to create arbitrarily-aligned box composed of facets 2. Updated regular-sphere-pack.py 3. Some small changes on RockPM.cpp 1. RockPM update. Added simple "destruction mechanism" 2. Added scripts/installOnUbuntu.sh to make "oneButton-checkout+compile+install+doxygen+epydoc" script. Not tested yet. Requires corrections. 1. Updated some scripts according to new syntax 1. Added PressTestEngine for simulating presses. Can be used and for PointLoadTest simulations. 1. Updated simple-scene-graph.py according to a new syntax 2. Updated simple-scene-video.py according to a new syntax 1. Changes on PressTestEngine. Now it defines the destruction moment more precisely 2. YADE CONSOLE emblem shows now without upper row shifted. 1. Some small changes 1. All variables initialize in PressTestEngine and RockPm Some changes on News Fix compile error 1. Partly fixed 1. Almost fixed https://bugs.launchpad.net/yade/+bug/412892 Just 1 warning: Some changes in INSTALL file because of migrating to BZR Some changes on one-click-install script Added "warning" in one-click-install script 1. Fixes compiling error without openmp feature. 1. 418107, I guess it is more correctly. Some nonessential changes Blocked some warnings in RockPM.cpp Small changes Press Engine and RockPM changes Added demonstration of buldozer simulation "Thanks" and Thanks to Janek :) Fixes VTK-librabry dependencies for Ubuntu Some small changes on Buldozer 0.20.2 Bruno Chareyre (22): A wrong comment on contact laws in PFC3D is removed. svn update - no real change (in principle) - recovering from erased local file - Fix conflicts and prepare next implementation of isotropic compression - Few files missing in the previous commit. - Add a new class for computing/recording micromechanical data based on tesselation Still recovering from conflicted files... I hope this is the last one. A class with algorithm for analysing contacts-forces-displacements statistics. Using the triangulation lib. 1. Fixed a critical bug in ElasticContactInteraction (kn, was not registered and left undefined after loading a simulation). 2. Test if fn!=0 (i.e. contact exists) before adding kn/ks to the global stiffness. 1. "Water" classes are updated based on the code developped locally (but not commited for a while) by Luc Scholtes. 2. Triangulation code updated as well (not compiled by default). 1. Add a missing class (for the capillarity model), this fixes a compilation error in the previous commit due to the missing files. The "stopSimulation" command in the compression engine is now optional, as sometimes you don't want it to stop at all(1). The default behaviour of the compressionEngine is not affected, with autoStopSimulation=true by default. However, the TriaxialTest IS affected : it sets autoStopSimulation=false by default. 1. Update capillary files and add a new engine for them. 2. Prepare for removing old useless classes (typically some variants of engines with "water" at the end) Remove useless dependency. -Avoid crash when computing fusion number (it was using pointers to deleted interractions) FIXME : no distant meniscii for now due to deletion by the contact law. Base class for the solid-fluid coupling (E. Catalano PhD). -Register "thickness" so that sample dimensions are computed correctly (and not with 0 thickness) Dimensions could be 0 when "updateParameters" was called from TriaxialCompressionEngine before TTController::action Missing declarations in r1836. - cleanup : removing unused (and useless) "water" variants of engines - some cleanup in recorders too Update of the read/write functions for the connection with comsol. (coupling via Berkeley<->Grenoble mail servers! ;)) Missing declarations of member variables. Set default read/write behaviour as in Andrea's version. Feng Chen (1): This is viscous force damping, equation similar to http://en.wikipedia.org/wiki/Damping, damping parameters are controlled by betaNormal and betaShear, it is sometimes more realistic than the non-viscous (or say, local) damping. While using this code, please make a reference to: Janek Kozicki (54): ouch, forgot to mark INSTALL file as outdated. We need to update it. some cleanup in the files fix crashes of this->parentWidget()->parentWidget() in UI yade.cpp: more detailed info about compilation flags. LatticeExample: added material parameters bond zone between steel fibres and cement matrix update to use latest scons 1.0.0 don't refresh display when there's nothing to refresh support for generating multiple file with single NullGUI call add to SimulationController buttons for looking in different directions. The default view direction is now down along the direction of gravity. improved generation of steel fibres in lattice fixed snaphotNames in NullGUI for .xml.gz more configurable MakeItFlat, TriaxialTest can be now a biaxial test as well. small fixes in UIs make it compile with boost 1.35 (I wonder what 1.36 will bring) Fixed OpenGL problems with displaying spheres, which - never occurred on my desktop - I've seen them sometimes on other peoples computers, but never could reproduce them - but finally have appeared on my laptop. up direction on Y release 0.12.1 created nearly empty snow package: pkg-snow Snow stuff - generate grains made of layers according to voxel data read from file. And daraw it. forgot some files - make it compile with boost 1.34 - some snow improvements - another fix. now it really compiles with boost 1.34 1. TriaxialStressController reads radius from interactingGeometry Code wasn't compiling because of #include MicroMacroAnalyser - comment it out. 1. argh, guys. svn HEAD must compile. You didn't check your commits and then I waste 1h to fix that. See this commit, to check what I had to comment out about WATER - added keyboard shortcut '.' that toggles grid subdivision by 10 - fixed error with grid step - grid step is now shown in gl window small snow update another small snow update 1. small snow update 2. fix compilation error by Vaclav (hey, you didn't compile again before commit! ;p ) rename file to avoid case conflict with directory 1. GLViewer - when sth. is selected don't move it to 0,0,0 position, but use its original position. This is done by delaying assignment to the next call of draw() - added extensive comments to the polyhedron code which will be used for collisions small snow update maybe spheres won't disappear anymore? a placeholder for drawing snow interactions changed guards to #pragma once postpone creating a 3d view, until a file is loaded. That should hopefully fix the problem with the primary view being empty. added AUTHORS file, for the purpose of updating file headers. This was copied from http://yade.wikia.com/wiki/Authors Why do I have the impression that I'm adding this file a second time? After all it's referred to in almost all source files "See file LICENSE for details.", right? sort people in alphabetical order sorry for bumping up revisions.... Snow, as it is finished by me. Further work on it will to be done by next post-doc researcher who wants money from EU for working on this :) fix miniwm3 / full wm3 compilation error I forgot to add those, sorry. (I wonder how it could stay unnoticed for so long ;) add option for horizontal or vertical wires in fibre reinforced concrete. 1. correct display of lattice rods on 0th iteration 2. on/off switch for using different stiffness for tension and compression in lattice 3. fixed a bug in BodyRedirectionVector related to deleting of bodies 4. two more lattice examples 5. few more safety checks in lattice code 1. NullGUI was still usin .gz while thay are not supported anymore 2. small lattice update sanitize stupid numers in QtGUI, eg. 0.1299999999999 becomes 0.13 small fix after isNew and isReal are removed I'll update the code to use new contact logic. Add an option to display DOFs and body IDs in the OpenGL window - Lattice uses DOFs now - old implementation is still accessible by setting a flag backward_compatible, so that hundreds of my old FileGenerator files will still work - some improvements to recording strains and forces - fix all my warnings - lattice generator improvements wrt concrete with steel fibres - some improvements wrt to generation of densely packed aggregates and steel fibres in the concrete structure - testing command "bzr commit --fixes lp:414868" - fixing bug 414868 Fix stupid error introduced when we have migrated to bex container 1. get snow code to run, albeit still buggy 2. why dynamic_casts stopped working everywhere, that's a mystery to me 3. interactions isNew doesn't exist anymore, snow code needs update for this Jerome Duriez (9): Added : - Engines for simulating shear tests at constant normal displacement, normal stress or normal rigidity (CinemDNCEngine, CinemCNCEngine and CinemKNCEngine) for samples deriving from SimpleShear PreProcessor (added also, former DirectShearCis) - ContactLaw1 : a contact law with Moment Transfer and inelastic behaviour in compression : see comments. With correspondant Relationships and type of Interactions. NB : sorry for the name : i didn't search too long for a more explicit one but I think personly that the comments in the files (and I tried to put some) are far much better than the one - Add of the paragraph for the CL1Relationships, which was forgotten, in the corresponding SConscript A (last ?) error concerning last revision in the paragraph CL1Relationships : "CL1Interaction" does not exist, that's ContactLaw1Interaction -ContactLaw1 : correction of prototype of action() : it needed a body, whereas now action methods of Engines need a Metabody. This led the simulation to crash and is now fixed - SimpleShear : now all is normaly done so that this preprocessor could be used (with success) with this ContactLaw. Moreover it is now linked to the GlobalStifnessTimeStepper (instead of the ElasticOne) Suppress of some useful comments (sorry for the extra mail) (At last) The good Sconscript file - The GlobalStifnessCounter engine (for correct use of GlobalStifnessTimeStepper) was forgotten in the preprocessor SimpleShear... - Correction of the headers of two files "ContactLaw1Interaction" which were commited by me and not by Bruno - Some features of ContactLaw1Interaction were declared in ElasticContactInteraction. This was a mistake totally not planned (and the fact it was in svn also was not noticed) and is now corrected - End of the job by suppressing also the corresponding lines in .cpp... Luc Sibille (1): I added a new engine "ThreeDTriaxialEngine" to perform triaxial simulations with an independant control in stress or in strain in the three space directions respectively. In this goal, the TriaxialStressControlEngine has been sligtly modified to impose a different stress value or strainrate in each space direction. I hope I have broken nothing! Sergei Dorofeenko (32): 1. A new algorithm for facets. It use only a class Facet, so classes Vertex and Edge are deprecated and has been removed. 2. Add functions to python gui for import geometry from stl and for creating facets. 3. Add Shop::inscribedCircleCenter for calculate position of center of triangle inscribed circle (use it for set facet's se3.position). 4. ForceRecorder now take cary about old data files and renaming them. 5. SimulationPlayer now allow wire body form view tab. 1. ResetPositionEngine is now inherited from PeriodicEngine. 2. Add examples/rod_penetration model and geometry. small fixes 1. New random-access interaction container InteractionVecMap (rewrited from InteractionVecSet) 2. Fixes: default initialization some attributes in SCG and IS2IS4SCG Intermediate commit for SpheresFactory. Don't work yet. 1. New engine SpheresFactory 2. Add function engineByLabel to MetaBody 3. Add members to BroadInteractor. 1.First worked version of SpheresFactory engine. Example in examples/SpheresFactory 2.Added PersistentSAPCollider::probeBoundingVolume function (for using with SpheresFactory) 3.InteractionGeometryMetaEngine::explicitAction has been modified to remove artifical (as me seems) asserts. 4.python import_stl_geometry now return list of body ids instead only their number. 5.import stl geometry now allows import facets without BoundingVolume and InteractingGeometry. 6.Fixed dynamic_cast for BroadInteractor. 1. SpheresFactory allows now to create spheres in a convex volume. 2. SimulationController estimates total simulation time if stopAtIteration>0. 1. SimulationPlayer now allows to reload filters without reloading all simulation. 2. ColorizedVelocityFilter now allows to setup minValue,maxValue without reloading the filter. Fixed bug in RotationEngine: rotationAxis now normalized in action(), not in postProcessAttributes() ColorizedVelocityFilter counts a scale for each frame, without using previous min,max values. Class SimpleViscoelasticInteraction is renamed to ViscoelasticInteraction and is inherited from ElasticContactInteraction now. 1. Convert SimpleViscoelasticContactLaw to ConstitutiveLaw engine unit (Spheres_Viscoelastic_SimpleViscoelasticContactLaw). 2. SimulatonPlayer have now Display tab as one in SimulationController. 1. The experimental widget for drawing a color scale for colorized velocity filter. 2. Convenient python function utils.ColorizedVelocityFilter. Merging utils.*_v2 functions with their "first" analogs. Add two new filters: ColorizedTimeFilter and PythonRunnerFilter Migrate GravityEngines and ConstitutiveLaw to BEX_CONTAINER Convert STLImporterTest to ConstitutiveLaw (SimpleViscoelastic contact model) and NewtonsDampedLaw engine. Fix bug in serialization for ViscoelasticInteraction. Fix compilation error for InteractionHashMap Update contact logic for SpatialQuickSortCollider (hope, it's correct) Fix typo in PersistentSAPCollider 1. Remove zeroPoint 2. Add prefix ef2_ to Spheres_Viscoelastic_SimpleViscoelasticContactLaw Fix link errors fixed and updated some examples in keeping with last modifications Realise InsertionSortCollider::probeBoundingVolume() so SpheresFactory now works SpheresFactory now can create a spheres by python function. See scripts/test/SpheresFactory/model.py for example. Add export python module with VTKWriter class (very initial) to export data from YADE to VTK-based post-processing tools Added a c++ VTKRecorder (for spheres and facets) and new feature 'vtk' needed for it. Add colors rec to VTKRecorder Add default path for VTK Fix VTKRecorder headers Fix VTKRecorder headers Add VTKINCDIR to configure options for directories where to look for VTK headers Unknown (3): I added a new state at the triaxial compression test in order to realize isotropic compression by the walls displacement until a indicated porosity value. These modifications can be seen with generator file "TriaxialCompression" in the last three parameters (added). I corrected my errors on the triaxialtest files.Moreover, i added comments in the TriaxialCompressionEngine.hpp. The isotropic compaction into the TraxialTest is activated: 1)I have deleted the translationspeed parameter (now the translation speed of walls is the strainRate parameter) 2)I have activated the porosity parameter in the TriaxailStressController.cpp file Vincent Richefeu (42): - initialize radius in InteractingSphere constructor. - add new plugins used for membrane modelling. - add GLDraw plugin for BssSweptSphereLineSegment displaying - 'Membrane' (in fact geogrid) has been improved but still doesn't work... - the class GroupRelationData has been add (not used yet) to define interacting parameters such as coefficient of friction or restitution that have nothing to do in the bodies parameters (in my opinion) - forgotten files - add the soft mgpost for DEM simulation post-processing and visualization. - set menus in english mode A very quick guide for mgpost (usage and XML format) a forgotten file in mgpost/doc - add a new tool for sphere packing (still in dev) - some enhancement in mgpost interface - SpherePadder devel... SpherePadder devel... SpherePadder (devel: 5 steps ok) - SpherePadder update - add a basic 'user friend' interface for sphere packing generation. The module can of course be used without this interface. Speed enhacement for the SpherePadder algorithm. Add possibility to make PNG screenshots (using libpng) Add a DataRecoder that generate MGP files (only for Spheres for the moment). The MGP files can then be read with mgpost. - add a class based on CGAL for 3D triangulation. - Packing method enhanced (place 380000 spheres in less than 2 min) Fix some bugs and enhance the display of positive and negative forces Fix some bugs Begin devel. of densification process 2 files that were forgotten Simplify sphere shapes when if there number is increased Bug corrections (min and max radii) - overlap with spheres inserted by user -> bug fixed - Densification begins to work Increase the number of colors for the display of body's groupMasks from 6 to 10 We can now make the packing denser with a stop criterion based on the solid fraction or total number of spheres. Add BasicViscoelasticRelationships: an alternative to SimpleViscoelasticRelationships. It accounts for the effective mass of each contact and considere the parameter cn as a dimensionless ponderation of its critical value (computed for each contact). Parameter cs is set to zero for the moment. Add a boost.python wrapper (module packing) Add a function to SpherePadder_wrapper to pass the result back to python. As Vaclav suggested, it is a list of (x,y,z,R) tuples. (Complet integration into yade is not done yet) - Add 'HistoryRecorder' to save the sample and network state with a given time-step interval. - Correct a small bug due to the fact that a Clump cannot be casted into a GeometricalModel (and I don't know why!) Use the clump mass instead of clump-member mass for the determination of the effective mass. Ooops! There was a bug Add the function NormalRestitution2DampingRate. Add sphere reading for gdm-tk format Remove default linkage with libtiff (png is now prefered) Add an example of python script for SpherePadder Add GroupRelationData serialization (not used yet in *Relationships classes) This class serves to manage parameters between bodies depending on their groupMasks. Here is an example of 'command stream' in xml file to set the parameters: Just try to use GroupRelationData with BasicViscolelasticRelationships. Cast size_t to int in pow(double,int) function to avoid ambiguity for gcc (compilation failled on one of my computer) Some tiny modifications for Mac OS X compatibility (Does not compil yet). Some forgotten tasks replace gcc-3.3 by gcc in the Makefile.macosx of mgpost Václav Šmilauer (313): Move big news to NEWS, ready for release finally (?) Update INSTALL file from wiki (brief instructions only). 1. Reimplement some loop-intensive functions from yade.utils in c++ for better performance (as yade._utils, imported into yade.utils automatically - transparent to the user) 2. Add poisson's ratio and Young's modulus estimation in yade.eudoxos based on linear regressions of position→displacement mapping along different axes 3. Rename Shop::ElasticWaveTimestepEstimate to Shop::PWaveTimeStep, wrapped in yade.utils 1. Add python interface for blocking DOFs: b.phys.blockedDOFs=['z','rx','ry'] It is friendlier than b.phys['blockedDOFs']=1<<2|1<<3|1<<4. The property is read-write. 1. Add code for computing histogram of interaction directions in given plane in _utils. 2. This is code is used by yade.eudoxos.plotDirections() to create the actual (nice) figure. 1. Skip (almost) exactly zero projections, implement masking of bodies. 1. poisson and young estimator now works on a fractional part of the speciment 2. do not switch ui automatically if selected explcitly 3. body::maskOK to check mask 1. eudoxos module cleanups, young estimator arg chages 2. fix harmless compiler warning in _utils.cpp 1. add utility function to compute elastic energy within a volume (the dynamic_cast to NormalShearInteraction still fails; what is going on?) 2. add utils.fractionalBox, an AABB reduced to its fraction 3. fix fixmes in Force and Momentum about unused functions about to be deleted 4. plotting interaction direction histogram now works on contrained volume (to study boundary influence on the distribution) 1. Beginning hijacking SpheresContactGeometry to hold more elaborate geometry characteristics, such as relative shear displacement. By default, those parameters are neither calculated nor displated (exactRot==False; must be set to True in ISphere2ISphere4SpheresContactGeometry to enable it). Test script is in scripts/exact-rot.py 2. Remove deprecated code from UniaxialStrainController 3. Add GLUtils to draw lines, text, numbers -- copid over from Shop 4. Add operator<< for Quatrnions (axis angle) 5. Fix energy calculation in volume (in _utils.cpp) 6. Other fixes here and there... 1. The exactRot code is reasonably verified now to be functional, including rolling correction (SpheresContactGeometry::relocateContactPoints) and will used in brefcom soon. 2. Updated the "testing" script scripts/exact-rot.py, as a showcase as well. 1. Remove GL things from Shop, moved to lib/opengl/GLUtils.hpp 2. Cleanup of SpheresContactGeometry code, rename exactRot to hasShear (I thought most of this was already commited?!) 1. Delete unused IS2IS4DistantSpheresContactGeometry (merged with IS2IS4SCG), adapt existing code 2. Adapt new facet code to updated SpheresContactGeometry (only superficially tested) so that they can interact using BrefcomLaw; add scripts/exact-rot-facet.py to show that. 3. Fix a few missing loggers in others' commits. 4. SimulationController redraws if iteration changes (the +1 button would otherwise not cause redraw) 5. Remove cruft from Brefcom. 6. Fix inheritance (dynamic casts) of NormalShearInteraction and of BrefcomPhysParams 7. Remove cruft from UniaxialStrainer 8. Add some python code to compute stress from stored elastic energy. 1. Move interaction direction distribution pie histograms to utils 2. Add plotting histogram of number of contacts to plotDirections() 1. Omega().bodies.append can take list of bodies now (instead of just one body), returning list of added id's 2. utils.facet can take both tuples and lists for vertex coordinates 1. Add NormalInteraction::normalForce and NormalShearInteraction::normalForce (moved up the hierarchy from ElasticContactInteraction) so that unbalancedForce can be calculated on any subclassed interaction type. 2. Adapt Brefcom for this. 3. Add Shop::unbalancedForce and utils.unbalancedForce 4. Add workaround for saving ipython history as suggested by http://lists.ipython.scipy.org/pipermail/ipython-user/2008-September/005839.html 5. Fix AxialGravityEngine to register parent class attributes. 1. Avoid compiler warning in optimized builds from pyOmega 2. Body containers now allocate the lowest possible ID for each added body. The old behavior was to allocate the first free ID greater than the ID of the body, if it was already set. Please let me know if this is critical for someone. We can do things like: 1. 3d view now has by default a OSD with iteration number, simulation time and virtual time. This can be toggled (all combinations) by pressing 'D' (which used to mean toggling d+ynamic flag...?) and set also from python with GLView.timeMask='rvi' (Real,Virtual,Iteration) and so on. 2. Simulation player displays real time and wall clock loaded from db (insted of bogus values of system clock when the player is being run) 3. new periodic SnapshotEngine that takes snapshots of any GL view to numbered PNG files. 4. new function qt.makeSimulationVideo that makes video _while running the simulation_. 5. qt.createVideo renamed to qt.makePlayerVideo 6. Added Omega method to query computation duration. 7. An example of using qt.makeSimulationVideo is in new file scripts/simple-scene-video.py 1. Add forgotten SnapshotEngine 2. BodyRedirectionVector always really find the lowest unused ID (the same for BodyAssocVector) 3. add Omega().interactions.clear() in python 4. Move the "Less than 500 bodies, moving possible" to LOG_INFO instead of on-screen since it disturbs videos taken during simulation... A few fixes in TriaxialTest: 1. radiusStdDev is a registered attribute parameter 2. skip unloading phase if sigmaLateralConfinement==sigmaIsoCompaction 3. new bool autoUnload, that controls automaticity of going from compaction to unloading; autocompressionActivation controls from unloading to loading. Note: IFacet2IS4SCG is broken, but not yet fixed. 1. TriaxialTest now takes readiusMean argument (by default negative, i.e. disabled); if >0, box size is scaled so that both requested porosity and mean size can be preserved. 2. Attempt to fix computation of stress from stored elastic energy. 1. Fix adding empty engine if record wall stress interval <= 0 in TriaxialTest (bug introduced yesterday) 1. Add Interaction::swapOrder(), for use in EngineUnits that want to avoid calling goReverse() 2. SnapshotEngine can sleep specified number of msecs after shot, which probably can avoid some hw problems (make_current failed in mesa, freezes etc) 3. InteractingFacet2IS4SCG now works "correctly" (not exactly physically, though) with shear 4. DynLibDispatcher should report names of classes if dispatch fails. 1. Add InteractionGeometryMetaEngine::explicitAction and InteractionPhysicsMetaEngine::explicitAction for creating contacts manually using the dispatcher 2. add utils.createInteraction(id1,id2) that requests explicit transient interaction creation 3. Add sample ElasticContactLaw2 (in ElasticContactLaw.cpp) that uses new SpheresContactGeometry code and can be used for "persistent" (transient but not deleted by the collider) contacts. 4. Add Shop::applyForceAtContactPoint (applies contact force at contact point to both bodies (reversed for body2) and moments acting on their cenroids as well) 5. All the previous new functionality is demonstrated in scripts/chain-distant-interactions.py. 1. Add Omega().reload() 2. Create map Omega::memSavedSimulations, where simulations are saved if they begin with special sequence ":memory:" and can be loaded again, within lifetime of Omega. 3. Add Omega().tmpLoad() and Omega().tmpSave() that use the above mentioned thing. 4. add utils.readParamsFromTable that reads any python variables from text file (useful for automatic parametric studies) 5. Add less expensive routine for sontact point relocation in SpheresContactGeometry 1. Separate player into two windows (controller and the 3d view); the controller may be hidden with ::hide() (and ::show()) or with Alt-H from the 3d view. This allows for correctly restoring 3d view size. Closing any of the two closes the player. Please log crashes, if any. 2. Update QtGUI-python.cpp: runPlayerSession 1. Yade now runs TCP server at the first free port above 9000 (accessible from localhost only), which simulates python console. (let me know if you need this over network -- it is DANGEROUS, since someone can delete all your files etc without any authentication). The interpreter has its own namespace. 1. Fix crasher (under special circumstances) in PersistentSAPCollider (deletion invalidating iterator) 2. PeriodicEngine now takes nDo and nDone attributes, limitng number of activations 3. Fix minor things is BrefcomDamageColorizer, add normalized damage to BrefcomPhysParams 4. Fix some indents in Triaxial Fix stupid sign mistake when applying contact force in brefcom. 1. PythonTCPServer now generates random 6-letter cookie (password) at startup and authenticates all connections by that cookie. It can be retrieved from yade.runtime.cookie 1. Add Body().mask in python 2. Add PhysicalParameters().pos and .ori instead of ugly pp['se3'][:3] and pp['se3'][3:] 3. Add dmgPlane plot in brefcom 4. Do not delete renderer if we close the last view (loses all renderer settings) 1. Add a quick implementation of bending and torsion code to SpheresContactGeometry 2. Update ElasticContactLaw2 to use that code; stiffnesses are hard-coded for now. 3. Update scripts/chain-distant-interactions.py to show that that code really works 4. Plot residual strength instead of damage in brefcom 5. Add some functions for spiral projections (not correctly working yet) 6. Fix missing std:: in DisplayParameters 1. Add 2d weighted average smoothing abstract class and its specialization on symmetric gaussian kernel, with python glue of course. 2. Remove .gz support. 3. Fix some bugs in the spiral projection code. 1. Fix errors preventing compilation due to missing TesselationWrapper. !! IMPORTANT !! Introduced new dependency on "python-numpy" package (numpy/ndarrayobject.h header), which is now checked for by scons. Tell me if this is not OK! 1. Add initializers for all SpheresContactGeometry members, to avoid nan's in the .xml file 2. Add code for saving arbitrary python variables within the .xml file, see documentation on utils.saveVars() and utils.loadVars() 3. Fix error (hopefully) caused by dangling symlinks in the symlinked buildDir/include tree 1. Fix algebraic typo in max distance check. 1. Commit simple parametric study interface, documented at http://yade.wikia.com/index.php?title=ScriptParametricStudy and wrapper script to queue jobs, which is installed as yade-trunk-multi, yade-trunk-opt-multi and so on. 2. Examples of such parametric "study" added in scripts/multi.py and scripts/multi.table 3. MetaBody now create Omega().tags['id'] at initialization like '20081028T102950p15498' (date, time, pid) that is unique 1. Changed colors to be random by default on utils.{sphere,box,facet} 1. Make our intelligence-challenged deserializer finally gracefully handle nan, inf, -inf (arbitrary lower-upper case). 2. Fix crash in MembraneTest (leftover in the commit from testing, oh well) 3. Add TriaxialTest::fixedBoxDims to not scale selected box dimensions if sphere mean radius is given (example underway). 1. experimental support for Real==long double (#defines QUAD_PRECISION, scons quad=1). long double is not necessarily quadruple precision (not on IA32, at least) though, but is still more precise than double. (search "long double" on wikipedia) 2. Some preparation to compile with -std=c++0x (not yet functional) WRT and boost::shared_ptr vs. and std::shared_ptr 3. Fixed in yade-multi. 4. New direct oofenm exporter in yade.eudoxos. 5. Don't fiddle with termios in non-interactive sessions. 1. Add INTEGER (besides FLOAT) to the list of wrapped c++ types in python. Fix type detection for long double 2. Fix Real->double conversions in GLDrawBssSweptSphereLineSegment.cpp 1. Body().phys.refPos is directly accessible now (old way Body().phys['refSe3'[0:3]] still works, though) 2. Labeled engines are at load-time or when Omega().engines are modified assigned to variables __builtins__.label; for example Add forgotten file. 1. Add linear templated interpolation function working on 2 lists, t and values 2. Add InterpolatingRotationEngine that changes rotation speed as given by value table 3. Lower info about starting python thread to debug message only. 1. Rename REGISTER_SERIALIZABLE(class,bool) to REGISTER_SERIALIZABLE_GENERIC, add two macros REGISTER_SERIALIZABLE and REGISTER_SERIALIZABLE_FUNDAMENTAL. Changed all uses accordingly. 2. Added REGISTER_CLASS_AND_BASE(class,base) which performs REGISTER_CLASS_NAME and REGISTER_BASE_CLASS_NAME at once. 3. Added REGISTER_ATTRIBUTES (not trailing 'S'), expanding to void registerAttributes(){...}, and REGISTER_ATTRIBUTES_WITH_BASE, which prepends call to baseClass::registerAttributes() (specified as the first argument). 1. Added Se3Interpolator engine, with scripts/test/Se3Interpolator.py 2. Added from math import * to PythonUI_rc.py so that math functions are readily accessible everywhere 3. Implemented stopStrain in UniaxialStrainControlledTest 4. other small fixes 1. Modify REGISTER_ATTRIBUTES macro so that it work with or without base class given. Change all headers in core to use that. It should help us if we ever make the transition to boost::serialization. 2. User REGISTER_CLASS_AND_BASE instead of REGISTER_CLASS_NAME and REGISTER_BASE_CLASS in core and at a few other places. 1. Removed cerr garbage from glviewer (use LOG_DEBUG if needed) 1. Fix warnings in GLViewer 2. Add (35.4%)-style counter if stopAtIteration is set to the OSD in gl view 3. Add utils.downCast to downcast object to its derived class and copying all attributes. 4. Add utils.SpherePWaveTimeStep to compute critical dt from a few parameters only 5. Add routine to compute overall kinetic energy to Shop (indirectly to utils as well) 6. Add routine to sum torque (momentum) with respect to an axis from forces and torques on given set of bodies. Fix typo (compilation error in debug mode) in Shop::kineticEnergy 1. Fix a bug in rotation ode in SpheresContactGeometry. Now the code gives the same results as almost-for-sure-correct FEM code regarding strain computation. 2. Add label attribute to StandAloneEngine (inherits from Engine) omg (code cleanup, no functional changes) Exit once the simulation finishes; do not rely on simulation exiting itself. 1. SpiralEngine that does both rotation and translation along the same axis 2. InterpolatingSpiralEngine for variable rotation+translation speed (replaces InterpolationgRotationEngine) 3. scripts/test-spiral.py for rudimentary functionality testing. 1. Un-disallow (bug) having all dimensions scalable in triaxial if particle radius is fixed. 2. Don't record anything by default to file in UniaxialStrainer (only useful for debugging) 1. Add a few functions for integrating piecewise-linear functions in new module yade.linterpolation 1. fix damage colorizer (removed dynamic_cast causing segfaults on non-spheres) 2. InterpolatingSpiralEngine now takes bool wrap rather than Real period. 3. qt.makePlayerVideo takse startWait argument, which waits for Backspace before starting (useful for setting up the view manually); postLoadHook gets run afterwards now. 1. Add algorithm for pushing sphere always to the same side when in contact with facet, even under extreme stress where the sphere geometrically passes to the other side. 2. Add bool SpheresContactGeometry::initContactOnPositiveFacetSide for that 3. Add test/demo script for that. 4. Re-enabled GLDrawSphere::glutUse (cosurgi?) 5. Renamed yade.runtime.args to argv, to be consistent with sys.argv 6. Allow passing sys.argv (arguments to scripts) through QtGUI as well 1. Fix clip plane orientation inversion as well as wheel normal movement. 2. Clip planes not displayed at all unless one of the clip planes is being manupulated. 1. Unbreak bad type (list) for sys.argv[0], broken recently 2. Color bodies by residual strength rather than damage parameter (smoother) 3. Make player messages (maybe) better formatted omg... 1. Make player reuse existing renderer if there is one (allows setting Draw_mask etc before running player) 2. Add initRun parameter to PeriodicEngine (false by default), to run at the very fist run. 1. Make BrefcomDamageColorizer run at 0th iteration as well 2. Make player set stopAtIter to last iteration in db so that percentage is displayed in the output. Fix previous commit, overlooked. sorry. 1. Number lines in parameter table for multijobs from 1 (as text editors do) 2. Print short summary at the end of job log (when started, when finished, exit status, duration, commandline) 1. Fix logic typo in QtGUI (warning when there shouldn't be and vice versa) 1. logarithmic strain for Brefcom. 2. change sphere-facet algorithm such that it works on corners. Maybe no longer necessary, but it doesn't hurt now. scripts/test-sphere-facet-corner.py as provided by sega demonstrates that. 3. "from __future__ import division" in python init such that 3/2==1.5 and 3//2=1 (is default since python 2.6) 4. Global GL lock to avoid crashes/freezes with GL accessed from multiple threads concurrently. scripts/gl-test.py (which used to crash) now works with one view; 5. Properly lock/unlock on all gl ops in QtGUI-python.cpp, like setting view direction, resizing etc. 6. Fix wrong assertion in SQLite player 7. add Omega().resetTime to reset iteration number, virtual and real time. 8. Changed utils.loadVars/saveVars code to be more reliable. 9. Added utils.aabbExtrema2d 1. Lock GL when closing view. Seems to fix crashes / delays at exit 1. Contact sphere-facet must be deleted by the constitutive law once created. Let me know if that is a problem -- I think it is the way it should be in the future everywhere, though. 2. Fixes for boost 1.37 (boost::thread not fully compatible) 3. utils.basidDEMEngines (not tested yet) 4. Fix warning in GroupRelationData 5. add march switch to scons ('native' is default, but not supported by older compilers) 6. Prepend default CXXFLAGS so that user-defined ones (like warning suppression) come after the default ones and override those. 1. Tentative fix for newer scons, which links empty files (dummy plugins) with plain gcc and doesn't find default c++ libs to link with. 1. File not needed. 1. Beautify no-debug code in main 2. SpiralEngine now saves angle it has turned so far 3. Remove failing assertion from IFacet2ISphere4SCG (why it fails? it was a (I->isReal&&I->interactionGeometry)||(!I->isReal&&!I->interactionGeometry)...) 4. Improve the utils.spiralproject function to specify period (moderately tested, but seems to work) 5. Adjust locks in GLViewer. Fixes my crashes on nvidia for both player and 3d view (didn't dare to try 2nd view). Next step will be to move this lock to Omega and lock it when loading/resetting simulation etc. 6. Change theora video quality to 63 (maximum) instead of medium 32 in utils.encodeVideoFromFrames. Makes the video about 3x bigger. Two BIG changes: 1. Add ef2_Spheres_Brefcom_BrefcomLaw, is faster (!) 2. Fix locking in Omega, we don't get crashes when loading simulation with the 3D view enabled anymore (please report any further crashes in OpenGLRenderingEngine, that should be fixed) 3. Automatic update of Doxygen configuration file 4. Dem3DofContactGeometry, which should be in near future parent of SpheresContactGeometry and other new geometries providing normal and shear strains (SphereFacetContactGeometry, in my mind) 5. Add convenience function to apply force to contact point between two particles. (remove no-warn for c++0x since that flag is not known to gcc-3.4) 1. Add possibility to change container types from python (for benchmarks) -- code not yet tested! 2. Possible optimization in PhysicalActionVectorVector, now disabled. collider mistake hotfix 1. Make triaxial stop if !autoCompressionActivation and already unloaded. If this breaks something, please let me know, we can do it otherwise, but, for Cundall's sake, can we keep the same behavior for at least 6 months?? 2. SpatialQuickSortCollider will not delete real contacts, even if bodies don't collide. 3. Remove redundant attribute registration from SpheresContactGeometry fix typo in collider 1. removed persistentInteraction from most places; they are still present in MetaBody, but not serialized any more, most importantly (avoids warnings) and not GL-drawn. 2. Plugin loading loginc completely changes, the underlying macro YADE_PLUGIN has still the same interface. We used __attribute__((constructor)), __attribute__((visibility("internal"))) and anonymous namespace magic to get there. This will make it possible to put multiple plugins to a single shared library without touching the source, only fiddling with SConscript's. 3. PythonUI is interactive by default (it was not?? weird.) 1. Add new non-generic Bex container. For non-optimized build, it increased the speed by about 11% (9:35 vs. 8:49) as compared to PhysicalActionVectorVector. Users willling to go with the slower PhysicalActionContainer can compile with scons CXXFLAGS=-DNO_BEX now to get the old behavior. 1. Adapted TriaxialTest and ElasticContactLaw to BexContainer (switchable at compile-time). 2. Adapter GlobalStiffnessTimeStepper to BexContainer (GlobalStiffnessCounter code put to a function inside this one, hence that engine is not needed anymore). 3. Adapted Shop::Bex to BexContainer (probably not needed anymore?) 4. Exception is thrown in PhysicalActionVectorVector is used && built with BEX_CONTAINER (would most likely crash anyway). 5. transientInteractions and persistentInteractions are only references to MetaBody::interactions now. Removed extra loops in InteractionPhysicsMetaEngine and InteractionGeometryMetaEngine. 6. Remove including qglviewer into miniWm3, as it breaks compilation if using miniWm3 separately from yade (for testing purposes). 1. Define Janek's functions to convert from/to QGLViewer vector where they are used. 1. Add BexContainer (same interface, but separate implementation) in openMP flavor. 2. openmp=0 is the default for scons now (for some time), since openMP BexContainer gives about 7% slowdown and thus is not useful before at least some engines are parallelized as well. 3. Removed getting references to Force/Torque from BexContainer, since that breaks concurrency. Instead, addForce/addTorque must be used. If you need to read back with getForce/getTorque, you have to call sync() (expensive!!) beforehand, which will compute summary values for all bodies; if you forget, exception will be thrown. Note that sync() is invalidated at next write operation. 4. Adapted a few constitutive laws and other engines for the changes mentiones here. 1. Add time-profiling classes. See http://yade.wikia.com/wiki/Speed_profiling_using_TimingInfo_and_TimingDeltas_classes for details. It is accessible from python as well. 2. SimulationController no longer asks for output format. It just uses XML, the only available one. 3. Engine, EngineUnit have timingInfo member and pointer to optional timingDeltas. Timing is disabled by default, but it may still have some overhead in the order <1% perhaps (will test) 1. Forgotten file with yade.timing.stats() that pretty-prints some numbers. 1. Move all container to core, remove linking with them since it is not necessary 2. Fix things so that everything compiles (at least) both with and without BexContainer 3. Parallelize ConstitutiveLawDispatcher, InteracionPhysicsMetaEngine and InteractionGeometryMetaEngine with openMP if enabled on command-line 4. Make InteractionVecMap default container for interactions; necessary for parallel code. 1. New InteractionDispatchers class that has one common loop for InteractionGeometryMetaEngine, InteractionPhysicsMetaEngine and ConstitutiveLawDispatcher. It can be used from python like this: 1. Adapt ForceEngine to BexContainer 2. Add BEX_CONTAINER and YADE_OPENMP flags to be reported by -h. 1. Add two more forgotten headers. They are not used anywhere, though, and will be removed later. 1. Enhancement of utils.box from Marco. 1. Make Shop load 4/5/6 columns text files for spheres (and skip 5-cols) 2. Change evaluation of args in readParamsFromTable to be able to pass strings better (single quotes) and consistently 3. Parameters passed as env. vars by yade-multi are prefixed with ! 4. fix typo in timing for resetting timers. 1. Make the yade-multi scheduler multithread-aware. 2. Add the collider performance test as example 3. added utils.replaceCollider(anotherCollider) for convenience 4. added Omega().isChildClassOf(classNameChild,classNameParent) 1. Fix collider-perf (TriaxialTest has hardcoded dt=0.001 !!!!), set timestep by hand at first 2. Fix Shop:: loading spheres from file (regression from yesterday) 3. Add functorCache to Interaction and to InteractionDispatchers. About 5% improvement, but not yet finished. 4. Add DynLibDispatcher::getFunctor2D 5. Add BexContainer::sync() to other places in triaxial (thrown otherwise) 1. Fix old rename of runtime.args to runtime.argv in default-test.py. Should run OK now. 1. Fix for the compile error in snow for OrthogonalPlaneFit3. It still misses pkg/snow/Engine/ElawSnowLayersDeformation.cpp in the repo. Compile with exclude=...,snow as workaround. 1. Rename BroadInteractor to Collider. 2. Move some part of the contact logic to this common base class. The Collider::handleExistingInteraction is subject to discussion and not yet used. 1. Small fix in the collider code. 1. Remove SAPCollider completely 2. Make yade log4cxx 0.10 compatibile 3. Other minor fixes 1. Fix linking of realtime-rigidbody WRT PersistentSAPCollider 1. change default xiShear to 0 instead of NaN (is not used anyway) 2. Fix linking of mass-spring in cliean build 1. Fix #include's that include files not in the current source directory via "". 2. Add scripts/linkdeps.py that can automatically determine what plugin links to what other plugins, which can be once used for configurable monolithic/pluggable builds etc. 1. Fix crashed in TriaxialTest introduced by me a few days ago (referencing engine before being created) 2. Add simple (working?) memory usage query to utils and collider perf test. 1. Fixed collider performance benchmark 2. Added script to generate graph from log files (like http://yade.wikia.com/wiki/Colliders_performace) 1. Added SpheresContactGeometry::updateShearForce, will be used (not activated though yet) by ElasticContactLaw and other. hotfix related to last commit 1. Initial checkout for the DemXDofGeom classes (will contain the hasShear code from SpheresContactGeometry and more eventually). 1. Preliminary version for sphere-facet collisions with total formulation (moderately tested). 2. Same for sphere-sphere collision. 3. test script for facet-sphere collision geometry. 1. Use local foreach.hpp if not found in the system 2. Use local file defining boost::python::len for boost<1.34 1. remove explicit linkage to boost_python in gui/SConscript Forgotten empty file. 1. Remove logger from ClassFactory 2. Cleanup logging stuff in main (use constructor function, among other) 3. Cleanup system exit from python in PythonUI_rc.py 4. Add SpheresContactGeometry::updateShear that can be optionally used with ElasticContactLaw to update shear displacement instead of updating shearForce. triax-identical-results.py show quite large difference between both implementations, but I am not able to tell which one is correct. scripts/test/shear.py shown almost no difference for 2-sphere scenarios, modulo differences at 15th decimal place or so. 5. Remove debug output from BexContainer 6. Remove warning about meniscus data from CapillaryCohesiveLaw (warning is given in postProcessAttributes now, i.e. iff the class is actually used) 7. Add logger to snow. 8. Removed shear computation code in ElasticContactLaw, use SpheresContactGeometry::updateShearForce. 9. Fix scons deprecation warnings 1. Add functions to augment forces/torques in BexContainer. 1. Remove some garbage from SpheresContactGeometry 2. Verify that SCG_SHEAR doesn't alter behavior if ElasticContactLaw::useShear is false 3. Implement SCG_SHEAR for sphere-box interactions 4. sphere-box interactions no longer call goReverse, but swap interaction order instead, as facets do. 5. Fix triax-idnetical-results.py to reload generated initial config to avoid rounding issues of sphere coords in text file. Minifix: link core with librt (clock_gettime) for platforms that require it 1. Add rate-dependent damage to normal and viscoplasticity to shear components of Brefcom (not yet tested, just compiles); other cleanups there. 1. add addF and addT to legacy ActionContainer Add label attribute to engine units. Python wrapper updated. 1. Rate-dependent fixes (more to come) in brefcom. 1. Remove MetaBody::physicalActions if compiling with BEX_CONTAINER (default) 2. Make everything work with both BexContainer and deprecated PhysicalActionContainer 3. Change PhysicalActionDamper, PhysicalActionDamperUnit, PhysicalActionApplier and PhysicalActionApplierUnit to dispatch only according to PhysicalParameters (not PhysicalAction anymore). That means that only one unit will be called for each body and that NewtonsMomentumLaw has to do the job of NewtonsForceLaw as well (same for CundallNonViscousMomentumDamping). 4. Fix (finally) defaultDt computation in TriaxialTest (forgotten assignment to defaultDt in the GlobalStiffnessTimeStepper) 5. Fix (finally?) timestep manipulation form within the QtGUI. If reloading the same file, timestep settings will be preserved, otherwise those saved in the XML are used. 1. Create ef2_Spheres_Elastic_ElasticLaw that has the ElasticContactLaw algorithm 2. ElasticContactLaw now merely calls ef2_Spheres_Elastic_ElasticLaw. 3. TriaxialTest::parallel controls whether to use InteractionDispatchers or not. 4. Added examples/triax-perf to show the impact of that. Also see http://yade.wikia.com/wiki/Triaxial_Test_Parallel 5. Fix a few compilation issues. 6. Flush stdout before exiting from main, so that in case of crash upon exit (log4cxx?) we have all messages output. 1. Hotfix for 2 issues with ElasticContactLaw 1. Remove all traces of physical actions: 1. Remove Shop::Bex (no longer necessary) 2. Add InterpolatingDirectedForceEngine for Roger (not yet tested!) 1. Fix linking of ForceEngine with interpolation 2. Random rate-dependent things. 3. Typo in BexContainer docs 1. A few fixes in Brefcom. 2. Make python wrapper correctly handle long long attributes. 3. Added test script for InterpolatingDirectedForceEngine 1. MAJOR change in python syntax (backwards-compatible, though): through small bit of very dirty code, classes can be instantiated as python objects with keyowrd arguments, i.e. instead of 1. cleanup UniaxialStrainer code, add vars to control length of the acceleration phase and the ability to set absolute speed 2. EngineUnits within InteractionDispatchers can be labeled and accessed from python now. 3. A few minor things in the plot module 1. Remove cruft from brefcom, a few fixes there 2. Add gnuplot grid to gnuplot by default 3. Fix speed in USCT 4. Fix InteractionDispatchers traversal in pyOmega 1. Fix weird python problem if LIBS is not defined by distuils (?) 1. Add working version of facet adjacency finder (to get their mutual angle at edge) 1. Parallelize initial bound filling in PersistentSAPCollider. Gives almost 3x speedup for the first step. 2. Fix missing headers so that it compiles with g++-4.4 (in QGLViewer) 3. FacetTopologyAnalyzer works (not tested extensively), questions of where to put topology data for InteractingFacet (will be raised on yade-dev) 4. Test script for FacetTopologyAnalyzer. 1. Remove leftover var in PersistentSAPCollider 1. Add preliminary gnuplot plots merging facility to yade-multi 2. Fix yade.log module so that it also works without log4cxx (and gives 1 warning) 3. Switch to new syntax in scripts/plot.py 1. Fix race condition in brefcom (omp critical section) 2. Remove -ffast-math from optimized build, since NaNs do not work properly (e.g. isnan(NaN)==false!) 1. Fix bug in FacetTopologyAnalyzer algorithm 2. Add angle info to InteractingFacet (not yet used) 3. Add triangulated sphere test for FacetTopoloyAnalyzer to facet-topo.py 4. Fixes in rate-dep in brefcom 1. Add approximate viscosity equations to brefcom (not working) 2. Fixes in yade-multi 1. Finish implementation of Dof3DofGeom_FacetSphere (with plastic slip as well) 2. Adapt Brefcom optionally to Dem3DofGeom 3. Add d0fixup to SpheresContactGeometry to get correct normal strain in sphere-facet contact with fictive sphere (zero for sphere-sphere) 4. Finish FacetTopologyAnalyzer; angle usage in Dem4DofGeom_FacetSphere not yet tested. 1. Remove cruft from UniaxialStrainer, add the ability to set initial velocities to suppress inertia at the simulation beginning 2. Jobs in yade-multi can be automatically named after variables suffixed with ! 3. yade-multi now shows and updates automatically some statistic on localhost:9080 using http 1. Finish Dem3Dof for both spheres and facets, migrate Brefcom. Removing stuff from SpheresContactGeometry is on schedule, once thorough testing is finished. 2. Fix attribute inheritance for a few engine units 3. Add ef2_Dem3Dof_Elastic_ElasticLaw, which works like ElasticContactLaw (but faster ;-) ) 4. Remove BrefcomLaw, keep just the constitutive law as functor. Adapt sample generators for that. 5. Add hydrostatic confinement to brefcom (isoPrestress), remove viscApprox. 6. Add interface for querying and setting (doesn't work yet) number of openMP threads 7. Job description is taken from columns suffixed with ! or by combining all parameters 8. Add function for plotting yeild surface of brefcom to the eudoxos module 9. Oofem export now uses reference (starting) positions of particles as it should 10. Add "-" before profile name when generating default variant suffix in scons (which is expected) Fix comp9ilation error for Janek. 1. Add python wrapper for clumps, see scripts/test/clump.py on how to use it (O.bodies.appendClumped) 2. Fix clump support in NewtonsDampedLaw (damping is quite wrong, it seems...; Bruno, any ideas on that?) 3. Move clump to pkg-dem (even though it depends on extra/Shop) 4. Remove absolute shear computation from SpheresContactGeometry, as Dem3Dof works fine now and Brefcom will not use SCG anymore; adapt a few other classes to that; remove ElasticContactLaw2 that was using it. 5. 1. Warn in --version if -ffast-math was used 1. Fixing bug in TriaxialCompressionEngine (introduced by luc apparently). Big changes: Handle boundingVolume-less bodies gracefully in InsertionSortCollider 1. Add utils.regularSphereOrthoPack by Anoton Forgotten showcase. 1. Add InteractionContainer::requestErase to hint colliders that wouldn't otherwise see !isReal interactions. 2. Use this logic in InsertionSortCollider, PersistentSAPCollider, SpatialQuickSortCollider (noop in the last one) 3. Add InsertionSortCollider::sortThenCollide to make it behave as non-persistent collider (for debugging only?) 4. Add Interaction::reset() that has common initialization code. 5. Assign zero inertia to utils.facet (better than uninitialized binary garbage) 6. Fix contact logic in Brefcom, finally I get the same results with InsertionSortCollider as with SpatialQuickSortCollider on large simulation (more fixes to come) 7. Do not install pkg-config; fixes compilation error reported by the build bot. 1. Use bitfield instead of bools in InsertsionSortCollider 2. Revert accidental changes in insertion-sort-collider.py 3. Add function for using renamed classes from python with warning Getting rid of the Brefcom name, classes renamed as follows (moving files around will follow) 1. Add logic to scons to delete files that will not be installed in this run but are on-disk. This should make it unnecessary to remove files by hand. In the same way, disable implicit target cache which was making problems if files shuffle around. 2. Remove Brefcom* files, move to pkg/dem/ConcretePM.?pp. (not sure what other place, since it contains Engines, DataClasses etc in one file that I would refuse to break.). 3. Add detailed comments on ConcretePM to the ConcretePM.hpp 4. Remove extra/usct, moveUniaxialStrainer and put to pkg/dem; move SimpleScene from extra to pkg/dem 5. Remove core/yadeExceptions.* 6. Remove Preferences::dynLibDirectores and Preferences::version. Omega now recursively searches the lib directory for plugins and loads them. preferences.xml has only defaultGUILibName now. Finally. Remove baseDirs and other cruft from DynLibManager. 2 forgotten files (separated from UniaxialStrainer before) 1. Fix hopefully last relict of pkg-config 2. Degrade deleting non-existent interaction from fatal erro to warning only (not sure where that can come from, though.) Solve the cause of nonexistent interactions being deleted (after O.interactions.clear()). Make warning fatal error again. 1. Remove Interaction::isNew, Interaction::isReal. Add Interaction::isReal() which test for presence of _both_ interactionGeometry and interactionPhysics and Interaction::isFresh() which checks the interaction creation timestamp against current iteration. Updated all code for that. Please verify that your stuff works, it is possible I made some mistakes! 2. All code saying isReal=false replaced by interaction->requestErase(id1,id2) 3. make requestErase thread-safe 4. Add interace for traversing interactions pending erase (template) 5. Add TriaxialTest::noFiles and TriaxialCompressionEngine::noFiles to not generate any files (default: off) 6. Add predicate utils.ptInAABB(p,minPt,maxPt) 7. utils.spheresToFile return number of spheres written (instead of None) 8. Add examples/concrete/pack to generate packing; other stuff in concrete will follow, with simplified version of the cpm model in ConcretePM. 1. Syntax fixes in ConcretePM model 2. Interactions will be deleted also if the ye4xist, but geometry functor fails (returns false) 3. Add utils.approxSectionArea, utils.spheresFromFileUnixial (get some parameters useful for uniaxial tests from the sphere packing, such as cross-section, principal axis, etc) 4. Add lib/computational-geometry/Hull2d.hpp for computing convex hull in 2d using Graham scan algorithm (I know it is in CGAL already, but I don't want to depend on that commercial stuff) 5. Remove some unused stuff from utils. 1. Add epsNPl to Cpm 2. Replace vector by Vector2 and make it properly serializable (should be faster) 3. Add function to compute forces on interactions crossing plane. 4. Replace custom xcombine function by itertools.product in grid sphere packing Fixes and cleanups in the CPM code. Add new 'feature' to scons called openGL, which is on by default. Qt3 GUI will not be built without openGL. YADE_OPENGL is defined in openGL-enabled builds. Sconscript files adapted to skip GL plugins or GL parts of plugins. 1. Fix missing libs for linkage 2. Fix a few typos 1. Don't link core with glut (is there a reason for that?) Do not include X11 headers if without opengl 1. Move all python modules to PREFIX/lib/yadeSUFFIX/py/yade instead of */gui/yade 2. Add pack module for generating regular packings (thanks to Anton Gladky for his ideas & code) 3. Add _packPredicates module for solid inclusion testing. 4. Update regular-sphere-pack.py to test new packing features. Remove leftover reularSpheresOrthoPack from utils. Is in yade.pack now. Add pack.inHyperboloid predicate. Should be useful for cylindric specimen in uniaxial tension, but with the middle part having smaller diameter. Added to the test script as well. 1. Fix (hopefully) clump damping 2. Remove unused code from InsertionSortCollider 3. Make SimulationController NOT change timestep (rounding) by just opening it (!!) 4. Fix paths in PythonUI and lib/SConscript for $PREFIX/yade$SUFFIX/py/yade instead of $PREFIX/yade$SUFFIX/gui/yade 1. Add resetting interactionGeometry and interactionPhysics to Interaction::reset(), as it should be... 2. Small things in CPM 3. Add GaussAverage.data method to python (for debugging and plotting) Include Python.h in PythonRunnerFilter. Build this plugin only with EMBED_PYTHON. A few changes to allow run primarily python with qt3 on the top of that (open, close etc as needed). This paves way to running main yade process in python ;-) (just kidding, for now) 1. Handle groupMasks of bodies in InteractionDispatchers directly (not in geometry functors etc). 2. Fixes for qt3 started from within python 3. Add Body().dynamic flag to the wrapper, instead of using Body()['isDynamic'] all the time. Anton's new things: axis-aligned ellipsoid predicate and fix in pack.regularHexa. 1. Add external euclid.py module (suggested by Anton, thanks!) to our installation 1. Propose *.sqlite ending for saved simulations instead of * in the player Fix error-tolerant law which uses no interactionPhysics: add void Interaction physics in the geometry functor if returning true, to make Interaction::isReal return true. (Shouldn't we remove ErrorTolerant...? Does it have some serious use?) 1. Remove constructor priority for log4cxx to avoid syntax unsupported by gcc<=4.0. 2. Remove some commented cruft from FrictionLessElasticContactLaw 1. Add boolean operators on solid predicates (&,|,^,-), shown in scripts/test/regular-sphere-pack.py 2. Add notch predicate (not functional yet) 3. Add utils.alignedFacetBox to create hollow box from facets. Shown in new file scripts/test/facet-box.py 1. Update simulation filename in controller periodically 2. Fix notInNotch predicate, use infinite AABB for unbounded solids and raise exception in the packing generator if such is encountered. 3. Add notch to the head of regular-sphere-pack.py guy 1. Add checks for GTS (optional) 2. Add pyGTS to our tree (built only if needed), since no packages are available. 1. Fix corner case in InsertionSortCollider related to instability of std::sort (quicksort) and bodies having the same upper and lower bounds (facet in the xy plane, for example). Thanks to Anton for providing the crasher. Fix feature macro definitions (typos) 1. Add GTS surface predicate 2. Add example of sphere horse falling onto facet horse in scripts/test/gts.py (try it!) 3. Predicate difference now takes padding in the inverse sense for the second predicate (logical) 4. Attempt to enable lightning on facets (no effect :-( ) 1. Add coarsened horse geometry for the example gts.py 1. Rename ef2_Dem3Dof_Elastic_ElasticLaw to Law2_Dem3Dof_Elastic_Elastic (in scripts as well) 1. Make virtual methods on pack.Predicate work in python (inGtsSurface, for instance). Add scripts/test/gts-operators.py to show that. 2. Move scripts/test/gts.py to gts-horse.py 3. Add a few functions to pack for constructing triangulated revolution surfaces from meridians. 1. Padding support (though slow and not very precise) for pack.inGtsSurface 1. Fix crashers in InteractingMyTetrahedron* classes (introduced by me when updating interaction logic) 2. Reimplement inGtsSurface in c/c++ which makes it faster due to pygts interface limitations (addressed on pygts mailing list meanwhile), but is dirty programming. 3. Set CCOMSTR to correspond to CXXCOMSTR in SConstruct (to show nice line when compiling c source file, for pygts) 4. Set good-looking timestep in the TetrahedronsTest generator 1. Add triaxial packing generator, with optional memoization to sqlite database to avoid generating packing if there are suitable ones (same parameters/scalable to same parameters) available. Sample script will be provided soon. This allows for filling arbitrary (gts-defined, for example) volumes with irregular, high-quality sphere packing. 2. Add python-wrapped class SpherePack for manipulating sphere packings (loading, saving, spatial transformations, iterating) 3. Add shared_ptr Omega::rootBodyAnother to be used with Omega().switchWorld() 4. Move some stuff from gui/py to lib/py, which is more logical hopefully 5. If negative value is specified for TriaxialTest::thickness, wall thickness will correspond to the mean sphere radius (avoids spheres going through the walls during compaction for some radii) 1. Add automatic converter from python::tuple to Vector3r and vice versa. Wrapped functions can take/return Vector3r directly instead of manual conversion. Clean code that had to do that. Let me know if something breaks, the changes should not touch any python code. 2. Some preliminary work on wrapping Vector+Quaternion classes in python via py++ (not enabled, just the files are there) 3. Add forgotten _packSphere.cpp, thanks to Vincent for reporting. 1. Add scripts/test/gts-traix-pack.py to demonstrate triaxial packing generator. Remove useless include in GlobalStiffnessTimeStepper. 1. Fix STUPID (my fault, sorry!!) mistake in setting Interaction::iterMadeReal, which cause ElasticContactLaw to function wrong. It concerns a few other contact laws calling isFresh, which would return true as well. Sorry again. 1. Shuffling stuff around, moving most python things to py/ 2. Wrapping Vector3r, Quaternionr and Vector2r in python directly, see scripts/wm3-wrap.py 3. Change the way utils.box, utils.sphere and utils.facet works: arguments to physical parameters are passed as keywords, not as dictionary now (update your old code by passing **dict instead of dict as the last argument) 4. Fix a bug with useShear, now getting almost identical results for triaxial again. 5. Preliminary Triaxial::facetWalls, to be able to used Dem3DofGeom with Triaxial. (not fully functional yet) 6. TriaxialStressController::thickness is -1 by default now (get thickness from box width), as 0 is real value for Facets. Doesn't change beahvior in any way. 1. Update triax-perf.py 2. Add Omega().tmpToString returning string of :memory: saved simluation 1. add yade.plot.resetData(), which unlike reset() resets just data 2. Add scripts/test/compare-identical.py to run 2 simulations at the same time and check at every step (configurable) if they differ or not. 3. Add active=active line to GlobalStiffnessTimeStepper, which for some reason causes two same simulations to be the same. Any explanation someone?? 1. Add forgotten py/SConscript (sorry!) 1. Fix absolute path generated by py++ 1. Dump XML in ConcretePM in case of problems (nan's) 2. InteractingFacet::postProcessAttributes gives FATAL message if there are coincident vertices 3. Update triax-perf, add script and openoffice sheet to make table 4. InsertionSortCollider is used in TriaxialTest always (not only with fast) now. 1. Change prototype of Engine::isActivated() to Engine::isActivate(MetaBody*) 2. Add maxVelocitySq to NewtonsDampedLaw for analysis purposes 3. Add BoundingVolumeMetaEngine::sweepTime and ::sweepDist to sweep bounding volume by given predicted motion over time or by given absolute distance. 4. Add strided run support to InsertionSortCollider (using sweeping) 5. Add scripts/test/collider-stride-triax.py to play with the strategies of sweeping based on strides 6. Add O.interactions.countReal() 1. Fix collider-stride.py to match changed parameters. (it is quite useless now, anyway 1. Make NewtonsDampedLaw compute maxVlocitySq at every step anew. 2. Make InsertionSortCollider find NewtonsDampedLaw and get max speed from there. Together with InsertionSortColider::sweepLength, this makes stride adapted automatically to maxVelocitySq; this will described at http://yade.wikia.com/wiki/Insertion_Sort_Collider_Stride. 3. Update test script for that. 1. Handle clumps in a better way in NewtonsDampedLaw (only if asked by the clump itself) -- future parallelization work. (related to bug #398086) 2. InsertionSortCollider now knows when to run based on maximum distance the fastest body could have travelled. (It is actually the physical meaning of sweepDistance). Handle varying maxVelocitySq in a constitent way. 3. Add InteractionContainer::serializeSorted to sort interactions by id1 and id2 before serializing (useful for comparing XML files). Added Interaction::operator< to compare 2 interactions in that way. 4. Fix openMP strategy in InteractionDispatchers to "guided" (as per https://blueprints.launchpad.net/yade/+spec/omp-schedule-strategy) 1. Remove doc/removed, it is in SVN history 2. Tune debian scripts to install docs+examples+scripts instead of headers (targets https://bugs.launchpad.net/yade/+bug/398174, but not yet finished) 3. Add depends on python-numpy instead of python-scientific packages for debian 4. Add ../py directory to doxygen (targets bug #398190) 1. Change NewtonsDampedLaw to a StandAloneEngine 2. add .updateExistingKeys (perhaps should be updateExistingAttributes) to all python-wrapped yade objects, which will change only existing attributes from given dictionary 3. Adapt STLImporterTest to new nicer syntax. 1. Add virtual destructors to all EngineUnits so that they can by dynamic_cast'ed to. This fixes possible crash from python, bug #398255 2. Rename updateExistingKeys to updateExistingAttrs, add updateAttrs. 3. Add forgotten script to demonstrate predicate operators 4. The default debug level is -ggdb2 (instead of -ggdb3). Libs are about 60% in size and still give the same debug information. 1. Rename scons option arcs to PGO (profile=guided optimizations) 2. Install only binaries by default (no docs, must be requested from cmd line via path) 1. Various fixes related to generating debian packages 1. Fix EngineUnits linkage for SImpleViscoelasticRelationships 1. Add velocity binning for collision optimization: requires cooperation of NewtonsDampedLaw, BoundingVolumeMetaEngine and InsertionSortCollider. 2. Remove time-based sweeping from BoundingVOlumeMetaEngine. 3. QtGUI doesn't show annoying warning when run from python session anymore. 1. Parallelized NewtonsDampedLaw (scales very well) 2. Parallelized VelocityBins 3. Add const modifier to BodyContainer::size 4. Add virtual dtors to PhysicalAction{Damper,Applier}Unit (for dynamic_cast'ing) 1. Move NewtonsDampedLaw to StandAloneEngine directory 2. Add omp critical to ConcretePM 3. Remove SphericalDEMSimulator 1. Add sweepLength to TriaxialTest if fast==true 2. Remove euclid from the pack module, since we use wrapped wm3 now. 3. Add _packObb.cpp to compute minimal oriented bounding box on cloud of points 4. inGtsSurface predicate with triaxialPack will work on the minimum OBB 5. Add check for engine to prevent crash as per https://bugs.launchpad.net/yade/+bug/399810 6. Enhance the pack.inSpace predicate to work as expected. Custom center can be given to the ctor 1. Fix non-tuple in graph specification 2. Allow plotting agains the y2 axis, after a special parameter '|||'. 1. Remove mutex from requestErase (should be verified to compile on non-openMP machine!) 2. Add mutex to PhysicalParameters, so that body state can be locked while updating from the interaction loop. 3. Add linking with EngineUnits to Tetra (thans Emanuelle) why..?? 1. Adapt simple-scene-graph.py to new instance syntax 2. Add support to y2axis to saved gnuplot plots 3. Change interface for yade.plot.addData to take **kw instead of straight dict (still backwards-compatible) 1. Do not use REGISTER_ATTRIBUTE anymore, use REGISTER_ATTRIBUTES instead everywhere. This generates (in addition to serialization code) also some python code and makes python independent on the yade serialization framework (Janek, if you want to try boost::serialization, I have no objections now). 1. Remove garbage from doc 2. Instruct getopt to not shuffle command-line arguments, so that their order is preserved 3. Add epydoc documentation for all python classes and functions 1. Fix linking logic for _packPredicates (fixes https://bugs.launchpad.net/yade/+bug/401029) 2. Make gts-horse coarse original horse automatically Fix small bug in Velocity bins related to max sweep length (should improve performance as well). Still some incorrect things WRT bboxes, it seems. 1. Fix mathematical error with VelocityBins, verified by new script scripts/test/collider-sweep-simple.py to be correct 2. Add py/ directory to skipped dirs for linkdeps.py so that it works on current trunk 1. BIG CHANGE: pkg/ no longer have SConscript files but are scanned at startup for plugins. See http://yade.wikia.com/wiki/ImplicitBuilds for some details. Let me know if there are some problems. add boost-serialization and boost-program-options to debian. 1. Move STILimport to pkg/dem, as it depends on dem 2. update RockPM with the new-old version (probably svn didn't update the moved version? what a deal. We should definitely move to bzr...) 1. Fix unreported version for the debian build 1. Fix snow compilation by #including Voxel/* files in SnowVoxeslLoader and noc compiling them directly (sorry) 2. STLReader is a pure-header import lib now, tolerance is passed as regular data member. 3. Fix all warnings in ConcretePM. 4. Merge lib/yade-* into lib/yade-support. 1. Fix monolithic compilation even with fem. All modules can now be compiled that way. fix compilation? Fix TetraTestGen crasher (functionally still brokne, though) Remove SDECLinkGeometry, replaced by SpheresContactGeometry at a few places. Not checked for functionality. This avoid many warnings. 1. Fix linking with full Wm3 library 2. Automatically add /usr/include/wm3 to CPPPATH if useMiniWm3=False 1. Hopefully fix https://bugs.launchpad.net/yade/+bug/402098 2. Fast in TriaxialTest uses nBins==5 and binCoeff==2 for VelocityBins 1. Fix SnapshotEngine and simple-scene-video.py to run. It doesn't work, though, because of the makeCurrent() failed error (probably getting snapshot from another thread...) 1. Add GLViewer::nextFrameSnapshotFilename to save frame in postDraw 2. Adjust SnapshotEngine to take advantage of that. This should solve https://bugs.launchpad.net/yade/+bug/396023 3. utils.encodeVideoFromFrames can take list of files (instead of just wildcard). Backwards-compatible. 4. scons doc skips debian/ directory (would mess up if deb package was build) 1. Fix for https://bugs.launchpad.net/yade/+bug/406343 (at least for me) 1. Add GenericSpheresContactClass to unit radius1, radius2 and normal between SpheresContactGeometry and Dem3DofGeom (to make it work with GlobalSitffnessTimeStepper). Partially solves https://bugs.launchpad.net/yade/+bug/399963 1. Add docstrings to all wrapper.Omega() methods 2. Fix a few warnings. Fix has_map warning and one more in FlowEngine. 1. Add infrastructure for getting temporary filenames in a secure manner to Omega 2. move selectedBOyd to MetaBody; initialize it properly to avoid possible crash. Remove leftover SConscripts. 1. Change syntax of YADE_PLUGIN("Plugin1","Plugin2") to YADE_PLUGIN((Plugin1)(Plugin2)) (replaced everywhere) 2. Add some perliminary and not-enabled by default support for boost::serialization (see ClassFactory.hpp) 3. Add boost::serialization code for Se3r, Vector2r, Vector2, Quaternionr 4. Do not reset BexContainer's moves and rotations uselessly (i.e. if not used) 5. Use Omega's mutex in SimulationController to prevent loading-related crashes 1. Don't use __COUNTER__ to generate plugin registration function names, as they wre added in gcc 4.3 Use the name of the first plugin instead. 2. Dem3Dof_SphereSphere takes touching distance as equilibrium if distanceFactor is <=1. The initial position otherwise (was the default everytime until now) 1. Generate proxy constructors to all classes deriving only from Serializable in python (like GLDrawBox and similar). 2. Fix scripts to have the new syntax everywhere, since e.g. EngineUnit is just abstract class that cannot be instantiated from python. 3. Add check fro valid data before saving plot to gnuplot; raise comprehensible exception if there is nothing to save. 4. Remove leftover scripts/pch.py 1. Add an example of uniaxial tension-compression test for concrete (ready long time ago...) 1. All different yade types are real python types now. They have their properties defined at startup. One can say O.bodies[0].shape.diffuseColor instead of O.bodies[0].shape['diffuseColor'] now. It also works much better with ipython tab-completion. It is not sure whether we will be able ever to deprecate the old object['attribute'] syntax. 2. Wrap ParallelEngine in python, and finish Dispatcher wraps as well. 1. Reset interaction radius after the initial contacts have been created. Fix FileGenerator wrapper in PythonUI_rc.py (has different name in c++ (FileGenerator) and python (Preprocessor). 1. Rename Preprocessor in python to FileGenerator, to be consistent with c++. Preprocessor is never used directly, anyway. The old deprecated name put in the deprecation table. 2. Manage crash at exit in defaut-test gracefully 3. Fix serialization of pairs (overlooked replace) 1. Initial attempt at regression tests (only the python wrapper so far); run scripts/regression-tests.py 2. Fix a few bugs discovered that way :-) 1. Replace PersistentSAPCollider and DistantPersistentSAPCollider by InsertionSortCollider everywhere. The two are still in the sources, but are not compiled. Will be removed in near future. Added to deprecated table in python so that old scripts still work. 2. Add some more regression tests for the python wrapper. 3. Fix timestep in HangingCloth (it does some weird things, can someone check it or just remove mass-spring from the tree?) 1. Be more tolerant to specifying invalid GUI at the command line 2. Throw exception about unopenable $DISPLAY rather than letting QApplication abort() 1. Fix https://bugs.launchpad.net/bugs/409254 https://bugs.launchpad.net/yade/+bug/409254 take 2 Maybe fix https://bugs.launchpad.net/yade/+bug/409365 1. Fix (?) https://bugs.launchpad.net/yade/+bug/409254 by moving ClumpTestGen out of Clump.hpp 2. Avoid warnings in STL import code (return values _should_ be checked, though!) 3. Avoid warning in yade.cpp in optimized build 1. Infrastructure for periodic collision handling. Not at all functional at this moment, don't even try to use. 1. Beta version of periodic boundary conditions (try scripts/test/periodic-simple.py 1. Assign new function O.exitNoBacktrace to sys.exit. Fixes partially https://bugs.launchpad.net/yade/+bug/410250 2. Add some more comments to periodic stuff. 1. Script that shrinks the periodic cell progressively (reveals bugs in the collider, though ;-) ) 2. Add function to compute total force in volume from interactions to Shop, wrapped in _utils as well. 1. Working periodic collider, yay!! scripts/test/periodic-grow.py shows that 2. A few changes related to that. 1. Add PeriIsoCompressor for periodic isotropic compression; see scripts/test/periodic-compress.py. Not so much slower than regular triax, nice surprise. 2. Make SpherePack standalone class (previously just in python's _packSpheres), add the function to generate cloud of spheres, both periodic and non-periodic. _packSpheres wrapper is update, but otherwise not changed. 3. Fix python-less compilation for se3 interpolator engine 4. Add Omega().runEngine() 5. Fix some compilation/linking issues. 1. Fix missing vtable for GenericSphereContact (crasher with debugging) 2. Fix collider stride for TriaxialTest with unspecified radius 1. Try to fix locking with boost-1.35, possibly fixes https://bugs.launchpad.net/yade/+bug/411572 (please confirm) 2. Add periodic repetition function to SpherePack 1. Fix linkage for boost<=1.34 (hopefully) 1. Fix a few things in SpherePack 2. Remove TriaxialTest::GenerateCloud, replaced by SpherePack::makeCloud 3. Fix crash in BoundingVolumeMetaEngine, if MetaBody has no boundingVolume 4. Improve sorting operator, remove workarounds for std::sort wrapping minima/maxima (https://bugs.launchpad.net/yade/+bug/402098) 5. Limit minimum cell width in PeriIsoCompressor, to not crash. 6. Add code for periodic packs in pack.triaxialPack (not yet fully functional) 1. Add #pragma once to SpherePack.hpp (possibly cause of https://bugs.launchpad.net/bugs/412442) 2. Fix crash in TetraTestGen (finally, I think), closing https://bugs.launchpad.net/bugs/408422 3. Add highlight parameter to utils.{facet,sphere,box}, passed to Body::geometricalModel 1. Fix assertion is 2d dispatch code, add indices to RpmPhys, RpmMat, CpmPhys, CpmMat (would break dispatching if using more classes derived from the same base in the dispatch matrix) 1. Remove {,Distant}PersistentSAPCollider and Fixed{Position,Orientation}Engine classes 2. Add test script for generating packing 1. Summarize important changes since the last release. Add your own things, if they are important for everybody (svn log has the small details) 1. Add scripts/rename-class.py for renaming classes (replaces in code, moves files with bzr, adds rename record to python). Not very well tested, though. 2. Remove core/StandAloneSimulator.*pp (not used anymore) Add ignore patterns so that bzr st doesn't show (some) generated files 1. Merged upstream changes. 1. Clen the plugin loading logic a little (no nominal win32 support), support YADE_PREFIX for changing prefix (should be runtime-detected inthe future, anyway) 2. Use $ORIGIN in rpath to make yade relocatable; it can be fragile, let me know if there are troubles! 3. Add make check to the debian package (will be tested soon) 1. Add compatibility for hardy 2. Fix missing newlines (hardy compiler gives warning) Adjust rpath for the main executable (ugly hack) Run tests in debian package (not failing if the test crashes, though, for now) 1. IMPORTANT! All features are lower-cased now (avoids mess: openGL or OpenGL etc). This will probably break your profile files. 2. IMPORTANT! openmp is one of features, no longer a separate options. Adjust your profiles 3. Rename LOG4CXX to YADE_LOG4CXX; all features now #define YADE_FEATURE macros 4. Better $ORIGIN syntax in scons 5. Abort yade if running PythonUI_rc.py fails 6. Adjust pbuilder script 1. Add wrapper for running tests, to trap exit segfault at squeeze 2. Use the wrapper for tests in debian/control 3. Add wrapper for vector Make the test script report what distribution failed (currently only karmic). Fix GTS features (thanks for spotting that; we need regression tests for all the modules) 1. Skip more stuff in doxygen, a few warnings 2. Add debugging code to DynLibDispatcher (disabled) 1. Rewrite 2d dispatching code from scratch 2. Expose classIndex to python for indexable classes 3. scripts/test-sphere-facet.py shows how the dispatch matrix fills with data Add python 2.5 compatibility hack Avoid error in pack.triaxialTest if compiled without GTS. 1. PeriIsoCompressor: add keepProportions parameter (on by default), decrease global updates interval. 2. Omega: startSimulationLoop warns if there is no simulationLoop (how can that happen? It somehow can) 3. SpherePack: resets periodicity (and warns) if being rotated; cellSize is writable now. 4. pack.triaxialPack: renamed to pack.randomDensePack; fix loads of stuff in that; rename radiusStDev parameter to rRelFuzz; add epydoc documentation; fix erroneous detection of inGtsSurface being defined 5. wrapper: lock renderer when adding list of bodies; Omega().resetThisWorld to reset current world only. 6. Fix scripts/test/{gts-random-pack.py,gts-random-obb.py} ef2_Sphere_Sphere_Dem3Dof: change meaning of distanceFactor, see docs (if < 0, equilibrium is sum of radii, otherwise the initial distance; defaults to -1). Renamed to distFactor, to break old code intentionally InteractingSphere2AABB: allow negative aabbEnlargeFactor, which doesn't scale aabb at all (same as if it is 1.) yade-multi: use the first non-blank line as column headings (not necessarily the first line of the file, as is used to be) SpatialQuickSortCollider: fix clearing interaction with openMP examples/concrete/uniaxial: move files around, add confinement and docs; remove examples/concrete/pack since not in the pack module (an example should be given, though) examples/collider-perf: remove PersistentSAPCollider, add InsertionSortCollide with strides, but commented out since no effect at hthe beginning of simulation PeriodicInsertionSortCollider: remove unnecessary (hopefully) condition that would abort in scripts/test/periodic-simple.py PeriIsoCompressor: fix absolute displacements changing proportions, operate on averages instead pack.randomDensePack: fix proportions in periodic packs; change residual stress to 1e8 instead of 1e9 SConstruct: remove obsolete (unused) standalone openmp option yadeWrapper: Fix compilation with boost<1.35 (again) (now?) 1. Fix startup crash if no $DISPLAY set at all miniWm3Wrap: update with newer py++; still errors out at karmic spirit: avoid warning with newer boost versions Add gts to depends on the script. Remove unnecessary inclusion of indexing_suite which breaks compilation for boost<1.34 (or something like that). utils.uniaxialTestFeatures: fix compat with py2.5 SConstruct: fix compilation issues with older symlinks in include dirs Fix OperationalError, reported by Janek in https://bugs.launchpad.net/yade/+bug/416906 SConstruct: add doc for pretty, fix a few lib checks on karmic Fix version detection on systems where version is not specified by hand or in RELEASE file and bzr is not installed main: disable SIGHUP handler (emergency save) and automatic loading. 1. Remove garbage from examples. 2. Fix SpheresFactory, fix examples/SpheresFactory/model.py move SpheresFactor to scripts/test as it doesn't work. Add notes to changelog and news. Forgotten file Mark release version Add mill script mimicking http://www.youtube.com/watch?v=005rdDBoe4w Some changes in qt interface (useless) Add series to package version to allow same version for multiple series in the ppa (0.20-1~hardy, 0.20-1~karmic etc) Added script for http://www.youtube.com/watch?v=KUv26xlh89I 1. Fix facet lighting, mill shown with wire=False now (looks better) 2. Revert recent useless change in QtGUI-python 1. Enabling O.timingEnabled when running will not produce garbage values for current engine anymore 2. Add maxRefRelStep to VelocityBins, to limit speed fo maximum velocity changes 3. Fix epydoc format for utils.encodeVideoFromFrames and utils.uniaxialTestFeatures 1. Do not save potential-only interactions to the XML; do not save pendingErase interactions (are only potential) 2. Do not save interactions in SQLiteRecorder by default (saveInteractions=True if you want the old behavior) 3. Add slider to the simulation player, in the "snaps" tab 1. Add scene that has been sitting here for a few days (should be run for comparison with pfc3d) Workaround bug #416335; static members of Vector3 work only on instance, e.g. Vector3.ZERO will not work, you have to use Vector3().ZERO; changed all occurences in the code accordingly, update your scripts as well.\n2.Some undocumented and untested enhancements for parameter table (backwards compatible) ================================================== yade-0.20.0 Sat, Sep 19 21:41:52 2009 +0200 Anton Gladky (33): 1. This is my first test commit 2. Added some initial files for mining rocks classes simulation 1. Some changes in RockPM, still does not work good 1. Some small changes on RockPM.* files 2. Test SVN_2_email 1. Test 2. RockPM.* updated Changes on RockPM.* files 1. RockPM, first variant of "destruction mechanism" 1. Deleted euclid.py 1. delete euclid.py (fised Sconscript) 1. In utils.py alignedFacetBox has been changed to facetBox. It is now possible to create arbitrarily-aligned box composed of facets 2. Updated regular-sphere-pack.py 3. Some small changes on RockPM.cpp 1. RockPM update. Added simple "destruction mechanism" 2. Added scripts/installOnUbuntu.sh to make "oneButton-checkout+compile+install+doxygen+epydoc" script. Not tested yet. Requires corrections. 1. Updated some scripts according to new syntax 1. Added PressTestEngine for simulating presses. Can be used and for PointLoadTest simulations. 1. Updated simple-scene-graph.py according to a new syntax 2. Updated simple-scene-video.py according to a new syntax 1. Changes on PressTestEngine. Now it defines the destruction moment more precisely 2. YADE CONSOLE emblem shows now without upper row shifted. 1. Some small changes 1. All variables initialize in PressTestEngine and RockPm Some changes on News Fix compile error 1. Partly fixed 1. Almost fixed https://bugs.launchpad.net/yade/+bug/412892 Just 1 warning: Some changes in INSTALL file because of migrating to BZR Some changes on one-click-install script Added "warning" in one-click-install script 1. Fixes compiling error without openmp feature. 1. 418107, I guess it is more correctly. Some nonessential changes Blocked some warnings in RockPM.cpp Small changes Press Engine and RockPM changes Added demonstration of buldozer simulation "Thanks" and Thanks to Janek :) Fixes VTK-librabry dependencies for Ubuntu Some small changes on Buldozer Bruno Chareyre (16): 1. Fixed a critical bug in ElasticContactInteraction (kn, was not registered and left undefined after loading a simulation). 2. Test if fn!=0 (i.e. contact exists) before adding kn/ks to the global stiffness. 1. "Water" classes are updated based on the code developped locally (but not commited for a while) by Luc Scholtes. 2. Triangulation code updated as well (not compiled by default). 1. Add a missing class (for the capillarity model), this fixes a compilation error in the previous commit due to the missing files. The "stopSimulation" command in the compression engine is now optional, as sometimes you don't want it to stop at all(1). The default behaviour of the compressionEngine is not affected, with autoStopSimulation=true by default. However, the TriaxialTest IS affected : it sets autoStopSimulation=false by default. 1. Update capillary files and add a new engine for them. 2. Prepare for removing old useless classes (typically some variants of engines with "water" at the end) Remove useless dependency. -Avoid crash when computing fusion number (it was using pointers to deleted interractions) FIXME : no distant meniscii for now due to deletion by the contact law. Base class for the solid-fluid coupling (E. Catalano PhD). -Register "thickness" so that sample dimensions are computed correctly (and not with 0 thickness) Dimensions could be 0 when "updateParameters" was called from TriaxialCompressionEngine before TTController::action Missing declarations in r1836. - cleanup : removing unused (and useless) "water" variants of engines - some cleanup in recorders too Update of the read/write functions for the connection with comsol. (coupling via Berkeley<->Grenoble mail servers! ;)) Missing declarations of member variables. Set default read/write behaviour as in Andrea's version. Feng Chen (1): This is viscous force damping, equation similar to http://en.wikipedia.org/wiki/Damping, damping parameters are controlled by betaNormal and betaShear, it is sometimes more realistic than the non-viscous (or say, local) damping. While using this code, please make a reference to: Janek Kozicki (40): created nearly empty snow package: pkg-snow Snow stuff - generate grains made of layers according to voxel data read from file. And daraw it. forgot some files - make it compile with boost 1.34 - some snow improvements - another fix. now it really compiles with boost 1.34 1. TriaxialStressController reads radius from interactingGeometry Code wasn't compiling because of #include MicroMacroAnalyser - comment it out. 1. argh, guys. svn HEAD must compile. You didn't check your commits and then I waste 1h to fix that. See this commit, to check what I had to comment out about WATER - added keyboard shortcut '.' that toggles grid subdivision by 10 - fixed error with grid step - grid step is now shown in gl window small snow update another small snow update 1. small snow update 2. fix compilation error by Vaclav (hey, you didn't compile again before commit! ;p ) rename file to avoid case conflict with directory 1. GLViewer - when sth. is selected don't move it to 0,0,0 position, but use its original position. This is done by delaying assignment to the next call of draw() - added extensive comments to the polyhedron code which will be used for collisions small snow update maybe spheres won't disappear anymore? a placeholder for drawing snow interactions changed guards to #pragma once postpone creating a 3d view, until a file is loaded. That should hopefully fix the problem with the primary view being empty. added AUTHORS file, for the purpose of updating file headers. This was copied from http://yade.wikia.com/wiki/Authors Why do I have the impression that I'm adding this file a second time? After all it's referred to in almost all source files "See file LICENSE for details.", right? sort people in alphabetical order sorry for bumping up revisions.... Snow, as it is finished by me. Further work on it will to be done by next post-doc researcher who wants money from EU for working on this :) fix miniwm3 / full wm3 compilation error I forgot to add those, sorry. (I wonder how it could stay unnoticed for so long ;) add option for horizontal or vertical wires in fibre reinforced concrete. 1. correct display of lattice rods on 0th iteration 2. on/off switch for using different stiffness for tension and compression in lattice 3. fixed a bug in BodyRedirectionVector related to deleting of bodies 4. two more lattice examples 5. few more safety checks in lattice code 1. NullGUI was still usin .gz while thay are not supported anymore 2. small lattice update sanitize stupid numers in QtGUI, eg. 0.1299999999999 becomes 0.13 small fix after isNew and isReal are removed I'll update the code to use new contact logic. Add an option to display DOFs and body IDs in the OpenGL window - Lattice uses DOFs now - old implementation is still accessible by setting a flag backward_compatible, so that hundreds of my old FileGenerator files will still work - some improvements to recording strains and forces - fix all my warnings - lattice generator improvements wrt concrete with steel fibres - some improvements wrt to generation of densely packed aggregates and steel fibres in the concrete structure - testing command "bzr commit --fixes lp:414868" - fixing bug 414868 Fix stupid error introduced when we have migrated to bex container 1. get snow code to run, albeit still buggy 2. why dynamic_casts stopped working everywhere, that's a mystery to me 3. interactions isNew doesn't exist anymore, snow code needs update for this Jerome Duriez (2): - Correction of the headers of two files "ContactLaw1Interaction" which were commited by me and not by Bruno - Some features of ContactLaw1Interaction were declared in ElasticContactInteraction. This was a mistake totally not planned (and the fact it was in svn also was not noticed) and is now corrected - End of the job by suppressing also the corresponding lines in .cpp... Luc Sibille (1): I added a new engine "ThreeDTriaxialEngine" to perform triaxial simulations with an independant control in stress or in strain in the three space directions respectively. In this goal, the TriaxialStressControlEngine has been sligtly modified to impose a different stress value or strainrate in each space direction. I hope I have broken nothing! Sergei Dorofeenko (28): Intermediate commit for SpheresFactory. Don't work yet. 1. New engine SpheresFactory 2. Add function engineByLabel to MetaBody 3. Add members to BroadInteractor. 1.First worked version of SpheresFactory engine. Example in examples/SpheresFactory 2.Added PersistentSAPCollider::probeBoundingVolume function (for using with SpheresFactory) 3.InteractionGeometryMetaEngine::explicitAction has been modified to remove artifical (as me seems) asserts. 4.python import_stl_geometry now return list of body ids instead only their number. 5.import stl geometry now allows import facets without BoundingVolume and InteractingGeometry. 6.Fixed dynamic_cast for BroadInteractor. 1. SpheresFactory allows now to create spheres in a convex volume. 2. SimulationController estimates total simulation time if stopAtIteration>0. 1. SimulationPlayer now allows to reload filters without reloading all simulation. 2. ColorizedVelocityFilter now allows to setup minValue,maxValue without reloading the filter. Fixed bug in RotationEngine: rotationAxis now normalized in action(), not in postProcessAttributes() ColorizedVelocityFilter counts a scale for each frame, without using previous min,max values. Class SimpleViscoelasticInteraction is renamed to ViscoelasticInteraction and is inherited from ElasticContactInteraction now. 1. Convert SimpleViscoelasticContactLaw to ConstitutiveLaw engine unit (Spheres_Viscoelastic_SimpleViscoelasticContactLaw). 2. SimulatonPlayer have now Display tab as one in SimulationController. 1. The experimental widget for drawing a color scale for colorized velocity filter. 2. Convenient python function utils.ColorizedVelocityFilter. Merging utils.*_v2 functions with their "first" analogs. Add two new filters: ColorizedTimeFilter and PythonRunnerFilter Migrate GravityEngines and ConstitutiveLaw to BEX_CONTAINER Convert STLImporterTest to ConstitutiveLaw (SimpleViscoelastic contact model) and NewtonsDampedLaw engine. Fix bug in serialization for ViscoelasticInteraction. Fix compilation error for InteractionHashMap Update contact logic for SpatialQuickSortCollider (hope, it's correct) Fix typo in PersistentSAPCollider 1. Remove zeroPoint 2. Add prefix ef2_ to Spheres_Viscoelastic_SimpleViscoelasticContactLaw Fix link errors fixed and updated some examples in keeping with last modifications Realise InsertionSortCollider::probeBoundingVolume() so SpheresFactory now works SpheresFactory now can create a spheres by python function. See scripts/test/SpheresFactory/model.py for example. Add export python module with VTKWriter class (very initial) to export data from YADE to VTK-based post-processing tools Added a c++ VTKRecorder (for spheres and facets) and new feature 'vtk' needed for it. Add colors rec to VTKRecorder Add default path for VTK Fix VTKRecorder headers Fix VTKRecorder headers Add VTKINCDIR to configure options for directories where to look for VTK headers Unknown (1): The isotropic compaction into the TraxialTest is activated: 1)I have deleted the translationspeed parameter (now the translation speed of walls is the strainRate parameter) 2)I have activated the porosity parameter in the TriaxailStressController.cpp file Vincent Richefeu (38): - add the soft mgpost for DEM simulation post-processing and visualization. - set menus in english mode A very quick guide for mgpost (usage and XML format) a forgotten file in mgpost/doc - add a new tool for sphere packing (still in dev) - some enhancement in mgpost interface - SpherePadder devel... SpherePadder devel... SpherePadder (devel: 5 steps ok) - SpherePadder update - add a basic 'user friend' interface for sphere packing generation. The module can of course be used without this interface. Speed enhacement for the SpherePadder algorithm. Add possibility to make PNG screenshots (using libpng) Add a DataRecoder that generate MGP files (only for Spheres for the moment). The MGP files can then be read with mgpost. - add a class based on CGAL for 3D triangulation. - Packing method enhanced (place 380000 spheres in less than 2 min) Fix some bugs and enhance the display of positive and negative forces Fix some bugs Begin devel. of densification process 2 files that were forgotten Simplify sphere shapes when if there number is increased Bug corrections (min and max radii) - overlap with spheres inserted by user -> bug fixed - Densification begins to work Increase the number of colors for the display of body's groupMasks from 6 to 10 We can now make the packing denser with a stop criterion based on the solid fraction or total number of spheres. Add BasicViscoelasticRelationships: an alternative to SimpleViscoelasticRelationships. It accounts for the effective mass of each contact and considere the parameter cn as a dimensionless ponderation of its critical value (computed for each contact). Parameter cs is set to zero for the moment. Add a boost.python wrapper (module packing) Add a function to SpherePadder_wrapper to pass the result back to python. As Vaclav suggested, it is a list of (x,y,z,R) tuples. (Complet integration into yade is not done yet) - Add 'HistoryRecorder' to save the sample and network state with a given time-step interval. - Correct a small bug due to the fact that a Clump cannot be casted into a GeometricalModel (and I don't know why!) Use the clump mass instead of clump-member mass for the determination of the effective mass. Ooops! There was a bug Add the function NormalRestitution2DampingRate. Add sphere reading for gdm-tk format Remove default linkage with libtiff (png is now prefered) Add an example of python script for SpherePadder Add GroupRelationData serialization (not used yet in *Relationships classes) This class serves to manage parameters between bodies depending on their groupMasks. Here is an example of 'command stream' in xml file to set the parameters: Just try to use GroupRelationData with BasicViscolelasticRelationships. Cast size_t to int in pow(double,int) function to avoid ambiguity for gcc (compilation failled on one of my computer) Some tiny modifications for Mac OS X compatibility (Does not compil yet). Some forgotten tasks replace gcc-3.3 by gcc in the Makefile.macosx of mgpost Václav Šmilauer (269): 1. Add linear templated interpolation function working on 2 lists, t and values 2. Add InterpolatingRotationEngine that changes rotation speed as given by value table 3. Lower info about starting python thread to debug message only. 1. Rename REGISTER_SERIALIZABLE(class,bool) to REGISTER_SERIALIZABLE_GENERIC, add two macros REGISTER_SERIALIZABLE and REGISTER_SERIALIZABLE_FUNDAMENTAL. Changed all uses accordingly. 2. Added REGISTER_CLASS_AND_BASE(class,base) which performs REGISTER_CLASS_NAME and REGISTER_BASE_CLASS_NAME at once. 3. Added REGISTER_ATTRIBUTES (not trailing 'S'), expanding to void registerAttributes(){...}, and REGISTER_ATTRIBUTES_WITH_BASE, which prepends call to baseClass::registerAttributes() (specified as the first argument). 1. Added Se3Interpolator engine, with scripts/test/Se3Interpolator.py 2. Added from math import * to PythonUI_rc.py so that math functions are readily accessible everywhere 3. Implemented stopStrain in UniaxialStrainControlledTest 4. other small fixes 1. Modify REGISTER_ATTRIBUTES macro so that it work with or without base class given. Change all headers in core to use that. It should help us if we ever make the transition to boost::serialization. 2. User REGISTER_CLASS_AND_BASE instead of REGISTER_CLASS_NAME and REGISTER_BASE_CLASS in core and at a few other places. 1. Removed cerr garbage from glviewer (use LOG_DEBUG if needed) 1. Fix warnings in GLViewer 2. Add (35.4%)-style counter if stopAtIteration is set to the OSD in gl view 3. Add utils.downCast to downcast object to its derived class and copying all attributes. 4. Add utils.SpherePWaveTimeStep to compute critical dt from a few parameters only 5. Add routine to compute overall kinetic energy to Shop (indirectly to utils as well) 6. Add routine to sum torque (momentum) with respect to an axis from forces and torques on given set of bodies. Fix typo (compilation error in debug mode) in Shop::kineticEnergy 1. Fix a bug in rotation ode in SpheresContactGeometry. Now the code gives the same results as almost-for-sure-correct FEM code regarding strain computation. 2. Add label attribute to StandAloneEngine (inherits from Engine) omg (code cleanup, no functional changes) Exit once the simulation finishes; do not rely on simulation exiting itself. 1. SpiralEngine that does both rotation and translation along the same axis 2. InterpolatingSpiralEngine for variable rotation+translation speed (replaces InterpolationgRotationEngine) 3. scripts/test-spiral.py for rudimentary functionality testing. 1. Un-disallow (bug) having all dimensions scalable in triaxial if particle radius is fixed. 2. Don't record anything by default to file in UniaxialStrainer (only useful for debugging) 1. Add a few functions for integrating piecewise-linear functions in new module yade.linterpolation 1. fix damage colorizer (removed dynamic_cast causing segfaults on non-spheres) 2. InterpolatingSpiralEngine now takes bool wrap rather than Real period. 3. qt.makePlayerVideo takse startWait argument, which waits for Backspace before starting (useful for setting up the view manually); postLoadHook gets run afterwards now. 1. Add algorithm for pushing sphere always to the same side when in contact with facet, even under extreme stress where the sphere geometrically passes to the other side. 2. Add bool SpheresContactGeometry::initContactOnPositiveFacetSide for that 3. Add test/demo script for that. 4. Re-enabled GLDrawSphere::glutUse (cosurgi?) 5. Renamed yade.runtime.args to argv, to be consistent with sys.argv 6. Allow passing sys.argv (arguments to scripts) through QtGUI as well 1. Fix clip plane orientation inversion as well as wheel normal movement. 2. Clip planes not displayed at all unless one of the clip planes is being manupulated. 1. Unbreak bad type (list) for sys.argv[0], broken recently 2. Color bodies by residual strength rather than damage parameter (smoother) 3. Make player messages (maybe) better formatted omg... 1. Make player reuse existing renderer if there is one (allows setting Draw_mask etc before running player) 2. Add initRun parameter to PeriodicEngine (false by default), to run at the very fist run. 1. Make BrefcomDamageColorizer run at 0th iteration as well 2. Make player set stopAtIter to last iteration in db so that percentage is displayed in the output. Fix previous commit, overlooked. sorry. 1. Number lines in parameter table for multijobs from 1 (as text editors do) 2. Print short summary at the end of job log (when started, when finished, exit status, duration, commandline) 1. Fix logic typo in QtGUI (warning when there shouldn't be and vice versa) 1. logarithmic strain for Brefcom. 2. change sphere-facet algorithm such that it works on corners. Maybe no longer necessary, but it doesn't hurt now. scripts/test-sphere-facet-corner.py as provided by sega demonstrates that. 3. "from __future__ import division" in python init such that 3/2==1.5 and 3//2=1 (is default since python 2.6) 4. Global GL lock to avoid crashes/freezes with GL accessed from multiple threads concurrently. scripts/gl-test.py (which used to crash) now works with one view; 5. Properly lock/unlock on all gl ops in QtGUI-python.cpp, like setting view direction, resizing etc. 6. Fix wrong assertion in SQLite player 7. add Omega().resetTime to reset iteration number, virtual and real time. 8. Changed utils.loadVars/saveVars code to be more reliable. 9. Added utils.aabbExtrema2d 1. Lock GL when closing view. Seems to fix crashes / delays at exit 1. Contact sphere-facet must be deleted by the constitutive law once created. Let me know if that is a problem -- I think it is the way it should be in the future everywhere, though. 2. Fixes for boost 1.37 (boost::thread not fully compatible) 3. utils.basidDEMEngines (not tested yet) 4. Fix warning in GroupRelationData 5. add march switch to scons ('native' is default, but not supported by older compilers) 6. Prepend default CXXFLAGS so that user-defined ones (like warning suppression) come after the default ones and override those. 1. Tentative fix for newer scons, which links empty files (dummy plugins) with plain gcc and doesn't find default c++ libs to link with. 1. File not needed. 1. Beautify no-debug code in main 2. SpiralEngine now saves angle it has turned so far 3. Remove failing assertion from IFacet2ISphere4SCG (why it fails? it was a (I->isReal&&I->interactionGeometry)||(!I->isReal&&!I->interactionGeometry)...) 4. Improve the utils.spiralproject function to specify period (moderately tested, but seems to work) 5. Adjust locks in GLViewer. Fixes my crashes on nvidia for both player and 3d view (didn't dare to try 2nd view). Next step will be to move this lock to Omega and lock it when loading/resetting simulation etc. 6. Change theora video quality to 63 (maximum) instead of medium 32 in utils.encodeVideoFromFrames. Makes the video about 3x bigger. Two BIG changes: 1. Add ef2_Spheres_Brefcom_BrefcomLaw, is faster (!) 2. Fix locking in Omega, we don't get crashes when loading simulation with the 3D view enabled anymore (please report any further crashes in OpenGLRenderingEngine, that should be fixed) 3. Automatic update of Doxygen configuration file 4. Dem3DofContactGeometry, which should be in near future parent of SpheresContactGeometry and other new geometries providing normal and shear strains (SphereFacetContactGeometry, in my mind) 5. Add convenience function to apply force to contact point between two particles. (remove no-warn for c++0x since that flag is not known to gcc-3.4) 1. Add possibility to change container types from python (for benchmarks) -- code not yet tested! 2. Possible optimization in PhysicalActionVectorVector, now disabled. collider mistake hotfix 1. Make triaxial stop if !autoCompressionActivation and already unloaded. If this breaks something, please let me know, we can do it otherwise, but, for Cundall's sake, can we keep the same behavior for at least 6 months?? 2. SpatialQuickSortCollider will not delete real contacts, even if bodies don't collide. 3. Remove redundant attribute registration from SpheresContactGeometry fix typo in collider 1. removed persistentInteraction from most places; they are still present in MetaBody, but not serialized any more, most importantly (avoids warnings) and not GL-drawn. 2. Plugin loading loginc completely changes, the underlying macro YADE_PLUGIN has still the same interface. We used __attribute__((constructor)), __attribute__((visibility("internal"))) and anonymous namespace magic to get there. This will make it possible to put multiple plugins to a single shared library without touching the source, only fiddling with SConscript's. 3. PythonUI is interactive by default (it was not?? weird.) 1. Add new non-generic Bex container. For non-optimized build, it increased the speed by about 11% (9:35 vs. 8:49) as compared to PhysicalActionVectorVector. Users willling to go with the slower PhysicalActionContainer can compile with scons CXXFLAGS=-DNO_BEX now to get the old behavior. 1. Adapted TriaxialTest and ElasticContactLaw to BexContainer (switchable at compile-time). 2. Adapter GlobalStiffnessTimeStepper to BexContainer (GlobalStiffnessCounter code put to a function inside this one, hence that engine is not needed anymore). 3. Adapted Shop::Bex to BexContainer (probably not needed anymore?) 4. Exception is thrown in PhysicalActionVectorVector is used && built with BEX_CONTAINER (would most likely crash anyway). 5. transientInteractions and persistentInteractions are only references to MetaBody::interactions now. Removed extra loops in InteractionPhysicsMetaEngine and InteractionGeometryMetaEngine. 6. Remove including qglviewer into miniWm3, as it breaks compilation if using miniWm3 separately from yade (for testing purposes). 1. Define Janek's functions to convert from/to QGLViewer vector where they are used. 1. Add BexContainer (same interface, but separate implementation) in openMP flavor. 2. openmp=0 is the default for scons now (for some time), since openMP BexContainer gives about 7% slowdown and thus is not useful before at least some engines are parallelized as well. 3. Removed getting references to Force/Torque from BexContainer, since that breaks concurrency. Instead, addForce/addTorque must be used. If you need to read back with getForce/getTorque, you have to call sync() (expensive!!) beforehand, which will compute summary values for all bodies; if you forget, exception will be thrown. Note that sync() is invalidated at next write operation. 4. Adapted a few constitutive laws and other engines for the changes mentiones here. 1. Add time-profiling classes. See http://yade.wikia.com/wiki/Speed_profiling_using_TimingInfo_and_TimingDeltas_classes for details. It is accessible from python as well. 2. SimulationController no longer asks for output format. It just uses XML, the only available one. 3. Engine, EngineUnit have timingInfo member and pointer to optional timingDeltas. Timing is disabled by default, but it may still have some overhead in the order <1% perhaps (will test) 1. Forgotten file with yade.timing.stats() that pretty-prints some numbers. 1. Move all container to core, remove linking with them since it is not necessary 2. Fix things so that everything compiles (at least) both with and without BexContainer 3. Parallelize ConstitutiveLawDispatcher, InteracionPhysicsMetaEngine and InteractionGeometryMetaEngine with openMP if enabled on command-line 4. Make InteractionVecMap default container for interactions; necessary for parallel code. 1. New InteractionDispatchers class that has one common loop for InteractionGeometryMetaEngine, InteractionPhysicsMetaEngine and ConstitutiveLawDispatcher. It can be used from python like this: 1. Adapt ForceEngine to BexContainer 2. Add BEX_CONTAINER and YADE_OPENMP flags to be reported by -h. 1. Add two more forgotten headers. They are not used anywhere, though, and will be removed later. 1. Enhancement of utils.box from Marco. 1. Make Shop load 4/5/6 columns text files for spheres (and skip 5-cols) 2. Change evaluation of args in readParamsFromTable to be able to pass strings better (single quotes) and consistently 3. Parameters passed as env. vars by yade-multi are prefixed with ! 4. fix typo in timing for resetting timers. 1. Make the yade-multi scheduler multithread-aware. 2. Add the collider performance test as example 3. added utils.replaceCollider(anotherCollider) for convenience 4. added Omega().isChildClassOf(classNameChild,classNameParent) 1. Fix collider-perf (TriaxialTest has hardcoded dt=0.001 !!!!), set timestep by hand at first 2. Fix Shop:: loading spheres from file (regression from yesterday) 3. Add functorCache to Interaction and to InteractionDispatchers. About 5% improvement, but not yet finished. 4. Add DynLibDispatcher::getFunctor2D 5. Add BexContainer::sync() to other places in triaxial (thrown otherwise) 1. Fix old rename of runtime.args to runtime.argv in default-test.py. Should run OK now. 1. Fix for the compile error in snow for OrthogonalPlaneFit3. It still misses pkg/snow/Engine/ElawSnowLayersDeformation.cpp in the repo. Compile with exclude=...,snow as workaround. 1. Rename BroadInteractor to Collider. 2. Move some part of the contact logic to this common base class. The Collider::handleExistingInteraction is subject to discussion and not yet used. 1. Small fix in the collider code. 1. Remove SAPCollider completely 2. Make yade log4cxx 0.10 compatibile 3. Other minor fixes 1. Fix linking of realtime-rigidbody WRT PersistentSAPCollider 1. change default xiShear to 0 instead of NaN (is not used anyway) 2. Fix linking of mass-spring in cliean build 1. Fix #include's that include files not in the current source directory via "". 2. Add scripts/linkdeps.py that can automatically determine what plugin links to what other plugins, which can be once used for configurable monolithic/pluggable builds etc. 1. Fix crashed in TriaxialTest introduced by me a few days ago (referencing engine before being created) 2. Add simple (working?) memory usage query to utils and collider perf test. 1. Fixed collider performance benchmark 2. Added script to generate graph from log files (like http://yade.wikia.com/wiki/Colliders_performace) 1. Added SpheresContactGeometry::updateShearForce, will be used (not activated though yet) by ElasticContactLaw and other. hotfix related to last commit 1. Initial checkout for the DemXDofGeom classes (will contain the hasShear code from SpheresContactGeometry and more eventually). 1. Preliminary version for sphere-facet collisions with total formulation (moderately tested). 2. Same for sphere-sphere collision. 3. test script for facet-sphere collision geometry. 1. Use local foreach.hpp if not found in the system 2. Use local file defining boost::python::len for boost<1.34 1. remove explicit linkage to boost_python in gui/SConscript Forgotten empty file. 1. Remove logger from ClassFactory 2. Cleanup logging stuff in main (use constructor function, among other) 3. Cleanup system exit from python in PythonUI_rc.py 4. Add SpheresContactGeometry::updateShear that can be optionally used with ElasticContactLaw to update shear displacement instead of updating shearForce. triax-identical-results.py show quite large difference between both implementations, but I am not able to tell which one is correct. scripts/test/shear.py shown almost no difference for 2-sphere scenarios, modulo differences at 15th decimal place or so. 5. Remove debug output from BexContainer 6. Remove warning about meniscus data from CapillaryCohesiveLaw (warning is given in postProcessAttributes now, i.e. iff the class is actually used) 7. Add logger to snow. 8. Removed shear computation code in ElasticContactLaw, use SpheresContactGeometry::updateShearForce. 9. Fix scons deprecation warnings 1. Add functions to augment forces/torques in BexContainer. 1. Remove some garbage from SpheresContactGeometry 2. Verify that SCG_SHEAR doesn't alter behavior if ElasticContactLaw::useShear is false 3. Implement SCG_SHEAR for sphere-box interactions 4. sphere-box interactions no longer call goReverse, but swap interaction order instead, as facets do. 5. Fix triax-idnetical-results.py to reload generated initial config to avoid rounding issues of sphere coords in text file. Minifix: link core with librt (clock_gettime) for platforms that require it 1. Add rate-dependent damage to normal and viscoplasticity to shear components of Brefcom (not yet tested, just compiles); other cleanups there. 1. add addF and addT to legacy ActionContainer Add label attribute to engine units. Python wrapper updated. 1. Rate-dependent fixes (more to come) in brefcom. 1. Remove MetaBody::physicalActions if compiling with BEX_CONTAINER (default) 2. Make everything work with both BexContainer and deprecated PhysicalActionContainer 3. Change PhysicalActionDamper, PhysicalActionDamperUnit, PhysicalActionApplier and PhysicalActionApplierUnit to dispatch only according to PhysicalParameters (not PhysicalAction anymore). That means that only one unit will be called for each body and that NewtonsMomentumLaw has to do the job of NewtonsForceLaw as well (same for CundallNonViscousMomentumDamping). 4. Fix (finally) defaultDt computation in TriaxialTest (forgotten assignment to defaultDt in the GlobalStiffnessTimeStepper) 5. Fix (finally?) timestep manipulation form within the QtGUI. If reloading the same file, timestep settings will be preserved, otherwise those saved in the XML are used. 1. Create ef2_Spheres_Elastic_ElasticLaw that has the ElasticContactLaw algorithm 2. ElasticContactLaw now merely calls ef2_Spheres_Elastic_ElasticLaw. 3. TriaxialTest::parallel controls whether to use InteractionDispatchers or not. 4. Added examples/triax-perf to show the impact of that. Also see http://yade.wikia.com/wiki/Triaxial_Test_Parallel 5. Fix a few compilation issues. 6. Flush stdout before exiting from main, so that in case of crash upon exit (log4cxx?) we have all messages output. 1. Hotfix for 2 issues with ElasticContactLaw 1. Remove all traces of physical actions: 1. Remove Shop::Bex (no longer necessary) 2. Add InterpolatingDirectedForceEngine for Roger (not yet tested!) 1. Fix linking of ForceEngine with interpolation 2. Random rate-dependent things. 3. Typo in BexContainer docs 1. A few fixes in Brefcom. 2. Make python wrapper correctly handle long long attributes. 3. Added test script for InterpolatingDirectedForceEngine 1. MAJOR change in python syntax (backwards-compatible, though): through small bit of very dirty code, classes can be instantiated as python objects with keyowrd arguments, i.e. instead of 1. cleanup UniaxialStrainer code, add vars to control length of the acceleration phase and the ability to set absolute speed 2. EngineUnits within InteractionDispatchers can be labeled and accessed from python now. 3. A few minor things in the plot module 1. Remove cruft from brefcom, a few fixes there 2. Add gnuplot grid to gnuplot by default 3. Fix speed in USCT 4. Fix InteractionDispatchers traversal in pyOmega 1. Fix weird python problem if LIBS is not defined by distuils (?) 1. Add working version of facet adjacency finder (to get their mutual angle at edge) 1. Parallelize initial bound filling in PersistentSAPCollider. Gives almost 3x speedup for the first step. 2. Fix missing headers so that it compiles with g++-4.4 (in QGLViewer) 3. FacetTopologyAnalyzer works (not tested extensively), questions of where to put topology data for InteractingFacet (will be raised on yade-dev) 4. Test script for FacetTopologyAnalyzer. 1. Remove leftover var in PersistentSAPCollider 1. Add preliminary gnuplot plots merging facility to yade-multi 2. Fix yade.log module so that it also works without log4cxx (and gives 1 warning) 3. Switch to new syntax in scripts/plot.py 1. Fix race condition in brefcom (omp critical section) 2. Remove -ffast-math from optimized build, since NaNs do not work properly (e.g. isnan(NaN)==false!) 1. Fix bug in FacetTopologyAnalyzer algorithm 2. Add angle info to InteractingFacet (not yet used) 3. Add triangulated sphere test for FacetTopoloyAnalyzer to facet-topo.py 4. Fixes in rate-dep in brefcom 1. Add approximate viscosity equations to brefcom (not working) 2. Fixes in yade-multi 1. Finish implementation of Dof3DofGeom_FacetSphere (with plastic slip as well) 2. Adapt Brefcom optionally to Dem3DofGeom 3. Add d0fixup to SpheresContactGeometry to get correct normal strain in sphere-facet contact with fictive sphere (zero for sphere-sphere) 4. Finish FacetTopologyAnalyzer; angle usage in Dem4DofGeom_FacetSphere not yet tested. 1. Remove cruft from UniaxialStrainer, add the ability to set initial velocities to suppress inertia at the simulation beginning 2. Jobs in yade-multi can be automatically named after variables suffixed with ! 3. yade-multi now shows and updates automatically some statistic on localhost:9080 using http 1. Finish Dem3Dof for both spheres and facets, migrate Brefcom. Removing stuff from SpheresContactGeometry is on schedule, once thorough testing is finished. 2. Fix attribute inheritance for a few engine units 3. Add ef2_Dem3Dof_Elastic_ElasticLaw, which works like ElasticContactLaw (but faster ;-) ) 4. Remove BrefcomLaw, keep just the constitutive law as functor. Adapt sample generators for that. 5. Add hydrostatic confinement to brefcom (isoPrestress), remove viscApprox. 6. Add interface for querying and setting (doesn't work yet) number of openMP threads 7. Job description is taken from columns suffixed with ! or by combining all parameters 8. Add function for plotting yeild surface of brefcom to the eudoxos module 9. Oofem export now uses reference (starting) positions of particles as it should 10. Add "-" before profile name when generating default variant suffix in scons (which is expected) Fix comp9ilation error for Janek. 1. Add python wrapper for clumps, see scripts/test/clump.py on how to use it (O.bodies.appendClumped) 2. Fix clump support in NewtonsDampedLaw (damping is quite wrong, it seems...; Bruno, any ideas on that?) 3. Move clump to pkg-dem (even though it depends on extra/Shop) 4. Remove absolute shear computation from SpheresContactGeometry, as Dem3Dof works fine now and Brefcom will not use SCG anymore; adapt a few other classes to that; remove ElasticContactLaw2 that was using it. 5. 1. Warn in --version if -ffast-math was used 1. Fixing bug in TriaxialCompressionEngine (introduced by luc apparently). Big changes: Handle boundingVolume-less bodies gracefully in InsertionSortCollider 1. Add utils.regularSphereOrthoPack by Anoton Forgotten showcase. 1. Add InteractionContainer::requestErase to hint colliders that wouldn't otherwise see !isReal interactions. 2. Use this logic in InsertionSortCollider, PersistentSAPCollider, SpatialQuickSortCollider (noop in the last one) 3. Add InsertionSortCollider::sortThenCollide to make it behave as non-persistent collider (for debugging only?) 4. Add Interaction::reset() that has common initialization code. 5. Assign zero inertia to utils.facet (better than uninitialized binary garbage) 6. Fix contact logic in Brefcom, finally I get the same results with InsertionSortCollider as with SpatialQuickSortCollider on large simulation (more fixes to come) 7. Do not install pkg-config; fixes compilation error reported by the build bot. 1. Use bitfield instead of bools in InsertsionSortCollider 2. Revert accidental changes in insertion-sort-collider.py 3. Add function for using renamed classes from python with warning Getting rid of the Brefcom name, classes renamed as follows (moving files around will follow) 1. Add logic to scons to delete files that will not be installed in this run but are on-disk. This should make it unnecessary to remove files by hand. In the same way, disable implicit target cache which was making problems if files shuffle around. 2. Remove Brefcom* files, move to pkg/dem/ConcretePM.?pp. (not sure what other place, since it contains Engines, DataClasses etc in one file that I would refuse to break.). 3. Add detailed comments on ConcretePM to the ConcretePM.hpp 4. Remove extra/usct, moveUniaxialStrainer and put to pkg/dem; move SimpleScene from extra to pkg/dem 5. Remove core/yadeExceptions.* 6. Remove Preferences::dynLibDirectores and Preferences::version. Omega now recursively searches the lib directory for plugins and loads them. preferences.xml has only defaultGUILibName now. Finally. Remove baseDirs and other cruft from DynLibManager. 2 forgotten files (separated from UniaxialStrainer before) 1. Fix hopefully last relict of pkg-config 2. Degrade deleting non-existent interaction from fatal erro to warning only (not sure where that can come from, though.) Solve the cause of nonexistent interactions being deleted (after O.interactions.clear()). Make warning fatal error again. 1. Remove Interaction::isNew, Interaction::isReal. Add Interaction::isReal() which test for presence of _both_ interactionGeometry and interactionPhysics and Interaction::isFresh() which checks the interaction creation timestamp against current iteration. Updated all code for that. Please verify that your stuff works, it is possible I made some mistakes! 2. All code saying isReal=false replaced by interaction->requestErase(id1,id2) 3. make requestErase thread-safe 4. Add interace for traversing interactions pending erase (template) 5. Add TriaxialTest::noFiles and TriaxialCompressionEngine::noFiles to not generate any files (default: off) 6. Add predicate utils.ptInAABB(p,minPt,maxPt) 7. utils.spheresToFile return number of spheres written (instead of None) 8. Add examples/concrete/pack to generate packing; other stuff in concrete will follow, with simplified version of the cpm model in ConcretePM. 1. Syntax fixes in ConcretePM model 2. Interactions will be deleted also if the ye4xist, but geometry functor fails (returns false) 3. Add utils.approxSectionArea, utils.spheresFromFileUnixial (get some parameters useful for uniaxial tests from the sphere packing, such as cross-section, principal axis, etc) 4. Add lib/computational-geometry/Hull2d.hpp for computing convex hull in 2d using Graham scan algorithm (I know it is in CGAL already, but I don't want to depend on that commercial stuff) 5. Remove some unused stuff from utils. 1. Add epsNPl to Cpm 2. Replace vector by Vector2 and make it properly serializable (should be faster) 3. Add function to compute forces on interactions crossing plane. 4. Replace custom xcombine function by itertools.product in grid sphere packing Fixes and cleanups in the CPM code. Add new 'feature' to scons called openGL, which is on by default. Qt3 GUI will not be built without openGL. YADE_OPENGL is defined in openGL-enabled builds. Sconscript files adapted to skip GL plugins or GL parts of plugins. 1. Fix missing libs for linkage 2. Fix a few typos 1. Don't link core with glut (is there a reason for that?) Do not include X11 headers if without opengl 1. Move all python modules to PREFIX/lib/yadeSUFFIX/py/yade instead of */gui/yade 2. Add pack module for generating regular packings (thanks to Anton Gladky for his ideas & code) 3. Add _packPredicates module for solid inclusion testing. 4. Update regular-sphere-pack.py to test new packing features. Remove leftover reularSpheresOrthoPack from utils. Is in yade.pack now. Add pack.inHyperboloid predicate. Should be useful for cylindric specimen in uniaxial tension, but with the middle part having smaller diameter. Added to the test script as well. 1. Fix (hopefully) clump damping 2. Remove unused code from InsertionSortCollider 3. Make SimulationController NOT change timestep (rounding) by just opening it (!!) 4. Fix paths in PythonUI and lib/SConscript for $PREFIX/yade$SUFFIX/py/yade instead of $PREFIX/yade$SUFFIX/gui/yade 1. Add resetting interactionGeometry and interactionPhysics to Interaction::reset(), as it should be... 2. Small things in CPM 3. Add GaussAverage.data method to python (for debugging and plotting) Include Python.h in PythonRunnerFilter. Build this plugin only with EMBED_PYTHON. A few changes to allow run primarily python with qt3 on the top of that (open, close etc as needed). This paves way to running main yade process in python ;-) (just kidding, for now) 1. Handle groupMasks of bodies in InteractionDispatchers directly (not in geometry functors etc). 2. Fixes for qt3 started from within python 3. Add Body().dynamic flag to the wrapper, instead of using Body()['isDynamic'] all the time. Anton's new things: axis-aligned ellipsoid predicate and fix in pack.regularHexa. 1. Add external euclid.py module (suggested by Anton, thanks!) to our installation 1. Propose *.sqlite ending for saved simulations instead of * in the player Fix error-tolerant law which uses no interactionPhysics: add void Interaction physics in the geometry functor if returning true, to make Interaction::isReal return true. (Shouldn't we remove ErrorTolerant...? Does it have some serious use?) 1. Remove constructor priority for log4cxx to avoid syntax unsupported by gcc<=4.0. 2. Remove some commented cruft from FrictionLessElasticContactLaw 1. Add boolean operators on solid predicates (&,|,^,-), shown in scripts/test/regular-sphere-pack.py 2. Add notch predicate (not functional yet) 3. Add utils.alignedFacetBox to create hollow box from facets. Shown in new file scripts/test/facet-box.py 1. Update simulation filename in controller periodically 2. Fix notInNotch predicate, use infinite AABB for unbounded solids and raise exception in the packing generator if such is encountered. 3. Add notch to the head of regular-sphere-pack.py guy 1. Add checks for GTS (optional) 2. Add pyGTS to our tree (built only if needed), since no packages are available. 1. Fix corner case in InsertionSortCollider related to instability of std::sort (quicksort) and bodies having the same upper and lower bounds (facet in the xy plane, for example). Thanks to Anton for providing the crasher. Fix feature macro definitions (typos) 1. Add GTS surface predicate 2. Add example of sphere horse falling onto facet horse in scripts/test/gts.py (try it!) 3. Predicate difference now takes padding in the inverse sense for the second predicate (logical) 4. Attempt to enable lightning on facets (no effect :-( ) 1. Add coarsened horse geometry for the example gts.py 1. Rename ef2_Dem3Dof_Elastic_ElasticLaw to Law2_Dem3Dof_Elastic_Elastic (in scripts as well) 1. Make virtual methods on pack.Predicate work in python (inGtsSurface, for instance). Add scripts/test/gts-operators.py to show that. 2. Move scripts/test/gts.py to gts-horse.py 3. Add a few functions to pack for constructing triangulated revolution surfaces from meridians. 1. Padding support (though slow and not very precise) for pack.inGtsSurface 1. Fix crashers in InteractingMyTetrahedron* classes (introduced by me when updating interaction logic) 2. Reimplement inGtsSurface in c/c++ which makes it faster due to pygts interface limitations (addressed on pygts mailing list meanwhile), but is dirty programming. 3. Set CCOMSTR to correspond to CXXCOMSTR in SConstruct (to show nice line when compiling c source file, for pygts) 4. Set good-looking timestep in the TetrahedronsTest generator 1. Add triaxial packing generator, with optional memoization to sqlite database to avoid generating packing if there are suitable ones (same parameters/scalable to same parameters) available. Sample script will be provided soon. This allows for filling arbitrary (gts-defined, for example) volumes with irregular, high-quality sphere packing. 2. Add python-wrapped class SpherePack for manipulating sphere packings (loading, saving, spatial transformations, iterating) 3. Add shared_ptr Omega::rootBodyAnother to be used with Omega().switchWorld() 4. Move some stuff from gui/py to lib/py, which is more logical hopefully 5. If negative value is specified for TriaxialTest::thickness, wall thickness will correspond to the mean sphere radius (avoids spheres going through the walls during compaction for some radii) 1. Add automatic converter from python::tuple to Vector3r and vice versa. Wrapped functions can take/return Vector3r directly instead of manual conversion. Clean code that had to do that. Let me know if something breaks, the changes should not touch any python code. 2. Some preliminary work on wrapping Vector+Quaternion classes in python via py++ (not enabled, just the files are there) 3. Add forgotten _packSphere.cpp, thanks to Vincent for reporting. 1. Add scripts/test/gts-traix-pack.py to demonstrate triaxial packing generator. Remove useless include in GlobalStiffnessTimeStepper. 1. Fix STUPID (my fault, sorry!!) mistake in setting Interaction::iterMadeReal, which cause ElasticContactLaw to function wrong. It concerns a few other contact laws calling isFresh, which would return true as well. Sorry again. 1. Shuffling stuff around, moving most python things to py/ 2. Wrapping Vector3r, Quaternionr and Vector2r in python directly, see scripts/wm3-wrap.py 3. Change the way utils.box, utils.sphere and utils.facet works: arguments to physical parameters are passed as keywords, not as dictionary now (update your old code by passing **dict instead of dict as the last argument) 4. Fix a bug with useShear, now getting almost identical results for triaxial again. 5. Preliminary Triaxial::facetWalls, to be able to used Dem3DofGeom with Triaxial. (not fully functional yet) 6. TriaxialStressController::thickness is -1 by default now (get thickness from box width), as 0 is real value for Facets. Doesn't change beahvior in any way. 1. Update triax-perf.py 2. Add Omega().tmpToString returning string of :memory: saved simluation 1. add yade.plot.resetData(), which unlike reset() resets just data 2. Add scripts/test/compare-identical.py to run 2 simulations at the same time and check at every step (configurable) if they differ or not. 3. Add active=active line to GlobalStiffnessTimeStepper, which for some reason causes two same simulations to be the same. Any explanation someone?? 1. Add forgotten py/SConscript (sorry!) 1. Fix absolute path generated by py++ 1. Dump XML in ConcretePM in case of problems (nan's) 2. InteractingFacet::postProcessAttributes gives FATAL message if there are coincident vertices 3. Update triax-perf, add script and openoffice sheet to make table 4. InsertionSortCollider is used in TriaxialTest always (not only with fast) now. 1. Change prototype of Engine::isActivated() to Engine::isActivate(MetaBody*) 2. Add maxVelocitySq to NewtonsDampedLaw for analysis purposes 3. Add BoundingVolumeMetaEngine::sweepTime and ::sweepDist to sweep bounding volume by given predicted motion over time or by given absolute distance. 4. Add strided run support to InsertionSortCollider (using sweeping) 5. Add scripts/test/collider-stride-triax.py to play with the strategies of sweeping based on strides 6. Add O.interactions.countReal() 1. Fix collider-stride.py to match changed parameters. (it is quite useless now, anyway 1. Make NewtonsDampedLaw compute maxVlocitySq at every step anew. 2. Make InsertionSortCollider find NewtonsDampedLaw and get max speed from there. Together with InsertionSortColider::sweepLength, this makes stride adapted automatically to maxVelocitySq; this will described at http://yade.wikia.com/wiki/Insertion_Sort_Collider_Stride. 3. Update test script for that. 1. Handle clumps in a better way in NewtonsDampedLaw (only if asked by the clump itself) -- future parallelization work. (related to bug #398086) 2. InsertionSortCollider now knows when to run based on maximum distance the fastest body could have travelled. (It is actually the physical meaning of sweepDistance). Handle varying maxVelocitySq in a constitent way. 3. Add InteractionContainer::serializeSorted to sort interactions by id1 and id2 before serializing (useful for comparing XML files). Added Interaction::operator< to compare 2 interactions in that way. 4. Fix openMP strategy in InteractionDispatchers to "guided" (as per https://blueprints.launchpad.net/yade/+spec/omp-schedule-strategy) 1. Remove doc/removed, it is in SVN history 2. Tune debian scripts to install docs+examples+scripts instead of headers (targets https://bugs.launchpad.net/yade/+bug/398174, but not yet finished) 3. Add depends on python-numpy instead of python-scientific packages for debian 4. Add ../py directory to doxygen (targets bug #398190) 1. Change NewtonsDampedLaw to a StandAloneEngine 2. add .updateExistingKeys (perhaps should be updateExistingAttributes) to all python-wrapped yade objects, which will change only existing attributes from given dictionary 3. Adapt STLImporterTest to new nicer syntax. 1. Add virtual destructors to all EngineUnits so that they can by dynamic_cast'ed to. This fixes possible crash from python, bug #398255 2. Rename updateExistingKeys to updateExistingAttrs, add updateAttrs. 3. Add forgotten script to demonstrate predicate operators 4. The default debug level is -ggdb2 (instead of -ggdb3). Libs are about 60% in size and still give the same debug information. 1. Rename scons option arcs to PGO (profile=guided optimizations) 2. Install only binaries by default (no docs, must be requested from cmd line via path) 1. Various fixes related to generating debian packages 1. Fix EngineUnits linkage for SImpleViscoelasticRelationships 1. Add velocity binning for collision optimization: requires cooperation of NewtonsDampedLaw, BoundingVolumeMetaEngine and InsertionSortCollider. 2. Remove time-based sweeping from BoundingVOlumeMetaEngine. 3. QtGUI doesn't show annoying warning when run from python session anymore. 1. Parallelized NewtonsDampedLaw (scales very well) 2. Parallelized VelocityBins 3. Add const modifier to BodyContainer::size 4. Add virtual dtors to PhysicalAction{Damper,Applier}Unit (for dynamic_cast'ing) 1. Move NewtonsDampedLaw to StandAloneEngine directory 2. Add omp critical to ConcretePM 3. Remove SphericalDEMSimulator 1. Add sweepLength to TriaxialTest if fast==true 2. Remove euclid from the pack module, since we use wrapped wm3 now. 3. Add _packObb.cpp to compute minimal oriented bounding box on cloud of points 4. inGtsSurface predicate with triaxialPack will work on the minimum OBB 5. Add check for engine to prevent crash as per https://bugs.launchpad.net/yade/+bug/399810 6. Enhance the pack.inSpace predicate to work as expected. Custom center can be given to the ctor 1. Fix non-tuple in graph specification 2. Allow plotting agains the y2 axis, after a special parameter '|||'. 1. Remove mutex from requestErase (should be verified to compile on non-openMP machine!) 2. Add mutex to PhysicalParameters, so that body state can be locked while updating from the interaction loop. 3. Add linking with EngineUnits to Tetra (thans Emanuelle) why..?? 1. Adapt simple-scene-graph.py to new instance syntax 2. Add support to y2axis to saved gnuplot plots 3. Change interface for yade.plot.addData to take **kw instead of straight dict (still backwards-compatible) 1. Do not use REGISTER_ATTRIBUTE anymore, use REGISTER_ATTRIBUTES instead everywhere. This generates (in addition to serialization code) also some python code and makes python independent on the yade serialization framework (Janek, if you want to try boost::serialization, I have no objections now). 1. Remove garbage from doc 2. Instruct getopt to not shuffle command-line arguments, so that their order is preserved 3. Add epydoc documentation for all python classes and functions 1. Fix linking logic for _packPredicates (fixes https://bugs.launchpad.net/yade/+bug/401029) 2. Make gts-horse coarse original horse automatically Fix small bug in Velocity bins related to max sweep length (should improve performance as well). Still some incorrect things WRT bboxes, it seems. 1. Fix mathematical error with VelocityBins, verified by new script scripts/test/collider-sweep-simple.py to be correct 2. Add py/ directory to skipped dirs for linkdeps.py so that it works on current trunk 1. BIG CHANGE: pkg/ no longer have SConscript files but are scanned at startup for plugins. See http://yade.wikia.com/wiki/ImplicitBuilds for some details. Let me know if there are some problems. add boost-serialization and boost-program-options to debian. 1. Move STILimport to pkg/dem, as it depends on dem 2. update RockPM with the new-old version (probably svn didn't update the moved version? what a deal. We should definitely move to bzr...) 1. Fix unreported version for the debian build 1. Fix snow compilation by #including Voxel/* files in SnowVoxeslLoader and noc compiling them directly (sorry) 2. STLReader is a pure-header import lib now, tolerance is passed as regular data member. 3. Fix all warnings in ConcretePM. 4. Merge lib/yade-* into lib/yade-support. 1. Fix monolithic compilation even with fem. All modules can now be compiled that way. fix compilation? Fix TetraTestGen crasher (functionally still brokne, though) Remove SDECLinkGeometry, replaced by SpheresContactGeometry at a few places. Not checked for functionality. This avoid many warnings. 1. Fix linking with full Wm3 library 2. Automatically add /usr/include/wm3 to CPPPATH if useMiniWm3=False 1. Hopefully fix https://bugs.launchpad.net/yade/+bug/402098 2. Fast in TriaxialTest uses nBins==5 and binCoeff==2 for VelocityBins 1. Fix SnapshotEngine and simple-scene-video.py to run. It doesn't work, though, because of the makeCurrent() failed error (probably getting snapshot from another thread...) 1. Add GLViewer::nextFrameSnapshotFilename to save frame in postDraw 2. Adjust SnapshotEngine to take advantage of that. This should solve https://bugs.launchpad.net/yade/+bug/396023 3. utils.encodeVideoFromFrames can take list of files (instead of just wildcard). Backwards-compatible. 4. scons doc skips debian/ directory (would mess up if deb package was build) 1. Fix for https://bugs.launchpad.net/yade/+bug/406343 (at least for me) 1. Add GenericSpheresContactClass to unit radius1, radius2 and normal between SpheresContactGeometry and Dem3DofGeom (to make it work with GlobalSitffnessTimeStepper). Partially solves https://bugs.launchpad.net/yade/+bug/399963 1. Add docstrings to all wrapper.Omega() methods 2. Fix a few warnings. Fix has_map warning and one more in FlowEngine. 1. Add infrastructure for getting temporary filenames in a secure manner to Omega 2. move selectedBOyd to MetaBody; initialize it properly to avoid possible crash. Remove leftover SConscripts. 1. Change syntax of YADE_PLUGIN("Plugin1","Plugin2") to YADE_PLUGIN((Plugin1)(Plugin2)) (replaced everywhere) 2. Add some perliminary and not-enabled by default support for boost::serialization (see ClassFactory.hpp) 3. Add boost::serialization code for Se3r, Vector2r, Vector2, Quaternionr 4. Do not reset BexContainer's moves and rotations uselessly (i.e. if not used) 5. Use Omega's mutex in SimulationController to prevent loading-related crashes 1. Don't use __COUNTER__ to generate plugin registration function names, as they wre added in gcc 4.3 Use the name of the first plugin instead. 2. Dem3Dof_SphereSphere takes touching distance as equilibrium if distanceFactor is <=1. The initial position otherwise (was the default everytime until now) 1. Generate proxy constructors to all classes deriving only from Serializable in python (like GLDrawBox and similar). 2. Fix scripts to have the new syntax everywhere, since e.g. EngineUnit is just abstract class that cannot be instantiated from python. 3. Add check fro valid data before saving plot to gnuplot; raise comprehensible exception if there is nothing to save. 4. Remove leftover scripts/pch.py 1. Add an example of uniaxial tension-compression test for concrete (ready long time ago...) 1. All different yade types are real python types now. They have their properties defined at startup. One can say O.bodies[0].shape.diffuseColor instead of O.bodies[0].shape['diffuseColor'] now. It also works much better with ipython tab-completion. It is not sure whether we will be able ever to deprecate the old object['attribute'] syntax. 2. Wrap ParallelEngine in python, and finish Dispatcher wraps as well. 1. Reset interaction radius after the initial contacts have been created. Fix FileGenerator wrapper in PythonUI_rc.py (has different name in c++ (FileGenerator) and python (Preprocessor). 1. Rename Preprocessor in python to FileGenerator, to be consistent with c++. Preprocessor is never used directly, anyway. The old deprecated name put in the deprecation table. 2. Manage crash at exit in defaut-test gracefully 3. Fix serialization of pairs (overlooked replace) 1. Initial attempt at regression tests (only the python wrapper so far); run scripts/regression-tests.py 2. Fix a few bugs discovered that way :-) 1. Replace PersistentSAPCollider and DistantPersistentSAPCollider by InsertionSortCollider everywhere. The two are still in the sources, but are not compiled. Will be removed in near future. Added to deprecated table in python so that old scripts still work. 2. Add some more regression tests for the python wrapper. 3. Fix timestep in HangingCloth (it does some weird things, can someone check it or just remove mass-spring from the tree?) 1. Be more tolerant to specifying invalid GUI at the command line 2. Throw exception about unopenable $DISPLAY rather than letting QApplication abort() 1. Fix https://bugs.launchpad.net/bugs/409254 https://bugs.launchpad.net/yade/+bug/409254 take 2 Maybe fix https://bugs.launchpad.net/yade/+bug/409365 1. Fix (?) https://bugs.launchpad.net/yade/+bug/409254 by moving ClumpTestGen out of Clump.hpp 2. Avoid warnings in STL import code (return values _should_ be checked, though!) 3. Avoid warning in yade.cpp in optimized build 1. Infrastructure for periodic collision handling. Not at all functional at this moment, don't even try to use. 1. Beta version of periodic boundary conditions (try scripts/test/periodic-simple.py 1. Assign new function O.exitNoBacktrace to sys.exit. Fixes partially https://bugs.launchpad.net/yade/+bug/410250 2. Add some more comments to periodic stuff. 1. Script that shrinks the periodic cell progressively (reveals bugs in the collider, though ;-) ) 2. Add function to compute total force in volume from interactions to Shop, wrapped in _utils as well. 1. Working periodic collider, yay!! scripts/test/periodic-grow.py shows that 2. A few changes related to that. 1. Add PeriIsoCompressor for periodic isotropic compression; see scripts/test/periodic-compress.py. Not so much slower than regular triax, nice surprise. 2. Make SpherePack standalone class (previously just in python's _packSpheres), add the function to generate cloud of spheres, both periodic and non-periodic. _packSpheres wrapper is update, but otherwise not changed. 3. Fix python-less compilation for se3 interpolator engine 4. Add Omega().runEngine() 5. Fix some compilation/linking issues. 1. Fix missing vtable for GenericSphereContact (crasher with debugging) 2. Fix collider stride for TriaxialTest with unspecified radius 1. Try to fix locking with boost-1.35, possibly fixes https://bugs.launchpad.net/yade/+bug/411572 (please confirm) 2. Add periodic repetition function to SpherePack 1. Fix linkage for boost<=1.34 (hopefully) 1. Fix a few things in SpherePack 2. Remove TriaxialTest::GenerateCloud, replaced by SpherePack::makeCloud 3. Fix crash in BoundingVolumeMetaEngine, if MetaBody has no boundingVolume 4. Improve sorting operator, remove workarounds for std::sort wrapping minima/maxima (https://bugs.launchpad.net/yade/+bug/402098) 5. Limit minimum cell width in PeriIsoCompressor, to not crash. 6. Add code for periodic packs in pack.triaxialPack (not yet fully functional) 1. Add #pragma once to SpherePack.hpp (possibly cause of https://bugs.launchpad.net/bugs/412442) 2. Fix crash in TetraTestGen (finally, I think), closing https://bugs.launchpad.net/bugs/408422 3. Add highlight parameter to utils.{facet,sphere,box}, passed to Body::geometricalModel 1. Fix assertion is 2d dispatch code, add indices to RpmPhys, RpmMat, CpmPhys, CpmMat (would break dispatching if using more classes derived from the same base in the dispatch matrix) 1. Remove {,Distant}PersistentSAPCollider and Fixed{Position,Orientation}Engine classes 2. Add test script for generating packing 1. Summarize important changes since the last release. Add your own things, if they are important for everybody (svn log has the small details) 1. Add scripts/rename-class.py for renaming classes (replaces in code, moves files with bzr, adds rename record to python). Not very well tested, though. 2. Remove core/StandAloneSimulator.*pp (not used anymore) Add ignore patterns so that bzr st doesn't show (some) generated files 1. Merged upstream changes. 1. Clen the plugin loading logic a little (no nominal win32 support), support YADE_PREFIX for changing prefix (should be runtime-detected inthe future, anyway) 2. Use $ORIGIN in rpath to make yade relocatable; it can be fragile, let me know if there are troubles! 3. Add make check to the debian package (will be tested soon) 1. Add compatibility for hardy 2. Fix missing newlines (hardy compiler gives warning) Adjust rpath for the main executable (ugly hack) Run tests in debian package (not failing if the test crashes, though, for now) 1. IMPORTANT! All features are lower-cased now (avoids mess: openGL or OpenGL etc). This will probably break your profile files. 2. IMPORTANT! openmp is one of features, no longer a separate options. Adjust your profiles 3. Rename LOG4CXX to YADE_LOG4CXX; all features now #define YADE_FEATURE macros 4. Better $ORIGIN syntax in scons 5. Abort yade if running PythonUI_rc.py fails 6. Adjust pbuilder script 1. Add wrapper for running tests, to trap exit segfault at squeeze 2. Use the wrapper for tests in debian/control 3. Add wrapper for vector Make the test script report what distribution failed (currently only karmic). Fix GTS features (thanks for spotting that; we need regression tests for all the modules) 1. Skip more stuff in doxygen, a few warnings 2. Add debugging code to DynLibDispatcher (disabled) 1. Rewrite 2d dispatching code from scratch 2. Expose classIndex to python for indexable classes 3. scripts/test-sphere-facet.py shows how the dispatch matrix fills with data Add python 2.5 compatibility hack Avoid error in pack.triaxialTest if compiled without GTS. 1. PeriIsoCompressor: add keepProportions parameter (on by default), decrease global updates interval. 2. Omega: startSimulationLoop warns if there is no simulationLoop (how can that happen? It somehow can) 3. SpherePack: resets periodicity (and warns) if being rotated; cellSize is writable now. 4. pack.triaxialPack: renamed to pack.randomDensePack; fix loads of stuff in that; rename radiusStDev parameter to rRelFuzz; add epydoc documentation; fix erroneous detection of inGtsSurface being defined 5. wrapper: lock renderer when adding list of bodies; Omega().resetThisWorld to reset current world only. 6. Fix scripts/test/{gts-random-pack.py,gts-random-obb.py} ef2_Sphere_Sphere_Dem3Dof: change meaning of distanceFactor, see docs (if < 0, equilibrium is sum of radii, otherwise the initial distance; defaults to -1). Renamed to distFactor, to break old code intentionally InteractingSphere2AABB: allow negative aabbEnlargeFactor, which doesn't scale aabb at all (same as if it is 1.) yade-multi: use the first non-blank line as column headings (not necessarily the first line of the file, as is used to be) SpatialQuickSortCollider: fix clearing interaction with openMP examples/concrete/uniaxial: move files around, add confinement and docs; remove examples/concrete/pack since not in the pack module (an example should be given, though) examples/collider-perf: remove PersistentSAPCollider, add InsertionSortCollide with strides, but commented out since no effect at hthe beginning of simulation PeriodicInsertionSortCollider: remove unnecessary (hopefully) condition that would abort in scripts/test/periodic-simple.py PeriIsoCompressor: fix absolute displacements changing proportions, operate on averages instead pack.randomDensePack: fix proportions in periodic packs; change residual stress to 1e8 instead of 1e9 SConstruct: remove obsolete (unused) standalone openmp option yadeWrapper: Fix compilation with boost<1.35 (again) (now?) 1. Fix startup crash if no $DISPLAY set at all miniWm3Wrap: update with newer py++; still errors out at karmic spirit: avoid warning with newer boost versions Add gts to depends on the script. Remove unnecessary inclusion of indexing_suite which breaks compilation for boost<1.34 (or something like that). utils.uniaxialTestFeatures: fix compat with py2.5 SConstruct: fix compilation issues with older symlinks in include dirs Fix OperationalError, reported by Janek in https://bugs.launchpad.net/yade/+bug/416906 SConstruct: add doc for pretty, fix a few lib checks on karmic Fix version detection on systems where version is not specified by hand or in RELEASE file and bzr is not installed main: disable SIGHUP handler (emergency save) and automatic loading. 1. Remove garbage from examples. 2. Fix SpheresFactory, fix examples/SpheresFactory/model.py move SpheresFactor to scripts/test as it doesn't work. Add notes to changelog and news. Forgotten file Mark release version Add mill script mimicking http://www.youtube.com/watch?v=005rdDBoe4w Some changes in qt interface (useless) Add series to package version to allow same version for multiple series in the ppa (0.20-1~hardy, 0.20-1~karmic etc) Added script for http://www.youtube.com/watch?v=KUv26xlh89I 1. Fix facet lighting, mill shown with wire=False now (looks better) 2. Revert recent useless change in QtGUI-python 1. Enabling O.timingEnabled when running will not produce garbage values for current engine anymore 2. Add maxRefRelStep to VelocityBins, to limit speed fo maximum velocity changes 3. Fix epydoc format for utils.encodeVideoFromFrames and utils.uniaxialTestFeatures 1. Do not save potential-only interactions to the XML; do not save pendingErase interactions (are only potential) 2. Do not save interactions in SQLiteRecorder by default (saveInteractions=True if you want the old behavior) 3. Add slider to the simulation player, in the "snaps" tab 1. Add scene that has been sitting here for a few days (should be run for comparison with pfc3d) Workaround bug #416335; static members of Vector3 work only on instance, e.g. Vector3.ZERO will not work, you have to use Vector3().ZERO; changed all occurences in the code accordingly, update your scripts as well.\n2.Some undocumented and untested enhancements for parameter table (backwards compatible) ================================================== yade-0.12.1 Sat, Nov 22 22:48:27 2008 +0000 Bruno Chareyre (6): A wrong comment on contact laws in PFC3D is removed. svn update - no real change (in principle) - recovering from erased local file - Fix conflicts and prepare next implementation of isotropic compression - Few files missing in the previous commit. - Add a new class for computing/recording micromechanical data based on tesselation Still recovering from conflicted files... I hope this is the last one. A class with algorithm for analysing contacts-forces-displacements statistics. Using the triangulation lib. Janek Kozicki (14): ouch, forgot to mark INSTALL file as outdated. We need to update it. some cleanup in the files fix crashes of this->parentWidget()->parentWidget() in UI yade.cpp: more detailed info about compilation flags. LatticeExample: added material parameters bond zone between steel fibres and cement matrix update to use latest scons 1.0.0 don't refresh display when there's nothing to refresh support for generating multiple file with single NullGUI call add to SimulationController buttons for looking in different directions. The default view direction is now down along the direction of gravity. improved generation of steel fibres in lattice fixed snaphotNames in NullGUI for .xml.gz more configurable MakeItFlat, TriaxialTest can be now a biaxial test as well. small fixes in UIs make it compile with boost 1.35 (I wonder what 1.36 will bring) Fixed OpenGL problems with displaying spheres, which - never occurred on my desktop - I've seen them sometimes on other peoples computers, but never could reproduce them - but finally have appeared on my laptop. up direction on Y release 0.12.1 Jerome Duriez (7): Added : - Engines for simulating shear tests at constant normal displacement, normal stress or normal rigidity (CinemDNCEngine, CinemCNCEngine and CinemKNCEngine) for samples deriving from SimpleShear PreProcessor (added also, former DirectShearCis) - ContactLaw1 : a contact law with Moment Transfer and inelastic behaviour in compression : see comments. With correspondant Relationships and type of Interactions. NB : sorry for the name : i didn't search too long for a more explicit one but I think personly that the comments in the files (and I tried to put some) are far much better than the one - Add of the paragraph for the CL1Relationships, which was forgotten, in the corresponding SConscript A (last ?) error concerning last revision in the paragraph CL1Relationships : "CL1Interaction" does not exist, that's ContactLaw1Interaction -ContactLaw1 : correction of prototype of action() : it needed a body, whereas now action methods of Engines need a Metabody. This led the simulation to crash and is now fixed - SimpleShear : now all is normaly done so that this preprocessor could be used (with success) with this ContactLaw. Moreover it is now linked to the GlobalStifnessTimeStepper (instead of the ElasticOne) Suppress of some useful comments (sorry for the extra mail) (At last) The good Sconscript file - The GlobalStifnessCounter engine (for correct use of GlobalStifnessTimeStepper) was forgotten in the preprocessor SimpleShear... Sergei Dorofeenko (4): 1. A new algorithm for facets. It use only a class Facet, so classes Vertex and Edge are deprecated and has been removed. 2. Add functions to python gui for import geometry from stl and for creating facets. 3. Add Shop::inscribedCircleCenter for calculate position of center of triangle inscribed circle (use it for set facet's se3.position). 4. ForceRecorder now take cary about old data files and renaming them. 5. SimulationPlayer now allow wire body form view tab. 1. ResetPositionEngine is now inherited from PeriodicEngine. 2. Add examples/rod_penetration model and geometry. small fixes 1. New random-access interaction container InteractionVecMap (rewrited from InteractionVecSet) 2. Fixes: default initialization some attributes in SCG and IS2IS4SCG Unknown (2): I added a new state at the triaxial compression test in order to realize isotropic compression by the walls displacement until a indicated porosity value. These modifications can be seen with generator file "TriaxialCompression" in the last three parameters (added). I corrected my errors on the triaxialtest files.Moreover, i added comments in the TriaxialCompressionEngine.hpp. Vincent Richefeu (4): - initialize radius in InteractingSphere constructor. - add new plugins used for membrane modelling. - add GLDraw plugin for BssSweptSphereLineSegment displaying - 'Membrane' (in fact geogrid) has been improved but still doesn't work... - the class GroupRelationData has been add (not used yet) to define interacting parameters such as coefficient of friction or restitution that have nothing to do in the bodies parameters (in my opinion) - forgotten files Václav Šmilauer (44): Move big news to NEWS, ready for release finally (?) Update INSTALL file from wiki (brief instructions only). 1. Reimplement some loop-intensive functions from yade.utils in c++ for better performance (as yade._utils, imported into yade.utils automatically - transparent to the user) 2. Add poisson's ratio and Young's modulus estimation in yade.eudoxos based on linear regressions of position→displacement mapping along different axes 3. Rename Shop::ElasticWaveTimestepEstimate to Shop::PWaveTimeStep, wrapped in yade.utils 1. Add python interface for blocking DOFs: b.phys.blockedDOFs=['z','rx','ry'] It is friendlier than b.phys['blockedDOFs']=1<<2|1<<3|1<<4. The property is read-write. 1. Add code for computing histogram of interaction directions in given plane in _utils. 2. This is code is used by yade.eudoxos.plotDirections() to create the actual (nice) figure. 1. Skip (almost) exactly zero projections, implement masking of bodies. 1. poisson and young estimator now works on a fractional part of the speciment 2. do not switch ui automatically if selected explcitly 3. body::maskOK to check mask 1. eudoxos module cleanups, young estimator arg chages 2. fix harmless compiler warning in _utils.cpp 1. add utility function to compute elastic energy within a volume (the dynamic_cast to NormalShearInteraction still fails; what is going on?) 2. add utils.fractionalBox, an AABB reduced to its fraction 3. fix fixmes in Force and Momentum about unused functions about to be deleted 4. plotting interaction direction histogram now works on contrained volume (to study boundary influence on the distribution) 1. Beginning hijacking SpheresContactGeometry to hold more elaborate geometry characteristics, such as relative shear displacement. By default, those parameters are neither calculated nor displated (exactRot==False; must be set to True in ISphere2ISphere4SpheresContactGeometry to enable it). Test script is in scripts/exact-rot.py 2. Remove deprecated code from UniaxialStrainController 3. Add GLUtils to draw lines, text, numbers -- copid over from Shop 4. Add operator<< for Quatrnions (axis angle) 5. Fix energy calculation in volume (in _utils.cpp) 6. Other fixes here and there... 1. The exactRot code is reasonably verified now to be functional, including rolling correction (SpheresContactGeometry::relocateContactPoints) and will used in brefcom soon. 2. Updated the "testing" script scripts/exact-rot.py, as a showcase as well. 1. Remove GL things from Shop, moved to lib/opengl/GLUtils.hpp 2. Cleanup of SpheresContactGeometry code, rename exactRot to hasShear (I thought most of this was already commited?!) 1. Delete unused IS2IS4DistantSpheresContactGeometry (merged with IS2IS4SCG), adapt existing code 2. Adapt new facet code to updated SpheresContactGeometry (only superficially tested) so that they can interact using BrefcomLaw; add scripts/exact-rot-facet.py to show that. 3. Fix a few missing loggers in others' commits. 4. SimulationController redraws if iteration changes (the +1 button would otherwise not cause redraw) 5. Remove cruft from Brefcom. 6. Fix inheritance (dynamic casts) of NormalShearInteraction and of BrefcomPhysParams 7. Remove cruft from UniaxialStrainer 8. Add some python code to compute stress from stored elastic energy. 1. Move interaction direction distribution pie histograms to utils 2. Add plotting histogram of number of contacts to plotDirections() 1. Omega().bodies.append can take list of bodies now (instead of just one body), returning list of added id's 2. utils.facet can take both tuples and lists for vertex coordinates 1. Add NormalInteraction::normalForce and NormalShearInteraction::normalForce (moved up the hierarchy from ElasticContactInteraction) so that unbalancedForce can be calculated on any subclassed interaction type. 2. Adapt Brefcom for this. 3. Add Shop::unbalancedForce and utils.unbalancedForce 4. Add workaround for saving ipython history as suggested by http://lists.ipython.scipy.org/pipermail/ipython-user/2008-September/005839.html 5. Fix AxialGravityEngine to register parent class attributes. 1. Avoid compiler warning in optimized builds from pyOmega 2. Body containers now allocate the lowest possible ID for each added body. The old behavior was to allocate the first free ID greater than the ID of the body, if it was already set. Please let me know if this is critical for someone. We can do things like: 1. 3d view now has by default a OSD with iteration number, simulation time and virtual time. This can be toggled (all combinations) by pressing 'D' (which used to mean toggling d+ynamic flag...?) and set also from python with GLView.timeMask='rvi' (Real,Virtual,Iteration) and so on. 2. Simulation player displays real time and wall clock loaded from db (insted of bogus values of system clock when the player is being run) 3. new periodic SnapshotEngine that takes snapshots of any GL view to numbered PNG files. 4. new function qt.makeSimulationVideo that makes video _while running the simulation_. 5. qt.createVideo renamed to qt.makePlayerVideo 6. Added Omega method to query computation duration. 7. An example of using qt.makeSimulationVideo is in new file scripts/simple-scene-video.py 1. Add forgotten SnapshotEngine 2. BodyRedirectionVector always really find the lowest unused ID (the same for BodyAssocVector) 3. add Omega().interactions.clear() in python 4. Move the "Less than 500 bodies, moving possible" to LOG_INFO instead of on-screen since it disturbs videos taken during simulation... A few fixes in TriaxialTest: 1. radiusStdDev is a registered attribute parameter 2. skip unloading phase if sigmaLateralConfinement==sigmaIsoCompaction 3. new bool autoUnload, that controls automaticity of going from compaction to unloading; autocompressionActivation controls from unloading to loading. Note: IFacet2IS4SCG is broken, but not yet fixed. 1. TriaxialTest now takes readiusMean argument (by default negative, i.e. disabled); if >0, box size is scaled so that both requested porosity and mean size can be preserved. 2. Attempt to fix computation of stress from stored elastic energy. 1. Fix adding empty engine if record wall stress interval <= 0 in TriaxialTest (bug introduced yesterday) 1. Add Interaction::swapOrder(), for use in EngineUnits that want to avoid calling goReverse() 2. SnapshotEngine can sleep specified number of msecs after shot, which probably can avoid some hw problems (make_current failed in mesa, freezes etc) 3. InteractingFacet2IS4SCG now works "correctly" (not exactly physically, though) with shear 4. DynLibDispatcher should report names of classes if dispatch fails. 1. Add InteractionGeometryMetaEngine::explicitAction and InteractionPhysicsMetaEngine::explicitAction for creating contacts manually using the dispatcher 2. add utils.createInteraction(id1,id2) that requests explicit transient interaction creation 3. Add sample ElasticContactLaw2 (in ElasticContactLaw.cpp) that uses new SpheresContactGeometry code and can be used for "persistent" (transient but not deleted by the collider) contacts. 4. Add Shop::applyForceAtContactPoint (applies contact force at contact point to both bodies (reversed for body2) and moments acting on their cenroids as well) 5. All the previous new functionality is demonstrated in scripts/chain-distant-interactions.py. 1. Add Omega().reload() 2. Create map Omega::memSavedSimulations, where simulations are saved if they begin with special sequence ":memory:" and can be loaded again, within lifetime of Omega. 3. Add Omega().tmpLoad() and Omega().tmpSave() that use the above mentioned thing. 4. add utils.readParamsFromTable that reads any python variables from text file (useful for automatic parametric studies) 5. Add less expensive routine for sontact point relocation in SpheresContactGeometry 1. Separate player into two windows (controller and the 3d view); the controller may be hidden with ::hide() (and ::show()) or with Alt-H from the 3d view. This allows for correctly restoring 3d view size. Closing any of the two closes the player. Please log crashes, if any. 2. Update QtGUI-python.cpp: runPlayerSession 1. Yade now runs TCP server at the first free port above 9000 (accessible from localhost only), which simulates python console. (let me know if you need this over network -- it is DANGEROUS, since someone can delete all your files etc without any authentication). The interpreter has its own namespace. 1. Fix crasher (under special circumstances) in PersistentSAPCollider (deletion invalidating iterator) 2. PeriodicEngine now takes nDo and nDone attributes, limitng number of activations 3. Fix minor things is BrefcomDamageColorizer, add normalized damage to BrefcomPhysParams 4. Fix some indents in Triaxial Fix stupid sign mistake when applying contact force in brefcom. 1. PythonTCPServer now generates random 6-letter cookie (password) at startup and authenticates all connections by that cookie. It can be retrieved from yade.runtime.cookie 1. Add Body().mask in python 2. Add PhysicalParameters().pos and .ori instead of ugly pp['se3'][:3] and pp['se3'][3:] 3. Add dmgPlane plot in brefcom 4. Do not delete renderer if we close the last view (loses all renderer settings) 1. Add a quick implementation of bending and torsion code to SpheresContactGeometry 2. Update ElasticContactLaw2 to use that code; stiffnesses are hard-coded for now. 3. Update scripts/chain-distant-interactions.py to show that that code really works 4. Plot residual strength instead of damage in brefcom 5. Add some functions for spiral projections (not correctly working yet) 6. Fix missing std:: in DisplayParameters 1. Add 2d weighted average smoothing abstract class and its specialization on symmetric gaussian kernel, with python glue of course. 2. Remove .gz support. 3. Fix some bugs in the spiral projection code. 1. Fix errors preventing compilation due to missing TesselationWrapper. !! IMPORTANT !! Introduced new dependency on "python-numpy" package (numpy/ndarrayobject.h header), which is now checked for by scons. Tell me if this is not OK! 1. Add initializers for all SpheresContactGeometry members, to avoid nan's in the .xml file 2. Add code for saving arbitrary python variables within the .xml file, see documentation on utils.saveVars() and utils.loadVars() 3. Fix error (hopefully) caused by dangling symlinks in the symlinked buildDir/include tree 1. Fix algebraic typo in max distance check. 1. Commit simple parametric study interface, documented at http://yade.wikia.com/index.php?title=ScriptParametricStudy and wrapper script to queue jobs, which is installed as yade-trunk-multi, yade-trunk-opt-multi and so on. 2. Examples of such parametric "study" added in scripts/multi.py and scripts/multi.table 3. MetaBody now create Omega().tags['id'] at initialization like '20081028T102950p15498' (date, time, pid) that is unique 1. Changed colors to be random by default on utils.{sphere,box,facet} 1. Make our intelligence-challenged deserializer finally gracefully handle nan, inf, -inf (arbitrary lower-upper case). 2. Fix crash in MembraneTest (leftover in the commit from testing, oh well) 3. Add TriaxialTest::fixedBoxDims to not scale selected box dimensions if sphere mean radius is given (example underway). 1. experimental support for Real==long double (#defines QUAD_PRECISION, scons quad=1). long double is not necessarily quadruple precision (not on IA32, at least) though, but is still more precise than double. (search "long double" on wikipedia) 2. Some preparation to compile with -std=c++0x (not yet functional) WRT and boost::shared_ptr vs. and std::shared_ptr 3. Fixed in yade-multi. 4. New direct oofenm exporter in yade.eudoxos. 5. Don't fiddle with termios in non-interactive sessions. 1. Add INTEGER (besides FLOAT) to the list of wrapped c++ types in python. Fix type detection for long double 2. Fix Real->double conversions in GLDrawBssSweptSphereLineSegment.cpp 1. Body().phys.refPos is directly accessible now (old way Body().phys['refSe3'[0:3]] still works, though) 2. Labeled engines are at load-time or when Omega().engines are modified assigned to variables __builtins__.label; for example Add forgotten file. ================================================== yade-0.12.0 Wed, Aug 20 14:37:02 2008 +0000 Janek Kozicki (8): testing if SVN commit messages can go to launchpad eliminate crash on simulation reload. commented out line 241 in QtFileGenerator.cpp some small bugfixes that allow all non-broken filegenerators to run. Those based on SDECLinks will never work, so just put "return false;" there to indicate failure. use NDEBUG to detect optimized builds in FEMBeam and HangingCloth small changes in Lattice small bugfix in scons (by Vaclav) No idea why dynamic_cast fails while static_cast works in TriaxialCompressionEngine:275 and GlobalStiffnessCounter:63 when casting to NormalShearInteraction. We will use static_cast for now... New ChangeLog format. Now I will update with older revisions. But next time it's ALL OF YOU who will write this ChangeLog. It's not nice to write 200 entries once per year. Release 0.12 is ready Sergei Dorofeenko (1): Fixed small bugs and misprints Václav Šmilauer (22): 1. Remove unused GLViewer4 2. Add fairly straightforward GLViewer wrapper (manually wrapped) 3. Fix scripts/simple-scene-player.py that generates video 4. yade.qt module is now in python and imports * from yade._qt for the binary wrappers 5. importing qt if qt not running raises ImportError 6. utils.qtCreateVideo moved to qt.CreateVideo 7. Exponential softening in brefcom (untested) 1. DECREASED Mathr::ZERO_TOLERANCE to 1e-20 so that we don't get identity quaternions for small rotation in ToAxisAngle (!!) 2. Add glue for GLViewer, currently may crash, probably concurrency issues there 3. added yade.plot.reset() 4. add data smoothing (moving averages &c) function to yade.eudoxos 5. Omega().run(num#,wait=False): if the bool is True, it is equivalent to o.run(20); o.wait() for convenience 1. Fix file generator to properly return bool. This fixes some of default-test.py. Change python interface to throw on generator error rather than return bool. 2. Added python glue to set logging levels for individual loggers (from yade import log; log.setLevel('TriaxialTest',log.DEBUG) ) 3. Configuration file for logging is not monitored anymore, since changes from python would be lost 4. Added PhysicalParameters BrefcomPhysParams to prepare for storing volumetric strain; adapted utils.sphere and utils.box to take physParamsClass argument to determine what class to actually create (was always BodyMacroParameters, which is the default now) 5. Brefcom now computes unbalanced force (which is apparently broken as far as the numbers go) and volumetric strain. Many cleanups in Brefcom as well. 6. The collider now properly deletes interactions !I->isReal && !I->isNew && haveDistantTransient. More discussion is needed on that, though. 1. Fix USCTGen so that it works within default-test.py 2. Minor fixes as usual. 1. Fix all crashing preprocessors - for the price of SDECLinkedSpheres and ThreePointBending not really working - InteractingSphere2InteractingSphere4SpheresContactGoemetry only handles SpheresContactGeometry (and not SDECLinkGeometry), OTOH CohesiveContactLaw works only on SDECLinkGeometry. scripts/default-test.py passes now all preprocessors. 2. SDECLinkGeometry marked as deprecated. 1. Permit non-zero integeres as bool - sometimes the serializaer confuses bool with int; give warning to console about that. 2. the -opt profile (and optimize=1) generates instruction set for the current machine (gcc: -march=native) which means that the code may not run on different processor (e.g. core2 code will not run on Pentium IV). Please let me know if this is problem for someone. 1. Fix error with newer ipython coming from the fact that sys.argv wasn't defined in the embedded python env. 2. Fixes in brefcom. 3. stopSimulationLoop in TriaxialCompressionEngine if we enter LIMBO state. 1. Fix refresh period: use it for regular GL rendering as well 2. Make "open automatically" open generated simulation even if a simulation is already loaded 3. Fix crasher (on some machines) the close() was emitted 2× for QtFileGenerato 1. Fix a typo causing crash in the renderer. Fix another crash - rendering interaction that were, meanwhile, deleted during simulation being loaded. A more proper solution (like having a mutex on the whole rootBody that would be locked during loading) is welcome. 1. Remove draw mutex when loading simulation introduced a few days ago (may throw boost::lock_error under some conditions) 2. Restore default signal handlers before exit from main; set SEGV handler to nullHandler since on i386 we (on lenny consistently) get crashes in some log4cxx destructor. WARNING! This version crashes - but I don't know why and I please for help. Summary follows. 1. Fix GUI "crash" when trying to run python console without terminal (running by clicking an icon, for example): if no $TERM, run non-interactively 2. Select QtGUI over PythonUI if no $TERM set 1. Do not propagate exception to c++ (-> crash) if there is python exception in the script being run from command-line. Just print traceback and drop to the python console (unless stop after execution specified). 1. Kill building threads if the master thread is interruped in multi-builds (profile=a,b) Fix physical action index. test commit (whitespace) test commit 2 (whitespace) test commit (whitespace) 1. Fix catching all (including SystemExit) exceptions in python scripts. If nonInteractive or stopAfter, exit immediately, otherwise just print traceback and drop to the python shell. Reveals many unfunctional generators suddenly. 1. Fix nan's in SpheresContactGeometry 2. Fix ThreePointBending returning false on success (?!) 3. Add Preprocessor().load() method to python wrapper that generates to tempfile and loads it immediately. 1. Fix SDECLinkedSpheres returning false on succes (?!). ================================================== yade-0.12.0rc1 Sun, Jul 27 10:52:50 2008 +0000 Bruno Chareyre (69): Modifications in the equations of "non-viscous" damping. The equations are now as defined by Cundall. Engines are now compiled in a different order, with DeusExMachinas compiled last. The full DEM package has been compiled successfully after thuis change. IsotropicCompressionEngine removed from SVN. TriaxialTest do the same thing (i.e. isot. compression) better, and do more. The damping equations are reverted to their original expressions, with independant damping of each DOF. It looks like the convergence to equilibrium is faster with these equations. Squared distances are now compared instead of simple distances (faster) to decide if two grains are in contact. In addition, InteractionDetectionFactor is added as member data. The default value is 1, a value greater than one will allow the geometry of contacts to be created as soon as the distance is less than Factor*SumOfRadii. Usefull for interactions at longer distances (like capillary effects). A simple definition of contact stiffness based on E and nu : kn = E*size of sphere (so that apparent stiffness will be independent of size) ks = nu*kn A lot of small changes in triaxial classes, impossible to comment all of them; but note that : - TriaxialCompressionEngine can compress a sample to a dense isotropic state (either by moving walls or increasing the size of the spheres), - then change the confining pressure if needed (for running tests with different confining pressure on the same sample), - then start a triaxial compression test at constant zz strain rate and constant xx and yy stress (when the sample is considered stable based on StabilityCriterion). - The transitions between the different phases are (in principle) very smooth. Revert erroneous (and unexpected) changes in this pro file. Minor changes in the computation of the critical timestep. All files related to capillary law. Update of all classes related to dry and unsaturated triaxial tests. Modified definition of the interpenetration for box-spheres interractions. The previous definition was inconsistent. Correction of a wrong formula introduced in the last revision (the position of the contact point was wrong). Some changes in the computation of critical timestep so that the triaxial samples do not explode when dt is set to the automatic value. The change mostly affects the first steps of a simulation. Now, dt is computed at each timestep during the first steps, whatever the value of the user-defined variable "timeStepUpdateInterval", until a relevant value is found. Cosmetic changes in TriaxialTest. Initial zoom in the simulation player is set to a more reasonable value. UnbalancedForce member data was not defined in the constructor of TriaxialCompressionEngine, it was causing a crash when loading a TriaxialTest.xml. -TriaxialTest now includes a recorder to write stress history to a file. -safety factor is set to a smaller value in the timestepper as the computed value was still a bit too large in some cases. -UnbalancedForce is defined in TriaxialCompressionEngine() to avoid errors when loading a xml. Link TriaxialTest with WallStressRecorder in SConscript This commit contains new classes in order to simulate cohesive interactions with transient interactions. The new SAPCollider does set IsReal=false at each time step, neither do the Sphere2Sphere4Distant... engine. The user must set isReal=false in another engine (here the CohesiveFrictionalContactLaw engine) in order to have the interactions removed from the DistantPersistentSAPCollider list. All new engines are used in the CohesiveTriaxialTest. A REGISTER_ATTRIBUTE was missing in the previous commit. Some new classes related to the coupling with comsol (main author Andrea Cortis). FIXED : TriaxialCompressionEngine was stopping the simulation at each time step... 1- New contact law and related classes : CohesiveFrictionalContactLaw. It will be the most general contac law in Yade soon probably (TODO : include rotational effects like MomentRotation law). 2- New Stress-Strain recorder : TriaxialStateRecorder, autodetect the TrixialCompressionEngine of a MetaBody and write the parameters from it in a text file. 3- TriaxialCompressionEngine and TriaxialStressController are modified in order to compute stress and strain on demand. Modification in the usage of default values in GlobalStiffnessTimestepper. Change in dt default values in the preprocessors. Added some comments (doxygen friendly) and corrected the author in some of the source files related to the triaxial test example. In Yade.cpp : conditional compilation in order to compile with boost 1.33 In TriaxialTest.cpp : instanciation of a SimpleElasticRelationship object before adding it to engines (to avoid a crash). Vaçlav please, what is going on here?O Few "cerr" are back... Different fixes, including -TimeStepper : previousDt is now a registerable parameter, which can be used after saving/loading. New feature implemented : dt can be increased at a steady rate : dt=1.5*dt each time maximum (to prevent sudden increases and explosions). Comments added. -TriaxialTest : The timestepper is moved to a earlier location in the list of engines, so that the timestep is set before the stressController call. REMARK : REGISTERABLE dt IN THE TIMESTEPPER MAY BE REDUNDANT WITH THE "RECOVER TIME STEP" FEATURE OF YADE, DO WE NEED THIS ONE? -StressController : revert a small mistake in last commit. Fix the bug with BrefCom in GlobalStiffnessCounter. Samll optimizations. r1263 is +25% slower than r1264 based on tests on 2 different computers. Added NewtonsDampedLaw : The test on penetrationDepth is moved to a different line : no need for a "new SpheresContactGeometry()" when interaction->isReal will be false. A registered parameter (finalMaxMultiplier) was not set for the relevant engine. Fixed now. Parameters are adapted to the last modifications in TriaxialStressCompressionEngine. TriaxialStateRecorder : add the unbalanced force in 8th column. TriaxialTest : revert order of creation of spheres and boxes (boxes first), to prevent numbering problems in SAPcollider when using HydraulicTest. HydraulicTest : search engines and modify their parameters (desactivate stress control/activate gravity) dem/SConscript : link HydraulicTest with few more libs Safety coefficient on critical dt is changed to 1 (was 0.25 before). 0.25 was far too conservative. A bit (lot) of cleaning in TriaxialTest. Useless recorders inherited from the early ages of Yade are no longer present. Fixed interpolation for the case P=0. Added minimalist documentation. 1. TriaxialCompressionEngine is now saving at the end of compaction rather than at the begining of compression test, so that the simulation is saved when autoCompression is not activated (before this change, the simulation was going in LIMBO state without even saving, which means it was lost). Some cleaning. A link was missing between TriaxialTest and the NewtonsDampedLaw (new engine doing the same as the 6 previous integrating engines). A lot of fixes to make this engine work correctly regarding transitions between different states, including situations with saving/editing/loading. This commit contains an experimental version of the collider based on the triangulation package of CGAL.org. Compilation is disabled for the moment, but uncommenting few lines will enable compilation if CGAL and my (me=Bruno) own libs are available for linker. Contact law based on voronoi tesselation (using CGAL). Not included in compiling process now, this is for JF Jerier. This reverts a previous change in IGMetaEngine from Janek (interactions were set "new" when not real - problem was that non-distant interactions are ALWAYS "not real" when starting IG dispatching - so they were set new and reseted at each timestep, which disabled friction). What was supposed to be done in IGMetaengine is now replaced by something almost equivalent in the SAPcollider (the difference is that if an interaction is not real before being reseted by the collider, it really means that it is not-overlaping and not-physicaly-interacting). Again few changes in the behaviour of this engine, it should now handle correctly any manual change in the xml file (still needs a fix : there is still a part of the code that is done only at first iteration, which means that a modification of sigma_iso by editing the xml will give a different result than the same modification from PythonUI). Timestep safety coefficient is back to 0.25. - ks is registered in ElasticContactInteraction (it was not, which was a bug, but it was not affecting computation because initialKs was used to set ks=initialKs at each timestep). Added code for computing porosity in TriaxialStateRecorder and titles for colums in output history file (porosity is in the 9th column). 1+2 : added "Key" string in filenames. 3 : missed that hpp in previous commit. A small cast mistake. Corrected a local #include path. Added a function that can modify friction of all bodies/interactions. Usefull to compact at low friction and then perform the compression test with normal (higher) friction. Modify slightly the way shear displacement is computed to prevent "ratcheting" (see comment) Same modif as in ElasticContactLaw (previous revision) to prevent "ratcheting". Added a code that will be common to all output filenames to help sorting different simulation results. Some code for triangulation/tesselation using CGAL, it is experimental and not included in the build process for the moment. Fixed a bug due to a wrong macro definition in CGAL (CGAL_LIB_CREATE sould be "ar cr" instead of "ar cr ''"). This macro is not used now. - epsilonMax added to compressionEngine to stop simulation. - data files for Triangulation removed from svn - bool "isNeighbor" added to interactions, used in triangulationCollider. - Modified numerical scheme to reproduce a standard 2nd order finite difference scheme integration (more stable). - TimeStepSafetyCoefficient can be set to 1 rather than 0.25. - same change still to be done in the separate LeapFrog engines (the default TimeStepSafetyCoefficient is 0.25 in the meantime to prevent explosions with preprocessors using those separate engines). NewtonsDampedLaw : fixing a small mistake and cleaning the code. IMPORTANT NOTE : Forces/Moments are not affected by damping now, only accelerations are modified. The result is the same in terms of damping, but users can now get the exact values of forces/moments on bodies at any point during a timestep. Mostly small changes and fixes from previous weeks. Sorry for such poor comments... ks is added to registered parameters. Fix a problem resulting in VectSet (default) and HashMap being used simultaneously for transient/persistent interactions in the same simulation (which for unknown reasons is not good). Janek Kozicki (82): added info where are the pictures for the user manual can be undeleted from the history if anybody needs that directories for Czesiu and Tiziano to work on yade packages: debian and gentoo. later we can add directories for .rpm, etc.... - better DynLib error reporting - new Lattice, only 2D - triaxial test from Bruno - deleted unused QGLThread Fixed some problems with yade-flat deleted yade-lib-time, and using boost::posix_time instead - it is possible to select (shift click) and 'm'ove bodies in the simulation view window. a dirty implementation. Needs major refactoring. - the "display" tab now offers the ability to set 'display refresh rate' in [ms] so it's like the priority of draw. renaming SimulationRunner -> ThreadRunner SimulationFlow -> ThreadWorker renaming done. FileGenerator now does not block whole GUI when generating. And can be stopped if function generate() calls somewhere in the loops: small fixes to ThreadWorker Graphics display can be now synchronized with the calculation without problems, and without blocking GUI - more improvements in ThreadRunner, ThreadWorker::~ThreadWorker and SimulationController - fiddling with lattice: angle between beams is now calculated in 3D not 2D, with tracking of positive/negative angle. So the angle is between -180 and 180 deg, while arcus cosinus can only give a result between 0 and 180 degrees. Finally deleted swiftpp and qhull. Now we have less license problems. There is still license problem with math library, though. ... ... Some cleaning, involving changing variable names, like Body::interactingGeometry. Which caused almost all the files to be modified. oops, forgot to commit MyTetrahedronLaw renaming: ElasticContactParameters ElasticContactInteraction renaming cd... renaming finished: ElasticContactParameters ElasticContactInteraction added GLDrawStateFunctor. So now it is possible to see bodies' velocity, acceleration, angularVelocity, angularAcceleration. And other State attributes of a body. =========-----------------============ -------------------------------------- NullGUI allows to call a FileGenerator Omega throws when loading bad simulation file ----------- SimulationController catches that error from Omega :) (when loading bad file) 'C' key centers scene on selected body 'D' sets the 'isDynamic' flag of selected body. (select it twice to unset this flag). -- flag that allows to enable/disable calculating of torsion LatticeExample FileGenerator can now generate a 3D tetrahedral regular grid. (before there was only orthogonal grid) Added drawMask to OpenGLRenderingEngine, it is now possible to see only bodies that have a certain groupMask Added non-working scripts that can compile yade using waf. At least they are not working for me. -- removed invMass and invInertia. Removed special handling for division by zero. mass shouldn't be zero anyway. Matrix3 patch from Vaclav - FileGenerator reports how long the generation took - yade.cpp takes PREFIX and POSTFIX as #define /some/path , without " - small change to lattice generation method - Makefile cleans after scons - SConstruct: removed \" from defines in PREFIX and POSTFIX line 147 added -pthread for g++ 3.3 moved env.Append(CXXFLAGS=['-pipe','-Wall','-pthread']) before invoking the tests changed "nor" to "not", line 271 removed PATH, becuase I couldn't get it to work. so instead I set CXX='distcc g++-3.3' - FileGenerator shows generation time, and saving time - QtFileGenerator remembers save file name (so it is not always ../data/scene.xml), this was done in previous commit. A small bugfix here - OpenGLRenderingEngine didn't draw Body when it's groupMask was 0 - some changes in lattice. - I added GeometricalModelForceColorizer. It is another hacky Engine that I have added (along with Quadrilaterals). I did it, because I wanted to see spheres colored according to force acting on them. This is a hack because the GLDraw mechanism is currently very limited, and has to be extended to allow things like this. So currently it works, but not in a way it should be done. GLDraw* changes are coming ... soon :) In this commit I made Interactions drawable by GLDraw*, and I added three example GLDraws for Interactions: - in GLViewer new keys: 'o' and 'p' to change the field of view. Useful for changing the perspective. IMPORTANT: In this sommit I have added a third parameter to GLDrawInteractingGeometryFunctor, which means that all GLDrawInteracting* classes had to be changed. If you have your own classes that draw interacting geometry you must modify them as well. The extra paramater added is 'bool wireFrame'. I think it will be useful :) - a more prominent warning when PREFIX was not defined during compilation - ThreePointBending does not crash when there is not file to load - some fiddling with lattice moved some obsolete scripts to doc/removed - Updated trunk/ChangeLog - new icon - fix several warnings, - merge latest version of lattice from my PhD - todo: serialization problems with findType in NodeRecorder and FEMBeam, remove ErrorTolerant, fix collistions in rigid body - removed ErrorTolerant, - SConscript uses md5sum instead of timestamp. Tetrahedron modification to std::vector - fixed crash in SDECLinkedSpheres example - there must me a dynamic cast in InteractingSphere2InteractingSphere4SpheresContactGeometry.cpp until the containers are rewritten. - fixed RotatingBox and BoxStack examples - and accidental mistake in SAPCollider - timestepper is set correctly when loading file in GUI (I hope!) - no need to reload second time - FileGenerator does not open SimulationController if it already exists - introduced body_id_t(ype) so that bodies are not numbered as an int, but as a body_id_t - added center 'scene button' on first tab of simulation controller trunk on SVN is deprecated now undeprecating trunk. Now I'll apply diffs from BZR back here. BZR revno: 983 committer: vsmilauer Fix scons to handle boost.*-mt well, some other cleanups. BZR revno: 989 committer: VĂĄclav Ĺ milauer message: Modify debian builds so that making source packages for release versions work as well. BZR revno: 991 committer: Janek Kozicki message: - Tetra2TetraBang returns before it starts (to avoid crashes in the release) - fixed some crashing of HangingCloth, but I see that there is still some problem. Tracking it down is a waste of time, currently, because new containers will enforce refactoring here and fixing this problem. - FileGenerators try to set the timestep when TimeStepper is not used. But it is still not working when simulation is running during the generation. This means, that 'dt' should not be in Omega, but inside a simulation itself. At last there is a clear answer for this dilemma. OK, I think that migration back from BZR is complete :-) Ooops, previous commit did break SCDECLinkedSpheres example and HangingCloth... I hope that it's fixed now. Also removed warnings from Capillary law... again. HangingCloth finally is fixed, and is not crashing in optimized build. The dynamic cast ought to be in HangingCloth.cpp FileGenerator ;) Added RELEASE file in tags/yade-0.11.0 more descriptive error message when directory ../data/ doesn't exist (usually happens at start...) - update yade icon, by Vaclav's suggestion (the red spheres are not so vividly red, so looks a bit better) - scons will create the installation directory if it doesn't exist - incorporated Bruno's fixes into 0.11 release - Updated RELEASE info in yade.cpp, although the best would be if sons will #define RELEASE update changelog Preparinf 0.11.1 release. Updated INSTALL file. ForceRecorder now checks if a body exists. changed char* yadePluginClasses[]= to const char* yadePluginClasses[]= to eliminate possible misuse updated release 0.11.1 looks like ready to ship Some fixes in lattice regarding yade::serialization problems (function findType()) that started to occur when using g++ newer than 3.4 The offending code was commented because it wasn't working. Now I uncommented and modified it, so it works. deleted unused class AAInteractingBox2InteractingSphere4ClosestFeatures Use lattice model for simulation of concrete with steel fibres reinforcement. SConstruct is now working on debian etch, exclude multimethods in Doxyfile 1. Fix FUNCTOR2D mistake in CundallNonViscousMomentumDamping 2. make it compile with python2.4 and qt3 (which sucks) NOTE: python-scientific requires boost 1.34 recent changes in Dispatcher seemingly broke adding InteractingSphere2InteractingSphere4DistantSpheresContactGeometry and InteractingBox2InteractingSphere4SpheresContactGeometry as strings. Need to create class instance first, then add it as a variable (not as string). I must assign something to avoid "nan" when loading dt. When recover=false, those can be "nan" and lead to crash. - draw linked interactions only if isReal. Added StaticAttractionEngine, which help to fasten a little the initial stage of compression - removing the central void. But it is in fact too slow, for bigger usage. That's because everything is attracted to everything. I don't know what's up with Brefcom, but I need yade to compile, because those guys who will sell us 16-core machine will be using this SVN revision for their benchmarks. dang! I just want the latest SVN revision to compile. yade.cpp give info about cmdGui -N name : specify the user interface (available: NullGUI, cmdGui, QtGUI) FileGenerator catches exceptions. Moment law. 1. moment law: some cleaning 2. GLDrawSphere - draws circles faster (wireframe) 3. yadeControl - fix compilation error with optimized builds: YADE_PTR_CAST -> dynamic_pointer_cast in previous commit, when optimizing the circle, I committed by accident a filled version. It's just a bool to toggle between filled and not filled, but, it's not possible to change from GUI... yet. Use / * - + for front cutting plane: 1. added new class YadeCamera : public qglviewer::Camera 2. ovverride virtual function which calculates the front cutting plane 3. it's possible that GLViewer4 will have to be modified - to use YadeCamera to have this new feature - front cutting plane. I didn't touch it. Ask the FileGenerator for the filename. - flag 'active' in DisplacementEngine - CapillaryCohesiveLaw now complains to stderr (could use LOG_WARN as well, but didn't compile) - flag 'always_use_moment_law' in CohesiveFrictionalContactLaw (so is possible to use even for broken contacts) - added comment about the contact law from PFC-3D (I used it for comparison with Wenjie) - started snow creep test. - added optional creep in CohesiveFrictionalContactLaw Jerome Duriez (1): - A new Preprocessor (DirectShearCis) allowing to simulate ... direct shear tests with the Engine associated to let move correctly the box (CinemCisEngine) Sergei Dorofeenko (17): Add linear viscoelastic contact model 1. Main commit: import a geometry of walls from a STL file. Translate comments to english. 1. Fix orientation. Now one can rotate the STL geometry. 2. Added rotation kinematics to STLImporterTest preprocessor 3. Bugs fix. 4. Added another example of STL file in examples directory. 1. Python wrapper for STLImporter 2. Example python preprocessor STLImporterTest.py 3. Small fixes 1. Added attribute zeroPoint to RigidBodyParameters and RotationEngine. This attribute defines the rotation center of kinematic body. 2. For ElasticContactLaw added accounting rotation of kinematic body around its zeroPoint. 3. Added phase flag "cycle" to Interaction. SpatialQuickSortCollider is modified so interaction's cycle flag is used to mark stale contacts instead isReal flag. 4. Small changed InteractingSphere2InteractingSphere4SpheresContactGeometry and GLDrawElasticContactInteraction. I think, towards an improvement. If there are objections, changes must be reverted. 5. pyOmega.save allows recovery 6. STLImporter allows multiple files to import consistently. 7. Bugs fix. reverting condition c->isReal 1. Add engine ResetPositionEngine for the return of bodies at their initilal positions. 2. small fixes. Small fixes Fix relative path in SimulationPlayer small fix 1. ResetPositionEngine: added import/export initial positions from file 2. GLSimulationPlayerViewer: skip bodies which does not have a geometrical model 3. QtGeneratedSimulationPlayer: small fixes layout and sizePolicy. 4. utils.py: agreement spheresToFile and spheresFromFile In PositionOrientationRecorder and GLSimulationPlayerViewer pass the bodies without geometrical model Small fixes in PositionOrientationRecorder 1. Added new engines: - FiltrEngine is a base engine for a scene filtration before visualization (both for a player and for a controller). The filtration is activated through GUI (now only for SimulationPlayer). - ColorizedLayerFilter allocates with colour a layer of bodies - ColorizedVelocityFilter allocates with colour a velocity of bodies 2. Add Omega::setCurrentIteration to allow current simulation time from a player 3. ResetPositionEngine allows onlyDynamic (without save subscribed bodies ids) small fixes fix errors in previous commits Fix in SimulationPlayer: search filters and updateGL after loading simulation Unknown (2): Fixed some typos. Modified Doxyfile not to show lib/ and to optimize for C++ Václav Šmilauer (208): Multiple changes. Add logging.hpp which I forgot in the previous commit. Remove printing headers being linked, add help for cleaning. Tried scons on a cleanly checked-out trunk and it worked. Sorry for the traffic. 1. Two sets of files were named StiffnessMatrixTimeStepper, of which the other one was in directory Global StiffnessTimestepper. Since it was not used (not compiled by default), I renamed the second set to match the directory name. If it breaks something, let me know. 2. Updated the scons script, scons now compiles yade completely, including header installation. It is yet to be tuned to be lot more fast, but works fine already. 3. Cleaning up messages at startup, (almost) everything is piped through the LOG_* stuff. 4. Included sample logging.conf.sample file, log4cxx is amazingly flexible and easy to use. Give it a try! 5. Lot of debugging since after the last change of allowing regular files to be plugin-loaded, yade would try to load garbage (like the file foobar.baz transformed to libbar.so and the like) giving spurious errors. Everything should work properly now, you can have whatever you like in the plugins directories. It will not even crash on short files (amazing fragility...). 6. Removed the message from ResultantForceEngine constructor, to not to pollute the console every time. Dtto for StiffnessMatrixTimeStepper. 7. Added one ; in yade.cpp that broke compilation without LOG4CXX. The non-log4cxx macros are now enclosed within {} just as their log4cxx counter parts. This should eliminate the need for separately building both configurations. First commit from the wm3 convergence set. Header files were modified to allow compilation with old methods with deprecation warnings from the compiler. This is the second changeset. Yade now compiles without any single warning about deprecated functions. For fixing your own code, I will upload a script to automatize that process shortly. As time goes by, deprecated functions and (slowly) wm3 itself will be removed from yade's sources. Provided script hackett-warn-replace.py is for eventually helping you with replacing deprecated wm3 methods by the good ones. hackett.py was used to insert deprecated wrapper to headers and will probably never used as such anymore. Deleted old erskine2* stuff, since it is superseded by erskine3* and scons. This commit marks properly all headers as well as methods that were deprecated during the transition to the official wm3 library. Next commit will follow shortly and will fix all deprecated headers warnings. It is (and will be for a while) to compile in with USE_BASTARDIZED_WM3 to use the old headers, but expect many warnings during the build. - Bastardized wm3 library ejected from the tree. From now on, you need to install wm3 separately. Will update README shortly on how to proceed. Basically, add "-I/usr/local/include/wm3" to CXXFLAGS if you use qmake. Includes and linking was adjusted in c++ and .pro files. - DOUBLE_PRECISION removed in favor of SINGLE_PRECISION. Therefore, the default is to define Reals as doubles now. SINGLE_PRECISION will be removed very soon anyway. - Removed yade-lib-algorithms, since it is not used. Once someone needs it, it can be found in svn history. - Created yade-lib-base, that for now contains 1. Logging.hpp: moved from yade-core to avoid circular dependency), 2. yadeWm3.hpp: typedefs for Vector3r Vecrtor3, definition of Real. This header will be removed soon, since it will not be needed. 3. yadeWm3Extra.hpp: things that were added to the bastardized wm3, like a few operators and functions. You almost certainly want to include it in your code if you get undefined references (and link with yade-lib-base). - Omega now reports undefined symbols demangled. Re-enable workaround for scons: create install directories beforehand. Cause of that is to be inspected yet. Disable DEBUG messages for non-log4cxx builds so that Janek's screen is nicer ;-) (sorry for these scattered commits, hopefully this is the last one). Exceptions from the FileGenerator thread are now properly caught; it means for example that if you pass an invalid classname in createActors, yade will tell yo +u what class it wasn't able to construct instead of hanging infinitely. Massive update of scons, which I now recommend for building. It has some autodetection support as well. Please report any bugs (?) / RFEs to me. Brief instruction are given in INSTALL. Quick fix for recently discovered segfault. Wondering why it didn't surface before. Valgrind still complains about PhysicalActionVectorVectorIterator::increment using an unitialized value, but that may be unrelated (probably is) to this one. SCons update, please use it and report any problems to yade-dev or yade-users. Thanks. Iterations/sec display added to simulation controller (someone more sensible may redesign the layout in the future). Line ending changed from CRLF to CR. Scons accepts additional POSTFIX parameter, which will alter installation so that PREFIX/{bin,lib}/yade becomes PREFIX/{bin,lib}/yadePOSTFIX (similar to EXTRAVERSION of Linux). Further, configuration files will be searched for in ~/.yadePOSTFIX instead of ~/.yade. Unfortunately, headers installed in PREFIX will still be overwritten by newer version, even with different POSTFIX, since most files do something like #include<...>. This is meant to ease simultaneous installations of different variants of yade. To achieve full independence, PREFIX and POSTFIX should be different. Note that you don't need to set LD_LIBRARY_PATH, since scons uses linker's -rpath option (hint: man ld). If someone has better solution, please let me know. Microchange to comment on naming of scons configuration options. SConscruct file update - make it work with older scons, remove -floop-optimize2 that doesn't exist for older gcc (<4.0). Hunted down the bugwith node enumeration, now should be reliable. Added some checks so that configuration is not done when cleaning, likewise for installable nodes (so we do NOT uninstall upon cleaning). If you use g++-3.3, you need to set e.g. CXX="distcc g++-3.3 -pthread". This will be fixed perhaps in the future but for now the default is to keep it simple. Other fix for sarge (0.96.1) version of scons. It is a variant to be removed. For some features (especially the srcdir keyword), sonner or later we will mandate 0.96.94 to build yade, but that can be a few months in the future, though. Fix node enumeration for newer scons's (isfile() is true only if the file already exists, which is contrary to what I thought). Microchanges: fix rpath quoting, fix ctags indexing non-c++ files (local bug? --language=c++ would make all files (including .o, for example), being indexed. Plugin loader now handles multi-class plugins, provided that they define "char* yadePluginClasses[]" symbol containing list of class names (sentinel=NULL) that the plugin contains. Regular one-class-per-file plugins work normally. Fix build process for older versions of scons that break if there is a target that has no sources. Added a dummy empty file to the loki library to do that, modified project file. Do not forget to delete yade-libs/yade-lib-loki before running scons again to regenerate SConscript files. 1. Include IsotropicCompressionTest in dem's preprocessors project so that it gets compiled 2. Improve erskine to indent SConscripts in a nice way 1. Fixed Omega::scanPlugins so that it loads plugins that were linked without -rpath for their dependencies. Everything is in #ifdef STUPID_DLL 2. Removed again IsotropicCompressionTest since needed headers are missing. Bruno, please add them back (they were removed in the 1045 commit.) and then enable IsotropicCompressionTest in yade-packages/yade-package-dem/src/PreProcessor/PreProcessor.pro again. Default preferences.xml doesn't neglect the last plugin dir (off-by-one error) Simulation may be (which is the default) loaded after generation automatically now. Prepare framework for "high-level clumps". Everything is withing #ifdef HIGHLEVEL_CLUMPS, hence shouldn't change anything unless compiled with that option (don't try at the moment, it doesn't work). More information at http://beta.arcig.cz/~eudoxos/phd/index.cgi/YaDe/HighLevelClumps . 1. provide Doxyfile, running "cd yade-doc; doxygen" will produce html reference documentation 2. initial (non-functional) implementation of high-level clumps, along with some cleanup of the changes done elsewhere 3. erskine now can be given scons variables (namely, $PREFIX) that will be unexpanded in generated SConscript's 4. SConscruct #defines HIGHLEVEL_CLUMPS, since other people probably don't use scons (yet) 5. New method Body::byId, new typedef Body::id_t, new constant Body::ID_NONE (jailed in HIGHLEVEL_CLUMPS for now, that will be removed sooner or later) 1. Clump code completed, but totally untested so far. 2. Added target 'doc' to Makefile that runs doxygen 3. New tracing macro TRWM3QUAT for quaternions 1. Clump::subBodies are now a std::map, subSe3s removed. 2. Sample clump generator ClumpTestGen written; works, but yade crashes because of dispatchers being setup incorrectly. 3. Omega::setRootBody added so that rootBody can be temporarily set during filegeneration, which is necessary for Body::byId. 1. Fixes in the clump code 2. InteractionGeometryMetaEngine doesn't crash if a body doesn't have interactingGeometry (like Clump) 3. Fixes in the collider, related to the clump code 4. ClumpSubBodyMover is now a DeuxExMachina 5. discovered bug in Wm3::Matrix3::EigenDecomposition 6. ClumpTestGen generates a usable testcase (either a random one, or with one-sphere clump and an equivalent sphere) for testing purposes. Parameters etc. are physically wrong, but at least there are no crashes. 7. ClumpTestGen temporarily sets rootBody in Omega so that Body::byId is usable 1. Clump code is not (hopefully) correct physically - translational, rotational and potential energies give constant sum over time (more or less) 2. ClumpTestGen generates a useful scenario of standalone sphere, {1,2,3,4}-sphere clump. 3. Inertia calculation fallback for cases with NaNs from EigenDecomposition. 4. acceleration and angularAcceleration reset after every position update for clump. 5. Body::{isClump,isClumpMember,isStandalone} added. 6. Gravity engine skips clump members (applied to the whole clump only). 1. Clump code cleanup 2. Python may now be embedded in yade: (i) SConscruct checks for it and #defines EMBED_PYTHON if present. (ii) main sets up the interpreter. One disadvantage is that Python runs in its own thread, so pressing ^C during simulation doesn't quit yade (^\ still works). 3. New class PythonRecorder that evaluates arbitrary expression when activated. 4. Pyade interface that makes it possible to retrieve some values from yade in Python. For example, you can now record body #5 z-velocity and z-angularVelocity every 10 iterations by setting PythonRecorder::expression="if (S.i%10==0): print B[5].v[2],B[5].w[2]". Retrievable attributes are documented in the source (or the Doxygen documentation). 1. selection in the renderer are now saved in Omega::selectedBodies (GUI to that list is in the future yet) 2. PythonRecorder now supports retrieving selected bodies; it redirects stdout to file if specified. 3. pyade now required scientific python to be installed - this permits to do vector and quaternion arithmetics in python. General cleanup. 4. pyadeDummy.py module added - for testing pyade without yade Microchange to catch exception from distutils in python detection. 1. Scons changes, node enumeration gotten rid of, now uses proper Install in SConscript's. Should eliminate scons version problems we've had. 2. Some new make targets: for repetitive compilation (with -j2) and first personalizaed target: eudoxos. 3. Selecting clump member in GLViewer now selects the whole clump; it is moved and redrawn as well (only with HIGHLEVEL_CLUMPS). 4. An attempts at escaping special characters beforce XML serialization (\",\n,\t,\\), doesn't work - commented out. 5. Project files cleaned from nonexistent include directories. 1. OK, the floats floating around for colors drove me nuts, so I replace all of that garbage by doubles. Since for one particular case (glMaterialv) the corresponding function taking double (glMaterialdv) is not defined in mesa (and perhaps not in most other gl libs), for that one the argument was converted explicitly. 2. New Shop class, that defines static methods for easy things, along with a dictionary of default parameters for some useful stuff: like getting a rootbody with reasonable set of actors, sphere body, box body. That's all for the moment. Add your own stuff. Filegenerators may shrink considerably this way. 3. Clump::subBodies renamed to Clump::members, Clump::moveSubBodies to Clump::moveMembers, ClumpSubBodyMover to ClumpMemberMover - to be consistent with Body::isClumpMember. Clump still not moved to DEM, that will wait till later - at the moment, QGLViewer depends on Clump (for moving clump when selected and moved with mouse). Many extensive changes: 1. renamed Chagelog to ChangeLog, will be generated autmatically from svn chamgelog 2. removed waf script (wscript), not used 3. scons now installs everything with '-version-postfix' appended, like yade-svn1072-debug 4. scons builds different variants (version and postfix) in separate directories 1. Clump member velocities are calculated in ClumpMemberMover. This may create problems since velocity will always be lagged by one iteration. 2. removed cyclic dependency of Clump and Shop. 1. Debugging mode now #defines YADE_DEBUG in addition to DEBUG, which seems to be #undef'ed by some header. Lazy to trace down. 2. Removed #ifdef DEBUG messages from dynlib &co (now under #if 0); use LOG_TRACE if you need those messages 3. Signal handler now handles SIGSEGV (segmentation fault) and SIGABRT (failed assertions, among other) in debugging mode: gdb is run, attached to the current process and backtrace of all threads is printed. Python fixes: 1. load pyade.py from path given by PREFIX and POSTFIX (we really need to define that somewhere in a unified manner: not by using BOOST_PP_STRINGIZE everywhere! - config.hpp...?) 2. velocity and angularVelocity are vectors now (returned only the x component by mistake) Attempt to access interactions from Python, commeted out for the moment. May disappear later. Not used anywhere. Do not use. 1. Cleanup of clump includes 2. Scons now puts buildDir include directory first so that it shadows eventually installed versions 3. jobs=x added to scons, like -j3, but is saved accross runs 4. Archive modifications (shuffling includes around) so that compiling with recent g++ (4.1) works - was preprocessor problem. Fixed error in yesterday's commit, should compile really fine with g++4.1 now. SCons cleanup. Now supports buildPrefix (defaults to '..') so that build is completely separate from sources. commit before mass-replacing Dymanic_cast. This will not compile, used only as wayback for me. New commit shortly. dynamic_cast replaced by YADE_CAST where appropriate: i.e. the result of the cast is not checked to verify whether the conversion was successful as condition for further flow branching. The consequence is that you have to #define YADE_CAST in CXXFLAGS by hand as either dynamic_cast (original version) or static_cast (for optimized builds) when using make. dynamic_pointer_cast replaced by YADE_PTR_CAST as appropriate. It must be defined on compiler command-line (-DYADE_PTR_CAST=dynamic_pointer_cast or -DYADE_PTR_CAST=static_pointer_cast). Scons builds get this automatically. Doxygen settings from yade-flat imported. New file tracking removed elements. Merged the scons-layout branch to trunk. 1. PythonRecorder runs optimized byte-compiled expression, optionally translated to machine-code by psyco 2. Serialization lib now properly escapes and unescapes XML-unfriendly charcters (\n,\t,<,>,",\\)->(\\n,\\t,\[,\],\',\\\\) 1. Remove cyclic plugin loading forever (undefined STUPID_DLL) 2. Tetrahedron has vector v instead of Vector3r v1,v2,v3,v4; (fem will probably not compile because of this for now - will be fixed) 3. Added experimental tetra class & friends. ough... put actual data in. Next commit will rename files as they should be (stupid svn...). Renamed files. 1. gdbCrashBatch moved to Omega (was emptry when a different thread thatn the main one crashed?!) 2. Shop generates tetrahedra properly now, including dispatchers 3. Tetrahedra rendering and linking fixed 4. Added an icon to the main window (not serious ;-) ) 5. commented out STUPID_DLL overflowing loader 6. log4cxx has level set to INFO by default (was DEBUG, lot of garbage on screen) 1. Some type fixes (signed vs. unsigned). The Body ID mess needs to be cleaned up!!! 2. More tetrahedron stuff; will not compile for now, though. 3. Icon update ;-) 1. remove vector from tetrahedra code, replace simply by Vector3r[4] 2. implement 4h-4h intersection peroperly, without relying on wm3. Compiles, not tested for correctness at all. Make compilation proper when python is not found (no errors, just disable what should be disabled). #define MINIWM3 when using miniWm3 don't exclude lattice with miniWm3 (lattice needs to be modified to not use undefined functions from full Wm3 in this case (Delaunay!) Adjust QTDIR to gentoo, too Configure-time decision whether to link against QGLViewer or 3dviewer. use -mt variants of boost byt preference if both exist useMiniWm3 by default 1. Fix fedora/centos etc compilation ( in the .ui file mentioning nonexistent file) 2. Other scons cleanup 1. Fix soname on shared libs (no soname at all; bug with "" in the name previously). Is it good for something? 2. Add 'features' option to scons, which can enable/disable python and log4cxx at compile-time (extensible) 1. Correct 'all' value for features 2. Add a few signed/unsigned typecasts 3. Check clump code 1. Fix uninitialized se3 in one-member clump handling 2. Fix syntax error in SConstruct introduced in previous commit. If RELEASE file exists, get yade version from there, instead of from svn. Remove wildmagic-dev from debian deps. fixed qt3 detection (used deprecated qapp.h header instead of qapplication.h). Adjust debian package generation back to svn. frontend cleanups, eperimental python console ui. Convenience functions for serialization - experimental. fix qualifiers in constructor of AttrAccess 1. rename ThreadWorker::message() to ThreadWorker::getStatus, ThreadWorker::setMessage() to ThreadWorker::setStatus (and private ThreadWorker::m_message to m_status) 2. add public: std::string FileGenerator::message that holds exit value description 3. FileGenerator::generate returns now bool (if the generation was successful); description in ::message 4. Adapted all generators and UIs for the above changes (except NullGUI - is that needed?) Adjust other components for FileGenerator changes. Add wrapper for preprocessor in python. 1. SCons will download new version of itself if older thatn 0.96.93 and will proxy all calls to it transparently (hopefully) 2. Revamped python wrappers, now all attributes are settable as dicionary entries in respective instances from python 3. added example of python script that generates and runs BoxStack 4. Do not #define DEBUG, since it is used in log4cxx (as log4cxx::Levels::DEBUG). YADE_DEBUG is used everywhere. If you ever get weird errors about that, #undef DEBUG after including qt3 headers. 5. Some code cleanups. Fix argv[0] for scons proxy. Don't warn on amd64 about limited type range in comparison. Initial .spec file for fedora 6 spec file works on fedora 6 now 1. Add screw example (this is really my personal one...) 2. Fix Shop::tetra (tetra interacting geometry still incorrect or incorrectly drawn... why???) 3. Remove python finalization, boost should take care of that. 4. Possibly fix plugin loader bug (first plugin after one with yadePluginClasses "inherits" them unless it has its own yadePluginClasses defined). Sync SConstruct with sid packages (lib for qglviewer is not in capitals there) 1. Tetrahedron fixes (intersections are correct now, but interaction still wrong - crashes??!) 2. Fix scons to work with versions > .97 (no syntax errors; didn't try if it compiles 1. Scons now updates itself to 0.97.0d20070918 (for testing purposes etc) 2. fix crash in ElasticContactLaw and ElasticCohesiveLaw if interaction is not SpheresContactGeometry and ElasticContactInteraction; this fixes tetrahedra interaction. 3. other tetrahedron fixes (still not fully functional yet) 4. mark attributes in class factory as __attribute__((unused)) to avoid warnings 1. Correct typo in comment in SConstruct 2. Correct formulas for 4hedron inertia, testcase passes 3. Switch to fixed timestep when timestep is being changed by user in the qt3 ui 1. Change default timestep to 1e-8, should prevent weidness onthe first run. 2. Fix cmdGui to handle gracefully reading/assigning nonexistent attributes (not crash, but caught exception) 3. Fix char* / const char * warnings from g++-4.2 in main() and OpenGLRenderingEngine. 4. Cleanup of triaxial test, added parameter noLateralConfinement (not yet used). 1. Default scons to latest snapshot (should be a bit faster) 2. Don't autocreate plugin path entries for paths that are not installed 3. Turn TriaxialStressEngine into state machine, while preserving the possibility of changing some values by hand in .xml. (should be tested). 4. Exclude Loki:: from doxygen docs. 1. Remove relicts of old Omega::logMessage &c 2. Remove duplicated Wm3 headers in core (is already in lib-yade-base) 3. Update python scripting (object.attribute is shorthand for object['attribute'] now) 4. Cleanups in triaxial, adding STATE_UNINITIALIZED 1. BIG update of python wrappers - enagines can be created, added, modified from python now. Examples will be provided very soon. 2. fix plugin loader for multi-plugins: YADE_PLUGIN() macro should be used in all plugins, otherwise due to linking symbol overrides a multi-plugin will shadow classes in the plugin actually being loaded (better solution?!) 3. Add experimental BREakable Frictional COhesive Moment-blocking material (brefcom for short), not yet fully done. 4. PersistentSAPCollider now can be parametrized to permit distant contacts (is not the default, though), incorporating Bruno's changes in DistantPersistentSAPCollider 5. Uniaxial strain-controlled test (USCT), a quite simple implementation. Used primarily for brefcom testing now. 6. Fix SConstruct so that it is backward-compatible with python2.4 1. Remove MetaEngine, inherit MetaDispatchingEngine directly 2. Fix unbalanced force reporting (thanks for complaining, Bruno) 3. EngineUnits now declare their types with FUNCTOR1D(.) or FUNCTOR2D(.,.) macros, dispatchers will ask for their types automagically. Old syntax still supported, once all engines have those types declared, it will be obsoleted and removed (like e->add("InteractingSphere","InteractingSphere","InteractingSphere2InteractingSphere4SpheresContactGeometry"); Shop and USCTGen usethe new syntax now. 4. Brefcom law passes some rudimentary tests and appears to work (normal force and damage for now; we miss shear force and blocked rotations) 5. Brefcom contact can be drawn with opengl, damage signified by color. 6. Updated Python wrapper. 1. Remove the old prototype for adding EngineUnits to dispatchers add(string,string,string) 2. All EngineUnits now have FUNCTOR1D(...) or FUNCTOR2D(...,...) in their headers. 1. Preliminary recovery of simulation accross runs (try sending SIGHUP to yade) 2. Python wrapper for body parameters. 3. Misc fixes of engines. 4. Allow Omega to stop at predefined iteration number (for regression tests, I work on that ;-) ) 5. ... Fix lefover of experimental code in TriaxialCompressionEngine (creating sohesive links after compaction. Updated brefcom, with undisclosable parts moved outside the tree. Fix typo breaking compilation on different machines than mine. 1. Remove "../data", replace by "./" (cwd) or "". 2. Some other stuff I don't remember. 1. HUGE update of python, it wraps almost all yade classes now (except PhysicalActionContainer and InteractionContainer). Documentation is lacking as well. 2. Move most of init stuff of python from cmdGui::run to cmdGuiInit.py (which I forgot to "svn add", will commit shortly) 3. Run ipython shell (or python shell with history and tab completion as fallback) inside yade Adding init code as advertised in last commit. Passing exclude='qt3' to scons will disable qt3 checks and everything related to qt3. Note that the default GUI is still QtGUI unless you change that in your config file or at command line with -N. Avoid using boost1.34 python/stl_iterator.hpp, should compile with 1.33 now (debian etch, ubuntu feisty) Fixes and addendum for openSuse based on comments by Sega 1. Test suite for all preprocessors 2. Added assumeElasticSpheres attribute to GlobalStiffnessCounter; if set to false, dynamic_casts are used to properly determine type of contact between spheres (used in USCTGen now) 3. Debugger now dumps backtrace without interruption and exits immediately (core is still dumped) 1. cmdGui now exports yade binary name (to allow spawning subprocesses) 2. default-test.py handles crashing simulations gracefully (by executing them in a subprocess) 3. Omega retains original argc and argv from the command line. 1. Remove NaN's in PersistentSAPCollider for bodies that don't have bounding box: use zero-size degenerate box at its position instead. (fixes warning when running ClumpTestGen) 2. Remove check for scientific python from scons as we don't use it now. 1. BIG: Moved latest upstream qglviewer to our tree. If you REALLY need to use the packaged version, let me know. As of now, there is no way to do that. 2. BIG: Removed many (unfunctional) parts of the qt3 GUI, in the view of qt4 migration which will probably not happen. Maybe I removed too much (simulation player -- does it work?!), let me know in that case. 3. Experimental GLViewer support in python, disabled. (broken and needs qt4) 4. Misc fixes related to that and other small stuff. Previous commit didn't compile on clean environment, fixed now. HOTFIX: final fix for the libqglviewer linking issue.... I hate qt3, I hate uic, I hate moc. It works now, please test on you machines and let me know if there are any regressions. Forgotten files, build crashed on foreign machine. Fixed now. (This stuff is not tested, do not use!) OK, now QGLViewer REALLY builds. Was bug in scons: file.ext1.ext2 is not handled properly with the qt3 tool. Created symlinks replacing first dot with _. Add tags, doc and clean "targets" to scons do their job, short-circuit the rest of building process and exit. Clean only removes builddir, _not_ installed files as -c would do. (LP: #202067) 1. Resurredced and un-crapped simulation player (for making videos). Updated howto is http://yade.wikia.com/wiki/New:Making_videos#Using_Simulation_Player_with_svn.3E1281 1. Include fracture strain calibration routines in brefcom. 2. Add BrecomDamageColorizer - changes colora ccording to average cohesive interactions the body has. 3. Delete non-cohesive interactions from inside BrefcomLaw when spheres become distant. 4. Documentation updates. 1. MAJOR improvements of the python wrappers (constructors take attributes etc.) 2. FIRST proof-of-implementation simulation completely created in python: scripts/simple-scene.py. This file will be commented abundantly shortly. 3. Add default values to some bool params so that there is no serializer error if they are uninitialized. 4. Add aabbEnlargeFactor to InteractingSphere2AABB (should be added to InteractingBox2AABB as well?) (not tested yet) 5. rename InteractionDetectionFactor to interactinDetectionFactor 6. Serialization now registers only attributes that have not yet been registered (there were problems with python because of that: at first save, attributes were duplicated and the xml file was less readble, although loadable) 7. Scan .tpp and .ipp for c++ tags as well 8. Some documentation. 1. New engine attracting bodies towards a fixed axis (constant force, points towards the axis everywhere) 2. some fixes in python wrapper (run initializers if needed; the logic in yade itself is currently broken however) 3. New python "example" for creating cylindrical packing of spheres 1. allow saving spheres (format like small.sdec.xyz) from python 2. PositionOrientationRecorder can optionally record RGB color of each body as well now 3. SimulationPlayer now can change colors of bodies, if the .rgb file exists 4. Bunch of fixes if brefcom, in UniaxialStrainControlledTest 5. dt moved from Omega to MetaBody for good; this allows preprocessor to set timestep that will be used when the simulation is loaded. 6. Code cleanups here and there. Update debian control to possibly fix deb package builds Fix header scanner if there are extra headers in the tree. 1. PROTOTYPE CHANGE: Engine::action(Body*) changed to Engine::action(MetaBody*); all engines adapted 2. MetaBody::miscParams can be used to set static variables at deserialization (used for GLDrawBrefcomContact now) 3. allow serialization of shared_ptr (only pointers to derived classes were enabled until now) 4. big fixes inside brefcom 5. enhancements of python interface (Generic class, fixes) 6. added Shop::GLDrawLine, Shop::GLDrawArrow, Shop::GLDrawNum 7. fixed QGLViewer #defining DEBUG (qt3) by #undef'ing it after qt3 headers inclusion 1. Add many checks to see where XML and BIN serializers take each other's work (non-intrusive). 2. reorganization of cmdGui (will be renamed - perhaps - to pyUi or similar) 3. BINFormatManager is now optional and _disabled by default_. feature 'binfmt' must be enabled to build the plugin. 4. Added utils classes in python, examples are much simplified now. Fix python2.4 compatibility fix of the previous fix. 1. Commit before other changes. Renamed cmdGui to PythonUI. Tests seem to be working now. 1. Define macro NEEDS_BEX() that engines can use to advertise what physical actions they need. This avoids the necessity of passing that to PhysicalActionContainerInitializer. The change is backwards-compatible, all present engines were adapted however. 2. Fix BrefcomTestGen and USCTGen: don't crash anymore. 3. Small fixes in python code. Svn wants to commit before moving files... :-| Refactored MetaDispatchingEngine to MetaEngine (everywhere) Support saving to .xml, .xml.gz and .xml.bz2. Loading works as well. 1. Added heavy comments to simple-scene.py, started functionally equal code in extra/SimpleScene.{c,h}pp 2. Added dependency on boost::iostreams to debian template (for gzip/bzip2 streams). Hotfix for file loading logic. 1. Ignore ^C in PythonUI (gives crashes) 2. Shop::Bex caches physical action indices in static variables (not yet benchmarked) 3. Check for boost/foreach.hpp in scons and suggest how to install it. 4. Fixes in Brefcom 5. Set initial qglview size to 550x550 Big scons updates: Fix compilation problems and some warnings in the previous commit. 1. BOOST_FOREACH now works with InteractionContainer and BodyContainer and is #defined as foreach. 1. GUI change in qt3: make the simulation controller to take _much_ less space (no LCD displays, notably) and allow more place for 3d views. If someone is not happy with that, gui/qt3 can be reverted. 2. UniaxialStrainController now measures and records transversal strain. 3. Add attempt at smart command-line completion for ipython console. 4. Improved the class index cache in Shop::Bex (benchmarks needed) 1. Added MetaBody* as optional argument to Body::byId, to avoid Omega::instance().getRootBody() lookups - took a few percent of time in valgrind-profiled run. 2. Completed SimpleScene, a script to generate commented source py2wiki.py and put results on http://yade.wikia.com/wiki/SimpleSceneTutorial 3. Swap argument order in utils.sphere and utils.box to match Shop::sphere and Shop::box (center first, then radius/extents and not vice versa as it used to be) Make the GUI loader/saver a bit less intelligent. .yade, .xml, .xml.gz, .xml.bz2 are accepted when both loading and saving (hopefully). 1. Fix tabs in SConstruct. 1. Rudimentary plotting support through python; see http://yade.wikia.com/wiki/PythonPlotting and scripts/simple-scene-graph.py for details 2. Updates in examples/cmdGui1.py 1. QtGUI now integrated PythonUI if available. This means simulation can be controlled both from the QtGUi and the console. The + standalone PythonUI is available as well. 2. Changed form workspace to MDI windows; main window is menu-only thing that hides when simulation is open. 3. other minor changes. 1. Replace foreach with FOREACH, based on discussion with Janek. 2. Fix FileGenerateor message crash without workspace. 3. Fix TimeStepper choosable at the same time as fixed timestep. There is some problem with switching back to timestepper, use Omega().usesTimeStepper=True/False from python for now, please. 1. Merging containers and iterators and iteratorpointers to one .hpp. (Proxy headers created, no existign code affected) 2. Moving default containers for interactions, bodies and physicalAction to core; therefore, omega is constructed with out-of-the-box usable Metabody. (no code affected, though some initializations are now redundant) 3. Dropped workspace mode of QtGUI, since for some weeks no-one complained. 4. Big simplification of QtGUI (no dynamic menus and the like), to allow Views to be opened from python, independently of the simulation controller and so on. 5. Initial wrapper for gui stuff in python, in module yade.qt (View(),Controller(),center()) 6. Exiting the python interpreted (^D, quit()) will close yade main window as well. 7. Moved python wrapper helpers to gui/py/pyAttrUtils.hpp to be usable from elsewhere. Fix: move files properly (with svn) Forgotten add. MetaBody still needs InteractingGeometry, BoundingVolume and PhysicalParameters initialized. But that would mean moving more classes to core, which is questionable. For now, fix that in Python and still rely on user init in other code (as before, but less). 1. Fix passing .py to PythonUI (local vars shadowing global ones); fixes default-test.py as well. 2. Fallback to PythonUI automatically if $DISPLAY is not defined in shell 3. Ignore closing YadeQtMainWindow if it doesnt exist instead of throwing exception 1. Arguments can be passed to scripts, propagated as yade.runtime.args 2. default-test more robust, with e-mail report Update testing script for automated tests Micro fix in python script. Cleanups for ubuntu package builds. 1. Other fixes related to deb packages (jobs=1 because of fakeroot :-( ) 2. transversal strain sensors max speed can only be positive in USCT Fix for crashing from python-constructed rootbody. Hopfully other things are not broken. Fix player crashes (dialogs should have no parents) Fix setting timeStepper / fixed timestep from the qt3 ui Build qt3 with python disabled cleanly. 1. Add std::string Engine::label, which user can set to identify an engine by arbitrary label 2. Fix warnings in SpatialQuickSortCollider 3. Set isDynamic in rootBody by default 4. Remove PlotDataGetter, superseded by PeriodicPythonRunner 5. New abstract class for running things in predefined periods PeriodicEngine Forgotten file. 1. Fix sync mode in qt3 ui. 2. Fix real time display in qt3 ui. 3. Fix resizing of controller in qt3 ui (childs expand now) 4. Remove deprecated PythonRecorder (superseded by PeriodicPythonRunner) 5. Initialize Omega timers also at construction-time rather than simulation-loading only as it used to be. 6. Don't print bogus values of iters/sec in controller at startup. 1. Fix the priodic engine. 2. Add layout to the main windows with 4 buttons. 3. Fix PeriodicPythonRunner plugin installation 4. Update simple-scene-graph.py so that it works after latest plotting changes. 1. Add the possibility to save as gnuplot figure to the plotting interface 2. Add PeriodicEngine (fixed period) as opposed to RangePeriodicEngine (variable period) 3. Make BrefcomDamageColorizer subclass of PeriodicEngine instead of doing Omega::instance().getIteration()%interval==0 4. Change some icons in the UI 5. Add 'scale' parameter to loading spheres from file 1. Fix segfault in GUI exception trap 2. Handle .xml.gz and .xml.bz2 (in addition to .xml) as command-line argument in QtGUI Small fixes here and there. Merged from https://code.launchpad.net/~yade-team/yade/t2. All changes here: 1. Add list MetaBody::tags, which hold metadata in the key=value format; this is to workaround current serialization that errors out on map in findType. 2. Add meanStress and vlumetricStrain to Triaxial for python access 3. add neverDamage flag to Brefcom which forces elastic behavior regardless off strain magnitude. 4. Fix some warnings in STLImporter regarding signedness 5. add __attribute__((unused)) to a variable in Wm3Matrix3 that was not used during optimized builds - to avoid warning. 1. Fix tagging, do it as list; must NOT contain spaces! 2. Adapt python to that (replaces spaces automatically and so on) Forgotten variable after move Fixes for g++-4.3 compatibility (still some warnings, may need -fpermissive to compile) 1. Small fixes here and there 2. In brefcom, make the parameter set richer 3. PythonUI now may be run in non-interactive mode (batch processing) 4. new function Omega().interactions.nth(n) for getting random interaction Microfix: unitialized diffuseColor in boundingVolume 1. Remove failing and unused screw generator. 2. Add rate-dependence variables to brefcom 3. Add raw attribute access from python, like this: gm=GeometricalModel("Facet") gm.setRaw('vertices',"[{1 2 3} {4 5 6} {7 8 9}]") to provide access to attributes the current wrapper doesn't wrap properly (i.e. anything beyond numbers, bools, strings and flat arrays of numbers: e.g. vector, as in Facet). The format of the raw string is that of XMLFormatManager. 1. Triaxial now calculates thickness of the walls automatically (from the extents of the first wall) 2. Add flags to scons, for the gold linker (doesn't link plugins, oh well) 3. Many fixes in brefcom 4. flag needsInitializers moved into MetaBody and it is checked at every iteration; assigning initializers from python sets this flag. It is set to true in MetaBody constructor. 5. Added wrapper for postProcessAttributes in python, for Facets. Serializable::postProcessAttributes was made public due to that (don't kill me). 6. A few things added to yade.utils 7. SimulationController now correctly handles decreasing timestep mantissa from 1 (goes to 9 and exponent is decreased by 1) and increasing it from 9. 1. Parallel engine that runs engines in parallel; python wrapper for that 2. Added simple-scene-parallel.py for an example (sppedup of 16% over simple-scene.py) 3. Added openmp flags to scons (enabled by default) 4. Simultaneous builds of multiple profiles: scons profile=default,opt will run all the machinery 5. Refactored grid-drawing code; press 'x','y','z' to have grids with that normal 6. Ported some things from GLViewer to GLSimulationPlayerViewer (grid and mouse events) 1. Fix the simple-scene-parallel.py (interactionGeometry and interactionPhysics may _not_ be run in parallel) 2. Fix the #pragma clause in ParallelEngine (it did not run in parallel before, but still there were performance gains ?!) 3. When saving simulation, set the filename so that it can be reloaded from the GUI 1. PositionOrientationRecorder save to bzip2'ed files now; player is able to read both compressed and uncompressed files for backward compatibility. 2. Unsucessful try to use glut for drawing spheres; left as an option that is disabled by default, however. 3. Fixes in brefcom 4. Simulation player now has progress window and supports strides when loading files. 5. Fix wrong timestamp format in saveGnuplot in gui/py/plot.py (%m instead of %M) 1. Implemented clipping planes in the GL viewer and GL renderer 2. Moving body is now consistent with mouse bindings for moving scene Remove duplicated code in GLSimulationPlayerViewer, subclass from GLViewer. Median centering routine (for cases where one body went nuts and centerScene will make the whole a small point) - activated with Alt-C (both controller and viewer) 1. Added displacement and rotation scaling to the OpenGLRenderingEngine (accessible from the renderer config). Documentation missing as of now. Displacements from reference point will be scaled in the rendering. At this moment, only geometrical model and a few other elements of the GL display are scaled. 1. Double-click aligns clipping planes if manipulated, x,y,z aligns normal with axis. 2. Default number of clipping planes is 3 instead of 2 now. 2. Fix compilation error in ResetPositionEngine (missing logger) 1. sqlite3-dev is now required for yade (checked by scons) 2. sqlite3x c++ sqlite3 wrapper is in our tree (5 files, about 60kb of code), built by scons 3. Check for boost::regex 4. Experimental SQLiteRecorder (the player side is not yet supported at all!) 5. Remove messages at startup about singletons (define YADE_DEBUG env var to get them again) 6. StretchPeriodicEngine that will eventually replace the overdesigned RangePeriodicEngine Fix logger error at startup. 1. Remove some warnings about persistentInteractions initialization (initialization is done in MetaBody constructor anyway, it can not break anything); 2. Remove long obsolete pyade 3. Add Omega::getComputationTime 4. Add Omega::saveSimulationToStream and Omega::loadSimulationFromStream 5. Improve the SQLiteRecorder: put simulation into the db, create table with pointers to other tables, declare column types. The recording part is almost done. Implementation in player is to be done. 1. Move FiltrEngine from core to common (I know this is arguable, given that DeusExMachina and StandAloneEngine are in core as well. I think, however, that common is a more appropriate place since these engines are not as important...) 2. Completed the recording sqlite part 3. Completed the player part of sqlite (in player) 4. Added libsqlite3-dev to debian build depends 5. PeriodicEngine now is activated when the priod is reached, not afterwards (> vs. >=) Other fixes. Please not in GLSimulationPlayerViewer I had to comment one line that I didn't know how to make compile (dynamic-Cast). 1. Set iteration number with sqlite as well 1. Views, Player, Controller and Generator can be opened asynchronously from python now. 2. Revamped the player UI, hopefully making it better. 3. Added Alt-S for saving viewer state (camera etc) 4. Added runPlayer to python for aotomatized replay. The file contains instructions for offscreen thereof rendering as well. Re-enable sega's code that doesn't compile here. 1. qtCreateVideo creates video from the replay snapshots directly (using gstreamer) 2. qt.{Player,Generator,Controller} now take an optional bool; if true (_not_ default), they will not return until the requested window will have been created and ready. 3. runPlayer return wildcard and list of snapshot files now 4. Adapted simple-scene-player.py to reflect that. Fix linking issue. 1. new class DisplayParameters is a string->string "hash map" for holding various configurations. 2. Multiple display parameters (i.e. QGLViewer config and OpenGLRenderingEngine config) are stored in Metabody. 4. stopAtIteration moved to MetaBody so that it is saved and reloaded (MetaBody::recover hack is gone) 5. Remove unnecessary persistentInteraction initializers so that we have less warnings 6. Remove deprecated RangePeriodicEngine, plotting uses StretchPeriodicEngine instead. 7. Add the ability to load from stream and save to stream to IOFormatManager (afterwards, I discovered there were actually_identical_ functions loadArchive and saveArchive; those were deleted since unused elsewhere) 8. GLViewer can now load and save state from/to string 9. in GLViewer, keys 7,8,9 load display config #0,1,2 and Alt-{7,8,9} saves current view to #0,1,2. Those are saved in MetaBody. BUG: after loading such config, the QtGUI-generated OpenGLRenderingEngine config dialog doesn't work anymore (probably some issue with archives, not sure). 9. Hopefully all xml-parser-offensive characters are escaped at string serialization (and unescaped at deserialization). We use standard SGML escapes (newline=&br; tab=&tab; <=< and so on) now. This allows to save XML-as-string in the xml itself (used for DisplayParameters). 10. GLViewer now properly saves what XYZ planes are displayed (using custom DOM element) 11. Some fixes in Brefcom, more to come. 1. Fixes in Brefcom 2. Add docstrings to QtGUI-python functions; 3. StretchPeriodicEngine adjusts both limit and period if one of them is nonzero 4. Other fixes in python interface Sync with upstream QGLViewer 2.3.0 1. Experimental scale drawing in GLViewer (disabled by default) 2. Fix (?) race conditions (?) in Omega and python 3. Other fixes in plotting, works now again (remove numpy.array, not needed) 1. Merge all gravity engines (central, axial, directional (the old one)) into 1 header 2. Cleanups of pkg/common/SConstript 3. Add more brefcom accessors (Fs, Fn, ...) 4. Fix (hopefully) threading issues with python; Omega().pause and other now use Py_BEGIN_ALLOW_THREADS to avoid deadlock of the interpreter if called from c++ interpreting python code 1. Add private functions to eudoxos.py 2. Fix a few broken includes Fix #includes in affected files. Forgotten file. Second file that was forgotten :-| 1. Omega().interactions.nth returns n-th real interaction 2. Export more brefcom data to python 3. Make LOG_DEBUG and LOG_TRACE no-op in NDEBUG (optimized) builds 4. Change linebreaks from CR+LF to unix LF in a few random diles 5. Make DeusExMachina registerAttributes of Engine 6. Serializer prints offending value if bad_lexical_cast. 7. Attempt to catch exception if reading bool written as int (254 etc) and consider that "true" (doesn't work?) 8. JumpChangeSe3 can set (angular)velocity if requested instead of plain moving 1. Move refSe3 and dispSe3 to PhysicalParameters; this should speed up the renderer (clipping is done only once etc) and make it possible to apply clipping (via PhysicalParameters::isDisplayed) in GL functors for interactions as well. 2. Add button for setting reference positionts to the controller 3. Remove warnings in NewtonsDampedLaw 1. Cleanups in OpenGLRenderingEngine, use systematically clipping and scaling 2. Merge GL functor abstract class headers in one (GLDrawFunctors.hpp) and fix files using them 3. Make qt-serialization layout a bit more space-conservative. 1. Fix default test to report errors during simulation loading as well 2. Fix preprocessors derived from triaxial to not initializa transienInteractions, preventing crash discussed with Bruno: containes for transient and persistent must be the same, for unknown reason. 3. Fix preprocessors using brefcom to define all variables, otherwise we have nan's in XML which crashes the deserializer. Add script for making release Improved the release script. 1. Add the ability to block individual DOFs on every body in PhysicalParameters and implement relevant code in both NewtonsDampedLaw and LeapFrog integrators 2. Add that to UniaxialStrainer 3. Put both CundallNonViscous damping classes in one file (cleanup), adapt all files to that 1. Make the -x flag to PythonUI stop even if no script is provided. Fix missing lib (error at startup) ================================================== yade-0.10.0 Tue Jul 11 13:17:04 2006 +0000 Janek Kozicki (55): small correction in INSTALL, renamed ayde-data to yade-examples, and tagged it. added schedule added some more instructions to INSTALL file I am committing new version of docs, checked by my sister. And briefly checked by me. Olivier, now you can work on it, and remember to copy & paste lots of text and explanations from the article! directory for creating packages with yade. directory for Czesiu to work on debian packages for yade. later we can add directories for .rpm or gentoo, etc.... Fixed some broken symlinks. Removed unnecessary int i from MetaBody loops added missing -lglut yade is compiling with boost 1.33, but I had to make some modifications in ErrorTolerantLaw <- it is compiling, but I'm sure that it Jacobian is WRONG !! - Oliver it's your code, only you can fix that. some small changes, before I start something bigger ClassFactory is one step closer to windows version I have started changes that will make yade compilable on windows another step closer to compile yade on windows: to compile yade on windows we have to remove symlinks. I started doing that, but it's not finished. I have to commit in the middle still removing symlinks... symlinks removed. but it is NOT compiling now deleted all useless directories modyfing all .pro files and Makefiles so that it will compile maybe it is compiling... so symlinks are now removed, and it is compiling and installing exactly like before. So I have started compiling it on windows, and I stopped on the problem that linker cannot link serialization with factory. General cleaning of all header files. Now they are much more readable, and more coherent with C++ coding standards. added SDECMovingWall FileGenerator contributed by Andreas Plesch. You have decided to rename class: Box2AABB to a new name: InteractingBox2AABB You have decided to rename class: Sphere2AABB to a new name: InteractingSphere2AABB D yade-packages/yade-package-common/src/Engine/EngineUnit/Box2AABB/Box2AABB.pro D yade-packages/yade-package-common/src/Engine/EngineUnit/Box2AABB/Box2AABB.cpp D yade-packages/yade-package-common/src/Engine/EngineUnit/Box2AABB/Box2AABB.hpp A yade-packages/yade-package-common/src/Engine/EngineUnit/Box2AABB/InteractingBox2AABB.pro A yade-packages/yade-package-common/src/Engine/EngineUnit/Box2AABB/InteractingBox2AABB.cpp A yade-packages/yade-package-common/src/Engine/EngineUnit/Box2AABB/InteractingBox2AABB.hpp D yade-packages/yade-package-common/src/Engine/EngineUnit/Sphere2AABB/Sphere2AABB.cpp D yade-packages/yade-package-common/src/Engine/EngineUnit/Sphere2AABB/Sphere2AABB.hpp A yade-packages/yade-package-common/src/Engine/EngineUnit/Sphere2AABB/InteractingSphere2AABB.pro A yade-packages/yade-package-common/src/Engine/EngineUnit/Sphere2AABB/InteractingSphere2AABB.cpp A yade-packages/yade-package-common/src/Engine/EngineUnit/Sphere2AABB/InteractingSphere2AABB.hpp D yade-packages/yade-package-common/src/Engine/EngineUnit/Sphere2AABB/Sphere2AABB.pro I hope that scripts to rename classess are still working okay :> ErrorTolerantContactModel -> ErrorTolerantContact AABox2Sphere4ClosestFeatures -> AAInteractingBox2InteractingSphere4ClosestFeatures Box2Box4ClosestFeatures -> InteractingBox2InteractingBox4ClosestFeatures Box2Sphere4ClosestFeatures -> InteractingBox2InteractingSphere4ClosestFeatures Box2Sphere4ErrorTolerant -> InteractingBox2InteractingSphere4ErrorTolerantContact Box2Sphere4MacroMicroContactGeometry -> InteractingBox2InteractingSphere4SpheresContactGeometry Sphere2Sphere4ClosestFeatures -> InteractingSphere2InteractingSphere4ClosestFeatures Sphere2Sphere4ErrorTolerant -> InteractingSphere2InteractingSphere4ErrorTolerantContact Sphere2Sphere4MacroMicroContactGeometry -> InteractingSphere2InteractingSphere4SpheresContactGeometry continued... this renaming is DONE GLDrawInteractionBox -> GLDrawInteractingBox GLDrawInteractionGeometryFunctor -> GLDrawInteractingGeometryFunctor GLDrawInteractionSphere -> GLDrawInteractingSphere GLDrawInteractionGeometrySet -> GLDrawMetaInteractingGeometry renaming continued.. renamig DONE Serialization::registerAttributes() is now protected, as it should be. renamed MetaBody::actors to engines added: QtEngineEditor is not working - doesn't detect correctly StandAloneEngines - can save simulation from simulation controller - can change background color of the simulation displayed binary serialization for yade - looks like it is working :) fixed one bug in BINFormatManager fixed savig ElasticContactParameters and SpheresContactGeometry, so that after loading the simulation is not going crazy added several try/catch blocks removed GLAPI and GLAPIENTRY from OpenGLWrapper XMLSaxParser is now better at counting lines I have just enabled commit-email.pl for SVN. we should get info about commits for yade-dev, I wonder it if works :) not so small bugfixes and imprvements: - fixed serious bug in bool BodyRedirectionVector::erase(unsigned int); - added function bool exists() const; to BodyContainer - graphical display now has reversed meaning of scrollwheel, so it is now similar to behaviour of blender and autocad - SDECLinkedSpheres is working after some (surprise!) quick hack. That part is still waiting for total rewrite. - big improvements in lattice model. beam bending works (but not always) with axis+angle notation. maybe with quaternions it will be better. There is a problem that shows itself when using regular triangular grid. - added function Quaternion::power(RealType) - lattice bending works now in 2d (X-Y plane), using angles. not vectors or quaternions. I will do 3d later. - 'cancel' when loading filegenerator is not crashing now - started non-local model for lattice - hopefully 2d lattice beams are now okay. middle button in 3d view is now "PAN" command, not "ZOOM" as it used to be. It's more similar to AutoCAD 'yade -h' will now display compilation flags (currently only DOUBLE_PRECISION - how about adding long double ;) ?) small fix so yade works with lates kubuntu: Threadable class has been removed: - when yade is idle 0% of CPU is consumed by the display, - when simulation is ran it is in separate thread, second processor (if present) is drawing on the screen, - with two processors theoretically QtGUI should have speed equal to NullGUI, - drawing on the screen is not synchronized, so some bodies are from previous iteration, some are from next iteration (later there will be an option to turn synchronization ON - it will slow down computations a bit) Renaming SimulationLoop to SimulationRunner. renaming finished. SimulationLoop -> SimulationRunner deleted yade-lib-threads, added SimulationRunner, that can start/stop or perform singleLoop of simulation in separate thread. final touches to SimulationRunner compilation fix yade-flat now allows to debug yade from inside kdevelop SimulationController is now updating iterations/time and sets time step correctly. updated INSTALL file (before release). Olivier Galizzi (10): Corrected one missing include into yade-package-lattice so that it is compiling Added new yade-spherical-dem-simulator plugin. It is compiling but I have not tested it for now. This plugin will be used to compare yade performances to a dedicated simulator that can only run simulation on spheres and which is not extensible. Corrected some symlinks (forgot to call symlinks -rcs) Improvement into SphericalDEMSimulator SphericalDEMSimulator is now working. But maybe an error remains because for now it is 10x faster than yade !! Started to add SimulationPlayer plugin to QtGUI Some small optimisation of Math library. Some if statement where remaining after an assert that were doing the same check. Improvement into SphericalDEMSimulator and QtSimulationPlayer. SimulationPlayer is now working; but there is no check to see is the files exists or not, so it is up to the user to be carefull (at least for now) Minor changes into NullGUI. ================================================== yade-0.09.0 Sun Aug 21 20:41:37 2005 +0000 Janek Kozicki (34): fixed some broken symlinks removed inifinite loop when loading plugins fixed setDebug and setRelease scripts. Set everything to debug. added yade-flat - it is very useful for debugging, because command: fixed some Makefiles - changed make to $(MAKE) corrected invalid usage of postfix increment++ changed Makefiles, so that qmake respects INSTALL_DIR and PREFIX_DIR corrected a mistake another correction in Makefiles still fixind Makefiles, should be OK now... So now I think that Makefiles are OK. But I've discovered a mistake in .pro, when trying to install to totally custom directory (for example /home/joe/work ). So I'm committing this before fixing that. so by default /usr/local/include is searched for include files. INSTALL_DIR and PREFIX_DIR modify this path automatically. So that there is no problem to install yade as a normal non root user, in home directory. all Makefiles are tested and are working. following commmands were tested (launched from trunk/ directory) I started renaming SpheresContactGeometry to MacroMicroContactGeometry, and also I am testing new fixed renaming scripts! still testing renaming scripts... after this commit I'll know if scripts are correct fixing item no. 35 in yade-core/.todo fixing item no. 35 in yade-core/.todo Finished fixing item no. 35 in yade-core/.todo renaming: SDECTimeStepper ElasticCriterionTimeStepper SDECTimeStepper -> ElasticCriterionTimeStepper finished SDECTimeStepper -> ElasticCriterionTimeStepper InteractionDescriptionSet2AABB -> MetaInteractingGeometry2AABB ./yade-scripts/renameClassFilenames InteractionDescriptionSet2AABB MetaInteractingGeometry2AABB ./yade-scripts/renameClassDirectories InteractionDescriptionSet2AABB MetaInteractingGeometry2AABB updated Changelog M trunk/Changelog D tags/yade-0.1 M trunk/Changelog preparing for release. is compiling with DOUBLE_PRECISION corrected instructions in INSTALLs text files. ok, I think that this version is ready for release added dependencies diagram started tagging releases. renemed top directory QtGUI to yade-gui-qt. Just like XMLFormatManager tagged all new releases! Olivier Galizzi (19): Better YadeQtMainWindow. Now menus are displayed in menu bar in the same order they are added in the program. Added preferences for QtGUI. Now it remembers size and position and exit. Now it is possible to choose time step from GUI. Created directory yade-plugins to store all yade unfisnished plugins Added iteratortest to test iterator on polymorphic container Improved version of polymorphic iterator Splitted files in iteratortest so there is one class per file I have introduced iterators for all types of containers in order to remove the dirty gotoFirst, gotoNext, hasNext and notAtEnd methods. Now it looks like more like stl iterators. It is compiling but unfortunately it is segfaulting at runtime, probably some problems in ++,* operators. Yade is now working with iterator. At least all configurations generated by some FileGenerator are working. Added some improvement into GLEngineEditor. For that I have rewritten a big part of yade-lib-opengl because it was not possible to delete windows without corrupting the id of the others windows. Added Makefile to compile and install all packages at once. Better design : added class related to MetaEngine. Removed all references to Serialization stuff into yade-lib-multimethod. They are now into newly added classes. Some cleaning in MetaEngine hpp and cpp. Some improvement into QtEngineEditor Improvements into QtEngineEditor. Now it is olamost possible to create graphically a simultation loop.Now I have to make something to set parameter of MetaEngines. Improvement into QtEngineEditor Moved REGISTER_CLASS_NAME from Serializable to Factorable and started to add REGSITER_BASE_CLASS_NAME to all Factorable classes. With those two macro we'll be able to build into Omega for example the class tree and then reuse it everywhere it is needed. For example it will be usefull to create GUI that want to deal only with this or this type of class. Now all classes register their base class name. Removed unused classes. Fixed some compiletion errors. Added Makefile to compile the whole trunk at once. Improvement int QtEngineEditor. Now only correct EngineUnit and base base are displayed thank's to the REGISTER_BASE_CLASS_NAME macro. Simulation loop editing is now working. There are still some random crashes to fix and for now it is not possible to load a simulation loop. ================================================== yade-0.08.7 Thu Jul 7 11:14:08 2005 +0000 Janek Kozicki (10): renamed mscv to msvc first version of Makefile for yade-lib-loki Makefiles are better added Makefile for all libs Makefiles uninstall correctly make install works for all yade-libs (weel, actually oinly three of them now) looks like commands: small correction in docs small fix for make clean after name change of libXMLManager we had to correct Makefile Olivier Galizzi (77): Created yade-libs directoryt to clean a little the directory tree Created directory yade-packages Created directory yade-empties Created yade-empties directory Removed directories yade-lib-rendering-engine-opengl and yade-lib-gui-qt because they now have no reasons to be there Renaming directory yade to yade-core Modified yade-empty-lib directory structure Added Makefile in yade-empty-lib Added README file into yade-empty-lib/doc/ Created yade-lib-qhull Created yade-lib-loki Added symlinks for yade-lib-loki Created yade-lib-wm3-math Making symlinks relative Created lib yade-lib-threads and yade-lib-time The lib yade-lib-factory was also created but is not working because it needs the lib-loki to be installed Creaed libs yade-lib-swiftpp, yade-lib-multimethods. Corrected yade-lib-time, yade-lib-factory Created libs yade-lib-opengl and yade-lib-algorithms Created libs yade-lib-serialization and yade-lib-computational-geometry Created libs yade-lib-serialization-xml and yade-lib-serialization-qt Corrected #include statements to point to yade/yade-lib-* instead of just yade-lib* removed old yade-empty-package Added new yade-empty-package which is simpler than previous one Started to add yade/ in all include statements Finished to change #include statements Sub-Project libraries removed from yade-core Yade is now compiling with separate libraries projects Yade is now compiling AND running !! :) Changed yade-empty-package so that all subdirectories contain not plural names. Added empty kdevelop project into yade-empty-lib Added script changeLibName into yade-empty-lib, which allow the user to change the name of kdevelop project, this is easier when you wnat to start to develop a library Added script into yade-empties so that it is easier to start to develop a new package of lib. Added yade-empty-gui Improved version of scripts that change the name of empties. Started to make yade-core clean (without all plugins) Making yade-core clean. Making yade-core clean yade-core is clean. When asking for default gui, QtGUI is choosen when return is pressed. Created project yade-guis/QtGUI Modified make clean to remove kdevelop temp file Cleaning yade-packages before splitting First version of yade-package-common with containers and DataClasses ok, but for Terrain which is not compiling Moved Terrain to sandbox because it is absolutely not finished yet. Some cleaning in yade-tmp Added PersistentSAPCollider and SAPCollider to yade-package-common Engine Created yade-package-fem and yade-package-dem Intermediate commit because I have put Ellastic*Law into Engine and not Engine/StandAloneEngine Continuing splitting of yade. Removed unused directories Created package yade-package-realtime-rigidbody, yade-package-lattice, yade-package-mass-spring All data classes are almost splitted Prepocessor are all splitted, so as DataClasses removed useless directories Splitted all InteractionSolver Added forgottent LatticeLaw StandAloneEngine Removed unused directories All MetaEngine are now splitted Removed unused directories Splitted engine unit to yade-common Moved directory previously moved to wrong place Temporary commit because of wrong svn mv Moved directories that were in wrong place All BodyEngineUnit are now splitted Removed unused directories Temp commit because of error Everything is splitted but RenderingEngine and NarrowInteractionGeometryEngineUnit Removed unused directories Everything is now splitted but RenderingEngine Removed unused directories Splitting yade-core finished. Maybe some changes will be needed in the future. Of course now we have to make compile the new projects ! Removed unused directory. Fixed symlinks error into yade-core yade-core and yade-common are compiling Yade-package-dem is compiling yade-package-fem is compiling yade-package-lattice is compiling yade-package-mass-spring is compiling yade-package-rigid-body is compiling So all packages are now compiling, but I still have to check for build order. Yade is now compiling and running Changed build order of yade-package-common so that it is compiling on the first try. ================================================== yade-0.08.6 Fri Jul 1 15:36:47 2005 +0000 Olivier Galizzi (14): Added some functions into yade-lib-computational-geometry Started to and graphical interface for buliding simulation loop and initializer list Added ommitted files QtEngineEditor.* Added unused class MetaEngine, MetaDispatchingEngine2D and MetaDispatchingEngine1D. I wanted to make something easier for the user but it is not working now. Updates into QtGeneratedCodeGenerator.ui. Started to implement the GUI Updates in Omega : now foreach plugins we store if it is indexable, factorable or serializable. Improved code generation : Data class, engine and dispatching engine works. No stuff is done for generation of kdevelop projects. All libs use now $(YADEDYNLIBPATH)/yade-libs directory as output path. Some changes had to be made in most of the .pro files. Added a preference editor in QTGUI Attempt to use Yade without environnement variable. I am closer to the solution but it is still not working. Small change so that yade is compiling with qglviewer 2.0 No there is no need to set up and environnement variable to run yade. Of course because the libs are still into a local dir your LD_LIBRARY_PATH should point to this local folder. Yade is now working and compiling. Started to move yade-lib-gui-qt back to plugins yade-lib-gui-qt moved back to plugins Yade is now compiling fine. I removed qyade and nyade since they are not needed anymore. Added user manual without pictures. But .tex is compiling well. ================================================== yade-0.08.5 Sun Jun 19 23:01:35 2005 +0000 Janek Kozicki (5): it's compiling now with gcc 3.3 added warining in Omega.cpp when trying to scan non existing directory for dynlibs added missing 'virtual' to destructors of some classess - I had random compiler/startup errors because of that. openGL problem fixed - yade must link with -lglut, and display works. Lattice works with rods, but dirty, of course - as always when something is implemented for the first time :> Olivier Galizzi (6): All EngineUnit now link with their corresponding MetaEngine otherwise findType doesn't work ! Why ... I don't know ! :) Because EngineUnit only use the abstract class contained inside corresponding MetaEngine dynlib and nothing else ... To make yade deserialization working I had to modify PointerHandler.tpp around line 90. Basically I just remplaced a dynamic_cast by a reinterpret_cast. We have to find why dynamic_cast is not working anymore ! Added some dynlibs for handling swept sphere volume. Tetrahedron are almost working. A bug into SWIFT is still causing troubles. Some changes into SweptSphere and Tetrahedron Putting some stupid name for 2 enum in Typelist.hpp (l 518 and 520) so that yade is compiling with gcc 4. Gcc 4 do not accept anymore anonymous enum type ! ================================================== yade-0.08.4 Mon May 23 12:51:50 2005 +0000 Janek Kozicki (1): added -lpthread in some places, so it is compiling with g++ 3.3 Olivier Galizzi (17): Removing MarchingCube GeometricalModel (because it is not a GeometricalModel !) Renaming PhysicalActionEngineUnit to PhysicalActionDamperUnit and PhysicalActionApplierUnit Added class Preferences that contain directory to dynlibs. Modification of library yade-lib-factory so that it is using multiple directories to look for requested dynlib Removing remaining directory toolboxes Started to split include files of libraries into different directories. Started to change all #include "lib" to #include Corrected some wrong #include stuff All include files from plugins/pre and post processor are now into include/yade-common. This is not final destination but it is closer to want we want. All #include stuff are modified according to this new structure. Added script countNbLines that count the number of lines of code of the project Starting renaming SimpleSpringLaw to FrictionLessElasticContactLaw Renaming SimpleSpringLaw to FrictionLessElasticContactLaw Renaming SimpleSpringLaw to FrictionLessElasticContactLaw SimpleSpringLaw renamed to FrictionLessElasticContactLaw. Yade is compiling. Improved documentation : added introduction to YADE Improved documentation Added librarie swift++ and qhull. Modified scripts so that they works with .h and .c files. Added MarchingCube algorithm into yade-lib-computationnal-geometry Created lib yade-lib-algorithms were to gather algorithms of different type : for now there is only a PerlinNoise algorithm, but we can put here sorting algorithm ... Moving, renaming some directories before splitting Added new file generator Funnel and TetrahedronsTest Added new interactingGeometry PolyhedralSweptSphere Added new NarrowInteractionGeometryEngine SwiftPolyhedronProximityModeler that will use SWIFT++ library and PolyhedralSweptSphere Added EngineUnit Tetrahedron2PolyhedralSweptSphere and Box2PolyhedralSweptSphere that will build InteractingGeometry ================================================== yade-0.08.3 Tue May 10 08:40:39 2005 +0000: Olivier Galizzi (32): Renaming ActionParameter to PhysicalAction Renaming ActionParameter to PhysicalAction finished Renaming ActionParameterForce/Momentum to Force/Momentum adn ActionParameterInitilizer/Reseter to PhysicalActionContainerInitializer/Reseter Renaming ActionParameterForce/Momentum to Force/Momentum adn ActionParameterInitilizer/Reseter to PhysicalActionContainerInitializer/Reseter ... Renaming ActionParameterForce/Momentum to Force/Momentum adn ActionParameterInitilizer/Reseter to PhysicalActionContainerInitializer/Reseter Renaming ActionParameterVectorVector to PhysicalActionVectorVector ... Renaming ActionParameterVectorVector to PhysicalActionVectorVector ... Yade is now compiling ... Moving NullGUI to yade core and renaming QtGUI to yade-qt-gui Moving yade-gui-gt from extensions/Frontend to gui Still moving yade-qt-gui Moving XMLManager from extensions/IOManager to serialization Renaming back yade-qt-gui to QtGUI to try to make yade compile first ... Renaming back yade-qt-gui to QtGUI to try to make yade compile first finished Forgot to re-ad QtGUI to kdevelop project Changing yade-empty-package directory tree Changing build order Moving all MetaEngine stored inside yade core to plugin/engine/metaengine ... Moving MetaEngine from yade to plugins Moving MetaEngine from yade to plugins Moving MetaEngine from yade to plugins... Modifications so that yade is compiling with new directory tree Creating library yade-lib-serialization-qt and yade-lib-time Changing build order and moving XMLManager to libraries Changing build order and removing unused directory serialization Renaming XMLManager to yade-lib-serialization-xml Moving gui/QtGUI to libraries/yade-lib-gui-qt Changing build order and finishing renaming of XMLManager and QtGUI Changing the way QtGUI load OpenGLRenderingEngine so that QtGUI doesn't need anymore to link with OpenGLRenderingEngine library. Making yade main application with flat directory structure Making yade main application with flat directory structure Removing all references to libraries Interactions, Body, Engine than no longer exist. Yade should now compile. Splitted ElasticContactLaw into 2 : ElasticContactLaw and ElasticCohesiveLaw. Autocollision for hanging cloth is working without hack. ================================================== yade-0.08.2 Fri May 6 15:57:05 2005 +0000 Olivier Galizzi (75): Starting creating new directory structure Directory renaming ... renaming directories ... Directory renaming ... Directory renaming ... Directory renaming ... Directory renaming ... Directory renaming ... Directory renaming ... Directory renaming ... Directory tree redesign ... Directory tree redesign ... Directory tree redesign ... s D yade/src/plugins/Engine/ActionParameterFunctor D yade/src/plugins/Engine/PhysicalAction Directory tree redesign ... Directory tree redesign ... Directory tree redesign ... Directory tree redesign ... Directory tree redesign ... Directory tree redesign ... Directory tree redesign ... Directory tree redesign ... Directory tree redesign ... Directory tree redesign ... Directory tree redesign ... Directory tree redesign ... Directory tree redesign ... Directory tree redesign ... Directory tree redesign ... Directory tree redesign ... Directory tree redesign ... Finishing directory tree redesign ... not compiling yet Now starting to rename files and some directories Renaming files ... Renaming classes ... Renaming classes ... Renaming classes ... Renaming classes ... Renaming classes ... Renaming classes ... Renaming classes ... Renaming classes ... Renaming classes ... Renaming classes ... Renaming classes ... Renaming classes ... Renaming classes ... Renaming classes ... Renaming classes ... Renaming classes ... Renaming classes ... Renaming classes ... Renaming classes ... Renaming classes ... Renaming classes ... Renaming classes ... Renaming classes ... Renaming classes is almost finished and yade is now compiling ! Renaming libraries to yade-lib-* Renaming libraries to yade-lib-* ... Renaming libraries to yade-lib-* Renaming libraries to yade-lib-* Renaming libraries to yade-lib-* Renaming libraries to yade-lib-* done. Yade is now compiling. Moving DataStructure to plugins ... Moving DataStructure to plugins finished Yade is now compiling on the first try. I removed initialization in constructor of bodyContainer, InteractionContainer ... because now container are in plugins and so compiled after yade itself. Changing name Dispatcher/funtor to MetaEngine/EngineUnit Changing names Dispatcher/Functor to MetaEngine/EngineUnit ... Changing names Dispatcher/Functor to MetaEngine/EngineUnit ... Changing names Dispatcher/Functor to MetaEngine/EngineUnit ... Changing names Dispatcher/Functor to MetaEngine/EngineUnit ... Changing names Dispatcher/Functor to MetaEngine/EngineUnit ... Changing names Dispatcher/Functor to MetaEngine/EngineUnit ... Changing names Dispatcher/Functor to MetaEngine/EngineUnit ... Changing names Dispatcher/Functor to MetaEngine/EngineUnit ... Changing names Dispatcher/Functor to MetaEngine/EngineUnit finished ================================================== yade-0.08.1 Wed Apr 27 16:51:17 2005 +0000 Janek Kozicki (348): deleted file makeSymLink, and corrected calling scripts, when current directory is not in $PATH - just added ./ everywhere better scripts - easier to use better script to switch dynlib on/off, but still not good enough removed spaces at the end of line Multimethods work! but class Factorable MUST be added, to clean mess with CollisionFunctor correcten compile dependencies - builds again from command line corrected (again) compile dependencies removed couple of compiler warnings added yade/scripts/setDebug yade/scripts/setRelease added FIXME about serialization, and Omega constructor tells about -rdynamic flag added comments into MAIN LOOP (currently inside NonConnexBody) scripts now check if YADEPATH was set scripts now checking if YADEPATH set, othervise tell user to set it moving GUI to frontend moved GUI to FrontEnd added FactoryExceptions so that ClassFactory is reusable fixed ArchiveTypes.hpp:FIXME : change SMART_POINTER to POINTER and STL_CONTAINER to CONTAINER added ClassFactory snippet, in order to clean it and make reusable ClassFactory now should be really reusable starting work with Serialization (spirit) preparing yade to use reusable snippets - do not modify those files inside yade! do it in snippets instead now yade uses exactly the same files as snippets. DO NOT MODIFY THEM, modify snippets first, then copy here. now yade uses boost::spirit, DO NOT MODIFY, work on snippets instead little cleanup, before making resetable rootBody corrected spirit. DO NOT MODIFY, please test on snippets first. now hanging cloth uses fundamental variable [ {1 {2 3 4}} {1 {2 3 4}} ... ] we started adding SDEC element. now SDECTestExample works. but not rotating box. and we need to clean stuff SDECDynamicEngine starts to work with contact law moving CollisionModel into CollisionGeometry changing *PhaseCollider into *Collider changing *PhaseCollider into *Collider (2) changing *PhaseCollider into *Collider (3) changed *PhaseCollider into *Collider, now starting with qmake static libraries inside yade directory now yade accepts more arguments on input: filename, timestep, maxiter added benchmark - we can easily test speed changes of yade CollllisionFunctor does NOT need to derive from Indexable!, rearranging subprojects changes in Math directory... more changes in directory tree (I must commit between changes...) more changes in directory tree (I must commit between changes... 2) moving files around ...still not compiling.. i must commit before I move directories.. looks like I've found a solution for qmake... looks like I've found a solution for qmake...2 finally! rearrangement of qmake/yade subtree. Serialization must be a dynlib!! it is absolutely not working as a static library!!, Omega must be freed from dependency on Vector3, this causes lots of problems (4hours to find it), 12h total, wheeeew. I Hope this work is not useless... CollisionFunctor derived or not, from Indexable? (in g++3.3 if YES - it's faster, but rather uncorrect...) now compiles with g++3.3, that was darn difficult to figure out, and is not slower than 94 svn status -q :) correction in SDECDynamicEngine with PermanentLinks, and Quaternion precise rotation added tetrahedron with permanent links, and -a flag, to say if we want autostart computations added NullGUI, and -p flag SDECBenchmark8k.xml is working now. added two more examples not working moment law - just after implementing it with JP wtf with euler angle on y direction??? added SDECMoreTetrahedronsWithLinksMDEM3D.xml this one works stable, but bad: data/SDECPermanentChainRotated.xml corrected SDECRotatingBoxWithTetrahedrons.xml and FileGenerator/RotatingBox/RotatingBox.cpp added LinkedSpheres filegenerator, small note inside Translatro.cpp - so that we can reproduce strange bahaviour changed order of REGISTER_ATTRIBUTES in NonConnexBody - interactions are first SDECDynamiCEngine has damping 0.3 now (should be attribute!!) added SDECCollision.xml - looks really good with timestep 0.003(FIXME - should be inside .xml) and moment law uncommented(FIXME) in SDECDynamicEngine XMLManager says line number of .xml if serialization fails, at least sometimes... Serializtion now supports: std::map, Loki::AssocVector, std::list, std::deque, std::set, std::vector, std::pair, boost::tuple1/2/3/4/5 corrected all .pro files to include toolboxes/Libraries/AssocVector - so that yade now compiles and runs good with all that new serialization stuff createIncludes script corrected, Makefile removed this version of file is used to make comparision of results with Jean Patrick corrected - works with current (unfinished) containers moved Singleton.hpp to toolboxes/Libraries/Singleton - place where it belongs now yade works with new serialization - added beforeSerialization, afterSerialization, beforeDeserialization, afterDesrialization removed class Contact, in prepare for interactionContainer added InteractionContainer (not integrated yet) InteractionContainer integrated into yade InteractioContainer eraseCurrent() useful for SAPColider BodyContainer starts to work BodyContainer with ids works, now I havr to move it into yade/Body, yade/Interaction InteractionContainer and BodyContainer integration completed. but container classes quality/speed is not good (and tested) yet InteractionVecSet and BodyVector FIXED threading on multi-processor machine (not real multiprocessor, just hyper threading...) rename BodyVector to BodyRedirectionVector corrected BodyRedirectionVector, added comment in Singleton scripts that allow easy switching between relative paths and paths in environemnt YADE*PATH variables threads are now crashing little less often... (only when clicking "new view" "start" "stop") XMLManager writes pointers in a shorter way, with _className_ preProcessAttributes postProcessAttributes converted SDECBenchmark8k.xml NullGUI works - for benchmars of Vector3 not in Serializable, etc.. scripts are checking all Environement Variables - more safety fixed missing is_base_and_derived in Indexable.hpp started with OpenGLWrapper and removed extra tabs in serialization OpenGLWrapper starts to work - now to write all those functions from gl.h OpenGLWrapper 50% done - and #DOUBLE_PRECISION works! (float/double) improved SDECLinkedSpheres (momwnt law is not workingsvn status -q) SDECCollision.xml is loading correctly, but is not working!!! - it was working previously!! SDECCollision.xml works for dt=0.003 SDECCollision.xml and SDECTetrahedronWithLinks.xml work for dt=0.003 SDECLinkedSpheres example now is much more interesteing, with dt=0.003 fixed hack in PointerHandler, fixed SDECBenchmark8k, some .pro were release, not debug ok, this problem in IOManager.cpp is strange... rearranging directories. changing directories cd... changing directories cd... changing directories cd... Finished rearranging subdirectories. Now we have directory extensions (we can rename it, if there is a better name), and ClassFactory is in toolbox/Libraries, Multimethods (once there are done) should go there too, because MultiMethods have nothing to do with yade. yade is now compiling (and works) with g++ 3.3 all .xml examples except ErrorTolerant, are now working NullGUI is now saving results during computations. Those results can be opened with QtGUI and we can see what is computed. added filegenerator that can import data files from SDEC hack, which must be corrected just after adding Action, allows to measure forces during simulation InteractionVecSet is corrected and working, yade is now compiling withi double (there was some mistake with float), but all the examples are now broken! yade is now crashing less often, so I hope that we will never again see that error: again was not compiling with double.... corrected some threading problems, removed MyEngine2 from DynamicEngine.pro, because it doesn't exist added SopeTest in SimulationController -pthreads -lpthreads are required for yade to compile with 3.3!! don`t remove them!!!! added some comments to code regarding integration with new multimethods started integration of multimethods integrating new multimethods, continued... 1D, 2D MultiMethods are working in yade. but there is a bug in DynLibDispatcher, current workaround is done with a hack added some comments to working multimethods, hack is little better, but not removed, yet Multimethods are now working, clean, checked with valgrind, and results from older version are compared with kompare. added two postprocessors - recording forces, and average position. but why they crash Omega.loadDynLibs(), when I use ofstream??? force recorder is not modifying data anymore, but anyway it`s dirty and must use Action, and work like real sensors!! SDECImport uses new dialog box changes into Interaction and Interaction container: version 0.1.5, with many changes: - moment rotation law added, mess inside SDECDynamicEngine (three enegines in one!) - Serialization now supports almost all std, boost and Loki containers and more - math classes are separate from serialization, and done with templates (but with wild-magic license) - for storing Bodies and Interactions we now use polymorphic classes InteractionContainer and BodyContainer - yade uses threads now, but still not fully stable - switch at compile time to select float/double precision of calculation - displayed objects are now casting really cool shadows - some examples are not updated, and therefor not working. it is guaranteed that SDECBenchmark8k will always work. - BoundingVolume optimized for each kind of GeometricalModel makes computation almost twice faster - new very generic MultiMethods are added, allowing multivirtual call with extra parameters, and easy code reuse added changelog small correction in Interaction constructor putting Real in all places where it is used for calculation was not compiling because of stupid .pro problems renamed DynLibLauncher to FunctorWrapper changing name from SDECContactModel to SDECContactModel - but not tested yet - I must commit between subversion operations SDECContactModel renamed to SDECContactGeometry, OK renaming BroadCollider, NarrowCollider to BroadInteractor, NarrowInteractor, part 1 still renaming finished renaming BroadCollider, NarrowCollider to BroadInteractor, NarrowInteractor changed color of sphere number 1 and 10 - this is for checking the shearForce between those spheres (as we had numerically different results for float and double) some cleaning before ActionContainer some .pro conflicts, now it's compiling, still before ActionContainer added empty class ActionVecVec I added empty classes ActionForce and ActionMomentum, because I need something that ActionContainer can store classes are empty now. So, now all classes are added: Action, ActionVecVec, ActionContainer, ActionForce, ActionMomentum, etc.. but they are empty. And it is compiling abstract interface for ActionContainer is specified, now to fill in the class ActionVecVec .... work inside ActionVecVec - not done yet, but is compiling corrected compilation order (except for CollisionGeometrySet, which is a sign bigger design flaw, than just a compilation order) and xorrected SDECBenchmark8k.xml so that it is running with latest modifications ActionContainer->reset() - resetting actions without deleting all the pointers - faster changed the way in which ActionContainer works, now all fields are never empty. DynamicEngine (or some other actor?), when if(first) must call ActionContainer->prepare(), and then ActionContainer works much faster. added flat kdevelop project, this is for debugging - I don't know why, but alleyoop is not showing line numbers inside dynlibs, now... finad() and getCurrent() in containers are now returning references, as it should be faster Body is Indexable, but we have to register SDECDiscreteElement to dispatcher, not RigidBody... it's a bug in Indexable class, or somewhere there... Multimethods now support inheritance in Indexable objects, I had to modify in Indexable class: possibly faster DynLibDispatcher, I was also trying to fix problem that Singleton is not deleted at end, but finally I made only some improvement, but it is still not fixed. renaming CollisionFunctor to InteractionFunctor, part 1 renaming, part 2 renaming, part 4 renaming, part 5 renaming, part 6 finished renaming of InteractionFunctor, and I also corrected colors in qt dialog, and qt simulation-controller window. But I'm not sure about colors in qt dialog. small change in ActionDispatcher.cpp, is faster, but if Action was holding body's id it is possible that it can be faster corrected SDECBenchmark8k.xml, and I've checked that visually simulation is the same as previous version renaming... moving stuff around moving stuff around moving stuff around cd.. moving stuff around... now I'll try to compile... still trying to compile... it is almost compiling so it is compiling, except for CollisionGeometrySet problem OpenGL display works with strange static_cast inside DynLibDispatcher started with PositionOrientationRecorder changed double to Real in few remaining places. small fixes in multimethods. remove ParticleBallisticEngine BallisticDynamicEngine renamed geometricalModel, interactionGeometry, boundingVolume before renaming class name of CollisionGeometry to InteractionGeometry renaming collistion to interaction, cd... renaming cd... renaming... renaming... renaming... renaming... still renaming collision to interaction... still renaming collision to interaction... still renaming collision to interaction... still renaming collision to interaction... still renaming collision to interaction... renaming mess.. classes aggregated by Body have a prefix 'Body' in their name, to reduce confusion of class names: fixed OpenGL display of cloth, and still renaming classess aggregated by Body Loki library in single place, but it should be renamed to SmallLoki, or YadeLoki, because this is not an exact copy of Loki library. added FactoryFunctors and UpdatorFunctors for BoundingVolume, GeometricalModel, etc... They are empty for now Loki ok (just should be SmallLoki, or YadeLoki), BodyAssocVec -> BodyAssocVector because of name conflict between Body::InteractionGeometry and Interaction::InteractionGeometry, maybe name Body::InteractionDescription will be ok? this is what I've renamed in this commit so now we have (arrow <- means 'aggregates'): renaming cd.. renaming cd.. renaming cd.. before renaming InteractionFunctor-s to InteractionGeometryFunctor-s little renaming/cleaning is finished. problem with InteractionDescriptionSet is solved, and now yade just compiles without any errors! I have done some general cleaning - removed unnecessary constructors, deleted unused postProcessAttributes, etc.. OK/Cancel works now in file selection dialog, ActionForce2RigidBody was deleted, because ActionForce2Particle does the same job now I'm staring to work on updating GeometricalModel of Mesh2D, so that cloth will be displayed properly changed names RigidBody to RigidBodyParameters and so on before making correct display of cloth geometrical model I have to move Geometry into Body, because Body owns geometry, and it will not compile if I don;t move it there. all renaming finished for now. so I'll start with proper update of geometrical model for cloth. update of GeometricalModel for cloth is working is compiling now with g++ 3.3, and with double FunctorWrapper is Serializable, now - because we want Dispatcher to work with serialization, so that Functors can get extra arguments from .xml Functors are serializable, so now I'm working on DynLibDispatcher to support this serialization of functors Serializable functors are working, so now I'll separate damping from SDECDynamicEngine. It will be a functor, with parameters. preaparations for ActionDampingFunctor, corrected SDECBenchmark8k.xml damping is separated from SDECDynamicEngine and works, strange is that ActionDampingDispatcher and ActionApplyDispatcher are exactly the same!! so they should be merged into one dispatcher. maybe Action2BodyPhysicalParametersDispatcher damping works quite good added ActionReset - it is not inside SDECDynamicEngine - because for cloth example we have two/three engines, so they can't do a reset - because they will delete results from other engine renamed: stared with RotatingBox with simplespring, filegenerator... RotatingBox example with spheres and boxes inside is working quite good - with SimpleSpringDynamicEngine BoxStack example is working quite good. meybe it should be renamed to WallImpact, or something like that... the real name for damping is: Cundall Non Viscous Local Damping TimeStepper is working quite nice, and now - even SDECLinkedSpheres example is stable! SDECParameters is now holding shearingAngle, young modulus and posiions ratio - as it should. internal shearing angle is added, and yade is ready to compare results with sdec, although force recorder is little dirty, because it is releasing the bug ball some small corrections, before comparing results with sdec splitting leapfrom integrator into two parts.... added velocity recorder, and timeintegrator is split in two parts started with lattice model Lattice model generation starts to work. SDECImport can also create walls made of spheres Body::group changed into Body::groupMask (so bodies can belong to many groups - max. 16 groups, because we have 16 bits here) supid error everywhere!!! volume of a spohere was 4/3*PI*radius*radius, EVERYWHERE!!!. the volume of a sphere should be calculated in ONLY one place!!! so now we will make another comparision with SDEC, after sphere volume was fixed renaming DynamicEngine to InteractionLaw renaming KinematicEngine to KinematicMotion DynamicEngine -> InteractionLaw KinematicEngine -> KinematicMotion renaming DynamicEngine is ConstitutiveLaw ApplyActionForce2Particle -> NewtonsForceLaw ApplyActionMomentum2RigidBody -> NewtonsMomentumLaw started nice class diagram better (but not finished) class diagram version used for training with Julien, Frederic, and Luc yadeExampleTimeLoop.dia (xml) almost finished renaming Action to ActionParameter renaming cd... renaming cd... renamed Action to ActionParameter separating gravity to GravityForceFunctor - it is necessery because I'm preparing code for courses. some filegeneratr examples now have zero gravity, and empty ActionParameterContainer, that is easy to fix with ActionParameterInitializer, when doing ActionParameterInitializer, also ActionParameterReset must be done correctly and split from ActionReset. Then we have to add ActionParameter displacement, position and angularDisplacement, so that ImposeKinematicConditions will work correctly (AngularDisplacementFunctor, DisplacementFunctor (it was Rotor and Translator)) some cleaning stared ActionParameterInitializer and ActionParameterReset started LeapFrogOrientationIntegratorFunctor and LeapFrogPositionIntegratorFunctor LeapFrogOrientationIntegratorFunctor LeapFrogPositionIntegratorFunctor done renaming KinematicMotion to COndition renaming KinematicMotion to Condition renaming KinematicMotion to Condition ForceCondition GravityCondition RotationCondition TranslationCondition, are OK renaming.. renaming.. renaming.. renaming.. renaming.. renaming.. renaming.. renaming.. renamed: SDECParameters -> BodyMacroParameters SDECContactGeometry -> MacroMicroContactGeometry, and all functors SDECMacroMicroElasticRelationships -> MacroMicroElasticRelationships SDECContactPhysics -> ElasticContactParameters SDECLaw -> ElasticContactLaw added empty classess: added empty FEMSetTextLoaderFunctor FEMBeam load the file correctly FEM bodies inherit from BodyPhysicalParameters scripts to rename classess, hopefully working correctly I'm testing the renaming scripts: I'm testing the scripts: I'm testing the scripts scripts should work now, with small human intervention I'm testing scripts again ./renameClassFilenames FEMTetrahedronParameters FEMTetrahedronData ./renameClassDirectories FEMTetrahedronParameters FEMTetrahedronData ComplexBody has now actors and initializers, and thanks to that FEMSetTextLoaderFunctor is working. FEM is half done - generation works. and other stuff. Only Constitutive Law for FEM is empty - waiting to be written. But there is a compilation problem we have to compile twice now, because of linker errors in fem loader. FEM works!! GLDrawTetrahedron is now drawing solid tetrahedrons. but sometimes it is drawing not correctly.... corrected some comments in FEMLaw FEM stuff is compiling correctly, some cleaning. created directory A svn+ssh://svn.berlios.de/svnroot/repos/yade/trunk/yade-fem created created created A svn+ssh://svn.berlios.de/svnroot/repos/yade/trunk/yade-qt A svn+ssh://svn.berlios.de/svnroot/repos/yade/trunk/yade-wm3math A svn+ssh://svn.berlios.de/svnroot/repos/yade/trunk/yade-loki A svn+ssh://svn.berlios.de/svnroot/repos/yade/trunk/yade-serialization A svn+ssh://svn.berlios.de/svnroot/repos/yade/trunk/yade-multimethods A svn+ssh://svn.berlios.de/svnroot/repos/yade/trunk/yade-factory A svn+ssh://svn.berlios.de/svnroot/repos/yade/trunk/yade-threads A svn+ssh://svn.berlios.de/svnroot/repos/yade/trunk/yade-opengl A svn+ssh://svn.berlios.de/svnroot/repos/yade/trunk/yade-computationalgeometry A svn+ssh://svn.berlios.de/svnroot/repos/yade/trunk/yade-massspring A svn+ssh://svn.berlios.de/svnroot/repos/yade/trunk/yade-realtime-rigidbody A svn+ssh://svn.berlios.de/svnroot/repos/yade/trunk/yade-computational-geometry D svn+ssh://svn.berlios.de/svnroot/repos/yade/trunk/yade-computationalgeometry A svn+ssh://svn.berlios.de/svnroot/repos/yade/trunk/yade-wm3-math D svn+ssh://svn.berlios.de/svnroot/repos/yade/trunk/yade-wm3math A svn+ssh://svn.berlios.de/svnroot/repos/yade/trunk/yade-mass-spring D svn+ssh://svn.berlios.de/svnroot/repos/yade/trunk/yade-massspring A svn+ssh://svn.berlios.de/svnroot/repos/yade/trunk/yade-empty A svn+ssh://svn.berlios.de/svnroot/repos/yade/trunk/yade-sandbox A svn+ssh://svn.berlios.de/svnroot/repos/yade/trunk/yade-serialization-xml A svn+ssh://svn.berlios.de/svnroot/repos/yade/trunk/yade-dem-fem D svn+ssh://svn.berlios.de/svnroot/repos/yade/trunk/yade-femdem better openGL tetrahedron display A svn+ssh://svn.berlios.de/svnroot/repos/yade/trunk/yade-time A svn+ssh://svn.berlios.de/svnroot/repos/yade/trunk/yade-gui-qt D svn+ssh://svn.berlios.de/svnroot/repos/yade/trunk/yade-qt A svn+ssh://svn.berlios.de/svnroot/repos/yade/trunk/yade-rendering-engine-opengl A trunk/yade-package-fem A trunk/yade-package-fem-dem D trunk/yade-mass-spring A trunk/yade-package-realtime-rigidbody D trunk/yade-dem D trunk/yade-fem D trunk/yade-dem-fem D trunk/yade-realtime-rigidbody A trunk/yade-package-lattice D trunk/yade-lattice A trunk/yade-package-mass-spring A trunk/yade-package-dem A trunk/yade-lib-wm3-math A trunk/yade-lib-loki D trunk/yade-rendering-engine-opengl A trunk/yade-lib-factory A trunk/yade-lib-computational-geometry D trunk/yade-multimethods A trunk/yade=lib-time A trunk/yade-lib-opengl D trunk/yade-threads D trunk/yade-wm3-math D trunk/yade-time D trunk/yade-loki A trunk/yade-lib-rendering-engine-opengl D trunk/yade-factory D trunk/yade-computational-geometry A trunk/yade-lib-multimethods D trunk/yade-opengl A trunk/yade-lib-threads A trunk/yade-lib-time D trunk/yade-serialization-xml D trunk/yade-serialization A trunk/yade-lib-serialization-xml A trunk/yade-lib-serialization D trunk/yade=lib-time fem-dem coupling ... disabled stuff in QtGeneratedSimulationController changed todo changed something in interactions ./renameClassFirst InteractionDescription InteractingGeometry ./renameClassFilenames InteractionDescription InteractingGeometry D yade/src/yade/Body/InteractionDescription A yade/src/yade/Body/InteractingGeometry D yade/projects/kdevelop/yade/Body/InteractionDescription A yade/projects/kdevelop/yade/Body/InteractingGeometry A yade/projects/kdevelop/yade/Body/InteractingGeometry/InteractingGeometry.pro M yade/projects/kdevelop/yade.kdevelop SimpleBody deleted renameClassFirst ComplexBody MetaBody ./renameClassFilenames ComplexBody MetaBody D yade/src/plugins/Body/InteractionDescription A yade/src/plugins/Body/InteractingGeometry D yade/src/plugins/Body/InteractionDescriptionFunctor A yade/src/plugins/Body/InteractingGeometryFunctor D yade/projects/kdevelop/plugins/Body/InteractionDescription A yade/projects/kdevelop/plugins/Body/InteractingGeometry D yade/projects/kdevelop/plugins/Body/InteractionDescriptionFunctor A yade/projects/kdevelop/plugins/Body/InteractingGeometryFunctor M yade/projects/kdevelop/plugins/Body/Body.pro M yade/projects/kdevelop/yade.kdevelop D yade/projects/kdevelop/plugins/Body/InteractingGeometry/InteractionDescription.pro A yade/projects/kdevelop/plugins/Body/InteractingGeometry/InteractingGeometry.pro D yade/projects/kdevelop/plugins/Body/InteractingGeometryFunctor/InteractionDescriptionFunctor.pro A yade/projects/kdevelop/plugins/Body/InteractingGeometryFunctor/InteractingGeometryFunctor.pro DeusExMachina /renameClassFilenames Condition DeusExMachina Condition DeusExMachina Condition DeusExMachina ./renameClassFirst InteractionSolver ConstitutiveLaw ./renameClassFilenames InteractionSolver ConstitutiveLaw InteractionSolver ConstitutiveLaw InteractionSolver ConstitutiveLaw Actor - Engine Actor - Engine BodyPhysicalParameters PhysicalParameters BodyPhysicalParameters PhysicalParameters BodyPhysicalParameters PhysicalParameters BodyPhysicalParameters PhysicalParameters ... ... ... ... ... ... ... ... A svn+ssh://svn.berlios.de/svnroot/repos/yade/trunk/yade-empty-lib A svn+ssh://svn.berlios.de/svnroot/repos/yade/trunk/yade-package D svn+ssh://svn.berlios.de/svnroot/repos/yade/trunk/yade-empty A svn+ssh://svn.berlios.de/svnroot/repos/yade/trunk/yade-empty-package D svn+ssh://svn.berlios.de/svnroot/repos/yade/trunk/yade-package updated todo A svn+ssh://svn.berlios.de/svnroot/repos/yade/trunk/yade-lib-serialization-contrib we have made a great yade-empty directory structure!! Olivier Galizzi (147): removing ui file from CORBA IDL section and problem with moc resolved by removing trailing slash at the end of YADEPATH moving everything into subdirectory yade (part 1) moving everything into subdirectory yade (part 2) changed README - better quick-guide renaming function collisionModelisationPhase to narrowCollisionPhase smalls changes into ContactModel, CollisionModel, Body for preparing to multimethods. Class Indexable added. Splitting collision function into dynlibs Multimethod implemented but not tested yet! Indexable class not working (can't shared_dynamic_cast Serializable to Indexable) New classes added (Factorable,Interaction,InteractionModel) Rewriting class Factorable and InteractionModel because subversion delete their content during last commit ??? Add some toolboxes (M3D,M2D,Rand,Constants) Some unused files were removed. The content of some files was again erased by an unknown reason? All serialization stuff are now into a dynlib Putting build order in the correct order Toolboxes Distances and Intersections created shared_dynamic_cast was changed to dynamic_pointer_cast Adding new class for cloth simulation. There is some mess with serialization. starting adding cloth simulation Cloth simulation is working but some stuff are dirty Cloth and spheres are working together but in a dirty way Starting adding FEMBody and renaming Cloth to MassSpringBody Putting all GUI stuff into a dynlib and new class QtGUIGenerator (that generate a qt widget from a serializable object) added Correct some little error in QtGUIGenerator subproject Changing build order Minor changes + adding boxes into RotatingBox Better tuning of rotatingBox simulation Added not working BoxStack, and createDynamicEngine script New SDECDiscreteElement and SDECDynamicEngine added New simple example for SDEC added Some minor changes in SDECDynamicEngine - but it is still not working with RotatingBox New multimethod design (moved from omega to nonconnexbody) Some include of Omega were missing in previous commit Improved detection of new and old interactions (we hope it is faster) We change the way we remember contacts from previous time step (new geometrical old physical) Added Actor so we can choose order of main loop modules. Started to add permanent links Added new ErrorTolerant classes SDECTestPermanentLink.xml is now working Added ErrorTolerant classes round 2 Added todo file, corrected examples, corrected permanents links, started to add jean patrick law Moment law is now working better but there are still some problems Moment law is now using Quaternion instead of weird modulo loop for computing dBeta (but still some problem) Moment law seems now to work or at least it is much more stable Added Translator kinematic engine Continuing ErrorTolerantDynamicEngine and added CollisionFunctor for him LCP solver using bi-conjugate gradient is implemented but is not working yet Starting moving libraries AssocVector, Frontend, IOManager to correct places added /home/evasion/galizzi/devel/yade/yade/include, libraries in correct places now modified todo, removed some junk Change all declaration of Math stuff so that it is using new template Vector3, Matrix3 ... Yade is now working with new serialization of vector ... Removed output when class are registered Added some todo stuff QtGUIGenerator is now able to (de)-serialize all fundamentals into a serializable Add QtFileGenerator, for automatic file generation with Qt GUI (not completely working yet) QtGuiGenerator and QtFileGenerator are now working Added improvements into QtGUIGenerator and QtFileGenerator All Qt libraries are now in one library. Improved version of QtGUIGenerator Removed directory QtFileGenerator and QtGUIGenerator Created new qt controller for simulation and new opengl library New SimulationController is now working Started moving simulation loop from glViewer to a specific thread Added ThreadSynchronizer. Yade is now working with external simulation loop Improved thread management. Multiple views are now working Improved GUI. Is ThreadSynchronizer really working ? Improved version of Threads library Added omitted Thread* files Changes into ThreadSynchronizer Much better version of Thread library and multithreaded QGLViewer Reenabling GUI updater thread Now we use InteractionHashMap by default because InteractionVecSet is not ready yet Added RenderingEngines Changing build order Shadow added but not working yet because of problems with stencil buffer Shadows are working but not when they overlap Changing stencil function solved the problem with overlapping shadows Better lighting parameters Faster shadow. Only 1 rendering instead of 3 and use of QUAD_STRIP Improved QtGUI Improved QtGUI some changes in shadows Improved SDECSpheresPlanes Improved fast shadow volumes Added new classes for bounding volume management (not used yet) Bounding volume are now both indexalbe and a GeometricalModel BoundingVolume update is now done into an actor Improved version of GLWindowManager library Improved GUI with (de)-serialization of RenderingEngine into SimulationController Improved version of QtGUIGenerator. Now boolean are represented by checkboxes instead of linedits Better handling of start,stop and reload simulation. Thread now terminate when associated window is closed Update in BoundingVolume updating. Now it is working for rootBody through it CollisionGeometrySet. Scene is now dynamically centered when file is loaded Changes into FileGenerator Fixed some problems with FileGenerators Boxes are now casting shadows Boxes are now casting shadows Better handling of thread into GUI. But they are still problems especially if you close the first GLWindows because it contain the GLContext where the GL lists are stored Improved stability Threads improvement Somes changes into Thread and GUI Mush better handling of threads, open/close ... it is never crashing on my laptop at least Now yade is no more segfaulting on exit Display list are now builded at each new creation of RenderingEngine Changes into threads Added createDynlib script Added Messagedialog Fix error when reloading simulation and display list are now not built several times Redesigned the way we use boost::lock Added mutex into Threadable sleep method Added changes from snippets Thread Better handling of real simulation duration Improved GUI Persistent SAPCollider is now working. Added PersistantSAPCollider files that were missing in previous revision Added script addClassToDynLib Fix crash with permanent links. Added clas InteractionPhysics Added subproject InteractionPhysics Renamed subproject InteractionModel to InterationGeometry Added into SVN repository omitted files InteractionPhysics Problem with InteractionPhysics Corrected kdevelop mistakes due to introduction of InteractionPhysics Added SDECContactPhysics and SDECPermanentLinkPhysics Fix mistake into SDECPermentLink : Some attributes were not registered Implemented InteractionPhysics and InteractionGeometry Updated todo list : Switch all possible dynamic_pointer_cast to PointedType * p = static_cast(shared_p.get()) Added Action, ActionContainer, SDECLinearContactModel, PhysicalInteractor. We started redesign to introduce Action which are the response of a DynamicEngine to an Interaction and acts on body. Adding factorable macros into Action and ActionContainer Add function add,sub and reset into Action classes. Almost finished action container. You have to call createIndex() from constructor of all Indexable class. Before adding displatcher for applying Actions. Added ActionDispatcher. Now we want to use it. Now ActionDispatcher is working. Added omitted files from previous commit. Added single step button in QtGUI Better SpheresPlanes FileGenerator Started to rename and move SimpleNarrowCollider and classes related to InteractionPhysics InteractionPhysicsDispatcher starts to works. But we have to make BodyPhysics HangingCloth is working again but it is designed using Yade framework Small changes into MassSpring so that it is faster Added BodyPhysics class and moved all type of bodies into subdirectory BodyPhysics To add BodyPhysicalParameter we have to make OpenGLDispatchers. So we started to add all of them. OpenGL continued... Stuff with BodyPhysicalParameters is compiling . Corrected some bugs due to introduction of BodyPhysicalParameters Created TimeIntegratorFunctor and Dispatcher. BallisticDynamicEngine is now LeapFrogIntegrator Corrected KinematicEngine. MassSpring cloth is now working : but we still have to update geometricalModel Added layout and scrollview to QTGUI corrected function name into Quaternion Added file yade.sxw which contain concept and spirit of yade Added tree file to rename files and directories Added some concepts into yade.sxw trunk-2018.02b/INSTALL000066400000000000000000000003461324306050200142220ustar00rootroot00000000000000Installation instructions are updating regularly. The most actual instruction can be found on the website: http://www.yade-dem.org/doc/installation.html which is generated from the following file: doc/sphinx/installation.rst trunk-2018.02b/LICENSE000066400000000000000000000432541324306050200142030ustar00rootroot00000000000000 GNU GENERAL PUBLIC LICENSE Version 2, June 1991 Copyright (C) 1989, 1991 Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. Preamble The licenses for most software are designed to take away your freedom to share and change it. By contrast, the GNU General Public License is intended to guarantee your freedom to share and change free software--to make sure the software is free for all its users. This General Public License applies to most of the Free Software Foundation's software and to any other program whose authors commit to using it. (Some other Free Software Foundation software is covered by the GNU Lesser General Public License instead.) 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 this service 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 make restrictions that forbid anyone to deny you these rights or to ask you to surrender the rights. These restrictions translate to certain responsibilities for you if you distribute copies of the software, or if you modify it. For example, if you distribute copies of such a program, whether gratis or for a fee, you must give the recipients all the rights that you have. 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. We protect your rights with two steps: (1) copyright the software, and (2) offer you this license which gives you legal permission to copy, distribute and/or modify the software. Also, for each author's protection and ours, we want to make certain that everyone understands that there is no warranty for this free software. If the software is modified by someone else and passed on, we want its recipients to know that what they have is not the original, so that any problems introduced by others will not reflect on the original authors' reputations. Finally, any free program is threatened constantly by software patents. We wish to avoid the danger that redistributors of a free program will individually obtain patent licenses, in effect making the program proprietary. To prevent this, we have made it clear that any patent must be licensed for everyone's free use or not licensed at all. The precise terms and conditions for copying, distribution and modification follow. GNU GENERAL PUBLIC LICENSE TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 0. This License applies to any program or other work which contains a notice placed by the copyright holder saying it may be distributed under the terms of this General Public License. The "Program", below, refers to any such program or work, and a "work based on the Program" means either the Program or any derivative work under copyright law: that is to say, a work containing the Program or a portion of it, either verbatim or with modifications and/or translated into another language. (Hereinafter, translation is included without limitation in the term "modification".) Each licensee is addressed as "you". Activities other than copying, distribution and modification are not covered by this License; they are outside its scope. The act of running the Program is not restricted, and the output from the Program is covered only if its contents constitute a work based on the Program (independent of having been made by running the Program). Whether that is true depends on what the Program does. 1. You may copy and distribute 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 and disclaimer of warranty; keep intact all the notices that refer to this License and to the absence of any warranty; and give any other recipients of the Program a copy of this License along with the Program. You may charge a fee for the physical act of transferring a copy, and you may at your option offer warranty protection in exchange for a fee. 2. You may modify your copy or copies of the Program or any portion of it, thus forming a work based on the Program, and copy and distribute such modifications or work under the terms of Section 1 above, provided that you also meet all of these conditions: a) You must cause the modified files to carry prominent notices stating that you changed the files and the date of any change. b) You must cause any work that you distribute or publish, that in whole or in part contains or is derived from the Program or any part thereof, to be licensed as a whole at no charge to all third parties under the terms of this License. c) If the modified program normally reads commands interactively when run, you must cause it, when started running for such interactive use in the most ordinary way, to print or display an announcement including an appropriate copyright notice and a notice that there is no warranty (or else, saying that you provide a warranty) and that users may redistribute the program under these conditions, and telling the user how to view a copy of this License. (Exception: if the Program itself is interactive but does not normally print such an announcement, your work based on the Program is not required to print an announcement.) These requirements apply to the modified work as a whole. If identifiable sections of that work are not derived from the Program, and can be reasonably considered independent and separate works in themselves, then this License, and its terms, do not apply to those sections when you distribute them as separate works. But when you distribute the same sections as part of a whole which is a work based on the Program, the distribution of the whole must be on the terms of this License, whose permissions for other licensees extend to the entire whole, and thus to each and every part regardless of who wrote it. Thus, it is not the intent of this section to claim rights or contest your rights to work written entirely by you; rather, the intent is to exercise the right to control the distribution of derivative or collective works based on the Program. In addition, mere aggregation of another work not based on the Program with the Program (or with a work based on the Program) on a volume of a storage or distribution medium does not bring the other work under the scope of this License. 3. You may copy and distribute the Program (or a work based on it, under Section 2) in object code or executable form under the terms of Sections 1 and 2 above provided that you also do one of the following: a) Accompany it with the complete corresponding machine-readable source code, which must be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or, b) Accompany it with a written offer, valid for at least three years, to give any third party, for a charge no more than your cost of physically performing source distribution, a complete machine-readable copy of the corresponding source code, to be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or, c) Accompany it with the information you received as to the offer to distribute corresponding source code. (This alternative is allowed only for noncommercial distribution and only if you received the program in object code or executable form with such an offer, in accord with Subsection b above.) The source code for a work means the preferred form of the work for making modifications to it. For an executable work, complete source code means all the source code for all modules it contains, plus any associated interface definition files, plus the scripts used to control compilation and installation of the executable. However, as a special exception, the source code distributed need not include anything that is normally distributed (in either source or binary form) with the major components (compiler, kernel, and so on) of the operating system on which the executable runs, unless that component itself accompanies the executable. If distribution of executable or object code is made by offering access to copy from a designated place, then offering equivalent access to copy the source code from the same place counts as distribution of the source code, even though third parties are not compelled to copy the source along with the object code. 4. You may not copy, modify, sublicense, or distribute the Program except as expressly provided under this License. Any attempt otherwise to copy, modify, sublicense or distribute the Program is void, and will automatically terminate your rights under this License. However, parties who have received copies, or rights, from you under this License will not have their licenses terminated so long as such parties remain in full compliance. 5. You are not required to accept this License, since you have not signed it. However, nothing else grants you permission to modify or distribute the Program or its derivative works. These actions are prohibited by law if you do not accept this License. Therefore, by modifying or distributing the Program (or any work based on the Program), you indicate your acceptance of this License to do so, and all its terms and conditions for copying, distributing or modifying the Program or works based on it. 6. Each time you redistribute the Program (or any work based on the Program), the recipient automatically receives a license from the original licensor to copy, distribute or modify the Program subject to these terms and conditions. You may not impose any further restrictions on the recipients' exercise of the rights granted herein. You are not responsible for enforcing compliance by third parties to this License. 7. If, as a consequence of a court judgment or allegation of patent infringement or for any other reason (not limited to patent issues), 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 distribute so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not distribute the Program at all. For example, if a patent license would not permit royalty-free redistribution of the Program by all those who receive copies directly or indirectly through you, then the only way you could satisfy both it and this License would be to refrain entirely from distribution of the Program. If any portion of this section is held invalid or unenforceable under any particular circumstance, the balance of the section is intended to apply and the section as a whole is intended to apply in other circumstances. It is not the purpose of this section to induce you to infringe any patents or other property right claims or to contest validity of any such claims; this section has the sole purpose of protecting the integrity of the free software distribution system, which is implemented by public license practices. Many people have made generous contributions to the wide range of software distributed through that system in reliance on consistent application of that system; it is up to the author/donor to decide if he or she is willing to distribute software through any other system and a licensee cannot impose that choice. This section is intended to make thoroughly clear what is believed to be a consequence of the rest of this License. 8. If the distribution and/or use of the Program is restricted in certain countries either by patents or by copyrighted interfaces, the original copyright holder who places the Program under this License may add an explicit geographical distribution limitation excluding those countries, so that distribution is permitted only in or among countries not thus excluded. In such case, this License incorporates the limitation as if written in the body of this License. 9. The Free Software Foundation may publish revised and/or new versions of the 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 a version number of this License which applies to it and "any later version", you have the option of following the terms and conditions either of that version or of any later version published by the Free Software Foundation. If the Program does not specify a version number of this License, you may choose any version ever published by the Free Software Foundation. 10. If you wish to incorporate parts of the Program into other free programs whose distribution conditions are different, write to the author to ask for permission. For software which is copyrighted by the Free Software Foundation, write to the Free Software Foundation; we sometimes make exceptions for this. Our decision will be guided by the two goals of preserving the free status of all derivatives of our free software and of promoting the sharing and reuse of software generally. NO WARRANTY 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, 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. 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR REDISTRIBUTE 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. 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 convey 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 2 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, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. Also add information on how to contact you by electronic and paper mail. If the program is interactive, make it output a short notice like this when it starts in an interactive mode: Gnomovision version 69, Copyright (C) year name of author Gnomovision 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, the commands you use may be called something other than `show w' and `show c'; they could even be mouse-clicks or menu items--whatever suits your program. You should also get your employer (if you work as a programmer) or your school, if any, to sign a "copyright disclaimer" for the program, if necessary. Here is a sample; alter the names: Yoyodyne, Inc., hereby disclaims all copyright interest in the program `Gnomovision' (which makes passes at compilers) written by James Hacker. , 1 April 1989 Ty Coon, President of Vice This 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. trunk-2018.02b/README.rst000066400000000000000000000032621324306050200146600ustar00rootroot00000000000000=================================== YADE - Yet Another Dynamic Engine =================================== Extensible open-source framework for discrete numerical models, focused on Discrete Element Method. The computation parts are written in c++ using flexible object model, allowing independent implementation of new algorithms and interfaces. Python is used for rapid and concise scene construction, simulation control, postprocessing and debugging. **Documentation:** - https://www.yade-dem.org/doc/ (includes c++ and python reference) - https://yade-dem.org/doc/Yade.pdf (PDF-file with documentation) - https://yade-dem.org/doc/Yade.epub (EPUB-file with documentation) **Downloads:** - Packages for Ubuntu and Debian: - https://yade-dem.org/doc/installation.html#packages - External packages: https://launchpad.net/~yade-users/+archive/external - Tarballs of different versions: - https://github.com/yade/trunk/tags - Repository on github.com: https://github.com/yade - Repository on git.debian.org: http://git.debian.org/?p=debian-science/packages/yade.git **Teams involved:** - General users: https://launchpad.net/~yade-users - Developers and advanced users: https://launchpad.net/~yade-dev - Infrastructure maintainers: https://launchpad.net/~yade-pkg **Miscellaneous:** - `Yade buildbot `_ - `Yade page on ohloh-project `_ - `Yade on Debian PTS `_ - `Yade in Ubuntu `_ - `Home page `_ - `Wiki `_ - `Screenshots `_ trunk-2018.02b/RELEASE000066400000000000000000000000111324306050200141610ustar00rootroot000000000000002018.02b trunk-2018.02b/cMake/000077500000000000000000000000001324306050200142065ustar00rootroot00000000000000trunk-2018.02b/cMake/CombineSources.cmake000066400000000000000000000017411324306050200201330ustar00rootroot00000000000000# include several source files (in SRCS) in one or more # combined files; each combined file holds maximally # MAXNUM files. # BASE gives basename for created files; # files corresponding to the pattern are deleted # first, so that there are no relicts from previous runs # with possibly different MAXNUM MACRO(COMBINE_SOURCES BASE SRCS MAXNUM) LIST(LENGTH SRCS SRCS_LENGTH) SET(COMB_COUNTER 0) FILE(GLOB EXISTING "${BASE}.*.cpp") IF("$EXISTING") FILE(REMOVE ${EXISTING}) ENDIF() SET(OUT "${BASE}.${COMB_COUNTER}.cpp") FILE(WRITE ${OUT}) SET(COUNTER 0) FOREACH(SRC ${SRCS}) if(${SRC} MATCHES "^/.*$") # absolute filename FILE(APPEND ${OUT} "#include<${SRC}>\n") else() FILE(APPEND ${OUT} "#include<${CMAKE_SOURCE_DIR}/${SRC}>\n") endif() MATH(EXPR COUNTER "${COUNTER}+1") IF(${COUNTER} EQUAL ${MAXNUM}) SET(COUNTER 0) MATH(EXPR COMB_COUNTER ${COMB_COUNTER}+1) SET(OUT "${BASE}.${COMB_COUNTER}.cpp") FILE(WRITE ${OUT}) ENDIF() ENDFOREACH() ENDMACRO() trunk-2018.02b/cMake/FindCGAL.cmake000066400000000000000000000027311324306050200165220ustar00rootroot00000000000000# http://code.google.com/p/pixelstruct/source/browse/trunk/cmake/FindCGAL.cmake?r=29 # # - Find CGAL # adapted from # # Find the CGAL includes and client library # This module defines # CGAL_DEFINITIONS: compiler flags for compiling with CGAL # CGAL_INCLUDE_DIR: where to find CGAL.h # CGAL_LIBRARIES: the libraries needed to use CGAL # CGAL_FOUND: if false, do not try to use CGAL IF(CGAL_INCLUDE_DIR AND CGAL_LIBRARIES) SET(CGAL_FOUND TRUE) ELSE(CGAL_INCLUDE_DIR AND CGAL_LIBRARIES) FIND_PATH(CGAL_INCLUDE_DIR CGAL/basic.h /usr/include/ /usr/local/include/ $ENV{ProgramFiles}/CGAL/*/include/ $ENV{SystemDrive}/CGAL/*/include/ ) FIND_LIBRARY(CGAL_LIBRARIES NAMES CGAL libCGAL PATHS /usr/lib /usr/local/lib /usr/lib/CGAL /usr/lib64 /usr/local/lib64 /usr/lib64/CGAL $ENV{ProgramFiles}/CGAL/*/lib/ms $ENV{SystemDrive}/CGAL/*/lib/ms ) IF(CGAL_INCLUDE_DIR AND CGAL_LIBRARIES) SET(CGAL_FOUND TRUE) MESSAGE(STATUS "Found CGAL: ${CGAL_INCLUDE_DIR}, ${CGAL_LIBRARIES}") INCLUDE_DIRECTORIES(${CGAL_INCLUDE_DIR}) ELSE(CGAL_INCLUDE_DIR AND CGAL_LIBRARIES) SET(CGAL_FOUND FALSE) MESSAGE(STATUS "CGAL not found.") ENDIF(CGAL_INCLUDE_DIR AND CGAL_LIBRARIES) MARK_AS_ADVANCED(CGAL_INCLUDE_DIR CGAL_LIBRARIES) ENDIF(CGAL_INCLUDE_DIR AND CGAL_LIBRARIES) trunk-2018.02b/cMake/FindCLP.cmake000077500000000000000000000024601324306050200164340ustar00rootroot00000000000000IF(CLP_INCLUDE_DIR AND CLP2_INCLUDE_DIR AND CLP_LIBRARY AND CLP2_LIBRARY AND CLP3_LIBRARY) SET(CLP_FOUND TRUE) ELSE(CLP_INCLUDE_DIR AND CLP2_INCLUDE_DIR AND CLP_LIBRARY AND CLP2_LIBRARY AND CLP3_LIBRARY) FIND_LIBRARY(CLP_LIBRARY NAMES Clp PATHS /usr/lib/x86_64-linux-gnu ) FIND_LIBRARY(CLP2_LIBRARY NAMES OsiClp PATHS /usr/lib/x86_64-linux-gnu ) FIND_LIBRARY(CLP3_LIBRARY NAMES CoinUtils PATHS /usr/lib/x86_64-linux-gnu ) FIND_PATH( CLP_INCLUDE_DIR ClpSimplexDual.hpp PATHS /usr/include/coin ) FIND_PATH( CLP2_INCLUDE_DIR coinutils.pc PATHS /usr/lib/x86_64-linux-gnu/pkgconfig ) IF(CLP_INCLUDE_DIR AND CLP2_INCLUDE_DIR AND CLP_LIBRARY AND CLP2_LIBRARY AND CLP3_LIBRARY) SET(CLP_FOUND TRUE) MESSAGE(STATUS "Found CLP: ${CLP_INCLUDE_DIR}, ${CLP_LIBRARY}") ELSE(CLP_INCLUDE_DIR AND CLP2_INCLUDE_DIR AND CLP_LIBRARY AND CLP2_LIBRARY AND CLP3_LIBRARY) SET(CLP_FOUND FALSE) MESSAGE(STATUS "CLP not found.") ENDIF(CLP_INCLUDE_DIR AND CLP2_INCLUDE_DIR AND CLP_LIBRARY AND CLP2_LIBRARY AND CLP3_LIBRARY) MARK_AS_ADVANCED(CLP_INCLUDE_DIR CLP2_INCLUDE_DIR CLP_LIBRARY CLP2_LIBRARY CLP3_LIBRARY) ENDIF(CLP_INCLUDE_DIR AND CLP2_INCLUDE_DIR AND CLP_LIBRARY AND CLP2_LIBRARY AND CLP3_LIBRARY) trunk-2018.02b/cMake/FindCholmod.cmake000066400000000000000000000070101324306050200173740ustar00rootroot00000000000000# - Try to find CHOLMOD # This will define # # CHOLMOD_FOUND - system has CHOLMOD # CHOLMOD_LIBRARIES - library to link against to use Cholmod # CHOLMOD_INCLUDE_DIR - where to find cholmod.h, etc. # AMD_LIBRARY - needed by CHOLMOD # COLAMD_LIBRARY - needed by CHOLMOD # CCOLAMD_LIBRARY - needed by CHOLMOD # CAMD_LIBRARY - needed by CHOLMOD FIND_LIBRARY(CHOLMOD_LIBRARIES NAMES cholmod libcholmod PATHS /usr/lib /usr/local/lib /usr/lib/CGAL /usr/lib64 /usr/local/lib64 /usr/lib64/CGAL /usr/local/SuiteSparse/lib ) FIND_LIBRARY(AMD_LIBRARY NAMES amd PATHS /usr/lib /usr/local/lib /usr/lib/CGAL /usr/lib64 /usr/local/lib64 /usr/lib64/CGAL /usr/local/SuiteSparse/lib) FIND_LIBRARY(CAMD_LIBRARY NAMES camd PATHS /usr/lib /usr/local/lib /usr/lib/CGAL /usr/lib64 /usr/local/lib64 /usr/lib64/CGAL /usr/local/SuiteSparse/lib) FIND_LIBRARY(COLAMD_LIBRARY NAMES colamd PATHS /usr/lib /usr/local/lib /usr/lib/CGAL /usr/lib64 /usr/local/lib64 /usr/lib64/CGAL /usr/local/SuiteSparse/lib) FIND_LIBRARY(CCOLAMD_LIBRARY NAMES ccolamd PATHS /usr/lib /usr/local/lib /usr/lib/CGAL /usr/lib64 /usr/local/lib64 /usr/lib64/CGAL /usr/local/SuiteSparse/lib) FIND_PATH(CHOLMOD_INCLUDE_DIR cholmod.h PATH /usr/include /usr/include/suitesparse /usr/local/SuiteSparse/include) # Check the suitesparse library version and set #ifdefs accordingly if (CHOLMOD_INCLUDE_DIR AND CHOLMOD_LIBRARIES) # SuiteSparse version >= 4. set(SUITESPARSE_VERSION_FILE ${CHOLMOD_INCLUDE_DIR}/SuiteSparse_config.h) if (NOT EXISTS ${SUITESPARSE_VERSION_FILE}) MESSAGE(STATUS "Could not find file ${SUITESPARSE_VERSION_FILE} containing version information for SuiteSparse version >=4. Direct CHOLMOD solver for CPU will be deactivated.") else (NOT EXISTS ${SUITESPARSE_VERSION_FILE}) file(READ ${SUITESPARSE_VERSION_FILE} SUITESPARSE_CONFIG_CONTENTS) string(REGEX MATCH "#define SUITESPARSE_MAIN_VERSION [0-9]+" SUITESPARSE_MAIN_VERSION "${SUITESPARSE_CONFIG_CONTENTS}") string(REGEX REPLACE "#define SUITESPARSE_MAIN_VERSION ([0-9]+)" "\\1" SUITESPARSE_MAIN_VERSION "${SUITESPARSE_MAIN_VERSION}") string(REGEX MATCH "#define SUITESPARSE_SUB_VERSION [0-9]+" SUITESPARSE_SUB_VERSION "${SUITESPARSE_CONFIG_CONTENTS}") string(REGEX REPLACE "#define SUITESPARSE_SUB_VERSION ([0-9]+)" "\\1" SUITESPARSE_SUB_VERSION "${SUITESPARSE_SUB_VERSION}") string(REGEX MATCH "#define SUITESPARSE_SUBSUB_VERSION [0-9]+" SUITESPARSE_SUBSUB_VERSION "${SUITESPARSE_CONFIG_CONTENTS}") string(REGEX REPLACE "#define SUITESPARSE_SUBSUB_VERSION ([0-9]+)" "\\1" SUITESPARSE_SUBSUB_VERSION "${SUITESPARSE_SUBSUB_VERSION}") set(SUITESPARSE_VERSION "${SUITESPARSE_MAIN_VERSION}.${SUITESPARSE_SUB_VERSION}.${SUITESPARSE_SUBSUB_VERSION}") MESSAGE(STATUS "SuiteSparse version ${SUITESPARSE_VERSION} found, CHOLMOD direct solver for CPU activated.") endif (NOT EXISTS ${SUITESPARSE_VERSION_FILE}) endif (CHOLMOD_INCLUDE_DIR AND CHOLMOD_LIBRARIES) if ((SUITESPARSE_MAIN_VERSION GREATER 4) OR (SUITESPARSE_MAIN_VERSION EQUAL 4)) ADD_DEFINITIONS("-DSUITESPARSE_VERSION_4") endif ((SUITESPARSE_MAIN_VERSION GREATER 4) OR (SUITESPARSE_MAIN_VERSION EQUAL 4)) INCLUDE(FindPackageHandleStandardArgs) FIND_PACKAGE_HANDLE_STANDARD_ARGS(Cholmod DEFAULT_MSG CHOLMOD_LIBRARIES CHOLMOD_INCLUDE_DIR AMD_LIBRARY CAMD_LIBRARY COLAMD_LIBRARY CCOLAMD_LIBRARY) MARK_AS_ADVANCED(CHOLMOD_LIBRARIES CHOLMOD_INCLUDE_DIR AMD_LIBRARY CAMD_LIBRARY COLAMD_LIBRARY CCOLAMD_LIBRARY) trunk-2018.02b/cMake/FindCuBlas.cmake000077500000000000000000000011371324306050200171670ustar00rootroot00000000000000# - Find CuBlas library # # This module defines # CUBBLAS_LIBRARY, libraries to link against to use Cublas. # CUBLAS_FOUND, If false, do not try to use Cublas. FIND_LIBRARY(CUBLAS_LIBRARY NAMES cublas libcublas PATHS /usr/local/cuda/lib64 ) FIND_LIBRARY(CUDART_LIBRARY NAMES cudart libcudart PATHS /usr/local/cuda/lib64 ) # handle the QUIETLY and REQUIRED arguments and set LOKI_FOUND to TRUE if # all listed variables are TRUE INCLUDE(FindPackageHandleStandardArgs) FIND_PACKAGE_HANDLE_STANDARD_ARGS(CuBlas DEFAULT_MSG CUBLAS_LIBRARY CUDART_LIBRARY) MARK_AS_ADVANCED(CUBLAS_LIBRARY CUDART_LIBRARY) trunk-2018.02b/cMake/FindEigen3.cmake000066400000000000000000000056621324306050200171340ustar00rootroot00000000000000# - Try to find Eigen3 lib # # This module supports requiring a minimum version, e.g. you can do # find_package(Eigen3 3.1.2) # to require version 3.1.2 or newer of Eigen3. # # Once done this will define # # EIGEN3_FOUND - system has eigen lib with correct version # EIGEN3_INCLUDE_DIR - the eigen include directory # EIGEN3_VERSION - eigen version # Copyright (c) 2006, 2007 Montel Laurent, # Copyright (c) 2008, 2009 Gael Guennebaud, # Copyright (c) 2009 Benoit Jacob # Redistribution and use is allowed according to the terms of the 2-clause BSD license. if(NOT Eigen3_FIND_VERSION) if(NOT Eigen3_FIND_VERSION_MAJOR) set(Eigen3_FIND_VERSION_MAJOR 2) endif(NOT Eigen3_FIND_VERSION_MAJOR) if(NOT Eigen3_FIND_VERSION_MINOR) set(Eigen3_FIND_VERSION_MINOR 91) endif(NOT Eigen3_FIND_VERSION_MINOR) if(NOT Eigen3_FIND_VERSION_PATCH) set(Eigen3_FIND_VERSION_PATCH 0) endif(NOT Eigen3_FIND_VERSION_PATCH) set(Eigen3_FIND_VERSION "${Eigen3_FIND_VERSION_MAJOR}.${Eigen3_FIND_VERSION_MINOR}.${Eigen3_FIND_VERSION_PATCH}") endif(NOT Eigen3_FIND_VERSION) macro(_eigen3_check_version) file(READ "${EIGEN3_INCLUDE_DIR}/Eigen/src/Core/util/Macros.h" _eigen3_version_header) string(REGEX MATCH "define[ \t]+EIGEN_WORLD_VERSION[ \t]+([0-9]+)" _eigen3_world_version_match "${_eigen3_version_header}") set(EIGEN3_WORLD_VERSION "${CMAKE_MATCH_1}") string(REGEX MATCH "define[ \t]+EIGEN_MAJOR_VERSION[ \t]+([0-9]+)" _eigen3_major_version_match "${_eigen3_version_header}") set(EIGEN3_MAJOR_VERSION "${CMAKE_MATCH_1}") string(REGEX MATCH "define[ \t]+EIGEN_MINOR_VERSION[ \t]+([0-9]+)" _eigen3_minor_version_match "${_eigen3_version_header}") set(EIGEN3_MINOR_VERSION "${CMAKE_MATCH_1}") set(EIGEN3_VERSION ${EIGEN3_WORLD_VERSION}.${EIGEN3_MAJOR_VERSION}.${EIGEN3_MINOR_VERSION}) if(${EIGEN3_VERSION} VERSION_LESS ${Eigen3_FIND_VERSION}) set(EIGEN3_VERSION_OK FALSE) else(${EIGEN3_VERSION} VERSION_LESS ${Eigen3_FIND_VERSION}) set(EIGEN3_VERSION_OK TRUE) endif(${EIGEN3_VERSION} VERSION_LESS ${Eigen3_FIND_VERSION}) if(NOT EIGEN3_VERSION_OK) message(STATUS "Eigen3 version ${EIGEN3_VERSION} found in ${EIGEN3_INCLUDE_DIR}, " "but at least version ${Eigen3_FIND_VERSION} is required") endif(NOT EIGEN3_VERSION_OK) endmacro(_eigen3_check_version) if (EIGEN3_INCLUDE_DIR) # in cache already _eigen3_check_version() set(EIGEN3_FOUND ${EIGEN3_VERSION_OK}) else (EIGEN3_INCLUDE_DIR) find_path(EIGEN3_INCLUDE_DIR NAMES signature_of_eigen3_matrix_library PATHS ${CMAKE_INSTALL_PREFIX}/include ${KDE4_INCLUDE_DIR} PATH_SUFFIXES eigen3 eigen ) if(EIGEN3_INCLUDE_DIR) _eigen3_check_version() endif(EIGEN3_INCLUDE_DIR) include(FindPackageHandleStandardArgs) find_package_handle_standard_args(Eigen3 DEFAULT_MSG EIGEN3_INCLUDE_DIR EIGEN3_VERSION_OK) mark_as_advanced(EIGEN3_INCLUDE_DIR) endif(EIGEN3_INCLUDE_DIR) trunk-2018.02b/cMake/FindGL2PS.cmake000066400000000000000000000030501324306050200166360ustar00rootroot00000000000000# https://unihub.ru/tools/ofservice/browser/trunk/1.7/ThirdParty-1.7.1/paraview-3.8.0/VTK/CMake/FindGL2PS.cmake?rev=2 # - Find GL2PS library # Find the native GL2PS includes and library # This module defines # GL2PS_INCLUDE_DIR, where to find tiff.h, etc. # GL2PS_LIBRARIES, libraries to link against to use GL2PS. # GL2PS_FOUND, If false, do not try to use GL2PS. # also defined, but not for general use are # GL2PS_LIBRARY, where to find the GL2PS library. #============================================================================= # Copyright 2009 Kitware, Inc. # Copyright 2009 Mathieu Malaterre # # Distributed under the OSI-approved BSD License (the "License"); # see accompanying file Copyright.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 License for more information. #============================================================================= # (To distributed this file outside of CMake, substitute the full # License text for the above reference.) FIND_PATH(GL2PS_INCLUDE_DIR gl2ps.h) FIND_LIBRARY(GL2PS_LIBRARY NAMES gl2ps ) # handle the QUIETLY and REQUIRED arguments and set GL2PS_FOUND to TRUE if # all listed variables are TRUE INCLUDE(FindPackageHandleStandardArgs) FIND_PACKAGE_HANDLE_STANDARD_ARGS(GL2PS DEFAULT_MSG GL2PS_LIBRARY GL2PS_INCLUDE_DIR) IF(GL2PS_FOUND) SET( GL2PS_LIBRARIES ${GL2PS_LIBRARY} ) ENDIF(GL2PS_FOUND) MARK_AS_ADVANCED(GL2PS_INCLUDE_DIR GL2PS_LIBRARY) trunk-2018.02b/cMake/FindGMP.cmake000066400000000000000000000016701324306050200164400ustar00rootroot00000000000000# http://liris.cnrs.fr/dgtal/cgi-bin/trac.cgi/browser/trunk/cmake/FindGMP.cmake # # Try to find the GMP librairies # GMP_FOUND - system has GMP lib # GMP_INCLUDE_DIR - the GMP include directory # GMP_LIBRARIES - Libraries needed to use GMP # Copyright (c) 2006, Laurent Montel, # # Redistribution and use is allowed according to the terms of the BSD license. # For details see the accompanying COPYING-CMAKE-SCRIPTS file. if (GMP_INCLUDE_DIR AND GMP_LIBRARIES) # Already in cache, be silent set(GMP_FIND_QUIETLY TRUE) endif (GMP_INCLUDE_DIR AND GMP_LIBRARIES) find_path(GMP_INCLUDE_DIR NAMES gmp.h ) find_library(GMP_LIBRARIES NAMES gmp libgmp ) find_library(GMPXX_LIBRARIES NAMES gmpxx libgmpxx ) MESSAGE(STATUS "GMP libs: " ${GMP_LIBRARIES} " " ${GMPXX_LIBRARIES} ) INCLUDE_DIRECTORIES(${GMP_INCLUDE_DIR}) include(FindPackageHandleStandardArgs) FIND_PACKAGE_HANDLE_STANDARD_ARGS(GMP DEFAULT_MSG GMP_INCLUDE_DIR GMP_LIBRARIES) trunk-2018.02b/cMake/FindGMPXX.cmake000066400000000000000000000016311324306050200167150ustar00rootroot00000000000000# https://svn.zib.de/lenne3d/lib/qpl_cgal/3.5.1/cmake/modules/FindGMPXX.cmake # # Try to find the GMPXX libraries # GMPXX_FOUND - system has GMPXX lib # GMPXX_INCLUDE_DIR - the GMPXX include directory # GMPXX_LIBRARIES - Libraries needed to use GMPXX # TODO: support Windows and MacOSX # GMPXX needs GMP find_package( GMP QUIET ) if(GMP_FOUND) if (GMPXX_INCLUDE_DIR AND GMPXX_LIBRARIES) # Already in cache, be silent set(GMPXX_FIND_QUIETLY TRUE) endif() find_path(GMPXX_INCLUDE_DIR NAMES gmpxx.h PATHS ${GMP_INCLUDE_DIR_SEARCH} DOC "The directory containing the GMPXX include files" ) find_library(GMPXX_LIBRARIES NAMES gmpxx PATHS ${GMP_LIBRARIES_DIR_SEARCH} DOC "Path to the GMPXX library" ) find_package_handle_standard_args(GMPXX "DEFAULT_MSG" GMPXX_LIBRARIES GMPXX_INCLUDE_DIR ) endif() trunk-2018.02b/cMake/FindGTS.cmake000066400000000000000000000125511324306050200164520ustar00rootroot00000000000000# http://code.google.com/p/booststl/source/browse/trunk/Modules/FindGTS.cmake?r=25 # Try to find gnu triangulation library GTS # See # http://gts.sf.net # # Once run this will define: # # GTS_FOUND = system has GTS lib # # GTS_LIBRARIES = full path to the libraries # on Unix/Linux with additional linker flags from "gts-config --libs" # # CMAKE_GTS_CXX_FLAGS = Unix compiler flags for GTS, essentially "`gts-config --cxxflags`" # # GTS_INCLUDE_DIR = where to find headers # # GTS_LINK_DIRECTORIES = link directories, useful for rpath on Unix # GTS_EXE_LINKER_FLAGS = rpath on Unix # # # GTS modyfication by # A.Khalatyan 05/2009 # # www.aip.de/~arm2arm # # This script is based on GSL script by # Felix Woelk 07/2004 # and Jan Woetzel # www.mip.informatik.uni-kiel.de # -------------------------------- IF(WIN32) # JW tested with gsl-1.8, Windows XP, MSVS 7.1, MSVS 8.0 SET(GTS_POSSIBLE_ROOT_DIRS ${GTS_ROOT_DIR} $ENV{GTS_ROOT_DIR} ${GTS_DIR} ${GTS_HOME} $ENV{GTS_DIR} $ENV{GTS_HOME} $ENV{EXTERN_LIBS_DIR}/gts $ENV{EXTRA} # "C:/home/arm2arm/SOFTWARE/gts-0.7.6" ) FIND_PATH(GTS_INCLUDE_DIR NAMES gts.h gtsconfig.h PATHS ${GTS_POSSIBLE_ROOT_DIRS} PATH_SUFFIXES include DOC "GTS header include dir" ) FIND_LIBRARY(GTS_GTS_LIBRARY NAMES gts libgts PATHS ${GTS_POSSIBLE_ROOT_DIRS} PATH_SUFFIXES lib DOC "GTS library dir" ) # FIND_LIBRARY(GTS_GLIB_LIBRARY # NAMES glib libgslcblas # PATHS ${GSL_POSSIBLE_ROOT_DIRS} # PATH_SUFFIXES lib # DOC "GSL cblas library dir" ) SET(GTS_LIBRARIES ${GTS_GTS_LIBRARY}) #MESSAGE("DBG\n" # "GSL_GSL_LIBRARY=${GSL_GSL_LIBRARY}\n" # "GSL_GSLCBLAS_LIBRARY=${GSL_GSLCBLAS_LIBRARY}\n" # "GSL_LIBRARIES=${GSL_LIBRARIES}") ELSE(WIN32) IF(UNIX) SET(GSL_CONFIG_PREFER_PATH "$ENV{GTS_DIR}/bin" "$ENV{GTS_DIR}" "$ENV{GTS_HOME}/bin" "$ENV{GTS_HOME}" CACHE STRING "preferred path to GTS (gts-config)") FIND_PROGRAM(GTS_CONFIG gts-config ${GTS_CONFIG_PREFER_PATH} /work2/arm2arm/SOFTWARE/bin/ ) #MESSAGE("DBG GTS_CONFIG ${GTS_CONFIG}") IF (GTS_CONFIG) MESSAGE(STATUS "GTS using gts-config ${GTS_CONFIG}") # set CXXFLAGS to be fed into CXX_FLAGS by the user: EXEC_PROGRAM(${GTS_CONFIG} ARGS --cflags OUTPUT_VARIABLE GTS_CXX_FLAGS ) #SET(GTS_CXX_FLAGS "`${GTS_CONFIG} --cflags`") SET(CMAKE_GTS_CXX_FLAGS ${GTS_CXX_FLAGS}) # MESSAGE("DBG ${GTS_CXX_FLAGS}") # set INCLUDE_DIRS to prefix+include EXEC_PROGRAM(${GTS_CONFIG} ARGS --prefix OUTPUT_VARIABLE GTS_PREFIX) SET(GTS_INCLUDE_DIR ${GTS_PREFIX}/include CACHE STRING INTERNAL) # set link libraries and link flags #SET(GSL_LIBRARIES "`${GSL_CONFIG} --libs`") # extract link dirs for rpath EXEC_PROGRAM(${GTS_CONFIG} ARGS --libs OUTPUT_VARIABLE GTS_CONFIG_LIBS ) SET(GTS_LIBRARIES "${GTS_CONFIG_LIBS}") # split off the link dirs (for rpath) # use regular expression to match wildcard equivalent "-L*" # with is a space or a semicolon STRING(REGEX MATCHALL "[-][L]([^ ;])+" GTS_LINK_DIRECTORIES_WITH_PREFIX "${GTS_CONFIG_LIBS}" ) # MESSAGE("DBG GSL_LINK_DIRECTORIES_WITH_PREFIX=${GSL_LINK_DIRECTORIES_WITH_PREFIX}") # remove prefix -L because we need the pure directory for LINK_DIRECTORIES IF (GTS_LINK_DIRECTORIES_WITH_PREFIX) STRING(REGEX REPLACE "[-][L]" "" GTS_LINK_DIRECTORIES ${GTS_LINK_DIRECTORIES_WITH_PREFIX} ) ENDIF (GTS_LINK_DIRECTORIES_WITH_PREFIX) SET(GTS_EXE_LINKER_FLAGS "-Wl,-rpath,${GTS_LINK_DIRECTORIES}" CACHE STRING INTERNAL) # MESSAGE("DBG GSL_LINK_DIRECTORIES=${GSL_LINK_DIRECTORIES}") # MESSAGE("DBG GSL_EXE_LINKER_FLAGS=${GSL_EXE_LINKER_FLAGS}") # ADD_DEFINITIONS("-DHAVE_GSL") # SET(GSL_DEFINITIONS "-DHAVE_GSL") MARK_AS_ADVANCED( GTS_CXX_FLAGS GTS_INCLUDE_DIR GTS_LIBRARIES GTS_LINK_DIRECTORIES GTS_DEFINITIONS ) MESSAGE(STATUS "Using GTS from ${GTS_PREFIX}") ELSE(GTS_CONFIG) INCLUDE(UsePkgConfig) #needed for PKGCONFIG(...) MESSAGE(STATUS "GSL using pkgconfig") # PKGCONFIG(gsl includedir libdir linkflags cflags) PKGCONFIG(gts GTS_INCLUDE_DIR GTS_LINK_DIRECTORIES GTS_LIBRARIES GTS_CXX_FLAGS) IF(GTS_INCLUDE_DIR) MARK_AS_ADVANCED( GTS_CXX_FLAGS GTS_INCLUDE_DIR GTS_LIBRARIES GTS_LINK_DIRECTORIES ) ELSE(GTS_INCLUDE_DIR) MESSAGE("FindGTS.cmake: gts-config/pkg-config gts not found. Please set it manually. GTS_CONFIG=${GTS_CONFIG}") ENDIF(GTS_INCLUDE_DIR) ENDIF(GTS_CONFIG) ENDIF(UNIX) ENDIF(WIN32) IF(GTS_LIBRARIES) IF(GTS_INCLUDE_DIR OR GTS_CXX_FLAGS) SET(GTS_FOUND 1) ENDIF(GTS_INCLUDE_DIR OR GTS_CXX_FLAGS) ENDIF(GTS_LIBRARIES) # ========================================== IF(NOT GTS_FOUND) # make FIND_PACKAGE friendly IF(NOT GTS_FIND_QUIETLY) IF(GTS_FIND_REQUIRED) MESSAGE(FATAL_ERROR "GTS required, please specify it's location.") ELSE(GTS_FIND_REQUIRED) MESSAGE(STATUS "ERROR: GTS was not found.") ENDIF(GTS_FIND_REQUIRED) ENDIF(NOT GTS_FIND_QUIETLY) ENDIF(NOT GTS_FOUND) trunk-2018.02b/cMake/FindLapack.cmake000066400000000000000000000007641324306050200172130ustar00rootroot00000000000000# - Find OpenBlas library # # This module defines # LAPACK_LIBRARY, libraries to link against to use Lapack. # LAPACK_FOUND, If false, do not try to use Lapack. FIND_LIBRARY(LAPACK_LIBRARY NAMES lapack liblapack PATHS /usr/lib /usr/lib/lapack ) # handle the QUIETLY and REQUIRED arguments and set LOKI_FOUND to TRUE if # all listed variables are TRUE INCLUDE(FindPackageHandleStandardArgs) FIND_PACKAGE_HANDLE_STANDARD_ARGS(Lapack DEFAULT_MSG LAPACK_LIBRARY) MARK_AS_ADVANCED(LAPACK_LIBRARY) trunk-2018.02b/cMake/FindLoki.cmake000066400000000000000000000010761324306050200167130ustar00rootroot00000000000000# - Find Loki library # # This module defines # LOKI_INCLUDE_DIR, where to find loki/Typelist.h, etc. # LOKI_LIBRARY, libraries to link against to use GL2PS. # LOKI_FOUND, If false, do not try to use GL2PS. FIND_PATH(LOKI_INCLUDE_DIR loki/Typelist.h) FIND_LIBRARY(LOKI_LIBRARY NAMES loki ) # handle the QUIETLY and REQUIRED arguments and set LOKI_FOUND to TRUE if # all listed variables are TRUE INCLUDE(FindPackageHandleStandardArgs) FIND_PACKAGE_HANDLE_STANDARD_ARGS(Loki DEFAULT_MSG LOKI_INCLUDE_DIR LOKI_LIBRARY) MARK_AS_ADVANCED(LOKI_INCLUDE_DIR LOKI_LIBRARY) trunk-2018.02b/cMake/FindMetis.cmake000066400000000000000000000012541324306050200170740ustar00rootroot00000000000000# - Find Metis library # # This module defines # METIS_INCLUDE_DIR, where to find loki/Typelist.h, etc. # METIS_LIBRARY, libraries to link against to use GL2PS. # METIS_FOUND, If false, do not try to use GL2PS. FIND_PATH(METIS_INCLUDE_DIR metis.h PATHS /usr/include/metis /usr/local/SuiteSparse/include) FIND_LIBRARY(METIS_LIBRARY NAMES metis parmetis PATHS /usr/lib /usr/local/SuiteSparse/lib) # handle the QUIETLY and REQUIRED arguments and set LOKI_FOUND to TRUE if # all listed variables are TRUE INCLUDE(FindPackageHandleStandardArgs) FIND_PACKAGE_HANDLE_STANDARD_ARGS(Metis DEFAULT_MSG METIS_INCLUDE_DIR METIS_LIBRARY) MARK_AS_ADVANCED(METIS_INCLUDE_DIR METIS_LIBRARY) trunk-2018.02b/cMake/FindNumPy.cmake000066400000000000000000000072411324306050200170650ustar00rootroot00000000000000# https://github.com/ContinuumIO/dynd-python/blob/master/FindNumPy.cmake # - Find the NumPy libraries # This module finds if NumPy is installed, and sets the following variables # indicating where it is. # # TODO: Update to provide the libraries and paths for linking npymath lib. # # NUMPY_FOUND - was NumPy found # NUMPY_VERSION - the version of NumPy found as a string # NUMPY_VERSION_MAJOR - the major version number of NumPy # NUMPY_VERSION_MINOR - the minor version number of NumPy # NUMPY_VERSION_PATCH - the patch version number of NumPy # NUMPY_VERSION_DECIMAL - e.g. version 1.6.1 is 10601 # NUMPY_INCLUDE_DIRS - path to the NumPy include files #============================================================================ # Copyright 2012 Continuum Analytics, Inc. # # MIT License # # 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. # #============================================================================ # Finding NumPy involves calling the Python interpreter if(NumPy_FIND_REQUIRED) find_package(PythonInterp REQUIRED) else() find_package(PythonInterp) endif() if(NOT PYTHONINTERP_FOUND) set(NUMPY_FOUND FALSE) return() endif() execute_process(COMMAND "${PYTHON_EXECUTABLE}" "-c" "import numpy as n; print(n.__version__); print(n.get_include());" RESULT_VARIABLE _NUMPY_SEARCH_SUCCESS OUTPUT_VARIABLE _NUMPY_VALUES ERROR_VARIABLE _NUMPY_ERROR_VALUE OUTPUT_STRIP_TRAILING_WHITESPACE) if(NOT _NUMPY_SEARCH_SUCCESS MATCHES 0) if(NumPy_FIND_REQUIRED) message(FATAL_ERROR "NumPy import failure:\n${_NUMPY_ERROR_VALUE}") endif() set(NUMPY_FOUND FALSE) return() endif() # Convert the process output into a list string(REGEX REPLACE ";" "\\\\;" _NUMPY_VALUES ${_NUMPY_VALUES}) string(REGEX REPLACE "\n" ";" _NUMPY_VALUES ${_NUMPY_VALUES}) list(GET _NUMPY_VALUES 0 NUMPY_VERSION) list(GET _NUMPY_VALUES 1 NUMPY_INCLUDE_DIRS) # Make sure all directory separators are '/' string(REGEX REPLACE "\\\\" "/" NUMPY_INCLUDE_DIRS ${NUMPY_INCLUDE_DIRS}) # Get the major and minor version numbers string(REGEX REPLACE "\\." ";" _NUMPY_VERSION_LIST ${NUMPY_VERSION}) list(GET _NUMPY_VERSION_LIST 0 NUMPY_VERSION_MAJOR) list(GET _NUMPY_VERSION_LIST 1 NUMPY_VERSION_MINOR) list(GET _NUMPY_VERSION_LIST 2 NUMPY_VERSION_PATCH) string(REGEX MATCH "[0-9]*" NUMPY_VERSION_PATCH ${NUMPY_VERSION_PATCH}) math(EXPR NUMPY_VERSION_DECIMAL "(${NUMPY_VERSION_MAJOR} * 10000) + (${NUMPY_VERSION_MINOR} * 100) + ${NUMPY_VERSION_PATCH}") find_package_message(NUMPY "Found NumPy: version \"${NUMPY_VERSION}\" ${NUMPY_INCLUDE_DIRS}" "${NUMPY_INCLUDE_DIRS}${NUMPY_VERSION}") set(NUMPY_FOUND TRUE) trunk-2018.02b/cMake/FindOpenBlas.cmake000066400000000000000000000007771324306050200175270ustar00rootroot00000000000000# - Find OpenBlas library # # This module defines # OPENBLAS_LIBRARY, libraries to link against to use Openblas. # OPENBLAS_FOUND, If false, do not try to use Openblas. FIND_LIBRARY(OPENBLAS_LIBRARY NAMES openblas blas PATHS /usr/lib/openblas-base ) # handle the QUIETLY and REQUIRED arguments and set LOKI_FOUND to TRUE if # all listed variables are TRUE INCLUDE(FindPackageHandleStandardArgs) FIND_PACKAGE_HANDLE_STANDARD_ARGS(OpenBlas DEFAULT_MSG OPENBLAS_LIBRARY) MARK_AS_ADVANCED(OPENBLAS_LIBRARY) trunk-2018.02b/cMake/FindPythonModule.cmake000066400000000000000000000015551324306050200204460ustar00rootroot00000000000000# http://www.cmake.org/pipermail/cmake/2011-January/041666.html # # - Find Python Module FUNCTION(find_python_module module) STRING(TOUPPER ${module} module_upper) IF(ARGC GREATER 1 AND ARGV1 STREQUAL "REQUIRED") SET(${module}_FIND_REQUIRED TRUE) ENDIF(ARGC GREATER 1 AND ARGV1 STREQUAL "REQUIRED") EXECUTE_PROCESS(COMMAND "${PYTHON_EXECUTABLE}" "-c" "import re, ${module}; print re.compile('/__init__.py.*').sub('',${module}.__file__)" RESULT_VARIABLE _${module}_status OUTPUT_VARIABLE _${module}_location ERROR_QUIET OUTPUT_STRIP_TRAILING_WHITESPACE) IF(_${module}_status MATCHES 0) SET(PY_${module} ${_${module}_location} CACHE STRING "Location of Python module ${module}") ENDIF(_${module}_status MATCHES 0) FIND_PACKAGE_HANDLE_STANDARD_ARGS(${module} DEFAULT_MSG PY_${module}) ENDFUNCTION(find_python_module) trunk-2018.02b/cMake/FindQGLVIEWER-qt4.cmake000066400000000000000000000013461324306050200200700ustar00rootroot00000000000000# - Try to find QGLViewer # Once done this will define # # QGLVIEWER_FOUND - system has QGLViewer # QGLVIEWER_INCLUDE_DIR - the QGLViewer include directory # QGLVIEWER_LIBRARIES - Link these to use QGLViewer find_path(QGLVIEWER_INCLUDE_DIR NAMES qglviewer.h PATHS /usr/include/QGLViewer ) find_library(QGLVIEWER_LIBRARIES NAMES QGLViewer QGLViewer-qt4 ) IF(QGLVIEWER_INCLUDE_DIR AND QGLVIEWER_LIBRARIES) SET(QGLVIEWER_FOUND TRUE) SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -DQGLVIEWER_FOUND") ENDIF(QGLVIEWER_INCLUDE_DIR AND QGLVIEWER_LIBRARIES) include(FindPackageHandleStandardArgs) FIND_PACKAGE_HANDLE_STANDARD_ARGS(QGLVIEWER-qt4 DEFAULT_MSG QGLVIEWER_INCLUDE_DIR QGLVIEWER_LIBRARIES) trunk-2018.02b/cMake/FindQGLVIEWER-qt5.cmake000066400000000000000000000013461324306050200200710ustar00rootroot00000000000000# - Try to find QGLViewer # Once done this will define # # QGLVIEWER_FOUND - system has QGLViewer # QGLVIEWER_INCLUDE_DIR - the QGLViewer include directory # QGLVIEWER_LIBRARIES - Link these to use QGLViewer find_path(QGLVIEWER_INCLUDE_DIR NAMES qglviewer.h PATHS /usr/include/QGLViewer ) find_library(QGLVIEWER_LIBRARIES NAMES QGLViewer QGLViewer-qt5 ) IF(QGLVIEWER_INCLUDE_DIR AND QGLVIEWER_LIBRARIES) SET(QGLVIEWER_FOUND TRUE) SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -DQGLVIEWER_FOUND") ENDIF(QGLVIEWER_INCLUDE_DIR AND QGLVIEWER_LIBRARIES) include(FindPackageHandleStandardArgs) FIND_PACKAGE_HANDLE_STANDARD_ARGS(QGLVIEWER-qt5 DEFAULT_MSG QGLVIEWER_INCLUDE_DIR QGLVIEWER_LIBRARIES) trunk-2018.02b/cMake/Findglib2.cmake000066400000000000000000000135241324306050200170150ustar00rootroot00000000000000# Downloaded from http://opensource.bolloretelecom.eu/projects/boc-wimax/browser/cmake/modules/FindGLIB2.cmake?rev=8f5b254534bd304923d4cc7bc7e9d6552c119ea2 # - Try to find GLib2 # Once done this will define # # GLIB2_FOUND - system has GLib2 # GLIB2_INCLUDE_DIRS - the GLib2 include directory # GLIB2_LIBRARIES - Link these to use GLib2 # # HAVE_GLIB_GREGEX_H glib has gregex.h header and # supports g_regex_match_simple # # Copyright (c) 2006 Andreas Schneider # Copyright (c) 2006 Philippe Bernery # Copyright (c) 2007 Daniel Gollub # Copyright (c) 2007 Alban Browaeys # Copyright (c) 2008 Michael Bell # Copyright (c) 2008 Bjoern Ricks # # Redistribution and use is allowed according to the terms of the New # BSD license. # For details see the accompanying COPYING-CMAKE-SCRIPTS file. # IF (GLIB2_LIBRARIES AND GLIB2_INCLUDE_DIRS ) # in cache already SET(GLIB2_FOUND TRUE) ELSE (GLIB2_LIBRARIES AND GLIB2_INCLUDE_DIRS ) INCLUDE(FindPkgConfig) ## Glib IF ( GLIB2_FIND_REQUIRED ) SET( _pkgconfig_REQUIRED "REQUIRED" ) ELSE ( GLIB2_FIND_REQUIRED ) SET( _pkgconfig_REQUIRED "" ) ENDIF ( GLIB2_FIND_REQUIRED ) IF ( GLIB2_MIN_VERSION ) PKG_SEARCH_MODULE( GLIB2 ${_pkgconfig_REQUIRED} glib-2.0>=${GLIB2_MIN_VERSION} ) ELSE ( GLIB2_MIN_VERSION ) PKG_SEARCH_MODULE( GLIB2 ${_pkgconfig_REQUIRED} glib-2.0 ) ENDIF ( GLIB2_MIN_VERSION ) IF ( PKG_CONFIG_FOUND ) IF ( GLIB2_FOUND ) SET ( GLIB2_CORE_FOUND TRUE ) ELSE ( GLIB2_FOUND ) SET ( GLIB2_CORE_FOUND FALSE ) ENDIF ( GLIB2_FOUND ) ENDIF ( PKG_CONFIG_FOUND ) # Look for glib2 include dir and libraries w/o pkgconfig IF ( NOT GLIB2_FOUND AND NOT PKG_CONFIG_FOUND ) FIND_PATH( _glibconfig_include_DIR NAMES glibconfig.h PATHS /opt/gnome/lib64 /opt/gnome/lib /opt/lib/ /opt/local/lib /sw/lib/ /usr/lib64 /usr/lib /usr/local/include ${CMAKE_LIBRARY_PATH} PATH_SUFFIXES glib-2.0/include ) FIND_PATH( _glib2_include_DIR NAMES glib.h PATHS /opt/gnome/include /opt/local/include /sw/include /usr/include /usr/local/include PATH_SUFFIXES glib-2.0 ) #MESSAGE(STATUS "Glib headers: ${_glib2_include_DIR}") FIND_LIBRARY( _glib2_link_DIR NAMES glib-2.0 glib PATHS /opt/gnome/lib /opt/local/lib /sw/lib /usr/lib /usr/local/lib ) IF ( _glib2_include_DIR AND _glib2_link_DIR ) SET ( _glib2_FOUND TRUE ) ENDIF ( _glib2_include_DIR AND _glib2_link_DIR ) IF ( _glib2_FOUND ) SET ( GLIB2_INCLUDE_DIRS ${_glib2_include_DIR} ${_glibconfig_include_DIR} ) SET ( GLIB2_LIBRARIES ${_glib2_link_DIR} ) SET ( GLIB2_CORE_FOUND TRUE ) ELSE ( _glib2_FOUND ) SET ( GLIB2_CORE_FOUND FALSE ) ENDIF ( _glib2_FOUND ) # Handle dependencies # libintl IF ( NOT LIBINTL_FOUND ) FIND_PATH(LIBINTL_INCLUDE_DIR NAMES libintl.h PATHS /opt/gnome/include /opt/local/include /sw/include /usr/include /usr/local/include ) FIND_LIBRARY(LIBINTL_LIBRARY NAMES intl PATHS /opt/gnome/lib /opt/local/lib /sw/lib /usr/local/lib /usr/lib ) IF (LIBINTL_LIBRARY AND LIBINTL_INCLUDE_DIR) SET (LIBINTL_FOUND TRUE) ENDIF (LIBINTL_LIBRARY AND LIBINTL_INCLUDE_DIR) ENDIF ( NOT LIBINTL_FOUND ) # libiconv IF ( NOT LIBICONV_FOUND ) FIND_PATH(LIBICONV_INCLUDE_DIR NAMES iconv.h PATHS /opt/gnome/include /opt/local/include /opt/local/include /sw/include /sw/include /usr/local/include /usr/include PATH_SUFFIXES glib-2.0 ) FIND_LIBRARY(LIBICONV_LIBRARY NAMES iconv PATHS /opt/gnome/lib /opt/local/lib /sw/lib /usr/lib /usr/local/lib ) IF (LIBICONV_LIBRARY AND LIBICONV_INCLUDE_DIR) SET (LIBICONV_FOUND TRUE) ENDIF (LIBICONV_LIBRARY AND LIBICONV_INCLUDE_DIR) ENDIF ( NOT LIBICONV_FOUND ) IF (LIBINTL_FOUND) SET (GLIB2_LIBRARIES ${GLIB2_LIBRARIES} ${LIBINTL_LIBRARY}) SET (GLIB2_INCLUDE_DIRS ${GLIB2_INCLUDE_DIRS} ${LIBINTL_INCLUDE_DIR}) ENDIF (LIBINTL_FOUND) IF (LIBICONV_FOUND) SET (GLIB2_LIBRARIES ${GLIB2_LIBRARIES} ${LIBICONV_LIBRARY}) SET (GLIB2_INCLUDE_DIRS ${GLIB2_INCLUDE_DIRS} ${LIBICONV_INCLUDE_DIR}) ENDIF (LIBICONV_FOUND) ENDIF ( NOT GLIB2_FOUND AND NOT PKG_CONFIG_FOUND ) ## IF (GLIB2_CORE_FOUND AND GLIB2_INCLUDE_DIRS AND GLIB2_LIBRARIES) SET (GLIB2_FOUND TRUE) ENDIF (GLIB2_CORE_FOUND AND GLIB2_INCLUDE_DIRS AND GLIB2_LIBRARIES) IF (GLIB2_FOUND) IF (NOT GLIB2_FIND_QUIETLY) MESSAGE (STATUS "Found GLib2: ${GLIB2_LIBRARIES} ${GLIB2_INCLUDE_DIRS}") ENDIF (NOT GLIB2_FIND_QUIETLY) ELSE (GLIB2_FOUND) IF (GLIB2_FIND_REQUIRED) MESSAGE (SEND_ERROR "Could not find GLib2") ENDIF (GLIB2_FIND_REQUIRED) ENDIF (GLIB2_FOUND) # show the GLIB2_INCLUDE_DIRS and GLIB2_LIBRARIES variables only in the advanced view MARK_AS_ADVANCED(GLIB2_INCLUDE_DIRS GLIB2_LIBRARIES) MARK_AS_ADVANCED(LIBICONV_INCLUDE_DIR LIBICONV_LIBRARY) MARK_AS_ADVANCED(LIBINTL_INCLUDE_DIR LIBINTL_LIBRARY) ENDIF (GLIB2_LIBRARIES AND GLIB2_INCLUDE_DIRS) IF ( GLIB2_FOUND ) # Check if system has a newer version of glib # which supports g_regex_match_simple INCLUDE( CheckIncludeFiles ) SET( CMAKE_REQUIRED_INCLUDES ${GLIB2_INCLUDE_DIRS} ) CHECK_INCLUDE_FILES ( glib/gregex.h HAVE_GLIB_GREGEX_H ) # Reset CMAKE_REQUIRED_INCLUDES SET( CMAKE_REQUIRED_INCLUDES "" ) ENDIF( GLIB2_FOUND ) trunk-2018.02b/cMake/GNUInstallDirs.cmake000066400000000000000000000163451324306050200200230ustar00rootroot00000000000000# - Define GNU standard installation directories # Provides install directory variables as defined for GNU software: # http://www.gnu.org/prep/standards/html_node/Directory-Variables.html # Inclusion of this module defines the following variables: # CMAKE_INSTALL_ - destination for files of a given type # CMAKE_INSTALL_FULL_ - corresponding absolute path # where is one of: # BINDIR - user executables (bin) # SBINDIR - system admin executables (sbin) # LIBEXECDIR - program executables (libexec) # SYSCONFDIR - read-only single-machine data (etc) # SHAREDSTATEDIR - modifiable architecture-independent data (com) # LOCALSTATEDIR - modifiable single-machine data (var) # LIBDIR - object code libraries (lib or lib64 or lib/ on Debian) # INCLUDEDIR - C header files (include) # OLDINCLUDEDIR - C header files for non-gcc (/usr/include) # DATAROOTDIR - read-only architecture-independent data root (share) # DATADIR - read-only architecture-independent data (DATAROOTDIR) # INFODIR - info documentation (DATAROOTDIR/info) # LOCALEDIR - locale-dependent data (DATAROOTDIR/locale) # MANDIR - man documentation (DATAROOTDIR/man) # DOCDIR - documentation root (DATAROOTDIR/doc/PROJECT_NAME) # Each CMAKE_INSTALL_ value may be passed to the DESTINATION options of # install() commands for the corresponding file type. If the includer does # not define a value the above-shown default will be used and the value will # appear in the cache for editing by the user. # Each CMAKE_INSTALL_FULL_ value contains an absolute path constructed # from the corresponding destination by prepending (if necessary) the value # of CMAKE_INSTALL_PREFIX. #============================================================================= # Copyright 2011 Nikita Krupen'ko # Copyright 2011 Kitware, Inc. # # Distributed under the OSI-approved BSD License (the "License"); # see accompanying file Copyright.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 License for more information. #============================================================================= # (To distribute this file outside of CMake, substitute the full # License text for the above reference.) # Installation directories # if(NOT DEFINED CMAKE_INSTALL_BINDIR) set(CMAKE_INSTALL_BINDIR "bin" CACHE PATH "user executables (bin)") endif() if(NOT DEFINED CMAKE_INSTALL_SBINDIR) set(CMAKE_INSTALL_SBINDIR "sbin" CACHE PATH "system admin executables (sbin)") endif() if(NOT DEFINED CMAKE_INSTALL_LIBEXECDIR) set(CMAKE_INSTALL_LIBEXECDIR "libexec" CACHE PATH "program executables (libexec)") endif() if(NOT DEFINED CMAKE_INSTALL_SYSCONFDIR) set(CMAKE_INSTALL_SYSCONFDIR "etc" CACHE PATH "read-only single-machine data (etc)") endif() if(NOT DEFINED CMAKE_INSTALL_SHAREDSTATEDIR) set(CMAKE_INSTALL_SHAREDSTATEDIR "com" CACHE PATH "modifiable architecture-independent data (com)") endif() if(NOT DEFINED CMAKE_INSTALL_LOCALSTATEDIR) set(CMAKE_INSTALL_LOCALSTATEDIR "var" CACHE PATH "modifiable single-machine data (var)") endif() if(NOT DEFINED CMAKE_INSTALL_LIBDIR) set(_LIBDIR_DEFAULT "lib") # Override this default 'lib' with 'lib64' iff: # - we are on Linux system but NOT cross-compiling # - we are NOT on debian # - we are on a 64 bits system # reason is: amd64 ABI: http://www.x86-64.org/documentation/abi.pdf # For Debian with multiarch, use 'lib/${CMAKE_LIBRARY_ARCHITECTURE}' if # CMAKE_LIBRARY_ARCHITECTURE is set (which contains e.g. "i386-linux-gnu" # See http://wiki.debian.org/Multiarch if((CMAKE_SYSTEM_NAME MATCHES "Linux|kFreeBSD" OR CMAKE_SYSTEM_NAME STREQUAL "GNU") AND NOT CMAKE_CROSSCOMPILING) if (EXISTS "/etc/debian_version") # is this a debian system ? if(CMAKE_LIBRARY_ARCHITECTURE) set(_LIBDIR_DEFAULT "lib/${CMAKE_LIBRARY_ARCHITECTURE}") endif() else() # not debian, rely on CMAKE_SIZEOF_VOID_P: if(NOT DEFINED CMAKE_SIZEOF_VOID_P) message(AUTHOR_WARNING "Unable to determine default CMAKE_INSTALL_LIBDIR directory because no target architecture is known. " "Please enable at least one language before including GNUInstallDirs.") else() if("${CMAKE_SIZEOF_VOID_P}" EQUAL "8") set(_LIBDIR_DEFAULT "lib64") endif() endif() endif() endif() set(CMAKE_INSTALL_LIBDIR "${_LIBDIR_DEFAULT}" CACHE PATH "object code libraries (${_LIBDIR_DEFAULT})") endif() if(NOT DEFINED CMAKE_INSTALL_INCLUDEDIR) set(CMAKE_INSTALL_INCLUDEDIR "include" CACHE PATH "C header files (include)") endif() if(NOT DEFINED CMAKE_INSTALL_OLDINCLUDEDIR) set(CMAKE_INSTALL_OLDINCLUDEDIR "/usr/include" CACHE PATH "C header files for non-gcc (/usr/include)") endif() if(NOT DEFINED CMAKE_INSTALL_DATAROOTDIR) set(CMAKE_INSTALL_DATAROOTDIR "share" CACHE PATH "read-only architecture-independent data root (share)") endif() #----------------------------------------------------------------------------- # Values whose defaults are relative to DATAROOTDIR. Store empty values in # the cache and store the defaults in local variables if the cache values are # not set explicitly. This auto-updates the defaults as DATAROOTDIR changes. if(NOT CMAKE_INSTALL_DATADIR) set(CMAKE_INSTALL_DATADIR "" CACHE PATH "read-only architecture-independent data (DATAROOTDIR)") set(CMAKE_INSTALL_DATADIR "${CMAKE_INSTALL_DATAROOTDIR}") endif() if(NOT CMAKE_INSTALL_INFODIR) set(CMAKE_INSTALL_INFODIR "" CACHE PATH "info documentation (DATAROOTDIR/info)") set(CMAKE_INSTALL_INFODIR "${CMAKE_INSTALL_DATAROOTDIR}/info") endif() if(NOT CMAKE_INSTALL_LOCALEDIR) set(CMAKE_INSTALL_LOCALEDIR "" CACHE PATH "locale-dependent data (DATAROOTDIR/locale)") set(CMAKE_INSTALL_LOCALEDIR "${CMAKE_INSTALL_DATAROOTDIR}/locale") endif() if(NOT CMAKE_INSTALL_MANDIR) set(CMAKE_INSTALL_MANDIR "" CACHE PATH "man documentation (DATAROOTDIR/man)") set(CMAKE_INSTALL_MANDIR "${CMAKE_INSTALL_DATAROOTDIR}/man") endif() if(NOT CMAKE_INSTALL_DOCDIR) set(CMAKE_INSTALL_DOCDIR "" CACHE PATH "documentation root (DATAROOTDIR/doc/PROJECT_NAME)") set(CMAKE_INSTALL_DOCDIR "${CMAKE_INSTALL_DATAROOTDIR}/doc/${PROJECT_NAME}") endif() #----------------------------------------------------------------------------- mark_as_advanced( CMAKE_INSTALL_BINDIR CMAKE_INSTALL_SBINDIR CMAKE_INSTALL_LIBEXECDIR CMAKE_INSTALL_SYSCONFDIR CMAKE_INSTALL_SHAREDSTATEDIR CMAKE_INSTALL_LOCALSTATEDIR CMAKE_INSTALL_LIBDIR CMAKE_INSTALL_INCLUDEDIR CMAKE_INSTALL_OLDINCLUDEDIR CMAKE_INSTALL_DATAROOTDIR CMAKE_INSTALL_DATADIR CMAKE_INSTALL_INFODIR CMAKE_INSTALL_LOCALEDIR CMAKE_INSTALL_MANDIR CMAKE_INSTALL_DOCDIR ) # Result directories # foreach(dir BINDIR SBINDIR LIBEXECDIR SYSCONFDIR SHAREDSTATEDIR LOCALSTATEDIR LIBDIR INCLUDEDIR OLDINCLUDEDIR DATAROOTDIR DATADIR INFODIR LOCALEDIR MANDIR DOCDIR ) if(NOT IS_ABSOLUTE ${CMAKE_INSTALL_${dir}}) set(CMAKE_INSTALL_FULL_${dir} "${CMAKE_INSTALL_PREFIX}/${CMAKE_INSTALL_${dir}}") else() set(CMAKE_INSTALL_FULL_${dir} "${CMAKE_INSTALL_${dir}}") endif() endforeach() trunk-2018.02b/cMake/GetVersion.cmake000066400000000000000000000035331324306050200173010ustar00rootroot00000000000000# Define version or set it from git IF (NOT YADE_VERSION) IF (EXISTS ${CMAKE_CURRENT_SOURCE_DIR}/RELEASE ) #Release file is found SET(READFILE cat) exec_program( ${READFILE} ${CMAKE_CURRENT_SOURCE_DIR} ARGS "RELEASE" OUTPUT_VARIABLE YADE_VERSION ) ELSEIF (EXISTS ${CMAKE_CURRENT_SOURCE_DIR}/.git/config) #Use git for version defining exec_program( "git" ${CMAKE_CURRENT_SOURCE_DIR} ARGS "log" ARGS "-n1" ARGS "--pretty=oneline" ARGS "|" ARGS "cut" ARGS "-c1-7" OUTPUT_VARIABLE VERSION_GIT ) exec_program( "git" ${CMAKE_CURRENT_SOURCE_DIR} ARGS "log" ARGS "-n1" ARGS "--pretty=fuller" ARGS "--date=iso" ARGS "|" ARGS "grep" ARGS "AuthorDate" ARGS "|" ARGS "cut" ARGS "-c13-22" OUTPUT_VARIABLE VERSION_DATE ) SET(YADE_VERSION "${VERSION_DATE}.git-${VERSION_GIT}") #git log -n1 --pretty=format:"%ai_%h" ELSEIF (EXISTS ${CMAKE_CURRENT_SOURCE_DIR}/.bzr/branch/last-revision) #Use bzr for version defining exec_program( "less" ${CMAKE_CURRENT_SOURCE_DIR}/.bzr/branch/ ARGS "last-revision" ARGS "|" ARGS "cut" ARGS "-c13-20" OUTPUT_VARIABLE VERSION_GIT ) exec_program( "bzr" ${CMAKE_CURRENT_SOURCE_DIR} ARGS "log" ARGS "-l" ARGS "1" ARGS "--gnu-changelog" ARGS "|" ARGS "head" ARGS "-n1" ARGS "|" ARGS "cut" ARGS "-c1-10" OUTPUT_VARIABLE VERSION_DATE ) SET(YADE_VERSION "${VERSION_DATE}.git-${VERSION_GIT}") ELSE (EXISTS ${CMAKE_CURRENT_SOURCE_DIR}/.git/config ) SET (YADE_VERSION "Unknown") ENDIF (EXISTS ${CMAKE_CURRENT_SOURCE_DIR}/RELEASE ) ENDIF (NOT YADE_VERSION) MESSAGE (STATUS "Version is set to " ${YADE_VERSION}) trunk-2018.02b/core/000077500000000000000000000000001324306050200141165ustar00rootroot00000000000000trunk-2018.02b/core/Body.cpp000066400000000000000000000027251324306050200155250ustar00rootroot00000000000000 #include #include #include #include //! This could be -1 if id_t is re-typedef'ed as `int' const Body::id_t Body::ID_NONE=Body::id_t(-1); const shared_ptr& Body::byId(Body::id_t _id, Scene* rb){return (*((rb?rb:Omega::instance().getScene().get())->bodies))[_id];} const shared_ptr& Body::byId(Body::id_t _id, shared_ptr rb){return (*(rb->bodies))[_id];} // return list of interactions of this particle boost::python::list Body::py_intrs(){ boost::python::list ret; for(Body::MapId2IntrT::iterator it=this->intrs.begin(),end=this->intrs.end(); it!=end; ++it) { //Iterate over all bodie's interactions if(!(*it).second->isReal()) continue; ret.append((*it).second); } return ret; } // return number of interactions of this particle const unsigned int Body::coordNumber() const { unsigned int intrSize = 0; for(auto it=this->intrs.begin(),end=this->intrs.end(); it!=end; ++it) { //Iterate over all bodie's interactions if(!(*it).second->isReal()) continue; intrSize++; } return intrSize; } bool Body::maskOk(int mask) const { return (mask==0 || ((groupMask & mask) != 0)); } bool Body::maskCompatible(int mask) const { return (groupMask & mask) != 0; } #ifdef YADE_MASK_ARBITRARY bool Body::maskOk(const mask_t& mask) const { return (mask==0 || ((groupMask & mask) != 0)); } bool Body::maskCompatible(const mask_t& mask) const { return (groupMask & mask) != 0; } #endif trunk-2018.02b/core/Body.hpp000066400000000000000000000160021324306050200155230ustar00rootroot00000000000000/************************************************************************* * Copyright (C) 2004 by Olivier Galizzi * * olivier.galizzi@imag.fr * * Copyright (C) 2004 by Janek Kozicki * * cosurgi@berlios.de * * * * This program is free software; it is licensed under the terms of the * * GNU General Public License v2 or later. See file LICENSE for details. * *************************************************************************/ #pragma once #include "Shape.hpp" #include "Bound.hpp" #include "State.hpp" #include "Material.hpp" #include #include #include class Scene; class Interaction; class Body: public Serializable{ public: // numerical types for storing ids using id_t = int ; // internal structure to hold some interaction of a body; used by InteractionContainer; using MapId2IntrT = std::map >; // groupMask type // bits for Body::flags enum { FLAG_BOUNDED=1, FLAG_ASPHERICAL=2 }; /* add powers of 2 as needed */ //! symbolic constant for body that doesn't exist. static const Body::id_t ID_NONE; //! get Body pointer given its id. static const shared_ptr& byId(Body::id_t _id,Scene* rb=NULL); static const shared_ptr& byId(Body::id_t _id,shared_ptr rb); //! Whether this Body is a Clump. //! @note The following is always true: \code (Body::isClump() XOR Body::isClumpMember() XOR Body::isStandalone()) \endcode bool isClump() const {return clumpId!=ID_NONE && id==clumpId;} //! Whether this Body is member of a Clump. bool isClumpMember() const {return clumpId!=ID_NONE && id!=clumpId;} //! Whether this body is standalone (neither Clump, nor member of a Clump) bool isStandalone() const {return clumpId==ID_NONE;} //! Whether this body has all DOFs blocked // inline accessors // logic: http://stackoverflow.com/questions/47981/how-do-you-set-clear-and-toggle-a-single-bit-in-c bool isDynamic() const { assert(state); return state->blockedDOFs!=State::DOF_ALL; } void setDynamic(bool d){ assert(state); if(d){ state->blockedDOFs=State::DOF_NONE; } else { state->blockedDOFs=State::DOF_ALL; state->vel=state->angVel=Vector3r::Zero(); } } bool isBounded() const {return flags & FLAG_BOUNDED; } void setBounded(bool d){ if(d) flags|=FLAG_BOUNDED; else flags&=~(FLAG_BOUNDED); } bool isAspherical() const {return flags & FLAG_ASPHERICAL; } void setAspherical(bool d){ if(d) flags|=FLAG_ASPHERICAL; else flags&=~(FLAG_ASPHERICAL); } /*! Hook for clump to update position of members when user-forced reposition and redraw (through GUI) occurs. * This is useful only in cases when engines that do that in every iteration are not active - i.e. when the simulation is paused. * (otherwise, GLViewer would depend on Clump and therefore Clump would have to go to core...) */ virtual void userForcedDisplacementRedrawHook(){return;} boost::python::list py_intrs(); Body::id_t getId() const {return id;}; const unsigned int coordNumber() const; // Number of neighboring particles const mask_t getGroupMask() const {return groupMask; }; bool maskOk(int mask) const; bool maskCompatible(int mask) const; #ifdef YADE_MASK_ARBITRARY bool maskOk(const mask_t& mask) const; bool maskCompatible(const mask_t& mask) const; #endif // only BodyContainer can set the id of a body friend class BodyContainer; YADE_CLASS_BASE_DOC_ATTRS_CTOR_PY(Body,Serializable,"A particle, basic element of simulation; interacts with other bodies.", ((Body::id_t,id,Body::ID_NONE,Attr::readonly,"Unique id of this body.")) ((mask_t,groupMask,1,,"Bitmask for determining interactions.")) ((int,flags,FLAG_BOUNDED,Attr::readonly,"Bits of various body-related flags. *Do not access directly*. In c++, use isDynamic/setDynamic, isBounded/setBounded, isAspherical/setAspherical. In python, use :yref:`Body.dynamic`, :yref:`Body.bounded`, :yref:`Body.aspherical`.")) ((shared_ptr,material,,,":yref:`Material` instance associated with this body.")) ((shared_ptr,state,new State,,"Physical :yref:`state`.")) ((shared_ptr,shape,,,"Geometrical :yref:`Shape`.")) ((shared_ptr,bound,,,":yref:`Bound`, approximating volume for the purposes of collision detection.")) ((MapId2IntrT,intrs,,Attr::hidden,"Map from otherId to Interaction with otherId, managed by InteractionContainer.")) ((int,clumpId,Body::ID_NONE,Attr::readonly,"Id of clump this body makes part of; invalid number if not part of clump; see :yref:`Body::isStandalone`, :yref:`Body::isClump`, :yref:`Body::isClumpMember` properties. \n\nNot meant to be modified directly from Python, use :yref:`O.bodies.appendClumped` instead.")) ((long,chain,-1,,"Id of chain to which the body belongs.")) ((long,iterBorn,-1,,"Step number at which the body was added to simulation.")) ((Real,timeBorn,-1,,"Time at which the body was added to simulation.")) , /* ctor */, /* py */ // .def_readwrite("mat",&Body::material,"Shorthand for :yref:`Body::material`") .add_property("dynamic",&Body::isDynamic,&Body::setDynamic,"Whether this body will be moved by forces. (In c++, use ``Body::isDynamic``/``Body::setDynamic``) :ydefault:`true`") .add_property("bounded",&Body::isBounded,&Body::setBounded,"Whether this body should have :yref:`Body.bound` created. Note that bodies without a :yref:`bound ` do not participate in collision detection. (In c++, use ``Body::isBounded``/``Body::setBounded``) :ydefault:`true`") .add_property("aspherical",&Body::isAspherical,&Body::setAspherical,"Whether this body has different inertia along principal axes; :yref:`NewtonIntegrator` makes use of this flag to call rotation integration routine for aspherical bodies, which is more expensive. :ydefault:`false`") .add_property("mask",boost::python::make_getter(&Body::groupMask,boost::python::return_value_policy()),boost::python::make_setter(&Body::groupMask,boost::python::return_value_policy()),"Shorthand for :yref:`Body::groupMask`") .add_property("isStandalone",&Body::isStandalone,"True if this body is neither clump, nor clump member; false otherwise.") .add_property("isClumpMember",&Body::isClumpMember,"True if this body is clump member, false otherwise.") .add_property("isClump",&Body::isClump,"True if this body is clump itself, false otherwise.") .add_property("iterBorn",&Body::iterBorn,"Returns step number at which the body was added to simulation.") .add_property("timeBorn",&Body::timeBorn,"Returns time at which the body was added to simulation.") .def_readwrite("chain",&Body::chain,"Returns Id of chain to which the body belongs.") .def("intrs",&Body::py_intrs,"Return all interactions in which this body participates.") ); }; REGISTER_SERIALIZABLE(Body); trunk-2018.02b/core/BodyContainer.cpp000066400000000000000000000040731324306050200173660ustar00rootroot00000000000000// 2010 © Václav Šmilauer #include "Scene.hpp" #include "Body.hpp" #include "BodyContainer.hpp" #include "Clump.hpp" #ifdef YADE_OPENMP #include #endif CREATE_LOGGER(BodyContainer); void BodyContainer::clear(){ body.clear(); } Body::id_t BodyContainer::insert(shared_ptr b){ const shared_ptr& scene=Omega::instance().getScene(); b->iterBorn=scene->iter; b->timeBorn=scene->time; b->id=body.size(); scene->doSort = true; body.push_back(b); // Notify ForceContainer about new id scene->forces.addMaxId(b->id); return b->id; } bool BodyContainer::erase(Body::id_t id, bool eraseClumpMembers){//default is false (as before) if(!body[id]) return false; const shared_ptr& b=Body::byId(id); if ((b) and (b->isClumpMember())) { const shared_ptr clumpBody=Body::byId(b->clumpId); const shared_ptr clump=YADE_PTR_CAST(clumpBody->shape); Clump::del(clumpBody, b); if (clump->members.size()==0) this->erase(clumpBody->id,false); //Clump has no members any more. Remove it } if ((b) and (b->isClump())){ //erase all members if eraseClumpMembers is true: const shared_ptr& clump=YADE_PTR_CAST(b->shape); std::map& members = clump->members; std::vector idsToRemove; for(const auto mm : members) idsToRemove.push_back(mm.first); // Prepare an array of ids, which need to be removed. for(Body::id_t memberId : idsToRemove){ if (eraseClumpMembers) { this->erase(memberId,false); // erase members } else { //when the last members is erased, the clump will be erased automatically, see above Body::byId(memberId)->clumpId=Body::ID_NONE; // make members standalones } } body[id].reset(); return true; } const shared_ptr& scene=Omega::instance().getScene(); for(auto it=b->intrs.begin(), end=b->intrs.end(); it!=end; ++it) { //Iterate over all body's interactions scene->interactions->requestErase((*it).second); } b->id=-1;//else it sits in the python scope without a chance to be inserted again body[id].reset(); return true; } trunk-2018.02b/core/BodyContainer.hpp000066400000000000000000000061171324306050200173740ustar00rootroot00000000000000// 2004 © Olivier Galizzi // 2004 © Janek Kozicki // 2010 © Václav Šmilauer #pragma once #include #include class Body; class InteractionContainer; #if YADE_OPENMP #define YADE_PARALLEL_FOREACH_BODY_BEGIN(b_,bodies) const Body::id_t _sz(bodies->size()); _Pragma("omp parallel for") for(Body::id_t _id=0; _id<_sz; _id++){ if(!(*bodies)[_id]) continue; b_((*bodies)[_id]); #define YADE_PARALLEL_FOREACH_BODY_END() } #else #define YADE_PARALLEL_FOREACH_BODY_BEGIN(b,bodies) FOREACH(b,*(bodies)){ #define YADE_PARALLEL_FOREACH_BODY_END() } #endif /* Container of bodies implemented as flat std::vector. It handles body removal and intelligently reallocates free ids for newly added ones. The nested iterators and the specialized FOREACH_BODY macros above will silently skip null body pointers which may exist after removal. The null pointers can still be accessed via the [] operator. Any alternative implementation should use the same API. */ class BodyContainer: public Serializable{ private: using ContainerT = std::vector > ; using MemberMap = std::map ; ContainerT body; public: friend class InteractionContainer; // accesses the body vector directly //An iterator that will automatically jump slots with null bodies class smart_iterator : public ContainerT::iterator { public: ContainerT::iterator end; smart_iterator& operator++() { ContainerT::iterator::operator++(); while (!(this->operator*()) && end!=(*this)) ContainerT::iterator::operator++(); return *this;} smart_iterator operator++(int) {smart_iterator temp(*this); operator++(); return temp;} smart_iterator& operator=(const ContainerT::iterator& rhs) {ContainerT::iterator::operator=(rhs); return *this;} smart_iterator& operator=(const smart_iterator& rhs) {ContainerT::iterator::operator=(rhs); end=rhs.end; return *this;} smart_iterator() {} smart_iterator(const ContainerT::iterator& source) {(*this)=source;} smart_iterator(const smart_iterator& source) {(*this)=source; end=source.end;} }; using iterator = smart_iterator ; using const_iterator = const smart_iterator ; BodyContainer() {}; virtual ~BodyContainer() {}; Body::id_t insert(shared_ptr); void clear(); iterator begin() { iterator temp(body.begin()); temp.end=body.end(); return (body.begin()==body.end() || *temp)?temp:++temp;} iterator end() { iterator temp(body.end()); temp.end=body.end(); return temp; } const size_t size() const { return body.size(); } shared_ptr& operator[](unsigned int id){ return body[id];} const shared_ptr& operator[](unsigned int id) const { return body[id]; } const bool exists(Body::id_t id) const { return ((id>=0) && ((size_t)id #include #include #include /*! Interface for approximate body locations in space Note: the min and max members refer to shear coordinates, in periodic and sheared space, not cartesian coordinates in the physical space. */ class Bound: public Serializable, public Indexable{ public: YADE_CLASS_BASE_DOC_ATTRS_CTOR_PY(Bound,Serializable,"Object bounding part of space taken by associated body; might be larger, used to optimalize collision detection", ((int,lastUpdateIter,0,Attr::readonly,"record iteration of last reference position update |yupdate|")) ((Vector3r,refPos,Vector3r(NaN,NaN,NaN),Attr::readonly,"Reference position, updated at current body position each time the bound dispatcher update bounds |yupdate|")) // ((bool,isBounding,false,Attr::readonly,"A flag used to tell when the body moves out of bounds - only used if oriVerlet striding is active :yref:`BoundDispatcher::updatingDispFactor`>0 |yupdate|")) ((Real,sweepLength,0, Attr::readonly,"The length used to increase the bounding boxe size, can be adjusted on the basis of previous displacement if :yref:`BoundDispatcher::targetInterv`>0. |yupdate|")) ((Vector3r,color,Vector3r(1,1,1),,"Color for rendering this object")) ((Vector3r,min,Vector3r(NaN,NaN,NaN),(Attr::noSave | Attr::readonly),"Lower corner of box containing this bound (and the :yref:`Body` as well)")) ((Vector3r,max,Vector3r(NaN,NaN,NaN),(Attr::noSave | Attr::readonly),"Upper corner of box containing this bound (and the :yref:`Body` as well)")) , /* ctor*/, /*py*/ YADE_PY_TOPINDEXABLE(Bound) ); REGISTER_INDEX_COUNTER(Bound); }; REGISTER_SERIALIZABLE(Bound); trunk-2018.02b/core/Cell.cpp000066400000000000000000000032171324306050200155040ustar00rootroot00000000000000 #include CREATE_LOGGER(Cell); void Cell::integrateAndUpdate(Real dt){ //incremental displacement gradient _trsfInc=dt*velGrad; // total transformation; M = (Id+G).M = F.M trsf+=_trsfInc*trsf; _invTrsf=trsf.inverse(); // hSize contains colums with updated base vectors prevHSize=hSize; _vGradTimesPrevH = velGrad*prevHSize; hSize+=_trsfInc*hSize; if(hSize.determinant()==0){ throw runtime_error("Cell is degenerate (zero volume)."); } // lengths of transformed cell vectors, skew cosines Matrix3r Hnorm; // normalized transformed base vectors for(int i=0; i<3; i++){ Vector3r base(hSize.col(i)); _size[i]=base.norm(); base/=_size[i]; //base is normalized now Hnorm(0,i)=base[0]; Hnorm(1,i)=base[1]; Hnorm(2,i)=base[2];}; // skew cosines for(int i=0; i<3; i++){ int i1=(i+1)%3, i2=(i+2)%3; // sin between axes is cos of skew _cos[i]=(Hnorm.col(i1).cross(Hnorm.col(i2))).squaredNorm(); } // pure shear trsf: ones on diagonal _shearTrsf=Hnorm; // pure unshear transformation _unshearTrsf=_shearTrsf.inverse(); // some parts can branch depending on presence/absence of shear _hasShear=(hSize(0,1)!=0 || hSize(0,2)!=0 || hSize(1,0)!=0 || hSize(1,2)!=0 || hSize(2,0)!=0 || hSize(2,1)!=0); // OpenGL shear matrix (used frequently) fillGlShearTrsfMatrix(_glShearTrsfMatrix); } void Cell::fillGlShearTrsfMatrix(double m[16]){ m[0]=_shearTrsf(0,0); m[4]=_shearTrsf(0,1); m[8]=_shearTrsf(0,2); m[12]=0; m[1]=_shearTrsf(1,0); m[5]=_shearTrsf(1,1); m[9]=_shearTrsf(1,2); m[13]=0; m[2]=_shearTrsf(2,0); m[6]=_shearTrsf(2,1); m[10]=_shearTrsf(2,2);m[14]=0; m[3]=0; m[7]=0; m[11]=0; m[15]=1; } trunk-2018.02b/core/Cell.hpp000066400000000000000000000407221324306050200155130ustar00rootroot00000000000000// 2009 © Václav Šmilauer /*! Periodic cell parameters and routines. Usually instantiated as Scene::cell. The Cell has current box configuration represented by the parallelepiped's base vectors (*hSize*). Lengths of the base vectors can be accessed via *size*. * Matrix3r *trsf* is "deformation gradient tensor" F (http://en.wikipedia.org/wiki/Finite_strain_theory) * Matrix3r *velGrad* is "velocity gradient tensor" (http://www.cs.otago.ac.nz/postgrads/alexis/FluidMech/node7.html) The transformation is split between "normal" part and "rotation/shear" part for contact detection algorithms. The shearPt, unshearPt, getShearTrsf etc functions refer to both shear and rotation. This decomposition is frame-dependant and does not correspond to the rotation/stretch decomposition of mechanics (with stretch further decomposed into isotropic and deviatoric). Therefore, using the shearPt family in equations of mechanics is not recommended. Similarly, attributes assuming the existence of a "reference" state are considered deprecated (refSize, hSize0). It is better to not use them since there is no guarantee that the so-called "reference state" will be maintained consistently in the future. */ #pragma once #include #include //#include class Cell: public Serializable{ public: //! Get/set sizes of cell base vectors const Vector3r& getSize() const { return _size; } void setSize(const Vector3r& s){for (int k=0;k<3;k++) hSize.col(k)*=s[k]/hSize.col(k).norm(); refHSize=hSize; postLoad(*this);} //! Return copy of the current size (used only by the python wrapper) Vector3r getSize_copy() const { return _size; } //! return vector of consines of skew angle in yz, xz, xy planes between respective transformed base vectors const Vector3r& getCos() const {return _cos;} //! transformation matrix applying pure shear&rotation (scaling removed) const Matrix3r& getShearTrsf() const { return _shearTrsf; } //! inverse of getShearTrsfMatrix(). const Matrix3r& getUnshearTrsf() const {return _unshearTrsf;} //! transformation increment matrix applying arbitrary field (remove if not used in NewtonIntegrator! ) // const Matrix3r& getTrsfInc() const { return _trsfInc; } /*! return pointer to column-major OpenGL 4x4 matrix applying pure shear. This matrix is suitable as argument for glMultMatrixd. Note: the order of OpenGL transoformations matters; for instance, if you draw sheared wire box of size *size*, centered at *center*, the order is: 1. translation: glTranslatev(center); 3. shearing: glMultMatrixd(scene->cell->getGlShearTrsfMatrix()); 2. scaling: glScalev(size); 4. draw: glutWireCube(1); See also http://www.songho.ca/opengl/gl_transform.html#matrix . */ const double* getGlShearTrsfMatrix() const { return _glShearTrsfMatrix; } //! Whether any shear (non-diagonal) component of the strain matrix is nonzero. bool hasShear() const {return _hasShear; } // caches; private private: Matrix3r _invTrsf; Matrix3r _trsfInc; Matrix3r _vGradTimesPrevH; Vector3r _size, _cos; Vector3r _refSize; bool _hasShear; Matrix3r _shearTrsf, _unshearTrsf; double _glShearTrsfMatrix[16]; void fillGlShearTrsfMatrix(double m[16]); public: DECLARE_LOGGER; //! "integrate" velGrad, update cached values used by public getter. void integrateAndUpdate(Real dt); /*! Return point inside periodic cell, even if shear is applied */ Vector3r wrapShearedPt(const Vector3r& pt) const { return shearPt(wrapPt(unshearPt(pt))); } /*! Return point inside periodic cell, even if shear is applied; store cell coordinates in period. */ Vector3r wrapShearedPt(const Vector3r& pt, Vector3i& period) const { return shearPt(wrapPt(unshearPt(pt),period)); } /*! Apply inverse shear on point; to put it inside (unsheared) periodic cell, apply wrapPt on the returned value. */ Vector3r unshearPt(const Vector3r& pt) const { return _unshearTrsf*pt; } //! Apply shear on point. Vector3r shearPt(const Vector3r& pt) const { return _shearTrsf*pt; } /*! Wrap point to inside the periodic cell; don't compute number of periods wrapped */ Vector3r wrapPt(const Vector3r& pt) const { Vector3r ret; for(int i=0;i<3;i++) ret[i]=wrapNum(pt[i],_size[i]); return ret;} /*! Wrap point to inside the periodic cell; period will contain by how many cells it was wrapped. */ Vector3r wrapPt(const Vector3r& pt, Vector3i& period) const { Vector3r ret; for(int i=0; i<3; i++){ ret[i]=wrapNum(pt[i],_size[i],period[i]); } return ret;} /*! Wrap number to interval 0…sz */ static Real wrapNum(const Real& x, const Real& sz) { Real norm=x/sz; return (norm-floor(norm))*sz;} /*! Wrap number to interval 0…sz; store how many intervals were wrapped in period */ static Real wrapNum(const Real& x, const Real& sz, int& period) { Real norm=x/sz; period=(int)floor(norm); return (norm-period)*sz;} // relative position and velocity for interaction accross multiple cells Vector3r intrShiftPos(const Vector3i& cellDist) const { return hSize*cellDist.cast(); } Vector3r intrShiftVel(const Vector3i& cellDist) const { return _vGradTimesPrevH*cellDist.cast(); } // return body velocity while taking away mean field velocity (coming from velGrad) if the mean field velocity is applied on velocity Vector3r bodyFluctuationVel(const Vector3r& pos, const Vector3r& vel, const Matrix3r& prevVelGrad) const { return (vel-prevVelGrad*pos); } // get/set current shape; setting resets trsf to identity Matrix3r getHSize() const { return hSize; } void setHSize(const Matrix3r& m){ hSize=refHSize=m; postLoad(*this); } // set current transformation; has no influence on current configuration (hSize); sets display refHSize as side-effect Matrix3r getTrsf() const { return trsf; } void setTrsf(const Matrix3r& m){ trsf=m; postLoad(*this); } Matrix3r getVelGrad() const { return velGrad; } void setVelGrad(const Matrix3r& m){ nextVelGrad=m; velGradChanged=true;} //BEGIN Deprecated (see refSize property) // get undeformed shape Matrix3r getHSize0() const { return _invTrsf*hSize; } // edge lengths of the undeformed shape Vector3r getRefSize() const { Matrix3r h=getHSize0(); return Vector3r(h.col(0).norm(),h.col(1).norm(),h.col(2).norm()); } // temporary, will be removed in favor of more descriptive setBox(...) void setRefSize(const Vector3r& s){ // if refSize is set to the current size and the cell is a box (used in older scripts), say it is not necessary Matrix3r hSizeEigen3=hSize.diagonal().asDiagonal(); //Eigen3 support if(s==_size && hSize==hSizeEigen3){ LOG_WARN("Setting O.cell.refSize=O.cell.size is useless, O.trsf=Matrix3.Identity is enough now."); } else {LOG_WARN("Setting Cell.refSize is deprecated, use Cell.setBox(...) instead.");} setBox(s); postLoad(*this); } //END Deprecated // set box shape of the cell void setBox(const Vector3r& size){ setHSize(size.asDiagonal()); trsf=Matrix3r::Identity(); postLoad(*this); } void setBox3(const Real& s0, const Real& s1, const Real& s2){ setBox(Vector3r(s0,s1,s2)); } // return current cell volume Real getVolume() const {return hSize.determinant();} void postLoad(Cell&){ integrateAndUpdate(0); } // to resolve overloads Vector3r wrapShearedPt_py(const Vector3r& pt) const { return wrapShearedPt(pt);} Vector3r wrapPt_py(const Vector3r& pt) const { return wrapPt(pt);} // strain measures Matrix3r getDefGrad() { return trsf; } Matrix3r getSmallStrain() { return .5*(trsf+trsf.transpose()) - Matrix3r::Identity(); } Matrix3r getRCauchyGreenDef() { return trsf.transpose()*trsf; } Matrix3r getLCauchyGreenDef() { return trsf*trsf.transpose(); } Matrix3r getLagrangianStrain() { return .5*(getRCauchyGreenDef()-Matrix3r::Identity()); } Matrix3r getEulerianAlmansiStrain() { return .5*(Matrix3r::Identity()-getLCauchyGreenDef().inverse()); } void computePolarDecOfDefGrad(Matrix3r& R, Matrix3r& U) { Matrix_computeUnitaryPositive(trsf,&R,&U); } boost::python::tuple getPolarDecOfDefGrad(){ Matrix3r R,U; computePolarDecOfDefGrad(R,U); return boost::python::make_tuple(R,U); } Matrix3r getRotation() { Matrix3r R,U; computePolarDecOfDefGrad(R,U); return R; } Matrix3r getLeftStretch() { Matrix3r R,U; computePolarDecOfDefGrad(R,U); return U; } Matrix3r getRightStretch() { Matrix3r R,U; computePolarDecOfDefGrad(R,U); return trsf*R.transpose(); } Vector3r getSpin() {Matrix3r R=.5*(velGrad-velGrad.transpose()); return Vector3r(-R(1,2),R(0,2),-R(0,1));} // stress measures //Matrix3r getStress() { return Shop::getStress(); } //Matrix3r getCauchyStress() { Matrix3r s=getStress(); return .5*(s+s.transpose()); } enum { HOMO_NONE=0, HOMO_POS=1, HOMO_VEL=2, HOMO_VEL_2ND=3 }; YADE_CLASS_BASE_DOC_ATTRS_CTOR_PY(Cell,Serializable,"Parameters of periodic boundary conditions. Only applies if O.isPeriodic==True.", /* overridden below to be modified by getters/setters because of intended side-effects */ ((Matrix3r,trsf,Matrix3r::Identity(),,"[overridden]")) //"Current transformation matrix of the cell, which defines how far is the current cell geometry (:yref:`hSize`) from the reference configuration. Changing trsf will not change :yref:`hSize`, it serves only as accumulator for transformations applied via :yref:`velGrad`.")) ((Matrix3r,refHSize,Matrix3r::Identity(),,"Reference cell configuration, only used with :yref:`OpenGLRenderer.dispScale`. Updated automatically when :yref:`hSize` or :yref:`trsf` is assigned directly; also modified by :yref:`yade.utils.setRefSe3` (called e.g. by the ``Reference`` button in the UI).")) ((Matrix3r,hSize,Matrix3r::Identity(),,"[overridden below]")) ((Matrix3r,prevHSize,Matrix3r::Identity(),Attr::readonly,":yref:`hSize` from the previous step, used in the definition of relative velocity across periods.")) /* normal attributes */ ((Matrix3r,velGrad,Matrix3r::Zero(),,"[overridden below]")) ((Matrix3r,nextVelGrad,Matrix3r::Zero(),Attr::readonly,"see :yref:`Cell.velGrad`.")) ((Matrix3r,prevVelGrad,Matrix3r::Zero(),Attr::readonly,"Velocity gradient in the previous step.")) ((int,homoDeform,2,,"If >0, deform (:yref:`velGrad`) the cell homothetically by adjusting positions and velocities of bodies. The velocity change is obtained by deriving the expression v=∇v.x, where ∇v is the macroscopic velocity gradient, giving in an incremental form: Δv=Δ ∇v x + ∇v Δx. As a result, velocities are modified as soon as ``velGrad`` changes, according to the first term: Δv(t)=Δ ∇v x(t), while the 2nd term reflects a convective term: Δv'= ∇v v(t-dt/2). The second term is neglected if homoDeform=1. All terms are included if homoDeform=2 (default)")) ((bool,velGradChanged,false,Attr::readonly,"true when velGrad has been changed manually (see also :yref:`Cell.nextVelGrad`)")), /*ctor*/ _invTrsf=Matrix3r::Identity(); integrateAndUpdate(0), /*py*/ // override some attributes above .add_property("hSize",&Cell::getHSize,&Cell::setHSize,"Base cell vectors (columns of the matrix), updated at every step from :yref:`velGrad` (:yref:`trsf` accumulates applied :yref:`velGrad` transformations). Setting *hSize* during a simulation is not supported by most contact laws, it is only meant to be used at iteration 0 before any interactions have been created.") .add_property("size",&Cell::getSize_copy,&Cell::setSize,"Current size of the cell, i.e. lengths of the 3 cell lateral vectors contained in :yref:`Cell.hSize` columns. Updated automatically at every step. Assigning a value will change the lengths of base vectors (see :yref:`Cell.hSize`), keeping their orientations unchanged.") .add_property("refSize",&Cell::getRefSize,&Cell::setRefSize,"Reference size of the cell (lengths of initial cell vectors, i.e. column norms of :yref:`hSize`).\n\n.. note::\n\t Modifying this value is deprecated, use :yref:`setBox` instead.") // useful properties .add_property("trsf",&Cell::getTrsf,&Cell::setTrsf,"Current transformation matrix of the cell, obtained from time integration of :yref:`Cell.velGrad`.") .add_property("velGrad",&Cell::getVelGrad,&Cell::setVelGrad,"Velocity gradient of the transformation; used in :yref:`NewtonIntegrator`. Values of :yref:`velGrad` accumulate in :yref:`trsf` at every step.\n\n NOTE: changing velGrad at the beginning of a simulation loop would lead to inacurate integration for one step, as it should normaly be changed after the contact laws (but before Newton). To avoid this problem, assignment is deferred automatically. The target value typed in terminal is actually stored in :yref:`Cell.nextVelGrad` and will be applied right in time by Newton integrator. \n\n.. note::\n\t Assigning individual components of velGrad is not possible (it will not return any error but it will have no effect). Instead, you can assign to :yref:`Cell.nextVelGrad`, as in O.cell.nextVelGrad[1,2]=1.") .def_readonly("size",&Cell::getSize_copy,"Current size of the cell, i.e. lengths of the 3 cell lateral vectors contained in :yref:`Cell.hSize` columns. Updated automatically at every step.") .add_property("volume",&Cell::getVolume,"Current volume of the cell.") // functions .def("setBox",&Cell::setBox,"Set :yref:`Cell` shape to be rectangular, with dimensions along axes specified by given argument. Shorthand for assigning diagonal matrix with respective entries to :yref:`hSize`.") .def("setBox",&Cell::setBox3,"Set :yref:`Cell` shape to be rectangular, with dimensions along $x$, $y$, $z$ specified by arguments. Shorthand for assigning diagonal matrix with the respective entries to :yref:`hSize`.") // debugging only .def("wrap",&Cell::wrapShearedPt_py,"Transform an arbitrary point into a point in the reference cell") .def("unshearPt",&Cell::unshearPt,"Apply inverse shear on the point (removes skew+rot of the cell)") .def("shearPt",&Cell::shearPt,"Apply shear (cell skew+rot) on the point") .def("wrapPt",&Cell::wrapPt_py,"Wrap point inside the reference cell, assuming the cell has no skew+rot.") .def("getDefGrad",&Cell::getDefGrad,"Returns deformation gradient tensor $\\mat{F}$ of the cell deformation (http://en.wikipedia.org/wiki/Finite_strain_theory)") .def("getSmallStrain",&Cell::getSmallStrain,"Returns small strain tensor $\\mat{\\varepsilon}=\\frac{1}{2}(\\mat{F}+\\mat{F}^T)-\\mat{I}$ of the cell (http://en.wikipedia.org/wiki/Finite_strain_theory)") .def("getRCauchyGreenDef",&Cell::getRCauchyGreenDef,"Returns right Cauchy-Green deformation tensor $\\mat{C}=\\mat{F}^T\\mat{F}$ of the cell (http://en.wikipedia.org/wiki/Finite_strain_theory)") .def("getLCauchyGreenDef",&Cell::getLCauchyGreenDef,"Returns left Cauchy-Green deformation tensor $\\mat{b}=\\mat{F}\\mat{F}^T$ of the cell (http://en.wikipedia.org/wiki/Finite_strain_theory)") .def("getLagrangianStrain",&Cell::getLagrangianStrain,"Returns Lagrangian strain tensor $\\mat{E}=\\frac{1}{2}(\\mat{C}-\\mat{I})=\\frac{1}{2}(\\mat{F}^T\\mat{F}-\\mat{I})=\\frac{1}{2}(\\mat{U}^2-\\mat{I})$ of the cell (http://en.wikipedia.org/wiki/Finite_strain_theory)") .def("getEulerianAlmansiStrain",&Cell::getEulerianAlmansiStrain,"Returns Eulerian-Almansi strain tensor $\\mat{e}=\\frac{1}{2}(\\mat{I}-\\mat{b}^{-1})=\\frac{1}{2}(\\mat{I}-(\\mat{F}\\mat{F}^T)^{-1})$ of the cell (http://en.wikipedia.org/wiki/Finite_strain_theory)") .def("getPolarDecOfDefGrad",&Cell::getPolarDecOfDefGrad,"Returns orthogonal matrix $\\mat{R}$ and symmetric positive semi-definite matrix $\\mat{U}$ as polar decomposition of deformation gradient $\\mat{F}$ of the cell ( $\\mat{F}=\\mat{RU}$ )") .def("getRotation",&Cell::getRotation,"Returns rotation of the cell (orthogonal matrix $\\mat{R}$ from polar decomposition $\\mat{F}=\\mat{RU}$ )") .def("getLeftStretch",&Cell::getLeftStretch,"Returns left (spatial) stretch tensor of the cell (matrix $\\mat{U}$ from polar decomposition $\\mat{F}=\\mat{RU}$ )") .def("getRightStretch",&Cell::getRightStretch,"Returns right (material) stretch tensor of the cell (matrix $\\mat{V}$ from polar decomposition $\\mat{F}=\\mat{RU}=\\mat{VR}\\ \\rightarrow\\ \\mat{V}=\\mat{FR}^T$ )") .def("getSpin",&Cell::getSpin,"Returns the spin defined by the skew symmetric part of :yref:`velGrad`") .def_readonly("shearTrsf",&Cell::_shearTrsf,"Current skew+rot transformation (no resize)") .def_readonly("unshearTrsf",&Cell::_unshearTrsf,"Inverse of the current skew+rot transformation (no resize)") .add_property("hSize0",&Cell::getHSize0,"Value of untransformed hSize, with respect to current :yref:`trsf` (computed as :yref:`trsf` ⁻¹ × :yref:`hSize`.") ); }; REGISTER_SERIALIZABLE(Cell); trunk-2018.02b/core/Clump.cpp000066400000000000000000000556061324306050200157160ustar00rootroot00000000000000// (c) 2007-2010 Vaclav Smilauer #include"Clump.hpp" #include #include #include #include YADE_PLUGIN((Clump)); CREATE_LOGGER(Clump); boost::python::dict Clump::members_get(){ boost::python::dict ret; FOREACH(MemberMap::value_type& b, members){ ret[b.first]=boost::python::make_tuple(b.second.position,b.second.orientation); } return ret; } void Clump::add(const shared_ptr& clumpBody, const shared_ptr& subBody){ Body::id_t subId=subBody->getId(); const shared_ptr clump=YADE_PTR_CAST(clumpBody->shape); if(clump->members.count(subId)!=0) throw std::invalid_argument(("Body #"+boost::lexical_cast(subId)+" is already part of this clump #"+boost::lexical_cast(clumpBody->id)).c_str()); if(subBody->isClumpMember()) throw std::invalid_argument(("Body #"+boost::lexical_cast(subId)+" is already a clump member of #"+boost::lexical_cast(subBody->clumpId)).c_str()); else if(subBody->isClump()){ const shared_ptr subClump=YADE_PTR_CAST(subBody->shape); FOREACH(const MemberMap::value_type& mm, subClump->members){ const Body::id_t& memberId=mm.first; Scene* scene(Omega::instance().getScene().get()); // get scene const shared_ptr& member=Body::byId(memberId,scene); assert(member->isClumpMember()); member->clumpId=clumpBody->id; clump->members[memberId]=Se3r();// meaningful values will be put in by Clump::updateProperties //LOG_DEBUG("Added body #"<id<<" to clump #"<id); } //LOG_DEBUG("Clump #"<id<<" will be erased.");// see addToClump() in yadeWrapper.cpp } else{ // subBody must be a standalone! clump->members[subId]=Se3r();// meaningful values will be put in by Clump::updateProperties subBody->clumpId=clumpBody->id; } clumpBody->clumpId=clumpBody->id; // just to make sure clumpBody->setBounded(false); // disallow collisions with the clump itself if(subBody->isStandalone()){LOG_DEBUG("Added body #"<id<<" to clump #"<id);} } void Clump::del(const shared_ptr& clumpBody, const shared_ptr& subBody){ // erase the subBody; removing body that is not part of the clump throws const shared_ptr clump=YADE_PTR_CAST(clumpBody->shape); if(clump->members.erase(subBody->id)!=1) throw std::invalid_argument(("Body #"+boost::lexical_cast(subBody->id)+" not part of clump #"+boost::lexical_cast(clumpBody->id)+"; not removing.").c_str()); subBody->clumpId=Body::ID_NONE; LOG_DEBUG("Removed body #"<id<<" from clump #"<id); } void Clump::addForceTorqueFromMembers(const State* clumpState, Scene* scene, Vector3r& F, Vector3r& T){ FOREACH(const MemberMap::value_type& mm, members){ const Body::id_t& memberId=mm.first; const shared_ptr& member=Body::byId(memberId,scene); assert(member->isClumpMember()); State* memberState=member->state.get(); const Vector3r& f=scene->forces.getForce(memberId); const Vector3r& t=scene->forces.getTorque(memberId); F+=f; T+=t+(memberState->pos-clumpState->pos).cross(f); } } /*! Clump's se3 will be updated (origin at centroid and axes coincident with principal inertia axes) and subSe3 modified in such a way that members positions in world coordinates will not change. Note: velocities and angularVelocities of constituents are zeroed. OLD DOCS (will be cleaned up): -# Clump::members values and Clump::physicalParameters::se3 are invalid from this point -# M=0; S=vector3r(0,0,0); I=zero tensor; (ALL calculations are in world coordinates!) -# loop over Clump::members (position x_i, mass m_i, inertia at subBody's centroid I_i) [this loop will be replaced by numerical integration (rasterization) for the intersecting case; the rest will be the same] - M+=m_i - S+=m_i*x_i (local static moments are zero (centroid) - get inertia tensor of subBody in world coordinates, by rotating the principal (local) tensor against subBody->se3->orientation; then translate it to world origin (parallel axes theorem), then I+=I_i_world -# clumpPos=S/M -# translate aggregate's inertia tensor; parallel axes on I (R=clumpPos): I^c_jk=I'_jk-M*(delta_jk R.R - R_j*R_k) [http://en.wikipedia.org/wiki/Moments_of_inertia#Parallel_axes_theorem] -# eigen decomposition of I, get principal inertia and rotation matrix of the clump -# se3->orientation=quaternion(rotation_matrix); se3->position=clumpPos -# update subSe3s */ void Clump::updateProperties(const shared_ptr& clumpBody, unsigned int discretization){ LOG_DEBUG("Updating clump #"<id<<" parameters"); const shared_ptr state(clumpBody->state); const shared_ptr clump(YADE_PTR_CAST(clumpBody->shape)); if(clump->members.empty()){ throw std::runtime_error("Clump::updateProperties: clump has zero members."); } // trivial case if(clump->members.size()==1){ LOG_DEBUG("Clump of size one will be treated specially.") MemberMap::iterator I=clump->members.begin(); shared_ptr subBody=Body::byId(I->first); //const shared_ptr& subRBP(YADE_PTR_CAST(subBody->physicalParameters)); State* subState=subBody->state.get(); // se3 of the clump as whole is the same as the member's se3 state->pos=subState->pos; state->ori=subState->ori; // relative member's se3 is identity I->second.position=Vector3r::Zero(); I->second.orientation=Quaternionr::Identity(); state->inertia=subState->inertia; state->mass=subState->mass; state->vel=Vector3r::Zero(); state->angVel=Vector3r::Zero(); return; } //check for intersections: bool intersecting = false; shared_ptr sph (new Sphere); int Sph_Index = sph->getClassIndexStatic(); // get sphere index for checking if bodies are spheres if (discretization>0){ FOREACH(MemberMap::value_type& mm, clump->members){ const shared_ptr subBody1=Body::byId(mm.first); FOREACH(MemberMap::value_type& mm, clump->members){ const shared_ptr subBody2=Body::byId(mm.first); if ((subBody1->shape->getClassIndex() == Sph_Index) && (subBody2->shape->getClassIndex() == Sph_Index) && (subBody1!=subBody2)){//clump members should be spheres Vector3r dist = subBody1->state->pos - subBody2->state->pos; const Sphere* sphere1 = YADE_CAST (subBody1->shape.get()); const Sphere* sphere2 = YADE_CAST (subBody2->shape.get()); Real un = (sphere1->radius+sphere2->radius) - dist.norm(); if (un > 0.001*min(sphere1->radius,sphere2->radius)) {intersecting = true; break;} } } if (intersecting) break; } } /* quantities suffixed by g: global (world) coordinates s: local subBody's coordinates c: local clump coordinates */ Real M=0; // mass Real dens=0;//density Vector3r Sg(0,0,0); // static moment, for getting clump's centroid Matrix3r Ig(Matrix3r::Zero()), Ic(Matrix3r::Zero()); // tensors of inertia; is upper triangular, zeros instead of symmetric elements /** algorithm for estimation of volumes and inertia tensor from clumps using summation/integration scheme with regular grid spacing (some parts copied from woo: http://bazaar.launchpad.net/~eudoxos/woo/trunk/view/head:/pkg/dem/Clump.cpp) */ if(intersecting){ //get boundaries of clump: AlignedBox3r aabb; FOREACH(MemberMap::value_type& mm, clump->members){ const shared_ptr subBody = Body::byId(mm.first); if (subBody->shape->getClassIndex() == Sph_Index){//clump member should be a sphere const Sphere* sphere = YADE_CAST (subBody->shape.get()); aabb.extend(subBody->state->pos + Vector3r::Constant(sphere->radius)); aabb.extend(subBody->state->pos - Vector3r::Constant(sphere->radius)); } } Real rMin=min(aabb.diagonal()[0],min(aabb.diagonal()[1],aabb.diagonal()[2])); //get volume and inertia tensor using regular cubic cell array inside bounding box of the clump: Real dx = rMin/discretization; //edge length of cell Real dv = pow(dx,3); //volume of cell long nCells=(aabb.sizes()/dx).prod(); if(nCells>1e7) LOG_WARN("Clump::updateProperties: Cell array has "<members){ const shared_ptr subBody = Body::byId(mm.first); if (subBody->shape->getClassIndex() == Sph_Index){//clump member should be a sphere dens = subBody->material->density; const Sphere* sphere = YADE_CAST (subBody->shape.get()); if((x-subBody->state->pos).squaredNorm() < pow(sphere->radius,2)){ Real m = dens*dv; M += m; Sg += m*x; //inertia I = sum_i( mass_i*dist^2 + I_s) ) //steiners theorem Ig += m*( x.dot(x)*Matrix3r::Identity()-x*x.transpose())/*dist^2*/+Matrix3r(Vector3r::Constant(dv*pow(dx,2)/6.).asDiagonal())/*I_s/m = d^2: along princial axes of dv; perhaps negligible?*/; break; } } } } } } } else {//not intersecting FOREACH(MemberMap::value_type& mm, clump->members){ // mm.first is Body::id_t, mm.second is Se3r of that body const shared_ptr subBody=Body::byId(mm.first); dens = subBody->material->density; if (subBody->shape->getClassIndex() == Sph_Index){//clump member should be a sphere State* subState=subBody->state.get(); const Sphere* sphere = YADE_CAST (subBody->shape.get()); Real vol = (4./3.)*Mathr::PI*pow(sphere->radius,3.); Real m = dens*vol; M+=m; Sg+=m*subState->pos; Ig+=Clump::inertiaTensorTranslate(Vector3r::Constant((2/5.)*m*pow(sphere->radius,2)).asDiagonal(),m,-1.*subState->pos); } else { // non-spherical bodies State* subState = subBody->state.get(); const Real& m = subState->mass; const Vector3r& inertia = subState->inertia; const Vector3r& pos = subState->pos; const Quaternionr& ori = subState->ori; M += m; Sg += m*pos; Ig += inertiaTensorTranslate(inertiaTensorRotate(inertia.asDiagonal(),ori),m,-pos); } } } assert(M>0); LOG_TRACE("M=\n"<pos=Sg/M; // this will calculate translation only, since rotation is zero Matrix3r Ic_orientG=inertiaTensorTranslate(Ig, -M /* negative mass means towards centroid */, state->pos); // inertia at clump's centroid but with world orientation LOG_TRACE("Ic_orientG=\n"< decomposed(Ic_orientG); const Matrix3r& R_g2c(decomposed.eigenvectors()); // has NaNs for identity matrix?? LOG_TRACE("R_g2c=\n"<ori=Quaternionr(R_g2c); state->ori.normalize(); state->inertia=decomposed.eigenvalues(); state->mass=M; // TODO: these might be calculated from members... but complicated... - someone needs that?! state->vel=state->angVel=Vector3r::Zero(); clumpBody->setAspherical(state->inertia[0]!=state->inertia[1] || state->inertia[0]!=state->inertia[2]); // update subBodySe3s; subtract clump orientation (=apply its inverse first) to subBody's orientation FOREACH(MemberMap::value_type& I, clump->members){ shared_ptr subBody=Body::byId(I.first); State* subState=subBody->state.get(); I.second.orientation=state->ori.conjugate()*subState->ori; I.second.position=state->ori.conjugate()*(subState->pos-state->pos); } } void Clump::updatePropertiesNonSpherical(const shared_ptr& clumpBody, bool intersecting,shared_ptr rb){ //FIXME //LOG_DEBUG("Updating clump #"<0); const shared_ptr state(clumpBody->state); const shared_ptr clump(YADE_PTR_CAST(clumpBody->shape)); // trivial case if(clump->members.size()==1){ LOG_DEBUG("Clump of size one will be treated specially.") MemberMap::iterator I=clump->members.begin(); shared_ptr subBody=Body::byId(I->first,rb); //const shared_ptr& subRBP(YADE_PTR_CAST(subBody->physicalParameters)); State* subState=subBody->state.get(); // se3 of the clump as whole is the same as the member's se3 state->pos=subState->pos; state->ori=subState->ori; // relative member's se3 is identity I->second.position=Vector3r::Zero(); I->second.orientation=Quaternionr::Identity(); state->inertia=subState->inertia; state->mass=subState->mass; state->vel=Vector3r::Zero(); state->angVel=Vector3r::Zero(); return; } /* quantities suffixed by g: global (world) coordinates s: local subBody's coordinates c: local clump coordinates */ double M=0; // mass Vector3r Sg(0,0,0); // static moment, for getting clump's centroid Matrix3r Ig(Matrix3r::Zero()), Ic(Matrix3r::Zero()); // tensors of inertia; is upper triangular, zeros instead of symmetric elements if(intersecting){ LOG_WARN("Self-intersecting clumps not yet implemented, intersections will be ignored."); intersecting=false; } // begin non-intersecting loop here if(!intersecting){ FOREACH(MemberMap::value_type& I, clump->members){ // I.first is Body::id_t, I.second is Se3r of that body shared_ptr subBody=Body::byId(I.first,rb); State* subState=subBody->state.get(); M+=subState->mass; Sg+=subState->mass*subState->pos; // transform from local to global coords Quaternionr subState_ori_conjugate=subState->ori.conjugate(); Matrix3r Imatrix=Matrix3r::Zero(); Imatrix.diagonal()=subState->inertia; // TRWM3MAT(Imatrix); TRWM3QUAT(subRBP_orientation_conjugate); Ig+=Clump::inertiaTensorTranslate(Clump::inertiaTensorRotate(Imatrix,subState_ori_conjugate),subState->mass,-1.*subState->pos); //TRWM3MAT(Clump::inertiaTensorRotate(Matrix3r(subRBP->inertia),subRBP_orientation_conjugate)); } } //TRVAR1(M); TRWM3MAT(Ig); TRWM3VEC(Sg); assert(M>0); state->pos=Sg/M; // clump's centroid // this will calculate translation only, since rotation is zero Matrix3r Ic_orientG=Clump::inertiaTensorTranslate(Ig, -M /* negative mass means towards centroid */, state->pos); // inertia at clump's centroid but with world orientation //TRWM3MAT(Ic_orientG); Matrix3r R_g2c(Matrix3r::Zero()); //rotation matrix Ic_orientG(1,0)=Ic_orientG(0,1); Ic_orientG(2,0)=Ic_orientG(0,2); Ic_orientG(2,1)=Ic_orientG(1,2); // symmetrize //TRWM3MAT(Ic_orientG); matrixEigenDecomposition(Ic_orientG,R_g2c,Ic); /*! @bug eigendecomposition might be wrong. see http://article.gmane.org/gmane.science.physics.yade.devel/99 for message. It is worked around below, however. */ // has NaNs for identity matrix! //TRWM3MAT(R_g2c); // set quaternion from rotation matrix state->ori=Quaternionr(R_g2c); state->ori.normalize(); // now Ic is diagonal state->inertia=Ic.diagonal(); state->mass=M; // this block will be removed once EigenDecomposition works for diagonal matrices //#if 1 // if(isnan(R_g2c(0,0))||isnan(R_g2c(0,1))||isnan(R_g2c(0,2))||isnan(R_g2c(1,0))||isnan(R_g2c(1,1))||isnan(R_g2c(1,2))||isnan(R_g2c(2,0))||isnan(R_g2c(2,1))||isnan(R_g2c(2,2))){ // throw std::logic_error("Clump::updateProperties: NaNs in eigen-decomposition of inertia matrix?!"); // } //#endif //TRWM3VEC(state->inertia); // TODO: these might be calculated from members... but complicated... - someone needs that?! state->vel=state->angVel=Vector3r::Zero(); clumpBody->setAspherical(state->inertia[0]!=state->inertia[1] || state->inertia[0]!=state->inertia[2]); // update subBodySe3s; subtract clump orientation (=apply its inverse first) to subBody's orientation FOREACH(MemberMap::value_type& I, clump->members){ // now, I->first is Body::id_t, I->second is Se3r of that body shared_ptr subBody=Body::byId(I.first,rb); //const shared_ptr& subRBP(YADE_PTR_CAST(subBody->physicalParameters)); State* subState=subBody->state.get(); I.second.orientation=state->ori.conjugate()*subState->ori; I.second.position=state->ori.conjugate()*(subState->pos-state->pos); } } void Clump::updatePropertiesNonSpherical(const shared_ptr& clumpBody, bool intersecting){ //FIXME //LOG_DEBUG("Updating clump #"<0); const shared_ptr state(clumpBody->state); const shared_ptr clump(YADE_PTR_CAST(clumpBody->shape)); // trivial case if(clump->members.size()==1){ LOG_DEBUG("Clump of size one will be treated specially.") MemberMap::iterator I=clump->members.begin(); shared_ptr subBody=Body::byId(I->first); //const shared_ptr& subRBP(YADE_PTR_CAST(subBody->physicalParameters)); State* subState=subBody->state.get(); // se3 of the clump as whole is the same as the member's se3 state->pos=subState->pos; state->ori=subState->ori; // relative member's se3 is identity I->second.position=Vector3r::Zero(); I->second.orientation=Quaternionr::Identity(); state->inertia=subState->inertia; state->mass=subState->mass; state->vel=Vector3r::Zero(); state->angVel=Vector3r::Zero(); return; } /* quantities suffixed by g: global (world) coordinates s: local subBody's coordinates c: local clump coordinates */ double M=0; // mass Vector3r Sg(0,0,0); // static moment, for getting clump's centroid Matrix3r Ig(Matrix3r::Zero()), Ic(Matrix3r::Zero()); // tensors of inertia; is upper triangular, zeros instead of symmetric elements if(intersecting){ LOG_WARN("Self-intersecting clumps not yet implemented, intersections will be ignored."); intersecting=false; } // begin non-intersecting loop here if(!intersecting){ FOREACH(MemberMap::value_type& I, clump->members){ // I.first is Body::id_t, I.second is Se3r of that body shared_ptr subBody=Body::byId(I.first); State* subState=subBody->state.get(); M+=subState->mass; Sg+=subState->mass*subState->pos; // transform from local to global coords Quaternionr subState_ori_conjugate=subState->ori.conjugate(); Matrix3r Imatrix=Matrix3r::Zero(); Imatrix.diagonal()=subState->inertia; // TRWM3MAT(Imatrix); TRWM3QUAT(subRBP_orientation_conjugate); Ig+=Clump::inertiaTensorTranslate(Clump::inertiaTensorRotate(Imatrix,subState_ori_conjugate),subState->mass,-1.*subState->pos); //TRWM3MAT(Clump::inertiaTensorRotate(Matrix3r(subRBP->inertia),subRBP_orientation_conjugate)); } } //TRVAR1(M); TRWM3MAT(Ig); TRWM3VEC(Sg); assert(M>0); state->pos=Sg/M; // clump's centroid // this will calculate translation only, since rotation is zero Matrix3r Ic_orientG=Clump::inertiaTensorTranslate(Ig, -M /* negative mass means towards centroid */, state->pos); // inertia at clump's centroid but with world orientation //TRWM3MAT(Ic_orientG); Matrix3r R_g2c(Matrix3r::Zero()); //rotation matrix Ic_orientG(1,0)=Ic_orientG(0,1); Ic_orientG(2,0)=Ic_orientG(0,2); Ic_orientG(2,1)=Ic_orientG(1,2); // symmetrize //TRWM3MAT(Ic_orientG); matrixEigenDecomposition(Ic_orientG,R_g2c,Ic); /*! @bug eigendecomposition might be wrong. see http://article.gmane.org/gmane.science.physics.yade.devel/99 for message. It is worked around below, however. */ // has NaNs for identity matrix! //TRWM3MAT(R_g2c); // set quaternion from rotation matrix state->ori=Quaternionr(R_g2c); state->ori.normalize(); // now Ic is diagonal state->inertia=Ic.diagonal(); state->mass=M; // this block will be removed once EigenDecomposition works for diagonal matrices //#if 1 // if(isnan(R_g2c(0,0))||isnan(R_g2c(0,1))||isnan(R_g2c(0,2))||isnan(R_g2c(1,0))||isnan(R_g2c(1,1))||isnan(R_g2c(1,2))||isnan(R_g2c(2,0))||isnan(R_g2c(2,1))||isnan(R_g2c(2,2))){ // throw std::logic_error("Clump::updateProperties: NaNs in eigen-decomposition of inertia matrix?!"); // } //#endif //TRWM3VEC(state->inertia); // TODO: these might be calculated from members... but complicated... - someone needs that?! state->vel=state->angVel=Vector3r::Zero(); clumpBody->setAspherical(state->inertia[0]!=state->inertia[1] || state->inertia[0]!=state->inertia[2]); // update subBodySe3s; subtract clump orientation (=apply its inverse first) to subBody's orientation FOREACH(MemberMap::value_type& I, clump->members){ // now, I->first is Body::id_t, I->second is Se3r of that body shared_ptr subBody=Body::byId(I.first); //const shared_ptr& subRBP(YADE_PTR_CAST(subBody->physicalParameters)); State* subState=subBody->state.get(); I.second.orientation=state->ori.conjugate()*subState->ori; I.second.position=state->ori.conjugate()*(subState->pos-state->pos); } } void Clump::addNonSpherical(const shared_ptr& clumpBody, const shared_ptr& subBody){ //FIXME Body::id_t subId=subBody->getId(); if(subBody->clumpId!=Body::ID_NONE) throw std::invalid_argument(("Body #"+boost::lexical_cast(subId)+" is already in clump #"+boost::lexical_cast(subBody->clumpId)).c_str()); const shared_ptr clump=YADE_PTR_CAST(clumpBody->shape); if(clump->members.count(subId)!=0) throw std::invalid_argument(("Body #"+boost::lexical_cast(subId)+" is already part of this clump #"+boost::lexical_cast(clumpBody->id)).c_str()); clump->members[subId]=Se3r(); // meaningful values will be put in by Clump::updateProperties subBody->clumpId=clumpBody->id; clumpBody->clumpId=clumpBody->id; // just to make sure clumpBody->setBounded(false); // disallow collisions with the clump itself //LOG_DEBUG("Added body #"< #pragma once #include #include #include #include /*! Body representing clump (rigid aggregate) composed by other existing bodies. Clump is one of bodies that reside in scene->bodies. When an existing body is added to ::Clump, it's ::Body::dynamic flag is set to false (it is still subscribed to all its engines, to make it possible to remove it from the clump again). All forces acting on Clump::members are made to act on the clump itself, which will ensure that they influence all Clump::members as if the clump were a rigid particle. What are clump requirements so that they function? -# Given any body, tell - if it is a clump member: Body::isClumpMember() - if it is a clump: Body:: isClump(). (Correct result is assured at each call to Clump::add). (we could use RTTI instead? Would that be more reliable?) - if it is a standalone Body: Body::isStandalone() - what is it's clump id (Body::clumpId) -# given the root body, tell - what clumps it contains (enumerate all bodies and filter clumps, see above) -# given a clump, tell - what bodies it contains (keys of ::Clump::members) - what are se3 of these bodies (values of ::Clump::members) -# add/delete bodies from/to clump (::Clump::add, ::Clump::del) - This includes saving se3 of the subBody: it \em must be in clump's local coordinates so that it is constant. The transformation from global to local is given by clump's se3 at the moment of addition. Clump's se3 is initially (origin,identity) -# Update clump's physical properties (Clump::updateProperties) - This \em must reposition members so that they have the same se3 globally -# Apply forces acting on members to the clump instead (done in NewtonsForceLaw, NewtonsMomentumLaw) - uses world coordinates to calculate effect on the clump's centroid -# Integrate position and orientation of the clump - LeapFrogPositionIntegrator and LeapFrogOrientationIntegrator move clump as whole - clump members are skipped, since they have Body::dynamic==false. - ClumpMemberMover is an engine that updates positions of the clump memebers in each timestep (calls Clump::moveSubBodies internally) Some more information can be found http://beta.arcig.cz/~eudoxos/phd/index.cgi/YaDe/HighLevelClumps For an example how to generate a clump, see ClumpTestGen::createOneClump. @todo GravityEngine should be applied to members, not to clump as such?! Still not sure. Perhaps Clumps should have mass and inertia set to zeros so that engines unaware of clumps do not act on it. It would have some private mass and insertia that would be used in NewtonsForceLaw etc for clumps specially... @note Collider::mayCollide (should be used by all colliders) bypasses Clumps explicitly. This no longer depends on the absence of bound. @note Clump relies on its id being assigned (as well as id of its components); therefore, only bodies that have already been inserted to the container may be added to Clump which has been itself already added to the container. We further requier that clump id is greater than ids of clumped bodies */ class NewtonIntegrator; class Clump: public Shape { public: typedef std::map MemberMap; static void add(const shared_ptr& clump, const shared_ptr& subBody); static void del(const shared_ptr& clump, const shared_ptr& subBody); //! Recalculate physical properties of Clump. static void updateProperties(const shared_ptr& clump, unsigned int discretization); static void updatePropertiesNonSpherical(const shared_ptr& clump, bool intersecting,shared_ptr rb);//FIXME static void updatePropertiesNonSpherical(const shared_ptr& clump, bool intersecting);//FIXME //! Calculate positions and orientations of members based on relative Se3; newton pointer (if non-NULL) calls NewtonIntegrator::saveMaximaVelocity // done as template to avoid cross-dependency between clump and newton (not necessary if all plugins are linked together) template static void moveMembers(const shared_ptr& clumpBody, Scene* scene, IntegratorT* integrator=NULL){ const shared_ptr& clump=YADE_PTR_CAST(clumpBody->shape); const shared_ptr& clumpState=clumpBody->state; FOREACH(MemberMap::value_type& B, clump->members){ // B.first is Body::id_t, B.second is local Se3r of that body in the clump const shared_ptr& b = Body::byId(B.first,scene); const shared_ptr& subState=b->state; const Vector3r& subPos(B.second.position); const Quaternionr& subOri(B.second.orientation); // position update subState->pos=clumpState->pos+clumpState->ori*subPos; subState->ori=clumpState->ori*subOri; // velocity update subState->vel=clumpState->vel+clumpState->angVel.cross(subState->pos-clumpState->pos); subState->angVel=clumpState->angVel; if(integrator) integrator->saveMaximaDisplacement(b); } } static void addNonSpherical(const shared_ptr& clump, const shared_ptr& subBody); //FIXME //! update member positions after clump being moved by mouse (in case simulation is paused and engines will not do that). void userForcedDisplacementRedrawHook(){ throw runtime_error("Clump::userForcedDisplacementRedrawHook not yet implemented (with Clump as subclass of Shape).");} //! get force and torque on the clump itself, from forces/torques on members; does not include force on clump itself void addForceTorqueFromMembers(const State* clumpState, Scene* scene, Vector3r& F, Vector3r& T); //! Recalculates inertia tensor of a body after translation away from (default) or towards its centroid. static Matrix3r inertiaTensorTranslate(const Matrix3r& I,const Real m, const Vector3r& off); //! Recalculate body's inertia tensor in rotated coordinates. static Matrix3r inertiaTensorRotate(const Matrix3r& I, const Matrix3r& T); //! Recalculate body's inertia tensor in rotated coordinates. static Matrix3r inertiaTensorRotate(const Matrix3r& I, const Quaternionr& rot); boost::python::dict members_get(); YADE_CLASS_BASE_DOC_ATTRS_CTOR_PY(Clump,Shape,"Rigid aggregate of bodies", ((MemberMap,members,,Attr::hidden,"Ids and relative positions+orientations of members of the clump (should not be accessed directly)")) ((vector,ids,,Attr::readonly,"Ids of constituent particles (only informative; direct modifications will have no effect).")) //FIXME ,/*ctor*/ createIndex(); ,/*py*/ .add_property("members",&Clump::members_get,"Return clump members as {'id1':(relPos,relOri),...}") ); DECLARE_LOGGER; REGISTER_CLASS_INDEX(Clump,Shape); }; REGISTER_SERIALIZABLE(Clump); trunk-2018.02b/core/Dispatcher.hpp000066400000000000000000000245231324306050200167230ustar00rootroot00000000000000/************************************************************************* * Copyright (C) 2004 by Olivier Galizzi * * olivier.galizzi@imag.fr * * * * This program is free software; it is licensed under the terms of the * * GNU General Public License v2 or later. See file LICENSE for details. * *************************************************************************/ #pragma once #include #include #include #include // real base class for all dispatchers (the other one are templates) class Dispatcher: public Engine{ public: // these functions look to be completely unused...? virtual string getFunctorType() { throw; }; virtual int getDimension() { throw; }; virtual string getBaseClassType(unsigned int ) { throw; }; // virtual ~Dispatcher() {}; YADE_CLASS_BASE_DOC(Dispatcher,Engine,"Engine dispatching control to its associated functors, based on types of argument it receives. This abstract base class provides no functionality in itself.") }; REGISTER_SERIALIZABLE(Dispatcher); /* Each real dispatcher derives from Dispatcher1D or Dispatcher2D (both templates), which in turn derive from Dispatcher (an Engine) and DynLibDispatcher (the dispatch logic). Because we need literal functor and class names for registration in python, we provide macro that creates the real dispatcher class with everything needed. */ #define _YADE_DISPATCHER1D_FUNCTOR_ADD(FunctorT,f) virtual void addFunctor(shared_ptr f){ add1DEntry(f->get1DFunctorType1(),f); } #define _YADE_DISPATCHER2D_FUNCTOR_ADD(FunctorT,f) virtual void addFunctor(shared_ptr f){ add2DEntry(f->get2DFunctorType1(),f->get2DFunctorType2(),f); } #define _YADE_DIM_DISPATCHER_FUNCTOR_DOC_ATTRS_CTOR_PY(Dim,DispatcherT,FunctorT,doc,attrs,ctor,py) \ typedef FunctorT FunctorType; \ void updateScenePtr(){ FOREACH(shared_ptr f, functors){ f->scene=scene; }} \ void postLoad(DispatcherT&){ clearMatrix(); FOREACH(shared_ptr f, functors) add(YADE_PTR_CAST(f)); } \ virtual void add(FunctorT* f){ add(shared_ptr(f)); } \ virtual void add(shared_ptr f){ bool dupe=false; string fn=f->getClassName(); FOREACH(const shared_ptr& f, functors) { if(fn==f->getClassName()) dupe=true; } if(!dupe) functors.push_back(f); addFunctor(f); } \ BOOST_PP_CAT(_YADE_DISPATCHER,BOOST_PP_CAT(Dim,D_FUNCTOR_ADD))(FunctorT,f) \ boost::python::list functors_get(void) const { boost::python::list ret; FOREACH(const shared_ptr& f, functors){ ret.append(f); } return ret; } \ void functors_set(const vector >& ff){ functors.clear(); FOREACH(const shared_ptr& f, ff) add(f); postLoad(*this); } \ void pyHandleCustomCtorArgs(boost::python::tuple& t, boost::python::dict& d){ if(boost::python::len(t)==0)return; if(boost::python::len(t)!=1) throw invalid_argument("Exactly one list of " BOOST_PP_STRINGIZE(FunctorT) " must be given."); typedef std::vector > vecF; vecF vf=boost::python::extract(t[0])(); functors_set(vf); t=boost::python::tuple(); } \ YADE_CLASS_BASE_DOC_ATTRS_CTOR_PY(DispatcherT,Dispatcher,"Dispatcher calling :yref:`functors<" BOOST_PP_STRINGIZE(FunctorT) ">` based on received argument type(s).\n\n" doc, \ ((vector >,functors,,,"Functors active in the dispatch mechanism [overridden below].")) /*additional attrs*/ attrs, \ /*ctor*/ ctor, /*py*/ py .add_property("functors",&DispatcherT::functors_get,&DispatcherT::functors_set,"Functors associated with this dispatcher." " :yattrtype:`vector >` ") \ .def("dispMatrix",&DispatcherT::dump,boost::python::arg("names")=true,"Return dictionary with contents of the dispatch matrix.").def("dispFunctor",&DispatcherT::getFunctor,"Return functor that would be dispatched for given argument(s); None if no dispatch; ambiguous dispatch throws."); \ ) #define YADE_DISPATCHER1D_FUNCTOR_DOC_ATTRS_CTOR_PY(DispatcherT,FunctorT,doc,attrs,ctor,py) _YADE_DIM_DISPATCHER_FUNCTOR_DOC_ATTRS_CTOR_PY(1,DispatcherT,FunctorT,doc,attrs,ctor,py) #define YADE_DISPATCHER2D_FUNCTOR_DOC_ATTRS_CTOR_PY(DispatcherT,FunctorT,doc,attrs,ctor,py) _YADE_DIM_DISPATCHER_FUNCTOR_DOC_ATTRS_CTOR_PY(2,DispatcherT,FunctorT,doc,attrs,ctor,py) // HELPER FUNCTIONS /*! Function returning class name (as string) for given index and topIndexable (top-level indexable, such as Shape, Material and so on) This function exists solely for debugging, is quite slow: it has to traverse all classes and ask for inheritance information. It should be used primarily to convert indices to names in Dispatcher::dictDispatchMatrix?D; since it relies on Omega for RTTI, this code could not be in Dispatcher itself. s*/ template std::string Dispatcher_indexToClassName(int idx){ boost::scoped_ptr top(new topIndexable); std::string topName=top->getClassName(); typedef std::pair classItemType; FOREACH(classItemType clss, Omega::instance().getDynlibsDescriptor()){ if(Omega::instance().isInheritingFrom_recursive(clss.first,topName) || clss.first==topName){ // create instance, to ask for index shared_ptr inst=YADE_PTR_DYN_CAST(ClassFactory::instance().createShared(clss.first)); assert(inst); if(inst->getClassIndex()<0 && inst->getClassName()!=top->getClassName()){ throw logic_error("Class "+inst->getClassName()+" didn't use REGISTER_CLASS_INDEX("+inst->getClassName()+","+top->getClassName()+") and/or forgot to call createIndex() in the ctor. [[ Please fix that! ]]"); } if(inst->getClassIndex()==idx) return clss.first; } } throw runtime_error("No class with index "+boost::lexical_cast(idx)+" found (top-level indexable is "+topName+")"); } //! Return class index of given indexable template int Indexable_getClassIndex(const shared_ptr i){return i->getClassIndex();} //! Return sequence (hierarchy) of class indices of given indexable; optionally convert to names template boost::python::list Indexable_getClassIndices(const shared_ptr i, bool convertToNames){ int depth=1; boost::python::list ret; int idx0=i->getClassIndex(); if(convertToNames) ret.append(Dispatcher_indexToClassName(idx0)); else ret.append(idx0); if(idx0<0) return ret; // don't continue and call getBaseClassIndex(), since we are at the top already while(true){ int idx=i->getBaseClassIndex(depth++); if(convertToNames) ret.append(Dispatcher_indexToClassName(idx)); else ret.append(idx); if(idx<0) return ret; } } template < class FunctorType, bool autoSymmetry=true> class Dispatcher1D : public Dispatcher, public DynLibDispatcher < TYPELIST_1(typename FunctorType::DispatchType1) // base classes for dispatch , FunctorType // class that provides multivirtual call , typename FunctorType::ReturnType // return type , typename FunctorType::ArgumentTypes , autoSymmetry > { public : typedef typename FunctorType::DispatchType1 baseClass; typedef baseClass argType1; typedef FunctorType functorType; typedef DynLibDispatcher dispatcherBase; shared_ptr getFunctor(shared_ptr arg){ return dispatcherBase::getExecutor(arg); } boost::python::dict dump(bool convertIndicesToNames){ boost::python::dict ret; FOREACH(const DynLibDispatcher_Item1D& item, dispatcherBase::dataDispatchMatrix1D()){ if(convertIndicesToNames){ string arg1=Dispatcher_indexToClassName(item.ix1); ret[boost::python::make_tuple(arg1)]=item.functorName; } else ret[boost::python::make_tuple(item.ix1)]=item.functorName; } return ret; } int getDimension() { return 1; } virtual string getFunctorType(){ shared_ptr eu(new FunctorType); return eu->getClassName(); } virtual string getBaseClassType(unsigned int i){ if (i==0) { shared_ptr bc(new baseClass); return bc->getClassName(); } else return ""; } public: REGISTER_ATTRIBUTES(Dispatcher,); REGISTER_CLASS_AND_BASE(Dispatcher1D,Dispatcher DynLibDispatcher); }; template < class FunctorType, bool autoSymmetry=true> class Dispatcher2D : public Dispatcher, public DynLibDispatcher < TYPELIST_2(typename FunctorType::DispatchType1,typename FunctorType::DispatchType2) // base classes for dispatch , FunctorType // class that provides multivirtual call , typename FunctorType::ReturnType // return type , typename FunctorType::ArgumentTypes // argument of engine unit , autoSymmetry > { public : typedef typename FunctorType::DispatchType1 baseClass1; typedef typename FunctorType::DispatchType2 baseClass2; typedef baseClass1 argType1; typedef baseClass2 argType2; typedef FunctorType functorType; typedef DynLibDispatcher dispatcherBase; shared_ptr getFunctor(shared_ptr arg1, shared_ptr arg2){ return dispatcherBase::getExecutor(arg1,arg2); } boost::python::dict dump (bool convertIndicesToNames) { boost::python::dict ret; FOREACH(const DynLibDispatcher_Item2D& item, dispatcherBase::dataDispatchMatrix2D()){ if(convertIndicesToNames){ string arg1=Dispatcher_indexToClassName(item.ix1), arg2=Dispatcher_indexToClassName(item.ix2); ret[boost::python::make_tuple(arg1,arg2)]=item.functorName; } else ret[boost::python::make_tuple(item.ix1,item.ix2)]=item.functorName; } return ret; } virtual int getDimension() { return 2; } virtual string getFunctorType(){ shared_ptr eu(new FunctorType); return eu->getClassName(); } virtual string getBaseClassType(unsigned int i){ if (i==0){ shared_ptr bc(new baseClass1); return bc->getClassName(); } else if (i==1){ shared_ptr bc(new baseClass2); return bc->getClassName();} else return ""; } public: REGISTER_ATTRIBUTES(Dispatcher,); REGISTER_CLASS_AND_BASE(Dispatcher2D,Dispatcher DynLibDispatcher); }; trunk-2018.02b/core/DisplayParameters.hpp000066400000000000000000000033031324306050200202570ustar00rootroot00000000000000#pragma once /* Class for storing set of display parameters. * * The interface sort of emulates map string->string (which is not handled by yade-serialization). * * The "keys" (called displayTypes) are intended to be "OpenGLRenderer" or "GLViewer" (and perhaps other). * The "values" are intended to be XML representation of display parameters, obtained either by yade-serialization * with OpenGLRenderer and saveStateToStream with QGLViewer (and GLViewer). * */ class DisplayParameters: public Serializable{ private: std::vector values; std::vector displayTypes; public: //! Get value of given display type and put it in string& value and return true; if there is no such display type, return false. bool getValue(std::string displayType, std::string& value){assert(values.size()==displayTypes.size()); vector::iterator I=std::find(displayTypes.begin(),displayTypes.end(),displayType); if(I==displayTypes.end()) return false; value=values[std::distance(displayTypes.begin(),I)]; return true;} //! Set value of given display type; if such display type exists, it is overwritten, otherwise a new one is created. void setValue(std::string displayType, std::string value){assert(values.size()==displayTypes.size()); vector::iterator I=std::find(displayTypes.begin(),displayTypes.end(),displayType); if(I==displayTypes.end()){displayTypes.push_back(displayType); values.push_back(value);} else {values[std::distance(displayTypes.begin(),I)]=value;};} DisplayParameters(){} virtual ~DisplayParameters(){} REGISTER_ATTRIBUTES(Serializable,(displayTypes)(values)); REGISTER_CLASS_AND_BASE(DisplayParameters,Serializable); }; REGISTER_SERIALIZABLE(DisplayParameters); trunk-2018.02b/core/EnergyTracker.hpp000066400000000000000000000067231324306050200174040ustar00rootroot00000000000000#pragma once #include #include namespace py=boost::python; class EnergyTracker: public Serializable{ public: ~EnergyTracker(); void findId(const std::string& name, int& id, bool reset=false, bool newIfNotFound=true){ if(id>0) return; // the caller should have checked this already if(names.count(name)) id=names[name]; else if(newIfNotFound) { #ifdef YADE_OPENMP #pragma omp critical #endif { energies.resize(energies.size()+1); id=energies.size()-1; resetStep.resize(id+1); resetStep[id]=reset; names[name]=id; assert(id<(int)energies.size()); assert(id>=0); } } } // set value of the accumulator; note: must NOT be called from parallel sections! void set(const Real& val, const std::string& name, int &id){ if(id<0) findId(name,id,/* do not reset value that is set directly */ false); energies.set(id,val); } // add value to the accumulator; safely called from parallel sections void add(const Real& val, const std::string& name, int &id, bool reset=false){ if(id<0) findId(name,id,reset); energies.add(id,val); } Real getItem_py(const std::string& name){ int id=-1; findId(name,id,false,false); if (id<0) {PyErr_SetString(PyExc_KeyError,("Unknown energy name '"+name+"'.").c_str()); py::throw_error_already_set(); } return energies.get(id); } void setItem_py(const std::string& name, Real val){ int id=-1; set(val,name,id); } void clear(){ energies.clear(); names.clear(); resetStep.clear();} void resetResettables(){ size_t sz=energies.size(); for(size_t id=0; id > dta=energies.getPerThreadData(); FOREACH(pairStringInt p,names) ret[p.first]=dta[p.second]; return ret; }; typedef std::map mapStringInt; typedef std::pair pairStringInt; YADE_CLASS_BASE_DOC_ATTRS_CTOR_PY(EnergyTracker,Serializable,"Storage for tracing energies. Only to be used if :yref:`O.trackEnergy` is True.", ((OpenMPArrayAccumulator,energies,,,"Energy values, in linear array")) ((mapStringInt,names,,Attr::hidden,"Associate textual name to an index in the energies array.")) ((vector,resetStep,,Attr::hidden,"Whether the respective energy value should be reset at every step.")) ,/*ctor*/ ,/*py*/ .def("__getitem__",&EnergyTracker::getItem_py,"Get energy value for given name.") .def("__setitem__",&EnergyTracker::setItem_py,"Set energy value for given name (will create a non-resettable item, if it does not exist yet).") .def("clear",&EnergyTracker::clear,"Clear all stored values.") .def("keys",&EnergyTracker::keys_py,"Return defined energies.") .def("items",&EnergyTracker::items_py,"Return contents as list of (name,value) tuples.") .def("total",&EnergyTracker::total,"Return sum of all energies.") .add_property("_perThreadData",&EnergyTracker::perThreadData,"Contents as dictionary, where each value is tuple of individual threads' values (for debugging)") ) }; REGISTER_SERIALIZABLE(EnergyTracker); trunk-2018.02b/core/Engine.hpp000066400000000000000000000072611324306050200160420ustar00rootroot00000000000000/************************************************************************* * Copyright (C) 2004 by Olivier Galizzi * * olivier.galizzi@imag.fr * * Copyright (C) 2004 by Janek Kozicki * * cosurgi@berlios.de * * * * This program is free software; it is licensed under the terms of the * * GNU General Public License v2 or later. See file LICENSE for details. * *************************************************************************/ #pragma once #include #include #include #include class Body; class Scene; CREATE_LOGGER(Engine); class Engine: public Serializable{ public: // pointer to the simulation, set at every step by Scene::moveToNextTimeStep Scene* scene; //! high-level profiling information; not serializable TimingInfo timingInfo; //! precise profiling information (timing of fragments of the engine) shared_ptr timingDeltas; virtual ~Engine() {}; virtual bool isActivated() { return true; }; virtual void action() { LOG_FATAL("Engine "<(new TimingDeltas); #endif , /* py */ .add_property("execTime",&Engine::timingInfo_nsec_get,&Engine::timingInfo_nsec_set,"Cummulative time this Engine took to run (only used if :yref:`O.timingEnabled`\\ ==\\ ``True``).") .add_property("execCount",&Engine::timingInfo_nExec_get,&Engine::timingInfo_nExec_set,"Cummulative count this engine was run (only used if :yref:`O.timingEnabled`\\ ==\\ ``True``).") .def_readonly("timingDeltas",&Engine::timingDeltas,"Detailed information about timing inside the Engine itself. Empty unless enabled in the source code and :yref:`O.timingEnabled`\\ ==\\ ``True``.") .def("__call__",&Engine::explicitAction) ); }; REGISTER_SERIALIZABLE(Engine); trunk-2018.02b/core/FileGenerator.cpp000066400000000000000000000047531324306050200173610ustar00rootroot00000000000000/************************************************************************* * This program is free software; it is licensed under the terms of the * * GNU General Public License v2 or later. See file LICENSE for details. * *************************************************************************/ #include #include #include #include #include"FileGenerator.hpp" CREATE_LOGGER(FileGenerator); bool FileGenerator::generate(std::string& msg){ throw invalid_argument("Calling abstract FileGenerator::generate() does not make sense."); } bool FileGenerator::generateAndSave(const string& outputFileName, string& message) { bool status; message=""; boost::posix_time::ptime now = boost::posix_time::second_clock::local_time(); try { status=generate(message); // will modify message } catch(std::exception& e){ LOG_FATAL("Unhandled exception: "< #include #include "Scene.hpp" #include "ThreadWorker.hpp" class FileGenerator: public Serializable { protected: shared_ptr scene; public: bool generateAndSave(const string& outFile, string& message); protected : //! Returns whether the generation was successful; message for user is in FileGenerator::message virtual bool generate(std::string& msg); void pyGenerate(const string& out); void pyLoad(); YADE_CLASS_BASE_DOC_ATTRS_CTOR_PY(FileGenerator,Serializable,"Base class for scene generators, preprocessors.", /*attrs*/, /*ctor*/ , .def("generate",&FileGenerator::pyGenerate,(boost::python::arg("out")),"Generate scene, save to given file") .def("load",&FileGenerator::pyLoad,"Generate scene, save to temporary file and load immediately"); ); DECLARE_LOGGER; }; REGISTER_SERIALIZABLE(FileGenerator); trunk-2018.02b/core/ForceContainer.hpp000066400000000000000000000124651324306050200175400ustar00rootroot00000000000000// 2009 © Václav Šmilauer #pragma once #include #include #include // make sure that (void*)&vec[0]==(void*)&vec BOOST_STATIC_ASSERT(sizeof(Vector3r)==3*sizeof(Real)); #ifdef YADE_OPENMP #include #endif /*! Container for Body External Variables (forces), typically forces and torques from interactions. * Values should be reset at every iteration by calling ForceContainer::reset(); * If you want to add your own force type, you need to: * * 1. Create storage vector * 2. Create accessor function * 3. Update the resize function * 4. Update the reset function * 5. update the sync function (for the multithreaded implementation) * * This class exists in two flavors: non-parallel and parallel. The parallel one stores * force increments separately for every thread and sums those when sync() is called. * The reason of this design is that the container is not truly random-access, but rather * is written to everywhere in one phase and read in the next one. Adding to force/torque * marks the container as dirty and sync() must be performed before reading the stored data. * Calling getForce/getTorque when the container is not synchronized throws an exception. * * It is intentional that sync() needs to be called exlicitly, since syncs are expensive and * the programmer should be aware of that. Sync is however performed only if the container * is dirty. Every full sync increments the syncCount variable, that should ideally equal * the number of steps (one per step). * * The number of threads (omp_get_max_threads) may not change once ForceContainer is constructed. * * The non-parallel flavor has the same interface, but sync() is no-op and synchronization * is not enforced at all. */ //! This is the parallel flavor of ForceContainer class ForceContainer { private: typedef std::vector vvector; #ifdef YADE_OPENMP std::vector _forceData; std::vector _torqueData; std::vector _moveData; std::vector _rotData; std::vector _maxId; std::vector sizeOfThreads; void ensureSize(Body::id_t id, int threadN); #else void ensureSize(Body::id_t id); Body::id_t _maxId=0; #endif vvector _force, _torque, _move, _rot, _permForce, _permTorque; size_t size = 0; bool syncedSizes = true; int nThreads; bool synced = true; bool moveRotUsed = false; bool permForceUsed = false; boost::mutex globalMutex; const Vector3r _zero = Vector3r::Zero(); void ensureSynced(); // dummy function to avoid template resolution failure friend class boost::serialization::access; template void serialize(ArchiveT & ar, unsigned int version){} public: unsigned long syncCount = 0; long lastReset = 0; ForceContainer(); const Vector3r& getForce(Body::id_t id); void addForce(Body::id_t id, const Vector3r& f); const Vector3r& getTorque(Body::id_t id); void addTorque(Body::id_t id, const Vector3r& t); const Vector3r& getMove(Body::id_t id); void addMove(Body::id_t id, const Vector3r& m); const Vector3r& getRot(Body::id_t id); void addRot(Body::id_t id, const Vector3r& r); void addMaxId(Body::id_t id); void setPermForce(Body::id_t id, const Vector3r& f); void setPermTorque(Body::id_t id, const Vector3r& t); const Vector3r& getPermForce(Body::id_t id); const Vector3r& getPermTorque(Body::id_t id); /*! Function to allow friend classes to get force even if not synced. Used for clumps by NewtonIntegrator. * Dangerous! The caller must know what it is doing! (i.e. don't read after write * for a particular body id. */ const Vector3r& getForceUnsynced (Body::id_t id); const Vector3r& getTorqueUnsynced(Body::id_t id); void addForceUnsynced(Body::id_t id, const Vector3r& f); void addTorqueUnsynced(Body::id_t id, const Vector3r& m); /* To be benchmarked: sum thread data in getForce/getTorque upon request for each body individually instead of by the sync() function globally */ // this function is used from python so that running simulation is not slowed down by sync'ing on occasions // since Vector3r writes are not atomic, it might (rarely) return wrong value, if the computation is running meanwhile const Vector3r getForceSingle (Body::id_t id); const Vector3r getTorqueSingle(Body::id_t id); const Vector3r getMoveSingle (Body::id_t id); const Vector3r getRotSingle (Body::id_t id); #ifdef YADE_OPENMP void syncSizesOfContainers(); void resize(size_t newSize, int threadN); #else void resize(size_t newSize); #endif /* Sum contributions from all threads, save to _force&_torque. * Locks globalMutex, since one thread modifies common data (_force&_torque). * Must be called before get* methods are used. Exception is thrown otherwise, since data are not consistent. */ void sync(); void resizePerm(size_t newSize); /*! Reset all resetable data, also reset summary forces/torques and mark the container clean. If resetAll, reset also user defined forces and torques*/ // perhaps should be private and friend Scene or whatever the only caller should be void reset(long iter, bool resetAll=false); //! say for how many threads we have allocated space const int getNumAllocatedThreads() const; const bool getMoveRotUsed() const; const bool getPermForceUsed() const; }; trunk-2018.02b/core/ForceContainerParallel.cpp000066400000000000000000000166021324306050200212050ustar00rootroot00000000000000#include void ForceContainer::ensureSynced() { if(!synced) throw runtime_error("ForceContainer not thread-synchronized; call sync() first!"); } void ForceContainer::addForceUnsynced(Body::id_t id, const Vector3r& f) { assert ((size_t)id void ForceContainer::ensureSize(Body::id_t id, int threadN) { assert(nThreads>omp_get_thread_num()); const Body::id_t idMaxTmp = max(id, _maxId[threadN]); _maxId[threadN] = 0; if (threadN<0) { resizePerm(min((size_t)1.5*(idMaxTmp+100),(size_t)(idMaxTmp+2000))); } else if (sizeOfThreads[threadN]<=(size_t)idMaxTmp) { resize(min((size_t)1.5*(idMaxTmp+100),(size_t)(idMaxTmp+2000)),threadN); } } ForceContainer::ForceContainer() { nThreads=omp_get_max_threads(); for(int i=0; i 0) { synced = false;} } if(synced) return; boost::mutex::scoped_lock lock(globalMutex); if(synced) return; // if synced meanwhile for(int i=0; i 0) { ensureSize(_maxId[i],i);} } syncSizesOfContainers(); for(long id=0; id<(long)size; id++){ Vector3r sumF(Vector3r::Zero()), sumT(Vector3r::Zero()); for(int thread=0; thread ForceContainer::ForceContainer() {}; void ForceContainer::ensureSize(Body::id_t id) { const Body::id_t idMaxTmp = max(id, _maxId); _maxId = 0; if(size<=(size_t)idMaxTmp) { resize(min((size_t)1.5*(idMaxTmp+100),(size_t)(idMaxTmp+2000))); }; } const Vector3r& ForceContainer::getForce(Body::id_t id) { ensureSize(id); return _force[id]; } void ForceContainer::addForce(Body::id_t id,const Vector3r& f) { ensureSize(id); _force[id]+=f; } const Vector3r& ForceContainer::getTorque(Body::id_t id) { ensureSize(id); return _torque[id]; } void ForceContainer::addTorque(Body::id_t id,const Vector3r& t) { ensureSize(id); _torque[id]+=t; } const Vector3r& ForceContainer::getMove(Body::id_t id) { ensureSize(id); return _move[id]; } void ForceContainer::addMove(Body::id_t id,const Vector3r& f) { ensureSize(id); moveRotUsed=true; _move[id]+=f; } const Vector3r& ForceContainer::getRot(Body::id_t id) { ensureSize(id); return _rot[id]; } void ForceContainer::addRot(Body::id_t id,const Vector3r& f) { ensureSize(id); moveRotUsed=true; _rot[id]+=f; } void ForceContainer::addMaxId(Body::id_t id) { _maxId=id; } void ForceContainer::setPermForce(Body::id_t id, const Vector3r& f) { ensureSize(id); _permForce[id]=f; permForceUsed=true; } void ForceContainer::setPermTorque(Body::id_t id, const Vector3r& t) { ensureSize(id); _permTorque[id]=t; permForceUsed=true; } const Vector3r& ForceContainer::getPermForce(Body::id_t id) { ensureSize(id); return _permForce[id]; } const Vector3r& ForceContainer::getPermTorque(Body::id_t id) { ensureSize(id); return _permTorque[id]; } const Vector3r& ForceContainer::getForceUnsynced (Body::id_t id) { return getForce(id); } const Vector3r& ForceContainer::getTorqueUnsynced(Body::id_t id) { return getForce(id); } const Vector3r ForceContainer::getForceSingle (Body::id_t id) { ensureSize(id); if (permForceUsed) { return _force [id] + _permForce[id]; } else { return _force [id]; } } const Vector3r ForceContainer::getTorqueSingle(Body::id_t id) { ensureSize(id); if (permForceUsed) { return _torque[id] + _permTorque[id]; } else { return _torque[id]; } } const Vector3r ForceContainer::getMoveSingle(Body::id_t id) { ensureSize(id); return _move [id]; } const Vector3r ForceContainer::getRotSingle(Body::id_t id) { ensureSize(id); return _rot[id]; } void ForceContainer::sync() { if (_maxId>0) { ensureSize(_maxId); _maxId=0; } if (permForceUsed) { for(long id=0; id<(long)size; id++) { _force[id]+=_permForce[id]; _torque[id]+=_permTorque[id]; } } return; } void ForceContainer::reset(long iter, bool resetAll) { memset(&_force [0],0,sizeof(Vector3r)*size); memset(&_torque[0],0,sizeof(Vector3r)*size); if(moveRotUsed){ memset(&_move [0],0,sizeof(Vector3r)*size); memset(&_rot [0],0,sizeof(Vector3r)*size); moveRotUsed=false; } if (resetAll){ memset(&_permForce [0], 0,sizeof(Vector3r)*size); memset(&_permTorque[0], 0,sizeof(Vector3r)*size); permForceUsed = false; } lastReset=iter; } void ForceContainer::resize(size_t newSize) { _force.resize(newSize,Vector3r::Zero()); _torque.resize(newSize,Vector3r::Zero()); _permForce.resize(newSize,Vector3r::Zero()); _permTorque.resize(newSize,Vector3r::Zero()); _move.resize(newSize,Vector3r::Zero()); _rot.resize(newSize,Vector3r::Zero()); size=newSize; } const int ForceContainer::getNumAllocatedThreads() const {return 1;} const bool ForceContainer::getMoveRotUsed() const {return moveRotUsed;} const bool ForceContainer::getPermForceUsed() const {return permForceUsed;} #endif trunk-2018.02b/core/FrontEnd.hpp000066400000000000000000000016161324306050200163520ustar00rootroot00000000000000/************************************************************************* * Copyright (C) 2004 by Olivier Galizzi * * olivier.galizzi@imag.fr * * * * This program is free software; it is licensed under the terms of the * * GNU General Public License v2 or later. See file LICENSE for details. * *************************************************************************/ #pragma once #include "Omega.hpp" #include class FrontEnd : public Factorable { public : FrontEnd () {}; virtual ~FrontEnd () {}; virtual int run(int , char * []) { return -1;}; // called before actually invoking it virtual bool available(){return false;} REGISTER_CLASS_AND_BASE(FrontEnd,Factorable); }; REGISTER_FACTORABLE(FrontEnd); trunk-2018.02b/core/Functor.hpp000066400000000000000000000117621324306050200162560ustar00rootroot00000000000000/************************************************************************* * Copyright (C) 2004 by Olivier Galizzi * * olivier.galizzi@imag.fr * * Copyright (C) 2004 by Janek Kozicki * * cosurgi@berlios.de * * * * This program is free software; it is licensed under the terms of the * * GNU General Public License v2 or later. See file LICENSE for details. * *************************************************************************/ #pragma once #include #include #include class TimingDeltas; class Scene; class Functor: public Serializable { public: virtual vector getFunctorTypes(){throw;} shared_ptr timingDeltas; //! updated before every dispatch loop by the dispatcher; DO NOT ABUSE access to scene, except for getting global variables like scene->dt. Scene* scene; virtual ~Functor() {}; // defined in Dispatcher.cpp YADE_CLASS_BASE_DOC_ATTRS_CTOR_PY(Functor,Serializable,"Function-like object that is called by Dispatcher, if types of arguments match those the Functor declares to accept.", ((string,label,,,"Textual label for this object; must be a valid python identifier, you can refer to it directly from python.")), /*ctor*/ #ifdef USE_TIMING_DELTAS timingDeltas=shared_ptr(new TimingDeltas); #endif , .def_readonly("timingDeltas",&Functor::timingDeltas,"Detailed information about timing inside the Dispatcher itself. Empty unless enabled in the source code and O.timingEnabled==True.") .add_property("bases",&Functor::getFunctorTypes,"Ordered list of types (as strings) this functor accepts.") ); }; REGISTER_SERIALIZABLE(Functor); template < class _DispatchType1, class _ReturnType, class _ArgumentTypes > class Functor1D: public Functor, public FunctorWrapper<_ReturnType, _ArgumentTypes> { public: typedef _DispatchType1 DispatchType1; typedef _ReturnType ReturnType; typedef _ArgumentTypes ArgumentTypes; #define FUNCTOR1D(type1) public: std::string get1DFunctorType1(void){return string(#type1);} int checkArgTypes(const shared_ptr& arg1){ return (bool)YADE_PTR_DYN_CAST(arg1)?1:0; } virtual std::string get1DFunctorType1(void){throw runtime_error("Class "+this->getClassName()+" did not use FUNCTOR1D to declare its argument type?"); } virtual vector getFunctorTypes(void){vector ret; ret.push_back(get1DFunctorType1()); return ret;}; // check that the object can be correctly cast to the derived class handled by the functor (will be used if ever utils.createInteraction can be called with list of functors only) // virtual bool checkArgTypes(const shared_ptr& arg1){ throw runtime_error("Class "+this->getClassName()+" did not use FUNCTOR1D to declare its argument type?"); } REGISTER_CLASS_AND_BASE(Functor1D,Functor FunctorWrapper); /* do not REGISTER_ATTRIBUTES here, since we are template; derived classes should call REGISTER_ATTRIBUTES(Functor,(their)(own)(attributes)), bypassing Functor1D */ }; template < class _DispatchType1, class _DispatchType2, class _ReturnType, class _ArgumentTypes > class Functor2D: public Functor, public FunctorWrapper<_ReturnType, _ArgumentTypes> { public: typedef _DispatchType1 DispatchType1; typedef _DispatchType2 DispatchType2; typedef _ReturnType ReturnType; typedef _ArgumentTypes ArgumentTypes; #define FUNCTOR2D(type1,type2) public: std::string get2DFunctorType1(void){return string(#type1);}; std::string get2DFunctorType2(void){return string(#type2);}; int checkArgTypes(const shared_ptr& arg1, const shared_ptr& arg2){ if(YADE_PTR_DYN_CAST(arg1)&&YADE_PTR_DYN_CAST(arg2)) return 1; if(YADE_PTR_DYN_CAST(arg2)&&YADE_PTR_DYN_CAST(arg1)) return -1; return 0; } virtual std::string get2DFunctorType1(void){throw logic_error("Class "+this->getClassName()+" did not use FUNCTOR2D to declare its argument types?");} virtual std::string get2DFunctorType2(void){throw logic_error("Class "+this->getClassName()+" did not use FUNCTOR2D to declare its argument types?");} virtual vector getFunctorTypes(){vector ret; ret.push_back(get2DFunctorType1()); ret.push_back(get2DFunctorType2()); return ret;}; // check that objects can be correctly cast to derived classes handled by the functor (see comment in Functor1D:: checkArgTypes) // virtual bool checkArgTypes(const shared_ptr&, const shared_ptr&){ throw logic_error("Class "+this->getClassName()+" did not use FUNCTOR2D to declare its argument types?"); } REGISTER_CLASS_AND_BASE(Functor2D,Functor FunctorWrapper); /* do not REGISTER_ATTRIBUTES here, since we are template; derived classes should call REGISTER_ATTRIBUTES(Functor,(their)(own)(attributes)), bypassing Functor2D */ }; trunk-2018.02b/core/GLConfig.hpp000066400000000000000000000030511324306050200162560ustar00rootroot00000000000000// code not yet for use (6/12/2009); if long here uselessly, delete. // // 2009 © Václav Šmilauer #include /*! Storage for general 3d view settings. Is saved along with simulation and passed to every call to render(...). Contains more or less what used to be inside OpenGLRenderer. */ class GLConfig: public Serializable{ Vector3r lightPos,bgColor; Body::id_t currSel; bool dof,id,bbox,geom,wire,intrGeom,intrPhys; int mask; bool scaleDisplacements,scaleRotations; Vector3r displacementScale; Real rotationScale; vector clipPlaneSe3; vector clipPlaneActive; // should be bool, but serialization doesn't handle vector // not saved Vector3r highlightEmission0; Vector3r highlightEmission1; // normalized saw signal with given periodicity, with values ∈ 〈0,1〉 */ Real normSaw(Real t, Real period){ Real xi=(t-period*((int)(t/period)))/period; /* normalized value, (0-1〉 */ return (xi<.5?2*xi:2-2*xi); } Real normSquare(Real t, Real period){ Real xi=(t-period*((int)(t/period)))/period; /* normalized value, (0-1〉 */ return (xi<.5?0:1); } //! wrap number to interval x0…x1 Real wrapCell(const Real x, const Real x0, const Real x1); //! wrap point to inside Scene's cell (identity if !Scene::isPeriodic) Vector3r wrapCellPt(const Vector3r& pt, Scene* rb); void drawPeriodicCell(Scene*); REGISTER_ATTRIBUTES(Serializable,(dof)(id)(bbox)(geom)(wire)(intrGeom)(intrPhys)(mask)(scaleDisplacements)(scaleRotations)(displacementScale)(rotationScale)(clipPlaneSe3)(clipPlaneActive)); }; trunk-2018.02b/core/GlobalEngine.hpp000066400000000000000000000014511324306050200171560ustar00rootroot00000000000000/************************************************************************* * Copyright (C) 2004 by Janek Kozicki * * cosurgi@berlios.de * * * * This program is free software; it is licensed under the terms of the * * GNU General Public License v2 or later. See file LICENSE for details. * *************************************************************************/ #pragma once #include "Engine.hpp" class GlobalEngine: public Engine{ public : virtual ~GlobalEngine() {}; YADE_CLASS_BASE_DOC(GlobalEngine,Engine,"Engine that will generally affect the whole simulation (contrary to PartialEngine)."); }; REGISTER_SERIALIZABLE(GlobalEngine); trunk-2018.02b/core/IGeom.hpp000066400000000000000000000017201324306050200156270ustar00rootroot00000000000000/************************************************************************* * Copyright (C) 2004 by Olivier Galizzi * * olivier.galizzi@imag.fr * * * * This program is free software; it is licensed under the terms of the * * GNU General Public License v2 or later. See file LICENSE for details. * *************************************************************************/ #pragma once #include #include #include #include class IGeom : public Serializable, public Indexable { YADE_CLASS_BASE_DOC_ATTRS_CTOR_PY(IGeom,Serializable,"Geometrical configuration of interaction", /*no attrs*/, /*ctor*/, /*py*/ YADE_PY_TOPINDEXABLE(IGeom) ); REGISTER_INDEX_COUNTER(IGeom); }; REGISTER_SERIALIZABLE(IGeom); trunk-2018.02b/core/IPhys.hpp000066400000000000000000000017441324306050200156710ustar00rootroot00000000000000/************************************************************************* * Copyright (C) 2004 by Olivier Galizzi * * olivier.galizzi@imag.fr * * * * This program is free software; it is licensed under the terms of the * * GNU General Public License v2 or later. See file LICENSE for details. * *************************************************************************/ #pragma once #include #include #include #include class IPhys : public Serializable, public Indexable { YADE_CLASS_BASE_DOC_ATTRS_CTOR_PY(IPhys,Serializable,"Physical (material) properties of :yref:`interaction`.", /*attrs*/, /*ctor*/, /*py*/YADE_PY_TOPINDEXABLE(IPhys) ); REGISTER_INDEX_COUNTER(IPhys); }; REGISTER_SERIALIZABLE(IPhys); trunk-2018.02b/core/Interaction.cpp000066400000000000000000000025651324306050200171110ustar00rootroot00000000000000/************************************************************************* * Copyright (C) 2004 by Olivier Galizzi * * olivier.galizzi@imag.fr * * Copyright (C) 2004 by Janek Kozicki * * cosurgi@berlios.de * * * * This program is free software; it is licensed under the terms of the * * GNU General Public License v2 or later. See file LICENSE for details. * *************************************************************************/ #include"Interaction.hpp" #include Interaction::Interaction(Body::id_t newId1,Body::id_t newId2): id1(newId1), id2(newId2), cellDist(Vector3i(0,0,0)){ reset(); } bool Interaction::isFresh(Scene* rb){ return iterMadeReal==rb->iter;} void Interaction::init(){ iterMadeReal=-1; functorCache.geomExists=true; isActive=true; } void Interaction::reset(){ geom=shared_ptr(); phys=shared_ptr(); functorCache.geom = nullptr; functorCache.phys = nullptr; functorCache.constLaw = nullptr; init(); } void Interaction::swapOrder(){ if(geom || phys){ throw std::logic_error("Bodies in interaction cannot be swapped if they have geom or phys."); } std::swap(id1,id2); cellDist*=-1; } trunk-2018.02b/core/Interaction.hpp000066400000000000000000000076611324306050200171200ustar00rootroot00000000000000#pragma once #include // keep those two here, template instantiation & boost::python gets broken otherwise, e.g. https://bugs.launchpad.net/bugs/618766 #include #include #include class IGeomFunctor; class IPhysFunctor; class LawFunctor; class Scene; class Interaction: public Serializable{ private: friend class IPhysDispatcher; friend class InteractionLoop; public: bool isReal() const {return (bool)geom && (bool)phys;} //! If this interaction was just created in this step (for the constitutive law, to know that it is the first time there) bool isFresh(Scene* rb); bool isActive; Interaction(Body::id_t newId1,Body::id_t newId2); const Body::id_t& getId1() const {return id1;}; const Body::id_t& getId2() const {return id2;}; //! swaps order of bodies within the interaction void swapOrder(); bool operator<(const Interaction& other) const { return getId1() geom = nullptr; shared_ptr phys = nullptr; shared_ptr constLaw = nullptr; } functorCache; //! Reset interaction to the intial state (keep only body ids) void reset(); //! common initialization called from both constructor and reset() void init(); YADE_CLASS_BASE_DOC_ATTRS_CTOR_PY(Interaction,Serializable,"Interaction between pair of bodies.", ((Body::id_t,id1,0,Attr::readonly,":yref:`Id` of the first body in this interaction.")) ((Body::id_t,id2,0,Attr::readonly,":yref:`Id` of the second body in this interaction.")) ((long,iterMadeReal,-1,,"Step number at which the interaction was fully (in the sense of geom and phys) created. (Should be touched only by :yref:`IPhysDispatcher` and :yref:`InteractionLoop`, therefore they are made friends of Interaction")) ((long,iterLastSeen,-1,(Attr::noSave|Attr::hidden),"At which step this interaction was last detected by the collider. InteractionLoop will remove it if InteractionContainer::iterColliderLastRun==scene->iter, InteractionContainer::iterColliderLastRun is positive (some colliders manage interaction deletion themselves, such as :yref:`InsertionSortCollider`) and iterLastSeeniter.")) ((shared_ptr,geom,,,"Geometry part of the interaction.")) ((shared_ptr,phys,,,"Physical (material) part of the interaction.")) ((Vector3i,cellDist,Vector3i(0,0,0),,"Distance of bodies in cell size units, if using periodic boundary conditions; id2 is shifted by this number of cells from its :yref:`State::pos` coordinates for this interaction to exist. Assigned by the collider.\n\n.. warning::\n\t(internal) cellDist must survive Interaction::reset(), it is only initialized in ctor. Interaction that was cancelled by the constitutive law, was reset() and became only potential must have thepriod information if the geometric functor again makes it real. Good to know after few days of debugging that :-)")) ((int,linIx,-1,(Attr::noSave|Attr::hidden),"Index in the linear interaction container. For internal use by InteractionContainer only.")) ((long,iterBorn,-1,,"Step number at which the interaction was added to simulation.")) , /* ctor */ init(), /*py*/ .add_property("isReal",&Interaction::isReal,"True if this interaction has both geom and phys; False otherwise.") .def_readwrite("isActive",&Interaction::isActive,"True if this interaction is active. Otherwise the forces from this interaction will not be taken into account. True by default.") ); }; REGISTER_SERIALIZABLE(Interaction); trunk-2018.02b/core/InteractionContainer.cpp000066400000000000000000000117351324306050200207530ustar00rootroot00000000000000// 2008 © Sergei Dorofeenko // 2009,2010 © Václav Šmilauer #include "InteractionContainer.hpp" #include "Scene.hpp" #ifdef YADE_OPENMP #include #endif CREATE_LOGGER(InteractionContainer); // begin internal functions bool InteractionContainer::insert(const shared_ptr& i){ assert(bodies); boost::mutex::scoped_lock lock(drawloopmutex); Body::id_t id1=i->getId1(); Body::id_t id2=i->getId2(); if (id1>id2) swap(id1,id2); assert((Body::id_t)bodies->size()>id1); // the bodies must exist already assert((Body::id_t)bodies->size()>id2); const shared_ptr& b1=(*bodies)[id1]; const shared_ptr& b2=(*bodies)[id2]; if(!b1->intrs.insert(Body::MapId2IntrT::value_type(id2,i)).second) return false; // already exists if(!b2->intrs.insert(Body::MapId2IntrT::value_type(id1,i)).second) return false; linIntrs.resize(++currSize); // currSize updated linIntrs[currSize-1]=i; // assign last element i->linIx=currSize-1; // store the index back-reference in the interaction (so that it knows how to erase/move itself) const shared_ptr& scene=Omega::instance().getScene(); i->iterBorn=scene->iter; return true; } void InteractionContainer::clear(){ assert(bodies); boost::mutex::scoped_lock lock(drawloopmutex); FOREACH(const shared_ptr& b, *bodies) { if (b) b->intrs.clear(); } linIntrs.clear(); currSize=0; dirty=true; } bool InteractionContainer::erase(Body::id_t id1,Body::id_t id2, int linPos){ assert(bodies); boost::mutex::scoped_lock lock(drawloopmutex); if (id1>id2) swap(id1,id2); if(id2>=(Body::id_t)bodies->size()) return false; const shared_ptr& b1((*bodies)[id1]); const shared_ptr& b2((*bodies)[id2]); int linIx=-1; if(!b1) linIx=linPos; else { Body::MapId2IntrT::iterator I(b1->intrs.find(id2)); if(I==b1->intrs.end()) linIx=linPos; else { linIx=I->second->linIx; assert(linIx==linPos); //erase from body, we also erase from linIntrs below b1->intrs.erase(I); if (b2) { Body::MapId2IntrT::iterator I2(b2->intrs.find(id1)); if (not(I2==b2->intrs.end())) { b2->intrs.erase(I2); } } } } if(linIx<0) { LOG_ERROR("InteractionContainer::erase: attempt to delete interaction with a deleted body (the definition of linPos in the call to erase() should fix the problem) for ##"+boost::lexical_cast(id1)+"+"+boost::lexical_cast(id2)); return false;} // iid is not the last element; we have to move last one to its place if (linIx<(int)currSize-1) { linIntrs[linIx]=linIntrs[currSize-1]; linIntrs[linIx]->linIx=linIx; // update the back-reference inside the interaction } // in either case, last element can be removed now linIntrs.resize(--currSize); // currSize updated return true; } const shared_ptr& InteractionContainer::find(Body::id_t id1,Body::id_t id2){ assert(bodies); if (id1>id2) swap(id1,id2); // those checks could be perhaps asserts, but pyInteractionContainer has no access to the body container... if(id2>=(Body::id_t)bodies->size()){ empty=shared_ptr(); return empty; } const shared_ptr& b1((*bodies)[id1]); if(!b1) { empty=shared_ptr(); return empty; } Body::MapId2IntrT::iterator I(b1->intrs.find(id2)); if (I!=b1->intrs.end()) return I->second; else { empty=shared_ptr(); return empty; } } bool InteractionContainer::insert(Body::id_t id1,Body::id_t id2) { shared_ptr i(new Interaction(id1,id2) ); return insert(i); } void InteractionContainer::requestErase(Body::id_t id1, Body::id_t id2){ const shared_ptr I=find(id1,id2); if(!I) return; I->reset(); } void InteractionContainer::requestErase(const shared_ptr& I){ I->reset(); } void InteractionContainer::requestErase(Interaction* I){ I->reset(); } void InteractionContainer::eraseNonReal(){ FOREACH(const shared_ptr& i, *this) if(!i->isReal()) this->erase(i->getId1(),i->getId2(),i->linIx); } // compare interaction based on their first id struct compPtrInteraction{ bool operator() (const shared_ptr& i1, const shared_ptr& i2) const { return (*i1)<(*i2); } }; void InteractionContainer::preSave(InteractionContainer&){ for(const shared_ptr& I : *this) { if(I->geom || I->phys) interaction.push_back(I); // since requestErase'd interactions have no interaction physics/geom, they are not saved } if(serializeSorted) std::sort(interaction.begin(),interaction.end(),compPtrInteraction()); } void InteractionContainer::postSave(InteractionContainer&){ interaction.clear(); } void InteractionContainer::preLoad(InteractionContainer&){ interaction.clear(); } void InteractionContainer::postLoad__calledFromScene(const shared_ptr& bb){ bodies=&bb->body; clear(); FOREACH(const shared_ptr& I, interaction){ Body::id_t id1=I->getId1(), id2=I->getId2(); if (!(*bodies)[id1] || !(*bodies)[id2]) { return; } else { insert(I); } } interaction.clear(); } trunk-2018.02b/core/InteractionContainer.hpp000066400000000000000000000155641324306050200207640ustar00rootroot00000000000000// 2004 © Olivier Galizzi // 2004 © Janek Kozicki // 2010 © Václav Šmilauer #pragma once #include #include #ifdef YADE_OPENMP #include #endif #include #include /* This InteractionContainer implementation has reference to the body container and stores interactions in 2 places: * Internally in a std::vector; that allows for const-time linear traversal. Each interaction internally holds back-reference to the position in this container in Interaction::linIx. * Inside Body::intrs (in the body with min(id1,id2)). Both must be kep in sync, which is handled by insert & erase methods. It was originally written by 2008 © Sergei Dorofeenko , later devirtualized and put here. Alternative implementations of InteractionContainer should implement the same API. Due to performance reasons, no base class with virtual methods defining such API programatically is defined (it could be possible to create class template for this, though). Future (?): * the linear vector might be removed; in favor of linear traversal of bodies by their subdomains, then traversing the map in each body. If the previous point would come to realization, half of the interactions would have to be skipped explicitly in such a case. */ class InteractionContainer: public Serializable{ private: typedef vector > ContainerT; // linear array of container interactions ContainerT linIntrs; // pointer to body container, since each body holds (some) interactions // this must always point to scene->bodies->body const BodyContainer::ContainerT* bodies; // always in sync with intrs.size(), to avoid that function call size_t currSize; shared_ptr empty; // used only during serialization/deserialization vector > interaction; public: // flag for notifying the collider that persistent data should be invalidated bool dirty; // required by the class factory... :-| InteractionContainer(): currSize(0),dirty(false),serializeSorted(false),iterColliderLastRun(-1){ bodies=NULL; } void clear(); // iterators typedef ContainerT::iterator iterator; typedef ContainerT::const_iterator const_iterator; iterator begin(){return linIntrs.begin();} iterator end() {return linIntrs.end();} const_iterator begin() const {return linIntrs.begin();} const_iterator end() const {return linIntrs.end();} // insertion/deletion bool insert(Body::id_t id1,Body::id_t id2); bool insert(const shared_ptr& i); //3rd parameter is used to remove I from linIntrs (in conditionalyEraseNonReal()) when body b1 has been removed bool erase(Body::id_t id1,Body::id_t id2,int linPos); const shared_ptr& find(Body::id_t id1,Body::id_t id2); inline bool found(const Body::id_t& id1,const Body::id_t& id2){ assert(bodies); if(id2>=(Body::id_t)bodies->size() or (id1 == id2)) { return false; } else { if (id1>id2) { return (*bodies)[id2]->intrs.count(id1); } else { return (*bodies)[id1]->intrs.count(id2); } } } // index access shared_ptr& operator[](size_t id){return linIntrs[id];} const shared_ptr& operator[](size_t id) const { return linIntrs[id];} size_t size(){ return currSize; } // simulation API //! Erase all non-real (in term of Interaction::isReal()) interactions void eraseNonReal(); // mutual exclusion to avoid crashes in the rendering loop boost::mutex drawloopmutex; // sort interactions before serializations; useful if comparing XML files from different runs (false by default) bool serializeSorted; // iteration number when the collider was last run; set by the collider, if it wants interactions that were not encoutered in that step to be deleted by InteractionLoop (such as SpatialQuickSortCollider). Other colliders (such as InsertionSortCollider) set it it -1, which is the default long iterColliderLastRun; //! Ask for erasing the interaction given (from the constitutive law); this resets the interaction (to the initial=potential state) and collider should traverse potential interactions to decide whether to delete them completely or keep them potential void requestErase(Body::id_t id1, Body::id_t id2); void requestErase(const shared_ptr& I); void requestErase(Interaction* I); /*! Traverse all interactions and erase them if they are not real and the (T*)->shouldBeErased(id1,id2) return true, or if body(id1) has been deleted Class using this interface (which is presumably a collider) must define the bool shouldBeErased(Body::id_t, Body::id_t) const */ template size_t conditionalyEraseNonReal(const T& t, Scene* rb){ // beware iterators here, since erase is invalidating them. We need to iterate carefully, and keep in mind that erasing one interaction is moving the last one to the current position. // For the parallel flavor we build the list to be erased in parallel, then it is erased sequentially. Still significant speedup since checking bounds is the most expensive part. #ifdef YADE_OPENMP if (omp_get_max_threads()<=1) { #endif size_t initSize=currSize; for (size_t linPos=0; linPos& i=linIntrs[linPos]; if(!i->isReal() && t.shouldBeErased(i->getId1(),i->getId2(),rb)) erase(i->getId1(),i->getId2(),linPos); else linPos++;} return initSize-currSize; #ifdef YADE_OPENMP } else { unsigned nThreads= omp_get_max_threads(); assert(nThreads>0); std::vector >toErase; toErase.resize(nThreads,std::vector()); for (unsigned kk=0; kk& i=linIntrs[linPos]; if(!i->isReal() && t.shouldBeErased(i->getId1(),i->getId2(),rb)) toErase[omp_get_thread_num()].push_back(Vector3i(i->getId1(),i->getId2(),linPos)) ; } for (int kk=nThreads-1; kk>=0; kk--) for (int ii(toErase[kk].size()-1); ii>=0;ii--) erase(toErase[kk][ii][0],toErase[kk][ii][1],toErase[kk][ii][2]); return initSize-currSize; } #endif } // we must call Scene's ctor (and from Scene::postLoad), since we depend on the existing BodyContainer at that point. void postLoad__calledFromScene(const shared_ptr&); void preLoad(InteractionContainer&); void preSave(InteractionContainer&); void postSave(InteractionContainer&); REGISTER_ATTRIBUTES(Serializable,(interaction)(serializeSorted)(dirty)); REGISTER_CLASS_AND_BASE(InteractionContainer,Serializable); DECLARE_LOGGER; }; REGISTER_SERIALIZABLE(InteractionContainer); trunk-2018.02b/core/Material.cpp000066400000000000000000000016261324306050200163650ustar00rootroot00000000000000#include #include #include const shared_ptr Material::byId(int id, Scene* w_){ Scene* w=w_?w_:Omega::instance().getScene().get(); assert(id>=0 && (size_t)idmaterials.size()); assert(w->materials[id]->id == id); return w->materials[id]; } const shared_ptr Material::byLabel(const std::string& label, Scene* w_){ Scene* w=w_?w_:Omega::instance().getScene().get(); FOREACH(const shared_ptr& m, w->materials){ if(m->label == label) return m; } throw std::runtime_error(("No material labeled `"+label+"'.").c_str()); } const int Material::byLabelIndex(const std::string& label, Scene* w_){ Scene* w=w_?w_:Omega::instance().getScene().get(); size_t iMax=w->materials.size(); for(size_t i=0; imaterials[i]->label==label) return i; } throw std::runtime_error(("No material labeled `"+label+"'.").c_str()); } trunk-2018.02b/core/Material.hpp000066400000000000000000000054121324306050200163670ustar00rootroot00000000000000// 2009 © Václav Šmilauer #pragma once #include #include #include #include class Scene; /*! Material properties associated with a body. Historical note: this used to be part of the PhysicalParameters class. The other data are now in the State class. */ class Material: public Serializable, public Indexable{ public: virtual ~Material() {}; //! Function to return empty default-initialized instance of State that // is supposed to go along with this Material. Don't override unless you need // something else than basic State. virtual shared_ptr newAssocState() const { return shared_ptr(new State); } /*! Function that returns true if given State instance is what this material expects. Base Material class has no requirements, but the check would normally look like this: return (bool)dynamic_cast state; */ virtual bool stateTypeOk(State*) const { return true; } static const shared_ptr byId(int id, Scene* scene=NULL); static const shared_ptr byId(int id, shared_ptr scene) {return byId(id,scene.get());} static const shared_ptr byLabel(const std::string& label, Scene* scene=NULL); static const shared_ptr byLabel(const std::string& label, shared_ptr scene) {return byLabel(label,scene.get());} // return index of material, given its label static const int byLabelIndex(const std::string& label, Scene* scene=NULL); YADE_CLASS_BASE_DOC_ATTRS_CTOR_PY(Material,Serializable,"Material properties of a :yref:`body`.", ((int,id,((void)"not shared",-1),Attr::readonly,"Numeric id of this material; is non-negative only if this Material is shared (i.e. in O.materials), -1 otherwise. This value is set automatically when the material is inserted to the simulation via :yref:`O.materials.append`. (This id was necessary since before boost::serialization was used, shared pointers were not tracked properly; it might disappear in the future)")) ((string,label,,,"Textual identifier for this material; can be used for shared materials lookup in :yref:`MaterialContainer`.")) ((Real,density,1000,,"Density of the material [kg/m³]")), /* ctor */, /*py*/ .def("newAssocState",&Material::newAssocState,"Return new :yref:`State` instance, which is associated with this :yref:`Material`. Some materials have special requirement on :yref:`Body::state` type and calling this function when the body is created will ensure that they match. (This is done automatically if you use utils.sphere, … functions from python).") YADE_PY_TOPINDEXABLE(Material) ); REGISTER_INDEX_COUNTER(Material); }; REGISTER_SERIALIZABLE(Material); trunk-2018.02b/core/Omega.cpp000066400000000000000000000234321324306050200156560ustar00rootroot00000000000000/************************************************************************* * Copyright (C) 2004 by Olivier Galizzi * * olivier.galizzi@imag.fr * * Copyright (C) 2004 by Janek Kozicki * * cosurgi@berlios.de * * * * This program is free software; it is licensed under the terms of the * * GNU General Public License v2 or later. See file LICENSE for details. * *************************************************************************/ #include "Omega.hpp" #include "Scene.hpp" #include "TimeStepper.hpp" #include "ThreadRunner.hpp" #include #include #include #include #include #include #include #include class RenderMutexLock: public boost::mutex::scoped_lock{ public: RenderMutexLock(): boost::mutex::scoped_lock(Omega::instance().renderMutex){} ~RenderMutexLock(){} }; CREATE_LOGGER(Omega); SINGLETON_SELF(Omega); const map& Omega::getDynlibsDescriptor(){return dynlibs;} const shared_ptr& Omega::getScene(){return scenes.at(currentSceneNb);} void Omega::resetCurrentScene(){ RenderMutexLock lock; scenes.at(currentSceneNb) = shared_ptr(new Scene);} void Omega::resetScene(){ resetCurrentScene(); } void Omega::resetAllScenes(){ RenderMutexLock lock; scenes.resize(1); scenes[0] = shared_ptr(new Scene); currentSceneNb=0; } int Omega::addScene(){ scenes.push_back(shared_ptr(new Scene)); return scenes.size()-1; } void Omega::switchToScene(int i) { if (i<0 || i>=int(scenes.size())) { LOG_ERROR("Scene "<(tmpFileCounter++); } void Omega::reset(){ stop(); init(); } void Omega::init(){ sceneFile=""; resetAllScenes(); sceneAnother=shared_ptr(new Scene); timeInit(); createSimulationLoop(); } void Omega::timeInit(){ startupLocalTime=boost::posix_time::microsec_clock::local_time(); } void Omega::createSimulationLoop(){ simulationLoop=shared_ptr(new ThreadRunner(&simulationFlow_)); } void Omega::stop(){ LOG_DEBUG(""); if (simulationLoop&&simulationLoop->looping())simulationLoop->stop(); if (simulationLoop) simulationLoop=shared_ptr(); } /* WARNING: even a single simulation step is run asynchronously; the call will return before the iteration is finished. */ void Omega::step(){ if (simulationLoop){ simulationLoop->spawnSingleAction(); } } void Omega::run(){ if(!simulationLoop){LOG_ERROR("No Omega::simulationLoop? Creating one (please report bug)."); createSimulationLoop(); } if (simulationLoop && !simulationLoop->looping()){ simulationLoop->start(); } } void Omega::pause(){ if (simulationLoop && simulationLoop->looping()){ simulationLoop->stop(); } } bool Omega::isRunning(){ if(simulationLoop) return simulationLoop->looping(); else return false; } void Omega::buildDynlibDatabase(const vector& dynlibsList){ LOG_DEBUG("called with "< pythonables; FOREACH(string name, dynlibsList){ shared_ptr f; try { LOG_DEBUG("Factoring plugin "<(f)).get()!=0); for(int i=0;igetBaseClassNumber();i++){ dynlibs[name].baseClasses.insert(f->getBaseClassName(i)); } if(dynlibs[name].isSerializable) pythonables.push_back(name); } catch (std::runtime_error& e){ /* FIXME: this catches all errors! Some of them are not harmful, however: * when a class is not factorable, it is OK to skip it; */ } } // handle Serializable specially //Serializable().pyRegisterClass(wrapperScope); /* python classes must be registered such that base classes come before derived ones; for now, just loop until we succeed; proper solution will be to build graphs of classes and traverse it from the top. It will be done once all classes are pythonable. */ for(int i=0; i<100 && pythonables.size()>0; i++){ if(getenv("YADE_DEBUG")) cerr< done; for(std::list::iterator I=pythonables.begin(); I!=pythonables.end(); ){ shared_ptr s=boost::static_pointer_cast(ClassFactory::instance().createShared(*I)); try{ if(getenv("YADE_DEBUG")) cerr<<"{{"<<*I<<"}}"; s->pyRegisterClass(wrapperScope); std::list::iterator prev=I++; pythonables.erase(prev); } catch (...){ if(getenv("YADE_DEBUG")){ cerr<<"["<<*I<<"]"; PyErr_Print(); } boost::python::handle_exception(); I++; } } } map::iterator dli = dynlibs.begin(); map::iterator dliEnd = dynlibs.end(); for( ; dli!=dliEnd ; ++dli){ set::iterator bci = (*dli).second.baseClasses.begin(); set::iterator bciEnd = (*dli).second.baseClasses.end(); for( ; bci!=bciEnd ; ++bci){ string name = *bci; if (name=="Dispatcher1D" || name=="Dispatcher2D") (*dli).second.baseClasses.insert("Dispatcher"); else if (name=="Functor1D" || name=="Functor2D") (*dli).second.baseClasses.insert("Functor"); else if (name=="Serializable") (*dli).second.baseClasses.insert("Factorable"); else if (name!="Factorable" && name!="Indexable") { shared_ptr f = ClassFactory::instance().createShared(name); for(int i=0;igetBaseClassNumber();i++) dynlibs[name].baseClasses.insert(f->getBaseClassName(i)); } } } } bool Omega::isInheritingFrom(const string& className, const string& baseClassName){ return (dynlibs[className].baseClasses.find(baseClassName)!=dynlibs[className].baseClasses.end()); } bool Omega::isInheritingFrom_recursive(const string& className, const string& baseClassName){ if (dynlibs[className].baseClasses.find(baseClassName)!=dynlibs[className].baseClasses.end()) return true; FOREACH(const string& parent,dynlibs[className].baseClasses){ if(isInheritingFrom_recursive(parent,baseClassName)) return true; } return false; } void Omega::loadPlugins(vector pluginFiles){ FOREACH(const string& plugin, pluginFiles){ LOG_DEBUG("Loading plugin "<& plugins(ClassFactory::instance().pluginClasses); plugins.sort(); plugins.unique(); buildDynlibDatabase(vector(plugins.begin(),plugins.end())); } void Omega::loadSimulation(const string& f, bool quiet){ bool isMem=boost::algorithm::starts_with(f,":memory:"); if(!isMem && !boost::filesystem::exists(f)) throw runtime_error("Simulation file to load doesn't exist: "+f); if(isMem && memSavedSimulations.count(f)==0) throw runtime_error("Cannot load nonexistent memory-saved simulation "+f); if(!quiet) LOG_INFO("Loading file "+f); shared_ptr& scene = scenes[currentSceneNb]; { stop(); // stop current simulation if running resetScene(); RenderMutexLock lock; if(isMem){ istringstream iss(memSavedSimulations[f]); yade::ObjectIO::load(iss,"scene",scene); } else { yade::ObjectIO::load(f,"scene",scene); } } if(scene->getClassName()!="Scene") throw logic_error("Wrong file format (scene is not a Scene!?) in "+f); sceneFile=f; timeInit(); //Add zero-force to the youngest body to be sure ForceContainer is large enough. const int _sz = scene->bodies->size(); for(Body::id_t _id=0; _id<_sz; _id++) { if((&scene->bodies)[_id]) { scene->forces.addForce(_id, Vector3r::Zero()); break; } } if(!quiet) LOG_DEBUG("Simulation loaded"); } void Omega::saveSimulation(const string& f, bool quiet){ if(f.size()==0) throw runtime_error("f of file to save has zero length."); if(!quiet) LOG_INFO("Saving file " << f); shared_ptr& scene = scenes[currentSceneNb]; if(boost::algorithm::starts_with(f,":memory:")){ if(memSavedSimulations.count(f)>0 && !quiet) LOG_INFO("Overwriting in-memory saved simulation "<(oss,"scene",scene); memSavedSimulations[f]=oss.str(); } else { yade::ObjectIO::save(f,"scene",scene); } sceneFile=f; } trunk-2018.02b/core/Omega.hpp000066400000000000000000000067601324306050200156700ustar00rootroot00000000000000/************************************************************************* * Copyright (C) 2004 by Olivier Galizzi * * olivier.galizzi@imag.fr * * Copyright (C) 2004 by Janek Kozicki * * cosurgi@berlios.de * * * * This program is free software; it is licensed under the terms of the * * GNU General Public License v2 or later. See file LICENSE for details. * *************************************************************************/ #pragma once #include #include #include #include #include #include #include #include #include #include "SimulationFlow.hpp" #ifndef FOREACH # define FOREACH BOOST_FOREACH #endif class Scene; class ThreadRunner; struct DynlibDescriptor{ set baseClasses; bool isSerializable; }; class Omega: public Singleton{ shared_ptr simulationLoop; SimulationFlow simulationFlow_; map dynlibs; // FIXME : should store that in ClassFactory ? void buildDynlibDatabase(const vector& dynlibsList); // FIXME - maybe in ClassFactory ? vector > scenes; int currentSceneNb; shared_ptr sceneAnother; // used for temporarily running different simulation, in Omega().switchscene() boost::posix_time::ptime startupLocalTime; map memSavedSimulations; // to avoid accessing simulation when it is being loaded (should avoid crashes with the UI) boost::mutex loadingSimulationMutex; boost::mutex tmpFileCounterMutex; long tmpFileCounter; std::string tmpFileDir; public: // management, not generally useful void init(); void reset(); void timeInit(); void initTemps(); void cleanupTemps(); const map& getDynlibsDescriptor(); void loadPlugins(vector pluginFiles); bool isInheritingFrom(const string& className, const string& baseClassName ); bool isInheritingFrom_recursive(const string& className, const string& baseClassName ); void createSimulationLoop(); bool hasSimulationLoop(){return (bool)(simulationLoop);} string gdbCrashBatch; char** origArgv; int origArgc; // do not change by hand /* Mutex for: * 1. GLViewer::paintGL (deffered lock: if fails, no GL painting is done) * 2. other threads that wish to manipulate GL * 3. Omega when substantial changes to the scene are being made (bodies being deleted, simulation loaded etc) so that GL doesn't access those and crash */ boost::try_mutex renderMutex; void run(); void pause(); void step(); void stop(); // resets the simulationLoop bool isRunning(); std::string sceneFile; // updated at load/save automatically void loadSimulation(const string& name, bool quiet=false); void saveSimulation(const string& name, bool quiet=false); void resetScene(); void resetCurrentScene(); void resetAllScenes(); const shared_ptr& getScene(); int addScene(); void switchToScene(int i); //! Return unique temporary filename. May be deleted by the user; if not, will be deleted at shutdown. string tmpFilename(); Real getRealTime(); boost::posix_time::time_duration getRealTime_duration(); // configuration directory used for logging config and possibly other things std::string confDir; DECLARE_LOGGER; Omega(){ LOG_DEBUG("Constructing Omega."); } ~Omega(){} FRIEND_SINGLETON(Omega); friend class pyOmega; }; trunk-2018.02b/core/PartialEngine.hpp000066400000000000000000000017431324306050200173560ustar00rootroot00000000000000/************************************************************************* * Copyright (C) 2004 by Olivier Galizzi * * olivier.galizzi@imag.fr * * * * This program is free software; it is licensed under the terms of the * * GNU General Public License v2 or later. See file LICENSE for details. * *************************************************************************/ #pragma once #include #include #include class PartialEngine: public Engine{ public: virtual ~PartialEngine() {}; YADE_CLASS_BASE_DOC_ATTRS(PartialEngine,Engine,"Engine affecting only particular bodies in the simulation, defined by :yref:`ids attribute`.", ((std::vector,ids,,,":yref:`Ids` list of bodies affected by this PartialEngine.")) ); }; REGISTER_SERIALIZABLE(PartialEngine); trunk-2018.02b/core/Scene.cpp000066400000000000000000000212001324306050200156520ustar00rootroot00000000000000/************************************************************************* * Copyright (C) 2004 by Olivier Galizzi * * olivier.galizzi@imag.fr * * Copyright (C) 2004 by Janek Kozicki * * cosurgi@berlios.de * * * * This program is free software; it is licensed under the terms of the * * GNU General Public License v2 or later. See file LICENSE for details. * *************************************************************************/ #include"Scene.hpp" #include #include #include #include #include #include #include #include #include namespace py=boost::python; YADE_PLUGIN((Scene)); CREATE_LOGGER(Scene); // should be elsewhere, probably bool TimingInfo::enabled=false; void Scene::fillDefaultTags(){ // fill default tags struct passwd* pw; char hostname[hostNameMax]; gethostname(hostname,hostNameMax); pw=getpwuid(geteuid()); if(!pw) throw runtime_error("getpwuid(geteuid()) failed!"); // a few default tags // real name: will have all non-ASCII characters replaced by ? since serialization doesn't handle that // the standard GECOS format is Real Name,,, - first comma and after will be discarded string gecos(pw->pw_gecos), gecos2; size_t p=gecos.find(","); if(p!=string::npos) boost::algorithm::erase_tail(gecos,gecos.size()-p); for(size_t i=0;ipw_name)+"@"+hostname+")"," ","~")); tags.push_back(string("isoTime="+boost::posix_time::to_iso_string(boost::posix_time::second_clock::local_time()))); string id=boost::posix_time::to_iso_string(boost::posix_time::second_clock::local_time())+"p"+boost::lexical_cast(getpid()); tags.push_back("id="+id); tags.push_back("d.id="+id); tags.push_back("id.d="+id); // tags.push_back("revision="+py::extract(py::import("yade.config").attr("revision"))());; } void Scene::postLoad(Scene&){ // update the interaction container; must be done in Scene ctor as well; important! interactions->postLoad__calledFromScene(bodies); // this might be removed at some point, since it is checked by regression tests now FOREACH(const shared_ptr& b, *bodies){ if(!b || !b->material || b->material->id<0) continue; // not a shared material if(b->material!=materials[b->material->id]) throw std::logic_error("Scene::postLoad: Internal inconsistency, shared materials not preserved when loaded; please report bug."); } } void Scene::moveToNextTimeStep(){ if(runInternalConsistencyChecks){ runInternalConsistencyChecks=false; checkStateTypes(); } // substepping or not, update engines from _nextEngines, if defined, at the beginning of step // subStep can be 0, which happens if simulations is saved in the middle of step (without substepping) // this assumes that prologue will not set _nextEngines, which is safe hopefully if(!_nextEngines.empty() && (subStep<0 || (subStep<=0 && !subStepping))){ engines=_nextEngines; _nextEngines.clear(); // hopefully this will not break in some margin cases (subStepping with setting _nextEngines and such) subStep=-1; } if(!subStepping && subStep<0){ /* set substep to 0 during the loop, so that engines/nextEngines handler know whether we are inside the loop currently */ subStep=0; // ** 1. ** prologue if(isPeriodic) cell->integrateAndUpdate(dt); //forces.reset(); // uncomment if ForceResetter is removed const bool TimingInfo_enabled=TimingInfo::enabled; // cache the value, so that when it is changed inside the step, the engine that was just running doesn't get bogus values TimingInfo::delta last=TimingInfo::getNow(); // actually does something only if TimingInfo::enabled, no need to put the condition here // ** 2. ** engines FOREACH(const shared_ptr& e, engines){ e->scene=this; if(e->dead || !e->isActivated()) continue; e->action(); if(TimingInfo_enabled) {TimingInfo::delta now=TimingInfo::getNow(); e->timingInfo.nsec+=now-last; e->timingInfo.nExec+=1; last=now;} } // ** 3. ** epilogue // Calculation speed if (iter==0) { //For the first time prevTime = boost::posix_time::microsec_clock::local_time(); } else { boost::posix_time::ptime timeNow = boost::posix_time::microsec_clock::local_time(); boost::posix_time::time_duration duration = timeNow - prevTime; long dif = duration.total_microseconds(); SpeedElements(iter%nSpeedIter,0)=1000000.0 / dif; speed = SpeedElements.mean(); prevTime = timeNow; } iter++; time+=dt; subStep=-1; } else { /* IMPORTANT: take care to copy EXACTLY the same sequence as is in the block above !! */ if(TimingInfo::enabled){ TimingInfo::enabled=false; LOG_INFO("O.timingEnabled disabled, since O.subStepping is used."); } if(subStep<-1 || subStep>(int)engines.size()){ LOG_ERROR("Invalid value of Scene::subStep ("<=-1 && subs<=(int)engines.size()); // ** 1. ** prologue if(subs==-1){ if(isPeriodic) cell->integrateAndUpdate(dt); } // ** 2. ** engines else if(subs>=0 && subs<(int)engines.size()){ const shared_ptr& e(engines[subs]); e->scene=this; if(!e->dead && e->isActivated()) e->action(); } // ** 3. ** epilogue else if(subs==(int)engines.size()){ iter++; time+=dt; /* gives -1 along with the increment afterwards */ subStep=-2; } // (?!) else { /* never reached */ assert(false); } } subStep++; // if not substepping, this will make subStep=-2+1=-1, which is what we want } } shared_ptr Scene::engineByName(const string& s){ FOREACH(shared_ptr e, engines){ if(e->getClassName()==s) return e; } return shared_ptr(); } bool Scene::timeStepperPresent(){ int n=0; FOREACH(const shared_ptr&e, engines){ if(dynamic_cast(e.get())) n++; } if(n>1) throw std::runtime_error(string("Multiple ("+boost::lexical_cast(n)+") TimeSteppers in the simulation?!").c_str()); return n>0; } bool Scene::timeStepperActive(){ int n=0; bool ret=false; FOREACH(const shared_ptr&e, engines){ TimeStepper* ts=dynamic_cast(e.get()); if(ts) { ret=ts->active; n++; } } if(n>1) throw std::runtime_error(string("Multiple ("+boost::lexical_cast(n)+") TimeSteppers in the simulation?!").c_str()); return ret; } bool Scene::timeStepperActivate(bool a){ int n=0; FOREACH(const shared_ptr e, engines){ TimeStepper* ts=dynamic_cast(e.get()); if(ts) { ts->setActive(a); n++; } } if(n>1) throw std::runtime_error(string("Multiple ("+boost::lexical_cast(n)+") TimeSteppers in the simulation?!").c_str()); return n>0; } void Scene::checkStateTypes(){ FOREACH(const shared_ptr& b, *bodies){ if(!b || !b->material) continue; if(b->material && !b->state) throw std::runtime_error("Body #"+boost::lexical_cast(b->getId())+": has Body::material, but NULL Body::state."); if(!b->material->stateTypeOk(b->state.get())){ throw std::runtime_error("Body #"+boost::lexical_cast(b->getId())+": Body::material type "+b->material->getClassName()+" doesn't correspond to Body::state type "+b->state->getClassName()+" (should be "+b->material->newAssocState()->getClassName()+" instead)."); } } } void Scene::updateBound(){ if(!bound) bound=shared_ptr(new Bound); const Real& inf=std::numeric_limits::infinity(); Vector3r mx(-inf,-inf,-inf); Vector3r mn(inf,inf,inf); FOREACH(const shared_ptr& b, *bodies){ if(!b) continue; if(b->bound){ for(int i=0; i<3; i++){ if(!std::isinf(b->bound->max[i])) mx[i]=max(mx[i],b->bound->max[i]); if(!std::isinf(b->bound->min[i])) mn[i]=min(mn[i],b->bound->min[i]); } } else { mx=mx.cwiseMax(b->state->pos); mn=mn.cwiseMin(b->state->pos); } } bound->min=mn; bound->max=mx; } trunk-2018.02b/core/Scene.hpp000066400000000000000000000144511324306050200156710ustar00rootroot00000000000000/************************************************************************* * Copyright (C) 2004 by Olivier Galizzi * * olivier.galizzi@imag.fr * * Copyright (C) 2004 by Janek Kozicki * * cosurgi@berlios.de * * * * This program is free software; it is licensed under the terms of the * * GNU General Public License v2 or later. See file LICENSE for details. * *************************************************************************/ #pragma once #include #include #include #include #include #include #include #include #include #ifdef YADE_OPENMP #include #endif class Bound; #ifdef YADE_OPENGL class OpenGLRenderer; #endif class Scene: public Serializable{ const unsigned int hostNameMax = 255; public: //! Adds material to Scene::materials. It also sets id of the material accordingly and returns it. int addMaterial(shared_ptr m){ materials.push_back(m); m->id=(int)materials.size()-1; return m->id; } //! Checks that type of Body::state satisfies Material::stateTypeOk. Throws runtime_error if not. (Is called from BoundDispatcher the first time it runs) void checkStateTypes(); //! update our bound; used directly instead of a BoundFunctor, since we don't derive from Body anymore void updateBound(); // neither serialized, nor accessible from python (at least not directly) ForceContainer forces; // initialize tags (author, date, time) void fillDefaultTags(); // advance by one iteration by running all engines void moveToNextTimeStep(); /* Functions operating on TimeStepper; they all throw exception if there is more than 1 */ // return whether a TimeStepper is present bool timeStepperPresent(); // true if TimeStepper is present and active, false otherwise bool timeStepperActive(); // (de)activate TimeStepper; returns whether the operation was successful (i.e. whether a TimeStepper was found) bool timeStepperActivate(bool activate); static const int nSpeedIter = 10; //Number of iterations, which are taking into account for speed calculation Eigen::Matrix SpeedElements; //Array for saving speed-values for last "nSpeedIter"-iterations shared_ptr engineByName(const string& s); #ifdef YADE_LIQMIGRATION OpenMPVector addIntrs; //Array of added interactions, needed for liquid migration. OpenMPVector > delIntrs; //Array of deleted interactions, needed for liquid migration. #endif #ifdef YADE_OPENGL shared_ptr renderer; #endif void postLoad(Scene&); boost::posix_time::ptime prevTime; //Time value on the previous step YADE_CLASS_BASE_DOC_ATTRS_CTOR_PY(Scene,Serializable,"Object comprising the whole simulation.", ((Real,dt,1e-8,,"Current timestep for integration.")) ((long,iter,0,Attr::readonly,"Current iteration (computational step) number")) ((bool,subStepping,false,,"Whether we currently advance by one engine in every step (rather than by single run through all engines).")) ((int,subStep,-1,Attr::readonly,"Number of sub-step; not to be changed directly. -1 means to run loop prologue (cell integration), 0…n-1 runs respective engines (n is number of engines), n runs epilogue (increment step number and time.")) ((Real,time,0,Attr::readonly,"Simulation time (virtual time) [s]")) ((Real,speed,0,Attr::readonly,"Current calculation speed [iter/s]")) ((long,stopAtIter,0,,"Iteration after which to stop the simulation.")) ((Real,stopAtTime,0,,"Time after which to stop the simulation")) ((bool,isPeriodic,false,Attr::readonly,"Whether periodic boundary conditions are active.")) ((bool,trackEnergy,false,Attr::readonly,"Whether energies are being traced.")) ((bool,doSort,false,Attr::readonly,"Used, when new body is added to the scene.")) ((bool,runInternalConsistencyChecks,true,Attr::hidden,"Run internal consistency check, right before the very first simulation step.")) ((Body::id_t,selectedBody,-1,,"Id of body that is selected by the user")) ((list,tags,,,"Arbitrary key=value associations (tags like mp3 tags: author, date, version, description etc.)")) ((vector >,engines,,Attr::hidden,"Engines sequence in the simulation.")) ((vector >,_nextEngines,,Attr::hidden,"Engines to be used from the next step on; is returned transparently by O.engines if in the middle of the loop (controlled by subStep>=0).")) // NOTE: bodies must come before interactions, since InteractionContainer is initialized with a reference to BodyContainer::body ((shared_ptr,bodies,new BodyContainer,Attr::hidden,"Bodies contained in the scene.")) ((shared_ptr,interactions,new InteractionContainer,Attr::hidden,"All interactions between bodies.")) ((shared_ptr,energy,new EnergyTracker,Attr::hidden,"Energy values, if energy tracking is enabled.")) ((vector >,materials,,Attr::hidden,"Container of shared materials. Add elements using Scene::addMaterial, not directly. Do NOT remove elements from here unless you know what you are doing!")) ((shared_ptr,bound,,Attr::hidden,"Bounding box of the scene (only used for rendering and initialized if needed).")) ((shared_ptr,cell,new Cell,Attr::hidden,"Information on periodicity; only should be used if Scene::isPeriodic.")) ((vector >,miscParams,,Attr::hidden,"Store for arbitrary Serializable objects; will set static parameters during deserialization (primarily for GLDraw functors which otherwise have no attribute access)")) ((vector >,dispParams,,Attr::hidden,"'hash maps' of display parameters (since yade::serialization had no support for maps, emulate it via vector of strings in format key=value)")) , /*ctor*/ fillDefaultTags(); interactions->postLoad__calledFromScene(bodies); SpeedElements.Zero(); , /* py */ ); DECLARE_LOGGER; }; REGISTER_SERIALIZABLE(Scene); trunk-2018.02b/core/Shape.hpp000066400000000000000000000030531324306050200156700ustar00rootroot00000000000000/************************************************************************* * Copyright (C) 2004 by Olivier Galizzi * * olivier.galizzi@imag.fr * * * * This program is free software; it is licensed under the terms of the * * GNU General Public License v2 or later. See file LICENSE for details. * *************************************************************************/ #pragma once #include #include #include #define BV_FUNCTOR_CACHE class BoundFunctor; class InternalForceFunctor; class Shape: public Serializable, public Indexable { public: virtual ~Shape() {}; // vtable #ifdef BV_FUNCTOR_CACHE shared_ptr boundFunctor; #endif //! cache functor that are called for this type of DeformableElement. Used by FEInternalForceEngine shared_ptr internalforcefunctor; YADE_CLASS_BASE_DOC_ATTRS_CTOR_PY(Shape,Serializable,"Geometry of a body", ((Vector3r,color,Vector3r(1,1,1),,"Color for rendering (normalized RGB).")) ((bool,wire,false,,"Whether this Shape is rendered using color surfaces, or only wireframe (can still be overridden by global config of the renderer).")) ((bool,highlight,false,,"Whether this Shape will be highlighted when rendered.")), /*ctor*/, /*py*/ YADE_PY_TOPINDEXABLE(Shape) ); REGISTER_INDEX_COUNTER(Shape); }; REGISTER_SERIALIZABLE(Shape); trunk-2018.02b/core/SimulationFlow.cpp000066400000000000000000000022071324306050200175770ustar00rootroot00000000000000/************************************************************************* * Copyright (C) 2006 by Janek Kozicki * * cosurgi@berlios.de * * * * This program is free software; it is licensed under the terms of the * * GNU General Public License v2 or later. See file LICENSE for details. * *************************************************************************/ #include #include "SimulationFlow.hpp" #include "Scene.hpp" #include "Omega.hpp" CREATE_LOGGER(SimulationFlow); void SimulationFlow::singleAction() { Scene* scene=Omega::instance().getScene().get(); if (!scene) throw logic_error("SimulationFlow::singleAction: no Scene object?!"); if(scene->subStepping) { LOG_INFO("Sub-stepping disabled when running simulation continuously."); scene->subStepping=false; } scene->moveToNextTimeStep(); if(scene->stopAtIter>0 && scene->iter==scene->stopAtIter) setTerminate(true); if(scene->stopAtTime>0 && scene->time==scene->stopAtTime) setTerminate(true); }; trunk-2018.02b/core/SimulationFlow.hpp000066400000000000000000000012451324306050200176050ustar00rootroot00000000000000/************************************************************************* * Copyright (C) 2006 by Janek Kozicki * * cosurgi@berlios.de * * * * This program is free software; it is licensed under the terms of the * * GNU General Public License v2 or later. See file LICENSE for details. * *************************************************************************/ #pragma once #include "ThreadWorker.hpp" class SimulationFlow : public ThreadWorker { public: virtual void singleAction(); DECLARE_LOGGER; }; trunk-2018.02b/core/State.cpp000066400000000000000000000021201324306050200156750ustar00rootroot00000000000000// 2009 © Václav Šmilauer #include CREATE_LOGGER(State); void State::setDOFfromVector3r(Vector3r disp,Vector3r rot){ blockedDOFs=((disp[0]==1.0)?DOF_X :0)| ((disp[1]==1.0)?DOF_Y :0)| ((disp[2]==1.0)?DOF_Z :0)| ((rot [0]==1.0)?DOF_RX:0)| ((rot [1]==1.0)?DOF_RY:0)| ((rot [2]==1.0)?DOF_RZ:0); } std::string State::blockedDOFs_vec_get() const { std::string ret; #define _SET_DOF(DOF_ANY,ch) if((blockedDOFs & State::DOF_ANY)!=0) ret.push_back(ch); _SET_DOF(DOF_X,'x'); _SET_DOF(DOF_Y,'y'); _SET_DOF(DOF_Z,'z'); _SET_DOF(DOF_RX,'X'); _SET_DOF(DOF_RY,'Y'); _SET_DOF(DOF_RZ,'Z'); #undef _SET_DOF return ret; } void State::blockedDOFs_vec_set(const std::string& dofs){ blockedDOFs=0; for(char c : dofs) { #define _GET_DOF(DOF_ANY,ch) if(c==ch) { blockedDOFs|=State::DOF_ANY; continue; } _GET_DOF(DOF_X,'x'); _GET_DOF(DOF_Y,'y'); _GET_DOF(DOF_Z,'z'); _GET_DOF(DOF_RX,'X'); _GET_DOF(DOF_RY,'Y'); _GET_DOF(DOF_RZ,'Z'); #undef _GET_DOF } } trunk-2018.02b/core/State.hpp000066400000000000000000000117061324306050200157140ustar00rootroot00000000000000// 2009 © Václav Šmilauer #pragma once #include #include #include class State: public Serializable, public Indexable{ public: /// linear motion (references to inside se3) Vector3r& pos; /// rotational motion (reference to inside se3) Quaternionr& ori; //! mutex for updating the parameters from within the interaction loop (only used rarely) boost::mutex updateMutex; // bits for blockedDOFs enum {DOF_NONE=0,DOF_X=1,DOF_Y=2,DOF_Z=4,DOF_RX=8,DOF_RY=16,DOF_RZ=32}; //! shorthand for all DOFs blocked static const unsigned DOF_ALL=DOF_X|DOF_Y|DOF_Z|DOF_RX|DOF_RY|DOF_RZ; //! shorthand for all displacements blocked static const unsigned DOF_XYZ=DOF_X|DOF_Y|DOF_Z; //! shorthand for all rotations blocked static const unsigned DOF_RXRYRZ=DOF_RX|DOF_RY|DOF_RZ; //! Return DOF_* constant for given axis∈{0,1,2} and rotationalDOF∈{false(default),true}; e.g. axisDOF(0,true)==DOF_RX static unsigned axisDOF(int axis, bool rotationalDOF=false){return 1<<(axis+(rotationalDOF?3:0));} //! set DOFs according to two Vector3r arguments (blocked is when disp[i]==1.0 or rot[i]==1.0) void setDOFfromVector3r(Vector3r disp,Vector3r rot=Vector3r::Zero()); //! Getter of blockedDOFs for list of strings (e.g. DOF_X | DOR_RX | DOF_RZ → 'xXZ') std::string blockedDOFs_vec_get() const; //! Setter of blockedDOFs from string ('xXZ' → DOF_X | DOR_RX | DOF_RZ) void blockedDOFs_vec_set(const std::string& dofs); //! Return displacement (current-reference position) const Vector3r displ() const {return pos-refPos;} //! Return rotation (current-reference orientation, as Vector3r) const Vector3r rot() const { Quaternionr relRot=refOri.conjugate()*ori; AngleAxisr aa(relRot); return aa.axis()*aa.angle(); } // python access functions: pos and ori are references to inside Se3r and cannot be pointed to directly const Vector3r pos_get() const {return pos;} void pos_set(const Vector3r p) {pos=p;} const Quaternionr ori_get() const {return ori; } void ori_set(const Quaternionr o){ori=o;} YADE_CLASS_BASE_DOC_ATTRS_INIT_CTOR_PY(State,Serializable,"State of a body (spatial configuration, internal variables).", ((Se3r,se3,Se3r(Vector3r::Zero(),Quaternionr::Identity()),,"Position and orientation as one object.")) ((Vector3r,vel,Vector3r::Zero(),,"Current linear velocity.")) ((Real,mass,0,,"Mass of this body")) ((Vector3r,angVel,Vector3r::Zero(),,"Current angular velocity")) ((Vector3r,angMom,Vector3r::Zero(),,"Current angular momentum")) ((Vector3r,inertia,Vector3r::Zero(),,"Inertia of associated body, in local coordinate system.")) ((Vector3r,refPos,Vector3r::Zero(),,"Reference position")) ((Quaternionr,refOri,Quaternionr::Identity(),,"Reference orientation")) ((unsigned,blockedDOFs,,,"[Will be overridden]")) ((bool,isDamped,true,,"Damping in :yref:`Newtonintegrator` can be deactivated for individual particles by setting this variable to FALSE. E.g. damping is inappropriate for particles in free flight under gravity but it might still be applicable to other particles in the same simulation.")) ((Real,densityScaling,1,,"|yupdate| see :yref:`GlobalStiffnessTimeStepper::targetDt`.")) #ifdef YADE_SPH ((Real,rho, -1.0,, "Current density (only for SPH-model)")) // [Mueller2003], (12) ((Real,rho0,-1.0,, "Rest density (only for SPH-model)")) // [Mueller2003], (12) ((Real,press,0.0,, "Pressure (only for SPH-model)")) // [Mueller2003], (12) #endif #ifdef YADE_LIQMIGRATION ((Real,Vf, 0.0,, "Individual amount of liquid")) ((Real,Vmin, 0.0,, "Minimal amount of liquid")) #endif #ifdef YADE_DEFORM ((Real,dR, 0.0,, "Sphere deformation")) #endif , /* additional initializers */ ((pos,se3.position)) ((ori,se3.orientation)), /* ctor */, /*py*/ YADE_PY_TOPINDEXABLE(State) .add_property("blockedDOFs",&State::blockedDOFs_vec_get,&State::blockedDOFs_vec_set,"Degress of freedom where linear/angular velocity will be always constant (equal to zero, or to an user-defined value), regardless of applied force/torque. String that may contain 'xyzXYZ' (translations and rotations).") // references must be set using wrapper funcs .add_property("pos",&State::pos_get,&State::pos_set,"Current position.") .add_property("ori",&State::ori_get,&State::ori_set,"Current orientation.") .def("displ",&State::displ,"Displacement from :yref:`reference position` (:yref:`pos` - :yref:`refPos`)") .def("rot",&State::rot,"Rotation from :yref:`reference orientation` (as rotation vector)") #ifdef YADE_SPH .add_property("rho", &State::rho, "Returns the current density (only for SPH-model).") .add_property("rho0", &State::rho0,"Returns the rest density (only for SPH-model).") .add_property("press",&State::press,"Returns the pressure (only for SPH-model).") #endif ); REGISTER_INDEX_COUNTER(State); DECLARE_LOGGER; }; REGISTER_SERIALIZABLE(State); trunk-2018.02b/core/ThreadRunner.cpp000066400000000000000000000054641324306050200172340ustar00rootroot00000000000000/************************************************************************* * Copyright (C) 2006 by Janek Kozicki * * cosurgi@berlios.de * * * * This program is free software; it is licensed under the terms of the * * GNU General Public License v2 or later. See file LICENSE for details. * *************************************************************************/ #include #include "ThreadRunner.hpp" #include "ThreadWorker.hpp" #include #include #include CREATE_LOGGER(ThreadRunner); void ThreadRunner::run() { // this is the body of execution of separate thread boost::mutex::scoped_lock lock(m_runmutex); try{ workerThrew=false; while(looping()) { call(); if(m_thread_worker->shouldTerminate()){ stop(); return; } } } catch (std::exception& e){ LOG_FATAL("Exception occured: "<setTerminate(false); m_thread_worker->callSingleAction(); } void ThreadRunner::pleaseTerminate() { stop(); m_thread_worker->setTerminate(true); } void ThreadRunner::spawnSingleAction() { boost::mutex::scoped_lock boollock(m_boolmutex); boost::mutex::scoped_lock calllock(m_callmutex); if(m_looping) return; boost::function0 call( boost::bind( &ThreadRunner::call , this ) ); boost::thread th(call); } void ThreadRunner::start() { boost::mutex::scoped_lock lock(m_boolmutex); if(m_looping) return; m_looping=true; boost::function0 run( boost::bind( &ThreadRunner::run , this ) ); boost::thread th(run); } void ThreadRunner::stop() { if(!m_looping) return; boost::mutex::scoped_lock lock(m_boolmutex); m_looping=false; } bool ThreadRunner::looping() { boost::mutex::scoped_lock lock(m_boolmutex); return m_looping; } ThreadRunner::~ThreadRunner() { pleaseTerminate(); boost::mutex::scoped_lock runlock(m_runmutex); boost::mutex::scoped_lock calllock(m_callmutex); } trunk-2018.02b/core/ThreadRunner.hpp000066400000000000000000000053121324306050200172310ustar00rootroot00000000000000/************************************************************************* * Copyright (C) 2006 by Janek Kozicki * * cosurgi@berlios.de * * * * This program is free software; it is licensed under the terms of the * * GNU General Public License v2 or later. See file LICENSE for details. * *************************************************************************/ #pragma once #include /*! \brief ThreadRunner takes care of starting/stopping (executing) the ThreadWorker in the separate thread. It is achieved by either: - one execution of { ThreadWorker::singleAction(); } in separate thread - a loop { while(looping() ) ThreadWorker::singleAction(); } in separate thread Lifetime of ThreadRunner is guaranteed to be longer or equal to the lifetime of the separate thread of execution. The ThreadRunner owner must make sure that ThreadWorker has longer or equal lifetime than instance of ThreadRunner. Otherwise ThreadRunner will try to execute a dead object, which will lead to crash. Do not destroy immediately after call to singleAction(). Destructor can kick in before a separate thread starts, which will lead to a crash. User can explicitly ask the running thread to terminate execution. If the thread supports it, it will terminate. \note This code is reentrant. Simultaneous requests from other threads to start/stop or perform singleAction() are expected. So ThreadWorker(s) are running, while the user is interacting with the UI frontend (doesn't matter whether the UI is graphical, ncurses or any other). */ class ThreadWorker; class ThreadRunner { private : ThreadWorker* m_thread_worker; bool m_looping; boost::mutex m_boolmutex; boost::mutex m_callmutex; boost::mutex m_runmutex; void run(); void call(); DECLARE_LOGGER; public : ThreadRunner(ThreadWorker* c) : m_thread_worker(c), m_looping(false), workerThrew(false) {}; ~ThreadRunner(); /// perform ThreadWorker::singleAction() in separate thread void spawnSingleAction(); /// start doing singleAction() in a loop in separate thread void start(); /// stop the loop (changes the flag checked by looping() ) void stop(); /// kindly ask the separate thread to terminate void pleaseTerminate(); /// precondition for the loop started with start(). bool looping(); //! if true, workerException is copy of the exception thrown by the worker bool workerThrew; //! last exception thrown by the worker, if any std::exception workerException; }; trunk-2018.02b/core/ThreadWorker.cpp000066400000000000000000000032371324306050200172300ustar00rootroot00000000000000/************************************************************************* * Copyright (C) 2006 by Janek Kozicki * * cosurgi@berlios.de * * * * This program is free software; it is licensed under the terms of the * * GNU General Public License v2 or later. See file LICENSE for details. * *************************************************************************/ #include "ThreadWorker.hpp" void ThreadWorker::setTerminate(bool b) { boost::mutex::scoped_lock lock(m_mutex); m_should_terminate=b; }; bool ThreadWorker::shouldTerminate() { boost::mutex::scoped_lock lock(m_mutex); return m_should_terminate; }; void ThreadWorker::setProgress(float i) { boost::mutex::scoped_lock lock(m_mutex); m_progress=i; }; void ThreadWorker::setStatus(std::string s) { boost::mutex::scoped_lock lock(m_mutex); m_status=s; }; float ThreadWorker::progress() { boost::mutex::scoped_lock lock(m_mutex); return m_progress; }; std::string ThreadWorker::getStatus() { boost::mutex::scoped_lock lock(m_mutex); return m_status; }; void ThreadWorker::setReturnValue(boost::any a) { boost::mutex::scoped_lock lock(m_mutex); m_val = a; }; boost::any ThreadWorker::getReturnValue() { boost::mutex::scoped_lock lock(m_mutex); return m_val; }; bool ThreadWorker::done() { boost::mutex::scoped_lock lock(m_mutex); return m_done; }; void ThreadWorker::callSingleAction() { { boost::mutex::scoped_lock lock(m_mutex); m_done = false; } this->singleAction(); { boost::mutex::scoped_lock lock(m_mutex); m_done = true; } }; trunk-2018.02b/core/ThreadWorker.hpp000066400000000000000000000045541324306050200172400ustar00rootroot00000000000000/************************************************************************* * Copyright (C) 2006 by Janek Kozicki * * cosurgi@berlios.de * * * * This program is free software; it is licensed under the terms of the * * GNU General Public License v2 or later. See file LICENSE for details. * *************************************************************************/ #pragma once #include #include class ThreadRunner; /*! \brief ThreadWorker contains information about tasks to be performed when the separate thread is executed. */ class ThreadWorker // perhaps simulation steps, or stage? as it is a single stage // of the simulation, that consists of several steps // Update: it is more general now. simulation stages perhaps will be derived from this class { private: /// You should check out ThreadRunner, it is used for execution control of this class friend class ThreadRunner; bool m_should_terminate; bool m_done; boost::mutex m_mutex; boost::any m_val; float m_progress; std::string m_status; void callSingleAction(); protected: void setTerminate(bool); /// singleAction() can check whether someone asked for termination, and terminate if/when possible bool shouldTerminate(); /// if something must be returned, set the result using this method void setReturnValue(boost::any); /// if you feel monitored for progress, you can set it here: a value between 0.0 and 1.0 void setProgress(float); /// if you feel being monitored for what currently is done, set the message here void setStatus(std::string); /// derived classes must define this method, that's what is executed in separate thread virtual void singleAction() = 0; public: ThreadWorker() : m_should_terminate(false), m_done(false), m_progress(0) {}; virtual ~ThreadWorker() {}; /// Returns a value between 0.0 and 1.0. Useful for updating a progress bar. float progress(); // get_progress ? (pick a naming convention, efngh) /// You can display a message in GUI about what is the current work status std::string getStatus(); /// Check whether execution is finished, bool done(); /// then get the result. boost::any getReturnValue(); }; trunk-2018.02b/core/TimeStepper.hpp000066400000000000000000000023461324306050200170750ustar00rootroot00000000000000/************************************************************************* * Copyright (C) 2004 by Olivier Galizzi * * olivier.galizzi@imag.fr * * * * This program is free software; it is licensed under the terms of the * * GNU General Public License v2 or later. See file LICENSE for details. * *************************************************************************/ #pragma once #include "Interaction.hpp" #include "GlobalEngine.hpp" #include "Scene.hpp" class Body; class TimeStepper: public GlobalEngine{ public: virtual void computeTimeStep(Scene* ) { throw; }; virtual bool isActivated() {return (active && (scene->iter % timeStepUpdateInterval == 0));}; virtual void action() { computeTimeStep(scene);} ; void setActive(bool a, int nb=-1) {active = a; if (nb>0) {timeStepUpdateInterval = (unsigned int)nb;}} YADE_CLASS_BASE_DOC_ATTRS( TimeStepper,GlobalEngine,"Engine defining time-step (fundamental class)", ((bool,active,true,,"is the engine active?")) ((unsigned int,timeStepUpdateInterval,1,,"dt update interval"))); }; REGISTER_SERIALIZABLE(TimeStepper); trunk-2018.02b/core/Timing.hpp000066400000000000000000000027671324306050200160720ustar00rootroot00000000000000// 2009 © Václav Šmilauer #pragma once #include struct TimingInfo{ typedef unsigned long long delta; long nExec; delta nsec; TimingInfo():nExec(0),nsec(0){} static delta getNow(bool evenIfDisabled=false) { if(!enabled && !evenIfDisabled) return 0L; #ifdef __APPLE__ std::cerr << "ERROR: Time profiling (TimingInfo) not implemented on Apples." << std::endl; return 0L; #else struct timespec ts; clock_gettime(CLOCK_MONOTONIC,&ts); return delta(1e9*ts.tv_sec+ts.tv_nsec); #endif } static bool enabled; }; /* Create TimingDeltas object, then every call to checkpoint() will add * (or use existing) TimingInfo to data. It increases its nExec by 1 * and nsec by time elapsed since construction or last checkpoint. */ class TimingDeltas{ TimingInfo::delta last; size_t i; public: vector data; vector labels; TimingDeltas():i(0){} void start(){if(!TimingInfo::enabled)return; i=0;last=TimingInfo::getNow();} void checkpoint(const string& label){ if(!TimingInfo::enabled) return; if(data.size()<=i) { data.resize(i+1); labels.resize(i+1); labels[i]=label;} TimingInfo::delta now=TimingInfo::getNow(); data[i].nExec+=1; data[i].nsec+=now-last; last=now; i++; } void reset(){ data.clear(); labels.clear(); } // python access boost::python::list pyData(){ boost::python::list ret; for(size_t i=0; i // make core classes known to the class factory #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include // these two are not accessible from python directly (though they should be in the future, perhaps) BOOST_CLASS_EXPORT_IMPLEMENT(BodyContainer); BOOST_CLASS_EXPORT_IMPLEMENT(InteractionContainer); YADE_PLUGIN((Body)(Bound)(Cell)(Dispatcher)(EnergyTracker)(Engine)(FileGenerator)(Functor)(GlobalEngine)(Interaction)(IGeom)(IPhys)(Material)(PartialEngine)(Shape)(State)(TimeStepper)); EnergyTracker::~EnergyTracker(){} trunk-2018.02b/core/main/000077500000000000000000000000001324306050200150425ustar00rootroot00000000000000trunk-2018.02b/core/main/main.py.in000077500000000000000000000320511324306050200167510ustar00rootroot00000000000000#!${pyExecutable} # encoding: utf-8 # syntax:python import sys,os,os.path,time try: import _io except ImportError: import io # Add search path for yade Python-modules # It allows to use both Yade-versions (packaged and self-compiled one). # See LP:1254708 for more details # https://bugs.launchpad.net/yade/+bug/1254708 sys.path.insert(1,'${runtimePREFIX}/${LIBRARY_OUTPUT_PATH}/yade${SUFFIX}/py') # get yade path (allow YADE_PREFIX to override) prefix,suffix='${runtimePREFIX}' if not os.environ.has_key('YADE_PREFIX') else os.environ['YADE_PREFIX'],'${SUFFIX}' # duplicate some items from yade.config here, so that we can increase verbosity when the c++ part is booting features,version,debugbuild='${CONFIGURED_FEATS}'.split(' '),'${realVersion}',' ${debugbuild}' if (features[0]==''): features=features[1:] libPATH='${LIBRARY_OUTPUT_PATH}' if (libPATH[1:] == '{LIBRARY_OUTPUT_PATH}'): libPATH='lib' ## find available builds libDir=prefix+'/'+libPATH+'/yade'+suffix if not (os.path.exists(libDir+'/py/yade/__init__.py')): raise RuntimeError('Libraries are not found! ('+libDir+'/py/yade/__init__.py, /py/yade/__init__.py)') # handle command-line options first try: import argparse except ImportError: # argparse not present, print error message raise RuntimeError("\n\nPlease install 'python-argparse' package.\n") prog = os.path.basename(sys.argv[0]) par=argparse.ArgumentParser(usage='%s [options] [ simulation.xml[.bz2] | script.py [script options]]'%prog, prog=prog,description="Yade: open-source platform for dynamic compuations. It\ is an extensible open-source framework for discrete numerical models, focused\ on Discrete Element Method. The computation parts are written in c++ using\ flexible object model, allowing independent implementation of new algorithms\ and interfaces. Python is used for rapid and concise scene construction, \ simulation control, postprocessing and debugging.\ Available features: %s.\ Homepage http://www.yade-dem.org, code hosted at http://www.launchpad.net/yade."%features ) par.add_argument('-v','--version',help='Print version and exit.',dest='version',action='store_true') par.add_argument('-j','--threads',help='Number of OpenMP threads to run; defaults to 1. Equivalent to setting OMP_NUM_THREADS environment variable.',dest='threads',type=int) par.add_argument('--cores',help='Set number of OpenMP threads (as \-\-threads) and in addition set affinity of threads to the cores given. Please provide a string with comma-separated core-ids.',dest='cores',type=str) par.add_argument('--update',help='Update deprecated class names in given script(s) using text search & replace. Changed files will be backed up with ~ suffix. Exit when done without running any simulation.',dest='updateScripts',nargs='+') par.add_argument('--nice',help='Increase nice level (i.e. decrease priority) by given number.',dest='nice',type=int) par.add_argument('-x',help='Exit when the script finishes',dest='exitAfter',action='store_true') par.add_argument('-n',help="Run without graphical interface (equivalent to unsetting the DISPLAY environment variable)",dest='nogui',action='store_true') par.add_argument('--test',help="Run regression test suite and exit; the exists status is 0 if all tests pass, 1 if a test fails and 2 for an unspecified exception.",dest="test",action='store_true') par.add_argument('--checks',help='Run a series of user-defined check tests as described in /yade/scripts/checks-and-tests/checks/README',dest='checks',action='store_true') par.add_argument('--performance',help='Starts a test to measure the productivity',dest='performance',action='store_true') par.add_argument('script',nargs='?',default='',type=str,help=argparse.SUPPRESS) par.add_argument('args',nargs=argparse.REMAINDER,help=argparse.SUPPRESS) # see argparse doc, par.disable_interspersed_args() from optargs module par.add_argument('-l',help='import libraries at startup before importing yade libs. May be used when the ordering of imports matter (see e.g. https://bugs.launchpad.net/yade/+bug/1183402/comments/3). The option can be use multiple times, as in "yade -llib1 -llib2"',default=None,action='append',dest='impLibraries',type=str) opts=par.parse_args() args = opts.args if opts.impLibraries: sys.path.append('.') for lib in opts.impLibraries: __import__(lib) if opts.version: print 'Yade version: %s%s'%(version,debugbuild) sys.exit(0) if opts.script: args.insert(0,opts.script) # for compatibility with userSession(), could be modified in the future ## remove later ## python2.5 relative module imports workaround v=sys.version_info if v[0]==2 and v[1]<=5: for submodule in ('yade','gts','yade/tests'): sys.path.append(os.path.join(libDir,'py',submodule)) sys.path.append(os.path.join(libDir,'py')) # run regression test suite and exit if opts.test: import yade.tests try: result=yade.tests.testAll() except: print 20*'*'+' UNEXPECTED EXCEPTION WHILE RUNNING TESTS '+20*'*' print 20*'*'+' '+str(sys.exc_info()[0]) print 20*'*'+" Please report bug at http://bugs.launchpad.net/yade providing the following traceback:" import traceback; traceback.print_exc() print 20*'*'+' Thank you '+20*'*' sys.exit(2) if (os.path.exists('./DirSearchYade')): os.remove('./DirSearchYade') if (os.path.exists('./MicroMacroAnalysis')): os.remove('./MicroMacroAnalysis') if result.wasSuccessful(): print "*** ALL TESTS PASSED ***" sys.exit(0) else: print 20*'*'+' SOME TESTS FAILED '+20*'*' sys.exit(1) if not 'OpenMP' in features and (opts.cores or (opts.threads and opts.threads>1)): print 'WARNING: compiled without OpenMP, -j/--threads/--cores have no effect.' # OpenMP env variables must be set before loading yade libs ("import yade" below) # changes have no effect after libgomp initializes if opts.cores: if opts.threads: print 'WARNING: --threads ignored, since --cores specified.' try: cores=[int(i) for i in opts.cores.split(',')] except ValueError: raise ValueError('Invalid --cores specification %s, should be a comma-separated list of non-negative integers'%opts.cores) opts.nthreads=len(cores) os.environ['GOMP_CPU_AFFINITY']=' '.join([str(c) for c in cores]) os.environ['OMP_NUM_THREADS']=str(len(cores)) elif opts.threads: os.environ['OMP_NUM_THREADS']=str(opts.threads) else: os.environ['OMP_NUM_THREADS']='1' if __name__ == "__main__": # do not print this while importing yade in other python application sys.stdout.write('Welcome to Yade '+version+debugbuild+'\n') # initialization and c++ plugins import import yade # other parts we will need soon import yade.config import yade.wrapper import yade.system import yade.runtime # continue option processing if opts.updateScripts: yade.system.updateScripts(args) sys.exit(0) # modify sys.argv in-place so that it can be handled by userSession sys.argv=yade.runtime.argv=args yade.runtime.opts=opts from yade import utils, pack from yade.utils import * from yade.pack import * from math import * # Run the check tests listed in scripts/checks-and-tests/checks/checkList.py if opts.checks: checksPath=libDir+'/py/yade/tests/checks' execfile(checksPath+'/checkList.py') # Run performance check test if opts.performance: checksPath=libDir+'/py/yade/tests/checks/performance' execfile(checksPath+'/checkPerf.py') def userSession(gui='none',qapp=None): # prepare nice namespace for users import yade.runtime import sys if __name__ != "__main__": # for importing as python module return # start non-blocking qt4 app here; need to ask on the mailing list on how to make it functional ## with ipython 0.11, start the even loop early (impossible with 0.10, which is thread-based) #if qt4 and yade.runtime.ipython_version==11: # import IPython # IPython.appstart_qt4(qapp) if len(sys.argv)>0: arg0=sys.argv[0] if (gui<>'none'): yade.qt.Controller(); if sum(bool(arg0.endswith(ext)) for ext in ('.xml','.xml.bz2','.xml.gz','.yade','.yade.gz','.yade.bz2','.bin','.bin.gz','.bin.bz2'))>0: if len(sys.argv)>1: raise RuntimeError('Extra arguments to saved simulation to run: '+' '.join(sys.argv[1:])) sys.stderr.write("Running simulation "+arg0+'\n') if arg0.endswith('.py'): def runScript(script): sys.stderr.write("Running script "+arg0+'\n') try: execfile(script,globals()) except SystemExit: raise except: # all other exceptions import traceback traceback.print_exc() if yade.runtime.opts.exitAfter: sys.exit(1) if yade.runtime.opts.exitAfter: sys.exit(0) runScript(arg0) if yade.runtime.opts.exitAfter: sys.exit(0) # common ipython configuration banner='[[ ^L clears screen, ^U kills line. '+', '.join((['F12 controller','F11 3d view (use h-key for showing help)','F10 both','F9 generator'] if (gui<>'none') else [])+['F8 plot'])+'. ]]' ipconfig=dict( # ipython options, see e.g. http://www.cv.nrao.edu/~rreid/casa/tips/ipy_user_conf.py prompt_in1='Yade [\#]: ', prompt_in2=' .\D.: ', prompt_out=" -> [\#]: ", separate_in='',separate_out='',separate_out2='', #execfile=[prefix+'/lib/yade'+suffix+'/py/yade/ipython.py'], readline_parse_and_bind=[ 'tab: complete', # only with the gui; the escape codes might not work on non-linux terminals. ] +(['"\e[24~": "\C-Uyade.qt.Controller();\C-M"','"\e[23~": "\C-Uyade.qt.View();\C-M"','"\e[21~": "\C-Uyade.qt.Controller(), yade.qt.View();\C-M"','"\e[20~": "\C-Uyade.qt.Generator();\C-M"'] if (gui<>'none') else []) #F12,F11,F10,F9 +['"\e[19~": "\C-Uimport yade.plot; yade.plot.plot();\C-M"', #F8 '"\e[A": history-search-backward', '"\e[B": history-search-forward', # incremental history forward/backward ] ) # show python console # handle both ipython 0.10 and 0.11 (incompatible API) if yade.runtime.ipython_version==10: from IPython.Shell import IPShellEmbed ipshell=IPShellEmbed(banner=banner,rc_override=ipconfig) ipshell() # save history -- a workaround for atexit handlers not being run (why?) # http://lists.ipython.scipy.org/pipermail/ipython-user/2008-September/005839.html import IPython.ipapi IPython.ipapi.get().IP.atexit_operations() elif yade.runtime.ipython_version==11: from IPython.frontend.terminal.embed import InteractiveShellEmbed # use the dict to set attributes for k in ipconfig: setattr(InteractiveShellEmbed,k,ipconfig[k]) InteractiveShellEmbed.banner1=banner+'\n' # called banner1 here, not banner anymore ipshell=InteractiveShellEmbed() ipshell() elif yade.runtime.ipython_version>=12: if yade.runtime.ipython_version>=100: from IPython.terminal.embed import InteractiveShellEmbed else: from IPython.frontend.terminal.embed import InteractiveShellEmbed if yade.runtime.ipython_version>=500: from traitlets.config.loader import Config cfg = Config() prompt_config = cfg.TerminalInteractiveShell.prompts_class else: from IPython.config.loader import Config cfg = Config() prompt_config = cfg.PromptManager prompt_config.in_template = ipconfig['prompt_in1'] prompt_config.in2_template = ipconfig['prompt_in2'] prompt_config.out_template = ipconfig['prompt_out'] import readline for k in ipconfig['readline_parse_and_bind']: readline.parse_and_bind(k) InteractiveShellEmbed.config=cfg InteractiveShellEmbed.banner1=banner+'\n' ipshell=InteractiveShellEmbed() # If IPython > 5 one need to initialize graphic gui if ((gui == "qt5" or gui == "qt4")and yade.runtime.ipython_version>=500): ipshell.enable_gui(gui) ipshell() ## run userSession in a way corresponding to the features we use: gui=None yade.runtime.hasDisplay=False # this is the default initialized in the module, anyway if 'GUI' in features: gui='qt4' if 'GUI-Qt5' in features: gui='qt5' if opts.nogui: gui=None if gui: import Xlib.display # PyQt4's QApplication does exit(1) if it is unable to connect to the display # we however want to handle this gracefully, therefore # we test the connection with bare xlib first, which merely raises DisplayError try: # contrary to display.Display, _BaseDisplay does not check for extensions and that avoids spurious message "Xlib.protocol.request.QueryExtension" (bug?) Xlib.display._BaseDisplay(); yade.runtime.hasDisplay=True except: # usually Xlib.error.DisplayError, but there can be Xlib.error.XauthError etc as well # let's just pretend any exception means the display would not work gui=None print 'Warning: no X rendering available (see https://bbs.archlinux.org/viewtopic.php?id=13189)' # run remote access things, before actually starting the user session (not while imported by other python application) if __name__ == "__main__": from yade import remote if (gui=='qt4' or gui=='qt5'): yade.remote.useQThread=True yade.remote.gui=gui yade.remote.runServers() if gui==None: userSession() elif gui=='qt4': ## we already tested that DISPLAY is available and can be opened ## otherwise Qt4 might crash at this point import PyQt4 from PyQt4 import QtGui from PyQt4.QtCore import * import yade.qt # this yade.qt is different from the one that comes with qt3 qapp=QtGui.QApplication(sys.argv) userSession(gui=gui,qapp=qapp) elif gui=='qt5': import PyQt5 from PyQt5 import QtGui from PyQt5.QtCore import * from PyQt5.QtWidgets import * import yade.qt qapp=QApplication(sys.argv) userSession(gui=gui,qapp=qapp) if __name__ == "__main__": O.exitNoBacktrace() trunk-2018.02b/core/main/pyboot.cpp000066400000000000000000000030251324306050200170620ustar00rootroot00000000000000#include #include #include #ifdef YADE_DEBUG void crashHandler(int sig){ switch(sig){ case SIGABRT: case SIGSEGV: signal(SIGSEGV,SIG_DFL); signal(SIGABRT,SIG_DFL); // prevent loops - default handlers cerr<<"SIGSEGV/SIGABRT handler called; gdb batch file is `"<(getpid())<<"\nset pagination off\nthread info\nthread apply all backtrace\ndetach\nquit\n"; gdbBatch.close(); signal(SIGABRT,crashHandler); signal(SIGSEGV,crashHandler); #endif vector ppp; for(int i=0; i(pp[i])); Omega::instance().loadPlugins(ppp); } void yadeFinalize(){ Omega::instance().cleanupTemps(); } BOOST_PYTHON_MODULE(boot){ boost::python::scope().attr("initialize")=&yadeInitialize; boost::python::scope().attr("finalize")=&yadeFinalize; //,"Finalize yade (only to be used internally).") } trunk-2018.02b/core/main/yade-batch.in000077500000000000000000000707521324306050200174110ustar00rootroot00000000000000#!${pyExecutable} # encoding: utf-8 # # vim: syntax=python # portions © 2008 Václav Šmilauer import os, sys, thread, time, logging, pipes, socket, xmlrpclib, re, shutil, random # Add search path for yade Python-modules # It allows to use both Yade-versions (packaged and self-compiled one). # See LP:1254708 for more details # https://bugs.launchpad.net/yade/+bug/1254708 sys.path.insert(1,'${runtimePREFIX}/${LIBRARY_OUTPUT_PATH}/yade${SUFFIX}/py') #socket.setdefaulttimeout(10) ## replaced by scons automatically prefix,suffix='${runtimePREFIX}' if not os.environ.has_key('YADE_PREFIX') else os.environ['YADE_PREFIX'],'${SUFFIX}' libPATH='${LIBRARY_OUTPUT_PATH}' if (libPATH[1:] == '{LIBRARY_OUTPUT_PATH}'): libPATH='lib' libDir=prefix+'/'+libPATH+'/yade'+suffix # run the batch always in non-debug mode (the spawned processes do honor debuggin flag, however) docDir=prefix+'/share/doc/yade'+suffix imageLogo = docDir+'/img/yade-logo-note.png' sys.path.append(os.path.join(libDir,'py')) executable=os.path.join(prefix,'bin','yade'+suffix) ## we just need this ... import yade, yade.utils, yade.config, yade.remote class JobInfo(): def __init__(self,num,id,command,hrefCommand,log,nCores,script,table,lineNo,affinity): self.started,self.finished,self.duration,self.durationSec,self.exitStatus=None,None,None,None,None # duration is a string, durationSec is a number self.command=command; self.hrefCommand=hrefCommand; self.num=num; self.log=log; self.id=id; self.nCores=nCores; self.cores=set(); self.infoSocket=None self.script=script; self.table=table; self.lineNo=lineNo; self.affinity=affinity self.hasXmlrpc=False self.status='PENDING' self.threadNum=None self.plotsLastUpdate,self.plotsFile=0.,yade.Omega().tmpFilename()+'.'+yade.remote.plotImgFormat def saveInfo(self): log=file(self.log,'a') log.write(""" =================== JOB SUMMARY ================ id : %s status : %d (%s) duration: %s command : %s started : %s finished: %s """%(self.id,self.exitStatus,'OK' if self.exitStatus==0 else 'FAILED',self.duration,self.command,time.asctime(time.localtime(self.started)),time.asctime(time.localtime(self.finished)))); log.close() #Show notification messasge if (pynotifyAvailable): statusString = '' if (self.exitStatus==0): statusString = 'Finished.' else: statusString = 'FAILED!' n = pynotify.Notification("Yade", "The job %d/%d is %s Duration %s"%(self.num+1, len(jobs), statusString, self.duration), imageLogo) n.show() def ensureXmlrpc(self): 'Attempt to establish xmlrpc connection to the job (if it does not exist yet). If the connection could not be established, as magic line was not found in the log, return False.' if self.hasXmlrpc: return True for l in open(self.log,'r'): if not l.startswith('XMLRPC info provider on'): continue url=l[:-1].split()[4] self.xmlrpcConn=xmlrpclib.ServerProxy(url,allow_none=True) self.hasXmlrpc=True return True if not self.hasXmlrpc: return False # catches the case where the magic line is not in the log yet def getInfoDict(self): if self.status!='RUNNING': return None if not self.ensureXmlrpc(): return None return self.xmlrpcConn.basicInfo() def updatePlots(self): global opts if self.status!='RUNNING': return if not self.ensureXmlrpc(): return if time.time()-self.plotsLastUpdate0: ret+='%2.2f%% done
step %d/%d'%(info['iter']*100./info['stopAtIter'],info['iter'],info['stopAtIter']) finishTime = str(time.ctime(time.time()+int((round(info['stopAtIter'] - info['iter'])/info['speed'])))) ret+='
%s finishes
'%finishTime else: ret+='step %d'%(info['iter']) if info['realtime']!=0: ret+='
speed %g/sec'%(info['speed']) ret+='
%d bodies
%d intrs'%(info['numBodies'],info['numIntrs']) ret+='' else: ret+=' (no info) ' ret+='%d%s'%(self.nCores,(' ('+','.join([str(c) for c in self.cores])+')') if self.cores and self.status=='RUNNING' else '') # TODO: make clickable so that it can be served full-size if os.path.exists(self.plotsFile): img='[plots]'%(self.num) ret+='%s'%(self.num,img) else: ret+=' (no plots) ' ret+='%s'%self.hrefCommand ret+='' return ret def stopJob(self): if self.status!='RUNNING': return None if not self.ensureXmlrpc(): return None self.xmlrpcConn.stop() return ("The job #%s is stopped
Return back to statistic page"%(self.num)) def t2hhmmss(dt): return '%02d:%02d:%02d'%(dt//3600,(dt%3600)//60,(dt%60)) def totalRunningTime(): tt0,tt1=[j.started for j in jobs if j.started],[j.finished for j in jobs if j.finished]+[time.time()] # it is safe to suppose that if len(tt0)==0: return 0 # no job has been started at all return max(tt1)-min(tt0) def globalHtmlStats(): t0=min([j.started for j in jobs if j.started!=None]) unfinished=len([j for j in jobs if j.status!='DONE']) nUsedCores=sum([j.nCores for j in jobs if j.status=='RUNNING']) global maxJobs if unfinished: ret='

Running for %s, since %s.

'%(t2hhmmss(totalRunningTime()),time.ctime(t0)) else: failed=len([j for j in jobs if j.exitStatus!=0]) lastFinished=max([j.finished for j in jobs]) # FIXME: do not report sum of runnign time of all jobs, only the real timespan ret='

Finished, idle for %s, running time %s since %s.

'%('red' if failed else 'lime',t2hhmmss(time.time()-lastFinished),t2hhmmss(sum([j.finished-j.started for j in jobs if j.started is not None])),time.ctime(t0)) ret+='

Pid %d'%(os.getpid()) if opts.globalLog: ret+=', log %s'%(opts.globalLog) ret+='

' allCores,busyCores=set(range(0,maxJobs)),set().union(*(j.cores for j in jobs if j.status=='RUNNING')) ret+='

%d cores available, %d used + %d free.

'%(maxJobs,nUsedCores,maxJobs-nUsedCores) # show busy and free cores; gives nonsense if not all jobs have CPU affinity set # '([%s] = [%s] + [%s])'%(','.join([str(c) for c in allCores]),','.join([str(c) for c in busyCores]),','.join([str(c) for s in (allCores-busyCores)])) ret+='

Jobs

' nFailed=len([j for j in jobs if j.status=='DONE' and j.exitStatus!=0]) ret+='

%d total, %d running, %d done%s

'%(len(jobs),len([j for j in jobs if j.status=='RUNNING']), len([j for j in jobs if j.status=='DONE']),' (%d failed)'%nFailed if nFailed>0 else '') return ret from BaseHTTPServer import BaseHTTPRequestHandler,HTTPServer import socket,re class HttpStatsServer(BaseHTTPRequestHandler): favicon=None # binary favicon, created when first requested def do_GET(self): if not self.path or self.path=='/': self.sendGlobal() else: if self.path=='/favicon.ico': if not self.__class__.favicon: import base64 self.__class__.favicon=base64.b64decode(yade.remote.b64favicon) self.sendHttp(self.__class__.favicon,contentType='image/vnd.microsoft.icon') return elif self.path=='/log' and opts.globalLog: self.sendTextFile(opts.globalLog,refresh=opts.refresh) return jobMatch=re.match('/jobs/([0-9]+)/(.*)',self.path) if not jobMatch: self.send_error(404,self.path); return jobId=int(jobMatch.group(1)) if jobId>=len(jobs): self.send_error(404,self.path); return job=jobs[jobId] rest=jobMatch.group(2) if rest=='plots': job.updatePlots() # internally checks for last update time self.sendFile(job.plotsFile,contentType=yade.remote.plotImgMimetype,refresh=(0 if job.status=='DONE' else opts.refresh)) elif rest=='log': if not os.path.exists(job.log): self.send_error(404,self.path); return ## once we authenticate properly, send the whole file ## self.sendTextFile(jobs[jobId].log,refresh=opts.refresh) ## now we have to filter away the cookie cookieRemoved=False; data='' for l in open(job.log): if not cookieRemoved and l.startswith('TCP python prompt on'): ii=l.find('auth cookie `'); l=l[:ii+13]+'******'+l[ii+19:]; cookieRemoved=True data+=l self.sendHttp(data,contentType='text/plain;charset=utf-8;',refresh=(0 if job.status=='DONE' else opts.refresh)) elif rest=='script': self.sendPygmentizedFile(job.script,linenostep=5) elif rest=='table': if not job.table: return self.sendPygmentizedFile(job.table,hl_lines=[job.lineNo],linenostep=1) elif rest=='stop': self.sendHttp(job.stopJob(),contentType='text/html',refresh=(0 if job.status=='DONE' else opts.refresh)) elif rest=='askforstop': self.sendHttp('Are you sure, you want to stop job #%d?
Stop
Cancel'%(job.num,job.num),contentType='text/html',refresh=(0 if job.status=='DONE' else opts.refresh)) else: self.send_error(404,self.path) return def log_request(self,req): pass def sendGlobal(self): html='Yade-batch at %s overview\n'%(socket.gethostname()) html+=globalHtmlStats() html+='\n' for j in jobs: html+=j.htmlStats()+'\n' html+='
idstatusinfocoresplotscommand
' self.sendHttp(html,contentType='text/html',refresh=opts.refresh) # refresh sent as header def sendTextFile(self,fileName,**headers): if not os.path.exists(fileName): self.send_error(404); return import codecs f=codecs.open(fileName,encoding='utf-8') self.sendHttp(f.read(),contentType='text/plain;charset=utf-8;',**headers) def sendFile(self,fileName,contentType,**headers): if not os.path.exists(fileName): self.send_error(404); return f=open(fileName) self.sendHttp(f.read(),contentType=contentType,**headers) def sendHttp(self,data,contentType,**headers): "Send file over http, using appropriate content-type. Headers are converted to strings. The *refresh* header is handled specially: if the value is 0, it is not sent at all." self.send_response(200) self.send_header('Content-type',contentType) if 'refresh' in headers and headers['refresh']==0: del headers['refresh'] for h in headers: self.send_header(h,str(headers[h])) self.end_headers() self.wfile.write(data) global httpLastServe httpLastServe=time.time() def sendPygmentizedFile(self,f,**kw): if not os.path.exists(f): self.send_error(404); return try: import codecs from pygments import highlight from pygments.lexers import PythonLexer from pygments.formatters import HtmlFormatter data=highlight(codecs.open(f,encoding='utf-8').read(),PythonLexer(),HtmlFormatter(linenos=True,full=True,encoding='utf-8',title=os.path.abspath(f),**kw)) self.sendHttp(data,contentType='text/html;charset=utf-8;') except ImportError: self.sendTextFile(f) def runHttpStatsServer(): maxPort=11000; port=9080 while port0: job.exitStatus=0 job.finished=time.time() dt=job.finished-job.started; job.durationSec=dt job.duration=t2hhmmss(dt) strStatus='done ' if job.exitStatus==0 else 'FAILED ' job.status='DONE' havePlot=False if os.path.exists(job.plotsFile): f=(job.log[:-3] if job.log.endswith('.log') else job.log+'.')+yade.remote.plotImgFormat shutil.copy(job.plotsFile,f) job.plotsFile=f havePlot=True print "#%d (%s%s) %s (exit status %d), duration %s, log %s%s"%(job.num,job.id,'' if job.nCores==1 else '/%d'%job.nCores,strStatus,job.exitStatus,job.duration,job.log,(', plot %s'%(job.plotsFile) if havePlot else '')) job.saveInfo() def runJobs(jobs,numCores): running,pending=0,len(jobs) inf=1000000 while (running>0) or (pending>0): pending,running,done=sum([j.nCores for j in jobs if j.status=='PENDING']),sum([j.nCores for j in jobs if j.status=='RUNNING']),sum([j.nCores for j in jobs if j.status=='DONE']) numFreeCores=numCores-running minRequire=min([inf]+[j.nCores for j in jobs if j.status=='PENDING']) if minRequire==inf: minRequire=0 #print pending,'pending;',running,'running;',done,'done;',numFreeCores,'free;',minRequire,'min' overloaded=False if minRequire>numFreeCores and running==0: overloaded=True # a job wants more cores than the total we have pendingJobs=[j for j in jobs if j.status=='PENDING'] if opts.randomize: random.shuffle(pendingJobs) for j in pendingJobs: if j.nCores<=numFreeCores or overloaded: freeCores=set(range(0,numCores))-set().union(*(j.cores for j in jobs if j.status=='RUNNING')) #print 'freeCores:',freeCores,'numFreeCores:',numFreeCores,'overloaded',overloaded if not overloaded: # only set cores if CPU affinity is desired; otherwise, just numer of cores is used if j.affinity: j.cores=list(freeCores)[0:j.nCores] # take required number of free cores # if overloaded, do not assign cores directly thread.start_new_thread(runJob,(j,)) break time.sleep(.5) sys.stdout.flush() import sys,re,os try: import argparse except ImportError: # argparse not present, print error message raise RuntimeError("\n\nPlease install 'python-argparse' package.\n") def getNumCores(): nCpu=1 try: nCpu=len([l for l in open('/proc/cpuinfo','r') if l.find('processor')==0]) except: print 'WARNING: No cpuinfo available! Setting the maximal number of processors 1' pass if os.environ.has_key("OMP_NUM_THREADS"): return min(int(os.environ['OMP_NUM_THREADS']),nCpu) return nCpu numCores=getNumCores() maxOmpThreads=numCores if 'OpenMP' in yade.config.features else 1 features,version='${CONFIGURED_FEATS}'.split(','),'${realVersion}' if (features[0]==''): features=features[1:] prog = os.path.basename(sys.argv[0]) parser=argparse.ArgumentParser(usage='%s [options] [ TABLE [SIMULATION.py] | SIMULATION.py[/nCores] [...] ]'%prog,description='%s runs yade simulation multiple times with different parameters.\n\nSee https://yade-dem.org/sphinx/user.html#batch-queuing-and-execution-yade-batch for details.\n\nBatch can be specified either with parameter table TABLE (must not end in .py), which is either followed by exactly one SIMULATION.py (must end in .py), or contains !SCRIPT column specifying the simulation to be run. The second option is to specify multiple scripts, which can optionally have /nCores suffix to specify number of cores for that particular simulation (corresponds to !THREADS column in the parameter table), e.g. sim.py/3.'%prog) parser.add_argument('-j','--jobs',dest='maxJobs',type=int,help="Maximum number of simultaneous threads to run (default: number of cores, further limited by OMP_NUM_THREADS if set by the environment: %d)"%numCores,metavar='NUM',default=numCores) parser.add_argument('-v','--version',help='Print version and exit.',dest='version',action='store_true') parser.add_argument('--job-threads',dest='defaultThreads',type=int,help="Default number of threads for one job; can be overridden by per-job with !THREADS (or !OMP_NUM_THREADS) column. Defaults to 1.",metavar='NUM',default=1) parser.add_argument('--force-threads',action='store_true',dest='forceThreads',help='Force jobs to not use more cores than the maximum (see \-j), even if !THREADS colums specifies more.') parser.add_argument('--log',dest='logFormat',help='Format of job log files: must contain a $, %% or @, which will be replaced by script name, line number or by description column respectively (default: $.@.log)',metavar='FORMAT',default='$.@.log') parser.add_argument('--global-log',dest='globalLog',help='Filename where to redirect output of yade-batch itself (as opposed to \-\-log); if not specified (default), stdout/stderr are used',metavar='FILE') parser.add_argument('-l','--lines',dest='lineList',help='Lines of TABLE to use, in the format 2,3-5,8,11-13 (default: all available lines in TABLE)',metavar='LIST') parser.add_argument('--nice',dest='nice',type=int,help='Nice value of spawned jobs (default: 10)',default=10) parser.add_argument('--cpu-affinity',dest='affinity',action='store_true',help='Bind each job to specific CPU cores; cores are assigned in a quasi-random order, depending on availability at the moment the jobs is started. Each job can override this setting by setting AFFINE column.') parser.add_argument('--executable',dest='executable',help='Name of the program to run (default: %s). Jobs can override with !EXEC column.'%executable,default=executable,metavar='FILE') parser.add_argument('--gnuplot',dest='gnuplotOut',help='Gnuplot file where gnuplot from all jobs should be put together',default=None,metavar='FILE') parser.add_argument('--dry-run',action='store_true',dest='dryRun',help='Do not actually run (useful for getting gnuplot only, for instance)',default=False) parser.add_argument('--http-wait',action='store_true',dest='httpWait',help='Do not quit if still serving overview over http repeatedly',default=False) parser.add_argument('--plot-update',type=int,dest='plotAlwaysUpdateTime',help='Interval (in seconds) at which job plots will be updated even if not requested via HTTP. Non-positive values will make the plots not being updated and saved unless requested via HTTP (see \-\-plot-timeout for controlling maximum age of those). Plots are saved at exit under the same name as the log file, with the .log extension removed. (default: 120 seconds)',metavar='TIME',default=120) parser.add_argument('--plot-timeout',type=int,dest='plotTimeout',help='Maximum age (in seconds) of plots served over HTTP; they will be updated if they are older. (default: 30 seconds)',metavar='TIME',default=30) parser.add_argument('--refresh',type=int,dest='refresh',help='Refresh rate of automatically reloaded web pages (summary, logs, ...).',metavar='TIME',default=30) parser.add_argument('--timing',type=int,dest='timing',default=0,metavar='COUNT',help='Repeat each job COUNT times, and output a simple table with average/variance/minimum/maximum job duration; used for measuring how various parameters affect execution time. Jobs can override the global value with the !COUNT column.') parser.add_argument('--timing-output',type=str,metavar='FILE',dest='timingOut',default=None,help='With \-\-timing, save measured durations to FILE, instead of writing to standard output.') parser.add_argument('--randomize',action='store_true',dest='randomize',help='Randomize job order (within constraints given by assigned cores).') parser.add_argument('--disable-pynotify',action='store_true',dest='disablePynotify',help='Disable screen notifications') parser.add_argument('args',nargs=argparse.REMAINDER,help=argparse.SUPPRESS) # see argparse doc, par.disable_interspersed_args() from optargs module #parser.add_argument('--serial',action='store_true',dest='serial',default=False,help='Run all jobs serially, even if there are free cores opts=parser.parse_args() args = opts.args logFormat,lineList,maxJobs,nice,executable,gnuplotOut,dryRun,httpWait,globalLog=opts.logFormat,opts.lineList,opts.maxJobs,opts.nice,opts.executable,opts.gnuplotOut,opts.dryRun,opts.httpWait,opts.globalLog if opts.version: print 'Yade version: %s, features: %s'%(version,','.join(features)) sys.exit(0) if globalLog: print 'Redirecting all output to',globalLog sys.stderr=open(globalLog,"w") sys.stdout=sys.stderr if len([1 for a in args if re.match('.*\.py(/[0-9]+)?',a)])==len(args) and len(args)!=0: # if all args end in .py, they are simulations that we will run table=None; scripts=args elif len(args)==2: table,scripts=args[0],[args[1]] elif len(args)==1: table,scripts=args[0],[] else: parser.print_help() sys.exit(1) pynotifyAvailable = False n = 0; if not opts.disablePynotify: try: import pynotify pynotifyAvailable = True pynotify.init("Yade") except ImportError: pynotifyAvailable = False print "Will run simulation(s) %s using `%s', nice value %d, using max %d cores."%(scripts,executable,nice,maxJobs) if table: reader=yade.utils.TableParamReader(table) params=reader.paramDict() availableLines=params.keys() print "Will use table `%s', with available lines"%(table),', '.join([str(i) for i in availableLines])+'.' if lineList: useLines=[] def numRange2List(s): ret=[] for l in s.split(','): if "-" in l: ret+=range(*[int(s) for s in l.split('-')]); ret+=[ret[-1]+1] else: ret+=[int(l)] return ret useLines0=numRange2List(lineList) for l in useLines0: if l not in availableLines: logging.warn('Skipping unavailable line %d that was requested from the command line.'%l) else: useLines+=[l] else: useLines=availableLines print "Will use lines ",', '.join([str(i)+' (%s)'%params[i]['description'] for i in useLines])+'.' else: print "Running %d stand-alone simulation(s) in batch mode."%(len(scripts)) useLines=[] params={} for i,s in enumerate(scripts): fakeLineNo=-i-1 useLines.append(fakeLineNo) params[fakeLineNo]={'description':'default','!SCRIPT':s} # fix script and set threads if script.py/num m=re.match('(.*)(/[0-9]+)$',s) if m: params[fakeLineNo]['!SCRIPT']=m.group(1) params[fakeLineNo]['!THREADS']=int(m.group(2)[1:]) jobs=[] executables=set() logFiles=[] for i,l in enumerate(useLines): script=scripts[0] if len(scripts)>0 else None envVars=[] nCores=opts.defaultThreads jobExecutable=executable jobAffinity=opts.affinity jobCount=opts.timing for col in params[l].keys(): if col[0]!='!': continue val=params[l][col] if col=='!OMP_NUM_THREADS' or col=='!THREADS': nCores=int(val) elif col=='!EXEC': jobExecutable=val elif col=='!SCRIPT': script=val elif col=='!AFFINITY': jobAffinity=eval(val) elif col=='!COUNT': jobCount=eval(val) else: envVars+=['%s=%s'%(head[1:],values[l][col])] if not script: raise ValueError('When only batch table is given without script to run, it must contain !SCRIPT column with simulation to be run.') logFile=logFormat.replace('$',script).replace('%',str(l)).replace('@',params[l]['description']) if (len(logFile)>230): #For very long files, to prevent system errors. logFile = logFile[0:230]+'_'+str(l)+logFile[-4:] #l is added to prevent name duplications. if nCores>maxJobs: if opts.forceThreads: logging.info('Forcing job #%d to use only %d cores (max available) instead of %d requested'%(i,maxJobs,nCores)) nCores=maxJobs else: logging.warning('WARNING: job #%d will use %d cores but only %d are available'%(i,nCores,maxJobs)) if 'OpenMP' not in yade.config.features and nCores>1: logging.warning('Job #%d will be uselessly run with %d threads (compiled without OpenMP support).'%(i,nCores)) executables.add(jobExecutable) # compose command-line: build the hyper-linked variant, then strip HTML tags (ugly, but ensures consistency) for j in range(0,opts.timing if opts.timing>0 else 1): jobNum=len(jobs) logFile2=logFile+('.%d'%j if opts.timing>0 else '') # append numbers to log file if it already exists, to prevent overwriting if logFile2 in logFiles: i=0; while logFile2+'.%d'%i in logFiles: i+=1 logFile2+='.%d'%i logFiles.append(logFile2) env='YADE_BATCH=' if table: env+='%s:%d'%(jobNum,table,l) # keep YADE_BATCH empty (but still defined) if running a single simulation env+=' DISPLAY= %s '%(' '.join(envVars)) cmd='%s%s [threadspec] %s -x %s'%(jobExecutable,'','--nice=%s'%nice if nice!=None else '',i,script) log='> %s 2>&1'%(jobNum,pipes.quote(logFile2)) hrefCmd=env+cmd+log fullCmd=re.sub('(]+">|)','',hrefCmd) desc=params[l]['description'] if '!SCRIPT' in params[l].keys(): desc=script+'.'+desc # prepend filename if script is specified explicitly if opts.timing>0: desc+='[%d]'%j jobs.append(JobInfo(jobNum,desc,fullCmd,hrefCmd,logFile2,nCores,script=script,table=table,lineNo=l,affinity=jobAffinity)) print "Master process pid",os.getpid() print "Job summary:" for job in jobs: print ' #%d (%s%s):'%(job.num,job.id,'' if job.nCores==1 else '/%d'%job.nCores),job.command sys.stdout.flush() httpLastServe=0 runHttpStatsServer() if opts.plotAlwaysUpdateTime>0: # update plots periodically regardless of whether they are requested via HTTP def updateAllPlots(): time.sleep(opts.plotAlwaysUpdateTime) for job in jobs: job.updatePlots() thread.start_new_thread(updateAllPlots,()) # OK, go now if not dryRun: runJobs(jobs,maxJobs) print 'All jobs finished, total time ',t2hhmmss(totalRunningTime()) plots=[] for j in jobs: if not os.path.exists(j.plotsFile): continue plots.append(j.plotsFile) if plots: print 'Plot files:',' '.join(plots) # for easy grepping in logfiles: print 'Log files:',' '.join([j.log for j in jobs]) # write timing table if opts.timing>0: if opts.timingOut: print 'Writing gathered timing information to',opts.timingOut try: out=open(opts.timingOut,'w') except IOError: logging.warn('Unable to open file %s for timing output, writing to stdout.'%opts.timingOut) out=sys.stdout else: print 'Gathered timing information:' out=sys.stdout # write header out.write('## timing data, written '+time.asctime()+' with arguments\n## '+' '.join(sys.argv)+'\n##\n') paramNames=params[params.keys()[0]].keys(); paramNames.sort() out.write('## line\tcount\tavg\tdev\trelDev\tmin\tmax\t|\t'+'\t'.join(paramNames)+'\n') import math for i,l in enumerate(useLines): jobTimes=[j.durationSec for j in jobs if j.lineNo==l and j.durationSec!=None] tSum=sum(jobTimes); tAvg=tSum/len(jobTimes) tMin,tMax=min(jobTimes),max(jobTimes) tDev=math.sqrt(sum((t-tAvg)**2 for t in jobTimes)/len(jobTimes)) tRelDev=tDev/tAvg out.write('%d\t%d\t%.2f\t%.2f\t%.3g\t%.2f\t%.2f\t|\t'%(l,len(jobTimes),tAvg,tDev,tRelDev,tMin,tMax)+'\t'.join([params[l][p] for p in paramNames])+'\n') if not gnuplotOut: print 'Bye.' else: print 'Assembling gnuplot files…' for job in jobs: for l in file(job.log): if l.startswith('gnuplot '): job.plot=l.split()[1] break preamble,plots='',[] for job in jobs: if not 'plot' in job.__dict__: print "WARN: No plot found for job "+job.id continue for l in file(job.plot): if l.startswith('plot'): # attempt to parse the plot line ll=l.split(' ',1)[1][:-1] # rest of the line, without newline # replace title 'something' with title 'description: something' ll,nn=re.subn(r'title\s+[\'"]([^\'"]*)[\'"]',r'title "'+job.id+r': \1"',ll) if nn==0: logging.error("Plot line in "+job.plot+" not parsed (skipping): "+ll) plots.append(ll) break if not plots: # first plot, copy all preceding lines preamble+=l gp=file(gnuplotOut,'w') gp.write(preamble) gp.write('plot '+','.join(plots)) print "gnuplot",gnuplotOut print "Plot written, bye." if httpWait and time.time()-httpLastServe<30: print "(continue serving http until no longer requested as per --http-wait)" while time.time()-httpLastServe<30: time.sleep(1) yade.Omega().exitNoBacktrace() trunk-2018.02b/core/main/yade-oar.in000066400000000000000000000263341324306050200171030ustar00rootroot00000000000000#!${pyExecutable} # encoding: utf-8 # # vim: syntax=python # portions © 2008 Václav Šmilauer # © 2017 William Chèvremont # This script is to be used with OAR task scheduler. May be an example to use use with other task scheduler for clusters # Adapted from yade-batch import os, sys, thread, time, logging, pipes, socket, xmlrpclib, re, shutil, random # Add search path for yade Python-modules # It allows to use both Yade-versions (packaged and self-compiled one). # See LP:1254708 for more details # https://bugs.launchpad.net/yade/+bug/1254708 #socket.setdefaulttimeout(10) # Setup executable ## replaced by scons automatically prefix,suffix='${runtimePREFIX}' if not os.environ.has_key('YADE_PREFIX') else os.environ['YADE_PREFIX'],'${SUFFIX}' libPATH='${LIBRARY_OUTPUT_PATH}' if (libPATH[1:] == '{LIBRARY_OUTPUT_PATH}'): libPATH='lib' libDir=prefix+'/'+libPATH+'/yade'+suffix # run the batch always in non-debug mode (the spawned processes do honor debuggin flag, however) sys.path.append(os.path.join(libDir,'py')) executable=os.path.join(prefix,'bin','yade'+suffix) import yade, yade.utils, yade.config, yade.remote from shutil import copyfile def t2hhmmss(dt): return '%02d:%02d:%02d'%(dt//3600,(dt%3600)//60,(dt%60)) import sys,re,os try: import argparse except ImportError: # argparse not present, print error message raise RuntimeError("\n\nPlease install 'python-argparse' package.\n") class JobInfo(): def __init__(self,num,id,command,log,nCores,script,table,lineNo,affinity): self.started,self.finished,self.duration,self.durationSec,self.exitStatus=None,None,None,None,None # duration is a string, durationSec is a number self.command=command; self.num=num; self.log=log; self.id=id; self.nCores=nCores; self.cores=set(); self.infoSocket=None self.script=script; self.table=table; self.lineNo=lineNo; self.affinity=affinity self.hasXmlrpc=False self.status='PENDING' self.threadNum=None self.plotsLastUpdate,self.plotsFile=0.,yade.Omega().tmpFilename()+'.'+yade.remote.plotImgFormat features,version='${CONFIGURED_FEATS}'.split(','),'${realVersion}' if (features[0]==''): features=features[1:] prog = os.path.basename(sys.argv[0]) parser=argparse.ArgumentParser(usage='%s [options] [ TABLE [SIMULATION.py] | SIMULATION.py[/nCores] [...] ]'%prog,description='%s runs yade simulation multiple times with different parameters.\n\nSee https://yade-dem.org/sphinx/user.html#batch-queuing-and-execution-yade-batch for details.\n\nBatch can be specified either with parameter table TABLE (must not end in .py), which is either followed by exactly one SIMULATION.py (must end in .py), or contains !SCRIPT column specifying the simulation to be run. The second option is to specify multiple scripts, which can optionally have /nCores suffix to specify number of cores for that particular simulation (corresponds to !THREADS column in the parameter table), e.g. sim.py/3.'%prog) parser.add_argument('-v','--version',help='Print version and exit.',dest='version',action='store_true') parser.add_argument('--job-threads',dest='defaultThreads',type=int,help="Default number of threads for one job; can be overridden by per-job with !THREADS (or !OMP_NUM_THREADS) column. Defaults to 1.",metavar='NUM',default=1) parser.add_argument('--force-threads',action='store_true',dest='forceThreads',help='Force jobs to not use more cores than the maximum (see \-j), even if !THREADS colums specifies more.') parser.add_argument('--log',dest='logFormat',help='Format of job log files: must contain a $, %% or @, which will be replaced by script name, line number or by description column respectively (default: $.@.log)',metavar='FORMAT',default='$.@.log') parser.add_argument('--global-log',dest='globalLog',help='Filename where to redirect output of yade-batch itself (as opposed to \-\-log); if not specified (default), stdout/stderr are used',metavar='FILE') parser.add_argument('-l','--lines',dest='lineList',help='Lines of TABLE to use, in the format 2,3-5,8,11-13 (default: all available lines in TABLE)',metavar='LIST') parser.add_argument('--nice',dest='nice',type=int,help='Nice value of spawned jobs (default: 10)',default=10) parser.add_argument('--cpu-affinity',dest='affinity',action='store_true',help='Bind each job to specific CPU cores; cores are assigned in a quasi-random order, depending on availability at the moment the jobs is started. Each job can override this setting by setting AFFINE column.') parser.add_argument('--executable',dest='executable',help='Name of the program to run (default: %s). Jobs can override with !EXEC column.'%executable,default=executable,metavar='FILE') parser.add_argument('--dry-run',action='store_true',dest='dryRun',help='Do not actually run (useful for getting gnuplot only, for instance)',default=False) parser.add_argument('--timing',type=int,dest='timing',default=0,metavar='COUNT',help='Repeat each job COUNT times, and output a simple table with average/variance/minimum/maximum job duration; used for measuring how various parameters affect execution time. Jobs can override the global value with the !COUNT column.') parser.add_argument('--timing-output',type=str,metavar='FILE',dest='timingOut',default=None,help='With \-\-timing, save measured durations to FILE, instead of writing to standard output.') parser.add_argument('--randomize',action='store_true',dest='randomize',help='Randomize job order (within constraints given by assigned cores).') parser.add_argument('--disable-pynotify',action='store_true',dest='disablePynotify',help='Disable screen notifications') parser.add_argument('--oar-project',dest='oar_p',help='Project name to pass to oarsub') parser.add_argument('--oar-walltime',dest='oar_t',help='Walltime: max running time',default='2:00:00') parser.add_argument('--oar-script',dest='oar_script',help='Script passed to oar-sub. Must contain __COMMAND_HERE__ string where the Yade command will be replaced.',default='') parser.add_argument('args',nargs=argparse.REMAINDER,help=argparse.SUPPRESS) # see argparse doc, par.disable_interspersed_args() from optargs module #parser.add_argument('--serial',action='store_true',dest='serial',default=False,help='Run all jobs serially, even if there are free cores opts=parser.parse_args() args = opts.args logFormat,lineList,nice,executable,dryRun,globalLog,project, walltime=opts.logFormat,opts.lineList,opts.nice,opts.executable,opts.dryRun,opts.globalLog,opts.oar_p,opts.oar_t if opts.version: print 'Yade version: %s, features: %s'%(version,','.join(features)) sys.exit(0) if globalLog: print 'Redirecting all output to',globalLog sys.stderr=open(globalLog,"w") sys.stdout=sys.stderr if len([1 for a in args if re.match('.*\.py(/[0-9]+)?',a)])==len(args) and len(args)!=0: # if all args end in .py, they are simulations that we will run table=None; scripts=args elif len(args)==2: table,scripts=args[0],[args[1]] elif len(args)==1: table,scripts=args[0],[] else: parser.print_help() sys.exit(1) if opts.oar_script == '' : print 'You have to specifie --oar_script'; sys.exit(1); pynotifyAvailable = False n = 0; if not opts.disablePynotify: try: import pynotify pynotifyAvailable = True pynotify.init("Yade") except ImportError: pynotifyAvailable = False print "Will run simulation(s) %s using `%s', nice value %d"%(scripts,executable,nice) if table: reader=yade.utils.TableParamReader(table) params=reader.paramDict() availableLines=params.keys() print "Will use table `%s', with available lines"%(table),', '.join([str(i) for i in availableLines])+'.' if lineList: useLines=[] def numRange2List(s): ret=[] for l in s.split(','): if "-" in l: ret+=range(*[int(s) for s in l.split('-')]); ret+=[ret[-1]+1] else: ret+=[int(l)] return ret useLines0=numRange2List(lineList) for l in useLines0: if l not in availableLines: logging.warn('Skipping unavailable line %d that was requested from the command line.'%l) else: useLines+=[l] else: useLines=availableLines print "Will use lines ",',\n '.join([str(i)+' (%s)'%params[i]['description'] for i in useLines])+'.' else: print "Running %d stand-alone simulation(s) in batch mode."%(len(scripts)) useLines=[] params={} for i,s in enumerate(scripts): fakeLineNo=-i-1 useLines.append(fakeLineNo) params[fakeLineNo]={'description':'default','!SCRIPT':s} # fix script and set threads if script.py/num m=re.match('(.*)(/[0-9]+)$',s) if m: params[fakeLineNo]['!SCRIPT']=m.group(1) params[fakeLineNo]['!THREADS']=int(m.group(2)[1:]) jobs=[] executables=set() logFiles=[] for i,l in enumerate(useLines): script=scripts[0] if len(scripts)>0 else None envVars=[] nCores=opts.defaultThreads jobExecutable=executable jobAffinity=opts.affinity jobCount=opts.timing for col in params[l].keys(): if col[0]!='!': continue val=params[l][col] if col=='!OMP_NUM_THREADS' or col=='!THREADS': nCores=int(val) elif col=='!EXEC': jobExecutable=val elif col=='!SCRIPT': script=val elif col=='!AFFINITY': jobAffinity=eval(val) elif col=='!COUNT': jobCount=eval(val) else: envVars+=['%s=%s'%(head[1:],values[l][col])] if not script: raise ValueError('When only batch table is given without script to run, it must contain !SCRIPT column with simulation to be run.') logFile=logFormat.replace('$',script).replace('%',str(l)).replace('@',params[l]['description']) if (len(logFile)>230): #For very long files, to prevent system errors. logFile = logFile[0:230]+'_'+str(l)+logFile[-4:] #l is added to prevent name duplications. executables.add(jobExecutable) for j in range(0,jobCount if jobCount>0 else 1): jobNum=len(jobs) logFile2=logFile+('.%d'%j if opts.timing>0 else '') # append numbers to log file if it already exists, to prevent overwriting if logFile2 in logFiles: i=0; while logFile2+'.%d'%i in logFiles: i+=1 logFile2+='.%d'%i logFiles.append(logFile2) env='YADE_BATCH=' if table: env+='%s:%d'%(table,l) # keep YADE_BATCH empty (but still defined) if running a single simulation cmd=' %s%s -j %i -x %s 2>&1'%(jobExecutable,' --nice=%s'%nice if nice!=None else '',nCores,script) desc=params[l]['description'] if '!SCRIPT' in params[l].keys(): desc=script+'.'+desc # prepend filename if script is specified explicitly if opts.timing>0: desc+='[%d]'%j jobs.append(JobInfo(jobNum,desc,env+cmd,logFile2,nCores,script=script,table=table,lineNo=l,affinity=jobAffinity)) print "Job summary:" i = 0 for job in jobs: print ' #%d (%s%s):'%(job.num,job.id,'' if job.nCores==1 else '/%d'%job.nCores),job.command if not dryRun: # Create script file from model scriptname = '/tmp/'+opts.oar_script.split('/')[-1]+'.'+str(i) i = i+1; with open(opts.oar_script) as infile, open(scriptname, 'w') as outfile: for line in infile: line = line.replace("__COMMAND_HERE__", job.command) outfile.write(line) os.chmod(scriptname,0744); #os.system('%s'%(scriptname)); os.system('oarsub --project=%s -l/walltime=%s,cores=%i -O %s -E %s.err %s\n'%(project, walltime, job.nCores, job.log,job.log,scriptname)); sys.stdout.flush() yade.Omega().exitNoBacktrace() trunk-2018.02b/doc/000077500000000000000000000000001324306050200137335ustar00rootroot00000000000000trunk-2018.02b/doc/README000066400000000000000000000002521324306050200146120ustar00rootroot00000000000000All documentation is at http://www.yade-dem.org, in particular see https://yade-dem.org/index.php/Reference_documentation To generate documentation see sphinx/README trunk-2018.02b/doc/citing_yade.bib000066400000000000000000000023011324306050200166640ustar00rootroot00000000000000@book{yade:doc2, altnote = "\\url{http://yade-dem.org/doc/}", author = "V. \v{S}milauer and others", doi = "10.5281/zenodo.34073", note = "http://yade-dem.org/doc/", title = "{{Y}ade {D}ocumentation 2nd ed}", publisher = "{The {Y}ade Project}", year = "2015" } @incollection{yade:manual2, altnote = "\\url{http://yade-dem.org/doc/}", author = "V. \v{S}milauer and others", doi = "10.5281/zenodo.34043", booktitle = "{Y}ade {D}ocumentation 2nd ed", note = "http://yade-dem.org/doc/", publisher = "{The {Y}ade Project}", title = "Using and Programming", year = "2015" } @incollection{yade:background2, altnote = "\\url{http://yade-dem.org/doc/}", author = "V. \v{S}milauer and B. Chareyre", doi = "10.5281/zenodo.34044", booktitle = "{Y}ade {D}ocumentation 2nd ed", note = "http://yade-dem.org/doc/", publisher = "{The {Y}ade Project}", title = "DEM formulation", year = "2015" } @incollection{yade:reference2, altnote = "\\url{http://yade-dem.org/doc/}", author = "V. \v{S}milauer and others", doi = "10.5281/zenodo.34045", booktitle = "{Y}ade {D}ocumentation 2nd ed", note = "http://yade-dem.org/doc/", publisher = "{The {Y}ade Project}", title = "Reference Manual", year = "2015" } trunk-2018.02b/doc/how-to-release.md000066400000000000000000000010571324306050200171130ustar00rootroot00000000000000* Create RELEASE file in the root folder with the version number in it * Add new changelog entries to Changelog using "git shortlog PREVVERSION.." * Create branch using the following command and format "git checkout -b YYYY.MM" * Tag release "git tag -as YYYY.MMa -m"YYYY.MMa" * Return to master branch and remove RELEASE file * Push master, new branch and tags to github * Download tar.gz * Create asc-file (signature): gpg --armor --sign --detach-sig tarball.tar.gz * Upload new tarball on Launchpad * Make announcement on mailing list and on Launchpad trunk-2018.02b/doc/logging.conf.sample000066400000000000000000000032371324306050200175150ustar00rootroot00000000000000# This is example logging configuration file for yade. # # Place it in ~/.yade/logging.conf if you want it to be loaded and watched for changes during execution as well. # # log4cxx homepage is http://logging.apache.org/log4cxx/ # of particular interest is instroduction (http://logging.apache.org/log4cxx/manual/Introduction.html) # and pattern description (http://logging.apache.org/log4cxx/manual/classlog4cxx_1_1PatternLayout.html) # # Set root logger level to DEBUG, one appender for console and other to a file log4j.rootLogger=DEBUG,console,logfile log4j.appender.console=org.apache.log4j.ConsoleAppender # A1 uses PatternLayout. log4j.appender.console.layout=org.apache.log4j.PatternLayout #log4j.appender.console.layout.ConversionPattern=%-4r %-5p %c %x - %m%n # this is the default log4j.appender.console.layout.ConversionPattern=%-5p %-10c %m%n # this appender creates logfile that is being rotated following a size criterion # better would be to rotate it at every execution, but I am not sure if that exists in log4cxx... log4j.appender.logfile=org.apache.log4j.RollingFileAppender log4j.appender.logfile.File=/tmp/yade.log log4j.appender.logfile.MaxFileSize=10MB log4j.appender.logfile.MaxBackupIndex=1 # Keep one backup file log4j.appender.logfile.layout=org.apache.log4j.PatternLayout log4j.appender.logfile.layout.ConversionPattern=%-5p %t %-10c %m%n # Minimum level of messages you want to see # Childs inherit from their parents, unless overridden explicitly, therefore # the following disables DEBUG messages by default (everywhere) log4j.logger.yade=INFO # except for the classes where requested: #log4j.logger.yade.Omega=DEBUG log4j.logger.yade.DynLibManager=DEBUG trunk-2018.02b/doc/references.bib000066400000000000000000001014271324306050200165370ustar00rootroot00000000000000*************************** This file contains publications references from docstrings (as [Author2008]\_) of classes, in bibtex format. When adding new entries: 1. Keep entries in the form Author2008 (Author is the first author), Author2008b if repeated 2. Try to fill mandatory fields for given type of citations (http://en.wikipedia.org/wiki/Bibtex\\\\\#Entry\\\\\_Types) 3. Do not use {\'i} funny escapes for accents, put everything in straight utf-8 Thanks. *************************** @Article{ Bertrand2008, title = "Discrete element method (DEM) numerical modeling of double-twisted hexagonal mesh ", author = "D. Bertrand and F. Nicot and P. Gotteland and S. Lambert", journal = "Canadian Geotechnical Journal", pages = "1104--1117", volume = "45", number = "8", year = "2008" } @Article{ Bertrand2005, title = "Modelling a geo-composite cell using discrete analysis", author = "D. Bertrand and F. Nicot and P. Gotteland and S. Lambert", journal = "Computers and Geotechnics", pages = "564--577", volume = "32", number = "8", year = "2005" } @Article{ Chan2011, title = "Film drainage and coalescence between deformable drops and bubbles.", author = "D. Chan and E. Klaseboer and R. Manica", journal = "Soft Matter", pages = "2235-2264", volume = "7", number = "6", year = "2011" } @Article{ Chareyre2002a, title = "Theoretical versus experimental modeling of the anchorage capacity of geotextiles in trenches.", author = "B. Chareyre and L. Briancon and P. Villard", journal = "Geosynthet. Int.", pages = "97--123", volume = "9", number = "2", year = "2002" } @Article{ Chareyre2005, author = "Bruno Chareyre and Pascal Villard", title = "Dynamic Spar Elements and Discrete Element Methods in Two Dimensions for the Modeling of Soil-Inclusion Problems", publisher = "ASCE", year = "2005", journal = "Journal of Engineering Mechanics", volume = "131", number = "7", pages = "689--698", url = "https://yade-dem.org/wiki/File:Chareyre%26Villard2005_licensed.pdf", doi = "10.1061/(ASCE)0733-9399(2005)131:7(689)" } @PhDThesis{ Chareyre2003, author = "Bruno Chareyre", title = "Modélisation du comportement d'ouvrages composites sol-géosynthétique par éléments discrets - Application aux tranchées d'ancrage en tête de talus.", school = "Grenoble University", year = "2003", url = "http://tel.archives-ouvertes.fr/tel-00486807/fr/" } @InProceedings{ Chareyre2002b, title = "Discrete element modeling of curved geosynthetic anchorages with known macro-properties.", author = "B. Chareyre and P. Villard", booktitle = "Proc., First Int. PFC Symposium, Gelsenkirchen, Germany", year = "2002", pages = "197--203" } @Article{ Villard2004a, title = "Design methods for geosynthetic anchor trenches on the basis of true scale experiments and discrete element modelling", author = "P. Villard and B. Chareyre", journal = "Canadian Geotechnical Journal", pages = "1193--1205", volume = "41", year = "2004" } @Article{ Lu1998, author = "Ya Yan Lu", title = "Computing the Logarithm of a Symmetric Positive Definite Matrix", journal = "Appl. Numer. Math", year = "1998", volume = "26", pages = "483--496", doi = "10.1016/S0168-9274(97)00103-7", url = "http://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.37.759&rep=rep1&type=pdf" } @InProceedings{ Alonso2004, title = "Micro-mechanical investigation of the granular ratcheting", author = "F. Alonso-Marroquin and R. Garcia-Rojo and H.J. Herrmann", booktitle = "Cyclic Behaviour of Soils and Liquefaction Phenomena", publisher = "Taylor \& Francis", year = "2004", month = "april", isbn = "9058096203", pages = "3--10", editor = "T. Triantafyllidis", url = "http://www.comphys.ethz.ch/hans/p/334.pdf" } @Article{ McNamara2008, title = "Microscopic origin of granular ratcheting", author = "S. McNamara and R. García-Rojo and H. J. Herrmann", journal = "Physical Review E", year = "2008", number = "3", volume = "77", doi = "11.1103/PhysRevE.77.031304" } @InProceedings{ GarciaRojo2004, author = "R. García-Rojo and S. McNamara and H. J. Herrmann", title = "Discrete element methods for the micro-mechanical investigation of granular ratcheting", booktitle = "Proceedings ECCOMAS 2004", year = "2004", address = "Jyvaskyla", url = "http://www.ica1.uni-stuttgart.de/publications/2004/GMH04" } @Book{ Allen1989, author = "M. P. Allen and D. J. Tildesley", title = "Computer simulation of liquids", year = "1989", isbn = "0-19-855645-4", publisher = "Clarendon Press", address = "New York, NY, USA" } @Proceedings{ DeghmReport2006, title = "Annual Report 2006", year = "2006", editor = "F. V. Donzé", organization = "Discrete Element Group for Hazard Mitigation", publisher = "Université Joseph Fourier, Grenoble", url = " http://geo.hmg.inpg.fr/frederic/Discrete_Element_Group_FVD.html " } @Article{ Pournin2001, title = "Molecular-dynamics force models for better control of energy dissipation in numerical simulations of dense granular media", author = "L. Pournin and Th. M. Liebling and A. Mocellin", journal = "Phys. Rev. E", volume = "65", number = "1", pages = "011302", numpages = "7", year = "2001", month = "Dec", doi = "10.1103/PhysRevE.65.011302", publisher = "American Physical Society" } @Article{ Jung1997, author = "Derek Jung and Kamal K. Gupta", title = "Octree-based hierarchical distance maps for collision detection", journal = "Journal of Robotic Systems", volume = "14", number = "11", pages = "789--806", year = "1997", doi = "10.1002/(SICI)1097-4563(199711)14:11<789::AID-ROB3>3.0.CO;2-Q" } @Article{ Hubbard1996, author = "Philip M. Hubbard", title = "Approximating polyhedra with spheres for time-critical collision detection", journal = "ACM Trans. Graph.", volume = "15", number = "3", year = "1996", issn = "0730-0301", pages = "179--210", doi = "10.1145/231731.231732", publisher = "ACM", address = "New York, NY, USA" } @Article{ Klosowski1998, author = "James T. Klosowski and Martin Held and Joseph S. B. Mitchell and Henry Sowizral and Karel Zikan", title = "Efficient Collision Detection Using Bounding Volume Hierarchies of k-DOPs", journal = "IEEE Transactions on Visualization and Computer Graphics", year = "1998", volume = "4", pages = "21--36", url = "http://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.105.6555&rep=rep1&type=pdf" } @Article{ Munjiza2006, Author = "A. Munjiza and E. Rougier and N. W. M. John", Title = "MR linear contact detection algorithm", journal = "International Journal for Numerical Methods in Engineering", volume = "66", number = "1", pages = "46--71", year = "2006", publisher = "John Wiley \& Sons, Ltd.", doi = "10.1002/nme.1538", abstract = "Large-scale discrete element simulations, as well as a whole range of related problems, involve contact of a large number of separate bodies and an efficient and robust contact detection algorithm is necessary. There has been a number of contact detection algorithms with total detection time proportional to N ln(N) (where N is the total number of separate bodies) reported in the past. In more recent years algorithms with total CPU time proportional to N have been developed. In this work, a novel contact detection algorithm with total detection time proportional to N is proposed. The performance of the algorithm is not influenced by packing density, while memory requirements are insignificant. The algorithm is applicable to systems comprising bodies of a similar size. The algorithm is named MR (Munjiza-Rougier: Munjiza devised the algorithm, Rougier implemented it). In the second part of the paper the algorithm is extended to particles of different sizes. The new algorithm is called MMR (multi-step MR) algorithm." } @Article{ Munjiza1998, Author = "A. Munjiza and K. R. F. Andrews", Title = "NBS contact detection algorithm for bodies of similar size", journal = "International Journal for Numerical Methods in Engineering", volume = "43", number = "1", pages = "131--149", year = "1998", publisher = "John Wiley \& Sons, Ltd.", doi = "10.1002/(SICI)1097-0207(19980915)43:1<131::AID-NME447>3.0.CO;2-S", abstract = "Large-scale discrete element simulations, as well as a whole range of related problems, involve contact of a large number of separate bodies. In this context an efficient and robust contact detection algorithm is necessary. There has been a number of contact detection algorithms with total detection time (CPU time needed to detect all couples close to each other) proportional to Nln(N) (where N is the total number of separate bodies) reported in recent years. In this work a contact detection algorithm with total detection time proportional to N is reported. The algorithm is termed NBS, which stands for no binary search. In other words, the proposed algorithm involves no binary search at any stage. In addition the performance of the algorithm in terms of total detection time is not influenced by packing density, while memory requirements are insignificant. The only limitation of the algorithm is its applicability to the systems comprising bodies of similar size." } @Article{ Verlet1967, title = "Computer ``Experiments'' on Classical Fluids. I. Thermodynamical Properties of Lennard-Jones Molecules", author = "Loup Verlet", journal = "Phys. Rev.", volume = "159", number = "1", pages = "98", year = "1967", month = "Jul", doi = "10.1103/PhysRev.159.98", publisher = "American Physical Society" } @InProceedings{ Luding2008, author = "Stefan Luding", title = "Introduction to discrete element methods", booktitle = "European Journal of Environmental and Civil Engineering", publisher = "Lavoisier", year = "2008", isbn = "978-2-7462-2258-8", pages = "785--826", editor = "Félix Darve and Jean-Pierre Ollivier" } @Article{ Wang2009, title = "A new algorithm to model the dynamics of 3-D bonded rigid bodies with rotations", author = "Yucang Wang", journal = "Acta Geotechnica", publisher = "Springer Berlin / Heidelberg", issn = "1861-1125 (Print) 1861-1133 (Online)", url = "http://www.springerlink.com/content/l2306412v1004871/", abstract = "In this paper we propose a new algorithm to simulate the dynamics of 3-D interacting rigid bodies. Six degrees of freedom are introduced to describe a single 3-D body or particle, and six relative motions and interactions are permitted between bonded bodies. We develop a new decomposition technique for 3-D rotation and pay particular attention to the fact that an arbitrary relative rotation between two coordinate systems or two rigid bodies can not be decomposed into three mutually independent rotations around three orthogonal axes. However, it can be decomposed into two rotations, one pure axial rotation around the line between the centers of two bodies, and another rotation on a specified plane controlled by another parameter. These two rotations, corresponding to the relative axial twisting and bending in our model, are sequence-independent. Therefore all interactions due to the relative translational and rotational motions between linked bodies can be uniquely determined using such a two-step decomposition technique. A complete algorithm for one such simulation is presented. Compared with existing methods, this algorithm is physically more reliable and has greater numerical accuracy.", number = "2", pages = "117--127", volume = "4", doi = "10.1007/s11440-008-0072-1", year = "2009", keywords = "Bonded rigid-bodies - Decomposition of 3-D finite rotations - Quaternion", month = "July" } @Article{ Omelyan1999, title = "A New Leapfrog Integrator of Rotational Motion. The Revised Angular-Momentum Approach", author = "Igor P. Omelyan", journal = "Molecular Simulation", volume = "22", number = "3", year = "1999", doi = "10.1080/08927029908022097", url = "http://arxiv.org/pdf/physics/9901025" } @Article{ Neto2006, author = "Natale Neto and Luca Bellucci", title = "A new algorithm for rigid body molecular dynamics", journal = "Chemical Physics", volume = "328", number = "1--3", pages = "259--268", year = "2006", issn = "0301-0104", doi = "10.1016/j.chemphys.2006.07.009" } @Article{ Johnson2008, author = "Scott M. Johnson and John R. Williams and Benjamin K. Cook", title = "Quaternion-based rigid body rotation integration algorithms for use in particle methods", journal = "International Journal for Numerical Methods in Engineering", volume = "74", number = "8", pages = "1303--1313", year = "2008", doi = "10.1002/nme.2210" } @InProceedings{ Addetta2001, author = "G.A. {D'Addetta} and F. {Kun} and E. {Ramm} and H.J. {Herrmann}", title = "{From solids to granulates - Discrete element simulations of fracture and fragmentation processes in geomaterials.}", booktitle = "Continuous and Discontinuous Modelling of Cohesive-Frictional Materials", year = 2001, series = "Lecture Notes in Physics, Berlin Springer Verlag", volume = 568, editor = "{P.A. Vermeer, S. Diebels, W. Ehlers, H.J. Herrmann, S. Luding, E. Ramm}", pages = "231--+", url = "http://www.comphys.ethz.ch/hans/p/267.pdf" } @Book{ Pfc3dManual30, author = "ICG", title = "PFC3D (Particle Flow Code in 3D) Theory and Background Manual, version 3.0", publisher = "Itasca Consulting Group", year = "2003" } @PhDThesis{ Hentz2003, Author = "Séebastien Hentz", Title = "Modélisation d'une Structure en Béton Armé Soumise à un Choc par la méthode des Eléments Discrets", School = "Université Grenoble 1 -- Joseph Fourier", Year = "2003", Month = "October" } @InProceedings{ Price2007, Author = "Mathew Price and Vasile Murariu and Garry Morrison", title = "Sphere clump generation and trajectory comparison for real particles", booktitle = "Proceedings of Discrete Element Modelling 2007", year = "2007", url = "http://www.cogency.co.za/images/info/dem2007_sphereclump.pdf" } @InProceedings{ Kuhl2001, title = "Microplane modelling and particle modelling of cohesive-frictional materials", author = "E. Kuhl and G. A. D'Addetta and M. Leukart and E. Ramm", booktitle = "Continuous and Discontinuous Modelling of Cohesive-Frictional Materials", publisher = "Springer Berlin / Heidelberg", issn = "1616-6361", isbn = "978-3-540-41525-1", url = "http://www.springerlink.com/content/e50544266r506615", abstract = "This paper aims at comparing the microplane model as a particular representative of the class of continuous material models with a discrete particle model which is of discontinuous nature. Thereby, the constitutive equations of both approaches will be based onVoigtshypothesis defining the strain state on the individual microplanes as well as the relative particle displacements. Through an appropriate constitutive assumption, the microplane stresses and the contact forces can be determined. In both cases, the equivalence of microscopic and macroscopic virtual work yields the overall stress strain relation. An elastic and an elasto-plastic material characterization of the microplane model and the particle model are derived and similarities of both approaches are illustrated.", volume = "568", series = "Lecture Notes in Physics", year = "2001", pages = "31--46", doi = "10.1007/3-540-44424-6\_3" } @Article{ Thornton1991, title = "Impact of elastic spheres with and without adhesion", author = "Colin Thornton and K. K. Yin", year = "1991", journal = "Powder technology", volume = "65", pages = "153--166", doi = "10.1016/0032-5910(91)80178-L" } @Article{ Thornton2000, title = "Numerical simulations of deviatoric shear deformation of granular media", author = "Colin Thornton", journal = "Géotechnique", pages = "43--53", volume = "50", year = "2000", number = "1", doi = "10.1680/geot.2000.50.1.43" } @Article{ CundallStrack1979, author = "P.A. Cundall and O.D.L. Strack", title = "A discrete numerical model for granular assemblies", journal = "Geotechnique", volume = "", number = "29", pages = "47--65", year = "1979", doi = "10.1680/geot.1979.29.1.47" } @Article{ cgal, Author = "Jean-Daniel Boissonnat and Olivier Devillers and Sylvain Pion and Monique Teillaud and Mariette Yvinec", Title = "Triangulations in CGAL", Journal = "Computational Geometry: Theory and Applications", Volume = "22", Pages = "5--19", Year = "2002" } @InProceedings{ Satake1982, title = "Fabric tensor in granular materials.", author = "M. Satake", booktitle = "Proc., IUTAM Symp. on Deformation and Failure of Granular materials, Delft, The Netherlands", year = "1982", pages = "63--68" } @article{Hentz2004a, title = {Discrete element modelling of concrete submitted to dynamic loading at high strain rates}, author = {S. Hentz and F.V. Donzé and L.Daudeville}, doi = {10.1016/j.compstruc.2004.05.016 }, journal = {Computers and Structures}, pages = {2509--2524}, volume = {82}, number = {29--30}, year = {2004} } @article{Hentz2004b, title = {Identification and Validation of a Discrete Element Model for Concrete}, author = {S. Hentz and L. Daudeville and F.V. Donzé}, doi = {10.1061/(ASCE)0733-9399(2004)130:6(709)}, journal = {ASCE Journal of Engineering Mechanics}, pages = {709--719}, volume = {130}, number = {6}, year = {2004} } @article{Camborde2000a, title = {Numerical study of rock and Concrete behaviour by discrete element modelling}, author = {F. Camborde and C. Mariotti and F.V. Donzé}, journal = {Computers and Geotechnics}, pages = {225--247}, volume = {27}, year = {2000} } @article{Donze1999a, title = {Study of the behavior of concrete at high strain rate compressions by a discrete element method}, author = {F.V. Donzé and S.A. Magnier and L. Daudeville and C. Mariotti and L. Davenne}, doi = {10.1016/S0266-352X(00)00013-6}, journal = {ASCE J. of Eng. Mech}, pages = {1154--1163}, volume = {125}, number = {10}, year = {1999} } @article{Magnier1998a, title = {Numerical simulation of impacts using a discrete element method}, author = {S.A. Magnier and F.V. Donzé}, doi = {10.1002/(SICI)1099-1484(199807)3:3<257::AID-CFM50>3.0.CO;2-Z}, journal = {Mech. Cohes.-frict. Mater.}, pages = {257--276}, volume = {3}, year = {1998} } @article{Donze1995a, title = {Formulation of a three-dimensional numerical model of brittle behavior}, author = {F.V. Donzé and S.A. Magnier}, journal = {Geophys. J. Int.}, pages = {790--802}, volume = {122}, year = {1995} } @article{Donze1994a, title = {Numerical simulation of faults and shear zones}, author = {F.V. Donzé and P. Mora and S.A. Magnier}, journal = {Geophys. J. Int.}, pages = {46--52}, volume = {116}, year = {1994} } @article{Hentz2005a, title = {Discrete elements modeling of a reinforced concrete structure submitted to a rock impact}, author = {S. Hentz and F.V. Donzé and L.Daudeville}, journal = {Italian Geotechnical Journal}, pages = {83--94}, volume = {XXXIX}, number = {4}, year = {2005} } @article{Donze2004a, title = {Simulation of the Blasting Patterns in Shaft Sinking Using a Discrete Element Method}, author = {F.V. Donzé and P. Bernasconi}, journal = {Electronic Journal of Geotechnical Engineering}, pages = {1--44}, volume = {9}, number = {B}, year = {2004} } @InProceedings{Kettner2011, AUTHOR = {Lutz Kettner and Andreas Meyer and Afra Zomorodian}, BOOKTITLE = {{CGAL} User and Reference Manual}, PUBLISHER = {{CGAL Editorial Board}}, TITLE = {Intersecting Sequences of {dD} Iso-oriented Boxes}, YEAR = {2011}, EDITION = {{3.9}}, url = {http://www.cgal.org/Manual/3.9/doc_html/cgal_manual/packages.html#Pkg:BoxIntersectionD} } @InProceedings{Pion2011, AUTHOR = {Sylvain Pion and Monique Teillaud}, BOOKTITLE = {{CGAL} User and Reference Manual}, PUBLISHER = {{CGAL Editorial Board}}, TITLE = {{3D} Triangulations}, YEAR = {2011}, EDITION = {{3.9}}, url = {http://www.cgal.org/Manual/3.9/doc_html/cgal_manual/packages.html#Pkg:Triangulation3} } @article{Calvetti1997, title={Experimental micromechanical analysis of a 2D granular material: relation between structure evolution and loading path}, author={Calvetti, F. and Combe, G. and Lanier, J.}, journal={Mechanics of Cohesive-frictional Materials}, volume={2}, number={2}, pages={121--163}, year={1997} } @article{Bagi2006, title = {Analysis of microstructural strain tensors for granular assemblies}, journal = {International Journal of Solids and Structures}, volume = {43}, number = {10}, pages = {3166 - 3184}, year = {2006}, doi = {10.1016/j.ijsolstr.2005.07.016}, author = {Katalin Bagi} } @article {Weigert1999, author = {Weigert, Tom and Ripperger, Siegfried}, title = {Calculation of the Liquid Bridge Volume and Bulk Saturation from the Half-filling Angle}, journal = {Particle & Particle Systems Characterization}, volume = {16}, number = {5}, publisher = {WILEY-VCH Verlag GmbH}, issn = {1521-4117}, url = {http://dx.doi.org/10.1002/(SICI)1521-4117(199910)16:5<238::AID-PPSC238>3.0.CO;2-E}, doi = {10.1002/(SICI)1521-4117(199910)16:5<238::AID-PPSC238>3.0.CO;2-E}, pages = {238--242}, year = {1999}, } @article{Willett2000, author = {Willett, Christopher D. and Adams, Michael J. and Johnson, Simon A. and Seville, Jonathan P. K.}, title = {Capillary Bridges between Two Spherical Bodies}, journal = {Langmuir}, volume = {16}, number = {24}, pages = {9396-9405}, year = {2000}, doi = {10.1021/la000657y}, URL = {http://pubs.acs.org/doi/abs/10.1021/la000657y}, eprint = {http://pubs.acs.org/doi/pdf/10.1021/la000657y} } @article{Herminghaus2005, author = {Herminghaus, S.}, title = {Dynamics of wet granular matter}, journal = {Advances in Physics}, volume = {54}, number = {3}, pages = {221-261}, year = {2005}, doi = {10.1080/00018730500167855}, URL = {http://www.tandfonline.com/doi/abs/10.1080/00018730500167855}, eprint = {http://www.tandfonline.com/doi/pdf/10.1080/00018730500167855} } @article{Rabinov2005, author = "RABINOVICH Yakov I. and ESAYANUR Madhavan S. and MOUDGIL Brij M.", institution = "Particle Engineering Research Center, University of Florida, USA; Department of Materials Science and Engineering, University of Florida, USA", title = "Capillary forces between two spheres with a fixed volume liquid bridge : Theory and experiment", journal = "Langmuir", year = "2005", volume = "21", number = "24", pages = "10992--10997", editor = "American Chemical Society", issn = "0743-7463", note = "eng", keywords = "Atomic force microscopy; Equation; Plane surface; Rupture; Thermodynamics; Methodology; Gas pressure; Calculation; Solid; Interaction energy; Geometry; Plate; Separation; Prediction; Powder; Vapor; Capillary condensation; Theory; Liquid; Force", url = "http://www.refdoc.fr/Detailnotice?idarticle=7435486" } @article{Luding2008b, year={2008}, issn={1434-5021}, journal={Granular Matter}, volume={10}, number={4}, doi={10.1007/s10035-008-0099-x}, title={Cohesive, frictional powders: contact models for tension}, url={http://dx.doi.org/10.1007/s10035-008-0099-x}, publisher={Springer-Verlag}, keywords={Granular materials; Molecular dynamics (MD) and discrete elementmodel (DEM) force-laws; Friction; Rolling- and torsion-resistance; Adhesion; Plastic deformation}, author={Luding, Stefan}, pages={235-246}, language={English} } @article{Zhou1999536, title = "Rolling friction in the dynamic simulation of sandpile formation", journal = "Physica A: Statistical Mechanics and its Applications", volume = "269", number = "2–4", pages = "536--553", year = "1999", issn = "0378-4371", doi = "10.1016/S0378-4371(99)00183-1", url = "http://www.sciencedirect.com/science/article/pii/S0378437199001831", author = "Y.C. Zhou and B.D. Wright and R.Y. Yang and B.H. Xu and A.B. Yu", } @article{Antypov2011, author={D. Antypov and J. A. Elliott}, title={On an analytical solution for the damped Hertzian spring}, journal={EPL (Europhysics Letters)}, volume={94}, number={5}, pages={50004}, url={http://stacks.iop.org/0295-5075/94/i=5/a=50004}, year={2011} } @article{Ivars2011, title = "The synthetic rock mass approach for jointed rock mass modelling ", journal = "International Journal of Rock Mechanics and Mining Sciences ", volume = "48", number = "2", pages = "219 - 244", year = "2011", doi = "10.1016/j.ijrmms.2010.11.014", author = "Diego Mas Ivars and Matthew E. Pierce and Caroline Darcel and Juan Reyes-Montes and David O. Potyondy and R. Paul Young and Peter A. Cundall" } @article{Potyondy2004, title = "A bonded-particle model for rock ", journal = "International Journal of Rock Mechanics and Mining Sciences ", volume = "41", number = "8", pages = "1329 - 1364", year = "2004", doi = "10.1016/j.ijrmms.2004.09.011", author = "D.O. Potyondy and P.A. Cundall" } @book{Radjai2011, title={Discrete-Element Modeling of Granular Materials}, author={Radjai, F. and Dubois, F.}, isbn={9781848212602}, lccn={2010051720}, url={http://books.google.com/books?id=w2ijcQAACAAJ}, year={2011}, publisher={John Wiley \& Sons} } @article{Schwager2007, year={2007}, issn={1434-5021}, journal={Granular Matter}, volume={9}, number={6}, doi={10.1007/s10035-007-0065-z}, title={Coefficient of restitution and linear–dashpot model revisited}, url={http://arxiv.org/pdf/cond-mat/0701278}, publisher={Springer-Verlag}, keywords={Particle collisions; Coefficient of restitution}, author={Schwager, Thomas and Pöschel, Thorsten}, pages={465-469}, language={English} } @article{Lambert2008, title={Comparison between two capillary forces models}, author={Lambert, Pierre and Chau, Alexandre and Delchambre, Alain and Régnier, Stéphane}, journal={Langmuir}, volume={24}, number={7}, pages={3157--3163}, year={2008}, publisher={ACS Publications} } @inproceedings{Mueller2003, author = {Müller, Matthias and Charypar, David and Gross, Markus}, title = {Particle-based Fluid Simulation for Interactive Applications}, booktitle = {Proceedings of the 2003 ACM SIGGRAPH/Eurographics Symposium on Computer Animation}, series = {SCA '03}, year = {2003}, isbn = {1-58113-659-5}, location = {San Diego, California}, pages = {154--159}, numpages = {6}, url = {http://dl.acm.org/citation.cfm?id=846276.846298}, acmid = {846298}, publisher = {Eurographics Association}, address = {Aire-la-Ville, Switzerland, Switzerland}, } @article {Soulie2006, author = {Soulié, F. and Cherblanc, F. and El Youssoufi, M.S. and Saix, C.}, title = {Influence of liquid bridges on the mechanical behaviour of polydisperse granular materials}, journal = {International Journal for Numerical and Analytical Methods in Geomechanics}, volume = {30}, number = {3}, publisher = {John Wiley & Sons, Ltd.}, issn = {1096-9853}, url = {http://dx.doi.org/10.1002/nag.476}, doi = {10.1002/nag.476}, pages = {213--228}, keywords = {liquid bridge, polydisperse, cohesion, capillary force, humid granular media, DEM simulation}, year = {2006}, } @article{Mani2013, year={2013}, issn={1434-5021}, journal={Granular Matter}, volume={15}, number={4}, doi={10.1007/s10035-012-0387-3}, title={Liquid migration in sheared unsaturated granular media}, url={http://dx.doi.org/10.1007/s10035-012-0387-3}, publisher={Springer Berlin Heidelberg}, keywords={Wet granular matter; Contact dynamics simulations; Liquid bridge; Cohesion; Liquid migration}, author={Mani, Roman and Kadau, Dirk and Herrmann, HansJ.}, pages={447-454}, language={English} } @article{Richardson1954, author = {Richardson, J. F., and W. N. Zaki}, title = {Sedimentation and fluidization: Part i}, journal = {Trans. Instn. Chem. Engrs}, year = {1954}, volume = {32} } @article{RevilBaudard2013, author = {Revil-Baudard, T. and Chauchat, J.}, title = {A two-phase model for sheet flow regime based on dense granular flow rheology}, journal = {Journal of Geophysical Research: Oceans}, year = {2013}, volume = {118}, pages = {619--634}, number = {2} } @article{RevilBaudard2015, title={Investigation of sheet-flow processes based on novel acoustic high-resolution velocity and concentration measurements}, volume={767}, DOI={10.1017/jfm.2015.23}, journal={Journal of Fluid Mechanics}, publisher={Cambridge University Press}, author={Revil-Baudard, T. and Chauchat, J. and Hurther, D. and Barraud, P-A.}, year={2015}, pages={1–30} } @Article{Li1995, title={Multi-phase model on sediment transport in sheet-flow regime under oscillatory flow}, author = {Li, L. and Sawamoto, M.}, journal = {Coastal engineering Japan}, year = {1995}, pages = {157-178}, volume = {38} } @article{Schmeeckle2007, author = {Schmeeckle, Mark W. and Nelson, Jonathan M. and Shreve, Ronald L.}, title = {Forces on stationary particles in near-bed turbulent flows}, journal = {Journal of Geophysical Research: Earth Surface}, year = {2007}, volume = {112}, number = {F2}, doi = {10.1029/2006JF000536}, url = {http://dx.doi.org/10.1029/2006JF000536} } @article{Wiberg1985, author = {Wiberg, Patricia L. and Smith, J. Dungan}, title = {A theoretical model for saltating grains in water}, journal = {Journal of Geophysical Research: Oceans}, year = {1985}, volume = {90}, pages = {7341--7354}, number = {C4} } @book{Dallavalle1948, title = {Micrometrics : The technology of fine particles}, publisher = {Pitman Pub. Corp}, year = {1948}, author = {J. M. DallaValle}, volume = {2nd edition} } @article{Monaghan1992, author = {{Monaghan}, J.~J.}, title = "{Smoothed particle hydrodynamics}", journal = {\araa}, year = 1992, volume = 30, pages = {543-574}, doi = {10.1146/annurev.aa.30.090192.002551} } @article{Morris1997, title = "Modeling Low Reynolds Number Incompressible Flows Using \{SPH\} ", journal = "Journal of Computational Physics ", volume = "136", number = "1", pages = "214 - 226", year = "1997", note = "", issn = "0021-9991", doi = "http://dx.doi.org/10.1006/jcph.1997.5776", url = "http://www.sciencedirect.com/science/article/pii/S0021999197957764", } @article{Lucy1977, author = {{Lucy}, L.~B.}, title = "{A numerical approach to the testing of the fission hypothesis}", journal = {\aj}, year = 1977, month = dec, volume = 82, pages = {1013-1024}, doi = {10.1086/112164}, url = {http://adsabs.harvard.edu/full/1977AJ.....82.1013L} } @article{Monaghan1985, author = {{Monaghan}, J.~J. and {Lattanzio}, J.~C.}, title = "{A refined particle method for astrophysical problems}", journal = {\aap}, keywords = {Computational Astrophysics, Gravitational Collapse, Gravitational Fields, Many Body Problem, Molecular Clouds, Stellar Evolution, Angular Momentum, Binary Stars, Computational Grids, Interpolation, Kernel Functions, Particle Mass, Stellar Orbits}, year = 1985, month = aug, volume = 149, pages = {135-143}, url = {http://adsabs.harvard.edu/abs/1985A%26A...149..135M} } @Book{ yade:doc, PUBLISHER = "{The {Y}ade Project}", TITLE = "{Y}ade {D}ocumentation", YEAR = "2010", AUTHOR = "V. Šmilauer and E. Catalano and B. Chareyre and S. Dorofeenko and J. Duriez and A. Gladky and J. Kozicki and C. Modenese and L. Scholtès and L. Sibille and J. Stránský and K. Thoeni", ALTEDITOR = "Václav Šmilauer", EDITION = "{1st}", NOTE = "{http://yade-dem.org/doc/}", ALTNOTE = "\\url{http://yade-dem.org/doc/}" } @incollection{ yade:manual, author={V. Šmilauer and A. Gladky and J. Kozicki and C. Modenese and J. Stránský}, booktitle={Yade {D}ocumentation}, editor={V. Šmilauer}, publisher={The Yade Project}, title={Yade, Using and Programming}, year={2010}, edition={1st}, url={https://yade-dem.org/w/images/0/09/YadeManuals.pdf}, note={http://yade-dem.org/doc/} } @incollection{ yade:background, title = "{Y}ade DEM Formulation", author = "V. Šmilauer and B. Chareyre", booktitle = "{Y}ade {D}ocumentation", editor = "V. Šmilauer", publisher = "{The {Y}ade Project}", year = "2010", edition = "{1st}", note = "http://yade-dem.org/doc/formulation.html", altnote = "\\url{http://yade-dem.org/doc/formulation.html}", url={https://yade-dem.org/w/images/e/e0/YadeFormulation.pdf} } @incollection{ yade:reference, author = "V. Šmilauer and E. Catalano and B. Chareyre and S. Dorofeenko and J. Duriez and A. Gladky and J. Kozicki and C. Modenese and L. Scholtès and L. Sibille and J. Stránský and K. Thoeni", booktitle = "{Y}ade {D}ocumentation", editor = "V. Šmilauer", publisher = "{The {Y}ade Project}", title = "{Y}ade {R}eference {D}ocumentation", year = "2010", edition = "{1st}", note = "http://yade-dem.org/doc/", altnote = "\\url{http://yade-dem.org/doc/}", url= {https://yade-dem.org/w/images/9/98/YadeRefDoc.pdf} } @article{Tonon2005, author = {{Tonon}, F.}, title = "{Explicit Exact Formulas for the 3-D Tetrahedron Inertia Tensor in Terms of its Vertex Coordinates}", journal = {Journal of mathematics and statistics}, year = 2005, volume = 1, pages = {135-143}, url = {http://docsdrive.com/pdfs/sciencepublications/jmssp/2005/8-11.pdf} } @misc{Lourenco1994, author = {P B Lourenço}, title = {Analysis of masonry structures with interface elements. theory and applications}, year = {1994}, note = {Report No. 03-21-22-0-01, Delft University of Technology, Faculty of Civil Engineering}, url = {http://www.csarmento.uminho.pt/docs/ncr/de_civil/1994_Lourenco.pdf} } trunk-2018.02b/doc/sphinx/000077500000000000000000000000001324306050200152445ustar00rootroot00000000000000trunk-2018.02b/doc/sphinx/FEMxDEM.rst000066400000000000000000000152341324306050200171300ustar00rootroot00000000000000.. _FEMxDEM: ############################################################################################################################## Parallel hierarchical multiscale modeling of granular media by coupling FEM and DEM with open-source codes Escript and YADE ############################################################################################################################## Authors: Ning Guo and Jidong Zhao Institution: Hong Kong University of Science and Technology Escript download page: https://launchpad.net/escript-finley mpi4py download page (optional, require MPI): https://bitbucket.org/mpi4py/mpi4py Tested platforms: Desktop with Ubuntu 10.04, 32 bit; Server with Ubuntu 12.04, 14.04, 64 bit; Cluster with Centos 6.2, 6.5, 64 bit; Introduction ^^^^^^^^^^^^^^^^ The code is built upon two open source packages: Yade for DEM modules and Escript for FEM modules. It implements the hierarchical multiscale model (FEMxDEM) for simulating the boundary value problem (BVP) of granular media. FEM is used to discretize the problem domain. Each Gauss point of the FEM mesh is embedded a representative volume element (RVE) packing simulated by DEM which returns local material constitutive responses to FEM. Typically, hundreds to thousands of RVEs are involved in a medium-sized problem which is critically time consuming. Hence parallelization is achieved in the code through either multiprocessing on a supercomputer or mpi4py on a HPC cluster (require MPICH or Open MPI). The MPI implementation in the code is quite experimental. The "mpipool.py" is contributed by Lisandro Dalcin, the author of mpi4py package. Please refer to the examples for the usage of the code. Work on the YADE side ^^^^^^^^^^^^^^^^^^^^^^^^ The version of YADE should be at least rev3682 in which Bruno added the stringToScene function. Before installation, I added some functions to the source code (in "yade" subfolder). But only one function ("Shop::getStressAndTangent" in "./pkg/dem/Shop.cpp") is necessary for the FEMxDEM coupling, which returns the stress tensor and the tangent operator of a discrete packing. The former is homogenized using the Love's formula and the latter is homogenized as the elastic modulus. After installation and we get the executable file: yade-versionNo. We then generate a .py file linked to the executable file by "ln yade-versionNo yadeimport.py". This .py file will serve as a wrapped library of YADE. Later on, we will import all YADE functions into the python script through "from yadeimport import \*" (see simDEM.py file). Open a python terminal. Make sure you can run :: import sys sys.path.append('where you put yadeimport.py') from yadeimport import * Omega().load('your initial RVE packing, e.g. 0.yade.gz') If you are successful, you should also be able to run :: from simDEM import * Work on the Escript side ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ No particular requirement. But make sure the modules are callable in python, which means the main folder of Escript should be in your PYTHONPATH and LD_LIBRARY_PATH. The modules are wrapped as a class in msFEM\*.py. Open a python terminal. Make sure you can run:: from esys.escript import * from esys.escript.linearPDEs import LinearPDE from esys.finley import Rectangle (Note: Escript is used for the current implementation. It can be replaced by any other FEM package provided with python bindings, e.g. FEniCS (http://fenicsproject.org). But the interface files "msFEM\*.py" need to be modified.) Example tests ^^^^^^^^^^^^^^^^ After Steps 1 & 2, one should be able to run all the scripts for the multiscale analysis. The initial RVE packing (default name "0.yade.gz") should be provided by the user (e.g. using YADE to prepare a consolidated packing), which will be loaded by simDEM.py when the problem is initialized. The sample is initially uniform as long as the same RVE packing is assigned to all the Gauss points in the problem domain. It is also possible for the user to specify different RVEs at different Gauss points to generate an inherently inhomogeneous sample. While simDEM.py is always required, only one msFEM\*.py is needed for a single test. For example, in a 2D (3D) dry test, msFEM2D.py (msFEM3D.py) is needed; similarly for a coupled hydro-mechanical problem (2D only, saturated), msFEMup.py is used which incorporates the u-p formulation. Multiprocessing is used by default. To try MPI parallelization, please set useMPI=True when constructing the problem in the main script. Example tests given in the "example" subfolder are listed below. Note: The initial RVE packing (named 0.yade.gz by default) needs to be generated, e.g. using prepareRVE.py in "example" subfolder for a 2D packing (similarly for 3D). #. **2D drained biaxial compression test on dry dense sand** (biaxialSmooth.py) *Note*: Test description and result were presented in [Guo2014]_ and [Guo2014c]_. #. **2D passive failure under translational mode of dry sand retained by a rigid and frictionless wall** (retainingSmooth.py) *Note:* Rolling resistance model (CohFrictMat) is used in the RVE packing. Test description and result were presented in [Guo2015]_. #. **2D half domain footing settlement problem with mesh generated by Gmsh** (footing.py, footing.msh) *Note:* Rolling resistance model (CohFrictMat) is used in the RVE packing. Six-node triangle element is generated by Gmsh with three Gauss points each. Test description and result were presented in [Guo2015]_. #. **3D drained conventional triaxial compression test on dry dense sand using MPI parallelism** (triaxialRough.py) *Note 1:* The simulation is very time consuming. It costs ~4.5 days on one node using multiprocessing (16 processes, 2.0 GHz CPU). When useMPI is switched to True (as in the example script) and four nodes are used (80 processes, 2.2 GHz CPU), the simulation costs less than 24 hours. The speedup is about 4.4 in our test. *Note 2:* When MPI is used, mpi4py is required to be installed. The MPI implementation can be either MPICH or Open MPI. The file "mpipool.py" should also be placed in the main folder. Our test is based on openmpi-1.6.5. This is an on-going work. Test description and result will be presented later. #. **2D globally undrained biaxial compression test on saturated dense sand with changing permeability using MPI parallelism** (undrained.py) *Note:* This is an on-going work. Test description and result will be presented later. Disclaim ^^^^^^^^^^^^ This work extensively utilizes and relies on some third-party packages as mentioned above. Their contributions are acknowledged. Feel free to use and redistribute the code. But there is NO warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. trunk-2018.02b/doc/sphinx/GPUacceleration.rst000066400000000000000000000152561324306050200210140ustar00rootroot00000000000000.. _GPUacceleration: ======================================= Accelerating Yade's PFV scheme with GPU ======================================= (Note: we thank Robert Caulk for preparing and sharing this guide) Summary ======= This document contains instructions for adding Suite Sparse's GPU acceleration to Yade's Pore Finite Volume (PFV) scheme. The guide is intended for intermediate to advanced Yade users. As such, the guide assumes the reader knows how to modify and compile Yade's source files. Readers will find that this guide introduces system requirements, installation of necessary prerequisites, and installation of the modified Yade. Lastly, the document shows the performance enhancement expected by acceleration of the factorization of various model sizes. Hardware, Software, and Model Requirements ========================================== - Hardware: - `CUDA-capable GPU `__ with >3 GB memory recommended (64 mb required) - Software: - NVIDIA CUDA Toolkit - SuiteSparse (CHOLMOD v2.0.0+) - Metis (comes with SuiteSparse) - CuBlas - OpenBlas - Lapack - Model: - Fluid coupling (Pore Finite Volume aka Yade's "FlowEngine") - >10k particles, but likely >30k to see significant speedups - Frequent remeshing requirements Install CUDA ============ The following instructions to install CUDA are a boiled down version of `these instructions `__. :: lspci | grep -i nvidia #Check your graphics card # Install kernel headers and development packages sudo apt-get install linux-headers-$(uname -r) #Install repository meta-data (see **Note below): sudo dpkg -i cuda-repo-__.deb sudo apt-get update #update the Apt repository cache sudo apt-get install cuda #install CUDA # Add the CUDA library to your path export PATH=/usr/local/cuda-8.0/bin\${PATH:+:\${PATH}} export LD_LIBRARY_PATH=/usr/local/cuda-8.0/lib64\ \${LD_LIBRARY_PATH:+:\${LD_LIBRARY_PATH}} **Note: use `this tool `__ to determine your ``__`` values. Restart your computer. Verify your CUDA installation by navigating to ``/usr/local/cuda-8.0/samples`` and executing the ``make`` command. You may need to edit the findglib.mk file with the following command: :: sed -i "s/nvidia-367/nvidia-375/g" 'grep "nvidia-367" -r ./ -l' Now you can navigate to ``/usr/local/cuda-8.0/samples/1_Utilities/deviceQuery/`` and execute ``./deviceQuery`` . Verify the ``Result = PASS``. Install OpenBlas, and Lapack ============================ Execute the following command: :: sudo apt-get install libopenblas-dev liblapack-dev Install SuiteSparse =================== Download the `SuiteSparse package `__ and extract the files to ``/usr/local/``. Run ``make config`` and verify ``CUDART_LIB`` and ``CUBLAS_LIB`` point to your cuda installed libraries. The typical paths will follow ``CUDART_LIB=/usr/local/cuda-x.y/lib64`` and ``CUBLAS_LIB=/usr/local/cuda-x.y/lib64``. If the paths are blank, you may need to navigate to to ``CUDA_PATH`` in ``/usr/local/SuiteSparse/SuiteSparse_config/SuiteSparse_config.mk`` and modify it manually to point to your cuda installation. Navigate back to the main SuiteSparse folder and execute ``make``. SuiteSparse is now compiled and installed on your machine. Test CHOLMOD's GPU functionality by navigating to ``SuiteSparse/CHOLMOD/Demo`` and executing ``sh gpu.sh``. Note: you will need to download the nd6k.mtx from `here `__ and put it in your home directory. Compile Yade ============ Following the instructions outlined `here `__, run ``cmake`` with ``-DCHOLMOD_GPU=ON`` among other flags. Check the output to verify the paths to CHOLMOD (and dependencies such as AMD), SuiteSparse, CuBlas, and Metis are all identified as the paths we created when we installed these packages. Here is an example of the output you need to inspect: :: -- Found Cholmod in /usr/local/SuiteSparse/lib/libcholmod.so -- Found OpenBlas in /usr/lib/libopenblas.so -- Found Metis in /usr/local/SuiteSparse/lib/libmetis.so -- Found CuBlas in /usr/local/cuda-x.y/libcublas.so -- Found Lapack in /usr/lib/liblapack.so If you have multiple versions of any of these packages, the system may find the wrong one. In this case, you will need to either uninstall the old libraries (e.g. ``sudo apt-get remove libcholmod`` if the other library was installed with apt-get) or edit the paths within ``cMake/Find_____.cmake``. If you installed a version of Cuda other than 8.0 in a different location than ``/usr/local``, you will need to edit ``cMake/FindCublas.cmake`` to reflect these changes before compilation. Metis is compiled with SuiteSparse, so the Metis library and Metis include should link to files within ``usr/local/SuiteSparse/``. Controlling the GPU =================== The GPU accelerated solver can be activated within Yade by setting ``flow.useSolver=4`` and ``flow.multithread=True``. There are several environment variables that control the allowable memory, allowable GPU matrix size, etc. These are highlighted within the CHOLMOD User Guide, which can be found in ``SuiteSparse/CHOLMOD/Doc``. At the minimum, the user needs to set the environment variable by executing ``export CHOLMOD_USE_GPU=1``. We also recommend you designate half of your available GPU memory with ``export CHOLMOD_GPU_MEM_BYTES=3000000000`` (for a 6GB graphics card), since the multithreaded solver will keep 2 solvers running at a time to improve efficiency. Expected performance ==================== [Catalano2012]_ demonstrated the performance of DEM+PFV coupling and highlighted its strengths and weaknesses. A significant strength of the DEM+PFV coupling is the asymptotic nature of triangulation costs, volume calculation costs, and force calculation costs ( [Catalano2012]_, Figure 5.4). In other words, increasing the number of particles beyond ~200k results in negligible additional computational costs. The main weakness of the DEM+PFV coupling is the exponential increase of computational cost of factoring and solving increasingly larger systems of linear equations ( [Catalano2012]_, Figure 5.7). As shown in Fig. `fig-cpuvsgpu`_, the employment of GPU alleviates this weakness (at least for <200k particles) and speeds up the factorization by up to 90%. .. _fig-cpuvsgpu: .. figure:: fig/particlesVsFactortime.* :scale: 60 % :align: center Full GPU factorization time and 1-core CPU factorization time for various sized Yade+PFV models Note: GeForce 1080 GTX 8GB GPU + 10 core Intel i7-6950x (4.4 Hz O.C.) CPU trunk-2018.02b/doc/sphinx/README000066400000000000000000000031451324306050200161270ustar00rootroot00000000000000Generating yade documentation =============================== 1. Get sphinx 1.0 (development snapshots are fine) either from * packages at https://launchpad.net/~yade-users/+archive/external ** sudo apt-add-repository ppa:yade-users/external; sudo apt-get update; sudo apt-get install python-sphinx python-bibtex * from its repository: ** hg clone http://bitbucket.org/birkenfeld/sphinx/ ** optionally apply the patch (only avoids warnings) from http://bitbucket.org/birkenfeld/sphinx/issue/407/patch-do-not-inspect-boost-python-functions ** run python setup.py install 2. Python default encoding must be set to utf-8. As it is not the default, PYTHONPATH=. is used to make Python import local sitecustomize.py file at startup which changes this setting. (The value cannot be set after startup, from within the script) 3. Run (from the current directory, replacing PREFIX by the yade install path) PYTHONPATH=. yade yadeSphinx.py [optional outDir; _build by default] It will be create/update $outDir/{html,latex}. The latex file (Yade.tex) is to be processed with xelatex (rather than latex). See README.latex for details. ==== ipython_console_highlighting.py is copied from matplotlib's trunk/matplotlib/lib/matplotlib/sphinxext Generating yade documentation as a book (for the official chapter references) ============================================================================= First run yadeSphinx.py as explained above. It will generate modules documentation and other things that we use for generating the book. Second run yadeBookSphinx.py (same command as above, adding 'Book' in the script name) trunk-2018.02b/doc/sphinx/amazonEC2.rst000066400000000000000000000162571324306050200175700ustar00rootroot00000000000000.. _CloudComputing: ============================================= Using YADE with cloud computing on Amazon EC2 ============================================= (Note: we thank Robert Caulk for preparing and sharing this guide) Summary ======= This guide is intended to help YADE users migrate their simulations to Amazon Web Service (AWS) EC2. Two of the most notable benefits of using scalable cloud computing for YADE include decreased upfront cost and increased productivity. The entire process, from launching an instance, to installing YADE, to running a YADE simulation on the cloud can be executed in under 5 minutes. Once the EC2 instance is running, you can submit YADE scripts the same way you would submit jobs on a local workstation. Launching an EC2 instance ========================= .. _fig-console: .. figure:: fig/launchinstance.* :scale: 60 % :align: center Amazon Web Services (AWS) Console Start by signing into the console on `Amazon EC2 `__. This will require an existing or new Amazon account. Once you’ve signed in, you should find the EC2 console by clicking on ‘services’ in the upper left hand corner of the AWS homepage. Start by clicking on the \`\`launch an instance“ blue button (Fig. `fig-console`_). Select the Amazon Machine Image (AMI): \`\`Ubuntu Server 16.04 LTS\`\` (Fig. `fig-ubuntu`_). .. _fig-ubuntu: .. figure:: fig/ubunut.* :scale: 60 % :align: center Select Ubuntu server 16.04 LTS AMI You will now select the instance type. It is worth looking at the `specifications for each of the instances `__ so you can properly select the power you need for you YADE simulation. This document will not go into detail in the selection of size, but you can find plenty of `YADE specific performance reports `__ that will help you decide. However, the instance type is an important selection. The \`\`Compute Optimized" instances are necessary for most YADE simulations because they provide access to high performing processors and guaranteed computing power. The C3.2xlarge (Fig. `fig-type`_) is equivalent to an 8 core 2.8ghz Xeon E5 with 25 mb of cache, which is likely the best option for medium-large scale YADE simulations. .. _fig-type: .. figure:: fig/instancetype.* :scale: 60 % :align: center Compute optimized (C3) instance tier Before launching, you will be asked to \`\`select an existing key pair or create a new key pair". Create a new one, download it, and place it in a folder that you know the path to. Modify the permissions on the file by navigating to the same directory in the terminal and typing: :: chmod 400 KeyPair.pem Now the instance is launched, you will need to connect to it via SSH. On unix systems this is as easy as typing: :: ssh -i path/to/KeyPair.pem ubuntu@ec2-XX-XXX-XX-XX.us-west-2.compute.amazon.com into the terminal. There are other options such as using PuTTY, or even a java based terminal on the AWS website. You can find the necessary information by navigating to \`\`Instances" in the left menu of the AWS console. Right click on the instance as shown in Fig. `fig-connect`_ and click connect. .. _fig-connect: .. figure:: fig/connect.* :scale: 60 % :align: center Connecting to the instance You will be presented with the public DNS, which should look something like Fig. `fig-dns`_. .. _fig-dns: .. figure:: fig/publicdns.* :scale: 100 % :align: center Public DNS Installing YADE and managing files ================================== After you’ve connected to the instance through SSH, you will need to install YADE. The following commands should be issued to install yadedaily, python, and some other useful tools: :: #install yadedaily sudo bash -c 'echo "deb http://www.yade-dem.org/packages/ xenial/" >> /etc/apt/sources.list' wget -O - http://www.yade-dem.org/packages/yadedev_pub.gpg | sudo apt-key add - sudo apt-get update sudo apt-get install -y yadedaily # install python sudo apt-get -y install python sudo apt-get -y install python-pip python-dev build-essential # install htop sudo apt-get -y install htop | Note that \`\`..packages/ xenial/" should match the Ubuntu distribution. 16.04 LTS is Xenial, but if you chose to start Ubuntu 14.04, you will need to change ‘xenial’ to ‘trusty’. Finally, you will need to upload the necessary YADE files. If you have a folder with the contents of your simulation titled \`\`yadeSimulation" you can upload the folder and its contents by issuing the following command: :: scp -r -i path/to/KeyYADEbox.pem path/to/yadeSimulation ubuntu@ec2-XX-XXX-XX-XX.us-west-2.compute.amazonaws.com:~/yadeSimulation You should now be able to run your simulation by changing to the proper directory and typing: :: yadedaily nameOfSimulation.py In order to retrieve the output files (folder titled ‘out’ below) for post processing purposes, you will use the same command that you used to upload the folder, but the remote and local file destinations should be reversed: :: scp -r -i path/to/KeyYADEbox.pem ubuntu@ec2-XX-XXX-XX-XX.us-west-2.compute.amazonaws.com:~/yadeSimulation/out/ path/to/yadeSimulation/out Plotting output in the terminal =============================== One of the main issues encountered with cloud computing is the lack of graphical feedback. There is an easy solution for graphically checking the status of your simulations which makes use of gnuplot’s wonderful ‘terminal dumb’ feature. Any data can be easily plotted by navigating to the subfolder where the simulation is saving its output and typing: :: gnuplot set terminal dumb plot ``data.txt" using 1:2 with lines Where ‘1:2’ refers to the columns in data.txt that you wish to plot against one another. Your output should look something like this: .. _fig-gnuplot: .. figure:: fig/gnuplot.* :scale: 60 % :align: center Gnuplot output Comments ======== - Amazon AWS allows you to stop your instance and restart it again later with the same files and package installations. If you wish to create several instances that all contain the same installation and file directory you can create a snapshot of your default image which you will be able to use to create various volumes that you can attach to new instances. These actions are all performed very easily and graphically through the EC2 console - You can use Spot Instances, which are a special type of instance that allow you to bid on unused servers. The price is heavily discounted and worth looking into for any YADE user that wishes to run hundreds of hours of simulations. - For most simulations, your computational efficiency will decrease if you use :math:`\textgreater` 8 cores per simulation. It is preferable to use yadedaily-batch to distribute your cores accordingly so that you always dedicate 8 cores to each simulation and ensure 100% of the processor is running. - Create a tmux session to avoid ending YADE simulations upon disconnecting from the server. :: tmux # starts a new session tmux attach -t 0 # attach session 0 tmux kill -t 0 # kill session ## cntrl - b - d to move back to home ## cntrl - b - [ to navigate within the session trunk-2018.02b/doc/sphinx/bib2rst.py000066400000000000000000000077021324306050200171730ustar00rootroot00000000000000#!/usr/bin/python # encoding: utf-8 try: import _bibtex as bib import _recode as bibRecode except ImportError: raise ImportError("Unable to import _bibtex and/or _recode; install the python-bibtex package.") import sys def readBib(filename): ## _bibtex has horrible interface bibfile=bib.open_file(filename,1) # 2nd argument: strict mode db={} #rq=bibRecode.request('latex..latin1') while True: entry=bib.next(bibfile) if entry is None: break key,type,dta=entry[0],entry[1],entry[4] item={'type':type} for field in dta.keys(): expanded=bib.expand(bibfile,dta[field],-1) #conv=bibRecode.recode(rq,expanded[2]) item[field]=expanded[2].strip() db[key]=item ## now we don't need _bibtex anymore, everything is in our dicts return db def dumpBib(db): for k in db.keys(): print k,db[k] def formatRest(db): ret=[] keys=db.keys(); keys.sort() for key in keys: i=db[key]; type=i['type'] line=r'.. [%s] \ '%key ## ← HACK: explicit space to prevent docutils from using abbreviated first name (e.g. "F.") as enumeration item; it works!! if i.has_key('author'): author=i['author'].replace(' and ',', ') # the module does not handle this..? # required fields from http://en.wikipedia.org/wiki/Bibtex # in some cases, they are not present, anyway if type=='article': if i.has_key('author'): line+='%s '%author if i.has_key('year'): line+='(%s), '%i['year'] line+='**%s**. *%s*'%(i['title'],i['journal']) if i.has_key('issue'): line+=i['issue'] if i.has_key('volume'): line+=' (%s)'%i['volume'] if i.has_key('pages'): line+=', pages %s'%i['pages'] line+='.' elif type=='book': if i.has_key('author'): line+='%s '%author if i.has_key('year'): line+='(%s), '%i['year'] line+='**%s**.'%i['title'] if i.has_key('publisher'): line+=' %s.'%i['publisher'] elif type=='inproceedings': line+='%s (%s), **%s**. In *%s*.'%(author,i['year'],i['title'],i['booktitle'] if i.has_key('booktitle') else i['journal']) elif type=='incollection': line+='%s (%s), **%s**. In *%s* '%(author,i['year'],i['title'],i['booktitle'] if i.has_key('booktitle') else i['journal']) if i.has_key('editor'): line+='( %s'%i['editor']+', ed.),' if i.has_key('publisher'): line+=' %s'%i['publisher']+' ,' if i.has_key('edition'): line+=' %s ed.'%i['edition'] elif type=='phdthesis': line+='%s (%s), **%s**. PhD thesis at *%s*.'%(author,i['year'],i['title'],i['school']) elif type=='mastersthesis': typeThesis = 'Master thesis' if i.has_key('note'): typeThesis = i['note'] line+='%s (%s), **%s**. %s at *%s*.'%(author,i['year'],i['title'],typeThesis,i['school']) elif type=='proceedings': if i.has_key('editor'): line+='%s (ed.), '%i['editor'] line+='**%s** (%s).'%(i['title'],i['year']) if i.has_key('organization'): line+=' *%s*.'%i['organization'] if i.has_key('publisher'): line+=' %s'%i['publisher'] elif type=='misc': if i.has_key('author'): line+=author if i.has_key('year'): line+=' %s'%i['year'] if i.has_key('title'): line+=' **%s**'%i['title'] # add doi and url to everything, if present ## ReST uses <..> to delimit URL, therefore < and > must be encoded in the URL (http://www.blooberry.com/indexdot/html/topics/urlencoding.htm) def escapeUrl(url): return url.replace('<','%3c').replace('>','%3e') if i.has_key('doi'): line+=' DOI `%s `_'%(i['doi'],escapeUrl(i['doi'])) if i.has_key('url'): line+=' `(fulltext) <%s>`__'%escapeUrl(i['url']) if (i.has_key('note') and type<>'mastersthesis'): line+=' (%s)'%i['note'] ret.append(line) return [l.replace('@tilde@','~') for l in ret] def bib2rst(filename): """Return string representing all items in given bibtex file, formatted as ReST.""" import tempfile,shutil,os.path d=tempfile.mkdtemp() bib=d+'/'+os.path.basename(filename) open(bib,'w').write(open(filename).read().replace('~','@tilde@')) db=readBib(bib) shutil.rmtree(d) return '\n\n'.join(formatRest(db)) if __name__=='__main__': import sys print bib2rst(sys.argv[1]) trunk-2018.02b/doc/sphinx/book/000077500000000000000000000000001324306050200161765ustar00rootroot00000000000000trunk-2018.02b/doc/sphinx/book/confBook.py000066400000000000000000000720101324306050200203100ustar00rootroot00000000000000# -*- coding: utf-8 -*- # # Yade documentation build configuration file, created by # sphinx-quickstart on Mon Nov 16 21:49:34 2009. # # 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. # relevant posts to sphinx ML # http://groups.google.com/group/sphinx-dev/browse_thread/thread/b4fbc8d31d230fc4 # http://groups.google.com/group/sphinx-dev/browse_thread/thread/118598245d5f479b ##################### ## custom yade roles ##################### ## ## http://docutils.sourceforge.net/docs/howto/rst-roles.html import sys, os, re from docutils import nodes from sphinx import addnodes from sphinx.roles import XRefRole import docutils # # needed for creating hyperlink targets. # it should be cleand up and unified for both LaTeX and HTML via # the pending_xref node which gets resolved to real link target # by sphinx automatically once all docs have been processed. # # xrefs: http://groups.google.com/group/sphinx-dev/browse_thread/thread/d719d19307654548 # # import __builtin__ if 'latex' in sys.argv: __builtin__.writer='latex' elif 'html' in sys.argv: __builtin__.writer='html' elif 'epub' in sys.argv: __builtin__.writer='epub' else: raise RuntimeError("Must have either 'latex' or 'html' on the command line (hack for reference styles)") sys.path.append(os.path.abspath('./..')) def yaderef_role(role,rawtext,text,lineno,inliner,options={},content=[]): "Handle the :yref:`` role, by making hyperlink to yade.wrapper.*. It supports :yref:`Link text` syntax, like usual hyperlinking roles." id=rawtext.split(':',2)[2][1:-1] txt=id; explicitText=False m=re.match('(.*)\s*<(.*)>\s*',id) if m: explicitText=True txt,id=m.group(1),m.group(2) id=id.replace('::','.') #node=nodes.reference(rawtext,docutils.utils.unescape(txt),refuri='http://beta.arcig.cz/~eudoxos/yade/doxygen/?search=%s'%id,**options) #node=nodes.reference(rawtext,docutils.utils.unescape(txt),refuri='yade.wrapper.html#yade.wrapper.%s'%id,**options) return [mkYrefNode(id,txt,rawtext,role,explicitText,lineno,options)],[] def yadesrc_role(role,rawtext,lineno,inliner,options={},content=[]): "Handle the :ysrc:`` role, making hyperlink to git repository webpage with that path. Supports :ysrc:`Link text` syntax, like usual hyperlinking roles. If target ends with ``/``, it is assumed to be a directory." id=rawtext.split(':',2)[2][1:-1] txt=id m=re.match('(.*)\s*<(.*)>\s*',id) if m: txt,id=m.group(1),m.group(2) return [nodes.reference(rawtext,docutils.utils.unescape(txt),refuri='https://github.com/yade/trunk/blob/master/%s'%id)],[] ### **options should be passed to nodes.reference as well # map modules to their html (rst) filenames. Used for sub-modules, where e.g. SpherePack is yade._packSphere.SpherePack, but is documented from yade.pack.rst moduleMap={ 'yade._packPredicates':'yade.pack', 'yade._packSpheres':'yade.pack', 'yade._packObb':'yade.pack' } class YadeXRefRole(XRefRole): #def process_link def process_link(self, env, refnode, has_explicit_title, title, target): print 'TARGET:','yade.wrapper.'+target return '[['+title+']]','yade.wrapper.'+target def mkYrefNode(target,text,rawtext,role,explicitText,lineno,options={}): """Create hyperlink to yade target. Targets starting with literal 'yade.' are absolute, but the leading 'yade.' will be stripped from the link text. Absolute tergets are supposed to live in page named yade.[module].html, anchored at #yade.[module2].[rest of target], where [module2] is identical to [module], unless mapped over by moduleMap. Other targets are supposed to live in yade.wrapper (such as c++ classes).""" writer=__builtin__.writer # to make sure not shadowed by a local var import string if target.startswith('yade.'): module='.'.join(target.split('.')[0:2]) module2=(module if module not in moduleMap.keys() else moduleMap[module]) if target==module: target='' # to reference the module itself uri=('%%%s#%s'%(module2,target) if writer=='latex' else '%s.html#%s'%(module2,target)) if not explicitText and module!=module2: text=module2+'.'+'.'.join(target.split('.')[2:]) text=string.replace(text,'yade.','',1) elif target.startswith('external:'): exttarget=target.split(':',1)[1] if not explicitText: text=exttarget target=exttarget if '.' in exttarget else 'module-'+exttarget uri=(('%%external#%s'%target) if writer=='latex' else 'external.html#%s'%target) else: uri=(('%%yade.wrapper#yade.wrapper.%s'%target) if writer=='latex' else 'yade.wrapper.html#yade.wrapper.%s'%target) #print writer,uri if 0: refnode=addnodes.pending_xref(rawtext,reftype=role,refexplicit=explicitText,reftarget=target) #refnode.line=lineno #refnode+=nodes.literal(rawtext,text,classes=['ref',role]) return [refnode],[] #ret.rawtext,reftype=role, else: return nodes.reference(rawtext,docutils.utils.unescape(text),refuri=uri,**options) #return [refnode],[] def ydefault_role(role,rawtext,text,lineno,inliner,options={},content=[]): "Handle the :ydefault:`something` role. fixSignature handles it now in the member signature itself, this merely expands to nothing." return [],[] def yattrtype_role(role,rawtext,text,lineno,inliner,options={},content=[]): "Handle the :yattrtype:`something` role. fixSignature handles it now in the member signature itself, this merely expands to nothing." return [],[] # FIXME: should return readable representation of bits of the number (yade.wrapper.AttrFlags enum) def yattrflags_role(role,rawtext,text,lineno,inliner,options={},content=[]): "Handle the :yattrflags:`something` role. fixSignature handles it now in the member signature itself." return [],[] from docutils.parsers.rst import roles def yaderef_role_2(type,rawtext,text,lineno,inliner,options={},content=[]): return YadeXRefRole()('yref',rawtext,text,lineno,inliner,options,content) roles.register_canonical_role('yref', yaderef_role) roles.register_canonical_role('ysrc', yadesrc_role) roles.register_canonical_role('ydefault', ydefault_role) roles.register_canonical_role('yattrtype', yattrtype_role) roles.register_canonical_role('yattrflags', yattrflags_role) ## http://sphinx.pocoo.org/config.html#confval-rst_epilog rst_epilog = """ .. |yupdate| replace:: *(auto-updated)* .. |ycomp| replace:: *(auto-computed)* .. |ystatic| replace:: *(static)* """ import collections def customExclude(app, what, name, obj, skip, options): if name=='clone': if 'Serializable.clone' in str(obj): return False return True #escape crash on non iterable __doc__ in some qt object if hasattr(obj,'__doc__') and obj.__doc__ and not isinstance(obj.__doc__, collections.Iterable): return True if hasattr(obj,'__doc__') and obj.__doc__ and ('|ydeprecated|' in obj.__doc__ or '|yhidden|' in obj.__doc__): return True #if re.match(r'\b(__init__|__reduce__|__repr__|__str__)\b',name): return True if name.startswith('_'): if name=='__init__': # skip boost classes with parameterless ctor (arg1=implicit self) if obj.__doc__=="\n__init__( (object)arg1) -> None": return True # skip undocumented ctors if not obj.__doc__: return True # skip default ctor for serializable, taking dict of attrs if obj.__doc__=='\n__init__( (object)arg1) -> None\n\nobject __init__(tuple args, dict kwds)': return True #for i,l in enumerate(obj.__doc__.split('\n')): print name,i,l,'##' return False return True return False def isBoostFunc(what,obj): return what=='function' and obj.__repr__().startswith('.*$',doc1): return None,docc if doc1.endswith(':'): doc1=doc1[:-1] strippedDoc=doc.split('\n')[2:] # check if all lines are padded allLinesHave4LeadingSpaces=True for l in strippedDoc: if l.startswith(' '): continue allLinesHave4LeadingSpaces=False; break # remove the padding if so if allLinesHave4LeadingSpaces: strippedDoc=[l[4:] for l in strippedDoc] for i in range(len(strippedDoc)): # fix signatures inside docstring (one function with multiple signatures) strippedDoc[i],n=re.subn(r'([a-zA-Z_][a-zA-Z0-9_]*\() \(object\)arg1(, |)',r'\1',strippedDoc[i].replace('->','→')) # inspect dosctring after mangling if 'getViscoelasticFromSpheresInteraction' in name and False: print name print strippedDoc print '======================' for l in strippedDoc: print l print '======================' sig=doc1.split('(',1)[1] if removeSelf: # remove up to the first comma; if no comma present, then the method takes no arguments # if [ precedes the comma, add it to the result (ugly!) try: ss=sig.split(',',1) if ss[0].endswith('['): sig='['+ss[1] else: sig=ss[1] except IndexError: # grab the return value try: sig=') -> '+sig.split('->')[-1] #if 'Serializable' in name: print 1000*'#',name except IndexError: sig=')' return '('+sig,strippedDoc def fixSignature(app, what, name, obj, options, signature, return_annotation): #print what,name,obj,signature#,dir(obj) if what=='attribute': doc=unicode(obj.__doc__) ret='' m=re.match('.*:ydefault:`(.*?)`.*',doc) if m: typ='' #try: # clss='.'.join(name.split('.')[:-1]) # instance=eval(clss+'()') # typ='; '+getattr(instance,name.split('.')[-1]).__class__.__name__ # if typ=='; NoneType': typ='' #except TypeError: ##no registered converted # typ='' dfl=m.group(1) m2=re.match(r'\s*\(\s*\(\s*void\s*\)\s*\"(.*)\"\s*,\s*(.*)\s*\)\s*',dfl) if m2: dfl="%s, %s"%(m2.group(2),m2.group(1)) if dfl!='': ret+=' (='+dfl+'%s)'%typ else: ret+=' (=uninitalized%s)'%typ #m=re.match('.*\[(.{,8})\].*',doc) #m=re.match('.*:yunit:`(.?*)`.*',doc) #if m: # units=m.group(1) # print '@@@@@@@@@@@@@@@@@@@@@',name,units # ret+=' ['+units+']' return ret,None elif what=='class': ret=[] if len(obj.__bases__)>0: base=obj.__bases__[0] while base.__module__!='Boost.Python': ret+=[base.__name__] if len(base.__bases__)>0: base=base.__bases__[0] else: break if len(ret): return ' (inherits '+u' → '.join(ret)+')',None else: return None,None elif isBoostFunc(what,obj): sig=boostFuncSignature(name,obj)[0] or ' (wrapped c++ function)' return sig,None elif isBoostMethod(what,obj): sig=boostFuncSignature(name,obj,removeSelf=True)[0] return sig,None #else: print what,name,obj.__repr__() #return None,None from sphinx import addnodes def parse_ystaticattr(env,attr,attrnode): m=re.match(r'([a-zA-Z0-9_]+)\.(.*)\(=(.*)\)',attr) if not m: print 100*'@'+' Static attribute %s not matched'%attr attrnode+=addnodes.desc_name(attr,attr) klass,name,default=m.groups() #attrnode+=addnodes.desc_type('static','static') attrnode+=addnodes.desc_name(name,name) plist=addnodes.desc_parameterlist() if default=='': default='unspecified' plist+=addnodes.desc_parameter('='+default,'='+default) attrnode+=plist attrnode+=addnodes.desc_annotation(' [static]',' [static]') return klass+'.'+name ############################# ## set tab size ################### ## http://groups.google.com/group/sphinx-dev/browse_thread/thread/35b8071ffe9a8feb def setup(app): from sphinx.highlighting import lexers from pygments.lexers.compiled import CppLexer lexers['cpp'] = CppLexer(tabsize=3) lexers['c++'] = CppLexer(tabsize=3) from pygments.lexers.agile import PythonLexer lexers['python'] = PythonLexer(tabsize=3) app.connect('source-read',fixSrc) app.connect('autodoc-skip-member',customExclude) app.connect('autodoc-process-signature',fixSignature) app.connect('autodoc-process-docstring',fixDocstring) app.add_description_unit('ystaticattr',None,objname='static attribute',indextemplate='pair: %s; static method',parse_node=parse_ystaticattr) import sys, os # 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.append(os.path.abspath('.')) # -- General configuration ----------------------------------------------------- # Add any Sphinx extension module names here, as strings. They can be extensions # coming with Sphinx (named 'sphinx.ext.*') or your custom ones. # # HACK: change ipython console regexp from ipython_console_highlighting.py import re sys.path.append(os.path.abspath('.')) import yade.config if 1: if yade.runtime.ipython_version<12: import ipython_directive as id else: if 12<=yade.runtime.ipython_version<13: import ipython_directive012 as id elif 13<=yade.runtime.ipython_version<200: import ipython_directive013 as id elif 200<=yade.runtime.ipython_version<500: import ipython_directive200 as id else: import ipython_directive500 as id #The next four lines are for compatibility with IPython 0.13.1 ipython_rgxin =re.compile(r'(?:In |Yade )\[(\d+)\]:\s?(.*)\s*') ipython_rgxout=re.compile(r'(?:Out| -> )\[(\d+)\]:\s?(.*)\s*') ipython_promptin ='Yade [%d]:' ipython_promptout=' -> [%d]: ' ipython_cont_spaces=' ' #For IPython <=0.12, the following lines are used id.rgxin =re.compile(r'(?:In |Yade )\[(\d+)\]:\s?(.*)\s*') id.rgxout=re.compile(r'(?:Out| -> )\[(\d+)\]:\s?(.*)\s*') id.rgxcont=re.compile(r'(?: +)\.\.+:\s?(.*)\s*') id.fmtin ='Yade [%d]:' id.fmtout =' -> [%d]: ' # for some reason, out and cont must have the trailing space id.fmtcont=' .\D.: ' id.rc_override=dict(prompt_in1="Yade [\#]:",prompt_in2=" .\D.:",prompt_out=r" -> [\#]: ") if yade.runtime.ipython_version<12: id.reconfig_shell() import ipython_console_highlighting as ich ich.IPythonConsoleLexer.input_prompt = re.compile("(Yade \[[0-9]+\]: )") ich.IPythonConsoleLexer.output_prompt = re.compile("(( -> |Out)|\[[0-9]+\]: )") ich.IPythonConsoleLexer.continue_prompt = re.compile("\s+\.\.\.+:") extensions = [ 'sphinx.ext.autodoc', 'sphinx.ext.autosummary', 'sphinx.ext.coverage', 'sphinx.ext.pngmath', 'sphinx.ext.graphviz', 'sphinx.ext.viewcode', 'sphinx.ext.inheritance_diagram', 'matplotlib.sphinxext.plot_directive', 'matplotlib.sphinxext.only_directives', #'matplotlib.sphinxext.mathmpl', 'ipython_console_highlighting', 'youtube', 'sphinx.ext.todo', ] if yade.runtime.ipython_version<12: extensions.append('ipython_directive') else: if 12<=yade.runtime.ipython_version<13: extensions.append('ipython_directive012') elif 13<=yade.runtime.ipython_version<200: extensions.append('ipython_directive013') elif 200<=yade.runtime.ipython_version<500: extensions.append('ipython_directive200') else: extensions.append('ipython_directive500') # the sidebar extension if False: if writer=='html': extensions+=['sphinx.ext.sidebar'] sidebar_all=True sidebar_relling=True #sidebar_abbrev=True sidebar_tocdepth=3 ## http://trac.sagemath.org/sage_trac/attachment/ticket/7549/trac_7549-doc_inheritance_underscore.patch # GraphViz includes dot, neato, twopi, circo, fdp. graphviz_dot = 'dot' inheritance_graph_attrs = { 'rankdir' : 'BT' } inheritance_node_attrs = { 'height' : 0.5, 'fontsize' : 12, 'shape' : 'oval' } inheritance_edge_attrs = {} my_latex_preamble=r''' \usepackage{euler} % must be loaded before fontspec for the whole doc (below); this must be kept for pngmath, however \usepackage{hyperref} \usepackage{amsmath} \usepackage{amsbsy} %\usepackage{mathabx} \usepackage{underscore} \usepackage[all]{xy} \usepackage{multicol} % Metadata of the pdf output \hypersetup{pdftitle={Yade Documentation}} \hypersetup{pdfauthor={Vaclav Smilauer; Emanuele Catalano; Bruno Chareyre; Sergei Dorofeenko; Jerome Duriez; Nolan Dyck; Jan Elias; Burak Er; Alexander Eulitz; Anton Gladky; Ning Guo; Christian Jakob; Francois Kneib; Janek Kozicki; Donia Marzougui; Raphael Maurin; Chiara Modenese; Luc Scholtes; Luc Sibille; Jan Stransky; Thomas Sweijen; Klaus Thoeni; Chao Yuan}} \hypersetup{pdfkeywords={Discrete element method; dem; yade; documentation; manual; python; c++; git, hydromechanical coupling}} % symbols \let\mat\boldsymbol % matrix \let\vec\boldsymbol % vector \let\tens\boldsymbol % tensor \def\normalized#1{\widehat{#1}} \def\locframe#1{\widetilde{#1}} % timestep \def\Dt{\Delta t} \def\Dtcr{\Dt_{\rm cr}} % algorithm complexity \def\bigO#1{\ensuremath{\mathcal{O}(#1)}} % variants for greek symbols \let\epsilon\varepsilon \let\theta\vartheta \let\phi\varphi % shorthands \let\sig\sigma \let\eps\epsilon % variables at different points of time \def\prev#1{#1^-} \def\pprev#1{#1^\ominus} \def\curr#1{#1^{\circ}} \def\nnext#1{#1^\oplus} \def\next#1{#1^+} % shorthands for geometry \def\currn{\curr{\vec{n}}} \def\currC{\curr{\vec{C}}} \def\uT{\vec{u}_T} \def\curruT{\curr{\vec{u}}_T} \def\prevuT{\prev{\vec{u}}_T} \def\currn{\curr{\vec{n}}} \def\prevn{\prev{\vec{n}}} % motion \def\pprevvel{\pprev{\dot{\vec{u}}}} \def\nnextvel{\nnext{\dot{\vec{u}}}} \def\curraccel{\curr{\ddot{\vec{u}}}} \def\prevpos{\prev{\vec{u}}} \def\currpos{\curr{\vec{u}}} \def\nextpos{\next{\vec{u}}} \def\curraaccel{\curr{\dot{\vec{\omega}}}} \def\pprevangvel{\pprev{\vec{\omega}}} \def\nnextangvel{\nnext{\vec{\omega}}} \def\loccurr#1{\curr{\locframe{#1}}} \def\numCPU{n_{\rm cpu}} \DeclareMathOperator{\Align}{Align} \DeclareMathOperator{\sign}{sgn} % sorting algorithms \def\isleq#1{\currelem{#1}\ar@/^/[ll]^{\leq}} \def\isnleq#1{\currelem{#1}\ar@/^/[ll]^{\not\leq}} \def\currelem#1{\fbox{$#1$}} \def\sortSep{||} \def\sortInv{\hbox{\phantom{||}}} \def\sortlines#1{\xymatrix@=3pt{#1}} \def\crossBound{||\mkern-18mu<} ''' pngmath_latex_preamble=r'\usepackage[active]{preview}'+my_latex_preamble pngmath_use_preview=True # Add any paths that contain templates here, relative to this directory. templates_path = ['templates'] # The suffix of source filenames. source_suffix = '.rst' # The encoding of source files. #source_encoding = 'utf-8' # The master toctree document. master_doc = 'index-toctree-book' # General information about the project. project = u'Yade' copyright = u'2009, Václav Šmilauer' # 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 = '2nd ed.' # The full version, including alpha/beta/rc tags. release = '2nd ed.' # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. #language = None # There are two options for replacing |today|: either, you set today to some # non-false value, then it is used: #today = '' # Else, today_fmt is used as the format for a strftime call. #today_fmt = '%B %d, %Y' # List of documents that shouldn't be included in the build. #unused_docs = [] # List of directories, relative to source directory, that shouldn't be searched # for source files. exclude_trees = ['_build','../_build'] # 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 = True # 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 = ['yade.'] # -- Options for HTML output --------------------------------------------------- # The theme to use for HTML and HTML Help pages. Major themes that come with # Sphinx are currently 'default' and 'sphinxdoc'. html_theme = 'default' # 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 = {'stickysidebar':'true','collapsiblesidebar':'true','rightsidebar':'false'} # 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 = '../fig/yade-logo.png' # The name of an image file (within the static path) to use as favicon of the # docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 # pixels large. html_favicon = '../fig/yade-favicon.ico' # Add any paths that contain custom static files (such as style sheets) here, # relative to this directory. They are copied after the builtin static files, # so a file named "default.css" will overwrite the builtin "default.css". html_static_path = ['static-html'] # 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 = {} html_index='index.html' # Additional templates that should be rendered to pages, maps page names to # template names. html_additional_pages = { 'index':'index.html'} # If false, no module index is generated. #html_use_modindex = 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, 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 = '' # If nonempty, this is the file name suffix for HTML files (e.g. ".xhtml"). #html_file_suffix = '' # Output file base name for HTML help builder. htmlhelp_basename = 'Yadedoc' # -- Options for LaTeX output -------------------------------------------------- my_maketitle=r''' \begin{titlepage} \begin{flushright} \hrule{} % Upper part of the page \begin{flushright} \includegraphics[width=0.15\textwidth]{yade-logo.png}\par \end{flushright} \vspace{20 mm} \text{\sffamily\bfseries\Huge Yade Documentation}\\ \vspace{20 mm} %\vspace{70 mm} \begin{sffamily}\bfseries\Large V\'{a}clav \v{S}milauer, Emanuele Catalano, Bruno Chareyre, Sergei Dorofeenko, J\'er\^ome Duriez, Nolan Dyck, Jan Eli\'{a}\v{s}, Burak Er, Alexander Eulitz, Anton Gladky, Ning Guo, Christian Jakob, Fran\c{c}ois Kneib, Janek Kozicki, Donia Marzougui, Rapha\"el Maurin, Chiara Modenese, Luc Scholt\`{e}s, Luc Sibille, Jan Str\'{a}nsk\'{y}, Thomas Sweijen, Klaus Thoeni, Chao Yuan \end{sffamily} \vspace{20 mm} \hrule{} \vfill \textit{\large Yade Documentation 2nd edition, 2015}\\ \textit{based on Yade 1.14.0}\\ \end{flushright} \end{titlepage} \text{\sffamily\bfseries\Large Authors} \begin{multicols}{2} \begin{itemize} \item \text{\sffamily\bfseries\normalsize V\'{a}clav \v{S}milauer}\\ \text{\sffamily\small Freelance consultant (http://woodem.eu)} \item \text{\sffamily\bfseries\normalsize Emanuele Catalano}\\ \text{\sffamily\small Grenoble INP, UJF, CNRS, lab. 3SR} \item \text{\sffamily\bfseries\normalsize Bruno Chareyre}\\ \text{\sffamily\small Grenoble INP, UJF, CNRS, lab. 3SR} \item \text{\sffamily\bfseries\normalsize Sergei Dorofeenko}\\ \text{\sffamily\small IPCP RAS, Chernogolovka} \item \text{\sffamily\bfseries\normalsize J\'er\^ome Duriez}\\ \text{\sffamily\small Grenoble INP, UJF, CNRS, lab. 3SR} \item \text{\sffamily\bfseries\normalsize Nolan Dyck}\\ \text{\sffamily\small Univ. of Western Ontario} \item \text{\sffamily\bfseries\normalsize Eli\'{a}\v{s}}\\ \text{\sffamily\small Brno University of Technology} \item \text{\sffamily\bfseries\normalsize Burak Er}\\ \text{\sffamily\small Bursa Technical University} \item \text{\sffamily\bfseries\normalsize Alexander Eulitz}\\ \text{\sffamily\small TU Berlin / Institute for Machine Tools}\\ \text{\sffamily\small and Factory Management} \item \text{\sffamily\bfseries\normalsize Anton Gladky}\\ \text{\sffamily\small TU Bergakademie Freiberg} \item \text{\sffamily\bfseries\normalsize Ning Guo}\\ \text{\sffamily\small Hong Kong Univ. of Science and Tech.} \item \text{\sffamily\bfseries\normalsize Christian Jakob}\\ \text{\sffamily\small TU Bergakademie Freiberg} \item \text{\sffamily\bfseries\normalsize Fran\c{c}ois Kneib}\\ \text{\sffamily\small Grenoble INP, UJF, CNRS, lab. 3SR / Irstea Grenoble} \item \text{\sffamily\bfseries\normalsize Janek Kozicki}\\ \text{\sffamily\small Gdansk University of Technology} \item \text{\sffamily\bfseries\normalsize Donia Marzougui}\\ \text{\sffamily\small Grenoble INP, UJF, CNRS, lab. 3SR} \item \text{\sffamily\bfseries\normalsize Rapha\"el Maurin}\\ \text{\sffamily\small Irstea Grenoble} \item \text{\sffamily\bfseries\normalsize Chiara Modenese}\\ \text{\sffamily\small University of Oxford} \item \text{\sffamily\bfseries\normalsize Luc Scholt\`{e}s}\\ \text{\sffamily\small Grenoble INP, UJF, CNRS, lab. 3SR} \item \text{\sffamily\bfseries\normalsize Luc Sibille}\\ \text{\sffamily\small University of Nantes, lab. GeM} \item \text{\sffamily\bfseries\normalsize Jan Str\'{a}nsk\'{y}}\\ \text{\sffamily\small CVUT Prague} \item \text{\sffamily\bfseries\normalsize Thomas Sweijen}\\ \text{\sffamily\small Utrecht University} \item \text{\sffamily\bfseries\normalsize Klaus Thoeni}\\ \text{\sffamily\small The University of Newcastle (Australia)} \item \text{\sffamily\bfseries\normalsize Chao Yuan} \\ \text{\sffamily\small Grenoble INP, UJF, CNRS, lab. 3SR} \end{itemize} \end{multicols} \text{\sffamily\bfseries\large Citing this document:}\\ \v{S}milauer V. et al. (2015). Yade Documentation 2nd ed. doi:10.5281/zenodo.34073. http://yade-dem.org\\ See also http://yade-dem/doc/citing.html. ''' latex_elements=dict( papersize='a4paper', fontpkg=r''' \usepackage{euler} \usepackage{fontspec,xunicode,xltxtra} %\setmainfont[BoldFont={LMRoman10 Bold}]{CMU Concrete} %% CMU Concrete must be installed by hand as otf ''', utf8extra='', fncychap='', preamble=my_latex_preamble, footer='', inputenc='', fontenc='', maketitle=my_maketitle, ) # The paper size ('letter' or 'a4'). #latex_paper_size = 'letter' # The font size ('10pt', '11pt' or '12pt'). #latex_font_size = '10pt' # Grouping the document tree into LaTeX files. List of tuples # (source start file, target name, title, author, documentclass [howto/manual]). latex_documents = [ ('index-toctree-book', 'YadeBook.tex', u'Yade documentation', u'Václav Šmilauer et al.', 'manual'), ] # The name of an image file (relative to this directory) to place at the top of # the title page. latex_logo = '../fig/yade-logo.png' # For "manual" documents, if this is true, then toplevel headings are parts, # not chapters. latex_use_parts = True # Additional stuff for the LaTeX preamble. #latex_preamble = '' # Documents to append as an appendix to all manuals. #latex_appendices = [] # If false, no module index is generated. #latex_use_modindex = True trunk-2018.02b/doc/sphinx/book/confManual.py000066400000000000000000000634101324306050200206370ustar00rootroot00000000000000# -*- coding: utf-8 -*- # # Yade documentation build configuration file, created by # sphinx-quickstart on Mon Nov 16 21:49:34 2009. # # 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. # relevant posts to sphinx ML # http://groups.google.com/group/sphinx-dev/browse_thread/thread/b4fbc8d31d230fc4 # http://groups.google.com/group/sphinx-dev/browse_thread/thread/118598245d5f479b ##################### ## custom yade roles ##################### ## ## http://docutils.sourceforge.net/docs/howto/rst-roles.html import sys, os, re from docutils import nodes from sphinx import addnodes from sphinx.roles import XRefRole import docutils # # needed for creating hyperlink targets. # it should be cleand up and unified for both LaTeX and HTML via # the pending_xref node which gets resolved to real link target # by sphinx automatically once all docs have been processed. # # xrefs: http://groups.google.com/group/sphinx-dev/browse_thread/thread/d719d19307654548 # # import __builtin__ if 'latex' in sys.argv: __builtin__.writer='latex' elif 'html' in sys.argv: __builtin__.writer='html' elif 'epub' in sys.argv: __builtin__.writer='epub' else: raise RuntimeError("Must have either 'latex' or 'html' on the command line (hack for reference styles)") sys.path.append(os.path.abspath('./..')) def yaderef_role(role,rawtext,text,lineno,inliner,options={},content=[]): "Handle the :yref:`` role, by making hyperlink to yade.wrapper.*. It supports :yref:`Link text` syntax, like usual hyperlinking roles." id=rawtext.split(':',2)[2][1:-1] txt=id; explicitText=False m=re.match('(.*)\s*<(.*)>\s*',id) if m: explicitText=True txt,id=m.group(1),m.group(2) id=id.replace('::','.') #node=nodes.reference(rawtext,docutils.utils.unescape(txt),refuri='http://beta.arcig.cz/~eudoxos/yade/doxygen/?search=%s'%id,**options) #node=nodes.reference(rawtext,docutils.utils.unescape(txt),refuri='yade.wrapper.html#yade.wrapper.%s'%id,**options) return [mkYrefNode(id,txt,rawtext,role,explicitText,lineno,options)],[] def yadesrc_role(role,rawtext,lineno,inliner,options={},content=[]): "Handle the :ysrc:`` role, making hyperlink to git repository webpage with that path. Supports :ysrc:`Link text` syntax, like usual hyperlinking roles. If target ends with ``/``, it is assumed to be a directory." id=rawtext.split(':',2)[2][1:-1] txt=id m=re.match('(.*)\s*<(.*)>\s*',id) if m: txt,id=m.group(1),m.group(2) return [nodes.reference(rawtext,docutils.utils.unescape(txt),refuri='https://github.com/yade/trunk/blob/master/%s'%id)],[] ### **options should be passed to nodes.reference as well # map modules to their html (rst) filenames. Used for sub-modules, where e.g. SpherePack is yade._packSphere.SpherePack, but is documented from yade.pack.rst moduleMap={ 'yade._packPredicates':'yade.pack', 'yade._packSpheres':'yade.pack', 'yade._packObb':'yade.pack' } class YadeXRefRole(XRefRole): #def process_link def process_link(self, env, refnode, has_explicit_title, title, target): print 'TARGET:','yade.wrapper.'+target return '[['+title+']]','yade.wrapper.'+target def mkYrefNode(target,text,rawtext,role,explicitText,lineno,options={}): """Create hyperlink to yade target. Targets starting with literal 'yade.' are absolute, but the leading 'yade.' will be stripped from the link text. Absolute tergets are supposed to live in page named yade.[module].html, anchored at #yade.[module2].[rest of target], where [module2] is identical to [module], unless mapped over by moduleMap. Other targets are supposed to live in yade.wrapper (such as c++ classes).""" writer=__builtin__.writer # to make sure not shadowed by a local var import string if target.startswith('yade.'): module='.'.join(target.split('.')[0:2]) module2=(module if module not in moduleMap.keys() else moduleMap[module]) if target==module: target='' # to reference the module itself uri=('%%%s#%s'%(module2,target) if writer=='latex' else '%s.html#%s'%(module2,target)) if not explicitText and module!=module2: text=module2+'.'+'.'.join(target.split('.')[2:]) text=string.replace(text,'yade.','',1) elif target.startswith('external:'): exttarget=target.split(':',1)[1] if not explicitText: text=exttarget target=exttarget if '.' in exttarget else 'module-'+exttarget uri=(('%%external#%s'%target) if writer=='latex' else 'external.html#%s'%target) else: uri=(('%%yade.wrapper#yade.wrapper.%s'%target) if writer=='latex' else 'yade.wrapper.html#yade.wrapper.%s'%target) #print writer,uri return nodes.reference(rawtext,docutils.utils.unescape(text),refuri=uri,**options) #return [refnode],[] def ydefault_role(role,rawtext,text,lineno,inliner,options={},content=[]): "Handle the :ydefault:`something` role. fixSignature handles it now in the member signature itself, this merely expands to nothing." return [],[] def yattrtype_role(role,rawtext,text,lineno,inliner,options={},content=[]): "Handle the :yattrtype:`something` role. fixSignature handles it now in the member signature itself, this merely expands to nothing." return [],[] # FIXME: should return readable representation of bits of the number (yade.wrapper.AttrFlags enum) def yattrflags_role(role,rawtext,text,lineno,inliner,options={},content=[]): "Handle the :yattrflags:`something` role. fixSignature handles it now in the member signature itself." return [],[] from docutils.parsers.rst import roles def yaderef_role_2(type,rawtext,text,lineno,inliner,options={},content=[]): return YadeXRefRole()('yref',rawtext,text,lineno,inliner,options,content) roles.register_canonical_role('yref', yaderef_role) roles.register_canonical_role('ysrc', yadesrc_role) roles.register_canonical_role('ydefault', ydefault_role) roles.register_canonical_role('yattrtype', yattrtype_role) roles.register_canonical_role('yattrflags', yattrflags_role) ## http://sphinx.pocoo.org/config.html#confval-rst_epilog rst_epilog = """ .. |yupdate| replace:: *(auto-updated)* .. |ycomp| replace:: *(auto-computed)* .. |ystatic| replace:: *(static)* """ import collections def customExclude(app, what, name, obj, skip, options): if name=='clone': if 'Serializable.clone' in str(obj): return False return True #escape crash on non iterable __doc__ in some qt object if hasattr(obj,'__doc__') and obj.__doc__ and not isinstance(obj.__doc__, collections.Iterable): return True if hasattr(obj,'__doc__') and obj.__doc__ and ('|ydeprecated|' in obj.__doc__ or '|yhidden|' in obj.__doc__): return True #if re.match(r'\b(__init__|__reduce__|__repr__|__str__)\b',name): return True if name.startswith('_'): if name=='__init__': # skip boost classes with parameterless ctor (arg1=implicit self) if obj.__doc__=="\n__init__( (object)arg1) -> None": return True # skip undocumented ctors if not obj.__doc__: return True # skip default ctor for serializable, taking dict of attrs if obj.__doc__=='\n__init__( (object)arg1) -> None\n\nobject __init__(tuple args, dict kwds)': return True #for i,l in enumerate(obj.__doc__.split('\n')): print name,i,l,'##' return False return True return False def isBoostFunc(what,obj): return what=='function' and obj.__repr__().startswith('.*$',doc1): return None,docc if doc1.endswith(':'): doc1=doc1[:-1] strippedDoc=doc.split('\n')[2:] # check if all lines are padded allLinesHave4LeadingSpaces=True for l in strippedDoc: if l.startswith(' '): continue allLinesHave4LeadingSpaces=False; break # remove the padding if so if allLinesHave4LeadingSpaces: strippedDoc=[l[4:] for l in strippedDoc] for i in range(len(strippedDoc)): # fix signatures inside docstring (one function with multiple signatures) strippedDoc[i],n=re.subn(r'([a-zA-Z_][a-zA-Z0-9_]*\() \(object\)arg1(, |)',r'\1',strippedDoc[i].replace('->','→')) # inspect dosctring after mangling if 'getViscoelasticFromSpheresInteraction' in name and False: print name print strippedDoc print '======================' for l in strippedDoc: print l print '======================' sig=doc1.split('(',1)[1] if removeSelf: # remove up to the first comma; if no comma present, then the method takes no arguments # if [ precedes the comma, add it to the result (ugly!) try: ss=sig.split(',',1) if ss[0].endswith('['): sig='['+ss[1] else: sig=ss[1] except IndexError: # grab the return value try: sig=') -> '+sig.split('->')[-1] #if 'Serializable' in name: print 1000*'#',name except IndexError: sig=')' return '('+sig,strippedDoc def fixSignature(app, what, name, obj, options, signature, return_annotation): #print what,name,obj,signature#,dir(obj) if what=='attribute': doc=unicode(obj.__doc__) ret='' m=re.match('.*:ydefault:`(.*?)`.*',doc) if m: typ='' #try: # clss='.'.join(name.split('.')[:-1]) # instance=eval(clss+'()') # typ='; '+getattr(instance,name.split('.')[-1]).__class__.__name__ # if typ=='; NoneType': typ='' #except TypeError: ##no registered converted # typ='' dfl=m.group(1) m2=re.match(r'\s*\(\s*\(\s*void\s*\)\s*\"(.*)\"\s*,\s*(.*)\s*\)\s*',dfl) if m2: dfl="%s, %s"%(m2.group(2),m2.group(1)) if dfl!='': ret+=' (='+dfl+'%s)'%typ else: ret+=' (=uninitalized%s)'%typ #m=re.match('.*\[(.{,8})\].*',doc) #m=re.match('.*:yunit:`(.?*)`.*',doc) #if m: # units=m.group(1) # print '@@@@@@@@@@@@@@@@@@@@@',name,units # ret+=' ['+units+']' return ret,None elif what=='class': ret=[] if len(obj.__bases__)>0: base=obj.__bases__[0] while base.__module__!='Boost.Python': ret+=[base.__name__] if len(base.__bases__)>0: base=base.__bases__[0] else: break if len(ret): return ' (inherits '+u' → '.join(ret)+')',None else: return None,None elif isBoostFunc(what,obj): sig=boostFuncSignature(name,obj)[0] or ' (wrapped c++ function)' return sig,None elif isBoostMethod(what,obj): sig=boostFuncSignature(name,obj,removeSelf=True)[0] return sig,None #else: print what,name,obj.__repr__() #return None,None from sphinx import addnodes def parse_ystaticattr(env,attr,attrnode): m=re.match(r'([a-zA-Z0-9_]+)\.(.*)\(=(.*)\)',attr) if not m: print 100*'@'+' Static attribute %s not matched'%attr attrnode+=addnodes.desc_name(attr,attr) klass,name,default=m.groups() #attrnode+=addnodes.desc_type('static','static') attrnode+=addnodes.desc_name(name,name) plist=addnodes.desc_parameterlist() if default=='': default='unspecified' plist+=addnodes.desc_parameter('='+default,'='+default) attrnode+=plist attrnode+=addnodes.desc_annotation(' [static]',' [static]') return klass+'.'+name ############################# ## set tab size ################### ## http://groups.google.com/group/sphinx-dev/browse_thread/thread/35b8071ffe9a8feb def setup(app): from sphinx.highlighting import lexers from pygments.lexers.compiled import CppLexer lexers['cpp'] = CppLexer(tabsize=3) lexers['c++'] = CppLexer(tabsize=3) from pygments.lexers.agile import PythonLexer lexers['python'] = PythonLexer(tabsize=3) app.connect('source-read',fixSrc) app.connect('autodoc-skip-member',customExclude) app.connect('autodoc-process-signature',fixSignature) app.connect('autodoc-process-docstring',fixDocstring) app.add_description_unit('ystaticattr',None,objname='static attribute',indextemplate='pair: %s; static method',parse_node=parse_ystaticattr) import sys, os # 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.append(os.path.abspath('.')) # -- General configuration ----------------------------------------------------- # Add any Sphinx extension module names here, as strings. They can be extensions # coming with Sphinx (named 'sphinx.ext.*') or your custom ones. # # HACK: change ipython console regexp from ipython_console_highlighting.py import re sys.path.append(os.path.abspath('.')) import yade.config if 1: if yade.runtime.ipython_version<12: import ipython_directive as id else: if 12<=yade.runtime.ipython_version<13: import ipython_directive012 as id elif 13<=yade.runtime.ipython_version<200: import ipython_directive013 as id elif 200<=yade.runtime.ipython_version<500: import ipython_directive200 as id else: import ipython_directive500 as id #The next four lines are for compatibility with IPython 0.13.1 ipython_rgxin =re.compile(r'(?:In |Yade )\[(\d+)\]:\s?(.*)\s*') ipython_rgxout=re.compile(r'(?:Out| -> )\[(\d+)\]:\s?(.*)\s*') ipython_promptin ='Yade [%d]:' ipython_promptout=' -> [%d]: ' ipython_cont_spaces=' ' #For IPython <=0.12, the following lines are used id.rgxin =re.compile(r'(?:In |Yade )\[(\d+)\]:\s?(.*)\s*') id.rgxout=re.compile(r'(?:Out| -> )\[(\d+)\]:\s?(.*)\s*') id.rgxcont=re.compile(r'(?: +)\.\.+:\s?(.*)\s*') id.fmtin ='Yade [%d]:' id.fmtout =' -> [%d]: ' # for some reason, out and cont must have the trailing space id.fmtcont=' .\D.: ' id.rc_override=dict(prompt_in1="Yade [\#]:",prompt_in2=" .\D.:",prompt_out=r" -> [\#]: ") if yade.runtime.ipython_version<12: id.reconfig_shell() import ipython_console_highlighting as ich ich.IPythonConsoleLexer.input_prompt = re.compile("(Yade \[[0-9]+\]: )") ich.IPythonConsoleLexer.output_prompt = re.compile("(( -> |Out)|\[[0-9]+\]: )") ich.IPythonConsoleLexer.continue_prompt = re.compile("\s+\.\.\.+:") extensions = [ 'sphinx.ext.autodoc', 'sphinx.ext.autosummary', 'sphinx.ext.coverage', 'sphinx.ext.pngmath', 'sphinx.ext.graphviz', 'sphinx.ext.viewcode', 'sphinx.ext.inheritance_diagram', 'matplotlib.sphinxext.plot_directive', 'matplotlib.sphinxext.only_directives', #'matplotlib.sphinxext.mathmpl', 'ipython_console_highlighting', 'youtube', 'sphinx.ext.todo', ] if yade.runtime.ipython_version<12: extensions.append('ipython_directive') else: if 12<=yade.runtime.ipython_version<13: extensions.append('ipython_directive012') elif 13<=yade.runtime.ipython_version<200: extensions.append('ipython_directive013') elif 200<=yade.runtime.ipython_version<500: extensions.append('ipython_directive200') else: extensions.append('ipython_directive500') # the sidebar extension if False: if writer=='html': extensions+=['sphinx.ext.sidebar'] sidebar_all=True sidebar_relling=True #sidebar_abbrev=True sidebar_tocdepth=3 ## http://trac.sagemath.org/sage_trac/attachment/ticket/7549/trac_7549-doc_inheritance_underscore.patch # GraphViz includes dot, neato, twopi, circo, fdp. graphviz_dot = 'dot' inheritance_graph_attrs = { 'rankdir' : 'BT' } inheritance_node_attrs = { 'height' : 0.5, 'fontsize' : 12, 'shape' : 'oval' } inheritance_edge_attrs = {} my_latex_preamble=r''' \usepackage{euler} % must be loaded before fontspec for the whole doc (below); this must be kept for pngmath, however \usepackage{hyperref} \usepackage{amsmath} \usepackage{amsbsy} %\usepackage{mathabx} \usepackage{underscore} \usepackage[all]{xy} % Metadata of the pdf output \hypersetup{pdftitle={Using and Programming}} \hypersetup{pdfauthor={Vaclav Smilauer; Bruno Chareyre; Jerome Duriez; Alexander Eulitz; Anton Gladky; Ning Guo; Christian Jakob; Janek Kozicki; Francois Kneib; Chiara Modenese; Jan Stransky; Klaus Thoeni}} \hypersetup{pdfkeywords={Discrete element method; dem; yade; documentation; manual; python; c++; git}} % symbols \let\mat\boldsymbol % matrix \let\vec\boldsymbol % vector \let\tens\boldsymbol % tensor \def\normalized#1{\widehat{#1}} \def\locframe#1{\widetilde{#1}} % timestep \def\Dt{\Delta t} \def\Dtcr{\Dt_{\rm cr}} % algorithm complexity \def\bigO#1{\ensuremath{\mathcal{O}(#1)}} % variants for greek symbols \let\epsilon\varepsilon \let\theta\vartheta \let\phi\varphi % shorthands \let\sig\sigma \let\eps\epsilon % variables at different points of time \def\prev#1{#1^-} \def\pprev#1{#1^\ominus} \def\curr#1{#1^{\circ}} \def\nnext#1{#1^\oplus} \def\next#1{#1^+} % shorthands for geometry \def\currn{\curr{\vec{n}}} \def\currC{\curr{\vec{C}}} \def\uT{\vec{u}_T} \def\curruT{\curr{\vec{u}}_T} \def\prevuT{\prev{\vec{u}}_T} \def\currn{\curr{\vec{n}}} \def\prevn{\prev{\vec{n}}} % motion \def\pprevvel{\pprev{\dot{\vec{u}}}} \def\nnextvel{\nnext{\dot{\vec{u}}}} \def\curraccel{\curr{\ddot{\vec{u}}}} \def\prevpos{\prev{\vec{u}}} \def\currpos{\curr{\vec{u}}} \def\nextpos{\next{\vec{u}}} \def\curraaccel{\curr{\dot{\vec{\omega}}}} \def\pprevangvel{\pprev{\vec{\omega}}} \def\nnextangvel{\nnext{\vec{\omega}}} \def\loccurr#1{\curr{\locframe{#1}}} \def\numCPU{n_{\rm cpu}} \DeclareMathOperator{\Align}{Align} \DeclareMathOperator{\sign}{sgn} % sorting algorithms \def\isleq#1{\currelem{#1}\ar@/^/[ll]^{\leq}} \def\isnleq#1{\currelem{#1}\ar@/^/[ll]^{\not\leq}} \def\currelem#1{\fbox{$#1$}} \def\sortSep{||} \def\sortInv{\hbox{\phantom{||}}} \def\sortlines#1{\xymatrix@=3pt{#1}} \def\crossBound{||\mkern-18mu<} ''' pngmath_latex_preamble=r'\usepackage[active]{preview}'+my_latex_preamble pngmath_use_preview=True # Add any paths that contain templates here, relative to this directory. templates_path = ['templates'] # The suffix of source filenames. source_suffix = '.rst' # The encoding of source files. #source_encoding = 'utf-8' # The master toctree document. master_doc = 'index-toctree-manuals' # General information about the project. project = u'Yade' copyright = u'2009, Václav Šmilauer' # 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 = 'Yade documentation 2nd ed.' # The full version, including alpha/beta/rc tags. release = 'Yade documentation 2nd ed.' # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. #language = None # There are two options for replacing |today|: either, you set today to some # non-false value, then it is used: #today = '' # Else, today_fmt is used as the format for a strftime call. #today_fmt = '%B %d, %Y' # List of documents that shouldn't be included in the build. #unused_docs = [] # List of directories, relative to source directory, that shouldn't be searched # for source files. exclude_trees = ['_build','../_build'] # 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 = ['yade.'] # -- Options for HTML output --------------------------------------------------- # The theme to use for HTML and HTML Help pages. Major themes that come with # Sphinx are currently 'default' and 'sphinxdoc'. html_theme = 'default' # 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 = {'stickysidebar':'true','collapsiblesidebar':'true','rightsidebar':'false'} # 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 = '../fig/yade-logo.png' # The name of an image file (within the static path) to use as favicon of the # docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 # pixels large. html_favicon = '../fig/yade-favicon.ico' # Add any paths that contain custom static files (such as style sheets) here, # relative to this directory. They are copied after the builtin static files, # so a file named "default.css" will overwrite the builtin "default.css". html_static_path = ['static-html'] # 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 = {} html_index='index.html' # Additional templates that should be rendered to pages, maps page names to # template names. html_additional_pages = { 'index':'index.html'} # If false, no module index is generated. #html_use_modindex = 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, 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 = '' # If nonempty, this is the file name suffix for HTML files (e.g. ".xhtml"). #html_file_suffix = '' # Output file base name for HTML help builder. htmlhelp_basename = 'Yadedoc' # -- Options for LaTeX output -------------------------------------------------- my_maketitle=r''' \begin{titlepage} \begin{flushright} \hrule{} % Upper part of the page \begin{flushright} \includegraphics[width=0.15\textwidth]{yade-logo.png}\par \end{flushright} \vspace{20 mm} \text{\sffamily\bfseries\Huge Using and Programming}\\ \vspace{20 mm} %\vspace{70 mm} \begin{sffamily}\bfseries\Large V\'{a}clav \v{S}milauer, Bruno Chareyre, J\'er\^ome Duriez, Alexander Eulitz, Anton Gladky, Ning Guo, Christian Jakob, Janek Kozicki, Fran\c{c}ois Kneib, Chiara Modenese, Jan Str\'{a}nsk\'{y}, Klaus Thoeni \end{sffamily} \vspace{20 mm} \hrule{} \vfill \textit{\large Yade Documentation 2nd edition, 2015}\\ \textit{based on Yade 1.14.0}\\ \end{flushright} \end{titlepage} \text{\sffamily\bfseries\large Citing this document:}\\ \v{S}milauer V. et al. (2015). Using and Programming. In:\textit{Yade Documentation 2nd ed.} doi:10.5281/zenodo.34043. http://yade-dem.org\\ See also http://yade-dem/doc/citing.html. ''' latex_elements=dict( papersize='a4paper', fontpkg=r''' \usepackage{euler} \usepackage{fontspec,xunicode,xltxtra} %\setmainfont[BoldFont={LMRoman10 Bold}]{CMU Concrete} %% CMU Concrete must be installed by hand as otf ''', utf8extra='', fncychap='', preamble=my_latex_preamble, footer='', inputenc='', fontenc='', maketitle=my_maketitle, ) # The paper size ('letter' or 'a4'). #latex_paper_size = 'letter' # The font size ('10pt', '11pt' or '12pt'). #latex_font_size = '10pt' # Grouping the document tree into LaTeX files. List of tuples # (source start file, target name, title, author, documentclass [howto/manual]). latex_documents = [ ('index-toctree-manuals', 'YadeManuals.tex', u'Using and Programming', u'Václav Šmilauer et al.', 'manual'), ] # The name of an image file (relative to this directory) to place at the top of # the title page. latex_logo = '../fig/yade-logo.png' # For "manual" documents, if this is true, then toplevel headings are parts, # not chapters. latex_use_parts = False # Additional stuff for the LaTeX preamble. #latex_preamble = '' # Documents to append as an appendix to all manuals. #latex_appendices = [] # If false, no module index is generated. #latex_use_modindex = True trunk-2018.02b/doc/sphinx/book/confReference.py000066400000000000000000000640601324306050200213220ustar00rootroot00000000000000# -*- coding: utf-8 -*- # # Yade documentation build configuration file, created by # sphinx-quickstart on Mon Nov 16 21:49:34 2009. # # 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. # relevant posts to sphinx ML # http://groups.google.com/group/sphinx-dev/browse_thread/thread/b4fbc8d31d230fc4 # http://groups.google.com/group/sphinx-dev/browse_thread/thread/118598245d5f479b ##################### ## custom yade roles ##################### ## ## http://docutils.sourceforge.net/docs/howto/rst-roles.html import sys, os, re from docutils import nodes from sphinx import addnodes from sphinx.roles import XRefRole import docutils # # needed for creating hyperlink targets. # it should be cleand up and unified for both LaTeX and HTML via # the pending_xref node which gets resolved to real link target # by sphinx automatically once all docs have been processed. # # xrefs: http://groups.google.com/group/sphinx-dev/browse_thread/thread/d719d19307654548 # # import __builtin__ if 'latex' in sys.argv: __builtin__.writer='latex' elif 'html' in sys.argv: __builtin__.writer='html' elif 'epub' in sys.argv: __builtin__.writer='epub' else: raise RuntimeError("Must have either 'latex' or 'html' on the command line (hack for reference styles)") sys.path.append(os.path.abspath('./..')) def yaderef_role(role,rawtext,text,lineno,inliner,options={},content=[]): "Handle the :yref:`` role, by making hyperlink to yade.wrapper.*. It supports :yref:`Link text` syntax, like usual hyperlinking roles." id=rawtext.split(':',2)[2][1:-1] txt=id; explicitText=False m=re.match('(.*)\s*<(.*)>\s*',id) if m: explicitText=True txt,id=m.group(1),m.group(2) id=id.replace('::','.') #node=nodes.reference(rawtext,docutils.utils.unescape(txt),refuri='http://beta.arcig.cz/~eudoxos/yade/doxygen/?search=%s'%id,**options) #node=nodes.reference(rawtext,docutils.utils.unescape(txt),refuri='yade.wrapper.html#yade.wrapper.%s'%id,**options) return [mkYrefNode(id,txt,rawtext,role,explicitText,lineno,options)],[] def yadesrc_role(role,rawtext,lineno,inliner,options={},content=[]): "Handle the :ysrc:`` role, making hyperlink to git repository webpage with that path. Supports :ysrc:`Link text` syntax, like usual hyperlinking roles. If target ends with ``/``, it is assumed to be a directory." id=rawtext.split(':',2)[2][1:-1] txt=id m=re.match('(.*)\s*<(.*)>\s*',id) if m: txt,id=m.group(1),m.group(2) return [nodes.reference(rawtext,docutils.utils.unescape(txt),refuri='https://github.com/yade/trunk/blob/master/%s'%id)],[] ### **options should be passed to nodes.reference as well # map modules to their html (rst) filenames. Used for sub-modules, where e.g. SpherePack is yade._packSphere.SpherePack, but is documented from yade.pack.rst moduleMap={ 'yade._packPredicates':'yade.pack', 'yade._packSpheres':'yade.pack', 'yade._packObb':'yade.pack' } class YadeXRefRole(XRefRole): #def process_link def process_link(self, env, refnode, has_explicit_title, title, target): print 'TARGET:','yade.wrapper.'+target return '[['+title+']]','yade.wrapper.'+target def mkYrefNode(target,text,rawtext,role,explicitText,lineno,options={}): """Create hyperlink to yade target. Targets starting with literal 'yade.' are absolute, but the leading 'yade.' will be stripped from the link text. Absolute tergets are supposed to live in page named yade.[module].html, anchored at #yade.[module2].[rest of target], where [module2] is identical to [module], unless mapped over by moduleMap. Other targets are supposed to live in yade.wrapper (such as c++ classes).""" writer=__builtin__.writer # to make sure not shadowed by a local var import string if target.startswith('yade.'): module='.'.join(target.split('.')[0:2]) module2=(module if module not in moduleMap.keys() else moduleMap[module]) if target==module: target='' # to reference the module itself uri=('%%%s#%s'%(module2,target) if writer=='latex' else '%s.html#%s'%(module2,target)) if not explicitText and module!=module2: text=module2+'.'+'.'.join(target.split('.')[2:]) text=string.replace(text,'yade.','',1) elif target.startswith('external:'): exttarget=target.split(':',1)[1] if not explicitText: text=exttarget target=exttarget if '.' in exttarget else 'module-'+exttarget uri=(('%%external#%s'%target) if writer=='latex' else 'external.html#%s'%target) else: uri=(('%%yade.wrapper#yade.wrapper.%s'%target) if writer=='latex' else 'yade.wrapper.html#yade.wrapper.%s'%target) #print writer,uri return nodes.reference(rawtext,docutils.utils.unescape(text),refuri=uri,**options) #return [refnode],[] def ydefault_role(role,rawtext,text,lineno,inliner,options={},content=[]): "Handle the :ydefault:`something` role. fixSignature handles it now in the member signature itself, this merely expands to nothing." return [],[] def yattrtype_role(role,rawtext,text,lineno,inliner,options={},content=[]): "Handle the :yattrtype:`something` role. fixSignature handles it now in the member signature itself, this merely expands to nothing." return [],[] # FIXME: should return readable representation of bits of the number (yade.wrapper.AttrFlags enum) def yattrflags_role(role,rawtext,text,lineno,inliner,options={},content=[]): "Handle the :yattrflags:`something` role. fixSignature handles it now in the member signature itself." return [],[] from docutils.parsers.rst import roles def yaderef_role_2(type,rawtext,text,lineno,inliner,options={},content=[]): return YadeXRefRole()('yref',rawtext,text,lineno,inliner,options,content) roles.register_canonical_role('yref', yaderef_role) roles.register_canonical_role('ysrc', yadesrc_role) roles.register_canonical_role('ydefault', ydefault_role) roles.register_canonical_role('yattrtype', yattrtype_role) roles.register_canonical_role('yattrflags', yattrflags_role) ## http://sphinx.pocoo.org/config.html#confval-rst_epilog rst_epilog = """ .. |yupdate| replace:: *(auto-updated)* .. |ycomp| replace:: *(auto-computed)* .. |ystatic| replace:: *(static)* """ import collections def customExclude(app, what, name, obj, skip, options): if name=='clone': if 'Serializable.clone' in str(obj): return False return True #escape crash on non iterable __doc__ in some qt object if hasattr(obj,'__doc__') and obj.__doc__ and not isinstance(obj.__doc__, collections.Iterable): return True if hasattr(obj,'__doc__') and obj.__doc__ and ('|ydeprecated|' in obj.__doc__ or '|yhidden|' in obj.__doc__): return True #if re.match(r'\b(__init__|__reduce__|__repr__|__str__)\b',name): return True if name.startswith('_'): if name=='__init__': # skip boost classes with parameterless ctor (arg1=implicit self) if obj.__doc__=="\n__init__( (object)arg1) -> None": return True # skip undocumented ctors if not obj.__doc__: return True # skip default ctor for serializable, taking dict of attrs if obj.__doc__=='\n__init__( (object)arg1) -> None\n\nobject __init__(tuple args, dict kwds)': return True #for i,l in enumerate(obj.__doc__.split('\n')): print name,i,l,'##' return False return True return False def isBoostFunc(what,obj): return what=='function' and obj.__repr__().startswith('.*$',doc1): return None,docc if doc1.endswith(':'): doc1=doc1[:-1] strippedDoc=doc.split('\n')[2:] # check if all lines are padded allLinesHave4LeadingSpaces=True for l in strippedDoc: if l.startswith(' '): continue allLinesHave4LeadingSpaces=False; break # remove the padding if so if allLinesHave4LeadingSpaces: strippedDoc=[l[4:] for l in strippedDoc] for i in range(len(strippedDoc)): # fix signatures inside docstring (one function with multiple signatures) strippedDoc[i],n=re.subn(r'([a-zA-Z_][a-zA-Z0-9_]*\() \(object\)arg1(, |)',r'\1',strippedDoc[i].replace('->','→')) # inspect dosctring after mangling if 'getViscoelasticFromSpheresInteraction' in name and False: print name print strippedDoc print '======================' for l in strippedDoc: print l print '======================' sig=doc1.split('(',1)[1] if removeSelf: # remove up to the first comma; if no comma present, then the method takes no arguments # if [ precedes the comma, add it to the result (ugly!) try: ss=sig.split(',',1) if ss[0].endswith('['): sig='['+ss[1] else: sig=ss[1] except IndexError: # grab the return value try: sig=') -> '+sig.split('->')[-1] #if 'Serializable' in name: print 1000*'#',name except IndexError: sig=')' return '('+sig,strippedDoc def fixSignature(app, what, name, obj, options, signature, return_annotation): #print what,name,obj,signature#,dir(obj) if what=='attribute': doc=unicode(obj.__doc__) ret='' m=re.match('.*:ydefault:`(.*?)`.*',doc) if m: typ='' #try: # clss='.'.join(name.split('.')[:-1]) # instance=eval(clss+'()') # typ='; '+getattr(instance,name.split('.')[-1]).__class__.__name__ # if typ=='; NoneType': typ='' #except TypeError: ##no registered converted # typ='' dfl=m.group(1) m2=re.match(r'\s*\(\s*\(\s*void\s*\)\s*\"(.*)\"\s*,\s*(.*)\s*\)\s*',dfl) if m2: dfl="%s, %s"%(m2.group(2),m2.group(1)) if dfl!='': ret+=' (='+dfl+'%s)'%typ else: ret+=' (=uninitalized%s)'%typ #m=re.match('.*\[(.{,8})\].*',doc) #m=re.match('.*:yunit:`(.?*)`.*',doc) #if m: # units=m.group(1) # print '@@@@@@@@@@@@@@@@@@@@@',name,units # ret+=' ['+units+']' return ret,None elif what=='class': ret=[] if len(obj.__bases__)>0: base=obj.__bases__[0] while base.__module__!='Boost.Python': ret+=[base.__name__] if len(base.__bases__)>0: base=base.__bases__[0] else: break if len(ret): return ' (inherits '+u' → '.join(ret)+')',None else: return None,None elif isBoostFunc(what,obj): sig=boostFuncSignature(name,obj)[0] or ' (wrapped c++ function)' return sig,None elif isBoostMethod(what,obj): sig=boostFuncSignature(name,obj,removeSelf=True)[0] return sig,None #else: print what,name,obj.__repr__() #return None,None from sphinx import addnodes def parse_ystaticattr(env,attr,attrnode): m=re.match(r'([a-zA-Z0-9_]+)\.(.*)\(=(.*)\)',attr) if not m: print 100*'@'+' Static attribute %s not matched'%attr attrnode+=addnodes.desc_name(attr,attr) klass,name,default=m.groups() #attrnode+=addnodes.desc_type('static','static') attrnode+=addnodes.desc_name(name,name) plist=addnodes.desc_parameterlist() if default=='': default='unspecified' plist+=addnodes.desc_parameter('='+default,'='+default) attrnode+=plist attrnode+=addnodes.desc_annotation(' [static]',' [static]') return klass+'.'+name ############################# ## set tab size ################### ## http://groups.google.com/group/sphinx-dev/browse_thread/thread/35b8071ffe9a8feb def setup(app): from sphinx.highlighting import lexers from pygments.lexers.compiled import CppLexer lexers['cpp'] = CppLexer(tabsize=3) lexers['c++'] = CppLexer(tabsize=3) from pygments.lexers.agile import PythonLexer lexers['python'] = PythonLexer(tabsize=3) app.connect('source-read',fixSrc) app.connect('autodoc-skip-member',customExclude) app.connect('autodoc-process-signature',fixSignature) app.connect('autodoc-process-docstring',fixDocstring) app.add_description_unit('ystaticattr',None,objname='static attribute',indextemplate='pair: %s; static method',parse_node=parse_ystaticattr) import sys, os # 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.append(os.path.abspath('.')) # -- General configuration ----------------------------------------------------- # Add any Sphinx extension module names here, as strings. They can be extensions # coming with Sphinx (named 'sphinx.ext.*') or your custom ones. # # HACK: change ipython console regexp from ipython_console_highlighting.py import re sys.path.append(os.path.abspath('.')) import yade.config if 1: if yade.runtime.ipython_version<12: import ipython_directive as id else: if 12<=yade.runtime.ipython_version<13: import ipython_directive012 as id elif 13<=yade.runtime.ipython_version<200: import ipython_directive013 as id elif 200<=yade.runtime.ipython_version<500: import ipython_directive200 as id else: import ipython_directive500 as id #The next four lines are for compatibility with IPython 0.13.1 ipython_rgxin =re.compile(r'(?:In |Yade )\[(\d+)\]:\s?(.*)\s*') ipython_rgxout=re.compile(r'(?:Out| -> )\[(\d+)\]:\s?(.*)\s*') ipython_promptin ='Yade [%d]:' ipython_promptout=' -> [%d]: ' ipython_cont_spaces=' ' #For IPython <=0.12, the following lines are used id.rgxin =re.compile(r'(?:In |Yade )\[(\d+)\]:\s?(.*)\s*') id.rgxout=re.compile(r'(?:Out| -> )\[(\d+)\]:\s?(.*)\s*') id.rgxcont=re.compile(r'(?: +)\.\.+:\s?(.*)\s*') id.fmtin ='Yade [%d]:' id.fmtout =' -> [%d]: ' # for some reason, out and cont must have the trailing space id.fmtcont=' .\D.: ' id.rc_override=dict(prompt_in1="Yade [\#]:",prompt_in2=" .\D.:",prompt_out=r" -> [\#]: ") if yade.runtime.ipython_version<12: id.reconfig_shell() import ipython_console_highlighting as ich ich.IPythonConsoleLexer.input_prompt = re.compile("(Yade \[[0-9]+\]: )") ich.IPythonConsoleLexer.output_prompt = re.compile("(( -> |Out)|\[[0-9]+\]: )") ich.IPythonConsoleLexer.continue_prompt = re.compile("\s+\.\.\.+:") extensions = [ 'sphinx.ext.autodoc', 'sphinx.ext.autosummary', 'sphinx.ext.coverage', 'sphinx.ext.pngmath', 'sphinx.ext.graphviz', 'sphinx.ext.viewcode', 'sphinx.ext.inheritance_diagram', 'matplotlib.sphinxext.plot_directive', 'matplotlib.sphinxext.only_directives', #'matplotlib.sphinxext.mathmpl', 'ipython_console_highlighting', 'youtube', 'sphinx.ext.todo', ] if yade.runtime.ipython_version<12: extensions.append('ipython_directive') else: if 12<=yade.runtime.ipython_version<13: extensions.append('ipython_directive012') elif 13<=yade.runtime.ipython_version<200: extensions.append('ipython_directive013') elif 200<=yade.runtime.ipython_version<500: extensions.append('ipython_directive200') else: extensions.append('ipython_directive500') # the sidebar extension if False: if writer=='html': extensions+=['sphinx.ext.sidebar'] sidebar_all=True sidebar_relling=True #sidebar_abbrev=True sidebar_tocdepth=3 ## http://trac.sagemath.org/sage_trac/attachment/ticket/7549/trac_7549-doc_inheritance_underscore.patch # GraphViz includes dot, neato, twopi, circo, fdp. graphviz_dot = 'dot' inheritance_graph_attrs = { 'rankdir' : 'BT' } inheritance_node_attrs = { 'height' : 0.5, 'fontsize' : 12, 'shape' : 'oval' } inheritance_edge_attrs = {} my_latex_preamble=r''' \usepackage{euler} % must be loaded before fontspec for the whole doc (below); this must be kept for pngmath, however \usepackage{hyperref} \usepackage{amsmath} \usepackage{amsbsy} %\usepackage{mathabx} \usepackage{underscore} \usepackage[all]{xy} % Metadata of the pdf output \hypersetup{pdftitle={Reference Manual}} \hypersetup{pdfauthor={Vaclav Smilauer; Emanuele Catalano; Bruno Chareyre; Sergei Dorofeenko; Jerome Duriez; Nolan Dyck; Burak Er; Jan Elias; Alexander Eulitz; Anton Gladky; Christian Jakob; Francois Kneib; Janek Kozicki; Donia Marzougui; Raphael Maurin; Chiara Modenese; Luc Scholtes; Luc Sibille; Jan Stransky; Thomas Sweijen; Klaus Thoeni; Chao Yuan}} \hypersetup{pdfkeywords={Discrete element method; dem; yade; documentation; manual; python; c++; git}} % symbols \let\mat\boldsymbol % matrix \let\vec\boldsymbol % vector \let\tens\boldsymbol % tensor \def\normalized#1{\widehat{#1}} \def\locframe#1{\widetilde{#1}} % timestep \def\Dt{\Delta t} \def\Dtcr{\Dt_{\rm cr}} % algorithm complexity \def\bigO#1{\ensuremath{\mathcal{O}(#1)}} % variants for greek symbols \let\epsilon\varepsilon \let\theta\vartheta \let\phi\varphi % shorthands \let\sig\sigma \let\eps\epsilon % variables at different points of time \def\prev#1{#1^-} \def\pprev#1{#1^\ominus} \def\curr#1{#1^{\circ}} \def\nnext#1{#1^\oplus} \def\next#1{#1^+} % shorthands for geometry \def\currn{\curr{\vec{n}}} \def\currC{\curr{\vec{C}}} \def\uT{\vec{u}_T} \def\curruT{\curr{\vec{u}}_T} \def\prevuT{\prev{\vec{u}}_T} \def\currn{\curr{\vec{n}}} \def\prevn{\prev{\vec{n}}} % motion \def\pprevvel{\pprev{\dot{\vec{u}}}} \def\nnextvel{\nnext{\dot{\vec{u}}}} \def\curraccel{\curr{\ddot{\vec{u}}}} \def\prevpos{\prev{\vec{u}}} \def\currpos{\curr{\vec{u}}} \def\nextpos{\next{\vec{u}}} \def\curraaccel{\curr{\dot{\vec{\omega}}}} \def\pprevangvel{\pprev{\vec{\omega}}} \def\nnextangvel{\nnext{\vec{\omega}}} \def\loccurr#1{\curr{\locframe{#1}}} \def\numCPU{n_{\rm cpu}} \DeclareMathOperator{\Align}{Align} \DeclareMathOperator{\sign}{sgn} % sorting algorithms \def\isleq#1{\currelem{#1}\ar@/^/[ll]^{\leq}} \def\isnleq#1{\currelem{#1}\ar@/^/[ll]^{\not\leq}} \def\currelem#1{\fbox{$#1$}} \def\sortSep{||} \def\sortInv{\hbox{\phantom{||}}} \def\sortlines#1{\xymatrix@=3pt{#1}} \def\crossBound{||\mkern-18mu<} ''' pngmath_latex_preamble=r'\usepackage[active]{preview}'+my_latex_preamble pngmath_use_preview=True # Add any paths that contain templates here, relative to this directory. templates_path = ['templates'] # The suffix of source filenames. source_suffix = '.rst' # The encoding of source files. #source_encoding = 'utf-8' # The master toctree document. master_doc = 'index-toctree-reference' # General information about the project. project = u'Yade' copyright = u'2009, Václav Šmilauer' # 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 = 'Yade documentation 2nd ed.' # The full version, including alpha/beta/rc tags. release = 'Yade documentation 2nd ed.' # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. #language = None # There are two options for replacing |today|: either, you set today to some # non-false value, then it is used: #today = '' # Else, today_fmt is used as the format for a strftime call. #today_fmt = '%B %d, %Y' # List of documents that shouldn't be included in the build. #unused_docs = [] # List of directories, relative to source directory, that shouldn't be searched # for source files. exclude_trees = ['_build','../_build'] # 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 = ['yade.'] # -- Options for HTML output --------------------------------------------------- # The theme to use for HTML and HTML Help pages. Major themes that come with # Sphinx are currently 'default' and 'sphinxdoc'. html_theme = 'default' # 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 = {'stickysidebar':'true','collapsiblesidebar':'true','rightsidebar':'false'} # 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 = '../fig/yade-logo.png' # The name of an image file (within the static path) to use as favicon of the # docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 # pixels large. html_favicon = '../fig/yade-favicon.ico' # Add any paths that contain custom static files (such as style sheets) here, # relative to this directory. They are copied after the builtin static files, # so a file named "default.css" will overwrite the builtin "default.css". html_static_path = ['static-html'] # 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 = {} html_index='index.html' # Additional templates that should be rendered to pages, maps page names to # template names. html_additional_pages = { 'index':'index.html'} # If false, no module index is generated. #html_use_modindex = 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, 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 = '' # If nonempty, this is the file name suffix for HTML files (e.g. ".xhtml"). #html_file_suffix = '' # Output file base name for HTML help builder. htmlhelp_basename = 'Yadedoc' # -- Options for LaTeX output -------------------------------------------------- my_maketitle=r''' \begin{titlepage} \begin{flushright} \hrule{} % Upper part of the page \begin{flushright} \includegraphics[width=0.15\textwidth]{yade-logo.png}\par \end{flushright} \vspace{20 mm} \text{\sffamily\bfseries\Huge Reference Manual}\\ \vspace{20 mm} %\vspace{70 mm} \begin{sffamily}\bfseries\Large V\'{a}clav \v{S}milauer, Emanuele Catalano, Bruno Chareyre, Sergei Dorofeenko, J\'er\^ome Duriez, Nolan Dyck, Jan Eli\'{a}\v{s}, Burak Er, Alexander Eulitz, Anton Gladky, Christian Jakob, Fran\c{c}ois Kneib, Janek Kozicki, Donia Marzougui, Rapha\"el Maurin, Chiara Modenese, Luc Scholt\`{e}s, Luc Sibille, Jan Str\'{a}nsk\'{y}, Thomas Sweijen, Klaus Thoeni, Chao Yuan \end{sffamily} \vspace{20 mm} \hrule{} \vfill \textit{\large Yade Documentation 2nd edition, 2015}\\ \textit{based on Yade 1.14.0}\\ \end{flushright} \end{titlepage} \text{\sffamily\bfseries\large Citing this document:}\\ \v{S}milauer V. et al. (2015). Reference Manual. In:\textit{Yade Documentation 2nd ed.} doi:10.5281/zenodo.34045. http://yade-dem.org\\ See also http://yade-dem/doc/citing.html. ''' latex_elements=dict( papersize='a4paper', fontpkg=r''' \usepackage{euler} \usepackage{fontspec,xunicode,xltxtra} %\setmainfont[BoldFont={LMRoman10 Bold}]{CMU Concrete} %% CMU Concrete must be installed by hand as otf ''', utf8extra='', fncychap='', preamble=my_latex_preamble, footer='', inputenc='', fontenc='', maketitle=my_maketitle, ) # The paper size ('letter' or 'a4'). #latex_paper_size = 'letter' # The font size ('10pt', '11pt' or '12pt'). #latex_font_size = '10pt' # Grouping the document tree into LaTeX files. List of tuples # (source start file, target name, title, author, documentclass [howto/manual]). latex_documents = [ ('index-toctree-reference', 'YadeReference.tex', u'Reference Manual', u'Václav Šmilauer et al.', 'manual'), ] # The name of an image file (relative to this directory) to place at the top of # the title page. latex_logo = '../fig/yade-logo.png' # For "manual" documents, if this is true, then toplevel headings are parts, # not chapters. latex_use_parts = False # Additional stuff for the LaTeX preamble. #latex_preamble = '' # Documents to append as an appendix to all manuals. #latex_appendices = [] # If false, no module index is generated. #latex_use_modindex = True trunk-2018.02b/doc/sphinx/book/confTheory.py000066400000000000000000000627371324306050200207070ustar00rootroot00000000000000# -*- coding: utf-8 -*- # # Yade documentation build configuration file, created by # sphinx-quickstart on Mon Nov 16 21:49:34 2009. # # 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. # relevant posts to sphinx ML # http://groups.google.com/group/sphinx-dev/browse_thread/thread/b4fbc8d31d230fc4 # http://groups.google.com/group/sphinx-dev/browse_thread/thread/118598245d5f479b ##################### ## custom yade roles ##################### ## ## http://docutils.sourceforge.net/docs/howto/rst-roles.html import sys, os, re from docutils import nodes from sphinx import addnodes from sphinx.roles import XRefRole import docutils # # needed for creating hyperlink targets. # it should be cleand up and unified for both LaTeX and HTML via # the pending_xref node which gets resolved to real link target # by sphinx automatically once all docs have been processed. # # xrefs: http://groups.google.com/group/sphinx-dev/browse_thread/thread/d719d19307654548 # # import __builtin__ if 'latex' in sys.argv: __builtin__.writer='latex' elif 'html' in sys.argv: __builtin__.writer='html' elif 'epub' in sys.argv: __builtin__.writer='epub' else: raise RuntimeError("Must have either 'latex' or 'html' on the command line (hack for reference styles)") sys.path.append(os.path.abspath('./..')) def yaderef_role(role,rawtext,text,lineno,inliner,options={},content=[]): "Handle the :yref:`` role, by making hyperlink to yade.wrapper.*. It supports :yref:`Link text` syntax, like usual hyperlinking roles." id=rawtext.split(':',2)[2][1:-1] txt=id; explicitText=False m=re.match('(.*)\s*<(.*)>\s*',id) if m: explicitText=True txt,id=m.group(1),m.group(2) id=id.replace('::','.') #node=nodes.reference(rawtext,docutils.utils.unescape(txt),refuri='http://beta.arcig.cz/~eudoxos/yade/doxygen/?search=%s'%id,**options) #node=nodes.reference(rawtext,docutils.utils.unescape(txt),refuri='yade.wrapper.html#yade.wrapper.%s'%id,**options) return [mkYrefNode(id,txt,rawtext,role,explicitText,lineno,options)],[] def yadesrc_role(role,rawtext,lineno,inliner,options={},content=[]): "Handle the :ysrc:`` role, making hyperlink to git repository webpage with that path. Supports :ysrc:`Link text` syntax, like usual hyperlinking roles. If target ends with ``/``, it is assumed to be a directory." id=rawtext.split(':',2)[2][1:-1] txt=id m=re.match('(.*)\s*<(.*)>\s*',id) if m: txt,id=m.group(1),m.group(2) return [nodes.reference(rawtext,docutils.utils.unescape(txt),refuri='https://github.com/yade/trunk/blob/master/%s'%id)],[] ### **options should be passed to nodes.reference as well # map modules to their html (rst) filenames. Used for sub-modules, where e.g. SpherePack is yade._packSphere.SpherePack, but is documented from yade.pack.rst moduleMap={ 'yade._packPredicates':'yade.pack', 'yade._packSpheres':'yade.pack', 'yade._packObb':'yade.pack' } class YadeXRefRole(XRefRole): #def process_link def process_link(self, env, refnode, has_explicit_title, title, target): print 'TARGET:','yade.wrapper.'+target return '[['+title+']]','yade.wrapper.'+target def mkYrefNode(target,text,rawtext,role,explicitText,lineno,options={}): """Create hyperlink to yade target. Targets starting with literal 'yade.' are absolute, but the leading 'yade.' will be stripped from the link text. Absolute tergets are supposed to live in page named yade.[module].html, anchored at #yade.[module2].[rest of target], where [module2] is identical to [module], unless mapped over by moduleMap. Other targets are supposed to live in yade.wrapper (such as c++ classes).""" writer=__builtin__.writer # to make sure not shadowed by a local var import string if target.startswith('yade.'): module='.'.join(target.split('.')[0:2]) module2=(module if module not in moduleMap.keys() else moduleMap[module]) if target==module: target='' # to reference the module itself uri=('%%%s#%s'%(module2,target) if writer=='latex' else '%s.html#%s'%(module2,target)) if not explicitText and module!=module2: text=module2+'.'+'.'.join(target.split('.')[2:]) text=string.replace(text,'yade.','',1) elif target.startswith('external:'): exttarget=target.split(':',1)[1] if not explicitText: text=exttarget target=exttarget if '.' in exttarget else 'module-'+exttarget uri=(('%%external#%s'%target) if writer=='latex' else 'external.html#%s'%target) else: uri=(('%%yade.wrapper#yade.wrapper.%s'%target) if writer=='latex' else 'yade.wrapper.html#yade.wrapper.%s'%target) #print writer,uri return nodes.reference(rawtext,docutils.utils.unescape(text),refuri=uri,**options) #return [refnode],[] def ydefault_role(role,rawtext,text,lineno,inliner,options={},content=[]): "Handle the :ydefault:`something` role. fixSignature handles it now in the member signature itself, this merely expands to nothing." return [],[] def yattrtype_role(role,rawtext,text,lineno,inliner,options={},content=[]): "Handle the :yattrtype:`something` role. fixSignature handles it now in the member signature itself, this merely expands to nothing." return [],[] # FIXME: should return readable representation of bits of the number (yade.wrapper.AttrFlags enum) def yattrflags_role(role,rawtext,text,lineno,inliner,options={},content=[]): "Handle the :yattrflags:`something` role. fixSignature handles it now in the member signature itself." return [],[] from docutils.parsers.rst import roles def yaderef_role_2(type,rawtext,text,lineno,inliner,options={},content=[]): return YadeXRefRole()('yref',rawtext,text,lineno,inliner,options,content) roles.register_canonical_role('yref', yaderef_role) roles.register_canonical_role('ysrc', yadesrc_role) roles.register_canonical_role('ydefault', ydefault_role) roles.register_canonical_role('yattrtype', yattrtype_role) roles.register_canonical_role('yattrflags', yattrflags_role) ## http://sphinx.pocoo.org/config.html#confval-rst_epilog rst_epilog = """ .. |yupdate| replace:: *(auto-updated)* .. |ycomp| replace:: *(auto-computed)* .. |ystatic| replace:: *(static)* """ import collections def customExclude(app, what, name, obj, skip, options): if name=='clone': if 'Serializable.clone' in str(obj): return False return True #escape crash on non iterable __doc__ in some qt object if hasattr(obj,'__doc__') and obj.__doc__ and not isinstance(obj.__doc__, collections.Iterable): return True if hasattr(obj,'__doc__') and obj.__doc__ and ('|ydeprecated|' in obj.__doc__ or '|yhidden|' in obj.__doc__): return True #if re.match(r'\b(__init__|__reduce__|__repr__|__str__)\b',name): return True if name.startswith('_'): if name=='__init__': # skip boost classes with parameterless ctor (arg1=implicit self) if obj.__doc__=="\n__init__( (object)arg1) -> None": return True # skip undocumented ctors if not obj.__doc__: return True # skip default ctor for serializable, taking dict of attrs if obj.__doc__=='\n__init__( (object)arg1) -> None\n\nobject __init__(tuple args, dict kwds)': return True #for i,l in enumerate(obj.__doc__.split('\n')): print name,i,l,'##' return False return True return False def isBoostFunc(what,obj): return what=='function' and obj.__repr__().startswith('.*$',doc1): return None,docc if doc1.endswith(':'): doc1=doc1[:-1] strippedDoc=doc.split('\n')[2:] # check if all lines are padded allLinesHave4LeadingSpaces=True for l in strippedDoc: if l.startswith(' '): continue allLinesHave4LeadingSpaces=False; break # remove the padding if so if allLinesHave4LeadingSpaces: strippedDoc=[l[4:] for l in strippedDoc] for i in range(len(strippedDoc)): # fix signatures inside docstring (one function with multiple signatures) strippedDoc[i],n=re.subn(r'([a-zA-Z_][a-zA-Z0-9_]*\() \(object\)arg1(, |)',r'\1',strippedDoc[i].replace('->','→')) # inspect dosctring after mangling if 'getViscoelasticFromSpheresInteraction' in name and False: print name print strippedDoc print '======================' for l in strippedDoc: print l print '======================' sig=doc1.split('(',1)[1] if removeSelf: # remove up to the first comma; if no comma present, then the method takes no arguments # if [ precedes the comma, add it to the result (ugly!) try: ss=sig.split(',',1) if ss[0].endswith('['): sig='['+ss[1] else: sig=ss[1] except IndexError: # grab the return value try: sig=') -> '+sig.split('->')[-1] #if 'Serializable' in name: print 1000*'#',name except IndexError: sig=')' return '('+sig,strippedDoc def fixSignature(app, what, name, obj, options, signature, return_annotation): #print what,name,obj,signature#,dir(obj) if what=='attribute': doc=unicode(obj.__doc__) ret='' m=re.match('.*:ydefault:`(.*?)`.*',doc) if m: typ='' #try: # clss='.'.join(name.split('.')[:-1]) # instance=eval(clss+'()') # typ='; '+getattr(instance,name.split('.')[-1]).__class__.__name__ # if typ=='; NoneType': typ='' #except TypeError: ##no registered converted # typ='' dfl=m.group(1) m2=re.match(r'\s*\(\s*\(\s*void\s*\)\s*\"(.*)\"\s*,\s*(.*)\s*\)\s*',dfl) if m2: dfl="%s, %s"%(m2.group(2),m2.group(1)) if dfl!='': ret+=' (='+dfl+'%s)'%typ else: ret+=' (=uninitalized%s)'%typ #m=re.match('.*\[(.{,8})\].*',doc) #m=re.match('.*:yunit:`(.?*)`.*',doc) #if m: # units=m.group(1) # print '@@@@@@@@@@@@@@@@@@@@@',name,units # ret+=' ['+units+']' return ret,None elif what=='class': ret=[] if len(obj.__bases__)>0: base=obj.__bases__[0] while base.__module__!='Boost.Python': ret+=[base.__name__] if len(base.__bases__)>0: base=base.__bases__[0] else: break if len(ret): return ' (inherits '+u' → '.join(ret)+')',None else: return None,None elif isBoostFunc(what,obj): sig=boostFuncSignature(name,obj)[0] or ' (wrapped c++ function)' return sig,None elif isBoostMethod(what,obj): sig=boostFuncSignature(name,obj,removeSelf=True)[0] return sig,None #else: print what,name,obj.__repr__() #return None,None from sphinx import addnodes def parse_ystaticattr(env,attr,attrnode): m=re.match(r'([a-zA-Z0-9_]+)\.(.*)\(=(.*)\)',attr) if not m: print 100*'@'+' Static attribute %s not matched'%attr attrnode+=addnodes.desc_name(attr,attr) klass,name,default=m.groups() #attrnode+=addnodes.desc_type('static','static') attrnode+=addnodes.desc_name(name,name) plist=addnodes.desc_parameterlist() if default=='': default='unspecified' plist+=addnodes.desc_parameter('='+default,'='+default) attrnode+=plist attrnode+=addnodes.desc_annotation(' [static]',' [static]') return klass+'.'+name ############################# ## set tab size ################### ## http://groups.google.com/group/sphinx-dev/browse_thread/thread/35b8071ffe9a8feb def setup(app): from sphinx.highlighting import lexers from pygments.lexers.compiled import CppLexer lexers['cpp'] = CppLexer(tabsize=3) lexers['c++'] = CppLexer(tabsize=3) from pygments.lexers.agile import PythonLexer lexers['python'] = PythonLexer(tabsize=3) app.connect('source-read',fixSrc) app.connect('autodoc-skip-member',customExclude) app.connect('autodoc-process-signature',fixSignature) app.connect('autodoc-process-docstring',fixDocstring) app.add_description_unit('ystaticattr',None,objname='static attribute',indextemplate='pair: %s; static method',parse_node=parse_ystaticattr) import sys, os # 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.append(os.path.abspath('.')) # -- General configuration ----------------------------------------------------- # Add any Sphinx extension module names here, as strings. They can be extensions # coming with Sphinx (named 'sphinx.ext.*') or your custom ones. # # HACK: change ipython console regexp from ipython_console_highlighting.py import re sys.path.append(os.path.abspath('.')) import yade.config if 1: if yade.runtime.ipython_version<12: import ipython_directive as id else: if 12<=yade.runtime.ipython_version<13: import ipython_directive012 as id elif 13<=yade.runtime.ipython_version<200: import ipython_directive013 as id elif 200<=yade.runtime.ipython_version<500: import ipython_directive200 as id else: import ipython_directive500 as id #The next four lines are for compatibility with IPython 0.13.1 ipython_rgxin =re.compile(r'(?:In |Yade )\[(\d+)\]:\s?(.*)\s*') ipython_rgxout=re.compile(r'(?:Out| -> )\[(\d+)\]:\s?(.*)\s*') ipython_promptin ='Yade [%d]:' ipython_promptout=' -> [%d]: ' ipython_cont_spaces=' ' #For IPython <=0.12, the following lines are used id.rgxin =re.compile(r'(?:In |Yade )\[(\d+)\]:\s?(.*)\s*') id.rgxout=re.compile(r'(?:Out| -> )\[(\d+)\]:\s?(.*)\s*') id.rgxcont=re.compile(r'(?: +)\.\.+:\s?(.*)\s*') id.fmtin ='Yade [%d]:' id.fmtout =' -> [%d]: ' # for some reason, out and cont must have the trailing space id.fmtcont=' .\D.: ' id.rc_override=dict(prompt_in1="Yade [\#]:",prompt_in2=" .\D.:",prompt_out=r" -> [\#]: ") if yade.runtime.ipython_version<12: id.reconfig_shell() import ipython_console_highlighting as ich ich.IPythonConsoleLexer.input_prompt = re.compile("(Yade \[[0-9]+\]: )") ich.IPythonConsoleLexer.output_prompt = re.compile("(( -> |Out)|\[[0-9]+\]: )") ich.IPythonConsoleLexer.continue_prompt = re.compile("\s+\.\.\.+:") extensions = [ 'sphinx.ext.autodoc', 'sphinx.ext.autosummary', 'sphinx.ext.coverage', 'sphinx.ext.pngmath', 'sphinx.ext.graphviz', 'sphinx.ext.viewcode', 'sphinx.ext.inheritance_diagram', 'matplotlib.sphinxext.plot_directive', 'matplotlib.sphinxext.only_directives', #'matplotlib.sphinxext.mathmpl', 'ipython_console_highlighting', 'youtube', 'sphinx.ext.todo', ] if yade.runtime.ipython_version<12: extensions.append('ipython_directive') else: if 12<=yade.runtime.ipython_version<13: extensions.append('ipython_directive012') elif 13<=yade.runtime.ipython_version<200: extensions.append('ipython_directive013') elif 200<=yade.runtime.ipython_version<500: extensions.append('ipython_directive200') else: extensions.append('ipython_directive500') # the sidebar extension if False: if writer=='html': extensions+=['sphinx.ext.sidebar'] sidebar_all=True sidebar_relling=True #sidebar_abbrev=True sidebar_tocdepth=3 ## http://trac.sagemath.org/sage_trac/attachment/ticket/7549/trac_7549-doc_inheritance_underscore.patch # GraphViz includes dot, neato, twopi, circo, fdp. graphviz_dot = 'dot' inheritance_graph_attrs = { 'rankdir' : 'BT' } inheritance_node_attrs = { 'height' : 0.5, 'fontsize' : 12, 'shape' : 'oval' } inheritance_edge_attrs = {} my_latex_preamble=r''' \usepackage{euler} % must be loaded before fontspec for the whole doc (below); this must be kept for pngmath, however \usepackage{hyperref} \usepackage{amsmath} \usepackage{amsbsy} %\usepackage{mathabx} \usepackage{underscore} \usepackage[all]{xy} % Metadata of the pdf output \hypersetup{pdftitle={DEM formulation}} \hypersetup{pdfauthor={Vaclav Smilauer; Bruno Chareyre}} \hypersetup{pdfkeywords={Discrete element method; micromechanics; granular materials; contact law; hydromechanical coupling; yade; documentation}} % symbols \let\mat\boldsymbol % matrix \let\vec\boldsymbol % vector \let\tens\boldsymbol % tensor \def\normalized#1{\widehat{#1}} \def\locframe#1{\widetilde{#1}} % timestep \def\Dt{\Delta t} \def\Dtcr{\Dt_{\rm cr}} % algorithm complexity \def\bigO#1{\ensuremath{\mathcal{O}(#1)}} % variants for greek symbols \let\epsilon\varepsilon \let\theta\vartheta \let\phi\varphi % shorthands \let\sig\sigma \let\eps\epsilon % variables at different points of time \def\prev#1{#1^-} \def\pprev#1{#1^\ominus} \def\curr#1{#1^{\circ}} \def\nnext#1{#1^\oplus} \def\next#1{#1^+} % shorthands for geometry \def\currn{\curr{\vec{n}}} \def\currC{\curr{\vec{C}}} \def\uT{\vec{u}_T} \def\curruT{\curr{\vec{u}}_T} \def\prevuT{\prev{\vec{u}}_T} \def\currn{\curr{\vec{n}}} \def\prevn{\prev{\vec{n}}} % motion \def\pprevvel{\pprev{\dot{\vec{u}}}} \def\nnextvel{\nnext{\dot{\vec{u}}}} \def\curraccel{\curr{\ddot{\vec{u}}}} \def\prevpos{\prev{\vec{u}}} \def\currpos{\curr{\vec{u}}} \def\nextpos{\next{\vec{u}}} \def\curraaccel{\curr{\dot{\vec{\omega}}}} \def\pprevangvel{\pprev{\vec{\omega}}} \def\nnextangvel{\nnext{\vec{\omega}}} \def\loccurr#1{\curr{\locframe{#1}}} \def\numCPU{n_{\rm cpu}} \DeclareMathOperator{\Align}{Align} \DeclareMathOperator{\sign}{sgn} % sorting algorithms \def\isleq#1{\currelem{#1}\ar@/^/[ll]^{\leq}} \def\isnleq#1{\currelem{#1}\ar@/^/[ll]^{\not\leq}} \def\currelem#1{\fbox{$#1$}} \def\sortSep{||} \def\sortInv{\hbox{\phantom{||}}} \def\sortlines#1{\xymatrix@=3pt{#1}} \def\crossBound{||\mkern-18mu<} ''' pngmath_latex_preamble=r'\usepackage[active]{preview}'+my_latex_preamble pngmath_use_preview=True # Add any paths that contain templates here, relative to this directory. templates_path = ['templates'] # The suffix of source filenames. source_suffix = '.rst' # The encoding of source files. #source_encoding = 'utf-8' # The master toctree document. master_doc = 'index-toctree-theory' # General information about the project. project = u'Yade' copyright = u'2009, Václav Šmilauer' # 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 = 'Yade documentation 2nd ed.' # The full version, including alpha/beta/rc tags. release = 'Yade documentation 2nd ed.' # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. #language = None # There are two options for replacing |today|: either, you set today to some # non-false value, then it is used: #today = '' # Else, today_fmt is used as the format for a strftime call. #today_fmt = '%B %d, %Y' # List of documents that shouldn't be included in the build. #unused_docs = [] # List of directories, relative to source directory, that shouldn't be searched # for source files. exclude_trees = ['_build','../_build'] # 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 = ['yade.'] # -- Options for HTML output --------------------------------------------------- # The theme to use for HTML and HTML Help pages. Major themes that come with # Sphinx are currently 'default' and 'sphinxdoc'. html_theme = 'default' # 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 = {'stickysidebar':'true','collapsiblesidebar':'true','rightsidebar':'false'} # 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 = '../fig/yade-logo.png' # The name of an image file (within the static path) to use as favicon of the # docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 # pixels large. html_favicon = '../fig/yade-favicon.ico' # Add any paths that contain custom static files (such as style sheets) here, # relative to this directory. They are copied after the builtin static files, # so a file named "default.css" will overwrite the builtin "default.css". html_static_path = ['static-html'] # 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 = {} html_index='index.html' # Additional templates that should be rendered to pages, maps page names to # template names. html_additional_pages = { 'index':'index.html'} # If false, no module index is generated. #html_use_modindex = 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, 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 = '' # If nonempty, this is the file name suffix for HTML files (e.g. ".xhtml"). #html_file_suffix = '' # Output file base name for HTML help builder. htmlhelp_basename = 'Yadedoc' # -- Options for LaTeX output -------------------------------------------------- my_maketitle=r''' \begin{titlepage} \begin{flushright} \hrule{} % Upper part of the page \begin{flushright} \includegraphics[width=0.15\textwidth]{yade-logo.png}\par \end{flushright} \vspace{20 mm} \text{\sffamily\bfseries\Huge DEM Formulation}\\ \vspace{20 mm} %\vspace{70 mm} \begin{sffamily}\bfseries\Large V\'{a}clav \v{S}milauer, Bruno Chareyre \end{sffamily} \vspace{20 mm} \hrule{} \vfill \textit{\large Yade Documentation 2nd edition, 2015}\\ \textit{based on Yade 1.14.0}\\ \end{flushright} \end{titlepage} \text{\sffamily\bfseries\large Citing this document:}\\ \v{S}milauer V. and Chareyre B. (2015). DEM Formulation. In:\textit{Yade Documentation 2nd ed.} doi:10.5281/zenodo.34044. http://yade-dem.org\\ See also http://yade-dem/doc/citing.html. ''' latex_elements=dict( papersize='a4paper', fontpkg=r''' \usepackage{euler} \usepackage{fontspec,xunicode,xltxtra} %\setmainfont[BoldFont={LMRoman10 Bold}]{CMU Concrete} %% CMU Concrete must be installed by hand as otf ''', utf8extra='', fncychap='', preamble=my_latex_preamble, footer='', inputenc='', fontenc='', maketitle=my_maketitle, ) # The paper size ('letter' or 'a4'). #latex_paper_size = 'letter' # The font size ('10pt', '11pt' or '12pt'). #latex_font_size = '10pt' # Grouping the document tree into LaTeX files. List of tuples # (source start file, target name, title, author, documentclass [howto/manual]). latex_documents = [ ('index-toctree-theory', 'YadeTheory.tex', u'DEM Formulation', u'Václav Šmilauer', 'manual'), ] # The name of an image file (relative to this directory) to place at the top of # the title page. latex_logo = '../fig/yade-logo.png' # For "manual" documents, if this is true, then toplevel headings are parts, # not chapters. latex_use_parts = False # Additional stuff for the LaTeX preamble. #latex_preamble = '' # Documents to append as an appendix to all manuals. #latex_appendices = [] # If false, no module index is generated. #latex_use_modindex = True trunk-2018.02b/doc/sphinx/book/fullBibliography.rst.in000066400000000000000000000004221324306050200226310ustar00rootroot00000000000000.. This complete list of references is used for tex build, in which sectionning of the bibliography fails (see ) @bib2rst.bib2rst('../yade-docref.bib')@ @bib2rst.bib2rst('../yade-articles.bib')@ @bib2rst.bib2rst('../yade-theses.bib')@ @bib2rst.bib2rst('../references.bib')@trunk-2018.02b/doc/sphinx/citing.rst000066400000000000000000000063241324306050200172600ustar00rootroot00000000000000.. _citing: ################## Acknowledging Yade ################## In order to let users cite Yade consistently in publications, we provide a list of bibliographic references for the different parts of the documentation, as in the citation model pushed by `CGAL `_. This way of acknowledging Yade is also a way to make developments and documentation of Yade more attractive for researchers, who are evaluated on the basis of citations of their work by others. We therefore kindly ask users to cite Yade as accurately as possible in their papers. A more detailed discussion of the citation model and its application to Yade can be found `here `_. If new developments are presented and explained in self-contained papers (at the end of a PhD, typically), we will be glad to include them in the documentation and to reference them in the list below. Any other substantial improvement is welcome and can be discussed in the `yade-dev `_ mailing list. Citing the Yade Project as a whole (the lazy citation method) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ If it is not possible to choose the right chapter (but please try), you may cite the documentation [yade:doc]_ as a whole: V. Šmilauer et al. (2015), Yade Documentation 2nd ed. The Yade Project. DOI 10.5281/zenodo.34073 (http://yade-dem.org/doc/) Citing chapters of Yade Documentation ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ The second edition of Yade documentation is seen as a collection with the three volumes (or "chapters") listed below, and edited by V. Šmilauer, B. Chareyre, and A. Gladky. Please cite the chapter that is the most relevant in your case. For instance, a paper using one of the documented contact laws will cite the reference documentation [yade:reference2]_; if programing concepts are discussed, Yade's manual [yade:manual2]_ will be cited; the theoretical background [yade:background2]_ can be used as the refence for contact detection, time-step determination, or periodic boundary conditions. * The reference documentation includes details on equations and algorithms found at the highest levels of Yade (contact laws, boundary conditions controllers, pre- and post-processing modules,...) `(pdf version) `__: V. Šmilauer et al. (2015), Reference manual. In Yade Documentation 2nd ed. The Yade Project , DOI 10.5281/zenodo.34045 (http://yade-dem.org/doc/) * Software design, user's and programmer's manuals are in `(pdf version) `__: V. Šmilauer et al. (2015), Using and programming. In Yade Documentation 2nd ed. The Yade Project , DOI 10.5281/zenodo.34043 (http://yade-dem.org/doc/) * Fundamentals of the DEM as implemented in Yade are explained in `(pdf version) `__: V. Šmilauer et al. (2015), Dem formulation. In Yade Documentation 2nd ed. The Yade Project , DOI 10.5281/zenodo.34044 (http://yade-dem.org/doc/) Plain bibtex items can be `found here `_. trunk-2018.02b/doc/sphinx/conf.py000066400000000000000000000713071324306050200165530ustar00rootroot00000000000000# -*- coding: utf-8 -*- # # Yade documentation build configuration file, created by # sphinx-quickstart on Mon Nov 16 21:49:34 2009. # # 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. # relevant posts to sphinx ML # http://groups.google.com/group/sphinx-dev/browse_thread/thread/b4fbc8d31d230fc4 # http://groups.google.com/group/sphinx-dev/browse_thread/thread/118598245d5f479b ##################### ## custom yade roles ##################### ## ## http://docutils.sourceforge.net/docs/howto/rst-roles.html import sys, os, re from docutils import nodes from sphinx import addnodes from sphinx.roles import XRefRole import docutils # # needed for creating hyperlink targets. # it should be cleand up and unified for both LaTeX and HTML via # the pending_xref node which gets resolved to real link target # by sphinx automatically once all docs have been processed. # # xrefs: http://groups.google.com/group/sphinx-dev/browse_thread/thread/d719d19307654548 # # import __builtin__ if 'latex' in sys.argv: __builtin__.writer='latex' elif 'html' in sys.argv: __builtin__.writer='html' elif 'epub' in sys.argv: __builtin__.writer='epub' else: raise RuntimeError("Must have either 'latex' or 'html' on the command line (hack for reference styles)") def yaderef_role(role,rawtext,text,lineno,inliner,options={},content=[]): "Handle the :yref:`` role, by making hyperlink to yade.wrapper.*. It supports :yref:`Link text` syntax, like usual hyperlinking roles." id=rawtext.split(':',2)[2][1:-1] txt=id; explicitText=False m=re.match('(.*)\s*<(.*)>\s*',id) if m: explicitText=True txt,id=m.group(1),m.group(2) id=id.replace('::','.') #node=nodes.reference(rawtext,docutils.utils.unescape(txt),refuri='http://beta.arcig.cz/~eudoxos/yade/doxygen/?search=%s'%id,**options) #node=nodes.reference(rawtext,docutils.utils.unescape(txt),refuri='yade.wrapper.html#yade.wrapper.%s'%id,**options) return [mkYrefNode(id,txt,rawtext,role,explicitText,lineno,options)],[] def yadesrc_role(role,rawtext,lineno,inliner,options={},content=[]): "Handle the :ysrc:`` role, making hyperlink to git repository webpage with that path. Supports :ysrc:`Link text` syntax, like usual hyperlinking roles. If target ends with ``/``, it is assumed to be a directory." id=rawtext.split(':',2)[2][1:-1] txt=id m=re.match('(.*)\s*<(.*)>\s*',id) if m: txt,id=m.group(1),m.group(2) return [nodes.reference(rawtext,docutils.utils.unescape(txt),refuri='https://github.com/yade/trunk/blob/master/%s'%id)],[] ### **options should be passed to nodes.reference as well # map modules to their html (rst) filenames. Used for sub-modules, where e.g. SpherePack is yade._packSphere.SpherePack, but is documented from yade.pack.rst moduleMap={ 'yade._packPredicates':'yade.pack', 'yade._packSpheres':'yade.pack', 'yade._packObb':'yade.pack' } class YadeXRefRole(XRefRole): #def process_link def process_link(self, env, refnode, has_explicit_title, title, target): print 'TARGET:','yade.wrapper.'+target return '[['+title+']]','yade.wrapper.'+target def mkYrefNode(target,text,rawtext,role,explicitText,lineno,options={}): """Create hyperlink to yade target. Targets starting with literal 'yade.' are absolute, but the leading 'yade.' will be stripped from the link text. Absolute tergets are supposed to live in page named yade.[module].html, anchored at #yade.[module2].[rest of target], where [module2] is identical to [module], unless mapped over by moduleMap. Other targets are supposed to live in yade.wrapper (such as c++ classes).""" writer=__builtin__.writer # to make sure not shadowed by a local var import string if target.startswith('yade.'): module='.'.join(target.split('.')[0:2]) module2=(module if module not in moduleMap.keys() else moduleMap[module]) if target==module: target='' # to reference the module itself uri=('%%%s#%s'%(module2,target) if writer=='latex' else '%s.html#%s'%(module2,target)) if not explicitText and module!=module2: text=module2+'.'+'.'.join(target.split('.')[2:]) text=string.replace(text,'yade.','',1) elif target.startswith('external:'): exttarget=target.split(':',1)[1] if not explicitText: text=exttarget target=exttarget if '.' in exttarget else 'module-'+exttarget uri=(('%%external#%s'%target) if writer=='latex' else 'external.html#%s'%target) else: uri=(('%%yade.wrapper#yade.wrapper.%s'%target) if writer=='latex' else 'yade.wrapper.html#yade.wrapper.%s'%target) #print writer,uri return nodes.reference(rawtext,docutils.utils.unescape(text),refuri=uri,**options) #return [refnode],[] def ydefault_role(role,rawtext,text,lineno,inliner,options={},content=[]): "Handle the :ydefault:`something` role. fixSignature handles it now in the member signature itself, this merely expands to nothing." return [],[] def yattrtype_role(role,rawtext,text,lineno,inliner,options={},content=[]): "Handle the :yattrtype:`something` role. fixSignature handles it now in the member signature itself, this merely expands to nothing." return [],[] # FIXME: should return readable representation of bits of the number (yade.wrapper.AttrFlags enum) def yattrflags_role(role,rawtext,text,lineno,inliner,options={},content=[]): "Handle the :yattrflags:`something` role. fixSignature handles it now in the member signature itself." return [],[] from docutils.parsers.rst import roles def yaderef_role_2(type,rawtext,text,lineno,inliner,options={},content=[]): return YadeXRefRole()('yref',rawtext,text,lineno,inliner,options,content) roles.register_canonical_role('yref', yaderef_role) roles.register_canonical_role('ysrc', yadesrc_role) roles.register_canonical_role('ydefault', ydefault_role) roles.register_canonical_role('yattrtype', yattrtype_role) roles.register_canonical_role('yattrflags', yattrflags_role) ## http://sphinx.pocoo.org/config.html#confval-rst_epilog rst_epilog = """ .. |yupdate| replace:: *(auto-updated)* .. |ycomp| replace:: *(auto-computed)* .. |ystatic| replace:: *(static)* """ import collections def customExclude(app, what, name, obj, skip, options): if name=='clone': if 'Serializable.clone' in str(obj): return False return True #escape crash on non iterable __doc__ in some qt object if hasattr(obj,'__doc__') and obj.__doc__ and not isinstance(obj.__doc__, collections.Iterable): return True if hasattr(obj,'__doc__') and obj.__doc__ and ('|ydeprecated|' in obj.__doc__ or '|yhidden|' in obj.__doc__): return True #if re.match(r'\b(__init__|__reduce__|__repr__|__str__)\b',name): return True if name.startswith('_'): if name=='__init__': # skip boost classes with parameterless ctor (arg1=implicit self) if obj.__doc__=="\n__init__( (object)arg1) -> None": return True # skip undocumented ctors if not obj.__doc__: return True # skip default ctor for serializable, taking dict of attrs if obj.__doc__=='\n__init__( (object)arg1) -> None\n\nobject __init__(tuple args, dict kwds)': return True #for i,l in enumerate(obj.__doc__.split('\n')): print name,i,l,'##' return False return True return False def isBoostFunc(what,obj): return what=='function' and obj.__repr__().startswith('.*$',doc1): return None,docc if doc1.endswith(':'): doc1=doc1[:-1] strippedDoc=doc.split('\n')[2:] # check if all lines are padded allLinesHave4LeadingSpaces=True for l in strippedDoc: if l.startswith(' '): continue allLinesHave4LeadingSpaces=False; break # remove the padding if so if allLinesHave4LeadingSpaces: strippedDoc=[l[4:] for l in strippedDoc] for i in range(len(strippedDoc)): # fix signatures inside docstring (one function with multiple signatures) strippedDoc[i],n=re.subn(r'([a-zA-Z_][a-zA-Z0-9_]*\() \(object\)arg1(, |)',r'\1',strippedDoc[i].replace('->','→')) # inspect dosctring after mangling if 'getViscoelasticFromSpheresInteraction' in name and False: print name print strippedDoc print '======================' for l in strippedDoc: print l print '======================' sig=doc1.split('(',1)[1] if removeSelf: # remove up to the first comma; if no comma present, then the method takes no arguments # if [ precedes the comma, add it to the result (ugly!) try: ss=sig.split(',',1) if ss[0].endswith('['): sig='['+ss[1] else: sig=ss[1] except IndexError: # grab the return value try: sig=') -> '+sig.split('->')[-1] #if 'Serializable' in name: print 1000*'#',name except IndexError: sig=')' return '('+sig,strippedDoc def fixSignature(app, what, name, obj, options, signature, return_annotation): #print what,name,obj,signature#,dir(obj) if what=='attribute': doc=unicode(obj.__doc__) ret='' m=re.match('.*:ydefault:`(.*?)`.*',doc) if m: typ='' #try: # clss='.'.join(name.split('.')[:-1]) # instance=eval(clss+'()') # typ='; '+getattr(instance,name.split('.')[-1]).__class__.__name__ # if typ=='; NoneType': typ='' #except TypeError: ##no registered converted # typ='' dfl=m.group(1) m2=re.match(r'\s*\(\s*\(\s*void\s*\)\s*\"(.*)\"\s*,\s*(.*)\s*\)\s*',dfl) if m2: dfl="%s, %s"%(m2.group(2),m2.group(1)) if dfl!='': ret+=' (='+dfl+'%s)'%typ else: ret+=' (=uninitalized%s)'%typ #m=re.match('.*\[(.{,8})\].*',doc) #m=re.match('.*:yunit:`(.?*)`.*',doc) #if m: # units=m.group(1) # print '@@@@@@@@@@@@@@@@@@@@@',name,units # ret+=' ['+units+']' return ret,None elif what=='class': ret=[] if len(obj.__bases__)>0: base=obj.__bases__[0] while base.__module__!='Boost.Python': ret+=[base.__name__] if len(base.__bases__)>0: base=base.__bases__[0] else: break if len(ret): return ' (inherits '+u' → '.join(ret)+')',None else: return None,None elif isBoostFunc(what,obj): sig=boostFuncSignature(name,obj)[0] or ' (wrapped c++ function)' return sig,None elif isBoostMethod(what,obj): sig=boostFuncSignature(name,obj,removeSelf=True)[0] return sig,None #else: print what,name,obj.__repr__() #return None,None from sphinx import addnodes def parse_ystaticattr(env,attr,attrnode): m=re.match(r'([a-zA-Z0-9_]+)\.(.*)\(=(.*)\)',attr) if not m: print 100*'@'+' Static attribute %s not matched'%attr attrnode+=addnodes.desc_name(attr,attr) klass,name,default=m.groups() #attrnode+=addnodes.desc_type('static','static') attrnode+=addnodes.desc_name(name,name) plist=addnodes.desc_parameterlist() if default=='': default='unspecified' plist+=addnodes.desc_parameter('='+default,'='+default) attrnode+=plist attrnode+=addnodes.desc_annotation(' [static]',' [static]') return klass+'.'+name ############################# ## set tab size ################### ## http://groups.google.com/group/sphinx-dev/browse_thread/thread/35b8071ffe9a8feb def setup(app): from sphinx.highlighting import lexers from pygments.lexers.compiled import CppLexer lexers['cpp'] = CppLexer(tabsize=3) lexers['c++'] = CppLexer(tabsize=3) from pygments.lexers.agile import PythonLexer lexers['python'] = PythonLexer(tabsize=3) app.connect('source-read',fixSrc) app.connect('autodoc-skip-member',customExclude) app.connect('autodoc-process-signature',fixSignature) app.connect('autodoc-process-docstring',fixDocstring) app.add_description_unit('ystaticattr',None,objname='static attribute',indextemplate='pair: %s; static method',parse_node=parse_ystaticattr) import sys, os # 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.append(os.path.abspath('.')) # -- General configuration ----------------------------------------------------- # Add any Sphinx extension module names here, as strings. They can be extensions # coming with Sphinx (named 'sphinx.ext.*') or your custom ones. # # HACK: change ipython console regexp from ipython_console_highlighting.py import re sys.path.append(os.path.abspath('.')) import yade.config if 1: if yade.runtime.ipython_version<12: import ipython_directive as id else: if 12<=yade.runtime.ipython_version<13: import ipython_directive012 as id elif 13<=yade.runtime.ipython_version<200: import ipython_directive013 as id elif 200<=yade.runtime.ipython_version<500: import ipython_directive200 as id else: import ipython_directive500 as id #The next four lines are for compatibility with IPython 0.13.1 ipython_rgxin =re.compile(r'(?:In |Yade )\[(\d+)\]:\s?(.*)\s*') ipython_rgxout=re.compile(r'(?:Out| -> )\[(\d+)\]:\s?(.*)\s*') ipython_promptin ='Yade [%d]:' ipython_promptout=' -> [%d]: ' ipython_cont_spaces=' ' #For IPython <=0.12, the following lines are used id.rgxin =re.compile(r'(?:In |Yade )\[(\d+)\]:\s?(.*)\s*') id.rgxout=re.compile(r'(?:Out| -> )\[(\d+)\]:\s?(.*)\s*') id.rgxcont=re.compile(r'(?: +)\.\.+:\s?(.*)\s*') id.fmtin ='Yade [%d]:' id.fmtout =' -> [%d]: ' # for some reason, out and cont must have the trailing space id.fmtcont=' .\D.: ' id.rc_override=dict(prompt_in1="Yade [\#]:",prompt_in2=" .\D.:",prompt_out=r" -> [\#]: ") if yade.runtime.ipython_version<12: id.reconfig_shell() import ipython_console_highlighting as ich ich.IPythonConsoleLexer.input_prompt = re.compile("(Yade \[[0-9]+\]: )") ich.IPythonConsoleLexer.output_prompt = re.compile("(( -> |Out)|\[[0-9]+\]: )") ich.IPythonConsoleLexer.continue_prompt = re.compile("\s+\.\.\.+:") extensions = [ 'sphinx.ext.autodoc', 'sphinx.ext.autosummary', 'sphinx.ext.coverage', 'sphinx.ext.pngmath', 'sphinx.ext.graphviz', 'sphinx.ext.viewcode', 'sphinx.ext.inheritance_diagram', 'matplotlib.sphinxext.plot_directive', 'matplotlib.sphinxext.only_directives', #'matplotlib.sphinxext.mathmpl', 'ipython_console_highlighting', 'youtube', 'sphinx.ext.todo', ] if yade.runtime.ipython_version<12: extensions.append('ipython_directive') else: if 12<=yade.runtime.ipython_version<13: extensions.append('ipython_directive012') elif 13<=yade.runtime.ipython_version<200: extensions.append('ipython_directive013') elif 200<=yade.runtime.ipython_version<500: extensions.append('ipython_directive200') else: extensions.append('ipython_directive500') # the sidebar extension if False: if writer=='html': extensions+=['sphinx.ext.sidebar'] sidebar_all=True sidebar_relling=True #sidebar_abbrev=True sidebar_tocdepth=3 ## http://trac.sagemath.org/sage_trac/attachment/ticket/7549/trac_7549-doc_inheritance_underscore.patch # GraphViz includes dot, neato, twopi, circo, fdp. graphviz_dot = 'dot' inheritance_graph_attrs = { 'rankdir' : 'BT' } inheritance_node_attrs = { 'height' : 0.5, 'fontsize' : 12, 'shape' : 'oval' } inheritance_edge_attrs = {} my_latex_preamble=r''' \usepackage{euler} % must be loaded before fontspec for the whole doc (below); this must be kept for pngmath, however \usepackage{hyperref} \usepackage{amsmath} \usepackage{amsbsy} %\usepackage{mathabx} \usepackage{underscore} \usepackage[all]{xy} % Metadata of the pdf output \hypersetup{pdftitle={Yade Documentation}} \hypersetup{pdfauthor={Smilauer, V., et al.}} \hypersetup{pdfkeywords={Discrete element method, granular materials, contact law, hydromechanical coupling}} % symbols \let\mat\boldsymbol % matrix \let\vec\boldsymbol % vector \let\tens\boldsymbol % tensor \def\normalized#1{\widehat{#1}} \def\locframe#1{\widetilde{#1}} % timestep \def\Dt{\Delta t} \def\Dtcr{\Dt_{\rm cr}} % algorithm complexity \def\bigO#1{\ensuremath{\mathcal{O}(#1)}} % variants for greek symbols \let\epsilon\varepsilon \let\theta\vartheta \let\phi\varphi % shorthands \let\sig\sigma \let\eps\epsilon % variables at different points of time \def\prev#1{#1^-} \def\pprev#1{#1^\ominus} \def\curr#1{#1^{\circ}} \def\nnext#1{#1^\oplus} \def\next#1{#1^+} % shorthands for geometry \def\currn{\curr{\vec{n}}} \def\currC{\curr{\vec{C}}} \def\uT{\vec{u}_T} \def\curruT{\curr{\vec{u}}_T} \def\prevuT{\prev{\vec{u}}_T} \def\currn{\curr{\vec{n}}} \def\prevn{\prev{\vec{n}}} % motion \def\pprevvel{\pprev{\dot{\vec{u}}}} \def\nnextvel{\nnext{\dot{\vec{u}}}} \def\curraccel{\curr{\ddot{\vec{u}}}} \def\prevpos{\prev{\vec{u}}} \def\currpos{\curr{\vec{u}}} \def\nextpos{\next{\vec{u}}} \def\curraaccel{\curr{\dot{\vec{\omega}}}} \def\pprevangvel{\pprev{\vec{\omega}}} \def\nnextangvel{\nnext{\vec{\omega}}} \def\loccurr#1{\curr{\locframe{#1}}} \def\numCPU{n_{\rm cpu}} \DeclareMathOperator{\Align}{Align} \DeclareMathOperator{\sign}{sgn} % sorting algorithms \def\isleq#1{\currelem{#1}\ar@/^/[ll]^{\leq}} \def\isnleq#1{\currelem{#1}\ar@/^/[ll]^{\not\leq}} \def\currelem#1{\fbox{$#1$}} \def\sortSep{||} \def\sortInv{\hbox{\phantom{||}}} \def\sortlines#1{\xymatrix@=3pt{#1}} \def\crossBound{||\mkern-18mu<} ''' pngmath_latex_preamble=r'\usepackage[active]{preview}'+my_latex_preamble pngmath_use_preview=True # Add any paths that contain templates here, relative to this directory. templates_path = ['templates'] # The suffix of source filenames. source_suffix = '.rst' # The encoding of source files. #source_encoding = 'utf-8' # The master toctree document. master_doc = 'index-toctree' # General information about the project. project = u'Yade' copyright = u'2009, Václav Šmilauer' # 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 = yade.config.version # The full version, including alpha/beta/rc tags. release = yade.config.revision # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. #language = None # There are two options for replacing |today|: either, you set today to some # non-false value, then it is used: #today = '' # Else, today_fmt is used as the format for a strftime call. #today_fmt = '%B %d, %Y' # List of documents that shouldn't be included in the build. #unused_docs = [] # List of directories, relative to source directory, that shouldn't be searched # for source files. exclude_trees = ['_build'] # 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 = ['yade.'] # -- Options for HTML output --------------------------------------------------- # The theme to use for HTML and HTML Help pages. Major themes that come with # Sphinx are currently 'default' and 'sphinxdoc'. html_theme = 'default' # 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 = {'stickysidebar':'true','collapsiblesidebar':'true','rightsidebar':'false'} # 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 = 'fig/yade-logo.png' # The name of an image file (within the static path) to use as favicon of the # docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 # pixels large. html_favicon = 'fig/yade-favicon.ico' # Add any paths that contain custom static files (such as style sheets) here, # relative to this directory. They are copied after the builtin static files, # so a file named "default.css" will overwrite the builtin "default.css". html_static_path = ['static-html'] # 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 = {} html_index='index.html' # Additional templates that should be rendered to pages, maps page names to # template names. html_additional_pages = { 'index':'index.html'} # If false, no module index is generated. #html_use_modindex = 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, 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 = '' # If nonempty, this is the file name suffix for HTML files (e.g. ".xhtml"). #html_file_suffix = '' # Output file base name for HTML help builder. htmlhelp_basename = 'Yadedoc' # -- Options for LaTeX output -------------------------------------------------- my_maketitle=r''' \begin{titlepage} \begin{flushright} \hrule{} % Upper part of the page \begin{flushright} \includegraphics[width=0.15\textwidth]{yade-logo.png}\par \end{flushright} \vspace{20 mm} \text{\sffamily\bfseries\Huge Yade Documentation}\\ \vspace{5 mm} \vspace{70 mm} \begin{sffamily}\bfseries\Large V\'{a}clav \v{S}milauer, Emanuele Catalano, Bruno Chareyre, Sergei Dorofeenko, J\'er\^ome Duriez, Nolan Dyck, Jan Eli\'{a}\v{s}, Burak Er, Alexander Eulitz, Anton Gladky, Ning Guo, Christian Jakob, Fran\c{c}ois Kneib, Janek Kozicki, Donia Marzougui, Rapha\"el Maurin, Chiara Modenese, Luc Scholt\`{e}s, Luc Sibille, Jan Str\'{a}nsk\'{y}, Thomas Sweijen, Klaus Thoeni, Chao Yuan \end{sffamily} \vspace{20 mm} \hrule{} \vfill % Bottom of the page \textit{\Large 2nd Edition, after Release '''\ +yade.config.revision\ +r''', \today} \end{flushright} \end{titlepage} \text{\sffamily\bfseries\large Authors} %\begin{multicols}{2} %%Compile error \begin{itemize} \item \text{\sffamily\bfseries\normalsize V\'{a}clav \v{S}milauer} \text{\sffamily\small Freelance consultant (http://woodem.eu)} \item \text{\sffamily\bfseries\normalsize Emanuele Catalano} \text{\sffamily\small Grenoble INP, UJF, CNRS, lab. 3SR} \item \text{\sffamily\bfseries\normalsize Bruno Chareyre} \text{\sffamily\small Grenoble INP, UJF, CNRS, lab. 3SR} \item \text{\sffamily\bfseries\normalsize Sergei Dorofeenko} \text{\sffamily\small IPCP RAS, Chernogolovka} \item \text{\sffamily\bfseries\normalsize J\'er\^ome Duriez} \text{\sffamily\small Grenoble INP, UJF, CNRS, lab. 3SR} \item \text{\sffamily\bfseries\normalsize Nolan Dyck} \text{\sffamily\small Univ. of Western Ontario} \item \text{\sffamily\bfseries\normalsize Jan Eli\'{a}\v{s}} \text{\sffamily\small Brno University of Technology} \item \text{\sffamily\bfseries\normalsize Burak Er} \text{\sffamily\small Bursa Technical University} \item \text{\sffamily\bfseries\normalsize Alexander Eulitz} \text{\sffamily\small TU Berlin / Institute for Machine Tools}\\ \text{\sffamily\small and Factory Management} \item \text{\sffamily\bfseries\normalsize Anton Gladky} \text{\sffamily\small TU Bergakademie Freiberg} \item \text{\sffamily\bfseries\normalsize Ning Guo} \text{\sffamily\small Hong Kong Univ. of Science and Tech.} \item \text{\sffamily\bfseries\normalsize Christian Jakob} \text{\sffamily\small TU Bergakademie Freiberg} \item \text{\sffamily\bfseries\normalsize Fran\c{c}ois Kneib} \text{\sffamily\small Grenoble INP, UJF, CNRS, lab. 3SR / Irstea Grenoble} \item \text{\sffamily\bfseries\normalsize Janek Kozicki} \text{\sffamily\small Gdansk University of Technology} \item \text{\sffamily\bfseries\normalsize Donia Marzougui} \text{\sffamily\small Grenoble INP, UJF, CNRS, lab. 3SR} \item \text{\sffamily\bfseries\normalsize Rapha\"el Maurin} \text{\sffamily\small Irstea Grenoble} \item \text{\sffamily\bfseries\normalsize Chiara Modenese} \text{\sffamily\small University of Oxford} \item \text{\sffamily\bfseries\normalsize Luc Scholt\`{e}s} \text{\sffamily\small Grenoble INP, UJF, CNRS, lab. 3SR} \item \text{\sffamily\bfseries\normalsize Luc Sibille} \text{\sffamily\small University of Nantes, lab. GeM} \item \text{\sffamily\bfseries\normalsize Jan Str\'{a}nsk\'{y}} \text{\sffamily\small CVUT Prague} \item \text{\sffamily\bfseries\normalsize Thomas Sweijen} \text{\sffamily\small Utrecht University} \item \text{\sffamily\bfseries\normalsize Klaus Thoeni} \text{\sffamily\small The University of Newcastle (Australia)} \item \text{\sffamily\bfseries\normalsize Chao Yuan} \text{\sffamily\small Grenoble INP, UJF, CNRS, lab. 3SR} \end{itemize} %\end{multicols} \text{\sffamily\bfseries\large Citing this document}\\ In order to let users cite Yade consistently in publications, we provide a list of bibliographic references for the different parts of the documentation. We therefore kindly ask users to cite Yade as accurately as possible in their papers, as explained at http://yade-dem/doc/citing.html. ''' latex_elements=dict( papersize='a4paper', fontpkg=r''' \usepackage{euler} \usepackage{fontspec,xunicode,xltxtra} %\setmainfont[BoldFont={LMRoman10 Bold}]{CMU Concrete} %% CMU Concrete must be installed by hand as otf ''', utf8extra='', fncychap='', preamble=my_latex_preamble, footer='', inputenc='', fontenc='', maketitle=my_maketitle, ) # The paper size ('letter' or 'a4'). #latex_paper_size = 'letter' # The font size ('10pt', '11pt' or '12pt'). #latex_font_size = '10pt' # Grouping the document tree into LaTeX files. List of tuples # (source start file, target name, title, author, documentclass [howto/manual]). latex_documents = [ ('index-toctree', 'Yade.tex', u'Yade Documentation', u'Václav Šmilauer', 'manual'), #('index-toctree_manuals', 'YadeManuals.tex', u'Yade Tutorial and Manuals', #u'Václav Šmilauer', 'manual'), ] # The name of an image file (relative to this directory) to place at the top of # the title page. latex_logo = 'fig/yade-logo.png' # For "manual" documents, if this is true, then toplevel headings are parts, # not chapters. #latex_use_parts = False # Additional stuff for the LaTeX preamble. #latex_preamble = '' # Documents to append as an appendix to all manuals. #latex_appendices = [] # If false, no module index is generated. #latex_use_modindex = True # This option fixes PDF compilation failure on sphinx>1.4.9 latex_keep_old_macro_names = False trunk-2018.02b/doc/sphinx/consulting.rst000066400000000000000000000010431324306050200201610ustar00rootroot00000000000000.. _consulting: ######################## Support and consulting ######################## We can provide technical support and training sessions on DEM modeling for researchers and engineers, as well as consulting services for specific problems. Feel free to contact us by email at consult|A|yade-dem.org for any inquiry on such aspects. Please use this contact for discussing paid support and/or consulting, exclusively. Basic questions on using Yade should follow the `regular way `_ or will be disregarded. trunk-2018.02b/doc/sphinx/fig/000077500000000000000000000000001324306050200160115ustar00rootroot00000000000000trunk-2018.02b/doc/sphinx/fig/cell-flip.pdf000066400000000000000000001233761324306050200203670ustar00rootroot00000000000000%PDF-1.3 3 0 obj << /alpha1000 << /CA 1 /ca 1 >> >> endobj 4 0 obj << /Type /Pattern /PatternType 1 /PaintType 2 /TilingType 2 /BBox [ 0 0 100 4 ] /XStep 99 /YStep 4 /Resources << >> /Matrix [0.5 -0.866025 0.866025 0.5 0 0] /Length 21 /Filter /FlateDecode >> stream x3P0P04bT4.U endstream endobj 5 0 obj << /Type /Pattern /PatternType 1 /PaintType 2 /TilingType 2 /BBox [ 0 0 100 4 ] /XStep 99 /YStep 4 /Resources << >> /Matrix [0.866025 0.5 -0.5 0.866025 0 0] /Length 21 /Filter /FlateDecode >> stream x3P0P04bT4.U endstream endobj 6 0 obj << /Pat101 4 0 R /Pat102 5 0 R >> endobj 7 0 obj << /Length1 1394 /Length2 5926 /Length3 0 /Length 6875 /Filter /FlateDecode >> stream xڍtTk.(0 -! ҂030 HHHRҍ %?wZY^vV-]^KTGrF~~A>~~"vv=]t!+@ 6y0 T] AHD$*w ),|UL.p@¬mP68!\Ç~dH Q6PtG(AŁ@777>3i-CtP+ 0@gscEXH(mApgt tUP`?_ٿ ࿓ V0{(@SQz-|+f@9( 9a AOYn)ppQD'CB!{lp{u-~tq>Ü\*MDج(0C!1 upv~|+4 #rB( ;}"0 `6CG&h~y{%nO5tU4x OV x CTL2[.OE-8 4h.CPߔ_~;_U_ )vsn4]PhY#wա0hy ÜaPK- bHj!a8,~UCU 킢E pE`$Aď&0 %7@>8N1H_kEs舄D* qA"t߿ϿUC!Dȣ`ۊ27Ŧ8pa+/5t'疥wR f;t7m KVAa(t.~8n2["譇DS"㵙n?71h03DMpZUm;k2*F&i+ʈmkdJK4IuSy:t=mX6M띝LX8fRPU}pGOOWzI`,p(QߴtT:?: 6bˊ>ӷ)3gt|KC2ra5-α^6EZ,4: ;rw &ҕ1e/ 2p?PX7YT7TV? ky\r̞7VB업gZ}ET?czm9r@Z-}$$.ʷ+kѤU5_X!-E,0߭!\iX^f6d4M[}g0<ϭׄ?4_P&S f0~aU'=Uzq#]ĹAA_[9?zds rA] 8jGdb"T Nos)xg*9^ՏNt0v/-U5]y0_6~E  P8ouឦ-lBVXˠ_ rk 'X{N 63]^/`n 5{{ΜPTʝp{g,+{&JVi^f͙2 8}$3"f}4fʻ!&f!|Gw.!8Z{i7lE1B) $7}rƤ?OEmYyzIG>e d 7VE#zɚ_D >f%Rɒ }E#L# 1OIN*\x,Z'6_z5 ? KKTxO8|Li'%:O eiB9ݛO?9}%J.N69g-5n5Pڨ0:[-ERHhcS=eW(_$ fdOtދdgn<(1al8+{lk|WbǖCQǕĿ/r®,{k" 's>Fg>p$ZotUe‰qꕩ¼< TߙY0}}c蛷#S70Fcfi(Ksp ~`dlqIDP{L׳BiwK(uՏ ,N[y'nGfߑ҄HtmpnRD?Tq~zl'~)ti0kq)=ϛH' I%`Vإsӓ]\Z=4'>_0%x}6k< ]`7R ͎!L} {H1-%އhQe7A9{r 9S R'j疴}.!8/% t_ 07T,#h|H ȐQ<*\bg$h%3niS]'w[ 8u&}Jn.{x17+̕J%eLMvUőri9m&4{ŭ2|^ӕiPdbyȸY 2ºmH[;>ET@vFIqcP'wd ,,3r_n齅Ws;իq$ ذ@]J'fcV<\~/S/X U$VaelwkZ|~~g] :OW$RvzPS޽x~︐ˉZ$U-a9~@ڼBZ,4Vg4F``(wé"Cқ4&a2\ b 9_צAw:wdr_=#)1 rlPoWG1_G}E.ߠW5JcK^Q}bc|u ~FD"+"xMj"r$u$ R$ܶ5jq[( I;LF{oA{:9]3Q$s܄/;qt#ر4?/쌚4Z%a!MuA3<Ä;[J7hPǠpQLp qB肳^!5Bx<|*<*󈰖fMIiv"0iݘ\,s c&1Ɇ5ƀ@eC5sDOw!LMOƖ*6]K-QiDR"}ͩž_z|sg_og] ;[֎kt^FTTGZoyG;C)QMS h lXI0Qt~^f>f?yJⰹhi(eLG~4J;F"&~}} և/6my %רz'^yqޣʩjw_q^_U Wh:ߐnw}{G޳-c9oSW~qD i9[`}a.b"tJYrS~Yz(^{g;^av<ʚdeW7GJ4RJ8qg 0q5_F tF}fI*6wիbaa ..V(}p1][h%);tCqzg t@8pdShhmoH^TfF͔k}|R~Ւ !Vo=vR4 T>Spk=aui8nwyMxu<$9#ȒJs>Nq5I=y5S򀟛r,]+SCG~S:&몘Murԋ{_x g HFT08z a|]us#z`7'=^J fQD8}G&hy+>p_X:Q lE O4`'=hi;I%$fS n]~[NLhܟgÎ 5T[7xw12Զð=$G3ҭOjmy )o-)L3I 99ke' dovj"6Vy#y_!]/ uE)V>F_7N2yb 3n]A t !vQwt~ 7h{rߵ9T 5Iw)}Izt B'/rcW $.6`!-Hʗ Oz/N_Ys聫3 3 PRa$#h_O^rCVnlaWlO͔ZO~agejX//& Lt;"m_BZ@=.]\Yi?#Qp2l (~=ƿ,UcmI,j7M%/9~QBy֦λs0Dlaا/IO ?@TgxhDcsG *EeZ22>i)K ǟOo&5ws׻ ypST1ʵHO}ͦfܭd- o$ +(ʨ3b}ִiF4GVI.FcR#Df ?T3'׮2Fǜg z~%ׇ"]6/-ɻN#,˦Ȼ4h;ƺrbLEE%~q瀳NQ ,h'Zw%ϜlfD̲l}۰ך]CЙ(`yЮw<2KqQ3^A 7Cr+hFQl"j]R+Ťh hr`psTOTXFb &Uf+H%I'r=&Yl}^啅ѣ*hmΒ*Zw Ξ*Y}f_T,F(ZbPkWh'Aa`_?x)Hn|TUU5lݰN.浕X}*.)nb֡Z?3 RRxK@0wj^ήM^2Ęx3ₔsFƜLvq3=̽\˱r3|x{CA!f[,PH^8Qc$eŗ|MpV{)[Bw7R{"$]ORmNIu$2gwElA| 3nʯ TO '#8^G.wϽ7-ɴkz> endobj 9 0 obj << /Type /Font /Subtype /Type1 /BaseFont /NSJEOL+CMSY7 /FirstChar 48 /LastChar 48 /Widths [329.4] /FontDescriptor 8 0 R >> endobj 10 0 obj << /Length1 1408 /Length2 5973 /Length3 0 /Length 6936 /Filter /FlateDecode >> stream xڍxTS6AzwDJ B$t^E:HG^w H(HQzPA{oe<3>[ؖvAϺHKJ`_R+I?;P"Ql1Cyj,H } #HlNxzx`g^x( !EA]^w*z|%\^Iꏖ{K9@ޱi\ylW9!KZ5Coǯ(q'1kIF|)=?]v7%T鬘y];8W0Vi<3Xe5tY),Y?F dupO!{~*$Ѳ3ko;Y[-NN !/a:8?߰KSWe);S }+i* Zr URoʇȧ>\8f@Uvݨmǡ8N~=gR}[ċMC٥|fAveqxo\}=f {罁x|i>ZOi5e‹M mc̬'-=o`M'@grCm7T\jN{ʮ\ߚҲ'UsԜI)Fr(;]i;熶Y%CZ~30C!&f0Q4KUMGخ׫qs-<J;Za)4w >Y Jnr6+S-UXDߙE=;*[D{ 3s1BPhyB u!\ $ M\z<{ dPk說;޸=]xk8moT^e]#hd$ևyؖԟIPb{"71>RcsJR&J"L8z.}W;{`tO"P:83aչLSK K|oZOωs"Z g%j~o#sAѹc=;ud1O^_]!c6g_ 9a\na>tEeKaFLfLu"0oCk4+˾-exxL;!׋`2a5_6;dGta{ey.rB(dZKE&>{oHgL;R{3uǖ)m٪.Kewr߷p~[2~yl,vaHTU\LAa/&:KK)G|ARv!'w`ZW.ԪC_gȒḵQni 19+m\lP2gn Y7Qں+m U#n܇2m .l5vgNrU27׳w*iV:?riV#Q,GTQ>!-IձIZSR?u4xxs}^=.X:vMjs.THm]VRg;ы_k<~fKc6en[B7x^?;3y!nGkp7 ;9Ͼ 2\> 6-0bJ֖_ _mlpX0E%DQ=>$1b@q'_F_v\ ÈzAY~9hBiD[:NQE=Jq9U>)πNNkGj9)1Ǽ}eD/gK7XT BABdC%ѼVC Ɔb&u.` 2@"f]@Z!kqay'e _؋}J;f//E9,QHPE)ede>LsR;_'$tos֚+|} U^xbG/GVV?oxiO293 s5QL}.2G\ٯRTbs OTl S$u \Dq7/kPl_Bڿ%r>4PDH^=48OqTm5]u<n˭Jm;BTZh? xC1%s^a3YZ&{5臁WcBڟ-d` LTSU[KEdIoWiY!kiX|x+z9+ϔ֤,_Rup1e;)yJ3Gzā4mqR*KxU d{Mհ5_"#ɴepk~3PPբGc]{%Dy_ uI5JZV~Ы%7O? ]Ih;$P*z{F,X鹾}g3WFp=¢`cގg3Sf2E Zk fS4P!mO|4qirٞpx'Z;s_w2W=f%B(˧i>=M1w\L⍞dsZt#>bү7^֚ h]O,~D^8TKyDiuĆo\1o AŧQt"!w!dX'[@^pQvY= 3:tKG(Zi{~nn6aw[~G&Պ)ᶰ$rX{Y#6M,GMl -ڠ/´0)x5ڤ(o1kmQtGő}L:Rn6]]YϦ6ڕ {y E\ps&<וʹڽu'H_ol%"o'x?300@4}e~9=*$bv7m5:nHn\ n#췖] 꺎>]e^6Zƕw3ZH"'S=5_}e1}k0NHb⎞L}5o1 Ie}.2\zbNw' ]H:iDn9d&ޱ^srZ?}jjC@j/YxĴ/LG=TnN> endobj 12 0 obj << /Type /Font /Subtype /Type1 /BaseFont /RXTXAC+CMSY10 /FirstChar 17 /LastChar 17 /Widths [777.8] /FontDescriptor 11 0 R >> endobj 13 0 obj << /Length1 1416 /Length2 6639 /Length3 0 /Length 7602 /Filter /FlateDecode >> stream xڍT.LGz;$ F JSb $$Bދ;ҋJQzQHvs]Y+<3>>eEBGFQxIHcenA Y)HZP@F`qH4JZXӆρFh PR*>H8H hF!pԂZhW($~!,ExWyGh!xQu1ҾRPO.*E]냀QC=IQ -]hg/;<0 w⍂#@ }C  l@g8@rB~'Ca0'C\HDP OK(/ >χ@Gu!f@9?p0,I8J*s>f\ @qԿΧ"`ss(/*o;H[^}?s|.$NI@Mx_{kD!L8 P O|`!W-uP04X,ԏt.,99`|9oMPhy :ԿnRV (qEy;B& J6ŞoEo#zv Sph?|%W)޳틑 MRfy]uWq0)z3<!8t冾OV'[Mn 22̴ldOf/j3u-OvJ_|"$Wx[^%K[$f})I<2j{߲}Rws2dwWEpJdK5h ];*Oݚ衼Pt|H zIɅZb"cW̃^%'Pߐ'qav̈́]Kdr,y=Q|c#a*pqjrC%ci澞wN#^ /[Zñ.H!Ͻ\2c?Idˍ^r=;Y,oZOo۴v?7t*qc>@wM&5^!ϧՓbJCׅӄ)utˉ̛P;s*:]& L[5ڈ)K'kv_zr&h;A VmEڤنED<:hŊ *SpdVR0u^E1h#ګb.rj ?(Kk> rCz-e9;?4KܡgH` 8Yһ1p4h s5̟U 7A k)##6%Gx5ɜ;AD\ ^fdNO{xpCͷ2?~ʥ \䙏Y/<&-M|NW eW)teEy<ӹHo(/_?_sXd^E1*W[{'V^7|԰cy%݌ס58MwDUH3`t?"ѯ>C66C@~ IdVwi(qm=ɭasEW4QOV{G`+C3mqb7}Z28їwcۏYY{=y+2q/)=䞅RKLw*h|ZK_'2=l`+LeJI[{Sd:dbK/&"7%8:'O Bky}KFz:|N*LajUž< :z`6w(s^L)-+KkK:IO;hwVX(ϣNhCo 79jٴͼXYcu{(CdW7Jni&y\)tyuF e"|Y֩_m,.HP6]%uXQp8 x}s_h;AΦ1nPӘ~dKq@JKijEQ˪Z/';)W{ohL'+hBnpRWvhQaͣb;}{ͺ8VmOP)L_:q'ԹgKp#ޫvm~VXUG{׎V%-CBqTaw8Qr/KͰUbU Ǿ'ژH/܍1{N2(Ȫ?=斂٨{҅5wd*{IPL{WOօ;L^J%j)mwLxA>[}a.11*8ѷTe&!COIlDl/&5VPG\N^c$.U}B'PuCrBovEu#*A}]>{`{>i/?*=* 5CyK.;v1g_H޻%϶+['w1Kfry9I.u,N!)dMg#m)cu\uh'Bס'L(wZg/ٴpyX4X&F\î{;!,Sݞfxlo>Օ2(_RɆPoTkd/װ  Jp8ZK s\I!ڏ,T:Q_חQ^ubh{ṯtڹnlE gn+?ׅHwˮ>cY 0q5 de7|ĬS0~O @)oVoe7| {x#Oid7mRv(},dӆpn=&>J"5l*R JyRΥQ&ꈟQ&_8 P"&W%4=@ױTsH1F,ѨTmF[kkU^.aj/c?= c2\c*OV¿tw ' }/FOlD"ĔЉpbw Qk5xG~㈲*,oLtYY~|ӡpKxsHQ|H(j}u| 6Y[R(Wi ^KdӈrհW{$%-Ajueyuۢ&M{x~f]?^b𔕅Sy@4TȡW>GdQ4yt~hTo+DϯlEvBil/Vjtaz..rw{8mʲD掣Ls8y'(5UuKBskZ{oW^ V27}=nư؆D#7" +jAK?.iL{_(*K$QRl ΤS //r 3nbk*u?%{ߌeBw0@bӯPXo$_N4Tp(V9W=aΤ_`P^~ &\qJL!)h1rOjg d)(K: T,P~r1!Rכ0B[-ׅOeZEDg2hG"eU=}LESwir4~Mg{Upm/vSǴc=n-a@O:J-Hp}W)-Me66e˸|#y0u*_Ƹ8?|y3l-nm^D%^2ђh )_RSbwS۰I!'6 \5Wdg 2.؛O~q?%3"7 !|Aͺ}ɳ:,2HUXYE>R>?a LUSI~QtF :n:#Wmn[>}g'4,Qn)cRk$֝t.Ϟ;n\d䉽mFAض?i.,%;6f*#e>X;I\ꆴ}ɳKa!&ވG'z4DkL_H| |[ыn鑛uT\loJؔ\H5F'^Ez9YٱU։K;Ae LGpA /ҏx\ڞS{X}JO ęzʬU[ 2^6|.aձRT+(-,B_IhTuz )G @f]mTq?x8reRu_$NC*݋>㞖=4k$ 4oumfw(UV>!ञsDP g-B?ir]X33+ksMʻpS"7QYqo*o(jو,s3|,OW] !^E5dalKF_'5k~URq55EQcÂ>eϩ #gTP :{~+P!eY $)sƂ 䫡Q@2'V7jѱ{䜆J$'Īip)ƠnkJlUH$h=$E\ KӪ0-DLBpNn 6vM<;*~:E8{khCw;\^P;a `qXtmG}VKo"N 沯9s٧LK(::%KݛLC" 03ÊNpO]?]5=#1넒 G NN ^c^'ngm-"] ܑp^|8vs7KqMqS-nD/֎QhBQ lp2ہ zhGUYXb~ϟ*IUT>DYjNWM9C>Y]pnS~(bV1UԬsk~'y Ч2Qr섩E/|D*CtQkm&9$MB[D#XU#I{aѴg~7i׳͚\Ҿ}dv %܇M{>2&^(uE8aGXq+6iBKVܒ eo®v Q\^sYP'@Xx&y5.6/һcF[$5* 9z?AuaXMmi횊InxվZGm#Djbzhi>9Ce`VDQ藡3Mp=Tsl>49Ze/Y|RJ s99MH;y)͛qoG$-4Efw&p?¥7g;E&@MkE VGPgnҳH3 }Aٻr8W}"g!Q aߓ`|z"|i><>>oZZ'Ś6HIv) C/u%:õ9ΦD\5B[5᱊ĸ{=.B/e0R%%Fh>UGݦgf1%Q|ђĉoJ.Q_~gNs7jLkG.nw„WM5˥7ǒ$yz|&W=gh xT-IIԌnpϛ.}Ȉÿ}!ҕOߒb(36p/wq`K( avXlQWMj'Kz}urޖ>U% SBZdGFXy*[ao5*qCDPI`ߙJı=. d@G-=WXߛԒFqvxѻ`qyY?K&*u{)ʼx0$]<3Ȯ),.١cgm"bY6h&(nw:ډZX]nՉu3F2Ϭl.'{jYʹyqv mIͮ ~ReClWVk\I)fP4vOQ?OY]_;S(Um x,߶sﴵo#3lMDbKMV[_8ɯ[,V ^O-F4N=/yбm.˨1S  ]b0hRpZvG#:.5,X8Hڳq,w 2uEbpg endstream endobj 14 0 obj << /Type /FontDescriptor /FontName /AKADGJ+EURM10 /Flags 4 /FontBBox [-32 -243 1060 720] /Ascent 691 /CapHeight 691 /Descent -189 /ItalicAngle 0 /StemV 78 /XHeight 459 /CharSet (/phi1/x/y) /FontFile 13 0 R >> endobj 15 0 obj << /Type /Font /Subtype /Type1 /BaseFont /AKADGJ+EURM10 /FirstChar 39 /LastChar 121 /Widths [763.2 0 0 0 0 0 0 0 0 499.7 499.7 499.7 499.7 499.7 499.7 499.7 499.7 499.7 499.7 277.6 277.6 756.2 501.8 756.2 0 560.4 770.5 655.5 714.6 828.3 605 499.4 765.4 783.2 394.3 402.2 668.7 559.1 1044.1 829.7 803.2 576.1 828.9 609 557.2 492.9 774.3 646.2 986.5 666 555.1 666.6 0 0 0 0 0 388.4 609 588.5 487 603.9 499.7 420 568.8 621.7 360 331.9 555.8 365.4 915.8 664.4 563.4 589.6 605.5 432.1 455.9 416.7 642.5 495.6 812.1 526.1 593.9] /FontDescriptor 14 0 R >> endobj 16 0 obj << /Length1 1392 /Length2 6122 /Length3 0 /Length 7069 /Filter /FlateDecode >> stream xڍW4WjWh(j E$DjբvmfQU{R[fx{Ϲy>C•PH Hh) D@ a2GMaG#PH C18D!j@!] !Q ( BK;P qȣ\GG ($..+q0( 8]paPg> cKqbPwAE`zpw8n uF&@4pD1X()08ၴ@}U +YOoBB3+ \\Hohp40^> iC=g-ɡ@%.; pŸ #BeE<ĸ< -{(,`@a*hDyUTd $w½`?x~q}]Q@{?!uz- 0 @qG# ,qC!q5_A9MUљ* s4eњQ V8aPIGD]feۏ'DD*>xf& t}dMdď1ee_ZPQS= T  2{4K뙟|"^y<kvw jxr%EvډIjSY,T/V]r&hr>:sΡ71֓L~py QrrU[v{)6N)dInF*^lj5e' _0[Q-%No#7ȟ#^x?P2 ,@ה}2 +{,_=2~IZ+T %)fެTʪg:R<&JJ([mAa.4dzE$2ropڂ )^K/t}vVV|ZP.XpV<>4; M~ƒCəBؗR5a vݭf 5RF9%j}bP!;{W ɸmr= 6᳷),ٓl/cmCEtZϟj(7,bsteadgF㺣;UNKL XLss9Q|eN2@6i2D_|iBę !.n^p&dbduI,͒a_' j VZ30>֗.@c !5JIЗR&/FC{~wb 1HGm͌_!k 񕬽%)FG&?HҷkT$SEUYෑQo > O4Y&E{_exI~V~(cV\džxF{`B35L#OcǑ!jUynRşx$tYsp9&Y 6CxkDE@w6 O-lze\j(\jr.,[\SO)|%PTܼ!3Jm_&;xymoHܑE< 7zMQ]}#=p+ݳIhPݛ{x^mM5BJ6^k B}]u4[QHPK\@8|Wr٭cEv1#\fd,R셫}=kmt]dϗ ?6zf4_> `0&dǺ,rz*z٭\ za/a~-0NY 02By] .V(3&&lcHsOvѯI=7}?OX c ]ͬ} p1^,D蜼ej)&;q/(7$6 EȜ{, #DT$V6Qu$~n罈ֲ- o6B]&l=WY4weFxdGn 1Y~ _W̏I!lYI(NHgfyI;Cc {Naӹ{Kڅa'W>ZG p}H?{ƭcY3Y7il_xK5" j֌ĮZNVץ?1|w^Sސi/f{^pp{=96kAx}s)J7r7T,Z,R \yˁy#&S3u٬%"'Ea#MȻ,ɤf,g2NMTv6ka"a}ͤ`甦{ftA9;d?=m"Zf`=nN eT`xcg/RU6-dyVfvlSEwRR v5}$D^PڳuRm*[/lemX.ɰ Y]aϟ 3a+4Ƈo/ӌ#E( G6sA)V5D7ekm)Ed|MrHi!yb(gJqE}?8|,M!ٕ`A"ـL vS 'Wzn8WگY#nx(HgsҪw`7E  ȋ;G}'YEj< Kk%sDC$h9iW\#Y)ʀ#0؎f`zo2f,[dA>6i0N^n$r^Q!mv xdbLJpySSUl"E/MwyB aIV3NOԖ2 " gWr>OKY\܆CqP;\,*R^6I;BET-|'=pۮ][]@/)e륎I%M5Ȳ<*uXdD^ʾ= j%gR,*BH`ðΊ;ieaK*7S!t\<~A' =0Yj@pؓr&%Fa ~_V䴨"iBۿh?tc 4ux܄  $933ɻ|ru5 ×]n1O$CNtGUJp^w@&2#'B%ĶWX2YAeX+aeCX&ܾ栁f(Y7s{ד;DZ_r&ڲ<Q[N⯦-o{vkȗIQSz8loq󛒋.E4tSwⴟM|Nlc#{-^Y6.eH~rȼ,cf-V?bomK-nY<*HJ!yKwR*}8?f!B<  cd~xQ"C:slޙ un4ɌcXNVh9-K+l\muVioz soy+2OM֚8$Yjƕ`IqZJ=ȓ>Y ƌ-Fz@0O4)_Z,):ݙ47C:.4= SJCc1j]w"r=KB s/ 6F3Ioa4au/f_)ۓ+5"A<{mY@^|X7ۃv+&je7w|Lٵa ysd%v1IQ$3T1!{i"bmWIH~8KqZ ?hl+Ϲ`7|`&-e_)ห_ˋJ]whzAЃ?ퟱ<&q}TLэd15d^.ߎn([*PJҎ=Ktgj F?0l?Fthr 6-8VE %Կin~KuSA;EwI^N)d9z)U7(=%ĔYHf] hݔq®|sT>61Z㣕9 iXVD[rMgs>_.Q_mp*s#fU%z6 sq쐝[iI޸xuߵ QJt?VwȤWV1 VOT܃Y{cz^יUQjxV%$)B^a?5)mFe~K b*5CýXr^G̈́}Sګ|[+_ U [ل{&h0[\4H{/)Um[^v !=>XBc m˰y #[ KdcBZ\їJSg_rmW K29Bl|MӎF*%"M6jv敌()Cd]&ˇe}Cc`˖wkJYԣm1 J9֑4׽T^ʒY'>N-=@i,+gl1o0?nM۩ 7г8a yMd,ӳp#_鍝JD]J0stb$ rZʼo;JߪܦOJ8k"F[j쌐"FDaH_h2&|Mk`aN"ʂf׷B^#;#K؆ ݄nK@)1ZiNRNทMJ^bT|| =[Sʗ!o!!@̤ooΰUK`6SSmYKyώqΰ9> endobj 18 0 obj << /Type /Font /Subtype /Type1 /BaseFont /BMIAEC+EURM7 /FirstChar 49 /LastChar 50 /Widths [625.6 625.6] /FontDescriptor 17 0 R >> endobj 19 0 obj << /F13 9 0 R /F14 12 0 R /F15 15 0 R /F16 18 0 R >> endobj 20 0 obj << /Length 7207 /Filter /FlateDecode >> stream x]Mm9N_qg|HuN-Rπz{e/;޽KtJIv>o߿o[nۙۻQǣ[nLB78ʣjyΐ!B)WpgAznܧ_~y}HAMU: :YwIJ zJ c>LƧ.m14}K6%uŒPJ%>ѸsZ͢R*XKYy,|7Ź|Af%㪋u,*RXkY,Z7j3Kpgadi]d-1wCW\]OQ(5]zB>e}+[&$kc fR uκ!G\4w=+3[הR}eL2uWƱq&tz t%Kh$Ae֣̰5e֔lN(eփbUDJVh4nEGM2bՏBvHEΡM>aO3of/Km~00!̹\/Nc;c">fͅJc~~"_aL9x{P(c?<ۥ)epa:VG 9)eƐb$Ѻq-vp*J[umkn[;6n[˹Wg ]tzĀ؏.&.\t-e-q9ºXJúXM钥5/q@aJ)P T=*-箧ʀrh=6@L*m.Lr.\7nxPԹ2Eֹq2l qxtq H6}9y?}_0;7Օj\FXvsDaS~VU.;'#_f$(T:u)o3ϫUpZC%j+A ~u|ΆiȢK1_Us'<9?k 3+eU|2ɀ+usL3RfI{tZ{Z7O JmqfS=lܷg6?u L'3da[ ҠѺq'װak-} ?-4e3)V\w#cI3<@^L\uxx*gtg^kt.O`a?ۡ}>bt磮=9bȫ~myYϱ:ahw wΏy2>XFC\루HobUùٙo_|^ۏ[h// Zy. l}c!=z?G+ʗ}Q_~rW ²DYe觵+%KI4ֿʙEKѱN?ܯ_B-dSm*=[Xy(Od;Y_cX1 *G<]1g@^E}˶ֳ9kK>Ņ6qt&!T/P7zֵy ]T uκ,|5}7(3fvsW7!R..tmyToy"Xڜu3~It鿘nsY3z|Lơ q&J#ZPpD*ꌛ=q5xBԦscaG%$ 6NHB+yMżh2rlTVP3)s2a:ϑyv)B` 2VLw*mۼϛr*voyp{@LYoۄc *yKVDfH:N2^c5jFtva}ƶA/HyXTUΚԄ%Y.t}]k8}ׯ{͒zH +:͒`TT: di~\:YҊ@w݉࿦<azDr7B4$)f'aS֏Tc n4F2t!h d֏De8{n*IcInNs*= f3J*5j)r>cefνfv!6b.bupA܎\?顺divw.Qpx}5L1T=~,֏+2j<!t:k2XbU%ܠ0`X)3ovktu-i@mKs'3{c,֏p%@ ;5{ލ5G80؝cCALUkb='h4篏.2-8.zhM*u u5\v ¸W=Lˠ i 3mRo<7TP=̾!&%Kh$㢇.+԰J w!j:ec %Xӂ\.fߕ"͋ۍ;7?Ғ[حK^TwO7\0\eH[ ǣ i-1 !s9u* .ePDZTXƯYsCcEG>OCc1׸|v2un&ݯQ3 Nf.YZ1{hͿ71"׌0.U[k \e6|7|7ȞV ͤ ǵ&A)K ַ 䉒wb*]놬}!DgM|]k/d PejEF"bɾp^"@"HItܱ3~5G瓚؀4^5{TPfwed|7|yMMdJvi灸l-z[ vo"B|֧Ȯ7:cgq^(%րEkYm9!j:B'(U^ьn &o¬i GQ礄d6$8I#ƣPk+7jdϑ-x8'!DI39tU,P;KhuN\Z{񰆕 [;fvyVκ]lpQf=O1s b1{ڥYȡɝh2U'F<ɌK=O ne8%|IhfB>I7ha"!D+2R,",]ғ5MMPjo"A,+ap,^ @7=3@9dmi0B5O^Wa)(ȹ0~؜;#Y4 ]͋1 g_B|<~aAlz׽IYr@z`Bb?h~\>-(ٻ-VL.7hnDKPPryND\JVHnEg'y&܀$ZNxqۃ{f~5tAeA}5#ZA:ġ܈dD Kp; \ނzϷ`=q7X~-sӑc=.ُ~RYbdݩ5f+@Wn"+Pqc{mlݠp6%w5˧,m֏_HBhJiPPZ9Q7n +A1@%un VmoRލ;0Ol6B~`[k-(Fn yZKVdOV|2r !4,@5zFsģ42<ׯ9\/5\#Sl{}2.YZa?.z؆A3(6t+30Q unTotڢYglԾpl@6 oUc`Kn]Q~\LRfRk GzqHM@^Dlthn](  (ި̟hvH̵cr5P[r9uzFiq3fP<a}51[疝%JDq"6f}Bs˅!5 *-=gѤdIQ)~\fЬY0,3, as'|f,-Gh o yWͼ-KlqH^H %'uF~K#uޕZRKPɗk[bec@܀|VUxhSf,%G؏f\2lR]fk,eja6VSk lρnK#*4( bHk߀s w(a?.zYO:;o^Z etKC3dѤdkF qŀR fotyl؀|VAlɝi%Kc +m~}ٖbgRnZdȒ232\ iVE; ׎lA 순nP;#}UBuXҼpXr{=RQYΰIrzDC; /0ڠ!{Dk:m*>ܕ3jkka:A#*>2Z:8}5qR #wU#Gц +B>} sWNFrb&uwͥjo4hB\h$y]^pul5A+H9HI#@?"AܒѺgП v2u=#M#EhHqnXHFrw#3H*!'$.LE-X[$f@]bFLu 5uE leH̀ ۸ub o_xļm: 9$(LT~yJnff@]u6r&#I7A[ GwT= Zh$w+Ph JфD9-EҚymg7 e3%2,ُ+?U Z*XjT)@" T'R RFcZb(B1$C77:ُT$Bs6X-Uʧ2ݽE%7u-9̹Kf{þ-v\c~m9vgR}Q,z)YZ{9K$$+5嗺Ý9)rW۵ZB:ױ~m[_n_ kki ƹzoŝ7ZRPR6ۣ\S4{'+u_J'|aYYP~j=S-;H:d|Zis 蒥˘* Jٻ[\nGO# \jCnЅ~;Teum*)WbG"7eҊCgQ! :-i(%$fja`XӋceW0Z^V tcyͨܮcvrċc+Z|Nܫż@!?R87^̟OpҊeW^. 0}u׀8>W(ta;CuX>- |@#>\t+JF!^ {$& kwB~W^?Cp2Қ޿3H~j7(I>K7,>c}9?Hku'Wq Y~ݱO_;_;?I59kt?Gx42G.G+?Dǟwxa摳?ݠ]l]ku _<&Fk-|ZzM^69 ,m["˿Io79 endstream endobj 21 0 obj << /Type /Page /Contents 20 0 R /Resources << /ProcSet [ /PDF /Text ] /Font 19 0 R /ExtGState 3 0 R /ColorSpace << /PCS [/Pattern /DeviceRGB] >> /Pattern 6 0 R >> /MediaBox [ 0 0 595 842] /CropBox [3.3 635.3 488.7 820.578] /ArtBox [3.3 635.3 488.7 820.578] /Parent 2 0 R >> endobj 1 0 obj << /Type /Ipe /Length 2794 /Filter /FlateDecode >> stream x\ݏ߿EWչ` JZV:O}V ZrGnΘ@0 l_6ƌg<36ޯ1^43M\i4 $[^zi@/d@lrӷ1J')x2rV-qxpc~_o18>[=YcA&A~ iY&hҞ8nCQhjj-4lb,Uyw4p[pf ^^wQN>HoQ/ipȞt0JVSeG:jLB /kxY3M=2秢Q}otA֤w0!EZ1YU K?%\M]ɒ~.T)HuNcK)OZN%]IΏVi#w'9Y#Y5Y!_|? &ބ[]!1??{i&++94nkGT-5;k]efvoj?V[Z߃ $!د|UZΣ) FaxL2ybW*xs xw3Q)F4z+ep.76X A]nD b`A V"LüᤕpEf]7?=à ,6G1ElXue[mw9bQRQS ?:*R2 A/6]+23j-Yt2a } 2Af `T6s6ƕ@N%R[ZUJ. uR ty5(V"feܪ̹4I:Ռ;abguR7)KmQTk=B' g3 ?9nT9'x*Dn%:Y#>u m`j7puz] cce D\ plzW@uF14*H!rm Y뀦W$lBe,81 HBcIdOc T0:X'fZK Ğc%|5F!QQP <2 ʉbBa:$Ժl7u.mꐹ DACLLNn|!\"Dq!A}RJ60DL (HLLu|1 &!U%UDƊʸZ8X}FWG !9^u{S-/"M;8B] D2h.] j ~Av:=NKpǤSQ)zf`th MS#cf ;c`Ea9Rv:=Ni >_91mS.A<>Q DcةmB٩X8p{ AԊȾ "9TGgOtv&ؖ =}J4E31iHَw]LGY^Ewkxfޡڰ={ilMPO =z)Ae6ڵbV;}&0m &wi,%4VNJ{>AtGmj%>kf[ƮH r lGɤ:BZaY'ES>R#oM/)%Rgz೎92{Y.j@Kc3IۉJ{|r$j΅R&* Z ,Ku ̽+/AVu(g%Vb_`5\kB}uָx~}m2]Թy7=m+s ̉sfˮPR]v~u̵(VgmY k7I#Rghd:mv_4Xh endstream endobj 2 0 obj << /Type /Pages /Count 1 /Kids [ 21 0 R ] >> endobj 22 0 obj << /Type /Catalog /Pages 2 0 R >> endobj 23 0 obj << /Creator (Ipe 7.0.10) /Producer (Ipe 7.0.10) /CreationDate (D:20100409155504) /ModDate (D:20100409162615) >> endobj xref 0 24 0000000000 00000 f 0000039073 00000 n 0000041951 00000 n 0000000009 00000 n 0000000059 00000 n 0000000305 00000 n 0000000551 00000 n 0000000600 00000 n 0000007587 00000 n 0000007808 00000 n 0000007946 00000 n 0000014995 00000 n 0000015225 00000 n 0000015366 00000 n 0000023081 00000 n 0000023306 00000 n 0000023871 00000 n 0000031053 00000 n 0000031275 00000 n 0000031421 00000 n 0000031490 00000 n 0000038771 00000 n 0000042011 00000 n 0000042061 00000 n trailer << /Size 24 /Root 22 0 R /Info 23 0 R >> startxref 42189 %%EOF trunk-2018.02b/doc/sphinx/fig/cell-flip.png000066400000000000000000002363441324306050200204020ustar00rootroot00000000000000PNG  IHDRs pHYsqF vpAgK\IDATx}|ՙOH !]ل R JZUZS.bݶ}(ZS\[і|ۇm%eu>HFk +7@3?^͏ Op=3眙7̙3us뤵8 $͛7o޼YN;0 0 0&0 Ä{3L$)*****z'xԮpmvm&M4iRCCCCCCUUUUU>/]tҥS0 0 0QZZZZZZYYYYYi:-qcܹsΥfzM0 0LxN0@얔 :tС۷o߾}{쮭=|ÇvڵkWy0 0 0K.]t9~wرc8p@}\ aI g->;q/50 0 OIonnnnn>wܹsn`OKKKKKChaaRM 1~_׿tuLQ&o!!,X`0 0 Dx[|뭷z-sG05haaƒM  |۷o>9_,SEO*!q}aa?@mȫǀg}gs=ޚ$ 5?7o~G 0 ˞a"?я~zKMMMMM 5n'eaahg8]vڵ^j\6=#<#߿?&a&X2L'aUdm3gΜ9sT… .\haa8rʕ+Wq}8o߾}gϞ={,FduhH v |W_}U)eaq&!q(ͩaaa:B3a"vw&6 \ӧO>ݶ 9/F򶢢q\Oc[~2 0L<`=D E30 0 0CI°&{Xt̨gRĉ'N7w=ƇF{io>8}؆{w!@WOalgAϢaaI KDEj>a>VrtR2/~ {]?ؚyBZB~ҥK. =+R0+}a&}h?0G^܃4/^oM}x񧥥!ؑDW2 0LɞaFÅ¢aaI߿p7o޼ytŕ 'H`Ǖ7o޼yݻw޽ߪLT; ^0 0a&{^,aa?D{?$'*+ ގ,0<: `G*OaW2 0Lɞa,ū `0 0 !8ǩSN:zHm!_C6 0 ,lgKBa0 0 GfXWљsÇ˾T>>|sΝ;wڵk׮][SSSSS'?aGzbN4HK{ݻi~ܸqƍ2:_EEEEErU=BcA]WWWWWWώk=zgϞ={,ѣG=p 0`-0 63"BS.el-xQ*|L|7aap…  1 N׽E+W\)M6m}Sz+Xs@'B*<W tzn#^{^{MN`l2 0La=0L(L8&]0 4E<ba ԸLT'Ժv!z>>4>!a2 0 e0  taI$0l`9Jlh:- 0L4oAZ1cُ ψ|{`)aqa[oa(+|8 ík # H2 0, U5HK@M5lh,П44/:0 0 ce0 0Ll tb (? 0L*B8S!H H{U$ba& ˞a"hdX1:+9ێatsW;ݰўtaaaB PemdС,P=agݱ3H *aa;8 0a'1dUSO4g% ba -bD{0 0 6lg  Aa[`vEEEEE"2vRTRRRRR/34(È>,&JǯgaCx?; 1 0 0Çb--q:g22ck׮]v-|穙S&~g(0T7?(KbijȮB ;L5J@=]ł~`{UOq/_:t!~^A3|D(+++++3}݊?33;d#8"FF$N0ހ&91_HC(ū P))b:9tҥKR&k9s̙3G<3----- &5b(X z[jF~auw޽{wjי7o޼y{{>|÷nݺuB4vF4 S/V.رcǎ{'x tхɤDjy.5Y~Ϧa. +QM IM!F)ǯd*XOF>id0A yר?c~[pR`*w4a@F9]%o߾}?Xh +m/[K? VɞB8b5$Y4uGk3Ҁc&ET6b6 M WN~Y@gr6:&p5*'ctaz.w`:fS)a$Uk*?>}۷]5bј#T3|W 2uH25:CJzf0`}0a=Dա}ttq:W HsNHN %PGd0Amal]60LCU΁㮹kjZCJ5-j͛7ol:W`=8Q]]ab+ 63L$tGl]>ȠQPGP_Vɦa8͔:Sq P0ˆZvڵkN\9jSMԧ6C4G26a€M IfwGĘq3{0QefuO||nOxL6Z#`0A{@(IA qҜI6CSF'Fba}0ΏaףLyyyyyyIIIIIIUUUUU1 Pn YIa Z~"ZaIlO?4C7777779rȑ#8{{G=ze]ve~J0d1qTx0JbөclhtdaRcn{ 9Q{t.`ֻw޽{K_җd:]z٫I P +bD{)$IJOZJߌӾ L2"&p`a)t#tΡ 8Uqb޼y̓/aƷVI Es$|8èC#<%?aPpt{Wɞa3=L0#;SͨA6x??8[nݺuӿDBD@IÙ9 oPsQSt0^8$CuTԣr{ϋ>3"X_1`=0LX=!q ¼?x_~N{sLdC<V %֓#$jh2c򢕠;NDO2tN=b}tdaRcG zgcM1=zfb}as1I!j(G2&X8 qem&yb:-yX_Q`^ 0CW6p^%%%%%%Xˑ].._Ez8 b#M“ `_0Q#'A>pxb&{ahzi UTTTTTqwqǩSN:[S!q761Fh4Bp";ЏpdX_b&{a+9Ar#H}9~jb\~ϝ;w9+̞={٦a `ȄIPCJQ5E0ẐdX_`}M 0AH|lO  :tСCa\d<%èZH0"&,1#ɰ a"|tҥKb6GW+0[1`H5Tr}0 >9BCUI&a}M 0aڵk׮K{ ɰ/Pp!q60&6)aI͑ ؏8iR63 Dȅ{vR @~駟~odѣG,X6вq|Uw? 0:e*BN  (iR63 6!tp{~'?̙1cƌ3LaσH<60 > rJT`}M 01hjǑ$!C 93gΜ9sT3 yx1Q#'G' V 갾b†M 01G4c JɑA… .\4`#<#L:uԩc0v^`Ya}`}!L(m0 jwyuuuuu5̙3gΜ96N=q֓O>OꫯUW]uUWIdg x4J}0gZGt>~_E q0% Kk+4$CyrNadim`+&7]M u}^$(}~* KL?b&{$lgl.Xd$ 虙t8+++++KE-pDc lz@:aE#_TJ=Ԕ~C0*qh &{}EΦ_;=ɞa" {7(4ܳgϞ={߿J2ٕd>50ths=T6z,y4w`Ja"~+`~Ix`}+F0Hj1nnXdi&xP;PcҰ6)~@`TUUUUU5x?nݺu֭͛7o8baNƱ_E4&rtɆ 6lذiӦM6[nݺu)yyyyyy8ꫯꂂ{{5u˨"cqhl+w@Cݻw޽{n۶m۶mvڵk{kQF5j„ &L0 <رcǎׯ_~{`UxW.& 态** WN`7 qM< hШ6(>CIEuyNԖP~P#h@9CDZSJ2c=c}{g}g*)Gk+FdtkVXbŊ%K,Y~?~N:u 8p@?`@F<%yOOO_%L-_򗿴o߾}={ٳ'b;wܹsSN:EǏ?`*'''''~ᇱ+~} N*&?xWxNS@i@c9s̙3뫠`}hŴ?tUPɈ&GOCPL]G+28IWVnZ%tq²**1I\#Ezaƌ3fb" fΜ9sL.˗/_|9錻E-Zyf zk U5@v PZ2 N2eʔ)(~c5;d0HM_yhUqDz WX_1a=c*nz+Z֦Rl+t&>4^t(2o#׻vW^y*9r:xQ @k׮]vϟ?CFC.# ݺu֭[KAqH ^hLrTbÎ;v-**mw17LGOM_'(熨쁺M$W;z+1M6t~S8ݾkx&]P ?+6|D-`hH.-----E`=_fc Ή*`hyG^d #RW=40PW̤T_!|͎Y_;`=:tyib4[,h:]m&x@c)Fcb;vرcS3CקA6A  d="A z X[iÔg`xw7|Ͷh+2Wfަ**-=PWNoWxv;g}dbMLm[l6it6>H10]fćϴiӦMF*ԁ cx*I6!L50a+ #KM@E_Ih[Ց髨LW WWmɞ ՒM;©tǨ&a^xuZMG=Zxŋ^4Cv>ݡetZ_NM &Q1z-C{\!JQ{УGWH*0/5٫+j̍jN9iW bML,}T:~w9Acg}ۖFWZDO|jK/ 8 Uoׁexz_KQ>xFG4X2~Щh}lzk*l2}E}8*a},X_1v&{&0$nDlkd-,}<č]T2}{%K,Yb)uǃ6LtZdx%аuG$G! EL9t(__7uW}EѢƊ\;,UPb} 놐"^dl =Oܷ0JBzhEno@$>tK}$DP2-kjYrrrrrrtPY\o{&iئG*XaJ_Rv3IKKKKKJ`}WU`==͜~S#m!93^%bE 02ͯqNӞ=u?0xgygt}`H)8`mJD{*XaC_Q= joд ddddddtرcǎ6LhX_%WQMgLh'lSMm.[rzw,MD3v B rF;Pw@sp5j^f^ 3$v+rWa=/fo)1rD~/\k+W* 20z@EaߑMvZ2o \BLFbHi:-vAE3Ĝ~B;!;."%IxMNc;و@W݋MA}~iB﴿ЏW2g}%UjJBL /PCP@4Y&{;f*YEEEEEE^8."T4b}۷)SLb:-lW"g*5X_%WaeĈ` @\2F7 MMMMMM\GD'ȥ{ݻSN:uH}}}}}=:xbĂh?Z鲝Fө%K,Yd60a„ 6nܸqFiatcG:ߝF\WɁUjĩ a}+a=e˔*1*v N`h4pL^`G{bŊ+V={ٳ>裏>j:E62lذaÆ9rȑ# ϊC<{̞={Ǐ?~ 6l`:E>W0; y 뫤++a=4vz:6t ,Ac KVVVVVֺu֭[g:-nlWPf}ZWI: +a=D r 9f:->3(3aN577777N l<+m)[ L=Q #"d#נqAP#F8b͚5k֬1xkfĈ:ދ W + +a=( hA_O0X=>+Koi?ȚBjECRyp… #-xtHk иB™3gΜ93jԨQFhaƳ>7 ʧN/jZGh/5Vg}66 W19s̙?%dbX_`}00 ,]tҥhMv !û ڵk׮]km%3d͛7ow޽{7BM(>| r-~G^!K2@>@W~rU%D:tGu֭[E-Z<ދt5moxwA^w2ƌ3f̘RiI.3 ѣp(Y_1dbX_ %y5ہ` [^$ ;.]te޼yͣ2Ip9/s#j;/ .+P?|o^QB++P~~3˗/_|9 5NIPZdӁ-y222222tq7 &ўNK`} zUtMW2X_1lM_tZl@ lJ8/R*AP2v06:أqPZEa$o߾}HglcϞ={Y~M{5SFׯ_wpJm5Z뫰ʣS_lg}J+ 뫨&{3k@촒`SFg!PT<;Rg~&pGTo9 ϧW 'nݺu=ΦZh2WlPZTDyPU 剺QŽh__ig}X_b}X_ɞ Z:@q?~t['zM4W$._Q=ov֭[nFeOiDkE=-Zh"_a0mFv`}S_ӧO>(2}.m&{WWW(W<3<Ԯ*ZɞI}1ʇx5\ܡCnL5>C=PZZZZZJooozAl^@~(AM!WS> )aS o++*,gn@KrPa+*Hj`@)T~~6} 䃈dJ+? W+kGj%U`="O6WucQjk85{/?RJ0mfWJi)T"lxkń`K~O0 /5LNLV;,R%B?S/Z{hBW†z?OPaԍ"{x!z12X_rGE_5ٳR+W2lWzu=(&{m\C$c`Jd0NÒ`kVzzzzz:" 0Z\Xt^Qц?4›.ܹsΝ`Y:HM}UG.sR"5CO> 'w/ZO{FWSSSSSS]]]]]Zu|F?Ee>#Zچ2"꠴ʯfAcǎ;vkk?N:uԩ//K/K&L0aByX_RAWt++d}e'qW} usΝkj:pÇnݺu4iȐ!C.رcǠ]v>zц+ʸ+۷ϿkQWȇ_qƍ۽/_yӧO>[W*(DVO'/9i nC[ЎaУG=zu&k{tA=8zY`8Wïо!hx=!8E>^%;d2.,9X;XX_b2x8Loi8NN8{}N]/%;W8|9~ժ.|qu@zjxQNS) B_:e #oҤag?۳q**~YছZ啺_o}ƃ#]vzqdȿ={:mzs8ҥ׌C5TWș͛z_>kv 7}t8|dI;o-Ǚ1c8S_/N23G\9ws>= Z{O eUU>VVg't3q-ַ.ڗ^rٽ{biHK1#g-'q>u֧sw^ᄚ4LX_$ LQ?;tyiqq'*/mhpijCd+VX_// /N>2az΀4n2/{ 0_pK eO㣤e#J#^SS)W?G0`:_ÅƈT/Ktᶟ1[QMǽnueh[o>o Đn~id:…n*}7rd0/_|rēy Wa\N[8G/LE-ԏ|q|pJv5^\0mȱ,ϧx 6yG]_1gB=.|ȢIRzxC㚐07Ӌqehˆ\0nl|D+J |F0prF@.\݃^裏K؈9z-};S"I3o'wywމ{\xPYYYYYI'ERСC?#A{{l~wyy+a}6P2۷obً簾+IJojjjzǏ߱_~8p(諓'OllDtxDG^z6ľ*,=8` {W_}Ւl\~;پ};?^x#zsTr{;# YB/4UT<<0D4FI|q -nK#D˫aOHZn( QU@LogO>|x̘A@U [_QnoQ>Z )DW@1nuU=+=r'<}v\# WWX1˲@ @1('M_H1t9d( K@^=?~h:xǟ8qĉ52lذaÆ 5iH+yJTTTTTTĩ4L2A^aꫯ(gϞ={,0;;;;;СC]L)/~_"jt~\E;ʘN_DX>>, a:uvšBƍ7nxǏw c͆ /⋇:׿?~t5Y}1Nvw}1:5ETU}:t5kq>q{ٳGV\fy1gϞ={J[n|Cذqk׮]zz޽{^_=8;wQWdDF ;v/;}ԮѣǏ9C0Q^\N @yr@WUUUUUU5Fc4~R裏,Ydɒߗ] yNZ; LYiCa I hAi 6lذaӦM6mڶm۶mvڵk.OjjڻOoĉ'ϞUW{7 _vt萟}A/+u+38pg 63M ]̋ϛ7?Yrt4$ z"x6Q#e8Bܲt[l0PP%S0lynGh<٨f,ڷojݰM_W+5=HAFlWq2ٳRAU_Mk wUJYb}QWX8֎]vܹsR,lgF ]Ҳb~H[]軁](t.`hB ]=݉`-9QཋDt̓a Jc/Y9z$8 =Ӊ+lmЍ *6xJw}eOW68Y_P\&̑*ezPo *j1M{> |v+& dX]RP A~Lhw`*x̟,ރjuRi5N,áEQI 7N j2T#ګG_1a&{0(7/\t*uyPq2ٻfg˯(܀?~dKGQX҈IpCxx=zٴyiQ_-;]HZxKP222222|;qw,`I{4M[Yڑ$ Z :KJ=U`}%"Wz*ULDե̑A+eRL_ 3r#𪯘a=c/BD]d5nc:r#+++++kĈ#F}%(Rn'B=YX4XQ\IU6BKM F9) 0oD{aJ Q_'~* }?=+17WDlD{ٓRpWG7 }M _8UMLC msE$DͅHbzu A!hBCBB7zVzuhݗIx`$C5O~UWa ^$X}?=`}妯Œ\/#"T`}dJWzAF+FlgBF/r sA *Wɞl0oD͎'N8q"!Ց$AwBl?! r^VVVVVF!աToy(_~k+ #RWq̦*"A髸Y_0#/+X_JozG+FlgrhGWH8.Ajiq {ɨ1UM^o($ UU@FXz6^BS$(8p nٲe˖-s7GF7r;ԛ=rCq/^xFW.Ot[9NuUWZ&}l+ z"*&{t}2Dr2^b&{&t.-I<#׻{*8'dO}CΝ;wZjժUW=ED#ůe5k֬YU 7 ݡ`<\,)Q; fBzš<𯯒@2=ΈJɞR2tFTHzꩧs#U^2ɣ0!S_____ߖə3MM+3tSP '0LᩮDL򫚚Z <Ǐ?O<MMMMMM *Gܽ - զS<+VXbEQQQQQ-"nQw466666~'|I75YgϞ={4 Ydɒ%KN>}i~n}__Y_L}3)S1Twd}Bҥn `JKϟ?_^0R.99ŭ+3Ϛ@L0T_ UMr!ȑUm?_w.6Fġ4╖&3r :?nܸqٶhA,ތz@=ӓ3f̘1# +o߿͟?o 7=͟UH2K_󣯒e\/CgD{zGW*$A_^FipxD^16C]=UMBE3X̛7o޼yQ7Q/`,{xwsd\#׻!8|ÇGmfffffA 0h! ;#ɑe1bĈYYYYYY=a>@ߑsC6o;}L⪯ROgD{JY_͚eOz#ڳJ\ehO++=p`&Ddb:SS8\rtfT u֭[7,3.{;vرc1Eܕ02Hw}w駟~)~5` trtZly%=zѣGyyyyyy D,;vرcmD+ ' ooN` 1cƌ3K }Ҍ@ӦM6mڔ)SLzVTU~ȑsXccka}啸+Ox=co:qWcZFA]+WW\qS o|Š! zDh9I A)Aׯ_~0 @|?/| 0%pzȁ{ݻ7*C#SW^yW`@ك96 ҃cj M*= eoEinnnnn6"Ay~j泳Eځ#A>> *܈:WvFa*=+⬯Z#ўW⤯vܹ|7xӑehP;lk iL»jĈ Eh;4{@ǀ!8Ieu PwI3o蒩y~d4!߻pD^[wy!A2Ai0a„ X4ː dPe4~+}QqQw[x=$N4(ò,rH+ڧrp'RWI#BL7褈H}W}L}|K1&#mVW=eyY_}}Fj,7[={w>ԡL?op&QjQr!fpOX&35V]B,7|7|_~e,g v|}2ćl0fnޝ2^*B˼XGpК(uÆ 6lؖ-[lٲzիW??'N8wUW]uU+4Ѕh} #&'9r w֯_~zh+YJK/ma0 o۶m۶m(G9rΤ[ྴa :*,+⡯DMWqS~`}%#> V'r ^I QWeUUUm\/Chb†MLHJeeqeegG'y xH)BAOLSѩ;8qĉ!p7x7oݺu֭O>}4C}R:b*qzƏ?~ŋ/^E3u9SOb;vرcqM*pܹsΝ;s̙3gzիW/Z;R,SGw'#####=^j\6,ķ 999999>}۷F={ٳw Z8+>!%+QT*0ٳ}1]3r s&X_Ɉ=Z;^hbBǴ??|s,v2]G/LN`UVZ*+++++C_,F{;:9<@ԩSN߿ZnaJ)5@(4^Ki4[鲅oo۷o߾}rk[\\0p#ڧC aЃΈ^UrA_!=0ꈮў;QW"׋N2hO'?L ȪhRyh]#ԟT$! UZzD1bĈ#LrBN$9*1Fxqx|rD-]D\ԂiӦM6 e KW^1]_^]\FUrgAΈ^ULW2sڵQTW:#ڳ|zo׮<WFlg XKKYY0 mRpzeQZ zѣGx5n*E$r-r8MVx.]tޑ46qx:`GhJڄ5v֭[nXM5r!m^IW>gNK˾}A?Kj*I_Cc|z.? ֧jr7GFH;T% nՑ+DWLxDfl48Sh/2o -ɞJ|Lͯ$l vOg~O><;J8r!tzvؐ({jM*aC˿!Ee} mIBR7hѢEV~P/5O AqW*ϽULqWz $JFS,~`}?}^AV;m\"+& dBdUq Ͽ.P8Q_0QdggQNS7橡>,wzة8bs @xS{ٳgzz1- bB?Zx.jjA8-zGWX|U}L iEa ~{QQqeegx #Wf͚5ka}e7Qi⡯R7񣯆>|pl>,\3=+l,7G0"ڳ|zF0Q'mU+yYdx- :` 뮻.کӦJ^X#~K3thBe#-*:Qa0qĉ'N2eʔ) S%y3e_ET($hH{8:T~&[@߿ԼF[JWb{+(C] 8ߧRIF&MUK_޿[W6Մu}FD{ Iʬ􀴶Oڵ^*8LUY_y-rI#GDWDܱY_E.YT.!7CXʭ2KCNHO7!Q,xSU~2 n[n]vڵҫ-o* _i {xIOE/ ~h]QajժUVedddddtԩSN̨\AsR©2zhm.Wt?K_>(}%ƹV/+&WAEJ_=<@۷omҹsέDWFg}}>[V;n'K_hJ=ɞ$"ˠ,2Qv_~]]ȼy͛4AcWLH!%mpe~OSɫمz!~>҈ ~ k}K .{ CEh'Dt.kFt?lp]TTTTT4ⷩ  i g5=ݸ+jp7K_yhNGV_!=ؽ~#[#'G_h*N )FP$Y@T}-}xzmɞ$"׻3R+tQ:]DTNYq:; J:=zѣm^>l$.֦oǽ˽0O=s̙3gHY! $l7틽kzjrr\WΞaC[ooҥK.]{w3dyĈ#F3f̘1cLsi}EMN׊`"+ +IRO)+jd}G#ArUjY_O_}>rPbW+lM Dw'K&q Mma ?ǎɉE%rLNMW%xn 3l@A?3^&@͢,:8,m#,P_fae}_rgڴiӦMC ,T.!ZAnE\ [_$+a+jY_Z#׷)$+@[V{'O_]:~}`ɞiݡÞU*qa2 >\2ݽ\&hC ǽZ`hE& %>v0J=D'ʌe:*MT~F\Eo =rS\Ң9ZD'm=&5Pq췩Xa}ʙWR~~2Crm$A_Dףd&{ `q\s{+o}ўU\ե#׷##I- 髨&{s^Tn#{"LP`{xQQB,ptwo AիW^shTBeFq,erP f:hG' ] }wAܹsΝsp5*i9qezzAt -'³ӥte)\PUyq[nݺut<) 91GwH9+z`Cυ::!}'L=JdOa}$A_ܪs+$O_}>T_EwM2r;ˉbs@iD~v>)hp51\8ү_~bvZgY\uLnb4d4m*(TLSJKKKKKSN:ue~~ۀx{#zAZc[]LIۇ0=BD ]!~`񫯐ꞧQW"W~`}e$+Y?RG_y5ˮI`}⤯Fw'IBDh+DUd >r 4X>; Mx1SC}VMd&OzT4w(KyL*+S'rGt`:թC߬v&PQ0%ElW稷=-C++p ÇWnmat+$M_Al' 9a+&{$Q_ޝx+Cg}}Zzwv\U`=vzT0o=OdOgݣF]\W 貵l}#x?r wRnѷ%lC%&f< .J(e}չsFҥO>xN}諰r_9k֬Yf'E_\/#}3f̘*zIW/zu׮:9뫨&/r Tt>/H'*?!n5g & "`2".Q߳gϞ={R6A6$@ *r GvF,5gnHC_C'*ķCcNWhK%** ټ[ˌ}e$諰r_٫W^z]MAUe$A_Htˆ WAEw'IjܸWQe_+ԟiD XE}`dh8z,2++Po\/{bNq̙3gD9Ahx!`b^ţG=z4RjkUx4 K_- B7y {wK\d/#a>*[_6N⪯\/#Ij+`}dXD 6A=25џ%RÈ\_9 ]۸>^N>}۷o߾}tgw!nxE"HEJgׯ_[ZdW6 W_E_㢠~ĵkALqWj׮]v^k0kqWD}%Lj0"H7鴨 Shq/l񔞞SO=SrÆnL2:ZaGlTR걋v A$L8qĉLb`4C3-Sg:u/r ~.AMA ~i ںFPC_% Kn} :A<)⭯:tPYvf>n*2⭯ eìT'WE$}uݭz5t. ]@UR2F^0Zɳa^=jfSNؑ5vj4JnjOD3f̘1XD- '( a܄D~d45oڴiӦMWM+5髰>}ܷϦ z2Eа`qWaFwo +ܽu}'r{\իY_N˥p+?rq)C 4555555444444pܰaÆ e˖-[^zՏ??~ĉ'N`#ꪫ3GhJU -)))))q9MO8@i)4YhB;vرcǶm۶mۆܑ#G93#uuuuuu痗7a:Wl_SSSSSӳgϞ={N*(W#sBOzvYT^ZSZ7|7|_~eԴFH2=W~&G ߵi髠OtUAI}$]}W}.ҜqJ sJBA-iP%*oG9k&oR IW{[oUWVțon? 4gtTWcǎ;v,C0\;wܹsgΜ9sL^zEEpmmmmm-; A@0!%J̻[5 Fځr.۷o߾};jٳgϞ=oTQa:46nܸqFkLe%sX{@ZGp^ h8onݺuӧO>}ס={[ @v뫰dE}]*E\jDU_#eK]EM_|dO{.waE}) 5Vrʕ+Me}G]jkMn%?6G 8 1װ wbͥ^4UŦ8SZZZZZjO.\/Qze64[e+]vE|\^c5FPC?8J LF%fN趁QӥfFWIh! +0ƞ84FX+(iEGw13㢯ў!?\OIO_|ʪtg2dȐ!ClTzQQƨ>2]Pk=)T11^}E^BRY:zREP/:uԩS'{59&FWɍh}eIW"f2^$9#"IWɍhONl1H_9viͷ)l3#%]tҥ Gʤaʼ8/TH2Bϑ  D z#nAH hȟ"T1Dd+cE+{H_fKu+ٚQ5Ov贿$U_9IW6O:6&Pe~ *_u ]N 7bt/`ɞlpE:)ȷ{ީ,Pٳg;IAWxǸQIM-6a ra=-#FB՚56 =جl#iJr4rb^}=#7G+6Lf}jVtꫝ;.e}e:-nX8;vER@6#F1bܹsΝk:]daٞډR)`C@ 4hPaaaaatJZAAAAA= )[n[t *m^lȟ86cP++ӦM6mZUr#ۦ$iJD{;M+ӑ)IWةl\/T}O>6n]SfSssssss!v0ÆeD߿;xeǏo߾}m+w=zѣGqTiu69iۤmCk޻-F&Eo_0WmWv4}?&{)W6}eOϧ**_&`Kz*u&N8qIJ2{$&{jӧO7+\OYlٲeːŋ/^T݈N ƍ7n8ٶS L6mNMHaC'Sm2Jڢ`ΎGK]_:y )G_I~N]ʦ #ӼA_*Q9Lyj9|qfJS騭xBa:_WTWWWWWN@$455555{}}}}}}MMMMM,UUUUUUWΜ kkt]eeeeen?tرcǎDљ#9.*>2s̙3g?~O=SO=**иhhtZW IW#G$bV_ +يR;ZQآM]Ma 7d}%zUϡ ,Q"WԭX_}2zh&HJ;55Lth 1sƎiӦM6a*(d_4#*?????m=wFxSgt`CJx_¡ә*ȗ^z饗^BEprz6-$m5Bv`Hi(i:wܹsdWj ̙M+;a}dc.IڱE_᭵)s$+IֲA_6utIZ]"ƴ?DV{u?̻hHSh\Cw/ y"kL5 :^<k-&hS8`@e4|\Z?6F}SK¡ެWLYlDf;WEg}E[$G7\J6&1o>Ým-XI㦆Lv P [ޯ_~τMˎ#&hAy9r=[k?"Ԉ(0%2@0J] qHMRWIhoJ_ +Q_4ٛWFg}EK~#ڛWvFg}%ׂ#IDATWIh/+`}@|@m:@%~Ls(ݷ+dK#2 .e&bˋ9^W\inތ/r=`}EU"WvFd߶.]_#-UtROz1SAྴr;vFѓiO-%$3r=sΝ;w6;m&QAD.ݮڥf͚5k, QYGׯlXBm3WE*i+;#WѲNrlJ /2yeݞjo=gD|WY`sz0~gfffff{cz@^K͑|۵k׮]x/Cq7 p )n٨ڷo߾}Ç>tZO_!uQty{m] >=======~VRWe%X+a}Žho^/r=`}V^zjYTN}eszWDhofYQ1 eRUQ=ݸ&53Se{R#).'''''GlƔC2tH~#E#I[ERf&9ktc@t$AK_%D plL/X"W6 e˖-[L+[#׳$Dףl\gD{J?l"Rܻ$@!&=č)jpK=@_j Od&(I=L&ѵM(]j6G5YJ.K _~L b;|}E5Zˢ"W|O}e'd &{}DU#WvFg}%gDNݸۇU)\RDTyxt&{S> -WB*k*<Y, S.4n(SO=SOoiI0F&ڮNcИ^n0xV1jjeHU_'}eg+wUxm0WbSzW*xGףΝ;+{\J*nWz0:YUDi1Mz͚>G^(BGwy>qȷx-PC:tQgiݤwjj>QYFkkb^?=۔HG)X_QRWhjWW^QWE7kק80\+*}}jtW#GbMz3w}ѝk"ʠ`i#65]cxA 4hPxwǕGyxxg]GeNcŘEU\jժUVx::t܌&?zDǣ{Zdb'\W.]Pw6P% kX_xWqhG_cwa}}E} ES&{} tKWZ*̺?**NGnX鋿 u]w*u0`qlN|cSRnς0._ɞn3(33333,(uSkiO V*W9|A}@L.HBx']tҥ x1SR ̐>V5+q$ElP}e[x?ҭhS)dxWюhG_@DE_tdO_ W,ҥK[xWv^r\pannn.E_E;Y}+ᄏ7?/Τ]88Xt+Z!(/////DR_____o:uP]]]]]Mw.ȱ9^{^/| _Ž;vرm۶m۶arȑ#GLEK]]]]]]#O/y! jWA+]QQQQQ eΘ1cƌoo/˨8ڧ^6NJק:Eނ(?a6zJq/jnnlD6lذa[lٲeիW^?'N8qD^^^^^UW]uUWњz Pځ՟ /*'qkӣ}%$5}<KTF(\PW( TXXX4cƌ~Tբi_o*ӣPs8Nkh)MMwq#Gfeee^U~ȑkWmT{C 6هz=xȐ!CNM-HxAʄ]{L(P w˯_wu]w%;gĈ#F[oVt 99999985e۷oիW^Ξ={Y|BtPk iUUUUUUS$tFgup̙3gΤFJVVVVVWկ~8qĉQ_p7x7hu֭[>}Ө+>r%tL[諱cǎ;סCD|9Ν;wܙ3gΜ9ބn^!ËB3gZ?ۋ}Akʹ*Z+#L?GWjB.;+++kȯ~_a}e?ʽGG_֢v`z).2eʔ]+Y_E 5}UP0ilTU8 >n\Sg .ta'=؅QHtLװ<}e@AE w+r=Gejid̚5k֬Y0Mg&( Z@g8y%N[ס!XqM#ځt)RPnj⺢a@-M,v숖D]˖jY_1RÇmS_g}e2}~d[_a'YlnHAWQh__]qW`jGP۹K+#ڇM@7,6sm 񺺮]["6~A\7 z!k VR (o,Qm̕ ?ՂʷBTi tEt,YM4y;4eia9Iih7 X_1"j K[Я>/+SUQQQQQ"V{K&{<92<\W+˔=-N}(~-U+Lq)}sҥ--k3Y +"t |]Ҳb~] Wck;ѿÿݿɞ`|VN|=So)G&8zREDo `A!0mޠa!Gi.TWlxoC/,jΧj>] S6X_Dd&{e7.e>x~m^ q^КS(Җ mu78υr>ψs؂׾uŊ+V裏>裓'OO⧯5555m]KQ}[ /wӦ^p꫈azv茷̪Ȫ^Uգ'yKpKϟ?N:uԉ־۷o۷o߾ݺu֭gBX߿8 =b, ,^$-rW(* qY˗/_>f̘1cƠ04ڬϥ*+v/^K2d7~)SL2e t YI9r:^ ,E04x2((^K2~ZFe4WWj%!R#U}oʕFج+?+wIq뮯:tСCa}u}%h/&9r'e}TQWхMm@Ē1:t)JG?я~4:i4UwRVꏬ-2*!J@M$ 7}!!;PG0p 1u/Q\*tө6 A ~( #a/Y_Rǟ4O*hݻwnBzuX_y%5}%[~_a}ϙE}]d¡ B̹J4+hbaf^ mCW!h8%QfVI[%F3<3Rݞr{(x|mW.ȁd]hiD2jԨQFJ:]F;A+k2lWq l* n?k#۪Rr_m eBV;05V 髝;.miYN}udt-~UUݨJr!~h2`?[KM89:tvQ\ Q^u L9ւؖtn*1UP79}^uPazg=@c8Ac}ʝpLLWIWH4ShFWW{Wэ\+wWGݪϣ/‹jdUةtu^7"4XS2=ci0?wT\txRx7z݉gV_E#r tK:3mBAK֭[n݊bz#7s5W%|}__%W"+U*,w=0^W0,؍uxDW%|}ulZF; ` 3e=@| 5NU,%do7nd9/_zwhD'ڍ, F^J^}ď}4X_bU"׻ GWGSzX_WGW!(}d7.un|"C*LuC9{Af.T$lMhEVy_&^Fj93`[,-xh=:%ޑM*{&Ѝ >ELr_Rfva}+J߄}Wa?EjĿޯJRzwX_tF񣯒^+]*܈"G:l ϧ%% G :?;3gRjsտІ^S]xͨ -Z!'9 1Rz:/jǑ$D{W ԋԞTUAـ֭[nz Wz5^#*zZD{&{Jjz«#ڋxWɌ\/z#lA[U1YU݁K@LLڹa^^^^^~A "#G t{D:o}[ַ} qctx^#'mo3======#####Æ:k )/]xʹ!%0imСCA tqz"RT܈#U#׻*}}肥T:bfH)WsvX_׈aդI=z聭 9rWaCD{ ꫢ"GJf"Q_΍IXXvpdU7“h"hء$dH8vM$Aeeeee>e~ӎe.ۤKh5CǙGv~IE &1߫W^zjxPVQl1hiqF2R<($ AfF;ih|˗R^D`:kDګ+##a}6Dwˑb_bBK;FW_>:dD{`OiI$uu+V,]/^8̙8#F\**q@^ԸG? F Ʃ?JNNNNNέz뭷.[lٲe65******P!+O:uԩ֭[nݩSN:%Csqߐ69` ~Kk{Aӟ?M=mlذaÆ w?Se=<К[]]]]]o!KCx:x^- @kxZ`p)*****Dvuرcǎ-..**,d}+>8ǎ56:N~ À)^ Wa?h[hixګkqoq.6Օ q =)9cϜ9sb)Ke$}h8pFghqUW6cz$s#GvƑUݡN_lpI*n馛nIbpzFQ3x2pvFlmج)('ݺZt m$~)o7utq#fݻwS`'6FަBą7j@YP#hb@vM0XٳgϞ=io_P]Mh~dFI8xŭf;uY_+5FA ,jGFFFƘ1ݻwcL *vnB>Pg&*L8?/!W;w.]Ҳf퐩2A`i;8u׭]K#2c+ZT.1Qq`:P\?\Ş`7 9^{^kY̢'𘃷mOƑ~M}#buM?l;>|k֬Yfg.!+W4&*W2X_Q6#_ tZZZZV,d"CZčA*TPu޾{ҍЦ20#HyvqS#Gmx4JhU+iQK;+lxqWZj* /H$S_JOzdY_J__/4To+ꇎ)j zWCgF~uݭ*dϯTbP~S иA"WibE5tǩMxIPy&-q8;w3ݽL}+6+#St+i>Զ( NI!D߫W^zP_/enSm5uC]S'ӥѴ_h/WQ'6&{oU@ /mqA%#:E0/.F @brN DJcT pE@W"(6+?FW(2}22d}%>+2y\OB9$دTL jŋ3fȐ>oo}?**Xa9ح[׮˗}wБա]Ev;) st{Xu֭[ǦwG%t~OYt~(QDz_1@o B٫/[4 "JY  RHWqM)TPɰu!=]vzۼ+מ*NWzxW^yLځ%5=+W"QWFpP\OIJ}gӡ8C:2-]}FQɪدMq_A_fJ_ُa=*IJviiiif>й\Txae~(:;ŰAu:tAQc³{q="@zݡ=ų/jzp=x!m9nܸqUqGIl^@?6Ne/ PrWq"5}60֧6βb}%# cKEW"h?}8C6crN)HFFFFFb $~|Sw별ĘɞΊ"ǞT:6 ",*(Z2lC׹JC?[o[BzQuĆ02:KC; жI' BSˇ` ~̑Ud8G_g;7p 7~W܉]^Wo ~xFCW4X_ɰM_r Foo3tNۯTVloMlMSN:u͚5k֬sHe˖-[LSPjtI_e⎐h}Ғ4XqO>OL%?o8pٳgϞ=4O?O?V: / /p &L0sawΝ;wܲe˖-[q>~{G޽{{ܹs /Wpoo믿zS/}K_ cǎ;vM6mڄa"㇑ LTxm,Z0 ؠ_FYf͚5_׿u=naÆ 6l뮻;;P$aFϞ={׿=ReŊ+VG~G}0r89*g?~{{sΞ={Y۽{ݻwذaÆ CDP2Q;p=5Q;PU=`D/⋿oΘ1cƌr ׿.*** 'xY]v;?_xG=zڴ=zF 'O}s? ' r$(Y?)ק[_?*XrP=.xF<ѿ@£)ݪ_/Z v6av8zqOڜ3X{"GTxU ڕVbFgU_9 <{D+Q_OwO|_lŬ21/{D8>3?-/cO>Y46~ass]ڵヒ S?-..,G,--(^ۻw钛5dHQQnCխ[N}'M=kܬ#KK{,3f7s|+#F̙3m`:wdŊ^x睯?x;:˗-{npoIɍ7o8wn׮;o'ygޣGMę̌|;qw?7nܽKJw/~l:wػ:~w9tȑGO·EE]ѥKYY]t3=}yy;gfvꔕվ=22>ĉ]򗦦`ӟzc}'ΞbvNNw{?}o1cԡCUS_z}ݻvICveeӮ]vϟ?#?SijSZGC: o}77=ztvvVV۩See:_|҂|4i?Ku~aCc9rEr53/kW0pwqq.58e8Α#ʬ,q.].řKsnh0\Wï48X/}88Nc8΍7:\J_98޽8\jn>rӧvթSǎS mۇrpzzvii3gpða۶y ۯ(گ 'ÃW=C=U8B׾K/4i3dqP&P\ܧO^ނf8 <>Y+'ǎ޾}wIƌ)*ڵs,jP_ 8F| /aH-&n2t+[˗oڄ /=ů3`xC]֣GN z0xpQQ~>N0rd߾8~CǏ:uȧO9s{Ԅ0u)}ğ aݡCnv-پ}z:zu6o޷** C~+UUCw]yeaaQQ޹_!OׯA|޸qƍMqZ 8 ۋMapӟ?)25.*j]1|?/˗?M75$P.}f<|qo9W@ێ jk{0^rŤ `ӳΞ=h7֢ygtc={GXec`,t㴚a=}q.wxOM菩)1IWկ:W88X'|7h_z3fL -f+2q1v >/8vĉ3gnn>y\N:}9VJsz:M>+(ͽzxիmcfwywk־'X7L#]`XdD#p!hP{y:H* b[yCev/yj`)鶁atc()XjJ T ,JEȻ뮻KSe:tr^ ?ޅځP>]&xWATTw 4[jn+ tket>T/IWu_OCv\~JFR1b_@+y?G]h$nGp:4U6[aGBR 2(;MB%\lr?6Uo/,G`#4yԗ-<#<Dwyx~jֶm۶m6bĈ#FNNNNNNNǎ;v Inj$`ү}k_0Vd_xW^y/WD:yO<OtcFI2Cɓ'O< >A`dzi7ݮf5λ pVVVVVM<<<& i'r7rSS^yNjwD>:~RVn֭[nӧO>}о 34UP/~_} z?Cn6H,al+֩@틛q 5O G`1C?a;wܹsR6 ݲe˖^2dȐqo~g?3f׮]6mZnݺ_n}wwi78>qĉH=Ç?p… o;61޷o߾.+++:U9-6=tС?9sLLlxg… Ç޳Oʣ³_p?6~իW^0___W <+BfSK9̦?5A͇dxAڐNzrކF=:.Dߤ]xjڠ*}؆m*1WO@LqD53tRϽ[Eɀơ-AyʼUQA[m2rI%WhLPWAѹsΈjСCX_ &l}WC+1T<~ۯa=]: Ѹ$A;64mԙg 9T8ž=;vL 6ΠZ`{t:(j·ԇ (.׫t0@m (o({*R r@/YuP} y+ؐ{Aq\bja⁊  Y_+wt+Y_+wjGN+_o:U_WBc/}f 7a^L4hРAe h hs7|Ax:ysKˊ3iرCgӥ ><UGM\pC5kFwS:ĵ ,;U0$pUGE'A7\୷rsss;wɑ i//bL_mMJ.}q6X_jii5cV~.lrGn馛n)lo~T~0ً,"De^sE:hAY M2|--W삣"bFFK(ÇGe*K5E:`nGR4AXfH57.Qܲn]BHpD$hnMNVG=z@>k1u/A! +pSWm 05P왛8'?PUr:WmEWii3grKt{WAW؃$U}6r}u>6~_~5}e cϊ4d8n lOo ldJcN]C=Ca/ )H+4[8ڴiӦM.2Y'?ӧwz;?SSN>wǏdžÇ>|86+ WNb0ivkkюS1ө >>۷o߾}Par}Cގ5jԨQemOڵys57;ץ--:t %ԬG}G5 666lذaw}dɒ%sΝ;w^J_56{o׮ee3r 7ؽm+ƟF)B8uԩS*;{&9wI韞_WIƟ/Kԩ.YbŊ+~w1iisN>yO:vԩS pJ;8ځBϤ1][[[[[[]]]]]'Y#<#̞={x.AN M6ms=s8_={hϞ]v萗׻a0ʔ͘1cƌaċ:wܹ+r}[64+d}WꤪZZZZN>}iqΥթS64MMǎ?Od}WWgΜ9s LgϞ={cǎ;omۯ+uٯ⯯ ?: x ~KQ _h,AFH|TIS,..**,Dn;y$W2HR$-~7iJF+zlبo3QVi9G%se •PF߄+|WP~t=lFRT(G+zW5 BEuZaꂹm}sҥTB_9r}A؎ݽ~X__&l߾p!ۯ+uR___@(&{:wM#a-k}SiԣFʠGhHgBgoхfs̞=kʕ׫7EcuDx@ o>2r;~mBj%A"6|{8q0̑^)9I%%)( ^ 4W+GZ}X_ +RW .\O`W!_ھ/`} ~w_6l _~}eQ%fT҄τ!h8z q*x`K#:҂Wha4xv\?EŲ";4noʕÇjaК zT rOߡ>h #J.jDž&9\J_ŭѥa},T𮯂믫精 W*|). ۯWx_60ö9:q^(W(3%$ACtb[s۷/\ڑc tnqP89Wl ԡC 1*/nQ#P;@IJޡ<$A2L S\/$K`921Wa}},0 Wa}گGWA+دF}e)4WTC.Q1E"@}9ܠq$~~m WWԓ?C 5htxtwВVu'~[zRHBOzL$'!xGg VQB~T𢯂S `}aĉ'N4a>߳lrNj* WvdLAMwNJrThENȽZ mbc(&P3wMo( /////O.:Q/yho0>)KtSj.I.ԣJ#*5X_E_[D{uX_+wn[n#tKGWW+wWa&{*K'BSD3q$O⼓xҠ_@0m5Q\ެ׃aC G(?qxNU*Q\7$̀z!x v3GgWG\R\_%W*$Y_~UVVVVVfDE~ۯ.e2}e?m$ NɋkWt-쓒>qĉ'U^=\%/Jm!D\(ׯ|V#׫ N{$!9~.m 8r=\dX+⪘du|X)Iqƍ7~$_+[fѱ/Ibn$sut -lmRϘrDW :d&Idw$* ^ 4hРAm-Ozܠo$!:r"ˑ"* )VU`}%tdў+w1bĈv#aEsW2~eS85}e/E&{"D\ss"ti4vq+,#ԩSNڒ;q\:6?4ZaDSP# ڙ=Uo ?^2nD* 5pL҉SXW7"AAljjjjj2 y뭷z뭽{ݻ;8s5@[ fq! ֚NW7o~O;αciSUpi::@hhhhhhٳgϞ=MF!=8#(x^ёAKKKKKK?xu֭[͛7oA%on!sBF$^:"R'NtDȚSNۯIft+sƉb#.2"ہ@^ٽ< 4XLt}w}'+r;% eٳg6ԁ 2;;;;;o^%PG1L  $0A(eJ ҪtD\J$+ 7=`}NU^^^^^^4W:"ڳ+ɰ_ُn}eߪ.$ o'Qȟ4|W :uԩS7%1r !C 2$ Q̃`) _lJHbzT4 f͚5kz(hIgj6jƑxSWY_y%**h+qWSL2eJ4W:"_ UTЧ_X Ig=z#~ 6l04vsѾ 5hr#@=1"{^z饗P_tP;!^_Inzh= ĵC>>я~RgsbkDDO@ ˑqZGh#*+u⮯'}շo߾}Fvh~NWBYLvoa 'O$+⮯ .p߰`}NUW# W*~Ep2 8p@{d(/&8t{iX7w\\m[UPWE}FpEm Ҁb*.Fn 2u@mFݦ=)Dg! (JGgϳ`:-/*+⫯TJGD{_GKVaM(K@wM<8Dne iE@jJ5s4 utu 4hPD},:oQnZpRO1i5 ΑA#.QLA%pm.4UT_aڦU_EĦ#*+⨯nbў Wѷh+kb}ui<6i'---oM. bia1GtΝ;wܹqƍ7nٲe˖-{GpGpG,lqSmSQqՃ-XPQI[ Gv޽{ϟ?<:I,aCG` رcǎcÆ 6l <`sA/FxrJV4DCR&n2n݀gjn3ICh2FmIl&Q7UcNJ$Sws9g?=w}^^{m_3;?|x`GtoK_d82P_/|;w6eiޡ\vڵk2/5Q纻S>&2LҀf|gGw:+\WccccUϧI>ѬS,\X_r͚yw}ttWT1J;% '< d5(0jxi+\WS%]C3G쿒j H|do($7*Х&âPAUwD}~tgR}Ǐu8Hql7KKwvl>ȗǢY4,]h 0SʢFy<^O'&_O+t׮Љ Ⱆ/_|rDNB[2y5Q;CǦb+0źld="D8\ Ug}%-}y"}$~x+WS;z;‡W忚+GLNoA@t]:`mf.c͂G'`FF)@Ewte˖-[uvvvvv~;rYQnx~tH|}ul 3ypѣG"ޤ*l2PaƙFUWY_vE_Eў t\_EўW2\_MJ~eo~}fBO@X9U*YAۗ8\9çlmrx [1UZSvgFăh =QסX∆ٟ}+/0~jE}+Zv Wwt__{Ka/?j:i/xMYӧ"u1 "ykkkkki5@f&1dNC#B>jOdSG:m۱cǎ;0S4z eSRikL@Zӡq@m۶mۆL0jR ZgٳQ2LX_Un\W1BGܸBÄW]UnX_ W~Z tw6dZOD>>>>fp ݑ&gRX!Y< iN(j4TwsC8F¦f8#aqK_ ;+n+hH8_Wg!1o]Tea}=:+5|kbm2wM rMSeWpK4=E]%(2I=]ƙM/Se6ჺH.oD'&`У33Lϸ]}53n3ڳrW_͌[3ڳṵ__ [}LaT8\ h&p_ E$6x`F  Xdv2)nP)C \_QtA:RЭt]q=o~33L!̸]}:ʻh]}ڻ_ўWTGyW2 wd0 ]g:;;;;;gϞ={l4zEׄI5DUzڔiu1>ٝ;뫪<^җ?)2% Y͠=mcp 4B;4m_JOs˚?tw55%%{*2u]9&]ׯ_~=>?VHf41 q-d'аumqz)UWY_xh ︥pfF{_ +5sޮ]?|Çi^<0:x:tСCs̙3g]ׂ#WuthۂҢP6sNeߵoiQ_UvE4 Zg>|3&1nh8KFVi78;|[9:kA7{ٳg):]Mo ugc=&&FG/_n S803nw<}NF{W."_ўW.+]jqѠ[lٲeKiiiii)UT>ٍ1b~7j2>z>+aqǏxԤz )Ó $~IL\O>)iZ^X^^^^^bfSMVؖ3ʉ0&㖾*4*+.WI*4=+t+WWdg)*T_уmOsi+:E/IP+hF4  BRn{FZ`'Ze>ЃP& 1yŌ*%èmϯ_ zD *Sԉ{uN=zF0X^&Bt*h$N~m6ذ<=s=H?J2Wd\e}宾#y*WQWnh+wWIЌc9x?j݅qeÆ 6l W jv(bpݻw޽[=0a̽.G'ObmS}"$AOT2>I'[:(رcǎ4'+R[s=U Z.1 SjXEi<4ސN{h„jb8劫 Z7cK0⊾ qʏ`Y_fF]_Rh+W3裏>]VM1QV(ΰ-nE`2 x{ Ø+J5*+*W3gg}庾RE]_⿚9=\_#I3\ٍeg3I nSSYȆ>w `xR1^T קDr7mSϼY8E2 }4+`PdzzQ-x6?č&Gu&.@UT&z^斫zzz︒at⊾ʝqU2U02ڳr]_僺ўW_#y`WQ0)@vL]fBIe4'+9|' g쓑;s t6Ӎɴoцi +fZGt{_ /s?\mh?t=$hh{>}cƕ\1دg\e} }?IW3ڳJu}jzF{_%?I_f׫SP3MFv/Tt04ҁ eC[Ua޼y?/N?;3Cټ?9)?L ),4"w@b%K,Y"~^ߨ s:<2n?9yHA'qƶgGhk##<#-_|I9w3g]0qcʸ*I*gg} }U(vLJP俚9` %=6޶lƵ:RI p… .\iӦM6_-A,s+5vdB䭞3' #fUTl_ڵzu[ܒ%rI_˗S'H*e`8ha\ڄm۶m۶mҥK.E.A>}7^I~q.0:U$I_-Z4{#gfG=*Z Wof_Vmn*=Ԝ9IW! &t0t$"pxg օh\ 3sq?z{qqqqq1&`]Э4l!ffzFe6hnxj ӤҤ-) 303@Ԃa}5=jpW_AɓwG|\Wq2|PV& wWq eL0ƁS4Ќ C㷰2rt[s9SeDn;u[_窣w'$IA1$h ?s (2B#[=,4`֬YfŤ;OGl$,nH3;7;.WW'i(W^z*'N8qsuw{^CCWrKf=ܣ뾾*g܀> +QAMJ ݕl +H;ԩppDvgׯ_~bbbbbbm ,X`A:& ]Cu`!^! }WK/flR+*nhzժl$` 1.3c@*'sΝ;wԩSN, ށda>L_D0Yd`XvڵkM>:qdQ؅D m q(R*7=\q֡->;:::::l۸GzLT -Y;=lķtcz\OaVV:tH82׋A Weج<}9mWzPWDBMFow͆?jbbt4+=Re}$------,lė^ O7Nꇁ{{`hYhѢELuH2׋DsA?L4 y(/hCqd&9+3ʰS_$y*큝JJv`&mtC2žj$fvtJ S.{ل-*Qj60DY7pK/=9]QiB_Dqo`>$;$$FG'Uk K-0AM_aY=ݢ`Ih2NdJlap{ z@daJ?.pY_N&ȲA\p… П^#Ppjƛw4veqF47+=&r<\/BFk]h =$*z-~ 4ǝ#,kIj W*7Yأ<}%fO+P}69MJCS/x4KZ/NnF{{Wq)=7b.~^~_~Ywd 8=H 0X3Ji>q3y͛7.!SEogFݑtK/K4?CTCĜ9s̙3<C:\^ȸa=$ :)orf+$[_QKA_ZٳgϞ6F,G_|_|1HHaJ?.P~kr:8Zw(;IDATDgCtp[o[oA:Cd=J>o޼yo͛R^Ng1b¦f&1@~~k׮]vMwvҥK.7v8/t>Ab&j_LSl}e WI7[_ꫯ~_W05)o|7N8qĉ)AL_K+,7ӷW3 ޜeڴiӦML?s=GA]q])g$POPW1U_+hLWW|L'|'"ȿ܌$Y翢{L+7.{ۛftttttt`nç?s=!rʕ+WܐO$t,DYwП`"̢E-Z}۷뫙0tD{k)a^drUWFMnN{ݻw޽&vm݆>_|&I+Sp~&)=g󅓈(١,dG >O}S$:1P `CKZ џqFt$0*WItJLUo$1<}e Xŋ/^%}EWp($)$W3IAwYLO!Q}Hgo{.:Z)QȮlا6u#cfYix[)m-aL_̈́W1I_\*MWhlllllsO_WrۘČfL! +3\WNDhcdbfSS8dFRElhme6/0tGRgD3,D8uDDim`"AGeC&&FGy$PpDŽ_t3)I_A>f+S:\諭&K^+HJ嚾resƎ:-xI1_?~nK"Z4=#i^i$}C(;N;(:Etۺ½s=ݴK} z^0Ӵa7+:wƉn}eh=W4,P˖-[lVk+c濒1}V& n3 [ZZZZZ -bm$gS_hdzz"cmmmmmQf5 V/]h~~h^f%bFJܑ,@Fn}eĿWp>) _Yf͚5n+uIZ@2Tj#WpG_9/ ЭY,mkɌ_@߉&Ô۴`QhOWuZ {Ϭ aWr\˸*C2 Wgr髤d׭=bɒ%K,I_ӿ$%>Y$厾rehvtiPa盕ܹsΝeeeeeeI*=9uBQ |S2gKꫯve˖-[-O@4˜I>1EX%17s=1 PTU.\˸*C2 W+ihhhhhVrzPݍh3_H2W*_t+GvM\`KJJJJJp 1&Dj0\֯_~dnC.GhdmaF__q'㪈>}e"uJ5 t+y{1?.Q|T~F{}+`?r A= D 9Db̙3gΜd:,$\%~ 9 fVz-??Trhv-AmupwcF'3 NU,X_|+lnnnnn]"=k73_T\Wκg:A8Y3B&I:ʚ9u1W&[^$Ŕt=s=Ӕm a;,IZ̋)16b&zNU>}e D+3W +A}WKnf2 _%}ON 33O)E)(?Evt4ZµVQ{VM\O3Ƣ,-' GiV"(3IgS2Ӿ^ra#t'*E2W2WeסLA8 𷜹d2_B. q=݂;#8p5:3wz@(7)F<̈́hMAmSG:]ƨ Õ2&!^?[7s=2fca3+9.d\:~X_\_^ҿ__%AW+*3Jw2_Wa!gW0#ey*:ct3ko}Сxe Awv贊 xթ(d-iŊe˚.,.ܻwǎ8)nwW@<čn/˦i Zfv m[ccKS\aE丐qUﰾ2ʝU___*9WՑ:BjEwWQ _٭Ყ1 Ir0{r?Ofe+=;UA=f]=Z5#ϗh9.㍌ۿ~=u;ɤ *䒜GRfv̜p1W_?N6uU\Ǥ=ѽ0^D}% Wue}e A uXf+Ԍb!_sy֌:WSK:E[_JRTwwwww7t]0d2LO8cٳ?qɓ'O|pڵk׮[nݺu0Rtk}=44444;³*5k֬Y j堞aܫ~.*^dΝL}JJϞ={YGΜ9sm5FKt >>CTC-$ &xR]c젱Qpؘ=\ww*3?s@&STzիW^J{lj'N8AG3<<<<~ w <#ծP#f:h O<O<׶+GeeX. 5ZƘN:*041 A#v2Z*>X_e&} YWL5>WpqoW+̴_\}sL"4O\X!jVuצĸBd\b;1'vU=Kδf'z6$u5Uz}n4MTS#5qSn!;ސHnºwaȥ- w'k~t*NՓOw_;Y AIʍtQC]WΝ/_.)+8Q[G~}spc+CE435[8\tnE,<ǹi8zPBzXXS Z#;tF)Gк|doٶe]nYIWgꄎz63bUr_ LU<9sZZ*+Y_Cʄ=&CtF…ŃO<#$)*d/=g@\9Gb8dkC 3PH:#>L_PDJBb#_[T 5PaڹL {]Q'De /!~fBmkg| "8R]R(gMD Cr?={> /s}~P=E}fAz}ң2-_&5>>Ỹ[=.2yd=0&1q_|+a}.H[=cWDTLeOtAWeg P3@# g766666.\p… 5j4aeԧ!HE=B ʷ@BV3Ϡ-Q9K_իW^=~FV!_nW-BT \f!cF륖_lϟ?~#a޼yͣ^GtD'aKm23s}w0 &r}PZ<}e";I&;|̓-"[\(y+;`U_+Q)EQY02L&# 4nGg T*Jūt?Nz{{{{{QBF7{ݧO{^uM4P+W\r%Hd3Gg}u/~_oot:FnW= WfQ4QT?o\N{ޱcҲoZg2AܹsΝ !8S$aQQQQQ.GG*|tī+o{{{{{;JBWmWW"جF]_Q笍u( +K@U_ !_M*.{<0׵J<6FWPC/ Im-0|Z,6 X'; 3׫Tx"IޠJClnnnnn !.9F'dF3׫)WN]ATM8WmW.J49/l}&`]j= YqqqqqѣG=S /+w`:alWκfDnUpA;2׫COW.u 6lذOw&'sMt4Cͤڔ^=I(ݥڊbB@'.uC2׫ 0 0&o gy5X_gy5"hxW8^Ì<_dD㿲C_9貧R (¡QaE[Pm, BAM=R8zF9ݎjs zzGQ裏>Fk ? J$J)=FC(Aٝ>p; ͒zR Gi$Rí 2G[2ʟuo3ׇ] $Q_=^q5ǫ\^_ѾwVAkѷ迢6n~!_@+;#.{gܡ+fAVMddG}]̥çVH<+y4Y.dWM6hܡG5ipi!&\R'IK\0 2}6d\'^}X_ť hB\*)+FL.f7ޟWIWqWֻicş'GP[qr-s?*5_͛^hqhO]o&CZz0Tɠ5o&Q/jONޟ vac3㪌U`} 3?QdnE_zM^FdW+`(Hu*ul73a^}05oᓭһk*3*XWDp#z$B6fzx0 & R21l}DX_ťh~O?AQd3+@hWIWx?z XwiIUe5A~zt˕@H DgǍlLkԭdrz &ԼY}̌JAvNQYWt9s?0LQ*DqU$l}\X_Eh/#<}\W]EeD=ڌ" _.{7 ա_!ޟlΣq@&(tUY;j^F~Y&1eJo ]ϗLk )ePѬ, +΅82뫸U)a諤Sn}7~v֡A#Ha+frWqLW&xh7ti\/0&Kg:qڿ 9F H ZrA2PKXWzmcݻwRCE n5o6LVz;taۡJ#Hd}_[/=%_}LQB4LR=74W$f+5.{`EهǜK%1s B6'k E۶m۶mҥK.]J6MDlR-I\/8b*F1vpLNNN;Ԍ@3SWtf+.{_]-`xƃ)8s}nGL).l_-LY-F>~o3._N<纻={Db`U\%+efhX``C]06z]гTÐẢW3Eˇ{PWih(-:aY:;w-\=ϚJ |&ɓ'O{/Y?*Lq꫆%)e.}eZ$WY }GLWiqu^z7+DZZlrʕ+W 6|"z.=q3oup=OE{^k׮]v68)){a} X_㪾۠3ʞW*J+6eovA\$9"~B<_6n0W,hwc>}ӧu(/FFڲŏ]AɩCՂVDQ ]xk?0j%=Wź.`GUaN}uY?:*W"sg3D_}{MNB\_Qe6.{P1.QPBRL!ݬ\vtIģKHۆBOVwYCuv\@AN}i2,*0~ة-(l90+J+̬1_$g.{x emPb%gfT+ۮ{BS4=C5:yϣ]]OS4dzvueUE܎LNl 0ةT]W`}e6p-@`q+6e:.{;:0jlF]KZl N; !P] OMm7mYHlzhA}1 Sv+5Wm  3n𸠰J}` Hb}%"W 8w޽{믿's=[@#rCW&]2Ahq_:H\̠C&j^IKٖSr ^f~Oa4 #Tէ>|*X_WN}5>g5^__{cc>|pXWW*1TWVVVVVΚ5k֬Yx~ƍ@핛hnȚ4z<5Օ=BDiWsIքlxŋzGqqqqq/҆ A3U@/1Ebm0LԸ X_إjÆ 6lX_忪./W쿢آ?~梢jҥK.ݿ}e˞etݩ堞O>v<2-]˿]ׯ_c|XIAwJRMhzϧt:d2 ^}|Na&\`q𫱾b}..髊ٳKJ]?_%Z/կꚷbGmMM˖euu՘+x &%챧}meX[m;w.^,joqM7Wa~?0Ǿ~;BCbJa@bNO*u{V=퍍ٳWtwʮ]7/^oߧ?jU}I&,4x K4^zt, 0adb02t+[xnS.{@ Gā`}020ĩHE_}'?reOmnXZ#Nq "?3cGK ɂ^vڵk41 e&zMqasΝ;wnUUUUUYjkkkkky)0]ĩ֭[n:ST_}>1 W #3G_W͛7o \usepackage{euler} 0 0 m -1 0.333 l -1 -0.333 l h 0 0 m -1 0.333 l -1 -0.333 l h 0.6 0 0 0.6 0 0 e 0.4 0 0 0.4 0 0 e 0.6 0 0 0.6 0 0 e 0.6 0 0 0.6 0 0 e 0.4 0 0 0.4 0 0 e 0.4 0 0 0.4 0 0 e -0.6 -0.6 m 0.6 -0.6 l 0.6 0.6 l -0.6 0.6 l h -0.4 -0.4 m 0.4 -0.4 l 0.4 0.4 l -0.4 0.4 l h -0.6 -0.6 m 0.6 -0.6 l 0.6 0.6 l -0.6 0.6 l h -0.6 -0.6 m 0.6 -0.6 l 0.6 0.6 l -0.6 0.6 l h -0.4 -0.4 m 0.4 -0.4 l 0.4 0.4 l -0.4 0.4 l h -0.4 -0.4 m 0.4 -0.4 l 0.4 0.4 l -0.4 0.4 l h -0.43 -0.57 m 0.57 0.43 l 0.43 0.57 l -0.57 -0.43 l h -0.43 0.57 m 0.57 -0.43 l 0.43 -0.57 l -0.57 0.43 l h 0 0 m -1 0.333 l -1 -0.333 l h 0 0 m -1 0.333 l -0.8 0 l -1 -0.333 l h 0 0 m -1 0.333 l -0.8 0 l -1 -0.333 l h -1 0.333 m 0 0 l -1 -0.333 l 0 0 m -1 0.333 l -1 -0.333 l h -1 0 m -2 0.333 l -2 -0.333 l h 0 0 m -1 0.333 l -1 -0.333 l h -1 0 m -2 0.333 l -2 -0.333 l h 32 760 m 32 832 l 24 768 m 88 768 l 48 816 m 48 784 l 80 784 l 80 816 l h 32 760 m 32 832 l 24 768 m 88 768 l 16 0 0 16 64 800 e 16 0 0 16 64 800 e $R^3$ trunk-2018.02b/doc/sphinx/fig/cell-shear-aabb.pdf000066400000000000000000000644571324306050200214260ustar00rootroot00000000000000%PDF-1.3 3 0 obj << /alpha1000 << /CA 1 /ca 1 >> >> endobj 4 0 obj << /Type /Pattern /PatternType 1 /PaintType 2 /TilingType 2 /BBox [ 0 0 100 4 ] /XStep 99 /YStep 4 /Resources << >> /Matrix [0.5 -0.866025 0.866025 0.5 0 0] /Length 21 /Filter /FlateDecode >> stream x3P0P04bT4.U endstream endobj 5 0 obj << /Type /Pattern /PatternType 1 /PaintType 2 /TilingType 2 /BBox [ 0 0 100 4 ] /XStep 99 /YStep 4 /Resources << >> /Matrix [0.866025 0.5 -0.5 0.866025 0 0] /Length 21 /Filter /FlateDecode >> stream x3P0P04bT4.U endstream endobj 6 0 obj << /Pat95 4 0 R /Pat96 5 0 R >> endobj 7 0 obj << /Length1 1394 /Length2 5926 /Length3 0 /Length 6875 /Filter /FlateDecode >> stream xڍtTk.(0 -! ҂030 HHHRҍ %?wZY^vV-]^KTGrF~~A>~~"vv=]t!+@ 6y0 T] AHD$*w ),|UL.p@¬mP68!\Ç~dH Q6PtG(AŁ@777>3i-CtP+ 0@gscEXH(mApgt tUP`?_ٿ ࿓ V0{(@SQz-|+f@9( 9a AOYn)ppQD'CB!{lp{u-~tq>Ü\*MDج(0C!1 upv~|+4 #rB( ;}"0 `6CG&h~y{%nO5tU4x OV x CTL2[.OE-8 4h.CPߔ_~;_U_ )vsn4]PhY#wա0hy ÜaPK- bHj!a8,~UCU 킢E pE`$Aď&0 %7@>8N1H_kEs舄D* qA"t߿ϿUC!Dȣ`ۊ27Ŧ8pa+/5t'疥wR f;t7m KVAa(t.~8n2["譇DS"㵙n?71h03DMpZUm;k2*F&i+ʈmkdJK4IuSy:t=mX6M띝LX8fRPU}pGOOWzI`,p(QߴtT:?: 6bˊ>ӷ)3gt|KC2ra5-α^6EZ,4: ;rw &ҕ1e/ 2p?PX7YT7TV? ky\r̞7VB업gZ}ET?czm9r@Z-}$$.ʷ+kѤU5_X!-E,0߭!\iX^f6d4M[}g0<ϭׄ?4_P&S f0~aU'=Uzq#]ĹAA_[9?zds rA] 8jGdb"T Nos)xg*9^ՏNt0v/-U5]y0_6~E  P8ouឦ-lBVXˠ_ rk 'X{N 63]^/`n 5{{ΜPTʝp{g,+{&JVi^f͙2 8}$3"f}4fʻ!&f!|Gw.!8Z{i7lE1B) $7}rƤ?OEmYyzIG>e d 7VE#zɚ_D >f%Rɒ }E#L# 1OIN*\x,Z'6_z5 ? KKTxO8|Li'%:O eiB9ݛO?9}%J.N69g-5n5Pڨ0:[-ERHhcS=eW(_$ fdOtދdgn<(1al8+{lk|WbǖCQǕĿ/r®,{k" 's>Fg>p$ZotUe‰qꕩ¼< TߙY0}}c蛷#S70Fcfi(Ksp ~`dlqIDP{L׳BiwK(uՏ ,N[y'nGfߑ҄HtmpnRD?Tq~zl'~)ti0kq)=ϛH' I%`Vإsӓ]\Z=4'>_0%x}6k< ]`7R ͎!L} {H1-%އhQe7A9{r 9S R'j疴}.!8/% t_ 07T,#h|H ȐQ<*\bg$h%3niS]'w[ 8u&}Jn.{x17+̕J%eLMvUőri9m&4{ŭ2|^ӕiPdbyȸY 2ºmH[;>ET@vFIqcP'wd ,,3r_n齅Ws;իq$ ذ@]J'fcV<\~/S/X U$VaelwkZ|~~g] :OW$RvzPS޽x~︐ˉZ$U-a9~@ڼBZ,4Vg4F``(wé"Cқ4&a2\ b 9_צAw:wdr_=#)1 rlPoWG1_G}E.ߠW5JcK^Q}bc|u ~FD"+"xMj"r$u$ R$ܶ5jq[( I;LF{oA{:9]3Q$s܄/;qt#ر4?/쌚4Z%a!MuA3<Ä;[J7hPǠpQLp qB肳^!5Bx<|*<*󈰖fMIiv"0iݘ\,s c&1Ɇ5ƀ@eC5sDOw!LMOƖ*6]K-QiDR"}ͩž_z|sg_og] ;[֎kt^FTTGZoyG;C)QMS h lXI0Qt~^f>f?yJⰹhi(eLG~4J;F"&~}} և/6my %רz'^yqޣʩjw_q^_U Wh:ߐnw}{G޳-c9oSW~qD i9[`}a.b"tJYrS~Yz(^{g;^av<ʚdeW7GJ4RJ8qg 0q5_F tF}fI*6wիbaa ..V(}p1][h%);tCqzg t@8pdShhmoH^TfF͔k}|R~Ւ !Vo=vR4 T>Spk=aui8nwyMxu<$9#ȒJs>Nq5I=y5S򀟛r,]+SCG~S:&몘Murԋ{_x g HFT08z a|]us#z`7'=^J fQD8}G&hy+>p_X:Q lE O4`'=hi;I%$fS n]~[NLhܟgÎ 5T[7xw12Զð=$G3ҭOjmy )o-)L3I 99ke' dovj"6Vy#y_!]/ uE)V>F_7N2yb 3n]A t !vQwt~ 7h{rߵ9T 5Iw)}Izt B'/rcW $.6`!-Hʗ Oz/N_Ys聫3 3 PRa$#h_O^rCVnlaWlO͔ZO~agejX//& Lt;"m_BZ@=.]\Yi?#Qp2l (~=ƿ,UcmI,j7M%/9~QBy֦λs0Dlaا/IO ?@TgxhDcsG *EeZ22>i)K ǟOo&5ws׻ ypST1ʵHO}ͦfܭd- o$ +(ʨ3b}ִiF4GVI.FcR#Df ?T3'׮2Fǜg z~%ׇ"]6/-ɻN#,˦Ȼ4h;ƺrbLEE%~q瀳NQ ,h'Zw%ϜlfD̲l}۰ך]CЙ(`yЮw<2KqQ3^A 7Cr+hFQl"j]R+Ťh hr`psTOTXFb &Uf+H%I'r=&Yl}^啅ѣ*hmΒ*Zw Ξ*Y}f_T,F(ZbPkWh'Aa`_?x)Hn|TUU5lݰN.浕X}*.)nb֡Z?3 RRxK@0wj^ήM^2Ęx3ₔsFƜLvq3=̽\˱r3|x{CA!f[,PH^8Qc$eŗ|MpV{)[Bw7R{"$]ORmNIu$2gwElA| 3nʯ TO '#8^G.wϽ7-ɴkz> endobj 9 0 obj << /Type /Font /Subtype /Type1 /BaseFont /NSJEOL+CMSY7 /FirstChar 48 /LastChar 48 /Widths [329.4] /FontDescriptor 8 0 R >> endobj 10 0 obj << /Length1 1408 /Length2 5973 /Length3 0 /Length 6936 /Filter /FlateDecode >> stream xڍxTS6AzwDJ B$t^E:HG^w H(HQzPA{oe<3>[ؖvAϺHKJ`_R+I?;P"Ql1Cyj,H } #HlNxzx`g^x( !EA]^w*z|%\^Iꏖ{K9@ޱi\ylW9!KZ5Coǯ(q'1kIF|)=?]v7%T鬘y];8W0Vi<3Xe5tY),Y?F dupO!{~*$Ѳ3ko;Y[-NN !/a:8?߰KSWe);S }+i* Zr URoʇȧ>\8f@Uvݨmǡ8N~=gR}[ċMC٥|fAveqxo\}=f {罁x|i>ZOi5e‹M mc̬'-=o`M'@grCm7T\jN{ʮ\ߚҲ'UsԜI)Fr(;]i;熶Y%CZ~30C!&f0Q4KUMGخ׫qs-<J;Za)4w >Y Jnr6+S-UXDߙE=;*[D{ 3s1BPhyB u!\ $ M\z<{ dPk說;޸=]xk8moT^e]#hd$ևyؖԟIPb{"71>RcsJR&J"L8z.}W;{`tO"P:83aչLSK K|oZOωs"Z g%j~o#sAѹc=;ud1O^_]!c6g_ 9a\na>tEeKaFLfLu"0oCk4+˾-exxL;!׋`2a5_6;dGta{ey.rB(dZKE&>{oHgL;R{3uǖ)m٪.Kewr߷p~[2~yl,vaHTU\LAa/&:KK)G|ARv!'w`ZW.ԪC_gȒḵQni 19+m\lP2gn Y7Qں+m U#n܇2m .l5vgNrU27׳w*iV:?riV#Q,GTQ>!-IձIZSR?u4xxs}^=.X:vMjs.THm]VRg;ы_k<~fKc6en[B7x^?;3y!nGkp7 ;9Ͼ 2\> 6-0bJ֖_ _mlpX0E%DQ=>$1b@q'_F_v\ ÈzAY~9hBiD[:NQE=Jq9U>)πNNkGj9)1Ǽ}eD/gK7XT BABdC%ѼVC Ɔb&u.` 2@"f]@Z!kqay'e _؋}J;f//E9,QHPE)ede>LsR;_'$tos֚+|} U^xbG/GVV?oxiO293 s5QL}.2G\ٯRTbs OTl S$u \Dq7/kPl_Bڿ%r>4PDH^=48OqTm5]u<n˭Jm;BTZh? xC1%s^a3YZ&{5臁WcBڟ-d` LTSU[KEdIoWiY!kiX|x+z9+ϔ֤,_Rup1e;)yJ3Gzā4mqR*KxU d{Mհ5_"#ɴepk~3PPբGc]{%Dy_ uI5JZV~Ы%7O? ]Ih;$P*z{F,X鹾}g3WFp=¢`cގg3Sf2E Zk fS4P!mO|4qirٞpx'Z;s_w2W=f%B(˧i>=M1w\L⍞dsZt#>bү7^֚ h]O,~D^8TKyDiuĆo\1o AŧQt"!w!dX'[@^pQvY= 3:tKG(Zi{~nn6aw[~G&Պ)ᶰ$rX{Y#6M,GMl -ڠ/´0)x5ڤ(o1kmQtGő}L:Rn6]]YϦ6ڕ {y E\ps&<וʹڽu'H_ol%"o'x?300@4}e~9=*$bv7m5:nHn\ n#췖] 꺎>]e^6Zƕw3ZH"'S=5_}e1}k0NHb⎞L}5o1 Ie}.2\zbNw' ]H:iDn9d&ޱ^srZ?}jjC@j/YxĴ/LG=TnN> endobj 12 0 obj << /Type /Font /Subtype /Type1 /BaseFont /RXTXAC+CMSY10 /FirstChar 17 /LastChar 17 /Widths [777.8] /FontDescriptor 11 0 R >> endobj 13 0 obj << /Length1 1399 /Length2 6400 /Length3 0 /Length 7351 /Filter /FlateDecode >> stream xڍwTk-H"A {B:H/ґ% !@h{Q@:HQtA^D9{׺we$3gynh"($V$ 끀 PL%2E`ɸ1R!T1pҧ^PH$Iʂd@((7A00@{qо3VW/1( Ѓ`7 c}UWEˊC=Q'E>A 0{1pe>0W`K Gz^x!ap ;D[`#$ ܟ_(历 }H'# 0Ⰲ qD]C!7%! r?O(z {"~qUrH*z:^W"Q>H-G -bDxx`.]d9 HFZqPg_ L}A/%@4 pD8/?=!p߁[d bp'?/pǿ pk@?l/C!|}"j&Z(TQABb!QK%)Q u{ !?_(_$.7?6|wG]m@{ ~ߴS}" /7q޿Gprc`k0G#S X_{k@ Q_A]/&w;\TGBQ_;'*! `0_2॰D%$q5 F)6IХ.Dp|L@6P/ r~ ۿdS(\K]xq Gh}xqyCh ,t.I[{X%ӫtÙ7S,ߎp6=9!Ҵx*߅ ^8oPNR14IFTք<)SgR/>U}|'+M;=~|J%;2v҆:cem6J"H#ƉubP@njH~:_%`HiV8T-K۞%Z_AcG4dzzdqU}XF:@9qP᫤o2 1'@LV ZgyB'h̍ӂݪub'&)IϨ24LOo|em5^v߿,pVDVn&ݛGM߬+hǽ}g[_cCmш{^;~֑wxRi!7-bVhaCɕvhí}MJ_S} Jd S&)Hyvo'3mMĠI`mZHŚ94C0}V&FpU' Sb*bt 0cK:t{{sV)BMOpYCα޽A0-3]ۘ'G> iQB?zJZjUp)?#ZolhX Ș$o;ڜlV6ئ |®&yPV}*Xk*fg1&ϭz 4x_͕BjTyf(6-0sߜ&B[sHĸtzާ ]h͏Q;F]%P3jȸzW4;ޗ{;NFٌ5fx6D$~Lrq\ ! W&xmn1zBfԥ7uom'_;5Y|1x[eg3n4 pۼuONlN*~sbtT ?p ly_=wvuЙ|ƲIb9&uSSY&̇\ȫ a&M8h}h[ye z[kԢ40 kiFqX(g y$P='{=O>Bs$#CEj^airLϜG`I b Lzd= 'G%V[AxMSVH[AJgtVqkUd(9zB2+X֪2m^M<~[j=`O+'m-GdCuf_#sNHq~u~-~\~&& Ozܻ;/E˟"? K_N>s}ݽkGdcgQAO5#okY̳<-'^[T!wo5!_5f7axO=MsfRMlw|1GpAx1 _f/PIyL;]kn`"j,^c\JTFҬ>bNr"n(QVџ)f{^ݒ+Sq\<.oPS Vrd)"#Ga&&^$6TQPč qVXBh1׵ؓ G;@$ɤ26"2^q6c%W -}R| p`|6"ii)=YKnI$xnl{ooJ[UfJRxFȺWr .)K~yd|HݳAM{u"EԋE(VA\H2+:U\h6gG ?- |fey0$&+Zݙfd?`ۦZӮ!7AT,"O,tV%9<':!@2D1}殠^DEtL޶c< 6~J bzv}e3"'A\EġJb9 Nݍڐ q`fV}] wC_~mwlD,Lc;=# c\~/{ӘQvPtV#N>qHך|F~5L/sgݝr;lx<郖~4ORHРDg8[a=[d6YE9tջB:V}-I;Km+O 70PH|z q3QZR-r $h!WJ +? n/(<->>F% zBȧIQXd[Ƣ7'Ji07՚:XpigLJ2yc5J-0ۢHcݸdt j.W\%-ϳ "`{40TRG'&.CJ=冋єޟ@>ο F*VyV`)3VuʯIO42꺳S] [ @k A*5:ku߻{wZXь@Q=ޘE-M)+]ې'A7&]ptS~pHQ-7uG6 t%bh׿X^dl`/a'|m2p/vptdz^JU?--Qid?y~!)XImGY}nEUhu}ͱJR@r*/b-I9xsQٞ9=R>"/~D,S&ܶi1h &-?lj]yT0m3:tN`KsoKV&XdjݱGdv ӏI{>< /w'! eq4g>M3V7*1,{HfB2Hj ,3DozƧB,KerNzYʭkvyQē!N̙nT[>)-=SA}\5 /6ǚ䭷D+ç8Vtu m1Cqo2UH a,.g2 mÔLu쾭i@/`ߒ^'){H氷'} W Liθ68~^8J39Aڽ\|/{?\= # e5] "[^*R5jꆼQp!'?:>4a4QjcHq[[D/{SREތKB?V~wE+63`FcHs.H bIձaZ7%C^I931? `~瓢8a ;˧ $aK>&Vyڒ3V!3ԘGI;IE%Bew00N *8s^[eW^.a{k2{ǟ A*ߛyqR0}A͕H&>^ǔx8hln'=2ϴ^xH׈|:N#S8;.{0L8$X`Q36ɦmwb{",0AۃdZE*Fvff}rˈP^g\FUf嚍,UV4.Y FlCU3Qkj8K ]Sdu٧kyH)Vϥx%Xlu9t3HE|qmtf.7 ]Н D;ӉΒvŨBQybbFAN㜩Oc]%?%'GwPJ,ňwhczQh\1iSTe}% QMWROV^&y˧m!Vizj (i?3j`QWU.h}O0qQ)=0v>H sW/s6ʂڤj{u8*qn?ŮM+}"7;MÙϣmU\ 1^ιb.piœz`P(a7 ;hh*ն+"v-U`kFn!z{J endstream endobj 14 0 obj << /Type /FontDescriptor /FontName /IDOTSH+EURM10 /Flags 4 /FontBBox [-32 -243 1060 720] /Ascent 691 /CapHeight 691 /Descent -189 /ItalicAngle 0 /StemV 78 /XHeight 459 /CharSet (/x/y) /FontFile 13 0 R >> endobj 15 0 obj << /Type /Font /Subtype /Type1 /BaseFont /IDOTSH+EURM10 /FirstChar 120 /LastChar 121 /Widths [526.1 593.9] /FontDescriptor 14 0 R >> endobj 16 0 obj << /F13 9 0 R /F14 12 0 R /F15 15 0 R >> endobj 17 0 obj << /Length 703 /Filter /FlateDecode >> stream xڵUn1WH4nھF {Nn{GvP]~w)'Ż.*~΁gBR!<&wj`K֐Zq[,Yn͉x#QV>7\\ ,&^zw+QUWkg== w9S\ӁNE>9Z*j<(=챝J$.1ٛh`mqj7C5d4ꛆs`tNřXJ\ƹu2 mkohrFffU3z8nqG{ƒ5!Z*3x ^ۀIbg*X9"y^)Z5"Z`;D"\IH1Lf AމfQը bͿD,,oN5Elr 7lU ZfSǚSICHI;J6X}^f@"%@#8?hW"iL3clYs|>0s>Uc3CරMW-Dg~-5P f3y4E(n Xnu/nsT*;Z2MaOLrԴg)R`fo7{՞fBxʲNL̊ ƥL.f/p"޷+pIN͜:/{r/Y0^5 endstream endobj 18 0 obj << /Type /Page /Contents 17 0 R /Resources << /ProcSet [ /PDF /Text ] /Font 16 0 R /ExtGState 3 0 R /ColorSpace << /PCS [/Pattern /DeviceRGB] >> /Pattern 6 0 R >> /MediaBox [ 0 0 595 842] /CropBox [20 756 208.944 847.493] /ArtBox [20 756 208.944 847.493] /Parent 2 0 R >> endobj 1 0 obj << /Type /Ipe /Length 1774 /Filter /FlateDecode >> stream xYn6+aP,LjZt ̪ۦ ڦm!d'`уeg]ĢKK.J:,xiMl 3$[ZCm (&e,_~ZlmosX BJc.R~;EȾ-F&yq ֊Z DV<˒jNyGy% yY6[t9A6vIU 5(l򴏊o.:RA*2$;7_ZBoʁu@S?԰U5lk4z;i]Q &7]Z"Qk-ԣm(MZ zmM5Mk(W/ ì)rb4)Ntgouf1͝jaˡ˛e,JRZ)6Do$X첢f{56qwՠK;F = A-VR"Y)ʌAlyѴL{G URʑ:U<ۉA9S QAnusheNdU͙ؼb XƼ0+hkA4Sk,Oq=moȎn~KY*[ʰGAO !#s 3w8Woܦds>s6ml:ŏ ]~ E$PLip *cmSPWW=2%Rc:CO@R0-]y۫jcx3:+#PQシXWDe}{g5vk6k*4ӵ^Gbv3^@1\׫\պ\[w2ml%G7<߷/e?_M(@Q(>! B>c} \4~m?8Հ&I'EQ SNyo e5<WbA_گ*ᷩ2@btP2z,Uu#yP/\,ڴPZ (z䐇 40AWa:{`V`B;hZv NkS# c6EjE"vK,E h#v *((8ۨ*DQN1sQln&&sBҚ4E\ =Ebfˢ~E}sBtS^W#.SoՀJ57:;H_.໾POX(XBO,^ds Q->A2P]$_ i jonCQHC=g!6gЁ|*mCmXjԸ|1 r43qp[y,bMy+cGi" F;!f CL" P|\šlCBC% L~ZSZ5(cT CO0lpJa( OSf' EPnѝ2qzWvP^';:tP`!KbQ:S48EsەM C J3t,]"8tvgPլw#n4ҧh$TyoHp=hGe3Il[,giW[4Dz=͍dۨZIɪMbE^vI O!hAș$)!coE~/ endstream endobj 2 0 obj << /Type /Pages /Count 1 /Kids [ 18 0 R ] >> endobj 19 0 obj << /Type /Catalog /Pages 2 0 R >> endobj 20 0 obj << /Creator (Ipe 7.0.10) /Producer (Ipe 7.0.10) /CreationDate (D:20100409151942) /ModDate (D:20100409153133) >> endobj xref 0 21 0000000000 00000 f 0000024330 00000 n 0000026188 00000 n 0000000009 00000 n 0000000059 00000 n 0000000305 00000 n 0000000551 00000 n 0000000598 00000 n 0000007585 00000 n 0000007806 00000 n 0000007944 00000 n 0000014993 00000 n 0000015223 00000 n 0000015364 00000 n 0000022828 00000 n 0000023048 00000 n 0000023197 00000 n 0000023254 00000 n 0000024030 00000 n 0000026248 00000 n 0000026298 00000 n trailer << /Size 21 /Root 19 0 R /Info 20 0 R >> startxref 26426 %%EOF trunk-2018.02b/doc/sphinx/fig/cell-shear-aabb.png000066400000000000000000000253071324306050200214300ustar00rootroot00000000000000PNG  IHDR?LЮ pHYsqF vpAgo3t)IDATxpe/&q!$BH@B dBXY=D@u;r[[U֒,ZzkreImrU5Y r\ I s|w*6'3&äg駟sD"ЈH`0 vtttttDh4*{h\ ""okkkkkk,mLj`KKKKK%ocDDD.@ѷ^"""S8ò'"""% mcIeL& WÕ|>ϗꕱX,577777#=JnsR'""x衇zݻw޽[|iCIOize( B9sGyGz뭷zKV<L'SBDDڴiӦMsrrrrr6nܸqFEjad0,d,cׯ^{k׮]v w`$'= q֬Yf͚#G9ri[+OC""M6y^+{IEL :6'"Ri̚5k֬Y':^3B.ڌq]?@C[_֜dF?t&Xdɒ%K&~74,c|pQL (k8أ <M&l%FQ300000 {](HŃ\׋5Nk@FZzիsssssu!wE/^>)?M6_W^ mbC^K"~<Wx<xౘē, 38MPőo\m=n[_""AtѣG6/8طa'Fu$3x b+Z`Q2/ >DD$AÇIPUjhhhhh+1|,{؛Q:sIHp.۷oO74444446666669s̙3+HB᠎ЫOHd}b 9g?7'cAs ߿;wܹ˗/_N******y̫CJ0~A(HS)+OXU|n[_"7NHzzzzzz;vر//+RK.]ti:ùGTM &$O&0IBo} uԊNb<'1;wc񞘩Lz8&{8L Ib%,DDDA<'1:DDDx<Q*_%Ok׮]v-^""cRͭz뭷={ٳ%%%%%%wywĈܰoo^ "x *۷od'{H]wu˕PP{;""0^Qn߾}L(5 %OgX '"u0^5',T0`wF x< D+u`SeIOa;}\b8J9!yRv̺:I<*X '"0^5',tuTq6ᙑ<*X '"0^(3tuғoq/{tucDDD.T 3H$O~tucDDDb ɐ~'Qi]<SsJux8͛u$ĭ䉈,⼚dg'e[9z&$Fƽ]<EXsgf8Rj:eTꤧhreeeQWo_hen.]]xb,=<<<<8^[]uuc34MgMM5'#$FgΜ932r 7ppEEE_eee姟c=7x㧟V 0$'jN&wuS4yt9ӓՙ6с̙3g::0H KKxJIѧYUUUU~?~1+aX<$ɪ93zxĉ+W\9}Z4onGD8`wttt5ߜݍg[@zɲqNz&Oǎ.[vvrX{{2m•VB "LW^x)Rt">^Jl8B@VW'=E'Rb pȌ^'G< @s3@@N՜\ӧ#G~'JK۷o|_}6}ڳgϞ 0cg$t Ŵd/갍RD#ܠmp}B(B@bgDBBFc'^CCh"睝ɓ--ĉMMĿߟHjZѣտ3?ƍ(h4;2^CCCCqOi$D"rבɓC= Bh4F+qH\~Q*onnnnnD΄aushvi4mʔRM;IVD4mD"aM{cPņYqΓ+5g*01pƃ.Dr[s}w]*/q:j~LWd466666b\iw*6DDj1>;22::: P(x>'3"Iɓ,g$2X(wHל6lذ*3+ܽΌIͮNzL g(:P:6V-{d2cǎ/Wٰ&^GUKjvuc@Hp7`8ۖDD2Usڿٳglkf+3:]<9vνfl˗o]9QJa $<]ؚS( %3f̘~,{/x6 a^5'`!<8CBŠ_~NW^z͛7^3&^՘$n쩹]:1yr\<9ʌ֍W,^vƲKW'=&O<`0Xy"2W_}uʔ)S &dq:i?'WI-?00095ŋ/d|:'1bNzLl̙3g왹; B'rckNpٳ۶1^ɕMfwo.^x1Nl^z=̨9!a+&9O I:1yK.]J~5yp&ッ([fԜ~J WHbRNPsy9lGvƚ{Cd$q֜ɓaxaaa(A`F ndM<ط'dI'O<ٍy5' +O@"+֟0ʼɾ]<'jN&}"!3Rvucd{<Q6̮9XyR<]<d/ٕ5"Ya>yW}ɓaڸ MK^m7uԩKv`e 0m\z%Ad^cgtucdc^[]yف5'L1#Y:L{p=Ɛ<Oj;B݌9  uEʓ Ķ Xh#Ἒ0y1L'|Wۑ]UsPQu' ^qw,[fހ:1y1I'|N.";w.!70J'c2^Ʌxuի7hI`F'uucd{UUUU~&`g}I?Wr!^;w܊ 9'C39Y0 AEEEJ$jN"JdWӦSf<74 |ac>+O25' ,3f̘>7IyDLlO𕕽Ds,T9AzG=^mFcvucY S%!'Q$bwygܻ˗ͻ{:1yr.{Y=aKzccccWo޼ysqy5'`8x7=KfvL0^YoϞ={Μ-/7#qvW'=&OpɘAC%"3Ys1^Y񪧧|ӦMFG+Q${ [ aH͚v+߿m۶m^]<9P( B8UYs"_s1^g<^?_q熮NzLS*ijyvDP$b2gnnn-ׯ_?e ]<98gO 9Us1^ [rhhhhB3RtucX{%̠(  d/٫$b2M6׹] 'kmmmmmc$Kd'$.[s1^eCW=c?bf'On9'WHd/p֋05'R}kNzWWSLu+>A3f^ +.wC:ij^M23jNrW%ƫ'?ٻwΜ9s4-Dnʓ`Lv67^.[WՑʌ9aފ I U:СC^|Br[W'=V\G<{YEoMd/fԜpXz&WfWsgW'=&O./=v6세"a?a}1@jم3,ƫ>G&'"vQ0:pʴ=5'=+}b1Ԅ̜NzLO+\p0vZe/cp^SSSSSp8J:w֜dkޕo9ֶn9U'vuv0b_NmߊwG7D%@1GF3>Ց#G WɓH$20F^k׮=XCCIIIX~~~~^$z.M2̡4DZ|<ѤxFLbIbB#c裯:qĉSN?/*xrMӴxnzlٲeĬҼɬ_jڹsmmr9)D"H$d/8W|ΜC͛7o\KO?C4!իWcx<O JKM69B|MQQQQu5~VTTT44upaaa[_֭~ldk]|'gsòbળSVSXI$iՅ[RRRx…Q] ?N[n| 6l7 IۺjML}}w=o_^u͘>wu;穀䉈H jN߾h2 #3!m2T̙ӅwþS'3A7]%ъy=d՜􉑾zEHX1R '9ٽXaBֲb =yT<I] ]z{.]д@ LeմK͹I]8 Q2y""f25' xo*,<}_zk G:e]֬Yf͚Ç>|xѢE-{+VXU$4Vn5`ҷ'""iWs8IA^PQν瞻ZGeENx;K/K//\p…>>8t [|_k|CCMMݚ55T,uH|άwu*)))))? x<={ٳ/_|{{~g^i DŨa\#?""Jכo>1}z**>'kkC77'5C2a7xʦY ?O>_~嗟|'||>$Xx=LM.䉈Ro͜a^ݛ|lְcnKL:]p… dTD"H mZjժUOΰ.s?ߊ}>Nv99lGDd)#9a(/*$jNL'RK3  V&WPPPPPe˖-[A3o&*=H818ؗr"a*+++++۳gϞ={iw>$\s'N'o1y""5'L"Fre$"{X\vKO^MbBLUoé^҂ ,Xcǎ;v|iڎ69}a06u>%Ztw]g&׍m9gΜ9slٲe˖!a2=<Έ|7|7ܰaÆ NYx<g:$N:ugVs_z毿ut0%TFLH2Sv{{<ϧ_%ڷoKn"$1%1RN"H$b7c?~q<}eo"u\YYVǎr[s:tx_w?»fι׷^cՙ::::*|^ JzgxٳgϖH]8&"Arrrrrr-Zh"HedcLT?,{):######L^6efҕ'`P([`=a WMsN}Wd6|((((((@vbH2JD=JVbDd)JD=Jr1y"2dQ$SLL T21:<aW',4.W '"""I`Dj>577777^""{`DRv&"J'"}ِ6^"""{`DRHXy""J'"P kNDDuXs""dvW:xe p8w:Q1j;;;;;;}WWWWWxMAX_v qɚFJ(U {YHu%b^$+~GEROE4FWD"ew>+2})<ef`````X 9)F8dDfOk iCCPICC ProfileHWTSI۞{oP.HRBtl$@1숨Z e, Z"}ADEYuMus=ޜ<<3sy/}lX**Jb?}6'] i)wהt/ tN*ĵD yåR o.B&1Nĭ>>Df%I@I:"@=>ơ{`F?&:lvw<9 # BvRbFdG *+=%"h s~5c ԈS>)~F7)+r JI Cm(an,>)Q)G>r1 VIό@;ś'gN[SqYbih¹\ K8RSc֔f&_>s‰s<ɖ 7DQil /e0[`8V369mĻQkI~ K0Uj&sN$s=wS&|z )/l"A,X4"pKrBe`/8ip\A'A/\1A!bX 3!sp$G,G"HRT"u/)rB"b(:1:uFh.D%hnFK*0ڀ^@=ha%`!XIXVUaX3ֆa#8KxqQ8n n%n Wknq#x ^owųI|| ~?$?G ; d2&nQyBa0J$5DwbM󉻈AIdK'őD\R ,4&$g$*"Ǖ˖"W#,wCnPnL^YD]>R>Y~||%o \ J)\QWHV!}  ŘEH))uGTՊʢrj7"Sqbb JrJJ>JlJJzFi6!ʩʛ*_U~BT1VST\Ta4CC[K] t:L/wGTUTUUTUϨajj,5j>3yջkLihLܦ٨P eTk%iin8 vO6^]ݮ=#٥sQgXWMK7YXMCOWwNCdVƈ~~~~AAQΆņ-#O_>{FrFF|FmFMc7?70a2y`J14]bZezˌ`lb۬5w0盗߰@---fgͨkIdZfZRckhjq3lZh]c}FfMM[m-;*&<=whZ8:9J;;U8:ӝC79_qxr9Uz/7Kngͪ5nvt`x{d{Vy>2zz4c&33_y[{KOzqYs -S+{o$!`Y@|`P^êcvbvk9(",99hm !!CMBF +{n<-8`ĻH-L2ZDE)7sފycbMqĸq?aA Mf-HkpљŊًOcfأ g'%׋[x''mO{KAur`)!)Rƅ1£S"Q5M7-+Kl!-q]cɈ$H?I_$=4c]FGfy懥KOd)gڳͳ7f?yngYrk`\LXٲpUުkȯIY{unQkc6XP>5_߻m 6[pк&Φk?T[J*z{"墜Xj}ޝ;3v)m5}]e=G++6VݽkO^{?SPPe\URMά~Z]ur@t6Ω-C/8yHSe}Qc/~-'OjkIɂ!aujf擿Yv3g?wv\ι. ,nq[a.]b+^=uZu 'wdcG M.]v{v_{-֭=s{nGݾӻ»eAC%Ua>Ǿ3# |`SӒgz>?=?bc*Yկy>2odMo5GRߍ/ǶO1-L\KנSl {`Cxs:aG&0d;fÓ @%?c`~Ln"]jgMYz&@cU_dp^3[iTXtXML:com.adobe.xmp 830 358 `@IDATx \u_3 rȡ9YOmJkնpv~jUVkvn[zfa (3{ 8x`{|Vr``L 0& ^e1&`L 0&$pC`L 0&`L':tw|ǰb/7gӹuX=U"ӒUWW㨯ϸlAAAADDD{NW8ѻwo؟FR 0&8Cty] ctDCEʋd(ďEux7uuuP __6>:jiiSp1f9{3O tWH`L 0'uGXz3 .~mAK+>=^g?_V!W'˄Pz ެ ́P ſ n,?*OF)O{cL 0&>+>N"mEk푧?br(\Kƒ5ؽe& +=.(! Ñ#GSOxҷ^׻e磻8&`L ,i Zs IC›idq]Bkzxz&͕StWmӏc`L 0&+>"B>}J3'#'yjVGL3y\K}ϥ13&`$31xΗǂzj3yvgW|wƾ+q_&`LGUMYn.8u&`L 0&`'#x/ luYYAP3ϮbL 0&/IIIJ6*.ͮsqb3쮏w7^`L Y ^OskiQ}"^ _|{ᾔڣ\u "ONO♯\oi4-O7uXE*,1R*Ĥ #@^?O)v=Q|EJl`s,ۛCz5bL 0&5gw[BIJ'Zk?fCIf|{;MS/ _xŠ7vtqEf59>$K-9?Oϸ޹zjE֢W5?iz@txZ-Afi6b?`y 0&`GGa~SN5r`JVۅs䠵YIs3P:Wc !ŧ25!*TSZy#JYGpPz"p Ǻos(hH\{=/,?~ܗ*_ ֧<%'F / Pj_4`ܜGZfAj?W%巋+W:uu>`L 0&.X=_ct" "=Dž#.WԂZf!>NÖ"&x/^(H|7W axʊF4`C˹kp^#M;?9 Ni0 H+Y)/~+n[HJ ,X~/ W&B@ex[(U k bH>t pk2~GC5xja (mb@(癿\FfF|+qn=cZ|-}~rK9ZS]ֹ??Ra?@ _7i n}za܋[Jab\{F0彿#Z\=f l yU&@\GYŸ䚫-؂VP8=)<2~'LeXC|sbb\D˷\/_lˣzDuMioՂ)F\"@%ᑙAk9sH>3eZB䲓F"8$޼%PB"aV6wf0)N?D {Np|Av%z[D@zш/.^ŋ?C#ȟ[kU/ۻy1l)I^C>Ş2쯪溃(++_XcĖ2+y(_O -Ҭy+dsO=}#ŷ>a'MqS{JJz|myGaj8ؒ71xcUʰ7c8^_B8BVQk;rrhOॏFY\Vc˿_'%xioDZثWc~*)R$n>~FuJώ 0&e%/n.ՈYv]ֶSqdӃ|Lq|~Zr!4쮻ؽ~b.Ij3oDy=8)\ª>JҗڢjW $l}`,J-4+Eʽ+& %'O^{Ai()j=|^T(Q§Yʓ:QxP/A_I&bɿ% 6i_fg 5 .U.ڝ@v#Gd=톓'BTT|, سb!M"x5Y2~1&E \dBy P,xڴiF>zIUzk+||\H)HXOV-K!x=HI˾+B͏kMcoj؈Et>]R4o"Er Bù;}D"0kp rچR^j/Di`5qZGD^"c됋׉G.[daaܦF>8d` r?[:e]Qn%uLHKCZ&{;pWά6ʏH\(C⺻0Eq`L 0&pPD~D@Etq'\X,/YB<"!JΏOQN!ߒ5fh2~n6.e*Rk 4C3agy3RiA .nЌ8u7\ٰoA#7<T 8EJV2? Dk(v9m5?Gn[qЄ8W oDe}45 [3Eԗnme%.EJ}"㲇k'a5q5I:_4Ar~=Zx=v=p[QG!$ >sH*<Vj' #(_ԚLNX3$W_}2Є+R|GIN;uuR]<Ղ&ZSLC#ߤ="&\hAГu245"PڜT +JS卢0@/,pBqpZ?|&Y.NJW`W9n8@_[?rsN\yߞT53&``$ю$UsmO[^N E$5n5ȳ˩hU]RCGHMKEFhQ ƒ0&M)!¢49;/ޚצ g‹Zq:[# & hM3`FX|@ܤvu Lr $K E8,JQ;}% B jvmbHw-zOMBqMW!&,B}߇_b^Tמjs~&N?mOJBi#1 ?-^.e(C.E5Vm#> {{n|SN>NE;)Q?)JX%rZpzߪW1{h-MXS^Sh)vL 0&/EvaW-)~)x:m&v<)_ A܌)HezQr<< $!@=@#/wIX9R˟%>FOJ.&8%qķ]:ks*Gt\gA  p)Vc','+A GE;Uorh0hm\_|YۓȲ'*v7P_P<{4ABuZ-61^F:Hu;}:mz"h-C |ǟ9Y|[fBA8eɖse)kD?Oʔ6w3Ì/}4\4^$w4u z60Ng \Gq6än`6JÇRCՏZv &y|gj*_{ޤ{ؾ;ޟ}Hl>eP>m $\Bp!RUDڝ/bt_|~cȠw~"8`O^+z*n2EBHuі!*8C/?ѧ{qť^]gK*WG2&`g@JVf"xN~7g!\'O O5,-!O.+Rm2).BiP=P"}kVDSԙ^4f,qU驠Y|U[ 42t,C{*t]u6!}@Shz,Ǘ?ҶbץQ"h;Džv`<6lJVRRgإKQzLXd V{P6Z͘ӷ*Pz]{* \]X~y/C)3M?N|E9b%>4e^7b?9mbJ綨]Sr >}ci( Yb 0>5?bJWCCN_=BʓQɇ߶8cȁ'LǏx<SEedBԿFJң//~yk <>%M/>Ӵ@GN֓>bLB؎,WAS@Wh"փ3fJԹ٫W`#t/=CM#ա{f#Ɏ 0&8 ``PG6a`\0*Za;/ugk--~|KIWWbԈ$뚑y43I1ELch {/֢V uhkOH/~[(e Gi'1H!RaG5֋:X*RAoD^O4{Q'4 >@*(OΨ]R*uU5X>QT>18ZqiRo _h-nB^@!FLR;P"uXY(J&S}XC ݿH.cEv[]L/Dʇ?#+hmrΧm0"hZS$ kFOйã'ET[ѣj<; dS}-N[@R[QOSVu&04OPL_(*២ G>M&F  K=t_S1HzD'NЖ/)y.i-FX6\ DѺ$*:묨8 }TքSěrRȍ0Bpq9~O6QTgRQi#P_{`eZǬ}݋aVI28X&j'y#D9R&`LGBVO"-|8E Kl{x,Z pgő)e²#I\wQRxhpD\wJX͒tXh5@bK: @i|lӼ$US".m`#4ɧF#ڙ@. [Kԙ-<<iHI 6]e65TgQQ;3np]v@ VX}Wg]kL 0&I@H4w<ئx#v!t 8EU֏EQ:>>}S.dQV=LlE4fk. NR(fSRnwlx}d$]``[Hk_i^uGtj7f孳*}&`L' i|~N n a^:Io :~.`L 0&ppN}K*}w7w:Og*c9o z2(9Տf=*W11&`gKt[AG./XCnB[4mnx06ؼr){$Ƒ0&`=JsJ? $E#?G~zMP-ڂ+C78 Ǯ V F3L;*{'% 0&8(> ĩFGZ͙( q ݏQuMR[p LTGvzM_,&x n^O-U+|s<&`L +l7Q'gQQZG#ifJ9=BЏv-R[pXLL ѻ7ffa]y_·QYY}<]b9O,ƞV| 0&8lؽԑaסZ|u:څ:&ď~qG25ӌ\-r0VǎChh(mR!8 􄅅Aps嘧+*ݻUKC1&`g@0uV~(WvM4ݍ݅O¬nG ՒP/ VxBv8b4N︫O/5`L ]Ld`L 0&`L xBו'Vv\QkL 0&`L \*>0UfL 0&`L˩nΞ 0&`L 0&p>pk9 ygL 0&`L 8`Ǒ3&`L 0&pA`炬V.`L 0&`XqL 0&`L \X `L 0&#oވ`L 0&`L x׷Z8"&`L 0&7x|qL 0&`L 05>^C1&`L 0&p`\`L 0&`^#pVY,l6yN9"&`L 0&x]qPz=DGG>>>n &`L 0&@w t7,VXzz^zORzX6N`L 0&@:teNYVˊK| 0&`L 0o8kS݄/)=[x`L 0&`Κ#2Sxc1`L 0&`$uөnj!XQI7`L 0&`=EǧrL 0&`L 3nv>FҔ$%%a̘1:!o1&`L 0&paթnQ\\,y끄#۷-mQ<| 0&`L 0_6+>¹n:;9ÇmP]]7 VYd+o4#rߎMY0I:4`;]b"}SZ"q 'MΩ$ \oYptts=8;`vk0f0ߝ㮤)Ԁ9_y,;3uAtZx܌R7O/D7ww#)`L t@k|Ǔ1MUznv1¦p#~HXecdBUujjM7Ii9U{+C 3KQ߹֒qBn@&Ւ;׃sA `1TLfi8' =XxȒZmjwh~> yv¨en[?;T`L \IŧFaÆuH^lBϸk:xӵLr>Gf0& 1W8q\uGM70]mQa}c1v M2bWwIamㆈ6'Dhca&"鐐v .O0blX`h4`lZ|ͼ碡Nrٺ LDe/ƶ"7>C5 #c[.j=?50s, rK设 b:X ف EL߭G^a>FTRKōWV؁<F%ssYa8aC=|zIQe,l@p*14TPsܰu$E&I‰zbO q%Q\S .Oǥ/>C f@h PbX<)ӂwyi9hP=9TTY&r*v@juLr* \g pTC.D 밈ʛ8bq n.3r *s>2V#}2KC}))),5/¤ȤT61ꇦJa=ᖱmM8Jy.T5#?^L772Ɗ0C[؆*ĥObjky6> ~3 5aI!h(چ?7c`Ҡ& !I1*(Ql$at/S$ȷZam؏9`H~l˯q1P$ AzXPq Al^*a0 OEB!CJ6 CrU$,prnTIH\/X,"OXIJ͍`.DSuD Hp*B\JxKfZL*(?1_ң"'٤z'!5m"M(~#VbM4RNU kG&}0$x70pAvp&Rzt "&H; y :P܊p*߁MJ&2(>!8oJcMCvm+MC\vS9KoCV0)κHˀBU{7;%4VDP`-8ZV*v4*CjdI:;Զ-V EJ>" 0)_ڍ8ruҎ&4 v'[Y;C-#5ʸz^.L&yۦPẾDȶ՗MS9VEJ Г2u`?%%b*0.\3)F[' S݁ncnS XCI1J%ǍeB5sBw!Gp2wQRd37dK5.Ů)Xѥ\8>gL 0&z*h"]\XpnVig"ڵ ;v˥.#^b*++=J#1GQ17-jq%`ɴLd`h҄`QiK~ǎ)AZy=LM9@ GRQ5J&9VC;&RHdؽ=d=hq|fJcTB䙓p!Ҧ C-E|izmTDe;U *~ZNeR_c/+GEhaϩ5Gd KyidӚ#-ULKsv!\ħmPUJݔ}kXh$z( CI.ѹ,ZpW"uG)m&]ͮIu`IHII`4nhtX I",RHI WHݵHMRt1M/a:CImҕb'AX3366I1騾đZ!Mk,F$ad$ I +FL7ڍM KEv?O~WD1tu|刡TQK/8h3?.JCؕ`| 0&1>45M:U\XbfL 0&sZQTWW#//:Iׯ4ms5;߈ɘC0)szq`gYhFT]0LZ]gtz)ph&-5 55I-cǺSON)`L 0&p^=Y/HDDvaqT~io3gl|h]a} ^RzDpMsXPKV7x(F]jRu><^|iC nPY~q}y1&`L 0&ppZ~~6** v4 N(=wuY5`L 0&`=B༘IɅsNo| 0&`L 0M_`L 0&Yブ?NI6`L 0&`LKn4u~SJ)5`L 0&@wx]T Sa 6&`L 0&`&u K qrx 1&`L 0&e^W|G+uԹ->]an4aX^_I马jEXXXWdL 0&`L  x]immlFcc##$,)JEEEQ'OQCܩ w9:rG»u}թJmV<׺UB B-*5\<㙍늏Sig݅(Ÿ8q]U|<#eX /=6 }5rkdK1R sOa/!+F+F<L@rз6Vewۚ}[JKdH[NC-K]{޶b4IB-j}JP!"&N`)X1aݤgziг¯#ǽ~qo=/GvfƻSa Pz/‹xzYʦ5q-ٝ 5>8w*A̬XN7[`ÌG&X̭ȡ)PiL.glZo]_ Ș {}"A_ yLdWz_fR_X/3}1gvr@IDATq<2#^5} rN>^1JT^#]qۊh?Ʈ0 }سz9(?ZCԦ>?Ȗk݅9etfEikrg ,Y X0K xd (ԼgZo5ԕz4 :6c|,]WjƖsb/.U=s[T30]PT^!hը_nJ lq8OT)|l/ ZDU#%L^Ӿۊ=-%j{jzm %MPj4Zf>;z%\,NB`Ćziu&nKt-TujkL6!mnjmLaHu#9wDcֵSv(tiW{1N|[c~']t&&Wv&bMaHR-j_}-$6mm~j@Pјp">w4Z# O>: J~J#>>CmЊ~[Ba-JPmK~j>[}bKKή }_g \>ɩ_|%{4y  -uXեg&n ߹}zӺ—I  FDhlR3?wmV*⃟:?YBt"i89ro?i8ϞJ,{erꁌ7c3cpɄ56;#{=G훶g2Hz==Nq"6i{\c?Wb-EX>$?5,bh3| )N;- Akwlo5d|iL4h Xv$ cR2̥95XcQl[ƳSxE\ɏ[ģS!V'W:'Pӧ ( ax%aTᄑ9kc3^(r>O'dRCu'א#D(L3L)ᆙxQK־+\P>LJCiAƇb|^$\T6Ou dGoҬ؜K()ή|yw㣏>j3ht%O^huMA$!s "t5Y2V1>W O8όis7 rC"Ajg`C.Ց&v GhlX`/2Zz_- >)\ c%Z&<)$ϨNπ~فuq8%ݪ%˨BaܽxlhK s0i0Qf>$FYDu<6ۼ6H31"itr8nyq\A5"dgmh)29fRe!aQaj޾ ߣz bȥ?&k܍tn"ۑ#ʂU9(&eW%(8 rI)PFͿ=HLشp9*m E>bGUbob+<8ip9+%ҹQ,-{'_#zIy'qHEr7`oT$ɔac'"ٿU^Hc̚Cֵ;-vb˃3E9jpE%kʑ*ԉSMP:\na ^za D|3+*%8:6ZihcU31,5R+[AvC&܎qRC񯜫H M LnƋk1w15)嫷FgCz-W!.%Jl+z[.v胘ws&= [ )ECːqZS36x{v4's1kx7S[f36.q潋uo%aHB۲hރyq(w_ 䶽&ѮPaL[oņ21TmND]?{kU%>4ܓw!vypQb1ok4eD-TYo>ѳީOiމ1ٱ)oFSߤ[OaJIø=d:P5!BPa>AN-]aCPV%b,}X:ԛ#- %Ii\94I3|:qÀї892w$#r^YPIb僠Daȧx.(x!.R{!Ln{93mC.3+2X %Sx҃0QV,Zӭwf RB̓^r 7oɡZi6UԈ~Ya,/FX1< jC)ՉɳޱgR|MgU) 1f PgeGuNy,U,3K32`ュ: yR;MDrkb1{<̛$!3Q6X4&'-jDzsGD#Z1b/UNJJo2 yK$ IA"{e.FE`#Btt , .:h9]WPS1v,@P$MıÝ)Gr܄ДqxՓ4klqJ,&cXXMxߺ$'CQBl; &Ћ}NaL:RkīsfFη٨ :ۛik9VUkSE_)'/?&v"~*b"FQifmmjW(rzDAu=v #95m3|GX\@+K6_ۄM<'NR{"Rz%KԶijA=Qѷ}Z-0JkH`DZtN5z9kʿ+ ;مވz& gs/߃(\eH@B(0j+= 落˟^zm8fB[kkhiMZ,.fZARrjb鿞wO#B섧mKr .=].+d兰l:a[c%)>F緺]}ޒUFZ;$8<-gTG`0U9ŋ/g.5gN%8W}nY1jSbPpD $Jj1E׿ע2#%ݛ!nRs6=+ᓀ& MwE^%b1e2_yLzNv YN`o!9[ Ÿܩ߾ R&E3WQPtS=tݴд26G'<@}.Q#' 0il~h޷AJE/>",FG<0q4"Dހ2jkGtx'ۆo&'xKc8y8VӘ?]x@kLrEH]H6ZMkF6EH\m+GW|2ޕUYMPf ^2+i)KiXiZjj,6wVXj$h" ;ps".3823|?bӱ&*JdYu6_D1ǩgޏ<2>Z\M˝X}o=WkӔʼ h7amU8;ב+!c[WDTZ}"zIQkwJ_;bFu{}@ӳ;A!3vBjC?7AL'wE"l4rׯ7y0%2loZ@Xl9?(G}lF:Fu*K1Td5Ɯ01m#O#G( %\wՄwJyb%խ3ߵ}4DSv=(hg!i.B7˿hϰnm})$N5)oajRvc]600~ˉɨSk[Co֝h|asQ7O ʦ< R uxy0=AFed6/Kz?[)slcw!Iж菅dԓ?H{HwPخ NJ=jzY7^F}=16#ɭ|DrhfW79+iu7  YqTG0TK)صӚf,.,hfo)),,>ūO6J}8v i~ʣ0)P艛S_jnۿhgOG Y$8w)=+Bt2N?]8ndkyz3~ v'p?QPWSecQiqPzj=}14Х_d$!:㳨+vl;vm|X_?W5ď lHxT} cs06O(qp X딸qԟ#a2&%æ\|?Cg7~6>sxJe5RUKI[cpsٙ`.&G%h.MB˖-Ŭ,ΝC\Fwi4mjf`H*ˣ_ RWa(2>W9#%Uxc-7l ?k?`B ˕OEi aMu+˨jQ\Kh;BpWyذK !LÓ Jf~oTY往m:}be"sYa .×f;e'a,ҧRWsuZUQuZ~7c_]\#-늿]ɘbk*D,\øtP~xR]PzT6~|үl[0Z328ڴ^ľ'MU۵:GIջQ[p`^CuL݄|:!UݪR\z'UVrrQ5ki߬55U?T)\U*Nեv-eUޕc_E׹08IV\ӕ\֩g_V*CtY.D1`U9ugG&\gÛ"'Njcԯ̯%n)&*p=j-kyQnZEm5#Nu9mߘKEU*CѾ1Q6~Y~ )^[-hYQ^eQxׁ*S"_OfRa5*ѮSYf푗GF5$"#D@" U״M[!Ӯ1:zpcK"ݚ4 Eè\r*ԭɵD@" m5ljpff&-mS2#v}?9VFAޘ$DB >U 7qznnn}V,PAijxC|#*LC"P9d{Nwb(Y7Re}S)n%*#H$D@" C@D*D$?w2=ɥo=nXve};杕̭Tߒ&M6--Y7 YַHAI6! rD@" H$톀4uJ6̏T߆*t" -[t5θ5M,뛦($#UD@j| ~*LQ"` -!#˺qYwNYn9/nH$D@" H$8]ѴtC"ٗH$D@"p# 5/7I# %k]w62!ӔH$D@"p' `*w 2F1njs# )j5G+H$"PHŋEWEc#1P:uP 5nO{)܌C$H$D!gpǎ@3 %o%7/hݦ:u-jAm]ԡ@$܍~n 'rr2/@4ꊊLcX. ս_" H$-AɠlJtl~$w&|=c'`goƍ=Q*ԴSOd\gFpvnfUWR P}~D@" fZc-w619>S@K7 !~`{yMI.Դ[ >B#}꭫ zҠ %gw"r/;I͏5D@" Hnb}6 G 0B^A}E obk517JL.g]VVk>EDf8u8.ݟm^e:ƆkծCF<m!S>Nv6{^)_L Pݹo /lK~\e]-چEկ|N*u縚o2ܫ vFv;v.l8 mtD'8wYѢ㏋?}i!^&-Ɓ"ɗڞ$wD@+)nurԴ {zQ,1 _εZ)6|rczON0 p Ḏ2[7 ֭9]Nzpz[l7#w鏁;Ň;á8:::aP϶&ߞرbj }h{%^1v O 8tMas  gM"VsR?׿_B]1睌Zy'Z+yu8y71 Ɲ ܵ]=_>3ZNuqbTשQ=^{CtqQX3 v0,PBOqUE)G:킀hZ8pTT䀺u,/^(\B!a}.UN :IڋXi06$m@ޣp int-OC`C&=swVl4n>/po ?V\&bf$cRwNܗǰ M GEQ [E>ca{ѿ1qlH΅kƫ6 R.֙spX_J;-6>.동Eϻ8ۢo3 [;c\o3W:9]FӷN"ocl[F".r-}0ǧ2i s3ܤ~LBZ 1c_q(O !-(,aRE=Pk'k5%h-T-D P <c_;Ѡ#yf[aXOhKAcؼisԕoi!WQU360t7ܨ Q @fa`\iWZ+#,ݚ x xu7ba޿bqB[/ @{{y #B&}Z{G,$ށJnʞ(ـjXMt)45:se@ϒ%Gi{g#oXEL>F.,ϦT_ ʅ6{Z5%eȨ[hN k [lo™eX,ߜ"ҵ0g'CI~vC0if~iZJI-E}Gr,O@H>+[f“$L?3P/İSIn]lض 5m"ߥTbZ(,aZIs!-FZsoǰP8Bpu[ |lKp tRyYelxxv`M]u f)Ƹ ~UWch횇s3 G1gS0;--cg)9"iWS{jNߍ%'bE: GQk?ǽn߼x/r_Ҍ,0zO˥(P Oc?C"X(K;l8^G&r.͖|hlu- .)ԟ?HC ~ [^*OIepg|qPү=|^3E K񬖵hƶ'xh#2׉=}ʽ3rgi%7?5/a>٘HAB9ZEi~FN^; !lQpbo&bI#xJ_f#)J]d\Kc/awJ3w1Y"ꙶ IӭQᥧB[C\yR s_7Ц`CZꝣ^R Ma>OE a-GpdF:!SGk>f͋2Y{i ƞ ݱ%fXk61vBop`>.pVH^9iGu1sLsx1*~Č-? ys9G? `L ]MZOB._>o^L aTB=`Wh 7!ÇX!Z_ }&7 A~X~]!Th|2K%[t>`J$m bmw ?g` r +~JajK~A4~xk:|lӬ+!x9V4ӇB=p; zkW nRNJ}E0 ܦ}Y.b^r@M?(&]%f_xمHT/o/nZ`.fbi>^%`Wi1x!m = ;YICv>z-=a^c_u4ÛUfQrmp(J'](?e:{ڹx@6En¢#χl7?`[Ǚⱻ0vNClV˚Ӈ uǠG7ĭ02*8RBmAi9֊fm|N=B⡘}rЀgHl9]7iHJ:r;1]`I0Vǿk_{r,d"vsn\-lH(y+ATp#t`?|}.-?mؿC|3^08npR[@ "sӄ3~@Nb#|~v &R 0/^}HV@L3&~"qN8tp>KsW~)TMڣ`Ӵ\( 3㑚爐/rQs0Bihy@y)Z;U̓>٣>o{ xd Qaw+<`ĒelOa) +FߟƉ#8덗/> ʥ]n12}`3y=^iB= иish6qAOvB#.;4h{tu2}_ړVꢾ^ؚaAmD!1 gEkM1qL U߻*-&4{!8λ"<v Sƶkx ;NBϵ 6OhJֆ'hL[2߄LawM绡K{0KG]QLrx);syʔLeC AQ! uMTEL|o `W6 5g x%!%zjJOL5 ­M[(Q)6}/O6=ᡸ+F3ÁͬcCj9Cӣ4OWwtb>4>M ^{P?W6Ҹ\q&Ib0rl~ J#r,6i[9"Œ;,Fǣx~Ǧ^\bðzؤ2WKttFQT|#Υ#hy؁z/]A6mVk!bkx|5_,*-R+LԊ/|57o+1HI8[CIC2^4rS, vV. u!4WU^LNc)q%j4I= ǹ!"u# }M*ZNj|J!n+ܚ@^?'Fn8/>y@k="/LņV{A =h 4qswCxoi IrBձѿ&t`Ы!;G7b Kլ[tƆѲY:+68|C0 +7+~ּ(N^+U&ByH= {ϦԄ̔4͉u;~Et J[RJ^\;;jwNf7r~Z%?)xPdOuD"M4UeLR0c mCfch*w߱ }B\ZČE/kS_?&gP2qlfCexP çKs=zj6ǿ{O=(JWc/gxOm&{WqE0}WYUV^A&@֡U%RGczRa 8CKwBT>uS38n9?2ƜSP6rȝ=.^[gQrQ, ~ S .7*ˎ.MnC?e߇(ոW{2&$\Urճ'ݸbeR\}Gj:V?m'PI|.&y-=Ct(tyl>;_-AOV QooqS X )Gz%&3gх5hL 3xd f0ũmd^W?1#W&ڋʂ)x{XܫL0847P;1A}Lx1e{)n>°I[y6 V mSɯcp:]tG5#"Eȱ4 V <|BwJleb#%u|7/-?0xqp^zSY}o#aAy!2ׅ/A~/aiuu8); =3YAԇc u)%/bqۉY/ Τ#D@" H$<7q0@槷ml#7˓IȸKC\3OŒG_6a7UGנ]u쬋xYc]889+)XCJI$D@" @§;h%a*oD)~PIopt2c bY)H"ͮBd‘^TYeTם@ >|!HN2D@"  kF@ P(7}l7+QX9 0>j}L">M~P_}eKdQJZ(ӇP;~  B'#Fl)عb ⨉Z^|ӏ'l~/BrJDf~0(,QO\^$VSݬ%$D@" Ou+p =I_G=4a4Bҏ;%5=K!D|gȬ_)-T Q߭OunM2D@" H$DNlrH*a83aFAʼK0T)OMs{gON" H$D@" R@" H$D@" nWYI$D@" \ !(((P>˽t-d\M@ Xu Q9f{Nן.^.%ED@" H$w t:\ӧOa8;;]oРի{{{ԩSiv;(7D@" H$D@A@ >"H$D@" H$=RXfP" H$D@" {|iFFΟ?د {֊$===KD@" H$DFghԨ5klhHWsssKwuWEQD@" H$7=\4]BtDz!PGg8 !S!H$DD 4GN\ɢS~A(~i4c-XeM@p<<<P gW*"tR WeF27J2/+xT㘥nYUm7C )xʱk/*CbdqG#PyGVq~=8Æ}{])qy7ӹC n I%7'jM6DL'99䅼H݇b㡱7E1NQۏBڲ>Ӆ5""?%Qk7#>MM~x_FQ*6,]`N_ mE-Xǧ_ pf#vlޗ4E/y+ί {8~}Ў xK}Xv'sgJMwst3nWmk=#^,=otP{l&Uj Q+RIЌ+NĖMH0xx6̈́zb&s)_~=tE[jŭB2傊vy*^iاl0k̲I;6[cJDz 7rcqXG7sǙ)msi 9yr2-ˀǁ3GK̇Cd>Y4'qXZZܜ3~ѷ3tOGKBrZ&4A kr`Ȑ!vcN -56e oGCj ¿ 4V AFՋ[Ql]) K> =1c8Gg)jqRwboyohǷ*Q,܀wðUFoN,>3M.2oC!쭥x:6$rgā]ٹy9:+I@KF_!Ѝb{!x8m,\fgi|#xN_:gSZedNbaـi8;q54liai]|P>:w;%( h,miyzeފzapRXYrq`yG K -5-wSt!&J]d pn]Z0\- MMH0?êBWCm5$R֠'(FRgH]u+[q%Ԑ$@MEyZ `R"-:Qw"Ѫxi||G iwHWx+BV4j+(ڃ_*hs&mؖ%]H)C>Noƿۋ9Qo]q$5AL#o"k-"ǡ#7Smh :6lHDߓf/!@6DHTY/sS+ysg޶;W ]axgxX5w6b>_ å5jMMz2k6V5 zbE|28wy|6?A3XZFP WeS[FK'-Ͽ:ëPwTJk'^}\v;a>k ?B҄xcX+nymFF]xh{O)}"ٯ$/#2%֩fSY 'Zus1vh(DC+Fya:8DPyC)5i C2:!4HIlA(:dZ{yr! .MS)i/آ74y 8_`=6=}y2k!av&(9t FV. l?h-}\y8ơP4ɎŖbi)l2~G%V=:Y5tI^iHVu ɦk=G_Ap/.vҢ=UluHc?OXc6}敍m[UGMzWb6D y A6 H~_N>=F1ӎr.As< y|>V1-k}*_|~?5;u"YZvD<= >`ߝFxk3DlX? MR:[]^ 2qaI;W.ne}p#Hǖpo c$HuȉXx1Y2vbqpz5Gq8j<łS0=Gi0BOz;Q{MS^s2 +΢)F>jLrBn6N_a4Y]•Ac`jXMr@<^B ٛ;fb"\Ұ B+BMG_!<1?ұ{4I!&k0upx|VS#n9Du9A' acѳQE+ΫW2.ă2Rw-! #ΫW.IX8~p;ۣepYEOڌq,H|Iw<"&!htذ1Nysb4t>.h゘QIy+6||&\LtNĆ`D Ʃ/k$R}9&T j Wø층BIŒ"(ƜC/ /bQ+ԥQY_=W(q(=4sCQ>!lxwtdYdB$u_~Hj涭UVnjV>q<Ą-41ތ@vWE"cX8Y57)/ {#r#_]\Nұ.H/ZoMv¸bYoJ$V3nm:fBhD;鲑 [;;ٱC&5=ڮDmz"BOn wu\Ѿg? 1妞ƩԡWdvHۇp]#'\*;4l̅F{Uаw+81=8Dž.i侧"hSO=&PXeXuk2@z.qb#dK dKs'8-lh)~ei5LG`0=)-Ȅsl&+в/۝Ga2ցɰwՠCW])\>u1")=h1ى&Lc]h= –E[[t#őKpiОJ8'GE2ǼpSF~./ ݂曘Wnμ#{ޜh.p_3j8 =.\ddnDBuIrjLLҾ(jF,;85X1E̽%.F"_#HsF .i*S7$sc'"r% |9ZYk7e>N7ku\~&V&Gb4~;79%-f#憣p6rp-c|Ɋy9-ƎKWŕ ?A%r-咁 s#oFnr{4Zs\qM ޡLʆ p !Ᵹj :vصUR5#%!Q`̉ãXzo1}QF9h@pf;Yh1k' h{5i"&:nf+B$~}~ʡ$ll<$6؏rs ;J')єY]:D {jcq! S@P|~0a=0S6 L#OzKfpUPɈy Uc[ 36$Sq EGxѯ!+X\JLjNиG Pi'juX!s ;#W X:A=$ 7[LmX;1>]M[NS9vOX Xc.G4}dbe xgy*>?6~lQwAjnǓISf=&P6vPS{s9Ί.=?yއt.@zyig̉9Y e]'C  %CфBgNsCJdƷ{d*ص?I{}tF!ѾC /|0ܼ#.PC'/£9uc$8t,k$&$ w"I_0]5mIHSm_PuDGZTr]?Ph' D PiQqiO#Hd{$eJlIHݾ.pl_4ZQ@텧C`֖Z:梊SEdJ#0Dl- >s]R\x¤p"$t$'/f G&_}4ZiP߇GUxN8Pf:o ~BeCsU]þxA[靽1)!4;X>c.ޝ3=j6o&|S #A*|/=t*}a3o:4P W x8qkȈ'wIXE4g ԶBA`vy;FOÆ7^rb|&WA&Dh]q,bA!Uwλ< 3I\،mw TҕFP-¸Y?(~ԆB$Ci}ޙ20_1q&45Cm04EيG"q,|4054a·0+E`Uٴ̲/F,W#jz|uRr"y3tBF˴! M0qk{PE7iQ1iS9]̋`M]~?tԔ>JB[Aڻ)#k02vpTPLW.%>Atb.@aKJ޸;m.$!#],%bϗa[W < 3!Ʀ_zbU^):I?.şU=˷m|ЪR\FOWl|%F!ILOAQzc䦧,RA"EOQ⇻|}쉢#8͛Pl~Hعv=؍$UwFrKIx]EzJ z)}t?T${OݼպMOωk1f:6g3l&T=tnބlY\*b4KMIћ, ,Ғ1D]M%u0?1bV@v8N0;RTu3F`bb8h6VkOzIni=+B }^n x$-m'Ӥ <076pUMJc{(;TdsRYsl RF&Fí4 6Kgf>8^>8szJXg7W,L4ߜu~Asxi _1nrr ` >% >539H?W754| |kɋy`E[ (|10Ppd%8_'E[0߿Oxeq(ИsG'fkN:9I(n:Bj~Y_!!Iч ˽N,t 5Е&BO&'ٗ Qd? J3=PǽDq A'IiHJio#O˻tq2pO-Eg|tlyr7M 'h{[ LcyN|Z ;-2`?/Ưc[=w3}K:R)jWE8 #GGVGM$VgǃLJjp+O;jQ@8Fhj.#!YѾ-S >oY—O&{iS?•ZAMK6px6WG߮lӺVPΖsOp'Ni]pq} bif)S{qܦ:w`ΩUKfF[(sGEG<F [K5y9+in1TBpI'ynWP/8~W۪NmWXNh8Z7i-=h` 6^XTA=4j5{~9_v:7.G <]Ἃw#;lɖ_А1aiE6iB 4͜De$ո xBc#VSZx.Vy3P7\~’OcJNqX>F =ʼnqbCku} ^%"Ø;"~6h%VkF #&4={i礄n炉<ɉO0[}>WɏXdfwy9?U& LA\ad Ky6s~B -ۧ8N}(Qba%%Iw. 0۬<+z ';DLێ5z6pqdk We Ve a4X,p_w nBW xMN™ZZCETa4:rJʬ3fnMĢ#=3O+rU!+ijj(qr^hqrRg[Iuqf]ܷ+|i \)iek+T s~\A㾄\~i3q O8ʶdh|p*=9#w1_c:JfφaR Z3[zR~L) \$ϴ(>}*b=߾z9OUŬ{ LA34N#x&5`1~ Kp_01# q4]%B1GdDZ*M`7=O39<}nw0ž\|h-<A gi6yjYM862MO *66%8x@ d<~J9 sE oZzˢw+pLQjh7d27e]U=Cn:L+YAԇfa:ӄٔ)j"/SڨHژKW16n 0i!z3!܅d>^fgjia6m%=Y8'Q9~'I0)K;ߘž<@Pq6}D63zF[W;r]7m'^/tJ{+(tnn!䋐=x h8Nqѷҩ8*DtV.]mBlп0QpO~lrz'1iTCk:E phps' =CIC?B4)jih WSNMD `ҳ~?sf!%6RD_B>{Dj5b[aOlZǂ&AQh6x(@8VfY'Vq5V*9kU \Yjqz_Yś0/n9|л)<߉]"zUV+HmHj7:o1kcJys̔,룲o횆/sҵЧ +K#P) gC#xldfbv!/{=\:bOOXڷ$ mB͉_G턿i'_F|mē)V \zS\8fs]4GuvqSܬ\IXƑZ"Q>SƻVքKɜaK;+َ4lRSu>c.etqSBS`E<מHo2Viz|GХK899ͭ5rSSNdJԞWÕV]k+xy2fӚL Sp&d3[fZinr\.y[orew䡈hlZ^@V+rpWMv q-78tצ0x#iٟl rMI⾉aسhXzJł)|'rֲk.iJݤܬ & U2^[LI@|ɌP HC|^ZJDZ=2iU 0(zUSkUĹsȣK3X BӦM+AH$B/| [c8 tS' T^vQC.t,&Bfk  HxmM'aYnq`˂ Iy8'`e[B5ȼ/>oD)h7\a٫k I5P*<"2ål(S`Hժ?&LH<&駟bF P PsŵN Y'G~Mz4KN(@ P@čS(@ PE`Пiii.}c;(@ P@ Ulߟdj`0a†SiVmƌM{;gs(@ PPAիWGT?~[@]L<͸q`61jԨ}S4(@ P@B OBk(@ P iEjZݰI]GH+{m{t9֗_׵z:U7o7_:'nJ PU 5O8|k~S}3쨀Cܨ5?)Ãs,:b]RsQ evyG}PRxo^{ gP~LR.3WbNFvͣ{Gwr֚xg$=c)@ PLf,ai5ӧ٥fW!Ol-'6+ųc}ǼiS,}x1n O1}ҽKmhbgc4:,1e[[>|r2>-(@ PWQ]^;*:ukW$̳Yj44nNI"-i|>d"[ocJ{$Zϑ'Ey`}[L{T{U&Q_>Dݙ_W_?VͰoY#;["QhߢǪPbf 7WH ߫#w8} ei|Wτ}ц؞C4qll۽!Fx[SL^~lߑ+S2X-o_im_/N4łGv(5uKh:ƀLaL']z; jIAY=fkP}4Bf+?↳[)##>#W(@ P|gqkC%i`Rr.ι}&t_^<[u5 OgoRYtpc1XVU:Q(ZlWlD:(>D]4A{'yi[|j m!c3aRN,oErb_M+1L2wI?e$XЃ܍LJ##SH,Ʒ~&IP@<$k>RAhvXhm+Ɉc3bo-;F PnE@!sK>WPr7FPgw~WKF94k$zۻbN_b bsLTP$E4}6dϞlqx&ƊҾnt`[lv}dLf;S5RINS@0H1D'ԕ]y̛9z@1]1ssH(@ P诀L 9y w#oRdd4Hd|*Eug]j9c!@lebyI.7ЙȨSWX+fG*5fC%hI\Bղ%(+Ŵ[Q[k twknvXYԷ3p5]0^LS"ߎ)Oa,i,l;w@j~_GdK-o.-튙*UV~Cm?Gʼo{<ϣ1)IG.C^_?(`p7ĶI:sů`1gj<#]e=t6Xϝ;3g,SS(@ K@}Ez*/.]BCCӯ! X+jp["VehGFdhoӤ7}J/by'ON|Ar~k P(@ $,(@ PSOrw(@ PI%'n7;K P(@`(@ P@R 0IR(@ P 9$}g)@ P(Tw$UoY P(GQ fΜ9$g3$쬔(@ ܜ{sDOӐϐR P(psCZ\>|g (@ P8X P(@ P`0>-(@ P$'N,(@ P> |Ͻ`K(@ P(@8 0,(@ P>-(@ P]d25ǷB_U>v7ύ(@ L-eUU(严9? ͔q1Y{ Uag M5w iW"S !g(@ P`MffBS(xܷP frql'.`DV;9Wp޸ށqi#+ņ CP(@ P(pKqOmLs2ELr=8d2 +l7LUNu\ۆmsur;R{"҉E࣐q,2$?ÞNkTׯ[%1I6R(@x qxA6c$" w=O-%p2hH׀⟆Yq›5 l*AJI"\zfTNɅ\8ZJ.K/oGfLY^ƵpȏQ~&Y:_Ͷ`̓pK3p>&G42>1 ^+F/]vs֎̝D ~8md~+ P(0@ژa}p AQ2 d$y}!Xps N{}ƈE?MQ2IDATjna?ꐫ;zAh-K*zO ؑ ]b!mǎDmJÏ7Ŧ*qw c\F4}<e)AOoՠ!W%Ѭ}D1d(@ Pg*q~#]K 46 ?0mb{ OJ"C6_žf .#jιz:RG¿BUP,BŒ)hS̩f{Q§0BJհd%$ғ/x裳xL8f0kP{`#YR^Oc j >Z{}ϷዎJ -hi*<ϔ)A,wg(wN͘.PޟHפ׎h'>#(@ P&DkUyn:cVVxTJB|kL1 7d-__ ,t!l =^*rY&#%wtH9*OŶ՘$ )ΞxI1 P(@ P#^ψ (@ P( P(@ P` 0(@ P3@ P(@ x>#(@ P~(@ P(@/gbv(@ P(@ P+`2t0_}>I8n &al.(@ P4-eUU(严"lfJ8vެ=Ç3%K?6́J㸠v]^0I[ņR(@A6v ;QL voA̪HOF[k ]6tcw?<<TMݏ]7oMb 6$T9-nK PQ"ԟڮ&::tnC&t/O_f#gڮr6t5\b^q:ǟt"eуOs=ۡ#(dd1)mnǨæYOԳ d:UQ\kd\kbRͫQD$]b)@ P@R8É?f1ٻ'זf8`m4$k@OC8gwWm6` b.Pu3*']BIe.S-F%&'v mq-fF6f[g Qet.(ܛ &~^ϣG^Fp#H͍} 6;N/I%Up9Ѵp(YBuXלrMv% {J} H%M(@ P0˨v zZQ|`2ԼVӾb9>c&57Kڰ#;&>]*\  {-XԎp;E^H-  U9@yKmN2>F M"+UÒKOn㡏2aì98$[[HNY_x3vVTZ]> K}NlS@= zPn^ìe6Owž* yQR Fs_1ƒ1zz s^ g~:!_a䇁OB~h P(P 2,](=aCyX"%؀#_U( Sڐ 烌\֡.𬱣^o7j,,hB/TBnTxɲ\ P(/ݚ0b34OxhFhR=6ُKQ ,~m뭎F 2]v,I]g|RRU6yUŒ,AN$005{Ԫ25ml?˒m=]e[2ߦ%2Fe03"Rԩ逶^P}W19O|D P(wHk=ù A"\9ׁPLsE)1_,3^HPn$A"=ʒO|N P(.ŰtuD*R>Lu,N)0P_cڌYX! hZeH } `[o\x\Tc2)GQ&OŶ՘$ )Ξx 4]0޽yRiX, %K-S䰮~cߘY__\nƆnw (t3chf\,|5{"EX{\*/p~ 9(Hqy/4W$k=T>[70sa̙(@ PP_Ѯ^".]BCCӯK.K7 n3=":+ԫdC6VBgd"TY8@F^^Թ,EVR^ܟ<'d甎o way,0M>@KM-hVZ jbҤI7nQ:!r/^(@ PC$0<!Hjs;{(@a~ uO|$`CpqA,(@ P[-+sKҁBYT?ˠg(?|g(Y7(@ PyGKq'[L P(@ P(@ PH<>wb P(@ P` |(@ P@ 0I{S(@ P`3@0&(@ POO3(@ P19(@ P(x |(@ P`L&LV `3| [F P+e %񭇥S`07M(@ PC"0m:4 6mHg-aÆ Z6(@ P`.a)MMM{ut]g;ԉ3}_,-bDh|OͬQD$]b)@ P@R8ÉAXg`}hxr5/GiCw>QThxOJPBIe.S-P0D o=pnډiYW?.Ez$@"? *6(@ W =- u4$X]|H4Á͔'ƷjP}‹ي+5 ;}jjFX %H~~_*8=7$´7#)@ P@ ߫DNhuE}\Oc/HX"QهX̗X$0J]UR4ƭzꓡ}xaӽ,MF(TaQKZ/'#? |sS(@ BT6Ȩ܍7Wp$}t}CF"q g =Bk%uhx Ƕ3wm(@ PC& th@j֥< χ&U]ywMC^S:'T__('KrAO_^}an>q(@ P6 L콞^à3QΩ2lOkq͉)Y(ދvd.w~gc]n|@QuNb g=J$犭(@ P*'KL_ ˪Ҳ.n R(R_mr]%ũ6k1  }d] fեY__Γߢe;P`̔9w#~, _,'Q6\ܹs9s@1=(@ P@?WW",ȥKЀ={#K:ee7_q.s"ܰc,p@oLFayP\m.#AhOT%Jn7Z ,UV4iƍ*8jԨ^Oj0\8m0Y(@ PHB#vsBʦā)flMO[3Nu=*,g]π'j7 U_9mY/(@ P>~u6BxЁ3U8O+(@ $K"d'(@ PXO|v(@ P"'Y4I P(@$`7](@ P@0I;~R(@ P $g)@ P(, |N(@ PHb>I|u P(@ $d'(@ PEd2Afkޖ4Rkq$A$ȍb3)@ P 栬 *EUթs(@;݉M63U;Pn<(@ PI$0 {jJj1fkS1z]#G28{<(g6lV;-n)Ӧv% \(@ PFbWKu{눜/jѡO? ] 2flitrl1{+SϧʒR1%UHk"y(ClY$whsTy NE3'X~*ar# P(0T!'ڑ[_ S mEQu8~\M?eGɛ /óP|pn){M?!~S/%MAË."DQE)sFw 0\RYMl@}v\x?ĺkfUlAh(7To}7Ga8` ?vem @ NueR P(08u82mٵC"*zσ&IhG֎58I@̕|_eHrt_ˑ)׏ܗ"G!NdL z:weF'))-f HAks#>$AdKq΃[+1N+c ,Tg|+A PFǏ~T\XwtkbYjҦ>F M"+Uä782gQXFh49)f$3R&{Ѳe l2;#-[Ia'$-,#CjgX*5;fA[oEFbQ%'/\pߥB7eTIOxj^&툝D_ˀa#>@ PF, ۅV`l *~"#4|D0pbkobu1mL8e|8A@Pv!l0N%aَ+.~SۉA:eȨ] =6^?*}ѧ Ա>-Sk5Ɉ~wF|w7 P(0Qc/Kr/OMDj*Z bkZ_5_l($awԺod,C(T!Qds%P(@PK9wcr/֧?Sݒ`HFxZ.%YpK3kB;Ξ(G8a,= VdcM߶KZ~بj2[DʱShSVf#{ooɗQ)e;>k 6 +rkP?nG;FR7  <(@ PmR؆S`=׊'4qK$}f/)@ P@ xBޑgHZ$g)@ P(< |^(@ PHZ>I{q P(@ $)(@ PVOzv(@ P#'y5{J P(@`(@ P@0I{͞R(@ P i$g)@ P@dB_ӯu;3hNu(h5'Usm1zį:;6YѦ>$E>ItU P(p GPq0վ7kpU+#_wWkm6TU`v>*1UUQʪPU 1)D?;k.w:*ޒM}M@QiVUhY\׻cS Dَ[urM{?]jIמѝҦ vK=>|w P^ I"cp\3[.`M+%z D=vاXn```jcђ:q14WP$CFF}w\vj-oVh3nHa]CʑyN1oLjϔ`wG}D"{Px@;YE+m:Se4R_:.iC{jv1pj4NH0XixN{-+6B充>Y w2rg8}5/@{^ (F~8I P(0jږuڵ}y W G =&˱]Bs9"I[{FɗkcS{>}*3*K~P;f[iF&Nn2nW?M9Dǖh~~<{ZlFL 8*[-ٮSgjBmS!EPӷta藌tm!=ۿS#e]y:'`=Sө2PFM>wzό"j(@ Ppx[*X,Sb72kꙗ0AL-ّMuz5Mk9kW"ޫJ:SdLM>)S2 \YV|3>8y<#~-]b#SbiCoLx_՛C%]+h ,Wj\djfߠO kdD a n2& .[ cnARrM&Y"f rjY;fVL P(0W>\8\.ןDy6VgbKzkCme=<ǖgBvZj$TѐR$A4ȃv>ڏmXpZg ׌>TkRc4F'Ylttb?|焤A> 0Yy.wuLPX=7#akQ+z@H:ZK> .]9kL[SA_ '1gWZ-pb#;x[%b9h! QW!jz~`S5i,e˓0kKJRꁢ^ʍ&렔{ {<ǥg((@ P` Pf؆w9<´hy65I_WB[x,X 0 '_.F|JZ;0+=1v}9"ۂ OC%UjlMhLε1V=]-wڢ,bvIfdg?zTWdc]uc]7rVyfo!|F1U_ mg^ U[,^IhikǴ.DM*QrڪĮ}:(a6]3Ɂk5c%RF{{W_! :-(}KR,[y_ #xl^U(Yý^uܹs9sf Q(@ vU0.]ٳgʿT'2~Y뉘kR'Ny&O)kGJ#sY얶mLu]-{o!}d.#KzO3E}$H_gR>>8d&l8wɮyW+lгj*,XVI&aܸqsF6Jo[cV P(/ī#,w ˾;&Y> Lh6|D~LuKF"Ts,(@ P *;P(K}סYM2c.gPɳ^ P(@ ];2Lfp'N,(@ P> |Ͻ`K(@ P(@8 0,(@ Ps/ P(@ P N |b)@ P` L&|s"YD`̘1q:/^(@ PC,x5kS.|VPs 0dPG P[@})sn?< .ٌ;'OƨQw|n]f=(@ P` S_V+OKΟ??,)04*W~4iF-wgh=k(@ P} /wܡaz*, 4Mg}uqpPرcK_s>O}յh}G_CeR(@AP_՟q%Q-n0e?/_G|\gYD|b?*R9Vg=`O]M PnQ@kbeQ:jST7uF}Q`8 D?*Qc5jZ}xo |-)@ P-KZMA #[e*x7OY>(@ PIMxtS_ Uࣾ_0;>f(@ P-TLH P(@ $Dsl7(@ P(o>bB P(@ P Q$c)@ P(@~ 07R(@ P*'QM P(@ [O(@ PHT>zn P(@ P |Mń(@ P@ 0I;vS(@ P`o*&(@ PUO9(@ P~S1!(@ P( |α(@ P@ IENDB`trunk-2018.02b/doc/sphinx/fig/dispatch-loop.pdf000066400000000000000000000317011324306050200212540ustar00rootroot00000000000000%PDF-1.4 % 3 0 obj << /Length 4 0 R /Filter /FlateDecode >> stream xZKm?vE ,h7b|gjdCoFTwX$[1r=h̐?7ކ|0/{ Hq<{+ǟ~{w7YKPR_aF+졫H:<*Sد#Z)\V+w%;Q$Sr]YUtm72KQs\\ϱ~%6#bGNF= B K0JYb,PPH@,FbHC̢C2KV؆";bƛh!Hf$qQC$W fd!{ }9ٿmmXW3a Uz砵ȿevGDjpnvq 6^{7CiC4')@p=ePg0Wm2qWF+ˎtf_Db5->oI-懨S>' ]R]EaO3U F Ӂ|(|:$d|8E]f|3 1V G`m~6;mR'O`u5O=J·ޓQteRINZHQM7gKe+%wX3, ͣAsa Ht6ȦH ËSѷ* Y , HD"P#pkZ!lGm9ycgbnh@STcFȁn'WX/9FoF&y|DpRfLKF:Ȅ7 4mkă kr'@[6}L*j"kLB!ՋG +Wv3;B،5jS Zh5S222-0u@?%.L.U 5.<g4 ֘})U[NA)XrǪⰣuQ=} [amtJrց@p~^T+<̯)BV 1T@H 'B@v7M#3t>}2kz|7aXg!M,\v?x<V|pG&_0V1i@*T@5,r 6#i j\پJ5zj], єlCzwEܥlYk>pN7XTF ="7h-:&Xӣ,huC*nʹY`,B1ԣ~SlXG/ȳ#L?GB\+F*aئ4yQa 32-8RWS}(\iBW:pfP7;T>8, p]6@Xfg~`w >dVTC<@\DPձ.a:j7: U-G]Z9ca^qetulh|c9RzxJX@ss*MZN,O7*tE~= d '`[8e` OY0DB97!+3du/'ߪٕFBM)}S̝Cu.w}L:rezָTK)Ӳ5S{cL!h׫-6 7ytDT*d } 0VQ kZ Zȅm/Bm,ڳjst R(ҼOؼ&ǰCI8)LbO4 ќŏ\@S|̕IC&bQOǵ,ALט@͐4Nܕ( n",E!0 &ĩqԅ<]EMoSQgvDu@R^ *Ţ4)a[4ܧ!8ywM*g)&۝gRK6 /陞Afq6N)-7BzpE-!(n'3n9c^b:7^{,#nAڱrxb yDur`EU6wzo ?^*?}.5?-N^FSXuix##FϷ7#gmySsڞ@;;Jh&ګז~r$[ii )^ ^*p,(q%QQ,aae@+eSfNc"_5{؁ezU99Q'as:lz!q%yNOC}ǛOaݞh|vQkYcP`RooP,nQ8sDv 'MTl.|!K'=ųΚ9;|BZ)y쩵I}jG,ɺcݘ]S,䦼-^8ՠս yd!l_噔 & 'M1ϊmyzV[]16ȩYKuvcMwU {]ˡ]F{$aey4%?뭁;뭁;˭gG;˵;˭;KWKwL/ ܙ^o ܙ ܙ ܅kZ7pmd9.qgy+;ӵ ޯ ܙ ܙ ܙo ܙ \ꥁ+l 6pMw@oV׉_lƒuT^}<ˆ'z+zCk-*_J|:R>Лw{WO~_5y91M/$tZFvm9/oyv21<ٳ=tykD-ߴTUI^^xbf5PjeS/t_$UcMN1r0ep];+S˅ݻbT@&'](7=9p~SO>_q?wSw߯v%>_?k;~r傥]Ur vO5ul> /a1 << /CA 1 /ca 1 >> /a2 << /CA 0.465455 /ca 0.465455 >> /a3 << /CA 0 /ca 0 >> /a4 << /CA 0.301818 /ca 0.301818 >> /a5 << /CA 0.356364 /ca 0.356364 >> >> /Font << /f-0-0 5 0 R >> >> endobj 6 0 obj << /Type /Page /Parent 1 0 R /MediaBox [ 0 0 320 82.400002 ] /Contents 3 0 R /Group << /Type /Group /S /Transparency /CS /DeviceRGB >> /Resources 2 0 R >> endobj 7 0 obj << /Length 8 0 R /Filter /FlateDecode /Length1 8420 >> stream xx{\UUZk}9}8p8QQQy),T$s2Q234FxɌLM123P4KjQgƩeCf ,gf<|YBTRNg͛QL3?CH}j.z}QTмo!$z%E$va>'$|3 ? 5 H3~ ۉs=ĖT ۛ]Y3NOHt$sxXO/^8x{|^JCRk"^AxH7\j8/]jK@W$Z?PJ!Tv}!`I2y*8f닕 >_lD%#ktl WU5_GGS-QΏZ:ZuW6pmk?]s#MbqڿjjAG RR~ө?MR_d}Z,KĐ^Mnbr}5=^ݥ!Yuved0zr-=Ė~+=A GJ]SHOB'AiISӐWKwu/}U/ϞZ_874Ϡ=w9|P&G %7 #QSҮ7Z7Tx"k]Yiڐ*݅\>XAR'DAn.6.]Z)Eovv/la񝝕s ,@](CI4)&U*̳&+=jG&cd qnJy ˍkؑL{{{Yɡ9B''iJ?u>O = jO)A;MI23 Xqz፹gzW?MS:>FaʭMviOpO?:4?ntv%3$=4 57*Us!Ru*E%Li썐캿w$)Q_a%id$%40)g24ݛKǥ$ؠ/yy<^\^|^rq oel_"~mr]؞=z& qűžbq\yl_9ciAzB*O|RրqY XL ?-M9+I7Ο}|^2Jg\9rwE3q섫lUL.**I])+l+&]l@&ld{d/gGkF[5#]6.M74=.#>S^{~=w?T*M!f ۷>1vCL]3ێ818eg6˕t ւn CuG|%ÇR7d`NRb\ɱcwTVw՝uU㷾{ǭQP'Yꤌ?!5Ӵ) N([QD zʉfiw\ ά lA\=Hkgi$b?o)^KEēi$ɥD:x^?4F7G9,#/&R5*6҆ k `Xzb^bq:,o&~ؕ xO!LSFX񦅋w7-\fwSSNÒ'A܀0+lܛ/uV"V<4s)1ϰ m}ooRO,,J-KraeVa4h?H{+0,OUfs%k>B  "/e4m^v2ޗ:H?_qadH0׀`Sst 7d<pbwZ81VZPTEPyѲ"G6K"hG {Blڼ5FD:TՆZ5?#{>ai4Oޱ}) BV\ JXvpDCURrRL)K)OYRr=%y_P)&ê{UUܘӲ4#Ef,_={_pO-:$;??8&gKն dzNp$,߾?6oM(҃6' }?NQ~2Lرf]kv5r>c }Pvt`vp>:s?xc_؛͚IP"2sVű c.jzECdΘ|QhsI@@՘'Hc,uD(.4j U{7Bzb* ,^Id6${<2ʂ{,1򸴨WIc1IE^ˣ!{uTdpE"Jx&s=/W1pćҊ[%8l& tK?o}7[kx|i.~fΏ+W^!.(}ժngCNC1n0鹻YmoKwi#f$i*h[н ;]VB?`GcW:/_g/aၿ)AGPˈsmso6FM󿿷i9Ty#͍qsqqc$4Q­ZRtKEфԺxr=1c,VǰII*0 ݷ0 %#bȺC,+Rk@O&[5U%&bYy|(B1Vzu\ƿ0fڜ5ފcmghi6cd*.x5K#aԠzp橏 "uP&,aeR"lff+ HTYSz({h9Zqkkh qbK񖀜$qZ5lk}0FA1PF#=hL&B8+kɓ'*gB:[ QsGCEd]",'eILzRfy2T}\[fVj&n669Yl"j/wt^ieuU8&eʛ$aKh434A W_6|:KB`nBzz?ֆ뾮.O{Dƚ| Zׂh'qТC1g$QFXY'nl N8HVEsC,MžHEYIA=7db/!W@n|rL0"!CbxU@%@,dlx[pH## dB8BOԓ/q?GNR|R& bivl2_E}\&AF2<BuPƵ.w }ǽf.:)ÝN CKbpl7=Iۤ \a|HW q4q H5՘#%xv ZO ,3qជ{DEŒ4*%,c4+X Yd. IZR+p63?},߸)Α#WY0&NW?rDjHBVăa?lf*Uæm <<%DmUݷ}EM*9\FXrD:D[Y*)/ RY9,> iR&)[U{frؗ7Y\wIWDJ @vF{zC|Q~&)qi/9I~V+R*Iڈ9AEk̂ asiPTU ?G78g~p=={õZ_U_eٗЊ['/4ç>ɀZÇ.2r8ep.hvsxû.s> stream x]Mk0EgD(ۋ~m&V1Do;lǙ1֌ݬ.a42ߜ"u4Ljl˶4f.I==DBirO?4YE54^:M$P|lve%85-S:sNjD=Ddxe;ҥOO"k*A9LAeU: :_]rP:&L ҁ2#B?` _=% JU~'W*q&q?e/~)ُc[ @˹*\)~$'GݜC5L>'DUė endstream endobj 10 0 obj 358 endobj 11 0 obj << /Type /FontDescriptor /FontName /BitstreamVeraSans /Flags 4 /FontBBox [ -183 -235 1287 928 ] /ItalicAngle 0 /Ascent 928 /Descent -235 /CapHeight 928 /StemV 80 /StemH 80 /FontFile2 7 0 R >> endobj 12 0 obj << /Type /Font /Subtype /CIDFontType2 /BaseFont /BitstreamVeraSans /CIDSystemInfo << /Registry (Adobe) /Ordering (Identity) /Supplement 0 >> /FontDescriptor 11 0 R /W [0 [ 600 634 634 633 615 411 837 575 612 549 392 770 974 636 611 352 774 277 862 603 591 520 277 294 634 636 500 686 634 633 557 817 ]] >> endobj 5 0 obj << /Type /Font /Subtype /Type0 /BaseFont /BitstreamVeraSans /Encoding /Identity-H /DescendantFonts [ 12 0 R] /ToUnicode 9 0 R >> endobj 1 0 obj << /Type /Pages /Kids [ 6 0 R ] /Count 1 >> endobj 13 0 obj << /Creator (cairo 1.8.8 (http://cairographics.org)) /Producer (cairo 1.8.8 (http://cairographics.org)) >> endobj 14 0 obj << /Type /Catalog /Pages 1 0 R >> endobj xref 0 15 0000000000 65535 f 0000012618 00000 n 0000004427 00000 n 0000000015 00000 n 0000004404 00000 n 0000012459 00000 n 0000004732 00000 n 0000004938 00000 n 0000011385 00000 n 0000011408 00000 n 0000011843 00000 n 0000011866 00000 n 0000012104 00000 n 0000012683 00000 n 0000012809 00000 n trailer << /Size 15 /Root 14 0 R /Info 13 0 R >> startxref 12862 %%EOF trunk-2018.02b/doc/sphinx/fig/dispatch-loop.svg000066400000000000000000000453241324306050200213100ustar00rootroot00000000000000 image/svg+xml Sphere+Sphere Facet+Sphere ScGeom FrictMat+FrictMat FrictPhys Shape Material Ig2_Sphere_Sphere_ScGeom Ig2_Facet_Sphere_ScGeom Ip2_FrictMat_FrictMat_FrictPhys Body Interaction InteractionGeometry InteractionPhysics Law2_ScGeom_FrictPhys_CundallStrack trunk-2018.02b/doc/sphinx/fig/funnel.pdf000066400000000000000000001217041324306050200200000ustar00rootroot00000000000000%PDF-1.4 1 0 obj << /Title (Some title) /Creator (GL2PS 1.3.5, (C) 1999-2009 C. Geuzaine) /Producer (Yade) /CreationDate (D:20100330093330) >> endobj 2 0 obj << /Type /Catalog /Pages 3 0 R >> endobj 3 0 obj << /Type /Pages /Kids [6 0 R] /Count 1 >> endobj 4 0 obj << /Length 5 0 R >> stream /GSa gs 1 w 0 0 0 RG 162.880981 390.480255 m 196.264862 391.799377 l 196.264832 391.799469 m 226.128693 404.130585 l 130.326401 400.483521 m 162.880951 390.480316 l 226.128693 404.130554 m 248.349716 426.163025 l 113.184036 242.478760 m 196.264832 391.799408 l 196.264862 391.799377 m 113.184067 242.478745 l 162.880981 390.480286 m 113.184052 242.478775 l 113.184067 242.478745 m 162.880981 390.480255 l 196.264832 391.799408 m 124.444275 235.244736 l 124.444275 235.244781 m 196.264832 391.799469 l 103.201019 421.037170 m 130.326431 400.483459 l 99.120865 245.802185 m 162.880981 390.480286 l 162.880951 390.480316 m 99.120865 245.802200 l 124.444290 235.244751 m 226.128723 404.130554 l 226.128693 404.130585 m 124.444275 235.244781 l 130.326447 400.483459 m 99.120865 245.802185 l 99.120865 245.802200 m 130.326401 400.483521 l 226.128723 404.130554 m 131.099899 225.182968 l 131.099884 225.182968 m 226.128693 404.130554 l 130.326431 400.483459 m 84.426865 244.677414 l 84.426849 244.677414 m 130.326447 400.483459 l 248.349701 426.163025 m 259.140625 455.393646 l 131.099899 225.182953 m 248.349731 426.162964 l 248.349716 426.163025 m 131.099884 225.182968 l 103.201019 421.037170 m 84.426865 244.677399 l 84.426865 244.677414 m 103.201019 421.037170 l 248.349731 426.162964 m 131.938385 213.836044 l 131.938354 213.836060 m 248.349701 426.163025 l 99.120880 245.802185 m 113.184052 242.478790 l 113.184052 242.478775 m 99.120865 245.802185 l 124.444275 235.244736 m 113.184036 242.478760 l 113.184052 242.478790 m 124.444321 235.244766 l 86.194061 449.935730 m 103.201035 421.037170 l 99.740601 218.262085 m 99.120880 245.802185 l 99.120880 245.802185 m 99.740601 218.262100 l 103.201035 421.037170 m 71.427582 239.181229 l 71.427567 239.181229 m 103.201019 421.037170 l 112.519241 218.041779 m 113.184036 242.478790 l 113.184052 242.478790 m 112.519257 218.041779 l 99.120880 245.802185 m 112.519241 218.041763 l 112.519257 218.041779 m 99.120880 245.802185 l 96.083221 185.182587 m 111.624817 159.153259 l 111.624832 159.153229 m 96.083221 185.182587 l 99.740601 218.262085 m 106.485107 191.519806 l 106.485092 191.519836 m 99.740601 218.262115 l 99.740601 218.262085 m 86.510635 214.103775 l 86.510635 214.103775 m 99.740601 218.262085 l 106.485107 191.519806 m 96.083237 185.182617 l 96.083221 185.182617 m 106.485092 191.519806 l 96.083237 185.182617 m 86.510620 214.103775 l 86.510635 214.103760 m 96.083221 185.182587 l 111.624832 159.153229 m 105.690231 148.782410 l 105.690231 148.782440 m 111.624817 159.153259 l 112.519241 218.041763 m 99.740601 218.262085 l 99.740601 218.262115 m 112.519241 218.041779 l 99.120865 245.802185 m 84.426849 244.677414 l 84.426849 244.677429 m 99.120880 245.802185 l 87.014969 175.626831 m 96.083221 185.182587 l 96.083237 185.182617 m 87.014969 175.626862 l 86.510620 214.103775 m 106.485107 191.519806 l 86.510635 214.103775 l 87.014969 175.626831 m 111.624832 159.153229 l 111.624817 159.153259 m 87.014969 175.626831 l 87.014969 175.626862 m 105.690247 148.782425 l 105.690231 148.782410 m 87.014969 175.626831 l 84.426849 244.677414 m 99.740601 218.262085 l 99.740601 218.262100 m 84.426849 244.677429 l 96.083221 185.182587 m 118.422134 166.702255 l 118.422119 166.702286 m 96.083221 185.182617 l 113.184036 242.478790 m 122.823990 213.437592 l 122.824005 213.437592 m 113.184052 242.478790 l 84.426865 244.677399 m 86.510620 214.103760 l 86.510635 214.103775 m 84.426849 244.677414 l 128.533569 126.215897 m 105.690231 148.782440 l 105.690216 148.782410 m 128.533554 126.215851 l 111.624832 159.153259 m 118.422134 166.702286 l 118.422134 166.702255 m 111.624832 159.153229 l 106.485092 191.519806 m 118.422119 166.702286 l 118.422119 166.702271 m 106.485092 191.519791 l 105.690231 148.782440 m 101.542557 137.201584 l 101.542542 137.201553 m 105.690216 148.782410 l 111.624817 159.153259 m 131.180054 136.653061 l 131.180069 136.653061 m 111.624832 159.153259 l 116.592056 193.620972 m 99.740601 218.262115 l 99.740601 218.262115 m 116.592026 193.620972 l 105.690231 148.782440 m 131.180054 136.653076 l 131.180054 136.653061 m 105.690231 148.782440 l 74.911942 206.190109 m 96.083237 185.182617 l 96.083221 185.182587 m 74.911926 206.190079 l 124.444275 235.244751 m 131.099869 225.182968 l 131.099899 225.182968 m 124.444290 235.244751 l 122.823990 213.437576 m 124.444290 235.244751 l 124.444321 235.244766 m 122.824005 213.437592 l 106.485107 191.519806 m 116.592041 193.620941 l 116.592026 193.620972 m 106.485092 191.519836 l 74.911926 206.190079 m 86.510635 214.103760 l 86.510651 214.103775 m 74.911926 206.190094 l 112.519241 218.041779 m 116.592056 193.620972 l 116.591995 193.620941 m 112.519241 218.041748 l 74.911942 206.190109 m 87.014954 175.626862 l 87.014969 175.626862 m 74.911942 206.190109 l 105.690247 148.782425 m 80.727310 164.329315 l 80.727310 164.329300 m 105.690231 148.782440 l 101.542557 137.201584 m 128.533554 126.215866 l 128.533554 126.215851 m 101.542542 137.201553 l 122.823990 213.437592 m 112.519241 218.041779 l 112.519241 218.041748 m 122.823990 213.437576 l 128.533585 126.215881 m 131.180054 136.653061 l 131.180054 136.653076 m 128.533569 126.215897 l 80.727310 164.329315 m 87.014969 175.626862 l 87.014984 175.626846 m 80.727310 164.329300 l 126.599915 114.813828 m 128.533554 126.215866 l 128.533554 126.215866 m 126.599945 114.813858 l 111.624832 159.153259 m 134.132507 144.500061 l 111.624832 159.153259 l 101.542557 137.201584 m 126.599930 114.813843 l 126.599945 114.813858 m 101.542557 137.201584 l 131.938385 213.836060 m 259.140625 455.393677 l 259.140625 455.393646 m 131.938354 213.836060 l 80.727280 164.329300 m 101.542557 137.201584 l 80.727310 164.329300 l 125.021210 170.187546 m 106.485107 191.519806 l 106.485092 191.519791 m 125.021210 170.187546 l 71.427582 239.181244 m 86.510651 214.103775 l 86.510620 214.103760 m 71.427567 239.181229 l 134.132507 144.500046 m 118.422134 166.702271 l 118.422134 166.702286 m 134.132507 144.500061 l 84.426865 244.677399 m 71.427567 239.181229 l 71.427567 239.181229 m 84.426865 244.677399 l 124.444290 235.244751 m 128.925262 205.088409 l 128.925262 205.088425 m 124.444275 235.244751 l 112.519241 218.041748 m 124.770218 191.054245 l 124.770248 191.054260 m 112.519241 218.041748 l 131.180054 136.653061 m 134.132507 144.500061 l 131.180069 136.653061 l 118.422134 166.702271 m 125.021210 170.187546 l 118.422119 166.702271 l 66.854477 195.712677 m 87.014984 175.626846 l 87.014954 175.626862 m 66.854462 195.712677 l 128.533554 126.215866 m 153.069489 107.886169 l 153.069504 107.886185 m 128.533554 126.215866 l 99.872147 126.225098 m 101.542557 137.201584 l 101.542557 137.201599 m 99.872147 126.225113 l 125.021225 170.187561 m 116.592056 193.620972 l 116.592041 193.620941 m 125.021210 170.187546 l 71.427582 239.181229 m 74.911926 206.190079 l 74.911926 206.190094 m 71.427582 239.181244 l 128.533554 126.215866 m 152.613251 117.781601 l 152.613266 117.781601 m 128.533585 126.215881 l 259.140625 455.393616 m 255.561874 487.824036 l 99.872147 126.225098 m 126.599945 114.813858 l 126.599930 114.813843 m 99.872147 126.225098 l 153.069489 107.886169 m 126.599915 114.813828 l 126.599945 114.813858 m 153.069504 107.886185 l 116.592041 193.620941 m 124.770248 191.054260 l 124.770218 191.054245 m 116.591995 193.620941 l 131.180054 136.653061 m 152.613266 117.781601 l 152.613251 117.781570 m 131.180054 136.653046 l 126.599945 114.813858 m 125.691193 104.218384 l 125.691193 104.218399 m 126.599945 114.813873 l 66.854462 195.712677 m 74.911942 206.190109 l 74.911942 206.190094 m 66.854477 195.712646 l 101.542557 137.201584 m 78.311569 153.078644 l 78.311569 153.078644 m 101.542557 137.201599 l 124.770248 191.054260 m 122.823990 213.437561 l 122.823990 213.437576 m 124.770248 191.054260 l 128.925262 205.088409 m 131.099884 225.182968 l 131.099869 225.182968 m 128.925262 205.088425 l 66.854446 195.712646 m 80.727280 164.329285 l 80.727310 164.329300 m 66.854477 195.712677 l 126.599945 114.813873 m 153.209946 97.023163 l 153.209930 97.023132 m 126.599945 114.813858 l 122.824005 213.437576 m 128.925293 205.088440 l 128.925262 205.088409 m 122.823990 213.437576 l 131.099899 225.182968 m 131.938400 213.836060 l 131.938385 213.836044 m 131.099899 225.182953 l 71.427582 239.181229 m 86.194061 449.935730 l 86.194031 449.935730 m 71.427582 239.181244 l 259.140625 455.393677 m 126.576569 203.064331 l 126.576569 203.064301 m 259.140625 455.393616 l 136.928528 148.448181 m 118.422134 166.702271 l 118.422134 166.702271 m 136.928528 148.448181 l 78.311569 153.078644 m 80.727280 164.329300 l 80.727310 164.329300 m 78.311584 153.078644 l 131.180054 136.653046 m 151.909637 125.161743 l 151.909637 125.161758 m 131.180054 136.653061 l 99.872162 126.225098 m 125.691208 104.218384 l 125.691193 104.218384 m 99.872147 126.225098 l 130.336517 168.909149 m 116.592041 193.620941 l 116.592056 193.620972 m 130.336517 168.909149 l 152.613251 117.781601 m 153.069504 107.886185 l 153.069489 107.886185 m 152.613266 117.781616 l 74.911926 206.190079 m 62.337296 230.060394 l 62.337280 230.060394 m 74.911942 206.190094 l 78.311569 153.078644 m 99.872147 126.225113 l 78.311569 153.078644 l 153.209946 97.023163 m 153.069504 107.886185 l 153.069504 107.886185 m 153.209930 97.023132 l 153.209946 97.023163 m 125.691193 104.218399 l 125.691193 104.218384 m 153.209930 97.023132 l 134.132507 144.500046 m 136.928528 148.448181 l 134.132507 144.500046 l 122.823990 213.437561 m 129.589233 184.086899 l 129.589233 184.086914 m 122.824005 213.437576 l 136.928513 148.448212 m 125.021225 170.187561 l 125.021210 170.187546 m 136.928528 148.448181 l 131.099884 225.182968 m 129.647354 194.262970 l 129.647354 194.262970 m 131.099899 225.182968 l 151.909637 125.161743 m 134.132507 144.500046 l 134.132507 144.500061 m 151.909637 125.161758 l 63.809769 184.335114 m 80.727310 164.329300 l 80.727280 164.329285 m 63.809753 184.335083 l 125.021225 170.187561 m 130.336517 168.909149 l 125.021225 170.187561 l 86.194000 449.935730 m 62.337280 230.060394 l 86.194031 449.935730 l 62.337296 230.060394 m 71.427582 239.181229 l 71.427582 239.181244 m 62.337280 230.060394 l 101.035004 117.674026 m 99.872162 126.225098 l 99.872131 126.225098 m 101.034988 117.674026 l 130.336517 168.909149 m 124.770248 191.054260 l 124.770248 191.054260 m 130.336517 168.909149 l 125.691193 104.218399 m 153.009308 86.876892 l 153.009293 86.876862 m 125.691193 104.218384 l 151.909637 125.161743 m 152.613251 117.781570 l 152.613266 117.781586 m 151.909637 125.161758 l 101.035004 117.674026 m 125.691193 104.218384 l 125.691208 104.218384 m 101.035004 117.674026 l 66.854477 195.712646 m 62.337280 230.060394 l 62.337280 230.060410 m 66.854477 195.712677 l 125.984894 96.164368 m 125.691193 104.218399 l 125.691193 104.218384 m 125.984909 96.164352 l 129.589233 184.086899 m 124.770248 191.054260 l 124.770248 191.054260 m 129.589233 184.086914 l 153.209930 97.023132 m 153.009293 86.876862 l 153.009308 86.876877 m 153.209946 97.023148 l 63.809753 184.335083 m 66.854446 195.712646 l 66.854477 195.712677 m 63.809769 184.335114 l 80.317047 143.771957 m 99.872131 126.225098 l 99.872147 126.225113 m 80.317032 143.771957 l 129.589233 184.086914 m 128.925293 205.088440 l 128.925293 205.088440 m 129.589233 184.086914 l 63.809769 184.335083 m 78.311569 153.078644 l 78.311584 153.078644 m 63.809769 184.335114 l 128.925262 205.088409 m 129.647354 194.262970 l 128.925262 205.088409 l 151.064728 128.782364 m 134.132507 144.500046 l 151.064697 128.782349 l 129.647354 194.262985 m 131.938370 213.836060 l 131.938400 213.836060 m 129.647354 194.262970 l 139.099762 147.696747 m 125.021225 170.187561 l 139.099762 147.696747 l 153.069504 107.886185 m 176.881958 93.288971 l 153.069504 107.886185 l 80.317032 143.771957 m 78.311569 153.078644 l 80.317047 143.771957 l 131.938400 213.836060 m 126.576569 203.064331 l 131.938385 213.836060 l 153.069504 107.886185 m 173.753754 102.192734 l 173.753738 102.192734 m 153.069489 107.886185 l 153.209946 97.023163 m 176.881958 93.288971 l 153.209946 97.023163 l 125.984894 96.164368 m 153.009293 86.876862 l 153.009308 86.876892 m 125.984894 96.164368 l 101.034988 117.674042 m 125.984909 96.164383 l 125.984909 96.164352 m 101.035004 117.674026 l 124.770248 191.054260 m 133.407043 162.885132 l 133.407043 162.885147 m 124.770248 191.054260 l 152.613266 117.781616 m 173.753738 102.192734 l 173.753754 102.192734 m 152.613281 117.781601 l 178.806335 83.223389 m 153.209946 97.023163 l 153.209946 97.023148 m 178.806351 83.223389 l 58.907898 218.730225 m 66.854477 195.712677 l 58.907898 218.730225 l 80.317062 143.771957 m 101.034988 117.674026 l 80.317047 143.771957 l 128.925293 205.088440 m 130.073044 173.748840 l 130.073044 173.748840 m 128.925262 205.088409 l 83.418915 483.120483 m 86.194000 449.935730 l 136.928497 148.448196 m 139.099747 147.696747 l 139.099762 147.696747 m 136.928513 148.448212 l 255.561874 487.824036 m 126.576569 203.064301 l 126.576538 203.064331 m 255.561874 487.824097 l 151.064697 128.782333 m 136.928497 148.448166 l 136.928528 148.448181 m 151.064728 128.782364 l 78.311569 153.078644 m 66.514221 174.003830 l 78.311569 153.078644 l 131.938370 213.836060 m 124.641785 182.754456 l 124.641800 182.754456 m 131.938400 213.836060 l 151.064697 128.782349 m 151.909637 125.161743 l 151.909653 125.161758 m 151.064728 128.782379 l 139.099777 147.696747 m 130.336517 168.909149 l 139.099762 147.696747 l 152.613281 117.781601 m 169.902573 108.531677 l 169.902573 108.531677 m 152.613266 117.781586 l 130.336517 168.909149 m 133.407043 162.885147 l 133.407043 162.885132 m 130.336517 168.909149 l 153.009277 86.876862 m 178.806335 83.223373 l 178.806351 83.223389 m 153.009308 86.876877 l 152.490280 79.096725 m 153.009293 86.876877 l 153.009293 86.876862 m 152.490295 79.096725 l 104.952438 113.116379 m 101.034988 117.674042 l 101.035004 117.674042 m 104.952454 113.116364 l 58.907898 218.730225 m 62.337280 230.060410 l 62.337280 230.060379 m 58.907898 218.730194 l 125.984909 96.164352 m 152.490295 79.096695 l 152.490295 79.096725 m 125.984894 96.164368 l 133.407043 162.885147 m 129.589233 184.086914 l 133.407043 162.885147 l 104.952454 113.116364 m 125.984894 96.164352 l 125.984909 96.164383 m 104.952438 113.116379 l 129.589233 184.086914 m 130.073059 173.748840 l 130.073044 173.748840 m 129.589233 184.086914 l 58.907898 218.730209 m 63.809753 184.335083 l 63.809769 184.335114 m 58.907898 218.730225 l 66.514221 174.003830 m 63.809769 184.335083 l 63.809769 184.335114 m 66.514221 174.003845 l 86.576126 138.124863 m 101.035004 117.674042 l 101.034988 117.674026 m 86.576111 138.124847 l 169.902573 108.531677 m 151.909637 125.161758 l 169.902573 108.531677 l 129.647354 194.262970 m 130.073044 173.748840 l 130.073044 173.748840 m 129.647369 194.263000 l 127.482391 92.116776 m 125.984909 96.164352 l 125.984894 96.164352 m 127.482391 92.116806 l 129.647354 194.263000 m 124.641769 182.754486 l 124.641785 182.754456 m 129.647354 194.262985 l 66.514236 174.003830 m 80.317062 143.771957 l 80.317047 143.771957 m 66.514221 174.003830 l 255.561874 487.824097 m 115.673035 194.797211 l 115.673050 194.797180 m 255.561874 487.824097 l 83.418915 483.120422 m 62.337280 230.060379 l 62.337280 230.060394 m 83.418915 483.120483 l 179.212494 73.564774 m 153.009277 86.876862 l 153.009293 86.876877 m 179.212494 73.564789 l 173.753754 102.192734 m 176.881958 93.288971 l 173.753754 102.192734 l 150.213531 127.892288 m 136.928497 148.448196 l 136.928497 148.448166 m 150.213531 127.892258 l 176.881958 93.288971 m 178.806335 83.223389 l 178.806335 83.223358 m 176.881958 93.288940 l 126.576569 203.064331 m 124.641800 182.754456 l 124.641769 182.754440 m 126.576538 203.064316 l 86.576111 138.124847 m 80.317062 143.771957 l 86.576096 138.124847 l 130.336517 168.909149 m 140.241272 142.158890 l 140.241272 142.158890 m 130.336517 168.909149 l 126.576553 203.064331 m 115.673035 194.797211 l 126.576538 203.064331 l 129.589233 184.086914 m 133.584366 152.977112 l 133.584366 152.977112 m 129.589233 184.086914 l 62.019089 207.141815 m 63.809769 184.335114 l 63.809753 184.335083 m 62.019104 207.141815 l 125.957626 161.772675 m 129.647354 194.263000 l 129.647369 194.263000 m 125.957626 161.772675 l 127.482391 92.116791 m 152.490295 79.096725 l 152.490295 79.096695 m 127.482391 92.116776 l 151.909637 125.161758 m 165.929321 111.226212 l 165.929352 111.226242 m 151.909653 125.161758 l 80.317062 143.771957 m 74.707932 166.633499 l 74.707932 166.633499 m 80.317062 143.771957 l 127.482391 92.116806 m 104.952454 113.116364 l 104.952438 113.116364 m 127.482391 92.116806 l 169.902573 108.531677 m 173.753754 102.192734 l 173.753754 102.192719 m 169.902573 108.531677 l 86.576126 138.124847 m 104.952454 113.116348 l 104.952454 113.116364 m 86.576126 138.124863 l 126.576538 203.064316 m 114.592987 172.629822 l 114.592987 172.629852 m 126.576553 203.064331 l 178.806335 83.223373 m 179.212494 73.564774 l 179.212494 73.564789 m 178.806335 83.223358 l 179.212494 73.564789 m 152.490280 79.096725 l 152.490280 79.096710 m 179.212494 73.564789 l 83.418930 483.120514 m 58.907913 218.730240 l 58.907898 218.730194 m 83.418915 483.120422 l 139.099762 147.696747 m 140.241272 142.158890 l 140.241272 142.158890 m 139.099777 147.696747 l 255.561874 487.824097 m 236.558792 517.915588 l 150.213531 127.892273 m 139.099762 147.696747 l 139.099747 147.696747 m 150.213531 127.892288 l 150.213531 127.892258 m 151.064697 128.782333 l 151.064697 128.782364 m 150.213531 127.892288 l 140.241272 142.158905 m 133.407059 162.885162 l 133.407043 162.885147 m 140.241272 142.158890 l 133.407059 162.885147 m 133.584366 152.977112 l 133.584366 152.977112 m 133.407043 162.885147 l 125.957626 161.772675 m 130.073044 173.748840 l 130.073059 173.748840 m 125.957642 161.772659 l 74.707932 166.633499 m 66.514236 174.003830 l 74.707932 166.633499 l 152.490295 79.096725 m 151.726837 75.082275 l 151.726807 75.082291 m 152.490295 79.096741 l 130.073059 173.748840 m 133.584366 152.977112 l 133.584366 152.977112 m 130.073029 173.748840 l 111.046494 113.546768 m 104.952438 113.116364 l 111.046509 113.546768 l 58.907898 218.730209 m 62.019119 207.141815 l 62.019104 207.141815 m 58.907898 218.730209 l 96.114441 137.312180 m 104.952438 113.116364 l 104.952454 113.116348 m 96.114456 137.312164 l 62.019089 207.141815 m 66.514221 174.003845 l 62.019089 207.141815 l 124.641769 182.754486 m 125.957626 161.772675 l 125.957626 161.772659 m 124.641754 182.754456 l 165.929337 111.226257 m 151.064728 128.782379 l 165.929352 111.226242 l 124.641800 182.754456 m 114.592987 172.629837 l 114.592987 172.629822 m 124.641769 182.754440 l 74.707962 166.633514 m 86.576111 138.124863 l 86.576096 138.124847 m 74.707932 166.633499 l 127.482391 92.116806 m 111.046494 113.546768 l 111.046463 113.546738 m 127.482391 92.116791 l 151.726837 75.082275 m 127.482391 92.116791 l 127.482407 92.116791 m 151.726837 75.082275 l 96.114456 137.312164 m 86.576126 138.124847 l 86.576111 138.124863 m 96.114410 137.312180 l 152.490295 79.096741 m 177.994873 65.882599 l 177.994873 65.882599 m 152.490280 79.096710 l 115.673035 194.797211 m 114.592987 172.629852 l 114.592987 172.629837 m 115.673035 194.797180 l 129.976059 92.980438 m 127.482407 92.116791 l 127.482391 92.116791 m 129.976044 92.980438 l 124.641754 182.754456 m 117.859375 150.339478 l 117.859406 150.339478 m 124.641800 182.754456 l 87.035095 163.695618 m 86.576111 138.124863 l 87.035110 163.695602 l 66.514221 174.003845 m 71.333954 197.460693 l 71.333954 197.460663 m 66.514236 174.003830 l 130.720474 140.866638 m 130.073059 173.748840 l 130.073029 173.748840 m 130.720459 140.866638 l 176.881958 93.288940 m 197.651764 81.552689 l 197.651749 81.552689 m 176.881943 93.288940 l 115.673035 194.797195 m 100.995987 190.665558 l 100.996002 190.665558 m 115.673050 194.797195 l 165.929321 111.226212 m 169.902573 108.531677 l 169.902557 108.531662 m 165.929321 111.226212 l 133.407059 162.885162 m 140.098267 132.616089 l 140.098267 132.616089 m 133.407059 162.885147 l 139.099762 147.696747 m 149.504242 122.436096 l 149.504242 122.436111 m 139.099762 147.696747 l 176.881943 93.288940 m 192.463150 89.171143 l 192.463165 89.171143 m 176.881958 93.288971 l 115.673035 194.797180 m 101.228516 165.827850 l 101.228485 165.827850 m 115.673035 194.797195 l 178.806335 83.223358 m 197.651764 81.552689 l 178.806335 83.223358 l 173.753754 102.192734 m 192.463165 89.171143 l 192.463165 89.171143 m 173.753754 102.192734 l 96.114441 137.312164 m 111.046509 113.546753 l 111.046509 113.546768 m 96.114441 137.312180 l 177.994873 65.882599 m 179.212494 73.564789 l 179.212494 73.564789 m 177.994873 65.882599 l 236.558792 517.915588 m 115.673050 194.797180 l 115.673050 194.797195 m 236.558792 517.915649 l 162.492126 109.692139 m 151.064697 128.782364 l 151.064728 128.782379 m 162.492126 109.692139 l 87.035110 163.695602 m 74.707962 166.633514 l 74.707916 166.633514 m 87.035110 163.695618 l 178.806335 83.223358 m 200.958557 72.451218 l 200.958557 72.451202 m 178.806335 83.223358 l 125.957626 161.772644 m 117.859406 150.339432 l 117.859375 150.339478 m 125.957626 161.772659 l 111.046509 113.546768 m 129.976059 92.980453 l 129.976044 92.980438 m 111.046463 113.546738 l 101.228516 165.827850 m 114.592987 172.629837 l 114.593002 172.629822 m 101.228531 165.827850 l 114.592987 172.629837 m 117.859406 150.339478 l 114.592987 172.629852 l 107.240005 141.631729 l 107.240021 141.631699 m 114.593002 172.629822 l 87.035110 163.695587 m 96.114441 137.312164 l 96.114410 137.312180 m 87.035095 163.695618 l 129.976059 92.980453 m 151.726822 75.082275 l 151.726837 75.082275 m 129.976059 92.980438 l 101.228531 165.827850 m 96.114456 137.312180 l 96.114441 137.312164 m 101.228516 165.827835 l 107.240005 141.631729 m 111.046478 113.546738 l 111.046509 113.546753 m 107.240036 141.631714 l 71.333939 197.460663 m 74.707916 166.633499 l 74.707932 166.633499 m 71.333954 197.460663 l 107.240036 141.631714 m 96.114441 137.312164 l 96.114456 137.312180 m 107.240021 141.631729 l 173.753754 102.192734 m 186.205475 94.095840 l 173.753754 102.192719 l 125.957642 161.772659 m 130.720474 140.866638 l 130.720474 140.866638 m 125.957642 161.772659 l 133.584366 152.977112 m 130.720459 140.866623 l 130.720459 140.866638 m 133.584366 152.977112 l 71.333954 197.460693 m 62.019089 207.141815 l 62.019073 207.141815 m 71.333923 197.460663 l 74.707916 166.633499 m 85.209442 191.586670 l 85.209442 191.586700 m 74.707916 166.633514 l 151.726807 75.082275 m 177.994873 65.882614 l 177.994873 65.882599 m 151.726807 75.082291 l 87.035126 163.695618 m 100.996002 190.665558 l 100.996002 190.665527 m 87.035126 163.695587 l 111.046478 113.546738 m 118.269638 119.079742 l 118.269653 119.079727 m 111.046509 113.546768 l 100.995987 190.665558 m 101.228485 165.827850 l 101.228531 165.827850 m 100.996002 190.665527 l 101.228516 165.827835 m 87.035110 163.695587 l 87.035126 163.695587 m 101.228531 165.827850 l 140.098267 132.616089 m 140.241272 142.158905 l 140.241272 142.158890 m 140.098267 132.616074 l 125.957642 161.772659 m 125.276245 128.795502 l 125.276260 128.795486 m 125.957626 161.772644 l 133.584366 152.977112 m 140.098267 132.616089 l 140.098267 132.616074 m 133.584366 152.977097 l 107.240021 141.631729 m 101.228531 165.827850 l 101.228531 165.827850 m 107.240021 141.631699 l 107.240005 141.631729 m 117.859406 150.339478 l 107.240051 141.631744 l 200.958557 72.451218 m 179.212494 73.564789 l 179.212509 73.564789 m 200.958588 72.451248 l 149.504242 122.436111 m 140.241287 142.158905 l 140.241272 142.158890 m 149.504242 122.436111 l 236.558792 517.915649 m 100.996002 190.665558 l 100.996002 190.665527 m 236.558792 517.915527 l 100.996002 190.665558 m 85.209457 191.586685 l 85.209457 191.586670 m 100.996002 190.665527 l 149.504242 122.436096 m 150.213531 127.892273 l 150.213531 127.892288 m 149.504211 122.436096 l 118.269638 119.079727 m 129.976059 92.980423 l 129.976059 92.980453 m 118.269653 119.079727 l 85.209457 191.586685 m 87.035126 163.695618 l 87.035110 163.695618 m 85.209442 191.586700 l 97.093597 514.659058 m 58.907898 218.730209 l 58.907913 218.730240 m 97.093582 514.659058 l 117.859390 150.339478 m 118.269623 119.079727 l 118.269638 119.079727 m 117.859406 150.339478 l 107.240051 141.631744 m 118.269638 119.079727 l 118.269638 119.079742 m 107.240005 141.631729 l 186.205475 94.095840 m 169.902573 108.531693 l 169.902573 108.531677 m 186.205475 94.095840 l 85.209442 191.586670 m 71.333939 197.460663 l 71.333939 197.460663 m 85.209442 191.586670 l 117.859406 150.339432 m 125.276260 128.795486 l 125.276245 128.795517 m 117.859390 150.339478 l 151.726822 75.082275 m 150.841370 75.715332 l 150.841339 75.715286 m 151.726807 75.082245 l 162.492126 109.692123 m 150.213531 127.892288 l 162.492126 109.692139 l 133.584366 152.977097 m 138.648270 120.708374 l 138.648285 120.708389 m 133.584366 152.977112 l 125.276245 128.795502 m 130.720474 140.866638 l 130.720474 140.866623 m 125.276245 128.795502 l 129.976059 92.980423 m 133.050095 98.812500 l 133.050110 98.812515 m 129.976059 92.980453 l 97.093582 514.659058 m 83.418930 483.120514 l 125.276245 128.795502 m 118.269638 119.079727 l 118.269623 119.079727 m 125.276245 128.795517 l 129.976059 92.980453 m 150.841370 75.715332 l 129.976059 92.980453 l 179.212494 73.564789 m 201.835129 63.299713 l 201.835159 63.299744 m 179.212509 73.564789 l 151.726807 75.082245 m 175.295364 61.542252 l 175.295364 61.542297 m 151.726807 75.082275 l 165.929321 111.226212 m 162.492126 109.692108 l 162.492126 109.692139 m 165.929337 111.226257 l 133.050095 98.812500 m 118.269638 119.079727 l 133.050095 98.812500 l 140.241287 142.158905 m 149.072693 113.199677 l 149.072693 113.199677 m 140.241272 142.158890 l 179.867401 95.481293 m 169.902557 108.531662 l 169.902573 108.531693 m 179.867401 95.481308 l 97.093597 514.659058 m 62.019104 207.141830 l 62.019119 207.141815 m 97.093597 514.659058 l 125.276260 128.795486 m 133.050095 98.812485 l 133.050095 98.812500 m 125.276245 128.795502 l 138.648270 120.708389 m 130.720444 140.866623 l 130.720459 140.866623 m 138.648285 120.708389 l 175.295349 61.542267 m 177.994873 65.882599 l 177.994873 65.882614 m 175.295364 61.542297 l 130.720444 140.866623 m 136.139236 108.667389 l 136.139252 108.667389 m 130.720474 140.866623 l 192.463150 89.171143 m 197.651749 81.552689 l 197.651764 81.552719 m 192.463150 89.171158 l 140.098282 132.616089 m 138.648285 120.708389 l 138.648270 120.708374 m 140.098267 132.616074 l 133.050079 98.812469 m 150.841339 75.715271 l 150.841370 75.715332 m 133.050110 98.812515 l 197.651764 81.552689 m 200.958557 72.451202 l 200.958557 72.451233 m 197.651764 81.552689 l 125.276245 128.795502 m 136.139252 108.667389 l 136.139236 108.667374 m 125.276260 128.795486 l 160.213348 104.011459 m 150.213531 127.892288 l 160.213364 104.011459 l 201.835129 63.299713 m 177.994873 65.882599 l 177.994888 65.882614 m 201.835144 63.299728 l 136.139236 108.667404 m 133.050095 98.812515 l 133.050095 98.812485 m 136.139236 108.667374 l 179.867401 95.481323 m 165.929352 111.226257 l 165.929321 111.226212 m 179.867401 95.481293 l 150.841370 75.715332 m 175.295380 61.542297 l 175.295364 61.542252 m 150.841339 75.715286 l 192.463165 89.171127 m 186.205475 94.095825 l 186.205475 94.095840 m 192.463165 89.171143 l 149.072693 113.199677 m 140.098267 132.616074 l 149.072693 113.199677 l 138.648285 120.708405 m 136.139236 108.667374 l 136.139236 108.667389 m 138.648270 120.708389 l 149.072693 113.199677 m 149.504242 122.436111 l 149.504242 122.436081 m 149.072693 113.199677 l 204.239990 539.319458 m 100.996002 190.665527 l 204.240005 539.319458 l 133.050095 98.812515 m 149.990753 81.089066 l 149.990738 81.089020 m 133.050079 98.812469 l 150.841339 75.715271 m 149.990738 81.089020 l 149.990753 81.089035 m 150.841354 75.715317 l 201.835144 63.299698 m 200.958557 72.451218 l 200.958588 72.451248 m 201.835159 63.299744 l 140.098267 132.616074 m 149.010529 101.787933 l 149.010513 101.787918 m 140.098282 132.616089 l 177.994873 65.882599 m 200.076508 55.593552 l 200.076508 55.593536 m 177.994888 65.882614 l 236.558792 517.915527 m 204.239990 539.319458 l 204.240005 539.319458 m 85.209457 191.586670 l 204.239975 539.319458 l 125.952835 537.715698 m 62.019073 207.141815 l 62.019104 207.141830 m 125.952881 537.715698 l 160.213379 104.011459 m 149.504242 122.436096 l 149.504211 122.436096 m 160.213348 104.011459 l 136.139236 108.667404 m 149.990753 81.089066 l 136.139236 108.667404 l 174.501617 92.986603 m 165.929321 111.226212 l 165.929352 111.226257 m 174.501617 92.986618 l 150.841354 75.715317 m 171.522003 61.452866 l 171.522034 61.452911 m 150.841370 75.715332 l 138.648285 120.708389 m 149.010513 101.787918 l 149.010529 101.787949 m 138.648285 120.708389 l 160.213364 104.011459 m 162.492126 109.692123 l 162.492126 109.692123 m 160.213379 104.011459 l 138.648285 120.708389 m 149.337311 90.350693 l 149.337311 90.350677 m 138.648285 120.708405 l 186.205490 94.095856 m 179.867401 95.481293 l 179.867401 95.481308 m 186.205475 94.095840 l 125.952835 537.715698 m 71.333954 197.460693 l 71.333923 197.460663 m 125.952835 537.715698 l 149.337311 90.350708 m 136.139236 108.667404 l 136.139236 108.667374 m 149.337311 90.350677 l 175.295380 61.542297 m 171.522034 61.452911 l 171.522003 61.452881 m 175.295349 61.542267 l 164.503601 546.653687 m 71.333939 197.460663 l 71.333954 197.460693 m 164.503601 546.653687 l 159.556870 95.038193 m 149.504242 122.436081 l 149.504242 122.436096 m 159.556870 95.038193 l 85.209442 191.586670 m 164.503601 546.653687 l 164.503601 546.653687 m 85.209457 191.586670 l 149.072708 113.199707 m 149.010529 101.787964 l 149.010529 101.787933 m 149.072693 113.199677 l 175.295349 61.542252 m 200.076508 55.593536 l 200.076508 55.593552 m 175.295349 61.542267 l 149.990753 81.089066 m 149.337311 90.350708 l 149.337311 90.350708 m 149.990753 81.089035 l 171.522018 61.452866 l 171.522003 61.452866 m 149.990753 81.089035 l 174.501617 92.986588 m 162.492142 109.692123 l 162.492126 109.692108 m 174.501617 92.986603 l 201.835144 63.299728 m 200.076508 55.593536 l 200.076508 55.593552 m 201.835144 63.299728 l 149.337311 90.350693 m 149.010529 101.787949 l 149.010498 101.787903 m 149.337311 90.350677 l 197.651749 81.552689 m 206.679077 77.680267 l 206.679077 77.680283 m 197.651764 81.552719 l 197.651764 81.552689 m 213.176987 71.489899 l 213.176971 71.489899 m 197.651749 81.552689 l 192.463150 89.171158 m 206.679077 77.680283 l 206.679077 77.680252 m 192.463165 89.171127 l 159.556870 95.038193 m 149.072693 113.199677 l 149.072693 113.199677 m 159.556870 95.038193 l 125.952881 537.715698 m 97.093597 514.659058 l 175.295349 61.542267 m 195.887970 50.689789 l 195.887970 50.689774 m 175.295349 61.542252 l 200.958557 72.451233 m 213.176971 71.489883 l 213.176987 71.489899 m 200.958557 72.451233 l 149.990753 81.089035 m 167.319077 65.809906 l 149.990753 81.089035 l 192.463165 89.171127 m 198.923203 81.018707 l 198.923187 81.018692 m 192.463165 89.171127 l 174.501617 92.986618 m 179.867401 95.481323 l 179.867401 95.481293 m 174.501633 92.986633 l 162.492142 109.692123 m 171.073349 86.895584 l 171.073334 86.895584 m 162.492126 109.692123 l 160.701721 84.343384 m 149.072708 113.199707 l 149.072693 113.199677 m 160.701721 84.343369 l 200.958557 72.451218 m 217.374176 63.435715 l 217.374176 63.435730 m 200.958557 72.451233 l 160.213379 104.011490 m 159.556870 95.038208 l 159.556870 95.038193 m 160.213379 104.011459 l 149.337311 90.350677 m 167.319061 65.809906 l 167.319077 65.809906 m 149.337311 90.350708 l 198.923187 81.018707 m 186.205475 94.095825 l 198.923187 81.018692 l 171.522018 61.452866 m 167.319077 65.809906 l 167.319077 65.809921 m 171.522003 61.452881 l 149.010529 101.787964 m 160.701721 84.343384 l 160.701721 84.343369 m 149.010498 101.787949 l 163.464478 73.937088 l 163.464478 73.937042 m 149.010498 101.787903 l 149.337311 90.350677 m 163.464478 73.937042 l 163.464478 73.937073 m 149.337311 90.350677 l 171.522003 61.452881 m 195.887970 50.689789 l 171.522003 61.452881 l 201.835144 63.299728 m 217.374176 63.435730 l 217.374176 63.435715 m 201.835144 63.299698 l 204.239975 539.319458 m 164.503601 546.653687 l 191.150528 80.931229 m 186.205490 94.095856 l 186.205475 94.095825 m 191.150513 80.931229 l 195.887970 50.689758 m 200.076508 55.593536 l 195.887970 50.689774 l 171.073349 86.895599 m 160.213379 104.011490 l 160.213379 104.011459 m 171.073334 86.895584 l 201.835144 63.299728 m 218.556000 54.800629 l 218.556000 54.800613 m 201.835144 63.299728 l 164.503601 546.653687 m 125.952835 537.715698 l 160.701721 84.343369 m 159.556870 95.038193 l 159.556854 95.038208 m 160.701721 84.343369 l 167.319061 65.809906 m 163.464478 73.937073 l 163.464478 73.937103 m 167.319061 65.809921 l 171.522003 61.452881 m 189.912567 49.562164 l 189.912552 49.562164 m 171.522003 61.452881 l 179.867401 95.481293 m 191.150528 80.931229 l 191.150513 80.931229 m 179.867401 95.481308 l 170.272827 78.164185 m 160.213379 104.011490 l 160.213379 104.011490 m 170.272842 78.164185 l 171.073349 86.895584 m 174.501617 92.986588 l 174.501617 92.986603 m 171.073334 86.895599 l 163.464478 73.937088 m 160.701721 84.343369 l 160.701721 84.343353 m 163.464478 73.937073 l 200.076508 55.593536 m 218.556000 54.800613 l 218.556000 54.800629 m 200.076508 55.593552 l 184.649216 77.353851 m 179.867401 95.481293 l 179.867401 95.481308 m 184.649216 77.353851 l 189.912567 49.562164 m 167.319077 65.809921 l 167.319061 65.809921 m 189.912552 49.562164 l 206.679077 77.680267 m 213.176971 71.489899 l 213.176971 71.489914 m 206.679077 77.680283 l 198.923203 81.018707 m 206.679077 77.680252 l 206.679077 77.680267 m 198.923203 81.018707 l 159.556870 95.038208 m 170.272827 78.164185 l 159.556870 95.038208 l 195.887970 50.689789 m 189.912552 49.562164 l 189.912552 49.562149 m 195.887970 50.689774 l 213.176971 71.489883 m 217.374176 63.435730 l 217.374176 63.435745 m 213.176971 71.489929 l 200.076508 55.593536 m 216.450821 47.009827 l 216.450821 47.009827 m 200.076508 55.593536 l 167.319061 65.809921 m 183.176208 52.552933 l 167.319061 65.809921 l 172.332230 68.325043 m 159.556854 95.038208 l 159.556870 95.038208 m 172.332214 68.325012 l 184.649216 77.353851 m 174.501617 92.986603 l 174.501633 92.986633 m 184.649216 77.353851 l 163.464478 73.937103 m 183.176224 52.552963 l 183.176208 52.552933 m 163.464478 73.937103 l 191.150513 80.931229 m 198.923187 81.018707 l 198.923203 81.018692 m 191.150528 80.931198 l 170.272842 78.164185 m 171.073349 86.895599 l 170.272842 78.164169 l 160.701721 84.343369 m 172.332230 68.325043 l 160.701721 84.343369 l 217.374176 63.435730 m 218.556000 54.800613 l 218.556000 54.800613 m 217.374176 63.435699 l 176.919373 59.212173 m 160.701721 84.343353 l 160.701721 84.343369 m 176.919373 59.212219 l 163.464478 73.937073 m 176.919373 59.212173 l 176.919373 59.212219 m 163.464478 73.937103 l 216.450821 47.009827 m 195.887970 50.689758 l 195.887970 50.689758 m 216.450806 47.009796 l 174.501617 92.986603 m 180.569336 70.792038 l 180.569336 70.792038 m 174.501617 92.986603 l 189.912552 49.562164 m 183.176208 52.552933 l 183.176224 52.552917 m 189.912567 49.562164 l 195.887970 50.689774 m 211.318008 41.430130 l 211.318008 41.430084 m 195.887970 50.689758 l 184.649216 77.353851 m 191.150513 80.931229 l 184.649216 77.353821 l 172.332214 68.325012 m 170.272827 78.164185 l 170.272842 78.164200 m 172.332245 68.325058 l 180.569336 70.792053 m 171.073349 86.895599 l 171.073334 86.895599 m 180.569336 70.792038 l 216.450821 47.009842 m 218.556000 54.800659 l 218.556000 54.800613 m 216.450821 47.009827 l 183.176224 52.552963 m 176.919373 59.212219 l 176.919373 59.212173 m 183.176208 52.552917 l 176.919373 59.212219 m 172.332230 68.325043 l 172.332245 68.325043 m 176.919373 59.212219 l 189.912567 49.562180 m 211.318008 41.430130 l 211.318008 41.430130 m 189.912552 49.562149 l 179.710419 62.306931 m 171.073349 86.895599 l 171.073349 86.895599 m 179.710419 62.306961 l 206.679077 77.680283 m 214.495056 66.393814 l 214.495056 66.393799 m 206.679077 77.680267 l 213.176971 71.489914 m 214.495056 66.393814 l 214.495056 66.393814 m 213.176971 71.489914 l 206.256287 68.172470 m 206.679077 77.680267 l 206.679077 77.680267 m 206.256271 68.172470 l 213.176971 71.489929 m 221.449310 61.624741 l 213.176971 71.489914 l 206.256287 68.172470 m 198.923203 81.018692 l 198.923203 81.018707 m 206.256287 68.172470 l 189.912567 49.562164 m 203.971161 39.124527 l 203.971161 39.124542 m 189.912567 49.562180 l 217.374176 63.435730 m 221.449310 61.624741 l 221.449310 61.624741 m 217.374176 63.435745 l 180.569336 70.792038 m 184.649216 77.353851 l 184.649216 77.353836 m 180.569336 70.792023 l 198.923203 81.018692 m 198.068604 66.662003 l 198.923203 81.018692 l 179.710419 62.306931 m 170.272827 78.164124 l 170.272842 78.164169 m 179.710419 62.306931 l 216.450806 47.009796 m 211.318008 41.430084 l 211.318008 41.430115 m 216.450821 47.009827 l 225.973022 54.621185 m 217.374176 63.435730 l 217.374176 63.435699 m 225.973022 54.621155 l 183.176208 52.552917 m 203.971161 39.124512 l 203.971161 39.124527 m 183.176224 52.552917 l 182.322662 53.383850 m 170.272842 78.164200 l 170.272827 78.164124 m 182.322662 53.383774 l 198.068604 66.662018 m 191.150513 80.931229 l 191.150528 80.931198 m 198.068604 66.662003 l 183.176208 52.552917 m 195.689392 40.605148 l 195.689377 40.605148 m 183.176208 52.552917 l 218.556000 54.800629 m 225.973022 54.621155 l 225.973022 54.621155 m 218.556000 54.800613 l 172.332245 68.325058 m 182.322662 53.383850 l 182.322662 53.383804 m 172.332245 68.325027 l 191.282654 62.066956 m 191.150513 80.931229 l 191.282669 62.066986 l 195.689392 40.605148 m 176.919373 59.212173 l 176.919373 59.212219 m 195.689392 40.605179 l 187.993423 45.666519 m 172.332245 68.325043 l 172.332245 68.325027 m 187.993408 45.666504 l 176.919373 59.212219 m 187.993423 45.666519 l 187.993408 45.666519 m 176.919373 59.212219 l 179.710419 62.306961 m 180.569336 70.792053 l 180.569336 70.792007 m 179.710419 62.306915 l 218.556000 54.800659 m 227.271545 46.508987 l 227.271561 46.508957 m 218.556000 54.800629 l 211.318008 41.430130 m 203.971161 39.124542 l 203.971161 39.124512 m 211.317993 41.430099 l 184.649216 77.353821 m 191.282654 62.066956 l 191.282684 62.066986 m 184.649231 77.353851 l 227.271545 46.508987 m 216.450821 47.009842 l 227.271561 46.508987 l 187.072845 55.094650 m 184.649216 77.353836 l 184.649231 77.353851 m 187.072876 55.094666 l 182.322662 53.383774 m 179.710419 62.306931 l 179.710419 62.306946 m 182.322662 53.383804 l 206.256271 68.172470 m 214.495056 66.393799 l 203.971161 39.124512 m 195.689377 40.605148 l 195.689392 40.605194 m 203.971176 39.124573 l 214.495056 66.393814 m 221.449310 61.624741 l 225.045425 38.643768 m 216.450821 47.009842 l 216.450821 47.009827 m 225.045410 38.643723 l 198.068604 66.662003 m 206.256287 68.172470 l 187.072876 55.094666 m 180.569336 70.792038 l 180.569336 70.792023 m 187.072845 55.094650 l 187.993408 45.666504 m 182.322662 53.383804 l 187.993408 45.666489 l 195.689392 40.605179 m 187.993408 45.666519 l 187.993408 45.666519 m 195.689377 40.605164 l 221.449310 61.624741 m 225.973022 54.621185 l 211.317993 41.430099 m 225.045410 38.643768 l 225.045410 38.643723 m 211.318008 41.430115 l 180.569336 70.792038 m 186.235779 46.889252 l 186.235779 46.889175 m 180.569336 70.792007 l 191.282669 62.066986 m 198.068604 66.662018 l 219.589279 32.409119 m 211.317993 41.430099 l 211.317993 41.430099 m 219.589279 32.409119 l 225.973022 54.621155 m 227.271561 46.508957 l 186.235779 46.889236 m 179.710419 62.306931 l 179.710419 62.306915 m 186.235779 46.889175 l 203.971161 39.124527 m 219.589294 32.409149 l 219.589279 32.409119 m 203.971161 39.124512 l 187.072876 55.094666 m 191.282684 62.066986 l 189.009125 38.875778 m 179.710419 62.306946 l 179.710419 62.306931 m 189.009155 38.875793 l 211.801346 28.964355 m 203.971161 39.124527 l 203.971176 39.124573 m 211.801361 28.964386 l 182.322662 53.383804 m 189.009125 38.875778 l 189.009155 38.875809 m 182.322662 53.383835 l 227.271561 46.508987 m 225.045425 38.643768 l 211.801361 28.964386 m 195.689392 40.605194 l 195.689392 40.605179 m 211.801346 28.964355 l 194.968323 32.512451 m 182.322662 53.383804 l 182.322662 53.383835 m 194.968338 32.512466 l 195.689377 40.605164 m 203.060669 28.990219 l 203.060669 28.990189 m 195.689392 40.605179 l 194.968323 32.512482 m 187.993408 45.666519 l 187.993408 45.666489 m 194.968323 32.512451 l 187.993408 45.666519 m 203.060669 28.990219 l 187.993408 45.666519 l 186.235779 46.889252 m 187.072876 55.094666 l 225.045410 38.643768 m 219.589279 32.409119 l 189.009155 38.875793 m 186.235779 46.889236 l 219.589294 32.409149 m 211.801346 28.964355 l 194.968338 32.512466 m 189.009155 38.875809 l 211.801346 28.964355 m 203.060669 28.990189 l 203.060669 28.990219 m 194.968323 32.512482 l S 1 w 0.9 0.4 0.4 RG 20.000000 508.000000 m 64.990372 503.822876 l S 1 w 0.4 0.9 0.4 RG 20.000000 508.000000 m 9.601746 477.066467 l S 1 w 0.4 0.4 0.9 RG 20.000000 508.000000 m 15.532257 540.504150 l S endstream endobj 5 0 obj 41008 endobj 6 0 obj << /Type /Page /Parent 3 0 R /MediaBox [9 28 259 547] /Contents 4 0 R /Resources << /ProcSet [/PDF /Text /ImageB /ImageC] %/ImageI /ExtGState << /GSa 7 0 R >> /Shading << >> /XObject << >> /Font << >> >> >> endobj 7 0 obj << /Type /ExtGState /SA false /SM 0.02 /OP false /op false /OPM 0 /BG2 /Default /UCR2 /Default /TR2 /Default >> endobj xref 0 8 0000000000 65535 f 0000000009 00000 n 0000000150 00000 n 0000000199 00000 n 0000000256 00000 n 0000041316 00000 n 0000041337 00000 n 0000041560 00000 n trailer << /Size 8 /Info 1 0 R /Root 2 0 R >> startxref 41687 %%EOF trunk-2018.02b/doc/sphinx/fig/funnel.png000066400000000000000000001027441324306050200200160ustar00rootroot00000000000000PNG  IHDRi pHYsHHFk> vpAgIDATx흿-{s}im ?gm>rIgE#^}kM A96E;S9.G>n7hro4BTI|qoooooo_WZX\?WeKWcm47OE6 ).e1G=zw}w7 =7:е\ܲy\ J_= /=FcRݝ>˕n-]o(__?o?+=FArqQe1i駟~z뭷zK3Z]\>E@nK.oo_h>裏>ok ?~,d=ߑb7 S{x(;??n\;D֢r(@xt"йڎ~.uЯQ k[DuQn\/H|$$(=(~.i߽m:WnD/)r0*z3Q6Yk,M{{)HJ}Q"_|g?pZXots3#tD[|=_V瞟~kEkВQٌK/=>_t\|cMBףGZv%{3\Dn~h[[PWp|7d&5 a}QH. -W"k1.(Y«ɓ'KRϾji7΀HU N:,sj;z)߉^r_~َiGGߢv&rgD^v'hPUjR㨨&EKڙ+$msk'[~_~8Xsr=%s͖@Jeoۖe5\磝In,GL_׿ޓoT#kmg_-sssss縘-#qfi7ƅ,>|POg8IR{h]!3%ǵ's=ץRzY||$ͱs!wm7faYU=[ÖL'|I\7P# !\ ulO q}0'V2X' WB5fdrO>UDl7n_F8ȖY:Ha˄1a)y䳾PzTkeHaeq!W_}Ӗzɓ?Q{~'w~2ty RC784zZdҭ:S^t<*DD藝//,+h ?We-ܢϏ?~-_ˀ_|W-ZBu^z%Ų~/Pgg8~YFs'.qLNUCwȲWbr~ַ< J|'So>#_X́-Q"-߇tFg]9k!|ūRL"Io>˃d%IL%Ph;ĥ5_'zs.Tۏ.1Ep| Gt.0h~_'?y'?٫ N2Ȋ$N_u5}3hN Wx \8S;wx(Tɡ[ 2 Joӵ ^A`dֈwSl NAhr?*1#P9J0%W<$t^Z׎ܗu52qR7Gܽ(3,XՕwwr߲NF6S` s<01:F|aVsE:kwIv0Rt6PA[9"K۳f%lk Ԗc;K2qLFouR v}AkyϬȍ͐3+{vK#Z/C]߷{ 2GDT` j4t8џYWgwz2Nx>[ȽcrrW8Ϋ:b > 2 <;oTTq=$drfLuuwъ{vs1r s,pŮ㴟\ǽ3N|g%wFLq,ѡ-6 s,@^w)Xř\.Ȝ/q߾[8a#s8=S GYCʸȽca@rDAgM\{w`vm9 +Q. [9.VvJ=୶!Tc XWu^aނQ@Bk:Yv3,_iDzYj 2GĜ#2["w0K\{U1Gjlv{-uYfba&(ju ϔ̙ΎݱY2dɝSᇿ݇ s,\&ˌc;e 3̵"A($v<9_ /_ s !\-½ʹݗpQZj2ɡڂ̱@'dWo? -R}>+5_N,q nf߆ 1HuAs 'g?ڳgrzϫn MޞR=X6okp?"{o|]o}=~羚i`_TdSVgVњG}Gi>4 ΡfвΛ޼ǎDЯ:#ʠZ7XמZ-MUr`N 3,%ߟÕ0u"̓4[#~RqXdJG9Qt"wκ]HT+N&RۏM*=chL\rx*S!ᄒ3#XmwvgGW$i}e6ET1!r,YF3gi_m?s s 7dž{И`i `2[JοYf("E5^]Ymgüz'z sYݞx23>G|\"dUNdGDή!ޓMrgmȵx%=Sc~,})seC"4 3)90N=nGW)\~Ew /k mhGx5D- QǷu.uGPsa@o hrNGeܒewPQ9D?Gse!MD=Zf_s8ǯY<Q qmvADA.]c.Tl.dU6x$ˌ`8g5;: C[ŽVyܰLh>'=6ω݋LBii|h:f.MK^a侥-'sĥH9ncWZ#6&GaPtq侥uRG&QwbQQ#Gx7-!SﭺlI2Q -cG;ec&!|=f m#"3mvmYf}(k(]kV9"q{vzmYG~c[ZU]:ZOj=*8s}vo uWݿ3Mz7ˠkmXuZ{Y3~a!sĥ(3#"ȵ57vX{uNb:er 앾"M".(4t -T+0"r?2^۷'wN̻r{U?G#iK39 (0h?aomxwy^NJ-UT55 KP̥=*| Q:/ ]h ]X5ir?H>oo3Ow*~;"\buQaLG]14¶YDi5iY$`*5K4+LrAƻ/>.bSgevq+BPѬ2[1myisԩt(k_h3v6T8&=ψǤȨ/:#DnFEE_<r9338쾜 MwIeѣG=@djb3Kd1skǔHڲͮ|{NwCnt+Nz*Qޣh}8Y:!v|Sρč&vYn3FGcy;U6~@"ð-5AV3+]x|hLJE ھvR`wņw2I>4*M}ʿҍ&RFݸEy܎UNTԩ#D-8F{`(m&6{H4rw͡dYDA妑KM5RNgjRbf+RcziTx_j=R2>4*h٤zi\sXT} s [oKE,Vy8{-ldT_ftn]p ݕYcs gR9p3ƴy#xmyv}d8MCdCrrFuMc']Rٍ[xzsӱ(R\e22o${3;KYl`|Zm$DЉsٍ[pI֘+ߖDEhr8Q9 C$Vvc\"#7 t隳gF}M"Ą~idoopmG}v,{+WEn3N&OM:JR.ȌpU}ZzH* |rmwObRDLЈ%Vr /훟h1<d]XfYaykˎK>whOOmIAYIGvhU3‡sPG6 mb+cR]cjQy)q}&QQ+r~MfSzzl2爲vqZ-s"Zb!$BD9d̲Ga]IUVO#5꿦j4O\WO\y4Ϩ)hĜѢYq*B#R5ȽTAwR8q2y:e!#DmRFCt%f&W=w9GiYpYK ˦"k)dLƑ8re4Qm E3guw_}P[Ct-Ig+׵6'.Uo|Oܰ:3d8^+$e!ņ/Wecz[P8VX^|mDn Omlv_N ZQHd8oqvcAJu/b[8Q Fd_5f{& ;֫d:QX?L\^oVL`qTB6Af =p1:wk\&@T} Nwnb(B.Ȍ_}g8NT;=#>xE0DMXnq&ezt7~tBVqxa%Se݇MAMN#]ܣڞDCDBy}ʼnX%vzϣ@))kgz_3^IqF1#[#ȳFؼ:m޼q>?cv-``RIs/>mHk[ʜ%A6gK;e=Bu(2B,!me>uѷ:r8PpIM1+oڇerC8y5wREv.0KE9Bv( $_=Uzjc멠s, }Be.JI^`_}Q(}K0:9sNQ,aDka¹YQ7l$tQm/aDނ1e}ʺt(ZѣGHcifnz[$ D8'5C{7䚊44fRhe(z'*%f3ֵ|S$>xDIeȝ6]y\Pk*}GV+bQB64'Y)uKWmв̠q _u{a Y[qs5y"s4AC`c|?K|^ku~0GD{\c>z=#'.UǩHsyU kqr(_ |>xE6 ŏ֘/$ڮf_)3fR]!>ڽ;V()ϟ PyU9zl?#Čknz,E+Y~-s/__NL!3w))hu͙Z;=Rكko T)pe"t&!@u `; ch GV-E-B~_Ѣup>>1!b jWcVw4G֛ \QV>Lkrn :s ~ 6.˞p[+7m@[\Lf-c^6븕\_ΐD{V\.EUrv>x&y~\ ny}9k#;.Kj)3^#;A{=ym*iV>~ 8sh٤Z(8zt*OpIC믿amk FR53{%S" ZR>[0J:Ӎ}m^IվlI;o&WjC-L_򷘏!)dsYDFwTNq.Llr|A8-/HvRUk北Yl&!Kюs}YrvYіپډ˼.zD{? B}*.ewFqھ Jܢk$LQ4@7"eWNnyN{tFTRMHܬ2Q=h+#+M)G@/ͧ:ɷ8'QL=o<["o!gp0Q|||cT`mpv*/[ aL4 >ATK FQإǏ7qKN'S9-J:RR E0K梍@iρ&ACN"`eɝVxѣ#2_J\opr9!4%H\Yȯ<s)h}7x;%Ҳ2k˾Ayi_]R늹d[F0E?Au-55bct\"g Ot5۷{C!wC=!K1>Lز6:hQ_+N.Q[Ho 'a1MɝYQ6ݼŚ򑹌q)彽~>%s):s׷3}+@Z^^`NyG䄨}(h^vZ[X6 .[]ŔǙЖnlgxȒu[r} C"(X Y9#ڿ p|.-nUmo`ozY+-#郺) k)]ReXTڗ["%$_QI\Eĥa>C^+y4J !4[1t{4o<82D@m" Ndh"+9Fįwm3sK-HE3B3W}(t$-(G-n#ze^\(Gah];`7θy!՛.MʻkCWl^]QWxm5LI`n~Fb{%.U UⲗEz}h#6UDMry05ٿ㠣ev-tFփ$Ыh;)f_=oJP -m!<|Ç|VvK=WXaz_oG}=Q=־~-+R۫H=_ [ZԬ}nu+䣹ݧ 9#tMNxqI|H!r0z<]KlLE]qMd*cXVsX~RE"؞hrn#{0"I[7y<\UFI۫H2BR=˪]e,k-td~f25}&T*7%7?l߼.DY!oVͲ/nYΩߝ9f篳e\` ֬]b^yt U~e%W?9^]>xʒˎH'[Tc׋(rRҖKAƣ;X7fA&R8fqωuon3WB-G)hTЁel"QԦ2q"pY*cef]7`_x V]Z`*7G4]3pv)Ty V QGk{%͊cY}S0U mm2_@#qGR r8i杉jJۼζdMk^s*p_$ ttehr|iB(zeU q-NO\dZ_޺xogn{(p&MA;sW/xPp =JehmχzkPE՛Ƭ#/NyvB#BK=obOVZ#}YF"#RnR+ ynS0LNu)I*."-]'7 vd7R1{!<!z]/F6  p-Txp "Kl %+=,[V :J42Y|ytqYw^|`>E&=O%ܳ_1DO19Κ*LEYZ >|ْ׻WW낮Gxy}ٱ*b/C$wmIgȿzСg--8L\;ERSKjeg7IAJISo<X/ax~t*!d"ڂLTE+sd4Y/KpXMK’pIXc*{Td]o%p8w^\.,IC:%,^{ӔFFML#@Fv/NyaNr7cn/ruhygעzhr\bcb,^F"|W(a>"bmIkyMxd1N~.F_iEΕ#_6hr0tROdI8>ފ<هWl=A&U*,[=Jmn)gjlc$D{}Is2pe8Hc=goTL͉v=STǪ e4t@5:hd(>k<ڒk^p0WF]vRQ4URoбs%HGEm_c^]ڑ1oi/Uԋ /%)\54 E y< *6f2.T@y.-r_ɷn'#:maZgK˲hqK~J,fdGdKjZQ ?xݨsͮk q ߂W{*yvY6h̼:Yʒѵ%o!ύ!¤5Q.~AMiL|Xߜ-R#٧c矝۾-0F1z,q!ͨvm,:h-[Q- r~x6ene<f_ߵ=ŖE4[zG1z;22{RR;kpAF2H$/*U8瀁boY} ]R(^ҥvޫT[שn(anvڍ]\>ʭc: Ye|VNܺKIFhr_zHR۝hՉusJ3_v:W%B.ܨh3ZƗCo7_XdU<^bK]Z`44qh{u:S̓zܗEʴ%oAcTˡ볆6R6;)<:f4qКњ.&]#5^߰>wHYbc쭟˜_MGgJOCfEgԪNS{.燮36gZ,WhOQ:bbzv Hʵc"Jw/kӼJœ$ xvmv.۴ٵmЖΔ%VƧ0gNϋ >S#dy8FLxJ-[nyal 6at_+͟fhJ^4>0TMfVg \!hFiqCUDjte3KheeI_$p8=u8&W85]QWZc:#yУPbE4Z].NB⯫GS h0M VbХD`MAiW\t~r'}"MM9NcL4>T$Q |K4v޺ЙG|+k}N&^ڗILwlZ? sfIjS3sx=iJB[+fm,w*BEvN}j5(hL}k2,YU\4>|>G6;+/z~Pfc{4>fQ? 2h2W2r4*ZNzjh%?:{ș\&w}v7SX1~)mSc}}MgjABc#}*3MkRISmv^fz|e>_nxhKk;)NS:"WvGe#x;]tî!4ͷh4ZsrN8q{j 3Ƙhr_B]n$"z"ѫG|W<# TΔ+S]U׉쒏O=Ƙhr_J^=" iS5XEl>KRu}t+:Ժ72(IHmὗWoM+SnN+*WB/GdF0.~ņeo+3c.qAX毙{lhr_[89M(TrM}1z˜˺R15„R6' f"r!z/=FVФŻ~4sWsL}W.9Ų2:/ƆWΑS@!#xn3Y1TtŘ3}Em>U ЮL󷟩jT3QԷӼƋn^ZNc7"{Ǹˍ2Q:;r>q$tiQN-Y _#/{`7T=uiᙽnJBj~J^X=EҎeoW;޷>p{{{{{_)h&=1K {.g4*Qb X|`sYh&|7gje;h)MZ$vLx%[Ӿ֯LvZ9פ12Zs_X{3r^)9ƶuwyulX: tRdiJ'ޫ Kc2S7w-wƑEV 2x9דrr$9mἜBE/Nw_Kg.+/8 N0>swgf.;?QK/f 2s?2T_CqƗIjtYkO7civ\v5GA N*}+ChdYmB21fX$ɯeoLh5iR IYj{p_ yjHkf 3&yBD#G#{Sw~ fP%{:{%٧)Y`u[XK \+8˜`R){YoE}D+UGg膽l&1~Vc:sc735yqtt u9(E*; ^UKN%|S㯽=eg!s앚3>3'eSc];'{t|[:h9xn*liT.W?kRXUϝ WjBdJCLUh wܕw¼Ot?0$4M C-pr'nݭ%~Wt6q`]lsWLDIreڟaNۖWFԷ|׉&AZɝ[{@t\I.D]ã;TP]ִMxfC٧{E6;_wbjkLhbg=t91-G)Wc}S?r+ЕɻVF൭τU\D2sD龎#>vw۲agWWPL_r5뷈4N%-znf N:#d{]R-+v٧rB~ 4·FMG{ͫW|ze36m,pNsFa3qX%a1.Ċ\̢^5^#ch}1x EάR[wG:?Ts|CeV?[.+ߊ:nR nkjNk!4/ ݬ}?R3 RFvtɲF*iSNuW`\)8n>}*pyiLSl\ZYtj _FzH"`nwɅ`i_|$2'qLTƘW|NVd6?wj:oBΔWO{^] |iF8'_eEzjZނF D>&7pq,jrոftR? p#F0nҿvϐ/g5'BF ݝ Q:^X_"ǦDve {u9t%#+՞E=5p{Qo5B{}mHт|ZU/21md_D_/h8Zs_ y[T]iKV4h|.GdS\ӔYstuvU)mvRQuTjK)5ޫ˱A5-w٭^RQ>_rWJ左:Saߊ.E跣7VϏ޷g7u4阏(iz#7)Jz3ZVGSd:#_X^>Wk1aeUhrj|'Au-(0Iv><,ݡyr(Q1k7.sҝ<҆mg_I:PqOSkQ*VN3eXMj;A꽄#DtcyF[K/+p(ڄz$5_{W_шq Gj{鈎ר_Gh^0 _CFbk35lUE{rS<9hO#ϿF|tY* -s9ϐuǀ] DG#zh&jY?xe]5gJ^4߁_ ]CD(!b*bK Cm"_T"V"?r,6{c/t̅\xkkVT;?G(i=ET*#҅9B5g:KRc%07r])yn4ޫQq#ey Gّ(њ^VQ*o1Ӽλ׹? .- 9 Zn;v}7 f"~DٶFDbT{dF`#Ѯ+osӞ[}fkti5~Ag08s~Fc2^]}5hsRdnD4[/!óyF0< ]J6tTŒlh& LI/D.syG$D^X``dc ݛdFsWd-!B.'F (2Xtry*+5kB8KLA"TdrK}xQ,e̓-cJdEJⷸp?Cvߢј&ɠ5Wu'wBJp˻Rǟ yJ4 !߅_!9-Yr3] ƲPLhb%E()F[$WTz4 "g诗pd$23B 2HcR`aeT3)Z^W͍gVFc_4ςx'YJ+.}˷x^%+AWL: mtTqHBw}8&_Th5+^Fc_4Om^~1ݜxJ>+1Gy* qd&-mgQI*Է,HsvB%ߓtiƘhrE^+ƣԵ?#CD==ڇyLx92nR$=Z .>qvVrF7G/u_5ˢBNK1LDEgk~/7% &=Ξ^B咳Tɪ5>g@/ޑ_^m^OF`bCcxԾeaK 4^j'9m;8'E}#,%-zpIuc Gr}%'û_۽| QR` 9\8"dd2FWMK T>h]Z_ISFc/,3,K"Fݑ$DDc#8D‘z T0PgeC,óМ kM|`($FH1^]]7BnIRGFGU)˒ܓ }nJy=[nƌL*ןA 6rŋ'FcL4O?LWI?蘖,ɝ9hz# CeJ-=ꗔ_;i ]~'=K 4'ǩ "]Q4t%J.YfNluCͺ2fEױn"}3j>e|>mMҾz#B%PIy0WhcNZ.HۡۓvG G~8.0>+Ñ@WW#(;M{mdRE`-]`z0ۣOp>ctbWE.]Zq>诔k*Rmm3sͫ"FrC; qIj&$GfMd3Rv92y5qdhu^.TOk򙰄0xQG~gB{ n} .h0fF#OUiB:3Ok~^x@T -@|hr/dș95oq$Jp5-'wvQsh3Hw+ o+ahMAID[ZCzMJ5<(^o3 dHZ<" Tg-Cgk+6{^وM$Iy1cQJLErmdET*pFY13QȦ|g{+:) z+h|oeWu*-dI~Y >+;z]*erV ;3o{q&>lڠ-.Pqj1rO}Nڛu ʾ$IS]*x.D L5;_ mάFc <MG*q-[ty PD7}X11.9R gJQ_}vfk4~,mEdpM.6r®-t~FYaQ-^1"rkC{ "AR2-`IMtI&jәyF^UJIT}oE+n4 M@Ib ;1igѺfre g{rdע]1w#~v׌& qBJ:~2hZӢNi;S6!xu\~^e(}9Yk ~wM' zU[q*d͸IQgW-$t{r2.b:.5ʼn<-4BBN@Ned[8n5(rSH  +Y0e"PHJCh>"D*{/Tv1٧l)d'5D!f2woZ룅6/еWW# D4,Vdv.D<%*w-idxR._4#}2E7SK#;4"DYCCL8]%QʵөxceɠAWwCljzxt/ U>S|GUNDCW@Ϋf~7R׌&x`]V@I'/1-#*B,+ fF)]>hL6*8vd Lj4΍&ɐ=HbrZ;*We_y`Du ZtQ5%&KiFl X}㪱.tWhS;9B^ӑ{N]Rbu3+$,%yXm4B?XABzb)C'He#:[uu1YMgD |r˝Giro4~.I5^\HdB)P0TB$>spp\@P3BD;g5z%oATqb ,[9^˞U? :ߊ.fh M"Ihw"?ьfB>S҇fU=cعpvFBΫ-nI*f`e]xhUMg( =FBDfNR-SzQs8ns-5:9fȷ4&FChr'wԋ0cTWDɺ8e,Oʡ{3m:z+&FChr9U%ԉlM)8FַZY9/]ړ}#&F}( ()xݚNW"80~g=)#}ty F۞*x$O{> "5Pjl闷7n4M JgQ/ͱ)]CE"B-5ֱ(FO)]:btִYߥ'h\5IdodmJ=o'0‡ጞɑ[މIsJ2^X*d I-jD%%ERcDWc}Vǫcr_++qU]KYihM!g"kĔѪ%yܣFvVQdzkGsLfr̭՞NJofhr_ NX$Dߟvt^m|y'9EX,`9=]\Sִx!E~'4Es'n x#rZlvvwQ"`3} urOh4A YeaSH UJF6;qL QE/tz{15M C-J xW~7D47܍rI5fI 5HFa3Cq^@3Gpvxۈ4b=+\g{#2ڝOݟי#xpMh\^]Z锱MG+#,4fk ˽6*;>ړn[ZrVqn4ƖC pQ`HX~.t{X#ݱ: y3e8WATDt0j;+"cIux EBKckm\'W`dOR!&wʷ/^ViQ@EBVblXbڭڸNCuP#'!kTtWH;"(6I)C5)*z#ݰQv}#YkF6{.g%Zq܇%]ڟ[(4qr[>9s}t$9Ekd;W+RDM큒nŹGLhEdclM r:=52k:2Ѽrd1kFK6'9=^G{٣P(x0%8*g<iX+ [qeyF..Eи64o(Hpn۲*KN+'阙Ƶ}S8%yDd1 5r&H::: '0_xYE#AT 2hMc"SY>WtLattWj\HRmyσ-,6'.5'hM;Eah\j:'F-84ZԾΫЋ ג|c1M)"$[>FLhrQDEoJGW$c=rVve>,҂f ඼>kL\"C1'ܢikhM;e(&h++ϊNi(+(X+Hdty;:V9mH`Ȃ&kې}4(# Q0Dž>DXtZp+J1GpCiS_=ԉVo#uϔ^3y*@el4&zQxxB)Hnr9KOlDxF(hr$)R-)?"r٪t3twƤ}3 )pV$wWh:q4 7JK:T#[. 'uyz;~'|R>7&e 9yt9Òd\s> 7je)"wd}<+~uIq(OLaZ6龒K;][CYsUmsMtttP)m&`Yd[1^]sҸx}9EUΞM>^x -zmy^o 7*GH6&Nc)j!e~E9Z2s}M&2;S|X$-h҃ =G[h:UwMd+5x_ܤ-.K^(hukcd4 y!6pz"^NtdGqigΣ0GgD:vrVYW ;{qOOTv׷\}rIQؔmjOJG֮/!N #jdn#f27JM;RaF+ ]Da]~WM4'wm7h86#UtYohr?9<0-.7|Q}XVYH-Eٲ0;>$w_$Ce:Q6baxX"^pw՛Ф-p<ֈ 9l&;ZI$MR9#)Y~ow7~.Q$鞎_V+@8'<MWoArR[=E㚻|'p;R\;?wVF7F@Ґ/0y|lV"<ԫ èyNeE>տl4A£bHC$89O7&;i5"wϰj°L42!J+kQ7\oGkc/4_)$-rjHdWFu/*+!.f\X>g.oH{sɽ 0%<&%}釬ר]F¥2Τ$dw5oy 6/q׫[%ɽ-pw%] @@Wiqi)G9fhJX'(MHoE 뽢hroUڊQ`%R^4>rG x,Uwֆ7<(LWo8ZMD 3N6*dw13r7w=@k2 ٹTt,Mc)47Jp` ed:xQ*%xTR-޽6=V<ҽ'dEBM$p)Ѹ Mtԡ\ $b$eȑy.ړ2Tʖ Qs[i\&d8EiFn<*\NqQy\/!ZGG=ԥt܊w)1M CҨť +RFwNle.SHڇs#YL[:c974:V<{L)"92D/uh'E/9>f³˿­lTXSLd@tlҨ$3`INzD,B; 8[^g?9!zTxteYۣ\!c9os>+㾍oѸf47 4=Q4,OFK3ch$eeR Xx>jyZϳ&5Gk4MUŋ.Ɨ˥ʈu3' ][P\i J|3S|WlMQ<ѹRLCrtj/E' t[osYyzuyDx\Ζo]ӍD{cu0*iȷӡ6TY6@[XU$㢐/9< ʦ\HW5NTٯm^3qϹ)rxB[.cC H<[ѵra'G'@]'; S|Tx;HǮJsL`G?GW9o]wDҏ6΍&n)҄S<ʑhȨt{܋S)$q_BXiS<"=aA47vFdSP?t&;v$H}'?`<{ͣpO&Ϛe(kk@{cx''eRǏk5RQ)շސKq/8hZQ;n47sw+죹]a[|LZS۹5y5١C{c8P@cBҋ%RE؀W J#ܓ:K]ԘP3?pttQi%WϳX8zF 5 Gy} <;.޷6Jsɽ14*O(ub| ڭߵ6pO&"F]ò Kg̲-ZMGG{J G+W付hro Gǜ8qωuy]"_l0s-,mLQRύ{zRg(D&5j8w~1)Ӊ^/5HFI3pfE}\C){ M+tz0Uh2z!O/!GD)<##쥧M޸:0Y#@I 聒1n/ӎ7|G{(dlg1ۣɽq9HmD^dFwFe@PT-<^ ~eU Z1۠ɽ0Jo^j>-e5h62**j6u^8χGYރD=(X MƟQΞ2ݿʂ~v|T$uc9KINשhro4fhDa9;_L'w|S3rmto#9+h/h,ٳ.\h;r*w%<&OL#HyKűYꅈYA{ID hro4۞"V-Q0#{,.L_S5v(ۋ1pidk}x=xZ]7 =zѣ~ߎl{{ goX?~>>EFQE?sv˷SHtt.<m7Gz<ĐM:3F^[q<?1(=s[!VGz,͜6~e-驓C4 ZSU c}L21h/[LF{qH4>|J.()X`?x~}cյˌ%Ҭ]?㏩8?oxxh4.G=%` kOj ?6c3g<0%ʏu8Qirhro4N`A1FpeZ6ҥYtMƩp-EV<ֽj)=#2.y\ceGD{qZ3x]"Ϟ7D9GDJ;X&F9^#۟,s 3G8wѱ&: IDATFp[\|EyR=?VzN h\@FE L.4"ga(if`&FJ!"S$8)^t-EǤK$ɗ`T֯bn1$Q{:4=ӝHnkKK^YuIUD4cɽh|]Iǧ5 GsJD u"Ymd.Ɵ[Ycbd=uj:*/#sNlHrƟr`MBRQRWtv 6R=)],1Yy6O?O{h4pIA,k/Q&/]D{?,r ejhro4߀7׮)kxQ`U攺bgkt*G޿hYh| ȘtI7 q Оǀ![8+k\WF1""P:?)0wT5E-sFp4Dx&FD-"eFD匔ڇ5Z*7h4BE R"t:S򠅤kS)ɽh|ဤxvk\2H$h|ݣǹ77@J=)^}r{\ĪB$u3Dh4J`I'w</[\A"=ޡc1yG@92F ߽:qZ3A#hOJ."h/i9`/kO9257 `8#Q4-戈 5Zds4#2Q}k 02Q1%jHcG_#Rqh4&LPZ\ H.xmȊ'OuFh4.7f=.ZQ 3H0 ֎מڧbSs? MFc5J\WHJ+B8Fwbj4B]Ԯ:}{=sR>=h4vHYdf,[%tEXtcreate-date2010-03-30T11:34:12+02:00"%tEXtmodify-date2010-03-30T11:34:12+02:00ȧ!tEXtpdf:HiResBoundingBox250x519+9+28(tEXtpdf:VersionPDF-1.4 1 0 obj << 0xIENDB`trunk-2018.02b/doc/sphinx/fig/gnuplot.png000066400000000000000000000275611324306050200202220ustar00rootroot00000000000000PNG  IHDRB_?|iCCPICC Profile(c``*I,(aa``+) rwRR` ` \\À|/JyӦ|6rV%:wJjq2#R d:E%@ [>dd! vV dKIa[)@.ERבI90;@œ r0x00(030X228V:Teg(8C6U9?$HG3/YOGg?Mg;_`܃K})<~km gB_fla810$@$wikiTXtXML:com.adobe.xmp 578 407 k,IDATx˖6 mjSMԻO 1$~N$  wK̈_?< @/?#Ggɩ7^Km˵8+xXe==6u޹_ϵվQ&0Pp뛸6WzmF|ʵZӻvhmz^\=|/]yVծr=~]nFNn#X̧=ygϥiz\WͳAG9zO>?iŵOVbh['_k3;_ m=ZZGydt17y|^ko;,\[6Ҧ]ǫ˭w\t8~v#՜~#D8ңɱ+s|)4ڵ6x^w/=Qǖ>MnkXc̳;{c<bm<}Ro>XQ:Q.}Ƶ8_g-^W.խ1lTשxqKR'v[Y{yͻT+#@ϐ @`nku @@C@""@ͽVG4$B ! @`nku @@C@""@ͽVG4$B ! @`nku @@C@""@ͽVG4$B ! @`nku @@C@""@ͽVG4$B ! @`nku @@C@""@ͽVG4$B ! @`nku @@C@""@ͽVG4$B ! @`nku @@C@""@ͽVG4$B ! @`nku @@C@""@ͽVG4$B ! @`nku @@C@""@ͽVG4$B ! @`nku @@C@""@ͽVG4$B ! @`nku @@C@""@ͽVG4$B ! @`nku @@C@""@ͽVG4$B ! @`nku @@C@""@]y$z[Fc[5 @`^C\䄣.rVz\祿u  @9hdf+mId|ʵ51o}o .km2v;BL8 U+eܾ/kx[TG_>r9%M0r|^~ĘQ>},>6^ԋcK\{q%8'q/={" CH>z۷?yY:/uڗ{G?p]} 9㾻7{}5ky^;죱3 " n8 @8K%A%^ҵN^.>q5fk?7{kwfp]Co畱C>[KR":ފZZVb @|$BX C>{5 @ HS+"@6 H6BF' oO(  0Dh=" @`Dh#j @|Ԋ @PZ;MQkom]ug{v.O_Dug @S w)ۭ/چnYkom؛]J~$B?Y*777EfkukL{6.O_Zw 0w^#@Zj @S H^#@Z 0Dh8 @% j @S HvloY  @_Om @ H^K% 8=ف&Rr-ZaS5[-+<9JG Qd 4rZs%]zV\zKϞ?_c _IՓaFg%ֵrԻXg븭v>[J_C%{g^MqDDעNo\/bNQ5Osfiy>qTsO_ϥr.Ϲc=~.:'0F!S ^t WMű:3b18γn*+)帖ϣrmiq-Qcr+ږG&G<1uy~}dHa.:'=/pEj74N֚gwy+PGK"61"r>/s|.GT;3?$BB ?P> Z]ΐ9VszguoMlY$qGcsUX$"g?7VSnmP-]__iS^H{ڮ݊_=^k.bxGh:_ExܦΈ"4ǥ6ѾXkk>Z%>XQ:Qn2R{kFwH~rLU^.7oP#@> H>ۈ @!|4v @ xGi;f @a(uD'orni xbx@kUoW '-}_ `K @Xf3(dN`J,|Z}y#Gq->y 8O|_vCë}=ȯO|Z'xG`ƓR+חQ.,{-Z/bK}FlR}8N \%\at˟;U ת-M0bKzD{zG}h=-.˳p5~4J嗺Z=;^o# Eݖ9]Q5VV pc @ܚ`& @nhL# g/̄X@"t1 @q$B;"CT%@ }F&0@ay7(4Rl:x "@[@"M!@QX..zWڱo;Jxkw5-X  @ HZ #@$BkB @i @5klMHV;B;6pT%@ #6  _#- @yR -:#t5'@)SXuJ`|\e%1C( z[3$P'6)#yoe) S @[ N) S @[ N) S @~'@Н&@nop @NН&@n84ʿh˪JkK:Ϻn+VU&0@?=Έ}u"pX"znaӟZu: 0aМ( z⮙u,"&G@T^~/IM}Ϝ{ 0_#4 e: @u묍nP~KX-HNar9\{bov|+ VP{&W;#wN$Bۜ"@&MDlmsRP@"4Z M@"I- @`B?YzM$ @`w9^+`T%@ {aZ{c1{8C@"t>$'y y람 0cˋmӯw4Wu@NjjơU#@Om WG@"4^N7nP~76E @)SXu+PE;_@"4?rub}8'@E@"> @^+ z[8 r @v- @ үz '@#?oG3U  @OmM{kZ & Z'@Mc,iol a\Cr>/S|^\) @X^[jGWڭ7 8$@lJ+ :So_ %@G HGaK\uA ԉM}8'@HӖxDg @7o%@- z< D=m  @G ҏ>'@Cw4SL+Fд @k5!q @`Zд[{Zo}bBo @XбV'9tD HcT~X$8?X+v,FW @lҝSwʜZ;ll U@"UGa y{)e @$B۰['=yX& mĠL@"t @$B @ H.6 0Dh1 @2_ DH;Bm @ H6 0Dh0 @Z_h[#~Jq7ǘ9f>V* @nM щHbX%Gv(k|^c NG4vu";^o#oɒ<1'ؑs+{gn8gտ _˂[- }]: @C\G$5Q>Zkm\>gc[_s?/PǖױV˷#YB.yc߈`r>/=r>c룪A'*h\sRS۱TU  @&+_7Mhg%SG@"4^캼 LS"@ Hn߂O(,yi}W @@r  @$BoX@"ͷt v'@/x-]@";  @do @ xGw @/ۚJn70 pD0> pDholvL[U @DL\'$u9z^W >Fs2=_6pе* @zu%PHG@"as[n͆UZ}bHt H6o ]JG.Ұ.3 @.FM# @O1('9 @Ha  Dw͜  @C$B0xD艻f @!!:!@('9 @Ha  Dw͜  @C^_{KOا\'@xU"T{⫕d^.&y;'@xU"K\Hj1X\#@g޾{Mqg%@oxe"T~~덭)N(c%B}LZ @3 hl]& @`DhJ @w՚ @MML* @K?!-G&}̏8M@"t  @$B;voX @ \'@uyC @ H6lJ ԑHM 6  @s$B\@ݡ MU!@m؜ U @$B;6EKU 6  @s$BxDd @9s\J<@@"M2E @_J[;Bc @' HN5 0Dh1; @DЉ&@?fG( ځ[~ [o^{kb;c؛ovy%B;[ZڰK[~޺5b? ?=Vsǟ R៸Em y-7Xo[c؛ovy}4ֺ @#4Z PKGZ@"4Z t @$BSo @-PKGZ@"4Z t @$BSo @-_[cr׏#~E7Ǹ9  @wܚwr=RbmJ @<zۍtΰ3vq_~WS'|z"gey4{j׊K[4VVUǗZ=iu\}.:i;s]X[çv;z\}Z{a->hl?ܖι\9[}c?]u{ZHڹd;zZZ\]C]+֓]<<Σn)uݥ^]+kFY{kyzu]ϝ5sؽkӮ{;B](y~ ծ+Y셁9}<1ԧyPh+O~U4ϵy-Hko1۪m]zۮG:Σ]iSbq~zR=˪ծ+ [_@sl T1Cshk_γծb6]CoK2Xzە|ve9½i7w6*y66ZZZo;Zkhͧծkyuwvk;VV5bkm浏ٻFwHq\7d:177k:囹\ڏ~z=q=_>f~ˍf5Dlim+bnyW_/kҮ[j?Jǖ5,qK2mcz}yFylYC2-J-kXve}mnkv1ϧGen+u+ˣԩOuF"@`b: @u묍D & lCLN@"t @$Bm @ H6 0Dh 1 @:uF"@ !C\' H @`6t @$BYLUŤ}`AQIENDB`trunk-2018.02b/doc/sphinx/fig/horse.png000066400000000000000000004543141324306050200176520ustar00rootroot00000000000000PNG  IHDRVxsRGBbKGD pHYs  tIME -CtEXtCommentCreated with GIMPW IDATxweU}^}SnitDl( ؕ(?{# D c5K51jc,>-Ds^Z{gP&kq.gS{}mZjժU%ۗUVZj!٪UVZlժUVZHjժUVE%hժU[lTZ 𢷉$VZeRiEխ(YPZB`2`ge VZ٥S2YH) !PG VrS[PlժU hefq٥z.ebF[M3ÚK)i P[ӪUV7w@f٥ff[uqUC)fzY<[8휧.jժX.s,M38 e^RdBA@UӾ76ڪUV7S)di"3K3.N(u!iK2GhH%!P3ؾVsjîlժU[NA^,L'@P:dTJ≐ yUھ-$[j"*UQ3rSڒ0Ң0(t% ψ #`i__SNdox>{kZjG.wU1#,Z[#-q5$)Gqc VZj*(:kNJkT ?!A -$oHr;|iaتUV7Hv򉓴ƥ\DMlˉk4-ZHޠlժUV7/ !p&p42,OBBP蔯l!+djժO#DKQV vSIjժ-LML A4 BL~sM R%+?Ijժ-G _' 4 ?`OC?V ,}1oIr%*X6VZ8:tH@@OCMss#H6OyU}jժUyP{\5= Y04H4a@͐`F+Q!hF wsolgj&y^>rm. 84rۃɧg*&*Mi0 TbFdc iV(% F JnG[ӪU43+ɭn<9vsX4| X>ц A%r<gp}(z[MjPeC0h,C,7;91t%U7s)RH-BUe'~['٪U$ *-PR.?i&_?y7sW9dGDAC VÐSV>7}avi +0.v.NG((_RK:p(RҤ¡Uv^[l!٪U {_B[EFEHJ)!}`yaJuq>qÅOC#.kp,Xs̊) 2r4RHBvrUܒIurR] ;͵ۯS_e VZ$+ S ZJTdڳkڝ#^1 p(Q!D$4~'xob=.90%*TXa,! Cjv_kϜ\% -Kaw0}…S^9qc8 6 TKjIaM^kwqdVZVYL|!W%j$At\kqCpDY("C FC] :@N)舊>%!R R3 v>|G~m GªVvѢ@ILgԿzқҟҙˌa1(#M&nv^k<[ (VZ$Tzκ0hGIJ!W7# j%J@@M#jQyW?׺?epY/=3f}ߵlj$ *rg%P2RwOߎ(Ŧsr=vH-gEʸ Gs-}(FL)($I M2 KN~ԋι!$ %Ê2qϿM3?s]#?e{\Ry 5Og -VV%HJ .r$Q&q.r$!8!6IβLwAj ]yͧ~ziO ~ t+5(VWf5TVBf>=o-HPjC}=§97'غm+33T |0(ygZJ QDko$tc2WT&44ZJ\ #\pOB!Lh-Pvdd gsކ;^tn kR42:\{@R8IЂ^x8Hl| _ Ȏ2[ѱ] S<)z ˼d:8U`$PU >KVF@c帢UsJ)PNrej ɛ ]`j Bv ofذ|2Y/Ͼw>*=69Mu+}VE:zοy{nivs9f3dyE3 SfE^+uZ} '<``QBޡrJQ#FJ$PO=F)| +ͻFb#.\eZбL< ;&,LiZuBT1(@R M,B3 d//u='nr\jwۋ%Zhha0c'9}xB 1('ԪJӏu]Brzf(zZjxij{*- {U쿸Dn,Z)}m< n+;(!3~n~{x/ჰ퀭,-.1S0ӛFIuH} WbeWV7 =qQ̅߾(unbQ5Pj-SkG$72]R0R,xjJ[壗[1 EI38~"ӂt\Uà #:#J)0k \H-նCtN [M =g=36'$Vjoy;o+_n (!cѓ{R &N2@Gpq7HW hV _İ[wzYh*as> J'b)v_ %,evWm~/xHqDx=.ӝi^;?h7o{muQ*/8{Ϩ9O/~ϛ[՞Qhx@.rMFaq AJTh!ޓOnZFP 1vl!>,RsDH)2”X9ۦbщXSHD}Iqlnzluop AΕXqVSX0DzRI7oPBGP $ϾL<` tdzX6M@YzFap4ҟ\_ܸKm RAZZJАs&P('v'\*%K2+ZAab&c8M'lxv^gKrF\$)t'- ##YFpDe;,Yfj6&HEdGH*)yqG)^w}g i,3$@4o<pĥ?|.!y؟>0ۊ`{jLu#;vrO;1ǫ? \PJc%+stPi\ |1r@1 Ql. :&#-AsoBN$N*Rh샔c, )(Qf{! 8|\׳.g<kF_"6 s׾iQ@if;}sqJ !Fk^³9ۦC6;eqz8qyұuT62)AEH  JDHq2kJ!VbU )DY E7B ̃[el F#6 XHH+ ȥZJS3ݬl1t1u:AҐY[-$(ƒ"VJhؠF'7' Ph?q?xߏaq5HEj*/mϓ!OO:|\7YV]rmxXx؇ϢBx˱ӟ¹c Bּv<}afi:k61ehE5V $MȤBF!|5!-rE )< Bs-r[fR.0|:D 6okQ}A]-,[/+{edV`J#(}U /y3Jqc)S&$ۿa6\Jm)LJ!NaVfM\4@*UzY]b]uT@۸r'Q_@JCE)89I yHd) *a0R#D nuTB9̒5i (BH h;1\p7$c~8v#`' n٣4$Cu7MwP΢FoBtrt vuA6S,G*2],c_1cx<"#rTa$<~1Rusg_M8A뜀k E$k%/͙hARu52FbkTYAr*I+4xߐ :H-$1YHP)Oӡ@JҸJ+9Tzo@=RiLr! :V0WH*T.V@$L*iR""R 9lj=ݡ* ##LryH(!Os+Z"@j2W{0㰂}R: o1v>).}c6t2 m;WX޹r-Ztd].v4zxoG9M3<f ; fNp.CIvGZhcHy':lYdEX!F5 ~W_9F2ḧ_N C,#SrIM@t;}%5<q0?cv톲D<n<@:0$@n020\wjRSeP LJk FG7yc5d׿U߯˨XI.2.`'9ʽ): 5H$@9E Ae\t%'ƌ+] dȒsWCrrI9,Pɒ*dF BP O}9㟞̫H-$ӘK=͙۟8ְb ^[:I<#%u?~b VRJB'@X?Hsbx.p9eVK:yEbwH iPJ1 Y 'W4Y jFG|?pe%'6z)I`9IcURWHU!U6>-RY7ŶT@# ԣ& ɃwB;!w1`ʬĨ[5# ~r4Հb,Net}ʪ!YViR,8eikR*z˾J3OD3]D#,~ƃ` ѼD |"= W\I/%m[e:?zD68`D %7~ ny3-[ E "SHbQLb5vSI^ .hkb=vJs 76I W}cߵ6f ;6'S5YIJ!& !d?9UW35&rErR? YkԝPɬ9IR 0Rpf8@k9"JTeEZa &x|eLamMʽ֥[Gli2KpT )E %KT) _Z.2j4c6f:3N[B7;c3Csq'32,wr y #KzS|Uȅ)DtC>!8VK$m쭱 g4\ۿ__g1oA%Lav*Z$ !%Ac2 > VNoiYdiF8)M.| Wo Ơu\FҐJCщUx:Sm!ЖR;:6vI&]xŇJ3hV7"D2@N 6!ke'Oea@CJ.Iɩ E a2D*1.dF! O #EA9vQrpzHSx:C!H2v IRT ߔyцeS|r9\U\wLgRKH/i|CWYIkwrՅ9-IBmT>4 n\#A;U0zIBHn23`42@}\CۗNo죍C/ IDAT$o16w_N# %K_+tQİp@ʥz@KVףb- b>Ҏ% | X5qOhӭ&:7ywD;mV=-hK O&UrTyT8ɵ!ӆ\6dʤKdU(Wv1hj7}`͍Q\TǗ>ww:+f-ܪu9X UC$!9C"Y6RubR+'}kBQ:44N.\!2}lXcX&jر}7;GCCHZTRcH=A8oH3i$s1\ཧnjNCUnK9+3c>}b7LwN-0&5n •K/8|e(yyeI<VW_]|D!;E jŽy9}S7;t_GFoj=T<`]u98sDUEH ҉MO HwzYg?翔G Qa0;u.RYiTYp2[SX9qA HCfI5_&IޑS8a`5[ *Ax7RaG*)>{K5æf#< CLLJ< GZ1WX7ae.^7VR{RU XqNNX*&=)$!L5-4ǧν>"u JAI*= XHn:@'[Hw<$,.);T&X`R N^en+3f:3dC1%"$E#2!`fly ui\ݳk]r{o{q&TMC>]|3]C})>y9EW(mc/WMqh8;?5,=ة)%L. $} ]Ʋ0{̱8*,xxSK~c Ei"c5"$'T3$!l 7-45rSd5%ɴ %NǑgJ0/z&4#NaR-eTMGJn˾0z S/F45WvrЫrn#9 wegK<丼`O@npUϔՈFq(^dHV¤Gȵ^+9w/~[FH^{. ( h5Xؗ FHj') &pss6ٍ_xI?06:enYˋ spq߿W>&DrW&2zU,{=?~ܻClͳ[8ë̴ߓ(DI;]R1/~#ЪRE^aMth _1Dx3['dy`mc@Ff6[6c:]%l:,CPqB 9TF#^s}'CrXgO K"D^CS\!r 1%'ӹB@"" mdo$WL L`t-Hx NF_aǤ9}=B2םK [ za_王: $'nR@SOyՎb@H2#Cڋ^ /6¬*)z砗ߖ@G|0 |dܤݣ@ (*&Let:'qNү^ij@"BHqQtB,hvO1ʇ\G ei-,2Ba~2/lT9R d FDHĝJ;l=^wnՏy㨛ٵ{yV_#;e *R+'/AwCxg6sot:N RI FCfei :4B8¹s|E ʛG7> &DU{RŒ~SnP5M]417Y6U JȌF'7)ZzQ&Qv@f}Fi: |3o㐩j 4Ԍ+ty<wB!uz'Y{ b  \[`۳Aa|4 o$8eI*,CRG<8I&3BX׮= MFXʰ1̚Xw;ٗxXٵtT8_7P]*d.R<5\t$UN١钻cq8nr#4qa9ϢsNsD/kFvsd=V3'(X(-Prn+7;뮜,Ȗm2ݟߝ,J4MK,.2~O]1A!'sRihC>~r=fu[ᦦ"R)IC3z:Ԕ29Nؖ87C8 N9๽>*k"\UH(@(ZMMӌhVbFҙ]"Uڀ5'DGU.GF^@vg(*Bҕb;>zİQT, +gڌ)t))Q|*ÓaZ{I֎ ՝ }q('H4E;LM544$Wd P)zt-I&8bPr5CF UZ5f6<3N8+@P%a $OJͼ̬F$31Ie7ݴY5HGV n5>a.c82ӛf?ۏ`)_|rCE ?&Fy2IHeJO 7QY jT~P9 Q Ep qV Fuͨ9E*.# r1A F5F`jZQ1|w,s9y*A2s9~2ÉϤ ;v`?KsY{~8"uY]|}:t))DFNA\ȳu@r$ 0pcm4qFsO*tc+G:||mÎ\Z$ȦS@Zg Sf|o;2ͦ&1uC*5Cr+8h<Χ&d49h)t2Dd /ā 14wg#Oc& 2P [:OЯʐ;O {$)q'@vg(cf@7!*JQ^э;YVVZQzD@4"b1`tk+3b.u {Td84 ?†SCS0kխQ`9ǧmLKb6yrU-1tmtXihc ˆa1#v5rqҩ(*7 J|HwJMŋ]To@y,IUC=Rǀ0 upAjc#ӵr$1sfһD9a M6$<=zs8PkL=aC&xhd9O ia\xB+ aǿ' K|Y(eVR8qLny'CЛa)ktf)|X vcfJcYv)RNY*.VzNcm !$ʨ+!6V2 + Mo]e`}Yq=ڱ3zC߲ם0*B1Bn r[Uix/{2 )sI8~Uz9~)jAJi_bGF٩bkI'zxE( BQWWy}t}2 1d| ]yw{'^걣lUy׺{|aʂbaC~\.JtYlZf8 L)z5TW|ikfImz1bHz=A9"U0R4Bⵤ6]ΐR2V7>|a?㷦NU$Q@ڸ##{rw¹9B FLOweU}^uSn;AAAX@Y!b/`]DT bFcQĂ XP"Ab# Cgڽ^cS̘p}m0ΝS{}, &fo(J]?wl1n2]*ڹBkա_7< H~v9X,J1()FIL(L`˜n⯐S//x1{WUz}~ruU7NM|c4Ŧɽ$~! jQ^x N4^`z?_HYr~ЌJ;~ =X@a#A CP7mLJ"}US]$#y 'KM cl6 E86u~G%Q ]HKA*r@_8 ^Bƣl1r]bO^zAkj_Sb"=9aިs4d`YvUtSŪR*$$JU)Rxp`YRD[Nrv:Ki,mewCy5KB$)a\P,!VB]e3fۚRajav"$? 4&"c[ÇYH ,^Z^Z Ss>\BI`@33ȴQF14!3U͑'<{nEv,v etnhF-EBs~Y+v Q㲫+ЅQMƧ}y޲ <ЫklȕpbJJ+$LiI1$ #\a*U( 9c@ iImlD@4 F 0cTjڃVuRt) @z'Pmμ'@RH9T>XxO}˫'T6vHmnm< 3)}_2q=X~I6FMR3*La < L+^ட=CYv*$k)&4=/:~K(kw}вLֆx6F䱜`݌gՇP~`9W4k$,U xvZ\^\d8bNb)<#)21|)3kݬYV1uaq4 3rnU%K{gZ/ou=~5l_ڲTբP+z6Lg9m+xlI6uuxgcVL$Y{$2VM2 pE!'Eacg"I2Uz{a 2|P)9=RSvB" C_2%Hi':TwKSTR U(C IDAT)K%(TFWfdDH*1{~׎?9֟҆mB2IŤH.\;cJlNڲ$iIvetdH4i=}q$wX CIfI(5@K<ףO6|'f(Qk#W~8pcC+vF CHs! T]1L9ڦK6LeG !@Fː"FKHbJteF[Ґ C*BrFMF7 ʈxjL@fYF鐴;$6(iɂT6[ k}(*zqJDdOk{ QEN@2vhv|&سPt*KK_ Vo7nZ uOLhk^TKK rŠWd ^fSJL#xi񻏽W2ޝɻǿ׿GGz{b]WXC1,I4;*PyLI8o?Э RQ}9|S*tdOԯ!Z.Y?G_ ٯC8B#"/" iyDY$qt2;lx ¢O]ƷEt0 HUFr2S)uBtA*mwb+ŘJB̜ڄ+3dFWeŽ !d# >"swSnB@oJH HҠ en*l:eTdP*NK!7 &4~|-Tj[]ĥDPUg#$E ;C;_4^΄ z/|1wt[-:e&qyJiJj~Z'ůЅZIZJRT|0]th᩽A]īosKiF zVyZ$dG\ufV>l"O.f Ⱪ'Kd{aӷ/gv[g^Cg0ty'bʄ<3q:@r2 )`臭o# w3TG2?( I\<7m5_b^rW\is/&WwppgR(T=q} `DDQx`x"|MfT.z ons`Јαe3OT ;#@yg& ֨&PM*3\C҄Ld S9V%XRb&S VZx(#Iʃ#,11EUwѸAh2 0C5 < %kzJbJ5tyJCq] -hHE,#P"WH)>|X畣ZF?8.1YrhE 3nBaa ӝݲM,cNp\hbc$%Mק5_IV^mv;3JbSOx4oLYo)4t">@[Hz[ȇxO9?!Hl҅73ݙo!f%BJjX.NqNNhL!%FO99bn9:-ɪYM%) I!|,$6R[h{Aiy or@ 3REGeN&ֶid-KVxj}S8L:&I\Ԏu_xӫZy6(Qu]??! ?al^b`ulv@H˿?owOG-隕gPo磽t3y_Tfq׭pMk'K(NPÞbI!Зk|(c6j3RJ73(BS-6SMʑKu8ԴZjo/ O ;6(IJC)#,C/r*,{bzM !M^{?4f]U4ᔭP1Xyeu$I@Uܴ|5n͋\L&L,i p ?֣TBh/J+uLaVP2ka$cة3vM?6K1y%G x[o<IطnK+1ou< 9mIe4MHe#*r3M" HaoVYpW7TuU}>/,瑘5MbtMyO*|PfBIhԱGa\՞͛*Sa~7zdԯK奆'_dV_1{>8@KM"=WӸ :-(v׬73$o'-KhæhU\i=irk(jG.:uk);mY5K۷cWИl:[JZ(S"T Q6|/haX3y^chmK4KK_PET]KQ )ɳXjAM*,tAR%V7ձ$&*8!u<4E8HSĭi®Y,E8eC@*OclF`(Z_{MݣlB&)BQ&[+M˷|3InK )I~Ej2]ppۀCH2vDŘa eS96/BlP&U554uh!_ι[ۺ=AoO{d3$=1vyn/ lKv^<2w871 !Nb&;5)ڴ%C2lbUU%I 8^v}:t! i(KRsS I_~! i\ Q / >'ؚ"Z{)_HV *\]G*I3EQ(2(aI8ArǝU$xn[.Y!7B2+r4)MK7Zףu(HR1τIqo?{AfoU@9%m,܇,<1,!M$iH VeYHSdxzx9X+]7A]ƼдZ]K޲&IIН\1GpZI h5qb5 a2O!c?RfUci8wc5_mXo1jwF5)bf*Ce;?PjQ,:%2i ҍcR⽌Hch¹-݆R+jCL8g~ia`A$ vq| X5`"(E e֮%IC5T*98!d> rJMtrM=s5=11Ciu]KogqAMɠ~|"Mp֯x|w_[n ]B3?zuٺcu|dRSsU +XY dv4Q3 α ]ajno{@g|F]u6mftʔN(EK1+\i2%z˫)Z-gII\&0,ot՘d)6IB3~rij^C]U),˒G{ c-֟~C^eP@ڠLJQ|gUCx'q"eg[~qʊcj<3^@ZE0qIcpZq1DbȠ&3$|y IDATUmo#>BcEVjŒzidp_O"m<}ǍQJbT䣟G:-hux7!0b"85능w~#@J~fEI L^j8pq Z)b jԜo \0 ,5=V=q✧=#%^ N~~#/֡~'&q{|=Zaۮ26#wYj5#98]ŃQ, cT-.۲;?|p!WKn7ef`v& *2MM89znn_{$DD hN5< H(UXJE%pB.RH !h d(rd3_ɏ_~b;rzRg8{c2UXְpg~!LfHa2h~z6U2VN X*B P_o]Rr[^ZE1':bc^Q-I۞aI"1\S%ӇX^<3;U4PXMs:.5w"L~$rK@J yqU27??p)7=UTR*x*!"\p=v@wRdnvD!2?ƏÔ"% \]l2rg\XvLLƣ=ָh)Nh!)NUr*ueIBGy+z6>[*Â()GR IY!M:at4]D/XCJ촡9*O e@-e~SW$Iǰ}[sc,_}]M°*:.9LLg[f[F5р`:Qv+3LprWnǸޖ<# b0O~Ŀ4q;ߗٙق[ 5avT "hW09z:C7w!C$ g+HXmM.5FiݹUf,E";\kʷ@'% D@z= \Lqsz4wrYQ0=b=05UJUHᨫR$d/{!_j373tw2/)$ JrbȒk¿g 7 #P1 ggH׮&AwvW5nE Wrldf UazTUZ HOc0p !d붚/Nx3 ߌv& fq7 ٠f/ZrAΠWRC{s=M5wZVCN~2$@*,UsP>n'enH!$-E0\^Y“eSn j!ʜ@!I~$U0BDY!,dث>h\p6Snbv<*V U*ض Y"v{ye8q5|Qex&(u 7GëNKUZTnA2$GHY׼D|P7<2Z0?Z%:)cyUp[98%WӜ|vn7ȶ]>XϺt˒n xM4R:hhYPd)M#qj}Vbnf9ZE"/)clJ 6!Ï[/k ojp&ViҵI,SYIAhE)uٯx9FU#HE=p|5mݓz71*ĭ4v8/v䖭5F$=ظPڍ H5􅝴v(#-UWMU7Xk0ZB$1&$ikxKvCILWzi 5aH(Udd,$-4& f'R@y Rv\(9o|LV--M)< *o eFm Ixխ_|JB XLFw6K)WQF5YGHnEV=gD[Qe'a| i!1dPyP]^)k-Yf)ʔH $HH|Ҭhuπ36

)*AӰ eSnNbZ{"i45G*qڄPG!oY3yiFʸV5*.]x/9g_@!>^ I|oro'ۖxNttE 9<@_$ Az'CS2RjNCS=z\ӏ <}ǝ瘛-KV(Y`Hh yb5_4WNgp>% =(.ɑB|C 8qg~S}_f=s3stݑ,l6'-4#  ǿ&7Ilt)ؙȵeC_+1aׂN>b-pMS + f)</|iNPTjOxnc֚gQ/j.yJ-ɒ6Y 10٪B@*NUL4Fjuʪc?x{#Sӫ5oh:@zASQGbf.Br*{DZg^#-p^U3bU0XپOZ/_~"DZZxn:D(t7׮B}NlQyRVP!$AXZ2SZpv+PV M]ӯ#?KeeJY_Yt2ꊡ&T䩠zN+vRe:`\Et:A5zE,_=3_y ;L|z1,hmZEV٦ ,G+AJq ?a݆%'\奱>d,!hf I!@Fn4Z5!I X^y.[!t8ϩ/eI"aЁ;bbGsb (\|1c%$s~nS{: y.(iAC5p,nضyYF89$M!PEF@t*cyƣjbM$NC^)bO2 R8G+58и %c$ X GdB d(9U:kQӒ{yc@9 hZUذ_qy նEU?!/-v'%-o9Ǜ;Ҫs#(9"(9fĄG̣˜EePԱ9O{S뻾oF}8y׽ֻ~O]4eI]KYTXg{'9;IKcОR)IdKlsFU6ıCx7?+O|v`:j| d5ۙȦf,cnݔ0I@~Z+yS0֬[˚Uwg@K?w]R5nɴ?' )8 kȌ3oc>tBl &R-t$ـ$ĩ% 1s׎MP5?9liw d^_6<73\ѽkTazmdYH1HDԡ{mpiNDwy8I/%jYt}\GZ砩jF񇫞Ko;/_k 2(@sOX&CHӘ୫Opg ⥯|KC0}f3tYcE;4$JH|g|s)y+F~)~0Ժx}YiI%BHAϑZS?$x/"6?ߟ7 E|gn[m:m_j0hR(#W$&!I<@&;V_qӛDi6HJ慄~MJ4Z"ăR(%n}ԖsB3.lO^JO;:6D%+؍ARjM?$o7s;OȌ꒩( +yau<[)^[l[}0kI ,'VђVT& $<طb 6k֭$ٍW!w~nzgb$ģKź5 y|t4"ƦJz6Hcf3\rXl<ġw%Llǧ Ehϲi,;yubZYOi%2LkE-$DAJ愳Oφ_(mm, `qlsep/FуO9 }A0^s֓tcLj1FT"XrPI]tc9~!B[HdmG^zLsXrUULztRM7KhVn423,cvκ ^ѧmqnŅE$敯;]x 60Iվx׼tsl7GRthdcX;}o^ֵMmoFRh>c/SBQCJUSJ?&4H(dE6 /`H=p;! R,R&rՊ龡k[՝I#5fsދճβhxƚwz1=5 'x/_P%eQRWUX=3!P8PUeQ07;Ǯ;Ɵ_;p#!o8~#2:u{0Hp=US%ʄ`5x珖wϾ+*TPVc~u͍`}Uv=mݣz`iiO]Df$*Ŋ=<}(YjB_ڿ $yw3lVNnjznV;0$I)_*d.vf7Ð߸dWm\Eu!FQYS4ENC'ZEpi|5F+r/pi`\sMm_d?Q3K 33I9K:pOpF (KoJ^$@v$-ԒsE/^>;`13~-W}?lnï9iHhXXb5;*dTU?`U>Mx @s~#KRr LR$IO^M:G '"S#OetOHW͐ ,R&^)l{W!@:2A Zi z&ļ$9祅byߏP:A#rدg5`]kU~#Â~{|p_}1Qĝ*E-\{-t2fj*~t51'kP75eD?ꑰlwMEŐK6TϘĖWft_!ʋ,PJe]G9YڔܚDwZ*RV/PblMPO:eq'9@]hӘEu+DIsyXD2>㟣DŪED$ʠM7>H~NxmZs{:Y$Rԍ/eEJ],)x)^&ǹCanŐaQ0,m\Eq)S$/%.jhD1*N,ۿ'w*I{h^t,%r~N`Z_ϱf\nU1. 7|! )vT1@\޳-ȐDS; jxQZhk9iYH$}9COLV>gz&v7$ie5j ;~c|;v89z+9 )4h[w MS27v+19dgtA歐dt@ɟdݲluy[iend4qƈ#16 B7Oy8Ѡ 5YGb"Ⱦay'8j1HƹPz`7B Ds<1q#I&YN]/~:S#!MW)YO|K?+\$VtI-~'sur4 }A tKo45(I{s14HtH=489UEU{'X`4I+!# U YXgFlY-3ɲݼGE-E6&.Ir8!2Rp||w RI~F/2$JbDt|>ܧ~ÎawYJ*3r"9]ݧ{vҁ$2cWn/VK^(Ö3qNFX1AaV652|70:R8qf 7/ YwSC+\m̱I #N{P(b# (')wڽf*_ *VzbK- yY#=@&)F~j_puZ6FH$+)"O]>rQ'232iKkH2M+Lm>K̥sęOǡDk>'0$=9COБ=Rh'ϛsIzڋh\תen,OG[oo[MTaq0I/X1uAUpiwK+d$mqEF[6;dө$m4V^1&uCVId)0bKb$6IeB*3DlgDR1:mӳ m/J3mI!Vk~wFkmEzTR0=!Uͷ?Yfp]r3zdyBzkoys~^:n"Y;MaE +[8n,h%^(ȡ:9?7]uF]42VRy5vqUAҚ pX hɜH$hvq55%gށ:L4UHgeCf'j|&Ś(~**5/RDXᙎD8 ݱQ9f?)"FDdp,MS-7ʧjCjOYu\{>ļnl(J?kB ©t~ lܽL;."?ʰƢFЗ j/ZO.6QKG#YkܭfOwaW^$J`PkY3F,[V YeE}O!X\lX"rխB#\\S?KYzph:@Rg^u/>_doaҤڠ y]ં? 5d] c%p7\y1SC済+h~c.=4*$뷐7"# " eh(j*[fQف4YVl[]% "IKv y5M;iMgm\@X)D(J|٨qz=y'>s0D!Պ4,1IG| e[O|A͡.9fYW1S+C_ϕH n~;"nA\yd`4c[uv\_5Eɉ$Hjb9sK/'W>e$(p|n1w @4TYև sǀ<]$wT 7Qtc6uZۮ:dYٟ9P nWuO7,K75("4_0)LExLB ?|iêHJ"%)joH#=@R =L&_.i~mf=+^е !z'#%>O\=T +Xkt;L[@˪fXgg y szj_rzȕuN'ˉL8:i(gB0?hDѶt&asbp7-cNto7!,h# E|d= fp>U RSYNl=Go*:&^yGٳ t@̎wɸ熤m$վdٹ%\`m`Z<0x-6.Y}F;e=O??@Ѷ:*$}q8Ǣ%#u;!](cp Y  "k\hw,hEJ "ͷ^ z{ȦKDo-IAQ)!_>ޭeqR 8'QRa.JHk j Ґ+?x{ghˊc3eo |d(x)r9+Njx8GN#HEB&rr&9H _l|:8txAq@#$ .O#G_uT$Wu`>|80KFhzI)E$5FJo_6TeC%ZlLۋ]Dkn0H_BISաzm#6M ^P<-]fvd4BaF;Ɏy/.p+> Nx笄 w9aNzlm.SC,9+`ЇH-O /;ܫ@@2?6Q׎/nܴW\}Il!ŀ[nɹx6*>:K'\o$Ɵo;Nk/Ǝ 4zp1/y=H1X!""-|ڠPBR9?ӌX1"oSw_LHarLRDz)VKs9fܠ/?6pMŅI~:tܦ?Xk)[!>wr*PI>I>P1(UH$W;F=WU4UjsPpz,R1[nEQT~D+D-ؾsGr4'\T/8R6$(>޴{֞U6xF"!x5l6C).\>"c !]{<_NzMU#ߟś 9ٹch{%z>~40.0I=$OR<5 z`Ǐl;R ~:M*lxPՎb-W{u$/eɚ{86B.g7^V"_Ώzpq˒ @FS|wz_&0.C ! JV-Z'ס/ ?5ce3*Ĩ?Ŧb^.IJ.5Rd|J>\}-of)緸uD83]Z.q(y󹄲Fhj9h#8\}u 0>)N}ɋ\U~ހ?rfVBn!)> E3,\֗\ݨ0Xa&NңL9CU/U)>D=PoZ'SʵgGQeYQL˕y" 1"%RC* CÅQ[8u~PtΫVgHNx~#Ղ;iuI٤eL'~KL$8< &bj!ǖ:gދwyX5b(B٫k=x0j"|į gnchn [ƒK TDj$;gPBCό[/O^6gT5c!~G^]; p]\p HH޷$J4ܐ kR~?[ɎvZ]o?~ P0=M ekh\M* wTuͰ0ܴe B} 6CNvM5`o>/ArČe;() ?jjjWk晫Ndyqk1vVC!IskYȚ>~z YAsE[\cשjgOM{e} ;bESpcf?l.\'o%imvKrSE0?Wo/|UoÒ#!u˯MEͰ{~73BX jQ/RO< 8,߳cX9oyx|a|*l>Eas:*M4̊Mc\a:6k[?'j4#KZ&C/qf2P*y,`9/?zˈz(&uIQ,p#.p mtZ-,) V" IDATW/afyLN[ Jz;6) o u%8jjb"_oR12Lt{V>EJn̤$zJ#i0K/ƍ;j㏁Rc G05jH4* _(kB<~ύe,-mVRp OvpQ5vEޤX(KzKZp#~,"2 6dM}0-ga^ݮ;QB r0p Z Idb.jI;73C+ӁMJb߷{Q*Vò1X;*. ;ke5=@6>B4 H{?ʶ:F)m7W5 afK;uȲ^"bmmsyW?If6NHTzIRVޝ DQ-lG?LꃜSC|cشhu]27'?Ϝ-z:qbm*n80j%/|zZ)Y+o)Ky]?w.QA *J$vYOVaįa=1Hf7~D!EB$@1Ku|ݧm-#H8Gc?N,%9[aIchRQ?ʃ` 3"l-Wrm,0Q", Fl%^5b8iˮiDJV‘B$#]It&Sߍ#Oe5[2XRegR;2-/P ъ+jZE8(ZHXrT$"Eo\كq)џ䩓_lG0j!u5$q '/$E;״3C;7$" R# :=$kL#عAmU~8ӸU^$}s z\5}(BE-ɑ:F*ȑ=K{XDD.[2 ҕKK ݐR'7Ǧ1qs/ "ICl3.'^=?,jEdYE+Dqׅ胟ɖ€PX:$5I>1m?BlL.LNKI&Zf6\Ck)T}PLrRqBP5iU:LyOQ)pMC]O1u]RCEd3N}܇/peCNU1bvVQ YL,8I9-opo=8? o%,Z5(ȭ$~tbz9uTP˂;?{,E,˜q\<"@j 4l8FTb|58lè'E&Aa7G,n )r/H- 0#j? X8/$dmEwF"cȀG~CB5=Ԧ$ :!ubzE`{ވ/FcdS rC >UUA2UdU>Mc, ~p|Ԇz U۟F$y;f3ϴ91Bo$ d5f/f؍GRzvK iq5OXdqOrTrmsusu|!dB 5׮ f@`!AXi1ҢYPR{ )u@җ[P~H baOp_q?HطO ^o %32͐J$%k7FiS1hǍ{FKᤍn!E<$8i ,AH‘ % ұfm!O|7t`~Lo:"N46Rh#|A,פI?1K_d#[YҰ岚-93>'|60aJ=sﱛIkoscvne$I ؆,kG8ham"pӛ579vŅtlQþvE` uQ;1]&@(rmV>M/7g!Mi]Ka+^:6ı I")K)h]㣦vocuZVަjrLZ8I崳pb+ց  [LA[x{ߧĀ**bB֟]6g XY>Oh*d.$X1q@A P"Ea"%P푌&"MLGFF<8\$N}n)mPR `8ppc9_D'BE⿧r0tnۛ;wzatILGePnD@`u÷bU\RmV0rԪBsn J ʺBKN9d*ei٧':M&"A E3Y{wWxt޷5,ԋo!Zw>o>լ[cH%#4L"K ɺqSy%Ba)'8D:~,ԫNǝ Y5%]hYPf m e1H j%WTd3Gϔ[}{e>]r0aD:2tCQ,IR߻mcUH@M(38R$R cb!rbڵ|lm֮^-iɢIRT7k曾W~tGAMy<\=M=&(XX\`n~_=~JPP?,>M\7UF}_eP6a w&1I#h=kDxw4 n|u6炟jbeIuB"iLR 98:l߾I[nHgF^ÄDa `e ଗH>SQs7RR5T/*qgYUfw<醺;@wE@A1:uLcs,:YDE@i:TWϩہI/tuwU{kYZ,.<उv4$DM5VqX{E}ޖ[@J%[Vu52%8z{v}.ժ~=LN$;EB13t6l&ܩ#(G2s_)0%|1?Gcl+䲇xu\$2`xCFhA`aþ]z)<ߵј:HY g|~۸V "gC"pZ.q, žxO~763!ľ19;DЂdT u|Db>{s?{~,,,Q!^-%u:LI$]V|1'z,B CU.3vDbZA]MIr`R(t*+Idy|`>߅$h=28w|[fH>LH2!HlX'V,"a*=GKUK諃U0T`A#Cv)k wTr;.pGpL$YG+o7lٲ,0Ie"T7 x_wS?{_]ŏȂ$u8*_1#FdG̻miTy#;9Rv?8JFW~Fl[u)cce4_ AHZ0VrC֮t~'az"H *64^_w,U )"cBX L-~DT$(WGkp¹}TT~z Ƴ2h <߱X_tGg1 Fµ,5e{8/Rh*W Y& &2IB1+LҐIxe73bcF-/'[G)U88"{^0H#_Mo@NLXTZ d?t󵤦ZoK7sʃ_Hw,Mq$1C TIBeL[3ϯġ7mv)\#(VЍ i-8n 砬=[GWywq/DbWc4 Ma-|ݡt )6JӓI8oi}9NI4K~%?OKJo";2C-3ї`m c6EFK ,9Z [_ ]*re >e{[(__{U_Ո!CS# ~LJ m'ܰWא(CUx艄i z"!PDAy$S4 DhN0an`|KRW6.ßHʂz } VJ?2WTU+Ke:rXc  y5*b٢1\"ovHel-27vGL疙hl6dI׎rk~C`'[C7[Cb$V)Jڶ4\ɰGWyARL;!ZSF>3}5}i6~'1PLfA*& l5yѺX{NjFj;L Lc G͈Ngl͵ce֦')w&Ñ2Y*<ڗ"xIا/aE !82Qd|͡KTJ1err`#{` N_BP%}_sB@RXyإ;䓯9V|cW}! j9dT|P^[^ǧ}'x:~ sPLϯ#o9VihDR2ba<])>}tu~ۃ91toѡ4O)\4EgYH(K[qUɹ$3Z\Ĕ(0X؏ԑ6oZ'ADcr譂);f5MbQ ٞ'ِGd>QDHyftC#tme k@㰤,EIJHN+HXBxquKKtZqS'cylVא%S$Gbz(;u`:Cr_kl(6=#@jl,qx^9OC*IޱlӂՐ$;I4p@)E؇7#dZ#anCA}yg*dZ@Yʟ+ӵfh/ظLV_%5#>D. %%W~Ei8n7}o6 $g䢙@3XvLrQ./9>oN?,anRX4~zDMd ɮ1˄}ݼ@kcaetQ i@8λ\#*h?T:CJBVsمݼ0 oH2i4KG2'] @(ILNHYŚ$M*x@{螊iy@ cLRDcvOFV7C*^t9 &ge09Kc;9E(1 @jkxM^4+$-4XߓIk;coz5LIEʼgk˫ Lqb :ہ/ᓿ]/Nmu$&`6?DM#Qozx O I.g (4yuy /8\W }Ր~ IDATo}ż蔏3\ =EM&5?gc.%юS{X:6^!A`7=øH v63yl38=^4Z H+ɨfدrTGMu l$C Aak9%^} cHQ,(WR̉6a8 Tdi-Z N(|ݲ)<혯H#s[=Ir^ᶰA`׼T[4A2IPyxI6ơs|e@Ǧ'XeQsŏ^" |Ak5w A:+203Hza(*5'}R!zXP.A $Xx2'wyY:WLHdo|C_X)ϙG mRhbuQHr+V+9l WȾ=LH3;|H2{=@ 0" MKz<4HLH-jvh,k>=־qjV1\鬨dG1@ySg#tfr:Au@ uU5hA#XE]3=~h՝6Ga:ϠҲz;t/cWp}jJ lZ׼M<V|y6?~bjv;a~:ϩeHcx_̳ 49v =+) oc¹sb=w~șU R ,?f$`*m{69ziCX"@2s'hm\=H|73gtMf4F9fdaTr!#R R"csg}y)Js[-Oڛ 70Ls.P Zx37O `tNXKKhc I :lP_y^/ k{ ٤Ѵi.&"UNFA@| !o|`^!A)6(eZSžTHCʂF޳]|3= ##Hf`Bixϵ[v8!p!wy_M9ʺM>wn$l-Nͺ4m5iZ9ƄZɰQ{ )۟b C{`K VaH8UͶaIɳ_j+q(V 5<ɝߛY(|Xz y)nct9J.%WQJy::r5>q5_GD* f$1[<Ǽ'rc KOe=>!&6y`& CR|`M?{>tTՀe35[#TSn[T> mH L2XٰvvS&5X<C \75lG3ۖjl)yú"Q1+zHc95I27EgEwp0—5iLɾoUoӲS!]z GEξXC IT>@{Fub]r[;^fRz8G#U$zsQI.rl=HjC)U%d37pA͞C?U $5*~ ;FkɳY$OOƚN1Evc]VKd}l[}7#3bmFdy$-qU*0;B+SU#)n2XCu{Y+X6[1hӔ+ dۇB릣eJWq8F7,|gsk?.yw)9IjV.⠳m kт5_/8o#)4;UE[nE($9K#n_r,aU>d/Ȥ2f7F+,QmiV=mJwѪm{٫F%k;OjkDOm>|t,835F~"h,۲qTRS.)HMNR"*WQ7>֖-U#=mYc0Ɋ҂c=q&R򸳞ͷE\ \vz*0ȹYLNrTҰ;0J|YCM߽plXd;C:*w rG3t+!12m-'Ty!obBj(@e|=7Ve5LNZlأku[rmWƙ-J1uHNud/d[}"X ,˻| ۖZNDfd)A(Q ]j?}q;[${[Ŗ 'W27[HO:oݼ6t'2Tѣ;EU[tH Hzt/ROit\fFBL6;Aװg7a&Stń(-RHa>yJG6rZrbOo e/q2{15XGt6 qZfaq7~ko;Iۃ4f6|o>e4߇b^Κ~TЋ iUIc$Qߴ%01&mB* 21{knwZup O>ź&-矱 IhK"VєQ<28ﶒ(0j[.l_M"%|Ç,WmݶtڲEѰXn?3|U,g*;T)Jf\uL^>lf@dg:m)S%R1DӖ];=9j&Y&r!)V%6VF^.UH҅_(ݎD{:69j"TG:*`W͒bIN TbP5/$>kcG}OtA&>Oőw%J8ðqvR ?{,PDe~n{D`)ms 5X{Q-v0J6{-rwI1U^ɗdhxxn n%8/"ƛ,JuiM:?{tUȺjw8eU2u5'^dLT;?f  | M;N{ͬj5O v%>/Ta2a2 Mv !V'7{7.U0Do36l?>CH#tԣ%TRv,:TA"PT"MPwVXjdQ72 TN tQn݆ &s %%v2G~ғՎ z!o%lX˪H1RsIT-f[c[ZmR[1YmiqUq94GBv(aY [&iy( erY3g/'Mu,vB4rL$I0Ơj9^>I w;.I6 =e)&:0p} ˤ"q$z)R)~P$N(M u>^VÍtS_s:MOGFƮt6JU(9)\5B*6>XjH]` AJuQ*CJkν 1!T !c%ExurklZ/Rճaa~iE yf;wGeaϴYN;R @ikNHT}-@֪m FLHL)_s>l)TyN2t:<P)KؤHmq}ܗB"Mq@⸖^\iF -K[^.4aߝ%:Xn受L^Xdkr|9툇-@RUC[xSWHXHreB%lb E|Kμkq'&NpKJ ;6W70{LS9yI53wUWnU[rz4 B!DzA%oyW+P؇*<߄?̐q`/EՂc{1>Q͛z QG}v{E1JqUM=Roc60zBd%B[kI=Sb'm^`fj=ݎ[HLc4h.%YRťlʆ[js0ybN8{r-I:\|8`0Mn5 ,E;FIv'3Q{A%[Yיct ӡ;ttᯅDES\(Ͼ #& ?qAkÔII -@jfD*!ImX5db$s\pZ5JCVދCGf_2HT+X2zC6K3xZ{3MU?i$dI2ofa8_QW[,.o]jR<H=b2daPbMz9̯:r7U̶en'r!y ȹpxJtU V'l(Zo˨5mJ s0sJ",Rȶ V m?GHMP6SdFxHuQ&,isTr'XVn`!y搑/eL*R,!, ~ĠZIvGc J)w"HV~DJK#OjA2| 6>'o۟+Em,u\1Z#_Mnҵ=kWSՁE.dg)Ǚdhμ*q炤4(}BHq6<ʗ<Ώo{,ALдlih#*]ؤs0\÷ٚmI_ʚ[HarSϦ|"Gʅ·䨤n&X3'}1q)V,*:JKYM QeBsidYgX*w+)TƆl ֮]SpS]&{Y)h<lH:QIղq&)b,Qs$R1g2~9jNXf',r+?.dr͊\F"N %:x\P?d-ǾuƃP+A.FкN@&4d 2Rf ~l:GյN8 +s_n!o4$̄4/RܛkΦr]*#6r5:8l-3*ة }'Bclq, \bRy W*j/!W\w_\rǍ 7wZ7VD>#mMĉ@ـ67xH5nLV]Ű\򞨞+W7#?gS>u_3KC|t'lXdOeL.sfQTe*Jtd>D@Z(k')L+"v/]I+_7xY }܋D0\,[N3ȋ.y Y"a5ZXfۃPruL*^o?پeoHQ%נ|8sxK$d$Rd˨AQ(c#"tHd,){tBҐ={J@~J-0܄ά @߃F 2G?`Q$XmBϽO :\SFp,!džyx|ĜG^Zi8sG)+>(D_S;AD<$l kؼ< ~6||ա/c$ 7/ yl8 :WNPFhF>գw;X ]Vݩ/}Ч~쇣{ -FTCd?07)teNWeTNOw(b5B@VT|/k w˄D$*É!\;Tza$G;Sq/9ٜtNѵD#;OUo֜or{C h9Z1p+1nJh0H{6' _ye$*Imܺŗ|c_1 c/<~sO٫G6U|W5do+G]9o4j\_ HeS9Z(,G, ٷ_a{=Iw3Aқ(#Hz *d~֣%ݹ-z폞YYjr#"ѻDA$9{7$n/YE`Fہ @j! ILӮ8<ə[znKYQdɭ¨\)neڍjK\]!kM/D"p9:L_zD=Zk{r m|Cx og=wcYgOE?+ RKn/K\q03-(g`V=z5Ӎ%^ήXQ|!f7pR8 ȿ(~0hƊGȷW2\vI$98 ȟ}{; Qd=2JfX)TM/d>{HܗBwjywSB[_i╡}ZXYY?o/TBKF'SivH7%2kTQL'k[vr%BՇfv9xFWlGwv~)pSjhi. G_lc=E* bs8W|_-+3a$oK-),y)ҧ6:b(çWN왘^kx'rC";) [m $+p_'M4iAH>yiV.2+9mNd}\^VSDQA^+Q/2dR1hYvU"TfFs8HH"Iм#<{tzz ~s쪘k+ SբnYRW}_A@)VPUFNaiM$91xA#&xlkTnBQF5*6! xvಧmft2Bne_ |n<+Ђ1ӯy䫨|zǃ :Ǔ~}lb-[3B%Nu6ˉV%)/Nͼ^&/{M?ƈ .-BWiF3$E92KQ5FIHOHK+LKml*#A o# _\NkVZ*DV2%#Hr4yC# qJ=6EXȵcБX |Whzլ~F㌑80 Nx\Hz(%X/)VOO1nRbcPJR5EUw&r I6W`rŦѕўXFNH96)Nm7GsWCӟgv,WS#eQ&'ZTMk jwhHhc^a&$%NRomԆ+9%4rWSkރ'sgGX0a2VӼ%Ѡ $AŘr[ƙ9zQ]&U R =I D9˔pm(`s42#pzRXC (|蜜+f]E+̬gz5ULd㴮@"ebaGGo]l8}3/I>2W6e%i"QLԽU'B]QuSAHz1 S>PEG:йD W73gܳs $˺OYgR2+8?Æġ(iI3$5EUPДRE'ĭD`b̗";L8E [j?7DuMm#~QU :RӐj]Ig[wewnXIe > YUeS^>K3Iv#(cd ( D#S]Q/\J:ݔk:쿪GnrFFGmmBǞt‡=﹨RJـSRKD7,꒍\$m#vnx*̑ՒHљZY\Ou%Yze]:턹{3L$tmvg$VDy m#^~uvzlBa$)#Xr iIHx;*zh"l!$" V!iV ޷pre@3cﱝA)ƣ-wg(v)xGXoF2dl'8Sw5=_cJ6m1dws؏Q. e Lw0ZWE̴#)D"DA_^pku9#!>獨odG o%AF1zw~x;\jn%Ѝ{VCU0F\"^F RrT'Gr~ȘpE]R>,վn9yد< 6m_ +pP㚫FSoC2$@2O0iRHk%pϤ\dbef|[Al%90/B2Ca>t6NCπ6e@% 4}CO/#_v5JLVxQ#PvR3%=_lm!-V`8yEFZ7hd]:GVU;NF#I% jN B\9O*n[]]ce$ 4jB,I)ZcN쳯Gj#NJS5Đ9q iYdqR&vz? GB.șĒc0"{|hb>6I6p-L M3#U{Dv푌VK*cUI3=_!}7_ wBF{(x =B27+{P鏒]`b ;QCN'-%+ґ;kni5Ƥ%C|$TBR7Z3~s+G- Ar<=1OM?{/G8 B M" DM\=AdˢA4frh4zJd^.(\dLd2L\VvZVm(R*@2n%$428$5C@ :wRof^<Y 5NGFa =*P@"&S}6:3&R aɽ?fo'3w^o[V$1g81o c I V ybc"rbqdOp G,"Sjl5-pLMlXby+C/c HeM%A;jfU~vκ7Aqͳ9wccM,4QfHP$*qq0BE'Z1 KmT#g[^tMXml-6MA~?~rx0E$ܾV8ёXCDY(4(CwRF4hztL;{|^{ۙWډ`}?Z_Yb{2G ;OћGD:M41h"PHHQ5!c8kIb4 Zw ]ccPcThx`Ĺ7_$e':{'>y'#b /e]ה_|ync>Y:lCEt;)##)sIľ 5@z~b2UB[t0Ĥ0lt $E3iX'_L0o#wIz IDATVJt[DD#\ S|ZM ܛKU-. ?v02ڣINL i D2ZVSO}R엸e+oAK3OzNh&t$H0" N>n5z)5ƘLDn$ ޑ4LD/ʅ&SL44M}9xwrfc"B PʢXd݌HE?]F_OEq6SD++YNQi{8/xa?dewVtbnpls[4\TM{}hA[jI-J%VvQJ^/ӯW $V1HzG@t~>tދoqkn!*,XT 9zDZ t8x'i#J so6:p'V8B4݌f6xkЕ|r+V#Uh7( /]p5Zcsel(SX?Uey>tw M-hUđ;ncwi )BR5?E^"`$eY0|N&naH + mMUl:/{NI*L[sNn"8 T*x~!'k M%H'#|Ɂ}l387GdيY5C2t`zݳ[\rLvEF,RfRŔ-3r˔DK 4󎄘X߽0'>k! 6}۱.JTvoGhqEjQU5J5_XR! C4yۉ=%Z9M|3:V J {d_gWEe+b"% pҭG-{& i¸6*"U3o*@]2zοuso:A|e|ߕ$+7aEH˖j k~Q6/qD Apu琢3<1Iu{˹4XDDX|c@{J J4yΞp? RҔ>B6yЏWsee;܇:t5W<;d5F[Y+Z`^;~IYDFsUD֐&cUQ3xSƈTK{|(ϛ|'bԋ:-\%$&?|'\UI:$lPR5#::~[goB7I&$cU͘~9Q1<<"ĀQ IC3 jDAOuȏA[ёȈE1Zz^Ĥ͕j)BP6(5Xz=\ 6:I/;]H6Q&IY!t (rq΢iڇySo}HQ 3>]9 Z(I?5 iB'xٳv(6_y5xpR3273jH#%B)rފݺ:(7/K|w8ˍ?/:VpsrcWӹftTapͶQ(_I&ɖEj Нq3XcTG{7aIddM@w>+6~>ҕtl5Œ%4Ie# C,b!DxH^ ӭkE@<BRӭ*I4Q$UdqN\siLJRtD!X-iyrnV2ڤhB,ET/kXDv'j.~*> @ ~|lT5JC$պP\Bc+$%[ϻ$EƍŸ'h 1e ữbtK"Л )X6XrwqEx#?]͗vXGCR5RC1, ( |/x̾ĝ1\tlqo/s/zCIVwsZ݌$ t2%y<eϵ}Vf<1|eZieLcfyu"@rEA EjhR6] O"iB1 &*|c|$PU耍Qlg1xn6M;#-jt| uww:qǦag D4ކkֿ槌6I)DI$2&I1&쉄TH.d*!|AN.S᧜vN:Hƍ?iH#a7i ٲI(kp? |0 P}Q#ˆjVj5B,3J- KP.*V(}ELM! Z<9x!QVZ 5[[?$+^q WO0ny9v)bۢiE`&B(Jl'r ěx%'ms-zLn;$ˈ %rٍaH-q. )W=%µ'cڣ-ڣ-nFd$tCPn=G_1-Ϯ$5L.F-˹so?Ϫ<4Tٵz?sj EcXE֤E&l,[IuN:v-D.bq‡#P?>y''}m:Fe$6'1J f·@R E2̶CPbwPj@&lA#L0iXQ`E" F"%9 3LHE2i.c/K.ED$-IVοmӴB&4Fg0 jX8xJ᪚d 9}_S G-aD JŒ\Ň <=*}@2 CmL+D"\HWѻ \p_C$6`4F }(D,rX 5e]s-hCkĥz:VujTS/\;lOG3=Ra]ZjCpo},ZFhiEfD׌EP.XO |$,3&Q'ևhϰ׶'(0²2yCʪXquM]!%-'zysTeEٍ[wg] c18 4&D6Brh1x n3m2rmIGsG]t%{+6qs7){5Ǚ^呟 ^4N)(Is !v@Ǚ;]:FT ] `5j,"D7qp N`uBlZ&EKۀ DkW"$R }p݇BWo@"bI"oxU3|U-3wHl,(9{f< i;G}E#!4Mh\-rœvgK~iΏw}!]w~!X4+/YG6m *4M4)b;%r(oAOY{6JITʥ7l$m'0goB{}]W;\U/D~Ysg_En]<|UVlbv<0چd]S5y m4@Z=í,(U`mmdhN:B7|87pbLo< zKO|5y'[`…/%X&"ҙg>mB7⑁85&o bg×Mki7Ls31Jyl3gmZ9ƪctNLjtFa[8u  ͟'Y &*&T=H")IҢUsIRAD MX0/Ej /Q miQBKAzO v 8"{Z?r5AO>}|{N/>[LftA#FRkiiޑBi7<{Ov8D<7|2 !p! Q!x8kD{xy{9ΜR^.& Bn%-Gt+Q[wqS_{_ +ނ?}K|W<5{Y\U7Zއ($?gn i֮XhgnK%! Sڅe @bI!$RzԵ7JnN>%igh٤TQiMv'J񮯱ޕi5Ev0tw`Xp]055 ~4DžnRd{7V= IhL#Ic${QWAk{;I8c#|Uod; BhN^xXti[賅3;f=_<񃿄AMbф8~8SU| کEiIdRJpԍ×Q)M,uUSK. :ct+'ۢl i[C@d#YfJuݲDH$S#FZ9g|ce\f7=?k6blDzq4J,u FS´Ͻ'όA/H KQEhߝ-ԽyjBsgii/oF 0(  Ȭ8BieQg,5 >kI `kW1ysV8n]춺LNmwiubhMlz|}xH4 u-(܈MclhCasVLhYwRT$ q"c^?퉃'T| *]G'.i+{B`nB3󎊊~ѧ=,w$z9j#Z.}ZR:8s2+g壷cn7#fQc䬻=BRכٲeM{ s;o#Qmv%Br'Oe=:X.@/<ݒSͷf|%(%ה(CH ~}ڽ}cE&Ʋ]H,GxXE6(璝_RnnV'1Id)^u+ΐ2iB8hKWYy0J7$QaA(x=;;8]/OO}祸ÿS.N*$Ou0-E *gp2dF~qb钏dMFH7:yz>Niӿ9oѯo7$8h36!4YKܵO<U]]MgJkt ͰrGh1kḬw^gL˶DĦl2[2n_}Ns ;쭜86 Tw# jj2庫a֢:BU $,]߅M,:<$l욄FHUhjQes+Wr7.G.BAe ,=ۀ_шDY991Rb2RM$|zL_91VJM16DR \kGDiM,o褍P͸Gc)2 ipۋ_Ͻ]8vHn{,ZU'xNgwY13uAew7:dK/##2u.ܾUK@ i[P;]ŖYoo[l!5!ƅ^B zd }MsȺGj#6BDrUܤ3} B}xv6R5lv3bsphUctdyJgyQr D)qՍ&lF?GR4c9_"5IlI84qTu,ZVNy,2WD2'm d9(.{)V/#QcC@vTAy 31H)H-? |H' ٙN,F"\cb2e՟HچGɭ&L6 *-)yHdYrT>Zd9&WۿXm4O2bn & vBFQ#]Hhnum_Z?p?y3trV(ew<'${LiNXvx؛?pIr_%n%S!h@>_W5- 9$y.ġ;J(i@m`R#%TMΣ MY1F{ܿz|!);oϐfC;(jM H,彯WFǺX92m崳<&9j9+yʜGNÕ) ;_ *~|j~5Ū|nܢmsZ6iHxh,8B$ID@H:lDWlƻ`\rUÄLJɺ6#t "KE`*͔ꩮ + Zi7Þp {/lIJ5 %qh%k„-ѵrqj-9i|e챡䲄tB{""N q`lcD]yXcMfɆDYJ7pp"օ(QFkO: 烿tudmZ6iHᆠR^AScku 9'.Y1 Ju<.>24˂{ߋo{;J*Q(E I*fCiv;xpF+Kgf&N&|P׎t|7S7a`ƖQdF)B۠P>ufqR)'|GH){Q\ss<; ( Id$`M2AdFl  c ٘_`0 d!!vyvBwWս?nuϬ~}=ujΜ7}>`C'"G2\w̾G^x)\Nq`߀}O;ys/]C hgh5εl^5&晛`H=VV/P ܼVyҵPs?mn]Je6tIu J I`!q y71V 4z+H҄$7߃~ٍHԣo@QQW_ eix2?!u5VF5F9Q7߷garWW8((ɢe$& y{Odÿ|5!!Xdt7Eǔ*3xXo Atܒ$lGDY :`lc@'OǠ5L@9XK PF=o7d(ʌH)V'dIN ~~ƹUX<.wq7$:܇/s!q M|_;_ ~9mp K8P2Bnj৻~ʛyOyYs/|PUؘ^`z"GC|=Kt{v]qIhxLע}v^uZfy9Na3'c5A8W"GG'4NgqG\!i- X-QMEqz;C#R,p2ɌTHd+Ӷ&ljE@.3ib?!ď\ĢlJf)M:CI÷iyH M)M)ń#ItdǠD>^|H 4ݯ*VN Ѵ[ ys&8&սC"@@^zNFɰ4p(a[(E Iʑ؍ڀ俹!K2$?^~#r=\xOr#6rNZjHoK7:|~Sy 68:NAE" Mltf?P$Veډ(.ӚFiӛ/3dIZ`DLbpicd5 +c4C_s=_ ($S`ELy.UX-1JU{Vc.{5YXcD֪4QH|݀O43[YYWcJ6%'L SPD1*D{\lBĐdE Id!FIX-wᾘAi<އV/s!}ԋC!FX[Xi"qY?dmcm H=ךUyx ۗqc(]&Ik;d!W$iJt}"GP66툃jk|u;9S@t9JN[BMMpCBG3u\-P4&1b8#wし]5mG# +iV"d44i@Uy~wgrĦY&')Y ACHYJe9nϐ`^oG ɶ͸YtYjfq ^3:b*P g|_?*Cf6ٚ@>oT^mHgIn&ݲ.;.X5cRiUd,?Xe1 GyShNy#uǾ%3Yܤ8mҠdl ,/)֋ؤ~K aׂFkKb2lcnɵ˱:dV1e;:jHJ%vX`k*>P8$GJE]~xpmg[".(mDH! A(p&)q5pPh)c%wCJ+W)ϹMҠ)"q`hy8M?Ͱ| ~JDLQ$injM];ڀvm?z&~ڣHU џ &EKM`6Oo`{IJjXS55IbGqkwWowak  w\Gͳ9$L㸃3kb2/K7uݟL^hf C^h4Q$Vӆ4UtVS:Yi홒nPCU,ŬY#6͡|9ofmSS袃uݯc@YJ3T6t12^ytEJ%><{JJ.Y̤&**޸(ؽZq*F[l:u,qw2Fi ^S! Qq?_S:ZEhD9GJE(ݪ%Zyh)8J#-$GtIA$IqIqoh::o%u#eig$0 u+rz #y=G5,Ɔ6 ^S9׾pNgU|\{̒SbL>K99;$E*w`=J(jPzXT` ^tovd&c]R֘90͘$Fl]7uǤ fPc3G1ݡc2̜cjڒX+bcg$1ܛ%`ZRiAKb[\sYK?]ps1,:@v86!m+ی^'̊y%׳z$RHi&% Z*)DY' o0bTt%%M 3p ndi)M#GARo %d73xbs ȉt'7a]uXBfuew\DZz\gd:mS)m|n}KRX:ER6Fjc2x~yɨ$vּe]uSLG!|r$6q,x#!@G8WkQcèu-w?H-F5r,/ F3zuq>l6 a8 aL+tb0@l'CRA#B4bӊz359ʮ(4yF""x>6\ϷpSN$XCEX FD`f)~"~Eb-rt^`{NҶIAjk Mg6{+|g+̈9eBd$a2L;d&!7)v-$J*|tCR[c-?K5be9)0$t%'c R@a x- .ђԪqӎM($CR64> U*_PgOx!Aozfn$=(mcJWuKrzJ--R6zd`oǿHnk캽nMm,7לwś{|x߱tp?dg~!M S_\GSv71UNS&I?d`35\F.ZAs @n HWr˞VcWaĚsL"edsᦽ7|?qŗի` <Y=7QLՀP U_yXt+<#P'd݈E&IT")nϐ4SQUG +ጫDPHC4l`%uM|)HD:Zc^2emWC+G,Br喝}~`^t%H(IHHc:RFH:eǐ 'v|jWx Sα(2f01hZHV!\ )!Ӛ?\bjUIgbd6"Zk_TdAK6ӛZ79O;Ҭ$IShmR KI?+y\&)Vزz2ʾ}C{nDs\KWk$IY+yhh*k`gÆA b&f:tҜ")R ߝkr`͑Fff6ั6 _Ы׸O=;v6k7Q`8IЇ-y8 S/p'EI*4 !Q.z<&1h)B}3q4)M;E)*uK-3P%>jL[j4 iXY\#ݦ_DhMhAx,JEN3y& 4H22!t5r<|!_a3sjwpMڀ;G|g F+ǥ)dg#cA$\5cX^#qQ屪U~pf6=7Kٛ"iH ~/%'XgQJQl+E+9NOY_nCњTۙ^PES2hq;c{LNgQ Y\oyg/]EnjM2'H\|G,\m-ͬdINu˦hAGb6u6$zzQHzk^{iEBƽ8~ SHcPM&G Z? rJ}3s2m&^ۣ3l5xɟ3}l4u_{V/WvDhaS<-?9u@@1%- Ժ&"X\v'0 rt& +x%k''1{x?YA:9%Ur uWԙKO{DaQ &iK0O#GH/R۞J>cfdB26rMx >d3nFxlm;ڀ:ڼn-JT0[wq\EE2QEGlPK1)H1I>=Co6i+8hǞYMU1X^BTqΙΟ3_N7O;@CrI = Er;_X-bMPs [ku85W''#H]+ h3m*y@AX=,有oH&r& x- O蕇#a3>Fx' .hq;?\QמJK:W#@M-mh%?c-(E=Vat@+ܠŜ|=v.]J:HRHhRQCjuMjT={ (X[aXp=Gf<5==*KYGqAg5BhPHBRhufL,*<:k8|̦E #ʠjXݷsOȽ.a&pk,&6ڷ,Ǝ6 #&qJtHR+?lѿy-k|;D.TG ^}~r]' >~@HуZuTyFKtN-8|b"P)2Yn 1*5!(<!rdrKcD)̴ezڐUo򤟝?Jga5U!#,x)b {Z:F ϻ ˻8Ʊ:XaP\giܰQ1 m L׼K Ct3E/SdVAb$ZƎD`k@y6dYH2JF8vW i]$_]T=n _6ggr#?GYNHyΠiLS:Q#kxB#I7$Ke7W`2DLUmg{&e:`(BIY|(̟p FG彬wيPAP$Bdq+(peIn6>PyCZ}3;A,6mZ+^wIF&*Q#I$e(;PVVǹ@b$}@(=3me3 ~pEIRFhneQh%|)04JPhq6)?>IGh|6G꯸=AA9FICEFs~8WCzp{.~"F6M3س?hkM> [T“vplfbcXI KFQ Wަ5zP#kKl!Bр0Ƭ2 /bM}"[Ux*`PU\oD~p1ϻcR7TǭktF.b]MrNTF98aIQkG 0B@H~Ϻ<EH., ]f*m@E͐q/Kp*Ū4 [j;%x<_OVHS9t2֢L!gI"$㚤iUL~`4brsdoj<&s$FKtvS7]q ,lv{`Buӯ x}@XF&fHS!?6 ? ?"D=!4K݇@L7vǍ@Gqo<5HbÎVHЀb⺰T{Wr~ *Z HӦZG~yVyϷȳ>t+>-"D`in^&~"c !x>ʻ?xJ8:]K'C~;Av\ZDŽ'Pw>m0sa| Ig&':R, 4zrPESuPfHgܶ|=:wFIn0-%mv`> L$>˨D2 (.B1JFEhcTQΪx0Uh̟+^rtiL3eiNh4ZVٌ֓:nb"Zɟĉk1V>fG"-81:Pmde?<~׼˿tdlcAE0 0hgSg`za(sGw翞w>Q< n\}6m4ipm-քP7< +?U IݎHg%Ÿ_#yK$Aw8g#q,ag_́}i[SzRБ pbW#hP} => M%ZFyV NK(zk\H-^(>"Q$֗q"lA&&2M!9It/<3 0A+ߑ|T‡Uݎ"ODH*6KNLI?tyΫ=׿fG;b !f@jf 3^ |:Q'`Pmq<5)颕>Du>H woQoX $_$xkw=]XLN ?MuL)cvb U!udGE:UT⡯pT.J;5y 9cyS$Ұ/]DKp^~MHj@t':pKd"CV-Fc @D՝aEVW=YV'emXrtϔ*7&ܶr]7@pk2z ~ps(!F"#@ &thٚ]aH%Ay "`ĪbAJ-S"7ӣp%LNIbRd1ܿ"LujOU1 {MLbfzܦW?6 ķ^bF nq9Xi2B-S5K '_ZMaXN7'3n#OXs-\ģv25'kU; Kw?SL 5DH"0^0 hţR>uwld,T !|.ʟt);2N8 33Cza9ذ3ϥ '-9"/(6 ?%[, i*[OCqg0 ~ =-u/jx]_ Օ$ydwS(l7ôHRN:I+B #AXj =˱#w\lӆ9XM*X<֔HҐib*GrʬS.B2h έ w9;_ւ^ueH$M8jNkZC5HJrI5:-vP,Kx&itw?xoC?@I{0ɮlSMPIFd^[ ,r x 1aF6`$DRő&w:a>U-a˵Z[=]Uu^ylrU9yU%r8a@NKYL[H4ڂϷn}*3P-?Pmmw~G[Y61xW#h&IIvHeݗ<.V?`DedEb:hBI BA2'"x_Q|M >}Ya|G6ht SgiŮ ]{z=Bũ5B,>Ʊ#.\ǖJ(PW=Nz[+fϔtV8r49L~yUR=$>А_x>V) B 2>yZ"113 uJ-a>,ȐҰokDJ1:fdF0L#AcMTD$8:h%.Ox?wϑ5#-H[ԭ1k$x|}Ӆx'M=a#,RP `-;F1EfI" Rb@ g~(X6gM>ʡs_mjqsf昜rT7bemkkZq :Mb5PxJ2h& dL Z$K)16o)w8k;jI#m$5,cdJ؈f=['nŘqsʺuX<}j)LjIyQ6SFbhjy0_AUFL.g;g}h!QW FZ#chAk3{d Deؔ57rA/ 77II9rqQD670Mжkc;+PKf,N@[BVs],Wh6͆¸8Fd9Jhdm5UkN|^r<웯j'M+rHǑͷ/:N$]%' Z(zJJzefnǝ[beHlFd-2BRz resI#Z>t۞L;spNm= d Dy?E4Zd~d{?A9!$Dd):11Q-FkAU"TK@2=W5y?qVQ(m@*&SgO^L3(7,>-|_-PV |.#(ikVrK)k__I 0Ut)xay+XS5Qbխu,tQ*Q(ThXǜqoK6E$bՒ-J<›H]f6hgF$Z4FKAJμ$<{w`s_ M')>RUh=->WokLhlyztkH_8e<5M:q-*k@sYblX+)@֒Fß{?ٷom!)AƸ6xL΃rը5_+5 c֨J+B2z~zgbktu+V @.O}%hhD ,=qzkӌZڭ$V0-Ef:I$aP #5ѾIAB̔(]dBBYB L0$$M8kҌVcFƨMu@I{@RŶMMǔ%\6S(D6RZx ^_0 Ka"+AyGm]Hي(;RESŧAlx\-BWP6`]yh<敤s-e¢Z#e)3Hs6C0 ;{a y s^}*58͝w#"/-6|HPRe4Kc%?ᇟ[]z{>xn2ڔ&$];b˅崁R`*=g2gjrjZm t}h[ V8qƐXCj PU}XLa܇Ac]3I7<޸U5H\cl4h3 k5j5$N׳` 8p֑~eoc(=61fJ(J"u S^o}w]˾r4~9?ώfYw cÃTg+" 5b%ak KQۖWLϖDRI lmofLs)͟=g9ezg3gPTh;!W"υ@ld%[jǥ(5Rc8ex0<Q@R, H- $!HBykO9s7N#zBQ5͚(c~k+$yحݯC|yɋ@^x*6=4QIzDM"\7gg1f.P7- r0rb+~AxߡZRfH@,! ,Nj/rCH1>`ʅՐdRY%(}ʣOv=gB⺧,vxjGҗQ PJSE7}o#矼$,{5'y/aD7||7M7N۴6F\%U# 1tC^<Ĭ189e5d( F\WWvFɞ}s0K.d}BH(";݄j U;NL#$%5/My Rº,,,,&@Hۣae^ qyR.جdFQLӀ6 !VcQuV7('c{۱# =BLcHшY0uNd"8B%a !yOX{Jv,i5+Uŝz=O88saCgh,A1ݛƙ[SqB*67>dZb)CQ@+fħ|_mr"c(D4ZF7EUCR|UIONG$"(u;m7^1mᑢٯVb*n1mPJ.dj ]Vle|SK`c_: 2 ĦJnffNՌ]Ffz??0,4AP!˙xFJjJkH.~m?MJ)rULFIcI2ːpeuH8aHa:sϞAeԡR=GϮJJd J)w~WMЙEjE*8]P? )Rp50[H2$(dHHa+0šmuw^9&YoY㎂3v:O6v3Ҵ rxq3t\w?x*LEX"2o?|fg V±5r~I躔ѸX*x]䫪W]n{2*ޯIkwT!07?ȕ9FkIi0n-VfW;~{J(^@)Ʀb-OMlII |D0HL4:,tdXv?ڬB9,)˜nKR2k髮}M%S*È"[۞q_c15Vo?~fҴdSh< mJ>Md#0@~?HGB t.*wz`ScQX1_z q:(2R(6mb2Kj璸(_s3, ?'l{mguy3J3T<4S:*ȸ({vƶvgp!@ :F8`ݭޡU2PE'{j%3ހؓtQf<cec+ LcLfc]խU$riV'͒9W#/# * / *QH/0Bd)@&H^c=ֶ?y*/#"{# y&6^ %8xv^#%&BsdŊLNNtHǢ:ձ~ 䐉MɇrӉ5$PZpǻAdӡ8PJB*d3dݼZ)Lqu8}ܱ{wrؒzQXnoI#>%B{ƶt!'1 I/CNG qAIMKJ-~J M"r|CYE9\#Xc{ѻq"F09+Ah_ ׯ[+V).qXc9y#-&f>5Zj)ۥ/? fm)io8.1*-TuIii CℤG v(i M^bY?8Ԧn.76a i4jppi}pƬ}` 4p#ZTPH4R$%|˺~[hdC>wYT9𼌉AXҔ\w"Mg{dZ;$mF/1ڲP3_sN)Aš? A!*HQ1 !Z1JdKŕH+@`]DH!O{RFɘL?veЩFZB陻iGz6 oeg-3ڲs^lܸ6hd(Ȳ!qƚQCш\##M]US`tԀ}#/73}fJ{7ƤejN5h%,(G@ȃ M˱ 3pN &Br Z>TENwv?SN ;}K?=z>xNgn4l3A;c,AQh$+hcљerzs@& B57=ta !y׈ Rn~{W>/ңZqcn[f-+ni4mfn+1?~}?Ӣs˂0Khbj:B/_4I!&LdGSS̔AeQWVQOmbɵBպ|̝k)ihe<7Ǟα#1z ZZЌUlܰ1ÒlM$ ƚ8 XVTbj9O`* ylj"'CfFW(.d\)l=>`#)-Nh@Xl,ϝRk-0ss51+[#UM`0 a5@(XmXݴ- 淪 R3G1#ݡns7_ueދOH_ y_1U< &6Au؃y_)d$b)]wO˺.,1RGl;CR PQ2s/k,A0Nc=*cKAç?]> \=o'̛"K;m#s9B7't{` ]|1Fcݞ6ff(A/K"~LeG숣yPt$%HI⒔D$&%qi+jhh+ Vri4-6bDʕ+WH;)b$AYd4&2ɢFΐl4Fu:&9;iKK[Z5u]Z/iꏛZVHtfC$6?j3{NӛXc[ڟs-AKLtp&NiL%Q+Ֆ~6ןUB $jɊ%3{񘫖}V\1?L1.y)&q-DPs򽦟&0wBTPEqӐ-sƏ; 6Өw_~ҿ/M}؃{CiYXKS, I {"P 7)% p_?0Ak]gEr-`wd5BM6EL(x t syLL'$ (ۥ[5ڱi~|"٨e]W~Bo򚍧"B U̠/$Vo= /{'-ʅS|Ϭ()Qcv^VLE(\=6BS:C5IUͬXa3>Ú]W?C>dB.+O{:EF>h9s,C%e| <@@Vy뫟q]KǃnŌ1Ր" B1:_cK t#wtHspۿVqMFnqV=h@mUb6}?R * r$SĦCdӓ"ly}:a ˭[l>}?bbڻr-)[Q{Ç;'e"Jq+ a_I(ZcB\r"BdQ~jAO]aRlˢFʻetpٕy*%kv!FsWy|J2ɢ(hƉDG@B"Ej(F*9:X9 ,K`^͊y7B"BkK)@3MVVVa':yJ,ٿ;xid4R<(n,.1;ۮU~_fotJJwx1a&;ǡ_;] /vؒyϯ[a9;;B`EÑ9*T(WTCt_>|/䓯FB.>y Sd@>BjPrHAߤKq\R~8aWBc*YG.8㌫91ւ{RaPL,&y)2sZgHzǝ":ڢS [(.E%BAU- HU1w7I{yi)]^+uRyRX ;NvpSc(vr$Dd ~TKY[,S06 eN'm3v:Qw$j݊2ķjpdAWA*[ek0/9fK6Ѧc5؟\on>W\y8CZ+BZ ZgUx~,3BS'hIǘt8:; }]zPnŚ|B܎a;lpX],(ȫy,ή9V-l܄}{=IzB{9z #H9mɄꝸ?VUElh$,^G|^Κ[!-&jXU ʁC݊sK_P 3ŒP:TZ!>2F2D G%?X/{9èh]IHM\odj(ZkThٳk?8|>sUI0a,&p?5wpDujHV>?/˹j}7HUne<'?y^ssx~c3: Bl}H֚TU=n*g8tǗ][P BZTO5ڦQ2bI>ÏGQ-Fz$έC6ZQ* 3pIJH)v}{xK~½^=y'n m;MPʎPXt"NZ/GzPv<'PuKNq}l sTx?Ϡh-8+F1-t+Cf9]ϖS%~!Z_B$4tB]YO+ _P20ڠPTU3}f'cDv-u=xFxظ3a 3?od"sP'*^mp7U^;V`ɡM(֛~ٖ BEJXގ6T)B%\WPvo/yZ8\_R0ߛGl >au{h1jdsIݢN$؉sk̿b^׀\˘[Gt8/zx'/B. ډ~M%@ 4|g!fD Rq$S;Tap\3'1$,Ae1 2I$8Az2N+zAƊt2x _R٢;^pa (SOT-ڄGNX4%-=s bu9a cV8fªFJ([=Z zM;2W] w|Cj) \Io# #|Z4]mRiY&1*O;s3z7Ж ޼LB%HaBdV \ZhW2A gVs+g']02cNjK[XixcΫ?8XfR^de20gns2% bsH"SL)\P VmE(P W<]> u1};߁8PR"A/3l[wpU?K|BIkZ&%l\I]H@/a?b_]K|SR<7s_ujX9PIi#%cR('@9/-L\dzczܺit-xȋ156k@:HRbcjKXn?//2 k@NVDh(V/B(E*BYJApdb!Qda_vyg5^&D"wvg1}bb!TEE39i/ KLi Qa%͟XvSBMe(L#f>qI_<|-Do k'7rl%rd,2BK p4h xGs ?}-3Aیրzˠi2V\}&Bu"=&@eߍ)f~![` scQG*2q*63I9Kx Rw#vsޒ~o?zݢK袤"I/P ܾvz  ϣ-L]Au)zLN|a pwBKT3+G?[~QsTe[v:B(R"A"J&қ ҤT ?KxA HMH)LfM6H|9S;{w<94z3\1t1iZnu4d#sʫ1G ]In@Vayճn!03J{o[9 HV?_Ԏ:JSz11_ur?@!,xnx&uf4$%M-q2#OnN5IZ' ;6Dh[HkY3jLmQ&}3j₯Wo0'>]xW Ӻг(X`5 'PAkm"Ȃ ^qWS `UȇEavUŸoݞz3\tUk4 GG+Gk.Ux 4۰e|׷ A6Z6MkV)lb].j\ʁo볨Qj}Ԓ2ZLGxk~hlZ:!jL] AYtTKDDN(ƅ4sP0BCZ+M0n5?p c낌(@HDs^V*iFZZ ~_ʟ;DZ$ ,FчR=oi{ =E x,n2XOYOR y1s=\"5WxeZjYPyU'EoRJs+ ̭pƿcsƘB4x2r ޜ2륅mHR$jbw6rPQfߢ>+m4m*lCEpDfiJ?ϣ/z=Q< E r>_e95w1. ,<~2=O)I^IQirQ:qzJ7%i,ǣucWR'(oH)gio4QF^6 ~aYsq5\NGn1.FyRR+|<i&(ܬجS3 lmVY3@YRؔ٥w_U(݇;8:%Z _@%?7VjB9IA$)dWU,BGyy<.:(dl 蠈YGc/Un6z%X s} 8%|rAƀqKm$IS#v=6M9x \FܰEZcq]6tTXj$O@qURiS_$XqJTk)W>*юB)[ocѮ֧|WL]G W[cE}1~((RD{Vd-b5GlLytԁ:QaQ~EIF,*lQ^O Oq}~kF;,տ2҄\|Lzn`hkԦN)r*OX~%ԨQFt! uRR IDAT--b N Ƴ)QXBTS5fp4EZl54)Tj*1VQ`kU:k/ˋ4.SQPnM5(? 7s 6TT@EETC> P^"Uʙ$k2Qf MH]y$3q->!O>j'~9/"r~D\X$ ϫul15jԈ5seN#~z 8eaƟֲc6 yRB?pǷlSoK%Nfہ%! &DE D΂>pL>2Ix-zY~,m<18͐Hq]HR)E-N( 9j< 1̪G9|/+zI$ `mJZ/Ih+o3Y;h]dJE286tq1.h8熉BlUͩ~f>rJRIݭ%\x aW >eVcx_qmEh%*ɚ+ZR^9$W}ltEVZj5jwwlZ\ Z;ho;['۠:ۧIAX$Ḃ:kcnǬ݁YF=YgD'l&-V`Lmct2rT;ct=ǖ Rb QD[LQK~_cSI\qJKgb,.W_q ~}*`q<,ԥ.fSNSIRT^NAK=v['+`٥l fs(uߪiMVI &%[6%IkI9?n غGQ#¬5"[I}UTG姴sOˎ1#ň0>{>-cQ"[PMH^;=ۭcp<ޕX$W©o :8 (Pڠ_%R[>~/\j2dR5Zg{,@ B˯3Q 8y8t[ mQ`LͪfQRM9bSl 6Iߪ_cvy*ETML{ $5%'!Q lTfzDIe뻮;X )aV+[O`1c:*PKu=vx?c4l=Qgw+")6r6qqOK\V}Rll:Z2W" _͹8` Jem謵XcIӬ%]WmlmZ@18,R[ {y SpHRH@E*k^M+Mτx&q]7ORZJ\|ߧXљG)Wh&XEU\q#8riVdTO#:y>.dF@2PM]lj;e@jcE 9=؇@)>pMVźw<[_z ]Mr!EqǙC!xx&/:m'?1DGjjWvOYڔ QJ5e x]fI/˲@$[Pn"|. }h㢵dJ;Z~6ؘk~;"E5:::"[unR6./CףIXZZ9kU>y.U*]ƨw\ 7jߐ CWgtvVH[[C3{)^,^ \ARjuAŤ`5V#W#T}²1-6lt:m$~G \ƏI-DSŋb:EkS) )Acq 629%M-^yy{hH8-XcyOo=.k`l(ײJ۔ 5yQuѠRZ% Tjt,s"IT\آj<@-2+RE "9aU$nC57w*YK"j\"\I8tMkk=T!Kˤ >/;̑wr\Dx9("r(\# C0$"S$弧cn&͸ѣ(vv-y‡HcKLlǭm il6(z5Yg$\1oQr㆙<B--SKTl󫭖zWe;rU)len@tfeQ(!iuJySֽ;M+X[[Tcۏ&Dqʴv<[2<Y kɹQQF)@H3~7~&Q#ɵ E|ZWh=em]0Yx3Pa&)ijI vZ\1oQ?{]1X9u `6p,9/p9a"HAX%)tɷQP(TYLJq: 8h>}%|vo=~sDOໄ3շ6n*Cl-r7l"A HRXEݟݦw.、F h.P T}ʭm(hq&ud*iB,X\Vad"I󡪾j.?Rj΍ e\?M!m"2|ٸ͞46Ja߀M; hy &j4%6WY4Wř pz룿j . *Z!x ||N1P.Ӗz>qHYYG_aCc{uېQTSF$6.I sC?WmE@J%fGmP3Y $RB7^Vkcyv|jvq2?'jeQd 0*ƨ񍡲XUD 1ߐ?6+[9V,R@ )] 1>;e zkyExߣ˙Ɨ L=O<[.jq]\mk_{QNz޾KT|,I ؄ZZ}r J]jcXV*k1f-q,Wb^=qo.^?޾y7n$|ij}1=$}WٕI}Zd{S5 eD|r֠swm (_)" sa1`Z)W+jrk}ю ="9~ugDw/yB@f}u'pާ)ɵQU%7CPTY {Y UDucl>s:kOfmՑ&xC\*FOO'&b„Q8NB,вwLrLFj-.kƭͫɏY]%z}Fևu@eu&/'ILZ{M"}7MA֒gX44 WJ2mm$vQ|ʆ.*C{jDWg4#elGf6џգc7,5KF-$㑪C~ny6}F$i(BZI>ѯB<綫k ڴ~}sGQ;;*Y5p났Kz$PavQ?t80ָ#pg |p ʸ8YN6kI$I"\ygm1Y4i)kٽk|v,rL6)HfMҬU)bTP{7sKwGSjqLSc$!N_*ȾJR 2AT,ꚬ5Tk],3YQł ,;|#YuGD ~a=EjÍmj@HV8Rl9H+Aޕlg#&4Azt:6Bl,:YZTVugрRFNŠNiF]OѸ;߾s0 [KML$k"P@A |`ӭ]c(7hwRsUӟƔئ$֒`IZi@}D Ka`-wt[E ߈ AI }' "/w="WWn{?  "IaՑdt:i˷>'۬[i* jE x>w'YAבV/,tU$ ""?«GJ)ILZ!CB/7Nj0EgBT # 5.㠕ZM]OOJUR0OGȏ9?$i66ZKF)== VrIzx, QeNh&IB&v "Ia%'CB?s< 0\;h{U\jq(QSJo [uBK屢9wn僚 pqpK-Q)W˼9M>7}_I V9*1)6N- G.މۻɅ9z:nI',A$)4ywDc";(S~ʯ-ݭN?v$rGA(HRXy$i6hA9@L>ʯ-#("Ὴ,ޟ=$I0׏pB 5 \=iVs H$)"Qd~ñup3B+8jXjZZgΝo'TIA$V^tc:pNjpr!:hkڂoI@) #A$)'][b\' 7ivʒcMMcJ$A$),uIb7%&%c$*SDy^R;ᴅhE;^6ɒa]wtyAI +1"ł"L8S]$ "Ia%؞ rnuN-aUB Fads1e*E߄S$*Y"?M; aDjCP,S:Bٍc6$p{ Ax_?@ 5z4Ch ʁe'nC4 oQpQShbx.#[ӂܫ  }}G$8y* m{`hFg1ٱlJ2j "IaA$]gp MA$)_D)GodӉ2)_[f-GfE\& "IaeoՒWA ,}ӒT!YwQ  (H  $AA$) "IAAI HRAD  A  $AA$) "IAAD  A  $AA$) y >o`^wgNU $WZn̫<&Ylut1_~aexҸ l<ĨY;1.zD "CӾż `my`Itv%56 kb[e'?5l?5npк~X)n)v߹qW,AD<iyOU>1i#v{ϯ I\brl=zAc~q+6X*hI}o\( #Wgl8Qw}㠕G>;QIAD Gs7eۓ@1Y<}9l0_3i7iOH9|49]#̆,կˊ*O=3g5m! "B$BS(yy{$AIc_ 9{brhxZ#Q-+lpiOT)'l%{_XcwzATh^oDCey߱ ]# |'׸]֨cJ1;|e} +2'o#~n>quڙ-U A6z}y~W^x`5+7[Mo;=}%V}sM)) 3vO@[1#&4|u\0VaO\qѪƸr \6|Ԉ&xC\˜.QP .MScScc(gSM6~Az?3;NS<^X|aFb[Hb=( jCPldaDw=.MC#YxW93vTGx!V9Nʎ+gVA6euYgo_njUy1>(~tLNQ0 H$!]6HHW=pWDnϟp,N^3kҪECˊ &qcnEV\,CA"UA°,N\bAc=E68Q^#rL~qr Yqm!ko|Am=_z]18ү? U9qAI GZQlq}'Rg}S tf6C5*^%Fc%I+giQn \Rӈ()ןʂy$,F>a{-cv*H) [C/c팋.SU3 ]qc/s0rd)lBe?vN-vyӵ7_P<j( E'(ď Xke,eL9keOۅ..K{ΩhpdQSGM_spw&dܾyIw3k6;j={|t9vv'sF +ېt2"]:({KҪ3oOc'ۑGщYJեnyV>vzJlMko|Am7pIg A$ݺ*Wg\t3-]wIF5ӣp[>><}9,{%f_}jrّ^R 'zhzeG_T^X*<>3IiK7L8gXe/n^‹Cڠ|Y4$x٨oFy}E͢p٬!qvm3TcpMD5η:m[}TMʎ`) Q#L9AIdl$t}Mv8QIvfoy3ղD٤_~ߓAw:j~s]u]?Ћ $Wl9n]H!oDv8 }l#%[(߷5Wɩ{2أZdcNzh?NGw^9!ael}}3 ^,3d10mlNsjr8)Y>v=bi:?ِ'BM/!kqs%,S՘?ܽv .}Ӕ_jEGia'{?oOyR#ɹE a.kyvt쇏>29 ^Cu~rh$ِ d"ܨ>T6{S},mخV-$afU\8c';lmֲK]F8x=a6;mvl;얷m=)vc|K[o-oz;uRhN }i=rFm'5?sZ9'KgzQsrivݷ#AVX-]լKQ<9@o.u211CEِpǧN mɯR 7͊uC9uRh_t;Վm&VY^~>_=*: $Wd=~5{Ƿ^~O[X2NyjD~`S4dU6"P7>3~#j7p&WgZZD9\TYk[^eGvD $?\oOE=~A.Mvu4}2%a'кn;=GT4$rz?8T-z֛\;ɘwDTD ҐVQw=~_JAVXV#M6٘l]3و(38hCϻ1t~oiio0wxc\sHC[*w=N3^r;p<感ųW\OM;_ھ#v&Hrebyډ݊tkiJi3N?^*norQjyEو>{쵧A`8.bjr]3PS'!- eQw=>W_ڮV۳5V  Ә\VDbmLAeϲOُG><}G)UUߺh"c1#rW| -ϵ%4Qwwե~)ŵK 4m8m[o/ypB]l%hR9~o*n3sW<5EzܙۦKPr3l/uxT{,@Qw).KC"l\_c_wZ rm}̴Ongtw3?ٞ}EJIAD+~n?&϶`Ԡgۏya/]m\z;\i\׽ϵlc9AZ9[~LRwΛi;ofG}U1kvzu'ٳ>}VsHH2d|P*Iչ_dW>h'*pg3ģ+ڠ.K v*~p_?e}g Usvnuۯ]j^O|у,F>*,USM5oÖv.t 놂b+4J_ӧj'C  q䳚)Otuߝbc}4nmz<(Vd @]<L|5>-hO j*] 킴Ƿ1/l ZGrXR5TS!6FW`+5W+ ` EǮ T<׹ɬ0:JniVⓡ2u brc6$vރ>݉C8MeSCQM>(pP^HcN=ԚL K[Lt JTSM۴;JAIAyn2OQNs;b%¹$ +:UO:x:30>jYaS?n- #݂1Ο-~<4ӻc^{: D;k/>j~ZKկ|[^i16LIL\UZaKNTSM5ElOA1UYV!ԩBc3Cբs :6< (}ܴJ~?Ys<]Uj؁⣖藟aܗn-V?YevӦcܤOjNuy}Ev^\nJou"y~&8EAby̐վO C÷ D%\[``ͨIx-|[߹CR01R5T]ua""#"BRķZ`,r-BD^lPрbmIi@Ru:"'ʜB[i0EبZP:DDhfD_|\|ͩAⴸWξc}Oܑgj^Cnx`᳛o69Nk=V'9Mmj/ZCN^tas+VyhGg @쫽 foK1tz:g!Գ_=/ͳ'ʾZSC :<$kJ #XvVkEtdoO # 7>F7:JK@"U]rW-qjVуf}5q~:R5TSݭ}͂zgJАa޴ԂXH /S1m\| 4>H:ln'ʾ:zpvCr,>?7Z K˓#>%KKIsI :<f}ࠜzJpGK8y !qǸtS㔪 7*%PaRxAg \;FTykRVH* 2sCT6x[2X6 ܤ=JbыmFur8e8S/Q]s>gPݾq4^}+DONfB垃`~sJzTpKW1J$j2cS% jsBCڪR5TS!_ZqT9; &hot 9 VOL=%΍8 ^H)%&P쫽,2,-Gn/A6iaQ2whԹ*nȦ#}1v5+>8щ%/ÜS_hAT@ H@j@IkRU%5oNQM5Պޛ?+#4I9/69#ͱ}rVTI9 ~홯O>}22sC쫽lFdT_-N+9痛iTE]kԃPAU_}1XF";`eV-L^ os@F; hAFЎS3#leWylR Ji2 ZUOr׫/j|KqL=Lb'k7K ;gFVsccLO.:oz_So[n8(pϼ33ۿvlKmj1i9X9S 4f`)"RUAy&󨦚j*$ `+饷x5zs%(╡qԾ&|㉫ɁP^(NhԃDeXq22O>ap }H#?yvKvvK2*^1QP2%P X!*JTS]'0֜AqT.uK]>[wa`,>2!1PO"",9N8o""L/ֆZ@ -D K9΢Ӌ#(?Uy 6`REiGj-¬0R_kл85]\3HIKJQNʛt.Tͬ-U3kΧTVsI15E Y~(L"OyS7>5Yn@oTx5:\p英u/VWsX.WקbJt=uTsd&!5jd&fpQϟCKȔ% "qID6#OYREnI%OO0'jR|U[jUw>(#{:]QM;Ho/Ab5uJ dviٮ&9~]s3܅Eg+W6Zp&_Qf;E61hT }p2FȚ҄|h崤SB I5UժЅ7٠C9,Rdɺ_ *Y5ڪ^դ[P2T ,C'_RI]v+CivH*R Z.d\+nҿR@rw>(7tTj{ lݽO'qg}llIƤD M[j񅐴8BA,?Y!CS6ߜO/{Yw^<}Ȓ :=^'}> % b/$1=!-j曆BA33,WT{ܭUREOM*=QAvj2k2YUҍJ6ɬ-qcd:sEW-!hx3""&'H!yZ-$qA!%)UT*uU1u)SjHwEwOAJbj\ST^դϔSL*=,we#%aEdjaW:n=ӥ,a(zgXZPy/V|FSu)e$n&CR"IL|&8YM,'I8²4!J(y_ R815iD/3CybO~ꛧ$$Wo&-!1o[9"IjuV|mm;r[VK,I:-Ҙ*HS)jҔ%WREw/iL*IUIMJ]XHZF ') ۖ&"ҬW),LLRH VbU$P…4㶶]y8(#،$\@)O)((yRW+|K)稠|Gܬ!$ IȒx%ȓxY˽ *tnIeM ënvq*k?V:%w׸V;t+#]Ӊ0B"Up?$HB6G> X@sVb!ukz~,,ȊW7\|FEk#v7(N7+=RjYҗ{1cwf% !>d%n"=njVbb$: ƌT,kOf'"XaAzi7_&-MgE+Yulh13Xc.zC(yx뎋Un˿~T1J}co+WjjC,=F TEAE!ԛfF|,z$7u V"'^|RMm^##\QvOrA}Y8}?>qP(9})4a<\'(R IcM#*}+ܸ2D N4iWcStUM*(:7 윂i߳qot9d1GinTưb@|̞гRZ罖:GoRU禍mp Wh Y`!SYi۹@Hv>t i p H[ZCG^vӯÍsoړ^䎬xq\QmyM)r&UTeA(+T.g`8пN][`S*Φ)g? -$}=+Zt*PjߏNf0s[H ISn?\V "Xx.KmS&V9DPorH=FnI+ZUAq Ro<6)r&*C`0څSq -| J4}f{" @#Qs߭&O0Nɧ>kAšUJGVEq} 7](Ca]Sd5+/?Adk6U㇙Jv6@ȁ+ER8Ue ?͒"GDžƭe>>_J:&a!-H ZI=}3 䃱b>deqJil]Hq~D']KZp0H\MM? Qr*I9(v !4ij5˵T2͚> )d <:Dk[6}*CM=5Y5Xld~_n{;+*TF6υcLUm*Ӌ]59Bֲ܌ yD}: ݤ蓥߫ت7,R{&{oMw?cVcgL{I\؎EIxl2֩7a wdc3{_1HW/cw?uox(rl̈ŧV!PJ1^yM9R2ɕAfC=VtYE]VTq>۰-1~p57Ge37¢PWkTr-h"F%iYT-uQ1?f-:9ҁ *RTP|+4_!'4K\\(cuKip}s%3Ni2<;/RN;'2pa :qv]W}_ a%_jkVk7)jŎTrpVBn;'+L9080K*] ;2dϳ؟펰gV1xA0K;^?Fr8s2 >kI}jPܝjXb` [RTxu O ʏhFTRPm.BU~e@j,q5 U(c? GYϤYN\w_LgׅDׄqZeꛚc ƞ j/8}plހjOsb|ת$=-私$]gfC{d*]\gva i*Vܮ แ>=x2S+hSl)cyr0 G"?SPJE>@rWjJ,G?${/i+J@S|X"c{߼bcPMp`RǖH+nbAK!e}~qϩWliv^N>@PFUƔԤVJ>2U{Ӷg`ԧ e,;&PUg' 5: ?%X8$M֊SDw&xC[q{nky,:q:z0ne@'v8G{^O;RO`Vc֡QܬZnTXë 8r3Z!5)U_IlRu|/ۜMն6~/P;pSZvH,#D5YjN,c:xu8 JOO1 SzMNZ_ pS߹zͪYvtɂ6ZOɪ4+e,Á z/יv"5P>[ 7Ղi~6$nrr(cK͠IB_r WR`C*ԵhE"4)06*+㓦 J~sM@ @&+< eKִ׹ArSιϟN@hR_կ Zw]Dj7y(~}mV7_ٯR5 hya4fRaЁeT.<\!jfj)4Qt(pkΰ=hKxz "ڇ.] \5<|t53OV-& ذlSl)8˕c \=Kύj [LWLT깻Prxx*cUP|AorPiO\P]&h\֖Fk)7!+|uk #?A`>,2(vph\Ue'Ym5pܦl`|c?&Y#nai ڏةZcN\'=YϱqX? z%O9ʁ>BϮejfrb>-,shZ88m1@?p؃"d^{C1ϑ+gQ$Wm^8 hJkq6YW`Rר4'7.VxZ:%}cPMW (jPn,ȏ-Y)H*]"DT4jC#4?X& g؇Lҹ`>]QqUAqќV?hTP)LRr@3[C@ )Ʉ4ոz Xe)yeBOOh |DuNOnآ P \>友jk-J4x枻Ц芏Vˏ7Hnܵ:쓛;LVZ} ./BHa>|$%ˊrw,hHȎ:F5&UP|mZX?,X"/cr%<~O"(g,`^!δ+\ݫVoBXP.=: ; ؑ#oc (5PDž0y(L+5 ,<ݫ#bEC)䄞8U;Ԥe,;UPR'k芣Wc8«)q;g%:sS,_0Q{0=]nԵdn$Su)rXs"Ɵcm*Sn++Fԧ ?@a)$yy:'J!;[ݨ*:O& T*$5XiJWT5x"Ol?)o[bNN<{򡙓|gcś$84 E}pt ;6F}w]/Hh?a ( VհBs);G5%`ڜM GJq˹b4t f25dER$j~ii5ϩsti|0f? f] Nxxkfod[jSRP9VCP0,RId깻HUp<q 9"G32K=Ɏe9Ajfo3@5NBv^K'Kfp,i.&HդPhܑi[Dy0g):\9%^i??c ەcQ]$pg̦au;mI쒅E٢F4UA*2r >cj8-*'V4 C+<-iCQ y*@G{ḅkff== k3zX<sfP`}V^Wͪ!Q8E(UҹM% *ki2uXXXx9VņݞwFՠU.@Ҵ'V5W J<},ewU:͔X {)T]tv Vw!I:ۛD$mpQ.9;Q{Qkʹդ<|1Rϔ^=콯OzFE ͱsN|ngӬzwǖlPβhaI]o F/IGKy:Z=޳*q3Iy('~^}K*$䱛]@_?\mkeyx4GfEPti)#XӤuZjR|7C\E{μ]f]oE\U lႋ㚞%j!(q<"^zYnjhNդ@*_#?>5))Ȍy$ȁ' fBC؊@\BYj&ŻDQ OdVS)6kNi~H{0]|3{ij,`bBA[C޿ nc0}T MJr0ʭgy*6>'9(]ӑLT!?m=_&љDwbL CJS?1u؈:n(s-[}N |(* ?BG=+EzۆWׅ%[G"`Cdؕ'XJ%%)g 3kN}Pf}[1d:!`Ͷq(+wcZ#%n$2Hm ٨vӷ+ &)IB㺑-m,sŸ6Y}K)޹l2bBw47!\J4܊9T V, ezUc] IDATc_dbO@WY?2ӑ  T!Y[Xh}bݰ Iy9zhηEIdZ!K6{K`~eO .[w `G΋#kxO<6Ű Zc̈d]+u-&f$_C ¨BYJ-e-iC+Lxq7Z .ħ|Qkf[-˼1P%#nx1H>`ڱscRj;`]CH .L>F йSЛ@Pr5Xyw~~ϺzQ_cBRX6ۿ~q8ן5rRQQJpXJy>GMTݯ*$mh`̛KV>@!UsQ-;PQp;ƖXӓ]QmvU(#EygΈ*sO2gB+ktF\Կ>.Xzn'M;lQ~u༣$[X]y gLUOoV+Zܵ\U-*H-Y:'@hbu~|VcەG>B2w7Hm֜jӗ)>Ϭߏ7TE؂:>qá%,PC8 O0H)݇ws1%zG4{"vr%.@O#o/WAz"(դܤJ&D]T!?k-kzh\~MU%TX64T}a#6|e"z,Cp%"diJA K+4M[=cS6]^|-(3L% Tґ$%pxZoЧ&MQ"2K>NbIs" gdЄCYKL4Ǚ]#َL& JfiE ݯRpSJ@Tͷ`j2n,۴Z*SRKU EyԎ1v_ۺ\mرY'`:6}m Gz.$]f\]r`fQUtT!?eYfg/\oє*;ZjoZܻ+N5p({W3Y8^(iXpl7ٔ )q+HS==5Yԧ$X6t~|WyƓv ¨ISԥ1@mT;_r-<.{͚ӚZi^^h吊-;S`iHIvl)ڦ2]1Xc,n,ݡJ&J\?$4yhYzmTp4j@M& $vLIBTP|l|ԧY,Z\q>/( pX͛)+odAIZ<2Ǟڮ)u$\8&C?a*|۔cM{Lҿ_ZD`JLa$Fy, _lu w ):oVqqYs{Vl2hg4ZPRIaispP7^AM0W0%}ٝ8܌E%~Ӥ9q)Iv&4=BN*-TN;^TL_}UTH$\2sRW+i1Lǣ֪Jڿe=#ڌ'N q&rI⇒#ŽxۍJ w]8`Q({Һ\L%G*0<%~b]tVuuIgi~H>V:Rz5CYj3ea-)(@^>TlAmtC-1\<2 RǂT UYdGcW*W:X֪U0qh_#;Hޝc#")zKe4{Z8˝a\ǙWPޚ>"BLԲӤ6v&"gl'&{ 6QyZ+΅]@{h/OcE^tyznj)Z%θ.)%N|\*g>&z@/4sx|Lrzٙk$t$鱫k#y9mҳuGM#ێ5+;}OhvF~Ҧhif$=(S4[ݹAI?Nk\O6޿BV^G7)lG^s"uhڛ:4xn9hz޸>u(Oغw D=k4bvg@{%(N'4"b>+8&2:ѿKpOX9+ęAeu?w86Η-$x z۴3ZIcV׫8%W\ c ©sqಝMkq\ykaRC7 (lqb.Η>ٓY*_Ud.W'0*u3Mr5㇔뀪/adWc6b\1n,IݯyZnWC&wϾIg *9~<zDzP}(닌+Ir uO& }Tݭglu]T˼m5 '?ccsk2H }-6K+Nا]ygc3AAem08_a3+h_媔6A)S H\;KȘGF'5C`鹡bKұQ~?jm<6|6|''2EPYR86kNw>׆_o[4j&ut!: w-0j#Hxc [ޛoFu qE ^جx [@'hbv}Xؤ1KZz1UXos0{Z4,˵~dRSJ+SM-I v"ID|[>fҼCu%3$*mGVSj bSrʛ1jՐUjrUrpX}RlXxr{.4ڒ4*w9;Srݭ&w3{\*wr׫}ݪJ.Ϣt:*&w&v'"„bCKݮ\JV":cȐUIENQY.Ձ^~1ߤ됺hMq*m+5z_U7k`"$SX+1hYVLÒC1ImiRrDVSyPj "$hbR <.V_+~Kȷ{^IòkK2R@Jc%\H$re'ʭC*n"~D ņۆh,&i 4JFI%@aɷ3f"SAYHARfY=ُ2JqXJAIDh{q86f2 V +!HrP \U;g8 B2b?2NҭI)gU3KCPGS OEA)WiL1%R*A >/jT&Wr(dQBQI=) % I4r@,ʤ뗃R }\MOl} [Ѻ#BbqGu^-N RNJ!kWk*[(Y^αYVߣ<ܐIBRx.&MQQBWSҸR3{JL:ܞۚT~6II:z2:Gc 7yM>X@zn/ idEVA"lV*$߲T^ymVRoocA1.ħuŸ \,JaU*V*{B2V~xGO?fɁgHUr<ަѷ:-lCUI;UpiE3ZҔ؟>C\V-RWb@JC0jP*).ރdVkzApT8Wu P^P%@:&߮kE& S)Tn&跼=IMfJGj,Y}<٠Qesk.B\%N cUnoÕ#ҩ벶29(K%E)Wuxb׆:>f&pF]0"ُ2}b9ivjxfk1$Wx,&qIyqJCJyl.}l5ԗ#|qIy<ҔJ 9JY>S2\mҳzxOأRlR))|5V'y2n)1JӨԲEZPlRX cZq^c;r7)I<{0iҎ=k26.稩 >ZخVEWmQZr yިO"4M>KʴuYкtv?Uc篼Q9xXPuqP'`cJSQ=W.W|5 ۣITe…zKٱ\]FQlCV 0QTndǮL 3͏䏇iHNݗ*7OiʼI9␦#&j:^ȼA9թ(Tőd*0D77Yb 3idU{6͵Tm1m(ݟ=eASFkOW=ۗ1j$^I/7&jR"GĖ{kUR|.RW}겴Y s1:O-1oMS*I8"8}J𞋵6:J 0qR6G>I%bLRdէ ZE*I%(U΢TfT}^SUƇ$)dhR52⭋C9`&+I"B }k۩*RUo?ՊEk?1)h4v[3\VuFgLdjL$R:Vb]T*FSբ%|@-ӧ(b,cU>#t(M~\𸥛G$u)ze^u9focJfU2k!ZYUJG>=$Ǖv}C=|U G} XrWZ|rIUFGATBLir8rN+/r}5Flo(31Wgb&Ec?DPP׽7cyQ_,72rlW_ Uń҇ _Eb| BR5kndE5SsiUT[rP3nGO#B7GK'_Aېng*f2)8QH%7xc{9^n]{9SM)#ݭj2n#Jbb!] X倔. ٻ_3N>^^Vsg 4S1_a HtGN?l9^شBwU(Q5bvD'-jn^XT5TWk9Je21H]4M SwOӜ h{jkJґBqq,JDZ?N )+5 nxBȺj*$ߪ$nGt\G™|WzwPl#=.+֯)~f(Њ@i)K,ȱb ` 5᜖ zy~]llu1ʂ h TXq=vwH]X뎥 {+ИٺtRUv*JH=)s $W#Tnw@f"]U_]akyWu*ʁȡ(營42ϛq8 շA5{zko&FɡhS1P.F1B}(WĈ]y&^?"$K&/A M2KTh}VhDƤ*2-q"UHwVz[YDl;bG\\}I-鿾0FP>1`-JVY IDAT5`4yͯ]gV'C*ҘoTD@xAm 1 S1W>N"sJA93 k* 7΢^THaɓs~ :_3pEwڂ͌C@JmT7j딫D)a["6E0CL?.>x:Eq,u2Z"Q\Vʫj^q(E_\s?&^q(Ej10bRP1%9l(>~/EiU+G)**-jP4m~Ө>5@`n~_LRORU25p |s ~&~+o[?T+N z#*iƇF Db[PrU,-eXbN+H)UVzh0MjA>NKa8OX+DFtt'̕msWRϔԓ'?3OVja&zv!:jN ,vx%SEdp5U 6[ zlXg0'J)G)F$ݙn$_O 5X+J9W/~U),5>k4_]Ds7;Jv o- HL83`zG>17o{6m%Vt[Z:%>0cNay:$/?MN)ֺ>,$SAsJ9%GX<:%&J11-DD}(czr(hۣne%uIѝ18\wrD7*"aVg^'@uu"4S>i.:&i4yyJ qi]WS3 &oW_65 DNҲ4UiYTYf#^.kHe)Xv05/M *2QڐUs_(V6;cMu;%Id)VwJj솁;zZȲ>S㑲y' -=W è}˾MGsy'72uMܮ֥TѹiE +Q /5gaժX_}j0W8iY{o6bn{ sS8%;점M9on|ʅ1j҆/) ֋V>KFSqJ9]z*2!ENQ%IsR=C3#2Ar!&+ᝑHzTԫP +SDl7+DqJ)6\wˬڱImu:&["0|%VT-kkf(+XAS 0qnǎWwf)L^yN^w&ߴB~/Y=6[+H=DLl^w[HgGow +,:<_1M}O[cBr2lrc7ݽrA2k(QH1ʩ zk+uqE4Y#AXq--'HC>BԊso((M}>m:n f"N%!岡W}W?it=$}D깇+.NiQJYhՉjm}z=gheНY[@tL5< dTv.3nٙ 8)Rw{ (+6$)9'3p.bظdd.\D|aa Ѿy ֧@ecMoZۻ~}k}#1@u ˃>p$0dcm5Q*9qJ0RyY@=#SO:K -NY%EY`N>]!:Ϣ*Q:_i x qcqYٔ?Ѭ2f?T71,0eEq߾M(v1;Yz򴰈,S^U.~OϮEvJ9.s3Ũ0C9㋨{ H$`NvNC"MMPKHdj5,֌Giܕ.<( jԩR[JTj+*~z)&]SyWz٨a, RZҼ01/) |$ƿ IF\ڠ1M={r% <^e퍛piHKW1AcqbF%ɍ@:|/h^d]3ˎdFn R2Q&Op/k)+2{F\/MiO cj>)^.ȱcS2'KJmՎMFF.<P@H{|hv wڴis{!bXhBDr؜+_m4hyt\Wz"0tkEwuLTE.>HxZL={J,mИe}~#Ԣ,( %?G'/4ozǧ1rEr92C^h˫\eI>wЃr-+pG~rL2;U{`p9 [f)K%ו*a*rBDi^$9!86gN=YjbA_bp2Z-j)Hq㐦:b#LȋIB] R{61j౯\~&c,GgR R-BH%h R֤ǥAp| zEP`x}6"9a1.kH+K%YD݃0pDihNa9F'oMEY ,NMU 9vSo5]:"d'%i!Mӫonӱ) TUI9) i}"u?eEͰ=QLmԪ'ЂZkZˌۙv.$+WV%Fc*:[ gGWnZqvtP :e-&'Lziu_-q{EW "lGL@R\F((K%6 Ebp48%fmfSWuD^7IS-N(S>AM绯v1(m9V$zFҊߧɹ0,)RW\jPo-*H(\{T7t&`ɒ rt3v|b)>̉ 'a~\WT>Aew[,~`t:UN>}RL?,9U-"ĸ*\ITnsI9bp“&MJ)K)E#O+ι>'{.H)86%?2œqC$Wf#J4d,r=e=(RdawUP"s& R"Ɣ -1shoVQv]V?MQ-ANk>XouslJI2vYqwO XdX/(70aM$ȴsiv*Y&QS%fLjqjhTU`˾@wSk8!`[f$ҚtӬ?dUZ U99H%"cݍ[,Z ے5LcAg<{>=6agRU"S }O.rB OuwOQz XY^ڄZ,b%mG )=AfJZSdy);fAadJzԢǔߨh8E7ֆT6˞JbjJ MY۔0cyX[l" ЦDFdsut)qtLcLv@_;{NVcm)VNӣM$zv~̗ s 堣8**G=ԩ2lGZU* .vo&iaI>$&In &5~x!sWh% nXs/VRoT(^F. "J,m- Ty\B]qoGX:r۔(-IR-H)ɉaXo2EZ D֙FPFbTsg/JJJ@L` ~& iiܲZAXS(N:09cAE)bdL«[ ;F(a[9*S,W%X|3!>f񵜼Hn.vh`Us-F:={кckF2~ztZ7X7RRCC}F<#H5&nAP5;  _C+ V6Sǣrf2} dcr֏c+P\t [Aوy&%V~O7""Bm|g O}C 71ɍfj7l3 j%LDԖrIJQJYھL/;V-Hk"tN+{!W˲va=9IjEÑ,9Q)(7y%1Z%Q_Ր{lNKĨ>UU媉T'J${F7>U3`bU+{xF̺p7xҸSx% ɶ7ؓgG9^4%Xȃ.yxr)fyvĶ,RyrKrHWіXMi:VE]<°w즼3c,sv[T܃[aI )&ˬR=5( zV_ :[1H9{( eUK( =Xc/Awܽn9@pPw0RX#KeVJc[rK=sBn#R]ؤZrKQ6nx 3V_[{ZRC&ϡ[>b5E֤ҚrvC;~c¯  / wqI%EwqTT%4HYYa8N[QjUIdUJDȘJʬ$7Y}☥b7M)j˪Rl޺jh\ڒܪ.5BXuiRI4a^G[XcrRr}iҖPMYʭڲkJFIv(\wRJ`4ȿϼBYz"y%SQFeY6J#ZieO}u$i\/7$I7kҤyYfC ֽr:;ZJj|\ʬJU;}A ?} WJuwG/Jzd.>ClaStwi0[ʰr؀w(H 2ߊ;J9󀇐Yq'HZQ8elYzz}@X7άiX`B3og NT^uc&ϡ>q9츇b}ʠo J@ j>*d$Gx;͂d.>mPtKTzVuU뵨\ RUaek-'/RmNd3MS6"eiƀFTj|GdU[B]HJ5 j:$>]#S׈T}_{,j˨zeVRkJF][Z~UCwvu˪+oqO_J*q7 ߸LrQ*>VLM%@]\:"j7&j=mnIgKF`SbcS:v$ĩ|ׯGUyETIW{:]Ӥ^jzS"9 }<"'<nvj=S$pss|b-.u@m].Ԣ Me_o#QjNR?j\ M9_78ƥSP)0̳"fst76i,JS rɴs͊+>1"JY~Us|^+|HAVؘ60\s|_0ܯ4侨Yq|/qzƴ7P ,HaI2E?0kf,t?#oϘ1'ʿ\eGU M<#W(E)7r7P6WrQ.p(7yF9$WRD$D޿o*It=p6TBAϘu# a<& er9ϤR)Q͆Ү78 T-p\}ZG4 7aD|Xg DfpYo^Gɿ*Va2\ne GS|ŧ{ 2.. 8_vz ]c/'JDno㍣E~eQ6> 9 OIDATn$!U${=ړ_ 9d ܌N)ul6/0,IyzN "^/00L!wBõ&zY9&إSm`!EJQʆ:SԂ_ƙ@Ctu׉ bjXDTI$ǐM"ʗ !=Qqa8I2/0:ڸo7+|9 [65Yl~kZF[ =RR֔\kj7o$4iJ3ӥ"x n9i)]6-H%WZw=)H-/] Òd^tm  L1 \Q~c~k&1PK LX\ y]qw"꺯QR+ð$ ׮ɵkw% N_J>|Ȃb&mi)rI:t&3} \}޻ mTe aI2L(EY⪲Oh`gR R]f}R8Ӯbj!K_Ҽv$ >ߤ>In 7 ؞uF36DWЉ%O"_H@'>vI t|z>K)屫V y{΍-oqԵ4eDDx'-5qyNmܕuFC^@?7nA:;w{LAaf}qȂ\w=jL TuNzVpz^~1$QTn둛#o=K$Վ? SX`I2ZnкSgYÍʯjdoФ%QK\R;|enT#G$8ռţHlnAR.%Lv'D.+x"N2ގHV$&nBӨXhkgΡc}mΜ8|z$v|&L1T[n,µdl+$ٍ37S?j}rEO3"4׹.@ 0$Ƙ -u&_R;>r}SAwiP#yDu[8U?@+y^~1Gdl ω͖Y|)\J&Qi$賲D:2bn5it;3QtN Ur-L*裹w> 7=o%;ǨU 4gP6]i'klpxnmECz` p6aGUnVN Òd_to#[OAp+N $ˮz$@0M8&x>ɮa| ή6TdGZ2whaU#Ks+ꓶ0 Kaq"%W9Sf lL <=,t֤%.eT;ldۙ?f[Lu"֦8%MȺtrd$Z)ql<&Z%Ws)+JHDk%LzHA: #obz*@,G$ {K\R72& %0֣NRHG'*ћY>=k0:woƫ#J;9)1J!4?wz=>"Pr%0}fl iS+ql;j¯8}'eWd罕l{Y3Ԩ'Cc!艒a$ØE;iJw Hؘ**KnoěŁvP/-XdE6DWs"*n-RO1b`0 Ka͑$J L5wr)ᱶ?knoMPz8p$ }jBU@PDa-%0,I)0gFFolTǐF(4X~ _04&EM+rxV#SaY:[/ 6کϡC{ ԬΙb: ^"y}vN Òd1Ʌ%I-.K ՜+QV$rð$&?71~;䏙b i4wH5TTryC?bRDPNrٕaX̋Ɔ5I-H>(, FAyuO|aQL_iމݾEM$aw PUDE))"kc2/ Bm߅y:neMp.}tDRq \e’$)""3y)Zh!%{2v]y,%H߹r00RM+<,G)K{&Ge$>@AǼ8ԎCjd甪t0-BQJ)&ԗWͫc;AQߝݾ(yla QQQFÂ4J!X! o21Uj;._%2*/Qbό%a ^q %SR\ve6TI']ud'aD(#?j5QzW>cX>cpPrw"}՗$$esʖVRdIg7mZ u٥"±9ytnnlu,[(*ܓ@=O\ve"/Im% 07R s;V3]z,`8ԙ*೻uQ?4ȿ_g"mgoᵎ#|8!s[-y)']fq>!Y&ӎ:E߳ĔͨʈƧ !G0Ρ"}(=fFWh]ÙR^Lia8I2ϫ,eK*#6EĪh֫(w3Z8OhNۿyWq$iw|~x}30 K)*#qdw&6%AtJJwojP R`q1 Kyވ;j׏ԢlP^i1#LiEu˄Z:;& $̨ -Sy/NRruN ÒdBK/2: )JmTϓ_PE+ð$\ne)tߒFKl Q]rL.G-$ˋd)ӥ,0.i4""ȯD~+݈ :.nBE}Y(v\N5{SFޤ&q6.Yjʺy㍷߸<5n|Tߨ[DY}o]_9' V9cT yP4=O%5iEQ-\,/'g_E]eWpd ֭Q>ϧkv^W3kq1/^")fCS֖BߏE)!l%eGO!6{)] ah98O{1 KyAJ/><Jr/G' DYTEiŽ%Q-K>3^`aCð$Nq2sԊDt|(ʢԢ^@@+DS1}g,EaI2??{:EZeX`EQ:zvS3 Òd _e^ѮdJkyT,*X[ru[ԗZ? !`VJN=T1PJY)3k-p#7Vc7Fǵ`VJ',c֪Y) f}hv5OIJi0+ m`m F wYR>m~Xg3+W9 :NٹKŬURJ{Y9KCk0+l_X<1[-ݶaR/RJ{_;ڑp\Ji0+`/o&u`^84Ri}e~ykޱߧ`VJK= e9Ji0+P/xbP6ᆵJi0+j5ޚ*fcz& x<C;hTJY)l_`qORJY)u/T̳s0}(k0+K~ŎpχY\S^Ji0+rslAfC{< Rj~xx_;l4RB%=e?3{[0+4RPPۤwZ1+k1"&Dݛfi7imN-ۘ6&$b Xa4`VJݛ.M/y۟ ava^vM_yWX"m9G]1++3l_uRJ)`VJC >fRJ)4RZ0+RJ) fl_`VJ)ROfJi0+4RJVJY)RJ)ze0`VJD!`VJ)R* FJi0+vtVJi0+vY~)RJ) fTU&+4RJ)o<~Ji0+wJi0+4RJdKlRj'5`VJ)R꿫RJ)`VJ)4RJ)G:RJY)L6Ji0+vpVJi0+RJY)RJ2RJ)`VJUVJi0+RJY)`VJ|t%RJ+RJ)`VJYf4RJ) f=RJY)RJ{jVJY)3&RJ)`VJRJY)&RJ)RJ) e4R;i6k:+RJ) fRJY)al4RJ)3%VJY)RJrVJi0+4RJ)Rj笒RJY)uRJ)`VJٲY)RJ) fRJY)al4R;)KRJ)4RǬRjKgRj5`VJi`VJ˲`VJTɬ`VJ)RJ)l4R;W2+4RJ)/ g`VJ4`VJ\RJ\VJi0+4RJ{dL@)JzS5_2GiŬߤ^ qh\WXiŬR!%mAD\x*W*jcs5sH9IqDlA 8 /xYZ9+ fԟ>p9,kH9Cj|wޭN2@Ҏxe~Oo( /KL"&9K; U\ʼ2Qg9Qg:.,x3B -Hs` Rp#{a>71.#qy)>Rx>/#9 ;?Nwrsft[iŬV#"B6p- j>Ucg^\;Vc 6/mnC9(#BtЫe})\FZ.Ǜ@;) fԃ+ɜy9I` <8"UH}G-62@2bC)yNei a1>$e:܀A!1"kB>˩_]Zi0++wxvlc@\0ēF@s2z lyld\9PY uzc NB[[OQZD6 NJa kD`:vPԃ0>60x'%*y_U(n 17"nXqJY(SSVhB;` |t1/)}N0a&o`VJ={{ XIMD&}9qiEEyVpŷߔ =☤]U$?#:HUw :p!_3J7aŋ'wjEZbobIs|uJx꘾tRJ=xs[G/ bS}ߨis?Ϝ0Zcq-<=7‹PBTU++ 7ݖ܉=]J1O骐.}ޝ>'-&߃WƢiaH9D6Rdy/#A=fokՈ <-7~Yyj^lWDcT^HYfY ;e#[d[Gh (Fp󏑕pV3Y]t~PB2a : xn2>gamf}rטn5}Gz#:S+c|LHHSg0;\ej=ac-8_tz cИ g7[ (&/ ^~VJ=PB%_D;hve ?A Xr͍2q>f! ($ڶ$BIyZ 2x,/dq}x|uK΂eX! @Gբ䖵X[=E4h4XNMK9b߃do V"u> ?vRJ/}?Vm`>39iHӜ ` ^<}oBC3_xTfcЦ&x[{FL96 )Ea6{;7n^s%#1KJ^ XEJJWQEk,|+/1+v' ( RsݕGzP|eQ2g )^u/ 3n`i6-]io<#y?;  1Ǿ }oĦ[M|&7UXn O5W7{7Oa߳z-^{ w;~ۯȽV9Ң -r<#+r"'+r](bVjy"? EC'r5De^bIBGc< H6Vyܶ;yO\L &A ĆubjA80 :Dim?2Aӎ6 νCLyﱯd3Lg0 ?]9,#C[vk})r׾PMGㄵ3:_Qh%:¦㤧-#M.-^ɢpLABJwϚRS@ `Z/v~N 0[Ȅ_\aJVHG oEYeο]|cay=wA @7϶RjE.wg~e۱A,_m(=`o>4V-Z¢1"`7^߿ͥad [<4 BCBU#h;ʴ$1 .plnz3Γ3V5bH87a:"bA'yOg1YE[4݀\U5R9dRCoWw'l{/YC}2px_~1#<b!jlߊ l]?ML0zҙ^OH/H$5 {t7Mo1P%N Sd3ioX.Ƿ`*9hi BK36ű< z=^7?dlbF瑧}8jRj'+{>R[ 񙇬c?m@@HYYs0F'"ɽ_vm 1\o݈+e^imP/jΑsOrG |铧aWAbS=:'nBBaLo$LjtQڟ\?oZKZDT͘/9./9[X-&Nq>䃌eh0+_Ȓ/=EʂmИ6 s&ǽp6$5$ gz.=bdGͩBed\(SN\>61ɠm!EZumL,X0YnokX[1j gP> (|P0eDI@ZaPuq:W҇7wJrXlOJjUK7{T١D"0}s_1p o,& ` x^#F*{ګO<5,viъՅOps8q[nڛs~ϯLw<9-G\O\Kh#v UͿqpΓ^NX[#%!A` ˅;K^=eQiKQ6AćS^qR5R;S847P93.e\P`,";rҷ B k 2u>Nğ\>~WZ^ o[^-lĥƉ]& 4-)y( XS`moS'椳ϡs.=fޛWWO4%EQ<Ԛq5-0_u4ZcdY<afj7DY fNλP]qD-fn:3sHPɛ7^\p Lmvx[_v;??V51[˓ RH= s}}^c"CHY4!AR(ݽsy͉/")=iYʔ}?)%Y䪹GQf}0 Pd}"HAK$ )< s|gsW6 .&d ԩw|\7o`? 3+ j:ܯ6-"}6&ΐ$~(Dj `yxJ$2y2o2FFP7e NgNi_y ,5"CTS;vяYK6^uMGrv V3{(iV{Ex.t%iQ`3G% flMcF񥧽3o2ؽZ47ϸWQ:ǠȊޠcAHj?VW5/zlWWFDs>1w{"#R-3PCڅep`VJݻs4y?|^,\Nf~|Ev!/ s2sƚk1o=0@ |QoFT:3Oeg "01 {E\T2Wŀ"w EÕ::M0d8$$t;z?epfNc پ/6a\\tLC=Ⅴن 4y&&?8塼{QID3w{ ձ PUU3`m$/>`4R'=)y>VoR2n+|Oq؋vf#D$q_ŷ|etPw+Np <5-`hͼv`O a\Xw%- 316As2' bD^":iQ⹸ J)pkc8iN^uSE #(Kl#A@&մs0qP+W eቢ*2_~Zc̶`wnR^.wa eYzqkzQOĹ.cKNa!14 O\viƈLnDH]NRb1ԣ&XQ{f)|t IDATa07Ļ/oq~B/ۈ5!>0uDǾy)-$p54B.%(}R틦?^~(yeA?Ӯ5 j2eN/m L S<`jGչa7}JZ! łOcFY)@ i'hH:vX8.XG`[L6), B\IVAOVƻpu_gMͰFK"_Ȥq9$OZyc 6)9oIRQFI.)}0SҞ3؉\c\M*bx"Pkլ쐗mZ[`L wʪLC|dU]͢b~Y/'MV)70dOǎR|.{夒Ӳ B,BZpO`&4 cmcΓA~ROZ℩wX"9^zX"!g/ fԽ[4*{rr-'"Fza=/ۋQH`-^ڰOLo@w).}OR LZx.7{37Xˆ ǢNb7Ck1&u"u 'd'. {׹ެ!r=+)(JVOޮ?[ 0T,_`OU@2[29ROΒ- fgM$o/x?,bCK 8sG l_t};S&_P`VJ݇ }^xE =?އa(fA\V4+cE r;*v[Pӏil8W'50p 7<"65qއ3~N|u]D,k yױ,Z׼m㲜2M)z}Bcl0 TNZ@ R)x?K=ϒSN2ù+?>^|^eި! DX>S 8?-?5B}:3<)?ʹ%1չLCyz T_P/^&L~Z4WArm19ct-3}\^t3^Ig*^o8|K]w[e0?DG_8Gyz}G48x홴Zm\ZPt>yO:3C:!O΀Lz]~KW3eK2ħIA D|p꣜1r~GJ2uDxǏefǷۿq=DE֊Y)+1OoLm44G |q! H숩p5qjtE~ާ|q< a񬚯"SJF*b""3=sC'7A%)^ k 8/%*9s }k' [5laǎ(h@3(Y窭aS~3G#~11Z9=Ww;q.$̈́A*LwF"4#!ͧL6` zXoc=%󩵫mFXVJ{r[`jOsFʺ!+ !eA BpE5j#7&6a{a_!e IHHj$XZ p8Ho/tB-$% ":2E'v 6 e@4ҨeY!tw!i_xbfk0aT}T A)RҎcm ^aUs_9# ?6{YCH`VJ;|Ksos%)KdFN>`= ^hhqʼe6yY"[?+AULM4(I|XGkz8R#_ۉ@退/<4FsP"h{։BNHegLҜx4QA4Rǥ.s()1/=;xJ$`I)@2p Mp! "t&=֬P&A- ,"ٯA"ɻ)$PH{xtͮ*WJTl`{칄pdiA :(.aY{^<;nt$uh x$`Ds|l8vU _MQU6FH3qGfLiqL ӪZ E{ Js-é~30|}ه`%%@_p?Kp 5\].>m2׬wnj-PH`20 j/żٯ k6ԈˆZ$*#luRjWsS#識`>˜S&l5@/ exο[ĂbZ w.~Q~pFTc¡M1;\V'$5Ӣf0x擛L  E0eƿ9*#NpN(ʒB?ۧu5xxW×oexI'K.F9jX1 & ?=H3aˈ4<Lx͓y NȦr2 wtƵXEA/%IAni!aBh KbC/J]Oq4­7X} 1@a>eR sƖSUgtI% 1MJ(ȸ9H! "!SϺ>#*AVVFC[t0p92pZ ޳s`U4cDQ (Vkqs|q8BlckG$,>iϖi#C9 n|G|xy~o7-'\\#Z\gN)]IV4MF[#4ugeot dVuuNJ_+3F&yܤ& \$jutΜeqy FnL"6z' W]Z>ê:X ze %)~ds Qϼ1{ Bќς`p,3/;LhH0aւz̷>g |-t]F[#4GA7>G~i0+çՙц[ĜP#E`羈`BzK2gF8i(Op|%i#α_ㅫb^id.w[`Kڡ_4FĆ!cZ,B$\c1s h4V:GٔnY G?x9بQU1-1怫>Hi 8` s{/x?cu-CPM65i 2G"j/uhb@osαzÝF,xEk(mmԮw&{\qs<{ וX YhԠ֨٘8Lяߋg> 4-"zc.yV,`龻3"Z4zZҨl?[~^]]dVc^#6U%]zNJ'-*x!ϫYf|'iI siG}WX\DHwxuF do>nM,'_Fjmۂxp=8Z5^2$#6#$EelxἘ`NL0c&1c_\?~ eRzx-h" a]Z3-fcŒa;jv×%RPVC84B J [B Rz22SO,8|'#D61mg~ w"^w37I].EH L`yԋ-({h$  7d[jrIGE># @\e= !iҕŀHF&@ʶKR+J ǹ$la;0-5 bVjwJV-ݏ$Qg4 e fԽE<3I0jaۂM/oŋIJpO|2n@p/ k6!CI;#~coҖ\-'FX yEWN9xx8ɝ# pX31ysxNJe-4M K>}ӰA)_|$.~_v,gN#AcwYwpaY5/` h8H1Q0(y,'%v&f:DMV$pHš)7f`_y߶7Sppv.##B&j@k0+v&="y#$i^qoN]̉W̏aTim fEk] {02c,-,W>x^LoBJG:ѧ6Ҡ^6,ohyՀo#cS1JKGhՙAk syƫzNn$UgcMp W=1P!?ӿsۮcc,tZk*wIBAFG%(bG@0+ucQGD13""ɖ,tꪓvX>] 39q=O}tթ:k}z}CS÷mMcaP |JwƳ|d\ȆZ# *BP"LHX0¡]SU4%'0[ ̺U:\Ϣ 㺆b8Uߐp^`8|8 '|_䭇`$F$}Q*9_l{ HcޜƣS9_59K'fI5ҡy=nlc 8V|+7/[DA,E'9{<1 ctE(U=)oB~cUn! bs,|1!ˆ! ZP v>Rk\C8 ^0vyHVklMiVϬ$RUwäehT,.hj2 {܄Fp8E&!^yUx| H K)$լ?U<'y@ 0Z>7b%xhW"&| [gEw:*E v(yMXya9(a%HW}&ߟ|',w2fʊ3T#g BTHEΧ]ۏ+J9[>ІR[:YIqyYM3tgȧ2nvFX@(ҹjq$"gYWvћV9 d@&Md泈ֆ1Ⱦ~ EfڤL$ cN} .•kqJk8J6c+zYl(d=[-W嚢I I"t`[y<  )',޲<M a'! U%¡E{$ BIܡ'iS}AV8f֯_{sc?ɲ]4*ND\s Nށ6Av*' C@=0n^:b$+ %14=u71J}F˥wLqhĆH0J@*ARy0m_/ޞ4'BP9g  __~~mw ET lsXKB˘=~e#}1 ՂE,k {o@;$Uo6#E#+ޣ HD`^ye&nR )AU xvϾgo̯nmȵAII %9zn8iJtp(4V`vVaqX,[HyF$*@ nqB!v|;!y3uz!^x|hnm:E~jP!0?沇UB鹜ŧ锞-C֘X( jȻi(N{8?Xxr xμ=ٝ$^4,>VRW8mL,g%S1oq({&I&~QύUMgrEzZ9[SA=@` _\a1TѤڧ|WQߑ=o?VoTBL"IP=3)g̣)؜~ U@ Ѻ6P*^V7q J9lEYpjz zu?AqYPbֽh hχ߱7^ ?qТ] SjaLĜ ɭ›˴lތMxj(7s[vnSzc((%s^3xj{Z^q?< ?Mߟ3C8UUu\v'Wr|OwhɊqZ pKyUcd3:$nJ6}l>i1o6x wkWpq=ۏ$5AW?yK50SX~j=콼t#X6+FR-)|?|ѡ<9զ0^A#6ulU9blt@$_BPBw Bo_6Hd7DP D8$"7[ IDATX{}V03PD*COH!9fI$zxޣd[Zֶ-f,%AO;lScʂ"1AXyf[J!7{`0 ை~X밹#PgMF~Ʃ_УD^:!8Ue?pd*9W3Fл +ѦGiZ)UћnpZ>սptaYqZeTB(Nw\0elFL#rmy[_JV |Y<3>>uwpR"𾇧 +K~xx]MEր D^ G*Z.KO[yMYbʒ!" n"GU9g^P)8죔GkrhH73 !4 nzm KPPzQL)s1(Uyy||-2xWĬxqC9]mtML% B""GXx^6Yu  }K/e?ž۞&jQ=)a 0 HʇlNYƑe"_T1y}a""*0 ce5UGɀWbvDk)mIt>)}wYUd#aEް3;g ~uv(Idj*{l@ 0%RL'+gVL ʞ%/4q5 $p1zPcrf=54Fh$Mi!$yf U㗋.␵1['SK qP;f{03Mګ+2zoh?(YdU?;QF\)tA=͞/2<)Uۗqi{ kܪh%#fϺ^ QTsg6:1AnkM{_E]]1kU i]Vy{CC֨qޱL򥥧XJeN/~ ?=ȈqRCH.rnAE`wj 0`ztGJfqJ|115Fu 6(tN6D=iHqǓ,H _m,;M A9LwQ UQ͹u1u F0"<=X?\{V Qk yg,?W(OZuAU kҀhJ*ʼf׏RCm_;7ўA%Eqo׮Ʊ?|Tk| n#B]Ŗ=JW5ADD*7"ڮCJz>.j4d %$== {xg>\{\]K)?Ub`bW]SATql vGYqwgQ2Ĕ U^+Χn{/}/K/8޸ndya4;l Zq'8j̊:!i*\^Fڨ4RUYƻNqp;]IG~QHSXyl IPZ[u#rƧ"?u]SC< d(TIBʼnc-x2 џ15j HEH,*柾Tv>`gÔyO_jjfHXZ`a:B=1Ihk_J-q|Ec@\ nQ& GMjJ*R} N󝭿+:Ǘ262DeA\1/:$ˆޯ;v.b~݆f,fPH+d$!VgoF]YƵ_ +HZo)Q,^} CMpSR u֞,2EmXamZ_0A *|rR?m|QT{E/} Qޓ# F ZU]< 8 P)B^^rDmL~:-b"EdՒۗj˩)(f$+g,;~x@10(msiIU2j z >'KVA"͸aEҤ6A:>EbCz3ڂ)h.dUlspi֕dn@zY"J3|lWYY. qQ^:owǽXמ%!Ɠi7'꘮wl? JH78RWx;55,Hsr͆ *R Cs~FVv, ?^my*}ܞۏI}$[Jȴ1I.`t>ټ?+46}JS 8w ׇ7w~ H3Oy2kɜpYzz\:K]k7׀`AQ.o(|Iퟑ2@VRm(dRꚂq p:V[q<,/~-ӥ;tMlNfl*[̖mwD՞6%C;okM;ܷ&^#~s5heSni厍=+^iiviq /׏$-%(||" +W؁^QGlb3կ_q&d.:F#<OeC:?uOeʵޔ8Sʬ=] ?8"z&+ EiNV=w)D~`@quf)Z?fAdձ?瑟Min~%cXKٱkǠkFq_P. zj=)MEUicD2ԂK!j)V;tP%Eu51W՝irylɊ| %.]|ԆA:$ݬC)[N2\|Yz: 핎Na XyJ[̐!fXt2$ >1sǒSvF A~^Bzίl󾻡s wܨm7%^bTURYNVRK %{6nC31 ^ 6!TսvYX DcQ[n_}RJ)${U~ 2 B˭#.HYDݿ䴲~mT~EAE(YEXF}n/( j2CiN! %%&>zJh';6SY?z\# p`aL0io?thPÁiD8xy"PzO[7KYhI:m qɞ9eb -!RUEhV{=t擎F䄏u@ wy7jY;l A eA%DؗOcR3EUWVV 3FII,%A/_b`ۇbxAݾ\#l&:  e}#b hum[TƑVl1T)i]:6N匎FK PX[IYZATC!p6px)3>g  3 >hPNhyϑquotufmqXyFu]\odrFi8=H}'tٙ)( |ރ^ "PMST?KtcCH93^9yx*]wGFcW|1Nm鞨 ƾ~W&_{D :;?uJ?wYU|+I珡Z&QvdKj%)Ѣ`p G8#OJi2W ͪ8VH:|;CqU*o nk9_eXʚ=yFҀzliD )ЎǦ zۍ\[֮QD=Rz2oѝY c8Sǔ9:/dMj) E;_/_/zE@BSy ͷ_`g d!NU.Ș|Eͅk.7κlH!Ơ˜^ uvgui1B ,~F2W ;i qRF) "'y@ 0;0L\82Lb!&x|Vbs jE:T̽³8c9hs,[z0ki P"{%/C\ziw`8mO01\ kkEn8MLw)LAsIF 2'9}Lxٞ8casd` 7}d/Z@4v4C#Rc9W}(V ,X4BjiEtV,&JDA!ޅfW%(F3zijTYbX zz-Y|!"UuЯn17\̵S*YRN##Y-܁@ր ~făjqXzX' k$$q? 10[xWK>5bQ#Ij|윫s8В=ޙ ^ɡ{1!| # (d>>k&oS>Bjg_đ89;oaHFPH^rYox-E?F&4:J*2] 1T׹D/ɥ!%BJ0%ߺZd"ā$ӎ7v̟Ԧ,-eh6C< ' ꌲ0"J[Gzϱio!wz7^ZR A3yCovfq 8S˂i5'9T `Ӣ֩55O~s첀,ç-GC v?rD}*G"(bEKNa ڥ31E?cas;6a,XC0!Ue-N8SཫZlLm2x"mn>4x VldB)\1 (Z %=4=J9KnOƢ-tخG[1n i$OaP4" ՖsP-B!ΙLwX㌛E2Vɱѝ+1YI팲jk_~b<67MY_|O<=V^zO7MOoX[8~u! 4dUث8) 1O@A<ͫ?'FP s!cLV:⌮X*L*^!Baun/VԶ'~ZfȀ$CiT!x2DEz:&KS[ R`Z$RG[Ga_oW`pSKbdWp/g JK9!VwWJU֡# EB:E^UV")8-*v ݮa /_]$w'C̹kp._:Z$x`f&6A3B!=NYz%xl3q &$#A F"h_qZ'|(B!n>eaS2 ʜ 9e—b"_--WRd&'!BBbb£1$פA< |TyqX*~y]sL6 bZbB(0AJ/Zg5d S8B(sKt! GTцU1d@=/f f]8y GI|RÎ%%JV>BTK_"5?ݔ3{`Ⱦ_yN{t2M2;FYh! 1{i)rBDs A_T]ᗟ[NWWmSv6-:C AZCk'U )a "sx^(fr5zhF ۠J\-l,xƑ`|l$$&l3t{ux/GeH1m 4YK%iTP`(] N;0Fؓ#ݟ:#5Ԓ7Q IDATbSo#=~<AEAVU.KG+켛9gd4TB]IYU3]vBݕnZJ81ʳ~o('+9Hgygͨ.80oP"!xüt'oB \h_^;@"U'R\@OpFAʱ_{>yWg!%MR_H6xDE` vӗlj%Hؒxr^5/ۗN"HHԈ8L1sۓ%EkCW'!Ap)*Hyୟ!硂!-+˞Kopg2̣p93$Xcic.xwrYwpR0CV ]Pzex\1 @o|s/]g5آ LBO4{ YZkZ$ a-hyQ#% O()Y&10?_q8(&R!Aͦ?y XB|^ЖpNHH Av*iq%C,IV$rgcƪI:5LM~l/?Z7کl= P ̴2ͭjf uQzA ,[$!h'\LT 6n>yM]ꍄ$918PuLwHԷz*>7?C4S|#Rao6|_/A*c"ʀ83Å[Ύ}hbyƶC#ᦏdyvJ>͓wU[kq:N$DI*JPAZ0V 4b (ҢPDQQɂd89ԩZڧ7s}=|=S&\{'}gE6PZ(b 7yZ9$4]s >K15K>:BQE(<$u%Njn#Ѻ@e h(X*RuU,x8o80hbb | 7/>Ͽ#T'L05$0\z gz2E*T &.W'Fex σ t4G1@30[2$!DټN +x Ǧ%Njal!rM'x>~˦9v! j !Axn'tZ'?0=%8KFJ'C`{x=GcƚNehISqg^A~ HEx< tJtt@4|ϯJӭm6h,SCQLr.~ zXۊѦ4}ח}NSn;ALOT&x4IADy-(Ip/XuɈ0TDW,[)9+k Y>&S3bGbP,#:yĔUgݰ9s$1=*-ѐ )WytQ6d[gS&kfJ(5W֊Ʋ1t3C7)6wpwgxҷ^O zGthJ.}JU =zݜ,ͱ CxNٹ7LSCښfW+˛N\sOwi*FZF }_HS0>UxWDi6u̒ NY⩬ i|1({y!y{y+ RjMSiR&9Ëzaܝ$ F푠)̋k( ZϘi#:#$&ob[U5ݍp )1(fG;hj%-cx>tU3^11 [{jW;Nz9͸Q߿v8D|btQ(HSc~tS_*-9k#)>{ڋG9ǿ%ENfG2ShR1#lq6 WUzW^1Q)5E*,`Qx0grXP:1IAlL)Mݤm:8q|/]^3ʊ^KfD&uhkB1X6w([CT}4Ў-#(;0OksNa?N() 蹂S׹V8K1m$J(>?**p~gZL,rΕ2*0ʡ|h/ 0/B-][@ge<ҭYwGYyNj HMԤ0[ec}X8'[h1J 1D%dcv /}UsDe0=ki%m&EQ5oÈcco=u噛4+9x I9Ϧ5_y&]1Sz]/?͇yɗNKz3sCլ;SLh8un"~h ;\V+-jE%*H6_k\QA BqMRUL{ŷvg"HmMPYZŀ(eyANio:)O@qts?^-Y!Uw'*Qi\X>ʲ|$Vu%a򢓿}R:f{r=EjJ顆wىb+j[ڕHB?q9R9^9ī``<~y!'/}f)O(2/xƠ"5{>+iާ0:v@ije(;=Ωصw+5Ы*' ;q;,Ib:C<2@Yg3]ЖLZ \YŎ6 hZ4 ٟ3TT"6e^I>MogzsɒϾ8c8FۿǴ~, |LΒFX`KP7d)2߁IߟR&)Y|'"R['߯冝of"HSʡ*:6|IzuRǴ4zQZ!FRxq}rZiBJDF)Jq3}\;GI[*P ż̷OLı}m9h{1i#tK$um)˂4c^;/nQ_qWᄋ2:Q3L%;Q ^䇗pw{IՓj6ZEc.GP_X+e}0$9~~էvˉ(F'xS;)" g0/Bo?''+HURϴ_\<^Igh' wK~'9h)ESiNէcV}vGd{-bD8 !8m{4qώӦDC*VDLJ{V/A/7%5-`O콉Cڂ6[*Wҷ=(Kڠ۬4|5g~Ph$m")3͎H$˲6&A^w~'>*@/`V㨬ԈB9o,yS^DoS5[Y-f<i2»^|!UQjbQ58SA ߳Ԗz4 YǗ5>?(=L@s9˗`]=|T$du5Z5Vh6A捛xlmU+}2HN5~@oUѰUQu<7r.oKqTtF`gqxNrX{8r#|cEJMՔmF7bhgJ8|LYX)PN$JO?ZitA{XKXƾma%|GXA}(da(bTkPR~ѵ$'L$鰁,=w&p" =WJWB5asM rɯXAgIJވPC-|ޯ&~碖G306RsZ:@Va2=22WXQ:xźIȘ+(Oٝ};T㬅hHSF>wcFB@%P_uvɟOys *x;mZt&(#RW?={B#'J8]P঺!p@$垻C88GcJMov.[ǹ*(JP(ڠaWv09h|GF d<[S jZӓ^]#f2y͛ۛC?lϢ5}peϹ[UUA5xqADHV?轇 ~Go[y!.V@ygdQX˾{M o|L\f<~wo)y A3 B")#g?>t'd ([o;\ǻP& o+863^1Yl< UL[诞@Dpgh䭜FǦ/g(TJbѱPV~8D ) i)E}7g3jӊ&,"шrGӊFɣ 8{E6//~n@YnN̢)!epHNw,~,$uU;מLr@YuATU(I:tY&̺1G7g<*r'OBKJ}'eP EyqJ%Jm@*"WL;Q]E* 4yd @  cnĹbO#Q bð(s;PȽrbV,' 5ٗ} cD=yv<9zOqtAVyW? ճ&^/0e-{y%cMF:1Yj=s@r/;"L+{m:02D3j8TQ{GËgvFDznGtKVȓzɱwk>`9K2_-Oa#V4->Ŧ%}X? j76sOXqRP/a=~A"~XgJ3wʩwXZ3DB=W4RNJGxeͧ+IbA;w$qfgy+xI3CP`{]\I*_[bc<\'5)sn=i/ ;QfgT62@Q"Õ wKtV -4 B1r;]<_I5"Яz\݉tb K9StRvҊz䔟>ƕaxPMZm+ʪ M2DB5:5޻ W}BbZvfa<`cMe5\Utm/*)as.k֯DQD>Be=t7[IW-bA5TStjh|8E5P9!sK qYϗoҀ8MTgY$iK xx\ [:1{[ W('6AB ҂{s_a @9ʂe="6ȰvuR֕5^1F0_E2*(*-%>C4$@d*-hEA9jnW A IoT_3YNP-dMg(O9\ >d(I:)*VOieMt'q'KqFdAI. rv~ ~((6 Jv-lË `*yάٰ$N,[JܟeGۻԮbn0Cހs5jM+g`^zPpJ6j *#0{Z_}'|MNhc'\Kn~l]-G^GLM(n]X#b`~UU5YRԸaxIɤ4"qʳd H!V:,.*jq[Xb(YDRq&)=E鑁i0mr yF%)BZ)؁l/E阻\=C-ɣ&A5pw}ֳQJᬥ rCX=~ ?05|궯}^CR'>ղ[j_7>s.rɈUBa4vq݀zg(4NB< (. \I;_D3#N[`R<5|EƷ_"?(.vX[9iG U]/v`4ʄj6okI[Vc~܍8GUs~z['Jz>#6E{ss<8sthK v@űf걅Ck9sn:C& ia)@i|xNhw^>}N&(E Y ew#qATxC@jPR 0(kI14iE5Z+t%#[Bz+z̠B @%/? ekX,ѝbts|a]sCk* ]Q{uhn\4Ƞe%b+( ۂ :uvTB*k)]A -(BУDU0Yw01Q23م8[H i_ P&FhV?|ldF!pYcs9@VAkaSNHL' Yt)w9>$!K>~÷p  ]k4vNCC7z$IB&|{i?UqN((ky- Sp T NT+oV,i8Iͻ)0:!ﭿgSt7VQ2(e4G'#D ee h O@0kzeFv H$(Sᰫ ċP)hW~(=H-ԥ -l͏\_YCHjBbA}ܯO{*/޿>>w捌jedqVź*@lf q8cmLhB]039=4,lo# |41|,nHcsZ<|a(k% tujE0X_͓D`vmL l t 3Op`rc##8?èhJE :M+ЂDVWҺrGt 1RTl3vV-ZeVfg:=x',+R AW%l-gaG&6жBQE_{NJ(=`5yyE87PY#9afrރYp?,N[4DGX ,܊G{7<篢ްv}m*(}MsʹI|bzf4K bbTX&[U f'xE1f ?7`lP^&$ =E!5n|aZMM31.nuMxoGu:Didy* Hŋih6eH&B;?_rsmpǠ2e'Q5>Y8y!Gcvg1-Q*B'>+H>3P5cwHlwۑQJ m z&'dBؤl]M }}2qex`ܲ,l-"-8azr~QqI\/Oёڳ1 =~ʕH#ºZѹ4G#ώ_y!c|Ƙ&Zom7`eHYn`kSo(]dA79<HǣJQ(#0iߣ҅ .1JU#T,lCEyu1M=\1q*\{ɉi׭44 ZD#|c6Ǵ5 'n`-Qk+t)LCzK2JAW rIq I q6`N 3(#,I<^^z]%teBP̈œY;@flJl]hu-F ,Š`g@{ x"̻cŪd誋0fK8gQB=ܸ^g QR=]ϽXϾ> |hjTD:%0;@,3`}9G$ʐTb1`0[gKLtC_eq33:G.$ì'VeRy#)i&$wc,Y\U!Ѱ>\9|۾WjLaSHl`Z|_ }Tv7sXDaLZ_͒F9 ՟@SF G%399Y;W|{ΡfY@9o(}AEcP+5̕­Oz{%w NRNэ31gKueGA$e!Yw",*^,qVqTO9ŭT~d @4g]1K#,Oj 31zQTc$I}D)LHdF:071CdMTeL# ; f-exYLD b/I ΣÖ#Xaff򼽟\^3Y10`m-"n^Wʥ9`h;9mat%FCVפ!a@J0.SvdYB'drԎWHd(@pdqsci˷G\é~hycaV{F\cXayZ~M{Tt]7 3IPEpUzV|Y)Ye 8<},Q$x;NywU'@a^r& JtFrIy/1|r, -^[՗`ˠiP R8akb|DFزZ<}con9p@2[i^3wm8 T,n~6yp gKq ;axo) u[R O8FXK)*Fm 2Kkvm?ԱK}/V( i1d Εude4Z6 +((8<pc2YzW'!7-<5G8S k Ҹ^j96x/jsw>vH;*"%igLhbVN>wd'pކv_᱅C6BխFGޱJhhF;?)Kc'à[$Ib=|,qEGoc|ﯱ/E;ģ[Rä5MaNtf7+W|=;@bY19t˼#^V&k&|+jHx?7|||z!Nppx_g$LL6=+ħvoYx&$ 0Bߙm5XmNqwv,@{ls8k43e)v:dgz0g ,1E! ִ;˩Y IDATj);tyol2IcVJ"RsSKU{ Ӝu_Pud >Tu;OE ^:t WN}2ı2ǖ(Bo<3|- ?[Y-3vIa;"VfŎ#4)v9W~ lQ/fuSa.hN:/r'g 1lا-#b!ʓ`9k4 r̚ kWY3T/O)r? u[{ND{@H017X{F֤"HALhQ{ GUV$c|KIhdgA9ўn˘L"[9VMݬEu+Eɗn o 3۸VŻYʲ`$_2(JL0S8IQܽzŎ(eH.v߁4=#! ^CcB&ѓuFԿo%BD =ȧ&F)*ɐqJlYrї? r$b(f^ĸpևE ,ztq[O._&=CB՜!?q럇. NJ$Oydc3a=HYmMK,{ZC4CH)֦- AwK_IJ'<<+?c &&Tsc )f[Q xyq." |w%Uoa?8)`;$o':=Ŋ1:<J%9TᅤaNY9nn?7"_Y;}QqLf4K ],_7ɚ* DǓ5cudhKӝ \>O0g|lV$b$4#5c%>~V1Kf) lԧir.c_n7虧⒌X|W_N%I0-Jmgl8IA)Cu;/nxɾ»4Ha! $i{C桜.3iEd5q?܍An3uӹss6naq; mRP9s̸rS[yt-F`@kC]vAPZ$ km'}s3U%>O$mK4t?JgPgFyK㺺p=s`7Ѩ$';`[D0w`}lz%#;*!q5Əf[K>5Qi;I,y-X 8G_h"c!y&Gxbq_5ˣȁO<% E:"E^1y'_?tMsh/PY_c7SQ}f~Rq87D#wLG?C'kyP?魻2ҁQK[d{-EH t҃TbgO59;4Et(cshf*uuysFQA+o4,V\y'xugm -H{{V7gIG)\qᝧЛ͙1 "1tIF YuP$&EYO,Fqs];i1Xu3`-?(.%U]ыtCʒ&5xu2x9jeFQERLrwlځdh. فFQ0RF:Qa+xd?b͋pJcFW$1*!4bW׬YM|#A/=G^zQFEHB;|33#4g-QZa=?-BPPHEJ✧Ms%ղ8&dIh}Ku)-3Ӥ팸Ki.|ȑ5#A5gqd3~/ '[ :&G?{4 ?ϋېo^X|;w \zC~ 1mb ht>>-mHnӵ>/8ޓM!(=)Zcx\o?lEs>E+"Xm"h JDG 3_1it"DHgs ^ \tDs\-g0G%T;a .t|$ˆg;.o'Z38AII5yQL3iv!λaēPU&z~ͱi>g Hen$g!$<[J Wyu8%ݙ|y,N)W'r&F4BżPJι⷇WjF$i$Wjwଠ"Hkӛt-R6^nˊWsoeBMl9N: "cP86ߓ=N4bH#+daD oϸŐ?sJSU[dz 1֔eĽw{8 uP*8{ &ٯ=Taj˜hG5cym3uз?H{Z`Cbz.z["nHSpx=8 [ҟPlS^E+yG&' !"T5Z, 2+p`& v?84 z_# oui129_ɮwI_-4I_0ŷەM"g%ޅJTxġo+w|i;={ޓx !Mx8}l9b*S[$Z_bzU}:#Vf'0~L_l4jC ҃xf %`$B A-G;ДPƶrr-Xdb~`( gb*b{. J[v`{,5_sC5|Xc3DF3T6Rkݼ6U VB@~c+5 g5,HC.s]6bF> z=0M0qQT9eSV &cL$ G:G#MC^Xq~ͯ?:"ec)qa*W#?/؀ȉ TIhyMQ3"Q*3{-*H)m#a,ҔPA"f+詌bE+z%߸(g-[J[0UM YVv jM1wǿw`oV$[#c~=a8k]qIAHR`?ӎt)-*$mxÌdSn12& $B`D1+DhQ㵳I]>cSV7p ]P'x*}1{ fz$w`c"K$6wɽQBC{b;9xA]$j ̀^6?thGD 8홽w~y;09NGiO^p{1r~0>=M+zy@9 P*<6r9ZMT]hgqDi,96 qBU: (2w(qeNUo3$9[UiХavc㻖r>/br2cPktP:!g녀D^sȎ5]\lm- ^×ݏq.l4ee4gdl'bqx&CiL#dY x©ǁ7x,BP/dxu$s,["g_c .MX=,x;}q֣Vaf3Go"xv'ҎZqY{T)*8.C͠ggqe([,~r yp٥_`/l{s&5r~95^Isvg,V 먺9G \uO{^-`ʏS;vw8[θ0+n2_=/8_]q!J$)i'aIZ605 >8i™bYʙi>:mډKI]x%Sbqӹ5g La)KvLJU_S5W|j>7 bcc6f|.F6GPQM= l^[uui}|y~=ooЃ`-#O"دiK!dMCG)-^M&dz0c/6#mxJHi2,1#KeGPZ5 MyF|ep;ȑJ6!9Z.]9*#P@GC5ylٚ,R,?[X-}oxK !Ypuz0k;o'WNs&dHO_ƪ5mFeDDHiwAQ0( `nDưSKeI(]$?I!8{PIjqFE1l qE '=0aX?BS$m&Z{6r̵$kDDT3sS4&Ά(mɲ2H/9ֆ,S镎zJp]mQ3!ԼUBx /eЫe6'frpu-hƂHn;O]4C0)>sݍ=brI7r_ƃ$+ vHSDqv)˅6lcb~s'h :IH#5<:3x1_Ï(>8yLBSŻ]wvnJV97q&C5pD K.P6/ytх't]7#k"ތvu |(#{-8VQ )夗SF㮋9]Aeù&0D%|y% t^zYK]l~Ʈe~0`Y#MFb9#SǼ]W%ݜ뻏.Ȁkwڡ/_>UlU17Rkh2LStH~z,\](~w둴]Q=u5@7 ;ۧ\k撕ux?xPJs(jG5|k~9N[f\ ZbhD8ZuM{;0?W2+ty'Ջ[!pO[Jc% H"q燺s82cǠː0Pi:Q} 7OEY‡`>Ӯ?10~MD[0=;A%!t  ̏ mfشu=}sC[Iʨf.Tgyb(Ne>xDps_yM+M>5_-ؐEv[kz&AZXufO< * Ci ax Z@Q_z7/.1 )$ၮWez vj v*`d"[uU8-I8xMp*JŢ-Ў=/TQX4K\P1s8,}.;]9oҡ^MWݺ iL#.1RP[6_u[8gi\8NLa0X?z/ϹW_t!1B_PA8GU$ſ]QƬxfUԔP*ZwQTn?P9=Cܧ p&kҁ'#IB'JT6G3OM|6&8A)"Nceh-k26Gѐ43Ʉ>z3CjL6Ѣ"yKT]8o%dQ\M @`>-j{#.;CDp>8/zzZx42ұPQr lt;M]Qa#ev1Okzpuq8@5GmWry*WZL1U9@r+DRJ2UcC bDD  sS3C8[< :S͑R.:W43EƛF9NIRZޥ8csq(s-b:2#broyVsٹO0">x/=ڂ}akQ_ڴ꼣2zſ?J>!2Jrxoy9}z|ueh;kP:-AR SLm 9#K桺(D{Da)f8M9TrTkv N3UCrvqdy\hLQ4@IWwR9<ؖ(k:Cu܊ͷ栶f~ 'F[’&^yƎv&KLru 7m:.*]׃jAx0{W=|]H'0ތjb :O$#QO\1fg;9=Lks@ >g-|]Qiylwiנ܊ l*F^~?O߳шN E mJg<٩\s/ IUDKv@HȚxHq}_d-IWLL - Ɛ~d`-vv|F-}gܻVUWQI*7ƃ5Kna 1*faohh o4LM?<& jٷpsHlD5cxêє"E2$i'M6 4|hm<v`~bd#}a"WlM8׀ "<"n,J%'M8IH҈8c-SF?\^-*HG>EzrlE_YDܨ]- u7U]\Lߏ1pՅ`Ǝa-a @/ToG׭;o穻kg{JH7[ƹm}޳2_GGzfsa\򱯳 :vk!R Qq"rG={r#Y ʕE7ͬ<՗tP>p?]^5*$ ,.Oe N`B፼ײapny6guT䖨!ab}TnǬ?bkn؉xlDY vF5M ;Rhb8{ {C ۝`xkcƷvV$ `#e!ڲMGi]ߧz|>Xi~. %ΐq:zrog~~[ t߰hSAa熳_9T燧jd@lA]p5uo)UTD % /jՊ& %ᒥFDJǼ+h;~vT+,vź/Oq60u$Х ˺2qJqQXÚd8M) )k[N~t6yEӠΰ1?7gm[4mb^dzޑ7ng|sk!0e/il6[^ˡlxڲyٙ.fLn9.UKl9p_Ƞ* ؁^DZ|0, {=qBϞyqXQ|IW~,W=U?wLYX?G4j,0MM671e_^ ֻIgݍ,h Sl>-hMAfr~u` c_|jqfi-.nHB+zh4,R  ( M8d!$a~^Ї[Xmz;נ.c{` tgCK%'^Uɕ}xq>^I~Mx$O#I&>u, ԛػj/ .PC>A ~hdXyʩcyLds.-\`݇ XUAh0 jYo9Ͽc ݬ?$O`}2!q$/!RbUN7S\q9)q#׼xfLz(I^ɇZ>Ox}FgޗN= Vw9XCۀ\ױ&gl7#52;SH^@0iXM )IF|7 3[UXA㼧V[A#t2I'=&bEmtgDRv6`d{ x_ǿLvNrH꬛ނ6/wӳldY?%@^``Zxqk}SU}vXڲY\Vѓ|F)à_>'Vָ2jY "VL..ir@=foo v`rӋ^!X].NN9fd&g`2Xi]ԡCLd]ۢpʱ_ VCy>'z93,.j܀4-<\qugE\O3̓-A)K)J3|-B⤧¿ o*o.議eJBlY:NjXYO=I< fh"q>nNkјCo#p$J(Z%mϯȁ0D xB+!οܱϵK6yQ*|Iw󛿲OEkrk߼{[O0 붾 f;<*謏tz[p^7 >BR8">57)oC GB[IqĻFb-~Cվ*:xz-RfTaH# (K.^/EP@4aAlne4RdeL >;g`06֭#2-9qSJ7>w3ti1h&uڃ_]襵hlY4 PNwi[ayLg +g Kξ9EQ䴓X jaZ/{>Uq&'Z;ol4srcE>*3 ͡u^>:w$1]ҧAm!aة YZ4-E`̗sSUŸ%r( FG(a-Fi)95Ket$ Cv-D-mE.IbI9nRNWjLLU,hhZoW_e>vȋ)f*A-2~rѰ FZʷ>O,> CI{k>hl 0EUHEw1t?`6M!xB_&V"2RbZ΍\0cdX«{V1^ {\!kQH³yŠc=1^v]AMbO[h ??Mhc#>X,..%`.I9"*ʈ`阽`maӬNQG62+xoGYE=ycZK.`Z̜#F`qc~ز{UhÆ6rxx_kP1l~!2a|cb^StG"%5IZ`[˴*{'$Ie%Y2]k)5IvhIW(nkWlmDa PxX+ȅ,qLx5t{&vudcaeZU&#|5xg9`Ac&r}fɕ/GTcޛYu1Du2YFŵa5ѨnjS[tXQ *LͤL27 Qb1x4w;!^ȡ^Hy9ބ7!7Og)_}d.z QD=n柲,+תb 6L9 gʳB{.1>0GBI-A9\͇9ol~]dںһ7|u>IPgo23YNwG"~wlja - b$B1bf,P3ZQ C1kC;C#3(|kN:vӜ6ncg3VK8rS%@^ZK~gaX/\g İ˚N(hK J"{>Z,K*JekLWVC!5D# F(ɷF;1;t_- Gq<1##4ǚHOnfmAkGk zw,|<_%b1H̾m""hN 842 _˨Q5)IRkH"㑗&Y;;9wۊZ"V@-U[E_m6 DF!<,sLV9koq6XB ,vu!ZWD57K1(ؚQmu jM54T7"Y4"p<{ÐzaQ25* y"F|TǒMw% PPlYۦX 97l[c `^{! /bKNNЭ ь#J+/9\lk$I˂_2.6ͳ-6.޾CKk wUZcx9{av\܃]~-`HӁ6 "aP|j>*%: 9=i,>ZmYU!*^Rc\; vOxO=勜&FmBe_f%EVb q\_ _|SYC IDAT1esV1vdXGhlp./w&$Ϛ[dus9A<7aٵ5(jF]GHbR%/ :r>]N$4Uts辵m> #1xRHP%96q8Wm#>OM`s99Z0mu>%w?| Yw!ڰil3>Q1+Fo eж9"M1Qn1.N[%`iv3*CQÂ2@Wg  h _u4&KBaGˡe@7d-EZxB+z;w_x1nAVTc͹c/7rID8 8RqワḩQA$> ;fn8M{WG Kk Kuvy4\~w[`Rl>=oݟ*Lc#w<eI(I|@J7*"9MP0PY'?)jSDQIWƠxݑOf3ȓKZǮslk,R?FG#O97d396VZzpQ={<~#QQl=XB |kY󘨏F =a4:"Eii=Hǒ$d!9lFnzN>F0#CYۜ؋9#tn"90%,P֓>=•MP4X񼨒)Q^͑kxqTRI0ZpVv"JQfESk狼rmr ra VGF"A-rLSn\A$Q4$'imxp&.fF]F3(Q* T\M8i\L¹H3 y')º ^y I[VB@ ͭ\emUZ# iX n}oD,,G TRXȋ&ߩu=kC)'v Ig SP^ZKk J&{q|ptPH-& Z{(eFŖteA\x1Eh?4!:-ǾX j˖՘@cєdXRFW6_AO:P"J*ZkZզ֬3QO;e6:[ÀȀ:^^1*_d.[qŋy|j^`^IDQϏyTi;٘;wk̕)XG0C8f Bzt6B[CZ&\_6D1pyC?2/A\1yP%"C- [Pd׷38ໟOȷvxPc}KZH䐉#C\)cQ"{}셌g{;`gͷ#x[c0Vȸ"Dd}6w`<(rҴ,g~n/6`D&4Da3aj56,JliݑZL좼d]`=&=,!'W5kSFʏ0Uv][v;=v06ҔgF<}#men`Zn] |iڞSZG t2NY(ׯDy1*$e`n}`~Yo97#~c讛oLz~AJ:Ǘ5n}Z1i,F;I3Pl۱7= ͪa, GɡmO݇=n7Q MY IHǩ5IC(QEIV R8jnE+M6X656|?T` #b$;?C9W:[hhɧpU/5U 1ڇ|ѻޅŒf4[tZ,x + k%87cD赴{x'I{\ ;ǖ1#hq_KtŖ62\8Pe׈8׵l{וU|(52*JXL |hzZ_֐BNlSy.& Flc Vĵwg-xdsF;p5,gbDccZoi'2i=66 f0\KD$ B1薁yHY(KtC_c3U5[z&( <$6Viހ#ᤇߔMן/ۆ^Bnf: 䥵m]jpmb4zh|:.  Y]Y+6,ul,' =@zf⭳m (hNbGƜ,)/) wYµ؃0@Y:2R z㑓QYADĄhTe*x$^ &D=7qkE])(<][XR\xM/ ~I5?ՔѴ>L3wfЛx2a!Óu,k5v[u6ϮϾb"yD2{a0|Ϗ4w1a;"Ԙ=eV'39F"Oi1JA!|im! +'5c-v?ʆ kuu±{y{Rf8zҊx!HY$ Ä?=Bj?3ѱ_pj H ؁Am(-{p2IHw}cǒ+taj v K90c4 < kTPuv̷ݟ9`֤_Kk ׊d$2F Korc,Y{nÎ*k%2F:2.]k-Y~3Xx7trmM|pKy&dEUes>v~*4C9!lNv;Pg&D^NhU_=EiE/8w(3u*;?4`l' ċ dWxvPÖ9RHs(A!QEF]ws~sݞJDXUb@ !f%Wmٍ%~sml"'IF'A7Wuz-|}e-C3vU-7#F)93'(-5T(Oa m@آDU ?,^7!!lc'HnIs! A?^ѬSOjBaӇIMʘ?ƈ?Bo:l6:ݾ8BbJf1(3˻+`M/ovYx2e* 8R]Cx@vx؋|ia <g(˒+-H2(X'Az*鑯.Z )4%|/򖏝 cFOd$F+E/|bUr%iiW]}.=v..Y'Ҏ,}T䪇5A0(s{q{0kCfN3ZL@jQnޣ="֥XU.:`nLF '"'C@VV4o<( +st"3aiUqYԎ$ HQ O%VUUuvN7b9؁qnreo?{3B@8!PQp$%Y1}1 mID#Bd,ZXeT{JSTl=ˎ{G;N<CMilofxJAI. $0kw|/aK-Bt|̟e62Sկ8 ވX v6y'o!kpК5z^`|`y']ƕ=\|i#e'$0)PᕓkA.}av6I"S($\Yn 0ZY^K |s]x:^,JǪ!f UieucQ^ؓRXK ^H|U4ƐrrwH_I#K=?^}p K.E>Ƒc NՖ3_pWOqH魌v(wRNt7пR9FY!qkcU\}&-HY*^+){yxQjP2ǯE`j, ŠCoO -2U]ҙPfg45grE MiK<'|Tj]sӚ*s=OF{ #p{cmx &/֙x/|ycb$bb4$ Df9GXKܼZJƖ( +< RVWR&荎x&Diԫ;|9{^~~uVHb>C\ HL{U `κ[ R>}ƒk7`;J5"땩H+FkόpX)ho"@)ET⭭lΡ2~KxPTlF1Fa^D~y >d=s3l-[8d}8o. xBP],hm;fdYΩjK ѧfKm8[xi-U6!}r%D:X?.l>~k9tw簄; F3~Ҁz/LBA .ȍf['>? VF+r׍,jm[O;\O0_v-T`~SVl2/iq)xnLod^D$;$Be ,y|1XcS 8mDUd$W-[B\<,+(Ưw9doy)/xЛ& $'zax $HP-AT.ָJ>'R!(R9-HJZEO jy09i܁rzC0OB^N_~{"|g4Ra&}ZL=ᎏ_.-e!՘Hh&xv%hmlc!nF 1佂^NɾcEe,&p[fHaev ,zd<۶>/yѩZ3gOaz} zw'*htϸ: 'wձuK^Idf~~pT/%`Ⱥ/]j5"U'2udSSa"]ohP Gb10fx8 /Kl:R>_~Ǩbe*Zܒ>4zE8`j#ee9*iNg۹h+JhkH] AxWR \s5bH_">_`BV1 uezmz#+lQVR8NsC݂#²QN:v~OJv\g8m|G38}rUM#װ۷`ǎYlVrE DB@ aL6_^r>>a=ZK-H'0?GF+W"EѼ7@h }o(7~(ٰ5P4۶-f {^=QcyjqOԖ@qU˄&/Ĺo8_*r]z9#zXFYLuޝK>sWl54L[qi-AQ ҟ/0frkGw#M𓐟kC{qlFc1<!0h. A#2oS,VXWTIc?f[9YI3Eh4>>-x&xpfZ׬aի72q̳Z#iE a@ Ȳ[+ZVlyxս;yүF7YG˖*^t/?E6'j߬|#161W^2@ Z=E-nЦDm6SIfcKb_Bc*|ke*ͱư?[^6~_ɛ|qgE& &R2^*P$ТAZCI6VZet@>yrx=SIA)_oeum<Yah QZɈcsfnn_¾'> j&|MTlLL7Ŵluk !(cMe|QR䖁gй>.D$[REċu88atUXJ"%l&( x"Ej-)ՖB?A[FwaE"N-œ-"3ۅG!jae"bC'ƙ?q}x0ix'i4\i[rOyBpy`^jհ&j{SE6(˔IP5݊gדGezY q\eJ.)U^I2i-1FsSpxRqqM'x O~لB!`4m۸㓣Qzz߽U9>P IDATqNy2RQK[&&c]),!Mxz? &LJ|Fxj>r <$h@RC&x̓Ae*u<5CV:= +v"tlQ_-|>" yH w1Cbi9y}>hMs6ܻwugiWROP%~11JC)) K[R"c4Zk<#bDe3[o)%GYT@f oD |u% 觜s:#伡\8x vs˒#0s0(wO_҆VpoF!;͓#VK"AG +_LS/?nOz<`XUjKpM-Sn4}=&cLӨloo1ۜ6C5BF1FS)/, [I}g}!63=CZLPƺB҉ZC]bǖZ\^aRBƨIgPolJ#GD-g%d%8gN!ę>EmM3cHO&Q F2?/ﱑ3JUi2 RZqU%nFo](4Z(}{q;I(S_V ֟;P27}/: 6/K9_p5]KMni^$oyV2圡wBbWĂ`ł KHTb Qc+*Q@:S3gfΜn=3KLn4;59{'rF;Ǩ\'W'o%[z'pw045IcO_MLO z|Q,/[FGW`COvMO:PBtYN$k 'kizN_(om<PUotdܤu\cYAvG<Q̍!!;_-"`?=We3zY\zo}PEys'A=U_1OHOS"֝4I<9Fw"~  wwjxʒwiqg>-eY{O^Hgfy=u陒$S_yݵmuZMۯ9~rpdhl{TdqDX.z3!Q KRǘ vv2Չ! 0BقIKr 6|; J>w%&$/Zt)E)5c]Y//,G{AcFkRۦ_Eɓ/: ֘_6m>ŵǜ!Fi/SOӻ(pٹƗ0 CLXRy՗S% k^Ȧһr# !ќ0rszVeXj 2~Q2omIJ;]& B /}e- 1stY4%VI;n?=^aK v-?s0zBsm%%Z3xĵ\iAQ)2;B> +a(SCiDA6)Z+ M(myw^ǧAۻ9CQ@-( 4XGYȍAK+ho݅}̰r3yBco"y<=3,3B? |(<"]F!X Γ_ BeгaAM8@k 㫀^U0RHr]~:A*kB C#!?ZVv n43@ (ʂ0N0Y)A-VG_x׼RZz8{!rc <\hiE+w%8cV`enI2fs+4́fQH6<&<1;ކ6/%5t C#$?kLW pW '_qЌkAr5o:ֲ` #lֱ~c nY6?x_yX5O7Kv:<>Cro~! ~]p-ehcƠVI̝Pl6رV ;Y~}M;hNaWkrY9F/Du1yA-oGzH)(QM@k{7`*kʠ T3~``-j*' ja#q[NW8Oq`_"`Х{0;;tX%2@-Cu{޶#wTMpb)KXj-PؠW:1~NVğ쇌&~pm渇?rb!͉q$"Ոju(Yƶ;og۝q=!B/mUɣh2bГ y79*16Q) fnXiNGp3%}ajO8 W4p˶M$ )SYXc{%7ֳIzN{6k^ TVR+-xH z]( '{ p݆#$ 5\)n9Ui]<ח8yw0 Su4`~ģ!?0YJ3la8v|s¢{)QN_`jW x5]_|gcyǢtI^fԢ:I\)uI?K<ԯn-,p?^.,@|%Wܿ&{4B"tv߿wv Q#$yGq/"_Pc-RJ΢涛!R6@DEXBXJZG νcyzv#fg)ܐؘۖm%_xyէy;y"G9W?+ VbU97q0A9`6]hn|gq\s8|mSo"i5'7Bg[BJ@LOz5VHo} ND$~Ě]+8ty/ k<%KO-}KAqR!믠F)!!J\EA9ogs$xWWP6@>ǯ|dh$}!V_áܟ޾E5؏y`64KE?ˉz)$5FkzK+ 9vOisvxHo9`}5W@kKA_Pvco|3/~߅dJS`d5wpf\+C7Ch')LɃ[,NBUpMG0w/Ox ŮwNvbm0=;8 ;X@19=;vp1;{zdd{=XfЫ ߷ O<낡IG~9F%@)i#xir3Ԝ$&xbd0{D>`GƩ s*Vi.8YYfYYfP}7}*XD9H?|0Oo?vO{?!#ۉ1,'JbH B):،AOX)* Aezl <1M3M!u'P?v6q$Ƣ+tKW3[&ShJQpuAU>AC4_ [TaP,+IہŧVGF(j q߉m,;2{ dL)4yV0w"7f(º>|niDx2ʞ`ʴKL\Ϲo7! W7@bG1rD8yf;>t5nA(nnúu^U0W8XNN4:O|=(GbNͰWWVOˁin8w[$2:!:Zʦ 3x$ݾz\+eo?.~tc60&g/i  r%jM[C'XQUkUv>ph"vt(L֤&a c^meeCELk|c_PlNˑ!|7gTR)()-ݫ=p4yr-4֍[ĺ==0O9qK'>[nAY&=P1AucF),u`kbB$)<_(e皛Z"I#J鮤w˱p~/hX"k,Xya[9.ZGwMw i6WR\h6z N/ ŮO?ݿ 5k ļfeнՂW`}s3pN< !G5 [Bu}m;{7y"(!l-|Vc$QLu,!+!!<ϹK_/|q.~9dyA\+$-rZi$t!8<%^eM5\Ivܐc>e3pvϼKIHqw d06rMH))uLD~fk._R'w%*Q7]p)qʒΠk>yW?^nb|QcE6I`{""b$b<}VռJD~~w;w4ÝL&4:Rz@VP(Q،dc0YIYIY'I?k4 K8#'cg&y[_^Wז@H1V]syۄįPl5|va-[|]RɧR=n IDAT͒鲔=btf(ZzpC3JdhYLaY=%?Y^v!Jnoy[_l3Hj$XkYZ >_f{gϿ޼ss@MM 2hh@Z-]jaoKf![1n2u_~[xKw~RS2Xr7k`-wsY~# hFhh%G2stu}saW~BH˻O8G䀡 ל!A /5 aL` ͖|:Z"E)G۷q1[`j2!N|'|ةt~ZrӍ|S"%A C90?pca B$I'9<-CHctw+6aa˧_,G3vKQ/H-:-Ź |O-ÀBivs^ߺ@^geFA{xrr0$&uA>ZV1}Ox j!s Nֲ~8n ?OmoKuW:j+}h czI甪$+ "mc jPHIgt۴+XkgkMҌI<X=^psk"IL= B>RRrҡ?{yg `rQcxADïQNoZZvwmr ,­Na-qc+ +mqV ]n'i/{_a( Vr1^55'\li-fQ(MQmC9ʯG3C{H)+ATN~!ʠq睃{2ƜU>[UXft}mL7!LfU2 &M($ypdC2|{5]\ Ĝ%Ĵh2*B|Yηzu,R\,,164B-N]k ͤB2v`پ鲤`Fjxx<_PYCmx (,Xb1x )b[k_zDUKTAJz?|3g3qcH ܗ,9СpQxEe{e֯,yQ:S.4Q<>*pcy߻n!ppT°nl09ǜJS^+(A k[v=6.oGM* !6aS^!IVL%'许>Jjm( >i,X$Lji񜠇4ѹ{KQ< B3rKN?$! Ɖ W(G]a^6N<s"3XJmK]yN?Ajw0>Hvf*Wc? ta0.M_Bíxa!(QSGH!eZ X=,e  yMm!UY)EgʦOWpt;j-rQ-kO@[Gm4AX'@cX&7M3CIiTҫ|.^#AOSa@xx%0,ɋ@XɑBoÐB`{ f֏VlGR{ >~#ẋG]Xrmy_6rޢר(tI)|BU>`h_0{S qe"|T#eITg[2锜cOƭNpIn|I֚:ԛ[;o9G?%*V--K>qFH&3ҕ?}=nif.k8Z]p?߸]é/A?㩛|6?q+OxDXHsB_n@p/. zfff(%nؕ^^U0CfyPhT@xrO,B1+0^\3\3ꪋY2@4n-lF0QNU6k(( yFgl__nyW%q:G~cLP0DJeK R}6$ccw˸I|,G2d"Ck4Y Bʈ\ XEfDG$j0~ ~.ܗ/n!_\VjdF)M7F# lxAYiCY+< _Me%KyVhm_LszzBH vc9xԎZG0e~k&к!k-JJ0ly nAT9/ i#'{A)+8{ZU/X*3vt#Jpٱ;AhcpUoorzrld0]7#ihr R,.W̽uo[.lI`B^4{if<\y'a0e 'v q0h* ʾb d˵xDxq]&e~2F*C<X#VY&ʸY=c &hdYASJ#-TҙP`MUvڪP(M7+eCQPMJ?T^IѢk9^{dV9^" ʚsߗ'4䙦WDk@=7 <j( $m赺Z]#miplER.q-O+ ڠZGʄHA(";{;t}tƦ#llom4ʔl6{;8d62cc:YŪjq峏zXR#+e,Xn. = 'B/_Qdw0*) 4$@Z)P;\u;z+ozJ k-o=rQnz_XG.ENs͑='PVV}~rBu&Wx/qP2>}UXy!_3Ek他a22 4ǜӸPM|?k^ (C>b,XARjl6{4w+{шٱM_/C&4yQPEF+!\?ƛʐ OJ̭L+ȢF#Up2{λ~|NNTkm'Hx~DH/|b~&9,lڥ, C͒8c~_,/L}t]ሻWlt+Q;; 2 ^|(HA l`FR;i&5M5j[kd֖(Qkgk 잏U| 3k8Yj}eyGe,k1C }=RBs.}ZC: '^@"x ѳ$(r^=s+_n[pOrGo:^$ƹ'^Qj.ݱHsARZ$Ư'&n`)ܴslV+6(;"0gChKsL^μ\ǣB{kx^ ϋ+(Ъ2e (UQj|iע[uB07$BZ k 0xR#{ʻ }0|6KmXq:/ *B[)yvN|eVr㧗)9T#f(%> )IyH_1'v2S:CI1wB{(yڷ>zJa͐f׭˒O7܌PI/%+Vϫ' W^(.L*0+Ulx>?*}33>;5k(c z5~Rd&kЧyN5o~oBpx$rlш!D}$z'1 ;yziߙA܁n'lj ~-Ob54CR%n q8U`ȲO~pB^`z폽Ҁ$n s WN>rWl-Ч^ sEqZ&o|׉deY@UUŏK;hD^Bڐ@aIm[LeYTǟSuZGڽ|;wϿ|b@ٯ[.,q׾G޳.3?Ad-cнqe-!g\NҹyL^ 7Pj1:QKs0*@$n,JwHҹt$)BWQF^~El\eR r+Hm1ۖXGH^qxΥ,By'c%+rv_?LO0111NsZ)}{[Zޟ~*3 C"QD,(8BxPc7b5vMEcTl(M3=m՟rǽ3{NNvYܿC;({.CL7h2BVHv3x\׃2[\FlS*|C=l(c1V$A01Xs']^y3'oz0Ε&d$I Ti+b'LBcO=)ĂTlVl`s){w< F##Fͦt }0'[a7{`[ DRBeHJgN ia7r5>VBy|och#],+SL(=X5E}H#*_wl`ۗ 'Gx (c9O݌C Gb#\(03X8IK2,^&@ؽ! o/>iqE.{Vf,ۿ}Oh̿?<˙b2LbxBꜥ<=yO8go %L72K.q33Li"+͖MvxξWh-d d+8U71ڐiv20z%җ u:VbZ@añNJt@пz֬VOMQuCb FIjt?`_ʝ"3%3̲1T.,O;Dj-G`n r[+ ]|%P953;8  W(n. =dXX|qP?@/SZ24UZ1[p{wv0|>\rmHFd`ҘFV 1l^#D!# IDATîv2ہ.H+ڭ|J8 7WzBW&엨1?+q"f%R e]J9aՍNfķɮ]2p%93 (N@X!M!hU`5ƶIGX ! BzxQٙagIľCK(!z> )RҚh-ɉJ>^ yFF 3|uְtPMq3O̷ xag̏R OR-1ć&|i`^;Sw=v2T z˽R2nZM8ڥ\wNnWU(ye=0₿]Tt뿐þPKs#L܎t3+hdfnAVʐlw[OxMP8Ckњ0 PV<ϩ5R(nIo*1`;_n77bnWI^?5/wxoNk/?EXΧ1GUdFʤ 0XcLB=H4Tr!LgH:qP tBUћ2]%hgǪjH{Tr }0=o(0!m0nmm{BfjC/]/$ı t'xAN8z1/ /-;|*bxS^LV p5,[ZNyos'VVHrX( NF֢Vk1SWXeCX28`$%Ԗ 7dEZ7(+b#>^pny/3r'nj&*G'PI4h5yW@CC"P7MXNGk|Mle )'_4ʈcY۷샹8Aϖɠ[%]A[`p\2%vP\JX.)`az-((E!Iۅ1m1ZzkZnI9QYv{•O|mCȴrP\;PvN Cqh?k7M4SLUU7em)GR!N|R9#}~S;@*qGtJRXuX[_/36ݳn -yXP(|\j[}U@y/F#qjzy>=#c-TUcJy{/d-͍]+?ũL]Egx13S箻S9lSYd/'qHBHsh<35>>[a_99;v'grtgFx@5U4={t ,+"&B,*h4N6b0 }GClo0X=9SSL._?2☾d?c/vt~{'uX@w2 lBRLr){=Sīe\󗗓+0*q95/Xߵ .vzE//)bl0v{gcJ` ,3* 2N[nZrH;ZdaP!rB(F_㰨BdE 9RE(ŬrZ&*]pp 1?~Zt:9߿םc>7 QEρka)"LC,%߽{{#Gdu<36flI2יidh93ٯ?i ҶubQ!ebQmk<aWf94熛~ӿ1fذG''Y5FF$fj?H AS^1x'|- j+G "O^Ep=@H>C P*#%a"Gg)^kk W z1>m tZ΁t3fj>{wTCP"P4-h%>AQ5C5m┷)`sȍ˘Lw5 "3$f2G#m0)&5haO=0+["k4Zu @ev `9Zv;Jy{n|ɑjɊX9ݯ*HIЋB Ra_  t嬋ٕ4qt#vTQ9$sӫFqo}T/qBѴsĢG@su S_@fg{׭X<5ϑV}0Yq').v`v7}N$f"7ؼx e-.5賫J\! qAd-y;Ѫ-Kcyʹ[v9Y!-mB?BmgEA@ EvH5!JcJ![\*TLSi 8[7hԦxއ#&bYGjwQY>BXMxokgiRxaD0 Jr!?$^wg8zn<)9jS sa|xEzPK>YGn6g~`UΩ%LJn -V{ oƀhkhhTSQKhŮ.JaB qVY#$1Ɓnzfaٚ:g>J{=Ư^^]5`eRQ%}Ra,O:YMhh45v׷dA8A:ܣHN^e%vo)}mbZ|?4k>TuO?qWHdzœ<4nkh.^{zxl -e4o5dt6C2:O .n2|Sȳ,oz`ԎM$](/\!74d+7B~ ԷC嬫stvˆѥ,=~,G/ܸDehWSM{H |?eC{R_^6h$l+TcI  Hc"13sn*TSS BfS)uA=m뼻rf6qcwc;Nݤ fkS~# ǽaJQ#CJR5󬋩’S4ZwЦ6z+ўGӔ*U0Bb4!L+h lՓ ]p yjz#O{Kna0Ic ˂H Ǿ|s&tзSS::+q=ݷO)L  2ml%E`Lk9'qi > EFkQ,c>^fH0慟:#=osFx]B5DnUl 1(.~;y=Mf5^YVC>vyN}[ϵ=؅䭬}R(X!@,\RS~Cfs.ncî (V IX&C3kL[<<0z nM 6ǚ<‡]2ґ*Pz{\틹qWImxDamQhmigK`v4a#D/_NahF!qh J>w(yP݁ c4(J2?$CaL]X+n_vyh0ZIBCŤiJ^cVwk-RzLt|M@頌a.͙K(4P/46`w\"h7׏~{],qONwEJadշO κet/ՈD*k+Sj,& ‹^S_4ƗrGm.3X20A ͎m9kP"H(a贏g~gNuIF\ńBXLn )lA*R:bw,|ռW&WZ[ƲJ$]04RC=5;ֶy({!]fcTGy BJN#]#D":(ϯZ04?ꓖXo5T QEQL B`2wM6dq'Ntd/*S^͗E|X2Pe\!aQ("K -JRmysPy#ao.p@Im-2t:Otw-jΰbcXGH h&'[.>]}[S&R7c.Gn;f4Jp py kV$AUk=xA0nZXuxiעX~ NSvm^wX]2̋sC%D&dyN֠6T2 AQ{1xp(E*r~޵)}] N{=Clon%K$AE2^qJtTJ],m ~ib?>xr''"K%]`~z!޽VF@}\FF`RW:6’[Kiy:dp#=KNCO0 <(֏P Bɒa _>KSQD4eצNG ^l+7]$eVQߩu 6K.sϙWQLԚ><4$&?Sڐ '+II}H* 2g 20 d-:ޠ;86Bn[@i $؂O W0*(o4^α@玞fJA$(j4 &7`a[XlhlyEOW 릧Y{c]9@`~|}$&m@mF` o ĉ`L߫Lj+xA$0N,+"sã8/292L)ZJTzTlG[he D%wMMvv$$H<2FFN& %`h7;s/O[y~Rq'TS |vJzRy'οRHI#9q`A_ןMK夝oi C\* DFCxWGH׉䩯^ᔾl63Y,?9H56et:e)})к@ كruj5ݚlFg:eߋKU[YGu3k> îĽjYM}/>yF&M2ܟK\rGǢwqYTa[ CA4d K:g?R#, ;f!ȯy LdJR> +z@ uݖt?g?<$DG\G4fl1 "%P"!*h&s06 BE!ɻ,֓RaK|pO*K0THJdzIz:Z E{!z>Em;_[b`4& B1/^츧u5n$([ Cӂ[z zCO+{K^~ȓO25d9-k]*ڳS~k7mb]7{^59>]`xxj^{A[6ttI\br|휭IT r7c5,yR ccȾP +SoV:Ei/auhoHGl=s0ɟ>F*SJ$6! i-4m_~XZ &^\F_+N ݾBkއ7^|{ot?]V..o?=߷aCjrezQPj_|3;G4 14Tℋ|5/9ebL/X L@!R1lj+z@vjc9G\>aFJ reVshmZ |`5vq J: \<Ec xYXc1^\OR&_uʠЈ{Bm./R@8U=ze=DzߠZxdI3*PY2-\B*1@e/B*mCe""mkd@hRkV6 bݨ[`/ms ,04wQV9[{6B\S֯ ˖-#}>?q //XdujqbtbifmMt[ Rj,gfFָwդ也`E סP-|Re2ejSɰ 71rm|rJB s}q.I+E@><# '%VSѪ6nJkRGڷ)p3MervqXZ6 ! Y̶s-p.7֏zmΪ VMN22S~{~Du!! ٹaa0BngDaHN;65w& FW>r9(OO;(_o=uWU<R {Mc,l7?Kı$!.Y e>Y,qD 'Ak.zZ~)#F`L4NyVh<mhBzGQ>kPXjmŶ[h6ʱ3XZ&f}x@}D8srә { uhj%[EMLJuC\%:_s{@g)*IY+ob{Xodx\zzrU 2hZu^Ѝ%M^}0ߍ(q;c&5dyƦ@"IJJeJIɭhK-g>_ WVcc ˰Z[q }(PmM.5x%A\59{YF_ .ps, jq {vƗZP%G8=/,t2B،<wnMw&awc+#q q!;9D*4LyP(^k{#W^;ZH cy0ß;.@tחun늬C6MY4LFM!B|GMpAφrLhicM T6HW`cN&֏ wϳnz"2^}0 o/ G2[Y^oxt$I9)9Vf͏-\r@GVS~w{WPȑss_b(^g!$jһo8?BvS"EIV0xD=(-=2_Hǂ4Iz|'oz!fQW5RcQNQԘgZ;mNDU|o?)0&XM+ 2%f僸]x þ+g:H<CheEd妲duE^Wl*8f,2$B ںKV16޵o rW7wPPM%c{h?s ӳ4?ہˠMK.'ۓ'V TQ9{dΒk|"^{֮)%.,jp%C9L|#XBY{^zyJMZ +HᡵEe [?xQ:''5)D,Yk[ c:VY6Xm]hpħYY>DOzS往ְ}/!5 k,Eg9.߳ݻt(ELwZ:%e"ǤKMof".j05988 {g˔ow׺u[[qǿf1 Ƅ"OyşZ7>%̐-?;JUr62Yi6_8V^^>qe Dp8}듟⌷^ xi\11+!(X9/iB"8`{% 82q m<7[9 tpRQ)vv֐ٜ̤y_=1Mӡ7Hgs-+ s8n)5қ+r0W]wfW:{L+N]_B,ߴƝ; :TrtDJ^}0h66 1CB2h5(cAy]dʖu!-#56]Gp7w._&J#xE:L%(MKL 73%BQ(~v+˔ `X.XaQĔ0D %=Jsi]@a;;݂29*^Y~:W&J\6w#eXƘ?IDh,]Z{JT$t\ƓlVV֨6:$=o\&(ZCI):Fg(&hљh)fwҸdr )Q!>޴[_r<:_- Y#kn[ǟOO} ; ^{A?>;ħ9VRbT3馬Fkͽwp-4NVk@RP-fw}3{uK<`PCAHyd`>Ϙ:ܺc+<jV/f..Cnw?^;|# aI \(Q\_S,۟U,$Ix$$ KhAS9p>LAD^"*Ĕ<'OΒe$Ad%8!{;K0SШǎ߻v*FeH$BBÄ5X y w۶#ߠ*x1,>p^k,]LrߗS$DlB(!TA{A-or?7}}>r C/;p07y75} fYߜ\ys'eeU&x;춖_׬ajb&&w|ܕc׿[M3Y{4{iƓ2 BR+2i?wڵ'8y 2 )**(qyM3]EAO]TD0a`r\7|af᪫k>/ndŊ ^襔yI ^y֛Ζw3m&ST1ʚmGI0feڶ0Ř b?Jk漣k)b"`»ht2ԦYZ_H_҃*tKgB ʴl6(ZJ?0^"$,}i>$"K/! En ,y^G8^1v0?K|zZ5>tk79wk,?uՔȦh[,^q /|%GzYkB1`jU! ,bh14SX*V E[A! kGqx{Ն 3.e…39蠻j 濇 {9GsOu=qry@ty͛X#̛:heh)A[(v Οߺ,/q=e)a `aʊhRYY+}i9x{mDUJ F!46(Ӣ-uW`0(rk0eם}s1Z Ԇ;+ylĺ7PIFǨ@~ݺxI>W ]5BKBڗ =T{{~COaٲL'&=x,Ko# 6@kx/x΂9`碭.ϫqeڐNPnQsab1kiguZEFJLC/gtc "-cΖ~TU/$̚Tk X~5lٶ äVdYF֢OXixqg^n { 7\>BEFTdD*c<$R\4o~#LEYܚh3z;[kxR5f/AGtNf;,?|I,\^W"ؼw{EO.xhHl~_LXMVQ.h7ZP>,>]'ķ].@y7mbŏyij8&zR=ZpŦstbd>BBwdL/$$Y^!'9g9ys- 'a46[SBIƅ]r6go=v܋Jଌb"D~4)o=-I3ag$"cCXhcPh^Ijq-"E͗y8`iQT}'5\1. <;E!PX)ѪX7+Ead Z 0ELsl<hh^bzr./q;&&fCK;32,ewAkm]}8 ?K/._&\FM{yBBƠ:9Tc]Xy?X,2S "$,wsGb&Wl,K|`Vm4Ufe4*xd0{< .{;I@r(]X}= @?;N7`.˜ Ox %̖&@ch*HI{"j߃@B]UBw`AO#SmEI3/ŏ 0VI4|2 ;=Urf?o2iō4+jMvjVp9:gUVu?]uzhll'@Wg=(㏭43e!fsB<,;'pEZC> nn 8r6 /ЉJ\LSnJm5)DȜY3,(KR1Ji1!ȷf$!O\546< ̥V N`u2S6靋.} biQ1Us~FNX]ٮ&g\jLe@$BbK99Zv}GjmUS˯9z(KD[/K*۟ޤ`O4 6 ǻ< ?w9c4ETRdZ'&l8dC=f S宺[zk83$(VMֵyN ΞMKv|paGβ®^ʕ?fϑ߰4ksolx>&~)mrEd6N`W-.ѢX?ca J&%eQRV p.N yҏ"=<_nf^lY5ϏgJ'e֭qd%E)2`-C(ACBƀް}NVszwt{;8ϕϛua8?+{b7ChBYi+V\B㦯!^ux!jaBu{Km(PF|w0TL2/ !)i-?e~a͚կz޾牶KO(SҒ hY"I']E1R|=tąAk[l! [Z?=>c^L y~O??9&akfzo2-~] ^͒xF5 q]u+sϜ1aoHEA{zsv.k,CC@?,4o~W^Vz{ABVk`EZyxϸ9'Vca O$~ЏTf1Esǎf~'3%2$"*Q|4n%BI&_Ҧ2e1dIUWp@߾q#k9#vi8GZ~Tl?]mO>< HFg<ĵ׺OsM(57ncK87׼}>W\|VOjH.(ӯxb!6ȧÝ+>^ ݈LeQ!=*iuoeNWfڭJUpin B皭fl3 0M#',W~C=/A3[?/_>_5Vy_/``dFx`r.I#8ȵԖv;Yu-{xgdjv!r3!#sd%3e *6cLMmcr{0m(ڊḲN0jWh6 :[^,?}ғ?Zmg(0<=!ydfðr|C>>j߻k_)/O/> / +;K$9ﷀ,Qaj@XGA&Z+2(s0&/s3_DY,Eץ^r`,{,CB{"0](Q{\{M _e=34vbWG>igQE;SLJB}k9HV,;o,<MFjT9nB1њb(EJfs<$ bDp?clgQ2>e cw.˞ &'I8 ՝dsvEaA 2! o~s}9J <^7VqMO XV`3 %$XJcP2Vo.X!A5< Z\tgTS8J:$>%1i^QqBtkcRVrء \$fCp˞~0G,z0 MO>iVkAI}*ǓU(N|hl'ϧ(h9&GkҖh$"& f6+4# %c9](w.qDq̿8{Sv&ܽS=d@9I <]]?=$F)hfիKW&}zCȓ4 ׆fJ[JcUg,v nS68乇2JX&PIA䊍Nqs)q6}A YbEx9$vL}I-X&Vea8_sKҐ&igvQ@ݲϜZ9 r7QoNs`#ga%'hR4H)0@۸ika,OOB uF貅/ G-(8'KjOJ0;g1obF.5QI_O+Wy,de6 x}u]sW]jf98w<}} \}s{N:k*y7ޭ||rz\ 鯍9i8\c'^oBj(b[LǚVk. 5h0aN= {$|oX(sx%p@Y;mxmzb-Op{|GHpÿ¶FNI|)hC"=`m 6n#<t8k(^N{(.l[6/))a y 69oE-{몫\ӃFga0^vw|]w۵U*9`vyOY@sikF7J@4f*L9KA-/hw\cw_p%r - mW}pxFvb KheCsnA[4$=@BPL{&Z|W񁳞HY*6TT $H"bcSܺj ~ƻYAFH)ֺyNZi2ȴa]0TP:ZBKF[!liny9O9W#I8ZŒy9@Zl>|cЂg]\"OR6ƅKd+n$iQZ(;PxYmLǻGz4nm6[8G!ϥww+0Ycogn?0n53s눧[3w% cuxB1߉7V Pr;ʰݟeB>_z {~ =X@$bp#=C+K)͒&ͺmO\cnf&n~༫YOdrҵGD->5%8hVMKln5i6YNYF>18䢧=͜@O_s0f+MY hOZ(啽ιt^iM7%\@wrW]Z OJH?яvܬ0t,',v;0]k;96C=`.y&a<4i'O, CӀB%UzqJ%z ߾npg43|wQ?@pW[%;GDjoH3`n7KmO#gO89@ Xx( wyWjV ,9V17y /{s[ֻd,W:>j1q1J)tmfF)Fs(,*414?A2M7XZBF pNZRݣRW]uu?ss^l}>R0ofE3P@^Z?`>nxMIW<%% cj]U $20Y k i_ E+rZLNslazs,Z0BJo΄/Vaj5,ouX$ XͯLm;T"N{6w*U,K1Tj58&c(, :zukp.0?1#7 Sp}ݾ wNm ; W[lW3ΰ~KmUas˻۷c,pPn6w}?rU.!T6vPZuZ vN)Z9m !JxLrti-?pLٗoc;R@h $ YlN/l:x¡[½Bu( )Qb㺂Z!:`cZs<KA \%C>eY,5/<{K SgxM喻PV{HX;\[G+j-gw֏9?!Rb6==o IJW!"G<ǁ9#<$+TjxQB١ fgc[y$퀹`w߱ ̿_mcn1RIYpPI{8J~u7gg|}@w״wzDsK|}E*;:¸-. iMW_SXa⹚m;WKi@E6E}T* a DZ+uқGKӇT/Av=OD`)b]ypVqΙh]o:'U<_%*dpS[~`L|:F9Ec@5eNQ$#;.z'AnŸ*;orYUw( [KS_ql[6OX2\l\F0ִL4,u5 |Py^'k'ϚY<+ȳ7RW]ut'SL2՟gg^d7b*μ' Xz&,b1PB0%AVm/ϧhLӮY)V|{=P z `)K*-t5BGp ]42M0s?ӹOC (XA54$ľ^X&0YNahڍ{ϯ K1T1AQr5kW]u{9(܅{_<7-;е& |U.ᜳ] ÎҰPYz|{Gx'8n7kdj֦vu&*=ˈ^va< f%}v9j[9H΄1)Z-f &c %I`%Ldʯ/8O~ϫS#Ys=p|UW]6PSgMyM>jAU $Y4V[2MhϹ8;e…[%DA},-# $m]UƘTF40,hLnڳQ܋jXqhE),V# Al,àsQ(}ݰx/<_v"=P-^ͩqs+:쪫o"{`ꕇBq*m @%)R䮔5cͿF牤A-l*& Ezb@5ZKb`Tyj1 A5Fk3chl^C_7GgC9dqeZTC #N爵Cc_lv6Ѭo!a gdw/t6#+*)i\ ZmF[mZ|-KBi(OĖ@(ݘ/e qnY)( j ?ꪫRծO'.VU0xI !O~r|sUCJOJm6ln`_dMW0A-Oa:_yZR"ZI4Y1KqbYg76OѢH'"n;xl[;pM? EQ)uIUZ-+BlyZR$_xcD:o\ sî:[-kX<679ev%4V֨΄k>ȍGc1 X~Wޢ'yy=?MiM05Fb !TY| 9t(2}JK/b;SHFjⷶ򕛎ʜtJz"JE~y߇ٶt+fM=Z=52&6kxsLox#㛯e BNl"mX%ψ`/lwll:k4KeIJ#V$?,I+b mmNݏv+44ZjqJDQ @vdIdh&ՉX!L0;§)45/p^Em V̍,:V<Sm%$NkWC5U8 3~aR$z0ii;&iGCsw0L1EՉ8Nu(݈QKx@S'dwx򡥇).iok1g/Lys6iGe,ee})&74&o 02ޚ%xԏ2Qk2LЈU>ͪ`u6;qH" ހmVAlZd}˟&pGVCo' erOalH `lH@pCq A(gs"o^H%AFedyF} 5_#t|I}AɊ>/'Xeȣ5Rҝ+3b6DdR"p) +e F>0ZZ-ìC:XBDC>8Gwk+4.׀al`D1|QZ2ϰ6Elc1V 5jm ʈz{7HM JJaƚpf^ԦPJi0+UUˎ|kۨ*0"%[;f{e6Il” 5EpPʹ `Y#{<#J) f~P+UͫiDMQfdG,, | kk`6&{W4RJYKVs]>w1l^{ݯj+RJ)u(RJ)`VJ)4RJ)uY+IENDB`trunk-2018.02b/doc/sphinx/fig/instancetype.png000066400000000000000000002354251324306050200212400ustar00rootroot00000000000000PNG  IHDRq+S` iCCPICC ProfileHWTSI۞{oP.HRBtl$@1숨Z e, Z"}ADEYuMus=ޜ<<3sy/}lX**Jb?}6'] i)wהt/ tN*ĵD yåR o.B&1Nĭ>>Df%I@I:"@=>ơ{`F?&:lvw<9 # BvRbFdG *+=%"h s~5c ԈS>)~F7)+r JI Cm(an,>)Q)G>r1 VIό@;ś'gN[SqYbih¹\ K8RSc֔f&_>s‰s<ɖ 7DQil /e0[`8V369mĻQkI~ K0Uj&sN$s=wS&|z )/l"A,X4"pKrBe`/8ip\A'A/\1A!bX 3!sp$G,G"HRT"u/)rB"b(:1:uFh.D%hnFK*0ڀ^@=ha%`!XIXVUaX3ֆa#8KxqQ8n n%n Wknq#x ^owųI|| ~?$?G ; d2&nQyBa0J$5DwbM󉻈AIdK'őD\R ,4&$g$*"Ǖ˖"W#,wCnPnL^YD]>R>Y~||%o \ J)\QWHV!}  ŘEH))uGTՊʢrj7"Sqbb JrJJ>JlJJzFi6!ʩʛ*_U~BT1VST\Ta4CC[K] t:L/wGTUTUUTUϨajj,5j>3yջkLihLܦ٨P eTk%iin8 vO6^]ݮ=#٥sQgXWMK7YXMCOWwNCdVƈ~~~~AAQΆņ-#O_>{FrFF|FmFMc7?70a2y`J14]bZezˌ`lb۬5w0盗߰@---fgͨkIdZfZRckhjq3lZh]c}FfMM[m-;*&<=whZ8:9J;;U8:ӝC79_qxr9Uz/7Kngͪ5nvt`x{d{Vy>2zz4c&33_y[{KOzqYs -S+{o$!`Y@|`P^êcvbvk9(",99hm !!CMBF +{n<-8`ĻH-L2ZDE)7sފycbMqĸq?aA Mf-HkpљŊًOcfأ g'%׋[x''mO{KAur`)!)Rƅ1£S"Q5M7-+Kl!-q]cɈ$H?I_$=4c]FGfy懥KOd)gڳͳ7f?yngYrk`\LXٲpUުkȯIY{unQkc6XP>5_߻m 6[pк&Φk?T[J*z{"墜Xj}ޝ;3v)m5}]e=G++6VݽkO^{?SPPe\URMά~Z]ur@t6Ω-C/8yHSe}Qc/~-'OjkIɂ!aujf擿Yv3g?wv\ι. ,nq[a.]b+^=uZu 'wdcG M.]v{v_{-֭=s{nGݾӻ»eAC%Ua>Ǿ3# |`SӒgz>?=?bc*Yկy>2odMo5GRߍ/ǶO1-L\KנSl {`Cxs:aG&0d;fÓ @%?c`~Ln"]jgMYz&@cU_dp^3[iTXtXML:com.adobe.xmp 881 463 q@IDATx |TՙjƷP$jIE2ˆKJ`@k"[[ښڄ* *jk * -M*ZL\euⲌ &-2 L*wL$H|2wy|Ϲwsq(w*N pGxi/}ӌWÈnmkWzطg7*]Iw9B@! B@ /} o{˖I߈̮#Ԗ.AKayh]|sgڿߓ&ac̥ѾR_W> wˑB@! B``&Ga.)1(M `}i-j?4g' p*_;TM-q!B@! V٪cI$Ǽ~iw|oEot cDo/ ԝ|I@[Z9/B@! dT{8rM|Zh{z-EBJB@! |X6.y^ɶd6?@8]q+KpS~cpf oƺ;gϰ7f+aK3ah /B@! B@ n?Ùh;/=Wr8w肗_m{GdKq\=8ؼ O=; 'u B@! B@ bgLS|ƋbK ߷߇N~K B@! BDB)MB@! B`!nP! B@!D@!ԙ! B@! >~K B@! BDB)MB@! B`!nP! B@!D@!ԙ! B@! >~K B@! BDB)MB@! B`!nP! B@!D@!ԙ! B@! >~K B@! BDB)MB@! B`!nP! B@!D@!ԙ! B@! >~K B@! BDB)MB@! B`!nP! B@!D@!ԙ! B@! >~K B@! BDB)MB@! B`8x܋73{\ţgLrB! B@!  aO&Es(D#}ҳ+HYO ! B@! +ĝoDl9! 0s$S>O#\-^ B@!  N kܟ!sLdIN'*6nȜ$K -M2Ш%"&V U<[+`Ih|n3<06K &i)hT7<9(bD88ό1\ߗRCb!.KtGq<ͰP@sR0mёԡb}u(&uڵ$벑ɆP)x )mS1'~9hےۑ9ƒ- 6{w.ΆzHӁiD +B@!  Px.sfBb}M~~LZu O^G?/eRʇ  U !s``@#ReF%;qFBEǫBm@KS 0^eC4kVx] f%#nR!az\R'CCKƦz}E2Iȼʄ(bY(I%3͔2/7Z ! B`Ts_p+vRj{/Va20\pFH'YQd.Xr$џ4Ld 6ґ -5cQ̎ܰVWr=j:s壁bxY\^lJSP ]J/B@! 4j^ލ(zTBƓ~CۗOAn O}Y|ɯ{g-**{g"kzn^x+eB-.ѵH |<-)=81'B@!pnhG@ARڂr pH ڏd,,D׌ߘKsҳ@y7du!2E5M,ˣaEf\]CĹr`0a3Ű/umZMBdif gҞnNOOѥSAQGJS2lv17-7C=Sk- ebSc](["x]>GQ3R5 EUs[ƦW7g&-fi^^g:Q8v@! B@!E&QͼdC?9o #>ǿ??d }|3r1*׫wYc߳> q|_S t}׾pHq 0 f ~}RG?<3UœY^XG[~N}2s&>Lg5>Rwի5ScWi/0\/Ti;8kMB 2B@! z>XƥaHr486`M˦yZ6 ZGtVL  p:y\WeK0B9< Z\zW3\oy}ʖecyL(8{lj㌋|h}M.5.HB߷9D} c/ɄE1 ύ-hv5rk1z7\>n{ [*4`@ʝ7?ߣf)Zтc !0<--BB@! )q^o9pѤr IJUVuZa\8uy}N.郖*Ya8//s/.\ ;J_َ9tBK\؏44W=zM m#YՈ 1~Mˠk-_f('zL`ALw]L|u0З;s1*x@!AQB@! " H-uٔƆ) g!3ʩI ]y1gC05Y4F1E+ 1<vU#k.y(b.헵O[ȬԚ*oRkq5ncvv;(Ĥ/GӾlXGh?o(*`{SC*@J (+w#~dRzW4q"B@!0 E4ya\*m HMz*~t*DŶx5S[UOlr50~|ؤMܭnlYۏin82W 0:kjQWN8Eb @XPʦoWP'\?P[ 6 xs0r>D#u ͛T/QgH` Ԃ_Yl Xn8:?Y^vFyUR0 ӎT B@! lL&0l>ߦGD+V ̙sPǦOUdLhW.CV!jz}o:|kZ!weJJ¥Y:?K? H3bUIuNmz_1߁PUÂnAR\[xR9>^O wK˿QH>)c̡\THG5Y`xMHۺ">Y9,o> Ń3NA|/&WIL{'S6Ejjra{FC Y{)p_ڭ+,Gc6#%`J>*)NdB@!0ЛY>U7ҝmPpSc.d4 ut2u6g}N_f1 R’x}}. Q&BT-s8P1]ye"/r/.g4SsEM(iwrc5cP̊K&w93 xĔtއ®R$Z'" p;p"8w4YG5(֒Pb0\- h;P_ ~TkP.&̑E'u#!0 F}ϕ"Z7fS+hj gݲ&,@K ! 8W\-L Uųs#B'ymmDՆZ }Xa RL|.TW=Əb]q݄2/kP;.+>rOmo;QȘc9qϷTlȫ!1+[ 7 YPu6TBmq &c-1>\'K'˧:Y} ;} 5B@!p xyTΟ_m4.q0--Ӈ]HVďMDkMrmza<%M6x3][,/'.ŋ3a+o 'NW,@þՏ]hp:kJa_.ʴ2GOc0| !p!\B@&y>nlݜo1gTy.\y*]/58*P8E&MʿO 떡fyt^Ў X܀ (xڢy(Zǃz-"+O9u:Ԣr?scc%5Qbg}]7^,Ww U‚,!Nć^ !p9Y.B@!pv |B/Iy Ⱦa*Z6m닩H2 G 5;EI0'vT/؁c>xj&"9$7\MX5ÏR^ߨv!tW7sbNx-;ByDun9h^L*ŝ_5W ب㣰r(E@s?6B@! {ؖo#q~$hH\771t-%|%1FO?4s$ p*SbQ݊R|n~}q+9[ 1&"Z-;ZuU7("ZKO3YkPQ@B_^ez;qi! 1<#"B@! 1L'c]d[m(6gn2ݫ wwG& LP{n *@J3x/t.3͊PvLߨ ц:~ T{o1uw~nll )FhS"`" 0`)^! H@itӼDz 9Hݵc<~,ܧhۂks[w#jKkX.zl~u,d`-Gn7=qG:c4EgktT6ye9 !V< zYXq/ [69<[YkF[JA@\9: I, {IIܻO7g 'd7<kkE_,;#n mc"mD-ׂ\{gX'޲<,K`Ł@)B@B@! c72,}AH6!АhGmy6,|tRn:,}̺ٮmнÍX$_ S>t@&itENg<&,ۋ0r/3QEhÀ+< )sMYZ6 qqJ2/GxfFBD滬4]%B@! 0KuSяZ.\ۚ 75La+(r6j9ehـm=*M߀Ϸ8 k,JV:` ck@ p^\ p.%[n\Tc3 %_-A6]{Wm o@v:iPSџG:l2I7{M,B@!pZ (rhj}6:( d±fwuF\sbLS^YV9 pU0ϣ ¦4SdE@<E-(FֈttٌM 3C S~2MaBq l[OsVs߫zCKcʊZg>h7F3b$!~x7C>$%nV?iMC O8P* m'"atnCk9 -vgIA[Q=vMT<}Pɣz {v\#p`!{4Qs \ֹF{nr- # *NcԁF]̽٣W  ddK ں^#ݍH՟l=~pld3Y`,! zI_VGvK34<]1,oigH6жNңmCkb o{hɍ%t!̃Jƃ)I=I(55"O;Y^76fSydP:7.O[حs/ș 缄.,}6#{~ʩN3Ezc&9xeZf 0}!ɺF}]`_Pߔo-{K1 [d5|=%ԽuwѴ<=TL\퉍(8uq|߂촙|5hq{]Fȯ y vTF|@t`02+aG(ˡ[7;5O+B5kwstSj䥝{S!2uRˇ<ڄsT?{ځ\GY>AlPDfk/?G4.]&D6e#Ϝ^=ȎhϟO|5)A! NG-|lE<}{C t` :TT3V_,ldrΦ܆" Vg#uu06WϚ,/rn_vMyOx: I P]=S2(!=@ɩ r/M&f<^^v+z q-ֆ{${D8r`kE;HCaEas8"9;? ;RK@6%ޜV{ }NM4 ~_5>ԏ>7%3֗#)l*MSKnO \@TT(P#R3Va f4ə p~PಟF? /G4.@b%%2lCŢ uV'RNݷNzP@i勔ț_W"f=Xzbxnarj=љnU?cuzԲկCbTUaُHo2XD>M=J [,<_MIO -FǐtP'B@s@[-8GRGG~|fhE<ԌW"un'jkt\8[\V- V;]e殦8j>8yL*X ![bpb֍k趖fń,McFnd][djvdKj Is9B\ifP.&.ϼkuV&⏺zB]aE1zQ8ql`$6Bա MTVVn`9YH8R-uPL9 )&7[SS_fR.oc6tEEe|ZLDTuwU-HObmMz UhlǦz )䵁u"+LQiMxA|l@$iĪ_ٹ.;2'ý(AS^$OBzQ8) a[Gږ)Hr\̴"!`-]k$8,ۺ\5oU-;p ~K<;PX? m8F 5t]R?^Fܴuy-ځ6821BX1ibMCS)7G 3,צ 4FoKP'V1eڙ,Byն 8)ߤaoDN몁` i}yQ#'i4s#y<ŏvZFQVEs:9 l~cwD"viOL`]ok#6ԢwX+|k )rץgh_DSȁ6)~= p.h?:-[yύHG U??-cl͑xdL[`G'}.>ǶŜqqR7hqo4ԡzcQ^gT#ӒB@E4e/>\Aki7; v'=.J H*̲%9sVF:ӰшXnW]=2 EBE@0TV`n/ 2*SS^VR)%eg ֛sѰ@c86PŜ~}j{I(QCHѸ֎inbX/ElU5_i,DfNXη9fݾ9G ocꤩ RhTo].eZDxY~=io|ʎOuxLdPqllȸo~ׇh:2 5#q9H_E'hK#( |g=Pg`,v)5BEQi.V]MiUopQr؞% F$&G̖x^q)fӬ Lȣ `2-[/֢ʔ\j B=ʈ l&^GkUH>Mk+:E;]\@1p=Rl}Au5J06s Q S^F6mGs^+Y_H$3Q>*zxwW!m:5;<g8+1)GE{f8P{izqn=7*drm@9ZzQ29}ølH(^(ߪm 3P}-:EOj[9^Ϩ>4Y! "`6ST f2 Gק4TPȟMyFo۲<>n.@Ńjj7̷[TQ>N{fql;q&OhV?Q{UC)\٨tQT 6iu)قj}\Ne#w աh{7qZ :t^U˪rs3ϧ"kxCpUy|K[zG2nbP;ϱj{\Gqh =Gڶkc5ٚn6l1ZFMZ9CAwv14<ܴx%7g 7b*8ULp;ꢖ} t5nmrvUvhc{<|ೇWf/iZRH>DG2,+RSmвc0ˊ`%ߵ!~{>! )0~)C|h6X n"Q?_1pms=&nm|^R"_n'z9w5xW Iۿ_(\6 I4ۿy%ڋiC7Q_N${ U?.:; oRcH!XsMOTSyZ)/#]y"g] 'S;DԀ'kKa &k6uhZ#{N"İg]z~K#PUI'F)- 5G{k+=lK/Eὄ|b~Ԟ:hnuN|UIq<'ZZ0"iYoCǑv4R(K%xhOyvbllP7A!`^>8g|hi3:Z9Q3ThE!N)R4t_'A(cF&"T=IoBO|.CEԂ}b٨+8QҪ>Ð[×J`6 kݔ*s9׷i}|Q(H)Pz!LK1cU +yZG`lUQ4ksqlD Ƹd 4RKG+M9Yj ^ŏ3P>1(Bɞvi=cԙ 62CɃXl(phY0FNTZ tn\3T jF f[02 H&v~.gq{zO; B;c|Ψ{MɱY/P815a[Bq*5=}T9`SslWQ #siG]ml* #0x|7BSr?</VQNHs n|ʉꎍ8ZգR + O2'0uKꞡ:!Tg-X@ `97VM(~ .U!~%_ ͧN˘,gR,5#4͘&B=w;:Ԭ*,t8TJxpGrTC>ϡH,.H/Ь !d1*Կ䞛Z'`a9z&tÇ\u(j=}6 i*k3lԆ8iн8u+%_Ŗ:茂RID Ĝ5`urfuWGXhO剥G=_[ߖؤT6Ʉ|:#*GnsLkcJ&lnH+6+wFť𾕆-.|lI*Gw=K+5b;&&..ЄN]م(];@IDAT9sF8eBnNy2.pie(!%R8_Mj۰oSo'ԁ)ɇuQaB0LZvVqROKMѥ~\ᄆLТ0l?[s R5?iR";tSML\T8r} eipPD+M6>9DTNlTFWԗ"BJR:8IGRzH\^6q2>Zƿ\29(lЩzYlA0UO.8q7v:8N-ҽԥȦkPkg)j#ޔHmrKG^ʥh dJ0+>fdv/8Z`[_E 2 4j¸ֲb}v/O fSS)!j#!l]_`_Ǩba.č!!qR1+-Z\z$E8I:Q:ԡ%= ˁ}Ԋ"59cWm(uR u3Q}wyGG" Nƶ&9l }1; /6#?m (\k3QpS\F-hzdԳ<*.~OG3[4!.r i&EiPpE.&kFkQ)$k:4g_,5c!0rg|wDM)O~Ƹtr\6hD=JK`]N]KR8ٜK2d,:;ZU!k!]-~.n܂*P|gDB'-4lhlN`<1?*^_J43=:&4DMMi):9alk@Pifzd 9nwVB֔6zK܇3 &0OKJEQ`M`Ϧe/Cz&uGh0O7 թ;cXw:88;ӉK&nKxTuS 74'`ZxQjQd<@^7,LDBlM8]'FeKJJo\5S31|<&v5ُV00.Z6ѹol3TצΪaquKSũ`oe-IR?ǍwVKX\C'wG\~G\0C3nTiHn7<4 ̟y r_=S+G-F+UHjiO'T^ƶi 5UDXǏ܊9B5]gZu;t. >zS~}Xڕ ⳂOW텒98%)8e~0ʳ{Mp2 H b {zT8^(hNam7K߽OXO io\bY]V5y:?UYJx)c&`!Y4*2'i'*O΅u=1ʝ8z}\]EԖ2Ty٭%|ter̯@\E+6zZ)A(MqԴ_%Ҳ)}\4FYng #Q4:=Z͍9{.ПV65ӾB`UsYZߧkE2EbM׿kibqK6HCkIk{W*hNhZmnmhܶ)OckEhcK (l.N8Va !~IHv!cj kewcջK @bmV+a~ZZi'ZPؼBYsmN"hCM  lVR#3I-L-7 *$;یm+B=S>0O:(jc J]"/76wԯd}A=Jo4K__Oy)6!{C9٢(iϭG3`ooq4Ԏ^TӋ7EL|dzWC ! NRf9}wEġ[L ꪅ;%࿺[8jg>p/;Wvڎ4:!93Azhs(F0mzqѾ]Ck2Qe|FbZ<tē#ȤZ zC՘#MB3W=gLXEP8:^߯e.N>UI$ 22N", *<1.~V8Nx EO_{TQd6fw.MFAz|\{|r#ǵ=yD_k-RxKӀs5gzv(!}lc\KUPc3GZ9!B D`hN7(4>H+iSP=ԊYCs5G=B( {juz35~_]==QN1C_uQ&փB@!vWϑAmMV"ݹsݓ2:WZtnx{TV4gGnt MoO^O\ppGM{4*kUڡOq&Bw~ϫ0}=;?'?~ihznCX w|ݏ"ϦR"ˏW(lM1nQ;rrq׺c@oS7^óQ;pMw{ ' tF/SQ_WWQۃ[Ju,<>Oף߆<5ǽxU5x0\k1ï'k/CbkfX6\h| NjX3S4zv7ZM@zr)>PL_ˇE[špD(;a .z w}͖O14VtX/uMz.~Bk}殤`䀎H5n;UG/k ) .RZndO΀KWPŦǼ"X8lXHߊy8…QRQbv]ݵ]u/b5ӛ.ݸ?5F.^q/ Wv}. w5czkw%wrB3EOe^M?z)f~oU> p\Ky 1G)}sƧti_isk=W0N>Hޅ546'7c?|KH^w71.tݰ|\ʦ !E\`nN [L!SjUxK\G= 0`,ОE1v7&SPYl>bw7qERX?Ryfaa!Lv3nZ3|_~jkp鳯៿#O~sf0cy;f?Ƌ/Ǎmy{=Ըv2;ޠ p3P Zză(YVY#shryG\S]pi\QMĜφo] ? Džtng<M80rqz8￰;L(Rqw>&?Emw-ʛGt|_* o-w$}g6n{w_tׂ߱C[Bjt-phɉnTH+D'̓6)0ϖjo0f/< iVLe6r+.X'\<j: JalPՓs@{;q?^13;=t_~sa_2\ ^3c6 óe=caSX;>,Rt Kp}܊l^6͏V0 > v~gy7(%%>oOQËVW-)=3OcK뱐k=M[l=MԷ-ȘmpGab I3b *<Ɣlxx2õ MHi [a@\5a6fF$bʴ,X-{[Q XR1e ctMhUHz;7SHfYD߾FKvѮDUK,ՏmR "j")páwwa;o. ߠ&kaQԽքM{@/ߺ}; e~Wغ/){MZ:\9ПyWb_Zno8vxCq5X~ljZdO$[]qw|Ozرi7tTou_}7 b߽^oP n` h% *JPŮ)f/k%x#4*T,Sb> Q̄kzs.,P{3=s7̱CEI|]X~5ҩ:_=t&kǎü0zX/؋olY\GnkyXGdז|#$7ߐ#id-\=+Ki{/t)+{gJAi-ѽ ׉"(]_&ٛWgF&nMrQ^- UkE؜I(pf,Bs,rT 8N.bfWgNHn>.I&܉(?(Wp@R(=Kd!ؼn))9={OPF*T]޺JǛ'bχy_+ѕ q&.s\)[E QXPW rt 2XC~3zIaXAհD  2 *FN%JIj wQzX6b g%5c#l _b \,{dZ֑@XfĉeZ]|i.Y:Yc~-qk*{MQ▓CP bzX.pqD_4H!fN"YdDLB"|yn F܆B;8pC⁸C%t^cI&D ="?٢O9RKoHb>rw#RChyEɈ `$́FS%h7TF8 &kz<+>r^q&^y-h}y@ } kFi{ӈT+QTنd_;5toSV@^5ɤI;/m:lB\%(ՐنKocPa0;DQIIT(g 2~ؓiL;3WK篡]%~YFz칆l}F_:]$Th!mYq;t31Ǔ%NP BўO)5+ =fNզʒ@D[V(p|e%,øcaÝzdx[Rf eGv0;J^qpAR :T_SAXʷ=u Ra~ß~܉H"7m611[A)Y休r@ {? MO@`Έt~/q @H0]z xQTl Jn!?=ÐF!re+APB)7#.ȵ*7oo +tHLtO_ 3 >7_@okR&*ϡ`YdդQ}e "{搋ܴdE+8H*POwsNcb~ggN4ɤ )G Jͮ7&EMl8fr kUQeŸ:;HnfNP|z ΔmR y [wgi.#/$DN7)ƖV̧t1gI9{$&>ߞ⒒LWB(p=L׎؁RnOu\'\Oǰetg))9J& PbB!@OQے bpQ ֥lIgIƞOF@d_5'{Υ +qw!: z-@ߜ46&#dy={pןuW2d CUnj/b0 Ճ"G[v@xYJN'1x. 4Z9!A6x'P9J%pbO)@ 9aCcwUO݊Dkr5LܒS( ]=#I47F|_h7TAtBzIG;YkȼDJ"=yqEI|u |@1=jCyPa,Í!(pB-˷']RTEMV€ih5_,AheDQ-b$;zgrc-bb;m=9d):N&*ȾA.T_UTf:'G\R"}B Ȼ#YG#թqRG4c:6g}q>O.k> o0YҴEC d8WWD'N~ٟAyj>Z%VܪDܹ@x]fĉr&zUƢPlQ(0'-2IE,Eλfz0z bG;pij#!e`Y=;b#,ME^%B'Ң&Vp'/Ρyj3cuVԷ-'YHleK>uA( %̭= C kRPE-pe B9ׯKK뤳ֵii/dzƧ@ @_LQj0H;,0qa4<irR94Ookv] T(1 L#13蠔rlIĉaW7X(,{!>9s'"Za\8l[)PEPV˸HL)ia,'Dm2iLjQgXڮMA{kҷ1wj=m[Ӽ;W\HĖXDB5i_[U)М6a ՋlmI;0Ylbg#evb  %R:i?ƐD YƔhxS^oaI(-FԠsUS>1HsjZ4^G{i\!Zm߾tb)u_tY_DVph'w[?x!oNe!9[Z6QZ|Z=wyQ}1Gj%i xIAu{sK Y~ίRjحAB5^U#)(?t{mۜu[$1/3wKX}G6),@An&&pulpr,Bֆh//#5C+H/ U&'KVR$|,)$cN*!)gl -u@j ,~_&E]gؚqo+CabsQp2h NI/=P܀lR i%\hg6ZBujR5Ң"Bϖ4> <Dn#?>cw0Y Vf^b!YhHѡ #DDMV0iXV|-fOdR+zE8:PlrlA)'ɢE¢I%&u-+=:Z|֮EݣPiMztY?o]iZ-4"4{&e7]/c}PstCߊǀt /ɓ#uCX&}kW4‚ OǓd}#sb7勎u~9N`}<0¾-;ٟ\9Ezʖq ;.HIs:A? |iOפW7̋2&#}VΧJV`)R4bL (K4U)s] %ك=E7{)dC2ʖUnB KwEW ȝR#ci"PGh ]ay xx+R\(TC^Fv+ɺVAsZ4& rC^PEkY~iPqj{#/QL[wψJg԰ԺcTc=Oޥ3OWP|p }vӺ?\wZ>@**'6#0&hRz{D ]”U[ ] @hrbfIj2r'O)ţwLH3`آljH Ẓ>Ւz9-r;BVX6VNcۣǯ`L,{=z+z+p`L 0&`!*mԶ+֧Y9!l5U.cҫ4VNxE8\E7)0"):ykҙI?3$+Qx?Eb&^ CȿRc[kW–jgЏFߍ/ %OмkZ\TZW]nwi-" iJYY@ &mZ J$R_'톮8Js+f6򵸸;FRE"Ӊ0&"ꕮHjm2vj- _M-rV%oN2&h액(!Yk.HPPՒvn(D3==KFյ1Pg;#aGTx"W(Cm c4*p !)HܛV?Ma:TA;GHʱQ<*dL< L$% wp\agwXvctroC8_N#ceሀnO:< )9[St}+kN=y/OPgtw*6o'"[x"m,=w(wp $YTWpV>P}Jwq:-ُT|Sє=a0)SR*A! kǩ] a\vi똹.sA9)q5( ie ,oY(; 7+p}f+Ř3|݋kˑ~<^j]UhҲ ^3MJ3I˄_$2¶Yړ~@;Q׏p4fK?XWquTU~ԒK98"S6f]9ӆl U吅( : e5sR .&M7,V"#Xrz[L 0K,+nN #dKH=[G Em@ \6)`A`gy c"j+Ow}ǚZRB_&ǻa+ڣuP1$!Y~z^8pC9濭BؔO?6opz BRp2Y͕ 0daj#WKkUF/iJ(J0Ȣ>Z[S yf qqPTk( ݫ5-Ji/kZg13 K0?O}_A $(`okJjƼ5YFvSwg 4;34%zQ|zv+[a((9pұ4YHXVݕS?Z©(Bt r6$Be#Hw :c?PVp)!vBNH(grF9#?FC/Y rnP6JڲFVZ4J#QN nacFd4JD0l"B=+ '@UV$pK7Z J)gKMa(c*E/&K& ]p3x7$cYov4Y.o#1+$d9<4ncD8ŧWa\BMQaEC3$vSg{j_ Ok?>Gu^@EQb?jMmbwP{k¸ ɓ&= Ĺo6Q|Zo!]%b(CןV(Hѻ\V6%! M]dxqAHز.-,:"+k!|a,M,C |E0n9ۆ͢. | (UԺ8 Ǭr SQ")By=|B<8x:q_ F9F9-8"i9HvFâH;.Җ `rH?(;q4sD$E\U߰Hn'zQ`)4a4EO[z, =Ɂ WwB6o3ʝM.7D7x94B ZިFW"kI b\M KWv$ | kSA_@e!$'n zI뭯3&&1 }IW(8s?6V>P#:% YPԵyFW)$+o,&ѢKv( P1 inN }RD\8|z.usI)*=? QG )fG,jFb6b@VؕHͮ>wLYh c-3PY> cjd%.؏P7oX _OJ ԈٰDƇ/K4.mL$p;( FA+H.wKJA)+8MFLqKܦMd'OСC_6.XFQw*)P݃1!k~TuPMst+qR#LU>F!pdlI>ǵH#}~(v -ARH%MfohA0w C8l4HZ~RImJ |D#%"X"xpD03JʑQj[i`(74peO_ll9Bs/vruD[Ҡ@9]Np @7%L$AzeSle=7m鹡GiA7XMd T|Q{iQE2P~JkQA"6[껈Hk/ÄlЫ0|^9/,K>=M"4 sjJ/gvIɧLj;HxL7Q\ g)bchTZV|RW իҟ^V䲗@s2ғit# wGg>M˻JyW[5z,n ]*o0&̐B׬i8:~>4: IK pf)q1P~6a39Dg?EHJ쭺84OmN4.?1ؖ(oHGPtAVyQ5bzOe{rwVԤZHeo7`Lj*D sDڂ6V{f4u/7r—&VQ)u Mȟ.V£&7xֿI#[8, ^ 9 ;CKeP>HHfUj1eOĘ7gvB<'5qQ2iY)UrxWM85zܙ+ӽfjD][ҽ) UtbLM HDŽߝ)9碥>g.-zی L|sv*zYj̠$1EM4w@Xt1զ_urh>z >4wvZE0d6p6W/mϞei;A==xח4 }'O];y{=-|fmi8wH;o~EQ F!ҼtHfYF]S+tNi~2z,=,_KW?N8Ui]/Q?繖J[W,Z{P)@{LAOd}%J j4紁h{lB7ڵQě'WIqg٪tS3'ig{5O{J[k!;\ud?z8K\N*t.| 0&p h*eee "+DUP2M\n[V{Mߴ]'=W㲨5B^Y58ƛL 4c#RpE@*[e1Ҩ$ɲDEV-l"Ѕ eo1v"wAH0'5K;]>W'(͂ߧG<0MqyG0Fw.fXԏiŧ]`t#~z*4dNocdF\5 dRZ~-k'%d |)Bgއ3ey} Z:UeAP{,GS:G)TW=%150 S_KKsn f?^y$Ii^.o;1H#˦؈ei̊,i^ŰlEV~CJ1Y^H;,Ђ)P`hC ]L`L4+%wSڢOWXS}[w 2>}Y4UAk&{ᦧ`L 0&fN xr`L 0&`ym gL 0&`L CX.6W 0&`L 0O L 0&`L CX.6W 0&`L 0O L 0&`L CX.6W 0&`L 0O L 0&`L CX.6W 0&`L 0O_`L 0;Ge91&0A-q&!&`L 0&`%\ 0&`L 0&`+q&!&`L 0&`%\ 0&`L 0&`+q&!&`L 0&`%\ 0&`L 0&`+q&!&`L 0&`%\ 0&`L 0&`+q&!&`L 0&`%\ 0&`L 0&`+q&!&`L 0&`%\ 0&`L 0&`&!&`LBS^r(-,aecBQwnZ-JQz]CVjW ^#Z<Uy'`{=](jiR2T!1EwvΔZ6\ 6DK~(Dy%]RL A5t` +aaeǞpapïtt=m~ _8xIۦgT^Ztsn_U?$u-s4'qoo|7woon, 0??P%-SgC5Kj>-s8=LՖa{t("7nn;Ūî&oq;6aGD6| QOEsj@B{zMŸUON`Y|ަa] ??5HEWMAD6X]RS|8&(ć`_w|e(TKHp%:)%#jp5G0b(mS$i_$g2YP;!K|;[ 6 qo:[YFőZ4JD,* =\ aW]Ļ`{4HQ[k⌙s4֥WSGR}A/E1sLi( {@Jy$@ǐFZ21{㋡1gT޿(2ORޘ Xt^/UF~z e㫃{,n4a/Z[٤BrSM]qzΡ~_446K \v 8xfoѝpbl,A~gaK ׍O9WI \&"umB֞T߉B ۘ8R#53 $#$C2,֔RzP3[>'ɴ҃L3VްL<'vވKQf6yKP&W؉8I[S:<\eGi_LKJ 1q\F (:2&lgwqt'an8rA;Z|- $2aYC? Fnq9V?C{’v"̙EB|"[P컯I'8Tb"lR0f/L]_K.*AŐPHP_|,_cg+pXOIgDJ娫=* oE+vӓ~xJw\';Vm-ե/ܫ'=m`ӆjF&j猓Rhn\@c_}>2:gS#Wܝx1q'Zq0r8g7C0‰i.aylT8NmhOJ (?q-*,n]GFz22HGin/`n #mvd<Ga 4ޜ9=Q<Сu>GSHb7J-{!JzPޘRC֡r6,_S?NLz]YoOGrP QPn J$8EeNٜ++y:_ 8Eq,.K wsHbҿgl>^$[$nd# ?@6O >*J#mm2!vNp}i={SFedkx\lЖCjj>$=]7xPu-Vf nav<?g'bƀΑljӖP>0m@d<^ܮ7UXNfxK M+1oTR+e4I*M VL 9q8B~U7d_7L l?~l#qLq @e%ݺײ1+#}Pwgy5 ߅#8b#d5j8ZC>wG jz2!xR'% ~=^^H&heM %e@!x6%wQ+"C39s3""Þ8x$_>U|noD@G%4&ř nja;5rӥ,ΈMOb< {#"x)z$٣VD*`.b"YZEMC93kɊ7J7gY_:{#ly2g^yB \e:gZj=K\KsRGjQ*_B0Tz6.R`LYG$UJPc]>csVX4}D f-\!"2 4F܍Wt)9"p$ vѹEIFNe9㫳''m=NS'@i!h":Z'bsU@^٣uKIN%*RWt8qd0"qHEͤE6<úz߀ЉcK|j4Phnc_s5; իR-)qn/o$lFyiNGξtoGv,ߍE8ڗEvQtxPjJ."d#e)/˨)@|0m*󼈜=8 ɒ!Yݡ)*U72,uj y5S:6ARNi~t,IQ)WBpDkII!!e#yY6DR&!,G%\G Ã+nAkAmD(N%rG%YRN$Ip&O vpK*䓿dtR}MVDi`/d*Rى$ +e8na0KBG NK&F {*sB|ѷzՖ 蔸zQ] 0fD@th䯓7lm$+\Vz8ڄh.,GϪU,;&wvqHDO"%|$n%MOjᎹS^%pԕK?f +5N$415&Nٳ`ao?ov?~̠I3ڪThwxxcۤl]%c:mjeC~4P"8bӢpJQV(p(_TAB{]ќ+a"TmjG8Pr,$pZL&r11p{N,hk.d` 2iK UJ0ʡ#fge vv]GY3 e"gxZW+\yzK %K8KA#04~(H?^3& .>QUxq~ )kİ.C0ItgˎwbtZ<1 w _F-/ӕPt6B=e,SBUrZ:dZwZC1Ehp/,;ݣ-lD޸QN?O}_ElӫC֪Я\na.>Cf=wdm+yCqƍjt~!~Wc!]#:N;判gH_p(9ܵ+gغ#Jp|ᇥ?}+X*-8BĩJܪǴd -ro J[ucz(%h/#5{2h^tyIJ<\IKE)QvpAt5 jR<"I%DmҦB#Ij_b 4)Bw?4Z!`,gJ$A΅*H"t? !* G^Fn>2h[zj2E<}^@&3I@9nЪ‹rVK\7ڴ#ںp#k9aAIUb+u|(8p.{tZ|J]`͆B QD;> */p l(P=cX#Y$}I [ws \J/ZdΖyjga(25-2I-GFY_ jy  潪"7H`r)8)qRP+IsvGv'C~#hURyd̹oL/΋q@fH@#zOL|~jی]`+f4?Gw PeJP;g\z=4p_ٟ\і-8Nc`l,^_Rs905ܞ [uԗ+?!Wa(z/PRtzpIql8^R.zl)Cr:OIWkCK\!6 %9PX}}Xbx@ᓽ`" ;,A#!B6pm[D•Q?Mpx[#6!t]ÍQ~H4I}ox߇I1RGhtZM In)z>3&| (3(ybb&Cbyld]tt9t7'ګ,I`VD*8|6W00b^Gi2}D$D3| %y|$"ސi]Gǐ˰^81}e9²'ü7)mq!9nEѪ+- Fk{CWG(r=rی` ͧ,}7­+Z(Dt06XԺgZGoJ;cJXw\%m8kekm^5XQ4wEk _Rv'he߸0AƮrRTۆpYN_M!YW"Hi=) !@Z(Cel]FEd./7X<&W 9B3$Po DX2KL4KFfrٚxS;խKY+eIMA+=5"1.AHxrZX;~n-[zaa S9iъ1.-A-,ozOCq:rfR׆ˠĴA5t)DЗEޫħ&1kC5);KJy\h Aˌu| 0CJ)^-ڃ~~PCGF.eB@f!L 0?@Y)q EpQ/Da \UUVI0"ܚ0KuF DKnؽ&6 :%6 P!V&VSb x 4asp-?r{}zz96;sf 3QN8=Z`.$Э'NxKW@g;nG^ZHH H Љ RŲXgu +F;{2U֌d<(?ϙS s&SHlqN!2&E$@$p Љ;@ @peHH78-?1@PF      -QF     GS      ^DN\/RE%     :q     E"eQT     G      ^DN\/RE%     :q     EL^$/E%     Gj$@$@$@$@$@ަ1K$@$@$@$@$pVwV'    m6Q^     Z,< @o#@'i ĝgIHHHHz:qMcHHHHH&@'V? O$@$@$@$@$Љm$@$@$@$@$@g5:qgYx     FN\o%    8 Љ;“ ~}Mp8i]XAk>[b3V)e;bw>'`Ng4[J=A:]Ylv3ѳͽVo:=~suh$B i'ln#L&G7cqBą9lLTצ"w4c{p/hz\<̓}gSWdlG;faʳ]8 3rmk'Dr[Q};IlkHylr!NkmHOn7'!khdSGzJ( @p7MvZw~zOoنZ we3lW? !R:Dy>er ] iz%ï=q+vTA##19Pi{Swf/Fu6~2-=A%DrǢjU4,yh>ŅkGr'7` Q _=pY*GsPj7fezkm(RO|5m*YSP vw2E  }۰x^&%Mhhxxp_`FC|\:b _l]G_Ap;bfmB3(|y_۾M(\Pϼ'QLM;jA2|TnwUˊCvxPdGTM/,?M ZYZ&//CH.G_)#N/6g<nx+H^ t}6Å 4=O&F"3&#* sXGċ_-`^͛_(2hҨA^5ُ VCj: ׎4aC +Bar|u'^zT|w*(2   h-B½ӱ|71Me 1W?GvLTlLo]|X~]kI^\q2m!0 cAXT}*a@ν܄c*<"8`W96hZPSF`c!5F/i@"K>쀲_'`=Caݠˑ^%=I&ȫիP(Kx+n|!46ݹXK֧0c˕%#2\RjBF6 r9`(#1'\^.X>Ua=Khԝ'c6K#du%7ioTI$@$@$p)_Ru$T:+0ypd%[BeV{7ҹ)sѮux& ŁŁMab]a)7Jꏆ__RD"8|KJ=Z&v5a+1y^ }&S,x߄!DtLzr>#K=LY$PhO@?W5ŘV,~ ȈeRd䗷`auM彲ىۿ9" :3xM8{k7Efy+~p"ێN9 hpUƋ0SHWz:q=KHHЎa3{ޱSEQoz#9F82|Z+ Wz'|,{lƮ9OM7*첹KA&<]c* V⭑&'v @=3GO;p|$ӝh5b)St'NC:}4t86B̰Y/1M%{QWjO=f2@?F{`42o#yizʞV3$|$M("݉oa.NPV38E~uW eaTd@L#٭dy{&q:eOe  ը8WQ^(DM]&dFDH IœmڀzulyZrd>"VCM;4m_r.;1d:d&(1]A\ꀊO&!!nWdhЯK q;tRD@qYexotNIf(Ze 9{(VYVFD r`(lV.r3s*tq+"=GY9X~3 ιyܙ͇QF@ѷEmTI:qg =3& Voe/dq?P#dl%nթXqpuhiiD17qiĥJ֞c1uR˗QR0-% qM#Nf}z6O 1:+jP5aŨ^ 5n fY'6&HF0D(cO4]>nsꭦYQx)F;=T'RcSYk*;,~l,uƲwhDgpby;eI0:1uܥvQ`׿U/T2:zQp2ʹ/ЉFLHH"`2G!Z3 y;ǼL,N@FlzplᎵȜWXd<+C )hj8q Bd-F Oh1i2Fz9%2)y7 ˎjzNBX;\H_נvmȻ+UU%S .Z空39U ر)`.ctHgSRrP#i4sOTv )C'ż)TIv}& e-d,X‪C/fMǎ}{f=$f>Z/L%Seԙ!R;n054["0.wN=zv-^W|.67QtrLͲ[eO j )!`BUJ>>9k4MFfZ<]c0ZT.|X_Cw\D]vvŶVV$-CrdĆD,Iģ5ݹ(pK=3+pl|fE9qedtENoq(-۩\+S#Aľ3r$/6fwq]v$м[P;k:0ĦW=w[g)EZX,|Dֆoƭ~1ɴ ugVN$@$@B]R=:u\sB*tMM~J 2Ў,|cRt|XzSl7K9|s|N@ [=ɤvUz02uemr>wl!{Fi =P   1;g -u?͸PdN1 $@g+:qgYn  @ˋPq+qb˓NU / t:qÕ @݂ @=\* t :q݂ @=\* t :q݂ @=\* t :q݂ @=\* t :q݂ @=\* t s%U&J$@$@AJرcAZ2HHH\o$     !@'f@$@$@$@$@$@^,J$@$@$@$@$@th$@$@$@$@$@$ЋЉEʢ$@$@$@$@$@$@'6@$@$@$@$@$@^,J$@$@$@$@$@th$@$@$@$@$@$ЋЉEʢ$@$@$@$@$@$@'6@$@$@$@$@$@^,J$@$@$@$@$@th$@$@$@$@$@$Ћۋd$@$@$4جZa EX0M2J|ď.Dw\v6kxMBy4B76/ Cnn\o= kaa 5+uX<$< ~ូ#-h>d1#pѹV4h6;>*V'iڷ6@h:۲xHHʹH_tPfNg!dg V@vFdfTp@ƣ@IDAT\I\xL6xKLA٢{H'Oܟ׾09S7aڷ?; {LQxl:lZbٙUJD$@$Ы tOYx( Ow^ц9sM?nO%/{rڷ(-,Bqș4U^ћ&`@X.3̙754@L>hsfB?FߎY_-'xlN浣}&lvn<!xӊ;YңT S Am V  @Ky(z`LoT., Q˄kCysh?a%qO!T'`_e4&ޟ/п-ǯbdt% +0 x@l)2S߭ĞSlw"g*؊t-\_)'p'/vaлML4 ek趥ڰe-/IOn2uG? a ^y ?mŕc1y*{a{[o=8j5]xf`ꖇ eG8vm^CWM"\"^xggY4$@$@8?/̓)U}@K .&; Y&`[{AdkDI2Jz/2Ԗs8V>'"C/^5cKw!ZK7;܇s;f'1 @+tղ5"FKd.2w=[ĒҥBL֤|e>i6OYMH1Oa%C'ra?y {p؏MFѩPbֽxYR`_c_GK&%Zɫ(\Wէ/ b vw%:s=giӚd_"jn;Ll6m=9zB>kJ$@$@A@;*n>88U*9 d=RY5BsJVz'/',;yܬb&?"_u%sάU|#*6}1O`Ş(oo}FXܶ?dܛ13#”zeڌaziwh_}i</*^@2г/VXQ";0{k Do{5_DaΊ|\!Y8؝;`Kx0sxnȴOo.z z#E@6Ϗނ [^WNk|&?3N\O  T !yjoc_%R? ^شv|m}-/0@=3dkG$[|";˖c1 0⾻ _S.A*`\C)d]U?ar?&j>:[A*26 X-b7<~'&]D%=*LkN"?F'v4˚֌vB^gecp' kt?zE^L@(<*Ŭv)w@N'8vM_r"m4g;3˟  r2OĐFkMnu٩9n~F:GTz?bDϩupO”_]f0s+av:ֽ9Ǩ_G!FgEq^3SVbӃ4eXޡ%ʚJ^=6?Ff؎)y7ѰLn2aV:TL!Y{A!SM|NiF[4aj~XMœ uƲW~DV Yw";,ibܯ{wS7͇RV5/ ) k[e4P  ^Kd3J6xK6>U xp5IR03U4}f-c"݅2Qm@r8|YjS{_mU-EzK. 8_}&@!QMbyz8P6Q>ڎvMS`B߁dʞLϽiQ  QӇ2$k(^LE: ىm/kBƂ %{)S*L'y#5E(ӎEE.*,A6?y*^>x2S ?bhc!.D~\Hj~k^&~9.]{]E0LȐ6 Wwkx#ȕ{sβs%~>SKf#[3|Me ✕lX1'˶ڋ(};MꯓE]0м ٰq0u^}3^cZel+^Gw6 KN`6:O:qgZ̟HHWԉsFXet,S;8.׎9f<~jt" &4 ;ډW7_݁5R<18}k<6Oi?*qw\#?b&O,AaB>G hNߩvj);_:>}8lxHH:sBlf|rf Å'7㮝uO;{JCIIH Љ >D$@$@Hlp>끷t TaLcLzvxx7f$@$@ЉGIHHʼnQt" !;eQ      JC$@$@$@$@$@=A$@$@$@$@$@]!@'+HHHHHz:q=DHHHHHBN\W(1 tz"( tPb     !EP       :q]8$@$@$@$@$@$CC$@$@$+X,^!'$ ^&MB%#     "OHHHHH Љ rx$@$@$@$@$@EN\p铥!    rt\, @p\diHHHHH W0G$@$@$@$@$\>Y     '@'. :qOHHHHH Љ rx$@$@$@$@$@EN\p铥!    rt\, @p\diHHHHH W0G$@$@$@$@$\>Y     '@'. :qOHHHHH Љ rx$@$@$@$@$@EN\p铥!    rt\, @p\diHHHHH W0G$@$@$@$@$\>Y     '@'. :qOHHHHH Љ rx$@$@$@$@$@EN\p铥!    rt\, @p\diHHHHH W0G$@$@$@$@$\>Y     '@'. :qOHHHHH Љ rx$@$@$@$@$@EN\p铥!    rt\, @p\diHHHHH W0G$@$@$@$@$\>Y     '@'. :qOHHHHH Љ rx$@$@$@$@$@EN\p铥!    rt\, @p\diHHHHH W0G$@$@$@$@$\>Y     '@'. :qOHHHHH Љ rx$@$@$@$@$@EN\p铥!    rt\, @p\diHHHHH W0G$@$@$@$@$\>Y     '@'. :qOHHHHH Љ rx$@$@$@$@$@EN\p铥!    rt\, @p\diHHHHH W0G$@$@$@$@$\>Y     '@'. s8ǭhGh3W_s\9Y_~

R}%o"     OSkFcmƅ+ʟ Svv]Oydµcґ91 ?c IGE?tqϛu\wuZ7^+ @'@XK֖E "W%_%O}ڰc]9n*oIĸ#phZJ{K'#3"+kD¤([IwZl^ϭxk4Η:Kk;Q-iɸ;Z^ U(ߴYO;O4y7oiW?HHHHHN@MloĴǡFDʔ锩n)+G{@ëQ|76d-ѸDoCdH J%0Gf!m`CՌxdoQؼ:U\BWhinBv\VJDLTZ&#=~n9X#;6lm{sW+L)#q."" [֣Mke QUIarو۲}= cf7qQwT< 2Ie!qh |     L4ąIъFJr7ʑ392Q$i|U ,4،!HѺZFB' -հѣGcr3Z._TwNj;8*kޚ,S%\Cb`X7S=!7!wfgTyȐ$@$@$@$@$@$9nuca*a=6nL4BeB"XB:ԽIe[(Y]1FbƤDUΖ ob͗fi4qu2W݆ n% f(*rͲɿ#|4lFېǂ z+iI[hf cjBk5Wh וJjN2,5IUc0$ޯ@dMJ@[j QowK$@$@$@$@$@]'}S2XQ967 i 3ٚd,yNԉ3 36Ni~Xi%HWvyb!rk GBD; s*t4n>5K`D ""WIJ<$^!{bZQ8ctN@$.BXHHHHHnvb5&WCB#)MY!FScvD<W&f@yHwtlRvJ\2  pz @ t&]1IHHHHHBN\W(1 tz"( tPb     !EP       :q]8$@$@$@$@$@$CЉ!$@$@$@$@$@$tBqHHHHHHCA1HHHHHH+u @!@'(b @WЉ %!     BN\Q      JC$@$@$@$@$@=A$@$@$@$@$@]!@'+HHHHHz:q=DHHHHHBN\W(1 tz"( tPb     !EP       s[XWlZP[-u8ck9i=akmB͆*TKζe~ֈt/7n` - 4PvUD Ȗ99q 瞿^oND$g^#O$SD2kUyort=c'vV'-\͵gNMiNK*NKA:LDP'E8g;1)O.]i×6t؞e rImu8HsE4;DWT&YZ'P:K6o]/N0ܓs}r8!r2fu(//:Ipih'ˇQ/iU!Sh/InԜ!poOsWJ$n'J1 DnRDŽ?8uOv 1s].ih#V.ӖwF!Q4 QO#.mh<8;5wϴ9yC'jK>Svس_}u'u(i U;꯽cK0bn%rG=O4S8Mv0ں)͉աS)_Q^\5Tf|:5^=¢p64nĪW`F q| "֣\oiU(YW>qHy0T-_DZWD H@ҸdEMVM2,nX RG<V1 I7Ocз\GBbZ͵([]fIv?OG|sR\;i[i*@g{jBZuan-+r~/7wn2\k[C:1 mC0v}kEkooMm9~֊E]vY:G"F2f`mq Ț' ÍhPiwgIlVC6wf ⯋D]i2F'>t>V-ˤXP8/3P_5z4nD.л3,h4Wb=(|WųaNѓQ(yHO'Y"ӀPTU`ܰHY3+oѦFeoC̡ȘSkX4@ʔ+2#$KV z=k]3 EVW*ܯ>J aNW 3V*WUvkDz=+Ӑ<#WdgSVvr(g4Wh\&2f[DG*Gih5UujA6̔;u0m.E\?Ϣl$"RkՑѽ :#(6^z˷ho(Ē d4 (a|l ҕjǼl ɨPi_ބK S6P ZttVh!/v>bχx|`Ehh%@^fE UmŌLzV蝆?[ҞoF͑6ҧzO!(͛bo4ȒH6K+ͥȼWd ֢@+[lfR/w u8i7jt; \ڍIʯjvY4`poW;o1 K>fkAEg3hY!3uk|~NԚ87N#vws:8GLؾR}}uE5F6iRu{ZVJOt1 nsߤV X$k#TYTs2>?}ٖuLJ.v-'i 6٣(j=ՎkZ ?T!wyNWe7rHZuXJ/e ,˷FVLs_k=&(}'i7H¥\v5Qb˞jƆ?͕M ``?(&vi>a7^mZ}kcؗrW͚O|ڶzIRlPQ6u J؄6]sѶKW_>/>nHW5DoiCs_*G͗5Ci i.F_1F;z#ս>5$:z`>}Y'we-6+s]6+f A:6uf+o; Dܳ];H'iꦑ_(ÎDw_%u!VKwѽFY>V퍒Q- WN/n벾\[av$4{F9LJH_u4R:ͭ:9&(/VKULiκ4}maA؅uҟ9_ ̿nsq>T;*k)ͻtKgbsZд{PeAYJKYq=Vv[ˮA㺲uі6.yvIjo+9@FQp*+V/X5zaؤkw^F>޻6h%ىͩu8N\SUT%vNOz[;Vz&muGf(mnn흲ektH)4m y%uLϯ"tېz.76ϭMmXk GNTu۳!QH릫g2kyҷ;^"S I6H{, szQg:/ThEMzaҧ{9ߺu:11ə2j%3FXLQ9F&&@Ƶ你+ K3*vLN{q+2npduLIxhfBBf>J\d`QB+jҍ)k9x<ekx 7SE3,N!44Db5($kKޕM2bWzZA(]/sud雊A0!GZQ+ϖ);5cFP)彘M^GT+]2Ru'O_Ga7<&':y/K@U3ڟBspӴjvSXEǍ7tEl4"[FkT&Q SB#1 (-P63*ָF#YG\#/YѤ|L7B$QJڮd`Q.rĢ|HFOĬ%2XE ̑{HX-Md=EuNe*J,\ Z!72]6Z=#:c8<<1 S[`@]Sxx&nia11l0*S/mF##S%>9%̄LCe?2jɆZE%FLϑ]7޺$ƮbӜyLWuBwN@;:M )pk4EIvWމb!8 &lRuy Jcѥ1!dZj>T0%]o{.TdMNn}U?̵Nc;&x,$^f$,*^f;eH)?l~Q!juw^wQ|*vtHOo<op^[ΔK$!rd$ 4I2"YX5ָcjmɄɳR^_.ot^эΗ`g҆y/[Ro]RovCL:@.WvK"(k }U0Z-~֝McžJisQS>1Pg}q&iw)ۏ=vD֛}nBbL_0Ŋs>wمa?|eZw[-7H*6n'R EbGq> QXԌCP\l Tꍌ9;(!_"EgjJ4ע\7]jM$z0!D9zZѼ]i.31bA }emv6#({a 6H3?yVʭFk<'[mks~¥-i-kAYlQ1F\;k7~)}vwH'V'N4Y_zpsPqBm#8*Ά7vt .ȡ~MOu><\)I6w~"~8u] >QQ s1w|YVTM`_O!]:ԱXXv_T5;:,{ک,H1;f6]d 6x]&HzugmO>'V*^I_x@r}qЈt}1f{c߶`8ʟZ1;z {ztlkeLCR/(iAd#2t /d"#(lA9"![dZ 5H.9}n?q6ٔD9pY+7#*CFڔQ VF؅X{j-H8Z&DuH8k@xpyG-r]uvL͈!#A-e2a$Eo""kM˺UKVÔr4e7 AnLڟ[.2UԹ~oG^j{ݔƶQCꬬ@o֬T$~oH~akߤ@RX,θNֽJZ(^?e _`?=Ț˃~9E6O{y #۱5k8_U#F$3_#Yd39pl PQeJUn`/ƥv}*3d'4TPKG˗se+J2.f]~]-}p% YäwJduF"ʵW((Re wyX~`2,ceIFHqo\Qθ }ob:dM#Գ8Zzai i]@ [eb "Ӗw63/sGwnJG#zk^ ؞vq6v*l&#"\Rxf>'k_6nO ^98G<''I :{sŸz ;zLCN7ݾɉ,4fJ;P5' %C4\# ܳ;FוwcfA2 ej"o-Eɇe,Y_dlmEs͓EU8aq/7d#*ىU˦aZi>u-'|-_IDAT Q|4&5eA6u'QVRՁkBDq eqn&'AF4귥s܂dYla,2&Wf?(C/N{gհULn[SF ;rjZr(̈$B$.Cg4.SQY{e籶1Cը\!%APb{#T+SN;J*TBs'ި2flLCU(SjCHmY2Mbm*C6phS]W9OBL#>bBL9( D"vZ0e#>= bg԰E1ZraeZW{TD^^?q'#~%c,ڞb=t-.kaL1d*tqT:۞Ln,V 4zݔNJơ>24[[NXF#9udf:!Cڞ&`VqN(zd4Ҁz<QYyR-hSy}O]](1ro2$q<̈E\qW^ < Q لAwb;9w8"x|jV~;aJŕutW5EEi"y/7C6v`C{yA1(#dtD]_\#;*.UۖԳKtYbvteyv-(+gE(IӄTѰe5ҳS;Ayd־ꯤI(8^H!J =@+ˋg_oKotB/ƽuC;bζ饯{u0xA%Qx1Jm6cp珗wO6ko&ҁJ>ew0c>9ӌ&g4K_+;>'[%?~*L]pͺ]h> LffX|,) 7ʹ׮-sl667ߌF烈P]L}6Wꥲp9Z E,Y8Ә-'ܰR7mF|f٫Nl\QնeS>-h')7Iw`8w9S?*SY)﹕#[U ֩]%㴌L}؃75jC8r.tjrtpEX7m4vu6:,kd]5>b*-81#,20RrǗ 4sy>ih嚝T|BJ\A st4DB۴;PwLr7#;Bךm_x=?A$E,lz;xecC9$|7yp%ǎi-n._[Ӵ?Ƅj#v~gZӁz`_V6m-Wi>hɜ3 S 6rfj\-0WٗQ#or}]o&7]K^|\+@z;5U\?Log>}_d?a{XV&H6oP|{6&]h*ru];.0jr`v3ehQ͵V;Jm/pmRu~K=zV,cX$sFiK}KO{sT7c\pIu^ݟy[PF a\*&7'}}&MY0qt\B񰼷]FRi}9q ŲN.ɎW ]:7#4Y A' zWM3eOZ%K/ݵ՟-W;$啖>iȎOۣ(#%K:}/M=RgޜI.$}P0үg;zIi-WMv~WlKZKj?"dZ ksvp7b^3%N7m1-[q3F߰Jvb:4bödy_Lغw]N[4)KOSYgR3͐L}.$Og<}!y}\U9Kȭ/Ou*=#˕k3Nݏ6|{Z"Ľ+{|o'崠)15cn~^[fl>a2%3wH%o>i_wG# 8tƒnHi.+q9r_NܹecSRzAPf^a |gm6cL0t: dk G_!3J /Sz;I~LӱHH{> S8Ė'g*K=}/M=Sl#tRb|2߇e@|}0j_':$2dN7Wi_E?rج_<ߧi7{uWp6iulC)׆L._ťJso?aH|ε zVK BtwڗUc -QlW.2O42w!k;wsQC\\_hB58>P.5`̐yG,NUm |h⾪sӱ.s[nB`УbF4]nt6&}*g:yK<֊{J 5U&14P `{]\FxnUxzbKW0 8ܸCGqA)L_=Y휗 ΖL>B@! B`%rtrA! B@!  .|B@! B@,m2[# ! B@!  .|B@! B@,m2[# ! B@!  .|B@! B@,m2[# ! B@! |xq|8 6H\ȗpïN`R"Kdј4^W=^۾ȇ҇W1Љ#P0Pæ*/f&zuTvj̜EXZ > K"B@>ZsC~^x~c'/Dq¿0 ͌? 'Y@a<0^l`ԇ+`Ptng;\uZ` HݽH6;FG`~oU8FoB.C0h < VҔh7e^wSG.s[u9t?ZqDa" ! WD࣋Z}1<}y-K:S#y) @=5wV"7=S\>(du]kJw%d97Sq.wq?&8|(ZTVxo$“' ;1q,t>FqE JS9 ಬA0Ti+EGc(3Ç No`r ͛ 7Y .@Yj/`qJbdzObeZQfflgUloJ{:uZ)m𯿶Y Iq,<. RmD>K=H$Ua>3D bxȿ^kwsve>|Ggru͘ș^^CO,^uk%g^WrUFѱSTݰ~Jk&Oy@DB ۊuuEynGVsy!)57aKdQyGP2bۏ })*Ѽ7WH6T% D\x:/ag;͕h?ҙw70V sQ.A$24d=Xf'FOY:A{wkݘ~sXrɠ7~}^UoKPzsq{s Fx8;QjA41D zR0qLYvPb!{+rx O;ڧZv@Sm5ˏ'*8 cA4`&PG`4lk#x64!2 1@} щDRvab\mp8дQ7di+Lr~bԈKw^SیF2*ۃ9-^wFh=ShN'`3o{~G۩1unZ)܎oU؇$:gq4VWu?b#K[VG-OY\>wa]Vr9dvglw`kbz޻{Tv@eN'=<&"B@! "0ʕ+ f'ZiT?>/}j71c0N?Vm9g3otϜڎL_^+e^>7ƏtU `q٧jwu3E06~oX!ӹ]Qlk;mŶ[8`cү],ݎkNۛ3/Cy8kAϻw{cd^m=35k'ʢڋK|:^l48t_^~tb#6*byypLmc*ť_Rߊ coL;hw~8cqC#V-P*Y!tO'. Ԏ C2g씜Єk,^vI9-ukaD?Zrj2$Z9S$3oee(ɔWW2} Jy+UGc6JNS*QZ;o*s5lT_M`KFϰIURxWJg+ټ;'_7Ճ`A)|kq=ҭ=8}]/t g"gps:$DB4[jqz{ņCX;@w:j_E9׬trXU5ӏXg,q"8k;47Oͪ~w"#JG2oEwʧCޕTag`,]Z<7;Q+촦1Ιڼ9z ( ꧤ 8h eF/X]u l^H9.p6Ku̜b4zBt8(ctdccp}܊:zmRus+"ìoJ P"f#[˽\R }/5y-s Dť}hj|jy,)åR~wUb5yc'c8"϶/+#ʬ_8U~qϮ%?4+diw! BXA\rP#ل*@<7Px&d8{iq]<rt9SX0v}h|nkq:ViF'^=pҝXgfwG)>Qό`6&443:'&-Mܡj~8 q_ ugBw;a8*( ݑmBJ*wi{W(IMmnXSb83XQ />=P 9C4 )ReP`j?_?84iNЦ [({|g/Tcm\1'd312 VAG9GGbAF߱tg3gN" M6EyYBPysI(>j. Gh=7V!Tex!LpPհ,TvՁv>˶C\N>|=}g8X^ =˩x*^!<}4&R֊j=p t05-[Gs$f聑a+8H7L%sYΥs2)!U!:܈ 4r(A=WW *xa}yd5~*K!ӣ:7!˥5W4'ztq'*%تOE g]'ƭ:* M9̐qPN' {">bPu^̷m k5l#ZLFt8ӓ$/suqC{(utRG./d nDͤP;9A+]`NEa#|.맟/#~֏J.Lbi_g98HɎ5Ʃ~:*=7p@KJ|-EJ| & vf/ÌsJ^ ??QO[t:|hp,]ٷqiO988@o9RO968\tf_clj;؉X_U^xY8pݰk UiLc7Xȃ+z E߾F8;SV@_Yp=vN_ b|4ἩtbC}[v"9~]=lP94u<'B@\qjgQ拗Bep{:888^v:Rε֩G9|ٶyNo5|ÝL㢎Xz,~m8^173E6}ُ-E}(ۀ <Z/ww<>rޜŲn8>r͙&Nn\hH[h'Haǖ^T?G MU1@o}}NRL}]oGNz`N7VFLs۱;ANMŸzTy >bᗫI`zX6jjBg9؈<ۤ}ZN1f*!ת+.52PWzϺϯZ.4]ެA(g%۪T|zN .?mݤg eŧ~G'||=F#؃o4p{MOo+kS+PV+|{% _M9M\g.o4:jhm5ahZėctut%[-/]܆}h}3*6=A%!_ym!4<1,/AaT=܁:7ppszƝ{/>#Aa65`֑ z5p-OT_l|Gnzw<\n߇NgvZ^O- WKSleY9};܃7m;tFs! Bp)*sDM3pFmu \Pw *a/;a̾NiDL.S/ͬߍL/r0Zm͡Iapr-IvzEGcknWl&zˇ]'L9#,^15S&";KgkU1sE"2n+7mz79Y8|yP\ޅԉtcEU'[SY/Mv ϒ{D;<[o?>;[/lmesc)V uL؃Z?79ATR+T #}N{0幌ڮn%gTM3U$"̙ڝٿ3S(Y{?:˹B@!pUx}dlP~pRݡG@lc9a_p'M @(2lb%OiU=N+Lttp̋ևS T {fgYsLގ?QAlCqn|#{g;[PpEљg;׭TY48j[f;ʵ=A64p)ڛ=Z"j+Į蹛/oRv nil>M$v-~ƦT8%rq :w<]:>R6TXdjwdvOO G;l.{ڸsQ;tS! B*XTR^"Gғt<5G㣾iOU~c뽏e>4m++'>3ėt^mæof/ ).0=ڶo鷦\8!A'?u#B@!`j8! B@! X8ENpQ$B@! B@d" LB@! B` A E! B@! 2B@! B@%D@qK2D! B@! @&2DH ! B@! -QB@!  .!/B@! XBd*CDB@! B@d" LB@! B` A E! B@! 2B@! B@%D@qK2D! B@! @&2DH ! B@! -QB@!  .!/B@! XBd*CDB@! B@d"QwIENDB`trunk-2018.02b/doc/sphinx/fig/launchinstance.png000066400000000000000000005601631324306050200215310ustar00rootroot00000000000000PNG  IHDR c iCCPICC ProfileHWTSI۞{oP.HRBtl$@1숨Z e, Z"}ADEYuMus=ޜ<<3sy/}lX**Jb?}6'] i)wהt/ tN*ĵD yåR o.B&1Nĭ>>Df%I@I:"@=>ơ{`F?&:lvw<9 # BvRbFdG *+=%"h s~5c ԈS>)~F7)+r JI Cm(an,>)Q)G>r1 VIό@;ś'gN[SqYbih¹\ K8RSc֔f&_>s‰s<ɖ 7DQil /e0[`8V369mĻQkI~ K0Uj&sN$s=wS&|z )/l"A,X4"pKrBe`/8ip\A'A/\1A!bX 3!sp$G,G"HRT"u/)rB"b(:1:uFh.D%hnFK*0ڀ^@=ha%`!XIXVUaX3ֆa#8KxqQ8n n%n Wknq#x ^owųI|| ~?$?G ; d2&nQyBa0J$5DwbM󉻈AIdK'őD\R ,4&$g$*"Ǖ˖"W#,wCnPnL^YD]>R>Y~||%o \ J)\QWHV!}  ŘEH))uGTՊʢrj7"Sqbb JrJJ>JlJJzFi6!ʩʛ*_U~BT1VST\Ta4CC[K] t:L/wGTUTUUTUϨajj,5j>3yջkLihLܦ٨P eTk%iin8 vO6^]ݮ=#٥sQgXWMK7YXMCOWwNCdVƈ~~~~AAQΆņ-#O_>{FrFF|FmFMc7?70a2y`J14]bZezˌ`lb۬5w0盗߰@---fgͨkIdZfZRckhjq3lZh]c}FfMM[m-;*&<=whZ8:9J;;U8:ӝC79_qxr9Uz/7Kngͪ5nvt`x{d{Vy>2zz4c&33_y[{KOzqYs -S+{o$!`Y@|`P^êcvbvk9(",99hm !!CMBF +{n<-8`ĻH-L2ZDE)7sފycbMqĸq?aA Mf-HkpљŊًOcfأ g'%׋[x''mO{KAur`)!)Rƅ1£S"Q5M7-+Kl!-q]cɈ$H?I_$=4c]FGfy懥KOd)gڳͳ7f?yngYrk`\LXٲpUުkȯIY{unQkc6XP>5_߻m 6[pк&Φk?T[J*z{"墜Xj}ޝ;3v)m5}]e=G++6VݽkO^{?SPPe\URMά~Z]ur@t6Ω-C/8yHSe}Qc/~-'OjkIɂ!aujf擿Yv3g?wv\ι. ,nq[a.]b+^=uZu 'wdcG M.]v{v_{-֭=s{nGݾӻ»eAC%Ua>Ǿ3# |`SӒgz>?=?bc*Yկy>2odMo5GRߍ/ǶO1-L\KנSl {`Cxs:aG&0d;fÓ @%?c`~Ln"]jgMYz&@cU_dp^3[iTXtXML:com.adobe.xmp 779 867 mV@IDATx} |TյÁ 8KHe^#րDjC*1-!!*M)[W .% FX!V|ċFj2Fc X;ygZѣO@   ~ 0KA@A@A@ ",  @HDX A#ХKj]JA@@ D PP3 8Gm ! }׉:C*LꫯZN"  'N',\j= }/Y /s|W>hn _G!y;Kn( N%,1a&(IJlۃ4iGUpuw;2@@h`ITA@@R)wP+ \ Ji"مWN̫#vGyj;xKiC PGnn0yd <eeexWAI- p!pr[gh+ދO1DA-7G/,x חhaɧ20dƵ쒋ľS/ уm@/w0hh{nԋ/ ]t;?pQOVKYZ@!-4h222p 7o֭[;t@-4 gbeAA ](iJhz8[b>낄Y3}%,fɬTW-F«Ò~_mŸ8Wm{}/ -QY/_yZ4܉@[h;?`Ay_p5kRJɅ @W5_80+n~WGuT^^otL_ߊ¡aJ1rQ a5ێA-k n 78 Uΰ κ l.fFxړ#N+{8QӃ?HAH64\[п-È5#Uj! H-nn*vr7jLfi eNhזz?СC,6o ifGHNNnD/iO:ʂp5q[g{Wħ;84J*G1sݸozuYZ=.cI:Xpn7p{=x Jøfyݸnd,7*~ 9x8=16^ԿWHy=.T=G>%ZKT n8~H1 Ko;R,FF?5W uOỽzN'P٨G+OqэF=~0q\uc@okW1yS8V5: IvшAAݵK -*hj-7hB'|oSNņ 4Cgd(:W-ׂ 399)8W,ºy`䤟ϛ͊B򩆠а 6\yXOfǡƕ*BhТL%txtjq&xCPh@ͪ,&{OZd8\k&X;WTو6 Ae!r}B72 $&eՃ羈̫DG@Inxo=җf ؖ?ڰzڀxdS$/>@a\!O7]"G 7{؝р56 pQם'h]d0܋uCQm4LA@ f~7ݷɠ^2y4 <7j1N/wߥH&͚GW歄 ]Vl}}}7ݻ& A@YYhAeϗe9$h~24"؆$W?ϸѳ?s 9KY{|Yz $[ޖ>W]@eA*}Ѐg `N$%׫u4|%/?VQX al ?10j/lU.{ǎLJomﲖㄩFeܡO>SUU4fA[u69-r^_|zQwn2f::&~*,6t ?3ßxk7aeX9S[Q:h$N,_OOŠ17؀_k}vg%N}?q{8vGh;d\w orTwDTBv7Ɠx]uؿa_8kWx. |sc"E#ڏڪ {tձ%'v[Ul믿˗㭷 ݪ[*J"  FDXhm(#;yoIǗt_x""Jd &a.)l wCZu^\ПpLk4p-ʪcw9hT5A zbC-rf߻sN?\woQvqOZY;+W]uUv\O< 2jF{s N )$ajɌޣY~y{Nhp=;h t A_9M쳃Á qZ í`DQdd}e֯GetimniLoȀ~CT;e#p3!oDkKg(g~:v8>)aKZ5Ds#~7M$ `d܀{ L=zρA}NW1U:*]zRO=p踎ܟ3 kǰ >;x4F:so"lh ŧ4ꭽ^JQ4ٖvqeEsbYg^?rA@hBX0ECWMh01p "胾 [? \N*EμݙgmC|z^;:;v/A %66p؝RA@@{5+R'A@:N͓A@833~gfKA@|Dj$@egC+R'A@:igg,9  aێA@8de㰔A@P:\%wֺuhHf aA@Nɐw6\ˬ['M$ Jv t~_Gلl% G@?r&     Xș       @   MA@A@A d   !a!$,rSA@A@DXA@A@)  ",   @HDX A@A@A@A@A $",En    B   B"7A@A@A@A@A@A@ !a   ‚Ѐ   DmR*[L pطo;׋:o>w'~IW2A@At 6aaB騯)  m*G6& S5@_kl,_;oOBL0Q  @mB+ F{ÇG*ZR}^*DB/߀pBEVK2A@!,Z-`4$~Ƨկg}cf&{jQ/!v!r”m(1C1 h w끨h8ٛ4e:`4ʩ!v<4T֡-e=zof`W _|*DEEWe"W 5{ZSt:$h  Bs GdA.͗Z&c\zdD6>^X97Agu(Y*Ƌ§W">:#mGHZG!gAdl(OoaApf.nKr׻þlr f=z6d֙{G{Xb[.ҖQImJG^8;QpT:ۅH w`iN!ȞB= aɊ"+7 }|$>ǝHjTJ@Ye9PREj,h e V>萿a-,Ɗ^͑3tƉ?hK]@lHX5…bƥcᯓ}WjQA@8]ta!Dj_2*1k9o7>w$m2J]d9xn#+`BV)g[k8ۚtUdXtS[iYEdJXRP$2nT=R5ۨ޴3մK@t*R2KPۥ >v$ޞԛcx8B3XLY%>*f=غ\`{cO^)ºZzkXL#)L,R=XuYq۬z9da{h~q5:Dz:E'At^XD4Sɳ | ޣQΚ Cobgwf+7⑚+ɁZLa\MXJy֡hAv] * GAYLv!o洠x`1Y,Jr,GδZJ>PVPThGٲ,+?u2]a% 8P\!A@sN',l)ť7:"o51}b TJqSw4Q[PԁCunilj~JՅkl2G`6>U D촁pQ(jV+P+%ʎԹP4޾0<`=DݖB8?μF2B?c67 \ˉEix^1XNj_GoFڂߵk{T$tZrJoGj5(ܤQA_ $IX`)EaN*E9%H(τ&ۓQ<vx\(@ӫ qF)(hyRM03Pqxejlr|mG꽙jGcpq1n]9:Ep>g vd?"[h;vZ~!(PE8zQ U%pzN.tZ  @6 gxVS BfdO[Z]Yƅ6qYTSw ~r EeMʜɴߒw9\_5/2a +1 uHE(#cg0cVE.úCPZzjY^FuG##`V:qbbcw뛄'2a?DNrYu9vANi:dbüx푷v42+)(0,XdG݄-G ff]mɶڑ8b#qcgh+g\<{ sr&d w/#޶q@6tX|×uH"݁pն)Eeb[J5o%>{wW!ظI3 _jBA}>`oM3A@sN',G$!Ѱ^ҭC7gRG?s_TnD/`V"%/Ta 4:)03k4Ɉ]OSL!=i!tf Փ797죦#o& (5 =ispg*Țsv:Eh쉊`!$͝.FOQ bu[jnN)eNNmSG[J4]\Ÿ}EBA1GkK>P.FƏDꂅ>U9a=۔0,I;;iG1ZvfHuU#JQr.梄ʉ(gYruPvպz Q7J:U+Ǩx\wH$ܕ;ȹ  p."鄅!m,jy) i٢x<]IzT1!ߕ3CuT4fQ•C6$:T;M:pݦuX 89{rR刿bQ"W&̚msIO4+iFvJ[DEsu}8Sz|ZU3Dv MݢzvcڜB2N=[h47m*XV#L/"oY)s`$ ՛JJf0@ U*5'i?\oX*@Pf.8y㽫!O:WkB _^.[Z~Wkmt_R 㲱|^OH WA@fwƹ?r>ʦb4*g g1A6Z4'KRNjI=m,șY4d#t\z 2[Kr}qS~GưX0Tu) hK8AQNdZ^؁/Ma! ^t1:{45%3uSe wqo L- 戌Hpn&@dƎ],<=vW5ψ&!zǽ~Zx),-.*A"NE9O %GTq Zي-NYU_YWO"+t˰ $Xw'0N7 J>`/~wzxAa#Ոt'fSM"(0 d9+C |LA'gqH5m(]8jA]Zqc Vlݥ3M6^zYM8鈡airRGfWm\`f㠣 Si$MԝE4HPBSpg|;? ҥaoi ﯃%np ֥ 2d.]!5izGS%G~mX&6,J])XrJۜ`Vڅ5 1♠־w(u[[DgJ'2"ծE*Ӟ+<@#=)3X ױ,E XLH1Q:g2J8D-4#GA@A\@hCw>N-Jq|xU$B+@}'t] =%Z TRQ?sVܵ E3.2X,_N`K-%i6GͫJPw6]|OE0: xBUaWL/D\_)(8!]ڴ:@8'=F3tu/+9]ܟbDJP}gs^ΚFE_6h.Z$ Q:drkM(`;@VCW4L~$(-)cM7dp_/OI9.LgДao|vcPܒrU1FVEMT`'qSil&]Emt8\zBC%֭Tw[_DJ}A@A@ VtU=ۜqou.Jࡇ!?,cg[ =m Kh X5xA]`)s w 2VZP|Lfn$O_uo>]t~j g*(R#G[݋k:?Om &gR(t,{vquhZKaڵ1lw6\r(FV}jO(X '3ooঝbfc:﫷NJ n'/@[LѯwBN|`X9_t؉ z2k1R,Q.`Gc45p@[ Y-Q7ˑ  pn!pF,ð3?Wd z=N[3pBL} hN1!lYn6w#0ϽaXU/Zbp3s>)(yK2]PPqw{T]3CPME>ml #nt2^j+_ODjlgލ+leHjT`e!z^^$ڙ Cв2^b`H\U :EPh\  k+ ZGI{C#p.,Fuw\\jn*}K[ݔtU> Æ㜵?A9TBi<h+`*tYT\e8xjհu [y  @Á3RkAF@6@`P sa\i#oEHODV" |h:žё #%u <A@=ڦt]   p"ж   g-m[Y8kᐆ    &rA@A@ DXC.A@A@AD@ 9   @m3pB.s UVa٧ѯ)-O A@A@! ‚Ё p:¿ۿbLA@A@85s-Gt -A@AcacRDP8K;V% 8jH=pV X,M*baN"(2 A@A@d~ah,ȅ; 07T"a{o{됗ջ&:}/"v9,1{徐v:`Tt;%Gskvz Q g(ߟ6D\69[R9 ފMgňw8Za-47wCS ȼ&RpnmljsȺdGKJ^4yHI懒Oǹ}ڳ畅LLh~[f.2E=jF!475jqX>/>zP|ߋy}<(_X<%0K; "-̤e|TZ},QV&˩GQLY .߀5zLXrM2K7;\ ɹNZoKh<û }f$YۤGe!{ Z8Rt~f[*vN# ؝ʴnOjZyǔ pc9R8aZr#*S@`߻T{ccaWy5hQK1uƎ0 F}Rژ<ܰb( jĜMІZQ סƭ IFذayt={4A+c/JdU~%(ؐʞFhe+&-G6 <Lz񽂵W]ZPAi=zHhb3bC1G>+o]h6S-Vp- 4NѱfVM3:ɦ`A3`+YzU5t߱R| eJ]T_e] 댔 :FMMC.WHԟ8?zl}t 3y)i(LEJ1;_؞eKKCo[}olXb:vc?>j34YrzEANJ^][ﱤBfAJR!Q9șs__G%m718Z/$TUT}5~fRÅIale&bMw#R~F2q>f_f3PSoHeaZie'տQFHU7 W%bw1@3wqڜ\T҆% 2|u'Dx}NW^?VRL5* jfbE_,9;_F:s JO8fmJ#}jfۆN6zZV{2>ywq3 |"-~!F1:xC'{PBLk.ppDT防)l[ .1!i&21NS)Bz{o|~ڣ5)GE M E|KJ@IDATQ"G+dQrw(nB-k3MfOֱXF :~䯯Fْ<26,}2d'YBXJG+j6`}^70=M]fE 6~s*R-Džesٷu,/@B8B2ޞYɈvQȚ*v7ReJOk{2+O=?/|\ds!aV&':r93:CD/i+Q?$Twhu5\4m3M`:\~1V@lo| ɳRWaEWˍ̏\d_bG  i/YY<ŌtRcxڢc]Xܕ8U.?R9(Yzۨ`eP;Wo Lk Í.x*dQ29xT!wz$ԉ{\.hP V}z-ⵯM2{ Q'leӠׄf8q6q1i G0a|אT}jN\T&6Ő2e$r& -НS26%q# 40Sw&Xnss V!{ X$.gPOUgj)(2` d6 4}R:XT -q(._O'QYGC(L-2 VGXlN `oP*NQ Z(|uHKAQa9J5G7 Ofq*l{)[62E/XԿȂ E}6md8W]|0"O7O5`lΤr:35Z,[:PhEQʲmIB swy:֌]\mUZ/98ظU^ p+B ,1rRӯ5"1☣Թe҂ISLEXxv 13rjH W9[^4r:M757 Ɔ"Y(|CO05y^^ F͎ T%z:bN!ށyIV@TTg^z!u~QΏ|ˏg0"8^_50TH)D|m B|jmWɜXxhs3"<Yu|Nx<4?19]˹q$bXۃjïs-!х/Lļv?GUZ+?UkGe:5OX'Q4W#s3uԩ]v s9/И<^ZqwL _&^_=nb;B5 $0Rm<26LF}\ua!ɇW?2#|xkq3QI_J&tp>N{Py o葫*Uu{e0]au:]:ƦrԼRG)Ur3e\O=pMYFJ vaSovcQyj6 J[ɢ3W0d #8EڦLC KwHeTYZFd\OF&=xvmHcEʕ\?1Ǐ `йXY5֊GQf.hoTu7@oT#iF[1vfGbw iR:06NNhN\TM Ȝ5 @e)JT^,6$5E;a$'mǻ8P]| i6U=Iq}¤4#]1GQ$CP׌R}ErꍨY^:mPCлxGi&4O֡c(jÍaR=rT@)OL<m@憷-}/-{{?FI4`˙͔@q9[ކ.NlٯA#{p&?0_%W±[+CŖ փDonPQOP+A0 Ed}c{1bޱC1[`i}~ڽ>;bZFHLMUUSg*rœ4oOnԩ'\3Te`^rlzMg[?*ݾtk6DN`q8U]w=ej-*mS4DNtTV2}cqc^M=o?tW]hA|w@sfɑ w!kmF{K:g]OiiR ѣm-׿nĵty~Ƃ^ὛhLj̎ʸTkeZ^ر%x#? ֑Ɍȼ= I7_z8|=OBl*~Q BcBB.GXKO!+*=ȸ[OU l sDymŪٸd"7ڗN?tS^ 0U:Vs֮oU.]Ϩ?lPJbT6,].g - 1ǭMğS3pWuP)ܯ*(O@yy*!I$B ws|rz\_@qSLx(BN8H# z6c"n:يF;ZU >h\Utk^PDwC5E] mOsg>&tY /6Я1arq|Ȟb?hwm1c}cZI? wzѥe5ԃmT[x;\y 6#o\ keDZ_ii4Nx1fdDZFyjr}2~^)n"kIca4;@ӖX-ciak1#G+ު -D+8d N)DX 83~m6Q[{~'-,H3:14n,0:уŒQ)z=w^7c.C+[5#Q'UXt.]MûrA$T0G U2h7^+vK.P;;]a7U!W* 2 {pmZU{-3Ԯ•Y0Ca^N/J%Í7(կ6]KRӨ9 4gV*_4U;#ů^N7g` , Z6NrZ%؆dռnЦ$k৩[5R݉j k@2˙%[)볳Nl}+&Tԍ(@ ׾ji A U~54u3m7Pz gyikPlE3L5TxXFD%}}MSC4y^d-wyGBꆢT]K["phY僚9drr(lD (_SYMfzr8Dϟ14ƞO%R]8.^\zqY2O}jT{A?h+լaߋqXHu<46[+O5XY$;?zpzcr@-4T\>׃,*? G2ZT;tj%coÎ3bhY׎doG#b\?}ڴL QH o N!!h%DfhL4p * $IA@V:O_tQՅ5hG]r;_;kEQՊBX67nN z @P^^DvU6g(s<X}ô|i?TԾiGv WXƘj.TP+=d%Pi޹ VKkA@A"pzՐI ,/p   ,N@k 6h‚AQLKeوZnFA@A@iN@ S4tܼF$ ^ފFߐeG/#s\ing\,G/rt{-;1ьNnqGj'Vq{ wsmM7I-!h a῞E|>3uYZyMO>"$p$=K6"]ڲnӎ&1]0kHL 0&`Le-Wg逗H:e|0CM{l@ؑW̠ST:ÞI:V86R'dH u:#i{R 0&`L t=nYhI 8}@>\(64E'HMTvI+ tN q/tBYIGO=p+Ӑ~⟆>Bi>ӂoKtNCD `L 0&x ta˂L6?()G13y+L N"]hYJAJؑLjO 0&`L 0Vz+,:|iGw/7_>[`*2AK8{RDD|L 0&`L 4KR5: pYB'am%t,IAp8bW1^'8-)Ĺ Rg?߁`kk 镔ԠwQ&+]k4HY򖑑77;aL 0&@gzD }>MD Z6! _"UeV)5wL 0&`L @Wz f D 7},av3զs>`L 0&`-xpz28}H˙`L 0&h3.R0&`L 0&!XY `L 0&xK* o oq/g9a^m} m&.v}h;?=fDz|`2Voeg)w[t3_U7 Wz+E3S)&HbKj2#æ}UyAԮ]H)1hK"6* +mmoĴ6\`Voe=R#+ aj]- bUN>UsjKZ-\ƪbMu{m՝0?ޛ4$%ȼUb,sXQh%1x ~k,3x hqPi@(dAjG)N\ToRo9Ԧ+&'Vز:ʟ)Ά'aj[ " 705Jl#nGixm{)2 n \<aN +qU0!t8, Pw_/k1Zo?Cj,s3l|1d S<t/C&/Z\ڟJ"͖d Sp31cw>KҮb|Ē5(Raxu$NSO X\sSlx#T/g w{/ᅾ>c*ROBre{\$bNL 0&"n`L~vO:zwTĨ(PO5whRyVLM1I7P8{KVi"Pr11pIfnJK([!Gs4JSRnEXe)4r^{s:h*ݐ)>#b IQlΦDRTʣ=j"]BeފS#Ʀi6 s58Pj01I5v!T"zF/BrA“B~73 ]GeYVIrAkV+`RIՉ%0nzFRR^}'!KR-dLFeCq%ZĮpLd W_u`L @- -b&/js@aHKy2T6oKC9f!: ՛VY]TV`ȝGr!cߚ:( Ò0y45F^[d0G7Lfy"Y'w{Fk?k6qf81^=zO)&oHŒ4Ӣ8{N0ŷadoY2B~-B811"鼽I ĺYCI~*z>iHi;:WPQM1*o!I`>:m x&.kꐉIr+dEI"]GDORKֆ/lII +ً3xFH'0&@XYh g3&9U41pfZȮ o m 0W kJݮU6 vSִcn9:EA4pFM.R?Y(=?VOJ#y{&՘x;ᅿᖥ>E a3WU('y9E8 [@Y -={!,"z!PxF x慤ap$w}jW ~Ϻxeo .B#Ϲ$p,Hq0d`lj 0&Z,cLr,ɏ΀ ޠNV(m0Tegp@ |0{i[&S#kX2vu4mN/E2cKJ9) " g='m|5&L3y?r Tj"lFF{B~*9nVFaDX,,):ܼs`O q[0pd@%LĐEZ+]ꊼYx UChG߆?`Li,)A+p聞⧿RM jLL?z$9*&?Y9(V0( >:-nPɻa!N†XBha{j$VdO#,"u{vK%ZI \4v٬5N!B8Y "28 A>{F⇗1;pNJlj,{iKJnt@o,98XKްյY7ݝ䰒 I\@Y?E;Bŗl!c1PX!81&@k=PWq&ujѬB~G{0|]`${G bZq `KVh9\X. `pyuVrVj{*h2&,ؑB@ъzZy?rq :X4 v. rq GC-j-&c)B#jC JWZ솤^AHm,x3P{_8 -?%qZ{ kI'JxgC|%)nU"e""Q\=aY%Hx t@?PoT_.Y*~>`=?DC_U tH}6MvPE9JnG+Ƣ$GO&O941|.<(T8]4 }ұOOamJ]bSbsv=Pk&.>Jr뻘b o WiµG%ZFPsgW7Mq7~6eؕ~#-܇oHFr8vEsY!|>ʐ-+!.`_ðM2G/ehZvW۵y?Vo"6EmlܤFM%!@"pbLN}.`-+τMplH*rG&VH'xfb\V#*9Qb{`u%s"a+%G '4V"a^d՘c8{19*FX_ofy(}d |tX?zơϑU9a[t@ǵ1&!$t4vM|>p$>H [[[ڄ  꾾ʦFҠddddn(M+=ãBM:ښam-T0wC5ГB'x`ݴmġ8;([d[IGqFR;-ɨhmKFeiUi`L .u1N߹valD@{d[ޗq,,ǗI7S |Ѥ()] @biIAݟD#}jK̒{?y> _fcc'` F̎pJoL M: vM<4S|*` :'D`EAHatDH{G] FK}TifL 0@PȭhpK7ɱ__ʞb)xc!)Jtw"J^W2ܐI鍐 8 wG(Hu1=t v/="@ځ|6IW|L91'DD~D[0 2!~gLǜp?UX}0KzICeO ݁`L 0$%@.c^Oǟ_X)y;{woRoO%*lHuq)mDZ!~CjE(C q|M Gz ?#qG-0& s`LX{b'XGQ52w'Z`:ࡸ &Ld%|@.s!CWPbPJ!Lz (HPΞoM[C;i)KO/NdT8LROck;++]!QoZY xz| ɒQvjeNCaL +qr+zp(sL 0& ":;|ȡ A7h+*ĒO+vIڋ)}6=D&SĬ_I&oƒvV]R6aG1x";S! D ]4vdkg6d%R#NjH^rpl*urJ>b-l8€`b@MGֽ+קbpK?+mK#pnڻN|`L 0&$uj[iUdpWg7DJK%HsQF5UQ"z @s^S%] 0&x teY"uH%&R`sSM.[RDZWk1&`L 0@,t:R F O?PG>`L 0& Y,#lJ8 0&`L 0vx8, |xn`L 0&@XYh 0&`L 0GHL!&PC+v7dW߻ڸ!Qe7[=`L N4 & O¼UC鼔ܣaa[sXaGP_:ULX JR=6ʻ3_ޛ6><穏0*|(*'Fd/3&=%pwih9`*I>ͯLJx}֧IhjP7+Shij<> s߷Qb.UZ*Tܨn/˯MiؓVz]ڗp]yݓfF]{=w`m#L s8GLQ"h=P- bUN>Usj:/`65"aĈ>u\ptr%SǼ\_{/e!QM#>P墥؊i6 E˸q;6ƣPzbWk#,NZceWQĩNp%|}2&@`erL m>NRJrH4+ vE&.V'6Æ ӣ9 (:="d6 o]S|ɒegd}ʼnw B4,EWJ,(7= U?EY(;X!tS@b|$edžrZF*L=<%%ưyb(RO=j&fEhUq bOB2ĊcvM /e~mf/`L,]!Ӯ^4N%מN4xvJ+m)fΨs5+N)'c&y.a9 /.VteR@bᖤĆ$LmL@>yL."~ݣ ZDӤzS*|<E+qD܋\veMqy9_-T^%Yޚ/c 6Z藲򎞂Ǟ2)`UXQf#phXtDxbGL[7Cr127\́_p-Y_7zq 0&VT 0(Z ,'܆=$7M./v5b3) &ӤC2fd#&M@s!JkdDXR |} VsdE\p$%%55{UjyGIQ"cDOm {^A@qEAJ R6Zw$E!`1oqXa&V-#K21$nI9b'j][jWI䖓 HdAhoH()؝bYq !UvC"<B6m0L 0&"zKw- &3*cFߞ_: pmkjQrC_R;]+]@POLspIܡj=&9`ph4-h΋ )s倞d34GgeE\GlF\V}}e?97WR:K4.XE,̌GyK*l IQY.]Agmi.[(Z ^b] {fՎ],AęĞ4/RXDnD&=x;3Hs[~D = E0OΦ#[*P61 0&:@[tBKMl E~yE{7!Z{bٟGl۫ , GxW@kpW۵y?Vo]Z nGJ6ZcBl82zsbL9Lh+=HW#$&^-!|)9าV$kY;݃?b( =b|O*ƻݩ ˌqIlT(dElNDB{$}7b6Q$<9W%<Y ^8c{X ݔ:8 ӆb-:,4%k+[ybF`bRF>ӧ۽G<pior7{nw${q6Ъxy>;=y k9 NYiw12y{[dRD ~@2|k4 XZXer0w>_Z蜶ou3ETfMk%IXT5HWkOp1s$o@olʢqq@Ăؾ鍁xo U"Y/6ͷ1`LC +Cn]g);(!Hxia|?[\p_^)jWr.E FӺ{?y~#z¼MSOpY.t [[[P:-E}}}kF{{݁_RNǼeddͭc:^R\n,b&yS5UZk¢QmMJw8Vz.-) [zuZY`:c1hBBGP[!Nϥh%8dx8&F[T 6lWL?EI䩏bG8LxsH@ TAJB Y:7cәj< ŤDcB}7.BmO{nx n/X ,MBq,_ `L 0&@ڏJzqznH2&`η,t^J%"0&`L t1]&qaq`L 0&#OG`L 0&``eq.`L 0&y,<`L 0&h+ s\&`L 0&`e'@&P^w#eK}T^lQ`L 0Go(|L$:! J3AϢA4l1;߾$n`~86ED(GCaY/gL 0&hC8L < ǎDWI;3Uai\Om(1āU">M۠}yvR0Ѵ t#*5'&`?V n܊ 0{E jW0IV {W"#Ƒ5b1s֗ $gIȹitK"04V' 6RaŤVZ4"bǼ_1b)a ! ;JBOxx{>z%KY,6$cFF>b 8'S|~F=h,! `L @;?dL @͝m{Jabb}Kk]յ =CH-풴&Pc8;];X7) 'EWD )i&<*g@ HS!RY \:}¡ H'w7S1_]FoW?d,+ %~cL 0 }0&.b|M쭑d$0 gLwiU~?˻ >$m0*xɭNYoLn*dGᲤOݖ 1.Te* |х?"Z%KT[1/fwşL 0&+ w3&p/dM6#h8d) i8q*c~ewϟ~)$H~#q,T,wК c4.f:ܻy CnZ5USBxB)"28(>FP+_1&hpmjȕ`m) 0W{;HQ%at%.SS)(2r^GJ~ھihO: 6h r@Oe*5,,20tufb\"pt v>nuL 0& ز`@L t~!H\:xɆicSg.-wô7~DP(0:[IlVcQN,?/AXVEJ|^X756Y))Cʊ.KI rkfTĕsGeb '"z|OܢL!w乑{VZ)Hm`SV] IԄ<`,Vٯ tM9exd$c%ǫ5.>F/{lQ6Bb{ExoYC쭰qnG" mc*%gO$+2me7RLLHyy̜  N^q0)Y}Z},'?e,+(|Ëh8obL 0&&5Ӑ0@ //4Yx%%%5K$JuGedd͍|9='@s X* *?~ZWH$mHeO&` V@*L TKŁ}4z 9߁E7 N=MA{1:U}?4p>}[ENK㢔oNAKɳ?'i!>9cދ^ d߾RԱu$Ә`L 0&@"pʂPZמO%z$'B:h8)yVۤP ;H7? BfB*X'rqJ*4,;7I/2w85pO1@Z3r2 0&`L t%V**ľ=~oʚ˫sǁxy4MY֑9fgpt.p?1QCbCgPwKO;xFgh~rdPA!OP% k%mn0Z'WdL 0&`O;5˗/o)""k4LrJɿHyldYp:/?Sg ;' y:GֽL䑵Vܾt3lAp1Mŕ ?0XWq $@ElK6Ts0)QG|`L 0&+v+ wDyŠrnM7ِ DOWD"˖ M^.i^8b(Kǩd^:44)?6- 2 EHq%;A#g 6 Y̛}r׶+&`L 0&p 42mB!_Ҳ{v ;uǸÑsi\X 48ǜqc?_m1W`(]gt\E g; 67m.O_A"}ʸvcvG_W2u)«v%jv.EGؑ>gMv]aL 0&`@/"*+W58jEYkRwV#f'k8vÝxR ҡ)LI2M2rS/m=.n}@_dȻtI N#gqi7a#n#o6N~3!X{do ;7Wv^v]G&aL 0&`@S%C7ie?n;󑔼^ Y\J@%]5O6ZFh/Y iG+td;+> xyߐbr d(Α('V]0G@MM-v]Q_FѕƸaYiTE_mmcԜ/`L I VUۯU23o4([ziܦYMc]L'8%BikRN>P`kkK'镔@~\⟺Th`ooߠfdddnDৣ"$v?7U"fYlH5x7<:9:B o`g#Y!4 c܍7rn>+2׫m/} O&`wM]q"PZ=54UV_%A/JwЉrO& dM8zRoj`&>˩w/(/2( :iŧ a('9,m2!>*߬s˿ieVy&`".eAtϔv=7bL!P(-91^=RȰ4Dpd*iWGRcۏ7KT `K#me3 eCBF/^ ל`L @`m"p G( *ӀBnX!EdQnA~.v.չS~'xڠ0Ƴ$as9VL|T'k),9 XJvR/ +o`JDBk'vⳋdu ,l:ohO 5„# ՈU ]gqՂ^dqA%L 0&<`]/ՙOHBTCu 6s) w!)eJG0a@CE*}Q f`b@'W.`ZO1D86F.-iD*'+Hg$cjbTa^P]]")Jh Yr*DK5Sܻ#~e.Kч&T'&~e,s}I 0&@ClYhȄsTq iΝv.FBr~{+,@ֶ=4G!,-E`P|A{cgb.;dD džao!40Y)&Ws b w2GT瀌 2|uϪd,bMUs[/ ;BUSX ^]oc`L 0`e`@wklVL Hg@>14Züp\t"\pwqKxZF(ך"\\/(UZ)p4 Gi$x)& 5@:&#@Ӵ;WVzb5,4jrqrԬEE9-ܗ*K1u)>ýo})Au$&`M`eI4\@g0 ;@b TcN4Ywr)G5&p ʧې_+ wCFI R__6=) [Ijdb xR\}!0t,?xseL 0&`L- 4 0N'`97Vq> 37!tbXQE^9f| Y]J?!`BBݧ /͞܌P'%f ) 2OerwenQlb&JWbKFJdZ\PUޠ+QS7_4B\`,B׸"?@^Fg V 7cL 0I Pn H`kkK'镔Ԁ(IS5 i6)##nn--vsw% $s X*;`龭bvF XL S(/-ViSKVwWaG[JtCw~M=3&Cݐ+3]ڻdX[L\bL$ &)ϒ7q h\;VQk~$[ڑI!6L 0&j aQ^ceJ,ٗ~VWc`L^(d%:LoNifw{1>`L>|K|k]Dn#2 Se5&RSAO+b/Y!k`0*Nٍ%_\*DR 09gb"* l[p>1 /AJFb%b8*Hl}PdcLR&-qf7؍\D0!?z 'TReH/ qMpv{X]М`L 0& e, +#kS`qNdRŘɮ0Fbs=ڽ{o=<Ww@|Az;:%C+`|iZ^4>,T*s#!$gGVtz~f!G,"m6^@\'`L 0&xPtAeA,-H ^6PW|L91'DD~D[0ܑWCPk[wU"Rp2ʾpoIQZ&ڪQ" {Iauy~=%؞+>b7 Xai"$mc$/h.&|D.HϏ?7 0&`L <&tBp7dKpީ8wR#j">GuJ,,%j4?6}rX7QBTAj䡝$ qj$3]d@5׸xL 9X<6`L 0&@%b%EAɛD3^_aB]j[U~Cp S jR$STD8QѭdјG=f慅!l1V!)Hχd.8"lV}QGbPF! 0&`L ta,k%*V5r Y")Ali*JQG]ŴB9/8|:$`%R,ɣ)hb\@Anzk%d:ndat' Q\%q1# n18E(RbV8HmSN⟗Hd`L 0&4wCmEEHJ!!pġ',,<\ /9+G-fOMX~ *㿗;۰yyTo FGk[댫I`mIwecrrÐma BjnKzA|z6ld `Wj#'`L 0&LZ?.*dcbUJ]XCECuEUq]Rކ`EI1Y (vAl?tk\r6)4E //`LԠ=OW*h4Ҡi3222v܀ 0&`]@[Ił&~>PY\q fkTh3s3& jwQJm{ŴAlHػeL 0JU*P 0!Pt1}FlafqœA/h O¼ fA-'⩃wp&r,6bLlÃS~+Ytްl!Mu:.NbZ.k{V0c'`L 0"3`L(?nۊɨLZě^퓗&)B״+ZyQ' *aŤ(Mw`KFy [e 3wՌ ˤUwL~Jb)bɥؕK96$ 0&#BcT8 0N$P{7aR- bUN>Usja)镶Xth/'jDU!qtW.Jܜ\'~ }4WOPCߴܯ_񗪈e]Fuܢ@ќ֏IH/EgZF`aa1>=Z2: CY8_447Fv]2/`L,4D|;+i3(dAjG)N\TIܠr*͐D[.lu|~1U,`O:)ѱRNOk0(:=آ9lSmio; q{&¥:XB?@Ԗ,DR)~k~O?Ҝ*yl7OnO 6 705Ul#=[ r~ȒA /RJ7( Ri/E\Pz!68]-fd-$yw`+1GV0 XlR#hpRG g:Ԙ=? œ:軓")1٦By 0& ڹ$]= |&nZan19BG肦i|C2fdUR+>sS?*J.Se*&sE\ImG"G^NK+>./O.~֌չoi3ƺM;J?nN@ڂ?"9vD"Qnn 7%'s+[ /'+įOVWLu'/YI tw<۫xM߭St2iu /a\sWa z_<(ߋFjQKeL 0:XYoltǫ7JsdT:Q!u19'BYAr zN%M%e|bsA:EAyaB.iW)2,Pv.sN `gT7[T60LR@c0ڡH*%)˔d d38TiT ;/AXiyXc'V=h,, * e(u'yoF$֘kk,D/) ԩ6Kfen:EA4LL"ZVPrӪU^ vgZࢪ[kLBP5zo`_-|)+$I %Q$Hb*^SR`fWAMR@ DSRNJo}̋|޿9>k>b/)-d^x틘ܧξ Nͨcm"@@ ;t$荫VuW,8_W5hI; E@nFŎCUoX&ˁt7j}L :+󯯆Dw}/ ř"?W/0߹ig)tK<3EݱlVF}gR>R8oe|"^$~ܨZv&dKxT.':}3,9j`atݒKdu'1 -Y4q8{2tGW. JD}Z׸1\Vk+zztںv>nڠ}6@z@L7 ߗP|ZdD0bØvB,4i+ΟQ$_J;Y;޹L СJ'/bu1(^x G菳i~QU&NKLZ[Qw+p +o[;1@ф#L"Jp0ۊs1CvKÈ ̄wodveb3 }O/8ɴJ 0dI@BLzU (02߀%1Ssv.VH d Zn˅簙nŽm^LUrӮ@xz=FtVUぎҟD)z!l+4n~L0'Ӫ = DP\Ej>{󍕸Ef$,>^y#0fP㗰J?lr0<5 )rѶ|e#e:-D3F*~]9_M!&mڣ?zEc ["@; uJdT4* Z?/P34o=7IxfBxQq/5U/=7)L'*/#O .3=hwE,ή<_723F[ Ƭ0ltK8,۾Э;l҉Mmu"ϴ $@6BYQ!<{*^BΎ|쥾|䏄MQ!O^F৒އ{TbwO<OtT*:&'NK.7]LjyO#(%AА9p6']lLq`3-Y;̎BelmWLwX<իer! #%moKs ȘOFda:B>ܶ!/z6_xh-i(.@5ldQCZ>X6M^, /a$*}Rr5jxbهye˛6Fݴ!@cPG06ޚ1#[+F:o+XMKQ~k`^+ט0t~IZW*228^WCNӗ!qd$v$SaV6BcSo`oObި*/C߄  IWrɂMN e`niЉ+hp^95fZe^l׈ %if)b*~}^WL>wn[@6GcuK9DO>f  k;wQbDA8DAa$r՗EŘx(iDו8Ѵa/(Ҷ")JQ­xnGEñ,1x`|Ehp䂉کR</ J   B| ʥ"$  `%Fr|† |Vcr?tp(`ND@@ 4Psw7ǪElGo7nB&?y$@>ȣlɤ~y*-0 nSX2aK8 # /E=7c] =Zꗔބ|} /ug/HT| ƃzR=Z(v&<އ3+4Uc' \ac|/(o&)UCB @@ j"%y(#䌣trSn鼃EfNafےp;P;S(JLAp/.]ќIvWS҃ܰj!h9(F3F{'%\W >4 Dlݵ kN?(>Pc5q0 "A#q#e(EvrB.ǓȜ-B6 Q%{(.pSX|dhx,X4* 0$r>󹤴zH@@ mn/8eaIdΤ ""x뵘3~3|,7`Բm~L2 RtH"EcO"a| L#"V"t-Ӌɡ<>#_'69A*2)RbgK#0\BQ@IDAT5 @ M=1g#N1rk0z+ @@ p{#p[( 2Dz:ZW(LYxFNu|l82Lgҍ4oB}FMAyO!&ˠ,tN*W5QQG`}nQ .[g e@U @@ p H3@eCtU\ Gh[$J\0'zCJR)?LOcGjm) rS-gO@@ @}hp:1L_R N:iLwW;K)@@ p p[,EEɷu=V0'@@ p3vn5 @@ @@ P,ԀDD@@  ,q @@ D@( 6a@@ P@@ l" H@@ @@( b @@ 6ʂMXD@@ @@ 1@@ _pɺ}T&w_E@@ hxe7yGPPX^h*Sxxå4C=EiXj89xտwM mXY \ih@@  @+ ;/ p"AjqK>DT w5/ڧ~R i[4*l̙H#jf^DX/oӎS^\ߢ"@@ FqpVGR vQpUhO" A0,( G.oJ͞"c.!q^/7h0D΀\lIÑRDt'EF8:%C3< :%da NäG Bb^yIԘOD)O :-=ʑvR1) q#C[@뀂8YñA/6gk./ò$'i $+f b7AyA &Ɓ؀$hPPqB1X-+PFT\*|_ E_/i\H*7-J(z@ B"SPEHZJgwPi99:3ݕRvPScKn^2~+C4a ^8~ !TdaLh,m]9Җ!KY" B` y0(0uCBF(#kTĽ@@ 7C2+ٺ ǎ׸I ER?iHD f`43~do) AitubwkB+>TdR,K~T׹3FRyc>S^)_9-K0bT2&ӂ@nP nZ O'cnzZ 2n6>ƌn B7y7/L\çs֔q@vAxS$, F3RTG [ ŏ&^]8EbT^W~l(#XIHC@fNՆ^AEPD @@ wl\8YtyPU!;_i͗pyk8ARH$u_.B h?gk&&Tz!trЗWx?RXTKp6գ]+˳hi#XL0 ?Ѕpxz9?5^<$p{9aMv(SXڃ;:ٔ2AS+A!-r<B6`dM5/ \Il_Ivoڜ 'kTB3=< VՁ(dKfK4o?9%H8Vb<:6_ *?SrSh[0>[yOfBP4 #L]@މ"av @;ІUPȟ8wC23)_‰7G?#}S]y@@ n lXܮ)D* L GBEY(--ԟSy]E9F#,(E枉.IhrTKG#' VM0dz L0?jk焌yc败|,Gl*&k'r (N#ZOݶ[`(LvG62#MyTb˻)?v^;F;-_yt7٣NrxGc"#(b'ÛR}˧P%o:=aPbĘ/@@ p' ; %ڐ{Ѭ+#,WV"T5ۖmR^"9c\ [)M+1$+CI_%JM{!Rm NMSp@w4=ၘ%SD4$0{#Ct2*лa`dڀ5R]h:>[5o%4oBw*lZ9a놺 &.PxLng2f̋+lZR")-WJC[;į@@ WjV^s%_3[XS 5N'o9h-@oCGxъT7&FZgFyYV_ʖuo"aF;0ɿQ7unƭ OFVGᘲUcC'.]J%o(r'N@.]=(`/Y @@ %4B}E]َ*WaS îG:ۺ:rXBfm,q/@@ phx[FQ@@ @@ pe:@E@@ wBYzYQ @@ pe:@E@@ wBYzYQ @@ pe:@E@@ wBYzYQ @@ pe:@Ew p@.Ng P?ᛂm7q7ٮ7||W߯\]xyp כ쑛^\W$ #iK8z  lڜlZ?<z ҊnNnntȘ;x%:`۲왜xZ>ؚS\]a#۶"}{ظb"^ul)xZC gmݬv[[ƔùF[֪ ;FE9/8mHn:vsle NN5eISDʇ1#;__^n7c7[}|&R^ tqaY'4kB~0l͋yIv7qÂyKaC=ri3ǰb H r*j;)b8Y@qV<3r+\ű˕h[#ΖLZq|"g3*fuby?J1FP/^>-xfkXek3n8Uuŗd\S1ˋ9 /5iF+G~Б]Mi@qxg} 4 ?[[TB7eXB;EK˅g:Ŧػ2]Frll߀u;R=pA)*ҴupPQS`TWK]-K豗,x, z 񇩏Vʅ&/ b~>&AΊ()xT&#v"=<  "?oOx=SB74^ 8?Ptkad[[G4vki[>6+u2+s]`=ֿFDM( g~ п?kXWw lcBskB: 3h7, 3R;_jo84hN1}JcU]VÃU@T yk4pHib]l)𽖅fQ4~ ?d@v}ԃkqhdV"ХRLJ<1|7. IY`4p<:L/!{ K2'ݛcs 0uЈBSq{>;;Et Dz z h,+;H)llZ{;|^ o=w_DU|>viAeD^3;5HJ; >3X:Ηi[4߱]!G1yk 4DLJHs+wL|L1ȇs<, [h15diiP?Ƽ5s5\'i^X~⁞u/CQF"bV/-G e`iSa%ؼ>>Ü\w9h {wXs5ZEol9J!@m?ZϙeWe$Ȓ}: v^ uW'$ +Jy?6>pdGa(*nRnmO|n:R<ʐԘ8I RBUEDm5-Q~&qq$/ BscSZ[lE/d-:2dt)JH_zN6FP>B~;GJR.$kUWk(3q+ױC=p4,Z<3\QdVp_tm3G5U*T_[LN~ħ3}h-  Toʐ2 .) 6,RzD$8êףQ|W9z,ϵqӶ ú=B]^7°i_9:vѸaq`m'dq^xHW]A>+h ^hȅ ;7x0) lV7+39(8m[^p|{]# 4,$%TWϤT?{N&bT%&Eh?s1XWQWc~aL%U?͛Pܟ34v-Ҝ[3|>b}1~=cVt6A'd~|swKْj-sr=߁v 6/[Ν:FnpccFMvqEhO³KT$We\kMh֙y_Gu^pmYKAr+BBsm-)bKӰ`^r8mnVy=Z PGM)PȋR;[m_>^.pαPҢ>;^~1#1/P62p-z2j\ZK8 ~>X8t-:~&4}?IH}Ʌ^$ 漪"a8kńLѷpj.L0##lf>*w8 Nf j/ Lh06,aJs!|ìJ& ph,UDbC/V? aBY7#a>}IYf=VdRS:hA[ U X[yiTeDRq+>RXa$Vƻɛi<[xạ]8CoGi2$s`A~}/aYd傱1PL9awg/ Lq+f:E,$hb9ʏbH@&aNL|xbA2]ɰW'w/?_h|.ފQXmfA4&pNC Tc/9?5YE Mђ#1S?2*5UB*G! {0H} ikSp| NJ$ wU?6ѳ?Ijp~ŸQcFc˶bzѢH1'aq2+#7!_KĤ$b.3ckDHõ6j6#o"!qs/f.v_@cc]W{#l,lz' qna5l,T|y%_#Z B|׿"i^0 dH;PT{sX5 `9/od,xƚă{[( CZm0'a$_|e%!jWȸ!;0*Sf4ќe=%[#;E͋Oڻ,#fe`';iU\Nr*Ku68=sza"9uqgBdcfrZ z7<@ṿW  ɏtz25* =~ gh$ZC-;qhe]iIք"%|pz_<#ቌwIMx q#׶_hD&N3G"̄폧,Bwz`c˹Fb ;يg),zI3OZS`q)0|HXMFfS}O};%@٨5=k47"?/HafLW{14 Of\\`B }:8TVˁ|k%F̈́<&98L&LWycۺަNQ9;9bb.ec{ᕅ m%Eep8mӒ}1[З!qd$vW 7MR$9 )( :MmZL Ǔ~Mf7I+spXRMQ ?_iE٣Ciޮ{Iu/٪irǣwOiۺ.>WuGo5=Ѕ^@4uE{P՗v ]̜ꅧ>۩X y<7F]O 5jb^>a9o獙zV0&m8&zL7[7ΏBF*GRՋy6~pN;ns\P3`eoud˂O)\0lEAl<)Tǚ%keA @|S64 &9jW9&k m\<`h(SX8mJ ?9D8 G&pڒ{a\oD@ga.!q $(H/!Y5X~*iSX麼i`͛\bHn穯Rbm"e4Iz&s^ŷfN9=]EHܓyN5J;S}0#̔+-Ћ=gH0MI/FnB4N<8-2Egl,P N-yٽY-oYW09V O(=[-d&!? EULvQEMšP, r^rW{rBshU|lb\OvӇi3ˊ#'525jbT,&GND̶e$4hMBƳy8 _ &R{?vb6'b3wQ@I{mZpc>@e/9>'ßtRX<Ù=Cxaz(zM>ލg͸@D,fJc3tvO"o].^տ|[UiH| LY#iU<<=SSu)6-z~|9-$DL'l /&Ҭ#Ip^; qq;IcطçUJ¹sc^eG.ļ!d +r~b]hX>ꬑVKܹTq~aGZV)z?+AX12YVuYyhNFu}a50dljrGuޱjpgp (mHD,ODWGpU4䇄-1bs! Qc"Vp !'9yN ?M6h|\(Y1cy\pN[Y)G]th`OM_d^(L%c~Ĺ+r/)#CKxЯ 0fyPtμ3q>qmVϑh޿bSvVd!hδ`: yk5tV1[ppRz(ɉgyʹTr IBP2нiGŞhС 2c{,^r")$ @;v=URpkyA￲G:݂Ԭ|S!\9(>V9&LZƞغ# % V }#\Mg'dxE{dA 8Nz,"zעq z(Ґg H`9 E{uAGvfy,Cr~S{6gDI)^۫{Vn,Iոcrrͥ6bH,HDenREjaA[cHG?槎\5# b($!rB*EG/d.43eAc>LΊle|N6N٭~PTSh|ќDbw`U'9.r jiQ7$7yUdNakQȠ5nװE?|hF9at9[R3F$z@k[cz^ݟ8m.^" 'Z}:d~koA8bdZ#%ㅣ9/H$k +l,3VW鉕[v%`WdJ! Wѐ'd+_?v-" Γ1硄3^*-FKF@;-ـyx*:YFǑ4V6oFo2J(6}2,ܔd6p۾@Z { {LBNHŌɶ͙ݐAJ"h.9"ӹ b"j&:U))ս7Ι)ʮl^V>>gmAN 8O?zKG#66ˣ#WKUoMpXO1YhB;I3o> `)wGκ7?)fgSy[0A,J7-53wrZk恉t*j23[ `:{*CSY{hDonυwf2WF&tշ̣|_wƀe?]3xi>o"1[e c✄ln@fՓK)^_&33y>jLd.c? SaiȎ{k-OVOo/Eɂ(sXE7C0bBtZGI(h, gNfE"fo=u$\ӹr:b;"mƄlłKX-+aŲ4gCVBmu*1vJ\M"բp-J,xl#{t{Ќ ?PrIA_擿Eou^2blfJ ߆uhl) !@i1М?6dp |dTAc ~Ϥu%Zw >Y=m6}{3^2 Cf"<Cω^2c_t*goґ۳^}h }ҨCcl<3s9Yzms<m#kj`f6vM1+'MM5Wb%Br'o~%s1k 2j G 8|h0衸q#2|D>F1{ s7W$TOd0jBɯmmJF%0y,+ J_Hq?R)ݗӉVݸY#c|#`Q4ޜ+i͈pq,L]_I+k:8&b$z#oQB} Ij+|yLOU#WZ}y?1G"6c*ld؞5kN;t ne:i)Sq6FhuPC*dOFV()Ol?<ҥKPъ7;8q]td ރ7=rz03f6ܹ^wSk:UUJJ>7[=sb m1` nJnJ g2{W;:?e oŮSQ0e1^tڈh׉ /ӳrE?ςhטׂUwKc-i5YO( cKW6?#qPИ^5lM/̰F$m$]| *2f501'P7M}V\G |-h^njj} d6_Q“̨8OIiFBBOGo,cF#?Stj\ ;l|Q;홬oSk[cPs | dt \7Y47\9뙃l`c1c{M4Uŗz4sni }߸ 9BV`_un/9?y%8kү/؞GipHcF(s 3ww2w"/3cUyR"F@U'WOor #a!v?nz9t2yb_^dVm@FNmp{ a] S~򫰭$E]KY rPĮ[8x$X7_"{Y1[댠tc(ąG)Jd-`W<23]$Өq_GQ[Zꃏ Y?Fݨy>Ƞ`4jK%(HN0eYX>};ez2?PG>U3i]gm1XKڪfJdt~, :lg\R<baNz 7c/4ueIYGk -0 |@ &ȹ/0lHh*m _8m?P b934 T.}cAFiakhTD>]Zr:dlji5`|3N؂ |Zڵ }QgVVbWR)_?-b΀9ΐ9HEf<"U'G"&~+vSejN$]}џ fcackPLm#sA<=o̙nijB$w=W,Mѓ:Yhs|7[YN_(_ u: 3c6oigf\hBG: GGZJ#׵dDejxBćEurKb2t_Vn捁rv d!He8p ̀hRQP.GpkfiLÃ[)x>d?]j(D(#6 >FЦ!e$^0tD%+h_ p7#@Ss7#xF@4f#pn6:kK_r7$u`嗮S:n9VW $z 25g6H#6- DȜRv'wڒJ]. ~g#V",l VD"c HJ ӀR%@kS (4P-e&#>2JNkXI|߀کҗٹҶTwxn18ۖ 5KlC+ވ $ۣ'8֧9MNY?Bu 2o'kT$"H[ ˢa"S+1`+@@ YPAG̎_ױTWw ^en;FJY68}X`Q],ج}tK eFM&"y&[E%9 ̿KQc|ijRN)W H^ ;شF88["n@@ D~g1 RUJ[`V#!mYc}6, 5g9&:ۜ ǚ3G$M4WEm(D֎(Y]=ûF֮acő튀Pnמ| []b@3F~9[ÂKfv.)[J19N*|" qIG9_|ZhGeV?%U^*|_}V_@@ NC |4WvTp_;2B'GxJ>+IS}/~N #@IDATw|w̠e죂 +3Q9_z\ Ep%Oj 1`Q<W(AH;)G<; J|Q;"^x?o*bE_׈e`疘AroZ \)*;*E1;S\D_  ͵GT-9yë|!L&Ezq@s',^/?K>oO@Txw(6F;_۽wƔ`I`ɀvg,r2GzYE8¿h^+a$h&;ńpz9%\ ~L}^v i&LjElmK.Wх*2*EEq[MuгgKӫ)2O^ĠE3+w;KBx_nVF@@ A?hQ@τ){q$ثa13ou: Ʀra Viw-֬8#'[b',xm,c?~?FR;#4bK~o/W;y MrRyzw*1~{t[5]cE5;n S*уf?;D.ca3z͏8$Dgxj\yF?3+qGRWļv) ?/䜊e!l]0Kw^ƠEeB@!p^p A+gv\CzCnySpu sYvMTS~3)ż!lz*IFߜeHˆX,sȸ%"F`+FpE N|hԷx\[1#- wW=OF9݌9u5G_Z nz0T3nQĬbxn >e0\!Uq{6 9O-C}#"cs$@#ìÂͺJQ.zD1bbB͘ӽrc:͹%}z1y [\_.cN+>))Bb]! M%nxSo?*ϵ~yxg.Or\s^GXRj@uH]sG];ơ;hh\JR v Ҽ qkuד?E\m{$v|.s9PюC1>GAʌ5*w5IDn,?鷺S}w!xfR"tw!﹗Phq.eBҷ+1=bdXԠ7#wyJϳS-BƄۨ'lą5O]1(|}E۫t3Ow]ٵ*/Q  }ט3y6*-zu>5,/eV}@2ަu0Y:׫v>{{yWx]5Fxʱ3Bbj^|+ta6fcj8D4u@;qB@! ."ճT_yP-_~e\]2`gݑ2! >O!w2x/ݍ놤!cR K>/8yUU+'zSy:>[bvau?|MsV_1/~GkpOsx?AL6[iMCe-~)x2 }ch(Kد7<9?l*32)i#"<>HBϡqx_&?[^kg±@B@! .iBJ~xyR*c8n@!]Exyn|OLi(z_Vx5xD2# U 羆e=QiGDZd9S[p= s;ٝki-pi}q"תN[cGTf "M(Q cft!Va1ݮ;FpT&ȝ*lW^!u0 ĬGhsCI?Ba( rҁ2.;mkFf (B@!pyo5T:_o=rXݨ x/*md9/~M>t;0lœG>#λq-i{UY;U4 H-~fB>dGo8ɮ>')+W>XH8`8אAVQf`8 >~uDG3-GzQ2|d$`!((/[L!*21])|~p i $eE߫(׺WC:1 ^ښ?ʅÕAÓ07>B@! ZX8Xļ):wG4'r]Ns6c2k=vfT/c!+7 |N$ɀwy(6>ok;nrHϩMB߰ehqԳp2UtX1P]~59<&+=HE٢W2Jua[fO)_Eɪ|N^]{#G 8=۰yc J+ūwL²ՙ7[K_! B@e` sU#嘳Xu{1o­n'@:xA׶oضz~liO:N 8x&qp(M@8:*3#@>iAgiY:3>ۍg"_Z{?;#j`gjC~1>} )7""đ4y\X 8PMFEZLad`~^W6M&Q٘9j=\" ɑ ! B@eZpFSť%(^[C~ j-HUV81yZv}z6yEV僥ORҧ8y~ O=%+"$Uރkm.q2t?Pj`&+'-ye? LهIOYN$޾>LkʷO=]|ѥZ΂Y 3o :L]A1jLwNPvWs0 Um&΂@&7~ Mg̽?ޅ[TC}fRB@! .?`;CmK \ןiH죛X'}x*-<=-͟ ^~:QO2ªܧJ ߫Ķ9 1160&0-OH"NZHylȁvϽSuSHr{oq5HciXHcO$58*<3x*Vc*-:7`Ҥ6eՅ21mxuxaqWW<+6A K>bs̫xnN3MR^t3B@! %I  ~R(>mjdϴ烿svs˯~~+8`]9-^5FЯNdSp gCAmp /Y! B@'?܂)!ký^ ; _HL}N @ oT?;"g:l4fH'QjVijB@! N,\%B@! BaX)B@! 8[",-A /B@! (荕b ! B@% B@! BaX)B@! 8[",-A /B@! (荕b-h_ ! Ba–@K ;ޭ8N! -"p}EŔ@B&^ q߭Ws~b\B@! 33`^ W~E`׹VB@! @T",D#C@ +wdHr"B@y2gb)B@! hZMB ! B@6O@6B@! BeDXh7 %B@! <- ! B@ae$B@! hDXhX (B@! ZF@qPB@! Bab)B@! hZMB ! B@6Op˖-M :p&;ojOICGtܡɨ>?b;vB'KlK! B@!ph;IEV~ }x㪁cta,`ػ)$/?ވ[w \񔿃g{ž}G~Xm(멝zo9eu}c0f`5s8zg;?"3a&'B@!  a8v-j:abM^y-9<>Ǟ?vݽP݁ߡP;пbO}=܂;֡ (Xp`kҳ&(c8nn7܃؃uۅF73 [! B@!f wC ꧜ ڬ} @cLС}x> {uVl߆cxB_t ]lv9=yă.נ׵vs:% | ߻'͏:* "η:BXCxH! B@!-Y3gԋ#&&tk^]uˎ>~F`! B@6NBm`"?A?Yo9.{4AګF U_pD8z8*\m{PCa!0F2 8AMo cSZ V|sp*shs{K!eW! B@Mf٥k E]o`(Cm/ᡕ?({ GwG^a|xnkYu:]߈BjTA &0w fL5G)\>F}_;*l++ÁA+N! B@e-%<;k) }=z6~4Mw6ºM:S$htd {~=e۰}{@݇j8~=Qf:cbq|W"(4\N ! B@6@Ȃ ښ.<_߶N&u쵷g*w}_{xOW:dTՎÝ}`C,cצ R7,}t_~8yUs%UQ9wrs쩣V:ՙh]_B@!  7@J88HRvAG%K77uѮ>C"((o'>K w*} }ZjUJկ;\GFB G.gq]|pDf5,! B@A տQucK|lAgO}Y}9ւ.]C?K~ oxkG[nPo|P'"se.UovzMM l6[g{O? 7p4)*O[ +YM5Y~_y, ! hdA}Y MqQܔG8R𝺞E+@CΜ® :'i[3MAFi!εR%Yș^s}tp) ! 8Z$, p>HBb%p 2F>=3+GSrEūzhVW?{.(,#x !  3'yB %ϚB .{=a^{a:nn~} vB_5ghL2T 1B@sN@sT"燀_rq;-kSSuɱ;174)\^7''kP*KxN9m^@Š?3{*ү~=T_SNqވ)'G#ǜt!i0Ϗ㸖A^hC3q-~NU8=fnYǛW[D4dǀGB@!b-6CjqP(gcf=?X;ҟL<v&,+ 1zJF #3}ܜ`Op.\4 Kcr3ڙyHezw[<[^J3<݃l0^`YK_lH,ҷu $;B@!B[Rˀ{w]?}qQJl1K7MZf:3p: x&(s:u'%ss(~詇 jdG w)ǿ3|t===O=0ä( nO@l|\iX~3Z6d=E$]k%7&U|yeRG>QֽRHuᆱOr>ƴźv{ Гgyx_jUJ a*v1j\ ̓K/. ~^&r,Dx]o5(y#_nH&ȷ y\a\L_]:{jkEQ85x. !{ ) Qg}n`:'j )) V3+a"r1Ȭqܐ5!1ɎB@a$:`gNe!j aÞ($,[igWq;rBtSl**iFtJOC1ּ$Iˍ;vn?Cl̰ 4C#8JC;Ѓ| 4qxӃyK5`YG +L^z_R<8A9?vl3ټ/vV?! hIV'Ts@7VuݓX#&^^ȕhHXjZjedO}nqm 1=?wUNZPOf`8qW.j吉k/(x#%X;~S?cs JiA^J 0#|\TL胂!쭪)R`!#g>= js_0ΨE! M# B8/!,H|0 si~[(M³?sz?/0u1 b1Fa3`'>[\k/sՀZP)^}5Sږ9fa'qXpHP!)Hqy̠BPBKAA0*߸  ^ɥa{ֿbxZ8al<O|¢! -"ЮEW_.AQ^cwUWscd/~3t~/a\TK9'IZ' O=Ƅ_޵(yV+#K%(JL!Lw|rNC¾P{( t./ZU3أqyW?Oε=G+`PWk)=y}\LHY 77-ߏUsp*~uvL9;gTX0;Zwv/? p?6ycKg_ O2GQ/|`11v>a9i sco(sZc#!  ~^咀+4V">)Sx,]c/SuE-#j֩8!pIj<;i{k+ 8ta bp֫CwuyKw2zx-.7;&qUNUNȓ̞k_!gGebքGNTIt=2^x2Y?vyϽ[ӈ餧ϙiXн\\}~U?oB:h A~=|`24V6(/r]k_\('}@*lvuigpȸ9c|)ʾB@a9Wo +1Lx"1:<~7&2rao#Q=x"ʵ9LY&]LBŹkU^zX[MFi'<{{h=zݺu;X}u:lʩ~k555H;w.|駸ETA-ia9|n8ḱvOpU5mm&2M/o(4yyΊ8kyiwO&^SÝ+>~E-cj2˛FMrVXqMz#B@! @V\ ʥmH0~$iG̩9V }ąYL8@adж<WHFU}dTTqɥ{+(! B@!phʖ$`Př5+DCv;2jʘ彎;2výv6(C6LN! B@!`h]a {`7uv yQ1ʏvЄ[ckg! >$R…@-i[?mB@! @u͐A"B@! @auKB@! B' E$B@! Bu:%U! B@!paᢿEA! B@!:DXhB@!  p"ɠB@! h",wIU! B@\DXodP! B@Z*B@! .z",\H2(B@! Z ]RB@! =+Z=>wVKXV,m;Ɔ>nTW/+}\}⚞ny7#筮AcR[\?j3 ! B@FUX03z_Aދg}jm6&ctIπ13Og2A΂jrF4*ޙYHZ47pYӗ4n7kKYm7`zIIW:ᰝG)T`|($%5a9*M_L|X35 QeTHU9ːҧeUX67wD6?48 ƩMPz#kn \x$K9aE';<~6G[؂%o#ũE3G#{G^u&Q~Ӳt>YI%O ވx6n~)i|(9;\1"X99B,߳\7!g74M_װSiJЈ.Vk5˂z8&Wx[;/,TU8‚G;d:w VGt@(;10?4ԼE"hGYHhSF/rQPJ$ϸFe.wUm* bGFt$i/K#Bi|GvQ6(Ţ{Q"TyJtd _Uy{eTzؑ2BweMP#s|uJ]ASOd#J_t&($LA&1S3Ż~j| ֋yz?wK֩jbZ,}{|ZYMeYІ%߰WK'PWiAeb1j?JU $/;nc̝M=X"2!AZG3OA>$ RUR?F4ڞ ) 3whUGxl][i|2 Nxq6r\d--_ 250 '낎o{%>;e2H 'x=>@Ū<@3l%|Z`jf1ϩΞo_7/ˎPs, (J@,$xxLȚh= PyB|d^fҞ瘲"ObuJF%ODQ?,bz$,K˴|缲8[)E]V9ۊF*7K/˙m@: WW`4XOUbYvOJUۥhr(9vu&juZow'23U "P lCgcv_WSli M|o}L\rY<ڒ2T&w3lBg5rgW{3,L&٫zuB#f@FvQ <>e4ƏDR/%EpuSTa}ҰL$I A^6̌!W"yT:=ژC<+mr=d`=JC ߙ/k7ꅍU9O"itJ Lu~ƌE2 `3ʩ0Q~A-j Vi)& Q!utUˇ=;!)X:70^RxOb^pQ;8!CY+kp^相IŇ v0_),FHi|*T D/{hB} Q?VG<ǠzLϬҰ~2$b{_ӑ`oH8NcnvrÕ(?G~UAZ.)(8V#^m4lvְCn (\ަ9+zA&-D~ÇzyV7ݞGZvҮI*Ck|Whu"b I bz>}I?W327W4>/dZq̋_]{C9f4%-^wP=MEg#]1E%n_9Ggz0Qۡp d`B qܛ`Ҵ+j{X~yP;\U~̆ HP){`MȴkzŶ+d?f%M}rr$KC88z19up22~'rB?ٶ5Zb`JީYf>^w*G iY_ԺS>jd߶a3E;)}+Ñ[  >~/l/l*9&Q9 0yj.Sw !_ @IDAT<+879;^U/|TD*T) hڨx=5:KkTٮ/jm30Q!rN~ތ)==ՅcRvy/b% |~l. V>TxZo%#NRȎ*\ CP =7 Cae5?v2nl >.9&HtXqBm?SPE68ptqxfq!U|T Ҽц&]jb2((g?ћ߫X0) Y67KݜAKE#ofh1j RY iͱbL]zje`;dkd~c@b0ɓഌĺ:.lc  l*a𳠱2I$ }z]VR poͧE$M4?A:UmT =hj젴tVc=*>V8T}PxA=R,']WcGY'4Vv= {cC:kdb#ъI8!8*Q&?fm&54H=|^1*1qiGxUC("%gF;cqa ͢P#P`z?>aY4tކP`CazU~B\eņRJ"f/݀_75rrRl=yLEa_UpiBGr/T zZשhK(_ ;+;)(̃ߞefv -i ՕZ:C[?EvȁiZZQ/jUk,`9\+y̚J!Tza|k0l66T~tm+.hJq6Α9.z4KUٖTe٧Nc<[z>hr*J#&lP AA/k8OUB[)+G!{S 5 Ŭ:}z83t*qLTyЃuД 1°: G<(.6wQ뺂 肂`Yin8}A{qQ~!u;@(l*xdf\ᕒըɑ;U2#RAjy>ၟ%iYʮyO, {lUqQ55Pk@WX)O+AR`(uH $#<=԰UbGf$G52ĦvzqpbЌsb+;IRQ3'2aCpdh*儥OriWz‘ q(x]3^XF W#`=(+;>t#F!ŖzohﺰwYx竹x\&5<ߛ'Q㸺ZK}8PS2聒nHEץ9ͿMjLϱv<0U>mmςYڂ]Z3-hxHjnv؇͇ ̦y"XT;SgjBtA6jl=)PD́ɨ,]CIHȣF#M՟ۧ|.bu~J JHև()ǭ#q_LF%(x_ L ]آ_Ժcub,_yl68WO:ywEW /?yr7Q ثVneZOvJ+a84y)d618Z|^UG0Tl[L>x.3$ui_mwe!țib4>߬WA:Z5Ȝ&`#{d KqY2$#n]U-u6e4Q3ɘ*C)OBY|I6z Pw͢C@ })8d%ߢ5.W9 ;<4n5d$8D[t6N}|>kֵSdZh.z0z 03&= <{x ӆ,┊[ˋ 05:yr[EڑW(\Un7471KFXG@h>itP[qk ;ꦄt}Y2Wuoֿ({ Ʈ)d}~\Euʴi6梄F\MFqx%(pNWΛ멉 whXkZmAjꁐR2F' =߾qS+iǠ{qL[S=QD9e!E烐4d(]It=zrU6H.i㰸Pb+W5J_@ZMAVstF|gʡv(Ae k2K*sm[Mi&a֗<\7g\*xئp@Syom> YEX<mFR9r 7MfgR(܁m5E;Kوeg!uH<͞Jt%yT 8%Ttj#]=P3 W}wN1nUps=:>Ϛ_|2PZ"iM|H<+Q\Z/[d(,8nV/X Oe9Q^`#gCS jKiG͓kX:F9ցsXFMTx<({#@(jfFV8ƭ|U:2~TvL_ʭY 8B"v@]ͻ0r$`AkWBu5ToQÛlG21Nn^8@3fsPǂy#Ac[>fjO+XEވ8o*֪qvo6ǒ |+Dv8y<Җ,fTI1'J J38B4D4yWǐtߺV9"~N.̢dǵ%UPiߨً :|71||عj8;Jmׅ51W5if/X^ɠ1=B8<9}+gg=1BJ&dvJk˺VTdc qVW>o~Qq!ݲűc3!^ xDJkIȤC-<XzK{JaOJE榈ړ%x?pȗNM'PV>uҍЅnXyFuZ>^KA88PfxiDo04b9E'9 +6x 'M)ԽG*xjJvB̖44o'+u.4g&ҔYkBd5̡[l8eWVZI wW8)4\Ymm`abvh@-k )ݪS1e3Tp'5Y-*Hqu%8p,W5~unt eݪL5z'CvhR桩gxV+!,܇j&}z&::T QepLKT*e)YcU\6E{9Y[,japҬI%w Ur͖B׾?b QFfYzwshSIT͎mMW>Id#ؚ0~T:r@C=36oTEg(d0ꐽh0Ʃ6έT6N[.3FR"+ٜ[ϵU@Y;5U3@HB2^58|,ĸևS'Mblٜ蠺'I# +GsǸb6o1) qJ`M"*%I['"\Sxre7_Ll91B+r6>󨸨*MIAF'$P7Z[ 5Slu7?2 Buu"3hj8Ga#flV$O}O'x%oݞ-Ռ[רWz09g|Oo X H;%LTrY8eEj9Dq~/|ΞJ- [ϦwM cG[4Yof \-BvkOJΣ)ʏ~ FvыsmRMULH6?QpDM( S \ #a~]F^U5"]}`Q)\3L;8*eUp|{5UGGtdQ)M)Qi/ph2S*ZfGNzEı\\D.75SöcK9)Mj7RvpB~%%;8SrVJټ5<Wڸem&>a絃8νbGU)e.#*D ԓ34m5;3<'>hm] M չ]5{K;2z(݉%HN4 Odj>jLiCb Շ Cz2bky.,F*LJcD<_4Wi1Zeȋhr`^C$hQW:izfg٧=S&ϲmoXO##uфvh,2o zX=̅|ԸGvc?7J=S⮛7u.Z; Ko=e=P (M}18.\:*,(fL$:M}5h ^UGt]S\ta] CSSΡK蘏 \4>kTr*kFԺ󗱘0Σ3lӜ8,VKg= @pRP<rgVq^{NjZDfVp; GOeW/LK3,ßZ;bWd#.9GEn>:ePהSUokjjhF`6>کsO? 7pNj~$<5{` Kl? !:iSVKM2Z&;+a+-݀cѼr9 i.V\(q7PN+DV{饛/u#V}iJU eq*/o-Hu\#elԉb9AO:pQe\fs/54IV`!p@~PPPQ츎Bnkp_`)Huv2Uiο^H'y1T Ia4ަzYIPPtϢ6tsrvY=ӪNilcgFqηި|*:tW6EYEKPys}8ns%#'6~{*XGP'jU%4/ƼJTۂA! fHuȉK!]JwK*B@\j]j ! B@!papa8K*B@! B# %w$B@! BapTB@! %G@KIB@! ! …,! B@K - ! B@ C@ YRB@! .[&B@!  "B@! .9",\rL2,B@! . . gIE! B@\rr,1j,)+u2&qyv1ӳ3 )qWᥴB@!ЪDXhUh:oEн[g+vq.U13gyW :O,K8o! p0I@((Ap6'N_gbDXLC3б]:hK1M@־hغvƗcqRPI ڊU# `ȒbVnC!pqa?;!kny ˈ͔|?nc_QᥨB@!КDXhMh"ӵZNj>&omoz-͑h RН! .j",\ԷG2'8Ume2"&:+aQB@ E–-[ǁ6yT{ 5>?b99%,ŗ'DԹ&3.]4ֹOK|yX;Cs `X ۩≏`7Qk;4aAƕ ! .-pw4xoT>cq1.>xgF|فqc`5s8$v ( _U!Cby'/k X/ 6o86w[FoaaU,hRH^@i%2-GS~|j=XꂻGA/we[q>*p:l'7[l=U}rH ח([8B>_w.pUUi)xAQSRt0oo[Ҕ9i6c_ Ә͛6cPKA ^ b87$ Go}a85M}Y߽yzڥj22'}::KQva2$.U cr׍ATeqҰڄI͍N̬m8DOVFq+] 0hL4B=p.T%xav=WXi@𧫱X%}aPx7{HÁ/֢D՜ `(Ķb_zv} SݙNɯP!r}UƂXwy ! n&k6`c{\>=`븿SY%/Wt> XrY˕ =ƒ4( /q@tJ@OGw1;js1zvFryJ|uqRȲ}G/V6)x @_ ,ϩor t]^lrP>-X UfS-B@2l,X5-Ó*c^>SԔKhS/dz/zp~/,aRБj"J/`3 ǣ} Uc~|2 c#&=@%yط;SY^9 1ǘp|g3,E-l{o4ݿ {r`=ޣEme{8Q'WDJ鮤ɲ'q(͐E䂗ڄXprr.G~S3 Z`ˆiNJ.M²mK]ו aQRс\+8pzs|0 5}k$Y,[CPB! nM6.ii} 0i3Pͧ\|o ־#TGPoČˋ+- @nk"UVUJEĎ?h(ڛM{󈽽?һMnLJ^fhtIKfR'{|[6®> Qo 4΢sA\w0 ~tg۶Xq2p=+3:B;2."v4bzg#C./GrD@ 1 !Uᢲ+WW^ n/ev+WN苶%k2*@<$Wp:@s%YED! .T\X](JJ.G9E@/.;+`Waw)P:Ӣà懏<.5Zlo.ҒcO(8 bs_WP`z0?ul3Y4vm.=y;6NǛ#4^c#T)zr 9| =܅:vBtqR] >]T-ط8p Ě'L-U(X WRF9lcVը\ѯ̵GVl-~7 5, &n\]"-pal śL lNw!wy`moLfuP'ԁ3>y<knፕ ~H} n(s}˒<32B(3k%⏻TW{31 |q8 kf(] 3Gѫ!+\3J mrW&<s1RvYP])(fM.h@)X\%o ӶUDq8ZKJZ[lmQNE?RS,-10$\i-Ѫx|!2Rgqf ƮQ}7n]vZ1N[x{[S^-ިlQμ^oVvewwe$PQlHn~owa`P!h{jTnDnqe71U q=o=3%˫M+{6H+dnwRUuWVj0v.*< EOtMF(FA+xs!ex]Nia" rT<_mG"O 5eZüfƠ =>e#tcCYmxs|(K]d{붭gȽjrtBB@OtBuX{QswYBSiٜ]MXCsͿ?^Շ3!Pم`o+P86O,PI^tiKc؃Q6yלkE2|4Oa|Rtk35~ ʣ`wPxuaݎa۹T7nijW:bcÐ۝rYD~f kxfEK>+`O}xU9 c-_zA/{kE />?/x<^ݢ$ݔ$P7'WM#gm̂ LcxŇ1G_/6B0=PAʫQ6b&^͛Ƌ+cd[-s!(ӣx*J:__Quz8WP s!3ofpȞŧ!3S[s.5s7CvT[z2~t0dlTa=S>Að!ݿ>Y1pvcݛ1HMǑܐ6#ĊV|OD뗻ß]>Hms1x/둡8:9-|0a_lgL.ޔ 5gEB@!p 4Xe,4Y.S⦇-vZ7wtǺAqDr>ړOneW0ht(o}c񤓃ۉNOJq5..C8s=[#]\G^S(r_y ծtM#luR-D/n#k'8/| kW F(Fp?'n{ϐ@}dbucސ>H*G˖3`wwOvt(Xw-bWp 5 b/*H@<6Z֏/`zR38r.v< \t G}o'dpQfp{OG,ی`,@*#h(f8+jlR+y߾8oC;Ɠ: ! nd,'2'37&Tpy8ros|-y8r4?7)_`ܤBJAذ`&/s*wGVa'*=Сkc+|f&QA/䗶lv׸QFnk:yW@T{}Ǜʩ_ ȩ [[5%eT[ x ~b$׌ݫ ̪o-Z`[N#Z{Hl^YZs>5S\ 42(k \ѼC:Ԓ.j;O߲jSyPOM ! !j:_e7Jy%F zyc#*$w֬O<1gćۍ5gs@*0_#NiFٕbmG+xsiLSW\.'m- P@_ݦdk-J[$/VWkC2>NS?# Ww\á8[AP6]| i\%#*K—OF?WXc6G^ne /'[QslY?jqrNoB@! ?& _QhJRTknvx~sǎuDd7Q6+=r^j^{%*fO箍88OPB3`9Q?l_8}r|U:qպ1]GWu *p Cڞ+h^Xvo_8Oo өI6]e8w3gЕowk=ɟυ_6\kkCS?]8! ׏@Xׯ!Ik$~կM^,e Pm ڣDTD <qk^2Zj7oTh &TwxsGll\<{o_zeXg1]^Ӟ$rEژ\a-ӕn!,͛#s+tPJ9ƃ,"`REIT.A Z!4Ň:mYfw>{cy]4lA>/[c1^/gەf(?{>ۋYl#&B Ќ#Y -m@3.RUݻwiGū.jǥ b=ݻ_Z?3._<*)s{pTYNhf=];cRX[їcE2Jc]I.PE|gK4Ao;\>Wf\S%3#3|w 먝7 lp;a :G?LYٚ:U'!gW/ݧѼ=w˕iA z(=ܲ>+!^:/gG\qWN{>AϾšvmЬ% B>ԚV14k͸k}A]&.nߺ2x&Bc`Jn;X3y6 7wrA=aBk4pkr]M)hڶ1͵i2dwV%_ҕLkh, :]yB@L@,`Rnu]*4&B}y涟k&MSiL8jl^=zTJg/!-B@!pCߐR! #|(쏨*uN* pDA1 q]RYSB@D@V72X!hoJ5[^JP! n3b,f'Ts{lݞL= Y9y镲xhL A! A@!@XP+*ԶNU2:2 ц9>eM1ҽj,$!CX͂+E]$<=~5,qʥB@[5!bd, CP|60O@+Mâe+[@D><sZnZƇ VK*f>s`ڲ3Ƭ xx.f bK]A}&,@-Jkl¶e3`M’&"'zSF;ϐ2ZeUΤA! #zC`w.%GNxTy~ `30>`<;kiX*,8}&ZVȸEtH R,/þ|ba3t#">./8j.Œ+#5S@Y="u*贤f@8"ј]FFc8(4,PY%! B@)[X@HLn$%5 [;+줏7z)ΡVye3ج_+XoPf[KAר4~ E¶#o[83—il~a9B@! ?J7͂NnIĢ%+QQj#a!k!,*֬OdٞVSLhD83^\<͙JCTǿNCc+˱(ʺpڋjO2 ! B@[-b,9hh,W)X]S7!$ӗ }7/sU 9TqH. }|,ܡLSmoxx?KĴW)k\ MiG! B@[ᆤG`J ˲?VЃoXsRH9 ϢqmU`FbIL DPD×KH+as67T,cl7fa5oV1 ,~¶ۮ0^ބ| ! B@%fW~[QV/6զ܋}RLP=)նYhU@qq1fkuWi*K}qii)y}ѣGѽ{[&B@D֙YhkEe1*UmWFB=[֝p)D ! B@[f*B@! b,Y:)B@!  WLJ! B@; wiN ! B@' 3B@! B qB@! B p̤B@! #pGfB@! zb,\=3)!B@! b,Y:)B@!  WLJ! B@; wiN ! @(=W I(_SJQ׭JϔwP9-c,d#5y V,]ĵ]PrK,\{-,"ĝL)aͨ0bB¼8tr v'Gn,7K7:W~b'^bmj̛MV"-=j5 B+x[^Z|))n l"Cp>\N82\'2^M X%B$,b~ku(@IDAT<| l^uUu/CB{^Oc]z%$|OK9q0E'.JpOu訢˱7kmaIB%pK oXV"6|5_smGvYDnıMkUJݶNBPԽ)EHْT@</hɫ63w[b#:Nda리&B`@GKTr 'p$xL%UU V<:4ϘkꅠUL8dUD:q?<SDho 3N8Ry 9n+miF]83qB6˾ HCވ<wûh~qC[:66Al!زw iC!LMv OcsI:Bv;֊J3poWt>KKlt8ZEw>qQmop"8 %wC@恘\,yU)WnӀ1SY&HЧX]wfNU8{x<>quJiي gb}{ύT<'K{G_Bx@MF<|k.3<4peVcvx+g& D ]UKPϹ:WR*^(ʮ͍fK佹@0slVfZznǧ!}a%\ȔRg8Ɲ(E~<& Kpwm#h:!гهO`("p'_cpõ3i+`VEX~6<NQϠR,|=-5puϳ|F؍R^ @ 㵮O>1w{_v=&Ծ ]KeYyޱz7S{QכpEnBY1xn((QMfLynCaV30mBgbVP3\e҆S˰i,hK%kI> cXwnE\ii8 y%biX1[~<#4TN^icZ 7.qtg0m)V8ެ(JhN'!e|A:L)W6s՟%F<1i#ɇRUƲ t@hfSYY[ IcS!V:`qѻ[ttk*NҐ9AMU˭4P w KKPb;Tߖq,ĺO񇁊1[qBl^ {!18wyd#E3Jϕ q:e7>Q?4V{ׂeb+GKOo4!?X!h!k#Тg7<ب1je],B%[=TC㻯 qs5KV#=L=dypɗMBL14875rYӇPv9%nI7(,n eZ>wW[J{)=/2Yw ܟߣKD,\K C8F Нh)}C'0ggh)9ه/~dhSnkuKjBN< QTNZWչE~vxfɢu(bvMw4tu{t Amj=־+KG&oC[w<ő=;m`he7! $pӍߐ`^~%"}o<$4Nr2 cTzv,|Ofb5$c漖1, E%x B1c,ĘːxY9 lDcO_<*OŒ#`^x[Y˖'&?;-ذ4^-yY w,A[Pg+,,sʨҸ{t@H<*-;PF{B8(<"9U;3Y6g6bz?Yg'kkM)w(fS0DnB gi8tY茺NjUtRBuo00s}&ΚKt?suNZ2*amR/, FQYCߖ"7=)!8z(`sYW絑A?c'`hs`V&ԚY`!xD9VGЕcu"ꕏ(n9ל':ueC@>Ez_. ټBB}ENu˙!o慚|/ݒbOUدe^y}L}Pq}MvE!(ۮQoH&X%H߲|i}؏QqI0D T JXx|UfWGZR14>j FG6"y^s6iFlcakcjUX5vd,+^KgcޚYTAⴉtZkb, >st~K7Pb&M؞H,NH" B*mw !M8y@ȶм3f)BmWA񦲡.M&i Gs ;ۓc~UsP:-tCZf)7ʯ!jT\侤t{4Ur*NxҰy?Ea&RnAѦ* ~׃fkB嗴tbIϾW/O |qXn= V⻔Aq)W#a;<ݥ }ʴ3q%hFwڃsR7Nsr*r_ ]k:|tomwqT, P';vw0-ZeӪJx7$KM״_yɩeHY1w["buϑu.~~t3<Mq2e\7D"e 8)ek[t:-ԽKS1ҮTc.xaOh1_4.}<{Ռ 23`t;sw=wy_gP,Vts/}2j/Cy6*Ȩכ:9B n ܈h߰h,X +]s)ߘ/OCaF;;TXG<?+lg9JAPR|3ctUIX4sFG`X$E'*9`F{43D+:{dn(hxWwnZw䃲dzeeX3 ݬb>Q/ѐL Bq\' 6VqʯۮĝG)GY[h*A#z (/-PH{?OıK+,]4 {0 FU+(NpCdRO3TU\4{?KfaRTE!,XXOk}Ի5ڷaT>>bcA/Mw%o[D*f֯Q}utQQc'.)d^zd3V-Ak`Uꝋ޶@VT¹XWXѽB!b`8Kc掉=XN]UI)Wuf0^yy;~w /pNzdt)UF]=ECz;^rYO*<'ɛRpVaw垣z5Ӫ*t=ڙK%#;%\nKӼ]\ (g 9)1yҕJ]nkukGL+~ۍWMCWqCֆH>X#Gh{^s8U4a+f.ޮ8W䷓Ůw@řX7]L~*KݕKX~ی<\^s<\~$!p4kUM%t%ʰ 5w<ƇzNtRe*FB l~s[T?T]+ʞ쩻)$+!+R_ȹ\0e\Pp0cB Ikotǵq*X;ݔ,[aYn_,?`@Y>2yT⌅!'UOwqFwj,Tn0 cfb\.j#;[ҩ q8Ű)u sc8[- |mQ U82jJV*x *Z#mv3%v'95Bx۸&/7 ;-zjP8WՙL6wY{K+.J~})x.Dʦї1.{O-9q).<G62nA ĵ Ѧy \V?ۅ""˖U?A\SF9¡>ת6#T9סCmn oEG_y/c)Wo I!9{Xs'76sǙZA]xAw9쁊c඿ǟ"Iern=!øVE[qq4stO̴X1(tMevz)w#[ʵ !}V6Anw=<όH9۪RdZ? ;ߣ!51⹧kjyPs @WzVg({ɯ[ZQ]J潝7uTF2]zNyB*4ps8~>}Lyv.FSa݇lQnH`< PI#sRӥd(f:ˋ$ |v>!ur+ (Jҁ Mf@L݋x0jg%Z{C`jhD&rޝ9x 5-uf<Vp7|.m?1?f͚iݻk{T R8pv\ZZ@}qގ=ݻ_ښX G+娨sU FH\kтמsߴvnsu(`UnQqW[ ? oKSu,\enr7ZWq +ԛޮ+ Oؙ]s3;14#j1wj,H݊si竊8Qqa,lyiĽv 3V{Ssv s]E֪Q2*Or(~j *.p^pR!B AC;Z@gP!&/b7/PIL-yXD~!ݱ 3m#ɯ/Ƒx"0)??@}[m 70׺K6@PePr3 cFPlUfXcj44BT81t~cJcUFW#7sw;p׮18?u 䲷d1nM=M-g~^צ3.r#kU 6d38.X>/}.=M& m3PPu]w"[s .oLkɐ$B@\7f kYʸm a|CzV.(fYcY?șԑ˱I 6]!WKeɣvbUYm1kWU-Bo& M$ 3 M'ń#BX^pE7&3c 7hj(-EI0tk QkC !B`lp4J:s 谤?~T]5.Rp@ɧY5n%Z! @s? t1kX-⫡|H kjVweIBA6,c|D`YOvԥ>A(_t: 1vM;_aؔ'B?O=}/4'w#bM;ƣ~8_)JPU8Pi`ndI>B@! & |͛դTʼ4dFTMSfe{KGb{ɦ3ݣu;x^0 J+GŇz}NH-Tă5طǴ -Վ-Zab:X.лk4D>@B9 8نz{rOFA|6S&c{r~z_dawrPPhAcixء]H۟{h??8PAjAPMA-Y*)Pʌa#[;,l}0}3~IiZ1Oz`8̗]8 ?] ;v x mf:ʕ*m IAJY+]|ZV F ŠG(TX`[wssw)1|CphL`JA*؆>w4G.z;Tdh,t9Q-/|ꍘش[@o f'!} ,{*Jj%&`~*وiшe\'#a\OGAYY̊.cҙXI /XLŪW,|B@!P@֪_ؔnf}XS^J*@]9vQu@_e P(ov0=g̓*ZfS=PTD՚C|?s rSaܥ+J}hO8QeR(_0 JqB;8 o'~s`S!=Q*..ˢeݠt;Z]z8 ,}ՇԸPK8N#_ z律_ 5ߪq6ZeSDNJO !phXӑ n!|$ ]wDEY7Z2pqe!q#X}}fA,V B@! n5M6PAmySB"#R Ø9✊NG/rp*n`[bFݱktFXP(NY <8?Q>B%rԌz<бӴvMkq@%9SNJTiʱcn g$ܘ %\G2Wܫn%偾v.Ƀ%NcGՅRQP]`_2c{ P>s_h|-wZμC!p$:򂳖!{ ĿJ$l[< #H/ 5cÇF4J*xymIC&JB@@Kc| k?q=v@>>ɑqMz|5!8 rJ ^+T;Ov.ܜ*aim;jQ<>A_4L-7۱m)Z6(Q[bҔhX6®> Qn \.d_ Ł4B8ѿ3Jg|G!-| ܔqaumB嬑ř,Sp=Y*Tfס.&U#QnJF /C #0)R;L79Z>l {'r~Mnaej/ơPV2^<ʫ*脩WaiԔq]YԾȩ'XٶU]޾h k=4-IQ:Gr^S)cՍhM몔B@! *deA ܍YV^U.9c /) _QifD*4w BfL]Aݺ:(YNhAwYu r]+V xcljX⇻(M5f8rLOcS QbQ)(̸n-CNH)4,z5ټб{"r/hQgP6"Yt*6Acʪm+[ * eK2ڸ LcxX{q$e!Vl[#b 0cdO= GAy 1kr(Yo %ߚ< Fu:c!b;cgّDd,zu VyC]N^EQNJ\,Sqe4 N..蜵{Hzli0&%U ;`+"Vơ3'{| ! "6-T]ڧOٿ}1<TyQ=e*s@7h4^]͎q{bc:uM*8{l>X6!*2+j]!hbf~(METs %!u@h6䕔*mri8V4eq|- VWZ*>T|?<^u] rJٷr䦧 CXM#;RQopFtju|JXi%!G7T8m7g(ܺw#O veI}3`I´tCA}oymPk]\>B`{5SadJhPLb`hxL{7[w˖B`d'a4vY?pQ7a lE+hn\`rgs#@?0&C[y;V`_k㨞sJwZq GVd=KvMG 9B@!p 4yfI-B]IpphrJ>EmtEG5ɣM0$kftnK`njK/Z> ;-JSqx&ly[vDt,Gl]eA [8ESkF\01lv;vfXLzh%hAص'}g7P:bcÐHY/g쿾l.l?G^4)[`;[lI- Z /%q&W|2Vk͈Ҵ羫(;d~iijm*Q"&̂yBmٲ)_q%4"CĐTr!5݌o0*RQ_™|h="ybbѳ['XN Ll³# ԚĨ4dh8A#q^.w$wzJjl4&BDE6_K~o*6JrR?g oeblUM7,[aNbX&R-܇BlEs!1c0fE"-֟X0+, ed!UGs!vؗWaF?2JV-@jAofbM.5tl-X! !@miдZXi9O^spLzWa>},|З.-wE0Qssa`ҷۃ+ZF"] KܶqCuP~Zxܫ_-AeC/lOp3&}# >]0QnOṕɗ F΃$ ϥWhU69{c<"xJ-H8skV[mVek5Xkъdj _ )1/,_<%HaAH` Xj*lac0X e -k#jTl f@CAk:_흉+)k>ߘMC5g =FY+t7ٲ8t'YjbʰIW6CA}5ȹxxÆL 1Bw 1f( pC5h_dDh9 ܦS}g=3k^|ߝ7 B%$cA=Y=1ާ77䃀@|CeOmpWK4E,zG=Z!vԔF̍#&/XuJjNNfbᴙHXSME`~RR\K2W *o0aISy4)  ֋jIdn-͒ӡ83k 4BR+Rn&i.k܍|/>n`=ś9Y={NapTOW6y)>9пX9P9+(77Q}A/jb6L53 HOF9Bm?mLnFۏE+EPJUV9ʯ۹vj2UYmR`ŘS>܂Llۤ tl7C1uTDtBΜ6K#tRa Bv" t6/55_rarõzTtFwv%(6ġ=hqDN@hqSf&]>M~_ui[6Cq2%S1رys097?ʪB vlɭEQ9p<0 ݷg}~J.f/3<#Q ,L3K`5a)9e89`j#h?R@! $zV&u !p"ֶ4 s4,LΆjztCJw>s=CzbH] O3 @.Cܦ J}Gp! -klD -7ttK0~,݃< LCW*{Vs҆W3sRcsM+z䋘 1Z%y6w(-$K^3NDv-pQv~: wMR%9 ! e -{jD0!`$/-Ƨ oE{;OC._xaSg}:vk 4wr}ՇT$)w[2$T<>)gR- 2Қ-&4\sRZ[-X6g:00m:TuiZ<<;HsOCfE盹l3RPSKf֪L%kfZ*4ӼxZw&G4Ūͽ.(s\~}:0iF/X[wz\27g\L"B@2| HBtx̘1CWFգTPO?_z64zxG&q9?2雐p g Y: CD+\z }'1ȹA[8ҡĴ\kKqBFn*P@4?8 ai^q')_N ! vB@'Uz,9VOG<7Td=btef|I6:%ҬWʙFS BJB@!`3$B@! H@XB@! ,= B@! @FBbsaU:k>1wP3RqRc[" +dFDOc,̽o_} `cJ˟T$B@! m,ݡVWsvnlԾ ͻQ!ᆻU0'>}Q5iE7xݽm!cTT9*V`lF5%B@! _YuEJy `&h.E:c+L)s4,- zεzCQAjQf"=hhOܞ 9+B@! + ڥw~;RT0YO+>5vz~F@(@V*pwBll ](wrDԏ-Qk=އP qꯪG Sp|>-uPvG*4oNmf47WSPjOڃZ* 6?]`Ш,vԼ2,OB>1>y Ép:F]d΍i,,`1hBӁ3+Й Sn۴E"B@;]Y0?wDf%X)!g䕈» ll/fߖhO5*   D|8tQڻTOlV.5̜-Xm=Q֓] 糹VB B@! B@~e0ϣP޺+qD #84i)Żbjy'(`'L.gnbXd$I 赽z-2ZtqBƃį!@eq#N{|S̳UM*_*hF`ByBH*<%pRQPA>aeRQPO,v9 ~YV*範3TtW4Cj\vm,Z_YVD)GޅB@! ]ӾB5@nmol*Fs L0' 4̫Kkт +=$.]G@IDAT! B@`ڕ g0B38o6+@^m羄e ϟD 8+V`U})s#~ ]PQʹ:yĻ!*2Q4ẋ@9}B(f6(0GsCU0B}BŴ|ά_QFjլF Qva*͜rI)6&B@! =Ӯ,ؿR+-[Vч0͋P?מk: mp|}Tg)SQ=Utr~U0ͱ3>(/W+;$w0}R~9[ՅSLjv+nAqi#`+,B*_ W7% \q}[%WS #z!-QԚgb,!ЏNB@! YV:875¿mhe~v@ee)*'ZRCu(=dDnl@Ɓg maOYϩSf;ʸic穼t\'~ [ 7'P3с[o2N2_*׈~iaTn+U;;UsdNfCSZm~x%<1c+jSAO?~|UX,IԼxG0)E! L`g(A[2ȳiI2 ̩vM?9zӌ8~G3nEAmyQ+. nzCJLF)O* T2׍4ȫB@! I`}4 nD΂4p옲A_(_wiB@! GකY=Lph|L9Ҩ͞2F! B$ 3 weN ! B@' 3B@! ]I@JB@! eJ B@! B$ weNݭqjo^ kC+p~kBsA.}R^*B`DY?+I)@;8lLVv~ dkcpt*':{+^)EӆRt]Af4ڀDr4kw3X! )b"!Bv&.(b[\Fpݒ֛?%pm]xo𡺤`B Tmu!7(0A4`fhq#kM&8dB@! %2p/]mL`hgϹ\d (XW;?E [݇GV;ֽPʄHWcƚ^Blg웵pىX/F6Tm oF@CP{1bm͓;uN.\ȵkoC_l}:TDL~{g uzC"ǿñ(aJ8iC^fBk 1C̿C? +<3BMhz9d 6rcǃkPȪ)/B@! v,WXwPº bQ0.X Tڔ/AbxϤR!4m߀vwe?p xk:KDw\ E9"u!fD g-g+AlPME"\4_+rj7.oν%Gd[P ?,DKG\pڢ!e+fTV1ۛ`=j%xTrng3oP,9ӀB. )1 (O ߅z76Z}.vz2Efx, r7zpUfRYdc̋CDCF*e<(p8x5S&XeC! w%Y!*LIs4`P FDR#inP*/RxJ{vح=R+ƤL.}o$=G 5zN("h,mDv]^pr1NS*\x((=41|@O;:H,,\@)T3(cs9;g >@!<ۄS(/a 8 `›ļ#Vb >T3FHG涢ᙔ,o4r[8%_'Qg:R1@*' w2݊Q||K$SF9'B.&048hꮆWctס:8Zb w*jh&6W(Vrs+F5z`‚K=K EcO9_.C"8JkuԈxwR5 hY&X8ra4GA>yOt'_^\s _½u%J,srT=A*fҟYk $4Jxb LRQ4_Xg^DCAg 6WApY B@! ӯ,$!#~}xtQ?%t,@]6 i~ ;6/F#jdӇ %ݍwNSp7x6\%8"86M)RJXQz9$~|bCbw`1W` nD gJM ts_&*ֆ˓"d?DyA)rbR߸߬뗠lJ&M4IQMXrEA(.= +G#l +`]z?BJQ|&xxKÃ2W٫|0q"dX@'W9K/Cf/YA~ŊڇX(1xJ ! =@K1iņ#q \r|ErCޟgXJplK9n-ch\$Z, QJ/+JVItx83 ѷM"R4QQpvbM>pyH:NJ7$`v n%4E_Tu9Ri%L&[~v?VH3]\>Ԅm7?WVy T,"r)_qpZe\Ij,J2J5uNNG,\qUe;cfKa.qKkqOVǭ.{ezE$LK`ƌ+2՟~iի#){y#LYy Jд8J6ϦN'A-;xu߂]tEۢGODQ'+8C7<5hPn7E jVvB@n 3$et`YT=`rlÅtF؟oF3ah~pkW;R,EX vé2A!KUpIo$GT^T*nU g;h}'~\z+t!,Eeg[ۨjUJ{1cMrSc 2~9]0p$=rSB@!p-̍lso4fLi5,\64M64vN\ZGMAA# #mqՖ]L-A:2+a4+;!>Y,6tr B@! n`h8AEa^7|80(ͣܔ>Li.+I^*U xadͪOF& X8XL*502wwrP|{Dc%ccd,B@! ,PV{0r!\"aFWftόQ7+ a PYcb0S燲kqzf ! B@!0ʂV}rMF+B@! Y,ܳ^:.B@! & |$V! B@ܳDYg/t\! B@McGK kkBF`dT"E ޼Y?'%?rc,p<8[ڊZ> \YK!  L "aij v!\$'hW5AX:K, .D p>l>Au;;Q3-(f.H 2tzeLP 4#wM;. ->*(3rז!j912fPc}H\OFaqюOyDwp<6:aٲeXQZ xPGP\I)ZNDupe$~PjelOoB5ą.\lQRTJE(ق5*G]wަlA[MyٲpS v״G3*4%+R4|# /ߗV@";~ qsF%VȑB@~e!v3͐L3M0?W M7qorz7vb%H0"4\}}i5;݂0mUtSiG)~9;%wq.B*QgC(Ĵв% 5PE4ZS4h+3Ga< P;,@9|? 3Eƥ)[9t3kL&K,gC|@ÛoDl]U3QwhQB^s%#.9vZsFPѷ>0{>F؃.fG/dkk uhRuS΄f;˖! SEQlC4B SOhoҰxc*6r=}D]("?%O9؈=Ab4ۚ_X~Fplm,vb.H FMp[~vPhRFaLaiRtɂmK |9Mq|QP8G=ȱOJ5CZ E+*vƇ )#+o~6p57dMnnXEM!<,0~2G甀#/ }Z |.sbhQVw3G-!6,,`3 9`p4`%*nr^lәPJ+}k}24W|J@}oӃ\KQ| jZѼ`CHαj'R*8%hh=6+79AޅT8ѠO~3ާ3)-3 GđÐB,B@!05>95\)'/r6Q;%v~0`m\yMZVTpM:P6mRgLl8SGeoq4'm}d)iNJ-m8v kevG[MZF,rUUɺC梵52Pxqe qTS\[ /lg2ɤPe8Oy{Dy]e-xt9~ `J*V Uߢ}̬PqXViW-lUS(- vSYf *imglbXJW}"ϮRLP)`V'ztq-#^ &ӬOÏޯ`gFӕS^06hrpqlqb-o3FXX G9 dq_Yb3t!rŅp?g,57 B-(r 1[ֆ͐+y?fsc,zW.R7T6DZ^4¡TrEo mCsqY -/P4-`|WE}nɐochzJ+u ֳ'ڷh7)9XHEflWe6[2Q*B`n eA\w#f:y$f'¯ҼgY!jx-[ Gr|V!2{Xr椃 W"Xe_g= sVJ1: nK.iLCY>i;cכi-Ԙ>m+&sM9Q (ilꤸ~}҄T>ɥ.7t MY nXk(̴czhb6G.G֤s`9"?{٭ɿ/>,\/Ǘ|iBBv **Ce)}g\NεkT]稔#oؽ򙸂RQz?G ѧxp_s7nl%VlFb`NOw[Q4^::$=0Y_g7H:_犄8}Fq֑PτD&Jp\5IDb}ט^) |bUKP7WHTR+)Wf2REujFN!ة TԠOB@! '+)|RN늂kqzju[ޅ_V)%&+V57RBHU%{':ogpَTY5UXÁ84ƽuXI*ӈ.@oztBiH(W Q=Da* 83APCÔPQT Dր#hJ%iz9_2=0C}W>B?'Zp{8!?&P mQ~: U\g+uW)C(A$B{}iї3M_URi|ǩMݘ3mX{\ rQj:sƎF#>.fGK0NO*qGje&;.ET/ґfN+(Ɗگy <}sMuY^X\)CTnÔJ}G.cjI:ȥcMepLdd CFJ~4sLN-f/n)B@!09?LYO!7ˆh?7VԼЩ؏rw<K8=I-\GPBSb7`܈?V3a"4AmFß.:t] Fi#5.Y<.i6"ZPG|ݼ˟hi3jDGK۸T)L8$D>;G7Cdr~҃nm Y4<@U:~룬8ʚn]ۧ58b N_e~}9Y9𿮾'7߆1ፋ?P#Oy;{JTփ5 ;`O ^jg+wvs[*"n})`Y0NoO=ͻQ4 :T;8Joրh5g)!#(BzCXr;д3²2졣 ~2fDКY,CB4 _#ըTD&wW ʍYT>^21髣C<7T\{- lJ ʬrD&gC]]╣ǙJ?#rG! '0 >99J^Qz͐##MPJG]4\׼ G`J#ftq}rSΕ'_F,pSSb_ [7LZZtOD א8JQٓ7lK%VrmT0Qh][F3'"PdyKvѓUPFc-GQ/rم<>GmE۰20nUM32mV#[=E*ϵcلw[-~ *qh|DLj*e6tp94fy}:tg_UHݘjFdb#ƪ+-+mBA2 pRVQ87hPA3BTѢG"W)#M31Yڨ74e[+]GB@! + GU4]PPE$cPEjt^).S:V~"'z%Y%hWL-CA~|-uF*EJVߌ#7 B2NF5e{y/rb~1}?"2p[N z.#V,8]?Ϊ .ZZ\&fUp2qj%pO)%]΁ Z\-7$B@1 |r9?C?&5^ jBPQ釢&~jx7ȡڛIGLgthsa,jJ9^)r >_h7 ‹H(~9b7?DQxp6/pyL^\n8Td;PtL~@+'B`J ܹ”bFxK'F"{YVތu7x+'6 * 9KQ.K~^u%V 3J< Bc ی,yNmpM7clxs¿غkƪoܩݔv !  Qn !%6\OZ _}H1- /?XlxtB@! .QF"I@9ogyfWW0wQ[۳*! B# wɤB Z?xwDHB@! Tea*iJYB`0 ХJ! O#n ! B@!0A20A`\KCqČ{upBm4 c@'^ʥx-aadeNԭ*w|TW枰f/na]v1H"y9H?vIB@[I@[IWw4 Ƕ5F V{p|ctʁ5nDC(N8]g}w[OYI7h#u>z':~4t"v3F.@goҏ1 7%)pW]NJf3,-#Gg'1̦0BJg Y6x=2{W{,[[z+&Dd2l64DAéTK6bETZeb;=-(-I]!nTb>l5E"BۡP-R52Y Ѵw*m(/C8-8P]}ky \ 0<~աLS݂͉Z|Vd~eCa7N]+wWf|BuI1ZҢ5jQZv]ȡB! sB6&p!_QEӔ(:taPC0XJSPC.\[ 􍜭HS]{m~*`YNaUwU9DQޅE9u{Q+8 oA`lgqCyv 9 L)7A* AXKP A4n)EPǪ7𴷡rcn9`6WUޜ/|IՆee`Ѵ8wwG=ʶVBI4"xق%ˎkW%fE#,RWZ*w)u1<m k)v(P8҆j_eDCp'ے؅mMTH,{=DHRطb0/`[:p4l\ٌs~k`.tMnrɯVQ^XkBT/nrZa}qu\D!p~3$2 r2Dt9`]s^ h2)ϥm8]8K!0.ss/kF:h²M-sܑQO[b|{΅|+PX/Իg1Uv,lki7l.-pv#Ql?Vtι*G F~ y[d!]GP24GӃ1tQZ/džqmJx}$~9Q0A,1 9UouJvM/W!ȏEkZ]@щ^4]L՞JLZʶ2=JhisoѿWQe(\ K*oC9"i$\v5d۲-NoO x|8>PdS_?=aYNWY|&-8{.@`TH$ FJݨZcW'ɵ*ϫ~;֤)Rf,{saK73 9qfla~S:-,zjPQ}C2]\|"HGtDC5Ʊv]ja*zT9!SL/S]THR q}?ޏPxOnL  *Gnp8.d^?E ( vCL64v"ǩSh)Ƿ a@NG_-p5a lh ׄ0MB]/ӎU8*iލRF^{%o3z-*`XĺmOخsz4B西Ȇ˜ rW%B@m㳠%\{JuEi:Ksr:sIhAS$XQA9Ѳtȑu-繮XmB q0r#\NueG cў&,rv {Pqh#q:V+)_Vj[/!v% ] '[8(@aytq6PBӓf*C9*8[3j<4?;Y| &|VlGYMMp$>+v]TP40 {[QK~'%ޛt0BO݄Y-'t.!G'ņ厺+ v`^xjc_\T2qX>SB6%p( 8\;J{&uSw(@"щ· ^&\fJzVuDz7Ugg9.v{9X@rxQ@0maD:d's&YjGY&{!4oLtj.Mh_)|L|՘e(ե; \ E+csUbwK%#e 4VXJp~uZa=TiA`BTr>|tnMҒLA}ͨ#fFŏ8=E&lc4"<9tlm\n̘gJ(焀w7C"05b9$O#2#_# [[)4GiNJ-m8vlRe/V"_&$.q WЁ<2:%ȷ7P~U䘩Gȋc cikpբ E598hW9nDr 놞d (VW9d8а,p MhVnDf2FٞC>:832-nWh.I  ;˸Y ۫}TJEۓNƉE%|a D{0ƌs9KQ7eؽUC%Wro2za"*>45*M VEF6# VZC94ѯqUt1;DZkoQߎm>/6ՓI0GYwj||lrЇ18V \jWr3Bk UG&pLg$3t bj:s/5~/c+B@۟2Lk3!s R5aݱٺރVoj˶Ā#]]ZumEհʄm%t|5:ƪv.H!G"%a%1ߏNs+e_ʹMm%lwjBR_qꎙIyJ.]<3fʨU VWrr`?@IDATLoGydj KѸA pn^r~Ôd& 2~ WA1֛`R N2oƑm08rϋLT4{dH]L3kօuR-{ /W#.ȓ}3WƗfS u%jW4ܘEXuj++2I% 3 5)!=ىe=fMُ+0jgsMlfsYґ9/F:ԍYFkj&B&0n˙ NL2i'r&[.^7-,%0f;ڣkI;ҧ̸&7C|:DU]M-~Uu٤W~3RT17R'qʹs帙~H^! -ՏȠeځ hlDqe8ٖq*>.>[b+26\o\aS;a"㇝-jz1p#) Z c&DYH!w! &IA.S[wg <,m@3 Z^>.ldyW^*9'A!p7~eA*;,gi:P^:pb7`9ZO=1Ng8щot?3trSc]..pL8z_E!0)fx&s2ٖҟwsn@껹7! eY4/2Zd\(`ohtӽۛQ0\1L3^ß^|[#bO+Dß1ܺc"+2Qr̼n.B@! wp ch[[\v3*$qp9-B@) 0fHD&Ƕ[\v3*o3)ɅB@! t~3) eSQB@! #q3 #گLTB@! 9w!B@! B@d" B&*rN! B@ 7B@!  B@! B@B@! H@XB@! ,= B@! @F,d"'B@! eA! B@! 2e!#9)B@! m,$9D(}5D<6:fD%bǵq%DX}]LIyRB@! G`-pW~wh ]OL۪QFz7"I7!'(P=MS5F! B`z |rzg&·8ֵ L>3T8q\cC0eeʔLފ#4g|KLһ*^snrN&!t.'ƄΞp&/gaA|1*㚆ЫaM'0&bK,a: G5į hָ4*ǟ:L*f9v89/TR ! 5_YHWԇƎ%𬶥:"hEnZc[ `RGzqcX Ejz5'0?ߌ|+ }hljA*łB^(BOd^yU+loV%j(hC8v7S@W__5r&KEjQ@C]yټԡ߆ڗ`7k;P`ueSHٶk 0.յhXmG]^V$;=e;oXLKE_%0_qK)JoXlAeGѵ~θR2aZr`G#7cB*%E cL#c_s Z_kPgq_SY#&핏1L %,bqҫ#]#s1Ι.u Cu W1ʛ( eCV*In 4 %b2}L锔"w9ff!AAy 8M[v# bTt Y;caZ o[1.Z攂n48W{Q~;Os8En~/Ba\d)P 6W[x>^r5*oDZO-"@rlӌ6]d6ߨF^'^sðc%.-4Â߲L`5ˠfVuf*rOGGB@\Oe?16mh*NB]צ~sL4}[~5'>nO[ƃ^dyysd-Cպ?bı{oAئ6U}߄Q`JG^΢k0R1+n,_賬񳟩~hy}{j'ܛS˞6]>TRσ]?$%T+]=܌M8pȍF^^?5VP]]:jG]zsӢ5;l)sk+45 39[JwI_-Z^#v"dO΄Cynxn)6e ).2OŊ<30偓"m14Rp+ufRyT2B+GC csBy} }g9O۪?Ul.=΢mOEaQn:.5̏wiI0ܜ7UicSwбfhYTxŮY{t_?_BEB ȯ܉8?Up<=}7JSX;t4Wakxwh#`=f$>2SJ2+wcS5A_x9Wlr ?SRe o"JHm)4hZGC5zu>lwZ3?o 5Z_Yte!DQޅ )zdrRiZQ[gÇ*2 +u8q g0cTg(<)H \3볋[)NIjT3yI/])1CH(G2wOo1[j#cf:X|n׾u3tZ>~O*E (` IVՇۭHqOhAq6Xg#e MNǴh)Xvm H뙩e;ܭ*sJ?00a+6*{tOѪU]J'jYB)kM%s 5H`LfAõL]3;P,*vbKa%Y;|jWbLLO!t*yQH1`;~_ pFm2)a1,n7+DVH\T j:8Ou=@UŕTBp̞uTd,lDFa!E#E협Lkn/l݌={< ?5;+9ʼnZ 3r#4~Zˆ+`NaƐ(R&&B@UcY7 cP3OѨ~ڐ|$VikDo!L#l)fMR[9&*J.b] HT=3i/ㅂJءڻ%9eU0g[՞V* \3@׋.c `>/nN8ӈiEHX(n+j]TZwA>ݛ'3Bk 3L(gy|+1rE$PXA"]k4õ39EjXΈLl~Ί}P,h6>`Ci4܈)@&x5si͜OQY:s 扱`iL΍э'Õvƍ't۟%s%#2z㨦tЅY\D@0(K'> Oӧ PLΙP"$P\IOʹ+ܞ`N*C P&]aakv|uQ9x'eVº^B Sg^Ǻ6>*NeY8]0mH\*_")I! UgYNa7G<A-@an@Et.&4D$Nf#աIJ5q )?17%vcF Yޓ !%e g+Q)9*v#j(NP@. YEAϹyOJBP꽥.r(n W#3XShTRʔ &F<Ŧ|n򖻫`_DzVBW*.nq3ڪ1IO>/V/]lڮ&Oʷکm[nt>>@:tƛ=W]vfϰhuj[*ѱ1* cy:STy5Pe޿%ۮ3K]]п 85`jGcV .~nOkGI-F@ &&o:})ǞKc3JƗ;F 3XQN.GH3M L tHXD \t(uùHux\ZdK! WW`ϜQhêU0wdBUF~3G?ޒ"3XJ]`y(I4=)`5a`0q<,[ J_ S,۝ V=]\5$(-Ք>w7W#pnBfR{\ WbP5w!;@01&MK([ۄt$P(+*GT/σ}a@奰тجbk`'X95KdjBD21 nXjn.:#KzRD4^HPԵנH8&! >J$n.au5QL1;N ^I!; z-^ly J p7`ސl`q& SiՖ8{F:ǰ#Y?K"W`&IU(T܇+AQta.#]<^*^Z}=nN@Rh6m9eeGZBC`o2RkI7'T|݅j͢D&o=bB*Vqn&۔m!  xGGnCVTtM?d*INZUIvrHOȎej.vNe㏲14OV=]}ط8. Y9uYdg ?pʀ\[.z]ޏIpi|g03>NZ>V!G)h -ZLOru61p9=9=+ylZt͆Uaw9_enYF.ߛ s ]J+]CmY}\Y7|w`Ҫq~hZMűF%sR1 tP3 مlHděJWu3܊DzC[џ]*gY(bhl봔Ъ{”@=%NQ Ve\TQmOL>Xr'͸DdŗiJzVƌ26(4Nx#H&4:XQgORRȒЅ 2Deͳ -Zx"h=9mF$R}] &qz6yB@ qJXo JVu_?:n%臆 de>`hDQ耬>w.NRT fHjwiAևϡerڸg.X\BsB໺gj*>giT֫pOnw~`2MELf?Ts3כV/㝮`ƱTBS-3,ؔ-X;Gz8Z t*Rܯ\ptEY:>. >P9!Y.\LAqӱj,rTdhPU( xeɤȦ|:w`$c-y@]|JJ>X1A$e U}yKnsu5'2!%fU! d~+#:u ÇǠAW_}1ԥ>i$mٳ xCڮ~y9~8~χj_Xs{KeݢLgEs *2ҙL$jr! |hAQJHnݹ/}?՞(J:)}) pjTzR;ԥ| xu'ՆRFBL֯`{M\}9.{! .ԩׯyݟ" g-W2UTY$Vv@TAB_/_f]! m,\WF' fr6NChJ@JQ0!{E%9_@~2T]dB@! .,ٳ}﯎_,>Źq ӎsg'_8tq f3h'2v/2gÞdL˴bwTH)m+u1ր~Dezw8gGUYT-NNf.X{jV:0g Рk%<=e52VhF6kH[0C/aᡦ&FՆcЁMt-B&pYnHJIя~',*ɓ{hA5b\;5[}xȱ!#G'bhd.wK%)1@O؀υ>jGߋۉa7?C |qLQ/,CCΡj.LGݻ[be>łys.y^nTx%0GJN/AId<:i Wp9HA _%&<nGӈ n.D:ypL:'[8?gN`hSZL30{7U"Ia&x})cxRZz=nC Ĵ`@ʼ,p(;'7v03  ՠda6QfNSi5:3"n-|7cPSjhyHYqmP؈ĚlݽÉ(6dC! Mಔ|6TtW%p(6l߇ ԉ|Mx|Ώg[qs', N!xx<0gO{#O'GSn1&~oک;݊=x?F G8GZ G#/21_4'9& ;yم~V ξYj*;I?x-atL |WyWO sl|- )omZѸuaOU-[W?Df9sn5"' (˫)&0Kn7sh hnGi SQr߉tަ@&lr g]9OpoJ1wA&S!=rG KͰ' 1Y`w)ƄGGF-gLwy%r' 3ޓ/̅d !B@! _YYPJB(Eo]{;廉pH<67Ã~JZ$Iu.jǘvY k8mks so܇WO(!ZePṇB<(PpN~1śL*yCObeFwGLR@T*?cfO)mžV z_ܛ^磎AS\nT&ZǸabsQQ}tNXir8<Ūx9Kjұ|~~0F|z!@o3uȝOA4nHy3P=SbX 7WaF^uVT=&Ҳ`]Gm>dڦx:@ uy[@TnB޴dp* MGE~(R(z>ܛJXȼVJ0ذƵ pj =a n˛B@e+ q:ÊBw6Sf\ OD+- _?DfC﷌F S k3NQ cGHr_ 3|?Zqu/%F{t?øZy0+GI7AC1xJҾw-h*BI˸hB?x>{&ݐrLB* M/a],Qd+o0c\Ⱥ,dc?k[r<-o—Ϳ^|٭8X:ۆ9aܨ g/c !@A)OIG6k뱋/ ?e g0PEqv^Exu)Y)w MMph`頑BsX _6**cx7\/ף&H LTG5/y\2lbfC7ͶDL 閤)B@! g,Aկ~-n_N |Åo(d|s8z}`5b5虉Lz87PI; T[C@~6#/1@k>TzRWy¼ٯw#}8YaIΎzg &ڂpݛ^N[z/ZG:X{::&FˮJr2J臢\"emh7 à 8:Еŝӏ||>:9s{5_/zRB-tQ;fC# Ȧ;Qd!cPTQj1<@+ʨB`[)Fj ?GbmkDo!l$#|dR q?,nR ҰNE앛1J[I`٬U;terVfUHB@!p} Я:wn$NFF3xTS'Ǧ?q[>tyi} e09yxGM&\ |>؀_*{b``ۺ ^4*@O67\`}$<-qtF:goÏ` &Gut9etz7VH|׋|fZ1n䄑hbbH}і`}]soa40G9>1\8G) Z6(09|+e">^|4rtԾбȻ%)G=Q w};[s\=iFbe Yt8i|6clP܎é?Ξ@儵Ⱥ Co oy 0b,8T$Oͳ Smd]u^1P z&[Q%Y~LWnMO"AѮ\6Zr,kaT"| P`[K1WX=ՌZ'J5>NPQ[(X:  Ƚ'6+0P-HOf%/6cs{Bԡ BZ&pE,_0Cv܋[(D۰L_Ax'I18~нp lP`cBL:~ӹ=K>=-u`|nw8P}ӓ8 oǘ3G7i"ƄY} O6s%= 5vqG3n?oluŀ\#eS}`$Ր^8XJQy;jб>vOƄߎ9ZOS" I V=2|0v|AC>ǚk0l}?WCG`?;]p J:^dZ FxpOqIYu45uȏڣ6tT֥Olr1y'X] a}܀ŕ׊ub@]]FB;3=kݯM^gJB@e+ ퟇aZ(46İޕPڲŐD9?KkYxe;!q&F%fxV?: KGÕ,<,tc%}H-#Dלӊ12p&Wy]yܠ #4.(1\v`qJY!X6NIK@P0zO R>apxish<1\*%*^>Vheϣ|gW=zkm} 7nsDjՊw+iQDLVJ:uAijc*K}ҤIٳga?~8NeiC>־,P{֌Rx {1߾ G?`:/ZutCB@! eYPOdV C_ROoKky'O,'MK\|*XV2x:>G`}UPb(X^t!?v-f?vxzleuEGPl6 \O \5ؾ؁uяj:wI(e5ڵ vW_3|<+zxx^UvBB@ eA.! .N U-@U2 pvW6tprw`é5adOQ3z7QJ]S/k+`ϜKP`*ڍ(s؃gl;܇ύ tp hF}>6vf/v7;1;Wk'9asY}hkpUr0Ms6fw3] ܪ- (1c=Yhw9:ČV 0 k9tu^1^!b EKXU]{4wBm QF&Y]tNxm`睘iŏR`_V&^UFp_f/s*TM"5(uXŪ6^sȪN2JNkn6 َ ʹQP7b7܍p3n 1rWڏ."B@ BP:5NM ugUhd S[6mׄrϛ(YiVx)XQWO%ijhAǷy+lJA\Q|N7u"٬7L: t:p=x(pk̈́_"0a,8ou}{&Zhpo`fXDŽ X+ll,Ǹ~g9T ژP FQ,>#RR%h:mBƂL$feD !7fz<\t56{sz ~3E*DܑsmK xM$-EvLorY]6%wQ̇[vz$cl+2}"E<9OkCVy9 9-YVcZs^n*nO8<@~0 "5/:uW8,>8>'nN! W pݐTf+ע*`C6F7FgVǚOݕLa?lx[~|%/Lik΅ykHz8[ qxlJmkJ0LN3Р6)fwPl]،ip m#6)- & TMq=y T^cpf2OGV ?6#Ӛ}޾YzQ$py gEqEPGK*'TaƒjT0΢p yXC~* &Δg*O )۝3΅Pv'g<*¯F+pܓX2JZ^ܵ(ȗbD<{5H.ޞ%Ǽ4]+9A8QՕ.*"Y\\ּ~K$=Y0Zh6%},K;{B\ Yp: =TJ7aj^qLl.?lE *gnt~ƫtTu?9%2z{gl{݆Rg)lUZ'Wt&%~^tlƟb`b}ѹq$еQǞ]dƬuֹ|B@+,A s-`lG:̞pWk mpu|T2xNBQ(;j*&h;f762achYoӮτǦD)9)?uD,b -pq67s5E!nNWMQw_Z"jV^ aTB@ +,KN)ZX 2# _XhbߋP3+{>@ Z FTo]Ⱦ߈ԟ\l-aA&$)yZ!Qz}_ 3JHL܉ Xsv:!+I#}u9 ]e`~TSU 7-趻I(BZ1t*gQ/G}dQB-GSϤL>ݤ˽/0.fT6Q)lA5cn2_#+,e*دMz1Oh/ @IDATR` 85/ lpQoPE ;cAX7K{jd\zR͚jCM6Wi%e4OY`m)u.%c8+YdξX ǚ*jƭ621K3b*du5a4)VN~Jadbh gR$\*7rLʠˌ9t9qnj@0O(y D%]|a'g[B;ǁ㳱 ] 0PʂA䘏BoX÷{zCw} $#QU^3g$\н6O ZUDo,g##h'" Un+3TsV>BA>]'{*k :B.q€ۄpn3P^lԔ;#VPjdQ&"6?Q@ M>G ؖNvO5vif R9H#ޕWe͉Q߉-T3Y[}dõ|HJf0%QÉl)WӞrML>^+nۈ)TWԠ|扛6< Z-X`,z.x 0֓5 ḒܝM58>Gj-5r /]cqRAK#lXZ<#bI_^IkKg@ʢROTbBw`\\qUT\jvY}ȼsTXs5GHFqe{ #'!>h|.Vc2+UW6o v? )%çSQkFsY!}^Jt/s?A>R\ \Q⮥ <&y~ 22F6W{9oo@Hul.6r 'q",$Gw0n^oz~,?VJE2` Im6ʠ&*92ƌ<7k=/{G;止GKs;;yTLX*4M~rj^TZ9 0TR`k`^^u¦UCL Hz8h-0PөS0|p 4HW ESI]&MҶϞ= I E?;S+j㬽v}gb.6v3PZ]jR`%K{pX ]:5=k6k7b4^F=񂫱/qp:7w*^+- c&kA,ߧnƴ1βo3r7cQMSqZ=ݳ%"vL$%)Hષ,Ft,Shנ~4a`ı\f44y0fO6{_Ah53~g>Nj2ӊmQ_낓ε&,&Ilّ/[Oװ!J쓢zYFѴzOw.aJ*a=RG~)N^TE`.Of }:'l:חi.U_mG6S3\}]--+qy}lJ ! . [; -lі3I$A͙H)YBb@]< Yqa ̚2~FPtCTP_7X8fq5ӷ Ӧ@_0k 2DZQ戀8+Ʒx>T)pTH=#[B#]rI粵Ad2?djo)mEiCdj;8s= JWL w[Fr! IW޲@=[d 5eW$<t%yW֣`5(97z ԊЂl0Xxz9+"e9d,F]e1h]zUAjRTT{ص֩m*/?zpu֯+Ђ66ptfbnvͅaC `z&q2 y@(ko%&ƥ+D&$X[nz<,Ŕh~yvLX 0PǠ0"eYbaBҜ|ujY\L ! +,(yIX8u Ƙn_~o3;':;0!~sbݔB@! W"pe,)uvfpxPQqȈAzz˱|;ĈT[>=SK$c\EnV&emLe6)ńB@ hWVY0ڰm6MY0și |o;&>ĵGgֻHSLQ :.ؗ߇Ig! .}Tqƍ4{<5?v-.3ul_WO^! Y0P0*`PhNOFrr2Q^A5 ^wKn<=e59yQe!EA1–43i&.PCNZe`kcm/@a"x2(Ok,s6-:n 28Oc^zic[~,9}6eC\J(x_+E|ixkP_ #Sڀ#B@! \qe!4Aَ&B,X^Ԭ,;x]p,,km(ǯf|ִ,> T.7, Q@DxN/ûg0 ĚaՍ9LTtmN*ՃɶuuRpûyK*5ѵ2F|:dmBb (',?iSV>|^ܵ.xJGLXT-cdg[n$ kQ6て%pD+Z"z p1[L;*vuxoکܻZNJ2Xtc*T-MNNf>ZxG&ݠ>),hF6kH[ !PSpKv#vT6.x4$ ! ^\Y7^)̻)I泘A/uSp|6? iZ0I#f,3Rx)5+f<\i ǏQXj RfyH>;Toa(~?O)m2}a+K!6/Jg3(AJl锺vKyY1`@UU;BTZ82prLi'gFa5RamCZL30{7U"a^lA+T]J+P6ޟqTnjD`T.|HLˁMʼ,*p(;'7ʢ@,<0l\WEZ*J?M'%_ 8ƌ%b T[i wxuY5sIyB@!p}#2+܄x/t9R.Rӡwc<@`g l;Vʽ9Xc=nԪԍ`@tQΎ*!΍aaBeϙuJ>b@Æ ]+ð`% 1k>impqZ'oZFU#ߺ/|0 ^>Q܁Iy4ʈj:RuL_g 3#mĶ,$%be=<홴L4ѲFWU_~$vw o~W<]TT-"- uNW7!oZ2L Hz8Ϧ#N-̅[7=o]# ZPX .0)QSFDDFZHqUqnpeK! LU蓿eNB{T4 >AL5[-`nM0jӰ$g#bWߋeU0:UC2X1Ԥmc\@X a6CVqol+E2Ŗ l~2oНI>UR=n:H UV1g+c7~K9ȧZ6e{O7he<-o—Ϳ^|٭8X:ۆ9aܨq}EVØtm^vx Č;}8MI7OH{f 唬;S&G4ڟ8$.hdlXvh-f+n^G=vko6#)3_QM3yj.T6 EoɝGB$p* W~+޽\r[f`pQCzQ<8IKaL 2G0%H __P K.bo$ݯ+V H/E(_8<tcr#!R{ٷLze(DXWFfJ9n1X[7aB6ocR>"I@ѴLsHxl Hk/3`<~#ee"n|Apu0>/ć~s# }EV*TrㇲP/&-ϟD]{~(`D܊2*~(Qc\M ?G`-m(1%.7K f,,S`*Ұp!앛KwokV}&$ ! ~ p =V>KBNJj5hE ~|Z1ɱn8)Z4F@ k*pb߹.=J%k wz͵ Dl~)Ů29v;t* H jJ܈j55ryy\6Vʡ%#/΂|JX*ئЛ^h5Kym^1 g5 jUb.)۱]$;zGpsz.܄؛ng};3_Į6闊>8Ma)ً=՘|H 7ꅛJxp&-a ƈDXy0|cf:DE\ (xji`.;symZj7m]B Jdf4nk?>Ծu\he WYr5"2ji$U4!;B@\}tUkpwCGG3tH/ 6r%HY9(%~7]*PcT>Ԍl(P۪62jj F9Ndϩd.Y>D7rmm:u ÇǠAW_}1ԥ>i$mٳ0:hGqhեgaG?hʨ{q'{b8e3fU`̇ac Q{J7T*F}n2-!d@qav$6't ][q3KQ`eeA(KkG*bڮX"ITT hS)mR>4AI- lhqr35؆i`0sy ,xn32QLi^ dBZB@!p>(N =GyE?lf\Ֆ3Lj&؈}z'eAzq. k>\fW{؟Oye??%~K]Ia Aq?->DG5emus1?UCw]DJB@! ">fP(tB﫰}=&tglEA`U`+?|r聆gawck0c1g?pF)j[ |?o˥2>pߠ+trvP) ?<=˜o߆蓏?|B@! lזpFI#}w N+' * qᔗQ0f@<0F&B@! F,\gE$z |="ņc1jISo,K<5jB@! QIr z矵cÛp6ApaX0/yU_B@!0p 0pϝ111ho֟rTz23/Z03O] Ô1! WQ ]?Æ |Vjձk)Qdd艣ڻR"$ ! B? П4+Nl6?ޡ/7x#Աk1p-UB@ WG7B{ўά܎ԿzRڧIB@! ;QJrJ)GBBEQ臓{-mP^ncgh:A{sjuysu{ /԰k\ ~EoB@'Q%a}p,+ca<ǐ_=[ JKr܋7{(3c4c1_&4!P1+'hTd"5YʟXZ$WU5O #֊Iqc0$109pss=ιsk)۱i}[) e;MFNEr:Hz˴ Grn.ϪRorwUa|{FZnw2fv7BUk`~v͆ Q}wx253BiQsP|*Onuh:`z;k1sU!S z>5>ۦ=PM ?R$ HI@,\'0]+ 47H\PvjU5C H.W86'#..0gڢRt|݅:.9eZ.qxpބl~26[mp9,(x^wYo~ ?pD;%7 yq r$'QhCzJ2fg+#-6c8wc\W>꫐?U9kaww6sN›gMxG;5D\lAa1κ[lK d,.?{4Bl{^8ꋑxd,@h-,;鰟T}4ʍsM*UuSبp;.)x|׵7(31z"F>5ގ83ޭr]r!r1F&XqvI(QJarj?:SǴTd,J}Yvh8C҂ rU&jDo6T K˨" hXNܬdF'"mA/Mj?NCP{򑾲 x|$e&t9IUta4\@"#n߳C9|'bd -Dev?opYSf%cI*QUϟf|̛(^&FVg6DIFhztEn1 |NXh-F(KvW\CsӈH:P?rJ䝑 ZV^$IknH_,* o.ӴЯ`n b`\{64%.[M(tMף`m!m.bѺgj*Vs#AIX_*Q7#;c}Y3%Bg7KakSd. MiYțoo U+RQZ*uծ[WTa@AU9 ܷA"%Fbֲ20"or yXK~"`y]GSa}(,P}q.@ɺ]HPExrx^y!)H%! .Ʊ`ZT]|yز$FdQR\W\ lZ^; )B *o\\!-(e [;y#vQCT9zjR'Dh>Cq-:l]7N-*gHE݌m.ܶ&!9#;lGͫeBay!L|R Ӣ(wkLĖJ.Za.::s ] HzLzqgKsI@z4z! w)?귣4]E8+q;TvX$+F1$º{;o#ZzT4/GQ4P./dtb3Vm.Yվ6螛'G9?NDY.]I:&tR/;&nr@I c44 (=4u\6$ *Bu: @ f)pǧJg936&)"8r *.8]i.W6&͙*C 5`UtYCԴXL jgw~rj(('&d$hbtdC},%tY4U.$>OJWT]$4rUcT?~q0oQE+7+g S}ֈÊ?QKG\H<řM-.tƠF7{b(6fO?z5vwvt2ߎ{+NɃ'ڿ9X$! (Hw*j6+8PQM\T^wt"q-؄5Fa%,(IVfRDCbؾ7\ZL "&a*2 (^ vACG(-s,I}>QnN4y[3J լوyHU}g<dnRJcG35@q 1z,U[U_ʹ<\g2}ė:"I ch ýLpEY:L:L L^>gpph4J< ]b6Op(foB3/ EK7a*D1do) H@dsd>I.$`۳UqLKsPslB+ Kh9;RFw-v Sʠr^s X&(Fs6a D_z1\G]H9qJ2XMאԝĪJP,s;};+ tSaP!!Mq*Yr]Y 9/:%~3 [X*H.j:vXĊCQgSt- n j3sYNnu{8xn SH[֙[͋JE"$lp1>#+. jT>e NSg+Pu* Vw1m6-FD.Jw+t9.Vb暲!5%uw \\h``3x0m" H@rf!yK 8bZg? D*eإO&KUޣ^@?>VT֗cs1]dVd, UKKPMDӅ)J4X˂-5᪣-FnTgJA/A%q++Q}p?z&z1S_p%[ ,_BvvJ`&2V(3:сNH%Ndj? ˔LSr#Tpo\ZLx`n bEIX ˙lABdE aBwN` g6⒙ d ^Vq<3 4YUCnsOV'T⎫AQYӢˆ0:1Ptgk = I/7`Cqz&$h3 ar} %Mљ4*1'T'bEp@i*N5N*\.*rq KzWE ҂d}ŶvrEo:;2F 7;Q2 Ub|`d%jf4t;(f@)ƙ0>7%:ΦRq[ :֥Tl[hG\G2$ H3 ᒒ$I߭$2DmBQ ˀvKlTn;p_"⿦ HZ]^뤿|=L:m#aԉRja<ӀtyH_f9l&n(R\Rؙ.EtS6)އ*c$TŮNY@$$mc6LV -t?T"w]Qoͯ\;.\D8>ܡ-lEͣ[}s˚$;;WB^^{'}67K ]7\rqvaӎR2zVf!IٓV vEU-p#$ۄ@ܐO?O`hoW 77u_PK^.t#"FlPm-< ىs~yc33{;@+GNɼ_W<3Atqx{#$a6O/^t5boijl|4;N&G=4:yfMŇ)gјJ~Ct'>U'I@$I@Ut{fc+۫WaQ|!r_QQ_WZ1KNAvڶbz>8qQÀ˜ OqÁ8N ǓÔ5?4 x֓GPCX8{"Z??5}b<&fNrXҡVi7[G/ީwGxJqĻ]m#x8eû֚k$%I@$I@$#}]0G$ϱ_w.kGۏuw`= ~h ƈazxԀ8 'ɣ1g2y!uQ?Bh4&C}8A xpM0#Dو&#L2GdwRoî]Gw:pNE ?x 0p]elI ;ng>#hگ=.Bb234,|i?-oy H$ H$ MnHWѻ=x嗃VVP"`my/=FFF?ǑZ,'K2hG[g"` a@J#~/DQO5 ?1{Ȅx;+rG>?vl~ =qO&V:y׽8s4 C~(zׇG<6쯩AwXҚ6EPwv f>? GUi7UOESXx a|LLő`;]θDm?EPs¨Xf$I@$I@ Qg" '/k˵İ}tC1 }"CҍK3[xt k6%s Ͽ܄^! 9F{ n=`U voŗa]EmmBjC_v|11@yF=? ٛA:#c ǎ4Mc@??W~h<.M bHͶc݋T'J$ H$ H@crp99Bv9dXD%F{!Xl!R5} \8_p Z;/.6rnDAʎ&lR~|D["i{VtQpQ_E>1q=clqipW s>rO1ct=B 1.Zq:s{R'tsjB  0 mJ"{҄*l.|ujKD$ H$ H$ %홅//y‹=W:{u5COPaɟTޫVIzhӘ>?py2d,]lGM1V0_Nph4No>0.Pu(~ųJQoxĎlGͻ_C\(@roJGهHeF %li1u9ϬC)OO[{³JI}G>ܫP#m1z'W<$I@$I@&MFV%MM۔Kihu*.eA5C7vp\rڤliזQWE$ŀ[FUvKɕRC6F$I@0ݚY;2]Ia t3 ae-XGOy!GUr}k5H$ H$ e,$=gـe$I@$I@$ە@W nI@$I@$I@yXyR$ H$ H$pGe'$I@$I@$' g*%J$ H$ Hwi,QvB$I@$I@y^ "%J$pc 4mp^@Ȉ 8tI@$I# Kˆ-\ZkZ,y) |wӲziGuur]|"@$I@|cX+w2(x3ƀAҀ0GDj(/] *Jv姣t]&lC GenP0e.T8?VV8f`T;qV%?V8mW?LJov\ .~gZy;tnop Q m&ދ]TD/[qc4; *}ti*~tǁ3-4.`Tá?,4@3Pqa췘-&XP^]u [[-肷T(52t2PƆ(G|iYJR -&b$I@$o7ܽ l] O5@.y(2W#oA$)Hz$d26)JfP ^A۵a BQ1De?+F ޖf ڂ ^ #&A63-<+OE$I@ /F&Ixd,@"R "tP< *40*s9W"9%HEC K6 ;uWcZӦcJSFO! 7MUDD0劊:`t"$Aˀbދ>Q<*x,Lw-Iynq:qD8PGO/ F2i(Z8\VkCIhds%**V B0U }aҏ2BG}uSh #HZ!D˕raH3o %8]?Y1*i%-ʠo9CjˑjT:$\w4_⏟셫+m|;S~]7◿/QH,PcZ[Wy;iYنؚ _7cOͦk%׃b` >{A?m.M i~9 QǀgpiTA L2dC;Z@d6:G^vZ{ӷ[QFfmXq:9fYZ+ n.i>0g b%&JN~$ H$pGef:HqPѣnFvg50PLL7#)f^= "Jh&$۹q(:|*ـmkaaOGa;)=\-ZU4@͢~S(w;[8*RsaNFIq%_uS5iVk7%ۧpve>ನ*zXZZ&OU1uoab׺ak'Gh:h1iR9+5-u yHd#X) h H'銴΂)ٸH#bW7m"OSwQ02 !뷅p.|%j-/_p~bD$I@ ƂqZl{Ɗ%8:K' \Vy!QU2xg* 'oja-y^8G_73=WqoVl/7}/(A,UvQ5RiZ3.?bCr9T?7kR鵼~ ~WGotPP4m@D c[a{Z*{1(撝3`0cjt,[z`_jz{tN _]>x ~~r4)Y!%V&zk]]Q⼀Fod,vVݥb^Y,+PkLD89{k}"M FީȜi2J vz~<ՌNbrҵ7e*c=?cTL$ H]H79LP*c"#ڀunk2ԥ}A]ۯ(ꙻ0XU T Fԅ&9T)3d3],\i۳%'5"/ ̗N:?꣪7Z= - R.rWu>fQmo#EO  a2{w%~c68{E?9wᅋm 77/߰]:Ż |GïIi$bڎ/"H蘗֋^k\G5U$I@Z¨DXā  8:A)He#B-w ~WS+`Խf`ƬׂOTw+ƛ"eAQ#B* zgl`~싫Q-WTpyy.ϫ{i0ǵc+^[>˓V;aԥ˜K8+t{_x0}vމ|o_Њse `~! _]HJvY$I@z78-M;++9z\)G垿REm"bNlEP&fE|pKM즗}Le4(?ĆN`NAe=%Y!f-GrgZ ,SdYgFV7sX@ Oj+;9RX}eZwsyZ{ֿQW: wƟ,sI$̂E2/,13gi5т}e5䞿æS_)_+Gkj?8<3vm F d%I@$Ikk(1.^Vzrs7T--A25Q?%QTR\˂!25zD E7% ܤ 2%.:..|{/IY5qj^U\__t)(\'ަΗ3Q._ٹVKK %ܗn"_I&ioeji0SGCB7SB7MK+HY(*mTeh'aRWDRuLͮ ^PgtmOYvЃ|>FLc@j ^=,uHZƠS&z~w > ӿºcù _up`ɬ_ER$I@n{20=)XV e N?g(W)O..=},+1o9z`6/KۅȄ\9ݑĽ˷ƈ^ygP}^鎿)x3g`{O=_Vt>3+X\;qFٝaAoY6`ι.zYAs7M [( H$ H@07f!X]_zq!w*(ӓJnAe}TD;4~+srn%Xeabw]YJ)zLw`.ɲL]eNp3ګpV fnq`ĈaK-z4I@$I@>3 ՛+~SWv6q4"پnYP4$$I@$I@IXIR7N@ 8rY$ H$ EnjHwlUI@$I@$Iv" iɶJ7WsI`kuua& mK$W)~h ZN]ř\!k}n[lq8n>)-UO@]3p|$ $i,$M)K 8 ;7ً6:]UdS^C\*76Z;|vlV2BYr*sSѹros'/F[i"ێ|$Vzw|V)6n!~g[Ƿyd%I6%pt͖ NTN+Cl}YSVf' / N]9GL _CQsP|*b3B?׹ǒ=vo3B.Z,;WRD9g˫y, H@OBOP2$;9h#b~hhPt "#GO-pCh@zf1,H]#T؋bsEQ_̝il G $²(m@'Hx=g>ޤ¹YU]> sR廮~{rx;983ޭr]r!r1F&Xqvܑ^??)e6R:;Jsvr6lTn]a?[|*☖Eв/+rTPN^ gc rU&jD_>*a;[ԗճ;qIP4; O A7G24sWTdd԰3'ɣ44 &< >3p^lHzrY #O9|'bd -Dev?opYSf%cI*:؏Wv>1o棨{"[>$K*%vG>&=;a0嗢/p̕k]thz YgRNI32Aʋ$ |  kE%P%\`65lmA|L? pTwfDeC ټz-DT(=P#)PJ4UFAx/ˢtX̳Pf)ltmTurץ[)q>- yTm0 `E1J9^Ku5#beSȞAm+SK4DRŹ̺ak'~.ʃ>34c><=PSVmCនGq(~2:l]7N-*gHE݌m.ܶ&!9#;lGͫeBay!L|V%q,Z|ؾzDlY?^¬l>B9F$=}=3P$# C)Iw&](8DWn;]>IQq Λh6 Qp b@U|!d۞ns ]bsSh1lj(˥+ h9KM1pZrxeP:I.rrȃ f:b 38y ֳq QK9 ˉOOK _MC+]@LU[q!iib0*Ƭ!jZ,BQ5X;?T9M}A524s^ sXJ肵 ~ͪr^{B' g*.xfrQ1fķULO)g S}ֈÊ?QKG\H<řM-.tƠF7{b(6fO?z5vwvt2ߎ{+NɃ'ڿ9X$! (Hw*j6+8PQM\T^wt"q-؄5Fa%,(IVfRDCbؾ7\ZL "&a*2 (^ vACG(-s,I}>QnN4y[3J լوyHU}g<dnRJcG35@q 1z,U[U_ʹ<\g2}ė:"I ch ýLpEY:L:L L^>gpph4J< ]b6Op(foB3/ EK7a*D1do) H@ng O-n :ĕR)PEHHyUHVnEUE)J֔u͡zmۇmVmh3F/5F̝Bղ ¯.,y\hKݵ`;ع2L)jyQ/`%D9:Ml2}[G]H9qJ Nͯeh\$VU\GhgIpi\`gW?DAR{jP#> j6$)Q% QN+X!"_xoFA42(RcE8u6M'0- eF;aa~P33ȷV+X+EHt٢b|F6W\p5|AܝTyW.UtG 7TBblZXtZW ]W0s),\%5eC>krmK\B;Afga_ yE$n5?#!E| -\$ҝ/@jQ11b) RHwz˨H3@z FT㱭:kVp j4JXzaD]"d yrpbĴ)=~DLTjk˰KSMD bz1Qj>$F[QY_tӓY-Nw4T--A17iIO*Ѱc, (ׄS)UOoq++Q}p?z&z1S_p%[ ,_BvvJ`&2V(3:сNH%Ndj? ˔LSr#Tpo\ZLx`n bEIX ˙lABdE aBwN` g6⒙ d ^Vq<3 4YUCnsOV'T⎫AQYӢˆ0:1Ptgk = I/7`Cqz&$h3 ar} %Mљ4*1'T#_r?ǔK!|.a73gkG8GmW]O@8:MٯF~mWD{j7߬o,! Hw5^52z\sV@ 8ಌДR7xKs`=Ӏ엲W_ OMuam"#ЛPr2wXy$ֆfcҬ=玎waZjOҿ7i[g]ҏܜυܬhwH)0:.f)5F]%mE͹芒r)J7 |fkiJkͯl.)o5-],K+T- dm/ZA pI$OֈYsIF2^qswܭ5mBgrٽpsB=GUTtWXڵ bݪX/q+܁MkQH?ۈ3( m8Hyj BΥH׌2Εh +raYK$yN~خQDKz@WsWXznZ@/?d*&M܈蚤 䪾ՉL# )O9Y{"bFAD 0PO[mCY';avg)$ۅ-a,XUUsu Lu4TBMHe88%ƆC\eٸ>l٨j.q\PH}ЛH);ls3ǔQ~+hMY!RyTo'4h(hiY.n$( V:nB>]3 b,PJGS.1ѷ"&O9{>K.UE015a: )E&I@$I@$[wCVe B֖sӧ2nVXC飯󆹩G.&$ro!IGq-32"PeЬ| f3Q&WF27r)Y_1-\Ba5֯Cʴ(88{SBE+KV ?$ H$ H n,8ۮچtV>;su*!ٱ~MQ5@ԸkMu| r݀i8r4~;:̘M#uVQ-piJ_\\\,{эb&G]Wѭ{*0̎.qfBAᷩM *:%6y3 >8K yB\jók`$ H$ HM&pb0Clý)mo9;7)m?b&OrVm.Fj6C8ITh-ґ+7ZPUĪ)c18ܳ½ Jk梟泴s& VŅpHpwWDX8Y '\8z %Y)TNU #RzG(+6J\_Ƈlߘ۹)\eҸqq(I+HH.$ H$ HK,p]Tf*@ &=3D'iX`fCܤt{&FXݗWpҨʴЛVKEϡ T w2MqGW¹8FD&D⎬ aⲁp6Rʈ/+S]ozh^frM aCG`[(%suJQLӨ.6"bKSQePEMQvp*CٸQV &ڑg#R4pK_2SX2$ H$ HCĭӜ.Z"D}͚u9J h*gC+#@~8} v 6|' ̝BBd\֨k s@u"g~Du4hM!O.%ƀ. gd#H s.(Oy&d1#;s {G{"d :]ϯt 9[<$I@$Iv&psgS4NFO8"C+%hʞ+6F;JR6Uˊ?,zt7Q%\01Su芾GvcC:3ܮ` B'!~$ H$ H$pK[7=W7ؾA ֙QvȊz6Iz.I@$I@$GrCz}@ |K$ H$pɫ!2dC$I@$I@$Ii,t"O%I@$I@$I@% &H$ H$ H@PXE^$I@$I@$i,w@$I@$I@BP,$ H$ H$ HcA$ H$ H$ܔ-(yQ ?`o}z1xCnH/hGW"0~t׻_Oߋ֡W8s8Z=Eu~~GNrjG͈3 cĝr_0x ~u6,H "o%xp#K$ Hi,bD6G@˟È -j>s}`g8sJuhq|8]m ߯ ,_BX^p,u ~Sj}N(˘4B.~N⏩# ;c\毾q{kل;|]\=FG߽y}qD߇I=>tAGޖ$I@Ipj%hC8lQF/ƃsW$K?>ѯ>BT}4ݣ-GXS:|+_a(1zObq}[~_m8B9v폨U{x0 Rԍз?gd$I@;Hcxβw 4/q#unCAt~396QVx.E '=#W(x0yeS#4GnID-S4PN=5#) hy}E lw#jt+ɝ׫%.F+JE_{#銪o=T@_,N5~cSgΔG nl&^31{s>-YiGTr$ќ4>i'+`~2>9 )Zbz~]G+[wb (Ͻ^ gg8@?(y &z\xz<2o(+XK(!+=\p_^)l0\d#zRd$I@ |I۠{F@0PI$Qqin6 Y>ƇᯰICa;N :!Z/aGgSC/ڻЗu* -/aV\wObߋ7f<7FBQio+< c>8#lOi9{aM9/񳷏wi; sbƖX}ކh0>Bs!aq.gξC~%e#asu=_̅p~9ב_Q~ω}I={PObc|@᩶/J>i(W_"`ClV;t fҐxlEyWUz o!#%‹:._||~4  }J9$`8~}~$ht=J$,o[Y cq=?z%A7s81F~s,%*O]|3h=벝0{IXH2I$ +,;) 8z=ȲmOJ<G>@IMi-D +܋{r>!+=2iĨ 26`o;նmunM4wf̙ .t Dkoa: LjىC'&UB2!rkT:M,OISw j.d&.Nrq4M]l???_~/o6F%l5#qw/ Ԇt~'.w`p C7գK4W}.ᓷ^o}LbOG%uG{(~4"z4 T|||Ю}U;r)~UailG3bo04*VSvgPS*O@gգZ c3óyE$/P#,vc[0{րj8(Ծ1RqUi&ME5_6%c-Aja4ctЇ{wMXS9Gnylj+}˗Nvo⸜~%p@ZyLO}%_->ŏ~u@h*L9-P>!%J0OԚ!יZMᏚI"-8^y!<ޡ S$:EOnv$Oi $?B@!@ @,ka(_w[qSGxaMF&c4 Ҭz5vaTduf=@Kp }4F4ٹԥPo9›)jGsG̕@[0#"9Mjo_ c{ƠQx @qsڟhRE> mÞ74j$Ο6eyо4N1p.6G71//Ncxs{(fN7ߪv>~v9]Z| SpO4B?Q +/3W(.n䊮LX^]_?AyZ)8fF͏2yV+x''2 >GlO @P|M{J.quF5 QpnB#xh',3Nw>F՘Ns0kZmlV ʈEo?~Ѿ^C 2)s੍IK3Q~[YxWĶ) `ߵ1?z-__g!yȷB/X)K\R-Z[J_=N"6u2keY-;1$ ̮A3-Oo?=dm+1.t4#s/ϡ|AC K]0OEy}nc)U|?s4V<2)]a B@N@leyB@! n.=Xg =A iIDATV 3w4统os 4̎1J ,BB@!  ġVK-ΎM[QWޏ 7 s.<ߢq&=ǡ% 3K@܅w[iHBÁ>Rfg^J61x (zv#?Q8mL! Bj&0fHq:j/a}!T1OQ0\v=P^}0c{0:XEo7!tClt{{G߸ڜ^xԵ/-q{s~[j<{x5Sc%'1Em/54_^C8PތnVE ogL> Ws0r}f,&a7^+M4]nldYWm`9*XFr3x}𺝺Т`RLu~rƔ! B@kkl_ps#?e'NnNlZL&4i4ސ[Q7T7v0в}poP(HŻe2DsXu=)oW:$zs[#G.=9lG1|zb![y_xEnQ$' !pQQ/|G7 Z ďP%Iu\r%kQ"粄Yw= flZ`u.0d)SY |@G;NFw#B@!pmwalȎhORhHs6鴸ič!e#͕bAݖXő8bak7oE]pQBfXCpѽ^9WgY &=U_#FYߨihx qqmo~~ i |w,V1IVhh b˺b(P Bs39ٿH3zzKbGx6Cu?͙s oZ?3 PsF#ɧ;3)7Qv`o79eQ}tNۈFF( HR=T78tPyU!8*:5 ٓ ݿdrW! Bj'P8p`V7l0c=oԓqkCN,@g**ݍ˫t=:Ǝ߲|y WiD:*J?Ti$ٳ[d}'&Nt[8݂/g>9 qFy sGʷdV %IpvU3P J NS?͟~B"xU#I92{>Kl9G k(xŜuQi.ݕC! B@k%(y睳Z;mBc㷼6BcϵXl_;.#^6*?UW #B@C`„ mp! B@!0\3X,pJӧiE! B@!  :rgG +! B@! ]s,%+c,đs&5# b΄Z*-Hx#qvJr*B@! sԺ.%zlȎhORhHs6Qowt‚rZ_Vr׎p334AQCqJ%/z̰*KzQeVFq? 7oEmAC! B@!pp֕K!I𠞝fSFp` MM4CRF>"4 <@$klEOLMVUǔ@dʮtc<܆njЌu,RãX][?`" (!T! B@\gJ*v Z_7Or'BLw.],;W:ਰ{{#<\P"]J_W(i]er-#_iOC6b`qmAdOYBc NfNJ! B@%08!(D%Q0J.+ 22FC*l1 (T.Ժ舫UPPZT6Ń8æŊ=uz4#[MJ`&j$4@rNL`sVE% #1R1DQ:)w^]:9B@! W%yMhL|'=5DOMɷ·>XoPt(.\ea] ۇ֗JxoۄjWh}P[q/M5ڛ-vhbTV΄B@! J`g,W9y<9fNWn YP1aKM3hn"j!`+9b6riR-ZISNQYY9]umpp՟i,B@k&APHX>%J#+3z(8KCDXYn ! B@R"9dxK%IENDB`trunk-2018.02b/doc/sphinx/fig/localstrain.png000066400000000000000000001645321324306050200210450ustar00rootroot00000000000000PNG  IHDRX"RsRGB pHYs  tIME Z IDATx}w]UΙ^2 $!"(W)*,pU A@DOAiґ"HUH7@ d)k>3gf$03s<{Nuw]_?%V13EryrJ^TTl60}0ԞLEy[ڬṫr`޶fYjZN؋͟7{mg2<4M>q@OѴ1a gZ}]BI9y9UiOr) g<pa.E?RWa3VVdW\{5V'ds<33)u=L:q>3TWW2 [<_ɧB5 aaC,3ǽ?7bG^1r ~_)c0vYڤV6u@8 @! ,Spa0{\f>RJN9&N8`=$aI'0xCs?~/ O ;Mp9-m{zRÄtY K`2=VA~[USt*cZa,20{o$zxIy"rݼ ( !G UEf#&B&r)Na1}2￿4aVuUE,‘<+Vm[~l7 Vj,!ϼfӴʣ7f}1.qR 6@Y>sW3@ڛ [.;́x/{v8c6|f=L 1H4Â9! Cm[1%s}]3vb .'@-;,.B}4 loR03G{ bCD5UщgV 3LT ii) hVu$n> کC>P@RڀP> )\c^ZA@kfVK1Mö . @4 ubƓrb#b{^)e0 TjIjx;64nFD]]4 )'d:qW.at~tTjdpɂ=@ }ڐR d,$ 1OeO'tIqIB̟;;ιܲ-0?WkP0IJp9(p&SQ[[={4EgΜ2gۙ!^|͞L yT4#rJP5,B.:|*SZ}┞, &`=qZݩy7YJcLCZ Pw:Tͻ*+nݬb8ήW_Ƕ-55Uj3mڤ%`+%3I'3͞5U-I'ўkﯿN XUUQmL/֮S˟lXAgt0t_'#f],mu|U* p#eQ B(V[S]5mDSN{X+c>]g^P/\{32= 6OۈҨ.̝3^{,Ϣ^yu-_^Ȳ)TWvwJ9 ${yj q\x ,]<҆;IJ0ĊÌaBχn;l.۹M /9gۙjN9?于n=e Z^swK]Ӥr7gHD_|i\; ^/j️`ZN $eJ8:06B"`3]yY],s.(㆛B0#g uQ'}0>/ڷ|?Ҕ#KmZڢ~sKj:)nV%?Ӥ˖ҰdBЇpN! bֆ5t#N`Jf!6\Oّ%8ӥn,~ -@Y2@Di([WW?ngg 'ORhPIҩ~肃&X_him^*챵Y>DyUR)s|N0T !E>Ob6U*) ` Zڜ* X {2yf](Xd s b)Æaop.97xsђ]23 o?o#mH9x\vEpILp9-jm;uPNiK/-zǭGy_O&ڞa`>J7h̖ A{39)[ ZϸrZN hY܁s=mGH0 /ѽvaƭ~^Ko>WA :hXGn:!Dy>sW^| WeV( .'6^ϵ>z;0Q}}mU5΀3:`wTI^pq] ,/H4Y i P@@TD|y `ʵZj+sy_/kcJuu5c%)讣g*[!w-~wgqm/xtן\î;,sߊc?(G̢sa3awz2Oem_뢹ήnU_c;,2)&ظB \ yKwo;:#,v=/OtPv?g4M8zew0>|>yީ_=GpF؈uJX 7騔I%V@P*VS]g]m]7\>~>-wݻo ']IqWnSN'xX& &׎ɗObV< =3' dyy ik*Zoׇ8WVVl2^jmkl4 dL1#YD$H^O" .'֥cdʵYl9G<Მ<Ģq˔l"N(i0DQjb .Fq_?.Yɫ}u`ᤉ|dIˣ0F2'<5po4\ CKrb[kΨ7Cʦrb[.!ZZ'F~͉ ė  Ip9P,::ɸeܰ.\rb~0=>DDTn#0|Y:5ޑH`9wS>@5nr1+-m6>rv{IJ,YxtP0o&֊%A 1>ގ 1 1"U!4==t-+,7՟<-%)!sf-! .'6=S&Nhm(X+i06Sn aBpˆF]6STMUUisі.$.c='(K^lBRxr23fN.ybOOϵT*G&iopeI'%LS-v\Nl+kiQԲ$zWJUU[H1 b1z˟GC0? N]Bn3Հ T̀p&`~1#Vښҫe׶-h%X'q3t&'>xr$YCE?b hԠ -&Mp#3Yz4*j[bAh}^5 Hp9ˉapagQf"'vh4F(J>Yy D@}1P&Y4˘;f}/6 ' .'6(:'0-I%et?:X%׊&}hhtI݀T;tHrGA@/>#Y[WUgytg O,1>Pdq; {M0$NTfZrM^e+!u15UDCݡS0eD$lITy׿:g\/8I]$ sDXm.@mL%͝G V; I zso(1@G ~ʸhyJd .^9~jn3l'x,ti)289bĜƞHIɖf+ {yӈ6d ѕ\Nlhxfs8HFG.>%oe0EsIc(3&Ò0Wp0 = nBRLuyXOaZyˆtwY|r .'6¸_ e*ͷҐ~EaA!u>* 0>Fa1ě0j@ka^So$+Dr{?Rp`f4H+l3cNCŚ@ڂH]slԻ G*b@2WAe`e!&"5h>NrƊe#NET%%y܉Q9vp]ۂTC)Kp=2U3|k&ߜo$|fm&iL$~YeERrQg~qL#`,LZ@0FfsoBz .'6 \#*39@0T`!ϭ{K+`IbGSї,_VX\7rᮁ6`}Qt >n0 FJ&Ko*,UK)#3[Y|=t@>pzyԚQ6SY``) ŭ?Fx; gber'<&a.a&`|Ljpa=9W ֏* 6 9 BCLhb/WϘ%cX ކ5'֟orby= 2Bz >'4\^a@ڈ!=@26jˇL;3͙1=CoKV;U{SmpRy|_`Z 姅;#v;‚ MjAQaN֮뀵@@gzCfYdסgdMwF@¡bQ7'їXx24|?j~'d0 *C蠒 @T#R"; T@o-MR#H#.;Ŵu1B1}%|9=N^C((*gY0ہ 8F#6 p%rȶ!cX]>lDC(Q6 # ɀ-Jp9\7n^(S~ l-@v G,hA^iG7_7y\*P&G=؎V#dKp9m> H3 .Hn܇E0 }5EU*&4d9I"}$"A'Xg[dΈ8J tQfXF'?>jx(s@ox᥄'r .'հ~f8p-D8!u~qnp>X  JŃH+V I-4ptQ3 PL|NC|Y^x0Brb%ن m,0!rfbo" nnj^YY'cJH x+tr"BUA pN0QDA)8(}}Yy xp6 .'6Z]x*DmtyEJn/72>rb24?бaB* @>C9wXr9XJ 0hG ~0"r% 슂GEpĂGdA1C4%v|?si ox$rdrB$ˏ .ncf49h1#g$. …K$~M9> VH͡߇db$_S C2+Eh4Gce$[AuҢ蛸)x̺X= é=zHч'?ѭ`i?LZ1 @\ S/fNfd@U`pȸ`Bӎ!a#E 2G@0 zW) W}6"unz`>v=|CkvzJuvKT에[bG^?VF+vNʎ^-^dr1E~ɢaKV\Nlc! |aLBnUȌB23'xbHF iVVڂA(#D[INxdPc|!"lHU  HAZKX> ٲߟ&$t96ƾI6 $$AdxuuFq}5tlgu0$z  TX16R[%tE>tQ_^gG IdR]$rbBL8aHOn vCZ fL\4蛴 mPF 89JsSGcgb(@:gn*sBkse$ذi̘6i0~e("pJE"dW C [I&8}dW:T~a&e 3䍦ZzeOz+}RIgn%0Īw9\NlUb2RD݂ !eQM%9S;f@/AЀ,Ao(Tꁉ#-Yc@:lpF?VNJ7PHt.z 6 sJrRDGʮ.wߢokܔk`f)egWF6?> .'V`=@w-۬vzA>\Gȼ ^؇K ̅7%d`6PVH{Yű^7Ћ{瘖"@q} ǖsv\cQqٍ}3 Pr{Gȃ\7~'GDR'wDwݿ?rS>DTW[}. zAJSy8J)O;G:X/{bl3f !` $I# Uh} / epW=J6}be0=wJd d}EaxψZC*')ĊRzeA%H&҇șyӾcpY>kTNRѣRBQ-[zSH3s. Bl "Z'3|?Hpy_餜Z"ݜ 䙳* R`A$ 'ӂt߀\-~o}ȿ W/m i@>ct+l^,?'bl,P%8raIEY$7)SrΎADee|޵,C&~#Sίeɻ89vaԩ5Օ&Ny~mmgΜ))"/Tʙ?w]CĐ sn 񽤔D4gsښmfM5 c=^1mf7Lf oy&eFɦ~֫0jNCDʀ+%$F'7~=Y QJ^~E {Y7v򞾫`(D6|-_ȘS 8vD$+,6ݕ— ѡ0B2Z~j{g7|_gΘrgp-[z'Y֭oܷYZUY1{.{7xw߳wϢ3gL~GkDoka˯jܟ^D4\M-}|4 \K~x gW_{<}-\z}SS6?k}c=hmcXEɆ.$C1OB 6BPוB^Ra) ! Y C@0@/oJ0 )6:𺔳ءdLDRnkB}[erǟ==bڟ_v=w\ ]S]N|b6\?oVUWz7yR㵿>.~,F 8UUݗ\O43=GzWR?@:η;BXqMM-'745̡zۃA(W yTWwWO=ޱmW\O<,1FJb1*hxLI b% NLHg#= DҝeQ2 [@o y>.(+SJ(d>f]si=Ze6'w,{fM_9g~_L:3 /aۦ>䀫JٶռEy "x̞5{<x7S .V-9w{ wnmfuc_D;`ʰlVW,BWa"6:cQLWC@RH}rKe6I-ybc}KKH {Z?|9Lgdײp]\2{2 ZcFmMմikkrdgOOS)^]]qdm=_J3o9xOM ~EBq] й@%}B F~%<@A8%)g 7VkK Q=p^ v{3+ Gp4^su1clDLɧ_N8͓Y{s']s_r\[ ~aϾw斶=v߱:$3{%y#+bB ND&|U0@ɽeeI_ uHH@0.dYH*Uqrg8kΫƨ(;]]u{y}$ `oOܹ~Oʁsλ'~ߞ;ް=> *) !~Mmwpv7gobEo/ p̹ dv= c&L}op)]{u?;Cf͘pq DI{oRqȁՠv0o0ɡ! $:'B@ %` z`[ NL޹1Yt *CJ .%(@& ԝ'LG7yܷzq;0 ~zzUX g a rNOG|N?ȃ;/X7f{}fG8%^__W}wg\aC{yY !oS]]ɟ;4s~r!0-B03Zդ|~/?ƛK._]zzݫ֬?=y#F6i1V3[==~w `}96utC" 61Th -Y0 b >vFa|?xxEܳ-δ(qc=:$؄B{uKf_{3G?_nĿCs= -tj)zUE}+#rƶX[jj ``ɰ"*rXQ@3m`l@NwZx0P46甞bnp d CSZԗd.2AW[ҵ"-gAV3/< }GS y%\XJp9̰1)72 =]u X EwTي2LxNvvc减;:RŎt)W/(vNQ$Â˒ujy)yJoJx[RL9SMM{GPeZd"$ x}=_N~b|C*(z=Nj@Z e+B{1bx$Ͷ ;󯌄q`P;{PPOR(@),x<YXj`1 2 #  LJ>QJ5b7U.4"t2\ 4*" OŒJhkl߾_?^׷0ˢ 0>l{ߋxλeWN93g]D;|?eDN97]m[K8Gn>2M3Ć}} XBvè` Q·CD\,~0fV9V(i<癐kQIv;VJ ܖcCρ+7&vlH걋ANW;\: rrSbn{ش0kaUA0,%z"#R.=A -gk8ӎy((^ݯbCovM @9$Kk]=;-{)?ZҎι>>J+Ps~oӶ(A?u?x=vK~EW_sO:e]eJRJ/? ~r5+N‘1P_鯾h 9;6?#24/, @:LEąkگ"pĤޢ+/:Zrй5bEvxּ~9YN9a:Ici@C?# %锖$2VMd26'RW-_vqUr?Σ?!S]={MMr{; mRc:wݷ_uꗏaR|kb6BO/v)H <뇧`94"z 3 Zerv< ^ 阫+\d,u((C}hvyy>:/3/~'3,?i'+rUU?8R){\VkKfϜr/R?4!:!M@;;n7Ǟ3 rb% ,um $zxt^`` p(STL&veBcHM}eTdRFpeǴ #!,]Z((2Body:ӆ> :K7zrCCݳϽ< 2s[[g}wEzfc0m^ۯN/C.7 xɲt.f2sŬYSz ˈbF+k{+Z>-meW8 [#l˖2q ‚~9–bL R!;*X! + Npt"P ̍=- q aID&4.^VIP@5Pkt L<_Uǔkҗ:H#3O%QIFHM<~BݒG0q܎~ _;)S?{K)8k=\4rt. <1wY,y4o~":_K':ͥﯜ:e☾,;`0 7` hZ&=%'> />FZ܇ouj秠\7$9&p1 HGf%80$`m IDAT3>e-Σ=bHPWXTD@@%`@=YO͎=:hYSrܫ r VDZ/?{ ǘyDžs|ņ zOb\<#N8CH)ݴs`)o0 2j )49U8uBJO># 7lCF 9 ~5l u  d@NP9c-ƙ@o50F_.gkA[`U &X &k@w;*3Ve2DWV^Ajce.=<Bw̌XZ.ʧ̆ap%>=Qi/UTxgƮH~AUB `;SP!r r¤*PU L@Dk5HåC1|~^C>vkp Ca) 96{ dH. >X/|&6"v҉OM:|)iB/u&$|y3H,#C8ݱ<0wtRffX."8Y8Uq9k** R%r}?JP$s;@g6ՕR+'}>"XBr͡ek<9w*pIrZb >)LB7jBB K!*Z 3O#ȑӈ]!:B  uIT# .ʴcB@Ɉq>Ru&*gnX-r I;?BH9&Y31);wViK'H PeCR2IS@ M&>V4B#0˟zr:@!dIE9kF14f?. @ւU: c#Rjb3GJJPc0wd\͔9#= 3_|eŪ!_D4 e> Bf|ڀ FU8@>Qokŗ!eHkqV,q9ZeJ qN,r/ t:(:+ 갊% 1N@|E ڱDlƤyly]JHD9K"v6`0lL`r9bB5zzWʁ&!_N|L!!l sxZy~E, &()Y_ШͅBSiUf;7+V9r:YxٞvV9ixZZ) 4I綕XI4 ꇙrbEHF Nڽ!D+֔Y_@ ׅ#U ,υiͰ@;BXC#a9j,@ 2]j9 5@i G'qFL R4b n_7E_8VHѕ˹\ |ek{mYc[8o-@94Lt#hGuj!BPV`rXO d˰.FXI0D! "!5b |2}"Š%4kB&SӠXZŏZ5b@e_4DoCu:+XzmCC#H׮o1BD_y4ۯ ~ ̜)D28`6 u[\ ZBȘԠQމ9\LఆT@EL5 kWR cł1'bׅ/ Z`x@F^0n0bc}@_/F%S.U,QpaMT?ʯ=˛o2c8Ϗ(X0Rn@P.Q XaS[P9rx@VQF05 > ;TŐJFG`+d?Vg:F-ͅ9Q@/y lỲ^bHX_ 1ZuB>Gp&å(B>Soݱ4+[o{`msz .'6JMt]c/6@ # Bcr*- ț0%,BZ Nl< 96c:Z ` P#VH1 #ɾgJsڹRnH#hCc98s.N|p`|q81` O~ʰL))xdI<Q^EZh'!5%m#,Vy(Mp=BX`wOc#.vXyyR+N ZTW:_J GnBdn 싖K`鳟F$9u;0BGd6dq&aȝw\8;"FqlDZ-kKwđrh|DZ+RA|vOs"itc"VT̳}ܽHԤ\2:(5eF (*x3wZ{ݏ.b(a#\^jhX(gh:̽ןI..pyrs*z%Mde)/+{ߡM2eo* qFC4@hC MX%i25eoC&sk4OR2[0:vAؾ,E-F9dL M_sj5(ǹU?uЏirwZp=1o~g+By316J5}It[< e4-V5FL$$h2c+^ kPGРu9x7@ɵȈ )mS s ^87E8}%g&:;M& fp.uA)$Iwܧ9R=늢xeqASUN%a+5+>z, ڀ6{bFW\e1eH6ۄ&4&i +$2OPeKv 7k˱ }ܯ oiRZ0lpgt#] oi.V~a˅$2拠̹곞~38_gNm~%/(3$M~?g>}Ccr27 {;T$Ph!ШQHHӴ98h InqRl<'H("ݴGWFyh b4$'k$FGwrx&3p!E򳴓A^-s#GӜ!`-?.eH{v7&u볟#1 _#m:_?/Zc5D|y?|vnlJ,s ٘l_exgYF$:5,nW[IgɅԠ=GmKqv|IiaWplD AVCu zz:MMt^✦!ruHV΅Xۆ 7Oݜ V-Z׋EhW0Tᆫ'Tl3VҩcJrV"V;Оy,RB!)ۅMzz81Ԑ:+Bi6$#" $L(rOd3.:}1/% 2~(YNrXl<q&9r=nayX{+ٕ9$= ""oJlVRoտOIZlu2IIj(u"R0b kb)#]"d: I[STTg1ۃiDQ-I;E$$sed}z2\<4޷JKgn"FϿGzw)/Cqp3ZSsfT5ʥaz6c1ELZ차 eY )VP+*l+xOKV.$̚)M!^NP>cR+:[](92(i or *帟"?{˅"/?2 xB0>>2=5ߛicFϗۿz/[uU ;oTJZ]&C\4TY&6(j^g̡W)(c1t٭hƝ> h~䀺+82.-pB L䆉9Q? ]qg$urbŏyGcwv69B/6XwRԲv3=5199T=wր-S'nz#?~g?M#9DlڬrNwn+l.((NIaJ)*]ecf"QE/Y_G2aK 34`MOoF}Ͻ-7F;e=p6 9@a}lSR.z۝<ο!?K9_/wg|c7-.{o_nno~>e neD|Ya]B #Ci4&mHRJ)0CA a~%]CxmojBPfs+sY@mhHeT^AčGUt͈hBGbbcw''6|, _}W|S=z﹪z 6:rdɶ#KqR-KT*l҃UREр鏎u[yS2d\ e?;ulnf|Ps!bNͺ"q[Ī?X2a;w٪wshm!]goKOw e(q샧&( Sc VRvha5?mS LVi%) $BiC)Phꇸ<\g8;YVZPc-8 'VHiB:1%b 7ĬleItrBA M s>2|%D=˨'}Rk?>1u?,M6z@_r4ܥ ~!-Α]%ґ*Xh YIU@=j`[=Da+qJDÄ6a 9hk+?37닌y#dz6chrGSHo ^*Ϣ*ߟ C9l >\NZ= |M_[7ߑtV|pc'HГhm6چ>"1e".q.+rDpA6,ԐQnO qX #9"뜃 ϵDl*o»e̤?T[q4P<ӷSJR.nC?ƥ7 EEe+Om,ar-E@mA{ E4@CKX@a7͈"H)}ݐeUwpa2iՎDz`ĂQCBv>o}.NZ֪ٚ I??2.ÊR;uR<6#5\LO;K)ص8"NzWTfv 3 ǖG/͡ff&tMH;} RpJ.d']N[V8.L"F,ql-\s3OłxH8*rL8Yj0K)E!.T1u] c2 i>/x*JѨMr z4hUg8Ϻ]E#yGJ^9)]oˏ_hwNxCm1,i6CKjD,[DGS+Rn,6iJ35yrt{]\aI8F8,22E]8LѶ`*9> FauÜ_t(2 i4ZV[R.:I.TGǾl/ZYo>8.Lg+7~8$$XDkҹ*ID}"'qOrEGozI&wa99XA>:0J 5XA.^ @B*, ws 9oZӧ"rwk.ӆVn̶z1|*yus3&'N>Qy-ri"k3Irq &> 5#4,eF++W5M:!c1?í5d%{>F#e aY:⡔B A[B1ðC|T4 ry!@f?SsjE0wHͱy9č.H! 4k}td o}?ؚf7響K_:g=~/BO|£\/i%?=g.3\Õ?#JX5g.53)THfrnn5مtڂ4`%f:ѱ7Ogv;4Sͧ505 t 4@khTHF!iڳ$62,"+P ۦoߥ}Ʃo9I[_|tfwskrFpС"\ `XO=sQ˰+4[f'hs/칟_7NYT&bOyt󗦧&.۱Ε{P{Y*,qLف`A)E ҕ:]*4%Ks.RIrFkA#p`>}^siK "eFB'FzVs4!g$%#,95# Gϣ33û0͐7]ܨDTWks}v]cͥPZu+_o}"N'KNwCjcWčRA͑ynvN5)l٦i! ZD*0ʗo'^čqg-XUf5%(]b=*,㈅##+Y7A]G ,CrzیJgۀ^_jIY+|lHr.s;ɹ_T%w딩I~ <-cE<ܟeXjZEoj3m?{ͯ՚\4܇˜ƪZ @Ӱ&nq$2*KiC5dr݂L0 YZ~ a@@4E-hYtĚ2a6QPE22df`F7Gs9k#%PAudH FNI vra:yyVo-{U|ύ8O_=KSu۶}歳bpyrr礨˝tĥniPp/Gd3X TFfY1Ed ֐+&.T-cn6(b:0cb'F!d M01a2cb91(ksk.7.f|ϠE>f_%rwq~qrǃ|hi]}aJUDW۶~G_zNR1 f}y}_ok_rR46 ^']w88>^-C@7E hIDfa@kH= !ۚsy]0 ;`Dn0SA>_)dјYeLr4dklqNN;|މScϬJv-=4ܟj>}3NV8oxm3Sxۯ-,,8$?It:'3Gq+oyg?wkT<yX+Mu3eCy!^.O4Մ;Pi6P+wQo0%,jHqUi.x,+Ga*e(Y`TFPQa4CW9枦w`A FR&f\a i$fj1ê:Ư9ESK46 Y+6E~PF?;߀+[Mn]yV}_e?|%;߿Nu$כ:,./\,ħ>}\*^p؉O~8葎cYQwkSǚJLrc 5rgUNv`LK&vҁPR^C9'!UXّ: .cD@L OPq"q}k|p1#b9[?@E!Y~tU,;o{{u|l~O\.? Auq[dgns8.NmX11˞2;9Į@m%V$Kǹj WeA<J"@cU8VHV/Δ7Ũ}tFh79 R" 7ȾɭOMPxPzANX7is'/ĤzVS$IWs=q^rSm^/ɗun~OpFx8Ha~d;@smx; P$ː p^:)sK6@&uYMݴeD1`װ,Hr9C<<|u*>g q7ٶy, h(sE, .4ayo;Th $]{it(*Dh*PH94Kݤ K /L9'Rb<5r W2 GmfX3 Ǩ 28j2ISYKtn+E=@+g׹.LdE;햦'/[7xO}6ӨnX%2Np6>*8lH JZ!".5fт-uW2adq%"e*"֎"ؤ`%H.F&a.ԱXX["&]aUB-B*ӴcګI&m:=Q=ÅY/2x1(xύ*4-}$^21.;U 5!._iޘr%v=X)q:ߎBԡiѶ)$EۥMGe,e[6 Z"ͶqH^t!L(]NFRͅgdtsf1g*F@F:8uڱ,qvR)2}uP5<ۘ!okpv;&a?5Y}w^Vr ue93dR9݇a}.+;IRD@O=5YaR@š(+ DRdf$IqN"A'ML$,tͷM]-98TJ |G !$ƶec_[l'`wMz팃gQsb嚁ٯ8;w񽷪 wJ ۷Mm}w/_EXt rKxbZJ;TeFhr [<O"DIﶽj%_f'6 .!U$Of5'W&ӃvNRؠ6(N )'I ܥsבmƛBVaw7x/xG= ώRCcc *@Đ(Rv.FDe>rlb1X1oC{;10֐E 贑 !* :8ev<uIR\)Ք1邃 b'FlRA+ Yo5a̩n˿ ABNuP`CQ<єg?~B fɜt|;M7sK)p9?D5d)W۹lOr=^Hp>Xi./^*mMm{߅ .*AQ9"ro aLA.iԖQ!Bg8Y|BJ1y)%ԇ 0 ( vQ1:,e:.@BXs@v!Dc 16)$h6$uRGj-"u cEA' ӜAv HLy).L/fr)L߆0WmwϾ]?_[D$tSuUDɺ|P`ė|(=5'{f)Yh_mh//eС'1Ujz}oRƆD{`qmsC{4J1t(xBwY;|kmjoy4Ct< Iٌ@4!:pt'у F֘JSȽ8_fML`P H~ks_WdM֖x>#ӏE& 9qCϽ3kHw39fvj4qR|OSO'"u]u㸎X'iz9}x5<|T|Il#ndݓ1_>Lv1N,BhtaEf31PB}Cӕ#sU<(nar6,4 &!%N+GgLLC,TTT bj.#3cfs:hIP IDATK{oy[?Gt Fg;F /Vʦt].}zڟmR1eMo}/K-xcJM!J9b'Ǻ|0ySu %+aQO+1UA=K[SeX.$ff|ވcFeDGT&9R؆QBA(z˨ng;XE2m3 $CpR~U7O~z5#֮{:+xoLYHܡ:s/&I>ϟZ^kX~+п\!.WfM5TII,j[}J'\Iaz4!j=B K$,1ӲTAfٌע {C~HІQ+8anH98Uh% L’= -,N6e2-?{W8+XH/ a:R>5t]<4GbXȻ/ Š믎wOW!DZ/3˱ -.R %D@HW@bM~oxD84uORuiTXt Heg\7̄5SaMH(@@m$lͬwط3yW|!'H-|1aͭCacBd]O)Ȣ*&6Isq*|?/>yӡ?wOٜj+'_3?co說j-1Ec!WlV[^ UUrx'>C\\ox7dwIUP + 1x0,mp ДsdS˺, $9¢J͂1(`H H `Ɣ.ׇL!-Cr̘Pp \\\ W> ,z ]cS@v*BiAT:4bP5׎[fgfM^wWrNJ4MF+y[כ8=5>:R.[wPǸtcޙ&(mOH&F 7A`r`,ΐ*@)([g,@Lf"(Ad⚙Q@8tL#alTpj!A<\*}Ωݷ MfYE~ A@_ H"Ȩ(* $0$@Ft'Cgǩ]]o$tZ}鮾T}w],T@`=T`UH৬](P9(27)+r̡y38 ;}n=fmRIzǐzK(HM.)ϹO{ƗlS h!ȑ'x+lg/{/c~͈_|>$4uʓ'=2(: Y|K&Z|YrB͖ ̆dQT%dUұ`҄d!PsAޝ.Z56=Pȯ#3 4a^sBC4wH LrWLBЂPXpަYNom:<!#!4 " :vr`z?܁ CF^$"YtfWIil\^ C>14ㅦ:"Gis'Ɵn"I[|?0TO}\겏p=JZ{XA?W)mOptF}'Q[B>2eu$CY!3&홙{w0)(q-H,k3fͺ3Iy8*t r!]hBNx{ iIA4Qv5tΘBj hǭ8"db x$ > , c0$FjϽg3׭/X2'9խ݋?g>eQB }L+:{_/Ԣ(Z (2B$( Mçc"-m,:7!lV&o@bsLԘ_E۵_,q)FK]tφЄP,#1j!fXPyǽÐWg 8(z-1go1BAF,an5rty%m1mbǎ?ܚ /xSn}{Ȑ}4ݧ* lq)sOje0 [Qk@v~B ZM*@rYNL97NuN| C`)C ٗٯ(37xU2^__OUr^6<+|R5-޵83gloU#,q="'L,T@N| Xt>ih; 1ZGty)aHD&ă Hhl;EEs 3xJh9Q)2 m@PG}.`j:9q_F3Md)w1f_֭~ԅy㟼/B*ƿXz Wh>.Sa9y@nД#!hzXֳ2[eV$|jisИd6mdF(nZ!nRu'snIs`RbG0B 2Mv4YzTFXnBAnX,4=~PV_A*؉BSIUk/.=XqIIyQܻc=^iڿQݻo/?\V!g ѥ ɷ|3/u) +TՈ"; u x,jc9j(N1&LUz"noʽ/~;/~ֳ6?0| ؓˠUUi/.}d<#ZWܕB$Q;nh4-! ^gt>%GD{gH|0i ӃGBSP8ڵ_kꡉf"(H %3+ A(L9u"KbW xҐ 3!10۱NK1{={E_R w^|>[.^}ϻ7Kزd.|.;P)D˧SnzHrw1s /s\.LDx~`=Pz%f_G!^Y@1$] QU Ĭ\k*Z.q̓4j4R\<{dqjnPhZmVaF%F7cV6lTYeX e\u\ 1O@@L_>Z|]Ü: ia|_!$ |D۷_Ώ$rrb#XY1|y=cL\ϔNb UK"aE5Jm؇ao_XA<1{3 (  *pnGBL܆*0 @KBn:tuoC*x@ LU" Y,PX8cv)_z$!h {Y/ߴi^367ڷo+jHK fPq_+\^9]f6^626GzcLMr쩒k4m@<Nc:K4IE Xwfnr8#8 ڇyC Ƚk`w4{Vħ^af;D֟!K5\9[  Kj˺mܻ\+4#Q#G](䧦f/g~)~CϽm.o+R߼V]2xc,bwX\~|⾟$vn;R#?GJ g&xfD  ""{q"֕pC\̉ ,n^hu YG~ N&B nm-CL@Q0Pp/4`9qzJ2#XpSDwHtǚZ3珲#/$OD$? >ݟ/sB!yfnBK/{1z=; /|e˯YgnY; lj%gTEN27UDAhNdG;R.e'Ѓ Ni^'w:pJH-v]5<,c2P {` kaF`DE'L 1R{zl$š9eȓ R{n/hY=\ׯ.;ѹ$^eI({gfܯR VPa-k&6IHX c$ ; g-($H/θS{<)8Y I(E "d,- njPz=  âSΎؗ ;V !LC:0f?yj>$&Ӳ`T+l}.%wx>HѥGחVl=rG> FG;?dNPUR*UԘY~oC;$pGkLbK!O<)@U20`}ut=uDZSY:CRU)C];`q#i_{v; 4ḟ èU\)Iɮ J)E)!$ yʑɢMHFH R㙎rqHoE7KKYf=g>h`t)H8Yh :eXd߭7֤>a+ Y@i0t. hvR<<uĠ4(4gAș;?(siAH'ce}V^5/_/,T+l}|c$wܵsnj /}?4O^g)o=[Ȫ]?OuC8YB^ IOPi^ "P[Ti4]?wU0knDJJD:@K"Ҁ\Ϙph ! ߽n_T;{9Nw[c(l} _>y^byl"2^.iA0BPi-[Rj.CP67I7a5N\D'8AZiE+o\̼q1Ϣi4R-FՉ!m^0'̦ǮeР%ncLyXfDhI A PFaE( rl}H g\!u<'\ l#Q,t?qN^Ro\ZT/93ݽk߸{?I\<t$4؊Z㙡ʖMM][]n' (ړlprp+jmKd T6` feЈl@{d] 'X$3JN:j*zؖc*#%G=.x9ҝ8Жt`!T$[!&䘛Z<$ݫkaLvp$":2D2ԂYCZn]ݍM۹N~gh) TJT##/|ܸ{ D@׺2c z-ۛp]K7|%<UOTԻΖЌɌ,>r]s|4J1 bCI1O6'-.p7 0, ٞjO">K5ЭMs8hky6=?J- ^' - .Â0C v9EvL(BmcT#DnztLdj3ze^3W]}r[d=mg jO(x`;>r1O(^AY}\>UJƬoݸ2?9I`!Xyd۸BInRPMb-:*E4!<Ę]5.?ɱnʅד+/{)\[z9f(,0DeBnsBܯCp BC;PL[gN;6&::-2f䴝9 [m ʣ4$#෿/=^[7rWvwܓ/:FEUzڙYMǗ{oÂV/}\^~&YڪTNJ'9BYħ1Of=bPΤߐP1d #7gHwB7$vvfߵT'0.vG6 j@رP, >ҙMnk|,X0m0h[r lU\@@=]:y4t>А;6xIt5}&_[6y|__S62وi M BehFaKXɴ^4Sг MbL`J`aYR+^ PvYifhLobՎ8 DU!,hӉ^"dM@Aؒbtp9XjQ9Uikܶ}Z+B,6&XkxM}@ָ͗q#cuJ5\5 GF!J6 ÂWi%rcԧQP@'eg]EÉz]- 2CtL-GKtt0\K4Fbv( :݋ga &s5ʐK͏AeH.w SϜw5s۝ZB.G6]yvvy/zcD糕JIcӞUc#SPU=$}\SfӢI}QPPjl*>5)v]6-~^H )$n.q]!cKqO2KzN{vbǾ= ߱Jh]Ҙ2 Vr:) eƶ)0< ,0JY:d =,.L9G9 _zCrfu]iCˤPY0RP*YFFG|_"mR KI][I,B 4gLJ>V"'GXsqݖ3FO;  g]/*AHlD-AeWYl:CCQJC: 痉''J!` Ab(eRP255I^I|>7:2Q&#T*e{.!i?aZB\-]7#{`fxa#RBA2@ְJ08 H1 (!P2(Ԡ&V# G&)f*Ƈ,^]J)c&̥,&*0M0m+UFD:= DN(H$cD>5阵m&aJT b9֤~W{_x³5#+|l/머,]טF[E!",£na·9tc8;A{K漥O8]W&-F}GfCv`-XE<ڣqyD9(40 Q`A3q snU3  p煮|hx+f>FCl-*F68v8<'G-o)$:r%e9'D9}Ք-2 ?vT9L}n`Bv u"C'3NXKlH絬 yҦ5Zق$+"QZQSþCU/q+|l|>w#;w_=b03y?uxx9Qd䠔fֹzO-PvBP&_dz0\VLLl1yqM.C"a: ~Bι7p'L:Ss0madAV*:ikL.b4Ir\X66+"Qj?  fXf~wn ?>_>Ul+RGl a' n&|: Q*LVx|G <*v6sdS ny!k!+cߐ $h;@Tux0m `S<҂:"/QZ`>zl2MrQ=J߿ܯӷb!YLjO0Kh)4|y82P\s@{'e ~uTzHC~Ѓ&LPV)#E؄2&oA|f+k*PrB62i]:$= OqqWcYЈ[d_ }\Q*6I!,z v NMn;2;Inf.vtŵ+涗,J`f10`4_ @OfkAM%tO yW r0nT!ч>N@V1Kl<˪w`^o:L~緟k~oEq򐇭:~&!MifQ,0Ӎ|'(A3EGCr,=7zg0Co@+csH4!5L]a#J$%#Ÿ`B(rp 7i-ksݻU~~(} |&-c*H*7Ԑh1D P㥫:vPC =jC2Kdnϙb < BfY%[) ,İڰ B! Şd;C=NP@Hy.}?sXq7~YQw6կ3$ d 4Iv@Mg5Ơ\be׸ܝ0_=}kõz=v^ImavnэJ6 3$m΄3a;d`1eE%n(Txf_P=>P0?W$uz!D՜.]Ni3X,ސNbh s#]cR#筋"qdKwzٴu(g{$g,@ fa aGa< ,up&N0yXvdȣqa_GRk)T$+́`㻉VU/?E_NlQudsr$ㅅ*J?[Ek jn@Rk6[z(ZQ462$"SS/gG)\:""+VGr)KbZk)݇#&.k{"i59⭚{Ӎ95qzwd©K_;<%#?}چ02B~ +22smM= !Qj6S\6B*9rk~63so|e^h_^7j;}F6u| RaO}/~կ{`G߱~8p-w-/ KjF_Y3\T֮$7}\׉³Rt!;M<A F֠`d]@Nb8:8p$7KCp? g|!Yz6$.T SJTj5 {RͰšz4 N -FIU$_{8Ͻ/|ɟ{8~ûGdž_ֻ/~֏~k.|Co]_Ckc&_/g.}k/pџ/K.y}vÝȇ;0P{mx~xm}}`'`:% a@c16A ]F  |~D#t!] Kx%/y1i*Im4v{b!Egϱ5ĮW&,~;0 )_ ܈ۈU7TsWX56Hٲ~뿯ݹkߚ5c$^z ۮ'<?Ԭ,,T>ɏz;~W[k[}r|>>8%mJUQjoyK_֔qu릡w{>.Ʒyc9'5W@s26o@*xxQb5v<=݅c)=$HwS5l rjuXAfHJ0Nt& 0 .] 9{k]@^o=o(-}75W6՗k8y{_l%::*\&y3Œ($AP0U{\ݨhq]m!=QLxLgY ~,!B=+ BiϭJKlfN5ވgL:=L|O樂 ]v[P ^TⷾxqI#"ֳ7s<뾎q*R)j2/ '?<@!ٍ$Y$[p`! " Œ:JqT&t6\j\URi'L! 4@ d(I6RRDG=EoZთ{FZvmwxPUU1Pi6sSz[ϳiڹ(7_hvl2A{NZF対8Ib?3lO[w5@b߹ǷV}\> xlaCam!m$Bc6Awvf: aZ`o3G?{߿_yG^8I>sӟ^k{]v}˟`oyPSׁ7}aEW/޿?HU?ϧkW~?1VH2oa@ ⨧7!)a YC{Y:+ŐBiXuckesЄR' _QĀ!9ݩ,da*5ij;>]yޟ齯|o&&g^qldxh@>|WVo~_/Tg򒏾 /{?׾osٗ^g p 闞5kG nzfϙ91Oz]ggͱ"$|aYVrϾ ;,LxA1D50MYלp|]ңvf17?]7 ǣ֙cix-(9~<R|c@rQDFG+/Nb/7?ULRnן*kJ)Z/Pȗ˥kwz[:<4˅GG^248||W\C`Wnm{ kVČBcr1df8ئtOӝݩ!1  ,k[F-gf6'ӣ~߿)m!0:Y E(n & '8ɮ L#Ãzh798X^lbX\Cv3xvX̻XdT._G2vWĀ 0 -0Hٍ &5+urlQOO9nn!-Hwt0$4`0& 甏f53+_kkՓ~J$Fq+w_D݁(@ h H C=N Ta IDAT|L܍4}.ʲ6KŠb犊ʼk6m4wGˉ %ru*E8.QqY dI3́xXmG y>2)`">ax՝-Kp.K qmMH9MBrC,Yt4ڃ*5÷?,ܤfpe}xPqRyo4i"tlb5bkFM&`4jDc7DEł Rt؅e6;3;wEl}g?~pwfv}=sTB̩1WlAap VVujAlRL-9}+!^G[d`nF ;&Z\ ; mOoHe: \C"d04sjue( Grt!5áw['<ƦfLy"e ̀+u]TRi~V@I O{€wk\"H nP$7d_{lXPp]{Z2alhrX~¨Ukcf!D,75Nw}bYUPI9u* 'c z!0V@ʀS`/ʺ*͗D F\v ض-vBA[ eDc~BY+ S{a6Moձ-W[LG,ݫŗߺr;_^Mq՚TPf]S7Mhp lb0Bրv銬:}C㌙E܎Bce=T 7wggʥT Bޣ @}˶Mƌ2jONyU4͝9gN'y?Ddq#BWC\v:2D 51 $!d.-BѲ:OdnB,9~[(+B~F$;{pFFov̑O;TʶmWI;H9o..* bYZDC W["_z Dpa/de))ENVvՃ#h'-MǯE4Hh[3<=jȃZ|)1^VPU+SS YtKq32N@;AVB.CD,H̶pu9116 LWV٧p @`Կ×7Ӎ+/oeUOxؾfnvk5vz``9CPXC@# ˀ*f<! `xRZzƬЖ6 /@2kD^FOԲ'׽t3֙n=4xYCO,ha@Vz@]N" V'l.dd z^f6~嶌l6l)tn0$30ɬu XZyy]te mP $CgM 5MMފ?5'2 =jG|t\);W `r$񖡿(KH{0vÁ]` aE$p[;+ Q 0!6AL~Og@2_9ɶaFsn;{9t̹_uNo;&"qjj2e+/YNu*6l(Ш3D1 V[ )P6X /n #Gt:צc=e(M&PV h4 P0>0֌G<3Äiz{_SR'0BY_}/.\GHo+EoQ]r{ =s䫍A ;n ?e!Бksk@( GFXt wa1;6Sͭ5a@$HAH S'x>㇮2,cy?{彖,xj>N ەLa^4U.쪿Tޭ[&w+VKIC,+y]. fa|X3Qt fC@֧.r҅lNL?dm*]C@^Y;~i5ϰv5DVP-U/vڏ=Msg-Bq-\ M3D D"5{MЏ:=Ko4e*lܙTsFOAH> (b`d!b.H, -v =l&#D! ,\',psflg* .ՈJfvt3کOX8w6Ӊ܁u+̳gϻ?廓/8ГzVzYGBӴH$ܱkVZs/k;q@?ώ,ِSaE\T@*>6lܨU f!D94bF @4tq2M 3ٴe2\w_ȩz|U}Ru2,|7cy0cSrvV5bx<:\׳,ۮz»{bŪ<`Hf4͑&])@>?wQv#%f&r5YG ӄ[(Dy|7wꗧN{5kj Ct ?)Xݣ{コzݯ/CiIaD@!ӥ^@7H0]B$w!*7W\ KɼWDd0;`4I!PȈT@Ml9 Y3ߙUPrgڷ::z Ҡ1㉧^9|x7077:D/+tjI,9c-|湩u++&""^[(wz<<0"L󠓀!@.eo.ysVlqPqϊH{F1B #m2)֤9rwSYXm5:k_wlfp9aIv++rxYaݢ{{Ú/ϿУG!Mҿn},:` "\1r8Œ H8K@lp&P4"q:3„YT7(Rz'1$>7w[G_9h\ݱ;h4\Wk6śO8X/+lVmұY ;"JA>ض[ne%pd!H( F$"#ZAo~`_ hpS^_OkE#h Yo k=$]à #C[(ptG,>qa۾x[gkB<73+*_hٴw?),SxYa H9OsԄҢoKoVZRdK R5%!삫ai5 z FD01|$vaܠ%/j6(. p1Rڐ<ķ]x/?{ܢ:G]~I)JassO]~s?asXvp?ٶ1 sДg^SfIeyV>x- 9 w0%Ep8: 6d˿5#i@r{tOZI,ڙ@|F4-JDk2ꠦÅ67Mv,;HDk뉗tUU/xY! MӬjAYCׇ߯|7aMRj#G/-tƐA pD؄D4 LDD PA !&)*ȳ#!_56%ch+Odc{!;0 }ʙ{ԄD*5mG"]u=]WLx #77|euCCSII'e˲'zYh~*0a!C =R@VP@`rͬPϟ?v[v#"TS]Rǔڮqd`ы3S.҄c_NNv^}G:ckF oTU- ,uߵH&SÇ߸L~OBm # Az1rA?^} D:Ê91d/k5ysA=#3ڷq5G3n^uWZs͊g2b nb5MѳϿrUݴ`a?uWr+^V~F&B}Cؽ5dxٜX4u;^#Gh ~;u}Y o۰<#Y )![ b ,#kwِ߭RmYޮB %}+u0N' 3w(Y~:^y[/]{}]ö1I)->p1s7ͼE[K/gc.\@I@3a&j9U/G*ȧthrf\V8UL~WZ95O8hG]tɂa[ Dy=櫅xKcs^yykŗ:k7 wT?:FŶ,`,`ahf„dtGb5RAUDڧ+H,jf,7e+jkӊ+lhN\kᏔQB@-@ ?xe';W_wga빕遱Bs! åp=r̞Eq#<{zZ뀹0r5N}b+tyPZzLDijj?+TjIh玈ŰBhT=>M.>t3G?ڽ'j^W$vvCϤRʐSѧ3c+WVS)4;= z[{o`%')BO,.J:jJ%"VrvoKﳻNq]/Yz{._\(4*IDATE)Ca+7ugN:歷?TB_Sl3#+/LYzP)/?\ ӣ LTʺ3g]UkM6e?+|^zXyoyУWu]WCV%38:_9tCQad~qT.ZʸP>6.ݻ˲l+3:ٛ߬|u,){B47>{oO0^P"".v:ٶo~iVjPbg"4śWUxgآqYeBD=Ec8:g7_qwMɊv rsҒ;|յ7W< qSX!wMMc-|e7>{|ЌMF_}pWkBѱ1@$;f:V#:8ll5z J8hZ]SuɇsuMu(ІR~G2ွGLt/z\|RKԬzhOBwjmX`WY]Tןoe6F_}˯=st$$}_tҕµ jכ}$|?{EL' !Ŀ%5:Ifʧ<|05]I*wsЉ]jF@((^YAD8ku- M8ݓLtԧn?5%"{HWJL&m h/u~MwdYiѵWuue1랙:d_VSԥ'W4)Fy0ҋΜ6H89o0'^P~lMz:Td'9r{S[ H53a2f䐟}8I6Ʌۉ"a/=q!c]O=7}Ƣ*~wCB/]֛3׮oh+^z:@& %ѿf_|$h5ϻ{YhRʁ!g{aB(QBgg$ 7yޥy^[""m4ӿ=^KuK}S rURi M?8.t]-ٶLĞw+u']1ZoF%֕ܜ^zvLHPP>6gfo> el=tޫ7Lѯ q{ܓp8|IxY'It][d|a߷W 憼}Х ؠQB7{]]}cO|嵷ڷuHAB۲3+++~Ƭp(# ;S Q\'">׋i,Yz]#?u9h萁w/MӢ:/+l4xu djMffQd'4u.s 5av1T׬cf!DNN}G}6kWs꺮 Us}CӃG.H$RtcG3sS(rƌ_8\/mk37{xt]WxYaG$^l7s?/++N ޚ m}E"c~V+͉%3}.'[x_$Ueuv"SO:fvvs:3P7_g&}p9f$M}Ďgq@q [lsA]W_/3_wԷf$mm`M]#{05^2xh$+A$T-%= ɛ+>ѣWg]RFg"qy5kUeM?~]7#e/Gak17S?]>l$U+<ܼ˯CD~Ї"el( g)y@'LY[|Vj_OS6??aꡧ|\ta K.8ʥo5Cq9a%*IENDB`trunk-2018.02b/doc/sphinx/fig/micro-domains.png000066400000000000000000001112301324306050200212560ustar00rootroot00000000000000PNG  IHDR,<6bKGD X pHYsHHFk> vpAg,<zIDATxy\U?X zM0A 4i9au+5!|-1EJJM4{ kﳇ+ٱXLuȹ_L8"""""&@D΅ (DDDDDD' """""c`!1BDDDDDt ,Dd(#####Cu++yNN-DDDDDDB " YQQQQQV93X2XxѣGnz:lٲe˖n  ":˗/_\۬plYYYYYY`iiiiiiƟ3k֬Yf߮1^z饗^j1*+++++HIIIIIў~(|WٳgϞ=['N8QT!O>fV &&&&&F߿F/?/O}VZJNNNNN6xF  "DAgnݺuXB`eÇמ4Ё˟`4nݺunب?TLI&M4 }xD@L"DAFFz\9M6mT?jǬE Cf^,!!!!!Awyw".....NP3V^crm!DDDDDBDE1B!2Zlٲe?(Ζ¹*`(AgϞ={/_""c ,BDב@`&ubtpB ?j}o3kGNzsΝ;wjxc%zۑDDd.d;hiqqqqq$"/c#6_yW|Ef״l %AN叏n"? 0`@wrVZjU "`1Bcbbbbb999999gh`h޸Cy` 7C$L1.DDC&QFۡwFƨz4eeeeee 5Xl:]jㄏZ'!_^mT{¨]B,X`e5`1k{+jK>_ Q^^^^^u/oȫXJ5Dꂌ>}G{ެKG7RwСCUVZ P5;g4!V^zj.S1`ED^ىq1QQT7K>[NDYhѢEB 5!arʹ]?"H<zOD,D@5z9`׉Ԭ !< D4r5^!XECD 08Dh8744444pWoUaDGGGGGkŏI 1BDvWÎ{BԨp۷o߾ǎ;vXmQjܡ7jfG/7;,(NTX?%g@-&l:DDd5yk*IȽX$p+,,,,,ԦKvHg1MHu;dϪڞg"2^1zr'db(2jYi;Y)^x~T!^ed<.fam7ځfM.yI`!a^zc=ӬYf͚D7nܸQJխ _.]ǫnEX!"\jk]vڵ |c`ř`!ORխq? / [9ol(btҥKڷH&'N8Qխ!@>裏> |>+++++˼ѣG=8Tܩ8D< ~ٳgϞ=BƆyد_~i횂{_S8p;;j7 dQQ '@c V^[f/׈#Fa^ƉW׻_ȵdfw$""G ,=݀5XP܉X5+v˗/s2222227^[^(+UWWWWW^z"/ l`!WcZ~[f->%wd ;+Ƚ6iҤI&78xLy^fgsfI_,[lٲeϖiX>LT-wLZ"Pȁ``"*V܉,Jd'no-/j[ ^!DD坍 #X"  JhK}}}}}=/D<8Dr?EHS`aĎ%sa%k z'l}!Vp,܁)fܾ_P($6666661_Ϟ={2@^z饗^zI{߃2trrʭ۳SMDKހ mg}gb̘1cƌ" 0s%QQͬ d[o[oU Hdž`?͛7o޼y낭 ~駟 + !:wܹs FZ z0fȑ#G |*ѣG=znsCDDD'qs̙3gx]p`.3XHՠW\ohf9s=\02 kU$l@&G>c-K\?I=zK(\1,Q ׯ_~=,29!,0ƍ7n}۷/@@S3CZnݺukZU۶m۶m+ozLB?E P'X䪫ꪫT/͛7oެ%0ٿV֮]vZխ jlllllT """RCLm_|_d`,,lAVVVVV 2S3 BL5Qa;wܹSu+ATvvvvvJ0w޽{|*bd$խp/G'6iӦM6UygyFu+ 7VXb խ!3 e S/_|r2Jq{ƃsp?d"`# %\r%58@DD'SYYYYY)Ç5΅aÆ 5t#V]PC߃\Nf?,?f}X|Z+ XU}vرcխ*DD L<`nx5#KEpuСC֐ҥKx;W;@;nV8(5RSSSSSU`h_;D~]n"7Yf͚5Kukȯ0!33333Suk!߃#y*o߾}Ч6ke|Niiiiiڵk׮|:s=\խG=z(3ZL8fT`~D*EEbfTs%/////Ͼ ~K.]oc{`9rȑ߬ZpPhPFuk"7~JDMAXp(vA Q͕|]0ۓf 0 ѪUVZny'+jX- LȪUh_`s{Vw.&O|U"r"dl9a`i=zѣ!mڴiӦ{Gʩ r7o޼y漓]6lذaBXbŊ:p;uԩS'!^{^{MuӣG=z}6mڴiVi)"r.oY0 O^zor8M\CrVZj*0&OK*k`9[hѢE ;=#s Z̫!AfWlڴiӦM|w\B\jv+>^蝗¥C΄fNmvO:cOT,'Y^xc'-----MիW^:<n m۶m۶リNn``a@ )*****"33333Suț8D(\rJaaaaa;< 9KyyyyyV$ 8duu֭[/͛7oO?Tw}w  #ƸtҥK26RSSSSStr~$_1{svvvvv-ZhaK3A 'T|Vz %?RoqAȩر=y͛7MV?m(~ MǓP_|#DD^ -!9?LJp-L[Vzיzǫzf׎C?v#0`1YYYYYYovڅL>ANc6YkY"N=]ѣGoS0ի#3Zx_5cf͚5k,!&N8qD jEgAAAAAsWr<x-s-+VXbwK@\Sӝ  4hРAFnM rM`"Q/jacތ^ 8ˮ]v E& # 1cƌ33Yn&kǧ9-N m_NQG^Q5̞~Sw=X!!?X1:޹ api`1b1CNp`z'6mڴiF?bWyvCȏ;vرb8p wIIIIIyyСCu_`ū'DO.+RUN:uzi|?H1}XOl!(qԩCc+-[߻dɒ%Kѽ{ݻHݰooYz衇RݪRp_%YvZ m+<ۓ r1rƜkC+F>.;m(۹mroh;*&֫atJ`EoRZ#ݡS+߿π9^[W^yW^j{@Y|N cǎ;w|jp{;1Z֟Qcx: N[`Y6(/"ivii{AU?Bŋ/^n{;~?o=Y))p:'{9}};;{iլo~h0M?,8~뱴T[H5z^_SSSSS] R߽z+.СCiE2-:jV[LGe˖-[h;SމL/S)d `c}aΝ;wYXMϞ={i@{SDvCsjK9nmo_f͚5kh׮]v?>>>5֛^/GU{(8`Wv(y9vA/%Ҍ9@鎻aÆ 6q=s=$DqqqqqMr*MdE۬Tn{0iՠJƯ1RXXXXX  4 ƍ+d 7p#+B ov7~hcr((((((p~GX{d%lrUp[;­Xٺu֭[ y|A22&"%p)XBg۶m۶mbРA R6 2dȐ!B޽{9 vh-[-J@d… .),_ q7xB$'''''^:z^BA bvx5C> ~'dch,!Á"77777W|խ^ ~aTkC=/SvڵkתkW3Ypn]>>(UqFU~5~##j >3g͛7o޼Y͛7o\]2[trʢ^0 ^:cD>{ݻyzx~ƍ7n BwذaÆ :.^`v:9`NB> XN;UHș x܏=phq0'{_m.gΜ9sLM:I,N]]]]]:tСC_~_O4+@@f0^}{zGQX,Wt޽{ݾӯr ف:=_~B7nܸqwk!Tn xu;k8}h՜6kknݺu](?mB=/8e(]h j[+455555pa;pEQM>\fVC!ڃ!~0X@f׮]vR c ۟_+ gr2U핁"tbr?h="E~K`EzߵDj!*Xo9'sH =ۍ\ ì@ˌ3f̘}W;8(r{PR{$RXC:ts!A^Ta<ԔwRo| S3iC):uԩ~ A0`_/oj`be۬T~9DԬi(u0ZhѢELBxe?֛GovL͌{{{uCv,XQ[CzXɓ'O<9!C2,O~g_뮻pCK5w#'rj5p=RYYYYY_".9r(">cz[#Tn͂O^]vڭ[;wܹsg-sΝ;w2\p>jC+t"˗/_||x68p`Eq͂@Ē%K,YMj 6lذA{{ ?4}ӧ QRRRRR=<#G9r{:T~TϞ={>cD.Jˏ}~~~~~V9WWֻѐ ; !!!!!A;vرC+niV I8j|V pJ&Uș,IG쪍(Y#ÇC㸋Z+k߿B 82V 6`W_}B"k=>O<O<=ovgϞ={z`!au;+(((((0?3l2dȐ!CX=`}a<)nxZ4xn:د?ۆx۷Gy{72] !XVO;vرcp!Ye4++2f̘1cƨn+n7Ԭf3#G??Ԇ9rȑ#SO=SO^ .K/Tۮi Uɓo/QQ& +`ٲe˖-S2"k׮]v[:FY#{Oz[n[nO>OOxC[nݺucǎ;v{{o ƔyƢS_XXXXX(s=s 1zѣG[{A۝31; ׷#,.x8})r)駟~v=yymZ޶m۶m۶++X F 2̂ZW\qW\ar>x>ŭhqK`Y`A1nn3l5y?Dkkkkkk_߻w޽{ v6T_@ǀ VbfNзk\(XN.11111Qu8__׿墵8^DpzK4zU_?Wx:uԩSoFmkkb߾} L2?NWȰ-]tҥB\x^x:E>x7 B⼅gGy-fٳgV;P;$=====]{Cx7xCiӦM6ͼTW\H12=t׮]v*Ĕ)SLAQddc͂ k֬YfyӦzꩧzjjȯ\rʕ[\a.O5/>7e3z\S@DѥK.'%R_}W_}%ywyE?͛7o޼se|+Oy>>,'g@ zCNǃ/09|`s{`lխSGwGqfyK~O {ji8[ !g 8n`r$lwIIIIIIB̘1cƌCHe˖-[h :XNdɁ:;;Ԇm۶m۶B{^C,'ǡB'9ܾ:Dbsuлt1gUm+:묳GyGZr Cj삀^^z=/gPhywyG^z饗^K.K=__X32rz\5+]?EfvCCCCCv>|Å".....i2"v! Jj d* ԇz衇G}G[?뮻:!ڷo߾}{Nh9%iS ~tXt>٤PtKXԗ_~_j\x3-lA!D + 6E&//////3B[SpRo˅ꫯjp$ 1}wRkZkCy@B1s̙3gzN:uT!06փ1FA:h RUUUUUZ7]kd-uur6\&ᬳ:묳B?>ׯ_~B۷o߾}o(r+BԴiӦMj"լbt(5Th72>7nzFGGGGGkXǺ:c$zġ¤$Kb c2c%8|_ucK/R _V Ӎ7x7jc뮻:c{Zf͚5kT/{5Rå ղe˖-[nEjڴiӦM⡇z衇_%Y?Px'ǟXUﭩs(#n܄Xrʕ+&C]DŽ &L coڴsF;<+//(Dvvvvv B@0;^&-r-1OneV*m">}G~駟~ݽ{}w} ~p":N;4!|'|Rt^xᅁG@~k'jȘͭ7vDZdV5X($}w}Ȑ▔x+.۴iӦMw` Veuh̙3g7UQQQQQ"~B'wwywqw:~}-c CLO?SQ`ԨQFb۷o7rkȳgqխ,>>;Y\۷o߾,D@1#G9rDUVZJz=zѣUƹ"/<2'Aw/0Y '|'rQXU9ֈ[uXXk{ݻw~<=nܸqƩn5y<apD7" $9kmzGa(+d:tAlK.K. 0i,{*^Yo IaH9{eWM>#Oo«ꪫtd- ҫ[Inu$DvЪUVZnEG^JsΝ;wo砟,Z,ی2m`´z( ӑʏ>|V;C?73۷o^nݺuV!:tPխ$tیL:uԩcܶm۶mۖ+d/2U` w_w)+cJm۶m۶o\d%%%%%%Btܹs΁aÆ 6h5F*pz5\s5ܵ^{ת^*#F1Bk׮]Vӌ6X~`ѣG=T9x~[֟n[is9$C}dFǧP3Vbbbbbbhhhhhh{糨fH&xj ,٩SN: {ݻwk`\y=s=h+!}HOD+[o[oiDvBO 3<3|ȭUA9隝#2kLA1+b` ^&Wlܸqƍ\ar뮻NJJJJJR*~ c8-ߠ` Uo/99Iռy͛Nsf _d-9%c~OQZZZZZ`2k7|7ߨnщɡC5^z՜/#Ud`,Ydɒ'L0a "wz~a͟  6Go{_!כn_k0sYu駟~jf2F5j(esp=1MDNWv<| 5++++++Sy9s 2d!. .@uBӧO>}n:(j1a.t"'c`D"Խ{ݻ;VYYYYYv [Qwpxz!j5ȘBnLhj=c=֨󰹰>R*a+zΡAFr& j ]x^x1s.k~Oն331N_׿/ϟ?_úpBFW/1|u^=*;;;;;[p>|[e=cU/{ 1{ٳg }۷n%5:vرcG!>?\{E-ZÇqCT﫯)r 0ڕEccJ.nˢ%o[nE;Oӟ`/_իW^UoJJJJJ odo7s,xAofs6nܸqF|#DCCCCC/{~`[npj`o[Ad.BnL/c`팏>ꦛn馛x饗^z%ߟ& `v ׯ_}~NNNNN/a^`YbŊ+Zo- .0kjjjjjovfM:uԩBt޽{BZjժUB|~({<:faU5x̄GozT!_ 5oBgԃ,C:tVߡ <#|֭[nșX^Pq̮Zw 0J)^(`gfffffniٲe˖- }Ͼ;KCd/l^E>nY}s>.j:tСC޹ $?W:"5:{\pB̘1cƌ=>^;3CNDqx̘1cƌ AJljٲe˖-wڵk.խg%L0a„ B\~_~y!t׭[n:Z P"H+E1vرcjϿo7dU|wz/qׯ_?!Ν;w\y"?A??h_<-ZhѢE`fŋ/Vب.{ٳgfC o8^xG =zûC\N$R8dȐ!C Ǐ?޹5OT¸C!VqJ́rv2۷ogygX4w޽{5jԨQ#*S:>*wHv Jܡ'@r&qYܶ^|p깽Du"2vuȑ#G"m۶mV`g#x}iiiii3~5| 7Ǒ3gΜ930̗]ErzZ8< @Q?QEoqAdɒ%K~YK.K.5`'9Xڵk׮]`h>;=۷o߾}[nݺu8pou {`J@q@?(Huk_~)W^yWTno?6"?bcr$CCпESR^_~Sݪ@N̎<>~߮5L=| !jjժUVB  EMfY3 C!2f ![+F("ťG(3 WXb !&N8qDk`#.ϳMjU=?~Ư4^C03jp/Ν;w->b^;厈 #rFFFFFq )zD'K&o'^=?u]wuW`Ɗ!w}w> ;tUUUUUUP<>|!ZR-k  cT\pAۨxfw9rȑ#ooh֬Yf9./8P3vԣG=zz*f޴iӦMx'xko`5SCM;wܹs< Q\\\\\,O?Okc@"yr =}b<9`"ԪǀT+EY jIIIIIIB~o+p9s9 \cXXl CdT|?ּݻwZ(%XQΰ!o*Gn`7"cI@oF9J?H_X:v&0iiiiii\+ jdÆ 6lB 5"6:ED~`b4OQHaF1^#F|x2'#y w@;]C;DdBP/dȁLz|WX`&Bcٳg϶ \T-D߾}k#\N|~l_D7^{^ <~j1;yU~鈐eZ3+_!E?\z":?KKKKKKOgW9U 0a„ ;?^JqVWWWWWkcV)|p䟋 -[lٲ%/w޽{-[lٲe_qW\qooij$r14Z1 CgY=0KH,2D>?C * ]ƣW+  b/^~ᇧN ;B@$"v]tM7tq\"?پ}a?믿)SL`Quqѫrp… _;'#٠wꫯꫪ@gvvvvvVڂN N7#͡~x8 R6pÝ6+. j~תL|y\#wԼ!wBFvP]]]]]V|wXP#;DP(t ,PZvgY !E֨*2\#rbaC:XSqHC(2rI.DŹl5LO\xzIs6 Dh̘1cƌ L>}e9#O'wDbbbbbbT/zر,N?D4iҤI7nܸqOeNtխ!:FitC/_wu]wT6x-Do?+'uӬf IC0VU7M\444444~3]Ŏ%k 2`(RIIIIIICu3gΜ9ӾH_|_ha? wZttSx%M>oFQ[˕ T6iooQ~駟Vz"5 U»pUuk Lz뭷z+p̱YP9mwsi߾}ԹsΝ;#F1B믿DNгgϞ={fN2w:Bny? e7Y8uԩS iӦM6^"5rrrrrrTDMEe\p_أU ǘf#foܑ W=P ?Oȑ#GqO7TO<O<p|˜$rL7m"8 [~U?رcǎ;TX3 p[0yL3FQKiiiii 4hiiiiiiBXbŊcZrF*O=SO=>>ۺ9oż"66666Vu+Ɂ-D*)<7o޼ysرcǎU/5p1cƌ3۷o_խ/o<"HСC(-bU,_nnnnnn0իW^e; wHRz eӽZ=HWZj*!Cح9:͛7o޼yGm{{Q4D_WM6mTu-X0HIIIII <42K+bqٳg\zC:4,{ݻwV-'B=<Xu\;KGd;;BۨJqzut<9.Xeeeeee0x0a„ WdNvv٠_PnݺuVխ2~"#$"ZEEEEEVY# ڈ !©X i3\kFɢGo / /^P jv`֎ϕg?.}飺5e*/,iQ$4%r- 6lذ!f\ŬZ.bJ-9ٔ)SL"D˖-[liy""Af,>>>>>?3O> =@<Q$pf˖-[lѶKl;#Dǎ;v8ۥ@<$Ȫ㕼΋nDD׹sΝ; {ݻwkcviiiii~32arrrrrr̯qG )Q??\io =\Z Lص"mhvwV&* VHm:1je@.]ǫZꫯڅ(.L[{ X8qM2eʔ)Mi7^XXXXXh3\P3J摈,vڵkvԩSN of/__UTrg;D`ň7nS ʁj`L`qf$T1VP{Eo[bŊ+ȭU,n\rʕBs9sNΝ7o\!^xaɒ^`F :L* t+*****Y),/!{WQU/'y`͛7oo nLE@`C~6nܸqaz_~eZ@.1\,zFf_AkWƭY,\@r 2. aÆ =dȐ!C^;\b\߿w!./XK%  ,X@u!dyjN#rᔀYp{뭷z-^zK46D@M0 }t-r%\r%Z_|_n=*zBnعsΝ;~]̞ȩ/ `B'NѣG=ZǏ?>vٝ@˙gygj|2_}w=eeeeeeF֨âG:5K*Fwx~&"3<3BYf͚5?#####C]n馛_gt+ !Ǝ;vX 6lذAO?O7~u]wu _g>7:pC$66666ּu~iũ%2 .&HbZŋ/^8,.p)?U8k<@`E}pȑ#G;TUUUUUilV0+#J=;yD~|xmϏ??G .mȪ^? j[裁;s=\!~_~%H(]wu]wN20$Hu`TPfZ+fgY!$"qASZ~߄h۶m۶m n#n7`hlNˠ7;#C ޕdFC}^#|˗;rEm)~ʎ;vQ??|]^z饗^bΜ9soO˖-[l)DqqqqqRxaׯ_~}jQ`?ϮׇqL/JD]fj%-Ԍzꩧ>Uvmv;X******HNNNNN|{Bm7j@KwSsAi~f֝X6 Ѯ]vڅN/-WQLx[M2xY۷o߾]+~k]XB;(z#۷o^}۷O{o3f̘!ywyn%Pviv?O?d^ýAfĉ'N=-70 w"(E ½ t~pA&Lf͚5kS[[[[[+#<#hI%$$$$$=fg}>XhѣG=\x*KMMMMM\Q0Q~~~+9s9?kܹsbӦM6mn!,ك &dgeeeee>3}(فPohhhhhp,"gϞ={vtVPCJѝiӦM6M[ 0`PZ{-فjPl]'%&&&&&~/<⸸zիWkwy!2 (ud gVhv`XzKfffffw/ ?ŝM,L! u;2mP{eXdɒ%K7o޼y?CH Cy"d w!H[__SO=S?]_~_Vv??=e˖-[^IOOOOOfe˖-[LÇ>\u8n`Gyވ31D N:$ wt#N:t!!vڵk׮CPEop99degggggk[54LjC@nůy̶_ϼr!fG9rHG=SN:u_|_=oWqP9rȑ#3̪*d|'|F| 4hРA~ݯC-v "ށ1*j+`n[f,_|_迮C:t. ;;;*25ЂCت_vA1xBD3j #zN$.=C=$g}gi㆑zZ` om'GEl%)nݺuXI&M4IWVVVVVz-9믿 k<"RLV!0пftj-`;%6X&L0a;L2Vh))))))sΝ;w^lj^`\wu]w.ܡrJʵ2N9SN9E͛7o޼Y9s̙3'1Mqƍzix7xC;O?ӅذaÆ \[3v^p… a_ R&b"'#+fNk/iQTTTTT86nܸqF!ӧO!}G}T믿{Ǵ@ x.;R[ZB’%/~"66666v۸Ã5; /"#'>no]PnꫯjߋG۷o_E]tEi#_~_~rBiӦM6H;)+F ^j--,F5WTm"'xϟ?_sL2B8p@!fΜ9sLk3r//u=s=W{ݻW!=`9cd۶m۶msqG=zl7:N҃, i5 ?9_5!L[ߵkjjjjjjT9o߾}v!n[n%9e;~NBM9aXYHQ8kYD&fPntYC ѻp3^OLoSO=SOiAf};`#p7f]jժUVֵ/Tcǎ;vlkv(| ^SדyQ=++++++|fyg}Y!x'xڍVvGw\\\\\\jB Rۑ|A$AFӶ7)N %K,YDݻw:uԩkz)H;v#vڵkZ}د\^fΥӎcDd?OLPt[3 bd<?6iCC=T"bC_`!E2Yh1%~[}._ctalW%w}g|ܮ~Fc֭[n:@ ~bbbbb@p!C 2Dݻw޽;@ gÑaV$(ZRRRRR"DΝ;wl_{T45x0;W2(Trͭpw ~ߎq7xB|g}뙉Hp`[y 8ܶ=k)u !'ЛH?Wo(W2QjL^n`(4j͵lٲe˖[e?^X}oFɘ1cƌ=(bC\Óo$fK}xf,d31B(j Ǭ@ ~P"L_,_涀Ӧ3]nd~'2fooz˃v~]rrrrr cW2Wd8Q~駟~ָUSaΜ9s?OyPi*7 E-Z$Ă ,X`_{Wwz{gCƄU%yyyyyyBi0,rG}GRdg@L0BH|ٿNw -Vp۷o߾}ڬXzjl=[UF/S""9s/5 m@Sk׬@>~Դiݺu֭{S8V;};+ڦ:hM/ڵk׮a l߾}ˁ|| ?X gQgygTҘvIФPcuuuuuu? uhzfC*(+++++6XF5j~"Tvڵk6کW\+trQFoyL>}0Z>cGDD0yɓ'3){>9`iX1҃Pƪ^:R-!B&իW^-RH3gΜ9sW_}W_Ua֐g)Bq-ZhBZ2]F9rHy]v<ٹ~ "jҥK.\u#߲Y;+T8!s)5=Ni=111111B444444H_5XnڴiӦM Z\\\\\\_P{ݻw[|.zٳgX}Жc Ə?~x!Ν;w?oZ"r(^P7~vy:f7gB%Ğ={):r,C*فnNOu yߣ(<xBD CQޮ)G.n2 rM4iҤIx9\[DAJp+XR.c߇`РA {1bO !b?En<SPPPPP=o޼ynwᄗdz!@:>csCDl*`%'y CH~0S&}i֬Yf =('HC:LܹsΝ;^Z vFu~ :.s0k0~?/(f_'qȊ:L"<^`yC 2d8%rtBiyO(PPL2eyL邙bg"55555@\HDd9 l"=qV~j \g۷o߾#|"ۉ%:t8|ȏr=G mw8px=XZlٲeKonX"pveó.ZjժF5jVyro._y~""+8:ߌ`68󒓓[֭[nZ ,Xx={yoo }VGD `lEHޠBM#g~ OXxŋ VZvڵkU;鬭"F)ۑB|,!DDPYYYYY 8y6p3.0\p\E LG. e UJ7XKjY6w˜UyߓVN|bza֭[n,B9b9<[SKCY ԡ&1wC{.S7 p@… .\`t~bݻw޽U"c,Bz.YpFĕ owl6p4Sj)lٲeխ0ft ivJX1wimFCȝXp\X9CLLLLLvk$ Gpzx!n=z9+얔r&s?`hޠ<"Ap-#s`ohhhhh+5 rG9r\낁Sa5NpqnׯV蓇rSaFf֭[nݬ[ի!AG[Xd8Z6#yƎnce$BE_AoHW0b- t""Uܒʏƍ7nR_j{!YYYYYY- M1*n$/ɓ'O7 ʊ@n K.]4p:8[ 0<`\5!!!!!A;vر#x@bnI9[#"UT04[.fdmڴiӦMkkkkkj}v=C-EZ[\9t"/(Xt""V;۳>jw ̮z`Wn򢢢"!^{^{MC:TZ$XX1WIIIII˙RN Mq'VQSUUUUUy{0 >sn?IrO RʄHOOOOOמu$.pcccccc_ChT/S Xw gnN=uuuuuuBwC^-Lr"X8.{c6^٢:S8Æ1ْ3dVhgϞ={1ܯ.vo=&?O"dlG|Y'j{} Ujsȳzꩧz>裏>7|7^!6x,)))))Q!G9rVy.t߰E?${0h}j),s}l=! u 0@#oZ+F_>8I 㔔No,$~aށ!!O>}QݚvGfGXjժU(淛'\""5E6r9s̙3Gu+`!!jhѢE Q^^^^^n2pfxc5k֬YFd4ȧ<ݝ\ݭimn>F]^8_tLQ($7*9QT,*^;1y5n`%Qh;]tU//#"r:H k}#os M8qĉܲe˖-[nen< 7b \rrrrrvB|g}gU6t,d*f۷o߾V$%«2ZT.ÜoyaԚuyyyyyy핗mR}%"0B'W_}W_ v++++++ l0XN=: n:DD7zgO~[nݺuk~ښmuرcǎv+^#w!`Lo:tn:⚚;ҥKxk:( w_:vO,w'L0akȻEJά%`q(X'a=!; DD8!S5BHLխа 1ppbٜ)!hV~QQQQQQz鉈"#0`_괿Xq7^,X`ZAZVw,Qȋ@%!"r=DD'|pխ`!BDzcXRN yh!gc x3Xxl]06'"""c8~T!ULuVZZZZZʎS!Y"""2&gF!Znݺuky 3XtD:|ÇkխSOL"""[lٲe~MG`eϞ={aB,DD!B։'N8Q{GS9tҥKj("""r?!D^ g˗/JBD;?xSUf)``{pG?'777777_@ P}ommmmMM`XtdfYĨkժUVY]?:::::Zzk7dӍ_/{5vرc hѢEHu֭[7!U-(XA-?( DUUUUU@DDD~74'TB$'''''^*?b|!" /1E?^v9 7> OPPDD ,Nv{566666ny3X|?' W~?KDD*qkWXn""gb9-brDDDDF`1s7Z>""23XHG^^^^^sy "DDLuY(!"""""" 3XHajcx'"UZZZZZ̪RQQQQQzi5X\i5O9DDD_VK<5X$ZDDDDtb,JKD xL]]]]]}gvǠƾuC(;;;;;۸&M4iqj{׻e(_0r8C\\\\\\䟋Ydr@w|DDDD*Cd ,ͮY 4hGDDa t,nwtG=zvp$""Hə%跸 -D}}}}}V1Y 2K0BDDDV9n'""gb#CCcK 7VjĀ Bխ |m'" Bni7Q 90XVK`i%tEXtdate:create2013-08-19T18:04:41+02:00Vu3%tEXtdate:modify2013-08-19T18:04:41+02:00'͏!tEXtps:HiResBoundingBox556x316+14+14}MtEXtps:LevelAdobe-3.0 EPSF-3.0 pIENDB`trunk-2018.02b/doc/sphinx/fig/paraview-glyph-icon.png000066400000000000000000000034721324306050200224120ustar00rootroot00000000000000PNG  IHDR%#sRGBbKGD pHYs  tIME *$XtEXtCommentCreated with GIMPWIDATXŘ[L? BP=)ԍ%JۚVSjaTM{&m۪KDʹRX6!Ӱ`c(`@ )MlxUF#s~9Gyoc—~J@%= &lҳ)@~n X )"J 0P KvB FC)Ex.G%S<K)K 4.tA0 11WMIˣ.H.\)=j}=} mA%2;>tZ:>GY_!4=XGbW2yuI(y[⍚eI۬!z>DRs$svf[pMxGn/}eN56}b@p$6s7~藬xHHP#/.'gh{BE[߿BnI*3f>Iޣ#9EONGM BR. +~?++d 3LXz+CEYsmb-cѧp:tLdz !"Br'e&P\HFv=)JbvΙ;T0'497ގ6m \rgQeEEDQ¢HelEFIh1d8܌.1_󚚛OߊD0xe,S^ՀL ,^×Y-1$ucbq܆:IΝ {Rݾ>_y,P]BDSKOan&mWP2uщFDksΠj)2s7ό"Y{92w3e& [>/MԔ.9(?BFEYuJգ6֑5>D`3" G G'qZl#s (_~4>#'Ґ ̈'-}U$1zm>KjQ`Ϩܢ:)46B!_@PyufXr;056g&!!Nuژ]d#$[rf|+^ $B0-]hSsP0ggYL-dfd@N+gQ1f0w:mSq'^vpὟQldcݚzM^,?n;w^ݽRG&~܎A 9**m/Mu:;DK %E 7~):XJmuNG-(1fIοm^b ǜ)3Wv$ FM`Q%,iɱ i/crKI[cOmEC&?囧$IENDB`trunk-2018.02b/doc/sphinx/fig/paraview-open-files.png000066400000000000000000001112761324306050200224040ustar00rootroot00000000000000PNG  IHDR>+%nsRGBbKGD pHYs  tIME QHtEXtCommentCreated with GIMPW IDATx{|}he3EsB!"ԯPTT1RB"r>3DZ}f6 u^?JIH1B!ZL*B!֛ Bn$ !BܻZp-"t $| !BHTnUWOuBgnUO!B{+tM}0U\> j{=Pྖ«B! Cn4هX~n"|VTpO$| !B`e`τƫ` B3V`p?>5\{B!B'w@nφgn;gxopVԓO!B;xflM spj):kTӓ_wKB!gṋvc+J\ |Z+sتOx.r"== B!m舻l?xb՞i ܃rsFO's۾z8=f`^K&pニU6]u{؇܊g ` 8 B!JĘ] IN$ {X HW_S,X G \$`FBQHQ1L$&%D oo7IHH$9~~>nK;w#ADZ:ޞ0o2xi2h`+eW_Oɝ[ӹ@q@ծ*|?C: +t*"B!D GjX,l7YhuZtZ-gϟd4wP68::7 F9FtzN\dՅ-[hʤsﯜDlf tיdoP㩭YϿ`\=u޺;#sZm7WkΝB@F͆+V,{Lm9O!d!M3=#?܄*(VVlrv6? -[6}_x-[ ww7ZNNU*jlYC۶qws/նc^JM=5ܰZ8:1dHM=ÈYZڰT6-ع[ƧB||+yUqRGٿqYnԭ]kӵs_'Wg«B-d= B']zM|ի4c^Y9p->0<B{=bZ?VXT >پL3:a_[+*sd2-RptriF]X] r dХSGT*U=~~:h4j&tk2[!''xg,ΎΥ=ʐ!k4l7Fd>>ü8x$Ճ<-[@UPsxuGunom+|߷5SBmZCwڋO_ C$v.Ol¦aY۷_nƇBQ΃b]Z ^3wwl'ZQlk3jW{_[m'TpwwOOr&Qո8;sa֮]*m/gz,Bwԙ^fq=wӘ|SO+ו 2ڍZz)?~ ̸;+nMdbѻڕ?F;:Ou~"8d͟lW*Bq|Bų]*x{SV-EEbX,X, jd RlFbQШms,*5SHt w.ƕZsfA]1-iEQۢ'/vG1q(<+WbErBb="wԖ71gDŽ엽p5y'Yi)+Oc_ άKxzbRds CǶw!LƙxMG3qc7)vkkl]W^O&jj:x pE+} +΄h};1v9 *lbj(-}4X`0Cgڳ<1*hBʗ5#QSVk&:iK<Q,2339}4ժELff&jVQնw;P+˗U[VWW׼@|i233Q,[^.^XrT=Ȝ-o]swǫ_tIKRF,&C-;Wp;]uDR>/0sqoF;X-+>Xr'&1#:']y9kPP`+yH=j"`.wJ,3ء}׮+gV`0{^j5!ժVپ_N>bƪ(5T**JEFf,XbRjWCfT*ҋNNw2ؚLfġ( l^:XrLF3 XIAEѠ@ƬG  Ӳ3VՔCŃZZtxͧ鈴|\Ǭ/.ϖ_.>>V1V+9:cVlu`oL}=fŊxrx4'p"N# v%w !ĝ=R{{?G)tJ4Z-[FJժmӚ~"!!Hjł;pww'##vdJ'm۴3Ne?XV,f3!~vx N* VU^* *? zR*mkTj 9F,3N:Ԏ8:jmf3sV`? Лt* c2[nd5`7QvwI>L (1U{#}s2>Q}dͅ1B9\0υҽ'*_7H:ylZH{ ʅgPfeNUG@TW+ 2 ]1޿K^ݺF#l*jZJlФzONGF`XPTh*Xǟ F#jzuZ՟l d砲G3JJƒƘNcqr*O-\Ը:ptt Y=#Pɺ4^ߛ3okٸqӝ^TH_=W|̗??i|,\n9󞢆@uFh|]uRƨ>/d*}nyEo|k8܍ƞ**OZ<ݟ^8yrΛj-ܱئ1zRP| VvBtJ*6ml!)9J(&խOR>WDUILJ¢XڴyN'=.KFѰl;yo SyHBZbT}i =%Rn9$^̽Bp_ B鯹>RSS3_GYЫb^ì>6t_ !(SixłhϷDL&K*֪* fM4%}Z ٶ}9h5*bVPۛ+&府k^%&*,f+O]E^?լ[Nx-7n}9ʭG_v'QtQs[*mڴV zt N1)Dx`_+MGPQy+qY+}f:;m… \ݝJ })0F ؝ .\HZz:TNx@Sa\dLR3R1Bd㏳s\G=ILNe_I8>3U64lȸ/c?) ?o-).аUi;ϕZ#.vBjNN<7CS>*'{y6m#ەy[]û= ʕ o S}MצMk6>)J TǥKdc!( J՝QIB!]cDT ΜG3 cawXqdc}~K89쟷d9 CdUq|.8N/h*zcP xߣ۝=<[gV ZB{RJjׄ3੐C/j^٬k?5T,oS]IcVxlOWW-3`|Oi-pf3TB{0<|h25:ɒCoФ˝Wr6dDG%J G;0hQL\y֝wjijܻoSNCU>O!ui jg*UT x*xu^U4o$撙n7'ExRSW6uU Xd.7[7? ޓ.kBZcBq =i+_=xG4/x^y_6O~j5_T gZ1^ʳ|0'*^*0ޣR֊u)BHS wy,WN_ғ'j/I Wt@ɼѸ<ВjZ{TWVXVd< ʊ%m/H+\;_:9EB!j~.BFy+*x^sQ 5մr@Of}x +j~rEJ]L7wf7..Uh"ܶmXQ$O!y37HMoO,+v[ċ*lG]CêI%^<ϥ*:)'?'?yRSSe !RSXr͆Ng7޽;=Dڜ4bD 0&@]HuB!Dq޽ ٠AO(v[<%x !Vݻp/~[jB!ݻw(tVʺ,B!7 HSЭV!fv(ҾSWB!)rT(3۷n B.Ԥy+B!n}O 1Kz sڅi,e: !HSyn>mB,EB!)ԝ]T0U2Tcj?BqCReU2g#;a#ި0rpԚ7u IDAT玳E/ :QZ݁š#GÓ^-]=]9NXDt@_;s6ΰ`aޠB{fsgطjtV^⭋BQ)TgQe- EWcrנqzb)+N°ҡn!3; 51Q,ƯÛe=dtF=iZ -j\B0$lF'3@U}0QŭJ9ڍU!C*Lmߺ9۲fJ9C.P4:)Rg+K^V OMbO+~A@@}X i9-u߰#U!sBV5aD[lN̠ NxDtC+/"E@ b$"U!D*L}( b0GҺCg ]u aP7F7 !0 Gb5G"E!(-JIP@@|BbcH`pXU }:}>ċ織BE?B;%ľhb->B!޽f§55)N7&<}ӳO,KTb}bS@,(RIB!D.S;ZB!(5(SRB!D.x2%O!B䒊(SRB!D.x2%O!B䒊(SwU|9BӖ1KXek!=G*LIųT/I~ëVe۵+G/WU㳶>Mk_=+ |5s9U#zW\6ֶB#OQdb!tG#cD<ƮU}Td֥LS`8ypEhqj j9݁EG%I.n8F|y~I[nB!JT7sSѶ LԒi9h*̀tj8_Pp >3!Ş |6*|CAB7qgv1;2=M1^hNr> VMscZ6&cvXM>?k;?-yDZzߞc0ڑ8rGV0B1O$y9 \ n'kO5}6þ2i'eSI.M?bۉ~j89nB!nx2[,2d4m#Ų_NqSuBoN!ΥQ;Uf`\`^ܽUաdkt2aɡ˅˙w*>CJV1Ǒ'A^Rx~&,͠/_E7& EWcrנqzb)+NmS[X,F'}׋2:꼪i@̥u0'ExRj;A'SUoJƞ z~+-g,fn WiqԘC"k93of !)TSX? EwSu*WEq 8z)-mrS,3YGbҹNBcDL0 (9<6lxViG_CWfbVdspiw{! =0dpb+K+.ڏξS'9SƘCNN`R!}CN;9aqeo$:/4#jpdxM'ꓻfә3h|.~;jb6c6I?9u r&BvV?~&C?9%B⻉iWRL>ݻwGHTb}bS@,(~+ݍįLG B!MeJ\tSupyhʛN!'x2U`߭v% k_}9Pm$# 7g:b481GNc\ 'TIǑkPO-XI6cؾx|r ݪ%!wY)݃UrmȄ8fAInSY:+~h=N_Y3K:u;6FQN8]I4yg~&hW"ZzcT|r?dbSrOYm֢@3g~_fͦW&1έ\?I͋Xrx$;B.=d{ֹ#vx{ *%$c4:UBO0ܼ ΘȸYG#g~BO67m;mOkIぉ)wKq7۰)n: yKvFS9b3>RHv_̨ov3Ӧl>Y\ +)_nت8^^ԤlD!CVgaYƮO|[`5 Roj6gq ~AoLN\t gږ咜Bqص{^{DZ{zj5i*۲:7}̨*R~0 yGh}l(s醿"E+_-bȶ͓sbߟ qRj7Qޞ9"ix{-cH"CC % V')h1Y&?c,]vútO='j>8%!߿Ka=_BPx$#CX)7`K>X̩[<kнM@^UIėf쩫wB!OBAi|oI9!>ZǮ?8Cc~B7i Zz{'e:W-ÏgTq$?›v[΁~<{QOitN+V~%%5/3*L($(ߓ7SCu=_EqG퇏2N{7 Җa3zn=LlwbA;_*T)U*:p|`gǐప@qwt:|rAKxK))T-tSO>/Q%[5#cT;Gm:]-o6!)o31((XVǬIX >oXڜ4bD 0`+)RIB!A=H\$Bygik̀n55BqB!)ʆt !B-]!mB,E,kȻC}^ݝ~J [I&NIa3w]?<> !<ŝ펪x݈1n5\stKKә!p*rB\ײ1g2 `86>I6#ƣ Nr^?oY7 !<T<͜UcGOEL;fG̓yhGڂƑ5 k{sԎ bwrd8#6g-!1́O.TO8i-u/F깮D*ݘ=kF*κ\] G^a˶vZ8d~{ug 9éh[cQ|>!_1{H=w)яG)}/a.23f [tvTKNr8[B!S{%0"ۼaEDWLjgt{8 _:r?/HKz ɤM<ӡ'3sśާ5W3۝ax|y7NNzsq<{8o , ~XʅQ7$ !O1ϭBglp*_Gp`(]7Lm>Uu]qOX $FscJV2|z!OFs~vqV[aW/^wUvv-nEi-Ǔ̴u[󮊷lXJG8y<9[^W5sӟAKtb vec/xp+N\eN8޸͏Mg,^BqǑ=RAzÉ]5Cu3bW!1ٹ9SHph.R/qލ䤓ZJ8AI7s`)u/ }> {O^Ŷ9X1qf;iX{u1rꇩ5g'i cDL0 (9<6lxK:^0?ah9&Q=6]MIXڧdY9F8˰fu'~Ȝޓ|/B(y<7ʧ6ݓ]nU\/ҪT gwi{d-mѬ |P{L6Gy0ЙnCn]]){mBh9/TMC &ѷX{tM*^o]_7/?Lhp9/TgZ@HÞLXv,BڏBQ4 !$ʡ$7Nb<7w%mG3^ LGΌ"¥"NI<9FyZ<Μ1䬭BqQ$^Tp_ >!11$08*`>>J6cJxK))T-tSO>/ )B{{Sfkc2ٴq͛@Qm bU,ٳ (k2)ao KTb}bS@,(RIB! ;EB!mZ=؆kܤ )yׯGQׯwy`fVT*eڶ U:Og:sw$?ϟܖ31"5-Ʒ^f|#9yN׎ӏuC3_c5hio2u?^ n~L/6Ų1Rub|=\aaU椶@c,G3#NnUh9v7(Ur&`7tL֒&Sɯ"DWgJG_5L 0)ӲBm?#9 ڭ8_͉3XV 63ˊsq.([gu|zˎ_%"$K~YOLO\A8~:|y}v (xx7qߏv=tC=G|?>ȲP=.G,3l Ƶm V!\ ,|k4;OK ?}pQM믰|v5\&"23g^~Ͽvf+,ghzo9gq`el MRHQt\ߏ LYw<r`6Wg_B!$Ko`ݥ+BIY[%]fVvfܜ.F*Eh͆sgn*<M 7ų'Vۣ)7fxZ-ʨ%%e[0vqKM,̛3F~[]Ң-W+2vZmj3&]ʳt[@E1q8N}UuFZCR,Oj~ÓlKi:,aic<X,v}$(V{ fł{]6V#pocg1\a{"fBm )$X.sK{PJ<)g!t/MwZ:˿gi *XߑKI +j_FIYps oZӼ"vo:dt= μDN#B4:Jd[G(^86yJW%)n8X-p;hSqWuES w#&&,F ^Bng./}N_|TT}3I=mKQ|̋CX*lƊiw4ޜ0:;Sf^gM9dUp.(P4u}ܖBB@xgei&pëڴS:ͼ(KA/G msBL(qhpՕΓ2>gqh̤;51 Ӷ즔Pط[Γ\KmIpgqz5=ՠzfq++P K}Ya V|JhҰ%m[ QƠuPv#q,cFe/_>۩}!utqDqP|%U(<%d+ !^-sfit2tU D1@xBv!DBo Kbl&N-; :=S..*kj"v޸3`4óHV~҅R6B4$,^,+2&JIBDI6[RB!Ȏ\f1ПㅦHdȫʫʫ ~˪EOD~+B!^v2..2LL&&cfӣM";'6~뗠S!x9\YW3x/qQ/]!xTA7W9lUʟy+k8s%{g.}<{:ُ;|q}޵FlbO~ƶ^BgO <_q'>Ť)ӲӬ+y^O]IZwsaH/74r;".Bd+i ,g˿Bd2=h>)-JA˂Sૄ$hЖLnP5J׭Bީ X䅛SfI&=4ik@'6#d/3Xf [5ͼ}dz}1&fﷸek҉U-1ߋf`SRR%&k@ f׌mo+Z|aE?{ gFiigfF<&Fر.Ҟբݜ,ˆ"v[,a[<ݗdk.*BZFQU<Φ+5YQ <*?'kX?(B!rT-Qf:lNNYhV"\%2X? RSYBAcz: P{ƌA*()V5jU~vYZ%(,l۵m W!BQD3Ż=EYp&WϮ$ݾ{T쁣,.}}SAV (-zRoMHMԘ{3})>ZӃe+˜aR*@E[ڕ3rע(^ w52*Rd[ sv҉z>~?Hщo4%o4 qοH,Ox<3AN!OCxgejDt}tb`O3UtMrc9~v)*K'd @P޴KlHܯ_qܝSOU[bwZ>3}|J{(L)$eBxJW{!3z?LOGYh("KУQdB350A5ՙ0sNNG EU^k~ʷtHx-<ͱF̘Hh m&p?3'¹l2E & Wu&8ʒ%P+i2ruqfpxSn6ex5f u.OFܙQ3#8Sj0h9J)rOLժY}Eӂ* ZJ19)ZA 9?>K}ӋeŴ Y`C4=8:Ì <-jd̠D¶(>~rPЬ_56|0a^mc%x-- l˼Fױ-QF 8v70s91>*iqBTsڂX5h51gFjJnH)B<$jyծe-V4$B0yVxYB!yV(YB!YA7IY -g6f{VQ<ʫʫʫk1u{%%B<3)WBNM B@䵰YB!HWf ֮ߘyI}sBߥ f~C7,!]fZvyGZ<_qzeT_ WHQҶl|1uvʙBNbyodzh.HsSL2-?Kx*PDۡM-``T(NɔoiyHd21z邙Ѕ31"5-Ʒ^f|#9*4أ\Uf4qFqɭ -17K]php։DaH/S|3?7  ~'9~j|[͞(FEw0>i'7O s =evi'cc\/h93)U||뱜+ 'eaZںb5@BQX1OS~HYƌ)/ۣX| dY>lr&.s\ ~VVi9n3s9 330O>Չeǯ~%̓x,'3a[Slp5O' GEoa鸼moÁ 63ˊsq.([খ}D!M|-dsngcn}U,1b0C0OT;]w '34O@A-0[&^z n2T!ē<#j4)X.RH*ғ޿x:/D4V[G(^8OVȀG?#`+a-xsh\d@ٜhOڗwҷ|fFr=d<7S*u_9x~ڃb"_<1#b ^-+u9»w*5zN5KAL]h:uR5jفI 7 S Xv@i@}+ƥ+I.wQL,RWZKv]0I'!W^B3CNNLԍ NMY~c¸^1JC|dr逫 b ԦxBv!B<*H YɳڅB񨂸xB/UeE&Wu}B!ă~^ųO!B<(ZXGZ< !)Btj`g労xB)Bt'>U`Jg!ʷxY޶9~HcF:5XA11 󪔫Bi5ߜgY-J:Ϧ?:!) 2Hó.W!/ɔ)?Bųz[8=-ճl|!BQ`9rOT-`7tyhM&׃?H}%`?7csӠѸy2Cc~`V3ʿujitݺBoG Gȯ?55_[1/EFݯ3%(_ ZҤ~u*u=Qz17D˙)M_\v_矅h灣Fƹ:ƭ#^f*M~* y+'0rt@88Vcws˘zӺg#~d.-ZPz!eS(a98߇YC8[Xv*'Yi_ mIƌ}WY¹YUxMykqzk[;^"{|(+ 068}J'Wa_?V٬"fYFC֗Sd.o_k0Y(!9m6ɩ\sʧg0+cѴ{S^;)Rw3=v7JRv(ѦT)FMф5 IDATuq뾉6ٿ,KQBۙKOCv"!C GQk6',-+`4rc}G,OGikI[<"`fm27'ƵN8TO<1oNإz>2exBdbQgvt4YO2tU D1@x ! y< Aq$,#`9ƲH:$B(OHQ("v޸3`4óHV~҅y!l6?uH)^,ˊL<|R%|oAKY !#jլgV>yoԪYHS!Bvd^ڵjBA < }!&) !B{qD;IB_FL.!BevʈE$,Dgn7B!ċ SB!x !B <B!S!BH)B! )]! =W F$B!xڠӫEZwvFՋ9JO <ȃSRB񊰱./_B략1MRX(R=?^St-BWDH پ*FA +Zm p!WS\J:pBWmfLfVœS!Wp$B!xfIx>\O!B+i|6:uV(Y/-C|W'GZ~BS' < S [zTF"M%4ղ n_:'==!8U_|7- ,YXVVXO(rѵn?ISr%\=Ql M&j eA+_:_ʤ7hռ1 z3c-Rp̿A~yVէ1BnཞwQ7'^5\=}i>z#!)\xп3 -gcD@kZ6o;MhE-W;,r܃cypS|݁ QA\t/ql?~.4 ~y,3`?7csӠѸy2Cc-g4~=s%% '*[ya v6K Wڥ!\z 'e/r-mZOOQAgMA,`'dT3*:X.OQ,5w7Ki#Os [ʌg7éc|)[;%ƺ<-KJ7 fFXhiF^'/Ρ_aeFkLDd8gϠF=V'JDI4>8X 63ˊsq.([##?ŁU5C*3mx^YMZz0^=%iץ2Ey;kui~3 9- ^ϐ8cOh4b2n < !?O5|SrKUg55KQKRgD=$E"@tj;$~xfL[$bOy,Z--Q0͘1 3ص6e,Nst>FQxܝCm;ӫc6`P \a6yF|ђe&%IAug*-Y$XF.$L*%$'-)33*Bmڲf,+[5D=|(TvSy`VO*߭O k_ۼ!x ΂zrhiqLN! Hbeٌ%g1 ?/]#t,zw:i>Ї˨)h=_7sFi淯웧3N#B4:Jc`Epucۉ y k6',-+`4rc}G,`Yҍ6Тû/#o,p>#`+a-xsh\d@)ߺ/?A|1MJf_ + 3Zc}$B +4q5~kPƂs(QCePSUh:i)،a_6tIɘz`e!,J&3br|E"׬ܻ1eQZL EVǘ"6iHV8U[%Ojٌ|KV c%ŌSWo^bԕZٿ$3(Rbo~X/g(qz3 KۣV(Pa[1k My_>͊b~@bX:R"y8Sj0`:"! : li`$Fu;)o׼/.4cVQ{bּmQCht*?صgZFBBbBN[W1a/cZUݳލ^$17TrˇYٳ :MƗ"KmIpgqz5=ՀFH1cXu7ڲ<T;HCWު^#d,E?ExiD#wKmrdil]:N|I׉9|7Eqw1eg<nnn:;|ϲo֐x/wSsSk( hoŸvstt4iۥN6Bs3ظ8ʖ)-Eߊl9 K8<.Ǥ ;Bt:BnF">!-U6:Z?CfM>$}ߠh"(\d&@ &1f_"s@ ;=`1B!9x՞7шJח{Ѻu+,-ݫ;E>|l$B!!0+ `ZM:uؽ':cieINd|J)DAW(%!npf2y3 Fu6w놕y<O <B!r ^WJ>ZjoO>-մi kkGO 8zٓƎ#i\*VjwS|p|B qww#J,QevkkkI)B,N0| WBYBZ9֥~V6J*w$zKa!K$M^+W.__rn3RϠl$"zSW!^&:mr%K:pNOٌdS{BQs;%\r+/۱ٔcx `B! |[2uHߞưZv/suV' { ޖ*kKxY;M&Sd޸xdN^ z1~Li'}4V>K޵a,iWrmngNK\SmM#O;g<ԧ)gm2EFnX-(`:ݳKH±lIiZl }.#`s YC׏P"w:o33Ex_g۳}M帰l:FG/e?v} +3|^Ǔ,Ӗc}*zG~qQ=F1}lO #q`F;Ҏ:exa)NLϱ PYameedi"]3:u*Wճuz}ԃ1cKFѪVU\=}p\чlp{zUի6-/@qفŒHjLׅy=?'Y>Fys%VĻ\>Uܞ/Bt eAj1Үmsj7ٿDe,yx磻%яֱp3IF5X*M2yҺ?CMu?q=;Фa l:h~ G-![6qɱ}*[c_ge'?:mbՓ{Sje+V3zD/m6S̘:Bǽb>l69)3e+K5J >9]f̆X-M:WgՆ[ζjʌ٬LLVpOkQzA"Y^hČMX2aNZVM굨Z{[rĥVEKP"s[1ѡ}KǿDMuQ?_ʸ{ץՐ/9lǟS23:RN}]݃kx(84ݻͨN5>ۼ/ݲ/g#ֿOϹ@9]],Ѧ?_7кeS}f}imڞ ӾUy6u,iiݱYק{9NOi7pcveucy|[랪5?,zŊw;heJե/SQth\ O*xTmF)h[_,c]iӪ5a/uߌ!߼ۛU]ʍDLi5ߠ~*xxQLk3ϽSOk:cڲY`q`f=35LYCuG|)K7Hw%ؠw'zr-o2p!'t-Av_s0ƛ>h*wnN<_Ҹngqy뷄yIw7ˌno-w?>f߄|sYcDYƜ>Yuaft3Svp'kM;_Ag8c2?fWA$g̫H.gEX>iJH۸Ӭ>}"Mp d,oǂF %(Z5qg.cP]8zIǍ# blpf}ܧHJ.Ǫ?AǶ1YK͙ )A%{OrhyJt0O^'ɘ)zT~zm>ÙR~uW˚LFbI]T\nUg-Cvޢoٹg|žwF2TxSBX2h -i.[I& ^Np g/g%>rx͛8[<噗etEb*:V F1NDzrVP[ԊsIiN!t&xAgUںt\EϿ}=فp|9sO춱ZA[?ˮ=SӞ:)sX}):[QsFf*Nyj3%n]*%񩖎}N]Ѵ"x$1X3|0_F5峽':őu5讱~@6OFAe`QZי"Y]l®OpYA3֜$ $gnj"L*@I֟CA=?i"~`jզJZtRhYsKF $揿ҧϯ­@O IDATUa5.}rG֝Il6wl?'d`rXdgjv_G~MJ@@cimMAQN+ ~9yw2FV34-jRJ ;[ʾ5ۄaKG^Ӆ:]zU):a" Dn3c[ٌln9-{6?3'g!t_wpV`QܗtZ}Ȏ;|YSѪc4.e٬[\fk{JN2broã*=NH: ,l PP ;"qw: Tgt\@LH=^N?;ER'Itj;}UuFݝ)n!h#7 6>fu=wOϮ<6<1sm-T9ss뾧Bڽ4g$U3=ro"!4r4]IKl0p[g;J+&c(imc˛:ĵm9}S>x< ~w /ewDSUXŪg~̺=[2IBCtOՇkr$gkyA_"w 1@E'e(`٤,ܳe5*w%5,Y B#7e5OΟ4'Br|&K;`&/>^˳k3h3y<06QCeʌ]4I4),ޗǣ3tbČNշ2"XMCjt:IL4=NŘ5uL!m].5Ѥī(?VUt m2qKL&+ %p\s|`UDOb~Ī̴=mӦ7{r&Gc,_deOM@%}&*>@UA(u!ٕa:Vq嬡w,ᷱM~S({(?{>}pC]ovJ$6D39C۾q햠iϐ>+lhIIIv O'!t&# ^[ܘCt/}a9͞My|.qldG;g=]I{,qZGGg)zSYm/ʶ/]+<ՇR$:2TKT66syM0W@m%1!Y #o3wv,{g#Rm仹\/P s !#kv6|]˸D bWXRǶ#^ד[gZrc:~>\@Uw6QW􁖟}?edImx=b·v=T5J׳~O1f%ɫ&w^T)Dbvkl#DeW(@`cmV bPخmG2$aSJ,=*gz4ac>^a{Wghk9HۧXoS(g}LMM>߉n Ҹ,ة?Nl0X Lgn0ϱf=Af:oz ;okOaYBGil5Q{qGs={gO?=|o _g?`_m\F OAJV+{&= nroyMuAS/1"L▌,} }U`׎/9P+?[.d1IEV]ÔT۱WSP> ]"T$@=[tc߳ yLՋ(yRKYy SFwUvq+*̪8;Š+v}X -5; 'G~h%&1GD<:_잇x|wV`W;9POˮ&n?QVL ^N#*".:qi?%&VnmDR5x(2W kzEE]Kq^*E %|}۝c.u2j(ݿ0jrD4:S^Mϱh:FGk{h&t:Q ).E襀an'wM_\v9=fȆ_R@U{MsIeAF.~)~ݸksL]~&YD@\Rrz\*-|ʨ9\QSHiLk$d9} G}c311o9 icSZ1.m:Y;{<9f1c@!";ӦQWb׍cAM{f;``_{jW''-#:BOZv_l^Fg_U{-O39Gq}yR*30 Յy}KUv]/d7؜iL9OS!]GgOdO3[#\Fls ~]4qS.ܶ| 9xDVLW@Ⱥs54m@/:viX=]'Lݑ97iwzad Ǟ [<窂G{D_$_M>̺Ϩ#e;Ie%wFY [*2i`,NXk$Vu]GBNLӁϯ֟?c2=웗 vu;nxawo2ջ? {r^BmJUN h;UR*й}ei@;F */8c2ԶM.=<>j6xyf9@ޘ;65ȒRRR)//N鵬}~,IrS 89(u_M=o`m /:쩨$hlacXi+**dt; vrm6"0p(*|E$@?g%%dc0添^̨I4kT;=a4avIkbDBV{%fυen=Fӌ^hCN%4QR~+֤3y*9rQE&5.|je9~LE.:9o.N/(=CZ5zS(C3G=G];.=j8())%)-gΜ!H$UUU4Bl6{P{rr&Ic҂#^(JjsGb j'Zm8Q<,_H)iqN>J$E6.s.~/iЇ!KZy hXwZBS NPg!H$ufsH!RtK OCx5BFv'1qHVZ[2I)1cd!H$EH! %3UPi:c2^.!Buy>URtJ$D"Hw=߄' ?"S ND"H$)@C E(D"H$Iچx/MIENDB`trunk-2018.02b/doc/sphinx/fig/paraview-rendering-apply.png000066400000000000000000001423471324306050200234460ustar00rootroot00000000000000PNG  IHDR=hs)sRGBbKGD pHYs  tIME #"\v*tEXtCommentCreated with GIMPW IDATxwx׽?3w%J^P E#ll 6I\&&/ɽqnMrNbcǐ F a: !ԥժlߝUA mf9=rekc!C$R  2.C]߾L~2d8dgC& 2“lB_S@x]Ħn>=O&>2d "9_p7_4O9ArS75鱯@!C&<{w_/Oz6]pC|'C$8|sM/M>"{lc]ħ 2dievA^ ciz=I/n105>5[-x{ kJDgYsW"| sWzJ!Pn2Ʈqh}UwR)ѫ=I@'ІZ+ϕݾ>M1u-/)Kbm1@T𜚉?Q~ CύsRњxmOj9/̋z19QSs7Ⱥ?[9<YՅH[<s݋eܟǢrg[6q/ jħsi^J q|Xx|#2ӈҶ+cz贵Pr%I=T<{# p?Ip|ېm*'=wÍu~hv Ѫsb_ۃcv6ץł捿*2L\APbwϩy+lU.ø4MǕ^|ذ;s]^mƕ0)tFSo}Ɍ%8Zk9Qa!"€ioݖ$Q<_ A@SuS&(+&qH_#R)e<ȫ+O;Ⱦp'ns6Ӽ0m#5bwx&._W`iz͌A6VPjQ0qՀ2h2ow?&i7oCnх)FCU>ԯxmW3)l\lmOc"'X~#o Y_ᆨ{yH&Ty}%GSo~MMG(em!GEѵ<5߳|>JY7P`b,ڜ1.7_ tl3vWZq&:c % ɌP_zǢp@A;Lvl{u9EkP!fܐfe-<ڈ C"KRgϖ-8Ҁ'bHܫ.gR $'v~'*8% WrUDDk项[r@sɏŇ? [b [t}+i}ӴTkoa5_>_[hrpďţ[?(9qU|?劣y-K~󿹋 Zp﹧+[9įe'y?oO4إ~5o+nOMZTs` W7r};ܕخa?zZ|ysIY% ؈Ik ny=foV9ɊM2Ύq ~ɺ=*wJ? \|Ѝ{g|_>ld[gPn3,fe9x(ȶM-–Žgi|2-|EYwbRz1ڰy) H4rs9YCiq[N@,:NuP]mx(՟?)|\bFc^F oN"Tx;Ha= qWDIJ*.6M\aTlz MVf61YO`TvytW'7mBûbя9g_s/q q0Ɵkw?YntkfqXϿ}LL.r^{`;u(Q_~,V!#}4ME|(o[}~ʻ*:0K]#kX7'njX`fOoځc,}8{)諸_ᎎyrHv;bo[U\%*n-4:+E4zCxp?=2"!}|Il)k]%%qvyk7/e1Hg*?Klj qb(sc7~=Jr-n(. b߼D9:QZDpWg._k^+=5ɟ)B@VO:忎gxaRB;NOC|&j"NuAT@۠k>A|9zTevo|z?sm2MYU%Keb;}6cza~8rS%1o~ ^ IY(xO[{G難UºxfRxCӁ Z=56b4 ND `թF5mS@kwH~|+Dxi&Y_^qC[.q*K5,ۊE?wkӂ P"Pwuмie)9 F\y?- bAEWݦJJMR[׫vػ]6$v\I n ?B>?PZ-ZIbv2TUz])BHJJTωx1Q軫;i| },IIլѩ=;iKt9<|mҧ}ֈe P>vg.7,f s8uefC&qW֪ثr4̓estɓdžz D:-6)rt$Tۣ%O$ߧ TES/+cE|UmWSuDѸ=2ocF ˫D;n>h@5d^, bxeUW𜏺퇗؝"w,bU+y(WDu+B *@NM-V0?"@|_0M}c~%H x;k7 vp&l{]=7_Pg_/,K-_W#m>)RD MGkU|,z=#]oMH(p|,}IsyVu^蘼HS+m_N4vN$HP_^&*8Iljl'x%jV&XLS)q BI>P~'3<0Uv'$%pV4 uDŽ; 'jiol_B(a|~I$_hU_DF5 yyNo#?Vvsk [>0mXؼN!Š07!8r':Aj%Kv*6֋K[3 z5;}::RmpSҹokyԻQI+* Bڇщܗ7  $."yV Ѕ0[S3yͤ;Q;{?gT] Q\;6VLgQP˾j%kQ!0ňm/f[@?SE%٢)Zv=M-Qc({"Ip!iɾ ׽UIO~Ĵ79k_N(#zͤ)fTۿབྷAA)\1BObNj22In("YH鳧V9sAZRf>}SNڌKR5QڛtI=H4H@/qw2hTرwؐT 1(VDmGs?S,jg3N_? "1DPvi$/>N/A9;Yv Nǁ~S3^.ecVt& 2.V'sD!8z澇l\79̠'fO9]r!]ng˚%Slr!SNrWN'CxrX E C dȐ!C&=2dȸQS.2d\:7d\ 2dȸtHn˥ CKOO ' cGNv~yN]4c:E30ڤwq1,\r< "(|-j9Ï?o,4$Lrqɠ:Extx526egg  b~kz]j~u;---}esfV**o8dWds~**jtL-,&oLQPC}}-'+p:M(`jaq/9dUMgj^^뻁~T/D9f,ʒh43#K5cPTxYPY̒EF]"_/Xxt3l:=l{u4g|ZoGYN-r9jNge?'LƘ+J7%9M)8NiXn"1!u.TWb%''*smmk}?t.+3ɓ'uuttɄ !3Jνړʟش223y4sP5~RRtW6bZuuX-3qb!JMzxYnʣ0itƚr9Xʳأɾx67u m6UPYza%hzVlб2D.f_G̞Y’?rp:ILHt4sZ[Y5kh|ɟjb@rR:B(u:M&'6Fxcuw0ytsp45zFKss6F1eJ1vYɐ رrMVV.STERT|ڵ]?׍NaCV׋1|xl< RoI^gI9I\F.u 7<őjXS8j-czb klx궣lYל-LlFl@ql7RF i MI2ϚI"UQ1ML4AEv،J!ddZ,sܱJUm&S4J7\};QF@^]gnKGlƬKe^RxsPYhZAFjQqD&4sÍJ,$8;٢0ĄyQ˥3r医jx$70!+5u+q^^4<DDJDF&&&,TV;FKK &fLP9Z?GωٳS55̙3z(L:X6lx^ԕ- I5h g;!3+M^^!v}#'gbXyK:łjܒ1Yv%+8Z-:<6&Y74P<Άi0kQ#R4 I9uV0>d(מ8vqyǥsg*jyRP0bibi"&&uVsE-؋/wyclݺ^.e;N}Y!*"Q`ժ˒%o_Y/RE ޢ3gdɭ ySZ /Nϒ%pرZ[zɱs甕m k +ɘLсw=:;s-G;?o v/\S4z;684I*v5*_ )"+!BvRո|&Ⲕ׸q4=++ S(T i5֣&LnelHFSc=Ji|؞g~b]*-s n`N6sN$?|[F%e }~?JojjBDWQqVKJr 9ְtIORbmkGE8H[{{X-)Q`00yDTj(o߁0iX^$""C$w(3hZtm}cjgF,1#`jT^r[6[z}0 IDATDH|1\׋ٟ{?~^mqtfҞI @5|My(OmgDcX 9]n_ڶ던MIq} mTdfn}zNJ!vN!:FĄ9A%#y2V/|[) >/gɵw+-귖3sR̟Iʝ7HQP7_qō2RЈ(wtA+MʡCL` >.4Sxǎ2RSDzإe3)b¢McJR/UEJiQ!@&c4F޳uVJ~HdH{%UIY\hY^ïC1SZɍ5)b0#̵UYx]c9y|ߨGs*y E4i=@[VbiBYWFAA155TQQq*q!y$'޷ HM}#- <OF:::̞5OA$DQD$^/jjway_~9]wG/BEE?)4pdd'115q*PT`!SE :NQnOT%:5h@eہSa<[%.()@.9Z3{/m] ca;}PzNᆝJ_2ufC2 ehUz2b&Oy`bӗ"9.ĸ̡ILMĄ,m ?MZMrœ3.yfrO!&>GH gLrvg64o6_Abb0}~]$էN^IJwq:}(.WFzL:Tz+L1#?~0!ٳQԽ(-݄Ҍ+ NUb2ƌ"b҉<Ѐ}6҄F-pDž!㕇)ݹXڃFr9S%s0#JrԢmUH~ًFmzDU'wJ^Vm(m A ^c˾#<R(biZIMIani_KGV6'w{O)}}vdɭfRQqի_f[VgO) \Or8V~Ҡ󱇿wV\Qʪ 1r/)$GKK#˖EfF+"=+.ge_&6}@^nOn^|a+5)cFxg_N&rk֮8N _/ϑ=6 P ԭ2p OO{?ؿ!}饿4pԈkuJY)o(l"fPFNV '{an亴]w iaEEcSS(/3z`Z^KK v#c1~?\nch4Nw Gf ~Ur'zpMbB W,auɋWm^OM31FϺ^xt :S{vpثbuL/Yz܄=˼f2Z,$w5J=J4INf4c{wR<+=ާl3+sݒ;4(E YCá}+ٸe=K.#oBdeMоǹQܮ Y;a[Z-t`mҊgd1S )9 FVAɾxrYvP5_ jB/iSQFڰN6$'CX,466P8TqrV;&WqmFm6n^]mSh*5ך1cؽtIϨ3FS4zQ<2MSī|pd :еGī[@vUGC\l"DZZ1F FHOdj:t:ؽw&St/h0PjtwN-,FamW!g8|MS'5TtPqHFzųgM˃UDFF;cɵZX,jf3i$%%Zڽ4dY,@Ĩ%Qہۆ.NC ^Z4n}+8$:A 9PBu1~HfNNNi{85o}4J7J\\ܰ766(/Urr+:Ftm߅\º0P_QJLqd5iidef"%|^/MT;DGNe;IH655pE3 $##Cn21`6b.:M\'z}͘$$I"&:%'+59;ٻw?&׋ w\_kiAk$Gtt4;Ŕ|"""ɏ i^+ǑGq8ue>uO?LUuV+q+IJ)]<'kzgLz0u*fFɭhy$RKBcc3Z(( (B /F466 Jz~ (|n۶2yDKgߨ2c4jl6#Im ( DAA.Y'背 :- Q&#rrnÃ}#GxըT1* cT)l/~?^o3r7b杔״D\h!C3qaאoXZ^&Е s>u:L&#.d B?!P[Wjm#99ԔaUԁ*S5l/qrET࣏?%?2I}::w WJu"v1iFZNTX4 ;zGEI$h8ܾ~e{'jBnh5n)fHLg$3 pEo]m^]_-ļ.lhtڬmþ&``b:UL S8xڽ]"iZIę͘:Y([}+ia/:5HI  N vX1G!Yaʗ9 /YH*{gu4aisuv8S!5ԝg7n!t`0`?iVPgZDs`r̾sSXHJ.cT4#FsٽW0Ncj:c1sL*fրZ_~}(1\$+Zi KiUp)suBG\6R?yL!#bY¤5u|穦ӏ_?^iY.kL3#ټe ii6N, 6ChjDQDVT(B׋'awؙ:<,,4662 y;~8$&$CGg'ƨl6̱KƐ=J݈NZq_jh"!҇ځ&=vZ]Z4^u{$Fxv@+OPP@ÃW)_ֽ3i:/5lm8篖3Аy<^}]>pNk3h tT*Z-vϋ`УP*PTzJ%z;p8a5ѽO؈9&R9J>2,kni!--u`Er`ku`fiDB+aJHlАURk_DYtyxQUߏ 4{8YEbs]n@AVLg=M#?{G~4y;# h\amCZ,-sZ?QYyV.Vr\#!ݾ?:w>ouӼ fLJZzeIBc/j1x>N'3$Iivpx= E/v>ԯt8XexfKK).*w̝;9{ZCp-@". xZ")\t+|7*&E$;ht{a/F3uQ obB)5hE bХᖛط{7KqAIΜ̬t̚3I4 ^$癦'Y T'5>8Slb am2 .x^L/B׾>wpM-y=n^oh(oJ QQUHfh4P(xSp?̌aNpab:SMMMDGGǼ?yc7j7o;v`f¡̽l6sx-QK9NJH:5cmw @Tl:^G^?*"ѷW=}Tb)B$5b6ӪekjLCA1e4mrg4|h8unT9$8v *~S3^m5 FER$RJ"`@ThPUZ[DEFzԔ~1k~^ZʔtӇݷoiuA`~zk3c]pss ় éqq\dY52.&z+OuZiӦx~fQ<^AZH/\FIqD}]tDQvۍ;;z=Ш" aϓ!€Fz]Ezwƚٚ^L0usQ>;3k RKCC#Cn2p8#v:ZV˅[uo/ g*tڑO0.:'{{L~2:742ueȐ!BǨFoeȐ!# 2d\"!CLz2dȐ! 2d\P() 2.қ_2O.2d\:gR!C%٧'C dȐ!" ɐ!CEʐ!b< 2d҃M-N}}Y R k&hoor9^1*Dt:ݘᠾFz $&&P_@KK DEE@RRBpNu);PriiP__O[{9G]i|^ %E30t1< $%3;bmt ȏ?^i.mmm0aTShll RBDT bhs8$I"2" RGA74t:Cy}>>~LHg(Y cB:=eQ*ř:NטC]]էj(J'Og'Pc0FEb6E'0^PTN[rHL0}4:::z]B! .h-IJ^Gk: {ANg:Tt(J* e9]., DFD2uꔰNЗַZXv/jILH --5,t5:ii)[Z,bՂ(H"p6yj7ST: kzhndj5cbHNN"?m6'ijjj'p88|(555cN)`ֆH(wj} ?Q"Ȉ{B$-5%l4wOTdef4eee+Iرq@JvvV0žH~+LX,T*hnZZ,l۾Z>I;%˒lyvbCVسZ(-hh1B (

?5u(œ*vJ0`01 /rKd_Ʉ(!fUEzjLb߰.l6rrHOO`^A*F@0D$.9ΞT#PKla̩t[=\ ZEQ1bZ -52b! Yp8T%6vNwO:vZ;vBS\TH_zL&dff"I}}}QQQ>enG.\tz psaLJJ`804pئ#d2 ^{hH(,z!tX6zP9۶lBҝwvjL&~?LOO${uz**ʨo`M|ҲIWFj~EAbXp8XV,1)AOk[;vrrrٲe 446rM,bwX~#AɄl‘`W-P8"Z>, %jN,jG3|>nIIB4|r_l6 BtvuQ_߀ !!L5b;6[;EQHLHijn‚uz;[m]w7 eYѻAC!4Z-Fъ(쮮l ,q:ݾ h((GQo81^V+~ߧ& z4Z͈ ЇuzAttvUii 7m9}iիto1NzB0zTTh`` ҘUQ3X,Q  ;; JCCL&6mI^Pŋhhl|Bff&Յ 55$gĚawu5{k"1!.:HJr˶mۧVn۾br'a61#͌ܵ%'.F׳}⢝d(--Quzj8xAAup=5Ez^232c $Ii7j5aZuSTTHJr2}}zHNNl6 vX,jڹsIKMUˣPm14BEE9 l6\.^i);hrvV0"ަ(ʢG ܜZZ[1L"q*J$Lt:E@qcnY8V4{V%%SMWW&,RSSB!LffFjkk1̤pBl8`~l)5-[i x>1 /L&⢉BՃBҊ`^βAdBk{;Yю'#%b>ޠ_{{'$0`7Ϯj'M͔cX*YzSBdYbbn[}@VJ2jślNLtvM8񐘐H@),'1!&:INNBRSشy3 # P8^SjT"Vfؽ>B 1j`5DCȘAFlX>_$XVedfÑgL?`QcÓh}0I0T?Px1YK|DjkkYp!ڽN#%9GFzCR+!j5p8b{Y*$I446GBb:d2:܅ jvKss {b0(/`4YB{ka0hhh$%%VKR5QVKzz:l%55u*Ќ>]t|KK1uHLHvIMM‚|lݶ3JU"fMX~Dz1zlE>,)ɉ~biz=zEAjԱq"Σ{W0$1!aɝ8,Y?zj1 Nʚ fbUlhZl6Zb1222Ph"/9̪>s0Xhӡh>TF#J(OׅV%1!dEvF!h b?{ `4?o"',%'.V])eb5IINtvu3cF ]jnƍ؝BvM  bIss3]XqY?V7-.b"pb bc".{[ ǃdieFK wqcq,1Uo16s2N7IrrHz(A9 3v[AEQӃ{0nB55>k``F$IRq:LM;ɉ賏bh4>zcc4$0z]hp8̛;u|XiI1hu0Ι5wN%p%]]ݔQPP@Cc#3fb a˖S[[`nLaA$%z^%>Y8 ^/ꚨT\t@vHjL&3zfN\qq~CeH"cc8wNuZ vr@ @qQgoC_ϣQubIKKrqYzܵĄؼeK,F$v"?/͆jEȤ׏@WW7,O[t:=ьVpxk+d2#^{`% h2`P=YqJtVUӍ$I"E͐$!ICTGH6nqҲt-===hZuY掰Ó'WRQ^$Itl;:"$I搛çkבLvvWLyYٔf@X,him%(j8`hlʌ 5 ю)E h*"??/tͧP_߀FQqGGlwTJ\PP@O=̙sCĆX0-aِ$ VKjj iN:YQ~ T^jgBdY?ӶVsK uhy^u5|Θ%*dU{{]c6^dY!)yHuq{6?Vj9elܴb7Oq2c6ꦨO_?mmxh86t$}} F|~? GGJ$''M7CR**1 7 6ᑥKXb֭|>uqѼɔ"3‡hP(Lx<$'4]. fͪ@ժ1̌ 5`|B99(5̝S'e׮ИF֭@Ey9'.^D{G'NHV+@-^od'Ev݆ݯPdFP  ^>k뢳@@&b+),GIFJUQ~T=p$ꦥ,^-K(III#BQ \{kl (;+!C^久C!!SMm]98knM [ !jeǎ*NV+jK<$YV3JKKHfI- f=ɷ 7/kY:B$Ovv#C6 VXbQ7Y1z_t—.]+V}*74-5~.[(l6a0j&Cņc446bZYJQ-=P>u z^UU; z&4ժp".~T!,Gq8XpޣWΞ=,\0-[qǓZdDn'##{ z8q)e͚O7w.}D( G&F¡HXg%.nC o`O^*˘s Fbcz1/$6'-t23NcϞ446◓ت7%1,:;2<I򑝝E||O6 Ϟj5u`<T6;;,>]T8q"5>·d1YKΝaZo$N8F$"IN,H23&6vm۷c2եL&3@Qrp8g(2T3?TΞn -x<;;6\JȘ`9'TR]O֬^%ىg0R֩LdsB%lڼb233-Q(|a\.M z),ȟRYzsNdMEgfdnzrGՊbNqxJ[**),,Vm?pԆו=I%Q)BY~10xxZ*A`ta*Nٳ*ذqY& Q\T>ˆttc9䓈0^v5lZ1zX/6:}R.KN6](IFۍ#99i55\`(,giE`;ĠK(npQ.VE MKYJem̐G8NENv/?mZ`ıe6$I ?#EoE,&UN\pZKzzgmv$ILb |1f E Lyymm[CWgOu 櫃{{]pBRR҈/-%H4I:-oG0&Ȅ >[HOO+!V;R8Ÿ_Óݼe8bQ2{iY1ns)'2IINfˈbN,I$ub Ja+pvxqYa#CD̙&+zzzhim=mb}ڹ^+0E_]hDөillvO"[SA=ҎtZGbVL~^!(MM-:s#Y&(t F#J(3^LɕՁ{,{9<bH&2dz2&"i`{~a8ӦB+-ې"DsK:zodFiIa_hX,'U-57\Q+K,Nel:::hhlB2rjzñ\|ss v4@dYVe`tZb1r2P|{d֥edet[oH_?ƃd6f;gYfxekInv8b#Րgmpm4,Gcg-[ގ.Z/؍V_{zzٶ};3fb2ضm;e3N~ō&,z@JBrz0!;Ts< #Ȉۆ1vOJ,ЏbY +u{_mvp{E(G$PPTT8.cY5c8:%{+Y&@ kh-Bޮ߰Q@pީ,@ 8~DohhH@p bz-27ldMB獘;kSۿ }q>:wUS?`~D۟ʵc1jV~RS2…c\_ӑ-DoZ⡳/m~רds-7bNMgF+?R^]Me~r,\0kkXMvv>s6| h(J uȨS8O M>]~ 7?>%^܄i~ʿ Irޠ=8J *z,@8V&*@ps|k䣨p&]U00M|ebFi>dov|*[Y|$:7 6sxp⳹p_ecL IDATq@ӘYRFἋޟw _|-: yvywapreѴЭ7_={!<۟{,o#@Wd5 G2ݻ. }WYT,C}ԲL _;`α>8cV!O0oHMNc׮6aj5^QQ!nr*-Etͬh>^y?x7˭H[xٶ>b_0Lo˛1myd.t~_:߽u?'d#Pr1߻iV C|)ZFWpLGvѼt;h~j`[^Z3-S3Ɋ T*r>O_5osxjE+߼DiauD .&Yv]ۉ7΄vZ\# ?Do;,`˥o';ucKLQ؍7 ~rucG.c7mx_L⟽c'0'`Zޯ [,ŒD?IoW =];UkQή6r@j##n/E6JhX,#JDo8QIIc;EMw @wc83Hʚ޿JӁqbLe9,SEtbeQtIiSZsuI-W+[0: 좡sRvҙtvBOOgdܱz zހ_?_ XO7Pv"r+|kyD{tqoh$hզjSo,0(jip g޹82 TAMNYq VjK/xF%-lo)jl y2fϚ?} l-n>A**)4!8fx򩧹jY^|O+2xv}+yĠ >Y ZϾĄ5Po~Vw]1onh,?Gq^fySvҖ^W;.|6;}eӳ_BՆ,wLJaIz ꔰE2vT%cZp I)TLf0`G&:{:)<,B*%<>YᛨMǵN=uϠx!/ν'z=@p=yQpBB1IBJgEM M} #bASz-[ s"9C e_ޖX|S7{5C "} yL;- q=@ DO  ÃHb118yҥx<Z Av)@ XzZg{%^M|^?K/ՎQ]VbR*<}F!>as@ dUV>G:YI/s(1m}s &e Wuշ^ϪlTf3OS'wM`.+ଇm)KP6"y%_ջ* B '$VVyu|L#S\w$o_k)*2wUعѷ.|}>ZMw}_V%J'~~I$Wizʌ~E{xz)NIzN"3ll%u̵J'M秮ՊN#RJRP 0}EZ&s53z|JH2@ށ$f1 c25=Ȥ`Pz 8f._7%;FLrN@ chh%΂`@o0qC{vxyA- 0Y<vtSS?ބ6T;tiCD@ Do&(OCH^/@``duKz2sJ8n.yYn(16|w~?/8;w\GPE9wB4xf )@SkT Q^^w͛IHLTu։'%|Vq#\<rr2nh}CDL/6 8&D{A0TAS/sI#//m~d@@ !z@pl-?]5rڀbeS0mkmkf.W7\ݸ\hL4&2w.,Zxx.<55;ј4uh!nƾ+Kt hM ^0/RG<6{;?UVI<|mRK?nNϻ{9vMy<ƽ% 1 OðO| &L:V!]ݯwp⣵|ofri*e' e-}{9&{1?y}3 Tc?!j~?^~v}RKKk#[^ hFe"~ <|'\k=Qwnˮ䊿ƪ<~Mqҷ?XK- ^͇.z`_zgsOaNq,Z_T'dc !$7̳!ધϐŌSCGΫ䷪հ ;3g.gފTryi1HO^v5W\NW9V"|cŊdeeQVVV噿e[(=[PanC^zzz#yQX8=P6f0WILJ{:ڗy^<'( â_3ri,}1D}{N|<gN ܴM+s8{m8/g.n.C3D'`p; IFCc3 N89x7ٲe ~;G d?~ikk183aO\W]dmxZزb;'줤80h4эMaxz=n=xt:s;^4[< g V!+}'ଳB9YgRqU[DX>o9*s%c'~~*(Zs@Skab'Ck w_2y=nsisIz80tiXl 䌌X7Qqo0@[נL`1$l 88 Lg)6LoːEcb۷ܧ_d>^^/,N({R6n⁹w\+g}KO4H爷~r|//c.w쿛Nf--+?7=Ϭ '?uۆܲ. KOp]{\z ૶#7#_+ӣ4tXm#KV5zI> J͟jB0v߹MjWf7WЈm*^nj{{8z&ЇNЗ M!0'65F'8ŷ7tPET+a[2_L8ωU+Xwx6{ KSUF Vgs>Jx>{w77qI|ٹ?a-~AKq׋7jF|.eΦͼGoP׎=F^嗝΍Q[~k8tυ'd@ I'1⨔)ƶh_ݍ{ln:3Wl3Y[hhקzHNٜ[lIBgU>hgvzyZwP\AWZ]Q|\h?%s39oW\ttK`]uX>wtq7탯)|P?'o@k"/2^ 85Ir)=Q O>“(=,|v(I=pa* ZZGcr>9@`d\M2rԛݽ!v,s|;Б?9J Ev_wƨBdoǭ7"D@'MsU޳lidy I1ٶ=D_JMTqY>;Ì)m#6`Ob?I|Ylz8!#wbkLkw1#70\ΫSwӧ?@aK B(O Bx}at vĿ| =- `/db'Dקxnz;\io, izO7=Tkv5hgYn{|} IDAT`@ @AZ@#n@ NR20d @+燄{++ !z@yE$27ldM:wy,\0Zyƻxw #_ms~׹(߿e\?p)]+:x/62 U?yZ)<'n3Ruli#wt>.:m>Wk~hsw yUF=wO윃];bKTbKT`5+**$;W[sD爛O?/xXQSxeu>?`3{V=Dr+(#ܾ-<|l[WUW}f"w3?6}wvQo:+  9֯RiӁnV>gWoo."\.xUC/N{q-ͅ릥Rի|<}lz/[|k1 ;Iz6%x;>Quknl)ǂTF!bp$,Co[v;-R(w"EREEW@P?"A+JEDzMB %3lz!,yIvΝ;gfΜs{H5b MhꎊToܪ &=ts3$]%1$O܍6O6d} |z ;?7]@Ub[6gSgkx+N P{QS(N#)rlH#Ƿ~;SN W2nYTǯV{ o\%V{Xގo`ro'ވCW'-odsqͱY{/T0p9لu貅 p%\|kԔE͢6q{j4 &-DGC ㄹOe`J&2OA*Z긦^!b++eqZQbUThob/5gߧ4Kۿz7(xE5^[_&A``vΘ΂]I3H:+>>sm}l~t5'3-D1=~W­#W $ '^v_6t \~/k 7ҝ oZ`x?~nvEcIeۂ8a^ѐi^9mvm<-_q _t BS7.Zs h} /9znʯ}+[A:hbVyۯpMac0vZq"Օf[17k\plLzb73}QKe[FLl3zG'ӎ 3xZڋz>żGBp!t! vW{D+̱%܉}=_H2Pb3)\cSd{k Zk~jϾ=)Ze[_UbjBο*տ] UuDFI#Jw4_~Uˬ$̾8+![@P%p V 8+!|)@pO!@ 837:==AH%!?__![DrJ .@PME[l=B %@vN".>Aj\;J h4ԭ[Zor囨+\dى+YԯWWr2+-%R\K/wk#oYaVgDȸːeoʲt+;KF n܈u'2JH\%rY ʊhDrlN d3WwX?9&˒x8D{ )5YsږΣxqqҫ}jWZ0J_:N})mELs{1]~MTW[*+bN;#;Q^C3sUHT9pWAѓCph ƽ=m֭dQqj*W SQ XAhtlRkFveƱU ΰIO2C5ysS$Jfm)YI ٝa{ҵ7"ѫaqrbW+s{@M=Jm GN.cgR0#9G=5cn>x4"A;z> ;l)ly9}zЮ8nޝ-hߤ~c}:f{H#@hR1|7f%+^K ~96#~c›tόHTe+"#J/S\8y?ڧ@ IHѰ됥'b $|=>\9FSpN4c0݊󷞋,y*:8߽ԷT!ٽ^ 󶙍&ήN6nWB=$R&*W2yҷ7}o[~g;l{ikS=9D]y)wDK\hC+VЦ-^Cf}ep l`UUmY_O eȦ{?dYzoAxæo֙~3#,#"Yл%F`?cşr\Ŕ'rN'瓟ٹw90olر|Kfޯqr\=o`*Ut]h:KŜZ,=ɹjo&1-VzJ<~֝wNb'@'+ДC&ӠkQSFL9Wy{eR{ROAjLىI4G.lt}-iҳE,XJ%l߼ߗԕ/ѯmsymF_@+bE(Gu<$>ÔW*jK Q'p%.o+:U@%^21$[/c(P$i ۗA(%y'a!n%Ȅnf~] )+~Nt{[XN&b8S"IJ:^58?&~ԙi@ހa/M$$gEs {lݪ'|3 iR@!׻3龄)Q̈\:CN)V'u~kRֶtUս PR=0N<P[^St9IZi⓫?k) >mq󫃟KSڼGj1:&rT xlL1a BۮblJ" kupdNA)r-{9U1|:'O cQvdxa@:IFFW-:Qz5._0o6^BV@F7 INGr~5vξt/ IQ5&r \k\SbHSW'[{loՕi\M1]Anu^5 DoBF9w̱}:/DG8a”~~ź)|JlvWIIXl1_ABz6fClju~nCͦOw*Hd0dUt-%ɶdjLj%I6Ǹxw}:&abĊ'؉xy{ƅIMIEotdegs豪³C߼Lϝ;Ɂ(+ee5߹mxUm趵8St/ߜ?8?qzE(mRvm')d RdȇzYp-~mߞR )!K2Wǒ/ :MjŘDz ^dz ; `$m  K0gQ2!/NTZ=Eb͂ ~;.:5JK/.>Ʉ7f3gQz uINJkhHOOzl,5W/Y{v80Un-kz+-ij%1[fp,99%-͛b20ˍ1QqnNf) QC2mTٖO`ͨn,dC'Nat@[RZ`||=h['mg Oi~<$w1\" JXFym/*ȰO]#duNԨNMڸj= i@Gb$e+?#ME#t5HkcTVAVW_o_Mn{ ZSI1g;saAzJr)e'x+dYf` (XyjR66'?R_ŷGR˜e$ΚNRv |ݫlAvl=p9 Zz%#6 ;.#5/.L;=!o{9ߎmM2&eܖГaݴ<uiMasՏETbv'7.]oPɶbN낷7ɜ:s@3f44>@$,9s8@rR!1SkM=ط"ɲ(qAՀڿ}  J_<# w%Ȇbc#R,[6Pa,,ɴ烃}:~tAYl*n~$7hCclM>+y <:{~W !`;%(Kxq>2bys pi+nN pbވCrUK8X02>UXݞJ9qR;J^IBL, !՘,k^O|6VGajї&54ˆ51 x-7pcG&>J3?YGv+q_8^K^Kgvڱ㝼4;jb9J|hkVݲ`KIR1[dffOrr2Ϟ%?I0-dd̵7P*ȶ} Rύĝ?UjyQ$p|&t`3#o9+6#M9 IDATGiȋt Ae >V}r,J7CtI^Ct<ڽ`N'59^cO1ܜ6Ұ }=VuK]iP/j-ʮz:5gSV^>MNW +).3mFV+,+ Fo,i' 70<z/7e݆@F(P?jt:ZW= 9T^j"fh=ĝg]A!513rU܎F~T-УvJ\y~f蓯,†7_U@%F$)g4HKMyf>̵gW]/27KY;~ѫ3ýhi -1ք[qFZB%LM RP Rϕ/f}$^%11@}Q)fg>'w1Y(â'Y4c%'ڣWڏ㟧yNg;|Ԕeڧd*P%9%h9he)F3fZeڿJ2RJBJy9{qPUf[iA6-/b7*YFF[ Q邏9Xdm)Sf5jĞݻ[WQ1al;] ynL&,f3*$.T5m'QQ(P  ٺoerʎ%l›BE )4xTkF*=%RQѺ# gGERtMNbtN lVF=C/ddzSGsz,ixĔx$s׹UIܗeWSjy2ζRp˼+Ԩ^WܨjRJJ;s% U7>R!3â}$'ЉGiQ{Yj$6A­gR< bViѢ;mz"XeD\n٨ %F R/ft7DTZS j\l9b[);dARoolАO*{R$W F 1,NZcV@KEӠP'{P(h۶ s~_`u?:vR|$^[>WOxx_?& W&e~cu?_}WoTe6[۷g0??GmM]Rn} %=#h h$)) 7772339P(X,f  +5s4i—{&?fc>DE{27.ğ8}D^K&##r .>7(qd 7-d%aq7z)I}3PAfj5U75BgCv93IH+YɌQN܅XT&/ͤMF6qceL9g/YõYjyKT}*IeWeYB>_# 6n_+;ےOV@ =@^moH%:O5-4YGi9"=/G۲56<ɖ51 Vk/afd_-:~n`sKn) t1d`4.j=b`i>>>xxxVE$<<v`Aڽ z }˱b$Z5EӂUOV<}o$_l[˔Nbv ݒ!mjrTiTZO?\:K۞bX0**0`'OB㢢}"BADdeesy,3U~Z pww+C&2_ ӐE ?3b(*%%J-eLf3^(_ZIuIr(J٩*^x#7RkUTo[!ϖ\EOӻ~%o/pNVJBTҼEs!s|L{$PoN̛r1qX7,sUr*,*6" Zz7n`?O<Σ됖zhQ(h\h\X,*GVqG<"ϊT([ܪ݆d]9]`%$$0d`4h'?ɛoRaJDRڼ)sչh遌0RwٶFe m^ob֜L>ӧG{ڇ9H\7ӝHqdޞ;3pO2:u!x]wl3ש]ZZM3{l5o-Vd=aey{;n; dzdjm dtx_;ƽ!)9>n:x{[?iР>c'<ݻ9s /F;KqePn !ӻȸ;qqqSPfh߮-|ڽcZyWz* wB#,Z-ݻOmpssCш;B ܶdw+EU(hZZ*!3"C n#U.G@  'n;QQѹ ==q}z#C2@(=@p7"C2=d{݈3ropiUnj)swӉ+ +d îΜ=+,riۦ5-[ 9%{U6GV [H >ewD{+ɊRb-zu92Dy@P!,=@PTdvEVffs]\a@p[AX9-.i* 1,zkng#5Q]  dTY M/I3f`ƌHT:{Dx4߀:a mՇQ37Xl1 䯷oYF֟dnμp"?ǾԿo8ӿ?F RqlBQ/E<:K卜/u𲫘R-_,dY{&=yxڷї*"TSKOVƪfΜɏ?c֬Y͛7GAYH N`4"#YΪoHܺ ^c[v{n xYʟۢ5$W)4{mKBD Er+V^?\&7'ӨR\a EFUں$/^iWNq5T.UD&{g2nғA={*8}O^|: ;1r|:JYƒ/_IkFB6C)mh7̺M@KdL1vKovtE3ep{6_ٽ ;0ə1FџVm%DzcJZº6x{*1Yz2'olF?a}|_w\U}9>[Z/F]xx*NgJȲ+KӰx;6G e?єj-PuG&ܥxm(f#|_;=J4w1&ds2xmRaK[7 KnRc̨z.F7c¨Qxwe}JF(UF6^f(7_:Fԩ?s9q2$KxzH3s_6]GI0/T|1a+Y . ʕLs#g6ӟft7:sś[?kXB5E%]~a4=&)lqL]n`],uڠjΟ旉m4zYE>h'mi%n2=kjJXJP5JB{ sX3(&~]zt9򵉴Л:;\[2e.7Q}Ҹ|?Φ6T%SI3Ѣb4]ȨSKקg /^Wŋ+;(|}zJx7WSȵV|h͑|8~P]t6S*_d_m:^6TVC6 7/JdĐA*._85TiĤ<77v+PE Q*yF.Gw d<z~ckedyZr S.ʿ1YHj%c$#r~Y١P:e1 2/=N!@[Ϟ=իW о~ܹQU=&m(xʠ򩍟)S6`tŠQ6"ctmc4 ՗\s]*k}*XlU<(,4R_&!a/PT9Ų@ Q, d(LVt u UiƪGd^m Ocٱu/Uվ40a_m`y:R۠(J&*\T2FsVYv刎~tdSa3 iǂ"pf ¾#غu+#F(ڜ^+pjz}&Qz޳<1e&47w,cʸ]%mN8+7B6us.Ì9kA y<B)B* 7ȑ`ZĺLY;o.;ۑT6q (|N!cIeKNatl̂xOik10f+5RfLYY /hҾ"ί6%u և%"#j.r4\Ѥ%P{ZQud:vs6g EuyRذt5|%{$MwS|Q55ʽ:]G%Yt`#Cs"7"IK..xꛏ0#۽I2>A}\xJGW w}]}f3FYj.8)]wa&c[$јf?C8?_ [ژ~ݽ.u eVFq{ {ay47nKxLΚԂ>^~(R6#^͞h`/\[iVɌ$(qقAoК*1$RP1 F4 BtV RSS52)1IQ;~>EX҈ ;$6?1}dbD)0OodB6[hi]'&M$9%K'33l}nق#2|kZ1is`5Cp DmlM[z\>_\ݜf6t = L߲>+@FyUUW}o^^F;U?yTf0xΗt~Do>>/ I|L-%wB Nϑ@VVRǬmZtJ |zfҁ^}ЫqA~ͥtV CQ!)nꓕ4R\/oo!u@;"GUR+6kS3(AZWYհ@̗Y3ˮc¿A92Ϝ9yY+b]ҏϾEFַF\Q#2ʓ5MEfF+=iJ cJbx*eѲ&*ԟ{Ȯ;zatk%I}n ];{An %N RVNse,))Pjru+,܅$ܸzSWIpH(U31XҦ&'@  dxy{ A 5"C *;bDDT TwL >(퓕Wc,g>=X;a[B%MQ};*{nQ@prN"L*aRɑQrC|ZDxx jg"(8XsoI勜4 YA8+qO"j滱z/>;k9MiP#X~pZP.a⣟sXD]e*_xR %FpHSuO"jitCϺmh_[ZVEcchBՄGߜFPx䲕/8a@ ( N f/ a@S5\&UD_6u@j(Sb` *]?8h񭋏y'ỷI>4)lphp2$.F0E>%o1qȰ ,,gXψ ɰQPNIDATBu@ (;wL Ⓢjx|jx &52esb"^"BzL q7W !&L"Z1@ TVL"**;*!J-ȑ!B #C s@@ g @p!@ @  @p!A2=dPznD2=d{"!B "!D C s8+QfӒ'UquA!SRpi|;ˬtSREFV=Q~\<'@ ({ VOO vD C s@P,D&*J!娨B J*JN n"!SRQQKQ֞B\6@p33!C_wY@p '<:J]mXV3(IGUEZ}"*kJ3``[A ',蠛LŮ֞ jWx@d;,.S '$ d9h.SzMF2tAl @ g), Ͷ] X{rQce9 7-` 'nAO 4 HoW|Ƣ\\5??q4s [KO(=@ 稧J/ݦm<^M٩\ljSzKHSl^MWTzrQ[{:wMl Qro=dج un)֙l5k_ @lk`l)Ǧ2ۂ2PxVzvdnV^  OXy.@FYzvŧ(?+K7VWp@ᰣS'+7)>J(;@P_II镶RJ TeX,U+\$g\IENDB`trunk-2018.02b/doc/sphinx/fig/particlesVsFactortime.png000066400000000000000000002023001324306050200230310ustar00rootroot00000000000000PNG  IHDR XvpsBIT|d pHYsaa?i IDATxy|LgߜIBBdBQbՇRkvR-ZEH*]ZZǒ *!dmr̓yI6I~y>9ssϤs/*Q """""*2L@0!""""" "z޽vvvذaX`j֬/]Niiipww_mlqqq6m:wJ*aȐ!x*Uk 8NNNhٲ%8` MDDF wwwSBD"*?*U(PVz> (B۶mEBBbbbtmۆnҕݿ tf@tt4B:KJNNFv0zh޽gFHHfΜWoǎtmaffڵkiӦQ7n_5љ_y&BBBp1DEE{չ~:,Y*U_>jժ_-ǏMx6ܹ]v!$$ɦJ!"*xqppEQf͚h^X…RDQ ߻wϠlƌ(8p@UV(J1%''L^M˖-`9w\4h Hdd^}e˖RK.r}\ʼng5<<\6lؐm (??zoߖI&(,;v0jy%5k4uDT 5ׯ[[[ܼy9MOOuݦ4h`Preeʔx9[[[279+++# r>|z®]oC'h4zm4);?~-oܹsF=/ʖ-k`BD cƌ`…9۸q#֭FjuRT^AQrEṵqFù2Wxu};x#F8’QA  JCrʸx"onPGDh"̘1#c}`[dd$yFÑ#Gtۯ^˗I&>|8"""0uTxxxN:ػwΝ;??? `ʕ/1-- sEN၆ bɒ%zO$K̙3FǎѱcG8q"lll0bAre <~~~prr›o7RRR0uTkB.]۞?Æ C >}K,۸q#z-ZzO!"zu prrСC#Obر񁻻;EWnݺիWqm(bI&cǎ;wn"SN={NBbb"ѣGth޼9u/aΜ9ի֭ƍ㧟~28vnk\\F^{ ڵÈ#m!!!ҥ ڶm5j>Лƻヒʕ+0fF||<>Xb0e/"*9|||͛mڴ1O?kNE7oo޼Y|||DQ|Ic@g9wJ___aaaboo/GTݻ˶mtbbbDRIFF#,"""...rYIKK~[^+F#:t>@Wi&QEy>>>yꛟuMrsN8ȅ cǎ'""wޕjժIZ$11Qo׮]y撒"""bmm-ұcG ǏƢ̙3GOy扈ȼydٺcnذAT*L6MWgy7tcy.^(ҲeK]Sʄ t7oެ='_ԭ[WbbbDD;>^z,7nЫ$_zܬ*Jf͚zUTEQdҥr=ٽ{XXXHӦMeΜ9J&MdΝ)ݻw;w>|T*_z]?~, 4-X@T*?VZIRRDDD4lP=z5j\vMDD߿/"~-[&*JnݚID%"*Y H||8`<44TDO@DDV\YDD$999iӦ=zHlllI>}*"ARN>^DDիgo>}DQ?DDq2g:}Q-Mʕ+'k֬+߳gT*ѣ^yVg/'M@DQ8qE\\\t]׿W!*JZj%""w/UT%"ϼ,,,LjڵKxk׮J|7(߿_[oexT*2eJJ43S?!cƌA`` ,Xooo}p81?cȐ!zvvvٳ'BCCoO>ϏO*P֭V^ ooo`ԨQ/oը_>ʕ+W>x` ((}IѳcgD?޽{ŋʟ{oذO>E;v숪UGll,sw1) QfffY 7oƹsбcG]YFFjԨx/ZZ5lذ}777v_}D:cƌApp0VXJ;clll ;s="҇ I&aٲeعs'Ν;MbѢE>}KkPSR f&MbӦMrJ3VXɓ'>>> |D\z.+  @{5~M4?^<)) zW^zi?BRaǎ/iҥի>C|裏0v~N> p0?W=j\Wo߾/!7uǎHOOG*U =:YZZnݺĽ{=Vn< .NDC 00 ðaLSrr2D3VBZ2Gŋ;hݺ5֮]cG^eJ*A@D^:-l 0n8ɓ_W\lF˗/###u4z!<</Fjj*&LN/؞)SN/h f Ӈ\'kkyL@(OZ~ ǏM@ִY>ӧPThٲe]V-lڴ GJ0yo`oohOZj8r+)) k֬J”)S5jX`ƤYFO?E0sL`Æ ؿ?z\5w, n߾5j@Dԉ֛SL_]b޽r|FADr,ڢ~:Zt)7\UF ٳ.\0ض|r]]WgggЛ,'/θ1!|aBDBtתUKݙ3gt}d}F=gmZ*vޭ-Z!9;<,, }]v:t(233a8jHKKx{QזL 6 G=0p@pq 0@7'O0l0]L={6vލFs|c޽h m?s kTV ͛7GժU?bɺ_㣣1l0k0m4qqFT:t(E1p 6LoKg4mE+VZ *믿6؞ӛ7x=zct!!!s]֭[bŊطonܸa>~{=֯_W'==W^ѣ E.0E0НJOΝ3TD$))IDQ}f͚%*J>+##Cj֬)+J%uԑ ]Ν;Bv4,,L\\\̙3z/_v#GJpiqvv0]aÆ̢qww;;;&%%}8::ܻw&_W~Yԩ("Æ 4'NrʉJ Y-[VƏ)*Jʗ//Փ Hƍu2~x͚ȑ#1fͮRB "r ]-[T*5j,YDWqƲpBh4""o>ׯ͙3GEћ fʒ/~1f; ǏeΜ9bnn.חK.mqttǏmKKKVZYժU^UW77uƍ(ҍgggC EQoKt\D;J ۷oo0޽{~Q 䣏>ҭg`ee%mڴ[wr'(O||L8Qʖ-+n7xCj("ʕcJttn EQrʲvZٵkTZUW^~}p9";v5k 0@"##u۷l"=<<ҲeK133EQ|2k,9}("jZ7n,۷QFɓ'O^z=z$cǎСKpp^ ](8::ڶm1BW|)ҬY3iڴL0A/azӧSNbee%+W3ghhןpuuJ*EVZÇaÆzqybbL0AJuT˹7owwwqvvAi߾mVϟfffʢE^zk̙3GRSSu6lsP\94h\rE3PreY~,^Xlmm_9}R(Nll, ;vMSADDD""ʵ>kYf6X߄YL@(W|8O+V --Rao{>|~~~7q1#GK n~O<#кukn1Ӎi1g5KO?TW."""rƍh_z[9ߗ֭[sWX~{բRQ*V(J??"+ÇJaÆ!J*,m襤$0`?cH0䷽o.e˖={BBB\gĈ).\ 3g;;;QEn޼Sƌ#""/}1f".fffvZFIΝ__~)HTT޽{Ve޼yF &흒"Փ6m$(lݺh1S 4iW\A;))IW^Un߾ˠ~1}M81O Jue'OJ%!!! Xc"h"dff_sػw/Ο? .DZPV-]Yʕ+V ##hqS䷽>o޺cHMVw#44/6VTH ݻw)GhPnkk [[B O^'996mڠ\r-ZΎs~F&"رclllPjUm ۷n8}Q)H{w۷7((1S7^/~bᅬvڡO>hܸq&Z01S e(SLKQmvc̘1 Cc/@*Ti1\ڵ+ k֬9͛XcMT1{l9s͛ ?X2ޫ#T`eaoo_if?BCCsEU3f ۇ c4h+W)rʏwVrv{{{!--Cb ͛2dQF0`9b^- 5*Tk׆el@g_2Ν p+ 흔"W* HHH/⯘wMJJJ:u>}ZXR!('I&666ؾ};1sL8p"׸qcHEQаaCDj#Sڵ+߿;wmJqpq˖-SNl36mj) Z`}WRq4?!Ch0{8 ʥ̲5k'`j({JrׯΜ9cuޫ]r-[DŊM٫ H2e ?+߹s'z꥛ #%%Eo(8q"nܸ(];wp%L>o{g?>~mԬYSWe˖ҥKFMK~Ǒ#G nfϞ=-Zpp+ oWWWSѠzF Cff&TzBRJ2d9d]ѣG{bh„ ҰaC˗ߺ:nnnbeefZZxyyɠADD$==]';v= ʵwzz 8PM^zRBUI΅^.p%W_~Rvm֭HhhIDDDўZ~_~Z-֭ӕ8qBlmme֭EwgÇEQСCzIIIboo/+ԬYSDD$11Qڷo/,Ҹ)"˗/G`` |||P\9ȑ#z"55U9vڅɓ' j>>> vz5䧽Pُ5jTOywvT*'x巽paL>^^^=?T(޽{֭[1|,[ Ν;eS8s FszΝ;* *TÇc„ hӦ D}ԩSMrŁJ$KDDDDDDF1 DCH IDATDDDDTdQaBDDDDDE & DDDDDTdQ)Q HFF֯_777ܺuuSSS_bС;vmOKKÇ~///mcƌ[Y/|||?ΉJ0tvvvX"GzuAQÇ7bQbVx"6mڄ+W">>uϜ9cĈ|Vff&z ?~`ȑܽ{7|MhӦ ^ ///lܸz2ʹQr9,]077ǒ%KhܹVX777]JɓQ'M/7n[4ѭ[7]+Wq5ԪU  ̝;1 %%nnnƍu77oիWakkk$"""$((Cսh4hР×3 C``#5 x70lذXp!jժK>rpwwNJ+شin߾ 9@VYfK 믿% wz֤I9sX'<<o߆6OOOm۶A]OOO0WVKw.]ޥKawdd$-[Jz;vٳg닪Ub֬Y9M.J\Hl߾o1p@4j}KtΟ?pqq18F JvuGDDDDKgϞ߿K޽{ @Æ W^-H _2?3 o&&NptmڴcРA|9zU~WZ ;wZn|qNF#FX>f?~={DDD,,,"BSj\t * SL NoҤ kTЛ*KFFT*ʔ)ºYcD@ݱl2L2* gػw/ڶm 6m2bQj I&J*@s2g:FJGˮ^n;v GŴi`gg}xz*͛g0Kw.=?~Gرchݺu733/^ Ghh(vc-[_}4i0Rd%)))zY3<}иqc<+&&:oܸ1bbb<_/;=8,Y$OF۬YL!w.]ޥK^ jLGaÆa̙nyѰaCԮ]X)5 H6mwۦVѠA@˖-SN#22hڴ)Gؽ{7N:ڞ>ӧOŋѩS'<]~r^vZE蘊f͚zyQL(S(q Hff&̑<`̞=R(M'''ƍX~,%%ɨX w^8Z~]۷+VիWc׮]D>i$aԨQӧOG1w\AӦMC0n8]=kkk|Wزe .^8q~GYF=W""""2.p?>ٹsh0f bܸqXp']OZjuѣV\ Fu+3,z% ș3g0rH;wгgOt?N߾}aggٳgch4ܹ3f͚9vڅɓ' j>>> 2O>aii 333l߾m۶-'"""" Ɩ[`#`lf{!"Fhh(/^ `„ "55VVVˣL29s&-[4m_uYf_K.ҥK`ڵz={gϞKDDDDí'0~x'>i؎' fffزeK>?%~<竬u"""""*L_">5]Zö: s8VVF)%1!""""zFH3ԩX,L@+]A[!%#1Q/߉ ?4aPlNoT*L@{ U8 ze04;{?STb1!"""RowUn_: j'-+8 ZScدcGT1!"""Rku*"ET`BDDDDKE`Q΢C*QGfZ8҃ *"[G iVn9>sLRJgkBm7a0uH """"*5?N8&aBDDDD&S! AbZ"]1dST*1!"""Ra鱥8|0[GP C*Q}ePBMGTz1!"""-5# B& ={:R hs󰷲ǚkRLRJbᑅz|N&Q[#S21c0jC"0!"""jzj6հrSCJv`ujaWQ& DDDDT%a-'SN&Jqý{pwÂN L= PFX[:$z""""*fyB-Le {")ѼJsl?!QQ*ډfef0rkq0m4 ffEQA[iSt x}`%QB{elz(*ފ""""*;O|/PͶQ/%}Qߣ403uDDDDDy["#3o5x  4{ݽ \\ܺeg)-9auPTFy ) HKj\\}J>!"""bRkP(94{&|BDDDDF|j<2pYQ#mV@&*|BDDDD?&擛aWnqt ʕ7llV""""*^يuA6e ?h_Wxxfc.TDDDD{o{0TxzӧhP; """"zFmIO;~Zǿs4x50& DDDDJ Ɩ[`#`5+[(""[f B9< n=;>M+7-&'k\=urPb[Yi 9r8x`ѢE o @LLL.\nݺm۶Eppp΃2%Cxvi~X(Ǎ&@V@zL>JxrElڴ +WD||K߿Fv#pyڷo'ONW/<<Xj7n\Qi {̭͍0S v*DFQQ+j{-]K% HF0| 2u3331tP+;֭[ 0m4<|ӧO׫;rH4l}888`ĉ6m_^""""*.=gtY:x))ѣ|ԪnJDuf͚7x-[v{`` S)SڵCpp0 ;R*$&&"-- W^v;wRƍbcc @R4n'N0D4iq"44T,J)1a1OHЮj]LaC40|G8y$EA=|rTP! 94i?xՕ1 :tF1n:ի@zzq"##aaaΝ;zS]|YE[8p`a6Q1~x܉:`ߢ<{.p6([hL>Ν;K" ;v@ǎ~^>%33z˗qq9rIJ@233d?#'ם:u*>}RRRp!=;uBQ~}zرc{DDDDKE`Q"wTef3Fkح[Eܹso֭[G:ugЀ^v²etNɓ'p¢P;ݾ};WKT ^z>eV̙( ֯_0޽+V`ٲeR%Samguֹ/98rD;Z^ZVYfKF/\jBgT\XbE{ R"3gΠy ׿^_Nh֬Zjv!)) 7TުU+޽s΅/{=Y#F(9d"qhZ)yNN{v]sseK~}؏&22˖-{B}׀vX>}ژa1YfyC !CfnnErM68tPޛ[{v-~,~3j ^]SZZAFtRa0\nzDDZjUAIH@:&90|;TXGktþ<+VZ;wBVqPvqQ +MC~ĴDxzcr/r Ndl2L2* gcu1m:""""*[÷EyZQX7* 8v HI>ϏܰxbZ "b6ܳPvq.XDDDDT$c/jVmt9 :ZhP眫M>v^䣰$''ƍ9r,%%EoP(8q"nܸ(];wp%L>Hc. L@Ȩ>G+v+8wۮgkW8wrҮjnkkh F1cqa…hР'U[nʦO͛cܹϧM:`ܸqE{2]h1x`dJ&{ [ ߞr]L ]x.,Att4BCCxb&L^]WWWJWfnn]vaZUW"&"""bczj6հrm}ꑑ)b>(fffزeK4% ֮][a """"2v`ujaWեKz*ib""""*tqqu8`bˉTS , Z֢n]~^ M_q/ݰii9pN """"*TK.3PF5ĕ+ի@کvaBDDDDn]8qZU J-& DDDDT(?ŀ #3ͫ4F3qՀP$ScBDDDDo#ɻ' Yq=ХAON6qJ`BDDDDҧa|-$?bocZMģIGFwDDDDot] e2i(\4n 4k1!"""|?_.Գ" m5j:Bz1!"""0wML@Hύg?oB$:>}}â 22oo!oO|yC !!8}N> p=ߝf:4*arwk{gOa{gPTJ& DDDDTf&pp&pX֣b&dL@Jdm'O÷kH,}92& DDDDLt4pŒBfh m:zRL"`L@J e௿/'[]L7=aAFHIΜ⴯APoz#jK1!"""*‰0 503+~H&B B*P"VWh xC#%#vwosCR Q = h_W$CЮHNOmlj JGwjcAm(xݿĴDt[mAY J%>!"""*!>xQ`rkדoSꍭlåR Q b7U@Ӧ hoG9r J5& DDDDܓ'Y<*V4j"DN{UZ`ǻ;`]R)s8yo,WII@2iw^<+{ρ¶C&*9322~z֭[?~#FhժoߞѢE o @L\pݺu7ڶmB;'"""o@ݺ@@΀PN&E94vl]ve(b62 IDATSL<x"6mڄ+W">>`G8y$'OW^ذa WĈ8<<KKK̟?۷ɓ'agg___Z }Ńx7MDDDSt4p<Հ;Pi7qcGM {nTdꐉtJFa2dH7m'''ܽ{7oD\\6mDDWǺupBXZZ<|ӧO;ȑ#ѰaC'bڴi~1NJ1N&6nŭ'PR=M4sJD6~ׯ_Ǐ?@U"66< ueeʔAvw^8tH;2o(_^^=L<6n<jcཨ\%IJ@% 0VLlVVV?ПuQDE CY Z(R h4'ޏGYٳg=G[GGGz=Rhj'""B!(fRRQ8{V9X:x\w{wCyFY)E/Ç9s˗g\^egs{ѹjKLL B!(8}ZIBZY*V&:)V+Zq6,nnW_1JBVM@fΜ_MLIIȱ m~AY׶B!AXdgcgu*>aCZh黧qqeπ=Tvlȣb>| E]LLLwExj[l[ !BB!k9e]ss][گjϑGptdW]xPXg{.K.}b憏e{9t^dzpBgի6mӷohB!(RSs=ΜQ%B'%it _v]1 2T,֭[̛7O?4g;`pܸq$%%{8paÆacc@Z*۷o߶m۰fBQ4ݿ)'g7hlotY݅Wbgn۩[%֭[ܸq#p4iš5kY&իWzxxxPlYݛΝ;3}bɓ'S\9>쳬vZKrIv˗;w.s̡L2j\!BRCrr/TO:= %=7׾+;1ak4po`E$88!Cpi:uD@@֭ݻ4mڔ<]FCݳ[v-&MaÆXZZşaÆڵ &0m4t:-SN|P!Bz p$**(3.4ϔF=ri VVl&.`!YH@ԩÉ'tDEE?333\mܸ1SB!(n܀PL)4Du؛ĂM6W` "!BdiiJʵ e?ss~+ *D!BEIIJ}GjZL:w6C L゙.h!TTЅB! !%Q +W~~1b1aM5t*E ͛7s%Kj~ b!B|z(ʁ'>盗^gĖt'-+k,TJVɉwj*lj'p}/̝;j9^0vXCw2"BnRQ Qs|>N ^_FOϞ Zn„ ̝;nݺűc(Wo/|ׯm۶?>ܹsxzz % B!D>HOStprf͠l翫뙰k:D?~*G, %))͛3dZwݺu7oz1c ڵk I@B!^RL2qRQ*4n ֹ{ӽ!;*F+ ʊ &@Ŋ֭[9u-Zlٲ|'ǫaH"Bzt T>W]h/SL`n۹ }m$>>FC޽֭[ݛ5jɴiŋ4I"BapAIDʖ͕W5||kF6R ڴi]vʕ+9{,4lؐ+WЩS'RSS mD!"Q\=x(ffc|cOg\q*E+ d7{I&DDDj*"T$ B!!!pryJ{wlkLl:QhEA駟>w'd…h4NŒ̠MU_RbfݺuҮ]lOVX(!˔(Q:dD! U=RS<=|kݹu?z ;9mHQ̟?I&Xz*>>>\~0ʗ/OTT۷gСpu>sV^mO$B!xLF288(Klm__~׆^:ީ:̓䣘駟9RbI֯_rPBRRR)XXX0i$̙k, B!D8PZ 3m{Ϡ_~GY_ <m۲][[[a52I@B!B9ۣdo{v\&i4zzdK1њ_BR!XKIӧ!*Jvuڵ}e7]t!5#ջ%P!G!B[QQJ򑒢,Y*V|>_O՝INOӫz33<.D' B!”=n]ϗq!ܞĴDVn˺07y! I@BQ+qq+@V/حc i T `[0-)==dl_t˷\mBQl\(ɇ9ԯG`ڬlC\J~oXYOB3|.\uo899QD :uD\fI@BQ䥦prGɒмRpDՊVM%6lL:5ypp0ϟWm|I@BQܸJx{[Us;65Kdg8Y9B쫯SNܾ}ΝKJXz5|z^%B!D߾\;;+K,-o7h7nPչ*捻M@]6;w777ЫT!ЋV\%%)ժGg@>JeW|FK#N:9׭[We^!BZzRh~萒|؀/TGH+%^aO=+a`̚5 ///,,,pqqcǎ~̀!JLTf=bbrCMͽ{ lIpٗcπ=s(a@IIImۖ`ccGa˖-Ӈ+V6̀!йu SSS֣vO>$> `E]Kw! lڴi8qɓ'sEq͛hZ[Ɨ!BW7o*NN*<0!WL\m\N?zjr<+S K,}=Z%B!D,JLT;^}TZLq)q]ٖ;.ɞ{R5|d211ѣG/ B! 4^9<<\I7>9z(NVN꿋%k3F@zz:OY}vnggB!DÅ JQ,4o^򑘖HU9x %,KNjRg0!stޝ{=zaÆѹsgz2"B Q45//pwWo$^_:ž}wP-9 Bv#GرcTZ;;;͉FףO?Um|& /^DEEajjJRِa!ȀguD eɕzcumWv]م [l^z (i4V\In8qfffԬYwyCbbb' ̞=?kjժE~2dj$B*6NJ\ԌTζmXZkހBYxx8t҅.]||G{j*Q~~~8;;cccF!>>{qE&Oә5k P+,!B@z=\zt`izH"-#^z߱4^ӬB3uZlgΜ1$ .\୷" `Nrr2{e޼y_,\B!D Oý{ʵx{k6mĜ_{JJ-T֖C>͉'xT?߿GFF2n86l7|Ғvڱyfvg}ߡ !{W9=01Q^S?eoj̴flxkm*QwP! ŋiiit]}D/Gvb߾}4o<B!DAիʵ=ԭ (u <gV`1aM5t| Q@,[֮]Wg:u뙻@ qƽTϏ|H!I\ry|rժ[hIb-\P`! ɓ'FѨ6~_=Gll,_|ǏѣOlqYt)ժU{jvYڷoOfhҤ +Vxj3f̠^z4mڔ޽{} !ÁJaa BK>l Р!K o|K(`zAPPiiit?W\I>19 JҒ}2}ti֬gϞΎy~zڵkB;wUV1|̙3hт~z{C\\~&$$ceeŴihڴ)ǎ˖`O!8KIQ ͣkWW0z>s`KSa޽;O=bŊ̙3G ԃXz5ӧO૯"44BBB1c _fMM{ B5x-7%Kd|\r%ݖ-[Xd 3g ?h>S!(΢B(e 7l1ydfŽ X{arvvdɒ,\0ۉ-ԩS^nۛƍ}={6̟?*W^qdkϞ=;vmfߪU+RRR͘1kkk5gp |}}YbG`^B!#ΝG;;h*V4l_v3 DsujժYre~_2j(t:j$ytS˔)S߯>RRR8KsC6oތF3Zjjٺu+111>|*U`j}uiii޽=W} !ѣGJG悀W^Q{{Ł/OekoZeG&--cf+/d,ZO>D 4oޜ7x_S^=|„ Ĩ6~HHkZ퉈 55gϒ#zP#קBQ\+]+˭<=s> i֡YL3 -g0X Du!9_cnnn,YD |lْh{=6mڔG}˗i߾jg.^OLLsDGGO!HM圏%ysо;>g!DUV-ʔ)n7::[n=uc`]4 #F`Ĉ9}WcY(RvyS!(߇S 9Y)4^]YvQOBFm䦓zKI(܈αծ^'--M6~π,X _d׮]җ3g$2 +QsO!( , V[[J|,9a y ܤI޽;[l!)) .zj4iš5kf̙3 *Ucȑ/GTT~i%3^^^=zgȣGUVV; G;P"F&}>MPPAAA]x1oL!0D fMzdZyf%7 `t iBVժUcܹ3cǎQfͬiׯܹs[j H֭jу .݌ -[Fnݘ:ujѱcGz=Ǐv?3;tek9yizŦM?Ba@7n(g{<|ffkP5g0k1lI>x5ksN<޸qׯe˖$''s MB!4mKKŴ1l#}6A1g0߷^!u(QzpA޽K5077W-Պ|M7o_| xS1ԫW˗?wYnݺ7PB}VҥKg׮]peΝ˜9svݻ7֭c4oSSS&OLr^O!(V\%%)UBƩ{X߃ }Bl)DM`` 6|fڵkzj:uD%o¬Y>}:۷oȑ#\tXlll(U޴nݚʕ+8 2ӧOЩS'Xn]V k.&Lit,ZN:oڵL4 biig)BVz=\.)Qf=ʶmt[׍t]:<{bI>x6m2ewqaРAԯ_:uAᵰstYԩÉ'ۮq8pr=pnB! De#srC`v]E]HHoF &Z#Qk IDAT9993زe +VZjȯ BDn)11JQԮmc}tLJF W}709(! 0///N<7!!!ԫW/2GJ*Z<!"tP`NNʉe;28x Vu )=UڳĽFF;v'ĉi֬W\1x<!"KL2q?#7v$%ЪR+6 S c%Dajjʴi믿x{{pB! B!"++%xUr)N0mV!>5[k_45޿Bb 4 $${DNv$B!$8|.\Pe%WNNƎLr7V+Zoy_6ڄYHIIqʊym6ʔ)b@sA۷?f]oذHC Bܹ,z@).Q HYŹs :) { KB%,,'>kժgϞW^ЧOzA\\j$aÆoߞ3fdoҤ o1DB!  'Kfؑ lV5يy!-[dƍd~g֮]֭:_O ٳnر?7x/f/]4K.|hZƌcpBb-6N_Vzh Iҵw;%,|TJJ ޽{$%%vݺuiӦ 4[Aٺu+Ga8;;g{Vtiʖ-˜9s BQlp2|XZBFPZJ>ytR'R"D!dffF2eL2l͛YrjdFԨQP!~\bb"!]r2> )nnP7{@nݠK5vۅիj}$)UIIIXYYH4L BQDF*Gj*'/orw \}x*NUK 2лwoΟ?@rr2GG|0uTC"B ǎ)ɇRh^TrĞ{ps3vXB닕...ۗ7oTƏOiҤ *G̀4hЀ#Gop%llleew`PBb!.!Gt/-[rE;gO=j {.Vb߾}8qWקt::w9GA2dڴi155W|ch '$$/AŊy hB!DAp>>r 6lΜ9þ}|y// Ywydl;ai4z}yB|:+ʵԩT)_՞㷏l^h4mBBBpwYy`hhhٳg> 5$7nmڴ! qW\/0D(B!Dry\r+PnA@ǠqGKGv߅(6mD׮]}j{;.dz{ywڴi >VZ۷6lX $ KfƍlnPB"58wNjjVj> $^_:žvPtmc%Q$''Ě5k.%%6e~XXXi:uw=Çg^$iڴsL4![j*ݻuɒJaiiܸ'9=7ּwckn˶>xkKOo.31119e⒧4iBFF&Ϙ.mժU {dɒ>tt!o|hP&4hPԌT۱6fK-4*a RV\IΝe˖ѦMKYx"##C3wܧ>OHH`ԨQy3/ 2bggǢEXbE^۷0D(B!DAx8dV)4'-#{f,M-k3M+<e]߾}|5$Bō7;eooZ̴fl챑F3|&MǏنXz*qu(_<}SL!00t>C7o￟XF[oTI@ZnD|׆E!(}[vv2n\xwYyf%ZSu_G*FO?1rHկ_rPBRRRznffΝ;;v,???-[ݱMLLرcG-$ 5">>gBQEG+K@Ure^RL4&r,Dq2x`۶m{}{{{/^/du /_Nxx84lؐ>}_Av>|3wz*3f0D(B!D+)ɇ54iUcQ#Z7VB<ĉiРc׮]lܸꫯh"U6 #III|N!(TYm˕S _`ez>! t^BoKK,a̙ҿj׮}̙3|W(/חsStDDDpC"B(n)JQ-kF3i$9 ?v9*!|wL>?83OOOZn wҥK 6yݿX!(ӕnTBN )0㠲z^y BHNN~bŅd7H2` yfD"B_L*1QR^}zz*᪍o"t\ءCD"B^.Jae+;]㫃_1yr_2#G$ OO'`իy檍3 .o~˪F*[r =zPBȀguD SllX[: M&0o#B[[[̙̙3x"TV TR9븺Rz'SbEE!0X8yʕZA/Ȼ ؆cAB`ҤIL>,,,2h<3x^)|_~j"B^/_)ɇ%4jի#(4{=zޯ>ZϒCB"00TcEmx+VHƍ?~_|9Æ #%%۳zjU'TW"*D"B'.'T 6-;麦+i4.1ʒj! ?z=ƍw!%%QFi&W12޽{mNhh(j"Bᅰ,,J4vTc{鼺3))tօV Bpttq/..=zcLLL:tAQ_Z!0#*JvuU _`@_t Hrz2t`M5څOt%:wLxx8[ΠUI@\]]i۶mڦƩSE!7QQJ򑒢l[򊱣?GnILKGkֿsB";vЫW/bbbZjAZj,]4322R!/M0rESN47n\]ٖx_biji찄hΜ9?t6m/^pAD"~<Ą1cƨJׯ_o߾4m???4iŠ+ry&ݻwחF=ŋӠAر#.]R#!0GBbEм(%ĦҴ|S6܄Bt ĸqHOOg޽UGׯQFIvnݢnݺ=+WFFv'Oj״iS{=ƏORR5|:u*-"88ggg~g|}}9z(+VT3 !Pϵkpծ9Ԯ|%g@Lr G?11vXB|ԢE :^} 8C=mzz:ΝSu(UW2b&MD2e,XDzٓ9sd;v,}VVV?Gt___BCC2e .>}_sNC~$ E/s@}[V 5"zETB-[rE*8T`π=/^tt4Æ #$$ءd1COҲ2Odd$˗Ϻ`.\ԩS1b^^^Yx?22GGGʖ-N "(((۽/GB z=\.)VN4w,@03`?E!D0vXV\oٝ`3 ͣL2ԫW/ޒ%K߿?׮]3H {:=Vȑ#ILL… t^dz޽{mF m۶%**7ofkFCOW^lڴ)O߾} !&1Tݕ%WE9NՊVFfƞ{Xa ! h|7پg=ze Ȃ 9r$wEg:t(}VZB ~FFZwww`ǎl۶ V˸q}l߾=[;vйsgUOB{z=DFBL *>>ߋXڬl黧)eS=PY$Dqѣݛ8qskݺZ!&5kG\\*TuX[[pǣj5jTVV_|1cpuu)!!ŋX9'N'&M0b曬s璐9sTj4kEPKHMêyGKGvۅg$ !'˃Fr]*UzrTP-&$ Ǘ`ܻwr"Uƍۮz9a= !x Ez IQO<ԉ`~;.]0Nɓ'YhX)=='n_ xb md֭F!"Y-uCdX{܎}SL]c%(=ztV-H-ػwSRjU1H Ș1c8z(',,=zPZ5:vHLL o|!BBQtpRag;*JHn츼3BKQ@uSAf@4 ?#d\x+G.]h۶!BQ=z,e;GHiiX߃?.{oƷB`s 0@ cggG&MhҤ!BQDef.yRS,-xqB.>_0`SMU3vXBBbÆ ,[piذ!vڪkbŊ,\0tOFFWf֭>|˗/"!͛p㆒h89ArPr Z-dd(֪<+2t uίĜ_zB@c%(222۷/k׮ͶATpp0 .?j$&&=z;ѣ&&&Ӈ@LLL BB_&) ')'5 ._?JBCvkFٕq2+hLI(QmmS..3kN4rDB=ڵ+=y =BLxa,ʬxGJy\T ; ".[Kᒖfھi~[4J3S˽g枹"⾂!;sޯ/>wW`s*qqOa5jV_xvhh ~x0#&K!:@rrşc䩧wޱƣBXNf,nzvπd'-lm-wͲk/=7Cb`00{lj֬y5|z>ѣ9S2uTOn>7sLJ.]t~$ UVw߽~!(ǎu_jG+AN2k9տ:4y1Lw]C+ aǢy뭷8q=ݛ̏?H͚5_ 2xW?ԩvbРA2n8|}}6l=n~Yܹs駟1t!OϫeSNNjiձM킕|WWɤǫ~=d OeʢgHOf?>oBY!!!3{wڴi{ |}߾}`gϞ\ .J@Μ9Cll,*TF(Ba4 Ξ?P3y%ׯjCTWɕ+QUK}m}imjdܸq 2ɓ';w&22wy%KX#!( (ԭf7.\PEGZ*U,CP˱ΜQʿRtQSZI;~8m!!5 .%mӇ#G:gggZju43%ŋ5kVA'$ |7^Z; !?GGhHJ@\R+b},~䈚^VY9N]?E[s92uamxz:,!D!)?{zz56oL (Q={q;U k׮qQk"vC*h7V5Wa\ TbQw~8xP%'*Yww9ZiM\R|kz|}l… 9Χ@`` iii9r{E>|Yf1sz~۶mڋdaRJ};v]vY#!ˣԬQծ ZA:|~թ<ƣlYԯR3(ҨboVO߬c^}U[}կ_ZjY#!KիÞ=}}WPj:h2n"i3 'WU6<?OٗXQ8޽#Gpȑێ)`Zl2.^޽{IMM%$$Pk vI%NApp뚦f}Şo[zDD J~!wjA6m5U}{p3&`Aʕ(džg7Xλ> !DaCαc(Y$5A<{Ǔٳg=zt{g믒!=g{ܤ$XvLGŊ2nq~G^ڋ/mFK!mŊ 8gtNGݺu9sEwX2p@Fĉt|ᇷё?R!Dzw= /ãY ''$&93N :#vJ!Dѱm6z)܈^zx{{ơCڵ+֭f͚yRJ1~xBBBYpa(S E!T8~\5q:ܦxpYtY؅źSB{Gc?}=z4Ϸ-矧tTRҏBq,DGT)PAugO*tXnٓ5}^!a !=;qǏ-<|aÆhd77nK,ՕJ*1qDʕ+gw"(NV˫4M~ԯ] 2]ڗ%1Kpvpfiϥ hc밄f  tx5k>j^$ y&ܸqkb_2c ͛gnСCjՊiӦѣG ֭[98yf3f ͚5c׮]y* e4uTul(K,äx~,:'Kz,!"0a !D gرݻwJHH:tɢ϶JR|y::<*E}:ub,]~> fΜ#~5Ba„ L>E|lذ]v/]ڵ###|nܸqӼ?]\\hڴ)sεxrlQɇj.hZ@/W~uzt[O:,!*Hn,V bʕ<|9֙URSҶm[|O>kU+V qN:zVZĉq۷o'44t ~?gB/'OuAu4_Ouhƛkdڞi19a밄⁴k׎/g:xMӈj}qԩÞ={8|0111L& CD 'O&--Fosڵ+#GՕPbzJ,ɓ'\@mmiQQQ!`eu\ ~<=MxwL9]gһ((.]D||<|ÃK>Ngd 50uTq-yҥ8::رcqvvfܸqٳUVMU4Ibbbۍ~ߍHNݻտz=Ԯ +:*__0t { !Dطom/RV-}ȿ>}:GAAZr-?NZZy:%&&ҧO7ɩS^:/Yn˗/'## ײ*9{Bj`PK4@ϺFo'[>૎_1A6H! s۷\ry&* NϞ=IKKKoY{n]eʔuW^t}||8y$7n5.)) =K!@{;'NcxaUt.gq|&+ _qDBaݝΟ?og[%裏hܸ1;w$$$uL&| sεh KTߑ D4Ҩ]6;wի9ƤL:u]6@qy… YpasǏ7%(`>@@KM>xm#o8"!(Ν;Lj#x-VI@RRRصk:,{9g X ...s233sLE9+88U2c vMc-Mj/ *FTTYYY9LvnOdd$9}'{7+(nR`ݺ5o}[~mBXyuNaaFٕNNNTX]vѰaC}Q^|E/3p@.]JϞ=ۇ.]5jfFizjsԏۗ͛7ӦM@ͰlٲSD !D:=JuT绽Wlk.B_}$$$niϟܹs* H2enݺ<쳼nݚ ___Ytlgҥ 6f͚Ǒ#G5gϦu֬_mr)L¤I3^^_OiѢ9J*Z!M#G ǜĩgPFmѭ&ֆ BZ2dH1rKJgѦM~gxyi߾)aoXx1| xyyԩy\ƍY~=.cƌd21c t5g}7n+kfƍ/B w/dA yoޡy X>gm?CQժU7FrppVI@UFLL Ǐ'$$SB֬YC= Ό5QFqܣ>ʖ-[zNNN|y쁴4ptzB[Ge~~1&v(ɇn-[5j|b\S9?8?BB9d%TG}K,YJ%0i& ǾCa76o|O5MܹsE+iݺ5zH `G!Dd2Ð]WzXq|=Ĩy|;:Xa?gў" ƍ;w.{ !D=_W5jcm9n?w#˔Edh$ΒCaw O?Dǎ ӧY`bHRr_|O?iڴ)VBk~]{ddَp%q 2t Ɯ'wuXBau lٲ*ɒ* IDATC,+W.СC2d_>G>!(ΞUˮ4My4h>ml:. nHk,GJP`ҥF\tvg $$Ν;-BXbsM??hT[_tZЉ4C;? B{eVН֜ 4Z1!ii{7$&`^Qٷ]q8#)Y) hǯ=a !M3l09svi={XLI@?4-7n:o49pіE! ի<3UW{$ ؾK02nѲjK= WGW[%6믓ij>Kω=Ytr$ 111m6yMF#AAAE!XJb-U U:*q1Lc&Wx}VSҥBB')) OOOqR|=bXd'|;88K/Y"!x W-*pqGRR}Mg7%ӘIruYw-^^M! ܓ_iڴ=;X !?'NcWkԬT6_B1|Gy/@dh$#mBNgʕ. 8pbHg&!,Uq:RE-[dXMjV*Lm@5jl>_c~eE6P! ttb0$BIN]T݇^ +WuTNG:}qrpbe6R! ?OOO"$$c gΜ!!!bH"tI| jkK .?BeڴiL<~T-,(B=MGNW@f|X[jV*O$`ro}xyUcfי{χ=+ z!OOO ٳg۷/ocѢEL\ѣDaײ`NUp9U|# !D9991`bbb}}I>Z\ZVqq9Yq\ի*#ٺl>8pqƱm6F#Ǐĉ,[f%B؍ ػ]SAAP,*0HI<†6oرcYv-@Ϟ=>|8uֵs%B؅7anHOGG mU&BN+V`ررMpuu_~ :?_!Dw>>ԨQ۷iZF#gΜ՘ I"(VΜh4(Yׇ%lUnHg:6G1/B2ޜ:u˭+==ի[,iD((Fؿ_|hj*ش$)ݐW;꼶5.%_rL<`Ã$B0 ̞=5kr|ߗyGxG1bYYYwN>X"3 B"/-Mmj^t..jUҶJ73sLnK\RsC! wu$Bz'NctiUlj۸Q  H!EIFF? \\mI@ZV8/Uղ+7 /'s%KVTx!D7r]KJJLQ!D=RRTQ*8&Ðٛg #x<$Bq͛Ǯ]r$22@Svm4MիǣUV>$BJ.zQm[jWܛ37a^N\?H!O>9i߾=NNN޽;׵cǎѨQ#JHI@ѣp:.SF{8K{0j!aTkMRYur^^!e24-׵eU>>>׏s璚;III :JQI@FfjT6('(2ھkmB .N-pUT1OMM% ^ٳgqg};v,G&%%ÇӫW/q!eBg0n-*ppPjIq?6D[s- &\e0!}Q~}~tBX"899{{{uVbbbxGi۶-]veΜ9~ Bf@6<=ݡA(YҶqUytC:?<]r/ٳgm;;;9'BNvkL>U8Gf2dÆ #--Gy/_'0c ۇϧiӦܹ3 a!6V-v h֑M?ǘ-cd8T !(v$::3g#oUvsscĈL0[EEE@޽^»t 6m+WTWh\}1i&^[9f,cێC!Dg HRR/s?N>ŋi۶m_Ǐ7/0Lt!kmۖ 6pA˾!,8pQ}>J! @ؽ_ѡccStB!.`⋌5 \VXN#444yooo*W̺u0Lt:VZEɒ%sNXXj*֭k"Davii* R3 PA!%,; ?>#uXB!D]2ydiѢE׳g-*V뚷7ϟԩSr5BBBjdcIuaaPt_rf2O,z? t.5:,!U}vvͼyn;&!!O܍]~ܝn㄰7nY[qʪѮ~i7xlcA ,\NjmBq#իWYteddǧ% iii'48sbb 3ԭ :re:Cxz*Uld밄Bb7 G}ġC_~ϟ'++`6lhƍ^#)) 2eʐqB؃4Uh~:.WN%?s7vn[N^?Iy뻎вwQ!(&III!!!꿎?N h׮-j'L&nnn*d p\1QBTjٕxpǮv\u*uXB!mxgϞhռysThdÆ tݻw$$$N\Blll111t::utx"##Y|y>}B`^Ua07h!GAi?f7­ ,SoC!D`7 H~ծ]m۲vW^^6{뭷XfMk׮k׮Y>`!l !6n5kB&P#+F[@xp6LŒwB!"I@ߒ̙33֨Qx 3kҤ &L0}L2&Md؅$; =<23AWEժWh2_79b25zQ !! ղ+%!,L+ V1>#uuXB!H"~]@QC̀JjN,zzO?e밄BD;e2j]MS5aacȊD:/[qwrgiϥa !V' v()Iz$&J 4'E$$a^_O)R콒G+=j밄BBM3g &F̀8;*:+V,գZꕯg밄BD;f=^UeBݺOvN[%RJ뻎ej:,!¦$ATde@*xvħT:ϮrʶK!9I@(ƲT <Jm\ݎ<61nߠN:rlBQ(H"D1W:n>鳆uXB!D% Й3p䈚qrR}=l9s my:jֲuXB!D& Hz:8 W-rum\HmŤxzyWuXB!D' ŋGV*.U ]sq"v]G K!($ƲaUǥJu=>K!(6$†]S릥FAAK׵'VniY%˟YB!I@ Sq޶˞t',dC]psruXB!D# Vv^7)IW=w9{n/c0 '~A;B!,A>a%O \\n](W֑OvLR%>n]zxq;X!B!>I"u]S˫ٶqWF0h\ヌޑ aWGY!B!~IvlڤGGx4h ɇ$g&Sͻoc~|ݧ%B!@f@L%u\*4wwm\ŝ!!t:Li{FjmB!DrE-P֨{a鉔t)NCӡi a`ٸ:hGmBad h(عS%Ь!ɇ|SboXB}*x&f4&q !3gΤQFlْΝ;sĉ|;k,z}X0Zː! ͛j{ݔu5kld1LZ؊cWѻvێk_=))L9~o _L N#z>f̘}a4mڔ;wRj;ޫiSLf͚s:7|Q<+2qغU%и1HaIY,> 7or=:wNB\g; ~F@ &$B!***??z//]_x19rMhhC/pX۶cχ?l KL{D L1ovC|r|q wu^EB!_|dC9ηmۖ 6p;?n8:vX,K"}:{Vm{&89AxraTjiO?ŗ}ɴ= FiNd ݐ'2e,~!G4VZEɒ%q-,,|vVZiժ#))a[$ BܣtUd˔-쇰(63L1[80ٴގq[ILODa41i&'SDY\lVBؑX]FŊs]ۉW^ԪUxƌCXXǏX̖$ %5q ΣqcpsudݼUʺWN]?[kbYxx24؈QGoHLA^'&!WNjpBaOu-o{7of߾}4nܘӧOӥK233-.XBCVDG%KVyj>1:M^z,:a__ҧN(VuQWFnɞ{f=20:?F!{c|Vݺuٰaڵ㯿b߿b.]S.^AA$ֱ zAτ>3ϐ÷/ow\aԦQ,Yˆf#$Baٻ^ݸq#׵Z2e\]]>}::{LVdw3 [lc޽(QƍkM^ll,o&.]h4ҽ{wz<_s̙sї IDAT|wĉ s(:L&ɓ tieO.&]P!ڪz4~~҂C0|8;839fb0prpݖoE!D13o&Me˖ԫWH% CܶQ9u\v<ʈ!+ ձ*4/!V7k,%cj4zggs2b0p;g?]}>NS)R 7'i">|8ÇիW8vwwgɒ%˪f ,_nݺѴiS bظQ%:Ԭ MHa+Ⱡ2fwĤp`SM(iC vC!(:IOOg…|smS eupHOhT<'n;Vsaycef5VB!xv>`w!bRSS5N.7ouqj>4 \15a`gNzUӌi=gpB6СC"ڵkO?ݶQ7Lj(pB.\筈|4P1+ԫLr-ERk τ>C5jy&!&DJV ə8 NB!=%XK.\ri&׵Ν;iwq>#""Ngn(sbccsOCH/_O>E))m=??hB¦chG\M^ǤyJ W!_~GGG:vƍ;w.kצm۶]6իWyPFiܸ pdcE|Sn4' ];Լ+B!J@N /a&885kRBڴiC-oIIIa̙kԨQ{_/ eed]p*:QM]Y%|G{3i$.˱HLOfMv/i]5| !>M ϫ S$o 647 `̙֭3s=4uJ<23AWEժWiKA赨\ۨ!*yI*zIs=Cԟe3qaFFkcDFLZ!+t =՞WᡰkQ>Px%Vhz x{۶^M!Fp|%9ODDFs9h'cf";!N"DG.=]e\(]VEf&pzJpaI܃݉x#,k)83c,(@=u *JPQC*@evlJK;{6һd- Ats@ztZ{=r.`wnI܃)G1h|DTpf1V'bam.AE)P k6B;ۨ14H|YGd$ruV;YVdiז)}|F-   *zJ^^W}d9g荷vHuP8`X09ә{9c-tyͰ PAT #r26gچk䋠9Apb7僊j{+TT쭨毂mP?ߦϚ7@XddH \['q&EFQY~'NarQMcZ#׵(\ ]=4iwPl@qk(nNP(p u=(+B+ph`䠢z0TXTBg6bBzqDz=^[jhk^[cg]T.6cbL1 amc̺(9_Xj65%F DޑNNP*IGC < ~!hnw/N>R;ΝP4D Ad&`>ފ,-PUL栂qjbizUGgD y?20pQr/G{jc,$=_tWyS'J (M,_ e0DYeT!;9C驄:\*Zv ԁTT?E՟jTGUOz08`aqju .VG@Nͷ#5?U8~LB;nCȿ J).>~WW nJmV$J)g)0U (\1BtkRi(B_X!*'l1tŧr=*?ir=c]V+׮I͵ףL_c)%rg7uŘ1a,F kٮ,2|'{U*U0pT@嫂>O(L-n0GtVE(Xf"iŬk_UWUwfs*8`UuiVk^q1y7%r]>R}' " rm)1ZӅPz+N/;rt B@VҤR|7ހpO{\4" W EiVڪJ T*Ac ̂F#(ѧu~VR-ơ+UI7v<*&,^N-lI.!m4A\顔'å ʒpkpCu@{] ..=\p؆(:SMݭ>GRzRPfz.(*o03׮ICL;J[ "j ocwnK=֪,*Q!!C䠣w1fiGqj4 G Esu4PQCTiĮ)p,\Q- RUͪ(=v~猱V@]4ף{=Jt%8x v%®KV)PPy`(ctFF8;Y䫼Tp_ PWar`D|Td2!bR FN^#pkuxQ"rv | c68azUre^$Cgԙ9!%~.~xcjT1)tŧr{Vrt.]?ŹI:2f^?8uQkJG2K1@ZGba &h 1ZР,TT[QBT~*7'l (*$E G"qn"pCɅK輩3(>W ^p w!S.\ycpbgʤ|G>ҿ ] NL? #&:ClX0N [Ƙ]ڞUD* w[˭gPae;yϊjDmnu6YZ8wv[ Rp%m0=ޣo-c8#\Nرa{=Lw&~@Z=zJc10h 32=@c̨5XfD2`+dӻ *BTڰ=A1X8ee\LC ;vЕC(ӗyNJ' ؎+ۄ1V;1T:#z 7آLcN *6}lT0XkH+WףS' < ;vbץ]+/vN 'Tsinj:T*FQ +c  @t!:pAV@qPc ^>}]k+$G@7qrv1xvEW k0 qlH ATz^\DgJw% _w_ǭ Ǧ/<|=ףAcqq eѡUMfɭ8:=<| 0{da(4@_Ȁb& $ltmͶrU?+>]j/a4g$MQkq8iEʀ3g,SQ^] $t^er##j4"\P`sA}!Lߺfw  (plhT@)c DjK;Ks=wE^&C4r 1a1v]B<0(4H04B%EFo]8~!1R-998;{6e[Sm]cH WZ*Iިi?Affmsb64&Aa@᪀Mz-W)tgr?W(#sW@*}71=v sbqF|Gprr+cǎ{=n 30M ߙ{",OS DGAG.<%Ɔe(4Ġf U P(ZM.c1PzϨjd>|* ={ʕ+VlkaÆ ?/RiGvt%zJ7g2L? *i1:|t@˰ Mg/PdZAJ1f{ug48q"j5N< QOb̘18z(ʖuK߲j W_Ň~>+V'zؕ ?$`uiZ,jTPqs&U{P`44!Vj!P%3~ؿ?.]ܖ-[ x=4@魷ނhĘ1ccbb̙3իWϿT$^R59N~g1Yl6<=ίXL+'8 ,KG_Ÿ0BD MR uT9 243k-so",, aaarZ6mн{w]/Bi95mw;#'9Q*wq.Ne/hg}{2=`(6X X#}:ELcU{Ƴg"-- wuE^>}SNaVH=#77ݺuVW IDAT990 _߯eO@]@Tptt9I=PmQ|]qk#34*OA)@P \vg@ Jў۲Ά&1c=3gAA#Z?;x(knܸQsM9aaf~J; ^_bL 1c2'o߫ǵkTD @CXRJ0U)\˰2c=cco  H.Zw/?Xsw,D@,~_~~> }}H83pgAGg8f|ߏMh-{U1cÇ.XN}~9䄜L@ݭ>wؾ}Y? ؕ rwU?Kc1ƘO? fiSL)S,瞱Gi(z]\qR cǎw}tAVk=q&LGyZd۶m6mѬpX6mb%nK&i&ر ss???oyh۶-z]7c+姟~"Q?6Kܹ3M40aBCU6mb9nK&M,qXKrssʼ$"%&&iiiiP(wީu]l-HĜ9sp k IVL|r ""ڶm˗m[FvYrpp wwwHE(##C.NoQII Ջϟo7m۶ Ծ}{t{mz:uDEޙ3gۛ ""ʢ@Zn]SWQ/-_\N;>>=\#sέ􆡦î]H:pVVVF>>>7^Iemm6{(++  A:6)`0иqh֬YV{k#G }畞c$IPkunlIRR @~Y'H'뤱f୷ނhĘ1ccbbpA9sF5k\6l|It)ׯ!>>111A~߶Ia!fϞ[BV%'']^~!>sXj(h4MUFNVk{czzx7쌡Ci֭[8o$Irr2+\]]yf"''}KK/aҤIoOmI&Gɓ+-o",, aaarZ6mн{w]zA*k7o2`v4@l{n#00,O>r~k^x"=:: عs'A@yyy!88hl63f`ٲe`mrU˸ѿe*k={BV{4j(`ɒ%fk~ӗzy&N8;BTӧt:88o$ɒ%K,ggg61ٹs'233OZͷ67o4 .]Zig"--w $''Nj6ڤgϞQTT$8p1112dy4@l,== 5ul ԩSHTFHJJj 65k֠o߾6l|Sɚ5kPZZ̟?ƍCϞ=dMDQ;-z Z'bڴixꫯ֭[ `ʔ)^Ν` .V^aa!&O,|S˗vZ{6Ν;1tP?iЭ[7{8\sRi&bԨQ瑜;v:/@lfgJljǎ{ĉ߰`J[|P*pvvƊ+{nL:׿0i$QmBDr7zk駟b̙x1`׃S^^Ξ=+uhZ̚5 7nZ=_3nl۶ ۷oDZc0x`yx= ;=1p@,\˗/\ڤ>81F]u䱓/֮ ۷oǺu4{j,]~aMZ-ٳg7 -B޽~رî~WΝ;cXpooQQM߿ӛoUVM6r=ɼy`kNN#"r&ϟ x rsAA^|E&&:ǎobРA~omRW-ĚXf44.Xf$c{jW^ygϞwa N.]`v&S̙38z(|||Xi( xzz6z}NÄ '[n~m^kĚ^N'N|At{iӧOcӦM8tYz~~>ࡇ3DEE^Cqq]XS롺v65HII?W^y"^ڤZt\t :nXT www:ZKFF̙qi#Fڵkq9lٲ4@lLP`رʲ;>> 66Fk:~-0rHǃo߸q;vl+[M6`0X<  ^`ihV4j(:t( <>ռEGGSXXE=F^zzAC aÆΝ;-?~(::J;vAm_||<}Թsg1bEGGӶm,ʵkԯ_?R($"yzz}gQ׃V P~(22fΜI6TUmruBx6͛IE=޽{)""L/RYYE|zǨ4p@Zh6[i05iFC/"u҅L4gAEkN@THc1c1k20c1ƚ  1c&c1cpc1k20c1ƚ  1c&c1cpc1k20c1ƚ  1c&cU";;+V@qQ[W&233l2"55_xg̝;eeeM^ڴi<<׮]ß#F`ĈHIIxyy>?ƍѣѩS' 6 'O'""%4׸qHEZ|9ѪUHڱcYxLyyyDDtQRTf~:ӵk׈"##i׮]#Qaa!uڕVZ%XAEY ÇWyΊEQ$ iZ !QiڵDDA /QYYu҅|Z={x{{#** y|}}K,T5J>g}/}C ĉ?\Ң-Y?h"DEEaʕ8uT ^(((c=4 0O>$_E^^z@{1zh\vM^*"";#GΝ;{U;"}ݺu8<^}U9(8|>yy:1f+0X ( gWWW@^^ :t0{? ؼys_388[n /B/9ʕ+1`܄"yȗ|9m Bnvݢ^5jΞ= @ |h4l޼{ADO+}~m̘1&L+VСC$ÇcŊ?~<`v@Eږ-[зo_|0acҥU믿o&1bfϞPyJ]>%eEcYS~NS3С\\\y/ԩSXj6l؀g} .Wj۶-ϟ?ׯZRFP\\?Cfʫ8;%Kb 'NoΝ;W: (((F{\xVu'|RnJgXx1JJJP\\ ^#$$"ť%$$ǛoYi|1fKcuTf7nܐ 77Z'N WWW,]rNÌ3`,_%jk"??Dd#Re˖ԩS=z4v؁^zaϞ=/)) m.XC&A/ɓ'gƍX*jj|`3g}~1fK0X֭O+--cӐ(SCUA>K.GŹsy?hzX_U' yBVYl`IDATy5_ 9k׮ eeexb ѣwޱZ瓻;SxzzO}PYY9sFIt}s=Gf͢.]~DQ$ZM}HZhij>--|A #GRll,޽۬̊+S_v{SL1{?4d=UMcƌ!7774h߿8@tR~~>EDDsppԳgOٳ{、T*IErqqy峲衇"ٳgSQQ믓H  @F}=̙CLwy'%%%ϭc sc1!c1k20c1ƚ  1c&c1cpc1k20c1ƚ"IENDB`trunk-2018.02b/doc/sphinx/fig/pdf2png000077500000000000000000000001211324306050200172710ustar00rootroot00000000000000#!/bin/sh convert -define pdf:use-cropbox=true -density 150x150 $1 ${1%.pdf}.png trunk-2018.02b/doc/sphinx/fig/predicate-difference.png000066400000000000000000015510431324306050200225600ustar00rootroot00000000000000PNG  IHDR}gusRGBbKGD pHYs  tIME !qtEXtCommentCreated with GIMPW IDATxyeWYo {N5WH@Q@iFB+fIbBhAI1(Sc"&(B"vۂ4CI0$t^C[7z>Ng׭Ob BP( c-( BP(0 BP( E BP( "̅BP( BBP( BsP( BPP( BP(\( BP(a. BP(0 BP( "̅BP( BBP( BsP( BPP( [Sqf<gr' ,c{P( oInϕ҄H[BBP(nw PUV5xC(\(a. B!F[lUw\U={[^=r=FBsP( ߻\ٚt<+˕M)sg1lrW~Hsp;”[P( ?z2-3,bdXcMEk|w\ϡ8g"ͅ탒0 B6sOOk/o)"ͅBBP(nUр] V\aA+R%Lg=c{Z Z.??=;aFTPWmT*PIJ кBGCBIyaqQI s0JT#4 E BP.O}_<~iĮFCv(J@+RTJы8㝧oLJg,v ҪgqxR%auz!=zL9Fk-0*1Rk1Qk+Te.E" TBqBsP(n\[͠ 8h\HKV\Lc$IZ}]rV6dX—*KFA6-!Ͽ;/r`Wa"F1kK\(a. /ząe zat>yav#뎇_v>H m+A2ݬFB/7ʲ{4,TE[ HNi3 [Jsh9~iĕΔcX*k0Fm*Th#zGOyWatHm!Rɓ%} hA*DI+بM~=$~.0 B xs,z.0iB\@_Y!"sg%hLGWLGWO9 s7<7^F 嗣'Hђ=HD%Wd(W7mA+ҳpHWP(\( [,Z{fb='(Ђъ&r h%$>res}fBLDvʨZJfSLNE)0LktFAkMA_m? rTtYiKM'ya0b'@ !]S[, 1gaT MĨo0Q-a`A TNPyo{|3 !Y䔛{őTOMD_+Sv{P݋~E'NL<2=qHsP{VlU( V/'{[+=-T9tYkՂѩ,3{oz SQ$jcD%$[=v<2;O*~x DEz ;-ry?o"M^&8C}loů>zO@ 8H*c$i؎[?Q^Dv^eZ$K,^P(|[3K;};/ 4?dyJ Ө!Fz \Qi&ɦ'4XV^Yp$1`̥cX])32#$m1McD唷M_oCz ;vP/?}A$s5'~o]EBpRψkCp s](@'jY#¬R'7m:YA=09eVH+&uЩϢ*Zz)#|ϖcZ@"8xW!CQ!Ҽ5ine\OTCm{I_$JA]#U UT{{'z{"N^jH v^ ^~{ypdYRKOҩQ~ !νS]IzvyXuN>.$VJP{8fWdP(\( ,3 ӘAn191 ('+=t܈QuG%O.},y(籕il<Ӳ9O66Y[[g<^Aoa҅HUeQ!u Uke񎸰l}I&\/8ZPZ*xH;ߕj%KbB/ }9r ,үa Iܾ>K',ψ0c|C{sb3Y+\(a. ·'ghphݰ4Zc{&%=V VUnb Y1] tPVkfcb.'M$L3}}?;a!0s o4c6X[%[]GHU!K+H?jFE΁wsDO7$UEj)E5R2OrH:?m\W^jtAy`_myCb@jjIg7<#i.0 B)`Ho`qa4 `{f]Y4bTRc5Q !D(QZh#~K/1#rd9+DD#N$ 8s, =IwOjdDž;}zab hTuNkP F| K sIO<7!J[d -HuKp+:yII.?\dД&xz.A^AJ\`r"`0+enw垧?wɛ4 Eo]lݴw |)/ ,ۺOo@8J I{ ֨0wVR X)M#0ZنҚC|'T2Bp0ϿeOY{ԩ$!:9ߑ?8tReA$J".]AA!lmӆ>#wo^}>Z A6~3&?u_* *LW.}]m| ZI?w&>5)QnY4ka*̓1 8υ|&O7Γ ue7/I &`Bh"= :@Or?L[X^1,#SjÅ.6l>|d;&P(gW}n*J~ {ƹqEyDo4?#6*MڙBȃC/KLBVx=!pSnOk;Ȃ ,}&=|YFJDvg9?# sdN/z7/*edBS{:I qmeʤ{%J) )sE&ksɖ3[ *%=%|r:q⑉AZD[O0zuPtp0ᚷAn_(au2_QKPuSO?; 3{#']".L0Ԗnځ!L:F/,%Y( DW<[pc52Ksnj&" IL5 FQJ#V;aϜ,ɡc4C *1JתJ!!WPZtwgT "ā4Rp] x$DE7{x?!۪]]\Z4J͌0t+-O&Bw_y+w><#=38[̅BpK.La`$#Hxģے6]ffo;ecL}4_'x(K:b\f͗w:Hi(U$*A#!{?_9>w4J O௼/rcRT Y\\WC .ok6Y}ZMlehT-v7NVŠƈJ/bLo:<3?CIgc!}ŏ^R\(D$QQnwܾN[4 [ ʯ2ťnF^*<籰Rc{ؕ^lմַVEIOk npudyxxo>BAUb-RYD$!O 49/bH6@tDAq={@TCď=Ud?87b<cn 8tA|sBmaX;V<~u!OcUd+:k$di=G #8_E4Ilu%&O~E HI|\( F~ĝw8)u2k1`&L&ω-\H\qCG|ipyB%0@p+|5ݴ SggU/wl8ǜ0,>(Yz/'yIGBD!^UA(0roqq7(ץ̥4P(**,em-h|8K b Yœ$54IwN{o&OY[Y6 8:Un|T5ȴ1#N)m6==->il{w7e5O svpֹ" JNoҽ<>]S9$Mǐe9X]W> e-_ϯ`Pr#DzۓJ\(aeDQY.\(nl*%!ʢlM.#< \@\s<'lzzCsx ֚.x ϕ`dqV g=b-\yM#rӮ b޿G`U~d.$D%}dEp5;-ht4&rncw}L@x&.0i< (%TVsӎpGNz7B9ρ;o^B<_Y=F9irx}h(y8 /H.,JMB+so, Ny17内N<a*;SaY?"'".|@kL;&6qq;W*j8B^KtYty.m6&m]>6:KcYJr;i^Qȑ:A&d<=b.P-$9Fh&]ҌblW2>2f|dB2 "4x|XV\õx><It"܈z6Oϯ*x@*p1G$%mqH4/(4V*TJ9FP,P(ܒ|cϙ0hkfVJ1]Ii|gޅhS}܈2pL6b@;?GCpRUmViY9?wDQujBFuϴ#w$.";jBCxq`im.҆<2{ZWX0J:aM 4سK3W|/*6MfAp1Ib-?~Wѡ#Wsߥøhjx9]@)J.XU&n\ Eo^QrP(g?ѭl6ZkB!o >wBɵ)U>v$m ]O/pu.]&4vhY:>|ͧzTοǯ 2 kqM<+Jh I s, iBGNu.|!Ö{/HC,>vJ2{vy/D W "Q!Yv()spc]%B5=c6J#\48<5h@tܥ̹]J&]Lw*9)gX%j4B Mch=/9Y܉uR Uש?LGl7J,vH!YujN" >`zGC IDAT„k'y-\(aE;!˅B%Z XUa 4ǐGQe=cx&UYWZocIs61+F'Y6뗕V`l?,b׊0KUܥ˭0h4rQL1!R$66h$̩&#ՌDI ZxHЂ|y7MV GG} ӳڀVTFa)(7~еLf,咏 ^f%Bh ,[6eN P$1xp&= .܏ٱiJ*֕EWB83/\(ayE;-E MN{fQ&Wª+U -;M}'.6g94?^=z={cʵT`"JC@%[*&F΢,Y,j;YyYҌ5IgJ1 JϔdvFSY(Et qV<.;\ܢ5x^X N&eObrE9NIm|DYȢP:iZJ nnp~b{g0I c$ R*r K.,JTq"1Aaa.YbVQH.Lj7(kgUdP(|%˅B{yaI&s)z>l")aJDc%HJg(oEQg2ONrNh3IڄX)s+̉sRҘ%9 . fY^Ne&)ßڱycl9F=9 L VOi,r*Ec!DdMPy!h\Aɟ@t{$\+5^z9Gқ0A]Ua˞UqB[,P`EFzyN+Z4Jrd0[Mh1C1UnE9"cR)iT! q.nr&xaF+G䢪6ug)eV[:d4+HcCB$B!sjG/,`Qg=O]όY5yѿ}=~EϜf1PBXMR|HP:I0cRT<߄> Z J 곳p1ߓh ΡDy#!q.0r- B?+.mf+e- #uC_엿:%VY(%9URҬbuWo3 p}^'C\1&%խXڬ%U$䜨zFG^9]VF#)]2"z<$JƘp^\ڳ}iyH]_i8oL7CoVC$i03, BHObX҇c: }՟`f͍9_>d_ 6i˞=!i.n_}[̅mwN\4EyK[QBEd0oMJ ImtتʩfD9,R|sרpv3NН=!c,9TP4QKe&MuJE1eY *3MD ҵyT-`wf$ bs1Krhn}dq`B$ya؊f'\\g;}!Sp#ʭe ,ͩ-)A:+}{4e'8»W_W=qG.&wq0`O_vaB&"ݻ"ʷ,Js+хB{όfKvf%=՟|L)sf3#]h'luq8\UoGޙ 9efk?s GmLwhT8uqVRLжǟw>I'QpBa4X=Y2+R1M|PS o3 }U]`oOQ|KW@xųi.ɄSnrI ۀ(}bx_٥KfC@bRPbX̧.,OhsGpZg2?4'|C(AOfTف&y|\Ln׮cA￸G$~3ON¬l,/fRBOYUѹc:̲\[˰W3{ z51D,͍4qh}=}sik~,Bq@w1@t7LV#)]>NmNCWM>8pqPdyEmvٿf:_z7ڀx,nrPDZ[c8į0 ky}RX>8-GqHsp#ܦsC߭!Y.̅­[[Ngt9-[Ue)s+zNE"*E*?Q"M0j!G=kY YKoVۮf*Е3ѧ&>s[]?{xYi@D@UU9k7MmW,:b̥73ܖzeȺ̛ P a "XJ,24+F*R:'2'ʊi_a-mQ*F A#R=z 6UJqшAa]%SQNJ#} >mZ)RX9'y?ދ;Qn%yrΤSiIƖpt}{,عĨcثӄM7 α8ko^Ͽ" {w_~U7w@MGKz>nT z3/~X~3Z鴁3F*Sq>JJB@ETz,!Mttzcf𢡊㟭2#ˊ*o0 s6G\ t][RB*7G򭙒2 BY֒MXV}~ݲݪE7eanu0ΌiRmI#iU^blcPpߝdg?`ߧ0*3M1]"B 3,OQUuҬ(ߝvϧ35o3)sXr,˶pm9]N\U}#iPΥ4ZcNGTn͔~5FƤg[c?5%|KU|7>s+ mG` s2F:":1}b{lv%Vz.afLjHE ۦ0R\BpgUd;dRlfRf1LxQrEՕfHP HĈEňeG֜prR`pfhMT*n(MTJ~ų=&$YnSfe theُqd4Z[RKUJc*VHnz~:Y0|vx>z5gq®Mu~!{ to-53鈩%|)abTjUb`{etʢlr )Yv!&aBUju'RP-(Ze̅wse̥ʭ,KNntlicfyH{^=++YkesC獃э8GLpҚ{c*Uus X$26 Js#x N(]dAA|/݋cg!wdI,Ϥ7hDXzbD-I!`$sҜ2Hw9Ow".+< &a'ړ"ͅwIu(-<g+U%FMS $,ݏcWG4HĨ)a ꚥ~A`25)9nK1h$c'UY;s+@ȭ&sSŽ7NT Mh?.'Yn'I]%2++]Ya ,ˠΛmtNVԓ75'&F @ۉq* 1irB,Uxo<J6OmrB/yLϾշM#ydbKgH"?UDB'bDSaqaTg-Ih!͵|LI=&Sz\,\( 1ʺa c9'&Y괎Q!VT%eNfy%yD爮!j^oaZZb2P4Dw ʲNi,Je4mxƓŅNSF*q'R ]MmJsJ(wZV\NjP^uP溠OK&wP]<ܗ_>8avLSkH! 0a(*"X2C`#/241rq WLuOi.w[2 ,}bQB11ؒ0CgD5JZZ(i>#?7bfd9uh1,uK9ect 2/ͺ+ǘeV1N%O+ŒRlֆcp}wdlQ_/]N>˲#6 1R}zή,^iWv,]Q֢"*@l !'Ao_XOjF[qNGm۲v7;|NZE_.xXas .LДR?llD67"'~}D\sYM*UVFga6YMN0܌9Zۃawپ2 J O"ͅ"|[Hsp Wd]/H恖M!&'˺m+),FBjt4HfT; hvڕ,]¼&J!8G9SJ.%˃a}2ea![q;-;vZ6.Ќ0;DQ[J3fYY:YN\wKFekcR8OeCz-Е޹,̎uZByB[Ë3_9uP|io bIdeyc#2 &uiu0Ma VKW<+6 1 (G-l ۷'aLu.# Eo/\J1 ±>QW]nۛ^$P@?yo7}KNΥan݃˃s9F?u11kr)ƴC,[9Nib.y)I],lNv.,"GfceǮyϷߔDy&a:-e1-:n'ֽnQ۩7NKdVH*2Zb7Z7R6IN *˱&x3/!&\5Bm1wbsO|| 2{!Ȯ&\\ vml~ãt]6"OPD!d'l|9Ozɬy66|P!Eo:%e.nFY>uC |3LMMhw[e}4?7ڵeZR߶AXz3n{]œku;c'YѩF.`|CR1ֲ<2pGdwvfYޑSy?7N1"6!3-KZ䶔d`4yޖyzRj^"!FzzTQ-ʩl&Nh2Md8gytܥ̹s Lecǧxoz0o><4.olD^;ySt׊zJrIF[ř\gT&T$Qh1 FZ+c4i[, EE.˅Bfvh6Y)詹~g/~ ;|lhITy}Uن$MBL$$SaV6˲9Zj.3URC6ڡ5ZC[^qT,Uݳ>; .(bp%h@qA 4*⋸"./AQ1wkb]4(.3]uιS3&_$u)~vLv8oٰ!g滯}1He[h E5RQeQUWݤoRaDk @߽4yoccJt᭍{۲d8BCC!61=x8y 5<)lqz|7V˾%i~tw6Ʊ\|p0GQ]6*N*hːɕXˉ*sItc覧LvUyW9u>:`m ;kBU^_V1(SJIJ*= *s+e 5EY10i_]K,"0d]>ȝ=)jUe1ZY>CK, Pqr"(cvq+7ox g~w6lX>SS;.{3kLJŤjyƫ~{[EM)Bsz?*,k7=fȋՈقM$kcP֐auQtT9cv&6xrDH*˕}r>TNKvRTtl Osq+*.-+&5H+!(*J'wgFK< yY&~ht',w[S;h.`^`dch ԡPWGRBF-^ V}R뇘`e5 CC0,. _(l@ ;Fh,ES*yc?Թ?7oY.*˙͔8N1)54vZjt>X[2PV+fA$ToHHz33œLU! 6** F^f;VEᜏʚ8pvZg?H IDAT1J/e_iߓν?p9wArO8JE/y*)2ën[h:20T{R!Z3cՊl ",y((a6,gU02&qI_|[4*m)F#Br%oMζt }奰<=z _mXa Vj DER(&e8;VTSS3#F2>_,RRܛ\DB>"ys}&ϸօ>F+<݋j۟PP2 !'jgNjEo,dXųctmoթ]u_XIeN:B2!UKq0X$1Jm`c[SFaɎ{zY6(ղc%~;fЙFOKkV>pgfjI95pIxmSQe UU &|l\-[p[ui"RmTTJWCܴ•T!"؋~'ӆL!g8x$x%>zTx`l'9O{J'6LPu:KLaQ,&:S*Bs7cw.:u@ ,NCSr6a$4VRQpM|y/-@TNa@y)B_Oz:OqGW\F /36pl4u"f RtXbq1،UT]>+u*sm*j|Suw_k~zT*jSXuyزe oeo1Ú.V㫖2AY=27ukPVV#t ^R.-15yKUhzq޳0Z"$Xn)Xg\ug @#Fk*9-jZU0\Zaמr 0o&:` mꪫ~:#2~&VЬr=KXI?y%o+pR$JʲF$Y2(C';FXy2{[~㣧wy)n,[nrC ᭘ ;9_tx' PLMפf3a/NO}i򴉀'_9>;R` -V[6ok?=Ga[O\2RcYe՚H(VGHkDb'?3`~ >GjW1JpRlghlh8]q>ƻr_]u%_#òV:VlfkQns¢ 2jEXJlvv#9oxЗ]u|[Mթ]uV*MSeŨf (`e=zOO{ '9m3h)Xʰ(,6<2Bs|6jA-|jq1!3٦ G~ݶj2_gR[T[ͻ>v<(y6PΡ梚 YD ^X@/. = 6QQe1 k`ubY6f4uըV/ׯ;ߌ]O4>q~#`*Eqn[W4}駳aXמ(q E߳McϱR5XF%HZ$1tT%6IZBʽ̓kvݍ<9bɕJB|`o`3%זB[ c"x 1YB<}!O?.2k+D^M.yn(חl۟,/uݤ,"ҁp*3Щ]uusܪ+a>޵@T!ר5 syh:Xu8^'.ߔyȋ>",( ;T< {4Em:9?MqYI;fǞ_3.tGOp ;(lܲ~k𹏭z2Mf%,/g~ ;eh+ϛlRUҜڃ{=djv^ ,Ya3Keh[g4#-$nGUe?y8A1q>E!R7A/XAx/8rU[ Y}2VܲJ_  !@6?˞؊Wlu={.(f2}lVrEoA8ܸdN@(dyAVi̷je/[2 i`^ O hF*?> |sƅ3k,>3&*̹dfv>x om4Bq l9n(?g`cXAp^ȬSW~jJW0 ;XTnGd39oYL"APf{\$?ge_NjiAˬw (hZE!FA'`aB A [r]Ŏv)X˴` rX"!> -FeXcF)t̴ez*Yek>IbW {&y8WGp#l( l^)=2 &NVrcͰqe-6}ǰ421q]bKB/ut̅:CXi˨K_,:8@&lE\:6KX N[f3f3ffV1 E@^.%nF+V0V=ka it̩ϴLV23:>'1IyݒޫhγɅ'|X$mt E1Mf- Wa .lVL~ݭ<;K.O^bv>n{fJ+Nne<( "bZ~c'@LDwm;PnK_ߧO6[DXYtc4vɑКJX׿@DǥSHPc3qls̠J ~UyGEZ܌Vg5 " `8lO0UI>?OU9;wª8Lef*k`yv:cf&_o‚ꅃ6 IfA@MA_jYkzWo nKj ֦o, ^kIJNkW$0SK}a\5ЬƇ.'P:MWTqԵ wfv`ȳ'ӏ>͵wyt¨ϹJM:ϙ-Rj`~{5!/=\umn2%?R3^{ԮL:`)mSlЧ⋿)zEe 5gӶ6b//_vŭEͱHL@m.Tj@1'Y=#>?qG'y98'-,iy ; ;8&J[X`tÍl7<fsfvٴY֦LI1(GhEmo7C3SLM35ݣȳ8oL~Ҩr'lnJ^֘ϴD Z>@ib4EDXqc3+(K?AsW)̷Tn|Sve LN>d٤C'%?@g9P* /૝ʖUE抰/_Ao|>Ͼ|AOTXq#%RDUUܗ%jӞh.~Iٜ٨.d[ՠ2UbX6qDum "@H{-M`iɱyku%?;~L z}2^ܢ|67<0Q.O`+ 'aY`$/s!U|+Tbq3oc/>JP:q {|?+BuTfUQ%l_l[\|7~r)h)M%B0as\Kf>{nTqoǽ:`殺d A<7O5wYC`9Bs),V} OvAMTnQYXRy`NA"0H>~|{}4{ 7mbMgfLTZCfa9rUrQR6>]{R(U:6vcn{>|ۆfmM.< cYB]0k lZr Cvr `zALӳ,o=C/bwSC/c"wxu4ˣŒsv;9"of #0He"̾bhv#~e ,Y^%h9{QGs"<~am+o>sEH<a[Wx)PuO>4w'[% Fi S xi::N0}Y `ߊ _ߜ8J@ВRRm (o;'?<?~oF~;=?7M}RFX~3 ,m|02tF", #,Ʋ<N6MgN[D j +a95*Ii":E1;1y69J^) dz3Ii&aiXZ,Y\qkCe: |7O  c2X^.DMPkQ}.lbSw3aC^Lߐ eK=piiB Ӛ3޹ċ]4y̕4bQ͹!+U].f?UXV@nr ]ĽI{nt'|!r4*oB&Q@a`UWOjJa4#Xk`?Iqs4]cH?nk*fB(`2k>ӺB[,QtxrCFe 6m^Msܷ1u·lq5(N#DYK=kIC_WdٜMW=9gN"A*`Ӏ6;V̩SKK#^o45ew;(E.q5|w&GtKURWjnZW >xD¾:IyEee9N2* "LJ@ߚ UiՆQI "2|C.v?ꀹꏭDY[lrtQQ} x|8Ynĉ/{NgX3P ֥2W)[V# /L1ϪQXXyAXZt eC?Vb(l]wӺ`3o̙>ʆd zj5EB($;Ff9ڪabE]0@Y/?Pٌ69_zwˢ%C+VAtLXMQ^ jrXl˯uyǷ/;Oϣl2NI`fl+D J$G~4E,>Kc]^ދsZB`7l CM `~U̓ i2h!>CBa&*&S,plݘEF`|fӱٯl )rMWɻl4e m/ r>m%CiSND#F+M"y.zyi`9 4<(2{*sU:S@入ʳT:^ãQ٪}eX PE?{ލћ1 2ctRI8&O8uOBAE'l]L02׿Ei` (`WpsnJQJ5q|*YXaMFsW0wAsW]ݪz/uus#(&bb^2 "<.$+dOH Ǜ)Aٴb &MxApBgrg8tIͩ2 Ӯa$e <+)zFh+@yXukI4ߣhO߇^B,)} S\ P6J#Ea( ݲTcYan(S PV_i*U9CR]pT)Ŭw=7 |ާ譵 (7Z@EKF #ctQ܄,>@) F5,_A˦,1NuJH>2m D7' uƌ5r2z29qbC=jVU}\c,/%-RAbtpn0녯οRyJD;~~`HQEUVUa &%pUŒNj > ZKLrF3כjܘd :?6G= ,' LtNyy*k,2QylhqXZc%~?|! \W]2l۶:`S\ oXmFOr6G`4ۘ.ЊBF%^gy?ύ͜0kg Ǡ@sPBv ^Xxg&` {W8O3>`nVkPV:|:):Jٛe$:Yn{gG_g(Ԅx;WZ<#Ӿ(MFgVS 44(OBs-M (ZD|$0ʢSܚV&)2w5񺋿!yTQV gtM(/Wns?̒KuRBfS\~<0/y߼Ө++(x;ņqR(K2KJ(#hD*Iuy`Y߂:[~6O_x @F\{1U]uUWffƊ1d&̄l(2TDT dP/}\ZYil_'Uy D/2²k= ?~sBt\&%A|_=<ṟf0}V6 Wu,7Få=sKs# 11>U#jGލa) KVkL(} {Xc>&ִ7/GG\ fQ,wsW]uխ\0?}@2+ mqѕ\vq>[7J5eusǰY0;W|E|;:`cy6ڎa0߫rbV]%O9e=\MX[m! * ^a*O~֐[`2=;}r~7} 4)Q\7Y kP2EeUhp.58- ZGZOY8;wr(VxǭPU$8Z͛曆s0LLLyJ'`68D/K[\i4)C:'jlR{i"B_9Fz=ݽ% [ sރs=qYz3!8TpwIS[.<7sz|?Ԧ*j~s8$Y "T!vncc$Xl"|E++q9G9O8E3:h:n50|,[mœFE&YNOX:|1VdEF! `gǸ8m UAgT]vk hwac.f_mn?j=g<x pA<ڞ 9Bv14Kh5ֆ@KEEqMcˀag̏4E_M`LT]U/q}\w_+ |漟-97[{|$_8By 2izdZ,?f# B $er{sm]uU]uG+"B3юLa6`d cX KGP%m #,Kڳ-aٌ:S+fC\kkfPJqO<̟E^}<}_;%( y2/S'R3|Xqv:$+E9*$x>}/խC(A 2&x[,~TiUMRMK]n)˵%Cb4 / X2Ӎe 1Q%DSps0?i/(@f5SӚ 6~bc95ޑ_ApHPJ}| nTLg}ݿXi_ T񣫸1BDÿs]]=hLjJ:yOȹꪫnD6  mbl^KO~xTwתMls;e \cjjKC5uѭ\{_ F~兢)z=( EV`; a4Ea y'&A6 :8%R\eVx^v'y6b!Ocӹ^ /{eRѓIFJxTz܅ 4/RU<85luQjʚXBE+ls)/Z10~\uc$#آTZ%hp-̥(Q]j 5Ɛ YM|*r$hqpKEu78֗Kf2dW=/}mCv|'P*sI{.ޭ9FkA>@+;PQyW>Q/njȨ\Wc78sjwy:`S檙 lRkKDqk`U9#+,6m2Ш{S;hMKƑؓnz_LY:kPV t՛8ELǭmHA9ޟfʬZAR3W/O6i N|QU_)nmPn9HN|TP lK\bִ 3:MGk-^W,r.fX`9k1b\KenY2Tdq vS>A9Mq&tJPq?ѿ:(Q%yM*~4Ҽ0 0-v4Yoر5Io:(C;XRmp0bN;Q%̋vE) 0@yz ʻƻ\Лxʱ2*_ޥ&Jڹ\m>]๫꠹nAVcT1Fan[fty/p\˹m \*? ̎sZdWOjmLM!";WXEؕ=ԚMNA9[fϨfE SVJ-UY@ <5mg Um*AY4@s;=߼$BVēnw. {BEz'v̫@s.Ч 3t>8+#x!`YȧA:5FanCZ^f_>~gЛ?K`XNWEW(#h-!vA-9i¢baKƌY>o>f헛(lΦMs?H#_2tCyO9Ck ustS T 9tJ:h殺ꪫՀ95\ń~er{Y;bX*g 4['Z8#({ .Þ'nH;KHb57 Tj17}ꅬߨYV33; &yMV-XNS7VAkc܄ c@xpJNV i1ꅆ(AiCN>SdfUen~ԙZEYUwה=O sv].6)ī-54_%tU]u*sW]-uyCRBզ%zbv Sss3 s ʺN40Q Gr<;6/0(H[ G;ߠYV1V1FSLM԰๢Qk.adxWҿ\;I N?+Q=6*/ϧ0ƦCmۃ䂳NP6 \0]eTͩQRʍaG(`m/'6f1l2z+a ˤEInr_TS [F@H R_iM?oIy<~98mf_!|DS0Q d=K_[]f7af}TiA8E wֆ  5PM͟\* ~F:h殺ꪫ <AA(]I~캨,O͍Za6e3|Zm1z7@1g" J7 cDX6*k s䱺lZMZъr$V:>}mJ29T#;/3dӚl`J*fH> 1sa}/됻/^&2ɢ&ϒ?̡4ǿ7_cm@7_V;eTPaY'97)TvB먺=̡k(4rz?X]R-gJ~ͥ6SdY\)ۋӨB(Bh%ftGIH蒼 q+(h.'d_=0Cs_aae~(w~ 2'79_62( aI#zCR(K $DsܵOsY(ԯ'Vvs~\q}q7뵽˝>@e;6<7 .6ܕ C=ZsiH 4uUgY ȬWLѷÎ9Ǽfض*פIgrb*/@8O+6'}w:ׯ-T'Oh.~iϾadsea )&u:FZ $OOh}$uI  IDAT80q%J dڐBJ?1YϬy&T!SQx:Hj#0O(J8hdfm8Q.(ʜќ(j] iӢz`~{7^/:8(ˣ,.=5 ֩m9RmHUڨ˾ZZYu[Yb9|/?fWgm.jQ$5X+U7ɨYp><~ۆQ8X2ڰ,zch^pQ]1׹!VWhGn4z4nY<2pĮӳ'lg-,.ؼ䇝Vdm){ &$ކ6 z-4ĭ<ﰮ< 1LfUw^p"(j_F,NCɥX&W @W sǜgMRXqCkQ Εxgqa'U -y?O|_e9Զ^ܛ<*ӨL#@&( ʲxtT 2Ҳ➌EY/x(ayR )cAW}*s_C|S@qf=ʐ@j׽\z ZeNy4,²ѡ;eT+VZm}ʮBbQ&j\/.Hc@9?/K;Jr8ݭ(F [r+JGM4{dWHv QaҚ=颦(䶕~?-t[nk{ʘ<Ît`pQO;=^lxp}!{?w[}N,:~*kv Z3v[8^&50}x^G'T x5_g8lAjl8&Nx\;X[ L֝J+jicGĚp i jsu !aYe*(љ@LGQՕE1 ) Ƃ`PEAvr-镚rJY-`xC l[Py‚?ݻzs_=0Cs_,:zgtқFY Dh֊ܶ,F!.MfЂe`9цTYI Uu혯{@YZD- P[u~qK EpY84Z݆疶Us56 }%vݔ-XV+,ueh> 0s[ŵxB)?rH,=I&sN7n.qn5H!Ij!qetLLtf:Zzod`lu 鐅$Èⵧ^7?TP V8RYꁹeUhEhr ̶uÐuy!ABRýx+_DrwĨa1F'*̢`* kUw .+P$(5H&wAYj`&v.e+x K?߿yQa4_i,mF:Y80 r ˪VQU'6_%Nm'?S.^wa)f_[Y4% H:R_쭀،U+PUf*s5{XLKeF[2B2Vd5q%o>}[+̅wL\L}I 2r<ທm/;Zh,9;5]c06~'zoI޵8=*XvKKuJatk%e0]NX9'ݬ^ËsDt7`ؑ/sup,ݲI Q JТRɆ\o+:N|C$McX&k5qn; VЕͣu1]eSRne.,C:.@Wly˺4'Ta=Q|ch94p:<3 ~{gg[16Aa / q%/H\v%[͘LhQqpβp(މuGI8wt9.fҨT8 a8rҽ]*z,ϞU%FEbtR;% hl Lnʵ\ptscÝ< .` \Y@"HJiO<Ԋ:Om0Aܮ1 WX>x0:aaHam>k#dD>)Unu nY\˂gs'ky6Bh$}/r2GYYFRC_k%IHSH̱+[0 YYFw'+eq/E) Ov'Ebz%=74׶F- Z]c8C5{3¾laΌJ",hhw$d +q6GE?#v=[(ќcXs%ebFJ1b$F0Gp^3.D"+Gk)RO<0ZMbZkl .Fmf۱_s1%|wJvq罠9\HINY戉L *tbT^A\-gN[ sik{Co?w{ (i-zhZx0](֌xR _ޕoRuhKGKmo<܀3I3+Ԟi4I0IRu]ymO~}10"7+}Oe3l{xjs^Ɵ\)̍5(&1\%"PX پqRT s֤B(ӵb4 ʱ!?2AX }BF_=0W_zر!qpmkF˖\ArP#4:I J+tCZ1Y+ǿ̥ R.xw:Z̕iG9i1 Zt 4󍾱-9U-NYVJbu:ֶc %(h>"fT)T;QO^! X'Ȱ6 m- (W Uj9*!0h!vZ`F;>yR acʜ\MqJ7í*VENYv r?7_|@cܶ5,mpn5, j0xZ3|l-3eȔ}#1YmZm:hf>|fLƕ?TF֓4Ng_^b45꘏`è^seE+]+֖$,ZgRK5J-Un5e5,u1:fÆEmVl,Wଔ2Aɯ}tӢ44m4O=ڒstq ZaJL2;[shޓz"m}ݒKOоn~U\At_'=-  A'BNXVQ mLj۶6;Z^'kS~S{ {[–l]xT4u捽̏~] ,, 4/)=_F&#i\_2dǹ;y9 ydLœ:3%Q1wޑ)b'|NvMsu, wv{h%DH,Cw5]Vr K-Z<.edQy v[/lhFWZ:X.Ɓ ;X(rf(nȯ凧?>=]8_O)>e[ϧ!|M~>@h\"7z>tٗ)mI^L䫬Mرt=!Z1j`ΒOv/ UZ5^anY' ъ1R$۰,27 s<"y_b os`x97ZD07k8(*mYL2-Hs͍¼}ݪu Xt19 s6F4!GE7bsK> <}ڇchԘOcvoTf{eXb 7Ufi+ՏD<W\9Uc$UW6,{G[] ?ÙdniVmhV4DubO06(,nhJNɏJx2zxRJfU46HŨ"T6(ֆ9 JkY2"% /|iG1 _:|xRMq ]ΧtK),[|Rx˟>t~>kAA9T-۞fv(/;Ǘit"x87F6+HM`q*ߋaYFChBr+HQ^Bмb7e:Iݡe7>u/c~ZUnYeVݩ(-yb 632h+y:)7{_!IǘdX+:};[{8cQJs{_{Ϯ՝\;A*$/8 L5}|}4[ Z7<ʚT ֩̍YRUw1H4i(6d; ss^OG`%'KJ*t: ^пlV2ϫ̕8X52qV,!Ţ,B\֖) Aej=4Tr:0_?&P瘵?JPYʯ~y,>QeJt-zR;Zor9(ŊeûiҡBIP~ NqvNƴf'RGr[khUrWeG^j0HaұkT?־Ǵ걎4_pT4F:f ,53?LIChTbo\"QiVҀrlyJpʆα:8kxaهg[}֐$cti,l"=k]TTڜvxQDZsy_WaњVK8Jp$>y5gܮꁹ0C5DP KғŁ!ъ Qev KΝnKpͳ_CSIUN*$頲%G&D$ 5,T@-uYVm&8;P@{BQo ؠ3Ń-_ENT^_;N=*ġƕVPcfusM,g#M6VDF8gt%Wvmt*\ˣf4RF,UQY֚xrۙmg=6r+ ΨM ,Fa}?POg ;7VaTxN,NQƖ8#9648%}$cZ,u,ΕX<ޗwkzQ[ n.Ioe,ߏMdih&i}kO{hWa:4ItvWiIܨg<Ԡbtb 7ު޾t(]a4 xF&LZy߳?&2RJ`<0k iBkYTfMĸ)PE%5S2t%,d *I** *]+)C.sZrǣC-`7|L|QA&S5ձwqpPNva'!w\u8{Y Aa6icNz _v[(eZ$j9 L'7p\ѨZ^,ZBAIv[%|R_ܖ)n%g%<悷27$i3_̝]0Ϩ"RCse(Aͨn u4x Am 1%pHbH6X͋(N[b[mڀrxeq+7R(WOIX{^7.]+/mHJ"LmT2 t-?s}ބw#-< q.\00ڷ<UJ"M&Ucξ ᤲD`vUfJ3}r8,mNhXVJEhh $!y$/U`|0*СJ5 $A5 V)m("KythDkn<0\?KJ,CĿ>Y껰-+[P4'4 :FV^~Cs_=0W_3|S~'?s硎9ך2L_A^=6/"a ˒&,JKAaa .M+~بD6겯!@+mmgj + G2<[q7[epoJqj%q_AUJp-=Ѯ,=#chQFw 3β0Qk`AT(U-^85HZ/c'SƤ F05Fiilۗ"Vah-X#:xO0F ($QRSu*if&6:԰lma_FP1UQm@:"ϜgQt,n:UyANΕv\ J ;i7ӳ1Z g=+[^ V,fMPFUYvq[\*A p,>AU&Lr6LKl Z4%ڋ4(@Qe6I-'Ul6 &_ ;FHԆ겚2XXc떄/|}H ;'{8"k:\5ѱ!P{z6֌Q<]|}Oi&0ZUX NG |¨0GuBUÿh/(3j;"aj0ZC[A*ρVa;DU b0y9DDY,0/A1 \K~{=(R#1-9NU \/aWzT}?:mI{W$1ʌn[0vcɨZ\m?/6_S9^01k>l֛YrK<ˏ/RIJ!ϻY^PƸD(9(rG`v>D*1-&\2BfF>t?#%[bԠš2ҋQcpl >{oQyue}W_erX{ ir9aɴޝdai\`sXz-G͏Q ͋ 1 B%R@\tiAp{q-1)C3-Q s 4T&ᨤUV C_P!'6Όsޣ2 RU @K#%R+3 .B} 2Hz"xcOwߵa4rQ?Wu켮O7ВUVFFpLdj2}LrŌFM $[ 7x8,v䬯e-96(iY=*Y Bm;F,K @J Rіj+ub~Nj}]:@9rb ѵBfݲd46'T1q|b *T )3 jFv>+@0b.LN"S>!6<5QQNwd`[r@p-$C+E4g5cH8k+8υnք"c4gz`=PNvײ&}哝0u AJMhv-OQ,K Y*XN:բ DeO'\cdekugY5rQBQ@D(im_uY Ro"C $D Wh2n;rO,7C Y` Lpտ2Z|wǣN,WKdhMöY,yPhhDy>{h^s'Bғ1`8-}V8UF #'7#IXjJ(;U{l*fU]Yhuⱸ$U-XVSr 4*S}UHiuz##;Oʫav*44Y݂\}׾\v;Ά gQ!ޒ>[KD~BQ1폣b刡J` ݚRU$yAEW}X?շii4i㹘A|]lЮ |>:є`6×s9\}t@7MS$ $a7A9全,WYZ`n{lLufIj5vB/`s[*D@4QsgC֠Vֻ7\NbC|GQwՙ(vM&,on?+-zʤ0z*r˘a}fQ!v ʃ&uh -=p$4Ԫn[9d2Ni\DʿZ6ĮTeȒ,&ڎAbEUlݜ+*HZlHFU`91RۀAQH+[6aS(N ;wkiH-B,#ÿ<jft=A6LsWEB[lZ ֶs4a"bCE`^H2T<XSmszaF IfHϘdxܿ}Ses_}@rUҨShIPn_z' ?~i=V˛7w導Z϶.~02wd:kV,Z!rn2Em^D[@ASeEFxtց6~T;BAKny,NJA1N&MiBkYte 5܄ c.0PH+?xVtoWŒvxb((46;V~Z;Wc NǐqsߒCt1Y$i57eUgUeוYI$*6xTT24Vj11AŹDsȡa9iʦ FWtd k+Lbql;>X֖'5a?8hbq}`tL͈fxu\dOWF>MLVsޝ~?*aVYsholP:KnKDRs2ߧ1( ӣYB4S17>{ܱeϬmpYH"PK99 ԑ&1ɛ_O~q,J \E+~r2uiKGY-1Bl k߱ p:w!rSd[:׿Q}պ4P$P!n8@U/)MRyBg**mumsZ /cBjCnˎ](eOQeVN)tʒ&hI9*n+ufJepz(1+#S(K$6;+ar_eh R+edZL%MU!B ʕQ%xܣL'eNYZVi`QzǫA9rTۺ)@j8%>F):i7^VlHi/)! d xŁm7ԇtqϮ/83YGrPhݲ[s$QibmyQ2o<)]ҷ#j (/ 9X{hyE`h$bm_H}W_tXVJa6AZ)iݫ~^iyͱ2qب3>RSi?eM&l=_Ёee_+Kt $ifT N",מؼ,*@kLx4Rsg}LkRI U\u ң R5#nT|9EcmG- w=b^-༣wpg BQWZ>;v!@Cr= ZegrޖX[s+|f m֘"v4O>g ?u94cV-&^z;F_=0-Ec :1tؾc0gy#p1gVXWa8R#d/@ wϐEPKL!E JﳟKϙ>'.a`K(5ң5RB~uT:8`( v^x~z*zDF򉒧q ϽE fSOSiR[Mr3E!ޠb*A[]Кܷ~^sYa `#6ǵ:cs{|s*s7YժFѱhaD.{(%]uY)` IDATNkU]&&eXqAa9dI½q=Wq _UraY(<4bu8KAy<9SO~ ^R+D,HTsX[`%8hq:g&< #DP*5ħx p=߰S4E(#IUW}u˪@ ZQn˟aaA0Vz>uҁf=N0Ɠh0Z^xJI6mB% :M~Š0/, FT;r]pEP]EO0'I( !lf>oUVom:VeyZIfݾ=դ _9{C>D:vD9{0Y:43'AD1bƈaU0ae 9UW}M#&0bBȚI8gtz=sp{yWQ}fz>u?q3KN!䅏= Vþ9tk+Z=Oڅ s>"Sgum{xa"rYؖ4L o,C_.D*_7\bzPKnl{2;%;[/'ZAW GEyl^;Ku,R31 ٕGB)ə$(<*^Ǵ׸%*cWsR"f\"C!]wqL>!lxD IW;$c><6{}}C'*²% 224E!,yYusY7OBIP7&$ s@_"B>๴cTlN 7NQ64,ZCҰc)]Or{ɔYGKF\9BECYǎz2Pl\1ki.4:F<,tl:I_y53Y0 TfҌ~W{6}bxh~g&w i޷;|SuЉ sKUnZ1߁`>5c  =u b#MVz0 .aHT ͺe޸ /ao+\`^$K4D#RhC32y>hbE9䨥wQUVBp^ &WhVYy xû=ј^Q-{-?Gxm#cN Rn p91ȟ1qݖCiErP![֞ :R.q._D(N<2MKrA"m[FЧUY:)IqG.~kh)~'u*)2pDÊ!񤫛,H\.NqFMˡ[c=Kַab}Pcp8+e7)SaQz>$mcG>G:yڞׁgg&ZZbzeK9ɗ.? 4yg ~,N<:eΙܥQXY뱲}c`ֆ,/BVSzm]o;*tl,O+&d?C3vŅ>7E`, 9_Q 3+  r'7nOH Z0t,e+l- H}B/>y+RY >|HE}% _ч&,O`"vs#qԏ<s`K^nKߛ-bOTA K Xex v c=ZAk` N,9o O )K5[en٫>k/tM iVB˓iDD^2e(Wʲhc/sPNCR` T}p{2։E]ȬW_3?MX΋ҎQZAցDttrȐ4|l)ㆆsiX /pʖ4Ҿ<7;]]f]y?u~~}s!? -EGJ|h"ľeX`u)ichqUErgBC']lE0FHab@ ҥ) /Uf .BcN㔽E&26̶eʈQr-—h0i8-hn150[?}Y,CeAi,^C |M?&3ڟW\<>{1qG` h prIX/_1!17Y/԰\9=R ΍R‚ .h;t$Ba^iD5#岶 <:j;aV"B)Ʒ~(?NcnCMJWDGi]7-8P"(Z6+Q+ٲeLMq9rc!9DUq=$I8C:Je"@r[Ls~r3bBd:6eoiĵccs1yJF2 X.ܐ¬Q5N:1/Jϸw}K~{j>@Oaf^v#xcp[dx'В8{c|g,Bhώ7ʶG#0. |=E^*SQri˘[:!^`pnX!87Bze4!Y;e_+r}79|L^+{3 Am ….lCD'<|8$K MEY~`ٮ/SFQ]B 㻟EBQ~čec<#'QәbK!gߔ%8VWmXV ;ikCMTQQ h^'dx/ǎ\q+3QqVYZv,^ q{,;R$2̳l0xcT]/YT5ʲPx!XSͰVMR!\Y8f/9pYg|_FH.N7CiFڒ2K!LD_ 1ix)AF+MdJ} dk,Ƴ4V\bA'ѯ4KQU7P|#0 F77?w2pdS_Ѽ)*X_ራϪT=^;솗yVXlny!)IglT83A 3c„(v[1 dW#t[-JaFP8q`E!"@y®OQdY] xY #J 3}#[}|3WO<00YBsRDžT̵^<5ʊeepߏ̓î,tP,7Tc!aO]ԉyyQR \I{WNp@&,Ǵ ![Yp{qy ,ʨ2;lI7(|R7@y ۂ-9M@Y_C|́y>/uPZIJT6, rIȑ<1IEHRcW|&,GH` \s?ur 6i*V^-*`RmG0XuPGE<SeXӚ>/7Uˎ"UOMTh¸obt=>X$}}6qм/Tqs,aJ(S"P=@) uj[aYn"lr͞ q۠@I;Vg/lm\*EGc<>ٚVQ/UɰrSr{CwvɱE>?!c>!?&Qan,PV:F`":qV,EKe6;>QtRA/]Go *Gז~Y/k2Q哑"@)Yٽ`xU(ܼ̣ܲ#}W#r_2v6&.nA~e),\rwCCM'lcߞm- lG=:o௔@t5rk,Gg_8ؾ91˃wr1}Xmq-) AٚM0Q`((8 -WR>InygAudrr U TmkI"揮IR{DR2BOzcjVW,馲n!kaw;oFi}Lʭ 4f+uBFDuL{4ҖQ硣,D.սexn`XVZʒFC:tX Z`pmr!n|=t؆-&5j#Lx]U|¬$TR+;&@sel+N*+Br_vEhr"bA1u,RIRw07Zk(%0o0+IZ~ĽW/nS18(hUigz߽yΪvjziO)ZuS$ӶYUu*~tt{*Y]lT#,[ YhRCĜx-E8_lG @n:s$n@cL1Ęae ܍nacBu Pu(?yP/r*ݎUgۿW<0;PXH~uz~dUĘխSTV%y Ťep<~ CKSekY+ 4[hZx괌*}QG@,[\Fy* ](\9QC7Nt 7,IH1͢VGJBPdmmVoFf)FAaިO6TK<#ӓ-JXD,jĂ{.<s`o jR9L/7YYkqCzVAeR p%p*:Қ'?@ נ ˳ϋy$QHD >ϾĠ =5(0hƉRWJeh޶RJO.B ʮ"1"n$$10%,Gː\sT@aוj %GhT]ڜE' yȽG`VM-SAsÎ1}̺xlc2Lleꪡe 견|PãTN'"4V )[T<⡺7e ʌhBYmpy,sx!X?'TRjXˆ `A̶l j.&+p ‰gO8P*IRa Ri/]< C,(X,8856s` CrÖhTf4YmYhʙRAtZfYPVq9P: t:NB!.{hJ' iWufrn?'X^Ly7lP~_ʠ+Ue Au} ]>4!vc-҇B WEY̲,bWVǿ,sr=B^̮>"xdmcƨhhlsU""`԰<¬b {T \Rg%}PI!J/>|' ink bI#Br-b{Ư>sy><_=u)[NJ*7TUvYO;ƌY&"vUS/)=RâFh >Av!K?+(LPٻ{xhkܽ3al"!!ErV ^hW5W8փ%X-b&^oy1 +LߐkeEUka="%|:yk9yR[XnIpbX'J+h@lX(zfc sh,ϼ~]`I |/lHYddğ _=w58lS s r"==5JeUAs+MEL( [˵y1}MlzҴyx `w؍JѸ IDATplexdǎtsl]L7MHuJ,IJTi*q-G:\=1ehOWpYWojsa-IhR0s>sfWx{4#tTC\cX{dujo0^smR(5,W.nu]dGزi#uAo{1WwqH5_Bo6d?Dщx2%3jC^ o;H_}sr90|W+̶e*J9%=ӣx]Ga֠F[wJ0Wݦ̣N2@#6јK|j}oag70hg`YFfB]]00q;v.RwPϲpᯃh+ W:ҲEX!_mc:`!Af\Rmƈ=?'cx|gT[hA ZWjqݺJs q٥իHJ@.mM[F'I/4jE8D /6cDˆ|e []gۆ*U+~X\ݔe(l`9K9mx#i?Pr`hM1>duYQ`ԃ6{XN(wpt|4,. !tG"UBJ,ui 1 Ǟ?6{!+",\],-֑D+q-qs w=o59tӏk_Ɨ-%D{%3*wyD6s`PxZ!ՌA4N%,kٶε<P)B!"^Y䟺vp܃>sbm u m;eЮWA~l1ᐿ:kSh >z|"8F6Ìo=%`9ͫ0Y.w=a %!O]I<jpX'S /ڇ(Q CZkuT8 .7O>?^O~~[ٴu)Dijퟰe3BkzjBo+&-uSm<ijX[:VW~%4[>:=P- ѐ|m;gο횁W?u .D%=tC%}nBp!SL*2h5, Aa1qle+W[.嘧<`D'Ӊg*XR߆͓K^Cm"RDRNAz B(l8]#Uk<i 12O2\llXPId2ammh8NU~ ߗ#,Q' CU% "^0;S_Q³=k{ ˖wT\fSW +s$JFǵr]9Ga cG|'%:,&њTb`+۵7/wE= LIpں BHT!mꓶÿL7#Ze~`֢WTUCt|7>vdl?!DInX^g apXoc\UW<$6|n` ن㕂ks%sK|rX1LzVE* Q:ڰfH1Y_ڏEm`3^jx_8?6җm۽F"6J:юE )5Bz3ޓ)ʎ- 1QU6qS6|(!|AR`Ph¼,+Q*A$RwCbE}F]ҷ O{CS {J=vZ@j1Bcum4M t"&h%b4!yhY1dÞ/jX֍`,L<&JY o(<_ȗnRt':ؙg^YmoIuHJ;7Tk5G\4Cٖ6scE-|F|^r uQE11iVvÛ,ˑJ!ʘK%jq }>H6+O<z`ԞrAܟm8w Y>9H6Sx[UThs0 N3/60'RŝxC!]J$|M94c>+_6q;OIeݭV;xdCz֢W7)('w]3ˡ(N8]')'W)g`[ W ui T*6(6P'!p;0Ei.}BГ4P$![qrk.xe_BQev& no1 D[fJem\.օg'I󤩡FÜ2f/SI\TKeRp u,{Q/XMPvݠ0Ɗ͛5r- ,wtB Ҏ"N,0GZ6g '^r{,)i["8O+|Kg|L1s>8D┏ mqya_??Aaκ)qWqf+ daOpqhpuK]lsqۗt( د>z;qcw7rYgQ.qͪK =zؼ $1Q[YTokrsRSЬ3YՕ$.g?ϡy><=o]:O/~[KJeiBo#,RA7Ur ʶ φ|s[wɟ >8.jRpJ9Yu7 :*a! [cY!jeb[A 2O:jc:< j 6 ])G??UX\|Ct`9³SЬTaoO5b .{ Q_^@׳a-A?†m8)QR , De&%(-UleѲbHٶb3et"Zr.8(2‚B_!`akF+AVuPk&EMU,_=MEx.lnsh?k̋c1Я/-Y%Kt`.s{m'Acg ˃%#_^=&k; ojF{&ښccǕ^?b`yS:Y I{(6&8׳c8VkSL!ddd [T[UbT6|m`0ѓo%LƼߢKnp` LɲiIX$X3T3K%~trċ\3SxL'1;XvsJ-cakFgAGeƒ=6wpXYv {wNxZօ1`m<=װsZVk-X,-SLGTCg-~8qxGcƣ1 K tYTW19%’l*Q%-}˗D"[%IeWD)- ,@'l3*²=]w9|}]J/t|4,k"A˄DhĪ}c;u6y^}0-}۱i^WK Y<]:cF%E}sa'$$be8{/ќKcRe3eqМd:$UB ֲF06Z9WKH[ltغcM[rs[+ucu6έk"XwS1kcv|0,?i40gc翻`$/I])˝AJg-*h j xsξb*!sk]ecЬbZ2p8 BZQkAr4F_P\:/, o79h#+Q-ZyU7^yg,lKR-$JUN)$V;Y skY/<~1cY軕tt&/gvnw3YAr ̍۲fldp.82LH",kVeLz*s G*;L۷AcCe%59H))|SLr>?Ұ}ae d 1=f N<={vaxp1/?=dX.Dg0;5x=h:  @7V*1Jkn/1l]uR?kʥUK(CZ"C[b'%Պ,Ѥ"M;'}2\`Ob-Ռ<^҂Ox ;%c@tBFU YEo*fHW&@;~"`LV#(Ȗ|!sAITvRenBry;<ɢւrxK+FX%PW2ѕc 6%J n/>hJ@6B;vϱ'[6_˟>rL騬O,HH:>-/>'ILF t><3S~~ EZmw>4GuIDL쐱amR-&T*,B%4"z xD 7GJI fn, 3>[w'4_/l0 :Uaߌ,URI3\c#]QIZs',o]yPNJ8%a]PmMG%Z3ﰃ7?k}~;b>|Y8/@9,u$gY4e}ϢRE yqVƟ-9K󽳮]=i'|KJ:z~~̶M̤2\Crwv}z嬅`'QVBަ͕*M*5hf Y*قʃ{m IDAT8 cQ3",nl9G*R%Tݐds B 0W\./g^ SY #ml'܀B$2%)Lc;ށgՏy!(ʩ!dί6%v4W_B,HEy"PG` k^?Ox$κ|] rsIF:FQk!bS6zsu|ʦfwmۖzI 2撉8.haIlemZib[lveH6\n1']ѺYT,\/:tJeV"\BxkBUPYL1f8=ɵ'²((Q5r*[J']s?d 8D;FcCUdۺN ]sm}t){ex;[DTрz T "0F8jw%< 4O< }v2L&t)XߋI܌e)ckbU.W^f_reHzl]걭~'/éؖ) CE[+,0He* y:<%G#gG'~~2g}DhgT`(AYa"zg l0ń^o7VT)EFE1luipSb͟8ސI%,#5j''-,?={-Mt2+`N"L2 }c6؁0@ue=hHT%|nBCUz ܾ whK`g,-ruLThsP4|knոĄSeQEZET1eB*EMҭ:p~ wx=,lH;Ja.9vBl$tv:r{= 16PNBt!`,trS {Eazw>-xڿW}1s<t3dM1JY2A!c(AyBFPq&vȬ၅=U>@Tfы`Ɠ]qgk5|7wo;LJ3^}ʷ\1\)ɍeTcR*Ҋф2f,o]uRD!MBD;F c[PD)V-2q{Vg/]EiIP;ORNJcU[ Yu+ޛre׳֪$AFAD0ˤE 2|ʐ($ $!LQ&A!`B99쩻z?֪{N{uW>{ֽ~D,*/|6(D<s%jR}թw!0gmo̾f9G> ?3_ XO>cK-**֎!&9ƒ[Y4?ݤ#6[VR&'7;ay ˒ب2cZ,8ofZ0 ҠSr(L[b;0$vND$`N*sw3([ J(=m<ӊ U6Lj7qƘɺab@c +cqओZ9(F5v *WReaNb2-p*÷b։|ەbuuu]Q)sr𤃋bYb\yst-ǒ[eɭ0K;dXI>X0@YC{]5$ A Ea%Oc>oyσnQ%&=,pEٽ߈;`y 40Z3+:t A@7&nԯWެ@yDl +1g[e -kx1>jUa)-(?$rFcKjNlֳl$6Iɉ{W$&"X7hn?i`mN+",,v޳ыm[9F5*Z@RiZ; gsb _T6 d/KOq6n z+9EOjet6QW|ͷ~\lVルyeo',kU`mJ-ђg>$Z1Dpa_Yfg bj4& ùe Z徯 cШ@/63q0k.W^(=ja3;WNRߕJdnHcu1ڌdd8ĺ*qΰ:b,eY8pqŀMͺ[\Z:dO192iC`ʧw3W .M4,-0/eɭխ0rSv #&)q ;^OҹrA`.dS#PCzYa2Yvә9EV,ӊqZ|鄗 ſ"Z4t#&r+o2r % #u14 d[`NóVƃ(QyR%ϢSy QA]Ge;r+A0glM*N;e=`66-,xvIIryۨĹdؤ2oA#mR4)˚GăV19Ա9j>^WLeoޫvygͰ̱`LQH2OP,.1HrcrGS218cpDuYU32D G_]N m܃Βa8,j6m 2.GPVB{XP{eR~?J;`2e:<q4IVF]#hX%|J3|]am)ygs+v{0A1A&^:gɬC"* AKB~^y{ooꊿ|lb/ݳW>\{g8.0/-/2r Q.UN)eb4̩k[~TZsQe7`×b@wy.fE,fLy[XΤ#VH+tI!bdfB8^N`^9g Y*G`Yx l(+^m8Ee9ϐʒTf3̡xG8'p&8=Ԏl2uY483(˴Ҭ$H@9Brcp]klt qh$i x!8%(68w w9-oߗ ~v'ʲ,Kr9e,;k(%h15 47K_>Pw?yKUWnWCzs4`3[{0Gprᣟ 7xkѫ᩿y,yɎ=̃Orc08D . @e5>T1ϵխ4csҭ c~%7?|+)c4S9`1{LZ@U][RG9'*5X鮊^8_ữ6˦ bKgۭƴ*Y$KRM̽O&PVYx`؀rœ<М֊{T%ЮƤH>22FMMrÞ14s1-g3=2쁴fkfR_ aـ5$fnB߉&Y0v.w?Oxv*sTBr1o}{VYqݏu<)pZ_y=ox/MØv`7_nTiij0黯|_t'X @)mIE&) ͱ,^?O!i+5#nAkSEXoy xi5ߎIҳi38j=.2 NY\,fLbf`٦D48 x05(7l &MsMAO=?Q..{J.CTl uaN"B|qU՞&sX%ֶa@F}r)1XYJ\dg*5@j|Us[ȁxЫ.e̋W[halmdӇe@yVeVO@ hjD+Zk&~F}Q5'?V|j8a0QFSe-dA fLݧ[+.i0KN]VўaV jIyRJ~愧Ke6y:j{ Y;!C!6E&Y55# A;u)K^昉I@p)z!QMVNan<Ƿ&3 sNe`0κ|:U!}e6M#v_LdKSVZ(6uU3^1Fr-{ٔlv;ͬhЇZ%fֲ2(X,yO]f4V !$HJ\(fְ>.|;p@ʦ̀6FU JyUIO{,>gA^2"q'P^$/ ׁ1 =ugzy ,Szxg0GXV&e`b0Ȅl%\欝h۫m6>_~'5%ϲN]_yl*}BZ& .1<5 mBo "T>l@l(2MkK\oKV[e9 Ȇ٠ LjqIE<Х`&W\oG1 ;bYb欁Q 'z=Xܔ[Z'0h-=ٷ2Luj16>vhoa~'c¼1SN2r\ORS R#Ј]{aĂ,)-Y{Z07-ǧN]@/#vPkҳ4gFiF!H4dR. .yPag1Yo-0ۼGU4ܨIYƶ"}kӡS;GPfahcئ߶/rc?6) lH wBbmNVi]=+F Flk!#O&ȴWO$M=`rh RP(o-p*SN)iRA{#~;~[LarI ÎKTO#c$ȺH/e3 9OC!"^ ߲lv=X؜j(}x'bZDN&=mkj5#(LY ? 'Òa{sz\o/Ґ|TR@:/CȬ`/=.ԕ§={bz ^bY,n)²DXlSXc0bw1/QgM-!f Y-\׹v%0*VYHCEu7٦Sd {b] bD{NL|a+K^MJshq[ў)7`RuFf1bk_]Nk 0P瀳I :MߧNefS\ZYF]C1ļm}d@֠Sko7M~k!]d1M[ϠH\yc*~;dC+2h BY}E`{JƄ`yFm=˦gLA ^.\5MV|.&p'|3$`hEZ7Ǻt\&0?OEY}AR d:@6XCKbLlRzf >EC;q$24t_?ۜ5iF-,W6G}<*:PUs*Đt~ 9T<.aŪŨ,>MEY177׻B`"[FpJk4/y,KUUf:Y1L$D6Y04B0@xya _Ļv}UI~1$(g4X648 T{^hcږ03*︞?>s Mns+%uӓ c=6q ԲںX]Mʹr02I/6P雦-E; {¡I,gbȉS󡙤fHү5u<3 e񬝸e0\f`GXuQ]VlP2op²ԓae!3,5⺅Co]@bt#K 2ӭv.b/ELRE1QGf +Qe bl1m@0{lʙLU\)Nkt'` fU*&nkMczl|WE6dHjl4"=Lh$|V$/Kn<6σeUGs Z6hj(OaS@WFxSnI1g܃쫒k8+=tq{QnkN}MXZ1G ܈ *̒ż*1bBLv~k!qLJI D(\KFKzмŲX[n{?|?H<> p-1|c2+.BrdJvZwL$Vh AZHvdv'KYMf4'SgCD6QBS4)uj20tBhegݔ%%̨@+8Cf8ahx ׳ [yA>-2mM*Or& ؎k4K/-C34K4=˳~P|S-e&E%qփ-; 46Bspn KY4`Bmn`Ŵ iaQUhc<6vP򞽣!ø%ev`06DDCV1!oFd_K!xz5a=ygSY&_Eu9)y&Ьjvs3g1cr:- /ܬTet#;iGKsyvp6!XI⊥NUnQbfVCMew*Նgp%99'GH^?f'ALN9A @uV[GYZi] ]ϚPNUۘ0(gTإΎ;l5!zA&8[!>ھ)߆g=M0w׵头OOrg|mlmgh9YyBQTMkSOHl%Fe)c.(kbFZ MjoA2SMR/ѺJU_aꊏ{6Wxo(\y1w{b<5~]9k.Y?y?0*<#,KܨHVXL0p&Cs!x8>?ę˘S$Mr{DŲXBOcX 9>T*)SX0ތimlZO`bvsOl۷?%A GgqXl*mWęi`6=[gb[WG1%$e_>*!xB*lџ;٧ی1XўʜmM:yP$kFC!))i,mߎld<:֤7ql4]zҮUpú\;FϭF͂ΝUU&M& & u8ڂi}ʳOhuv : kQ;תMÒFe6ieSM֌v>m[)$5Z<.+,c$- f(ؑ`&b-}~˭g RD 5apW|z+FGzYXז֖qUX2%h UH .%*v>P{,ǒe^qw:'@3?Dm-4`5i+~FHLuK1.Ǻ"5+ 9Alۏ W)}?'܈Z+UG8/EW^u^ywؾ♜sқ[`.Ek"׵ufxz*wnnМ\dA(g j*l7 m2,%M*M*si?Ynyk^e,y,sl< +L ;Ӭ1(rEAJP,oQ_4ӛќ2eqSV>E7*;h.,Ffy O,q]v |@}Ǧ4Ȥ`S+o*O:裒Weabv$*sD?̦ȦayO|m<^mMe}`qi&%kFި˞jwmhjn،/[sǟpq.7/>(]AYfF_@`EuZhn0wBlL Ynfߦd`z s/d,]OZuTBƵ {'j8MX-xB5~ ֭9$ǟͻanvM4ۼ6pcUҰda,ol}YS%c4ϼzV*Aރ\ԵkWݬ6c:x*BY.)[Woƿl,7y^[2{Q8&6Bru19nEz6 I z= E!oeƃ*4Q7 oΎa:a K1 BL'،|/μMbWsM g7ٯ z jum&WOGd[||M5'X$P4 zPgQhl+[eӪ V 6 ~̋e,qևe!̀48c-91׿BEJY`0a*!C眛u:7f1ZeЌiu90 &qTЬwb4-(ھΒ>`݊:vkKOݿۻk;- cۄ19F619y MUUU{]Z`N݉۝ šRYT k!ң$ tuU1l2lQN6;഻˾9r,dgIkjQ(qF+FghT欧2`GUt5)p[Ozl\Fo5C_n"'9^2F`lNX^.9F2DwUmomT|*|}+I F5s.h)X&L,fUҠ;U涪R>9Wc]A92-Rijh%BhS b-+RRf-?ց`Br3N'ܳ^?u?d"d@&~kZ@Ԣ&TrM|h)#=X*sRSK/TSxS gM֨jJQ`$?<{aXbY,yaZHDvUwrB#4org_@*l .MzHZ2T TRWbW%FȌz6C~)NjhP+vՒwz%@Ѝތ 5)u)ˊUd&VDwUw^ ѝ_LIn@Yz_Nh.'k<ۯJrdkKIAɂçh79oQ88$2-_r\}-?1X{>yƞgbȲy6J*ByIBI~7DBjx}53v56m`7w~:(<Y{w7#Y4t $ŲXozt);aYsBs}֩j0uz [&kʮltn`;&tyaRQ\eL /s4lehjʵ&ǵn:WZe9&Y^U\,4QYnneŤT->7)~?N:׵9ݚn%-KjvSqAs2[w'},Wɲgښ7 'L&۔r2f2f2 {}>pURVQ*|+F78",wYouv07XkFDA0` X-gy9kYiCqo jmO3yR",">Vh:`6 ,Ǣ?8EoE!ƾ 5oxpY hdz@N tHzT/#Sl\bIl5QafXhNI39pm V`tvԞg(E@y̍ȲQ,, 5!TPQ9f+}XkUŒp޵o3\I,ώCw|͌khG?)~˟:KSËb/k0 ˝옞bi49!>v;2X쎝ܜrsҵJ՘ڜZ 6)ѿ<2K&'#fJG$'U65Ch@5r<>b8Z(F=9>%drIY(96w&PiXzy"mg*lZƜ3?x iSda{V 0L.v{l"?:XBfLR.Š^c%lLqq;&|aU RLa`8m< (B͊,G$Y{6 #SR FP&J|Rd|ٗO[\`C iW%=n*s.g˓I`cs_1٣[nFa9+l\kОINH/ȚkE[X\Hy!r{Nߞ"𢲜`9Dۇ·<|/@C`y7K2g2*4b:)˓+*bm9^u5&Ϻ|cMQ]fN"-Ì_DL.K~VÏ ~X Y 9'zew7{~~Nu,4—[YNhDleyz3f୙ΟVxWŷSx{ 8%8f@s<_Gܔx#Tdw`i°Xb)Vq it)c:0&%4-'?Acb6 e2狥Lx8Y?yܢgx/vKBWRc[_j,eėƲ7%X> ,`kcքXk;E_=)W_LҪN^8Pk =AR{vځG%gs/脽a{/dŲXvı&5 {0 @uoRimƂDA |e:k2vus*TSۜBkHr*˥/9b,QHٯYs)_.3(y…MK\W\ eh_K(DU9MKhH j ]E9\Y@Yq]*koJwqasʪO:oPF: Z2T]< 7vz,by{N]pyqVX +{rekcJKS{i30y9xRvgwة27Dyy2hmzo=yZ7%tl[L;΄U:y'gVv:˓=́Kw{>:(笞ˣUpNmR[u9Or,NALAd!2kZ^qv_}9^MM=fN/|۝hhVmk*t4d mDY IDAT508wxS!Il ae|O`uV] 0(,Ƅ0}P%ȍ~"e5Mh9bP)UkF;mQ2k9s3fm59~7'0//{-,aYZ~:"=;F<tʔYJF|.}?Ӫ̹YjOMtl:vPb2,G?@(M5)q6JepߜwgK.߮90Y, .jGC܌^6U.OIU]<zuoQC-T,ӂsR%9q 87mʞf<Ea1=9s],;rgϢhGϽ<~uͤ,)^w͍c6qYstk[/:jR(y#i:"UA(+JdynRU6Ͻ};o 'bz7~.DG3 뒫;wR[g/d4+KkܗNNRZ#4v1 gm!om, 4k1}ZJx7_{| llgVmAz?kR=^.ǿÅڤA5Ɯ}8ÿ¥sp-.Ea)raPõ1>v( pfۨ@HW3c*E oU&!f 5b0m.< /z I//>S,3.bo/ovA,ŲKQ:X]Xڤfhٜ 8FCZ3Ay[|7c` m*MrV]YOت˴]UoᏞ#^;ܞeJa rɥHkܶ^M8\Nޢs1>^*Ӷ^~c<>fG849!TvW.831)x"`IJ}^.N6ӮhVo({pXț9j7_RO"0[dqăX>y?ea~`y |DUL1:/{y7¼X\NՃ"`Y ,7?uiQΪT6|T=sH=~YiC(r[>o}>bL| 9?_tVlO\eYk{8cBF  0 . Q@D&ED`^^ aT@yF& 2HaL{Zk?US}\Tug=TW}oߓ^m[{Rw`#8,wyCo~_Sy v6XOaIT/Xd#G C]n0M>zU OZ@f,샧9m뜂o|K#U'EdNmgsfי\l1ŕoOI2[-羲CO]Bjy6,g^عqRG ˥ ep,f'uGf5)Dgn k ݨB5 RM"O>{<<.YXR+4xYR ؘNxx~7_Z{Mys{q>|?qwN9eb"[&wK *s/UM/lUakyݭzFDym#u~w4-;`in.Iwe\)Ic~=YY;x5?6E~ce~jvO#~U0F+o+CLQ!)̄H#!u_*²Te+^zskb\;}zz-k9 ?}~ܻz,P{L#y\@CAlr`n<-b/ů\~DTqK[ 6`_'}{O3*jVG#mCz"7tƔk. X}|Hc eρF97Ew}sT6͍`?j)>Yڡ}ofQԱX?>,č=H^-{Ġ0/>9w<)Fz{=GݹlV5QThƺ=ܗAټ‰v̓-Ut36fN@;T Hs;k% cW4f2xΓ8~IdyQ&Z9Īd}Xry{=Ez. "v %DAжR@[gGl3M%7W?>C ) ?z6EUEa[2!)o|V<&vWX],JJfF95c&K udGvhvP=/9p!t_朚DF}66S$#6ʉH[\_Aa L2kB)JqqҞqq3三^C,,ks _" aYL7|ߤNi^_;ʬEUXC􊳘@zIHf?/.yxՋ?Ѿ>@cE;,$\gU:I dѡT"?}7e00O:.QUr3,(=!*mpÌw'sFEKn\h$ ,v./p ma35rU7B@6) ,b Ɯq\vM&99Jdݩu?sY ˠת,/ގBKgZAL<+x-Nq BPolwO$[LJ0)f0C#Q"~޻Lc'>Sl88 S_X˘B/n-.%TA=B"YXc-qڵʬlp˱$?^ntѽ11àJ\Vw 0*">q,nKuߣ?} \m, Y|l;K.9Q)>q[/Y>櫣[r.}x3$[86f0,(W1_X=f-4zl& )\jns+¤0 fT3ɖc1.v0knk忎{嘝{M Dp`]Fa&DXKηaaˌrU2ҲYM_+/㳽G#E ˦(RVԝL:rm0߹Ϥ.ҩi|&k@eI]P<1D\E{=ǼUɰ6#3# JV$wdA: `95F2dsS9(a9˒G71~bgrδ](6$]tL9Ӌ1U;7/pM*!@VDGgr[Fѥ7e_wZ1UO ˷ 23TݦF>2J4\$rI1s¹a\\:f;Ug֨pM AlRn} 8,S*[~-%Ȅ)e,mYf8#3,f-4_|R]jGﰒͰWw-?±9ŜSˋXWX)}Mʛ/}/6+bMEMX0 Wf@Xĩ,Y R_R/ճ 2&.+xOWڑWw4p2eleC,fCh/KOn0VzS՟PZ7rhIL !xRWV']aMc;gV;M h5o60o/Zv#8?tс-li|tIiḻij"mi`W%)KQ E)[`F+ :o'Cf2KOo{ ۘ."Ŋ`7s.Z 7 @"b U@/yyk;?c/ct+v-ofIʿ^ 2(a9@4cLPPn@ >aN憰<*TYvNdNY)k䒊T]63guTw}j̪Eh!Z_LX8px'c"fu+@І ~PǕ:۝[ 4o}MwT~77F.b{9M 0BBr߆!&ǺSrXP\g+<37 d,e،dkXN`}T¦}'6ZlX,kq,iqxU>87Kaqz'<3<3r{VS}v_|Ot310fkȧ'rz0 SnZ-lƂxS-(|#?ub#Ueݙw,g>H1nh>|qGHOCs {3'-Mg6m ;|B0Ü-^ ^(]n0 Y %C63JAAo6z+Xd~En <_ggU+KdYύ^DŸQCU*|S>VLqA־>٥\``W YqtKF"E}]YZف2l`eO]U՜cޑ=6?{MojaV+#\ o}C,ɔb)Pb(Ŷjf1PJH2FJ&pK Lc4̚-gm.eܪc\{e~M8e=ָ&\C[`Sʛf9<18k T)Ls|oa\JBV4?]g^;F?'!o;x_y!REZSN"0u\ IDATz2 (o]t@6T=Zpr2~v֟V{IeZ6ޅ۳ 3,EUX:m+%8wtY`F%igp; O=rՐM:0^T[`#¹X+(b-Dōn$<`3Iq4Lap+nEvҭ2r;Zpqɐ 2kܯ\vq.8Q9|i%e8,mfB T5uUaU;4.Tzcu0ɦ!VX*9w6AHiuE99Ĉ+}Pߣv=x>oy}6\Y{嬧ʽ~ 58ʴ*!ٴv8!Y2@w_ (d)2PUۨ61z&YXuaB:3XeCfɎo2l0(uu>ű8eIo˿|{=ޓcK"bYیk?ɹBrZt‰=v\O=B{n&XY=_uK7+ elel:klbPUV/~}noϺ5(uɸ.6}N?qս߳ uX(L1}b=~.iH#.60o/a.)\G].M#G9IJ\Kp"W){vޅ{efqY% ɢ+Rk³xȑHsY4nBC'fS%~h5J, `#&Kծ4?;{]Le.cÊʩӝHTU͍>&Y[v3~]m L:i&W;(&#qYON)QaNJ:Z*L*z,V'6fL2>-+o>ފl4|7m={/-i!ɛS }"_Yb_}OfK }_Rvm@ma)gKEP}Pnn%ЕT}lbtG2J;EhT\q6=˜> Q[IJifRxV6tB-' ĊZ\w63I~kw2h>~׻!?w`i",cUUvs_/- }9qc?3YٙeXWb\JKbC Z w4KzΙÄ؁r_a.5ȴ_Ua ݪ҃eHDф c#68u䓒byk__:" Gƭ;}_ɧN|!G6aV=r_+ɷ4ro=CQ5s7ؔ/vS.~-6,wTfedXll^f6Ok( !DjȄ|Ae}I֣f%ͨ4jEﳱ!w7GL0f1PuBcu+ּȎ3n`Y KHԂ@eY%2R[<j+osp9ޗ4/:fcLٕ$B|c2h͈=t_{u_m"~|!LQZ.#3ś^!GU&7PUj yYD`b 0ӳ3,r)#2t%K-QԦbgIe'LWٔGIAx8HNeBrgqΊ$`g'j"$MV3?\}Ȋ P־_{K[ a,K-,Kؤ*wɰ}k$>c/ʕKǐwʕl\Y*{+r|%w63ʊѿBHy7_:Ӣ"gK7H>v"wr\;r~EٚSR_MX1K:._>s/<,)SUMǟ0Yp~uy{ b =Xz_ 1enaͨ|Hey|wX+|AZ.ٞ]d{Իڡ4=rlb6A;PF}BeXx SUbɨ:$u9tfiE0:}<0K1i2TPҮ,̙ +B fbbr"#F >Dmv!DOfGDuJ .UaUNŵBeYΦ2v1DֹֿLX V1L֤TɦU9T*˸⛷^dmϴٴ&|N:c~67` *lW(Gf7r{=Z$Ǝֽhccm%0 )2wE :z4F  u}8䧦ήO 1_EUmcgnnٸ!@+=[F7v6)>{l(R19'|\uڣՋo ,KP,oapmoxYҖS'{ǻƻ,)WY[Fu\}ê,b4 LB?W$3kfjT% SoO{oD 9FzjߞDxz\LJl8Ü1~@LfM~cpy'8 i`6 /߰ $g^Fa\Mrt;h 3q&RوBUuǞ}^sm?e?#?\ bt׋D.30{dwEU`J /Dj(i4M%L.[]V`n@\ V ec}/ ҃%H_4(- ^Y!WH%PnDcҶd8.r d߅VY"&$9ZhVu=F69EW2![kĭ5ڶSmJ< ^3i6 )\desdAtHnY~_Uz ƺ@so_NgE#Fx;+Mn.C8䷶ s`˒dXJq]b,-@7AH{ E0K[u9\2r;pη!47e:'i -j]20.&D2ű9|fvkX[`vae^ϹsOn%My)&UI1$^ʡ s.1ҴlW95çQ#M3!T?7TQv9V,Xq௪OhR|ƐL ! `&ǪUq, V h2:Zt3 I碋#!/H5}f%NP٤JMAiYSyk*?:/ FOyٍw d*.5*qb! -l-MK Byɨ xVa.$',KY>Euي& ,挌BJtޡMi`O:6\m%G̦-(9=mZI 1CC3T2/_IY^`y`˘$KE+逗7as7zn.?q|]ODL∾WUԯO:?J!"4}}C1$nj㜺Iפcb'}+qBa+ΣsBloS/VPNrg?{|L*Y>y$+Qgb\פ5ƧQvz܆m`^,?}q9r0 Φt Ӥ'4ݐR_VUVpw&b0[v* ]^|"BeJ庅,1(mrmVaFUHDFIR{ [H‹-K$`.}*xU4$M*Y2}h7q m@_ z*hĥ fg"¢L$g#z׋.c^L1sb QMRuA6OMPz߶F3t@cJFcmݪXc<܇fw1&91YIMn{[ri>"lzyE[ݝYNSK$/Ȱ8JTUNrVܦ$j>#A=yM6ǂq&yk$gɺ?i4g^VcMQE;F4Cxǭ=7dUqBJ/فr-L 8Sl&o3%+SX%XnU"N!) 5) g]7c\^bK0fৈK$v_?O^DX-F|Ckzuv`ˌr\0진dHצ<ΔC&S s,9';f?FV~h0L<sMmHHg.mS sF-Fu64o#b]ǹiUecHty)27+1F2+:bQC"FC<+GRc.EY_ĭ9h Ck>p:$S_fVQXrjtF,Ğœ`t%qu *U'YArkH E$&y4*svQ**7bj;KFώ$%#Η^7&fHPFQ`j-; \pgB6*`kMFZ%ǐU[Z4Ck&j7gh7<96WhndM.B11M_6c]sns(JK1|`Y;~s8:T[ey϶_^'4TFtb8@ Y0[Ȝ<6́u uvVal}.Z0TXp2-LN]Ɩ|y~al;zV&6:f]W ׶|6Q,=(r)>ִ"gH>)qH$S@Ibzw1(yK*1Ea\ 1PEl6޾Vp(r-Fؼ慂r̾W3>i˝~z ,}%E!L܄\ Mu{rWx"\gO坟!`3Kz^-eU`7|k.ϵ]$nm/{Z1ƹ()}@6 " V*k[1!ĩ]jN^|,cXZ?cYjuTO< iW8AL)Q ˊі%]lu4 8"CM 7JIZ*}cP2w,D)LXKlT):ۈˢ7XX1,)JA==0>8Q 0 *Ԗuk,zyI0C^2(Hk!h[lܖ0PY,S(D|v|m,Z1{_vIY֢EHk'1mBWz*s^|ܾKK>d_}悷rI&LLjЎycA=,QB66گcrUM/#;*8;qGȚ!ö~VanI]n`9E*ɂcL%A#8u9[\i( LU5}U@\77 ']r˔Zu5KotwM(Tﲸ"lϷNDTjl\is$UjWw&)WMVЭ4UWjJVN8/yc2$Kr.87=76؍NN#8r'%E$L2|︎sƜBV|" -t6k<׸bsCud!05wV35&$m`G{0$@֓VlvozW@@;m%eeMRІ 3ON=Ɉ;cErqe9ɪі lr륕8;hy#QfŽ_MRԒA[x$ Q9Br:=Qm>m&ԢtvY%kG sgɰk-ʢ}Ha\4# A ^/ "Ef0a--D~ג 2Wł+Vf5S]׏%_1.۴|Zr:I5-0KۊIU;`vEX7dI~NvB`nMq1L{usr˒ m+ 'Ű=-F,BuFYr(`4QߗEc~j|z5jh=7eL=KEU %Ɗ1qC`) bՃeͰ/DbuH ȒF?*0[.s:0'NuМO`Hi>f1hl},'Tan{LTkE>c<ӄ2 xFΔ{)WPj*!&W1.39:8YT*d$ylpֱKvQcCf'NJdXcu #:n ZdSnVeu|l=憍}SF.c}k]L>p9CH2f axpm[60o/?;FRǹ!mU| IDAT*JH(̍C$F6$]׸ZY Rü.Rd#n YU7s y?cFmbk>7Հ r;FYG Jb1 %tF,[}c)H:E2ݵu2S03Muyq'{`0_|(b,_9"7 ,. ArZ ~_`nYTcwJB7u4ޘjE'=+F?sx}}?o˰ɖӀU:Kʲl;$el`g[E:`l=#ృ@ώAc&@nM;dvLD$r(/6{'WXLn0f0ayp?0:)F&S}DڎXk8!Ġǎ+Ü[rrQ4HY- HHfK @*\^b2f9Fb<@=4hrr bkXG P;BkCbk}fL~ TH8bɮ|C IeM]slecBR8BSI|UbK ҈?*N~݅M,{Vs0g:Ͼ"mhG0箳 d}y i[-0s)ɥeY17Cg3!rt~tC07qps~/ /scͿ{ҢuΥAj)Vd+?sLHkW٨ݞQǶta1 p* jlA#F-( B@K34X ̹V3ml!!coa;K!O~ Kf'KjU9Mfr׬ţ`D| Q/;yg9"! xH)$w8u>׌kkm7q=翐/>ꙩhuuUAap~m |nYPgzDtN$sBw}?iT[ѵ2pV2 ;]Iox0.g8[>$u_&;M׮\f~0[m FZ|ռ7dMO1zboC60o/?JpZ5*`{ШT$,-cZړ'>q̓#z,dHo{^קI bLL8rs[Z14Ҭe9\1gɐ^'#F=vDi6}4$u_>bׇ78$f~ WgQR]yjVRbO;Pû/>v[etuL E] o>v篿&2/b͚3εҋRe6]adUǠj4BkpuITf,)YI T"KYड;.&6 #~pt>׼ ]zMS6{궦 bS]X^HiaĔ[\apsj2ҿ5#ƁɆabPuYZP19]j ^DC&\栕œ scN3M-_}ylJϨJ32&ЯzRfK{Z;F6kM,sK 9LmM>1Tk9I vץfV4!g-q$8YDq=9 0c 9瞩Mee:6I3y3ہDˁ;(@QB3wd[F{EIYwY{*Cf||*E,u=<LpyXTS#٨$dQ" {\Au{A>,G?A]m.ˡWąFKF̲ P|"I;2I]nɍ>" b3Ƽ'Կ֌}QzV~pۈN7TQ;B&pU1;&;>}7٣3^=k"_kFYɜf&#V:reU%)}T5nT7q;Pav~c3wmtǸ%P:I}mQ+]xLz5T"OM9?٠.E{zs:[\e9̅ ҫShҥԆA_Vf 0)5Iĕ}h֌dpN3dg9Gߋ[{0#w%%1=oΟnLcɗF.nlDlH#"T-,k\X97, L h;M9:Xʕel3@&kBUV^Ew9 BEYfpGt>s v!٣Fqpv1' LѴu '2ojCfEx& Ai[4Hy5,&x PkP +;m1Hϒkg;0yɲ,s-,g.Z;搊ttA]/)@j=΃ы\.%ɤ&##V'EY,ݨ}&s[ԸLM\R3ZZ(>|?RGO% O֦g Im{ru25sK'7fTJTT2'c欔">z}0hayz/lO|/8K6Ķ 67mP]6 vgQ*,{ Ze-Wu4OΥbt&]ypl_܍8^伅V|o7ؘ^˾Cv>r~}w׿5>Y²K2))a$iGTW$QPy7hBq3! xe571Vs&ĊS q ^a ,Ƕ9N0ٛ߄J^~H?t.Y1|昞EP6U׬6,o5&T+3[ZT(eRSٶ(Mʦ7#(eG>w-AhmZEvXV;J ).'d5YMk)-K_m .Sht ^kRlXj[6˩{/1<񺳉xVU 7C݃)s5v^DTiT1y$U}:[Ur`A(*5ją &ECFT$h4F%F 4 30̽:8wP%svW:?pKas [p  J LF³GOύdm4&>:[6(u4c41:/;bdNzd$U0W*r_n2@pȖ\Y2-N"+4& ;qX,R(QbIY_U֘A)+nʅ 1?0?o3weVP7c}~/{җw],dPGyE0T֪b}s˚!hdO3m[148!!uWqF=v5hB`-Qhk| sJI5dTv>[y?4t#_?c3-cRюTPhyn [C~8P#|1EJs v56|"梢 Ηe9cv<ċVT^aEPBzy-g^H&( (JXhVG|={sR2 +@gmLȵjrՎgE I030 6C(}c.<3*sGaix]hE.|HVJ8?\X/B[.4?#ڃ?{0( >Ƹ5]6gfA29q)r3 ?}QSxM#,GeyםNB e}ہ pS7 -{G4Tv 7(̳\14U(UYEq4*5N"=g}sN;܄Eȏ<`03zǼe2lYn,4H ˱nsZeh񘋷g}8 Ζ$>7fG>Nʬ^Po+6FbT"{=5P䱀F"||%q6O*o{vq%GU:ΊX58gAoG Ëf.x\}lo>oo]q}{>dEFup9p!Gt.,-+_EkUOu/lf޶sgnQ$k8nJAXpTF3_PHAzY FL~e9,hc8ᗿ䊃 '끲U6[+"^$z5<̻bt `SXGa}XgBg`nr=܄7w<1l#$OʕX7eRNQ@+m^~O|8jXv^a%TzLH2)Ȅ ݂7r;4p\ ~&v}l{C3ql]hD&i\4#u%r/FJxcp_U^..Hk&% @JюDR"FX"l0^ꑊOn݃_9,PkGl(<Fej#笺)~PLYO1`5ig/ '%N:G \Q#,vBrGufa -C~ (׳Wᱳr_GnkꁐWi&4By Ȑ8tG&W:6_x&?)v V! Z}zYD/]˜AGeV*E)w|sS8'qGlBp}۶܄w53֒f%0o|]ˏ:lEh/=^*l9Af{!VraRK[r}pG=<ܿ[>sEd!ATQ$<;Sv]50o;H!HQ-PCyhe^{T")V \:ʳ,T3sDe礌]!_4fV 1M{~G=}·CiK\sז CJ*xR)٤T6 ߞ,{>t ~#$mQVuyϹ[L%w#3NBi-{-Muo9\#Nb&kY⭣5;ֱ톛x;;ǫ>KN,p޳} 8lK,@qqO+ym=z'\lPc:AL2jeϏ-[REII6V"Z,dDŽ4Wp/E% Nf(=RdhD_ZkǮ7s #ڃ]+r ˱ Gx'!7_9/x@!%\ ʟUyk({rZO;/|vBj)m`VH$L D`>cJ M|9+i͵Oo^uߴf(,=D7z00 1 I,0tmϨZǂS`Z ^D~ڝ@ H"U4l?GL>)%SaL[. )aLYuS.xll,Ϯoz4h-HSE)Tf] 09<< Iv7nv]|c˝ԾdR5@`9U j (j p 8W*4*Uy`Hx;_p:{R.~68̢|=MVz? IDAT`๴i1.lip!z$f,[Rt R8t}.(:rPSX'Ѫ)@Y6|؈?{!B3j#, hO>]L6i6 ul`BǢKFI֏[yI:ܶRp QE香Gx_%y 2!ސ 2HmXFbBQLc2cJxm\_G?$m5RiX|A|o/j='9JsT<PzĹXϤt夗!L/X1T:U5V#(T⦻q#P1|S_cI+]WӑnOH$M-…a[1O3?7f'ѽ u:60HOH_q<,qE(`uE+JlYRo41|:/%{)};*;O2)C2)߃>@r_r e#{Ad=L EG O<n. SsdjT HdTxւ34T6RVr8Ĺ:J[R!r/v\vbPB,0%<l*0&qˉ!dQ-V's-x{݊9[n[g}z8<0a@,0uc`p#;&9Or|ͱ EW/YuMWiǿp'X8̣S8[j*?2!%fo;o5l3 (Y/} s U1p…_?p".C\>>Qf8:6_gΓJF' _Lxm<`unȔD2 e›6{!lsEdHB$!cgTaGP.cBHŠ~ 5Tݥ5usV d*!F2vt,A/b?׆J} `޸7@r׎Kb=2G dqIQX,crz F f_a( rO*4h-J^HHd ZƮuz&F%QHLr+6)0(JrR;f?8}ݶ!UEsrvvp ,sڣZę3ukɽCH͡xŚ/ Y*v[fShE, oЕ㣟>T aEfseYeʖZLhØ UwգRZaTUw9,JhV9˫+\}y7oDieve;7n;z*Ja)%r6vDkRG6S6R6T,ܪ5UuT ?\w'>zG)Bs-}&d50%cFZXBj*,UI81}s۟tB֥{W.ܫ=k1q?P)|H*6Z3.f:E + ٗI kV}~7yˢ=H0 Q(2t2(  q"^>p)%5WiofA8T |=T(-JzKfՍSjjcHTgty¨-:2DP֬37%ptQ6R޻e;'RE[=mݾyW{9,Ռ%yiy|1٨7?3ݭ$ ]/M @o.HI͂0RFP9 )#9Ԭڜ2dp硉q{8h8dVoH-Hu42%k-E^D{c sZ݈­JU%㔍_m2Ro,6KbGeYuu$TQZ)u`{՜{R6v*X]0H ;)=@heAX Y6(7\Mv\VΓ.e?2*$fW:zNcyUuh7FMϗKVOY>%u!G~"y>Lj!KFeYh"W'qԝKL`Y"K:t2U!ӹWsRC=Q=b/L=C{$sD1'sH"t2<\{ 50K-XE?;|׊##0OA'?C/`9sbӢ8}7y_e VOXy3ɫ>E&0' 0- U'A'HrJF(x{.N( SuKk(&9euUz9^Bc$EI6h*0c'ܺ3, ׌ ,{Z*֢T8!7 HiF(sĕbZ8CS;a\N~mHs(-"MxUoEz$es ysϴto%i8gξ_x@}_;/Te{gY+sHbޤ,V+mjaUAW`~Ec@a MMm``dZMcwbQhA.I߲(֣T=儃رX-@^I}ks.3(B]Ι|7.y C>xz!4G?]8>d&C+d;}\/ykQV=YGeė%X/KdYcg|4om +X,۸j]t4WmB0c1xeVwko0Sp_4WG19*?8uYHbDͪ.NbTYbd|O Fk&${ Ř~=&'s낲)0 4'LeX%$*e\NiGeu˜=!?JXʪooMzya;Jޖݛmv|>"=-`>iXH.ySdh*-.:Ae?UA9#5r.KLʃ!hL T`Yg6wYq[5D_OWE \L󜅅z;/_u*RW)eHP3)=Y߷a9lSNٿFO4=fjY1SP.S)TK gNY`(7:r #K&&Zώۦlee9}[nw*.B:* "T*CJFPB,8t2rXޗ&&3ox䙼*Nr>N-AT7ODe5'rbߖ3ڑr9ϿZD;@9<4D}Hɨ(8;} ,㲫.G mXjJIiD%/|֠^c`Xnr{Q*a:ùW!.zج4Xucj "(WX ù˸OZ zٍ܆f}Z-λ<_ ֆz/E5^z|785w겊v+9EmmmOM`y`~%7қJ'41j3ANx×лĉ=$ZttLj@y #4 UG*TZ5 hh~&[L23NnL r̕<>Ƅl. :>_T겍ҕ0LHeBei~ΚC$BsL |OB PRݯZ:Y ZK’ޔ\q,N:DeB`Z]7): :{( Cn:"Z%s&ےUG;F8!%IBg0X61̡9hY2R+ *xz|xOiYaeO_G|zR+Dd=ȷa4ixն5狾Qh)U+='V.&ҡmV2)#,< '׿PY)Ѩ-yVm.3HcKY8k9%}uSXlc0G ӨDL2q15ÌM! c2$lLxbJ><cޗ^A:gH>}t[xǔm9 _|oL2@<#(We"ǁH+1S#E~8.ռT,$` "1#ֆU%uR燆z,'3>J{f) (GmȈr?s ?kÖ׀r{)lθ3)|/d)e.2o̧ss@}yHs:2DQ9ⴷ~_CzPAi^a$K ,7,+̺th%JR"N黳ajmvJ#\9ƗczE̛#BIH`V\zOsUyOXFnZ`!~#8KPZn{RmXgf)/hRRTgy/5#Ğ7 )b^cfa/ڟ+ІYNϨ4W }l-ćm?UT9I/K?r,]ۚQEZ1\Ax\}wr}tOB꘷\ArXT2'U&^.`aTוn?o>)d{|̓ߟmSaYӚLkzU "Z3bf'<jm䤯܃#0 , OBkg@h}[a2B}˻YU%_آlY3N4ЇqQعzFӾw_ف̕8s&эealCKI% Gߛ&X IDAT,z~_5~@eV uUiq w-O,=#5[ YP]Ac%* F֜D Sp嫬j_HU`= &CSY~K] @DxeS%[+5Jr՝x V6#] ig]cӾL&%O~S)Z'{:<fm(Ԋ5{UA]m|}d qH)=KRs",a -e0yD"՝sAdxtQrZ阪cYO9aOʷ:|f_~z RqPmE>.%~fx+~ņy7n%ҋas-u#ⷥ[*s!ɭd)4!lU?b˿?a#`dXW\ucR*|17댄$``mC+)Lmz4 <eenV&˔+gVM䜵 Q95 siJFBnrsU`WC#5hBJx+\A No+z*Yϐ,rdGg~oY8l&ŚհM%N"o'y `޸A-El z(e W,z{Hqp]Zi $~d}kL}rM[+w"R1qc7flG8l.j ,?ZՒwmoBP{papϞ5il.0fHCw<:W)Jc=X5,7QPcgl7+%;&#,_Ed^-rz,YfRf]GUR ZJ^+%.\ݶWPeWU: 0)T̺֨n6"kR }PHY K˩ȓR8GXXmv$Ib C'&*͡O7R^k] luY m?sL=–ń,S?|BAh͎xݲZ Lnjj?V2Z!lHCGθE+Ds~VمuUr^Yx.3{pxg}p1֬THJa6*NuWs}ѿ.Dï,Rh< ^::b>+@eWXv2/gMbhW~ѩuf\<¿?wWk "k@`uTe֝g#r^z&֩>I2rgC$$>0ϪΕ8W`$M[ޓN9\u"xaC$z)\lO4x}Yz3'Wc lsy\"IP]U6`s fKɠT`<Ю,)SɄr<O7V9TkUv!\rVI 1dZ=Eoyo^1'\˽ C<3eՠEgu6DJv W͊9Bcx&^_WJ<2?f X;D˨[u6J$I8fk=5deO[EY.51se5s2J($Da.dZ+^ J$VaC/ZU3<_ye˙OzbHiᐅs26V G R s{k ˱y]̓eK\DhdR%wQ]N#,*d3ڊCs!=$:YɲU,+sXWReK9v[H)& 黸OJ>_HER2Z150K|Zcw `u;⩿|R*(e1T\ `gk ^dA/4Z2k2 SQT kuߓʔ%T$$.eLǖeYAP窆+Ez4քfڷ@##0Wvjш ,"%9Tn P:K 6t%p)7| H$QfՕKtUfQiV k39)cu\Pl{eYG3gYYFC_'2tie;+B0%/;VrE7TUB /F721?ńŔ0V/>k<#Qtf;) 崭]%7 oY'o,%TPIm[#?& O[pUYz(㦕1xuoag_!9xS ,Shr-B,Wt=3 ^_3bTwyWH I4JU&ZǴDkF<]Z"\1I5,|[7l(jXc\ݏ{I l9!(k!Fe5sH1_kg-6ZJ^r7E(a7FY[->$u^s^ |F$:m)̽k PXbEb!)NCJҙ$I+> I]%eTK!y~:MW5#~fJx)2ZSL_93 |ۆyB^wk'Yln%k[E͡V;$M,RIޡdͼ^ Q FDՠl@`lD$g6Z7K#IIi.%,Ƽta)AkEhI&xn@IƗqX!€(]IH~ 4oԗ[JZFww6&AY(n[p1Z3WF!,g=UGZ0}5 ;l,Պ4U,,7}l!{'!\ׇ'wbҗ[|t*o0dž%ЩlZr’9RR(9)+zCA6$iT[rJ7_v,fDZeNTPlZ2 Հjc]PhGń$E-$z/Xؾ z&qE ugrJj5h/H!Ta[HKu]oz@u3s!}"fӷ!Y3k/A yJ10Wyk3XQ "eY&=6t;sZs9+'!!pUeW%^_X Q[[֨M#ayM{½gg)*K ,Ԍ!6LvP`o/upbT s*,LV'j#B,0;oUDe%tl/?c`@/Ϩw.W\WOd(t.K L% J[|B [\- b[gjF6-cjst\ ƨ&C5sp ,5\ZGb"(ĖϽRS&6~b&qdU>(̺_R}?eFJAxp> >軬fxA&`٧Eݰ\[Bm4BZoƿ,.(Ө2'%hR!m#4V1[.P Na㐉FQY*A: ,FaɅoy ;غ{PlƻP1ruTYk+Z[F>20h( *V LdIRp%,au*ф+rz}A[lnC6gT sմaIю`lu7o;}M`{à,l(H{[t,ᕢoRRQeV-Vџ U1IH/PRiI@]?g*yĉl^LRE/U.%<JL0xߞ ԀRh0{gnnrP+͑Rcm^ChFDw!B{! uMNQ 5סhwYƸ.=ʫN1$ژ,>w涿=|4~]z f?cwM_*GS޾ua.0hw,U١ "atQ:&1 4 :yd@a0!*"bQDJDp'tw?]}%~kZYEۧOuWug?y!Y3pۀXZ! xQ_8[Qw'ySr[pnMDV}z;@^:*R0PdB!9Z3o{*aS2eu, SóNs"wIɚYk7H.:i`,l"u W80eZ5 "p1[W<"xX]MolДdٝB=Jʤ,7Kh8 Z5^H.,]Ih('e[y#utXBFJ)RLJ߶gxx*޽8$ 8C})9FyLiaE9,}^1DƢQ_f=BE`b" C)tRI3pEc[  +2eٝbӅAYDg5BO**}}ބe trP9_A9|9ܛ@%LtdP#,zAUEбr]nY1Ҵ(͂MTHPGC`Ͱycf:gr YY_$ˠ@ '''$pVC2;,^U}c%NXG\8KYrTkhrìúC,*XX-Xk!yݕeBM*q0q,NHN.7DX5,2`IJ"HV_WW.RF3w2zv5'(.F`+ 뽫p. qOvmWr#Ze蘰f&7p ͂ I0p|!L_Ta8|)4MSx̺.kz;}I .wL*oʼ ݏyWgGOdCuBhV߆i=1Z-hd"(˕(A8@F%vz-ٿaW{6dXBj[0+re<2Åxg)TD>+tTC )CWb?LaD UeP.L4FHzKs&pvMۑYP$ <~ߤ]$[a@2t1_GB60lxyM*ݯPF$Zlz44RrYD)ބկ^L)ϯ{$ŐȑRzE*+Ч8`4Gl6"EONϽAۏhKV05( E"#<6:6ކx.SB*p($_ Q"W!<`9jŒ'F*Wюa<?k‡_%uTR$jܻ<@1ɿ삢0KoQaU!{FFeZ6rsxB"Ff1j8'9̚D8 čn-r1 N Lڳ.-Τr6M9;bbڏ[+/yn`Pt پ~m< sV̺eQm#ApU;(!.Gh`8w"A  IDAT|t[!?=~h}F;s&3rg.+\+^(}τGu8vx |1y.~;CDnp388ѯoxw~"бh :2D Ki[q)ϯqa / ;Ư^]r=|lB5F59K4Lh^bOޛ:pgUoy 䛎X]zQ>%8t~ 8d<ԅ?xz[p SM5ɩJa|3@8(yN"6m/=]4]"(zM=MRHju)gO88)26=ubSZ6`%.;T1}.s_0< O >,ΥBXM,Ծ~%{̀YJjEWM%?dGq wznrEv/ML6`9" "ms"Bs欘 NnۀY>|ovQ]'Y(5$P.|<R7A'^)9~9_6'XJ,,:GҾpD8f^ɦ^N>JOCKa,`=2̵I]f=(Ձ*uUwĒἣ^kl} ^x"Bp>59 lj/ޫ};?\ߙ=%ZT1RDBD/sm'-@H:\b.VDvG/u.sp AMH.T&=cԪ_mږ >Q !]1#)VUk^@,J7C!fU'3IUPAmv.Y}vދp]ft?Y5*KxسnaoK?jScP溥]4wᘤ΁TspWɶDT]PLƒQ+n R 2[0}Rge / ŝ1:rŨ>taScTSطS`*.-xA x+8rG*[AAuaxÀZLZJ#,_{ L(9~Uᖒb&gwʛ~qyRJ ͦ2~8`giϿ~;dY16bc ԘzkQWZ" ǽSȬ$8=D6Pmn[29Z7k9 EdMF'XSdei,L֢mс#EX [7JDs,Rk=F h#eC%2U<6ٰ& "k[FY7 'R8uBLȾ,TkyoOy9}8Hw6,'hbϴ2{7^t GW[OY9#"ףњmkGp{Sf9Ƕ:f9,EX7;S-E`YX;q4E(ఓkם6Kz9ԓ[J5?n[iW~a9`rz; s[x\s=b㱑p\d|ijYw]L.[6xȥ6`cj|42èM@b9\n(+aʿVnC8~׽9ЙF@Yi:Y2.8>CpH'³s } eE&:BnFlu#Bf'-E*fөx?/ڠ\Wۘb!Uk)̱YHFs@Jp&@.YwHQũFm $RqkCj劎eB=^О:1̱'=\/'|';w,O-DP>i 9M#\胪~,;N2Z@nIfVO{ ͦ28wL݀3=κl Y]W[1^HS!=BBџDDXx-ʑТiԺ6Jن,~*sjCfn=(1mBi4AY62gh22k>X(LUUK-`nejw;%#,*%DQ5-wCR!bV~ Θ~ˠWq]Lq> xmhn"jXv`jP1 ņpɏNg{UyƓ SYxnG't>7z 3!ɥ54pt&y*=j "$ ~z[_C|̲mGg%,),xð Q*1lp0X(>iF 0LW_Jk ?dO]Ox.T#&IJ{xaa~Yc='_lr2REu.UJY:LhKꘘ_{;tJxƁSS(:sk'vcuCe9*o✠O|$!th>=̙h?5xdm1Dp8g, 3?iQi>Z= 'p-\fULTgM_K6[;2h:&?D̨Dc]T}m[2"ܟ}_ʫ~ʰ2TִQn_Wٜob u6f_9ύFOf/M&cGcw ݩ[ :V̐mHf ,&zt*-m6Cz=' ,7m=eYU(}9ͦ2~i `ivXd2]zO`gqL$Wt`k /2Bu=Dرzhѐ:+peF [(װmG>R.baV1ŁJfK2Pw(%Lc()Csd, _jvǕaY@A/cQ>k1[ш?7hv Sjnr=3[]ç;K|]zF9O\ OiYO07~(wӝ ~v%s߫\ 8[~m ,|"*ܨBYD߱UX.lO޷`bjTTCYȖwٓvfB]ZS..6#(H-EVuDF.Cvr :E÷"AJ-=w/DT3lA!'>gsD~#Vu<|c%+ )^t`rCO*F#CCVbLu#_QlNYYPϹ#.G}=\T6K/_Og4)ΦHVg>se-e[>X+u**S$N{D(>U?`"hpޱe9fu]K??7|hqkX-YzSLYB`a7o^dT队jf{@ 4;+xpgέ߾!FђQ7.*!^j3 er(!^/6,u]‰=ɮEɍ1ER*> Ev$ڣ Nѡ {U,CgU4:,7ڲQX~r+u7 RG6~_ՃHl]nlos ˢ۵^n/ wWΓ_c7tk^|u38 E*{γ&_Fv~+n%E1jRr+A;9~owwR+'mr.쨘c UF;8đ4Ȝ((dkK]Q!C"98{9f"$c&$MЦaDuWao?X`#>%_䔃TdS=%,3>o75{霹m*6`n~}w3d-gOf(y\an&Eh15TaJ{T>88 uLt\LM2Y[S <.ˑz:*q³GyBY=CzNWN ](Mm7آV]c4KR*"beW<ׂf\*SUe9\t0> Y<S=C|w`l(2Yfv+IqDz>""rDBi@έVUA?!AV'zʹ XEeyXZ ]y# RTzQ{+޿Ը?/J]NKVB>GcAZZyαّsj)y8633S;tLH S!lnmyLLMџY~kl\xUEe5"41Ԣ u,K/pl $_bܪ*ζퟕ_6Ye f5c6$"׎jC_._􄇅p1A8oHIeV@*6-.pw@˪6k%sm78,Z'8ֲV}uo5{ػw0 UqEb‡ټv}}/{[F~ME6z]`LTip]9]'e9(欁9Q]#U1 }؅k~;,cCxcY_Y7s`1NA ozZX 23cNs8U~n?6O0Ԭ$Cu: ^ -EdXCT.G*F:St (o't-l]7Pi@g޻d3^}1gvcNPkxY}G|vܑk@ #&iHЄ kr ͪFqS1TUܢqC/LHz:GOؒAH1כf ]=o6m`=pR<53}jl:Uc\,LT?qdq08RZwlSv;rCC,:U鿏v1Eo]Z~'W`ǘ@uZ]O7Xl- m0 ˓L˒^uՉu>MLGa 7ŲQ0w(B_G\kt@*8<䖊Q`6-B5oyCF:G?Ϲ@*E;uX^I0pOg:sTf@U 1f7a\ IDAT-{lxl2֬2Egc0, ee~Gy1Wp,sME"ԨҼ0mLMJ*r!0xB$32EKMNr-i+nѡ+l`ۊǏr8`Ú'^wQbSZ6`_MB6- u(scAq_-ne>\֧'Pk#{]dBhTSP`=U(r9րb|A6}ho j ы"c=bR7<7zJO0'E G Y+;|"ʲX]o͞2E Ӻmi[ʴ'lm . `=왏%W~` 8{Z!*,[FKI8Wz?`DE)JJt%X{b#\k)(Ee߫tW]/7Y-FX>{sL ѿUL,kyJu)D,v52cDТ%S3HEq'$ ÁzCWHScEDu$ew]kJfx H`L sWQ]Gy޴L9?{ -ˑfu)fN)zCQE߲.&.p&zxk$[5 8KcYj$[OdFO+c18aLyG?eYߵ\M;y` ,cМg0֐9 yggm;8>񻨉,(9j2/?Ϝ*H%ɻ{p־]{u<ݕi)!?5򘫛b*á? z0rm z{u? r!hqn?Qa'zh)Ħ%1%n[Cu`R I]LYΥ=Z`I4~ԉiөȷ=ss7l\%??X6z/x)FD su(,aido#bѣIFN\tGYnt "Z,-R&U9) hu(L+r=a ,k)вuhot>>0ǝ?_"/q/ra#,uYRǁ9oy3%Lt|iK' r0!\~N~_p< HbC;=`;^?2a wQQ,(] UTH{́ =nü ۿL?uhRtˍKdt ]S;dFNhiUuԜ|d]HE̊Q\_lRڎa-6;Y'.cx4p\hؠpgb],BlTT- 0O%"{~X%}LOfbrd`: xb607"N z-Y7kS9"%RK3{ Ь:/V孱Jm}^}gfBK蘒3#U?> !_5C63ӂ,;MpEy\ TQ`瓢V+lB4JۖkWn=`*Ǡo,KO̯d"G ~Km;2ԗ8`م9v=\eEc;x$G21|/lK΋eE Xv, ,,-1o٢W@g5o<$qYBγ'k\wQv%,w0f=ƾp/BU PYA"b7xgɻ{7nKmG=y-5Zi>53ŵP,cg㴹HEX58{b#ƒqd!Wr(籪lͮ7U>A5\W8ΣDZKwtFa6޶G Ä0gBQOy{\SˁYǎ15(0Q`vNp1-dapzaY5Bj?t9XoY/q7l`YƷURǬfqfB^JKrS>3`hljl ٪UgVP K ^Y&sL! T[#௅\f@'=Rwe[VX{T۟,RPy7@Ur;su]bđg篸ֱ F FjVV}y.TpXRqġ p8Jo)F!CFAFva So S?c "uo:.ʬ%+$ue 9RtZ?KOڏ.ʳ+h!Z"tT G?Qϯi 8sx8 މZƀyv22\+#vو7VOy`HrNX2iTerĪw' 0F`GЊn[]МI9qJh<2yu'_ݲ)HA'[YQf!F{9\k;SC S Y32D֯Yv}%(!HЙб=|D[4%7N% O7u%WVEXf|qTU5Eu݉u謋t?|v`Zt,Bh [zBV[w꫞1N119TKUV(HjWa;V?3B׵z@8稪 ܝrNZq»^u [p]&އ^m;`- ɯ9)T9(f׬Ff-XtLb:ڵe&/2u.xpU8lamomHcA4"Жˍ77

~'9astVo W;֩Щ"lc8x"ת+KnOcop}-<3^[O*XNkF&%B>Eק$ 1\Č{ͱ'mte-r_Cq,xi4S׿ aK*y9yygu :De. 58 %,!3ym ,vew*NeR}yfqUfE}0HVB=ep_슑UJgm^."^j[q`eA4t9Lt)sرXL%`.dP2\EUxa]ezU~ݻcr˝N YDUyd]""0.y1Xc02JVsZ*>vh^bd`’&C!,-Hx"r{!j(ǣr:Gy D 5>Ӹa+o'΋*(UdQ]V͐MO鼝YxmåpxSlëN ĭ?3wkXK!YrC!% %O>?f:$z"㫘@HDD!(,\_2yͷmaq~@ߍf"|{p$2EEuYb>d/ x+zk]{s=UuSUfs^Um%I'0i{tЦN+gJy^ aY)= ! 02Q*aP ݆-#yG 8[eq E%*n0;p7`Тy9&crxy>]z(OʆQ+kv t+w`m֠u 5LZ/\;Z2RQ".xpC)b{%|eP&H҄$1#+6A!QV kkPVAYՂ@OT"(r\tw˚&;l_:N<{zZ.:N+Y:Yf`}.$&?=lD{:TdzdbMXĔ6Ej`VeA]vEyPWTd2<.`XppVϘF v1ܐ8g,-zZvnP&.ځHMu.V 89J+^ Xl ǖveːSp ‰0%hh8c㖷ƠY+u*NR|R#;Yf KO_}KKSS)In@VmC ߺO4kDUNd*WT<2ci$۾t%q6.K4z\)r <¿* hAM,'#"j]aiő#iTW*4nZ1l(*~SeD`NLU M,kX ?|C~"Mc) >}ANb 7c}~ũ}FzBR/ 0#YJ4/eYؓ1&I/rSAsӑ:3Di ?opo20xh$QJ%ԆD nĖʬu}|sq?eFJix e2 * 8Txu6٢Yڵ5w[HQE UReAQiNڎ(`8|Wl*gö(C[:#mѴcҠbkSr4.4Z2JrTKeY)!TEc]P ctoZ*Ӷ(7カ<;;xٳՇpI_GSẗl ix 7c)oŶ奲wdB }z r, ;7~^wrNLHZ+ֻVK%G>8J@vg8_Tƛغ/[um?nH'ER\.}bH÷]$)嵔hiCenZ0겮ej\&x7zzan8 tl.7,nxò4YUs54{o#N ,'JcT/3+F.1V Ue%wo녊^F |JKD;<=w;\G 17w}/p8zOlS^d--i M-;VI%U f7!hI1*\;P9y7[D'NQinH*(p'ُMɋe3f+@)Й+⯹帯UV ݀8W.#G ;pn|~|.}~xt|DvޣTQ5Nx׆|Ln0Wu~ ƾwFCрe5 & J+]GlpΦPħ[d[4>%i̥*(;zoOgMfYT IbHM5aVRaVpZyl*p>އei{β0e2i蚦$V+GW rcOxx %v };]'P|G qhvIB~n)=$MIgI:AE`UJP̡EyZ,nmqgÃic?Yf/ouVu|ϸ,Cv>Wߧ~'M SЅGUmB(܉1j1(i4ICcQ 2I?mXw>yI:eJE] ]2~qIӞ~e8oKHޱpgU "PK,Zwݖlg9[22 "ݠ0K4 9&@ʶ#%GP6>TVdC#1e56RLSaVENe*F&Ǽ̿l[G[2XN+ںEP@SF*~-S)*r%UTT$YP6IPMhT(,#*!Qhec%V) 4*΀`߈vu$)$$b6!y~o >tS˝ nՐltFb`)G\Qwc$Aiw*wL7 Bf]|Am;FLPQ]n+^ %*ǫoe, W QIE֭;g ӢFM<nORCWyetUmdP*a|oAϿ|WZ'?焬~Je|'#GIcs~?Ø4Lj0r950@AN⫝ .8׍Ld;fNW. X.+ c"xWk˒suEX6c,cn/.%| #) ))e~ F*-4Ky ֎qLf'6 pMT5jLӰ 6yf'ȝ̿\+̥-6 +F93p-W^qWX w l}|TN״#i&h1Am tЫ`9DP`8 , _2>v;XHLny2f.ix*$#]*b}AZnK!VK`V)آ9^(l¿ly;Y+ZRяw{~Ζo|+JY6!-$!D8 WxxةO- Tͮ(/rDՊ9N'tfJ\B߱eGǼZY}^vD U' M!)!yTUn#VV;$3\J(WXZ 2S/G>50ŢϐFelaDV." )"{x\8Q PnT]=`N>#|Ԧk*;F0E-#!xe0R<v ,k&Bx=+?hkCrCMi$iKJVc lòg/U>UAdR(v-}3ϩ!k64(fs[xoCP5聆-L$9'%?$`N^vM3ǽIAd 듩.xx/u)w&z8m'cLjC5Y꟰0iFgf.E]l8"*73Bb8 <P lYok_7:H`(c.Y|nS;pÑי,>#﵎o\6+Q$&@sW5DQ$ *A;O^DX-煎6zrY z>,u h/@>;UЬoW<7Q591P3Nv}>/p*Mp N*R*dy%Nђ4WZV)<gb#ttM47vz>,턥 z%_ta҂hay _%<ۨI:B)9~6s[nN=~z*sД ߫Ig]S]ۿr//:uyǐ; vuS}i&&<˥/W>aPr@ϺTkޣV[jvTf: /"]ME=rZ!8VZLTkmK ^د κ11\G (UTzB259ю1RS F Fߐǭ( (+xOG%fۿN0hjT;+=ߪT&0[ĔSs54+,7.ǽGs?}FN9L^`El2ӭADe/na=x%'N/ވУt6 IB_eH+GP /:^akM?Aɋ0e (l޵,ힽe8P(yWހLS,I@@N.K00vJ1闾Ғ|,4#({e4\r |^R_ދw Cy+h N1hwnpgtb9Vi >#?(@OoYrvȍv[jY}5SJY.Y}^.ǗO kRKZd*#-RvGUb;34}yPT6~l>hM}5 ʖKӞ-aϾ -1O7vX%w'V+sf,w TiRo!c\s}-hnqI(fm4!&4 HA~Tf'#idEa,1Yj/x Pf}4 >\<<꾡<.}}?vs&VB0gMXVĝjTU.9ڣ4A* [?CB>^*L_K[gO<wS,^؄aamײ0Ae`(7eBR*[F Ͻ7pOceJh&W}=zcϠ\rn O]w:/}݇;};k;0{`a|<8ОN`w r &f^#R>Μn.Kn\bhEh!_ْDc*%#wRmN 2bňv C)N?WĚ%T ^Wzk, = b$ pm3bXW.$@XAEpPiRojNy:ŴUFʭm^E* m,qK/Y3Vaaq2nyӓ;?~X_0m„R-`$ CKQXb!4x}ynW3Y8$ľ:&AyA% YT!=: :R}ΚW=uk> siɨmY,Kl. τ0Jru7hAp]|'@Y`(+]gPSU1F6 ?#3*>eݸ9Myy_ siإ4Z1{ደPJ4LjVr# ;=Y3kfH9%,W sxKC+WeF%9Z#k꞉5H5 #GpO*&Zc< D]B:`JuyĞѰ҈Wx*;#g%I}z;mhMD9(^uȒ$ ~(\Kqgaca/>-!J Xo!˂ߚw.6?ob,-PH.^̓˞I ٤ (2sěD0nc>v&W@YNy~G;FP0͌$c/ŦuhzKXF㘘T2MԢx«-W6%cnL ]N Yf!"#ڲڿ|1wt/rR+X.JhE2$: HˠY5`9sbS&|q¥DžE&$mE7C5u=3eRTeaI]"x?J $Ҝ0 fUe *bn??y>7aNHhA.W "9wׯ%i㎐YV_Ǣ?U%+';ǺYC+̴MPӐ̑c.ۗ:_O7ZCH#Bp(]E?1=xRnģDBnev&,,u *aQaCql 1i_lUѣ禺\%d445߉DEp9/Y;QKKbQv 3Edi 3kIc(Lڪah, snlI~轰| O/x9р失!Sb>IPr`1.%-,"8B҃ _m,evzq6JFD-/e=L^倄s,(H Ja&NT-)R(vxN.TxЬlo?4=5hUam=PǦ&) Ǧ'FXWw7,bDhg=j`r'&y-y1<[fd!:13Uv0̇,,Α΄bOOw+u t( ]\ Q(KGu<]6' ?[A36&wtP6 _by֛JR:鄝,R-dJhiUm6>PdB̖uB". +0ہw9/o>wrG|h4MYdŝ(9V rs\Y絪!vҨs5,n͛Po\N**~w2o?.y~_q|etf5,0Vc]ԊL۱e^S Y2+U[ijϷ4Tf{aLU/ Kv:yض#-<- Z ioә$LTV ej&2 \QiuK)dgEy{w,ljVBbVZS )2u/ mڢBP_6 g;cO~7IN?K_ZMnr{C;d)_$<J;UCqX.[q/eFMAx\ N5wM)ݶ-2K/KjKFSM< U`^ևMmZXlb :On۷ M3D15dwtMP Ca˒겊BV["(SxqbVQ |@7UL:l8ߓ:ABԵ bbʋa= f/J(Q  vV;#EP?c VW](\!h=[U/u$K.,vqH7AaU_Z*ˉe " /79x_ bR-Mppn=xӿ\pq˺|9yGK!6!lӘ U_/4 BvpkTxq1w2迂VJ]Fj2؎Y3]PG~g#~*I3w + ;YQtQXa,/ g`a`=3NN,Dѱݝ4O"YZ2&:YýN &cΒs8#&nU!cǼ g[=v祴faG?Ӣ-9Ekͦk9f E@j2N\n*H\ó,6iS~b|DIŚ3;rٹGGtbә iOvl`F`6d YĿrueB 0UYi9i]_2Jp6f˳MYftRr[dD]33 Fg9|G/pc&2X.f]++SGm  7DuyvMSUDf*XUA eR].[WvsaY-vs|45F1بٯ./'#,C 4`Y`Ց,xj_ ,լXbFP(|a4DSU{jVJZ,lHs*s^]0(ZIz brwzR\Vc7CĖna.e ýTQ#qxەk>۟rAIsNvt̆_~6UFP)BS ZiкهDk;q+j(AلڀP sˬwά9ϻ"԰H(D02dʼڭ Pl/?Ƣc27<ˀ`"U*f;5Dh5@93ηeߋ-`u/ZR8z 2Խn}WǐŪLk`-2VB_=ACڭm,yּ9#?lJR-B"uǝ-؉e,mӊG ȣTa.mg)A]8`cA- M-됆̎1ѱk`eXvJu %\+N#x7ӘNSvVM*ʎ*<4e퟊ѷLjqmG}n|O(kn)R?+$c580 ag\h]Vf+Q<o?}1SSkZℿ,âٲAqRsPo EbݥPt2D]T~];x UP /p xSx46m,-dx^h^hkDdV~&EjYD{ 36fѪe` Hb96jv>Vug*YLJ=|;<Tz&N:t:n?.L$lG1QU[鰎cRb"0w"0V؄.8zG]kq+נ\_6VhAm|p\< &!x33f84*X3 #*AޅIi˨&^Vt8Ǣft(}>!ft'Y%h'Ve[ XʗK{ J+ 8(R|-Uk3¨dHTTF-؄N&C _TeTg͸h).ǫ!8Eao'QGt9pKNm,әarơaRk١KAAAq+o꣎[b~Lr\~OA&Ȉ9ZD~rq%-XRLT[zO˹8a`a4P3ĽOf;i}W+uyh)A%c+U e=AjC-=Ec=.Ryw3 \,{ salrֱ#LT7+ EYX9tJ~7T#D,zZŦ) pe/Za l,^HkFQio (ι 3\ο5`9rPN$ޣBØ]ϖ6sƇF}EN2dM/.DcW"0EzYay+Og,hּٍYPYh^Q J]A0{3cμ`Q\ tmtdȲWaV)-V!4tgNCa.=e쌌vynf%0֨Fɂ5ZHB=r\D5Y\hI9(ǂ3kliyUHӏ8xGd?V#<_yP,$RE:!0k@.m_'pKT;k 2v72F|>Dʭv ml]q[Y̗X.8\dx7JUծh锣jE~u1:\y/憗z6NX(RY($DV†MYeTghESZO($f3CRm".hnCI$R>Q;^[B_6N.7UiM+F /zLѓIzLQP`)oភ^ZelLm~J;%~>ZIoGAy4SWhbєF$Dx6}տ9G;bx@Oo+|V`q1FPFGs /Ȍ0Rְe I7 8gɒ6hOR 2zU )$yB2ۮ嗛8pQp mMgY61,m 2>|f\܄e[QT~zA.{7 ;$׼/4Br&xڢM{Jfij2J/'ybߧii*R:s +PYک`hi,G9 -TyD_"}vLנԈʜAcܒ!_U)FIH pFe|(@oDVz C[ۊ^|Lp_>'[Oׁ$,fi!>}/? ,`Lt[CAa3VQo*k6+#DZFY@Uh$M0ӽQܕ1-+ɅNw4s0 r`U8`)ﳔ/8\d1_}IVМD.ayܺy;.熴g2,f62j!Đz9t }tf(tP]?Qұh{mA"4nI(~1P >CPV4c=;OrhIzLb0dfXm?>4l|2X: s8:YVQ%!WH#&i;fR:Ţu/45;ήibDY.%( wXqLdj+LЙ CP+`DEQʣCtH[X~S[kyCG~e?c3ՒVPGn,;V9(ћ*Y HSeZGhTcsǽ LPةSbPi%-(.A#cB2&h,aB(",Q`1\έ[rl}-/xDZzVDE`6P/`CW1BS|37WLxe$qHbۇzCO!}{8{-rYBT&2AO[-Qt""y,cR/!XQΗ[nSYlfi hrbeU+e ah'!Kzˀ#:H+M.CUZU.F|,)_V eYI|XK_Vql(C^D# ig"sUܴd(`njsp.6xj%<;_|>Kq< oogcwudOz i3DXO˷ݐ6 XD{knz.BUi#lvGV .ϒwRo0mp7!Lrg_Ms;*[p0YX`S~FQY\Lm*a?{i -V*²d*s ʪa9ӕ/[a:H㯯B>o8nvRA ~Θ,{:&a_pL$&XG\Cz I&&$#Ug#4')Xג[8_ی9eoqe3_6CXgxa!A3E 4ғ4I]z ,pnP؝6vc'\0EK;0VFXlBwQx_eo^?[ӂɤfeJ8rvLmC!{%Oo}˪RZ!<[Q5ex;?B&bpk*`X85;=pk?{{%߽o:ù6_{IWE܊Q]P?.QAk4+ CߊޛYVg眪A@Q"A'Q8shPbĠ!LjCF?5!qs4@5Z֞[%u}ݧ3ֽ;a\fZf\kMnX!$8ơB \EXc EUE).P.հςЙAz ]d%_{#BNjV;yo1.vq, IDATQqbh a3nTch YhkA 6X%AY\PhzD&(QxTN,@xaePS _5C_j,7o?WfeU#+*jj_C1 m K 9l+wjɧ4[OU'KB+zKXԖO Di ~{S{1| ۷;p7è2*F͎l[.ӌ XVt495œjO|xmeT.LA #('VkPBs2qX40lsǖsnFXO^qͺqS\|씆3޻`NW50X5L̂—P- 0*nY[-cШA{y] ]( RgF~P@yCWml"`PA cl RBR D'`ρЍÛrMo|S !;5EvU^4qP'2@\R.:Vho  46Ph&~7ج6A(C0 Gc%9J*0Dwd ܮ?^ HĻ6kwA*V:Q_:n!5BʊS ٤Klnv4ޱ9.Keu?Iw&F"jzrk4 Պ$kfhUkQp[nlTCruSC6\oZJrx^b` ,E"<6`w_ ;vT^-&'7m$5pud! ` #vL.B`^`] x5@Td&Zv R9p}vZe'5(a*5qfZI0Zw͒e 0OmV 7*hmv(§z?lnc~Hqn1=F}?~pzëlƐ/Ą C1DQ.JsT/h`y&Jʐ+@|Df&:7;F?+x^ (aߓk'QQ#,ێmZ k9hE U>N3xWs9x3-ũ<"LTU]G`r/}kaŊŅw>K_9gPy]QbjPVMBZENvm[zQaIď%2t0  (3}#,1etjOZ !L[";D}_PACtw:&.Ȋ\ѮV{_>|%ݦ~ˣWfuFUcc$U9#o|CjvP󸣊]QhV8S'lF ɍ,0ar^M41zs^e͝䉽|TcsE}݉hmH,8)K:A2-tJ7bf,);3xrcen(?;lL1Az\߷*G Ws5w朇^7}/XxTaWlG/iFt]udV**\_FA~`%#BZh?潧VMWM 'ǮR_}`"XQQ 5*cLVo2Zwa^6ʼjZ! ql|Xd3=fA2Xִ|Ҭ VWd]I$s׿ܙ[ЇlI (l-U07 ?&f0gd `ug%W>y{8~jv_kU18 ö&+^uڨ7jkX+o٥vȡv&dQY]B068*7A$TXēl∀+CX*<~|#XX-3[^ay"E#ݳRI[E$uYIfȆjY-,,BI[@S7KD\n1%pE`!1HQr*@k e8WMi|em[u)#|^tSgi&Vs*'겞AA ʤ&# WU#e=|aWṊ%~ Mqs"WIl<އX[WNTT1; %]鉮:`Tk2O5x%EADJ+=UpHҔk@<3;# NeRI| R98kewcMP ^*\2 u1t?POhK-Ѧ#֋cJخ]Auk-u|+&Kv`T 5B*I\u=_ 1‡2i"u'QBs_08a)P )+t3 m0Oua>le^Q Ɔy型n{pӓAz]oww| gn1^Ze>7oA_eSr^l81hcRjV)i˰M|<#N0'nbՈ 'YȢ<_w> p(c"nnGn[yG6t޽qS$X.LC,6Us^/p40TRm|ܭQfm50em46pe)߸?`dnOdL.|=6SLUBWi"Bd9p!K<'L_}mQR4~ww n:7G*Bм7l/՗ݻR|(A*FMe2ܭlzǫf;l%[ b(7W?ٯ܆Oz:׉h1QdHjCWpssHZΓ_y5 ESM BY-XٳqMO壋KLMTUh$EW^PaBt`N|m aVzk+V҈/jts>zfqnX1Fg<2\_l `:*/4AFOj Ihk2U|%h%ѝl}pjx{V_(%6 (ml 7^rMA^Pe1qeu3WAJI qНV{PUjy PdpgR8Kpk5Fvyp;&N!BOS MIF~<Zi/ g$009V޵tּ_mICh(Lց*JGh@TVJr_v_b4!@Qm^\t9a׎1OLfj`3JH lҿQ,:6o{8gQ9U5&nx껞N␿=m`9W + я4/UӶ!oS(g (0c/j6?0;_D}D#"6~b-rRѡ]-gy%AkȆ90G[lmgМ5K^ /(nT$w٧XusfC8ߏ^ 54[HTRou Snm]uÖQñˀM*Ԁ1ɋ>ۯw?=-?5YjNf^Pp{.wApqHpl-&zR\nNzT ݝ-H%0[\1uv5/Fbֆs =4.k ɶ103Kt! Kmo2ECLhg?iBD;wW3o_0װ< uIG[Ҵ\w*s,Jq;kL%_n*d$0A/!+-(?_r}^6`[ky6Suz*yMmYuzסYT^2!3=XVaŇi\<;CFŚQY߇l*y&3N i Yp~O9f 5$Rwohk "T.[GNӿJn®b)YpWil16]ox]l>"O~?^<3,7Ye2f|Xb^X 3]8qÐ]<_mGJ#7] M:qU7/y6OtXz?42J/T_HN0 f4SӨ,^arjms ^ī2)&m]Eަ#'ivɹTWsRkP[ ?E\1.{9fG]Ƨ$Aw0/v3e],4/Za\֠Ba42.#4O*kjL{W: 5T!]`\y|ɵͿa#l`A9f0Xĕw'\_;G2" 45<1UۉUiDžECӝQ5Kf[>;?`ed^GU W"7O0RuDc_2_-1[-q}Jƞ]m'RL/\ɥ|7>ɿuvޕx>aU]Ye%餥ۖ*i␎k-?Z &h >]-,g36,HH U2kڊs._62{T볊5r`0bqq1 4׷{^ lt1NN`F_paGeJ5NI%UZN*QA;ce5;,g7-v\A3].DoY-@.3~*㠮 Tȝ|3aÎTۢr\0n[UQiUY9BU6b< *_׉/x𚐬i]Sʀ:RLpRo;D+J~VU/׼z2;`pZ:X%yv|_E-+v]7;8O1I/wjRqϺ,gTqxd"+4FP4\%x2G`z%V{bW`l db!V (*bj` gMcCsU2aw>Faɧ?<\BSsg- i^-f4Hg )oƙ^` ؙX[Ñ釲Qy*v7)kPe,!T[!"Sd?>La ml߂@rkY:")t$\U_i: IDATГ6ݳ)ӷatι\:C%H,[i7Fk ;|o_enwv,-,6P^4it¬X*Q3 815(xȏ7A >yɀi.^Py2|wi?v?1N;YߏO7ڗXX\T :hs8FiӦohW7x^ɆIcP* ko]jSB50v<wAA)nJƾdO9r 7 Γ{=XQDnRe{Ȗ,Tj^ v- 4k2BwNE d^8P!Um`٩-XTUImJ a<6- x|m_.|ƫBaLN6IH>8Ҟ9fA'\64%dMZvn?j#/\4ڪة(2J ,+6"rWa15*spvĘivtZ|@̂M|KjL j? a1ՒLW]HݯlY^?D ygJYI\ ͹Eƶ&:fO3qY*/ st`Vu]ѫ\ﻺY8NY\q?cnTxMa֦,jԠܼ78Teڭ4Q{}1p9ƨ\j`.۞rcPTO˄ n'Ia?Ǿx)U~͓P\`ODC'{ .z5:F%*htlTT3X*\ɲsǘk\e(VaP #|c!S:H.CG)uIYX&|xM+PU(0@5Z^:f̓m@|$X<ԐbzҞy&K)?5Umo]1*j+m}SjR˦厥ɳˣ,=e*s>"XkqG4t(+=XYt5B(Цjy`azBEr\54wiKV.쟱{c Ox9JA#7jp& sfRddڦ i !xv;mN;;F&  ^'Ȅ3kl8Vnߕw\u-4qDv 砊^Ɓf,P'>\6:-נ6TSk_9@,,-_0/l WfkG2 PLF [2d|_ əM!(6\A+1CyՠU3Qsa,K赋#ne װ 6hb '«^a>SXVDW̍+f+fz{Z鯼pc򦏓נ9Vy ]%tym@92U,xό{ToQ_[!!S#r5ՃU-5D hi>adx, N&q"( d…'K~VIamޔx׻9A$T\; _C+9c\Txb\6SSB0=*ü.-'̈́ۃ֪5k'U2 A•qa"BA?O%BA0 ^O`bal] T(%`M@ϫvd-d(Rq#TQ1 G> A=}Kd^_D5Ta;,! \C2`r<&,,=ÖOy2nrK>5Xїd;T-u T(*2?2wzķPI]$eyj rWsW]u>͵E }mI9'08bفyVjR?a}'Mz׺}#$g&n}dXWfGY!e23{Q-EqA?9Xl8ͪx}RewK]֏f0'9RqoJmt0+%7MagIIao;1 0>C͚m͆-X9!ti+yF O!B4Kzج:OEKK(%/uDÝ!z\O(ښKRkh8dzq;'@(Kmzڍ_U缕LɞԾ1h3Q(lB@T.F 9Ϻ@I mt\Ngֶ` { ,!H9_:i~2B$XV"B0%B`FO R186dJGPNͯ'0( u=KuS5Q!\fWwr"+mZJU@#qR K_e>z—  ,k+FWiV89Br &ʖ43§q*1WI?qSa9ZmT{gB(A:G/MaRp @%ku5Uf,ׯtTeCGma}` μ뮾xAPRQR6acdҬm:rWiAVgi}V^sǍy}2R7ܗ/)\Osbmx"4۬FK!/Fd_O~p"+dՅ&ut~,#*\=">9iv#|h_Ck,Zi|ȧ.̷[FS7CsSf#–v}2UGa0JQef PȤ^]} l8ڐU*Y#t ˙E"`Iu#yXv&ShHNي, l wEF\o.cIJgBźyQ?d]j sxt (sY˭LU] >u4yyMYL Dx0gz 6UT. h246)E&2o= |-AsJ~CRBA儥Puw853M C0uTƧ-x4C3y{Y\N(-.#>}=  uhb~df*(aT(HX)TF,O߽D NXVE9m;pSَ-tÇոL_VYMJYǰ:\Er?*om.Tennpe>~j.~ک1_F@`9t4J?Wbc(q!t OH)<γ.<EYGarv+09VM*g `heIN[2fC[W>cf ) lQaֹjm!2<d"F4>.LSLSd&'3}U96m&jH+yTLQ^uQ$d/ڌ+Qc`8U>]DZ&8b1?kwT ̙Z耲HSb"dn{Hl҆JI0\Q8'?cɨfzpB;d"H,V50 QUO]YIl@rsB?kf)=yGMz@1@exS͡0S KnE7˧y(O>,9ȐDzpg e5OaU-,뼈겵 śԅzu@t4NuHfA_Wn's=6M}X$FdMh J ZR' d33kT\w6œF˰vb-Ǒ}wI]6ju9Шly.x6*P| ,;z|ƨ$rnYIeWUq!Yu% `o멯7ejvlR X+ +dI0ilz<7L_Ti xHlm=7S[U+fmmc)aGn'ة{_N.Fa.4 Emǿ2Tu(rg${e ue2B! 7b5~ S4SE@n9m&FHl]]*TL)2{w ?h^]wۯpKz!ރKNZe֝yՊ/p7Eo4G_ tϒ!ZK>7>5e%C9E5LOOg뵇!/ Q6kۭ囎`dzYwe@4Wl=TnY7=n/=) : ]Cat rkbPYe%L2TT@/wϡ>Cy>|s+ 7 yG8^.7O.w&=Jm1#ٔG`tII">2-4~D"P %7G; ߾<2 7v'~v 1a :V)k*Ju夊!Z7JxLFufq=ɘ#0V : seQJ>@s ѸÏdS TE>38LzHvK?^ 0\!u[pV_8Uf 65 _vv`m+mn ; kœJPXڍ4K]N5ǢZjk_>,76*JXZg8+Z)t3J*)A9.nJ $z΃RoAnSSysf=!AQ6An ٙ ,Y`l휭{_/R(cLŏp%YG6L*r$NwG-R\1pO抣)iA-Ec<_^tA{7VcbPE2gj{~Km]xu] 6$h֬0 RmmSlpqaۏt>3.=琨,L4 (w_'N?1UY2l{lʵH]yTJH&ؘ:"3# *)„*Xծ½TjL⴩eM &a,ۻCuY kF@ppg(O#a9èjsn_XIܒ1AWTH>3[X` >_.x=SfZL0ZQB2Co;/1,!n6&_3> ^a:>*u,^8h Fq䜽B]ٴ[dMҁqơT| }c(afA(ӻ(xϲ BkyWyq!...$wuǒӢIPl~pHd:3!yUjRCE kSm'%E M>sYyJAxЦ~QeY,Z +BoAY@ zؼ;/.Xu֬HP+;=Z3֖Rjۼ3*UF50]00'hf&H-h9EIMИ``.LDgpW cS}l|DƠ&~B瓳?̍wƻ-KJujx]QǪ* l)sҵcKo+ͱ1Inȁ4U*3Wi?so8+5TJ5]SNoۋܦ^9|CHZ͓?hAe?pp~1گ' gf`Pekmu:Uڪ BL,bT`s$VU=l92&CR1׫NVlǔ5`}[\gAYtyu=ʶP!223=XV̾a IDAT\ 0Rf29`3y k+Y1&~&1TGen:PW¾rcNI=}NIz**dh2L =C+&0+L&Jpv-q~ߌ2$r`S<<˓ 1!"kc v WKeUN"pjoڀ"0bR;nvnێ[Nc`5fJ7A2IEBtL&r⨪YF24*14i'٪ _0.ZlIk\8'/CŘtlIOjS54O -޺uΙSqj3^)G+OghTfjE*'&PKnßv.GAYx4r{:^n,3hRBT=2MZTl*FUJHtk/hNM3W˵>CL06M|_2Ħ\@Q6df*(@P͹N69Ol/(1Jd}6ǵ8q|[?-.4Դ/ã[E&5Kzf~!Chz3[˗ʰ?adZVPF`^| >uGLk{휣9˧Jxik.W~MP.:һ[w{b;=[/XJ.8w,'B`5_ALEPN I55(#M `#۠4Om}hTj u◦,bql@[Б)Mtl5SG\tD*066m^r[Ә*K:>Hbqca-fDG 4.R*IH:&*A.azVl> $g&O$5ek6իtʘ.vު9) $o4*ce*{9V.XP1*HA5(Jbl(`7 @RkDcRp)k=?e9y^~dxmf̝3ZY0ͩ mH0`=^jhq0V#*1;>m6S:GSSe_;^ N<"+20i'0`O+]{%e^P B@whHf A秔av0qA0q&jk5jh(Gwo.8 ktvlKψ&]X\?Ǔm.ѩ_1hzS^FoΥǹ|8@4kY5 (}3no])=U^ߒQ?OS+ʐx*l`.^7:W}dupjͬjʴGXLbbDU+_-zC I<5yjQe As{TP_cj;riQ0GXeS:m&:+Xo1pkYp̦3MG;R0( X֫73A{tE(Wt+Pm-iR9OVWWqq'.>j/5.zA㭱͑NZ1Q a Uo|7A~=՛l0|" > >g~ Y&"xqxb6^,(3m!y,Szr*s*NzUz!e^ EO[l*Ex1pׯ7LJXmlVUSy[ VL1*Xo;D+m݃L/(ͺ3˱LD2F:9V/Mݮ3HIH(;r\3q)\ WdmIe]s5L"fF3IT!NjKFt[6AT@9-.Lb*X19uHsxN{6.\IvȒwdzZrALS3).Ƃ: 3#ξr.^:owL 7B6&B-^믥` |[2~D/З*_Ţ|gr+8#10ņ3t:2lb`v(gǵ\}o%`U ^樰Zcb[웝ݡzѠ+N) *Z4jd#_ >G[Fޝǹf'AZʈw?~Z _;uhkk8,W_ݭo"J9W/׌x—e+94ɸ?QYT#y*Y={.7FWM?$4ҺjuYU=uI襽<5]Nm$xv½,kڢ")ڇL Zc`[`MfkXbR'ܻ*ۖͰks2MQI[YPucURQ. PyfkfnW]|?B|h6BS[Js%ޅ 5D:} j^z&HQݢۿjm37+usתv4YR[m+󝏼{VU,Sxt1O^gӤU 60:ϫ&R 6AY&j:!ѡ%B4I<{87|^o3Ǫ=4Pd_fI;(Niq_B{q8K\,,Epp'tjP| O}RxSbř);`GXl9aI>*q15ej5/t<1)UxU(~67TϽ+өgt=3d/(| ~U`4ܕbxҙLUU_btԆr+!e#O<-Dh:aPX$v R\yё䅵{fy(_EY@-6&/o . !Z3)Ir/2-5ʄJ`y}eTǓ^]yK+bU<΢Tѝ?y$s)sZu9M¶ƎQ#GUyݯ* v ~[؃oUR rNbJHRm(Su jTY4oTvֵR-"Dzޟs9i%)jQL(bUލ+y\+\5njXU?ç_(ҤuTJjPFevNpe&|o'EX=$!鿢J☷H+9~9Gf$Txu/;{{foݩ - x)lI9pI.Y't۽Fb#:.%#~0Ͱgl*vve`H$dgfχeQ}dQ9}l:D)DMu!9t 10(|– vfCM|?h*}IkzB]nݧv4I&5ʲjaJY`8D$ss:*GPQmz% l4 l(3̢6NOlӉEkЕa^/~ IJX,lbxڝ_r˩CZ.RT; /E?re Z Vctcqn?>z?Qt{IA_꤈|=uf%*Ix-}bt_a78 ǣxD&(̾R8/tp$%|h(i, yۯ'a97ؠhjOrU%nrN8794:Ucj Clcⅲ *sf-r׽UX`H*<4~ò-6M,͡EОc IGS$Ř X,s#,|M`YF|7+Ɔaޟ\L*K}䩁Z=}gv٧>{V{O7vF eƻMvbܦnWNx+y·qP7yE&6_nO_AG˶S[i$.w&=:IZQVE9P+&?8r;[iMfMio) f'QINʹ ؃_q|Zė"0W\NT, z^Il-dy][VoIIG!~/ Wּj5ݸ'ȩXmbQ/>Gw߉+Cn3sװUZb%yT;"r͟4\ٸ174 s_V!Φt*s2=]zf?NVďVoֆEVe~yioXx { *[eS4-n<擽Q"ز$eEJXcHYfyE$vb"#kwxNz(ד96~5^;4ScuW&3ÍK +;-*N~a}i2r0$f"0 ê`v~lbnjbM-C'1t1Ddxa2/Xo@gL_q3@?,}ߧ0!_XUH*XnբtUb@Pŋ(f~m>e'cCsSX0XW)_8 Mٮk~\P.V&C:QJa:$̽ (i K;hiFB#EXv,'L8 |O S)-UcV:Q#|?|eǯ\ttmǨ')2BsX_U '(hi9:ƔHJw"J'33P@!`o7 xVI&I x!+h>Ǣm !, "*E ^Yɐ, 3rNyطxV N}eVpE*0R+X't$Ep-5Y5Tٺ3Ij61u;kQeCuGhx0a[oMYPCfR{lJL&*lAF%2C>CC n;G"*]CSwgWOSwlrEfk6~tR/O9 Y.Iah訪<3pvj/X-!a4WuG/t+NÏ goZ0LnJ9n~zG!a%N ZEP* -O5(X#$ee8R:G|Y" GY KO~WslMQ(݈$=JsOO۵)NLKaֵ:Om5:єÒwi/if㾼Th#>vFQƧ̇.q&E¤(E+K~ΫJaff[m[2cl/)^xk;T ($(v2µoyЪ3 7MvCʥwr2")j*NRkL01iՆf_u?5"|4>ڸBf+/he_z .*#0tTƖv*!f&r1J#%1Bԏv 70Wul49&83e@͵ \3~JY` J3>zpJ)p~+ήlI)SP҄(UϨ* N) ]gqNe:*y{_MfdlH4 m PNhii@tЍl|`%J AK᫖-¿Wk^>gkO8.GYgt 5:&iRP;\7~OWV_M\*GlNuZqk>;;ԤV_ىg8_~oJgIu*)#96G [~^ [ G4؞A4sᾭ0[ۀTʷQ>D}?Jl{ܚ0{ֆF \/fy2ITOѸ[X1ۉ;F}|f&gъ`[(|(H(K4(.Z1 'QtņI| JN[ %|1ZcS v*qT ;"h>*(=if\*sQ?%zhb 3QmG tJdG{$SkP Vر}55 Iihz4RY de=/z1<裟ZG o tU>DբOTzb7#FQQ _wQ{4ᔌ^:A9-^\ZóP`2w$xLDFlPd}/Wi7aujF9 BWU2a`enkWQK_RHQ[<$F~Β5[J*D^Ֆ ;5:AJ Řd@ Kvߚl&CXW=y>mCdDŽg3m!2 )41}g[V61s}hN <%t$_P[nLx#1A]heC7X)LIpۻYs FFc| -[J2vf) +(L RY1:|R:AczCaMVd#/"B6f l6u0X߯4V'FM9c8g^':i b'F;8Gsa%hUqam )gx4 1**ULSXp!!!_ZB%I7{B*{UA}1>΅#w*u-\qX=V j+QڲB )k0?gH IDATyPE!B<)$o{:YhЄ%R8e74gY N0:߶'R8ˡk55L%4vZԭ&RKAߕ3SpN㌧,]%vMmRw2\94[fBYޔ.Fe@Z? Vc}&̧H*H7qleFO1m gW 6Y8le!y^[\u㫊u;y_՟QiN;贋:a+ĻKA!2@kTuV+hъQJ`Sd.׀rVYGey '9RlYg{bP(ǔ +obUJ9c>tS3;)r+;f聡l<07d,jc"8;ygΎrTf}MUcը`J܅Ң .Y)>ҽ/#Q)FjfЬZ]s2a<-7"|<]^z&tq5y8t/ݭN0m}4?%R=}[kAiAfH6b8(QZ*xDy蓢 03Г<3,˪*SQ[ѽt9Pgk'p΄Sy60Qu + |0et4̉\X$u꠪hFc2}I:[,gʠ;7?Ep-CWe꣺l 2Xud%a.e)&*̯:!R /f̘ʐ@NaM5)ڤ6ZIhcJQ~ D`v8鈯[ B)3 owظϤg*>RR_ me9l P`. TBtM, Wlb"V>j'4v m"e7P6x$$3agS e:1,Wω2(iw ;;:C1<~p*[svD:|jׯ?궿 w/fHxI/'~_ynݛc.MMV1*!3y#NʴU1**^ĊfD*>tAYÎʏ}tP+YF2]͓}јor^PY[J 4f`0.>-vJJ&Ř c2N0 A٪>e/Uÿ*91!8u9Nm%Ön}wX؜ $3jTب+X6cˠ_JÿX&p;'܁E2sb]aQ2}O%z\!sdžf OA}@+F~ BȾ/qDzyO_?8a/9xa:Q} 6'{pe{p{P :4ߪCw9,'+۴,M7=(xgjpa?pНIKcszNA7%gn-n|=f>zƢeǵ㫹@EX&u&NچpuVN ͐$ sfF%u4ٚqӼ]sɡI?j)̖-к]w<&fDk(jxnˈʒ1gP[[w).k˻~+WF,ٝoKf'7l?_ ٫*5wɁeS~V{nt" `t)&Z+fWnBU4V}5}kΎEDkS`1JG;(;I_VQuTHZY6)B:ޣl˦2l;F푎vhD".+͓>N)&yFmϼW|uhcufc7e`M$1T 'p%hJ&Q1afu sѱ)}PNlWtӾN ϊ_ .G~3֭a5ol<揪y c%COh*9*̵_ [^_aY)4^xձ'EhN1&A$6X^X,SrHXr[$vt!"*`?ٝ@bɉo#j".i ,SȠ>{[ E "!{OV[aKwBIXYt5@BM*Z!5] K70c{i'iLN*s >Wr{x Sθ]˒B>khnRdiBdYT@E`H@J__=bKvCn*mϵ F5qSs]Cs(,!_Z3*4~[̈́GxG?;֏+f{_Rzw^/&=4iqrw\!;c-n#^X"JΛXbz#7=q$X0?5*Y`TUEQ+0FP%whnLH)v~(;Uò2X{1@zAUOOQ&ë/p3s®f:Yʯ.$iԨDt1[9x4Ao9w=/?rSP0 Y1m2;/q>4 Duy[m}ϑ&$+#o[Nc0>*:T{VeH ׻=Z2NsPceU_Fsguj;,FHcdraIP6t[$͠juZqq2`|֖Dp+ky(r O?~WP|0W`m&V cI@%VRrB .~@k6h&_KKKK\vvHP<$q$,kIƤ #DBuey\ylIf_IAm29B)/:՞=6 ʒp fB- OQ R+,gPef'% U'JѕVMΨ0ۮM%gFVFcaRgתatSt6IPY#py,ƀ`B`9z76a\䬌]\w<`mPݬmPĆ4تVYPì:7M~ȷnGkŭxJ+~s)#:9Zǯ-H 1,_r/}CzfIpɋ6X %pÃV1j<]&Q@U7S څz5a^ʩɲ٬cX.1"F'(IL-RJb'P]D+Qlе,S'+c#OfQƣfǏL2gw"]4~UݛDYn,pbeyVFux2]v5 Ϊ<1 )sp+DykO? {z^Ό ZLݪکXoaY@IU& V4 ۢ%ݸt6ҍӬSV")6KJ0ƪQuW"M2Fl GCc #Ob&[TPւTxS! Oġ0a(tcؖ%#$ +S w/\w/8aW WbN**x2ƪo2 KC"*{*ƚ-;҆*]ب0+t\L7ދӓ ӍkjSʄZA(!g>=,] ӳS(XXw!UKuӕр"Big׾-vy/duv*5,cTҙ:ܚ8' IbVI:<p&厇u[i, 6͡ k~& =C:1/B^S$pޱ]z΀rd$IZQ)$ K9{)Hm1aLתYs1 l0Ϧ9z5r%X΅s'/EƌN'tn'c2+}6̰23R:ǣଡ଼eбYc 0U硇9/U+(r8ʒJH**JXwu1,+e*ACmrYU%x%O#>JR_Ka\:ZXwYk\ UFekm<13a8r;,IMg({'B/1!TP#,ղ9 _iPeP/|]x1$߯c XUSx<O޼E'Dmޕ.(oztuߪIy`^uQSp6KMv/j1o=/=a_JVBďa1D)U}\KLQ{b9]oSڮ,/& 3L 5,HT~s3v#F.[!>XeQ,"S્JQzcmȷ67* (e)r?|{g_ZKj;\[Ͼhdqk>ޤɪsIHH䣴T\Gw{7?dӘE/Lmlu4_+VrNG w94Ĩ׾:-leʘ1[7̱i633[. 5E0G;.tبv#0I֊`Ȏ n3{0dq0`\kOz/M`9Ee:nX$.n'i.[Y 2 ZJ(x !G3%UN.ʼG7}lAZz7QjĶJζ.Y8rT˧b(dniL'JL ͂Ar6|[eǘjZo V.lyΟ+x}=H /_Lw#5^z_+pVj kڛ*/ߋn'\szm[2L̷^n<* u\^k&Ȧ4ba& l˪0hwKsd&#u)3rxİQ"FvŁLM ᄼh壢IbJUVqw☃< IDATgŝo9슿CRt cBwȼ ]SVg)gՌE};M̥aR\)( z q$uGEQc<7173@9I*ج<:4(t`ٔ%)Ib16xGk}tdsҩOO~ [~=:)txo 3Ћ粶VphrŅETœYԊUR)0(gOabj*aj:Mo,`4Zg-eF^RGR(2&6 Ȉ:l~_jU9jLbx_Y(GpҮb♿/XO0Wp׀ %:GKY2,#GLO'==ƉNj "WKy8vϜ_O#EGʐ|7^K׮VQ7R&A35;1ec41|KCfOٶ>7M`X AruHs78+߫02Oy<:V>/²xeBWzD!Fc,.=_TB ت=ƛ=0{X0"*9g265WsZsLkhsw~M[n>M/tCrB&BP sX }_ IE$׏WV ,F=\noDEs5zAhɰg諤S"4 mu:JxRVBHsrXs.4a@^2c|P%$Ɲv_6Lqv'Hsʧ.#ٯxS&dН]eX8p(C.{@d{/IU U}o4 FqMPkVP F̺aU kQ1# VQ$LC9qNUW.{3vWsymn~r%uyK.x4o8/2Y,e:6\-X0ר˽: *2lufLpeq,, y"$ͭ /zu:%,9 /a߸QqXU+ݲ} ^CQC^`R/U1uFwXrwhpV@RM}틿Y]V~Z \+̵-ê)XZv ,fz0y) Y %A|.lo\unT\緷;J1,"S FWlYJ^ϺXeþvo.7G Xީ$s\|@a.';w;\s%#@ Ji ;֮gݚyS1&+:+Du Êcf&y0T>C;juWuo4C)qsESǪ@<ɀ]>e=g@0(ɬf4 Y3;e?7K\IYUJ a)TAg!yݞ߿m7lŴ˺S9m{E։@0s_ ӝN(˵%BHT3NeH.&Y6KרZl"XYW{4zkJGm|/<.x2T"'b„-{`I ];v+o@enT6]Ŗ~rvFT(:}CmTvJ(AHTRY[HLLplۙ's9څ?b$#*c JQn&m5OJ5i(:rm9 Aȋ8E[5^+ׂ> @,V)ܪiRZ 8 |;ljȶA mۤ<x㰒\sO= KFW`6xjmw\zIXV;m؂1#0fx}ujr VƎ%*NӺ`9 鄌[edʹb&pVnԣ5vBl?;EQBVFX6FE e3t7f/IBcC$:3[?{ }nv@\ Tob;I`y|M ̈́¬"";(qaubibk*I`FPdQa٣}l ^p& ^ƠdC߫/nv5 ~Y+w`8NSX{̑o7Z̴FwujXi&9BBYj4f-K@U*.Zi>\giMTX6ʍBP))*JM"bl汲LL3Wog:*̵ED{WG2E16~4Ͽq)C{3{"^wth5 Wz͜\b_7.gJڕOvu˕: ]"o6~pϵ3B+ͱwt/tܖE#qtP<" Ӱf -lMѰ; X^fF<,U$tBA3Ybg"y 9:Uy-< P,JsyKG9¨|[1,3CaƫZae/qh ]TuϦOK=m`VMxK0JD LۅYdC0B=ŗsCzxlиLC\ wdZ]-&Rue f*ZЊ0WZk6q^+ͥZSd9>88i}պy z5' Lu:1./0y;MܒT$;FfcJZ ":e#ʡM[CY^rk[-bm.x#,gtaQ֩0V],~ 8])dQ g߆/,<l?6w".@a 3Yf\g7;EurfM<˙* .65N>%CUS1'^[ژ.Fw1f h]4|C*sS)JBH겇ֲ!vw/.cfWy*vd?|AІ|wCzq w?YΎYOlB&&[d:0 *3&Z00F,VEJGw$_b,W-PZTl‹?6*`׎~TީhJ2%O\SAshRVFm5t F+CPJt,Op/XN( 3(]9.GH^7 Eс@0>םâ-(I.'pV&yrVo?*GV\˛>3xɷG%Z)P6OV#vNH=SFAZOat7J#`yBeƌ3O"B3 CThqؙu0ɫ9)fҖuyGIl܆ X6:jg",WJp"j˹~t4GX6LSE[M΍")*T*^tވ#$+1:LzL\JfI%A{E Տң2^xAⱗ'|3~%vMucms%wQ"MSDXFu7H<ַ@y>2擐+4IArqYւT,>k\u2k4rTTgZs1ףm5Zƶ]i'&TeI/nvn5pdKMksNzυuA܄h5,Onf 0PE]Y1y,i&\|Ȏ|+8 3#xǎ3tΌ6enr2.0$QHIV[ 8 VӜC}uL!^AxաB9vv~,16%xU3nU UKYNV qEgQE*ss&_1T[jZHHE5CO1ڡӬȴAiAh(̵1V8*€2XcC9"GCCtg:Rn=2$@Y_X^Ƹ&U5]Mܲ'mVZAe2pc\N=y'n|_ IDATީnwH<`YZ- D@٤. F`#{|Ⱥc ksfMƨs&SR_ĉW/:iUl%f!ʵʬ .PCMi47;// ,khq[bAWs, /E;sK<9'MPՠJ+D+ ݎM*iy9: .~! U6ۍ1௰c_vXm jT1혌ՐG~(3Pfm[\٬(+**rQ!ߓ<@xRLc,7RP1_~KWͩҴӢcZmVtx5fw^5ryϚz8xo \s]/]K>xC>x~_g( υ7/~{[mbY;eⲹZ\^dڷPS3S쵡w1G*X.Jl u$h ZH"ѻagbr^yTdh4hwǙv,*-y;*2NVLE'7ъ I^'Q % *s##nLNTI+*KkC)LNogL ZyA\P544}ձ^Nkӹ5 T~> TG|vcGbuYтi.V mxv זB[((Ot5CKJ[hdM.ZXTj :"^C0Z~SLu 'OĠ_}Mox1m5j_ܙF7-=ZiMyMhxڲh;]S=奕Ѷh:N(tm7=tyh;Tn OX~,9F<ϣ8ȧw'bPlߵb`57RtenBfJV DOW9\|6$,^$c9 !z&}J*4Ь߿χea9 !2 /|z}=b{Uc?#fYVp.Q"[ e \2jbV*Ϡt *.{)t,ړ:ApQ\f6XcBl>^P1 +|~yk~mk>GncinC;w_e6txou7=]}a٤DrCD,z-o_Csc)H*c<:vgBr;K2𹏯=i&Nj%*"<8L_xH{>aUבNTiynvoؾi֭ۃMkϹɯ)3kg8mOz"T\usKe.RInmQgҒ2J֎;ԯ_&.ȧ; LŤ cƃVJS^ÖOm|Nd Ƅl`leO{ޟ~|~W5,SM6hŃʣRu[;t40S 'U#LO*u#Kr2]`u^sGڟ`$D!͑BsdYZ1c`ɕo:31,,ޱc`6j zG@% 米nfPy26mH1ҦOjp ]e"&zP(kXu>aϟv.nXٲ̓?mS@EPJ!SNP T+=>(.5`_,$Xb$gl:!x >G3 )pUܻ@.#Emq`"M|.AX[)s\ Ҧ'*/:|S/ƁZN_ޤ,cUf[;rC,ti)ڃvfRMo& +ԠZ&.8F+ѐl^bqx^p%m_"~ܶ-YOO TA'h[g C ~t ݅`ȳ, ŻVp$!UUD5, M:h4d9_G˞j wqJ+ a6Ɋ rO@7 Kf'l'r-╶bI`X V#\ptGޝ~bIjL,&qhpUy "Mcw0@/`N86?W`V.0S7t|#YvNSJE)%Pvr)3jӳ) j:7L|VC|'d`fv-dc@U2.sß G7{~}sor st,rώQwŇ!FJy+ (jf3;a#e=c11ⓇYNޔ]4Uo6sMuPĕ[#w y jK<.hn_]WnEwn 08}SQYibwX*sV),R*Xe5LʲN[gZ/[jX.KK+# c,/ "R8j+$eY%.T^^FM7 0RQnހ5Y汢~173kg];Y}&e?ɑw0D멱œ.|8 (^xk8C̠.'`α-XMV\T])] 'S1{95տ,,Jn9td.`}JK&s!]1Ksw}\YMэLѵ;Zi݀2MsR-0԰tD;.Kf V{5v蚜RѮNʲQRv ! UfIYVh[vUa9J+h}"t UBT@̟9M7jjX6K,7\C*B3B;^';hnb|!#p9s~qx^E Pw'SNz ̷TQtq$0T,!~Ï|q)om˰U7 (H`.Ɏe'f@!`[V1$GB\!AI''zfKZo8xͳ<~xI+?VێJiFetKCEJ)62Ɗa<^`'c_/Y\qsJ(+I9qXΈu'H(Q3?9Vx0b3겉XmWY1B: Minsj,&'9jHDx;ܗi ){PۑۇK[پE^~yAᳯ \w̫en۶Ҳ{r˛zLh)F4l qx Kr,gI~$utۺT0abJM_|mTG,ro.w=*+vg(_Y'P޳sӰܛ5bCp~R.уn|('0j֎zP8{5M'r"֠\dȰYf(Uo|{gJ4 P eJQòAuC>mr<} e5L0֎A9R<4W2(7~ufW57) =eDQ :|F` %$X6:X(v FPeLt #0 j`ѳcr0M/۳IaV (Uk_Vem.eC8;t E!8\PA$6řC+z?Zx"~4 ( '+?mhǟ"kmdna,GP֌,S$(Ӝ"!ViFPR#>r S76t1K'vUt'%_Rx?/RFr!s讵3{/:s*ni+n^7<پ:|Q٠IIss7 ^]e{5GhDX^fiM+O[TI}A"&9}?[̱u/Gg~|۶ddcu2'u9m+!"lq̬3Li:3cibuo"= }Y:s3Q'iPiͳ>AvYRLzزe;}~Cގ?xzZ]wyRYVƶVGsQR] (,U2=}'֔4A#l{rIߑ5 ,=e)@YzCd/Q"Lf*s<6g[y(f4\4)N>^FwηㅓxcTY+^90d>jj|v39M7oG _}g#0{n2N]èąg}+735fvœ-ԦUq4jp)⪂,+?<3B D`f|IR߿tO}U8J<2k&55vRY.v0ߓѪ0w7TJf~}G+GEȁ IDAT-s 13^RQ 4=)XW*in#!.jY?"ciU+ymRNPю]UaT q} ga#~G}[s Gccx> ؼ=4P8`>yo6Fh߉#Z|#TJ0%!#EQl0\'iaT=VAxKo"6n]k5;Um

T %E*ٔPu7P S(B@Dr;z JU`,l+Vu]|\Z%/3@luuܖLIsƢMLB9ŝjfLe7\"sQkia~&E88/-m ?d믤*o͐n={{MMxI7tJcG+v{aEDPcfqD)i>ɴ"K*s\ihǨ[e\c%Чm.qܜKV/Lݔٴjp\[h?Ѧ|be:_D,Tg/4xDx&K`k\ˠREtp[Z/Nc2{c |=Ùv@[2X%fA<+n%}vi`!Kd;?B)"z@ȒOϫHC<р?j7o";Ak4P]3k֬ףW~af57̳v%˚ A;3C-nOvW}qIjh͓%#dhe]/*h! 6}̮7ad:.x@޾wo?uoEi=4`ym GeDP ]2Ҡ_AMZ3zijyY7Ws ajС)[L)£n{c%t\/~O~]?dlTZ#!3{NG%5Uwn/O &hbkc4>=T~G)v8V?s/ԁGwya=Aw[0&uyvvmCUmclHhA6/9LS`MTk`DpYjJO9:>n)ɬJ8YexP{v?a{ayQaSUI@#&NgَPunr6$DۆW^nvN6o~..jGRchnr0Ӗ) /mH) ꭼ8`0c [Crg2$:b==Z")˹c*oIk'yO?L>a )|]w'\wܝkٳgN'-0}̜Ln#?\YǝK19~/1 xοUSxSTN,EN8BcԸK.W)n TnMG7&Uؒ=y' K- lݒ'ҕصJ05:y)'hP:EPD{?^.5Emf:sd,E`^VJ[H豯NThA4Ry",W+~n\d35,("s`)iY=R3H9/oDр?QLko/֭ە^ X~?4,+X[7{Bdւxt y|VJ҉WFI]4 q4._G|I:IH&uBn%@$)EbU͐a"rݟƗ=l Õf8 e5~]~|WaʜnzNףf|?c,(#3~N$tQZCZEj*GZaH]&7Bspԯ_HC1PȩFaNt0MSF^tjqwǝ. ':!V:j;}ykǖ`e60@!kZ WtrW_aΛWϴKWbff.;2nZE1i*Q$օO $Cc^U{ o.~OpW'q\v(ozc'Z# ՙ6L{P Ox/cfʱ}MWؾNȘ:2WXej%$^C 顗;$ip b™U.bӦaƽLPa9dr.&bN01;Y@ÔVf8ר̝O֌)u_WkXalͶn8XF x\47&#? @J4:э189Q>'GAN*WTb򱟂lW|F$N"Qx#tHtͶkqe$p4>֊880cb9o}nQs60+ls-^%FļZUֱ;bXkh\ܓu>PG)C4_`(Vڔg!CŃ^͋$k18cPp֝~Q̿0e|IԴ]D7, SSKLJ@̯/ 8){f7]fKu[ S䝌,j˔4&zeU1 VGrU\7'e &@364`nytR/0hhfբd;kZ:jQjmu;FVH1|Mtd1jhPIGh)-PYOYك}m~N?o3?z @sQM@eEYV>*!͙9fT% Di ,FhJITE=,' !aEQid w_N8C\5X ꞔmK+K,,-W<ꏮ:R,i4Ml%Ƚ0)?3[=abQºo,Ed\w>`h4REhꢲ ,kp7oe xt$:H@Oۥ]{:`Jl=d6z\<4]J(9ۏUw RjQQnXO_l JT1^3,'n`ynn˘<5[*w: ɯڵDgvN{m^D_" g {fʼ^և^t|f:NFЌ6Q:ʯ%>4$%Hˍ¬|p`Pݣ3b1#E9j<a^OLfZBìt#,ƨtL]cy4jKPY{vO mKnreĂCZe7CVĨU-'>N"vX>s͑2 ךW5^%!Ьd%/9)-q; t>*Jps9n[ݕ[Qwn׬ ʊgvVZ?X}$, v`z G*[c xyrrYIGe30-MVR9 ۜb;hyPa*FV#ҭ 1UCS "D>کǯDos!1kICsd*i\ RIQ N  Y{O)p+neFPC6 clW9BSx]+.N1F.vʎ2 e(#0יŁ*t\76QxLFǶ||܃0=Ija.|צּK_K461C(C%Zo* pPq%6hs,Y]c9B]''ӻM?{ښ`ٞ{ʓgYC#f]ay&a٥rIw Ί.{n3P؉e;`:(I]4HBkXTK /{c:T=G\K0-r,'8_|{NR3^haD&1c͸Zţap!`Up\uk|T~c.=b smwT5QԴYcUzt ɾF+rM؈QDi\ SnT7nK9ס&<=[⭿Z](cK/;OSyީmHROޑB.{m \eeSGVw3rxGRIeHEjT,&h-*x*+eVǶnDU&Z1">ۘC,YF,=4(S nCMǕ$ԶBe91f"b;ґ!hw=1/6e=cS L ll)*Kb_nSq~eqg7zL]k&l"F$QX+iZ*ֺdmUϠqT,kI&-Eji,.HmULB)'O̠`jb,#9A5RkkhFf*ٌ  e+-ycFcC|˚}oE*U']ݨ.[)hu &sN`$J&7 x2±ir0G,w;[ C!+吻c ?9桤F̑3%/WȔEXv-gHNBײ[PǭuvrUT,Wߛz <Ze A]$u Z8KQNS@w]*KpesT E5l&w]jt"0Cs{t%I ;;W3f*J+l Q*cWG5(ĎP7iP ^Y0PzcGǕ0/R HP$n 6mަ鸢c]eaL ,?i'Ew䑪wByܶf ۴c&khNSC Xo^(i1s7LL/!H&3LbrXLfFיAPСiE:ZO9 EudIkƮC~T_A߽pd'SQ9*P3ukZ7% J1t%tJvI&Guy؜d#XwYlK]j֪QkGXD ͵\ C3&I)nƉn,Zᵖ {f XQΣ*G9c^w"h߬7eLO1=ӧ%CkY9(b(-^&\Lee$ME^Dn:?8io9)-pk4%X^s{'Ɂ Jk;ۨ`p@f[~\_tkkUЕTӒy S&=L2T]S,Q*^ul긨0k-UԪnȫ͂Q8'D4v(p0\d $k9`^G`]<ӛYḙ 4/%6 9V%5VY`D+^\2)ʊYhօժ.UӴv;_0ыSccVms 2aJ$ ) [ݟc\^~k39#Z}ILA"qrm\Hw#`.2u++BCض-m[UJ8j8ؓ#,J4B!}Kd$6>˞C5=D_)\";mY` 221{SX=EbH/)I_Mt3[uacU9d""E_o7ˊʵג̬)TY̛֨7n-[x^Ie_0 Y6,3f';:'Np> |_p]fX۷:6j"DUYMp^ UFtal铰Fđyw?3~_-i$6(}f{)cUb2.^?q>_y:o{lɤdXI棿y\#g,& _J,#;/Q0 ".,겵M¶DK>d<kjhxk.t}s]Nd{ 2 c`_=_^0Su7 cnsrX+*WczЬc:S!b[0{-Q8¬@a?Θm*;8Ö#N7=ٕC#]) mNl1~GGV`b@J@Z%\i~ wwὭbw's0`_^{c.3>.+e?FoۭU%'^IYOU٤Z<뢧&a3}_<c5nw\M6q-v޻>3pA߮ %7fHke?Y3~![zǏZg57!6Z4\7kv`^r :G3V:Ѹ$?n5<[n(.-V^Q@<)fclZ`!9r9EkfDZw벣EUrnrrIHL&4겍 5iF[mWzܺNybqfFE9eD$KYB&̢Dԍn^u(Fcbt?'{{t0NnRLU1=cs 3=zyV\͂ZMXYZ>ZU%AP``q'!xϔ cm9B?9o/`>I_)(6d~Z3?VAns ͚*MuنU\PE2ZzcǤ4lcvT0ϽgDy25IĚSqO3CY~2~N:eZ @裏TۆG΍_8yGn' {?g}5AX?{_'fzi6Nݠ Bv H0!fML1 Nd ,eauRsr֠IP.²ġ`ƞ&AcF",/U =}! 7phݜwMϰ*ȴ迵ss&\]l!By'=/p~38B$v{ , 0wFi3Jv (W8Wml\p`Qcђ.~|ןz< 2Vd[ Sk>uٷg;u!%~>wuA9ߟnY/annú}uebm+F cz4[ffn̫fͼ֒]v̒ǔnirQ7YXp$ѨV5Yr1PxtjpJ8Z (V)-xuQ[},g|ӒFUh*tSu0.94}J$R7e=ۡgXeZ]Τd$&oTDI 'pa_8*FD̬g*)5vVFtk 2Im  8ߟƧO~+A0&ef{׀2cX <KnG#$uCQ;|NF\ZsGY_^|?%akZ;<ڹjEc,x[O.n% A7)IڌNGj !0Wӹ 팩svWS?*4JVȘe(t:Ϳkw>bktjP^\phk [Q]%%#!ȫZhqj&QRQ`*JRQwc 03rYX{=#+W1ٝ`riyǝ'w=E+F㑢H:+'b;dp6bVTTY:4?ro,eQ]F4TfWTgLH>vewH״Un|0yX Qd2 QM1d]3`8y橖C6=k`e԰D)M;L0}Ă-k }q!l{65; ˱D.(˺.ֈUw6!5)F{`ϰ2(Wݎ8|50ZZCcRq^:x{ݏ{tZQޞy7ϝtNũ>YV>6X=>as`} |]X`64K;f)%^=u K^`P :n!&(ùJ.rUI'5+b:&c눜X^煯9e8R/x!NX}e},I ('$'I:dYgdV EX5bLB K[I7Yv;knMc)w|k:*%^gcArŝү_5,L2H]6?ib6LGx1lUsWY ,=nP@<%^3x,3iNd7j@ ɫOq4D.&&EFeOQu0'+ +JSy߳Ӗ-e-`乎S[l{ݱ-`$h!V$8% q\ˇyXF,zCO|pϸ+0&!3xGpE9(dIFjef;-X,2,>B蠝g3ǘ?Y3ro,$c?DrۧwXVG(`*nzb^;b{ Т$w (D᥿i`o[zmϲ +D17od.a8TR%Y5l W{~o?Gq5.//RkkH%Р;^m6އތIXc*̈UGVxAG3V 0;'ѢbE |;){;mfцfX^j?dFUHk;\]O'`F]Fx.$ Y\M*/գٔzJcgZO=E{0=;@jFv ۙtwI5kpc-3r)Ify(y,o=vosWcfXe`X2ard 6Xxĩ~eū^pa(9х?(z=bo )x̊$3$& ˫i,t\q w7 Q5-BOb֫֞Z y>NJ_Jfb⦭;طV6ڪ5cXfd#e0[.s!\}Q\:w'<}$<$Xԭk;Lb$bTu"+ |YM[}QT@幹-[ʺ.affؼ4?GH}mM"S}e}y%Y܊7{Kl_FQ;k!^S v߸l'Ze6RynQ4#K3L6\GI;i@+e* 4,9i\P}ˮnF[5ḆemU#T.|ez}rgu $HɅ{kL! ?}g=.\oCS~vdSVwSZe-ո5C+n3/~T0tEa]vrʣG;1xDH:* Πfv}\7ϦZhh`5$60GY;NfyTcex<8gImoa,S?{5rw3΢$fo֠ǂF㉎n\|g&:t)nF[g ۶.$jm'%K_T 8ek! xӯ^0zL 'V8k#aZ=N"T &LBN΄dS ml6ƥ A786$,( gXr|?]t㖯|W^?sZ0wם@5)}3Һ~#&lڴOcظq5֋խ䋺|'EQwFg0r!n`Z0&6o=Rt.sSddMLa=. !I-ncuV.,twlf;æ5Z$jҔnޡ2ݟu9y64 m)̉RhZShS*331î@_JUdmF:VKh즃x5C)YO{Oy`Lk[} iaNyiwrhc Uj[3QS:'b?pW ~Ά;Y?4yjJj)I:^e!T 2܎ ygDZV .D+qY,vi||io<5+'U%muZnུ.Y/="U BqĽSu/ ls庭k{(;Qhk-h'2|ɿvǿWc@>]oULf=4Q]Lfw0O3uJ *4.ǒXgGl$KEcmK{όDJ#ˤ *H DyDat7m-ٖVY@[Ӥӫ&Ӗta Ϊ-t*XR+X.ctex( kJ9¿nUI`)fHHnd 1e81m p>tn}b(ܐ%o3/ktPRY4OrAϖ _Q1Xq,\CL{1IIqWQ0|՟䐳N${$6o[OU5J;o5>z$uE3EfdG[!KWpa.'7K*ͪ +nZ} ˥,TC﨓nR/dtzQ. ]C=yyPQG>˓F/f?|I<mkby>A5 p}9f[PFƒ/z]/A u, j{Q"ܜ{ӽo _>d672?ya=!A9M1r$&tC ﯆d;jmf~㫾^?Ǘژk;FVsfAR(C) B(c 6 tmpmO w B5 Y֭{|,,RM'4 5izi&2>6JQqhL/5R5TSc]p+DDc: ّg!l=M)}^}nʼnDpQoA :$LhEڂ$q`VcT"l ~ =ƴ+Ji^Ŗ*9C|HN9hSO 9<%oTk9ƝLy6..:~ۈw8ㄚ x]6sDrj VAfjS]W6wXĦ05f,dY ?rqo,=sy޵P-9k.nafd6 W[2 V'$*@sEex)(PX#4z3 7ʘ5ai4#4}ȩ } 5~ؘɃ%#V$:TueW@bxBerA&X)ai0 >ީUrՑQEѯ'вUcw%Xd<\1@[eOO֐Y)da\nwmU|ƿcnv4M` O`xae0bii0,|q"4,#cs찣QiJ)"@ۿ<o-(U773> CtPIlLP/&&S8zoHmJdI yW/%=ٶnw. 7֐wK\G P 5dY`0KtƸ3E~3g탨 ? M ] *LS ܨ\S5pZQhːj[zz$ ,UAqԍo{R&(JaVU(jE #?=?}c s{K,H($w(mv3t%Eaȭ!K,Zh0˜^QcBXZm󘛛"M-ibI$EY|pD<gv0bCe\~}exl <}A ai~q7Rf kPڇʻX]O}a )oz&tY nqJ\wU$Ym-ZYN`ί>jr١ ࿜6Q+xyZ0o`J[1'q߃ 3HsK1L0cHEQXw>E9O?m;G .̷BYU~}v?%BG<>y2Ob4`eP Vk$I0, uo Ye'50F衟g<~|vH{]^ k`uNg58׍ : L25,4mX̕RձEbGB$(JEq4W?8R/GHnrѹI9orA.Z v{9NJg)g3jjLTe\t<$a]\}NnuYL\u^Z Dꄡ*E$(J >EY x~>:kg?`n?Aanu9ɭ1՛^#m: /Nƌ˞s=W* ZFd4~<ע s .iT'=aϯ;q v1-{Nk9wG-pEhE|R{ћmQ"r ۗU=Ͻ,̓7^ێz3&Xv$Iݜ4O: 67莮UfklT{$$Lc6d3bm4!>gIg^ܷ:e2AcluMƒ3Ջ=E$ŧ: IDATXVpSw|Cxdfٞ9fVKKwŤi|y2wEX&ڵU(As΄uK25^yϺfgc!_cuAa>v 7>V=T+䨦e6~OFSĹ*Ν$;JlT&hC޶e(e}^uף`ienTZ)ѱ<wR;D`Ҿ"vBaҩpکj'jYOe|uP>DF1d_;;ʧafN')d!dYmzV^E&% c͖&dIݶS[K;]k2Wa՛ 3FW,yץ^L4dX"+#y>IKKį,uqi|^=&YTZ+J`AQ,!8(JV%-2* ŝJIe-u6ոWJOmX9ÆQ_d?)̡%_p>ss[@yz3{jeN8GQm8k]yxݟ%nvT(&M>0cz%n;SE|_̒vzGe0> ;yx Zit+ Zab1nxe2C"]͝>x YMBaM ˜m@o> ,aS@WGy@;2`%԰켯m7"$k1RB&x qgVfe 7:eyo=GGc V4 긺t%Hdq82;7yE;EP.]̽nFY$yR/Z߄eB3qTF46d)"#Őxzrm{$D$:3N2?}raq%sU'qnK66[?c$ne^'N$ :Ԡ3!㍬R[.^Y6yʐC90?t :$1Nn%r,z ZXY^aO|ظtT@${V\+a%s׾p5ô1MĚE#u =rgs$X6 `X|%UeiY3qtKƶrHEU!(Qx_ r}z4_E,L c{X5 #ao`QxFʥ~gmw ` 0*PyaJ Z 3r!Mt| -=;qo8-OTۏxJn+C/V1I2ݜ*$SxuųR5;(efUߋwS7Zsqo}G=3D',sC)[ vަ15\ZrxשS҂fZ4h54H[o)C(o8F8?gW*$\9) ذrC-*6IHW@*Q)u!7$,WVyc&a&CgxgkH!ML2Yh:鞉Ƙƚ\x?`ru(^8?W'Cu,M(g:M C7dǵ˿y|3Oeu;\Yb6sG=<)].9̪:d*CFGe 0K/vnvzǿѿ<4b$q 1N\cNq&x # l QmF5֋^omE+Q:1;̠3b3S{Gn]Pq;wލSZYg=#MWr??/;/ܿ˝Ɠjslh͘j54CS^|ftjm']YؔGrl!ɻi.8nثzf}6GkXuYԵ.U[Vܭli^iTK<-N(P 8ryt\})?fӲa=XZF #Tf(X3X>o^eyfVrUԴHDR&;a'Vr4 ǙFevVׇ0װPThhŜFA(0L7fr/ T { Li 9WĎ9푷(̫A^jcܚKGsRQ }/:dBw d˧j܆7LWOz.gKoGl+UY̵ׅ"u4;޲}>S"8}\y?WJs]Ru'Ƣ0Ě$RDHUp,?/ezZ#L+AlX?&&o{@޺$d/5E5b*FI5,s,G<%I(%&4PYzBVjTth5 YתY^:T sj sWMV6ZAsT ۔c^:Ai v{ u$yCd,o9<IJM T th]ގ>xfbdq-VNRC/5L$&(MV]g)aYĸ QyJW2w_ ǿgW[mA37=9EO/OOK1(ZclB#8`Ws3ԑ;*T7C1fRTfqARZyJ` O>ۿ- \jewY^nAfcH7!J4R KM.%t,,$6 X~=h,S)EP_ږ250юc/nO<_xs3wGPn' F9sYT禴.Ab]>~o5El$1us.3 Uc,԰Eod.= G>aIlM 7:ØF`qഝlmhyXe3ONbCpX[3yO?W߸F~,M-5,Q{ W\1L6E[c@W+J#:FeviVLRvYqU M )EЩŕE:T?Rmk*8nns֨MBOJEȴ%3C͙&Kctg"07}BuhUB ܬZinX#umz;M\T0oMB(!F1IZx -XCJ(x58;tmѴ1eey2RIu̪Z5~^+Hpx*!8\EjXn6i8%/0rCD<7sa\BT\X{yyR}aw/oLJXd}۴׼cq`"; W#q9?;V!J(r)V뺃ig ݏJҫu$l(Llf9T!; T}'=Q,[]%6gq@L0?_LMekTխW'_~kx!GΤslN6dTc291(d :n#ޭԉVh,!R`UgC#qBN9\:6'lweSteX:-Kʕ,|Su;s&\V*99t}y݉'i{78ز<1'AT^dJ1bsLXmv۰\ 2SlaD4va"$EpFi+qhq8 j>1AoO+(6hlFa^ ǫme4 WRTU$(ʓaI*]yɣj\',UUG.W&Q%Pg^](K~~RpE ѫCԢଫ崓743kX^4♏ 3m 4<]wA)ڥr|6GX.8X=ghHR :Ɖ_v#6e9|-`UJ]Q-e䈔(Y=`0 J}7my8']5,OIR&P*[Xt,3D3"'#!Fk}mˌjVD$d[1J,p"1֘(Y\ L gf6֐qVff&X/VmXi/5ov]@M:Gu9Ybh@Yt,E9 E0؋I剙 h)pl˖5U8ɀYZq;7nwNslnBeNejQsZ^@"%fZE{k< $lARr_%`4ƮTSDt'Ȯk?nfa9~X1*pf x΅5HQ8X-qɪyx+f[ٶ9pKލ,j+W8?q1'NgmڲN2HUi4EL(˧~4&T+#{{>;=XAٴn#>//Qe&ٗ ts7_%W7K˰ҫ7H}<=գG&dheȰ F QjHVB 8@0¸w\QٵuO܂h&Ulb}ҕ%<'&fZ|k7?hs{i3??O61%Vu%5*G߱w6JY臶׏xg ()95QCr 8PG`X"IYb Kx,@:1Y7(CFax6s+5# 86 zBAræ"0G/./潆V`91T ,z_7r],&'ITX#4Mn)ellQ1|AkhilCjL] vKlSdU+SբT%y|,HZ\cR$q ,ςkshDڪstZ*sQz #!rR+f:紭*GeY`felWȅ`UxJR wݧ*T5sX*!rΫcX~) R|a#b'=M 8d,v`fPz&R[.g+K̈j1Tl^R)bWMNk9j /dbج[ s!Q1ly]ϥxOoX8 ~oSQ;[L̷NWѷlxf $$$FuчhQan5,XYQek̍Pal/3ūw[WiJdN`T" CdO~i w1l| !I4eQx5oL/dtt±<`qy9DXϺˍijB{X=c﷮$ U QGUZ(g*+ Ir+66QYm݀,-RJqx$B.ꑨ e+%ǫ!!% %v[vvu2oVC C9# IDATJkb՛_7aukH޸6^쩕uYyUַPU2xn U ˰Yb4x F=\ݫ;խ Lu)zj2cg]Dupx)ITe"'z]L֩f&DI(F6x4sVrIl8Ṉ֨щRѰw-qPt(:0Z\)uQZ3iQիwO1aC\wb*aբ /_rr~YSKxنMl9u׾yp/ǻ~p:If=ְdQd117$ 3n6mY3L.tZ{iL\VIཱྀB:=m?={] '| ssS][|ŋf$U~ȻO|CMN9r5N+[F6K`,XY2XQex\q㎗L`9*ͦϿ_ evէnґCvGX߃{glͯМ =, n{ ߰ߖ zיӫYJX e\ x׿\}\eGs(]UpF'UڣGe ̧H(ׅNn@1]ݣCKDMTi:dXYPTl~_̿CJC+t' s5𸗝/A;6l]cVe LGst=5IWMU*S+YC:[2ɰ1 $^4mY@A\ lfm]es%|u<_7?323*lӠrU+]Q1T'QkZCw\uqXJkx~7x |6<^?[q:~"W Αmh(%"!`nԘ-#]tuK R44E'$- tg&ː4'{åfBn0IE 0Kd%޽*8!M'oso@2c#]u Y0T^A%aC[LQ^ y'?7C$ni)Jў eeyĠ3$_ItF,YؽO`2˃\rcHtbk2.AYk, 9oyv`# GϐiX`I#nLqjX.Yl(²Ԑ,`A(bXGWt eL>Ul[ Toqet,`%jRQ>Bs=e찜>R+F8Q-AKsh~LC>Fu`;? !خ~> f?|v~öl-Oy<zNz&̝p ~3W8A B!0D>P*XIdl Z:WJ +VZYNtM˖NJj@Pc\e1KWju㎐Q cCԛnM7 &;cL26 \uF[*se7Yg1%]UV)tm (_KVUƲ&Kr2+mN`'E (y cOUӷ}²7 0z  $U9qA]2࿺I~gxпl[<LK;5r(֨7ʺS@jZeKʭ0OJ xü%5 Ř0٢}*\K4ꭿ{]ibBMӍ\sՀrƾr2ąnB/;yuP>{ϐo'1 5.D: >V1rLzNNC;NTg4J!C[03 Ώ9,AR%HPW q/vg3 Zb}=\I2v5r e8B G0`XWv0+-ClK԰DBw%Xصc'?%w~~w4@uO vnv6[鳮Dg ,cPGP>0͌A͵.K7๏|d~~[37`mPL)FCaqQt46Q ¯*uY٪*z1׼⽬nPLL[S-׼b x40w eZiTL%(RL*gߌq f5L?}ߏ=U[rF$E++"\,u -?}-*Dƪ*7 UV*DLOxrMY#l܀fcJ߻^C7?t{g1M_Z:q ^ERCLV8g:%,Ova%K I$%V:le#-. Y$A#EeNR`Yԥ, eB%XŒ8V;v:x.}OV=Wy 7TwH!Oa0#4DHfdv'.J ÖVݡ>;y?6Ks8JXuRcYT9 (Y>~ ҟ|F{VU uV|ӈ\g1=S }ێ[}A$ʺ|qUG '})ظ&" |GHv[6erQ݉B+/M%eB5wY}X̺\~ڧy;q9fIy8Ϩ0ֹ[~zT,o 2gb~n²h5'5њ fZ˫}T |-EʩJr8J Rvm w2y>N{wcd"SlW*b*p.1EJ߃߼2Zo!"qU'5y_xxqrXT)ƺb3c S$RnrPI]bG b,bzrDY.m ߤcq?Hc\oHc2'1b\!-zU2{uYB0hmԬuKVg|dl(wB**uY{+ڑ;CfsHE:RH| [6nG브5pU2$JGY`YšU~ !,nNoT+u;){jߟ%~BW7Ʉ1#"EDreOf#ǑA>Ȥb\#lBJn*0Ϟ]vF;ҷ7֭}~M\wX{W|N'&U=\uڵ{ v^WK6x}-R:Boٛ%v+΄=cU1ޑd2 ߈8C u{yWnک׭=.5L2Q_y&ķ% bZ"LB"$7P|%5\CvFl_/{̽k#Mf #r<'7d绐51 םX3IuMeTN'-" 0H|Q?%}Dl緗taE:jZ֊s.({Dx-}% JeylE$b]">H#<,[iɌahhz,l[F#h85`.`]ĉb|"`9 x`5dyf-kݜcm3ф=}fm(OJMu梩d8 "!0 j\ ݒJ@fyrHp&Jo1Z^g:>֓UKKyJܤax  cl_{O'y0?.JX'_VjDU^uGvh1wGRD}or)GW_NRCY"N|a?>q3g+6XSƧSxʭ|>ȵĢELZ*8& *;xŠ~+4]eH9фcџw>C_KKM$tY,]^IJ"EW #Wh͚f19AJ<U٬*;ߝ`mG`&&eE_/[( hn`4UJIUJ,)B"7?Ci`VB [_s &@sEZ"wo~JW&?{F[ b ]hpka%\/ 'Бm&wd#|PωcIo v }pf;x4we-i1%4m"̺"xE3g+q咡 VUCa+U8,CHSf;3˷=Yɣtc1+؋A6!}mp=s17G 1e6}>K;l vֈVyc?m="EZ>AeXr˰qm]n}ikBO^Æ0frgaˏ *mV]~~YZTYU;o/bSs<>Yl"jnuPSC] B'ʷ.Wk4cG@ ~+Rčs"ph5h>ާ;(6QjMEi Ȃ MSToYKQ~Z~?d<9c8 8K['wf+?Ag T]z(06D/ԧ IDAT4 l=W\ӲAdWZ;mK*H9m  R &"9Dnװ*FwȲȢp Y6pC,&[׳mzP?0D+j_|U0^hycVbW5C*{tc/{nwɿ_k|8WLA9׎L;LWǥo(V>Nf𳇊?' s}k[\C1󪂿 \b͌dL2gyai1`WkһL`[n"n~3meٌlaW,Z5>wܜ_=vJRbB4"tв0K!JXlNf 1X28a݀Oo0`yy[xøD #F)W<,/簐9ԃ&5^LOO4ZY[.U =ծ`[B"EN`αn@&?up>k׵}lFDZvʮScR &&u}Utf[>n" oVV& Ÿ !8[CX2^ᫀotXjbUOb?CJyz{j([fs&o2K,~4?57^}9SF!!joԹ5>A#hOt VuXD,|]$PDZ\hF1aC_\>vw|}؄v 9ԡUg7d'&,n92Κ|@ArU w+Ks=cE:,׎Ji`-ymנLW\ XЗ{/>bJu9}M~,6}Xn>+G^;Rcb∏ <Vj-K v +6 BuBI\._*=nX]!VHD+J(|QZּR-!]:B^ pHE:( Uzrq\prff.aj&fz&ab:5`|h\=hrNxUZi1mN8H}C"-DeGJeVC W[HA kgC9A MkCȺ,(mVYqְbEk5`T].E}Ds`2 ^E:tnws#֋z+ c?/?<2gt7zWb9DBrb"aٖ]~l 7Į-;Z˖Zc:3E2=n%;Z glʫ9*jm/.xoףeTG]6RQBT.o 8恰}|GtT8q9]E}<s1\>o Ge1r՚9~>lZC~ߧfu Wo)H.74oϻl,q?[OA Y%#}qފgL(ˍ%D+N)Oĝ|i;vmLjgz'3Kek,l1!" `05+F1= B\QK# |G~<1d(jߛٜ YA1J$W b'E|X/}kMG. J@dt!:3JJ(O$LLN'%$k-ū :0搷&#Isۑ$r^֑A$NȍbESj B,R-LBE!sZRg!*0_pŽSMXnC*Z#TTVP^7@ u!8oyϗr_c{p1#P 8lpYl8CyտWT<_Af3e~!fnADWHȚ6ίDMc*aK.=j9oc<%R6 >F10B֌$grl&vG-JvPcQ҃TK]1|Dv C`u[Jrp,j3xvxl -x}b'DV.=Smip jBֲuM v,r͡E 7/7UyYd aM@V+΢ ̮ N4ov=]N<m_&{DPL\V-JP2_ޥ~Xz\;LdyqO u`R+TQO[u[V^0''3}K`.*։Prǜf>iH+c>\N>Oy>*t3Jk.Nزl޲]w!Z(""ʲRH V BO5 H!ÑB*_(lJ<5057%E>\h)$5`'.ZPZvAny+޴#EY X;Gʠ6g6ʲ_3ܷ?֟y`ypa[TRqTW-|gp⁜t98eYV +eʦZX00,& < E޽z9o@[? 6:]~A[W/rѴ2B V!ګ̑ =P_3;`YFkTQǺ*+3jXL?#Y>:pB'3sq@ VoyD#_TYO(2Zd8v+fɐy%#҂g`iO5?ʒW"WpIF|"Cm8+Q[2 H:Ay^LH.$~jC,]aaYH MyuYnyf.l78SYL*N7|k~p(>8'/),eaSa]U{2/8l{HYǭ\VHYl+#te?W &ì|hGV彘GZY,C Rg|5usg6h˘H3YH_|I ˂6^w0o9e_{߱SVN+^O^3jǕp[f>PT^u' kؠypw-# m־k$ki6c| K9>c'chDe0_:sG}6sQ"IʶٻY%]`@yrrSS#p+z1о?dnCcP".G-Ӫq#7H"b z,3{$Vrb0|2qoeݳwAG>>.SLqǿBG'[2EA Z|?t<\WˑB!TZS0+`A**!BCHiK00G5`FVurYuyiﻯk֌2g>?% PRmNda ^6}zR2+0e'jò.Rη_(В͏Ũosql_ZW \uU{HRIźr[(o5Ue * 7d\O:Ou"<$[gJu IĺgX{P5a|=iv9yXU.#fɦazű;cC.[+5uYCse(AY"_XD5;,F!L e\h_S+*YtCl:B3W^s0iXjP6C]Il**Jdf&`Ç4ZW, |W| VU}Cg/F"{T{F7KlH4xݗ3?*{jb-mAT<D*B VNDgY̖{N[Jȯh{O]Dnhn=;lryu/,t,ѥ'ލ,4SWS`068?-cI҉5H0"iDDHót:0Y)1=w]8x^2 Az{=̓>)}ѳTFaa"jdV!r%AUP[af9_Pd JsԤqD;մҕD7a( Z7\H{e`Xi\Ap5t_{rwc _]EEg10LSvM WbcUN˕"ZlG_Jnzϛ;~pA θ+[\m{ϻrRo w+{b [F}%W)ڢnp/ݳ]^|K*!X6|[p)[@@v+WK# w#tZIÆ4(* K3XpA.9QRς+\X-OQxCj3qW.iB?=dG:xB@8oN8:yBlfA|DjdrreҷފA).ۯŧ>'\QU{hrX޾ecT 0¡Ci^iө,s]9YC*C~9ɋ8ˈ.ܤYz.{b`C5:"yo{`.t!eXY\X!M#ޅeœہ%(2RзZ+"'?#N}y5{O"$cUoȧxhBe+W3L3Lf-uH0km;Ibu*M1nK>NJGOQIb>|뚋 C J +h.'g9ȁ|^'shV2ot"E'R$`\vH~Q3'Z8 09)t$8 s|-}!e[Gc뙘Cle컭[m|dOyqA4A]TE<'Ս 2eE}/8*C8G^8!Qٳ(fb'dc?J+Vm.pV )ՈHʐ2B$ 1>4z hIr[5p + +SZ,D.;k0~I+_Z@kǵ(KW[*YWrӹop~Ltg$;ݾ~u3smo)fZ1-L1*JrTGiE3cX.ˢfp7ԪyeRj i4')#RiΎ{(RNvQl}Cގ^Q|?Ǎ5A5ڿu@6h›/-ca痔n 3}i֍EO/,rGz?# gKþf?+t!QD'ޘqڭ>G\AsTbEK0%8~['>ǾC+B)VTrut]xM }j~V-bMC "aUY" RcgD  %,ZKt5JΊҦ|b~hSn9>_0yG6bgXr쀓"Ioxl7ȭ>s?z-¦F1DH(I+)}PAe92&jY3b)MT?-+ F6TFriZHbW*@(,0ύ=I~!vMQ^$Z 5db0ˋC_s,D0e E^>x Ylx>6yc6-lN!9YEa~uclD:zhe#L ȢR.PdzW[w}6}\_k=9q6kť>YJi|Qreps_~xtuο6.<ŵN3n4.2PF`nINsd {>r2K{Cu;Ik$uW$rV<=\ޅ|7Xmʳt%ۯc߽Jn$1O>e\ICiT۝esWnY^JY9] HYZ:2O▻]7Omc!3eOs'h{85|7Si\TGW_ap8apxUnsҠ}.FsɅ3 {J V"DfF, ~Q,ll}9m_HO QkU+?_1[4+ˆ%6Z_ >爃_up}V c>晓|h_!II/: g3{=6/˼FR.mVGϫd|L/.guLsgOgO$HTc 2C"a8G7Ǫppm />u_yMWg0'<)v^&ct㘨B-As>/w1Y6οn{c[jW4rqe-g&4K]r3,ey#ߍ" ;F2>,lTm1~Z\1j-vo[u~P2`dK|/݊ ~܍YV( $(4|%P"'ؼerw py؟xJ ʠvә 4Gq\垐jyrr߯ {~bz~N.[,\Y\ZV;J5d.U\SFu>g<(/eܲo |A=N^|lwur ;2z:D2F7*:PqYf=\[ [Opv9|x.v)cIaNZ=TRbB]'nehGmںM;jAvW(J3g}-cz,4GwƇ7ϑT#:aY15}YaD6lb]k2l7]R-w|4֜m^eUqHTyPJe+ʝ/lgqɱغ/2&916Uf_f6͛6k{W]?͑]s47u7Yڍt恏 >I2Hros 'H!Z)"X!"#~y INK$J$jW[(&CQCx+w3EMUvTaE 3_j-3B~K-ZTPu ]qC>0ѽL-l k}dxljKV9׻~oꐨ6JD,vg1 *AK"V1;4N2vŪR H[r@0U V<x|z;̯3363Se3ZexG5ko$H'FƳ]÷>k UUrP VLkt 1r2A$eKD֑o>tHj JEsx=\ZriȭHQJz\mX/\dg< kӂ v X9O"ʴ큹nHۨeOBxA;RQ_mo^C* )"tC\³,B Fhʁ8s BJZX& &g]fZ:8$b8BJ+[2DJ\S+#ą!*GT0ިj3UKwyh /bA*`uI9ox.R#EY.kފJ32=3$EJc-Tf%RhKUeW@s0W%~ JdF=eo=ˮOJ 17q^s|TF6btpem#˰JrG.iG=1iꭳk`pnro+6>=l:}(Nn["nu[)2ce+V{g0P 1NbgA 11&Cb4"Q"8ѨE@ÙݽVU}T/w\kuSI¶;.mfyMWVs,7^OGT{޻!q|g|iA2BMGܱ!ZsAq.lN.{p:Sx˲]aōZyO07SfqQq(uSFj`.[q֓G`[xagՓk9ۖ+`q#~^iz<wI,lXϿq|1eþ۹?$d:M>Z24CD+2 18r9Vbl_E,6oyooǴ/7[ҡZWi꜍bE[O 8d 8gb&:bmedž&m=x/9;[cԣfu1t,^5³QP -ovEB H/\+̤s_]6? {3?;٘]`V9~X͓hClȬf[=[F;8gQ_T,HV;OQ{9Z3,*Q>*eUG.{+ w9K)`V*䦗9LnT>*Sp)i˝Nk,B'_ L L6U,=fhwYbRzÍd=JQ\6!&$놺T͊J;s!MY'&(IHf؎hޓ o#4o?+9rI }": ` `(߆dlh +[ ԢMMؚdξV]} tTo8yl7g͂6c"0+݉KC›w ގm0yAe$#=;-$Kq,·n:|1~-ֲ$LoD\T '.-v24Eimw qO78=RCGuv4^ecُXv#=B0IK!pkXѬ¶&vP"i,{:O<Ҥʡc00\֠$7 /d%t]X5e^a<ZU4oj!54&2ns%|Ժ0j2R D<:U#E(rc-ܼuf_O_|&08.ބɗgD9ܰFsF9J\BB]ӷʋb,=m 'F9_}VqW[)m%#69B0ME4qxn"S]7վ$ TʖQM)cpJ)x9[#0G8H +7Blfv{GP78^ҽ{B IL1=]iTR83 :IفZ y.eIbK@-\#y‘?d@}3_͵xɏ?Ěl5i/hJnnڰ[7Wn)KX0EAvK@eʊa󨬩0w$A:MNy*]89Cc\`d qOYVN;}ŪGh]t"Y.CQRBqםA[~mi! ,1W3?+ r."w\ÊQn@seǠ }A1S+٧RsR[\MQJx=G~7*A2.cG+=0k=e,w[2V{ϐW|/,  Yuorg`(avXk,$U>^p_B3G6:vx?_Bf`oYb~4p+'ehN2)lUW"תyr# v .oi@`'2X04o^oϤa; sa`9RtT[X'K_+ ǓF.gDM?S/ WP Sb!Ut3%B2.wmCD ^@4AuE+X+X;lΛvݹGc7cvXVCte;dɮ↜S9AXʓ!Ϣ/@:j IDAT6غ~iʼRd"\3#0+]Z2иש xsl Y]H0^|A'svjκ!أE~ҌE?s{o+sWM.7U椺 (D͹@dXy}l}^gM]%"-I쬚L:ťeͨOr\e+կ Nѐ&؇4aoB6hTe3{"놱YVBgwÿv/ Ь,:U:Qu/f .Kh wv+uT%QA $uǾ>\j@|5~ҹv.:} ds8V<$VXtg*UR'e5iǐZlB-Ut+9Ʌ^ jTMQ˾R^ w>X0^QՍ)꫏=\qݦqQ;)'#Й-J]NhX2d 渍#8ד۟ Ae"eǺ"a&$g[W}DpxVT'JbYv#xB#Y2d>EuJE)H$@aĥ/T3c*3&k2!$5ڈV|kDqIH|ՈʋHzqfǜHUTx% oE^{$-D1CMX2B+`ʆ ձ $17BҦR-t-8eQʠM[6.眴xCwzN>੫}v#yƍCD+V yz.z4̥|i3;Q [Z9 W.X14У>t=RCs_(Mbf|DKhENӸۯ>Ƚ-[d03΍^ ³o̓Ά#htMN; :&bHg ]m /BHTxIfJm-Ute]Ae|{δg(*]TKWet2$h4M 3amoȶ%$as((⾽(H{Id-AYIei-9ݬ|9js`y,{a97p<ℕ ګS+30g"b(||'LBC"u%,/z-֩`#Ƀw l +3~kaR\6oV҇[SKƳ*P+!33fevk2rЀRFXodU!cEߺ{vz>.ld3mK/r wr u41RPI4f|3*Nd]xԠwwy֡@f-T*;[0k]щFŅ'.k  S SRU }A?sx/=d-s BJ Q ;sKO-6v!Y m7]||t,Ke[o%@KP$DTf* %Qt[fɤ\.N`+ֆkS-v(/v,C ",FA[+e妷zV?d:bPhhT,KbPiȴ)AQKh<;(BUA9nR'վҚQz{zZ ~$ZBM8_?UKsC sCq̓˵@\έf$3;D}1e=*酢zE>|~ u5بJcButEηˋXsWup"X1CPaed}DU.sZnڰc鱷e4[X DwT8߿8ha('B603֍:|qq)F*M,TI$3&sG3~HtHMF Aa6 ڤU''gP`3!+9:I##2/n'ej5XƜM|%>h0q7-:t 1!)zO,bJa~-CǙT;bsP(f1chNWzLJP_]P!s/|pnƚn~fRhĘ`HTl.6ҰfD_XdvdL|sEvmZ$M+hkܲu7Ǐgy ^x:hPn**^DeYT? P܉K <zn_$JՉVMc/ ?B=4C4 >Qg=6u&?bhZ2Q~.x+_SaR v fI^n:D.$;7ӂ]چ1s(ir &\[3RT[m}Z|^'E_;*| w05-BFJZjԀ܆hՊ 1{U[g`Cojmse.(8#zshg  fUP=!2A 1& Tcjm)A+x[xx8;σug<ސWoFhiqr??);FJ)r;*u\1˪J=?ӛh*CRE7z\9p䌫78Klf=bF#ܺe 96 k{I%lT*[2B X 09?>J%rנ$#}}f ֕؁^0Xf1ʏw*fuqddvPIY0 Dű*->1CWUmҊbJUBР);F ΢S@u4ʝJ .I6Y]| !1g"5#6 Ns`.#Hii2A2` rܖ?p{{?!Q8? v8{jwUIpi0S5*73!uZJt%66kKQQe3jS^C]քupuϑe6m);?M\>r2M v  Y=oe pWq} IjAs+Yx]eh!8<>g!%,Tچf+mv3]4\xs\+P.kGH /Uue-Gր,҈?W:L1I\D(J6v6J4ЬIMeCcc1:([2G9ϓwhY1O9\CZAR7U qyp'_N dj}xR/Bnŷ$rH, EM/g5&8P'1'4~-r7󕪌$sOC1u]oH4ifR8cO(]N&ѕ<x<ՅD"01qTr:sQI6(-Mcf<{~V! H!HЦ\cyGeY~\̥O3nNXch^^s;$wl>OPo *eZaֿCan[1XlEcmL!g"a:م (\Bs:d^NT/b*Eh"[d}!MҐAz&0Gh&Fh(8&{ESvj/+k_?}ODDIc[ Fv؍SNfυd647}ӍR8ظRߞI1bfQ6F+-5o496 )Ueab!ъp)΍i@K D+8,;,E` O_s YfH'6.vi,ye9giLj(GN8nc Ƥ*O6c\e<(1vԺx5t7b=ŲXD|VǞ4dؓIU[豸ac7QLGbRhe;FK?X"ln^6Ja\ۚG0+Af'}>TmGdmp|..v|e+_`Ps*y4b|Ŏ]). vscًYhE u']zoa?~ IGH[.M[4Y1UƼl&UA]r6~ñH;I\u3T«og~i|Vg@3nd2[n*qr}CMAWu.<viX5 Sv RUU+O[DZr㈊쪂?\#OỐVy9q.c]V}xZ,CGcG sLJmjk[z^DXvpK#JlQS@Bo*x+oSy=Qdap#byȻ|~5@g;UfGlL.ޓ4Y呷$b\\C_*Ҁf",79cXҴNP 󠍄%C}llun e8 *saѾE&X:.9_}JeJi4^9z釟avW_> )|G_ :zv~_=_}Z;ʁDTdu14fG(ef)u!gKamn{/pz[kiUhE?yPvEbM2@|ʒT'@^%@4;8vzsr!oF]HEWn%~:d>zRŞ KFY9oR2BXBɩ{]ҨL ‰ *`*^ -=V<]yc}lm7d~ ͋a!o_z?ku\sl%r\! c/s H|~;;D ELQ%,ua颜NJA] `R&nȤ,[\B7RT8<@sEV㭠Sc}oqg *uybĎVl]`dv1Go=<TnJc^Ig7^)sWܹbޱ/[7\ @hW8fCi)b@L/%#3)h9]ádŊ&]?stiYfkκR8t ij歨^SV"׭l5(8ί'o>tAkHVI0WsTx1]yǣ;h]$*Bh{a;hֆfqQ:,{X ++QK (Z^h}V0 t tҌDUfMCsn%K22aa㶍 .}|筯eTS2ʼnN8?'TnPd ܓ$ 7.|n|9$.#Q)*\=ڵp"YcF#lۼM!{sX~m-Jx߻<'& ZQҁm#.ti*g 9 k96ZC?|oАE1f^w]WR碷bNaBa9<7y|(eZaއ_cre-3!ˈ3J]8%`8mf߿}_7ނff%s4#Q}Mk9*ym/{&>3_x]Rxk=?7v{_`$CjX`iiw_'ayB mU``./aAbG4KVUZYk07M = Xز ɼrtxᑷ!.^EU47 EC6s2Zצ IsG7lL\*ZUm]T1+`,|vfmwaҜr+l[¶Cͬ^_xQ|KxGǼzoy$aߟZMwg8c ;1wn%1ca^θLHLq qeØ"ņ{ewE׍TҺcN΍M4Q\Qys02` a5EaGŕ>?A'JRBsWc V7+x>oIE2Z9hzuGЙ'OC밼^/lXA-Ze,`"ܞ=|N^}tP]f~cJ/_7 By'5J!syߝ۶+X(z`v>9Ff38Pδlٴ_ 4x^ *C>FwuZ= nSg$47Vާf߼o%Tn2=9hL*UtS&]~g1ӎҒL^Ws"-:? ʓ?x?h(֑Wy(~iٵ/M@N0b¤NLhN9ևIv4;3{Nx8G˖Mv؂GGUyZe(IW:L^OyzUC ^+;/9p¾r70'0'~T/a ̽B_EYaee,ъA~#\*'H.x%4ìUR ZWPC611,K)K_.Jw<1)oz3I:5ą(_al}&la}]h\:G^׾s8$I@u,2C>8pΑ$;ܛhèZ &8p+|NWIN2=]wR.y-EfYy֮n^ ๵ y<,t44q1\αk|0im&˥2zyWlntA.(M:PʕU}g :qQ|sʳ_쳰a{p 7IO,2I(Z,lU*JWlI2z͹cʦt"4U- R4˂0.rcWjd]d$EIs.Ez_=?e =5KlCZ0a ~~îN\rYlmv2g k|)Aa.r\ s=mw{=غ٘³g7*86e)4AeXi] V}aeg^84bXz_|nh9ci+/f=^i$Gum>tJ7O D'_ ?j*U2 0$7+'>WUυ B)v=5R')}鄨 `.RXPkqen|i៙ wrz0x&cF$Q=UJ)8[ణpoӂgL|1n`O=^n>~'7xA+&9pg" 6(#ZKJ[a0[ 8IJ2ڇKG!S7OdmΖ 9q07LXN寖W\l Z*'>!Ւ>d:Q _Xϩ/>S^r9<pSЗ䓯*Vln8?p4f,cq'ѷ }_,FdE+[pq~+䣦>V.焅gCvXOo.cשa٨*OyO1)"$`(9yo*\~\}`r[l\hv#B+{{X:bd_!]LO< s0ܣ3_GCϹ k &ˣ*Rh 1psϟ Jb#S{֣|흯!A'e>fOJap!{;n…?39è0;Y쾸I~ֻ4:3&t㨔[óM1ԗi];caQ]VT S1NI'_;%OrGsH0H2E*}{]1g'YySrmq[`7~l>G%7~j#x~rӡA8yG g6XvOY\81~D FXd]'}N?cVZ>D8+is:Xd$z5Wkt 0ڤhQ@' R%*.NL\c&m +k6t,q2i|=$Z?ңTTJWtVS79h((8=[.{:Q>a\Յڄf[洖Kbxrw'-փZL;/xUqZ>{A0hUibXz8`9nwǝΙۗxZr)k֦qVL [a.eG*XrE<7 ̷rAXOunv`Ki^JtҤR9O>*qb(ֱc 3j.8p/\H1˻ =?)܁FD%#(L/1Z\Zj`.,۱'ʃw}LM(򋰼aWc/=Ũ(6$(i[ He)ej 0W<ɒ`R'k;暧~?U0SRLѲUj|ǾI[{' Mhk<}6zha*Eki+@,iX.k/\eeo/}Ya]Ise^`JJJBPS4!'FJ(KtIqDz~.8_P6 YJoe_d4q2 0(<ܻyRavD8RaĆ$)f\ %(RC|&U50K$TAp}byD~l=+#륜xjhVTM3e=|f$>.u+Qa VG+FGG|R٪uVL o}8 Ou[ˎ+~١j+k@ l1ኳOa󚳃yg-*] WkD!^ꖢ堆\ ̗}Tnb@4*>?ȲpI_o,2zݔ~7 *ړXey4zG>h=3Wѿ| ςx9-ږ+Y)tWH Ǎ=.uv,QJ,lX[Knl!QBqaT'G\'zI.ݬG7M;:)ݲf a3:'9J=֋0Xv#W=t/seEi)BG]T&.* *hϢi|#uXaco- һk4]zk0׷TfThā{?C ]29Kg5#FĻU avVb" O+Ԡ,LDZp9 R]΄Z0`Le:$߂(E4U N%US̗۬Yvss?i<O(z©S Ub H V*mVE|ǓJG@$1y>6vt=]P;bqJFM2V&4QDg/.ƃQPy qhQ.v= s‹`}|v{*%CG(ZVJ\c1&eҡMM(T>yo>?}0;.=Z F^rldv^,<YͫAǸ" }֛g2?_?x8hjU[XFMʊjT"*Ĝ 3Qx#Ypɦ;_a) s/(nFZ-qOmȼ/PWo4o*&]T*VJ\O$5iJBhT6cx<= Q劈$Bsxg#xܒ<;7wF)P._m%:L.cexrt#Ul<r{rۖ-|ky!((7՚:xE#%'IxThfrdOj|zAx.H?S:i8iO 1"*`(iۂ MtѕQxE y%w(]^ 'Uh7ԉ+8nH+ҫrS}vLCvyum'~Dpg?/L ,Hok%#, eWp[Pb16kƤab|qq,^ih.Š:r9.1nEmKk7z\C®F L!KU~Ь1a\l*aCp< ,`O4To|ota9btwNbhN8lL3U6gҾς xyU4;as,;= 3o 6cGhG,*nGqP 5}@|G2e% :;sgUQA (((JPU\Ys\wAP)T@DQ@9T]K* |/\s@>1nN <Qd|m=q$Dzj!:H)~/b1ˆ1ƍ0S[Y-ڗO>G}/yI;60_pU冲_gv,ϽgRC%kg|CUgPwN,m%S%iq^R5ֆ*yOo9? gnzJtL"Tޢsq!pJJ> '/{MBăn I܆f]se( l!$$F'18gp{}\;e`0/ܼ$69U~x}R:k Kl8g[\$f5 ʬWf2"7E>Aoi!,"2ytL%??UV{? \h6a@+5P S4yk2e,nvP1f}U jhv4`Y׃q IDAT K'xG YmXvyPδ<AW R"9aE,".U %uW uK6sdI(貖&4 NOpDasoNz /> F`rdr]5yáaQo.٪1GQL9v%غeX^2#pso#GmgBt\Un}:Δo9) X =ÉY/'܋?zgb*ۚAA V4-7}SV%X5|(!6-p>%rIӠd9-ʥ*d2@.P`w's{h>f.3w}⡷K|;nwYų->J薁ڎ!pn`Kg,F73ϫ]{,UZ :8w0,͆nYX^e' >kyf˻.%,wcA5AóoViu[ pƃsVspZ- SA=Q#d}!?E_:2n\w؏,&a-[g~n }ʁ\9MK,d9ݗ>=HѢO$8{ Yb^oYŅ1W]y{D"#ވYQIiy"dA>7p;7]Movsq]ե7ӣ;evYN<U6WSJa#6U$KldYZw!Wx)r[9~˾-r*PM;IkO݂ $:f$mJ*]"L+*x Sb455kI:N!*`/-෋T(UAV⻴ %|Rav^`Xt#;?:cd N깷o)kFl]?uEB@i4'^Y)˿/ѩ> G{1kK ^_e$F,0KcuEݙ04:y(+-}T VxM|0omю驤V4PycT> 5fO(KtC~ٍ%,K)}sQE Te*X٘ [zpMex[l}ߑ<+z=Ă^hŠ*njv\7ȴ𰬝܀2:HJQvOU:9dڧkH!rR+moĆ'\,%4[@4fe<2GWc+{=e~>v-o>~Z ¾˿!4ٲ6VJ{{;!haVe} t 5G~_f3 eY5x=ioŘmV,\U##4XoqfamRh1w o Ulzq[CrCZ"OP:d9&'OYHrxmvoܲDImÆ}Ҁeq%nt]R%+T|xK!NnEej#Ԅl |g< o|'-@~m?BʺHUKupm*-#[v!-uZu/ի40Y{l=/| d,±\BIJqc.81?۩*W bneT.rgXXt*`"A`F1o-? 1T/>ՠ/{I8tY6JIs~ԴPvZ0 Fʦl2@Jn;Poy wnu]"5RF!Vcl\;Cf 6clsF6cso?!GL=xJ0[72(Lxv\s< <,rEqvsca6x,.2sr 0 /39yށAtp6e%X1 -r+Y3G\xՅ|u1yg!ݍ7>g52q/`X~'3Dqrcr▭|ŸN0я,<,wyqOǰ`|/h=+D$_z"NXagmf6lҞ AiHɩMi5>7s_dO;v2<'CVMy-mYuEU& }&fSXFQ|Op,@icE-Yrdn?ڟl' Cn.m5$B,=9K*Rt.pWɬ 8qdv3WPe9mױrڪGNx_EU";Ca~sO~Y:k3dD $モq_qc *X_*Z? f,M7)$99:P<@KYQa\=Gǒ,by|~29^uGt< Fy)_:n-sXn^3kIc~*vp?F[t)1XN8A-ZY"9_`4s] `{>~Wke˶k{}>AYuo q$Th8C2mY0:l&l46)VUw1n9XJ)Mj9ەv3\m_;vw1ЏgMk,a2uzX.IHnD3f %J) XJ3TH¡]uWa!܄v;xmkXTf~Z]y)X9NsA };-o|Vഽ p–/ +]Ye>IV迕P;?kp' S6Vl3Ji60/Oo'σ1k xhz ,&:ַUI019#ǧNaY كs*Z n^}r0we&,"3A`Vkuy\8F9 sX;ny.γ{8"j(kv BK7t,NXܟ3f8އ_޲gBoW9]xf[l帿ފ39jrr |տ@H#QrdU:aUǒs,X>-c4l)`魚DDJ m3 [7mw'Jta9 R %4\,q-rԿךAPU&<_5Tf@T+]vvX/HoڃG5YIoYC619.Sr2xcr|DbDq* la~h?7RG(F8ЩMW)B?^ #tkV$He.IP}:56m,+3cÌ8o^ahE:ep?Dezb1n+OhcH,,5<38/i{~W;r)R_)(Y4c nnnяx:nV]?;mÓVfk!x =S{ ۽XK]_VylRFJb'NXOxwǟO7~sk;Xa (q/_- ˵,D只gbU=&9 _BR[QolFWr?^ Ee(AYPe Cf砰[KW8p~Lc.AuY0 &}fb!n{$F`I,ق%[9_{*Y2rQgMkm~?̤Ɔ06#h klZNyyOΟ<.rHB;L!8\"Ko:S9@U>o +s A8Uy^]qˏ{=xٿ:!0=] ɀBS,d׷ eA,Q("q{#3,{71R 6F [>)zG,'IDc%h.mAsXX-b8cxz@~ Aa"rfQ aivra: ȢokiNY~>8 rWT!V \q_5i`O㜶Sk+FZ7 \'ݡ05ݖ2׍+jeUr.c  A5֭q~*emF'<߃ j6b<񄷬ܸċO7{m*"3{-E Q\2m!cƻO?A"Ћ(ĭ1<,"e Rav,f0*}gMP\7y}yo>g}i .N;ÒYrmb^;2zڹ#_ jgW*8v9{`u~g˖e:،숱aaoQVUCeIyiLvŊyOp%YröӸ-Wis+ڭaqִΰ_m=f*7eJe8a_T \ڟݿ=ªU"M"*z#c:ʸ_]%#*a+Jh,KNl&,g,fsGK+%4 X!?|]w$Jeu.ZaxZHhas|GZ,s$qGT)͕xmLC!/i?،^ڧ=IRK[Vc-Cnr l^ ~JGHtT7qX0Zµ.F>Aa9I"X]J iZ Mpp0c8uې|t#WDLG̡Ew;onP^up[U"  nZ՟/@@"fdVXч/Glr^XQe[6\hw(-/,r׋CBuVLv"vYr"tʱJ{osQ1\ cЋNb}~zʾ]8?Th1fD3tU-'@9ZpV-댟;IfD r/I`.aY TflJ(6x@>DN~yokY4, Sp<α6˥l$lKRd5"'3tdL ͢!0˸|r*7i+nBevWϼ6A>?/ u?R6jȺzLbQ܀ebK+ID;O~~X+\/Y~mT{ udKXxHnl s]86.9f9t#)e k̬x,Zbu%FϯX+{ u/<rwn{}Ij?;eG,$:d^94Q޿lz/RYc|Vk0  nT=_' O\C]F 3J~ ~sAemlA$*X2v VH^A_S ٯ@T8sE8 :G,SP{6$@Fb#M΋J2]4ePUL~ڣX՝˝8%T>X a9*"+苫4 CJ޹kQg5"ػ4oUahڻ\fHsB?SN֑$QP@Z;~|`NÿV,[VQ`).b"_|E{+XOnA0FTykM:ۆ]Z.wq\NrSi#[{o\E܊l$Z8*8ⴻ(ˍ"!cz[g'>zfH^:.g`bhM2T936V/N,#Q!aŏ7^W[lDJTPeAuƭh{ɹ=y"7YzA*+!D$_/JhV+Ĺ%,JEe%DuX(6˾IPz |¸~z'+`v!cķ+uf2dJ2=q rɷ M Ҋ,W,Xcnw˱`0.EfUxX, !i( EQ( Isg7fhWo/[e(c}<3]zz̮7'9f|Mt0h"BQ7{և5Q,Sc?’&,uCYn[%0m{QPSZ v3 N<0w%'=#1ȃïLPޛšaۢ3Ԍge}yY}o {*F&)dZ!pYm6֌4Eш֯g-[x__=˫2AOls~!1l!$RDKK z/9{4oRk^k%(+ʷ?,kpPs^ڥtH4N>%QPEƸDFIEfrʅkYN3":)z.'iDUZFPHHg5{heeQʂgcnuXWB10{xk;@eـ6 OBtzh,/{l@s .Fs+^Kxr7,owSlk u΃ů*hn0H#E#ų.>7/ [4$;, ^ɃZߜ)^!uv̲ff¬²vT*9^Ҫ]`}K7o^;! 2rUe/ AU\RH, 5"^ƊQ>-@ ]Q+u %m*ޖaC\^~g|N״VܝtYf s(X:E)}ca2XL+%8pU"zAJѤᔫl%s|ߣ?Q07<{qRJ$ 1Hi:EDu+NX5-uycJT Z " O~%S r/VlfٍkD{9xqÎ.fWSg37f6TWT!)$=9Ì^SAsGJ| 4ی C wc0a!Zė۲`H޼-Y̵=CtI)M7F iVdx-.Á7c"?V΃zh1EڡttiDtɭrD*DKUnŸex ;FlʑgO(ѨK$M#kLwJ(' `.iN8Y8.Q' Y#.WFK2kpoxݥf"݊Js |cUWe2~M{F[Twg~~+׺ /|Wg*Y.Tҫ80!c("Ѿ\br˿%2V$, sɵ}u3g_٣?ᝩ/ѝ.RDB4 F; w:Rm(5mǨra7\8 "&)يj[BۇrSOt%ǮBuY I (`N,)c8un`#{Z&Kb]^a\1.+eiYeR]z."cu޲, \RiHz`"b/ 1h\_{N8+PtY76ǽАZav> 09D((1yHb9 |{?<^K%RrلUms'(t0eˈ+ФRٛq@ VݎUΝzz7m}!襒U(bդD'퉶I'lœYuy@hi'Ɍiy>YnERUe_=m?GW< ]˴^=7>6ƻW]>O`7 j ]5C*C" CÑ1XB;9.z8w<TwjH.i˨\w5w'D*n$p11K!I?v 2 WlL0E 2ZCFG)Ք\5v fD]5<38'JukgZeM|\κ2Q{`,|v)\QU0 eyZqnNZ4ZiD,ITAD3KUY'eȞ{DeŐ*(A l V1_܋] *dyf%"g ~ttCyEٮК kfi/aflESY8tuB/?9r} z( sjjX6¾~4S%d*&j`KPDBXu$#) B0v-# x*mJ CV ¸rAaCZ]:#%#ThmbBB9of|^j7(*Z +?ze9y^P4Z6o7M*eY7 fǗQ\X 26 ԰, ;VfF 4ZUq"0jUCy&բʻ=H5fSN[ѳ9 #+uWy/mkHo 衈~ט˺1pp9^d8ʢgIۺY}pgX=tREHD"Du9ڛ`9͝&`~~D09S^8ˢYEh ;:>Ae Q+RDCl`l;;qf^1|[<0,`NId*X260[E&@[`D2W1[+S)UlYq .G5ݤK%:&R14:LH]v )uHrҐ+|kjQ&S~:R/Xn^O%RvcAe0Ee+qVbObpJus/;v-?Ff"qZc#q鯕\G)EXΔ4?ԝ x`n /!: \uyw69*>NQ졹Fpmhs[ o {0DbI |8`x[33`UGJ*Rz$: ^6(P*+ ((R*sN6f@9#%CT(>KˈH*JTr 2xyK`BUp^E8$`hT,ٶ${+لKk,k B{2by,JjTPV*my8('_XY= V2ڿfy7hoݵ$N*+UBc (Y+z:Kz/5 }4妺)#M:Gi"grWƒ_(CQCa>f6[FFjƁ8^jF -#:G"2!WQHWjC. ,,} ;H.7YHՆZP0y`QrU2D*"# N$: E-Ue aDJ99Bz)-@`ط,GBS Y}%Z’ /L8+=VB|:VYW=*lU LHhD5_4Xֻ]ÂV?AgB`:G,.MaѨ[Yh=ZKb;(Z9!ryu` S'ete78u3׉.i;#XHZ/S2W*$˃aY6UfQg1VpjTy ['Nz`b|//ÖdPEvWe#7]lP.]>QYp^iF"d4>ht#ǿ'2?7bZ+'_=q0 -U2Ydy>yqCb\hn8(y<lݠ*Wr/ dM՟PAus;. ՜.VCo8vg%,wgWyX~?KyxDe#rar Pڜ7{6{ؠɔ,bXBA 1ٔW b-]!Ig/XVN rϹܼ=;+Q*QB%|Ii,+H?SˈH^V]1>zO:N(4Iffy|*(U !'ܞ0 {5kf*u˚vb⺤2W6/Ny>IebL%CӶ #TT+ hꥬ̦qq]G iV2|s0GA]6| D4u*ȅ@eY.|uZ s'bE˂Nler V'REa|`xvZnj 9M|mX>omŨybht3P` NͺHa֌Wl08I]}LCs5-Pnn%0K%DM&ekuhOVjpԝ .|tQNܛ&%41IE# 1>8pfm 61eg-<'9lj3UZ_reOXNema B("¬je 'P-`V4. FUJYثqb#ڂ_oXC PX;ΑIPk6XW'xDgX3.HRR"؉FnĘQ'ɫyx"U(wjX x$*N<4cX)p}2}_k`` Pr*N,0PX_}Aj\}1sCT$1ŒP%IW"bVEP+Yԟ,A]p b QpNH-ٶmwmt۰oڶ̱zAuur]_zTTrҋDBǦ`:ե[ܦcN\q|][GM) ];$nU(.BJ;7k/<( 9JzHu,GTRaS6}ߤ*u!54J)Z_5e㶳s#6yySߓ r"_nw\˼ݫ:'e*W`0Vr"Bi *, ޻\^(tM[e6K1iQBTscƟ#*tbs, Z _?߳->iG1:Z 2\~hX0ku MAt0Xlռ Fp(b?wV"5C,-l讪 Y**bBWJ\t791V>Jh.C6MdV)9c|sʣܒdH 6-jq;DT =h.06OGqzO>ޞE,8oqS8ö??'ZXnh"NHFd`0HSr.T_l`AQϙ\Eų}-?~WȎc@Y:S$OxQ}PPsO]`d14 üד؇o+o]Te3AN)NL`YFX2{Y"^KIQE5Ƭg3jPٛfyʢ:7f1ǪSb3evx! ep]Eh,YUf)QLɲ4ZDHPAUelEǎZ2|:>) g^X2d60vpJ1c7XyHMuYhbJPMSOgů8cQes@ dVQu!D))8ȭʟY (fq628ҋ1h-54ЕO`\$e /._ÉQp7^~rc~̖ P1NӃEX,r[ KXw9bWhK)*~J7Zeb{]e峷q˯p>RV:nQŜ.|'@`_?O4\Xj0R3< @}hY*sӞACMvQ]!Uo=B7lT4's4;T=^_;m-l-*߿`+",O $dzvL鞈Ѯ Y?U>|m6ڥB<r+Vh,;rPN4@"RF*`jIpL~+S:w, mŶJu鐣2rfhіcJ`$Su DV]Kw׍8o9lݫLH(AyS͐T[`HTB Kr κzFff dx+=2{&!Cr0yO*NLjYP7}sOep+]ˮo8>ac4VT}i7+jYe3|h<:%GܺlÀڔ/Wnz ˞e/ XVchGn::x˅e4g6V^ X!V\,r6j -K2RRB0%Z> >gX[4ҚZV37K%Tme(_ϳlreul܌*T_.Dh%Mu#p"tbI_zf]FMX"}2L;ﱹIMLY)1E`݆y뇰x>E9@R H%JjP!hJHA"]}@Oo>x,Jx~s S6YX?Iu1CE`h*ei(o4PB z1=k{s{i"1&;7 ȤN,BWIMi_y`=ɋ UAz> Ÿ*$s ry=+Xzlh crD}1tK؄R}_(pLX[_ *R$Myۏ}?(,d,p.3Ĝ =R%eqʠgl9 xp'7,υf_M<^4|~ ǦR[x _>#* !&owT v"Dvn, #ѕ5HD2{MMDk#Ij-@W9ESTYkТEȘ_'RTQRzSHWvz*Mo#0q6CU-m;@~Uid]gմl ˓|QX@_Z0q6Z3Juq:*̮( ŷ'#H=%`KJlT=17\k`y3>}Vk0x,dz-Hvz˭p!t\V| (t{b+Q(PD/ h4-Jh4 >gZY]l1TDu50KY--bqPr7ӟa=ƀ=<6c|*y t̓ 9 uRHݫ<4E<כ#;nܕ7;BiW `2@bZ( *]*u9=k66@Px[Ll8yꨱ? ź4GDːoܴeZ2LoQ*ءk ^zDy.,9#< -}X$RTQ,Uf!xԩ bS,9ƣ%&cӛ{퇽!)Q`=3r=H"(mDID#E I"TOE3$k;F&Pѓ\F@JDe&$Wi<6U'zֻ51򮞹/ͻugAl@TQC*K<+UA^]v<X>` CEhpչPg%ai=; C+|5OE,el\lHJ&IGA4s {GZ(H 11P(BPW颺JG)la _avUr,e8br9>wC`߸=o<^,Ы#*ma09ɒX,+.Na!:Yʯ3od5:/}]"$ 󡝯g^(6!4n f1U򒕜s嶷a XH\$zq5, ֓*Q67ml ʘ͉F%"τc@ T0S. ~U(T,VĠ>@o +9|O4)8{<7N"ݹȕ aPt-ze2ËP*eS~&'_YۻFX~ћud:5Ԫ^.McWM`9SݪGu: MPLtv!1-B֤5s\5|#5ɘB&o9D ͺT4 ,Y !KAy㊂ {kA\_6ictaZHAA)Mnk @D2uv} `iI!{w]qX~\ZJ3k / ~xbc<^aDWuG +V j n<=[$hT ·t jdJ~>\5mW;< 1<|TMC>|1Cczp CcCQljÀ*T 25o]ς#u{`F$ 4-2(!YG޴&_^Kǹa|q XY`|af Αy.+vaV"=*Gj kYd4†f_RhCh-ndOYVZ{_Y2JX̻ >$Z&փIR4HЬ0.>MmYWGwUru,E^n|&"؀D1a|B\A{_04/|fD`_E>UYa-~>vDOT8'{s$Y:K#oأ$IlQZ\Ck/۱pLܪ2}KIf239)9ccP,GwϚ~|)Uh5h )jHK?A]![Y -t@Z\8 b+NA+Aa]hILbF_Tlf1B25(g8%Pqup 0oeB>]~(z,F,g]Zx!ɂ2Ļ.9x~Sjb/-1 b4ay;U#*UTv+|۞h>۽\6K--q$" eb1ITeU{T)2#d͍pbXhnzT1װ\EJ)^^U`X~#7 'T2,fK9nt҂lUmz*M {ڷ;p}a29\T 8πp0:C$QKU7z57ikL4 <5]aCڝ-CXiǐ? >9麂RyTE jeWEyX;>}-,VX$w²QT+Rʜw F=77'mA!\{7b^hnR.7k?s'9qЬ>M;-iX}160&?w63X6ŚUw0]X#`mf,qͭ{"thgY,Wdhn.-C.4 !$lÅ7ε ޷C bٍSuַ&;<{Eg ܉,zԩE=Q 'DB]Jȁ(jAP=]DXv5,AC]nK( Yn34W\ed N|sQIA*Ōm- -lQfFGXhQk.g5֮u \rs2cuo5F+Lly[{\?|IZ3T*QYJa.y~|dA1Y`&4DAwm 27faBn (\_R xc!$M1&ŨTfȾ q%;qӟ^s;@9׺/1,_{Lø=HM!|Luvأl[8&=&9bP_̓嬝հ<`yhl}k{Mz9FZG {wʮB75-mt,cXRTa(E)lx-ڪ2i(Qڄ%I=m5g_(Kʑ g5kLi!J  ̍G bbXS+s4RFVrU,Re=`m#Dh'jvH6:mqdSA FH4]a'gp3d64aiÖ 5倰?&T]B$9c/G]@&,/}^yd)J&)axwUWd./\"J,@bB(>fX"s-Ѡ$3>\y8ړ4kbM|C_sa9>OhŢRGr c@X\̺J0&^b>ghc_Y}cX<:T^U$a@XNڽitQH#, u>͘Q; ?~AZ`od8zFG ==?-S_~D Cc x#Y2 Uh&qThp0ѣ\sOqE[sޛ4?|,k=ŋFft`$=E.Nh-Ki-OYV}*'abRw TK!`fU^v<`G(cʝ8},}L" 7矿A]6)JIdʷ9cqxue=?oWT֨a[p6k͟XJ&TUI c-2Sr Z>`:& ݜnnL^pܢ9qեs<;fwݼaP?AWaQBT[6v!Lh6N׭+gxUb^S9sl~Q e^}Q"- B!I+֫`x'@ԗ7-eс0+yϛ~_ϰ.zo.' WL01㬟U9{s .y7&sr[Їl@ iPepI?IkyJkyFkynE+F"N ]{\1#|c~& t澤-<a-b?/2t?zS ~oٙC׼׬|#'b(к 9F4ɍ IH ;F\+f/7V)-Y屁-cQ{z.;:ݜN7gɦ{;>EZx)~ͩ sJ 4{ngy6Zj0<' ڣ~PŠem.LЙ-#Ɍ(<ݞe%4Ÿi|h)*hc<yAo*ٮ\:\wk 6C$3,a|޿;I$*cFeXIPOq߁ə=${v ?3pn7g}%y\HaqѱUҶQN{է+P۫*wpc HJzFGɛ!SO{[621,ѨUgƯXxkuiiɱ7 L}UUƛ r'θgء$ *ͪuͲvĻR2 м,E' B^U)^z4NypLb]T @wuOdH3;sZ{_Y˿f \]3֪U/8/!S3[;/>纙Э{~f$r'@j2w( 0Γz@b$9-Iu2d78I)`=fpl_ߒq b^uS8?+dy6\poXp]w=(K7&,L4v>-9/->w i;EaF疼L>/ {gm(tZIPQa@ $MdY8oHT)JL3,Y{#=m{ rWR4ԖRm7B%DICIUMcIkme,6ArKeUs%VBR\ lXw巴 NV,;='/ q(+U<{iF)֦^a ʢϊфgf'4nvZ (QD\vQg1δH#Hp݂Bhr֓kZWBv#GAnP@U0V,Q>Th>AfDհlRNH2l0cEW~<>Y<Wgc,QY޳ uz }H+p~R^<( ,?P.g{qAm9˃hT,z/}}CP 'BoF6hCXAsٹ] ;SE*^~7$ a9#iW?<ʘ+-ʜ6kN:lK"<^wl\ ¾@َ+_傷¡_2$addh>@i7G,܎6Ⱥu[~d>Lm:Z,h1%$1IS_ .t=4Αz*%`01^e`9ݯbiP2B*FX"s9ͱ̐ ^hЀ3`: *UFbDXHSp!|R P쒶tbek_C6/Z8e?*Rn{^VFgZ~h%[A're!DZ"(jKhV0g)0 b>'s}P1Xhv9KY7\/BRNyᧅՄ8rdhD;}[Nk=Cs 8vN̒wdK7y۵n;vKl{{ #ɍS||3y֗=lЌ˳Q NaJ(cS9(ȳ\5\Ec$^T}=˲cuA|j!, ǟ= QD1!R%F`1uYT~ROAΊGA 촅H0cҰ"SMmA-p9nǽ_{ 'q_"|F(cz#yhh`.FMiY*8Y~k7pћK"^ ?%ʎ16Т,(6R ,:O=Z(,K"5 e<`yhκRR2EE_].[Dir Ϲ/mNOlN޲{y~XO^XC!-!52zvAX't$>~nu1|h]Cz{Qd8!Eh:~eFQ){p_mq k߾a7?lw`כ|O߾S+~zvS)M+OŻZv߄yMFt@U²4 `3v=>8o4oRHEh@^jIV {t lvVv $u>y  (f Of0m>y/8XxW;\ Z$₴0mT7MpkCzNտu!~@jOom` \YU0*3mg,\@$5, WJ1>ĝ%plGZ8da1eL*#xg`\9k$-)+PYV>ZY.?-=cl9Fj&) ooa`ARN mdP,*i"Iښupﯺ}ݡ~T}u֑,y/xRݡLI("$,RZ}Z (o=Q!iI"R^޲X}ʲ)arǔFwA>KAr=E]\|w IDATA/NBORT Ь+K,P>5 = ""\$ {BaO@DYJs4s} k$[uqrWs3Y(k"~F5*0#"4X]_9 |C!gCl^烏tLU<-1 "&d&35,料H2R>n~T(tt%#1JchjжǦ^Rΐ&WӈL /_"F0W  JtHY*RA\o% <>||hBR)mcLL6-թY&\ZT̸inbM7r<#o.)ߜ~y֮T&,K$.X.f\bI;D1E w2db*2 `R8+\X (,ZTa3V3,Z]6EIǾ%h˙0R)ɂ "0`dxo#NP˴2ۗ6PlI%U6Ps]md`ߙfsϵj|]ܘ4Ǝrt2$ʐT7*sTD`S8ct-5-eH |zSu[­_HAKAG_oӓNjGa٤Ha.. Y?^6+e6*@%IPFjS+:grTv&&,Hx֟{뎚] 'V\vܭ&UfˢƜg Brk>94۷s=01(C*M5gh""̡|m#c#!s&hp3uZ›t.0U?,ڍ^~RRB==W0w^?:V9O lxo(\d1ſ8v'\_zavx|#y?_i6*t?p]$)*<_b 09;⷟?§Ih:Cs`Yq=qI0gp.NCJ:q ـ">GqΠ4HQ2AF k)) ln t"z̈́F5 f|NIXz!~Pn>?]-[v||#&RfX/0KP>+\;n _~'N缰ާ5ƒQƒE,KH! JPA{oo)j ;EKe]{?O-"5$̥\ƜҗUkхE*KKT@)'7׺φ0xo<S3aao5rHx3{ >&v:f~Xvx^ЛҝQ=Os1R@jzMy`<09[,pOe'u:#,[tLΙ`iveIf"J20 %9 g).l UaX0)z˘i13ZCA/+)_?rW~_)55ŒFU9E)H…h!k`F /-(^#d;ve`~. fng-T֢2\ {&?k޹'nxޕ 'V#m\Ky<}wzi~ Λ )7r] θ ]6?іӾ#CPMckpydEӰ`NwX+I2. 1*dE* -b*Y{8˪a}iQA#Q8YcFDM7qBsg "((jDlꜽZtNUC7U]u {g=6mSZj//b hVFz-04[(/b/EWxgf'-l3uF2(җ>8Fz*T ,Q^CbG!GUĀz:DrlNr>"boB,C3h`V35 jNU~V<&9T)}h$*85%VLS)iP|?&X7]n +\pL7j[)uV32.❸=%56UuhSPb&2׾B{0>fW^qbec؂.YPSxX[|OrM^h_xäވ;/Y>'AaU|珏E d U-1k^YҔn芀ALEXQ œloL UEpiǨ@*csvk=X˟^cޚγ\}_! 5˻5S`MI i֡:0|iuvՌm('O sVLfLW[ep,oK6#=+(1((el N g;;12twyQ4*sf\CftJP?5VT.F{ 7(&LS"cBEJR+U@B- 'Z!ѩǴ,b)cA"]^f<sw*}Ebt؁?hѣM&L = T<;l*v1JTɭif1(1ɛ뒲z4S{`/1?o}w89<9wO2g_w>WCxږux_zۄs8/+ZjkmDflelele7a4UšXt`BE&9m:m"[}e[X6cLlQ< %+$%0 .* LZ;=ה2jdWayъYT* 3іڻFn{NoS ؙRjׂ<;ȗGyG`@*\ 0.V'&ljs,vJe¡-E5-FkN^Ck*)˩ɀ#U twTn}k}}84NM=wٯsUq#o|0Z?^~g;տ~Z겤`||qx~7)3*&CtnZirTP]ev>vШޟĬ*2.,њ!Rgm|Ȗ7%+F #h΋l , 1:&kTOCt 9윃9D+5XVcƤΑcexo;vEZKF췳51 XEp-#zUꕹ(tƨҜ5 "%U^$I< =I\vAѿmmۓm/xKnO fH9Vy_x9Q٬.(w,4V}bqc"U̟rΧ|ږ[$W<& V .8?_v?DIBt=̿ x3&z8I,/2ͳ`S^6Xm0bLgF%XN-Tc>6̥D&)(t'9kpTxf,g|n= {^67pOa卮<~7N>W(cY2[er v*`ҵGPbE$E,Q TAQ,Mj|p\1_:NuI\òJP 禩(j?K<ܓ͓9sq#PS*!~u6=Xo] #6M2*m\ie,"$ a\p.8hJ_5vj8FQٻШ-&3S_Rf)kk0\拀(5`d3)U!x^h=T^lNP¥(K`ڥ)ҭS*SRJ Vci YWz9c"? C.d & V! ENw|C.oh>휤 :ˊʊ_ɷ/wL~`4f8ĎI_ЙyXA2v|4*&= X^Z{f?8"KwSW̥̘x (BZQڟ.n(tﺮ=}RT7>dy}u٤)mP:5dnƶ۟ ?QPes[M5,!rT:IkC(^ \=YYZ ٞkz(g<.358GQ~3|`<.~]IX';\_^,:_ϊT|XSTt&@EZU#ŵlg7x_>38RAI ~o}>JǞ5m~vr^RNŻؤǞCohŨa^A(!Ψ,tViKͅ)NcZ~ԡmuuO5.|ă+$W׮4YWQTTa&+̺nisTf; N9RG`YҚhm¨Dh61(z@k`!v(*!~;gjFt !ѷDz^ N8X{r*T*K겥Pi`X1ړTS?ň =ClXRyXH): sLͥM c*U?v;{(bM=!mzS9wqt`W^pi%v}dH;3֨Np_Li@r3m;GN|}#0{F&p?joJL,T8(r`0֠i \8Sc 9aNpQd0vQtA.KHH>r-!hB8WòMofO42wZԻuH Z?~ާc,^8WFOexN > ~v_܅~OӌYzR-,̬g% fdXx qafػGX\+,<<J̓ &c}RØB<бӦbVmZpU`6*7b.< 7;፝VGX~g3,G59#480Fa֌suw=dAj IDAT/a17(fi>$W\ s|C *:A`͐@e*QEo*JZaTE@VU>OeM* gK stX F)[u 4y2\|M<޵,et(.m~G&)̚\iMnGҞvV ̇d x*VDp! `h,"4W-LW^ݼꤷ\ rJL8f^E_xS.o=3Uz63.m~(g5p-k=o!f;9d~F3ʊv/1sױ~;0*myUogf+Z?qR'm W}tKR}y' )ӗ68G`x1YEKˤ"hKy-񆟩!vGikz~Tw| DA06frpʶ,_,8\arL/7]e0gH,<()&'8N+`h$xD[t7y'^΍O'5o{Gi@ͪZxu`9nUql:?;Ɵ/UfPYRi~ ~L3s[_DA޼n7&\;JP"jOo5{Nxn/,w+~u Ђ3۞k4=yc^ONt#ٗv@KyQ/T]ekş3PyjʉzPS짌D^j#Z1*5iWcYUD`8,ygsC+|*梗e5)0Z7gtkYbw_B<&jHz?g`nX6A*Lg|fIzE)W۫K9eY ,GTG+w8Ϲ6#;c}95v~VfocI=ş 4c . [wyyZc$v3"\D2ЬTcɈ*Sf&8Σ+@`<*ؼiQs"l8a^E{D&lG0nw;u "as9,{UsGiNJd**vCh8(l&[ P 4^xFk4s+kH7Gq*~غy&7fҪ##YTBs6wkZ'$uԹS%XVXb8ȖQC<*wfO ]U1&(]|}X^XbZYůevO""ZS{AԲ9'DhF2e9yxI atf[_b)kVWyD:@ɡS sm,Uͻ.؅w#6 )w}Gxesm<6;8/pm"'kdt1Ny0*B uJsaL.x1<䗼q%M#_Sh>lsmד nɇ_|S|%;գo4,R*_xq2v Uѿ Yըxc0/ IO"Ҕr+|jpΫڻmyDha98b&͇0p;^^\\'!=vB\Å.+][Fx迿s/xo~[EwZK:q!>^Rg4ҥ;ϕc_Ncȭ/Ctw5Dh٢g0ujSe֖_VN=>Z32+ȽcS6R}`VaaϨiP162Xa9ZrMg.|_?XmxIr-mW4zAkPЬ4Y&ΉZhnb efom}kNMkyU? .f$^JP6̓1 ,>BuNgav׃Yf3VX+g@ٕQloqClJY9 u; gu|q9<:Ę"$`֧SU ){*&x*v'tD`aÒM4EA)i|N;MI]h>Ou׵Mܢ͢t'YP9\,ps.4oWyAݕJ1І !S 2uɉ(bz @ BB_(Z0L&Ɏ1*0M$1CITZUg DuٹʩhٲY/^/b(  8 WbMjHR7&es )>k+5Wb$5﹭wogޓ9w&` 41Q'PV:rz'5C; /dk`9 0++(gh@B'0 t 7bt7̵,z7o;ADG/sa$",r5Un_?p9~CO!GЁ:y=4IѣCI]Eϼ=u`*VWy(˱oN!U_V]]$B`S1a`$72A2լMXz zª̓1LO-xUKYsaZy۾U];LwKW0xVgS8Go)vS:hȅ)EX]n1R`2`i|g{G'[) òcAO~b4g|@( Ĥ0mcHen<)cHBWS*u^EQ=ux0N s6\;ors/.YoޜeA+oz! c;yen ]M#4sY~+sn7qM;)NlкQxW:5 dfGiH8D8)9QTMU"ɜ\BWċڂQ*p. ɷ8g.Y}s:Ϛ0>~.<-N }_yR"YKQ#S3 trӳ.O(S"ʲ6'?oO]٨5<ę11U#(![{U2]kFEtHԕx^F5<2צ2Ws{)}f9>ȏvcݬˍ#CPFBYEe0\o>z$Y1vՂrf)nn=iwO "x-OYAZ1KV}-&Klr]CsO:0/Qh.KG[FHCK6lz5j ye07ooP50g{M7>ArkSLBfG{4oW!!P]$:6J'`Å4M~:ŗ}6;/=׺,ONxϩ>Bm4XHN P:)t~9~}Z7-l 7r^DJ)Z8kMf#b:dcGũ֠.Kf`%55dQJqTw'\񅷢cMjXl6^N>8ϪUf ηyٻh66*=h*8c4c!Pe.ǷfE k9rU5x;1+3R@ڂcX=1j8@Yx1 9(AYɒ:=NT'Ute)iKuX8,wfYCW? k/:-Oyw/Don.Dbe+ 9?g_yøŒT)I`5Qұ|,vK=.$?~J)Txrإ@Qe̫̲0d]T$^l̎ 3UZ]ʠ:* _AW4j}՞T{Om`mΝGy4[n,y'|xnh2Уعhv(7oqï$&zY/gZ~CzԠ@AԙO(-HR4ߩItKde%s|I(Yp9GDe^YgIVAR'hOklf{6:KOh)Ӵ( Uzjy=Mjze`W;9߲o $Fop#Oŗ_/[K}Dƌd@r.[BCGe&ı/2b}70,Br܎PV54K бU|W[#j`VJ3VqF(O^v:NEd˾}@W;z;;!l3C)l AF(̚|'wLzP*Op\òIb4tv0`pՔ]ݟGm9业KXסmB(ioN5٥"R4(؎s*‘gǴ T‡CoE:vmscbozlRր<kjb6\= xr)٣2'0e@dѩi0.N ܅| >*QVǒc@YRu'-C'{5jCzM̈QqrM{$,OȠ Υ-C21ȰmO+J Nk@y'`NjtFMނJ;[z .aM"0Ile/ۆ#6o0kUVB3Ďeު,[W]V:^lR.(W׫8$"Z`l02a5{ Xh2o`8K"!:,rٟVkz4֌q54HaV5?k8o(l]pwi& jQe3VL%}qwGgƬ16-ԍ!j#ݙ1VU?~́Erֶl&'c"#dX mO]8π}Jo=p1|9*v³-(̱'(WJoƖyLL8|2MM>ykq+VO5AV3.lvo:.~8EW`nՌE-6PLЙ2Iur jg<|m{li3'·ץrIWucѨ_mvBu#2't+ȋxδr FHc/ifbB}'h"bȲ}(8~s#a.]uA sZǼ/sq/hXM&!s*f=Xnٞ,їGWh3~]{Eq)#1*Y$V(W9^}{'e>,Ym[-K>)˂K U,MGvJ I)XAYIfiCqR/A`>8(gYSlYY/@n{Ni`9*Ծ6F]_p;nrw+F;'׮⛔ZasHhu.y6C  $`ƀ >·Iſxc. lAY4Ia]P%sO<y'0p"i8ރb&K=X6Ild(elzCI\uD%?u\-[",Aq' Nr<F=Qq%'uSiYrqg8C jԤ2i_)X^hId3d-0'J^UW U'fXQZ(+ǭ~5e>gp=FA{nxک}_;I*( s,˘m.QkQy'/T"Ts% [?oy͓7n,w-Z<'s2&/~qRpQ|W9ɯRGR'H60P:qW Y/e=uYSW7O@$FHhHW|`m;B.{ 1fQҪ˩:xT\JM9wK9ke$uAߎ$//C,`8DQaA2JOwxc_83[Eˮg9޲9hh#BǔdF)V=RTFre4y̪nd X2V ŠaWf~S5w; TOӈQm0E0n9uhG0*!6q=ay4h@M+.3vh@a5zzFIc}ԥ(*߀4Ъ<2ȐQި1j^uᔰ<`2'(h:yX\GaSVkDp.w">fa8{qa~ ؤX"0aU=P0*= G*kf٨c֌w teQCa$`~32Khpr8?_J3I&M#c%T Q]65@(PB`Ϸ/coF鸎.4iTFa΄K7*Zk5u33ˤ 0*qԈbQoը1&88 HTP(g쮪Z?SU7:}g=hJSTk wϷw !& Ktg8}?y[OjxY:hxuY(L޹<_j.:qvLm!+ܢc^6,//ʹg"m4IUR].#mۿǕ<(r(A VYZG@joG)E3ԯ.fgH0.cUW/벀2/[,奂f|OPϳPzwA凞-`$[*AY)(?w[WX[)7(w:mr y_ |d{Ҙ$"~:həp)>12^l&/=9܄STBf-WxW ^ !vnd! +M'T|<fEs?!kW _{= |hcPlz}tMP]ihHmp̝>1f|"ƻ-цg*uTl  TfiM -L0y}Xǽ|6'0I#g. 6HnlNi'"M(UDTe,gYAf<η ;܏3?E(r8Ҟe/^uM殤tH% T)z2"gD&#'B@Ўn0I."ҊZջ2k kɋB9Jxَ4:$p"/(y@FS\>U3DkN]R50+!-aYJVk!G$~G:?9ػ|Os7e'E7mfD7_Jٳ$[Hmxɣ;ѿ ~ |3{QgNԋktPFqݞUQ}#*#$Ţ ofıubaBM)X^vjvjLr>Ǥ_YMݍ&$yN"D{q,(KӍ&XjU'4W=Q\C0(-K@cFFÙr wGxZBaV3Fhlf)Be{mAߟcݺWy?▪e<9\{-<#9޵ QMU7b Q7M˝6Ge%~_(yC7l% &Oyʠ[O*w"v䰿s} xleǘCcr`( KtʎLdžt8o˱Mv{G6/:(xuNOw͈[btض+p'>NhLa#g>eLwU*D4ҕb9XVAwB˅CTsXǂc#~OaxV#ok5`a9ڨЦZ#=$<UHo6yȾ⿮thc$< hT 7},G|9\V/x%GgHJi0BIQPDK\!6]8 -DFtnkY -( _wYX( !Wr̀ı"N4QƋaWt{)n@s0%,7suCO sj.$vd2Za>2Q)E]E] rSF*Mw9yK^{Gsgf7 mw]/scU/&:Ľ݈enxMނ?I+PTeA5Xb+c~DdE.7eۅ`+Z\B$<=Y2^E䢂oA+[þF=Pa)9핪>6'qC/*,˜;Y`wfZT˲)v ы xⒷdԅR/锄MHpOa(o!ﻵA9IUA1??heA_3(pnXyz?3i戭+/~ߟ>o (s-3<,9Fg9ܲ~wϳ_2^+]P_-QPW!mKXԠm&kFVFEa\ݡc_(w_ǞN"TQF:xm˶XR@ЬY,A ZыHhE~"CuMDC*Daqtĉfp(_$5t^DD^Ve.iaFTn\ۺuD`|)6AbH"El4V_JO (c\eOwыLhŦ~7|iuY:R]UdՂBEAŘ%z-OL-A/mq(!Swo8myo嗜`) Xff7K?DPNx]XzXGVF W v?g%9,k[U+[֋0 eEC$ɶuwUW MH:IǠdWXsT?w19ʉFš -@s_n*"|˽=.(a)GB[cH,נUGCef&,{rP|` bek/m}]iDسC kX*+F{r+qVqD4a9;I*7f?9[cnh+o=o>s EDR0IeAssN|O~f&,ePq # zCJ8-}91Ks',Ѝ*w FZ\>~otَRSRsHRòXWA9 Q9\_ynAϾ98(]|wk(:/^8kf#wf!LWKTUOS?^%&(˾%vihA3`A~MQ^+ֹ%;ezT3% ђ_J$sKeN?1 hUlgc&f'F$yaz532RW7SҙK@rۍ6 cyV)]waH <:QWI @LNԶv_{9; r;ae/FR '޲S}WڵT\ާ}N|\~ Y,v\*&lQ -|tp7/*N"vW ^w(xFA8noro /}u?zg;K'4yPR/*;F]nvl [Z2T>!3ݪ3aInW&]PUk3Ѧ?H~ 悠*cBefž!ȹuY:.YRv˞:w9ިjӯ>RhEiذ0m0F7:D*C);0dڒa4HԭwK!FYԚ81[cҗ;ޝ|SѯL}c~p?Pȫ8-}9~Z1J[F7&(#2S>y-EGs{~AgEra!0x)T sn}=6/]`[)@6Ҽ,WD88!*<᡹C ;)\pUj;L& I *Q}.? r*s-DB`UGxNp sia4l{8?[OC(pU( _ڷ+EEVפ ,35TI&a?]zeᬭwy\x;|f䲗-峄1yW(b5-E:d^\~9yϠe{ϲvYV1`9RbaPct98a@mEL _b-3kwWY9nIB6;m`E೫KSډHYYAIWmTX5>^CΒ9˙}Ȏl~e| Gs`eTo2](ke;ӥ?_Fk8g^r5zTܾd7eqMVVsWgݾ<\ogJxl5&3hS,T@(-j,^JavH9_hG!V.kR =/:\c'UDثq$% G]M۴JVyޟҎT絋a2#_Qw6T>i0ecV];e_䲩+岕dx&#bnY=˚=fV .X-yjS"6޹ȫXrL8vmS]ۺ$c<5O!a;=K_Y2u?ȅ< "oBf8=Ӂ>jBq8"[̚ }q7^ l҉ *3.f e"F:H+h.;)T,CPXmڴdG \u|O~jm}H@JUwWJKF⁹T^1&/tPUIR y;Fx.쬕mE fH⤺^ܚwz s[c1W<juk^C.ix`" )PGZü\.fm`yN+n,U~~\bʧT+<ffvlObW`Al`fЅX_eƺ`TUN,Gls;n\û۲ܦov#?o{i߬ړ߽Ӱ\M~y,,`{ -w\~ei89r1<bB1dK 6G#]CwUd[ͻ"BoGoGwKoYfry0v%ȧAB7-Kƹ޵-W> Ǩs aƆag>|/ M䰸# p;ށ(9ɋn={J P٧evLQȊ3SfڇhK4$):JP^߬3W7^>#?{][[F1nE/YZ5xջ"K;w]}-'?g/'?{3QFx.@=8f1U=\whf^e 3ru CcK x5^ӂR… W?x KMirhi߀f Xh4(.-n σ'w?idP8ꏭu G T Ur''%07a27>Ky9 Wh3lbՎ9_ya_ !AHʞX,oZ9m9LOӿ]j$a$V#99ffg'IZcV-;ck}fR^08v(8X\\dk,Ov4C]$:zHhM/6t#CRr̡ja]#,^*³WTs0_*[w W^ׂ䲕uӧ,vμ-`669#32t]W&"gh 23c ۍ\ū׳$,W:QњN}.ryO~LS,j\Aצ;JYیq1#![|=@c)fd&i~n}^]۞t,-H;Ox:*^- %Po1lÔRiCȒ/:̾E"5$\ֱak^K`V9@5U^a6]$x+nі\qi2i-SR5sAի.J}nuUY)elL9_xfAef*hd~d\Hr Ft,}9WΑ;Kkc{h.l-ܑ-_}ds_zA tۆ[F(o? <;;K0 2ejCUfshw^8;G!Ehcmcxh4fq~'ܿBoGS -6ͬa@y+$%FKr{q/ +F?:ȃr ,cbYy #n\^^ern_?ڵ{MMy$, [!" j`]}O ң8Ƌny,2ba}r/"H~bΆ9dᯟO7 {8ZףpGV]}[UOީWDkG8&}L*4UfPg [*ho)+Ys^B3,sGt}$ u(U]q[(2-Ř"EswvƇm m Ƥ%#},fgguđկJzefџH=:.9.O㯹?pK>qS;s6+4,1g+hޕa|_4f%COcyaWKG n Z܋Lc+L#g ̅uܹ8ǟ}VPs_^{`ffUeXn?fgU ?=vaYO:7?oe`CLRR7ˈa̷7rw܃2걮a 8gK{m;x߷6W@5.套DOsk. QB *1?!\+1ZY3v7?tn?XţO/㒫AP޹aGk7,}~𲰬Phpiк/.Ǝvnßjٟ~{#V޼C\_ C_f\Q Zen+lfmbm|%>XKjRe. jc9ǎőc( ||n_V#1+L1.ٸ[2<;oG@yvMB}Z g$i1&Gd"o[p4`=LPM;]nZ3Bgg",'ۺlfF6kxϖb4?. ǯ߼'ZUQoYXNUD".f8]PE|c.gfʼ!_䪅۰\vRFҎ{ӷ'^O'MSbcVlY&\=-KEJK'd(YYx5V롕۩0Muo,lQ3*rf\#ma.{:3Lf}8YFq^ʊ4E^i=0:׫ФFX|Za +nSxg=u %(w`؟cŊeXu~G{) =z@Wuli&*hU0ֻ1(TA!yv,;1pzƴbkXVRש+M4wKgovUWWFQ+T󶬖>O.@7D$RȐM5҈faaѺ ֊g~>ܭ>`?(ovɂf+< [o}A8b߹f_Vec*7֥̋OQ%6vjf~7ΜDDJZ,qMb?ڀE qnlq{Q"#۷,|J+aY G`Fkd-wYᖢ V֕*{֤9I:H}'SQHk5.#A {[~cCqM5}/~`K]NUDgVfiL±1c;bdYs.NArfÄ9D7tURqf~3їY-[DQbH\;Wch(z -ahsӒ;B{#ؒ$o_/-OHNJuAL'Nk#,\yү1ʷ5)ޭ|3l슕^(sN{f= Gy--%䟈Q8+pqD8q;ȉP z:¶ 舾[9Uq.Yvwѡi jfβ2q=岕u3z}фYf Qw[a<8^Asۈ|9xjX^y=8ߌpw׀gcw hL%@+bWzlX IDAT h4_]׿]XA5e-?_uuɘHbh-ۦIȟoGh"*-#,2sCC//}#j (,eHv =8{hfVdAb}#eAyXNFB}1S\b֯HGt:uHa/y÷ ۋ*϶=[]O<{tPQnt$[$, cZ@J]փ`* OxDEqydR;*b`)U9U ;4JE44">:`XWw<-9Klݡp+s^§oze8/=|WJ4|%4\2Rנ}Bc8?g" ֦ʑ Y s+XW'm4:5QVҪ405tOD f]O˾F>>>xҬW_;J[v{63rAyժ=*Ś5{,YзT+,󠼽V;zK*XG0OCs0-&5'U`\!c̘?pf,bJ1,B|,`[aiBsU`%@~:]Jsٚ۹Ig E X._+|W<(r~'1F"jhVٍci`.l2*FGqH/}:%0.-;`>a XBl2~FT[chH{څlNlZq]UtOJmh ,qPDw( I/("LWo(˽6'Q]W"piՀd `0 V\sfEo?fiT@p!#VŸe^dAH'u=^ig~% {KF.wѱAk ͇v 1* i.5:6uuͭu^VKh.%&0wULWG89-= ;tME ͪ0K8G]Xkg)(Qt<4f8ix0}=c[ p h*4"- =vJu6m]]ʉpF!ֆN  Qz "RZUm :p?O̲ OL1C-O>?w_@:膦\Cv~|0S4(ͭfݺ+e ɳu+RQ#mޕ,3?%[Qzy ƌŸ1}M( Q+[2B{\D":\I8YiL}LGt P*Mr]w+[Lg ǖD&̪c|5%󗾎bAuMfgr-[5lZG?I-ob0v?gGj]-X*!9If0.&2XN{)T,&l[B`Rpf+0 SP?V;*bШďJ|ܚJ#Y ^ ,#P΢"w7tibr aa&uGkKFÖ쒊CJon'﫞Q .&q(tSDK*UwKޟe 0HV,kXs*s=r^P筡 VKeFX_v>``y~~=s ʽldU^"nd+fw Ox½s60G-#[XY+X61#gT6WuPUOrixTEUL}XDu9vUl&܂ed'lCkpLfn<3r"5lV&(-S9? [TҎԑEG'q.;tf|'|yDq^}e UF^VTr44qM{F49 pc:aX !z4wBpytAaV]NCѤƐD>y4,AAKHn6|ٸ$ɲE|, R?rAWsa<.,$'4AJD"(]f&''" CcIAߪƊ,`$lrTVJ9*eܡhV!őW5nD58>?u^둎FzsϠm²@'a1Z?eMN9QvsLC^ (Q²/K=$Gu< mY-,bرE9ǐ/.*96\˵wҿ\ǩ((&k@*`^f7 CNlQ|s#$08 28P; /~=ݏ.,tT>j 5Le?ύʐh[WZeR0㛫`/Ƶ eJrϼ-`^k|ʽfΡF`_lD/:l)hX^zOYz'qZY/֯ߏAjepUppN 󠕹\ e_V).em^:ʯ+ ܬDn&OPv@'q8LkA:kj*R  +%O:RTc ;nm+e]I%ޘeC J# q;ko_hEGZ]U%b4|e'hA4j /A]:ܧcDbZv|H'w93W™.iPUB\Dh`hJIL2 5+]$|Є:ǖME6 ^u(O0&<{R[о8ECRTq3 ƙg98_8[ÕW"Y!r\/!IDzM<4&{eN3P&TfmsGl6K^8^}S}pbEj| AYkn&jt͒T 3&`ڮ$-0{Mko5JhR\v~+h*ˮ{kc< G S² ;ORs.k(_M{`CiMEa޴qA=պfLW{%\E8Sqó!ag{3j?aǨs<(,p!7Hr 6j8`i{,A Px=):CVK`󐧭݇oVb!յʬO() dŠɐ-( +[Veo *k}?}ʽe||^4NʎUS>Xشi>`v>\BrVDm*UyժS>f+kw zv.yu۠{Ǘr.[Bp9 _a9l6J5Hd΂X1hpeV`~ˎa>t/YIXעD$~UL%q@6"#p-D^lZ:[ƃmoو(:FjE(6;+1r|Lj&Nmڻ\.%,87٬~&8k8q8vk4):!"X1y+L$1JUNRV=,Ԙ*"ZFyE~d$"V%C)Ze ɋn̤h~ZU;`FY(RIu-ʔC: 13e6;xh0Ӥł*Td2[[WG=!b >K/s0Vs8vtVDSQ|+~ᗦ΋{%<]tr S\=s\JљyYfﭵت"+ қ,kXc@LngTWke=|^[[]3e2a] :rLyZl:\΍Q7޲p5 jnŢGX~$H hI+$7ױ}1BKB ɒ$ ,IDJ=by\.p~ Ң;:?]WDǍy7JYzFK杀eY k =IV&I$ `^l1Y/Yt޳tW AU֮Dc=0 U)I+Ue.EXNTƹ5l0v Wk2MA'Y!Kـк0,Z%-u&8af2Y"1^Y6:CDKĺɢ;ڻ9X\'o yk?= ,WVidO}z$m@;v`8w}z1ʺ|Qd3GN޲I(8e!%`b;^[Yc†`]w8lu9:Y=/}`є4*8l]X!Q IY"SShǐy\a+<0̉??u60^ɸQ97ƿreBLg5^tk?s~?UN*P%C(Uu>&pdxC:RHEQ0~As7u\5@ ˮ U#1k*KmZ9a%6yiʆ᡹O0F8眉 DPcXt`yV֓&bGfC"7,mܠjzvhâU&f[FI0_)f]0D(fIZtA9j,kHȓ!Ek p&lۨN6è̀cqtV{v1׎W-b͍; y/˽g"tw1I.[][Ƀ2NJVgNP "U)JB~D8syށ'Ǟk{ Ln9SIU&IeYOI \'E[f[.T[*ް*@rT4Qקkq2X(ƃsKY+v 砷>NV3g͢w $~ǴQy[u]] |Ny7B }}>(`iaHDy/s˒V;`"\yqrZ<ͪ1N@󬫶Q_szJa53` $҃s(?lm[,o=̻}  Q]ٰa*#1GߙHox"8Pc{ϊ$\jlӰ5(wYڿve+4%#]xUe0I߃6ahZeZ~)`V:̉)Z97ЬkòU%+1*K7/]s*Wlgwsy͈q4ƚ7zװEgmJ=wdْhIe(*5 Wrҗ^x;FUY&>"uz^Ny̷W| {QQn&֯z+nvC={ܱdaŽkrkQkYNp' vs=Ҳ[P]2fj([vYf_%HTFb s]0\EJ-*U CiԴw+ƚ`8`{|%[6ҝ֍X>Y&9ua[If+>1GtTD%ṇ(yy*u)FX[T9b45XF5X KS?yGQnscUv~厒[77zmI c(?w{ž#`:J5%q<ȧKY7҂2jJ$²Hl+M3M(MJW@*lIZJ;F;N" O_/ƚ8w&_C5/r1' Cg;$iG\iPF߷;bP嘌+mA%Vs}Y_"(/I>뼯sˏ|A<%j>$mryOsJ+=*m*F$,"d_}o})e#μjNjo9uSF$+Ȥ*O{>!=k٤wOK=,g$OZaCe${sS+JZcؔ6 #mpLCPy%,JZ֋n񊯅i|o1'$8u$p})`)]y~TZB`0Y38'O Dd¬D>VO|IBOJǘX]mp"xwou?JX-Ύ oxGA YBusoʰ#Lnv1'c8'35GpQ;oI 7|`ElKFs͂D'n{WXZ|X`o#8,[_dxZdy*dy6KƮ#xXo=MTۊ-[`a;z[b3EB[}&D!aك^7OA6n.$,|b8Ζ(k4Ьe=dnxߣ?AG<^!wkUamI*j9uvj<c7jp>M;FUay0ǽ͓oܸf,YðJ;F*\yp< iˮB9+QE9nG p -mrwm!XÍAzބ |h.&d+O%ӓ~&TǯUQiEUi&Ö50)}ݳ s ,BX+f w:|XU~=Yx])[ο_I4u ȇ9I`6V>Ouh¿T3iEu\EOle'ev Qu_W "rwyxWKM+xϯ+u'b|hOއyO< }[.7?7sv`Ƿ^a~O^CO'P[ܚ@l*ϟ֓"il!>Q:"p%me6yQE:zڳpr7'}>{%_CW jgKәN,|w.oYM+bwG0\[+eFYϐL҂d헔&t9(帢͓QdU_ܷmCNT^vÖdťE,mO`ut١'bwD~'ǏeJ38Gܲ 7_iߴ:%iMcŅYb_ludp<[~ڙ߿q=]ܺEGa6JcGjZ}{LJ,i (:c剳%1lʇ$_3bRF˂!S S]rc॓ 7ИDzlvE7,'U{?@o(ذ`0HHSDΚtvWy+-U\2)-I-мaHdP,!PZ:aeJn=έ9B аeC/z0 *raK)sz6p,QMq8#(,F5y]{?v|zIAaz>+_yٰV[5(=Fl,rFN$-؅5u5|>&A (wA݃b;[ޖ5FF巾^߻[vrtљVn_I$*F޿ ښ (Qx۰,ח2czN4O+Ơ 6Ghio*[RڊVXk:}/{= h'~TGHn&$EٙmxvQN," lF:)YywD)to׳4,I||_{ΧsVAe1ӣˆl\y|]fupэy)Wz-؎-=}ͪocg^kmO%J*-FTEe,)XNBV"\pqݕ;),*ʢJ`'2_O8^ﲔcK5cC׵Eȁ2 SD]4E+ IsCiR]_~Bx ǶKw?M16$<B5PՔJ۝vs.&drƒΤN|D i)MYp;T㊞لXUVؾ>+rYL)m6}:I!Md{n*|Q;ܡY~$S*SG#\RnX)ɮip?g/0zc;[N7!Rc(}R};U^l_w -B1I & y+Y(ǸR|; &1|3\;d0#GHY$vmA{< \u Lт&@x9V/mJ| XZiz;N9RQSd8Kamro& /{]w#sMjLs?EhzƃW}y|Wb`th\1,iMrۿLGY- P:#( RuHKYn[360kQ eR*f_݁ċ ]u9@s,!v!}9 ח]c|dl{O w8siet9^ `kPεg\(f,)Xs{(IH|Ӝ|Ygw?|OTb<,qςf̭] ?z(Wrac}h4 |!WV*9uƏw*h@oXy[. y$D`VZ K">W;U~:CY!7^Y;Zei➮m_€=Pd9=]Г\gۧpicweJ|DxAr+9m~|V( eVqr9alvs>63~(R$RDq#~I=)B$-!M5iHMb47q.iBt^%bw=8;2]kN[{u;\l{h6?x6-JǘCmXt"47,5KX4]*NL{H*sr{Q8XKش) R& NQJ:cl'{ɼ鎧|& :kYd:k)&*ZcnSmUӒDSudm;+7w_XՂQ@7T2vk?5Xn nnk=sW=Am 9:;g6y YwVZ_pcޟZ-BtZ'2o0 5Ha+*qEN}~u"F!c3GRiJ[14ʖxdVOS%6IGaN )8݄WGu8We*˦ &85ʩx2jm:v²QjZdJf5-u!YP:*j\^q,\w㈗//"s #Ts0 #Xqw ˂Ky+skyȗ&]K3wR(cP?_G zs>y_˺ /> T魯!f^nWu[QS 3د{MJ&Vʍ'%[[~}Nǻ<?C_6 cʜ:" &㦏*sbB$Lʊrf4+St86,7 P0Nqe07FdQQVk9 lucp&VY9•ghl& nB9?HW_~vwBe3V:(Zn+Հ2]pb6ܙrXg(X^fZ1Vs]gt%`EIhXaxNr;2ݗpYog[|}]*f">f?ul";tuX͜ xc|?_1%*䌤յ|> _U8<5\3CB([qtsyڠ k2نRZUSaoc+褈e=͢M+!mhD`m`J9eȰF0HHגb8NWYttak'5&HV-Xn=Q3ʥ%y˝9ՙ8zab^Q+7F&B9*T>f4XFAYw[`~.gu^h~{/s:y `}*osݹ~)KԶvvYh,&~6XyºKvTSciUP~;sk9V-g+S{4[Xޣ.~ Id}54/4V R ٫v{zv``vOgG,b%Bo&$ӭX8kAs;qTcf]7ٰtn>[Ub %k@^tYr;! IDATE,EpTM/`Ѓ~/Z겇dY\Z8ЖÞZ).͙ĿeW(t?|%8&$_DUp.;~M)37\-[p*#S~10y|èƢ][F嬏s¤:(ReT%Ҍ>wLںSBaW:_Ͽ+eägRzx+GݟV> %;%mR'y,3˲zf4äa";̝ǫ29l"u d ky.|Z imØ\©)/=V{Q-ȘG}(﷓IW\wԻ_8X0mB,/w͂bkaa.6e#9ZPfl<,Sbۏ]Y@6,K[wï;ѭ+Ҳo/Ye3~> @}$@Z7 Ps {Z/%Yx/ߩ)Үc3X?;L*l-gLS =(!4IꗕeE9NhVTcUb[)N04%$9xSRV$,rL5?c ˵YZXf䐱GٷeOǠȑA^gP i2S]3x?GpaY,aOkjLP)a5$ftQX@x[#O^r~dY' ̟#=mtal|YmHhӌO`:O92fs^dk{ǂ17BlY'ŻT1R%2a~eRFt+Gž}\6AkekoG}d[3a hqѥ d=T>@}T>@̢WsH5AJ=4,܉1کõCkt*md+zж2a: eR(T"@s0C0.X^evÍfs͖g>33F<(di-ϰcX[d k^UJQd)Rq\igޏ~_g $I'oK镅d[^./zp*r)ĥ~^TM6#=͔ FtAgS4w* wnc͈`n'dj#6ӏrͅIC A†8R,<,'Α:K^za\M!U[4nmX;"f e+0-\pzKQ|?Aߗ"O$wUU{vJdfI\|{9\ |g˫As3{^IMrLPuZ)X D,Gh 6YU .CUsq.n+̺tC)!tXoM@0Y4: *,XaplS"Uq5o=JX2a?Hs!SQ+bJ./4? 7屍y 9{b>8%R`$'^fYnB5SbFan6a LR!)",Nq\]9n/{%| ~SeBfgN|Uw 1u:V HaȐBfxO٩Bim5,x/xz ILs8 D^whEUx`{L-7cY+ #U4k :5ߗca(k_- \I퀲*Z s % rT%0ǚ)%C}}Yc^F֯>LnlDH%. (k2lܜ80Kb yRd)Idf]  eY mܚ-vy7B+ #('i@Y׊OV֘ij`ې ,:X JR]+F6/U7+/y >rN$9sI0-"4׶ɢlENI -`gZSLl9Br }R(ʍT,S&K䚣8|os8@3ld7q܇?ձ7?ɿ`?bݕ-X׈Z\|腍ON \R|gynu*&Z2i%Lct&I{J*5AUJ1&si&T->^n twѩ6I^uY'Iدmv6$ʹ&CG"0?qo+}I394[co MB"SzJ́vR**WDs?j0ՠo~ ⦛ d8wa ۪ s qՁItp>X'ޤAx /˸͸οt-~8;-Z>'n4?) , T1 P l (sT_;N F5m٭ŕcNTFq'}k'9k~ǎ79I-au8J2S~<-]Y!N度4^$ir'ȧ"LF7(*ؤߎu=|BqZJLKT TN*Y]]^Ϳ`ki6M2Bi2c3 %vy*͍5 eK VT2$fCX.j-;Z,ӓ EɁuI 58G[FU̻  6'e->>Vn]a:V] @C5AF:Vn)6?rwɃ#0)0 t :щ[k[īˮ=s.i]*/I-<^BƋPF(eC;La򳯾cn Rv Y_=)M{l'G#z۵(Bm;3vyPT ̍#8]P+fGI VlC$CP@@YjU%_>y2q#? P_ *:tDurvwf1 0g7"Hxl vv2zYGYn'b|]pwGat)M9rkLRˍ܀6T+XJӦC P<sf܆玐 y2dZd.6Koeކ +&Y+ ,[9b,HI{9yg)iUU=wz?fp`M HNZyPV.jX*p +ǘf=4ct%#>!2< w h!ġ>+x:`&߈k']z wo奡 v0A ؾ;-HD!W(UAcM id>6p8-Õ#ʏTn[>>|F\}Y^ RJpyȲW/LްBR>ԝ뿱㶶G[^kr RNpV#D*_BI¸K|-`~,/ʒ,TUɮ8_o"ni))i,ȭM5@s?Tf[B%J)2RYs==T,-8^նV2iwspLlIiK*W!G~]{7= A+0Zʁ434϶eԝX/Ue5\@ y_%>MRpUeU#$-pIKRN@[l,) ٙl~0Y'suעnxڟǾ}4K;]~ |{7zhԘw )O+̦e#*y2sNh*S`+XM-7 Wo yO¬RUbvIUKano.X|+4 'zV:-Hpgl0y[x>'}yY AT20M҄^&&lO0PGV)= n؈Zoy`NeMe;R W4Dž4 WMH-=ҹ&0I *aW  7 /'q \"lQ7X獜stVvhE^s \u6nJ8@mRvaҖvỔnwmrxH/Z0Yʾ- ‰E0Ru^e.H֡u+ T_ɔKPW*7t`R\{%8g ./XSZ0ZGMBi/ls9w'Վk2bFL\Cs&H<투2یuMs0ǼڧOcxk L_S^YVٕNjd@&jKKG}܂:OI0*AKUfP!Zmnix81DPeo 9s`W;9pno7R-{hţ/:-':[cDžIѵd(0{>^s5J-U0Y w. jgsmWoOsOs㙴$QJVJbW7][:juYWUf0뺷@XfruT8lNce+6lՅ 7xܝ9@RPtGE0X.61P91YJI2IfTˮ;f.>bQZJjvR7}S3$9\[+,*ͪ#d t^a>rQ0ߦ2zXnrOA"yMg[YM⛔8l"Y,J?+F~`^ ̒er_D+rF5sK7miX/!W-ޟ=!*!I$!6Q`f9OA]-/|pQ;Ζ<.9 JDqMWDDQ$(aY~&ŴJP ׼. ((r4sNwW3saay{3Np[/߿f|D> Thl2JeV 7MuWP9P W 'I(ҐGL)U@(Rês噶L=1Ə),x́4ष 0lɝtA6P)go~@7y$~v7I+xYˣٕB ԐE֭e]]@c겯zmf[o` ); 0g ҭ $7~{:oc u>V@-9=`pZf7ͽx3ˮo-_c (Gh1{*IJA! %!}%<D-`ydġ; H l_C"<1@mĝg+[ 8_v 6um<$AtEqqPC-)Hd 7øܱ`", aIp5`\iPMC7 V Y u R>zgƹ]4H+exU bQ3t1:&>.$b~{gm RH0wÙΛWY 泟i]sv=7xu v}YgW {"xX c_Vڬ wțX5-sw'ܴ<]R>5$`$T<òv ľJ@.ɪ МG;"n+>cG[FcAI'6z\@!!tx.]l;o$ z Z#Kpw+5@5l][50̲V'&x3րHbS_y2sb&W/ЊqT IDAT^DyJT0ɫ$e$ kBKl!p/P|lp7k9fIζ3HV+ gKT2|S*9KQLPm* I0/R:gw(`~ [D&5}.`FTQ8a s{PAٺB_XfɾHVrCl0o`Zn'c24G1,7*uS2YWrs,CP*AY3Y \iw.<pQ|HN;c2MM+Ѧ^a 2VI1aIAwޢ7̒|cj[;^=oݻb p~]oy!\kVQ)'u c9fUnx6@47}ˎsڏ|u5#'5j!7rɌW[T??זmPU_nΖ0݅SiϿszS_E2xP 3#=i.wbs?++u8hON[g~_v$?ADxuf;3кed)…Xh'Ck|L9E]Nb TFY2ʵ۝µ"2FKɤ94ө}ePjRfVA$|.y%#i L2 vu +)Tf9%7ik(uSuKraYW'yMT6SOKr aMZL8Fs57p,~t) ;XSTx9_,E* |%Φc$)Mtw(ml #7AgX>8W} ٕ@+PZ^}[ |X'&>ʅLLT%MC]s%xb\iqs$3.9jP/ʼbق0spHY}м_w׾o |sOTa5,Dp]|pG:7 P\:s#LbER^]6Ր s;6sxoY0 5h@ЊsݺMH]ε7>r_n精pZ^rWrNli|[5h@TsA$J7 (=4+1w}\AY 9BG??XTaƁj~q\Wg&lV,%!uu΍BȃUUUqDG&h}>hfvpȐWEG|kȾ]v:"Z$:pJpuT)$G` c)25/v<-s?NkrA٧0WŬ\rƓ?L;7)>,\v<({FlvB)̐,B~.Di\,) R4d\r)W©@3/=_xNĪPP@ xe)WemĔb`*(oAvrf-;݀Uf)-+\T~N8dpu>\i`W.jzVdS~H9zsq_xCPAha_"3tiLo4w0l~T M簧l(5XG`&>fD^)#JL 1[{?5ΔoeٌuBgcK1#HipR Цtf:ˍ-9|̞?ׇPYxu94"@ԫ B=Q|ZK]#oi L( d4T PWg~e)5ZĔb#cT QZpUl17)yЕiaf ۜrES U6hQgx_F/b%6Ѕ.<0glxį6w70g> $~ TUp(p`-Ş߼*Z>\k*M5Ć(,sm RyR$Hehnk@9táCYsFaڿWk).\ ƫ%`WYl'³w]/^ГۢhC%|DƟ^1VP>uzu9(l_^"Eyѡe,Sk>@9՘csFgyq'An^.#5|g= -uy$yԖ̂%t"SxjСf$o᥷boΰ<6B#^\g@02{%QsV‚jNE&[^ 9![nCeXlXZdCVXܴA2Xzu\G~cSA֠wb O|җ O>;rpnlY$_{ň#3v?6>cپII誧=(sRQgmk YΪ)ks:(̓V㷧aÎӟ}T&u9 ȌzdNQIISf9؇Vs,A9ht%YmSpo ;Wc[OE[G`r8,'u߼qIֆ+g"4 f ;1u4mzKe <,B4c Ww)9уr-)yTJE6Tn ynghɯxr@#t~r`A(  z:2k׃ 9BdsCS Dg p$pcC2u;6Ѷ׽=T݅|yTêζ)S훀ܙOJh)s5U cS"8o:G䭒 ttʻxIc{9@\[ :%Fpn_Qvd_. ԎcN tnʍ[`œl <7G|j壧I6!91^v~] LKlqqYvzn^ξ֟>N.?y߇'(]+<NمpSք"cU9l9fLu<ȲL[h 0(];-9iLr5N뎆ίnr:r<#|ߖQ+ $As Aa^)Ti;8{<IE)ro DzDQL1i! 'ŧ /:ۍI2 6,#}YuX1F;/N~չO܆Gw(t'̸{1DM| XϽE\?Z8:nA9/-X!j@iqztҵI;͙Ox+>i[]Kov[w]tv,x-Ra(ӥ6,֯C kk9Ͱ\p^rɹ_;B֑-;ܨaugμt< em0j ư6"I@mHiQOhدjin`XGN7Vg.^+3}ro:kS]b) N ~YGv{dHlE(mG GUF]Vne뀢p á``!^_@Nd]1ڡء-W D| k"B9 4B=U FQ yZ|o7,C{ާ'|L9r'?Q$]js2D gF/6<7\b-2h18&Y3-#f07 )&BsS2Ѐ*hy+rm5y5lXXKM3Rnpl)%^J=:ڂ ' E'ռl-a={e!` pfb=x:xVAȺ#i0O9:Zex_F`r&sAмb0De,YD@ :0'*Dk^maIz`\"F]Pϩ25(A% g5h!`Giƻk J&[Դ:]y吔]za:!Ak‰7 - :HVɵ|mMY l)y;Cyç~o[fl#\5eW3S`:W49בZ]#qByϒ;mWŒ-r^GDe9X3ZeQii"0;8U6R'&+|v_AlU0Egs=x_r ?{.8 o/S b!  QrccJbܨw^] eXv+1?E{*2KX"yAa Sځ֗&c}j8',)w"`*ĵ2IfՎ6d${94L2Va>Y<dH5bTµg(^T_:IN y!H(Xؗ~G*7j{X2+ |9mw.q`pANV ,۟Nlr S\XQ6慒["b˝|TvJ|k QHLHH"@xeҫuRze9Pl?`xNδi/x[ ^,ϰ|swZ7v279@s3LJJ()ZuuY+̙lYAI;V@xvTw ^W[0S e<~Ǔލ\a;b(CsԒ*H${^eЧ?HC,Lc. LMIt;;UeIxY_8K82t5ِx?BBzXW2Z1%hh vPSaM6Dm,1Xr+Vvi |KF.Rp(~l-,7̉jw7vp$_p{]x5hc~/?MfjC`,}<lAYЕ;(} ^5,?Z-;zę{4sRjzpA}w?'3o'^'.uŭ? ߅n3Ry1 ᧞0J;W8od4 +.4V86!9QC~MުPY'Uf!B>aqð1-˳ f*ɵMPYNVcPi̕rwE~aЪQSK${_p6 +̃a0@Vg;"Ha]tƭ~G#l,ςǡYcꋖx(:ƿC9偏eᮛ#!ry0\q(,Pb(`8m1 cuPJbtG KTeDo{}Mg='~ C(k+YF E= 8ơ6K|{xdQ7#,M6Mtրr`&?e IDAThssYTRwC ARvX|ȥ^%W#` ?oZnv Zw ܺp!U^@Qo\7Kk1zz/{eٯf(u ݕ킿L4K~3(+d)PΡl<,Ko2,gJZr<=Bdw*L-ܐk@+V%uCX !Xi* @ $=z AZ_ .&?(ݎXr=.19V-UTaS`8ʭW}^_;p)+y$ @'2 _eω_<+Mysyq0n!䚋"_eDl.en` 6pXBU Ipǯd^zؒ1P25j /ϻ$_ ^"CI@rM\[Mn)Ԇ̎[`fXl$2[e^aNaiT2c`. ̟e,y0f+8W !=$Yu~"!95'HeRZK՝%2{%$dؚY8#&2shAB sIN;5l.tV΢kE8xHqCNa^ڃ. 4Ht\}, wo ,MNQ,3vA#ֽ_/I/O:.Ta9#(M!S zLw t7Uv՘%#aGxLMu[Lį<8;gϳ9ŽDӹ$ێۿI-&f7{[GZ7Qa KW,~v5 |^eֽg0K[#LQ]T]C ܐGXMV R`FӀXQNYu/&HuR1@c `9g7=zKAu9q a?MPR"00[[kI04aleOrE1-ǧ/S^NR^ޢzz{^o=0CU+?̔ P\ z`^9,Oyr1Oy X|-aYz^ZVÃJu( tqՀsx޻V/ׂ#?G{?7(S}}y?⣻>Ad+X29*ER~ o#Xj^^u\M7VW*C?ݵv9>Y)(j$;ik-PWpssk^t*q@p\xA3B0kMhil\eY*8-7i[6`!M`4$#(S QmJ'U]F* !,b:"ɾG|k:e&xY`m a+8ġnHq W1QanSIX[,]%V U *B' ,jf5xm Bѩ9SR)4bQ XNXcZ,MmA3y k-jo12cEH $6*) @sOYclMɷnwG$X@#GoW{ԹSXYB8O8rp 70'ueddeyȱAȠ "ӀVp/AzZ a4Y@ c-#ޒ<8KrBBÃڃY 5s@X!ϧ@BZ+{&=G $vv79tD?z{qxNA ,>;V($d_{9*+bÉK9hs!MJ%C3,ue=N\sG{Yt#(C1&^&4jwnmcǨ*k VZo0b`m+$!1DZԸjM*-٧,dͰ֠t 7 Ok$:NfIh65/λa칛;]|;QJYNf3ɨ0'Ӕo$$L9yP|?*sp,s%'eheVl-@lɾNKjAzhvdRYޙ$[Ff25@ \@IX0?м%BiH]@`N@9'|^[VYaJaH?w9e\dKhaY).q_oq~ #smWX5;LiPff! ET!37L1f)10 ' i ^}b1EEURPMƤOsbX縉jorPY^q`} t _'? 4nF)A9̙ rA]wfê+WjGlTcgD dy'Q[{Q ,Sz=Ph=ʪglssp9­X_7>|>0\~܍ʬG_h >v [3C];ChRIDkKT'BkZǵ^ VhS{Yh34!T]*fV"TA1oԊ!14T?J pL_ۊ q~RHg+]v{?_}w kDjѥح- K ~%p¡B9t8ƳA]mO(iUҕ1s G6Ӱ,a=׊lE.SgzO(ge<`c5N9(ֆ2,g`Ίrc@CX2jXSj^XSƧHhNòb̆ѝ 5P 0gAߖ=58%RL,j`9ycc0sR4?$,<,SU2*kH!A>b)3hv ;ʙ*aEX@*l@5D]#$X&X+DV:hoz@M%.gB Z O8@V ǃg|Ԕsp"%@udyYEQbkyۂ!/^e CsZfd=0_/ s޻+)pVJ25aE*<34Nq2ȯP[8T4ʏqGd˖!0Wր"c ηU wkDtB(!U,sV!72l`9lhQ$y@@V?Vu^]&)!}l1[X&!"DlHyjj e&NW(kE!Bl'uPNݷ780(|)=ՀDyK\DT$ϼGµD<l̥ĠdȹsQ+Z::CW ;۽ J:tgmTeP?1@fVJJŠ޺gVha!BSCw xRbpe1Mco¸5\_08jXǿBLs3/dݑe!pݾ0IPmQQt]o/*gmŇ03Te 0ː-gУcqo9} dV4"BQ`s5@l/7]Ό';38겇ee)ӐJ<k} D- %?f6zhz$[<Z2L YDXNՔQ;Fx8 .= `n߾uI7Mo[wy{9c/Knފfr&;!0+0,Mee A BI4(˼-CNz PVIG>QVB`OZVCRDbmaG!s?D z?9heQ%.b1"~WQ }4|0y9 UMP; ~0`QEw졹! 5'[ BEc4qPl&8̒~YW̼sHE`>(d? f;fe"[v^rLHИ/ܓd[Q$P;V\,ę^`=ߊM (Me8?9Zt~;͵*9՚ s`w^w58;)2 3EYgK~}2u&8"0\~> C_ u"lwߥVEZ9`WG:U p]d˪Q/s46˱լ@2 X`=o_C@Nc7^4nHYpVŸΌ 0tz1ְ VF =~E:f c,;pE?k "YWR5T:8~= dWoyJsc)a?(}# h[C )obQ7:횤kkCs( LnF4ېb? {a~q$@@=ܫˢ9#s'y Ώ9C-_elǘ-]}bw}ÅD+I$mDmJ?XVoZ#hĚo|Lz]~2YB4 (ũ/*s'_꜆10,ZAHB-um7쾾qerZ2r nC2Ffպqk b+Ta 6n٬8{1#-w-/#ࣰF8 @2iGY mޭYuE[ᄌI ]R{&M@s˾瀪nۊ&o.S=2j"Ȝ3χSj@.a9@35oa`Vԏz!<.rdE]ZrgK'Y3C]yP.UY%`I B$$^fn{: \OnXhgoi& (z=P~?2H+?N , >2/)Ŋ]m/s  z)y'||DtP'0fيa-_<W=( c`+=%ϱO =Hd t !+eeP-H,/^Y-X`h3 Z I.뉤1U#R:YMPO4U`~j'ZPc/~[> [B*;^S/̀gNCr҄;t>{_ 3OIǾ;.;Y2h̀Vòo>%Rh&4E +H#QsQ!z yVUƠ,Rrܒs ʩlM{9 )\ò5j7,@[6oQjC._Z/|ϹQn7뛱8hěLe?|[l<7[JsX񗛰{w^:>WDƚ_EI'q@ê۱iO߂fM/B @)/gw=ۑ'`}ecLR$ ~[U[e\kSq=Hl鵰ǙGx+FX@ckaR(O: z"kV͛9e=:) UVp@ ,mNUp9OqG8kyյ}y!OĴ+x4}i??==Re^ ygLY>BJтdт6`,b U={?<` `BpF+1/=+BӒzfcCBD(<(koЄbG4x%nX"Di5:.Vm|Ͽyy߇RҌ32/}GUAJ,&9 4 eER*3ԑ_BJ.[?Dp IDAT[UGt1C1 otу)Lqqp\Xr`]xNF{~S]t;qۛajLc.k?ϹlA)fm[_o(ks_/fPD5rXCw:oD%~/5Yqy#83<ri6 xۜ @eըprv+0l)}S[]NJ&jzn„u֫J}t}x|F L`ȭMOЬ}X&rvQpPT.+YO312wCxu格hwN%x/pNEpqT2cVTtFC <þ15{kav˟5 Aˑfm-/sCS*Q#k޺b,#n)V`݁e޷"3wswd^qc,ˡ:pge~p Nk2DeγnJ{X#*|ְ d/hJ@X ),*i Cy3ot:tT]6uyu ehm,mr*;~-/lGƯz^5gkj\/婰k ѓ.dlVsV8x7w*)痔?$tl1RBMH2W$ J )CG+XeY37f(hTeg*W Qssi!eRX=.t:3i6dey\VcXhpq?a)~{QnI2@Z LY_׭=?\7KwW];0/$zn͞vZCw:QtyFe9VLS Vwxe9`%r^=,Q%ar0@97p,V-[ێLyT ?ﬗ]65K~ aYFhn=csݼef 4G^e[n0kj?:7tƶ-gIMdžLPBA±Oy$Eug5òʲ?92v VrcҭE][ea9I3}۠IΫ.3QowI?{oYU2`IavF{XD#-&*cn.v^1R圽Z?sg$ٳs瞶w=yW ȶE)hV55+[ٟvO[;OWWwrg}S5^YEG큍G kf5][z66^Z̧۪L#4 fʽ`ڪcmq[Wʦssy\)TѶt*7橡vn(hPdӌ([&įiïֿ|&oy:~Ofn%/SN<gխzV~%?,2qή ؚ4MT5;>)S{)ve:ι3~γٝ~vp. _ ԐCh8N]|읎sйo::TbP J ʙrS~\'2`/oא`a/Y0<ƚG'e>3w$Hs8ޥIտF ;|)HuPhn7,(Eb$ fU,Ǝfלu0_ueOj&.q гTP҇n&Q/q7z"zDeNYyւrEsNac:!@a*{{'nyu  ORw{=O,ե;;9yGÏ8a91 YYtʬz_w%;ƓuVWY]_7ES22(DuY㶿FUyK IȕC |R,y]Jbz//D *]5^5o(FE?91k9[–F[F/z"|>ꆛòQ&Tt4] Qi~VX^eqD% ݻZ>¡ʮE>\d3`x򍨽Qc7Dy<\a)Sl6ٱװG9%TʒhpvUݵݹtF%Ib &4c 9És7ܻW9mmO&;˨+CzO񇿟̍JL\dlp+*Cn(w {Wů j Ax_r<,睿:tSM[')iƱ\==N}gdia9 *T}1DKF*k1=p} Į~ѿl Ƃ՞dPL)MuFEuq AX.rTPES@]n$F9uSd\Q?dv%~c >t;>ӊ4DhYQ3b҂s}+hFW)s$k9gre3ӖvRdE̛Gq_%qdI' مnPUNþǜnzUO/mVZ/cVeU|ۙ)k:*.N;dږT[s]n7<2n\&0!2l], QѪIb0mp ֱr9KNPn>0fNNc0I1$0 3y(47פ&LSdgݞ?h ʥVUΒNaΚʬQʴ s8Hq}̣xkkIb$4kkU&X׽r˕nK+׷6&C![+hhf2s+ }r6ZX+ϡ(Gp^3 cdi&,IбIRÈ2sd7.묰..f5mY ҈"O[Ypn9Bs*gr1$螪,mASg͞ma9rTޡl+:ۊb*!xK5(7-`޶پԂB?AiI$@ &M,(I. rV*|a}{{2{SyQ r-|wr^QC}^*^rq S? - Xu{dw:N<<'L!$#HJPL3-DOMvj^j5*\$UNUCsyÁm[|Cůk}$i,S! ²֡e}Ǧ4 ]bAc[EW Jehe'5~<`qӊs$r>,,lٺLYU,K1ƴ(tRdy>qq_ym|Ib1њ3ߟ3Vh Z <˂֖1pf֐R3f&bt\m| Q1"ʙМd*k|nLm{GhmfLc8>uCP)׸˕v)GݏS;l ζ3]\Z Kw#0'$hXhO~Wey1>./Oʅ JXH:`HŴwO?deXrHب8, +씝ddfe"m0kE&-,giXVfm *S1aA Zb[^ HLpe^zx^i7?!FPv_#'뙰woEcwC#g@a̓RK'|ha'q)uBc^U^Sb~s[UW8W{kyoYm1GDDS.`.G |EDlmWj,tb9]"Q&*i-,s"Y-fHZ`n"X$%ŋgۖKYZ,9eDٺpsĐ!'r&թ9<~o$q_< k* N~4(,Ćr>&y8\ KgrM7e!:/ѺDX1@kH_EXW8]L ^pJ61.vtF?c2*1\Ps@6q۬pCŵr0q8K?kb\B}oJ:7 xw3l h V6$yeсWnp&HF ܴyߨ2Kc5 ć;&G% Y2Blqz9oszL9$%M6}-}S[u9B]wws"*mŘ=}O<=>5FH^ jn+Nqdƃ10t-Sٶ 5_Ie'7ӰօGg2W dˍO1>z>|hE2QІ,8<<ɂHXro"Ὥ}H PxD;'AaN&`^@o[$0eh1<#RRNe֩$a+Be&ךV1SҔ"ZX.4=II)gdת1LTo4AUW;hr7\iĶcT8I{\(% |Ճ5L0k^we/ޛ|X<=?ȼ *A΃d򭐔S\f8ª08<^l U344h.Mz/&cF]Ò-QF XfO_j`93Hbf;eYY„,37!r Fv"M(#4iF4kD0;`'BƎ?E0ȋ,ȳ )mM5W}FYcqqWW IDAT@O|KF_%yVR#$!5i5"1*5J$Z%(Ip>*˵ES;daxs`/pǵ0+Q˒}yWqmoyy>ޞ*]g-;\uYζt\WpRrA~=1oCq &'o}:áЬ~ͧ)\9e*h(i1Wu妊bn2PPno!Ǵ o#>;=w-*4P$A$RDͶdHYŸe15C(SڃKbxOۼ\} 4EX҃feE?yWUBC:Kƴ-j=AY"4.<N98eTjg< σԄ :TmqS-ކiaw/eV<)c^ƘP}T]V$+B }J@rx-0+iei_g|d{ibėmϒ =9*̝@O$X1+kKx,;XBCf?߁י uO-4($h$Ѩ4t+nYrW ֳf8=UXnٮFMEtvkK$g[:+F:jZX|RCaFO:Z9x/p]ײ> w⟛֏,*qKъ#d=`ҁ2=pZ 0k&1aFL`n^%!2 М!(eJ >$+KB*[O{GQYr)Ib∠II5blHW4^cͩaa[(^}IHt7&!0=;FZ7IWnj+I ۦY||8ȰsŚ #e%7v1IC)lEZKT,[YLC_fdCˆ $X}(^5$Rlx~19#de8t>C+>7qׂM1QdBB TBEm%_=4BqmaXp⧺ y_ Qa vT2 sfJp?X&Řf^]SHTE׀ǮWK SSA貖'SU`alaĢ6LAQkGtAȖ0JЊM.&So2;΅-`*pFHJ?w'm~9|OzI(j+ߡ-FQU-k (CYdmEgP_.(h A5 IH9DMbp Db ],D'#,NUװAUT}(xx, 6/;8^hST": pt*z(^_°97^f'(E\LTLs x<Ɠc:𰛝^#V kB[F&$&M_y ڢ?#]rP0n-t ~"mSLBW 8oAju%u#o"_y~RTk{@Q LbPN|olv͗Q-kR\2狅b\,) ł,n_>5T&·}X[[g5.d =Mq#Un#mc]0`yZS{G8w![c^] 7}p+ov54 Z n'.s_&RaC[l,|OwC*x 2N)[r(>⽍ ,vWYb~qet$t! bfytQ>4m׏Z_ EϤZO{&r` LE)4ô˾jٵJ3k67f禸8 Q>ӒsT[S327l} ǮIW+cKxcy?,+ԢT8 h5/(UP4}|l{A7sw_FJ1kEAaq_ZX`,g׼'aCb00~,OP=[??1B˪q۽q&%Ď1U2Lm!ʢS%W|/qCjm&*I; }7+khr"!{+(,Wf%W qCoM|v6,:Za9,ρyas[q $bh(CߑW}suY_dR =׫-eSɪQ ڣ,p7,]jte3[k,U^^ώFj;Kh{W:){\Umru :"LXF[b: !#NNp5 vbߛNk%بf=XN#,7*,`n>#@֎!9 ϿQu-[ `{6hU668S{qދm LgBKSV=q_ܟ՞r~i~+ZA!=r_Ynu0kY'" yJTP%Q)ܯ?{ǜ ^7GcHzQe sɐZBsm^$YJ #lee¿@e3읋*sxpԈ }(4* !@X; >)en؃y8 nOFP*EҞW7*δ3Tf8W*lW򉛷 VFe~TwE=u瑿&5AVJ1+kXyGbLmPm;!o~/cZkU ˽ՐBM%B_Evz|&,%ǂK>DZD͗90ϗ<؏ =p |jGPdqCC`ӎ9;l6w6{*^JnfCGpU`C_WMgZ1j<<{tˍ'K2w> ˈMCaMzW"YBSPѵG _>tgÂ΍G[78~Sij`Y?~"` #IfU] #z}e̮r!eR㿻:k ي}Y~G9F?|!{8@g|CZ34OTEL Jg. ݞ7VG:TV[P~iWM~_6d?sH" 4ǡl]v*)IuJ3~ן7.7ЭDSz{t:d{!079̢ jSOv~pD8#3ρy̗,z:fC\rm_n}߆2kj\lB_%|m6kmw>{}ޛT;E}\EmE4,8O[tl66:l€oS֎j-x{?iZa˺W_̱_lcِ'̠2Jt/21Pv*X6h:bvoawsip`Yg2.ycw<:Houx ]C\ب.'=^#79v??D%d |.[w<=p]7pS# wf̴`lfՋ_nX; *s܊]{G}ׯOQ) 07М~:d52w$#Rm~zCp޶g7* kv]}Wf͠M>ڽ4Z Ozsĕp͗ێDޞ?G@rҿۢsShr uqp4}W0tO6!(+ ZIkF@r$J^޴ͧ0ZD{hMR-8 aĞ`ՂrDW;zMVq?RwS鬻{{sm#vpYA<|m!˙B%t.{wr,%i IDAT%rM+)''7ހkǻQ9y/;d,QAY)ӝcP6}Tfy\U&6DM*$g=gӉ׳as+-={Vzp|? gYdc>vk"x<ΆulŖ?,Z κ4#M麷Vx*'ItFSkƧ%3|w˝ʃbZeeg}h!qNPy-?DeD.akH[ B!]`6*(ʡLjo9,ρy̗;X,~_t}` $w\1**V?|G&,- k'h"{n:O]Am쁳~=KZxgmR,e@@g$ۖ,'ܼա79w~kꦵqesgw,Q|tm1K%R/o1fY̾v1Mj]L.I˦3vA՛II0r~,pVz+0 ey3}̂ݸ끲xƒ:Ύ&᧋:@vzEMFM/iX_|D:+8,|L옕NCdlɉ$&x@vkfډA)˵:X9,ρy̗?,Xn .xͫIC0k o,`yYa~'ODVҠUZ *k{A]VakF4D$0rUWR]L&kIaЉbͥY%ѧ7q,liMA:'pK^qު] ~)wn}GI4"H %y+;^=ϕ|;7:4.iY6?YQڈxR1҃=Ň'(sNN_u8f3ЦJr_Yelh/[F ʭcnx-/d\IH#E)0;rSDzҋ0Yy/vzo}@hp3.rm_$Y.;P xkm7[ͩ={v^+UoCn%ɶKѩCAs{r5atL̞`ɨשɥÏ-"йoH1R>$_344E<7])Cd(GX_v;ƓհVkc1e!-0(  L8,vxO܄-|Ĺ2<_(,KЊ?j;Z?pT 6ipj2(<~>]PBMiyיvo9[rEʝhߚrߎѬc'ص/CZ,Do-d̵6_^vb9X(+r qa 4,]qڑ$3$[%c=,|6, ,7o=JQm]53o%VlDqK;ra=h`K?{^;mb1 rZu 1ʰg]4wv"U"^76^^ r2Rv.xvfBe>Fa.3tDP ЎCcg_q>$`(XQd1vMK}؀ķw& ֞|xOM^peiAI1P=p]g>8{$[<^amkfC_緊bI4 #Q<!Q={^{A?]!qv͗90ϗ,O$M6_Q a,>ƎPHI(^}{|mErY}Uyʖ1YFaV6@Q1wJ*m6Pت̘*,l[sh$#(|N`9,!iGLcexOYegP˜gy$PX#&Ġ=ÿ7fbAdxL'ܒ _~^< zNT N0r>uk~|Jr S*FAu:A9c j.g s1 3e*"0۪.`h}pSJtL 6(ɠD!ImZ!²yˋC0l]S"T, Cm(uZpbqnhӿTuRsh-?3(59^ ߑ_,,)X j}pGI@AM*jJYʕ ˖-#BkNR]7;)S(2*A2e2sv$W87lQKLCn$$ Ǥ $~5ຆ5VwtMWu שyh0Cl.|n,`v!HbANC_62x 5gH  إ#-J:EE ;-U913BշTpp'eч%kl^|sJ٫15\l̀y~[Vor1rh4Ke8뇙|ŲKF. Pˣb|o\C+F\n#dx7^Rܘg(zA"CN~m;~ 9Co@}\\{Hf"r:0|9TbP+2!+6`nQ  X頶umap[LA.wThV-xn!2Mv@Ƽ۞!``?p­qw]>SA'Q"7ߺU?v-60.P_8x%((9)̙r̖!ab(eq;eCYdmڀNLTV>VFa=(l@*rgktNcP}bXv#?0~Ye(c_H+ɦ۩n9@3@N2JɁI@o]0D)81p9 FYjFa81TM BBpm?.fk̳_n;J*8el~Nъ V?t >x`bl]Zt+DsҋSAM{C`G7Ѫ΍%-i40`k 4Z&`{T4e|%czbJ?'Hk(Q 1 JTKF-.o^YC)jMf֦D4[XGNຂNU VRi+쇈st;F3|mhB=Ra9oe ֌qIrAw]ۖaz(.bPe L겂s%?caZoCw h6RB\N}>:nt˳5Uo ,wzvUE3aAv:^a61? RuhCA[( QLaJ^d"漓܆ekuEP3*RVoq8l0Gl`}gQ;F`$27L:gy\enH@рrfl Q`p`*vgAL3AOF-^e֚ʝ$yK9砬f`mt`HOۿLr ֹYi8[x8ފ8jO~*IY%; g;s'NHEqAR#.(eh6D6kN>xU :Ed0Zhe4-a[lxXj+o%48?Xҍpfk̳[˻SR"t@FK3j`ZƤLM?7.kT( f-[1bZxكkݕ`[Pv 4U1//bo\0fX9AjcwzG';?9zhE(76ݻ/oK*'o=Ͻ!&|?nAu0;O2z2 Ձ YCMx93YCdK+ȯǖ۩DHtuG׆RT,;s%X+`9+3E{_ˇEd 87"^e&VA:#t*'g^fnĴ?vfY?,gG;1pzPSUXqu(PkbWW_"ZyX*2X6kޤ4mkjԖ0Pp=[5Cׯˎ1X7X=kyܛh.g;GeUO߲IU"0z??iPUF+>á)<,Akbee@ue-`˔mԲӝ.)\LEfХ;x0;#q:WlPn)pޣ-NKoz0P `]y98q~Ц {`֍\thJ[QeIB႒TCrBeZ-|ᇿ +o0,VjRa Jn* $(KHHOB IDATue*M0amD5V0+E'T~@eBKFnYɤ PeS3W*жFWYz7,^n2SR(NUIWgiRp6 XsgJo-@wVn+~MU?vj2!Q*X281ȼ?>֡j-kajq& gkv~)s0rgw⹿?CrIhc(,GŹ.@Մm%T3E91rX7xJfg!Nsm64ŷ4"peJAirㇱx 9WSL\ńko_0pSBwǃ%CevȪӼw<<3bΊuߧݗ>o;ӻ^2;,e˖ѨNHAXW88\3m82DMC[NV V 6?ŏl$C rcs61`@p9 Ċ& b_oy,5&*@3yI; @6"@;l2A&yw*,^܊\޶ e R&~p:\oӪUTBTDZ7.Z<~C'mmA>qN RP%ZbV}u ٽٚl֯ V[Y'XfxَmjǑ8o]%-] ] tPJ fQU%œ P9$Khn8[.`vfe>._uZd_ݟÅGrPSd.^ٔ򀃿Q@}n78Vr M&eJ.G1)tfJhi,-%`L.\](RNGECw4 h^ɎsRmH`kYc9Ua>GykiR2_L?P2vϮ~8KL5.2!*;2HNl-r"CJb a'/q*NRǢVB&4V <0Je%TQf.,pCf<[E2M_u16/dak1[?7iX-6nK&@x8\R;c_/^}B$tPP 0v:r^ 0@MT9(3e9#䇄XXN,GlpsjBCu8~voht_= O^Ia\@W[-Wh΁>r<CIeKYnힱ )Z,,j e3'HN 55t7(וǠل  .\ .;vTX,q7Pe!م'a1xvkrk V09ʔSZSa'@Z|^Gpa[1N8Nx@xr0\s߰Ͼ,-e^YV5ثɣP=;f<[3yfkͰ.cP@oYAs^e. (Ѩ^YiAu6?}c5æM:4c2,&>큑Tfb¶[,c`|f ,NtpWφޔ}'tXoQص\*Q&@a.#"I2tϥpׂκÎl#!.WkNv#gxOQlef%f7;r!П܃J&"+QfkS3$bj!@΁¹e.e+q҅?؇ `dτe뺯B;~?C'<ϞAl̀yfn <~ynܚ0v3&d&ne"v]{9t7?"*<?Mr_˄q-cV sK8E|x@ʌR-9TE<3bg̢=ߢ.@O!1ákni_Y2fk_7έj7M]6r_|ݭǵ8B  jBpX[a +FFjk JsdMTrIl|_gK~RAim (})IQpF˨F˨Q "?tvYyt7W<*-ccoow6 &29(Y_6t+MJsYΟ2駞_Eg]C9^+D+,FTyC[ʺUhKxYn5@\ƎW`L2slO)gLw@ws17O!p$!Z1`\OK7]֚Z˟O+yydk me[]zz0i(b?G,+θKuWczt<sQ2"kpeἋ.Kޙ<[3`0k^o5,o_Sljq̌ #A/>tsl:7g-8) 剩 pƒ[S ʄv|^ EE?)J(a3gE#0XZ[pn/J{9;CjW;إ w&JXBQp]g3 (uwɎрxq}f+sAb5|aIr. u/P, 4OFS4Y.?_~|WDi(,{VHD;AezW/9ϻ̧' ,`EY@?)m$214\Y1fH seN\|MNj|؀bsr塭qONIDVZl5{*8@xaC3f4eaf͘_ךY2f77ϯFCcq0㜅s6 }=8>x[[C,ƄC:2_e8I ^A˷cjY?No`EفR:A337v7ו~<{;WjTߵyP@=4:SZ"Ca",s0t6KCB( ėg %wP9n.MF z-q36{kCn4Sp,vKBuo*,{[A@\==4w@zM˦0к%h9"nW]h95Rnz4),8)h8k[*5qq}ގ RWx̧N'xyw}OSWZg4p^ xGFkÙ5cf l̀y-p^زظ1JUN.rr@s 4GP7w4u}MX 9vM_Rh:^8'Ho];ڊnEa93Zhyp K;w`񖛰AWb_90DمCKV#aG.z2AYކT W{l%W`߁.u7TAa2(ʀkCt%j75'.ta`65ThWǔ53.G1ElȪ'մU[*8ir.qړyoIn۷';?rEx?8Sgk0o9,1q ˫񔒒)j4҉x5l%pŃ/7b7now9/} vMBljB`l,%>4We̯GNWEg+aIkeE<*b nr%v^oxdlMG|ԉ2 @sTa`hupTe\k|[DA65"w{+8چ^ҹ?%M-{-{;n],_;kpAkAY[+C#bGs0$dTfR,SnMkTfUXCD @ Yw0;S~eIs:61Tg0鈊#:YK_1,Sy+Pl3TACTٚl/ZظR+.A9"sRseRaV: 5~tמּq;vuƷ^C/y:5jt?!ۺ.ṬtQv`S ,ak@??$nY]6^V`0S܆f*i†J6 &+ϳ9{A qDo!^ <ѯN{2ϣ0梕U_3B8?>z[Y2D ձI8'̠CTfUz uUBaQg %*BBCkCT ! @Nzғ~uNSj?$v,VhJ7#krÎş_,iki{zXt֌o?g_3yf<[};u.94wTgE4U9?nL@ $rgbg߻` nkkxkXN4 taLksRŹ,W!hy G9񶭴w{ׄAF7u <Ǝ P3lʬ pW,Aëī8xuѺ4Kfc[.{gȋhm<(k 4N<ؗJ,2C'In TӪ|ފч.U^C١usZغF]BF0j۔"JiKpoĪexn̎#oP4_VJYk1<<"@ j^ ͦ;{R Ƌ%^ramY'V)Y4Qhb DoƟ^ph[%b?=j1T릊sJ.mu2AHbkW/(<0kc`f C:Ԩ{8jlQV,^87Cd̹,u.<gPoB $}=xÎm:+x̣az=rto5C !̚1[5U|kayviXům+e.s:~Qdjm:/+2 ?~5p/'R}ɾ !V :dڦLl\p۸EӅ6ńuC$  wbx>:^|y`3{]~=ejQ=ipx9l;0:^vrEx7Sgk0o),+jU`O>Qӽ * #³ ioy̹кhd g/Ay 딟9?z0վɄNL?Z·牾Dypp<L㟶,pw MO^o~#!J@ `m!\c92#I{x(3pR}qΛF ^\!ʕ'~X`[oFPJohaXJe<20Y<0=?:={x&m`Vܨ9D[f6[ `eh{Zq IDATJ(&l8 PY7l2`x_Z$6kaŢ z? {85`Ly%La3o X6]P)X[mM^" vɚq2Ϭ5?q+`yn57*>S9s2@df8#/WAV; Ь(74eOk^NlY7rF)?OhP/kGsg{%o](uo:w׽-/y4Xku< i  Lon PR n'|Nz>tScb y\mb7_C|-y=pO^BSt]Hrf]Nre>r!D vmJ lo2 g7sEXn%cBe޴ߦݶ_bMUJIQ' V9L+*8gM9S-e4N3;MA dBbDpF 6 ^n'Y1nòRж 19ث>8QUFwbE4FƦLǞ}rK>w:%W;i?x/4.< +@Qh@fhi^#$SUhU 9SY6LO^.*̈́yȗDXV1U%B 1!Z\ 88GPU_ro'YMeP?r}AsN)Tf h,Wd޶.P\D`^Bew83C*`a{ܶbnNւ}p<[3`pX#oUjwmLj}|̪80E.KAۏWc9ˇm?on#1~S VP\L(ih.Brk.4;ryН8`+80gLFg)Vu©'x`f9|=IۡF _c^5@!{幧v+>{W_ 沩әcsbv_5`]xn?]eJqh&ޘܜCs>DD,#6 @R ?hZ:|r_p!gʱzLe0˴c.,o>`GX^v_70aëg|ZP6A/,pynʌ~J-J㼖dAKus1?j|̍J +nL*s e?f^2ċfG-F̲6#AT~rڮ֔w-ބ *3e]yc0#)FkEuYuY|Ƥdc h#sG&\` sf^AaJ7lhjdAy$?%KhbAmGexͤYָ%V3Ai&c"E?0C^).s?%  ;0/˴(c2Ks s`&]'XNH`ӑU,.D~r\#{ߛ g7gm9pogyNͭh2g-Xv-eV<0C{ؐlf=2CyhC`ބˤ,s8yh[&(L(J_Csd'|Hcd*J.AB2FZ$pTxY2Mnn@hK2 `6 C`>x%_\Wmn p݉3DY̯0v3]?q`oC>͹Me!//3 3 J+DDuc.xjCW~,|p6Z:D&sLuDh>5rw_5:|sؑ2 ̔As`Κ4LiYGxh*.@Ě`|35yX2U'?p]f35cf<[{- +;Ɯ[vAg-Ep+iΪqx^ j[3Amhu'cA5`Ȑ@%|Lhf("hp 8 mKF7(̜TfΊWs93uF~xc*h㫜c9ŊʜƆi2$` $e> oJ6tp7W0fl=<,;;L(18TGPNܼ7u,N]H}Ә87̄ O!Yұj (Vxe=x}%<`.&9g;S__7YC)n[ܲahS灔O!ew9* 7 AaÓ#*[Xj=_y-l8hf֌Cg̳5ړ^5ƻc1x?iJNEH9K5z 1àϘ@` C0\F CYC۳Wc겇w,wQʲ {h/f2ޒ ǡzPe>׉E2áGA= *GYe7T9ȥshBHp wL 5ZSZk00@s`= .r.C @|ޱH``!ىzv\u;@QrQQ`r)8e"0S8*5,Y I*f'"4(g%2A?ts4H((*؀I5ш~Wn "FvUqLڱ֪iw>Z3[)wW_3b7AJ aGe'8eے<Ͳgk̳g2[:,fz ey,Z@2*^Cq(=1'Ȧ^ek~CnY硑P\H̀ +]oM-pU҄KLr C``a8'p&Xy:(̺g[$U`:bU 3)N|8;0(@AFb g}ګaJVh9.q=U~޿leU4!OL[;"D ,4 QAa'Q s#7 (e,``[85lЧcUy% SYpPkt<4{I7kQe,X;}_w}|} |Ot}g51Dc nL{/$־[p9ۧkzS] ց-p_W!ѷ-֭l`!ٲβgk̳'݁孷ٺ[* Pq{}5tܳ%*S8q) *,Xԏ A٘& oSߤXAʁ A.xA#fuVQe@Q(Vm Te&98aNGW 4SGU9)0#f' jSZЌa0  ^S]4FSpVn@ *Ca&db`[x`V m{t 0FOmP19pb'KFtl46 ᤨ,`Pll IfXe8وz60 [ƯVWϧ嘪9;80>Ǚ-cQ5)E.H+j2IMgi9J:|9(hl T7B/-gk̳9,Od`b vufݚCqĆw>JaLS$}((xI%&+FU&65 p*!ǝ1yHf"(ƌ qXX7,`LP[ 62,+xhIYƢ(. id# 2 QJ'kϾ8oķ2hSјVedq2/6EL܇(X80xEWBpRCEs">FcZ s#eG>V5kKsiG.sÌ}nK*skX\WRz%;"oIXDá'%J5@z`ٝލ20Lp@9gfR 1@"$t;w$9վXm:jl`kx̳5߻v ޻Y^׭)_9ev+~ixU/LW( M2 c'2| CYS *R׷YH;F&8\Zv2CCIAT, 4+9<=,g a? Odfwʁ K\y_y ?J;O8.;xoT"2wrUλTU]o$! "(((888l**0(7qEPAQ èKqں.STuN/۷~sl< c61tyC2{T=đA!\5g+Y kMd<JsCwόOŸ>gj!ʯ^f"PˬF3\=$S>ru9WWUK֌R&V2<3{t%~PAh_aXSUd4눡C ΋UD9`sy%VàF %H? | `9x/sZϺcߕ.jdzhṁ1N@:)š(y066功uYY!WB G " XfPwR~S`VAwVE ׫`CF٠2G}r٢%GiOe8r43iqp.ox_~M~ bGF,yd]׆蚊McٶMYagLș\A5VJOsy/,){z/2c g]zV /xni.>_U#LXIp/V[(d`e.7e_AE7Dui>Œ/]:*6)fՓIc KK/<o7wM{׌V~%=.%uP ҉U+ sD{<9):`vVc X0wf.19OPEjf̽O}6]a5:;}'=Γ^G:)Ͻg5#b# V!2CQ@ " a/+]nDChÈm]2T14'&f$);nGJ ?:8^X9vh}@pw~V몁08~mٸ|vf%Xm{<ZC9/\%*0{S/!d՜ Cu*eƃ1t'@&4dN^{K0 M]b #. cO[k{YusX^ރVC^<՗]Ο͚iHiVY,{Suifw4 OT_s`NFIUxV!)&O]2/ey.EuIhu?sN ~ _vhfhn>:<֪Uqr,_5z¥ Cif*g#aʓy pGeʡ^Ux?O AiX.`3ÈA IDATBn3)yu]`]a_m>&X~=F,܈(}m~, 0_¯]E7m¯T_Oyh2F#fZZ{˗؋/_]50-,OaL;s±DLjsUy'ƃ^ҘqO7 x|Ua9~#w=K#%.ǠȀrXY`3hHׇ9Rpů}ԌXXFÝTTfdttnP X3z^eop^^$eY{ eP@)Aswe 8##(g//ü9#h6~X6*1+B>ߺ `c͠, G[| ۾w:vV1ŘSl"XSo[N,[]I,S\9K/=nX~, H &rg2~*rfu_9ս6^n͚ooqӯ]u\ˋ-iY+AyC/`&L!sʵ)~0(4d ulmfcE;n?F}'7/{x/*ځr.ʜAֳ TL@q25h [(\e)p_@`IdsQZ[>G`.A39D#^p'QID5`E)8c蒗9;Тu?0g ]V 5Sc?17J9Ir׭Uqre~溞<` ,/aqå'W3P,S zУJz`9\ f(m;61W"11"cT?3o/|S/)wo(cFD ޶&zѐs6Yek)0g& E49Qxx~mOC,$"Vֈ,:a`m yS~M <.򃒑#TH4'Fex.pCW<`1Go3R\ ۪ ˥ I_p?}d *3{ƾW+ˏ?d|$j"2σAEQa0|]us yNֳf 4֌իY Ϳ5nښQפU0׵)x=;=ird2noԯ0[oT;4SXTa5а1"mV g]N&%T(pa>Eץ0`mg Č M be*9`so~$SPKNrRa3 mDQ3lHk wXXv@&RW9c8W~|("ls0f (XVHW.Dz fp2Նs;^ t-@Æ\,_;@s]2X|/&_6Wѷ/,"qh^f2S!y/π<;Onڭ2\)Z~fku\דV-7qI#f+?U߫8Z @O#F?, ))s[Ŝ[-2`6R$Ь,g(:X3ꚼ_bʳ,͞3{6-Zv*s]50w6Y.mϱs&Th<%3#$ sQ ͌܋uy HlFXeY|ʜs \Nf4Z =~N^xw;?`^b?;UCD"͈V\eVWlʪ 20[Dh;kͶǂH̍̑0J#fF=,3̘S&>`yC_zͱP0ѣ2[[<L cdԺf@{@`ȸFN"PdAe_ ˍHsFMZ| ͉ f^h THQ/RI VדJ0~ qr=4uc_~w v2GgL ଚM汱|`=6ZZ;dd%F͉L*?&*fFQ@3%(|yg㲦 d&.)8\\]0`p)Kq]\rEXyp&n|-xt0rNcY2XQ= } m"ľh G RC1C3#aMDgvRsЋWbl|@ajH<@L,h2:8Ka8 }d5hZԈ@)LζULev 2\#L2_bΠ2M&h= s/Tmk[Һҥ5ù|lic몁'\I\ c̍掛thLa3 (@ YE2#sgV,cGNe9., v{0vs?1?>}^OAս՟yAz~̙WL .`j,AS%/s@mn`9UbQ1=j`dQ c3PXfD쑺; iHim&9"4*f3<|UPCR(dPq 2(HI^gD!$eY1ye`C5 454C6([Y7#Pl6  S0DCY%u6=dfk9c)ҖXw%HDTe `y^f2\Ww=\GNOVz>D8V ¿TuPKHDq'a1T{mlϯX3(@nx<,Uj`Ps_r?nb ~s UCSDuϻ<=wU"Ls=&9 /, ٷ/ Zl,g ;(ț"mu]?O<2].z`x.Ë_?p*j3wGQ!^rlà\UTǬ )1_YaMЈ621Fad@3!^2u)v@`d@>11"Bp>To>.q . F AH g*Hۊ .9^YP[mAsUL9  [D#*4b;u)u)֭+I4#òTY0c|`?/*sẄ0#^g^u+hdb(rH ԘѳwaJcxk{5r%̙ 0|XdBX+Wc^X.;o1ndxbU7z̉(J2Y؇L]ff2} NٹCf g\UfX{Yk߾y/sig C4bX<KE6~stLUX :"(c.y\`j1Ӯk{8uS~ih&PMg,"=/Bs)m74f3Z@]d3Z߆l.u s]&-wrȸyѼa7r-,!;e3Us}\3Y`(j"6"ci |eU>eC> {_CǣOg!QMDfIѸ {/3ZP@J/wPQ2`cP@CvuS{-Ta+ܴ\Y[F6= NEnB]V&41P6d@[uńyy^~/93F0#3Y]+IH4i$밿ى}8k4u`&H-23V 988XA\W!dw_\܈T9UeT5,oj/1UkF~m7۬n6Cr帑qyU nM}S_mh՚- d ߭QKq%0g W=^|!O>%g;fPS`P6t+ڇ8Q&4\ s`;DŽ]~6Q| ͠(-,{*'fd"yPIe`+L uxLeX.qLeTqf[s$ =RJhu'x(`0"Rf,H"6 k@V?CTE0x4BcV;T!2$Nefw D4X!j>X6% FOi1w U9W}hK7pO.=NF?4gE*q. _>jX{"T+0<o~WCjFEpkb` ,gTkި ay@d(oLg3֞9yHU8pu|3<#(Q0GV1 ?X2wڱˡy*7m{fq7{ &鍖C%P@rUao7k/}gUu?P`Y2HCqՒa5J՘I5|Յœ?З |ϡt,»NDzi؃ /xHGJ K~#fahZ40 c L)PLPV[1ݧDǙD`6T,fTm &),oycad)Ij7[/9\d;G}xȞlyJDfItֹ5C594 u<%umz޺/aAAs͍o#<A# zi~7^qݟ(2o]/'TR2jD&W2j&TlOd`:Qp0R}`4U9`g(`)SEד/{߸;^~e4Hy8KFd"]VX za9[LrtSZ%7½?F`H&AsG?y\o~U&*D pHP%P.5y>; *uY9,>gK6m1l?fKzk*g '̆s`&;s_S5cchhow^zie=̧V66˻n=02nAoj_ʛ|l*/QQ0wV 㪮S[, % s3HLFewK#=}} X cF}=n#دvvZ$`YGcMt .m8~ )CKiʃ:{qe r΅\(Cr^|) ,;O ?s+e|oCc \.7l2X~@it\¯⤟ 0yѠ='u˳OQ( =`<:е=.lIP $S8"ɷK ̡`Oh7[vƌl桡5=Tךּ5,5),kUY6K6hiaT#!;Ha.R>RAۉば<==gAi -6bLrZg밭7,gZ@ńoLϿUdWw)aP" aIdHF-ޕò. +D!Ǹ%(~׬ !rU܀JJK܀J8b\`b#9qc д@ReI I^sk96H>G#5X!5f[WcT/ :A4y9i(YI(Sńzwg :#򟁆 ـ ]Q%DGEt,f(ؤk(v"taQ6Ui {Ժ^]QԌQ̘9/ƹoyKī2d2rt <}ζ8Yܯ*ah_<WUqŻ˅ݼ*`6ڌGamJ2+â8;ϡ_^\[~ZÏQDe9W33V|cbǠ]3u6WiO\VC݀QK?Bt1h1NE&g1j W IDAT8ᦷ`4)|63e|ǹ_HVhc,{& !\ǃ_XFK#xnX%c_fݘ-6 4kB þcW#nDEiR8tE!ށi&(̔g9ۺ/1Ȼœ=ҶG{M_k)߰9STf"Z- 4y;pښĵ\@X}۾ȸJԷa09 4 #DYdQap˪uWdl) 0+fab ( (;'-EQ ArP^ppg=pfpV6@(gѣtC[Fq?߉,y\p[gh to8c SlqnYp H`f8p)t<}zOUE=m•,{0 ~vP9{tcZD-nj]F U7,g_+z1)h 4+`k3AyF :| VkJDgg37\7nEeֵs/,/si%2nQ2dCbC2 Jxin>]HeKFvuO3s0g*Lac2#Ƕf.,y:N>dξ9mNB4|mJV @Idpy6@:P;V.%Yċ@sø/PD0`4Zf< jr1)pK&}3n$adJv=Ү}{Ovṑ@r6pD>fy^JG@7Au:2M&Z~nnlTf/Uʕqܧ?]N62.ay"X#aq8ZUЇgVPFsbtNt5\Uρ0׽gQi+O+[1~;F!E~0XNϢ`K*A`&L,ʠ\@9sfW@"?)!e$#RP%O380RPNX0,hC6a{ƥ\q=`/4s^yETBfHLzcHdrYeNSyݧ[梽N{ۨ+/qɖQz{/lp {ayK61(ryC50O𡼁<ʵ[eX^ײoq*] Te+2d49,9Zr]fM.22CĚ.*Q2<Υ;pO_`[>uDǙ(e2 Pέ%9@2e=Tʢ ;PfSf 8(k48,aY!a10}`ɨr8كv.F}NjQHm2.WJyvpa9_OybHSl<=*R^.eyYL 4V5CÄeck`kSmw۶彗9,c_<E#ء$@heœ`0S2P%hv%^[%XҸi ZC 9|e?grBa&^ Sm\Hʨs˕uEpN´xzŎ$t>Ea0 +X#bRdW2^,DVCP6ir=#0\[,s0Z dkvr}?!81hl""B9,gΫaF_ jt'[m%iq\i0s,,[+_?50?EXpPD貏k({oalo"0kU` L3`ftow9 Ispv1w r KRU0&P=^l46<+N_nhQQ|kK1l 8*s&0gӲo>k>g0 H4xj. 21{D,h d-Hgyj;_lS6U(]Ma920mtL!gfRstwmgHgޟ{ <)$>sJ\r-$nlBfTrt]/S16ƪ> (7jUɨOyG>`ra& iٻT|7kF'tzydv{"aYQgWUː#2wV )Q5A@0ϾG\x-J֜efE7I LӤeQ2B;CE)=4)waj|Z^9#iD`0zK "J)]6cnyt[zsbs]u=[vl\̛(0T ɮ牀A9c}wR K%HmxKG%cHfG}ebxYW)I`5[f E"%s k%/sfYrr͞~kfУ S [JJ:ܙ Ub"8#Y{X;rhVM=gN,&}՟f9Ƞ4{_| $tnǰMTA3A̚@-zFEc 8s;CakM;PZpsRd$I20GgۦOF=YϿ ;`馢0Fɒ)E9f%2[A1tF>wUW]Oog;wn59s?Vm??pmͨy׆ j,7Uc<ی `pSQLy0XyCE(ZQ}'yn1b/~3#Oz4Y|g0iXo l9}o(|)#;'>t .w 2PeKX^$8`pyaeSzj `ƚ ܿ_aTȾ.+%;%+cA+W{kCTk :ySeA9h{}/7ߧ^fOݏA6nZlev–-+v&l@FG0:VI`lԀm45QM c6I` ءmU`9wr_su \:˅RI# T԰z.35{7T=:5*)!WVxqK'G%C KECe3pг,<#j*$ QaU kXiF#2 Hb8ֈ4X! K$קl U#xvNG9fQ̚5 /Z~8{l,3{0z&:;|yrte{.N _U>V# :6ZE0bI0HtHx)<9Q+O)Br K~.)(QZ~^ǮKWAכ&5"m1b 5DPC *ܒey޳.ïpk+#@)rΏy_EX~Tg){b,OX9|PH^WE.-Ju+bx6TFO8 8CeNAkCW>A59SkkY\n uwګs-$tP -eVaoa w^9rp܁K%)Z֚Gw'r#]l+i{&@*MJ ) spU1S?%[`zs7jXX!2pʑA-C5Uv.1#}aݑrFЫů$+T} \e93>~|P ˷ƌ Mc,J20+%:@ft:`^W]yxŖhn:Vjܰ|9FVhM7SZk uYGЈ`BV1$Y5S|V!xj%OTAspnI\ZW 265`Br>̘7:7D@̵c!)&(tS'}ݑMu7%\=Q!Ǟc 4 +򲽗VkM0F\3/ d!+a~ s-\8n6yVk`Y5,?us;M"h*Yl&V A) 3U*[FEQP.U:rˣK,Bs`΁)V8b޴9ޜimZkx]W^7]h(f->8) ?iH2e#x]PQ((媯o~4~:&!DZFWe=M{ˤQR<4\j؁.uURc+VΚ% $ydDf/Z? 50UW]ڭm ̖a9M Sh3j+A'*Hi|C%\fP%rs>>,(T@9)XkF% FY)r՗^-qm.  ϯ[P# y!39L+5T4YG%`,|%<P>$d4eV1- =fMm:-F-|dɭtCʬ ^ҋ̒}I`Inv衴vbH<5#SGfE#/~?n$U{)P([RW 2e.we.n9RH&U@_Vf ec-)v.> #4&U`%qJ+XApށl;v|zk/7s喢0GQGF0{,[80>sߧ50UW]N:U3;,T,eY5٨GQȺJ~\ɊA%K3`ƿ44j("/[(A tE7UP|4kဟ:Ұ4 XV5**Ye?1 `b{704- LP3B L3 No6 ؘyo[[=.j$kCYCC*FYU~3U Uߩ}!w]\nl4r?ck`:ѹx`h i\\SG2QI͙Ӆʜ{sx=0]Ң2'>9@LU'QϾń? Q`o=̙œr0k-#K`. f4w٣,)*&G$nTUG%sd40X2!l#tWo7/`1xV x)P+1h aIR350UW]O@9&t,N*_2< ߓK3a=+ɹ=au9lC{5<J^KWfk,I"JE~xڞ-GD&iĪjɈ?OiYd.<@tf0 L / Zp*aʁ9"0| lg')\C<89rkƢ}2\W]u=qub-2'#IAYʽ{}ދ>&9l+ۅ[ @`%oŸ*4:B?{ @`SFD M24됈JW?ʀܫ:;`2 /3Lf/1Q8bC5!BeYk@iRwJ\qh"`m=6[vlP9衡ܚQ50UW]Ob~F IDAT`Y,*sadȦl†Q{HSs "$PI e#,AZTA-IIP``?{з0W-LR\fɠ2ہaY3Cջ]ɋ⟀cBŎ4AiS@G?Tu`]uiRd3k=0y4@o}+p\s]uT()+U 47Іs os@EV3hVȺ4w\U.7ar8Q dšၼ]҃{#Uu{ܙ}UVZ,mTm}>4T6&jA5QchbDIIi jZDѸ Ȳswf{~{,} ú;Μ{~?lY`x,jEr.zyt.z "Yٞ{2 İX73Pㆅ;SZҢEPeB!3ϝ5K0 04'Y|H2%9g(#Y{:ݖU]tn`ֲ.ejx= EÊr~5rNzv`؉28k} `h41@#+d92krk@1:,g{3&I62[aaQUWeƓhi7hfp.E1җ.ᄹZi V$uArzV{`,YYD:-8ADMK\Qv;frQ;a(bE:VN cQfAhA:.*Ws8gN7ḫ.]<"̂ 7E's?Fv闎e$ $0ZV(5V02~Nmw {[a]&[@ݒtsVrlԛeA" -X`cŢhT@Q?6m^eo$YmAhq>BJ0r x>.Lm*p 2t]*AE9"tUj.!fF`Vڮ*{G-Bך@6gTMrlD#MeAr]|1E۶YQНН8{zp5HRY†Υz.y6[ח\Z A& †˾Z/nh[/:I* \gBUe.J+ͅ\[R^KҥR-`3y"6P<|J6SQdY$>[UaNmq"uvb^ON'kґ+V0,YD>zٶyKZ spg qNbt!̈/8~&'`Eo$p9UckHAKpoO'ElXFdIU\;2 t ьGaeo/z)&8S¼b NTȲ GMM{33t== fAa>L&[IQf^rrba7ζSn{5wyX`_쳶UDTZG5p* K6֮I:qy/*4e'V{C#JeQegٛ\13g*}=7꾴(<"c+"Sj.Y_qzͨ*,.$5t $lT"$,7GW^R\ DZW#m|Ȳ cDx{3J@ћ9 |_y>ω*RMƛYoe^(A#esGr $nu*\"3xn|ʻV}ꨯ+o}WxcF֋>NK>aL|Un;̕ RAߏZ?>^#Uxp^lkv|d/a<^*,Lf?q7t{ dV303f&\`*H!|NXt<}_?s8_C Lk988T 5#ٽ{}?߸CC,o>({"A3! a"q7rH+̺=ĮsXQ|<S`*UPتex1+÷,+lZl"˂ L4ʛ6!1M?wPëT`׮eh Q,!-Ra>{VDe֯ns@w(Y Dhƀ`߀M\ 7d^|y} кܬ7sfz3 6pTؽsgVe20yDL֑C]ea2pʻϳj/A !}+({m-g`#pU8sb06{[pAhyuLE3;hkXzMŒJoޜIU˗˾9FH$0q8&By!.PhfXmEVL1X'0 h< L }}6] S.c[Ao[TxPSk?*f 36P  mW A)@eQu.̞]o3z3#7vrn0aS$1&"dkYB(BQ lMN1 0I^/):|!il9c z cX$*6~Ice< -,LV8"J91 I#]u X%0"ghHdYʕEi+ c̀uܬ";< |QޞqiPh+:r^} v8`sDAܤ{plm%h FYeSYr"̂ LVӱRكe=B)r&M5"˂  }u+c>\fTZ cxTI4eh'3w)ZEB+7L#ʔ\*=7&{AZN#lӘKN[rV<,٩M"P0"̂ L.^GHUSiNʜ=Y9 +ͻv5e{PFT"A8l S3$68h+@]Vesu{;P2twc"cTc ଳUaNPe~v210  LR>3F4(4>Df%I@I:"@=>ơ{`F?&:lvw<9 # BvRbFdG *+=%"h s~5c ԈS>)~F7)+r JI Cm(an,>)Q)G>r1 VIό@;ś'gN[SqYbih¹\ K8RSc֔f&_>s‰s<ɖ 7DQil /e0[`8V369mĻQkI~ K0Uj&sN$s=wS&|z )/l"A,X4"pKrBe`/8ip\A'A/\1A!bX 3!sp$G,G"HRT"u/)rB"b(:1:uFh.D%hnFK*0ڀ^@=ha%`!XIXVUaX3ֆa#8KxqQ8n n%n Wknq#x ^owųI|| ~?$?G ; d2&nQyBa0J$5DwbM󉻈AIdK'őD\R ,4&$g$*"Ǖ˖"W#,wCnPnL^YD]>R>Y~||%o \ J)\QWHV!}  ŘEH))uGTՊʢrj7"Sqbb JrJJ>JlJJzFi6!ʩʛ*_U~BT1VST\Ta4CC[K] t:L/wGTUTUUTUϨajj,5j>3yջkLihLܦ٨P eTk%iin8 vO6^]ݮ=#٥sQgXWMK7YXMCOWwNCdVƈ~~~~AAQΆņ-#O_>{FrFF|FmFMc7?70a2y`J14]bZezˌ`lb۬5w0盗߰@---fgͨkIdZfZRckhjq3lZh]c}FfMM[m-;*&<=whZ8:9J;;U8:ӝC79_qxr9Uz/7Kngͪ5nvt`x{d{Vy>2zz4c&33_y[{KOzqYs -S+{o$!`Y@|`P^êcvbvk9(",99hm !!CMBF +{n<-8`ĻH-L2ZDE)7sފycbMqĸq?aA Mf-HkpљŊًOcfأ g'%׋[x''mO{KAur`)!)Rƅ1£S"Q5M7-+Kl!-q]cɈ$H?I_$=4c]FGfy懥KOd)gڳͳ7f?yngYrk`\LXٲpUުkȯIY{unQkc6XP>5_߻m 6[pк&Φk?T[J*z{"墜Xj}ޝ;3v)m5}]e=G++6VݽkO^{?SPPe\URMά~Z]ur@t6Ω-C/8yHSe}Qc/~-'OjkIɂ!aujf擿Yv3g?wv\ι. ,nq[a.]b+^=uZu 'wdcG M.]v{v_{-֭=s{nGݾӻ»eAC%Ua>Ǿ3# |`SӒgz>?=?bc*Yկy>2odMo5GRߍ/ǶO1-L\KנSl {`Cxs:aG&0d;fÓ @%?c`~Ln"]jgMYz&@cU_dp^3[iTXtXML:com.adobe.xmp 463 73 ~f5+IDATx| tUt:X V\PBW)YIM"-֐ %4pc,XӀm6pۀtզFe  +巟Ξy3M^+39}9DJ"("2\R *"(A@ "("0Dx0-("TPE@P!E@P5*"(P9D"("Se@PE@"K|{#)2:3y{tɦtИhGxg乏o(TS=GwPzߥ;ÙIeuQ߇2q5:p:s봤Ì)`T _I62cXeN$inzշ3 a>G{˱<:GC?^jWϢLvjNtRǦ _]Oi| =9QH_Lpج:fe5Nٛ9HH9XLrpȸjsҒ0ipȯg'LήfD7\qc6-5!Uv]A뾓dAj*56O }<Tr(k e,^Gb8룢UW"~j.ῒF NjZgPOp;P*+}GC=>BQUh]>Rup=uSn*n~\9M5%7Жc|h -6#z4SiQ_w2q(tTReB̗6rƗGKhaL´U稿uS+?)@A 1P׮(%^j_[}dp۩R03si{vRG~_7:(KS|ɣHtb1. E 9H%屜5q"o \ _Ʋ8: /C6/Ʈz1z i.؉w#p3y۵ ak@{~/5:}kKd}!Um."OWo_Esp3m$[Ξ;yߡ7^6wm$}|S2(̩lP`k ~7~*B 쥆̯s3#N:ݻilɰ2),=_D(`ŻY"DcQVIe+9d%ڻoNn ]LeXUmwzۻ.ciTxceO (;Lլ(vvovU,g0]VBy%I><͠+2oa4VB]5Tx4Tt q.!`fާS0]9͗כț0Tx8b{qEq4|Sy\ON=Xq/fZ(XyhʭyW 5 >c|c8orvNxyg9a z5޻\C ;ȼ/L<'o%HpF:v6µr b\ћ"FiTgjM)<^z-z0w{=K]q~nl`д#MGx82kvvZ֨#WgygL, TNWi)W/D+<2Rߡ.ӗ`;-,o`!zEW{kGq:5Ǚ1\y^gXO1PRukyT:y߳L줅yl+)#iۮ%Aٴ?yRxwilV2A*cE*j潸jCb'z(&>"1i|dcGMzmj5_yԴ1Zzyp{#(wygѪzpĖ]QKřq[\]KgÜ:ZoYiUAO)[ʜ$\jiW)>|MK5nK}iϹ1uZe"rw7f),{Lu6*y!%>zih/҆SYn,:IoG0әӢmk3;:Ds\oҟjb$**hJ*>,qL[և5n1e(탂k8AR*P_B]mQ2'~C(V=` xyf,`G2簀/nƣ9L/ ulBƒYFY&Ӥ\6:ɟIi%es3pv 9tȯx[78_@UPárp`O3v7ᰗf~kmYΎp obv}5:wpũ=o0^Z6Z.ij[/Tx_BCMT3Jj(m6G2;NP/~8ꃑͦ ޫ𓖘>roS}?je]-fwf/C_.YV =SV$=+?4gupɗKw)' -ck&k\kw8|ZVrZ.:pW@+9J)n}^9'z 9y^_p*XGW^osA/ͺ>Z8}_CiSh&G ,pypfYd+5t.6Lc΢4%23Bon$S@Rz·#uH" >8ey9{.ZPp/w,Z> RI"΢YWgvfp4K2rɐ7Qq^NesiV]938.{}~ʽgcMċāqrv,k~&jGf,_u>MSWY=T>Y`V(t}VU nLĖnʩmI}s"5!`gW;(?.eoIVH+LNbEy/y(uX"l,?D "}Jh6F;=deOU8 `aڔT^sm^ڻӒ]Fg?5V?^4`LbP3P9I#ertR8VaC6GBl%e/=xuਖrWnbŷdc0N`rY6cz>M?ڻ1`Ȱ#C()|`hSqE_8 CA-U͹i#5qT K[Sykjc>֭6Whq',n^?CnKE`DH9S0Ә~NeMsR~Zx}WjЉ}M$O,v>c)Nje#B;.֮[~, UW}3:pY= >'QtSh{Oub/=Ef/5颦کVp4MRN# bڎpZtq!>98ʇi$lVRoeX?B sj6Ա= Tlé3H!7*.Q+gUKW>|f~Ļ$塌+c[h o`w ?\o]]Nh'LCfH.h(P~ط szix3lN|3PƍOs?Xط'40/E`+!sO4 0v'wϋ? 6W;f㿂*+iueEl8蓮rN3BvNX{-uCTObzAShY%ap޲9c'vC #kMM|^ YF3[?ЮIҝMcr`2]!?Z"= Xɷrid2ȿ[\N6LژrJyY0vQ~76^*^R"Sۃe(e؎l>`͆?.X<ۃ GʨO֭(uLmFİ\>ݺ˚͇K: !>S1ev1@?~AmVQխ5,PϦΝks㒡mTf)U4VȚ+Y&+EXylD iop9⭍T4?۝59:mF轅K"gz1GIdp21z2C>]42w"L˿vȓ| OO m^~)bBRC'Qni C¼ QoFأg{ωNn{œ?`^ ȀSJ>mw:ħnP^-c/}>;ҋ)7h:Hw$dS l㾏߽0l ,d{ khնG'0Z: g=7::)L!5ieh,]o 9ݑa}Fx1d!Nc7OuPa/i Uթj .˿3K 9QW@{P93r!t*UTl~6ƇU*o\s D~˯؁_()C@ěS"((#p(W"(C@焛R"(h#sV"(5nJu@"(FX+"L8xN))"6jNX?>;#x`7wޙ`#(#FeuVڱcGSL g?}6{jhhcǎE-Y/^ɉ^|~45 wM.袘x~_Ek_RJJ\@n߾xh)`b wAM{{6墭~ӟPr[oyGgy搇Cѣ>#7i޼yC5*A4iR\y>'ίk>F?"[&Ot|M~acX`زO y(8.w^^{5Sl ~6FOm #G` {/e'2vN27t~Jd{/"޽xODvx饗o~ c3G^ j;w4sժU1JT)}#zhVH2xbH2EįtsapWC`O(%"KrEjFIN>-[6 m, ?jLJS=ne]F?OD0F޽{MӭȆ' 3p5'06m޼٤uA%̸#xϐδӃ6(,,4 7l`"g1Rp9p,_+E+7^uwx駟n%~Ѿ}ג!B- 8'NWo6[!;ϛ[!xKTяRcce,]GhG=ς [ nك\=tR2RS"RHbdLDG."($a` ]} CG0q6ɢBϧ/~)Vp6o+0FԩS3V(z8/h#YN1,8PKcB ư#z ϯZo4C.m89wa#-/$X!f =kT!;gCCr2H(x:6 N35AV*c>ƭb!A!A2E*JBAo PMXKDKWaDE /lrxP83#-M',ÇiզJ~8Dd_x+1w2xΜ93&SGCxc07h{,X.Y\6/|`ΡxE8K_P`KY`׿e.`"/ N\/RT JsrwFexaCP[[kbNtAᒒ)vDg/Z(ֶD~xH xC]R 襍79C?ŸI]~l(AVyy8a3?@Zxs*eO3V`!D[>Xg A'husgoBo8vHLa|[ ti&a@⑼+l%^Eʽ| ?L&B'PAcq Yp}y  l~/^9 __phEd>qb,aL+, 2 )K|cv=Á\Bx 8EnP.H |A+8=2=j16A4 ʔ x0w`⽣P22qPL]A_,+4c~bᔁXr^ 4#pD]a0 ic;wnԹˋ)+}ri5 NB6JH & Fd,xJ9D D D<`LQr~̥񶟁/ c΅㽍75֍Tdn3qk<%NfHԧ̽(Vsi*(xU Pn᳟c2a@ "F. @!)>~kD1B/d ^~Κ5xKe ѧH#}. 6m}6 5 }s?K G @bJҙLڂH9G%GƢɜi?`mGPH{ pd lgr-T[pŽlW$*+uRHr.bR#cITJvnCs?O<1ŵDGȽYy;,t:ye`/<!; 'h6[㙖f$Rb0³AĈr=C%'#_c%jgCG- $.Q։xJ?32(S M&-Iᘗ!)K8?r%&`CP0j6rԏd`LE3}s$c  *x2>`.1/3K:]F cxC%jSFY`LD]vG?yc;T L@Wx=ca 5, P[󨋶X%y' k<"~b-c_lh NN 7d`0~Q(ᄺmp`qbb$p 0V B K_`*$lnSx}4[uADx6`-``zƆ5-1ok$iOtR@mB¢D:i?II9Q‡gf݄gym<DŽۊI!tJ(D P P:~k~ b<؃A(P{08 ې{ꫯ6H JJXxbAڄrXȩW(N1B|;Y}D;0PND_mļ sR1(!M-[F:r_ a= -ψB2gufϊ3"m_<.0eeeѦP8RTL"D^"K+W4"-.H kKȃȉdāزdB}X%#`޲'{d$1Nq; x(lƍLB!ceڀGFNܾa8Z-/EZC)!"r{ph($yܐ#LOrCO.Dx(Vvxv~oGcq"1O7DXxX {,#'xbS,HXoVDfjzaLCG7,J /v(8#}@x.Q{7mBF2>d5l^{-Ƃ ;w6V5k  }'C.rna*(^$N5~ =0ysZ0֖֒ 3L]X1~IںQ] y&mJe٣09o|iwc*H-A0aOK(ކ*Gk@0hӪG1hmlՆw䱶:hZƜEay&͚{meEm  * 0fdɊ*.~-̙CYf.^L2a ՟گyE|ˡ~ħu%tn/>caJ/#Y+lpy6z{z153~S?\Q 6,b"q"r/tJ9ed4vϚs_kFi5ޒ6fP~`h^C~  jyB*/ߣL!6W5PY?iSfcY_Iu¡}ӓI}v呌_wEv qu!LMl7;?J>_/ b:~hIaEژگW܂@"6AAae@J%3{K)W Hg(-!q5RP_5QrQ̗T=C|[:מ)_˓^py &oy2=yg-8cq 2#w U];R&"jZT xUSdСڿ=u zԆ   `jhUUy)%ijH&oSN*!K ԽUmWRGNkE)Eݮ%4hJ[RG&V/fr佧KmHng2?q#.C5EIY mʰHWR*:j ^bٯxP2W+vavյs~!  +9P-|B-I&)IJun7w @%С=׫RR6 ֵTTduהEXVT;@a%lJ(dLu4=ҳy-=npFrV/w]+  ~0pAW06UJ6(--e+)DL^*ZU5 AAAX2Z%Bk]Ŝyvv9ZSAA ׂ ZO?W9ֆ   `HkAZA=K  :\cj-AAA|,VAAAe1sO$gIT/Y1)BFUUmӳJ oAAV*YS'ѱۺ r$J)wa132s$X"   %0tˑvNeV:ӟ&Oc   n0RJe'O?LZk%4tCiW[ZVA=I$SAAAX u0*ڣ7k~96ndKW.]3 ~:AAaeP@ ʶAyE>hZmW[ٮ  o_ZkERV@*d#  AAAA)hdNAAAhZ/3{lȞOzme\[_bW+GAAAX䃹f\|؎w HMvf3dRڳsfmWw ~9O' V%9lvS‹'o"N÷f#IvlvYt_bSMK[3'{? @f#ȭJDߎ<7|*fR1sATYg?AAAX 8dx:ovW_ԕۗ]JI&JgS;j_ dgz5Sm Gw v<~5)]ޛ a]ک6IՄ` \ ZW®[lDϾYoO2F Ʃbw6x3F0i ٹoso ۄf6u}ތaklq0W6E)-sC='/u'siGssqrm_v[.?YMtL}0> v૗?㮺nxͼU[^̭_˷    hd١<Zy=O_@nn _>?\*}{k8>ݔF^%͵|iݫ1k8񮉤`)_*_} =q }s!2S{<8>? ֓|ɝW/Ûud%e'n%k8Ϲ^;wOk 8y]9y^Y̧vىdض21=;瀃f~gs-:7]{FЭGs/]0_!ggS͑nܚA÷{ëns`DTJhӥi4޶z7<~թ |v<^^Lw{劓cGq Mw=Eɨ7M士č n vJ0s9dpl{tjsa@e#9b2f|2ruٛ-{V(9e} s!=%ޗs.;!ml68;YM zd']60>+W26}$T{33aLJaWo3ƃP dƿ3Oyx}NuߙK},yp lPF,|G%wZxaGv{M=8yr7geip=|^k¾}F~^~Y^<=\?ִt,_ˮ7'&<){O?O].3ξy1eg?HЬQ  j])ȁ^Ǔ/7 ;Nqө!AchYkLU !]J]Rkkqm&[_M}~5k8jpMvD YZ`yďQ=w:Y.r=b-PVj2 Y 5K7jwNUgeAmaބJKTfW;X%8Vε>m;ڀEᘞ[?Q(n;ir/sN9 >y.X6m]^K 0_Iw{/BzTNXnlnlEMxrAl1Jgφ7Ɩm;ӿa2_϶pȮ٦hpE(e32ײ[p[`]Չ*>cҜ7^R=qO 8{:n4(N*s Hyr0hRBcpdT7k"nnY?WPvKKRȤ.mde~|__uآ+f+s3ijO~7SG$#CkE61[_z ·]yi$KlIg&p0s5hPC}z185˰-6 pUc ؉UIJѸJ6/d]#mϘ2g1#̞<Ɇfm__,~*ZAAU`yY(7q0m%܋c];a~+UHvGvFNFȞG\[woL=mÿ۟=9Cڶ ۟q8^=6cgׯz\n%>/3h^MFV"uq2,%/{%g?W֧&兛g|bI֠tPAR19畇] n`n2#лwGJ-sS L {7ֺ Krҹ#ᥫ97?k1,u3Rv3WbA u1ٿYXet_i~ MH,vߛLӸ}z;G|  jF<3hrܐ=T{C W_9;ǻT #~Ϣc==!٩agY\pQ.~˳xRlmEdyP}CU4M@ߗӿ*Dq-q&rxR~&uW ]6`xJ5 gmÔ%sWpD$K7zEO3O⹎(/fGV6]ݟムz>ZSZt\}y.9~v%p @u?]u\l5pĞ\ш\˾ڲ2Os%ѻZ~ MXؼB/2:Xo2e*  cLvأd^|Ϋ`#,7if&x=!ߩM+T/;;oHKjYtjJk9'kjAAV& ios_f\Me8ʲJk2SAAAVb0@Y R( 2iݶ#țuׅWeeqM+/    Pj VI䤿?Ivp4;|Z_   MK,Z   G0@a #f0K^m*6{5u   `B;',g4ϲF0 ^M   +7`YbV)R

    Y.W|SʢM pu"AAV`@A5aC@]"~1)>ߠabAAa KF :1{1  J A~m²,  AAAAX H0@AAA2$    k AAAeZM.N&"XkHؖ-_ +hSAAAXcv^a_ڈ!c)ڴ@ PնZ|-,M%   Q'ewiۮ= e3 T*5s~IfkJjo̪eST   Fx/?xCR%BP 8j´\Q~3Yw=f}0=L_W̍gqV{MAAa [y)׸hP-Uړ{@ՄkJp6).;14/L >(<[u…rO4&̀iLFަy(^;}9.Uj9/R LQ;2,rAghG{/beA* IDATAauÍ=o9'ףiЪjlNjB5'.T#bEyq.+qRDs*fSyj W6 Iu|%k1ѩgZ! wДy67MLj\p  72sY^ _PWx1gJ]mb}NC8Vj~yO|fE>^!"ظ-k jGΐ72p=}Ln:/y}@酌;l6d<*g΍ܗ-K?rf}6c 1Υ6gWAAAXXi PqB/0yl\%ƋeEf-G裇Zi&>88f(("gȨ%QYS`:܈sYؔ6b)k?cbHX<>',hM_Yse)pE*_<,KU&W!wx}Mn"("TήH<@AAXX̀F Fb*!:}2Ht$2z`lH^"J[Nܓ!kfx❼=GLtFm1נg.7r_?`0ou#rGG F~#vm_pX5^ rq:n_6VN*UNӿTTC#T(A FݹereY_0禥104:2^}v:w6=o <]vɰm7(z1lvt#zB![@+Tvw2nͨvbcfemOKNij_Fl[832# XBI@AAX{?p9ULb6?2TEILe1gh8-ߏ'9%=>bI|5x$J2 ƄL|b}Q&e(E~<9Kv*Mn}n;'/bTlCîz>}(TWwwAAaUg TLeUah*,GQ)Հ&{I;̱{3$rЅ^O췿A)+&%c$8Ygyvf?Nά2x4`x<sxѷh&Wi^Y:+Fv6߳ЙmĽ j[fƾs341MxcQޡKA2)84d]q#slŁ\{Tkm*lV%S,qu?ggcQ_dYXB9@@@#:z  š 0/WMQ7<,tQyj*V/e˫劻HՑ_ Mں0cQ+l?Ocn*Oq֥o2fY.s9]6tMvinRIinV2߭K;&/Ȳ]O;A @1i*h AAa5ck&Bǡ(2h|&Sox;׾7m0:YL6M}C= gw9Ĥ,٥1>MWӠѧo'J8G%wTQ}2?1jW3xo/2;D-h|K`u!bBѶ=ƌy&fqCl\ƿ ,efHDߟ.ZҁO;>{37dÇo+g<ۯ}OhMf6_Sk59E$o많oC}g}<_.tPER)05g׋:5L~هѻϾ;x_Ω՚Lu{qGR=sϮǨr:WRw-S/E$  5MMn6W1т\/т[H6UD3*=3yt^{n$}͝uRN{f@…^ǣGSQѓ-C=.&ՏS\/fUЩ>[Dx_a ",NHrd+>2l򎳺Z%(7y'(>.;#E3-1`}Lv$7 NaGcw1wmw}ng^l\1| ѡkb׉5@ q@]t4^\<2XuʾW^`.bQ2|"ta[8;Gu7zGq_=8λ07{YEi 9[֭mO98w(YT}$6kl@4fSAAAXuQ3gD>ѤknX__oư7&LŗPɫJ"rŪd^l!zUlbګzb ҲLF`Q14PFũfrxyzmu$Ž*C~*z-,u |j_ ,ݚِK)I6-++/zɤj{6нY@AAX$k/?u<&bT0Zh"&7W^a9'L|Yަ 4¯X#lX!&D`SIQ$_舟ʌ_⾍ Q%{%> \v1/i$X W0n[AAAX#EM\"!L;t}fW*-" 9*U#Z(ER4Q(Zq&OۘzuRaAN S6TŅ|{մ͵Ik|f@ShFV4T   >ő 0U~ʷ]l<9OR$[ Sdfi^X"E|-_.1Hm+A*|XӞhɫдrV   ج©1ё'TjLĊHEo7=׹5E<_1oΆQM-n24gieYamK),²,8-cB)6j2'-;oރRS:<Әh00\z2PSvʲs-]1 xB 뢍Fk4,eK*v혰eyK`ڋ.JC;f>hm[Riڿ,o;"i"~2}{  24M+svԱ 0Ck.ȼ m i "6V\L"򳒢| /3d0 Ԕb62e*Ͽ2/ϿĐ߿_$N^A0-.6L / s/G8~D~pE{.h䩼~ 51`[6Um1tWa4"Jb|ZX~6yna:˟V`,mrWYVCQ1m"K`+cUP&7^k*:SD$t(,΍ Fě2oT谎j@5L?> '\w2ʽ^ɤM* mVd`^*v<%L]OlVW/tCT o9'h:R^=R,LR\ץ]ǡԛ*qpy۶0{LW8ب,o/AHM*R+.wr/@Hl&{./D8q0 $ (?~$'(skYtT22PVVF6KPa`nx>}: VxΗ Vu˟oQ`PP L%H|f :XW*De*/]R+_P 4H |s&5`@TV4* (0?'|^7e1p*bEi3ϣ b2i* {Ѭ3iTҹKׄ< v-V[ __qx GzcYHRUj?b'4}X0%Td )0 |pM63@BA}53ϖPa"\S@D :1h]oס[a_O7Z  o R!a{%H@Y)5ؖbŴk׎d"A&'!2ylX? es UTTP__ιf1 pz ńLŊ©{R Ҡf6mj'*AqE/g6Q)4t" nփ()ެmH!=#L A.]\uH<{lNE4M_4>=FP*֑/c7llT>`j\9:?[ Fx@ W~/Š/vE- jScIUiéN"k? [6ejVc5_,PkM&TeYX I.pelySBᔴH;AciCDAX B+|&pq@I7e9{ G Fxb<"c*{0,&|8>(6Ve"D =Յ+D%x lnytNVo)Syc/ ۾#1s>zCٺE]]AI ȎCLGb!;e#fC.'X4&O;_C&s!PϿ2/{ߵ#L10i߈Xd 5YXP0W@Gl|~P0(Nx,W?rb ;<~2o G- k`9HxA Z;]䖁:^(0'UxBS^ {#Qd Hzm)iJԆ>U2XFEϊbr1BL0 (F'Ľh*i)X%6bxK\NL,qKQ~[kK7&Bw;ossϏMwm8N6;HJ5(a}& R$, O ) ~J 4n]BNDS J+FN`^ SA"WH;:z.L(v]o=u?M5{#3nP(Kk?ւ̭][PjQwŊEW!]LQ:o@)7hf5nEasСv,_tvŞT +<N{[]R:U$ю_,FEnc^?QZcڗ*M! yGQm{gv7=!JЍHT@AD b6ԧg}| E;"@Ezݙ;;4gf̹eJ{9n?c!_!>ò(OD/Ӡ}.捭 bK.7F/No趲*5̀^'SOϻ;OgKXD5A 3LˮY=MkK# ;VyM-`-$m`&tb^ΘlF ePQ`H]"ࡁP ronۆ?m/( HPlo a\pz>`thKxBna3p(н`e=ie8QgFt]!!#r Ic߇EQz3mY:d0<"I&M32I}X64+s#3Fuz6~3vѹ ױCR _I`_F8aʘ`q=v{spR,ۣk# (A[|jՋD-ێo8 ONQ/gfRѕFn,}YŻwǛ@(4][y>xޢ?UL>w_<..I9|]0zSXX "Eh]k E>N)n+YhY$;f@rJ˧xatB.+S:֌Ph%ϴRzk "c0(\k1[T,Ck)*B`V4X19Vڼµ<|kΨ{pj W1~ߡhh";Ǽ΢Q~$FxGʲ2p (wvT3FrẝaŜ^]c̓+v[JBcriغt-4Jf /A7}[w1>V-OE;pj.3߃xf˾5KY_dҥ3n+f{TkDlc Q11? =q?hVRqe+R=edq3v~k54p{ڹk G5ag. 3ʺ`N *vF2K^?ۢEӶAG&;8kncyմe4wUJ(w~bŮ#,?\ObC5Y& C~ecq&z1j»w^ WWSQy[uo[Zc>OKǥ+C=jA8VAoS^;vŶgUz|{vppyߴ[8gTL+ ؿgCϱKC XDJW1-+BC,(=[Y9CQ+?Z`Ξ ;0Ym8ObEXێCۘO3wƧsfr*g<~e{J(fm\ ~1o=|};c&=;R%*d)-N>3|)"gq)TG1fTY8-8\k]`/A; x$<궻~PrCޠf @N3C,!Lp.T1|vǏa=1 p.8,^nK::eTecYX~2A)%[wߎ{b"c {\W~?l)Ez~MAwź*ZڌH&.5a㯤淘[q9}2sv*!/!9MLڶY̶J˫h '1Ӻ9kLS۞Va*Q?fUĶȏr(96[HX؟ 6EWi 6گPc^7Cu>tm5:)eCib#G3w1y|ۙ2j4O%\+_NcZ.^ѣIG ms;|>5tu]+؜OIˍT/N?~Ai=1?n>F#B^]wTb} VXa:J(%oiv<" ڊ2#ǁۓ!졀E&5F-G`6krf%^jvZ(bp\ U:@hg_7 ba i3PڛeBڍ`@krcTPcq9ldQN|[H-Ǚ9trPz Wٶ`{i׍݌ᚣ+ E0#vBweICEO2ZFF qLt_&ehHϔf?Ǻ'#{_2ɯU+=%#C.5"TGe,lq]>jDG8ks8W\2"A8N.3_yee}xj\vgNko!''>Inj2L]7Q%k5s)&xʬœEESfb۵ wRlv7oƭo !/Yz*'± _-uUxًTVvHLBx /#9S !,fe#G2DDrs<;Z/!7bUyw%ر6tm+M>rrr&ϟ. wsG+ )_;a!!:#TT& U f򕧲f%E'"ZbR޵HtV>]gE/:u:ab2ZSU/*ܩ&iS*R~lThclYƀXu9ƀrlfTj*RJLp4O }-<5'COEl,81\:=%fZ|*x+6r!!5 = X=y#&vDhWS5Um Lj-ZwLLy/wh,ؑ Tin{ _^K+`A'3`3/Q62f2h IDAT[ HAk}5fNi ***%T1JePIle8DʼwbV edlm(ǺCGy)X\wU)ԟ5e~Wa;_9!_hi5qQґ}?N;?)LY-7/db)ddEְ*:]'㩠\< 8 cf5O 7]Ul MmrZ͊ٯ1w3[.?%oWJ+Sј/(_UK{T<uW/\"Rvl;#l_?!/ǵ1 L\sfO3 S8v39=hˣj4o^ ?ڔгj%f ~>r/gŇOĖ3!3<`7\zԭH^`?57䟯=І:>o pŹѢi_Q} 3Ab֌2 ݐQϿqv{AOCns_5FZ+Fҽ)lv <]p aExˬ|G+cB&/[{`W$^v2}R"9TOAAA>4Msֵ|h(_FE(-sح7%" S̚1ZbA!AAA8C   p!AAAA8c    g1@AAA3    b AAA 1   qAAA8C   p!AAAA8c    g&7wAAAs5㹺sjٕ~bRN ~g/FU{蒕M̎tsv^yٻEܓ4B1o.clldF0a{3rp_2i9t Y|SRu1q+%)̻^ˀ9x,_3N9cbݭUw'NzJv+Zc҅Lb~1#   M\1 VgU;*˚aǙ y7\^E ϑWIzgQNΦ-[ NOKǥ+C[R8ÓCҨv2Zޏ|9fƍyV_?Mϸ'7^&PH'&2vwJCx|K +Hǜn䌜I6c;!-Kd+i-oyy K&H4lKy\/ }/Bӌ$Rj4ɭp ~+y}N.F;=F IFɨ辕l};17^M>7l~u@Nvl,6#UM|\n*Wuw]LMfrm[J,f[ Pv ,* xecnδkZjIl@zi%g삒󙵥 ] OF[F4j߾?Bczq_̽/   g "ɛm-g&s^}p> -avX:u$M+1 ?Շ3hrqp";u?+6SrSc>Ο-Vi{Dǹs֯x }v ;DvAgm)܁q wh䩸nशh޺+7)&"L# n+ ɺYr33g̽3zEyr3   af.R+nwwfuh6rΨsd~Fq.đOZ<qN85~؏_mA>/Oe/r)׏a(so;L-]U._zZrעyQ7MdquO. tޠ)FCLݙ3yd,xWNc2N;M͸AԯĂʍکɼ|\hF/xF2?6׏Zz=v0Ċ'eѭ pQkZe>AAA(cZ:F ,=lP-fe*g\Z#Xŧbi! Rd/s=2 MTMsgvְ*:'9rM%д8&353}tyoPJnܒϲ)k oOFa+7+E77O/Wg%E;Pt"ʏI"*˝/Ċ+ظC&c833AVAAA1HlEt£<2Y#:Vox=کwnbHhaY3|V>|%4EqG\>Yqg)ԬKSA89Ga!0+|6眞'`/0/䥹E2Үo žiP-#['?Ey}MJT0&ލL nO^a>{7}ͤ{&Nomٿ[Vn?HA.m&[XGUAA(qR z8qy?l:ʮ3z`]iّ`N_>kWӸei   b $Z=QGY*qޔ~V}OxbG>cxr,j*hG<<ݪW<&K-f[Zo.!;Z;?&eSӹ"2*~\_eop}ʇp+l*q<>+:s['    ƀJLw\[JXq@:t#gT8Wo,uygK&@ݷ#aޭ7W~z XVYf[2'IG/]u"ޱ:Ӯi-%%ZA H溻dpNSj&Sun-%k͡ړfE@.Q~j5O/Ck/! 9d W6wdŅuO5jQV=+Gr;kՓ1bRkN%?a̴h̠}ʯSK^[ hٱvg]N4'm/ɯ2d'6=\w@VUKdI:hgqwФ4Fsɝ"  p qdjM)tԸf2Vgv{1N~5 #`7iЕfq~ZegG>ݶ|nILdCޜ"(FU;_{ u|J> ò_9,|߼}7ǵ!"2q M=(J}0_]aHhaY3|V>|%4Eq.潟sIhw|f-ArPF*.M!N   GWK#xKh󪟸=rF=ڣsc=}{a^-Zb՜1y}b<{KW^sS) Vl];tIdQÑK0`tkP#C n9LN]ic8PAyZ\2iZYO邻x`@.\3/\F 3\fAp 

    gQvm*-3p`1O^vZyA7\=LOCw{*gW5e }rZ q{ڐ9ohaAAAU7YןH8 n_HfErc-;`96i1c OQTw)Ebr*N|8-[@a~.]dx| so85}yue!W [f ]øv7*Zǃqx%=?^75f÷q^>o{N+F#~RR3J'ξ_qBL%]1sZzj̞Ol>{L/پѲ{jZ6qXԠ 'umEnhaO{q52idc\_5+m'@i$7)]e/Gq cWv/%wEhW~ ړH<&*ߞqW^Jq!c˥ :a ?TH)4M0G4/,PrThm Ђ(Y)6hia1@/gk ނꄎƀ`hFGZN 1-Nν7Rp]]Lt>;)Ë́SHJ=]w O>=g_3'si^ `4v^3/1-e/Op71;}yeWuWfI]^k)0?ZuWobJ*{vL="5w Ǟ-4u>i>,fQ: NB6V?6[dˏ_fމ.!eڹ,$ݍٞլXSH>h@!ye1m׃ io0%.a5)~K~N5M 4QI `i5sTu41@H/..ۜ6gICAAaNcB@ih ><2<蘩#Ixmҏͽ+{Ecϣ3hϧ}Qݰہ'& œsxohͣ4 rsIM:2$eФ)<.ܽLGFFyಋy(aX`Ͻ>䕕tsM9f͹4K Ȕ 4.蠑ܲ3 =(AsPd]p1Y/woձ?ϿdboAqkaեIipΤ[!TI ̔BHpaݥckdçѰjK7PKmIZױH IDAT~ݗ||Tj=duUjo|;9>lHSx1[|O1p,a-0tK96GX3Z?BJShJ4nXoֲ#-m,p m 8)^o*[he_Avߤ?r?'؂ģ&[n߽ͪ`%cI,#+&0ue>(`WĖ c:kxsH%c4RRS) Hu6 9.$9oT RRQQR-|?O n|QMdxpͬi\TUi[h@R=C;9{hn~?A,~]yFIţנyA4̹F9tnF-K$ 'hcE @ WUBO8fzzA8v`y`iJih"vSXP(W.)ɖx>*%7%v8a4^a/v.!G)nh_u|2#n:<ȥ YC蚆{ZK]kySLSxJ@oVhF Pecfy7cr<|轟[b︥]7Ty[ aY2]iݮ=[/%m{ﺎO><& tK׏b~>?9ʛ~M"ٹn3'FөNFNV4ov6qmIaދ#!w򟄮$&Qw[wj+I@yPIGi4M_kHiEz |ysw$b4jLãyC2X|f.ڲAtmKْ_f S8\n+rA=[4L R-c@iknӾcg<>ylڰt5jO!k+G[4M<~?̿32#vA1TxgK+Y6u1.։XǢ@Y_b7>M#hQV։#81l 0ͣ.өUǘƕ+-갟G92荶$iqrhL}pNw{<ۏ.##"???~&%~ S9R <Ƙ7n{ u<7λ1r> Kh>΍}g:v>u8>&S39f#4cEJ} Dcx )=D~_%"jOfz-$zivHҸW%|7k Pd$Ve X[DX'AhcYܥoɁ"4֗Z뵼r~Ac 40Cy˧[6ZUTFrrv@~ \.%!:R۴ߠUO7z *qid4?wOa͸.g?=тS>ۿZ=|4KDEI%۞"T87m('CtZ~M `+/}j@.k?{cw hliI5=uwv}/m9}؅L.c3ŕc ޜ kwMFlޛ^v?!-c*{ bx ylr&,X͎|P~z R+tpRa K1jhUQih- aYTC0~pݕ(J4ʓq$s.9;?ҩY6^~5o<I. κoP78WjjծUr-V7p˩*8tzx*]Q|?:7"7f]%$RAsvmLJj)%~¼\sPy\.~RO ɉؚ4MC9p4dt'_YNɞZ4>\UkG΀vUj#ɷHiݖeY)/DycuS*ؽW:tLqAL#ZC y\4nԀ;wҲEZE+厩.5:3`Y }Eٱ3] 3Ǡ"OZBǞ`EVKNl֔mZ$ZFK( v6_Lr=No|A;@ /PZ0Cٲ g#W֙Vt;It$_„^cMikg-cy~^j:Ώ'1Wr{pߖw{<:u%sv82?y.H˥s^xrZE_ct-3~i捗yدn3jgܗݤt{kAW//s:/;pMwk/b묿 U{jfhQQ5hfY^J1'cʹv-e*uL% {s^:GO4Xgrк}}8YyhӶ-cヌ&pa_pӤc^Zee/Q>q{HQmst=;fl߼=;Fu!)Z%  `438FTOFz5 5 ]³=ukKݡ\f)0/K~۔"TGv SK׃2ʫ7agЦ 8{½ciIެ5uE]X2gQW:+8MCc[ +gFz)Ļ׾3秊 qL,پ]|d/uj&iٍ%cyL'$Q#-'*oyfP~''@Nd ӕD. =V N}nWyKiȩ{R ?دwigvb2ͺ s`|zLjgȩȈ#>>ӏ1O> iSnuP,R&=Dj}*׏pxTf20%X? s1u<>کwnZ$N޸ԭpD(}4\Gpy4dROBk1|Ά'A0M0er UL]k4$wBY໏uB`dB ߚOKKå\s8Ho &ֲv3>!'(:&ϊ`C0Qi-亻Pt6^¬Y߲3P9_cpZ*<;l:o'#AqB@DeWt9Ǥ Iަ/V%`Q)TI%X j{}E Hڭ'h?\V߬~Ě+sʑא}[uffN|@|LN=g(Zd&S;YJAR-C-T(9(P䱷@T '`Pk 4BmV\R 3{ qyeU)/BDW~(G:6_N?w;^Yʭz~%yO\q̞q9~ԃ.˕ wpFDR;V&n eąfJ}Jr$z@O6據˥5P4C̈We1~?y$%%R\\L!^a?'ׂ "M3lPJa. 0())Ap\T(*k`~2 "NYu w9'֧fɡ(FmyK44`yUbFTؔ+?l:Lv+?>/VW!VmēUR|-etzt΢vՂ- / a'P)4Њ*ٷRoMLkMOB'RM9(=4 ʹ˼梓vhAѰgWihX8~V:P[g禕,X+G U|}NYEv0EэF^gCVMCK昱=gD[X#Q2:U1cLJ̰v]wGpيc Qt)s:)[e] ,]@(bCw{C-jDM-vXbl1{W@K[Ocڽ3wfgLiw}Ϲ3y{#nz(SF,t+(hK:f޽JiWX֊ -_II.kW`0. PJ6rݏ,;/ȭP3Ylٽ0q/~{{95v'Gя%o-;oUʸO9>WWs'@v>ANǑ 4Fٽ8b ^AFnC9cmj#/a\ul BngFwgK! ^l:ov 7+=xr08#PgY8¤dFl|eWߘԨhx便F{xiG$Liڥs8s^$Tݧc~ 9Ce&''xYx]4^5y|F@S 2}yBݿyኻ`,<0o9+>W^~s {8sxiCmuAO{Μ$f B3j|;wc象hmzbV.O=K *ع3A֯N:]J5AhQ:t[ߋ+ 4ʊ =˽N2JzaŸA39P LӔA#ӑAhlڰ- r"qq\-@빐i.]p1 ZYښ:B pصnWJ3@AawΥ}N搛2xxn\n]@Pö-Cjˋ9mTSV`N}l%-s^+_f%]JH[ b   &-]S5jL hwy@ -VH߲i4Ðo!AABfj7\o>֭2/E1AAAA^f@ ^$FPRғQn_}S/gƔs8bPR:#/}/7{\1$^m7jl@ܻHkfr9п8|j=e.>|(=sX9۟m|y)8,oq֊cDOlzn„x)[|0&:c|:>,Og Pᬬ,?[h<<knW5YYYx\y~U3y}aGNlOGyei,{¿i{8kDWdƭP{C]O~ތ3"7&rC٫W[̢d}V֙¼6XJy˪ ࠄqZEN%r^:{[n[^eq]3)c.KGű݄_+cxw h-6ebm].qOâmV%7pCiVḋ#'l}"ׇ>Ï-kN(P䫥5'')Xmsxg^m1}))ƀ?-Ȟec r;˿ItnQ_QMVo^ .d9x_-|rEǠN績-^ҰyYԤM_W0ttwg״=b/Riy/'g3ŻϠ8'XOqs-/l _`-AcgJrz,l 7X;(<^f~9({~-c~ {_ә|Ž4m9wNNwoð3`5Ï}_.8e^6.EW-LCn*#RͿ24M๵ï7ȓM`~t=0cQMh`3_>x)D)7^餬FL=|Xǿ?sʅ9<|;5?W*.{W<(S@uTYN`- 0'i&><]ŜG&q?L4xN9i:P2DJ"wK˸b>?y|.W?(Ʉ!PۋN=a>C˞΢M—+7pv&G͏reҴ:ұS!tua cx BkDh-ryzڕ3/3/q2a+;s qPo{XEI IDATy筸p#L캁gϙțL:5=r.v?7>7_Q }/X-cijʞlS>uaϛ-.)) `!]wL/^Aw3SZJ?ÿ/#+9r`ooȴ)SߏJ@ՆI.: .sk3x _G=?|/NׯVµWۿg2{%9!~I? }i!aΦ_Yʈw.E\uhv g|!//돥,]4 tHK_Yݙ|۹ރϤp]cna/YU=Kicleɽ Y>s=U ;,|g VHVqz^zbQh*]{nÝ_G^?[oE)k4>6^gYkd6Fs@ h-e=5Y\ X]طTi'rӠG5[(Kb떲݁5rJhle-T[֬Ntƞ$PSImu~~T^bzw)oTزy N's퇰iu6b0=[^ÖmI)K%ogqB}YMDZWI.n r{QOoy~^W.ݙE" w'Vr50 ߇WI銷xcljKEmk[W ΅{?{Oz'*~ÔpC > et) 8d"| $AvV [W݉ET;ҫ0wt2*Y]h8=}R3ۂ 11ZY=99'8ENM{x o> jŊZƍ/IwKۋkW3y{q߉CnxCnp0 VOH\\% ᫯R01G}=F>\l rUWM모6B2SkUElC;o%l k+ojjj+q5xrwsR!Mmc1g[n&tN>j!s:@ $@>}t>}{6/HN= -={mY9//.>S剿~4A,`:>$Ƀ9*|}Ԙ_fEqӰy>\tXx6yc~^WEM+g6jDL  FL*oO1 ӛISf۝0ȿ*Q㏤1#J]9e|[y >p嶧ȣ~HtJ޹~n]prl'WZWF{&<2,Iq$M;۬^_-O۟6o">1Z`(`yeo9Y}&l/gXnw^q!/ӕQ{wUOpڝwqϰɯn?S!d) =O2!ᮿBh`5HW3޷y\}l3h[ˆ`ܞc*kϥjJ=v?_{;vŹwqMS3OLjSϕ@ nN]8쐃mӆܜlrrr򒕕vv0 L#kWq݋3mX Y<6:*u`A rIذv;vm17A6nXCäJMM-͑AAeF *¬ 떋 =^+`ݠXb+gA3@AarR8:>0v鞆aĵ}1AAօ2vr% f  n,oqvq \ PE9C)] 0t/rlgnتߓFZ ڶ+b  (;6mhKn\rsq{p]\ݦ iv;s C¯ROAA .pa>Cе{UAv.]z"K   +auX   !f    f    b   n   !f D1%    4A6~S|5~{ LSү6W cӫH tLW_7CЫ>6^^R/:!kh]t?-Gpq)Dz~p9G̫ܽ5X~   ;)n tL*~7=GMGWg`nZ>t.<\8bo{MoS:K.o9WԇO3`>: .oFlӷ^p-/~3,bp_~!rՌGgf;L䔁gci/z1sAށ>:+S:Z:OonϿ#^2ߦ{W>%}fc띤M v3]F;ХMKZC` 7-gc` pEDjŘ`+[](k2*Xfa6Q3h/x7根o9X ;%nsq\f>|s((I LdX<.#=pn% RP؎{ 0vr3 +۹o>6_CvN.@cZSy#kV. 9Mi1V]w{pqfg/r߆)Fa3n pP^7-c]= U3kv0ﰇ96k[4a)2M@h R||1FCC9w곜;3MoXnpڤ7q}Fl=d) B:|˖-ʪ*~럸ЫWOB鶺ERmwǥL~\spP3RMh8}=-m=n_~(ؖ,߱iM*4M$;"BC,eq]0xq/rfFB5>,Ib߬kvІr~tߍh߷G䘛M lx1^=K" -Ft5b>3N?#ǻ3O)WRлpv@t]+5xDp' i!8kL'm"O[lV=#&BYil\[*WƄ 5TXǦkqjG_)q63pX|GMj/!R6hc ">VNVvbr8i̓րPQڧQJ4kAvɲ (͵L>ke܈fk$3PTmCS5 7hc#l@ޅent†:ZE*V[cmˑ4.*-I )EShS WKj,ݨbq 6_ * gqwW?F's޾?Fm/.)W}G|pu_n: JWV 4i[? ƹ,-"&g|F.7`۾'r-sUd1[I9d: ,5ԮyK3<~CWQe„Q*z?=qJ, *b]YpCm|Ayݡ) yRb  ,ԯeyڣohmW5+,n`%3n}^o&|Fth^5"#Ft4QfX)0x+l?rn%}~}tP ,]=iR'q}ǩRj4cm{Et$;fA =ŗNGe. IDATK& clDilT:$qI~]b;ɨ=6|Ŗq;1񕯤tcJgKr KX̀Le޾ҡxGT:,@-E3v\MwBC9;9.: _RWfB̞3_'(Kas|R1|W[|Uue?>ꪪE`u7l:у¦Fƕw?Oh?thzv,-( ʩv 1^CO+xRfn- ρ4aAuÀsoNJ<7ڼ%cRRҍ JкqQq?Y[R6eժ_YW~P&@5\++WfV_ T.' c#@%'ŸUw[l'^.PK\i+gA7T.`'1-T.o?qiw?>SCkkl _0154p!ǜC==hKksc 5o36eѩk6: j flU2i1AAv6*?ႃ/è>#O{qQ==2nOz:K }g mkMxx-W/mIm4pJsi*b#폷ٗcZ ʃGաOrËkیMlM'&ky9ూoB{XijNV$[EbM'7Hx)'Fk჏> '!@J e)lE5o:\AE^َm_ygV[K_]r9jش6@qG"먬2ȍ Hfm+6DqdtQ: C?Vގt"|׬-G1ANR#6CO?<^\@vat+bhEAp.|u靟a,-[";ӥXGpȑ0 Ձ[h rF5.!Ǩů5ڬaz?E%qGϕz*YSp3o1AAv2X,'=)ʞ nWۼgߏJ-Þ*7֡bmDqh12PvKC ϸ"#q8zH^uw51#s}N*cbR2dZ,`Ub-c V=m&EY`؄gY5thxH4tic+TC!U,>LHpmSfhXe_)1[忬бc蜭f܁BJ:Ul66;mgV=cpmȲtXGu> 1tcꨘ`M^Q6 4 dU] CؗN9NlT>\@w  5ݖXo6jFznXV~+0<(=Y[[9O6YжMʅ7;O| ch-\ta*< :u!+Z{C/W){f,F  ; `Cc( P>g\CKygE.-Ƀ?ˆWqxxs֤=y{q⽜7ȜiMf 3-g}\g8sub=ʮ~gu2V% u 2mSOq5a/!m-vm]=`[4>܄nu F}ۺQkeʀ&mDb I6K&`,vso՞>|%TDcY֕s?E`Frt`0E;-@$ q^+E$vW#{Q<(uk-*&'K?,>0s( ghJrOҳS.Ԗ2eqtڣAV6Sc-"/@k3փT4=0d$Q6kX5jprT̖-"f  NH=˾\A5lݏ96~~37\ӑ!G\s]/ޓ≊0Py'qY?O3a #n*L3-0uoQkm[ƌV6,2."b~ _[֏W6g2iԎJ*2LkǟpLx$&N B:H.%-q뻇ߋȲr #=a|&uk3h?J3o jtCGRcئgFt0O+"&@Ԕ-?8uȘ)`_01Ӟ}ǚaض;Pl;fA ܲ >~pu̷L덙qnŒKk+8u7̨?uk ܗK47m:gR 9f1 ~ oS`.<^/n+tZ>@$>rz.QAd1얷7ƍ/);q r -'C"_q]2fr"]| _| iuSf{e`։Y-a456=GG?-ߚ]` ck%HFBmWl\@M !Fl7ČԖq]Hl**Gj-|m8}(C_V;3_NƉ`Y*8##{Xp}tUJi F;wL86U8t$kC<1V/ -|fdڍ ӛm{N-BrI.C"A M>lm d˦;6o4 dn(;tbQ+@S]YuRSRڗlov  ` '1P[_!+﮻*![`ZF?B:*鸥+P KӞU(KN:.c 0h(tNM͈9(qb5o"6l"T%- ;N$4Aɒ:i:e7(TxG2[Pے^ Ǹ;=:NU:1 uF3d3Sܶ-{ 0GMhݿɲ+÷>` ٸ~ 9t#aPA}k*6od% k(э]7;z/zyڴ믦GTk𪲼DYt5 gpif$04Ekj*إApaOA ]Ôv/\?W]z!Kf ˚:ElF#Yrϵ[;Rդ+T'Eiftlƨd 7k[µf}2,ޑ>m'KPSu˟+z>(e;VhIeiyFjWM~mQq`FtJK@u38.,. ~ i$ -jhP=w垩 ]Gorc}=Ug|El| =xD+:nS-0_-h51Ҷ0uv\NLTjdW!TsUz'j8`W ~R} ⯯ _fp_PȐ0wUZ+׃+Eq_&ۛM!{Iজf`rE%uk h@(g"ZZlhߕN~ -k8mW-uwx Ah1 xrƾBJڒ?} dPF@N6.Å+[vM, ,v3 H)R7(}ڑGA,2 4Rb 4H_DΠ4vOL6MA6ecK   f@rr? ^&Ys:L`FI笁X) JΠ5v-Ljt2 91   Nfp?MRJ unH)iAh0k`73 UU t :J?N8@2,` H   f@CSR)Ub_7L3i Lh*iZ@cj.<@ŏkۤNĿjRT3cv@;P/Ui73b@iAA] Pgd"UrIys2trA׫o9h x)Pc`'sSHb LLn NbD3PUYgD) L!AA P OL7\~Mʼn mُZn:A?G<]s mc$Nj 4hwpLo|Gs3Hi2Rxk1AAa3?q5ЀO_7p,*Ntn,Ps˓6t#4Q&@@C?e 떋NэXɌd 8S@AAvf3 T hċ }N%iXg`8 -s @|AlV?  yrvlm>_'?Ubm;C3FEB!   Sڹ jPg*F*Ɨro 9S=25 tBRY)@lwk ^@rWM TLy &,Q7$Hi ,AZ :,/^/^Ǎra PJIAv39U[7a*k^*nOz hhj@  }񟮂O'$4C4>OX].QUƀP2j1 3 :N{.oZ'11TXm ! fϽ$77,xlIig $H)fdH  9Xtvdn&@&#iV+4(}I`ítS-h mQS21E7 hPtKxe+Id23Y vr$3@Ah7T<^a'3oKR_*e}xO' ܌.r ?K'l洍o$rqI2:S"*-;cV*6ZJV&w/vFYuRM2A2BV,6*Qm)b=ӓq}B*Yn9 +qXb։~xBױRazq1kC.AAwa݂ $c<9 $d(;~kJ BCuҽN:htHe 8 wL!p+n@S%`3b|Z@:I2RNh( ŚNu;KC'Aܣ:KH6D'~X Z2t\\sô+! dy .EKe;[7m@;f hF#at76 >:Ts&n@QKQָd,[)1 >{ TF2nP 2H3K } KrҰ`HmSe8't*F fg6*~rlÏ,&S@\JeH[j\DAv'B-Dײ\gp738ڟa>ké͓1VMXd.d; H1- ~֒8%@$؏KSMHPMI D;Sn*zk,H,tLZ8M-Ha QXy@KdDrh LZ@S2bRx,Q'iߜI`54H4 MH-yjڀuG1AV?6eޕIQܛ]XX]XDBEAE#Q;1 FTI8DM<'r{t}U]] ݙ~zC}L1.;?յ~6n[\Q#N@ɰQ8X3nſ/? %ƟV52(̜r6 ;%ؙAlY(Uw)^#| A~ IDATx:lt/@$"Pl T.XSRgp=i18!@D' @" 灠#HA !:p,!d_'"1Mu@`G0) ` A$;V:L8 g@`>S˟HCWf-w&kn,68\_ "/C;OXr||!:e ڹlb/Uw^] c?`S`'ࡓ[Ŋ}Z YA>jTc@(!9bڅ5v.Jb+ϢmFD=k4AH6~'MߗfʼdaĔ( )|9i'VJ#>!pvC?#;ʍ $e?f^+pBB@?op @`&HB%NA30`- %`6`&((((((("JaC1o6Ll:OL g1":_9 K^kX~m5٨6)a/dOx7zS_v72oD`[f@+< Un4:YbK~ ƽSJ+/z~s[lwU[/yw86 @wcp{َ-XEC(_睅Vf@}P(5\.7 >b8y7$UX^ҍ#XD9]3[+Yi;"3{BLV ukS2쳫1й$ҊE@-6,HV 3X Nq a@T K_gI؀D-Z;֛anh%S֦ {/c[Z=>G({&^&w/rqp ??Y7N~ 7[] X]%cʽ00g?Vm:wӰi,_{ n_YY\iQ%` ^9x Ok5k7zf~'a'sgb]Xm'81o\.O?X_F̻:,([|#~"E_O[^_6Ak,-pZw5kj*4D˼NXS3 JD%Ec`O0%vv&T=n``O$ Dш[`挩o.ba.B&M!a4|0 0  0 CP7^Ͻ៵r]៸fh_چW]^= 5ߴǫaasǶ`ɼJgNq~L ol7(NZ4T~d_N9>\;&]yx[0&"4^Vk~3]chycNǟĴbPkVPh}HmybBMo!L@DսLi m/3q( HfBX:HD9i6 !IQJa \#\mC%&J(T6 %(((((bF/F[^ŬEwGA!twZ6N  h/GMߌw>).J܁j_ʍKގ}96a3Rx齕7xxꗀkVP8tB,o1I7DH3/#- "'ɋ6Cly ۷6Nsp'CX`K$Jbo@`W) > D6@ !@ ЊjGFrڗagENwÚaK4tBV Ƀ_*(/8m/ƷK{`.s$탒*SBς⇚;+L@[8LIH|>Ł}cEc0SQzl+a?1}&垨( -DNp?k]\@pWX.]2%^y4"_s I} #PDQ>bЅgՑ !?)I($?bg*^(|ߨn|){OY?ڏ;z 4T|npp\;7Hxtڵrco127L-͂BF^t ӗ^Sfcn1y.3Wt}1,t@cOAoCp#7!V$$"oW`9VI#$B BLȰs&8?[$"<@\b A!$fO"ĸb?;L=-_2F M, A<>Ǧ3 %^ҧ|mQ i8 Rfb^ʻURAÍ 80y gfD CSb  &d@7ruRpTA^e|:U󀭠Жe WRLX{""vNꛬP$!z9i/Բ{G#L|jg|"6#ɻN*̅ #Q)ɍPy($^P".ݟd,$cE, J) #IOItp#ks$B͚ rou>]!@BVBDd0Q6x'o\ { zZ'\6+r+~e= `6Xn*"T@^-3H \.8}WvPRo h, m;^Ъ&=zZTH}G B!j׽XHx(`H4H d=&K^Y Jll!VY=):{Z &"ÏS~7P2]l Z-TJ~Ā$胹"g#@bN\y^|#c $N !޶N{DS/misv=ŒʛuˬYQEQ2DCID형l[p*(((((ih31;=lm-X3vy;v\ x)X0GWsq錗',}w9>_2\BX:XT<X+xx{b|Cpߊ'כxrBHq͛Wh"a+ o)R/ jBz9!@ZncuIبkA&"}IfP}ycG bow*LHE^-+((.#yQve+:F5q͐Zҡb=Jq9^ӎl#нÊ0?j}?WqNq|}pތPkXoQg# k^60T}?htv3vbï~ݸwJ)ze"CUynr٢xxv/BQh+ &>\|/oj t zz_ 8ӊ-d=R+nBj-Ag(`"*\r8\7mrFZOZ(2i1}P`] Oj\PPPhn NG_,Oۡוq@\<zp /āY=)Aa8qZmǴ⏖y]R[I, 9yQ1F:;{bM[s0kQ\+>,y:@_jyqN ltJ\rpQ-:Vn:oUwGu=4Ta^Gp;@rVWbpIX4o ُUe,4l3 7WaZF؂y+oynsNZ d؉8ܙrW>ĘQjor|2l: u%߾!9Qqm"j9Ն.%! $IdNb$X&Ho]6j:9Cz`WjAM5uNXFwcZlSh\ 6 #aL957`c;bƬxpRf`/_ -Xp Ke>@?ˏP_ .w4y]KN5J}4n~U:؈^ۆQ}}&ܭ`M݉<1dbS1(=딏X=bKl^BgKY5jxMỈӑbq/h'B,_&DOԸcOf~^'D_h 1n;|H |`65V`g;ݤX8k Veշ]*%P]2D]n'U3x?ߘ1#6(7 $$}o6^OM$hZ(~DƐx?R DعޠLey IDAT \dzW*jV@ ^BAˣЊF@QēAK#Cp7q7nx.SsMy~%@.On6NQr^tb]aFx2p 5(m.|vԋo]ebjt _ KR5B#mh2>=^TB61 aO5 HWII:J&NEM)lyW.ϋNF‰Wpqο;cz&,v^~3^zL<@ЃU[@>탒*SEh51wVb]sq8v͑k7ǜ`!TWø~@cf/ٸp^~e2q%cP˛A.I5x T%~soaO8ښCo؎8XcNӀډ>g2GV-5Sֲ'`oV++((¾wl{ФP5u@I|ooclv !=/ܠ?WĿ[|ح=*΃Z%y x6YҋvZXP;6F^?n  Ф1^`,,XNI_ ӻbŽ„_!>~rn!{67 Xo6:ZUgm#ž@A;ױqi|\~o\pQK@(TU//"ub'sQSjBj]+r18l?i?Ǣp̯NEWWhR09 J2`H [<!HO{!(ut|@T6Il^eb)B9tHQ:ĴL H@lҊV"e*jZ>m2Nl]BHsaÒcS#.< ŕk4p>60`[B ft wNG `żQݼdb ߳ 2i(!>m'|x?>z 4T|np7vn^GmJ ėywȂBF^t^SfcMx1t̙e.3WCp#7mO`03qrA2(-Ã=|#q 9gvjIIgVO4{Ԑ<7nxbO rDڣgbs|6}سPڷ)95h[ks 3?N$;1y3lS._M4b JꭟbO1R4hj-]_VP_?qmh 4>i?>Ę{v qH 8zt5}6c 1'*o醓jω(m.u5.n0bO1;ƅmsYRGZGo؏?fG8׺gnܔwxS=y?I|=Mr/>`u#\4v}p~WhJ@x.2C~ $L)չ5por}H 9t zbB&NJ'.߰MO{X!e"t{B}q:pp( `v:J`ۂ3'RIRB` Ih{ISHkqkp ܴ/Nx k0kQ fɚ^_LQd5qҳ TG$غ#_Z)GL"dS¬lR#ž "K̈́gɼXG=$U*JFss_5s""'}#L&]qN 0TH;R:efĺZƎou}ZR50 'ZY@ F#YUwJʄNY; aD҉&|cφ&'3FKNGuKR$4檺cm@NjbXf`Z X쇼W% c'JmaV4hjkT`jLA&!Ǡ2 (--UQ8(,.i۱qc>|GAEUJpeix;!y ‘j;֡# L;.(1\@*D։[ƅ*z;Pa@a..HE &d\6B-a3IbOhܓD4T9T$01QN +D̗m6;^+rROUq"//9ҕ-.n8yx5 .(!T'ĞmVM@mҗE3_) ^7rqKff@p$%bׁ\0v"!Fn_)x@|^ԌU`" pJIw7  aa O;@Q# ÉquREDj{# -9?2¡JpPN( @UYAA #$LNӧgr)$4˴_ (%`"ĈKb/y^¶bfD- oBS("M [M+ VnN_)/o81MA͠w["Kܖhn x8 P1 Ƕm;P(PBѷ;n^B0-DW>GErm}۴m <_JdeyA@. G 7v AH ἉLWL"k4T{;@,(QA 6.+ \P-؋ ®W@)jS?x:(6|VwBVf)&^((.д*W}{vA4erбS?j4"Ԡl&m 4*@-h6`5wt%cgǜyy7 !(KNIfx;%C@>BBS!%0R`OM!R804#&{k;;@,\ (`! XC3b T(:+@7'IhB0=@lIJ'4 ''MOЭNn[%ABA ~&]je[e6iD9#*21@ΐ¯OfV%0L#20^a'8E[jFΟ!Z:*fzk m/`$ O0T@;4 9#H e€G)eSdl\/H"`*.Fdv: _ mZ|(U$I e ۭlfm8ܚ-HO.NzgƦ)qO] (`ȏ(>HcQ{]Դ.?[kǶh4ƺIE&ܑL?~I e b)yɸT A<$_$cQIݘ0&z1ڜ2reM@llxyHB^(apCJ\~IH/ Qeejp84Jժjx#DVٷ6\3kJYzBkyUijrAx ! X}|^@[!8~QTQ0}{дsN(55Qۨ ؝IX 9dy0 W?qq+A yB؍qAڠQ O3j,q$3zUx%J8 &?t/PzYX/!we竁RPPPPP8 Y'zC-)ȟ)':'';$uaE@Y}B)E,D"yX@**I js~JlO4ywюR Ar@e/'-oB،L1SpXwkڻ!_?QVv$V 0[E-%PYR/9+ P^梀RFEd*PtW|1x쟨9PH%6E7щb!@/w<Xpi`ju `u^Q0LE71Y! ' pу5jT__ YY>nݢXAAAAApF#n6j> H1\tZC.U# PN08@-[%#`iyOp/Pt"@||8@mH0 *!:B(t,ö(  s!Y*tË́xl;<# m in\xQ9w^{2Qm<^B;q@+(!5c"ЪE˪bGI[T M }E4(׎(>~ -J@ԌmXkӿKW_̱1(**‡/^xe)?,k#wi>c/8ۢT?k8rbfD/baR o K@( Ȅ@K+'QHuqDK/8BUWo1ÏFfidexP>slO?uAPZw5 ؼv\4]^wj,i܌%wa=k^~xk[@RVzS4)R)]i&"EHN#*ct:h[u%ьD\1ߑ(Jsjj A 0F|2>^|23Aزs0*%AGq|)I)C*=Ӏ `n`SnDw;Wd2y`<_Օd%!҃QA!`g.BǺ@!K ` `%X yxƕ@ufK.@A}d~ 7\pڮӪ ݏWM0qstjϻÊODvqqϖ.]*~pXӢM(:'o\uY߀v nx b͓q\,_vX(*N0$FmAO*;!(eUsqrHI):U&6mN|zD &l@ ;Yaq()mӇH3[l2B `@n~y8W$hsI{L1&w'^'~8A(tld[FǏo Z?wocwH+G7c4wg?tƓP E'ǟ+3oً×;\Ǎxv\(2k\_}EnFqAlxPx#?,<kvB ܩg^z5~ t_cm-[YŸos ܿ*;`ʞ`+뫡l{HCfyl%V*DIMJ8VБxElԙ(@H0ccs^%9 BO</2{X=5@Ө2"% o8R09 #!@-omF$V <D|=6DQA8@R¶ssXpvTB\8JZ )yDc/ Mr&|^%۔ӣthܐ~nwTb`i%xc.^`?Eߩ{qFUGڥ31 tK*RU=,.qp+PB[aId8>V$uH*SR,Zo#sJ(D y/TQfE޻nr|@i4m{c oңaڤ]BPg8cHxtJ) G[Ʉ+rnض܏ڦ.;jYh Ph5b@^Tz(΋ovhBg[+Iv 5S.vHGzT7ipl{Ę ,6`<xP=5K>d^zbiG!kF| m Jaπh}}ܸq`p3444CH @_ɨ<ԍ+I&-<tגl bX=MK3/KOˍD=NJ×@v?zxCoYXj΄(7'мpi^ sG~@ٌ]+=xhǩ};3E֞q˱闍 V)ӝ/O*;JS/EZ#|훐⧽Fr 7m}>BK޿%i_Abe8o"Tʶʾ`'$&e7%]RHsX7Kax.c\0#(€ q7" Bk0ŤI0w\v24jqJ$$!|X~H_Ad+'B=R#1"8` '#D0s B (5uolPT#N+A˞O_cW ({j~YY:JQVvrWhb@q%.#wt'^v[6q%.#Ú@& /ܟ +q~;IC Mp@C#Qh¦f_J1?Xs}seD%`SU= tO,bamA m/\5 rL8hb߁8 4MàAL\s5ͳ" V^F+H5YXc!F+JRA@%`S`@*V IDAT1` -п aW\2Km7@jq•)Uh>T؇j^>i5ଢ଼w7K1Tۨƨжm• 7cfA65]`ENoč|LRGMX^ rLDQQO]3\}T`϶ / 0hY[Scשlʶʾܘ--nYœpD!wl^Gb0ׅX2D-)",!(L&Ġز.4D@pΝ1uT\y0‰΄s ݒ^Q.KB7QSVf_3"vZr|4scuAf7ȘxdcB$zY&NrIT0gE7|[;.GؾgT4 je[v#:V#F}?!,]T9BZܹuMaVPXYj0Kx`k--) /Xȶgaخ]{{{՘+*"(,?~j3rA ^@Ľ8Jc/`S6 L@<9$M&€/*$'7 1qDTWWa„ >}:n=n0|Bxx,q!Oaݎ C )5;v/0! p󒤀;' :{pS)!`  .­~ dxb p܈).B0}6h􎽻F.MegyPnӎ 0 /@ I-` N<1K$ھ};\|1Q}xpw77ވ|<pt:Hx7!,:"SEJ(eg* lgN݉`[ 4SDg+ ;oߞ"'ˋ\*@QZB BBaqA= (((H|-*F(! q› (@t+2Qz A-afJ"^t8z\:<74ֻnj30c ᇱjZhבO4 @BًACFGq IQg×[MӔQBn8y94+*Yۦn#*hΞ=-sT.D}PZ ?eX' B%^^Դ uuE7ۊ.9f< [R/YQ IajS_> dDX;Ç®ά/ƇŇmJsG"MЈDÅ%nøIFb)Z(@c=BomR z?ڞT^j>38`) ])W@ @㴟4#i Z* 9Ifș%!KQlc^G4@%H F #q,8.s4ﳴ@VEb9`[B:MEV PB@kгONG~qǩSZ?~z9$OeYY;-'8_'xrsV3wDz7EUPHB@@AVO5˽L/;{'0O~cKQ \6(bީHQ%RlsRj=~|X[LP D"@ I`4vsNy6/y1 —̃Zw]< Q[3^zoW0Uh8yxe 46tu5{+ )"[c5CZza@5LXGQh`*pDfDl@|5(`[0 iyCA8GA 4֣e*7PdWBTw1E~٭Oiۜ^UKc|Ppac~vq)?cD~tl;pyl;{:vijŽZwBFl\UVymBuB!Pwbpr펢sXmc% 0$K3ߞ8 12֣L%;%3;#HUDB$q| cAsQoX2oDbo @.(!@!y\ۼC:o܁SGD7._N.=zGj)$ -B}m wmC}m p u~]cff&rr<(;RKڻy.#R`0]ېnE@KEAA`H飷::ڿ;W9 Lh4g}O5%C % y\*N  `K,ъP!g#"dVe[ vɛ( +Yh 1nKT%H|ZуB V]!#}#Yu֢LxSϞ=QPC`;4zn)x$$!gPI깒|9Dq,ЙG"`& %t:' fߐ0K ́My2 рC:/.;@ 2Pkz?SN\@R DZI5Q! @uTƺEAG#=3 . P]61$cpvcHV_Q#i 3 I'_BN.术ĐLH~I'WE& uvsI 뀳.5ſu̺? ǹG+:dm}q!-f=i"X (P5j_AAAAAtj/ 7c# h4'W=Q܇`$[1%$vV^z"aK 9H(`ޒ@nw承-/{f̅6DdD%ؙ$y'Hh5rQ PP wZY۵d^]'w>xAiʰpչR4. %j/ѫ@t*]~o>GHIg?[|Wq1|w@lнq'oGpTwjQ@AV:_ \N LgI4`ex rZP\]IA!wW 6!Ԙǜ :ѥ^xPVv2CT7V+#f|y΋Pk\YRnVBbLؼqJK^TCٷ[6 &(.^gĞ9䇂1XDR`a1*Y&$"Wn~,JA$vPU z '{@A*) )5ѯS6yuAB:e=@(ip̫lJKW*C1 n] 4^] z-{rT V{@BoЭ$8t@4- (E'|{l ; bp BB-{D uدL"+ )ሼ%!6rc$p0l41gl<}}un7p3 ۴5w}큻c=ΟVshڶj(((a)}MH,-tQ*%VP l=1'NL,h)^fE䩹<#w8&;D!QsKa}1 _(-2G5CpcOBazxۧb)ǣ_Q(-UmN뉇{_pBfKֺh&=KnƾY_OƕwWU6n:+~z ^0w䘵{p08kzw/ل{s7i]| =3kzƀK6""E)&XR @(^;.  .5Pv>T^>d~_67K/UvBۚE&*LŵGI jn)vmb耩8\gwFXǎ BrB{@7 ݺ!/oQEzvvyU鳽d{l)P$ BP&rEQS (DRoo-[fgy~L)ϙ !3{ʼ<ЖRznKjOI80YOrP'ƌB>uoy0ִKayUte5S}xi/-~K71q׻13tX,SD@#2_E-~9tϼ%l8 IE9 MXÿ --R4|2٘65/U]O}в^&MGm'Ưt6&zdl+}} wk|?CKJHTQVY_/:1ҹsI8 OFM !#R&.k^. e@.i;=X6z՞l?`_q cWO}s y,b/N&ёzcdYIrbBDCMUbwj I.AEelX ( :p`/d;vbQ`QlGu݊Y'/BVF(6޵pqFᘔ|e"lX^h JRQ'C76J5oD  Ah>ox׋yZ5ヘWY EgV~K*'`އZz!P?ц۶t!PP\)Ms G855aB ⷏_@aՕυc~r6Q6v4!|BIk.[TP~kc4vኹ7+ph̀ ֦/ 3D#(SxWed}ل-V!|!x O,/ P=e%Gr@@VD]!Iq-%?J.āOD b5 p\()+Ǣhk <^/ܮhg㘹-$7KS"CsrLa ϽrG-¾}0yr9H!l8r G;NZ7.s֍ez|>46MEq5Dkhiy+/jIb]$ {& 3nO0?ۉt Ep{uuXTAUy A7+` JU?..|v:!c7/ݻ]76`_Tɘ\7y@g{?2xG1X,V, aE,_XD6Xdd~JdGb.܁y5yr 5?Џ*P-api((B2VGKyp--]o R t@Ic=ZgZ~f*ŕN?$\ȱjj. JFerz̻kkIIi?$kI_{Jܿ׽xqF[^NƏN] B1-hwj/}6? ?y~LuX&wGq{r7e3u3brG(ش]TV76mǡ0caX`q%fX,j aI';bX 3SoVÍ Re5Pu5p5Z(i[;5i^ 6ߊ NjyrCD j/Xby2g{ݯܛc{gy/n^ܲ~nOk= `h̟goϿ߆w7@=ZZH_YQ 6maFyfXQDDt 3絠m4_F".`bX,xͽ(؆]Q^^K>7&[S)K@d?~l6C_1{e~u R,=# 9_eEX,+@,D䰝A|m$@RS,b o4rv"(VܨD_ _0H$ H"Xz)'ؓD %b%@|rb  yr dEFd( >K^\)︭YY,g-)S)('txhhE#G ?`Ύ!@A:Cem=Y,JQdӰB(lk_)Fu" [6|P1}lD+&s[Xq?2`Lv@9,;S^.GX6T@y³'T@nYwG"0kG Ӛ 5ؼSh{D8g̜gmY@ha\Vdn   "UL(4"c:gXA ( @F >)Eô&F >n7mۊsCp ~wln`X\bc"3(ڿBg~? 0B!T##eE7h-P@0 `XR-ij/BHtlކq X,w! ۃMFB":3厺H aXg;D 靄g"`(9}vHN^?B bwB67O=c8 bXcAq~{n[D71{  帗llY#NQ 1yuщol:&Eq\k'e@f?"GނPU;U5 $p|H0@-@I ݙcǣ T@HK8:ϗp5cZ,ө3U8`5Dv$o3 BWdgNhX܀w஫[{NV7|Q5Vf.O3QRw^O ^>,BN1{-Q@`dưwE!LEUnL "4@HT?%0^nߒYlSz3G@`IH` XQ X#B}})ʼS:vXIpݨ[\<;bzs90 - d4^H^BšqÃp@#8#[:\]"j~ U}x'E\y_!6w H>3>уjU(](E"UB\>?)ʁH@PJ"Z韆mBa¸__0@--@5 ,ED'M0` D iYf}Q?`Aؽ 6$l XUmh_<5A8(9oq̌8㲏b2xq('Mp8=o @\̏Ylr .hG}(ZWwbn, t^ |!?~re/ P0__7.F\۬nl$ _F("C)+O@)]>n$ bJ%r  cUozL8@&^@{ #xDL~ύb*>Sp+O{/%zT`M2bQBxQV,a P"֜ A"TރPafG f=ɜڃc+ޯn `  n [>cgD|3ߗ˰jUᾃ+xPuޱpYcX*"G!E#(AD Pt=A4A7 Ш ANrkD dD dB(RG&ȉYms ''ZPpbV.'Gd_EgLLmAx`#AfpWע p!_$ bt EH"T<2JwxJzu\ӎ? !8ˇ`((S`kAl,vycXN? AX=QJ+ H]>AAZЀGkXH (%6W@fl"Y|~8f+,'2dAſ#~T@'!@zhLebpv}jA 4,78Q bgX pA1`oP؍_Ԗm97ʵ S}d輬)^q0 LAa<jFEQFH (2R@ylD~NN(3` ~hV-(:c: dD !ǀ@8p: 5DC3"JįG_c7o(O~\eAC(X}Jf"|1!iV[gcpa20kZ q ' YmڧG$%`xu7I!(@h6V>j8pnPD '%!0rͤX 6Npy SK'UBO>؂ `oFGCI FlHz׼o&cw/o%-?w<֍;bbA]17`wQY݀8Ì9a흉zR(0@[ @6*0QB*9y4Dd rhXbԹyo Ɇ%֣6$ID @wֲi ^W2 ž BҞ qҢ3Q&En[őRB cJGwVޅ{ۆb%GLքkZ!+2*jppO;v܊y!,l! hDd@+XqrC*&  `82˟0}XٻczKY2$0S`XکND 0`7x~pGCi3]c@`D?~%.a.!Dc=X,+OENHD\}*4  @dBMu:Ȁ)>"}V j(-fD֯ZOhi|#2:0@80BRCy)cM\6RAMs pq|P3P[qaT"B/f[:p6DU1?h V$ y`*k!'~XѕȦaAoW'Pd־Hw/t u TP. L"0GzFd yd -lM@ >GPl낰XUǂXN,q{?B/Vڊ6g;|?w4z3o4Lmža#q,5m^aLCΚq2޷c0RDNc bL(c-ώX;|gehj>O^ J GWMO]O]^Kr1E\.p5ZRd92߷WdH8+`)\ga9S_Ȯx=cI]JL a"rӖ9v"!Sm}H ktdW COoo:?5Z@BvK}Vc|V 5 KN: 󦟎(Oʛ/S7 ܨwDۣ*]Nū XcC zuܲX"!X:70"ӽglU7d͇R`.`dBӧY=N 00] ŎYB d X* m-ZtL7>:=Jjz}6 Q+G(\ n.z<1"W ^!00ȡGYFw!*:KdXֱ-'Ӓ!/.)1smG5/ 81pV@Z4Q20dbۈJ@&%<6Xc~9 -  莴k[@J*6D𘍣Նfь3 x\x\%Vv!@)Q#zڃ-(V$;o SpWX,襧N9-vHb&( – rR 諚}Xj< Ԣp&QffAIA}'nWIhd8`6jD*3P!RH׌u4 XUSBvB$I@! Eyi`t;RE 4*!=ipr)8/μrTsXQ~&oL#q`P0;o4",˹I3@%=-Ѐq~6@ cTt=h6h\@ Y9` !%d p )4:T-+79x߲ b ܃"x>v|0,X_Y%3>l/bl Blݶ?OqӗiS :#2D|dfT[E@:' @(- Z P+:@%+ÌhC /,I  LG 8L؏M`KpDžX0Ϡg|20A7KQ.LJp?bXN,Ѩm۶z=|S 1<ΨRKҋQBo T@@dGG-2)V5B`(hh3ZV.r*qWWnEX7q-l@_'O,kj-M< hܗ60y2,57XNSN!P`Ǜ=e1x4wڽ{-bBM(Aهd*>n-NFѺnb9b@woū.ğ;\B|a۶)ˍ1\.#(\?54^?8bZ}A*!w3~Jom IDAT)U#S#)uzq|-L9&®Q6IHw|f*1ڲݢ6 ;N3h2@'F se tsٖ:Li؈ 87Cs^ԋrt_sq٩ATW7"PF4#Pǡ‹ϣN6u%? !~t4Mx|̏[5f!CO_sCo'"Q L3.4\\YDV }H6pR^0К@_+lsiN VW1gbSIೇVDFI zp@JNLna Ld,d9srӖDX_!@}Ώ0%]ѹed?n;uҫuߺO|*<**lB?~z;,ƎG%nbzãyz,k<(9[ %+N? v*EID `lz̄ċsՍ@?RcU js>T$ &`dL7OS@X:`@5ZL6X# -%7'N6.US P<‹ߋf`g.ky?GSC܏[gˀg{"` ]؁c<'?b_}ؗrw,y |\-N,+Ǣ!byBD=Q4DD tVl !4A@ O0Ow"=X%_hXݜQl: sY\ :@ؼF oJm٪V/!, ȼklb Դ(_jNJwM7}﷽ k8==W>_iEKhi&KZO'JڰO;ͯ|㚬*]24Œ૿i͍ _> 1}|:_4=Bx!j:7nL, /=ꢙ. g4Oïv osmU'`S='uli=exh}lG)ࣗ0̔殉8mL76ijwkwN f MXbW R>?~} ̹xʦEݨ_q9tL-O>bFs.b5J!Iz܁YVf (  .2-IWri<ӭaoa3|̼E|@0:jG>[o[GeuqֹK0yZ[j_wc_cXJ}8ƒsl[olW&!<:zw;>#+adž'7=gn{ڒ\“ @+xlJNلm}h~W܏ꡍho[ y_[VMƽEq|)?_X, II}h樀6HP+˦L Ijq@ʚӜS' 0aVE}?uj<М 03- F>e`I AIB[reN Nxw?t@\#F ,` ^[[`a@=ud..J0a~eXQ}8[pý]] r<ߜjn.9UmL-}O{q~g^ԅ!u?S<Ʌ<;{jnv{:zOą-~SJ"h>68/g_ßh߮CX1#=PrC+IP"PdY`#)fY@1@'W:Um *lAa i8)%ȨJfdTCCCPHB Ȥ $@ ;_2 FZQ>K⌑n7z4$ Oe”J"~tyh,~y*P#axph(vN,ݸjj=a6$oFB_&{Ѻr0B!4}?p}8EB PP%uW2`XcYҷ&"(~n$O@& IF02@p & " ܮ6Ј V:-3J  s_etmKzal=>?3>>c lA(灀vɛ/{t |j6mل p?~Z84_ FV/Gg{vnr1vlߍk.=6% $f;|h) 4R !/+2 >0 ߍ>9AC76J$/ +,-K=2\sS1Ns;>5yx{ W}+n}Z=lUo}k.Gpσm5&Y@6HQbFFP`GrҍV׎P7F @P@ LtJp w@ ck܌e` 7O- ;20;/@vvҍt@B{󗷎o> ,ƧO-_7Ӟ=~i{}^[W;Qo$9mkESKp020j$ 3D#(SxWeIf9j6aˣոk{;{{Տ"}PC3"JįGء]Exf. #cB?U?نoN՟OeVdƒ!^\<$ D(> Z?0J`B3rT@P"Z`=H$4>Iitpu`ԁ@)Y '5 P5Z@c6] k̃+ ‚=tU~`WL;*g=n~+hzH۴Bo1xs5;G`ø̊[dуFo0:#Xwt4~RՏ N@L|柤'Wq@, ˤ>Z\ō`]?.m.'$M]x8(}2}ߍUS>|8w\{4Mֿgp}4  "hB8)hg񟆻8O[p (8, vT@ (l/c=dO3&i#+ DN s FXY_+WUa`@8J=%+(@j@P=,U/GE+R? qAx]x%>X h͖ԃ[pÉ"b!zܘ77kl;?~;/8օ9:eQ ፵ϩB\@~@Xe> TVV E:{7`Sa'6JG^ޅێb L.n0 a%xO R!f_}] L.GL'{<rzo&tǥO8VQQЛ 8lN @=ϨA]߄ b!I ƍ_SQXP%LԐHQ %_$9.$:@DQ  (wnr44,X3!.A'ⓟ\_|>6]t@~xXKlObXф+gjLXR"`p\9FSkt;3 2`j]Wҧ.cB2 KM.0j-$*Ay53Rp}mj p ^,<,GX6XZ2ۨCѣ~LC7b{ @0ib~t-:P80 G`n3-aie4e 07Z aX4 LuOg-{@ i~FZ;q,,Z`%Wk ``]{o`nB ng@@$ `CR#WWtǦ(PT1}Q _]tz4s.xSq'2رu /tNthm}ʲ?t.y*0>τ3!liiFa 5bB \n.b"9X(8x Jʡve`orrD&"%T\$ 'ԗK16޶ ¡PPHB0O&ʎ0h krD >gmo#uA>EO63"02j54Cu'UJZA$O=C:̟4  2PYڦHe5wHk/_k SG-5>3i;Y`v:eG@I][O1Z] JdђS/_ypXQє{k!PZVG84`{dq:"(E1{V뎇 'F%±iG+xrqb־fwa HB>ʺbh2L׃,Ծ4i;Z4櫓V@1f.n)Ƀ%aAZ)-p>c4}x=c mW˰dp5|.3 BH~[U3sIwI&6GEd tHZ95)Cc-<eJJJ7ϼ]I.KJqBrW$-){PddN)7S eY4.C,@ATO10ĝꙬ%Ia0"fUcZ߳Qcx 6#ri( ,l3'@.V]VVɼqN?o4vF/R߁URеRwTĎq޽xdD>?`"0kG Ӛ W`XajMQ]݇;pp.L9t@HL !FhxaaLXm!D=*9ƔJ= yO=v1c!6Ҡ@OcXyӗÏ&~06Fzr9,vxӊ/pp޽x:D` Ӛv{AX,֨*Ir%L7% p |^@FQ%324 dΦh @.v I禑s)QiI()lѵ qxO<;/Mm?ŗh\-`hVY,ϭ zlqxG6L8ffh_nGX\xƋ#BvwQެF42Bɯ䮧P fسד^m!âq Q80TJtAphXtZMב?9 un¥h X ((D )-?0 Y2,Ztֺ TS3ޑ m$Mg3OK Ҡvh 6ȁ=g2H&nj(T^7Юv\rm-\wwsZ| Q lY :*Ĕ=XcP&:bX,VΔ`08Ї{G@Qi{S!(3]_.>/Z:A7<6xO tHZf$Q՝hY 耤z3vt`tZ'pf!q9"eV@s0l5nǼ%SJ/4l5n7E 2M9"*3ƣnO^ bX,+'>XmLi׾{O@ X]q `E!\.Wjd2i暴GSIs19>{& ¾U7sA"v3YF6}df}TGГ֎c&Rpe=rk#g]*eT!8S" 0mzG0$`X,g%{qg{ k-g77;3M HѶ Ӛ硶~ Mcm$(Dtdv=MeΠŹb2/WzQֽٺ_Ӌ0fң3t/2#/z{dv_Ϝv- To6Zۆ|% Y=F.ʅX4ۯ:L4 8*bpyt7CL9լhxm(҅}+k_W[;) r,rMBe٨|f?"" UQU5@ᎬY0S mӟb>cUـiό6LmG]mh%_-]@@3e063Pn @+*,FsHpΌruafFH55s;Whkx*9LGXB%%KHsiδj`BZ.X9Ν;onnً{mģl> Юb|5x]7\pU}rl.\.dMG 7skxQSbvࣞ =":)Ee$Q F S1~):2͎&;5iL-=QFЬ/ft6cӭΉ@ I(m TsP q L^6ʙL+}pXȼwg@6RWg'@YA/ʹ ?Ǵ y.ū IDAT _fKk19ĝ߇ŽPC'Cjr30> u`a bg oqf/^ES Y,˞_B̭Ǽ Ue$,N2vF?0i4=Bs;a@2HJjAzAD.`؞fIL #켖7W7f*i{({&IhCfe}ˑ41R@t@PhEVGv_1 ؏kލ_"](i: S v~5ad)tle'{O| \|rX,%E h[ix @Zq"RL !r<5IӮ=H9I5O?^!QzcP,3PI wC&͑q9MGH~f!tk.=)@(q}$+rF3Y5 wzůsםA@kM.ؼYcJ pC;=X6Vړ%M5()*Eդ98uy6WpP4Chfv hկ9bu)fRDF{M{/%XWS|AT2j  "# &Wj$pfCr2:ۃt3ĥk/B@{q(ؙ3OL@fj|a`Z5Nʠ,zHr, sM8ƴvW߽۬1y[qY읈֖WZ\ 3) $>H9pB s[B-=uQV1.J1/@J̷R]XgB)`!: O4kLF'c6I -=@!H- w.L\(#b1P ͌Ѝ0 SqdlULFV$^4N X,VL6! D*@Bc]n?ч_>@/x2h܎A pz6bJ<}CCP(Ŧ2LTN`Xv}q?)޳BߡM-iNJ@̱qi5jjSY㴩HezR=-U`TLNnPTgƴCNA@>|B4 #Z/GbѓctB23@D럈L5}FFA+T[[EP[O{,"ӗѓe ?zRHL:.>.P:oz> ^ ޒ{ƙ_Y,m^ExzRҎN lজL-:`z ȑH۔ARfp2-6 4!-ha\@ԁ@:|AZ= B0)H#QC :LѩRМہ{Q916f^NC+B 4w凼KbYGEWK&2r 꿭<`r7n'/wSqWbѹbu X%# 6} M%mǡ0caX`g) -U:<)ԒfZ# s  JVK lN'^8/$IeF8BQFSBf<-R?xaSZJGS O9S)G&"ivFCR,6hy:0&RKZc N!Ud~杶[qV5n6lWB2D:aX,J"5agzYc߮عugg;2TܾuIA3eux'>uEɑZ =nI'o2^2ɜt9.2ȌxX^jF!0I5͙0j-FzwO}D9[,^1@#WmɧATj h< a)+ <Ņp#k4J_kluh,kL!" (@Ӽlx7n-I>kK'qO Eq S0< $ǩ'(͜Jg_U4P.@ 3>@-S:^4HYDH #QpXQ6daY۩ zh9L w׮{$dܐVO|Y|ccTlY,ָFi  fkA5hn)Hq)u'IdY;`xz~ԁ BW;:y5@@@P+^`: Ҷ*9֢RӀ@ɍe ˀ@ӫYbu.,U`U Pz:QF bX,k{?%9>ww[[< Ћ@)EՀ@DH2qP@ M`d)QV`%z6zڹIn10)Ǫy׾tOVv%ȸ47u\ ǥ @Qy=|ۋ50]Q??tϜcj@V俹Wbr&!b$' 6CM1O!Sp%OԂZ&tAbJHMBVf4,y>聀J^*m4ak LV@P@kvNSy}i{B ף T;wã~lƌS8+*-.++])p ]ǚs% "t؇zSXѕȦaAoW'PdSn1f=Ed!KzG+@ ױuJeBAҬZ, c 檙[ɔ j-u(@1J!p d)'17"Buv4@ ,_k4Q;Ip{P,r;`YֶQ%1X.zZ[ang*>̹-BJ  b/?{ok]UAETHM3&40xy;wy=cz' KHfuQ/ >1=bVWջx΀B$uŽwe [z6{D)^+^"#@ z! X!# N!(P \cu NPa"c Q!W rJ)c|&O}[300MhQ]I FhHHR=E9~;m(N64LX6 _W@30Y=h8@MS( _t=}_\w:[q:1OxV 73;!r_.Z@9G++ԼN2BT  m`< )C/A zP. )^TD)mp\Ezd19E0M)/6E97 fG@ tTWaYRiRV"V^Y.^v!(T9kh44 57\&gBjˍO,*t6҂A~$@^W? L@P5b< d4@dG20(;j$h_@#8\91:<9r oqAGh_:GErpӚ* )uZ@Iy%d@u眭~ƒ9ł@vC*; BZ9j ;1؏Q!h|yQ8!P+G q 1 NN_ p@hG zNL"]A߇Fu N1s JAN: QG1\2274;2+fmѝ^;E/Ĵn-!>.L+nwbq/t-F $(ti(M@kP"9 `QN@ +D4{SY?/©9B^Y!z99❐Fr48ozWA}37@GF} ^[6m![ʶQqǢAxS43;tX1DT` E"VGpUW2vp 9܂8p5(rХ|}ӮL~wC)%}h$u~}x' z.) RK$`pqO~Gz]'~x U~w4f ~gMv x.ѽoErW(>tiu`oÎ  _kucﱎ_ q BQaxNZZ0`)@Dpy;bFx'KԹF%"زt;$0i*@K'q߭1H|)>5F=9BF DB,a}lQ-[3v e= ?4p @4 V#!o-fX%Id~+l}í];7KFP[O[P(Z2veК\2`}4>Q2\@y2QLb4Hi!h #>ybC="-Pψ0&=r <c RMtو1?k[P( PM@##VV bX#PP(&=Bԍք06S1BV/Jױ3[zW.Qooh4# s&RAq`F9b_ם8gG*c{#ѣ) EV\EFw}Te4;7QP(Z m @su)%N #n?X  ?2J#wh#@68q wQQE <#[QF0#?1ȵ$@~IDxKP(ӓ\/x/fDc)CfXgAv\g3-bsJyUpBP("scDL+cǸD}gt{քnjWM ytRx}.O2ߚه/L6أ%! ʔOjes8ŭr1W{ޚMs#ϱyaʢbCF;3F?d$S9aH/*y}$?aWe.^6a7FjMu3j՚5[R )&1C`? B4Yq%D7N]^r",RŇ.[ȁVhLX_NG [z#ڹ1c(`e㔭t}U9xbSq F^K: jw Ia,+_ҏ7QNk垅|VִE勻O{ X;ogh;_]@\ξ/-Vl;ae3pnihq@ض=N` ?;` l6F$h nA>Lfo`l԰8yXIa'0wY'pُqvw9mto/cf+c&ro-SNżPWOb]# Wׯ)^5{~׌3v\jfm8 94I%-ZaW :'Z:v 4s@FUh2@Ro _}<ѹE(Q$P(,݁pw :x*f8 n<7r9 Ϭ4naG֏yIk$ +#)7}zs鬩tnܷA[{>L`x]Ӄ|:ܠ8,.9Æfx w_Ł@1'2fb.c&dqx_D65wL9~orwn}3vFW5w) O/adLgWkļ1{؎w%Hc;5ڨ.GN$k/̌g- CX<+i__djnbIY7,{2:}6jqe`߰+.}nH`ĝs7K?gнsD@gی9=/*q97y`<^.8AHݫ :dYmFxg=BFG>7J4R80yd 5һVwAlE_FBV_P`T8+I!͝cY tW8kF6 '3uIEÝ}ay(~Bat[fW9Xdy2Gr<[pU@xlXj>SCJ6J xg_oowS4H 4[ɇV`XZ)|R7}31z'yq\0n r@~ֶ,὿{rAf\1f-w; &SSxE{Z{)C5țpGuE3_僷gץ}ڨ#H?M~ۑǔj\ʣh2! G6B/Kx-g'Eb߃G+/d%N h厀p 9V`(QڢR3ڤ 1 ſr(|jѝlݳC:{ /as0%߉8r23Rz'Iu΅?b''z:=ʸjxDv^[C`"ic*/q')ͱ /p#oF1E;|k旓⽱**  ^ +@\ {{M1uˠ:k P%3Ix0w 埵FH]{9tO<)Y&3/:63y֫SkURKZټYxswҨAM9Qؚ`Is ^xUʶYP5ۻ\RDK)u:j,$J@Ԋ NNM,98<ߧ18bw$QФҁhW-dN.r tԋ{9O)QbaY IDATLm8:y¬c8oI81RcYl 2  Xsʱ<7sZ)U9!3tg3}Uَ85q|^~4ke\U3cŜYOsX=CܼTEsp:XNQNm&Eg.SiH77erv0GM`W%C|GGu$^skL f)^]Bk! =+{{Is&b,O+30.p:+3iqU"Ft1i錽\Mʉ10rqą%W߈Y hscAˠ r(ap9SuErv(=bjjuXԇTsGFF` >>$0[i)9>G(Q5mM?0 \wZ/^vheVLKkŀW+ ,,f[⣳YD;Gޡs_E`rk<*9Ql28-(%%<= };n.)mb}ǣ_L11S3gv{(C~dHgsXrW]uN ̮#\tO Hv}r=_SCtRWH ~}E W3FiZsVq('@;j8Rf k&-? vU5ߴX5RN;;7kHYHJnđ[T* *cX;U!u7J'T(+ڊGO2ʑKcxNﰇp?ec0k+@; j;Hh!lHr EE,&N(xn U$L"9$ОӅT*{o똛f~51%8V(ڪ>{le^>r\Y^.!HhtD(R^P9~[;еKON=%sY)HB2R1.[b6N-sԄT%() CRJBg7 EJ$ z;Z[@ǭ͛'~uH<f48Uu* P(Gm)]6S aՇ{]0I݉ 4 j/Dg½eW4t Dˏn#3***@Pe&݆V/4lv%oh%餦qBӐ6^:W ں6ePq҄ E3 uh-唱DaLjIQP((W̯?~G]-Q(-MINM 5W  ݻ4 j߾U&G@QQiЄ;PPnXG㏓zaJafE2BP(n㷟s>LFkdOյBrcKe=gfŘLOS*&CX6. MSP( BwJv`3iIUB#Y,3;\zM 0hSP0]F Bh6ap'QuP9d]5 xL 0$fVBP4!@RE(h47^"|g7҂̀!N}4 E4KH&Q30fqBP( m $6a1%3S(Gj\"FJk (X8ƠJΘOSP(@pMv+@bQP(jʫ%=iSP@=4iYXړ߉eg/SP(pCHIP(mU:Tj@  (ژy5U$%'dH$ј#U(sw&^}i1uNU EC-GOt P(;Tt+8[v9|\FB'tf&N`%M(Z0ȋvqP0Ԅ~'L?>:ѩ,);ĺEnԩ;CO7~bBъ=B D RDXUmƔ BX;k-mM|$.54 X]Jv܉u׬LMe䟰f~]~L^w$怜5z,.}?keqy\4)5XPrx0_/-Swղv-o3e7e;Eڭ"u;8 {=;wdU摒PdEDlR͈!Ϋ[xw(VIvA 7ܿɽt{IE %_v(9m1ށiwyI/h'Neh}[{>I\b=:+h-طct}#xb#,`ww ^|m+W}1[/#5]p\l?^hU/m[{oiԝajJR%MAINMPvSS4ZC۵1aB%~7wΞbRzP8OEX{^'>軃+KfM 0<_o0OOwal-p}7-#ΌFqͿ_F֮H sC[qM.3Ev˫y`k gg6Mño +Kq:bl?E {f|ž#xQjV a]Aa0E pls i?Mk%MGDKƖ{֮a@Pi ftRR)m׶}P!,+QՇ"J_TjMR^8#T_nO14Scwջ^1?#3]?٣JSݞ_w FuLVWj:yYJ.+n>x^Y`deti2Y]J5q{OjԔTh~2քϬ5!]:B#)=9zg.uJO!-R#jYq` FI܅mm -WvSSmyP( }Rt9l`ka{3ӣZ)UMlS+eQbBPmS}{Hyw C \OϾf?#/V0zM[+xlO%Jkjj .-ck1O9m+ }xJBx{Ul97d(!B vXrصFċz헨A$!ߥċi>!-n>5m;xe;k7"*t]efP(t;vWm62L\f r[nui7L= 73+'OU|݃>Ic𴦫cF>|^v8V,R05p3@peTڪj*&nB >Bs#}m~4>B"<>O= O4I pf7犥.c[mg7v9\+msyF/ Q(RNbW)ȧϦ 00{okO:0$UEm[2oaIVIM';֛V &g̴摎gW-njb9RbbqJ a 3uehS{ϛYq ߏ ~T~ EkEH&&[d2w?3 8 ,|N"fEC3A]֎: /:sU >4݂(|dvk< xvS>js(ŃD̤P(ڔ7+I՝9d'F&Ƭ緢ݛGg vϽC+"w+-,37 8L$֚`x 뿇% 3I:N{:nkN;2@n.ǀ^en9&8䌘S/_L3@CyfK6Jx;<:܎Hep">SEhTHs\P>Nz _H/Yu)M# Ʋx[F_NJYF-V[jsn Ex**8qP*Chknܷ7^Sf=l+WW{oJ.gr76ؘڔ~n֬h'N2YUJwN"NgSOg׳=)@_{z!PYTgS-\a%|:"~T9 DC :Y!C @R7)=#^E"ԀhlTv B``7y7;qYamc ͵6PDC&JW~7*CP? cǠ*Dh-w4W@~ZO$~B!xqQM,c~E=!sp=c;/r<[{a A+aLȞ,͋ i?hf]sà\s)ɩf3uX&f/Q+GINOSmb}:͕r!]m B-vۉ-\0/m<'R33mjXm'wOr'3Ġ`7?@\4/=l_[~` 蘛ߪ;coywl\PxD>p}Sc' Z20\| 6s5珼xckT1gk"0åu%<2ݲʷœWpE9N 4RO~^ɾm?Op xLyjDu{ү>(xh:N#!XlXL29cuXR`|v7ܽǕ#N0MxUF.=rJߪ/lz@֤9wn>l/]7a7a`&+R@gXeku m1\,-Ncpn6IBN)X2H2 '!9Ø|3̟<ϕA-H~uU_ Q ڍ)W|w=iv 8a*JY '1lz)wpc P'9KQx'?'6!eح@q 6ZAh&ćӎc8t󜳩7cy<Ӹ"ng^;=]uRBⰁٯghc`&msӶjV0̧x5dF0'yCڭn>מycS"o?6X~$ΰ{y6NlA oc 8_OqI9K{A6۱ł%EÜaĥ`P( B*2)1;.v@$ c^x!±W.ExlY%O'x_*)&[@8df{pj:=2$]sx=ƉuWodhoRZ'cٯ,bB223YLr56 'g=/v.qs_Ҥ:@mPUU]a0 LI`W** "ӡ*BMӨo)nQ7VDq혧 TUc2}€\ׅ:Ыqv2 sq0LÊ[ޙߟ$g"g]09u~Rbϔ6:Dӊ!*B,I<_RMΕȝv<yyy:bFPc9d*ص*+y}f2^K춚3w.)GH uW7ߡX_4uN 3FJh/^Fr4T FT9Fӟ`G'8{FxOX#ץ0L@6Lf]|1e^_{&G,x,M)d%j %U#Jn="<G)?һc*]{-bo+{3mW[ul39v#HIBut^bF9sHHO@s Da`A͉t%z3d^&;k3HUU#צvMՒI7ڑRs;O==29n1bDJS>߅G'hR߅d hhs)%Uno2H'8uKP(dfcOuPDidwQOn_Ml dFuZZa)$>IҥҥKy KU\KIsLqRG:N`KL1yL@C1/Iڑ>b#+ ÈԝٗrIS:;*Wٕ<#dW2/I?9S8yih'M`S^pRq겡CZgy<'lUAx sݗ`7i i]Hs|ź";G$}13~Ld0[4횴|`rG{'a HvS]kf|ļ?@ݡ͵:c5SUf`+/BQOz32h:vW*U16&f 3s A8+Zw:[q:KiU;8Lp:@Jf>W mesK錽bϝ8:$=N`Fx7Ƚ4ͶWdoHr8^cP}59 p0Oq plJ &zB^{ 5;_ϲDiArq:;{-_V^Ha ..! PSmT p:19uyL9I-R>bk~HҐM]RGcc8􊝬m#CFkwuNԝ]oV/es.18_!wOah{#ֲA˥L&&=o:,3z.!ɍmtlmνnA|.q"Kk'NL#eջo}xhݲ-UڭI]G'd(ڰjg L~BA{w!#龦t? VZCB8NRIO .>e/.pibm 2tD(_B6&M3"x yuJVM3%uϜcY5X=-0!^;V0HψI2M|};24td'ƁÈԡQH|mݏGtDR?>n=T>.qNt]GS% u!=ҧb,ȡĈZRFm7 AuRw7 s} 2Ԯ {i枒#I G\47n:,}/WfsRs:1Ӳwh:YKָzMr80\=-+)VLa`ǎH XT(d_1{vi E hFVvOrjZâ~(RJs~3ƞ<kuE% *(h2{%%[bK l {.~]EF,fCϿ?!IvGOn^v j-N.?e5žWO۩6ע\||BGVUO8t8N<=/2y<ѾtġqRWꗴ p233~X1 !U9p#9u1e@, slώYL?V~ÎGBB9T(1Ͳo?sܨ/Ѽe)7: rM.$ b;ZD%tDŽ A9qF7YV#lmNeu5;TW Q)]JPoX9m΀(.e (uZ%P-az`"&X ++ e7_/e>fžjX]i-v5ok8%X[uB?i.#pE(Z+ ܽ-/+WP(u+Ţy hHeQe0=HJD6H(7;9bs$-PK`N`pmv͵j6"Jj[%:8U6FUu TVCR{8TŴ>۶Ϟ}LUBP( KY:yonFʈ.½"_ ]GD˱[ MJlvkSm^vkLCn~3N1lv'T[oQXxG.tҙĮ |4sU BPD^Q;4AQQY̶۫-XLM+))nIɚn̻n;]UvI_ֿW)mf1j2VeY`̯O#V8%qEy7#W.*"Neh=L =TL+,BAJς 0Mg5Z]WV["&HM$_>F @MNvS(uNE\Yw)aDvc1%$U+R[f^^X"3rZV&,>:>c,'r;׳l@[Yt5gv9Lt1;q/}K"PD<_ 4 E,Nl |nv浛B ,~ uk (dҭa]䟰z7PTf2x)SӪq =K%k96޺r2H^N~Jǿʷs'zfP(ޫI{*SUP( E뤲!tRUV^…r%_˴>J᡽XV^ ޛy~Lx݄}$gAA|:sDv ڋ`Zث/kgݝrw=USs9bto-}RҎ%UtO1*zװnMz57{ps9:?zisQpzonnM ^|m+W}13^[SZ,X,ɠF*;1؊:2;)/)aՔS!-MgЫoSݔh7h.M(޿'_Aħ75y̬eOV cn?׉v3ƿ,żt #s8j= iN3>i:mхӗ8_ݍY3%.*ΚMܴpd®:37cWО_Li00`;_VFxG7^ڃWKP4?=݅3d'ÓbǾ5,iǡ3D 2qAF"|x؅AfqÍgS,ȝv@ ݱ3a5ӷ4M5Ж{֮a@Pe7e;E3棸"sތb AmHㅕ6QcRV3 ZӺ!lqs%Oᬛyi| 6sK/W-LGҧ=N8]Iɸv|Q\)YuT]Õ=f%<{Zbgޘm\>fJvs.z7}.8R~ AMI5:afMfw 7rj+_;F*7 ХN}?%=p„〓ӹ ۾T7\MN浛BP(Zi!;fͬo}tv6:hx~)uYQbBPmwnXrnDYGc39a졮aok\L+^  ܛ^JɨT⩡謦D4d:g\Ny" P(487wWL-̵vsNzD/뽸~.vkWp*۵6P(օ9F=?cd߶0_t;iD6okf䐴(~y?f(33}xK4K/=p SkAm&>{bGmd͜8hg@>ڋG Veqx^JT4 S*ʢ6o B[ q-(꾉k7>?CGN )~?}W[Lc Qv?|>jXlG0 e;j92dlv6!#G3Y) u:rK5̜[.DPW<-@N&Ct-%Lna}Q8s_&zpZnySd7t=?|_Oxie.%:#)lgi{Nb|WBMWާcx@Kr1|r -t}N 2 8LLCvxH@Ѷ;DŽnMlfF/cN^QmNPDȓ8~@nAU18V+GgⳫ_vv0}rew^Ò7^9iPG*q>TL? ` sg瓱yCd{i Ly-7+`U9cf5>&49Ƭ緢ݛGg v;>2gfK RTB8TĂ bGPq {^NŊz*bA(*k$ I lvw[2;M!^݁~g9ك6!CwGxt 9f~-Lsl~SWDZ+H~ ;~ OjCLt4.wK~ _W4vu#LX|$+m*Q$ d#fq-#IKAN¦][H0[^ٸb"ryQt'rs"-ۀ`-lj$ [/-|ܥ~PcjVc(Ƣ_5eyߔTYVaaCDU9~(gÔ/1mYg?k IB3Q5^LyC??3zkrg"q5_\U+Ws$9E-\ H $! BqŢaYq  kyI[qWx%+: ORWGSOuo#;+Nx$N4kcZ f˟sg&~B3S5oӖ_pC[z236U p9;U 1(mCtBdDb=$RBLt xIWR7e&\>gO|':=-4o?syÆg~ӝXN1E}@'v qZ,9]\cxw<(GV,&Ke#xIƀupW s% 8>_(9sIDDD&9?7" |t *631kTԨ]hM% j~"y%*BH䄤\Lu<WAn|&p 2+7{ܴ;3 Q?r0;wp96og/O<!xqvT[sDDDDDǧ$P?u}5j $$=$#7KOSJᙥxTeه3Y^Ys?I& >LP7 {@rZf}M[ ghw[RK.g{ɧɓxg?9GDDt% v A6,I 5\CHPn#"\Qu".g/kB7|Dt'PV;b6zЮ/ s!ƞɄݦ]ڱ!@xc᝼?FwuFO὾~VbOiuky $YB|$l۴q]a0(DA ݒMVUh\ <]WRJ%|܏,t0K:à5?9_j33Kz ^u: & k)t>n7ߤQA<~b`NR%]8 v<9GDDQMf :?\u[EQ(DeI)8g䅰Zjgqף,I & %$zÔlzYG ;4{DGz}E;Wb)7ı`8tN3|{)ssDDD11 2*c/# *sbjf sNs/I,\ hTNB@/i&bHqħOt[M2qMR#H?Dz%`"7l0nmݸ 7)=þ>4%'l(vR>kxsZjQs B(|.Q$0MbZbQZR.] I{+:B(B@UQ%$6c15PeM4ɕ;#xu&5# Q?[H^c9'RWqܽI«R * IDAT{oQI+n# G")'5\%JZ=8^!{5#ŅwfcssDDDbH EMNh' <Gԕ$DFE!+/CBp!}lˇ"w$#.+ ܌x({&0uDG:>+Eh'snR%!Ds9+pL2.M#:1rf{Fz=[ٛ,yMg-ݰH3lsip;n C{^O bxnh(nCއN@zSxщU!I2݇[{s|uM -.%WmJpr+Gpe DBO/ITIJ IE06)iU0nxDNRKSEVnTAŎuLnWg^<ؓ(,,Ï=={:1Iŀz( HlIJ4'iIkEfM,_bFTvۆ~2HLu;@`^9q#"""}m*<1W^1 ;W^1?1 3:@nF F%IPz^71{c%R#>շX򌛤9OGϹz5nƜ11cV) JCq= Lt .7!!pXLt 8f%뷾'>Zզ f8?x%_KzHT$Iw8z1Lg rɅz2\yP-}i+4rRӲqӍzq̒װnwЉgUPG.\p_O3T Ϲ}W{\pR?_*|{puc[@9csh)t9W k[i9m/4ɉOhEWhQ_JJYdSMTB}T m>عMBIּGu/3$!i/%>ὂ* [L}PTmY@x ~p#n?9m玛'h v#nTcv;s#""j]Qςs+ 7W7i5ETECB(h_J(`l0BvW(  nnGmuU;.hmrH6I$G J,NFG"v.2g IzϼO)t@x%^ F#/!|q\4qsMx9](>5'>⠓w[ϹwQ#&TXS9(gkPY F@ЎhFֶM*!q$GOb3yEɕEgOs=t.{k>as'D2D}«R'%78{Uq{qS42wM܀=z;, ̻!F tc'Brbs9""cE5trf+7"6}ce[&lY,PGϏ(p=Jfm Ǔ%O s$ıwb U/p=@OP|_<:9GDDԆdT`|j ( AbIJbpnkچaU1gpa_bkؑuʆ\ ډ{duѾG}Or}rwP:C!v$\] jɹZu:ci=E{i17xrItvރU+9|42s1'cJH ޣ5 \@I*n1Pv>BgRzu7b=Xw aȰ󑙞O>xqiQGia[!%'%~b'|ݧކ9/2[$!3E||jas]{{9GDDf1w Dؑyf%2x3?«Gsp͈noC/3:j%, Bd SM(IDA4Ñ{5FEK ^[!@|’9k.J<Gf݇c.;!w1|X?Hj `buOǿAkPQk.MwrJu/!wt/P 7{gaon`a!5\@S ]ks뮾3}~݆5kspVwkaKv):L6SxzAV(4O-O8!g_adš}w( {%>TI$y*h+D,itTW`G:'GS׈1=k \u823L";\/!kݸiF 47UbEx$ivlGюYSuR_0~:+p _"6V<m-Ǎ(v49bpӴùm#""" 쎜 `DłagV }!`wdEx'^(9˺ɥ۫G~^ǯo4ͲniPmܕwOW.{uT<ߐfτD tѸK7TV͹o̸]q /fK/FHHW%?#vzqos._E_/f/q"Z<72&hQwш("GJ#QvyE;VK &hŀLktmn ʽdI>rG`ND[%ص$\;=爈k]-jUB"@+H Y6[gKDt=E J3~}'!^ ~z{RǭƎ""f(v.ņWPo ,ˈO3DDTth;}x]N(C[2%*:w$$yB ktN#""z6k~e R{˦`0Qd۰uz] c/~,CʟK\]YNPDK@!,ic' I_K$ Į$m3v<炍QGJbԄH! Rl|"֯Z\X %1())A|B湉vUYqc8Ǎ#$@#MkZ>blBFN.vnß6BQ8-e Q1];F 7Ǝs܈ںـh0$!m:9d&S2n߸ub `n{ø1vDDDDDDj-1gLo{ ׅaq㱿@6Y1Ƥ[l%dRS>y5l0gx:`}&" {?ysކ {=mn]cp߄cc?(laMNFr|_@DDDDDKJw }N^ ~{->)$.7s^}O iתBό\8'[f+U*i0SFs=nWx葜cqaQ-W*~;b@z2{bbjqKn<W/C7S0(?ƾ > )Hcf|#Y ?u~ykQoD<݂?;ŀ s bLO0oKcxtv?>QP@$< >Y EQ IQӘS+ط+;!nZ*ᖰN3{'v؊3TKlf7'/[?m;9xDrΒW'ݎIÎؾQ.}[]gۇ osU㗣18hB,"ƾuyr"<l 䮙_SSdLg|ܗϬğyy}TF!t)I ҟď]UB1y< l1FKIo6_6n16[*$DDEQ:Q]pA65_\PQ!V;o:ZtC7; z]Z̸nD$XKcPo(Sіb> ^Ax̽W@xF 7ozwN,l59_R`ƴ(/z4kW#9ߡ8ȼY4on!jb0|4'"j6$#* 2l""j!cx\`חcP 2PSKHԶfSKp}F՞Spn0q<ԎiʀYDǐC+ 2 dY,ː$щGː^ !'m 5Hx\+e'<_B.# 6#vSXS \woo,ycgbk: V,5IzzŶ|8uxsxwvhx%#B [}e@9Vc~#(I}k+FMoX)i`Dm ""'HM=2X`ljDZɎle@wC9?y 'îع(uU}CqHvԄM?Kqi<lEX'8LWƓvZ NJ~L?yV,bMٜz.^# !ctFB$P j~z79,4}"ZDDpԠ l:|jT;9c1ϲ@;?@a}YpKu>ۆFJF.? K8$3gco7:@cOL`>f}igf 59/n17g]gnБq!ͮr=YacnҔq0~0%NIFƨag5:}㌍8OFp 1Q;\FG|2![xL&l^uL&FFos`c'j,7Sfb"""jb:C6·w7UY`֡m@Z6'Q$~ IDATƆ j Y(S@DDDbq\/)?`V BAm5pQDM&&aۦ X eH E}[+DDDf薌u7A`~)X_} IԜ ɌCG+ˠQ#:ɲĤ3BX-5,Q{If ˮl0(DPvX-5۬,QaEͱJ(vtDDŽ,d6C ,Q(v.ņWPo ,ˈO3DDTt`1f_ %οl (@v [7ڕ0I,QJbԄH! Rl|"֯ZsтDDDDD0I!8"F֝p#N P7`N ms//FYJ r$o݊·ʄXaUT-[`zo>x0tAΪUtH@'"tѵ#AL6p" ƱhDĠ]呇!`X`3P!q#ª` d[NIohDDj#ARRRSS-)a Hŀt*/$TGG#cҩ!ǎA`LԻoތ·`" {hlfu(B͛\7ŐMbF!""jF@UaנA$n4bg@H͆˗?ҭ ɄcǐfmCxE浽'm`XtJKKI@BstuLv3\ķWY򉨥pdQQVK qN$ލK )yydQr%zZSM ޣ*5#Jz$y,z#=1hcǣD%>qd$'1_Ka7qdQF# v;JSSa7`P5k^Y^_V L zn܈M#F8n)(/ws(- EYY^!ǎPUO '{CBܹN ]|-|j\" ǖwƳ7dWf.2sp X^|Ę݌9czc&G݀5N9'FRF.Rr{aݑ(78`8kh6rF٫7y{Csd.xdnju䁃?+;wBdB'G9?qd Xt4:9{ޏĠā.E),uaa(Ecs P#IIZj((+ DߒF# ~t`C'"d`Xԁ P!aWFJ8+LMEE{0-Ռ׮K=oGP|xWL,lQ r<q?F\m9Wjغ]F' {Byy_x P8C`h#zm<'N2 '_T ~Q NO%S0tJ8٣oDb@L :9 bbJՎƢ8=6F%%ZPFgd8{]NY=EV_2V+ 6['"R8{@X묰X,j4NHm]5e-2ܔw3cҰh|w)GHճJX,Y{y L6vn !$2e 'ӑ^_83 I" C#YT SU4 a jk@oXa`ٚj+ 1>9!@DM9|>6NY8}VmWA`ARxD@@LB`GJ P~#&ȈOMqu*p\ڒ)0y^$L0ӊqEYfp? $ jRXPZTa>OHHp׋aT<5 _%9M^x ԎH(jDWۊ;bH}Bn-3X#5Z7^R9bvlݎnT/2Si\>`+لM89+2C;G @DDDD|7`Wdl[Zp:Dd!x(U.ؙV p݁|&Gd#$ 5,$4~|Uwly,] ۱bkؑ5 WÞ9PGsįKPu {[0~Zo :m2߷;tݵ lX"": (fe"cJB5&n,JE'%(}W![:uFX'Uc샸VKƛ{qAe{7== %8 9 Ġ@D2IW(KMb0 =6m}2!pvD!e726~C#""jִ8/UWt0*{STׯ9xR[~C>RQyUM!=qSpSz ;㬷v/w&7LLׅU6"jE8ҭzZ~KB۱Ku?eظ'-]kQ}..4~C#"""":pdQg7PD(F#Lq#:yk }Qե 7o@ ;`F#ׯw?ZPgOt۱ǎRl#PJt߼ٽnxE_{_bbuhpGDDz;Ë齐܎c0X l?Sm-!$IA`AǮAjU۲`ABچ tfͰ"&>qqEGGw1h"xQV۹3tAƛ׬c11HOl6l6lقc11h[5;cg"=6n-Czm6M&K{@EbΝEC?""""#:0kh($E9I!!}Kkj kH4aZg}ry]1a PZfnÓ쏈&Kvl6+dY Z+*}`f`EuTpa Aj~aV33fM@C}a,!km` ~ZnPV= BEDi)oeBr7>}ЩU 5-I;g{QȒ,NG\IJBڵ0WWCKYQxO 폈L Lf :?\u[o-5Le$&ja1ZN(EСP kjcF!a߾4r;X.m 6޽md]j}uݹv [: 31o9\NYv@?""" &1 2*c/@bL@ankUY V+zlkjZM̶rW|ے@J^Rg]m6wKם;u΀?""" 5*$/ǭ6&l`1Ep)6xQdYF|b71t$"[utDDDDD0)peS`0(Dmغq=֮\Mb1.PRt&LFBd6Qb~r@1m46 k֬}XbTsQPP#GbĉFxx8yp饗j^׿{/wuTWW㧟~K/ Mn&3]vjE~~>csxi!VZg}6W(BpDQcpy{MNbQ׷o_xWpwCe=Z *̟?d9|xܿ;w"99<}Q̜9YdժUXd JKKɓ'W^á( ^|E`ܸqG}O<O/c6mK|6llѣ+_[v-3˗SOE߾}QPPziii~>&WEoO۶mç~IĿ[c^999Cxx8{96M@qF 0Oj*|>#FsСCi& 4hJ\z0`ƌyϹ瞋%K`…9s&:urjhaÆaҥXjƌ[n 9$vNN>> *x'8X ׹sg 7> ŋ_DjjǏ7|~,Xݻwj⫯BRR^}UTWWWF޽/]3g_{v]z83}.olZ W^y%L"<#$ ؾ};nFt :uԩS/\+W"** IIIRDԲ!Ek.l6|'(++ B~p sh&ӳX,0ׯ_͛7_SOSOE|||@]&No7|3߬mRWW2l۶ ?8.h̜9/e֢Gj>?Mu3\tEٳ'dYĉUV ѣ`$dfFcQ / C^p=`˖-(** h3p%nCaa1cȐ!kjI&!55aaaHKKìY~l۶ nݺ!<<'|2z!{| li94y;0<'/lwŝ 蕙yC; 5ܰr~0#s z`8kh6rF}=#駧9 75qgF$)Hx7y{Csd.xdnj`?/Q+Nb@DDx DEEaϞ=1cс񂵵?vX<#]33j1=3f@ΝQ^^իW^ hyxx8*^ 8ꫯ'g} ,]46߿?DGG+nVvG|͈1||'QRF?K)X] ԅR~"| S& ,[T"!+X/u`]q 5듰t_i^%Rk0rCm<'NB '_T ~Q k\imzOb5sbܹ>/\\/ŋ}}vz꩚^|EzڝrA\ϟp̜9^ DDD w9[I~8f|5a`[T`Ow9 zV);K`Z {Ƚgwqxfz$`C(,,ŝUg(LobYT S0!*-/Wv-\=& """":$;NR2r},ԡ6GL(7YS?Ǎ)]e>S/LPcul2Ć뚺XХ (5B`l ?/1b1N ǜaX>3̣hgbcE͈GkŊB @~{JcEňn!(5oal ôl¦x S;_.yDDDDD'"S.lAKIxlk~HF9.WBQ^Oi!e ͈ߎ O /Eb_-`{GHigRX,! -/?Ea;_~ ;&uسY<>g*>/Q+DDDDDB iTN2?f,coC,JE'%(}W!`è $yXNY sxՀZA gIK .zm Cc&%_srstꥸg(6# ߿ ޗס`xso"2QrC.M@ 9m8t,`0@eȲ IQۓI7ބkԳCxxBCCv[z.V~YԆ=2a\,5X0o.xq ]%$j Sg>^]5]T?!uU7I۾i2rc"gZ]YQLq z ;ki8ޜAxqSrȑ%c@v2cX """"dZo """""":@DDDDD%Ɔ j Y}d ]mm,[X2$tKbp""""" <01xroEaHe$&ja1K2c@dT ^v-dF! bjfe1 5 DDDDԮ36QX """vkDDb[@DAk6Y """"^D*j@DDDD^vV(,Q}.6+YȔDDDDDDDWky‘D$Il""6vMDpdQ+@@DDDDvIO92Zu}N^w39mZ}?ϜSߩ߾^5O]?Ε /x3 HiAϱ̆qfw]#J&aJbβ<ԙw f|ss>2}:׹j1jfn0SqJƝbyY%GL!,TG̔+j'$x=nfnr$91"9A~GMRAq` ߝk̇rѯ>nդj2,=_3 z|ۃy4xs66/)G*}n:%ώnͦO2)>7ÿN{I{cg96, ]ZrYK>_,;`I*O5ۍߵ>?Ѻ=`ڴlEs=P'{Of͏2 *4_:\2@DDDDpgT׭:HCfB=3jZ:5}xUڴua`ǿgI~\yg5zZ78T>c+?D+rթ4״1ZLH ?VV\QAs^)`d̒-!̛¼CUo'q}rͨO _ߴ 3U%b3Č-E! ,I- ` 'I|7;'M`R%Yѕyϖyrс'j #E`G5:qobC[us~?|"y!L X?؈ %RYK[v?>^׻q׷+qy/r)/yv6۰$ûx6p|y480Q 3?o ՀB |\ɄjGcQ- rZ( ۓWb%A˃ᶳxzPZV+⚫˼&eIlN^¼6VfO'm1Kp7t6b\+2 .\TβڤǺWxG~4+O=[M\q*#\Ŕkb@ܛʚmyp}sǗ``-Tml0 ٿPZ?ޖHE]f#,m?*إlÌ$V]UNAa/J-rn yU6I|M)'+~¹|f޸C@Ipqҭ)ᵲnD6&V# 7#VCqr96: $Q=r3i܋V$憲p#zN¯ȦG LĔ")j"g!kme2iND/8n㴋ŧͦZSF!m72q.'XM^Bubϑ&@$0ptZӧ0ݓܙg$'-L$%|?KkHl6_Frcb8ߵKwPq]GZ=zq] z֛+%8ƁN֮$$( """"&0musu3Q6+ r_p)2% N C1m{ 3o%VafBHgѬԟ^~y!`2 .PpwJL[i":>W0!7Ud\eΔ _UP(_8H7 ṻZ3keyԶٿb߆SESӇyCt{l;#éT]>vT -r?mBقcr_ h}u߸{%<d*ӣ$ آI>ڃ$;@52Kxqr%$W-_JDDDDb;3zG/h~uBB rp8lجV,V+JD|y됸: "`lnUi4w#|J5g^GhWy|h)vlM`*4E` pI~>&l ²şT'mvdW+Ƭg^-1)dJXSZH|GRfgv?҈_"#-U+DDDDu8] 2%,,kq,V 9?Gw?rx㽼|S# fz?+( """"w츮]:Gٗ}%KLߟd`2΁_l_l&{@""""r'㊈i?Q@D"""""rAHxѿA Lzo)&lv;fESi 3J'{N~r fUb .Rs;ףdS-!v{cXS +-}Ss;%DDDDD}{vnm^:+Wl(v9ݹ EDDDD.& CwejZnRs;%DDDDDDD/dEF"""""k>;nGY6|( """""gߌPLdy8c~~9OY{63g=mZlF{h+K9\Yqtt֢YQ,vvY$;8X_7XLgix 4!]"RZOpyΥ^\oY>/K|d|_-u|?cSڹg8>4Ƨ <6|弼h23Sݵ.ty %DDDDD|9!14uSKL!E<~2c]xOmkW.-ŮSϞJ=`lz~2YW$q" FY9ޢD^% @+m-1o8p8,RzF 5- #;xlpc< V3,RS0)t9❵E ݩ M9etǁi %V%B(15IpF`P0K_ ?2$wUXh.x3Y@/cN9>|9 xm[XGd/ ri֯#Grgv_)}j2f^*?7g`r;x̰eokfݫUMͷ~F~a=Vr@:YYWu?x G΂~\To2_\>/ KgMX֒S *qxhiH=t~\zEhOhVMFyf!h G"ŲwikϚ7Ӯ=_z|S4>3_)۵qǥصuxmly/Sᅭ"""""C LMu3xt6-c L(y5׆Ht3xD%Of1N—x:fsCqZ%[ďn?i3{Ưsvj_Ι'Kc ;'@BM_&Ęn0B.1}E9 8I%^fYMZƈHwfLa4>8 M6= aH>+}|ǧ{ 46e6N],>|Go}чΜYd1~N{ȱyH:f [sL>>z T1DaWi'X)O uӶy]k&Ұu?ƼڝȕXՋ%8"@ы;^0` %O#ܢ +O@NQ)=*q7jбC_='1g nf9aQfn#zݼܾ'p|J/h|g@cSkTK*Z;iv_cB-:_t@گd߄=Mƺ'(^+~kgGH㌏?H:d6X'lcƏg` K \ޟ/t;}5 ;r5:Gw߳yiR'lR?e0el) dMAhf[%uG+|"w}_ħd@c9^ϓg~ֱ>=Q_c2 1<"w00%p$Х xJlϡc≓=4J> Xjۛm;2YWٶyl_<6ƍ5lH.o_>u;fV9;ނ/i@n5XY`~:=r߃a>TQ3'}O{?Lx|, Q4"ES?%=ϋa͇S"" #h||9(}l;e %9 }Tk?_Xk]DDDD lnNɂUu1@pz\ 'p[8'}LM=qGgP+ }1%QFZaP!MI$ǻO2lH9^CCx_S`+׼8-<ѺO9o ϥmXd܁Zmћ3lgޝ4ۑ4J Poه۪Y/MP<(|#zp&,̥^ >3@S?8wQގ+{w}Pkeiä""""=#u4 !!z͆jbb11u4 ),[ Iu/X Tzz1Uuq!O6N;'+wK?mݎHjη~$4o?nHKc"""""""=& """"Rq)7 """""( """"r|7 Dxf3`(v9"""""Rn&UXW|^MM~f 1N>b؝K3@DDDD`iq l)?߯lJ\5VW!>GSY 3zCSA{\d2@DDDDz}S6Mmv;fEAQ2@DDDDDΉ9l\d2EK%"J m7C-0ٙ!=6ٮl_N^S=1D>U?!mMA Uf͗]ĶyyQgDbN۷D¤""""rq0I鰟ked2a4X N"ȅg8OݕD//뿹mg:֕M"G':%ײL;Y̷ &V%Mi-L%DDDDDgbմ $X^Or+|f-vRh\xAg6uFXD 9AR]hfѠ>.u6F"ͣVX" XUﯮ_DDC"#c~(_ଃd2"""""I6SsXZ^Br?΃^Z GǵS;[+QLfaQdWj"` Tq"ra*""""rAeCKYX>s Ӹ|V.C]*-L^ӯr뇨+w1]f%٘'o6Le- u( """"rqK@jpWё7AG'-6-78χUϫ E KL9K59io߸LMEDu,""@PDDDDDD䢣"""""߇30afc6[ 3O'{N~"r1T˕HXD9C S-!v{cX%_Xl ;T2@DDDDDΌggvvELS9OEW/}q՟6L7 9YCۍ= ( """"""rQ2@DDDDDD"dYM)eI7ף,>FD \Nn;w_]Yqtt֢YQ,)F+5q:8 4!}(!kp؜d?ߌPL }= r:Kog每˰4bؘ{w!7 ]'k7.GwQǛ=^iĀQ"0b.7ƓM)(ƌ4בx˗1>"JU&h?a#?Y}8ڸE;I!ޯp1ObwUZ2>E/Is3QG_u:m\˖_72"ByWػo-9iܠ֘4oyuEhOhVMFyf!cYx:8ը{Uwhf } ۠ҹ˗>d:fMlp$Ҿ],vv9lq3jO?nšt%5cגYb'&FNRlQ2@DDDD,3so67gkK~@h|(bPv1Y. #1Q{<圼~l^ %ׄ9KJC7(̥ "KLE-DAQN ɀo",Y=ɀZ1`eH![ďn?i3{Ưs2g e<ߵ._zl[ӾkxsSyw{<~S͇u:7zBSM+hrBX7|cf?~6lpN(_My<DtNB 3?=ԡ^mLrqFCbqJUl;-qS^#g6M|C AQBp[T9_!yE|„ǛT#Q{Jꩼ}c cw7jD^OбR.A Н/ >% &ݽT?+櫄49^.w3 R7u,Ի͍%H}waK:Ekg2-:kL /kDkdx#pDS'̦c6T4ρ՟O$&&1~e3Ά\3WJ-lF{Fd-cBMZӸQ%DDDDDmv3sC:-ێ\:Vw` geɛQl=U2$H]~?(6Jr=`\ɏdM! խk^/3 ѯQ'S7>ǿ&L 1sGN_ʄǒ۞I&\..0p< L> XjۛmWMYr%?X+3Eq%֐R;lW{>Kw8Q `iv8{W8Cf2@F]_xĶId\z7#"{Ggf Vn^L;fw'v4J> stream xڍTm6RK7twwIʲ,-)!(HH7J( )(tJ||svwfYvf}#~(DA@I@!vvc8#PB2}A".0$* B@@$J C:M$AtEђ t+ 0zv!!p_%h7IAAooo 0  P `;= !` ppC`Otih` )'W!8w2Apj }|0 v@䃽p U~ !wQW1 JHWWA|p r3w_?@z#/PO7As"A0w(w}!  n>=^0 -C;A7n_>K@?߬oE"\|}łJj(TTDBo Ąg*j HLo"^gm3 [E7N*WT=]\~ǹ`W 3_k=]7l=T>0> qK<Of\>A5~`7*kDD` KtPoMHM G~ݤ@P ̛o_M!("͉o= L"!RN tkø KcESxjc*'8 ^P[Q/z{vc"iF2bVv Hl--9S&/"^v㫅PzLʜNNPu+Μm ܊4eia2zy+xw21`n_~VOhU$=J?M)s5[<08ESw&zn7T />8lUKuZtyS{aP/"G#dnP2'7ͅ:ќRYEϵp a&7 -fsh=\p=ԢƢw(.ݳEI˚]3 ۸OrU7T0 <#0iƱD YI  L00*RWUNp2(@=($)@,&U(7xr"UA&ں6F |u,6suU3~ZsND 0A^J*D^pY9m1?bi.b FY硤Ԭ^ja#> !W >/(guLx/鸸b.%x5-v6[8ʘyZgd4chDyLv,ݠ)R8aִ,֗oOt7K?ߖʤy-2i.n BX bo$Jm%N)t_m$wA[DKq7׫" v'އ0+BʫϤZHsȧn_R5<(*rZޓGPٗ#$pth$%*X|x (-q٥æUҼ FW%J s\p\A"q.8ݲgԔîL/2=i]Vx !曜 'zi~J?,%m1c}ۘzom>XZi$Om5C\{>Jʂ߇rLQשG>fQU̻g jNwMޟRzO\nOa;Vo,%9]Г67wRnqI|#Ӭ>nm/V2k~=AimvT *7Ny'  uO1ֱ9N3Y9=P̅ySl[~Cz9_q8u-J>ԙQE褄B1(4E>B#};s~`fUoJO{o^K!$I[pN˳pűzg`hJI5#mm$E۶S`s^4J0Hmb1?1m k~dnż@ƯFkno<#ju-#fr$ j WR.ômBD&KG]˙Jq}GNcIN"@1/nU|5* [+5 *jթw5 ;"wQ Uytx٥4kz[ 5J`T*hVc2NON&>_sI>|(Ju~og[gcfrQi~n.mMfaIBCD8W*]!*dDtFqѥTm !2;#jg.Dcx. KV_Ѣ sWL*?o)_˶(5mZ3]`B YӢbgjonԞ%tS]. @x/U_r%JYK߷nN~4Gv nTwjq| ݠcJX[9JΪZٴupճFM Pb<$'KVf?M;_M8{8ߊK rkbkyG:u 8ɟv y/TRsͯG53vi3 @M!&>C2&cACn$+@>gOWmqM8TTn5CHi'O)2mfذkt66>j!}FYz ¡3 $OB+/+oE&壓iCʒ li1IzF4۲ 'UEVG"_d`#sSgesvxT&S\-8HexhԄ~"9D[OBw3!.| Y85inF')1%L!I4oo} flVJQ1t5K㗾A SἌ507ĨƝ{x}xy`F (uZ7YN}״}ty[~Hh)j->zbk0?N`:kOܔ۪^iR<+NCGxأH :-m^XzžS_Ȯa8鿯s2lX헧N.̟wIR·h"l 3 wRse$dPA09t1@z\5``zJPiek<eeA }>L8_QRpl"ܐթMhƿw 'dc6Z~$A*a2 A,,yu蜖=4é|PV}A&:9C3(O?%+Eyb_ӻ:&$H#N$;}1O%bq߷|,q$y*OۍRiV ЪN`[wR.y͗2:8XdAPA[*PǞ:姪0~jB:-@|hl,,,WAXN8Q@sg-$2jHö́Qf&*&tUv()Į JJDV.A/InCFC,,FOW%~rWL5tVjy|5Y!~}v 5-.%Gɭj*Gb0,_UgmHYHA 2qj30)WvE]\\cBpP"[x+Μh*?S)3C . y=2;0ڡ!lg ͑rrmˬÑtW;w$,ې c=/T 5hW6?sr*кQn5Mui{]װƲ: 6fn"uYU< XAy߹$@1_V=f2{qOT뙖B Û?/og>b"*+2' ¥tN5.ˋ{'2/8y?4gjxr,>-Ip2ssGG:(Q|(SIw5*;tW*-`PtК#qf̊8s]W{j&')3$6/D 7Bo O}9,1 ylѺ+lXK 0qyqF7oJ(ɽmRl(̚3&*LHV,c-*ɜcg\WNF+3椔eS*6vvLտgZ'ڎ[+n4m?"aNl~%lU`eti+  [эX_@ JmV4q|=CaR)Xq`1% VNcAiM~ kG:Lu67`QE[T J5*=VݩF\f2qQS awO=$x7tcg뉜&ىJq㫴& v3aVU b󧭡'i1X[?G=)fSl`=KH23CkC{}:&qRR{C*c9e!D.=;E }E2#RsV(:TSRUS;'BK/~7c*cR,ġ3EM)B`.^5>0<=]hZNY;Ֆg^-t+RZ.חdz.¢/fD$rZK+3ZmO)gvнEzw|F*KJc%0(. ٳʫ 4[ĩo1dHOJ^uF8|1Oz1>\3 >hڴVo0s^-7H94F#;-/, skvN}#4=u9Rb̒OJ${6I*=ѷie}*#;v*}iЄ9k'MMFI AzkKkaiJm-u:⋎bZM JxCh3,OX}=V endstream endobj 4 0 obj << /Type /FontDescriptor /FontName /HCIGPR+EURM10 /Flags 4 /FontBBox [-32 -243 1060 720] /Ascent 691 /CapHeight 691 /Descent -189 /ItalicAngle 0 /StemV 78 /XHeight 459 /CharSet (/C/n/u) /FontFile 3 0 R >> endobj 5 0 obj << /Type /Font /Subtype /Type1 /BaseFont /HCIGPR+EURM10 /FirstChar 67 /LastChar 117 /Widths [714 828 605 499 765 783 394 402 668 559 1044 829 803 576 828 609 557 492 774 646 986 666 555 666 0 0 0 0 0 388 609 588 487 603 499 420 568 621 360 331 555 365 915 664 563 589 605 432 455 416 642] /FontDescriptor 4 0 R >> endobj 6 0 obj << /Length1 1374 /Length2 6117 /Length3 0 /Length 7060 /Filter /FlateDecode >> stream xڍwTSk. {1 ( %*zUj@HIG #D)*U7 (H. Q9{׺we<3<޼\FB=\ *&:@H\#0^S8BJ/mP B5@QqiQ i(I DP/PGB=J(w4# JIIBh @1p70+C1>J'Kp8a0 $/!0@C'w" ԅ0F(G W G`p49HCGk PTXtEJ@`(7w(t:"\@=Uma7FE:B]=Qx ~ TxOaO/"૬tPBO)#p>":{!Z8"H8`EL,\C/ Ap ,+;S tē ?' Ġ * t@0@{ Ovgo> ' sg [k"' kULL (!;?۪Eu6j Q@?_kbA2[ "!7RHnWXW8f?w@`۫gtrxOU7A9ߵ+ Gy"~=aB Sx;ѿTAPM |E>^Tb`0O?pz#Q| :Ѐ_}/_iaX4?dۍý0$ &,†Z$*:Ѣ; 1M6hPܟ?]1:&s2m՞CIPNpNӰDZ'$*;˖7&%:ת?xvOj ,9JeAr_mZp,c,iіXc cև,}>Yg-,fthuܩc!)[ɓ"H,Y|wƆx@tu}Z<}KCN>'g/x `CN4vtz:WQ ԟndljW]JN.N?Tp0o}(mFq*Cp;bIF\ y灚gu_amX\2,[ %Q֊>Gpzr>;}T="[O&ePHlrN}k͓bpѳXhW&ިϓk.uxUʤڳ-+K?@z~[ӥ Ƿ(. d&!?߄ %7c\{}T륅g|NָJO}769?䇁@KhB*/TwSڡqt2:roЏ/ WjIOj7|0sL̊%|" }ԡ)+[~2 7ݿJI : 8{XiS Oʞқty)_>gKȢ<kW=Xz[TsJEtrg z_GC 5UyC>߀ UnV¸p$%K kq>oJ9ϳ. pg,%>&S{3?uuDeffm9u/)]Ɉ}K9ޟW[ҷ}.U90XThVS+(a9 il{dh[!ǩRiؔkB^>gpHG[ ['V᯶.(E{ךt~XqOgSFqS$mS~JoT(\UXHNt_&GcwO)+ @9"=s3sN%rV>kklB Z~*|?QmRVoԚ:T-~,6z~c;jvͧ Iq,D%ݻyV SkHҔ:.zoq{]>-F@rӁ azBi;Q31ZOHgā}zzR$yݐ##\x䮈82nJp`b,S#J"΢} |AT=]}#ĺ'!]5,h47U}H) ,#~LHޅ1>-/}et!s8UqxzhFnϭ~BCn|k+{`jɅ&vb{ Vyn+eԚ!GbƪϦr"ћz4s }de/i7S5c-T5,kM4)-7 淍2hE Fzetl rsq3D|2rϢE+0IEٚ*oѕNMU Ϊk_@p-y-qO=63רZ>X8OPn!q%*Ԑ+St5-d)ǏÖv_ B 4 7ӓ=)}Gd.=|-Slr5.ttIG7{\"*Y^a7~_\l ιQC. w+֋~Iy/h҉Y&G_ekف,.r$2r (DwbhR|?=>\S8ywCbbn5ե]CӘ+Wmc t߭wIÿ =[79s0EtAn>%B,( i8t)tU6+&7Q K%V T||AA2mZa+y1b/tڔzhķրe_EʛMcғ\5jL"yCg^fz7c0i.u'#$KtƶT9h/Mi'y;ojk}3fRGև[<ڱMc/nP#QwRr^/cޖYג\~ft۲!%1`q~'bt|HZ[B~qʢ;(=P*C j=ɵe9A۪ѻvRS[)O~(ɛ6HnDFS\݉>6˜15;_ MZ& uʻ~`,7'%DSFџ=}?4/a|vMc rK0w2+ uQXdƯ)leJ}>'d$c4 ;nGH~Ĭ4S}Ų|Lʍ,6Nܪ:uuC@ˇY葹9ϭY4=Scs l/K[ YSlf{g]6k.g-PkY7^WO3$6= /u% ?Tz!C3T\oJ+AGc1 mi>!7>q:eGAw~iSdt/z#J2ĠVT=KE}G&أ2tkB)aҸ_3^%3dTTTQH5{Yk> Ұf ޯZB#g %,d5ᒫH*Y fjNH"wΗ(`,(vgœeQKlӗC |Y CpZ ‹Ӆmc{EoN+4 #aG OCRF8RT-HFA~bŠ|4w]%u]FDOzZOE@6ew3^Yj aQK:כ4M]O-g5ąXl ;1ὢ=ىvecul7-x7fNqdZV>Xiq뫳K^rAiHji-,?.7"bƪ0y{/V(rԽ)Ovt}ipxZ 6wvH )P7(/:YPMqp ikT迤E;bQd?-$]"ju<}|Ovd@V,Tx{K 4Vsv||م7]jmL韹tڰԩ,{x}?)T֮VX0Y8holC<H"Zx\m$J 5%%"<wdEGX^F]zKIi$t7[)!PcCRf%uLتK5T29W.X-{[Ny xnzZ(%eB}u' }e Y &|^eyUX dd0u]b|1A:t!q(Ϲ"G_He"AφVS6ZmE('Xc=ĥ9G3FPAϹMizfkHJ{xgtb[A,T7Y)6**՗X/:hτExsQQr]KF>l<*np]Wo*˅ϓ!ڏ0}M>v(K z ;1^BlӋP[-XD인.ji=H^퇇! Xt.D_*_3J{G|Jڊ24iq2U/ձa.RӤ:wrdFBrtH?)/+ZLtJ Y9]sƌΡ%/MwD6S(rW]gV^4(z%}ϰ/?Di\'T$cZ^=ck`S X06η$%dOtEF3.(顟/Za/}DTbJ9rt@ .8HۋR- В?`dWr {f5y>;pׄ%(n7usߕQB B=HUd2}HB99&zؖ,UJW!܅!Nef{>\K [Hw̜̽XZs#z{#] endstream endobj 7 0 obj << /Type /FontDescriptor /FontName /TYXLSU+EURM7 /Flags 4 /FontBBox [-5 -243 1229 720] /Ascent 692 /CapHeight 692 /Descent -189 /ItalicAngle 0 /StemV 88 /XHeight 460 /CharSet (/T) /FontFile 6 0 R >> endobj 8 0 obj << /Type /Font /Subtype /Type1 /BaseFont /TYXLSU+EURM7 /FirstChar 84 /LastChar 84 /Widths [617] /FontDescriptor 7 0 R >> endobj 9 0 obj << /F15 5 0 R /F16 8 0 R >> endobj 10 0 obj << /Length 897 /Filter /FlateDecode >> stream xڕVˊT1+Ta$VQavbWciAA}O%WGʩ#fEpMk%1˕S*-g֊usJ {G\9EOg8!Qc;Jb!Kpl;vřR390Q}j#b˸D_ȅ n~'TV IB0?>x E=ZEt`ϜY0x<JivP[#'j}Fz 7$wKKENzb٠s@N )4$45 \Y4]#6>h P*[6 E䃆|qo_<PLgZ(C%e2{R6pv6T j(l(-Ë+"4ﭳ6Z!#'Hmc0۠D{.2;Qܪq7\xLnJQUiQȧ:G.OA>(mFDAtܩX ʔU &Ns6S΀ߦ>T?U"f:`>8 B3I&{hI/ܹbqՏ3>3g3ߪ\2 Xu4*XojD8\7 y+7\7}1/NklŘK=2y8dxn|:$(wf4H}xܵqݍ [**D!?cZrT> /MediaBox [ 0 0 595 842] /CropBox [47.1 695.1 504.853 842.91] /ArtBox [47.1 695.1 504.853 842.91] /Parent 2 0 R >> endobj 1 0 obj << /Type /Ipe /Length 810 /Filter /FlateDecode >> stream xڭVMs0+4\#$Y_.;Ol I$&@aݕnƓo^MX*rz$EMiZ)=ttS8cBp.2!U՚YBfaܶuA2]5+󍻽s ǯK?-dyWĆHJ[r&\,]Dƥn?Mm_lV['o;Jo D ÏaR*%\Q4j-(lȔnA{/~Hr4l\Y,c:_+(D?xܭ>^ό]P@}2ܵmAGpnhʥ:y]?#1qBXiT'5>gKm ͨɠ h}i\+ʹ!t6 \r!Wr_9$xȲA2> endobj 12 0 obj << /Type /Catalog /Pages 2 0 R >> endobj 13 0 obj << /Creator (Ipe 6.0 preview 32) /Producer (Ipe 6.0 preview 32) /CreationDate (D:20100221141214) /ModDate (D:20100221141829) >> endobj xref 0 14 0000000000 00000 f 0000016872 00000 n 0000017765 00000 n 0000000009 00000 n 0000007566 00000 n 0000007786 00000 n 0000008115 00000 n 0000015287 00000 n 0000015501 00000 n 0000015637 00000 n 0000015680 00000 n 0000016650 00000 n 0000017825 00000 n 0000017875 00000 n trailer << /Size 14 /Root 12 0 R /Info 13 0 R >> startxref 18019 %%EOF trunk-2018.02b/doc/sphinx/fig/shear-2d.png000066400000000000000000000353311324306050200201310ustar00rootroot00000000000000PNG  IHDR4ws pHYsqF vpAg4%G9IDATx흽qגZ8kk@Ʈa& 0`NµFJ8a@y t pHß&?UzfAAp W~,RD~R[ h,KB'/@bArǷ$IyUյqCDWA0:N`*%=Q. `]A8B ^. ({AN4EHS8m^ۖeA|R8x~]A+@$4s<#ME." ;K3HVc[O]"  VVtnf]A؉,چ[YQUQdlEtAv7sqv++zA8]FgMPA8rlعg)3Kf <>Iq-'~EYDWTڣcK{qMA8IQ7ux7sl? bǂ " : RD#[-rA$b)K#]]AYF#" V4QDs|Ǽ7++"@Ss<\ 0&M);MB{#" lAQB@SP&+`$q'HUgXc_L]DWy\Zs} \ *]tEtA ᶡ}ӎu!9tc" p (wDӎ@A0H!FgI6x:T" H7 B*|?.S" pS)T<g=w`WLDW]"8nSRfHDWey*Nݞ$: :ܺxk ++T[I- #QCᾚy] aˤ2g]]A6l9/C p7F" ƱsB9}&S iѮtuW.,YCUqkqw]ADWؤm<,3 l&զ@ ¹o!Dtm h7m(ewXVUnkm%˒DW|b%. lEKmeO~y3}?<&蒃vr=/u 0$d(.xn}!KߢFp T"os`C| MX 9/4cѝcڍY+YGnYsKCog%-MC 버pw;N-L[Q`v]TF1J]#$LݞHJ3쯑ME;?O8mCP|w ۔ O@ꏟ޼ۥ:+ 'Pf08?{5{CVN9oX}kT=TDW̶y"69sz+ټ7!Y?󊃝Q^Kq Yׄ[^{Į\ysmL::n:+nf{(/j~R?/<!4mq6UQ٭Oؗ ]fS}?^(`VLRe;RV$@̦֦Ϳwv;(Q R3+>>0#`(6z+{OvVq+P]fuѻƖ{3GOtURԛ7OgF3wrÙ{$VkzE,.; h;szR.p J,o)[жC|g6G@93{Mga!&kvEx`]*)?;F;A Ev=%*x-l4ubշ]iܴ39BStUwS3HnOGwV%& G񺠿fi"u4upKq ++~f"gNϢ{H ܻnZbҦ=^*EE#>ehryUKqkEVvkTm.C # (Yb22Dne?- -CO!o#{rsƿܘ~T8;KJgx>=1]Ѕ"Lg|V;sEwy,vJhbZbf-yI}˭3B͌RR.dn`onEwL[t{ &JL)sY }s)M;8 MBDF׷k~s$,覻F]dZ&k/eHUtmoDT]t!J2t}jӔGqcU^I:22r]@Ut24;-8c],C[PUou;i~m _4(g(:I|g)W^w=z;_@Wt249P4_tͿY*PP]Rit~%ɱL_%}tFhlZb(Ife(Yw0 ED; ꢋLY:͟Du{CR[;^*cpĤݑVlH T=EWn*&G0Dt ꢫ٭*~X4n/8Ѷ @8ߙJU]ˠퟐ蚋)͏>a.f^XT مyXh͋nUBumk7w9o/C;ꢫ[h}hHerPD"\Ս.;4v+eih&n>u:Veٵ^2EW89)*w,m?>pQ [zEl+ ݠ}^wDWg6PRcz xվWYхݱ3CL]\ű/C|d06z$aܜy"z_cťi46CϜMѶ6ŷfCng/Sӛ$${ ݆OYv0wɪQ)Q…s:I*^IeMsݭ+?K-]'4Ů`-g]3},2ev\>Z.Cɔbezsݭ8Š炰Dkv>W8NNh*cҾ>-1Y!û0{\f|Dw8)DDYS^.5NGv)8v}pןy9QPl`W~N 9Rs]g]N,yN7y4 Αa*o 7\ WJ#Dw-ι!.D$O$z;mG˗ ɉǧAg8¸d\ <+fO UDw_Ϲ!.O6u< HZ=ϺTs Q4itv_+%VxD]B]}?Ñ浩'gG: ~}ᣏhҵ:wߵӋ8<1'E73o9}7p~dԙ;S5s.?I0E,ye89Moh.;T p0 κs mW ,peo߇[k>31y! |]z-ԻpN,.>d,UEo>>ڛ&~k<n,nHiKLdv^ώ}ɳ.5xOשw,t{x ˼s^& .C?{Cx>Ϟ-κ/ o{c?12{OVNs̈}?](wR᛹Zo(/8s 9y?8ݶ~4U"nK>"޶g=ٴ3oK9#do!>>>WQFEK{\ZkoKdOy{XJL-Gi9M3n5fgWrM9]oU_~sC{ҙQQݲīU\' Dkm'۳yܶO>ynhϷau"˝?='+ q0+R ':6c/HO tEї_>;˒k|lti~0{OV6*q;pѥեzևZ<oztDRC{j#G(@i>7{1Bo!E=>W]4f},ۛ {A45g{ǹ$oBȋ rN+nUY_)Fp>&Hէz\{ p]=x&W],?-4Hu:(%?e̹ 3\!y 5ѓ#;?忥/o..2dQujx F>E uI9n Mf=G&%:ߧ~\!u*ui(omY&'~f8%q[CwV¸B. 06IB܄Y;/_t\C{^D-C-bXn=- C.>/JQ0JΟD% IE\U N5DC%uEt:],W|?~7PJy cEt>zb %Bș_(?p ~W#n43*Mzhy|՜;n[:e6anKقi;7\ co.Ҕ)[Aጷo__NEŌg]:0Vɜ-jR?mTQX]zVŷu~ ro"ڋ W]JqV7jN֌Ioу6T^]8kQvхa.!/;{57Л7 0¹&YTOIr{%g!8yDtݓ\)zg?oUS$?=MQVUc޻w{' {xڷylI~՛7WU}hz}[tR+^D7,q?~W#n43Ð0F )-w4R]h2Dj fftyI}OuVDm~+KРOiBCYGր^*!)Vc&QEt饙! E4 q3_Yta!t4(Ȯι \Z"!,PDo}?˜wP],1g%O Cm=Ib|1{aCt u|?2蚾P iR7N~e7 -ԥe_aP8)C(x:CԟyltIgM: Ct?8M6a-ږ8CHm Etn?"_0"./S7 cIW!l pnA-QCC8>FBREs ނ"ۻw!DgVW+Qs!DpEijA2uŏ:@u-0[Л']sՂQ 1//Z~Pm6Aۄ0붡1 5]=e6O|!nQFՠm1JzGo0Du"Fco~ S"ZyB0:R,s.5\,mmM-)RCmyQB<^^6=QLIƱ+DkǑKS4n~J%m؇pP;}pA$fOvEw864KQ8c9o8*nf]lHlSW4P EBqù hiϝg壹ov+|bh=ݡhZʷ,CLYUܣ玉q̢M73cn>L]Q]"e9~P ҇133κRĹ..w#j=pq1ﻺ玙 r݆| 4=wLM̰@T=<Ϡ R\>hrs<]t:Aj=ۄ?B٪mNsohir\m7\WcM]0=n؏67bO`'8&9W=`2QT>d5pk*t aڏk 6C﹣߄~4gM9vD}\?.ԍcW[6- où mY`=wwԶizVƗdrYtξ~#Q5p;Է;h0~DG-pxw=΢[4) 6# NNPnנQvTxZbn*0u9u5mpnS}QO p sѦ!Y7K8b;SJ|?Q.Dsu5:pȐbw Axo=K-/7WtFSu5 4wp،Mk$We-n[nҴ˷R _]rmpnCxl :wl}]g7PжYbQl== :xn44u\*k.}mN(HܱBg3>kY#\<:U9< 473Sߏuk;Ð3m&Ak|.m[Qwe;isa5}nx)p),- ޸(yå⎯nm(1kƦ-̧_~]e1Ơ>v{sǨ,!HF:4O57%fϳ.Gѝ۟>_..qQtq]Ej۲A|_կ\|f/H* geQtιpqMˡnOtqεiU݀ԙeYצOu]U)OX0{vdv'8>OqPln0E'H? T6{_D!%zu}lCжu~ؐZ| fD7*Ae1&Lw'76b]%Φ3k< EQT+~Q?#b͢snD4g8ǁ (L1a.S#&n,&?WVEg7Dw8x^"Dѥx%N;^s(bqx8x^"Dyqi]~~C+fxx.\*{N.6G(u)袴nRf%,CN"\ݖk''$.ւ6^)BGtCn\"4@~=Ep]B0͆k[>T|DX'nhDS\J ȦNEtSqK:u:.ފaĩ.j! a.s.@: EJh=.Y8nűʱ2{ M袘}8L_tw^؇nːSvmpqrj,.ظshh/m_&.I[T`PD772m%mхh 5aA[tq}n#sUazdoh/,QZJ1 ѥ*C;KYtEsL2!KwS=Y:} % +| BJTtEo4WNG9KWt]]NeRM Uх?g4WnGwRݽ;_q_t959F\4Ewfӊ2)}PoYt.Z_ҵ%=L-{{٤;|.fޢ{h(.]/@Cff܂۞ǘ"-46z'YnJ`fo>DׅD΢r3XND7ϹW6{㩉 g nU=j띖R;GQнi}]N=\[ in\9Ut͝R;ŸHIi 0W p٣ ).$וrDsg05FGt]HC{4{8gQTD2)׊NAEt)K.8m,C.Gѽ^4wJM:}S[À٣"Dx]zYQ¿0bhL?kfQGN,p]}|׻om[Tmt0d(,Cߢkt.]um>7y~E)e\41!Яk󏏃[:NKa[I<|.F۫Oyk`]D~sLM8. pw!M|+DSTo <0l|.C_4:sg3#\nh$#p+ t1n 9qpJ"ǿcr+ Tb [tuӳ@BCty^r [WSX2{WXʭlqP+NES1[(Ir9ܮvlĭ, {"#Ж8xv.A$qUjoזLfOw *J5.2-nVʼnj7ra셷 IJ2Ez){uG..V;bm]bgl 2DkreJ.]_3.ͶW\T@1ENGf/eXwr7Җ]K[@ۦeV޶GSSf˰8_%w+kmm|! ;H;`kV\jt47ڱSpО͞ZIce\b=E onYW{ = =0GCGMt6q25ͭa,@k[(LMs˰iISLqLAmZ (.o'4nij&URUH4Wuhehib5 Ʊ)鈮r+_Emhq<9_QvL^iT!`p2E׃TѾM2%j~8Q~Jpl( ljHfQ`Ab aE3-\orMi/f]UIuY* h'H,…kBtBϔbw/B3 .?m1>DyV3!0]4EWkFA<ʈ /(۴"F(w3] ;Eph 9ODt]WvinD7pih] T/o_tHDpa40MEAQjA4H/}mj)+KHC{蒃-qԼ|JFfE B"pYJ[]͊n.((mZAƈ蒃*Ǧٚ]4FGAl#Kz?]Րv @DE rzqM9 "/=m[C_tUǽ A K>=F=}DwhO3"'=Mާ~~YtU?472/ +wDtWt4 h>Ƴ ダ5Dב76idUɩVlZ뾟z蒃Vq Sc_ߣkoψ +$=" H%tEXtdate:create2010-06-12T14:42:38+02:00P%tEXtdate:modify2010-06-12T14:42:38+02:00h .tEXtpdf:HiResBoundingBox457.753x147.81+47.1+695.1otEXtpdf:VersionPDF-1.3 3 0 obj << /\IENDB`trunk-2018.02b/doc/sphinx/fig/shear-displacement.pdf000066400000000000000000001230111324306050200222520ustar00rootroot00000000000000%PDF-1.3 3 0 obj << /Length1 1394 /Length2 5926 /Length3 0 /Length 6875 /Filter /FlateDecode >> stream xڍtTk.(0 -! ҂030 HHHRҍ %?wZY^vV-]^KTGrF~~A>~~"vv=]t!+@ 6y0 T] AHD$*w ),|UL.p@¬mP68!\Ç~dH Q6PtG(AŁ@777>3i-CtP+ 0@gscEXH(mApgt tUP`?_ٿ ࿓ V0{(@SQz-|+f@9( 9a AOYn)ppQD'CB!{lp{u-~tq>Ü\*MDج(0C!1 upv~|+4 #rB( ;}"0 `6CG&h~y{%nO5tU4x OV x CTL2[.OE-8 4h.CPߔ_~;_U_ )vsn4]PhY#wա0hy ÜaPK- bHj!a8,~UCU 킢E pE`$Aď&0 %7@>8N1H_kEs舄D* qA"t߿ϿUC!Dȣ`ۊ27Ŧ8pa+/5t'疥wR f;t7m KVAa(t.~8n2["譇DS"㵙n?71h03DMpZUm;k2*F&i+ʈmkdJK4IuSy:t=mX6M띝LX8fRPU}pGOOWzI`,p(QߴtT:?: 6bˊ>ӷ)3gt|KC2ra5-α^6EZ,4: ;rw &ҕ1e/ 2p?PX7YT7TV? ky\r̞7VB업gZ}ET?czm9r@Z-}$$.ʷ+kѤU5_X!-E,0߭!\iX^f6d4M[}g0<ϭׄ?4_P&S f0~aU'=Uzq#]ĹAA_[9?zds rA] 8jGdb"T Nos)xg*9^ՏNt0v/-U5]y0_6~E  P8ouឦ-lBVXˠ_ rk 'X{N 63]^/`n 5{{ΜPTʝp{g,+{&JVi^f͙2 8}$3"f}4fʻ!&f!|Gw.!8Z{i7lE1B) $7}rƤ?OEmYyzIG>e d 7VE#zɚ_D >f%Rɒ }E#L# 1OIN*\x,Z'6_z5 ? KKTxO8|Li'%:O eiB9ݛO?9}%J.N69g-5n5Pڨ0:[-ERHhcS=eW(_$ fdOtދdgn<(1al8+{lk|WbǖCQǕĿ/r®,{k" 's>Fg>p$ZotUe‰qꕩ¼< TߙY0}}c蛷#S70Fcfi(Ksp ~`dlqIDP{L׳BiwK(uՏ ,N[y'nGfߑ҄HtmpnRD?Tq~zl'~)ti0kq)=ϛH' I%`Vإsӓ]\Z=4'>_0%x}6k< ]`7R ͎!L} {H1-%އhQe7A9{r 9S R'j疴}.!8/% t_ 07T,#h|H ȐQ<*\bg$h%3niS]'w[ 8u&}Jn.{x17+̕J%eLMvUőri9m&4{ŭ2|^ӕiPdbyȸY 2ºmH[;>ET@vFIqcP'wd ,,3r_n齅Ws;իq$ ذ@]J'fcV<\~/S/X U$VaelwkZ|~~g] :OW$RvzPS޽x~︐ˉZ$U-a9~@ڼBZ,4Vg4F``(wé"Cқ4&a2\ b 9_צAw:wdr_=#)1 rlPoWG1_G}E.ߠW5JcK^Q}bc|u ~FD"+"xMj"r$u$ R$ܶ5jq[( I;LF{oA{:9]3Q$s܄/;qt#ر4?/쌚4Z%a!MuA3<Ä;[J7hPǠpQLp qB肳^!5Bx<|*<*󈰖fMIiv"0iݘ\,s c&1Ɇ5ƀ@eC5sDOw!LMOƖ*6]K-QiDR"}ͩž_z|sg_og] ;[֎kt^FTTGZoyG;C)QMS h lXI0Qt~^f>f?yJⰹhi(eLG~4J;F"&~}} և/6my %רz'^yqޣʩjw_q^_U Wh:ߐnw}{G޳-c9oSW~qD i9[`}a.b"tJYrS~Yz(^{g;^av<ʚdeW7GJ4RJ8qg 0q5_F tF}fI*6wիbaa ..V(}p1][h%);tCqzg t@8pdShhmoH^TfF͔k}|R~Ւ !Vo=vR4 T>Spk=aui8nwyMxu<$9#ȒJs>Nq5I=y5S򀟛r,]+SCG~S:&몘Murԋ{_x g HFT08z a|]us#z`7'=^J fQD8}G&hy+>p_X:Q lE O4`'=hi;I%$fS n]~[NLhܟgÎ 5T[7xw12Զð=$G3ҭOjmy )o-)L3I 99ke' dovj"6Vy#y_!]/ uE)V>F_7N2yb 3n]A t !vQwt~ 7h{rߵ9T 5Iw)}Izt B'/rcW $.6`!-Hʗ Oz/N_Ys聫3 3 PRa$#h_O^rCVnlaWlO͔ZO~agejX//& Lt;"m_BZ@=.]\Yi?#Qp2l (~=ƿ,UcmI,j7M%/9~QBy֦λs0Dlaا/IO ?@TgxhDcsG *EeZ22>i)K ǟOo&5ws׻ ypST1ʵHO}ͦfܭd- o$ +(ʨ3b}ִiF4GVI.FcR#Df ?T3'׮2Fǜg z~%ׇ"]6/-ɻN#,˦Ȼ4h;ƺrbLEE%~q瀳NQ ,h'Zw%ϜlfD̲l}۰ך]CЙ(`yЮw<2KqQ3^A 7Cr+hFQl"j]R+Ťh hr`psTOTXFb &Uf+H%I'r=&Yl}^啅ѣ*hmΒ*Zw Ξ*Y}f_T,F(ZbPkWh'Aa`_?x)Hn|TUU5lݰN.浕X}*.)nb֡Z?3 RRxK@0wj^ήM^2Ęx3ₔsFƜLvq3=̽\˱r3|x{CA!f[,PH^8Qc$eŗ|MpV{)[Bw7R{"$]ORmNIu$2gwElA| 3nʯ TO '#8^G.wϽ7-ɴkz> endobj 5 0 obj << /Type /Font /Subtype /Type1 /BaseFont /NSJEOL+CMSY7 /FirstChar 48 /LastChar 48 /Widths [329.4] /FontDescriptor 4 0 R >> endobj 6 0 obj << /Length1 1399 /Length2 6369 /Length3 0 /Length 7320 /Filter /FlateDecode >> stream xڍwT[.(E&!BBH E)ҫқR^IB/"?߽kݻV33p PpM+ 4MA@(.rs!w/ )j8{S`/($@$eAR@ @ !>@_B½HPh? + dd~T<C.pw) cUOEˊ@pe)7W`K; Gz]x#ap ru G HO?ٿ !!P( A!';`'a$̇@KC*%?%pQW6k aj(8Ek zw??D"H/0o9 Qt @q .0CAܗ(4<! X7<0m@8HTtÝ/l><]* BE- P'bJRb@@ӁAv* PH\vo">gl^ug8?/NiW{G|87ao`!tv}/M3B`._; 7By!~]4a_Y]^&^g;/`fN , `0?R॰`@r8apoMDE(e 80NRQ濖zc0[{=p8%FA]NjTX|7FW_$YE<3.zZ㞪JFeo|9v,k`4T  w9oPcR1k_Y)llxS-#ŷ6xe- xrDŞ΂MPJ;*-U" &L|Ǖ8Dj[S5ĬW}{ŷ{%+#ioNAǣ >R+K:C|.ᰒيqG}V"kojߝd >`CQgj@x6T;\2Zz1݋j3W.k^#SgT%Zfg7}?3}j 3^8,6* :QOpv^ۖ'_l)8čM9Q< `")X[x~Ք pm^4UMxJK/Ѡyrc"mmyd7ÁWYdtO\vZE k6主P Xdju=~Z+#4BZS)l2Ey%Yh6wfyk6§羐 @K̻M.1dBZWL5N;qx+c&%Se=GA6V7<*z&w=pe&=N3ݏ]O]d2m!nJV6_*<0##ZhIysa%rŚQqmM3q~vv  =Iag1F Ķ[fe$IW!Ҙ$c=3tc^ۖi<ʩkA)~[?EFy}z@Ae["AyדO)`y/Aub-~8TU[ )8p׬&M'2}C2LO}dxݥvJ['H;f)0ӫ!jdA'IBڻ#4)u?@,zk -}kuBY8:%m%mq*_87Y~4櫺vV'y2lɯy^Lv:l^:~3/yuxF+uA(sAl 9 QU MTMMed{td+f"5ᚴ0R;5١Um:;u*1|:j fqX(k0Ȥe8P#@WFQ|pf.ܗv .f۹1˗CP#^'~Yt: OV[r-)ү(>cVpN3k*0鉠+"\ aXmϫˈuW1o}π V\_;Z&f#snHIaӃ[>&&M_/:TЮtK&,f%tY{Zx;&:rґF-ٸt>EakN`/7{7ԛrn?<?KsaTKlqpꞿzAx tP T0@KjVmc#jޣJ̔ҬZάrmdwP$#~zwJTf2;59`v+@MXˑz^4;+mML$nI$6TR rUXAjIT;tؗ_#sO$ܺ2622^quZOګM@P {J`|6kv]Tg+7>&`FWUfC) 6ӨQe% |h|PëmGM©"yTw^qGs\ƧLH7'O Ṵa1(bLȑu! X vnwΏE3Wȓ+"Ler*@cJO/nFZ5m$O!N$y2?h㱚H42(<Q}yZhI&TiqQQՀ8ՎjFxM`u2˜utBF;vmH˘@Vv}ݧ42>I~,ļ:ؖ*Y׌G&X6!&s{ ÜٍD!2U}0ZG;w.`xA4WRDȰXw{q=[diHÏL8+Ckw6_566[\&i/ey<#\vJO7& U]lخt}Z-z 4Hwj #W8/UXQl/\@6s..V?3r F>Nڮq6; MK3*)?;ao 5HIk\n,tȼ`?1̅VJ%8עH26oN\!:U[\% ~fǻ &'g}8 NL\xRV~m/12UQ`#lZ.tNd? Pú+43U}![}DI!  ەٺ4_Q7Z9\عۂ널Y+ҟqT*u{V~X61.tۃm0L]@9B\HC|jҌT:dtTdu$1 ]Y" oҤL00Aȓ<ƈCo9O@"4ǂ>4^т+OE3,!7@dzQi;koй{BBj2q*l&  Jg r07嚦;fEmyCc hXcmQ,9a(Wۂ=p1gQ+7lRC MO7zJΪ]^W Zl%?b 9zJu%M2^5XZg>rf'c0݈iL[HŴط#%Rq_7,M^9*\wkT*IFLMl(jE} V&mV-yksnISH|(Euo9}H;1̦|g%IM3Fk>C檈+i%PR/ndB >7gjUIbA&h; =ZYv#Vby5R8`FAl879#9aO9Q{m>Q/la펎ZpnzaV]\o>tP'(P]UyXM\!Fks6HE8JN>B|t`KdFnTY=]p#J/BQRrmb_~׉H^g@Pv`\ft/ѱ7aV#~Ѷt Cx;=cY ;m T: I՞>{JSxxD}ct㧤\Xb%{~Z #&n:';ؐo1;/Q.Xmd lRk[IXoAdzUOy::\?]k(IlaUCᦝTCpBWU,mE3#D~};eB%f6mD!_y0/G E^xty^@p4`o*}ò(d/*&8\e-o3_iZ(o\Y!eLNrS^_As^ܦ}6h2ę9]r7k:\08>6_oN@қ8_-G1+1𙙓eɂ%Nʇ )M }Q_*eHNլ0ױs3Wԧꦹ;{oY+[ 5IW,an99 BzNTMkͺ5:}Y<LvȘff?_vwbŦlHBQ _+QUd"%i{^[+YyQN,&tźxR䢊M>Kshq,/?qC8E6NAp3 C#u=n%{ج cm\{+ʶm!|$iSdɵUuW(Z, >qӔ$8L*BY4:am`ºֽDͷ[803 EA0A~SʥKyoTX=<$y*pN0yw$C;Ikqo23ulg*G  %$o%yNdXsb.ɷ[` yd3nOcţ{{3rDBLy(cm{u9Z,,!=_)N,"_Lj'7?adwkY7&w%{?grA:Vl\1e-Ϧ*zڵ?Uc M5yzkFƙz-.l:͎iú3|+$;qk}B%咭I?Z>xnq34߿%Z`. v[b-B8x;Z ݙ|}]q*9Xiبeo11~1P|Om%#=@wRՂWzv2+7N[Tsiρ'DΆAK}%7Ǚ>.I.bvWq9T.X/>-85bOȰIĂd{{fgY $ij"Rq'9J $CsNTuI_>?r͞vEr6wPT1d=$K:ݡY 4UiS𴉅es֚Bpxc;qRIwI<[Ij1,oF%O%=U7R>LH^hKp8^ŎhzG,xpx>3 \@ٗI/һ@NG6nn=cd9їRixM^%P{(\&VLN-"@/t: ~uD B0Xے3-o2fn.s!Q5k3jDϸ,CĽ?C+pQlk/)Ϳ,.tR)pDnVaǵ* d+6\9Wq%#)qؒAݢݺ+YkdAiLzP5U endstream endobj 7 0 obj << /Type /FontDescriptor /FontName /XOMCMG+EURM10 /Flags 4 /FontBBox [-32 -243 1060 720] /Ascent 691 /CapHeight 691 /Descent -189 /ItalicAngle 0 /StemV 78 /XHeight 459 /CharSet (/p/u) /FontFile 6 0 R >> endobj 8 0 obj << /Type /Font /Subtype /Type1 /BaseFont /XOMCMG+EURM10 /FirstChar 112 /LastChar 117 /Widths [589.6 605.5 432.1 455.9 416.7 642.5] /FontDescriptor 7 0 R >> endobj 9 0 obj << /Length1 1423 /Length2 6576 /Length3 0 /Length 7542 /Filter /FlateDecode >> stream xڍwTk6"J3H)!9  C4Rt4HJ( H J ykfag㕱AXCpP`!@A"vv}t!A nt`ԍPuu A P?8@hTp  G~p@8bb"<2P$ 4({͉@AQJrG|`G>NP( j vAGз!lQh0 Q8 PM+SQh9A8 @OtEJC G'0-Eu>;r; nn`w`|/x.$ sW.+mP8ʅW}0$rv?}G^ 0/6Np+TE/?:;(  L:{_=7|NP-PmD$ APk NO5|3|$` lpϗ_鉼1de/^WPbA Y쿵`_IE@` 647T8a| w):86sf#/W(Wjsuo |2p;sQCma(7 C._7 W/͖A".7m,ѿTC6MF"DR @/3@݄\Q>[ET%a1ͭ|s*? . "(U)?+ uBoZΫdy7> 5d{٫*;Zٔ $iM{?v7kOXNTBa(t~l`I _#?a"21}o4},!Y9;'bG@)KTL]HUTY44U_`V$,Lw5$\ʇ!T,/{U\0˂N&; jJ-p/.Vפ+EC>Va:why_ _+EOp@(N߅.%}{ }S^;WSFV|ABs%7fR x 8+1JTpB`(E1z=f * E.PEC˃Ek-XysN-tg]?/!Ѭd\|w. ~)Òsλùc )nfIt_Q\}^>5M{rl~%Uœύ^[wvy$R A%hk@aVh뙞bf6l)5:]kz|:="a1KO nV@ԴDbCVl y.E(< :mX3 dԯMe4wbߟX_i }5RA p9+ p!SDl{ I9a T,Nɧ$ت @0uGjd5-R5/$:1s w=:o=f)`l!ْ"nF<89UZ؁K?_XH6͜u*á MX NBrb,K=JqO}Mڷ, XunUѻyYz6կ%%D@P &yNf/R aZC]r~[*8i#vwWL1%>FUc?%D" /N="x7Ws]O-ڮeLZ& :T}l0XefB7&_D] .w" 8O˽+^e q~Wc+rpfC g/Md. 8gH/tWVkT50Zꓚxb2kjdmwS U1(tWJ16sHm$Ԏ鈛+ (+&|uIqI?QV5{ݗ:ZZǠF32MvȤ7z_؃(/Q |Yԃ&)hR#.}JE"yUÌy0fԮvYnʛE䎖*+Tu;? -/^FT8Z6:S9,>W0񝮻~<OyXX/<[ko%FfFsy[*YM8=Y,e!uY2ZԽ~L?k JNc"= 0P{Im=- 5/ZÝ=MW3-òg3c7-1wXXEt`y;Co[Jm}/6zE޷MP`=*Y-atƪeLW. usuPX(^zvzL8d<!歰M5s*p"Nj s $.Dv=^lJ_/EvY"=;j [Է\&,0wdeʶUUỷ%ӝQKn ՛ܷKɓDKbgn5o#zK̫~gZVcI!1hru-xxX B]pj#I^I~xnC0pnTG?R=`+od?fW4 B @ N?_=С*kJh5O itzp c:+0$𢤊0s_$qQO8|P]tsTqAjI!N*W ˗RbŨ x>:ĩSom>f8UA\'ߧrq{P:@ztJ9'-ͧ\=-u@R=VZ""O <N'.v_1c!) {+'il!g[hVVp٭Mj9 CW6-q ߲PZ%SVUQƙ~0[,e5A~XY^W$op{ n z\#FUz?Qׇ oMJJ| -v($NGQқXR*y[Zd7/4h[[˰z5A c9}4γ2J!ԭ7?[ I}T=;#0SNrkߧ罢F1۸̿?줊]p}z>Dh1'pnRڠ'/SƓ<3c, ?sYiy920tԐyZU֏a"UG^@r;ܝIjIif !2#| ǚ'%|" sO%7x6`h0m&,k~F(iԉ\ZLifa'6za{7{>LexyKʚGVή| o8w5"9}YCиknF3o:W(ȕ9GHݦ}S&sܮ8F)n2CÒF q=IF r\y=\qj`MR܈#:Z;u/wa'-ˣGW!n}9o ӻ :,T&L}E C#qQCtb%r/);дd/vW!ShGx^u^Ոm0F"2EEl $釘GU$18}֗ЍDtߦLyzsZHTdۉIJR[~8 6n&rQqOe v~U9?eQ}MޥUX9xx>a&6dp]}z=ց?M{-q.ɸ%O0]T a y϶Z]:ReI %3He+ ie[#v onϕEarT^LBV7{~ʤK˼A􄘑6sA!٤軱}Wg6xֿ|8t9 qWL6<R4D*=wϙ2#ba32''Jilq3`J%3~}> u&+D[QGAmx~>ˎyH̒M]z/UW$>A˜r밎mcB8*)i=m/o>]C~RAJ+ Iy۩L=jRs2~Iiz#?Re|]>AЬH{u B R.+Q5U~ђ ][X.L3J@l*3ָϛơ)pDǿOHǑz_J8++djwֈvz ]sG ٛH(w̉ W3Wn ,@EeWQJr9-d@:ۋ'ՎT!a9b %c~Lxș5B$PkFR7)궹@=Bv9Lє'vY! ;G4" ^zf2C$tC4x$' {b1|](jCSZsπ%L\fSw? 8vh^D< "Jk16{za4SϽX<1fU]x\su}6-190wcRSW䡟?lĉ@V.+!(ՏsU49W+iw5Q=TkKZa=mjGRi:|Ňˍy#+rd\ Ͼb/OUUu4}~EM\f{\*f=p:`xn;`kD$pKNvqWYF]Rx1=v pqÁ4vhtҮ4 ְ'._ > endobj 11 0 obj << /Type /Font /Subtype /Type1 /BaseFont /GZDBGX+EURM7 /FirstChar 48 /LastChar 84 /Widths [625.6 625.6 625.6 625.6 625.6 625.6 625.6 625.6 625.6 625.6 374.8 374.8 915.5 628.1 915.5 0 694.3 931.7 801.7 868.5 997 744.6 625.3 925.9 946.1 506.6 515.4 816.7 692.8 1240.9 998.5 968.6 712 997.6 749.2 690.6 617.9] /FontDescriptor 10 0 R >> endobj 12 0 obj << /Length1 1391 /Length2 5976 /Length3 0 /Length 6916 /Filter /FlateDecode >> stream xڍt46"ѣF{N1ØafDoADD^"D -$os{[3Ͻp (9"HF@X$ )[HT!4aпĜfPDHBc6U0 C"m/8 , K KH@$7T0G@OF"hbN RR%w( #=0ꎭc$# !-$#vG "Q< AP7ECP$L\`H'8 E!^G( VtP_`ݿ‚J'W"w0A{~03C꺂_ ?F8h$6 X遲u%Cey`ЂhG_imVC8 ݡ Ta((w??uC }`G_4.x8f^)Cr^A 4:#gC8=53!/՞/ǃ1៬I|Aq,EOMD ,陯P r(o<֦ (~ 7#;+y\nj=؀D@.+TV{$ 7 \Yk "85R*8`!'|o:XڷwLx襅>Q/f\*j^Ms1ƇA 5>t\]aU|GW\#_`ܭ?IwJ 9m~xޖgU_Es@>Ba*2Mi_KNײl^8;|bi[EvRYmP8G!v#L OV%wrҍ7 US,3mzcs!b}k]`.V'>`)g*O%'Cy7]+Fy"vm՞F#"8;̍i'W-uB 4PYGj5gOdGUyjٍ-c.yLZ{fz˚%k)MD:}͵(aDQ敠SYe,K&G)(EҌD넂<{Yγl:>ѵ(6uu2eScnBmkc%-_}$x@`_ʧ }hIn*R&TExdFL>* c(GXn=vn>w"'H#mI|e Rr?9ץsxIt+0 W]2"b݂i> 嫳"[su>?իrHHˋ\e8|e̝x ''v3P^y\pVuھ0ANq8 ݦenߴ 3U{*V/67pnr)Ɀ\#i/쉤]{y;m[} ¥PM/^7U_r|[GuHjkͰ{v%.i Ϩ!4l3ޓA<'|GVRU R(ds v4ףL=:&˅3|ksH4WwkV1olV7prKMo}Kn_6/ȅJقL4D8qV/?ڶn-؁m"-!MvS/|)(&\@%1 kʍzU2WDE$#jC1WK~7 XvK6 oU&NG{]&t{,LeG/n`7VltRW,xj7g3b wx[= |Uۻ%4sq ZBnw5P_/7|蛨{^p28Ks:q >ZJhՠ (qWѲuR9IʹoSœZbvl>) c?ӗE1}3hYf-nVhHT^&.ﵶh}ERbx?&$jւͻkS' 4];^?71 h$:MjRÕ-/3VOn4˷AsD SF.|-ύ8&4@ QgT;s qroZ^wNZ=YG4x@KRǶf޸B"Rĥlr@3v mu,ks:I8vIRe~b +LoO6nfęǸ{JrrǨl6wzT]κ2rCdHӨD^I՛ؗTSET0bŌwҼyOH:'ʄ8Wڎ?_s7h*ܫpk1|-Q"#P l>P\Bm_i_)Ӗ :ehh *VwIT8avժ#""TA2&&kӏI97G:/ ?͑q2ޤioq~^<19h̭m/(3ܥJV7Ǽi#g_7bz>YSqXBo5&MF>[Qxgݪ8;yzN>ؗ_&oUm`KIj xWEUfVgg/O'6{ϢYe~=(CXMu3f;g;]RWJj?.~ODpn/ WQILjsUF'>?@M7dmKoW3HqZC)wгMSv-]gCC1]鏼 #\s/)o%Vdr@Bp O$IU+PIOC#Nu/2-~s+¯'?>,vF^#]G>W|SS&jm  UQGf*x ,RU踍#GG8_2-,دFRJg: vHo0+THׅs"ٗ=xZ9%bmȲt1K[x5n }\Ͼ݋J]ykq'KF +qBPcY]3Aǜ'gH;ƚd:tV\n32MiҩN|$χ[b85<] &!8TFM:_Ğ3&|4-#*xv^v+06I@zStbCTf ׆|nKM}(QŖFF`2a]2FM#iv949ȝhܦ^g:7wjPCef̤KY,[3K?ۍٗ?/>X : I74:.$D8r"}:hoR?C3Fە3p[XZ k(/3mAofy54Hn.z<>TOi:'#_B_LQv2!|@"CjZ#xF:^hxn%z WG3-'5x't'(<p]ӓصn_o TnpAPu#8EW?tbm^cHG)$oa V 8;@BsmE;2Lf#WF(4S4V*eGڜB94%- j }k@QI&CD٥>Y#kfŽD11†Zgϓ<[kސkQH*2>uNH*J$z Rո7{h6 rgNG&H]ږbmy;68U]up1{}Bߣ7sO!ݝicumg7Y#{֖>%-}`U4󍏡 :> endobj 14 0 obj << /Type /Font /Subtype /Type1 /BaseFont /UNRHAG+CMBX10 /FirstChar 67 /LastChar 67 /Widths [830.6] /FontDescriptor 13 0 R >> endobj 15 0 obj << /Length1 1496 /Length2 6782 /Length3 0 /Length 7772 /Filter /FlateDecode >> stream xڍw4ӾhѢE;QmX.v !j-J ѢF/ |}g}f暙ea瑳C@p?/HP /$bE X !H(.;)Q7@MP@"jhj8Ǣpq:8nrܿ9; 4(GMG[0 GB!(`rD\%x.H^ 7E9z$b h] Rc ?{8`P[y7}U @s8?/U E>P`Ame ^7~0$& 67GrPW ͘v u>E;f>|^3˲Ѱp{y@Tܸsa$ n֑WW / ?W+`C! g??`E6(?o?wzf_,nf|}|zںr\R;(/xDa .. ."۫yT#?8 / ϭA q#gA7_*D08'v|Du7>kB.UEoVD{P2bE:< _o:߼U7;٩T"~-vwn& , l>^8u xT\ra|[7B`1oyF\a|濎o~ۿ%v/L!l%ÜZ_z a/F'D X?9j|8&omWA53]1:O˹>m,,=IBJ".5CIh§ ɂ]hyv ltNt<11n5Ơ1O٧há@(sxI^:1KzgSI%Z KlHc&zM HAIkŒD~JIi+~NwXV(LUri/c. IV:XhCuSRt(UuKJ^tlko=(I_6|o mWM1h-_?x]׿SjA/C8bȌ1`|^PCoSS*T]S^w] ; 2[rJzWawj Yۖx,>ge5r)VdY n)e:W/.Zi!MUc9^{EnLdX֭CicR89ζgSr%O Wv ,p# foز*U;A>}ڿKA3vlIj3^éG,et©sڪƨ_9g~Qhƴ.'Hv ߻ ;0w{CmrKٶx(jg}JXݚ 5QA=[E$ };k CP:+x#ЕnF愬ȭKV4s7GAcXgTNw־M;g@O_$Cx3trsW coo2jC2E(",y$nu%G' 9Ίp4A ;)z}|'>g*o-G1ȿ|B1Vw&ǕqUvZOՇBYTFf"twoC+0w 4%{$]71ZCb~^ ` Z{经OgKا2IF<0 zj©dgNw^qe#|xʹO7w* 5SAV^MW?sqE BMrȉwͷcRWQkx~_@Bk$YES5f\2ǣ 2~=oGg/Tg;SR-F'k,T<|]:cϕJvH@AB 4^xmxJ"&(5D|\j]uv]x)1=a%uZXG=! InrBE[:TBXY >J1;@njcvmZLB[aXZ,>ʽrd5:c^r0ʨF'$9b7E5zTgF:۞~ɷ”LCidxN<֎1*Ry ꒔M)=/NPG<G[*4t9!X4IlCxZ Rə; 1KSw\[ѝ!$EoĮQ{~`w5nNMv@ Ҕ˥^QT̼E8u>9ڒ.)9k5 _7d,Mt"\9}W'p6 F &Ҵ֤O XN{k+Fw e2c'?^ ⬬[N5>0bՐѕռ6X/!'ځHQ{i1f1ݙ#rMx 0d[ַV0|>Oi='Tjs#D }G>*UH)!OJI3ֽ"dR23+i'Ey} SSCl& a51"Տ}c㦃gxd*S[%EgLݨ@F5yRds8wQ8hwATkWEP'5<4{h4KLf\6ؓke#UlwU3267"J%U"5,u~?w8jf/TȁV}UgD+E3o`=9PCƽa?eE`|o7<|W07:͝ zI2h{kStJҌ޾_Dp~(j$Npa :M9r$t Uەwf9|+Gf/6GH.AicD~,BYo^lyJb) >}ݒО@1MV-S&oM 3$J|RүeC)Ye") JacjGzELhi6gBBNLaV~TFBF+LMd'-dwijB5jҬHYEQjǻt~'E<պ/T 1L&U5%Z؞G}辧ZlƺzWa< ݗ}i|.$$Uz~ rN,w՝aM@r#E14gUsRu!qEE J'3ŵ9G.VShnJ Tzy1|N LA-f%"odNZslUgq | NhKk _XNU%f^c-hiԌ o˓cn);j/}MgMϮE<٬b)yXW!{wGcrnyi\2IFѽ?iE-R &$bTg#%nwFtc˟t*ljx&ٌDH&vm_>~y?eQij[Ut!72sbVe7r'Bʓ"4V0Ŗke+DglW֙Ư5&c`+  ~JK4hT'.]TNM1W:j/j:)N+MPpRfō ї Bt p]-pkluKr{ Ě<1K] kuU}FQ+"^Ymv%WHSOAI}wiiVfN]n[i ^$q$NPXE 1,SGM闊qdTw,q|=#+zzţ= K%(=\g`޾]rq36+4uJ'H H®MzksdM'7vJ̛)OG){_Zs(3l=d*[T %PpK3{VXaP%s*Ab'l9Fjo#(|([$K_0uri/Ft'Ʌԩ`ZBNw|nfXm(`8Nh'nwr(H&sHϫCa6 k++ނ,;2.KJT~S#K Bjɦ]X8ao2=#s4zkEG{;UL;&7/,TY Ή[Q΍B1+6LtvbYg/ n*:Dz aHW#iDEC-Ř\lf-dh;0 jWJT$g&5py(#p0'$^86i𝬡u_:Ҝ7R'gND> ^Bn^Izjpɛfc"5P+з[C ~HgFeƢ6kHS!rbS0EckL"- $^֫z\G}>J$c̵)Hg2o/(j <hqWzM]>Small|N}Z]AFc$6`|#4jblO!+bըs ð`[Z]^ZlEn2r^4:`-~h$\R4C6'uɈT-!}-wc ǘcMuRAKTS#8$-D j墳ěiT8i;XBCP8hdp끦p\ P;:r!zEnv66ۥt^RO|z1&yaE4%_}]%=Z7hl>ܴ GN:\Qha,mv?^{a[sS!Vb{e9nPd2 v%/A)Yw D6/3RT]閮FD;W;I<HZNЀ㔹(A'5T(_zB VǴ9$/al荡 &dEâu2d 3Lyw#9&4# eǢԨF> +:Qq_ $ q?,C> endstream endobj 16 0 obj << /Type /FontDescriptor /FontName /ROQAZB+CMSS10 /Flags 4 /FontBBox [-61 -250 999 759] /Ascent 694 /CapHeight 694 /Descent -194 /ItalicAngle 0 /StemV 78 /XHeight 444 /CharSet (/a/c/e/l/n/o/p/t) /FontFile 15 0 R >> endobj 17 0 obj << /Type /Font /Subtype /Type1 /BaseFont /ROQAZB+CMSS10 /FirstChar 97 /LastChar 116 /Widths [480.6 516.7 444.4 516.7 444.4 305.6 500 516.7 238.9 266.7 488.9 238.9 794.4 516.7 500 516.7 516.7 341.7 383.3 361.1] /FontDescriptor 16 0 R >> endobj 18 0 obj << /F13 5 0 R /F15 8 0 R /F16 11 0 R /F27 14 0 R /F30 17 0 R >> endobj 19 0 obj << /Length 1057 /Filter /FlateDecode >> stream xV9 WHb;+VۈjEi@>k`4Iq.Pw )k kVV8ϫbnrSKr,鼓s$2ptVy0'Paxhnǐa jN+ n5[|XICi`TXhںM7l!N?N(Ζ\bm#"v9 W*5r-Ƣ7I"a9]};HRScKW!G {y I  Z g`-a[H:JԂ52.XXS GnX-U;IƐfeXcnkDzKSK,Ҡ_^>ȠZXStxXn,9̒KfD.s2oÆS]v=*+Dmevq@fꏺ]XL)l#L P{n Ъ'f9(VAK%`-!L!Ἷ3dS{^퇗oUMYo|Zn~q[(A7A[;0F+r~Q(^O7A[;k=*|/A[o`CUGAjw-zCA.n7*jyOfBQ%:C !4f. >PXbAFC-zC@7#$,D96=.@ ync&rOcC׫G@ûs _b*ıfZ1nNJ.!Lj(0,u<>)V kmh+GG4RB}N\Hꤩ9ǧÏ;yӷ/=ey.wa=H)0:7Y| qhhH>%i1k8[ilus8yvQ >7qNGZi*||bdռ0 JQ]? endstream endobj 20 0 obj << /Type /Page /Contents 19 0 R /Resources << /ProcSet [ /PDF /Text ] /Font 18 0 R >> /MediaBox [ 0 0 595 842] /CropBox [43.2177 596.777 278.267 840.4] /ArtBox [43.2177 596.777 278.267 840.4] /Parent 2 0 R >> endobj 1 0 obj << /Type /Ipe /Length 1020 /Filter /FlateDecode >> stream xڽWNH%YQcDlf?K$T!j'l }N9q0g!!?:S7WyC3Øchz2R!//ݪܯy1w1\F2_;Θ bQgwuY" a(}p=s/br5;D*6Sw9\ YL%Ex5م-ڄ.~U?&ެBٯ:.jںE3RE+m(X꜐᯿\z24ys^)XQ+I7D{Kxb%anlЦ s.Yb sW p*eS#˲mP;yZPC2p ةSa?)ĺ1&nx9Oa0{,O% GTt xרFzv7eRJpZ'j9 4&U&EXzפ?4 W8뾖rlE<()e|Z)OM0d7îߓZݡO0y'<5wT<78Wabɐm؂Xʨr\1x.-;FQ%d{.OQl199z/@tje5&QlVwO"8d|fLMcWXC\3~]-tcO`'!I=O5(?n~4]R?/2 endstream endobj 2 0 obj << /Type /Pages /Count 1 /Kids [ 20 0 R ] >> endobj 21 0 obj << /Type /Catalog /Pages 2 0 R >> endobj 22 0 obj << /Creator (Ipe 6.0 preview 32) /Producer (Ipe 6.0 preview 32) /CreationDate (D:20090218080408) /ModDate (D:20100422125909) >> endobj xref 0 23 0000000000 00000 f 0000040606 00000 n 0000041710 00000 n 0000000009 00000 n 0000006996 00000 n 0000007217 00000 n 0000007355 00000 n 0000014787 00000 n 0000015005 00000 n 0000015176 00000 n 0000022830 00000 n 0000023058 00000 n 0000023407 00000 n 0000030436 00000 n 0000030655 00000 n 0000030796 00000 n 0000038681 00000 n 0000038912 00000 n 0000039164 00000 n 0000039244 00000 n 0000040375 00000 n 0000041770 00000 n 0000041820 00000 n trailer << /Size 23 /Root 21 0 R /Info 22 0 R >> startxref 41964 %%EOF trunk-2018.02b/doc/sphinx/fig/shear-displacement.png000066400000000000000000000411011324306050200222640ustar00rootroot00000000000000PNG  IHDRko# pHYsqF vpAgJAIDATx흽FcV:`faƀӁXXPb 0**6pdpdXFN116{w*MV&z "}V(JSWA{t2A:2Un@!Q{FY*KD$4=ɝ?HԞ22= WLD4u}AvP$Ө7&c>lHNCvtFO:TH;%M} z$ y DMA& Q;GY9H!/ t֗)P9mC^?$HQׇ-[w(M)HgCO'QD)um\n2q>ώE]UsNK4_s'IOQ='෇C|n^8LR.뀸]uM[D\B_=nWU=X }E 3?&j~k:MhudNwEw8l[lˢn~}ا4?k(7qxTu&uUiL}uӢƳd19EEͷFx/x2W܇9‚a[3RwOƧED=8 붿-La0@ݬry:0o2cBbxx/ٻ!8yl])ik_1V, HEjk moLs<~&ff~0KIv[L4E^chU>ҌBюM,utv'Y78C"ХcA?a+.Kƶjӻp:M,zOf7Cw/Lf8E76p5zofY[nӐNc& '[h5$MC 6iݶ`5V=m*BԚ,17.j$1_֧ېuQLb ͢ޫap>w3|BZE l;-Wc{xx h~%rz+q%Y;jME*۰c&=I65̝z5QY8٭ 2gaA4v`trQS'v$Ul?(5:qq丙vH(5cYD.:(5͢Lq/OXMNDOf&B&P7JDP,d+Y]<}29 SlozP9ϣ(j|[B 6=߻ҀE&ql5v{vyR/L6fzqբ$8-jQ}7 6M$:Y)jڍӳǠ{5ibi6QjF˶KR%5m -jdP@LGOҢxA5qb}lOuz0+6!4뎻s D/J{7?..|󫯶"iҼ]tK PhX =Q/OQ0/^ؾN[B|*ZO~8ן?}-pM[_~XtDC8ЃOؾNh,s/:wFƱy.on-j74Zz{[ﰻBS4]M_ܩka`v矱BFaZn@^zW /^ܿqkvk:*1y絨CQOݵh a~pL?-un.ϟ_\ܻgsw"mp5+)E ۻke$z~y%jMus]S{ت+QM֧NygBLS W#A@]۱k?crWy3xJhdrO'EYÐ票qt IOq/0:$N/F \^N-|_c`qw?*M]8Etם# uvpԁIwa)#-D$>8.G MfU.Ih-j oЅpe։ڏ?Bq7Uѵ,KL}[U1PY&vOC߶k2`nt"]t L+4\tnGF39c:k Q/0dO4UdbJw8N۟2۞[r Q/0tP Myl},だ]ж*䋻~)j.URsg`4B28.lC^ݛ]*3x{dRԮydkMGю`۟ HShPqjtsxj G/kOVžp7R$!`om9k;03]5/x3{/j/D%Ԋq<QkUI,Fޗ/rzl`Ya3MH<)$n"0uZb4l$WddǩUlc֍uSJޕ>l.mU#er뱨u{7(,1Ee\Vs^4?xϞ%{ru4anV SAuDawX?mWb*ZS%~vc0IEu|밍zLNbz"^fA:c:hk/VuU]۰ɳsKQ=fKO\.uí@gӼާ|jK:t3ݼ>oVfs"rOƔ*_]1a:|p>ij=IQ/TDZNݯ} Wj’ ~N*XO{R?~ղfYz@.ڗ?{=+Y3#B 6nU>n"v4m?>Y/jEl A݉c"[皡o@| ͓X*?\u_ݖy6܏ z>K fmO>iڢ&-Г궺'>6%LMVs`/D5fAmk?RyT:܋ģ>6Cvm,w/ n.UE h3=gОl `Qtuˎbߠ7Owp\+0Yk12V޳G[>#8Y {WZ֯n+udSr #."eܤzck&V֯3`&FA È8YsA`joE}"At^1a9,1E6e6ã|i׽1[ߋz_dc^_"<=EݶBڢF5؋N_Va p'#تţ&.aV=MZOׇS䔨'Ԕ$CwQ}&laT&>GHkۭ1&O"1̐:|&Q"JptWqrUV=ӚFk? UDӨڱ幋11vq$/ɭ]؞x΢0e특{./)|o}~'bwvծBE.kym.k{|}1/_;IsV纮m nF=|LYwzLLDV^ 퐅|IYN6g}_6󺩲?h\ҍX,k2գC}M6C6f[#x[kԖSw\v6Y >{ k>k":f_5NF}WY| <3˹.pMń)YG<  LmE6MLi.}Ў`iھ*PSN撆{ΝFҶ{nlB17"4HMWY/l,:kYTez?.ioNkQ=b9"jX6vI?Ө^*X۔mLnm0/y;cK%+њ6q8toOs<`y`|}1Z#YwE԰|ZS@ʯ5*jU.VN)iZcC vT6'0%Ǹ0RA4:8͖»״hiME|L#'pXyO8ǣFBsYLWCfMpjcwK1Ъ*;w_w`Jn ѷ@c}mLEqU6~, 9t84 L}{-ouA*h^ߡM Ŵ^r=' Q02ݝoܐ\N}M~`ƍ;wy^ӴʭsDhwB?eߒ"Ia8g]#]ӛ//q#Mg=wٿ1)d匽mI׵-Kku4.^/`3C0k_߿[o^;0wZg809u] t{RLLڵ?E_|r{ Tkq!FqUa6tٝh#_>ӧ]+Q>ys6S$qɉ/Ѡ{<6vd۷1y5f_52ؿ e/,2ݧbMt+HQ4R.t:0@vw/fFMpji2=?3WU5imwE}:9N+)oW66'+EEH[)1<%s|ӠPw)f5(09X?uXn//ic؎Bb~rseERkՊvٹ4k.})ve#pNzctP$LWm}-wۡ.}4;6{k!q1童2C';BUױ\!ip*nPwj[KR4ʰtݵGVyĎ؆?mluD vL@e>dݒ.xWZl z^{,[|hr af4㾡%Gɲw1KfӴ1$GHbN4=5.{!Il0 ^~lomRnm&]Y~BFm\pxk0"oJv$m6EUЄ Eze.-l[?M~.ʺ%=qiE˾GMug'&}>= ֈ6M8<)5Kㅞ0>+VjFql*R<'ilUx{|ի27#<;A{4|/:"_|1 us%.UKC]*/~wZa,bԑK45 J|{y𻠘o5 DdQ&yٛ\2HHwRUqF.&_תj%7b+|M [{_ (ۜit'f麉2YÌ >9Yb um-8CHZ.jf ]t9KktoTXLe4o:,T~X ̇9 /yŞ&1zzu(z]$O|z<% 췌 `VuE_Fu|ptbjY^n& ooV[-C]DlXU߉c#CCkCpp/sgK5:[.J*j;癹1QjPsF8矋a:guk|.KgKOQPDnLX5slik4{KLI:M7:нVXZ88d3\} Ol~onLŪ8UN(%퇺flFT.Pp6nn.]aű0c.80CgkfVpa>>mriӧXUbk2|8eVE ԙ.duU;NN_ 6~faswX53!ˍD5mP9,a}^4*./uO&[У-S(I\I}:~BBi?x`y]+r`ivL:I"lMX+<\Hqd-ȗV1wp%W<{v{gDH[Ū:! #xIXck :aZ4s̞-|* 6oÐj[/s6l9!j`ֵVe# c&/9s|jgWW}4?!eQe7|$mm}{2Q6\,c`Su-@Q7l BD{ vʼʒ:a~VKɳacçėO>{չVe-R7)Lö2nrm1z?_r%[Z9 XMNyQ/D5C⤨vIf;g lWӾĵ4fQi_-IHT}8e]{ި_ gE 5-E5% և]Ql^\[(3KfIzݢ΃/Æ-'`#kg~mpk*zv+6LwZaZ/W`}E. f/;;|KϞ]\̿Kz1$a ݤpdYtw{]eEqoa_ndYx0![llOU#]Ze,׈>w$&͙<,/.ZIb*6Fm AV (u5}ZkDQCAϙ{NjrƪFdE5(>޽2TH!֏ƃ@E>q|P5]6ʲS:Mf3s\cFM.ϸ0T08ܦCZ2'Onݚz/4m;]R=5+Hfn/A! γed.)0=V1.ꩻ"[œO$ó?OSEp{,hҷeO0 cl71]!xqu11߼D3*˖x:|{.\iVQ듉7i`{cѲ֭'O{pgX`jm=dAc,g~辂 U_Ut uI[E!;v :6 [ fT6efo;s{Q?Ae}(\rɘŅ׽\.VnK_?_ k~쓷f/vcl_ϗ,z3V?I[r\jW8Max3{ 8Ȉ^ץx`'0uBJb9~Lmb.!jPص^22vKF\fIsD˲2.Ɠ'v <69陸61]w`.LMDv MX+FmPpe2|nO,`GrڱN䒭b^%(ClԿ 5cY`dNy\+**l#X51Ҋ;+\KRibR/YX =a(dS4);6`M.I$Ҫ xnDm?inM\.K(tQ~ގHEv;|)6Stio;r3ԛVdbxh'%JHpdbmA=`~lɽLfHGb]ã{~D w{O>a'x6jъ}%tOZݝkOW\$,Ya)س{$n4EO1Yr.ױ.yiݟwn"m`}>5g'Ź.ag1H^9Cecw%~K\2٭Z\TսÒA$y`ʵnM8.غaǢF}Fos4@Ԕv ö;>0\ov/7S0I9louv }{עޓrɾbsv ;uXvOsQ{jϲr>sJ+mY xȌ}QvvBD{IҜEmɿfd(Zڭr{4]Cf&Qns4}]`̧L\2S{uXwݻU?㧠^ ~Ny%?\2~V,Vmv c7HԮzQgi C=TIck;#ָM6=OpȲ;wXt8LWƍoAd+eoOgz<{v-qeJΝ(zp%͞j𿲓bl;q0[_;ܾE߹YݙaKwOhwP`/?u. g!Q*M]uX(L}]Yc٬S#Q@ҨkR7!wBm0b[eFf>"v O-xƒ(8Zա N=nZ=!p(%QH5jP{zWY&$ߜN*:$..5f&QPy6s6?ԲA!Q*XgX~yk Qn˼'}#.o.A$Jք[ kVpZi@DNKvAyNY6VG]KY:zoaKu=˳te*NFpX+3!QRU*ƼQ:5C.AĖD=ug^b{|kɤDg !|> ݺβ8L^Ⓥ7?SױmzIVNp@+m'zu O?d-rYަ9ēF̅ΠL=V}!߼>oeJ!{zѨjR:u-4A]^|3㧄lvZ6G*e 7WQAga̿B͡}QwW~̯5|z- lflߧ<2d]$j!ժۢbn"y8*1'z㍷߶}HJP{-8nfhxx4t ?XG/}v;eGV@U-/ eYvXIGDe([\ްtS 6}8Q{{M~Uk,EoKS8wozybTRꏨͭKS+EXb.Ln[.-$jIm5{O؞V) G *-'Ћ[tɶ,W-jJۏI/|]a[‹[t j jh] E|w>:M}[ܢk2BVNσwT|zIM]ŋGl_#$jixޝIlW6ouPeJ6x]쏷D$~?+(qW)V1ݑWQ׷o9ąT M̂5:Jρy<ڕR}W ۷B=Y]70V)kpA! WCE z)s7BjnQw[{AI +$k4Z˝F]dm=&Q+ $k{@V/]; YŰ"$jѵs &k}cKAZ ~3Ep=W$jeXI"rA&z-i2Tc 6w`Q/uϪ#Q+#}7w62DM /|{䝊,>QILgJ^Ȳ(2]@&&!y,ODprp2yׁIxW Qk p0 9whA&B{Z ȥ iuCRH.T[k4D)eX|W-j76+$jM\LV$:Ιne Qk6GS$cF Qk$BUXAxt`@&Vp>cl/Bl).S]HJ7cl'Nc3_6H3l M򐨉M 5;X]g5Š}8* Aq 5 {\4H$j7@PCQahue[N>|$j#$~ev$x'Dm89eN>ٸ .HԆ@/3s.;IGiڏG$ho4''m>$jcTU8U6AUe)^$j(M4oK۝CpUiz8w:0풨F]_ "iBIeVŢ`ׁSE7 Qi>򒯑fdu>cRA\1}0!3P{<tUQ4oU cn p!B&hBa6@C sd'0 QɲK۾>7 QD`PUD Q[=@ Ӯr Q[c%Ip$jkEcύ}Dm$E4xc!$j 5,VLa@J]?41H tⴏ5 "0H$j 5A*MHԎ,}Dw]Hb>Cv72 ,7bCtQ?$j@U }Dvu2^!Q;FYWxMxEHG5N:B $j 5A DMAvƼHRU&F˒XAvsJ!iGK=!Q; W3nTpj!Q;3ynG߮xFaBvQE'N z$TH$j 5A DMA2$j8%χ{ Ci}" 5 $j"8v.B?$jYWk;mCDkA 'u $jos~ Q{Hɭt Q=A& QD` "0H$j לӇ9 k?NJ sx{753$jϩ(kÉP Q{ϰ-?57$̃(t!Q@8Mt:hM4 "0H$jz"v%tEXtdate:create2010-06-12T14:42:37+02:00cE%tEXtdate:modify2010-06-12T14:42:37+02:00E4tEXtpdf:HiResBoundingBox235.049x243.623+43.2177+596.777tEXtpdf:VersionPDF-1.3 3 0 obj << /\IENDB`trunk-2018.02b/doc/sphinx/fig/shear-slip.pdf000066400000000000000000001561571324306050200205720ustar00rootroot00000000000000%PDF-1.3 3 0 obj << /Length1 1460 /Length2 9191 /Length3 0 /Length 10176 /Filter /FlateDecode >> stream xڍTk6LtÀtwwI3 1t7ҡ(HH*Hҝ4͇soZ3{_{_^0krJ p#Uxxxxxq (o5 xɁ~p@ "@A/?pbPa`W&Y p?Vk6PXXO8@ =Dk ^Up9r]$8=@ vq~h!aC\m 0AXan0 p@ Y/߽JwD؟`5 (ƅDp@0ߎ +!@AV*sv8!\\!y o6nN0XYoοuv`GPv=r18 ~[+ @|X#V`; `ۿ@<&<x~d@/8 o?VRb l22pOs'/?d~_ wqQf yp OЀ?p `7Myy 'ߨ)A̬3nP?,] :Vea=avhAq)0k\\@^8> {6 G<l.8'*,V~Wm0yb=UC 0l3; u m\gZ7|3v2#xLMǓUyS9[OM׾7:M83#=;i96|o} ^6 Qaqv#R]:?>Q){S:c~)*sIl0{N%o7x7r{k#19 )(V ٴOqBόgX!]c Ox+!:ѰjVZ@( UZruI &IeE4%fVf̠-{c#vO΋_{86^}]m3|/-Im|&oo0/N1fV&~1F&KRc,މ<@ϟ+ίPuy`Ahr"qoa=eF8kn|,ڢ5ډRrR./:_ϕ;ק+L. v9jdBºlVdI2q?Cs_fj*JGNvb[=ӓqw7Zl+(Ucb)UV5+줨 "}uZ[Ӣy(bS6E0A4Ҫ$ q_M|/"->\PNji<J.ٮY!$D)s F@5 Α)Ihx v =~F2Vņ6Zˤbދ5m;|o='t|;*]RX0eb M뱐s$ͱ8bB i@jCp:M_ FI Q^3:wEN;uoMd|hظ\;IŹrc^ hJ%#uKl#>K䯧L/:(.i |稯3ƌTTs)~UE(fxͳEwjPbd^o_}L&[6i#ߙG_RbQL32ٽ8WHp"1!,Ye_$U_Z>azh(g,Y:ax$o43h֧ iM Kzm 9ӫٌURw׀1}2R'W{X` )[2>J%-7 EU qqYZo,ae:{S`bvP6閁Ln`8c M.ͬeGPmngx]…^V8(+Ԯ=sjevpnd=-'HpGA ̎a[eQvvyK+Nf!xqx6,"n奙LZyf J 5r5 keNF+8 [$ )?YiAF Bn\n WFpMK9}<\ ( [ovw^s fO߾wr?jIz6]"#Vo$[. BH==/By1Z3.s"pŊ;qU,CrRgQb=EoVBJQ]ۊj[l/w1h')µZVF BKZFϵwc]#Y,Wi^h.s?}]G'a(Üp1a wqc1gWS>A(a3BE"Kjצo:.@Hnkl,-C u>j4 WbN횸v9gFEh] ) M+ŰJ kK +.#S2L[or`)X&A:p XJYޠbHl|I)ՠQU]?4nӟtR2 kTsIrи<1Q~ qGx YjxWCx:O.,]5a 2S;FQӳʡtf51/hPG 韄R>z{~+>ьt:2(^b'e>cBp]HKWX6Jj%.3!.%ӝr.Juť%Id텖@ߟ7Di1ɜl\P2e/3 Yk\:I 4׺rG}c=)$CHT &+v0 3G^9zUAj): պtditd@/;-r2EjW-mDa5R_s;-w}|#lHW6Ci&B;okֈ %.l!q*BZS1,%$wRsy;Z,ҞxXKڪbU t8_)?g^qNJbR<2o8YCT;H$,"ʣUtd^%K=ì'Yb T5 TM ?H9qy0Hr-z(F򕩤} }e5^ˈE^vtWD6Gu|E)/7W>wݵyRAbf'%2քzx6*Ӗu|e.u쀲U.xoD;\;Ԉs=_ P2R,F6C+%3B"LZ\eĹH Sr{JB칐!172UY;mK9\Z8vtM;ݲN2oUIJW9E9i C" T6]5.>K{y5g'β֠z&_Qѻӄ| ]g+dDqرy2z;FhdFL$>vΆSu, xYA̶*-o_6xvI.DJХZzaןY3d3ʹ7$ M5 R6Z?_{6Do"&X"C]/GЀYpU/ۅ /ڇ]:ȭOi=sTY^ז\¢Q&CQO,Z;<(wATr7 GjP2B?URQ~1Saqx`|*%oFKo 9q2_ZJ,?3uX/~w-ltcnzYԭVaͭC رG~ >)䌦uZ/0r ^JiyGi#j`xM;cGII#l=ǩJ,riAmp1n^ĭzwɥzQKa'I%+÷DE/wߧ`.f!DVkh+)M еII(mjbh_dl8ܶAA>~k .('m7X^dD7V\1qv3$X|s=2lQf N'D f\yUE}#qYqxZP@i܋[  (P=Qz0>v RKU(h[:C ~PK"qMLJGRr#R9K|_oVhO]IEhŀ{*/2ѧ72pݤk~2Y.B-xn^Қi{OiAF_8"#rliCTWG-5sn,y֚g[l$Q1ɫa.Fޅbd.oEme,#p8ަߠGQ) rRL/kEVH*tv-\}?Ql'3Z3Q'WO6KyHdILacnU@~ӹ^"'P$'K%yPO@X\bu4UY[IPC}Թ&PS˦G9-is MU܊:Q&TX7.{a GO 繍>,rXP^Bt+S dM2z Zl$>4$(`94\zy|}֊ʭ+b~ƈcSk}tQJœr+3{) BŅva=~BHn(kd1)lHsvð{7o6,6PP]3MTWR?z+W)H/^]$"2vVGJ0wvCx"EcY}܃'9mnKjyծntiEfޥV' /cB֭Qw<0/XhmPUBE&ŋ!{{_M>.Į$fh0%VJ.of*V`A G:vt{ջB%Ex"qݾ 44]> "C9S0-.ۗ3"'3u?(< #xB"{R #P ۺq ׵6uJɭno(:Ox5փ18ܒ >\$M<0<OנT,=`[0 a{誣 ǮlyM}.vBZ]'?ꕑ :zAiw8>3UL Lbt9 ԋ%%1,`;d΅A  zxW!Kk33kVߓt5LyÎ7YɶL Zb9H>:0'ŴĄ sN:rp90~!EnG{xΖ Xl5omkC{FL9^υ<F1{4FɏVԘBOlE@cGRxZRH.VLxEu=,( < ޏA@ O"pΉ)Y TvyH?%.%2{M9oL$Ăa=*%s^OpsOJQ6w1oG3p`!ZZm.k4WsNUQ/5 fٝ]=d`tI_-ʹhdLT6ȡVJÞ+ޏTUU>~Yk; #R0<7::fivJHRcㇲڻׂAPw(N dg8s]tз|Nm.c5S1Z|K.^!L9,\!#CN.4 I,LK4)x !pH$*Af!IW `WLdM$Z?qxz 5$󾦙4<{<5XZq±_]? xi,L! l +hPxIMBAj5q;znZ2˨oj f*~Q'/uR~}c[Ԭ(_rR|蒕\t%o6ӣȂM@%P%G OKi}&T} d@|9ܟ-18飄s>e5?+#L&^7{x;IuOn%0~}%6f;CWkp~*}oZ6Rq+[X%:М {̜fk B8i!< zjEe&#*A:Z-iv ˷&%eG?y,LwOW L K~tO<84,*CmBbG;U{Cx\ϋZwb9t!$qJ9=@FFVWϩ >de9=$@6c2Knӏr3HQ%')@R+."턻c$<`=15=zO/sU娽uUʥPXzUsJ2N^W^%gb%EH DOI:~Bcu к2A3 93ZmM]8N>];m狜zSzW|}RXaSJ;nIN1#9,-EF`)Ebp/;I0ݖaB[ ?$woG9uV[nYJ;3KY]wX=?uAM~2ki+~2(^WfJc|y"xV#v>je5`XI|m4]+9[.rJsEE4JNe{1#5m3zVlY8M7=n_*p{|FڎrG7uKZٓVZ?gc,)ڊcU炗.s$ܵMbKCuzq$72e0g۟]i㬢GzIH g~J95\zʳDEК&ĊpjCu/|gsfEE[{p/ӎYF--"2v-gGZQ`|m2.⶗Ҩv\CoonbYXn.<˃K@?9źSaq4K'}L5 [V6= ±BE%lShۙIvQ40pMYLй}FqkOwt  18KӸ *Wm> endobj 5 0 obj << /Type /Font /Subtype /Type1 /BaseFont /IUUUPH+CMR10 /FirstChar 97 /LastChar 120 /Widths [500 555.6 444.4 555.6 444.4 305.6 500 555.6 277.8 305.6 527.8 277.8 833.3 555.6 500 555.6 527.8 391.7 394.4 388.9 555.6 527.8 722.2 527.8] /FontDescriptor 4 0 R >> endobj 6 0 obj << /Length1 1394 /Length2 5926 /Length3 0 /Length 6875 /Filter /FlateDecode >> stream xڍtTk.(0 -! ҂030 HHHRҍ %?wZY^vV-]^KTGrF~~A>~~"vv=]t!+@ 6y0 T] AHD$*w ),|UL.p@¬mP68!\Ç~dH Q6PtG(AŁ@777>3i-CtP+ 0@gscEXH(mApgt tUP`?_ٿ ࿓ V0{(@SQz-|+f@9( 9a AOYn)ppQD'CB!{lp{u-~tq>Ü\*MDج(0C!1 upv~|+4 #rB( ;}"0 `6CG&h~y{%nO5tU4x OV x CTL2[.OE-8 4h.CPߔ_~;_U_ )vsn4]PhY#wա0hy ÜaPK- bHj!a8,~UCU 킢E pE`$Aď&0 %7@>8N1H_kEs舄D* qA"t߿ϿUC!Dȣ`ۊ27Ŧ8pa+/5t'疥wR f;t7m KVAa(t.~8n2["譇DS"㵙n?71h03DMpZUm;k2*F&i+ʈmkdJK4IuSy:t=mX6M띝LX8fRPU}pGOOWzI`,p(QߴtT:?: 6bˊ>ӷ)3gt|KC2ra5-α^6EZ,4: ;rw &ҕ1e/ 2p?PX7YT7TV? ky\r̞7VB업gZ}ET?czm9r@Z-}$$.ʷ+kѤU5_X!-E,0߭!\iX^f6d4M[}g0<ϭׄ?4_P&S f0~aU'=Uzq#]ĹAA_[9?zds rA] 8jGdb"T Nos)xg*9^ՏNt0v/-U5]y0_6~E  P8ouឦ-lBVXˠ_ rk 'X{N 63]^/`n 5{{ΜPTʝp{g,+{&JVi^f͙2 8}$3"f}4fʻ!&f!|Gw.!8Z{i7lE1B) $7}rƤ?OEmYyzIG>e d 7VE#zɚ_D >f%Rɒ }E#L# 1OIN*\x,Z'6_z5 ? KKTxO8|Li'%:O eiB9ݛO?9}%J.N69g-5n5Pڨ0:[-ERHhcS=eW(_$ fdOtދdgn<(1al8+{lk|WbǖCQǕĿ/r®,{k" 's>Fg>p$ZotUe‰qꕩ¼< TߙY0}}c蛷#S70Fcfi(Ksp ~`dlqIDP{L׳BiwK(uՏ ,N[y'nGfߑ҄HtmpnRD?Tq~zl'~)ti0kq)=ϛH' I%`Vإsӓ]\Z=4'>_0%x}6k< ]`7R ͎!L} {H1-%އhQe7A9{r 9S R'j疴}.!8/% t_ 07T,#h|H ȐQ<*\bg$h%3niS]'w[ 8u&}Jn.{x17+̕J%eLMvUőri9m&4{ŭ2|^ӕiPdbyȸY 2ºmH[;>ET@vFIqcP'wd ,,3r_n齅Ws;իq$ ذ@]J'fcV<\~/S/X U$VaelwkZ|~~g] :OW$RvzPS޽x~︐ˉZ$U-a9~@ڼBZ,4Vg4F``(wé"Cқ4&a2\ b 9_צAw:wdr_=#)1 rlPoWG1_G}E.ߠW5JcK^Q}bc|u ~FD"+"xMj"r$u$ R$ܶ5jq[( I;LF{oA{:9]3Q$s܄/;qt#ر4?/쌚4Z%a!MuA3<Ä;[J7hPǠpQLp qB肳^!5Bx<|*<*󈰖fMIiv"0iݘ\,s c&1Ɇ5ƀ@eC5sDOw!LMOƖ*6]K-QiDR"}ͩž_z|sg_og] ;[֎kt^FTTGZoyG;C)QMS h lXI0Qt~^f>f?yJⰹhi(eLG~4J;F"&~}} և/6my %רz'^yqޣʩjw_q^_U Wh:ߐnw}{G޳-c9oSW~qD i9[`}a.b"tJYrS~Yz(^{g;^av<ʚdeW7GJ4RJ8qg 0q5_F tF}fI*6wիbaa ..V(}p1][h%);tCqzg t@8pdShhmoH^TfF͔k}|R~Ւ !Vo=vR4 T>Spk=aui8nwyMxu<$9#ȒJs>Nq5I=y5S򀟛r,]+SCG~S:&몘Murԋ{_x g HFT08z a|]us#z`7'=^J fQD8}G&hy+>p_X:Q lE O4`'=hi;I%$fS n]~[NLhܟgÎ 5T[7xw12Զð=$G3ҭOjmy )o-)L3I 99ke' dovj"6Vy#y_!]/ uE)V>F_7N2yb 3n]A t !vQwt~ 7h{rߵ9T 5Iw)}Izt B'/rcW $.6`!-Hʗ Oz/N_Ys聫3 3 PRa$#h_O^rCVnlaWlO͔ZO~agejX//& Lt;"m_BZ@=.]\Yi?#Qp2l (~=ƿ,UcmI,j7M%/9~QBy֦λs0Dlaا/IO ?@TgxhDcsG *EeZ22>i)K ǟOo&5ws׻ ypST1ʵHO}ͦfܭd- o$ +(ʨ3b}ִiF4GVI.FcR#Df ?T3'׮2Fǜg z~%ׇ"]6/-ɻN#,˦Ȼ4h;ƺrbLEE%~q瀳NQ ,h'Zw%ϜlfD̲l}۰ך]CЙ(`yЮw<2KqQ3^A 7Cr+hFQl"j]R+Ťh hr`psTOTXFb &Uf+H%I'r=&Yl}^啅ѣ*hmΒ*Zw Ξ*Y}f_T,F(ZbPkWh'Aa`_?x)Hn|TUU5lݰN.浕X}*.)nb֡Z?3 RRxK@0wj^ήM^2Ęx3ₔsFƜLvq3=̽\˱r3|x{CA!f[,PH^8Qc$eŗ|MpV{)[Bw7R{"$]ORmNIu$2gwElA| 3nʯ TO '#8^G.wϽ7-ɴkz> endobj 8 0 obj << /Type /Font /Subtype /Type1 /BaseFont /NSJEOL+CMSY7 /FirstChar 48 /LastChar 48 /Widths [329.4] /FontDescriptor 7 0 R >> endobj 9 0 obj << /Length1 1571 /Length2 7309 /Length3 0 /Length 8330 /Filter /FlateDecode >> stream xڍT]6H(-C7 tw:C3tI7 ݍR HIJwwO}^kϵZ@!i 3nN@ZEKy9@LmA "0 ĽM'E{7/[@[Pa"+ PApLi3q?f  pbT@ {FxW f1Q͍9[ &vv[~ P9.mЂY!@`bC\`g-e#Y/;9MwD`@V{0@MN` {8> ؃ Ij@]∀s!jͲPKicڟ lqw sz PK_eX8r@!N.`9&ج?N ׯ=ܿ5x9Ve} V  ]>^:ars,!9O{3/|w^~/{Y >b. EC)mK)%sxqp8xaaa 0ۀmUA V0_57:\STar~c ?y~IWwGr.?~oƽ]Rͳ ^~D$6-! T9ܧ@08׍w?tv ~3KB-`_rvy`⾟RKoq80}<י @L!aſ~,.Pex/E. .? \?]~ꄅ5[m`w8B4cJIr7~ŦX0~l/G/^Z&O {] %g!d;8rz.[T02Ae8 JF(FLz7mk?@.M"9"Z2i9Ҁ~7(E[Ag'r$$\sDzQLʷ$ޣ\ RBOE7_Iƥ+‘ϤKyGۑ{ <1h?Hr}QCmݚ=j(t2!3s[`k#JqdCD>Q *&bs|.9T9C<|z\ *?m965VEB\b'-Jsͷut24D~<* ɯVz*z_e5io4j$z;Q߹#B^dʏoSPl4 m zmcx?rn?%7MynNTYݳly'u0\1*Ϛ3/~WFl-,ŕaB|jLy'fB+YoVrks!'YP;7m1s[,7u_8I&0dr׭o[%|iQVUFY4#/F79$PK+U`Yx)g^V)V!wsvP.X2ұOwu`qom'lMnKS.UO;-h|5*;.ŸE Ն*ڰ}Z`/§eg_ (l G~cԝ1Mt8o9a@'Πh2&9hH)9 ~Ͳ"IZ) \s␺$._Jl}fbUֆҴ8QDhgWejCMHpmuw8Jw$Z?WEboP{ԝ8u4u_G*BviS{_zfVuL\/iQTT+Ȟm;a~}D> bA /'ӴEW0 amm:t%ҪSmBLg8#}pc$BL hrU&Re CJ  Pt<+㜺w Te%zT|;K#3h "x)C9z qNp&sOn?8ᥳxV߫c!/A>uw81L/\s)cwrbH|>ؙx>~VNe>/+: as8eDZtϾ!իAݩ4!JH=ġtI)W%tjRrQ#suP(=ڂ$Ci wP LA^=X3sQX0UMi*K4H% m>PH}dBB|YC۬ coD ~cݼާw]=$]ѓe rh"ZQH=~1ĵҔ!1-"Wӱ$ g)*ctCMO^9^B/GiuK~ǠDڸ})W7SĵD}E_\>1E)Eur<."zW%{B+$Aqky7b]E]gD[-a3M%-Һo&tˮEB hOPG$Sv Y6PqW:v B :Y@jM9W8 Y0kV/ib`\9Ⱦy!̀]Qwݜ(5ô/&'9i㎤3? NN; }Mqqq#Б`~e92$"D7MV[qrUdHdj/LV쯅D˛nSükRgR 7CD2&4+mKCFګ@BVya]LHd}>|8.N8'5W ۥ;Dpm&TL{@tI.h ɚ$?O*;[#Mz#97V rݔwR @XxCg9(ׇo2Q׈99S/p,kj 's8;M銩R9L󜛵k'VeN\iќuaWH(=Fn[LHiceZeJ#V1dlڨ%{RƄ%N6Tsi#0cqh]O/Op68sǭ;(#?D1_ trlp'6SlWi=*gRVU4 Jɠ!7Vh6^(Cb ZziCO^,ΉRQ 1+Q^D$GН!)õU}_mT YC98yZN҇#|!KxP Wa.x6FYУw^z3ԟ3m*(bRzҰ)_%pgenG' %EfX{$=6͠RVXzgL"/c7 }*83žmRh}B|xTwbnRQ4}'^F4sԱA9NPPӵٻb;0 _YlcJ#^P+ M?ج >R#ޣ}&ʰE׃_P%6~\<X [%}dAj6OHpW惾_F*nި8v*NY#8!4qauAo*XIأjY>1 )wJz؄c>gEC|mkD]~+ :#Z}WbԷX^|0&*oTLvk{M?Pٝ1^ӰYZ6䎅y'1[h\B#,t-h ;2 _}ϗX 4xof; U+GK!1t b.dt7+ endstream endobj 10 0 obj << /Type /FontDescriptor /FontName /QYJZBT+CMSS10 /Flags 4 /FontBBox [-61 -250 999 759] /Ascent 694 /CapHeight 694 /Descent -194 /ItalicAngle 0 /StemV 78 /XHeight 444 /CharSet (/a/c/d/e/l/n/o/p/q/r/s/t/u) /FontFile 9 0 R >> endobj 11 0 obj << /Type /Font /Subtype /Type1 /BaseFont /QYJZBT+CMSS10 /FirstChar 97 /LastChar 117 /Widths [480.6 516.7 444.4 516.7 444.4 305.6 500 516.7 238.9 266.7 488.9 238.9 794.4 516.7 500 516.7 516.7 341.7 383.3 361.1 516.7] /FontDescriptor 10 0 R >> endobj 12 0 obj << /Length1 1399 /Length2 6369 /Length3 0 /Length 7320 /Filter /FlateDecode >> stream xڍwT[.(E&!BBH E)ҫқR^IB/"?߽kݻV33p PpM+ 4MA@(.rs!w/ )j8{S`/($@$eAR@ @ !>@_B½HPh? + dd~T<C.pw) cUOEˊ@pe)7W`K; Gz]x#ap ru G HO?ٿ !!P( A!';`'a$̇@KC*%?%pQW6k aj(8Ek zw??D"H/0o9 Qt @q .0CAܗ(4<! X7<0m@8HTtÝ/l><]* BE- P'bJRb@@ӁAv* PH\vo">gl^ug8?/NiW{G|87ao`!tv}/M3B`._; 7By!~]4a_Y]^&^g;/`fN , `0?R॰`@r8apoMDE(e 80NRQ濖zc0[{=p8%FA]NjTX|7FW_$YE<3.zZ㞪JFeo|9v,k`4T  w9oPcR1k_Y)llxS-#ŷ6xe- xrDŞ΂MPJ;*-U" &L|Ǖ8Dj[S5ĬW}{ŷ{%+#ioNAǣ >R+K:C|.ᰒيqG}V"kojߝd >`CQgj@x6T;\2Zz1݋j3W.k^#SgT%Zfg7}?3}j 3^8,6* :QOpv^ۖ'_l)8čM9Q< `")X[x~Ք pm^4UMxJK/Ѡyrc"mmyd7ÁWYdtO\vZE k6主P Xdju=~Z+#4BZS)l2Ey%Yh6wfyk6§羐 @K̻M.1dBZWL5N;qx+c&%Se=GA6V7<*z&w=pe&=N3ݏ]O]d2m!nJV6_*<0##ZhIysa%rŚQqmM3q~vv  =Iag1F Ķ[fe$IW!Ҙ$c=3tc^ۖi<ʩkA)~[?EFy}z@Ae["AyדO)`y/Aub-~8TU[ )8p׬&M'2}C2LO}dxݥvJ['H;f)0ӫ!jdA'IBڻ#4)u?@,zk -}kuBY8:%m%mq*_87Y~4櫺vV'y2lɯy^Lv:l^:~3/yuxF+uA(sAl 9 QU MTMMed{td+f"5ᚴ0R;5١Um:;u*1|:j fqX(k0Ȥe8P#@WFQ|pf.ܗv .f۹1˗CP#^'~Yt: OV[r-)ү(>cVpN3k*0鉠+"\ aXmϫˈuW1o}π V\_;Z&f#snHIaӃ[>&&M_/:TЮtK&,f%tY{Zx;&:rґF-ٸt>EakN`/7{7ԛrn?<?KsaTKlqpꞿzAx tP T0@KjVmc#jޣJ̔ҬZάrmdwP$#~zwJTf2;59`v+@MXˑz^4;+mML$nI$6TR rUXAjIT;tؗ_#sO$ܺ2622^quZOګM@P {J`|6kv]Tg+7>&`FWUfC) 6ӨQe% |h|PëmGM©"yTw^qGs\ƧLH7'O Ṵa1(bLȑu! X vnwΏE3Wȓ+"Ler*@cJO/nFZ5m$O!N$y2?h㱚H42(<Q}yZhI&TiqQQՀ8ՎjFxM`u2˜utBF;vmH˘@Vv}ݧ42>I~,ļ:ؖ*Y׌G&X6!&s{ ÜٍD!2U}0ZG;w.`xA4WRDȰXw{q=[diHÏL8+Ckw6_566[\&i/ey<#\vJO7& U]lخt}Z-z 4Hwj #W8/UXQl/\@6s..V?3r F>Nڮq6; MK3*)?;ao 5HIk\n,tȼ`?1̅VJ%8עH26oN\!:U[\% ~fǻ &'g}8 NL\xRV~m/12UQ`#lZ.tNd? Pú+43U}![}DI!  ەٺ4_Q7Z9\عۂ널Y+ҟqT*u{V~X61.tۃm0L]@9B\HC|jҌT:dtTdu$1 ]Y" oҤL00Aȓ<ƈCo9O@"4ǂ>4^т+OE3,!7@dzQi;koй{BBj2q*l&  Jg r07嚦;fEmyCc hXcmQ,9a(Wۂ=p1gQ+7lRC MO7zJΪ]^W Zl%?b 9zJu%M2^5XZg>rf'c0݈iL[HŴط#%Rq_7,M^9*\wkT*IFLMl(jE} V&mV-yksnISH|(Euo9}H;1̦|g%IM3Fk>C檈+i%PR/ndB >7gjUIbA&h; =ZYv#Vby5R8`FAl879#9aO9Q{m>Q/la펎ZpnzaV]\o>tP'(P]UyXM\!Fks6HE8JN>B|t`KdFnTY=]p#J/BQRrmb_~׉H^g@Pv`\ft/ѱ7aV#~Ѷt Cx;=cY ;m T: I՞>{JSxxD}ct㧤\Xb%{~Z #&n:';ؐo1;/Q.Xmd lRk[IXoAdzUOy::\?]k(IlaUCᦝTCpBWU,mE3#D~};eB%f6mD!_y0/G E^xty^@p4`o*}ò(d/*&8\e-o3_iZ(o\Y!eLNrS^_As^ܦ}6h2ę9]r7k:\08>6_oN@қ8_-G1+1𙙓eɂ%Nʇ )M }Q_*eHNլ0ױs3Wԧꦹ;{oY+[ 5IW,an99 BzNTMkͺ5:}Y<LvȘff?_vwbŦlHBQ _+QUd"%i{^[+YyQN,&tźxR䢊M>Kshq,/?qC8E6NAp3 C#u=n%{ج cm\{+ʶm!|$iSdɵUuW(Z, >qӔ$8L*BY4:am`ºֽDͷ[803 EA0A~SʥKyoTX=<$y*pN0yw$C;Ikqo23ulg*G  %$o%yNdXsb.ɷ[` yd3nOcţ{{3rDBLy(cm{u9Z,,!=_)N,"_Lj'7?adwkY7&w%{?grA:Vl\1e-Ϧ*zڵ?Uc M5yzkFƙz-.l:͎iú3|+$;qk}B%咭I?Z>xnq34߿%Z`. v[b-B8x;Z ݙ|}]q*9Xiبeo11~1P|Om%#=@wRՂWzv2+7N[Tsiρ'DΆAK}%7Ǚ>.I.bvWq9T.X/>-85bOȰIĂd{{fgY $ij"Rq'9J $CsNTuI_>?r͞vEr6wPT1d=$K:ݡY 4UiS𴉅es֚Bpxc;qRIwI<[Ij1,oF%O%=U7R>LH^hKp8^ŎhzG,xpx>3 \@ٗI/һ@NG6nn=cd9їRixM^%P{(\&VLN-"@/t: ~uD B0Xے3-o2fn.s!Q5k3jDϸ,CĽ?C+pQlk/)Ϳ,.tR)pDnVaǵ* d+6\9Wq%#)qؒAݢݺ+YkdAiLzP5U endstream endobj 13 0 obj << /Type /FontDescriptor /FontName /XOMCMG+EURM10 /Flags 4 /FontBBox [-32 -243 1060 720] /Ascent 691 /CapHeight 691 /Descent -189 /ItalicAngle 0 /StemV 78 /XHeight 459 /CharSet (/p/u) /FontFile 12 0 R >> endobj 14 0 obj << /Type /Font /Subtype /Type1 /BaseFont /XOMCMG+EURM10 /FirstChar 112 /LastChar 117 /Widths [589.6 605.5 432.1 455.9 416.7 642.5] /FontDescriptor 13 0 R >> endobj 15 0 obj << /Length1 1423 /Length2 6576 /Length3 0 /Length 7542 /Filter /FlateDecode >> stream xڍwTk6"J3H)!9  C4Rt4HJ( H J ykfag㕱AXCpP`!@A"vv}t!A nt`ԍPuu A P?8@hTp  G~p@8bb"<2P$ 4({͉@AQJrG|`G>NP( j vAGз!lQh0 Q8 PM+SQh9A8 @OtEJC G'0-Eu>;r; nn`w`|/x.$ sW.+mP8ʅW}0$rv?}G^ 0/6Np+TE/?:;(  L:{_=7|NP-PmD$ APk NO5|3|$` lpϗ_鉼1de/^WPbA Y쿵`_IE@` 647T8a| w):86sf#/W(Wjsuo |2p;sQCma(7 C._7 W/͖A".7m,ѿTC6MF"DR @/3@݄\Q>[ET%a1ͭ|s*? . "(U)?+ uBoZΫdy7> 5d{٫*;Zٔ $iM{?v7kOXNTBa(t~l`I _#?a"21}o4},!Y9;'bG@)KTL]HUTY44U_`V$,Lw5$\ʇ!T,/{U\0˂N&; jJ-p/.Vפ+EC>Va:why_ _+EOp@(N߅.%}{ }S^;WSFV|ABs%7fR x 8+1JTpB`(E1z=f * E.PEC˃Ek-XysN-tg]?/!Ѭd\|w. ~)Òsλùc )nfIt_Q\}^>5M{rl~%Uœύ^[wvy$R A%hk@aVh뙞bf6l)5:]kz|:="a1KO nV@ԴDbCVl y.E(< :mX3 dԯMe4wbߟX_i }5RA p9+ p!SDl{ I9a T,Nɧ$ت @0uGjd5-R5/$:1s w=:o=f)`l!ْ"nF<89UZ؁K?_XH6͜u*á MX NBrb,K=JqO}Mڷ, XunUѻyYz6կ%%D@P &yNf/R aZC]r~[*8i#vwWL1%>FUc?%D" /N="x7Ws]O-ڮeLZ& :T}l0XefB7&_D] .w" 8O˽+^e q~Wc+rpfC g/Md. 8gH/tWVkT50Zꓚxb2kjdmwS U1(tWJ16sHm$Ԏ鈛+ (+&|uIqI?QV5{ݗ:ZZǠF32MvȤ7z_؃(/Q |Yԃ&)hR#.}JE"yUÌy0fԮvYnʛE䎖*+Tu;? -/^FT8Z6:S9,>W0񝮻~<OyXX/<[ko%FfFsy[*YM8=Y,e!uY2ZԽ~L?k JNc"= 0P{Im=- 5/ZÝ=MW3-òg3c7-1wXXEt`y;Co[Jm}/6zE޷MP`=*Y-atƪeLW. usuPX(^zvzL8d<!歰M5s*p"Nj s $.Dv=^lJ_/EvY"=;j [Է\&,0wdeʶUUỷ%ӝQKn ՛ܷKɓDKbgn5o#zK̫~gZVcI!1hru-xxX B]pj#I^I~xnC0pnTG?R=`+od?fW4 B @ N?_=С*kJh5O itzp c:+0$𢤊0s_$qQO8|P]tsTqAjI!N*W ˗RbŨ x>:ĩSom>f8UA\'ߧrq{P:@ztJ9'-ͧ\=-u@R=VZ""O <N'.v_1c!) {+'il!g[hVVp٭Mj9 CW6-q ߲PZ%SVUQƙ~0[,e5A~XY^W$op{ n z\#FUz?Qׇ oMJJ| -v($NGQқXR*y[Zd7/4h[[˰z5A c9}4γ2J!ԭ7?[ I}T=;#0SNrkߧ罢F1۸̿?줊]p}z>Dh1'pnRڠ'/SƓ<3c, ?sYiy920tԐyZU֏a"UG^@r;ܝIjIif !2#| ǚ'%|" sO%7x6`h0m&,k~F(iԉ\ZLifa'6za{7{>LexyKʚGVή| o8w5"9}YCиknF3o:W(ȕ9GHݦ}S&sܮ8F)n2CÒF q=IF r\y=\qj`MR܈#:Z;u/wa'-ˣGW!n}9o ӻ :,T&L}E C#qQCtb%r/);дd/vW!ShGx^u^Ոm0F"2EEl $釘GU$18}֗ЍDtߦLyzsZHTdۉIJR[~8 6n&rQqOe v~U9?eQ}MޥUX9xx>a&6dp]}z=ց?M{-q.ɸ%O0]T a y϶Z]:ReI %3He+ ie[#v onϕEarT^LBV7{~ʤK˼A􄘑6sA!٤軱}Wg6xֿ|8t9 qWL6<R4D*=wϙ2#ba32''Jilq3`J%3~}> u&+D[QGAmx~>ˎyH̒M]z/UW$>A˜r밎mcB8*)i=m/o>]C~RAJ+ Iy۩L=jRs2~Iiz#?Re|]>AЬH{u B R.+Q5U~ђ ][X.L3J@l*3ָϛơ)pDǿOHǑz_J8++djwֈvz ]sG ٛH(w̉ W3Wn ,@EeWQJr9-d@:ۋ'ՎT!a9b %c~Lxș5B$PkFR7)궹@=Bv9Lє'vY! ;G4" ^zf2C$tC4x$' {b1|](jCSZsπ%L\fSw? 8vh^D< "Jk16{za4SϽX<1fU]x\su}6-190wcRSW䡟?lĉ@V.+!(ՏsU49W+iw5Q=TkKZa=mjGRi:|Ňˍy#+rd\ Ͼb/OUUu4}~EM\f{\*f=p:`xn;`kD$pKNvqWYF]Rx1=v pqÁ4vhtҮ4 ְ'._ > endobj 17 0 obj << /Type /Font /Subtype /Type1 /BaseFont /GZDBGX+EURM7 /FirstChar 48 /LastChar 84 /Widths [625.6 625.6 625.6 625.6 625.6 625.6 625.6 625.6 625.6 625.6 374.8 374.8 915.5 628.1 915.5 0 694.3 931.7 801.7 868.5 997 744.6 625.3 925.9 946.1 506.6 515.4 816.7 692.8 1240.9 998.5 968.6 712 997.6 749.2 690.6 617.9] /FontDescriptor 16 0 R >> endobj 18 0 obj << /Length1 1384 /Length2 1124 /Length3 0 /Length 2002 /Filter /FlateDecode >> stream xڍT TgFVB>_W6LC# D!*;$`$ !)HѲY*jTTQTZW)*PD I({rd{{{q'%⡈)7.aTD(F9pt^IF$D)R40AZ% !H`RULB%DJ_d]OIL (A `ҎHHQJhبB&Ȅs\A8TA2J2 hf:DL5 d%! rLqƥtu*!>KsNg6%ps0* %k1<09KE!l*r(.5Q4|ufh᧒RUđcJCYK┊i_ FB w-2$ມ å2 Zɉ±d5 `h-Rq,0TI"T Rf'd9uJB d4 dcTh zێ'& 0Ùof(<'T7Ko7p9@ap< a? tn |/w0}Џ2b/?H^XD3 &mF< 07eDjwU`rC]@4q(bjŻ u'ȇۇDX*a$qpuON0Pa =+yG+MDJT.H idI!.!&<Jk:.-M)L5o4qCRM遌 Ir/GI9"DM3†0J ;cmYk?[kͳ=+xnqcj5-_Dո䠨+[[!OS'n3Dfoc˭E; Jj9| c]ۤD?3U~ծ)m©3"[= (6<ܼ:{0kϹW/4GRӄs> NIo}>; MEv}%)O O tCLnYkf8CC'{[[iÁv215uN폪EOz#K1ۼ>YTd%7.;skYVhGq$2~g w/v85Vꌗk߼&;V_i>{122ݽΘV1ڪTװz xG&F庵h}6&W=4+eoj\_[VG|]mpS,S'jnStz;6k헱NIvFN+zzWWϺdcr ;U'cz2kX}ٱ&Xseԭγ;~[m.dݩMvt6'сtHeS+O+j5 S~6=b8pɎP5jw'%O\c02\%'':3+^o]vުqŹs+.Oj/q>m<]ʇ/6zqcieN{+?}/9k %&_kTh?j‚lz{߮->}^dXX /ޜ7Tzl:@?j)>7mG쨖yVEVӜfr{Jj֟,;-jzBԉS o.vu`֪&i^~{{lJ}Kī׳]' k,+}# endstream endobj 19 0 obj << /Type /FontDescriptor /FontName /EJDEJC+EUSM10 /Flags 4 /FontBBox [-8 -192 963 738] /Ascent 0 /CapHeight 702 /Descent 0 /ItalicAngle 0 /StemV 70 /XHeight 459 /CharSet (/bar) /FontFile 18 0 R >> endobj 20 0 obj << /Type /Font /Subtype /Type1 /BaseFont /EJDEJC+EUSM10 /FirstChar 106 /LastChar 106 /Widths [213.4] /FontDescriptor 19 0 R >> endobj 21 0 obj << /Length1 1391 /Length2 5976 /Length3 0 /Length 6916 /Filter /FlateDecode >> stream xڍt46"ѣF{N1ØafDoADD^"D -$os{[3Ͻp (9"HF@X$ )[HT!4aпĜfPDHBc6U0 C"m/8 , K KH@$7T0G@OF"hbN RR%w( #=0ꎭc$# !-$#vG "Q< AP7ECP$L\`H'8 E!^G( VtP_`ݿ‚J'W"w0A{~03C꺂_ ?F8h$6 X遲u%Cey`ЂhG_imVC8 ݡ Ta((w??uC }`G_4.x8f^)Cr^A 4:#gC8=53!/՞/ǃ1៬I|Aq,EOMD ,陯P r(o<֦ (~ 7#;+y\nj=؀D@.+TV{$ 7 \Yk "85R*8`!'|o:XڷwLx襅>Q/f\*j^Ms1ƇA 5>t\]aU|GW\#_`ܭ?IwJ 9m~xޖgU_Es@>Ba*2Mi_KNײl^8;|bi[EvRYmP8G!v#L OV%wrҍ7 US,3mzcs!b}k]`.V'>`)g*O%'Cy7]+Fy"vm՞F#"8;̍i'W-uB 4PYGj5gOdGUyjٍ-c.yLZ{fz˚%k)MD:}͵(aDQ敠SYe,K&G)(EҌD넂<{Yγl:>ѵ(6uu2eScnBmkc%-_}$x@`_ʧ }hIn*R&TExdFL>* c(GXn=vn>w"'H#mI|e Rr?9ץsxIt+0 W]2"b݂i> 嫳"[su>?իrHHˋ\e8|e̝x ''v3P^y\pVuھ0ANq8 ݦenߴ 3U{*V/67pnr)Ɀ\#i/쉤]{y;m[} ¥PM/^7U_r|[GuHjkͰ{v%.i Ϩ!4l3ޓA<'|GVRU R(ds v4ףL=:&˅3|ksH4WwkV1olV7prKMo}Kn_6/ȅJقL4D8qV/?ڶn-؁m"-!MvS/|)(&\@%1 kʍzU2WDE$#jC1WK~7 XvK6 oU&NG{]&t{,LeG/n`7VltRW,xj7g3b wx[= |Uۻ%4sq ZBnw5P_/7|蛨{^p28Ks:q >ZJhՠ (qWѲuR9IʹoSœZbvl>) c?ӗE1}3hYf-nVhHT^&.ﵶh}ERbx?&$jւͻkS' 4];^?71 h$:MjRÕ-/3VOn4˷AsD SF.|-ύ8&4@ QgT;s qroZ^wNZ=YG4x@KRǶf޸B"Rĥlr@3v mu,ks:I8vIRe~b +LoO6nfęǸ{JrrǨl6wzT]κ2rCdHӨD^I՛ؗTSET0bŌwҼyOH:'ʄ8Wڎ?_s7h*ܫpk1|-Q"#P l>P\Bm_i_)Ӗ :ehh *VwIT8avժ#""TA2&&kӏI97G:/ ?͑q2ޤioq~^<19h̭m/(3ܥJV7Ǽi#g_7bz>YSqXBo5&MF>[Qxgݪ8;yzN>ؗ_&oUm`KIj xWEUfVgg/O'6{ϢYe~=(CXMu3f;g;]RWJj?.~ODpn/ WQILjsUF'>?@M7dmKoW3HqZC)wгMSv-]gCC1]鏼 #\s/)o%Vdr@Bp O$IU+PIOC#Nu/2-~s+¯'?>,vF^#]G>W|SS&jm  UQGf*x ,RU踍#GG8_2-,دFRJg: vHo0+THׅs"ٗ=xZ9%bmȲt1K[x5n }\Ͼ݋J]ykq'KF +qBPcY]3Aǜ'gH;ƚd:tV\n32MiҩN|$χ[b85<] &!8TFM:_Ğ3&|4-#*xv^v+06I@zStbCTf ׆|nKM}(QŖFF`2a]2FM#iv949ȝhܦ^g:7wjPCef̤KY,[3K?ۍٗ?/>X : I74:.$D8r"}:hoR?C3Fە3p[XZ k(/3mAofy54Hn.z<>TOi:'#_B_LQv2!|@"CjZ#xF:^hxn%z WG3-'5x't'(<p]ӓصn_o TnpAPu#8EW?tbm^cHG)$oa V 8;@BsmE;2Lf#WF(4S4V*eGڜB94%- j }k@QI&CD٥>Y#kfŽD11†Zgϓ<[kސkQH*2>uNH*J$z Rո7{h6 rgNG&H]ږbmy;68U]up1{}Bߣ7sO!ݝicumg7Y#{֖>%-}`U4󍏡 :> endobj 23 0 obj << /Type /Font /Subtype /Type1 /BaseFont /UNRHAG+CMBX10 /FirstChar 67 /LastChar 67 /Widths [830.6] /FontDescriptor 22 0 R >> endobj 24 0 obj << /F8 5 0 R /F13 8 0 R /F15 11 0 R /F16 14 0 R /F17 17 0 R /F22 20 0 R /F28 23 0 R >> endobj 25 0 obj << /Length 1062 /Filter /FlateDecode >> stream xWMo7 ϯ1)HItMȭނ Iah? Z$P{cx8 Û6p5Dc$`-R(%l5=t(`OO< @(G~'o.FF6 JCDɀZ[$..G_H3j%-%F&*cy*JʨU , c-CH" :J4Z'ΨvGӈbўEioL-q`n kQI q:g 8M\bըtA- =`gF>!vIb fe⨰&(-y*j7^%H_Q*'* *˻2C ac5Më/Ѩhؾwb尽 o^\~pqyrRz˷p>۟X6 \H}S#,ԡf Rr06Yq%PM m#+l2ǶY#DD:H?eCEꘝXMɻ[a?^Wj^_RQ-S=d-< XG"wqX>N=LC3YGݏO'ן\u}5,Іw6s0:$-deEe *oWR<3Ux)'"E.jO,$Շ M'BLExmJz`t{NJLu==4X$\Dg~m}S`1z*G2G!oNFqۘ\e//ܣ㣘{⏂>[yb([E~IEnLn>qESv0'E t`r'I.+Ҟsh Ϙ> ?^{ݕΧwy;.k9s*Yuz~4ucrdL:cZ|q1F'qeJG=O Z4{y4S Q9uz"zb'(1EC_/M9~k-dyL</wwˁrg/@^V endstream endobj 26 0 obj << /Type /Page /Contents 25 0 R /Resources << /ProcSet [ /PDF /Text ] /Font 24 0 R >> /MediaBox [ 0 0 595 842] /CropBox [72.6 596.6 281 840.4] /ArtBox [72.6 596.6 281 840.4] /Parent 2 0 R >> endobj 1 0 obj << /Type /Ipe /Length 982 /Filter /FlateDecode >> stream xŘMo8 Q`OqEٖAe{ P(^LRv|LӉ ĮP($;[x6eel 4ˉ7M AQgk^&;=Lp(d!>n 9GI<׉(^[Fj2^|ՏfmRSnnIڵ0Uּ.8<6f8~3%dzE:-jLU0EmV^EU]_iFqڥ(Cš`JΉ 00p'< sw]"/H0/ͿOId3n^=j<4wɻպb$ٟb 懲`m'2@YRYOԈUBa'Rpቊ4dװ$cJK:H~R e0$Yi>}) O l_3 ˫ΑT]5Nu](ϔQ~ˡԗuR) up<殈r,?.΃8Q~25 NtЏU) BoF:t~9/89yNP#kUvz4n]/t?H endstream endobj 2 0 obj << /Type /Pages /Count 1 /Kids [ 26 0 R ] >> endobj 27 0 obj << /Type /Catalog /Pages 2 0 R >> endobj 28 0 obj << /Creator (Ipe 6.0 preview 32) /Producer (Ipe 6.0 preview 32) /CreationDate (D:20090218080408) /ModDate (D:20100422125820) >> endobj xref 0 29 0000000000 00000 f 0000054451 00000 n 0000055516 00000 n 0000000009 00000 n 0000010298 00000 n 0000010523 00000 n 0000010794 00000 n 0000017781 00000 n 0000018002 00000 n 0000018140 00000 n 0000026582 00000 n 0000026822 00000 n 0000027080 00000 n 0000034513 00000 n 0000034733 00000 n 0000034906 00000 n 0000042561 00000 n 0000042790 00000 n 0000043139 00000 n 0000045254 00000 n 0000045467 00000 n 0000045610 00000 n 0000052639 00000 n 0000052858 00000 n 0000052999 00000 n 0000053102 00000 n 0000054238 00000 n 0000055576 00000 n 0000055626 00000 n trailer << /Size 29 /Root 27 0 R /Info 28 0 R >> startxref 55770 %%EOF trunk-2018.02b/doc/sphinx/fig/shear-slip.png000066400000000000000000000350721324306050200205750ustar00rootroot00000000000000PNG  IHDRک pHYsqF vpAg]lE9IDATxMGz[ҵ$KmfqB"k0tYk7&lB"j?^$FOn pT ߎHd UpTgʿeY2#DV,/S$ID$24EEc߫Hd@"3BYcY#5[G`FfyplD ~8 n-M4!KH {"{~9mxG(ፇDfW<1G|l*>Mon!G~,ūT;H|>P_oE^%vLp|fIfc!3@uGrrjYʗz9nڗ>Ȅ\.I Aw21Ovfu- Vog_FYI',1:q1L n^Ge'qK8]%]OyY .aG- 5QMh4|#2쉰cyə DZ$2K{2\Jp般8fntd0}fn1khs,^d&J _QlTχ֫E(d#1ŋLR]dU=Bd{v+_֗wgW,>#xzfm1Le!fãA,8u= ]sHd~/o!Nzs/eeKš ,C"#ː2$ ѷ4Y<܈ 8,]j ,LdnKb9Y4E^F5YQ i2Tw$21Lj3͂D`8qRň \A"#ː2$]"LéYHdQ}#rK1 Js/"z!9Zd~ˍs= B*]j5Eĥ"7J"[RHE"\*Ȋ"1"T"A ,C"tt=eC"䄓%$@B"[~ JdaTh$ϡ"Y|KͲQYK͂\3cX"ш,ҥf "BLI";ϩ+QlISc 6YR(DF>C"[("<vKD`AdfقA8'`Qt(l7h>@(. Jk1^C*,  EҥT$B!@aNKE"-NddU$B#8z Rd7Op"H6A+4 sك*4'@.Y 5I"cw[ah E. 嚙$-yl5E "k֣*KlRd0E"ҥC3s 2*] ^3G"ҥCy&^dTs0"G: -ȶ[;j%ͥ" jb"AY%E 8AĹh=OĎcQsZd "ҥnP}#TdealhmRš_*ꁼfMRX= d&Pd.6Ko>OXŹ n?_;ŢD#A@"+ D|'?+s,7n< ^ͬN "C\HVyBv:?OF[sͷ [AlƼ2 Mw15v [1s1۲ 34yOÙ"MfP[hkMϬ"W)ƑE62,l(|.9qvF-ٵ\:X;P*3]vYon,'~k)MYG9*4.u6IdSzMժcE٘+>z[nL"֚;"JީY-}:XUf&a"ݗNb΍fT,5, SA5@8겝⚎1է"yHd}Loǐe>^KliE" K]HdE1mrŝn|6+R e Dduۜ6hgJ@DݶzrKA "2}A#">Q#Dv<ڝK{oZ[8",b[X!n2ӥޅ!E4K-plzasbmÿyrq4pMlusT#&%FVȀ/AY>YoSi>Sd7<;ڼͧPqtk7Ydg0A'6?G|عf9Gq,OKq>r}Y*l;!ǹ= SAT"a#di75pHӐ2ݺ1%2$ׯȈ8[[lӱ6dB9G]U`YvZms9&ǣ}}BC"; V6bvI0PF ;oK(mn=JAdQ{QCtq j%D[(yI.ETh"ÒQ3b.}5}p\UdHy*j ["]p7ӕARpβsvyu&c)ݙLvxgaO'/fdq=Xd(=nv̴߆] `OϕhѮړaC_ ۭlF-x\\o ť 3'67s 5bG ?kU"c./]v+fÞWVoEe ~*uj7_ z~2$(m ;w6V( Eg&?xn,n`cwqeikǼ83滴Zdu(D6GJ vsq,95t<+9؏l=GRyJ !Q?$hylS6H3M*8F f}'O./a|gXop̶[.1xΏEFeYs|yj\׶nإ +2 =Gkx<>}zl6Gd~VsA3b+lcRTF,˧O lw5#2?kAF7gYL7+)2 צڴ9;r:YO[:DRFy'g (ŦsazxjܷQukr'랛Ȟ?ggMqmwu@}!rjz쌝!Ѹ"Hi1 %؅/D" =QfQ!Oph|0}m//Y"-QM8L?dۯ?2k|LU|A[\بO>8>nPҥC0FJWl~YX`]YYݖ]pcDmHm=6\ѷ1V LokYqAZ?6 ܭȨJ??,gӇ 9cu?Htp0|7}ISb7Dcu 1OyeIu-B\*Dvwuhy'7*1PV-DN"R7MZuȳվ]l8}/!?? `臫ߤǢbC~3NQ& 2 ޫ0P]A*]Y7kmvD~Â{I\E6C-9(فQvh_̢/ ,91gOg8E"=XO{}_}7>Θ?٢RĈb?G/1Ml\$6ci}8eiڼ(0|ݓ'}[~ دᎍpfx>ӑ2FP)[]-piyW[i&ZpM0>zcaA0iLx׶ϽvS)(6]E6E_gcA+N|YAxvn&)Ynm6aEOp4}!#|X>߱ Q-'/?Ç|Z~=:&t(qC<ٹ(.ut`ږrt:,~Z~&6JizF߸ ;C:!SF#OF̶08`b u\NkQ^]d7{gٓfM[oԤ(n|=\F&KE? 9 Y\pz}SYxYXh.'KD&-yZXV4;~7oNO%5˗sD)eW'4Sͥ}nU8\.Ѫ|tEW2mrg HqFi 0H` q!Zӄo֭jSWYИgTm׾AIGOTñSf2رs;3ޥ㺍0Pf6dzNu?vq%hqq yFݻ8nɸYzEN" q<>{Ƽa0{ܽ*MYwGD; 5 < /&\(}/2p鼼~;3i_|\dXfPv!/w$,9 #bG 38>H, [K,{p2?uVm?+cpIWyd{.zBNS>9 2̶!Mml^*zK, . ҕ_ ဨF}ߓ!'@~pk^Ff;`o/%KΝE̢n'={TUҬ~Fh˞l\E[7F /@(٠yDvU>1"χMe1/Y<|+x&7àKȰ 'op4 kMӂ)p_f"nU)Zܮmj] 憐ٳ% ;Ns p#Fyc?qْ0gryY#?cڇ1}E׈=obBIYt|ᴠ1lsQR-aYDۘ$7#4&uUi8.:Nhپj6 z-{PD(yDbƩ:37n],J?u| U2!Ѵ,?;kxD&^Qi|Pnl~j8_ժ0[5\>^JgtG}QF!bU /\ʪ]4G.G$2|ݛ>BujQ!Fsdeq ^]?׹(F LmcצD6,T/%]\ SMs܆ZU\0[d4ל$&he%K8]z7X27LD$2mڂyhPX~ ybwU! =NT"sFsa;4j8LbݡX\QDsW:&͛skm s61gg!9M>􁝋1UgRWG(u!b~3ac!yikvCEwԃ̛gXܡI1}svY<~l_: >LT`\K*]EyOSSU?L,?`nt_v7"c`|1Y ŢONNeqvf'\v۟T$$^S۲D.3|fgVGw#CfEA\^ʻn]_о'irCv<ǥ o?vj{Qt+$ߑGm&|dGa"ŢySa$#.Ȱa妼z??C"g]r}iU($ `zZJ ֏KmY&d_Sǧ*~bw9,9Ԇ^?]r%Xw$zëaUؙ,68"{"Ioɰ25-`Md5 uF=m^ٳa .ەpqV~x}²ƍSړ!eDWg2| vܶ\ivY\C޻+.-TqBnݧE,6k2.?C\37wl6 aKW2oW13oav!\W a/Brm[d yōUfq,mSM[P2(0N#c Ad;o S$Җ- (ntUPbvgaaTzjoE&"*z-HmFoyv__5JɆWM94JCY "S$qɣU|ds1EYȄsT7''tU<Q7n𱪢b L h DdfJ8׊ td{ N-2CC ۷aYTl~HV)wn Dfmޘ$G(NO Txr\gQu6D-^Y"s dZaYZM]-kDUWYLA+ vRBrv*ҡ6GyO%"',%! $Kp{Gf$6` /O-2эm[xE(d+}j },^d,~e,jV98BC2<6,{|޽k W;`/`?5l7]W݈Ni&O'-"S^YODlk0Y}DNJ ϶gR׮s8i p5iTv #Cb]A3Yd?!8Y13`HU:PhfAaXdHknͬa߈#&dN hއ?/牶3$;1t0' ~ݤU8nJC5.1tUiр?s.2n&1ՆDQԙI ]ղp~O-=s٥s"tl^NSM *"ǩu?[ܢ\Ȫem+>y^_6=`zFWaTC۵s]t{)`tQˡ9>'k[0*܌yy"@M3˸ eUw7n¨|Z ga"ltbkx<ӺIޔۅDo:f~ED0ꧪcf廫!nS꜠|,1&UK_o= .؋=>.fIUHd#cLj;~mbNdE4D0Px'2_ ywDvڋG/X!1Ef!2=[W"3]>(ĢNmηy]|8(;ļBަ˂ylh#?\V2wW"c1kGW oK,|}z&1OEee&so:G3&ˀDuɼaTqxŚx*_z_*%Feƈvl#4u}W3 ^b"3<6U;A1'. +$jMqٔF~.L͙J"Y>Dff8v7;>O"l^*ﻝJd4A\$OIvGX֨73(xUhb"Dn_HxVȊ/w`BdUD&1cFeCw"E&+ 2VdzD3yL&73-sO4˱ȆJD5!&j;%=ɍLv?oR^&AlH!@r3aGZ%LlL\6zYfI:Hi"2y[3_d(f}jYݖs`0bM^F $BFLTDz!aTee aN[ǻi(ș[&0xŚ$e-9i56L?RIBcwě0;qe($`#id Eo1=V<_@pC:HS1"kzY;P!l,f D9ރD3 y6ݴ{p)X;D,qD|u+{r\x)~`xSVÉ[dW#3\aEON{*zcspソ-ƽDBY|TZFؕh@E y‘{6>dz|Y#a3:x~kQ=/End+ rwM}7|7^? `O"aCϩYVd8DMGc+J, {7BY>Q!MP<fzf-Wf(d`p5XhfNL}TV^:f\= s 61aj̠#^y!KBs{W0g0?yn'#`A1HC?1XfMdKDF tL$ 6"tLȢsxᭌaK$"#;cnSVt,,ʺ&AؔebtuDdpURUZbұD"!R\&眶tLwLFdfx3Fc)O{hD yvN&èG$F"ۅ3s+F" AA4{2pYٌЮ>{Hd3DlgqYM EGڄD5`7J@B&,>ps<& "R3;r)BA3M-VIdAP\ ܀%IbBW7 0 Cg63iN?A&/PnU3E~BSB!qLڂD(py dzZ1_jNE"#S Fb'_dp31Փ6 ߑOu~KHʖOdx"D= zw(!6c<*<}X[=EM c$^r5Q?N/lzD#<EQąI&luT7+ۥ) ¨e<;qV3]a `c?/I0볞bOagI jw?af=!GL,/IdIS'/Qݿ/ ZP=SE&JXTQ]^^&DvzHW(/IdTG[ggrWCZg~ MߠV`2>S!k"zEuL:ыl9LiLǏĶ,ZR_axz!-1,Bfof+,BS¨XȦEEƴ0*b>&2:]RaD7uz YYP@.IDyN@"#ː2$2 ,C"#,XdEa`308UƂEV!n `" 7QE!NPU,\daFrBEb:T&lHd?5Iu=.лD AXDF!eHdaAXD&2 $2 4]Ҧ0 U7Q)*.Hd52 Y\>(\0 r\vل-Hd  Ӑ2$2 ,C"#ː2$283$Nv9>r uۏ0!`7Q)j zY{'LN~W_L3_KE?v!ie64-AXDF!eHdaAXD63EA5l Q-@0%B"/ Z&$-m'8.Պ lPEOD6Z ϩR!$M0 eHdaAXDF!Y*r==$dFaTD6u!o "8$ G阦0*C"3@wCBއ&DhȌptEXtpdf:VersionPDF-1.3 3 0 obj << /\IENDB`trunk-2018.02b/doc/sphinx/fig/simple-scene-plot-1.pdf000066400000000000000000000621171324306050200222110ustar00rootroot00000000000000%PDF-1.4 % 1 0 obj << /Type /Catalog /Pages 3 0 R >> endobj 2 0 obj << /CreationDate (D:20100711110742+02'00') /Producer (matplotlib pdf backend) /Creator (matplotlib 0.99.3, http://matplotlib.sf.net) >> endobj 8 0 obj << /Pattern 6 0 R /XObject 7 0 R /Font 4 0 R /ExtGState 5 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ] >> endobj 10 0 obj << /Contents 9 0 R /Type /Page /Resources 8 0 R /Parent 3 0 R /MediaBox [ 0 0 576 432 ] >> endobj 9 0 obj << /Filter /FlateDecode /Length 11 0 R >> stream x[eIr؏6~y~Ԡ#X3ےs}TuOL {zZ+/q̈Gyꙟ῿xgzϟ~J?{~,4\Eu_Եo>xCǏZ~?<;X~r:{UjNv1zV4^{i;-+]uN6u{ի^AK\ʙޮq7}GZSoʥYF=l~uFK4k'z^t]M9_+ 5kSOўeJʥ\d0L˺z,=kǣֲ:Cn噯ЗiX檵hVsWʅQ0w2>czum׭3$&3K2sϏҀ̙8#2)u\Hz]Ҟ9i(W좣Vg]-[ʵ&zϤ(Y|МZU^?LMŞm̺WdHpyfi+1{Aj,sϼx5.rEGI_WHq- ;[yu~"knLk$!\+PvnFf,Ȕʠ):J` ;Óuq}u?hhx9|V ;o\nY NN;mР=ϪOÛ*x̄-Q}?(%1R=J-;nE\Ǡ{>3?Pi]֜H+)&FV0NRzz}]},R ӡ7YI {3yϻ hcӾt /(gae?y\`-͘AS?> D`a|^wG a`ɯўJCP$h!ĵߗN!E%eM !4i*=xGJ!J5y/O1Pw"ӌT+&,s ACMĮ2Ed<鬡ϛh,je I<׻US)m*"ZMzI&Vcr}'eXTMY}"cհygL!smt{;0k+%q =OaUQ =S{:f@^^~9!o&>|Kc?W] ky^"ڙ}x6h(MLa;yJo1Lڂ2yZۻ&Վ F}d͈.쐰@zG<ƴ(zzhd/k_]=d{\Y*aA܆4no) ?#h1RYx Q#݄'ttQbJw;tKvjo3  k3< O% ugxooV swS#+C<]4.ق`8h+Nn*1LT3.(7Gx@tq H|FGv,g;'T^;+,ߒ4?8Yc!|׏|1Փ{Y0XyG~1pPnӉ?o0 K 0 y{xa csn~y.ŧヘ~π>o0Х,K!OCk*n|פ+w{C||dwwuOC쏯յ.߾ .~$PC"׻c㣇 ;ѧ'IAү:}߯uo yS@|3o#g>}7>~m}kQ_yPO'%k?2SX >^o߯)_Y7Y0(- <,hG|-nLO<>{6^:ةԙINw>=I,t2^IKCꛙ3Wk7[MbV,\cgB' _qF4^Mw aSMזٸLBiv i9PqI s2Y+ֺ/צc`ЫC['HG೶x x&#z?2>䋶.u{M!UE*݅3(e>CӨ5ہz2ebCҽԾ JD?eʼn.# Dͯ_psgmv_>ۧT<;yGi}|\1[ )ދNW|WLSKxlyPn1r6;1*'L7y~優|{t8i F=׏pVxqm{| Nb wm(&1`}^aٯ:О6|?vQ0ckr&_aڞv}y~W]|>{ Q鿴)R!s5>VWw[ϤUO~-!zWn2o^N{h8+Vv/hT'#q%|fi\ 8Rr>.Fͅ>#6_aB^tc|Ǐլ&m>^/) w|-^ʹ7Md~vm 'g431-/]Ah`9#D_~(!Y#J%Ç[$ue ž!‰?O(d&Llտrp=ۙ-(y1s^(9gCvy\QPud`T?L+{?Y]_6^TXL]۬ V"F薝x.~,5LռґxjFW {+[ۡ\NfFWXL )B:F"אN`v,{fډxCkJrKUR'"o  5_l1Ά6s6 +  Ѓh/u 樯ݬڂOVE7w8iPsG~~vxB/¹ˬ6{\^Q$+׵4Yk'>jnϣ87 ^ry۠ŀQY@c˯݈yzEuҴ dyMyT:W%nOxX˒/ߚw7{D1U,@ # #b^R{FU/LCcڛ[8jgjm1io9tyaje)|6F;̈́{Mj~hrJjJ^}6X>V}GsN!m%v_oLdZڇ ]׷1Hyg\,_ӻPg/QuZh'ėW5_f.y@M_[7dx7W,\*fŒw~3,,nK0=EǯD[ :&ϖዽ,oW"kRNji?;1T>׶9}WGUpг{vWǪy՗yV*ƨL^Kٚcp+@vaӽK._9rٜ|vki%s{Z9)-ſ#۸z+}i6_ks7'/_{gr\v)q3iQ  ?6T^Ԙ'q01u<_6xZuy+xihaAkΙ53ܲIku68'{< O S,T^  Zd{/[)h4#>oiƚ{6}`Qmn=If`[@"(l N3PS ZiCbX 3C@c& ^E!Baѹ+% apf%x@H*6`)023Ś|m 5Ҍdn[Udp~q{3@`ޘcot/3 }f[j)+eSqU)01-Uher7MJharͷ%X19!iwi-Vbquj)fXk ]p_=2j*tA@+F5aajR(h#ՒM=tvmY$4jQ`WT 1E 1df:I&*Fٶm7eĶî:&`uwz@*P T@*P T@*P T@*P T@*P T@*P T@*P T@*P T@*P T\UN,(@ P(@ P(@ P(@ P(@ P(@ P(@ P(@ P(@ P(@ P(@>KXaԵW%>mXu K-Z݀}:ukgJ=#_4֖v+bj-X4GsFn+喬I7Y񂛥Y5kR*n=KiW2[˹]kcr-շsVuG[,L8knUusZjәf9S&|gsb LVYsVsʕLimP SjV-G^NŤrY5mj)WM 7h/r Zr wj2[,iQ3ܭ&$IUJT-j zFwY_PB0>Yn̴-0\rL{cս=2s׉j3p90ВgXk.oTL ;ob6z`#7BBǶ6%`WCxoCgnR^HB+46*>UǶ؈a%.ӿ633g0>rAvOVv75a`=Pnݙ݅fִXcWʦ9,rg I{B`RPab]ku`t +{7ݵ>wι66=V8dۓ]T]iAGol(!Pg٭T5rOq5j6 %3>Wmp5ʪXx 5;}=oR02QٰE1\9+ ; +}X])4-b`hQfmƅB̆b'G'>$ F0˚wVF]˴Ima:#wpn*!0B0,jBʖ8rP0ψ7q %DL\ e [)8%@IWYVš3X0?7L k;Zݷ{LS{+#k>1)ʪa$>5Df"]MVxv@XUnf2 NQ6ͻt 8{ڐޓPݲ3ْY(Sh}y6k&[|`cM՝MenJ/G竨p-ɦ uͲxxU5rc>:VzZfNᶊls[q3S|lsgygI3%f`m, lUU.znLJnUr\rZQmJţM57y_FV*E7e ZE yWK[l nξfYRҮΨr6՗keUgzޛ)l6{UA%rp n+1!x6[vjeFIqQڸ:?j!v5rY9U-hkͺ­Dil[ V}#w" LF9`" !ٚ4([{bR@\ɿ*fbSxgm՜ 4K9bqJrE p`ͺ >VT>{u@&^jkHg)P tТ9*mrPp 4 `` UkJᅠ9SYyT} m^ 팋a[nvZ=볦g Yh#kM3p F6崬udi`_FZ]~V@h3÷pFK- ` :CxVҔ0*8 $yM[XYuCV 6"Ch8ː2Y{E1!Y{}-9` |,Li8C{a}ìAKoucQ1l qț70 :_32ptJu#PB{KfpXWh/f dۅI#uA`xٴx~ KFtKr&3EJmάX>?QC6ͲmB|WWZ2[&c AN⛼)()t8m+Jԕz9EJ}9?Zm;m)W)GiLQ|T>=PUJʭ fJCT󾻝tm;p,Edf.7f)i&sWT+/cev̞] f,3V CS3TĝH ;<*}\˩d2y`~An}ufJgp0R,nb%ꮥZKsUI o}go]e"s4.6d'n㢅Erk,)%ꭇeQ]QJhvVɄ䱛>S]HiSS sW;p xeԫM0y*Hszs~T {xm(GYm I G;83U©mM|nL׿QcČQΒ ~,?0(֟cz*GQS VKϻr$fOmX_V>dbU,U'ƃa(IG:URW­}';.t x&/?z=V ƢwfSeg0 4RT['66r 3RyZ쬼AU} X[J mj]l G] f&$%<L 2 [5'lc;)p޶bbe4:bkBy)yJ+" 3,+gG"43zjҼr%JZ:|:Ͷy]ǎՎCHbwGf N-@W:HYR6E%Tt4 mJ'h녙%ڲTT s(? y&ݱ˜qh=+;mg[Y*`"8Jcpn].lI-)[yU@29@*P T@*P T@%ԔXٲE& P(@ P(@ P(@(ϼy/lI%umU݋mդ3Kp7uzTng)2D'[T&ECyTw-+WiOR)f-w39.[:'trOBU&=5ZI{HEdT3tA靲sעݹQXO*ʪ0KV:$^I){|Ҫ+w!6yAZKټA/؄s_ۃ!$ppe'%.2= qpk)"sj,LնN:_5n}NzIMWrG]S\-uV'PpwUw}u Idk(Jn=´huoϬ8gUmznVriwS+9"N>h>Wm]Oynzj]ձy+tp otk iaT-o4}yO: dꢩ:0x+54gԚn8lQ*ԙawpRi$/XPr^ݥۛ\ {BBm: Or,ֹr`{“khq$*,mQo/S,UYeqVTï O6%DO4TdU; nrH(]Ot$F.ѲsV~  a:u%q.7әoǟMcvLC]wi10ΆS\R5ܵZLG]ށ`\Qt.vWe;);qܼ)g'|N Gr\>.}mYnpPkdaY'B*6.Z]v(DŹ@3BkȓGʉS)I& SXSXce #ƵR9&3hK:ӹR璼Q3σRt߹[,sa?1ˇq4vͩMw!Wy嚝pa2'h04#ő]ԑUw~W?a41Zx۹WkH2/0t.' .R(G$02e]Ȏ<*{<ǥ T͈nm Ԏ5(΅ҍC'XmL,}]B;9q@ :{Kd8dRp1*兂*& Yd; m!8@>e^.ݕ\ȹeJt(GSn+ ɜֹlIiFRR\'6lTےk^0ҔM${9Yt/[lB~LmCj^E.Bs`SS~sSnuUfTg$->(KNJyYL}\zI`ɪ ;08w`36A.Dp6_06-)rPD0fWbj_68ƞlU:„ UI{"dִ?-/f}i:wop=ۢQO{ Sޡ*^{*ݏ7\3o\;O0O`!GIuhv-K|l#N˽@*P׎R2`%r_(@ P1Z6d篞ȥ鏿,ϪVj=שσxUKsRϵoLZ*d}͋Q?>Y^V.%,VKzs^S{V\u㡞/NKv-n:dNվIp?4:Aۓf oG.D2䀨}^ }^u)}]?g3jYBK@8]Pk=쒼<"N vF˪Yi_ט*0+j,JL5_NY{̯ϯ.pT2c"TԿSU]%>KIba#]ܖUy*ܳOL*O1DϳjM~8DVHf4ڛΎ4 ex?DuYX4Rv><^|G`D9Sn+Boɧ4q8LӵV88SO:y~N~7(z2)S!GwIݻ?a>3=@oPV/,sX9i/7(g07}i~ ԯs ZYTHEZ~S߯՟yTLυ>vz}w?33INEX{oecM~ kI/K%_zhY_9d }38.Yk׿:ߜAŘYnt1W endstream endobj 11 0 obj 16473 endobj 17 0 obj << /Filter /FlateDecode /Length 304 >> stream x=;0 C{Ȍd'>2VI(/u< i& b;w؞D/)ϡ+E:Ū0[M*K õ}74uK hY pu;Gw5<TQ!OJ|<(!\{0FS@\^BAjI'> stream x5Qq0 54sۿ @;a@dJ\UGM>`!S֖{&UF!}W2j]* UYFp&I8d Rӿccz endstream endobj 19 0 obj << /Filter /FlateDecode /Length 68 >> stream x336S0P0 F )\@>,́,# .C c0mbl`fbdY 1 r endstream endobj 20 0 obj << /Filter /FlateDecode /Length 74 >> stream x= 0 "#4M2i >d(]iBIO[_mվ*K{ j26|w wN endstream endobj 21 0 obj << /Filter /FlateDecode /Length 133 >> stream xMA0~tzJ-6팀03 endstream endobj 22 0 obj << /Filter /FlateDecode /Length 163 >> stream xEu1 CsUx:?G i@xx= r]Ņ ?޶42܍e@N"WI3Tb\/:"̒@#|:C[ۙ~:!**na.@RԏQꚡ*+kjڿ"}\Ne{g+W}: endstream endobj 23 0 obj << /Filter /FlateDecode /Length 317 >> stream x5RKrC1ۿSpΘ}tj'+-@B./YK~%ۥW%B>R-G- Q=2'":xa>N)x_xN;2$KMH=I+4t~&+s{rj X+)$=Hr7VސWg%&&MܕBXtLX㰄*aՃM5fcdxLP} #GMv²[6!D3,($Nc$ Ұ9 9e, mh%zМaמE[{ endstream endobj 24 0 obj << /Filter /FlateDecode /Length 45 >> stream x32P0P4& f )\V.L,іp "} endstream endobj 25 0 obj << /Filter /FlateDecode /Length 338 >> stream x5R9@ } ] v͜~߆_ CVie!U-.Im W%ڥ Pt,6˯JH+kLwIi"Eo7o}=@.^ AS(i|Ъc(ew 4<3}(~_K&(? _osџa`Ś}@*z`yT endstream endobj 26 0 obj << /Filter /FlateDecode /Length 90 >> stream xMA "OPDtz_NE5jK02kP)U0\ 2IL{qIqzz"X endstream endobj 27 0 obj << /Filter /FlateDecode /Length 236 >> stream xMPKnD! s\I$!CU۱T*HUSbuM2yOQ nAb$<4#',;Ofč.`5[́9m:7@dP "B.ї9`Jw&\>աqZ coYaLq_֨ɲY I!}{ y+ՠ0u J*}$]S endstream endobj 28 0 obj << /Filter /FlateDecode /Length 210 >> stream x5P C1g dVukm;aBXȔy)K>:L." u%ʚ +`p&^7`i5tႦ.B%|u{OxjrvC` jMX> stream x36P0P07W544U022P042QH12443s`9`a$r`Zs: P9\iM8 endstream endobj 30 0 obj << /Filter /FlateDecode /Length 248 >> stream x-Q9AzBsˑ C :-qPO+Uwu9HTM]vf5,?c 7zqxLu5{kOfP2+qSușO \ ȹeƌ#M!RH&3AQ~#aU#j \Ks4;<9GW +ET<pC7ҹ^s0XM7/=[ endstream endobj 31 0 obj << /Filter /FlateDecode /Length 52 >> stream x350P0PеT526U05 LR \$S as*sT endstream endobj 32 0 obj << /Filter /FlateDecode /Length 72 >> stream x50{6X`KEoC|N/Z #pu#])Ec΂q_H1F=#O_p} endstream endobj 33 0 obj << /Filter /FlateDecode /Length 247 >> stream xMQmD1 \ky R]oC /)%K [UC?13,=?TPbht/"+ߏe s`&4`oI&ռ3d‰ATwM,3V7: lx%D`r Z`Q+ tĺv7C/਺x} K{,|BL;wI#fR:=b}@e+ (\* endstream endobj 34 0 obj << /Filter /FlateDecode /Length 49 >> stream x36P0P040F@B!H Y@8&+ & endstream endobj 35 0 obj << /Filter /FlateDecode /Length 332 >> stream x-R9$1 ~`LtIUls#h/#xE=f۴[iGiK,W ;BjW0wy.2meDkag؏]e8*Jl !2J'Qw\I2[E™w2;yNE{ kF9+%|6vzrYɩHHӺ NKؖߗ3| endstream endobj 36 0 obj << /Filter /FlateDecode /Length 80 >> stream xE 0D{`~&f( JpO{:2Sa ,S`5FR죰n_uzS*Ovvq= endstream endobj 37 0 obj << /Filter /FlateDecode /Length 392 >> stream x=RKn1)@Mr[T /1 %?ꒈ3L~r]Qljg!.6Xr_rњbO/ȴTXVݣC(-װr{d`Jn@CHYAaPl( WԬtb ) ٠[]aP[[xfޑ3qYk?=Q2QMg|2RCgB'`$Ip#A 1qOl)V;ޒ{,\L'ib?lK\+E(~Aq|XdDw#h% 0xyDhDԎ=(ͱ&{ǫvzcw. endstream endobj 15 0 obj << /FontDescriptor 14 0 R /Name /BitstreamVeraSans-Roman /FontMatrix [ 0.001 0 0 0.001 0 0 ] /BaseFont /BitstreamVeraSans-Roman /Widths 13 0 R /Subtype /Type3 /CharProcs 16 0 R /Type /Font /FirstChar 0 /FontBBox [ -184 -236 1288 929 ] /Encoding << /Differences [ 44 /comma 46 /period 48 /zero /one /two /three /four /five /six /seven /eight /nine 95 /underscore 97 /a 102 /f 104 /h 108 /l 112 /p 115 /s 118 /v 122 /z ] /Type /Encoding >> /LastChar 255 >> endobj 14 0 obj << /Descent -236 /FontBBox [ -184 -236 1288 929 ] /StemV 0 /Flags 32 /XHeight 547 /Type /FontDescriptor /FontName /BitstreamVeraSans-Roman /MaxWidth 1342 /CapHeight 730 /ItalicAngle 0 /Ascent 929 >> endobj 13 0 obj [ 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 318 401 460 838 636 950 780 275 390 390 500 838 318 361 318 337 636 636 636 636 636 636 636 636 636 636 337 337 838 838 838 531 1000 684 686 698 770 632 575 775 752 295 295 656 557 863 748 787 603 787 695 635 611 732 684 989 685 611 685 390 337 390 838 500 500 613 635 550 635 615 352 635 634 278 278 579 278 974 634 612 635 635 411 521 392 634 592 818 592 592 525 636 337 636 838 600 636 600 318 636 518 1000 500 500 500 1342 635 400 1070 600 685 600 600 318 318 518 518 590 500 1000 500 1000 521 400 1023 600 525 611 636 401 636 636 636 636 337 500 500 1000 471 612 838 361 1000 500 500 838 401 401 500 636 636 318 500 401 471 612 969 969 969 531 684 684 684 684 684 684 974 698 632 632 632 632 295 295 295 295 775 748 787 787 787 787 787 838 787 732 732 732 732 611 605 630 613 613 613 613 613 613 982 550 615 615 615 615 278 278 278 278 612 634 612 612 612 612 612 838 612 634 634 634 634 592 635 592 ] endobj 16 0 obj << /a 17 0 R /seven 19 0 R /one 36 0 R /f 21 0 R /period 34 0 R /h 22 0 R /six 23 0 R /l 24 0 R /three 25 0 R /four 26 0 R /p 27 0 R /zero 28 0 R /nine 18 0 R /two 30 0 R /underscore 31 0 R /v 32 0 R /five 33 0 R /comma 29 0 R /s 35 0 R /z 20 0 R /eight 37 0 R >> endobj 4 0 obj << /F1 15 0 R >> endobj 5 0 obj << >> endobj 6 0 obj << >> endobj 7 0 obj << /M0 12 0 R >> endobj 12 0 obj << /Filter /FlateDecode /Subtype /Form /Length 139 /Type /XObject /BBox [ -3.25 -3.25 3.25 3.25 ] >> stream x}P 1 5oUܤmNMpi  ?eyC)aV$-}bQ'jSjO7I# S AؘE̲Q:Eސ@]fJ^ /[ endstream endobj 3 0 obj << /Count 1 /Kids [ 10 0 R ] /Type /Pages >> endobj xref 0 38 0000000000 65535 f 0000000016 00000 n 0000000065 00000 n 0000024780 00000 n 0000024400 00000 n 0000024432 00000 n 0000024453 00000 n 0000024474 00000 n 0000000216 00000 n 0000000451 00000 n 0000000344 00000 n 0000016999 00000 n 0000024506 00000 n 0000023067 00000 n 0000022852 00000 n 0000022381 00000 n 0000024120 00000 n 0000017021 00000 n 0000017398 00000 n 0000017791 00000 n 0000017931 00000 n 0000018077 00000 n 0000018283 00000 n 0000018519 00000 n 0000018909 00000 n 0000019026 00000 n 0000019437 00000 n 0000019599 00000 n 0000019908 00000 n 0000020191 00000 n 0000020329 00000 n 0000020650 00000 n 0000020774 00000 n 0000020918 00000 n 0000021238 00000 n 0000021359 00000 n 0000021764 00000 n 0000021916 00000 n trailer << /Info 2 0 R /Root 1 0 R /Size 38 >> startxref 24840 %%EOF trunk-2018.02b/doc/sphinx/fig/simple-scene-plot-1.png000066400000000000000000001566251324306050200222340ustar00rootroot00000000000000PNG  IHDRX'sRGB pHYs  tIME02 IDATxwXgǿOP uUA*UWXZ~k:hqbbUD:ZUq`P+uUmQT`UdY?$$!$$q>I>}sÜ;wAQSAB  CHAd  !AA!$ 2AA  AB fu]v%$$<}ήK.3gtvv.YAAƍ\ߴiӸ\.5AD$***..ŋ<]v3glڴi”cǶm͛77n ,ن ?~q+VO<~ A5wwƍ ={Zv҆DwΘ1СCNNN aÆoӧ###:0  j8O<BP޿>0K///PFOQ)**:ydխ`ٿH$ڱc|GYYYQX_։դILaooOcD j\./((pqqQقK.-_aÆk׮5pT:C(JWZpO^%]FFƈ#BQٿٿ}۷gff8p`}ᆣrBLa_977W&=33SeYgϞ ,`^ܹŋ'cALsNҁ' " }gvvv5jԨQ۶m r^zn8*!k׮MNN]mZj%ɒX[7ne s>YCn+o@ HyRtr -X&<<ҥK-噙2 @zz Y ׷oȇ޾}{ΝC )s۷/nĨQFQ޽~իWΝ;pXgD0g}?T*MKK+**b ={60a!…aHIPJsA"' >FBggB7F4eee-$,'UgC( 2d]$ ?>W'PGDP 0dZP@Z@(hӦ @B  CHHIR ޽{;͛7Id@&tR$,ZqڴiU J-!$ *;:+6d+sJ0yҶΞ:uMݺu}}} 8Nhhի|}}o_|\\]6m;FDecG8pV^ݲeK>_v;İ>:{xx8p@kߔ??Zj5lpժUd+Ν;'rJj?k֬cǎY&>>>**W^BP]N>cǎ{ŋO~ȑ#w㏇zԩ &,^ȑ#0 *^^t}޽of_|Ei_reܸq2" PT&Ʋ[hL%رc N<9i$|@jM@ʛ z@GPaG>|?zjց0ѣK^^^C}Qzɓ'ᄏd???gQFѣov?~ 5nD(C(4?۳ܺu={{ǎk׮믿$֧T#jԨQ=.\]vssiFU KgF0xٺ [:#D0Ə*Ng??zꕶ{w?… -[V ~GW^-((Pm0h ߞfhDXͅyr6ÇD#Gꫯ6m"Ə~Z~}իSK%<)%%)\Lژ9^E"W^M6ݳg|pΝK-Sm߾}ӦMg͛7-ԯ__.gggۗ\.u 5::"'l23g^vŋ 4(k׮]v`.at!vn3ba6h|5Sl!* R޼P)G7#2^رc7nܸe˖={8999W9k֬YfyĉAAAÆ qѫWԷLOOgN:梚L2 3aMNxx?w^OOOjذappWRRǏ, f7b|DkyE5Φ*%hb{; >w\&:BǏMLLLTTO]0xrXOo> | iӦM6%*!45ZP8x1;*fĦ7JTa-Hy CHpf3]lSS/kAuJg Q@Y#p_H! V[B9`2ڈ6"n)O4"ܫ%@|>a4z"iBEDD|tKT<oggI[miLx@pWEɰvvv|>ߨS_l-Hy ChP%s %JI])pj&jd܄ ӻwoT`^&M:s̳gL4ɓ' ϟ sX,qFZg Bvχ[e ar]h5t) 0Ӳ'gSLqqqspphժUՕ˴j>}m[%=@6`ȁ I-X$|pٺu+u+V'*dV+?~;;{ԩnnn666uMNNVtNhhի|}}o-eggצMcǎxׯ_޽}f~7nܦLQrD?%-[pSfAK*ʼn& h/H$;uaVft9Q+W(mYf;vl͚5QQQzR޵kӧwرwޜ>}y… ,Yrĉ-Z18BpҤI3f8uT޽?7n=wݱcDž "##o߾=dȐ#B'|e˖*d5kA-F=޽r]!|_/!Jc.]ؿݻ7 >/(m+W7n„ KUyLFHx{{h"<<<,,4##?d#;vؠA'ON4I~XO8qС;S:H$=zhܸ͛7-իǦlҤ҈0~|M6ԁV<%Jwӓ'0ÇDGճw}W_mڴ)$$D ?>,,A[~}ӧOU/kժѡx8PڹsgGGG\ޥKB2 qe!P -O@|r0|=)o ܞ9=rv9sk.^ؠA[:::]vڵott \ O7NOOwqq)pݻhѢs/SSSk¯&&7: + ΕzkڨT䖚ŵyM{2%0V_ᇣGUaÆIII7?^PP.NHKKxbPPP)\$rm; ߗ aUukxy_駕zkr"AD%?7oĉԩȾ٠A͛?x 7nXbSRXXdɒڵk63УYr/; svvn޼Sbbb MV]jh vvP\@"8APׂieTqhDCW3߿/j6-tgϞ!CEGGZ*$$Dĉ'5N:gϞmذjp5>3ds/;}^̙3dȐSN߷Usrr,H,==4kV ڠ)|@I&;wԧF=I,taHy NNNWXy,^xٲeթcccbvhDXԯ??DEYJbbck"eظ|#e;5j2[ R Zqgӭ}S@VM H€A<5JJ]XtR Cs\jWSwr UVmaKDCZhݻ%L=s_X/zhL]\(FY,5zMJziHcNJJq WO/@X1 hٶn(I(ƴi~ |Mvڝ:u" S|~HKCR.EB e tO2:v~Ad̓!Y p `x41X>AѰ% Ch6iB<@fo5؞ABײpB)Od?[ !TU^ dqsvC e,HHy.f _ݻiG}![>1cghgg_E-,v,>>(.eppjqss*&^ɝk׮Oue̙κ!ݺuKr̙}Q3"s۷o׹A~~yz_umٲeVogCp%1)QTtϤ?#>oggIKTN|mRRRƎ۶m7olܸ144422f >|KUe ƍm_q܆ N:uş| [^Ċ 9sG pF B>GG#H}XGGnCʛfeeDlEXyE6˗/W͘1#''Ig;[x^LeT|X,{nǎTG*Xs_iΫB=P`lo)o-,-YT,9B6::zÇ;vXUvС(**jԨQyyyQQQ*lhE`<ʥ"aK4*Tdr#G}hHyR`K;v8p7j)99yo޼uꊋuqqYx 0rۣY={ >x`ΝUˁ+WduVnT;V<~T 0۷o;vnݺ׀p@ݺu؈PEmmm/\P^VZUPs)/*:O,ZnQ@ʓ5\vo+Ç666f/榤HIIIII)((HOO0aBrr2ɓ'SRR^xqᨨ>L c(*s ?TA( j2ŚEOZI@ʓ5K.-ZH.gfffffd2QzzzBBBdd͸ lx5k0 3}ta֯_Ꚗ*>r-[:gBmRI8|@R\P 4-b戬)OdbccQE0 ?JRIHHؽ{waaa'L`k0XG`,е06$@!2$MRE *>BáVQѿ?*L" H1:wܒVp߾} <)O!,'Oe2tPπj5:Xa:u7`HyR CXY]oAᑄ#=5K ExV}}ZÇS[R'zÇHM5qwRjj!ph&d= <@8… )Od+5:u*ǠZF)8HmHyR CX1yvBCsl~Gܨ)Od+;&˘VP2Ȥ2"QqAd ;VPۚ>4<pNxQ[[R'VvAǎ&͎bTۚS"XY38@}@V' 2U܄I1N2B1n9` (5#GRC[R'VLp\P2e $8 @& p>t,' }xa.& Ԣ`:uP89;`NCNTXXH mHyR CXpvFFΎAerrs4" _;ZXh5U Iy aՠwo#fGcbc 22L 8jX#ZZ*0-3M Wce^o aK2"Ƶ <)O!r7bYћX^ Ae!xhҤam 8' Ch5 ]( rx<1:p499*}jggץK3g:;;ܬ`ƍ \.oڴi\.\`H$ܹ1cJ@*.Ze۶m6mK.-Mx$bP ,wK ;v-ѽGH'v$mD/# ?>ݓV'k2)))cǎݽ{_zs 6<|pƍ_{n3^ a߾}ǎQ999cƌqwwoѢ=zT ֲzef]4H$gxNhuW"XFS6^x `㒍2ے 2|>}lr̙oF(9sf֬Y͛72eÇr8aݺu[ly)X\PPp̙.]TіS-#eN[sExL53QE {puqeǑ&Bٗs}a<==ٗ^^^B0--\`+V\tϟ?_pamCxWA(.eE, @: dL߉> <)OD;v8p i%T'1++BHxb[n޼Y ,[궙_ Lyk?jT,6^bR"C [*]i_@%emQvNBN[*5UQ"XUCy* Zer7|?DDDfۄBaY#z̙Șs 6"MIIa))))))&L`WvQ.GDD=~xݺukժUmKe!KCPj:%fpk@8ʨ/p4Q}@HyR&~ҥELvn>A߾}###>|x;w2Čf[P5kְӧOgfiiiEEEV^O2k.,,8>|O8d@.<514X+$ HC͛7)U ILll,0}ke~gWWWT2#""fϞ&L`*ԛ̚5HLDL V И #|='?+{zfGҹ$E6a͎ f+'jPjEzD;~CrAOxwzN 2 4"Q>Ρ#P) _cyL1ZCS!`P@}L#8zF5a͋Nj߅O|Q6@rDQAЈ 88 ;P8VP6Z|Ykr\#XF8LF-!N&螰 :<)O!t Wvf5-+z)O^ax5 !IyºhMjB)PsBԨрA-Ղz!IyFU WbŊu3:aHҧFY oSz |}488͛7d+@ Ye <)OoqX,6n_<F0cF7ߜ's85 6p4GFźKիr )O;88dl-l2@m "RI&f<5qsGtQBԨڂzh[6jy;6x\9r$J"M tRZՉ3gμz֭[m۶24"\*#e$ظ|ϢU4"jT5KmQ6Z; 4,>RB׃[[/aHtҥ}\<Qɱ4h/_N8Ѩ5dM'|O8јؘOԨ砀A|@ 5jcuQlm6p W8t쐖^h9ƒ|7o,zRRZՉ#Fa}!"𷆃p=IĠG=5usXP95ڴnSQfxb-WZEM > նꖃ|ubʔ) !͏F GfAk毙.BȁʩQ9xhP G# 9!eK[ R:ѿ%Ch"A\.c rہYYf(KRSSeR15QZ@kQI J!rrr,mL&"H,bT;dѲWPL]s3^SZ*ǂ.esւdff.\pFd̘1zv!Chzi::hu !~m3%w`H:uP io; )_-Y~}AA͛|5kBCC6m_مFMA X B-J 5pj@]@<x ioGm<@}N]Õ"|iذw:um֫W/( ԢtN`K[_6RM*} 8xB,'*AT888~@ RRRlODUbZ k4titoEQF06 gG7ؠ͋/jzpȀ+j]RÕ"|e˖{aÆdktr J-^^LJmFUI5\5Mk=R{°#HԩSL>]$-Y_|]sU/#*!KCjo>_~ڶ5}G{}绍hDK%pءa =Px)! WN '@/Rw`>?9"# bccJjQ(XH3I +2//X[IEp@KEjic٩QU@U߹5"ChVaθv ƙfe;w{2pT Pb!(RN*eH%ܽ{wO<>0̙3gJۅXB g7] 5vMxWF֭[knJ4F;jk˵kV^}˖-?gGLlLi5[ٳ!c H7Qe|Uz>x[_ &"[F~WhHUrT//ۦb2MemveFkV<|ժUDiDX.IR@ Hkm Y-#/@=jӇ{)rpzv- {6EAsDa5VyCWKڵkf.4"4mg)//\6m|Ҋ'&%J;KU5&YAf5Qrm W9"trt"2su~EDD4mTRN ֙QI&)-jTQy'EI [fQ5WrrrҦ|(ұrX!,v*omHļy_FFFjmpY2f@3I T~0yKU#T{2 C/5fJ>\;E 7j7%Fv|eNseB>B#LEPz_AfHҢF ,~2l!W?& \hSrD)]CT2@@L+2SL(T[(^&`Phќ9vs|`l\Ԍa0'tNMnTm]eMd'hIB */^H$2@QK1/c^بQYBQÓns쑯z;ݦbmʓpW#pTIC&Ak1PK{*o[M}36^Ƽ(FKT7vjt֘Yʼn֔U?0jSM's_sG_k˨WHy qv&I*CxO]ZԨSaK ^J;dȐئm*Eؒ0[[ xBQ$ EJ3IڬH~:!0eJ5*j8j*\T1U.)*tnރ{(BAւ'hDh$2,mۢ^KiQFeQ`["p"V,Qp\$M ʣ3]"B0AG۶u˜ggFy<Qře–edQ.q`eĚhmn2Ȼ ݮWHjodjo8qd=&z5 gؙ d­[0HI(|OgAFeQAH2iin޼YR/?T[W.`b5 ]IMS@WK?iӦ7o_ Aq&C9^Z|zf0j-/\"0mڴզG(+*H^~U-,SӔ<Ւ+Vp;9" a; gNCm9O^ZԨ_.JKQ 2|aK(FM +&X 3cwwww!Ch!UC֙$U8t”V-󜼴Q'Ԛ\2nSE"<,QhѢP(4jݻwo޼xra&O\3eL*?kW\@ĠG=x%+pء˿_kPπ#NAq˰#BO๥.f…5GJ)_x\q7.22rZeJ3QQQ;vhڴi:u aW3IaСn2!dCrfo-cd[ cc}6)GsB\9~f) t `@Ŭ)_YoŚݲ2?)/zI:a 9A. \&bwG66ol=_#pͭfi ),STՉ}uOvؑd-F3I'^^رl珉 Z+ ^_5*+9:/@WK~/??ԩӷ~k!\`)s8ߙTf'Zcl~blH2 ]l4^?lmqꝣur_ct̚ï} |Zd +)ics^ Wcu{ IDATP @-Sڵk' tr̙3gΤXACLotn*baBr x+|uk=[TѴ)ĉW\VP!@15G8؂I3" )_-8p`LLLq(:thzvJOO/deeifҥ 4i 7A2vm}!pd!sؤ&WSѨn'p盘iM"QS?1ӫ,b8|ٳ+M L-Z~=_)_-?.^غuk\~޽ݻZ ĻԿkhWWW/_\vիk/F‘(D2A{PP{^Τrd%}:L8\JhڈG.ǜ9xnT1x0NGGLdF5,mlEN'Q.鬒I:@RZr0))i۶m:{O>&*j@FSII.UNm8pT=̔k۹!18:v<(ݟm>Ch@XF˕W`.W|Ύ:/@WKLw#twwx:y,U!(R5PKV~t#T|]@B@E>UcHMEp0vDӦ} :&,ئicULRF[z.ߺկ+uFa-޼yӨsELjpܸqׯ5j; 99y߾}&LP4--ui||z޽ӧOs8VŋcƌzСCWHCʮA//ܼi+5x5d!E%;-/o c Nh#<~Qpb5pj4&6p{@.uU8~@W4n*fΜg-Ă gΜY^W^?Cl2aa˕RSDΝ;{{{o߾] 48xo޼ו urIF9:t=f6qѷ:vbnoصudž#KVcvtÎ ½{q2n6'"!G9ؘM Ok觋>=2Ӎ{Sie2 kw>zh>x]vΞ=۬YM6%&&_8ChrҾ}qF/F(**??J>@3i 5ʟdmEQZ2"1glU߸]bXupaF=za''};5u:i޼ѣG}||\2a_oDu>|811{ݺu4iǏ}- Q aNѺet&*eX>€Acug.`ch;ww/֭T)m9[6*4hb2_#hXQ@t(iL ~Ovٲe6m7nnݪg9s?~Cmٲ%zWڣkժϟ_hQ˖-7mdo~%z|Ν;\֭[ݺuS2vXՓ 0۷o;vnݺ+WTUt&eB󲳣WA7i C')|;}_/T}W_a$ܹcKkw߽%u7Ϝ+/+&Akłz. hV3+Go7+jR@V#lZQ%?|@:Wy₃Sϯ}oV~fϞ}+ǏO6}gʔ)ؿϜ9SPP S2o߾{W͛={6uԓ'Or7n.-姟~ݻwyyy999 LS S7ԨQ+W]> {ǷH^虨OϥV߹Y*SEZڶ09Ӕ;߀-D@Bf}=z{ɽ8!H:>eѴL@g^y B''Xve`yɓ8Pפܾ}ttt|%Wv @Wf,NRh8U͌txyaj4hb{j==pB`y څ #wG/1uŭ ƍW3DfjS#Ɩn44<2@&9tuyav>|hccc ׷o˗ϛ7/''k׮d:777%%%-- @JJJJJJAAAzz U%%%VxФ:]3/t/}*v9}m v|}Qׯ#.Vrl7á d)sM>{J2:,(x=z9p@ϜukZΞ7o"ƍW.^f L>a׻===3Ғ 6bD;q}an}t\P%Z11fuèQM^cB2 SU{ %SM5ڶnݪrf )_(p$$$޽~l,e !ׯ;ӘSLYzJ+]k^[F5]7ilfݼY.C3v9MpaxWqvЦ#F 0Z652mlk2L&S)/// )_(pԯ_߼њ%1 DR[E j.6.h˟qTg@͔zeF6adԯ_-s_652mbMZS ?(`> U*i]CU  a'|O8@`rB6>BhOic#exگIҏڏ2 I@r!Wwa)_]p8ӦM{n޾ AZвeygGK&6p֘YQa9ø8`Pk6͜9sǔj& A|BQ@ JbCptYRxd8Kk׮ׯSPPk.CjU5u t5Y?YG|CG$L;C#OO-Rp%K66U@|†H-PRJ=AXaƍtovӦMz3 ,iO8QZajiΤ2iII3Y|#zv<~8Lj֙5  AdZYQD a v޸qcѢEÇ/]9Q&9ã\lu.0lKk;n݊QԪ 6mҰS EԨ 2y: d֠*MT|C37l֬εeB[u?^C3cђd&hu(#=XQoG)<}:=ajmʎԴ2dU11b8jQUq6(Z 2:/@WK6lؠjoo/ZoR]%֭1c˗k|fAVPXOM] #d|9=UzXԩF17Ӧ0qUp@qh$i+adž2OԦM꼬)_sy7o6\9s> 6Lv&mlhQvD#~]>a.a ޗGQf_5tg%! knE5EAQqA!*ㆣ8F233cDFVْ YHIzުt&yxxNwWsϵ¼y57_vNikk }5#``7%4Z=Hn|믿+{WN6mРAgϞb NYi #EE}auʰ_k[|ǴF^zGkX5Vhk+m믇YJH9_9 T6m(h9UaMz.YD(b[ m">G n`kkkk=#5j 0gɩ@XA+2 _&o[K8Dw+"p`V\Yl݊AMM/Ɣ)X^@ nn̾z>A(| `*1'Nn`k ͭ#6aAh9ΝxsT\5/iܦL @ ѯ~PDAfE̝-*b/(\=MMMs _O}b43b<)07_b5<؜9}—w7qN=OڝA.ǒބ,Y"\='N,V+F|wQi fE(#~Hs۶ɃXBBVbbCl朊@R@[vߔ H6VXf.q[+jBBp v $l߾zM(1z5h1wGG@AOAyFF8Sr믲v] Jj֮eoFڂ C-@ ^p ظqI$={ٳ #cؖ :n3FePXCc`%.!s"tm; л7lɞS洹ݏ}I}] ES(cDHTH;vL6M{ꫀ_+PLC%Q4`1e$E'1suɃ}'%YžSӀeΩ F AĜe-,߳30$QT޽~-}B | AVĬ=+P]$UW:~N=KOPV-N.n` gm۶Q1c?n$8B^u;護œGr*ޭ\vItgdۥv$'X8JERٳZdT],RX4o_FȼU=|E"gΜZ[[{9w//P=-u9eIYٯOLuVI,#׿zs*y<ǯ<ŵ}L/ Ҕ鱃"_OmG} bA%&Nؓ.kn]v˷~{>}s˻vkbJ!V4Wۑj>pN-odky &!>&v:LT@,zK/^C=tnzZ.8B tLvC.Pˬ߸>ObX̱SfTᬨCٺl}HMn̵Z8dvswZjUhpa. ơ=gLwZ^@GO_Kx /k+HKCCЫ/Oڐ ´F-KGk^Gq]x=倉mqlʥg#9+ h+^qimEPhoWd{lN# ,~u4\.QJ uPz|f޼y* FJ~) 6m$8B0f~!"}oGm mâym Y11{wiso@W{qr͙5Nkݘ=#7E,6?'Ԩ5E A`J !{_ >k$@h-U ddGLwk>aAЦZ1fW,D@,e˭YNB"!2!CJR*m̴NRFPr~_M6 ߇Pa@@ `p!4-/;}5%023}"-뎘A&Gm,\M`cNlٳ5(B^#D9h׵s eK%84 8 *^y畞qM Ϟ=??k׮moo8,>K#33q)- 4sdyH<н#b'R\Ť.pO4M5݈;ih1wcn?oyBjz@FΩ;51)D\ %ZfK@h дB{뮻.''߿y5?+(fbP\ `'Zom=;l~z~Ymz۷qzWBTC:ϩILv ]:ŀ9R?ۙxA@3 FgrA 8b1Ǎ 22R [ZLj5P\߫ khb 7h[dZ^EkI :tvFhXYTM*0p-Y]_rssC͒f5L׊QW B:ѣGϋ'MŋgO^j1/&vYYogBmQFX[S\{񅩉p-IoVY$[- viW B:e˖EGGX"..@IIԩS=׮bRqGsOP};,aosp+k:ggt5BFhopO•KWz|UڔہG- B/#=k.Xv.Y-&uvDiI(]WKS,tj'L ]W r̵!J Nܹӵu[l)&uUx#d'fqfjBjkaðy&>_Y!ΐuW#T* NDxM( (aΗ IDAT6l\^ b.Y=>j]F!-n1$򑞎Kpl33g VCoa9-03ϩTڡ׈]]"Zˇ'D%) X BG՗.SfS˺HH8ʴO4Ni5P{723Ыrk!73%y[ˇg;!ChӁ;@QWQON(Y^/@8ܡd-(+;ih7zad+V7AVЯok~F轡!4C0uV6xt\Av'n,yOݠ@~>223gjcyk!7cȺ6XkꁕKWE=6e-`̖Bm%\n!eyB~~_Ztfa`{G XdݻQ[lUj㓍%% y$'Y&{뵡 qޖZ)'ת#7HU Bw}7;;{ժU w׿566Vp\ r'5Po˗#BE6vP|GBiX׆z%s a<[ U $aq=N:D|H$ZjUvvo}bAFʡ#~3gliia& wҥF4 TbpOw]_^ "חlaI EE(a1ݛv1ЫjkˆʌFƗئS& QY[aXsյ9wfqM'5 ޗ_^](%Z  `3r\OꡲBnJt=W@h4jz۶mJs&Çw޽~T\DhWClQ},2~;yA'2qԝll^ع٫K"B4]Հ2Q(e @@hC"Ɔyx5ԄynN+;ʴOTS}}u~zR#d'^,W.]yuIo.Cz щ$BK0Yj 8QRFC>l<ٳg~a EQFMIb4떬nqGl5.1N4+6kfRs1;bHCoٟ' [ |Ndco'M4⟔1i7,Z;%'Ι-QӈvW׭X7Yl,߽ T5VXu(c*{B0D,/ X`ŋ~N<9**K-Gȣ&ltnD|o\60mfr'O;Ҝ26 :lasGt;; NVgz449v$# A'[F${F)ӽ"5"dE:DzB2)D,/ 1cƔ)S\#U65a]4Cc"pO=hs7󑖆xISYytvX u q-!oJ0ohen-Cр 8oSՀp"cDP 7\{ߗ\[5Bp dyOܠĨ=)(/ O<`DCFԽ;1 *Coo[?[&b<1Ɖ5]  bE0z:^@|ڵkϟ?n:^rJB` ~]SN}޳f fff߿b(oDSy 1_wp ^|E}`y Æɓ؈€DxE5Ш-(~V .Z@k _Z0U ZFܲ  wh4]v-Zh=ؖ-[u[fɴ`_K/Aؑk Ǐ;,YTeID^Y!~f`k$'M[h'be) [-;m)8z~ Ev;-M-ӨmCuxXBgϞ%"5rHFS]]}U:Z_|ų>[o544kO:Y`j2Qz*(?fFUUhoL1L eARLp.&F'7z, €(r76h<1wc/3u%o\WyN:<;p r#d.N/k^@hIRjRLp㨮sGyd̘1iii=ӧ]z6޴'_2_ܾWU聢_ȟ:dp@[|Ж_޾}tӷoΪ [q8y$7|Ӷ=lnn;vlssmo2?O]wsܸ8do>fefIn%@*oA_}9P|u+ց @&3 hn-!wtۛފEXY`K p}[Ql26]H,Ke MW  4B& .]vȑ2"HO9%2yqrڧic&-N"Udw 9=eՔ5W̭V&"E5'GȴOpeJf@x"0f ]Rэ Lo#`*!NT@,dy@ʕ@k+>8{%%t xETVb̝S:dFm#i]u3qԨ3"aل8ؽt1J5uן;w>{ ZDo߾5k 駟&ދ6غͳ>._l6O81''S2fbBTf(3!T~4m=\ ىſ)A했};ab@ĈA!t S(~P=7bd\B Q,m𫏐챎'iոH$ص &q̙s{8`NLN&;PȜ4~6/cɒ%>X,6m?daX# 旴D2@ѭGcR̈/.jjH=MǏc45yinuVNdg޴#.[}r wbJ*p/1`tlqɕ*ݽ䮻0y2ޏdOwBgg7T`Ÿ=5*h r,_Vn-`}ة=Dl,>gƼmy `p[ d8.55DK/H2@lmtUȕ ]׼.o{FI:+9/Nmm8Z\tgiH1hZZPW5phl?x'"ex׼k=+&ț"SMt~҈GtGT*L4x7ɄGU8d&CD$}? < X4o]hEf̘o' n޽~QڽײTOp,+lHu aτPMR:¤?GK)ɒ֣] }Zc0e%,=,=b[D`dS a?爼Ƀu9jH5#tX\{^w-f,o`ݟifka0YMH:|3o[` Fu a> $B5Qƈ? Hx|VcvbfG⨭}i@gq &LMi8wsp9fU%51pGqT:.\g#l ᇸl X$0)i7^FkVLb<u VH:PS^җi([UW+n wӀ;xWM+ 6mc o`]my9U8lnHJҬCJPSk2`4ԌJ٦M~ᅬ^.D_"B ˸ct}P!R%>h99f@X,Y!%xYYӢ񻏐 -)WJ#~pG?` ̚ӪTX p2,sў6 og*gwݼl|_GX15@Tߠ몮SrEAk.[ACf/bCd&uZS,nW/뱲gdGn8 [-*{' d,Y]_ `߾i??h&"o I qHC`(3?gF]N{F77fsdb~07 $!!DR!!)! Ax4A*MJ%4^JΩ:E''F11+UHFH,y[o`BRrguMՒF@iB'K*=*17u-GEp*&[`Cp ?!RM5"t mmn';ajoĖ1~*B6lxꩧ\_b0qR kֿ]jKEWP PcrID)GQbREA)D&j1^6kJXk4Mu&LteXF2]PeIA854{v @{] )`RX! ^_/Ԣ"Hfڝ僈wAz:mغg+[ Re#2'.9dL&ILz+fAoŸ_x/kĄ w^Li Ť q13cW[Q=ʡng"* >{g-KWZ85Hrg Bocr{/ ^ $a#8a2m:e8 )1&DcR@\vb/9ti8 8nSLJ (c#de) $1G{?޻k}qR>ɖ 9B$zyx^k~KD8ɁHC55nklY[O?qbjIfy33.+<+{dkBDHb%XIۃE{F^خYhXchiSS'ɓĔ }qz[0T^+gdt-&!)f=$X? P Cv;oY̫ᅧ(ɥqRO;+; ¡qּ4Hb%>_(?4P'>HE!Ca`y &5))*ZIV\|`?ѣjfצΚ~5ad}q#vAX_|ZQe2uY<hޜܲ?Θ[1wfFOvr`<z3xQ"@kOdqFypPjg=sO0)TD6SSy2ÀeaCLq褋F 䥏//zE`7=sIDhZBE4vh IҫHHFG(V2{F;U\6[l?t-[ :B{KPhd8^ݣ꾮;:xHۖW1*q>4ԟ< GZM-Z/h;6U^7w\jל-KWʃu9NDĘ?GYI~w*]U|&4SF*QzaZҕiy=0#\2/̞\@$AXRHWJUp?h\\(4Bw(g.@C5A5ʷ+|)["LA&fodc/ϸ68,iQG W)<^y畮8dYWyO/5qX5Qhbv͞<%˴iVsaSLZV,̠.-=5¢/ CPeО^JvZw8W./?G+I`I N\M"ȾlFݺǁ/k^b8|k^""qab̘ 7}ީEP<%Nnݘ{gtjO!$i_EMzw=x I(칳]d#F6VtN%DDԨQ/.Fe؂{J /HNFq#smQ[_ኼ}Qb^/f@S_SeE*H@'MiYk760boWKb5J\#Inp.`g7Q4ML&3mi3MhD?iRI*R3YȾ0J`Xy#ŤЁ,Qָ$ 9u`YS/{F6MӳWVhweho}ū?t`]qr#OML-*,Bޤ3ue:C6V+II5:5o/XħqrX46 kj6-{[,mcz!霙h[P1vKv5!&^n݊ݘ T HoI:s7O68,Ȩ"DJb%Q9" hQ:U[U+.C*llzBzᯅisYWӝ3mi)Z!&#HRE#(5R,#G=$fh4Zm6R)DԄC9 Bϴ^s{>Uz?|<$lpmKodu]254}_IӡS';`pnQlrҢ$>ͥ 91!"ĸq\\y-h+ j[`QywQEfܪCDIb'[KӮesQSnRc?-} PC;^UX,)%:'e:szV={+.D5UdUJQUmRVUIЗ](sw8D.zrj((( !%DJ8JLHUDb֘Wzbi5fbj2I%)'w'+Kb%^bj0MF&3!&D2H&EL$RH%I"%$8J*GDM,3Kkq+,: (=%HIFX7_vaj¡>}@=s/;2H,Mr0ϝG=,cb:5~χJbVa!"#'N"]`&KߏodoթkSkU{ef̬TNZʩSM:v-6+6Ӵۢ\X k#G$N/,i4޸{+ͻv5+z%'ǎE+QC)65ŧKT;(&uWCUfU;Tɋ~KPϷAgq!J㥪I*k>^ ($cR_cjoH'GX" U'Kd4 JҠ$!'W P׫'dx*6^^9N^o7RB-GQbID%3ba~ Hb$8 +H$! ID'JđbBS2P ֟ ,Kj,mcWJG2"HvZwD ƶa8RL(%li?PLj¡Dj*JK1x0AUxIݦM1IL%W{l,gAK&<I7G;[6w,MIcro-+'|8d?~TZ/LM;aҏW+?(|)EMSOW~/ @E S &$<@n)Pu"q @ޥ<9%pYa7%m(|ykXYq}39.vTZZ^ 3urcrRkݽn=ڰA'ȔÕӣ)ry,Y;1k(u gW7O|eLZP7`ɩ71qcIK@nn0A$ uK,c$ebJUEUgߏ,\v-??71rc3PjjN=u*ԉ^ju&zamb+LLZ@tj\n&!ٹr+,W,]zf'&/]Bs3}:%^ffYL;vQg{ؿ_Ye(zA|f3lYNvu,#}O AȰO@@ E-vRaـoЀwEZRSqx de!&Gzq2>$p RAfM@rNjԄCԠfh~'0hG#܉:ayvG'Y+kXώc!]`Sn4FuV{0ssޜę>a6)69n,j p͗_b@|=g Z47c& 2c||FBJpyG M)D_D} eGxU&\Z3BL p,x^vVuިęH}y_]IA5{u8}--7Ô,>NMqV5N$-gPFiX7݄Xlv ټ('/N"dvpm#de`|/{7΅!Vc| jj0ofcћ[6`U,07V )"WX'9B]!W[ Nݘ[Y*N .wc|/رc~P -7׉`f{_?!0(w ܫN!R[6͘2558y3gz:zx8{hhСطϣ[6"@10xcmXFHh~$G`9I۳LdY0y`z^477{}N+*=ʴջYV.c|N<Sm)Rh`N"R5x݋rONEç!6#"LMuE.v̜Dlߎ޽}.}`MVl$r J˄ZV8\zh};a0,l _Y1L2z=JJp^ZmY`*=QP&B20 Fpݐ٧&µd'G}ꅻ§zLW0'lѻvsxpz-1;oq(dgC"7@OOxi|,{q4 J`ñtXbuJL )\q=1Nd+kGxA@s'Ec䧀;Yӧ 9Dwף$礌Iiq+,2t[>$w}QUJP!5Z09;o܌[Pf/`j^lxKW[j#84Ԅ-NjWiDg' 䜍*/Ywq5ky]pW F!>LAa>=55zpGt1bpc ·S!bvoRIj5--LK+oOX qgd" a\E/s&H a.d RSѠ9t)}!U K/.cytI&gGj@+75ꎩ1rؚ1⁰j٪As̓a&I<8>vʛhR4_k;lFƦMx In+!%`ɢ]I 8 ]=@@DJ 0o[ޙ\!&lٹ^Vi]P$_=?ѩ)5*˓c*͋`D$;{٫bcn `NTaF}i`9ft],{i"kvi2e>־Ͳfz J:w\ש4wD(8 ÙtJO9BވЩ^ pJ;2{,µsg`TlWԨ/`Ɲl|O?l XC"v21Hka)4c)zpla)p02>` ܉iӼq݊u ;7jp()Aq1ϟT1*0;OeCs˦ى,@K:oTYW5*deW](.X޶z'o:Eܔ@@ŒA!RPcp[u>N/ ZLs2~9]*sh Q^-&|:²2n[G8'YL \Gw 2m>u݋8  z@PA9?6miy$GzH(7jehiO;o-6Ө3"ܻ2]CՄR.peÉv«L UH;]_#G`0?qR;w}djfg~a˃ ̘qOۯO?O.H:o -2Q(et@Q?Kg܉FMVGgl8';=#[iϲ2F#vD7HML\G7Grm>7sӁ$R#PȵOBJ%_}󧄨'@<}w>sҕƮPe LMo558}%cPN@]v+8]s{J'l(pSsJavOٳ8seehnIw;a'E̶ IDATg t9QnJ9qÇC}뭷||fʃuluy˨QČ'3ƎELO[bG?HeB#Gua44pc %^h?O>e#-H$. ΩQf'&~>7޽xDGCma\Oc,44| M b"M8mG7z B#BЇL0eY8#ֽ _eZ4eŵ(:#εdrOmEr9;jI\g'Þ*q4#F`N<|0H-:yH# R3}ql\ wEɃulqhhҔrɉVe[bm 7/MQpl!"o[[ T8Xi(1+eO0e RS;ٸЂ`I޳Y Q 1fpN+ލx>Wl, f:.= 6C nIp~Ir0d.^N0)pÆ ~F̙qhSGs˄4M2]5>q1i[hjx <х/-:V4u*PP W\FMf3?8/`тn6Grw$a ]Rb⨉VxǮ]x5YcݦHfJ$0Ⱥe =W#_t̙3oo6GDʜt!աÆ}đDщ$BPu&xSٹ7Y %o\oѨm @n5wQTY^@BCH@6YeDŏQAaf g&12 (`""2P@dAATâ&0!Nwު;P3<&޾u==PwۛGw/Hm;ZjՔa: %ɂ@gn$RRbVaR^4!"4Nu r8PZ~BY֒!m۶M4i=\UUXb v|' j8pڴiGĉ?f0fH=EO(UG{&K=P卣WZPd$,e(ݪӆPab 1m_C?²ex)[7|)f}>#?!C;Xu|=qߐ'/N'Ǎ׻wN:LG}(=zXVW?lذ܏?x555ﳳ/_Nh͛7O>ᄏ /_<;NԸf0}͛7S7gWV.۟ .v_6_}fK+ސ6. sל2$IKeUeC ͛Ǐ~︄i ItdUXtb$|=.lF7ij}|3g $ J-a-5//)_|m* hɓ@c4fGEsDvEQlЫSˇ ^>7oۆǔ)ðaxq5}"!6e^3Xxǎ{ʕX}\ԯ_? ?&& j7UUUѣBAcD8p@Yvĉ/RM+Vػw/滱q̘11YyK^ l rŌEa}))عK>y>q8mi )`m(,~;-`*3+RrE{&iBS118ynD<|)np?֭XGJ.WtSmp%A^iw]: mH )Y!1wx\ }~L ܍؃"򏗏ǰa|v]nl2͜ ,^M~>|ؾu ={<ߞ0w1@4;:߶vw,7!H/ 'ϩ}?con1< fƒ⧟ܿLL>ѣ.\Xzu~uv̙JgΜٽ{ ?{oر >J TwV"s%b͚5IIIeee^?tЖ-[nj%7D7Ĩ!dӄԳ=g$4Mۿ>tP/.v/ )Jot訔 \i%})dQ@@rv,CK0=q2\2N y)4::ΰFYwbr<¨Zl 00 maOV?6oCcs#MǛj5NfvDDp섥2]vf@ Dx@sS#;F777 }UKI?c:{CI1o ΝEܹ:/a0q"LOOZbEcc_|Eft{]~fСìY&L0=====_QI 11_W"taR bp1?ݑd]]99?? p JW^N@*Pb ,\PJ,&@C)C L"ˀQa()Ay9 $ hr?7F$9Qajբ(R>h:_? r;XhmuK:񮯪ǽLB325jgG S nAQ\CHA-5Ƌnkd$mË/{G1BoNƂ`4Ih 6+V's Njʹib,gmG=6l=  GHOX_he%CQy0 gtWokw)P2_}{AT [ GղeΊÔiۡsk/5~Xw[gaBԉFwRLW1E"?;|8D2ƘzykdkSԨ`2bXXÁc8ݻ_0}D+Ȣsg/! dRѳ'~'p'V\Nl GDj"sfzb8 myOХ )^e$7W_^UQ 0y2>1 1B#kh*/ǤIH۶_@ErΠktPcW8'T]3yؿ. <jjp8 } ϝOUCd[6tcU 8~~2N:%QؓfFdK)*'cGHjI[FГ +1I_q` @3 FUVVWʿ݇ox̛?ض &0bɤ`@.1~ ۏP8~sީ~`╫6O:f2ZԇLO0,'%1易'ab`Y+JJ8 BJLR0#1bwv Г#"'\*'gH#l W [;o¬f[Z$M=caE4p!nlX/@>|,=@#+ O [7Yfa& 6MHOG{pv/2_}áw\x| d$;X^<83S'ͭUۼZF8|Тn=h᯾"ʺԄ?9h'nZHFzdalC04CHahP(͆Swiuay{,%dz}pi>Yf34H{wLE#\@{C"AbdTY@{he&_UVOI*Ti}eBNI .ySj5 2laO`RiA93s׵⨫!$5 ) hBbg˹NLf. i#&17aϟJu/ڏqwJ6 ͓#&9}7)4uTͻ kW꫌>(ZyCAI܌q0SaV&A8[vmdmML&ȳ:֨ 0]SL&-&:$ [HOl B6PtLd=jy 4C,wsOCQRwmf'EFq{rN5fk'4=}@LZ%9FЀ A/0r'{=h"A ƀZy$ed?aL1 ё n%&Msml2)wH= eC!I⧟zIJ%(** (C&ѳ'^}ٕ,ܦsZ|9'}{;ZՍVhHN0Mѣ\y|ҷwW9(ن%yt'kТ|>Qi:GxO9rhABNF5vkE1C_9eGK+Jz|SBWe4440ɤ"RuJ;QHi(鱜hvӻ,'8VČ`:@oMGW_!X9*M'\߶7O z0.$pxO2c |YB̬څ3%$j2 50޼ac&F!=M !YF~@ssėi,Z6VpOC,۫zFu ĵ5(0Hj+۹߼^zHi0%Kӄ.W6iAB&L^LW_) |Bx7ʂ KZ_ +4FP@]5EԄ^:)*O)?^ i_;`ѢE>rJ,*P hձvL@Vht"C+߽CwZ=-V(V縇bA޼-XDEDm$ɧx|YA5pTG@/1E4p`48aAUJMI>F& 7+E]56L 㑐W&)I@،.K??#B;X ++r}vsE8ޞQgu2'lw檽TP(p~T+S$$]@s4L8/C8чc^ȍ:{wl + JhLQxʸ9`€ S΂/a9(kWmYDDDH|0$ UZy+o0I8Z^u HW@oeiABP륚31%5-U(7j>+p$ho0IIý3߈lZIJ! &bl-)! 6&HaH">@gȃl,I@HW^E[yOt䍿JBFmD1#2v$ǔ9rMeτJX٢S˯ӊ66WuS ap`5s F NFt^t?Xa@Lb"<}篤゙ Fr=BU*&nZR^9@ǯ7 ..;1ETnl!iL5i hf45" apԄC^h*-hSFsXrڣ5j/XZN+"|4g#,謁 j8e4$ߖIDATWSnIHI.45W~1]1ʍ:K9鰭23f[j? ~Ϫ! & :д EpGZv/XZNE+c..Qv ~J@#LIJ{7VXۢuJMhww# Hԫ E&h׾h9Rd92-+O].P3#dcIEX>a!-YyIMȫUGNO=9Ixcȶ_r [dPhT{"'z+J8ԔTq?%GHKް$`w R^4C”4@H_eUC( AS}i 3.a2D+:~e$a'KeU>!\yO5ŠRڥYUNbp BO+!u^nwY&mtJAM/pp^ '9To,Ô1wkz%y ]R?BMM8P6m4P|A(HŅ=IUtQ U,&2ShvI܂ȱV&#t܁ a$pL#[+X@UV՝|W=;q x  >b ^-Ӥ$fܡ߃P走dO1'Vk'(* -(9.0E{|OZao`O.kNDj"Q#^AQf^+ol| w.ԞQM8̜]ipf`+4olR%7nq?&Ke ' V>{t|}57V33G{]K{:_eerVrvLu{U5a37m= #]AaP4F-p+õъCFtE=͔;N5~ j!S~SGNa;S4Y&`27e|)l]>aQ#7 Sɨ75-F تhOK>1} =WuR))H'#D?#]tae:̰@dh=!m&h>VZł-J]|Сr/Co1M #2"+Oqs7㣵tD[m88-(5-w Z/-Vͦo BzJpT֚D` LhO/1M0uzj >K\B>7xB*42nR[+k@OBPI6ބ9k5Ш҂c|GX,Az4i9MKF#6Fi]ğT%yKDͧ<&v1ߝ{bSC~&z媀2WOp8{+ê@+˸QOI*qԻF5/oŅ",tpP\,u&SQd}C4u193r&m@3 X fW{ ]C|5G-f%#O>D򹝙 ͙v0 W,H'V@Mg ж -YS'ŗie8RD 4MySIwF745J,g:JOD#8 +;'Sh*'}DՄCZha<Ţ USXSi𩄅ϛ6ItD0P,98uT~Y>cW^pĕtZ 6@"%$sȰfkv NG˲aaHSo.t[B;hS_ w2_#F2yP\>jԭ5pO(QP˶ -nIxH] s)+鴣 31Fyu~,%>Gv{FYȩ=nnAkL|LhM3;lecMY€FݺeEʗO8 Iw@?'؛9+ƨF?.I]t ;^FZU"eeV9բ!v QC t)G\\RkX TʻW';q>R}o=N9#T7oIC2fc2?7_Aww2GtyGsԇPA(9mwe{*6vXk؜9*C'DWރ_}]>P#* Zݤ t;zRTӼiD8#cԦhw1K$&ya&>LnN=(a6\z ! (x=649vDKEH{R'{T -ݶC;eva:.L ?ᮋ;bz⏉!8ٵ6QȹcWllk5@+N>.+VP*ۆ.wtЀA@PCǙLr)ШwԚ'/fo ,SF3ν;=T3x 5A(~lYFkW,\|Ie.$Ʈ^갖r  ەS⠩ [ef']r)(wCig2eW;t-^{yB)ەS'+omCCI/<@1.uġsXנӧiވ#5#Z/# Prtr"j-𷹏zP0BXP/SO8Z]>͙#wLh4t'& >;|$1FqAP=x%TU)ϕϪ!@@u- XLÄ0)TЁ3G؀?ʢjr"oȩ#dBLJRm#8Z]>aF k*zLqvb"3$uAz C扌zj(b O霛&@AUwr kxgCB4յء9kߚdj^.@>uU> T-. C~OUK}LqvbtL"{z;P 2b7$"HՇ$B==;kôSIdŮ%xHZߡtN(x8\5kݥrfëC77Yd.9(F s᲏)t'&bs/KL cەЕ)Cx ?3 (y=C&Z8Ȑ,[!X/.]0Q YKA2L6zAè@U\s`MLB J3~R0I+S_eegR-c٥jP/~~wMpqa=mC!TQ~6LQ^X+u)[y5C dVFm>/.6Y_*1c2Vhq@Lj c8LV$3#9>J2L{UCD&Rьd:(VsкV+zXy>!ˑr]-(gf8!Y&55տ S4F lʸ$6b q=pÕWzQ6}K!SC,Хħ(uL.s BzXy!$#HADQTL 2e(!YƯ"ZYk*uLq0~23-]'h8##$`ſj/R}I;ƞ)؆Io!C|"Vqj! 4A1mk]>B',~XBv8 &+#CbZ\dVf{6hz\\9pL&-߹6*8͕Ail\lYj>gMs9&H+OQ[iLτ6F7ivFDVlh6k i,Cx{{x*:aQrʀٙty?:cf8 h;P1RUm.fϸbsssnj8(dSLgL (i7L=˾K*uE)"i&}m|F؂MjKIzhVl9&,~KD/1A% jC݈,Znv)m۶M4i=\UH~'NܺukB:piӦyΝ;ͮ{2)F$;ҰO+؏Wp;w  K/g #SR' d ''"+t+vLC:#u,ͧ{G'j۾}{^^ަMl6ҥK]fҥVuӦM .ܱcǾ}B1b}8^t)33$ݰ<PjBo'#8+bVam|"F+JUT蘢1J|'Ӝ[ L&g؆FDExOv9y-X.))Aiiiqq -\QmP?0U I D0@#qA}[^j(q3f,zjߝW6(XWWGd\K\\\m-NZ$I&&&!ܷo_]]ݤIxf,ةPo G寽5jwf٘0=Ks+_˥:0恳--BPѠd:ZF6l&4w&i'OSVh}Z7=3<L %tllnӦӀア!,..>}ȑ#Y8jԨ~:##/툌T{P$K Il6'$짘 ҨTUUk򍋋f"BQ ?*h={vժU6mbc4R *TMp}Aн{'Oq***\һwoի׮]LFqqqn,R,^___RRRVVl6WTTdee={-r+̭5VB 7 L&G}t .^_~ݺu;sLVVt;Xzŋ=cǎ+81SY_yfw_~?… GcK{1uTǨ(Ѩ.L <@޽ ǺuBv۷X![> `SL)))޽;T߾}4|R5AXy] z|S! #Fp wPWWW_]k׮f͚nݺ?*k,+O!""Bbٲe\''[Bg۷BQn(Oޜe)[<ȬYvڥ[oqʸv=vج-[8JNEFF׵ʢzM<̞={FGG;vlÆ v]M111lG~U! :t={oOˢz]IC S'UxXqheN:5660Ԟ bԨQh4TR v^h4s m Q|M'oNѣB]@#..mLFiYGV5LBʕ+/]j*ю[ԏ'/9 pܹ[nE] ǩS ۱cGuesu)d$W_}ٳz`7-<|ee%3g޽… {7vXud«DLL̈#֭[wܹӧOcǎUAX˗/رZn?ԥk>|xѢE$IVUUUUUQ_hnֹUUU+VhlllɛVft{]~fСìY|"7]PP0|N7jԨ,u݂zСC[linnNLL3f`Ϟ=A# mۖdCp۷NA *TP"ШKB *TCB *TP *TP *TPB*TPB5*TPBjUPB&E=%|IENDB`trunk-2018.02b/doc/sphinx/fig/simple-scene-plot-2.pdf000066400000000000000000000340131324306050200222040ustar00rootroot00000000000000%PDF-1.4 % 1 0 obj << /Type /Catalog /Pages 3 0 R >> endobj 2 0 obj << /CreationDate (D:20100711110908+02'00') /Producer (matplotlib pdf backend) /Creator (matplotlib 0.99.3, http://matplotlib.sf.net) >> endobj 8 0 obj << /Pattern 6 0 R /XObject 7 0 R /Font 4 0 R /ExtGState 5 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ] >> endobj 10 0 obj << /Contents 9 0 R /Type /Page /Resources 8 0 R /Parent 3 0 R /MediaBox [ 0 0 576 432 ] >> endobj 9 0 obj << /Filter /FlateDecode /Length 11 0 R >> stream xI$Ir)(|H}ԠC#QO3T -0 #e-JzanV#={#D[< ,TR9Bo=$RwT;vf8uHV9`Z2½K<&@ACZ@M74D{9{j8Ph10*׊(^_ o!sPS#A@ ȷ]i@X1ՖG ؜?Fw 1#p]҄)d>^,"" m KlHbpecVBənJ Їk^% US^\'0Q&~?khE0u3F+LwI1 OIG7>txfs8:^^7 knpIHm;+Mx9 c#9[~[ ٤Iju)q@ѵ&e6]0 g"@qa_yټ섳G+/Qu?4 U 1ЦؓKZ2x|=6p%۫G[^ s0a--n?G $'psƶБVu ƋF >eIHQ#LcQ6|p^vId@huR▣K% w[LSk܉DGR'éahrծC ۸K ڀ]-:GćpD5`pax)`e7Е(Udaf:7Y߸> a֩>=>m4[;~&.|'b8*p Kc_;<mGxz !Jjr0Zipg ,-$D;jɦb8XjG+ML0YGaG]MʨTQ2XpL dTrVV7is;GK\ O~>EPT<cLGdD)IJ<`opI&s e?P}lW`cqy nPV笓4׻R !m52H;t"߂cpd>Rqk[wc NbZњ&\JEN| .ِ'~$FByBzۼZz('ǫMOOQ]Q[|w{myOS2O]( v/o?( |Q{ȵw 8׏iN@!I/O=t~y OuV^P :?||Ou;/ޡ(fwe2/H_-O@oWYq}B?C޹Risؾ4Ŋ'w#^.S]DڇOEZ%HHO$GDI DK]'iL^&X,l]4oIM'U镮u7srJz÷4?и'4m$ 7P>^=累,?7#pj 5*Ma 4Cw?c/EY$>ƛFTXjѓjo?2Y)D}8nä>=!oՐ?uϳw[?bd9]N 78?<*oP6Zl5{҃j7'{Q3 ҩS05Z֩41yNѸZ$Sm$uuk'6Y|H}hɉJ;p^;Ү! i?΍*BzC#*EI5%}F"|Vh1w\!mI*bN VIIJ:P!$@A:wpD妙7,GNX Z Q"j;/0FU%1ښ*m|$he6JW7@Fn7x2l{=+f6 kOeTSѩY2b_ TE4{M|(Vwc]Q|K4¯X]+j(Du DWGp+c֐hu=-ꋣ M7mv1ACb>߽c\}iYDpp&(8FC(!Ű7hZ+Һ[H8D#ֲ5ha d,&sP|([VS9O`|k_]s̭QcǤuX`*<Ѭ&c-F/\> bf#֤2&q!\dO\ o4#`Q\̢0Iq0PsY ubAh^WȝCip5s%|gX-74M{\qR_1I毩}g%{zLX4nֺ)!=w-4 xsnġ%ZŹmG98 Ֆ19Z}J9NÎs[l[ s5!k/qkFmNz%BCw5g;[LtjOCsͦAxpijAњXSVl|}rY<ݢji~֌""Qy,CIkx+{;aՅ}^"k-%t1U]Zb/8<2VLqV\!c%l}e#fZ| ך,MZ͒M BQ|+Z~AEog٩,PҜSvԢ\`r}ND5c]#1_9{"vǖYovlh] o>D:_*aQwLlӘf*k-`%&{Op `H&AZ\of E&i,OU%HHڃ%ކ5H56GQv3rY^_u7=j'z(fA+Z-LRxI?f[rKqWiM+q,ҡTғ r#-5|C\rj*:%i-iB +*̕r-<1*IőUz)N\ĸF~7\4gyvL$Nv3ֽDF R WMqP/H0kdm! >I+jkg:pnѩ$O|4 dT9V=߯ RS/UB272lsKMa]kbڛюfW^b䌳պ]H; 8XU*phoMm%?ƴp`eb>`Uh1UM=G ^ϐ6W0RYL.vUWxHeUQOե,ܖ"u{#ztgOp-4S栵yFTg8(qm(i]cŕz^etrTKJ^ a+9 V(Sk~<ޣjeyxѧK.!eN!!bkkqxG1APּY&RD"hm2yLvKM%'+u!%M{hVCi}Õ6:Vbו*r8.fE'nSJX|K.ߵJ9;ZaWV elua]+L_,;\AKkB_?/̧GږqmZ#_1i|oD` Cȏ~/n`0sy -~+xׁox>[ Q%_Hziy 6 endstream endobj 11 0 obj 5447 endobj 16 0 obj << /Filter /FlateDecode /Length 304 >> stream x=;0 C{Ȍd'>2VI(/u< i& b;w؞D/)ϡ+E:Ū0[M*K õ}74uK hY pu;Gw5<TQ!OJ|<(!\{0FS@\^BAjI'> stream x5Qq0 54sۿ @;a@dJ\UGM>`!S֖{&UF!}W2j]* UYFp&I8d Rӿccz endstream endobj 18 0 obj << /Filter /FlateDecode /Length 68 >> stream x336S0P0 F )\@>,́,# .C c0mbl`fbdY 1 r endstream endobj 19 0 obj << /Filter /FlateDecode /Length 74 >> stream x= 0 "#4M2i >d(]iBIO[_mվ*K{ j26|w wN endstream endobj 20 0 obj << /Filter /FlateDecode /Length 133 >> stream xMA0~tzJ-6팀03 endstream endobj 21 0 obj << /Filter /FlateDecode /Length 163 >> stream xEu1 CsUx:?G i@xx= r]Ņ ?޶42܍e@N"WI3Tb\/:"̒@#|:C[ۙ~:!**na.@RԏQꚡ*+kjڿ"}\Ne{g+W}: endstream endobj 22 0 obj << /Filter /FlateDecode /Length 317 >> stream x5RKrC1ۿSpΘ}tj'+-@B./YK~%ۥW%B>R-G- Q=2'":xa>N)x_xN;2$KMH=I+4t~&+s{rj X+)$=Hr7VސWg%&&MܕBXtLX㰄*aՃM5fcdxLP} #GMv²[6!D3,($Nc$ Ұ9 9e, mh%zМaמE[{ endstream endobj 23 0 obj << /Filter /FlateDecode /Length 45 >> stream x32P0P4& f )\V.L,іp "} endstream endobj 24 0 obj << /Filter /FlateDecode /Length 338 >> stream x5R9@ } ] v͜~߆_ CVie!U-.Im W%ڥ Pt,6˯JH+kLwIi"Eo7o}=@.^ AS(i|Ъc(ew 4<3}(~_K&(? _osџa`Ś}@*z`yT endstream endobj 25 0 obj << /Filter /FlateDecode /Length 90 >> stream xMA "OPDtz_NE5jK02kP)U0\ 2IL{qIqzz"X endstream endobj 26 0 obj << /Filter /FlateDecode /Length 236 >> stream xMPKnD! s\I$!CU۱T*HUSbuM2yOQ nAb$<4#',;Ofč.`5[́9m:7@dP "B.ї9`Jw&\>աqZ coYaLq_֨ɲY I!}{ y+ՠ0u J*}$]S endstream endobj 27 0 obj << /Filter /FlateDecode /Length 210 >> stream x5P C1g dVukm;aBXȔy)K>:L." u%ʚ +`p&^7`i5tႦ.B%|u{OxjrvC` jMX> stream x36P0P07W544U022P042QH12443s`9`a$r`Zs: P9\iM8 endstream endobj 29 0 obj << /Filter /FlateDecode /Length 248 >> stream x-Q9AzBsˑ C :-qPO+Uwu9HTM]vf5,?c 7zqxLu5{kOfP2+qSușO \ ȹeƌ#M!RH&3AQ~#aU#j \Ks4;<9GW +ET<pC7ҹ^s0XM7/=[ endstream endobj 30 0 obj << /Filter /FlateDecode /Length 52 >> stream x350P0PеT526U05 LR \$S as*sT endstream endobj 31 0 obj << /Filter /FlateDecode /Length 72 >> stream x50{6X`KEoC|N/Z #pu#])Ec΂q_H1F=#O_p} endstream endobj 32 0 obj << /Filter /FlateDecode /Length 247 >> stream xMQmD1 \ky R]oC /)%K [UC?13,=?TPbht/"+ߏe s`&4`oI&ռ3d‰ATwM,3V7: lx%D`r Z`Q+ tĺv7C/਺x} K{,|BL;wI#fR:=b}@e+ (\* endstream endobj 33 0 obj << /Filter /FlateDecode /Length 49 >> stream x36P0P040F@B!H Y@8&+ & endstream endobj 34 0 obj << /Filter /FlateDecode /Length 332 >> stream x-R9$1 ~`LtIUls#h/#xE=f۴[iGiK,W ;BjW0wy.2meDkag؏]e8*Jl !2J'Qw\I2[E™w2;yNE{ kF9+%|6vzrYɩHHӺ NKؖߗ3| endstream endobj 35 0 obj << /Filter /FlateDecode /Length 80 >> stream xE 0D{`~&f( JpO{:2Sa ,S`5FR죰n_uzS*Ovvq= endstream endobj 36 0 obj << /Filter /FlateDecode /Length 392 >> stream x=RKn1)@Mr[T /1 %?ꒈ3L~r]Qljg!.6Xr_rњbO/ȴTXVݣC(-װr{d`Jn@CHYAaPl( WԬtb ) ٠[]aP[[xfޑ3qYk?=Q2QMg|2RCgB'`$Ip#A 1qOl)V;ޒ{,\L'ib?lK\+E(~Aq|XdDw#h% 0xyDhDԎ=(ͱ&{ǫvzcw. endstream endobj 14 0 obj << /FontDescriptor 13 0 R /Name /BitstreamVeraSans-Roman /FontMatrix [ 0.001 0 0 0.001 0 0 ] /BaseFont /BitstreamVeraSans-Roman /Widths 12 0 R /Subtype /Type3 /CharProcs 15 0 R /Type /Font /FirstChar 0 /FontBBox [ -184 -236 1288 929 ] /Encoding << /Differences [ 44 /comma 46 /period 48 /zero /one /two /three /four /five /six /seven /eight /nine 95 /underscore 97 /a 102 /f 104 /h 108 /l 112 /p 115 /s 118 /v 122 /z ] /Type /Encoding >> /LastChar 255 >> endobj 13 0 obj << /Descent -236 /FontBBox [ -184 -236 1288 929 ] /StemV 0 /Flags 32 /XHeight 547 /Type /FontDescriptor /FontName /BitstreamVeraSans-Roman /MaxWidth 1342 /CapHeight 730 /ItalicAngle 0 /Ascent 929 >> endobj 12 0 obj [ 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 318 401 460 838 636 950 780 275 390 390 500 838 318 361 318 337 636 636 636 636 636 636 636 636 636 636 337 337 838 838 838 531 1000 684 686 698 770 632 575 775 752 295 295 656 557 863 748 787 603 787 695 635 611 732 684 989 685 611 685 390 337 390 838 500 500 613 635 550 635 615 352 635 634 278 278 579 278 974 634 612 635 635 411 521 392 634 592 818 592 592 525 636 337 636 838 600 636 600 318 636 518 1000 500 500 500 1342 635 400 1070 600 685 600 600 318 318 518 518 590 500 1000 500 1000 521 400 1023 600 525 611 636 401 636 636 636 636 337 500 500 1000 471 612 838 361 1000 500 500 838 401 401 500 636 636 318 500 401 471 612 969 969 969 531 684 684 684 684 684 684 974 698 632 632 632 632 295 295 295 295 775 748 787 787 787 787 787 838 787 732 732 732 732 611 605 630 613 613 613 613 613 613 982 550 615 615 615 615 278 278 278 278 612 634 612 612 612 612 612 838 612 634 634 634 634 592 635 592 ] endobj 15 0 obj << /a 16 0 R /seven 18 0 R /one 35 0 R /f 20 0 R /period 33 0 R /h 21 0 R /six 22 0 R /l 23 0 R /three 24 0 R /four 25 0 R /p 26 0 R /zero 27 0 R /nine 17 0 R /two 29 0 R /underscore 30 0 R /v 31 0 R /five 32 0 R /comma 28 0 R /s 34 0 R /z 19 0 R /eight 36 0 R >> endobj 4 0 obj << /F1 14 0 R >> endobj 5 0 obj << >> endobj 6 0 obj << >> endobj 7 0 obj << >> endobj 3 0 obj << /Count 1 /Kids [ 10 0 R ] /Type /Pages >> endobj xref 0 37 0000000000 65535 f 0000000016 00000 n 0000000065 00000 n 0000013468 00000 n 0000013373 00000 n 0000013405 00000 n 0000013426 00000 n 0000013447 00000 n 0000000216 00000 n 0000000451 00000 n 0000000344 00000 n 0000005973 00000 n 0000012040 00000 n 0000011825 00000 n 0000011354 00000 n 0000013093 00000 n 0000005994 00000 n 0000006371 00000 n 0000006764 00000 n 0000006904 00000 n 0000007050 00000 n 0000007256 00000 n 0000007492 00000 n 0000007882 00000 n 0000007999 00000 n 0000008410 00000 n 0000008572 00000 n 0000008881 00000 n 0000009164 00000 n 0000009302 00000 n 0000009623 00000 n 0000009747 00000 n 0000009891 00000 n 0000010211 00000 n 0000010332 00000 n 0000010737 00000 n 0000010889 00000 n trailer << /Info 2 0 R /Root 1 0 R /Size 37 >> startxref 13528 %%EOF trunk-2018.02b/doc/sphinx/fig/simple-scene-plot-2.png000066400000000000000000001343511324306050200222250ustar00rootroot00000000000000PNG  IHDRXLExEsRGB pHYsP|tIME/6eotEXtCommentCreated with GIMPW IDATxwXW?w{ gR몣RQJk>Zu}b r[V:juFTց8v  _וKsܟCɹ}ĉ y `1""""bEDDDDL`1"""""&XDDDDz ŋlܸx,--ѦM;NNNe˖!::"9r$D"FDDdlقH}dhpQ,_;k>pvv6\B/}$ b1J\4kLQDDD&.??B %WKu}BR)֮]@MZZ!^JKKCݺur9RRR`eeŖ-""jBP 77*s#??j¢EK}%0gB5B<_~DDDΝ;|ob͚5HMMoSbʕt.a \.#,]Lre晚}2Çaoo_)`$jlذaXnON;UCźYZZvڨ]66m^zŋԩSs M -Z8ֶ7n \ׯ+;]zSN٨/ܺڱV#{sb܉qjV'B\Bt}ɒ%8wMBTB.1h )%,_Í7n:իr{ ZJDDd֯_[n!99wy`nn֭[k? `۷cƌQHn 777d2$&&"??_>$$7nb10hРOT$0ĸ yߏ4K. 233Kbu2׻رc*ˬ0uTL:UJ ɓ33V#“N&d֬YK"<rsYSܔ)Sxwb܉ d^^Ri $=177WZ"2[".X %Ҙd]P- }%J:NՋMRiϟ&q- Q'^z$0["$) c͓1""""bEdΟ?ϓ1K.xwW\aLNoP(TN3GL|N9r$`wg.1""""Q(< LHN< PXcС#FfffQ:w}B!f̘yܹ3### KKKx{{c ;wB(???3o<4j氵EV{n-ۇ-[ >>>J<^||<`ccZjaΜ9 ,֭[*ٳgPo/ǂ p)lٲ:ui6n܈Gbڵؾ};222еkWеkWm >}C@Q֛o3g" ؏O?СC9rvG}Ġ`.?}M uܢ^ɊO :ia>?M4͛k׮ŢEU.]6:t耨(7oL HJJzY_M^>n !ɗe%#J'0_eϑǏ+m޼pqq)w~/_Ɨ_~(W5jH\EZ| />PyެY3|;7Lƍ+8n]lcTQAA T{BV\C"""[ ƍ\]]Te666Ŷť&nDDʏs0ϡG{W>sOx;uOOO؛7oV P#F <<'N@nPF +3 e~D"~ ^"|]X#vv@FFѿ/DDWrSoo# |ޭХƊ9vX\t gΜA͚5-¢Ecl۶ SNH$ҥK%''799bMT1cBۅ̦.ȘQ: Ve˖͛Pq_| dee0aX6Ϟ=+3W ё_`e@Uv/>m07%SK,}v4k֬ǩUBCCׯ;pJtbb"Μ9Ν;L^UGqFb [ؗ E%#JB?GVVvލ-[o߾j_5x->+{]Atr9~7۰z;Ȝ={'N2mH͚5ѠA2ܹ3>#B,#::T3oAAA&Xd6l~Æ juΔ:L\v te2ibP :^5'Nd2"C~1ZWmoDDo1~R׫3;1"#P1ю;x˾ bPxzzӓ'JNd81NDH8] NDIG&!9;' UֶR%UQ'O0@&q'"c`2% ] ߞ3;Dck3kH  zIL07oDAAbccѲeK&X5ꗹBTibcеAWI8,+8+ij:MO gggDFF76x{U}PM/YD%Dgge6f6ȖfDVZgw (v<\|ҥ ڴi+++&XT V[)ɳ-}`@QF___?ĸ|8nI`Q^OLڴi|~m,\}ŤI?&&ĠAz,~\.Ǿ}}аaC,YWn .(oժj֬CaȐ!jL^Zc}8x ~we2kBaa!:t:uʕ+%$([ʹ3ED E yNN;|@")zdeMnnv%yDߴ4|hҤ 6oެ>~~~Xv--Z%nr#VڵѡCDEEl׼ys! '''`H&Mǯ&3f@F`ffsssԩSp-V^&X:tKÓ;pp(zNیW}Du&핳}T*޽{aaa~+WСC֭[ƍCNNvŻ^ 55UeMbq[i9΄ ѣGȑ#ŋyyyj%BM2e ONF,,  ߯R~GDѣHOLLę3gйsg- ?T*H$RYvJKd2G~Og->C}s[ق2DT={'NrGa͚5ѠA2ܹ3>#B,#::T  ɓw"+#%1K*uÚ5k6'''ND0i$H$1n8=G޽]|?4/ywjx,@Ԑ(4pjO"R277%<<VVVSsz 5=q{jm<;2221aYA*jGU~PK߫<;v,F 888Y7j2"R)D"D"J p-jxjsE%Dg'gD7ht2A'YTWAA֢b$ bq/'#88X%0e˖ǖ-[駟";;6i#^oR{{ 2ݿ1;P vZyG]6+V ++ cƌj ngggL>vBϞ=ѿhlaر#BCC]vu*tCBB0{lW]v*V#vSe={k֬App6ڵ{١.?2z/pC)_'Ĺ.7n0{l; |,|+_dd$BCCѻwowId2̙PQFWEWwqa޽dZgt:UNll,BCC˽dӧ2d~g*T9sÇW#hkMz@|{7"zFyyyZAqg46U\.Ǽyp=,]:t(~7ԨQtZ^ DEE76ZfN j'Wжn[YӦM' bw2] -B\\.\X ( L2:I233DEY#774h:tx ك-[`̘1jzQ ml>>ހusaMθZd Ν;iӦAP 55P,HJJΝ;"99X|9>N:9s ,PԏjԨQXx1ܐwݹs?3rssQ~}|xwV> 88e\Su44\`wƝL׾} T: lݺnnnd*9i&AAA4h[uKШ !""*Gi}L4lǎ< ;1DL4ё'q'Ɲ +,=yN¸Np9Tld<`1c_5wSnWjߨQ8x^ɓ'S&qg܉`*++@˓KV$IٹRZ- bp)ĸ3DL r 2'Xse0Mθ1.^>@E: WۯR[  iK!~{Ȭr R$""be iSJo.4KVb}2A;Nd <Wi m'@}2&qg܉[A"Nd2, -?c۶mYLθ1Y[@V,x. ..'q'Ɲ &ٳ'q'Ɲ N807 ddd޸ <Ь`gǚHDD D۷666FW~`Ӕ):{]7vaU>Njn*.<12@QQsk2U>ΥK`Ə UeWY- b3&qg܉`Q $h_@"ի bw"&XΧe_&*! ~`c^"sۛ1;1T9k?Ap.*`[_@^ni&2Kϟg1A;N ޛ~5r65#2 5)9))Ǖ+WXĸ3DLH=˖Mq,0O$Ӹ|C#9r$ bw"&X$ު,"""CNwjgLZ`=gEDD'݃ |\4ׂkwƝ /6vھDI"%ĸ3DLH}8_۵ IDATOkߞ[٪Hu꠩kSdK-ͮ1u0~Ɲq'bE6 5Jyc'ͬ5~zK[jmP(Hu뢆U MIȈDDDLH璳q3&ޭƏ-ɗ=]Nz^:r sQ׾.@ h bw"&XT1 Ý k6懀 (m^aNeHOOg1A;N*fL 4Tc;y$:{uJQDf(m-Zs/CYgLθeHRF%Dafک8B1 dQ ؂eHjXU ++:yvJQ̈́`iK^^ bw"&X7f"3t 5mjjuRb~B|j|0m41;1%BC[ԩ]ͽ7Wi׶j41̙bwƝ U\mҒ1;,8_j{.ƐVCT#0DDT-iݹG$Ve&$ܱcO bw"&XT9oGON4XxW/r r*}\GGGĸ3DL ։޽ώ>8$X7WYV˶-՞ϰ$+&qg܉`Qc&3?{oE=zZ-bą~pZ2Ђ%psM2DD ۙgPϡ<<:µgN̋UˮW'O0&qg܉`Q)@ffw_UKj҄pĸ3DL*&СOS VE"8"j se=1A;N*زBdKL~ b!\#""o=CeWKj9Ӵ_qb '""rŽ; ZUs!y\]jnwƝː .>IԾm۶e1A;Nd xА{Fq΁, IHDDLJ8 ;M]vm|P9v\O))Ɲq'2:Dx)}6rrrpQvoO?8Bl!!!UE 70tX(ݴvбoooƝq'ӵqFDGGD6m0vX899Onn.-[hD"`ȑDZ-NZR)Zn>me2Mggg/@AAӬM11'n/G&x,i ]˄SL᧍ bw2]ƦMxb<{ 3f(sK޽{XlΝӧOcӦMZ/NZnT|g(Oo߾5ki֦bw[w+!=l4٬+ir~ONMDDWy>vX=ppp(D"cǰd4h0l0,_C FFhԨ>رchӦ kV)ɳNƿza?[1̼r˔f¾mZn WW*D"@,ʪw܁@ @f/|}}!Hղd'~ΝC=$n R(UE_%ª?>?]LθEݏ֮]@MZZU}试fZ T*UVaŊw}ghd`ڴW pYtmHhVЫW/~˜ Ɲq'd3gB!FU}uu$&&bҤIhԨ1uT >>:vPb׮]hݺ5ӕCBB0{lW]v*ƚ5k8={lӳgO8r\5k 88XevիgFHHyzzz7m`AA'X-мfsg8xoh[VΟ{/Wy{{`{?(_dd$BCCѻwohѢ{\ѣGX`,--K \\Ғ-'N(t<"44apBݻWMFFz+ViӦ*ggg#((7,ó8rfuM+Z?oO)""8D۷666B ڵk/7IH$ӧ,YL؎;˗c=33e񈏏Gnn.1h rZB@xx8...hܸ1kk{yr]"ࠥIN&qgt-YΝôiӠP(Te 9=|rܻw7nuЫW/_*0 gΜ ]5j/^ 777$&&"??ya6lB!7oC,6Wll?7tnn;O1?^=lx\r Ɲq'ӵo>3FL `֭pssL&S)K7nb10h Ud.8XZ].{XpvbFJDhL (P(jq䛐+ 8E&7~VoǦ+OIع1 0!O1"xv @QːBaWw `!(w;s9;7М&= cw"&XY*hjIV^a,ŖjmSձWt0;1`,c2}QPߩNle!Pk/jՁFt0;1` :,\Z -X]wufpH/u릜044YYYLLBTSO595`UTdd$ bw"mP4LAA%Bcӽ;n 膜_%՚9a\]RW"3DPԫW0s91X:dD=؅fa&2[1ԝ*g]uZ+CYLθiˌ3p1<{ W^EӦMaaaQbUnCOS$j^n |x)\Q`M{ f"3 r܂\XY]^^,--Y!L&A$LywĸM:J32F!!@Æz+:yvq?3FiʽNU%BխqeݬPQibIv;v`U`f"Ա5~&Mpu03g낖}xVwVnw҅a~>Xرyx#-/ ϲ֭my@=N;R=4v,EZ81X}lmoB.^]x"dIR$''ӧ*ˈ ! ɀc@$2bﭽŜ}رnkQNA.&^Ə7TwĸοRSpB@P+pر2g W_jdTپ;NJEGMhŸ&ƝtmŊ+`nn `ƌĬYݟ-XF$~Zlk_&Xq?~RhӬcPq7M;˗d U_~:}'=[,2r#Dz^꿇oel@XW\I!"E֬YH$' )3 =ڿ\`]@՗VC/x'ObH4 O 4nwҵFΝ;f͚aӦM8Xƒ`~yػc&|Ӵ>Xk?w\V &XsDYwĸhBwwwDDDTh&XF]vhȉ7kԙY(`#iuly"H$dgg kfUh=ʤ ɰ%B""]u͛qy`UֵgگmVZ:/b|\|ffcMN6oX(%'NseJN ӹ\;z~K# 䛬Z:LdƓAD? 6D"&XnΝ;~zcݺuX~=Ͼn Ķk_ys =( 4Y4`;`MN| 8˗/ٳg.))cyf˖-Xv-<==ȴ#3?.c9!2BM%XR2 "H<$$F\]~ibIXlV{ Ν;1eهgz*$Wsi"rBbYa4`eJ7_HeR^˸&Ɲta֭;V=%OeLk=-2X}V,Ir@ (zu$ ~r룏>yƍ%JJ>@g %*q݋\d׏mp> ㏗|3hTq'>\xӧO?3f ڷX ֜9s Ξ=sΡ^z*;jXNAbc0aen!@p`KOOޛS(> S==zΏVP\TV_[s[*Gq'Ÿ޽+W?0c 9|I,P@BPKlNW(/ďD};0lk&4+ug7[7W~ I5_/7`i=dwҵ_&M 裏>[o~ ԩSyVg4 wgXbk\P-+srg"2R-Z(yJ7A||<;cǎ!>>gݐ=|>2ނ NÌ UeYY@U+z9,5ت2zhlh3RNFq'] ݻU) ٳgeLNNƬY'''@ZZ7ߠf͚{ص ]/L?[oáhڂiӦaŚ{| 7Ws-Ph` 'S{n-_;Ɲt--- gϞř3gФI( ܾ}h߾=̙>eN PI& S,Z2 *;'{6v @˚-n%OLeԻSaefeTegMnz=yJ]̀SL)M-Xׯ_/L ƍ})I5aԩŸ:U4Vq.O&D:m.h܉q'*CUy-Ŗgdd^z<Bf9 O1 5`꛱?HDkzزe N8e` 8/Ƨ~7(#..;vAT&;]v9?O?۷GBXJ_'O>+q#iWR>+| >>U?@f&t7..$^16䛐+ رcGo63+J^kS+ EݏšߋFN;0a\zUeرcb(j B@@1vXٳgx>'X}`}3T*E֭5k`Y&vڥ|P(b <%W[s[4ui_4&i|F5@^2yh;z]z:`c`K[uۢG{{quq'i@."?۷/ \fmm^Cݻh޼9BDDΟ?ŋW-䤇X5.B] .`̘1ղDݏB'NFe.2G[PIȓ 0P'g@o_` ;k=c4j@Q?FxPm6/_ɓ'1m44j:99ɸ|r;vDhh(BCCk.nZerА̞=[իh׮1U8ph={ā׬Y`mڵkҩn Q>OOOW˰[`͜5-+`aQu/ 2 SWj|,ofj^;N[?[;oi|_O֭ϟ /22ݻZILyТE ԫWݻwǸqw^dr-[p9gaWn;b9Ш?oX Ç1b:to666_ž}}@D$c#qm5)p`V(N6;7Vkvka\qV=:gF+Kp} :jԨrb͚5FS(Tvvvx)௿[oU'=|LM:F#!SȌ2)JPTiK%ejh$0lϰ*>QUܻwfff:i)3S'"##<==+BGbb"܏' IDAT 㑛d 4qqq*\~=#!ͯKrr;f~ÅGg!PVjn* Ĺ ]OIDV\")) ;wĿdDGGc X,~#DY+Gƍ#=='NTv28p`^̙3X`q)F@ŋ -8|05k:aAC}&@BG_}i5xuoV*ō:?8$e&!I,]+}J&ΐN*rpɫVq'Ɲ*"i&AAAʻ`D"t޽/ 6 C2_ѣzQ*/Y`"i=#vTڋ)sS+W^?+.Wʽ,+?XoEzul7WLW˿oĸSEs WWWݐWG&y;=3ƥDնm[wJNt8H}==dݕUS\!?O~O&|?)5 ;`$szO\0Y7}gce(yگkݻnZ-8'37q:t&'4ޞf=ŭz{kAMDz*'N˗01k&L2[ndeh <:@, ezyRfBLflmf /G/l:r`f@*5ԯfIUPêޚ+ZU}={1B#Gĭ[&s ,_:u˴,7ӞO`WX ʝ k48?N2 &q'}>}:>CL0KZqF\|\lܸBf:SN*LdnvvoXp0`fƊZ#wKp2$O-@oO?Q+:u*lRfubCSn$@4~b 2PU=|~PY./^PH8ֶ)9)pvf,;駟iӦ AS f͚={oA^^ϲDݏB{Z$M"aj!ɞKti㏁- Xb d&X)pv}ܩZbIZl2{:u|rxyy9 VZt;III~+⣏:N&v5vZ*n#Gd`MNPPPnz뭷ܗ  13-ZV_sZcTVSצHLBz^:-UW:8T YDj'7Y"2h.\9sJ xq&Xbϭ=XCu л7OcKkQêmq3&y{^c{I\\oo ?Vh kVmoP(UExx8{= 2ιuH'D (B'/'W!Wȵ>{XXX[%ŝLN;ƍjKH=W*I@= 5'e`8ysN]n= E:˷Z;Ɲt+s ""7n܀gV@&X *! ~`knoE6N9&M i29 ./qcl,dd̖1#"i&D ш.w $X{7OYK/([+22EO>,n&@XMfC,j%͂N&q']ؾ}ƎKGj̬W-5~XP\l.Bs9[+іW "tɕPw.,iV99Y(gTd0O)))ggg&XY7H$*U Wv_>U >4*Ӯ]@EBr\[5UNy B@upb roU`I wQTnM%=!$$ "U@"Oȥ* KTޑ$@(iH!dCB6[ef|';;sf)9=w:u#G[o>OEh(>Lڤ$IQZS W;Wŋg ~>V* P?Dp8BM6֭[x HHH={PVVŋSEyڵg$!x^=WWW[_Y7pb H@OlZ!‰&fPPwuAN16/_ƺuЩSѢE ,YQEV7>Uj=N6NM;˗/'2;%>͂cۈmJ\ Y9h%5 ,LkN.h)QeQggg8:6>K ,kE Ƌpx@嗭^<ǿ'BTuj@ dڌUG#tÈōR~nIHjmԷUŝb̙3[nEvvvgضmE/"dDHRz҃0>|̦ ''&XqC!gm]ۚ.zQ#"-q(25K.-A3fx;ⶒSL:u*HMii)B!>}ݻw 9Gl m2lKfbG|?{'5oX)K^2 e?g?|T(mdj$y;.Ϧ7M D={V˩R2WS˗4q8YYYz]9r&>  7-j.>=1Y!MُLĞ{0,zӴSLaÆ{*X$2qyqj4X߄H*b݇\\\RLNmU}&fRad...?JGoVwCB ;.]9sA[MrgW {oT̺Mb|>A$08xФDzw B$WFKiZQ)*o/7H+zDzSQAKKYݩG\X&fP*Ev@׿rue26"%1*DB)Ɲbl|>**H;wGUUEۇmM[())̞er,8ԧЦ =ьLcv9r69i&+Ɲblv۷cG>}iii) kg[gk^ rfv3ΰ\.< ?c!9[Fƌ2XH}tu޸q#JJJxh>4D:yLqdJ$c}B #"qyhѬ2.VutwGAUGQ(fą rvSYS"dh;~QN-q;HdDGaPAF^}zm#&vaqTx\ =ʂNh5GȄ#F)*t):ǭ]ZqcyYM8og݆'D϶߮'XWet}ϏT&ktΝ;[Dn> W;WHw;wT,K;z=X,p-=}u:4\팎Zx\=n)"%ІTv73ںT&EjI^q7W"S#1 WNb_Ӯ=}UGgF+eb?~m<X h/ 0=l:*ŕV'swGqu18BPEiHљѦW`TIE 8;~@z&kbZifogtղ ,ƤFRti惼[%Ɲb^\z"ohHN-ʥi7X[X* ;8~8H&snjzn$wUwsV-88!Cq?.BN1/;H$W^AϞ=)牮]T`ILlK*}6 ͦ3I&Xc-o3_lZY)ѣG^0`֭\T`1-R"1l3QBbx>}O>Ÿu"""_@({bJq%|zPZ68}?[SOMʒɓDkb73n^J bxzz(//o7T`1-wAFGݺ UFER;CFsݺuST z!OB\!{f4 ;)x)ك7xãG0cƌF{:D(\:O[0x|._iJ%puehb1c|.φ+l6_'ݻjK*r{XoGoLq9K{B~8XU\mb|f͚4t &L ,A=X=|ze>oo, Wֶ+ `"HdVQu X1;n7zS#GbРA*gj"f=2\̞+*{YDҐezUUϠ ! !U~W."y P/+sQXw)J1aX֎ÄF3{BC>a-nvnz Q-F/Ǡ 7f߀Nw cX]́ rhX*,- ,kE `bVZkim?^XmέEDF_?5v퀲2ƷAw^'@_zw BIu ˳5..fH l5PcvڃeyqPE1#4`Dz@Ms= 2m l̯^`׀F)NDjq*,/(IW^űcǐJ\t:._C!==8q"^u<"Bv63 -lӆUVK! S#=2iXrWSh^p ۷~~PXUhq$vkQU!O' =QUU͛7ڵkx:t(Νi҃%н{wL:U/\m۶aƌ8x ;6ȿ1%3(tDGw+Ԗb;|yV[?-0-I_kӷq1D۝;pkq=2$P7nē'OyfYׯ_Ǿ}VcڴiZ$D۷c…x饗鉀td^Wab+W}*_*}lPة&ox:[b+VnDD`@;clX$SNOxЦMb8~Qf\VRR!1k,7+WDQQ a J Xpš!rf$ Q7WR]&)30z0e(k)MǣGpСË;wFii)222O`egD`Xf b jx7n{%2 ңT#R21sfUUL+oooz f+VzuƽSNӲ+H-NS^])**BVm%vctژ=֭xb۵kMS,Ծ,>]~>T !OL6mªnLz|n߭[x*Q?#o-׏?>oh"7:ub\c3s{.-Z1Xx1v؁@T*ū7",L1AFBII _L 4| zhߞnْR ːQ?g?W]:#z%ig>sY낫6̫]w()TT=<J_=$/bHx/A嗗Gmgmw{^1ЧŅ5zl1ޭR8;;ԩSpppYO`ٲe8w\2yyyzF`` d2>|X}"ʐ\'*aƌHH oÎ:t(كxbƍܹ3kD(Z&eT<\\ԗJN&eUf;N쟼'Oc-1x`DE+4 yBEHSS4 ,K(6mղqyqhN6NWb~ᴶbprruVX<)z܊\ۤEX-ˇ>zW""^{ K˻!=,<<.|o8+ŕݵo{99ILHt .D@@,Xe˖!<<3f0J[R}ذa6l/_,?ć~hҁpR?U~Y:UgnP]] [ V0\gS,bgdj$z^@i)l9 IDAT0D8x{dgvMOKm`2YܻqvFb3&x @ZF\^J4Bc;:UOO>'|bR/Bn݈gk׀%KL|'O5AO*w˗bKoODf*;cӰM*q _-[ >?^lެEWqzN1ďW<}hqZmw ŔKOdr6"͞a9iZ66ɶWyP`[J1I㌴ æB_>q5q3D`Y9NPeϹ+`÷Ѹ\` k*n2࣏HP2tg@ކh3\aϩũxV֑88vg( ޽$ijj=Ѹ<}Tn-O`yZdPe%DF"/|~il@Eԫ;p!#<42o ?bNغɿQh׺n\ش7VO) 3/\^0E$Pe#};$Ikžt\Ts5mzUUqȑFW֔lA [s NV;C `p>>W^{6;B#I^3Ӝ{w&E= D$1C.ZGusE 88ߟ1s\B`< iWW_~I#K×bj5-,+mѧeZNPešBT>=Xλ.ױ:(!F6X >>dԡQum 0Z+!ee:o/;z/}gU&B~}! YΕ+r:'%&cu(DR?7"$ʤpX퀬,bbb Ob1 mirH/IGKP t&~pS@._&zRyy8tPݿRC/ :bdԕP%RY͝ {*Mͳgj.qPb´سx'.Zde19۞)X2grj1F :S]M, a;iSWE߁wmڸ9B *{fVqXN҄<\ 馄+XnQk֬XgeaW0VH ׄ:2n\M,2`vR$\ڶ%(?yBqjʶBPiP,#%v|ffy;z y yĨ}Xbjm+رCՆxAjY?T !p O߾$'n@ٳ&8T? TGf& TU齊!m(9 d4iH39 ~$u;koJG. X"IĝBѥI/IǯqbQEP>pg{n(IsZ/[5.[tCM3//w5{V"!?0{|}*(1 E9M iII()A/^zގo*qa#GZNs;K.>GMĕ\tgr|O>J N*^'ƇTGЄuH?&5,gPjU۾7'7ۘ@0B=CWJĽo)ӲAhv;TI X:eֳg*LM& Xc`S?40X_v<>#:UU!I}&1YU@#k`o0ejќjR%X፰7iOϿtIal-Ġdglaߴ$ ,X)uh`s% 6AiC LpOx9nk k;GJWt颼7mh61kvUl).>eΩsǯKIgBDq`쿷_NPeaY3g0lI~# 9Z!!ZMCW#Ր7sSNq8ܚs :X9y QpXʴ ;w,P6&׉OKÆ8= R88Zl!"lUv6YҥKuZ};vG +`4 hʑ#bd칳tuU.wƄ~~X ´ߧY֧ܽZ͌fw ,Ίf <9\.6l Og-$8|H*G\Nq5vh9b<_sHOggF=}m5ɾd@~w.C (̥hȥK֭z=l[U5e!0^*C7b堕}ϻER~W-j4Dx,<jyXR #0Pgߖ\9|5Qb:r%$'( H$0#wɾ6UqlPU8u#x۶5ڬgSEa(<'MoaF**e nmWG/bYC`i 1JS"SvH&oŬ о=dݺu9lPZS[J+sp䄅q6ZuZ'TaL`+i*DX{sgڵbLT]W޽ɟ*"3632H'/? ;mV:-zPEp\R\Wjg9L4  n#Fh7 8fEDJ>1cgƣuu\*gCVt4;|VNEBJx$$t7j*88X˹DžMޚ;p!pQOEX-6ȇM`dǹ\=;lbk ^ ȼ,_~hْL ֻBdƨH!T̙qH`]䊍Ə'۶iG40S(T`QtEo_TWȍXU~ Heڗwڀ}gP ȉuoMl|ԡOD?̙: '$7&_|񙸟W>&b6~ZRMbv{t#~_]  z2{CTkkdKN)\ ,Lu)u,jȕ+-"%h||<1`W[o'cА*˸ ,0hpȃ_GW s:U&C Wo[]3uܒɾcÇ+ޜ9UO\]f⫗RIH}2#T%Ri{O&8eN޽ ]1PY0V.'nRΖo.gXaCprFJ~r7t߾H✣tXxk:zT+VBf7rD^e!B_mطI央>t?{'U3WȱQŷߒ+@/5~ƞ=W⋪=^$7HDDʘ9v'OֽX 4^ǘetdCɟ:v{'<=^Jlld_LGG!%-Zg5xbSSO{눭JUƽ5njV{1q{@︳MDJzAj]>"u4T}]H~DdiD`({ E|^<ݽKzP!.r-'+ [KIUR|v-_ݞD,KI^HzKYtH&Jv6yix>?DLԨ S/w,/'WVCi"$Ύ^޻RΟ'襥缾όùrܜwFBII XޙL)-%ĉ,A*2 H65^Z|$caߟ8h0B%C#&y,w߹keYhŁ.D H? LztOdɭEQ$ %WJ)[[tꨨ;I󤗮>DDLˤ\3dddHKgX!pvvƩSs69<?Fz$6onpTYgS|E={HM6"zdOEȤ!CT%;o!C~X*=.J9Dr9qH+7V;̛z35+R2S!2+K콻>U6tq!eާmmMW\d]Gl^{SWUW"@~e>>뺶*ڶ%S81gmCTje%>LrنQ?a:3oӼEBO "04I6df$3UOl?$/4+ z8p+Bغ Ӱq8,dmژWlzTR!3T"a驁0JLfNgK+q)s-[׮m`3^˺ pIbh@8;[plsoM3FϯB=CYG>Be W{ejqP2HdTd4C ȔyeՕ2uY3֔lBVUE*2L|^ɬ<{y 2dNPeZg@n.iW%:{^L 職7=I'EO`u-WM2-1n5HAUU)&~R߫2R";=ՠ^*, /`|xyTgp=:X2n=X:sғJ/JJZD Z3r!S-Hq Uie5C'QfH\} _>S\h`DG/?]*IU]UoT[oo-U 4C քxWQU ˑ]Κ׋ U=9Cjp肍͋OH ~@ ~~pySP~%톣klXa%Sg_>l޴ؼY <AfZ8ebͰse%)y#r5CEj͔)F_HJ^g ~b.]c2cHPEaë"w(Q] lDzmwP.*GNyCcz1:g*K [;d=~ ;{10K!ϯ|+_|PS( ip{;;|Vע!Nћ3 ,rfEXҪi6_$s 73n6ˉN=^Xp?1%/ɬ,<>2"qvԺ2vq2$S(]5˃T7͹Z]3͌psG;vMkPU@ip^yhJ^~:nsD"S#Ż \l]_QN`ynkaEƍ8jx9+,:qzn{8 ,Scϝ=8t777I196Ϗ׮u a!Z'| +K |{᳁u> tfpN:pI\z 0n˷iMc\b\.aooZ5x뽣WG$$FRC*L1;QPU@lâ"^Z߶skI JҚߥfc\Z5uMg(*+&;n: q>>DjN>tj {=~$xu 1Q/q[ªBa˷ERAAS2EΌ 9v quBzbkkotضMbU*=<wF*":3Um Lk\bfرu{P=PP@T`÷u=9 pޔ=XZ bn׮ 5m1p9\88i^T |!TRqyU䏥ݷ/Hئ `(G];G4K8F!V Xz*;$TVVҥKj0p=ϟ36T-7`F zF[ȐC4;wl:h qyqh(K*zu|9KR} yBzIչsgx "Ups3v q/JO&Uȴۑ] BVEE~``!#x45P÷6zTFRg[gzS';{w& W/brRIhܚQF"Sx ?J j5z[bg11D- -r={?3,Y;wB"/Ԩ'}]=ѫ6iFppwWt^|?,x=M#T9E$%˱m6aGYӜOfՓ'?{E=L4 =/^SLArr2ڵk7FM"ɓ8~81x`L6 <>#S#1"pQظE Np$$$ XM.U$&7[ 0Q)+keL*%U<.ϸD#G+C:llF娋{ffG[/й3{[20vxz>3 %ix d.DDx1{コ<==ѢE ǫXwѣGcXMMV` 6 >>>pvvFBBmۆrnѺ@Rъym7CA} U4v37K..,MGE ܿJ\=}XGx׼3 hjXu1J2\Ĵ /)'OjQHVlۖ] ;^B.+FD*MSuOoa~B!?[ d /zja^r`"FBSرc7ӧOFh6e!*cѢEXh=ݻ bUC߾}bڴiصkW2gΜoÇǙ3gk.L6Ma}*̀\j3Z-..ֺ}i͟UNNd~=*x95(M,U߾* ?yg""kJKI2\}HrɤMJSi|U˭զ}yv"#W+Ե~V])Sk*wJϙ;~_}}^^ot۞9uΟ_g]\\۷[z5V՛r/:}S([{Ο?EaܸqBL:99ᠨA BhY600yyyΕ+W*޽E5Z!W\ڵkqiTTT`ԨQ())]En$ݾB^/"@zi:FQkqk-zpmmښGE"`\hvs[.2朜-_ LW}q/1 YţG@Ny^(Ê T_+_S sYINµYtHKjՋo![׵q#޽[ߗp#G*䖖NC6sEo {*|ce\_.4XHgl,pֈ#?[7A+ ?/, 2}'V̟}2DG\<=ÇW0sNu42>}ԗLR-7n~7DGG#-- }:uꄶm">>3f@іxTd(Ƹ@$./JaqIE\DD&CǽR\71!tk$uv޽JePPVS-{7.&LPDRA?Vb]S /`ƹޓHo̙ _zr /,w׏m#Y\.`g9H/M+KQ|R+>$ _Z IZ Æ Caa!֮] t+V ##!HٳgnT5l0 ~˗vOlٲ*S] KGlY i31<s.lg{*TR!#8D ?;,|ZZ KkkgT_T~U-V=mcl3{wz:uΟg/O9'+8ʸդ UC $%zTgveJڷGʯkgbF{^jš^^6+O^bIRӴ:\+ cXe\.ԫχ ЕcTUV!<;s#TSkJ1V7嗺%R{7租>)D"r/GOf ;wlP[[ƇЉW_Sy6#]¢LwjU?;llC L=JirQ9zg՗KF-Al2Fϯ?DcN,i&x\uR-m_U r.Ч Ls!>6T %g)Q(T`5YeY:8"HuN[t4p۫FiZJ6Ŝ%!dqp SY_`[-\>%3&kYB X*LGpUL+04$7Xrͺ !  <:9: k{|.s79pqsI/j= PE1cn'O`ށG-%e(,0Es 1;A"@***l>^/ JQH 1.3IlPZSB's+)ݟoc;(e(!B^?װؙ܈_<,WTT7sXfϪ~wRR]XXw]d+ŕ8N'@8x:4mmqGLF8x7oh\BTQg?p*ԔbCsʥ G& (6fUa=LDOߞ[\܅)PU\[wpHWlfm]ۚΔbjq*J] ,T*ʞ.T%+/J]鱸SN;fdP,feW6k!ʛW,3R):+Zܿiuq e$Ԇl԰+aqŌӬ,]h~}tV}ebM+j .LDrivnvn BȯW_aFX&q?k^&M"lLSv-мG}:Lzѫ̜iv|HeRik2n?oa5&b(ä&NԼn̾ar i"3禟3MBsk>vޝY?sbhۡ s@aLDwhfӌF (*4Zԛ$[!gqWhEE^_dqLoۈmjB"#ȤINMVX&Gw~+fw$nfd?XVD&Љ4,P]&#UX۞Kd4&{,Kp7 ,o _;zuDaUau{#0GV2,V7[-*y8cC807nѶgq=wUY~; tT9-*+t@ xTHr9{t-lњallB99xۤx Vb%~:4z 99)ϡׄ112b .VRA_j!$J:xu@vyVH{9p=z7}QSEa;;[[T+^b=ggg䡉H'-~R\jIQgKj{/T`i񇎬?#Cb߉E{5<_D|a)y_b_k!i;148h]I*(l!Ia'#~tR)bM6r *ǘKS<7QsdL.CUS9Ż kwGEš6m^$\.uM}?>M7+6GnjFέ21RS?ƺv҇X %[n@\HYd͚5_QC'X&FԸKSC=CH*7Q߀1oDZcWk޹5q$`rQQ!zյԪhm*]ou֭njZmWZ/]jkA jE0E. !HHBf2Iy|eNsfs=w޿j1O9%;BVinT0gϘ-\N(3gv-/}c,+ZHHr $@ 0m8h#m^&}GZ/D*J 2>M7<@Q)"NE0=TYrur-sLvU*de1ʜ}v^𹬮 : "(xq,NYL"M;r[%s"K/IG oo M&x  g~#\>TдbT/6&abˌ?8 >+mEbIM+P+pƅcl<*9N&˖'(lcfm3۲{njOfgTK0.xCSX;v-V?1q7G$-9|._˦9}V炟\Zzu \:*+ﲌNOtDp`s$V֖b73pWv,[Z'ԭt痢ݡC-+њc >.lsC<+?RIDATv{wI&M2e2bFo, J^M<B'PVXw}e76fn{FAMyI;&aO1;}=jr BZqQ3)Sp ?lxhP}(B?MJ8Wٵ(@("W*QP<{ES@>ܕ[D<*!1!]!@c|\o H(F(Ȝ#p83*Q#1usrãG&m, 鯥-LY iԨ@@^9=9yA0oo\Df%2:h4yD`1&U}@ޤ45OyJ(AHqg)g㳩ތT4zu^ly=:76wV1J 4hMlRhxY#F^q*9jk;#&4#ݮaDMڦQB)oÿ?q*ftĝܪev~=(o[UB3X+.C"2Qޔ_|Yfux~rewrv=&M"iŦ5(!!t<yi'nU"q##H٠AD`QL{Z.頧ktNb"kQXB+'n>p9l9šYیGsQhbVzu=ma@KS}S^`yx9챠jV/(+ruIӵ{O'v PL=gq"`.w? v}fHjyɔ2t'X +vY!>CЛߡ4^&~("ZE=AqgSss^~u>½-NnU.Ts$[eӦM-f̪5jZk"_ p\ڎ.dJYmI+IѴ%7," [_7_lwϕ=H{m/K- ,TWޜ&m#Tڣ{6 bqDe).:g=c73UD`("qB"mN>vKһ45zb'H:1q=554_'! #V k֡Yo4ܽsTP5޿|%g[@{_0KR Zw-tixA2' vCNbV"2r-^&BzlOţS`wW'WV2VR-[?E] W@9+U㽻}m?h:!_خ).m^ȺK-YD`"ϬC#\6V,:|C^r),7xqFcs#2/8l=¤}aN֯3Xru: =3J2>0c"Dg>Jf9( B]&/ssLa˟qq*n%6aDҨ0Xg1VF8j}]kIEOQ@oN [Ҕ˱2ueϯ?"0 ,ZlQ* ׯz1t";8 ^z!^F ^zdUc~Ne:-{}ua[QR[z99k=;=k*! m)\]^xGpQ8> tE9yN W?s$ }Y#sB^ulXK#=KY-mU=ƲtRZ5ۄiG(N'ظO0>aK1m4p\1ˇYEk"^ȯ,-m6=߂O.)-]@>n} kKY4!{dH|)fǴ2 +5pSzlomߖ~n~=^IpL@Q۵uI&0>頧;}oڠi88M3*⦒]bd[z _-K sP6CJ+81K 4Ln{ ꫠ֨${l0.{|Bqm1XXD`YopcO|=kxD?D";fQU,YpsrBLUVX,J kpf}uTIIr<ӡ!|\}CSt~@-M&}xwhkH3@gܙǴAt[E9ӫ-E%LspEtZyqb|4,bkʮn,Z/٧UV2 [l#mG{.XP+PPLۢmY(9I,&@>9XB,???gW>×9_:tw\܁! q[Kr\T**ici%?jJ"p445XtP^fc yK՗D`lˑr;š?kOe+V[շB`9Y}.K;vkP0b%[bս=+ g7R%lk XvѢ/~x2Dj9V7.>q!j5XɓVk.5ǽVU;=y$R }7f5hfdd^3({ [|/#*@K> x}I+IÅ 8*9mENzBth NA1ħԧNc|cGEtx=zDk{J}^Ų  #S)>O碟)hRL)+5?39J"=#ӱzjbj[uT__sv! JJ i1SXv-텋QQWZU-eA೩!yf2*ь{|b JCV{ \x.ȭŦMVf@xqii)e_X8grO|RIĢ=I9܇YAKٳgcҤI_ Lf2 .&M?KzuhXjÆ K/doߎ~u CY#{?h7;"tk2QOYd?;sH{, "~xZ;{7})e-, 1&hEr[DD"gN6,Q3RSSW_ƞ={܌{{=jٳ 8tRSSC`EGGc0~)==eee^EkX̙3iq XJ~z5VMx>o5qv㿏֬cL@YqVx6EoBthmSQ!Nl2 N,9=ѣ3gF Y7n@AAA޽{999Xf 0b̞=Ger`e2ڵ `}1D`ϣL11lWZ1O=?7z*;-^;c-WFY=Z)-ŏlRZwjܽ{Cտ&ЧOH$.>>:t( V)1֭[1sL۷ψANAL@>-yXs[%C8q zMq_{ЬmF̼FmEt Ϫ Bv55~Vӡ"qVjj*r9fϞQHuR &# @Np~5'mYCң+Ju]6 'rrrpM ULL |ML> @ @ :J^^%bAAAe2zaB555^d`Xߨ1N`-Zco~~>6oތ={{ ͦt@ ux|  RI*h3x`l0oT*C둘%Kqqq8q":s $b׮]X~=J_VXG||<&MDhԸϛ7f? <tp)TVVba޽^h?C#!!x /^#iq7y_sqq3Lbݺu?~<áP(}vhZ$%%͘2=m `͚5;w. 0`vm5ム X)qo{l[Ln?+?c۶m߿?`…HJJ«Ji[d86lhe믣s[-cKbŽ}aڴiXp!;F:ҎsX,bq.%%%ӧOǂ pv\.˅@ 1ezۊzq8 4r >Sh4aݽݣ㥦씉'Ν;P(?t T۷'O6<DC1u/߷o_ȑ#D``gĴl6! lF>l6v5X[h(F^;#,, Rt" QWWVh{WKp  V nVŦMPVV͛7՘gjk(֊be(F^;ޤ3jn{xx tQXXwww6NÖ-[>[K[lV ÇDܻwpAL>t a!;Bܼy9ONq+**p!A*"##III> endobj 2 0 obj << /CreationDate (D:20100711111047+02'00') /Producer (matplotlib pdf backend) /Creator (matplotlib 0.99.3, http://matplotlib.sf.net) >> endobj 8 0 obj << /Pattern 6 0 R /XObject 7 0 R /Font 4 0 R /ExtGState 5 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ] >> endobj 10 0 obj << /Contents 9 0 R /Type /Page /Resources 8 0 R /Parent 3 0 R /MediaBox [ 0 0 576 432 ] >> endobj 9 0 obj << /Filter /FlateDecode /Length 11 0 R >> stream xɮ%IҤKԆm@!\\qh&ѽק|U? P%"ƭ#U5Ukj_q~?ۿu>|ߟ2MZtqn?Eo7/{ދ;fo[wqghXs^~xo]oэ;%oO9m5;\>&9=g}~y9羮kmLfc{t]M:s;W}kk+Oz Pg>tXp}ܟ5PqΉGsbq9ϡe؎ =wSZꅞVeL=~׵u]Ck;[O}hhhF9Q< jz>[ۏf]NO?r'Gͽ~lucw9^5?шS՛ zAI9>~O̞OVx}滍5w?Dmzi4>=_K3cMchSи'-?<3zS}MnM0򡍦? PZ'4QG7#43w)]g_=???O|j47ߟWh4 l'?8D:&OݩScy:--&G-g$]jo?_GO6PQyt>?ҦǹV5V޿qv׷y<33,Ի5giэg뛷[5>_CSXmeG??SsKc_|L׾v)ߌv&t֜n48}lZ_4;3Z-=Z`5fZ+;k[~g }_kh,9-(i{\IoUo_-Zi_KFտFE,m8"?ޟQAoW]dls/sA;Z[54CVj?:-z@-7tFm0/iY j֬7j] !'F n]~ktkښsQC7a7,fYwyTftw[bbhieӏ?dhL?X" elmlGS.&s_$ikӢc-Mnȷ5Խ??N547 >-fh37|zYZ߾`֩omzw;Z=>$z}7M:}-ڞuqrN+b9W_XIKgk54/46No{q:ͦ|1Z´khwrپ?:ֻĜZ(4G}mgZ0755kw{GĿZgkPYZIsMBF5&jе=cu^_*4yxK vPˏ sKF˽Fbzob`gzj} ]z3j}d,(yŤM)w$^~{~W>aFopcxbs!fk&c-W{_|[Vy=4=M)>JE SʏW54s!^a¦A} ?_ϵHe'y}(}fޞvF JR1٥{]Z(q)w7׮v,yRv8Ѩ'n+G]L6+XG d0j=Zb5ۮ\N-ʝnak2ٵ _Sk#_AmjyyWBb X1mwˣ jqca r!жMKvߑٍ)آw؃ ^6[Qbinoö+@nq6.+Z`9vG5#ZC8a4;̠i1H:LíNho٦[n픛L{K?;6rz֤&j 7쏼r㾻PXwj\=W۵oLb># ˶]928yڏmWfճܨ?v5lG֕slRx6Z'$`uj]5.ӈ8pl4J%wE۶+ma Y6cg%=L}woZ/nTEap/Q6|ĥ:Y::_|]GuC0bWkU|޴-|#@dr~]07r~ v=4]\o3fQEc{붿:F19Hٻ.ƁH{'4lȞi)B]a]ܸ jr**vhpZ8<58\\M~;`o {c-.w%."=HW.lbs^c@/͏!o]Ns7rv7=8Zv~:75\j Gy:a8=Xs;\MVڐ4\MweoU|TrÏĔKi#wѶ;q]MXw7Y#gi2ɰt8QEg{i_a{p,}zGkV.\sJݝv!ᴂt7kvYp79z`^o o]̃oh]|pQrG{1rGlc_8]4|,]7\3t7pm4*yg;]{{S@pN||cmT\#˼coH'״﹟ FQF8\>m$lhtʁwE߇\I iw96H {.풏lq/kl2b |r]nyoͰsdAACjf ejs˽ Fe#QI.!vV.FFoddXt[Ã>'l/tF]*s7A <YB_$PTjەu tfDM6VIܨgje`uA%Y}(VNiwW}!Xm3<.6֚ Q]0!J}wAs5\mȬzm&I7''(l'& '\. i o;c 7'%:ocRA=\mLj20uضKHҚ (w~) {M*md\Fr׶R!^\0j,J2S=\m,J289<ySM7NȶKI.Ͻ.&cImpAn%21VJbz.5]hC}Qppo4ZoZab/W^"hecȱxo}RQ =dCQ,Pq ].U28=eqѨqʠ2B3w|.\Meae05=20\ګ6\MF)e-.i-]j2 "r8GoYèsA7/5p88M }7 T`ՖՓy|{e˻#djgp:CQm Jko m0Egpԣ(=]vhU>J2bۣ$v|Km`:MƟiW4`1`fSA;e Fi׌(xߨ]J2Hup8J2` s84WKƞ c .65:S8Ծ]4^F73Ns ʏjyYɀɐ`9kjLEn NI=?.|w)ɀW(svJ2SnJ2R! 8:yrݨd-BosP 1֞6LevhZ/EPܤ( {`$P?pdPpd|Tp7 K&vQ]Ҥ⎆Tw7-S %%<=9]0 >2 m%;%[܅q&$ŻPͱ e$j-Q-e**Þ[N93a6h fP]Pe.ɻ! |rpvR?NIZ[p70&.+tv^=!m0cH5kj2Ns7΄6EPޑ9dev5 B>j2ݨ?d\ɁupdoɈJfۡ&ނ;u8Srz7JE*&L"t ad.UQQwWޯ-h.6״:\u FaE#Wе(ɸC(ʶ8/%o|noD7%a8jFˎlQQq QvNx^rbh0 v7(ap72εW N0أp3:A...E:ve q7\.*z{Ù8ɓ F5hv9/? r[ϸMm=m4]M8L nׂ1mempp5~S;G*.鶻hx{ k,Ao~BYQ2Ilqt'pQA޾ñˢs\ lXi WR=\0j'!O{]t6(~ i%sFvqE6Ƹm [} 6"z.%򜶻 [5|[܁4Q{dB7 !VpR8O@< |˳Q|SFys(Bܨj2:5 2\Nkհ 1Ķ:1\XI8Ҭp/|[AUud7aWS P OrOAsPVpA簂 k‚BP . +65ahmwedLmmJ\pXdYu(8Vpڱ+%Z#4< .CUƵ ѭe0auu(xVpcAлF|VV)lX3@]k Po(8P[v)Vp`ەwܻ\@D10dd+oI)udVfj\XlZ(* 0kq(VpA8:pƬ8VpA8Rv:6(+@6ۚ\tN߄{T[PWr-m5aH\ AM+mtJ?̰O"׭YjcCnO^+ A. .pP! !a-K8BtJ2 . 4HnYG F. .w wX  kwPpdLX2Qr 4nA 3_4Yeܝ*,#IQG+jGz'T!~^&RQc-G}Y+q&CԠZ<5͉zazh =\Өܵu=E% {}=t홫Te@{AHڇdt񥍔2L,LyTT|8P-L{0L۷>LPxYS8Y?;60 Wdг&Y˂2TyYn5N@Cwe#~``;YXWBw Qҟ]# )R7O~KwAdwn$wCMPRΪiW"A$W&_~tvڀ>HH=R] U@|$Bξd[Kua>FXejtCKYtN3x//+sRM}Rb`zL-w1QIܾg"_Plj&E\j61Z*m ʵYgg_d`Πɉ ؠ8S.՚k;дiG=1tLz5GG>"ͽ!I:19dq]ciFiB;eqY4AF@3D(^Ѵo&=&''&3!wWc^5/TdpD`b%\2/,Ud6Mtip~DЧ020`0WL flB&5R{4G5[iG奓/1]KrSfszd_'yRn`'`7jg  !:g3Nf<:ٻf| Dg6I3_Z&5zrjh2&=lj$૳f"_G /О+!zM V_#a`nt/r"Ue1kͽk{CZ (w0Ȇ|A5<F@ ց1`ɑw+#]&E-NuXv5pO LV"RFF\FtUq,j `$ PZ!R(k2Ld Ry̻}( 0P4040".0kM{n0X+M#[1CV W&DXYikeK9\j\Q+O#W`.wo\5MpI`)%G_K |AY[5N3njT2 i꽚Z^4xm^#]@Ύ&;J*XIe_aZ6nfF\7h#s`- d&WaD#WX d7)ޛ~jvja4/4aW1B?FtF{-0] Z`p kB5ݛXLp}VGT jYjSi_TAftTq mbӠ:e.6IpI30GߎQp23a<5OP2@R^6&ea_e arU7=3)4030Os&DZ&>{o XDkfL;n&{%N2P 6$F-qWb^g+BsU![ռ9s\3-jZ`ZFc]Hp:JH՗$RS#4pjd`"=ܫk-rYQKf-gKNL yXȴL૽p5Sl89b^cked R>Mø ZRYqn3l"_+jKX҅CW#d[w$,5]lJibpJ; L!u;eWۤ jboK\0'll; [٪7f;Jd c@ Y@S\ jޕA+YBܨb绻)/wP1wRzq2Nc{:*qsj;נ2 C. 5M$N%1)7'pZl&&qWn5U{/ %R|>,<*fVi[s:jAbV'&nt:w$¡hE7T%wS=CTn!Q V޲i9Ӊ?u3|d3z8N!mWNdl[%C0.a (Spî]X''o9:S۬mhT] 0q: ͠wHNKhr:0;ud@:4ד=Czq0}ۇ $3qpBaqOV]u4jł6 ϮK>Tj6nUzEoHW?9CQOJ7n%P~ڸMj-E/K\`5#S[u Ee}8pgGh캌A1S3'k8qCq{O\J>N\#4}lϰK/V$N 3ڥ: qZ `Tg%k^R^3z)eaCdy Kgnڒa,jp(xJ ]Y}S.F~A>ٶ{/4@.FM eE29Ⱦ"P {o mF5&Bub_yie^fpp FuR;@R7 9\MmAigxWIGn'$8O5QxФFu6nˬZvz@qbݘt5TΞ۷vInM£kw5 'd1ZM=nT; 0mw\68E!:.LZ0qo2舝^e1{[_]uaS!5$:?qN[VGfYsC]ʝfAMym)йON:T'NƋ FJ\$чQ'NӣCp)WS|F]̬*5QCt.5]+\wG@j`5.)3'.1iVupRAZz$N3&\>_^dڤ)V]靸HMdm)qz& nCk*odq w"3n٪p'p2"r\ ]8mB$([|51Qj꟨?5R\VP'K#T-T2+weZÝ}}),ora >ܛlդcSFTdq& mYyl3ӻF BXwK qQ^:H]lwVcLs7U:37R'v_V=L`v-mە{f{4-j~wn*;rf\PEwtZG3qdo9 .ݰj2dv8`ۨOG}E:(r=|w =Ğ߄@N#ÍkwsjwM\7k/w 8qkb~ nSՒXu)C9Ǒ۫'&@.UQ3c'[_'NFL07 Jn24mezX \- tPq:Q`pQ˩V䉣&|՚aKMƮYGMƭGr+?GMmW$Q+|K %0in?8)o89s5WDʸvO[-Xn?2tE{=2xL2 lQ(G8wVs|#]L52$s_MNUt-Y MRxˇtBm˼&b5ӣ{ԞZܽcִKZr'qTeZ>k p3j)x57҇g(NIƩ*Z/8%}o)ɸ5CRMVkQk1Q!ڶ^ӛs9(X"Xw8[{[28ldWRqkUlp(ö{rD(. mfHF0 $#jQ!k6#PM W2~gGI\7'%❼Z*,q@mo 3% ݨהfylWfD&cR&>|C~xv(Id;O_2mBQg(Z. n/j2{fhЅ3Bc5ˊqwM8J2nngiAIƪI|GIƮ5z?ܥH$]0* _J2QٔGѬGI{m[y O|#En MQw~8#d*cقMMsVp7 ƕIhe,]9{^'Yk' d,6MJȰy!v9YO\L,}صtv$Gߴh(騗Q?_j2=G~ B( qPjQ[?,{KpR(˳b[<]m^H儺vZ0ȇ)bĕ `mɛp>8]K؍zq1]b95I9 nS WRqjQԆÝ (ʈZ{!v]maBcN#f;ө\8 fo(mPv׶JIƫU' 3#pRqj-i$cֶ{{f ɶ!QQqoķ -dpGTQ ).=0 a.יKtdXEJ[%b!{4B7tZܠ&S-.dzҨG@P'2z0.%P;۳LD.up$9]${8]VI?ܠp-ajcQ;Rzz sJi 7'tY\qǬBYx vw9|H^ֺ=lɈדyݣ639[\'ujl}*lѲ{ŝKM}֞ZOIq <RUӗ ]%H{O,^Ņg9W]S OH#/ .tJo`ђCwV iRʭ MColoVf$QRJtpAnP %GBAPU )ᥣ u[zk#Oy5PgC|!neG]tsթYYMӞY`5c2#~M*&XObrĔFFV޷MWH4(Tf]`BO#)u;5˙d,wqfP@*hI Gы<#55b[ylN ,(]FiC)vYG2}Uwk3{]ei5l0n)Vm2 3H$4AzqF XtK/ h[hv1h@:aW(b&R@o_YO ErC0lfP+ԭ[9:nBIVѢf䒦ZzItfQvѠ>saXƕ\f Xޠ,-3Ir h7(ܽA.30+(0ܗYww|,n4ͤ*uP| JʛՠέIEΰj:, ؇q'^|g YE H$ک3DYy Ó)QN4=&VM!K%3d޽{"v4,/-q]*[;s!0^E V` :/Caiy!%y3ӹZ ɷ$Z!' Jp[$A^F8.$$ydIZ]~u%K"1_IPlF%LP2yJZcy)諏2hAn~ǀ#Ci]wZ(pHQ`m-3}[vLBw-FZsAE 2wMn*Zo,nd;p&MZňk/fMQSq`g A唙~ZTֶUF3S5E/Kk6X dSKSfbF"5΅q̗%_50:z-e|1F-v , RI;& ? z]- !k`dh`YYˬZc,Jzݐ\ gN,+%wiZu~1(9;`Yl%3{/)wkLFVunXq]k_<ȝiԞY  8xjDMYT=^)yjz:-e֟{6R @r+o`܁̻̾5au&5R06jF`-iu2`5yfJKh4yPj3I_<r84OUILǖjֻC9dX@ռ)/7o:R$YQl`d8互I[NL˙{RO3S$`1)٧fN(d3d?SN칄#WbYԆ pD_QWa>Eᢡ6#3_P}; 4kiSUI?h@k`gJ,L]"N')DbRODhǝ+N|W.qI9ժ!bia"_ړ!OA+k#j+ɖk`Hmվ9bH,u;y ev%{w-kIAjLZ ڹ9YD4A}KE%Յ"& *\70p{- uŢR%=ͬܮ _zZ2TfD X Vh㓳\g1YMtIZQf?F+ 3͚Ws8K&[ j `)ESkt[ژKvWGo򢺫q)?̗@^j9ϗF+02fM2^@3%ٝ$3 Fʮr\V" ʚT*~RFenz$_`ʊSrIX5{?S@/'eckt2Q ;ZϕsrFjF@YՀbkknE5!_DHí&2?j GN&qZP>fL&7j}ʯEVM] ,kSݙ$x }5uik-RGЗpW_>.A@ ]-0jrv` ̚QE0vPfjS3Ki*Ҫy/ ^}(vՇ)L FRFmF}@yiB_ѐvsdww6TJWNJ@i؁<06nd&񽄫 lMS3_F7b4d50վvpS_yW,'B5#aRH@HeK'a#a j`m_hZyXD`3*eׯuk`?ljBրF@w-0j`d a`d Z nۊ +g:ޖk+e(V/XWdVlGWV8!Bh 'cGfH-uU8j^_jP==I7ba,hC77fQ*L#CkUw_x}unu,(:7fpdCt.ޠi>^]=:,*7JPn#s:OqXfIB]9FJnenMhu ]8g'^_5pszK"-0nMdea?26AyGF>UюK.Fi]x!2:FV^V^]F մhՅ㯖讑g\9dX*EҎ^_9ٖ^_]q }^mյphY0txZt^3%n,eG3'z}Z !:ǐ#tkMkP&f2;ڱWc Ӟ,n5gVvw78^_]~|N~`5نǦ|78{}u;7`[NH8Y.w8Xph/:4z'K:F W'WG-rUwtÞ՛jV^]Յw7| 9%kۡ&72KWoQawG[zcy뫷~:UګZ*q7]0j@~ӛWZY#Y}A\0j5x}1׽: kG 6Z7{zVt, gJB減m(L-IZG솕xE^J! d!t8fxL.60o/8>ʍfŝ2}evVFZlBĮٗGIzbh<| 7r Pv8϶Y.6YΖmL@ʀ'.6x[ (XQydNM$X8.6 * jn|}wTd+w2i7U?.%@MuL`+7Yo(CJkpdܚ61qVpPk.| =8t}mޭ`pWM`8bGFJ5>cRa~)%v#o^E`0քE\;X ]j20a =T$E[}<.5Z&m3}-@`ZP 7^np^sd~3Sᰘ n-('qȄooˇ"Hw6.t9[KC'Vn-(_̀ծy$*m6iB3ᚿIi@^|Հ Ú[]Y6:l{Zu_j^`TDET˵t@QdM k5B_ 4|lZݦ(4]Ӛf˅8yG #[NJO5]0CCllG6mX g!N;Vǃkgl2JYdtna')avF;@pG]X*x6"|a}P Wag"PyIq 7!<6#Ps,;6 #xwӟoCau`ڤm~A#K-ǽ!w'l?a6[(_ ͚ yl$ !-zx;wn9L.Eۖq" F"!q0=|4 ˉsi|YWZ*%`5'@хl0EM^Fq Ci0X;*3re`sNLL6jY0gW@dZ.RMR]?v)"ոwLP(z#%zF-o]ώ4o π.IΟ8Ic9NCͽGs a;2H5L 7,eㄈqf+ܲ*-?N,g}89uz8}'520yx_Y@$f+ou:$ V ˬ700װ5Q;1M㜧f5jKmj>m"AVpMAB^՝0/C G{8-n i;uN)$Ŀn'BwoCg^}E9r`9XƧx w5Yݾ./fOs1 GY*L^C1PfV1JHu߷; (V{{$ I&-''Y@yNP\kQ\։{PzN#E!VIBpTxircɟpԩm(V(%WFvfAGT._cD*q4 prG.zNt|YWSnM[Zlh͕u<ߚ3-sr\ɮ .N+MLugM—,6ڴ)v5,ֹ w 3⮦²MVΜkGC)v޵ĜjZ،ԃ9``0yM|҆ (Q]$Zh# ̂t[rouw16՜d' z6bMRA;q$S=Tw@efPIv"m9=681Q5KM^3ً՘r:dg<׻LF3atXY;N3 OΖmsS4U]dڨ[xu5?SKUIc`0I4;S&ЯF.vl6cBkfMgM[R f&wVOj4/HI1/&Ck~'{q굚 .fQ1ں#O l[)lce"栻mPB j;#xK-0?f#]- u`>sLpI+\r'SېM'e-Eku=klXYJ5pK`t\82f͈RyseyųLIədphee ʅZ8ϰQZd]VL6J W;LBP cg@nCjy |Pε9 +=V66c:IkjG[+ll\8yud^F˞2TcбpTmqNZ[8F\_ԁ;V6vdvG*XuT(B y YlqVFupP*,r!~ ?EİS6syo;3l̢%q*kec'(˨7 gecI'¶+OV,WfE# #ۧ"`< nVCcڵwb,B"w[}b2e:(sIP}l(9PZfjۗQ]N!*3YXY~S5neTn: L *w!8󞯒 i*u4$k°װ&yqVG/R(<3*uG֤mV VY|^Fu3NBdhl[3}plaE)5^Fum m7K̃,!6k"9,.S-m,Z3+ Ӽ*{X-QXḧ́:7ufcˆLzHVqta~F R i9DKY&3hRe۝IjBZO;";ʨ$X&vzzurN< y9j#s\.Gڅ{+HYfR%!8 y;%7 ;ű[^ "QmO ZV#)Wf ; hߴkQc:R4E mEډ%Ґw^Fq֭RG3C.-=Z:%wVFu˱,]- bĽ֭l,LrYdaU"N;尲=&,`m祈jx{X-ڦ__xGeTVC+mYEX#X;E;X*u@s|)8^Z]M8%d 9Z¶+SjpK!q:CDVC2 YX 1]Z}vrͼjiַNb7qTp˨&δBk҇ĝ7['>!VY 8>kwe䳲DR~w1q<筌{Y됧<6Xج piQ]d.ԡa:+jđ7;^G5;V25"Ex"$^/Tw+ԑgdՉWTfiQ %^=){?ۮGő^'ۄQL-.%)P!v=]RSJFCna:djM#p{Q`eceLC`z krX$[ua105{(wYuk\qg$WdP%@2}'KH/;CQ2i:I3qD:a:qwXXNZU Hloaec "ͬN^OǒIh2RJUPZ s n Ga9VGUnZCYfݫ~v*kD۟^;op d8%^wQe?VWJw*i`q]kݒi9] VEL߼\vhqaQ[[m/t^]SMK_kS'&:lE"}oxXhp[ dqF c&!ώgJ6)Z_B[ G-zNK^A{46b HN]7&FЫt#Ͷ E󪉩G\Jq`!nsŐYJbc6+^j6ġö{{O~H1e Of"6K]Hl(Ϭl S+X}]8FZ!>6J9=qlt)r[Mv=ǐ4`q ̕zsBTj`Z1~sPFNWs=/İYqfZ]dAO7<;zWg  L9A_Wd.ǔp璁lzp 20ckwdǼK0Ԉkssaz#o}:f (<.囵v f1`3^HJ]z]Ruktj+cTo9d-;K* f5퍢GmiyvAvAȢSV`Z9bm/:gw.%Yu_qvnE%@0J $z/9̪c0V"EE[kVsl(=*а*)=&7i8t)t4P۳ $;hIAA:fFnPQCo:y R ðz(gGdA-Lje1V,uxs,䝴GNЊ8a7I,{o4qSӠo;& >%OGStw, PG*ؐ_;Ă2}SY铻} y#PvE;kްHs|YH9|#Gu|AmJ3WH/M{fM\-|pj9V:sz8d8*GnBvB+eN"9idhsT#SnER+&4['mNl2:ݎ&?!fp8AG \Y"N,Iݭo:g{xK]xMKQϬoBc=Nx] gg|`La)t* ގ9Ӿ[Y3T/iΤm&O\DC5ZSs~`uN522>XFZYs sLJ]bS(aFp4[ >~48oQ oix.-{}mRO j1(INצ/>Pߤ9іiTo2\'] mqZO巋/f¿ʲiJn玏Q4`@n`fIqwGpwlϱ SJMٙo 6`_Zrէeicív2sq Ϝ$I[t+Ò$9LէwLէxjٗsLK3mKn%NYyzHJJdc[۳íI&O1s2<pLմ1驛ɢgbi%ቪe;cahiYpX 9;ܬp)ڸYοqJ:88xkqښȧ{ 1'CD}A_;yw1kRFq79YSi@zQac㖟' ^m۴K{=q3sK)[rǐM7OLH|ldAeK ƶ1Ғhk!4&tp OToS,ujOcq`ce*u>>RRZUViUe-Zٕ,2W)w2ǤTK&δՙ Ȓ8 `,+0F%~,ՐWIf碈%DǤ&?^F|-YwDlq0efZ,N(- 6R:F8G9(q'lr%$դ,з`{M>a' b\&|8uЈ8z"oBwI+ G Vdzɕa;`kJx( Q pY,eek,fa9\f̲@+9֛mC Ѩ96~6 =iiCI^¤;rƗ9p.XFD[8NTGl#N?F"9SHi# _yJ|_ٺǟ~U}+qV]+BT* g2E\*3X6BfxA^6a3m`Ceb+$mt@чUqPLStR{1Mzu-*G-)٤B+ 5NٔRCK*>#waKbgX&5e eǡF:Z t4dk]:N9k]L{W^[M/sLy&L۱ɛNqOqYNBflz B&Wזa\æ`'R1 ֣gdvxÌS\;nN)>M'+*, w{5 'L1zz*Y´vVp KED?XRyZp6i5Ȃ3mjL<RD WʢM!==Sw]1<-n¿')i8&y2><ާ+O$8GTp\3>]:h8X VaA ldݞj` 7Hσ_\7vZq_ؕO&Gwdϴ8~tV@̖!~iɣZ,q#@tm{[uZ'xdԘU Y`pKۊ{s[$T㚐(s8L./>G5Ca;߁%W;pvC$ 4ϘC Zڈ'콺>8\yzA^E4<ʧniOݒ9>EG8HD&6<.[1AA(Nvztp. ϴF 8sxZC/0gz)cs,Ml%='dYxȷj/|~:ԴF Cդ5hU^ j'Qۓ8_ x4Yr90v贰!Hz@:qWgvs5z#Yܪ *:Dgᙐ V#^`4Qyuho53h 'K۲kYpr C^/>Gl7<~r Ccr^`Uc= $ᶦ“/MR_ [|63IՕmoR CXξD )778-$۸܁AiZlQqø'-HOcIYTGmՂrƍ?p<_,/,.af[wX^M[zkaw:Z]mɇ*()-Y_we  ah_҈V"Y%jkCR[+m.Bӫ"i^hkXG*q{'[S./XO[ZVVz:Z[C"FֺtRF3mkorj(>kܓ8iկSgq9 >;e7EZ:d3}a s/N9I`Nt:v=o'K[G~ep[4Ss+>$_Wy: ۟%VM88نu}W.'hm@7`g4W3D{⥏,aԬEfe7'˿[To O7XCbڕ"SeOĚ(F(=aCW%mRs %#[]1i|Iߙrl*ɛM9b>1`h#YZ:}m&+6>ct}nm]\ -]Rܺ+ħlvԃJ0;Mct r)2n9j.WeБ|7wL"=09}9nV V{s/n;Rw^liv>$q!3M#` Aۊ{eˠ+=W^SueKa}F9-K"@f[ ]F;ʣ !r)|| |krkt !K|$\:]VU6t ĶnJZAuR**6'+k_M2pӧ!;R/NժٶO|4Pul׎% RGO '`KC:? sy`vq^"ioۙ $m࠾t+u/iGA3D!wv7W=HcqRz&2q,2DciɪvtHƋ-Q_!`A&?ؕyHA_`>mDj&ru!L;ћ`LRN'Y m\UVkѮ2W4Jjy9܉ Lo1&0h>ـ'E{NEL=FfwA[ NN.@&9> H.["W1n̞N4L傞kvQ2 B I-w7 FLNSw+B88qtwjF-[ϕt\1wӛܞb848J}7ӣp2O0Kf_W )1Qc›I&m7Y:v jĹiohK7ִdAb'At8>Dő;[WHBjm:Jb^VA|13}+L>WHy!]Syό3>miw$uS=)fLEi&(gPm4AG8 4 c/t M״ȶ LⰯ]Ͱ<4H|p9pYyF'N;UӾڧl[)3%t$SY9\gJ˜Gg8Iuål$ȧA+o!n*Q[HZ+} =Zx,/J}259 1ȚD9&?Nְdh74wʒvUVX +7q.' z̵̧F?HoW~tKauymOM|vv9}tY5}z|e8$[HcnǑ\GX4 s@$z+{2SdZ\W;+u݁X'^5WᎨ}8DWܔ_=DS| bǝg8\eYTq1477O1$]BJ)q쩯5&}:dٲV=mjU=%_3awQz@&19?v*z>_}|C8c@˩(T|ߤ?CaW/A/Wyy/ }_/?3~ǃ.yW<+~@xů:-UU~!=/>Mii">;ڻ@]nRwy>;~B߯;Q$~@?jݯ!%|/04aqE?~~0G]-_7?MyC2W osxP[넳*tוYq'o=?ZHO/m ,4%빥*ڏſ7˞WC8Dk^]Rp1>ߓ>Cܓ^ǐߛuHӅ*e>y7-s^+`-;͖@:nYJ8Q__3w Y_J6t}<Ϗ[;8қ ~!YX zk/ |ut5+LKnV:W>Bt=(s>1F5t#(0ד!黔\1t Sr$ΘtisEfEҧk4JB?%"VTGc>_QVI]φI#qr,ң,s.ql1~-s}0˅OVa >g޵Ml|s>j"r.}{ T>.:^zZRxӵn|~,#^c46ja%cvX29owH;ݵpx#k!F)seZ'@_LzܰvtV쿮Q|~Fumj hw>d:_nv%.?~?Y58Wߟ_Z+?Zn麟 ƏK  Ogk!oH> t/.=I~v~Nu?;~ZyG;kgǬ$<>k%"2ky?p~j.?KDHiomw~P `959"31|6"ow/ga洙_->.zP9c[tImGr!!O?~=N)^*kͯk5ڹzD)24mW'뮝ke XOguY2&apY"k~yvҫU;Y-SƦ2dBwz~y Osy첥\v=[H<.Qʹ}o)x lɜ~,魓]qY~T[({,!Evƞ5XIZ ?B$ˋds>u ymzT$F)qȆ8Atr;-dr2_kK@?%N?ig@4P~^|pc< u#}kUϏuNkh=SWuj),ϑ|lqЯepa_8I^^?^"C* !h˯(O5 ByyY!Wr8i*_ 滝ʽ))kRPO'K} UzԝV-r9eaɝh|\^R8G1tnы3lRv_%&txC$Rz:;VFZ?v.J-\ZmOyV~)k=pa=gZSϨ[]Vysw}!(1} Ə_9ݕ=>m4KjLoWkZZC$kadqK /Iݜ.ͯ;s1qKH(pq~=yl@uO+GqMutƏIЁl/ jiZ!U{JjK=] aYR}•7gs:Vͳb\ϣԵR1㵔o,ʹj İz%#\~5GiV uVk ?J~FqcV,3m~^s{c\߯$ngrpzĤͤM>;KI M=N3iwMz ""RIԏբW5 X.`?pƤdc,-jD0DE7dhǐ7JPO U֐uyfֶ.LI'&ydbGee4M}ݖ|UQP 6d͔*/w$AEʑx8$3CgT0i\%c)&ЊaMJx!*#g34-AeK-V4d=T0푴㇮͡*/1Gʭ).Z aC[KF]+BCCVG5gv^Z_7k6)lM{X{jbbzڲ"aȍ|,{ߒCć8ز=>`Y FFu?') ZC$0 ax2,h(CrM&kY/D=&&HYK&kU!Hꏵ{hɄǵVHyBѴKFS/X)iU &)BBڕ@OÖ2S[TphYBP߶XE懜_ɽf`zcEmemVӜjqMZa<<H2e*FkЪ-, CD֤} Z4e;!1F4ruj)aXU09Z҃\_nʹ5*ZlV QU$F%z44e9F% q%jJ9֛AB(\2du[H<e;8/Ոc󒜺L>7owQuQuQuQuQuQuQuQuQuQuQuQuQuQuQuQuQuQuQuQuQuQuQuQuQuQuQuQuQuQuQuQuQuQuQuQP^ޘ7..........................................g'6֮u*uUyb$_1,:L9{uu, cguk'iX[ڭ/!2KZ%9E/[>p̞pD+,YLUXp2yRr8#ie`b\ΥR6V7k!RjZ}ܞ3b-?hjΚuGz_sZj|fsSc=sW*k~ePy}֎ghRK}e^%{ Wsy5#kƫ<$pZ.u/t|Np[ݻ<,iQfI/\UJcX fCr-*AQW=uHF>;u:D樏m6/cYZ?Hds?hCJ&ơv֔ ;{K#^4vfkt\B,ΒAR0ip)}פuV撮 ۤ9,+">6 G+^[ӛbd줹 'I[~ ?_Ӣ9F5(CfCR᪌ԕMB"IjpR/+W-dN͌TvZl>'j푒Y}H2wι6PMKof]&^z&P] &!ޙxD՗(UYlUCe"FMA)ܕj4 2C 3}nYZւUͺ6n+},ܵaVhʯM W|*;&+}D&LIӲ  BNNR-T0XY¸`eTkv#aԖ_3 n%!p/@KA!ZmBnB~8̗xx aY.!B$452"ᖵ8I#U,BK+ɚf*XIEo85ͥmni=f+K65JBi$X8 =77~5![vhXe Jo~sf w`ruмK"k$-Ii$s&NNga*ĉ86?dsbp8rCƁuP+p`k2hL2Q HBuxq]۰hcoE]E]E]E]E]E]E]E]E]E]E]E]E]E]E]E]E]E]E]E]E]E]E]o E-㢉$]]]]]]]]]]]]]]]]]]]]]]]]]]]1*5'X^{IQf䰻P51{4=:fM֒0#l$Xh/a9{ pW܇[n);Y_?bvm=[+k ;_~cNSi*4jM7콄ӣ{liU⮡hG'Jm̚6QݲbOQ&h}~b[['6m-k }1`sT*Ǹ\wR)qqt~z ג՗P7k;l'nkq\p/NZ+=O'Mݝݖ~ܴ38GnndYL) l]6lUir7nLFw2w* g^8@;VoZdaLbM)ּ$F E`uް+B n?46Ys'ϔ➫J̐}8Öt3b1-ԥ$x )0f]Z|V]vaLܫְ\vH[ΜiPݠM'mJF `tT͵}iͅ eYbKVZfl,;ň[Fru;V<6a$Y %LJ]0r(VwILW#-;VhW*L<\JlI$^rʵRcX֖Ejzh'wKd~6Yyvx!JnZ8{0ę~mX pZp+f`i-Ӭ]s/NbXRK'fCLXD}pڥ%.L<¸3 &S.v-^J?3$E14Τ{U"N(QC mc4zIրe1bEZN웼;iF5Bwq͉6֘G*`1JI7IzQuQuQuQuQuQuQuQuQuQuQuQuQuQuQ(?4h ]]]]]]]]]]]]]]]]]]_POcCZIJ{ʆ{<V! @e*4l}9϶V桹-S;VHQ| 6 ]eǓ)7 ! y='`'];&|uLj%$,r7K}̐MALƪq4xAj Fps:`B:4Z-ν&;%f1pBD'(c֘{X]Fy68L͛@PViUf zJiyָ@z_ @˥=ټq댬+H 3!5Moa9^9ޗ!:58ZvL͈rPzнJIp[Vˆ@3HٳnCi0hE4kM%?iapZ %n:bHjz܁p TH.d-~NLvϽB,*G1[~h&ꮥZ >\i w,zyhSl@NܦEKT)N+ gT7eV߮RP:ƸivtjA_ )?&dRt)ͫ-˦MG/#Z59?4\Pk 8B fڸ iyfbpz6#^|n&½_Q,#' -fuHP,قh?XĨ?1=*vтЀ&@jyP.bk}`i K ͪr؆A0jrbL:LV/^wu x^G q#`i,]Hp  KR?[S-ߺ2mEZlf%%W -cXP8u< !>HJ2dx"IbfeD rMg 9o؀,kBuMDҢcgV#C.rUKIǓFƘaIB 3mEMWhn 61DmY[{!tt8в5Uơ4\{}eא"30B˭R4ITۆ|U LGe)unw3'0QVb'#pt5l15ĝdl[ F++5\-C΅̣#&k#U,g`L"|tLBB^{bZ' gk4e ,7K8Uh=i+g#%B == ji݄#%Z>|ӁtZl];kLBRv}7R7sV`zyV4:4a4H]2JT4A RڠN'h#ׅ;у6TцAߘ(y?pvBd$ս23dnrhcOWdJ=hr%9h}wKjpm^UjWCbG8o........._JMi5͑o...........m<.a%_DREV.ժ} q.&zT&ՙuZқTfH4qآ 5LA L;R)}Cr-VS4Sukj\ pFk5δ&#j2L U;eSk\}n*7SC0U% ܁Y*=: uHOZut\A^ip"V]Ŕs<4 p ѳ n^ EķtʬV۱0V;:<<`!o ui>c'=zNݳ)hHuMke NjapZ3kT^Ӌ7kedW%M'B-:W!A)&AEܒT z^,';5Jnii ܢh7Ō(sWfs? 5ʓKujO\;kFX} Df~rbn+ ӈAŕY[1Nz!CWW\& N2RmA8+Tj[\I+8IQɫX@-=.$*ޡsAaR``XH>lve͙UL!Z0j֌ Fo0iW4shܮ2cognce9*<uaTX3S^$dKVm}F2QFϣ:sA2=cGu'jT'HcXnաqFb?{iƴq͌ft!+աڕִ;aiANdևLSjVdj1SRz:>.hr+ݺt Lax*y7M6U6r˸6s:Jf7 XN̎P,Uj))v)2/m1R̪UXφD"VFi65C2 yG3GnN 䮇BhpC%?$4e;P*<i>%gj$|VꎘEpT5fv_97FaUCy #zI(:Pzٽ-KaJ˰#F^G(S<1J2RHHPN#x,2\+ /d4gW>yV {\椡bh"h[a|^wKh_vwdSoUqSdZjYq"tȉ>[r9Ȓu8LIZ¥91amà'O{Mpzm]L=C\k<>H@< d]:շ/qDTtgbkrQkkίqCMǓ5(G&zӡm"jON8ߡ'D쬂40dTJ9VwHG*`d[xQuQuQuQuQuQPygN7_]]]]]] Z64);:\a-X&_Jme}0rihDVJv~Uܝl?sNMPig͖*5gڙ-Itr3\Pfr'4sL $/1j^&91L_ MŰVӤɆp"mM岯hVqaiӰp'h6!rJ5ҿ{Yu5BRYHWr~)eSړ)puRu]BҌR )=-inp{I!Y|A\SMgsuN_I#uWhpT jBi5f_GrM-kKddszZOBjeᨀpuRjq)l&[n9PFnݦ04Uo8)JmW۵V2%y|4)j]O{GOWNXB/iG{Ĺ6RpFMSٹe/`PJBl]I/oixejuѤ+.vCo\a2JJ HQy`pC*W?&'dJܲRDZGudwQ4YKޱ,IK G$%un=Qr',]ɵ$*ώ;d S*kpeh[+oxvRqɯKMIkNxw7j!1w-l…-c'hdg历4R\rMJ_z ҔCcݦ`Ji"A\]D.3oi$CÇ8L2ꮺf )niH|d#ʞR" Y"16c Ĺ84z0Q(7r@ ƚnJ! bbv>F*E.y(UBd$[cvjt mِL"c sP8\S2W+p4eW= Ռ9So$Hj%Nb؜yR8$:j<.6kڐ&2| ߥ&'k;$qb. 5{eV-` ,je)L6[P]!辎{J8V[.f9Ξ Cv=`uG4p~,fUب:{*Ǎ\3ݾߔZ\·IDa9BI'5jh .>m0bnE]E]EGGA qotAtAtA :q:"]ۄy s\JKdv;<8zj9+mE0DefŝJy4zXv lv)1PC;a1:>5V{im 72֜PW0.~˗{M1OC72/Yfk.5I"'%dt`` ”kE29#kGw;gFJ_孉%Z6aY"{Z>Zz8"\O1B_8r8N'<F]coR}b$_q|'+;[V<6횊;"ےbGiXs%L>ұ~5k{SjpD=;]^KFm]a@-ȶwuU5^:\ ۳ZKAkmM&ȫp0K0"JK떵"xWWx$;57ZmM'nh zMY.(ٴh: 4ii8VR.;ύCgWLâ_c6'˥UVף͓pSji"_Bt幐+\a-Qȕsl}ribgINl۟LVm<ڻ>c߽?{}Qo?n6ǭrwCp +#!˕-?}Dӕ+4}}XັOxܬ=YiEȜz}&C1rʹ)×V gΏ+}Hn&e%ϱw-ECя3Y}.`I,ԅ<.s9z'9SkKC=or3 ;0M x^ct2ڞ3o2pC>^ߔ/_zzp]O+{aN&Mj-XE_iFzd0ӳs}=<٨G_EN댊MJ /X0(f!j{& tI?i* d:)>߸&KLcLU=_W8Փ\9~jbI:M23oD9g?}YԔ[G2K/ $k .X|&UY:f42kz;B?q^%>-\֖{^9?s5ּ|~^Y==y^g k^qឯlwpJk~j?}/ek*?ٍp~8Ml* poIiyؔxaZ?AJI&.ψC _w(4Iez;\)/ UOs(;Z}IG+'!)eX~-Zݏvb]ף}FWґvW ewگW#n&;R~vf@7_#o-l2pL]p#Wįdia. >p+)a$;\qF~0?.Z2o^x֞?\n=*kw%5w;\k#vƄp$f )gZ*FyDR>Q% w7&u_~z쇻_~N< Np5Uv]ty֩nls{1?܁cjc!!xU͏w9F.,:;^rx9bβ4}8JswFKh"?ޓ2x;aEI)G\t~g pKf_~r/^h))ԓizbI[P;)L/Dd+MIōs;*_wnB'4o[M%MiVL8ޗ*3}hF*u썫%IuP}hf퍫E،,@iŝHU|lfܫiRPM -p5<_wk~~fqf8$Q~co\u?%)PXۊl:1ẇu[DgB%YE0 iM+ȾzLA~Hޏec3r ^o>,Zmp7a66_{669qΛmvr:8̋pW\ lL>dV9j/j՟{Q]%7f͕U?"KwVO/Nb^1}lfM2qzxKDx2zǙ} q;]{㊈.t;2p+op$ٴ^yC;FM+.If j7\".iY ]0s5%nJjr,໣p+G^jeEP蓨;nMN.O[kZ$fq"JkWi}Rac3㕓p?*˴iek>!f K@ п4Q66S6BϢM'ED{ll0J،E&ʎTiJX*jc3J9466e~Æ5Z;p7\64C2p!݆f*kXW n62Ca^RldF]R)'^pwXn#3,K }8ypv: UukVm#32{%E~cҶ ѽ]ը62#ܤϼ#-m#3t[ozN?f X;^J+Nҩ62==x3)aUmdj)S/d̔ XRi6LwYcc/"fV}n8ԫІmY2]^ir_*/ThQME5?9nhM&zM5'^㴃oK)c3bc3b _f6ȫ%RMRJuQG^'kF¡Q8IAt㴱|8\h%Ъ?!dZMb,4s{Yi6llP-Y~ntLic35^]llr$O7Sa}|a!:?\@،FZ.efUҏ2 +QS*#`zutOe(?/5~a,k= }^L-a 9ɥӟx '+T~£p?"Jo%J!?;J8~Tb#)4G%ƶ%vGp4mdFM$6J1F)?nAcqc{R/7!KVTL^ x'2b UȚ>TIWTc y62Si9ӊTcL,nU;:LctnxԗA666#.kzSO$N\vz oR:ps5W^K_֖2^nϽ*4m،p ?\zǐR66S'^ɐW%0N1GKo\ặdc3o{=DOfhג?NF?Y"NU:ܢz U:mlFU_],TX;E9xbc3ȫ.1,1,1W]6zbtA%3bϡlj1Ln}lM)}=9ެƦc̆E-yG]L^tTb,;Ж/"mTjc3\xiI7}^$Ull1ll" њF#: uf n?M7 Z64#ePѫhp3u͆fNՄ !;\YzhY7񝺍HoZz pO$påcL4z1ȌpFfZQB|L#k4 ~h-i^dUpf,?^*1 ?p2J 8t?:]ixjFKOhJ#h#3ք Er.E"%9~b4,Qq{b#3<叽ZcLk&9?Cqmd5іȽAϽcЌ`L%ܦu/\p_2rfHշTg~/ Xώ'v V;u1'aUllF ic30xq:s֩66fϽCjC01p N;Ao^њllFI۠zIbX66# ՠzIQ[Z ϼ;J.tv񡠋?:-_+bec3yR,F.\Z~c#/j1 vrypÕfN{_-4vբû.f0|lfQ}lf;-J1>6zX,*1pp˽ܴRF}lfޭedλ߿/H:U;'\` %bbȯ|-mYA۲ ߪu1D֚7:,{m@ygzm-\S55xLf m$ ťGfP Xa8xk<0Vfwq0EݯoQN (F:0VfFI=^̌ AZlE+3B=/Ȉ eQ#(01VfؙjҮ9`A-udjs!8ZmYoMBo?~L|9: G hefg`T=hef_5Zz(je&p8jef0i5>q$9< HLzgKWwZ r rcTqU5;_/5Ć+>7Fq6YGAk37BXZ}zQi cki:-]5PBa6=VZ GK3s1fD7zglyn= $NbfThi&Od4M;Fap1f$Wh`kii󶦖f|5fѵ4;t}V-ssDHVscAA>{(̉˗VZ? -SK3!Nּ6ڥhk:lKCl/lx_z,^0f6< YpF.K⥙ ]iq/l|S;fh[I6Ѻ˜^90|#==Vwx́P6"~+B0  ;{^Ѻd<\fM/q.h]&puYbxӋawƶKC+D32ҺLJMzœCI<)z!cx+p-L<^ Ql o{MD w<]o}K i3Wm^L<ȴffm/̋W|\ā_wf16XZ ۻ^Oخt:ƒ׌lZ]nѵ6#By\&Nff.z 426?K-jx pduVŏV_^đG-Y9\BCyИ9ׅ&E'y30r1|=!Պ8b<{#z{}i@ž/ _Hi4F5ŶҒ4dvT_<Ok9}.7$8tcK;Ny!άoDkÞ?t0;G)WDZ"g''٥v|H;1[Z*yձ1\ěeGѡ04f)Ճh/&%qVz~1>ˌpOhݾ0F+k)n;Yj2n$S0 mT P a#k@JS9q^uf# &auWJ^ۊgaVZ`xXD~r滞\kAXaE_{K,a5z#ym6Ҕox֑]vܜI׵.s4MTlukQ,aq{?JPeub\+&O&ׯ~u& [] k `G/ڷ mgZ\jvWxT! ʁgfRA`#!ɓ! P{-6>]Iy/EeQP+ amKrsާ~W :\),:IifLK[Gk>W@X%.ј ۝a)y~žtZ[w]'I!u7K6[xI4Or`]S!uw/Mt8CM(w$ l]hHNJIi`8YQ8^?nj`b/ZϵD5VN]Qo~8Hǵ5;[-av s_{awp2A!Nv7 YAS}}w>t޹z_BG72ɑzmOsk9 /&ӵ~)B,-^J:^ T/u=(ͯ\Ccܷ8ۦRʺWiC/ˋ^LבjXyIm;yp\c+ 'V݆(a{aQ4K%G,vOHXg=&O@$뻼ߓ5;@dW6ѪN3I@MO!5H|] WGBJָwor&.Ayh;nƎ@:0g k3+%avF +qGF]nWҼ =Wh ',gˬ_c R+O J{$lSϏK;$qw;!iQ:=і%b ]BJFe[C%l-K:64X  x݇yj=i҅@ǮsH^2&ʘ~6~WL2MwguN$f'~TMEGzQ11(NCyQK ZGݏt0n#NN;ܕbwp9MJsFhYcjĔnn0z5 {V&W LgZ0s&10=> ^Zw7鄡Q-;l]E5u[v,`]wcH:,;_{⛢Kjc=aL'Ҫ^)|Iۮ ,,;u>nU S[uxD(q V ^[AbϪ}I;^'BswIcw GƌVJ-H|aa{*Y4Yl2nU6Zz6U H$倅d1%y!NA-@ yR=9Z;a=۲ÚAw11XvXw11PjX73w13ЬjXsPn{$ i"xv|\brjXSV:15p'&!ُNNOv# T^]R{~7b17X/z-GLa|‰5ղ2hV5,n!(굕"{k#YV5 /Lf` DzCx%q6&b$ cb.ԓ0&T(ԻkB{k%l76ȫFJ^H56fU ݤȁMjV5,4lXch`Xհ I`Nj34%eDýjXy,ogז3KcnZT#6x[^r xXX*SӪ|_"FQk[K#a2TυV5dV<_V5,%Ǔ\(tvJaaZհKb#c`43-r=Ӫߴ @`,e*j.jCp4Y<>4T3U`f̣ߧ_\CY}s8EQ::u%|R;N}qCe!!~$}qqmIhz75?U'Js(cpzfOVQj@!1BuS;qDsU^/n°'~psij/n%rC/n3ܹv?tWd+-'8E^\.ams8nrV37_ܬC$~p'!/n8%u+89=&w1W>Vadly^Xx7z,݋b7 !_*][2C,P gaѧˍP .4ۈj{D}ܒN]39I_n.C]EºMg~zqey0*]^#@/BG%gi 3\Y&jyghw=Eu_E^ S-4īC;jih[v<u4NFoOJϹS_܄>n[?=`dFW:?I/~Ga׻䋣-}Wz%jwG |׫88{kt4|h;A4wGPp|q(%XpkڏWTޭ݌߅dpۋ/*FW1x!,dJ}#v;Q̾k꽸Ż^8Xzu+8TWf"eА~7|qQBm5p^6npK|d"A*M>[\[EG_o&syi_/v]\Gz6cZA~5.~>=sqh+U^\F.~"h?w \n ۋ܅p0imBswH|;MƨfJ)iť˅*phm\i*tCXm䋋<3K{_fMk34>һd*mݪp߅2_lGk3WD^Qhu_]k3fEk3λ) ە/7-/TQbgV < =m!^pRh86G;a=v#s*B~qk /fswuxq ML7J1]T)/|^'Tf kcln_Xw7[EZPfq} 7.cȋ#TLJ2E= y]ttD8taʇ~X4SFNdzP5fwp}r~y{2=ƴ2SfD^qiG]L>?^ߋw sZ)NL<300V{Lɋ {\-]4ML4"+򣕙!uǛ^J2?CVZ!Giv72SVJ#ie&pg=rw=^܎(nC& ё\Z!k^C+3~PVL7O(v^eNWf%_^eJf"2F_ख़Sp/a%{f `GWr|}NĂϦifd굙GN5̉ؖ[!v}0Sk3X=hNdZv{ULg &BTq7*4m-T~7*ꆫii&ڽ_)nnK!XѬ=1I~8%oD=nz+pkjwlZx$ćq-P]Zw6shޗ hi"uZU"Lm'3M*iic`{0߅C8hMK3ݝ8Z1˅.__`ػ/ ax+puJly߅yiS2~=Fׄ#p(k]exëJкL xǫOкL6Ï h]F6S;ax+p+G2Vudu{\E%^_/Du:'qXGɁ녉1.!x޵Ium]Zذozqq3]/M]t .7*"jz3+vWj]?녌] g-̠V6^ .x۫}a]12acx߫нd䳺Wf6t 6d zUL#&Bg!;ܼUO#xi&vu}a1UdAX/B+fH3,M|<:1fd奙ܼẃd{TC%ʻY6Wd oy5ZZ+k "n*x(.wTr"bDx5FY?#N_.o7# ltHQox5DQ/ &Ll@ʪ//&l(_ǂ$fŵb] yu48VZi">jLJ{0mcki&p>ZiC^/wk];[L0fc ZiL?Low1J3їP|DܱKNpKMowcia&pxcJ65߈׳~r{j]&p;I:jԺL<0e~WC;_/$uL~W]eǡ년 /f9| eTۣ ck]wWi]Ѻ e{+EXZ ܊*fbWK#{ {1YlGS;L4Lڬc01/& a`k_L oz5hsz#ZwÛ^mBŘZ ~.tFsǝ5Vf^)[8Nf;/CȣOL2f^mEEkDqp1fw"zL3-ʹg?]Fw^q~⥙8;BƘgz+B~˅4,՛^ i|v cxVybxϋg8Z+>;xϫ`ABM/͜P**GLƦN\ڽQ8[K3NEE]QLAx # cZ^4oxR~|0x^ lLGy+p˫@58ZA",%Z!fgim¥cU_.$^~jm>olhZXqqf:. <))whmkZ\3_/nc if|p0f⍊hiI BP/>[qŧJ2˸o$l; []}:p5G7w9" Lp~AN8^Ŕy)P};s}x5-2[ڌqr (53PlqGY}z a{BIEGɃ׍?"HV0刭Tc/c!QpZܘwY d0/SSO8Q[!GfsyjǴاz>ѳk*#O_f޻X ?A;A٧%je֤}!N-J]ݜp8N|8%w>r]OIq8/ >ȢEA4l.\ڰGތRyu$!c~>yrx^p+<= 5fڑE sǛ}\sJPwA!tPvOkkjaό1}.qm68" EҼՏ2c|1q>=Wٻ<9Cǡ ;1uuЍOk o:>{Н8nVr쪺E1?xkO{Gb4ƹUS [8eK*wvH)a03b./4!̤˙S_cR_[Hy5d"Rr3b47Xa(Q> W&K4!êxf5?0,cxB+je4b\[Bdbꡐά*-ՙ8?l3TeqO96 Ukx%\?zaw7z~A_ӇxRb N`eTDSސ,wo9ZcjZ9vp= sZ"i =® JF<wmU+/@k(o߇DB}]Ǭx 'l3]c {-Ts#mx yuOڱWtܱĮуRYq3 Z]s| q }$wx9+Xi꓏-|x#0^P4e^^S?| Z* `_ E#B fMZJ4AnZ+K[H إ!&^Z1~"N1ߔ)ӯ6 ~zMd~)!tJN]Z<륡efCm? /ŝK#ýWUFP7,X 5psKK4b0bֹo3aSYiH{K4a1!h0E9D0 v4kx&,bs'' ǪtpMZ`p}eՔ mTfО~PIX\ ;s6 i!-GKآyRHFu/ۉPwNi"[^3t݊uW1HA$hopOoXq5 e^[ g`gdIYq%Du"$R7X;D]';-SWgז>NE!Pz.2522drj}/4Ԛ|[8:;5Yڡ`zi6=ң>EHkJ*B>iE^H4F h[XK2^ZK˴^%,^4W&De`,KK+ϣg~=GB0^mw S@233yɆֲNۥ 'ǀ 8̶ VT }tLehKK! D`}e3ؼdX{dwXca(l0F}nBYzXVDOOà+a!}yRJeuvtV2,b3d"c{sc'OJqd{tfqحf*:Qf 5ZͰx8l,&hX,\M>Œ2RӊePYlFLz ț`z"R%Zz" ][O=\=:s,=U}HJkKmb=p&l]z*,[rX:kӊN%Qv-9t?ȣÁ-5-9RyܒCyt|D%mECH c]V4D蜻d=;wۄE4h%uz.tGa~%y=JzB;"ĕv.$mM+{I tJX:}ٹЗkWf @UN5"(KK8kɁi%g3aq˖D1|IX*f1:Pda'l4|,丵v&.,HxxKSi9#8"1v" 4ܔ^sFBt䚌 EvV2|3H]t|F:[0pCi`fX,g3h ɍӶC,i t@tPYD6Xm 4+D!VàQqV2DST.EbZɐ)ڳ%9o^ZMxe2/(E=٘k;1ʼ](a y KwKqCfDuJ?hH{P@QcEC x]ص1ՋY˜hz  )kpÎߥYzGP39W> Ƽ@!'Q,B54o Ƽf`hoMF. >:d|T^Xmz9CR[q}*TwT'SVz{HS,sQ=/}aY N D{T;S= n1 tpGu:U\}H:숮Tۣշ;L8DqGuCNwGuoϫ8kQtU`r,b* lXn0C{3͌^v_d5wTbqK$3q'>T8 {^6%t_.x_L^pG;.hNH;#^}K i^^GQ%PьV)ꑹv\"bNsq#A+ȗ7DOOG2%bЁۨhz#Xս% 9=szz`rxNr0Sd,Wrw"Ss'Q^#Vqw0jPvOՓ*RmO8IV$VGV5UT"9Cz2Ga= \.1//p_>q[b/ {?{3)a%Ѹ:҃qO`IE6T$%|4w=2{? qO㸧(Fj1A^ˈm'n1m92|&7Ftn11t/0|Raܬi qW -*.Å̡10=%5Wt'6-ՙ{-՟[L[PBS.Yz|nG}R/+n}ڢ B5z([y]R@}nD=]k3~v}aD8B踥:(xrG_k3Ruvzg LAzn.un"kB΁6[[AE%Ec*Xuz"tKȈtc].z}?v/{#m]rerog'j|.N"ˠQ{-.0|}*pqOqOu(rq{zz55A({oUgz|j]t-je Ma6 YWnQPM)h]&*D5+JlL2ȒD =GhlZ)ؐ6Jv^WuOT5N%^S=^NV׻K7؁}smLSS([rcblg;՝S*K3q4szvg5dB:xˋɼᩂW9yB;&!i*;^S[L˽Uꕱ Кn@~鮢;[W}곩 ϝ ޗ1'~W-T,8aO-+ // ]ƽDLEz:;pV:_nB]Tw;RV5Ū54SOǘĠj(z}#W-ny *w憱/@kjhOT"W?-Q`Yk/.B9GG^[H>}ʶVf";^sٴ23&hC<_D7[^ tYT}l^LMU!'{^oމG= F]^ď2Vfꀋqyq \D8M+3wWmi_/_zŧfwv[8-rTɆOqA]A z& \$:yd#CrE >|++"^?b hSª6\_?Ï,\EIҏnY{ggA¤:>eư7xj^c.ۆ{ݓgWF 県gln?A@?fŀ_qRFuowrnj wD KDɸʋnrQw:]xepݽ8[>9a`dan>35q(dC!2i^xȊ˹89n_Q{&IǦI]q.}:A8I3t>+/Ɖl޽^X\݄2ahkV7J feLg26/} xE\Rt,4 CbQIX0븡˭֜TZ⩾-&U zu$}ې1hINDEG|~族ҷh"+ǐTo!>>OvP*x'N<3PxN}9csJ,?bē& Je"LUrj )F| n~LWmr =fLf|[cMF7I\p|Kԟ9B'~Naݩaп&`:I'u,>(07HJw ,=gY2g XloL<z~zG̾τ==GV4\zmƄz'0&wƌTMO{•0|3 ;+٫v/j+evM`A+!JWY2MuƯʪ}aׇ*wXpO%ĥ?w};abBn-rX5 ab@(jrdya^[Y^}0 `r]()6h#ק{}+8o7vm{8L]Ces>B1V4=vQn!%[R]4&*#+qnXd,o/q5ԑy؞-^hGv |'=K)BOƖs5A!BFfYzmIY@D+szhtQLww˞[w(.36+"{6bm`p4"+fQO˦~fʤjCHg A6tFAshdsyԷn kQ:2ia{o.&A=F/& <)mYϔR VqPb2XCUSK%qAk#^g:*g{dZQ?ӑZьWXmdI>Cuݹ 0,S@c-/#y1U2>ϿlRw`zk&)qM@Tk{2kC+Zf{)Z{m3Ŗz"k#/z.uyD+;6L!FHB]q;5_ &Zsl,Dzmakb4x|B`xu-?眐gvfVV'Zӱ~٤^Z:[X`B/jtZz ?h;ۜZ=B"3،"=@z$<ظko}n=h3{uLi¥T{Hb秗}ujdP!o6۫u?Voռ.2+pxZԼnG;歵M쏮v e^Z6t1lY4 }p捝^[lg&;Rnڵ㖨Cjh:f=`t_: _ckØԠ 0v 8F¯& w=0kcjc=1Vӧ:۲CkCaO/XkP b6r(,2!L pdUCӷ7h!rLAN?bs7! l3`e!s0XUt khZՐX iUC:;8,,qs=bZR]X~/| ams#Ek X(8˪\pEڈ 9j-Vuj= X{I`aVq x E3\1Ώk?}p36p'nF lo`ei9Y^g d~[1|n^:]c22cC6u\1C>ۑqaj: S{vԎ?֊9wjuG!|3ͻ}(ӻÈp'I.T,.xa\1xԘ4V\c3n:;N,!z7L 4m !?ƎۘsjfO SQ憩aV,ռ۪ 7cT?0njd6woc<.0`\1v̹K%^\TBw9 #"7к"@I=SLzq|_ c⑹CvWIva]؃?B~\%RѪ?2\u]}&/ M!\.]cٗT,v]ArNyaAY]9mHA]S]laC/Μا?rG(ˈ#rw:N_t`$c:g .,Ip^xsc*C+墄,S/wv*C܈ q_ܢq=e|n~eE&L!~*.m6KGsijWܙ?MWK z͵:+{~KVU\ d*~.EVvt垩0ƒ} ~p*uF32~S+ųՌNdyc9h)0k枩) =8ʌh LM6ósqW=a[k}0<p]wxFpiJpw upi觸ы7^H|{ٝ]-|3.{ODz<4(^e0g*3uПW nLt >|>jF.zJDx lX};a|o}G=)}P)[_C=$4,{JWW|pT;_,宽Z7RzEmG$tb]D0vUU令鋋Xƪy oSq)ŜV軷* ;}MYyqaIS"xurxx&-~}Ɨ{gΟ0Rԣ?\?/Xbt:C].67 "GB ћyn8jl ;I?36#Шwaӟ] )7p?aH_?i8 Ic־(8#bJ ~-GqCglI#b? C9Fy:HGtf0 ;ja_(]G/qX$tD_:qG#,dJS#{^oLޥQ+'G F #EK+^m Xa@hI6@U4Ҙ.V'u7h$sPqJ}{:a_J5:=* 4>I;,m tLlFARΪʰ7שYi MbJe8 =fǾMI>TI_y `bߛsόT`fmSfz}hA|*D# k'E5q}0aqntݡ#:9Z[gϘc=(?QźvX%iu*X7f'/3bvBJuZHUQyL`{iY( Au<+d!TP_CyuI{6MkvM ݚ^URsQq莒6 TF ]7% loa = ݣUf/ 푀`,}7aTU+̍b և TVsA^'&;\|Tlރ@=P,Gl*Q'Uv?bVa̳67?(*NgT<<=p(6UjcK.d:[n?"S{'QC[Gx4sw}\0i3M5DƤjʝ#QPw7^YlwBFjKlм .B8ۍ nୗv?D^LX6Tr NWhWhqa`y4yWfL F۾rҧBYD{;۝ϓ5N[so&l۩6>qY83NsUTL{89xj kرTybէ{rҘ^JRWfqSWgq+LBv7 uu [p4cnϧE]s!np뷛ReF \CvpГPc7Pn7|GCCU"FCGݐ kHn0g#{!q-p]s̑ 6tzC[Q.#BFCݰ~ *kK1gh'Bh^.ьWtk3hO/ ݅^C%OApMNK4oh5v{Z|S"w? V'"zmeya>oh Y~nUPHDR-˻*\;g6) T5vˊ˺k&,t\9Y !WӆȤDsZM(!NtiZʠ BHPhRhrU,V0kG[e5vKoˮ G}ݞj6SdyGFj.$Ml.$2'~& ѕ]^0]/|<,}o$ǑNB_I%t!2]7N tv,qvW뼦 ݣE/wl~}qU~K|CNd^.BkۇPkz~]2G9? 6:YoE~;ؖ(.b{q]}2ŭ ?8kCsgn[in4N8j{xtK]96e-:E \51]7N+,|@{d]A\G"F|VH%~#%~{<ir2e`:k,'Jz`5>$0KCk= 6\wȬK&ˌh]r3Y}G$YqQ@T1Czd繏Bjd|> ŵK8ZǙ~pЎ α" A9õp\2J3"fw"מΐZHז L\wĺ8C\w"+#MWn.uߕ.;=ZbVl.a~edd\_.a0mdWiEZ"ۜܞ|_.JVNe\En|L>\?R7d`nG {tqd ʺx)]8.ZEqH6DV 9\k>ef}FMU Xez߂⒲ν\3ߒ?_4}d:ǥf0#os7r}X8t>Տ8e+R)A;bȊLoJi?DMz_Ji? {W~Wlb`#$XAՃ!|B]Jϖz Q#!$>_b {}P&Y4n{zG~ sZ'[K^t9H&~XH<8268] C+;v)1gNk(nU@`C ;Gš=?FS>G8T{Gh)v'W8$ݙNn~J0KǭAvS烒<<1'_4(?{ Zax)ENth&/stwcX>MYroݕߋN)e(:C窥 "LC'F'h4DĞ{ZvJ^NXKؓGĄ M&rʅ~!1w>f~?N{ܺi {oƋ6Ӝ"T21ip=mbXL.;n60wL((Ⱥh ߵR#sMo1 WO-#HsA7V^r1τ,bvǡVm㟥n#yӺKNY*U:jiıϬb2xջ #qw4Q̯#ӏnG&? U#kK/2ih3Wڛ?6^BğH |T'ICRះƱcr/|P?*>jGYg܍x~ Bx]f]wZ]7EvwPxe;L ՙL7d(B]wFHgLD.>NJHCJhiHY緻 n)&X/Hj+3>Uk#rOt"hxx5X}Н> Qݙ-T͆qT|?Aw3e.ڈokM֘*5"ɛ30:UQ53kb\5t^U|ݙTk\\޶jm\Vl/whz.P6dך -BUk 3"Qjm@={VZq("Q Ⱥ'BFB20ʕwU`G#"Q2ʩV:e6?X[,;ݙ#,"-2ywL؎ݾi'"\v ƈAυxk^xv⮹LYb0偷 ]F3gܔDslcg`WF\cFl#[93HK0 Az> [i`~P"~>CϭO1JmfD!4CHkCCe|Qb58j;í>"WI9PSTT>> &Pd9|PV3*ԇbc)uEwf/CgРbm6ԇPq P>חԇS ԇf>6'NvE|Q:]}wA}cd=|e¶m ?ĄN ?U ?ZPw|B*n[|9aCLC?C&qCC#^T1QjDf5?E&4C3CʟA~h*Cm=x~B'}~diׂA~hr!TS!rA~b ?^4;Y͊A~h1A~(=>bK.Nbˍ#Σ++ğš}+oxX'`??~HVwhrJpzx]H\im؃<6KW@mܞ2:P6rJL"0:ďN s-!dyou[χ"E2ٶoV},ی'MlQ8>@_蚑 ?w៏w7SĈm>/v31yF?(B͋$~4]mޛ?sch <[.Nfl7-*PaHlfzS |!nKşКS>zF'3=TЧu~RE V= ~1b }n/qi_Q}Rei!|!]-΂vi),>N|an/ޖ& MX@;)gM5[Pk/qzkHb=ƈ/t#n̶%6ӻaP"oB {q6?'|˥Ƚ?XF7 fHpRzh݃ Q'({"\my :GK"SeضuR ?(/nVmg/Rwo,be:10BIGR-TH0)'kU ɭ}K^KlE: >knhhW5uM t߂4%3%ݱvۼgZtο#AutD77|WSNigo^-JW(:㔰YQ_FET,qq2NΦw`[ S.n8vJ0m7>fg=w=5wrpEL9]CS`Ak{ e.vWOX3͙C Ur0jq H8:>xgа77*oqUx~^q=N)kȓ q |06}&FE3Z2e\(b~BG0nX͍.ZcVX|:aqi&89˄+"@´LJ$WwRza~VJ̪߬S,UeP3]V_O*w_7H]ސږhO̶GշcलK=bqk?4}.%}+a 'psd>m7}jK%gVh)(RC5Ռr|ĵ4q{y2n5AmiBVc 7N`{ e(nZ?}΂ɽJ~W6ܜXz"q%9IBǞ8,z'\QM^z[{> t*QؽC6G}П8hbe|P?-^+q89Dwo9+NJ;a={ϻxZjX-gFOo@q=b/8fCohջU;.Ibwn?5L?w_8 5TG|/^}F0r)T6|8H gῑ ؠc~_h(ȰVF'+2»7N}2$\{r;?pP ^cezW;(TiJ\͓[5a懱{7ru )]L_1>Xƴ{%{| i| ~֠}Q07ADLi!j^y5F|{7Rq`_<=>xLS'2=sI? _d6HP(د +<[ pF]Ȟ J*`e4~pYW6B**|~tDnȇ Ә~-zF]gғWqR,oVpbPxuLg?) $cB.91$qt;ۯu___O_(;/%~) S/o~KKnP"/7%# uO99/2\'"#Uɭ5 C? .WWK%$f߫úگrP{X%0?|0GTKeh+W; uOh6+_7o2?-_Olo_>'qsgw'~7q˛Ȟw׈f=* ^Ư>o3yG>Nӿe;zJ׮OWǿ/楉Gr A>ߞ㿋3ށr{?߿_t_~?++/IV_ۿ endstream endobj 11 0 obj 79364 endobj 19 0 obj << /Filter /FlateDecode /Length 304 >> stream x=;0 C{Ȍd'>2VI(/u< i& b;w؞D/)ϡ+E:Ū0[M*K õ}74uK hY pu;Gw5<TQ!OJ|<(!\{0FS@\^BAjI'> stream x5Qq0 54sۿ @;a@dJ\UGM>`!S֖{&UF!}W2j]* UYFp&I8d Rӿccz endstream endobj 21 0 obj << /Filter /FlateDecode /Length 68 >> stream x336S0P0 F )\@>,́,# .C c0mbl`fbdY 1 r endstream endobj 22 0 obj << /Filter /FlateDecode /Length 74 >> stream x= 0 "#4M2i >d(]iBIO[_mվ*K{ j26|w wN endstream endobj 23 0 obj << /Filter /FlateDecode /Length 133 >> stream xMA0~tzJ-6팀03 endstream endobj 24 0 obj << /Filter /FlateDecode /Length 163 >> stream xEu1 CsUx:?G i@xx= r]Ņ ?޶42܍e@N"WI3Tb\/:"̒@#|:C[ۙ~:!**na.@RԏQꚡ*+kjڿ"}\Ne{g+W}: endstream endobj 25 0 obj << /Filter /FlateDecode /Length 317 >> stream x5RKrC1ۿSpΘ}tj'+-@B./YK~%ۥW%B>R-G- Q=2'":xa>N)x_xN;2$KMH=I+4t~&+s{rj X+)$=Hr7VސWg%&&MܕBXtLX㰄*aՃM5fcdxLP} #GMv²[6!D3,($Nc$ Ұ9 9e, mh%zМaמE[{ endstream endobj 26 0 obj << /Filter /FlateDecode /Length 45 >> stream x32P0P4& f )\V.L,іp "} endstream endobj 27 0 obj << /Filter /FlateDecode /Length 338 >> stream x5R9@ } ] v͜~߆_ CVie!U-.Im W%ڥ Pt,6˯JH+kLwIi"Eo7o}=@.^ AS(i|Ъc(ew 4<3}(~_K&(? _osџa`Ś}@*z`yT endstream endobj 28 0 obj << /Filter /FlateDecode /Length 90 >> stream xMA "OPDtz_NE5jK02kP)U0\ 2IL{qIqzz"X endstream endobj 29 0 obj << /Filter /FlateDecode /Length 236 >> stream xMPKnD! s\I$!CU۱T*HUSbuM2yOQ nAb$<4#',;Ofč.`5[́9m:7@dP "B.ї9`Jw&\>աqZ coYaLq_֨ɲY I!}{ y+ՠ0u J*}$]S endstream endobj 30 0 obj << /Filter /FlateDecode /Length 210 >> stream x5P C1g dVukm;aBXȔy)K>:L." u%ʚ +`p&^7`i5tႦ.B%|u{OxjrvC` jMX> stream x36P0P07W544U022P042QH12443s`9`a$r`Zs: P9\iM8 endstream endobj 32 0 obj << /Filter /FlateDecode /Length 248 >> stream x-Q9AzBsˑ C :-qPO+Uwu9HTM]vf5,?c 7zqxLu5{kOfP2+qSușO \ ȹeƌ#M!RH&3AQ~#aU#j \Ks4;<9GW +ET<pC7ҹ^s0XM7/=[ endstream endobj 33 0 obj << /Filter /FlateDecode /Length 52 >> stream x350P0PеT526U05 LR \$S as*sT endstream endobj 34 0 obj << /Filter /FlateDecode /Length 72 >> stream x50{6X`KEoC|N/Z #pu#])Ec΂q_H1F=#O_p} endstream endobj 35 0 obj << /Filter /FlateDecode /Length 247 >> stream xMQmD1 \ky R]oC /)%K [UC?13,=?TPbht/"+ߏe s`&4`oI&ռ3d‰ATwM,3V7: lx%D`r Z`Q+ tĺv7C/਺x} K{,|BL;wI#fR:=b}@e+ (\* endstream endobj 36 0 obj << /Filter /FlateDecode /Length 49 >> stream x36P0P040F@B!H Y@8&+ & endstream endobj 37 0 obj << /Filter /FlateDecode /Length 332 >> stream x-R9$1 ~`LtIUls#h/#xE=f۴[iGiK,W ;BjW0wy.2meDkag؏]e8*Jl !2J'Qw\I2[E™w2;yNE{ kF9+%|6vzrYɩHHӺ NKؖߗ3| endstream endobj 38 0 obj << /Filter /FlateDecode /Length 80 >> stream xE 0D{`~&f( JpO{:2Sa ,S`5FR죰n_uzS*Ovvq= endstream endobj 39 0 obj << /Filter /FlateDecode /Length 392 >> stream x=RKn1)@Mr[T /1 %?ꒈ3L~r]Qljg!.6Xr_rњbO/ȴTXVݣC(-װr{d`Jn@CHYAaPl( WԬtb ) ٠[]aP[[xfޑ3qYk?=Q2QMg|2RCgB'`$Ip#A 1qOl)V;ޒ{,\L'ib?lK\+E(~Aq|XdDw#h% 0xyDhDԎ=(ͱ&{ǫvzcw. endstream endobj 17 0 obj << /FontDescriptor 16 0 R /Name /BitstreamVeraSans-Roman /FontMatrix [ 0.001 0 0 0.001 0 0 ] /BaseFont /BitstreamVeraSans-Roman /Widths 15 0 R /Subtype /Type3 /CharProcs 18 0 R /Type /Font /FirstChar 0 /FontBBox [ -184 -236 1288 929 ] /Encoding << /Differences [ 44 /comma 46 /period 48 /zero /one /two /three /four /five /six /seven /eight /nine 95 /underscore 97 /a 102 /f 104 /h 108 /l 112 /p 115 /s 118 /v 122 /z ] /Type /Encoding >> /LastChar 255 >> endobj 16 0 obj << /Descent -236 /FontBBox [ -184 -236 1288 929 ] /StemV 0 /Flags 32 /XHeight 547 /Type /FontDescriptor /FontName /BitstreamVeraSans-Roman /MaxWidth 1342 /CapHeight 730 /ItalicAngle 0 /Ascent 929 >> endobj 15 0 obj [ 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 318 401 460 838 636 950 780 275 390 390 500 838 318 361 318 337 636 636 636 636 636 636 636 636 636 636 337 337 838 838 838 531 1000 684 686 698 770 632 575 775 752 295 295 656 557 863 748 787 603 787 695 635 611 732 684 989 685 611 685 390 337 390 838 500 500 613 635 550 635 615 352 635 634 278 278 579 278 974 634 612 635 635 411 521 392 634 592 818 592 592 525 636 337 636 838 600 636 600 318 636 518 1000 500 500 500 1342 635 400 1070 600 685 600 600 318 318 518 518 590 500 1000 500 1000 521 400 1023 600 525 611 636 401 636 636 636 636 337 500 500 1000 471 612 838 361 1000 500 500 838 401 401 500 636 636 318 500 401 471 612 969 969 969 531 684 684 684 684 684 684 974 698 632 632 632 632 295 295 295 295 775 748 787 787 787 787 787 838 787 732 732 732 732 611 605 630 613 613 613 613 613 613 982 550 615 615 615 615 278 278 278 278 612 634 612 612 612 612 612 838 612 634 634 634 634 592 635 592 ] endobj 18 0 obj << /a 19 0 R /seven 21 0 R /one 38 0 R /f 23 0 R /period 36 0 R /h 24 0 R /six 25 0 R /l 26 0 R /three 27 0 R /four 28 0 R /p 29 0 R /zero 30 0 R /nine 20 0 R /two 32 0 R /underscore 33 0 R /v 34 0 R /five 35 0 R /comma 31 0 R /s 37 0 R /z 22 0 R /eight 39 0 R >> endobj 4 0 obj << /F1 17 0 R >> endobj 5 0 obj << >> endobj 6 0 obj << >> endobj 7 0 obj << /M2 14 0 R /M0 12 0 R /M1 13 0 R >> endobj 14 0 obj << /Filter /FlateDecode /Subtype /Form /Length 112 /Type /XObject /BBox [ -3.1031695489 -2.6770509831 3.1031695489 3.25 ] >> stream x}11{hm.kV$d.ۧG3 endstream endobj 12 0 obj << /Filter /FlateDecode /Subtype /Form /Length 60 /Type /XObject /BBox [ -3.25 -2.8480762114 3.25 2.8480762114 ] >> stream x53U030073244QU5V0PQI(`3V0 j2 endstream endobj 13 0 obj << /Filter /FlateDecode /Subtype /Form /Length 29 /Type /XObject /BBox [ -3.25 -3.25 3.25 3.25 ] >> stream x5V0PU9@k 9\\N>` endstream endobj 3 0 obj << /Count 1 /Kids [ 10 0 R ] /Type /Pages >> endobj xref 0 40 0000000000 65535 f 0000000016 00000 n 0000000065 00000 n 0000088063 00000 n 0000087291 00000 n 0000087323 00000 n 0000087344 00000 n 0000087365 00000 n 0000000216 00000 n 0000000451 00000 n 0000000344 00000 n 0000079890 00000 n 0000087690 00000 n 0000087900 00000 n 0000087419 00000 n 0000085958 00000 n 0000085743 00000 n 0000085272 00000 n 0000087011 00000 n 0000079912 00000 n 0000080289 00000 n 0000080682 00000 n 0000080822 00000 n 0000080968 00000 n 0000081174 00000 n 0000081410 00000 n 0000081800 00000 n 0000081917 00000 n 0000082328 00000 n 0000082490 00000 n 0000082799 00000 n 0000083082 00000 n 0000083220 00000 n 0000083541 00000 n 0000083665 00000 n 0000083809 00000 n 0000084129 00000 n 0000084250 00000 n 0000084655 00000 n 0000084807 00000 n trailer << /Info 2 0 R /Root 1 0 R /Size 40 >> startxref 88123 %%EOF trunk-2018.02b/doc/sphinx/fig/simple-scene-plot-3.png000066400000000000000000002221721324306050200222250ustar00rootroot00000000000000PNG  IHDRXLExEsRGB pHYsP|tIME11@tEXtCommentCreated with GIMPW IDATxy|T߳$0 &PATR*m՟R(Z*"" EAP$"("b:VĄR# @$@:IfNfȾM&y^7d9{jΝVAA6C-@AA,AAqAAAAAAKAA,AAA,AAqAAAA.Ak.Ξ=Ncذa̘1z)++_f׮]h46mF& Ȇ 0 dggj꫙1cnKJKKcҤI 8b^~e,Xի=楗^"==_~r}Y|}}:u0AA@CBB{h4~zq>:%TرcL>-[Pkhd$&&r5_zjlقJU& Ι3g:u*׿ q+_C4XFVoO8JbРAmh4)+JA<ė_~I߾}k9WKh]}A***Xv-Gۻ1zOknnXW"[ p`Z)++#$$xg0Lٓ˗ڗlfɒ%jxf[󔛛˝w)+QA.C>#wn[o̜9sXf :žDv, ˖-###^z DQQy9y Jq(* Vb ˋ/{シr!] 1ӧHѫW/z;v,{!66žDuV+˗/'55+WcX´#G@޽F@@UW P*rWBpP=9D{{bb"}jG^^ .0ydRSSz뭬^SNO?o3vz/tVU-z: U=.C1^t[ )xw8v.\ĉ,]ooo&D[֭[QT>.]гgO,Y"}y;HFǻ幇JJJk@Ӻ?Cl۶d6l@ll,Fiܻ_ڵk),,o&;;iɓ';w. ./_~y睵ƵsΕ끈/ i>رc w~yp=4zz r7s=P\\lRHKKg 4'b0,ӟW_}ɓ?>},(7C+N>ϣ99mχri]veذaǏ^`„ >>M:n͚5dժUk׮,_˗s96nȜ9sh4Kq.\u*c*`:<-vG8>Z+Wٜ'M/k hV:⡇/ٳg3~x:dsICVTH0c_||T*[ʱ/8F4V͟[Hbb"~! jyzIBBtڷ}vtff&&?:mVPP _>h>:_(U[swS\\ٰ͛a&Lhr?wɓ'Z,>c~_;ECc>sjkϞǹ裏r=6$44)\qqqq <Vˮ]8t>V3fqYp!5N:-'N끈/w^yOPcL> n&8~8>zӸ=zpwh"ukƏ?ޤ#r[^tI>qVuֱn:}SLnaÆ~z.\HUUcɒ%}o7+;mwҀ:ooVMSn]qNG~~>mrBC2|vؗ L6S__N((U-EыG)GAgĈfvժL2;v/ɼL™3gعs}۷~ٳ矩СC\{`]NV[_+ű €w{S_®"5? $(e9_r{ hv:mݖ{d`0i/7X=@ `~iӝ3*FjZ&#sutΊ_ѡ믿.  BhҎX,#F0l0|}}_F\GY9Gٵ}[>a֍[dž-:x`Y]p~-jԩS=￟0&..ԚZ͂ Xt)}Ϗ8=Z#c0&++믿kגի駟;vl`̘1#%%^{c׸m!^}_ʯ v??Gsr$`_9cE%wA:/|)۷[ظ3nyޮ]2l0Ǐ /0a„&im_W&Ol6jԨZ, [nkcbbׯ,[>.77Tu]Ghh(_~%SLi***7ްrM7_e:1kBUUÇw>|Hrn쭬Ý'"]}!NΣR@d`$`l)Q+{GG v'9~LYx8^z9JŢE ǧIǭYSj*Jnݘ9s&Nw^g4(//i[.]jj:nє̞=UV1}tw^@yy,& )V)ٹ_ 5iB/9nTɱ pF `?.x YؽsRk(-=d `k۷VP@F߂t*22j[Z7^&% f̘ٽ{7M>k׮,_˗s96nȜ9sh4Kq.\u  i4E2o<8\ Y@"X]K-jOM;Owt=&Ԩ4d38{xVįOG-"X tN^ycv7F{}c}}^_v>o׮{x問[=Dy>C ٓ̏?o픕gff{nm 4Ӷk׶|DZATT f3Áo V+**S|ESWz=Cjm?|1 -FװwGt֍{+ǣ>=C`` )))}۷;rH-[FPP}嫯bM> V`{M,55,URK_5o3:bz`H3`H?-DOؽS[nMkSp8qʺu馛^:Iu1l0֯_رcgƍ,Yi=í}ǟgoٳJ3RQCqo?رcIMM嫯jPܹS%%%3Bz}&jbUU 6MV&px͜+>ǹbۓ$C†0~x{ymǷ1_({眚ʀ/F>tQh"Y/ߺuk|,է^Nc [MBN-=W~o,V[Un]΀hڀg8RǧL!) l5ۜF]`H3`8i 8 s[G?~SRRm"v܁~qz[Ω{}X,mo~W~JlÉibKR&t9ndݔV0fpM5Spzu>|h=l۶ S}%H'&&}aXn]M*~>N!/_i<"6m,<D.3d18X }!@ U@9鯠&E4|A(c/ܲ7 ׇ۷ +W  a,\آXJoB&t7թe1VJ)6^NbJb;w,:D.Bg@Rm&LzcM ݵ7:{ [&i|/m#!xbwA&99?~RkgNbOt:nO)jxEE}4Áo{~ooJ}}4LsXX{ bwh4EeڵgϢ6l3f ((cfϞ͑#Ώ͘1?`С[o5|/-Ge}bMBiiiL4R\\/̂ XzuǨT*&L]w[8tPc} =S)pGU<#%5X䁈j TVVJiFVh?O?% [oI&9=Exb2ذafȆ  Vʱ3 6mryO> 6zh|*&N(.].` Ȭ]ѣG7f9r$-bժUO>_oO5j'Nk9(*U7!!!>YXx#G1i$' IDAT5jӘQF}vzI&9y'VO~t?:t~7x#}]ETY09)`Hcgkx/2 m9?GAŋ+(('d~2` !!qua6Yd jxA">>k+n3gg{oFΝ;]8t >Epz=ϟgʔ)k\yNcJJJ3f n>@ 4tphqb6,Uh6^#Vo7vg(//oTވg`4 `֭tžbtRN:K/{NLppD޽;>>>$%%ѭ[7i?x? 4@1robyj|rRSSy\We\`Fff&`{2--2.\ɓIMM/IKK#;;O? 66).L84a]<>eJOTnPȺ?s2 K,oD.vWjw`mٶتvؗ ||TTue _q)V_(Gnܺ&clY~Av> VgA=#W7jgTU/u38rܥ܅ v  밼W]_5Y700P"v Bg@+}Bs4"YJ`Dv)].` ,\ wخƺl>JITèQֿRZ?o# ۙX|qr v3IWwΖ恈 *`4k14 e#8CSWrJY\].I!cǒG MNz.+p"`5U& ܕ#GP"|dqy bw t$f/]JWlѫ ^;%X9G;-\p_h|((/ B=H(v]oز%P3jg`Zİ Pr~QQip|0RQ9f=Xl;_/,,D.vq; ׭VA)8 a+(, VmVKggz1E偈^.dJ/PB+dgu`qb6lx m>aC?p|Bz "Nz\[\ӅĘ  {7ȥ&MVt;8Y*<J*d3Ř  {0qDtW>//`̙m3Bd`d *7Iqeqk-[& 3 r:FaآW w_~٦?K_)+HhPbz4\J3g-j_8&mر<]:Iu|6yz=fky[[ s,N^LQeQ}b@bwAKVõ:BZ}6{^whAq6%**FܕW^jV |4>XM_GOii6s|TT_Һ)))<]EpjK9$pԏa֍[)"ĔD,X$r_= u~SM>|X"v Bg@D#e6+z{(PF:,%zURYb֚"r+_|:^1}"9%91}YZl`ڴi<]: \R3̶gu-*Pgؗ$rJrv %.2O2 8XBBD,UhV5 Ę^1EQn.l/EғؗO  v.U>+Ee8i 1%F˫li-sΕE䁈 V ֹsPuX@ړ"LfSSf͒E䁈VҞ:,%=8X6K*7yRK\T:E&A,ASuiA+((nt\|t *,:=.2UiB 3 tXz=e?0N0M?n~ꬉ5[l#XL1.^]0M$'Ңy@bwAKhVm;,_@Mլs*|UmXZysD],`>z"#ؒ"#[8q, D.v΀;Euv<|*20MW`Y٨3"~+W>8Sp*ZX DFn`lw/eKΥ MmF.mZ5Khg%EP`*D-5y.,kt< ]:r*RQ}߷ʑW 犀+ `v޸QŠ8rr-TZ͓え0*vnsG 1 c~^~m:W%*ݯ;)v 9S  M$lMKrb~FdY<].` M"**.@0cPlڴOER{iVrWQZUڪH33 )B7ARVUӛtCcI/ï]sn"1F|_¸~ @D`D[ 8XBVQhPߤslm^+!9fU-`@bwA ME ax~@rr2 C_Tڼp=1%ƙj@|&b se΅ -s='3 )B7 JW?NGfbžh29?p,=W]` l;2> TZ*  ~taԬQ^U'\y264](@7n g,)=4' VzA:g PZY' A1df7~VWEcU+,QglE "#7`]dL:8tZ[ȸFj]믿. A,^0p9F_-fzlWʑ+x`Y0].IJl,Gc]ZpT_e3OUYMէ+ջ߱)tRzKnYRgO',h7nA"v 8XBЫT$ZxQ[)5RTrWƙL>2.W e?Ϳ@ 8XB;:Xz8.ٮSAkUkRP]škjSR&>$B|C,rOMMe`< ]:.`%''1c~bipǙ={6#Gd̙3syQ&*j 9e5r[Pd*B+59O?`D.v8{Ի_>DqEq{D`[R FZ-u?q*Aٷ <Hff&67w1rHƌCVVsEsS' P\^n[8 ["Qkw2KR/J)(EF_ȨcdJ`)7Y-[&.].`]ѣG]|z=judמ͟?_W^y^ϢECRPP`?k,/^lnFsL4z~5įQؾ}[oŤIx9r~̚5q[bY/p{ΗpcN?hs_%3{ iƽ81w!(_ۯ_L3)B\dyI |~ {gy  7kbaٲedddϣ (**r^j/T;wy:t |N:Gxc 7n t_RR˜1c(,,Ds91\»pz:Fʫl)PPFEbݸu>5Ohvc>׹oqb6beHv+* tF#lݺ.]`ZyXreNhd$&&;vzj6oܮ,DHKK#3֌8--4ʸp'O&55뮻ʕ+$==_|nݺѿZ\QQg_)#p*͕2;ؚ=h}؋@zZϪmRRR%11yaZ#//ԧz뭬^SNO?o3vvO)ݻw=z`6| (W\̙3j3yvKS"Rq9Уzۉ/WUCp C†<`Hqr))|Nˏ!$E(ԕ"LH/B7S*j?C~<};JjGVni. NSeς BG,7GVłc<6B 4 vJV9Jn!" HWzA: l*,$'ٛWKEwA3KtvGP i`6:A!a|tTmXhTbwA H[qQ+` ת*V9@wi,EHsJ/As-{&$$P\\,匿/{X+!wz)FDމ !.2ΞT6IB j3-?\NogG(*|A Vv\^@Bj ؓzY)6,,mғA.v"&& .\XgsX8XKGM`akxV5:o3 Qn8i%to'pU 6%3]p ,`ǎpOVOTTހW0;꯬tj-ᤁ *(Bw/II ݅gȚ!\,GFs=8X?Z]`~`5/)nE2 ' Qaha!GnxI-uuTdDprt:,vCn5g"v\͜9sZK9ȻYKv*4`_Y?q_y(S13IMvCz14Fk4.Ιz|+j]cQHW!vL+;4NRi&q.M*7XlU^f܀qޘke0CB7f%EXZY DII ,Y" f;SQ`= &;X!V'a8E*m)bɫij)~V\`Rmsaη$TW`-W%*βo dD3 `ȑmv.IhYVuP @rrJq蜽sS5m4nپ-.2H1~p"%%N3A AH***pϟwz5D:s4("(k?ڪ?35-櫤sKs/ovGIjTVsOnڴInBо-{&bw /w^V+VkMGJŎ;  JWoڢW5c;r Gg烣4YݍFm(Pܲ҈VG!vLYbeee+̞=gy"{=N`uh}V4e^ 㨱uӇj"XJM?l"?V|jOփK(&]\wT4KWs+Qٓy7r)P J]<'mѪj/!%u,f?ջ+9%9 >4Ƕ|CA$O(/ IDATŋݛP=VDn j`F16yz o.^+Gdz"~SNcE VįhԹjiVvv,vXo#ØaVTQڍ*]p5ќ8qA{oAϞ=\(YbM2KB֒[[Ԭs|tIOBiVi@(ۣ]pW^rrjkaÜ0u]t~y܋ƻNHH\IB[>zҟC*ii1IhAΦJ΀)诔7dJ\\%|>EJT)J+K"|2Ibz`8iH,UEƑ[! 76f_>q*h 0PSK/W b M@RnHé0W5zE؋~.g}Fʫʛқ~RzIJOb_>e˖bi`zaz)mH3(iC  iSK݅ΆDWe=rSvJIG ݕӜ-<[+ l+>W羱cbi#hQ.^oP_>zCmnc՞U\`= j>=RQ?l9*)/̯US*[tCVjaDD`NBwymmWҥBaR{Å:IBg"vZECzKof-+0 S1ͭx {;r'>\+axiD%Zwo`a۩ÊtxVWV LEyeϓ" ۑ[Az[. j\3 DY,_|A^j#岹at)>"Hw^x`cZ+oIҢ㓄Ƃ֓dmޔUO97Ş5txPWŪU8|0-T*;vh8!}hƑ(oE*q|_i_J/HoqJ4-sM&-2sP5h={:zJz0gã*93=$!aIApTJ\Ѐb|mAAZm1UXˢaR_᪠q"* dd2fY~y'e23 Y}]`̜9{{LjFҸ{͊]]F ։'8x ME'Պ$I5k֐LBBR|UsY?\ɌtJBP7c;aHXg Oz2(zNWd695}~g$&#U*p;""ۨEQ =,i}' WwKmw JO۶V7l?OFR 5FzW70@  2I&M(u\ v@W`'rIеaOSGe42vȖ?M+i/{O> B|}X"#ކلcɁiF\2'жyiKl>ىlG]\vP}Lmf@َ FβhoH<$D83f(G=zGVSl6y˜FKS1qbӔM*'; *VjڠgWJaGٺ c/s$0jemedXdMt79ZvgBwJ\Y¤I3fk… j{n;4QJĜ9sČ-am^KCCCs/g?&''Lx7='UQBa>ԥSן 4YLeRSS1&*NWBKMr ?`0(+ (+ԩ$@$W[vgkB.!{gn72m_]:;&͂-^WAVw=(qW/={6rE3X&M"##N{zAYY<Áo+nϽQF1zhV^z⣏>?Z\/v*r {=}aKzoPѶ-s<5{aLxp! j&b.ttI~k$!mǬ4WlDwp]@![fuXߚ;vbf|ͭ*:6Wu]\Bxl2Q-Qp4^{-_5)))X={:̦Ǐ-VRW-F{%{U/P4$6oQN[hDOQI*,VK7V6~ / d jϗϜ [\!I Dw=3!/hkKIIHSm~+G_:RKP_.!)|dBV?鍊n`РA\t{%X{]0><;Ą'W~[T?1w{(Zsd]ރ(Mӂ/*Td 4Y1#>s{ 3kn:t]W.ELռ֯Ÿ;Hu$Fh=;A+*P@AKؼy3| ?iӦى7 4͛73vX~Gy.^aSy{O>aϞ=ǯwu3f̠V[n!**S]DIҴ*5r$]kZ՝%2s}~IOIwv9=G,t&`Fjͧ8EȂ qJMM%uh*Xlr"jj7֐! `<,i_>\ld1?ȴt{@QRR]J &+V0}t>#VZ{g͚Ŏ;xn7YZn11&'';v0w\XbErrrrZL?%33L>#FgaLJotǴiӜj[nusk8q"[n?^z5ӦMsz͍7$[`r]w1sLb6[|W_֭[y'{1kW{ܾ}{ңGV\h$33Om6lQ/{e>lF?T[[ˤI"..ðja0zHH\$+`Cv8 g멭<.DgAJ,GC֜U\ZWvv6/iri'|0<ɒ@ xlrfΜV\SLqM1*T\:[#8#;=l˿_jGT._ېv+PCϖ-[ɞ@ٳgyGCbbbPwիjuFii)=z`޽W^~?wdY0\X`g9ؽJBAJB_JM.T}:$%`+6=VcX_)`Ơ~k%x ߥ??νW6RXި'."pرcHw =$ P!{ݔ"Zn$V݂[j `'/ q%²2 t6zLox%'h?+uu\-I$!/JFքuXZ9 U .!Ύd҈A.MT&T ( x衇}U)m!0-D"₺l=9{UB FxW WuDx(zYn;z~W/"4ڪ~c{۷ `[ .^()uj U+hu1% ~;gv;?>}BD6LeS`bx2K^9 IDATo{\=fGʎtfb޸yLj2*۰V"^;&):ɾzOHMMŌ{;֢M7y|H^A?krU0}zvtfd͚v76!%Niߚ#/ X``~JCVFM+שY"tW@AG1cx78psj-b_dٲe:u^xHHuN^0Wͭ$ FsQ.XrUgXw1>XДZ% {*eggӈT[䞃 @Z`cʔ)̟:xFn#x]wq`k~kR~k"Jq׸.=b"SSV[fgXd=}b+(.% #Fpr|߿?:YYYԴ`]}լZ^z BSQZ+ ;HZߓP ٿz͉ge0I`99өF6={dc!˦N0www藻ߚZRLEkkX->ŵmXPV[F>OtA(qWXt۶(mſOwkȑ#o? &"T̚5Cd 7n0: n@Pqepwtw]n(`d1`"eB 9k&JEyE#q6ٚ!=᭝;)d(޳ZrިWLV"5Ć6( VLm&fs7 =  >,ݺucڴi`eeegedd0dN:P\# Tx1 ݪ!Xjږ E6x%=k&€ۀ2ovW7JdUsy3GMcVk]>3gZk* ǩ$k{QڦtA(qWОPRR¾}}РA 4H9~UYyVvx:Hÿ&-z /:7'9!~@֠ ۈ:==?dԩlFR+.E\훛cWf㔍N_ΠG3_pAeP⮠=O>ӧY"R< Fmkz^c*ۯƹLh!Bg3\oK_!Jn$$L$~{>A. ǯrd3ώ)Sԩ"[m8zbI{Փ`;{cTqk4$|' JwS[[y 7x}B0BB#IZ\ĹLX h 8.Ӥ%#2 @F^jh4p0:s/{, g]-^6YӲx⃸8o) +2B;bn (Yp!՞>_B"ǭnQQPWMZEKAd$z  ּqoݗɂP +O7v2sLޛ5F wˆ0,ܷv+Ap |-: YND@(P Xl~;3f >>+͞B#5&QgGʐ|fFZT {+[2md`r 6<{.D).:,:~kJ`gj3uN;EnFBڬ̙\T +hkTVVD@` Bi Vk#.|BFs]#GP *YA8l=*v/ؾ+o07`zo_;-%ʄg*Ϡ3?3E BƘ1c8vzM!X!ÈZb%=\~g-򽄫0^k`@-Xm%baNjj*Uj5f<-INHj +ou_ {TX:aOO>ʅ B֭[Ybǎ#99٭w߭ nhɵV$aV+q_Iږ9^C&UTm@M=:>L]7GB&b[`=уU5}))Fo*P@ƻDصkvr{B.3Dy`A zVV&^yi:Ԫ4`XB\=$UHLFG;}\}FEoKXm$LBԩ>x;qIvPGuEh"0Z>/c02XDjĺ:z_\VKF"jP⮠-m_ 1F %t6Gຎ bgH {]O$c[J5XHSӷAV.N$/|ӱ˃|mnq䬜U!?vY.,-_wEPK:t( P).% .'.]o .BX Ľ'a*?[)Wt+GFhh&9ʃB~@>366OS[9?o_h ڱ= ЬK]vQQ_6.-6rJrܚuB:zVRA`P⮠a2xٲesll,s?8F!X qWac_P_M?_'D{@\\=lZ؟^b0}!x q:V~u5DL<1fV (P Xl?#O?4Æ TWWs)r!z_aA^Ix!jӓP-~4B=aL31k jA߹,,:m1t-Κ6AumK z9:BKDo}e&zS`0w{'_}Byg-4r {lx,ͺ}ϝ;Nw:k㮠"66֣h||<>tQVW j5L&99sx&ޛfϡj,3/T%2CE2+Vt1Jt~>@)MY,  ~.&~4 #yS5~2 I(=eX0K .t5^3 :go<dff}JJJXr%uh/gugXi\g}0v9QOB`SEErh:S]t,P*1ו_y8.Hw-2͒1Ѭѿ2ag*|}Q ޮ ;it [zz!lz=f$֯_(N겔A^ItBw0:y1a1!`Cx  VYHLM KXmϩcLJ cp=N\Olf =]$vzGˏ3/ZʅTˈ{̷s \0jM&4q4ɺ;m㳼B̙&/$ 5T'Cϫ[SNs5~JӘ C}`꯼jG|^ecO{o^ QԘ,&Vȸ5VȾM=.˵=3]A Z~`}[0L"4+ ]`nd1nFro($0E@DxD[^~R:,;kOl5 ݓbGBBBlHdsKR3ک2 t+88z( "|hZOyydj3fs/'7t^;5{ƭK2悯=x*69 ?gϞW_9r>,RV/gڲ e_ ĩT# _"J@h"ϗ@nldggS=+iM1qޢv|=l `/FW@qRBv+t_AVF^(,2ݐKczr+^1v놱ÖH9`󽋼79~k"#+D'.*]`i`gNJToq[l-[e|IKLcwn;TtX+ :z|Mrss7O\;w6ČM3,3mB&Ml+hZIf3):M3CcY X sy.!"Y%zN5P3$uc6KdL D ؚ5l/`geؽm ζ3L]¢BB!W Xz5gϞ8q+!ĝF\@&6}paqۄ SJH;Óeɞ%~^鈘2e ˧N%`%oLJφh#jt/O hsIjkTF\'[X7;BS-#11?OnۧOxҥ&|?¦!+#i^mi%-ɷ/+ Gc$HHIjB* qWеdW#ﻏ攎oP L_3I!K_xp )胜jIFs + O]:1LBeja0P]O;V9d tv2QXJ±cv n(%5zgo|E@!X pT/;FE~= Mkmh/Ƞ~O>ArF`.v ! HL u-Dƣrf$e–4v՞r0&p#&d) )+\ OEr@`u I҃Rr3jWd t/o $Qc4rڷl1ҿ.v.N$.F17` {R"Qިo1n|Ih9%!a'^{ 7O~j/^L=*`Ҿ=W[5dgg3nܸVFѮ/ F@j}^4dZt7tVVMbk$B%d(트-q븎2AP tՕ)^X( :&>#ˎ;Xp!r v7p*NpĈ߯ ߙ`~nʃQQ[_oI(Pӌ;tȯ+?Ƶu`n{d}O>Gȶ1H땎< k GgKRi c w촥]_l\&Kc2vXٱc'<<7zrwgz^d1 60w֙\ :1p/谽Y%hQi^ZM {޶{Pw{vhVbb|`+g'&}i;U܄2{R"T!,,={ңGbcch)-8NѸqڝ `h7lp`Ԓ<䐑A=2A4(8rި'2Ӯ$2, |]s?ިohv\Ix(FŕJ,y?xaPWCG|o)..Gz6p',VӨ4X]ļC,_Y(pκP14L"ͼO3f̙ɩÚ_.dwtRt[MVw}:nK>KA爻G}s1|pxƍG_ hܚخ F]a&4Y2W]vSvM%`#zA6.-=`_3}a}~*0m07`JjwZ6r҉3u%*g]e"CYUJ]A=íqBBE1msYL<m21A vF IDATi*4Ce܄$uL6ӤJ?,PR}:DA./5dj3}*orYO>*nBF?=n2q|; D@e<Ъ++ε:L %IJ:ޓPcCX 2H Ѹ€_>[&d_>ur3 Jy1&My/rߚA$ko^\97RA)#I_eevZOسgOkbhpN8J]A׆BAX" q{vB']{f5$E'9M ֯_߬$Ȥ(9Zrt^Ѩ255X 2*,cgQgHO\:>ZM/bx-k':5s $tv RRȭ?}]߻h;YE{Vz{GNX ;s(,tĸ+PVȑɓ'/t>⫯>_|]ޙ,;Ը5." A^y'H#  &S@ iY劸8'WF\]_s`5s1LEx|O]:E 6O/6\= tV'Y|9vBVO>M$X 5ѣGz_o3pWSSS^.B`ip,|#Rɘc:JB_&q !kVv$_9%9~d6)=̄m8fRX͂@A[KRPP1 '**G}sp_k2*=]ٳw(g|ZfT)[@ծ2xn݊}IcZ!F9\q1g-Z>Q"7&?\)JE՜3CjZv$_; vM\=̼$uK:cIq NI:Z\~>ꫯXd  _gƌ!mNɓ't466裏5klMQ@7"0);6nm$Rր>{[1տhdOlT",jlec& bnW֮0O? ]7T*:-$f)!n"xMkۛ#OlXiܻ^5 @QMKºuraGvpss$q5ط9^υ 0`@"X%%%{̚5hV\ɋ/˛yEfj*GV! DQWw(NO~D!-*wn\TXZj]}:/n@K&f#}`Qޜ`#fFɓP+V㧟̮0uGSC.kKo7a)0ݩv X󢢐Ǜ<~5W.{9#:,Q{TG;w*w *++shDMeee E^e_naÆsq.\ l|QcO?,]*Wei'\ @`%:;i GecF9 ^&_5-oAY`ݗNcԨQt: :toWN6ͩuV&N'uVիW3m4xf K|[&+8yoM,V 7_qs_¯m&YG߯B 7u|zPn@n4:硇BBk:_:w۹/w'ptv?EYY+"VKVFYYqާWQ,o0Uk`bcFd/1r$#$DI87\-ZT\L?Ib$qW~!o\D,+ IH wi.$'$syw?q#c6-E_K{)߯~?VKff&?Ç:uY鳶onm;p^U999Q_AuZ }L@M7gʦGv VA85Qo^o8f L^?nscяs7:}4avfzUv o=n/aH8; vSq\)q~Ӏ3`Lj. :/z=lٲľ}3g۶m)SvW^F… v%2WIIIͼ˓6hOPdLXز_=xğEΈ-*#a3 %ہ+>{\IQi0[N%/|;?@m /TmQqS~v َc@D0uY}H SK'=Try93OqO$z~[sWGlǀD/0QF\W-ٸ֧Gm}bh6rN]fϿ!CX,=zԞ ;t?&%jD)//<)//GƒoMnn.gϞeҥ9@vuphza* _/齈2\JGr,%%<$%b0~AdVm{PԔו;!+77%9x#:Dz= [èC:a0-oh[rkܻI}̮x'ڲ\[mY)n׷ -ZD .if2yd*fDLiގ]GF0(O1~x^uΜ9ñcxڤT&oW_}UxIO=$E޽pFc̙3Yr%?<&1c0{l/PFUUqqM*z~y: $. zJvnEE% JEDFF#Kqef84* Vkp5e\#x -_Hb%$ܡW <_]F5鍻]CgDԔ`0<6{>Mimi@"nEE:G]co$mgq=M][QJda-Y-Hd;o}lp^Û=&":-^3X"3@gZ=@Ak3<òe˘9s& y6Kb&L`񫯾rCgg 3^Ξ=EsQe#MN9{LVow![B &q^IX h 1tl_<΅ NjKٕP !y]_^)[b0x$W:J¸8{&~®!v,}y`M=xC/*,]qMǕ"Uۘy #NӤEs%Z `rP Ye}ye0t^Œ=K8KulvNz]Aׂ|"**^xlғGRR"CME$=(-:%:{)QDF& Iq=1Lm{$$M:,|iSmVF<)dϒK!;;F(E16w\ِt"2MOIwžAg3Dbjנ@00~ sSJ]捛;rG[+)\4ΝK$q*\iF ro$|8-V`2MBHVq1LbyBؾ՘dGv];e@A{x?xvYmIlf ]KBy5Kaa >6Y#M:GAizJ:,z > .l1lMJ {0ж9Q\w `5y%%!a1noForUᐱ*ʛXzIe۟ Y"E,m}ީINH*-ا(\{`2BNn@B:AKa._]ʱ]O-ME -eTDGw"\HR>>o׬Џ^^Au^GkX+EK ."AGIM }b;Q'ިWL/&؈ Fq/Vmkr\E]c|*ssd/~/PAzKV̞׶}:,4YBUgj -c\͍>å()ےma J#!q}|d^kFcyq![B(/٦8G5(K sh4@8Fc Alk+"åK>J3o[,^@]{vE0JGoŮE%p}Qa?vҤ9k;[{$U͞]vY IIH-} p\eKKz"ԧ,ZZgcwq"|Y#G>>7_q3KMjLY>Ֆu1@B B,v 쯻eǎo=-!8LsKB.V48dʉH T+=>qo>u=O5 5֖D@^p%ܟkDo-_\Zt&ȕFr|aW; NzRy"O}`ziOk4$|L"m*gz[n?eǬV0y2Ϧ=C.9pQ dyE WƷ>$[PR%S)!\N9.ν:Ck[<)Pv_0۷ J& 畈qM+lV$qs8;Ē@2M8.5CݗX51w]#;>Y[sHMM%X\k-X04Ԟ:E$r>˂*re&WmkȍЇ\`~b`1j@W$֗RkM<*,1PQWz7o z 6_0dY(ΆF<-a tZA#Zj5L&:̙~❶ch쀱w轀2KzyQOQ m3DLtF]bT  u:.t+cp=ّ7i-K=V/͢`Пa4eA+rOUU>uV+l =Sz$X{rVwsTQ- ʗR];!ONcS(PVF\DUƦ?zlA^$<.(Q iGS[[Httt@a>ruX7onN;"]gw Ձ[N@sCޟJBQV (3oBiW<曼0u*8ac@e[6^uLbݑu M*}%$B@+5qZXZ0:s{ gTxZM^Q0G粮c[UGo;W3Aapød*j-qV>:-Q $$)zV^Xuu-7 VzJ$'$w(! $ _*=aʔ)t*&oH۟(EH`Ʉ%-fq7&5n1)bBC1܁lXB.cJv.vDx >(Dt3wN\G3i|W@!X Up$J ZI4Շa6%j wq!ѴP_*5a& BdB|c8n@$^lOq=;[w$B/ZbTW Sqۄ7ɽѬcDJJhw,i"cc}OJE#&?{gUyd& $ Z}A[5Ok ؾuEҢ*TȢZ [}~9sf& ـ䃙d29繞3:ۿ#GT T5l#c0^B}F S]NgISIh #1x]ZjxZZDj0NK Zڈ$R{^XL iD AN5D{,X k dv;KJAE$ /+#GD,q @VmSS/cλE ~r>Xv< `VUU[[Un7iY #\lڌeNͬ3g%2~[߼u QC>T1"ߣ(:A0Fs>@[iPX|C9^zUd/^ɈiӎT6WFn]X匴 0ҚB+.*;D{'QR~k6HBBz[|23[N':,[Ža49(Nؾg;e5@ApW Qї2AP@IIII@[# )"QlT4Ǎ4--"ҀvCQV4XC.D]2K=L!*|Yxʫc%Դbxa cxepXDKrӳO`w?|0lb "ƈd?S1@3(| Pa$y,Ĝ`>6u cH;`X0չT(iZq6*+t׿e(@LspX|0`HO6OΙCag;m[VFfJ {FʏYmk][F4`}MhuZ>A|:ĠʭːR#3).CFP}VR\jϻg FTbHSY W!RK,n#I$ɂ4Z5I r+J|95+& mhp䔯JɣӞ3`:,ьg}+sWrJ_IYxnP3bco*d">wgry 1o?~=Sbl"n Y;d>W~U{4eNz Ds!,,eaTNl -2*c .nQCL,Nxus N'Pu`n ׋bYl>Ș1)HUQQH}~;4;8q"68a@8 YrQ,B\p}Ą?Y$}K<3^"ՈiSFJ՘/uX|K&lr,Й^ $arJ|6Ia{wr$l&X${})#G?#lOq_5t6>L׬H10] ] ǥQG톆V2j56M dԇФ҆Ku(L>M=N|2偈4^7O$K6Kb~zc͑h`*#5)JJJu ĺY>lv逄rx垻yk<:5^ `is6.u}uo=}$P6? IDATg> (*)vӶ:R]/.Fш-j@[3~~>CI,Bx@ lո QIy|yˈ#Bܽ^/_ k׾T94ߛdҁ`MD^x<167Y l֞Cװ[F1: @14%Ԃ`Le8]z1W_a4 K SBr5xcb1w\ϛGugLLG2M5F,#W7|{&i~`U;!{GҬU|ڜM`1]X's%6M} P`itG OHSWo@YcYDޝkA ϋϨU NAYif3&iCNjCOFQ}3\ڢaڏĩler_ӄ%7| ;mTTTls(,c|%)8l ƍj4U+ V{+82&H,KmG-i=^c}Gmc;z\wǠٳ &DhD8`2n(# {_\en"0m(dV6M=5}./PNYܵl̦Ii8L53QiLziTD}B;(8jY8b49-eDVj(c0H#&FCM%Ճ}"|;hc$H'm/6Æh(8K`ON:fL 9Ol>u$ick 5ۚyy;O\g&4(Ę+e}F#QՋ uuTP{`M|BbC.8q 4 QqqHGr:EEQ^"+5V?`TLSr!@>wE꽫I6'ݼoi aØ?f}4,Vr*Xjx@QƯJ4& Onmz˗!4-i:xB|Oq0Ήk 0yRJ;=u"yq E#Qu8p([,c;ZvzT 8#D ʚ}kx0H8 ;;`pթ0qK lʗ$ yC/V.@2曰7wog3kީMVR0[n|- ˡ/Ъq4{4t jHeWV^Z Յ8E_O V E_z%*s~6 ~̻A-9 k S/_Zn k3S^He zl*m&;'шF` xkN6!&e)lG 1CR6kv۝ xx0-g/|wO"qXN'ɚT+!X7 +@6hǧjAK .  pEKLd`.b|ifK9/\`M] X,h4eW6oq_2J] "|W/F@Pe|ЧdE *]g-z)ئ#x{e[ţ~iiuؤ@9o*˩Z3<<]X'Co֡^o f5 ECOo%z[ϽbiǣfJ,bZ`5RV,5 &JOUeLj`+G94Ej遁&SjHf"K};pˆA2Ej#2h#WʻLLhSZ pE+St#bBv"t:,^^8) 7/=W dh#75Z*+Ih|EcXdZ0V'[0 ,mYy(Od߳`"Z5!S՝߶̀]uWWtUN違x+` 9UeူTnewn\]x(o;&3#wl #Z e,bCL, 1G`TJKi1*W]/W/jeX3 eʻ̡X 7ջc=(egU,#ڶhj "Q"\.11)뗩 :90ݜ_wD?tu]vqRm* ~젍ಯB0x@Y']O)|o`8 +?X1믟X'kok,T=Wc* Bf5 łA2 !qϔ{zjc7x]X+r /áX.HEK|F2r0ЧM:>cG, ?]c)*(,t|e\[*+= v#FhxdI åm™sn5Wjvh@S]K% #sIӇn\tǛj p@VRI IoVHiZ@ٽBP K^|‰Pi`ףXޜk$^wǣezGG›iGڸDLq`[i",QoIO?nS<9eWfr `R^ $abz ضm[K~4`UCJ.W.=JJJjM8ԅObh =益@}/Rכ†/+Cs:O/g\2i=:&*ssނm'c$Af==m{6*\&(iHbuq$BtG$=Z/(͹D$oj0܋1j%*>)DzpgNBqx#.%G΢tHhּ/{𦲳;ivqxz`q$m8 ay1u]aHveXqȤ|QCs˩SO6?:h@2v|ˆX= 1IxG'$r#ͶfSo<ƭFN~UvO!+jlV0ME٘jXn:8XHIcu.~?0=pZ00Dmv.FyF$#\j\mh7Jpэ:'jDVz qw<KR̤xpa@n,zVC0Zڡa= 8٣|bܤhSj REM_[ lB#6.S,`&ju@ҋ ޿L,vF)T~@۰^|3]$iڸXZ?V3 rkLۃRѬMj' LyFbMe y>  b!I ii`KE`;gF#1xIa :p=lgnRݱ J0cl(:Ƕz4n="77;0ZgUEꃰCrX ƫV:֢Ky$3Z&%) C[I(J%a0ZsrYZjRť@>׬-S;\-IdH&r+jκ!Ӹx~|}&ψז `8֯_TniA=#^ 1>ބ澋C^v$Q8 RY*zZIbK7` >I\Js_}5D i}x h8eCOVQu҄U N~70z2%%%g+P-l8 q$ ΨDz7ٜv.m~D& ܰA}6̟\_&d\. W^Iu;TW ʼ^2@׷Kh`fž^MiM)&1Y<:mq/qfcO+ B.:Ĕ~ =;ޥ# I"66#wO4 BW;jwa/ 'J^/;W|E/ҁL 4r:$IK^ѹ 1)fLrRs]0U@gϞͣsp>A␓V %\~ \Xt߸7N!7nFH[|uҕh(_˗/g~K_-k2_z,'xQ [TW͖$b2K.ؚܿGns$ pkX˭lBɡ>O)PG}kӟ׭Xa:Yh^,>o46aBx ?Tܠ!ѸX%'5ds2㮾}/gZ2n5`iZfe9,ڴH"g-r(+vྋ@ GCK-s:\*8M$9LAfms"*QTdI} cPbf l.bшbw%qw|Y_O/rXyWd,^Nq:Š~ O{5{]ZFr1aٰjU={SL[6C-E(҃'n&_{mӸCjo.ee\8i ;("q9ϵ7Vn2 eզB 32ytFiS-u?VsKlYZLcP밖[n.ׯŷl(*1d>;jwtÍ oK{t47&>%K/!#.5_*:+ ;JAAG>…#/Zne۱mCB0-( |;stv-hib 1M6 )LonVd !]ŋC0N7 Lu{r2+cx}wܡ݃nkU=`r^5[#qy\L˙o~ayVcl {ltq@4e|mlÍ~#v ] T4WrM0-vw7 |l2~8m2AD,|VlMyWwWTTЎŁhNۈ@zM82FWЮ譤 lŋ;wnϱ,~@$hko7I^lѤ\-3(u:#X4Y) N9GCf#)26M -gq׆d6ɄR[OE#Q5ȡR52{uDdBOtϫڈP |?q>ӽ:?`q `Zz~SN  rk۹siC/[[թ`5㦵5nBr]ybK7` u06-FL}?Qz8`Ap')(פ#WhzIS@V2rq,rw+@ ,GWW|饗v]._:-.:xLkhD)hNV u%{KքW.0---4=(l>L8-@E<ꁪQYi3'̤ZQ8{jɽ)P)bѬ[j-d#G%#ͥYpzL6jX,3`zn:2E׭ +ExbLP7Zn5WFK$q&ranZdFho" 55[1Ąu»0)&IC%V4 ѸOw<,]']o5^M{WPD[8!MALdT91YbE@%0-)) ?ԏ% 3!ƭV[ GF0w\ޞ7M"&PYakCƮFf-ENj(oL?t`g6ČW|c׎ pny}=;4etHFts?S"/_8x\|~!s4Quʆ^c];1l(&I.'{Qh-I%-!7-ێ"G[AhS>'҇|jjqh[Y7hWj;4`…\X"Y /ڴJ6dL꘠cC=t,NwA}ǦSm%Lgr2\z,ֹ@J\ jzWTTб?gh lh7E.\p'û+o+`V6gp7.\M|]—̞=ahė&Z"22qr`H &`:K+]cYutYeYvnHКx #vA§vHm߂zpO5bsٸKIOso`AE , HFý^fHRhE# ef(+0(t|j]_ctKGӞ=d)/=h31ԐyjM+ Ҷ hwj*Vy Fn'K,Zn |Ͻ]̽=AiM)g@G}He1FAӄfB g-0mZˬZ  ʃg$+YJ5rZδdĎҊ3tUm,ޕ ݺukHܦLcWq))Ր<Ҕ c+M[ qU[p$qY4QP U>7*y^}Q]dEndF"GĂ <33i7jo0n= HٳgМ9\|g pm5nؽ=T9c:!WfV{+ PNJ"ٶ=p.MU?REۂSWK_ewe ovdfX[Ғ cc\zQ/UOཹM >WFISNcWi~n"b()\R(n;M ܈l+جX`Wq1e6^U|q .}_х6 N 1NRZ#z)ꅎ)XFe! )k*Sw-=.]M5 L/t_~ ye\ X]H' D;{`hl@=̒ɴ;}q%]%e3#H-fY[q8x[Uc_1T?!WjhNqWMfK˾^.>kJpkAGƏgWZY~8蹊zj|@N6v5ǽ`˩& : YUʜ.Y؏ )11|xX+(:+!\NyJ`ZD1m@<KKUBk!;]RR9Řy݆\=С^ql1D6ߒ ^ " E!GZȡC9ڂj~;w.v {)d/f9YY6nޱ-[/M3Z z+_>28~7xE;xb=t?kɆ1`AJ6'(h3q())`o`]Ť az|:.Y }Uj9+kU6MkVhJ ~|m$ զ>~}6Qk(R![k0XIW Veתb?Ka;1C;|`E%Kp]wRm6 V6ߒٷoIjfk]A2>y䠋?ĭ(d\ZW_G|JfR֨6.SN%ٚ"X#~o1Y(Wt7pr:Kk⮆so9/iQEd(K{Vks0Q`AJkJWrywW||c >Efˀn<';VUa5n 6@@&խ8Oߺ=KO\{q *G> 34lVsG$X%%%{۷N># <\_}v~~oíP H1H,Xӊ03n`K &Go++r}\q㍽:'ܴ_j* bJ+piPߥXc,kF] ͅAΘ10f7Kgo^?\2+[D02cRsϹZ֮%GXZ'FyVvZn0}) o+L^ )VuC9s^N3c3骭FxH^e-r[m_odH?,{ez)pB{6+]$ Q+@ևldMznYچgTҨ^_y7WDkIQ4Y0Ea׎*t$n3]B-S\\L{{;&M'==x9sP[[SO=c`9&O̅^ȫK=n>=&!Fgo!G~l4%p -lz)V$D(q{zx**Ny۠Xq@\sss-iG.?±gUU7Q[;C鯎j?9II[o ұW5??hs^ۻ`1vBf5|1Api,!r+j;kYѲ:C-:wȸ8Fc (GWYXR\V'Lpu;YпՎs >a 2ЂUd$/VRLfK.n:#kv]ojܪ۫nq ̶c >&Һ(B0#z{,TŠ Rnʕq ;wjgfI~@擨nbM]٧g7nh;;vjј-[eL%.!&plzL:P%T ˆp Y/5qL]y$Ԝ(|11 3ZW,pE2r~󟫯 6lv`-]^zLnnS`M>#Gž={ӟD{{;sO~P 0 KC[vܵM=~9WZu\KîWt_ ;Μ>׮ 3N${Si~oEbϠ]w0߸մ۸Yˬ:Zl\:[νŋjڐ {MMXRYV{ iFN }^7Jw9shC7SQG;߹)Sr$E"jR,۝)ˎizIV@V rE)Yv6 vh_ vrss;tjr>}Bk!(GnZEXYBЛ\J?`SKTr#1^"n6ɲd<9|8*4T gܱUAayԶD/E=ʽ_z|G&~ob^?Nw~S)inn敍ITUB^N3{&W*5З}IqQ1M]&oKq08TqS<ȎL/΁}|iz9r67SMhFѸO7i$~ E:TV?oWlzq6"Z)CTrLdI k|]Xf>gxmhATlUo2o?W_]M$qf$&Zcc#a㉺~-҆ ԉ믿G}lذ vZL&u]GKK :v>(:x1I&ҙ?e3 c|^X(7yw/?;˟g~F/Cϖ$>ſ xA fijIYdW^E|L<2& يI9ʃ4go|&䓃{߽@0(ilݸ˦znيo|}9%%%|4Soyƚ5L^2-wmQVSX}?[DcPi}x}K U rΟi0`TрO#$o4Zl k4IzL`28vtdL띯=frj;=Ky~5n?4I4=nm>[3Hd7"W (J+|阱]SOɈU%Mkv#⏀5k֐]we]PWWǜ9sx뙙~-V^ߕ `QVVѣĭ2nc]UUŊ+O>^xk\#v[OLIa;9Yzq*# &Ҋm\=3}~i9Ӹ:j tFb\_()XcO۰Bk!B^5ĸq?Zfeui=>'Z~i ћ0]C}_/3cƒbN}劭j  -+}ܶn?&Ey\I#AyxnZo;hskQeiV{~c* 6Ho2YX=68IH ppe٬f D-ҋG4L0XILZgCRq&PDd*UaśҩlD#bPI3g;yf:ij>˹K^^wnAwի9tǎ?7^/Fd$I}HDQQ#FѣؕF111|'l6 u]mg;E3quױrRN#P?uHIך &F$`Z4.=߸By}9D/JB!ΈSt׳fŊ?BXc, 1 q0m6Uu}'ظ[d0* 7Wmkm'/>7OE7!ɐ]~QEZ-2]S\Qhcҥ!>9糓{SN YC$LGa(9G╟] ]2xVԗjp$@kG_&O;L=W,34ÀN2%9}/I7r*t`˯@rՋzDDM# Cl~h#Q'*ʕEÂ*Hmagb}q1{5)dzB|PmL>F.\HGG'OOv;GťTL&֮]ˋ/bȑ?wՀӧ3}?aXaVi<5?w<,] ˣ455xF&$;1K'-rbP~M0-(JgbBVXѭ=Cf; IDATl$!^jU8}0qKrcRǐӧ㦷Րu|l26 /O9دfI+D.;s ""k5;~v{ů? 9j?E5ɮh\b tt.E]VŖ-L^2=Wsѷ;>7aBi-2!cB؄e'fէ6uTRJtz 'wzk3&e ۏmg2Y߰vS]_ k'|zg9sX,TZT ]xebc2]j+8v";vKդ t(ǀQFJr΍&W gnM'Wø? zKd#{vcBjFO]zl0_i!WaN8%?ؓ6 Iҝf}zum?yʮRο:M6wWF| kE,& 1DټqV>Yh7QTPDEEq6r#xRit<.bM ]}z"sqJdZ[_.sv$uIyf 9գsl7,iɧ=obu H}+I7S_@\U] i\Ser%QUTMTjKĸ?2\9JO˙FCWCHq e#+<Ͽϫ>e2ġ,VpҷCAEXW~2* ^o۫[b΋9n:&a̝;WbmPAUVeQ@4 dT ) |msL2=-iU6)XڐSoY jn U$k]d~ ՠ<:Mg`SجQ 2cL=;;6ʼ^ΦCaʆNr┊Q3a1qձPr)&L%#."*+cU\!MfNgg ˨r 믧x P6ڎ'wőM}Ҏ7 />7}ڧ?W"fϞsG5ЮF'T Ygo}))VJs?NOkmk'iBLuS X#@rÑ'CrX9My5ZfƮ]}ڻR~zYgiW k R Ĵ7A @ҝ 1 w֎q `DC` {X"ZSQ ap މLMu|Bu<esm- M5L}'xqa3.F] QؽkJ._v9_AVիw[a"jbt:;T۩XxοQ B"%NSxaM="_X_q%Xa::9RWx2.amNSxuAH<߈2~X4 !Ix#L~S#J iC>Y_RV:ԨkrJoُ/bP+F&? qrp QݗPuYYȤ1ZF8i,#mܸl#Vq=KpFTHuutX NZTtܴ1P WBl#'?-}nb4} pdSt3E*[[q)P^DuM0w{L!qhY:"r1!B?WUuq&I(3 ы)1pB%hd~˗#@'d0EgDx膇o&)W 1:f:uNW(5o2,._^#f_ӡ0K1{?F J4e}0a f7;ⱊt/[ (#_ki xWxh7A].IkE󄬷 7@אT8knl裕JsJ #xkS$#&mJ5yCw=<.23^0'!eг=!&J˖-yX)J@ݬ>t:0~ZbxΠ*WrV%}$ɼTWeު!?/r-0KM=p. 3cTt) =,;G*ڱsxQ6eeRo 'Q+22/O io} '5${w!LH=qҜRJ>z7>(WB2f!G5M0F@hl]0Tx}lTB. s]#!_0j*?] du u\H2HpWxX,@ OrWmtɼuw?kpH-ew%[|p?63W)޵*甆6<|5Ow\NRj|*EA{(W2@ +PaPPEݶ@ъ @Uy%\BX{8h%v ?>ctn: l>L^Ia vr3,-`e^ExjdhgRE-B[\f+l+ä"ͱc _xJÞw&%Tbw cɷ>}y0oǓRJo켼ߠm04d*xAk4\kE""%9%I6|dL)% ?jzL33ymO:%"$|t3!Gx¦";|.Zs Grpq=-I@J^|Zgg'$"xܻآp"1 6p{Q1>O&3&'`l&0m auYjw~z>9ln[˯Y,Κ-x&bwۑ)dH*|x|'#ԨkV W? Mٹ~ _x'e^!].GodDo꺢 .]J% ]fB@17oTk8wDtd7qm*GQZNPNgWYWk4^Z^>/CYpH6hTH/j5*1(`uZ#C)=EE(ܾd1!N0ŬWhͤ].<28"?_eFnÂak\,G4 z 6pkc #c=&L8av6xqCcH }%FpQQ9Qx0g3μd@cDBfPQ{ЉGr h3 nZȃ:uƨa<硌+:"6 q_Bi/3nzҥvRm>:{P*"?- s%!`48=WR*ȭ~_+ίb`dbK+VJa֤(%(0}YnY8gVrqtt-^ twމۼt*$7|;`Y8y=O&ԱOP"`޽-BWl>WOضz_Ȃ&-x ? ^ ]Fl}**J'tY"'''-+d=TwY]] #\2b(LY"Q̴睏> 7C-`swPӃ'ZJ@csr8X.+9 Յ{a??F}cX8)$u3 3o~ KYg8?_-g\L1:wݍ[gl_t1f}m9OC6t_>> &l7G@~!%9%߲g'L1dn;r/`^X$wV$W﹇!9(׿\&R3hoW_}5f t(\&[SYoχ%{f8vCkw2om&H{Þ7h4F  <9 Rt9sSn]=&k`7jo*wVjo 6D( +kR)W2 sspׂB6& >_2\B|DC%^Y2vs0!FiJB =Ex=Zsi+Jy0 E(gj,A3Y)Mosti:V_KK"wBՏC~cQ/BUn^0y,v&a8b:]*gUr[rAym!Ou#$`)q$ǦM8wyV5yGmD&vR=mX !e(Pf(f~(63'O3%/AúJdݏLY&,JBχ<-@itR c`@pZ IDAT3* B!L.J<)ɔOnM#M>D: %#}JDhlλw@zGBOhk>i ѿ6-lJ0%㥐zĮSiq!WYoՇQ(Z[j7O`7.d alΥ)P˒farE Wrx]O^,tr~{Ń`<:2 W3Bl'w~f˳>k}sR?"|ЧXR7mh]L~OAH$i?>k*wVڋUr8϶ljUrzM=>ÑƘ$b<&r p >ABtw8햊!;S9FH_fgͦVCqPP1?\JT$ZT/~{<|9jTPہy@(• >ԣŋ}MPM zR~:ϝZ?wygnQBL${a8l[}b{{^gk͓q皇?u ֞(VUxGC6 !.M=^<ҜRNvP/~;%!Eѥڃy0r8?" C?bDŽ?wy]柾)Xb{pk`ٲe] S=@b5؋գ馛b򾑤DOG5Fsj5hkU2y'&Ș~QfѸFh32f$L`Ap7j5И41g2v$TnnP<1BAH?*wVV`ƤBᜅbLT$ .!Qx|=> +pn;JrJ׀ly6CsOS8(\q,yքǼ8y|.?[bmecU0{eXȂ_l.-.GkZ{k0WwsN;X[_țvά# Uv<H@ӘL4qD"sr !JأkWFz(.9o_֯_6|118$Ƞ'B bZj:f;&h&I495i|& f9fٚ # 2ԋa 3y J#P0G9W_LYf]Y$yy|;75G<1}־qDW)$];9"iw(,mN"D*X41bdGV'PʕVd#K5AJ .'* 7a$PeqUcwnl&M=yK0[8WlGC_z=:[ҜҘy&[8-*qJơԆ!tubL4p<@C^}L0v{ۭ3ÆάRFsF1szbdᅥKӡ+qnJ=2 d(ܚj^P=}M[w֨QŞs{ F]3Dw3kp1F}#Ĥ䪽U=o6`/ݞlXd2dS(ZlEv!Bzdqr %˿bi?m" ݄5YC_9xv ;q6-ؚu< B`^ȯ`sblx|dɲ0hI^V &zT\%6 c%RRUew-I}6Vb;f\`>H"5> c5uMLFC]D`u8%B?ݏ!*wV è!qظl#Vq@HMg->+sz``%!W όu!0`lĝ#ϙzk2 v=bM"=prJɀ@ET$+V/0p%f ;ˋ*S*$X[63ln{LF_EpLxmBkn<Ny70()EC_ҥz㵋]nusImC"ÇIV,#ldR #\Dq67{n͛3`Fx5bJF<u~\0,Dl1Xl?}0QUQSҜR+|$ӮdaQ(}!.,!͡1&<~G#ϙTx, E5Q.v;".HHbVL%6݌ʝD;+QwN8>(r-bJx?5lnff̄Z7(y'> 0[9scOySH|f79f,SXPx*|sKsLJ<=jWbL~a8'Z7>3v'l7ٯ(PQL%Jtx""\++H­6b㲍녿bM8T>ٳ1GȾ)GЙucRɆAnYө% whɏ6,)Z bK c Grg%4&MLCٗM'k>847rB|d\1x(u1|tu5H=+sVPxpy营άcB'N0Y'~a":wzv1".ӟk / ]5UqWÓg]6.y"N=OX-!iǧX^| dqX5nsLj̀G7_&|?3sB rDj|i#@$iIoU[SPp gS]}pVq?.K-xq m@d)2W_MsoBINI=x%^{ދ7ua@7> qd16S[N0j@p7 EZM-Ceb^"J4)1i AQ9:9 X)b:60ɸ0p==qG/ /3ȍ,Kʹ)Ã_SoB`- b)/eOlT>3Q*jP.P}=sn/\/$#/8/,FY޸m5IyN()1$ܮܴ\n?L"x2vڅ㗏ǵc=7{#iB<\֡{ŵZPDR$;;Oě}w_sw\>`5 Yʑ2އo |mp/@1~be:jjQSC]_P&9M\ ݄99h3^몙gkv ;ǴXY8y~'{O ֔". V}W㊻'(26y|q0'*Q: 7D`:taʞ\^*PS 泍N͚9u]'`sx1!KcfI6 GڏD"m漢L,;|&tYAx\v:j!jX|``M)ٸ0xU=OOTA!Vq0d_ 5d\r'>QH ?onƾ} i.{kάcj5!}391iO$FI(}uzAA00I2V4`k7yωxNaqbho"LBC [\dJ`%CT=hσVVC0e6 D[<(Bwr÷8wyiypz̼%26.ۈ/[޺%.:slP#d+a5dEђ"_v "d+ޑmie8H|dloz"8-KL񜴛dNsDkա$dҿ plla20({kU=0"lpI'By ά["Ǚd_kHg9t8UkxSv0ʝ()OqIÂh`J>7K18[1<[<<?Tcp1+ ̌dIN]+AHDC LYbL K 1 W OBITC`QX%bey0X$fHnB)B8<@rUͼ 9dH7BVD0d?n̓q3d9=1T%nH$ (\c_$FJ, q"dg)mNV|$5G!{H|>8?*iu>!GEMM nvmz!V^=䀳q*$ g|}סoǓOƧ! oP[[ݒDX@QF]f^σu j5cS=dފ2Æ57I L]v=pw&op̙ѣ?/rʇIu#Gp=ĭ ذt G꼓ڗ1%:ZH,'żZ:]]\dj4-c֨ CN|z y˫!.Di0Vǹscx)ٜr@#x߿?WFAyy9wZ-b̙;w.>۷/5 pa4kᥗ^|<:m:@2,15 %tB뿘f̘Yfy҂"̜9ym L6 W^yUUU7oޤ_٥Ue`$5z:ϟn؍nK7$" c`D"eMv̛q̛!§o~Y"y?9 1%f}"oX6:: Ӊ DZcp:n8wCp:Q"8֣:9X'=vbCnn.wsss144j2f̘up֡C`Xp}5fg?88= X;S[G[7*;;;n}õN;ZZPߟ8oncǑ#~8j옒5d¦لM(p@Ӏ!qᆭ>lzǾ~l; C#vvۛ: h`O)O͆ mOp~7 F455aʕJRaÆ wN޵!-- ԍ++ AaަM KSLv/v|>l6_XT*dܹWd4F#rrrƼGnn.L&5b~AO8(5ॗ^®]81THS `j蹒d(//ٳgqۋ  A36Dcc#bʿ⤃5<< Fn^FFf|AЙ(--eh I] @=Ggg'^y,Zeeehnnƃ>AaYYz+|2{*Ԏx>3Ku@QlقBtwwpL?FFFdg?C^^^3زe N: |{~aE&!كjN:W^:ݴina G7\x!lضm;X JkB, ㏣ڣ>{W$G;v P(׿G}t !}gvW &wwws>Sزe ~ ==ո;bGȑ#DC^9qqd2_[n].L?{q3effB* $oLzо tݎ{+W a,X==r\$¯~+vmXp!V+m׋۷3EPL;{#  $`{IDATbÇuV\uUz۷oǚ5k%$ NP(g=ɱi7GOS %'3/J b/f^c bFFC |¯;F/"FFFVPL@vӋX"wkp(..C=$ ?cxW~zH$T*<@KR;v wnnj3pw 8x ($'P={' @H6! @ , @ @ , @%@ X @%@ u1*IENDB`trunk-2018.02b/doc/sphinx/fig/simple-scene-plot-4.pdf000066400000000000000000000455251324306050200222200ustar00rootroot00000000000000%PDF-1.4 % 1 0 obj << /Type /Catalog /Pages 3 0 R >> endobj 2 0 obj << /CreationDate (D:20100711112452+02'00') /Producer (matplotlib pdf backend) /Creator (matplotlib 0.99.3, http://matplotlib.sf.net) >> endobj 8 0 obj << /Pattern 6 0 R /XObject 7 0 R /Font 4 0 R /ExtGState 5 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ] >> endobj 10 0 obj << /Contents 9 0 R /Type /Page /Resources 8 0 R /Parent 3 0 R /MediaBox [ 0 0 576 432 ] >> endobj 9 0 obj << /Filter /FlateDecode /Length 11 0 R >> stream xɮqgܥ"7,&E+˒ 4ԀYgd-$9QQxÑn#㭴ߖ?o?~s?/I86?|>'zq>rɬq+gG^.~}ǟ| ?өg#umcPl Zbiv\r=roZBa =y,"Yưk|Z@y>c^jq*XMo) s 6Dsk;C%z;ȻJ?s ҙj1ΔR(ZhPz #<2 ٣ސc=5sѽ-z,.S:k<]6莩%-!SV@ڬ#Š4`-bPl,ZR1t8VKLz9'q5zwߩ3ñ~Cr_ %3p* jjcBP L4ثߨ $q#ކ(r1?j)c #; rHB5º10!h\ y̫S2t)K2O m.ЍޣkC8Ú\00!5rH r#)qU:J4rϐ,K ' J(=!JfSlG?#O/`^Ir$O ᭇ.qo:+/W(_ydw^\>]WxĹWPODyFjI%s^㍇k{ݕɼ"}(n>\y&Kaz+ܙ^ۏzo<?JHHO$GD ¥4X/ }邽˾2kM'Ф&*JzۺmֿFGO/4GFzp _ǫz=+Gn}iԭ-U gH^q\8R.nן#$.DɿN޿_k9|g IwwjiR 8[IDzp<={PG-NT!em;ԫK yp-8B}-LUL4⫍\zĬ?ȏ<*IEWUv~"ϚwRx[͞arq׊~:I>PSqRdj-Uy2Bs2U4C U f"GDՇ`&)fH(ydznGRQ/lv=0շr y f^+nՆL^3;'-! ^̕G0 yQ^Uk#:.(8>9լ%;Q{Uom8a_+Ra>VQSlw?{T,zc>?nqkRMsH*8J*tZ+'պm |H N0UbgN=7!OE)j#ߟIr j[o=;ns5Cu{9Y쒇s$`h5ϖk=E(S"X`&dy߮y(}@>%rW9c,N~FKHT39>NtuhN _wU|p҃f_Т?ʀ_.{>Djdo H=m- ڡ΋1yy JLjPŰ7h1XWҺ[ ppG^MrvaiňͳA>^y+\é{p'0N9ױcR >,(ףjuy\FڕCӞIrYۚ{䲚,8Xʘ,h];.8iF1mE6ء6ZMĈDѼ詯_;0k^M!K0ESMp5c&y\TqőG(Kq&~$6ئFRDG2Z(yMWˑ3!"1VGhpV]uSjD`y+lzZӑ1l,8X\}hqZ{Z{픬iD 2e[oK,a|Za*iW\}IK.sێrB1pAl?/c^3;xwNjU*'R ;ywlm-λ̇eP3%b`d.愯2]4E8l^u[(6ƒKS=>h٬%I Tq;̘||*i93NSyY:007TuVZRA*#vKP^MEĸy9=yEEXU>P[0^RP U:AsV wrX>Yֶ ĿԮ̺(A*|J )S z}Bwg#>Sv}6k>JzG@FEsH |-H(dzɔծ򕹑dv Zx}5v4׸{%gZ"QyDzvVC|Cmm+01+Nw5*Hɇ#yY/gH{/-i,Z,VFnmq;m+FxUZ!2Oe(;53"u#ztgOpfS栵yFTgߝ(qZidŕz^luWV^ a+9 V(Sk>FޣjfyxѧRma!eN!!bk(]x9Ę?5b9~ԴvZ;Ym9t׊\͜0LОKM1'+u!%Mu -Cij+%AXۺ+*r8.f-nSJ"}K.ߵJ9;saWM-I3 Ik(soIcWYw^֮AtQ#VKy{o\>=~G /4~iD`ȿ/ 1O?`޿}~iV#/2h@sR2Ic/ 3T~i9me~ /4|ݚ>k#H_ݐ0e5"cZò SZx~7#:j}MWߧQaHG,H~p93D^oZ մm RߘeMQ:?knٹ~䵕^;=c٤q~VMLrd9ă endstream endobj 11 0 obj 5601 endobj 16 0 obj << /Filter /FlateDecode /Length 411 >> stream x-˕c1D/ B_IOz΅  εdIWĕ}6o"M>-5bK+`]|6MMũw'id2`ݫ3=4p36 I޲ReGms+WxϣS&.3p0^ϳEKV қ4Q$K?EteW3$xJ$6NL"21%<;m?y&:z?Qw̼WFpyUkA] ej t́دx7ilc|'p!8BYYϹ%*6~ZP^6`uy1ɶΙYėQcLNTUhrC1z!v;hr53's3' endstream endobj 14 0 obj << /FontDescriptor 13 0 R /Name /Cmr10 /FontMatrix [ 0.001 0 0 0.001 0 0 ] /BaseFont /Cmr10 /Widths 12 0 R /Subtype /Type3 /CharProcs 15 0 R /Type /Font /FirstChar 0 /FontBBox [ -44 -250 1009 750 ] /Encoding << /Differences [ 50 /two ] /Type /Encoding >> /LastChar 255 >> endobj 13 0 obj << /Descent -216 /FontBBox [ -44 -250 1009 750 ] /StemV 0 /Flags 32 /XHeight 453 /Type /FontDescriptor /FontName /Cmr10 /MaxWidth 1027 /CapHeight 706 /ItalicAngle 0 /Ascent 706 >> endobj 12 0 obj [ 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 333 277 500 833 500 833 777 277 388 388 500 777 277 333 277 500 500 500 500 500 500 500 500 500 500 500 277 277 277 777 472 472 777 750 708 722 763 680 652 784 750 361 513 777 625 916 750 777 680 777 736 555 722 750 750 1027 750 750 611 277 500 277 500 277 277 500 555 444 555 444 305 500 555 277 305 527 277 833 555 500 555 527 391 394 388 555 527 722 527 527 444 500 1000 500 500 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 333 625 833 777 694 666 750 722 777 722 777 750 750 722 583 555 555 833 833 277 305 500 500 750 500 500 750 444 500 722 777 500 902 1013 777 277 500 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 ] endobj 15 0 obj << /two 16 0 R >> endobj 21 0 obj << /Filter /FlateDecode /Length 68 >> stream x336S0P0 F )\@>,́,# .C c0mbl`fbdY 1 r endstream endobj 22 0 obj << /Filter /FlateDecode /Length 320 >> stream x5Qq0 54sۿ @;a@dJ\UGM>`!S֖{&UF!}W2j]* UYFp&I8d Rӿccz endstream endobj 23 0 obj << /Filter /FlateDecode /Length 317 >> stream x5RKrC1ۿSpΘ}tj'+-@B./YK~%ۥW%B>R-G- Q=2'":xa>N)x_xN;2$KMH=I+4t~&+s{rj X+)$=Hr7VސWg%&&MܕBXtLX㰄*aՃM5fcdxLP} #GMv²[6!D3,($Nc$ Ұ9 9e, mh%zМaמE[{ endstream endobj 24 0 obj << /Filter /FlateDecode /Length 338 >> stream x5R9@ } ] v͜~߆_ CVie!U-.Im W%ڥ Pt,6˯JH+kLwIi"Eo7o}=@.^ AS(i|Ъc(ew 4<3}(~_K&(? _osџa`Ś}@*z`yT endstream endobj 25 0 obj << /Filter /FlateDecode /Length 49 >> stream x36P0P040F@B!H Y@8&+ & endstream endobj 26 0 obj << /Filter /FlateDecode /Length 90 >> stream xMA "OPDtz_NE5jK02kP)U0\ 2IL{qIqzz"X endstream endobj 27 0 obj << /Filter /FlateDecode /Length 210 >> stream x5P C1g dVukm;aBXȔy)K>:L." u%ʚ +`p&^7`i5tႦ.B%|u{OxjrvC` jMX> stream x36P0P07W544U022P042QH12443s`9`a$r`Zs: P9\iM8 endstream endobj 29 0 obj << /Filter /FlateDecode /Length 248 >> stream x-Q9AzBsˑ C :-qPO+Uwu9HTM]vf5,?c 7zqxLu5{kOfP2+qSușO \ ȹeƌ#M!RH&3AQ~#aU#j \Ks4;<9GW +ET<pC7ҹ^s0XM7/=[ endstream endobj 30 0 obj << /Filter /FlateDecode /Length 247 >> stream xMQmD1 \ky R]oC /)%K [UC?13,=?TPbht/"+ߏe s`&4`oI&ռ3d‰ATwM,3V7: lx%D`r Z`Q+ tĺv7C/਺x} K{,|BL;wI#fR:=b}@e+ (\* endstream endobj 31 0 obj << /Filter /FlateDecode /Length 80 >> stream xE 0D{`~&f( JpO{:2Sa ,S`5FR죰n_uzS*Ovvq= endstream endobj 32 0 obj << /Filter /FlateDecode /Length 392 >> stream x=RKn1)@Mr[T /1 %?ꒈ3L~r]Qljg!.6Xr_rњbO/ȴTXVݣC(-װr{d`Jn@CHYAaPl( WԬtb ) ٠[]aP[[xfޑ3qYk?=Q2QMg|2RCgB'`$Ip#A 1qOl)V;ޒ{,\L'ib?lK\+E(~Aq|XdDw#h% 0xyDhDԎ=(ͱ&{ǫvzcw. endstream endobj 19 0 obj << /FontDescriptor 18 0 R /Name /BitstreamVeraSans-Roman /FontMatrix [ 0.001 0 0 0.001 0 0 ] /BaseFont /BitstreamVeraSans-Roman /Widths 17 0 R /Subtype /Type3 /CharProcs 20 0 R /Type /Font /FirstChar 0 /FontBBox [ -184 -236 1288 929 ] /Encoding << /Differences [ 44 /comma 46 /period 48 /zero /one /two /three /four /five /six /seven /eight /nine ] /Type /Encoding >> /LastChar 255 >> endobj 18 0 obj << /Descent -236 /FontBBox [ -184 -236 1288 929 ] /StemV 0 /Flags 32 /XHeight 547 /Type /FontDescriptor /FontName /BitstreamVeraSans-Roman /MaxWidth 1342 /CapHeight 730 /ItalicAngle 0 /Ascent 929 >> endobj 17 0 obj [ 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 318 401 460 838 636 950 780 275 390 390 500 838 318 361 318 337 636 636 636 636 636 636 636 636 636 636 337 337 838 838 838 531 1000 684 686 698 770 632 575 775 752 295 295 656 557 863 748 787 603 787 695 635 611 732 684 989 685 611 685 390 337 390 838 500 500 613 635 550 635 615 352 635 634 278 278 579 278 974 634 612 635 635 411 521 392 634 592 818 592 592 525 636 337 636 838 600 636 600 318 636 518 1000 500 500 500 1342 635 400 1070 600 685 600 600 318 318 518 518 590 500 1000 500 1000 521 400 1023 600 525 611 636 401 636 636 636 636 337 500 500 1000 471 612 838 361 1000 500 500 838 401 401 500 636 636 318 500 401 471 612 969 969 969 531 684 684 684 684 684 684 974 698 632 632 632 632 295 295 295 295 775 748 787 787 787 787 787 838 787 732 732 732 732 611 605 630 613 613 613 613 613 613 982 550 615 615 615 615 278 278 278 278 612 634 612 612 612 612 612 838 612 634 634 634 634 592 635 592 ] endobj 20 0 obj << /seven 21 0 R /six 23 0 R /three 24 0 R /period 25 0 R /four 26 0 R /zero 27 0 R /nine 22 0 R /two 29 0 R /five 30 0 R /comma 28 0 R /one 31 0 R /eight 32 0 R >> endobj 37 0 obj << /Filter /FlateDecode /Length 497 >> stream xUI[1 D>/`@4^u+~Hv,Pˆʹumݲ_~ #DŽ˱1mi1Vgڪ I UkьRqq(3љu>ʷ%(y^yOb[P!CL^b/L#kYU$1E ÆuոCL-=l`vY԰S+c3t3n|:{gFm`unTv>i>}D|ЛQ M]]tLR_yKa=aoO5xVmР]u'JD|)ؽH ԒEs]$q䛬#x"LJ^V^:-Tҫ)J0/a*>5?BOesk9P\7Ls=65 9 f6rp.o2( endstream endobj 38 0 obj << /Filter /FlateDecode /Length 550 >> stream xE˱\1D7 *3.4W @-+^~q=?ye4{t9evQ\ƴQ\<1,cڱ$gXrbzsSӜv$I@ڞP#kV,lt )Aw'G))?~SD}#/4˩Y]xަ)dCa= WӴ^ ZP^B\T I]/zWWdĖa_ڶlv}U؀_w,38;#q+~cR+ޤ :QNv:C4n:<}hS{ RJu1"VTMzY4q:Ze|,MM)TK0Q {r#6l1ߏH\} endstream endobj 39 0 obj << /Filter /FlateDecode /Length 512 >> stream x5Kr$1D} .<31+~I(J Iֱa3ˇ_ 럳mü|)~tu-zֶ\g=m<\:%7!42^2eq9VgޤHⰤ,RNYQ`&u{SF"Ro@ qB(ei`lvrD[reyƶ:1;UJ\4*'Ċ=ܦBKTbQ/MLΕŻ"lKHqz 6{v%x4>f%rYWb~={_t5FoBSE*j u+Ke۸^K-:Otr,Bz!Onf~Zh%>rL*jr+2@){hRT)6d{}Bi5?'^01+!IPw>m2Yz"obWP%% =ى yaRzfPMT{[9vw2vNϨ]{'=FviES$jIE endstream endobj 40 0 obj << /Filter /FlateDecode /Length 145 >> stream x5C1 C{O ˒esR%,:@jfގQ|#›j"LaHu%#{:{rugi`E3"Xzg3J'/ncYi!qS_9+0>m3; endstream endobj 41 0 obj << /Filter /FlateDecode /Length 446 >> stream x-I1D} .1h:wxe뗨PJI m|XmUic{ڿq14W}5 c eϲ(nԦF YR)Q_1{Zsi;Ts =v \\9-Ol:lȱ|rlp9w'd\qEJꌶb^Jq\8 'WB?2AT8{, u9 Z|hU?v. pUCUCJ ]O:R6BGii3W[W^Φ.R''uA\Ѯ)龖JBJM:QSXwFl3yE+SA\//_7Z楨"-OG_F endstream endobj 42 0 obj << /Filter /FlateDecode /Length 582 >> stream x-K#9D>pS^y@$Y5mX}UO=}ߧ7|CŸv(ؓ\zy|C$bYm1E?bPFp*Kd ˅-UK~ݖܪSvjW["zkOaqsJRD1WzɼcXd㽶sx[uOw: nLܕdqo8Cqȥw0c0e,$?M^G(m7چ 2ۧzHPj6ƪ/nl}M!r6;"Oveg,C>O|;@9l _?urݒqVK!A#ۣJiC [kˁBwWO2EҔt7S]jI>W5W/*pziٲDȓ7pz*hl)AE _,Hqi|vHHMV{2O5.w7m ܺ!}/u?KH!"#u w}w endstream endobj 35 0 obj << /FontDescriptor 34 0 R /Name /Cmmi10 /FontMatrix [ 0.001 0 0 0.001 0 0 ] /BaseFont /Cmmi10 /Widths 33 0 R /Subtype /Type3 /CharProcs 36 0 R /Type /Font /FirstChar 0 /FontBBox [ -35 -250 1048 750 ] /Encoding << /Differences [ 61 /slash 104 /h 112 /p 115 /s 118 /v 122 /z ] /Type /Encoding >> /LastChar 255 >> endobj 34 0 obj << /Descent -250 /FontBBox [ -35 -250 1048 750 ] /StemV 0 /Flags 32 /XHeight 443 /Type /FontDescriptor /FontName /Cmmi10 /MaxWidth 1000 /CapHeight 706 /ItalicAngle 0 /Ascent 750 >> endobj 33 0 obj [ 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 0 622 466 591 828 517 362 654 1000 1000 1000 1000 277 277 500 500 500 500 500 500 500 500 500 500 500 500 277 277 777 500 777 500 530 750 758 714 827 738 643 786 831 439 554 849 680 970 803 762 642 790 759 613 584 682 583 944 828 580 682 388 388 388 1000 1000 416 528 429 432 520 465 489 477 576 344 411 520 298 878 600 484 503 446 451 468 361 572 484 715 571 490 465 322 384 636 500 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 0 615 833 762 694 742 831 779 583 666 612 750 750 772 639 565 517 444 405 437 496 469 353 576 583 602 494 437 570 517 571 437 540 595 625 651 277 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 ] endobj 36 0 obj << /h 37 0 R /p 38 0 R /s 39 0 R /slash 40 0 R /v 41 0 R /z 42 0 R >> endobj 4 0 obj << /F3 14 0 R /F1 19 0 R /F2 35 0 R >> endobj 5 0 obj << >> endobj 6 0 obj << >> endobj 7 0 obj << >> endobj 3 0 obj << /Count 1 /Kids [ 10 0 R ] /Type /Pages >> endobj xref 0 43 0000000000 65535 f 0000000016 00000 n 0000000065 00000 n 0000018286 00000 n 0000018169 00000 n 0000018223 00000 n 0000018244 00000 n 0000018265 00000 n 0000000216 00000 n 0000000451 00000 n 0000000344 00000 n 0000006127 00000 n 0000007116 00000 n 0000006920 00000 n 0000006632 00000 n 0000008163 00000 n 0000006148 00000 n 0000012109 00000 n 0000011894 00000 n 0000011493 00000 n 0000013162 00000 n 0000008197 00000 n 0000008337 00000 n 0000008730 00000 n 0000009120 00000 n 0000009531 00000 n 0000009652 00000 n 0000009814 00000 n 0000010097 00000 n 0000010235 00000 n 0000010556 00000 n 0000010876 00000 n 0000011028 00000 n 0000017037 00000 n 0000016840 00000 n 0000016513 00000 n 0000018083 00000 n 0000013343 00000 n 0000013913 00000 n 0000014536 00000 n 0000015121 00000 n 0000015339 00000 n 0000015858 00000 n trailer << /Info 2 0 R /Root 1 0 R /Size 43 >> startxref 18346 %%EOF trunk-2018.02b/doc/sphinx/fig/simple-scene-plot-4.png000066400000000000000000001224551324306050200222310ustar00rootroot00000000000000PNG  IHDRX'sRGB pHYs  tIME 6j5 IDATxw\SǟL![ (XZZҺԪءZVQjնXmۊ QEq0- ad'?B#B2{{?AA 2J " ! " ! " @%,gbbbAA8p%KfwؑHP,X@P A ÇƖR>},YK.7b0'''$$ãvǎ}]tttݶoߞc?ϝ;/A9<]][23ڧnkG]7RzSJL:KMHqqqZ&qX,EMk"7kjC jd25@z:рNo )DVVs]v?m^m| A:7#W VLi2I_d7#UH$rvv>vؐ!CӦM333k&l2oopmI+ .bh 坯 Tո{m^ l޽{xBKc2dDQybFŤBvٳKk XZZא/,CO>q>/Q7oޔ^2]x[$'L>Q#<SSSN0aӧϘ1cÆ gϞ,fMW|O=<*O,۶m˱c]VTTԫWׯ_O2{[n 0`ƌGxa^ hbРAÆ #H2fss󒒒;6?<>?L"ӌՓ5k`iTW)nnn}Y``7$-R駟͛7˜?~HH˗/ƍÇ'N|oԨQpiӦ%%%59í[g^rЗƍ4*ʫ?ԩS>z*$%%D7o~Gpmɓ&M}^ G^|> 9bkk;ydI{$p8fV`D1R[%͹AQyaddԯ_̯bO<ر@ H:t++˗/x1ソM5$ZZZkTGC!PٔnZϯWCQQQF@Qyy7uo234hhJ3mP*D5*#h;$'cmЧmF7ԣ, hB|~?4 Fhŏ4w^_#}̚5KS>|>k](nݺuʕ...q1`ΞX[RilYCDFXXAM ?995C@۷oݻA>e\4-- ?卌͛JB3Y$W& /PC,XPyT@N>}m''q=|ɓ'mu7"^~ݩS>}m۶˗/@bb3uzB@7A"''mٲe۶m2eʾ} 66'ϟ߿?rrr8q"$$$X[[={655up捥~a~~K$^?32sx t]2}嵵111 v=z說/^|qqqӧO1cd +,,={vppp\\fG59nnP(@ 0j΋F{8˖{T  ]QQ9l0aaa)))cƌ177/))ر#Ԉb=a3@09*# y$ ̍7'N/ !!A;ѣӦMKJJ:qԩS b k*!!̙3ϟ?v,_sss Ƙ1c-ZDbǘ7QQyo˗/>|(hoo[[ɓ'?sp8J&''ޟXPP`0dI&,[?,YҼVF|}}+V 2믿fX?UعΝH\_2SiK8osժ+>i/l6`H$6-Zr{FvUDNNNHHGmm;滑HSJ bFǎ|&r8e˖9::/p1S{֮T)%)g3ϚZ+T&pTX|dhTW5$I$m޼ظ  JP~aԨQݻw_dIzzzuu= O5x< BP$t>k|}5x|^.IS)t5a2d<*drhhmoo9dXT*Xϟ5kVLL ۛ>|O>;|0TUUܫF#HC *ʫ ɠP8p`ĉtYu&LЩS' ]v.^Xj~ɓ'O2kBC ?y򤯯-,,LCBBB}.]jI!00P:s!!!үz_ -Z$}OTO|!՛wzFTt"/ C{1]LWEڋDɓ'KM 7n$ .y///LJ;wJ!I:Ry=zҨQ eee_3gίګW/WuuuAAA.HVi&% z2h;up8[D͠:<Ų3JhI+ .6D?Snn噼?w'NXU"KKKϞ={ŋRq4fI9:37n{R#!m۶;wYF,WVVVVVD"hl%%%ǎ{eYYYbbbttĉN̹6o $i…$_~/..rFv&G h^UUP uŋA-/Ws>x.;KKKq= 8X_Xp?9w6!vTT!PyTA#40DbQB~FHйBadd$F@QyP˸w_)<{#_!E5BA4BzUB|~`4 M^#c&A4B,-aXXJNVG޽{5*#GFЮ-ΕF@QykWP%1<F@QyP01:E%A *v e#TGCٳ(*# Gл7t耗 Qz:ŪU<~rӭM=]y. z:[ëiQJ>3 AP=Q0~ӦMX@G4B-F,&p6a3dvZbckTGFÇ ߔa:XXG!Z&* [1UvQPW( f1Q'c(bjiMNN<* &mڙEMk)mH8䌊 RIKK2<*j7;vj6 :,H=)dʭ[e `,mʣv :_O[OŌA`22yt͵}:F@QyD 5Dǎ!_ȷ5e5Br5Bp8XG4B}F]uN qҮ)s*sڕĚ5k4*#Fiʂի5ɇڵ?O1v]C6n܈PyTA#v33]ɬG F@QyPGv_7^ͽ "G);5vF=z(ʨPyTA#?ZZͪ4Mie=^3XZZb kTGu})IIj2Bn9\yI%PyTA#@af5ܚsY\,\Iyݝ n_#$HK2||A5CRa3!g7O[w&:vp$QXjG4BB,yvϏW>Ĩ6#4*@TTF@QyPn;W.#̋х0#l3ݛc2IDFFbjTGu?ÇܫWRBBqJ( B tlWKW"k"\}Ak:ѩG[)^vO',ϔs{biTGFk;b:ӌE!S{Ծ}],N}|pf@QyPllԜb#B=mI23QI*#ф3g<ڵkd }ݻ333dwXXNjl6hMJ Wl.]dꋪ6mٳx?T7dĂ1p%KXYY5ߍfر#11B,XB bj<w̙-  ׬Ycmmoܹ[NW.%dG =w|UJFk_MLqqutժUxOjT7drrrBBBbbb~7o|w2w۾}{nn;"##oݺ5±cGZڡr̙0uԵkj-ZdI t0s 6Ž;vʕ+|>f_~}Wyû 'x$yr䧤Zʷ-lh?,J7ٞM"zn XbV`Ν;&L *))^ƲD}[mo5h'`ӦMxjTG8p`ĉt:WUUUҡ'NĪ*3Bzzzݻw׮]År%Yd_ȿ]xE'Y^.T$V<*7 3Mn3”⯿{WNIIiСCOv@J􉤤͛7DZp!D_틋\.XXXO7oLӧϦMT]tgg!.C;vw\fl}4 PyTސpDZxOt{{{P( Rf"6D6*5µώo98MF\}BQFҒVZ ӧcajTGuΝYSm!Z#DA5dɪNFE|%O泍i-}pd.bEB PyM#Rp|%x:; 2\3yrd_[aMu Z^nOPyM#hѫh*MyCU ={>{)7nĒp6!q<"Ffϣ{Mݛs6")k <5B=UͫOO}nft.]23P0/~HAbP#ydW:'ϞG=z((SgsI_}_k$fnX\lgjժRD5*H>BʼnϏ*U-} k_Or騼@ciCE .,`rhsRk^3'F@}JPA#DZ$WRS2ypߪ\},euJKKLS˫}]nT^SM^Ս!H-EY /F('QQQX1PCB@)G6nuvs@K-ݞ1e$e[Y@PyM#hס*R_?ՖSq$u9u@4BEBwiWP=MhLDVFjd/ w^/{}E5*`p! PᒁYKfJ,.ȼW>>>^rj;[=GFH4ݺߪ K*Yٷkk;S2G k8`QZ/8qcj)7þ^pà2H@ժk T^_IMMᅪ %%e˖-| JpF#lb8+dSEIbMvoeqwwm D2T^#D5*t]0`WWO:Ci}ǕCVښڪ?閖aBT)T M@ ؼysYY~gOF#T#ǎÇJD fl>d,zF*qPy}eڴigΜ)))x-s7n(( Yhфʌnv7fq]̾hϓ Qu Vy+Ɵ;w,hdd!8-}]]]PPPuu9J6(l A$n8"έDb,,,.\`jjJɹ\w]`F (*EKG or\_G_A«W9K.hSQ_Ѹ\qFh45CdG ^#L.ϋ2Ft޽{/^=zubL<2 DT^_133 \xȑ#oܸ{K.cPYj5J u*yy`l\IƑ*fs_ ݔ *QAf$''GFFܹ秦z~}JqW> 3(+L&an(*j= ,ȴ3B<#-S[[ sa2=ߍ Mm# ~yF5mofה9Ӊh^TU3<=>߿p=L=5B”GPy?^\\rsrrƏдyP桥  6on{7{W]$F16>'OMMM=pɜ1cF^oaӨj(,+WRh2aR7VJ>s8J,^[ ~ :alΩ|]\t(P5*ȁ% >|ٲe֭k삭!Dwm}""/Ij۶7yB'J0y~ͼ;T\yGډI\\9s>|ؚWUU:u* k< 2Iv?*2../FFhy&0d9})Sm1 )))!H[qq CaSLK~޽{+Bp̙ϟ_vܬǨt̙M6>}ByD+ y<Ͽijj *Ju{Qn](EdѣMNG`iȈ R[`\c(B˖-{%K|G ekkKPCbGBtvѣGwUtۿe?bN7~D,ڵ\]RSf6嗠Rw \-]-3\1L&n+67y\cAu6CJSN1a!q7|3qD__ѣG {".55ٳSL5jsܻw.^X )>?~x nSu4B:^0m2HIU>N9sT$1ԣUy+r]>@嵁6Cڇ=zdgg']ܒi_~Ν ˗DP(j3)Uꆄ M6r\%#nK\<} 'Nl[qSGniiq/ f͊g8MLLLGd|mۆ ֭[j`ĭ.;l0cccm/'$mESl #FAg&*7ߙrZzҌ{Ւ NWq8N jR!T^W0aBN,,,233wU[[+gkzeiiի;88,t5BPԜ8qwNJJ"W.1t'OJㆅmP%ziK_^tIy!!!ү6ld2%馾J5߸_tmmȨGE& DY 6mҔβ0~]6fσ" {™cY I 81cƠjK7666<<|^^^///LJ;wNJa߾}o߾ ...-){B۷MMMY,N4hPyy/QyQxxxK$_.\l׶ Ŭ┒I!3/Gz:<TIq1#@kht=Z1ncђA1s8qcǎm5 jkk?3gdOjuvy\ADIrʸq㴿:w֞LGa!6)..6]VV6k֬LnϞ=+**2Z"ܹ\.1F(R^ʾ]-#|H-jK#d㖨dWzZtqT{*6رc/_,++KLL8q"U@%?d'DiYwuuŊǏg6=$%%͟?&H .\`υBd޽{KsB @!Q_}B¾.f_ܻwI,] yyQ#ww6ʘڐ.kk޷'Oۿz֭[AAA҉A[/lg;ʚkkm4x>Xݿ<:>\\?OzgT?%6GT*4tjNNN)))vZ,0F3,'BYQ5t)!.i6XSHS#\dtiAA!"~nlG^F;ZUFh@( b>>>  Y' >8QYYз/S7'mS6;üVɲe/}]:f˫WyF|Ө.t)Q==,}&x 6S[[ sa2={D#l΃{XϩeLDSt)wBMͤIF-!F%#*7??8qɓd29 ̬TԨ 㺎{="8bPJ"Xxmz;,u` 3F+fX5'Ƞ28Bח[kWsB#*7&OliiY[[z&9cƌAúGк.R BJz[dFu~w)REEB HZP(VY\ր0W1-*Tp33Ç>4jkRF* 7 /^V G"A:a4h *vU9 U*1ZXۛgg% F^^Yج P-̞-]fV!R#8=y;8%[%H`e%:tNftOcMh&7J"ȟDrr274o֬Y׮]@#T;+;+)t_>" }劶t 6^ݕ14"jBT+7^#LKKC#򆆉I\\9s}jPFT/|P]~pډilCh7n_nmO!h!,mpw.ojյSNjZ_GKp8...?crr@ =zQezH$4 g @𙰰LNcߑ99jUE%SGl؆O駪6bTܺeD5j%L-֔nJzAX\YY)S]bx)Aا IDAT6W4c&/*s** )I5a2%C#oHmնԄPϯ7zA[XZZ>1;;; kۮwWڜ&+1u4הH>}z'GGtNWoJ20VBdb/y>={?~yyyBB¸qZPuuj"Js'YNA$zowg5VFxu4M$ƽ]!k׮9::Ν;7<p5lQg yDKKK8u ]Z9p8 )SgƾZZZjmL{_GFꠧ/_ D*u|P'rT8|XmOg1k+aMBaiϞ\gZY 쐩@8\NѝwGʾ.#q#hjvm's.]4W_)6^10._g6YyBΆr  t}щ5B0cm;88ЫG{|N^{sRv/o3uk^277ZYYI6D"={{bŊOrÇ?>y򤯯/ɔ|ѳY%?~/@I ~W.] .00ҥKkwIWI L&qaaa6lhrI-OL@iǧI t ]t6f~tV"ݔ7IwU/*蝃u6I7666<<|^^^X:HqqqDѣG2GXb޽=zP8nܸ۷{{홯 677}:wpг'P(/CDbQ1Yv_?g߽:oȿ?-LІV|bcccCARW_\ 74s;]'hYkeeiҮ]lױQ5X, .<|䫲ӧ899^gϞT*UZ|nB=˅i`f?D.QtAht{Zi ~hM\P~7Ufy&Gi{ӧ!!A$hZkǘfܮ\˽&c[ʯ_5|~/PQmQ*.dٜa:'X,36 ]׮EgO)Opβc461\\޾6>vUI_z8lu(heV]͍iyZ]6|رѹ &#LJJ??L".\`ϟ b.aq~ZjѢE֭S'MpB￷F|+WZX"FHKnU7*MEfQ#gQ)SR7o6|95xfՕ|n$2~$g5>#,,-444""bȐ!f"<D0a„ o~z}˗/׈$dfBZܸѴIjkm;W!!A;uo݊!+D}ޟhhL?6BYM㻍5myϺumtM~~_H)s_gSuEH7n܈+w"Ԁvqr;fntDvll:[tNLWoɓa> 0,(0ajwHn4!("Ր?*<AB0Ix=OR5cϏ.\$MIy#̘׮8๘\!E++\9H2DqqH,lAqDͦdS”)? BXHCBhhГ!@jA* Qz;&U$NYC遙UJ-$(V@ .4ۊ<W^}{(E!n!d"ц?~@z5_UCB 8{?aM<2#wSk޽ZC}Fmkń  q AH5Q8:j$1jaG@I ,] :_~ͧh<`9>HK*<ª*ePԊj5of<} K5Qþ[,O 2DB > aݜ}Ӑ? *<P=ZZndj*x~ѭd(OC)4RocfS軂;HJkkb+V`<=[@c#}ׇ},+ߥ\J <i>}0i/ :oIy &JUkM~1&Dpv] F ^¤C+C]}srE;u\_ G"„„nX!(JzK"YbfNj|GG=^SvZi]Ƥ$Bl='SS8xt}08p"4D,Kً(s>05,;zFěfPy e?d#D$q822($9t"ViqDh8 2Z 0(-Eė_"!I5Hq$ܢZ)Z+ "Bc#ܽ j"̭νaXv2|n-,Z {D-O_?T@=m`Сp!\խnݒ-9

    ryx+l~=\ؿeʌ!Cߪ TUirj) MHL#B9-LM[p̪ZnFf&f7kx>@\ayG渗s,LCGRޅǃ7TO*n=>l:k(̳l2qq7NMb̴$†|W|e4Z4qaCxE"mFQtNZBSQa%ur6oݻ{E/H$(9XaJ ؁~$GhIjrwʎ3/-ϓ/p'DGKfQT2/k4?/)Gܶ6O*IrmS<_G$oʿ}L׫=ƌTXFa@0wyQCy9\ 5 aI ^,yd__dm[L_}%r(q k&/pln@ Lh4$%+kxryrmcmXNm>} ^&`NIQ%eeplׯWیV@^`V3v&vV䂏Ε: PH,s J%>sy!;9{Z{>+~ֶǁ^XUPPl6Hdؓ/κ- xH+BD)tNΟPݏwKPx{A]PkBeewlh8p4bՍ:2~v~Y,yφ{,Uocό'ri65Ugm]" TU!i$:':2CA6 ~ {ҥpLlDU^pDD:)(21x0xx@HAb"zLӆjDmͨgrhyÙ߁//5H".D +6gt 6)kΝ>S^5BZAp0.Ӧԙ٩=Ąd"-`ptz^} f"zfN\y{{WjпƈχblMkr'y øD0Ahh=ڜsHI7N,I P1y1ݗچAv6> ݏ֔an8w*)/װBddV`; VV`zݼDQ9FQ.+ (jDi|>8:E:8[-g٩gID<}q D"`:(,TDD#j51Q?^Z?FlfvìP;;XRRM˖aj7j$4ڥ ̜ٚ<(DJvu6rc*o%JERٳ O[X}{uRIBP3cϽ=׊P,#`x8%ӳ`ar2L kbc3+ЧqD݃م""H))w목 IDATx'RNyD!t)\#XDve& .AT޿$W\[-ܺͱ9;vMr5f "B'<^)%\yMVɄr{ٟ** 8X֪!!BTlY[4&/n99/9s`toÁӧA+$8v, ^hp"钄Rq+W*ӧO\c[i(6־*{}ر (2LR႞ *یp!h@R XB^M^I] Apdu&M|}ŋ>%PemϘ>}9&NVF7f9PMF-Ye$77 )S遟D7`fp 75Av6ܽ .tܹ#a@%60VԸRL0pu5lo`n66Яܐ'Ixrse%? ׯc)&2/] p0>?>:{AFvRKG v9Q{s{88@2H$$&xY{Zb(_Ar bUH˩8Y8)lppN!37 H|[BL^L_4ʼn{P㕞L*H9v ZUtb^K琲ُHg 5\K%-}^$aVy_лo)8ƍNٍ={BM A^O?xq+B NYZ=2Z=&N.W4PpEh(/6֮/ `a Wu&^ ff`k ))a$ݹbҥ ȥO0R+/o}I"LQ`>]@&}#:@s>os}ʆټa  k8?>Fgћj՟b~zhᵅ-:od{m\S OExƿ+ӑ#…V?7 pXNS/n&mk׼m9tka;Nc#P(WAWZm:u++psC-1B$Ś~XVVV7nܠh`0hSyIƢEй3ڥ< ر Gy?+yUU^$, a(ى#2/*Q.N"ӂm#ZDpFe}G<|ےr-X@1 ݜngf'0Rr:Vr Ȍ6`A#YO>~O,D"89aς65ZPZ* 1>x{* C[Uq{ynoa_&$ȪcNQ (z> b颫qo~pl@aٌNQR׮@:^EBA&RނW[S;oп}7{eC%Ѿ=@zڅ4ŨQ0p_[~7 :Y¾}*YGə7l؀ݧXw"S Wx -=:[t~e'N[6(5kPUII[^B<X<[իW#s`]gme3աUoyA!Qr8Mt}\ߩSpv-?eea2aofh`8p"+Rƺ@"DGJxHFߗŠNNNˠ 99Y䋯/zёeePSc´*NzNn5]WCY!q[";Ԁz|DY<e`2@[@m]vɿH8?6djPѢ2:}:`NEHQ4:!@hrZ!1Qp 2LNN0i$a@3+@)h$TVA!RPjn*WˢnGK;!&^q-WrFAnNO9m}N8TA,3gl.\np*ԑ#=Ǿ}ϭU BM&pJPhhj$;&Q` ӧ5LkwTZtꉰ'$a'By/XktǛZ3OTcs˘kE6""ъUį>>s&곾t)8N93F"p1Jr-?J LTV*[ڌʊR[Em_ng?;8;W#DAB5${&γg:'BTH#)$Ap0<{z/i֕.@(RG 3!pd%-{9R{ uo}jbIEHj>c#3#oN8>;zDz89 ڍȯQog|l}t{}VxXyw\.]R$-k7o`؞N=J@[(f,[of&, C[" 2RᨱMj A/K_$ '^yoVE|[6* VEKLW^`΋cve8d(-UϥߏC[rھfՊᄐntY\:LE^ZfY͇ 0i ''_@i-*<h*NU~Z>Ѯem߾>>\>@?X:C ֛׋0SfISuu _0H]]!j <i F@ %G+C?fLK!@3Fo+/}>I7gуղpDhx\ڣ=LYr͘zaj~M=W/]~ ]-]gjkwrBai43aU˓G\~\,hGR~6X??'MT զQUH*#5${!!8ODhhW˗Ä wWU-&kPZ伒Oʰג~.{`bpqٳudy?zuLb䤓0d>E"(*RJF"(&!ݭqH0/̋"t`cx iW69RZ($ 1={4jUԪ":*(5k@0qSXXP j 8Mv]qn  JJD`HBPڶm-0Zy"izjo!*ʈ-~Gtd [_s6 ˫u['K{ets趼re}ηS+=:˗ՇjUpD(N8$"/ٰۘ732Z^Q(G9B~Ba,oʫL AAG(LЋC Jk)ylsgЯ%8]xUk \BI>g+nݚR(Xz.ܼ%m֭S]N8#!A L$MH˗p.85B-,}I!R;v׷مBHK‹cKjp6ضM.)36/ЋCP{i7k۲yN&*N|}a4"ȼiy]4 O', ƎUs3$%U.#N8 ULjŋaV=\7xcbx !9!$**9FWi i@k"ɏdezu"dAwQ 8wrn?"&@z`vuv#` 5e!DD2?r8 M 'Bm>z}bIb{{,0}fuq?SC++tnyQssGGͦd~.#/ϼgjuxTpT2U:YH ZZ*nA oiI"ؗ#e־ϻtrQh*t邲l/ڂWq*j[j(..ի DE*_uJJߕ+WN:UAU-H.UA0Hc9Z8bxQ-öPA5!ܘ}CG'ѣG}@y}9ô5Űe&RRܼ }BWr3`j*C"i~~~~7j.]io7r\+U A`:!1H,I^oL.п? 0ݬQz%$")}ezOf(^by+ž ">?0eP4NHu--ay@z`*#ۛnn'=m[>/п1sa>ŽklRHi%%-a#**ٳ֭;z(߲eB/$ѯy#Gׯ_ލJ`';ch ƩSз/tQP.͏4CQ!!zF,Ϟkׂ;J]N`~+Wi uIB1ty4"xi l)dV6T[RW}%20O(;bvv>dpZ3gԩh~\2cƌ}ڵkCCC|ꃚJ{v횕ȑ#̙Cy͏;%~WnݽuL~. 2220.f1ڕ٭4+o&YG7r^@2%Ͽ(y |3=PSSU=ʚK؊pĚeA=z]: 0&Ƥoz/Ν;Ǐ9boo?q?X]D8fggg++/ukl#wBC lzvhѣ݉ 7#zdkv ³ VdMODx-Jѣw>jԨBOk66ks"6)^)ljU- _p )af$5תï%'? l%&By˗ԕ8[&—/u% kV-)R(ݺ@H|g᫯eN¥?< }sνutOx律 Y|HbɋwǧSNtmݖe?Xy>ٱ^×D.ly-+)y^:}R%޿rr$9fW뗸_fuժ(5AI]Ɍ3#c0`@xX..R 7oqB|</W`XVVV7nܠ}G[l͛ #44ѣoϞ=aa]]]VVVqq1deeeeeq81 ())pBnn.?pqd2PYH@( xj(4Νm*My}y_ <)͂AwT@ko}{)풁N޽G6ON69_;3AU{̛'M?_ ;@ EB.ys&}IBPBB®]3˗/'ᎎ\.(J||St (=-"/߾.]ЕYǼc@#s Ʉ')&Bbb``ty745<+~6-pWTY9,lnI44FZkD8mBdVeysa?c0;{d,|f&<~ H6DDטXt;8g `fFm6B;s;DqphR6[fا֬HP4A3P_/v4f&cǎ޽{o޼\nqq12|C|ggEa)%3f0߿'3 5~-6$HYql…C8tF+bh1y1a좁@YY"}B!RZ .dtD(|}୬[\L3"k%s5jze*"Do^Qqcw+ww+Ap0 WT@q/k4F&5!nQ6nWKBU:ٰz5~j C@F*.e0"y\e2b,={p#Gt Z =BxCl~lm#YVRz{Sqi"^ 3$~n|D$և'c0~ʏAzH%R1H͋ܧi+97+ Uœ9/+nȨqzyA\p`fhj01ɓA{ݒyy% EI&)0p!@R#ۏ>}݁vv4D"#0M "^#<:X&J,H̫ɫk`.$ cK޼  dO||XTY!Cix`"K$gKk~*JX\"#v?~()oԩ&$-Ld޽CSS(+PSS+Rܷ7h.ޟR-'''>6+]3[<3άnƫ4H&w9ZSU <=ܬܰF׮'?dR5HKSVPh+l;$])}^(s]0K¢ Nk syԜqf}N'?[;2h36?6=suCDM @zRu .Dwe˖iCi~ |l}JJLɦF~ S@Nj_\by0{TMco/6WaE='IT_,=/k5y IDAT&N.VVhV?;;=``Ih_.guNutHGdclpAf[#Q|ta2#YzVuwF禝ᥭ:};dF[ٜqWmvl1M[/l-<ڏ=?v h eĖͮ]!sK+Aj#44DV1^I%mG?kͣyAAQ ڛ坜`qd2:s;<l7uk6PǏjL :=pzO-eqcVvUUr`EPԁnLp+p=fWy.};1QK$h<`Æ 6(bjD{< AUD+H 3l6tSO81_&U-)$*:|;gyӼ):8p"!JJN3l6O]MLZPH&P4F8ZӘX@?l~YwATd"yv((h޴s'ӄ!NFO|rj|X,;VBlM%%yA{ϔqdlbևTkKK:<6HW ;,M-MW-%&N80HXXIwׇ`jtD 3[D[bT5Tܕɐ |>?|HAڵ+̝۱:dI 4ZXy-s9h\ <<6#'$"a~8vC%BcPϼ4Sűts:Aq'ዯ/H|WN'80DABB^?LjpBB<̙3^ U*8u)-|{ Mf"l| rr?嵑/9`Am}B.ⲠOշWh%|1A}N''H͏WdTetkŦ 9hz`n=8DhTHJ(Պ0nU fd[oeVe@eɋQ1O^ݱI'[mqˆi۷mJ.toz~-HD ϩB*s!BTBfjuַz+} F1!vz ;w<=ê7xxac[3[ n N8&OgD&ÌWRM @=Qm6N0aB+~ &+y=5hBAFeSaJ#Lej8:uG|Sw>Br`c0S&bRQ f+B770p1DFUƽ{a4SVE4A 違 $uݽ]@0 H*+۪iXMr<^XĂcKers @=|l}]j=0OH=ZonCxy{)_^֒oOoffp0PM'_l4a.]VV{ɋsXfSbCSC#WO)Oz'By<*xp7[زTIh27&]TWjɕ{uH#p"qmI䒎wdh 4DZ9mAp=BA>xH_XyJr\' (jm?2fh^M^Hh+PNwUE!ATz76pz'Bmq/ :8 3`|!Q Ol>Ȥ#+Ӈ{7"َۚ2r㐍acy&dxLF ͊+ayO'/yiz% ZzZ{jo;YwV^|'g3q" ֶBL   =p/n퇡?>w#W]kP4  vL. G, k ss5 S?Q#T=5:w|r᪗*f\}Z)L~M9}3C8*@eCejEP8 a,W }a <ڧv_՞Q{T[9!]dhacfsyeS>SUX-L,jר=fB&O@ -SMH kq`q tJ rdᤐkq"D6Y1\tjb/\" m8##v($JҏD`Ǜ/lۜ0T z1#Rm(H~{U>yYr EY\VmcF{~g!3Nf|'K M̵kװjʹs;VZ^xW’(۪iE#|QnNW_6TLʙJo/ >񦱾ce  " lׯm|W/x~x_!Bpt8}Zt@ ǹIyd$'tdq`7l[<*xnl?폶P vFyخJ̈́VT/&B;s;}\>R%m]::nTFjwł2 z.;FiP$|pTN8ǗQ__͸YitM$#DT6T3a˾gDG}Dnݬa̓ U~_|}~D3/Ԩl ll߮xkN1ޘQ`on>ԂvܩiV(@$H<sQϭΝyi‘@(yizELS) &?f l`i漏(Z8t48kثcqq($B]%j6qʞ@rYp\XD[AA Okkj?,'a^zV^V5<2hyFBa-aU*|&Dz;2^}e =PZ7yRDsb qh t:99JLgftݰL$+J]d!1<=y^9 M w\)2h[HƗ-M ;uӎ!Y|edf]7yE ll@Py'LB1/:yxaXGc%!(حϋ8pQϊ>>ŪU DDH"T=5Hq"p5o|V EgZnAqV:#} uԸxu!((FÄou^x[D"ָU UZ^XL~ӢWүhzz1>}D" A4x`vDh|ŧ6%gEll|m}gf4Gg1QEwGx]D$;jkk],]Wn;dPm VZ遒0NF=cĹ@ |JSU:n݋ýkhpDyrR;p {.ļٙ3gb掄= >do*N\l~l*I㳳gE.\73]XyOL^L?~Z<5m[$.?azWZ5w+wk3.^Ŀ@&BQ\#hkIz`zeH$2!Cttj/8"m٤1P(-KL;Qe㻎7Whh @G⋦X8bKWy.} Y#^bf545@DjL^ &2(0zrQ-! Ÿ֕0...,,l„ #F UEׇ*dj ='}ر=ɔ߁dcǎ6mڙ3'baC<w޳gV}<<<.yBk3XCG(@ ssYÇ3ϵwbrL!BBP>SS;t3Cqxs5: Ξ=nݺG-[ew_p!* ZNPȑ#̙q,**9sAl<|P*0C#ۘ٬*l`"Cb6[o.dM(JtQH#w2\wf8 &9(By5Mqʕ3fmڵo޼ʒ!;;;%%eڵnnn}>}˗ ?@޽7o \.X hVڴiSxxҥKgΜ9fl/2:tЦM8ribbW_/˗/;v,޽`YfM:O>LbP(t ::@ ;vL_[~}cc/駟.\7,7Κ\7Gf' 8`ݞSJ'`ڵYYY>>>ΝspppssODK o8aȑke;X]tE8p`xԘ-/J;<غubŊZ33$'N\hիWq߿'AAAb+8I&͟?ԩS8Xbd333Cd';fwTky9sFKJJ:r@ LtjKKKĝUWW5ƌleeqA6_fp}qyT'$}2~?/_80ѣ%H$޽T `D"q=5*Ili$/MkI 5U+חO@Ps΢]vQT&BI_u>2ɱMP(LMMtx+++2zFNNBM%D"ݻ322~7e۠!OvL|UUGFF߾}ӓ&M%MXZZ9999iii'N4i>A˗\p!77`8p`ܸqd27ߟ1|>d͛7:󝝝-Z/j$rW޻wW_}E&G=|nz> stream xڍtT6HtH< )9tIwwH00- HH* %")t# )sk}ߚy}_ UC"`!i@Y,  88L`h8aEy(4֦AcqH"X\,!-$ I D7 'Ce~y߯p@ 9B. uVtc# G nYW4]ZPG)D>0+`NO€ 0qy#>0G(plqXSw"~u~?3 +ts `j:h_4A8BHl<C_7jK=OG) (3 ˪'e uĶOd#>?g' '/wAS 5ms1!!! )@}]7sr ܑ34 ?dJC|[*fzJ:){Iuo$R67SB]I6moJw>6zӻg WnlT͸JAPjB+s\˸1#ce ݻo J7Mso㴍}]d4/~%^ Ȉť8YCᎋp5QdHWu\ߦ_ĕ1SKu9uo?S%Nu,ՁldfK\9hӯ+gO^|U1Dt|C+}:6ơzN} 59y,΢UWeB7#J~ r.ZdZ}8Zh g[axV@@Ȩ͔X\|'|3f, C=%!~+ * $JmXw۩9Ϙvݝ͎+]%~:]^~_"uĮ'IӾ(xiXgTF<6^TJ]Z98tGz?L \>\1Xl .詟 ˆaZA#/DpA~{Vپu 3hW)&bh񸰃c^G]eseT驜!`Kce^*p񥧰\PΈNPS0KU>ͬx ^C9 ^`;ݗ !}񺄞y> 0o=\.ܒ}'3ln=}B0)pP$;5RT|ڎ8):*57{H&p l=u]w;aJZ] yx;&۠H㑁ԘxO(\.RfMԤ.ϡ5, 2lg"9E}($+;>36._kv[fF&ΰ8֛j10okz]}xCc7z DFiV.˃-KhPc8㵂أq(>5\S!28j9ܻ }dhԌfBvΦl>D uK)H[o\4A N wr5sPiSZԮtV`-{<)Hin]GY4ŽtJhw3J{sx}$3*Vmf)O=^7tOzP2mIu`UczkuKh|{ _|߄r 髧2}ݬ2Y7K^ah28Ţex Hk\&;V6mf33C kN~x6TN\6\tsӖ5?MBb4RK zśh_m^HHe ~J|uWhrp6ǚcjy&;J=67C Gzr=Vu\1Q~1@Rhd@Y ?h&6RA,4yfnt$G^ӭq/*Ƣ'5t*hEUnr`<ӲIx gj-<΃Kj55򹋚8 Po$eXFE H>TϯqQ/U#k.A~݋ѿ:̥o|zٹȦ^J^9xu<찺ֲ:ޖ$tx^o'FN%Qw#2kQ}{!I Z9+lo'bȦ]d#Ι>nRJIRROp4ǭK DԼ5e?6 7e/Tf_ϥ >`]:92h8PΝ>wȚ-Iظ}<̚Ek^~쨽ԙ'^ G6^`ӛ͛MJq<+0i&)2;~:YӅ!\|;toYw9LǛqIm?)q,ԾCWf1/F|L?$q[PN s?_k$~p&@kت3;cHɽfy0mmz dha]м_RD wBg+;iaŽ:uCb|΍%Mǎb9KrWIhR޻L* NW fE4 ~L"B=]/e5xNJ;>DbҝV?joM^VKcj2"3!wc|]bqm/Wrڧnϼ{j`k}dsÏ_ #yf#lL]B%zUeɶQZbJnrx)*o!lӚ(Z77}lmˀYtk?QAr&χ4 Db֕RE Fsݎ'/CܗҴW1pr⥅]_6֚2bg1s ߚEk%]2 ky]G(dz&y'la3&sOVc]gآ1}YDzKa\mvU䪱EˌPl O(a;Q:5ׄ)7&Wxl #>A|$wjUqX@[wE?[ގ..,'ЪۥćfrY$WwFE+$fIX>;6q[nmM jkqjIߺ2z&# ?&FK=83-8)uD1,=tVA]4uUNFO$)B2HO]"a4p t (]gʘ2@:3UQd'atטD}Ҩ3 74̥{4tg'dMWQ1e̓e=Zޭ|,{UOf3@ #geM[,LN;rWo5L+I=<oybkmxf8dPdAD8]y!,9^㡎KYcEYa B[μed_Sj(3֑{DDsM],~sܬ)IϓŋBf"P22o "*t-au,|LC sX6:V׮(=].$>D_wDp5mҚ2\*5'~z@o_yt&(z 1g+(uMyWH"q4jg򃆸W7UEwq^TYSh%n4g>}Awk3*.suG|kivjкzҔ8f0y`rō:.u> endstream endobj 4 0 obj << /Type /FontDescriptor /FontName /BCIDWK+CMR10 /Flags 4 /FontBBox [-40 -250 1009 750] /Ascent 694 /CapHeight 683 /Descent -194 /ItalicAngle 0 /StemV 69 /XHeight 431 /CharSet (/macron) /FontFile 3 0 R >> endobj 5 0 obj << /Type /Font /Subtype /Type1 /BaseFont /BCIDWK+CMR10 /FirstChar 22 /LastChar 22 /Widths [500] /FontDescriptor 4 0 R >> endobj 6 0 obj << /Length1 1399 /Length2 6268 /Length3 0 /Length 7214 /Filter /FlateDecode >> stream xڍwTk- E&=wMz$!Uzw&t( U4Q,"G|]ޕ̞gϳ 'D`AB@.E@NN8 #2 1W>U0 D=] QHB$)D@鿁H @tH̃SA02x t ]0 vv#!p_%x0CvTx1N# ~Q` pLo0r!0U' Ct(/_@BU @n(0p8]a}u! ##`WU> w?>:d_10Bp_ts!0$Χ G Wss.7o:D "0-?+|0 @( 0,IWw}!Dh? A{w !#AWn_X5J~ oW ">ba3=Su]=? *+#?AQؕ`@:Lo`Z$@/WeY^;! G6@q SoU)>8_#vAx 0ƺ0(Zf(!]CA _{Wk G _ ծA\&Ww;ZTC@_;'".`D~°5 B 1W)'&D_=| 106O4j~ ۿ $SHls]Xqʫǖ}Q|?'Lqwe{h%4҈wS|ߎG6]yU'{]P$dZ*DC {k|kdpnͪ<6)R1JZq`,m(&Zcx#嶸'> C }iz%*#hnjIz}.1[KYP4Ydr*;&G K5ģz>?.85I"'=utτhS9j_EI-zPnDqfl<שZ'4)2MN,N?49wNGR8=&k̯3{v3h޵=;x$ͺ}x{su~[jUąX 5%{ ^UfνmµB KjO L3<'[fhY;8m|VLkPܱ=[#_5(y|*&֮F@#! J<#rڨ2c:N6&MU +jT.~D,ՐצabrjgT˱8/w?)},ThK3 ~M()$5q""Qg^aBk>鄪uU3qR 􏢺Nhh{GX*="r.<$SI6Sr6p-I2 &S%,Ǽ{1nsOi[& EBoB`fNdh2z(iFCARFb2|'²[[+jD //(J;HwhH/搕OG\o6iGCZKlU[gWGhm(Epi̡s&hob ɃPZiT֗|]YYLJr0omb뷴;[5^.gD@]2E;Ҟd":hPYmNwк6jìoBwȵ+h­pbתI͇;rٺ҇1/^T햕i9ȭz;m;,>_?FWJ3J:Vo#SNpq~|?>F&uoZ%Wr'/~ϯq<ɱr%v$r>Zک-6itEǶ[v `␷7Uon UeM&h81S$R;t]}(LjگKA(RKzL\jVo=k,u1͞w( X5DY.dHń-\#L/DɒMW<;%>Lr2YpǽfrCNXt:q~$_4 )nNM$ $4k)p[h+%>tk>'(K_Ѥ"&""NaeJžWІdA׵\\U.D1T.JZt{ܩ7`gBSUjC!!n4#hţV&!CS*hl@ͣA][mَYdwnaG$ ت//|Q.*%5悳)H"tСU>]Qbάzܡ;w5/Յt'KWd3IUwzi,FWi$9~>#̎8` w2YK!9dUN)>[ۗcPydDofG[ zVs}1ymO#ɱX@sGj'6R/}u'{G*<D>ҼW#|悫WK'79P*Y_X(yZt߷4MG @T[f%UIWX*qo%E3IyɘiL!VT16'yeypϲ:`Y4YciinVY+'TϘR|aieD"kOׇsyƍUIX:#cTka0lBrz~2b[otQݎ>#i߯怪|&D28˜όy p2iD>zrձҫ<&!|/G)\3FPÇH/ŵz΃y㴶*ag|x=r~@KtKV!9DPz4tnM<$zs{V.HB[ Q ǑG.)óJTn.F@u=[x5(O2~R#;zqAЦ9T-, <,.j.iJ 1){LU5t;g˾-J {oEf5SOY{GCy]$,c̯<Ҡ5^#O}E7|l2js-uFs2we+utޕDT<.V~<8:2 <)r M$2KQ}nIQ`HыUFW6% Sy+MQǚV/ Ji6Kx OFj2~0p >ـ(UnVe-SPְСƪu]fi Bd?^ž[I@ua8;w:al<՟gs{o&VQLXJޅ(G3"3 ItŕQ-k=+uN㘗Je%6tOzw,eֵ<ȶK`G W5m斮0}\+#U OV\ *oGFXJ)6Ft>ij+Y2CLA fmM{y3|uc7k'_C0.0dd]_M |}̪T43 `S ^3CE\{?]=R 25<ΜU- $):eD58yvXy~[ 72.$~4Abc@vG"YDzƈm.ڔqJp[qyiwׂ[hZB>jd$liibc>ZRmԍz<Awː\ F~*"[O m/۟ߙ$f(e)s [tUV*ﱣB276Tznlj/Z Vᵒ/9?;=8SAGȤÚ-M(_D& ^6?u_6}=7ו/V8C%GzgYw6"ʷTRgg*S^B[p[35O\+zp%1]뷣&#:.;sz_"q,3_dP/[7&27HE^s{sO+Zn/َp 4 1Qs%o~_!>:&M.gpY"3FIғQ=qrgZR.*on\]я!3mgߟȠ^J5Ed^Dag6f3y 2qntiyx|7(IxrgNCtLRF.QyN;P* @b_ue=SLF_en+!E?Ѥ M?>> 7 ۘ¹3Q`+=a^'b\Ⱦ&Ňl>Vڰ2,PO}/ӓ ݭYYiP5RAp;*dLEl?"A{S4Ix\OYWfx+HP)?Θ#yE.M1?tHDI-i^Er+6惤Fb[ndH7飏(3RFpdPQf5ྜྷhT2Of'/Ur12riOǯXQcNܬs'Mۓ74Ʃ:Mwo(Ϙ-a A#]f=_i0O&ǴU:bq) 29{ nw0fG]?%v-t>olJZkO}!e_L6.JJJSx]Ypic}U;\NLT`d>ՍIg="i+_ܹVeZs ?Oy-jUZ!&s>F: ~/ 1gRX :C;:xi-k5.h=ȹAh^T8?nS$yktx҇IP:P/~ײ(tj2|ɩTF,)n-aWѮ=r"D&I&@eGaBzGTgо#eZ=tࢢ)ۨldHlrj˦ƷRݢRθ-+X~@[m5f4[L@Hj(틈sB݊URb->!Ӹ2[9syw,_>sFe"s5vq$?Q΁]e`"5%5 fd4s f`ʽWV$qIkc "$Z!Q.i )* |utNك4?Y[Hz mfPggқR,p)w SzKiJpP$~^HBt6&(0˪aƼt=rЮwAкAbDj,jSKj:ukٍ%~YP_}hM(n ZLS_S9QHVCkp8Ze@/*K{O%NZj6k~$daE=Y["9֘(aO"O_Vۣ?=,Mȣǘ5r)fy35{kIVW"y,}K?koZ6A2@5m RJ^ԗGqqQCp/xI<9وNC<2c*t5`r13~M0!UŞUxxX51rRy4T5=f6]p)WN}32Y8,8^*~W̍U=bsޢ)R)aDU0>t=״U'G0.fLR;N@ZІjGSz,>Z>!E:d2[bVnup/+-{8岧zX=\.J#0$ yǾ&_Zݛvs?Qjxr"!@srlO{38je5 >\ծPnoޛ#)?KVօ,2/#^a-f۴ҒDeUB傌 KA:wyrg/S.g86tf%L4ܨ$[$)չVs[1l/Ȋ?[1B^ƤNLIM,?x$y( wğWZ6tw\UCoènaFaP;ߺ#=#CiߌJ?}N%o Üa:*H ?;ʻȥ.ȲIK]8ӥps8-s]XRz]OF[XX;'Q#165OfQaZ!|k[͝z=?+cצ_:'efZi 301ZLvM?MgW]K>Ÿ8V3=Ǽ!|'K`xar>}NT碊'2wAWD,d9!sGz]gcKᏫb0*92zIî4r/*вyB`J3YlMS$fWR̓(DC^870 w^tP%P=kg(DLC$sXLv=5UN_Y+7HQ̥:y836}5.w(@5.?5u8~ewz5!Q\;k2t^~g@] 6ٗ]8gs7dIŇ/ ^SVd5Blh۾iVCy{<:0DPg7(s48}\8sAV)yS5rSt6T -B1MI(%%*?m+?؝"ne]xSE؎[k'9lӣ-KF&&0j ۯ;[֗W"CmpӼiOR#TNelnxb"* UTݬQ|;(fa]~<4M"nU ”UdF=L}+[MT?ܓ2c^g-7;\ Ig2dzSsn#[Cwe{-ݜrT/_~QR jD.<|E7$\BgC8 j kH ֖\>/b5ߪ וqdSar(I^L_qf2I? a{d#CV3{x)+dD'ѢZ=-^Ϸôr\h`Dj H%j1s7}w:>OLuFE40]r=uިf(" ̮=_3P,>l-|4,N4Ϋsu_NmlKNħ'E)k Ch:rڦ!\6Q #aX QA[)B+"-ʩr?M%O8,vgdf8zdWoIayt-Σ/1]N l |W>H{׫~Dm1Bm蚜SEvg;ʶQxl]xGWL.1$0"y2DEPl%GUFnYWB3v#nJ"oAbu#٣‹da׾"LtؚNED'+=4SFeZŻ]|Q"j5W={ZbA``t#Lʺe03!'KCD.06Nd=/?T"@*x޹frKKR2$G4pt#/"0`,k^\&6DN~ jIze o@%q6Sn2نO/VKiE7}p Βo-=p2(hƗXOK]XQ :XQWldsad3{-[aҳRI΋{N$[יkי#q?<~iʊ57LojrZ #Xi$P9?.Q@@ endstream endobj 10 0 obj << /Type /FontDescriptor /FontName /EECDDE+EURM7 /Flags 4 /FontBBox [-5 -243 1229 720] /Ascent 692 /CapHeight 692 /Descent -189 /ItalicAngle 0 /StemV 88 /XHeight 460 /CharSet (/one/two/zero) /FontFile 9 0 R >> endobj 11 0 obj << /Type /Font /Subtype /Type1 /BaseFont /EECDDE+EURM7 /FirstChar 48 /LastChar 50 /Widths [625.6 625.6 625.6] /FontDescriptor 10 0 R >> endobj 12 0 obj << /Length1 1404 /Length2 6229 /Length3 0 /Length 7181 /Filter /FlateDecode >> stream xڍtTk-HEQ ) UzJI$@B&Hiҫ(E@?z>www^#b+ pz0  ;P%fQ96hw pmpC`u+ s\Wj`I'W"8w0A::p-h+i=0 v@!n``.|?F8JsfETCQĿwAg; Eg;T`M!H0 y@~0tv~9x;!64`>p7 ]\a>1!h5 Ok3]3@?oOE"<=b>e-}y%(㔓CzyxD @Dw:7V0WFU  M28 'z8#sr/eo0<ƚ0(񿽪hf"liAuh_kGt( z ~`׫k`'1ZX3]/'[>^}prEl.Ŀ&) ]}mm99~U rUp}ϿA'񠧕AM<+_[cM:ÄlYvʣrVһI:S!#vN<>RDc)v.}=;h1Bt"ITq Vff '.EVZPkG@1%+ztlFH&Et9e4YՍHx\ 3e蔾$oKCnRc殮KSCa '`JNİ9TG~N%ay]4Z}-^-f[z&obXx,3O؞^fQk@WEH%{ϼԊ/M?&%X,hO7|0!tAH!<7هeȯ;etVW̖// )ƚ|w öu*cЁvޮ^ {2j\=-[ ~u/Rʃc0-8#̽6D_@~8bKOKYyĨgRuvQd\'arƖtyMz77ݚ[-Z|rs&~ZG <$"uZIs@Wb7!hEl7"oTUPsԷ1]=fl;==nO7&/= \,,ZQa`:ח_K0)/z)2a|?;քu jWvk4CY]1O&%mUրl~Xn_9KMɨ# %qII+Ry5ܡȐ Ȫ\|~E=Mca xߠHAB'9JzAk.2qFoÀ`-U/7hD8U ]*Py[foڦCUu uE/Μ?ԳnYupSy/RNǢ@n1J۝I8.w z u4м-jh3z*_)YW_f'DoZǢ5ak_3dzG~By_Hu NcױTVڗqxz7j|ТM"8/Q03_v;gcGc+]pEFYDK;V ]4avܘ˳ wgM;92P)/>&\m a|9Mk7wIXxloV47)i?RiIsg>-WzxkhY~:`6<a md8&"𬓲8֣C{nわm(_*W" l+3 @|e~pmxz8S}R&n;oRX#I1x\ '?)aCB:p〬z2QfsVNBe2>pL>\7!xN7ा3jFyUj2k=QV(3g|$j$_B6m 2\xfjy vEiy4:(eKPU$ǛwxO,l\Rt]=Qz̀1,my@riĞ{Ez:#R9x1Zl4@L.NNg6q?5:ET$z5IƵLZI9zCXcg\ oD$1I{Ϯƻݒ풎7&0CVJh'ς!ryeS<Klz]>y#^ͱ+k)@UW5WS0}DO1e*cc v0UABHDSf!gjd{w^$u^-cY ֫:K#/B0h9Ǿxp`,ڮ@45<M;oi*5~y3ks?||!b0pw| w旲p2]fo`]Nc TqTIE^G7hqmAX`IwO]߂Dhfgji U&eGA{&'73^.$) imBV^1B4Q ˜PŢyaٷpÂgxw s%"ǻ<|fht}z{ǜ;2Ԉ. q,lAR J,{IAt:gػscUt*c2deB+S H+vߒ;I:X:3&Fۏٺ>V%֪W߂:q~ oF΢Ay5DCo͵9̐l|}aeT@bBR=iף%w)O)JZ\fdN害1YV4 ׭hnhC;F*e߲ 1>fZoi^Sq^vx , _/պ8J.!/6@V{wvTDPr)29rne97^|lۢFyF}" FzCb湫{ Xf^5 嬄/d*8}ʄQ0G#"6`n%,{TûtaNhg[I~[t^5N?lmum#x?ToY5շ5ç?Z ϓ͎E@q1fmS3pX7֫"!72 jP PXKj!aXŮEf`G`#D1gx}2LVPCJU2:aRιm)D<'bX3VRJ}ff7 Q,Ҕf icL;j"2\LsQW>aPabFe*ߤiɵÛE줯$%5+v2{Nh-T[mͻorʙ@c~Ěl|Ȥth!:wE3Ux!^=Bz`ףjѹ޵a'$v渝6:*2}O_"yC\|jj[a2IǠ,GtwP_`t» Ud[ OlE\ q֕瞪U 5NCҝC;8 bLA=;VG^ě8L!# %ci d onI%WzpAIi [k7YI=: fPyvuBaDʳo;%\dP?hmܧ 3BAZ1UZԮA@9@Svi\ ]Ezrg-qOZ=4,"uK:{69[kCK&Q6~.\r-w%r2Np{Ԧ4py΍l$zu׏MӰN[g=*bbk^:v^*D wRe5ԤdDܢgrLړ5a܊ L%-n6Z>=}{ F1mJΝR?Iz y}QK1me? QfqyXܜE+gzoS@i"2U:w,ܟ?]t(gNW.nv~L?:f;C’<5jA=X8>^Xα骚 Y_tm YZF.CM d^zី1 } h #g?x,q:ֹJRQjB?[MҏFƫYQ@nfSec7vܰ>RNgwƱ{{TawdN_pe_]zL{SgYq ǖhH_NV vX0ǘrrp-Gto4eZ)fG YM/X.o^>>ez! >)r''^N߭5)z_feHJUtR}ZKN$G>>" - ϻ.)MRe$1Ť_ ۛ|m<-(@X,J)<[[ºLy17h۾.8Utu[*Sl'LTӚ" "j\%\Q \fobJq{~52T~4ݍ,tVJ=>^Vq/ kam]:VY@{MRdҽ/&597kjW/ R[ȥ3\\&k[2ԝ*z-orHfUU5m+ݲQ0hG[4a^k N >d]cP!ˉ掞o8*yוv: endstream endobj 13 0 obj << /Type /FontDescriptor /FontName /GNSCFD+EUFM10 /Flags 4 /FontBBox [-28 -257 1055 741] /Ascent 691 /CapHeight 691 /Descent -189 /ItalicAngle 0 /StemV 76 /XHeight 475 /CharSet (/equal/plus) /FontFile 12 0 R >> endobj 14 0 obj << /Type /Font /Subtype /Type1 /BaseFont /GNSCFD+EUFM10 /FirstChar 43 /LastChar 61 /Widths [756.2 277.6 756.2 277.6 501.8 501.8 501.8 501.8 501.8 501.8 501.8 501.8 501.8 501.8 501.8 216.1 216.1 0 756.2] /FontDescriptor 13 0 R >> endobj 15 0 obj << /Length1 1381 /Length2 6016 /Length3 0 /Length 6950 /Filter /FlateDecode >> stream xڍvTk-"Ҥ(MBҥޥH@H ̀"R7R" "iAT?߽kݻV>3{f=3]00Rla`+)D""b""\\Xo;9) 2 A6UB$)H De8+ B!ar.7\`h Ѕ`a. 1z+#*zzz C\0( u00 Eq&L0Fc=!h€w$\Tk]aȿ: I'W"8w0 AzÑ{8c A`P:V2@.ᇱC]a #W6!*(!u?U8fwwo:#QH'{8jh4U`.L9 1 s*p esE/h|r`0_vDp;,G'fbhJb@_\lDx{@ - =%?q*+8!1;?ov+&E&g3xȆ z}xE$D.@"_Y7#?pG 0%ce>M,BJH?̓cp/skup$z@""Pūs1.؅]R iR$FCE.JTB]H 0c}(49JJ*LJkF_/j}j fG>9 p h>PbZ'<64ڢ+D)C>l-eK4( a:u- ,ZעP6ua@*1tCVj+"+8Rb+e9o1+@5a^"iVI4MN LދVɽxDW咥ALt8b{cҴ0*EfaFQ/]‰v"94ML1g[Rj>bϩ|ZℽiG {*C|RjVr$,Cv>@ q)9k4Kc(XtԈUƧ+LJɔKNeK8"XQ cU(SPxwd1dq w*ڀTlɼ3A| HՉ1eR>uN*h`)޽vAo:KhgN }+a%چC+Pwg| $2`הri=KBP)VIN지[`0%DUWz:퇐 orhr ݤғN;tURcl8&:+3 p'A5M9J!ҋW8t W eSXa7kSnR4Feh =ձ}dC/[D,&Fyw?{=vVCβ|(}쁶U8K&Ve)S4=J֣#T;KU*C,mMWUٸ_T,nXd0o'E>>켭ǭnvy=% u9nv6d @ ,Zȼ zގ *C{r4HrS] oPsBi ]ׅ^r[XK~cϪb9>n )I7Pq-vq uB\qHZ$2 vl RN|%)ʰKc?3÷)74( ~K,w[A= GȚA~:kw_;_X/6dۗZ77yj^**h~LbEoz1~\9q7IRD6Y*DL֟z^eRU:sb|SzkIaJPt'm2 jf3[E+`P3E64qU8UXԁuy^1 AiC/d D)a\>l$"*tHy08X#&dCźk~RS*Z-e/-;$[ e턕GWmCYuthM4CQīH{:~IZ /ix?-]U2rJ-jPsӓ0։Oΐ|oQFb YGB|@n9A\= 'JծEw|鍌[NU`Y5D@Gw|oԃMw^l7L#ZctmT\k".})TtْI7?Aw_Gt܏.Y c֪؏u7b ҍ\^ tG"T&4mTzdVΒUUR9 T ۍym[yysxM07$Tbe\#יqA\8f8Ǵ}{NM[fҥwtGZI֏7;؉iv1o<$co=~sݚh5.|'o6$G2Cc7M!B~`kG!Qe(!z<0sUzX XjWֽ<\ <+][w$?`q%>ߞ,m+D_MN'MBϦ-3]mGepҭ&p4}t'ESpnZ,wV^*Ae /=^ ջƬWMHF})o%n"ne!THw0j)d,+7p8RoV_YݷSw>%?UBZC@܋F2O2jǢy~GFr<Ę=X JE8߶G4Jg (<[vk_<&b^V'~(5ɗ")/%q?sFވfb0а m5fH&a.H%==5%2窫su@Q]вeǂ%) ,:Eiy*oT>E~zV{{8Z:F7LVؠHqAKkGx kkvSp`piwV ڍi*$#6aYP $V^G4f/>Ydp/pˆ%x Q3Љ47A5hOBXf'R֪>hs} sy<''rNܤ=>έ7"Qj=Qا+[CRFU9dZ YM/ 116ز=&Կ~7_OQKn;B7ogJuǍI3I,)S(vмڢu|/6NV >,Tݼ#VzMWʩ-?uwP e(PI "gb*Bd>EFg?:zWFX:1τ﨩7vF{&52<&)+ik `K[Tpjߠ:v=}/>u]Peg[JLhf+1+M;$ ƴm="6ݦyj^Hh-9[ZUsvӸz3ۍ_81pdT)\C:͵[^MTO8p֟캜%pV41O{?7 FGrP䢘q%2(uE,6o}mWMͯՏ'g=N4JދÀɂc<% PL>7Kc^ Q:LH<&&Z5yGOsZV{5D,$&iB7w d{N*iǵ㷵9_&?4[5'*_hS/Yq +Y$OLG ػ @g{;X+vxhy7[ 0[m%æ9A(tn-45`:U=Bqz:Y,MgHZ0]9 ʾ!:;]GA6p\@)2}:G|8ϺcSi%Zur%Ѐdfy|uv?1Ǎ)[̷b&GVphl#Id.NxNPjL/ B)mT,~3ԕ3Zn5Š&En(& R?/ ˮl Wz1}n[SH Σfxɧ2 x^E?>In'HrA!A^1qbx\0[π fÊ[HlƁVBA.cl  oZ'4@%>5\֥"ZK@g m\ Kp$^#׈T3[ĺ > endobj 17 0 obj << /Type /Font /Subtype /Type1 /BaseFont /YJHUNA+EURB10 /FirstChar 67 /LastChar 67 /Widths [789.4] /FontDescriptor 16 0 R >> endobj 18 0 obj << /F8 5 0 R /F15 8 0 R /F16 11 0 R /F18 14 0 R /F30 17 0 R >> endobj 19 0 obj << /Length 701 /Filter /FlateDecode >> stream xVn[1 UQRh d+-d-fh~WA`"#k&SWKZ8&hX&uLu)I;U0E*B^⹬/N-NV:x a~+e)Yo|`꘼$m` f t*yWQ Zl>ʸSU^:2q$&a7l3fuD [,UVqM>Ꮫ,4/L-g&RW0.Z#46{hM[vt%_VWVV0Cتw(l2;oHDzׯofŧaPހ1&Gbe=wF *0]!FB:=NzB"n=9P#s I‘N:ny;!7 qF4䇕MIhn=*[:Ra/^yEC/X.6Uk[z eAOn]Dc9]Z:^j·3j;QMF=ݿIA-?+ h (œ3?{HoC:ذM]ơ?[2AZ厗Vۚ9vI$)T&Flg9O9byjT+s endstream endobj 20 0 obj << /Type /Page /Contents 19 0 R /Resources << /ProcSet [ /PDF /Text ] /Font 18 0 R >> /MediaBox [ 0 0 595 842] /CropBox [63.1 703.1 240.9 832.9] /ArtBox [63.1 703.1 240.9 832.9] /Parent 2 0 R >> endobj 1 0 obj << /Type /Ipe /Length 698 /Filter /FlateDecode >> stream xVn0}+,o+8"? UqjB"'eB[l]!_g9{j67ZXRqޚ)PT{ql4uj)SFiݍ;c 0eC'` l2+l~,n>ɚ+¶]̳țm `նi]s] .oob)z6% ^Pi7,Z9YUv[uXԴEY1-dt],>DJjd_0ۺ[n7|z4 3NZ@25Aw8|2W)n,0|d6WzgksiE.>!Rv")LfUcE0ZY\ c)"Lc g]4ͯٷ_M'G! 4=L-v &DS(7 <31`G5;;}^|)~Pe_㟛|@<$r0*i|Z7NbAb MsxP ;#XBK{> V8g$i=܏J%p%`!I Ҙ1PIWDKqZяǖ-G&mnFI endstream endobj 2 0 obj << /Type /Pages /Count 1 /Kids [ 20 0 R ] >> endobj 21 0 obj << /Type /Catalog /Pages 2 0 R >> endobj 22 0 obj << /Creator (Ipe 6.0 preview 32) /Producer (Ipe 6.0 preview 32) /CreationDate (D:20100514105700) /ModDate (D:20100514111242) >> endobj xref 0 23 0000000000 00000 f 0000039070 00000 n 0000039851 00000 n 0000000009 00000 n 0000006924 00000 n 0000007144 00000 n 0000007280 00000 n 0000014606 00000 n 0000014824 00000 n 0000015045 00000 n 0000022433 00000 n 0000022659 00000 n 0000022811 00000 n 0000030105 00000 n 0000030332 00000 n 0000030577 00000 n 0000037640 00000 n 0000037859 00000 n 0000038000 00000 n 0000038079 00000 n 0000038853 00000 n 0000039911 00000 n 0000039961 00000 n trailer << /Size 23 /Root 21 0 R /Info 22 0 R >> startxref 40105 %%EOF trunk-2018.02b/doc/sphinx/fig/sphere-sphere.png000066400000000000000000000167601324306050200213030ustar00rootroot00000000000000PNG  IHDRr?, pHYsqF vpAgrj]IDATx흽sH3MS?Δ'܊*.0>$) 0auNSϲ0\|:ܠ 1q%Ҕ|ܐ/|itƱDɪ,vJSA'ȵ 5^D\ צacުdLSE V 8vm נ nJ A (̼V)D U4Ü _}H?bN/ S7wNœ n? y_l#ȹ"mO Gw\6_~OvZrҔ7~꫟,=* 8-*1''7)1_'jǀ I!-i<?nTM 򁰩7-q&u"',SIU/~!q$QqAUE9*UU_|0WH )yU'_U$wkaC bfv^CIGQg?C,`_(A#†aHqwE3>kPд'm?!7N%;MKvB4{F7n^[b&̷oO9y5,}N 9y'޺'qڷ3?1/EI,c_nO 9y˪,C/N vf~˸r$c7.jcY]ZWW.e礐GU6 V]Tʊ0PrUr0peR\[T̲( !j4?I}ڦ C( !O=y<)͌RA u7UY:4Q 2: ׵*MSןT>Mߤ0):.8NBlFfڈ@;MSIQOnbKIR'4'LC;uKF:`娪<]<+M9d3Haycoq7̣;NSN2TdC9u B'RpDZhIM(iY[–; 456 D7!;]t gű]`-#Lv\l5f-_N(":VY"esR~BZ / $^.B4{l"nwB^QN*{i"-]#1$$ĄG_sH7C:]OY{dġFȫ* -ީiduėě =_l_ֲD 'wm4w-9:}-[#u.\Uo~\4[@ nJW77wuUWc"/ qampinoۼOl_Eck-rqv^0DMFݴ+ϩC:8_y|ZzeK՟P;qUrqEx; w}C Jتۈ- A$Q4_=|f!W u!lL.. {'ASdUa׼n_cUuj?|@7/y/_~.o`*NbuDc68 956'V u.Υe!Fq1?YE,Molݛ$-go?YYrX!MiEjh~PpD&/ġ0MPXr0!w.MQN+ DŽSvO1<|D.Ј@ /o( NgpI3~-SPyGP˚|Ez\U R\[M5[iŲ,{3mLdDTC[Z߈>e.V t1/9'gcyG^|Z^hN'L Wb~+T#oM{:rUummSșdX]GA|ru,zLԂ/~O#Ց}Q0s@98N+@CbTw@\RRH[K|Fxzʙ;C7u<1jg5~ qco[Y.I&sLq',NMN@Yh~tK}."c&8 ?F..!.yWӬ8HA.cK9*P \&%.#l^-k֎s##EWEj:sc#LtsQ=8aZ-xYpՋ5ȱ >9w3oִ9nЋD8Kkn =eve;W9>6QᕮI߯3u%UUH,CDːa;!"Jπ8LXם u?X׈7[jrVS#0wFF$ 9@1}Qhb;~`FE%Ve?DAUM_[V ^)wf흆?B}ȫ0]: 6u)RemA:vvsWCoMsǀ%-Ya䝿\ޖ qC4nj2{C_ ":myvp44 1e5MB5iw;|8>}hf¤qxy'?~t.rT ?gC 9陀KP;4,Nwc3$r y`r|N6đD=y1ұ9nH²Ɓe8 xec߶rֶ HOUnfsF[n dwӨ9>\L;4qqg9b+',h%Z:˺D $bo:圈L-ToUUzzs/^ =>gs.?:?؇>|/?csoވ ~=O߽m+zܕ~ x;}^[ܕgjݕ'Ono߿C=@&Ǧ./OT} ^0-ur9Y}_iz6BUE;hC]7"v푓Oe76jwiHڳW=XrU[&v S|AGny9.{~䫾|K%X/ S~D՗vI#{,4S6$)7wq|dc;֝'vv=j=>6j>]lm5 ĿOl]1qNdm|sOjBg"7hx 6ޏ#>fŇ|m|sQU0V'd U"2d%Nw$ΫW'>Ͽc٘1k*[Ϲ*ߣfeӴ9rpZ&Т#5:3гsl?Ϲ+V-uk}HaY3Rr^htǼ9-řyc)Bi9s.GA2\Cƛ@gVK6j+ řJm/ G #u,Ļ' Krrzt* [ܑ|9sì?/R.qCɗs_mPх~C&%_!ɖX3A#ĉyY,a\-MHuS{isWU.ze]>;~NRyAnanmPIn[L_:%5"qVMC3r]RZP M]XZC]KR?]5mAu$FhacSRl\>Pt8B e 5U˹\X=r>A>iM2; ܪ ˍ8-rBrZm3v߃"9h幍?uoFcW_0QV|unHF#q|1/ &1- ݌\.DYFIY0QorXCCk^5ɌYOںWn 5IƩӳ^oI /TG/IDԎ]D1Һr7v%:>ƒDytK MEfԢadχVNei*Pv{#z,b5]יW4c XU(A؋ g{3ȱ>,p 8:a,G@i;ڸ!"5_tlANj&ls~xvlh(S^6UB6'ԝwJjwA|P9,XU,OHُ9ڳD8?\(a!aUtA&""nC@|v( Oz:zٻ~Vm@b ;J2Ul..LpAE.\g WO }_hX9WjBWcQAG&} 9W@% 1Zk^[].*{(!ׁ䎡iQAYӧ//`?˗> Q=F设&޾6&rEi+,R Q?Jonn;CTAG}1\ǜAԢocSCUI_1_mg[ϟѣ կlt~ xx8`>~?Oy۹ëyݻxK!t{vU+Cou1"w֓'߫3)4=ӽ)ș|c`8+,$wíWsx9F?}#PU\d\R]t[|sS̽ rmGܔV&۲U6*EnL)"EFݔV.%ƦA w Mm )Qd>#A@U 6T׸7v rEcU+Wr2~!uE׫+\*qqID+bDZ ̎F\ĝd\PB: 'AIu3en^1AQ̮}t<rbqAwp 7^U_EƄh6Aqp 7@(sWL0"ȍ }"Is_P? QŞܒp nMjVUşe^׭^Mօ)a)lOw^+6m/]3xT n&zzE;dZQG揬Wjj"5źQP;nE;i@D. ,m06MY#4B;Ả JcD* -~#{LA= ;"IS"u-0%tEXtdate:create2010-06-12T14:42:38+02:00P%tEXtdate:modify2010-06-12T14:42:38+02:00h +tEXtpdf:HiResBoundingBox177.8x129.8+63.1+703.1ʴ;tEXtpdf:VersionPDF-1.3 3 0 obj << /\IENDB`trunk-2018.02b/doc/sphinx/fig/spheres-contact-stiffness.pdf000066400000000000000000000622031324306050200236130ustar00rootroot00000000000000%PDF-1.3 3 0 obj << /Length1 1413 /Length2 6440 /Length3 0 /Length 7397 /Filter /FlateDecode >> stream xڍwTk-H"Ajz{HPBU@I^EP* ]:""H9{׺we$3gyάoįACUp4?H(P11@(Dn CABQ0\!PP0Ƨ Fup$ Iĥ@(7(a@z+!~(3Z W7$));Ep uv!`P߿Jp8H)AA$PO(  CP `;< !>`pp9@7)^p0! )'W!w2`p'# S@`8扸{an`  ?<P0$S27cVCP8ړa(\+r!h@&pTCE  $%DPYWc?$w}!(@oh@`ЛO7FyAw{ N7n_`+@?lnA}łJF(TTDB"7B@@п3aN*jɿHLo"gm3[E7o%*OT~ǹq; o5ցB`^@o6C`0_(DvpK<Of`p>A5כAoV-UȯQ(FXBrB5 #7):@I1I/o Q5uBnnN{P_$A:¥!N·}wqUQ4TA6^ژf0]"d _Ѕ4|4JߪgNPFiT<=vl7ɀ ۊ3l"RF͗WcR4:tBޫ[,ٖ֤( /c [`'V3>yۗ._S+VEq_9o0=VO Ec݉ۍUd8CϗOs0G:bW3 :LJ&0%<q"tN' 5=^H|N$)y^X]k8\l4Hͷ]hN0C-Zk,:qdchV~&ǶgGO[D[=Ӱ{4QC S ~wuq^!5x$8 R9z; ѡ#Sf+ ^s*$q8M] ىfH 'ɿIuqc/-}MPQ#_\=j ]Y[/7LPh~ ZȿƟk5y2 XzJE#U灔Ԭ^J=a: ue\KoC:һ' $xաmZ|%9Rƍf@P;`c-+ ~"ϙFdeC#o ^"%O{$̚:bsdO3R5Ct?Eߩg{4@/jPpff?JUPp]ۄҺE[iNw@_3Jw.,ҩrX[ i|{?Z:̰h8bb0YY;f ]"Ըo,fk:"!ehUcȂBѭ=Y ;C~$&wONQ!ߝ^4=u3ttիY#ȻȊ|`m!D8lt zT88Ʒc:($){ݙ ex6Eǟû> %曜{- OviqJ?,9u)cË1k-N-pnZZk$M4CliZ?{=Jʁ8Ψ#HfauIiIZ]m`|a9ӥDθ3*x/\|I[Z*y($rn7A!mڧ/lRX_#¤$3ȮSI36xvuO3ֳ9O1^>=TȁPl[}݃y^q}?u-Is~JI @0)4?BZ ; `05"PDZb_ߗ|=4bK]}bHZBhlGG@ͨoV{8 26H: 곧<<|J{[0p՗1>ZXta#wH)GGK7k6ͥoݜ8ޏCӫIًB(>>Sob|~ﳛwKܘgؐJYoHyt?u4},xvPkTNm}nǹ"&x:.;LL\{Yמ}| 2*Hˢ2)g`H-9P-s14X=/ '{FP6"w )ԭ\{Rhn۶ >LhQ)GuZXʺTsٳt=snν01 A5^@WRmx[:H_7&5]Z{|Bg7iV?_PV%g\klgIM PbGj%i[j+ShO{8KH rkb 4ŽþqG&u 8͛v i })ٵߣTM\&kic/프ɘ-"H|fINM"{~]=[[Hi'O)e7۽aî^n`c$goQ9s+2mC 犀$#&L'e""We>ĿSON7-h_']VleogDc=.Ka)@= I{[}xO'~*+']s@e0؟K2+,͋@͘/E"IZ zJwu*e%Dewl}p|CzgTgԤAO3bJCмb٢J:iFŘB& J|nq%WqF9fTt5sT/5 g9YL%I4 O⚶Δ %U ebOO%-ba{COsozӛ0j ų(:~xhO3\/x%Nb5 _IOcg-[W%,8us1}zpKSw7,|$Q[ 0MR;^ Q6F3 As$md~Cigw_`\ K/yY e{M00dZ4 ҏ懢ُz^7Q -ԕM ?DQ})#1غR_$UgJeNк^`GwR>uݏ႗:&x#dQPIk PǑ6`:0~JB:\;:;Z;(W9N0$W$\YMq5 /}aք(;iCn(oiYp-JZ>9)5K^œ$(l@L`Yx`PӪj#bq*!Y[Cao~u1gi0ӢgDkEar ƒ68rEb9= x?8-sR+h;"]\cB0]DL|gvN/Q.oV2 wTnLТ:Cg]O(I6qp Hs'aB*7H~wwQwH#-ے*(#[ZR Jޅopt_;ߟrDg-Na|on⥫N¹#Ԭ*\le@08o87kk.H |9Ӌ{z]j}MP:tثC]xY ]őJ K5_Ɏ _I'V{pS^Ӱ48ڰ?C?~JءRH.~2?r)E>hk5NŁ%F97TH]VOBjOdV({c(mq1= '|ହf]#pz i ݔتxfهJ}5:"[ ~p3}كey(qeH[!I|K0X@~|lAfNo(c5S<"I5ζM pL.nBZy`_~ WU>cw"ԉ6Md'{27$>ym4w|lDYF^Hz,tjtf8g+Bk ܈<ڐ/]e|$4 Vp: cIͮ~Je80L'hCI 3mkYUomDׄOL œӮMG_5>|yЀ| S~OSϴ"g,Ȥ|pB~Cvr$jfnڡ̍^kqsRGwG'fi|Xy>QJQs6ݔc,)k!tmmᕦ+qG@j'q+$ cU1= q <°$%}6JXڍ&.]رLBߺ& )d˭sЇ6|z#YfNh{E}LjX"83IOuiBMzzA[on I3X /+CXV1TUvH G-uuÔVl09P2ڐ{$ ʥ|N5.Nj+|9NnO߭,:?g෻ O}7\ZXqdhÇ$T@i۫v_}=nz ;H砆K!35[W3Q4>o|xtߝb:f^OtK>W!Q'dLxhd*SaQ@Q.o?<Ig\uG׹fE *pw`ǟ/ Tx_PR{u dزkI_)Wr͚J0&+LJVsj,+\f5Z`0QNF+0f'gQ*6vuL5gZ'~w%W*iP)~D䲭o'=lnkavg0`U̳6vbq~Vvŏ*m~۩ X8{Sy]{¤ zqq!c,BnmCwf5H(0-jo6[Ţ0fd}?RpݞHl Nmx$ŧ 2i~kMX-Пe JC \USdvy * U'tڥeϿSYwG2#FӱL&yn#+JaAKYI%_ [TzDcKRMaۑPc5Gh0J%6Y?%⠯/֨3D =w/ߋ[vhHHFb1+l~]ku# '²͈ 䥠'5r"^3'IYZjew)A{rsV/b~ibHSH`љ Mf4 :>M 2\/F vீ .ع_'xCJ@*rh݈<&J#q|^Ksu:F/'}0PX?aU|95K}*,B紆dS'Ҳ8ȟp#@Q3h}8r>7ꈼv1 endstream endobj 4 0 obj << /Type /FontDescriptor /FontName /CBXSPI+EURM10 /Flags 4 /FontBBox [-32 -243 1060 720] /Ascent 691 /CapHeight 691 /Descent -189 /ItalicAngle 0 /StemV 78 /XHeight 459 /CharSet (/E/l/r) /FontFile 3 0 R >> endobj 5 0 obj << /Type /Font /Subtype /Type1 /BaseFont /CBXSPI+EURM10 /FirstChar 69 /LastChar 114 /Widths [605 499.4 765.4 783.2 394.3 402.2 668.7 559.1 1044.1 829.7 803.2 576.1 828.9 609 557.2 492.9 774.3 646.2 986.5 666 555.1 666.6 0 0 0 0 0 388.4 609 588.5 487 603.9 499.7 420 568.8 621.7 360 331.9 555.8 365.4 915.8 664.4 563.4 589.6 605.5 432.1] /FontDescriptor 4 0 R >> endobj 6 0 obj << /Length1 1392 /Length2 6122 /Length3 0 /Length 7069 /Filter /FlateDecode >> stream xڍW4WjWh(j E$DjբvmfQU{R[fx{Ϲy>C•PH Hh) D@ a2GMaG#PH C18D!j@!] !Q ( BK;P qȣ\GG ($..+q0( 8]paPg> cKqbPwAE`zpw8n uF&@4pD1X()08ၴ@}U +YOoBB3+ \\Hohp40^> iC=g-ɡ@%.; pŸ #BeE<ĸ< -{(,`@a*hDyUTd $w½`?x~q}]Q@{?!uz- 0 @qG# ,qC!q5_A9MUљ* s4eњQ V8aPIGD]feۏ'DD*>xf& t}dMdď1ee_ZPQS= T  2{4K뙟|"^y<kvw jxr%EvډIjSY,T/V]r&hr>:sΡ71֓L~py QrrU[v{)6N)dInF*^lj5e' _0[Q-%No#7ȟ#^x?P2 ,@ה}2 +{,_=2~IZ+T %)fެTʪg:R<&JJ([mAa.4dzE$2ropڂ )^K/t}vVV|ZP.XpV<>4; M~ƒCəBؗR5a vݭf 5RF9%j}bP!;{W ɸmr= 6᳷),ٓl/cmCEtZϟj(7,bsteadgF㺣;UNKL XLss9Q|eN2@6i2D_|iBę !.n^p&dbduI,͒a_' j VZ30>֗.@c !5JIЗR&/FC{~wb 1HGm͌_!k 񕬽%)FG&?HҷkT$SEUYෑQo > O4Y&E{_exI~V~(cV\džxF{`B35L#OcǑ!jUynRşx$tYsp9&Y 6CxkDE@w6 O-lze\j(\jr.,[\SO)|%PTܼ!3Jm_&;xymoHܑE< 7zMQ]}#=p+ݳIhPݛ{x^mM5BJ6^k B}]u4[QHPK\@8|Wr٭cEv1#\fd,R셫}=kmt]dϗ ?6zf4_> `0&dǺ,rz*z٭\ za/a~-0NY 02By] .V(3&&lcHsOvѯI=7}?OX c ]ͬ} p1^,D蜼ej)&;q/(7$6 EȜ{, #DT$V6Qu$~n罈ֲ- o6B]&l=WY4weFxdGn 1Y~ _W̏I!lYI(NHgfyI;Cc {Naӹ{Kڅa'W>ZG p}H?{ƭcY3Y7il_xK5" j֌ĮZNVץ?1|w^Sސi/f{^pp{=96kAx}s)J7r7T,Z,R \yˁy#&S3u٬%"'Ea#MȻ,ɤf,g2NMTv6ka"a}ͤ`甦{ftA9;d?=m"Zf`=nN eT`xcg/RU6-dyVfvlSEwRR v5}$D^PڳuRm*[/lemX.ɰ Y]aϟ 3a+4Ƈo/ӌ#E( G6sA)V5D7ekm)Ed|MrHi!yb(gJqE}?8|,M!ٕ`A"ـL vS 'Wzn8WگY#nx(HgsҪw`7E  ȋ;G}'YEj< Kk%sDC$h9iW\#Y)ʀ#0؎f`zo2f,[dA>6i0N^n$r^Q!mv xdbLJpySSUl"E/MwyB aIV3NOԖ2 " gWr>OKY\܆CqP;\,*R^6I;BET-|'=pۮ][]@/)e륎I%M5Ȳ<*uXdD^ʾ= j%gR,*BH`ðΊ;ieaK*7S!t\<~A' =0Yj@pؓr&%Fa ~_V䴨"iBۿh?tc 4ux܄  $933ɻ|ru5 ×]n1O$CNtGUJp^w@&2#'B%ĶWX2YAeX+aeCX&ܾ栁f(Y7s{ד;DZ_r&ڲ<Q[N⯦-o{vkȗIQSz8loq󛒋.E4tSwⴟM|Nlc#{-^Y6.eH~rȼ,cf-V?bomK-nY<*HJ!yKwR*}8?f!B<  cd~xQ"C:slޙ un4ɌcXNVh9-K+l\muVioz soy+2OM֚8$Yjƕ`IqZJ=ȓ>Y ƌ-Fz@0O4)_Z,):ݙ47C:.4= SJCc1j]w"r=KB s/ 6F3Ioa4au/f_)ۓ+5"A<{mY@^|X7ۃv+&je7w|Lٵa ysd%v1IQ$3T1!{i"bmWIH~8KqZ ?hl+Ϲ`7|`&-e_)ห_ˋJ]whzAЃ?ퟱ<&q}TLэd15d^.ߎn([*PJҎ=Ktgj F?0l?Fthr 6-8VE %Կin~KuSA;EwI^N)d9z)U7(=%ĔYHf] hݔq®|sT>61Z㣕9 iXVD[rMgs>_.Q_mp*s#fU%z6 sq쐝[iI޸xuߵ QJt?VwȤWV1 VOT܃Y{cz^יUQjxV%$)B^a?5)mFe~K b*5CýXr^G̈́}Sګ|[+_ U [ل{&h0[\4H{/)Um[^v !=>XBc m˰y #[ KdcBZ\їJSg_rmW K29Bl|MӎF*%"M6jv敌()Cd]&ˇe}Cc`˖wkJYԣm1 J9֑4׽T^ʒY'>N-=@i,+gl1o0?nM۩ 7г8a yMd,ӳp#_鍝JD]J0stb$ rZʼo;JߪܦOJ8k"F[j쌐"FDaH_h2&|Mk`aN"ʂf׷B^#;#K؆ ݄nK@)1ZiNRNทMJ^bT|| =[Sʗ!o!!@̤ooΰUK`6SSmYKyώqΰ9> endobj 8 0 obj << /Type /Font /Subtype /Type1 /BaseFont /BMIAEC+EURM7 /FirstChar 49 /LastChar 50 /Widths [625.6 625.6] /FontDescriptor 7 0 R >> endobj 9 0 obj << /Length1 1404 /Length2 6229 /Length3 0 /Length 7181 /Filter /FlateDecode >> stream xڍtTk-HEQ ) UzJI$@B&Hiҫ(E@?z>www^#b+ pz0  ;P%fQ96hw pmpC`u+ s\Wj`I'W"8w0A::p-h+i=0 v@!n``.|?F8JsfETCQĿwAg; Eg;T`M!H0 y@~0tv~9x;!64`>p7 ]\a>1!h5 Ok3]3@?oOE"<=b>e-}y%(㔓CzyxD @Dw:7V0WFU  M28 'z8#sr/eo0<ƚ0(񿽪hf"liAuh_kGt( z ~`׫k`'1ZX3]/'[>^}prEl.Ŀ&) ]}mm99~U rUp}ϿA'񠧕AM<+_[cM:ÄlYvʣrVһI:S!#vN<>RDc)v.}=;h1Bt"ITq Vff '.EVZPkG@1%+ztlFH&Et9e4YՍHx\ 3e蔾$oKCnRc殮KSCa '`JNİ9TG~N%ay]4Z}-^-f[z&obXx,3O؞^fQk@WEH%{ϼԊ/M?&%X,hO7|0!tAH!<7هeȯ;etVW̖// )ƚ|w öu*cЁvޮ^ {2j\=-[ ~u/Rʃc0-8#̽6D_@~8bKOKYyĨgRuvQd\'arƖtyMz77ݚ[-Z|rs&~ZG <$"uZIs@Wb7!hEl7"oTUPsԷ1]=fl;==nO7&/= \,,ZQa`:ח_K0)/z)2a|?;քu jWvk4CY]1O&%mUրl~Xn_9KMɨ# %qII+Ry5ܡȐ Ȫ\|~E=Mca xߠHAB'9JzAk.2qFoÀ`-U/7hD8U ]*Py[foڦCUu uE/Μ?ԳnYupSy/RNǢ@n1J۝I8.w z u4м-jh3z*_)YW_f'DoZǢ5ak_3dzG~By_Hu NcױTVڗqxz7j|ТM"8/Q03_v;gcGc+]pEFYDK;V ]4avܘ˳ wgM;92P)/>&\m a|9Mk7wIXxloV47)i?RiIsg>-WzxkhY~:`6<a md8&"𬓲8֣C{nわm(_*W" l+3 @|e~pmxz8S}R&n;oRX#I1x\ '?)aCB:p〬z2QfsVNBe2>pL>\7!xN7ा3jFyUj2k=QV(3g|$j$_B6m 2\xfjy vEiy4:(eKPU$ǛwxO,l\Rt]=Qz̀1,my@riĞ{Ez:#R9x1Zl4@L.NNg6q?5:ET$z5IƵLZI9zCXcg\ oD$1I{Ϯƻݒ풎7&0CVJh'ς!ryeS<Klz]>y#^ͱ+k)@UW5WS0}DO1e*cc v0UABHDSf!gjd{w^$u^-cY ֫:K#/B0h9Ǿxp`,ڮ@45<M;oi*5~y3ks?||!b0pw| w旲p2]fo`]Nc TqTIE^G7hqmAX`IwO]߂Dhfgji U&eGA{&'73^.$) imBV^1B4Q ˜PŢyaٷpÂgxw s%"ǻ<|fht}z{ǜ;2Ԉ. q,lAR J,{IAt:gػscUt*c2deB+S H+vߒ;I:X:3&Fۏٺ>V%֪W߂:q~ oF΢Ay5DCo͵9̐l|}aeT@bBR=iף%w)O)JZ\fdN害1YV4 ׭hnhC;F*e߲ 1>fZoi^Sq^vx , _/պ8J.!/6@V{wvTDPr)29rne97^|lۢFyF}" FzCb湫{ Xf^5 嬄/d*8}ʄQ0G#"6`n%,{TûtaNhg[I~[t^5N?lmum#x?ToY5շ5ç?Z ϓ͎E@q1fmS3pX7֫"!72 jP PXKj!aXŮEf`G`#D1gx}2LVPCJU2:aRιm)D<'bX3VRJ}ff7 Q,Ҕf icL;j"2\LsQW>aPabFe*ߤiɵÛE줯$%5+v2{Nh-T[mͻorʙ@c~Ěl|Ȥth!:wE3Ux!^=Bz`ףjѹ޵a'$v渝6:*2}O_"yC\|jj[a2IǠ,GtwP_`t» Ud[ OlE\ q֕瞪U 5NCҝC;8 bLA=;VG^ě8L!# %ci d onI%WzpAIi [k7YI=: fPyvuBaDʳo;%\dP?hmܧ 3BAZ1UZԮA@9@Svi\ ]Ezrg-qOZ=4,"uK:{69[kCK&Q6~.\r-w%r2Np{Ԧ4py΍l$zu׏MӰN[g=*bbk^:v^*D wRe5ԤdDܢgrLړ5a܊ L%-n6Z>=}{ F1mJΝR?Iz y}QK1me? QfqyXܜE+gzoS@i"2U:w,ܟ?]t(gNW.nv~L?:f;C’<5jA=X8>^Xα骚 Y_tm YZF.CM d^zី1 } h #g?x,q:ֹJRQjB?[MҏFƫYQ@nfSec7vܰ>RNgwƱ{{TawdN_pe_]zL{SgYq ǖhH_NV vX0ǘrrp-Gto4eZ)fG YM/X.o^>>ez! >)r''^N߭5)z_feHJUtR}ZKN$G>>" - ϻ.)MRe$1Ť_ ۛ|m<-(@X,J)<[[ºLy17h۾.8Utu[*Sl'LTӚ" "j\%\Q \fobJq{~52T~4ݍ,tVJ=>^Vq/ kam]:VY@{MRdҽ/&597kjW/ R[ȥ3\\&k[2ԝ*z-orHfUU5m+ݲQ0hG[4a^k N >d]cP!ˉ掞o8*yוv: endstream endobj 10 0 obj << /Type /FontDescriptor /FontName /GNSCFD+EUFM10 /Flags 4 /FontBBox [-28 -257 1055 741] /Ascent 691 /CapHeight 691 /Descent -189 /ItalicAngle 0 /StemV 76 /XHeight 475 /CharSet (/equal/plus) /FontFile 9 0 R >> endobj 11 0 obj << /Type /Font /Subtype /Type1 /BaseFont /GNSCFD+EUFM10 /FirstChar 43 /LastChar 61 /Widths [756.2 277.6 756.2 277.6 501.8 501.8 501.8 501.8 501.8 501.8 501.8 501.8 501.8 501.8 501.8 216.1 216.1 0 756.2] /FontDescriptor 10 0 R >> endobj 12 0 obj << /F15 5 0 R /F16 8 0 R /F18 11 0 R >> endobj 13 0 obj << /Length 560 /Filter /FlateDecode >> stream xT=o1 +8QE"K@A ҿ_Rw: PI=Q$ x_1pY3B >*P-;S i_&Ι@@mٹҴ4c;=W嘖pOˤ=KR2lk ٧(uI94TGKV~ܬ›ٹZz1aD)yJX,,Kłl@Gٻ" ㎌cy1ubL+3Kt‚􇐴5Xɢgΰ{Zj.g+)3n}oo]BDV!:nJ*aW6&4Pf:I3b%\;v_ {݊Z5ntRRi'Z}KuqT&*C S6>. pjd|Ӌű˼QX 6ž {pǧZiC L94]N5θ}=UҴglm=/pUI.Iڮ zAVPyi endstream endobj 14 0 obj << /Type /Page /Contents 13 0 R /Resources << /ProcSet [ /PDF /Text ] /Font 12 0 R >> /MediaBox [ 0 0 595 842] /CropBox [15.1 719.1 176.9 816.9] /ArtBox [15.1 719.1 176.9 816.9] /Parent 2 0 R >> endobj 1 0 obj << /Type /Ipe /Length 643 /Filter /FlateDecode >> stream xڵUێ }W oU`qS1űn{i)f K3~ݶ’ҌcFWLJBQΚ;^w5Ϝ2JgE0j^'s3^bT}W u[;sv0^in񿖋$T&sqdmTX~0mu  MLSCr# vhUfhEtvjV;(&A TH \oFg|%  ѣD)y2zbT|)9c<-HMМ\h.sD.hJRCNatg`JRm]o ,vv z0n eRbڀWb1~h.2&e9lK+R{"}wW>APpN$Z yM2.=g^B̷ȉ,Y|ń-3օ +`1>񅽜17W endstream endobj 2 0 obj << /Type /Pages /Count 1 /Kids [ 14 0 R ] >> endobj 15 0 obj << /Type /Catalog /Pages 2 0 R >> endobj 16 0 obj << /Creator (Ipe 6.0 preview 32) /Producer (Ipe 6.0 preview 32) /CreationDate (D:20100426123751) /ModDate (D:20100426142129) >> endobj xref 0 17 0000000000 00000 f 0000024330 00000 n 0000025056 00000 n 0000000009 00000 n 0000007518 00000 n 0000007738 00000 n 0000008115 00000 n 0000015296 00000 n 0000015516 00000 n 0000015660 00000 n 0000022953 00000 n 0000023179 00000 n 0000023424 00000 n 0000023480 00000 n 0000024113 00000 n 0000025116 00000 n 0000025166 00000 n trailer << /Size 17 /Root 15 0 R /Info 16 0 R >> startxref 25310 %%EOF trunk-2018.02b/doc/sphinx/fig/spheres-contact-stiffness.png000066400000000000000000000145511324306050200236310ustar00rootroot00000000000000PNG  IHDRQ pHYsqF vpAgQ8CIDATx]fl9-  [ !0 72RjkɖiiإfW+}9p'¾ڻ!9͙s;p j}>iЭ;`IQUP<,G5>g8O^m>tAz=˒zͼߗ5UH[U4]Ars))Uhۦ^6M^#:=hI$rc`ʇnQv/_S1z>C➡"h\. g]w]G\Nјz! Ebܨ-t[(XPYtOSB=&x(YJsuVGv#)ѓ4ł.NQ;Q459YӘv{mYz1!)s${Qr'S1ewS~LmX1R~/ˇwע8]AQCu>ӻ]jXŠ9(}Ŗ̉Zץ)bp@t*˴lm \ڿ*vuU!zf[44cbPA9\FJXV,kՃ0b&E, HfGaQC"E11zH&;mv2"(6K 2ie_vC'EG=vˏIZ^Sp1Ea|=$eJQ߂Kl&BSd<}? 8TZ FR?e7ݶ~ E<JV6h<5nTAR-EZ=!jbYOJ#Lu:S{V2}KM3]r"i$Jι*>mQ6ר?I"T5LJQ}og>U_WT G*W,~w O?1EkK`pmEQ iT$taq)@O#Ch?PvOTuACNlW!PQو0xB^P LĬBPˤM7rvsg**d) Z>:+w~_,~EȮϮ懥KuNk ~-O4yRcP[/[D%d8"6ڰ= - qDiP4$'t5 V1Г\HyqA _GW۷阝V 'l#(Bw17 aϞ=.&*;/+(?f5pICQc%os5D 6 XgB)Fu#km ~ϿׯAY7|#oo ltRRT/UBÏ?~۷9g#~:^ś7.ZfeShHЇA!9< $!ǖt>SBgES4X(M<9atL)EEelq2]:I) VԀ;bQ߼?EtK$9L2E?/S/KtMBQ8\wد S$J)@&8VE!EӴ)nnGOW@Hm(KenGU4w'}IpGOTAULS$rOԝӼl୦܊EyAP=ޠ_(c9-^D=ā|D&$~C!E!{X Pl~ MuE}lPn7y.~2JCתCznBTՏsJ_ǒ{\7o*H#}TÙDj=z}̆GlHQ]l`S ˣv9+/7a B&+T7ȣ(qF1\]$. . n;E..'_tX^U;rF\zQn?b96 (e*7cMFwd8F$l~9(*8fu*6AfGAQv4R `+lX tbve=-?G?2;ET*nls*&X{vW?īGIRy_ RTE*n;/R|^zO?뉙STU(Rq|e:+8#u`ї/w? kGiyS#6n8^(U=";CvYy?s77s{7Ups5g0?~+iG޵# 0N;~>Ums7Upәg92x|voE [#DcA6*njmojtAǀoⅯ~018FaT5\M\!ßQQT[#x'R3)8^s~_?6c*ND nFp{寿G̠XK&8-Hը2\3H씢c'Vçb7V-d=xR1]Fa$&'S b7Qq%=(g?E>)&JL15*+ˠ`JL15**J$!F {=̆8JضC~Q uX ۧRdT 쒼soޘm!m[(OI+qE*Nw*ߩ !kUlHQYaހS,J2[1z0x'R ~(nR8~Zi5{Lw4&vw{?T}*;EV*ryxP۳PF=fV]F?("LZBZj21]ϤF~ć!nzsAYBth*֝:R1Gɠ)qAau)Xb^ =ƀCQsfİ+G3~j@X60:&1gׅ8`尦ONUzs61v=QlΦ(?<(bS;t!_"(N̦T`>0EWSt3޾ LgO5T%W3#tH;Ꝭh׵;}?"=".mVN"nQUUlwNx1^Wq@`ũPsG\)( o;t+A;]_; -%̏.bd ?gz- ;dM`ou-E׹.J=0r~yT(*\FE v(.8$=|crDQĉ[׹CSQS Sc5<{Mx\ yg( +:_ǎU; oE~v⏕M;9D4d~HʥO/'j  ,}z0 'ALzŵע8yrK1j|"j]y:J#!'A\M6T]W?Fįw+Һq>bwTSt|.%>;ȏ恂i05Eo@erv .~?˧p{nCxSOpj (pP)\xְ˅-J&4 \\cNPI)l!Ec=CmWVd2tt;74k`oE^/pG; klBQ4UQ׺1Ft~Ȗ%,Qr)o Eޓ[`B~Q[]*3MF5|I_J3آҡ=QvS6!+K7"/eR):1(-5Rp@G%`-=Y=$+0ROWMQUwxR|6UڍJ]@BJ zfC4k RÀDPf{M4:d-^tPtD쒨t.i.F$2Scp6 ¢a-9 0sPKe0_j/ۨ"QUGG ?wR6;\x*{HM]{ OYi_]u M~PKUZ)]K2C4dB\uݶ ҥJ~&EP DWBH~|`|o!w WeIAjV_XM/5IAC7@QtϋD[XMrCƾ)jf\@]]HD;Eurl" Y5n)რ( 隈)r(g,`\\cdVl1Lt{8!Ez̊iX &r@):ZmGeY}MwV7p+moC781J|xhϻ+2[mH%NNwqZ~ہ8&y0Gn&!Nx7¾kEASH\TTp]= "QFs%tEXtdate:create2010-06-12T14:42:38+02:00P%tEXtdate:modify2010-06-12T14:42:38+02:00h *tEXtpdf:HiResBoundingBox161.8x97.8+15.1+719.1ztEXtpdf:VersionPDF-1.3 3 0 obj << /\IENDB`trunk-2018.02b/doc/sphinx/fig/spheres-dofs.pdf000066400000000000000000000456551324306050200211250ustar00rootroot00000000000000%PDF-1.3 3 0 obj << /Length1 722 /Length2 13922 /Length3 0 /Length 14497 /Filter /FlateDecode >> stream x}ycp&mlybۘض$yb۶=9q2q&ۼ9{?ڻȤ4@lLl̬u)e5E6V+3 f tAuʆ@wtvrК в[[=ͬ".B@7+WVѕUJ+iA @ `mrp,]v1fo Y8%5D49@Ab7WrA.t]llsk37)_::X86wwo#dOwwrwA.blrM[y 'U \-vo6?Q[( "*.'wT@wXh/؊@7k/>+_ O/cgؚ=ZU@f+f6mU & +l3}WVƇHsAvwdo[Ɂ XHR*ˀW—HK(-|{3%̍`i:9ol&]hۉkt4YzH}ZyKSu]+*|c  qCASX=ƍ^ζcTCz X IAp#gߖCxQ2I'`Hi6(5Q 䑷apIXz#}v !iQ#C4;'9ֹ̤;ԛj5Nu(f?QdxOy}hI2is>91Cܘ!cuc=zyvM&^-hV1TQJe{s&rݮ;8)qhG 4TxPG.uB9>M]ܤuσa9>K8iުuh@A٧lB\5;tO|Yݢk֒/'02|20n蔢2 p1jQ79j0>kT{3>}-c|| # Eɗ߃kvDO0V>o4shidFu#/؟Ֆ=&T/cfW4ӃRޫ׍p96%C|XeAŎ@am2$]ھ0?n5Fyw7w|ASMYn!^DS4-QjT[M1~'?("gy|z}-%_8e~arˎoI"]\[*C{-#A.Rr{Emڡ sT xQrţucEK[+V%X>eٖL K\Ss ²v֑V&cuIyTK>~bNN=Np]5mWk0)~hB۳f/%Ǩn4; ߯}h8"XI)8BT>DA֢w3l%&8ϲ9Q^3zI}Kϖ{JGe0|ic0;XVpHT"y#Z.LO .X'(|iPԹJFJ ܻNsS{}6K] G+z(ܖ=uoQ'9vO \(PHᕯ9; YfV>_s-L3Glo&.YOKp",X%2 kPXx >9>.,ùdA1u Ɂu%+M1 K}/ZLu` K#+3"IG֋} g$19Y[zY _EO `יj[6ī&ĺ'A`!Rf>%S탕.l!< .b7ef ;nyݕV7,pZ:ukygѪS!F5QRᓛ}D\B5)2`niY|S/1ZX1}8ͯdN|ŰĝsS4NN}갎'vPT5e,H`\0:?x26YDUus҅&~(5M>/VN$2%2 ɦĖ^%[bs> D19Z Dh*Q<Lr|6#|ZN LP5m" pìйDbMC#Fy 5Q8v:S3wg`h0 {1ʐ[ >o;dMamZ&\M ?fVrfFFTY>85}dø4pGw[evmޒPO\ FBbqH]ZER Jf#t]g3Ġւ,8k3MH\7.%XRn; Apf$lw42 >ϱh8C(#$b މ4z RZAO M _X|k_O96NkMhOtU5by{Dui׏N6MZG{nDڏ5Masj%? :%Y1C-~v!mwPGݓϽU=ЗόY+D3*)Ztu~KLG=/8Ql -GfS:htKbYz8iɘXr'%Ӎ9^״3n{Qs^N\K.`nW ױ1:v5IIř 8L=ظmlSn0(0rZ|TqPgOR<66O+[,v>E%sr$60:%ι)4EfIR'X&+.]\F?&s'/TKr{ϗՔ~Sd5Biwnk{H'LcE BڍԒX 6 DBf PaZ[ͺڊڴ!&D#CyiͬWdi;rnuGEL}7K287}&~ʰroxy(c _(2cQĠ=M&{Q)8{;`ԹF:&7[9=v.fj.pT~gNEJ <)O$ y G&5J[BZ&yKR;iŐoO poԴ#+y-$Mk_<5Ј$0/ֶLcf=hkd$~{*Fd0nBlGc4sTֿ2YLDturQ C;Wqc%k8Cp@u9*V6md/0=t3hS\~8KFq82"(|$C߷Ċ Px,}/>s鹀$,-<8(e9-+x>5?/کZ]Pp{*Fhfa/ _;ŊƦO9&$?gHL7'T ɘy(ےxhY4@GD=4 Ӆ gуY-K̑82*Y 5jD3 zТ%U|?bf#~gD<7hHz_5xhb}" 3Bygρva/Y?@Jp3^E㯻s:cJ 2;6pS| vրe0D-іl=z/20jyep:`>VRg^#oǕߨR>PLi>xvė.Xp|,Y >jT*_ԧ_bw9HJh }_gQ3ߊaE~K?|P6Ps+̋i݅Nb-c W{Y\0V?OwPEIユSQS`Ym!J[ЬzOF"ki?EIbz^ HՔЄH#j̣ye_WCh`[ S#^FM=.>O=P.w4 ]FWR\aO$0>0Ssׯ$1pMZ+jxЎJdP myden9X\Ioq-Ssq}AxZ1GjV`s"Mh<b6 ublH"dW`yyoPtBCR}*8|eNf)_?[SE`A|W?zpF N3\ZX2ntCh`ue4P6T23okG^I/wPp%v L峘~%2I5Lcxv!->wI+Fof,=V_BLy*!abEcĮ=Ե8XVnȶKG}69ԸqK5G0]geJlА-[ jD1V"2, IN"?9I/5oϖX wsz,өZ&ZQ#AX/؊x^Z , F:5˩l{؜ߏW3zFj;Q/lJMؐړQ b4#\*(2:xJDǷi"8I+ Ys[ }7Hwx<-`[bLR~E Q Wu$HSzy3r@ȱe ~.M/׽&6AGQد0q:m'Q-~'n)ߴD>ͩ2!=-3ffJnSжCZ!eUvfҾߢi[3X yzmjd8$W|vP ͝iH@2%Ub=IN ڹi\LѰiL pNo(Kĩ 9WnH$RH}+Hm9u;m>(Vص(qDߕyPqF~!5,M0fdZ̅;}$ozd|x*^n#@C^ e{RN8;qi(^?dP7P^9Og*/ )&Lv|J <_GPܢP{Lwrw!Щ/Ldt()y8=j>dȊ#75ů8#[` ?.mf/U*XBm/ajGg#q/,KγjzRkU<жj [,9u4YWTv^<$^3w0$df/x-8Ӂr{'* R&>E/b# ѸSmZcLxX_ TZW Lie" >$U+gfF^ #y)-\¤LjA6>Mc M{P^H$yaQ~? `D1(YV^ u5t*ØM4?[X[cP h>uVyv̗.\Dzl#] C(x}t4}X71@L o*#JR}&7̑]XOhd<.'ƒ(3]15vsC,|`4g=R>y%U!G,Tl<9-"aJ6"G6Zj(_Hn66yo tNH>8S4uVyݺ"UM }R?=9~86TDn$KK>Ӱ4OVo>UYߋA~ǿ:tu5 S(Q##z;ed uX-Ry?hռ%yM= TJȚ#\:;j^Ī;0ƺ|Z,Wҗc?YvM.fmBC btbLf׬J!\KhҠoƐT]`IvelZV-=wv&\/cJSWm⦹ [k?m2pH;6 7=_ILvӱ۔Y fRe2EW!sOAi⦘m@5:KNKQeS"\Bp[xi%v '8g^aPAwÅ!ʬdfcvPmg(G;`S`_B/nY_hn8Z@KN+͟iMjtaj'b/IHuwu#k;SdM-BQJR7%٪tPW$d.S=b8 Q kco~THDEZpSh͎:?sǜ.*,iuYE)n>YNa%GN$^T)Q(CЎ`S*,v.^Oq=t$4ҡyf83צ;iCʼ5ʅx(UxLeЉRpֶ0户DVV'jszI!.BH+@o^/]ujΣ29t|ѯELԕ0ȚaMKwQ< Zj95~ZČ:M[⻟ Nf_L5[# vn6C쌄' Rl GQv_Ё`DŘKLJq0bSy?F<5aQY0nK8AL 9*'a&1|=١Hf]-5bB=Dog[u=liR4@ 5N/443&<\hX{4/k:i#6Dl,½VgfKNXBC 3k<^ " =0 u89ȧz^r籑٩dD@*u7ĦxNm*@Qi'~ħuu ɀ󝫎{Pi{W.`Q.lZjzuEMz4{C^Fd d:C OB+ѲW1A3E)Q-舌L9}n@f*۪,B@^|Lo\I% t3zX|yLSM?5@`(+DxXmNj%+7#G|2 [>N# *Y,2^pw{Ǿ-#"y?"kddjY~hy!`͙qUs>T&Z >CbQ=}P=Vj_װtl߯Xnb۵cDHc+6(Ⱦ{6Qd,z8#@ߎ] (,۰ x P =;cE9[ Z'mKw}fj""{ 7 2^32 [+q+pD[9tT}Q//M_ysqGs$Ջ59g՛Xm8Bj$&"DBHwy(F7up`,v _$s endstream endobj 4 0 obj << /Type /FontDescriptor /FontName /KQCGZB+SFORM10 /Flags 4 /FontBBox [-199 -316 1461 921] /Ascent 694 /CapHeight 683 /Descent -194 /ItalicAngle 0 /StemV 50 /XHeight 458 /CharSet (/D/F/a/b/c/d/e/fi/g/h/i/l/m/n/o/one/parenleft/parenright/r/s/t/two/u/w) /FontFile 3 0 R >> endobj 5 0 obj << /Type /Font /Subtype /Type1 /BaseFont /KQCGZB+SFORM10 /FirstChar 28 /LastChar 119 /Widths [558 558 836 836 500 277 500 833 500 833 780 277 388 388 500 777 277 333 277 500 500 500 500 500 500 500 500 500 500 500 277 277 777 777 777 472 777 738 702 722 758 675 647 781 738 350 508 766 619 905 738 777 675 777 730 555 722 738 738 1016 738 738 611 277 500 277 611 777 277 488 561 444 561 444 305 500 561 283 311 533 283 838 561 500 561 533 391 394 388 561 533 727] /Encoding <> /FontDescriptor 4 0 R >> endobj 6 0 obj << /F16 5 0 R >> endobj 7 0 obj << /Length 1473 /Filter /FlateDecode >> stream xXKo7 ϯ[SI)[ߒulaE~IF^/Ekm?wc`~.Śbg5e0)LoBe];:/(_#O6ʚS!yZ*R^_˟ Ap1|18/j6_5o)+  S4 98Sb \;mv77)7#O5ӎ,*8W|qOA:>OTnv&&[nF:h:@q csH/XiND[NTepX!-vn };<%LT俔:G;܊_)(ŗ1 >㛾, 7#AO4)Z܂cs?g2OZsXDz\`R_]-JWך|2WՍtq8wK.pr 28-Q!vkR,ZaiÏB8\|ѺǮ]JS$d7~݈ s$X9&NR |oYdHI|}b@P"Es|xkJ-4D+| qH}((Hv02U7HjNY=^d{:NvxVΪ.bLIst tGgC(yMt~8w3%vpO_uOणS|FW[|Tp 'JhɃJsCnkt;6\:&)1_DZj7iJ]߳:~ -j'); - t}[j7W86?'xz-Z6NieТLѱ"Fę=Nlj0Ql䘊Ґ(qQcvD<0u iD-HV3bL*E)aq8ڂRI~.Dl Yd ״)J&ZGBҮi#!2şnOɶp+S%cEPm4g$)mtk{ѲRqP-ۍd|-ܻT$麥v'g\vd>kw6ηٓ+y-o#=I]!6"{ZG.0(eհ=:ieqU: ILYs\1fD؏p(2۬d{*Ƴ̙Fh+Ě۳/ endstream endobj 8 0 obj << /Type /Page /Contents 7 0 R /Resources << /ProcSet [ /PDF /Text ] /Font 6 0 R >> /MediaBox [ 0 0 595 842] /CropBox [15.1 655 512 840.9] /ArtBox [15.1 655 512 840.9] /Parent 2 0 R >> endobj 1 0 obj << /Type /Ipe /Length 1371 /Filter /FlateDecode >> stream xڽWK6 tJъ%Qni9{mf$z]=3zY}|8ypx3EnU^4|qE6&ͯ?AruNj+ ֧_C1JpX[g2Ei2UH3n$vz_ޑNԝޚC=IXFKsAI0 (n'M6#.2Vոb&^o RI4CGiZbdw>5F1=*RN2Sb~.(&sIoaFp?RCl@4[^9l1fݠEr`XH2]&6mB@꟤Pʽ=ukO "8tjSc@JI(9F(aOZ]kԽΫU5G`j:}uptw8vsyU5$06_NW5V Q4JwCSJ%:b @4<(J:{ѽm1Hnb`&I VW{ ET(vp ]HQ1KG~"Yq z!GY Ƚzܤ tDF}~z4;qhT 74W'ޫH>GC2)h¼#4ץdWwA-,&,-' kKOƧ]@>PJ-ہU VLDfM6J2Mt̥%jW?a&2+}A\1ߊecz Y;]@?'O>Y喎0޷P$0Z$O9!9z6^U:E?0甭e›F楊Iɧ$R;a>}ÄXy6x8BPlN9z#-a>~Bw}\4v{li:C_Rj9.5ȸ.!PMXsuQ"_*&q*D]i0Vr @[38=o{=]mϯkx-.-x@^\u *-=H *u=|`D j; S1ųoU0P ;Dhn7 .2Vu endstream endobj 2 0 obj << /Type /Pages /Count 1 /Kids [ 8 0 R ] >> endobj 9 0 obj << /Type /Catalog /Pages 2 0 R >> endobj 10 0 obj << /Creator (Ipe 6.0 preview 32) /Producer (Ipe 6.0 preview 32) /CreationDate (D:20100221143042) /ModDate (D:20100221151600) >> endobj xref 0 11 0000000000 00000 f 0000017366 00000 n 0000018821 00000 n 0000000009 00000 n 0000014619 00000 n 0000014905 00000 n 0000015582 00000 n 0000015614 00000 n 0000017160 00000 n 0000018880 00000 n 0000018929 00000 n trailer << /Size 11 /Root 9 0 R /Info 10 0 R >> startxref 19073 %%EOF trunk-2018.02b/doc/sphinx/fig/spheres-dofs.png000066400000000000000000000504601324306050200211260ustar00rootroot00000000000000PNG  IHDR 8: pHYsqF vpAg 6fP IDATx͎ș.uQ,{m͸lHt.\,7F Z4F7g`Vњ+-|V \o;-{Ifdf|ABΓ?A#~!B>n!̝,I^GzEl6ieEQUIe!X|MJ-Il(ڂ!|,B1JUeY(nWu=E1ǫf?I)( !eIe}!Ӡ, 4ȬFe!\@Uml2 MV , 34](\ZsWM e!E% &Y2[Y2XW,H7r1e!,QHBRh2/( !dub96zQ(dNPBr9@SH1e!T}U,$L( !yE>S Oj:r,4:"YuqL7}p"{:߭ q:I8 ;<ϙ~H.ǹ,e8]FriQl6Gp?|N`:4mȎcXս鞾gbɼ{pܷۇ绻ۏ\_՛cpʽ G&^"g@LCC -C/:=L{؉4ń `80yY!|HVaIcp*FVL~"p 0GqetЗIsxk`yyz؅cff,ʂG/_Ěo oI,۞1x>Ӈ!N<Ȃ>HFLO= 6ߍ`=<>{׸Z)lܥqYʃ^;^RC`ovjze3{?>">di1x)u=~dn UkY¡%v.]w~^,0!{fxx11x9Hl|u( e]۬ mr;Uf==!-Е$C!d[x<] ]_֓_7۷xzOO>}*fe>zyy ; {ݏ<2u?p Bqؓ0C\p,YtUuS_r$E]g6Lct'(Bp,m.*۽ՄD^naN}=}p lt5V߿$?ANd4 k EI}V4Βpo6kN] wﮮ$!!ˀc4qe`fb3dT>YEvzY"=5v_B/3zc=>mǠiPwRUܪH1Y 8tQ<5CWtJg9M{(w|[9E``7dAx)lp JOi-c)11{,A?f4lPa#@l6ieemeH'gL! CD.ǀg[b/ڵAU8U}Zw," pWNOwczt0|ȊI#ЧC\oն% `Ipj-y0Y~% di):ȤmT{ݝ+ۄAV\^T$,2a>9@6EȲDNh$\܍9$T 4wQdǠ̹%a'8~~6!?hZ],,vh'{A{`6).E>eJ2 GeRs?W3( WȞ{ 5]۷f{z=q} GpeQaƠ,[7Sǧ\_px М'c&umaqM)w(O<3,$!Q}{ 1+ѕ]dCj[v, 3 }Yjy!Hyf_cT\s+K]c~㥧*r =_U7# 6o'2*8J/p p^[/TH3 ϻRכḾ,k]1*`Z9 oqƗ~FQP $RL@R~8f@-萸31hdnŸ ) {ci{!6(LC< A?C0Dp*w͡;mnCH=Kr+2li)Ips.b.8K8i ~wŶá# #?dS#^8FǠ Ҕ~ǯdnǓۥ# w`2/:Q~ixw7S.X]>$Iɱy=<Y~ åNvΟH~>A$ 9_cYnh]?ҽK,g&.?\_O #p a{c}pEK,7޶S<%mM97!n(g ѲN&RkHGir'gO۷Mp -V-P @y\u/wvcC;/wr6/޽[s5Mq &9la8?LC6Ю+y.F퇇s´ڎn~7G޼ wMSq~vrC!f)iֽ9g\xvJQ#A7DY~޼id~WYzV_~YmyUE(f>]m9}I,/UdfCN&ǥBRȝ/CP)1pS5AMqU+#,%ZVH}_ NyM85nWPl*W3zݗ,$YήHz~Ds95IRYzIj÷fSXXXsjGƜ`Y'r C&+JgDzz=GAtj |n L~q{s|9'&9W+1'InINS??Q5Ois!As{򫯢?nQ]ɂ(sā }h\]մBo+mS!&dsU77WW0=iYp8pU`ǧF Itz=Z,CY,kOe?9D9`8p% lti$n)jf}>L} mŅzDlӘVe r)ߢ97s͜O#sȥ,#Q|e'[ݻK#t%ɂ(OSmpuxٮV8v)U\*¾D`Mm{ Y+?E}7&̝,X°we adF"Q,[5+P@ "<(sUU?~֚`_ w`ٮC0j]ah^S.!tGte?{?_jȂi@j"X:nχܰچ;Z`BSqNi b#߉%DͦN6oDnl7 20}zbcNsu}Ieqk NNSgQIK$ɛ7ԌDu`QI 7G%iTmٝ9.+ˍ6&q[ooߛ(H'^dQib3.(E0fTGyēۇ BHn9w;`B d@]I 3O)+y .1LEEQ݅Twuesnmd7U0BMLѸqG.GY$S3\{Ű[U)>.*4$̸rE܌)1";.xj[_Tۺ%Cԥ/Pш3޾t//QSMs~ f &We Bc|PHYŰ{mR20=9#UjN A?q}QnAU%I㕔! 쟻<8^mA*N\Z|y1۷WW>1lT&+K?P:N-CH,䋖,Xbt#}6˭up?[1 V0TO¶X`ɞ}Z_Y~As219{7f oNǠo6mHé"u=drM>:BtKT,vq'`8ΓC~zNcw /I ,n4 縕t GTg8/x1Hlؘ. 7ޑKXБ6=K:>Ec\]=?OǠt3ceȂ=˳Uv+/iy>1yIwF}dkvٓЎtm]ROqq^p*\/Ǡ?l!1]OjE,X=8p S69f-˝k;r"yG8%$!3d2>e9v> xyY -gS OΥ87"Rp J9IǠ,ǟHוY g' zM%.96{Se<=! b72.#fL<=]+Pp J>*1}q6}T|˂ehǀ,@t*{.u=m4w@ yr(K,{isymAY0ǔk)mvt`\Nzq q{4f%.2=+ λys< 4iKOis  +7WW=!sd˲-^ԵOٯ,l9_c2[wp*H8}onarscAYTfEϷњ$1Ӛqg~g~ ) (/Y廉f9wͲm||]J!ۓ:PoiPUۭ9EyH>e]29! -(ɬ #؟әWW;)`Js~};|S5ɳ.f>6 [{7OOQd3[cP&(9ecs$w6=s?Ykhxq'׹m/<[v!p8evw}{W\J];ier1' _;A%%O']$Ǡ\*(Bck2EM{?>&dvg*@|LNpIYVY#G!c[ݝVKY1(sl_O%p"Oeoޜ)T07# PP+t-F} G|[L?t6az ^^no]f b~^oUYq 9=K+G$0Zlyd,=|· PN/cclS2/ `>1FbW0Y@i9w$*η Im*KSꂂ}!mW|~Uwc0$(,M7#"[D,H_>ϋ½B]q3aC> &,&¦ tɡnPV||G1.'Х($oD0S&{Yз4LjnT-o ,Y!o*eIʖKWOu;IPvjڌ9p0@j>g<LǙ OTIU@e~#C}/pAb۞@ce٧vE/~a® dr~u{-fY騒ino%MJp L?R=L]Oc0j"Y""U;!&Sqi^^eVB%!%x a-_'Q?~vARw\( [Ӓz [.P>{5Z}J:n8i+]q wwCs9>d*c%ȲukhN ԵBR0,H;gcz/<6vvC8 dP1E%~l7}Uq}ץp}$p g |#4`X 6.'HUW_s\5p sL?(|>Tۥ # !$zaY0Q`{3d=7(Ku cXEVH[Mp0 1CSv4˾ # !lqVe6ڱz|pNp"{;i?uIَA"cz,"@__~w/Pa[!pNYPHev}K>zPrý=}"pЕ:}";.*>޾ ܯbǵaŦͯ㩁pP39{x{˂n2Ɏ,rh2[gL]7ԛ?n{:kb6#r垾CbW2͚Cm/gYdkY\c%(N-,ۢR>zmh["vikE3J|_!/_Z=z+l!-\voUHpSǡ?M =PI,нgPt{G':!=E$Q4tYк϶9tv_ldvMͫMhݿb6mO}dHuogdn1TOx,9ՂQFu?3N@oi^md~}n_XhF+ʦO vw"~9j7ͦ1>՚a;CwGO * `#9a}Y-cׂGewz_o@Ypx"oH}\}FPGW@w˂1#pyfki`Hbߕ?aMvhUBW[%c9sCkE;7֒,@f!% ƒf' koRS[g>{a vsQ˂ÿQ ?l]" 9>jaUq>1uU/NC|~CA̱[̛d;6϶i=7M(Z&!hv-h~키,8zӲ@au(S#пy x"ܿYP(=܋!Yi"~>Ϩ?3Mޡ'&YݲVr}~vWJSLm`A{kA^DC7e[ߓ!7U`Q tvٿ%iY78?Q:>wq~ |W"xt{BƾO.۸ ưAq;}w0`8[L-xìksZw5 ZtcFV_LͿj{ZjDWtvmj.@Ecj3*!ўMga}z/M.}m\j!OMM~ FɵvV@#<1xf=>e{4P*D>hhDZ6n'B;EۿC>zH,hvETnޭ%>I0Uu֯`بߦﶯ@M qao;3-{ AC׫RkE5Fi4rҮO 6@ڠ ڮ1S!(A|RG)^W@j[b)-AސTt+w[Y/0}1]n28=Km}",BfJS5mۤk7/1 9ElB.5pà˄B!PB!B!BY@!W( ! e!B^, B+ByB!PB!B!BY@!W( ! e!B^, B+ByB!PB!B!BY@!W( ! e!B^, B+ByB!PB!B!BY@!W( ! e!B^, 3wPw|iɰ?4Uu8J}2 0fY&>Z%If|$zW(wmIi&j5v$zuNva4`U6qɲ.yE&M|7$h2MĪ|H%AMJi:yPy%fUKLv*&,Uvyh[%Ԓa>$D[( H`ı9ӆW8} ,9_`"rs?42#CY@a{Vʢ|l-)|S0s`h +cR|ۜ,R, ݲ6&}[v2oObBGw^!۰?4L }sA]oFn%K<)P!*M}_\, 4E~ 0Đn !X~`h ˜~'ܺx9DXW+XD[o"klGvah܏w+B'hShH +|_EI,@Y@f#{@cqD6ep^Kmh2邰,!e#, BӢ]7e(Z2u?*'+ sݲm妴]]܆U~>!(Ҡ, O۳"x.MxP, EN9%V~\03I“22, ^ E魔:ho8E|_\~#2N,C~*Aa0 ⑺Ɓ[q>_n!O_wa.CU_E|}3:I|2L--eA$7+hc?#*jq`kW_hyQDi1Vseلx#\{-)8>&}'~1_zPe}mzV/.NJmLZsz6?$~ 7.8zȭ,*[a^,P/]}(Ko9bnbO?bw} ?y߂ vO}E%( `ؾu4$Bة>s0돺//P{]O~bZl6se8g6>"mkwXw߹W@| __ ;P>֜c\eKWeeD{O<>,6@Y@smcr꽄0㸦,:{[rzhɛCr<hcw21g Ӓ< lnO`S~E&omQ-Td!Q d:yleqJى)gw{E6&f롅?CY^GfsI,}Wu|k2p/kRc\8w}^Q$ 3 CY@q v+{G;es*nxG]-oɅ8!Ď{|^6*Pcc̵۷ՏEWs&8K9(qA㻭v __]a1я?vq3 hc1)aQڢ`GePge?[kJ8!'j׻y wje:6?L*~ DքD.8"hyj %t oNeS1>;o? W4̞K, Xn=M;_G]k0NHp?/C'GE:~+%N]kaC kF,";UiaCs˂8=/yFlb 1!T$c3>etU%I! >, 8XE(vSHϣ?KoKʧ/}AY@ Mk!e-uܺ2 Ԃ$̇CCع^/m^eɝС0]bv;\7!:,/o%)i\7g1PlKnmjء2'Oe㲳Jlfz,te|G:FL$w ?̌M Ob$#n3Q49?4)Ԁez|BY@,˜pLm&_m8a.gW>Jr"^@O?K ̄XE+qit7fsaw 1B )@jT 2LBꏲ4̷.U(*Rs^zγ(r7!(, YӞ|-MKHKݜ9 eȴf&_ $Yoa}M K }@Y@,$!VJ7I7kmK]K|RlƼu@ : r|-މXOXgٻ{ -?yR݃<).9ivAxPkvlcBW!?UضC#@NWPk,{sbsZOQH.`I]Zb ,4K|A? "k4d$ɲ)a9sHI:nG ÝT<H>b8oXe<Rc]\<ϮOVj%FbqnPkOrC}e5R6Jݐn)I;IYʼ@R:( %֥YR)V:i` 'V%֥Iu)~ H/!|#'VrӺ\L9y~<#А %@S?D@HI'?݆|E}4$:~sā˼aB!e$J?|M:I-Ra% `y"5-uoD(m|i8(sā Y ǏU~}cυ R2O-m!X#Z ,8GP㷌V>UAJ + o=ʂCqp( YHpjCA,adwٖFg?~7?i4_թPeABȀU b ץY$EGQg ˂O>ӿS PQ/$Зq/( <d@mM70Qz[b_Ͼz\X2T7mRH8PxACAI|%7oBeAW(` PHX2TUq,YeMʂ'='  b )_fG[D!P?( ymHkP0r ,AG P,}(KHylGYh^‘( U$œӎJJ<;QUO|:E2Yfo4b,[&v7&0HvG;n2s[S$CY@Ű;0B68f]`eL/uV/r1MY.Wo&er'*m|}n!쏆8Z6R| 4 ~8j:,c;SaECjQR˝AY@Űo i5'L$z-=a@Y@9 zl.w8s M{?znC= =tz~A$T/cJZS0K:u݆+iGx}GL@Y@e8קk[ʄe@"+N]ۼ=&, A=chǁiK!N2f3ƙ+ GNju1 eqvqcmMNt#\{9y߼3z@Y@<`t)+ulnKGQWyzn&~V^h؟-{yNB_LAWsIv!N ڭ#, ^h>dX?]/wv, i\w2{0, ހ-Pr}<\G9H= z=CY@<Bb*vC~`h"%#M9]‡ {YKH%+|N86pl!.&BA0=4S, ]V6Lk),Mh>0_( ilvl1u=?Rph( B!ηㇰmȩP|PBuل烔eʟ~15=|ւ8Oѐ!U*)'"5%)ݹ틏K c0C#?B|CY@ܔR$q5 "_d8~ " X={+I#߫cFjG:!@Y@āiHJm* lt2DŽZIIu &qqtA$qqB|( @0AHtj. _ $DuHlah bhSRzePد=6Է_.%Q~4Qt$ȓy@Y@{ėonG[ 6yBY@au?-6D/o~Ppkȉ:-v ƭwM/onP@b bynn NcEuٳ2M㘶eq[* ( AY@BO)S $bfېF`=6?F\of+`׶, )XONw] .M~Ra4vZ_.gwP@?uIf hv"~ zCY@f9YِSoטY+<%ɩ)Ҡ;I8(?dA͔, Vv;{ Co?y>oj8vj[.dO:Mu42v>iE@domZUQeEm'la!b !d`4Ó`CׇaDPJ}'ލLlY'Yг,ĐE«vuм_S[~/jٸT=Hz0 ( ٟLMp*cm?nLG [exdA7;Orl&nld6r'=Ԧ7hې,8|ÉC㲠ɗ)\w_҆7̞~e'VJ{ Un>@,p:>~Yǰe~,i_yg"І7]YkD# SoY٫a8& xO?@g rYx9H8˰CA Ȅw׵JStYۭVyG~,ж"eA ɂwv8Z:^{(`,eei+( LfH6fXRac9|7+ ڝIM]eFPs ]WrE s Dn ?k^nNYdEeG9-P\ZnSQuk0>d4^1"[mTY曺2av9CC;w'&C"TNDg2q ~6Уڅڹna;5^%^?}n# X݉b;CY@fڒ+COIXT7!bcծrp0!6[ű,VI)֥`[wh!`cv,j Jl>/JX?xT&KpU566Ϛڠkj! n4:$+ ڧkC +µ_XmlDsLjd`PʵF_d/LR\{i>oJL_M M|GUa/ysQ a=<2أ*tbL$yN܁§fYT/Zڛ~o?kꝺ]j#ji?ճ>A7џ QIi3oDՂc7sPb J[> stream xڍtT.-!)1HK00% R"H#)!] ! )H׏~߷9kfwzYtdm`%8 k>xyyypYX!H(o3.! W< B@8M8 @!1//W@8B r4jpE셀; `aEE9d !Nm@PFzG 6 $Yà GKs< H3+ 9Bƍwe׃!=@0؀an0[0pv +l@n'dcwr 0{ h+ip#=w   P}J= HWnW7De;9aHWS 6ck/`p;7[7g wȽ 6{0 +,6<{98|{`? n`?. `{ `G@<xf~O/[8 ?1WS |rrpO/OM2,Ur* 7- ;?;h f6x}!%7(p P솼&^5%eM-鿽Hн%E g<%s92 Ϣa]gۀy)PǟI֓ޗ֓Z"ɪ-I_i[N ;hD?&Mv:|j]>s5u>Ut0ZˤM,CUI 9SD1Aa>?.E0Pi|UjL}z܅iYJ;<,rW gTxǪ+^ )9\ V(H\\$ܵ?,B{߰ 9^@a}7kh.O.$[vk;9 maG\7}ms]ӕeъ֦p%/׆ڋ=8TXXf5{Է%"|gge_մj<"\e>d_oZ&Ck _֘}fo₯ѡ Of/KjX2𐭝y97!TjxWPXʚQAz7-sI YjVDz"NҬyYA:kqO|]XQT*c|(Ms2~3 3ƶ"FYb%KD_1!K=+#5*nya5dʓ.axLA];S*SyJ)}`TL[uוN(M-Nhv:Zw8\ ]ĮΔ>N!dz4Mi;ԑNK"GUPŅnm yLHy flԋn߆l Ь黀;ijӛUdDS.Ӿ;T5-sZL6;!r]3$ГMԹ)wpR(")zͳ=Rg 0LRaf^m_7p%QSL{>cO8SJtkuSz/%EL =h~ZӉnNpɲtwz2 ;嗑ofȊ'ҥk#ݿ`X;I[l2 OGdU/ Ԁ y<)ڙQ>/A)ǚ6xӢzuY;M xn3D +nǨé[h*iw5KIIfDlFhֲpMO6O^tLk6_G{,;5yR !]cTcmc,C8<#&o /jmVE/kj <(466$=Wsww LtA8$]6$}e>M[QS,Ǽ ,sH??{ZC\k.X6TOeh(>29R[;g""n.d+<>WLrEQiR]\?uBaP, j_ّ=g=^аU6:H@HQ=x'bXvjbml&wBBio*X2^Tћw-hMeaQfU $`P!/58%# uBXYY𤀥q'u&_kYgQF7B RRe@p n89ԝ"޲*ES^GyH[EYCgnS>kdp_ LM@0XØ@NC٬ÓdUbQPrha+&r1Uv Ch=>jAč؍cmXJ3v\5h9? uJ4:#f4gL{~ԁM~:S}Lk0^v#leJDPF_hELFWML)]aͼ:*F_Vd{[yXAx%Jsq~ZI(Ik{DrUn2ݤa"NkQHsY|+?˙W1Vkt9 1>׉zz,YFaOjZl0j u=U"6_)*{'?L7Ӳ>Ϙ- -eUHa&u*lgӻ>=_Eg3&:pP ߾:Pi"PyZS'\Wf@uvR~nZ.:a=ն <~OVcDr:O͖*[e4NG]nyER'?FU9< :}@cy_$'ٻ}GOӛ%K\Oᙤ2> ^x >6F?}祀X;ٻ꼶e3;|! Jз?Vd뒻;#pW{79ʁ_|-Xh?[NFt D pS#(=fb o(x EK妷˓pSx/꣔.._*.L#u<U|<0W&%4!a%8{|;Ss`pֹhZq+~G{_dR"CIʮ= AbϘEͅHD^:yUCꨢ;OJ5Y>A?x~j-V# cFoE $5? 2_`e}PF0* $.GyCۼLRe6N64]6&*| 7Bd;BԿe: 3qVZ5BnV3l^Lt$Rz} 9 ڲ q~n]|~Bkqxkg!ph>}}gj2;DL(M;{Mdv2E.Uͥ,gwHNXs38=v:Q5 ʕ/r1^,Z %)+wV[Hɹ&Ch!k_UdFBn^4< $x7=.\7^J+(C_ -6V}JChSy=Gs&tU4[OgerxX$U+YȽFZ߄%kqOXzczA2GkyZ|,ZRP'yu#VAnGy 4]Fh?$NȩRe^KCHer(}؞̌Ŧ=E+FXAvJ q3-0Ǹ`~7xϵFQU<ywN򷌛~Aޚ|P A7f5NU!0&R3O\87B뗚*}G>Qg3C}o0gۺRla؉rb|AAQ֥{=I2I[GƪH.xEY-p|FD\ute5 _eGޮa~Sz$QEbveSN-o;e%M?$]e-| 9"ٝ ls}ΔDFUUR wrՆ.gι 5]X008eYyռ '(~5,k;v⺅uQ6<[Sf+-vWIkaZ ."'4qY[UV*͌wPT홬s(u~ox0*"핤N5|Ejk|r71t$3٧Z'3 rʵ9Taڎ˂<˓=+?څ fQzh { . edcZUL=̝D=Eixp`fh@Va\VjxH+ժäNA䚬!#cS1'FMeЙMj+x^^0}NO)9uO1Es/cOW*ƹpDx2%_Yc6$tBMJqڪ55+'hq5eɛeL6 U. 1Ƈrr%xQ~͑Yx~73;oi+ xK]:z#ˎ|Y)[n{,bFJ:A8+K_Wqe/n;ev*aZ. +O1ɫPnV[TJc\ƑdY_vԎLU$0h݈@_Vq.ͱRv"Dp8i}HBa44߱7+LH(rӌ.0DC׫ P^Knvaږq'gplGQPn/_|`D(ɝO$;42M@Wl(gIgB@5Sm刁ُF=T~q_K ؅[8ީ`t [z\1cn{Xv{xzC+2 yE 4f'z%:b?PsGծB`:)27}Nq=Dk3IƮ-NNxg9eg {pE1ij"h҂]Tg׆]{h0;dҰ ]DdjKsc=vބXR[km_t-|tpkX[D IBiOB*0?z,& R'7?^W'TgĦ -S"k왓{1؛8Rx;NobZt'B|騃M<E~8sK})Zi1@W!f-۱_.Z0x> F0L}2=~Z/ηRGlώ8~-ͽ6Y!uy',jv-&d"WdG`[Z:j6e_N9.MAb-]E.Ņ _r0 Ti "`Vi_@1&~p]P5 k[oN6V&fb: l@ QtBE]y!=  ޯՎ6Մ} <+Qoj̣AOP?W~MޡZ&NUT҉}[CZ! (6_jxI!G0^o@?Jߥh Qc1T\lhxl3iBG~<.%+&.}`! qBJz} Xt6 }+\&V!o6iVmV|mNëLg"UG04VyNOm6&8SyV, @C?3vg?/zq̐q];N*g۩ F/+[Zx<jmOJhvRϯدT>>0"Y/[&i7T8Y8'-8TUHzF 3ձn.WB%K1K}GcKQ'/78~4_\R ڋjq6g:1"Ϯ=x})b<A=֤ң`߃uR+v,ZQJlT sEb{̆Lw\=JQb$s5DsDzC݄wp_iZYRx}\_h cP?VD˸;> endobj 5 0 obj << /Type /Font /Subtype /Type1 /BaseFont /UCDSEP+CMR10 /FirstChar 126 /LastChar 126 /Widths [500] /FontDescriptor 4 0 R >> endobj 6 0 obj << /Length1 1463 /Length2 7128 /Length3 0 /Length 8113 /Filter /FlateDecode >> stream xڍT6 J鈂@ޥ. $@( $қtwHRI"UEEzC{}+k%<3{?Y/;!a UA $@@@; ODpB("`O j# HI! Po ) P{ mE+"ܼ0G$G7$!!; Ep6u^0DhvD$(AC; ((  BP b9P h/0 vpuEWktݠZ )'W!w2{{ %Ơ`8B\=05}~(;$ @\~qUp" G~O ][:^p߿-{bMs+\s"@a}9 Z ;p_Ӏ?D('Fz@|;o@`vh-'Ok7/0 x-?ϓյ ࿏XPH6<@`B~ׂbB@߿Ӂav__$7?36܀3[Ev_!_Uxs0?(&пX yoT ?탡T`(DsK<wz\`puA@Įg2A]z25sB"0 &^ KHD N[A8}p@H_')rK/sD;"ƣؠk] b˼mkvH5N!BjBZOׇDƛD9|3T@ ͔M=v: \HQEc)>Οve FR#W)vl,p,8L.B oVl<|4^3Уcrlxj̶0$DZy^x =y}{"6}ӧwY=]aTw}4s$di48+$ NvԖQX/fB;?,Vaq>q'O"S# @LZ bO)(NZxÅI ȋҎT=j3{}e;Zqg8ÁzI'4~ug-+cHF~:Xŋg ",*˜<`s!gzT㫲TAXpA_M[ȵP&AL22%4Zj 7t ՈҜTwTGWôE{!@L'I)/~}ڋf )!j^ԭ~հZz\f"LS| Y[-7LTl- wϽ,4!o4~4 Lnu_zF~xf #I9soݤV[TǴQ PϮ'|%'+V-X7鄸Ѓ,jrZr[҈VWLHЈ(gee֌lF&4'K)OY?X]|y!@b(ǕHb7vĹ" yg&F>D7b]-(7lӕᆚ_,t &П s3oS]G%4ދ{7\QFaPyK29Ȭ$ )XBk=7m$YeDC>)w eh7Msм|= _ ZcqrwOwHOLst&AAj"]L.(V$Y`T:D`̄' biW%ߞr;.SF{ؕU_.p^=i $ᛚ{+Ov0;%VKS@ǟD5VzRSarCݷ\N1 0!;ލ< h@dAЇȫ$#Hv~yZ 4Zz'.3E$8xUc_;#oh(i'_X&)# 4B!kMgFȅ)L 2+ԷR@Gl~)L-<뙧ҡCiYP/B2{p{U|&ߎ@GKS_xS۩!,)* v1HMCw>w+q7l q>RWpe& plo zHj*mǵcEY 0]`Nf +‹XU,Gz2fJs7B Ú!º#1OQۨs2#L^1bb.GG/p*SReA8LZ$xȣCt%Mq);]at8V.b Ġ] Nj=IRg͉|DD: Mغ2 "Ґ$Oޱ?T*$,}v{_' 0&K6bb?KV=5}Jb}{<="Y݄62X=ϓ+pbdtDǍ K gf9UҙݙvIVhO)YPm+t0ZK&: 󙣌"\|}2ק'[!14bM[~`@ ؜ٺ썗2xھz[#*+}zA N㛚o(tG53j[e_RJxN{s&*$!:Aϴ&7. s1'<{yxky=J*.D[{}a~"Є5dZzn#(RQ ZL0/wX1`,]-\oM ۊ*}?O 4ѐ|KRҪyU[*wwzY)Ȉ|DaR=) =7piݓ6<_pa#sD(hنDß'oUqX"{]8'ũdSlE Tc<ʄ"񷪍~rlhč}6ywM13wwI;CޏOuv6V5"z8#@$tT&drn`w(ʳ/܃24˯䭇LZ-<|*K T6E󽻤 v|T[>+xA g7$ogTqCIJ9F~4cҜ8.Uxuu-] 'u].o3w0?Z'!ߢ I*_ҸLCB[<#8/`Ǹ]s/LJ-z*Kk @ZF)Y5jwkbp FLC5()4̵Xɇ{rN.~qIAh9|iykJǀ,LΌSLDݓu.78/zPUvS7Ȕx(+[)l~-AWsCnT2 Y߳wk6 x;8o5ؑ!:&QOKeé,\PTwI)s+2nE Ʀ"&L'/Q`Y [5:uo(,H:|5 qb뺯r0VuΡitrcB%Ee13鿱bݠBӑ0lD%&M-R|طh%0Ä!V[K/SMofDrkI6^ξ|ݵMcG*GGwBYV"罇H3fǽAQT1 S7(%8zHbd F~H>WAmX$x@_^Ȯv`W: V9l>m0W!V~kՅ(,*C8ĐmG8s]Nq|1Y)D6$P-&7.Ϣ˨\^Z}vnbc,lF QぺCឮ kGg]-VdB.>>7|yDMű{xK Z*6[rԷlN8&twq73lX(6ITT<&@XH)>99chVSyY{͋~VaM\^iz8T5xv'SV"ڋ`::ii'k΂M!pPaƆ=*>5$:]ɟmJ^n,eRN{U 8#6 f8^:K Te j[\⤣:bOEAuZFbI@m{:ߙ* I1^jp7?jWC5,a9X؋끩vƏuy_3!df#+u"6CɅ|K˂߭R"rt3dtUaORd|&KN r yM<4[}ZiϳX"nbZ:5@dؓ߫g]l&.  ,\n H}hm7akjuBz[ON m?ɼsq( ψX;5+D:fm2"''gfV$r7#hV4z:QB'cshE]!*Kf%Z¡=~1RZ3]^es8wu,V4Gv떄|5EP͗!y012 }ZnoC7 :,\CXw0@> 9O~fGjd;صci{D3NB3L[N3rI";Pix'UB[i59;\#qV3,q;틹)+zIY5(JT*8SoJbu];Y{.:I;. +SL~ FkVpKL96qYF2~Ӳ m<ٵgge9 p,9'xାb]5$rOA6R䗃ΧGن Є\pL^^EXӻ~Y2zUd1k^|J(r8hcTc#d?#aQbY Y)=<[N' Ź˒@D=\pE-*=hn} Hu.D bFֺeT3<^֏r3+Ts$ )!YL(V om6]2ba:'d9 )dʮs Ϙ=EV §$NL=tjx,3YWub jjAÚސ3e7^>W lxb*%Z L)3drC]榬S.s Y^`-ipc?Œ+~ g { E/ӻ Eis7ZR#ofbq^J{QQ͛hj%B=?9M|KE"/zfmqn .i{(Xc,=V7L fA2 ܽ=ar)(q0! g7I蘹lWP{:ru!Iv! mד àxKe}tH@P6W?ʮqeTŪ$P޵ j;ñX8]4竅7}l7|(т &ohz ) ZZ!-:!u:ڤ:IGlNt `VuQ' ,7 i[k)lKdn_ĢImsꇙw=h{w"ٲ<}S0f9f 闔S~W"yւpHG rPD#6c|6Oj :k<8^4}SBp#\Mɝ\0Yy;̈9 O#?p">LJ=E|[.rT0"{mdQS=-N ws0eJ :E3k,Ko>KƷʈTCx&l/ODCO_ng5%[8"4 mi 3ԵL_&oĭo*_8jq2/H \9 *Ԙ1|:6X[ q#,|e/W5Sՠkg.&K<ܝ+wj*2SzV xma{ClB+]=]V,_˙-F|&'gl?Qt PԄ-y¹Nh;Fb;yDPrKOS<>c$XadȔLs~34t% j;$ӥXvP łYx-k *}\vt E+x1wsP] P3_/d#i;oFVayAtfOCĕUӶ7ߊCoIuaT}dNhr?k*G-i24 'JaaR4Ǔ0[oQBm'2AًsFzm^gg8/e2;cpЂ)z18t311Vb3Om%(vwJo4aCԽ2wcxSv*^! m.j|+As(_NPQ0M!;pCc'6o,( KϲxQݾ,bE;gW]8> endobj 8 0 obj << /Type /Font /Subtype /Type1 /BaseFont /KTPBGM+EURM10 /FirstChar 49 /LastChar 121 /Widths [499 499 499 499 499 499 499 499 499 277 277 756 501 756 0 560 770 655 714 828 605 499 765 783 394 402 668 559 1044 829 803 576 828 609 557 492 774 646 986 666 555 666 0 0 0 0 0 388 609 588 487 603 499 420 568 621 360 331 555 365 915 664 563 589 605 432 455 416 642 495 812 526 593] /FontDescriptor 7 0 R >> endobj 9 0 obj << /Length1 1457 /Length2 7043 /Length3 0 /Length 8027 /Filter /FlateDecode >> stream xڍ4k6,zD3>0^#ZE "{o Ѣ '9=ooZ3Ϯaa" 8(Q<@ ?  [bEppEB!;u秎Tܝ|>!1>a1 A<`u ucEx#a(1?6v0ןp@u|w AQMr8 \Ou"=߀g_xXz0;' )`6P];UVh@97>; 'bcpvap{ TPAyoG.9AT(;sA\PnLך+ؿ2^ISzB";.{(-~T8= A6=))sÙpn_ w28ψhQ;ƌOf H w=e_豰s8_L2?-/d V &QaL@MDt&[\`kHJr輣6[1u4Z}Hwŕ N`rsĞ Q'qtQYq}%F5U]ZS +fO_LN6Z})=_(<_+©)oxZ뇈L ZHfBLa"By"vӳQ|E@gB3yw^eH ψ' ygH`Μj-SV (* ׂ?c;@8FV޼R_AƓɍ[]G HHSq;/?9WI20̓X0̊dM92m2פ![4xjcneuԊ,d7a? MJ_!g'~n Q-49 QӦrݸC`A$e,:x/ =$[KN4^k gǂx+ˏX#?ln%m_Rcm7F9r(-b%QkN&Z>v'NV^a]3 6{SY☊A]'/ I^aPwIkBux5k{? 󈊬\uڷmj5:})<2ղ"m-QM*}#^mo~M; yS|#gx):7mNYbY+,4Q2 QSSeQ6ļ%!2BqpwywDFYtZr$ޅHQ,cP+ߓY  AU4w=-4 B#qY9y#¹6? ~YWX^ xk5k<Y^OL9:C|D;DԛWq _ZWUv@xQAH䭅r#kZM c ,l(Bh(-jլKJk*D[d|Y-Λ~qV k}2׏cƚr-u8fOտQ 䳣Ƕ$o `/yb;2愇]œ@núC*{ ̇ 'y7ǀVk;|Njvl#>@ Pko5~(gl-e[R_.2jdnϵr,7o<@`a$/(bG0zϸ z?D'f14dHV󽂸i(MTledZxAt Ya%6s;ǂl|rG#{j+ /}]i>i.uUx׳/=Z`9p{`gfJ` a1t_2Yƙu-!W;PL ϟRڿ\>Jq@R28-۱R袝!t;uEAFmspj4z;2ݝ9j%"V ?{V͇Ex1q:x>J:?7ɵCAP UGO޽3)]g~V{uzF%}5ʗ;5YNi׊e3S\Y6C{;0c\{wh| [qxA'v\^ٖhb^5u8 ǥ60kTz&{ۈcBtx9X-ϝQic S "]!N0;PcUˊ3Q-DtU2 >i̴.;E0L+f|GZ߿ʣ-iQoYO|Kcp3.,waB`\1^рS# n[Kh4gU{Yioxָ:d3\ StՋb#97{7Q?(kv i E[E=017 KlppQl.q "=uxĘB+ZY'9.XvpvfKj3ѹB$r1ʫk"ZBd<:8Y]O=Z1ihdqxX}Zg[=B_DYKD4nj~bHqJٛ³lO,nvEbnWޱ#}"^$rzW7O cV& c+XΌ2Y=զ>afbo+gh\5h0GHX<,97 n|\xq8s(۟(u?QbX۸5^)niROq3XYypX;U״z:ց8HM7:OUcw+],iGPǘo-#楄zM~ 0;ۇ!!8 ^qe&ǚlF*~[1,)w`vl>|A&êZ\*k#ޏĝjH,\^SXIiqP\\%aW@( 鰰 ]S0|T^{_⩵>{5fgW9'eC;U2U6sǬ;HuUJ\ t!Bj-@vDgȽê!Ƨv',}o;|X-le)_$9HYї'4(Y4bvyE*hرLPqk+[i:Oέz~UO%NbnF$ endstream endobj 10 0 obj << /Type /FontDescriptor /FontName /AUPZCE+EURM7 /Flags 4 /FontBBox [-5 -243 1229 720] /Ascent 692 /CapHeight 692 /Descent -189 /ItalicAngle 0 /StemV 88 /XHeight 460 /CharSet (/one/three/two/x/y/zero) /FontFile 9 0 R >> endobj 11 0 obj << /Type /Font /Subtype /Type1 /BaseFont /AUPZCE+EURM7 /FirstChar 48 /LastChar 121 /Widths [625 625 625 625 625 625 625 625 625 625 374 374 915 628 915 0 694 931 801 868 997 744 625 925 946 506 515 816 692 1240 998 968 712 997 749 690 617 936 791 1175 813 688 814 0 0 0 0 0 499 749 726 611 743 625 535 703 763 467 436 689 473 1095 811 697 727 745 549 576 531 787 621 978 655 732] /FontDescriptor 10 0 R >> endobj 12 0 obj << /Length1 1386 /Length2 5927 /Length3 0 /Length 6865 /Filter /FlateDecode >> stream xڍtTk6H ) ݍ033 -%!!-!% Rҩ4H#tyZfg{k}_{aaᒂ,($  xYXt;vb}B/B0W@5Pv|XP,$ @ qA5n@YdPN+k+B){ Aj5"b蠠8_)[c0"<<{47JpE`m8({j,5Ceq8+ GB0pUQR4ȿ8? BQ;iX"*7 @_@uq WWRZ~hF#~qrH ĠO^ݝpm(W'KfفGpt+\ccX wnPk_t࿝_+^( a #DC\> p+?ٯp˿WwB&_ޞ\m sytSZxr>x0H@^Of۪ AʨDD\lpg87 W~vW~~=_2VU@!}< D`-ݿ D>4/ߕ֠WՌ~WRwI9$9^Aq']-=W~4ÍDaBg`r"5I~>JPg'+UٿϿ ēc(hME@I+(` x*A°9v_DaЧgtvNL[S1XrK߸2{N[ 0ՐPBn= [EPZje3zXx(0Ri͛Œ0FTN&κJzCҍ//DNHRV$ eƴ;4)ʵh,Vi#Xھ~qEleB ̧sV;Ä׌wy:$&-aNvqWè$f'U//R,I 5{%c?!qlʻ@1ԯJ<ӄ0y A>Mg9aL^)^U{;[*c|dS'.E- =!P'֣)v-8#? Z*Ȝ)S<[&F^l`xdZrsHݗOvׅO>/aJB%X=8. v4|JMӨcMT+Uݑ G祟/xu BF@d?)17 lΕӼ [nG^-$n1wCx[ldwz{svDFs+nHpo^)0g+Gmˍ=lwk؏:t$4 )z:v|3$' = 9E25Ҹ1ǃ>yeMY9fvNz3"1(k äqvv [Tw S\zjiӐhMk Cj67` KκCR|t⟱a3UpEO?xZǴV:a;OehD V yx4]bSh3Je3R+kKWg,Z1ȎRŒ0C 5H]vjlt8Sm#EYK?^UwtK8m:@z-RCreG5dVY26g`,OnB>~=Qm`9E&HQٱ9[aA,f DπP^| )Zb^c";~{}B‘ ̬qz?n|giHKՄ^s/a[öE E٦h޻=,<ַ+ w5@1sZz:+wz /jn22yV'N"E1 .K%fDa74p=Oӷ)L A :h0Ʈd#Q~{AQ]ܮ1qCdžы)rЮHy- tm+0 A%pn9qL|t!gBuWA2^; !hgh~_J%0m3_1sR=AAے`R ntT-ElXRbN޹Qq^yXz~ڏO7.f 2ѡ47J\5ǬSn2em1PD|O7!*%MMi%g(NP(~P ʽF~:Ei12n,پ O46ͰdUKF4!?f̭(r{ $vOc_sw&.꼪.E?} ThѼRkk Q]5 sMbivhyڮ_pCif0 FrrhcqEQS'D?ZP&]Nt}㺝E1ecչ;8[2b{z=W7FR8 )?*,rIDaQKTnT"PPtt& L&R=}ጶAek>>$Io\hkGa&ҳ0+N1Bʤ~X}EHRZE 4ajJeSCk[|zJtAݞD=CEFE[N/B*1˰m;f5o*NBFf~{}hw 2gnThh>4x^=G̓뢕ΡfcEBXݤfvU-)LɈu" CJ}~ %TJC2TE|4 c% J-ݻ(N̳X&Ks (~ѕ?Sa(X0}"!Mi 8hMݙ6^"8||~_!xp>QYx=٤&cAD.'O.%u'Oj嬵4V(ծNmi؇g0.ix)ܴ&4 ;zp>{Xd/HT5b%|b5Mc ɬsgf4 `4ZH&n~φq/*4io\;eQ:WȈ-Bx8S6o]P$ٿ 4eyxJ#6)sS v<+RƙG[Zi[ ?UV y˕ɆE e/EĴnOLa@_ >4h`P5j#VA^c:3rYkG~dRJO#bǥ[StDBԢ\Lq엕vһr8Ł\ dDP]\>]BcRWLț"w׿KusчPiQ,FϺXš^GAd_|!^Mz6s`fCa#BJwehwt"SwoŮ6,NOYPU:,)R񞚦ST3ZoR\2 2H 4MH,ɚے?ġ|Js\UI#ۏqpC_xg041ٖIgd=fQDW$!S,-}Iܺ=^H@$gl TޥN_r.nr~1ʵqMZ? KY;}5éG4ޝLUyt+LkhgC8b #=kckuIN\N WRyI&? zE\xbbU8NbۅhrGDg-oP컍4d[H3Ϭ"転.,*ܕ*KAG"xVKv>@7!)"mlٰD>rK&g7^|?R(.1K1l¦h&4_tsO㞺mnCsb7jA@(-t`YwS3rmٍ{夵_ȉ咴amD~7lp$h UBQ?k(o~S?a] Zdfm .[:!zK7\ ;?JiGޱkldk7zBmn6(vʅ-&Jo5 4mڴz@Ft5cJ c WrO5lT yϴŔbX:Zw܏#6{mOi=Y:%g@$6[8/˯p |R|Y. endstream endobj 13 0 obj << /Type /FontDescriptor /FontName /UGTHFA+EUFM10 /Flags 4 /FontBBox [-28 -257 1055 741] /Ascent 691 /CapHeight 691 /Descent -189 /ItalicAngle 0 /StemV 76 /XHeight 475 /CharSet (/plus) /FontFile 12 0 R >> endobj 14 0 obj << /Type /Font /Subtype /Type1 /BaseFont /UGTHFA+EUFM10 /FirstChar 43 /LastChar 43 /Widths [756] /FontDescriptor 13 0 R >> endobj 15 0 obj << /F8 5 0 R /F16 8 0 R /F18 11 0 R /F21 14 0 R >> endobj 16 0 obj << /Length 965 /Filter /FlateDecode >> stream xXM9 ׯqY!·\`%NoӠ'oIgniŨf8v۩q!8׉2؜ 1阹QǻeP4ww%ܬyũt۸c sgNSrkcyHz8^vTB$YG2nbEpЭ{)J iqA0Zo©.=XjiaZ2ϳNV)=i Ewj E}ףVu?SK 2YEq\ʪ9HfCբcj_B=Q[s("dT XߜHvv`ϺzQj3z5ŖнT M!Bٺe_,]}E(\iMDw}%D~ӱJ^װ嘇DnXy7["pDICR ֽ/sOES?w/c~{9ځg&ϒ|2}xv1fgy6=G+םlWg> /MediaBox [ 0 0 595 842] /CropBox [41 635.2 400.4 841.643] /ArtBox [41 635.2 400.4 841.643] /Parent 2 0 R >> endobj 1 0 obj << /Type /Ipe /Length 860 /Filter /FlateDecode >> stream xX]o0}ﯰUq&i/Pm2ՄDIJaU j LL%8nj\aږW@Q*x@f5u^/ШBJҮ}&8Cc'N &9d=NI8 i2/ 0mij'O-,}Jm:Uma'>YȢt)$-dĤ45MUYG S/w4),(̤&8LJ E,0Y.pkynxfҁSd>̟;()a&%RAP(IARDns F{(0"s+g7&ue=t( HdmRu^5iՔ0~r υ#Z^ ú^܏׀i &b$UKM+.!}X_gaaC^`9EPٍ(y % UP(f<QO+7r@}%+=vdPǍs乩𿝿f\;Dؚڹ&BvXMHa !t,0Az[j6=[t0${_>T6u+z[ڥsKPꈨG~LIP XmNl@iz8bp~Ҩ d> endobj 18 0 obj << /Type /Catalog /Pages 2 0 R >> endobj 19 0 obj << /Creator (Ipe 6.0 preview 32) /Producer (Ipe 6.0 preview 32) /CreationDate (D:20100223142155) /ModDate (D:20100404141917) >> endobj xref 0 20 0000000000 00000 f 0000035479 00000 n 0000036422 00000 n 0000000009 00000 n 0000008791 00000 n 0000009010 00000 n 0000009148 00000 n 0000017373 00000 n 0000017607 00000 n 0000018022 00000 n 0000026161 00000 n 0000026397 00000 n 0000026819 00000 n 0000033797 00000 n 0000034018 00000 n 0000034157 00000 n 0000034224 00000 n 0000035262 00000 n 0000036482 00000 n 0000036532 00000 n trailer << /Size 20 /Root 18 0 R /Info 19 0 R >> startxref 36676 %%EOF trunk-2018.02b/doc/sphinx/fig/sweep-and-prune.png000066400000000000000000000750351324306050200215430ustar00rootroot00000000000000PNG  IHDR4ƓsBIT|d pHYs' tEXtSoftwarewww.inkscape.org< IDATxwdU+i!dd%$"QD1ADQr 90C9bj VZ {zw>M%klQB!BC|ks7KXg4=%],aIB!BfuI7;{~d[Xx|e !BkZK&޴|c!BK*}O7J xXh!B' )QpsmD*ol(!BWMwI ,?OyX``Uœo B!}!Q:T$B׶3́ YI |!Ba*%%?ΟO|%0m_? ^ҬfkR!B!tZm[A<yǛ%=XX9B!j%_`mIg y0Rpi%MCXHNH,!B!tt}y;grZH''-x (iUG!B]k@#D, OzT;rf!B_dŀw3ksHC!BZ\aV6$tܻ6"iRG!B/=~..iZQs-xp7[d(ߴK}&޵v}&F'B!n{Eg_F`* l ҘB!π&V"iRF d;twI XZҼ.]`mU"b !Bi׏Imo"[[B 8w<叿zd ?l]H!BV<.izI8[7$P8I?UF0SyB.>`~`1M 6B 4bD7&;9:MC%IIuB!Wic:D7Y/6"Zy;^F4^{nev &JahT!B /u~n>$eJ:=EҼ}DJk}'K X E9BYD}z%f^Q}&q3l\mꄲsu$v]vQAc|.ؾqÀ)HL3qϯޮXgۮP6u ෶?q Ej߈w8a{:14ؿEqɶgu:Ԁj%"iJm L%ˤ0Hڪ/04,5};]kN`R۷THs&$=CHډupi}>C:^a_G5:.'u1+'`ҝI}=}x??(n`x~L FJKפҧO%}s5vdQAdQ/At5u<_|4ý^4pW~l#icwK;5;C1T#޶wj5l 0/#mg5Nk%M6#HXRb<&m7Mid9IIrl~搴-sKJ/ml_hp&Rx5 fC1T#)`Vi۳5ZJ1{^)"Qo_,sF?Oٮ7_(IP'HOW1BwK.{)M$X¤_`~$,aHZX83XDZ*-0D92Br|)1xTkݱrxi4$}XHC5Q_*>- `oeEE@#ǓF06Vaλ #Ķ IogK5|QQR'pQ~u"O~߸Ÿ" !җ/D:m?e!#7mך1$f/٫ވLa+w&k+w~)wX87U[/J_Z+ XqTqG%(eܛY[s۽6i<2J% liH 5O~oS"MD=LWU`vH/H `@Stwk kNEjyO&!9p1pb iwZ ?bAwʤ0d`FbR?R)Ik*l?P%Tbl#I}{ %i Rb89H[jQ`ˤRˀ+"IFW$Fҝ_ہlobH)F`7IJ1 qiQFj߹ Me/%ڞQՎe~>/{I}T֦F2~KIS_CҷIγ]orhW")9#F[k7J!iR`M98va$MO*[tDX4#{ۿb:4'l(>u'沗J `)a `M&|a K؜TgCHIz]FٹMME]w=]h OC %%狑FwhNW]ˤc[%_C2 i%ځ:8n!џҗJe/%*9x1xKҫNc;X7CsP;W[B#H@:9R' %mnjǒFZ+ӆ#SKh$Q}Ih?i\8\'nݛ|_ < $R69]6IbY}]IL(L.ؙTuyޝ$Fjvpˁd>ŕ7]]MN^#XxwBH* ZiߐzBDſf-4#imI߳7H iE`Gld($WpX-&)B}ヌPz$H&"-+Dl0C,>t'"cAk&GtQu2bԟ&۟G2>%_#]jI~Tzw, ֙-QT_m;++J#.yVs r~ i q J)Bȉ/s{c%]KפvΓאnv/::͟OJK|lVZ;/Y]RGW+m?pC-AJZrX({UplH8Xvц`L~~StXx ZAjW/HwֈFC!BҪEBtLN3\iAsBr.BHBҾmh?c#GIoN%h/y"WHZ8X= &/gD;4BRT/>n҇omBQ&ppDnؓr/Iw}]AqM&[:MjT06?7l?sFp۫Jh3%/W*/_z2Q]Hy`ni]8nN ;/pl`HCXخ%~*ղ=V:Vt#BuJK;aVH7$}|#eWIZ!LJ8X%W'#S{wHQ}?5piuŚk>9%Q/f}}!5g~$F&H;^[s=f (:F^4ڃ3B(D'u^J&6kA8!L2QBߴ#IM#KŤmuBh/ئl֍^? &jCOIM&^*{g4o,pI !i;`sח:o5H:{`}F|mSzlN\ ifjojl.iM@I uA(I2[C9g+hfk$h&^OH !J_B0o#OkB/$}Zk'JcuKl_oqr)J6Z?4]SްU #BMy!ˀ?>h/4b9Í,Bm ̚?7l? h5|`KۗyKUyLPLĝ.4@=\-"iI_O`Jo<RziB1r m]agHGፕY>!v^?1ե|B'8Te'Jإ$vX 0=i~j1z>z r$L@0;`3Iv"^sΔO[VVX&k߀L_1? 6 KZ@HCɫe< ~}2`ÀgH+C?>Z xsOM eE"ݑ nc!%TYXH.}XI"ի "Q!|N҄yzη#pyI4tϰWIvzK?r@V/iJXs$-GWɤ!rnȨwMfH8wEVs]/H+9=ABEB0fI\eF+65|65PN`3`#R1~>'$m4B04dyu$Mوs4( _/!t^kR eք-6g$M le&]f6]^``C |۝Ƶ% Q}oݽC(} !u8ɀ65m^`hUyz=Hu;x WjihQIZXI8q#7ZBO%cc Hv/=+#%d`_ᅭ#9HCQ&VϣDCpnX2)0'=RFol?S!/3o?II Ɋ"gSI#n\Z춟wI9_oC\$!SlYj]| f!uf1iDy ޶h@ldwHKzNl^nm&9`JRƆ&ߐҊ[6!D=$i.` `&]Ʊ}_?rj'V"o{"i>`97=p:ǀǚqnIӒ:} n5%}MFq14.#iMI_A2FMF<e{ۿ@}&yF$M L2Z[;b҂GwR+#7n-jZGEBo1pKVl{ = _+}FC6j8ޖ'fEKԨ[~GZmI #_JwIW{l, iC ]&z~X4p 0=p h,@Z+%BCBDB؉tk-I8 pb#*Ics" !q6$tHC^=X*ɓ\O~GFU_G bR!+!& ix,!'O*zmE`eoR$ #:m<,iBJu EhmHm6OU0_OtM3xgDҿ{LCMdCIGYt:B^&FCh&zD=s9JAϝFIÀ%yq/3KZ !fF`AI&i +HC>DHl>:pQlmuB[K}f-:Pπ[tׁ)ZtBu'D}.:P]0}-_5df˥o!z'po >.n!O HC>I}Ĉzj`!O$oZLjzj`!O$mLDoecD=6a9`JIKh}|Tt!4$yqqBF%OKhc}Ztss=ou M-fm?k{ m*@Rm_E$QB{X BD= %ISVD=6bU`ISKhog.iIKsH:Omic`}Tj~StMXt@}ϻ80wz)J_WLSuC] Wt }E,-I~;,<|8D=4[} n~RtE\OM.Ο0|.zӧȵ6x$Mnݢ )jۏ۾ tIkJzA҈>.پvK6/%iiI_+{SIsSy Eqwf)!FDh_ŀH#.wĉIaɤ:|D֤}$0%Z}B]cftH󉺤)sG$G1eǏlMMҤOHߓ_F^$0aNB򝵏Hf aNu`Bm4MĤn!'æɞ受$#:I}['|]Rf}okYm*`/.z O2jxVm!*ɤ<\?NJ0sۧ~2O6\ҝnE܎l?^^b"m!_сύQ\7P#zV$f{ vtKn%;P_i梢 \7ڔŠ*:ОӐFJ.%]84k^ti>IK䅐 +,icA DH2t/!gDhOאGKm4yt'n `Ü|Ic$m))mUqC{(˔$DES$M5H-?}˳ھw!M53 D_Mk:IzRܶ+:PDe nF_BWG& %S`{{BRa]IG۾z5I'"Q/)&a^sQB,^t D8J6?~lhy3l_^Pͤ,2&*R60/ō_k7pB}9lWt Ĉ8#]vlX-揱}k,TtH%t-!ijSo!#H9&EJor&G%gqwm!tO$MW1sQ2֎փ)  w61yhpH!0eA#$ `M瑺qtm|"lGwE遛l?@{YvmECMH*kIsپ ! +EKH%HT?T8p"vt i+S%ZjwpBѱ' !+%!'F)(:6viIH9N6 J2!p+ ! ;D*PؾX5ўbr% /(^2-4/ʶMp !wI{0Omk[%t =g?).v"\ afla !ۢz6Tt =h ZTWNiGsCx"Q~ąjYl g]/C;Tt=h8-H%M 8 !4\"Q c\c _d1II+KiՈ%gĈz(t%} i" v):D]DͼNi%FCUT''jioIZXzH+F7.zBhwPAP IZ WW3R smW#VJwֺ%aeH_Ĭx϶uj!|oپΗ+*<^އB SԸ]G~}`f]#tQ*A ۵ iwb۷mUj۾SEaj,@Q牺 9y 0()9+=&m?ɯM?Ur?P5Q1RҖGWzc^WNfZ{.Nzp p5uF_uϸQjVnC!S7Ē]oV ! $"QISFU QlDa$-)12p)a>H#'Wcf``(Ib6!$@z`FT҄ɣ7!kz D>8kN@Z;k#%֪n6$MJ̣u͋%m*iU7 WT+q !tHCED`(iyZ:z򓀥۵$ < 8Rz$CJnPJo3  Io6ci!PQg I#H K_B4H@ױ5I˶yy*4Ii'c @'՞j{IgV?} iˁI'^y>hěNI$8tKhZIloBhkQ1Ѓ"Qe/%=]bBcz?,ab)I.%M:ĐF'0ņ$ldAb>bilZ`'TR/-&k.#zmZt\EJ:GRWϵ}ܧ; ۤڃ]dz:2z??#k29]RNb{áڍl?iQ6c :Q4 i}]m8 "Qje/%/"i*Ig4-mƢcjg96$kR]%{àuI&E_`GBh'=}G8TzmN$M׊`zŁkI+tnmÂC߶s&zIЭ$yw`I"x<~M*N%Tz?`^^ʏ7˞Dq 7~l:#Hmԏ?#u=_ʭA}HD;wnZ`!v211*b`3ITl1$h9i9xX. d>IGIZؾV KJQ`˜yV̶S.M: _[ZHyw#Q'OU!G <Q-?zY/FwrR_{loIzHV{EJrIwytIee%-uV`oBˌ$~@BD=4iB t4=`jw(*]=iUӋrWlL^sWw1wHIrIWn;/nyC 13 ݯK_$}6/gׁuJ@줖t}At3JzB_$\+iC&Ҏ7X ~l_˜BapwA:V|"}DyD47py^l 'Kz\QNtxrI3sB((ഢJ_n %\5̝-=9Lz$-d2`{+%;f!}p&pJ.1Iگ?S{3;FVBMȅPQ$7z7F;SM888t";]B91Kn*i&׉XxQBFc"H딿IZj;t=lTgSIjjFKZYۻa{g9_XEՒ޴ ;@^\;tΤW!P2]t}uDMOF= V&nǁ!ն݌ŗ: 8O%-9lO s6 !iIg~H\uu=jb^ 7t6! 0` l?oC`]I 'I:QҼCIZJElhԑ,DDS1#3T213tL_.9tIIA҂`IPҒ(Z*Rڶk$0.Q!k"NQe-\`aK%K4[Zd9F?c6IcHe'4 ˓H =&F"/醶qx]_4A\Ym?^t,].iwJ#l?[MDDK4bl?0cCMpTHCE픨Oc?&u7B2Cqim~R~"pm2sٍFl(~m{Aw2i.RqO-i``Ţ4_ϽGTysBbo>e!T6>T|}/ }|ٶeo&nIډԾ$>4QMSԄ[2}1t#wKzOҒo):t-p^a iJuuT{ˤ;u8zαBVh-T$)JTJ!KQHȚT$=$e}_p޿?̹Ͻ{fy?y=\s]9g>}]=oADKK×6+mc:1Q?2)뵉URh! !`V iA۷K4Y/}!z Q[HڅiG oOF|})6;NbyIKZCuCҼxۏsmSꯍIG;U+BYtWI+wPw㱏Gԯ IDATPڶ6?Y$ L}cݱDؾPҏ$-$쏔.WҌc3uJ_S],Ye=;UM/v諏=|jH?-yF_`{3/92y5wS7RDt- !hs햨OTu/($덎rުaۦ޽lcڞUՀ *FYG$5Oʞվ F?mow/=!EhҗW]EҬ +[<L?+(~VwHZ2pZw78 +\ZUT5oJ`=bpe5歙: cVdQvQl+jˁ}(]Ϸg JqHbxkWD=Io#"blڌ| #[W75$*i1}>ss[^4L.GHv ۓ$]CivaDDkHZ3u$]Z2ڞl;tڭe(ЫjeO߮)Y9m?Tw Q۷JZZLMXۜ\,Etj}KtQg2@6꺃/Œ!8eeOKD4ǀk3R#.@IPZ:AŰ,N&mFq lAt$@"bF̰(}=CIY(u45ŀs"m$Q!tU'"f;mQw,eD}ت~,lhz#zQFR#"s}0cĈtd.ifJJ:j ;h KD_fI;>IcqzUsHQJ^S`9`>IK8xd1Y9S0q%-i-jԇ@J/cz͹?b-t1 ؄QW} 04ۏ؞ܢcRM+Mк!Zt`IԣY;h D]RVhISV &=c%QfHD49ԫG4WU~2''Oh( ZDFGkたK?pHt$,"Juml -ic $mLxIݱDJr#6I;Y)_;.9VTu?"uQ$ ٿ)ѽVqMNE"f@U~EۏOt$1b>*@VJnK[F|N,cۗ9|8Fh 7Q$m]"۷N^l 7'S`~I[y#:]/}$Qc)Ic:3@omRw<1$jc > \ /;v'iUzKvhkDb]ҺW'U>fѺD=Su4ۺllN\dD#Ig[OD4Jv~5k>DUQ|C`V`{;v"i,˶;ޒGSٞ \lTw,16#m$Yw<ja`_OD_G+du%%`)Δ4x"$i&4ۿ;$Qf\ύjnYIsRIk;%^j PwO()͎}d݁HPG1PLj6A&vm~uK۱GKIxꎥ"Oh#3j7I Mݱİ|C$b4I2KݱD Eh%p1vjAQ-Iߪ; KmO;Ht $H4YC%Z=%41Iڛ(&_;J-S-rqݱDK|8u3jeucq>ac$j߬;h.I3_;Y]1E4C#wLKt$RIݱDS}uaǔnM4DDZ9Ŷ3i::u]%v[W XXl+QJQSpIZ#p'b$2#&iIwC$`ы*Zh_Aj/xښ$-UwCelJs$x"[;N%D$ AG$-= DHz76ก\բ3`A&iھ`)V;2{`{p؞lD=FEk7ʯcUW\1M$}x""i¦Ys8MD=F틀'%mZw,1Cv}c݁j,ov#i&I?ldch$1ھ -iJHy3.bqJp%j ("鍔0m?XsHMD=FUoWdI3O NҬo(uO>Sl2!D$}`{j݈1'zje%PWH%:N4搢HYҾ l[wLD=7HOBi``#1IKRt ISl_sH-D=]yW:h3:|vTg:S+V0ΖcM҇N2fH[Vu'zgdjWKŌ4.}A6r@uB3ol_F)AR V@yX_뎩E ^EL#zԪW?83oA(oeuZ0+RʸPE qNiSꎩ[I8u|Ġtp~ТI }m_UsHɈz?;n%I;<n&#G Y }jIң%Qv;0H:x$1CGPj/BIW7Iot.ퟍ 3"zme%PFUKp|xbl{'IڿIJpOgшG[=2}f:ZZuwh6VQ6D ?ؾ"N1IVŮn051sm? }$wj?}dobۇ6`h﷽9pp#$-QoT1Z$-"Jmul?`KugIQ6ZHa]fFf 4lZX?7GKz پ: (uhÉ.d/ | 8U-m^sh 2wS`>cR5q7}'7?To'I:O뎩IGط7f3)mGK.ArJ_dg(+KY ˟$ aZmZrάc$- X'Iz? t1:$,tHd۫>+Iz _ 2>) Xw \f{A_4IS'ȃm#Ѯl_b{IkW U% OWD'Ŷ%_z\%/g-ORh1ۧI'po~M5a```ÌGVoKSܪk])*0s"g(]_g(_myėF^%e/%>jpDo|CSS?IS&`cMG4P$y2'cͯ%e/5`M}ŚC) c!E[mXSDIߒXuj͌=$]O~9mXotc2C]r7=6Uux]OOD+y{0k]&vItW5dz댣HZx2%%i^l?Wk`cLǁSm?Zk`cElX ucӔ]*9^.pچNҫ8i{-dӣ$.1&i IP9u'9)]#Iқ&NA53@ҸDrmb$-1ȷC1F RFlGY}&SSКF=g)9ؾ%f/` $@Dvg@(Ѻ坔_I[ň )Qն_jMXm2qEHdRJ*$ dz>IK[QzhUBy9py5|%`=PISF/$i$ٺ*Tm\rhD{:хe/=lO`jdؾýS$iZ@sOK:5R1*iX]Tո^b{:Z|G&}"xʊ)ϊhCIԯ$P^GC~Bz IQ}=\WHRdb]o-b1bU.?4+Lf]`wIk.trU&`ن{Sٞ |bRחv~lZU4m׶2k;t}t`;(I5m\LI7U󏉯%}X7%b($EiS%(Sw2m[6aڄ|iʤ{[ĺ㍁UCGްP 7Q_X,i҂щW`tkQO|$G`w;e"k/4cv7.)o=<#7k)D= Ioaj2.JɳC~:LL}}_3z_^On0e~mLbGS hK5(೒lFi1w# =$]\=\Uw,gB`v9xJ֎}YV`uCfMpsqu*edyk|=ִߊHhS/U$}wVl?΃q0|xz/:?mxr ?D}=ϣua\]2>mIkCC˟(n$~P)?<ۻr~vv7<~ yX]|&P2IAb.ڈzUJO)i!nL[M)Ǽxo{jOLd|2ۃ QH xPΣt15~oo>TJǀ{V{mKZsǍ1Jt=e񢗁=(_m m$JZ8x7+د(1~(i>\~C߼a UFtu=_3o(C)M pW H=FK>vھd,J9T_(辔歛5;3/jrmR6I$X2'(;4U~tdadOU??

    T&q$=} t%xLz%Q{gPޓH{@ZLIRJFvv Vn](-; )s.nJo$}@wMcDQ?R>9eUmrlz$]<;k(I_6|jÌᨚ\}}`Ǒpq\w;IKOGצ6Lis}O.N@<iLԿJYaUJ].>>zw#I[԰czR]r{CWF_߶h=$ %i)#9yێ|vIDAT2nJzÉ 5kl_?/PZw7ŸwJNn4 xIҭ>^c%""""FN maM|x=qpI/v@ň1Dэ%l tz~7CHDDDDD;۬5MVon;(C(i^ʌօu#Z]DDDDD^|,okvPnإ/UHZteVQ$|.K%}x;l鋒.}u}EDDDX0ɤO48fEϗV?$Q,]R!As^Im\w1-I ~q6$V#4GDDDDq?tp$&]Ï/""""kM6V^Q^XxE?_NړGDDDD9Ƥ:&퓆5]Hا8fo"""""03\Щ#DX[DDDD1D9Eݑ헀ꎣHd;mFPZh?L+ۓ!5IM/I%Qh'>9e$Qh(uQKudחJDCQ&i)3q&"""""M!zDDDDD[JцGDDDD$m(zDDDDDJцGDDDD$m(zDDDDDJцGĨRsKZΘ"""Q5 \RgP(%3=I$Eݒ;jcl<| 8֨"""ڐlCWm ?!gˀG(2 ,c'F3@]&hx`Y૵F|o.&wI#"bLȈzkDX8qHxG%,e{㊈hǐH8p+/ֵjEG]`Mdk}~ ljj*96= 4GDD$1(Ij۹k /:LJUeI6%WӁ3z􈈈.#i-`^'&>aY)mnmm[IkDDDm=Kwmv7QO6vM5jہu(#SF꙼zVqtuv١S-9J`^(+{Z1& Jz2}훚x٪~$-Ji-c`Jʶky#""V.bnI+PF.o4er >Puӎ||[`3MΣ~\C~Gx$\f`/`&Jq>↾[7?0U㊈M3%pwa/.xq=eBڤ5)`^#ied֏S=}%퓘2WSk0w.?XXjjU~et]o}eu&|nm#z&duj۶-݁%]m6OI:Q:2J~P5( Ӝ =9U҆y'i:I/;>|l~bHIǮ$]<;cl'i7l/$ .i_Nn,TW}צgx#Ip`O)E+Q?RҸѿlOQcJ=Ih{۟gɔEi{H@8\{jDRVsQF?0xf$h=IP& eʤ({ؾc]EYh~{i^jmD=&QF,UIKZp{$""$Qrwq cc&h<`1$1*l]w $s=Ht%Qcl_OI#""";^F#H)jۋymO%QD=" b?#""Ɣ$]N:զHz Yt3ۯGDDDǝlg5lo1<{'4u͕/-"ifI_tm/Iۋ!i^IKRնI:""ZJ_"Zg `@I+۞Rkd 8_^iu2:kW:>(:w|8Π""]j;\jԻ7Sھ"#i&`9`yYRDDɈzDo$WJڹ֠"z$ o `z#LI#:$Gg^݀C$"9`ਚcXL%I~a(NO}VMU,lpc-Lb񃞁ǘJþaj[ɶ`I2q:"b%Qhѧ$ۏK8A;m||q7l(3woRlCV^Nh#"H: u\jv~'}QFO`/`Sm8S~"->U >Vپo[z;lт-3z^^0EDt$d wH)988Rz0Ξ fJil=_'i񔉢Pʵ&_DĘD=bIZOn} "eoRFO"[7L;>y1Gґq$.i?I5yͶ{ʫԫ/ԹDQ%LytW#?}ioO VIO+~|y/G)w_Ѳ>z`{IS_۾'1S;[m0+퇪m.tQ IsS~Ii%۷53ވJ7N"ifWTaԲguŪn>60pI0՘a w^jNj%1`MS'I۾eǼ3Nԩo_Mx^?{ce ֠vIÉ1"[%QpISC~ed.xt F1U(&L9{ $Qok}$S㆒7$W1G%l{(i)J?) }Ggb<Ɔq$~>kvUyol<}`'M,C&""/zD$mL[_ xwym48xYTw~5&׎b\_xYml?3cGDGoNJb}:0>J] =C$ҕc+uL7RJվ)}i1kB3;PpM8~DD4HQ3۽[+^$曑.UWߑ,%iߒ2uI{thֱ){m:Y'y(fў&PV~|zꕴ/Ceߚ2>yd=#f'C0xpw2W)hאoEi犖Sl?0cP>PE>6GDD?Ҟå="i,oZҔ?sI:&͈eY2lKDXD%Q;$NYutal?RsH}fJ`3ۯRDĘҗ6 iVG8ppyIQ3IP%(Iz% &bߔ51%Q)n) KSKT; X/pVꋶ?Z[TcT] O $bW$"d2id҈i\DDDDDGDDDD$m(zDDDDDJc%}H҄R>Qw\4W !M;I#bLn5AHZ =pÈ/X$1m xp=:w 7ׯ"bcn}H*i`SFjD4[ھt470Gzh,xQV|`n@o5AH.`Q%۳R /P \W]nΖ:c'#$җ$ѰSc%Mm?а}[!V>, oj=끵[T"Hm?ǾR&o]=欞m?WMt|<%IIgJZa{H @җêxpz6؞<] ?dׯ'$=%lI+5{?~ I$m ͔>|H{m`ժ7D``oi8B}a`=VQ<:a`#Ikھʶ%}cÁl__KI#iОՈ)75l?P zk6J^^ׂ#c{ߞfV:[m\|z5Mҗ:'{U7]+St{m~>GD4W(kA(qovl|I#[L@I{|X娪K{$޳0E1)6l:4p9˞z\I#[?H=R:(|Nvnްm J9$ٌE^I~DyLd(@)[:~Z<pp> ^ҥ=+~D>Vh|S#IIbS&6njQS˒̕%ҍ "/W gjH,$8~`͊3#zDt]$}y^`YJ˃.)o~<$m,Cӳn>H<ܠ|5Jr`&JW Q[Q]"\Jg i^硼~<|ھRuzd*YsN^۟f\1 db$M{FtI&Q-xD3G9&M3!$/OR>)i ש).~IGʈzDt4gmUr S?9o~+Jy۫U/+H؞1KwcDU) MY(uñf12zZnI51]7m<#'4e;(]ܟPsDL1DkSA:BIRKR2OP4.`!;xX_Fp%SNKD="}^6IL}FkmlWoJULalmJҳ*ՑO}HkZ@Cc,Ic#=G#zDDL{};/w$-IPG#sL$/PZ(ߌ|eDPI#"H]ۓ}۷-i9`J>;~5I{DBSGJZJ{)_{NҞ1"fh{y)IV-7sGH>dy˓^?al;آ~I#"z43ǔa˹KI㚹aDV;J#\JIkھx""J;۾xbƌ;6`u=kQDD _Oȼ~uGD> IENDB`trunk-2018.02b/doc/sphinx/fig/ubunut.png000066400000000000000000001004651324306050200200470ustar00rootroot00000000000000PNG  IHDRfU˖ iCCPICC ProfileHWTSI۞{oP.HRBtl$@1숨Z e, Z"}ADEYuMus=ޜ<<3sy/}lX**Jb?}6'] i)wהt/ tN*ĵD yåR o.B&1Nĭ>>Df%I@I:"@=>ơ{`F?&:lvw<9 # BvRbFdG *+=%"h s~5c ԈS>)~F7)+r JI Cm(an,>)Q)G>r1 VIό@;ś'gN[SqYbih¹\ K8RSc֔f&_>s‰s<ɖ 7DQil /e0[`8V369mĻQkI~ K0Uj&sN$s=wS&|z )/l"A,X4"pKrBe`/8ip\A'A/\1A!bX 3!sp$G,G"HRT"u/)rB"b(:1:uFh.D%hnFK*0ڀ^@=ha%`!XIXVUaX3ֆa#8KxqQ8n n%n Wknq#x ^owųI|| ~?$?G ; d2&nQyBa0J$5DwbM󉻈AIdK'őD\R ,4&$g$*"Ǖ˖"W#,wCnPnL^YD]>R>Y~||%o \ J)\QWHV!}  ŘEH))uGTՊʢrj7"Sqbb JrJJ>JlJJzFi6!ʩʛ*_U~BT1VST\Ta4CC[K] t:L/wGTUTUUTUϨajj,5j>3yջkLihLܦ٨P eTk%iin8 vO6^]ݮ=#٥sQgXWMK7YXMCOWwNCdVƈ~~~~AAQΆņ-#O_>{FrFF|FmFMc7?70a2y`J14]bZezˌ`lb۬5w0盗߰@---fgͨkIdZfZRckhjq3lZh]c}FfMM[m-;*&<=whZ8:9J;;U8:ӝC79_qxr9Uz/7Kngͪ5nvt`x{d{Vy>2zz4c&33_y[{KOzqYs -S+{o$!`Y@|`P^êcvbvk9(",99hm !!CMBF +{n<-8`ĻH-L2ZDE)7sފycbMqĸq?aA Mf-HkpљŊًOcfأ g'%׋[x''mO{KAur`)!)Rƅ1£S"Q5M7-+Kl!-q]cɈ$H?I_$=4c]FGfy懥KOd)gڳͳ7f?yngYrk`\LXٲpUުkȯIY{unQkc6XP>5_߻m 6[pк&Φk?T[J*z{"墜Xj}ޝ;3v)m5}]e=G++6VݽkO^{?SPPe\URMά~Z]ur@t6Ω-C/8yHSe}Qc/~-'OjkIɂ!aujf擿Yv3g?wv\ι. ,nq[a.]b+^=uZu 'wdcG M.]v{v_{-֭=s{nGݾӻ»eAC%Ua>Ǿ3# |`SӒgz>?=?bc*Yկy>2odMo5GRߍ/ǶO1-L\KנSl {`Cxs:aG&0d;fÓ @%?c`~Ln"]jgMYz&@cU_dp^3[iTXtXML:com.adobe.xmp 1006 102 |@IDATxtTu.e&XyFv{{%̙3֥    5MvTAA@A@A@l]˥    \c~uTGA@A@A@#  A@A@A@1q:D#   ݎ\    ¸_c"A@A@A@nGCA@A@A@k aܯ   vq!ׂ   50XHuA@A@A@;¸ѐkA@A@A@ACk>W:޿~sq¥~Qc]3 |dLM7¥K{d,   MdP/zc1Oۋsq0j4r>/-Ιqעu¼_.uA@A@ACe-Cedo+8N z ;]ȼQU}ӧ)g H +kLqc BU+UpTV;tÛI.YHKxpe:YVuyD!^jNfaOLB_0?Iq ăbeW3.B겄ꗃr1}8 5Wɝ  |!wBMv`p[pO~#o .}=yq7ΰ`nlkv=v,<֌}Xm wK^J@_z8=9ȝyEѹMńQ9: lVe_GoW-7-DŽ4~$5p +Z-bec r'nƂUjLxѸY#㦨q7"6-@.5ڒvֳ=,[݋7+zCTު WpZ,Yۣ msb^g[Esmظ sWG#x%Ӏ]qo3+Ѹ<FNzٳt!gq9*1W@á{. `5FCtF.qyyQαoK *MBq`w[q8!lCPp NJL %s;fEWؼm*A]_cw}azm)CV6v㇭hg }2sJQ~=#VCWAEtN]ʹ}Ԙ;1eWOp˧ fqLQ_MUxY[bP6Z*o֢iS3~japeD2ZWcA}u1UDuS*Ӯw昘fOtbiWPt|EfUB18a_ySaY]1  #:i~:`0FmCo0S[m5EO (fU3XaMQLc\d>Ӊ*rZϴZW.,45b?is hM%G+p\>7H6#>Pz}O;\ RP6Ҁ !j`ZGQqN6B3@36d|svm}!ê  ##5ť YSn{lQFaHM ?iԨ[ppE-nħ6^ 907up!>GAL~yAq:)1Ob|J'?)hD@_%j֝8FSMI8XChZ0~79L('N۫PNwl)rFǷ#qr~ji%kvgJdq̰w7U E+* )3^Y7>S ;h (PMC )sfC_L/(+[?3Us{Ӏ]o.7)Y;Pa y2<хt"'m"u2Q p@3ڭq P4!r·Q6{lqLg*/F֧;TڡSga:V&MYBI4۫!8dz%Q͉ Pdφ%ODpJ{ ڟk͘7m\s+ڍXQ d-ǁxiE'ZA@ܐxEgx~r»ocqg&!oƥwTl\ V3<6SJ נ1C%w55.[6{A1U'L{gLHwF6}yv3ٓyT!^/5>89Πn}'2>czPJ(^djm9j rЄ^ t{ QRd&hg?}sūR%')|רʯSMhT [ۨadd> TV)^ҧZ{zJDUV3s{y:g˲ybyb(8<^2۩'qS3HpdX2 Lc:a弃;θnPƷN0ANj;{j.F&cl{Erk6ڃ< KKA1.ۛѴ^8] qa;1#c jGd͜ÚχWibԬ* j?e&C_>c EӨ&R{Udd"tTaCBhĜw_D{8g(jBi#Rֺ} =ط-LkjJOZ`zikQ3 - Rqُ±|E9q ͛h{L;gUyz f黭J⒣u%҇(x'1'iw2,8=u:CtK-ʾi3C=)\hb۲KA@Aj!pC2w_(^EtohGs^û? uݏo3Fn#s%+Ttmx8 p8K8vD~1<^8NI{CԫƲggHhOx#`gPܫg.|i#E̍,bmLŴ)F J]&'=v}SHFBgdXg~3;> !2م;`vr90p KV>ڧo)b_糘!;iJn< ߉f[ڃA6m\F[\n# M8 7țJxtm[n:κnVeQ2σ]vUrhXx( ܥg UGMc:Z=N`Y #|G3 ^?E*98wi K U`g;itl"| ПCq?:=F F`1ZEmw6MhՃڊL,%Er w&4(+P=v,ELu@E?G*+}Ͷwj\+?jD)a4 RЮf-ǟ/La-\  WQW|? S~3~1 kBpv&ߢzП}x7ߗf|1[tϣ".#ǎFT $5,Ud\(,a=oEKtI;fsW7lƒ/̻kqj}1y@0vlǁݍ0nnbΘMZ:^j'P033DXWJ ~2qsu񫢊sEϑ<}ڈ'EޅGUz}Wj "cs=]ӽpYԤ0_@La4SM>E6O:^&> SO C|:p JF'ھ ̘QcJ03L*'>及.oA?m(JMǶ:|Դa]9Ȝy%VŊJUjzۧ$s79[d(yB}Zt?d=X2Ժp-th.:L}\Zƕ6mdV)CahiR҇Zt{^9[KנZJWނy(^E )xJH^ށ[@eᬱNHj֭]mQfϭAQ}r㜮0b`q!ы@1T@YDK`ڗnD"qnnA LFoேmZA@zܐ~S߆xW{'S`/˿<}:ԁ~s}~~^c`u -K^QugOxͧ5n,6Н&3˃^K2DJO?1]^ԴQ8Rʧ =:d=*koމ8*Z[YƳjN)36o!7|8h:=F:g UTe%޵UxC# j']S.+r# oUv惄6ܴ:LKx2O_ө2#F*j2Jt)oŬ#93RƶT5兟#&at."M,*cLy7R86 !\BbLX,/FduVh^N_* N*{&zۨ6nCMGixFAػsb!-AikD&覃?5ϔŒYd%palؤ1?RA. h&7Q_VWEN 1s ?'(Xf݁,)ϸR_A!QsDa^l,vM v)ajP0{rta VpYόmcDuw}\3b'pdc=77aqi> =8j1.<=eOx 6pwIN`X [XC:r-C8>DJ9. OIMolR7mm<́cŭ7l?ښI[ެG\cj~;ܩ6?5#}ZΚly.3'dNla9*&(]Bݰ*]A߉*=}0·~T_^h߂<]x3ڕ2HznijLlc9Qُ/Bm ^mk?lcĻ<69pgAl&ZxFtks?Y rcw ?UP\?Xɜ6}MYuGH*q_C~mͽF:xJjrbi|J~zEQ^δQI=Ήh9AՊ|P<;v[ 1[SXgw8H&[SA CesIi(9OeZE"Wj J}vʗBLz"U égZ34 ` BcT*J 7/<F*c;Ѯ}aAA;BC#t ET7􈈇'+ٷ X43炀 CdGu.N_oܙ _&~]}]3KǨ;}g0~Ɍs*lSC{N=D$m8JjCQ exGMT#7Ѝfu:zNximz_7i_I dM~Fq43=7U7Ն2̔quN@qyiϮQѐNTbcsO}ouU5{r%jLy]MI'ۃ1al;9>a-mJ]$!^'7p]}'iJii(ލ16TT$*yOzbh +L<\Y0}%Lnk^E6"BtHm2]ϛ- 2P @f.ՋK^͘yuL:3 ɜ'd7%R `z$TGrd"N{OAA,viKc|GaE~8ߧ6OE[L!B#Ì'.˻yk/Q:ݎmGT :@6R@_gx f$sL.=Gh) eM/!N3&K}dڕe&3Is%:r&1}HO\:`=Oj)tڧڬҴBi1ނxeZg5ڷrmƗ=x1Rt=INFix> M f~d=~5ms"V}HA@>dnH֬Gz_?-nigtQ62gwcof :s=wڦ›߿0ޓ1m5KJX4uڣ6[dvٱn*Q}6=oڧV^Nm$OӡՂ.#S_Sq7ݭhV9 ;юu-Aܥmɍs[G;2]_p•q ;@F\όM&:vK3TxbƧyڀΚaKB%Jc!z[ɸ+uy7}Pwբl-=/]Yf% im;mdEpJ]9(pi_3ț$\8ۋNST 2e]usN<ԚS)?*9dS5C+,mH5tȗ9vreQ{Wˆ<W,@7O4"i 1+8{O@m\t;V!^9y)3-T cs) hhEp=.8F{=Yb3}_hm뚵5SX)r)$|նex0_ίb0VBCsJd>ms;-zo~3utĶEQ|][񓅆p`z1i߻ JL(vz[QXȥ  0C) /N]0z̝<9<¨1w'Z;.݋q+/>k#.^AvOλµ{H5./L u oi=秮TpNqTm̍mtf|'-Tܫiwy1LŴBẌòˍ 'O)7`kHyY#*:NMzڷq! nÊ&m3NͶO=g|=z9y7f$.ka#3F׋+@F6eоki6-OzJҵuk)#͙Vqꦅ_Oې$a1?yy~ꏂUU>;iQ8k4N  i#pÇi#y2UީO҅pz_*m TqJ=?Dž0cCxwn5T//Y̸+b祜{cO^/OgN| ^bdHNB*Í5~ W)Zr O<Ĕ#7{OYb<K  7jfԩ" Ux~߮h>/? Ł][A@A@A@ ˸֞o"~PN?6Ov tRo/!mNH~A@A@A@>4n\=ҋgW:qgt,{d:So}cspC(    CAW A@A@A@.;$A@A@A@qfB*"   # {<&DA@A@A@f    0A@A@A@AA@k+"   @<¸c"OA@A@A@kaܯ   <A@A@A@qfB*"   # {<&DA@A@A@f    0A@A@A@AAk&R+@ȏֵ;ﰕBϖ&u|Oϴ]mqFb8E![Nn߇~`\rA8Պ?Ԏ{;^ ugpGy,ɰD r{o+NC {д-_Q YU8:$=6Bنm{"sfh')|=ըꝆ+S%~Ud>Qʙy'M:/сӍܙhq.29G?0|# u K)Z$B$f,>R7«58=`T֔X]N#T ݴq}p"'}Y٫%h[Ofweg95\j%K:PYQaʉw}u o]xT]]h :*e_oŬY%->kQ 31In?@Z]_[AhoXE&փuX2(eUsKP ;Mk0*6TS v] '̏c]ӉdNxw^ޓMff2h]]=$ !"oa64a pPZ$dAt=oӅ] b;QIU@AtDS ye!s"x7pA&s>W}fyޙig`?74<CO<$20q?J52־wh׵yyqu?]"Mx'6u"x~*p5AzBz=CO g(|Z%sk#TnlSK_Ԛ2AC\HvrU9$q:@~8Ufی+tKOcBt]֞ʑHLt.Onj9p_ZUf?VGzkBc:ڠqX ظ#klW>j%?3 kgg*Im\0'f~ַz{/ի,p1D(FWm w)iWpx X{y> nE=W yrdmT:PQo3|̛`6>k*au}u 9ʅC >qa͔*u#E[UzocӢ;0|NɁB}  TgKf 3Vmrn'W upܵm5bjZYQuՂj 4~TTj'JqgPptBԸNU %OUC| &]}7Oe ռ1炪f5B3f箪g|d>mm&\n_1@6oI&1tz7Ӌy9(Zl h|쉿C9v7ܐP]-g{;IC88'mlT[pYB[k`]73e߫)hbQ-D E:l Mӊdd؈ot֕&T)cj1audVmjfO5ah5,L"x Ʈ7 _[l-З)Gɪ6=o_qUzC hb*z@,:S۟dX5q 5&8}hRtc '=ASѺ)2.w9BwQ;xƚF"+3cFӝYXe'td9TjZFRূs#'ekp>1O_|(X:ǽ^j>t&@]U|~dF{:(b#& $0P*#-00ׯ[Tlӏҋg9+k[ԏ"{]h5Kpi!= jX4*uڤ3q d}||X3}x_w`͢پ]r#Ռ1s7e;6I #*}<,C%ճ/ȯ"\tTE`% 9ȦLZ:QI$inl4}=h|n#g:ѾDHlW!qdݦ}ۑk+Zjb`j6 U;K'7gl`2g[4b\,j׷35iNͨ]t-|~C#*ƍb+sZQU?:is f_O&$J46rB3z7y<*aN//]v9)ڪ>86?hlgqrη6;j+ro3 YzG3ݽmSMoUFrsЧc4O:Q~.,G*ܔy= Z-jcmuE-'r'(w#E+'Su7W R/9"C.: ΓS=tu#6ֆeEMzk)0xjP]Mo8O+u"k9@TJ"%<`OV|[b:8j*jMhK'{Q|a)3IDATQtϋ\e2(ZdȔzbYqB2]9کZn |o>3H վ'*1 Vqg˜(md u-(T' fԣ"Dml z񉟟)ArvvgepyPX}E5-;JRfYk)0): U_Q6 tpLjyJfW![dijm榊 wϑQW)\F'jƫ?#uTV4ƒBmbPKzAG5;MSzW՟eAUYp̩V*IsSztfztx )?{>S4KD" =P5,֕ :AQX fU% GQAf҉GEۺ}̦רqej5>Cs)HsHj`F}XRMgV֯&RFX3e"gU +(}<⪯0g\3Q}\+9w[Yz⛗AQe#܃kф;fn"wL7=@~=پߩX9]4-M׾]p]u7Ƹ/(Ȟ-YLs1j7|L>tqi9XB xD-ŀE5Qs"[ -7<2?;4&(fďS$nb}>?]kO^u7PC˪e<.„H6dR m1;xUʜ`T!t:ubrUi^ھHrw`ֆYL3'ۀpcǨw탞0Tcz`دc۫pwkĎj $zO*$JSdSCy,N~$ӭ0qb-ȧq諩bN+ -Sd&ʑSɝn,8ĶUs ,OcS) c FHL>#vg#-px g1)}ې'Dvz=&C߸vT[M^:RJM4^dZoK:ܡ.BQq5A Tw*)@os#bbr"zxeZϖ^8qRȣcMF ׄW3v{F4$ ͸o-7 YRµ!* tX6y*D/( f5 %=P0u_k-=F-A Uf)j(2hW<lu% +AѣYvɨZD~^w4>!5 ؜8'ds=kٙI?wgHYo3y zҹ0١@=^"GUhޗc2ˡtn<ЁڈyhW:{95Kk>,jAEN< ?_Az P_rPĽpe84*o ҧDFcI7GGU$kz!/qhzwU<:qcۙim9Qфn#I{B(j4  ~).6OZ\tXoD7ɵAӹ52MHU6Bc /9o߶S6)qgi:;hqx pcTnF=n̴b*HU_+3%{p$O*  UW.'A8z,ҙ O<MUr^ىW55nyF=c nyOZvjzU ^me=Kr1 u''_56bZrv) Kv9{W6u/܈\<%u&A2 b x?/PuF kBOl*2P#p0J=b;im3?ugc TJd?~ø8xcncn^WV*4` պo)JkY̢9~4Y~ dh]8 WOѿD[G8N|64bMnw~)=hU' 6za ~;ݣm3eT!DӹA `tc;7!7oQCS/K=;3%J3 MzUn=i,X Tb]LNG\,O=zl>hOύ?~d=;r_پbJ3U'Ub{UtPӾf=a ({Vچ:n^+aI:W3 YMG.n0HVk) w`o-u|%sux~;0y:ouf^8>ml` "}&fKEgkjc4fIۣ<gߎS;V?RR: (}tܕ;/S+n48D(3tIX#lأ帪}Đ Fk.0@@FԎxYTyLPQͼT6knޱ U'Ov! OO$v:ulU y 74?OuU~ؒ `?#ÔToWsZݝiO:(-poΌ1W͂(-*鹏[GqXީ#(>OT<@G~W~6UkIm)uf/VWÆoڧ( qLH6Q@͙ynwrS@f.b^bOQʔ}3Ja3al_,SEiAҧʙ O<1O;PWNcצ61}(*qI#UV1U4C%Z|":Iע/t|J zܜfyzHyO2*Fqd6k:H֌\j:{:TUW fijF wv&pMM.Hf?aru\q]K>.mc{ӫ'w8 .h^!xY{00"\W;D5Tkr L\; sȃ|fpCy)$uu݂ ܠkbz)[K' zPL[8+QCGdōZ2=xKSZf'7*_#pyߣf9Mη ([scXRS EB-]-=rb[ofE:moYSl'r*f̳Ot'*crAhGI{f jNTO3L|r5jClȸUfCɬTm7sϰ1OӥCiu>\L?_c"RJ'HdJK 7k_CƐ5VU~ѱ2:}ΏCxL78&MyKib#g5r) *Po3?Y3iV-x~qbLcWN76Qo2ી ~ŃvV4N9̴DRw:v¤{rKh\C5:\N~F9˳b%p~ma]GhqҠPCmF&T#lY0$/;WTSH75KUKm&izd \O'7[OY#mzi_F·NISFuKHƯUNB֝N4ė6~7AHIj&Y'>0D_ESLs9yjg .ѡ} %*^~jvoDӳw-ލ:Qm"6rSm$seoPU& % }Ɯwͷ z`NE#BѳkISF(XN_6 $[ ajEŎPLI;q~9Q[9'uD&lLOhmT.)y{mmyQmgoO9sCJ)y 3mTkSQ)ֻ̽]ROm. vL {OV5j9) _tStߩSJ=w+i;Y YϻH_ODKUvq]Fk!5Oý/O˝lFjsk:A˾B!H5.S9Dq/Ih"pܥ3gN?x.$'| .$+#Q g.mz's=." /۪MEc=H?{cӗ^zkJ^z|K}G:&.~{Fϱs)xfu1ǑUTsNJkl.0KT9j% ǨʓLKD*sKӟyiIÙ56LZNt9'|פˣhĥ 8(ZllK&$*KONl6F1^(9)NDB5IcpNDw,x*'S8|hd{6|gY)m1UѠamB:g+*˴?35.jL/!ֶjڲI}&%Kl|c$YI=^rc%43j<ٞd4e[搽Cy& z'~s#?a;n$Hn*sl>O&aT+F6<:Ֆ\J"$s4qFS+|2vx>Om4y~euά(ʠo^rS_BY=NF>a/II`a5y3v5UniՄ^*l9` ǨsbN%N^-AFiU1c%A?BhO7}Zo*rK7dmF&sI N",\Hz(jJ4Iy<2HgҤwtM7 팚{*OeFi/'U[ջAҹN5bi=tES;ù I(iA@0p`cwzBֽY!#+ k]Y?yB$e"͸8<蝀WopЯ|YNReޘ]緗?{c?5\VU Mcn~s?(1;H 9qA`!/F?\'-ZMJ `i$    \%*O\I1 /M   \,UA@A@A@>2ji   0cIA@A@A@>2ji   0cIA@A@A@>2 ˫eﺪ: lRV"_l{:{4>?Lbu۸XrFg'$2M{~=Qgh0^nA@A@A@nm6<$ /,wbBT_xMz]6$Ϋ8>n{4u8n_8\   |$j4?h]7!܎ȴÉni7Zy}$-¿c01pTn(g<}sZV318w~a? 9|㯾oc<<|S[rlǒRr׀1bX5=JZt+'K[e6HwݠTˊB\1NpQڲ.W qq\{o~0TIy<}~܍{'$!kD_L_Ð&TFlc|^?3`F<Wo_HS^ !)+-qY6ً>ŅcT:w@Qp s3=}Y/K߻C]ގ ?se ޜ!:negfW^xA$@$@$@$@$@$wn}e$b%o {:)ВSpgcԏvӶDʢF|.\<B=)He(o@lB|xYCɸ >)9b} j u&Ӊ&{pܓs}N K"     gLʍxnnC}7v1<8wiHNISXrHݙ7L)Af޳Xl_ɿnޯ|3B$Kʀ.ކ<.l 7pB| L1k6M`ۺdA3wy#V<}􊞒 |PxvI HHHHHHb~6b,_K /oO24;yQ@Z@ﯳ0zbX*U݋ydW }6aivʿhﮗр7oaK@!P_\{o/j#M)5*LD}HHHH 8KaE6tM]lhX Z&aܽA^\7vYxGQUoԫŰ?Kډ,6"\"}~bp & /*nתI6+,3L퀊FHp > mrGWmzPxyP5sB 6Xs'OK\7Y$P(ݨ磱j5B3 "ա 3%~ rن:ܯz`]_PWv a;m({\o"PP4(hmNgPr=M[10KŢ A lE;*O֖zڱD%p赲q 9;pI5kSe^Bn > rQp~u"BavDCHzØwX8C*kE WŞHHHH .0Iύޞ8逶a48::ѸÎBC4xj7hm/*Ce5z/eRܿ(Dsmrϳ+'R"TD/n6@JiZJdH6a >DN8҆Hi@᫃fa(@/$ | E,Jkl`) $e|%H :;[Pe$\'\GQ8PY5$’~EM8סMph<^Ǐb ^5߲W 2ÿx+EeQ(3 '1=/:|cܫ_Ķ p^~M,OisO/QhpO:f_KCob6jyi(FV]ۋP.7/.A60fCF'֜E}Ӊ=ɼ M+y!lYv<7c8%M棈j30? o_OZ<mG;e,1B$@$@$@$'@F8vMJo"Fkv6ۂQݣ#IXej ? HنT#$q8diV'-U*mCp9WqV)Ǿff lń }UeB 0U".=ȏSoF7 !>>)5Ui*;eA%,WRJgd)KTn ~[};x ͿnƩsnׯx rWqҐ"1j U۲asZfœwoV h{1+୹LZZt-ba%@%?k8VTv{Ec i60tVzk谬$ľfCRC~2oւ+܇QCn3z$d>s" ,)<=|S:/̰-/ۢe",YX'/?{V 33Q텅HHHH ^}t#`5"iTy_O k!h4Q4.4ңlL,n᷆LBcNJ1 sgQ$}^D*^qDӕҔ{qӕ-EhV÷!T:KRaz':>`Tٞ~T(9 +hh^ kf$ gH_?S_|tzE{O/oytn":n ]DB3$lKPOz6W=p04HD (oAlPzŷWcݓ߁w~>P9W7 4b2W ɢ:,|4P,ׂsQ>n$@$@$@$@h7zt:Xw)*bEīwr%h.AU_^4D2/&&,OW{ ȶ;ueGVfRShz OfSDhVNڿkb~xVk9 i&8O$.]Ҷm 3Ğz_-Ik{r9nD7i)^y.DRX!!C͘i X5 AR'7?ΔH+16xOQ?HBZٷDG%үX ߾-u</u6XP͡!.ux6ȫOCJKP!ܒ{`1+Jť(YdE $^LY|Ӵald_"q }­=$%{ #t>F4c @`=?\IYJH r|)>UC&䦈CnڄVIco\ek.D` 6s8v4"?&Q6IERŦ9ݪ%{ mוg 'AU1WVTS(-5 4$i"[)'X(t)ҿN\Yj;Qt+dA^8$}7{r /,?l3ꪛ+ejz}B s|$w!= 6,/H|ȁqeeٲG`%*y/w(*̱!ŐGѰ+/9fo'ŗYWՠ OAY$^-!Rm(cY#4;j9~i^Lӭ\_&+?1Ut= PYؽHHHH n|3)q[v,Ru_oe *t{D&ćN"D/7SM)pKҖqy**EjS#fDfWJP}XKQX~nOt"Y jEa5gN>OżwؔHHHH s"D&ZU#Mƚ$#\$5~|o$h6UaD~J,?G5Tl"X #^qVbPga#aQѮIzuιgFt5S7/6!    0x ! irHD*|YE|;{M$@$@$@Ӂt%H$@$@$@$@$@$x|=;N$@$@$@$@$@$0POQ$@$@$@$@$@$@qK=n'     (ܧ(G      %@Cώ La# @pۡgIHHHHH 0JHHHHHH n Pг$@$@$@$@$@$@Ӂt%H$@$@$@$@$@$(vq      @\CZIENDB`trunk-2018.02b/doc/sphinx/fig/yade-favicon.ico000066400000000000000000000064351324306050200210620ustar00rootroot00000000000000  PNG  IHDR szz IDATX5ypa{W$˒lYl1mAl0JiZ`&NPgN2)'.1G)%``blO,ٲ,:WJ{io_}{a`1>1ɕBFٲ}3k:_}ε rphEvn);'OĦۨdanEUY۱*L3OwTTp]w_F./_~%mm 78"UE'XV KX\~֕B"HT:N}쀸icL&"$Rs d3QU]%F1MSɧ&h>qc!v fg!3w'Oe~Njzj"sLLQS]#<B~nqgTW#>&g(Y,.XXdi5^~9ώwK7(Dפ}F^_ӷ@<1 5ՂÇ%^CC@9)%%ee\g.Pgkg픴LJtgAINp.H%PcYv"]a[jyYT@gNŨROLLѩ 8!nuϫrF8{~t[yQJ9e%(p+"YQADәK&}++amEA - ț:uQFGyAXژ b`7hnYK$|/injK%wKKϓdHNL;}ųIΌy* H~|/ٌ(.}Tq J-Ǖ$ ldb|D|X4JKj ZAM 6av;h_ i*ՙ2뇈jAy yꑊA08CWJ6U8w!_^֮]K~ /b݈#Gmnhȉ RlaGo_GTC\#Y0y9A.` EQPкR㱃wsnuEdaMՈ'F%odMNbw>ذl6  K!Qf6M4rSgT{jqRS%:6C*]ρG4tVx<GÏrx2{`|0Ѐ?PTui -BٰfT2[ħ&&^eilZ5޶4hc~n^6׊}"^(eY)e4,G G۲χv# Lx^R{w^ptQ ˔sW(kڶqcx$Ğ[|. bݷpǿms9B\+.-2@qj[nYĿ$p2Y`11iҴG>=eRs]9߾L8";o 8}>hh@mjBI% 99 Br͡,ui_KGB{{R?pf,++9Y 3#7ԉҌp:l(?Z'qH4;\uŤ )JklAezn≄|7E>/|S3a≘]V->9 YP>?e!qfvq:͛زiCo9>P_1[n/mk=u8 LOϲN;~ettstqMZeaF"d͎1c,8]Y+TLB<NXtuLue SE3:9ɤ3RUT ]xCs*9s BdmIC%/qF.c.B$FqC6% *V4ʉ0pDU\Ax<誊\8vb|/MElL#8}U?5ʾ{7F鴳+:sy28JAQ5C\ ω?8lN'ۡaZ{)lkAx<77'5mfly.yM\eoZWK}C |qW Ӕ($Zs3J>rKk d hE9W%^R@gB6"vKY[D4BuE`.Ơ]>R?ZV7[ kIENDB`trunk-2018.02b/doc/sphinx/fig/yade-iter-loop.pdf000066400000000000000000000616601324306050200213470ustar00rootroot00000000000000%PDF-1.4 % 3 0 obj << /Length 4 0 R /Filter /FlateDecode /Type /XObject /Subtype /Form /BBox [ 0 0 747.079773 398.072327 ] /Group << /Type /Group /S /Transparency /CS /DeviceRGB >> /Resources 2 0 R >> stream x}M%9r>]7 ` ZH^-d/s̼z4heL2 #"<{ݟǿ~=/_/#<~ˑ"> =C%Gj=~zߟ~y_q~y ȿ<__˿<~89#\j4]Xwʟù{K:[Iޞu6+Hx}89 eǟi9U#~6k52RT#5)Xk 0\1@<}㮺^{ɬLcΧ^\f!*j:7y11V_n_ͅoKߛ76ҭa6x#7͆r5-&*\YMG9n{.7Pqєiܾ%$=ɭ>F7o6H exiq2|@m /Yx]^W].<֐.q7ΖlCêkN;v]1B<^L9볝cGϜb*>8wEN~8of $T?| r跘5dryd&wCFnzUgoϑʘ̄HÅ^bx5z*9m1 LOk9Wov_w6e݄i;0Ÿe"DZrm:bZ߶`gOS>aLM/sǟ|4l1#4[spJĒ2wuI9)>[|x{+[G3Pb* 82^^*sleʟ_,e0ϯbx֩RIdT=:( *ʿ'lݛe: lUΠ{ (Ň+𹩯SD^,\V#8 ,WW:% B歸HeT8R1qQR"i'le/-sP^ZIõjV2/P9QR]@Lr:VӦ2k~LK2:ͱ6Rt{Ra0a|XWh?02i-03*.qmei\gQT'gNKf9tQI:9+Wƶk|(vYav]'lLfR>*H6 aLh/X7VjkDpv8qyZDYhN9Ds<6X}dD߀b&)+uj"^Vk)sʏUBTMV>nE(diش`d#U&}H#s D Fh|\(Cy, D\1@NJ 26J;@prn@dҶl96)gYv@,b+|MI#$sߞ+OR4Au je0S:r$³*.2G҈@\'?s`=gY;5QB#)'x艠4Kisc"BFuH>VmS hUm*jT4Oer¦,3"MtDΉeЫŔlTVFȴɚAyN]# ɻ:;εPȲPSǦfl!xT&۳9zIjR)Xӕ2ѩHQa+ -@kPmx6ޔq0_[ELaզᘎs{X}1}.sA6R X,!婙W9em5Ȱmu98ep"n7"%2nSe4m'VYGBT<aiVECl\O2'65jX]:d?Ue-Jns&%WTĄA{R{b[6HJ3U۠vi_L'Iy ٬00A :^L7hXE-d9̞378"U4tOj,j,D7]$`8A*r6xɆR@#al,X.@"kfLՌoO]S0B R̰k1S]:}~ӏ9xa{yWdg9JQIUy& *jcjg7CAܢuUho3NNbZ>둢)q>nѢ3QW}D΂^Y g+,q?e>d-mxk)8߬.Ѝu;;->As4 Iߟ4YibV(BYЍѱG.8k,?Q`jWJ-3KasLE&Δg[zJ^Hi"P ʂ}_eػS*M4nR0bc`S"m+>7DqJ A:R 2- ,nN[KYkLFpaqILǠ(+P'_SYɚh\@}1¾ed|e%۠2dD[-͌LN`1eh4=!7D(jX+ "֍Yr߲'k:6y a@xG.B#tM)UZqq9.Pe36&2N,)Q3Jv#=jRVDIQψZԈZZ҈ZZ҈"[VS~C5B#j"jI#jM$"a"5/Vg5`+sJ)T1g7z;ۧ\5_jz,ೈu>NJi"$ @hCsqըo\6/j_$B施=R48Q,S4~l@l )EGvK,kfAXPMhOX{^(f4Z٢=ʲ;LO|J^?01i--cW|gA 7S/]@FYўr:r#R4 b["q x Y{`3aeLZg T\RȬg+m1i>Z7x *a6&E{=U,0$YI6˙eڽcH@^&9*hEG3f`̪AuHP)6b<(IU3G$q9k,BVq)Ua1M!d7@P- tK,! Ґus Q)?P-Fu , I(a%eNP΁*-02Rd+)O]Bg?UPS'sQwݪHo6]}8çLOxA$)ou'Aq_ "l[p un![R19Y#*҃<ԝšF(w[YwNa7?'{Q ԠfPNi z/1HmvP$\B u] w=ڔbar<` V>Dx QX.@YUL]2:L"Lahd2 D4P?Jpzy9ܿ4;a3YВ; oSm.C0 -9wJf/L ,#r4KLP9P-*q?PIF8Ơ&P+H,A3Ac9o?6B&&&/>Z&\DhrmoilS^ p1RUdwom^(Ъ cwjFӽsiVR6F>SjLeN׀`aۘ"FbbҋiZNˇK0A.Ie%mSԸ|a(zp(sQto9"DJ1*7ʪ*U$7f'3dc{T}r}kZѵywqn_"-OW`nضʍ 4PhgawPGYaK}0)~yn8PdLZws^PH'DQ4d(^_DfQT9x,pt!@l"i5i;0q_Ќey+mR :(KtPfLQrgQ*_*3-؀~rd'ljXܛ$IA1DwY@IL^P/T5 سHҢLB:4л"`cUMmh}Yuz ָ*2j+UvK&,U..1RN*U,~ t88S\:JJ )dEuKGPP%@'r1N`9`@efAm` e[}k-ޗm &^qD!dQUPE02+cFY`nom`~RW`sk"VvԷdB1qBJ[-+f8jZVh  ./VwĭIaޡYu<˲Amf684R#'ՠf7f2FhֳeEF5-өeNRӭ0ͯϼ}3^ކqa&qF/(#lU] 5%gjֵ^v]3vT_ᆨqa4.M>J%ڃOPfշNe8/7 RnKjFIZZ+MRcsP ?;;2hcN96']le,^(X;>l®'CwlBwO;&gPdt;>ŖtrOGmʇ{Ay.okb|iWu+rrJ1%R]gq6tσ[$ .C"— VY?<ԉz3 ]bEV-+_=c8="T2yln4}QA4>&3̤9)0vUd#4uV }X1^ti,.&AYM|v~d)_,XÛLXOMaq9XMzBUM:rnV%a5.P66L}<̿Ǻi>ž6+͍)1(34 tn= bsFc辪2HJD0}`2 gYUwF.1c׀2SzS&;d5+,c+ 0p/\?qbͻ'94Ǯփ2\(]Yd) S5Jہ7'ő`v@ݙv |cA <yT8l@#"LDk`+(#,mp#il.q h7읢N0dB:e Boӗ(tP2mC2X\.ȦCأ^#S&ݟ*/-~ڤr¤.'SbDβ נ/>ҽ|X?G\>K+e+z!xmbuʿӉxY8"}\0 λ,p6 y,rve%a~f-lwuì`+̢+̖ lwE0;w9_aJY0v̫`6qY|*l Wo0Ko0&0{SFqp;̂,a9L7EHa,p/0˜ fgH,&fo}X{y҃Q'=T5AcfE <¤K1 FgILG$5T<40 §&=xU'h҃ IıLbAE łI B.*Ozpij҃J\ēNP"Mz0 :Ŀ!&= D_uw 2a-5aIiGB&= )J5SOegeMz:Lz@Cwü&I0 $kuw'x&=5Oz),46ėYU,Gyv6^ZS|(KnT~/eT&,kC|biQ Y~Ae VƲ_̇iL/!̀n#KBzl^HsC%|hiWn%{ks*V}BNoȭ"\ _7(;AWP@5 M] m!. 편mC\@XCEi{] RUDG]  bM+ _V;9ʚoނ?8&lűmG@5uw= a[Hi/2S?3E@ 7 n#_;02֎fkUaiYQ6Z:ع-m-ˇpX0Ymsv4㔏PVc#8S.F?$.)bU6计݃UXO4|8H lBB,d=ckjuPDZE/vGXtKsO{pp_|Fm2{?kG3{bHLU01?]?|5qӏX#:+^No8eʇSR4?gr+2\WJ8ů.$x$)+|{t,}$a}!OIy|ȶTaQB\XrfIg4eA![3x@14cx=" <_n/q"Gi?5^(2(LJ,f9o` Zua&4Fb܀!Z8lVSOYx؁RTzhboi(7qWHG;tv.yPkxX:Jm&si0\̏;פ*TI| acL/BkJanҹ@̀}T}=Krgo؃{@ 3&q© xԳ?6 JZje-< Xh.IHE;QTO0v$rӚB]j_)(˗5CR,'ӆ |Qq|t' #kf EBiɅm[яrQlY[WoίmL"Z֏sC,Grx|w)dJwF Sů9Y 3GI%*kZbȮg~)e~jm8:؊$M^$zfލR4kA>tJBҁ$Gs8+@8=UY& D\SeF0p^0+|㎩SS;A1u S WL7LwLWL- poMZ SASa}>j8"xr`xq~05"2zEZ SQS+LwL/.Z;pTuTqEXrG> /ywTrS SDSSqTS1uSsa*cjajacj9o+x~JP+#Xmi4W6oR}oUw\v2!{Ҋ9+u>%z~K46D0cqa.uJ{ٛk}j߸VnEo|wWk_Evȅ q#3#ݻQ>ӛ#u!໳@(O9^;.wWvLR9tҋtvl*M~G7-8^4cNlQ{Nknw7nc~k.1WߎaDv[vN(lW e$q˅DoAg [( VӒru+E9*ЂĽ81:.' -JѬ$.[9iQw"4.JqCvßܯ>'^mH9QTJQ/Kր$uF9\˺6+GSwƻ\."N,Eb ٴj^&^e~[=^+4 rF'2 W >/Da=Gc&d ȕ4̠g* EO7YU%j4r4(@W3Dr-k@A@EvO#h/9ly "" "d|,J5P{:86Aekz/P E%#vƢ v9W9 >,\Uj`^e&m%L,I se 2Eyw2iwtu|utstutwtu|q:].SlLL\.SLѡ˔}\)]])LSL)2E2`.S2]]].ӄu`3)]HvtFkǻn@RVKjIJ@YAOѡzʴZ$]!\rǼPh z\xޙ8\Lڬk㑒,GF( NKz/tFy@Vooe>7'LҬ;$~XUDS#J^Xr~8^;fZ̾$\BG̓KN΄UD舒ڰԹzyV!2>8m'KPO8 ,w۾?.Hq Q7 -lOusضVֶDŶESmo9}S|߯ji6`$g^(vu:W,eeNg=G7l~ئ5l6qszMmk֭&"okK\~H;7MMUE; 6e=vv'Q}׏rB%,g~j|OC`M?'ȶgMk\8W=C$XdmD"M^L% z59phmJHEDµ<ir3 R*LARV 4 U/NA 5݄DU @չE #]";΂"HEǡ!(D5WlQtkHrȨ!P`8O`FA9w"LR/~Xb %yX A~!fbMfN(j옗d$+"%=g"M߈A;A"A%y B$4/ AsC$F)NHxF-p PhABHd[?JUlG,!EeqHU0{CG%G}ەa6ϥ0$-#CgX[10I 4NA b;c8iJ|ƩCrrpDjS+JjΗ79Eb~a'YSֳBs.HsUs]yftdBљL,As >άEiucs1UI(JDA U9 [0<v$r+~Xy}_c1X,1:W"mV++!𞗓w~_L\7ǷNI6m)J Z)-9l{g$]`53َ/I[vp–չP>?[,}Da,iud;Oa9=w\y{i8g{$>,Wm^ d]^c$K}茌"*pЄqQ2/2ڗtlb }e"N7ɒ67QS_2c γ8*/?$0GCRX]~w k".uc& rdjVA|3Gyؼ~, Q" Ҿn~P,?*ۤI\]Lc;ynôP#yۇzX(^Z2 O~ WiL#jΫٷ7Y[.g` wᅻwX]˸s8:? endstream endobj 4 0 obj 13134 endobj 2 0 obj << /ExtGState << /a0 << /CA 1 /ca 1 >> /a1 << /CA 0.449814 /ca 0.449814 >> >> /Font << /f-0-0 5 0 R /f-1-0 6 0 R >> >> endobj 7 0 obj << /Type /Page /Parent 1 0 R /MediaBox [ 0 0 747.079773 398.072327 ] /Contents 3 0 R /Group << /Type /Group /S /Transparency /CS /DeviceRGB >> /Resources 2 0 R >> endobj 8 0 obj << /Length 9 0 R /Filter /FlateDecode /Length1 2824 >> stream xTytT;K2&(K`R b%hP)!R(V Ҁl"H))FZKCH©P*Z;~3h{Nis|AD TA 3RkLZO .&28pE%RykI~G·D^Z6{F"- )Uo"x{/]١`kނY3E<3V.T_W/\<{0Gr{dIa'کsХnkLE6'H^.rĞNjm}Ƞet6Q %'3dKe t60ങi[N枳 N'9{X۵u;a.Um?Zk\Q :G3nVj36H1z Nc+VN4_~8jZWՓ]FÃڡ;n5:52ۮ XA/y4y[mV,4Ҵm'8yW\tkMeGF|D+\C{.IѰ7+1q{c0Sש"]$0U?PO'Fc!~qx^47P#j$X-muG3r6IcSUzO5QZTZbdc~PӦRw7.:Vd*TItM%m#U>Z-\mN D;e,y8/7Q =eFH7h5ҿfXC|5NZH fpĚ_S[ŴVo{H؍7NmQ,FTuPJU{g6VI:\JjUt"YgNtB %5 3*Q2IS>VexKD 0i5D)Ozm9XVKRsռAM(>V]KPkCZ30&TTԴ~_KkݫKT=VwEQ=.'᥌~&k?Yב1~da\ ZSZUdmꑷɚqͼK]"ӓ?x;<[}z;;FeT"܋?(а ^{OwQR(t*#WmƢ9H՛[,܌F>a\g-Qp!A[GFk\+EjWDJ޽ߍr&.1ȸ?NgV~˸ 3CEAFCeƽý^'^s`{2e Q tC'!'bP7d50Qg1&b@fFf"g8u NBz߀No@/z #+D3DR2Ҥ4/!t-T7 vets: ĸ;bt1:0exVOr$p:j7%ڮHd$Xp2`}DR ,X63`X Qo*O?C endstream endobj 9 0 obj 1995 endobj 10 0 obj << /Length 11 0 R /Filter /FlateDecode >> stream x]PMk +q{XLL/ A(Kig*4*4y.OA:oaKay 0N[/*2Nq_3.="לv8=0 &g8}]kb}I -ٽ^~&DIkT3[Л|w&U-]"dE@]A:4ųp(km,{1Ģu endstream endobj 11 0 obj 239 endobj 12 0 obj << /Type /FontDescriptor /FontName /DejaVuSans /Flags 4 /FontBBox [ -1020 -356 1680 1166 ] /ItalicAngle 0 /Ascent 928 /Descent -235 /CapHeight 1166 /StemV 80 /StemH 80 /FontFile2 8 0 R >> endobj 13 0 obj << /Type /Font /Subtype /CIDFontType2 /BaseFont /DejaVuSans /CIDSystemInfo << /Registry (Adobe) /Ordering (Identity) /Supplement 0 >> /FontDescriptor 12 0 R /W [0 [ 600 837 317 684 ]] >> endobj 6 0 obj << /Type /Font /Subtype /Type0 /BaseFont /DejaVuSans /Encoding /Identity-H /DescendantFonts [ 13 0 R] /ToUnicode 10 0 R >> endobj 14 0 obj << /Length 15 0 R /Filter /FlateDecode /Length1 8816 >> stream xx{|U޵jWWWӝWN:^$6F "2*AD$$!#01 /1 3aL1z㄰sWU'9y+U]k}Z;BRF<6="9k۶uBɍ}Esni1gm5{-T=5bLz Z1dxȋ5*Qa_5Dpe'c#c+,n X9̻[e]*Z&]yf_EeNOrӎZ8;F; ;O60Y=Rk=>V2>:CT.m*E=&j,Jqn[#֙J~I$$ɶT{OGO_@i!fJm=Ǒ !BC,$,!9:Rm d"(L<1MXXQSlӝyz-):t%'UwrVerT9Vն͎:ζױYIws11E)Ͳ,a ?tW~V"&漷nS/N!b5=#;BtD3&j/u"T@jw'N9j[!dEk 9EʦZKH1VEV++v]^] bO Y$Bw-dͺ?´ϩ{1={=q5wEojGsD]TE& 3N Hجc+yĮdDbA.G!j$Hk dc1~0_(J LX' ~+Xq硱+i_FAb`[XL%rxR\4-ZJ-Ob^j-QW*Ulc#H7a$m<+kl;NZ'^q/-l8(cQ`}, gw^jрJSy?6.}%$y 77jczP"_&$B%š}uE4np: 8„N K \+kk7l\ZZ_S_ͭ4,?@[ɡP \y7iLJD*)ڇ͙zGV'5`A&ŴV+&YAa0T٩FW%#gFi;{`|Z@ "( lT-S_Qc,l߼K8Ա\8tsX hb]X/0N9-JT!zȊty܂%ߕOCتkjW\|/ҷZZ漸gD!C\ZZ)HU Mf &n&^fT0$u26vҹqu{Ecƴ_ʭ.&ᄑ 4DKƅaody vI|?A,b=B~Z/bu:T kԏ]uOUUm$jh)o|)+b/?/~>@mM>5R0\?D??ϹYՃj85p;3f}-IgFFHdyN2cM9k֑#m44Gfz!ߑ٤ƶx)wY[k5Y>B&RIB179"^% P!'VPE}%sa_ )1H-01Ʊo) ?o9Md4!,Ԙ&d*$z1Ls}crh?U %qSZvVIgyO$Y(E5NVk6!3t7\\˳hx< :ophwÿC&W |8\A@[kjhm/?8|1>ῡW&5p^ }>kOs pG5!\p(_q_ pa4/=jxvÙxx7p\gHh&78ps8ơQGthF a3kG2P' !A8sxBxپBػa vѻa8u^`/fvP8qm <ös5\ lY6qXcg9ء lCX?úG:kdk2qlLXg8~/[;tww@*Ua PYTEu-+t PơC7˗pXBIa)%t6x\kEh;,lb 8^ endstream endobj 15 0 obj 6519 endobj 16 0 obj << /Length 17 0 R /Filter /FlateDecode >> stream x]n0 N jQHiĸ񜄯hP:dLL&ُ@$ Cw ~ sJ~~L'$ )IOVx|o[z*}>,T\I &U endstream endobj 17 0 obj 364 endobj 18 0 obj << /Type /FontDescriptor /FontName /BitstreamVeraSans /Flags 4 /FontBBox [ -183 -235 1287 928 ] /ItalicAngle 0 /Ascent 928 /Descent -235 /CapHeight 928 /StemV 80 /StemH 80 /FontFile2 14 0 R >> endobj 19 0 obj << /Type /Font /Subtype /CIDFontType2 /BaseFont /BitstreamVeraSans /CIDSystemInfo << /Registry (Adobe) /Ordering (Identity) /Supplement 0 >> /FontDescriptor 18 0 R /W [0 [ 600 634 611 634 277 615 520 634 633 612 634 862 392 411 277 686 633 633 549 634 974 591 317 636 591 352 817 390 524 390 636 317 698 317 ]] >> endobj 5 0 obj << /Type /Font /Subtype /Type0 /BaseFont /BitstreamVeraSans /Encoding /Identity-H /DescendantFonts [ 19 0 R] /ToUnicode 16 0 R >> endobj 1 0 obj << /Type /Pages /Kids [ 7 0 R ] /Count 1 >> endobj 20 0 obj << /Creator (cairo 1.6.0 (http://cairographics.org)) /Producer (cairo 1.6.0 (http://cairographics.org)) >> endobj 21 0 obj << /Type /Catalog /Pages 1 0 R >> endobj xref 0 22 0000000000 65535 f 0000024749 00000 n 0000013426 00000 n 0000000015 00000 n 0000013402 00000 n 0000024589 00000 n 0000016731 00000 n 0000013596 00000 n 0000013810 00000 n 0000015898 00000 n 0000015921 00000 n 0000016238 00000 n 0000016261 00000 n 0000016495 00000 n 0000016884 00000 n 0000023498 00000 n 0000023522 00000 n 0000023964 00000 n 0000023987 00000 n 0000024226 00000 n 0000024814 00000 n 0000024940 00000 n trailer << /Size 22 /Root 21 0 R /Info 20 0 R >> startxref 24993 %%EOF trunk-2018.02b/doc/sphinx/fig/yade-iter-loop.svg000066400000000000000000000477021324306050200213760ustar00rootroot00000000000000 image/svg+xml bodies ShapeMaterialStateBound interactions geometry collision detection pass 2 strain evaluation physics properties of new interactions constitutive law compute forces from strains forces (generalized) updatebounds collisiondetectionpass 1 other forces(gravity, BC, ...) miscillaneous engines(recorders, ...) reset forces forces → acceleration velocity update position update simulationloop incrementtime by Δt trunk-2018.02b/doc/sphinx/fig/yade-logo.png000066400000000000000000001244221324306050200204040ustar00rootroot00000000000000PNG  IHDRRlsRGBbKGD pHYs  tIME &L&vtEXtCommentCreated with The GIMPd%n IDATxw\U?>:}yz'OzH%TD Vt]]WuwEEY~*]VEP@i QTғO1 +;Νr9FmFmFmFmFmFmFmFmFmFmFmFmFmFmFmFmFmFmFmFmFmFmFm( /8j>~l6YC@ŧ7FTg' @: axxh xe .DQzG̕_iiF( _轟ȇ>@{O(6n$*#ʲq Y K$P@Z{7DAjy}޼y,"A)7|E>O >}6͔k4+](} _MӴ `W_}ַkX[+Wa&`Y@<d2@6 H ;w]7У088̧xB7z.\Όˆ;˲BMׂ7*fͯٮ"+iJu}0+ !~ _TJXo/~0wV?&@l0(k):7r]wXY˄ahB9|e*Պ>15aJ)52-elṞPR>huB Rb i#!mօO\^>vR$!y4[8SJ)?MuMgxI)PP__R%DzNԬE }gJ͆Q$b_BP Z4[/@udbtd;qWb^xa6JOOOw2 ^p9w\4LR۞fE,N0 ](Qť1~Yg4[ @;0|vvud"iRJYZ5ͦ688h͝;,ˢNaQ0b>s;3u:c1ƨa1aHQRiJI*bRJB!%l"J)@ Zog#'?=m aD* 0F1@"V sfZwlJ믽4f&JrVtBHi!dzݱFaNOh|+e/@3cĸN;zE*|߯SRJ H}v\)Ah4]hL3(ioۓ -p֟_K2:n t.忷UFk4V۲ Ja# ӰX} KJB(lDA  q2=39ʲLK׏~|S 苗p ) aHyC0=M11A1=0[dThy ?kկ+>}sWW}*l,r\gyô"VTb>nu֘fjVݺ}Sײ}XJ)m6CoU-eM%,\+~go~Dt:ѓesTGjAh+,Ji"O$+劥뺮fn#j a0muxCk)J)] RDlˢ Blb\kǗ_:.$  4 t iR)4,ݭJض (iZȡCrJc9 ٣Ak#!LBy0wЎ[:4kϮM=41v^}j1[ !#k׬z-R?Eh>h6PCC<==a\=~FQZWWWT-itHH7H*N:YO]P'JJ)bx<^O&3^zikAqTcI)$cQFJBȾ>@֛ e˖`L9~h9s)X_oTjjtdrd)z% X!P AVx)Bz#ZH~r D#@UK.=iQә9sK/W*)}%rp?61ð;x(/?fy@@*4MGزyKn.53;6n#Tg:x&xh80D2tMhFAdkzLiqR1"Jm*VzF8կC#C]l2L~(RT90zxL|P{^\.FQh )IQǜ9`At/{zzx6jUVLH%qhl[T>e|>G?~^J-RJަ^Jq+(Gh!}c7Fhq-J61w` ˴ TaȋŢ99>㫔R.>swЁ]wJ2{nJ]05eZ*⑩<5wVΖIP2M~FI!Aۈ\GGOGi:$"4i󓉵'!1"=}@YmƢ~ i K'km>߲*E}ﳂQ(Ɩ5w)tLҌ!p̴\l6BA而E z vIB?3)QJJMӦɉeQjJ(_O%S = !JFF˕j%Wordl1F0L߻yիW+7tl64Iy2Ǔc:('&'fWr0 Q<0sT+RtsPjՀQ%HŲ:E&'6M2o`ٹ{ή9aP9KƓPBZmo@[Wzs_FVN&YR4U%G>~(0/o)mTׁVf'리d^sfVSsztwH$tZ!S4QYMlvi'ooG4A*:46:Q)Q9֪ 64RŅB 8J)d7߰ܞgJҪ=U[hafZ]7߼ZR}J)ժںmH%|S_e jHRT*1 I?HSBu(PES 2Dr_3}1+Ff'FE&uv{7l\>W@<Wt)UJ:o>?/h^w9nAKh{{P}?@_{*pwTP=:fr?x Ftd-f+m TRa${^lԴe֤eYC4%''&(Ze;˗_(2npMҨ@@ts.JƓ;N~ŢGeA0}_ptN#uֈljc5=>Sӈi2m3{e  zƴU~Ԉ}׃~i2]5YQ5R)1k}a\%, X)]w7>/'X`wwu)[a{-[~y8-2c?{ǀ/`vW{3AHLYŢ# EYg L&.lmD"%4{z(DQ-˖-an9sŸjr!E^OS-}߷<׻U[ol6MLYl$DuݥYx0[,W`hhHmܰђR'''zm $Q @3f>߹oh=n !RTRާwGnu>y ; m2<ȹz.~:3F&l.'Ԏ޷\O;A1dq$@EcI\|xz#9; ,#?.las;?CB,c -WTZP!H ,nZ±1sN'p2 C~n DaD ,|i:91?{ú5&zO ,[}=2K-nB|ǖ[9svwwʥeI!:FsE$qx&Tvpȥo~?۫V+i`O7hM,ya C"ey]#eT?€3}}zǗ {Ju¥S)*)S K)GzJAs ^2\0J_ WH;`15o LSAV8:ju]q( #EhV,qk70jr]w>tC}Vo108b7 mZ'Lժ}eA2Mv /߫j }G=SxR8+-[xZ}c2\WV^SJ>(p،eYەrLML޾F-^0}? V@ͦӆh&cOC?E<3úߥ3ͬPBc}}pPo;j@Mw!V{bq،mƄm_ 񘍯w7fB; 7RaR:AZ~6'Z-JFCCCfٰy1/cubTc([[l 1LN11ڟry̎gSrU\\$=Kx‰0 cZmIV{fh\]fjw}3(s9RJ-yi:RZ*g+ɩI\-Y}y7s\.yntt4>тE?xca&R)BHҶxOwКkm{T)8`"XwW58a=AИoi[BLݰI}^t+E1~ڮq!U~jjcx"U/yϻ>a̖Gӵz9trMRRm6:.yɺ*" #/^rHhEV8CUP> rײGC" k.T7Au0WFF 382@J!P[s]A )J)޽;RPJ(ݻw(QJG>cSqf 6ƘEQnd:إ+j5><2̲lvNw=wUsպx'ԪNX}k*2ι) 4K$وGzqFR:r-8pМy۹;me}نa.\ JwQJ33Lj`PpU%oy`pQa %% 0Z*¸fG{iZsοWӴP8c⍿Sկ}H粏GM(%-G?ʣ"E[~ siPhK$I݀jAUVCP{ A8#2LQ]nf݇8iARHxeYco7e{G<8~.0?r_,^m6h6aM700=5][xM)i;yB޾^qA=]=R4VVc=/M[0 c;w>~dSQ ˒#!QbLԊetf;cP&z;"]_<`A~tԯ+d<<k~VlDס9 @b1 xfʤm\$ T+3LLỶI 3x_2}Gt 8s.ХCPr`R e2jc*9 t(RPR c L%Sҙr+nEy]Wj.[fK5O ,+Ӧju33QvFXQ 4L$ |D>Q14jJ{A" JAB:M1>>ѨiWЧ=E{DLG{Xd٪a7õsWx.~H eMy&bLӛ<.xldd$WZ:5n3G ԝ;/Ɏ,շl!"esY-@!Ѧf_|wm 7-7XOa5k]`R)c.ɏ_\`ӛyh߾Zw*Rq*!R((M׹iY#K tDqTs2h]]1^T*! ~jy]9"  iP?:Д8X}<}!kȽRi7mYub`P*`6& B4XQ ]H&A2-\G.mۆbkH*/Ld"PW\qGY~\pQh䣩dJ.]|c_5ysͷ_O5ym[F$O?k޽( IW6zeHcPZaE,f=r'GЯ%Aiv,`Sw/%X.;遁y9WU?ٽP*K) mB7_sQ6Jx, B] 2C]3s/#_I0Z# ճ -K> |z?N Bq@A $hiaɉ_~۶흞U~JKAP,@H`@ $l<硖-F@JZ\Z`WR7]wyM,ӂD2Ң0jJ=? IDATIAt(=q*F~_d<?`{:7 s4 Jo/_y qŴQ廊RGOLNM&y8[j"",߬iļ~J0zht3&AP?ujwE`cnZNfg3 /?p͚PJUggABpTJ)1fq5w`bBgPJIιl4٬+ӈ{s8V╗}79φ $),K!-A}޲RlP.H4 P -q8L" \z}:yf62n1J): Yy8^[s"4NՊB}6CMLHȡO=Y;sG% k?B(?+(:;3K}gX\ $ 4KJ_F9G)]==5dUkձqFH*eSyoASu::k4 iAPMQve@T-baOJB9%qCJq)X+{5vuL8FJU;XIv} ' r,4U|:m P,l(R/4 ԦMTOOϏ; lT+qCh eA~+j6]9&A%J%d$ !]ƢVnxXr0,i4hi" q%92|49SFErb3ذx͛3N{1#nŷkDQ (J\n #윙wg}Koo.uq?CZ#/ ðs33K|rӊbq$CPl)h6.ڷO&tCq ,+hECyaa¥љgkbrjkuwhP|bsXSLQBH0CӲi,3fd`훗l47M8aѱa^z].aFJR/ H&[K0AJaMWkA_Vaw<#GW@W"$N9ϟ?8\2FG @Q4@D<ʈ$R*UkE!DPiD1ERI)cbޑNK_R_7 wꩰz ֡CQh@hP0kux8rxWWK9, 5JA`1PEot&sSOmܢ|Ӿe}6b r^76oܰud;D*tM:==01OOJ$RJ&T-#RAއ~#upIT P@q0 `3:=9<.j4ʯ{bĞAWgZzp "kirB"(J @lB UBX@]Y_w |_sţF,OI~3;H]k9e0ye(|Avt6(\2|i_{yM60  +YA =S]Xۣږm~E@)$R'N瞻?\,)\x zWHI xBL nRv"*6qNaVVr;R#c#ao_Zph:`rb"z~IXl3<252wwJ ʅA(`%EFBUGG^ 2oHO7D4I@ hpA(%fRa4af33Qqvt/[?NcΝxnv4f+U' CV'I)q^ib")*`HKnx˖-m 338=88!DtCg˟ߵrjElzf鞞 ֯]9}>kst(j+? R+̓xE&D0l Ї1Xf gAB~"cߗH5Ȓ/y>܊̌ꢔbX|NR" `:!%'_7tuCC#rձb Mc~N)`FKW31q&P$cz.D-w!=Ch%~ ggNvkqHRPmC'(@3ZK-(*܇!#]GPo6;.V aHRZdZX 5!P B1S&gffEV )&S7ģ==aij~s d ovcϞ=w__'})8@K; 6n@&0==N~sjɿ=C#=[m|OY+7RV)&,е(jD"4ŷTNV3-!j`=<ʏU 'ݮV'"䮻E_עŃ2]ֵcNqHwwg [O~=E\B@)%e0, j719TB7,^(8р zݻa*PGq:i=2yHTB!Zy@EB)p8IС!hP!k d X y(u/Oh LFXKmlyV3b=&B{^u]}vH&}`bqVC@j&˕.8O&SZk v\G=ctP_5i4QGi~;e?z%u2e;0{۶0|h#~`h~̩%P.^9}qf[FB]uQF3n Fߵk?;2?Abb:],N#Q%! !B4OL4_IәʸMTÑ* ^S8P"l]0 )д֦됦aې4d&XJ-t}DR i0kNb S&A гObR?NH4u亚[h]gz*d3՞?x,^]gtvfFQwCnJzQB{A_OL)A@Nffgh2OnMi:eiWwOك79btޯ/sT?ک5KYbXuvʳ?ֱ֚CCo>A&y+NzsMLq*tyu>5k& j}–,YvhhL[h@KaNHʍ.W<3Ulg9rک'4䟚 +3U#!P51:>р4(<^ R()!l4 k- CsHqeh Ӳ R ('1p]0 p (dǁL@hw.p TV=R;7@F(0T:uRA@Cfffh<g~{СCƺS֮V+PJI)^hn$ fM,\z!O\ՀUGs麡L]&았k'q40j}|xoD8692܌=XY"OT"x~eOaLӴ;wL&ejxo}xsW~7hTs.koxv꯯սS/GB!Z6&CO76);RSr-!=X8dܱuǓՑad3f.3w/_N өl4=;!o6\AN=}U*U:+/Xuz2O<05֯%ý$# @QAC˦͘Bٛq$3R#*mJ (oMx"Pׅl6A}=,3gƛ+Nꖄ$! 0h 69=fͷn;4Db,;a@Fc.EU(]FK I T,-KF{VSOxRZضuZ?ȑjj dmKxk|_ۖ tz6 `Ï*cGirXذ~=7]ۖͳ,TTW^z(xGy;Uo#y *D r]Cw] 2V :M Au 5FLJbhuAiEx1Dio| *``Oj(B@~eumST+dRŇ.n܀RzqRyObzE]7lᧁT߷~I]_^ƣ P "EE  Y}P{6NťjQ|$q6~Ɋb CX$cmyƚ#riIE`rYِ'cpӧ.?î3616N ¾Rd"mE@zaxQ-6nرJN)_Xjh@Sݱ$m;ظˋ:X*YhKS~κQ Gh'0\*QuJ]@D6mڲ$ j X>)>ɑc'F%M3ʬY6ρіDfbsm+4ɸV:Y+_ʕ9s8 fluۑsiU/"XSiuWRXn>3=  _Z' OSf65iJ Wb1t~2=eovCj5PuX>ŽcT.ZBB@nlN$:7ʚZB3l"ٽzs~7x W7 wS ZjegEj]39?ϰ-{4vV`*첫w v'ӊsAj;}`SeL# !RED"&bt;qcziaYN粔nuqJ)|ԚEdT/yu}s@+ɶ(A2Z,@py'"h@Rt&ANZ_~9嗯XU*y>ibIJcNqZqHsukMpUE_`mc⛞, e޿Uz~[M`hPS J^Y=:,XXʆ;5nk:q.쎍cÄvc3ڹ;.hS@eI(RIRJ=2W,񭻒9s0py$@ dbx*Dx~}I3UsRne;@n7<͒QM"/9Y#fT$Iَ۴k޴ߏmW_ݷ2w*Nq8֥*g_lvڍ DĖm2,[PhI;Ki9X_*NMM̭To.fav a2o#gLSA2a ?udpL)sxH/K ,NY~_yVٳYL{'OgddvD#Ԇ Әv3K5*rccjnn6Dࢷ>qݮ_eYqܲdXZ۰vj圲yRR{OI<%BaJ F?}I D1+N8Rh ߇X\4,[>Jb!Ru[r٤OZ߱里AjDfW@ hnf Vi`tum3 tjtYlzPR.-'Sz˭oDrD߳1K)m"QTlB ';A6@?gԠW@9n: h(f0K5JMe݁@K d )Mf'Fx7 ! +%ILg`t%>SY/V>ϻ~+i`F*>7fYnӛ~VC>ڲ+iu,R"f|c2v 'm|+(0f޶mШ<77x!kqYs{&?}T,8%/؟o5FQ0cCVӲfVkms42sr˲Ա']4ϕҶV )G_8#M%Mk1IAiAjm, <,^ ¥"u { σv -q ey&ϲաIiLx{jq CSMW.. wL*h`FFjfx_*;Mn2KvM7P8I$2E 1pd4~š j.~ea@@ܲd^.})^3{źuŁ_8yHWaz,(m]~-*rˢy݋°Ii߰6پc˼f:nIۉ0,:9[۾cKisNd/9qTvqYY`(˲b׳xyE)PCf d߾ |"n(·[oh ]2fB,D |4|q҂$:7n<R)PXAڮѕCڵ h[xr I To mQi3'N.?J+P,"fgW$1T"T7N|t@bAa-\.WD18sfʧ,l4nwxc'Omݺ8:6,8 :d[֑S_~_(:toҬZ}rrbW qll4;aH(v <޽U/ ȨR*k0/[0 C¢I;V"Ypܫ $s'ף޻T 8m6A ]YBCp8 'N ([B0"e Z Ǹ98IShdKhp)1<-@(ȿIFÞo贗OӼK!2)rlz/.8q2 @Vlڴ5 '@*Hd}Cʇ9Iݻ_~ZՊM<jE "\fI:i j6ɮu]zs{Kc8JL 9ťR5%S^,2xl~~ۅ50b=ʑiFvޖ>h$LSd~_śEFN&  W:y۶ޘ dM2|n݁bf{{﹟: $I8$څB3\&ijez* Z>xrIF-"'IJZm*I -qP@I }``& xRzXR  N2Io+@ojV tZ|Y[YD"ؼ"Dttq1%,zq\Ϝ<$Kw߭r۷ \]>j * >( o=܇w?fAD:M[y]ռTRIɱLnI%,}X~08IN'˕"aq٨d&=ne?xADx;ٸѵa~!#[.ӭYZ>}X|NhmC:jmyR(eڡk*LK4@ozQ5!wXZt d; 2)m0*;v#2lGb< #Z?Ѵo-,tlOs`?09IX QLV*,,&CBR2Z Ʒ56n؜ w;ݶ.K[n<1 48>tW~\?x[(לܢؾ}mt_nU.ł^&$8_}8>MIoʳ~{H)7t N{~YA:ЊrENw5\.eŚj3Ogű^;6fUff$ jR.<(uߏpf+ +?N=. Gc%u|<߇(!\" !W;n##ӐO@<" - ]04 gr*p%|"J|ƵWS¢מ̞ܺN/:xH` 6n<5YW9jgng^eÖw}'uRbjrZ !Tv100^w}z<0X*xw~Q1vn[jbE认ZXcO_;DT,ukJxgy04M-<~ۭ-u;(.$A6ef[̰F7+^_Fk~1*7ڠ&+R{y;eذe\ 9s(a pWdR<ϐZVRˑ=DF˶@! 2b !.RHa!Kt<5.d_ &|j5']ܵA4 EOub{K|CO7+̨yEC5І*}\rX6Z;.Lr):bK-[PfUNtb:;wT+'bwd̾}u֊VG85CqU60ا}pZ鍗ixunݞ4M7L<)Φ]}jxTmv;QYiJi4Nxxȵ K)TK-~bՎU^IHk/@ukUy@(*P}#UקgVUJmfH$3d/z/ð0 !a " r ?Yp4+;úQ;qO`|^{#F}SE_Eg7WOzzrU9d>54CШ IDATEC_R ;SO>CYkKZ?lmu\'-;~sݸ+'zy3O CT*T}EDDӓTVϰ_{_{/o|ë-wl~2MK8]%ـեc*əӑlt =GX.j|3.ƆAsGIiZM3yX_we{>y.ü߬[A!+(U γ;γq%,"",-y޹~{ީ'&FߨA~@ n`VI T’@l`=): ID">9YշJZf džtioMRcai3Ciy$! trLaгbc"BBxÜl">H\I!/t :\aߋӃXk}nz'];/=i+_C7]CbX~_v/u:@EW>5i%EDmT~G(H&"Q ݰ>/\w1>? w9j4#p8E@nڠ+;Y^2sD˰M(M c= t=ԫHΞqi=qFnJ$AiPI Bp'.tLVh{ц86PL.B[] \ @R6+ҞX~qVn=kԩ'> < cRR⟏Ο拖?pR)k֌{ѻw? ~;?d_4M替oxbvHE$4s&<[,{Nm^^xbEc{ə3 T03PuT| CGZtqn˖s;-i'cc0?5'Ƈ6ls767WZt= Z8?2([\}\a{CC0bZ |scPB9$g՞&1CLWÚjD*aCislnvbayylZ&DFʘ)3:21\y3#S5sk.f(G;wm}O6չ\kᇿo>.Bնrr06Y(t_M̖%Mo)U nRm<{rgq|Ã#nX@Pcl\|w{gz), /r: \N3 ?hҊB}Rd;o0kt EYt0QXA P0YEaS=k&iJ)Ƞ{O>| R_}$=/ss!dL z)=>1oܿ}tӿ89uԾ lGSP}c8.&T(|ɱپڐ^\|}7G)ЖUƑ>9&B2iDzơ9ғ:m-sqc2ll _?k rͮ}+ 祔48TހQx~!<𼵴X_/pņJu˿3u45Dỻ< mwʝC4_mK%" .x:~;mكcxkmMMN7jİ( `ta+IÙb|d5 1/&) #/v:W< AoX{] ˶C r # FVJdɮȖm:Nv 2ۿ |ӁrP"wp-KU\1G~KCyeO;K6@ 9XY2 8Q`234[on4:Bgö6sk ڵ4I;_969#fM0NIՒMjWkg Ң]dn;;)h1ߎļS8%P^72Nt2͢O]mi;M'h5?eZ6\wZko:aN:ljyqX BVKsCCGvQn[\Xc{w4ZIu3ga(ʗʪ8~E5"l%0`RW^'ܴqudf`2iO!{䉓W?5/C_ _-f/;/@yyYkd. sx0jYX1T!{4} h&ft6m8vdouD0>~bp`BԿ$HHnj×ػ[a;$L(uzQB\=4ʧԮv^bf)Z7) Ng|(ګݻ,Y+FG?tlJmYwEY1òeu>{k{^ɭ ~jfggaF%M۶MsN8~.ȋ2]'O{rېD<>14Db/|¡/Ux_bBe_jVW=ӫQG<7ԮtPpܶt^hc/-[J3ykRt)j5Qx RfTo %}{y39u&q0 O% `Ć7Mm jz$@~ÍC:zr땻v؍&1}fHPKs8v0Ddn}m߹ѵZu޺mϭs33LD| x ;vNO+g]e j~~TdYO}= r pE0!S+Q+)g-4Wmnq6hYp;sQw`C6~X8Ú){VH`%D2*ʳ 'N{ZSQaVpc 7@wu=![O'|LJ,М!dTMCZ RQWn16_GL ̱spZcebpyl4#_H_8i ,!3X^7/ATy' VvI EpRB rS@ 8Ӏ.<|y: j1|.KC{_ + '@|nV$e12D,5i..87^}%Ə+VpѬcwW=P8˂` t$I JIݢ&ٕ.;NFBri~wÈhJmV2?"{/ZZ{yáw.: Fu}_8l|W;;73 qǴ 0$H,>H>}.! [ToDAA[CRߗ=:Unzep$'2`? bh6D ŘWzlTAT lQ@9L=@x>2AA 8 ҆]Hpz[_vba h&ƢJEׅڵvZ,hlX{Y:=5kb*Jeg{^]gρ+>xOZ=`R>^Mўݻc?y?o隻R 94oo X'vF}޾ mT ס6mBpn#*H*õvB s.PZ25(/RDR̰_zA\SwU ^'LJB_NgX|HDW@sWG2,uKEZS3;QC SEb"$vÐ6;0yQt|㶭6oWĶ+6o8̋L;ǣ a+ ⸿|y'z,3ٝT`Niw"t]Q٭PA}ʿoV;;On{fa+:)(*ݛաZz!,i *58/͠4B!1I!+vUZP2T`!sv~zכ~;7jՈ5FR3EZea ,fH­&ג{]c⹩q/$S׾^v֝l}TJyrj9ysV\"mLS# fe(e46d-;Tb@ 00GRQ Q( C&!kyz;TGpDHN֮>}qyo͋^>'ٳgxbtd R:N@ En0 a-d)6`^, Ȣ@kEg 8.RAkx:2 b86#4؎d2GH6b0^mA\wG idA20#(%RBK2zqm "jx'"n;]O{ʪ.4wpkA]"ɞ|. Yʰ*UR%vaP(2Df]QEp8Nn$ڕ{J#>9u >|xmc[Kw|k,/>~TR7nu)=o|i햛O>0Xc bcU-Ѕ4U4. J Rmwt|Gvr6ggAyvAiZ41 k{Q@)~H`"hmA+Pa2 4J xϿFnܶ" Ѝ(bduOƧ8) n$ HeZNPRp8pT@J`EXf ^,j#2w_9ٹGEoys N^zIޱc#_yvJV:gZm]YًBdɷr]h9M aL '.}0a6#F'zC ^;kpAI[hsPa+k#ˌ Cj80,UzKP(HEABV :NNL3Tkfa$F˳#r(6͏?Cf~6m#%e8NY*A6[ m Pfy:0[B[림}zɓ7oW" * O^IjnցdRJӆgO5̬:nơ&, ]7QKe{\ Adz=sͩ,W(K?QEc$%ʻ7;ؚ9?V nl fb0q tL(# 8&h\`,J&%,%Zkpív v]C7>}!p7, \!(H GGo WucjW,\h`*PcCWord8K8bV$X『tV%(a@Bjm,9+%C:o2q͔?8=вօi/ei[!IfD4ZsR/vȉzEaq^}"U_^2_ie^Sp"cĘ a ֍gG!j{^v''6x]Wص DPW4kXrn ϝF/aKn{Z-ymFv ^Cw͙r~qH'(_MvaVD"~ȷ~_c]#PxmF˼ Rֿ[>=QvqDL !s CH@i:c2.!HXK+f&m g94qd3sh5\ʍX-ZBZ( ѳַ'3>T@ Mc"̳Ԥ[g=Kss\KdY{7< \d 7oxX>Q~b?w_ \> 8gQgW\jWzsoǠ @q[6O)$YW֪u`W(ech 8-8́m7ei8W'rl"  ؎XnORڋ\?Uov}T*o8< 6mnҪڇN1r-s;fK;A!;9.> ZCPvS a"!)8x|B=9e߱&݌3JPô]J68 c vJj~t+/ P x$pyւE=}hc1LZqFh]cka]jrJ./(X̙g]z}hHPIdEb)79ٕw86׫#}2V zWpߙKqhw`A~[™wh4o [~trݶRDڣKDL7 Hbغ? 8IRx kwSXZ!EPA"35ךʱH3ʞzN3%M )JAA`ÅY`rs׈<}@[YJ*(\YiU4FY.e%#/4gtSK$Cˡr$S.dS K@:KZ;K,gIR;o o 5 EQJ"%D 5!DQaJ-~ǀ{WY%XáV }_ ^#gww6oRx>^zwT,./]M5\?KO:1~mK])\l#&wRæ9/SWC z#5Gq;fHqG5',O_L>56jhqY8kYduD#KDaQ_8_@RilfMk1O"GOhkwBJ fJbp8c)@JADB#cK 4a''Y㑧D[# eKfr ruc!N8&GUz_W(G<.]t l5F7Ѹ[OcV'K8"׽-LJDY!PBr f01>׺dBl hTN2mc*^#EU"Tճ?NRpZQ56 sn~% #:&,.PL€zJ@ڗ]ݴ"DrLe񵒍@0w3vr( `p'e_ۚ}MU ^ +￸6{)9XQYPr'c`tktu/}Jg+\'.j.cJ-+0vQ(H0"hN{qiy"M<-AE+ͭ ֝^ 2yj{by*w3sg^ZT<%ME S$ 8f1iE˓՝0tz­v'ڀq8B0ڢdoxZ_L|9,|._ ů5!3HȪi e<7 5^թL9Rry-f7UA+°Q8 Ca%dĜwLFFU ;$(T MQB#!@W\4r.gn,C3oC4n|n륂3/ԠsRp|ϕqUI,w_l'^BD8vOE`0kȊw%T##MUd&Ƈ\vrpI<+1==9 Byiw3㪫ju{\X WK%_~lGG1 tts2HTPk ("HOY󜛂6ߥ3zIM3ק\ܪ2PJn$K)-2PsqI@^k' (~iAZ]mPU9*0.k \`/?ko{cy>4X6@9"RwD~}:me~-.3\C ZSEy;kT}[~'~O55ADk 9MD"#:gfs3"ElCk+ime%1[,1 T+iOu }tR^Ȼ7woNjfCHBJA,ua)dRʱR6kD>>7WWΕD$)ϽnezNx4i߱tmY訧zw?zovbኀ((@( ҲV-IDATR9דiC0[XNQj?t>]>nR+mA4k^:)s}(]_w/}T亇IEc4EIy2V's'ϋ(P2M?TkQɻ+k,byeijb[H{%3RkU!B)Wr!rVE gt={ЭMwޱ<SG#:&m[.N xT @Lą31t\?T$9WD;Aqzb~۲ԔS^0O0'_*r @(`2q4 I) X,CzyV ۖ _  96"RHWxu|[2g{(t✁oUy {u \VHj? L'NWuBv;}*"yd<4-VkJ]g9f8繺F%ycf)j٬ k( RR qHB8)x(Fs]-I=B8 EBOTA@JVB8Z _ = ݱDQ=BV=kwv|xULAU]  $ƵG `T霵pXܩy_|?ӻ_CԨˏ 5p.FĠ05څE8 UjjJ?j߇-x$z.@Jʔ|'S!eg,|z:^%~Jtc9XmP`ʲI5.qFk~ |k٘U~o ʬ뾗? 8AʉQCMJ PPƐYXah3w{C-4_}wscXkcrbl)e zU`y5MAQ*8Ž,'<~75}?*ې$cV#N{#p 6PJq|cC?֋kH6IMbBFY'QK uJѽRR(Ep $cKW{ s1tWJzQ$tq EpaŒ^|S2f/|Njj :ZKvN$+y$j`߾!/m{  簉p~|@nR8. Cr^VAK=RO^sKי?+)$E!;)ëȴ7*%`d滭F ?cKopM8fa돏.Ͳ( $R=Ӗ*ל7:vDXx$7Y+ R"%Q7C`i -[qv _ٰ6ް?VQ\ئ$r)A* $lCQuEwlx{@EkN/|WIoo`p\1kK+yQM^#P1!HIb% B0c6`m?A b 1A b 1A b 1A b 1A b 1A b 1A b 1A b 1A b 1A b 1A bT.YIENDB`trunk-2018.02b/doc/sphinx/fig/yade-multi-gnuplot-merged.pdf000066400000000000000000000255361324306050200235200ustar00rootroot00000000000000%PDF-1.4 %쏢 5 0 obj <> stream x{p}I0e졍qHkn$mZ1cPnIP I'ʹ) e0r2dکET-(Hw{~t ?==s*ƪUU]Nx96OWU&w_[_%dByWխ6?[ݗ$R)ʀS}NU_)&rBHݸf͹TfsUܔ?6'jՉ*ocU֬]׏^{mMmMw\}Նu~~Ӽ˖.]27]{wl}SBy.lΖn^tt j}O<'N Ԍ(vwm+.' wu[{쥖9rE%w=vlKӒcý- C緯B<~jhlwCT-w6vT_ Ɩ^gaǮ=U;,~z#dž>p>KNwyhm=eqqotk{ߡ}{v^\vtvv<|7#,s[h7gCst79mbK̼onO{ d[_k|hpݚ}ӺbñNwhr蹋nXp.R'(8Q:}* (?6k26\[B[r7ضzt-no{n]zyDSDkGG;=hG?_hx7qz;~ q1v^@ہxU);ע]ڵ ]Mصtpg]+pܵϮ}p]?2nȢۚ/' <ڜmh_<X1:<޹+OǷ~tw?~cT'Ҧ綾%*C}X:P[:6W4ZqcG֭< }^qs(w.^2>lsʖn|NsJG\^)3 5Wxr<ΏO-;HbuV0ԛLw]-?o]-_\t9; l0ΖnttZ?^^*^ַ57߿{,y-wS|}$hơp`S~ΗkmۼЊ}/{ǖ:t 672rkeM/➺~'7tv]:s슺7oޱG_vAB̖4̌ǂ(L% e6ts8/Nұ!R3߼#}jG.>Ԟy/n^cቶDq~}(}]}Wmm#=7]`mF7׷d=I\XU;p\yzߡyשS/`o ^.sΆ>l&sNGsg-nzM/}6DP|uq\eŮNşXǝN8x=ԃroO5OcɫH<KDXI䂠pnU*/`Yگg"$_NLR[dWr^rⓉfS~v.:xNOy((;;Evfͭm\f ͠=̀_F3Ghd'f^&U ml.ޚZYAcfGie)$25~V3h=?.[6&7Ѱ]ќ55t̎8=h2Kc_EegJƦ6(_6Q٥ٹL]HF5 ̒"{+׹w]K:wghm :Hf2VDnh9{:x}}#/XZoxhlƖv+xLmC*xsJݮ ܡAC.WihٻH*ЇʋJ!PBYL.ӡt>P7Nb1t}ͧ~}wwu.;yɫf!>j&O w>б`鮾0M:֞oϏboH=ſyGH]7ND-Tx v nmz\h=g~Pm(?s )3 ] E*̠Jp8~%;R0a.u׮sU}d56]ö٫6޳H\kxj~UW~ʅk]tu-ۛ]\|}7n]wM#ng/Vlll7;3U7=pP+GsՔyܶk^zj/^ߴּqHm qmt\o?v7~vK^}>:Q]S}KܺAfv@5~˷qIvS\YVC8L='kwv8\| ۖb>8əLk3HmK~dUQRe>^[~w͙՟؛ ?>W?=k#)rcky+0Yk/6C?zm•L\zx:6㎇Οڷh{㡱|]G{Cމ?:xS+Vǁ;DĤSFp+yC$yQar;sL^m__~~[\9}mX%Qz|a:yH&??]߷cW5CkZ _hs\=榞nL/[o{F_/qݶuxÛ N+ӴhNtidk5R)*d*) W5dN_vVg EA3/H[X6dPX--".ZZr*]/,u:/S`_]iŃ})94 畛Yck ҳ1kjF޷oⓣOm ' ;:12WL8ԇ Ÿ7l/,}ET>>Ff«St_x2|ϥQ*~Raf #-g6J5w%ɾ˅PP[7;,yckh㉞xQ#Zaρk,r +U~FRZ%% $6"Eg7)jؾ%*<>F1h` F 60+J!D^&{L[ . 1A@bPm"ȣ0h[߇Q}JZ[cz`'.5B,[`#*srUpCtH2! . A0/B5-+B G mm*3LOAJv m4Zc%TCkmGb(DBJ{r6KoHAv,ڋm֞"YEt,8*mBTL鶉}(U`lC5xX ^:j<{v%6JTe|Ӕ꜌Ї 샎+$5y:ym1q|?UD4 +hC ִgA=vښ<0OJl̚o;hC$S.vxc+@v-f{mT/y't\?s\9!7}Fϋ+:dq<+zіeӬG {Uut{E(?f^wile iQٜm8 cGi$P2 :L WJXպdV*_څv2uGKv.{ \pt'RpPS%>Qkkua0`W.oo;x_DpWЁB[opk#}뻊O{w]=itW &6yj\ZY# tW =.(o^R(Gi(5_]Ngv]%u$Q @"~N="D"cԎI :&Hs$(#@ Q%E$#@՘"IcI 3&Hɘ#4I 0&H"btI0XL\1 DZ$ vʮc H 'G89ɑ@ KLA0 krH]K{pHv$PI4**#J1 TZ*("@j*q$Pv$Pr$"!vE;B@@ٵ=@THqIUL5^&L5ۘjiWLu#ڑ@  X@mRI" Ծ>Zk(.@ 6x&@_Lu|HQbj@PL5y$Pt2 P=&:#Gu&$P) ԰k&:#F#~$v9ʑ@ ͑Iz$Psm1 4Vg OI`[MHZ$xtv$$Q9#dHã#vRLD "F%h@ϑ@ITڑ@;6hLm p$@29c{#ۙhLAIL 4&9@uz\;9g`$h"~nX>ڶ9h:h pn>IeB"Gg|G.H:h`4bh; g ^L#GLH *G OpCh?DM(p.#7 3&clas0©t EBҵ獋X. ܽ R_? ~<[r~#+_{hȢ5 'Z?ooO6Łd;Y$ vOsdk{'ooܸDpPRgð  +)e 2+nbg2t-\Z0,s=Ka:T0,7T80L(1J0Ql`XnPyI! C 00DL!Y:`$)$頌`HBq#C 0$!CX9`pF C#20$RALoȓt 5}"5q"꤃O!tЮ R'E**T V!  dEPv @'$$b*0D$CЉ![C I10DPtb`2K 30D EdA1Cٺ00DY&C$: &K'xHN:|I8; tAKy9ABtN:(0&A%A!3ځ&rAY.傄H.(@rADDWN"H6I:RYP,mΓtf)) !'pm~o$eǶEEI@ۂJ>}*)E Mp=J|G;'a@mkl+*Nۆ<7 $J4=@;f=26w w%j3zȐ|2FzR³J܀@OJ%=@ 倞5!I:guA3k'> XNi? ?zo2qvMNxSd~P-{ը L᝔uS%o+w=f: c߳-nU`2f"!J;4j wLg5-]YIMwFMK9#\HM+o8NMKסi©΋Q <V(5f95qޝJMR@2;uPNI3 4 #@M5\ L3 l 4$ = 5H=#@VI.%@c/x+q ABC Ad棯$Wn #%[u?&c,El*C X dGb* Fch?,C$0\|E$"J|E^Dwq }lOq,R(:$|Cp) Cwymdv .Y2p$XBa#F.t6pT0] (?p\%2;ג A\kR Cy1pH-":SOE,5I+H] ;("SpVm E))"1d<548ElX6x@&8حAhrE@ZQ00J90CN^ k,1p/2/SѶtIb6)y_3/f91EV`,"fg2wk|ZҥbJx4Xα;25K+w䋕wKסb/΋Q </V(5/f9_1[̝&L) 2"Y$H4(#DH e2eIw;"(#.GC|7gQF$|L1eDǔ$vL1eD2ǔiSF$pL1eD(#5G9 QF gLڒzLeSF\)R0LgJ 2*Ѝ2eTHQxMf eT cʨ V="x%ʨeL5eԠdʨaLԑXUZΑ(^$AɔQGdd(zǔx:i2/L(0QxJ(#e4`7FDFHPrʈchPFe4J'z2ڕzB!+3:FQFC(1B&ᱠYv!T`.ח et(ѕ D(u.OB ͔фfϔхR,96ʘhm:hसG2Ȃ2i :trpm>oemOᝤ BQF$z6f ׆h⦼W!/\zTQ)Zwa{qaTe}ne]Kʥ 2+nbӀr~:P 25Kru@\fp|!G)bB<F)8JMeFYLtPA*[E +VO :2I[ (r@4l`"f٪R٪Glfl&U|V;٪o@ 3PFP@Yl2Jj(zJy-ee2P8( xfe2P!>]3 x e2PJd((dw;*S`;fzX 7lJd uU r٪ iJl,ӣDJϽDJ%s9]&U Ng*֜dle+.< ;g̡.K@J]0>+)<36(S/pq~-.4=RY!F2>YׅgS9I($@Y %%<6 ;B<;" Pv2#P0o o@91 \ӂ908l1qfa `rj}ځɞߡswfX\endstream endobj 6 0 obj 8740 endobj 4 0 obj <> /Contents 5 0 R >> endobj 3 0 obj << /Type /Pages /Kids [ 4 0 R ] /Count 1 >> endobj 1 0 obj <> endobj 7 0 obj <>endobj 8 0 obj <> endobj 9 0 obj <>stream 2010-04-14T22:32:21+02:00 2010-04-14T22:32:21+02:00 GPL Ghostscript 871 (pswrite) Untitled endstream endobj 2 0 obj <>endobj xref 0 10 0000000000 65535 f 0000009043 00000 n 0000010583 00000 n 0000008984 00000 n 0000008845 00000 n 0000000015 00000 n 0000008825 00000 n 0000009107 00000 n 0000009148 00000 n 0000009177 00000 n trailer << /Size 10 /Root 1 0 R /Info 2 0 R /ID [] >> startxref 10748 %%EOF trunk-2018.02b/doc/sphinx/fig/yade-multi-gnuplot-merged.svg000066400000000000000000000715661324306050200235520ustar00rootroot00000000000000 1 2 3 4 5 6 7 8 9 10 0 10 20 30 40 50 60 z_sph y_sphreference: z_sph(y_sph) hi_v: z_sph(y_sph) lo_v: z_sph(y_sph) hi_rho: z_sph(y_sph) hi_rho_v: z_sph(y_sph) hi_rh0_lo_v: z_sph(y_sph) trunk-2018.02b/doc/sphinx/fig/yade-multi-gnuplot-single.pdf000066400000000000000000000146421324306050200235320ustar00rootroot00000000000000%PDF-1.4 %쏢 5 0 obj <> stream x[pUG !NlB'wJWT(`$d˥8*l %[Vݑ;܍\ a-IB e† cvr'g\*^완_> rY"q}W!=C_$I!Fn)T#> ROgLg}OPm::*9m4T Wj 3H 1IDz`b#ҏ>=_zPUU3Iweoz`e8%+z/|jaiK =ϝXԻc4>d@ ٿ ۵C^l{juK4SӈZi7pvȴ#qefDdڞkP^<_Je)+bE蹔 0 , ڮl_ h:)qɜ9Jy9W9bRU4Qv>\~Zx/^\?!Vm* -y'ђjMdڏ}y"m1?jWlm?u,7K@mմ-^s%^+GP=*boVgU( \V9*Ğ2ʾlVO{\ڎݾ}.j,jDVZ7w'\t3uLSs$EGzkw}@j}uA*8[9A>o EVzz+|>=18[z,Y ^i bUUfsI;JyrW;bj|!Q{²ք=lH|l(#DGݟщ!Wc7*Wr\!JhqRʑ#WB*Ӕ/ӿ^pJH|Q+i,YtGٚ3%?pMϒ+l 85n`Iߗ^)AyZ}Fjk>e=CK--.+j+MųM7pMG {Nd]uNn_&c-gtӾʗRdkM+`JD3=F6 wnq=s=Ӵ@=En)%j5κm[ǒeG ݃g7ȑMʢYW 4ሽ Zp7p wH?qizO5)=x'wh?W8Q4z1Rxc$-a/dk"Xr]tKȶ'@$9p| KڊKY׆>"|#Z2:DT$̑c$U]*Sdu5o3r$d_?ETɂ)=%9,Zd)qEWe03 xbA)\b 52|FȬ݁J@o'i9 rƂzy _ YȔqݍ0tR=[~hAΘ<Ǟ-i^-SPYqGzuL`l3K\8%^,1_ԩ82OZ!n_ l`xÚ`.)Tڰ3`imt8O%c'*j_y~iNgAc/S:TgWnΥ}H=?w6Oڽvy(0i ^nILU2nz*Ns{;KN~E7[#CoߴFZƍ=F.J7{Y5v` PFP٣sv(8eo>yn޶#m$}9psz{Ae *jd^w :*S!үY\ɿܴqxvq,XU V 8P3] ㌏솊l@C9~;nlۖ~L-6o:7Ye7jC4}LIQULO5@tjCBw~SW/|򣱺X!;,>ct:?nAt͍?8g${/k"Ӊ?n~}KvNd',7aqϱۊgtF|Bvӽ9ȁ+\_K%3V3 +:hCgB$I6w[;M] &.xct=Ow! SӀAS[鴯:k{]%wݽ õ/}8tw@;c'੥m oW]PU?}C U[οe1#҇Ptxw+\i_w3#@v**Ft5sw[.$}DI-VLbݦO޸SV?>z2n.b[#aΎEhө '㥽7m_7:;i_%)14'-\b$SϓN*FdF.!HL<:7Y|uɦWҘ%6^J"%է]< +=((8 DDJ. xaZ杖ax__Ag:%HyVj(Sz`ԓy`pUI u0LQUA#dp4+O2;d('Ax(GC0g'  L:t25F(]־1JWdsF(5tMԫQLß~ʐ1@"~E-:WYЈy2Yב@ۺ%"?5Ej|MJ'SRx9&2CI2T6Ѹ.UOQ/5XV864_,:0ijp>zxLT6(bcSc2\LGLM~@暚\ bqlMQ4GˬTV96qS4XPs9ACuG ų dPa\n"MPedΡJC?W%!uWe:߿ǭbcUgsjPF)N1xUj⢡:xpR,!&pj*'S._A9֕&PXC%TCQSqh(pLlAmKӁC!.75>&p"EL p22& sH2E$*Na 50u^f éFdM`8U#-Ф=3h슈1`Uᖢ*L&u,ɪcQUw0"4FM`,qj lHt}q@"c~,\ C3Jendstream endobj 6 0 obj 4201 endobj 4 0 obj <> /Contents 5 0 R >> endobj 3 0 obj << /Type /Pages /Kids [ 4 0 R ] /Count 1 >> endobj 1 0 obj <> endobj 7 0 obj <>endobj 8 0 obj <> endobj 9 0 obj <>stream 2010-04-14T22:32:33+02:00 2010-04-14T22:32:33+02:00 GPL Ghostscript 871 (pswrite) Untitled endstream endobj 2 0 obj <>endobj xref 0 10 0000000000 65535 f 0000004504 00000 n 0000006044 00000 n 0000004445 00000 n 0000004306 00000 n 0000000015 00000 n 0000004286 00000 n 0000004568 00000 n 0000004609 00000 n 0000004638 00000 n trailer << /Size 10 /Root 1 0 R /Info 2 0 R /ID [<61401214681998CEE39FD32D9F29B263><61401214681998CEE39FD32D9F29B263>] >> startxref 6209 %%EOF trunk-2018.02b/doc/sphinx/fig/yade-multi-gnuplot-single.svg000066400000000000000000000556771324306050200235750ustar00rootroot00000000000000 1 2 3 4 5 6 7 8 9 10 0 2 4 6 8 10 12 14 16 18 20 z_sph y_sph z_sph(y_sph) trunk-2018.02b/doc/sphinx/fig/yade-multi-summary.html000066400000000000000000000031741324306050200224510ustar00rootroot00000000000000Yade-multi at flux overview

    Running for 00:10:19, since Tue Apr 13 16:17:11 2010.

    Pid 9873

    4 slots available, 4 used, 0 free.

    Jobs

    4 total, 2 running, 1 done

    idstatusinfoslotscommand
    _geomType=B00:10:1996.33% done
    step 9180/9530
    avg 14.9596/sec
    10267 bodies
    65506 intrs
    2PARAM_TABLE=iParams.table:2 DISPLAY= /usr/local/bin/yade-trunk --threads=2 --nice=10 -x indent.py > indent._geomType=B.log 2>&1
    _geomType=smallA00:09:53 (no info) 2PARAM_TABLE=iParams.table:3 DISPLAY= /usr/local/bin/yade-trunk --threads=2 --nice=10 -x indent.py > indent._geomType=smallA.log 2>&1
    _geomType=smallB00:00:246.95% done
    step 694/9985
    avg 35.8212/sec
    9021 bodies
    58352 intrs
    2PARAM_TABLE=iParams.table:4 DISPLAY= /usr/local/bin/yade-trunk --threads=2 --nice=10 -x indent.py > indent._geomType=smallB.log 2>&1
    _geomType=smallC(pending) (no info) 2PARAM_TABLE=iParams.table:5 DISPLAY= /usr/local/bin/yade-trunk --threads=2 --nice=10 -x indent.py > indent._geomType=smallC.log 2>&1
    trunk-2018.02b/doc/sphinx/fig/yade-multi-summary.pdf000066400000000000000000004455301324306050200222640ustar00rootroot00000000000000%PDF-1.4 % 3 0 obj << /Length 4 0 R /Filter /FlateDecode >> stream x; ~O.RYvDO?Fp0N& g61B9k99 *5yWl[^ y A =P$ endstream endobj 4 0 obj 91 endobj 2 0 obj << /ExtGState << /a0 << /CA 1 /ca 1 >> >> /Pattern << /p5 5 0 R >> >> endobj 6 0 obj << /Type /Page /Parent 1 0 R /MediaBox [ 0 0 560 400 ] /Contents 3 0 R /Group << /Type /Group /S /Transparency /CS /DeviceRGB >> /Resources 2 0 R >> endobj 8 0 obj << /Length 9 0 R /Filter /FlateDecode /Type /XObject /Subtype /Form /BBox [ 0 0 560 400 ] /Group << /Type /Group /S /Transparency /CS /DeviceRGB >> /Resources 7 0 R >> stream xr8%8Sx-SS(؃-`-}ߩA[ қ?>Z;oUKy綿cx{GoyZoox|:QiU9M}<ޞnS}O;M̲@~ΥͱYԾ,XhΞ[ O->s@ʩ$Ϳ7$*@>S\(ea |./(U>/]^}=sxßvg{WfSj9av;_lhEi=-R|GWL?z z8{O<տ}t_\_ʭ WȬI ّ5Q8,}7z!a.?|Ͽxe.v~\VӜxǸ@{;UǞ~81ru@HN_h@+?P;:VqUqL?P;֗pF DY/v}B4] O!'_z)FoLj&|GKo,tw8 fK+d'KWt_I\eٟYwٻON8mxEʓqOq7?aq[X®Vȉ܃tίX;o |կ=} D=<`\:x[w_/7Le~ܿp}֝X9=QڏQ2$ k~+~_~j8s~8_0\ 4~-N$u=qKޯ~~jxtCOTssG?ܣvyu }@[TmXX $,#YtzE]v6~f.ţ TݣỌ?;scbJ~H:u@H^'^1)c& O?JTun͗qX!^=op& @IY0}\,/,뛸x~4={GHOkuv?Qoh^/yIϼկwa4V$$zGnHSq w~_?Q)pJғՓ Ix:\)eOeup 1gwi 3i> 񃿏9W_+*oh~;6XZ0{)&p~)_+ά{'Mx+D+_{h1`̱)_z^!z!`.UOwJ@|O K7pp:͍Qb_a}]Ы}`~^q~=%W^DSw,3_΍-,kw,eb_ԫX{{#79?ΠǮFjQ?kf;3iSYۇi7~z,W{g{/y`/B 'zpJ(RE.B moD{={8l\j>xz_6~}$'mA4@&Q kbOJ쇴5bO篷JRٵ4UN@.S= MTz2{AWnv)qŏxJ\Pī܃qi '9U)ֹҞ2)ی:l`s;][p;t~sa] $nJsĥɹ!j!m̭vE6iOXۛ5?pw.ۄNl\jTC[Up'lhs#T,yh'ʏz$mZH"|!﵅xS'^ }Hzʰۤpe;.c/sCEv|+`M,r 0h6LݴMéVl0w9@3N'psYDNj1ϧ8zwdMEeYC`lJv8= !I(2`VtBu8lc^׉~6E~IR5k*^z1WciK)fr ԓWCE1/B'V:w$*iW lrF gXi]`HX,TpԌ zJ\aM#9lWЩ+9 :{{ܤUxwo%qF?[$ɬƜ 6f?T@燢,ʿphdg, f&:OdDpAMJU-MJTn.(4+8G̵udXm.:5f&Vs.t0KIEW}nԇ,Klk=/񁖱扶ecgKIv歝Kfg 5-p$~8zm+l,pd6Md]sdX?nm!pH_<,;zhz1Yѳ{ _ƈ52\]jO>ge_j-m6`ݰlf#uhWˌqPh^4l !ѡ|6\{ί7>]ι38O|0dvS|ާ:Kygs%nҸpgwCa ho98 n 8' ԇ溲J+Mn82&: T][Orc߇ ;Wƍ!`^:7E$rޅF!eąoy21NCӗ1T.Яcb{L nQ}lb l|b}Ԙg}7Bm\;чlнO 碽h|n﷙.زxD tww>?=Y(ﵧv|98߭V|99ߺ߽vlz XDwsrF&D,#JDSQy,VtƎDdZg ҝS[׾z78=͑Gx7fB?(O?/-Jm'\+m=dSUW4rM#@z_Q5,gyM:b85v]2s>fքrY:ӌwnekPyw<чtн5½ x3jsWl&'nnHdn:=x|=~{pXH{@v> 搴|$G+$?~o]mi Ro?l}PIa Y9m@$ŧ^i8`>U%6`-SfnmҀHM09 qp# JRj=RQOvfS;IERAAi7U99ws!'ShWVN&?azrXHU>dxZMg5c;]M W3TYTE ֯퐲 ALk+щ,py#d l:| /dQ&\ڐk1Rt7+\3@wL }L]{9߫Ut˘W^E|M>Ȝ>F)ygj.A@N2QϪoH?g6)?IS]MeֲΠlFc!`hrY:Š22*!0:h!:T@-Eoz[I2() 2QLDiCkf0քlaV (VMliBѼ\3{3"jzPōW>"MCtBد  $WJ@񍸓^ɖ=p D/~# ?ʮ|$\" a`]ȴ̉_ npg@1&02ed-z;0`[MxGR6$z8\j=0f{#/i`a͟Vl=&a}|թc10C|q$eY.hƑbɏKTR#H-ת^e=!>Qݑy"$cf9cbq؏.E߹o.!dJT_2z蒱d ~'O>'l7&s+H.;,7=PQ5WыmPRX3$hI$4RṖ/-KF== QO?Ú!|-ۏ;o_/UC2[oph)}Is4\>Ry\?B ?~jTbWN\g<6e~Ūy5p#ht7ޣ06!GSiْ,7ccq\ݏSrƵ_IJSo&.V>9gݷL޳s, zXRdƇ+t(2|rYrqsQ3>j~mHU)'#v6[Y3G6uruIuQ bD []Ff>||K6$:F[F!0%pYH66^9eeML} QE% \4i¬XxAڅ`P/q4ˏ0Y\& *uaMZZ]UU|eߦL2~##K{^{||$ղpPZ>ӑY rDqD`sNOk*uo9]g;8?iK %BHZ:hwHN⧩ U/,l2I? {27颻=>&9>gksЇ1{ 3U dme7rt!=lгCzt^6++v_i>ym"A"L-6A&Rzfm.of{@14*a訫ӧd4t*uU1AH7"'=KtXbl2rO] Eږm@{!9+?o!rr~[EEqiG$iXh EE#0y]Ubl[5BਠA˕3O$%j.7tt,^#DE@Zp!V먗6Š:miN.Čsb5f:ܗh(nFR~I49oGqWT(-!5# 7=)Kek_@pj,c*-?D,&Ǵ:C0 M& D'NJ4dc lhMMͩ3jj(ѭ0*׶o:u0y6ϩqb4ЏoJxB, i&ZJ#̗QmQ$^ۭ&cg#晉^:RI;=D/t_dzyٔ{.VgI)-ΒK D\]1'CQXbܨN 0,Jni]+:cWX+yVmT =fZpZN-u. f9(]JiʁE$o(@+Ke3$=S5=h= [9Y`@>'704/倘dI7WvIsb ׉} y&LR)"VG T'̲"y~B<4tvr% AB_|#R8x ?|"?\ #[% aWv9(-]oI{0oYsR6^Vqa_ _ a3MZ qlwnY~0>h zޥC$=C P[~]`s9|S*R5(5ҏeiچKˬ `bIy[#Z2?Kv ȾN.^h1(f 02*rBdv q?! KlDx 6.[!CKSf;>v/7|%`l"Kĺmk?-l]h1?!"‘](["D_m9QNh`Kf1qvxuI["u{aw{KkDr{R+%WT+fzpg0ArsO|,KH.NOQy:RYr/y3*']N$9N^$>180*FY#ӍhIDچ(42rBbhT h1&s2mR#f`ŦјNڔ1Mg6Pd^¦fHc'V6EcCeؔ%Qd Nʅ@`/aS(trtαv.Sؿ 'ܑZ=J]ky!h~ J!Y~R]up7@r[h}KJYRj^}I3c'(]ŒwZr̿v Ebv gE)Q*5-U:\sV=Bw܍jsUs)1N1Ms\"MnJiLp2~H>\9~vsiMATTFsl&vMh)mffK%[1G}`#ǎ+QUUWc+= =I#Le9յLUFSVymuHW WŸGas$k>@4ED{hF\I; ɤ4-:ݑ`~ۈIȣj0sNz؎K$$w_OƞUɕQܑ;̌[-y$ `iC#Ek1{6` ,M4c)쵒ʧfe-R|G[@1خwi E*mͼqYF(\2a(I2s+^|P.j̫n >|צJ=(];w\SXe6R2`Hlѩ:'NPJuAۍDE>Aqԯo @9q,з m&ȋ?^sZJR8_&( -v 5?hD"kfQj{p4@N8-q7N.t.Ҷ ʙ}M #F "b2=ϭַF iB٧-$k˨WDOq"~{kc$M.Q܊QDi-X%e((vۆsGݑmi~cjLwv) 7Pm t17Jzb4X}/]|p'sCCԴ!ukTx'̀_SpPT YK[XD«m&GxںNN+gL$>`o?uf*?zu +lP/vFD[^s;QȹN_o{stov]Ik+n&63uډ~\:~ f.Biܯ_Ms[le>M \cn`.Ћ7ý{; [eQUߗS:E]Yx];.T?1w" bRJjdfirKɐM;_;]K٪pjFI+*ؖ2Zytr1v\3!dl `kgk҂QKFKWgha}?[ieHTfS)9L6@ϋnnE~Mi󕶲vBs6;_svoKohWQ3&ziSEƛs"}[Zȡv@ousQGp5̂3C˦O >ӑl¨Ȕ, DZeΪF* ? zߺK&PRfzEB!>4xj"|>n&tFCՄPHL 7oѭ(}n>K\?In ┘-[2X+v%B?˼a6NC[.5"dLqM:`7   A]IJEe]V^7ik\x8v izM OYk2j#4pU(VƉIܲĒ#y6UR{mYb=$h-`Y- )Êm7{'zF7!=-*#Oqjwl dOw\zl Cy;T[]pCAVe(| &\Iz E3"$ ˅ sCVʈ2;Q=.nt몪e:^oM| I(TڴZbaP#/ΗSՌ8T^'`o65Z|r"T*K!_o1\9$\u0?7$HS8ػ} lA]E2ҫhvӣ-1kc!B !M1A]gl4%h̖F?@VA*lbNO/I0Q7|r;t&~]R$r9!gv+@cRmF)\y*J\VrOU@zMӶu-F˄o6LD:wnqEj}5oyڬT O&Hc $ -}ij0Y.9:G;G*ݝ#ΑQY%#o$1VHk$NzG^9G |2;}#Q47EtߨO0 ݎ:Qߔٟ+k}:Vcըz'AYmќ C-(~(/vZ ~Eތ\ \1Ը4Qj|FI05JKFiPWi+;G FI.08|nZlw RJP#,R#waZvԸ0"0'E,t6GjaO[$pMhZnqQ q bi2KԐﶘ$yߕ1IB Ej/t-|'`IZF{_X;p`Mn"u\6DrtSW:ñT> 'D@?)g' ݢv|9 X7vuǫ@Jj )|XȎC$h!&, )"ƛf`!dTOrEI[m#-$-ؑ$r- 4$]j 6 /J5Y]t#y>ë}6 `ld?|w'\vDyFɝHptn6RNR[md7 Bn 6wFaq$52쨦i{eu܄rf`$CXȱcO@ P ^?.>j"MD:- jyG %!i!^1"YLBTV~zB8pBr%::tTR oƻFrC ` 5>8W/`}auf#͊1Gy)@"w,i7ٌ(xc֪rnl"iS2<WqM;[͋=**֌1B}^=٫Řm#Nٌz7^d^"2XHUEMȮG>p\.- =f^ 92< AqܑhWwd݂ž`н9,j6Eǧ6+O YlG%0+q!V'X&Wz,=%]Jm^yxlƲ6jÎ|P?8p"E *<@"r+(\s\ζ1mDd4glŸGo 9)/[Ua_\^"Nf[, 챮[Hs&(qL*J)/>AJHrw0-V'꾨rUdI$ZM-|}n2Rߴ.f-Zul V~3oXy,cp,uhx-re`͞^?T9ͅqlo~-gRhdG+CCcW(Wh"pZ"mX}51c:oW;R1/`oN\'t25IO7-?A"aR(&dpvlEU4Cke=vIC*;A_Fx$2ƒRdgydnXP ~h ޷{{C^J^8^/Z;fٖ䭄],y+1سRr?XӷRBO"`IHceFHQJb.ypu]mqmI䒲{S\zh nnǗt*w5 \";> ҊIZ~9Y!?l}t I`eGi.GHS£W}W]pdR$@9~>{0MW̳c>zN, ӿ0&R.-H`zqM#L5tboPǾ3Y!} յpY@ qh{JOeX7WE 8" O)OH= l!#xv H-FQ&Fg]o`sf'D..$@h\ CGS\~ ]F7aǟ|J^eTaW׸/\hYul6> "uD4pSaM'&1X_$ C Z0ר9fǶlGuǰ88r)E5e]2Dʫ0aX/..J:]  NZ~ϪpR<(Qߤ»q`qNn'Zo(O&)`hr +d=d{fRe`d&c}5ǺLknSc?qYG@,]=pRm_b_1?X eÄġc >4ct[yJJc<JUFٶ &m*FwXSZ)VK#~7+$L۶idFb"sC Ebl.񒗄ws{pA7 6?zmo'^5M_Q Y4Ju4 fIL')0kڂ>[2_.uؘXm[{46g_/)q8$b%','pyўY,RK/yND>+8WFZl6|;$9:Mvt`L|Li:ib `XMX6kM LTNMB\vAlH9oyӤؓ4zz:M^ʢX̒ ,fɁEƾ^2O# Ϸj] _ $ DUL m^_Nzm<;5ؾ^!8q*EC5O t5ymsZiۖ&iOWkrn]2}sсEt;O, oIx=HxQJ$luY6r@*K?Úrw%?K^^#Gƹ6n7}"6i w$B֟Bї 4~Vl+q%Iz5XK :eᥓG '@h7R%OY2ʨY_>@sИ:~+jF$MnDďs@ -wL[| Q `sȸa6NC{U;- žv~p 9'D>;>7E?bCyRY 55ϓFt :Jm'?]ٳɼw\WO^J[l&6оq.Q#R3_߿rQl`uIckN@oZ8&f_ b4t|lDžaw'E&.~!y#Nrjinb`w&f K<(ս7ey$b˼Yu"xlae>!RhZJS HS˄|h!:xl\Q˓ }{z v"]=h GrնHle`EWe [z翵HX i8_o;~+瑋-T1:E @jOC͡Dq4`inzo> H |ê!Jsjd]ǜ"1c=4Q.:Ju4K!]egv,Lj~x˶8 ڨ36b>`j);y{)GGM QL<ceZ7\ m36DAvf '2 uF0_ C la0’oצ>!MUȾ:Z>~zmd7kP/e "[Pk$1V@bNWqa&yqgY/D}wA# VY`͡G\/dѵgkU,hM.Ya2 R)гł C}7r&wܢ0WpU̇9'@USO>]433i+uF?Q jy~s2O&Rlpy&,6PbhEk@ 9OHacl.%v)Ui:AD Glĕ{d  AYRM}]cw|TYP[7fj@]dUM'b{?N"^k}XjdajiԻM1;L L҃Ms}>F= _4Dͧ&V__(q#y&5 BR71L>&w h}<rN) p"ZSC %XTowaJD`nb9A.N *05ݝ2LC˧9 hYz!~B7/.(( tHImz2)m⋝tx Mxt$ ' z73ۆK8ҖM6b^XF*{ݰ,osxC61؝[{[ORY6@(yE%ږPBs4 RAFiάd?kg:mOH}&f(|K◐kš4aB%q }\gR")- 8%SeC=p?VctB7 POi_tx~?75JzWMpX 16,Jt tt5:eH~VyńxLکfAfK [Yz'rjt0@H/;v H^sG8 z,7y8̼ a-}xO5DCMڲزy1>}w0vq{ňz6??DJ`[U1iFCtT y̯ZN5-!ѷ }(L#f3.; ٖ(ߑBb BgSyN`?H&#GO˕A~ύyS Nb\W٥hRYL$Қ@x-IS}E6mgu=$̈́[&&6$ 킋/ D3wDpa!)1m)tQ\$y:'9! vxZ[Vfuvٛ YFWY 0EС Ke!1ޕ!Etsh@aTs ]GMBüBF;Bqw"BiZ2~`Z^O3Eml[z߬!f-q&͏E"#艆Ԝf21f.~ŝ;:t!c>sR\fJn 3fV[ mCO^|K;}zwitf,$  Wj%~}Z:x8Ը׼ J~kNnԆK?s k{uIۦiL6J͇w;YLpHK!\8$8ro f [{(.@2|Zs\b?.M/< k'tlQ(~pê2z炢`HəI{/_ >P|25/v R VԘxBcXMBQJFN:&b!VKĀdY)kR.Uc~hEuH[c X.T4[Ҫw鮚Z6nQ'|kBX`1Nu آwn۱lPE5A jr*u5Am%u*e5%c;TbڴoP[H"l*@S89~qsE:os{|7XW*^+vX{ ow~y-#!_ >i֡GD#Th~pE["",a͊OmQZЃհ,'z4%%Y0|ˬGod0 Җ@" H haks>|,ECC,Ԍ㪡x>eHe*NU&¥@q\&w%ynBkw}#Js&VȺCU$K;}kI-e/4dmg/e/b @O ?B|B~Hg)6"JDdVb+x~3=en/+'fZ]s^- Ik7}"yNJE Z7v,wm%<YBoXY6cO; 2LK7,Eb"3;leE)F u\_@CB\ kAPYr?Ӳi rӢ K6.Gb9v.1o ÷* ]+1!^Kfyۺ &0;|ƳS]2[^'ܽ( 冋{,Yz1uƚ~Mx~ԺK"q`i@(SA~Gf~ WqcHYW9 ^EaV|Ca+jitrmtoĊ?2-&#r!tm+lpiX)6*8׍QQGerV(,K\n˒% wͅ%PZkĒod؂mdSaǝty7&~h!B|C`/&QkmL^̲,cgs掁A,eh,VLZ@gq0Li!Ossbmrۥq\Z/z0HsHFrY ?$~F?s;E'lή]6C8a¥GaipI{|UJ8vk푑AĈ [(,#~bi!q-*׶o {qi.tY܃Ï1>F,Fm:ka=|%uuN BQPN'is⫤|9x%<8Z)8/tF;J <^@CAqgbXdo02k fZ9ȗ@'pTLI9ʚvMlٸ3&l,6)aNӐ4ȡ }:Ŕ#=e>2zsCiچƵEu˸mql]zxm Ow$Iۤ,>oe YTg+DG`֓*/;>T.5$YmQ%*P7Q+jp:,B`/ ,U@+)K[# WkhiW}rF"md+Sm$nٲ)WȪ"a+^5gki @y$9¦Q[+Ȏ]nѦn(6d ^8^ 4~F zd^\†8` > $!a')h5mꞥ]Dds]7?> ̢:'wZ*~4Yc!mi?f]A,/9rԬ޳9mYiۜlsu$c)ͺw uaU гIX"/!H;-&ΆG¥&k*zIW 6WA"6Ү.$l>BXtwo{d{!T'Ҕ\ C(A™ERx=<$NN_H5>H-#.#oڈo d}ӕúRYXHX_1஛]8(Icni)$oS݆"h T*.I+dHn~է P Ky~XcL=Vw8Mdg*鐾dHI@2@)u@w Y 9"i$&B;f%mW#WFЊq:6w-2x=MwGAOXaVEi 9fz # 0|i:%t&^ޡ 9(&V]y v/;{Wemc֢ؖ+Nŷ>V=dI Nok/2? z!VGE P"Sv?,_i.oBzRosF$*s`)H({W/MXAϺGUǥNmBXn1X׀-XeIp\e^8$\kLY26{؁2.nTYX.ݩ ̛.β_of{ҵLsQ+XLDp@\CE-Jg_ ݭR\O/3 (vBĶ5{N1[`pl `*}UvR Aj%V˭%A}ݦщ0jXI J(PO@&]uU@tha YO`W2CXѼA},v?m̔QLkLi<'xM)-ڛ䝇WKdpzOYG6XzXm\{nN]RosTp7`.=l]شFT&1 ޭ:RMKjҁ{[@0J|X/'ZE9fkI5X[ ϣvz{p:ƥưSvuq7ѿϭͶkwm{}q-vTl*c'Pp]81krle2X׎*!n8wq;r͓*]ifJx/f5!xy]kG[^<2Yg&6#rc? ,NܷSXopL}J7ܷM,o)ط'i-Ev{طw>}蟤q޹P׶O?.ַ}y]`KN vez[xoy?we(3!ba67p!(X nG"}bݣ#'ƱA.w8v[/?WyZnO?*rmgd ;1aS9Lg)U@=yIӖbHSpҩۺR7;,<~`Aq+_B/1~,tM 3ݹ3ċvhXz,;]C" }Vb 2(P's9O-ebI"ѵDeًV-|=-m: s ։0'èf Is0paNż`d^X]PPI09.~F`1<>`>peM;' f3:8E<@%ؖ%z'|TO[FDP8u& Z$Wu|d.څ(GGڥ q\8E܏ֱΪT"M67u*h]c'qA> 355w=a%<61qWaV8:&Ncs{%0'm^(?VOUGQyV>O wh ћ~^(?I+ A)oʭ|dܷi[+(K e+Fq} tncu"({ SIXf7V NKp6|@YNzzt$o $O|˩P6g#/qJlOEB؏ P^l\oZko:i&P>:;ERC/r#(*$zLSO[+e]7t:[b⍎7RSxc^ekd^:4>qu%#Cq$&( R /IdTDTفpOf|V ق7`J*dOFB)\־#OooEģDΜ:DFx .O rJ1JC.Axc@صkKMINCrpf'm$Nì7QZ?8X.gFh%XSԋ4ScazxC0v5ˋO\Jl&J2=ur lP<Ui zN!+_ YuQ*]`+Xj"G("^SkOBDB^"9Z%ކt7sr4Hf:y['ǧ)~*a5vP~ nK7? mRm)&O%|R*Z|KF5|,y_( -2mj+z"tuKusr{FK^n8jڜS3ϻr&Xg#"jEdTgdm[a4)\T+]yF/Zbn7Sd#ia6O6:d\:Wg=y8.Fœ #̹^0=gmeJOFR'IKi?8k8ODăQ2%(}HXŗ;0hHt3Wteݱt.~Aei\Z(ij #%JmX=Jևd5Zx,]Ea(:|vF9A=Pi:/ȉvc$[ [6:jиhƞW oPFEM(0b<'R$0L쪗ĉp$n<3(4Mv9adJ(7FIK,RV=|.lVMn\ 3ok W$f6?e@6i W{ԎՋ܄=v9a5LW㫋 dFy}$80VdhU]EГu1"|#%cq #&jf,&: i 1L&ǧl3f71oY5Wڍ A&- s5aV)(mb}hgKB6&/7fN@)17]+L' p9$ TWf.̠d"+l悿lB@pa2a _Wy0Z()@Ok!<чbvm)׾|1oGWf(~ko<$Q*Iy0ӊQ؅߁Hm.5{ߑwǑ|,cG^EOլJlZkD4=4.*o I[Ѽ֐R*m lf.ZӨEF\ZΆӔz$Y&hG0H\'ibY˨4zׂy*uݏHs:ulgd UV7}%>ƅԽ&ͫ((8aEv}hw|S(٨.c8}m9}U4^ 6f"3.RBuɽ0] L9x[CgH69[OBAJq=!TQt !?5ƌ^e(W'ъ%>1ѱsZq*Ԗm*x>o5Ih̫cֽȂiUTX+2 [_hU]?#Q/*L֊ pO1*gUSgF(̕/DDҜLx4(0ToՍ\xJ{AD)wгX~ .Qy9(2d*Q)ELTPIb)`>-O{X+»ŪjVj PbzZQhB:դֈ&Mue-D5Z|]H?)?9Lmד[3fj!Z8 0TPX)Mcd=w +EShvq4M) 'vtP_'4T U[ rj¶A.Ļ(teC6LO^Knq;R6IWێ6u};bT@g$ů8^E~~˃d][H)Aa4S4jF9!IeՓHy]4Q-S4UB.V =/tqqTxB8-dHb MRE"rKȫGߖjDS=)9'}iͽ?ZtVP*[#>LC{PDP@/d\"_,٤z/P:z* __()ACC EȺ&^*zg!EJ60CҕMCs*yiƎaWvbJwDN1[QT@>͚XįBf ̅aQ!B͐1!Q4\A=N^ʊS!+}]Yeg -X~+E HakPFo*cg?HԼ$;x*ŧiT3+ډgN $A:Ţ$ZH d9()3 @+LɉmEDŽrJx9]}_SQ:SlR|_U |E#]wbΦMT뉻8/OeJtUwKd┧&.muj\a0gCP}H`ȇÈ\ԍFB2/b81zئ:ͤ>H"O G#CM;`tܺ;;kpqY#!P@|$-pEWP|!L(rʧX$*H=FCWiؚ| PA簫)tJYsHzjcs"Ho p|a0եx3tDxy'cb,N""ZdSvOŜdJ|StA8.6TPE Xr`p6y-Wa ߎ}g+FLk=٪l4"8;xH_1ɒ-oDp?妹UpH3Kò@a^P> Ini,l\@p,M_4<`湲T[sF\nz ˚pI shF6_Hx\gOP/S"5D2ߌ[c/ o~oW/\MNQV'Hf11̖<3iejSpXjl`< cEaҞİi㓊^Cm0]@pQ/lF9:Ro]*( k .Lt/\pB)ʵ C|9N!/ xTɼ G펩}CA(Aifb.[dL2;t-VS ܩj]ev(*g YD|i!-mv$jƨl@bү5,ew6 UC,p]" V/ԶyR-Dh:vO_P<yE(ZYUp~$yp0vXC_ Ng:3sMm7l /'(nm\pmv6TŜ< vWia4{}&ڕa[hXxG&)dNa8UbE#?D)8Ld^9U,Π/2AYo}|B~{}l`ӻB8wAy*(h85N~vB̑/>`5{i9V潢 1=b+kƢ2LnL0`f\VfҊ[I vX0A8^ױHǫ4gjugHSgE5~Ǝ8\8)JD34vw2C50Z-:_q,8L ֵ!#~wSx xLŕЩ0q{#ͺS.]_DJ A aȤ;ޙ-T&Ȕ@X0 5xÝ&Ztf7n-jh}QvO Í;h"|sՐᎶ㽂-_76;:xe50*P'0vhS#|v6"q*NV4:] 43L{*bZ[JCӯwpy9-`G:o\Fq%n i#lnIt;uDX+L0 m<y{5Ѻkh@r5 ztOroڙՃG*.E>OܱnHC{{jJ8"f[p$$@kA˞2|ڟeÕ՛a~j*_ jDSˑ2 ?Ac='\Zg07 H}eh; k÷Ƅ Y?YOJ[^'X^%rq e\F1.\ec…9/~h}QIضmㅶu+[붝w ;aVbo| Sj W5QƩ Qm92 3rt]:U4JӌH3RؘTjq)ӻp.و2Jlv fJOs~)#MPпD!(@h<#O"+른lآQ&\xMP\_AqT]I gᐝ: {~LtIter/.'V~ ְ)f:=qLۣxjվT˰O[vR5"Zk?t]M0Eي'ۙoҩÙbxE.],r N"N]liz3¼'kWӇ RN 4|`෯"H>N]]jL:uB Պ*:p"j|d$@ge:'yZ h|HLO庱n6%` $eBIϐῊ NZgJ|o!־QGOJΐܷbXV7O ~ੂ9ϟTg`PvT?{ROgTTn?_(xr?ѹoEWC؃|Qvk J4C '}R5)&00(Xt-AtY5:ykLcG:ePOL'rD:ಘLBHgebAA:D8О;r8DYܮ8+Sr:)Leb#ƸL=Q:}:,b/SaKcLt4R U;UC|5z5NE-2Q!1Yo\o($ hŲ<7v>$2f3%Z$bO*tl¤at['7H~J2yi+FP~e:e( u+ ȿ'e >aUT+  fo6YY& 3Arխ|#VBb!c)f?8ݜRus70kNKe\l&objlEZ?F&|甒ǡ_ 8WCXi<\'Reo]>TxyYɻZG!'e%uWq&I&ÆeFt?ayPeyi(,6^HǠ(KbCa ߜÖO  !jHJRwdoiP#^ϯX{]\{)\{?$X{Ko#T,2+SiO:m%BX宗y2qD Yp.Do\bp.v\X$7zoI<J]8ԧ&,Bj3 `bSbaXΧ-_?R.R5$(>s(*Llė_Z|֑> RfcWSbݪiS(9Fdu\ߠWlxD!fH^$L/>aWq2G/BB\FʵQe᭘l9߯KTylHwP ';q9(W^*O^/MSvA@DZAӥEls+Sy#xs?6|W*CK*6&FvF@LLh/bi#C#dB=lj:: m";1Y]+'ŕ[~TRVmQ7< ّR2ݶNq蒢 (]=T )*+7~1e` _j^Y" [h1|1AA`dÁ$lv?Hf&@cb[/ tc[T఩򤖉pu^W4 ctQJYOEjS \KA\~A~em%z=N%vTKnknʃKNONvfKI9ʸQH'  ܐg1,!vcUQ{٥(Cc[K[=v;\Jx3;2Iv]Z%>2)rgF|GK?Iq5y[Q~[ʬKΙY7Q4yVo,.sI,&D ɝC4v6 _Y )v.Yȥ;b@OLUHۑѬ 6,q.ٌw)`#!.cI.M܏Aњ8%Eb(pTa 7tUdGQ-ѻG*U6oED,QAoC(:T3Ӵqe8EuQ;5B|XY2иyW`+!0EJ>MQ}1aS̘F İAyi\p3FW RFT08˂Qҁ"1:pf>i*&ECQ'b$=0KdE Hw G:8UB]0d\(u#;3[!>b n"c_$@XF1-͸dcrNԄq]@c`DӅ`w|˓zBEXwd-&<_'/AH}G1*~ D%dƉh2xU=>pvQZ˗0yMa~U℈4G8_f!O P/H?2!'8;OHv0dM1+PJUY3mz#8)ϲ bV ǧ|rc#q>m`.0U#БΦZHck@iE&O6.#4ws>M"c Gji}o$N-9=a;+s5}lpX%̣2с)u0ꠁK y&RT *\$ (gaP43jĺF:{"T&iԇ 2` Ԍu~UD <d|Zu2&f^6Q>m.i47G0HQ{ G AVG:g3SGg>-.FPr› =;%si܍r@#.+c"6F 6fS|ZJ%-dŎ]Vh`~^.³83a Kqrۘ4=3q[Z,o]kfK]j'/6 r xA.mW?/Fq/KLPv)*l3EacdJeֱ +e 'ϒbq^Q¾ZMl>FQJvar ꊔ֗geQ!PhQyU}U{VO"ۺZGܖm~\'bg?0ӕ*(Z)ӯ=Gh:q[(Tb482ai_li '#pq:HN+:w)$w^.~.Й2]TH#ňD Dٜփ]|W`J]V4)Ruѡ9 hhh,uxxu")zDX+-Ԏ* “(zNU0kL=PKBj>7c@爄@Z5cPĵp<WL:*,ҷSphR !HK^^9^Y@Xon +fكOSWU `RI@6P*^Lz\-) BG0c;˪FM3>i|oX%é쏞[_ b >(Ocs{2)6Q2 ^}<$@nNvg/Xt*}4Nwtr7vbN8fNJ,!vn&CNt{:,BuO.mp҉X_`>/t$Da7xvt">'yͶ?Ջe5j;:i1 wt{Қހ[;͌鶥ۛg$U3IJL{?et)?Ҝ2nGEhGʽrRRq<*q8A8g'e1eR'4&'t"DYqv-%}q~ /^J35tNkYPNy[;%{SY6+^R_ww2:'#KhE2*=eh8=#扽K/f|;V2F 4?(U3P|L ]e\JBˠ%BCV h;klL;8lNЈ2{Όlmu> RB^ѝmBuo p6Wc1&ƍC(MY@ph'B5C4c(2%d]g.D%'p1_8x3 Ἐ|1N5{ur1=B7HIua}>9S/f9'sŘ0lDщ2C# ZG`x+9fZQ s6i%9") $&eq p%G@!mɺ)ȖeH綻>54<óyiOBm "ekFh F"鹱aIތOKŎD[դmUGn*gvZurD[cxRp)F*[9# pDձ͎Rc$Q\Gn?xJO $]V>d1:%4lUNFI VyQy +}z&D꫆]̙Ϝ)D|8qDL=^\6XuoTuF$8*5.QϕVf3`EUqaXQ3Ij)9UO8XG,*"<K `Z OEZ1Y(_KV&pheIKU.'#esH'˼%lDp0omw1\ݻU(!e#LʄY4Cz2*%biV iv[LګӜg:] {~gqngN>% } +Yovdl`xD<ͶAZ sM[T.Ehp^63XJa4a5J)) ֊yk6A9gڛ(Yڭ]̽̈́,RpH|T. "i.D92nϵ*ϊ~y+y9$ eƴ9(rh:2e{Oƥ->IK$/>mO=^3DFz+}.P.WtsUIZF^ϤR4>K?q&`Cg`; ۋ>_ abM3W42>ؘ+Qtd3zh]] .⌬@#602dm~LBg/$oWIL=ֺ4QD05څ(0Fy9)ʺWTxIILWʣk b> |D0˅ԗq<*u<|>*&Q7"z3.WI|AG^y<+rͲ1/Aϱ]ze+).:6,Fdb^1qd~dq\Sez#e)1 yV!cAe8OV*T7tOV OyS7|X.?xk~c2A录Q7uƇ\FR8_AOV=rW2'⡋;8]֧"HI7A8b /!4.+RhC[]51/; M;*bz`'m^?^je9"O|θCF!,e<^v]U?!қ +bIZL(>zkvaan_'M\zP 1 dedCQ$O5JylCO.^<6WXdǿ(.̨(K'Ai5$p֍֡KqJ*]X eDvHy˽V'T>87eHd&mþ!:xrn߷/}wʰtKj~#\aO/~K>W2{ 2/;<C*lPsWC |*;%Λ@!;%m~3ax/JsWȴ"% Jf&02i&員bnqE-# n})w*@,R4RP0oS4-#wH~/S /ɫf [-p=w Ġ3A|6WMQE_n77"XxWEX*@S_tK1pq/QRGόNؙvˊPB lEmUijjC'3]aY2OQW>(2yy Ң ָ(Cpjű\)fܦXI7S]Xch2 = I%=MH.òI{ Yj- k.2 a>EctR.Ms53f Іܺ ]:騛ƾi\*u;]dr>*Rqle@44/6co4T;/HWɝ2eǝU'(/Bg+}>N }3f=g}L_Yph&7(P&Ir4*')~[rc~v~P~X7ʣ"4x FA㲇q/RQB zJGglRD%LІm}7Zz*CZ,Sd (iФSXҎ2."Z6;'nɇ\ŴdvF.  HwWϣL;6.D 7ʣ"4FZYUxQ~T?}_vEoQpKgH lŧma# #12u4T]Zvɹ#wv,NYLvv~|^0B:S[|%n,0A~qPG@|B2uk?JDI#q +=B )gFJ Kށ t|/ζdQ:>1G")*$Fdo-EvC*XZvyܴܨ #4}8I2H"%'et!Z5Ǐkfʫ916RlCAiODrz@>RO_I~dY^6*+ẗ́U$H=w6>}[W̸ @X'dTPPP詛[F=:e]VMÒ=TAh=H $)xE位<\nK={j {"$y%j_%~H|Tipw(S|+5v]:"6`ٵsmקG 7~ 'ԂzȐm q\ze=KN .:iձ<,e.^ZBMB ^`0 w0DZX^ULy.jY<+ӃDZ>Q4$@X5Rt=pai 2 Bb吾X0Dbfkdv5Q+U7+6ZN7˧ B\Ȕ|!f$2\~Lhs)[3< OL *q8rs.{Lb52ej"Il2Yy[JxWcZ#FQF8@AcO@{)R'>ѮU.rHrջ$΢^( 9 Vz0aj3]l 7i4Gй-n[`Aԋ;#7'ea?J$z;nAoJ&Pi .K:ӗv"=wNk=ֶ{ap\(?Aq忊#{E^|6e>*ͳrb(2'gh!zvN9QR^4ؑDI!F#4 (ëTPt$e" :-S KL,:ގP\M:u.[_VDlxLEZ*jWt2UoWڲTR> dUB.쯾21@Vz!C,$| .opF._wϯ_ xˀ鴠tn'@mP5UFOSb%|~,7Q ONЄMث&*"~؇2' >˥Q$hXֵBz*]BK:W"] AKUu"*&` / rDbepj !JJcQjRC|x!TYJ̒ @<ՃYE]|ANOf1V~_(rwC 6 "r/tMIa+zm:"%?6" NQ YV MߋZo%uOCM+2x2󛢭eK}:qBʆXd_SɼN|@܍4*8hF!0dJ'f Ka8u<;} 'q'(nSÕJ=)MY4L NHiC…3ؔ'K^$8/Qb VpAy:ޑЋAsWߧ@aRƲe/OS+u9e\]n G+Q`ڹ<}yn.*8-9/LF9m^;UPވ ˹4q?c$kRc܌QDxfRI)q#,eW5/|[bj _6`;Xttm?h,ѫ %/Aipcљ^ U%zE+EPmRvK\eZD/*U"eSm*__/NYa\C ZT@|MV.Sƹx%.P(ΧgpɨYF͓7&>e (Ļ" L6QLVtkzRBk> ]h0J/.T9M(,keAL[x"l߉y)HZ WKOkW4l>Jue4ej$}]+#(JYI'Pw'<ƕ X$k$5gV4Rcً@qjBiX_Y;u ABIP~<*œW. nvGnu%:}xԒfQW.|]߯LQuN0-3FE;fưDr6{% `Oͷ9tNؽ$9ʂ:y4 fK؃7Sa\Fjը;hߙY 'gҹΞ0pא(fC8M`7'ŬAm~Ձo؃'օ(¼Q~hQ~TgT.8|/[I E rQn'o]J@6/)GNRI['NԉSH =jH8e#~b|hww#_ hX^H2I)kݳ ݘȖ jJ#4[WҾxzy+i&V[EOW.glA'nl )*n$BҬxqI)Z;0%F^ <,- /Kt b+  9}vWؔ(Z9A)#! 8eϲ yu ?8$&np &np^Tn)|b1;2npL3'cLQƢP 8 VjƎn0LSH9x3:ŭWvpllo0i+8B!cO0}5bnb+8L[RI#[Fp+8B}+8ϚTT]ue򴭠Q^ HcR6[TV/"eqޝ{˱O;%_Cԣ#ً!B5ؖV0inYn '%̈'|\W]oZI8(op4R\.$s:!B(>-M텈"Xbn_ /}!KewBpWѼ'Z:;)Ke.*tfͲ.2Jƌ(qd vhZ|p_oc"O_Gx +7=8ߢ9W| 3/R8:r˳IP>.tR mrʃY3#wBe6<,Zh] ICɮN~+l k;SR:w7?#N]Ѯ^W>sP!T*Q2ߕ[:P2v)WعK"]5PlVD#詭WF~@|+Ю#8(Q'3IfhKy3P`n9SrZ)X1%bv%i[Ap:$3N_)#w?y$.@N9nTQI!O]1ƃO NA" X}fECrTS<UQ-1 ٳ P3ׯWwvj'ŏz3ID+u3ϰ9ţ=agp+X}xX2a@^ -}g e'/"8UN8kGAIS7J`'GE'4NGM~-RPc%6ei;Ub粉:Gƣ~6w8]iv$d$ PyIp/lFikWrޖŠ@R$_/%,gp9/DQo^(ޠ*U{ (cS?)\à52_6P(QU87T3YH7t+7#Axe'l=||EhEs/AD5B{ t9Cͷ9j)Xsu9pܵ<Giz\.Xz !)R#> FtaՈb?)>;)?jn Іy@l<F}c_UvÁ"q<ّL_@q#𜅀nDNb 0b}z2֚ٓ]EIؾ|c'GJ)h^(Vq_) h"_P|zUOJɕ[U+na;kE0+kL{Xxz}`ş |*Aْ 8_0>/e3#TuWRƱ]Y _ÕH'7 lڕQZFiYi\Y 3=RCE0GS'jjuP\i+;0Z`L8Zh< P«b:\?)$M_=:0p%#Jށ W֭?i|UWRp[W0xgpiX&0k%Dj| Q}oFidPz7Wf iXPn1v&+mR#`s_&Aݏbb~ F14K/ 0Z'C ŻM$sIPS?zu*N1=Tv_`j*%5Pc/ʨ{~3 oxF3^GJ".<(nr ^t^@O GJPq(N*FD@eCJ0ˠ#)ٳ_Jq}V q.8"*f~y"U sNBJZU`%'`ʰirzVtLF342K1LFBF)a$ =*oʿU2˳-ХIDI%}ԱÙcP)/jREYAo#}Af3C:J$KvOWSSve"E/29QԐ/i5MާUs30Qnz p?y44DR! yE ݜc4mT3ⓣSY&p^Hduce)+[1&q/&{K,blT(g\ZY)֨Z> C1 a;z4COnC9DdUm?y"Q-*/AT%kȆ!Bκ]f<- \V+̟EP~%7ʣ"4x[LxpۛIDқM So"42p8&bǻJUm wO%$cKp]%o"6sM.ΧAD8){⫎*Gʤl5\7v+Wt<)q16"XwˁK>!4F]jS:NA aQGQzVO h?8y?$);gi vlʠgXvBŠr {{Tkಐ0y<)gڧՎT9*Z?W E'`IFnЁe >!G l|[=ޡ|XڕtN6?p'-QP5-DR=%RXH@tKjI1\y31(TC5| Ykt1F)Hdme8=VeCR[%{^o)v[{ߡRpM$$[ wCqLk":6~&l#No5۪dQg}BUEVʭ{~~̓r9d 'Q00*BFkۣ>+oOo$v)A|,BIFI_(M5h<7hIsW{62q]e fR>xcOŋAHC&NJ\wJAThrz\7:vhJoN&MG₀f#"]MU@ވ +8ymK4 \B A\]bhsJ+^0),[(GϊCO[JZ!_ `t/Tt@g>dV+|Hx ¢C浛^V\Tl[Oٱ:KlSVG|[鑢OPXҳpWe'"Cl>zB-,Gdgd+#^ANXʏS=RnGE@6ZTo*ʣ+j*q!/d?(+͇ ~ʊ~)kpFfr  P$;;۰y2!{bD=N3{8 {`7w{(T|4Zn lƶUt(WʊjŲs=By :6AJ%@[Z e8 ˴CC<4FfO09a? RPVY(Lu"K1_9ׄ"pz.H_5Zx(EAS ֡?1BODՙmzs[GB-^YÚe 3fL⦺ZK~E'wF!Bѥ,s ;l{OmlP ф"31oLv}Q޳}z yL.@1in䐪`zl+ޏƈbh23n=(P1ʝ{o0*e[vts4'`.wޞ0"'Ŭ \od9rgnI3 M~L&/.GXWNF-e678U+y?k$F7*1]`v4xD^>5K'* e#/BjDYgHsP~ &Oi z~=2O6pHjA G)(p~Z|`:JR֣2wZ1(I%BʊGAb @[p9*ΤI/dJEQQMG\԰ MW1z øx+$*ƬY, 3>eҹqeΒ+t!7wVx[T.:1JS0y]l.tEV+<8hƖ| 2̲S\g3q+׽F~p ϟp$_hrxd2aʟܤtRB/g1e~# x~+8/E\$lڕpmꕦ䶌~"FHA uxeSiG9;-טLSU @yvp"<7F✫eHq_U3FO.|C@.ݡ`G}O( ߕPZiBֆm|^z4xlpaĚKR{`V޻\;3b̈́^{qr:[ lrݥܛgyKA6lG@6ݘ#dW/,J+)(O6.#( 268DAשF`pO]ܔ5=ҁz?%r]νZAmSwP3d}ꍺEH~PJ(bHzE∫ʚO}'u!bt VE9 +edR+"1#0c*2PEwE>lIדXJ}.T5nHe"2}p}?:D }Aqh}X] ;;e꽢 xEFaAzI9=* 6S~*=~H Z{U-UߕS9g-,pR,pJ)3}G p)Ja筮X _!/Br, pA]m28Mq?S plSvRro_([qVl[oÄje.7u;]C浊K*4tJ[҇)XÁ`kUXٙM_(yVgye&,3a+PÌ.d͟ WSG'c&ܿ/]ћ1;d7E2s_xYHeO_[?A-R)30<#.,<Hp|H+T4ۀ.zg>CE+mRΙ@8hxr4}  Ϲr ~ɝ7%Q5m&- K2ch=PycgHʫl:@%9P$* v* na)mſ$'|Ppbf{1{EN)%'36 ؊"㔃X)%WFɈbk*7mZrW3~>Jf+4#%YIN4w4OfҌp)r7@(nz_)i&]qJ RLb)IBIFWT|ńYF1=(i{8Xsc)eY3w$Ryr~ va'` f'8c9jqJfqJLSN`G"OΪ"{RW{SqLd.N^z[iy)xޑ㔉8%)YK篔r뷟gOXwLJ%3~w³/ N{}M~toQUtFM/Q[ uÈPJSa,ȟV}Z$e"DWJY|[&#E~xRnF6_ɆdI&E +ȳX XәXQ~:bS@94Q2օnTД"ï]spR' fQF}9c`ktf6R2c'EYoCw-4&%q|1T`-mx*edSu^+΅[["?O:OwJx:ϲ ;YC(; 7NJ2NɇӷY%*k@1mImnSn +QlI6>Ή 48!|!7POHȞ{ /WSϼJ@D+kv8;ȞQ(l)hl9Tw b˭:> b1yAN$E;fWOq dY E1r6%r MPYwp X@,E i, vœ%Zj \[jPjr w3HtWmR%TʴE4BO pKd^M;ӳxUw!0f!edj"+Ył3^8=vOSyLAbKs)5HQCUޓg p7b#Q&TrPqqIr.:N|όG~. ҆6, S qj'-=TB"=7Vnj `#w$JqxP6#I껢h\Uxf<_=mHP e|! QK)DDm(`!FYSonF/(I-p7QDi_)B{:}4ZtGwH bEAiϔ:10[Q's[8u nbnUan67.W'nPdP->&,\`B͂~W,^bx',<$-R{/"LN5W0_9{M) k^rq$wF %Mw(Hr(jԅ@m%Gt.n^{^sS%j'@JU6f1,Qs\ ~FX⍭"ճZ.Nzy{S"2Q#e>jSћ4W4hԉF&XΡ '16Q{aX" 0?"R#Z=|6x/asr o6&.yl,}H`tqw m'"6 ;UN%d lg>i+?'*ϧa@hg{_磰&QCrT癐4vI';]>Kݣ?l`&C^FW?*ʰDʣjW7op(?N U|V.8G$CF H9\LH* T9"ۦmD\eܥ!HT`T{YLyb(<6 n`s~Y1/]̛56B|h} {[|jS\S<<[Zͣdr\z 27Rf Bra!b2Af%ҭa8詂ñ ,<d{ъY!aEs,DBhgR+#X1>Kq)+#<9ͫHLӨ)wMA%W-%1-Vq}/'y2n(f .Ƽqet4>iBvF\ &cf.6rT@]Ts ڴES4y2h`"EC+fM_B;5 㳥:H 0S<&*%mŘe,zS4BsHe\L=EaxݏiIG)y:bzrFNTEpQV^GcPj`CcSSQ1o.J2\))PG⨭oi xi =߾8%9{̀POO!)nΐM$NY; ҴQys4պv?8ZW)Ҏ?nK9,Wfu)=!tE$Cp\KypkhUq j`xZ+hmBbM.~8i6E_rBaҨ$Iy|ٻ0z%C9U6E(BSH*\2!:۸tߖj^UVUrgH̋ۇYqy`,:M!b./dBSܽfoyxEqoIE$% w9&;1&\ FM%yDO-&#0bPN足:um~Dpe"fTRq/V k\( ' _?Jʢ6(ܓHxIdpȺYG`ÜpsZ:瘘#kq{A^/DD 61) "R*p:PP2t9n#:'ϸ`(O5)p 6 !*ꠜ>;5ݵl%E;uN;O淪![%('8-Ҳ+ MAHٝ1x)r7 ap/lU0YbBqY`Jl|H)?Nm~,Tπ7<*j2xE}FC^_Nؕ~),?U>2RÁ/'4vns6(G$Ƿ7M.(\3.w23/n=nya;Σ" jCrzA~}s1oצz۬g}fpv$=}z΃W (R[z!k50XT>+-QlD`L@ B6A Zk1Nf3İG">d-q8EG]n،'BR49EQV9HV(wI\P#{k(?z|sJSgV;^yQt&#: }riNeɫMe' :Ai_ @а5P ,ynDMV'.f0k8NL?;Ifҋp#G!QP|D‰1I.d0ݠ T8]:M8iG'0!DGػ$W> Xv܉Ec m'u><(N諭>yU֭amw.}IؗAӏ;N¤'K!Ub}qt߳$K~p4ˠ¸io wbG۸ߌIDDl\'f6 N.TҡXJXM7#$BzOfإe--0I۬)zӨQ49ea'q)\UȲ'%WAk猦ڻsIҰ[ F(z1i}R'C ɝdMͫrcpλKA dF=U*ǯߪM\@ 7Y~~+7ʻ"pYwJvoWL{ńA6 =y#$[CM;w:k'% &͋Ѓ ktbU\FAM'ٺ$>  d2˧5Rv Ȍ<Xy’I(!-NrS9OeA\^I`j(op,x| vbwv~bǬ+F@?!+p̜pt?D#µŢt(7=2,|[Vu`b5]Q8I)"^k؝U{'<ԌkbR]W  =cQ֘-fZc;gZ#"kh#:ȯ"z,vIhbK ϑ-gWO[_9w{3.\:;dW1۵u ѣNGP 0mfi/s7V~ory3CuzTl+i682s^ a3۫7ۇ10LovXf'] s썿[iw轾 ͤWi=O0h:27Y3!q-9}p4^LP(LL2+?ti`DolVt/  p]s"-N8l,3ap/.@mxvjGNZbZu$s޻L#;i2{5Z޸+.Č;om[Z ؼ_mh} OD2R{۞gUo޿4WDwkmy|!;R$;8ZZ]Y=-I[2XVs=Uc{'klTM Gslind*…~ͩɋ|)ɌڡhQ˷41"B0y+lDCy\tY$tdBL!Uh޷g(֘l4)^d0ܹ'b)پ#\T.ׇfhSpQt5E2uc;>"zUp__kEyVN*MW'y.Iewp&q7 xRa3.}%˦jN ,LN9}SSsXJGKK<[{T1gY>K$Bچ fnkl"6ܕ'ŽKFeFRZqfl!9XdBB ,%9e/;M}{CA> AEo05ty To&F>tՎL Ymbr"-JRmm4E暪F,ioXUY.)נՍ{,Z}8[?dĦMWM>Awc\rI~٪pLM͌4(neʱ_9IMP_9U*"X`yP^gDޱ\\Kdжyy&AHIvϖ>Vk2i ;Wo󛠴{Ytr43hY(R{4R>g؎öc >kf SAWChp >iYS.MSzJY18mP41d3a:g^ WaZy|$1>}(1)Rb4&0ceeބMU6gƇtTb l]f4"laf%Q|Y6ͬfƇMa'V|I׮LFH쬞xj8pF吤dMU2e·i\OQk&]9nD">6^WUyW>O"Mi`HYm"?5C;,)Upw``*8W_3Nx ׌o\#ZUƚ9Ǹ1&ʸ 0mX+f8~SWt,: fO{prEb^4_IE!Ql*8[_4Ny|竆tL{s9*"UC jab4!J 0mX+j8Rs=EYÎG(x0)ƾ* >ZQK6-:/ C \<_#Fz cph`0H,60p< 6&VM[e]魜]$VX#hg.oЄ5}zݧWN31}%Y:3rFIAyUaoi񪼿*\#W[Q~e:eP  CK/$~*4<&h!MΡy;*Ζb4׵C ͳӳ>&2/3(Ox+K#4.mߪL>Qd{ŪN*Qc Hֽ'eȚ ,TRq=n0p{U+_M@ ^0R+IQ`@M=b*ySR@5V3"1f3G]DGҞ 6i1i*M  Ҁ6ղu>Soe6żdfѽNEg҉OQE*"d RH~ ֡BU1 xA{ۥљ|]G{jpr+yMW) <$N Nw'w5XwcfN„xq=yKyp Mw/%(UCZtVxP0 qL7BfyHx?FlxݍMH٪3*RBX}OҫUQ|J%NsSI7q*Ɍb&@$qvj5.(z4@j|4bUA<ҹ),C; B*%m3qc4M&l´alBd~cyL /Xp6@Rl&R=JI Ċ }&ռNn;̝mgUSqhێB+%:>(>Z5 'B%؏旱 ҙ?\IF#ƳUs=pckwtwҠ(7eٷM$ 4*mf8 >Q q )l/Th[&Q8\;ȣ7: XLQ}'K2[= ޕo P8]ӷEE;nj^=gX E- TQJ\4Y8Lf ڋYZ>"#UԽ%4 bSXa*W(ӆwTQc ) #, )C/ Pވc]8p6ϧtwTs r9+4 y7(7FsD)7aS k 6LAJO2x*U}7OOXUeUOāî3+;MW0.)Q6y$'*0s˃M|&Dhр^7fA+!b R`&B-)lK,+NXHT]oC)aM{og_^+襤sAXj>MU('Qb wlDOʰ`>;u_k$`r[zq˹`wt;!)|WDz`pOd:bw&-9 2AǡL:#BJA#ŁF|2 c@kwEC{HbF~ҏhNrVJAy VnI! FE>52*;ॎ@8K4E/0"rmW ln6v*>6J;ʽq`!.z] g9yE hFK)W_Ǎ.)W-3 e@aV4x*Y'&5ƘQQ^& ab&e39Xz BŇ+feM8ѹĵdúz2I Y&R;K &>I|R~pSCw+= Dz'FJY^}Q(')4eh|r8Oǃ$*dԂbFvx[RVTz `4Kxf^N)8NTFQ\ݛԭM#`ጟpr^Bz/;PmRF#8#eINvZ;>hx~kU}T⓯`t_CI˥_ヶ,B2^K˲H tYֱgu/e98=1s-]gI#m]~Q%g٨}_aPs WXaʽWyp"Ɛ_07Qő*ť(a σW;^VJé^PgU$|7 1QQGK&ydgka.Ԋ88 WJuiʥK(?r8?(B~ʻh\^W6c*Ȕۏg*!DCtɒT>:2:n;DP&Kq:ęV:QvZD!C`6HvZZskAlmAc6^nR°-H΂Ud^ պB0(=Aٜ~29%U_?{SZRӝ *x{2WKQ~dʫ"m?C Nǫ򼪾*C'`Q~_%=*V^g1~W*i>kl$D?Hݑl>V&w55&5+};"Icv $YHrm0b |$9YgL@@R1.$I)F̆4U-QԂ3-xə8si|Hf it;塞iqb7J,~'C7U{:,R\i9(z޻F%>|(M;tegZ J+,SGqV 0Y<Isϸ8Ҽ<.(fxyUoJluRE0񠧔c Ցs;_1~bH,l_|!y9Y/^E^ڌE>'(*/kU^2׎3VoMWZvD@ʚoaUaNf*\]g;%XEG1c_勼)~tݓ&!D`WŌ)[WMTU>t*PLD_)m#̥=XIy9s.8KZ%V˳#X4+2Rݗ F2E1)*s:/|u^F,>̿!,4s1dr;=YHw%ې>* $*;JP ]#-u Y|1cHC39Tr%9RYꬬ|Ol.J;Q&O=GBÌ@?8 *Khף^4Oٙ,4_V(O]ŝOů/W k^x_L88W%'.c'C!玓[!x 5BODٛs\[و 4[M.uX#fϲ#zB޹Y3z%1c_Ѱ;yD))7R a<2 e3$d]kXL\ T!w .B8#;~QKb⯋.AmBY 5&+!A{O0W5 EoJ8ڸU<8TK߉aK8ٱ5? 9&hfB=,9 bMѵL3FQ^DД1!(e-NTGQƆe Fhf7Rg2Qt۩:hi~jJgxFL~8ފ:Ppxg*FcvSdlC x괥e?-tΕ︩厭P̷Nc+eֳ%y~pkZRv:v(sjlz)cntnJO;7@VHX0V(]O+b?s\L址ɡW 8![9\]0h!vڹt@P !n>O!U/*_zB(-,i[4Y);3&R#dR6s;1<y1LuKfnjYҪ5yzb` .VpqĬ27–ц:Qx 'Q8pmaV V2zi"k^@o}BQ*ҹFrwKT;5ׁ/=5Ji۵H=f_5iE ܡqr˄F'$*M j4q绂$ e:z#q..U:,$*eb0E9V8Rrt.

    +kiSoIR]']9{rc7] *3|]0KWoV-fD_cAe./qVJb^eM7V:&Ldҧǜ aʋ41I^0I+3^+4-"":{" ?9\q:(OF0|#N hu}rnҞL4R_B=`>B}q@[Ht*nk4uOZ(cwLɩ W5 "ϱ<#.z@hW/X1/p *}h~5^_H={4c0B@g̣bܽe=nfVEǝo$y%4{r>އSb5Ďg]k7]K8ǃYߔԋ)-M>a^ m:)ڑqҭBٔTە il2gDf$frp(ifb袬mW7! R_oʽն[kw1o忊г"i/>}4e*S]9}ZR Mu$4-u`2y^9Q_K ` s~Td#.}..DJ#$s߂A@B\8/?GjfuZcOs0yNpTg7Dg,XKL,[R&1lPI]Y u@#2|1N%^Th2TRf6T-tJȹ(L][%%ȫ*%¸B۸ 4S_),MTV^,w7Α%4ҴC FU _Z _yRS _1(ӫ ʋR^f+BxA*3~uG)Ev!^Q,YխQ[28^QkkQj U$D_99'F5'$WzZ=3ͮƢsH{^Y\:ԐW^#_ZijS^+{)j_D%, L;Jf;-h X(oRBnIxBm痡q+Jt:C'>_9qe)=%M̱v3X  ^[9r(ϊr`F"kg3%}T^M;r\ K&ar90jqXV%t}|%"Ik\ΙBdY I+(2&I|JK˩bP|6@B}+Ɨm MT͇K&lUd]Ҏ,m[iߊyoU|mCɍl=q܂RTUДFlN|psFC_]X[]V)Wn|zEǧ HF178_l{ ٘xxV* Djw7[bRדt SAV !FHbl >χ$gs32Z }Y54lh)1ˁ@ATʣ> ~Af$`S*2dE xVzU^U $6v=]?SLQ3;{4oBۣZa'ap ηds`o2pid7Wli3`{#C=WZ*r<}Fr/؞o-elӻ(R3ͻ!/drS/ 3DkvW1!6[,%Nr:nmX,{29F<,xO>x` gR2fOgYLu= Ia b2+-mϲysg&>AU3; o{g{oyy `<77(_^a0t{ Ɗ$Z-Kt7ؗGtI &Fgt W;ؗ`f'ߞwWPR=*3-V`7 zf_t?R`pK` ]VF`o# B ʝ' S/L]{i]DѧSpL4t1٥}.~9 ^o!;oJZ&+I=-E]iy5vKaHW1P?^Z̘ˆQW?SwJI {qd 3T+9lcsm٫[lRƾj#/ļ~ZԒDx$[xg FUŸi2z*:Ƚ xH2B'*.e\*9o&Х(:ve *yrEὤM'gE)K͹K\(SMN\挓ug?u/8 SVb46SOW`m JI>PJxmRG}y,c\lݣ: difiwޔXFg-Q\,c> QuA~ƻ<>u"^R.[I@*Nc`s{DNGWÇߛ7& `u dB@s9@{:{O(k2<[8E|릣`ܱpÅRkM%yJWh~L KIW4xQ޿ &t0nDY Q|V. .p9|ML#@[NR4N)He.L{⯭FۉLŭP缙%;-9ەOz_(a\NY/e'շ4=o5(Sasdك8]24Wdu}i;.y_1%]ѢXD˪4%DDI=:஑<[;g;ZlٿAƯ$]0,Wkr\ܺ$WJK רҞ)I)V !|UJ N0(.M.k& y g<(S_)%*ڙ܇=Ja^ő,e >,{E=jP_ nB[EeV,¼PeAލ+J>_*")Qb_)=*iR<{Tu;\𭤼;湡6 Y\Nb<bcFBO>~$)L&Z~$)gMt''G8^5faP+o8lz^xFFIU`/UH`Q~_E"]ޠQs+%ƾGF10(0 LKBE Rws$Va*ܑ-+/v} ĭE}tP,Cxxb*aBɞ={%aRwoݖuR?;Wl) t@ Qt3@pfɛdہrVFK_vXcV'!7TĆxeI7J,trHTG$_Px;rTZ}K^J$A`P{j%r@u4$#{K*f g&OF gi0B= ljac'сWOI;F}m=:5!*{@ޣfѣKy7F֋`l0;@4.(HċXTz!XB)[x a0_kM sxU\|2 ;<8^e,Npbq [g0nYrbq|`BC 9 6[%WP_(X>uF@N!ո*m@Uq&'I|E_ƄWrOy*]T13B*#r:9Jj'D_| Q*9uYPpX)D RA4UWIYծlgՙgZR'~1LV|p;PkH#fbi-Ġ0xThZ'wqT)> @?i_P|zUoJɕHʼn1[v\v~Zc%]Uk]c/]e~ UnvgAB:jE l(~Z-U`j)vq;| :&{z^ Lj]._qZq4+L 7% :iW+I$ bv\-K% Oc$9V?_Y7/\7bQ?7JI췤M>vfoX~R$n`ѬN'pZ-+ Պ7-fV{ =–Ogۧ39r<>1Use4,K{s&ݿLRgK Kom,j[,芹Ⱥ5>ƍ-w.hT5)G'>kwQ|apampmXՏJ7>_*H7r}%InA9{cn ~% D]vf;-`$ዖ23y#D|sP`$HyP [g B' ;6!>q V;TCP]{υLXՅeANX^ vx>9r*!.hv<(ap{v$JFJU"%=L/U (/B:IQ+KqS,I&,k饀IH Q%(l~4AG}ÝZI??žMR^3,,ee;kM6+Ӽ1fx(ߢIb&Z CX7ʭzF<4 0zaŦ>%%caIb7GGSXR6$cyMwh<8Z࿊Q螕UdQn៵(? U|VW1pNf_)~ (z /B_fN!mdFq :e^ۦ[bR6\+BNSbΧRN!R|_n椞>mT滼+^RzYlB@ҥHR`RHrQ Nk[!V^fY2+Ӡ9cyMP_A mUISo*|y|*_ӥwXOź T::ۯNY$eGW0Ө)6CJ{PeLhFB.\ǫl&țrfݎ|b{|t԰gTjGЁ[t zuYsٴdv*pjq`n`$Qܾ㥑u7yw8 J'JKB(q>$E\ڳ&)% VQ<&Zg>W<シy!Vq(\-bG8"D\g?"*RR} nIkՄ#[VRU (HH QRsDȺ}qL".NNs=DYome0՗0yn5(b {TKثlZkz I{ė {q.& &VA#czc\92;fDZN#ROA@x/A.M8 Fzދ{FxV޿󭣨#_!k|).%)* '|Y_ VU D"A[{C̕2nU򭱠[||evʖdmOI 9EfjΟPېUuy~QFP"+q!eI<+ĎRl5fRpV~p F\.pRpde >P qF iV&4cXd6\Beq lAS0,տ zt ݲ](v[ l`*ҷي}Ϟ#]Ckނµ@ڃÖEؾoY&NƄS*-N-`z4}x}UV]q+(M$m"K5-V4\~Xx-иу:51"hoѥ*}~Wo- ALx*V/m a@/ P. ٟ|9(LtY<},VxT5֧"bpKISRj:PRҰh;$\7d/f%]|f:UcVgx%͐ GPh! (-dؾR|Tf_LuK1/tKGn#e s#Ҋ3fLdUaBawd v2 KGH](cGseX:Fɓ>' AKGY3"i#: ض6 fr*NE%Z6kLJ}X`|N(wMnJ2b8SE:Ŵ7%9j0LEބ΅%(DN%[(ʛpU4Yw0j P)дBq_*cH/eʊSzTc _y) l+[3M!.A'c.8 kL/JYbƂ bUv;%n_wJ},D+.pw׷⛅ۗGY~vD6D{b Jee9j* puɟm>Q.%iO!z&IE?^_TlXn?$ʒκ"$(cf;Hq;Y}K”/u{ϰ.e;M̘ܦvr*N&%ɒv3!L-Z3۲QDEt;a>fWR{.Ŵ`&KJLt3}Lo_Ŵj}Boe]ElUӕ}ݻҶwAGsL79iHd53#b ntYL˶:,$k! Y`{{Do:j S@@А0S@S{¾8JB wݎ۔xC q%:nPdV1#K}(jxM&""h<ڲ}ҪF}F倠kX}l}BV"E8Q ^[YTde( g sI .KkB)3L-~;ւ ..D,,(hT--9H&"dLrrnw˅I-m S1<:8ĽЁ2P5fo+ڧN{ʈ/>"_#bg2`7 UTeActR<(`iH:%E>.k,ʝ$A N)c(i9rB- SԃL/B',HَtlP8e>ZuF&S{D[WLI1 ky8{ʣ4v#MvI4[ jo4ȕQy'2Ĵs*SN3B wf;i߽UZL[y$.)um jw@J)bJygύ{ HrF@hzFNrT p]?KhMeRpv>3񸾢lB$x 2E#)guyGOtR9tUDQ=2Iٲau]Ȏl5Fb06lI1.iGq3S5/IGւ"[%y2>MΨa 枏dB-oGbM_REsDB#zMܝk W2NqAe僒Dw DTɷ$eѯGN)?w%.DaQѯb}nBp8j1k gdU6e*nI"+OF1vm|Qe?*9$| Jk sRc?'lW/`ԉN؜sBJ꫒zo7s=X~-~_@yLCPDW̶ArpVp8KESq~$,P݃fĺ^9}K%)L:wAKkM\o\Ғ7)a6)+BQބ<+>l;! 5J| UAE=S uU>Da+SbK 7|dTiƺ,vĩ)W x#N'kZ 0&JQ=+Itlꦗ8EДSr3ACu¥!!h9Cx 4N`ArQxL=%jH v&/QGȗ*EEO:Q0F*7"܅$_EM;?o}NbTJДgdzY޿J=oua7EǢ^-F@Le^ޙ] XAe5ڛ-HUpgP[T7P )@<PJ?LWFBavtv:.E]Xtwqz(2*S<W8G֮8c'WJ `˥dxV{`@ V;P,b5VUT`*%f9h0v[d9>uQմ!Usb\Ȏkg/D9 U[ (XDc,Ok<)pc+j*-\1bj*^dwaHdC>L1-;q<Nn;9=+C&4nwJf .ErKˑG"I(gޚaG8>#aF2$ݥ2Kl%k%NI9RL7d{PO1z8 +2ȳpQIǧ&tESf4{*RceK^^Uo7)s,|]Mݬ{YW` TO97J!IH!o1\'8kHjMq= A6`C(ˎ}l粼0 zX$Ym^KGedƏ$3]4_26=:M~$46fd2S.h{TG.I\=7м?nWP^j&t emRJSUmifJ\ l̂1rYP䭸@||"bv {'̈́nfD\ŗw}LSMxLەc]!< wHKgM 4e ,rQ A퀎Q8 x(s)ݱ}p.^GZHeT.'sOS0{Y"vrTiz(q%_?+rZԮ?^iD'ՈEW"1+;DQWv >F)L.;ßo=3+{*@վz,zdtk4hAǏf\wVYWr'݈|*4Hg{B|"ލWH#GP=©T5/gT>oDDriޛu/2Y ii{3ٴ)"S r CfTfqx G>`-)hIV6RM"(Vv2W cfP)^U\B wbAnN,@lL!*%5|mHO~`v$AJ0b˄\sC+Gc[=8{'w2ܓT5?N05w3F"3h))?L){-'*DK$+DQ$/u ׂah6 x։z A\qr6E/.mkH'brC9r-yv)oV vg-UYi-Y;b[% ZdžfY4k{R9䡞R:&?߾Xګj] E/h@IҤq,:'au~7'b)TE#'"Lt}TV_G0~G{EDqXEa݁wɔ[2?S/}(_q/Umˆ/iE{?mrC婗_ohjm_1 3_']z5nFӓŐCcjPL7XYq08Fk|W>N2 ( 5E٘gÛ#Bb+̾.bOUt5K,a(Z!;οCoLzjgձn;U-ʱ}oAkx*2U*S>?/!.'K|ۧ"z2 OXto'i<!6`ʗ8+QqF]h.ÉZaJHF^r,*>[]xnzϦN&M fX HvmW1e\ak ^խ3_ b3q%uL『\qZ 0hO 31 cK@T;SM+o6\mQG?`_1s{nKES*oWwaAoC ;vAi9r*;YUv~߰T >e} 3.*_bqsaJqfEOpK(k0l/dS裂 ۧTߕf=8 k<bNYp8s GFĺa^tY8*Zs*5m7bt=S5O ӯ> v;qcW*|c7x79V='oxn |:t5ݜ>' kt rXEKlg./Fx^H80\.w:t7җ liھ/$9\OG?Z##$k_:']z5nFӓ?1֍P&|0=8ص9ie%HtUtkk?eVwٵg>$$xEsm݋J;3)"[^WA]\ᑫ&Kx- r J8纸3f>Rs>9]2a\\m.]ʬ5.ǍنP=~QEqqdmv9V.'?n#8*sOMWj|7mYQdKX4v.1krcʞ: r@L ƧC^bֺ|ҩH3dmlRf s%p8i~w Ba5 % cE١MͯC/L>۝[Zg#׬CLi 펹-r}î)n~F6ԖI_f'QlN̳1Dl4~`^u2ŝOɈ(C_,A6vsMfޕT-n8AN9Wb%tR 4IiKaRtdPzCrb}@W^`m2 G3ǻ 2S]O[OpK%zp,0<ܬ_F̢Y {Z~W@ZFЏe4DܚkHsڊ3nK#_)I}Ѯ4 Ɂť lo 91uG/ .*jVybxxGy8_;ᝏ6Na>WxKĕ?)H ^(ÎS Eu=wU[(E` ,"0. gM=N-Ω]mDtTҔ!et /-g'[g͔"v]lh: sˎ"^+e 2C29$:U |2.fONT|tV"7Oe+\a*Z&~ҟl™nRG(NVbxfXl~.o FeO-_H 嬟{J1V~=%Y] &oA(0itWUa=0߁YOG34M&h)tÈ \Jwo%p. 7&? y8xuz0 Kx4_ei~y } F+jp-㭫Őd<Wb;,,b[ -7?WIlA &&̅ RCMؑ.cH]v:V d2A= w\ԕݏ2J1T~)UG\ 9's9.%kq̾siJR"z7Gbڦ9"0҃Zh#64Nϓx/CɕSR2!X$C<=JA‰Ŭ>)vqINo~;P˩3!To[oOxXbn 8w ?{~sy'8jYhh㘹#O;UG xw.Fޠ P!3Y}L畭7QGUF˥.JH?y-ӭw+&9q/tcmxr{7m'.}k~JӤTzbs}UBe`N!lfuި%a皑5+6dk~kfm'9<^fQf's q1*Kf,ӒN1|hdq,k=KZtw 'W̡T/%z2yikoch c;, M0g^1BNܷYjY߉ ?hvǻwB.= ' ܒ.U%0RX=^\D9}ۏU۪ulM; KZ>5o^R_>xk>cyKe3C-mF \U gFUW3C9CRS劭Ǻmq}@r-=T]Aut}X:})r0M<ѥirT4c%ɯ6JGؚiaךCJzfX3:rʹyKk͛o ٚk$=gN5>`>P˚i, J޵W|#*#xr<Xe0nzn"O|9`ƃAG,>r{i|c^[/sm'cdO-'⒯O<X/]6ٮ&&AP_˗vǻwN}Nl0uzW]`fkWƹ 7 '*ͺAkMi.L󉹦jE?B!gtï~"xI0G#ffL*p9'gS/cA;>͔t*skN Uje2Ss>LMT136o/=z$a84K.Nк|0U0 ;%X|t6z&(waqʛTL @ _S5r)~M$>i&N|~L\NTE'aLq]f?@ e. _gl'b QߴwL|s V#+?fUPM{Tj$%:G(먿m0h끡-G Ⱦ4Gk(IPYMx'V\<~bp 3/V'8_t#9_c&ދ+p:=jswuyr#bb8L9hddWuKWn*h|ódޖm}AIM$ ]2]8Vky7QԖ[@Dv0,"xR0O:Xv;qn_uiknX@h%5!>Pb\P`'|0N Dy [`}(TVٺm> ,@-S6Q\ٗ)${<0 f|4ߍ]ObXͤ Uu3Jd 7 鿈!,4`o8xJJh}[jRbZVie!͵ˆISaE-LmJL"N쇿fa[1ǙR8tvH1$;Xt8~5eiU'kpʙ XO94{T"C]v*išT48lJQRQkrHa H)5}˹ƴi)>4\} :giQU9ffɥ}.M~ȘAd+}X&oN gS y"عбĞ> % A +k0kFTJP}&lc}~18'9d #0nNB"N"3UJHZȮJnNևPc7ex>( zivbM -rBިuC2-ozŰ]lӨP.cќGN^2(Q 6X64!9/n~d2k +ݞ'"`9GCƑ}ENlдa~Dd˝c*K51Zф2$e(~ XK1(;a#̖gt^ ]d҄8Ò4#qm)( " XjPLdgÓ7;cĽ4,Knc%Wy8 u ⹴[bNFGP:'4҄Dv :{(S@+`/9MVmcy"P=w٬'=os}x5ՂXהagg %<4d9X8pvvi¹đ&p:=TЈ\%1{ȷcN;p}C E DH_H+?vo@iunھN9Ff ZDawyWWa[$/[hwFwnr2΄s$1V5qh^:rM%pGr^s_wF [m`MQ?1!?rLY032`^x=8Zv+I`Vv sxɥ < nVDyc_n@tݣWxچZj1q^ Z.<_C9s(>ZS672idI꯸nȆjSvX%lAiA&&{l{̒^1-V[З]l@T++8]ZpNx'ƫ$™DqguW.{al#ۊu; X<40Yk#A6/bd_- <2>[ؙfVFd;< m{9?017)kA&?>񲰠o Ŋ:ȍ9f,3|" 3Yb^ޤy!4 =0&݂#x0K{g;.Y`-=͇`m<{+`.zB;1\;p<8/Bpx&'1t b߶F-FDgsaJf vrc޽2+[&$;q%k 0휎M66 cJG۞zm0YvU6e`/vlq^ᨫ-qpn MwwQ.ɂcIkC3 E.EHv^dג X{ˮւ .=|RVK&$n.2߯Y4I\t)ٗFoҐV;g9'J̀!P7ˈKAIr.fkj3* "~i0|YΩ =19s8PlOtV 4cH .( t5 Q^p)CY׫%CȠ`MME%;)_=Rl$j`Ie]12nv9K'g|ȑv8%Qai!؂NZ`X|\ls6z+{˛rZ lk k5RbIraQ) K`ιk >L/k.k`)L@fgeX|),K c #7WdaOLr@-| ̟&ww(}x[P}bl)t .q6Cڞ)Y90ôC3G??ymarނOLh5QO=-{qxG8 SMnt=8 Xz"#*o(%Dd@0y J+EP $*ES`i DG̪&ì caQDg*1e(aOJ WŗHjb359i:RϢ }jqDkemUʖ}87^X]J͆rf_0tab s/6qGjr=X$}'Vąo*"&[_Lû拷kobߢo/s~,!w?CS惍lM+:+18nctKpɪl%u{=SY7, AE0t&ƈN+b'mmE<f(Ǜ3=KP6>T+g4FbQ(~#8aTE}MeBv `K,͂8CG<*x%BͨEjG 71 z'*4\ 6uDxÕk&3pK1*X\au uiBe/ή9#sk$3V&'9L/2$z v_Y;QƢgصiĐrU]{\W KR|bJp1ǹ)œ}kY XT`rȮf W5;UEuT ࠹XM %]=8ubܽ-hr?d+{J-Ǵ:[IrPgv=v&e hT uQ5KM ٠ԅS'~(ƈfO!ڎ3k Yn!`|K'v<6Ɣ)_}QE:QJ,,j^"s=|5SU1~,j<*ד֓\Vͯ8$FF#6qHj|j!Zb*nRq*oO.LG$z=y3u;Tn ֎#RJ\OܐbNy 0riH 0\MBAY3)S< ZaYs}artdjCXFrD'jLـg_&- "ڃʄuK IJ2*XU"rf >lMBP.Vڔo>1:b4 Z1KS5>>*{6t??1 UC81ҿ ]C$0f>Fw˝"0/ <;s)#RHGZWE=+/8jq(*+;EūN͵)Nl `z~'*t,;);Bsp@7 {зnAOX6 LIޥ ] _{o/\}8+8| !bs"0U~tH_Hb@j9D V{0-+̄uK %6 c%2Hc~7P`R[GwZa(ФsnQBSe{S ȁ)-4íZH0S MrS E >0[ 2㩄./M ?^bj(yjh}cjSQۗo5-Kn1Tt͡ ti!]I ̬BlZ(Bq-Ē%կ6g|I Ր[z'KliTC&}0C M]c`SiZțor*L68z(!K?QehQK6-*qH gJpv:{$zbzX6; ʲ.d|nd:bعe)4d9ƐDpyʢ09 fo MNKw8My v8q?Q(w`5bs]atspa+ޙF1aU\Ԛ}/ҠʈbkE[U*zG -T ! a4m9dhCKa2'{s]^g0 +{i/5R"h띘C+PatB˥L&!)ʕ;X]Ӯ$9SŻe9T/j¡+tIfK200Ki-<=Gy/38/}<*?H|Lďsgߎ{/fKTa&4Hk7@k:pc^3*P<"pk^b'*wUf9J1@;> DdlbNVOl杷( Щ=G4M֧7,S_`u&ꊈjҊ2|#n|Pۊ Z,%$ȑ(1-&2D%Kff=XT%K0C3b%Z@wG0ReY&ATiax-] W~-RÞp[T; 9%*)k * cX(_Y(qY@ uX\UiMrez1=2s{=B/#&وr93sЇEu/iwjf\͆H4:c++J6UIU]E HV𣖥K|O\ Z G([k'ERRѤZBʦ_g21U9e<LQXfV Y2 5bc/TlKGeN[BSVTKFyoFi|ݕ68[LZ69 v\.e_ʅw̾|J{dhdT^Q._E1KtigoK כ]JX|xFKA䐇23\:Lj*}V8(^9HLNaK#oo."`Dփ?lF%gθ 0nS*Μk˟,0/JPDZiE#^z:.݀A']/=UbBފJo*ˢ?Rp Ìzd"vL0b ̰L_%_5dW=Ba-UԔ|'*SKVc?t[D aB$2XGծԼe7 v,Lu0Um΍ne mנ}_߿| >meVWTjH1I7>udY4 ]#fXYRe vså븊͏ݓJS1b:U,Zxr3;b(#83$;5n"DՌZLrN#R,|aC%Or8eˣ@Ρjq-7&6'7W $~L4~~zWm 8O9ka[pN_lW JY |)SɑExeNi<ٙt-xC( Yȳ*ʶ\Y 6E=vNCJl2~yNsz*A"8¢5>X`Xy\r5؛FVRȣ{)4Ѯ.,(+xӉ1q;ŋB)ΙF]zI*Ê֨Oi l^UFSUog!rD7IܽgWZY+r`ٗWyug6.[5Uk4)BmTέ7Rx zz 'XOL|=zc>P4+<e9Złz~㡯Gj\ޔ1i;9"vrljo1U?NgXrO4/yuD}UA+M b3Jy<@2T9>UaJ_e22y.Qx&S>UpJdu/Eq)ȻVB<׋ː*)ΞN7V\F V]z={s7gbs--(0p/N.W,\+VW#Yw)޼ |XߍU]"sŞtpY-i$ʼk9(Za#XzYtuE|'"?MD\GߴgO k{mFCP(+?+#ay☶Ċwo<';^-!vՓ&?تn'5jw ke8oT5ĥ-ϭgZ ~YX~FqzF9[)J(N+^d ?wqN~&2Fi$`>EF'yfTÒLbpWbb68B\8s #qi!, ԇ7 b4EwdCѽ4FPά-t x qT0gf<|_@doTG>V@㊘Q鑵eVg I˺ YjqD~J(a@WGPCW82+h?dWctB݊K8,6$N!~s07j=' !oKYUғ/vf<;/T$!"\W"%Tn<4XdK9t[G 5!m"nxqG5y C!!^/4Ag#>:Eat 卫\uW+iE0B>a[uDZfB%)pgLn1'ļW Aki҅aսVGC豥>"5]w6SSN<~PԜ}f:2i- 68܆;kYg9cWb^TP#baN/9w(#<:WF}(\[h5x晿sz^ ըʨ,]unyEX _iZN6TryUqQmsUx âzQIc$tV)J2FǺUL_@(=*ոv:/cc9-̲XꖱM!V8mjKyRcŕW7u·uĹNwg+UچORn-M3'_0?@,`TD0+V-so*nN UN1.,,"8d;Ԇޥ<'(U1AjP>oPF6񝈘A显|[Fώȅn| ?a ]󡷙-Vث_D`̚j fu&H$g(:_?&bµ"蝗'ie%+8_^{~^/\%_N;we |m`^ύ/ٺEV'=iWraw|\DKc}a\_[ي5O!\7)ݼ" O9XF{Mrn(ۧW,!k_ g~"8Oy$ON"/X`b6TΗ МZ& i.5Ĺ^ >#j|(@ww_?$._ [@i$ZGtk*B\T\?Fحu9@8s.t"OM.ood_1 ȫÊm`RA{a444C]1Hoю&_ԬnP<Ś}mn}7BE{n6Z`_,׷Ҳ}XZ*-{ξ޼Ҿ{DI]_ :?6J7U#.RƝlƣO^QW0K1դeT3DJ[|"ލ(ow׫8.'X \sDO'2T ol>OVlS (F35a+2M !T9Co}:ZQMj6$(lڿXӐ bԗ)樄9U4Us%po9z>s7`F~޹`goEqa J\BX:T[\Icxwae ?Ai \W84qf+ J|a#r:Ϩ͹@0grT=fa9M2MЋ,z("X+ I5"5C!kX1qM="Z` G4`sEW\).Nʯ+ yt \!:C눝o%ãb2.õUQ;)y8䶬wŮ*aD׳ΡDT08ye*&ڍ>r!?Nt>DN77HQ$ˎ 0F 6 Yl94]ȷIdlLi/FW6 L7w+|-{Xt7˫ҋx!_>@zrK+\vňU}[%%u f}g@~"KOL"f|"8ArSJ`@g9_)wN-3tWVFSQF쓧_^!}xac2`15OOM`!S{ 9: ww| mP-ڿ8Q_ٿq6aNCsMZoaơ{wǟ$>nC7}sq'帝vQXd$P|)-v)c3!9.cWCn$Uҫ^,l{ھܻM)XN!WF]k `ևi>K-(w6ԌL]2q7g(FL(ZvJ*"cJCi O%u%|{LzKhIԒR.क47_H8q o=Ʈ"ee5[L凵'o.>ѮF-.UY Ӓ-P!K]߹^E#ݛ6p,/myUWy .ih ;/\;RQySފڻO^:a6܃˃iM\LzkH WY5?f˛eV2f@R]ʀb'rPUSE|!۲Pnm|8~.\V^9 { Jyنƶb|qaʍ]BlNtי;%}[K]!q3C0Qz>ULdfsS5B6Ql{"Z,! &Z06k7{ȕkIh13{]U4/e][9Z2,]Ti΅;$% lrX=\\o;8Õ4C1R"@Ԍ>\whWnex4K;X H^S` jI{ĵ[Jp<=*AnmS DZ%&YbؗA XJF K me? wK;;~)k++,yWGQ~ Yd7[yE%Wq UYErjpE YYb@OrѽQ<ڻj7B|R?mnw^ K>vȍԵ=OE8C$X<@M1v'$ב@eZ}5YC7l?&*MjdNnk|f,:؃I*$!h{V+Y "Z\&zy<3&mj7Ɉʌr%^B!"Ħ`B@>I> a[qZWֿt}:NeRngT F,SO )Jq`R7B7ACfca&SYg x/Rr7ڛ?<1|L ZW*v0|80 XQિݭP*g{}ȡ{#fe"3Ö&;ܪ*gp3:≮Y$e,׆k|n%ȑ(傐~dkC?hP,^&/XxzcBG/Ta_kϵH ĨwhM`Q@AN6n=^'!(?8J^.{}eP6\ըbG琭CP\#Ǚ'rtqn]0q<pAC*8iHx(d5'ة5]&ӯe:/|`N~CGhthh&(<9g)$L̷I$(w*I@*"5`9Rf d qB|ޥ Q#=}l¨+4 y _eXZ5}/^%2u'Ҏ}WZ̦ᘶ)O*N~3Q?3u՘FpzLiU#RPZSKit#;yh>3w0"댴F7x5]ZW5DZ~xRf{O@ɮv*bYnVBFV#j䬏ގ\YY&5 A\n|&D^j'uɤ)3nd5JkY`V-9M"(]O" sH,F)5겙dZ#ЇB֨Gf%Q9Jkeų{nս%&OD[Qݤ"yO+@V!^X*0-+fG8¼AJVq^j|5gQ.l*1 &E*0K,jvlQjvނ.NkPdzGG~gqd.GzqgnIPQ ;+2K+KuWJ$V>zZnOs sorE`'`Y܋RzrE3s#jQ`yeW+pϺP4^*oy.+r r@ UvqDHhX cW/00% _dWTxcίGH`0 m)npV4 ėbeB~T>bEW }R55R^ u9Rq/;f.Y䀮ɓċ߹frjE[@ u9m؛fj ݍIz~3Gk WESt]D Ɛ1WR4ٖ!U2Xt]I5,nalLLPƂrOL,BJ|hV$):A!=IB0g3ʂ5X1̀)^Gx?)iAO$bJC2 u<0 La@QbgPl7͊:BbZ`<>bM݅M&؅T=IkrNzk},;F o-3KgĄ6҉9oWKs-=׈Ѓgn̷"LF Nfh۠cJ/P|_~3@2\nAh#b[m}݂n h=”R^L-(0EE'ǃ){-(=)}[P >X,`?`./_~j<*'`Z9G WXǷD)x0X\d.gQX$Ch 0Z|unĥdA%V ܕU3:,r3T^ViZt."ܶ$0':KE&{V|/Ka>b J&fGC?7~oAF?0߉IW ^ ۻ~6O "Zם1xr aYc~eʄ۲khSeRݤ}Rh;F!vŞKXU]Fάl_4)U:L4`&â`xeQܢDE)C#6ebyG4ù8aYu6T+//_391^q 0kvT̮jY\j94Q̩?TzW5G*2~"|x<NbAȉ[n[|9OM}brWIx)~25]1P 4QseZ1N!~hTm^tx :jD''0$~d(YԅnJTE3z|M;fsz!'8LspЪfq.mٔiJ,2"2J9j-j!x$(eG5v5mT'͔qbɼϘ/b|pL;:ؿS4aPw . j@ ^:^:n=mSꋴ?? b8Y-VRˮI\4p ~M/N~%WVwQ(e{tע"N;`ZU݈*~ *~DɓbL'&U1&x 1>4<|V揑 ' >fQ_GxDݏx$8*~$*>T *~*Qx/mS:9}BuuE.yUlz~j/B#'W_>4:*WqZ1bp@VJ~Qz<:%uO!~q/ Od[1zȀ+c"J}"m{9DNSoD1dtC!05gl'Ƈsc1  ZT91~&k5bcٳ!lê|%d WKqΜŅŒ8ԫ(8`*|ZP8ZQ͊z0m7{(lQWl{qjM+6p(b 6WLa!90)H`G-lwS݃!ǁ(5K (n7c(F'Myy> tn-Pv qJhǒ(\9E΢+~sa߂Ow i)vnt(8v3Q~UGZVuR/vrVYsMZwVfQMH*8c_LrLYYa -g;[:ݵj}ŲrN^;b ljS@Aż,|/|4ߍ{OLj,Ԗc 3|wbp'XFh(og5VX}n9ɭlKhbI,vRh,$ ZhG?Y'&%@i=폙lw>ɻ,0~Os. 8s˶-@SX 5MRcqwlZk/ `XQk,08~K9&oHIH4Qx,QmWb0]IC jiaٚgT"fůQFߕu8tĆ!6=1۬n+%1#8(q9_#*Z0*zǼqsNrC2JYwBe^ujf2Ôkk+ͯFPl]\]ϯDV1~́/lj2$zslxC˷C"J.޻U;wL](*Q r[`K@Q0cYh|lns >D *#x4ğX)98/*YKơ&g_DUSMUFٴ+T`8ΰIiWpp:s~"l]9v1R!A.8ܹȋܕ7' 2L*,bJy-Ƅ:紅xMSU,ܭ^wb ѐ:|7~wh18 ߝ=I[۟P\-p|!Bmo*{jJؚ[ݪNn'J6:,USߡ1|̸ڶgOpgu,KDmYAmXSX5i˟Xxkl͞6)Ic||xTWF4fv.7ҮS>Z?8ajvË_XI6qI~6_FPl[FMH!Lk!J5\Z`LJ|4 &!o*ߍ/WC?7~1k D? /sa߰ӪH<bi>[`DluLj֗YyDHW17+XvvދO:-N3oM /# em&tq2,4:H"p=~qj.斮 G0 8*GOHZJǿjhb vgAn]A} yf.,Ew;%;1ٺ!vU %W5s 2O}OF6<՝%Ux )rykiR`h}XA>bWrt έpt4k&in>%v[, 䞪 ߳YEX^!k%RB S"m[쫩aA I`:+wM aYU[3`.՝J W9毶sY]Iag); ae(073,Y<^Y<֐~/x##!]Wz{ :r^)uIaR )pau Y<춇]<YXOkgB/yAYcRj.:dӉ>aONIZpqh#͑z@X^< >IE$?6KXxZPPݖlY]w3n9#;+À5{"-3h+ciAG.O2ȓLW>_nr*RrϜ1&9yHC*!7Եi 8"Vф89 d)3vCg@L{s$7~-^,n,cĈ̖ ĬD}p5w۶!]/\̴"-j16[AP@kt6,ه2dkWor4~+nfJ}c=@pjGQae17Ozsk{fa>Bko4ҐnQ҇waeX "F8[+ .fSVa>{En?8d2r ȃyPϱg1Ҟmuـ!o| ZG_L1o>mA {cRBadH;#rac,&9Ú"b ?XJ'0bY7'_3Sip?3: &ĢcGGKzgs$ fۻi%nᱳDo}E|;"/2߫ jDl a^G m#"rɼlF )JDwmzPG"`u؂x?[ݶ`HQC R# fNY`#<;9o h3ꜵO3QL_̄L"WC%8^`q59S㷭kWĝӈV,r#}TƋjXڶSs7QΈ >\`2=u؝qc.Bp'JqgeQH:CG#>E%m2ff_*u4kkmm9evϵeCkXEJir;[ e2d8ӆj\}k}.k>Ҩ @j)#n͟OQ% QSOx+JteU,?6f>CIƳY@\It  ;~WJ(A EUJz<{< A>%gQ5ΖW'hԱ0{qxL1=ljOPCy#˲ڹNt7>"K,ó0]ek ?L]XQiKJO XW| Kwx |VƏQ6d*2ǵ N Si@-|݈Yo)5Y(a\Su79y[%::"wJuYGbW఑ad'*R.diR`bv֋]w`i=f M;e^NZU1O1 Ofݻid ;ig1W 4XYpk'恩)+LMZώU. 'ʾfibhˈOꎕ@l~XuX-k1qQ׹͋{[ܮQ+߶nm$E/g( eI/kd*SJc:&A`NGf)g[Y[b]`}b{3We3NmD$9ns)Ly5Rtb>q:l-߳'̚FDTMz uLYk6S~U)SOA|8>,ޥ3Ogqxp0,Yȑ~`rZĵ q8iJ0BmJǗ̑:ԑu& sxΓYe{>u[ #r /"xʽyܫV{7jL]NQSoQr/80) j+x$R6%+X`8Bqt>" ~X=:l@3DLZ*Eh& ]:"\C_uNt_Vh?^~pZ^|>`SNWq:ŅwߕyJ.T+8=.TsBz1g j,&~}fʊ6Bؚr,>hCbBp\LQ9"UFgܦ0>TK=Muˀ;1s٩<R,.+ߦ6tv'M!_p!j312?8`vF]f eUro4;߆m8,ۉَCy|"bY-ϡC~.Rcp6?Y7\>ToK]} ] NGzb@>Ħܷ禙,^Wvޅ?4%O(qﲁăީϬ6֜Ny\rnفgsD',迅'd_g 䯟r\LrpWT=[йNγIyq :&'Q?8p KQV " fAwI)WSb4"VM_e-˿3P,l-Oˉb3/7Ho9i3iPdMF yBTգ\T5e}n1&1robwpj3@.!亳eXfQ0IVкGe. mUsb,70<JLWSi/O[^_ {x % '40uz\f 3-(p$EvI7?䵡;`rfy+ xVn$sic$`z+ y+ X'pHҽȖ-Z0,J1Wjbu(!Abrm@e'8[Yu{++f\cc@Ȳ{+N-X&4h|2rNN6~ ԝʍh^cc~,Ѽ)#AkwPa5%Fq\ Wo'cvF7>8203ONv0M~=jhf{ٙ6*%@5λ9w!Ng,ߥYhKs=Fʔ߭H0Y&_םnm\cAj2=dFL(Yvn33ֻG Q4;"2R6D.0JS|O cٽOƆ;i)d6P6aV -}0{ ,S Ş/L2!N4xDlFgw}hV8-דj@6 n;> ąPOBuGw 6!Ȫs0$k=]' 8J@>c (ëT{%%΅_P-$@:uj&EtaLN4ݦŽ+l0L]8SA+^=.hveQJ iar'.7s/2U}'WL zޏ%[s:7upb{hKKG {ΨyNu1r=L*T:N +~Պh-!vtbS%͌뇏y=p@}؄RhvUء>=^H=bB>M: gDj˒ɸyL_=f4&R$W_Ig!XYn۞嶏r>P>K"3=ԡ"ħ<ΩO6,Cy /=©UpG\!k v1XHw""ҮCX4VXe˨10V>h11ѻ.öA< El׻ltUnvj{2,@v`9M Ϋ*İ]11^ӻ\Ja/9(AGc`p(QfdX>Oh Yp_HDC3ݞ LFR[vݸфg!Sꃂ v@]5 0t?UX[(>G:҅tդ3}h`5;~;'OX>s(. 7Lʰ{*/z~,?p#4 9Gg#=yX^h ([qCdp%FΑeˎt>o-rɩd\kP;14 Q4敔Q; ~DzԗzÝC#K5h+ "PϷ}X+c#X0QAHۃeH+(اarWv$>'(Pp3o(4._9j~RZcEEXC\C*cgYn{>.ħ$Żd`wF?:ڍS?~S5ϺQu0j٤w+h$c'A[Mst&34Х(+_stx`3Q"/Jj\ۂaϭ^|hǪ\}&+|.%zߣ9t\M^Z6OTM0A tqK@Pٚӛ}ؿVW EѬ˛h;m#_TڧPγsx`+=Vs]5 X囫̈́KupkӺ#K2~ˏϽNg*,b8K0`4%/0|p**' ')\VZP<^@Kx8TIx_Qhм Ƒ?Ig'>#ݿ N/ 8/q6ͧ.`V* FZ9=>ibc@q`S^@߷=AQX]09ᥘn?P2t-λ_z\ .Xus=zC躸|xOirI BC0eB"=9|l G Uax/.uaRhb!Wv X  |>Kubc&Qrnhۤ__TF:7Oi.teq@[sTϗ'ubٟWI5m9"gZ-'h”Wm_D/->HTrq\jc-K@/h`M6Fg >N}EjJ3/Y'\'6;1on7"ՍW#`. 6zs|i[bnֻjw5kdž{ q Z`3=Ǿ\Eqƭˑ-#zb_)NV>F.I clnf{a<{ft_ͳEZvxrM`_uTQE9Cx@E@GI,P95溞2𦑆mcsVHN2[G9qŔ.rpP$8K^`Pi2Nۦ6z>.wǎ> #U: [p<4leއ"oE7}sF~}g,cݾYƒ/ .p&׺ w. D7 ։η(|wu {8ߺ0r1OffxaX|pѧ}g.ڶ-#r-pG>.'ΰň 7Cwnv}w% >́" 3kw*+zK4Ňc՛e岹ͅ/8qצ,\ [jZջ_B,=o#o3iz !!6.!=oCMrٵjy޾n.ғ}Tf>^֡MbaI)Tf&7,cw.OoҔ@ϛyK:Sk)f/oQCӄmT7ftvo!V :k8ވhX(v3Ǜ%'QqB1f6Wgʰy8Ǜ~s=# .։1r`}c,Irh +He0O7<;.wy2./ J:8_tddܴK{[ou]wY@1FЙXAvVlCEC"[0{{J-U-HAcl '=SR$Az’}EOzzԖg [̺9} sA+ m*-6Z&ZW:a] fo :svB[;]-k30z7L8qscۖݾgbbOBs Pu-h>86$AV4EQHͤJ/9H$$u2ƗcUAHD؇ƀX A0r!vl 0A#D҄<@գ.|Q].88!1ìH} AEQ:CRZb` kGkЖ4K 厲ؕWğ-k5Pht??PSaU|=>< EiJcKTzA^wױc;htl_d2| d$GW~h#ʖ'J8mF%z >m3[2>|6 >X[hX6sV8P?J3e tfVQ, gBz6Ƿ(?VW >v*vjxOLݔ"c\t[9[`eЎU& 3S NΠtr,aΠe2 ! _d\ Ϣ}j1oq/zVM› <%,&g`anSz{QUOF ؈zzyj$'Guc~Ol13lqh/g=bo} +o F0EX-~S&5pgH `,Ey va,| /M+Yqhq1ǐ gوA) sFV4aU1+8Yva&csӿ3BG!^ m\mXZ.l+o[ ]W{c,r,rIˏe+~R~g t%<(]?KxK1ShyG¢a\4RK;siF5+&F0.|vh[h?ᘘP( x?2-wAjWq *<wfk/h$ɣ@uwp ]*ƒkѭh~ǁ&+.Mh=,֘ Ǥ8ccj_2WI08Kw+3< =~w1Kʿz?Βq_Ktdh'&W"zٙ?ͦ"D`iL*%u5,K1~L͕^$GVi4x}.^~ht&i#OtLtTMci*{K3t{i"biҢlh [.-MZ +70;a_ebBaKHW~,*k~jS{jO,rO.z oM>iƗK4ZKQSw?/fi}>[o9[K1eNZGFNxdV]v^ya=6/bs5n5iTk<ӕ;w?hͣ2.]rሪ[p* 7Phn%sIPXR=d Zw+;H{?9Mz[/>]CtVl,wwƳ>𹮀;KUxl1]ԩÁc${]T׮˜$ n榇v9K$wC اla N ^P.D.€+ #wx}wLY5-Y퓸dm% E"ׂXIoP'?0(ݺ^}]:v.}4 o S:y^246p:ӵ6/>0 />00B7LKcԯL%4eI}U3NrO߮@b!bƎnH| ݥk`xp|8(gd3M[Srɛw+?7d_"H.DeP:A"Q#1jtŴzrHQNΣZmvA9a1 %~"2G7'VH'&w{h{d0aLIRXXH{1554EtsiHRRg@(хs/H 6AB^yإ`)(}n=w(1}X/0vZkcG=F3Oe uHzTף'>MZbQyC8(G.#sG#K‘d$o)fulGf V@O?h oơ"4;Xiǻ/b9.m!w!}BW{^ew1x,7oA7{HƜ{_S8QceG=}몣^t7a@zY5wޏIAcYc#fuz!QRE'~y@kJL)~i4 LEЋ$e؆)Q%ھ(O(2թ9QA76(<1 ֝QFG#0fIj̵[:t( .Y"8.R@Uo9_ʒ",yٯ}R3 N?< ۋ",mAu`s:w =NܯXĕ"eĬj0`fibr8xK{w0}guJD/>By3$D|S1y`^ֵ^QZyW\yXLx[h62%nqNOej}/Chr"'mO4y'g (&LS@쳧pfF !@*E>WkI4$] JH)*OŻr6e,wT Azp˓'$1H6h\>Jx҉9wL^hެBQ{|7.rџ*(ʘꊑޡ\]{E4HgT/UnĈA%2 +vk(ihKeX<(GN9^nCb4_`lrbs6 Y6,(O<*"iLQ E҇~ `5̯ Y|ρL@Ґv}2rQ0a ǫ8KHXn%CtXhbjMQis%T,iq+]aٞE㿃!]%EM3d©йэ> kn1Ztޚ EEeCoz.,HLnPU&BPS5 vUX'o*t(t&-@.:G!Ը&] }U~Ki9A-VPJ^累[-wt&9@=,@hw6^){靥auĶ Ŋ:~оtm+DH|"`|lVQ}B$q|^U+rQ*|re+iPzk7zt/c.IYED 3n%t58d¾ hh0۸,%wtkf/>~iCKHESEBFotK(ɳ."8^ ] y?Ӄ.N-Bh6Z@saϑ&(󅛃T:rg;%#_ŷdľ) Kۜ l8KnI4/wiֻ0Ff :kV-xLdOgQ{rvAREH~v4?\>rdHf+71tk? a dz\TUp`,U;Yz rmJ޾ą 818Ju}p yٽR9EKq[!@6J#h‚8.iunu`+~aGz7բne\613 U^]SkV|7#* T%g`krR|pyMtq=qc_cܴNkME7¬uvPw}/s4̙서^RA2]#5E=_sFcײ-J]*/{|.HMBPl)h%`e3÷rE5Af[7Uzr,ZЊ8^7o4gHVEҖS6zώzy]I1bwobt-~΢g]6-bik1FLjp$mh0dF,R?0*/ރr9淐eu/fx7"暳E΂v0iyzZ!W8>Ant0p=d]]@c1i(|w!z{Y?=RwUW0/QQ|Fz5%&~[‚$~s>7dM/{ńvi30 ZA ~6b}19WpKĄ'́8&D0. bܖ> iw: HA 8s`țϯMqKKX%% 9R|О6F 9ݫ5rk392=BY 'sһ/p[%VM{É'k|]өHyc S< iIxZDCÜ?[09lw1H w2ùF2Nq;xT_Cⴈ4&j1uF[vԀx}j\6jS#+#۴)cmY6Ύ}؍+L U_aX(3as[XvYp]to[ΤS9sWdW!i #A#ۈVuAĝTb_u?B6Do9P2^ݣ] V>,>l ,,qRܽ§[_f╽ljd; ̿Rg̿ke&#'׸3͎XydbilQn0|-݄V6gĭ3liů4jͦeÁЮ=zpý!58VvZli[S8#G'Կ`VcQc@HMXrqp).](y4H}i_6iǹGsL u!r=JR Pwcv7"Z^dV8>@k6< qX 2XoXr[>]4?'5+f (Vp浧)]!>  $s2ޓ10+5]4狲6<ǵ3ߔ9,?{g\"dWŻ2W?,eE1,ޖ-QM" ti C'2ih1CmxdNEKSP/4^m⟛⥇ͅ9Zk>{efrrXƦ]|ssX3vϿ߆8֞S(knݽ}:|Yc _8  _-^aӘZ݌B +?VAM:[:V9E zE ɋBsi dx{Kj*w"k{]/F̻QqEduOߔ7 `kfnuvM2׽I58gv8);8ggmlፕsV2~g ~{0gpi%:#$ n}e GxGh%"X] Jq-zN3q(cF=%1K;ѩkdl\Ƶ0H35]6nKb؃w[X7Kb^@ŵKh"꒸qbv: L Ol_Dӑt̏elq,dL_e6eN~X^;n,_/D6A% :02" [\`6tPB-eQq`noW+N6cRzܢM'<9"h} =<ޜ.\'- Vj]WPZ_^ `r05鶠S<oŇrTʓtڛ#GyWat3Fu+w[ǽ'Gro H?'8){B*7ÞjsOZ/cO}Ao-nݰ'Le^=aV{S'8#Wm,YƴE6K&/DtE z,T>gӞMR٦2sS^pq  q8C)Ѕ9/;R,mR"Dڠġ_"ұz48)Q1H?Jͨ#Q֥K~ o`saI϶N 퍐1u$g1z$݇൛e3ģsԅzwa?z<؉7c7#Lq* !ް*pWf"7Y[>$gW2R@2It X '.ڰl99mwΝ d4XDbu<Aؘ쀈uђФ?q$,VW&[I'ҾXG– =8Lؓ~-OzA!-C>@.!|ǖGBa0.^TNВ-ִ!kgѯIW?{Y"³w9L?bTч!ẀaA&z&RW9mJA`,xwč!iw z =MW/2pMܔ$[?P\I/~tj _[siwM%•i@hPS7'NLH_j0/rED (n `Z;P'lȥ{4&g+oX ~+*4ʸ} ;vZƽ3+ʃ"\gdOfI3xiu/)Wjݞ5ܡP\4(9! ^dUeZ[<|ziζǝk;F/Dq0Eq9!# i}~Ta kBbw3.dLAWs n$<_TLDdP,h7K 6Wޡ= 3tbSf}ǃ 6l4T )-oUi ص: jqa_7I e1یԲoqKV)PuxccQZSŗVU+j2N16м:6D髣łUʌY^y9vȒŷG>tEE,YH6ھdE-7Ƴ9jFl-7z+7ـ~g< aUڟrsO,k& &Ԗ}h^rV{OG tnt|ȝK9ZQ,f'[ܙ0.]a9C\^{O{ xR0Egx!܅.]ynpW 7w9Q|< MijW>vTB\,\V.k*HlIFvcB=~+p3`Ȩd':U\0p'f֥ʝM͇# ۱neal>4r25YCܝ'`w0eWJחQO.{Ng;[G])v<,w-bF#9a qrf;#5<9`5:O&vn-'V|յgM'S E 2<--=,/ScFQ|NʖdL:bJTԅRZab!(~00.  pQ: 4)IgA: WM}RR:l20r&Ռuxw HL2]]\}eXlw^ub\_g q;p228Y-wyf9m6[^=Blʅ8;brHCr!F8Q 9S6oݧw+ ΘJc+Tb\'6b!A m KhfE91ep' Hy+ "7>֝|kB`[鞉> 茘$zW"AK~cYsPH䋠M&+x8D{ N'%3nڪ^T?.A.hL ga?4~gX{G `>[-z.> ? > ~縷pپʼnIe|0@;FK.:#yƈ3v} ێx '< fǞh'~Œ=X*qek4, Ѐ6 ;2zgNx>d$s$'9%d&ɠ *|F.PiS%Bc\ZMj|'xX]c / ?ЋgusM!Y&z."j/1ZzYJwΫV]z-ZN7?/s R|p(4aH8 Poq'^)k&DŽ JֵhL%'[-L5+M{EXSB2eR2ee]XJ%(a:Dce^c"\%'7>LWL9aƥhK9b,;YzD7w5kX_[Vt(F3ldMES._t1xrI,/ݿfV;DӾڢx xp!~P2ɶf-Dd[Y0!Eb a3'^fLA XIwVՓh3M[ХItuPm"Ssv$WbGrg֓*,$繢sM OWSt͗š*]shFT,3Fٱيg0v./diwƳU#V>܃q<]N*B&RK<%hEb6q ֔:ª +cr6rpŰϿG)8.u.g_A443 6fnyc>5\V@ST}ms]Ѕq(kM˪]]>DޖET}kh}өytj@ۚBNbb҈i;I]x[y'cX\g3RG] PQ];`u,02xi<Ig?8V9fV - xsA.}<Ldo,qui21PB1(!EWs=5׫7#W׈gzjwn>qLeXrUхhe`dɲ8 aUrU AXG7RsAtG &|DjeD}}fon&CmI.+O6[&>L>ܯRg&Ts \ǒv<N˶u nhɻ%Xc.D) :6eFʺe #G,T\B.ڔ{a !$C1ߙ E ۰7p<oPuFݢ ,4՚N61fV!l?rD}C\CH Tu7aYȫϛzEmg\ K(n(oDt -#:*ҧP)2{s6)k3n ~[#J'tEk"h=QbEakX SRU=PZ]^W/yj)6}sŠef؞Ş1w& F&F asEF ;Q>[Џ&Vl Ql d_:rWW>,ݫLd06>W4۰Uo.~kpF1iM)ރ:W=GкUqHwJ`$<9 *|0@fנ-p!l2P95rrd޸@g3IB8S;n7ѻ}B̻σ7?+4E0>! Ιo0HD[)pJA_D_f(4b)sˈQbŮ@) LK&.朞s>wL(A'zRlޚuKj!u Zb(0 B\dۂΑ4nϭ^F;zkO` c*vT`1mB2qafٱ,'Jk]FR.oAgg}k6J{M}^YP0U뮀D7LnPb j04 ȏ;"?1u$slT597;dBiF7sQye3TO Z y@E/^vjGW%|R &{ѻ,=,r雴<.^@ZjKD NP[xP6$Ώ.~޼ͻZ,^ۢaڼ- cu,OUHZ;lAv"yѤLX%%پ.U E @ZnaJ'[Ąg|ej&.bŔ.CWf.ЭmcĄؒ}XN!LwB4[HaƝ,Qq>]3(7> Z.;%E#!1Q1,+*DH9Vn%Y ?3i L>8٣Zکl75m[~Fp4>*=rASgmWǘS?\͞Cd762AsoC>ֽ^V{LE gZZXa Av!EQT(Zt7ȖuK\SW8Ō>={DRvLJpx"SX|%b!j&5qDfNE&{qt0Ͼ?j1';`䔤S7nkɩN߇߭FNN]ԧ;fIe8'dQ HXƎNΈK`ѩORyt3M2DZͰl]7@7wwX&*6?6-FΰcӛVޙcS*`{m#D<6ؔ Z43KRޥ Н%C\P[͛oheȜ\OM>*moGu~mh]28d+M6$GhNH9$1 쌶08̔{f4Co4;Ɔ)8[M72 H693{^q= q@v,f 3w.-#??7#K#|\VK7>˿[“0#\{ffn+NAgc(Zt4]7m4\a> o K]Oy ] LzDz@~-#M5G-M3Y=O) /X' ? çdU@ZZL@@v6=\BvB'x\uLtuz{%`Ĕ'm/.]W>'5xkP@t $vQI: [ΑW.Ob̋˦*qA rE@7vAmSfwn-eU8Lz -KIq(`K2i C1ӠF(R_2-UIbձc^WְsmXɈa8YzWũ!፡ ^EǥL[F\bE}iZ]o"fXy4lߴKZ< b?9ϻmMKu4zB-OR>:XlyWrȬٔ&zT~ʌR8e``dG_]Foθco$&fwv+N09~E)!P ߇][ F;((R:IT!"K[Y d2j%.rDC!ߧ'݄)TY56`tIp'б!`1mGە܃")T8{&I0C#2ےUE@jITrFetB-C1|wҵ;`2gw)AD2Ta'h$N8c1Jv,__[ԛ$>$CS F6Mg[ka#[de1?xgceR|ő\:{ 1$~Y >d2+SgX|Zm]!պXD';iE5#;l~g,~."kMm<]wƳa~=@YU[Ɉ^瘹;#{`\Yոs6€ FR KF8v 0qh 4J㱞nF3Dy_S0TkU|P.#^=_Mj z6hL6 Xfm %0!w7|?r9>jҭu}\IG˔Za"2j`ps9qEvfɀ dP.4ے|E;>'ω#Tsu$%Υ_}NM!|=Z=uM,KNP0"b~ǹ\DL{F`<'ŋNI빘" JWDKX`(2WA°tWPEQ3-jqKb29o9Ah}x]8V؛lPp9pdTHWWJ:4\ ^c07-'Z7%#:d|O$1JLM:3]=&ywб9#v yB55'H6հ[Na~o~hi;c9y!9gynZW(.p݀S{ֈ ];8F ~n9 .9 %a `bt_~e~ug6ﲇm[ ՄE35kս?U,W[Dv3)9}5|~ /![4ʾkyPDT⎓w=w*+~SQ.*C:7]޼+ko/^ۢEջ_ㇸW#XN4Ɲ۽ N9鞟#˲#!n{29]15nT3.Y% 'tWz⯩9 +r7zAk\=[F=}뻅sP/6jre. ?N z%[p6pb췻71Oğ;=5⺞ȁO% #IKhvFBB ?{P=Tx^((ZMfMߛ?esABكA5H?Ep<ţtzP aϞ|?Uqy{{Ljs%y\BTƳ,?#Ԟ|pAvUU{xCEXOӖ@%Z/v Z 0[8+g\yArx1f4D}v?Tâtxl^U\AN_GB A/%wf\ER0* 4JͤqnZvk=pn &0 d]DR ׵x97Z*3d c#ZU}(c`( B=);K= jwP7vgu-I:tU nA/u>L?!LEV"59/ |l\$޿~ӕNqM1t^GN9gr4S;NOhgBʧG_j9{$= zRwBt;AqwC1CxKΕ#mڴS"fz\yvI:V}9)?4x|=, '9oY>E#^3#.QچSɟ+Fuq!OEmsw;؇fkD3\}Z:uplI,_Ua1X!=K*L'Nv{T? =IhST&K l[$>W6t|I[)o^yq]ѯv]=+,2˂A-K]GA'J'#klɲSÔ$T}ϊ}?2x߿Pj("G1RcX4FMEn4dt!jaCz# +6Ә u?'nAi3nT5 ='L[#޶1E: 'vl+c(n!^6@,m?;/8(g2ZWU0/Z ,#>cWwe7̭Nj­.[P,켩 \'.ضE+ܯ?z}Z TggŔ#+qTÏEE@W!YPy3dAd^DJxPd^h8kE&d*L9nSMH, )8cUE}6v>& ͝ u!|ɽ>9eVp /!]Ku鲟-F ÒoP]ZɼEe[Oĩe02~@nG<'~L_hcH a, ÒD"}&ǬyfksτAu0Dyvq?[ܶMD벉+#ߣ2]#bc= ozm>Z8'oɞOS7͒MˁPoR r<mr ;/&_ゐ_2Tj{BO:|[48|[Ȳ?l,3,IdHrE'<ϑxNto3Ld{+mψ=O^`4mϣ;+۞GG-le{:!۠]ghׁBү. |2eb'Y}+*\KN6~eSid,C0n/Dr=Yx,1ي+g1b?=[/wG2oxҩ 6~eMnVvLs??6 =o۳k[m;<2[*Mp#WV(ΆywlSpq)Y@y89؝ z0iI ǖͱժYu`8[ c? [zZ}6*>;@_qֲj.M[3S-[{gwվH=-CHplujӸɃq.R6bn/"~MqѺq1pl!rq1 'p噐Ƅ?x:8X u;h\4]ӡ\ \>ƭj/x:>!*[nW["ȟqcǖ+gpl8:KzCñN5[VEIJf~%}o #̻Vvñ\#p};'_ÿkqUN˅*drXpvWc V:ySgK.J&_uVAcTw'\Z#F ".r˭r1V4*od֌L>Ļw8&]ij N!BӀ7&˔.OJˋ\o V9) A@jPjWC/ޕYƖ/oMo&F|̇iYc{,\SguxE-uUXSd^o?u/E߸8[OZZOqxޖo %bsg$Tk`F ,#VqH!2oþ ޑcT@k 2=Da&;LL? g\zV@F7~np23,8]8J:8i5xA\ Xҕi@55M}'ʴ}+:M{(hhO۲gҴؓeWmiP6^*dι~99OpmЗSnSP _37֐X'nx1h .^@H !]q_iS-*}ImS]lN9\ 9<o磴sB겻'u-c4]˶겺 -\aۘJ-N-[֌D{*F/u:+S3\Bb+rӝ1Kja617Fv&nfs98Dǥ2|!/8cڵ%|sBbֳ֧ѫ*Z0ܭGu(q>;'y6\,m֡RoܬC[@,Kv}YU%,ou::"SU6ЀhB;ou[]#~ΕPuuBxl'vx w12rtd.l<<:o1L6PZ{*3&GckiA:qoo/x콟2}wg+|4:`|4<h47B@686:Î2bȲ4:Py Nx|KU ( 9t$A]`\@˄9c9u .d 910^Yv HΝ! ciPԳ$$ӱ y)[7X\ 8>Baxf>m3yW]?Be2y`Avh92, ߧm #8(E+PD2KI0Z(?r,$"~p7! 7艺and t+[д{ U.r'aEgy ׼ Yh ]$uק^ T~ۄvx f~TO{̴|ķBYM / * b|(sp_(aBwy_fu1eN.^xAVVD+wZ`/{.w / \a1 dHPA(?+vw}Kzx`m { U#rq 8߇RPUū*2@&$@~elʁr_ 8Yh)J:龎NZzsy y}_k$U0(A sl"+`U#%-OO@m./ *d;|g&4E*ַ t!n##Sx0.Ρ9-vy,A$zC)*>L#^\J XIaPi_83[%x1;Q~r<Z'\pEN*y KT#n>b]g(xQ6|B=蕼'W&6N-zUA>[#Nf+NvRGnK4է(dtMwX/A+/z9`|tp>xǗ\P$9^dƿkO黮K1K_w] jO3o.&;NԼs!ӣG:ٖLG󸈓E92beG*^`эJ^_aJd eiOo|$64F@/ˍ_=-10щRU;n|wnczQ D]Dt3q 'Nӳ_ endstream endobj 9 0 obj 148666 endobj 7 0 obj << /ExtGState << /a0 << /CA 1 /ca 1 >> >> >> endobj 5 0 obj << /Length 10 0 R /PatternType 1 /BBox [0 0 560 400] /XStep 1920 /YStep 1920 /TilingType 1 /PaintType 1 /Matrix [ 1 0 0 1 0 0 ] /Resources << /XObject << /x8 8 0 R >> >> >> stream /x8 Do endstream endobj 10 0 obj 10 endobj 1 0 obj << /Type /Pages /Kids [ 6 0 R ] /Count 1 >> endobj 11 0 obj << /Creator (cairo 1.8.10 (http://cairographics.org)) /Producer (cairo 1.8.10 (http://cairographics.org)) >> endobj 12 0 obj << /Type /Catalog /Pages 1 0 R >> endobj xref 0 13 0000000000 65535 f 0000149766 00000 n 0000000203 00000 n 0000000015 00000 n 0000000182 00000 n 0000149505 00000 n 0000000303 00000 n 0000149433 00000 n 0000000503 00000 n 0000149408 00000 n 0000149744 00000 n 0000149831 00000 n 0000149959 00000 n trailer << /Size 13 /Root 12 0 R /Info 11 0 R >> startxref 150012 %%EOF trunk-2018.02b/doc/sphinx/fig/yade-multi-summary.svg000066400000000000000000007422601324306050200223120ustar00rootroot00000000000000 image/svg+xml trunk-2018.02b/doc/sphinx/formulation.rst000066400000000000000000002217771324306050200203550ustar00rootroot00000000000000.. _chapter-formulation: **************** DEM Background **************** In this chapter, we mathematically describe general features of explicit DEM simulations, with some reference to Yade implementation of these algorithms. They are given roughly in the order as they appear in simulation; first, two particles might establish a new interaction, which consists in #. detecting collision between particles; #. creating new interaction and determining its properties (such as stiffness); they are either precomputed or derived from properties of both particles; Then, for already existing interactions, the following is performed: #. strain evaluation; #. stress computation based on strains; #. force application to particles in interaction. This simplified description serves only to give meaning to the ordering of sections within this chapter. A more detailed description of this *simulation loop* is given later. In this chapter we refer to kinematic variables of the contacts as \`\`strains``, although at this scale it is also common to speak of \`\`displacements``. Which semantic is more appropriate depends on the conceptual model one is starting from, and therefore it cannot be decided independently of specific problems. The reader familiar with displacements can mentaly replace normal strain and shear strain by normal displacement and shear displacement, respectively, without altering the meaning of what follows. Collision detection ===================== Generalities ------------- Exact computation of collision configuration between two particles can be relatively expensive (for instance between :yref:`Sphere` and :yref:`Facet`). Taking a general pair of bodies $i$ and $j$ and their \`\`exact`` (In the sense of precision admissible by numerical implementation.) spatial predicates (called :yref:`Shape` in Yade) represented by point sets $P_i$, $P_j$ the detection generally proceeds in 2 passes: #. fast collision detection using approximate predicate $\tilde P_i$ and $\tilde P_j$; they are pre-constructed in such a way as to abstract away individual features of $P_i$ and $P_j$ and satisfy the condition .. math:: \forall {\bf x}\in R^3: x\in P_i\Rightarrow x\in \tilde P_i :label: eq-bounding-predicate (likewise for $P_j$). The approximate predicate is called \`\`bounding volume'' (:yref:`Bound` in Yade) since it bounds any particle's volume from outside (by virtue of the implication). It follows that $(P_i \cap P_j)\neq\emptyset \Rightarrow (\tilde P_i \cap \tilde P_j)\neq\emptyset$ and, by applying *modus tollens*, .. math:: \bigl(\tilde P_i \cap \tilde P_j\bigr)=\emptyset\Rightarrow\bigl( P_i \cap P_j \bigr)=\emptyset :label: eq-collide-exclude which is a candidate exclusion rule in the proper sense. #. By filtering away impossible collisions in :eq:`eq-collide-exclude`, a more expensive, exact collision detection algorithms can be run on possible interactions, filtering out remaining spurious couples $(\tilde P_i \cap \tilde P_j)\neq\emptyset \wedge \bigl(P_i \cap P_j\bigr)=\emptyset$. These algorithms operate on $P_i$ and $P_j$ and have to be able to handle all possible combinations of shape types. It is only the first step we are concerned with here. Algorithms ----------- Collision evaluation algorithms have been the subject of extensive research in fields such as robotics, computer graphics and simulations. They can be roughly divided in two groups: Hierarchical algorithms which recursively subdivide space and restrict the number of approximate checks in the first pass, knowing that lower-level bounding volumes can intersect only if they are part of the same higher-level bounding volume. Hierarchy elements are bounding volumes of different kinds: octrees [Jung1997]_, bounding spheres [Hubbard1996]_, k-DOP's [Klosowski1998]_. Flat algorithms work directly with bounding volumes without grouping them in hierarchies first; let us only mention two kinds commonly used in particle simulations: Sweep and prune algorithm operates on axis-aligned bounding boxes, which overlap if and only if they overlap along all axes. These algorithms have roughly $\bigO{n\log n}$ complexity, where $n$ is number of particles as long as they exploit :ref:`temporal coherence ` of the simulation. Grid algorithms represent continuous $R^3$ space by a finite set of regularly spaced points, leading to very fast neighbor search; they can reach the $\bigO{n}$ complexity [Munjiza1998]_ and recent research suggests ways to overcome one of the major drawbacks of this method, which is the necessity to adjust grid cell size to the largest particle in the simulation ([Munjiza2006]_, the \`\`multistep'' extension). .. _sect-temp-coherence: Temporal coherence expresses the fact that motion of particles in simulation is not arbitrary but governed by physical laws. This knowledge can be exploited to optimize performance. Numerical stability of integrating motion equations dictates an upper limit on $\Delta t$ (sect. :ref:`sect-formulation-dt`) and, by consequence, on displacement of particles during one step. This consideration is taken into account in [Munjiza2006]_, implying that any particle may not move further than to a neighboring grid cell during one step allowing the $\bigO{n}$ complexity; it is also explored in the periodic variant of the sweep and prune algorithm described below. On a finer level, it is common to enlarge $\tilde P_i$ predicates in such a way that they satisfy the :eq:`eq-bounding-predicate` condition during *several* timesteps; the first collision detection pass might then be run with stride, speeding up the simulation considerably. The original publication of this optimization by Verlet [Verlet1967]_ used enlarged list of neighbors, giving this technique the name *Verlet list*. In general cases, however, where neighbor lists are not necessarily used, the term *Verlet distance* is employed. .. _sect-sweep-and-prune: Sweep and prune ---------------- Let us describe in detail the sweep and prune algorithm used for collision detection in Yade (class :yref:`InsertionSortCollider`). Axis-aligned bounding boxes (:yref:`Aabb`) are used as $\tilde P_i$; each :yref:`Aabb` is given by lower and upper corner $\in R^3$ (in the following, $\tilde P_i^{x0}$, $\tilde P_i^{x1}$ are minimum/maximum coordinates of $\tilde P_i$ along the $x$-axis and so on). Construction of :yref:`Aabb` from various particle :yref:`Shape`'s (such as :yref:`Sphere`, :yref:`Facet`, :yref:`Wall`) is straightforward, handled by appropriate classes deriving form :yref:`BoundFunctor` (:yref:`Bo1_Sphere_Aabb`, :yref:`Bo1_Facet_Aabb`, …). Presence of overlap of two :yref:`Aabb`'s can be determined from conjunction of separate overlaps of intervals along each axis (`fig-sweep-and-prune`_): .. math:: \left(\tilde P_i \cap \tilde P_j\right)\neq\emptyset \Leftrightarrow \bigwedge_{w\in\{x,y,z\}}\left[\left(\left(\tilde P_i^{w0},\tilde P_i^{w1}\right) \cap \left(\tilde P_j^{w0},\tilde P_j^{w1}\right)\right)\neq\emptyset\right] where $(a,b)$ denotes interval in $R$. .. _fig-sweep-and-prune: .. figure:: fig/sweep-and-prune.* Sweep and prune algorithm (shown in 2D), where :yref:`Aabb` of each sphere is represented by minimum and maximum value along each axis. Spatial overlap of :yref:`Aabb`'s is present if they overlap along all axes. In this case, $\tilde P_1\cap\tilde P_2\neq\emptyset$ (but note that $P_1\cap P_2=\emptyset$) and $\tilde P_2 \cap\tilde P_3\neq\emptyset$.} The collider keeps 3 separate lists (arrays) $L_w$ for each axis $w\in\{x,y,z\}$ .. math:: L_w=\bigcup_{i} \left\{\tilde P_i^{w0}, \tilde P_i^{w1} \right\} where $i$ traverses all particles. $L_w$ arrays (sorted sets) contain respective coordinates of minimum and maximum corners for each :yref:`Aabb` (we call these coordinates *bound* in the following); besides bound, each of list elements further carries ``id`` referring to particle it belongs to, and a flag whether it is lower or upper bound. In the initial step, all lists are sorted (using quicksort, average $\bigO{n\log n}$) and one axis is used to create initial interactions: the range between lower and upper bound for each body is traversed, while bounds in-between indicate potential :yref:`Aabb` overlaps which must be checked on the remaining axes as well. At each successive step, lists are already pre-sorted. Inversions occur where a particle's coordinate has just crossed another particle's coordinate; this number is limited by numerical stability of simulation and its physical meaning (giving spatio-temporal coherence to the algorithm). The insertion sort algorithm swaps neighboring elements if they are inverted, and has complexity between \bigO{n} and \bigO{n^2}, for pre-sorted and unsorted lists respectively. For our purposes, we need only to handle inversions, which by nature of the sort algorithm are detected inside the sort loop. An inversion might signify: * overlap along the current axis, if an upper bound inverts (swaps) with a lower bound (i.e. that the upper bound with a higher coordinate was out of order in coming before the lower bound with a lower coordinate). Overlap along the other 2 axes is checked and if there is overlap along all axes, a new potential interaction is created. * End of overlap along the current axis, if lower bound inverts (swaps) with an upper bound. If there is only potential interaction between the two particles in question, it is deleted. * Nothing if both bounds are upper or both lower. Aperiodic insertion sort ^^^^^^^^^^^^^^^^^^^^^^^^^^ Let us show the sort algorithm on a sample sequence of numbers: .. math:: \sortlines{\sortSep& 3 &\sortInv& 7 &\sortInv& 2 &\sortInv& 4 &\sortSep} Elements are traversed from left to right; each of them keeps inverting (swapping) with neighbors to the left, moving left itself, until any of the following conditions is satisfied: .. FIXME table =================== ======================================================== ($\leq$) the sorting order with the left neighbor is correct, or ($||$) the element is at the beginning of the sequence. =================== ======================================================== We start at the leftmost element (the current element is marked $\currelem{i}$) .. math:: \sortlines{\sortSep& \currelem{3} &\sortInv& 7 &\sortInv& 2 &\sortInv& 4 &\sortSep.} It obviously immediately satisfies ($||$), and we move to the next element: .. math:: \sortlines{\sortSep& 3 &\sortInv& \isleq{7} &\sortInv& 2 &\sortInv& 4 &\sortSep.} Condition ($\leq$) holds, therefore we move to the right. The $\currelem{2}$ is not in order (violating ($\leq$)) and two inversions take place; after that, ($||$) holds: .. math:: \sortlines{ \sortSep& 3 &\sortInv& 7 &\sortInv& \isnleq{2} &\sortInv& 4 &\sortSep, \\ \sortSep& 3 &\sortInv& \isnleq{2} &\sortInv& 7 &\sortInv& 4 &\sortSep, \\ \sortSep& \currelem{2} &\sortInv& 3 &\sortInv& 7 &\sortInv& 4 &\sortSep. } The last element $\currelem{4}$ first violates ($\leq$), but satisfies it after one inversion .. math:: \sortlines{ \sortSep& 2 &\sortInv& 3 &\sortInv& 7 &\sortInv& \isnleq{4} &\sortSep, \\ \sortSep& 2 &\sortInv& 3 &\sortInv& \isleq{4} &\sortInv& 7 &\sortSep. } All elements having been traversed, the sequence is now sorted. It is obvious that if the initial sequence were sorted, elements only would have to be traversed without any inversion to handle (that happens in $\mathcal{O}(n)$ time). For each inversion during the sort in simulation, the function that investigates change in :yref:`Aabb` overlap is invoked, creating or deleting interactions. The periodic variant of the sort algorithm is described in :ref:`sect-periodic-insertion-sort`, along with other periodic-boundary related topics. Optimization with Verlet distances ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ As noted above, [Verlet1967]_ explored the possibility of running the collision detection only sparsely by enlarging predicates $\tilde P_i$. In Yade, this is achieved by enlarging :yref:`Aabb` of particles by fixed relative length (or Verlet's distance) in all dimensions $\Delta L$ (:yref:`InsertionSortCollider.sweepLength`). Suppose the collider run last time at step $m$ and the current step is $n$. :yref:`NewtonIntegrator` tracks the cummulated distance traversed by each particle between $m$ and $n$ by comparing the current position with the reference position from time $n$ (:yref:`Bound::refPos`), .. math:: L_{\rm mn}&=|X^n - X^m| :label: eq-verlet-l0 triggering the collider re-run as soon as one particle gives: .. math:: L_{\rm mn}&>\Delta L. :label: eq-verlet-trigger :yref:`InsertionSortCollider.targetInterv` is used to adjust $\Delta L$ independently for each particle. Larger $\Delta L$ will be assigned to the fastest ones, so that all particles would ideally reach the edge of their bounds after this "target" number of iterations. Results of using Verlet distance depend highly on the nature of simulation and choice of :yref:`InsertionSortCollider.targetInterv`. Adjusting the sizes independently for each particle is especially efficient if some parts of a problem have high-speed particles will others are not moving. If it is not the case, no significant gain should be expected as compared to targetInterv=0 (assigning the same $\Delta L$ to all particles). The number of particles and the number of available threads is also to be considered for choosing an appropriate Verlet's distance. A larger distance will result in less time spent in the collider (which runs single-threaded) and more time in computing interactions (multi-threaded). Typically, large $\Delta L$ will be used for large simulations with more than $10^5$ particles on multi-core computers. On the other hand simulations with less than $10^4$ particles on single processor will probably benefit from smaller $\Delta L$. Users benchmarks may be found on Yade's wiki (see e.g. ``_). Creating interaction between particles ================================================ Collision detection described above is only approximate. Exact collision detection depends on the geometry of individual particles and is handled separately. In Yade terminology, the :yref:`Collider` creates only *potential* interactions; potential interactions are evaluated exactly using specialized algorithms for collision of two spheres or other combinations. Exact collision detection must be run at every timestep since it is at every step that particles can change their mutual position (the collider is only run sometimes if the Verlet distance optimization is in use). Some exact collision detection algorithms are described in :ref:`sect-strain-evaluation`; in Yade, they are implemented in classes deriving from :yref:`IGeomFunctor` (prefixed with ``Ig2``). Besides detection of geometrical overlap (which corresponds to :yref:`IGeom` in Yade), there are also non-geometrical properties of the interaction to be determined (:yref:`IPhys`). In Yade, they are computed for every new interaction by calling a functor deriving from :yref:`IPhysFunctor` (prefixed with ``Ip2``) which accepts the given combination of :yref:`Material` types of both particles. Stiffnesses ----------- Basic DEM interaction defines two stiffnesses: normal stiffness $K_N$ and shear (tangent) stiffness $K_T$. It is desirable that $K_N$ be related to fictitious Young's modulus of the particles' material, while $K_T$ is typically determined as a given fraction of computed $K_N$. The $K_T/K_N$ ratio determines macroscopic Poisson's ratio of the arrangement, which can be shown by dimensional analysis: elastic continuum has two parameters ($E$ and $\nu$) and basic DEM model also has 2 parameters with the same dimensions $K_N$ and $K_T/K_N$; macroscopic Poisson's ratio is therefore determined solely by $K_T/K_N$ and macroscopic Young's modulus is then proportional to $K_N$ and affected by $K_T/K_N$. Naturally, such analysis is highly simplifying and does not account for particle radius distribution, packing configuration and other possible parameters such as the interaction radius introduced later. Normal stiffness ^^^^^^^^^^^^^^^^ The algorithm commonly used in Yade computes normal interaction stiffness as stiffness of two springs in serial configuration with lengths equal to the sphere radii (`fig-spheres-contact-stiffness`_). .. _fig-spheres-contact-stiffness: .. figure:: fig/spheres-contact-stiffness.* Series of 2 springs representing normal stiffness of contact between 2 spheres. Let us define distance $l=l_1+l_2$, where $l_i$ are distances between contact point and sphere centers, which are initially (roughly speaking) equal to sphere radii. Change of distance between the sphere centers $\Delta l$ is distributed onto deformations of both spheres $\Delta l=\Delta l_1+\Delta l_2$ proportionally to their compliances. Displacement change $\Delta l_i$ generates force $F_i=K_i \Delta l_i$, where $K_i$ assures proportionality and has physical meaning and dimension of stiffness; $K_i$ is related to the sphere material modulus $E_i$ and some length $\tilde l_i$ proportional to $r_i$. .. math:: :nowrap: \begin{align*} \Delta l&=\Delta l_1+\Delta l_2\\ K_i&=E_i \tilde l_i\\ K_N\Delta l&=F=F_1=F_2\\ K_N\left(\Delta l_1+\Delta l_2\right)&=F\\ K_N\left(\frac{F}{K_1}+\frac{F}{K_2}\right)&=F\\ K_1^{-1}+K_2^{-1}&=K_N^{-1} \\ K_N&=\frac{K_1 K_2}{K_1 + K_2} \\ K_N&=\frac{E_1 \tilde l_1 E_2 \tilde l_2}{E_1\tilde l_1+E_2\tilde l_2} \end{align*} The most used class computing interaction properties :yref:`Ip2_FrictMat_FrictMat_FrictPhys` uses $\tilde l_i=2r_i$. Some formulations define an equivalent cross-section $A_{\rm eq}$, which in that case appears in the $\tilde l_i$ term as $K_i=E_i\tilde l_i=E_i\frac{A_{\rm eq}}{l_i}$. Such is the case for the concrete model (:yref:`Ip2_CpmMat_CpmMat_CpmPhys`), where $A_{\rm eq}=\min(r_1,r_2)$. For reasons given above, no pretense about equality of particle-level $E_i$ and macroscopic modulus $E$ should be made. Some formulations, such as [Hentz2003]_, introduce parameters to match them numerically. This is not appropriate, in our opinion, since it binds those values to particular features of the sphere arrangement that was used for calibration. Other parameters ---------------- Non-elastic parameters differ for various material models. Usually, though, they are averaged from the particles' material properties, if it makes sense. For instance, :yref:`Ip2_CpmMat_CpmMat_CpmPhys` averages most quantities, while :yref:`Ip2_FrictMat_FrictMat_FrictPhys` computes internal friction angle as $\phi=\min(\phi_1,\phi_2)$ to avoid friction with bodies that are frictionless. .. _sect-strain-evaluation: Strain evaluation ================= In the general case, mutual configuration of two particles has 6 degrees of freedom (DoFs) just like a beam in 3D space: both particles have 6 DoFs each, but the interaction itself is free to move and rotate in space (with both spheres) having 6 DoFs itself; then $12-6=6$. They are shown at `fig-spheres-dofs`_. .. _fig-spheres-dofs: .. figure:: fig/spheres-dofs.* Degrees of freedom of configuration of two spheres. Normal strain appears if there is a difference of linear velocity along the interaction axis ($n$); shearing originates from the difference of linear velocities perpendicular to $n$ *and* from the part of $\vec{\omega}_1+\vec{\omega}_2$ perpendicular to $n$; twisting is caused by the part of $\vec{\omega}_1-\vec{\omega}_2$ parallel with $n$; bending comes from the part of $\vec{\omega}_1-\vec{\omega}_2$ perpendicular to $n$. We will only describe normal and shear components of strain in the following, leaving torsion and bending aside. The reason is that most constitutive laws for contacts do not use the latter two. Normal strain ------------- .. _sect-normal-strain-constants: Constants ^^^^^^^^^^ Let us consider two spheres with *initial* centers $\bar{\vec{C}_1}$, $\bar{\vec{C}}_2$ and radii $r_1$, $r_2$ that enter into contact. The order of spheres within the contact is arbitrary and has no influence on the behavior. Then we define lengths .. math:: :nowrap: \begin{align*} d_0&=|\bar{\vec{C}}_2-\bar{\vec{C}}_1| \\ d_1&=r_1+\frac{d_0-r_1-r_2}{2}, & d_2&=d_0-d_1. \end{align*} These quantities are *constant* throughout the life of the interaction and are computed only once when the interaction is established. The distance $d_0$ is the *reference distance* and is used for the conversion of absolute displacements to dimensionless strain, for instance. It is also the distance where (for usual contact laws) there is neither repulsive nor attractive force between the spheres, whence the name *equilibrium distance*. .. _fig-sphere-sphere: .. figure:: fig/sphere-sphere.* Geometry of the initial contact of 2 spheres; this case pictures spheres which already overlap when the contact is created (which can be the case at the beginning of a simulation) for the sake of generality. The initial contact point $\bar{\vec{C}}$ is in the middle of the overlap zone. Distances $d_1$ and $d_2$ define reduced (or expanded) radii of spheres; geometrical radii $r_1$ and $r_2$ are used only for collision detection and may not be the same as $d_1$ and $d_2$, as shown in fig. `fig-sphere-sphere`_. This difference is exploited in cases where the average number of contacts between spheres should be increased, e.g. to influence the response in compression or to stabilize the packing. In such case, interactions will be created also for spheres that do not geometrically overlap based on the *interaction radius* $R_I$, a dimensionless parameter determining „non-locality“ of contact detection. For $R_I=1$, only spheres that touch are considered in contact; the general condition reads .. math:: d_0&\leq R_I(r_1+r_2). :label: eq-strain-interaction-radius The value of $R_I$ directly influences the average number of interactions per sphere (percolation), which for some models is necessary in order to achieve realistic results. In such cases, :yref:`Aabb` (or $\tilde P_i$ predicates in general) must be enlarged accordingly (:yref:`Bo1_Sphere_Aabb.aabbEnlargeFactor`). Contact cross-section """"""""""""""""""""" Some constitutive laws are formulated with strains and stresses (:yref:`Law2_ScGeom_CpmPhys_Cpm`, the concrete model described later, for instance); in that case, equivalent cross-section of the contact must be introduced for the sake of dimensionality. The exact definition is rather arbitrary; the CPM model (:yref:`Ip2_CpmMat_CpmMat_CpmPhys`) uses the relation .. math:: A_{\rm eq}=\pi\min(r_1,r_2)^2 :label: eq-strain-crosssection which will be used to convert stresses to forces, if the constitutive law used is formulated in terms of stresses and strains. Note that other values than $\pi$ can be used; it will merely scale macroscopic packing stiffness; it is only for the intuitive notion of a truss-like element between the particle centers that we choose $A_{\rm eq}$ representing the circle area. Besides that, another function than $\min(r_1,r_2)$ can be used, although the result should depend linearly on $r_1$ and $r_2$ so that the equation gives consistent results if the particle dimensions are scaled. Variables ^^^^^^^^^^^ The following state variables are updated as spheres undergo motion during the simulation (as $\currC_1$ and $\currC_2$ change): .. math:: :label: eq-contact-normal \currn=\frac{\currC_2-\currC_1}{|\currC_2-\currC_1|}\equiv \normalized{\currC_2-\currC_1} and .. math:: :label: eq-contact-point \currC=\currC_1+\left(d_1-\frac{d_0-|\currC_2-\currC_1|}{2}\right)\vec{n}. The contact point $\currC$ is always in the middle of the spheres' overlap zone (even if the overlap is negative, when it is in the middle of the empty space between the spheres). The *contact plane* is always perpendicular to the contact plane normal $\currn$ and passes through $\currC$. Normal displacement and strain can be defined as .. math:: :nowrap: \begin{align*} u_N&=|\currC_2-\currC_1|-d_0, \\ \eps_N&=\frac{u_N}{d_0}=\frac{|\currC_2-\currC_1|}{d_0}-1. \end{align*} Since $u_N$ is always aligned with $\vec{n}$, it can be stored as a scalar value multiplied by $\vec{n}$ if necessary. For massively compressive simulations, it might be beneficial to use the logarithmic strain, such that the strain tends to $-\infty$ (rather than $-1$) as centers of both spheres approach. Otherwise, repulsive force would remain finite and the spheres could penetrate through each other. Therefore, we can adjust the definition of normal strain as follows: .. math:: \eps_N = \begin{cases} \log \left(\frac{|\currC_2-\currC_1|}{d_0}\right)& \hbox{if } |\currC_2-\currC_1|` associated with both particles. In the simple case of frictional material :yref:`FrictMat`, :yref:`Ip2_FrictMat_FrictMat_FrictPhys` creates a new :yref:`FrictPhys` instance, which defines normal stiffness $K_N$, shear stiffness $K_T$ and friction angle $\phi$. At each step, given normal and shear displacements $u_N$, $\uT$, normal and shear forces are computed (if $u_N>0$, the contact is deleted without generating any forces): .. math:: :nowrap: \begin{align*} \vec{F}_N&=K_N u_N \vec{n},\\ \vec{F}_T^t&=K_T \vec{u}_T \end{align*} where $\vec{F}_N$ is normal force and $\vec{F}_T$ is trial shear force. A simple non-associated stress return algorithm is applied to compute final shear force .. math:: :nowrap: \begin{align*} \vec{F}_T=\begin{cases} \vec{F}_T^t\frac{|\vec{F}_N|\tan\phi}{\vec{F}_T^t} & \hbox{if } |\vec{F}_T|>|\vec{F}_N|\tan\phi, \\ \vec{F}_T^t & \hbox{otherwise.} \end{cases} \end{align*} Summary force $\vec{F}=\vec{F}_N+\vec{F}_T$ is then applied to both particles -- each particle accumulates forces and torques acting on it in the course of each step. Because the force computed acts at contact point $\vec{C}$, which is difference from spheres' centers, torque generated by $\vec{F}$ must also be considered. .. math:: :nowrap: \begin{align*} \vec{F}_1&+=\vec{F} & \vec{F}_2&+=-\vec{F} \\ \vec{T}_1&+=d_1(-\vec{n})\times \vec{F} & \vec{T}_2&+=d_2 \vec{n} \times \vec{F}. \end{align*} Motion integration ================== Each particle accumulates generalized forces (forces and torques) from the contacts in which it participates. These generalized forces are then used to integrate motion equations for each particle separately; therefore, we omit $i$ indices denoting the $i$-th particle in this section. The customary leapfrog scheme (also known as the Verlet scheme) is used, with some adjustments for rotation of non-spherical particles, as explained below. The "leapfrog" name comes from the fact that even derivatives of position/orientation are known at on-step points, whereas odd derivatives are known at mid-step points. Let us recall that we use $\prev{a}$, $\curr{a}$, $\next{a}$ for on-step values of $a$ at $t-\Dt$, $t$ and $t+\Dt$ respectively; and $\pprev{a}$, $\nnext{a}$ for mid-step values of $a$ at $t-\Dt/2$, $t+\Dt/2$. Described integration algorithms are implemented in the :yref:`NewtonIntegrator` class in Yade. Position ---------- Integrating motion consists in using current acceleration $\curraccel$ on a particle to update its position from the current value $\currpos$ to its value at the next timestep $\nextpos$. Computation of acceleration, knowing current forces $\vec{F}$ acting on the particle in question and its mass $m$, is simply .. math:: \curraccel&=\vec{F}/m. Using the 2nd order finite difference with step $\Dt$, we obtain .. math:: \curraccel&\cong\frac{\prevpos-2\currpos+\nextpos}{\Dt^2} from which we express .. math:: \nextpos&=2\currpos-\prevpos+\curraccel\Dt^2 =\\ &=\currpos+\Dt\underbrace{\left(\frac{\currpos-\prevpos}{\Dt}+\curraccel\Dt\right)}_{(\dagger)}. Typically, $\prevpos$ is already not known (only $\currpos$ is); we notice, however, that .. math:: \pprevvel&\simeq\frac{\currpos-\prevpos}{\Dt}, i.e. the mean velocity during the previous step, which is known. Plugging this approximate into the $(\dagger)$ term, we also notice that mean velocity during the current step can be approximated as .. math:: \nnextvel&\simeq\pprevvel+\curraccel\Dt, which is $(\dagger)$; we arrive finally at .. math:: \nextpos&=\currpos+\Dt\left(\pprevvel+\curraccel\Dt\right). The algorithm can then be written down by first computing current mean velocity $\nnextvel$ which we need to store for the next step (just as we use its old value $\pprevvel$ now), then computing the position for the next time step $\nextpos$: .. math:: :label: eq-leapfrog-nextvel :nowrap: \begin{align*} \nnextvel&=\pprevvel+\curraccel\Dt \\ \nextpos&=\currpos+\nnextvel\Dt. \end{align*} Positions are known at times $i\Delta t$ (if $\Delta t$ is constant) while velocities are known at $i\Delta t+\frac{\Delta t}{2}$. The facet that they interleave (jump over each other) in such way gave rise to the colloquial name "leapfrog" scheme. Orientation (spherical) ------------------------ Updating particle orientation $\curr{q}$ proceeds in an analogous way to position update. First, we compute current angular acceleration $\curraaccel$ from known current torque $\vec{T}$. For spherical particles where the inertia tensor is diagonal in any orientation (therefore also in current global orientation), satisfying $\vec{I}_{11}=\vec{I}_{22}=\vec{I}_{33}$, we can write .. math:: \curraaccel_i&=\vec{T}_i/\vec{I}_{11}, We use the same approximation scheme, obtaining an equation analogous to :eq:`eq-leapfrog-nnextvel` .. math:: \nnextangvel&=\pprevangvel+\Dt\curraaccel. The quaternion $\Delta q$ representing rotation vector $\nnextangvel\Dt$ is constructed, i.e. such that .. math:: :nowrap: \begin{align*} (\Delta q)_{\theta}&=|\nnextangvel|, \\ (\Delta q)_{\vec{u}}&=\normalized{\nnextangvel} \end{align*} Finally, we compute the next orientation $\next{q}$ by rotation composition .. math:: \next{q}&=\Delta q\curr{q}. Orientation (aspherical) ------------------------ Integrating rotation of aspherical particles is considerably more complicated than their position, as their local reference frame is not inertial. Rotation of rigid body in the local frame, where inertia matrix $\mat{I}$ is diagonal, is described in the continuous form by Euler's equations ($i\in\{1,2,3\}$ and $i$, $j$, $k$ are subsequent indices): .. math:: \vec{T}_i=\mat{I}_{ii}\dot{\vec{\omega}}_i+(\mat{I}_{kk}-\mat{I}_{jj})\vec{\omega}_j\vec{\omega}_k. Due to the presence of the current values of both $\vec{\omega}$ and $\dot{\vec{\omega}}$, they cannot be solved using the standard leapfrog algorithm (that was the case for translational motion and also for the spherical bodies' rotation where this equation reduced to $\vec{T}=\mat{I}\dot{\vec{\omega}}$). The algorithm presented here is described by [Allen1989]_ (pg. 84--89) and was designed by Fincham for molecular dynamics problems; it is based on extending the leapfrog algorithm by mid-step/on-step estimators of quantities known at on-step/mid-step points in the basic formulation. Although it has received criticism and more precise algorithms are known ([Omelyan1999]_, [Neto2006]_, [Johnson2008]_), this one is currently implemented in Yade for its relative simplicity. .. Finchman: Leapfrog Rotational Algorithms: http://www.informaworld.com/smpp/content~content=a756872469&db=all Schvanberg: Leapfrog Rotational Algorithms: http://www.informaworld.com/smpp/content~content=a914299295&db=all Each body has its local coordinate system based on the principal axes of inertia for that body. We use $\locframe{\bullet}$ to denote vectors in local coordinates. The orientation of the local system is given by the current particle's orientation $\curr{q}$ as a quaternion; this quaternion can be expressed as the (current) rotation matrix $\mat{A}$. Therefore, every vector $\vec{a}$ is transformed as $\locframe{\vec{a}}=q\vec{a}q^{*}=\mat{A}\vec{a}$. Since $\mat{A}$ is a rotation (orthogonal) matrix, the inverse rotation $\mat{A}^{-1}=\mat{A}^{T}$. For given particle in question, we know * $\loccurr{\mat{I}}$ (constant) inertia matrix; diagonal, since in local, principal coordinates, * $\curr{\vec{T}}$ external torque, * $\curr{q}$ current orientation (and its equivalent rotation matrix $\mat{A}$), * $\pprev{\vec{\omega}}$ mid-step angular velocity, * $\pprev{\vec{L}}$ mid-step angular momentum; this is an auxiliary variable that must be tracked in addition for use in this algorithm. It will be zero in the initial step. Our goal is to compute new values of the latter three, that is $\nnext{\vec{L}}$, $\next{q}$, $\nnext{\vec{\omega}}$. We first estimate current angular momentum and compute current local angular velocity: .. math:: :nowrap: \begin{align*} \curr{\vec{L}}&=\pprev{\vec{L}}+\curr{\vec{T}}\frac{\Dt}{2}, &\loccurr{\vec{L}}&=\mat{A}\curr{\vec{L}}, \\ \nnext{\vec{L}}&=\pprev{\vec{L}}+\curr{\vec{T}}\Dt, &\nnext{\locframe{\vec{L}}}&=\mat{A}\nnext{\vec{L}}, \\ \loccurr{\vec{\omega}}&=\curr{\locframe{\mat{I}}}{}^{-1}\loccurr{\vec{L}}, \\ \nnext{\locframe{\vec{\omega}}}&=\curr{\locframe{\mat{I}}}{}^{-1}\nnext{\locframe{\vec{L}}}. \\ \end{align*} Then we compute $\curr{\dot{q}}$, using $\curr{q}$ and $\loccurr{\vec{\omega}}$: .. math:: :label: eq-quaternion-derivative :nowrap: \begin{align*} \begin{pmatrix}\curr{\dot{q}}_w \\ \curr{\dot{q}}_x \\ \curr{\dot{q}}_y \\ \curr{\dot{q}}_z\end{pmatrix}&= \def\cq{\curr{q}} \frac{1}{2}\begin{pmatrix} \cq_w & -\cq_x & -\cq_y & -\cq_z \\ \cq_x & \cq_w & -\cq_z & \cq_y \\ \cq_y & \cq_z & \cq_w & -\cq_x \\ \cq_z & -\cq_y & \cq_x & \cq_w \end{pmatrix} \begin{pmatrix} 0 \\ \loccurr{\vec{\omega}}_x \\ \loccurr{\vec{\omega}}_y \\ \loccurr{\vec{\omega}}_z \end{pmatrix}, \\ \nnext{q}&=\curr{q}+\curr{\dot{q}}\frac{\Dt}{2}.\\ \end{align*} We evaluate $\nnext{\dot{q}}$ from $\nnext{q}$ and $\nnext{\locframe{\vec{\omega}}}$ in the same way as in :eq:`eq-quaternion-derivative` but shifted by $\Dt/2$ ahead. Then we can finally compute the desired values .. math:: :nowrap: \begin{align*} \next{q}&=\curr{q}+\nnext{\dot{q}}\Dt, \\ \nnext{\vec{\omega}}&=\mat{A}^{-1}\nnext{\locframe{\vec{\omega}}} \end{align*} Clumps (rigid aggregates) ------------------------- DEM simulations frequently make use of rigid aggregates of particles to model complex shapes [Price2007]_ called *clumps*, typically composed of many spheres. Dynamic properties of clumps are computed from the properties of its members: * For non-overlapping clump members the clump's mass $m_c$ is summed over members, the inertia tensor $\mathbf{I}_c$ is computed using the parallel axes theorem: $\mathbf{I}_c = \sum_i( m_i*d_i^2 + I_i)$, where $m_i$ is the mass of clump member $i$, $d_i$ is the distance from center of clump member $i$ to clump's centroid and $I_i$ is the inertia tensor of the clump member $i$. * For overlapping clump members the clump's mass $m_c$ is summed over cells using a regular grid spacing inside axis-aligned bounding box (:yref:`Aabb`) of the clump, the inertia tensor is computed using the parallel axes theorem: $\mathbf{I}_c = \sum_j( m_j*d_j^2 + I_j)$, where $m_j$ is the mass of cell $j$, $d_j$ is the distance from cell center to clump's centroid and $I_j$ is the inertia tensor of the cell $j$. Local axes are oriented such that they are principal and inertia tensor is diagonal and clump's orientation is changed to compensate rotation of the local system, as to not change the clump members' positions in global space. Initial positions and orientations of all clump members in local coordinate system are stored. In Yade (class :yref:`Clump`), clump members behave as stand-alone particles during simulation for purposes of collision detection and contact resolution, except that they have no contacts created among themselves within one clump. It is at the stage of motion integration that they are treated specially. Instead of integrating each of them separately, forces/torques on those particles $\vec{F}_i$, $\vec{T}_i$ are converted to forces/torques on the clump itself. Let us denote $r_i$ relative position of each particle with regards to clump's centroid, in global orientation. Then summary force and torque on the clump are .. math:: :nowrap: \begin{align*} \vec{F}_c&=\sum F_i, \\ \vec{T}_c&=\sum r_i\times F_i + T_i. \end{align*} Motion of the clump is then integrated, using aspherical rotation integration. Afterwards, clump members are displaced in global space, to keep their initial positions and orientations in the clump's local coordinate system. In such a way, relative positions of clump members are always the same, resulting in the behavior of a rigid aggregate. Numerical damping ----------------- In simulations of quasi-static phenomena, it it desirable to dissipate kinetic energy of particles. Since most constitutive laws (including :yref:`Law_ScGeom_FrictPhys_Basic` shown above, :ref:`sect-formulation-stress-cundall`) do not include velocity-based damping (such as one in [Addetta2001]_), it is possible to use artificial numerical damping. The formulation is described in [Pfc3dManual30]_, although our version is slightly adapted. The basic idea is to decrease forces which increase the particle velocities and vice versa by $(\Delta F)_d$, comparing the current acceleration sense and particle velocity sense. This is done by component, which makes the damping scheme clearly non-physical, as it is not invariant with respect to coordinate system rotation; on the other hand, it is very easy to compute. Cundall proposed the form (we omit particle indices $i$ since it applies to all of them separately): .. math:: \frac{(\Delta \vec{F})_{dw}}{\vec{F}_w}=-\lambda_d\sign(\vec{F}_w\pprev{\dot{\vec{u}}}_{w}),\quad w\in\{x,y,z\} where $\lambda_d$ is the damping coefficient. This formulation has several advantages [Hentz2003]_: * it acts on forces (accelerations), not constraining uniform motion; * it is independent of eigenfrequencies of particles, they will be all damped equally; * it needs only the dimensionless parameter $\lambda_d$ which does not have to be scaled. In Yade, we use the adapted form .. math:: :label: eq-damping-yade \frac{(\Delta\vec{F})_{dw}}{\vec{F}_w}=-\lambda_d\sign\vec{F}_w\underbrace{\left(\pprev{\dot{u}}_w+\frac{\curr{\ddot{\vec{u}}}_w\Dt}{2}\right)}_{\simeq\curr{\dot{u}}_w}, where we replaced the previous mid-step velocity $\pprev{\dot{u}}$ by its on-step estimate in parentheses. This is to avoid locked-in forces that appear if the velocity changes its sign due to force application at each step, i.e. when the particle in question oscillates around the position of equilibrium with $2\Dt$ period. In Yade, damping :eq:`eq-damping-yade` is implemented in the :yref:`NewtonIntegrator` engine; the damping coefficient $\lambda_d$ is :yref:`NewtonIntegrator.damping`. .. _sect-formulation-dt: Stability considerations ------------------------------------------------------ .. \def\Dtcr{\Dt_{\rm cr}} % http://en.wikipedia.org/wiki/Harmonic_oscillator % eigenfrequency is (1/2π)√(k/m) for simple harmonic oscillator % Bruno gives √(m/k) % http://imechanica.org/node/7670#comment-13672: Δt_crit=2/ω_max The leapfrog integration scheme is conditionally stable, i.e. not magnifying errors, provided $\Dt<\Dtcr$ where $\Dtcr$ is the *critical timestep*, above which the integration is unstable. Usually, $\Dt$ is taken as a fraction of $\Dtcr$; this fraction is called the *timestep safety factor*, with meaningful values $\in\langle 0,1)$. Critical timestep ^^^^^^^^^^^^^^^^^ In order to ensure stability for the explicit integration sceheme, an upper limit is imposed on $\Dt$: .. math:: :label: eq-dt-angular \Dtcr=\frac{2}{\omega_{\rm max}} where $\omega_{\rm max}$ is the highest eigenfrequency within the system. Single mass-spring system """""""""""""""""""""""""" Single 1D mass-spring system with mass $m$ and stiffness $K$ is governed by the equation .. math:: m\ddot{x}=-Kx where $x$ is displacement from the mean (equilibrium) position. The solution of harmonic oscillation is $x(t)=A\cos(\omega t+\phi)$ where phase $\phi$ and amplitude $A$ are determined by initial conditions. The angular frequency .. math:: :label: eq-dt-omega \omega^{(1)}=\sqrt{\frac{K}{m}} does not depend on initial conditions. Since there is one single mass, $\omega_{\rm max}^{(1)}=\omega^{(1)}$. Plugging :eq:`eq-dt-omega` into :eq:`eq-dt-angular`, we obtain .. math:: \Dtcr^{(1)}=2/\omega_{\rm max}^{(1)}=2\sqrt{m/K} for a single oscillator. General mass-spring system """""""""""""""""""""""""" In a general mass-spring system, the highest frequency occurs if two connected masses $m_i$, $m_j$ are in opposite motion; let us suppose they have equal velocities (which is conservative) and they are connected by a spring with stiffness $K_{i}$: displacement $\Delta x_i$ of $m_i$ will be accompained by $\Delta x_j=-\Delta x_i$ of $m_j$, giving $\Delta F_i=-K_{i}(\Delta x_i-(-\Delta x_i))=-2K_{i}\Delta x_i$. That results in apparent stiffness $K_{i}^{(2)}=2K_{i}$, giving maximum eigenfrequency of the whole system .. math:: \omega_{\rm max}=\max_i\sqrt{K_i^{(2)}/m_i}. The overall critical timestep is then .. math:: :label: eq-dtcr-global \Dtcr=\frac{2}{\omega_{\rm max}}=\min_i\, 2\sqrt{\frac{m_i}{K_i^{(2)}}}=\min_i\, 2\sqrt{\frac{m_i}{2K_i}}=\min_i \sqrt{2}\sqrt{\frac{m_i}{K_i}}. This equation can be used for all 6 degrees of freedom (DOF) in translation and rotation, by considering generalized mass and stiffness matrices $M$ and $K$, and replacing fractions $\frac{m_i}{K_i}$ by eigen values of $M.K^{-1}$. The critical timestep is then associated to the eigen mode with highest frequency : .. math:: :label: eq-dtcr-axes \Dtcr=\min {\Dtcr}_k,\quad k\in\{1,...,6\}. DEM simulations """""""""""""""" In DEM simulations, per-particle stiffness $\vec{K}_{ij}$ is determined from the stiffnesses of contacts in which it participates. Suppose each contact has normal stiffness $K_{Nk}$, shear stiffness $K_{Tk}=\xi K_{Nk}$ and is oriented by normal $\vec{n}_{k}$. A translational stiffness matrix $\vec{K}_{ij}$ can be defined as the sum of contributions of all contacts in which it participates (indices $k$), as [Chareyre2005]_. .. math:: :label: eq-dtcr-particle-stiffness \vec{K}_{ij}=\sum_k (K_{Nk}-K_{Tk})\vec{n}_{i}\vec{n}_{j}+K_{Tk}=\sum_j K_{Nk}\left((1-\xi)\vec{n}_{i}\vec{n}_{j}+\xi\right) with $i$ and $j\in\{x,y,z\}$. Equations :eq:`eq-dtcr-axes` and :eq:`eq-dtcr-particle-stiffness` determine $\Dtcr$ in a simulation. A similar approach generalized to all 6 DOFs is implemented by the :yref:`GlobalStiffnessTimeStepper` engine in Yade. The derivation of generalized stiffness including rotational terms is very similar and can be found in [AboulHosn2016]_. Note that for computation efficiency reasons, eigenvalues of the stiffness matrices are not computed. They are only approximated assuming than DOF's are uncoupled, and using the diagonal terms of $K.M^{-1}$. They give good approximates in typical mechanical systems. There is one important condition that $\omega_{\rm max}>0$: if there are no contacts between particles and $\omega_{\rm max}=0$, we would obtain value $\Dtcr=\infty$. While formally correct, this value is numerically erroneous: we were silently supposing that stiffness remains constant during each timestep, which is not true if contacts are created as particles collide. In case of no contact, therefore, stiffness must be pre-estimated based on future interactions, as shown in the next section. .. _sect-dt-pwave: Estimation of $\Dtcr$ by wave propagation speed ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Estimating timestep in absence of interactions is based on the connection between interaction stiffnesses and the particle's properties. Note that in this section, symbols $E$ and $\rho$ refer exceptionally to Young's modulus and density of *particles*, not of macroscopic arrangement. In Yade, particles have associated :yref:`Material` which defines density $\rho$ (:yref:`Material.density`), and also may define (in :yref:`ElastMat` and derived classes) particle's "Young's modulus" $E$ (:yref:`ElastMat.young`). $\rho$ is used when particle's mass $m$ is initially computed from its $\rho$, while $E$ is taken in account when creating new interaction between particles, affecting stiffness $K_N$. Knowing $m$ and $K_N$, we can estimate :eq:`eq-dtcr-particle-stiffness` for each particle; we obviously neglect * number of interactions per particle $N_i$; for a "reasonable" radius distribution, however, there is a geometrically imposed upper limit (6 for a 2D-packing of spheres with equal radii, for instance); * the exact relationship the between particles' rigidities $E_i$, $E_j$, supposing only that $K_N$ is somehow proportional to them. By defining $E$ and $\rho$, particles have continuum-like quantities. Explicit integration schemes for continuum equations impose a critical timestep based on sonic speed $\sqrt{E/\rho}$; the elastic wave must not propagate farther than the minimum distance of integration points $l_{\rm min}$ during one step. Since $E$, $\rho$ are parameters of the elastic continuum and $l_{\rm min}$ is fixed beforehand, we obtain .. math:: \Dtcr^{(c)}=l_{\rm min}\sqrt{\frac{\rho}{E}}. For our purposes, we define $E$ and $\rho$ for each particle separately; $l_{\rm min}$ can be replaced by the sphere's radius $R_i$; technically, $l_{\rm min}=2R_i$ could be used, but because of possible interactions of spheres and facets (which have zero thickness), we consider $l_{\rm min}=R_i$ instead. Then .. FIXME Why not by sphere's diameter instead? We could say it is because :yref:`Sphere`-:yref:`Facet` interaction which has half length? .. math:: \Dtcr^{(p)}=\min_i R_i \sqrt{\frac{\rho_i}{E_i}}. This algorithm is implemented in the :yref:`yade.utils.PWaveTimeStep` function. Let us compare this result to :eq:`eq-dtcr-global`; this necessitates making several simplifying hypotheses: * all particles are spherical and have the same radius $R$; * the sphere's material has the same $E$ and $\rho$; * the average number of contacts per sphere is $N$; * the contacts have sufficiently uniform spatial distribution around each particle; * the $\xi=K_N/K_T$ ratio is constant for all interactions; * contact stiffness $K_N$ is computed from $E$ using a formula of the form .. math:: :label: eq-dt-kn K_N=E\pi'R', where $\pi'$ is some constant depending on the algorithm in use\footnote{For example, $\pi'=\pi/2$ in the concrete particle model (:yref:`Ip2_CpmMat_CpmMat_CpmPhys`), while $\pi'=2$ in the classical DEM model (:yref:`Ip2_FrictMat_FrictMat_FrictPhys`) as implemented in Yade.} and $R'$ is half-distance between spheres in contact, equal to $R$ for the case of interaction radius $R_I=1$. If $R_I=1$ (and $R'\equiv R$ by consequence), all interactions will have the same stiffness $K_N$. In other cases, we will consider $K_N$ as the average stiffness computed from average $R'$ (see below). As all particles have the same parameters, we drop the $i$ index in the following formulas. We try to express the average per-particle stiffness from :eq:`eq-dtcr-particle-stiffness`. It is a sum over all interactions where $K_{N}$ and $\xi$ are scalars that will not rotate with interaction, while $\vec{n}_w$ is $w$-th component of unit interaction normal $\vec{n}$. Since we supposed uniform spatial distribution, we can replace $\vec{n}_w^2$ by its average value $\overline{\vec{n}}_w^2$. Recognizing components of $\vec{n}$ as direction cosines, the average values of $\vec{n}_w^2$ is $1/3$. We find the average value by integrating over all possible orientations, which are uniformly distributed in space: Moreover, since all directions are equal, we can write the per-body stiffness as $K=\vec{K}_w$ for all $w\in\{x,y,z\}$. We obtain .. math:: K=\sum K_N\left((1-\xi)\frac{1}{3}+\xi\right)=\sum K_N\frac{1+2\xi}{3} and can put constant terms (everything) in front of the summation. $\sum 1$ equals the number of contacts per sphere, i.e. $N$. Arriving at .. math:: K=N K_N \frac{1-2\xi}{3}, we substitute $K$ into :eq:`eq-dtcr-global` using :eq:`eq-dt-kn`: .. math:: \Dtcr=\sqrt{2}\sqrt{\frac{m}{K}}=\sqrt{2}\sqrt{\frac{\frac{4}{3}\pi R^3\rho}{N E\pi'R\frac{1-2\xi}{3}}}=\underbrace{R\sqrt{\frac{\rho}{E}}}_{\Dtcr^{(p)}}2\sqrt{\frac{\pi/\pi'}{N(1-2\xi)}}. The ratio of timestep $\Dtcr^{(p)}$ predicted by the p-wave velocity and numerically stable timestep $\Dtcr$ is the inverse value of the last (dimensionless) term: .. math:: \frac{\Dtcr^{(p)}}{\Dtcr}=2\sqrt{\frac{N(1+\xi)}{\pi/\pi'}}. Actual values of this ratio depend on characteristics of packing $N$, $K_N/K_T=\xi$ ratio and the way of computing contact stiffness from particle rigidity. Let us show it for two models in Yade: Concrete particle model computes contact stiffness from the equivalent area $A_{\rm eq}$ first :eq:`eq-strain-crosssection`, .. math:: :nowrap: \begin{align*} A_{\rm eq}&=\pi R^2 K_N&=\frac{A_{\rm eq}E}{d_0}. \end{align*} $d_0$ is the initial contact length, which will be, for interaction radius :eq:`eq-strain-interaction-radius` $R_I>1$, in average larger than $2R$. For $R_I=1.5$ ,we can roughly estimate $\overline{d}_0=1.25\cdot2R=\frac{5}{2}R$, getting .. math:: K_N=E\left(\frac{2}{5}\pi\right)R where $\frac{2}{5}\pi=\pi'$ by comparison with :eq:`eq-dt-kn`. Interaction radius $R_I=1.5$ leads to average $N\approx12$ interactions per sphere for dense packing of spheres with the same radius $R$. $\xi=0.2$ is calibrated to match the desired macroscopic Poisson's ratio $\nu=0.2$. Finally, we obtain the ratio .. math:: \frac{\Dtcr^{(p)}}{\Dtcr}=2\sqrt{\frac{12(1-2\cdot0.2)}{\frac{\pi}{(2/5)\pi}}}=3.39, showing significant overestimation by the p-wave algorithm. Non-cohesive dry friction model is the basic model proposed by Cundall explained in :ref:`sect-formulation-stress-cundall`. Supposing almost-constant sphere radius $R$ and rather dense packing, each sphere will have $N=6$ interactions on average (that corresponds to maximally dense packing of spheres with a constant radius). If we use the :yref:`Ip2_FrictMat_FrictMat_FrictPhys` class, we have $\pi'=2$, as $K_N=E2R$; we again use $\xi=0.2$ (for lack of a more significant value). In this case, we obtain the result .. math:: \frac{\Dtcr^{(p)}}{\Dtcr}=2\sqrt{\frac{6(1-2\cdot0.2)}{\pi/2}}=3.02 which again overestimates the numerical critical timestep. To conclude, p-wave timestep gives estimate proportional to the real $\Dtcr$, but in the cases shown, the value of about $\Dt=0.3\Dtcr^{(p)}$ should be used to guarantee stable simulation. Non-elastic $\Dt$ constraints ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Let us note at this place that not only $\Dtcr$ assuring numerical stability of motion integration is a constraint. In systems where particles move at relatively high velocities, position change during one timestep can lead to non-elastic irreversible effects such as damage. The $\Dt$ needed for reasonable result can be lower $\Dtcr$. We have no rigorously derived rules for such cases. .. http://books.google.cz/books?id=_KTsjCZtt_EC&lpg=PP1&ots=nK0B4wui2F&dq=stability%20analysis&pg=PR5#v=onepage&q&f=false .. The leap-frog integration scheme assumes constant $\Dt$. Although this is typically the case, and even dynamic time-stepping techniques such as :yref:`GlobalStiffnessTimeStepper` change $\Dt$ infrequently, big changes in timestep could destabilize the integration [Skeel1993]_. Periodic boundary conditions ============================ While most DEM simulations happen in $R^3$ space, it is frequently useful to avoid boundary effects by using periodic space instead. In order to satisfy periodicity conditions, periodic space is created by repetition of parallelepiped-shaped cell. In Yade, periodic space is implemented in the :yref:`Cell` class. The geometry of the cell in the reference coordinates system is defined by three edges of the parallepiped. The corresponding base vectors are stored in the columns of matrix $\mat{H}$ (:yref:`Cell.hSize`). The initial $\mat{H}$ can be explicitly defined as a 3x3 matrix at the beginning of the simulation. There are no restricitions on the possible shapes: any parallelepiped is accepted as the initial cell. If the base vectors are axis-aligned, defining only their sizes can be more convenient than defining the full $\mat{H}$ matrix; in that case it is enough to define the norms of columns in $\mat{H}$ (see :yref:`Cell.size`). After the definition of the initial cell's geometry, $\mat{H}$ should generally not be modified by direct assignment. Instead, its deformation rate will be defined via the velocity gradient :yref:`Cell.velGrad` described below. It is the only variable that let the period deformation be correctly accounted for in constitutive laws and Newton integrator (:yref:`NewtonIntegrator`). Deformations handling --------------------- The deformation of the cell over time is defined via a tensor representing the gradient of an homogeneous velocity field $\nabla \vec{v}$ (:yref:`Cell.velGrad`). This gradient represents arbitrary combinations of rotations and stretches. It can be imposed externaly or updated by :yref:`boundary controllers ` (see :yref:`PeriTriaxController` or :yref:`Peri3dController`) in order to reach target strain values or to maintain some prescribed stress. The velocity gradient is integrated automatically over time, and the cumulated transformation is reflected in the transformation matrix $\mat{F}$ (:yref:`Cell.trsf`) and the current shape of the cell $\mat{H}$. The per-step transformation update reads (it is similar for $\mat{H}$), with $I$ the identity matrix: .. math:: \next{\mat{F}}=(I+\nabla \vec{v} \Dt)\curr{\mat{F}}. $\mat{F}$ can be set back to identity at any point in simulations, in order to define the current state as reference for strains definition in boundary controllers. It will have no effect on $\mat{H}$. Along with the automatic integration of cell transformation, there is an option to homothetically displace all particles so that $\nabla \vec{v}$ is applied over the whole simulation (enabled via :yref:`Cell.homoDeform`). This avoids all boundary effects coming from change of the velocity gradient. Collision detection in periodic cell ------------------------------------ In usual implementations, particle positions are forced to be inside the cell by wrapping their positions if they get over the boundary (so that they appear on the other side). As we wanted to avoid abrupt changes of position (it would make particle's velocity inconsistent with step displacement change), a different method was chosen. .. _sect-cell-approx-collision: Approximate collision detection ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Pass 1 collision detection (based on sweep and prune algorithm, sect. :ref:`sect-sweep-and-prune`) operates on axis-aligned bounding boxes (:yref:`Aabb`) of particles. During the collision detection phase, bounds of all :yref:`Aabb's` are wrapped inside the cell in the first step. At subsequent runs, every bound remembers by how many cells it was initially shifted from coordinate given by the :yref:`Aabb` and uses this offset repeatedly as it is being updated from :yref:`Aabb` during particle's motion. Bounds are sorted using the periodic insertion sort algorithm (sect. :ref:`sect-periodic-insertion-sort`), which tracks periodic cell boundary $||$. Upon inversion of two :yref:`Aabb`'s, their collision along all three axes is checked, wrapping real coordinates inside the cell for that purpose. This algorithm detects collisions as if all particles were inside the cell but without the need of constructing "ghost particles" (to represent periodic image of a particle which enters the cell from the other side) or changing the particle's positions. It is required by the implementation (and partly by the algorithm itself) that particles do not span more than half of the current cell size along any axis; the reason is that otherwise two (or more) contacts between both particles could appear, on each side. Since Yade identifies contacts by :yref:`Body.id` of both bodies, they would not be distinguishable. In presence of shear, the sweep-and-prune collider could not sort bounds independently along three axes: collision along $x$ axis depends on the mutual position of particles on the $y$ axis. Therefore, bounding boxes *are expressed in transformed coordinates* which are perpendicular in the sense of collision detection. This requires some extra computation: :yref:`Aabb` of sphere in transformed coordinates will no longer be cube, but cuboid, as the sphere itself will appear as ellipsoid after transformation. Inversely, the sphere in simulation space will have a parallelepiped bounding "box", which is cuboid around the ellipsoid in transformed axes (the :yref:`Aabb` has axes aligned with transformed cell basis). This is shown in fig. `fig-cell-shear-aabb`_. The restriction of a single particle not spanning more than half of the transformed axis becomes stringent as :yref:`Aabb` is enlarged due to shear. Considering :yref:`Aabb` of a sphere with radius $r$ in the cell where $x'\equiv x$, $z'\equiv z$, but $\angle(y,y')=\phi$, the $x$-span of the :yref:`Aabb` will be multiplied by $1/\cos\phi$. For the infinite shear $\phi\to\pi/2$, which can be desirable to simulate, we have $1/\cos\phi \to \infty$. Fortunately, this limitation can be easily circumvented by realizing the quasi-identity of all periodic cells which, if repeated in space, create the same grid with their corners: the periodic cell can be flipped, keeping all particle interactions intact, as shown in fig. `fig-cell-flip`_. It only necessitates adjusting the :yref:`Interaction.cellDist` of interactions and re-initialization of the collider (``Collider::invalidatePersistentData``). Cell flipping is implemented in the :yref:`yade.utils.flipCell` function. .. _fig-cell-flip: .. figure:: fig/cell-flip.* Flipping cell (:yref:`yade.utils.flipCell`) to avoid infinite stretch of the bounding boxes' spans with growing $\phi$. Cell flip does not affect interactions from the point of view of the simulation. The periodic arrangement on the left is the same as the one on the right, only the cell is situated differently between identical grid points of repetition; at the same time $|\phi_2|<|\phi_1|$ and sphere bounding box's $x$-span stretched by $1/\cos\phi$ becomes smaller. Flipping can be repeated, making effective infinite shear possible. This algorithm is implemented in :yref:`InsertionSortCollider` and is used whenever simulation is periodic (:yref:`Omega.isPeriodic`); individual :yref:`BoundFunctor's` are responsible for computing sheared :yref:`Aabb's`; currently it is implemented for spheres and facets (in :yref:`Bo1_Sphere_Aabb` and :yref:`Bo1_Facet_Aabb` respectively). .. _fig-cell-shear-aabb: .. figure:: fig/cell-shear-aabb.pdf Constructing axis-aligned bounding box (:yref:`Aabb`) of a sphere in simulation space coordinates (without periodic cell -- left) and transformed cell coordinates (right), where collision detection axes $x'$, $y'$ are not identical with simulation space axes $x$, $y$. Bounds' projection to axes is shown by orange lines. Exact collision detection ^^^^^^^^^^^^^^^^^^^^^^^^^ When the collider detects approximate contact (on the :yref:`Aabb` level) and the contact does not yet exist, it creates *potential* contact, which is subsequently checked by exact collision algorithms (depending on the combination of :yref:`Shapes`). Since particles can interact over many periodic cells (recall we never change their positions in simulation space), the collider embeds the relative cell coordinate of particles in the interaction itself (:yref:`Interaction.cellDist`) as an *integer* vector $c$. Multiplying current cell size $\mat{T}\vec{s}$ by $c$ component-wise, we obtain particle offset $\Delta \vec{x}$ in aperiodic $R^3$; this value is passed (from :yref:`InteractionLoop`) to the functor computing exact collision (:yref:`IGeomFunctor`), which adds it to the position of the particle :yref:`Interaction.id2`. By storing the integral offset $c$, $\Delta\vec{x}$ automatically updates as cell parameters change. .. _sect-periodic-insertion-sort: Periodic insertion sort algorithm ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ The extension of sweep and prune algorithm (described in :ref:`sect-sweep-and-prune`) to periodic boundary conditions is non-trivial. Its cornerstone is a periodic variant of the insertion sort algorithm, which involves keeping track of the "period" of each boundary; e.g. taking period $\langle 0,10)$, then $8_1\equiv-2_2<2_2$ (subscript indicating period). Doing so efficiently (without shuffling data in memory around as bound wraps from one period to another) requires moving period boundary rather than bounds themselves and making the comparison work transparently at the edge of the container. This algorithm was also extended to handle non-orthogonal periodic :yref:`Cell` boundaries by working in transformed rather than Cartesian coordinates; this modifies computation of :yref:`Aabb` from Cartesian coordinates in which bodies are positioned (treated in detail in :ref:`sect-cell-approx-collision`). The sort algorithm is tracking :yref:`Aabb` extrema along all axes. At the collider's initialization, each value is assigned an integral period, i.e. its distance from the cell's interior expressed in the cell's dimension along its respective axis, and is wrapped to a value inside the cell. We put the period number in subscript. Let us give an example of coordinate sequence along $x$ axis (in a real case, the number of elements would be even, as there is maximum and minimum value couple for each particle; this demonstration only shows the sorting algorithm, however.) .. math:: \sortlines{& 4_1 &\sortInv& 12_2 &\sortSep& -1_2 &\sortInv& -2_4 &\sortInv& 5_0 &} with cell $x$-size $s_x=10$. The $4_1$ value then means that the real coordinate $x_i$ of this extremum is $x_i+1\cdot10=4$, i.e. $x_i=-4$. The $||$ symbol denotes the periodic cell boundary. Sorting starts from the first element in the cell, i.e. right of $||$, and inverts elements as in the aperiodic variant. The rules are, however, more complicated due to the presence of the boundary $||$: ========================== ============================================================== ($\leq$) stop inverting if neighbors are ordered; ($||\bullet$) current element left of $||$ is below 0 (lower period boundary); in this case, decrement element's period, decrease its coordinate by $s_x$ and move $||$ right; ($\bullet||$) current element right of $||$ is above $s_x$ (upper period boundary); increment element's period, increase its coordinate by $s_x$ and move $||$ left; ($\crossBound$) inversion across $||$ must subtract $s_x$ from the left coordinate during comparison. If the elements are not in order, they are swapped, but they must have their periods changed as they traverse $||$. Apply ($||\circ$) if necessary; ($||\circ$) if after ($\crossBound$) the element that is now right of $||$ has $x_i5_0$, we must do $(12_2-s_x)=2_3\leq5$; we adjust periods when swapping over $||$ and apply ($||\circ$), turning $12_2$ into $2_3$; then we keep inverting, until ($\leq$): .. math:: \sortlines{ & 4_1 &\sortInv& 8_3 &\sortInv& 9_1 &\sortInv& 12_2 &\sortSep& \isnleq{5_0}, & \\ & 4_1 &\sortInv& 8_3 &\sortInv& 9_1 &\sortInv& \isnleq{5_{-1}} &\sortSep& 2_3, & \\ & 4_1 &\sortInv& 8_3 &\sortInv& \isnleq{5_{-1}} &\sortInv& 9_1 &\sortSep& 2_3, & \\ & 4_1 &\sortInv& \isleq{5_{-1}} &\sortInv& 8_3 &\sortInv& 9_1 &\sortSep& 2_3. & \\ } We move (wrapping around) to $\currelem{4_1}$, which is ordered: .. math:: \sortlines{ & \currelem{4_1}\ar@(dr,dl)[rrrrrrrr]|{\geq} &\sortInv& 5_{-1} &\sortInv& 8_3 &\sortInv& 9_1 &\sortSep& 2_3 & } and so is the last element .. math:: \sortlines{ & 4_1 &\sortInv& \isleq{5_{-1}} &\sortInv& 8_3 &\sortInv& 9_1 &\sortSep& 2_3. & } Computational aspects ===================== Cost ---- The DEM computation using an explicit integration scheme demands a relatively high number of steps during simulation, compared to implicit scehemes. The total computation time $Z$ of simulation spanning $T$ seconds (of simulated time), containing $N$ particles in volume $V$ depends on: * linearly, the number of steps $i=T/(s_t \Dtcr)$, where $s_t$ is timestep safety factor; $\Dtcr$ can be estimated by p-wave velocity using $E$ and $\rho$ (sect. :ref:`sect-dt-pwave`) as $\Dtcr^{(p)}=r\sqrt{\frac{\rho}{E}}$. Therefore .. math:: i=\frac{T}{s_t r}\sqrt{\frac{E}{\rho}}. * the number of particles $N$; for fixed value of simulated domain volume $V$ and particle radius $r$ .. math:: N=p\frac{V}{\frac{4}{3}\pi r^3}, where $p$ is packing porosity, roughly $\frac{1}{2}$ for dense irregular packings of spheres of similar radius. The dependency is not strictly linear (which would be the best case), as some algorithms do not scale linearly; a case in point is the sweep and prune collision detection algorithm introduced in sect. :ref:`sect-sweep-and-prune`, with scaling roughly $\bigO{N \log N}$. The number of interactions scales with $N$, as long as packing characteristics are the same. * the number of computational cores $\numCPU$; in the ideal case, the dependency would be inverse-linear were all algorithms parallelized (in Yade, collision detection is not). Let us suppose linear scaling. Additionally, let us suppose that the material to be simulated ($E$, $\rho$) and the simulation setup ($V$, $T$) are given in advance. Finally, dimensionless constants $s_t$, $p$ and $\numCPU$ will have a fixed value. This leaves us with one last degree of freedom, $r$. We may write .. math:: Z\propto iN\frac{1}{\numCPU}=\frac{T}{s_t r}\sqrt{\frac{E}{\rho}} p\frac{V}{\frac{4}{3}\pi r^3} \frac{1}{\numCPU}\propto \frac{1}{r}\frac{1}{r^3}=\frac{1}{r^4}. This (rather trivial) result is essential to realize DEM scaling; if we want to have finer results, refining the "mesh" by halving $r$, the computation time will grow $2^4=16$ times. For very crude estimates, one can use a known simulation to obtain a machine "constant" .. math:: \mu=\frac{Z}{Ni} with the meaning of time per particle and per timestep (in the order of $10^{-6}\,{\rm s}$ for current machines). $\mu$ will be only useful if simulation characteristics are similar and non-linearities in scaling do not have major influence, i.e. $N$ should be in the same order of magnitude as in the reference case. Result indeterminism -------------------- It is naturally expected that running the same simulation several times will give exactly the same results: although the computation is done with finite precision, round-off errors would be deterministically the same at every run. While this is true for *single-threaded* computation where exact order of all operations is given by the simulation itself, it is not true anymore in *multi-threaded* computation which is described in detail in later sections. The straight-forward manner of parallel processing in explicit DEM is given by the possibility of treating interactions in arbitrary order. Strain and stress is evaluated for each interaction independently, but forces from interactions have to be summed up. If summation order is also arbitrary (in Yade, forces are accumulated for each thread in the order interactions are processed, then summed together), then the results can be slightly different. For instance :: (1/10.)+(1/13.)+(1/17.)=0.23574660633484162 (1/17.)+(1/13.)+(1/10.)=0.23574660633484165 As forces generated by interactions are assigned to bodies in quasi-random order, summary force $F_i$ on the body can be different between single-threaded and multi-threaded computations, but also between different runs of multi-threaded computation with exactly the same parameters. Exact thread scheduling by the kernel is not predictable since it depends on asynchronous events (hardware interrupts) and other unrelated tasks running on the system; and it is thread scheduling that ultimately determines summation order of force contributions from interactions. Numerical damping influence ^^^^^^^^^^^^^^^^^^^^^^^^^^^ The effect of summation order can be significantly amplified by the usage of a *discontinuous* damping function in :yref:`NewtonIntegrator` given in :eq:`eq-damping-yade` as .. math:: \frac{(\Delta\vec{F})_{dw}}{\vec{F}_w}=-\lambda_d\sign\vec{F}_w\left(\pprev{\dot{u}}_w+\frac{\curr{\ddot{\vec{u}}}_w\Dt}{2}\right). If the $\sign$ argument is close to zero then the least significant finite precision artifact can determine whether the equation (relative increment of $\vec{F}_w$) is $+\lambda_d$ or $-\lambda_d$. Given commonly used values of $\lambda_d=0.4$, it means that such artifact propagates from least significant place to the most significant one at once. trunk-2018.02b/doc/sphinx/github.rst000066400000000000000000000161071324306050200172650ustar00rootroot00000000000000.. _yade-github-label: ############## Yade on GitHub ############## ************************************************ Fast checkout without GitHub account (read-only) ************************************************ Getting the source code without registering on GitHub can be done via a single command. It will not allow interactions with the remote repository, which you access the read-only way:: git clone https://github.com/yade/trunk.git *************************************************************************** Using branches on GitHub (for frequent commits see git/trunk section below) *************************************************************************** Most usefull commands are below. For more details, see for instance http://gitref.org/index.html and https://help.github.com/articles/set-up-git Setup ===== 1. Register on github.com 2. Add your `SSH key `_ to GitHub: On the GitHub site Click “Account Settings” (top right) > Click “SSH keys” > Click “Add SSH key” 3. Set your username and email through terminal: :: git config --global user.name "Firstname Lastname" git config --global user.email "your_email@youremail.com" You can check these settings with ``git config --list``. 4. `Fork a repo `_: Click the “Fork” button on the https://github.com/yade/trunk 5. Set Up Your Local Repo through terminal: :: git clone git@github.com:username/trunk.git This creates a new folder, named trunk, that contains the whole code. 6. Configure remotes :: cd to/newly/created/folder git remote add upstream git@github.com:yade/trunk.git git fetch upstream Now, your "trunk" folder is linked with the code hosted on github.com. Through appropriate commands explained below, you will be able to update your code to include changes commited by others, or to commit yourself changes that others can get. Retrieving older Commits ======================== In case you want to work with, or compile, an older version of Yade which is not tagged, you can create your own (local) branch of the corresponding daily build. Look `here `_ for details. Committing and updating ======================== For those used to other version control systems, note that the commit mechanisms in Git significantly differs from that of `Bazaar `_ or `SVN `_. Therefore, don't expect to find a one-to-one command replacement. In some cases, however, the equivalent bazaar command is indicated below to ease the transition. Inspecting changes ------------------ You may start by inspecting your changes with a few commands. For the "diff" command, it is convenient to copy from the output of "status" instead of typing the path to modified files. :: git status git diff path/to/modified/file.cpp Committing changes ------------------ Then you proceed to commit through terminal:: git add path/to/new/file.cpp #Version a newly created file: equivalent of "bzr add" git commit path/to/new_or_modified/file.cpp -m'Commit message'`` #Validate a change. It can be done several times after every sufficient change. No equivalent in bzr, it's like commiting to your own local repository git push #Push your changes into GitHub. Equivalent of "bzr commit", except that your are commiting to your own remote branch Changes will be pushed to your personal "fork", If you have tested your changes and you are ready to push them into the main trunk, just do a "pull request" [5] or create a patch from your commit via:: git format-patch origin #create patch file in current folder) and send to the developers mailing list (yade-dev@lists.launchpad.net) as attachment. In either way, after reviewing your changes they will be added to the main trunk. When the pull request has been reviewed and accepted, your changes are integrated in the main trunk. Everyone will get them via ``git fetch``. Updating -------- You may want to get changes done by others:: git fetch upstream #Pull new updates from the upstream to your branch. Eq. of "bzr update", updating the remote branch from the upstream yade/trunk git merge upstream/master #Merge upstream changes into your master-branch (eq. of "bzr update", updating your local repository from the remote branch) Alternatively, this will do fetch+merge all at once (discouraged if you have uncommited changes):: git pull **************************************************************** Working directly on git/trunk (recommended for frequent commits) **************************************************************** This direct access to trunk will sound more familiar to `bzr `_ or `svn `_ users. It is only possible for members of the git team "developpers". Send an email at yade-dev@lists.launchpad.net to join this team (don't forget to tell your git account name). * Get trunk: :: git clone git@github.com:yade/trunk.git This creates a new folder, named trunk, that contains the whole code. * Update :: git pull * Commit to local repository :: git commit filename1 filename2 ... * Push changes to remote trunk :: git push Now, the changes you made are included in the on-line code, and can be get back by every user. To avoid confusing logs after each commit/pull/push cycle, it is better to setup automatic rebase:: git config --global branch.autosetuprebase always Now your file ~/.gitconfig should include: [branch] autosetuprebase = always Check also .git/config file in your local trunk folder (rebase = true): [branch "master"] remote = origin merge = refs/heads/master rebase = true Auto-rebase may have unpleasant side effects by blocking "pull" if you have uncommited changes. In this case you can use "git stash":: git pull lib/SConscript: needs update refusing to pull with rebase: your working tree is not up-to-date git stash #hide the uncommited changes away git pull #now it's ok git push #push the commited changes git stash pop #get uncommited changes back ******************************************** General guidelines for pushing to yade/trunk ******************************************** 1. Set autorebase once on the computer! (see above) 2. Inspect the diff to make sure you will not commit junk code (typically some "cout<<" left here and there), using in terminal: :: git diff file1 Or using your preferred difftool, such as kdiff3: :: git difftool -t kdiff3 file1 Or, alternatively, any GUI for git: gitg, git-cola... 3. Commit selectively: :: git commit file1 file2 file3 -m "message" # is good git commit -a -m "message" # is bad. It is the best way to commit things that should not be commited 4. Be sure to work with an up-to-date version launching: :: git pull 5. Make sure it compiles and that regression tests pass: try ``yade --test`` and ``yade --check``. 6. You can finally let all Yade-users enjoy your work: :: git push **Thanks a lot for your cooperation to Yade!** trunk-2018.02b/doc/sphinx/index-toctree-book.rst000066400000000000000000000005671324306050200215100ustar00rootroot00000000000000.. Yade documentation master file, created by sphinx-quickstart on Mon Nov 16 21:49:34 2009. You can adapt this file completely to your liking, but it should at least contain the root `toctree` directive. Yade Documentation ^^^^^^^^^^^^^^^^^^^^ .. toctree:: :maxdepth: 2 index-toctree-manuals.rst index-toctree-theory.rst index-toctree-reference.rst trunk-2018.02b/doc/sphinx/index-toctree-manuals.rst000066400000000000000000000003631324306050200222100ustar00rootroot00000000000000 Using and Programming ======================== .. toctree:: :maxdepth: 2 installation.rst introduction.rst tutorial.rst user.rst FEMxDEM.rst prog.rst github.rst citing.rst fullPublications.rst trunk-2018.02b/doc/sphinx/index-toctree-reference.rst000066400000000000000000000001751324306050200225070ustar00rootroot00000000000000 Reference Manual =================== .. toctree:: :maxdepth: 2 yade.wrapper.rst modules.rst fullPublications.rst trunk-2018.02b/doc/sphinx/index-toctree-theory.rst000066400000000000000000000001661324306050200220630ustar00rootroot00000000000000 Theoretical background ======================== .. toctree:: :maxdepth: 2 formulation.rst publications.rst trunk-2018.02b/doc/sphinx/index-toctree.rst000066400000000000000000000012221324306050200205450ustar00rootroot00000000000000.. Yade documentation master file, created by sphinx-quickstart on Mon Nov 16 21:49:34 2009. You can adapt this file completely to your liking, but it should at least contain the root `toctree` directive. Welcome to Yade's documentation! ================================ .. toctree:: :maxdepth: 2 introduction.rst tutorial.rst user.rst prog.rst installation.rst amazonEC2.rst GPUacceleration.rst github.rst formulation.rst yade.wrapper.rst modules.rst FEMxDEM.rst citing.rst publications.rst references.rst Indices and tables ================== * :ref:`genindex` * :ref:`modindex` * :ref:`search` trunk-2018.02b/doc/sphinx/index-toctree_manuals.rst000066400000000000000000000006441324306050200222740ustar00rootroot00000000000000.. Yade documentation master file, created by sphinx-quickstart on Mon Nov 16 21:49:34 2009. You can adapt this file completely to your liking, but it should at least contain the root `toctree` directive. Welcome to Yade's documentation! ================================ .. toctree:: :maxdepth: 2 introduction.rst tutorial.rst user.rst prog.rst installation.rst github.rst citing.rst trunk-2018.02b/doc/sphinx/installation.rst000066400000000000000000000426751324306050200205150ustar00rootroot00000000000000############### Installation ############### Yade can be installed from packages (pre-compiled binaries) or source code. The choice depends on what you need: if you don't plan to modify Yade itself, package installation is easier. In the contrary case, you must download and install the source code. Packages ---------- Pre-built packages are provided for all currently supported Debian and Ubuntu versions and available on `yade-dem.org/packages `_ . These are "daily" versions of the packages which are being updated regularly and, hence, include all the newly added features. To install the daily-version you need to add the repository to your /etc/apt/sources.list, add the PGP-key AA915EEB as trusted and install ``yadedaily``:: sudo bash -c 'echo "deb http://www.yade-dem.org/packages/ xenial/" >> /etc/apt/sources.list' wget -O - http://www.yade-dem.org/packages/yadedev_pub.gpg | sudo apt-key add - sudo apt-get update sudo apt-get install yadedaily Be sure to use the correct name of your Ubuntu/Debian distribution in the first line (xenial for Ubuntu 16.04 LTS, whereas stretch would be required for Debian 9, for instance). For the list of currently supported distributions, please visit `yade-dem.org/packages `_. After that you can normally start Yade using the command ``yadedaily`` or ``yadedaily-batch``. ``yadedaily`` on older distributions can have some disabled features due to older library versions, shipped with particular distribution. The Git-repository for packaging stuff is available on `GitHub `_. Each branch corresponds to one distribution, e.g., xenial, stretch etc. The scripts for building all of this stuff are `here `__. It uses "pbuilder" to build packages, so all packages are built in a clean environment. If you do not need ``yadedaily``-package anymore, just remove the corresponding line in /etc/apt/sources.list and the package itself:: sudo apt-get remove yadedaily To remove our key from keyring, execute the following command:: sudo apt-key remove AA915EEB Since 2011, all Ubuntu (starting from 11.10, Oneiric) and Debian (starting from Wheezy) versions have Yade in their main repositories. There are only stable releases in place. To install Yade, run the following:: sudo apt-get install yade After that you can normally start Yade using the command ``yade`` or ``yade-batch``. To check, what version of Yade is included in your specific distribution, visit `Ubuntu `_ or `Debian `_. The `Debian-Backports `_ repository is updated regularly to bring the newest Yade version to the users of stable Debians. Daily and stable Yade versions can coexist without any conflicts, i.e., you can use ``yade`` and ``yadedaily`` at the same time. Source code ------------ Installation from source code is reasonable, when you want to add or modify constitutive laws, engines, functions etc. Installing the latest trunk version allows one to use newly added features, which are not yet available in packaged versions. Download ^^^^^^^^^^ If you want to install from source, you can install either a release (numbered version, which is frozen) or the current development version (updated by the developers frequently). You should download the development version (called ``trunk``) if you want to modify the source code, as you might encounter problems that will be fixed by the developers. Release versions will not be updated (except for updates due to critical and easy-to-fix bugs), but generally they are more stable than the trunk. #. Releases can be downloaded from the `download page `_, as compressed archive. Uncompressing the archive gives you a directory with the sources. #. The development version (``trunk``) can be obtained from the `code repository `_ at GitHub. We use `GIT `_ (the ``git`` command) for code management (install the ``git`` package on your system and create a `GitHub account `__):: git clone git@github.com:yade/trunk.git will download the whole code repository of the ``trunk``. Check out :ref:`yade-github-label` for more details on how to collaborate using ``git``. Alternatively, a read-only checkout is possible via https without a GitHub account (easier if you don't want to modify the trunk version):: git clone https://github.com/yade/trunk.git For those behind a firewall, you can download the sources from our `GitHub `__ repository as compressed archive. Release and trunk sources are compiled in exactly the same way. In order to get notifications about changes to the truck (i.e., ``commits``), use `watch option on GitHub `_. Prerequisites ^^^^^^^^^^^^^ Yade relies on a number of external software to run; they are checked before the compilation starts. Some of them are only optional. The last ones are only relevant for using the fluid coupling module (:yref:`FlowEngine`). * `cmake `_ build system * `gcc `_ compiler (g++); other compilers will not work; you need g++>=4.2 for openMP support * `boost `_ 1.47 or later * `Qt `_ library * `freeglut3 `_ * `libQGLViewer `_ * `python `_, `numpy `_, `ipython `_ * `matplotlib `_ * `eigen `_ algebra library (minimal required version 3.2.1) * `gdb `_ debugger * `sqlite3 `_ database engine * `Loki `_ library * `VTK `_ library (optional but recommended) * `CGAL `_ library (optional) * `SuiteSparse `_ sparse algebra library (fluid coupling, optional, requires eigen>=3.1) * `OpenBLAS `_ optimized and parallelized alternative to the standard blas+lapack (fluid coupling, optional) * `Metis `_ matrix preconditioning (fluid coupling, optional) Most of the list above is very likely already packaged for your distribution. In case you are confronted with some errors concerning not available packages (e.g., package libmetis-dev is not available) it may be necessary to add yade external ppa from https://launchpad.net/~yade-users/+archive/external (see below) as well as http://www.yade-dem.org/packages (see the top of this page):: sudo add-apt-repository ppa:yade-users/external sudo apt-get update The following commands have to be executed in the command line of your corresponding distribution. Just copy&paste to the terminal. Note, to execute these commands you need root privileges. * **Ubuntu**, **Debian** and their derivatives:: sudo apt-get install cmake git freeglut3-dev libloki-dev \ libboost-all-dev fakeroot dpkg-dev build-essential g++ \ python-dev ipython python-matplotlib libsqlite3-dev python-numpy python-tk gnuplot \ libgts-dev python-pygraphviz libvtk6-dev python-numpy libeigen3-dev \ python-xlib python-pyqt5 pyqt5-dev-tools python-pyqt5.qtwebkit gtk2-engines-pixbuf python-argparse python-pyqt5.qtsvg \ libqglviewer-dev python-imaging libjs-jquery python-sphinx python-git python-bibtex \ libxmu-dev libxi-dev libcgal-dev help2man libbz2-dev zlib1g-dev python-minieigen Some of the packages (for example, cmake, eigen3) are mandatory, some of them are optional. Watch for notes and warnings/errors, which are shown by ``cmake`` during the configuration step. If the missing package is optional, some of Yade features will be disabled (see the messages at the end of the configuration). Additional packages, which can become mandatory later:: sudo apt-get install python-gts For effective usage of direct solvers in the PFV-type fluid coupling, the following libraries are recommended, together with eigen>=3.1: blas, lapack, suitesparse, and metis. All four of them are available in many different versions. Different combinations are possible and not all of them will work. The following was found to be effective on recent deb-based systems. On ubuntu 12.04, better compile openblas with USE_OPENMP=1, else yade will run on a single core:: sudo apt-get install libopenblas-dev libsuitesparse-metis-dev Some packages listed here are relatively new and they can be absent in your distribution (for example, libmetis-dev or python-gts). They can be installed from `yade-dem.org/packages `_ or from our `external PPA `_. If not installed the related features will be disabled automatically. If you are using other distributions than Debian or its derivatives you should install the software packages listed above. Their names in other distributions can differ from the names of the Debian-packages. .. warning:: If you have Ubuntu 14.04 Trusty, you need to add -DCMAKE_CXX_FLAGS=-frounding-math during the configuration step of compilation (see below) or to install libcgal-dev from our `external PPA `_. Otherwise the following error occurs on AMD64 architectures:: terminate called after throwing an instance of 'CGAL::Assertion_exception' what(): CGAL ERROR: assertion violation! Expr: -CGAL_IA_MUL(-1.1, 10.1) != CGAL_IA_MUL(1.1, 10.1) File: /usr/include/CGAL/Interval_nt.h Line: 209 Explanation: Wrong rounding: did you forget the -frounding-math option if you use GCC (or -fp-model strict for Intel)? Aborted Compilation ^^^^^^^^^^^ You should create a separate build-place-folder, where Yade will be configured and where the source code will be compiled. Here is an example for a folder structure:: myYade/ ## base directory trunk/ ## folder for source code in which you use github build/ ## folder in which the sources will be compiled; build-directory; use cmake here install/ ## install folder; contains the executables Then, inside this build-directory you should call ``cmake`` to configure the compilation process:: cmake -DCMAKE_INSTALL_PREFIX=/path/to/installfolder /path/to/sources For the folder structure given above call the following command in the folder "build":: cmake -DCMAKE_INSTALL_PREFIX=../install ../trunk Additional options can be configured in the same line with the following syntax:: cmake -DOPTION1=VALUE1 -DOPTION2=VALUE2 The following options are available: * CMAKE_INSTALL_PREFIX: path where Yade should be installed (/usr/local by default) * LIBRARY_OUTPUT_PATH: path to install libraries (lib by default) * DEBUG: compile in debug-mode (OFF by default) * CMAKE_VERBOSE_MAKEFILE: output additional information during compiling (OFF by default) * SUFFIX: suffix, added after binary-names (version number by default) * NOSUFFIX: do not add a suffix after binary-name (OFF by default) * YADE_VERSION: explicitly set version number (is defined from git-directory by default) * ENABLE_GUI: enable GUI option (ON by default) * ENABLE_CGAL: enable CGAL option (ON by default) * ENABLE_VTK: enable VTK-export option (ON by default) * ENABLE_OPENMP: enable OpenMP-parallelizing option (ON by default) * ENABLE_GTS: enable GTS-option (ON by default) * ENABLE_GL2PS: enable GL2PS-option (ON by default) * ENABLE_LINSOLV: enable LINSOLV-option (ON by default) * ENABLE_PFVFLOW: enable PFVFLOW-option, FlowEngine (ON by default) * ENABLE_LBMFLOW: enable LBMFLOW-option, LBM_ENGINE (ON by default) * ENABLE_SPH: enable SPH-option, Smoothed Particle Hydrodynamics (OFF by default) * ENABLE_LIQMIGRATION: enable LIQMIGRATION-option, see [Mani2013]_ for details (OFF by default) * ENABLE_MASK_ARBITRARY: enable MASK_ARBITRARY option (OFF by default) * ENABLE_PROFILING: enable profiling, e.g., shows some more metrics, which can define bottlenecks of the code (OFF by default) * runtimePREFIX: used for packaging, when install directory is not the same as runtime directory (/usr/local by default) * CHUNKSIZE: specifiy the chunk size if you want several sources to be compiled at once. Increases compilation speed but RAM-consumption during compilation as well (1 by default) * VECTORIZE: enables vectorization and alignment in Eigen3 library, experimental (OFF by default) * USE_QT5: use QT5 for GUI, experimental (ON by default) For using more extended parameters of cmake, please follow the corresponding documentation on `https://cmake.org/documentation `_. .. warning:: To provide Qt4->Qt5 migration one needs to provide an additional option USE_QT5. This option is ON by default but should be set according to the Qt version which was used to compile libQGLViewer. On Debian/Ubuntu operating systems libQGLViewer of version 2.6.3 and higher are compiled against Qt5 (for other operating systems refer to the package archive of your distribution), so if you are using such version, please switch this option ON. Otherwise, if you mix Qt-versions a ``Segmentation fault`` will appear just after Yade is started. To provide necessary build dependencies for Qt5, install ``python-pyqt5 pyqt5-dev-tools`` instead of ``python-qt4 pyqt4-dev-tools``. If cmake finishes without errors, you will see all enabled and disabled options at the end. Then start the actual compilation process with:: make The compilation process can take a considerable amount of time, be patient. If you are using a multi-core systems you can use the parameter ``-j`` to speed-up the compilation and split the compilation onto many cores. For example, on 4-core machines it would be reasonable to set the parameter ``-j4``. Note, Yade requires approximately 3GB RAM per core for compilation, otherwise the swap-file will be used and compilation time dramatically increases. The installation is performed with the following command:: make install The ``install`` command will in fact also recompile if source files have been modified. Hence there is no absolute need to type the two commands separately. You may receive make errors if you don't have permission to write into the target folder. These errors are not critical but without writing permissions Yade won't be installed in /usr/local/bin/. After the compilation finished successfully, the new built can be started by navigating to /path/to/installfolder/bin and calling yade via (based on version yade-2014-02-20.git-a7048f4):: cd /path/to/installfolder/bin ./yade-2014-02-20.git-a7048f4 For building the documentation you should at first execute the command ``make install`` and then ``make doc`` to build it. The generated files will be stored in your current install directory /path/to/installfolder/share/doc/yade-your-version. Once again writing permissions are necessary for installing into /usr/local/share/doc/. To open your local documentation go into the folder html and open the file index.html with a browser. ``make manpage`` command generates and moves manpages in a standard place. ``make check`` command executes standard test to check the functionality of the compiled program. Yade can be compiled not only by GCC-compiler, but also by `CLANG `_ front-end for the LLVM compiler. For that you set the environment variables CC and CXX upon detecting the C and C++ compiler to use:: export CC=/usr/bin/clang export CXX=/usr/bin/clang++ cmake -DOPTION1=VALUE1 -DOPTION2=VALUE2 Clang does not support OpenMP-parallelizing for the moment, that is why the feature will be disabled. Speed-up compilation ^^^^^^^^^^^^^^^^^^^^^ When spliting the compilation on many cores (``make -jN``), ``N`` is limited by the available cores and memory. It is possible to use more cores if remote computers are available, ditributing the compilation with `ditscc `_ (see distcc documentation for configuring slaves and master):: export CC=distcc gcc export CXX=distcc g++ cmake [options as usual] make -jN In addition, and independently of distcc, caching previous compilations with `ccache `_ can speed up re-compilation:: export CC=ccache gcc export CXX=ccache g++ cmake [options as usual] The two tools can be combined very simply, adding to the above exports:: export CCACHE_PREFIX="distcc" Yubuntu ------------ If you are not running Ubuntu nor Debian, there is a way to create a Yubuntu `live-usb `_ on any usb mass-storage device (minimum recommended size is 5GB). It is a way to make a bootable usb-key with a preinstalled minimalist operating system (Xubuntu), including Yadedaily and Paraview. More informations about this alternative are available `here `_ (see the README file first). Cloud Computing ---------------- It is possible to exploit cloud computing services to run Yade. The combo Yade/Amazon Web Service has been found to work well, namely. Detailed instructions for migrating to amazon can be found in the section :ref:`CloudComputing`. GPU Acceleration ---------------- The FlowEngine can be accelerated with CHOLMOD's GPU accelerated solver. The specific hardware and software requirements are outlined in the section :ref:`GPUacceleration`. trunk-2018.02b/doc/sphinx/introduction.rst000066400000000000000000000615571324306050200205350ustar00rootroot00000000000000############### Introduction ############### Getting started =============== .. ipython:: :suppress: In [1]: from yade import * In [7]: O.reset() Before you start moving around in Yade, you should have some prior knowledge. * Basics of command line in your Linux system are necessary for running yade. Look on the web for tutorials. * Python language; we recommend the official `Python tutorial `_. Reading further documents on the topis, such as `Dive into Python `_ will certainly not hurt either. You are advised to try all commands described yourself. Don't be afraid to experiment. Starting yade ------------- Yade is being run primarily from terminal; the name of command is ``yade``. [#f1]_ (In case you did not install from package, you might need to give specific path to the command [#fcmd]_ ):: \$ yade Welcome to Yade TCP python prompt on localhost:9001, auth cookie `sdksuy' TCP info provider on localhost:21000 [[ ^L clears screen, ^U kills line. F12 controller, F11 3d view, F10 both, F9 generator, F8 plot. ]] Yade [1]: These initial lines give you some information about * some information for :ref:`remoteaccess`, which you are unlikely to need now; * basic help for the command-line that just appeared (``Yade [1]:``). Type ``quit()``, ``exit()`` or simply press ``^D`` to quit Yade. The command-line is `ipython `_, python shell with enhanced interactive capabilities; it features persistent history (remembers commands from your last sessions), searching and so on. See ipython's documentation for more details. Typically, you will not type Yade commands by hand, but use *scripts*, python programs describing and running your simulations. Let us take the most simple script that will just print "Hello world!":: print "Hello world!" Saving such script as ``hello.py``, it can be given as argument to yade:: \$ yade hello.py Welcome to Yade TCP python prompt on localhost:9001, auth cookie `askcsu' TCP info provider on localhost:21000 Running script hello.py ## the script is being run Hello world! ## output from the script [[ ^L clears screen, ^U kills line. F12 controller, F11 3d view, F10 both, F9 generator, F8 plot. ]] Yade [1]: Yade will run the script and then drop to the command-line again. [#f2]_ If you want Yade to quit immediately after running the script, use the ``-x`` switch:: \$ yade -x script.py There is more command-line options than just ``-x``, run ``yade -h`` to see all of them. Options: --version show program's version number and exit -h, --help show this help message and exit -j THREADS, --threads=THREADS Number of OpenMP threads to run; defaults to 1. Equivalent to setting OMP_NUM_THREADS environment variable. --cores=CORES Set number of OpenMP threads (as \-\-threads) and in addition set affinity of threads to the cores given. --update Update deprecated class names in given script(s) using text search & replace. Changed files will be backed up with ~ suffix. Exit when done without running any simulation. --nice=NICE Increase nice level (i.e. decrease priority) by given number. -x Exit when the script finishes -n Run without graphical interface (equivalent to unsetting the DISPLAY environment variable) --test Run regression test suite and exit; the exists status is 0 if all tests pass, 1 if a test fails and 2 for an unspecified exception. --check Run a series of user-defined check tests as described in /build/buildd/yade- daily-1+3021+27~lucid1/scripts/test/checks/README --performance Starts a test to measure the productivity --no-gdb Do not show backtrace when yade crashes (only effective with \-\-debug). .. rubric:: Footnotes .. [#f1] The executable name can carry a suffix, such as version number (``yade-0.20``), depending on compilation options. Packaged versions on Debian systems always provide the plain ``yade`` alias, by default pointing to latest stable version (or latest snapshot, if no stable version is installed). You can use ``update-alternatives`` to change this. .. [#fcmd] In general, Unix *shell* (command line) has environment variable ``PATH`` defined, which determines directories searched for executable files if you give name of the file without path. Typically, \$PATH contains ``/usr/bin/``, ``/usr/local/bin``, ``/bin`` and others; you can inspect your ``PATH`` by typing ``echo \$PATH`` in the shell (directories are separated by ``:``). If Yade executable is not in directory contained in ``PATH``, you have to specify it by hand, i.e. by typing the path in front of the filename, such as in ``/home/user/bin/yade`` and similar. You can also navigate to the directory itself (``cd ~/bin/yade``, where ``~`` is replaced by your home directory automatically) and type ``./yade`` then (the ``.`` is the current directory, so ``./`` specifies that the file is to be found in the current directory). To save typing, you can add the directory where Yade is installed to your ``PATH``, typically by editing ``~/.profile`` (in normal cases automatically executed when shell starts up) file adding line like ``export PATH=/home/user/bin:\$PATH``. You can also define an *alias* by saying ``alias yade="/home/users/bin/yade"`` in that file. Details depend on what shell you use (bash, zsh, tcsh, …) and you will find more information in introductory material on Linux/Unix. .. [#f2] Plain Python interpreter exits once it finishes running the script. The reason why Yade does the contrary is that most of the time script only sets up simulation and lets it run; since computation typically runs in background thread, the script is technically finished, but the computation is running. Creating simulation -------------------- To create simulation, one can either use a specialized class of type :yref:`FileGenerator` to create full scene, possibly receiving some parameters. Generators are written in c++ and their role is limited to well-defined scenarios. For instance, to create triaxial test scene: .. ipython:: In [1]: TriaxialTest(numberOfGrains=200).load() In [2]: len(O.bodies) 1006 Generators are regular yade objects that support attribute access. It is also possible to construct the scene by a python script; this gives much more flexibility and speed of development and is the recommended way to create simulation. Yade provides modules for streamlined body construction, import of geometries from files and reuse of common code. Since this topic is more involved, it is explained in the *User's manual*. .. ipython:: :suppress: In [7]: O.reset() Running simulation ------------------ As explained below, the loop consists in running defined sequence of engines. Step number can be queried by ``O.iter`` and advancing by one step is done by ``O.step()``. Every step advances *virtual time* by current timestep, ``O.dt``: .. ipython:: In [1]: O.iter In [1]: O.time In [1]: O.dt=1e-4 In [1]: O.step() In [1]: O.iter 1 In [1]: O.time 1e-4 Normal simulations, however, are run continuously. Starting/stopping the loop is done by ``O.run()`` and ``O.pause()``; note that ``O.run()`` returns control to Python and the simulation runs in background; if you want to wait for it finish, use ``O.wait()``. Fixed number of steps can be run with ``O.run(1000)``, ``O.run(1000,True)`` will run and wait. To stop at absolute step number, ``O.stopAtIter`` can be set and ``O.run()`` called normally. .. ipython:: In [1]: O.run() In [1]: O.pause() In [1]: O.iter 104587 In [1]: O.run(100000,True) In [1]: O.iter 204587 In [1]: O.stopAtIter=500000 In [1]: O.wait() In [1]: O.iter 500000 Saving and loading ------------------ Simulation can be saved at any point to a binary file (optionaly compressed if the filename has extensions such as ".gz" or ".bz2"). Saving to a XML file is also possible though resulting in larger files and slower save/load, it is used when the filename contains "xml". With some limitations, it is generally possible to load the scene later and resume the simulation as if it were not interrupted. Note that since the saved scene is a dump of Yade's internal objects, it might not (probably will not) open with different Yade version. .. ipython:: In [1]: O.save('/tmp/a.yade.bz2') In [2]: O.reload() @suppress In [4]: O.save('/tmp/another.yade.bz2') In [3]: O.load('/tmp/another.yade.bz2') The principal use of saving the simulation to XML is to use it as temporary in-memory storage for checkpoints in simulation, e.g. for reloading the initial state and running again with different parameters (think tension/compression test, where each begins from the same virgin state). The functions ``O.saveTmp()`` and ``O.loadTmp()`` can be optionally given a slot name, under which they will be found in memory: .. ipython:: In [1]: O.saveTmp() In [1]: O.loadTmp() In [1]: O.saveTmp('init') ## named memory slot In [1]: O.loadTmp('init') Simulation can be reset to empty state by ``O.reset()``. It can be sometimes useful to run different simulation, while the original one is temporarily suspended, e.g. when dynamically creating packing. ``O.switchWorld()`` toggles between the primary and secondary simulation. Graphical interface -------------------- Yade can be optionally compiled with qt4-based graphical interface. It can be started by pressing ``F12`` in the command-line, and also is started automatically when running a script. .. image:: fig/qt-gui.png The windows with buttons is called ``Controller`` (can be invoked by ``yade.qt.Controller()`` from python): #. The *Simulation* tab is mostly self-explanatory, and permits basic simulation control. #. The *Display* tab has various rendering-related options, which apply to all opened views (they can be zero or more, new one is opened by the *New 3D* button). #. The *Python* tab has only a simple text entry area; it can be useful to enter python commands while the command-line is blocked by running script, for instance. 3d views can be controlled using mouse and keyboard shortcuts; help is displayed if you press the ``h`` key while in the 3d view. Note that having the 3d view open can slow down running simulation significantly, it is meant only for quickly checking whether the simulation runs smoothly. Advanced post-processing is described in dedicated section. Architecture overview ====================== .. ipython:: :suppress: In [12]: from yade import * In [1]: from yade import utils In [7]: O.reset() In the following, a high-level overview of Yade architecture will be given. As many of the features are directly represented in simulation scripts, which are written in Python, being familiar with this language will help you follow the examples. For the rest, this knowledge is not strictly necessary and you can ignore code examples. Data and functions ------------------- To assure flexibility of software design, yade makes clear distinction of 2 families of classes: *data* components and *functional* components. The former only store data without providing functionality, while the latter define functions operating on the data. In programming, this is known as *visitor* pattern (as functional components "visit" the data, without being bound to them explicitly). Entire simulation, i.e. both data and functions, are stored in a single ``Scene`` object. It is accessible through the :yref:`Omega` class in python (a singleton), which is by default stored in the ``O`` global variable: .. ipython:: Yade [1]: O.bodies # some data components Yade [2]: len(O.bodies) # there are no bodies as of yet Yade [3]: O.engines # functional components, empty at the moment Data components ^^^^^^^^^^^^^^^ Bodies """"""" Yade simulation (class ``Scene``, but hidden inside :yref:`Omega` in Python) is represented by :yref:`Bodies`, their :yref:`Interactions` and resultant generalized :yref:`forces` (all stored internally in special containers). Each :yref:`Body` comprises the following: :yref:`Shape` represents particle's geometry (neutral with regards to its spatial orientation), such as :yref:`Sphere`, :yref:`Facet` or inifinite :yref:`Wall`; it usually does not change during simulation. :yref:`Material` stores characteristics pertaining to mechanical behavior, such as Young's modulus or density, which are independent on particle's shape and dimensions; usually constant, might be shared amongst multiple bodies. :yref:`State` contains state variable variables, in particular spatial :yref:`position` and :yref:`orientation`, :yref:`linear` and :yref:`angular` velocity, :yref:`linear` and :yref:`angular` accelerator; it is updated by the :yref:`integrator` at every step. Derived classes can hold additional data, e.g. :yref:`averaged damage`. :yref:`Bound` is used for approximate ("pass 1") contact detection; updated as necessary following body's motion. Currently, :yref:`Aabb` is used most often as :yref:`Bound`. Some bodies may have no :yref:`Bound`, in which case they are exempt from contact detection. (In addition to these 4 components, bodies have several more minor data associated, such as :yref:`Body::id` or :yref:`Body::mask`.) All these four properties can be of different types, derived from their respective base types. Yade frequently makes decisions about computation based on those types: :yref:`Sphere` + :yref:`Sphere` collision has to be treated differently than :yref:`Facet` + :yref:`Sphere` collision. Objects making those decisions are called :yref:`Dispatcher`'s and are essential to understand Yade's functioning; they are discussed below. Explicitly assigning all 4 properties to each particle by hand would be not practical; there are utility functions defined to create them with all necessary ingredients. For example, we can create sphere particle using :yref:`yade.utils.sphere`: .. ipython:: In [3]: s=utils.sphere(center=[0,0,0],radius=1) In [5]: s.shape, s.state, s.mat, s.bound In [6]: s.state.pos In [7]: s.shape.radius We see that a sphere with material of type :yref:`FrictMat` (default, unless you provide another :yref:`Material`) and bounding volume of type :yref:`Aabb` (axis-aligned bounding box) was created. Its position is at origin and its radius is 1.0. Finally, this object can be inserted into the simulation; and we can insert yet one sphere as well. .. ipython:: In [1]: O.bodies.append(s) 0 In [2]: O.bodies.append(utils.sphere([0,0,2],.5)) 1 In each case, return value is :yref:`Body.id` of the body inserted. Since till now the simulation was empty, its id is 0 for the first sphere and 1 for the second one. Saving the id value is not necessary, unless you want access this particular body later; it is remembered internally in :yref:`Body` itself. You can address bodies by their id: .. ipython:: In [1]: O.bodies[1].state.pos In [2]: O.bodies[100] IndexError: Body id out of range. Adding the same body twice is, for reasons of the id uniqueness, not allowed: .. ipython:: In [1]: O.bodies.append(s) Bodies can be iterated over using standard python iteration syntax: .. ipython:: In [1]: for b in O.bodies: ...: print b.id,b.shape.radius ...: 0 1.0 1 0.5 Interactions """"""""""""""" :yref:`Interactions` are always between pair of bodies; usually, they are created by the collider based on spatial proximity; they can, however, be created explicitly and exist independently of distance. Each interaction has 2 components: :yref:`IGeom` holding geometrical configuration of the two particles in collision; it is updated automatically as the particles in question move and can be queried for various geometrical characteristics, such as penetration distance or shear strain. Based on combination of types of :yref:`Shapes` of the particles, there might be different storage requirements; for that reason, a number of derived classes exists, e.g. for representing geometry of contact between :yref:`Sphere+Sphere`, :yref:`Cylinder+Sphere` etc. Note, however, that it is possible to represent many type of contacts with the basic sphere-sphere geometry (for instance in :yref:`Ig2_Wall_Sphere_ScGeom`). :yref:`IPhys` representing non-geometrical features of the interaction; some are computed from :yref:`Materials` of the particles in contact using some averaging algorithm (such as contact stiffness from Young's moduli of particles), others might be internal variables like damage. Suppose now interactions have been already created. We can access them by the id pair: .. ipython:: :suppress: In [1]: O.engines=[InteractionLoop([Ig2_Sphere_Sphere_ScGeom()],[Ip2_FrictMat_FrictMat_FrictPhys()],[])] In [2]: utils.createInteraction(0,1); .. ipython:: In [1]: O.interactions[0,1] In [2]: O.interactions[1,0] # order of ids is not important In [2]: i=O.interactions[0,1] In [3]: i.id1,i.id2 (0, 1) In [4]: i.geom In [5]: i.phys In [6]: O.interactions[100,10111] ValueError: No such interaction Generalized forces """""""""""""""""""" Generalized forces include force, torque and forced displacement and rotation; they are stored only temporarliy, during one computation step, and reset to zero afterwards. For reasons of parallel computation, they work as accumulators, i.e. only can be added to, read and reset. .. ipython:: Yade [1]: O.forces.f(0) Yade [2]: O.forces.addF(0,Vector3(1,2,3)) Yade [3]: O.forces.f(0) @suppress In [7]: O.reset() You will only rarely modify forces from Python; it is usually done in c++ code and relevant documentation can be found in the Programmer's manual. .. _function-components: Function components ^^^^^^^^^^^^^^^^^^^^ In a typical DEM simulation, the following sequence is run repeatedly: * reset forces on bodies from previous step * approximate collision detection (pass 1) * detect exact collisions of bodies, update interactions as necessary * solve interactions, applying forces on bodies * apply other external conditions (gravity, for instance). * change position of bodies based on forces, by integrating motion equations. .. _img-yade-iter-loop: .. figure:: fig/yade-iter-loop.* Typical simulation loop; each step begins at body-centered bit at 11 o'clock, continues with interaction bit, force application bit, miscillanea and ends with time update. Each of these actions is represented by an :yref:`Engine`, functional element of simulation. The sequence of engines is called *simulation loop*. .. _sect-simulation-loop: Engines """"""""" Simulation loop, shown at img. img-yade-iter-loop_, can be described as follows in Python (details will be explained later); each of the ``O.engine`` items is instance of a type deriving from :yref:`Engine`: .. code-block:: python O.engines=[ # reset forces ForceResetter(), # approximate collision detection, create interactions InsertionSortCollider([Bo1_Sphere_Aabb(),Bo1_Facet_Aabb()]), # handle interactions InteractionLoop( [Ig2_Sphere_Sphere_ScGeom(),Ig2_Facet_Sphere_ScGeom()], [Ip2_FrictMat_FrictMat_FrictPhys()], [Law2_ScGeom_FrictPhys_CundallStrack()], ), # apply other conditions GravityEngine(gravity=(0,0,-9.81)), # update positions using Newton's equations NewtonIntegrator() ] There are 3 fundamental types of Engines: :yref:`GlobalEngines` operating on the whole simulation (e.g. :yref:`GravityEngine` looping over all bodies and applying force based on their mass) :yref:`PartialEngine` operating only on some pre-selected bodies (e.g. :yref:`ForceEngine` applying constant force to some bodies) :yref:`Dispatchers` do not perform any computation themselves; they merely call other functions, represented by function objects, :yref:`Functors`. Each functor is specialized, able to handle certain object types, and will be dispatched if such obejct is treated by the dispatcher. .. _dispatchers-and-functors: Dispatchers and functors """"""""""""""""""""""""" For approximate collision detection (pass 1), we want to compute :yref:`bounds` for all :yref:`bodies` in the simulation; suppose we want bound of type :yref:`axis-aligned bounding box`. Since the exact algorithm is different depending on particular :yref:`shape`, we need to provide functors for handling all specific cases. The line:: InsertionSortCollider([Bo1_Sphere_Aabb(),Bo1_Facet_Aabb()]) creates :yref:`InsertionSortCollider` (it internally uses :yref:`BoundDispatcher`, but that is a detail). It traverses all bodies and will, based on :yref:`shape` type of each :yref:`body`, dispatch one of the functors to create/update :yref:`bound` for that particular body. In the case shown, it has 2 functors, one handling :yref:`spheres`, another :yref:`facets`. The name is composed from several parts: ``Bo`` (functor creating :yref:`Bound`), which accepts ``1`` type :yref:`Sphere` and creates an :yref:`Aabb` (axis-aligned bounding box; it is derived from :yref:`Bound`). The :yref:`Aabb` objects are used by :yref:`InsertionSortCollider` itself. All ``Bo1`` functors derive from :yref:`BoundFunctor`. The next part, reading .. code-block:: python InteractionLoop( [Ig2_Sphere_Sphere_ScGeom(),Ig2_Facet_Sphere_ScGeom()], [Ip2_FrictMat_FrictMat_FrictPhys()], [Law2_ScGeom_FrictPhys_CundallStrack()], ), hides 3 internal dispatchers within the :yref:`InteractionLoop` engine; they all operate on interactions and are, for performance reasons, put together: :yref:`IGeomDispatcher` uses the first set of functors (``Ig2``), which are dispatched based on combination of ``2`` :yref:`Shapes` objects. Dispatched functor resolves exact collision configuration and creates :yref:`IGeom` (whence ``Ig`` in the name) associated with the interaction, if there is collision. The functor might as well fail on approximate interactions, indicating there is no real contact between the bodies, even if they did overlap in the approximate collision detection. #. The first functor, :yref:`Ig2_Sphere_Sphere_ScGeom`, is called on interaction of 2 :yref:`Spheres` and creates :yref:`ScGeom` instance, if appropriate. #. The second functor, :yref:`Ig2_Facet_Sphere_ScGeom`, is called for interaction of :yref:`Facet` with :yref:`Sphere` and might create (again) a :yref:`ScGeom` instance. All ``Ig2`` functors derive from :yref:`IGeomFunctor` (they are documented at the same place). :yref:`IPhysDispatcher` dispatches to the second set of functors based on combination of ``2`` :yref:`Materials`; these functors return return :yref:`IPhys` instance (the ``Ip`` prefix). In our case, there is only 1 functor used, :yref:`Ip2_FrictMat_FrictMat_FrictPhys`, which create :yref:`FrictPhys` from 2 :yref:`FrictMat's`. ``Ip2`` functors are derived from :yref:`IPhysFunctor`. :yref:`LawDispatcher` dispatches to the third set of functors, based on combinations of :yref:`IGeom` and :yref:`IPhys` (wherefore ``2`` in their name again) of each particular interaction, created by preceding functors. The ``Law2`` functors represent "constitutive law"; they resolve the interaction by computing forces on the interacting bodies (repulsion, attraction, shear forces, …) or otherwise update interaction state variables. ``Law2`` functors all inherit from :yref:`LawFunctor`. There is chain of types produced by earlier functors and accepted by later ones; the user is responsible to satisfy type requirement (see img. img-dispatch-loop_). An exception (with explanation) is raised in the contrary case. .. _img-dispatch-loop: .. figure:: fig/dispatch-loop.* :width: 13cm Chain of functors producing and accepting certain types. In the case shown, the ``Ig2`` functors produce :yref:`ScGeom` instances from all handled :yref:`Shape` combinations; the ``Ig2`` functor produces :yref:`FrictMat`. The constitutive law functor ``Law2`` accepts the combination of types produced. Note that the types are stated in the functor's class names. .. note:: When Yade starts, O.engines is filled with a reasonable default list, so that it is not strictly necessary to redefine it when trying simple things. The default scene will handle spheres, boxes, and facets with :yref:`frictional` properties correctly, and adjusts the timestep dynamically. You can find an example in simple-scene-default-engines.py. trunk-2018.02b/doc/sphinx/ipython_console_highlighting.py000066400000000000000000000101271324306050200235600ustar00rootroot00000000000000"""reST directive for syntax-highlighting ipython interactive sessions. XXX - See what improvements can be made based on the new (as of Sept 2009) 'pycon' lexer for the python console. At the very least it will give better highlighted tracebacks. """ #----------------------------------------------------------------------------- # Needed modules # Standard library import re # Third party from pygments.lexer import Lexer, do_insertions from pygments.lexers.agile import (PythonConsoleLexer, PythonLexer, PythonTracebackLexer) from pygments.token import Comment, Generic from sphinx import highlighting #----------------------------------------------------------------------------- # Global constants line_re = re.compile('.*?\n') #----------------------------------------------------------------------------- # Code begins - classes and functions class IPythonConsoleLexer(Lexer): """ For IPython console output or doctests, such as: .. sourcecode:: ipython In [1]: a = 'foo' In [2]: a Out[2]: 'foo' In [3]: print a foo In [4]: 1 / 0 Notes: - Tracebacks are not currently supported. - It assumes the default IPython prompts, not customized ones. """ name = 'IPython console session' aliases = ['ipython'] mimetypes = ['text/x-ipython-console'] input_prompt = re.compile("(In \[[0-9]+\]: )|( \.\.\.+:)") output_prompt = re.compile("(Out\[[0-9]+\]: )|( \.\.\.+:)") continue_prompt = re.compile(" \.\.\.+:") tb_start = re.compile("\-+") def get_tokens_unprocessed(self, text): pylexer = PythonLexer(**self.options) tblexer = PythonTracebackLexer(**self.options) curcode = '' insertions = [] for match in line_re.finditer(text): line = match.group() input_prompt = self.input_prompt.match(line) continue_prompt = self.continue_prompt.match(line.rstrip()) output_prompt = self.output_prompt.match(line) if line.startswith("#"): insertions.append((len(curcode), [(0, Comment, line)])) elif input_prompt is not None: insertions.append((len(curcode), [(0, Generic.Prompt, input_prompt.group())])) curcode += line[input_prompt.end():] elif continue_prompt is not None: insertions.append((len(curcode), [(0, Generic.Prompt, continue_prompt.group())])) curcode += line[continue_prompt.end():] elif output_prompt is not None: # Use the 'error' token for output. We should probably make # our own token, but error is typicaly in a bright color like # red, so it works fine for our output prompts. insertions.append((len(curcode), [(0, Generic.Error, output_prompt.group())])) curcode += line[output_prompt.end():] else: if curcode: for item in do_insertions(insertions, pylexer.get_tokens_unprocessed(curcode)): yield item curcode = '' insertions = [] yield match.start(), Generic.Output, line if curcode: for item in do_insertions(insertions, pylexer.get_tokens_unprocessed(curcode)): yield item def setup(app): """Setup as a sphinx extension.""" # This is only a lexer, so adding it below to pygments appears sufficient. # But if somebody knows that the right API usage should be to do that via # sphinx, by all means fix it here. At least having this setup.py # suppresses the sphinx warning we'd get without it. pass #----------------------------------------------------------------------------- # Register the extension as a valid pygments lexer highlighting.lexers['ipython'] = IPythonConsoleLexer() trunk-2018.02b/doc/sphinx/ipython_directive.py000066400000000000000000000442231324306050200213530ustar00rootroot00000000000000# -*- coding: utf-8 -*- """Sphinx directive to support embedded IPython code. This directive allows pasting of entire interactive IPython sessions, prompts and all, and their code will actually get re-executed at doc build time, with all prompts renumbered sequentially. To enable this directive, simply list it in your Sphinx ``conf.py`` file (making sure the directory where you placed it is visible to sphinx, as is needed for all Sphinx directives). By default this directive assumes that your prompts are unchanged IPython ones, but this can be customized. For example, the following code in your Sphinx config file will configure this directive for the following input/output prompts ``Yade [1]:`` and ``-> [1]:``:: import ipython_directive as id id.rgxin =re.compile(r'(?:In |Yade )\[(\d+)\]:\s?(.*)\s*') id.rgxout=re.compile(r'(?:Out| -> )\[(\d+)\]:\s?(.*)\s*') id.fmtin ='Yade [%d]:' id.fmtout=' -> [%d]:' id.rc_override=dict( prompt_in1="Yade [\#]:", prompt_in2=" .\D..", prompt_out=" -> [\#]:" ) id.reconfig_shell() import ipython_console_highlighting as ich ich.IPythonConsoleLexer.input_prompt= re.compile("(Yade \[[0-9]+\]: )|( \.\.\.+:)") ich.IPythonConsoleLexer.output_prompt= re.compile("(( -> )|(Out)\[[0-9]+\]: )|( \.\.\.+:)") ich.IPythonConsoleLexer.continue_prompt=re.compile(" \.\.\.+:") ToDo ---- - Turn the ad-hoc test() function into a real test suite. - Break up ipython-specific functionality from matplotlib stuff into better separated code. - Make sure %bookmarks used internally are removed on exit. Authors ------- - John D Hunter: orignal author. - Fernando Perez: refactoring, documentation, cleanups. - VáclavŠmilauer : Prompt generatlizations. """ #----------------------------------------------------------------------------- # Imports #----------------------------------------------------------------------------- # Stdlib import cStringIO import imp import os import re import shutil import sys import warnings # To keep compatibility with various python versions try: from hashlib import md5 except ImportError: from md5 import md5 # Third-party import matplotlib import sphinx from docutils.parsers.rst import directives matplotlib.use('Agg') # Our own import IPython from IPython.Shell import MatplotlibShell #----------------------------------------------------------------------------- # Globals #----------------------------------------------------------------------------- sphinx_version = sphinx.__version__.split(".") # The split is necessary for sphinx beta versions where the string is # '6b1' sphinx_version = tuple([int(re.split('[a-z]', x)[0]) for x in sphinx_version[:2]]) COMMENT, INPUT, OUTPUT = range(3) rc_override = {} rgxin = re.compile('In \[(\d+)\]:\s?(.*)\s*') rgxcont = re.compile(' \.+:\s?(.*)\s*') rgxout = re.compile('Out\[(\d+)\]:\s?(.*)\s*') fmtin = 'In [%d]:' fmtout = 'Out[%d]:' fmtcont = ' .\D.:' #----------------------------------------------------------------------------- # Functions and class declarations #----------------------------------------------------------------------------- def block_parser(part): """ part is a string of ipython text, comprised of at most one input, one ouput, comments, and blank lines. The block parser parses the text into a list of:: blocks = [ (TOKEN0, data0), (TOKEN1, data1), ...] where TOKEN is one of [COMMENT | INPUT | OUTPUT ] and data is, depending on the type of token:: COMMENT : the comment string INPUT: the (DECORATOR, INPUT_LINE, REST) where DECORATOR: the input decorator (or None) INPUT_LINE: the input as string (possibly multi-line) REST : any stdout generated by the input line (not OUTPUT) OUTPUT: the output string, possibly multi-line """ block = [] lines = part.split('\n') N = len(lines) i = 0 decorator = None while 1: if i==N: # nothing left to parse -- the last line break line = lines[i] i += 1 line_stripped = line.strip() if line_stripped.startswith('#'): block.append((COMMENT, line)) continue if line_stripped.startswith('@'): # we're assuming at most one decorator -- may need to # rethink decorator = line_stripped continue # does this look like an input line? matchin = rgxin.match(line) if matchin: lineno, inputline = int(matchin.group(1)), matchin.group(2) # the ....: continuation string #continuation = ' %s:'%''.join(['.']*(len(str(lineno))+2)) #Nc = len(continuation) # input lines can continue on for more than one line, if # we have a '\' line continuation char or a function call # echo line 'print'. The input line can only be # terminated by the end of the block or an output line, so # we parse out the rest of the input line if it is # multiline as well as any echo text rest = [] while i2: if debug: print '\n'.join(lines) else: #print 'INSERTING %d lines'%len(lines) state_machine.insert_input( lines, state_machine.input_lines.source(0)) return [] ipython_directive.DEBUG = False # Enable as a proper Sphinx directive def setup(app): setup.app = app options = {'suppress': directives.flag, 'doctest': directives.flag, 'verbatim': directives.flag, } app.add_directive('ipython', ipython_directive, True, (0, 2, 0), **options) # Simple smoke test, needs to be converted to a proper automatic test. def test(): examples = [ r""" In [9]: pwd Out[9]: '/home/jdhunter/py4science/book' In [10]: cd bookdata/ /home/jdhunter/py4science/book/bookdata In [2]: from pylab import * In [2]: ion() In [3]: im = imread('stinkbug.png') @savefig mystinkbug.png width=4in In [4]: imshow(im) Out[4]: """, r""" In [1]: x = 'hello world' # string methods can be # used to alter the string @doctest In [2]: x.upper() Out[2]: 'HELLO WORLD' @verbatim In [3]: x.st x.startswith x.strip """, r""" In [130]: url = 'http://ichart.finance.yahoo.com/table.csv?s=CROX\ .....: &d=9&e=22&f=2009&g=d&a=1&br=8&c=2006&ignore=.csv' In [131]: print url.split('&') ['http://ichart.finance.yahoo.com/table.csv?s=CROX', 'd=9', 'e=22', 'f=2009', 'g=d', 'a=1', 'b=8', 'c=2006', 'ignore=.csv'] In [60]: import urllib """, r"""\ In [133]: import numpy.random @suppress In [134]: numpy.random.seed(2358) @doctest In [135]: np.random.rand(10,2) Out[135]: array([[ 0.64524308, 0.59943846], [ 0.47102322, 0.8715456 ], [ 0.29370834, 0.74776844], [ 0.99539577, 0.1313423 ], [ 0.16250302, 0.21103583], [ 0.81626524, 0.1312433 ], [ 0.67338089, 0.72302393], [ 0.7566368 , 0.07033696], [ 0.22591016, 0.77731835], [ 0.0072729 , 0.34273127]]) """, r""" In [106]: print x jdh In [109]: for i in range(10): .....: print i .....: .....: 0 1 2 3 4 5 6 7 8 9 """, r""" In [144]: from pylab import * In [145]: ion() # use a semicolon to suppress the output @savefig test_hist.png width=4in In [151]: hist(np.random.randn(10000), 100); @savefig test_plot.png width=4in In [151]: plot(np.random.randn(10000), 'o'); """, r""" # use a semicolon to suppress the output In [151]: plt.clf() @savefig plot_simple.png width=4in In [151]: plot([1,2,3]) @savefig hist_simple.png width=4in In [151]: hist(np.random.randn(10000), 100); """, r""" # update the current fig In [151]: ylabel('number') In [152]: title('normal distribution') @savefig hist_with_text.png In [153]: grid(True) """, ] ipython_directive.DEBUG = True #options = dict(suppress=True) options = dict() for example in examples: content = example.split('\n') ipython_directive('debug', arguments=None, options=options, content=content, lineno=0, content_offset=None, block_text=None, state=None, state_machine=None, ) # Run test suite as a script if __name__=='__main__': test() trunk-2018.02b/doc/sphinx/ipython_directive012.py000066400000000000000000000650671324306050200216070ustar00rootroot00000000000000# -*- coding: utf-8 -*- """Sphinx directive to support embedded IPython code. This directive allows pasting of entire interactive IPython sessions, prompts and all, and their code will actually get re-executed at doc build time, with all prompts renumbered sequentially. It also allows you to input code as a pure python input by giving the argument python to the directive. The output looks like an interactive ipython section. To enable this directive, simply list it in your Sphinx ``conf.py`` file (making sure the directory where you placed it is visible to sphinx, as is needed for all Sphinx directives). By default this directive assumes that your prompts are unchanged IPython ones, but this can be customized. The configurable options that can be placed in conf.py are ipython_savefig_dir: The directory in which to save the figures. This is relative to the Sphinx source directory. The default is `html_static_path`. ipython_rgxin: The compiled regular expression to denote the start of IPython input lines. The default is re.compile('In \[(\d+)\]:\s?(.*)\s*'). You shouldn't need to change this. ipython_rgxout: The compiled regular expression to denote the start of IPython output lines. The default is re.compile('Out\[(\d+)\]:\s?(.*)\s*'). You shouldn't need to change this. ipython_promptin: The string to represent the IPython input prompt in the generated ReST. The default is 'In [%d]:'. This expects that the line numbers are used in the prompt. ipython_promptout: The string to represent the IPython prompt in the generated ReST. The default is 'Out [%d]:'. This expects that the line numbers are used in the prompt. ToDo ---- - Turn the ad-hoc test() function into a real test suite. - Break up ipython-specific functionality from matplotlib stuff into better separated code. Authors ------- - John D Hunter: orignal author. - Fernando Perez: refactoring, documentation, cleanups, port to 0.11. - VáclavŠmilauer : Prompt generalizations. - Skipper Seabold, refactoring, cleanups, pure python addition """ #----------------------------------------------------------------------------- # Imports #----------------------------------------------------------------------------- # Stdlib import cStringIO import os import re import sys import tempfile import ast # To keep compatibility with various python versions try: from hashlib import md5 except ImportError: from md5 import md5 # Third-party import matplotlib import sphinx from docutils.parsers.rst import directives from docutils import nodes from sphinx.util.compat import Directive matplotlib.use('Agg') # Our own from IPython import Config, InteractiveShell from IPython.core.profiledir import ProfileDir from IPython.utils import io #----------------------------------------------------------------------------- # Globals #----------------------------------------------------------------------------- # for tokenizing blocks COMMENT, INPUT, OUTPUT = range(3) #----------------------------------------------------------------------------- # Functions and class declarations #----------------------------------------------------------------------------- def block_parser(part, rgxin, rgxout, fmtin, fmtout): """ part is a string of ipython text, comprised of at most one input, one ouput, comments, and blank lines. The block parser parses the text into a list of:: blocks = [ (TOKEN0, data0), (TOKEN1, data1), ...] where TOKEN is one of [COMMENT | INPUT | OUTPUT ] and data is, depending on the type of token:: COMMENT : the comment string INPUT: the (DECORATOR, INPUT_LINE, REST) where DECORATOR: the input decorator (or None) INPUT_LINE: the input as string (possibly multi-line) REST : any stdout generated by the input line (not OUTPUT) OUTPUT: the output string, possibly multi-line """ block = [] lines = part.split('\n') N = len(lines) i = 0 decorator = None while 1: if i==N: # nothing left to parse -- the last line break line = lines[i] i += 1 line_stripped = line.strip() if line_stripped.startswith('#'): block.append((COMMENT, line)) continue if line_stripped.startswith('@'): # we're assuming at most one decorator -- may need to # rethink decorator = line_stripped continue # does this look like an input line? matchin = rgxin.match(line) if matchin: lineno, inputline = int(matchin.group(1)), matchin.group(2) # the ....: continuation string continuation = ' %s:'%''.join(['.']*(len(str(lineno))+2)) Nc = len(continuation) # input lines can continue on for more than one line, if # we have a '\' line continuation char or a function call # echo line 'print'. The input line can only be # terminated by the end of the block or an output line, so # we parse out the rest of the input line if it is # multiline as well as any echo text rest = [] while i 1: if input_lines[-1] != "": input_lines.append('') # make sure there's a blank line # so splitter buffer gets reset continuation = ' %s:'%''.join(['.']*(len(str(lineno))+2)) Nc = len(continuation) if is_savefig: image_file, image_directive = self.process_image(decorator) ret = [] is_semicolon = False for i, line in enumerate(input_lines): if line.endswith(';'): is_semicolon = True if i==0: # process the first input line if is_verbatim: self.process_input_line('') self.IP.execution_count += 1 # increment it anyway else: # only submit the line in non-verbatim mode self.process_input_line(line, store_history=True) formatted_line = '%s %s'%(input_prompt, line) else: # process a continuation line if not is_verbatim: self.process_input_line(line, store_history=True) formatted_line = '%s %s'%(continuation, line) if not is_suppress: ret.append(formatted_line) if not is_suppress and len(rest.strip()) and is_verbatim: # the "rest" is the standard output of the # input, which needs to be added in # verbatim mode ret.append(rest) self.cout.seek(0) output = self.cout.read() if not is_suppress and not is_semicolon: ret.append(output) elif is_semicolon: # get spacing right ret.append('') self.cout.truncate(0) return (ret, input_lines, output, is_doctest, image_file, image_directive) #print 'OUTPUT', output # dbg def process_output(self, data, output_prompt, input_lines, output, is_doctest, image_file): """Process data block for OUTPUT token.""" if is_doctest: submitted = data.strip() found = output if found is not None: found = found.strip() # XXX - fperez: in 0.11, 'output' never comes with the prompt # in it, just the actual output text. So I think all this code # can be nuked... # the above comment does not appear to be accurate... (minrk) ind = found.find(output_prompt) if ind<0: e='output prompt="%s" does not match out line=%s' % \ (output_prompt, found) raise RuntimeError(e) found = found[len(output_prompt):].strip() if found!=submitted: e = ('doctest failure for input_lines="%s" with ' 'found_output="%s" and submitted output="%s"' % (input_lines, found, submitted) ) raise RuntimeError(e) #print 'doctest PASSED for input_lines="%s" with found_output="%s" and submitted output="%s"'%(input_lines, found, submitted) def process_comment(self, data): """Process data fPblock for COMMENT token.""" if not self.is_suppress: return [data] def save_image(self, image_file): """ Saves the image file to disk. """ self.ensure_pyplot() command = 'plt.gcf().savefig("%s")'%image_file #print 'SAVEFIG', command # dbg self.process_input_line('bookmark ipy_thisdir', store_history=False) self.process_input_line('cd -b ipy_savedir', store_history=False) self.process_input_line(command, store_history=False) self.process_input_line('cd -b ipy_thisdir', store_history=False) self.process_input_line('bookmark -d ipy_thisdir', store_history=False) self.clear_cout() def process_block(self, block): """ process block from the block_parser and return a list of processed lines """ ret = [] output = None input_lines = None lineno = self.IP.execution_count input_prompt = self.promptin%lineno output_prompt = self.promptout%lineno image_file = None image_directive = None for token, data in block: if token==COMMENT: out_data = self.process_comment(data) elif token==INPUT: (out_data, input_lines, output, is_doctest, image_file, image_directive) = \ self.process_input(data, input_prompt, lineno) elif token==OUTPUT: out_data = \ self.process_output(data, output_prompt, input_lines, output, is_doctest, image_file) if out_data: ret.extend(out_data) # save the image files if image_file is not None: self.save_image(image_file) return ret, image_directive def ensure_pyplot(self): if self._pyplot_imported: return self.process_input_line('import matplotlib.pyplot as plt', store_history=False) def process_pure_python(self, content): """ content is a list of strings. it is unedited directive conent This runs it line by line in the InteractiveShell, prepends prompts as needed capturing stderr and stdout, then returns the content as a list as if it were ipython code """ output = [] savefig = False # keep up with this to clear figure multiline = False # to handle line continuation multiline_start = None fmtin = self.promptin ct = 0 for lineno, line in enumerate(content): line_stripped = line.strip() if not len(line): output.append(line) continue # handle decorators if line_stripped.startswith('@'): output.extend([line]) if 'savefig' in line: savefig = True # and need to clear figure continue # handle comments if line_stripped.startswith('#'): output.extend([line]) continue # deal with lines checking for multiline continuation = u' %s:'% ''.join(['.']*(len(str(ct))+2)) if not multiline: modified = u"%s %s" % (fmtin % ct, line_stripped) output.append(modified) ct += 1 try: ast.parse(line_stripped) output.append(u'') except Exception: # on a multiline multiline = True multiline_start = lineno else: # still on a multiline modified = u'%s %s' % (continuation, line) output.append(modified) try: mod = ast.parse( '\n'.join(content[multiline_start:lineno+1])) if isinstance(mod.body[0], ast.FunctionDef): # check to see if we have the whole function for element in mod.body[0].body: if isinstance(element, ast.Return): multiline = False else: output.append(u'') multiline = False except Exception: pass if savefig: # clear figure if plotted self.ensure_pyplot() self.process_input_line('plt.clf()', store_history=False) self.clear_cout() savefig = False return output class IpythonDirective(Directive): has_content = True required_arguments = 0 optional_arguments = 4 # python, suppress, verbatim, doctest final_argumuent_whitespace = True option_spec = { 'python': directives.unchanged, 'suppress' : directives.flag, 'verbatim' : directives.flag, 'doctest' : directives.flag, } shell = EmbeddedSphinxShell() def get_config_options(self): # contains sphinx configuration variables config = self.state.document.settings.env.config # get config variables to set figure output directory confdir = self.state.document.settings.env.app.confdir savefig_dir = config.ipython_savefig_dir source_dir = os.path.dirname(self.state.document.current_source) if savefig_dir is None: savefig_dir = config.html_static_path if isinstance(savefig_dir, list): savefig_dir = savefig_dir[0] # safe to assume only one path? savefig_dir = os.path.join(confdir, savefig_dir) # get regex and prompt stuff rgxin = config.ipython_rgxin rgxout = config.ipython_rgxout promptin = config.ipython_promptin promptout = config.ipython_promptout return savefig_dir, source_dir, rgxin, rgxout, promptin, promptout def setup(self): # reset the execution count if we haven't processed this doc #NOTE: this may be borked if there are multiple seen_doc tmp files #check time stamp? seen_docs = [i for i in os.listdir(tempfile.tempdir) if i.startswith('seen_doc')] if seen_docs: fname = os.path.join(tempfile.tempdir, seen_docs[0]) docs = open(fname).read().split('\n') if not self.state.document.current_source in docs: self.shell.IP.history_manager.reset() self.shell.IP.execution_count = 1 else: # haven't processed any docs yet docs = [] # get config values (savefig_dir, source_dir, rgxin, rgxout, promptin, promptout) = self.get_config_options() # and attach to shell so we don't have to pass them around self.shell.rgxin = rgxin self.shell.rgxout = rgxout self.shell.promptin = promptin self.shell.promptout = promptout self.shell.savefig_dir = savefig_dir self.shell.source_dir = source_dir # setup bookmark for saving figures directory self.shell.process_input_line('bookmark ipy_savedir %s'%savefig_dir, store_history=False) self.shell.clear_cout() # write the filename to a tempfile because it's been "seen" now if not self.state.document.current_source in docs: fd, fname = tempfile.mkstemp(prefix="seen_doc", text=True) fout = open(fname, 'a') fout.write(self.state.document.current_source+'\n') fout.close() return rgxin, rgxout, promptin, promptout def teardown(self): # delete last bookmark self.shell.process_input_line('bookmark -d ipy_savedir', store_history=False) self.shell.clear_cout() def run(self): debug = False #TODO, any reason block_parser can't be a method of embeddable shell # then we wouldn't have to carry these around rgxin, rgxout, promptin, promptout = self.setup() options = self.options self.shell.is_suppress = 'suppress' in options self.shell.is_doctest = 'doctest' in options self.shell.is_verbatim = 'verbatim' in options # handle pure python code if 'python' in self.arguments: content = self.content self.content = self.shell.process_pure_python(content) parts = '\n'.join(self.content).split('\n\n') lines = ['.. code-block:: ipython',''] figures = [] for part in parts: block = block_parser(part, rgxin, rgxout, promptin, promptout) if len(block): rows, figure = self.shell.process_block(block) for row in rows: lines.extend([' %s'%line for line in row.split('\n')]) if figure is not None: figures.append(figure) #text = '\n'.join(lines) #figs = '\n'.join(figures) for figure in figures: lines.append('') lines.extend(figure.split('\n')) lines.append('') #print lines if len(lines)>2: if debug: print '\n'.join(lines) else: #NOTE: this raises some errors, what's it for? #print 'INSERTING %d lines'%len(lines) self.state_machine.insert_input( lines, self.state_machine.input_lines.source(0)) text = '\n'.join(lines) txtnode = nodes.literal_block(text, text) txtnode['language'] = 'ipython' #imgnode = nodes.image(figs) # cleanup self.teardown() return []#, imgnode] # Enable as a proper Sphinx directive def setup(app): setup.app = app app.add_directive('ipython', IpythonDirective) app.add_config_value('ipython_savefig_dir', None, True) app.add_config_value('ipython_rgxin', re.compile('In \[(\d+)\]:\s?(.*)\s*'), True) app.add_config_value('ipython_rgxout', re.compile('Out\[(\d+)\]:\s?(.*)\s*'), True) app.add_config_value('ipython_promptin', 'In [%d]:', True) app.add_config_value('ipython_promptout', 'Out[%d]:', True) # Simple smoke test, needs to be converted to a proper automatic test. def test(): examples = [ r""" In [9]: pwd Out[9]: '/home/jdhunter/py4science/book' In [10]: cd bookdata/ /home/jdhunter/py4science/book/bookdata In [2]: from pylab import * In [2]: ion() In [3]: im = imread('stinkbug.png') @savefig mystinkbug.png width=4in In [4]: imshow(im) Out[4]: """, r""" In [1]: x = 'hello world' # string methods can be # used to alter the string @doctest In [2]: x.upper() Out[2]: 'HELLO WORLD' @verbatim In [3]: x.st x.startswith x.strip """, r""" In [130]: url = 'http://ichart.finance.yahoo.com/table.csv?s=CROX\ .....: &d=9&e=22&f=2009&g=d&a=1&br=8&c=2006&ignore=.csv' In [131]: print url.split('&') ['http://ichart.finance.yahoo.com/table.csv?s=CROX', 'd=9', 'e=22', 'f=2009', 'g=d', 'a=1', 'b=8', 'c=2006', 'ignore=.csv'] In [60]: import urllib """, r"""\ In [133]: import numpy.random @suppress In [134]: numpy.random.seed(2358) @doctest In [135]: numpy.random.rand(10,2) Out[135]: array([[ 0.64524308, 0.59943846], [ 0.47102322, 0.8715456 ], [ 0.29370834, 0.74776844], [ 0.99539577, 0.1313423 ], [ 0.16250302, 0.21103583], [ 0.81626524, 0.1312433 ], [ 0.67338089, 0.72302393], [ 0.7566368 , 0.07033696], [ 0.22591016, 0.77731835], [ 0.0072729 , 0.34273127]]) """, r""" In [106]: print x jdh In [109]: for i in range(10): .....: print i .....: .....: 0 1 2 3 4 5 6 7 8 9 """, r""" In [144]: from pylab import * In [145]: ion() # use a semicolon to suppress the output @savefig test_hist.png width=4in In [151]: hist(np.random.randn(10000), 100); @savefig test_plot.png width=4in In [151]: plot(np.random.randn(10000), 'o'); """, r""" # use a semicolon to suppress the output In [151]: plt.clf() @savefig plot_simple.png width=4in In [151]: plot([1,2,3]) @savefig hist_simple.png width=4in In [151]: hist(np.random.randn(10000), 100); """, r""" # update the current fig In [151]: ylabel('number') In [152]: title('normal distribution') @savefig hist_with_text.png In [153]: grid(True) """, ] # skip local-file depending first example: examples = examples[1:] #ipython_directive.DEBUG = True # dbg #options = dict(suppress=True) # dbg options = dict() for example in examples: content = example.split('\n') ipython_directive('debug', arguments=None, options=options, content=content, lineno=0, content_offset=None, block_text=None, state=None, state_machine=None, ) # Run test suite as a script if __name__=='__main__': if not os.path.isdir('_static'): os.mkdir('_static') test() print 'All OK? Check figures in _static/' trunk-2018.02b/doc/sphinx/ipython_directive013.py000066400000000000000000000652201324306050200215770ustar00rootroot00000000000000# -*- coding: utf-8 -*- """Sphinx directive to support embedded IPython code. From: https://github.com/ipython/ipython/blob/master/docs/sphinxext/ipython_directive.py This directive allows pasting of entire interactive IPython sessions, prompts and all, and their code will actually get re-executed at doc build time, with all prompts renumbered sequentially. It also allows you to input code as a pure python input by giving the argument python to the directive. The output looks like an interactive ipython section. To enable this directive, simply list it in your Sphinx ``conf.py`` file (making sure the directory where you placed it is visible to sphinx, as is needed for all Sphinx directives). By default this directive assumes that your prompts are unchanged IPython ones, but this can be customized. The configurable options that can be placed in conf.py are ipython_savefig_dir: The directory in which to save the figures. This is relative to the Sphinx source directory. The default is `html_static_path`. ipython_rgxin: The compiled regular expression to denote the start of IPython input lines. The default is re.compile('In \[(\d+)\]:\s?(.*)\s*'). You shouldn't need to change this. ipython_rgxout: The compiled regular expression to denote the start of IPython output lines. The default is re.compile('Out\[(\d+)\]:\s?(.*)\s*'). You shouldn't need to change this. ipython_promptin: The string to represent the IPython input prompt in the generated ReST. The default is 'In [%d]:'. This expects that the line numbers are used in the prompt. ipython_promptout: The string to represent the IPython prompt in the generated ReST. The default is 'Out [%d]:'. This expects that the line numbers are used in the prompt. ToDo ---- - Turn the ad-hoc test() function into a real test suite. - Break up ipython-specific functionality from matplotlib stuff into better separated code. Authors ------- - John D Hunter: orignal author. - Fernando Perez: refactoring, documentation, cleanups, port to 0.11. - VáclavŠmilauer : Prompt generalizations. - Skipper Seabold, refactoring, cleanups, pure python addition """ #----------------------------------------------------------------------------- # Imports #----------------------------------------------------------------------------- # Stdlib import cStringIO import os import re import sys import tempfile import ast # To keep compatibility with various python versions try: from hashlib import md5 except ImportError: from md5 import md5 # Third-party import matplotlib import sphinx from docutils.parsers.rst import directives from docutils import nodes from sphinx.util.compat import Directive matplotlib.use('Agg') # Our own from IPython import Config, InteractiveShell from IPython.core.profiledir import ProfileDir from IPython.utils import io #----------------------------------------------------------------------------- # Globals #----------------------------------------------------------------------------- # for tokenizing blocks COMMENT, INPUT, OUTPUT = range(3) #----------------------------------------------------------------------------- # Functions and class declarations #----------------------------------------------------------------------------- def block_parser(part, rgxin, rgxout, fmtin, fmtout): """ part is a string of ipython text, comprised of at most one input, one ouput, comments, and blank lines. The block parser parses the text into a list of:: blocks = [ (TOKEN0, data0), (TOKEN1, data1), ...] where TOKEN is one of [COMMENT | INPUT | OUTPUT ] and data is, depending on the type of token:: COMMENT : the comment string INPUT: the (DECORATOR, INPUT_LINE, REST) where DECORATOR: the input decorator (or None) INPUT_LINE: the input as string (possibly multi-line) REST : any stdout generated by the input line (not OUTPUT) OUTPUT: the output string, possibly multi-line """ block = [] lines = part.split('\n') N = len(lines) i = 0 decorator = None while 1: if i==N: # nothing left to parse -- the last line break line = lines[i] i += 1 line_stripped = line.strip() if line_stripped.startswith('#'): block.append((COMMENT, line)) continue if line_stripped.startswith('@'): # we're assuming at most one decorator -- may need to # rethink decorator = line_stripped continue # does this look like an input line? matchin = rgxin.match(line) if matchin: lineno, inputline = int(matchin.group(1)), matchin.group(2) # the ....: continuation string continuation = ' %s:'%''.join(['.']*(len(str(lineno))+2)) Nc = len(continuation) # input lines can continue on for more than one line, if # we have a '\' line continuation char or a function call # echo line 'print'. The input line can only be # terminated by the end of the block or an output line, so # we parse out the rest of the input line if it is # multiline as well as any echo text rest = [] while i 1: if input_lines[-1] != "": input_lines.append('') # make sure there's a blank line # so splitter buffer gets reset continuation = ' %s:'%''.join(['.']*(len(str(lineno))+2)) Nc = len(continuation) if is_savefig: image_file, image_directive = self.process_image(decorator) ret = [] is_semicolon = False for i, line in enumerate(input_lines): if line.endswith(';'): is_semicolon = True if i==0: # process the first input line if is_verbatim: self.process_input_line('') self.IP.execution_count += 1 # increment it anyway else: # only submit the line in non-verbatim mode self.process_input_line(line, store_history=True) formatted_line = '%s %s'%(input_prompt, line) else: # process a continuation line if not is_verbatim: self.process_input_line(line, store_history=True) formatted_line = '%s %s'%(continuation, line) if not is_suppress: ret.append(formatted_line) if not is_suppress and len(rest.strip()) and is_verbatim: # the "rest" is the standard output of the # input, which needs to be added in # verbatim mode ret.append(rest) self.cout.seek(0) output = self.cout.read() if not is_suppress and not is_semicolon: ret.append(output) elif is_semicolon: # get spacing right ret.append('') self.cout.truncate(0) return (ret, input_lines, output, is_doctest, image_file, image_directive) #print 'OUTPUT', output # dbg def process_output(self, data, output_prompt, input_lines, output, is_doctest, image_file): """Process data block for OUTPUT token.""" if is_doctest: submitted = data.strip() found = output if found is not None: found = found.strip() # XXX - fperez: in 0.11, 'output' never comes with the prompt # in it, just the actual output text. So I think all this code # can be nuked... # the above comment does not appear to be accurate... (minrk) ind = found.find(output_prompt) if ind<0: e='output prompt="%s" does not match out line=%s' % \ (output_prompt, found) raise RuntimeError(e) found = found[len(output_prompt):].strip() if found!=submitted: e = ('doctest failure for input_lines="%s" with ' 'found_output="%s" and submitted output="%s"' % (input_lines, found, submitted) ) raise RuntimeError(e) #print 'doctest PASSED for input_lines="%s" with found_output="%s" and submitted output="%s"'%(input_lines, found, submitted) def process_comment(self, data): """Process data fPblock for COMMENT token.""" if not self.is_suppress: return [data] def save_image(self, image_file): """ Saves the image file to disk. """ self.ensure_pyplot() command = 'plt.gcf().savefig("%s")'%image_file #print 'SAVEFIG', command # dbg self.process_input_line('bookmark ipy_thisdir', store_history=False) self.process_input_line('cd -b ipy_savedir', store_history=False) self.process_input_line(command, store_history=False) self.process_input_line('cd -b ipy_thisdir', store_history=False) self.process_input_line('bookmark -d ipy_thisdir', store_history=False) self.clear_cout() def process_block(self, block): """ process block from the block_parser and return a list of processed lines """ ret = [] output = None input_lines = None lineno = self.IP.execution_count input_prompt = self.promptin%lineno output_prompt = self.promptout%lineno image_file = None image_directive = None for token, data in block: if token==COMMENT: out_data = self.process_comment(data) elif token==INPUT: (out_data, input_lines, output, is_doctest, image_file, image_directive) = \ self.process_input(data, input_prompt, lineno) elif token==OUTPUT: out_data = \ self.process_output(data, output_prompt, input_lines, output, is_doctest, image_file) if out_data: ret.extend(out_data) # save the image files if image_file is not None: self.save_image(image_file) return ret, image_directive def ensure_pyplot(self): if self._pyplot_imported: return self.process_input_line('import matplotlib.pyplot as plt', store_history=False) def process_pure_python(self, content): """ content is a list of strings. it is unedited directive conent This runs it line by line in the InteractiveShell, prepends prompts as needed capturing stderr and stdout, then returns the content as a list as if it were ipython code """ output = [] savefig = False # keep up with this to clear figure multiline = False # to handle line continuation multiline_start = None fmtin = self.promptin ct = 0 for lineno, line in enumerate(content): line_stripped = line.strip() if not len(line): output.append(line) continue # handle decorators if line_stripped.startswith('@'): output.extend([line]) if 'savefig' in line: savefig = True # and need to clear figure continue # handle comments if line_stripped.startswith('#'): output.extend([line]) continue # deal with lines checking for multiline continuation = u' %s:'% ''.join(['.']*(len(str(ct))+2)) if not multiline: modified = u"%s %s" % (fmtin % ct, line_stripped) output.append(modified) ct += 1 try: ast.parse(line_stripped) output.append(u'') except Exception: # on a multiline multiline = True multiline_start = lineno else: # still on a multiline modified = u'%s %s' % (continuation, line) output.append(modified) try: mod = ast.parse( '\n'.join(content[multiline_start:lineno+1])) if isinstance(mod.body[0], ast.FunctionDef): # check to see if we have the whole function for element in mod.body[0].body: if isinstance(element, ast.Return): multiline = False else: output.append(u'') multiline = False except Exception: pass if savefig: # clear figure if plotted self.ensure_pyplot() self.process_input_line('plt.clf()', store_history=False) self.clear_cout() savefig = False return output class IpythonDirective(Directive): has_content = True required_arguments = 0 optional_arguments = 4 # python, suppress, verbatim, doctest final_argumuent_whitespace = True option_spec = { 'python': directives.unchanged, 'suppress' : directives.flag, 'verbatim' : directives.flag, 'doctest' : directives.flag, } shell = EmbeddedSphinxShell() def get_config_options(self): # contains sphinx configuration variables config = self.state.document.settings.env.config # get config variables to set figure output directory confdir = self.state.document.settings.env.app.confdir savefig_dir = config.ipython_savefig_dir source_dir = os.path.dirname(self.state.document.current_source) if savefig_dir is None: savefig_dir = config.html_static_path if isinstance(savefig_dir, list): savefig_dir = savefig_dir[0] # safe to assume only one path? savefig_dir = os.path.join(confdir, savefig_dir) # get regex and prompt stuff rgxin = config.ipython_rgxin rgxout = config.ipython_rgxout promptin = config.ipython_promptin promptout = config.ipython_promptout return savefig_dir, source_dir, rgxin, rgxout, promptin, promptout def setup(self): # reset the execution count if we haven't processed this doc #NOTE: this may be borked if there are multiple seen_doc tmp files #check time stamp? seen_docs = [i for i in os.listdir(tempfile.tempdir) if i.startswith('seen_doc')] if seen_docs: fname = os.path.join(tempfile.tempdir, seen_docs[0]) docs = open(fname).read().split('\n') if not self.state.document.current_source in docs: self.shell.IP.history_manager.reset() self.shell.IP.execution_count = 1 else: # haven't processed any docs yet docs = [] # get config values (savefig_dir, source_dir, rgxin, rgxout, promptin, promptout) = self.get_config_options() # and attach to shell so we don't have to pass them around self.shell.rgxin = rgxin self.shell.rgxout = rgxout self.shell.promptin = promptin self.shell.promptout = promptout self.shell.savefig_dir = savefig_dir self.shell.source_dir = source_dir # setup bookmark for saving figures directory self.shell.process_input_line('bookmark ipy_savedir %s'%savefig_dir, store_history=False) self.shell.clear_cout() # write the filename to a tempfile because it's been "seen" now if not self.state.document.current_source in docs: fd, fname = tempfile.mkstemp(prefix="seen_doc", text=True) fout = open(fname, 'a') fout.write(self.state.document.current_source+'\n') fout.close() return rgxin, rgxout, promptin, promptout def teardown(self): # delete last bookmark self.shell.process_input_line('bookmark -d ipy_savedir', store_history=False) self.shell.clear_cout() def run(self): debug = False #TODO, any reason block_parser can't be a method of embeddable shell # then we wouldn't have to carry these around rgxin, rgxout, promptin, promptout = self.setup() options = self.options self.shell.is_suppress = 'suppress' in options self.shell.is_doctest = 'doctest' in options self.shell.is_verbatim = 'verbatim' in options # handle pure python code if 'python' in self.arguments: content = self.content self.content = self.shell.process_pure_python(content) parts = '\n'.join(self.content).split('\n\n') lines = ['.. code-block:: ipython',''] figures = [] for part in parts: block = block_parser(part, rgxin, rgxout, promptin, promptout) if len(block): rows, figure = self.shell.process_block(block) for row in rows: lines.extend([' %s'%line for line in row.split('\n')]) if figure is not None: figures.append(figure) #text = '\n'.join(lines) #figs = '\n'.join(figures) for figure in figures: lines.append('') lines.extend(figure.split('\n')) lines.append('') #print lines if len(lines)>2: if debug: print '\n'.join(lines) else: #NOTE: this raises some errors, what's it for? #print 'INSERTING %d lines'%len(lines) self.state_machine.insert_input( lines, self.state_machine.input_lines.source(0)) text = '\n'.join(lines) txtnode = nodes.literal_block(text, text) txtnode['language'] = 'ipython' #imgnode = nodes.image(figs) # cleanup self.teardown() return []#, imgnode] # Enable as a proper Sphinx directive def setup(app): setup.app = app app.add_directive('ipython', IpythonDirective) app.add_config_value('ipython_savefig_dir', None, True) app.add_config_value('ipython_rgxin', re.compile('In \[(\d+)\]:\s?(.*)\s*'), True) app.add_config_value('ipython_rgxout', re.compile('Out\[(\d+)\]:\s?(.*)\s*'), True) app.add_config_value('ipython_promptin', 'In [%d]:', True) app.add_config_value('ipython_promptout', 'Out[%d]:', True) # Simple smoke test, needs to be converted to a proper automatic test. def test(): examples = [ r""" In [9]: pwd Out[9]: '/home/jdhunter/py4science/book' In [10]: cd bookdata/ /home/jdhunter/py4science/book/bookdata In [2]: from pylab import * In [2]: ion() In [3]: im = imread('stinkbug.png') @savefig mystinkbug.png width=4in In [4]: imshow(im) Out[4]: """, r""" In [1]: x = 'hello world' # string methods can be # used to alter the string @doctest In [2]: x.upper() Out[2]: 'HELLO WORLD' @verbatim In [3]: x.st x.startswith x.strip """, r""" In [130]: url = 'http://ichart.finance.yahoo.com/table.csv?s=CROX\ .....: &d=9&e=22&f=2009&g=d&a=1&br=8&c=2006&ignore=.csv' In [131]: print url.split('&') ['http://ichart.finance.yahoo.com/table.csv?s=CROX', 'd=9', 'e=22', 'f=2009', 'g=d', 'a=1', 'b=8', 'c=2006', 'ignore=.csv'] In [60]: import urllib """, r"""\ In [133]: import numpy.random @suppress In [134]: numpy.random.seed(2358) @doctest In [135]: numpy.random.rand(10,2) Out[135]: array([[ 0.64524308, 0.59943846], [ 0.47102322, 0.8715456 ], [ 0.29370834, 0.74776844], [ 0.99539577, 0.1313423 ], [ 0.16250302, 0.21103583], [ 0.81626524, 0.1312433 ], [ 0.67338089, 0.72302393], [ 0.7566368 , 0.07033696], [ 0.22591016, 0.77731835], [ 0.0072729 , 0.34273127]]) """, r""" In [106]: print x jdh In [109]: for i in range(10): .....: print i .....: .....: 0 1 2 3 4 5 6 7 8 9 """, r""" In [144]: from pylab import * In [145]: ion() # use a semicolon to suppress the output @savefig test_hist.png width=4in In [151]: hist(np.random.randn(10000), 100); @savefig test_plot.png width=4in In [151]: plot(np.random.randn(10000), 'o'); """, r""" # use a semicolon to suppress the output In [151]: plt.clf() @savefig plot_simple.png width=4in In [151]: plot([1,2,3]) @savefig hist_simple.png width=4in In [151]: hist(np.random.randn(10000), 100); """, r""" # update the current fig In [151]: ylabel('number') In [152]: title('normal distribution') @savefig hist_with_text.png In [153]: grid(True) """, ] # skip local-file depending first example: examples = examples[1:] #ipython_directive.DEBUG = True # dbg #options = dict(suppress=True) # dbg options = dict() for example in examples: content = example.split('\n') ipython_directive('debug', arguments=None, options=options, content=content, lineno=0, content_offset=None, block_text=None, state=None, state_machine=None, ) # Run test suite as a script if __name__=='__main__': if not os.path.isdir('_static'): os.mkdir('_static') test() print 'All OK? Check figures in _static/' trunk-2018.02b/doc/sphinx/ipython_directive200.py000066400000000000000000001204231324306050200215720ustar00rootroot00000000000000# -*- coding: utf-8 -*- """ Sphinx directive to support embedded IPython code. This directive allows pasting of entire interactive IPython sessions, prompts and all, and their code will actually get re-executed at doc build time, with all prompts renumbered sequentially. It also allows you to input code as a pure python input by giving the argument python to the directive. The output looks like an interactive ipython section. To enable this directive, simply list it in your Sphinx ``conf.py`` file (making sure the directory where you placed it is visible to sphinx, as is needed for all Sphinx directives). For example, to enable syntax highlighting and the IPython directive:: extensions = ['IPython.sphinxext.ipython_console_highlighting', 'IPython.sphinxext.ipython_directive'] The IPython directive outputs code-blocks with the language 'ipython'. So if you do not have the syntax highlighting extension enabled as well, then all rendered code-blocks will be uncolored. By default this directive assumes that your prompts are unchanged IPython ones, but this can be customized. The configurable options that can be placed in conf.py are: ipython_savefig_dir: The directory in which to save the figures. This is relative to the Sphinx source directory. The default is `html_static_path`. ipython_rgxin: The compiled regular expression to denote the start of IPython input lines. The default is re.compile('In \[(\d+)\]:\s?(.*)\s*'). You shouldn't need to change this. ipython_rgxout: The compiled regular expression to denote the start of IPython output lines. The default is re.compile('Out\[(\d+)\]:\s?(.*)\s*'). You shouldn't need to change this. ipython_promptin: The string to represent the IPython input prompt in the generated ReST. The default is 'In [%d]:'. This expects that the line numbers are used in the prompt. ipython_promptout: The string to represent the IPython prompt in the generated ReST. The default is 'Out [%d]:'. This expects that the line numbers are used in the prompt. ipython_mplbackend: The string which specifies if the embedded Sphinx shell should import Matplotlib and set the backend. The value specifies a backend that is passed to `matplotlib.use()` before any lines in `ipython_execlines` are executed. If not specified in conf.py, then the default value of 'agg' is used. To use the IPython directive without matplotlib as a dependency, set the value to `None`. It may end up that matplotlib is still imported if the user specifies so in `ipython_execlines` or makes use of the @savefig pseudo decorator. ipython_execlines: A list of strings to be exec'd in the embedded Sphinx shell. Typical usage is to make certain packages always available. Set this to an empty list if you wish to have no imports always available. If specified in conf.py as `None`, then it has the effect of making no imports available. If omitted from conf.py altogether, then the default value of ['import numpy as np', 'import matplotlib.pyplot as plt'] is used. ipython_holdcount When the @suppress pseudo-decorator is used, the execution count can be incremented or not. The default behavior is to hold the execution count, corresponding to a value of `True`. Set this to `False` to increment the execution count after each suppressed command. As an example, to use the IPython directive when `matplotlib` is not available, one sets the backend to `None`:: ipython_mplbackend = None An example usage of the directive is: .. code-block:: rst .. ipython:: In [1]: x = 1 In [2]: y = x**2 In [3]: print(y) See http://matplotlib.org/sampledoc/ipython_directive.html for additional documentation. Pseudo-Decorators ================= Note: Only one decorator is supported per input. If more than one decorator is specified, then only the last one is used. In addition to the Pseudo-Decorators/options described at the above link, several enhancements have been made. The directive will emit a message to the console at build-time if code-execution resulted in an exception or warning. You can suppress these on a per-block basis by specifying the :okexcept: or :okwarning: options: .. code-block:: rst .. ipython:: :okexcept: :okwarning: In [1]: 1/0 In [2]: # raise warning. ToDo ---- - Turn the ad-hoc test() function into a real test suite. - Break up ipython-specific functionality from matplotlib stuff into better separated code. Authors ------- - John D Hunter: orignal author. - Fernando Perez: refactoring, documentation, cleanups, port to 0.11. - VáclavŠmilauer : Prompt generalizations. - Skipper Seabold, refactoring, cleanups, pure python addition """ from __future__ import print_function #----------------------------------------------------------------------------- # Imports #----------------------------------------------------------------------------- # Stdlib import os import re import sys import tempfile import ast import warnings # To keep compatibility with various python versions try: from hashlib import md5 except ImportError: from md5 import md5 # Third-party import sphinx from docutils.parsers.rst import directives from docutils import nodes from sphinx.util.compat import Directive # Our own from IPython import Config, InteractiveShell from IPython.core.profiledir import ProfileDir from IPython.utils import io from IPython.utils.py3compat import PY3 if PY3: from io import StringIO else: from StringIO import StringIO #----------------------------------------------------------------------------- # Globals #----------------------------------------------------------------------------- # for tokenizing blocks COMMENT, INPUT, OUTPUT = range(3) #----------------------------------------------------------------------------- # Functions and class declarations #----------------------------------------------------------------------------- def block_parser(part, rgxin, rgxout, fmtin, fmtout): """ part is a string of ipython text, comprised of at most one input, one ouput, comments, and blank lines. The block parser parses the text into a list of:: blocks = [ (TOKEN0, data0), (TOKEN1, data1), ...] where TOKEN is one of [COMMENT | INPUT | OUTPUT ] and data is, depending on the type of token:: COMMENT : the comment string INPUT: the (DECORATOR, INPUT_LINE, REST) where DECORATOR: the input decorator (or None) INPUT_LINE: the input as string (possibly multi-line) REST : any stdout generated by the input line (not OUTPUT) OUTPUT: the output string, possibly multi-line """ block = [] lines = part.split('\n') N = len(lines) i = 0 decorator = None while 1: if i==N: # nothing left to parse -- the last line break line = lines[i] i += 1 line_stripped = line.strip() if line_stripped.startswith('#'): block.append((COMMENT, line)) continue if line_stripped.startswith('@'): # Here is where we assume there is, at most, one decorator. # Might need to rethink this. decorator = line_stripped continue # does this look like an input line? matchin = rgxin.match(line) if matchin: lineno, inputline = int(matchin.group(1)), matchin.group(2) # the ....: continuation string continuation = ' %s:'%''.join(['.']*(len(str(lineno))+2)) Nc = len(continuation) # input lines can continue on for more than one line, if # we have a '\' line continuation char or a function call # echo line 'print'. The input line can only be # terminated by the end of the block or an output line, so # we parse out the rest of the input line if it is # multiline as well as any echo text rest = [] while i 1: if input_lines[-1] != "": input_lines.append('') # make sure there's a blank line # so splitter buffer gets reset continuation = ' %s:'%''.join(['.']*(len(str(lineno))+2)) if is_savefig: image_file, image_directive = self.process_image(decorator) ret = [] is_semicolon = False # Hold the execution count, if requested to do so. if is_suppress and self.hold_count: store_history = False else: store_history = True # Note: catch_warnings is not thread safe with warnings.catch_warnings(record=True) as ws: for i, line in enumerate(input_lines): if line.endswith(';'): is_semicolon = True if i == 0: # process the first input line if is_verbatim: self.process_input_line('') self.IP.execution_count += 1 # increment it anyway else: # only submit the line in non-verbatim mode self.process_input_line(line, store_history=store_history) formatted_line = '%s %s'%(input_prompt, line) else: # process a continuation line if not is_verbatim: self.process_input_line(line, store_history=store_history) formatted_line = '%s %s'%(continuation, line) if not is_suppress: ret.append(formatted_line) if not is_suppress and len(rest.strip()) and is_verbatim: # The "rest" is the standard output of the input. This needs to be # added when in verbatim mode. If there is no "rest", then we don't # add it, as the new line will be added by the processed output. ret.append(rest) # Fetch the processed output. (This is not the submitted output.) self.cout.seek(0) processed_output = self.cout.read() if not is_suppress and not is_semicolon: # # In IPythonDirective.run, the elements of `ret` are eventually # combined such that '' entries correspond to newlines. So if # `processed_output` is equal to '', then the adding it to `ret` # ensures that there is a blank line between consecutive inputs # that have no outputs, as in: # # In [1]: x = 4 # # In [2]: x = 5 # # When there is processed output, it has a '\n' at the tail end. So # adding the output to `ret` will provide the necessary spacing # between consecutive input/output blocks, as in: # # In [1]: x # Out[1]: 5 # # In [2]: x # Out[2]: 5 # # When there is stdout from the input, it also has a '\n' at the # tail end, and so this ensures proper spacing as well. E.g.: # # In [1]: print x # 5 # # In [2]: x = 5 # # When in verbatim mode, `processed_output` is empty (because # nothing was passed to IP. Sometimes the submitted code block has # an Out[] portion and sometimes it does not. When it does not, we # need to ensure proper spacing, so we have to add '' to `ret`. # However, if there is an Out[] in the submitted code, then we do # not want to add a newline as `process_output` has stuff to add. # The difficulty is that `process_input` doesn't know if # `process_output` will be called---so it doesn't know if there is # Out[] in the code block. The requires that we include a hack in # `process_block`. See the comments there. # ret.append(processed_output) elif is_semicolon: # Make sure there is a newline after the semicolon. ret.append('') # context information filename = "Unknown" lineno = 0 if self.directive.state: filename = self.directive.state.document.current_source lineno = self.directive.state.document.current_line # output any exceptions raised during execution to stdout # unless :okexcept: has been specified. if not is_okexcept and "Traceback" in processed_output: s = "\nException in %s at block ending on line %s\n" % (filename, lineno) s += "Specify :okexcept: as an option in the ipython:: block to suppress this message\n" sys.stdout.write('\n\n>>>' + ('-' * 73)) sys.stdout.write(s) sys.stdout.write(processed_output) sys.stdout.write('<<<' + ('-' * 73) + '\n\n') # output any warning raised during execution to stdout # unless :okwarning: has been specified. if not is_okwarning: for w in ws: s = "\nWarning in %s at block ending on line %s\n" % (filename, lineno) s += "Specify :okwarning: as an option in the ipython:: block to suppress this message\n" sys.stdout.write('\n\n>>>' + ('-' * 73)) sys.stdout.write(s) sys.stdout.write(('-' * 76) + '\n') s=warnings.formatwarning(w.message, w.category, w.filename, w.lineno, w.line) sys.stdout.write(s) sys.stdout.write('<<<' + ('-' * 73) + '\n') self.cout.truncate(0) return (ret, input_lines, processed_output, is_doctest, decorator, image_file, image_directive) def process_output(self, data, output_prompt, input_lines, output, is_doctest, decorator, image_file): """ Process data block for OUTPUT token. """ # Recall: `data` is the submitted output, and `output` is the processed # output from `input_lines`. TAB = ' ' * 4 if is_doctest and output is not None: found = output # This is the processed output found = found.strip() submitted = data.strip() if self.directive is None: source = 'Unavailable' content = 'Unavailable' else: source = self.directive.state.document.current_source content = self.directive.content # Add tabs and join into a single string. content = '\n'.join([TAB + line for line in content]) # Make sure the output contains the output prompt. ind = found.find(output_prompt) if ind < 0: e = ('output does not contain output prompt\n\n' 'Document source: {0}\n\n' 'Raw content: \n{1}\n\n' 'Input line(s):\n{TAB}{2}\n\n' 'Output line(s):\n{TAB}{3}\n\n') e = e.format(source, content, '\n'.join(input_lines), repr(found), TAB=TAB) raise RuntimeError(e) found = found[len(output_prompt):].strip() # Handle the actual doctest comparison. if decorator.strip() == '@doctest': # Standard doctest if found != submitted: e = ('doctest failure\n\n' 'Document source: {0}\n\n' 'Raw content: \n{1}\n\n' 'On input line(s):\n{TAB}{2}\n\n' 'we found output:\n{TAB}{3}\n\n' 'instead of the expected:\n{TAB}{4}\n\n') e = e.format(source, content, '\n'.join(input_lines), repr(found), repr(submitted), TAB=TAB) raise RuntimeError(e) else: self.custom_doctest(decorator, input_lines, found, submitted) # When in verbatim mode, this holds additional submitted output # to be written in the final Sphinx output. # https://github.com/ipython/ipython/issues/5776 out_data = [] is_verbatim = decorator=='@verbatim' or self.is_verbatim if is_verbatim and data.strip(): # Note that `ret` in `process_block` has '' as its last element if # the code block was in verbatim mode. So if there is no submitted # output, then we will have proper spacing only if we do not add # an additional '' to `out_data`. This is why we condition on # `and data.strip()`. # The submitted output has no output prompt. If we want the # prompt and the code to appear, we need to join them now # instead of adding them separately---as this would create an # undesired newline. How we do this ultimately depends on the # format of the output regex. I'll do what works for the default # prompt for now, and we might have to adjust if it doesn't work # in other cases. Finally, the submitted output does not have # a trailing newline, so we must add it manually. out_data.append("{0} {1}\n".format(output_prompt, data)) return out_data def process_comment(self, data): """Process data fPblock for COMMENT token.""" if not self.is_suppress: return [data] def save_image(self, image_file): """ Saves the image file to disk. """ self.ensure_pyplot() command = 'plt.gcf().savefig("%s")'%image_file #print 'SAVEFIG', command # dbg self.process_input_line('bookmark ipy_thisdir', store_history=False) self.process_input_line('cd -b ipy_savedir', store_history=False) self.process_input_line(command, store_history=False) self.process_input_line('cd -b ipy_thisdir', store_history=False) self.process_input_line('bookmark -d ipy_thisdir', store_history=False) self.clear_cout() def process_block(self, block): """ process block from the block_parser and return a list of processed lines """ ret = [] output = None input_lines = None lineno = self.IP.execution_count input_prompt = self.promptin % lineno output_prompt = self.promptout % lineno image_file = None image_directive = None for token, data in block: if token == COMMENT: out_data = self.process_comment(data) elif token == INPUT: (out_data, input_lines, output, is_doctest, decorator, image_file, image_directive) = \ self.process_input(data, input_prompt, lineno) elif token == OUTPUT: out_data = \ self.process_output(data, output_prompt, input_lines, output, is_doctest, decorator, image_file) if out_data: # Then there was user submitted output in verbatim mode. # We need to remove the last element of `ret` that was # added in `process_input`, as it is '' and would introduce # an undesirable newline. assert(ret[-1] == '') del ret[-1] if out_data: ret.extend(out_data) # save the image files if image_file is not None: self.save_image(image_file) return ret, image_directive def ensure_pyplot(self): """ Ensures that pyplot has been imported into the embedded IPython shell. Also, makes sure to set the backend appropriately if not set already. """ # We are here if the @figure pseudo decorator was used. Thus, it's # possible that we could be here even if python_mplbackend were set to # `None`. That's also strange and perhaps worthy of raising an # exception, but for now, we just set the backend to 'agg'. if not self._pyplot_imported: if 'matplotlib.backends' not in sys.modules: # Then ipython_matplotlib was set to None but there was a # call to the @figure decorator (and ipython_execlines did # not set a backend). #raise Exception("No backend was set, but @figure was used!") import matplotlib matplotlib.use('agg') # Always import pyplot into embedded shell. self.process_input_line('import matplotlib.pyplot as plt', store_history=False) self._pyplot_imported = True def process_pure_python(self, content): """ content is a list of strings. it is unedited directive content This runs it line by line in the InteractiveShell, prepends prompts as needed capturing stderr and stdout, then returns the content as a list as if it were ipython code """ output = [] savefig = False # keep up with this to clear figure multiline = False # to handle line continuation multiline_start = None fmtin = self.promptin ct = 0 for lineno, line in enumerate(content): line_stripped = line.strip() if not len(line): output.append(line) continue # handle decorators if line_stripped.startswith('@'): output.extend([line]) if 'savefig' in line: savefig = True # and need to clear figure continue # handle comments if line_stripped.startswith('#'): output.extend([line]) continue # deal with lines checking for multiline continuation = u' %s:'% ''.join(['.']*(len(str(ct))+2)) if not multiline: modified = u"%s %s" % (fmtin % ct, line_stripped) output.append(modified) ct += 1 try: ast.parse(line_stripped) output.append(u'') except Exception: # on a multiline multiline = True multiline_start = lineno else: # still on a multiline modified = u'%s %s' % (continuation, line) output.append(modified) # if the next line is indented, it should be part of multiline if len(content) > lineno + 1: nextline = content[lineno + 1] if len(nextline) - len(nextline.lstrip()) > 3: continue try: mod = ast.parse( '\n'.join(content[multiline_start:lineno+1])) if isinstance(mod.body[0], ast.FunctionDef): # check to see if we have the whole function for element in mod.body[0].body: if isinstance(element, ast.Return): multiline = False else: output.append(u'') multiline = False except Exception: pass if savefig: # clear figure if plotted self.ensure_pyplot() self.process_input_line('plt.clf()', store_history=False) self.clear_cout() savefig = False return output def custom_doctest(self, decorator, input_lines, found, submitted): """ Perform a specialized doctest. """ from .custom_doctests import doctests args = decorator.split() doctest_type = args[1] if doctest_type in doctests: doctests[doctest_type](self, args, input_lines, found, submitted) else: e = "Invalid option to @doctest: {0}".format(doctest_type) raise Exception(e) class IPythonDirective(Directive): has_content = True required_arguments = 0 optional_arguments = 4 # python, suppress, verbatim, doctest final_argumuent_whitespace = True option_spec = { 'python': directives.unchanged, 'suppress' : directives.flag, 'verbatim' : directives.flag, 'doctest' : directives.flag, 'okexcept': directives.flag, 'okwarning': directives.flag } shell = None seen_docs = set() def get_config_options(self): # contains sphinx configuration variables config = self.state.document.settings.env.config # get config variables to set figure output directory confdir = self.state.document.settings.env.app.confdir savefig_dir = config.ipython_savefig_dir source_dir = os.path.dirname(self.state.document.current_source) if savefig_dir is None: savefig_dir = config.html_static_path if isinstance(savefig_dir, list): savefig_dir = savefig_dir[0] # safe to assume only one path? savefig_dir = os.path.join(confdir, savefig_dir) # get regex and prompt stuff rgxin = config.ipython_rgxin rgxout = config.ipython_rgxout promptin = config.ipython_promptin promptout = config.ipython_promptout mplbackend = config.ipython_mplbackend exec_lines = config.ipython_execlines hold_count = config.ipython_holdcount return (savefig_dir, source_dir, rgxin, rgxout, promptin, promptout, mplbackend, exec_lines, hold_count) def setup(self): # Get configuration values. (savefig_dir, source_dir, rgxin, rgxout, promptin, promptout, mplbackend, exec_lines, hold_count) = self.get_config_options() if self.shell is None: # We will be here many times. However, when the # EmbeddedSphinxShell is created, its interactive shell member # is the same for each instance. if mplbackend: import matplotlib # Repeated calls to use() will not hurt us since `mplbackend` # is the same each time. matplotlib.use(mplbackend) # Must be called after (potentially) importing matplotlib and # setting its backend since exec_lines might import pylab. self.shell = EmbeddedSphinxShell(exec_lines) # Store IPython directive to enable better error messages self.shell.directive = self # reset the execution count if we haven't processed this doc #NOTE: this may be borked if there are multiple seen_doc tmp files #check time stamp? if not self.state.document.current_source in self.seen_docs: self.shell.IP.history_manager.reset() self.shell.IP.execution_count = 1 self.shell.IP.prompt_manager.width = 0 self.seen_docs.add(self.state.document.current_source) # and attach to shell so we don't have to pass them around self.shell.rgxin = rgxin self.shell.rgxout = rgxout self.shell.promptin = promptin self.shell.promptout = promptout self.shell.savefig_dir = savefig_dir self.shell.source_dir = source_dir self.shell.hold_count = hold_count # setup bookmark for saving figures directory self.shell.process_input_line('bookmark ipy_savedir %s'%savefig_dir, store_history=False) self.shell.clear_cout() return rgxin, rgxout, promptin, promptout def teardown(self): # delete last bookmark self.shell.process_input_line('bookmark -d ipy_savedir', store_history=False) self.shell.clear_cout() def run(self): debug = False #TODO, any reason block_parser can't be a method of embeddable shell # then we wouldn't have to carry these around rgxin, rgxout, promptin, promptout = self.setup() options = self.options self.shell.is_suppress = 'suppress' in options self.shell.is_doctest = 'doctest' in options self.shell.is_verbatim = 'verbatim' in options self.shell.is_okexcept = 'okexcept' in options self.shell.is_okwarning = 'okwarning' in options # handle pure python code if 'python' in self.arguments: content = self.content self.content = self.shell.process_pure_python(content) parts = '\n'.join(self.content).split('\n\n') lines = ['.. code-block:: ipython', ''] figures = [] for part in parts: block = block_parser(part, rgxin, rgxout, promptin, promptout) if len(block): rows, figure = self.shell.process_block(block) for row in rows: lines.extend([' {0}'.format(line) for line in row.split('\n')]) if figure is not None: figures.append(figure) for figure in figures: lines.append('') lines.extend(figure.split('\n')) lines.append('') if len(lines) > 2: if debug: print('\n'.join(lines)) else: # This has to do with input, not output. But if we comment # these lines out, then no IPython code will appear in the # final output. self.state_machine.insert_input( lines, self.state_machine.input_lines.source(0)) # cleanup self.teardown() return [] # Enable as a proper Sphinx directive def setup(app): setup.app = app app.add_directive('ipython', IPythonDirective) app.add_config_value('ipython_savefig_dir', None, 'env') app.add_config_value('ipython_rgxin', re.compile('In \[(\d+)\]:\s?(.*)\s*'), 'env') app.add_config_value('ipython_rgxout', re.compile('Out\[(\d+)\]:\s?(.*)\s*'), 'env') app.add_config_value('ipython_promptin', 'In [%d]:', 'env') app.add_config_value('ipython_promptout', 'Out[%d]:', 'env') # We could just let matplotlib pick whatever is specified as the default # backend in the matplotlibrc file, but this would cause issues if the # backend didn't work in headless environments. For this reason, 'agg' # is a good default backend choice. app.add_config_value('ipython_mplbackend', 'agg', 'env') # If the user sets this config value to `None`, then EmbeddedSphinxShell's # __init__ method will treat it as []. execlines = ['import numpy as np', 'import matplotlib.pyplot as plt'] app.add_config_value('ipython_execlines', execlines, 'env') app.add_config_value('ipython_holdcount', True, 'env') # Simple smoke test, needs to be converted to a proper automatic test. def test(): examples = [ r""" In [9]: pwd Out[9]: '/home/jdhunter/py4science/book' In [10]: cd bookdata/ /home/jdhunter/py4science/book/bookdata In [2]: from pylab import * In [2]: ion() In [3]: im = imread('stinkbug.png') @savefig mystinkbug.png width=4in In [4]: imshow(im) Out[4]: """, r""" In [1]: x = 'hello world' # string methods can be # used to alter the string @doctest In [2]: x.upper() Out[2]: 'HELLO WORLD' @verbatim In [3]: x.st x.startswith x.strip """, r""" In [130]: url = 'http://ichart.finance.yahoo.com/table.csv?s=CROX\ .....: &d=9&e=22&f=2009&g=d&a=1&br=8&c=2006&ignore=.csv' In [131]: print url.split('&') ['http://ichart.finance.yahoo.com/table.csv?s=CROX', 'd=9', 'e=22', 'f=2009', 'g=d', 'a=1', 'b=8', 'c=2006', 'ignore=.csv'] In [60]: import urllib """, r"""\ In [133]: import numpy.random @suppress In [134]: numpy.random.seed(2358) @doctest In [135]: numpy.random.rand(10,2) Out[135]: array([[ 0.64524308, 0.59943846], [ 0.47102322, 0.8715456 ], [ 0.29370834, 0.74776844], [ 0.99539577, 0.1313423 ], [ 0.16250302, 0.21103583], [ 0.81626524, 0.1312433 ], [ 0.67338089, 0.72302393], [ 0.7566368 , 0.07033696], [ 0.22591016, 0.77731835], [ 0.0072729 , 0.34273127]]) """, r""" In [106]: print x jdh In [109]: for i in range(10): .....: print i .....: .....: 0 1 2 3 4 5 6 7 8 9 """, r""" In [144]: from pylab import * In [145]: ion() # use a semicolon to suppress the output @savefig test_hist.png width=4in In [151]: hist(np.random.randn(10000), 100); @savefig test_plot.png width=4in In [151]: plot(np.random.randn(10000), 'o'); """, r""" # use a semicolon to suppress the output In [151]: plt.clf() @savefig plot_simple.png width=4in In [151]: plot([1,2,3]) @savefig hist_simple.png width=4in In [151]: hist(np.random.randn(10000), 100); """, r""" # update the current fig In [151]: ylabel('number') In [152]: title('normal distribution') @savefig hist_with_text.png In [153]: grid(True) @doctest float In [154]: 0.1 + 0.2 Out[154]: 0.3 @doctest float In [155]: np.arange(16).reshape(4,4) Out[155]: array([[ 0, 1, 2, 3], [ 4, 5, 6, 7], [ 8, 9, 10, 11], [12, 13, 14, 15]]) In [1]: x = np.arange(16, dtype=float).reshape(4,4) In [2]: x[0,0] = np.inf In [3]: x[0,1] = np.nan @doctest float In [4]: x Out[4]: array([[ inf, nan, 2., 3.], [ 4., 5., 6., 7.], [ 8., 9., 10., 11.], [ 12., 13., 14., 15.]]) """, ] # skip local-file depending first example: examples = examples[1:] #ipython_directive.DEBUG = True # dbg #options = dict(suppress=True) # dbg options = dict() for example in examples: content = example.split('\n') IPythonDirective('debug', arguments=None, options=options, content=content, lineno=0, content_offset=None, block_text=None, state=None, state_machine=None, ) # Run test suite as a script if __name__=='__main__': if not os.path.isdir('_static'): os.mkdir('_static') test() print('All OK? Check figures in _static/') trunk-2018.02b/doc/sphinx/ipython_directive500.py000066400000000000000000001232251324306050200216000ustar00rootroot00000000000000# -*- coding: utf-8 -*- """ Sphinx directive to support embedded IPython code. This directive allows pasting of entire interactive IPython sessions, prompts and all, and their code will actually get re-executed at doc build time, with all prompts renumbered sequentially. It also allows you to input code as a pure python input by giving the argument python to the directive. The output looks like an interactive ipython section. To enable this directive, simply list it in your Sphinx ``conf.py`` file (making sure the directory where you placed it is visible to sphinx, as is needed for all Sphinx directives). For example, to enable syntax highlighting and the IPython directive:: extensions = ['IPython.sphinxext.ipython_console_highlighting', 'IPython.sphinxext.ipython_directive'] The IPython directive outputs code-blocks with the language 'ipython'. So if you do not have the syntax highlighting extension enabled as well, then all rendered code-blocks will be uncolored. By default this directive assumes that your prompts are unchanged IPython ones, but this can be customized. The configurable options that can be placed in conf.py are: ipython_savefig_dir: The directory in which to save the figures. This is relative to the Sphinx source directory. The default is `html_static_path`. ipython_rgxin: The compiled regular expression to denote the start of IPython input lines. The default is re.compile('In \[(\d+)\]:\s?(.*)\s*'). You shouldn't need to change this. ipython_rgxout: The compiled regular expression to denote the start of IPython output lines. The default is re.compile('Out\[(\d+)\]:\s?(.*)\s*'). You shouldn't need to change this. ipython_promptin: The string to represent the IPython input prompt in the generated ReST. The default is 'In [%d]:'. This expects that the line numbers are used in the prompt. ipython_promptout: The string to represent the IPython prompt in the generated ReST. The default is 'Out [%d]:'. This expects that the line numbers are used in the prompt. ipython_mplbackend: The string which specifies if the embedded Sphinx shell should import Matplotlib and set the backend. The value specifies a backend that is passed to `matplotlib.use()` before any lines in `ipython_execlines` are executed. If not specified in conf.py, then the default value of 'agg' is used. To use the IPython directive without matplotlib as a dependency, set the value to `None`. It may end up that matplotlib is still imported if the user specifies so in `ipython_execlines` or makes use of the @savefig pseudo decorator. ipython_execlines: A list of strings to be exec'd in the embedded Sphinx shell. Typical usage is to make certain packages always available. Set this to an empty list if you wish to have no imports always available. If specified in conf.py as `None`, then it has the effect of making no imports available. If omitted from conf.py altogether, then the default value of ['import numpy as np', 'import matplotlib.pyplot as plt'] is used. ipython_holdcount When the @suppress pseudo-decorator is used, the execution count can be incremented or not. The default behavior is to hold the execution count, corresponding to a value of `True`. Set this to `False` to increment the execution count after each suppressed command. As an example, to use the IPython directive when `matplotlib` is not available, one sets the backend to `None`:: ipython_mplbackend = None An example usage of the directive is: .. code-block:: rst .. ipython:: In [1]: x = 1 In [2]: y = x**2 In [3]: print(y) See http://matplotlib.org/sampledoc/ipython_directive.html for additional documentation. Pseudo-Decorators ================= Note: Only one decorator is supported per input. If more than one decorator is specified, then only the last one is used. In addition to the Pseudo-Decorators/options described at the above link, several enhancements have been made. The directive will emit a message to the console at build-time if code-execution resulted in an exception or warning. You can suppress these on a per-block basis by specifying the :okexcept: or :okwarning: options: .. code-block:: rst .. ipython:: :okexcept: :okwarning: In [1]: 1/0 In [2]: # raise warning. ToDo ---- - Turn the ad-hoc test() function into a real test suite. - Break up ipython-specific functionality from matplotlib stuff into better separated code. Authors ------- - John D Hunter: orignal author. - Fernando Perez: refactoring, documentation, cleanups, port to 0.11. - VáclavŠmilauer : Prompt generalizations. - Skipper Seabold, refactoring, cleanups, pure python addition """ from __future__ import print_function #----------------------------------------------------------------------------- # Imports #----------------------------------------------------------------------------- # Stdlib import atexit import os import re import sys import tempfile import ast import warnings import shutil # Third-party from docutils.parsers.rst import directives from sphinx.util.compat import Directive # Our own from traitlets.config import Config from IPython import InteractiveShell from IPython.core.profiledir import ProfileDir from IPython.utils import io from IPython.utils.py3compat import PY3 if PY3: from io import StringIO else: from StringIO import StringIO #----------------------------------------------------------------------------- # Globals #----------------------------------------------------------------------------- # for tokenizing blocks COMMENT, INPUT, OUTPUT = range(3) #----------------------------------------------------------------------------- # Functions and class declarations #----------------------------------------------------------------------------- def block_parser(part, rgxin, rgxout, fmtin, fmtout): """ part is a string of ipython text, comprised of at most one input, one output, comments, and blank lines. The block parser parses the text into a list of:: blocks = [ (TOKEN0, data0), (TOKEN1, data1), ...] where TOKEN is one of [COMMENT | INPUT | OUTPUT ] and data is, depending on the type of token:: COMMENT : the comment string INPUT: the (DECORATOR, INPUT_LINE, REST) where DECORATOR: the input decorator (or None) INPUT_LINE: the input as string (possibly multi-line) REST : any stdout generated by the input line (not OUTPUT) OUTPUT: the output string, possibly multi-line """ block = [] lines = part.split('\n') N = len(lines) i = 0 decorator = None while 1: if i==N: # nothing left to parse -- the last line break line = lines[i] i += 1 line_stripped = line.strip() if line_stripped.startswith('#'): block.append((COMMENT, line)) continue if line_stripped.startswith('@'): # Here is where we assume there is, at most, one decorator. # Might need to rethink this. decorator = line_stripped continue # does this look like an input line? matchin = rgxin.match(line) if matchin: lineno, inputline = int(matchin.group(1)), matchin.group(2) # the ....: continuation string continuation = ' %s:'%''.join(['.']*(len(str(lineno))+2)) Nc = len(continuation) # input lines can continue on for more than one line, if # we have a '\' line continuation char or a function call # echo line 'print'. The input line can only be # terminated by the end of the block or an output line, so # we parse out the rest of the input line if it is # multiline as well as any echo text rest = [] while i 1: if input_lines[-1] != "": input_lines.append('') # make sure there's a blank line # so splitter buffer gets reset continuation = ' %s:'%''.join(['.']*(len(str(lineno))+2)) if is_savefig: image_file, image_directive = self.process_image(decorator) ret = [] is_semicolon = False # Hold the execution count, if requested to do so. if is_suppress and self.hold_count: store_history = False else: store_history = True # Note: catch_warnings is not thread safe with warnings.catch_warnings(record=True) as ws: for i, line in enumerate(input_lines): if line.endswith(';'): is_semicolon = True if i == 0: # process the first input line if is_verbatim: self.process_input_line('') self.IP.execution_count += 1 # increment it anyway else: # only submit the line in non-verbatim mode self.process_input_line(line, store_history=store_history) formatted_line = '%s %s'%(input_prompt, line) else: # process a continuation line if not is_verbatim: self.process_input_line(line, store_history=store_history) formatted_line = '%s %s'%(continuation, line) if not is_suppress: ret.append(formatted_line) if not is_suppress and len(rest.strip()) and is_verbatim: # The "rest" is the standard output of the input. This needs to be # added when in verbatim mode. If there is no "rest", then we don't # add it, as the new line will be added by the processed output. ret.append(rest) # Fetch the processed output. (This is not the submitted output.) self.cout.seek(0) processed_output = self.cout.read() if not is_suppress and not is_semicolon: # # In IPythonDirective.run, the elements of `ret` are eventually # combined such that '' entries correspond to newlines. So if # `processed_output` is equal to '', then the adding it to `ret` # ensures that there is a blank line between consecutive inputs # that have no outputs, as in: # # In [1]: x = 4 # # In [2]: x = 5 # # When there is processed output, it has a '\n' at the tail end. So # adding the output to `ret` will provide the necessary spacing # between consecutive input/output blocks, as in: # # In [1]: x # Out[1]: 5 # # In [2]: x # Out[2]: 5 # # When there is stdout from the input, it also has a '\n' at the # tail end, and so this ensures proper spacing as well. E.g.: # # In [1]: print x # 5 # # In [2]: x = 5 # # When in verbatim mode, `processed_output` is empty (because # nothing was passed to IP. Sometimes the submitted code block has # an Out[] portion and sometimes it does not. When it does not, we # need to ensure proper spacing, so we have to add '' to `ret`. # However, if there is an Out[] in the submitted code, then we do # not want to add a newline as `process_output` has stuff to add. # The difficulty is that `process_input` doesn't know if # `process_output` will be called---so it doesn't know if there is # Out[] in the code block. The requires that we include a hack in # `process_block`. See the comments there. # ret.append(processed_output) elif is_semicolon: # Make sure there is a newline after the semicolon. ret.append('') # context information filename = "Unknown" lineno = 0 if self.directive.state: filename = self.directive.state.document.current_source lineno = self.directive.state.document.current_line # output any exceptions raised during execution to stdout # unless :okexcept: has been specified. if not is_okexcept and "Traceback" in processed_output: s = "\nException in %s at block ending on line %s\n" % (filename, lineno) s += "Specify :okexcept: as an option in the ipython:: block to suppress this message\n" sys.stdout.write('\n\n>>>' + ('-' * 73)) sys.stdout.write(s) sys.stdout.write(processed_output) sys.stdout.write('<<<' + ('-' * 73) + '\n\n') # output any warning raised during execution to stdout # unless :okwarning: has been specified. if not is_okwarning: for w in ws: s = "\nWarning in %s at block ending on line %s\n" % (filename, lineno) s += "Specify :okwarning: as an option in the ipython:: block to suppress this message\n" sys.stdout.write('\n\n>>>' + ('-' * 73)) sys.stdout.write(s) sys.stdout.write(('-' * 76) + '\n') s=warnings.formatwarning(w.message, w.category, w.filename, w.lineno, w.line) sys.stdout.write(s) sys.stdout.write('<<<' + ('-' * 73) + '\n') self.cout.truncate(0) return (ret, input_lines, processed_output, is_doctest, decorator, image_file, image_directive) def process_output(self, data, output_prompt, input_lines, output, is_doctest, decorator, image_file): """ Process data block for OUTPUT token. """ # Recall: `data` is the submitted output, and `output` is the processed # output from `input_lines`. TAB = ' ' * 4 if is_doctest and output is not None: found = output # This is the processed output found = found.strip() submitted = data.strip() if self.directive is None: source = 'Unavailable' content = 'Unavailable' else: source = self.directive.state.document.current_source content = self.directive.content # Add tabs and join into a single string. content = '\n'.join([TAB + line for line in content]) # Make sure the output contains the output prompt. ind = found.find(output_prompt) if ind < 0: e = ('output does not contain output prompt\n\n' 'Document source: {0}\n\n' 'Raw content: \n{1}\n\n' 'Input line(s):\n{TAB}{2}\n\n' 'Output line(s):\n{TAB}{3}\n\n') e = e.format(source, content, '\n'.join(input_lines), repr(found), TAB=TAB) raise RuntimeError(e) found = found[len(output_prompt):].strip() # Handle the actual doctest comparison. if decorator.strip() == '@doctest': # Standard doctest if found != submitted: e = ('doctest failure\n\n' 'Document source: {0}\n\n' 'Raw content: \n{1}\n\n' 'On input line(s):\n{TAB}{2}\n\n' 'we found output:\n{TAB}{3}\n\n' 'instead of the expected:\n{TAB}{4}\n\n') e = e.format(source, content, '\n'.join(input_lines), repr(found), repr(submitted), TAB=TAB) raise RuntimeError(e) else: self.custom_doctest(decorator, input_lines, found, submitted) # When in verbatim mode, this holds additional submitted output # to be written in the final Sphinx output. # https://github.com/ipython/ipython/issues/5776 out_data = [] is_verbatim = decorator=='@verbatim' or self.is_verbatim if is_verbatim and data.strip(): # Note that `ret` in `process_block` has '' as its last element if # the code block was in verbatim mode. So if there is no submitted # output, then we will have proper spacing only if we do not add # an additional '' to `out_data`. This is why we condition on # `and data.strip()`. # The submitted output has no output prompt. If we want the # prompt and the code to appear, we need to join them now # instead of adding them separately---as this would create an # undesired newline. How we do this ultimately depends on the # format of the output regex. I'll do what works for the default # prompt for now, and we might have to adjust if it doesn't work # in other cases. Finally, the submitted output does not have # a trailing newline, so we must add it manually. out_data.append("{0} {1}\n".format(output_prompt, data)) return out_data def process_comment(self, data): """Process data fPblock for COMMENT token.""" if not self.is_suppress: return [data] def save_image(self, image_file): """ Saves the image file to disk. """ self.ensure_pyplot() command = 'plt.gcf().savefig("%s")'%image_file #print 'SAVEFIG', command # dbg self.process_input_line('bookmark ipy_thisdir', store_history=False) self.process_input_line('cd -b ipy_savedir', store_history=False) self.process_input_line(command, store_history=False) self.process_input_line('cd -b ipy_thisdir', store_history=False) self.process_input_line('bookmark -d ipy_thisdir', store_history=False) self.clear_cout() def process_block(self, block): """ process block from the block_parser and return a list of processed lines """ ret = [] output = None input_lines = None lineno = self.IP.execution_count input_prompt = self.promptin % lineno output_prompt = self.promptout % lineno image_file = None image_directive = None found_input = False for token, data in block: if token == COMMENT: out_data = self.process_comment(data) elif token == INPUT: found_input = True (out_data, input_lines, output, is_doctest, decorator, image_file, image_directive) = \ self.process_input(data, input_prompt, lineno) elif token == OUTPUT: if not found_input: TAB = ' ' * 4 linenumber = 0 source = 'Unavailable' content = 'Unavailable' if self.directive: linenumber = self.directive.state.document.current_line source = self.directive.state.document.current_source content = self.directive.content # Add tabs and join into a single string. content = '\n'.join([TAB + line for line in content]) e = ('\n\nInvalid block: Block contains an output prompt ' 'without an input prompt.\n\n' 'Document source: {0}\n\n' 'Content begins at line {1}: \n\n{2}\n\n' 'Problematic block within content: \n\n{TAB}{3}\n\n') e = e.format(source, linenumber, content, block, TAB=TAB) # Write, rather than include in exception, since Sphinx # will truncate tracebacks. sys.stdout.write(e) raise RuntimeError('An invalid block was detected.') out_data = \ self.process_output(data, output_prompt, input_lines, output, is_doctest, decorator, image_file) if out_data: # Then there was user submitted output in verbatim mode. # We need to remove the last element of `ret` that was # added in `process_input`, as it is '' and would introduce # an undesirable newline. assert(ret[-1] == '') del ret[-1] if out_data: ret.extend(out_data) # save the image files if image_file is not None: self.save_image(image_file) return ret, image_directive def ensure_pyplot(self): """ Ensures that pyplot has been imported into the embedded IPython shell. Also, makes sure to set the backend appropriately if not set already. """ # We are here if the @figure pseudo decorator was used. Thus, it's # possible that we could be here even if python_mplbackend were set to # `None`. That's also strange and perhaps worthy of raising an # exception, but for now, we just set the backend to 'agg'. if not self._pyplot_imported: if 'matplotlib.backends' not in sys.modules: # Then ipython_matplotlib was set to None but there was a # call to the @figure decorator (and ipython_execlines did # not set a backend). #raise Exception("No backend was set, but @figure was used!") import matplotlib matplotlib.use('agg') # Always import pyplot into embedded shell. self.process_input_line('import matplotlib.pyplot as plt', store_history=False) self._pyplot_imported = True def process_pure_python(self, content): """ content is a list of strings. it is unedited directive content This runs it line by line in the InteractiveShell, prepends prompts as needed capturing stderr and stdout, then returns the content as a list as if it were ipython code """ output = [] savefig = False # keep up with this to clear figure multiline = False # to handle line continuation multiline_start = None fmtin = self.promptin ct = 0 for lineno, line in enumerate(content): line_stripped = line.strip() if not len(line): output.append(line) continue # handle decorators if line_stripped.startswith('@'): output.extend([line]) if 'savefig' in line: savefig = True # and need to clear figure continue # handle comments if line_stripped.startswith('#'): output.extend([line]) continue # deal with lines checking for multiline continuation = u' %s:'% ''.join(['.']*(len(str(ct))+2)) if not multiline: modified = u"%s %s" % (fmtin % ct, line_stripped) output.append(modified) ct += 1 try: ast.parse(line_stripped) output.append(u'') except Exception: # on a multiline multiline = True multiline_start = lineno else: # still on a multiline modified = u'%s %s' % (continuation, line) output.append(modified) # if the next line is indented, it should be part of multiline if len(content) > lineno + 1: nextline = content[lineno + 1] if len(nextline) - len(nextline.lstrip()) > 3: continue try: mod = ast.parse( '\n'.join(content[multiline_start:lineno+1])) if isinstance(mod.body[0], ast.FunctionDef): # check to see if we have the whole function for element in mod.body[0].body: if isinstance(element, ast.Return): multiline = False else: output.append(u'') multiline = False except Exception: pass if savefig: # clear figure if plotted self.ensure_pyplot() self.process_input_line('plt.clf()', store_history=False) self.clear_cout() savefig = False return output def custom_doctest(self, decorator, input_lines, found, submitted): """ Perform a specialized doctest. """ from .custom_doctests import doctests args = decorator.split() doctest_type = args[1] if doctest_type in doctests: doctests[doctest_type](self, args, input_lines, found, submitted) else: e = "Invalid option to @doctest: {0}".format(doctest_type) raise Exception(e) class IPythonDirective(Directive): has_content = True required_arguments = 0 optional_arguments = 4 # python, suppress, verbatim, doctest final_argumuent_whitespace = True option_spec = { 'python': directives.unchanged, 'suppress' : directives.flag, 'verbatim' : directives.flag, 'doctest' : directives.flag, 'okexcept': directives.flag, 'okwarning': directives.flag } shell = None seen_docs = set() def get_config_options(self): # contains sphinx configuration variables config = self.state.document.settings.env.config # get config variables to set figure output directory outdir = self.state.document.settings.env.app.outdir savefig_dir = config.ipython_savefig_dir source_dir = os.path.dirname(self.state.document.current_source) if savefig_dir is None: savefig_dir = config.html_static_path or '_static' if isinstance(savefig_dir, list): savefig_dir = os.path.join(*savefig_dir) savefig_dir = os.path.join(outdir, savefig_dir) # get regex and prompt stuff rgxin = config.ipython_rgxin rgxout = config.ipython_rgxout promptin = config.ipython_promptin promptout = config.ipython_promptout mplbackend = config.ipython_mplbackend exec_lines = config.ipython_execlines hold_count = config.ipython_holdcount return (savefig_dir, source_dir, rgxin, rgxout, promptin, promptout, mplbackend, exec_lines, hold_count) def setup(self): # Get configuration values. (savefig_dir, source_dir, rgxin, rgxout, promptin, promptout, mplbackend, exec_lines, hold_count) = self.get_config_options() if self.shell is None: # We will be here many times. However, when the # EmbeddedSphinxShell is created, its interactive shell member # is the same for each instance. if mplbackend and 'matplotlib.backends' not in sys.modules: import matplotlib matplotlib.use(mplbackend) # Must be called after (potentially) importing matplotlib and # setting its backend since exec_lines might import pylab. self.shell = EmbeddedSphinxShell(exec_lines) # Store IPython directive to enable better error messages self.shell.directive = self # reset the execution count if we haven't processed this doc #NOTE: this may be borked if there are multiple seen_doc tmp files #check time stamp? if not self.state.document.current_source in self.seen_docs: self.shell.IP.history_manager.reset() self.shell.IP.execution_count = 1 self.seen_docs.add(self.state.document.current_source) # and attach to shell so we don't have to pass them around self.shell.rgxin = rgxin self.shell.rgxout = rgxout self.shell.promptin = promptin self.shell.promptout = promptout self.shell.savefig_dir = savefig_dir self.shell.source_dir = source_dir self.shell.hold_count = hold_count # setup bookmark for saving figures directory self.shell.process_input_line('bookmark ipy_savedir %s'%savefig_dir, store_history=False) self.shell.clear_cout() return rgxin, rgxout, promptin, promptout def teardown(self): # delete last bookmark self.shell.process_input_line('bookmark -d ipy_savedir', store_history=False) self.shell.clear_cout() def run(self): debug = False #TODO, any reason block_parser can't be a method of embeddable shell # then we wouldn't have to carry these around rgxin, rgxout, promptin, promptout = self.setup() options = self.options self.shell.is_suppress = 'suppress' in options self.shell.is_doctest = 'doctest' in options self.shell.is_verbatim = 'verbatim' in options self.shell.is_okexcept = 'okexcept' in options self.shell.is_okwarning = 'okwarning' in options # handle pure python code if 'python' in self.arguments: content = self.content self.content = self.shell.process_pure_python(content) # parts consists of all text within the ipython-block. # Each part is an input/output block. parts = '\n'.join(self.content).split('\n\n') lines = ['.. code-block:: ipython', ''] figures = [] for part in parts: block = block_parser(part, rgxin, rgxout, promptin, promptout) if len(block): rows, figure = self.shell.process_block(block) for row in rows: lines.extend([' {0}'.format(line) for line in row.split('\n')]) if figure is not None: figures.append(figure) for figure in figures: lines.append('') lines.extend(figure.split('\n')) lines.append('') if len(lines) > 2: if debug: print('\n'.join(lines)) else: # This has to do with input, not output. But if we comment # these lines out, then no IPython code will appear in the # final output. self.state_machine.insert_input( lines, self.state_machine.input_lines.source(0)) # cleanup self.teardown() return [] # Enable as a proper Sphinx directive def setup(app): setup.app = app app.add_directive('ipython', IPythonDirective) app.add_config_value('ipython_savefig_dir', None, 'env') app.add_config_value('ipython_rgxin', re.compile('In \[(\d+)\]:\s?(.*)\s*'), 'env') app.add_config_value('ipython_rgxout', re.compile('Out\[(\d+)\]:\s?(.*)\s*'), 'env') app.add_config_value('ipython_promptin', 'In [%d]:', 'env') app.add_config_value('ipython_promptout', 'Out[%d]:', 'env') # We could just let matplotlib pick whatever is specified as the default # backend in the matplotlibrc file, but this would cause issues if the # backend didn't work in headless environments. For this reason, 'agg' # is a good default backend choice. app.add_config_value('ipython_mplbackend', 'agg', 'env') # If the user sets this config value to `None`, then EmbeddedSphinxShell's # __init__ method will treat it as []. execlines = ['import numpy as np', 'import matplotlib.pyplot as plt'] app.add_config_value('ipython_execlines', execlines, 'env') app.add_config_value('ipython_holdcount', True, 'env') metadata = {'parallel_read_safe': True, 'parallel_write_safe': True} return metadata # Simple smoke test, needs to be converted to a proper automatic test. def test(): examples = [ r""" In [9]: pwd Out[9]: '/home/jdhunter/py4science/book' In [10]: cd bookdata/ /home/jdhunter/py4science/book/bookdata In [2]: from pylab import * In [2]: ion() In [3]: im = imread('stinkbug.png') @savefig mystinkbug.png width=4in In [4]: imshow(im) Out[4]: """, r""" In [1]: x = 'hello world' # string methods can be # used to alter the string @doctest In [2]: x.upper() Out[2]: 'HELLO WORLD' @verbatim In [3]: x.st x.startswith x.strip """, r""" In [130]: url = 'http://ichart.finance.yahoo.com/table.csv?s=CROX\ .....: &d=9&e=22&f=2009&g=d&a=1&br=8&c=2006&ignore=.csv' In [131]: print url.split('&') ['http://ichart.finance.yahoo.com/table.csv?s=CROX', 'd=9', 'e=22', 'f=2009', 'g=d', 'a=1', 'b=8', 'c=2006', 'ignore=.csv'] In [60]: import urllib """, r"""\ In [133]: import numpy.random @suppress In [134]: numpy.random.seed(2358) @doctest In [135]: numpy.random.rand(10,2) Out[135]: array([[ 0.64524308, 0.59943846], [ 0.47102322, 0.8715456 ], [ 0.29370834, 0.74776844], [ 0.99539577, 0.1313423 ], [ 0.16250302, 0.21103583], [ 0.81626524, 0.1312433 ], [ 0.67338089, 0.72302393], [ 0.7566368 , 0.07033696], [ 0.22591016, 0.77731835], [ 0.0072729 , 0.34273127]]) """, r""" In [106]: print x jdh In [109]: for i in range(10): .....: print i .....: .....: 0 1 2 3 4 5 6 7 8 9 """, r""" In [144]: from pylab import * In [145]: ion() # use a semicolon to suppress the output @savefig test_hist.png width=4in In [151]: hist(np.random.randn(10000), 100); @savefig test_plot.png width=4in In [151]: plot(np.random.randn(10000), 'o'); """, r""" # use a semicolon to suppress the output In [151]: plt.clf() @savefig plot_simple.png width=4in In [151]: plot([1,2,3]) @savefig hist_simple.png width=4in In [151]: hist(np.random.randn(10000), 100); """, r""" # update the current fig In [151]: ylabel('number') In [152]: title('normal distribution') @savefig hist_with_text.png In [153]: grid(True) @doctest float In [154]: 0.1 + 0.2 Out[154]: 0.3 @doctest float In [155]: np.arange(16).reshape(4,4) Out[155]: array([[ 0, 1, 2, 3], [ 4, 5, 6, 7], [ 8, 9, 10, 11], [12, 13, 14, 15]]) In [1]: x = np.arange(16, dtype=float).reshape(4,4) In [2]: x[0,0] = np.inf In [3]: x[0,1] = np.nan @doctest float In [4]: x Out[4]: array([[ inf, nan, 2., 3.], [ 4., 5., 6., 7.], [ 8., 9., 10., 11.], [ 12., 13., 14., 15.]]) """, ] # skip local-file depending first example: examples = examples[1:] #ipython_directive.DEBUG = True # dbg #options = dict(suppress=True) # dbg options = dict() for example in examples: content = example.split('\n') IPythonDirective('debug', arguments=None, options=options, content=content, lineno=0, content_offset=None, block_text=None, state=None, state_machine=None, ) # Run test suite as a script if __name__=='__main__': if not os.path.isdir('_static'): os.mkdir('_static') test() print('All OK? Check figures in _static/') trunk-2018.02b/doc/sphinx/prog.rst000066400000000000000000003151661324306050200167610ustar00rootroot00000000000000********************** Programmer's manual ********************** Build system ============= Yade uses `cmake `__ the cross-platform, open-source build system for managing the build process. It takes care of configuration, compilation and installation. CMake is used to control the software compilation process using simple platform and compiler independent configuration files. CMake generates native makefiles and workspaces that can be used in the compiler environment of your choice. Building ------------- Yade source tree has the following structure (omiting, ``doc``, ``examples`` and ``scripts`` which don't participate in the build process); we shall call each top-level component *module*:: core/ ## core simulation building blocks extra/ ## miscillanea gui/ ## user interfaces qt4/ ## graphical user interface based on qt3 and OpenGL py/ ## python console interface (phased out) lib/ ## support libraries, not specific to simulations pkg/ ## simulation-specific files common/ ## generally useful classes dem/ ## classes for Discrete Element Method py/ ## python modules Header installation ^^^^^^^^^^^^^^^^^^^^ To allow flexibility in source layout, CMAKE will copy (symlink) all headers into flattened structure within the build directory. First 2 components of the original directory are joind by dash, deeper levels are discarded (in case of ``core`` and ``extra``, only 1 level is used). The following table makes gives a few examples: ============================================================= ========================= Original header location Included as ============================================================= ========================= ``core/Scene.hpp`` ```` ``lib/base/Logging.hpp`` ```` ``lib/serialization/Serializable.hpp`` ```` ``pkg/dem/DataClass/SpherePack.hpp`` ```` ``gui/qt3/QtGUI.hpp`` ```` ============================================================= ========================= It is advised to use ``#include`` style of inclusion rather than ``#include"Class.hpp`` even if you are in the same directory. Automatic compilation """""""""""""""""""""" In the ``pkg/`` directory, situation is different. In order to maximally ease addition of modules to yade, all ``*.cpp`` files are *automatically scanned* by CMAKE and considered for compilation. Each file may contain multiple lines that declare features that are necessary for this file to be compiled:: YADE_REQUIRE_FEATURE(vtk); YADE_REQUIRE_FEATURE(gts); This file will be compiled only if *both* ``VTK`` and ``GTS`` features are enabled. Depending on current feature set, only selection of plugins will be compiled. It is possible to disable compilation of a file by requiring any non-existent feature, such as:: YADE_REQUIRE_FEATURE(temporarily disabled 345uiysdijkn); The ``YADE_REQUIRE_FEATURE`` macro expands to nothing during actual compilation. .. _linking: Linking ^^^^^^^^ The order in which modules might depend on each other is given as follows: =========== ================================== ============================================== module resulting shared library dependencies =========== ================================== ============================================== lib ``libyade-support.so`` can depend on external libraries, may **not** depend on any other part of Yade. core ``libcore.so`` ``yade-support``; *may* depend on external libraries. pkg ``libplugins.so`` ``core``, ``yade-support`` gui ``libQtGUI.so``, ``lib``, ``core``, ``pkg`` ``libPythonUI.so`` py (many files) ``lib``, ``core``, ``pkg``, external =========== ================================== ============================================== Development tools ================= Integrated Development Environment and other tools --------------------------------------------------- A frequently used IDE is Kdevelop. We recommend using this software for navigating in the sources, compiling and debugging. Other useful tools for debugging and profiling are Valgrind and KCachegrind. A series of wiki pages is dedicated to these tools in the `development section `__ of the wiki. Hosting and versioning ---------------------- The Yade project is kindly hosted at `launchpad `__, which is used for source code, bug tracking, planning, package downloads and more. The versioning software used is `GIT `__, for which a short tutorial can be found in :ref:`yade-github-label`. GIT is a distributed revision control system. It is available packaged for all major linux distributions. The source code is hosted on `GitHub `__ , which is periodically imported to Launchpad for building PPA-packages. The repository `can be http-browsed `__. Build robot ----------- A build robot hosted at `3SR lab. `__ is tracking souce code changes. Each time a change in the source code is commited to the main development branch via GIT, the "buildbot" downloads and compiles the new version, and start a series of tests. If a compilation error has been introduced, it will be notified to the yade-dev mailing list and to the commiter, thus helping to fix problems quickly. If the compilation is successfull, the buildbot starts unit regression tests and "check tests" (see below) and report the results. If all tests are passed, a new version of the documentation is generated and uploaded to the website in `html `__ and `pdf `__ formats. As a consequence, those two links always point to the documentation (the one you are reading now) of the last successfull build, and the delay between commits and documentation updates are very short (minutes). The buildbot activity and logs can be `browsed online `__. Regression tests ---------------- Yade contains two types of regression tests, some are unit tests while others are testing more complex simulations. Altough both types can be considered regression tests, the usage is that we name the first simply "regression tests", while the latest are called "check tests". Both series of tests can be ran at yade startup by passing the options "test" or "check" :: yade --test yade --check Unit regression tests ^^^^^^^^^^^^^^^^^^^^^ Unit regression tests are testing the output of individual functors and engines in well defined conditions. They are defined in the folder :ysrc:`py/tests/`. The purpose of unit testing is to make sure that the behaviour of the most important classes remains correct during code development. Since they test classes one by one, unit tests can't detect problems coming from the interaction between different engines in a typical simulation. That is why check tests have been introduced. Check tests ^^^^^^^^^^^ Check tests perform comparisons of simulation results between different versions of yade, as discussed `here `__. They differ with regression tests in the sense that they simulate more complex situations and combinations of different engines, and usually don't have a mathematical proof (though there is no restriction on the latest). They compare the values obtained in version N with values obtained in a previous version or any other "expected" results. The reference values must be hardcoded in the script itself or in data files provided with the script. Check tests are based on regular yade scripts, so that users can easily commit their own scripts to trunk in order to get some automatized testing after commits from other developers. Since the check tests history will be mostly based on standard output generated by "yade ---check", a meaningfull checkTest should include some "print" command telling if something went wrong. If the script itself fails for some reason and can't generate an output, the log will contain "scriptName failure". If the script defines differences on obtained and awaited data, it should print some useful information about the problem and increase the value of global variable resultStatus. After this occurs, the automatic test will stop the execution with error message. An example check test can be found in checkTestTriax.py. It shows results comparison, output, and how to define the path to data files using "checksPath". Users are encouraged to add their own scripts into the scripts/test/checks/ folder. Discussion of some specific checktests design in users question is welcome. Note that re-compiling is required before that added scripts can be launched by "yade ---check" (or direct changes have to be performed in "lib" subfolders). A check test should never need more than a few seconds to run. If your typical script needs more, try and reduce the number of element or the number of steps. Conventions ============ The following rules that should be respected; documentation is treated separately. * general * C++ source files have ``.hpp`` and ``.cpp`` extensions (for headers and implementation, respectively). * All header files should have the ``#pragma once`` multiple-inclusion guard. * Try to avoid ``using namespace …`` in header files. * Use tabs for indentation. While this is merely visual in ``c++``, it has semantic meaning in python; inadverently mixing tabs and spaces can result in syntax errors. * capitalization style * Types should be always capitalized. Use CamelCase for composed names (``GlobalEngine``). Underscores should be used only in special cases, such as functor names. * Class data members and methods must not be capitalized, composed names should use use lowercased camelCase (``glutSlices``). The same applies for functions in python modules. * Preprocessor macros are uppercase, separated by underscores; those that are used outside the core take (with exceptions) the form ``YADE_*``, such as :ref:`YADE_CLASS_BASE_DOC`. * programming style * Be defensive, if it has no significant performance impact. Use assertions abundantly: they don't affect performance (in the optimized build) and make spotting error conditions much easier. * Use ``YADE_CAST`` and ``YADE_PTR_CAST`` where you want type-check during debug builds, but fast casting in optimized build. * Initialize all class variables in the default constructor. This avoids bugs that may manifest randomly and are difficult to fix. Initializing with NaN's will help you find otherwise unitialized variable. (This is taken care of by :ref:`YADE_CLASS_BASE_DOC` macros for user classes) Class naming ------------- Although for historial reasons the naming scheme is not completely consistent, these rules should be obeyed especially when adding a new class. GlobalEngines and PartialEngines GlobalEngines should be named in a way suggesting that it is a performer of certain action (like :yref:`ForceResetter`, :yref:`InsertionSortCollider`, :yref:`Recorder`); if this is not appropriate, append the ``Engine`` to the characteristics (:yref:`GravityEngine`). :yref:`PartialEngines` have no special naming convention different from :yref:`GlobalEngines`. Dispatchers Names of all dispatchers end in ``Dispatcher``. The name is composed of type it creates or, in case it doesn't create any objects, its main characteristics. Currently, the following dispatchers [#opengldispatchers]_ are defined: .. _dispatcher-names: ========================== ================ ====================== ============== ===================== =============== dispatcher arity dispatch types created type functor type functor prefix ========================== ================ ====================== ============== ===================== =============== :yref:`BoundDispatcher` 1 :yref:`Shape` :yref:`Bound` :yref:`BoundFunctor` ``Bo1`` :yref:`IGeomDispatcher` 2 (symetric) 2 × :yref:`Shape` :yref:`IGeom` :yref:`IGeomFunctor` ``Ig2`` :yref:`IPhysDispatcher` 2 (symetric) 2 × :yref:`Material` :yref:`IPhys` :yref:`IPhysFunctor` ``Ip2`` :yref:`LawDispatcher` 2 (asymetric) :yref:`IGeom` *(none)* :yref:`LawFunctor` ``Law2`` :yref:`IPhys` ========================== ================ ====================== ============== ===================== =============== Respective abstract functors for each dispatchers are :yref:`BoundFunctor`, :yref:`IGeomFunctor`, :yref:`IPhysFunctor` and :yref:`LawFunctor`. Functors Functor name is composed of 3 parts, separated by underscore. #. prefix, composed of abbreviated functor type and arity (see table above) #. Types entering the dispatcher logic (1 for unary and 2 for binary functors) #. Return type for functors that create instances, simple characteristics for functors that don't create instances. To give a few examples: * :yref:`Bo1_Sphere_Aabb` is a :yref:`BoundFunctor` which is called for :yref:`Sphere`, creating an instance of :yref:`Aabb`. * :yref:`Ig2_Facet_Sphere_ScGeom` is binary functor called for :yref:`Facet` and :yref:`Sphere`, creating and instace of :yref:`ScGeom`. * :yref:`Law2_ScGeom_CpmPhys_Cpm` is binary functor (:yref:`LawFunctor`) called for types :yref:`ScGeom (Geom)` and :yref:`CpmPhys`. .. [#opengldispatchers] Not considering OpenGL dispatchers, which might be replaced by regular virtual functions in the future. Documentation --------------- **Documenting code properly is one of the most important aspects of sustained development.** Read it again. Most code in research software like Yade is not only used, but also read, by developers or even by regular users. Therefore, when adding new class, always mention the following in the documentation: * purpose * details of the functionality, unless obvious (algorithms, internal logic) * limitations (by design, by implementation), bugs * bibliographical reference, if using non-trivial published algorithms (see below) * references to other related classes * hyperlinks to bugs, blueprints, wiki or mailing list about this particular feature. As much as it is meaningful, you should also * update any other documentation affected * provide a simple python script demonstrating the new functionality in ``scripts/test``. .. _sphinxdocumentation: Sphinx documentation ^^^^^^^^^^^^^^^^^^^^^ Most c++ classes are wrapped in Python, which provides good introspection and interactive documentation (try writing ``Material?`` in the ipython prompt; or ``help(CpmState)``). Syntax of documentation is `ReST `__ (reStructuredText, see `reStructuredText Primer `__). It is the same for c++ and python code. * Documentation of c++ classes exposed to python is given as 3rd argument to :ref:`YADE_CLASS_BASE_DOC` introduced below. * Python classes/functions are documented using regular python docstrings. Besides explaining functionality, meaning and types of all arguments should also be documented. Short pieces of code might be very helpful. See the :yref:`yade.utils` module for an example. In addition to standard ReST syntax, yade provides several shorthand macros: ``:yref:`` creates hyperlink to referenced term, for instance:: :yref:`CpmMat` becomes :yref:`CpmMat`; link name and target can be different:: :yref:`Material used in the CPM model` yielding :yref:`Material used in the CPM model`. ``:ysrc:`` creates hyperlink to file within the source tree (to its latest version in the repository), for instance :ysrc:`core/Cell.hpp`. Just like with `:yref:`, alternate text can be used with :: :ysrc:`Link text` like :ysrc:`this`. ``|ycomp|`` is used in attribute description for those that should not be provided by the user, but are auto-computed instead; ``|ycomp|`` expands to |ycomp|. ``|yupdate|`` marks attributes that are periodically updated, being subset of the previous. ``|yupdate|`` expands to |yupdate|. ``\$...\$`` delimits inline math expressions; they will be replaced by:: :math:`...` and rendered via LaTeX. To write a single dollar sign, escape it with backslash ``\\$``. Displayed mathematics (standalone equations) can be inserted as explained in `Math support in Sphinx `_. Bibliographical references ^^^^^^^^^^^^^^^^^^^^^^^^^^^ As in any scientific documentation, references to publications are very important. To cite an article, add it to BibTeX file in :ysrc:`doc/references.bib`, using the BibTeX format. Please adhere to the following conventions: #. Keep entries in the form ``Author2008`` (``Author`` is the first author), ``Author2008b`` etc if multiple articles from one author; #. Try to fill `mandatory fields `_ for given type of citation; #. Do not use ``\'{i}`` funny escapes for accents, since they will not work with the HTML output; put everything in straight utf-8. In your docstring, the ``Author2008`` article can be cited by ``[Author2008]_``; for example:: According to [Allen1989]_, the integration scheme … will be rendered as According to [Allen1989]_, the integration scheme … Separate class/function documentation ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Some c++ might have long or content-rich documentation, which is rather inconvenient to type in the c++ source itself as string literals. Yade provides a way to write documentation separately in :ysrc:`py/_extraDocs.py` file: it is executed after loading c++ plugins and can set ``__doc__`` attribute of any object directly, overwriting docstring from c++. In such (exceptional) cases: #. Provide at least a brief description of the class in the c++ code nevertheless, for people only reading the code. #. Add notice saying "This class is documented in detail in the :ysrc:`py/_extraDocs.py` file". #. Add documentation to :ysrc:`py/_etraDocs.py` in this way:: module.YourClass.__doc__=''' This is the docstring for YourClass. Class, methods and functions can be documented this way. .. note:: It can use any syntax features you like. ''' .. note:: Boost::python embeds function signatures in the docstring (before the one provided by the user). Therefore, before creating separate documentation of your function, have a look at its ``__doc__`` attribute and copy the first line (and the blank line afterwards) in the separate docstring. The first line is then used to create the function signature (arguments and return value). Internal c++ documentation ^^^^^^^^^^^^^^^^^^^^^^^^^^ `doxygen `__ was used for automatic generation of c++ code. Since user-visible classes are defined with sphinx now, it is not meaningful to use doxygen to generate overall documentation. However, take care to document well internal parts of code using regular comments, including public and private data members. Support framework ======================= Besides the framework provided by the c++ standard library (including STL), boost and other dependencies, yade provides its own specific services. Pointers --------- Shared pointers ^^^^^^^^^^^^^^^^^ Yade makes extensive use of shared pointers ``shared_ptr``. [#sharedptr]_ Although it probably has some performance impacts, it greatly simplifies memory management, ownership management of c++ objects in python and so forth. To obtain raw pointer from a ``shared_ptr``, use its ``get()`` method; raw pointers should be used in case the object will be used only for short time (during a function call, for instance) and not stored anywhere. .. [#sharedptr] Either ``boost::shared_ptr`` or ``tr1::shared_ptr`` is used, but it is always imported with the ``using`` statement so that unqualified ``shared_ptr`` can be used. Python defines thin wrappers for most c++ Yade classes (for all those registered with :ref:`YADE_CLASS_BASE_DOC` and several others), which can be constructed from ``shared_ptr``; in this way, Python reference counting blends with the ``shared_ptr`` reference counting model, preventing crashes due to python objects pointing to c++ objects that were destructed in the meantime. Typecasting ^^^^^^^^^^^^ Frequently, pointers have to be typecast; there is choice between static and dynamic casting. * ``dynamic_cast`` (``dynamic_pointer_cast`` for a ``shared_ptr``) assures cast admissibility by checking runtime type of its argument and returns NULL if the cast is invalid; such check obviously costs time. Invalid cast is easily caught by checking whether the pointer is NULL or not; even if such check (e.g. ``assert``) is absent, dereferencing NULL pointer is easily spotted from the stacktrace (debugger output) after crash. Moreover, ``shared_ptr`` checks that the pointer is non-NULL before dereferencing in debug build and aborts with "Assertion \`px!=0' failed." if the check fails. * ``static_cast`` is fast but potentially dangerous (``static_pointer_cast`` for ``shared_ptr``). Static cast will return non-NULL pointer even if types don't allow the cast (such as casting from ``State*`` to ``Material*``); the consequence of such cast is interpreting garbage data as instance of the class cast to, leading very likely to invalid memory access (segmentation fault, "crash" for short). To have both speed and safety, Yade provides 2 macros: ``YADE_CAST`` expands to ``static_cast`` in optimized builds and to ``dynamic_cast`` in debug builds. ``YADE_PTR_CAST`` expands to ``static_pointer_cast`` in optimized builds and to ``dynamic_pointer_cast`` in debug builds. Basic numerics --------------- The floating point type to use in Yade ``Real``, which is by default typedef for ``double``. [#real]_ Yade uses the `Eigen `_ library for computations. It provides classes for 2d and 3d vectors, quaternions and 3x3 matrices templated by number type; their specialization for the ``Real`` type are typedef'ed with the "r" suffix, and occasionally useful integer types with the "i" suffix: * ``Vector2r``, ``Vector2i`` * ``Vector3r``, ``Vector3i`` * ``Quaternionr`` * ``Matrix3r`` Yade additionally defines a class named :yref:`Se3r`, which contains spatial position (``Vector3r Se3r::position``) and orientation (``Quaternionr Se3r::orientation``), since they are frequently used one with another, and it is convenient to pass them as single parameter to functions. .. [#real] Historically, it was thought that Yade could be also run with single precision based on build-time parameter; it turned out however that the impact on numerical stability was such disastrous that this option is not available now. There is, however, ``QUAD_PRECISION`` parameter to scons, which will make ``Real`` a typedef for ``long double`` (extended precision; quad precision in the proper sense on IA64 processors); this option is experimental and is unlikely to be used in near future, though. Eigen provides full rich linear algebra functionality. Some code firther uses the [cgal]_ library for computational geometry. In Python, basic numeric types are wrapped and imported from the ``minieigen`` module; the types drop the ``r`` type qualifier at the end, the syntax is otherwise similar. ``Se3r`` is not wrapped at all, only converted automatically, rarely as it is needed, from/to a ``(Vector3,Quaternion)`` tuple/list. .. ipython:: @suppress Yade [0]: from math import pi # cross product Yade [1]: Vector3(1,2,3).cross(Vector3(0,0,1)) # construct quaternion from axis and angle Yade [2]: Quaternion(Vector3(0,0,1),pi/2) .. note:: Quaternions are internally stored as 4 numbers. Their usual human-readable representation is, however, (normalized) axis and angle of rotation around that axis, and it is also how they are input/output in Python. Raw internal values can be accessed using the ``[0]`` … ``[3]`` element access (or ``.W()``, ``.X()``, ``.Y()`` and ``.Z()`` methods), in both c++ and Python. .. _rtti: Run-time type identification (RTTI) ----------------------------------- Since serialization and dispatchers need extended type and inheritance information, which is not sufficiently provided by standard RTTI. Each yade class is therefore derived from ``Factorable`` and it must use macro to override its virtual functions providing this extended RTTI: ``YADE_CLASS_BASE_DOC(Foo,Bar Baz,"Docstring)`` creates the following virtual methods (mediated via the ``REGISTER_CLASS_AND_BASE`` macro, which is not user-visible and should not be used directly): * ``std::string getClassName()`` returning class name (``Foo``) as string. (There is the ``typeid(instanceOrType).name()`` standard c++ construct, but the name returned is compiler-dependent.) * ``unsigned getBaseClassNumber()`` returning number of base classes (in this case, 2). * ``std::string getBaseClassName(unsigned i=0)`` returning name of *i*-th base class (here, ``Bar`` for i=0 and ``Baz`` for i=1). .. warning:: RTTI relies on virtual functions; in order for virtual functions to work, at least one virtual method must be present in the implementation (``.cpp``) file. Otherwise, virtual method table (vtable) will not be generated for this class by the compiler, preventing virtual methods from functioning properly. Some RTTI information can be accessed from python: .. ipython:: @suppress Yade [1]: import yade.system Yade [2]: yade.system.childClasses('Shape') Yade [3]: Sphere().name ## getClassName() Serialization -------------- Serialization serves to save simulation to file and restore it later. This process has several necessary conditions: * classes know which attributes (data members) they have and what are their names (as strings); * creating class instances based solely on its name; * knowing what classes are defined inside a particular shared library (plugin). This functionality is provided by 3 macros and 4 optional methods; details are provided below. ``Serializable::preLoad``, ``Serializable::preSave``, ``Serializable::postLoad``, ``Serializable::postSave`` Prepare attributes before serialization (saving) or deserialization (loading) or process them after serialization or deserialization. See :ref:`attributeregistration`. ``YADE_CLASS_BASE_DOC_*`` Inside the class declaration (i.e. in the ``.hpp`` file within the ``class Foo { /* … */};`` block). See :ref:`attributeregistration`. Enumerate class attributes that should be saved and loaded; associate each attribute with its literal name, which can be used to retrieve it. See :ref:`YADE_CLASS_BASE_DOC`. Additionally documents the class in python, adds methods for attribute access from python, and documents each attribute. ``REGISTER_SERIALIZABLE`` In header file, but *after* the class declaration block. See :ref:`classfactory`. Associate literal name of the class with functions that will create its new instance (``ClassFactory``). ``YADE_PLUGIN`` In the implementation ``.cpp`` file. See :ref:`plugins`. Declare what classes are declared inside a particular plugin at time the plugin is being loaded (yade startup). .. _attributeregistration: Attribute registration ^^^^^^^^^^^^^^^^^^^^^^ All (serializable) types in Yade are one of the following: * Type deriving from :yref:`Serializable`, which provide information on how to serialize themselves via overriding the ``Serializable::registerAttributes`` method; it declares data members that should be serialzed along with their literal names, by which they are identified. This method then invokes ``registerAttributes`` of its base class (until ``Serializable`` itself is reached); in this way, derived classes properly serialize data of their base classes. This funcionality is hidden behind the macro :ref:`YADE_CLASS_BASE_DOC` used in class declaration body (header file), which takes base class and list of attributes:: YADE_CLASS_BASE_DOC_ATTRS(ThisClass,BaseClass,"class documentation",((type1,attribute1,initValue1,,"Documentation for attribute 1"))((type2,attribute2,initValue2,,"Documentation for attribute 2")); Note that attributes are encodes in double parentheses, not separated by commas. Empty attribute list can be given simply by ``YADE_CLASS_BASE_DOC_ATTRS(ThisClass,BaseClass,"documentation",)`` (the last comma is mandatory), or by omiting ``ATTRS`` from macro name and last parameter altogether. * Fundamental type: strings, various number types, booleans, ``Vector3r`` and others. Their "handlers" (serializers and deserializers) are defined in ``lib/serialization``. * Standard container of any serializable objects. * Shared pointer to serializable object. Yade uses the excellent `boost::serialization `_ library internally for serialization of data. .. note:: ``YADE_CLASS_BASE_DOC_ATTRS`` also generates code for attribute access from python; this will be discussed later. Since this macro serves both purposes, the consequence is that attributes that are serialized can always be accessed from python. Yade also provides callback for before/after (de) serialization, virtual functions :yref:`Serializable::preProcessAttributes` and :yref:`Serializable::postProcessAttributes`, which receive one ``bool deserializing`` argument (``true`` when deserializing, ``false`` when serializing). Their default implementation in :yref:`Serializable` doesn't do anything, but their typical use is: * converting some non-serializable internal data structure of the class (such as multi-dimensional array, hash table, array of pointers) into a serializable one (pre-processing) and fill this non-serializable structure back after deserialization (post-processing); for instance, InteractionContainer uses these hooks to ask its concrete implementation to store its contents to a unified storage (``vector >``) before serialization and to restore from it after deserialization. * precomputing non-serialized attributes from the serialized values; e.g. :yref:`Facet` computes its (local) edge normals and edge lengths from vertices' coordinates. .. _classfactory: Class factory ^^^^^^^^^^^^^^ Each serializable class must use ``REGISTER_SERIALIZABLE``, which defines function to create that class by ``ClassFactory``. ``ClassFactory`` is able to instantiate a class given its name (as string), which is necessary for deserialization. Although mostly used internally by the serialization framework, programmer can ask for a class instantiation using ``shared_ptr f=ClassFactory::instance().createShared("ClassName");``, casting the returned ``shared_ptr`` to desired type afterwards. :yref:`Serializable` itself derives from ``Factorable``, i.e. all serializable types are also factorable (It is possible that different mechanism will be in place if boost::serialization is used, though.) .. _plugins: Plugin registration ^^^^^^^^^^^^^^^^^^^ Yade loads dynamic libraries containing all its functionality at startup. ClassFactory must be taught about classes each particular file provides. ``YADE_PLUGIN`` serves this purpose and, contrary to :ref:`YADE_CLASS_BASE_DOC`, must be place in the implementation (.cpp) file. It simple enumerates classes that are provided by this file:: YADE_PLUGIN((ClassFoo)(ClassBar)); .. note:: You must use parentheses around the class name even if there is only one (preprocessor limitation): ``YADE_PLUGIN((classFoo));``. If there is no class in this file, do not use this macro at all. Internally, this macro creates function ``registerThisPluginClasses_`` declared specially as ``__attribute__((constructor))`` (see `GCC Function Attributes `_); this attributes makes the function being executed when the plugin is loaded via ``dlopen`` from ``ClassFactory::load(...)``. It registers all factorable classes from that file in the :ref:`classfactory`. .. note:: Classes that do not derive from ``Factorable``, such as ``Shop`` or ``SpherePack``, are not declared with ``YADE_PLUGIN``. --------------- This is an example of a serializable class header: .. code-block:: c++ /*! Homogeneous gravity field; applies gravity×mass force on all bodies. */ class GravityEngine: public GlobalEngine{ public: virtual void action(); // registering class and its base for the RTTI system YADE_CLASS_BASE_DOC_ATTRS(GravityEngine,GlobalEngine, // documentation visible from python and generated reference documentation "Homogeneous gravity field; applies gravity×mass force on all bodies.", // enumerating attributes here, include documentation ((Vector3r,gravity,Vector3r::ZERO,"acceleration, zero by default [kgms⁻²]")) ); }; // registration function for ClassFactory REGISTER_SERIALIZABLE(GravityEngine); and this is the implementation: .. code-block:: c++ #include #include // registering the plugin YADE_PLUGIN((GravityEngine)); void GravityEngine::action(){ /* do the work here */ } We can create a mini-simulation (with only one GravityEngine): .. ipython:: Yade [1]: O.engines=[GravityEngine(gravity=Vector3(0,0,-9.81))] Yade [2]: O.save('abc.xml') and the XML looks like this: .. literalinclude:: abc.xml :language: xml .. warning:: Since XML files closely reflect implementation details of Yade, they will not be compatible between different versions. Use them only for short-term saving of scenes. Python is *the* high-level description Yade uses. .. _pythonattributeaccess: Python attribute access ^^^^^^^^^^^^^^^^^^^^^^^^ The macro :ref:`YADE_CLASS_BASE_DOC` introduced above is (behind the scenes) also used to create functions for accessing attributes from Python. As already noted, set of serialized attributes and set of attributes accessible from Python are identical. Besides attribute access, these wrapper classes imitate also some functionality of regular python dictionaries: .. ipython:: Yade [1]: s=Sphere() Yade [2]: s.radius ## read-access Yade [3]: s.radius=4. ## write access Yade [4]: s.dict().keys() ## show all available keys Yade [5]: for k in s.dict().keys(): print s.dict()[k] ## iterate over keys, print their values ...: Yade [5]: s.dict()['radius'] ## same as: 'radius' in s.keys() Yade [6]: s.dict() ## show dictionary of both attributes and values .. _YADE_CLASS_BASE_DOC: YADE_CLASS_BASE_DOC_* macro family ----------------------------------- There is several macros that hide behind them the functionality of :ref:`sphinxdocumentation`, :ref:`rtti`, :ref:`attributeregistration`, :ref:`pythonattributeaccess`, plus automatic attribute initialization and documentation. They are all defined as shorthands for base macro ``YADE_CLASS_BASE_DOC_ATTRS_INIT_CTOR_PY`` with some arguments left out. They must be placed in class declaration's body (``.hpp`` file): .. code-block:: c++ #define YADE_CLASS_BASE_DOC(klass,base,doc) \ YADE_CLASS_BASE_DOC_ATTRS(klass,base,doc,) #define YADE_CLASS_BASE_DOC_ATTRS(klass,base,doc,attrs) \ YADE_CLASS_BASE_DOC_ATTRS_CTOR(klass,base,doc,attrs,) #define YADE_CLASS_BASE_DOC_ATTRS_CTOR(klass,base,doc,attrs,ctor) \ YADE_CLASS_BASE_DOC_ATTRS_CTOR_PY(klass,base,doc,attrs,ctor,) #define YADE_CLASS_BASE_DOC_ATTRS_CTOR_PY(klass,base,doc,attrs,ctor,py) \ YADE_CLASS_BASE_DOC_ATTRS_INIT_CTOR_PY(klass,base,doc,attrs,,ctor,py) #define YADE_CLASS_BASE_DOC_ATTRS_INIT_CTOR_PY(klass,base,doc,attrs,init,ctor,py) \ YADE_CLASS_BASE_DOC_ATTRS_INIT_CTOR_PY(klass,base,doc,attrs,inits,ctor,py) Expected parameters are indicated by macro name components separated with underscores. Their meaning is as follows: ``klass`` (unquoted) name of this class (used for RTTI and python) ``base`` (unquoted) name of the base class (used for RTTI and python) ``doc`` docstring of this class, written in the ReST syntax. This docstring will appear in generated documentation (such as :yref:`CpmMat`). It can be as long as necessary, but sequences interpreted by c++ compiler must be properly escaped (therefore some backslashes must be doubled, like in :math:`\sigma=\epsilon E`:: ":math:`\\sigma=\\epsilon E" Use ``\n`` and ``\t`` for indentation inside the docstring. Hyperlink the documentation abundantly with ``yref`` (all references to other classes should be hyperlinks). See :ref:`sphinxdocumentation` for syntax details. ``attrs`` Attribute must be written in the form of parethesized list: .. code-block:: c++ ((type1,attr1,initValue1,attrFlags,"Attribute 1 documentation")) ((type2,attr2,,,"Attribute 2 documentation")) // initValue and attrFlags unspecified This will expand to #. data members declaration in c++ (note that all attributes are *public*): .. code-block:: c++ public: type1 attr1; type2 attr2; #. Initializers of the default (argument-less) constructor, for attributes that have non-empty ``initValue``: .. code-block:: c++ Klass(): attr1(initValue1), attr2() { /* constructor body */ } No initial value will be assigned for attribute of which initial value is left empty (as is for attr2 in the above example). Note that you still have to write the commas. #. Registration of the attribute in the serialization system (unless disabled by attrFlags -- see below) #. Registration of the attribute in python (unless disabled by attrFlags), so that it can be accessed as ``klass().name1``. The attribute is read-write by default, see attrFlags to change that. This attribute will carry the docstring provided, along with knowledge of the initial value. You can add text description to the default value using the comma operator of c++ and casting the char* to (void): .. code-block:: c++ ((Real,dmgTau,((void)"deactivated if negative",-1),,"Characteristic time for normal viscosity. [s]")) leading to :yref:`CpmMat::dmgTau`. The attribute is registered via ``boost::python::add_property`` specifying ``return_by_value`` policy rather than ``return_internal_reference``, which is the default when using ``def_readwrite``. The reason is that we need to honor custom converters for those values; see note in :ref:`customconverters` for details. .. admonition:: Attribute flags By default, an attribute will be serialized and will be read-write from python. There is a number of flags that can be passed as the 4th argument (empty by default) to change that: * ``Attr::noSave`` avoids serialization of the attribute (while still keeping its accessibility from Python) * ``Attr::readonly`` makes the attribute read-only from Python * ``Attr::triggerPostLoad`` will trigger call to ``postLoad`` function to handle attribute change after its value is set from Python; this is to ensure consistency of other precomputed data which depend on this value (such as ``Cell.trsf`` and such) * ``Attr::hidden`` will not expose the attribute to Python at all * ``Attr::noResize`` will not permit changing size of the array from Python [not yet used] Flags can be combined as usual using bitwise disjunction ``|`` (such as ``Attr::noSave | Attr::readonly``), though in such case the value should be parenthesized to avoid a warning with some compilers (g++ specifically), i.e. ``(Attr::noSave | Attr::readonly)``. Currently, the flags logic handled at runtime; that means that even for attributes with ``Attr::noSave``, their serialization template must be defined (although it will never be used). In the future, the implementation might be template-based, avoiding this necessity. ``deprec`` List of deprecated attribute names. The syntax is :: ((oldName1,newName1,"Explanation why renamed etc.")) ((oldName2,newName2,"! Explanation why removed and what to do instaed.")) This will make accessing ``oldName1`` attribute *from Python* return value of ``newName``, but displaying warning message about the attribute name change, displaying provided explanation. This happens whether the access is read or write. If the explanation's first character is ``!`` (*bang*), the message will be displayed upon attribute access, but exception will be thrown immediately. Use this in cases where attribute is no longer meaningful or was not straightforwardsly replaced by another, but more complex adaptation of user's script is needed. You still have to give ``newName2``, although its value will never be used -- you can use any variable you like, but something must be given for syntax reasons). .. warning:: Due to compiler limitations, this feature only works if Yade is compiled with gcc >= 4.4. In the contrary case, deprecated attribute functionality is disabled, even if such attributes are declared. ``init`` Parethesized list of the form: .. code-block:: c++ ((attr3,value3)) ((attr4,value4)) which will be expanded to initializers in the default ctor: .. code-block:: c++ Klass(): /* attributes declared with the attrs argument */ attr4(value4), attr5(value5) { /* constructor body */ } The purpose of this argument is to make it possible to initialize constants and references (which are not declared as attributes using this macro themselves, but separately), as that cannot be done in constructor body. This argument is rarely used, though. ``ctor`` will be put directly into the generated constructor's body. Mostly used for calling createIndex(); in the constructor. .. note:: The code must not contain commas ouside parentheses (since preprocessor uses commas to separate macro arguments). If you need complex things at construction time, create a separate init() function and call it from the constructor instead. ``py`` will be appeneded directly after generated python code that registers the class and all its attributes. You can use it to access class methods from python, for instance, to override an existing attribute with the same name etc: .. code-block:: c++ .def_readonly("omega",&CpmPhys::omega,"Damage internal variable") .def_readonly("Fn",&CpmPhys::Fn,"Magnitude of normal force.") ``def_readonly`` will not work for custom types (such as std::vector), as it bypasses conversion registry; see :ref:`customconverters` for details. Special python constructors ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ The Python wrapper automatically create constructor that takes keyword (named) arguments corresponding to instance attributes; those attributes are set to values provided in the constructor. In some cases, more flexibility is desired (such as :yref:`InteractionLoop`, which takes 3 lists of functors). For such cases, you can override the function ``Serializable::pyHandleCustomCtorArgs``, which can arbitrarily modify the new (already existing) instance. It should modify in-place arguments given to it, as they will be passed further down to the routine which sets attribute values. In such cases, you should document the constructor:: .. admonition:: Special constructor Constructs from lists of … which then appears in the documentation similar to :yref:`InteractionLoop`. Static attributes ^^^^^^^^^^^^^^^^^^^ Some classes (such as OpenGL functors) are instantiated automatically; since we want their attributes to be persistent throughout the session, they are static. To expose class with static attributes, use the ``YADE_CLASS_BASE_DOC_STATICATTRS`` macro. Attribute syntax is the same as for ``YADE_CLASS_BASE_DOC_ATTRS``: .. code-block:: c++ class SomeClass: public BaseClass{ YADE_CLASS_BASE_DOC_STATICATTRS(SomeClass,BaseClass,"Documentation of SomeClass", ((Type1,attr1,default1,"doc for attr1")) ((Type2,attr2,default2,"doc for attr2")) ); }; additionally, you *have* to allocate memory for static data members in the ``.cpp`` file (otherwise, error about undefined symbol will appear when the plugin is loaded): .. code-block: c++ /* in the .cpp file */ #include /* allocate memory for static attrs; no need to assign initial value, that is done from (hidden) initialization function at class registration time using default values given to macro in the .hpp file. */ Type1 SomeClass::attr1; Type2 SomeClass::attr2; There is no way to expose class that has both static and non-static attributes using ``YADE_CLASS_BASE_*`` macros. You have to expose non-static attributes normally and wrap static attributes separately in the ``py`` parameter. .. _valuereference: Returning attribute by value or by reference ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ When attribute is passed from c++ to python, it can be passed either as * value: new python object representing the original c++ object is constructed, but not bound to it; changing the python object doesn't modify the c++ object, unless explicitly assigned back to it, where inverse conversion takes place and the c++ object is replaced. * reference: only reference to the underlying c++ object is given back to python; modifying python object will make the c++ object modified automatically. The way of passing attributes given to ``YADE_CLASS_BASE_DOC_ATTRS`` in the ``attrs`` parameter is determined automatically in the following manner: * ``Vector3``, ``Vector3i``, ``Vector2``, ``Vector2i``, ``Matrix3`` and ``Quaternion`` objects are passed by *reference*. For instance:: O.bodies[0].state.pos[0]=1.33 will assign correct value to ``x`` component of position, without changing the other ones. * Yade classes (all that use ``shared_ptr`` when declared in python: all classes deriving from :yref:`Serializable` declared with ``YADE_CLASS_BASE_DOC_*``, and some others) are passed as *references* (technically speaking, they are passed by value of the ``shared_ptr``, but by virtue of its sharedness, they appear as references). For instance:: O.engines[4].damping=.3 will change :yref:`damping` parameter on the original engine object, not on its copy. * All other types are passed by *value*. This includes, most importantly, sequence types declared in :ref:`customconverters`, such as ``std::vector >``. For this reason, :: O.engines[4]=NewtonIntegrator() will *not* work as expected; it will replace 5th element of a *copy* of the sequence, and this change will not propagate back to c++. .. _multiple-dispatch: Multiple dispatch ------------------ Multiple dispatch is generalization of virtual methods: a :yref:`Dispatcher` decides based on type(s) of its argument(s) which of its :yref:`Functors` to call. Numer of arguments (currently 1 or 2) determines *arity* of the dispatcher (and of the functor): unary or binary. For example: .. code-block:: python InsertionSortCollider([Bo1_Sphere_Aabb(),Bo1_Facet_Aabb()]) creates :yref:`InsertionSortCollider`, which internally contains :yref:`Collider.boundDispatcher`, a :yref:`BoundDispatcher` (a :yref:`Dispatcher`), with 2 functors; they receive ``Sphere`` or ``Facet`` instances and create ``Aabb``. This code would look like this in c++: .. code-block:: c++ shared_ptr collider=(new InsertionSortCollider); collider->boundDispatcher->add(new Bo1_Sphere_Aabb()); collider->boundDispatcher->add(new Bo1_Facet_Aabb()); There are currenly 4 predefined dispatchers (see `dispatcher-names`_) and corresponding functor types. They are inherit from template instantiations of ``Dispatcher1D`` or ``Dispatcher2D`` (for functors, ``Functor1D`` or ``Functor2D``). These templates themselves derive from ``DynlibDispatcher`` (for dispatchers) and ``FunctorWrapper`` (for functors). Example: IGeomDispatcher ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Let's take (the most complicated perhaps) :yref:`IGeomDispatcher`. :yref:`IGeomFunctor`, which is dispatched based on types of 2 :yref:`Shape` instances (a :yref:`Functor`), takes a number of arguments and returns bool. The functor "call" is always provided by its overridden ``Functor::go`` method; it always receives the dispatched instances as first argument(s) (2 × ``const shared_ptr&``) and a number of other arguments it needs: .. code-block:: c++ class IGeomFunctor: public Functor2D< bool, //return type TYPELIST_7(const shared_ptr&, // 1st class for dispatch const shared_ptr&, // 2nd class for dispatch const State&, // other arguments passed to ::go const State&, // … const Vector3r&, // … const bool&, // … const shared_ptr& // … ) > The dispatcher is declared as follows: .. code-block:: c++ class IGeomDispatcher: public Dispatcher2D< Shape, // 1st class for dispatch Shape, // 2nd class for dispatch IGeomFunctor, // functor type bool, // return type of the functor // follow argument types for functor call // they must be exactly the same as types // given to the IGeomFunctor above. TYPELIST_7(const shared_ptr&, const shared_ptr&, const State&, const State&, const Vector3r&, const bool &, const shared_ptr& ), // handle symetry automatically // (if the dispatcher receives Sphere+Facet, // the dispatcher might call functor for Facet+Sphere, // reversing the arguments) false > { /* … */ } Functor derived from IGeomFunctor must then * override the ::go method with appropriate arguments (they must match exactly types given to ``TYPELIST_*`` macro); * declare what types they should be dispatched for, and in what order if they are not the same. .. code-block:: c++ class Ig2_Facet_Sphere_ScGeom: public IGeomFunctor{ public: // override the IGeomFunctor::go // (it is really inherited from FunctorWrapper template, // therefore not declare explicitly in the // IGeomFunctor declaration as such) // since dispatcher dispatches only for declared types // (or types derived from them), we can do // static_cast(shape1) and static_cast(shape2) // in the ::go body, without worrying about types being wrong. virtual bool go( // objects for dispatch const shared_ptr& shape1, const shared_ptr& shape2, // other arguments const State& state1, const State& state2, const Vector3r& shift2, const bool& force, const shared_ptr& c ); /* … */ // this declares the type we want to be dispatched for, matching // first 2 arguments to ::go and first 2 classes in TYPELIST_7 above // shape1 is a Facet and shape2 is a Sphere // (or vice versa, see lines below) FUNCTOR2D(Facet,Sphere); // declare how to swap the arguments // so that we can receive those as well DEFINE_FUNCTOR_ORDER_2D(Facet,Sphere); /* … */ }; Dispatch resolution ^^^^^^^^^^^^^^^^^^^ The dispatcher doesn't always have functors that exactly match the actual types it receives. In the same way as virtual methods, it tries to find the closest match in such way that: #. the actual instances are derived types of those the functor accepts, or exactly the accepted types; #. sum of distances from actual to accepted types is sharp-minimized (each step up in the class hierarchy counts as 1) If no functor is able to accept given types (first condition violated) or multiple functors have the same distance (in condition 2), an exception is thrown. This resolution mechanism makes it possible, for instance, to have a hierarchy of :yref:`ScGeom` classes (for different combination of shapes), but only provide a :yref:`LawFunctor` accepting ``ScGeom``, rather than having different laws for each shape combination. .. note:: Performance implications of dispatch resolution are relatively low. The dispatcher lookup is only done once, and uses fast lookup matrix (1D or 2D); then, the functor found for this type(s) is cached within the ``Interaction`` (or ``Body``) instance. Thus, regular functor call costs the same as dereferencing pointer and calling virtual method. There is `blueprint `__ to avoid virtual function call as well. .. note:: At the beginning, the dispatch matrix contains just entries exactly matching given functors. Only when necessary (by passing other types), appropriate entries are filled in as well. Indexing dispatch types ^^^^^^^^^^^^^^^^^^^^^^^^ Classes entering the dispatch mechanism must provide for fast identification of themselves and of their parent class. [#rttiindex]_ This is called class indexing and all such classes derive from :yref:`Indexable`. There are ``top-level`` Indexables (types that the dispatchers accept) and each derived class registers its index related to this top-level Indexable. Currently, there are: ==================== =========================== Top-level Indexable used by ==================== =========================== :yref:`Shape` :yref:`BoundFunctor`, :yref:`IGeomDispatcher` :yref:`Material` :yref:`IPhysDispatcher` :yref:`IPhys` :yref:`LawDispatcher` :yref:`IGeom` :yref:`LawDispatcher` ==================== =========================== The top-level Indexable must use the ``REGISTER_INDEX_COUNTER`` macro, which sets up the machinery for identifying types of derived classes; they must then use the ``REGISTER_CLASS_INDEX`` macro *and* call ``createIndex()`` in their constructor. For instance, taking the :yref:`Shape` class (which is a top-level Indexable): .. code-block:: c++ // derive from Indexable class Shape: public Serializable, public Indexable { // never call createIndex() in the top-level Indexable ctor! /* … */ // allow index registration for classes deriving from ``Shape`` REGISTER_INDEX_COUNTER(Shape); }; Now, all derived classes (such as :yref:`Sphere` or :yref:`Facet`) use this: .. code-block:: c++ class Sphere: public Shape{ /* … */ YADE_CLASS_BASE_DOC_ATTRS_CTOR(Sphere,Shape,"docstring", ((Type1,attr1,default1,"docstring1")) /* … */, // this is the CTOR argument // important; assigns index to the class at runtime createIndex(); ); // register index for this class, and give name of the immediate parent class // (i.e. if there were a class deriving from Sphere, it would use // REGISTER_CLASS_INDEX(SpecialSphere,Sphere), // not REGISTER_CLASS_INDEX(SpecialSphere,Shape)!) REGISTER_CLASS_INDEX(Sphere,Shape); }; At runtime, each class within the top-level Indexable hierarchy has its own unique numerical index. These indices serve to build the dispatch matrix for each dispatcher. .. [#rttiindex] The functionality described in :ref:`rtti` serves a different purpose (serialization) and would hurt the performance here. For this reason, classes provide numbers (indices) in addition to strings. Inspecting dispatch in python ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ If there is a need to debug/study multiple dispatch, python provides convenient interface for this low-level functionality. We can inspect indices with the ``dispIndex`` property (note that the top-level indexable ``Shape`` has negative (invalid) class index; we purposively didn't call ``createIndex`` in its constructor): .. ipython:: Yade [5]: Sphere().dispIndex, Facet().dispIndex, Wall().dispIndex Yade [6]: Shape().dispIndex # top-level indexable Dispatch hierarchy for a particular class can be shown with the ``dispHierarchy()`` function, returning list of class names: 0th element is the instance itself, last element is the top-level indexable (again, with invalid index); for instance: .. ipython:: Yade [7]: ScGeom().dispHierarchy() # parent class of all other ScGeom_ classes Yade [8]: ScGridCoGeom().dispHierarchy(), ScGeom6D().dispHierarchy(), CylScGeom().dispHierarchy() Yade [8]: CylScGeom().dispHierarchy(names=False) # show numeric indices instead Dispatchers can also be inspected, using the .dispMatrix() method: .. ipython:: Yade [3]: ig=IGeomDispatcher([ ...: Ig2_Sphere_Sphere_ScGeom(), ...: Ig2_Facet_Sphere_ScGeom(), ...: Ig2_Wall_Sphere_ScGeom() ...: ]) Yade [4]: ig.dispMatrix() Yade [5]: ig.dispMatrix(False) # don't convert to class names We can see that functors make use of symmetry (i.e. that Sphere+Wall are dispatched to the same functor as Wall+Sphere). Finally, dispatcher can be asked to return functor suitable for given argument(s): .. ipython:: Yade [6]: ld=LawDispatcher([Law2_ScGeom_CpmPhys_Cpm()]) Yade [7]: ld.dispMatrix() # see how the entry for ScGridCoGeom will be filled after this request Yade [8]: ld.dispFunctor(ScGridCoGeom(),CpmPhys()) Yade [9]: ld.dispMatrix() OpenGL functors ^^^^^^^^^^^^^^^ OpenGL rendering is being done also by 1D functors (dispatched for the type to be rendered). Since it is sufficient to have exactly one class for each rendered type, the functors are found automatically. Their base functor types are ``GlShapeFunctor``, ``GlBoundFunctor``, ``GlIGeomFunctor`` and so on. These classes register the type they render using the ``RENDERS`` macro: .. code-block:: c++ class Gl1_Sphere: public GlShapeFunctor { public : virtual void go(const shared_ptr&, const shared_ptr&, bool wire, const GLViewInfo& ); RENDERS(Sphere); YADE_CLASS_BASE_DOC_STATICATTRS(Gl1_Sphere,GlShapeFunctor,"docstring", ((Type1,staticAttr1,informativeDefault,"docstring")) /* … */ ); }; REGISTER_SERIALIZABLE(Gl1_Sphere); You can list available functors of a particular type by querying child classes of the base functor: .. ipython:: @suppress Yade [1]: import yade.system Yade [2]: yade.system.childClasses('GlShapeFunctor') .. note:: OpenGL functors may disappear in the future, being replaced by virtual functions of each class that can be rendered. Parallel execution ------------------ Yade was originally not designed with parallel computation in mind, but rather with maximum flexibility (for good or for bad). Parallel execution was added later; in order to not have to rewrite whole Yade from scratch, relatively non-instrusive way of parallelizing was used: `OpenMP `__. OpenMP is standartized shared-memory parallel execution environment, where parallel sections are marked by special ``#pragma`` in the code (which means that they can compile with compiler that doesn't support OpenMP) and a few functions to query/manipulate OpenMP runtime if necessary. There is parallelism at 3 levels: * Computation, interaction (python, GUI) and rendering threads are separate. This is done via regular threads (boost::threads) and is not related to OpenMP. * :yref:`ParallelEngine` can run multiple engine groups (which are themselves run serially) in parallel; it rarely finds use in regular simulations, but it could be used for example when coupling with an independent expensive computation: .. code-block:: python ParallelEngine([ [Engine1(),Engine2()], # Engine1 will run before Engine2 [Engine3()] # Engine3() will run in parallel with the group [Engine1(),Engine2()] # arbitrary number of groups can be used ]) ``Engine2`` will be run after ``Engine1``, but in parallel with ``Engine3``. .. warning:: It is your reponsibility to avoid concurrent access to data when using ParallelEngine. Make sure you understand *very well* what the engines run in parallel do. * Parallelism inside Engines. Some loops over bodies or interactions are parallelized (notably :yref:`InteractionLoop` and :yref:`NewtonIntegrator`, which are treated in detail later (FIXME: link)): .. code-block:: c++ #pragma omp parallel for for(long id=0; id& b(scene->bodies[id]); /* … */ } .. note :: OpenMP requires loops over contiguous range of integers (OpenMP 3 also accepts containers with random-access iterators). If you consider running parallelized loop in your engine, always evalue its benefits. OpenMP has some overhead fo creating threads and distributing workload, which is proportionally more expensive if the loop body execution is fast. The results are highly hardware-dependent (CPU caches, RAM controller). Maximum number of OpenMP threads is determined by the ``OMP_NUM_THREADS`` environment variable and is constant throughout the program run. Yade main program also sets this variable (before loading OpenMP libraries) if you use the ``-j``/``--threads`` option. It can be queried at runtime with the ``omp_get_max_threads`` function. At places which are susceptible of being accessed concurrently from multiple threads, Yade provides some mutual exclusion mechanisms, discussed elsewhere (FIXME): * simultaneously writeable container for :ref:`ForceContainer`, * mutex for :yref:`Body::state`. .. _timing: Timing ------- Yade provides 2 services for measuring time spent in different pars of the code. One has the granularity of engine and can be enabled at runtime. The other one is finer, but requires adjusting and recompiling the code being measured. Per-engine timing ^^^^^^^^^^^^^^^^^^ The coarser timing works by merely accumulating numebr of invocations and time (with the precision of the ``clock_gettime`` function) spent in each engine, which can be then post-processed by associated Python module ``yade.timing``. There is a static bool variable controlling whether such measurements take place (disabled by default), which you can change .. code-block:: c++ TimingInfo::enabled=True; // in c++ .. code-block:: python O.timingEnabled=True ## in python After running the simulation, ``yade.timing.stats()`` function will show table with the results and percentages: .. ipython:: Yade [1]: TriaxialTest(numberOfGrains=100).load() Yade [2]: O.engines[0].label='firstEngine' ## labeled engines will show by labels in the stats table Yade [2]: import yade.timing; Yade [2]: O.timingEnabled=True Yade [1]: yade.timing.reset() ## not necessary if used for the first time Yade [3]: O.run(50); O.wait() Yade [5]: yade.timing.stats() Exec count and time can be accessed and manipulated through ``Engine::timingInfo`` from c++ or ``Engine().execCount`` and ``Engine().execTime`` properties in Python. In-engine and in-functor timing ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Timing within engines (and functors) is based on :yref:`TimingDeltas` class. It is made for timing loops (functors' loop is in their respective dispatcher) and stores cummulatively time differences between *checkpoints*. .. note:: Fine timing with ``TimingDeltas`` will only work if timing is enabled globally (see previous section). The code would still run, but giving zero times and exec counts. #. Engine::timingDeltas must point to an instance of :yref:`TimingDeltas` (prefferably instantiate :yref:`TimingDeltas` in the constructor): .. code-block:: c++ // header file class Law2_ScGeom_CpmPhys_Cpm: public LawFunctor { /* … */ YADE_CLASS_BASE_DOC_ATTRS_CTOR(Law2_ScGeom_CpmPhys_Cpm,LawFunctor,"docstring", /* attrs */, /* constructor */ timingDeltas=shared_ptr(new TimingDeltas); // timingDeltas object is automatically initialized when using -DENABLE_PROFILING=1 cmake option ); // ... }; #. Inside the loop, start the timing by calling ``timingDeltas->start();`` #. At places of interest, call ``timingDeltas->checkpoint("label")``. The label is used only for post-processing, data are stored based on the checkpoint position, not the label. .. warning:: Checkpoints must be always reached in the same order, otherwise the timing data will be garbage. Your code can still branch, but you have to put checkpoints to places which are in common. .. code-block:: c++ void Law2_ScGeom_CpmPhys_Cpm::go(shared_ptr& _geom, shared_ptr& _phys, Interaction* I, Scene* scene) { timingDeltas->start(); // the point at which the first timing starts // prepare some variables etc here timingDeltas->checkpoint("setup"); // find geometrical data (deformations) here timingDeltas->checkpoint("geom"); // compute forces here timingDeltas->checkpoint("material"); // apply forces, cleanup here timingDeltas->checkpoint("rest"); } #. Alternatively, you can compile Yade using -DENABLE_PROFILING=1 cmake option and use predefined macros TIMING_DELTAS_START and TIMING_DELTAS_CHECKPOINT. Without -DENABLE_PROFILING options, those macros are empty and do nothing. .. code-block:: c++ void Law2_ScGeom_CpmPhys_Cpm::go(shared_ptr& _geom, shared_ptr& _phys, Interaction* I, Scene* scene) { TIMING_DELTAS_START(); // prepare some variables etc here TIMING_DELTAS_CHECKPOINT("setup") // find geometrical data (deformations) here TIMING_DELTAS_CHECKPOINT("geom") // compute forces here TIMING_DELTAS_CHECKPOINT("material") // apply forces, cleanup here TIMING_DELTAS_CHECKPOINT("rest") } The output might look like this (note that functors are nested inside dispatchers and ``TimingDeltas`` inside their engine/functor):: Name Count Time Rel. time ------------------------------------------------------------------------------------- ForceReseter 400 9449μs 0.01% BoundDispatcher 400 1171770μs 1.15% InsertionSortCollider 400 9433093μs 9.24% IGeomDispatcher 400 15177607μs 14.87% IPhysDispatcher 400 9518738μs 9.33% LawDispatcher 400 64810867μs 63.49% Law2_ScGeom_CpmPhys_Cpm setup 4926145 7649131μs 15.25% geom 4926145 23216292μs 46.28% material 4926145 8595686μs 17.14% rest 4926145 10700007μs 21.33% TOTAL 50161117μs 100.00% NewtonIntegrator 400 1866816μs 1.83% "strainer" 400 21589μs 0.02% "plotDataCollector" 160 64284μs 0.06% "damageChecker" 9 3272μs 0.00% TOTAL 102077490μs 100.00% .. warning:: Do not use :yref:`TimingDeltas` in parallel sections, results might not be meaningful. In particular, avoid timing functors inside :yref:`InteractionLoop` when running with multiple OpenMP threads. ``TimingDeltas`` data are accessible from Python as list of (*label*,*time*,*count*) tuples, one tuple representing each checkpoint: .. code-block:: python deltas=someEngineOrFunctor.timingDeltas.data() deltas[0][0] # 0th checkpoint label deltas[0][1] # 0th checkpoint time in nanoseconds deltas[0][2] # 0th checkpoint execution count deltas[1][0] # 1st checkpoint label # … deltas.reset() Timing overhead ^^^^^^^^^^^^^^^ The overhead of the coarser, per-engine timing, is very small. For simulations with at least several hundreds of elements, they are below the usual time variance (a few percent). .. TriaxialTest(numberOfGrains=800).load(); O.saveTmp(); import time; O.timingEnabled=True; t0=time.time(); O.run(2000,True); print(time.time()-t0); O.loadTmp(); O.timingEnabled=False; t0=time.time(); O.run(2000,True); print (time.time()-t0) The finer :yref:`TimingDeltas` timing can have major performance impact and should be only used during debugging and performance-tuning phase. The parts that are file-timed will take disproportionally longer time that the rest of engine; in the output presented above, :yref:`LawDispatcher` takes almost ⅔ of total simulation time in average, but the number would be twice of thrice lower typically (note that each checkpoint was timed almost 5 million times in this particular case). OpenGL Rendering ----------------- Yade provides 3d rendering based on `QGLViewer `__. It is not meant to be full-featured rendering and post-processing, but rather a way to quickly check that scene is as intended or that simulation behaves sanely. .. note:: Although 3d rendering runs in a separate thread, it has performance impact on the computation itself, since interaction container requires mutual exclusion for interaction creation/deletion. The ``InteractionContainer::drawloopmutex`` is either held by the renderer (:yref:`OpenGLRenderingEngine`) or by the insertion/deletion routine. .. warning:: There are 2 possible causes of crash, which are not prevented because of serious performance penalty that would result: #. access to :yref:`BodyContainer`, in particular deleting bodies from simulation; this is a rare operation, though. #. deleting Interaction::phys or Interaction::geom. Renderable entities (:yref:`Shape`, :yref:`State`, :yref:`Bound`, :yref:`IGeom`, :yref:`IPhys`) have their associated `OpenGL functors`_. An entity is rendered if #. Rendering such entities is enabled by appropriate attribute in :yref:`OpenGLRenderingEngine` #. Functor for that particular entity type is found via the :ref:`dispatch mechanism`. ``Gl1_*`` functors operating on Body's attributes (:yref:`Shape`, :yref:`State`, :yref:`Bound`) are called with the OpenGL context translated and rotated according to :yref:`State::pos` and :yref:`State::ori`. Interaction functors work in global coordinates. Simulation framework ====================== Besides the support framework mentioned in the previous section, some functionality pertaining to simulation itself is also provided. There are special containers for storing bodies, interactions and (generalized) forces. Their internal functioning is normally opaque to the programmer, but should be understood as it can influence performance. Scene ------ ``Scene`` is the object containing the whole simulation. Although multiple scenes can be present in the memory, only one of them is active. Saving and loading (serializing and deserializing) the ``Scene`` object should make the simulation run from the point where it left off. .. note:: All :yref:`Engines` and functors have interally a ``Scene* scene`` pointer which is updated regularly by engine/functor callers; this ensures that the current scene can be accessed from within user code. For outside functions (such as those called from python, or static functions in ``Shop``), you can use ``Omega::instance().getScene()`` to retrieve a ``shared_ptr`` of the current scene. Body container --------------- Body container is linear storage of bodies. Each body in the simulation has its unique :yref:`id`, under which it must be found in the :yref:`BodyContainer`. Body that is not yet part of the simulation typically has id equal to invalid value ``Body::ID_NONE``, and will have its ``id`` assigned upon insertion into the container. The requirements on :yref:`BodyContainer` are * O(1) access to elements, * linear-addressability (0…n indexability), * store ``shared_ptr``, not objects themselves, * *no* mutual exclusion for insertion/removal (this must be assured by the called, if desired), * intelligent allocation of ``id`` for new bodies (tracking removed bodies), * easy iteration over all bodies. .. note:: Currently, there is "abstract" class ``BodyContainer``, from which derive concrete implementations; the initial idea was the ability to select at runtime which implementation to use (to find one that performs the best for given simulation). This incurs the penalty of many virtual function calls, and will probably change in the future. All implementations of BodyContainer were removed in the meantime, except ``BodyVector`` (internally a ``vector >`` plus a few methods around), which is the fastest. Insertion/deletion ^^^^^^^^^^^^^^^^^^ Body insertion is typically used in :yref:`FileGenerator`'s: .. code-block:: c++ shared_ptr body(new Body); // … (body setup) scene->bodies->insert(body); // assigns the id Bodies are deleted only rarely: .. code-block:: c++ scene->bodies->erase(id); .. warning:: Since mutual exclusion is not assured, never insert/erase bodies from parallel sections, unless you explicitly assure there will be no concurrent access. Iteration ^^^^^^^^^^ The container can be iterated over using ``FOREACH`` macro (shorthand for ``BOOST_FOREACH``): .. code-block:: c++ FOREACH(const shared_ptr& b, *scene->bodies){ if(!b) continue; // skip deleted bodies /* do something here */ } Note a few important things: #. Always use ``const shared_ptr&`` (const reference); that avoids incrementing and decrementing the reference count on each ``shared_ptr``. #. Take care to skip NULL bodies (``if(!b) continue``): deleted bodies are deallocated from the container, but since body id's must be persistent, their place is simply held by an empty ``shared_ptr()`` object, which is implicitly convertible to ``false``. In python, the BodyContainer wrapper also has iteration capabilities; for convenience (which is different from the c++ iterator), NULL bodies as silently skipped: .. ipython:: @suppress Yade [0]: O.reset() Yade [1]: O.bodies.append([Body(),Body(),Body()]) Yade [2]: O.bodies.erase(1) Yade [3]: [b.id for b in O.bodies] In loops parallelized using OpenMP, the loop must traverse integer interval (rather than using iterators): .. code-block:: c++ const long size=(long)bodies.size(); // store this value, since it doesn't change during the loop #pragma omp parallel for for(long _id=0; _id& b(bodies[_id]); if(!b) continue; /* … */ } InteractionContainer --------------------- Interactions are stored in special container, and each interaction must be uniquely identified by pair of ids (id1,id2). * O(1) access to elements, * linear-addressability (0…n indexability), * store ``shared_ptr``, not objects themselves, * mutual exclusion for insertion/removal, * easy iteration over all interactions, * addressing symmetry, i.e. interaction(id1,id2)≡interaction(id2,id1) .. note:: As with BodyContainer, there is "abstract" class InteractionContainer, and then its concrete implementations. Currently, only InteractionVecMap implementation is used and all the other were removed. Therefore, the abstract InteractionContainer class may disappear in the future, to avoid unnecessary virtual calls. Further, there is a `blueprint `__ for storing interactions inside bodies, as that would give extra advantage of quickly getting all interactions of one particular body (currently, this necessitates loop over all interactions); in that case, InteractionContainer would disappear. Insert/erase ^^^^^^^^^^^^ Creating new interactions and deleting them is delicate topic, since many eleents of simulation must be synchronized; the exact workflow is described in :ref:`interaction-flow`. You will almost certainly never need to insert/delete an interaction manually from the container; if you do, consider designing your code differently. .. code-block:: c++ // both insertion and erase are internally protected by a mutex, // and can be done from parallel sections safely scene->interactions->insert(shared_ptr(new Interactions(id1,id2))); scene->interactions->erase(id1,id2); Iteration ^^^^^^^^^ As with BodyContainer, iteration over interactions should use the ``FOREACH`` macro: .. code-block:: c++ FOREACH(const shared_ptr& i, *scene->interactions){ if(!i->isReal()) continue; /* … */ } Again, note the usage const reference for ``i``. The check ``if(!i->isReal())`` filters away interactions that exist only *potentially*, i.e. there is only :yref:`Bound` overlap of the two bodies, but not (yet) overlap of bodies themselves. The ``i->isReal()`` function is equivalent to ``i->geom && i->phys``. Details are again explained in :ref:`interaction-flow`. In some cases, such as OpenMP-loops requiring integral index (OpenMP >= 3.0 allows parallelization using random-access iterator as well), you need to iterate over interaction indices instead: .. code-block:: c++ int nIntr=(int)scene->interactions->size(); // hoist container size #pragma omp parallel for for(int j=0; j& i=(*scene->interactions)[j]; if(!i->isReal()) continue; /* … */ } .. _ForceContainer: ForceContainer -------------- :yref:`ForceContainer` holds "generalized forces", i.e. forces, torques, (explicit) dispalcements and rotations for each body. During each computation step, there are typically 3 phases pertaining to forces: #. Resetting forces to zero (usually done by the :yref:`ForceResetter` engine) #. Incrementing forces from parallel sections (solving interactions -- from :yref:`LawFunctor`) #. Reading absolute force values sequentially for each body: forces applied from different interactions are summed together to give overall force applied on that body (:yref:`NewtonIntegrator`, but also various other engine that read forces) This scenario leads to special design, which allows fast parallel write access: * each thread has its own storage (zeroed upon request), and only writes to its own storage; this avoids concurrency issues. Each thread identifies itself by the omp_get_thread_num() function provided by the OpenMP runtime. * before reading absolute values, the container must be synchronized, i.e. values from all threads are summed up and stored separately. This is a relatively slow operation and we provide ForceContainer::syncCount that you might check to find cummulative number of synchronizations and compare it against number of steps. Ideally, ForceContainer is only synchronized once at each step. * the container is resized whenever an element outside the current range is read/written to (the read returns zero in that case); this avoids the necessity of tracking number of bodies, but also is potential danger (such as ``scene->forces.getForce(1000000000)``, which will probably exhaust your RAM). Unlike c++, Python does check given id against number of bodies. .. code-block:: c++ // resetting forces (inside ForceResetter) scene->forces.reset() // in a parallel section scene->forces.addForce(id,force); // add force // container is not synced after we wrote to it, sync before reading scene->forces.sync(); const Vector3r& f=scene->forces.getForce(id); Synchronization is handled automatically if values are read from python: .. ipython:: Yade [0]: O.bodies.append(Body()) Yade [0]: O.forces.addF(0,Vector3(1,2,3)) Yade [0]: O.forces.f(0) Yade [0]: O.forces.f(100) .. _interaction-flow: Handling interactions ---------------------- Creating and removing interactions is a rather delicate topic and number of components must cooperate so that the whole behaves as expected. Terminologically, we distinguish potential interactions, having neither :yref:`geometry` nor :yref:`physics`. :yref:`Interaction.isReal` can be used to query the status (``Interaction::isReal()`` in c++). real interactions, having both :yref:`geometry` and :yref:`physics`. Below, we shall discuss the possibility of interactions that only have geometry but no physics. During each step in the simulation, the following operations are performed on interactions in a typical simulation: #. Collider creates potential interactions based on spatial proximity. Not all pairs of bodies are susceptible of entering interaction; the decision is done in Collider::mayCollide: * clumps may not enter interactions (only their members can) * clump members may not interact if they belong to the same clump * bitwise AND on both bodies' :yref:`masks` must be non-zero (i.e. there must be at least one bit set in common) #. Collider erases interactions that were requested for being erased (see below). #. :yref:`InteractionLoop` (via :yref:`IGeomDispatcher`) calls appropriate :yref:`IGeomFunctor` based on :yref:`Shape` combination of both bodies, if such functor exists. For real interactions, the functor updates associated :yref:`IGeom`. For potential interactions, the functor returns ``false`` if there is no geometrical overlap, and the interaction will stillremain potential-only ``true`` if there is geometrical overlap; the functor will have created an :yref:`IGeom` in such case. .. note :: For *real* interactions, the functor *must* return ``true``, even if there is no more spatial overlap between bodies. If you wish to delete an interaction without geometrical overlap, you have to do this in the :yref:`LawFunctor`. This behavior is deliberate, since different :yref:`laws` have different requirements, though ideally using relatively small number of generally useful :yref:`geometry functors`. .. note:: If there is no functor suitable to handle given combination of :yref:`shapes`, the interaction will be left in potential state, without raising any error. #. For real interactions (already existing or just created in last step), :yref:`InteractionLoop` (via :yref:`IPhysDispatcher`) calls appropriate :yref:`IPhysFunctor` based on :yref:`Material` combination of both bodies. The functor *must* update (or create, if it doesn't exist yet) associated :yref:`IPhys` instance. It is an error if no suitable functor is found, and an exception will be thrown. #. For real interactions, :yref:`InteractionLoop` (via :yref:`LawDispatcher`) calls appropriate :yref:`LawFunctor` based on combination of :yref:`IGeom` and :yref:`IPhys` of the interaction. Again, it is an error if no functor capable of handling it is found. #. :yref:`LawDispatcher` takes care of erasing those interactions that are no longer active (such as if bodies get too far apart for non-cohesive laws; or in case of complete damage for damage models). This is triggered by the :yref:`LawFunctor` returning false. For this reason it is of upmost importance for the :yref:`LawFunctor` to return consistently. Such interaction will not be deleted immediately, but will be reset to potential state. At the next execution of the collider ``InteractionContainer::conditionalyEraseNonReal`` will be called, which will completely erase interactions only if the bounding boxes ceased to overlap; the rest will be kept in potential state. Creating interactions explicitly ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Interactions may still be created explicitly with :yref:`yade.utils.createInteraction`, without any spatial requirements. This function searches current engines for dispatchers and uses them. :yref:`IGeomFunctor` is called with the ``force`` parameter, obliging it to return ``true`` even if there is no spatial overlap. Associating Material and State types ------------------------------------ Some models keep extra :yref:`state` information in the :yref:`Body.state` object, therefore requiring strict association of a :yref:`Material` with a certain :yref:`State` (for instance, :yref:`CpmMat` is associated to :yref:`CpmState` and this combination is supposed by engines such as :yref:`CpmStateUpdater`). If a :yref:`Material` has such a requirement, it must override 2 virtual methods: #. :yref:`Material.newAssocState`, which returns a new :yref:`State` object of the corresponding type. The default implementation returns :yref:`State` itself. #. :yref:`Material.stateTypeOk`, which checks whether a given :yref:`State` object is of the corresponding type (this check is run at the beginning of the simulation for all particles). In c++, the code looks like this (for :yref:`CpmMat`): .. code-block:: c++ class CpmMat: public FrictMat { public: virtual shared_ptr newAssocState() const { return shared_ptr(new CpmState); } virtual bool stateTypeOk(State* s) const { return (bool)dynamic_cast(s); } /* ... */ }; This allows one to construct :yref:`Body` objects from functions such as :yref:`yade.utils.sphere` only by knowing the requires :yref:`Material` type, enforcing the expectation of the model implementor. Runtime structure ================== Startup sequence ----------------- Yade's main program is python script in :ysrc:`core/main/main.py.in`; the build system replaces a few ``\${variables}`` in that file before copying it to its install location. It does the following: #. Process command-line options, set environment variables based on those options. #. Import main yade module (``import yade``), residing in :ysrc:`py/__init__.py.in`. This module locates plugins (recursive search for files ``lib*.so`` in the ``lib`` installation directory). :ysrc:`yade.boot` module is used to setup temporary directory, … and, most importantly, loads plugins. #. Manage further actions, such as running scripts given at command line, opening :yref:`yade.qt.Controller` (if desired), launching the ``ipython`` prompt. Singletons ----------- There are several "global variables" that are always accessible from c++ code; properly speaking, they are `Singletons `_, classes of which exactly one instance always exists. The interest is to have some general functionality acessible from anywhere in the code, without the necessity of passing pointers to such objects everywhere. The instance is created at startup and can be always retrieved (as non-const reference) using the ``instance()`` static method (e.g. ``Omega::instance().getScene()``). There are 3 singletons: ``SerializableSingleton`` Handles serialization/deserialization; it is not used anywhere except for the serialization code proper. ``ClassFactory`` Registers classes from plugins and able to factor instance of a class given its name as string (the class must derive from ``Factorable``). Not exposed to python. ``Omega`` Access to simulation(s); deserves separate section due to its importance. Omega ^^^^^^ The :yref:`Omega` class handles all simulation-related functionality: loading/saving, running, pausing. In python, the wrapper class to the singleton is instantiated [#oinst]_ as global variable ``O``. For convenience, :yref:`Omega` is used as proxy for scene's attribute: although multiple ``Scene`` objects may be instantiated in c++, it is always the current scene that :yref:`Omega` represents. The correspondence of data is literal: :yref:`Omega.materials` corresponds to ``Scene::materials`` of the current scene; likewise for :yref:`materials`, :yref:`bodies`, :yref:`interactions`, :yref:`tags`, :yref:`cell`, :yref:`engines`, :yref:`initializers`, :yref:`miscParams`. To give an overview of (some) variables: ======================== =================================== Python c++ ======================== =================================== :yref:`Omega.iter` ``Scene::iter`` :yref:`Omega.dt` ``Scene::dt`` :yref:`Omega.time` ``Scene::time`` :yref:`Omega.realtime` ``Omega::getRealTime()`` :yref:`Omega.stopAtIter` ``Scene::stopAtIter`` ======================== =================================== ``Omega`` in c++ contains pointer to the current scene (``Omega::scene``, retrieved by ``Omega::instance().getScene()``). Using :yref:`Omega.switchScene`, it is possible to swap this pointer with ``Omega::sceneAnother``, a completely independent simulation. This can be useful for example (and this motivated this functionality) if while constructing simulation, another simulation has to be run to dynamically generate (i.e. by running simulation) packing of spheres. .. rubric:: Footnotes .. [#oinst] It is understood that instantiating ``Omega()`` in python only instantiates the wrapper class, not the singleton itself. Engine loop ------------ Running simulation consists in looping over :yref:`Engines` and calling them in sequence. This loop is defined in ``Scene::moveToNextTimeStep`` function in :ysrc:`core/Scene.cpp`. Before the loop starts, :yref:`O.initializers` are called; they are only run once. The engine loop does the following in each iteration over :yref:`O.engines`: #. set ``Engine::scene`` pointer to point to the current ``Scene``. #. Call ``Engine::isActivated()``; if it returns ``false``, the engine is skipped. #. Call ``Engine::action()`` #. If :yref:`O.timingEnabled`, increment :yref:`Engine::execTime` by the difference from the last time reading (either after the previous engine was run, or immediately before the loop started, if this engine comes first). Increment :yref:`Engine::execCount` by 1. After engines are processed, :yref:`virtual time` is incremented by :yref:`timestep` and :yref:`iteration number` is incremented by 1. Background execution ^^^^^^^^^^^^^^^^^^^^^ The engine loop is (normally) executed in background thread (handled by :ysrc:`SimulationFlow` class), leaving foreground thread free to manage user interaction or running python script. The background thread is managed by :yref:`O.run()` and :yref:`O.pause()` commands. Foreground thread can be blocked until the loop finishes using :yref:`O.wait()`. Single iteration can be run without spawning additional thread using :yref:`O.step()`. Python framework ================= Wrapping c++ classes --------------------- Each class deriving from :yref:`Serializable` is automatically exposed to python, with access to its (registered) attributes. This is achieved via :ref:`YADE_CLASS_BASE_DOC`. All classes registered in class factory are default-constructed in ``Omega::buildDynlibDatabase``. Then, each serializable class calls ``Serializable::pyRegisterClass`` virtual method, which injects the class wrapper into (initially empty) ``yade.wrapper`` module. ``pyRegisterClass`` is defined by ``YADE_CLASS_BASE_DOC`` and knows about class, base class, docstring, attributes, which subsequently all appear in boost::python class definition. Wrapped classes define special constructor taking keyword arguments corresponding to class attributes; therefore, it is the same to write: .. ipython:: Yade [1]: f1=ForceEngine() Yade [2]: f1.ids=[0,4,5] Yade [2]: f1.force=Vector3(0,-1,-2) and .. ipython:: Yade [1]: f2=ForceEngine(ids=[0,4,5],force=Vector3(0,-1,-2)) Yade [2]: print f1.dict() Yade [3]: print f2.dict() Wrapped classes also inherit from :yref:`Serializable` several special virtual methods: :yref:`dict()` returning all registered class attributes as dictionary (shown above), :yref:`clone()` returning copy of instance (by copying attribute values), :yref:`updateAttrs()` and :yref:`updateExistingAttrs()` assigning attributes from given dictionary (the former thrown for unknown attribute, the latter doesn't). Read-only property ``name`` wraps c++ method ``getClassName()`` returning class name as string. (Since c++ class and the wrapper class always have the same name, getting python type using ``__class__`` and its property ``__name__`` will give the same value). .. ipython:: Yade [1]: s=Sphere() Yade [2]: s.__class__.__name__ Subclassing c++ types in python -------------------------------- In some (rare) cases, it can be useful to derive new class from wrapped c++ type in pure python. This is done in the :ref:`yade.pack` module: :yref:`Predicate` is c++ base class; from this class, several c++ classes are derived (such as :yref:`inGtsSurface`), but also python classes (such as the trivial :yref:`inSpace` predicate). ``inSpace`` derives from python class ``Predicate``; it is, however, not direct wrapper of the c++ ``Predicate`` class, since virtual methods would not work. ``boost::python`` provides special ``boost::python::wrapper`` template for such cases, where each overridable virtual method has to be declared explicitly, requesting python override of that method, if present. See `Overridable virtual functions `_ for more details. Reference counting ------------------ Python internally uses `reference counting `_ on all its objects, which is not visible to casual user. It has to be handled explicitly if using pure `Python/C API `_ with ``Py_INCREF`` and similar functions. ``boost::python`` used in Yade fortunately handles reference counting internally. Additionally, it `automatically integrates `_ reference counting for ``shared_ptr`` and python objects, if class ``A`` is wrapped as ``boost::python::class_>``. Since *all* Yade classes wrapped using :ref:`YADE_CLASS_BASE_DOC` are wrapped in this way, returning ``shared_ptr<…>`` objects from is the preferred way of passing objects from c++ to python. Returning ``shared_ptr`` is much more efficient, since only one pointer is returned and reference count internally incremented. Modifying the object from python will modify the (same) object in c++ and vice versa. It also makes sure that the c++ object will not be deleted as long as it is used somewhere in python, preventing (important) source of crashes. .. _customconverters: Custom converters ----------------- When an object is passed from c++ to python or vice versa, then either #. the type is basic type which is transparently passed between c++ and python (int, bool, std::string etc) #. the type is wrapped by boost::python (such as Yade classes, ``Vector3`` and so on), in which case wrapped object is returned; [#wrap]_ Other classes, including template containers such as ``std::vector`` must have their custom converters written separately. Some of them are provided in :ysrc:`py/wrapper/customConverters.cpp`, notably converters between python (homogeneous, i.e. with all elements of the same type) sequences and c++ ``std::vector`` of corresponding type; look in that source file to add your own converter or for inspiration. When an object is crossing c++/python boundary, boost::python's global "converters registry" is searched for class that can perform conversion between corresponding c++ and python types. The "converters registry" is common for the whole program instance: there is no need to register converters in each script (by importing ``_customConverters``, for instance), as that is done by yade at startup already. .. note:: Custom converters only work for value that are passed by value to python (not "by reference"): some attributes defined using :ref:`YADE_CLASS_BASE_DOC` are passed by value, but if you define your own, make sure that you read and understand `Why is my automatic to-python conversion not being found? `_. In short, the default for ``def_readwrite`` and ``def_readonly`` is to return references to underlying c++ objects, which avoids performing conversion on them. For that reason, return value policy must be set to ``return_by_value`` explicitly, using slighly more complicated ``add_property`` syntax, as explained at the page referenced. .. [#wrap] Wrapped classes are automatically registered when the class wrapper is created. If wrapped class derives from another wrapped class (and if this dependency is declared with the ``boost::python::bases`` template, which Yade's classes do automatically), parent class must be registered before derived class, however. (This is handled via loop in ``Omega::buildDynlibDatabase``, which reiterates over classes, skipping failures, until they all successfully register) Math classes (Vector3, Matrix3, Quaternion) are wrapped in minieigen, which is available as a separate package. Use your package manager to install it. Maintaining compatibility ========================== In Yade development, we identified compatibility to be very strong desire of users. Compatibility concerns python scripts, *not* simulations saved in XML or old c++ code. Renaming class --------------- Script :ysrc:`scripts/rename-class.py` should be used to rename class in ``c++`` code. It takes 2 parameters (old name and new name) and must be run from top-level source directory:: \$ scripts/rename-class.py OldClassName NewClassName Replaced 4 occurences, moved 0 files and 0 directories Update python scripts (if wanted) by running: perl -pi -e 's/\bOldClassName\b/NewClassName/g' `ls **/*.py |grep -v py/system.py` This has the following effects: #. If file or directory has basename ``OldClassName`` (plus extension), it will be renamed using ``bzr``. #. All occurences of whole word ``OldClassName`` will be replaced by ``NewClassName`` in c++ sources. #. An extry is added to :ysrc:`py/system.py`, which contains map of deprecated class names. At yade startup, proxy class with ``OldClassName`` will be created, which issues a ``DeprecationWarning`` when being instantiated, informing you of the new name you should use; it creates an instance of ``NewClassName``, hence not disruting your script's functioning:: Yade [3]: SimpleViscoelasticMat() /usr/local/lib/yade-trunk/py/yade/__init__.py:1: DeprecationWarning: Class `SimpleViscoelasticMat' was renamed to (or replaced by) `ViscElMat', update your code! (you can run 'yade --update script.py' to do that automatically) -> [3]: As you have just been informed, you can run ``yade --update`` to all old names with their new names in scripts you provide:: \$ yade-trunk --update script1.py some/where/script2.py This gives you enough freedom to make your class name descriptive and intuitive. Renaming class attribute ------------------------ Renaming class attribute is handled from c++ code. You have the choice of merely warning at accessing old attribute (giving the new name), or of throwing exception in addition, both with provided explanation. See ``deprec`` parameter to :ref:`YADE_CLASS_BASE_DOC` for details. Debian packaging instructions ============================== In order to make parallel installation of several Yade version possible, we adopted similar strategy as e.g. ``gcc`` packagers in Debian did: #. Real Yade packages are named ``yade-0.30`` (for stable versions) or ``yade-bzr2341`` (for snapshots). #. They provide ``yade`` or ``yade-snapshot`` virtual packages respectively. #. Each source package creates several installable packages (using ``bzr2341`` as example version): #. ``yade-bzr2341`` with the optimized binaries; the executable binary is ``yade-bzr2341`` (``yade-bzr2341-multi``, …) #. ``yade-bzr2341-dbg`` with debug binaries (debugging symbols, non-optimized, and with crash handlers); the executable binary is ``yade-bzr2341-dbg`` #. ``yade-bzr2341-doc`` with sample scripts and some documentation (see `bug #398176 `_ however) #. (future?) ``yade-bzr2341-reference`` with reference documentation (see `bug #401004 `_) #. Using `Debian alternatives `_, the highest installed package provides additionally commands without the version specification like ``yade``, ``yade-multi``, … as aliases to that version's binaries. (``yade-dbg``, … for the debuggin packages). The exact rule is: #. Stable releases have always higher priority than snapshots #. Higher versions/revisions have higher pripority than lower versions/revisions. Prepare source package ---------------------- Debian packaging files are located in :ysrc:`debian/` directory. They contain build recipe :ysrc:`debian/rules`, dependecy and package declarations :ysrc:`debian/control` and maintainer scripts. Some of those files are only provided as templates, where some variables (such as version number) are replaced by special script. The script :ysrc:`scripts/debian-prep` processes templates in :ysrc:`debian/` and creates files which can be used by debian packaging system. Before running this script: #. If you are releasing stable version, make sure there is file named ``RELEASE`` containing single line with version number (such as ``0.30``). This will make :ysrc:`scripts/debian-prep` create release packages. In absence of this file, snapshots packaging will be created instead. Release or revision number (as detected by running ``bzr revno`` in the source tree) is stored in ``VERSION`` file, where it is picked up during package build and embedded in the binary. #. Find out for which debian/ubuntu series your package will be built. This is the name that will appear on the top of (newly created) ``debian/changelog`` file. This name will be usually ``unstable``, ``testing`` or ``stable`` for debian and ``karmic``, ``lucid`` etc for ubuntu. WHen package is uploaded to Launchpad's build service, the package will be built for this specified release. Then run the script from the top-level directory, giving series name as its first (only) argument:: \$ scripts/debian-prep lucid After this, signed debian source package can be created:: \$ debuild -S -sa -k62A21250 -I -Iattic (``-k`` gives GPG key identifier, ``-I`` skips ``.bzr`` and similar directories, ``-Iattic`` will skip the useless ``attic`` directory). Create binary package --------------------- Local in-tree build Once files in ``debian/`` are prepared, packages can be build by issuing:: \$ fakeroot debian/rules binary Clean system build Using ``pbuilder`` system, package can be built in a chroot containing clean debian/ubuntu system, as if freshly installed. Package dependencies are automatically installed and package build attempted. This is a good way of testing packaging before having the package built remotely at Launchpad. Details are provided at `wiki page `_. Launchpad build service Launchpad provides service to compile package for different ubuntu releases (series), for all supported architectures, and host archive of those packages for download via APT. Having appropriate permissions at Launchpad (verified GPG key), source package can be uploaded to yade's archive by:: \$ dput ppa:yade-users/ppa ../yade-bzr2341_1_source.changes After several hours (depending on load of Launchpad build farm), new binary packages will be published at https://launchpad.net/~yade-users/+archive/ppa. This process is well documented at https://help.launchpad.net/Packaging/PPA. trunk-2018.02b/doc/sphinx/publications.rst.in000066400000000000000000000026731324306050200211070ustar00rootroot00000000000000Publications on Yade ===================== This page should be a relatively complete list of publications on Yade itself or done with Yade. If you publish something, do not hesitate to add it on the list. If you don't have direct access to the source code, please send the reference (as a bibtex item) to the `editorial board `_. If PDF is freely available, add url for direct fulltext downlad. If not, consider uploading fulltext in PDF, either to `Yade wiki `_ or to other website, if legally permitted. The first section gives the references that we kindly ask you to use for citing Yade in publications, as explained in the `"Acknowledging Yade" `_ section. .. note:: This file is generated from :ysrc:`doc/yade-articles.bib`, :ysrc:`doc/yade-conferences.bib`, :ysrc:`doc/yade-theses.bib` and :ysrc:`doc/yade-docref.bib`. Citing Yade --------------------- Corresponding bibtex entries `here `_. Pdf versions are available for each of them. See also `"Acknowledging Yade" `_. @bib2rst.bib2rst('../yade-docref.bib')@ Journal articles ----------------- @bib2rst.bib2rst('../yade-articles.bib')@ Conference materials and book chapters -------------------------------------- @bib2rst.bib2rst('../yade-conferences.bib')@ Master and PhD theses ----------------------- @bib2rst.bib2rst('../yade-theses.bib')@ trunk-2018.02b/doc/sphinx/references.rst.in000066400000000000000000000002751324306050200205300ustar00rootroot00000000000000 References ============ All external articles referenced in Yade documentation. .. note:: This file is generated from :ysrc:`doc/references.bib`. @bib2rst.bib2rst('../references.bib')@ trunk-2018.02b/doc/sphinx/sitecustomize.py000066400000000000000000000001501324306050200205210ustar00rootroot00000000000000# necessary for our docstrings containing unicode characters import sys sys.setdefaultencoding('utf-8') trunk-2018.02b/doc/sphinx/static-html/000077500000000000000000000000001324306050200174755ustar00rootroot00000000000000trunk-2018.02b/doc/sphinx/static-html/README000066400000000000000000000005641324306050200203620ustar00rootroot00000000000000This directory is currently empty, but might contain static files for the HTML output. From conf.py: # 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-html'] trunk-2018.02b/doc/sphinx/templates/000077500000000000000000000000001324306050200172425ustar00rootroot00000000000000trunk-2018.02b/doc/sphinx/templates/index.html000066400000000000000000000172741324306050200212520ustar00rootroot00000000000000{% extends "layout.html" %} {% set title = 'Overview' %} {% block body %}

    Welcome to Yade - Open Source Discrete Element Method

    Yade is an extensible open-source framework for discrete numerical models, focused on Discrete Element Method. The computation parts are written in c++ using flexible object model, allowing independent implementation of new alogrithms and interfaces. Python is used for rapid and concise scene construction, simulation control, postprocessing and debugging.

    Since March 2012 Yade is using GIT as VCS and placing the source code on github. The Launchpad is importing source code several times per day.

    Please make sure you read the "Acknowledging Yade" section if you plan to cite Yade in publications.

    This documentation decribes Yade version {{ release }} / {{ version }}.

    Yade Community

    Documentation

    {% endblock %} trunk-2018.02b/doc/sphinx/templates/layout.html000066400000000000000000000017101324306050200214440ustar00rootroot00000000000000{% extends "!layout.html" %} {% block extrahead %} {{ super() }} {%- if not embedded %} {%- endif %} {% endblock %} {% block footer %} {{ super() }} {% endblock %} {% block rootrellink %}
  1. Yade | 
  2. Documentation »
  3. {% endblock %} trunk-2018.02b/doc/sphinx/tutorial-advanced.rst000066400000000000000000000007101324306050200214020ustar00rootroot00000000000000Advanced & more =============== Particle size distribution -------------------------- See :ref:`periodic-triaxial-test` Clumps ------ :yref:`Clump`; see :ref:`periodic-triaxial-test` Testing laws ------------ :yref:`LawTester`, :ysrc:`scripts/test/law-test.py` New law ------- Visualization ------------- See the example :ref:`3d-postprocessing` * :yref:`VTKRecorder` & `Paraview `__ * :yref:`yade.qt.SnapshotEngine` trunk-2018.02b/doc/sphinx/tutorial-data-mining.rst000066400000000000000000000214341324306050200220330ustar00rootroot00000000000000Data mining ============= Read ----- Local data ^^^^^^^^^^^ All data of the simulation are accessible from python; when you open the *Inspector*, blue labels of various data can be clicked -- left button for getting to the documentation, middle click to copy the name of the object (use ``Ctrl-V`` or middle-click to paste elsewhere). The interesting objects are among others (see :yref:`Omega` for a full list): #. :yref:`O.engines` Engines are accessed by their index (position) in the simulation loop:: O.engines[0] # first engine O.engines[-1] # last engine .. note:: The index can change if :yref:`O.engines` is modified. *Labeling* introduced below is a better solution for reliable access to a particular engine. #. :yref:`O.bodies` Bodies are identified by their :yref:`id`, which is guaranteed to not change during the whole simulation:: O.bodies[0] # first body [b.shape.radius in O.bodies if isinstance(b.shape,Sphere)] # list of radii of all spherical bodies sum([b.state.mass for b in O.bodies]) # sum of masses of all bodies .. note:: Uniqueness of :yref:`Body.id` is not guaranteed, since newly created bodies might recycle :yref:`ids` of :yref:`deleted` ones. #. :yref:`O.force` Generalized forces (forces, torques) acting on each particle. They are (usually) reset at the beginning of each step with :yref:`ForceResetter`, subsequently forces from individual interactions are accumulated in :yref:`InteractionLoop`. To access the data, use:: O.forces.f(0) # force on #0 O.forces.t(1) # torque on #1 #. :yref:`O.interactions` Interactions are identified by :yref:`ids` of the respective interacting particles (they are created and deleted automatically during the simulation):: O.interactions[0,1] # interactions of #0 with #1 O.interactions[1,0] # the same object O.bodies[0].intrs # all interactions of body #0 Labels """"""" :yref:`Engines` and :yref:`functors` can be *labeled*, which means that python variable of that name is automatically created. .. ipython:: @suppress Yade [1]: from yade import * Yade [1]: O.engines=[ ...: NewtonIntegrator(damping=.2,label='newton') ...: ] ...: Yade [1]: newton.damping=.4 Yade [1]: O.engines[0].damping # O.engines[0] and newton are the same objects .. rubric:: Exercises #. Find meaning of this expression:: max([b.state.vel.norm() for b in O.bodies]) #. Run the gravity deposition script, pause after a few seconds of simulation. Write expressions that compute #. kinetic energy $\sum \frac{1}{2} m_i |v_i| ^2$ #. average mass (hint: use `numpy.average `__) #. maximum $z$-coordinate of all particles #. number of interactions of body #1 Global data ^^^^^^^^^^^ Useful measures of what happens in the simulation globally: unbalanced force ratio of maximum contact force and maximum per-body force; measure of staticity, computed with :yref:`yade.utils.unbalancedForce`. porosity ratio of void volume and total volume; computed with :yref:`yade.utils.porosity`. coordination number average number of interactions per particle, :yref:`yade.utils.avgNumInteractions` stress tensor (periodic boundary conditions) averaged force in interactions, computed with :yref:`yade.utils.normalShearStressTensor` and :yref:`yade.utils.stressTensorOfPeriodicCell` fabric tensor distribution of contacts in space (not yet implemented); can be visualized with :yref:`yade.utils.plotDirections` Energies """""""" Evaluating energy data for all components in the simulation (such as gravity work, kinetic energy, plastic dissipation, damping dissipation) can be enabled with :: O.trackEnergy=True Subsequently, energy values are accessible in the :yref:`O.energy`; it is a dictionary where its entries can be retrived with ``keys()`` and their values with ``O.energy[key]``. Save ---- PyRunner ^^^^^^^^^ To save data that we just learned to access, we need to call Python from within the *simulation loop*. :yref:`PyRunner` is created just for that; it inherits periodicy control from :yref:`PeriodicEngine` and takes the code to run as text (must be quoted, i.e. inside ``'...'``) attributed called *command*. For instance, adding this to :yref:`O.engines` will print the current step number every second:: O.engines=O.engines+[ PyRunner(command='print O.iter',realPeriod=1) ] Writing complicated code inside *command* is awkward; in such case, we define a function that will be called:: def myFunction(): '''Print step number, and pause the simulation is unbalanced force is smaller than 0.05.''' print O.iter if utils.unbalancedForce()<0.05: print 'Unbalanced force is smaller than 0.05, pausing.' O.pause() O.engines=[ # ... PyRunner(command='myFunction()',iterPeriod=100) # call myFunction every 100 steps ] .. rubric:: Exercises #. Run the gravity deposition simulation, but change it such that: #. :yref:`yade.utils.unbalancedForce` is printed every 2 seconds. #. check every 1000 steps the value of unbalanced force * if smaller than 0.2, set :yref:`damping` to 0.8 (hint: use labels) * if smaller than 0.1, pause the simulation Keeping history ^^^^^^^^^^^^^^^^^ Yade provides the :yref:`yade.plot` module used for storing and plotting variables (plotting itself will be discussed later). Periodic storing of data is done with :yref:`PyRunner` and the :yref:`yade.plot.addData` function, for instance:: from yade import plot O.engines=[ # ..., PyRunner(command='addPlotData()',realPeriod=2) # call the addPlotData function every 2 seconds of human time ] def addPlotData(): # this function adds current values to the history of data, under the names specified plot.addData(i=O.iter,t=O.time,Ek=utils.kineticEnergy(),coordNum=utils.avgNumInteractions(),unForce=utils.unbalancedForce()) History is stored in :yref:`yade.plot.data`, and can be accessed using the variable name, e.g. ``plot.data['Ek']``, and saved to text file (for post-processing outside yade) with :yref:`yade.plot.saveTxt`. Plot ----- :yref:`yade.plot` provides facilities for plotting history saved with :yref:`yade.plot.addData` as 2d plots. Data to be plotted are specified using dictionary :yref:`yade.plot.plots` :: plot.plots={'t':('coordNum','unForce',None,'Ek')} History of all values is given as the name used for :yref:`yade.plot.addData`; keys of the dictionary are $x$-axis values, and values are sequence of data on the $y$ axis; the ``None`` separates data on the left and right axes (they are scaled independently). The plot itself is created with :: plot.plot() # on the command line, F8 can be used as shorthand While the plot is open, it will be updated periodically, so that simulation evolution can be seen in real-time. Energy plots ^^^^^^^^^^^^^ Plotting all energy contributions would be difficult, since names of all energies might not be known in advance. Fortunately, there is a way to handle that in Yade. It consists in two parts: #. :yref:`yade.plot.addData` is given all the energies that are currently defined:: plot.addData(i=O.iter,total=O.energy.total(),**O.energy) The :yref:`O.energy.total` functions, which sums all energies together. The ``**O.energy`` is special python syntax for converting dictionary (remember that :yref:`O.energy` is a dictionary) to named functions arguments, so that the following two commands are identical:: function(a=3,b=34) # give arguments as arguments function(**{'a':3,'b':34}) # create arguments from dictionary #. Data to plot are specified using a *function* that gives names of data to plot, rather than providing the data names directly:: plot.plots={'i':['total',O.energy.keys()]} where ``total`` is the name we gave to ``O.energy.total()`` above, while ``O.energy.keys()`` will always return list of currently defined energies. .. rubric:: Exercises #. Run the gravity deposition script, plotting unbalanced force and kinetic energy. #. While the script is running, try changing the :yref:`NewtonIntegrator.damping` parameter (do it from both *Inspector* and from the command-line). What influence does it have on the evolution of unbalanced force and kinetic energy? #. Think about and write down all energy sources (input); write down also all energy sinks (dissipation). #. Simulate gravity deposition and plot all energies as they evolve during the simulation. .. seealso:: Most :ref:`examples` use plotting facilities of Yade, some of them also track energy of the simulation. trunk-2018.02b/doc/sphinx/tutorial-examples.rst000066400000000000000000000017051324306050200214600ustar00rootroot00000000000000.. _examples: Examples ========= .. _bouncing-sphere: Bouncing sphere --------------- .. youtube:: CMfL8PGq-xQ .. literalinclude:: tutorial/01-bouncing-sphere.py .. _gravity-deposition: Gravity deposition ------------------ .. youtube:: YUlUSI9YADM .. literalinclude:: tutorial/02-gravity-deposition.py .. _oedometric-test: Oedometric test ---------------- .. youtube:: RjH1v-Fth34 .. literalinclude:: tutorial/03-oedometric-test.py Batch table ^^^^^^^^^^^^ .. literalinclude:: tutorial/03-oedometric-test.table .. _periodic-simple-shear: Periodic simple shear --------------------- .. youtube:: ZKHrILQCyZs .. literalinclude:: tutorial/04-periodic-simple-shear.py .. _3d-postprocessing: 3d postprocessing ----------------- .. literalinclude:: tutorial/05-3d-postprocessing.py .. _periodic-triaxial-test: Periodic triaxial test ---------------------- .. youtube:: utTDLZz0y_w .. literalinclude:: tutorial/06-periodic-triaxial-test.py trunk-2018.02b/doc/sphinx/tutorial-geo.rst000066400000000000000000000145101324306050200204120ustar00rootroot00000000000000Towards geomechanics ===================== .. seealso:: Examples :ref:`gravity-deposition`, :ref:`oedometric-test`, :ref:`periodic-simple-shear`, :ref:`periodic-triaxial-test` deal with topics discussed here. Parametric studies ------------------- Input parameters of the simulation (such as size distribution, damping, various contact parameters, …) influence the results, but frequently an analytical relationship is not known. To study such influence, similar simulations differing only in a few parameters can be run and results compared. Yade can be run in *batch mode*, where one simulation script is used in conjunction with *parameter table*, which specifies parameter values for each run of the script. Batch simulation are run non-interactively, i.e. without user intervention; the user must therefore start and stop the simulation explicitly. Suppose we want to study the influence of :yref:`damping` on the evolution of kinetic energy. The script has to be adapted at several places: #. We have to make sure the script reads relevant parameters from the *parameter table*. This is done using :yref:`yade.utils.readParamsFromTable`; the parameters which are read are created as variables in the ``yade.params.table`` module:: utils.readParamsFromTable(damping=.2) # yade.params.table.damping variable will be created from yade.params import table # typing table.damping is easier than yade.params.table.damping Note that :yref:`yade.utils.readParamsFromTable` takes default values of its parameters, which are used if the script is not run in non-batch mode. #. Parameters from the table are used at appropriate places:: NewtonIntegrator(damping=table.damping), #. The simulation is run non-interactively; we must therefore specify at which point it should stop:: O.engines+=[PyRunner(iterPeriod=1000,command='checkUnbalancedForce()')] # call our function defined below periodically def checkUnbalancedForce(): if utils.unbalancedForce<0.05: # exit Yade if unbalanced force drops below 0.05 utils.saveDataTxt(O.tags['d.id']+'.data.bz2') # save all data into a unique file before exiting import sys sys.exit(0) # exit the program #. Finally, we must start the simulation at the very end of the script:: O.run() # run forever, until stopped by checkUnbalancedForce() utils.waitIfBatch() # do not finish the script until the simulation ends; does nothing in non-batch mode The *parameter table* is a simple text-file, where each line specifies a simulation to run:: # comments start with # as in python damping # first non-comment line is variable name .2 .4 .6 Finally, the simulation is run using the special batch command:: user@machine:~\$ yade-batch parameters.table simulation.py .. todo:: Parametric studies need to be described better. Perhaps the behavior should be changed so that in batch mode, :yref:`O.run` and :yref:`yade.utils.waitIfBatch` are run from the main yade script by default. That would however make it only possible to exit the batch via ``sys.exit(0)``, or by a new function like ``utils.exitBatch()`` (which would call ``sys.exit(0)`` for now anyway) .. rubric:: Exercises #. Run the gravity deposition script in batch mode, varying :yref:`damping` to take values of ``.2``, ``.4``, ``.6``. See the http://localhost:9080 overview page while the batch is running. Boundary --------- Particles moving in infinite space usually need some constraints to make the simulation meaningful. Supports ^^^^^^^^^ So far, supports (unmovable particles) were providing necessary boundary: in the gravity deposition script, :yref:`yade.utils.facetBox` is by internally composed of :yref:`facets ` (triangulation elements), which is ``fixed`` in space; facets are also used for arbitrary triangulated surfaces (see relevant sections of the *User's manual*). Another frequently used boundary is :yref:`yade.utils.wall` (infinite axis-aligned plane). Periodic ^^^^^^^^^ Periodic boundary is a "boundary" created by using periodic (rather than infinite) space. Such boundary is activated by :yref:`O.periodic=True `, and the space configuration is decribed by :yref:`O.cell `. It is well suited for studying bulk material behavior, as boundary effects are avoided, leading to smaller number of particles. On the other hand, it might not be suitable for studying localization, as any cell-level effects (such as shear bands) have to satisfy periodicity as well. The periodic cell is described by its :yref:`reference size ` of box aligned with global axes, and :yref:`current transformation`, which can capture stretch, shear and rotation. Deformation is prescribed via :yref:`velocity gradient`, which updates the :yref:`transformation` before the next step. :yref:`Homothetic deformation` can smear :yref:`velocity gradient` accross the cell, making the boundary dissolve in the whole cell. Stress and strains can be controlled with :yref:`PeriTriaxController`; it is possible to prescribe mixed strain/stress :yref:`goal` state using :yref:`PeriTriaxController.stressMask`. The following creates periodic cloud of spheres and compresses to achieve $\sigma_x$=-10 kPa, $\sigma_y$=-10kPa and $\eps_z$=-0.1. Since stress is specified for $y$ and $z$, :yref:`stressMask` is ``0b011`` (x→1, y→2, z→4, in decimal 1+2=3). .. ipython:: @suppress Yade [1]: from yade import pack Yade [1]: sp=pack.SpherePack() Yade [1]: sp.makeCloud((1,1,1),(2,2,2),rMean=.2,periodic=True) Yade [1]: sp.toSimulation() # implicitly sets O.periodic=True, and O.cell.refSize to the packing period size Yade [1]: O.engines+=[PeriTriaxController(goal=(-1e4,-1e4,-.1),stressMask=0b011,maxUnbalanced=.2,doneHook='functionToRunWhenFinished()')] When the simulation :yref:`runs`, :yref:`PeriTriaxController` takes over the control and calls :yref:`doneHook` when :yref:`goal` is reached. A full simulation with PeriTriaxController might look like the following: .. literalinclude:: tutorial/peri-triax.py trunk-2018.02b/doc/sphinx/tutorial-hands-on.rst000066400000000000000000000462521324306050200213570ustar00rootroot00000000000000.. _hands-on: Hands-on ======== Shell basics ------------- Directory tree ^^^^^^^^^^^^^^^ Directory tree is hierarchical way to organize files in operating systems. A typical (reduced) tree looks like this:: / Root ├──boot System startup ├──bin Low-level programs ├──lib Low-level libraries ├──dev Hardware access ├──sbin Administration programs ├──proc System information ├──var Files modified by system services ├──root Root (administrator) home directory ├──etc Configuration files ├──media External drives ├──tmp Temporary files ├──usr Everything for normal operation (usr = UNIX system resources) │ ├──bin User programs │ ├──sbin Administration programs │ ├──include Header files for c/c++ │ ├──lib Libraries │ ├──local Locally installed software │ └──doc Documentation └──home Contains the user's home directories ├──user Home directory for user └──user1 Home directory for user1 Note that there is a single root ``/``; all other disks (such as USB sticks) attach to some point in the tree (e.g. in ``/media``). Shell navigation ^^^^^^^^^^^^^^^^^ Shell is the UNIX command-line, interface for conversation with the machine. Don't be afraid. Moving around """"""""""""""" The shell is always operated by some ``user``, at some concrete ``machine``; these two are constant. We can move in the directory structure, and the current place where we are is *current directory*. By default, it is the *home directory* which contains all files belonging to the respective user:: user@machine:~\$ # user operating at machine, in the directory ~ (= user's home directory) user@machine:~\$ ls . # list contents of the current directory user@machine:~\$ ls foo # list contents of directory foo, relative to the dcurrent directory ~ (= ls ~/foo = ls /home/user/foo) user@machine:~\$ ls /tmp # list contents of /tmp user@machine:~\$ cd foo # change directory to foo user@machine:~/foo\$ ls ~ # list home directory (= ls /home/user) user@machine:~/foo\$ cd bar # change to bar (= cd ~/foo/bar) user@machine:~/foo/bar\$ cd ../../foo2 # go to the parent directory twice, then to foo2 (cd ~/foo/bar/../../foo2 = cd ~/foo2 = cd /home/user/foo2) user@machine:~/foo2\$ cd # go to the home directory (= ls ~ = ls /home/user) user@machine:~\$ Users typically have only permissions to write (i.e. modify files) only in their home directory (abbreviated ``~``, usually is ``/home/user``) and ``/tmp``, and permissions to read files in most other parts of the system:: user@machine:~\$ ls /root # see what files the administrator has ls: cannot open directory /root: Permission denied Keys """"" Useful keys on the command-line are: ============= ========================= show possible completions of what is being typed (use abundantly!) ^C (=Ctrl+C) delete current line ^D exit the shell ↑↓ move up and down in the command history ^C interrupt currently running program ^\\ kill currently running program Shift-PgUp scroll the screen up (show part output) Shift-PgDown scroll the screen down (show future output; works only on quantum computers) ============= ========================= .. Multiple files can be selected using *patterns*. user@machine:~/foo2\$ ls *.py # * replaces any characters (except /) params.py remote.py timing.py user@machine:~/foo2\$ ls ../foo2/*.py params.py remote.py timing.py Running programs """"""""""""""""" When a program is being run (without giving its full path), several directories are searched for program of that name; those directories are given by ``\$PATH``:: user@machine:~\$ echo \$PATH # show the value of $PATH /usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games user@machine:~\$ which ls # say what is the real path of ls The first part of the command-line is the program to be run (``which``), the remaining parts are *arguments* (``ls`` in this case). It is upt to the program which arguments it understands. Many programs can take special arguments called *options* starting with ``-`` (followed by a single letter) or ``--`` (followed by words); one of the common options is ``-h`` or ``--help``, which displays how to use the program (try ``ls --help``). Full documentation for each program usually exists as *manual page* (or *man page*), which can be shown using e.g. ``man ls`` (``q`` to exit) Starting yade ^^^^^^^^^^^^^^ If yade is installed on the machine, it can be (roughly speaking) run as any other program; without any arguments, it runs in the "dialog mode", where a command-line is presented: :: user@machine:~\$ yade Welcome to Yade bzr2616 TCP python prompt on localhost:9002, auth cookie `adcusk' XMLRPC info provider on http://localhost:21002 [[ ^L clears screen, ^U kills line. F12 controller, F11 3d view, F10 both, F9 generator, F8 plot. ]] Yade [1]: #### hit ^D to exit Do you really want to exit ([y]/n)? Yade: normal exit. The command-line is in fact ``python``, enriched with some yade-specific features. (Pure python interpreter can be run with ``python`` or ``ipython`` commands). Instead of typing commands on-by-one on the command line, they can be be written in a file (with the .py extension) and given as argument to Yade:: user@machine:~\$ yade simulation.py For a complete help, see ``man yade`` .. rubric:: Exercises #. Open the terminal, navigate to your home directory #. Create a new empty file and save it in ``~/first.py`` #. Change directory to ``/tmp``; delete the file ``~/first.py`` #. Run program ``xeyes`` #. Look at the help of Yade. #. Look at the *manual page* of Yade #. Run Yade, exit and run it again. Python basics -------------- We assume the reader is familar with `Python tutorial `__ and only briefly review some of the basic capabilities. The following will run in pure-python interpreter (``python`` or ``ipython``), but also inside Yade, which is a super-set of Python. Numerical operations and modules: .. ipython:: Yade [1]: (1+3*4)**2 # usual rules for operator precedence, ** is exponentiation Yade [2]: import math # gain access to "module" of functions Yade [3]: math.sqrt(2) # use a function from that module Yade [4]: import math as m # use the module under a different name Yade [5]: m.cos(m.pi) Yade [6]: from math import * # import everything so that it can be used without module name Yade [7]: cos(pi) Variables: .. ipython:: Yade [1]: a=1; b,c=2,3 # multiple commands separated with ;, multiple assignment Yade [2]: a+b+c Sequences ^^^^^^^^^ Lists """""" Lists are variable-length sequences, which can be modified; they are written with braces ``[...]``, and their elements are accessed with numerical indices: .. ipython:: Yade [3]: a=[1,2,3] # list of numbers Yade [4]: a[0] # first element has index 0 Yade [5]: a[-1] # negative counts from the end Yade [7]: a[3] # error Yade [5]: len(a) # number of elements Yade [6]: a[1:] # from second element to the end Yade [7]: a+=[4,5] # extend the list Yade [8]: a+=[6]; a.append(7) # extend with single value, both have the same effect Yade [9]: 9 in a # test presence of an element Lists can be created in various ways: .. ipython:: Yade [9]: range(10) Yade [1]: range(10)[-1] List of squares of even number smaller than 20, i.e. $\left\{a^2\;\forall a\in \{0,\cdots,19\} \;\middle|\; 2 \| a\right\}$ (note the similarity): .. ipython:: Yade [1]: [a**2 for a in range(20) if a%2==0] Tuples """"""" Tuples are constant sequences: .. ipython:: Yade [1]: b=(1,2,3) Yade [2]: b[0] Yade [3]: b[0]=4 # error Dictionaries """"""""""""" Mapping from keys to values: .. ipython:: Yade [1]: czde={'jedna':'ein','dva':'zwei','tri':'drei'} Yade [1]: de={1:'ein',2:'zwei',3:'drei'}; cz={1:'jedna',2:'dva',3:'tri'} Yade [1]: czde['jedna'] ## access values Yade [2]: de[1], cz[2] Functions, conditionals ^^^^^^^^^^^^^^^^^^^^^^^^ .. ipython:: Yade [1]: 4==5 Yade [2]: a=3.1 Yade [3]: if a to see defined attributes Particles ^^^^^^^^^ Particles are the "data" component of simulation; they are the objects that will undergo some processes, though do not define those processes yet. Singles """"""""" There is a number of pre-defined functions to create particles of certain type; in order to create a sphere, one has to (see the source of :yref:`yade.utils.sphere` for instance): #. Create :yref:`Body` #. Set :yref:`Body.shape` to be an instance of :yref:`Sphere` with some given radius #. Set :yref:`Body.material` (last-defined material is used, otherwise a default material is created) #. Set position and orientation in :yref:`Body.state`, compute mass and moment of inertia based on :yref:`Material` and :yref:`Shape` In order to avoid such tasks, shorthand functions are defined in the :yref:`yade.utils` module; to mention a few of them, they are :yref:`yade.utils.sphere`, :yref:`yade.utils.facet`, :yref:`yade.utils.wall`. .. ipython:: @suppress Yade [1]: from yade import utils Yade [1]: s=utils.sphere((0,0,0),radius=1) # create sphere particle centered at (0,0,0) with radius=1 Yade [1]: s.shape # s.shape describes the geometry of the particle Yade [1]: s.shape.radius # we already know the Sphere class Yade [1]: s.state.mass, s.state.inertia # inertia is computed from density and geometry Yade [1]: s.state.pos # position is the one we prescribed Yade [1]: s2=utils.sphere((-2,0,0),radius=1,fixed=True) # explanation below In the last example, the particle was fixed in space by the ``fixed=True`` parameter to :yref:`yade.utils.sphere`; such a particle will not move, creating a primitive boundary condition. A particle object is not yet part of the simulation; in order to do so, a special function is called: .. ipython:: Yade [1]: O.bodies.append(s) # adds particle s to the simulation; returns id of the particle(s) added Packs """"" There are functions to generate a specific arrangement of particles in the :yref:`yade.pack` module; for instance, cloud (random loose packing) of spheres can be generated with the :yref:`yade._packSpheres.SpherePack` class: .. ipython:: Yade [1]: from yade import pack Yade [1]: sp=pack.SpherePack() # create an empty cloud; SpherePack contains only geometrical information Yade [1]: sp.makeCloud((1,1,1),(2,2,2),rMean=.2) # put spheres with defined radius inside box given by corners (1,1,1) and (2,2,2) Yade [1]: for c,r in sp: print c,r # print center and radius of all particles (SpherePack is a sequence which can be iterated over) ...: Yade [1]: sp.toSimulation() # create particles and add them to the simulation Boundaries """""""""" :yref:`yade.utils.facet` (triangle :yref:`Facet`) and :yref:`yade.utils.wall` (infinite axes-aligned plane :yref:`Wall`) geometries are typically used to define boundaries. For instance, a "floor" for the simulation can be created like this: .. ipython:: Yade [1]: O.bodies.append(utils.wall(-1,axis=2)) There are other conveinence functions (like :yref:`yade.utils.facetBox` for creating closed or open rectangular box, or family of :yref:`yade.ymport` functions) Look inside ^^^^^^^^^^^^ The simulation can be inspected in several ways. All data can be accessed from python directly: .. ipython:: Yade [1]: len(O.bodies) Yade [1]: O.bodies[1].shape.radius # radius of body #1 (will give error if not sphere, since only spheres have radius defined) Yade [1]: O.bodies[2].state.pos # position of body #2 Besides that, Yade says this at startup (the line preceding the command-line):: [[ ^L clears screen, ^U kills line. F12 controller, F11 3d view, F10 both, F9 generator, F8 plot. ]] :guilabel:`Controller` Pressing ``F12`` brings up a window for controlling the simulation. Although typically no human intervention is done in large simulations (which run "headless", without any graphical interaction), it can be handy in small examples. There are basic information on the simulation (will be used later). :guilabel:`3d view` The 3d view can be opened with F11 (or by clicking on button in the *Controller* -- see below). There is a number of keyboard shortcuts to manipulate it (press ``h`` to get basic help), and it can be moved, rotated and zoomed using mouse. Display-related settings can be set in the "Display" tab of the controller (such as whether particles are drawn). :guilabel:`Inspector` *Inspector* is opened by clicking on the appropriate button in the *Controller*. It shows (and updated) internal data of the current simulation. In particular, one can have a look at engines, particles (*Bodies*) and interactions (*Interactions*). Clicking at each of the attribute names links to the appropriate section in the documentation. .. rubric:: Exercises #. What is this code going to do? .. ipython:: Yade [1]: O.bodies.append([utils.sphere((2*i,0,0),1) for i in range(1,20)]) #. Create a simple simulation with cloud of spheres enclosed in the box ``(0,0,0)`` and ``(1,1,1)`` with mean radius .1. (hint: :yref:`yade._packSpheres.SpherePack.makeCloud`) #. Enclose the cloud created above in box with corners ``(0,0,0)`` and ``(1,1,1)``; keep the top of the box open. (hint: :yref:`yade.utils.facetBox`; type ``utils.facetBox?`` or ``utils.facetBox??`` to get help on the command line) #. Open the 3D view, try zooming in/out; position axes so that $z$ is upwards, $y$ goes to the right and $x$ towards you. Engines ^^^^^^^ Engines define processes undertaken by particles. As we know from the theoretical introduction, the sequence of engines is called *simulation loop*. Let us define a simple interaction loop: .. ipython:: @suppress Yade [1]: O.reset() Yade [1]: O.engines=[ # newlines and indentations are not important until the brace is closed ...: ForceResetter(), ...: InsertionSortCollider([Bo1_Sphere_Aabb(),Bo1_Wall_Aabb()]), ...: InteractionLoop( # dtto for the parenthesis here ...: [Ig2_Sphere_Sphere_ScGeom(),Ig2_Wall_Sphere_ScGeom()], ...: [Ip2_FrictMat_FrictMat_FrictPhys()], ...: [Law2_ScGeom_FrictPhys_CundallStrack()] ...: ), ...: NewtonIntegrator(damping=.2,label='newton') # define a name under which we can access this engine easily ...: ] ...: Yade [1]: O.engines Yade [1]: O.engines[-1]==newton # is it the same object? Yade [1]: newton.damping Instead of typing everything into the command-line, one can describe simulation in a file (*script*) and then run yade with that file as an argument. We will therefore no longer show the command-line unless necessary; instead, only the script part will be shown. Like this:: O.engines=[ # newlines and indentations are not important until the brace is closed ForceResetter(), InsertionSortCollider([Bo1_Sphere_Aabb(),Bo1_Wall_Aabb()]), InteractionLoop( # dtto for the parenthesis here [Ig2_Sphere_Sphere_ScGeom_Inc(),Ig2_Wall_Sphere_ScGeom_Inc()], [Ip2_FrictMat_FrictMat_FrictPhys()], [Law2_ScGeom_FrictPhys_CundallStrack()] ), GravityEngine(gravity=(0,0,-9.81)), # 9.81 is the gravity acceleration, and we say that NewtonIntegrator(damping=.2,label='newton') # define a name under which we can access this engine easily ] Besides engines being run, it is likewise important to define how often they will run. Some engines can run only sometimes (we will see this later), while most of them will run always; the time between two successive runs of engines is *timestep* ($\Dt$). There is a mathematical limit on the timestep value, called *critical timestep*, which is computed from properties of particles. Since there is a function for that, we can just set timestep using :yref:`yade.utils.PWaveTimeStep`:: O.dt=utils.PWaveTimeStep() Each time when the simulation loop finishes, time ``O.time`` is advanced by the timestep ``O.dt``: .. ipython:: Yade [1]: O.dt=0.01 Yade [1]: O.time Yade [1]: O.step() Yade [1]: O.time For experimenting with a single simulations, it is handy to save it to memory; this can be achieved, once everything is defined, with:: O.saveTmp() .. rubric:: Exercises #. Define *engines* as in the above example, run the *Inspector* and click through the engines to see their sequence. #. Write a simple script which will #. define particles as in the previous exercise (cloud of spheres inside a box open from the top) #. define a simple simulation loop, as the one given above #. set $\Dt$ equal to the critical P-Wave $\Dt$ #. save the initial simulation state to memory #. Run the previously-defined simulation multiple times, while changing the value of timestep (use the :guilabel:`⟳` button to reload the initial configuration). #. See what happens as you increase $\Dt$ above the P-Wave value. #. Try changing the :yref:`gravity` parameter, before running the simulation. #. Try changing :yref:`damping` #. Reload the simulation, open the 3d view, open the *Inspector*, select a particle in the 3d view (shift-click). Then run the simulation and watch how forces on that particle change; pause the simulation somewhere in the middle, look at interactions of this particle. #. At which point can we say that the deposition is done, so that the simulation can be stopped? .. seealso:: The :ref:`bouncing-sphere` example shows a basic simulation. trunk-2018.02b/doc/sphinx/tutorial-introduction.rst000066400000000000000000000006301324306050200223570ustar00rootroot00000000000000 Introduction ============= Slides `Yade: past, present and future `__ (updated version) .. todo:: Put link to the `Yade: past, present and future `__, or adapt it to give more general intro to particle models. .. todo:: Think about what is the scope of the introduction. trunk-2018.02b/doc/sphinx/tutorial.rst000066400000000000000000000022471324306050200176460ustar00rootroot00000000000000 Tutorial ============= This tutorial originated as handout for a course held at `Technische Universität Dresden `_ / `Fakultät Bauingenieurwesen `_ / `Institut für Geotechnik `_ in Jaunary 2011. The focus was to give quick and rather practical introduction to people without prior modeling experience, but with knowledge of mechanics. Some computer literacy was assumed, though basics are reviewed in the :doc:`Hands-on section `. The course did not in reality follow this document, but was based on interactive writing and commenting simple :doc:`Examples `, which were mostly suggested by participants; many thanks to them for their ideas and suggestions. A few minor bugs were discovered during the course. They were all fixed in rev. 2640 of Yade which is therefore the minimum recommended version to run the examples (notably, 0.60 will not work). .. toctree:: :maxdepth: 2 tutorial-introduction.rst tutorial-hands-on.rst tutorial-data-mining.rst tutorial-geo.rst tutorial-advanced.rst tutorial-examples.rst trunk-2018.02b/doc/sphinx/tutorial/000077500000000000000000000000001324306050200171075ustar00rootroot00000000000000trunk-2018.02b/doc/sphinx/tutorial/01-bouncing-sphere.py000066400000000000000000000022571324306050200227750ustar00rootroot00000000000000# basic simulation showing sphere falling ball gravity, # bouncing against another sphere representing the support # DATA COMPONENTS # add 2 particles to the simulation # they the default material (utils.defaultMat) O.bodies.append([ # fixed: particle's position in space will not change (support) sphere(center=(0,0,0),radius=.5,fixed=True), # this particles is free, subject to dynamics sphere((0,0,2),.5) ]) # FUNCTIONAL COMPONENTS # simulation loop -- see presentation for the explanation O.engines=[ ForceResetter(), InsertionSortCollider([Bo1_Sphere_Aabb()]), InteractionLoop( [Ig2_Sphere_Sphere_ScGeom()], # collision geometry [Ip2_FrictMat_FrictMat_FrictPhys()], # collision "physics" [Law2_ScGeom_FrictPhys_CundallStrack()] # contact law -- apply forces ), # Apply gravity force to particles. damping: numerical dissipation of energy. NewtonIntegrator(gravity=(0,0,-9.81),damping=0.1) ] # set timestep to a fraction of the critical timestep # the fraction is very small, so that the simulation is not too fast # and the motion can be observed O.dt=.5e-4*PWaveTimeStep() # save the simulation, so that it can be reloaded later, for experimentation O.saveTmp() trunk-2018.02b/doc/sphinx/tutorial/02-gravity-deposition.py000066400000000000000000000046171324306050200235500ustar00rootroot00000000000000# gravity deposition in box, showing how to plot and save history of data, # and how to control the simulation while it is running by calling # python functions from within the simulation loop # import yade modules that we will use below from yade import pack, plot # create rectangular box from facets O.bodies.append(geom.facetBox((.5,.5,.5),(.5,.5,.5),wallMask=31)) # create empty sphere packing # sphere packing is not equivalent to particles in simulation, it contains only the pure geometry sp=pack.SpherePack() # generate randomly spheres with uniform radius distribution sp.makeCloud((0,0,0),(1,1,1),rMean=.05,rRelFuzz=.5) # add the sphere pack to the simulation sp.toSimulation() O.engines=[ ForceResetter(), InsertionSortCollider([Bo1_Sphere_Aabb(),Bo1_Facet_Aabb()]), InteractionLoop( # handle sphere+sphere and facet+sphere collisions [Ig2_Sphere_Sphere_ScGeom(),Ig2_Facet_Sphere_ScGeom()], [Ip2_FrictMat_FrictMat_FrictPhys()], [Law2_ScGeom_FrictPhys_CundallStrack()] ), NewtonIntegrator(gravity=(0,0,-9.81),damping=0.4), # call the checkUnbalanced function (defined below) every 2 seconds PyRunner(command='checkUnbalanced()',realPeriod=2), # call the addPlotData function every 200 steps PyRunner(command='addPlotData()',iterPeriod=100) ] O.dt=.5*PWaveTimeStep() # enable energy tracking; any simulation parts supporting it # can create and update arbitrary energy types, which can be # accessed as O.energy['energyName'] subsequently O.trackEnergy=True # if the unbalanced forces goes below .05, the packing # is considered stabilized, therefore we stop collected # data history and stop def checkUnbalanced(): if unbalancedForce()<.05: O.pause() plot.saveDataTxt('bbb.txt.bz2') # plot.saveGnuplot('bbb') is also possible # collect history of data which will be plotted def addPlotData(): # each item is given a names, by which it can be the unsed in plot.plots # the **O.energy converts dictionary-like O.energy to plot.addData arguments plot.addData(i=O.iter,unbalanced=unbalancedForce(),**O.energy) # define how to plot data: 'i' (step number) on the x-axis, unbalanced force # on the left y-axis, all energies on the right y-axis # (O.energy.keys is function which will be called to get all defined energies) # None separates left and right y-axis plot.plots={'i':('unbalanced',None,O.energy.keys)} # show the plot on the screen, and update while the simulation runs plot.plot() O.saveTmp() trunk-2018.02b/doc/sphinx/tutorial/03-oedometric-test.py000066400000000000000000000107211324306050200230110ustar00rootroot00000000000000# gravity deposition, continuing with oedometric test after stabilization # shows also how to run parametric studies with yade-batch # The components of the batch are: # 1. table with parameters, one set of parameters per line (ccc.table) # 2. readParamsFromTable which reads respective line from the parameter file # 3. the simulation muse be run using yade-batch, not yade # # $ yade-batch --job-threads=1 03-oedometric-test.table 03-oedometric-test.py # # load parameters from file if run in batch # default values are used if not run from batch readParamsFromTable(rMean=.05,rRelFuzz=.3,maxLoad=1e6,minLoad=1e4) # make rMean, rRelFuzz, maxLoad accessible directly as variables later from yade.params.table import * # create box with free top, and ceate loose packing inside the box from yade import pack, plot O.bodies.append(geom.facetBox((.5,.5,.5),(.5,.5,.5),wallMask=31)) sp=pack.SpherePack() sp.makeCloud((0,0,0),(1,1,1),rMean=rMean,rRelFuzz=rRelFuzz) sp.toSimulation() O.engines=[ ForceResetter(), # sphere, facet, wall InsertionSortCollider([Bo1_Sphere_Aabb(),Bo1_Facet_Aabb(),Bo1_Wall_Aabb()]), InteractionLoop( # the loading plate is a wall, we need to handle sphere+sphere, sphere+facet, sphere+wall [Ig2_Sphere_Sphere_ScGeom(),Ig2_Facet_Sphere_ScGeom(),Ig2_Wall_Sphere_ScGeom()], [Ip2_FrictMat_FrictMat_FrictPhys()], [Law2_ScGeom_FrictPhys_CundallStrack()] ), NewtonIntegrator(gravity=(0,0,-9.81),damping=0.5), # the label creates an automatic variable referring to this engine # we use it below to change its attributes from the functions called PyRunner(command='checkUnbalanced()',realPeriod=2,label='checker'), ] O.dt=.5*PWaveTimeStep() # the following checkUnbalanced, unloadPlate and stopUnloading functions are all called by the 'checker' # (the last engine) one after another; this sequence defines progression of different stages of the # simulation, as each of the functions, when the condition is satisfied, updates 'checker' to call # the next function when it is run from within the simulation next time # check whether the gravity deposition has already finished # if so, add wall on the top of the packing and start the oedometric test def checkUnbalanced(): # at the very start, unbalanced force can be low as there is only few contacts, but it does not mean the packing is stable if O.iter<5000: return # the rest will be run only if unbalanced is < .1 (stabilized packing) if unbalancedForce()>.1: return # add plate at the position on the top of the packing # the maximum finds the z-coordinate of the top of the topmost particle O.bodies.append(wall(max([b.state.pos[2]+b.shape.radius for b in O.bodies if isinstance(b.shape,Sphere)]),axis=2,sense=-1)) global plate # without this line, the plate variable would only exist inside this function plate=O.bodies[-1] # the last particles is the plate # Wall objects are "fixed" by default, i.e. not subject to forces # prescribing a velocity will therefore make it move at constant velocity (downwards) plate.state.vel=(0,0,-.1) # start plotting the data now, it was not interesting before O.engines=O.engines+[PyRunner(command='addPlotData()',iterPeriod=200)] # next time, do not call this function anymore, but the next one (unloadPlate) instead checker.command='unloadPlate()' def unloadPlate(): # if the force on plate exceeds maximum load, start unloading if abs(O.forces.f(plate.id)[2])>maxLoad: plate.state.vel*=-1 # next time, do not call this function anymore, but the next one (stopUnloading) instead checker.command='stopUnloading()' def stopUnloading(): if abs(O.forces.f(plate.id)[2]).3, exit; otherwise do nothing if abs(O.cell.trsf[0,2])>.5: # save data from addData(...) before exiting into file # use O.tags['id'] to distinguish individual runs of the same simulation plot.saveDataTxt(O.tags['id']+'.txt') # exit the program #import sys #sys.exit(0) # no error (0) O.pause() # called periodically to store data history def addData(): # get the stress tensor (as 3x3 matrix) stress=sum(normalShearStressTensors(),Matrix3.Zero) # give names to values we are interested in and save them plot.addData(exz=O.cell.trsf[0,2],szz=stress[2,2],sxz=stress[0,2],tanPhi=stress[0,2]/stress[2,2],i=O.iter) # color particles based on rotation amount for b in O.bodies: # rot() gives rotation vector between reference and current position b.shape.color=scalarOnColorScale(b.state.rot().norm(),0,pi/2.) # define what to plot (3 plots in total) ## exz(i), [left y axis, separate by None:] szz(i), sxz(i) ## szz(exz), sxz(exz) ## tanPhi(i) # note the space in 'i ' so that it does not overwrite the 'i' entry plot.plots={'i':('exz',None,'szz','sxz'),'exz':('szz','sxz'),'i ':('tanPhi',)} # better show rotation of particles Gl1_Sphere.stripes=True # open the plot on the screen plot.plot() O.saveTmp() trunk-2018.02b/doc/sphinx/tutorial/05-3d-postprocessing.py000066400000000000000000000033301324306050200232700ustar00rootroot00000000000000# demonstrate 3d postprocessing with yade # # 1. qt.SnapshotEngine saves images of the 3d view as it appears on the screen periodically # makeVideo is then used to make real movie from those images # 2. VTKRecorder saves data in files which can be opened with Paraview # see the User's manual for an intro to Paraview # generate loose packing from yade import pack, qt sp=pack.SpherePack() sp.makeCloud((0,0,0),(2,2,2),rMean=.1,rRelFuzz=.6,periodic=True) # add to scene, make it periodic sp.toSimulation() O.engines=[ ForceResetter(), InsertionSortCollider([Bo1_Sphere_Aabb()]), InteractionLoop( [Ig2_Sphere_Sphere_ScGeom()], [Ip2_FrictMat_FrictMat_FrictPhys()], [Law2_ScGeom_FrictPhys_CundallStrack()] ), NewtonIntegrator(damping=.4), # save data for Paraview VTKRecorder(fileName='3d-vtk-',recorders=['all'],iterPeriod=1000), # save data from Yade's own 3d view qt.SnapshotEngine(fileBase='3d-',iterPeriod=200,label='snapshot'), # this engine will be called after 20000 steps, only once PyRunner(command='finish()',iterPeriod=20000) ] O.dt=.5*PWaveTimeStep() # prescribe constant-strain deformation of the cell O.cell.velGrad=Matrix3(-.1,0,0, 0,-.1,0, 0,0,-.1) # we must open the view explicitly (limitation of the qt.SnapshotEngine) qt.View() # this function is called when the simulation is finished def finish(): # snapshot is label of qt.SnapshotEngine # the 'snapshots' attribute contains list of all saved files makeVideo(snapshot.snapshots,'3d.mpeg',fps=10,bps=10000) O.pause() # set parameters of the renderer, to show network chains rather than particles # these settings are accessible from the Controller window, on the second tab ("Display") as well rr=yade.qt.Renderer() rr.shape=False rr.intrPhys=True trunk-2018.02b/doc/sphinx/tutorial/06-periodic-triaxial-test.py000066400000000000000000000063751324306050200243050ustar00rootroot00000000000000# encoding: utf-8 # periodic triaxial test simulation # # The initial packing is either # # 1. random cloud with uniform distribution, or # 2. cloud with specified granulometry (radii and percentages), or # 3. cloud of clumps, i.e. rigid aggregates of several particles # # The triaxial consists of 2 stages: # # 1. isotropic compaction, until sigmaIso is reached in all directions; # this stage is ended by calling compactionFinished() # 2. constant-strain deformation along the z-axis, while maintaining # constant stress (sigmaIso) laterally; this stage is ended by calling # triaxFinished() # # Controlling of strain and stresses is performed via PeriTriaxController, # of which parameters determine type of control and also stability # condition (maxUnbalanced) so that the packing is considered stabilized # and the stage is done. # sigmaIso=-1e5 #import matplotlib #matplotlib.use('Agg') # generate loose packing from yade import pack, qt, plot O.periodic=True sp=pack.SpherePack() if 0: ## uniform distribution sp.makeCloud((0,0,0),(2,2,2),rMean=.1,rRelFuzz=.3,periodic=True) else: ## create packing from clumps # configuration of one clump c1=pack.SpherePack([((0,0,0),.03333),((.03,0,0),.017),((0,.03,0),.017)]) # make cloud using the configuration c1 (there could c2, c3, ...; selection between them would be random) sp.makeClumpCloud((0,0,0),(2,2,2),[c1],periodic=True,num=500) # setup periodic boundary, insert the packing sp.toSimulation() O.engines=[ ForceResetter(), InsertionSortCollider([Bo1_Sphere_Aabb()]), InteractionLoop( [Ig2_Sphere_Sphere_ScGeom()], [Ip2_FrictMat_FrictMat_FrictPhys()], [Law2_ScGeom_FrictPhys_CundallStrack()] ), PeriTriaxController(label='triax', # specify target values and whether they are strains or stresses goal=(sigmaIso,sigmaIso,sigmaIso),stressMask=7, # type of servo-control dynCell=True,maxStrainRate=(10,10,10), # wait until the unbalanced force goes below this value maxUnbalanced=.1,relStressTol=1e-3, # call this function when goal is reached and the packing is stable doneHook='compactionFinished()' ), NewtonIntegrator(damping=.2), PyRunner(command='addPlotData()',iterPeriod=100), ] O.dt=.5*PWaveTimeStep() def addPlotData(): plot.addData(unbalanced=unbalancedForce(),i=O.iter, sxx=triax.stress[0],syy=triax.stress[1],szz=triax.stress[2], exx=triax.strain[0],eyy=triax.strain[1],ezz=triax.strain[2], # save all available energy data Etot=O.energy.total(),**O.energy ) # enable energy tracking in the code O.trackEnergy=True # define what to plot plot.plots={'i':('unbalanced',),'i ':('sxx','syy','szz'),' i':('exx','eyy','ezz'), # energy plot ' i ':(O.energy.keys,None,'Etot'), } # show the plot plot.plot() def compactionFinished(): # set the current cell configuration to be the reference one O.cell.trsf=Matrix3.Identity # change control type: keep constant confinement in x,y, 20% compression in z triax.goal=(sigmaIso,sigmaIso,-.2) triax.stressMask=3 # allow faster deformation along x,y to better maintain stresses triax.maxStrainRate=(1.,1.,.1) # next time, call triaxFinished instead of compactionFinished triax.doneHook='triaxFinished()' # do not wait for stabilization before calling triaxFinished triax.maxUnbalanced=10 def triaxFinished(): print 'Finished' O.pause() trunk-2018.02b/doc/sphinx/tutorial/make-simulation-video.py000066400000000000000000000024511324306050200236660ustar00rootroot00000000000000# encoding: utf-8 # use another simulation script, create 3d output and graphs # # the simulation must not run by itself (no O.run() at the end of the script) # when run, must stop by itself (without exiting) # # plotting should be set up as usual import matplotlib matplotlib.use('Agg') # headless backend, makes rendering plots after the simulation _much_ faster (and more reliable) from yade import qt,plot simulations=[ ('01-bouncing-sphere.py',u'Bouncing sphere',100000), ('02-gravity-deposition.py',u'Gravity deposition',100), ('03-oedometric-test.py',u'Oedometric test',500), ('04-periodic-simple-shear.py',u'Simple shear\nwith\nperiodic boundary',100), ('06-periodic-triaxial-test.py',u'Periodic triaxal test\nwith clumps',200) ] #for script,title in simulations: def do(n): global script,title script,title,freq=simulations[n] O.reset(); plot.reset() execfile(script,globals()) # add output engines O.engines=O.engines+[qt.SnapshotEngine(fileBase=O.tmpFilename(),iterPeriod=freq,plot='snapshot')] # add the snapshot subplot plot.plots.update({'snapshot':None}) # open the view so that it can be set up qt.View() def done(): utils.makeVideo(plot.savePlotSequence(O.tmpFilename(),title=title+u'\n\n\nVáclav Šmilauer\neu@doxos.eu\nhttp://www.yade-dem.org'),script+'.mpeg',fps=10,kbps=5000) trunk-2018.02b/doc/sphinx/tutorial/peri-triax.py000066400000000000000000000022311324306050200215430ustar00rootroot00000000000000from yade import pack,plot sp=pack.SpherePack() rMean=.05 sp.makeCloud((0,0,0),(1,1,1),rMean=rMean,periodic=True) sp.toSimulation() O.engines=[ ForceResetter(), InsertionSortCollider([Bo1_Sphere_Aabb()],verletDist=.05*rMean), InteractionLoop([Ig2_Sphere_Sphere_ScGeom()],[Ip2_FrictMat_FrictMat_FrictPhys()],[Law2_ScGeom_FrictPhys_CundallStrack()]), NewtonIntegrator(damping=.6), PeriTriaxController(goal=(-1e6,-1e6,-.1),stressMask=0b011,maxUnbalanced=.2,doneHook='goalReached()',label='triax',maxStrainRate=(.1,.1,.1),dynCell=True), PyRunner(iterPeriod=100,command='addPlotData()') ] O.dt=.5*utils.PWaveTimeStep() O.trackEnergy=True def goalReached(): print 'Goal reached, strain',triax.strain,' stress',triax.stress O.pause() def addPlotData(): plot.addData(sx=triax.stress[0],sy=triax.stress[1],sz=triax.stress[2],ex=triax.strain[0],ey=triax.strain[1],ez=triax.strain[2], i=O.iter,unbalanced=utils.unbalancedForce(), totalEnergy=O.energy.total(),**O.energy # plot all energies ) plot.plots={'i':(('unbalanced','go'),None,'kinetic'),' i':('ex','ey','ez',None,'sx','sy','sz'),'i ':(O.energy.keys,None,('totalEnergy','bo'))} plot.plot() O.saveTmp() O.run() trunk-2018.02b/doc/sphinx/upload000077500000000000000000000001011324306050200164460ustar00rootroot00000000000000#!/bin/sh rsync -rv _build/html/ ggamma:www/NoBackup/yade-sphinx trunk-2018.02b/doc/sphinx/user.rst000066400000000000000000002650131324306050200167630ustar00rootroot00000000000000################### User's manual ################### .. The imports below are done at startup normaly, but it seems ineffective in the context of sphinx build, let us import silently .. ipython:: @suppress Yade [0]: from yade.utils import * @suppress Yade [0]: from math import * ******************* Scene construction ******************* Adding particles ================ The :yref:`BodyContainer` holds :yref:`Body` objects in the simulation; it is accessible as ``O.bodies``. Creating Body objects ---------------------- :yref:`Body` objects are only rarely constructed by hand by their components (:yref:`Shape`, :yref:`Bound`, :yref:`State`, :yref:`Material`); instead, convenience functions :yref:`sphere`, :yref:`facet` and :yref:`wall` are used to create them. Using these functions also ensures better future compatibility, if internals of :yref:`Body` change in some way. These functions receive geometry of the particle and several other characteristics. See their documentation for details. If the same :yref:`Material` is used for several (or many) bodies, it can be shared by adding it in ``O.materials``, as explained below. Defining materials ------------------ The ``O.materials`` object (instance of :yref:`Omega.materials`) holds defined shared materials for bodies. It only supports addition, and will typically hold only a few instance (though there is no limit). ``label`` given to each material is optional, but can be passed to :yref:`sphere` and other functions for constructing body. The value returned by ``O.materials.append`` is an ``id`` of the material, which can be also passed to :yref:`sphere` -- it is a little bit faster than using label, though not noticeable for small number of particles and perhaps less convenient. If no :yref:`Material` is specified when calling :yref:`sphere`, the *last* defined material is used; that is a convenient default. If no material is defined yet (hence there is no last material), a default material will be created: FrictMat(density=2e3,young=30e9,poisson=.3,frictionAngle=.5236). This should not happen for serious simulations, but is handy in simple scripts, where exact material properties are more or less irrelevant. .. ipython:: @suppress Yade [0]: O.reset() Yade [1]: len(O.materials) Yade [2]: idConcrete=O.materials.append(FrictMat(young=30e9,poisson=.2,frictionAngle=.6,label="concrete")) Yade [3]: O.materials[idConcrete] # uses the last defined material Yade [3]: O.bodies.append(sphere(center=(0,0,0),radius=1)) # material given by id Yade [4]: O.bodies.append(sphere((0,0,2),1,material=idConcrete)) # material given by label Yade [5]: O.bodies.append(sphere((0,2,0),1,material="concrete")) Yade [3]: idSteel=O.materials.append(FrictMat(young=210e9,poisson=.25,frictionAngle=.8,label="steel")) Yade [7]: len(O.materials) # implicitly uses "steel" material, as it is the last one now Yade [6]: O.bodies.append(facet([(1,0,0),(0,1,0),(-1,-1,0)])) Adding multiple particles ------------------------- As shown above, bodies are added one by one or several at the same time using the ``append`` method: .. ipython:: @suppress Yade [0]: O.reset() Yade [1]: O.bodies.append(sphere((0,10,0),1)) Yade [2]: O.bodies.append(sphere((0,0,2),1)) # this is the same, but in one function call Yade [3]: O.bodies.append([ ...: sphere((0,0,0),1), ...: sphere((0,0,2),1) ...: ]) Many functions introduced in next sections return list of bodies which can be readily added to the simulation, including * packing generators, such as :yref:`yade.pack.randomDensePack`, :yref:`yade.pack.regularHexa` * surface function :yref:`yade.pack.gtsSurface2Facets` * import functions :yref:`yade.ymport.gmsh`, :yref:`yade.ymport.stl`, … As those functions use :yref:`sphere` and :yref:`facet` internally, they accept additional argument passed to those function. In particular, material for each body is selected following the rules above (last one if not specified, by label, by index, etc.). Clumping particles together ---------------------------- In some cases, you might want to create rigid aggregate of individual particles (i.e. particles will retain their mutual position during simulation). This we call a :yref:`clump`. A clump is internally represented by a special :yref:`body`, referenced by :yref:`clumpId` of its members (see also :yref:`isClump`, :yref:`isClumpMember` and :yref:`isStandalone`). Like every body a clump has a :yref:`position`, which is the (mass) balance point between all members. A clump body itself has no :yref:`interactions` with other bodies. Interactions between clumps is represented by interactions between clump members. There are no interactions between clump members of the same clump. YADE supports different ways of creating clumps: * Create clumps and spheres (clump members) directly with one command: The function :yref:`appendClumped()` is designed for this task. For instance, we might add 2 spheres tied together: .. ipython:: @suppress Yade [0]: O.reset() Yade [1]: O.bodies.appendClumped([ ...: sphere([0,0,0],1), ...: sphere([0,0,2],1) ...: ]) Yade [2]: len(O.bodies) Yade [3]: O.bodies[1].isClumpMember, O.bodies[2].clumpId Yade [2]: O.bodies[2].isClump, O.bodies[2].clumpId -> :yref:`appendClumped()` returns a tuple of ids ``(clumpId,[memberId1,memberId2,...])`` * Use existing spheres and clump them together: For this case the function :yref:`clump()` can be used. One way to do this is to create a list of bodies, that should be clumped before using the :yref:`clump()` command: .. ipython:: @suppress Yade [0]: O.reset() Yade [1]: bodyList = [] Yade [2]: for ii in range(0,5): ...: bodyList.append(O.bodies.append(sphere([ii,0,1],.5)))#create a "chain" of 5 spheres ...: Yade [3]: print bodyList Yade [4]: idClump=O.bodies.clump(bodyList) -> :yref:`clump()` returns ``clumpId`` * Another option is to replace :yref:`standalone` spheres from a given packing (see :yref:`SpherePack` and :yref:`makeCloud`) by clumps using clump templates. This is done by a function called :yref:`replaceByClumps()`. This function takes a list of :yref:`clumpTemplates()` and a list of amounts and replaces spheres by clumps. The volume of a new clump will be the same as the volume of the sphere, that was replaced (clump volume/mass/inertia is accounting for overlaps assuming that there are only pair overlaps). -> :yref:`replaceByClumps()` returns a list of tuples: ``[(clumpId1,[memberId1,memberId2,...]),(clumpId2,[memberId1,memberId2,...]),...]`` It is also possible to :yref:`add` bodies to a clump and :yref:`release` bodies from a clump. Also you can :yref:`erase` the clump (clump members will get standalone spheres). Additionally YADE supports to achieve the :yref:`roundness` of a clump or roundness coefficient of a packing. Parts of the packing can be excluded from roundness measurement via exclude list. .. ipython:: @suppress Yade [0]: O.reset() Yade [1]: bodyList = [] Yade [2]: for ii in range(1,5): ...: bodyList.append(O.bodies.append(sphere([ii,ii,ii],.5))) ...: Yade [4]: O.bodies.clump(bodyList) Yade [5]: RC=O.bodies.getRoundness() Yade [3]: print RC -> :yref:`getRoundness()` returns roundness coefficient RC of a packing or a part of the packing .. note:: Have a look at :ysrc:`examples/clumps/` folder. There you will find some examples, that show usage of different functions for clumps. Sphere packings =============== Representing a solid of an arbitrary shape by arrangement of spheres presents the problem of sphere packing, i.e. spatial arrangement of sphere such that given solid is approximately filled with them. For the purposes of DEM simulation, there can be several requirements. #. Distribution of spheres' radii. Arbitrary volume can be filled completely with spheres provided there are no restrictions on their radius; in such case, number of spheres can be infinite and their radii approach zero. Since both number of particles and minimum sphere radius (via critical timestep) determine computation cost, radius distribution has to be given mandatorily. The most typical distribution is uniform: mean±dispersion; if dispersion is zero, all spheres will have the same radius. #. Smooth boundary. Some algorithms treat boundaries in such way that spheres are aligned on them, making them smoother as surface. #. Packing density, or the ratio of spheres volume and solid size. It is closely related to radius distribution. #. Coordination number, (average) number of contacts per sphere. #. Isotropy (related to regularity/irregularity); packings with preferred directions are usually not desirable, unless the modeled solid also has such preference. #. Permissible Spheres' overlap; some algorithms might create packing where spheres slightly overlap; since overlap usually causes forces in DEM, overlap-free packings are sometimes called “stress-free‟. Volume representation ---------------------- There are 2 methods for representing exact volume of the solid in question in Yade: boundary representation and constructive solid geometry. Despite their fundamental differences, they are abstracted in Yade in the :yref:`Predicate` class. Predicate provides the following functionality: #. defines axis-aligned bounding box for the associated solid (optionally defines oriented bounding box); #. can decide whether given point is inside or outside the solid; most predicates can also (exactly or approximately) tell whether the point is inside *and* satisfies some given padding distance from the represented solid boundary (so that sphere of that volume doesn't stick out of the solid). Constructive Solid Geometry (CSG) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ CSG approach describes volume by geometric *primitives* or primitive solids (sphere, cylinder, box, cone, …) and boolean operations on them. Primitives defined in Yade include :yref:`inCylinder`, :yref:`inSphere`, :yref:`inEllipsoid`, :yref:`inHyperboloid`, :yref:`notInNotch`. For instance, :yref:`hyperboloid` (dogbone) specimen for tension-compression test can be constructed in this way (shown at img. img-hyperboloid_):: from yade import pack ## construct the predicate first pred=pack.inHyperboloid(centerBottom=(0,0,-.1),centerTop=(0,0,.1),radius=.05,skirt=.03) ## alternatively: pack.inHyperboloid((0,0,-.1),(0,0,.1),.05,.03) ## pack the predicate with spheres (will be explained later) spheres=pack.randomDensePack(pred,spheresInCell=2000,radius=3.5e-3) ## add spheres to simulation O.bodies.append(spheres) .. _img-hyperboloid: .. figure:: fig/hyperboloid.png :width: 7cm Specimen constructed with the :yref:`yade._packPredicates.inHyperboloid` predicate, packed with :yref:`yade.pack.randomDensePack`. Boundary representation (BREP) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Representing a solid by its boundary is much more flexible than CSG volumes, but is mostly only approximate. Yade interfaces to `GNU Triangulated Surface Library `_ (GTS) to import surfaces readable by GTS, but also to construct them explicitly from within simulation scripts. This makes possible parametric construction of rather complicated shapes; there are functions to create set of 3d polylines from 2d polyline (:yref:`yade.pack.revolutionSurfaceMeridians`), to triangulate surface between such set of 3d polylines (:yref:`yade.pack.sweptPolylines2gtsSurface`). For example, we can construct a simple funnel (:ysrc:`examples/funnel.py`, shown at img-funnel_):: from numpy import linspace from yade import pack # angles for points on circles thetas=linspace(0,2*pi,num=16,endpoint=True) # creates list of polylines in 3d from list of 2d projections # turned from 0 to π meridians=pack.revolutionSurfaceMeridians( [[(3+rad*sin(th),10*rad+rad*cos(th)) for th in thetas] for rad in linspace(1,2,num=10)], linspace(0,pi,num=10) ) # create surface surf=pack.sweptPolylines2gtsSurface( meridians+ +[[Vector3(5*sin(-th),-10+5*cos(-th),30) for th in thetas]] # add funnel top ) # add to simulation O.bodies.append(pack.gtsSurface2Facets(surf)) .. _img-funnel: .. figure:: fig/funnel.* :height: 6cm Triangulated funnel, constructed with the :ysrc:`examples/funnel.py` script. GTS surface objects can be used for 2 things: #. :yref:`yade.pack.gtsSurface2Facets` function can create the triangulated surface (from :yref:`Facet` particles) in the simulation itself, as shown in the funnel example. (Triangulated surface can also be imported directly from a STL file using :yref:`yade.ymport.stl`.) #. :yref:`yade._packPredicates.inGtsSurface` predicate can be created, using the surface as boundary representation of the enclosed volume. The :ysrc:`examples/gts-horse/gts-horse.py` (img. img-horse_) shows both possibilities; first, a GTS surface is imported:: import gts surf=gts.read(open('horse.coarse.gts')) That surface object is used as predicate for packing:: pred=pack.inGtsSurface(surf) O.bodies.append(pack.regularHexa(pred,radius=radius,gap=radius/4.)) and then, after being translated, as base for triangulated surface in the simulation itself:: surf.translate(0,0,-(aabb[1][2]-aabb[0][2])) O.bodies.append(pack.gtsSurface2Facets(surf,wire=True)) .. _img-horse: .. figure:: fig/horse.png :width: 8cm Imported GTS surface (horse) used as packing predicate (top) and surface constructed from :yref:`facets` (bottom). See http://www.youtube.com/watch?v=PZVruIlUX1A for movie of this simulation. Boolean operations on predicates ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Boolean operations on pair of predicates (noted ``A`` and ``B``) are defined: * :yref:`intersection` ``A & B`` (conjunction): point must be in both predicates involved. * :yref:`union` ``A | B`` (disjunction): point must be in the first or in the second predicate. * :yref:`difference` ``A - B`` (conjunction with second predicate negated): the point must be in the first predicate and not in the second one. * :yref:`symmetric difference` ``A ^ B`` (exclusive disjunction): point must be in exactly one of the two predicates. Composed predicates also properly define their bounding box. For example, we can take box and remove cylinder from inside, using the ``A - B`` operation (img. img-predicate-difference_):: pred=pack.inAlignedBox((-2,-2,-2),(2,2,2))-pack.inCylinder((0,-2,0),(0,2,0),1) spheres=pack.randomDensePack(pred,spheresInCell=2000,radius=.1,rRelFuzz=.4) .. _img-predicate-difference: .. figure:: fig/predicate-difference.png :width: 8cm Box with cylinder removed from inside, using difference of these two predicates. Packing algorithms ------------------- Algorithms presented below operate on geometric spheres, defined by their center and radius. With a few exception documented below, the procedure is as follows: #. Sphere positions and radii are computed (some functions use volume predicate for this, some do not) #. :yref:`sphere` is called for each position and radius computed; it receives extra `keyword arguments `_ of the packing function (i.e. arguments that the packing function doesn't specify in its definition; they are noted ``**kw``). Each :yref:`sphere` call creates actual :yref:`Body` objects with :yref:`Sphere` :yref:`shape`. List of :yref:`Body` objects is returned. #. List returned from the packing function can be added to simulation using ``O.bodies.append``. Taking the example of pierced box:: pred=pack.inAlignedBox((-2,-2,-2),(2,2,2))-pack.inCylinder((0,-2,0),(0,2,0),1) spheres=pack.randomDensePack(pred,spheresInCell=2000,radius=.1,rRelFuzz=.4,wire=True,color=(0,0,1),material=1) Keyword arguments ``wire``, ``color`` and ``material`` are not declared in :yref:`yade.pack.randomDensePack`, therefore will be passed to :yref:`sphere`, where they are also documented. ``spheres`` is now list of :yref:`Body` objects, which we add to the simulation:: O.bodies.append(spheres) Packing algorithms described below produce dense packings. If one needs loose packing, :yref:`yade._packSpheres.SpherePack` class provides functions for generating loose packing, via its :yref:`yade._packSpheres.SpherePack.makeCloud` method. It is used internally for generating initial configuration in dynamic algorithms. For instance:: from yade import pack sp=pack.SpherePack() sp.makeCloud(minCorner=(0,0,0),maxCorner=(3,3,3),rMean=.2,rRelFuzz=.5) will fill given box with spheres, until no more spheres can be placed. The object can be used to add spheres to simulation:: for c,r in sp: O.bodies.append(sphere(c,r)) or, in a more pythonic way, with one single ``O.bodies.append`` call:: O.bodies.append([sphere(c,r) for c,r in sp]) Geometric ^^^^^^^^^ Geometric algorithms compute packing without performing dynamic simulation; among their advantages are * speed; * spheres touch exactly, there are no overlaps (what some people call "stress-free" packing); their chief disadvantage is that radius distribution cannot be prescribed exactly, save in specific cases (regular packings); sphere radii are given by the algorithm, which already makes the system determined. If exact radius distribution is important for your problem, consider dynamic algorithms instead. Regular """"""""" Yade defines packing generators for spheres with constant radii, which can be used with volume predicates as described above. They are dense orthogonal packing (:yref:`yade.pack.regularOrtho`) and dense hexagonal packing (:yref:`yade.pack.regularHexa`). The latter creates so-called "hexagonal close packing", which achieves maximum density (http://en.wikipedia.org/wiki/Close-packing_of_spheres). Clear disadvantage of regular packings is that they have very strong directional preferences, which might not be an issue in some cases. Irregular """""""""" Random geometric algorithms do not integrate at all with volume predicates described above; rather, they take their own boundary/volume definition, which is used during sphere positioning. On the other hand, this makes it possible for them to respect boundary in the sense of making spheres touch it at appropriate places, rather than leaving empty space in-between. GenGeo is library (python module) for packing generation developed with `ESyS-Particle `_. It creates packing by random insertion of spheres with given radius range. Inserted spheres touch each other exactly and, more importantly, they also touch the boundary, if in its neighbourhood. Boundary is represented as special object of the GenGeo library (Sphere, cylinder, box, convex polyhedron, …). Therefore, GenGeo cannot be used with volume represented by yade predicates as explained above. Packings generated by this module can be imported directly via :yref:`yade.ymport.gengeo`, or from saved file via :yref:`yade.ymport.gengeoFile`. There is an example script :ysrc:`examples/test/genCylLSM.py`. Full documentation for GenGeo can be found at `ESyS documentation website `_. To our knowledge, the GenGeo library is not currently packaged. It can be downloaded from current subversion repository :: svn checkout https://svn.esscc.uq.edu.au/svn/esys3/lsm/contrib/LSMGenGeo then following instruction in the ``INSTALL`` file. Dynamic ^^^^^^^ The most versatile algorithm for random dense packing is provided by :yref:`yade.pack.randomDensePack`. Initial loose packing of non-overlapping spheres is generated by randomly placing them in cuboid volume, with radii given by requested (currently only uniform) radius distribution. When no more spheres can be inserted, the packing is compressed and then uncompressed (see :ysrc:`py/pack/pack.py` for exact values of these "stresses") by running a DEM simulation; :yref:`Omega.switchScene` is used to not affect existing simulation). Finally, resulting packing is clipped using provided predicate, as explained above. By its nature, this method might take relatively long; and there are 2 provisions to make the computation time shorter: * If number of spheres using the ``spheresInCell`` parameter is specified, only smaller specimen with *periodic* boundary is created and then repeated as to fill the predicate. This can provide high-quality packing with low regularity, depending on the ``spheresInCell`` parameter (value of several thousands is recommended). * Providing ``memoizeDb`` parameter will make :yref:`yade.pack.randomDensePack` first look into provided file (SQLite database) for packings with similar parameters. On success, the packing is simply read from database and returned. If there is no similar pre-existent packing, normal procedure is run, and the result is saved in the database before being returned, so that subsequent calls with same parameters will return quickly. If you need to obtain full periodic packing (rather than packing clipped by predicate), you can use :yref:`yade.pack.randomPeriPack`. In case of specific needs, you can create packing yourself, "by hand". For instance, packing boundary can be constructed from :yref:`facets`, letting randomly positioned spheres in space fall down under gravity. Triangulated surfaces ===================== Yade integrates with the the `GNU Triangulated Surface library `_, exposed in python via GTS module. GTS provides variety of functions for surface manipulation (coarsening, tesselation, simplification, import), to be found in its documentation. GTS surfaces are geometrical objects, which can be inserted into simulation as set of particles whose :yref:`Body.shape` is of type :yref:`Facet` -- single triangulation elements. :yref:`yade.pack.gtsSurface2Facets` can be used to convert GTS surface triangulation into list of :yref:`bodies` ready to be inserted into simulation via ``O.bodies.append``. Facet particles are created by default as non-:yref:`Body.dynamic` (they have zero inertial mass). That means that they are fixed in space and will not move if subject to forces. You can however * prescribe arbitrary movement to facets using a :yref:`PartialEngine` (such as :yref:`TranslationEngine` or :yref:`RotationEngine`); * assign explicitly :yref:`mass` and :yref:`inertia` to that particle; * make that particle part of a clump and assign :yref:`mass` and :yref:`inertia` of the clump itself (described below). .. note:: Facets can only (currently) interact with :yref:`spheres`, not with other facets, even if they are *dynamic*. Collision of 2 :yref:`facets` will not create interaction, therefore no forces on facets. Import ------- Yade currently offers 3 formats for importing triangulated surfaces from external files, in the :yref:`yade.ymport` module: :yref:`yade.ymport.gts` text file in native GTS format. :yref:`yade.ymport.stl` STereoLitography format, in either text or binary form; exported from `Blender `_, but from many CAD systems as well. :yref:`yade.ymport.gmsh`. text file in native format for `GMSH `_, popular open-source meshing program. If you need to manipulate surfaces before creating list of facets, you can study the :ysrc:`py/ymport.py` file where the import functions are defined. They are rather simple in most cases. Parametric construction ------------------------ The GTS module provides convenient way of creating surface by vertices, edges and triangles. Frequently, though, the surface can be conveniently described as surface between polylines in space. For instance, cylinder is surface between two polygons (closed polylines). The :yref:`yade.pack.sweptPolylines2gtsSurface` offers the functionality of connecting several polylines with triangulation. .. note:: The implementation of :yref:`yade.pack.sweptPolylines2gtsSurface` is rather simplistic: all polylines must be of the same length, and they are connected with triangles between points following their indices within each polyline (not by distance). On the other hand, points can be co-incident, if the ``threshold`` parameter is positive: degenerate triangles with vertices closer that ``threshold`` are automatically eliminated. Manipulating lists efficiently (in terms of code length) requires being familiar with `list comprehensions `_ in python. .. FIXME some example here Another examples can be found in :ysrc:`examples/mill.py` (fully parametrized) or :ysrc:`examples/funnel.py` (with hardcoded numbers). .. _creating-interactions: Creating interactions ====================== In typical cases, interactions are created during simulations as particles collide. This is done by a :yref:`Collider` detecting approximate contact between particles and then an :yref:`IGeomFunctor` detecting exact collision. Some material models (such as the :yref:`concrete model`) rely on initial interaction network which is denser than geometrical contact of spheres: sphere's radii as "enlarged" by a dimensionless factor called *interaction radius* (or *interaction ratio*) to create this initial network. This is done typically in this way (see :ysrc:`examples/concrete/uniax.py` for an example): #. Approximate collision detection is adjusted so that approximate contacts are detected also between particles within the interaction radius. This consists in setting value of :yref:`Bo1_Sphere_Aabb.aabbEnlargeFactor` to the interaction radius value. #. The geometry functor (``Ig2``) would normally say that "there is no contact" if given 2 spheres that are not in contact. Therefore, the same value as for :yref:`Bo1_Sphere_Aabb.aabbEnlargeFactor` must be given to it (:yref:`Ig2_Sphere_Sphere_ScGeom.interactionDetectionFactor` ). Note that only :yref:`Sphere` + :yref:`Sphere` interactions are supported; there is no parameter analogous to :yref:`distFactor` in :yref:`Ig2_Facet_Sphere_ScGeom`. This is on purpose, since the interaction radius is meaningful in bulk material represented by sphere packing, whereas facets usually represent boundary conditions which should be exempt from this dense interaction network. #. Run one single step of the simulation so that the initial network is created. #. Reset interaction radius in both ``Bo1`` and ``Ig2`` functors to their default value again. #. Continue the simulation; interactions that are already established will not be deleted (the ``Law2`` functor in usepermitting). In code, such scenario might look similar to this one (labeling is explained in :ref:`labelingthings`):: intRadius=1.5 O.engines=[ ForceResetter(), InsertionSortCollider([ # enlarge here Bo1_Sphere_Aabb(aabbEnlargeFactor=intRadius,label='bo1s'), Bo1_Facet_Aabb(), ]), InteractionLoop( [ # enlarge here Ig2_Sphere_Sphere_ScGeom(interactionDetectionFactor=intRadius,label='ig2ss'), Ig2_Facet_Sphere_ScGeom(), ], [Ip2_CpmMat_CpmMat_CpmPhys()], [Law2_ScGeom_CpmPhys_Cpm(epsSoft=0)], # deactivated ), NewtonIntegrator(damping=damping,label='damper'), ] # run one single step O.step() # reset interaction radius to the default value bo1s.aabbEnlargeFactor=1.0 ig2ss.interactionDetectionFactor=1.0 # now continue simulation O.run() Individual interactions on demand ---------------------------------- It is possible to create an interaction between a pair of particles independently of collision detection using :yref:`createInteraction`. This function looks for and uses matching ``Ig2`` and ``Ip2`` functors. Interaction will be created regardless of distance between given particles (by passing a special parameter to the ``Ig2`` functor to force creation of the interaction even without any geometrical contact). Appropriate constitutive law should be used to avoid deletion of the interaction at the next simulation step. .. ipython:: @suppress Yade [1]: O.reset() Yade [1]: O.materials.append(FrictMat(young=3e10,poisson=.2,density=1000)) Yade [1]: O.bodies.append([ ...: sphere([0,0,0],1), ...: sphere([0,0,1000],1) ...: ]) # only add InteractionLoop, no other engines are needed now Yade [1]: O.engines=[ ...: InteractionLoop( ...: [Ig2_Sphere_Sphere_ScGeom(),], ...: [Ip2_FrictMat_FrictMat_FrictPhys()], ...: [] # not needed now ...: ) ...: ] Yade [1]: i=createInteraction(0,1) # created by functors in InteractionLoop Yade [2]: i.geom, i.phys This method will be rather slow if many interaction are to be created (the functor lookup will be repeated for each of them). In such case, ask on yade-dev@lists.launchpad.net to have the :yref:`createInteraction` function accept list of pairs id's as well. Base engines ============= A typical DEM simulation in Yade does at least the following at each step (see :ref:`function-components` for details): #. Reset forces from previous step #. Detect new collisions #. Handle interactions #. Apply forces and update positions of particles Each of these points corresponds to one or several engines:: O.engines=[ ForceResetter(), # reset forces InsertionSortCollider([...]), # approximate collision detection InteractionLoop([...],[...],[...]) # handle interactions NewtonIntegrator() # apply forces and update positions ] The order of engines is important. In majority of cases, you will put any additional engine after :yref:`InteractionLoop`: * if it apply force, it should come before :yref:`NewtonIntegrator`, otherwise the force will never be effective. * if it makes use of bodies' positions, it should also come before :yref:`NewtonIntegrator`, otherwise, positions at the next step will be used (this might not be critical in many cases, such as output for visualization with :yref:`VTKRecorder`). The :yref:`O.engines` sequence must be always assigned at once (the reason is in the fact that although engines themselves are passed by reference, the sequence is *copied* from c++ to Python or from Python to c++). This includes modifying an existing ``O.engines``; therefore :: O.engines.append(SomeEngine()) # wrong will not work; :: O.engines=O.engines+[SomeEngine()] # ok must be used instead. For inserting an engine after position #2 (for example), use python slice notation:: O.engines=O.engines[:2]+[SomeEngine()]+O.engines[2:] .. note:: When Yade starts, O.engines is filled with a reasonable default list, so that it is not strictly necessary to redefine it when trying simple things. The default scene will handle spheres, boxes, and facets with :yref:`frictional` properties correctly, and adjusts the timestep dynamically. You can find an example in simple-scene-default-engines.py. Functors choice ---------------- In the above example, we omited functors, only writing ellipses ``...`` instead. As explained in :ref:`dispatchers-and-functors`, there are 4 kinds of functors and associated dispatchers. User can choose which ones to use, though the choice must be consistent. Bo1 functors ^^^^^^^^^^^^ ``Bo1`` functors must be chosen depending on the collider in use; they are given directly to the collider (which internally uses :yref:`BoundDispatcher`). At this moment (September 2010), the most common choice is :yref:`InsertionSortCollider`, which uses :yref:`Aabb`; functors creating :yref:`Aabb` must be used in that case. Depending on particle :yref:`shapes` in your simulation, choose appropriate functors:: O.engines=[..., InsertionSortCollider([Bo1_Sphere_Aabb(),Bo1_Facet_Aabb()]), ... ] Using more functors than necessary (such as :yref:`Bo1_Facet_Aabb` if there are no :yref:`facets` in the simulation) has no performance penalty. On the other hand, missing functors for existing :yref:`shapes` will cause those bodies to not collider with other bodies (they will freely interpenetrate). There are other colliders as well, though their usage is only experimental: * :yref:`SpatialQuickSortCollider` is correctness-reference collider operating on :yref:`Aabb`; it is significantly slower than :yref:`InsertionSortCollider`. * :yref:`PersistentTriangulationCollider` only works on spheres; it does not use a :yref:`BoundDispatcher`, as it operates on spheres directly. * :yref:`FlatGridCollider` is proof-of-concept grid-based collider, which computes grid positions internally (no :yref:`BoundDispatcher` either) Ig2 functors ^^^^^^^^^^^^^ ``Ig2`` functor choice (all of the derive from :yref:`IGeomFunctor`) depends on #. shape combinations that should collide; for instance:: InteractionLoop([Ig2_Sphere_Sphere_ScGeom()],[],[]) will handle collisions for :yref:`Sphere` + :yref:`Sphere`, but not for :yref:`Facet` + :yref:`Sphere` -- if that is desired, an additional functor must be used:: InteractionLoop([ Ig2_Sphere_Sphere_ScGeom(), Ig2_Facet_Sphere_ScGeom() ],[],[]) Again, missing combination will cause given shape combinations to freely interpenetrate one another. #. :yref:`IGeom` type accepted by the ``Law2`` functor (below); it is the first part of functor's name after ``Law2`` (for instance, :yref:`Law2_ScGeom_CpmPhys_Cpm` accepts :yref:`ScGeom`). Ip2 functors ^^^^^^^^^^^^ ``Ip2`` functors (deriving from :yref:`IPhysFunctor`) must be chosen depending on #. :yref:`Material` combinations within the simulation. In most cases, ``Ip2`` functors handle 2 instances of the same :yref:`Material` class (such as :yref:`Ip2_FrictMat_FrictMat_FrictPhys` for 2 bodies with :yref:`FrictMat`) #. :yref:`IPhys` accepted by the constitutive law (``Law2`` functor), which is the second part of the ``Law2`` functor's name (e.g. :yref:`Law2_ScGeom_FrictPhys_CundallStrack` accepts :yref:`FrictPhys`) .. note:: Unlike with ``Bo1`` and ``Ig2`` functors, unhandled combination of :yref:`Materials` is an error condition signaled by an exception. Law2 functor(s) ^^^^^^^^^^^^^^^^ ``Law2`` functor was the ultimate criterion for the choice of ``Ig2`` and ``Ip2`` functors; there are no restrictions on its choice in itself, as it only applies forces without creating new objects. In most simulations, only one ``Law2`` functor will be in use; it is possible, though, to have several of them, dispatched based on combination of :yref:`IGeom` and :yref:`IPhys` produced previously by ``Ig2`` and ``Ip2`` functors respectively (in turn based on combination of :yref:`Shapes` and :yref:`Materials`). .. note:: As in the case of ``Ip2`` functors, receiving a combination of :yref:`IGeom` and :yref:`IPhys` which is not handled by any ``Law2`` functor is an error. .. warning:: Many ``Law2`` exist in Yade, and new ones can appear at any time. In some cases different functors are only different implementations of the same contact law (e.g. :yref:`Law2_ScGeom_FrictPhys_CundallStrack` and :yref:`Law2_L3Geom_FrictPhys_ElPerfPl`). Also, sometimes, the peculiarity of one functor may be reproduced as a special case of a more general one. Therefore, for a given constitutive behavior, the user may have the choice between different functors. It is strongly recommended to favor the most used and most validated implementation when facing such choice. A list of available functors classified from mature to unmaintained is updated `here `_ to guide this choice. Examples ^^^^^^^^ Let us give several example of the chain of created and accepted types. Basic DEM model ^^^^^^^^^^^^^^^^ Suppose we want to use the :yref:`Law2_ScGeom_FrictPhys_CundallStrack` constitutive law. We see that #. the ``Ig2`` functors must create :yref:`ScGeom`. If we have for instance :yref:`spheres` and :yref:`boxes` in the simulation, we will need functors accepting :yref:`Sphere` + :yref:`Sphere` and :yref:`Box` + :yref:`Sphere` combinations. We don't want interactions between boxes themselves (as a matter of fact, there is no such functor anyway). That gives us :yref:`Ig2_Sphere_Sphere_ScGeom` and :yref:`Ig2_Box_Sphere_ScGeom`. #. the ``Ip2`` functors should create :yref:`FrictPhys`. Looking at :yref:`InteractionPhysicsFunctors`, there is only :yref:`Ip2_FrictMat_FrictMat_FrictPhys`. That obliges us to use :yref:`FrictMat` for particles. The result will be therefore:: InteractionLoop( [Ig2_Sphere_Sphere_ScGeom(),Ig2_Box_Sphere_ScGeom()], [Ip2_FrictMat_FrictMat_FrictPhys()], [Law2_ScGeom_FrictPhys_CundallStrack()] ) Concrete model ^^^^^^^^^^^^^^^ In this case, our goal is to use the :yref:`Law2_ScGeom_CpmPhys_Cpm` constitutive law. * We use :yref:`spheres` and :yref:`facets` in the simulation, which selects ``Ig2`` functors accepting those types and producing :yref:`ScGeom`: :yref:`Ig2_Sphere_Sphere_ScGeom` and :yref:`Ig2_Facet_Sphere_ScGeom`. * We have to use :yref:`Material` which can be used for creating :yref:`CpmPhys`. We find that :yref:`CpmPhys` is only created by :yref:`Ip2_CpmMat_CpmMat_CpmPhys`, which determines the choice of :yref:`CpmMat` for all particles. Therefore, we will use:: InteractionLoop( [Ig2_Sphere_Sphere_ScGeom(),Ig2_Facet_Sphere_ScGeom()], [Ip2_CpmMat_CpmMat_CpmPhys()], [Law2_ScGeom_CpmPhys_Cpm()] ) Imposing conditions ==================== In most simulations, it is not desired that all particles float freely in space. There are several ways of imposing boundary conditions that block movement of all or some particles with regard to global space. Motion constraints ------------------ * :yref:`Body.dynamic` determines whether a body will be accelerated by :yref:`NewtonIntegrator`; it is mandatory to make it false for bodies with zero mass, where applying non-zero force would result in infinite displacement. :yref:`Facets` are case in the point: :yref:`facet` makes them non-dynamic by default, as they have zero volume and zero mass (this can be changed, by passing ``dynamic=True`` to :yref:`facet` or setting :yref:`Body.dynamic`; setting :yref:`State.mass` to a non-zero value must be done as well). The same is true for :yref:`wall`. Making sphere non-dynamic is achieved simply by:: b = sphere([x,y,z],radius,dynamic=False) b.dynamic=True #revert the previous * :yref:`State.blockedDOFs` permits selective blocking of any of 6 degrees of freedom in global space. For instance, a sphere can be made to move only in the xy plane by saying: .. ipython:: @suppress Yade [1]: O.reset() Yade [1]: O.bodies.append(sphere((0,0,0),1)) Yade [1]: O.bodies[0].state.blockedDOFs='zXY' In contrast to :yref:`Body.dynamic`, :yref:`blockedDOFs` will only block forces (and acceleration) in selected directions. Actually, ``b.dynamic=False`` is nearly only a shorthand for ``b.state.blockedDOFs=='xyzXYZ'`` . A subtle difference is that the former does reset the velocity components automaticaly, while the latest does not. If you prescribed linear or angular velocity, they will be applied regardless of :yref:`blockedDOFs`. It also implies that if the velocity is not zero when degrees of freedom are blocked via blockedDOFs assignements, the body will keep moving at the velocity it has at the time of blocking. The differences are shown below: .. ipython:: @suppress Yade [1]: O.reset() Yade [1]: b1 = sphere([0,0,0],1,dynamic=True) Yade [1]: b1.state.blockedDOFs Yade [1]: b1.state.vel = Vector3(1,0,0) #we want it to move... Yade [1]: b1.dynamic = False #... at a constant velocity Yade [1]: print b1.state.blockedDOFs, b1.state.vel Yade [1]: # oops, velocity has been reset when setting dynamic=False Yade [1]: b1.state.vel = (1,0,0) # we can still assign it now Yade [1]: print b1.state.blockedDOFs, b1.state.vel Yade [1]: b2 = sphere([0,0,0],1,dynamic=True) #another try Yade [1]: b2.state.vel = (1,0,0) Yade [1]: b2.state.blockedDOFs = "xyzXYZ" #this time we assign blockedDOFs directly, velocity is unchanged Yade [1]: print b2.state.blockedDOFs, b2.state.vel It might be desirable to constrain motion of some particles constructed from a generated sphere packing, following some condition, such as being at the bottom of a specimen; this can be done by looping over all bodies with a conditional:: for b in O.bodies: # block all particles with z coord below .5: if b.state.pos[2]<.5: b.dynamic=False Arbitrary spatial predicates introduced above can be expoited here as well:: from yade import pack pred=pack.inAlignedBox(lowerCorner,upperCorner) for b in O.bodies: if b.shape.name!=Sphere: continue # skip non-spheres # ask the predicate if we are inside if pred(b.state.pos,b.shape.radius): b.dynamic=False .. _imposing_motion_force: Imposing motion and forces -------------------------- Imposed velocity ^^^^^^^^^^^^^^^^ If a degree of freedom is blocked and a velocity is assigned along that direction (translational or rotational velocity), then the body will move at constant velocity. This is the simpler and recommended method to impose the motion of a body. This, for instance, will result in a constant velocity along $x$ (it can still be freely accelerated along $y$ and $z$):: O.bodies.append(sphere((0,0,0),1)) O.bodies[0].state.blockedDOFs='x' O.bodies[0].state.vel=(10,0,0) Conversely, modifying the position directly is likely to break Yade's algorithms, especially those related to collision detection and contact laws, as they are based on bodies velocities. Therefore, unless you really know what you are doing, don't do that for imposing a motion:: O.bodies.append(sphere((0,0,0),1)) O.bodies[0].state.blockedDOFs='x' O.bodies[0].state.pos=10*O.dt #REALLY BAD! Don't assign position Imposed force ^^^^^^^^^^^^^ Applying a force or a torque on a body is done via functions of the :yref:`ForceContainer`. It is as simple as this:: O.forces.addF(0,(1,0,0)) #applies for one step This way, the force applies for one time step only, and is resetted at the beginning of each step. For this reason, imposing a force at the begining of one step will have no effect at all, since it will be immediatly resetted. The only way is to place a :yref:`PyRunner` inside the simulation loop. Applying the force permanently is possible with another function (in this case it does not matter if the command comes at the begining of the time step):: O.forces.setPermF(0,(1,0,0)) #applies permanently The force will persist across iterations, until it is overwritten by another call to ``O.forces.setPermF(id,f)`` or erased by ``O.forces.reset(resetAll=True)``. The permanent force on a body can be checked with ``O.forces.permF(id)``. Boundary controllers -------------------- Engines deriving from :yref:`BoundaryController` impose boundary conditions during simulation, either directly, or by influencing several bodies. You are referred to their individual documentation for details, though you might find interesting in particular * :yref:`UniaxialStrainer` for applying strain along one axis at constant rate; useful for plotting strain-stress diagrams for uniaxial loading case. See :ysrc:`examples/concrete/uniax.py` for an example. * :yref:`TriaxialStressController` which applies prescribed stress/strain along 3 perpendicular axes on cuboid-shaped packing using 6 walls (:yref:`Box` objects) (:yref:`ThreeDTriaxialEngine` is generalized such that it allows independent value of stress along each axis) * :yref:`PeriTriaxController` for applying stress/strain along 3 axes independently, for simulations using periodic boundary conditions (:yref:`Cell`) Field appliers --------------- Engines deriving from :yref:`FieldApplier` acting on all particles. The one most used is :yref:`GravityEngine` applying uniform acceleration field (:yref:`GravityEngine` is deprecated, use :yref:`NewtonIntegrator.gravity` instead!). Partial engines --------------- Engines deriving from :yref:`PartialEngine` define the :yref:`ids` attribute determining bodies which will be affected. Several of them warrant explicit mention here: * :yref:`TranslationEngine` and :yref:`RotationEngine` for applying constant speed linear and rotational motion on subscribers. * :yref:`ForceEngine` and :yref:`TorqueEngine` applying given values of force/torque on subscribed bodies at every step. * :yref:`StepDisplacer` for applying generalized displacement delta at every timestep; designed for precise control of motion when testing constitutive laws on 2 particles. The real value of partial engines is if you need to prescribe complex types of force or displacement fields. For moving a body at constant velocity or for imposing a single force, the methods explained in `Imposing motion and forces`_ are much simpler. There are several interpolating engines (:yref:`InterpolatingDirectedForceEngine` for applying force with varying magnitude, :yref:`InterpolatingHelixEngine` for applying spiral displacement with varying angular velocity and possibly others); writing a new interpolating engine is rather simple using examples of those that already exist. Convenience features ========================= .. _labelingthings: Labeling things ---------------- Engines and functors can define that ``label`` attribute. Whenever the ``O.engines`` sequence is modified, python variables of those names are created/updated; since it happens in the ``__builtins__`` namespaces, these names are immediately accessible from anywhere. This was used in :ref:`creating-interactions` to change interaction radius in multiple functors at once. .. warning:: Make sure you do not use label that will overwrite (or shadow) an object that you already use under that variable name. Take care not to use syntactically wrong names, such as "er*452" or "my engine"; only variable names permissible in Python can be used. Simulation tags ---------------- :yref:`Omega.tags` is a dictionary (it behaves like a dictionary, although the implementation in c++ is different) mapping keys to labels. Contrary to regular python dictionaries that you could create, * ``O.tags`` is *saved and loaded with simulation*; * ``O.tags`` has some values pre-initialized. After Yade startup, ``O.tags`` contains the following: .. ipython:: @suppress Yade [1]: O.reset() Yade [1]: dict(O.tags) # convert to real dictionary author Real name, username and machine as obtained from your system at simulation creation id Unique identifier of this Yade instance (or of the instance which created a loaded simulation). It is composed of date, time and process number. Useful if you run simulations in parallel and want to avoid overwriting each other's outputs; embed ``O.tags['id']`` in output filenames (either as directory name, or as part of the file's name itself) to avoid it. This is explained in :ref:`batch-output-separate` in detail. isoTime Time when simulation was created (with second resolution). d.id, id.d Simulation description and id joined by period (and vice-versa). Description is used in batch jobs; in non-batch jobs, these tags are identical to id. You can add your own tags by simply assigning value, with the restriction that the left-hand side object must be a string and must not contain ``=``. .. ipython:: Yade [2]: O.tags['anythingThat I lik3']='whatever' Yade [2]: O.tags['anythingThat I lik3'] Saving python variables ------------------------ Python variable lifetime is limited; in particular, if you save simulation, variables will be lost after reloading. Yade provides limited support for data persistence for this reason (internally, it uses special values of ``O.tags``). The functions in question are :yref:`saveVars` and :yref:`loadVars`. :yref:`saveVars` takes dictionary (variable names and their values) and a *mark* (identification string for the variable set); it saves the dictionary inside the simulation. These variables can be re-created (after the simulation was loaded from a XML file, for instance) in the ``yade.params.``\ *mark* namespace by calling :yref:`loadVars` with the same identification *mark*: .. ipython:: Yade [3]: a=45; b=pi/3 Yade [3]: saveVars('ab',a=a,b=b) # save simulation (we could save to disk just as well) Yade [3]: O.saveTmp() Yade [4]: O.loadTmp() Yade [4]: loadVars('ab') Yade [5]: yade.params.ab.a # import like this Yade [5]: from yade.params import ab Yade [6]: ab.a, ab.b # also possible Yade [5]: from yade.params import * Yade [6]: ab.a, ab.b Enumeration of variables can be tedious if they are many; creating local scope (which is a function definition in Python, for instance) can help:: def setGeomVars(): radius=a*4 thickness=22 p_t=4/3*pi dim=Vector3(1.23,2.2,3) # # define as much as you want here # it all appears in locals() (and nothing else does) # saveVars('geom',loadNow=True,**locals()) setGeomVars() from yade.params.geom import * # use the variables now .. note:: Only types that can be `pickled `_ can be passed to :yref:`saveVars`. ************************* Controlling simulation ************************* Tracking variables =================== Running python code ------------------- A special engine :yref:`PyRunner` can be used to periodically call python code, specified via the ``command`` parameter. Periodicity can be controlled by specifying computation time (``realPeriod``), virutal time (``virtPeriod``) or iteration number (``iterPeriod``). For instance, to print kinetic energy (using :yref:`kineticEnergy`) every 5 seconds, the following engine will be put to ``O.engines``:: PyRunner(command="print 'kinetic energy',kineticEnergy()",realPeriod=5) For running more complex commands, it is convenient to define an external function and only call it from within the engine. Since the ``command`` is run in the script's namespace, functions defined within scripts can be called. Let us print information on interaction between bodies 0 and 1 periodically:: def intrInfo(id1,id2): try: i=O.interactions[id1,id2] # assuming it is a CpmPhys instance print id1,id2,i.phys.sigmaN except: # in case the interaction doesn't exist (yet?) print "No interaction between",id1,id2 O.engines=[..., PyRunner(command="intrInfo(0,1)",realPeriod=5) ] More useful examples will be given below. The :yref:`yade.plot` module provides simple interface and storage for tracking various data. Although originally conceived for plotting only, it is widely used for tracking variables in general. The data are in :yref:`yade.plot.data` dictionary, which maps variable names to list of their values; the :yref:`yade.plot.addData` function is used to add them. .. ipython:: @suppress Yade [1]: O.reset() Yade [1]: from yade import plot Yade [1]: plot.data Yade [1]: plot.addData(sigma=12,eps=1e-4) # not adding sigma will add a NaN automatically # this assures all variables have the same number of records Yade [2]: plot.addData(eps=1e-3) # adds NaNs to already existing sigma and eps columns Yade [3]: plot.addData(force=1e3) Yade [4]: plot.data # retrieve only one column Yade [5]: plot.data['eps'] # get maximum eps Yade [5]: max(plot.data['eps']) New record is added to all columns at every time :yref:`yade.plot.addData` is called; this assures that lines in different columns always match. The special value ``nan`` or ``NaN`` (`Not a Number `_) is inserted to mark the record invalid. .. note:: It is not possible to have two columns with the same name, since data are stored as a dictionary. To record data periodically, use :yref:`PyRunner`. This will record the *z* coordinate and velocity of body #1, iteration number and simulation time (every 20 iterations):: O.engines=O.engines+[PyRunner(command='myAddData()', iterPeriod=20)] from yade import plot def myAddData(): b=O.bodies[1] plot.addData(z1=b.state.pos[2], v1=b.state.vel.norm(), i=O.iter, t=O.time) .. note:: Arbitrary string can be used as column label for :yref:`yade.plot.data`. If it cannot be used as keyword name for :yref:`yade.plot.addData` (since it is a python keyword (``for``), or has spaces inside (``my funny column``), you can pass dictionary to :yref:`yade.plot.addData` instead:: plot.addData(z=b.state.pos[2],**{'my funny column':b.state.vel.norm()}) An exception are columns having leading of trailing whitespaces. They are handled specially in :yref:`yade.plot.plots` and should not be used (see below). Labels can be conveniently used to access engines in the ``myAddData`` function:: O.engines=[..., UniaxialStrainer(...,label='strainer') ] def myAddData(): plot.addData(sigma=strainer.avgStress,eps=strainer.strain) In that case, naturally, the labeled object must define attributes which are used (:yref:`UniaxialStrainer.strain` and :yref:`UniaxialStrainer.avgStress` in this case). Plotting variables ------------------- Above, we explained how to track variables by storing them using :yref:`yade.plot.addData`. These data can be readily used for plotting. Yade provides a simple, quick to use, plotting in the :yref:`yade.plot` module. Naturally, since direct access to underlying data is possible via :yref:`yade.plot.data`, these data can be processed in any way. The :yref:`yade.plot.plots` dictionary is a simple specification of plots. Keys are x-axis variable, and values are tuple of y-axis variables, given as strings that were used for :yref:`yade.plot.addData`; each entry in the dictionary represents a separate figure:: plot.plots={ 'i':('t',), # plot t(i) 't':('z1','v1') # z1(t) and v1(t) } Actual plot using data in :yref:`yade.plot.data` and plot specification of :yref:`yade.plot.plots` can be triggered by invoking the :yref:`yade.plot.plot` function. Live updates of plots ^^^^^^^^^^^^^^^^^^^^^ Yade features live-updates of figures during calculations. It is controlled by following settings: * :yref:`yade.plot.live` - By setting ``yade.plot.live=True`` you can watch the plot being updated while the calculations run. Set to ``False`` otherwise. * :yref:`yade.plot.liveInterval` - This is the interval in seconds between the plot updates. * :yref:`yade.plot.autozoom` - When set to ``True`` the plot will be automatically rezoomed. Controlling line properties ^^^^^^^^^^^^^^^^^^^^^^^^^^^ In this subsection let us use a *basic complete script* like :ysrc:`examples/simple-scene/simple-scene-plot.py`, which we will later modify to make the plots prettier. Line of interest from that file is, and generates a picture presented below:: plot.plots={'i':('t'),'t':('z_sph',None,('v_sph','go-'),'z_sph_half')} .. figure:: fig/simple-scene-plot-1.* Figure generated by :ysrc:`examples/simple-scene/simple-scene-plot.py`. The line plots take an optional second string argument composed of a line color (eg. ``'r'``, ``'g'`` or ``'b'``), a line style (eg. ``'-'``, ``'–-'`` or ``':'``) and a line marker (``'o'``, ``'s'`` or ``'d'``). A red dotted line with circle markers is created with 'ro:' argument. For a listing of all options please have a look at http://matplotlib.sourceforge.net/api/pyplot_api.html#matplotlib.pyplot.plot For example using following plot.plots() command, will produce a following graph:: plot.plots={'i':(('t','xr:'),),'t':(('z_sph','r:'),None,('v_sph','g--'),('z_sph_half','b-.'))} .. figure:: fig/simple-scene-plot-2.* Figure generated by changing parameters to plot.plots as above. And this one will produce a following graph:: plot.plots={'i':(('t','xr:'),),'t':(('z_sph','Hr:'),None,('v_sph','+g--'),('z_sph_half','*b-.'))} .. figure:: fig/simple-scene-plot-3.* Figure generated by changing parameters to plot.plots as above. .. note:: You can learn more in matplotlib tutorial http://matplotlib.sourceforge.net/users/pyplot_tutorial.html and documentation http://matplotlib.sourceforge.net/users/pyplot_tutorial.html#controlling-line-properties .. note:: Please note that there is an extra ``,`` in ``'i':(('t','xr:'),)``, otherwise the ``'xr:'`` wouldn't be recognized as a line style parameter, but would be treated as an extra data to plot. Controlling text labels ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ It is possible to use TeX syntax in plot labels. For example using following two lines in :ysrc:`examples/simple-scene/simple-scene-plot.py`, will produce a following picture:: plot.plots={'i':(('t','xr:'),),'t':(('z_sph','r:'),None,('v_sph','g--'),('z_sph_half','b-.'))} plot.labels={'z_sph':'\$z_{sph}\$' , 'v_sph':'\$v_{sph}\$' , 'z_sph_half':'\$z_{sph}/2\$'} .. figure:: fig/simple-scene-plot-4.* Figure generated by :ysrc:`examples/simple-scene/simple-scene-plot.py`, with TeX labels. Greek letters are simply a ``'\$\alpha$'``, ``'\$\beta\$'`` etc. in those labels. To change the font style a following command could be used:: yade.plot.matplotlib.rc('mathtext', fontset='stixsans') But this is not part of yade, but a part of matplotlib, and if you want something more complex you really should have a look at matplotlib users manual http://matplotlib.sourceforge.net/users/index.html Multiple figures ^^^^^^^^^^^^^^^^^ Since :yref:`yade.plot.plots` is a dictionary, multiple entries with the same key (x-axis variable) would not be possible, since they overwrite each other: .. ipython:: Yade [1]: plot.plots={ ...: 'i':('t',), ...: 'i':('z1','v1') ...: } Yade [2]: plot.plots You can, however, distinguish them by prepending/appending space to the x-axis variable, which will be removed automatically when looking for the variable in :yref:`yade.plot.data` -- both $x$-axes will use the ``i`` column: .. ipython:: Yade [1]: plot.plots={ ...: 'i':('t',), ...: 'i ':('z1','v1') # note the space in 'i ' ...: } Yade [2]: plot.plots Split y1 y2 axes ^^^^^^^^^^^^^^^^^ To avoid big range differences on the $y$ axis, it is possible to have left and right $y$ axes separate (like ``axes x1y2`` in gnuplot). This is achieved by inserting ``None`` to the plot specifier; variables coming before will be plot normally (on the left *y*-axis), while those after will appear on the right:: plot.plots={'i':('z1',None,'v1')} Exporting ^^^^^^^^^ Plots can be exported to external files for later post-processing via that :yref:`yade.plot.saveGnuplot` function. Note that all data you added via plot.addData is saved - even data that you don't plot live during simulation. By editing the generated .gnuplot file you can plot any of the added Data afterwards. * Data file is saved (compressed using bzip2) separately from the gnuplot file, so any other programs can be used to process them. In particular, the ``numpy.genfromtxt`` (`documented here `_) can be useful to import those data back to python; the decompression happens automatically. * The gnuplot file can be run through gnuplot to produce the figure; see :yref:`yade.plot.saveGnuplot` documentation for details. Stop conditions ================ For simulations with pre-determined number of steps, number of steps can be prescribed: # absolute iteration number O.stopAtIter=35466 O.run() O.wait() or :: # number of iterations to run from now O.run(35466,True) # wait=True causes the simulation to run 35466 iterations, then stopping. Frequently, decisions have to be made based on evolution of the simulation itself, which is not yet known. In such case, a function checking some specific condition is called periodically; if the condition is satisfied, ``O.pause`` or other functions can be called to stop the stimulation. See documentation for :yref:`Omega.run`, :yref:`Omega.pause`, :yref:`Omega.step`, :yref:`Omega.stopAtIter` for details. For simulations that seek static equilibrium, the :yref:`unbalancedForce` can provide a useful metrics (see its documentation for details); for a desired value of ``1e-2`` or less, for instance, we can use:: def checkUnbalanced(): if unbalancedForce<1e-2: O.pause() O.engines=O.engines+[PyRunner(command="checkUnbalanced()",iterPeriod=100)] # this would work as well, without the function defined apart: # PyRunner(command="if unablancedForce<1e-2: O.pause()",iterPeriod=100) O.run(); O.wait() # will continue after O.pause() will have been called Arbitrary functions can be periodically checked, and they can also use history of variables tracked via :yref:`yade.plot.addData`. For example, this is a simplified version of damage control in :ysrc:`examples/concrete/uniax.py`; it stops when current stress is lower than half of the peak stress:: O.engines=[..., UniaxialStrainer=(...,label='strainer'), PyRunner(command='myAddData()',iterPeriod=100), PyRunner(command='stopIfDamaged()',iterPeriod=100) ] def myAddData(): plot.addData(t=O.time,eps=strainer.strain,sigma=strainer.stress) def stopIfDamaged(): currSig=plot.data['sigma'][-1] # last sigma value maxSig=max(plot.data['sigma']) # maximum sigma value # print something in any case, so that we know what is happening print plot.data['eps'][-1],currSig if currSig<.5*maxSig: print "Damaged, stopping" print 'gnuplot',plot.saveGnuplot(O.tags['id']) import sys sys.exit(0) O.run(); O.wait() # this place is never reached, since we call sys.exit(0) directly .. _checkpointing: Checkpoints ------------ Occasionally, it is useful to revert to simulation at some past point and continue from it with different parameters. For instance, tension/compression test will use the same initial state but load it in 2 different directions. Two functions, :yref:`Omega.saveTmp` and :yref:`Omega.loadTmp` are provided for this purpose; *memory* is used as storage medium, which means that saving is faster, and also that the simulation will disappear when Yade finishes. :: O.saveTmp() # do something O.saveTmp('foo') O.loadTmp() # loads the first state O.loadTmp('foo') # loads the second state .. warning:: ``O.loadTmp`` cannot be called from inside an engine, since *before* loading a simulation, the old one must finish the current iteration; it would lead to deadlock, since ``O.loadTmp`` would wait for the current iteration to finish, while the current iteration would be blocked on ``O.loadTmp``. A special trick must be used: a separate function to be run after the current iteration is defined and is invoked from an independent thread launched only for that purpose:: O.engines=[...,PyRunner('myFunc()',iterPeriod=345)] def myFunc(): if someCondition: import thread # the () are arguments passed to the function thread.start_new_thread(afterIterFunc,()) def afterIterFunc(): O.pause(); O.wait() # wait till the iteration really finishes O.loadTmp() O.saveTmp() O.run() .. _remoteaccess: Remote control =============== Yade can be controlled remotely over network. At yade startup, the following lines appear, among other messages:: TCP python prompt on localhost:9000, auth cookie `dcekyu' TCP info provider on localhost:21000 They inform about 2 ports on which connection of 2 different kind is accepted. Python prompt -------------- ``TCP python prompt`` is telnet server with authenticated connection, providing full python command-line. It listens on port 9000, or higher if already occupied (by another yade instance, for example). Using the authentication cookie, connection can be made using telnet:: \$ telnet localhost 9000 Trying 127.0.0.1... Connected to localhost. Escape character is '^]'. Enter auth cookie: dcekyu __ __ ____ __ _____ ____ ____ \ \ / /_ _| _ \ ___ ___ / / |_ _/ ___| _ \ \ V / _` | | | |/ _ \ / _ \ / / | || | | |_) | | | (_| | |_| | __/ | (_) / / | || |___| __/ |_|\__,_|____/ \___| \___/_/ |_| \____|_| (connected from 127.0.0.1:40372) >>> The python pseudo-prompt ``>>>`` lets you write commands to manipulate simulation in variety of ways as usual. Two things to notice: #. The new python interpreter (``>>>``) lives in a namespace separate from ``Yade [1]:`` command-line. For your convenience, ``from yade import *`` is run in the new python instance first, but local and global variables are not accessible (only builtins are). #. The (fake) ``>>>`` interpreter does not have rich interactive feature of IPython, which handles the usual command-line ``Yade [1]:``; therefore, you will have no command history, ``?`` help and so on. .. note:: By giving access to python interpreter, full control of the system (including reading user's files) is possible. For this reason, **connection are only allowed from localhost**, not over network remotely. Of course you can log into the system via SSH over network to get remote access. .. warning:: Authentication cookie is trivial to crack via bruteforce attack. Although the listener stalls for 5 seconds after every failed login attempt (and disconnects), the cookie could be guessed by trial-and-error during very long simulations on a shared computer. Info provider ------------- ``TCP Info provider`` listens at port 21000 (or higher) and returns some basic information about current simulation upon connection; the connection terminates immediately afterwards. The information is python dictionary represented as string (serialized) using standard `pickle `_ module. This functionality is used by the batch system (described below) to be informed about individual simulation progress and estimated times. If you want to access this information yourself, you can study :ysrc:`core/main/yade-batch.in` for details. Batch queuing and execution (yade-batch) ======================================== Yade features light-weight system for running one simulation with different parameters; it handles assignment of parameter values to python variables in simulation script, scheduling jobs based on number of available and required cores and more. The whole batch consists of 2 files: simulation script regular Yade script, which calls :yref:`readParamsFromTable` to obtain parameters from parameter table. In order to make the script runnable outside the batch, :yref:`readParamsFromTable` takes default values of parameters, which might be overridden from the parameter table. :yref:`readParamsFromTable` knows which parameter file and which line to read by inspecting the ``PARAM_TABLE`` environment variable, set by the batch system. parameter table simple text file, each line representing one parameter set. This file is read by :yref:`readParamsFromTable` (using :yref:`TableParamReader` class), called from simulation script, as explained above. For better reading of the text file you can make use of tabulators, these will be ignored by :yref:`readParamsFromTable`. Parameters are not restricted to numerical values. You can also make use of strings by "quoting" them (' ' may also be used instead of " "). This can be useful for nominal parameters. The batch can be run as :: yade-batch parameters.table simulation.py and it will intelligently run one simulation for each parameter table line. A minimal example is found in :ysrc:`examples/test/batch/params.table` and :ysrc:`examples/test/batch/sim.py`, another example follows. Example -------- Suppose we want to study influence of parameters *density* and *initialVelocity* on position of a sphere falling on fixed box. We create parameter table like this:: description density initialVelocity # first non-empty line are column headings reference 2400 10 hi_v = 20 # = to use value from previous line lo_v = 5 # comments are allowed hi_rho 5000 10 # blank lines as well: hi_rho_v = 20 hi_rh0_lo_v = 5 Each line give one combination of these 2 parameters and assigns (which is optional) a *description* of this simulation. In the simulation file, we read parameters from table, at the beginning of the script; each parameter has default value, which is used if not specified in the parameters file: .. code-block:: python readParamsFromTable( gravity=-9.81, density=2400, initialVelocity=20, noTableOk=True # use default values if not run in batch ) from yade.params.table import * print gravity, density, initialVelocity after the call to :yref:`readParamsFromTable`, corresponding python variables are created in the ``yade.params.table`` module and can be readily used in the script, e.g. .. code-block:: python GravityEngine(gravity=(0,0,gravity)) Let us see what happens when running the batch:: \$ yade-batch batch.table batch.py Will run `/usr/local/bin/yade-trunk' on `batch.py' with nice value 10, output redirected to `batch.@.log', 4 jobs at a time. Will use table `batch.table', with available lines 2, 3, 4, 5, 6, 7. Will use lines 2 (reference), 3 (hi_v), 4 (lo_v), 5 (hi_rho), 6 (hi_rho_v), 7 (hi_rh0_lo_v). Master process pid 7030 These lines inform us about general batch information: `nice `_ level, log file names, how many cores will be used (4); table name, and line numbers that contain parameters; finally, which lines will be used; master `PID `_ is useful for killing (stopping) the whole batch with the ``kill`` command. :: Job summary: #0 (reference/4): PARAM_TABLE=batch.table:2 DISPLAY= /usr/local/bin/yade-trunk --threads=4 --nice=10 -x batch.py > batch.reference.log 2>&1 #1 (hi_v/4): PARAM_TABLE=batch.table:3 DISPLAY= /usr/local/bin/yade-trunk --threads=4 --nice=10 -x batch.py > batch.hi_v.log 2>&1 #2 (lo_v/4): PARAM_TABLE=batch.table:4 DISPLAY= /usr/local/bin/yade-trunk --threads=4 --nice=10 -x batch.py > batch.lo_v.log 2>&1 #3 (hi_rho/4): PARAM_TABLE=batch.table:5 DISPLAY= /usr/local/bin/yade-trunk --threads=4 --nice=10 -x batch.py > batch.hi_rho.log 2>&1 #4 (hi_rho_v/4): PARAM_TABLE=batch.table:6 DISPLAY= /usr/local/bin/yade-trunk --threads=4 --nice=10 -x batch.py > batch.hi_rho_v.log 2>&1 #5 (hi_rh0_lo_v/4): PARAM_TABLE=batch.table:7 DISPLAY= /usr/local/bin/yade-trunk --threads=4 --nice=10 -x batch.py > batch.hi_rh0_lo_v.log 2>&1 displays all jobs with command-lines that will be run for each of them. At this moment, the batch starts to be run. :: #0 (reference/4) started on Tue Apr 13 13:59:32 2010 #0 (reference/4) done (exit status 0), duration 00:00:01, log batch.reference.log #1 (hi_v/4) started on Tue Apr 13 13:59:34 2010 #1 (hi_v/4) done (exit status 0), duration 00:00:01, log batch.hi_v.log #2 (lo_v/4) started on Tue Apr 13 13:59:35 2010 #2 (lo_v/4) done (exit status 0), duration 00:00:01, log batch.lo_v.log #3 (hi_rho/4) started on Tue Apr 13 13:59:37 2010 #3 (hi_rho/4) done (exit status 0), duration 00:00:01, log batch.hi_rho.log #4 (hi_rho_v/4) started on Tue Apr 13 13:59:38 2010 #4 (hi_rho_v/4) done (exit status 0), duration 00:00:01, log batch.hi_rho_v.log #5 (hi_rh0_lo_v/4) started on Tue Apr 13 13:59:40 2010 #5 (hi_rh0_lo_v/4) done (exit status 0), duration 00:00:01, log batch.hi_rh0_lo_v.log information about job status changes is being printed, until:: All jobs finished, total time 00:00:08 Log files: batch.reference.log batch.hi_v.log batch.lo_v.log batch.hi_rho.log batch.hi_rho_v.log batch.hi_rh0_lo_v.log Bye. .. _batch-output-separate: Separating output files from jobs ---------------------------------- As one might output data to external files during simulation (using classes such as :yref:`VTKRecorder`), it is important to name files in such way that they are not overwritten by next (or concurrent) job in the same batch. A special tag ``O.tags['id']`` is provided for such purposes: it is comprised of date, time and PID, which makes it always unique (e.g. ``20100413T144723p7625``); additional advantage is that alphabetical order of the ``id`` tag is also chronological. To add the used parameter set or the description of the job, if set, you could add O.tags['params'] to the filename. For smaller simulations, prepending all output file names with ``O.tags['id']`` can be sufficient: .. code-block:: python saveGnuplot(O.tags['id']) For larger simulations, it is advisable to create separate directory of that name first, putting all files inside afterwards: .. code-block:: python os.mkdir(O.tags['id']) O.engines=[ # … VTKRecorder(fileName=O.tags['id']+'/'+'vtk'), # … ] # … O.saveGnuplot(O.tags['id']+'/'+'graph1') Controlling parallel computation -------------------------------- Default total number of available cores is determined from ``/proc/cpuinfo`` (provided by Linux kernel); in addition, if ``OMP_NUM_THREADS`` environment variable is set, minimum of these two is taken. The ``-j``/``--jobs`` option can be used to override this number. By default, each job uses all available cores for itself, which causes jobs to be effectively run in parallel. Number of cores per job can be globally changed via the ``--job-threads`` option. Table column named ``!OMP_NUM_THREADS`` (``!`` prepended to column generally means to assign *environment variable*, rather than python variable) controls number of threads for each job separately, if it exists. If number of cores for a job exceeds total number of cores, warning is issued and only the total number of cores is used instead. Merging gnuplot from individual jobs ------------------------------------ Frequently, it is desirable to obtain single figure for all jobs in the batch, for comparison purposes. Somewhat heiristic way for this functionality is provided by the batch system. ``yade-batch`` must be run with the ``--gnuplot`` option, specifying some file name that will be used for the merged figure:: yade-trunk --gnuplot merged.gnuplot batch.table batch.py Data are collected in usual way during the simulation (using :yref:`yade.plot.addData`) and saved to gnuplot file via :yref:`yade.plot.saveGnuplot` (it creates 2 files: gnuplot command file and compressed data file). The batch system *scans*, once the job is finished, log file for line of the form ``gnuplot [something]``. Therefore, in order to print this *magic line* we put:: print 'gnuplot',plot.saveGnuplot(O.tags['id']) and the end of the script (even after waitIfBatch()) , which prints:: gnuplot 20100413T144723p7625.gnuplot to the output (redirected to log file). This file itself contains single graph: .. _img-yade-multi-gnuplot-single: .. figure:: fig/yade-multi-gnuplot-single.* Figure from single job in the batch. At the end, the batch system knows about all gnuplot files and tries to merge them together, by assembling the ``merged.gnuplot`` file. .. _img-yade-multi-gnuplot-merged: .. figure:: fig/yade-multi-gnuplot-merged.* Merged figure from all jobs in the batch. Note that labels are prepended by job description to make lines distinguishable. HTTP overview -------------- While job is running, the batch system presents progress via simple HTTP server running at port 9080, which can be acessed from regular web browser by requesting the ``http://localhost:9080`` URL. This page can be accessed remotely over network as well. .. _img-yade-multi-summary: .. figure:: fig/yade-multi-summary.* Summary page available at port 9080 as batch is processed (updates every 5 seconds automatically). Possible job statuses are pending, running, done, failed. *************** Postprocessing *************** 3d rendering & videos ====================== There are multiple ways to produce a video of simulation: #. Capture screen output (the 3d rendering window) during the simulation − there are tools available for that (such as `Istanbul `_ or `RecordMyDesktop `_, which are also packaged for most Linux distributions). The output is "what you see is what you get", with all the advantages and disadvantages. #. Periodic frame snapshot using :yref:`SnapshotEngine` (see :ysrc:`examples/bulldozer/bulldozer.py` for a full example):: O.engines=[ #... SnapshotEngine(iterPeriod=100,fileBase='/tmp/bulldozer-',viewNo=0,label='snapshooter') ] which will save numbered files like ``/tmp/bulldozer-0000.png``. These files can be processed externally (with `mencoder `_ and similar tools) or directly with the :yref:`makeVideo`:: makeVideo(frameSpec,out,renameNotOverwrite=True,fps=24,kbps=6000,bps=None) The video is encoded using the default mencoder codec (mpeg4). #. Specialized post-processing tools, notably `Paraview `_. This is described in more detail in the following section. Paraview --------- Saving data during the simulation ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Paraview is based on the `Visualization Toolkit `_, which defines formats for saving various types of data. One of them (with the ``.vtu`` extension) can be written by a special engine :yref:`VTKRecorder`. It is added to the simulation loop:: O.engines=[ # ... VTKRecorder(iterPeriod=100,recorders=['spheres','facets','colors'],fileName='/tmp/p1-') ] * :yref:`iterPeriod` determines how often to save simulation data (besides :yref:`iterPeriod`, you can also use :yref:`virtPeriod` or :yref:`realPeriod`). If the period is too high (and data are saved only few times), the video will have few frames. * :yref:`fileName` is the prefix for files being saved. In this case, output files will be named ``/tmp/p1-spheres.0.vtu`` and ``/tmp/p1-facets.0.vtu``, where the number is the number of iteration; many files are created, putting them in a separate directory is advisable. * :yref:`recorders` determines what data to save :yref:`yade.export.VTKExporter` plays a similar role, with the difference that it is more flexible. It will save any user defined variable associated to the bodies. Loading data into Paraview ^^^^^^^^^^^^^^^^^^^^^^^^^^ All sets of files (``spheres``, ``facets``, …) must be opened one-by-one in Paraview. The open dialogue automatically collapses numbered files in one, making it easy to select all of them: .. _img-paraview-open-files: .. figure:: fig/paraview-open-files.png Click on the "Apply" button in the "Object inspector" sub-window to make loaded objects visible. You can see tree of displayed objects in the "Pipeline browser": .. _img-paraview-rendering-apply: .. figure:: fig/paraview-rendering-apply.png Rendering spherical particles. Glyphs """"""""""""""""""""""""""""""""""""""""""" .. |paraview-glyph-icon| image:: fig/paraview-glyph-icon.png Spheres will only appear as points. To make them look as spheres, you have to add "glyph" to the ``p1-spheres.*`` item in the pipeline using the |paraview-glyph-icon| icon. Then set (in the Object inspector) * "Glyph type" to *Sphere* * "Radius" to *1* * "Scale mode" to *Scalar* (*Scalar* is set above to be the *radii* value saved in the file, therefore spheres with radius *1* will be scaled by their true radius) * "Set scale factor" to *1* * optionally uncheck "Mask points" and "Random mode" (they make some particles not to be rendered for performance reasons, controlled by the "Maximum Number of Points") After clicking "Apply", spheres will appear. They will be rendered over the original white points, which you can disable by clicking on the eye icon next to ``p1-spheres.*`` in the Pipeline browser. Rendering spherical particles. PointSprite """"""""""""""""""""""""""""""""""""""""""" Another opportunity to display spheres is an using *PointSprite* plugin. This technique requires much less RAM in comparison to Glyphs. * "Tools -> Manage Plugins" * "PointSprite_Plugin -> Load selected -> Close" * Load VTU-files * "Representation -> Point Sprite" * "Point Sprite -> Scale By -> radii" * "Edit Radius Transfer Function -> Proportional -> Multiplier = 1.0 -> Close" Rendering interactions as force chain """"""""""""""""""""""""""""""""""""""""""" Data saved by ``VTKRecorder`` (the steps below generates cones rather than tubes) or ``export.VTKExporter(...).exportInteractions(what=[('forceN','i.phys.normalForce().norm()')])`` (the steps below generates per interaction tubes with constant radius): * Load interactions VTP or VTK files * Filters -> Cell Data To Point Data * Filters -> Tube * Set color by "forceN" * Set "Vary Radius" to "By Scalar" * Set "Radius" and "Radius Factor" such that the result looks OK (in 3D postprocessing tutorial script, Radius=0.0005 and Radius Factor=100 looks reasonably) Facet transparency """"""""""""""""""" If you want to make facet objects transparent, select ``p1-facets.*`` in the Pipeline browser, then go to the Object inspector on the Display tab. Under "Style", you can set the "Opacity" value to something smaller than 1. Animation """""""""" You can move between frames (snapshots that were saved) via the "Animation" menu. After setting the view angle, zoom etc to your satisfaction, the animation can be saved with *File/Save animation*. Micro-stress and micro-strain ============================= It is sometimes useful to visualize a DEM simulation through equivalent strain fields or stress fields. This is possible with :yref:`TesselationWrapper`. This class handles the triangulation of spheres in a scene, build tesselation on request, and give access to computed quantities: volume, porosity and local deformation for each sphere. The definition of microstrain and microstress is at the scale of particle-centered subdomains shown below, as explained in [Catalano2014a]_ . .. figure:: fig/micro-domains.* Micro-strain ------------ Below is an output of the :yref:`defToVtk` function visualized with paraview (in this case Yade's TesselationWrapper was used to process experimental data obtained on sand by Edward Ando at Grenoble University, 3SR lab.). The output is visualized with paraview, as explained in the previous section. Similar results can be generated from simulations: .. code-block:: python tt=TriaxialTest() tt.generate("test.yade") O.load("test.yade") O.run(100,True) TW=TesselationWrapper() TW.triangulate() #compute regular Delaunay triangulation, don’t construct tesselation TW.computeVolumes() #will silently tesselate the packing, then compute volume of each Voronoi cell TW.volume(10) #get volume associated to sphere of id 10 TW.setState(0) #store current positions internaly for later use as the "0" state O.run(100,True) #make particles move a little (let's hope they will!) TW.setState(1) #store current positions internaly in the "1" (deformed) state #Now we can define strain by comparing states 0 and 1, and average them at the particles scale TW.defToVtk("strain.vtk") .. figure:: fig/localstrain.* Micro-stress ------------ Stress fields can be generated by combining the volume returned by TesselationWrapper to per-particle stress given by :yref:`bodyStressTensors`. Since the stress $\sigma$ from bodyStressTensor implies a division by the volume $V_b$ of the solid particle, one has to re-normalize it in order to obtain the micro-stress as defined in [Catalano2014a]_ (equation 39 therein), i.e. $\overline{\sigma}^k = \sigma^k \times V_b^k / V_{\sigma}^k$ where $V_{\sigma}^k$ is the volume assigned to particle $k$ in the tesselation. For instance: .. code-block:: python #"b" being a body TW=TesselationWrapper() TW.computeVolumes() s=bodyStressTensors() stress = s[b.id]*4.*pi/3.*b.shape.radius**3/TW.volume(b.id) As any other value, the stress can be exported to a vtk file for display in Paraview using :yref:`yade.export.VTKExporter`. ****************************** Python specialties and tricks ****************************** Importing Yade in other Python applications =========================================== Yade can be imported in other Python applications. To do so, you need somehow to make yade executable .py extended. The easiest way is to create a symbolic link, i.e. (suppose your Yade executable file is called "yade-trunk" and you want make it "yadeimport.py"): .. code-block:: console \$ cd /path/where/you/want/yadeimport \$ ln -s /path/to/yade/executable/yade-trunk yadeimport.py Then you need to make your yadeimport.py findable by Python. You can export PYTHONPATH environment variable, or simply use sys.path directly in Python script: .. code-block:: python import sys sys.path.append('/path/where/you/want/yadeimport') from yadeimport import * print Matrix3(1,2,3, 4,5,6, 7,8,9) print O.bodies # any other Yade code .. perhaps turn this section into a list of FAQs on python as gathered from the yade-users list? ************** Extending Yade ************** * new particle shape * new constitutive law **************** Troubleshooting **************** Crashes ======= It is possible that you encounter crash of Yade, i.e. Yade terminates with error message such as :: Segmentation fault (core dumped) without further explanation. Frequent causes of such conditions are * program error in Yade itself; * fatal condition in your particular simulation (such as impossible dispatch); * problem with graphics card driver. Try to reproduce the error (run the same script) with debug-enabled version of Yade. Debugger will be automatically launched at crash, showing backtrace of the code (in this case, we triggered crash by hand):: Yade [1]: import os,signal Yade [2]: os.kill(os.getpid(),signal.SIGSEGV) SIGSEGV/SIGABRT handler called; gdb batch file is `/tmp/yade-YwtfRY/tmp-0' GNU gdb (GDB) 7.1-ubuntu Copyright (C) 2010 Free Software Foundation, Inc. License GPLv3+: GNU GPL version 3 or later This is free software: you are free to change and redistribute it. There is NO WARRANTY, to the extent permitted by law. Type "show copying" and "show warranty" for details. This GDB was configured as "x86_64-linux-gnu". For bug reporting instructions, please see: . [Thread debugging using libthread_db enabled] [New Thread 0x7f0fb1268710 (LWP 16471)] [New Thread 0x7f0fb29f2710 (LWP 16470)] [New Thread 0x7f0fb31f3710 (LWP 16469)] … What looks as cryptic message is valuable information for developers to locate source of the bug. In particular, there is (usually) line ````; lines below it are source of the bug (at least very likely so):: Thread 1 (Thread 0x7f0fcee53700 (LWP 16465)): #0 0x00007f0fcd8f4f7d in __libc_waitpid (pid=16497, stat_loc=, options=0) at ../sysdeps/unix/sysv/linux/waitpid.c:41 #1 0x00007f0fcd88c7e9 in do_system (line=) at ../sysdeps/posix/system.c:149 #2 0x00007f0fcd88cb20 in __libc_system (line=) at ../sysdeps/posix/system.c:190 #3 0x00007f0fcd0b4b23 in crashHandler (sig=11) at core/main/pyboot.cpp:45 #4 #5 0x00007f0fcd87ed57 in kill () at ../sysdeps/unix/syscall-template.S:82 #6 0x000000000051336d in posix_kill (self=, args=) at ../Modules/posixmodule.c:4046 #7 0x00000000004a7c5e in call_function (f=Frame 0x1c54620, for file , line 1, in (), throwflag=) at ../Python/ceval.c:3750 #8 PyEval_EvalFrameEx (f=Frame 0x1c54620, for file , line 1, in (), throwflag=) at ../Python/ceval.c:2412 If you think this might be error in Yade, file a bug report as explained below. Do not forget to attach *full* yade output from terminal, including startup messages and debugger output -- select with right mouse button, with middle button paste the bugreport to a file and attach it. Attach your simulation script as well. Reporting bugs ============== Bugs are general name for defects (functionality shortcomings, misdocumentation, crashes) or feature requests. They are tracked at http://bugs.launchpad.net/yade. When reporting a new bug, be as specific as possible; state version of yade you use, system version and so on, as explained in the above section on crashes. Getting help ============= Questions and answers --------------------- Please use Launchpad interface at https://answers.launchpad.net/yade/ for asking questions about Yade. In case you're not familiar with computer oriented discussion lists, please read `this wiki page `_ (a Yade-oriented and shortened version of `How To Ask Questions The Smart Way `_) before posting, in order to increase your chances getting help. Do not forget to state what *version* of Yade you use (shown when you start Yade), whether you installed it from source code or a package, what operating system (such as Ubuntu 10.04), and if you have done any local modifications to source code in case of compiled version. Mailing lists -------------- In addition to the Q&A Launchpad interface, Yade has two mailing-lists. Both are hosted at http://www.launchpad.net and before posting, you must register to Launchpad and subscribe to the list by adding yourself to "team" of the same name running the list. yade-users@lists.launchpad.net is a general discussion list for all Yade users. Add yourself to `yade-users team `_ so that you can post messages. `List archive `_ is available. yade-dev@lists.launchpad.net is for discussions about Yade development; you must be member of `yade-dev team `_ to post. This list is `archived `_ as well. Wiki ----- http://www.yade-dem.org/wiki/ Private and/or paid support ---------------------------- You might contact developers by their private mail (rather than by mailing list) if you do not want to disclose details on the mailing list. This is also a suitable method for proposing financial reward for implementation of a substantial feature that is not yet in Yade -- typically, though, we will request this feature to be part of the public codebase once completed, so that the rest of the community can benefit from it as well. trunk-2018.02b/doc/sphinx/yade.bodiesHandling.rst000066400000000000000000000002671324306050200216360ustar00rootroot00000000000000.. _yade.bodiesHandling: yade.bodiesHandling module ========================================== .. automodule:: yade.bodiesHandling :members: :undoc-members: :inherited-members: trunk-2018.02b/doc/sphinx/yade.geom.rst000066400000000000000000000002311324306050200176420ustar00rootroot00000000000000.. _yade.geom: yade.geom module ========================================== .. automodule:: yade.geom :members: :undoc-members: :inherited-members: trunk-2018.02b/doc/sphinx/yadeBookSphinx.py000066400000000000000000000222761324306050200205560ustar00rootroot00000000000000#!/usr/bin/python # encoding: utf-8 # # module documentation # import sys,os,os.path outDir=sys.argv[2] if len(sys.argv)>2 else 'book/_build/' for d in (outDir,outDir+'/latex',outDir+'/html',outDir+'/html/_static'): if not os.path.exists(d): os.mkdir(d) #We assume that modules have been documented by a previous run of yadeSphinx #def moduleDoc(module,submodules=[]): #f=open('yade.'+module+'.rst','w') #modFmt=""".. automodule:: yade.%s #:members: #:undoc-members: #:inherited-members: #""" #f.write(""".. _yade.%s: #yade.%s module #========================================== #"""%(module,module)) ##f.write(".. module:: yade.%s\n\n"%module+modFmt%module) #f.write(modFmt%module) #for sub in submodules: ##f.write(".. submodule:: yade.%s\n\n"%sub+modFmt%sub) #f.write(".. currentmodule:: yade.%s\n\n%s\n\n"%(module,modFmt%sub)) #f.close() ## ## put all yade modules here, as {module:[submodules]} ## ## don't forget to put the module in index.rst as well! ## #mods={'export':[],'post2d':[],'pack':['_packSpheres','_packPredicates','_packObb'],'plot':[],'timing':[],'utils':['_utils'],'polyhedra_utils':['_polyhedra_utils'],'ymport':[],'geom':[],'bodiesHandling':[],'qt':['qt._GLViewer'],'linterpolation':[]} ## ## generate documentation, in alphabetical order #mm=mods.keys(); mm.sort() #for m in mm: moduleDoc(m,mods[m]) #with open('modules.rst','w') as f: #f.write("Yade modules\n=============\n\n.. toctree::\n\t:maxdepth: 2\n\n") #for m in mm: f.write('\tyade.%s.rst\n\n'%m) #import sys #sys.exit(0) # # wrapper documentation # # classes already documented docClasses=set() def autodocClass(klass): global docClasses docClasses.add(klass) return ".. autoclass:: %s\n\t:members:\n\t:undoc-members:\n\t:inherited-members:\n\n"%(klass) # \t:inherited-members:\n # \n\t:show-inheritance: def autodocDerived(klass,doSections=True): global docClasses ret='' if doSections: ret+='%s\n'%klass+'^'*100+'\n\n' #ret+='.. inheritance-diagram:: %s\n\n'%klass ret+=inheritanceDiagram(klass) ret+=autodocClass(klass) childs=list(yade.system.childClasses(klass)); childs.sort(); for k in childs: if not k in docClasses: ret+=autodocClass(k) return ret def inheritanceDiagram(klass): """Generate simple inheritance graph for given classname *klass* using dot (http://www.graphviz.org) syntax. Classes in the docClasses set (already documented) are framed differently and the inheritance tree stops there. """ global docClasses def mkUrl(c): global writer # useless, doesn't work in LaTeX anyway... return ('#yade.wrapper.%s'%c if writer=='html' else '%%yade.wrapper#yade.wrapper.%s'%c) # HTML/LaTeX def mkNode(c,style='solid',fillcolor=None): return '\t\t"%s" [shape="box",fontsize=8,style="setlinewidth(0.5),%s",%sheight=0.2,URL="yade.wrapper.html#yade.wrapper.%s"];\n'%(c,style,'fillcolor=%s,'%fillcolor if fillcolor else '',c) ret=".. graphviz::\n\n\tdigraph %s {\n\t\trankdir=RL;\n\t\tmargin=.2;\n"%klass+mkNode(klass) childs=yade.system.childClasses(klass) if len(childs)==0: return '' for c in childs: try: base=eval(c).__bases__[0].__name__ if base!=klass and base in docClasses: continue # skip classes deriving from classes that are already documented if c not in docClasses: ret+=mkNode(c) else: # classes of which childs are documented elsewhere are marked specially ret+=mkNode(c,style='filled,dashed',fillcolor='grey') ret+='\t\t"%s" -> "%s" [arrowsize=0.5,style="setlinewidth(0.5)"];\n'%(c,base) except NameError: pass #print 'WARN: unable to find class object for',c return ret+'\t}\n\n' def sect(title,text,tops,reverse=False): subsects=[autodocDerived(top,doSections=(len(tops)>1)) for top in tops] if reverse: subsects.reverse() return title+'\n'+100*'-'+'\n\n'+text+'\n\n'+'\n\n'.join(subsects)+'\n' def genWrapperRst(): global docClasses docClasses=set() # reset globals wrapper=file('yade.wrapper.rst','w') wrapper.write(""".. _yade.wrapper:: Class reference (yade.wrapper module) ======================================= .. toctree:: :maxdepth: 2 .. currentmodule:: yade.wrapper .. autosummary:: """+ sect('Bodies','',['Body','Shape','State','Material','Bound'])+ sect('Interactions','',['Interaction','IGeom','IPhys'])+ sect('Global engines','',['FieldApplier','Collider','BoundaryController','GlobalEngine'],reverse=True)+ sect('Partial engines','',['PartialEngine'])+ sect('Bounding volume creation','',['BoundFunctor','BoundDispatcher'])+ sect('Interaction Geometry creation','',['IGeomFunctor','IGeomDispatcher'])+ sect('Interaction Physics creation','',['IPhysFunctor','IPhysDispatcher'])+ sect('Constitutive laws','',['LawFunctor','LawDispatcher'])+ sect('Callbacks','',[#'BodyCallback', 'IntrCallback'])+ sect('Preprocessors','',['FileGenerator'])+ sect('Rendering','',['OpenGLRenderer','GlShapeFunctor','GlStateFunctor','GlBoundFunctor','GlIGeomFunctor','GlIPhysFunctor'])+ # ,'GlShapeDispatcher','GlStateDispatcher','GlBoundDispatcher','GlIGeomDispatcher','GlIPhysDispatcher'])+ sect('Simulation data','',['Omega','BodyContainer','InteractionContainer','ForceContainer','MaterialContainer','Scene','Cell']) +""" Other classes --------------- """ +''.join([autodocClass(k) for k in (yade.system.childClasses('Serializable')-docClasses)|set(['Serializable','TimingDeltas','Cell'])]) ) wrapper.close() def makeBaseClassesClickable(f,writer): out=[] import re,shutil changed=False for l in open(f): if writer=='html': if not '(inherits ' in l: out.append(l) continue m=re.match(r'(^.*\(inherits )([a-zA-Z0-9_ →]*)(\).*$)',l) if not m: out.append(l) continue #raise RuntimeError("Line %s unmatched?"%l) bases=m.group(2) bb=bases.split(' → ') #print bases,'@',bb bbb=' → '.join(['%s'%(b,b) for b in bb]) out.append(m.group(1)+bbb+m.group(3)) elif writer=='latex': if not (r'\pysiglinewithargsret{\sphinxstrong{class }\code{yade.wrapper.}\bfcode{' in l and r'\emph{inherits' in l): out.append(l) continue #print l m=re.match(r'(^.*}}{\\emph{inherits )([a-zA-Z0-9_\\() -]*)(}}.*$)',l) if not m: #print 'WARN: Line %s unmatched?'%l continue bases=m.group(2) bb=bases.split(r' \(\rightarrow\) ') bbb=r' \(\rightarrow\) '.join([r'\hyperref[yade.wrapper:yade.wrapper.%s]{%s}'%(b.replace(r'\_',r'_'),b) for b in bb]) out.append(m.group(1)+bbb+m.group(3)+'\n') changed=True if changed: shutil.move(f,f+'_') ff=open(f,'w') for l in out: ff.write(l) ff.close() def processTemplate(f1,f2): "Copy file f1 to f2, evaluating all occurences of @...@ with eval(); they should expand to a string and can contain arbitrary python expressions." import re def doEval(match): import bib2rst return str(eval(match.group(1))) ff2=open(f2,'w') for l in open(f1): ff2.write(re.sub(r'@([^@]*)@',doEval,l)) def genReferences(): import sys processTemplate('book/fullBibliography.rst.in','fullPublications.rst') import sphinx,sys,shutil sys.path.append('.') # for bib2rst genReferences() for bib in ('references','yade-articles','yade-theses','yade-conferences','yade-docref'): shutil.copyfile('../%s.bib'%bib,outDir+'/latex/%s.bib'%bib) global writer writer=None #writers=['html','latex','epub'] writers=['latex'] volumes=['Book','Reference','Manual','Theory'] #would work if yade was exiting correctly (it seems) for vol in volumes: # no way to use custom file name for conf.py, instead we replace it by the variants shutil.copy('book/conf'+vol+'.py','book/conf.py') for writer in writers: #genWrapperRst() # HACK: must rewrite sys.argv, since reference generator in conf.py determines if we output latex/html by inspecting it sys.argv=['sphinx-build','-a','-c','book','-E','-b','%s'%writer,'-d',outDir+'/doctrees','.',outDir+'/%s'%writer] try: sphinx.main(sys.argv) except SystemExit: pass import glob texDocs=glob.glob(outDir+'latex/*.tex') if writer=='html': makeBaseClassesClickable((outDir+'/html/yade.wrapper.html'),writer) elif writer=='latex': for texdoc in texDocs: makeBaseClassesClickable(texdoc,writer) if (os.path.exists('/usr/share/javascript/jquery/jquery.js')): #Check, whether jquery.js installed in system if os.path.isfile(outDir+'/html/_static/jquery.js'): os.system('rm '+ outDir+'/html/_static/jquery.js') os.system('ln -s /usr/share/javascript/jquery/jquery.js '+ outDir+'/html/_static/jquery.js') # HACK!!!!========================================================================== # New sphinx-python versions (hopefully) are producing empty "verbatim"-environments. # That is why xelatex crashes. # The following "script" removes all empty environments. Needs to be fixed in python-sphinx. if (writer=='latex'): for texdoc in texDocs: infile = open(texdoc,"r") lines = infile.readlines() infile.close() out=[] for i in range(0,len(lines)): if (i<>len(lines) and lines[i].strip()=="\\begin{Verbatim}[commandchars=\\\\\{\\}]" and lines[i+1].strip()=="\\end{Verbatim}"): lines[i]=''; lines[i+1]='' else: out.append(lines[i]) file(texdoc,'w').write('') for i in out: file(texdoc,'a').write(i) # HACK!!!!========================================================================== sys.exit() trunk-2018.02b/doc/sphinx/yadeSphinx.py000066400000000000000000000212471324306050200177400ustar00rootroot00000000000000#!/usr/bin/python # encoding: utf-8 # # module documentation # import sys,os,os.path outDir=sys.argv[2] if len(sys.argv)>2 else '_build' for d in (outDir,outDir+'/latex',outDir+'/html'): if not os.path.exists(d): os.mkdir(d) def moduleDoc(module,submodules=[]): f=open('yade.'+module+'.rst','w') modFmt=""".. automodule:: yade.%s :members: :undoc-members: :inherited-members: """ f.write(""".. _yade.%s: yade.%s module ========================================== """%(module,module)) #f.write(".. module:: yade.%s\n\n"%module+modFmt%module) f.write(modFmt%module) for sub in submodules: #f.write(".. submodule:: yade.%s\n\n"%sub+modFmt%sub) f.write(".. currentmodule:: yade.%s\n\n%s\n\n"%(module,modFmt%sub)) f.close() # # put all yade modules here, as {module:[submodules]} # # don't forget to put the module in index.rst as well! # mods={'export':[],'post2d':[],'pack':['_packSpheres','_packPredicates','_packObb'],'plot':[],'timing':[],'utils':['_utils'],'polyhedra_utils':['_polyhedra_utils'],'ymport':[],'geom':[],'bodiesHandling':[],'qt':['qt._GLViewer'],'linterpolation':[],'gridpfacet':[]} # # generate documentation, in alphabetical order mm=mods.keys(); mm.sort() for m in mm: moduleDoc(m,mods[m]) with open('modules.rst','w') as f: f.write("Yade modules\n=============\n\n.. toctree::\n\t:maxdepth: 2\n\n") for m in mm: f.write('\tyade.%s.rst\n\n'%m) #import sys #sys.exit(0) # # wrapper documentation # # classes already documented docClasses=set() def autodocClass(klass): global docClasses docClasses.add(klass) return ".. autoclass:: %s\n\t:members:\n\t:undoc-members:\n\t:inherited-members:\n\n"%(klass) # \t:inherited-members:\n # \n\t:show-inheritance: def autodocDerived(klass,doSections=True): global docClasses ret='' if doSections: ret+='%s\n'%klass+'^'*100+'\n\n' #ret+='.. inheritance-diagram:: %s\n\n'%klass ret+=inheritanceDiagram(klass) ret+=autodocClass(klass) childs=list(yade.system.childClasses(klass)); childs.sort(); for k in childs: if not k in docClasses: ret+=autodocClass(k) return ret def inheritanceDiagram(klass): """Generate simple inheritance graph for given classname *klass* using dot (http://www.graphviz.org) syntax. Classes in the docClasses set (already documented) are framed differently and the inheritance tree stops there. """ global docClasses def mkUrl(c): global writer # useless, doesn't work in LaTeX anyway... return ('#yade.wrapper.%s'%c if writer=='html' else '%%yade.wrapper#yade.wrapper.%s'%c) # HTML/LaTeX def mkNode(c,style='solid',fillcolor=None): return '\t\t"%s" [shape="box",fontsize=8,style="setlinewidth(0.5),%s",%sheight=0.2,URL="yade.wrapper.html#yade.wrapper.%s"];\n'%(c,style,'fillcolor=%s,'%fillcolor if fillcolor else '',c) ret=".. graphviz::\n\n\tdigraph %s {\n\t\trankdir=RL;\n\t\tmargin=.2;\n"%klass+mkNode(klass) childs=yade.system.childClasses(klass) if len(childs)==0: return '' for c in childs: try: base=eval(c).__bases__[0].__name__ if base!=klass and base in docClasses: continue # skip classes deriving from classes that are already documented if c not in docClasses: ret+=mkNode(c) else: # classes of which childs are documented elsewhere are marked specially ret+=mkNode(c,style='filled,dashed',fillcolor='grey') ret+='\t\t"%s" -> "%s" [arrowsize=0.5,style="setlinewidth(0.5)"];\n'%(c,base) except NameError: pass #print 'WARN: unable to find class object for',c return ret+'\t}\n\n' def sect(title,text,tops,reverse=False): subsects=[autodocDerived(top,doSections=(len(tops)>1)) for top in tops] if reverse: subsects.reverse() return title+'\n'+100*'-'+'\n\n'+text+'\n\n'+'\n\n'.join(subsects)+'\n' def genWrapperRst(): global docClasses docClasses=set() # reset globals wrapper=file('yade.wrapper.rst','w') wrapper.write(""".. _yade.wrapper:: Class reference (yade.wrapper module) ======================================= .. toctree:: :maxdepth: 2 .. currentmodule:: yade.wrapper """+ sect('Bodies','',['Body','Shape','State','Material','Bound'])+ sect('Interactions','',['Interaction','IGeom','IPhys'])+ sect('Global engines','',['FieldApplier','Collider','BoundaryController','GlobalEngine'],reverse=True)+ sect('Partial engines','',['PartialEngine'])+ sect('Bounding volume creation','',['BoundFunctor','BoundDispatcher'])+ sect('Interaction Geometry creation','',['IGeomFunctor','IGeomDispatcher'])+ sect('Interaction Physics creation','',['IPhysFunctor','IPhysDispatcher'])+ sect('Constitutive laws','',['LawFunctor','LawDispatcher'])+ sect('Callbacks','',[#'BodyCallback', 'IntrCallback'])+ sect('Preprocessors','',['FileGenerator'])+ sect('Rendering','',['OpenGLRenderer','GlShapeFunctor','GlStateFunctor','GlBoundFunctor','GlIGeomFunctor','GlIPhysFunctor'])+ # ,'GlShapeDispatcher','GlStateDispatcher','GlBoundDispatcher','GlIGeomDispatcher','GlIPhysDispatcher'])+ sect('Simulation data','',['Omega','BodyContainer','InteractionContainer','ForceContainer','MaterialContainer','Scene','Cell']) +""" Other classes --------------- """ +''.join([autodocClass(k) for k in (yade.system.childClasses('Serializable')-docClasses)|set(['Serializable','TimingDeltas','Cell'])]) ) wrapper.close() def makeBaseClassesClickable(f,writer): out=[] import re,shutil changed=False for l in open(f): if writer=='html': if not '(inherits ' in l: out.append(l) continue m=re.match(r'(^.*\(inherits )([a-zA-Z0-9_ →]*)(\).*$)',l) if not m: out.append(l) continue #raise RuntimeError("Line %s unmatched?"%l) bases=m.group(2) bb=bases.split(' → ') #print bases,'@',bb bbb=' → '.join(['%s'%(b,b) for b in bb]) out.append(m.group(1)+bbb+m.group(3)) elif writer=='latex': if not (r'\pysiglinewithargsret{\sphinxstrong{class }\code{yade.wrapper.}\bfcode{' in l and r'\emph{inherits' in l): out.append(l) continue #print l m=re.match(r'(^.*}}{\\emph{inherits )([a-zA-Z0-9_\\() -]*)(}}.*$)',l) if not m: #print 'WARN: Line %s unmatched?'%l continue bases=m.group(2) bb=bases.split(r' \(\rightarrow\) ') bbb=r' \(\rightarrow\) '.join([r'\hyperref[yade.wrapper:yade.wrapper.%s]{%s}'%(b.replace(r'\_',r'_'),b) for b in bb]) out.append(m.group(1)+bbb+m.group(3)+'\n') changed=True if changed: shutil.move(f,f+'_') ff=open(f,'w') for l in out: ff.write(l) ff.close() def processTemplate(f1,f2): "Copy file f1 to f2, evaluating all occurences of @...@ with eval(); they should expand to a string and can contain arbitrary python expressions." import re def doEval(match): import bib2rst return str(eval(match.group(1))) ff2=open(f2,'w') for l in open(f1): ff2.write(re.sub(r'@([^@]*)@',doEval,l)) def genReferences(): import sys processTemplate('references.rst.in','references.rst') processTemplate('publications.rst.in','publications.rst') import sphinx,sys,shutil sys.path.append('.') # for bib2rst genReferences() for bib in ('references','yade-articles','yade-theses','yade-conferences','yade-docref'): shutil.copyfile('../%s.bib'%bib,outDir+'/latex/%s.bib'%bib) global writer writer=None for writer in ['html','latex','epub']: genWrapperRst() # HACK: must rewrite sys.argv, since reference generator in conf.py determines if we output latex/html by inspecting it sys.argv=['sphinx-build','-a','-E','-b','%s'%writer,'-d',outDir+'/doctrees','.',outDir+'/%s'%writer] try: sphinx.main(sys.argv) except SystemExit: pass if writer=='html': makeBaseClassesClickable((outDir+'/html/yade.wrapper.html'),writer) elif writer=='latex': makeBaseClassesClickable((outDir+'/latex/Yade.tex'),writer) if (os.path.exists('/usr/share/javascript/jquery/jquery.js')): #Check, whether jquery.js installed in system os.system('rm '+ outDir+'/html/_static/jquery.js') os.system('ln -s /usr/share/javascript/jquery/jquery.js '+ outDir+'/html/_static/jquery.js') # HACK!!!!========================================================================== # New sphinx-python versions (hopefully) are producing empty "verbatim"-environments. # That is why xelatex crashes. # The following "script" removes all empty environments. Needs to be fixed in python-sphinx. if (writer=='latex'): infile = open(outDir+'/latex/Yade.tex',"r") lines = infile.readlines() infile.close() out=[] for i in range(0,len(lines)): if (i<>len(lines) and lines[i].strip()=="\\begin{Verbatim}[commandchars=\\\\\{\\}]" and lines[i+1].strip()=="\\end{Verbatim}"): lines[i]=''; lines[i+1]='' else: out.append(lines[i]) file(outDir+'/latex/Yade.tex','w').write('') for i in out: file(outDir+'/latex/Yade.tex','a').write(i) # HACK!!!!========================================================================== sys.exit() trunk-2018.02b/doc/sphinx/youtube.py000066400000000000000000000027471324306050200173240ustar00rootroot00000000000000from docutils import nodes from docutils.parsers.rst import directives CODE = """\ %(extra)s """ PARAM = """\n """ def youtube(name, args, options, content, lineno, contentOffset, blockText, state, stateMachine): """ Restructured text extension for inserting youtube embedded videos """ if len(content) == 0: return string_vars = { 'yid': content[0], 'width': 640, 'height': 480, 'extra': '' } extra_args = content[1:] # Because content[0] is ID extra_args = [ea.strip().split("=") for ea in extra_args] # key=value extra_args = [ea for ea in extra_args if len(ea) == 2] # drop bad lines extra_args = dict(extra_args) if 'width' in extra_args: string_vars['width'] = extra_args.pop('width') if 'height' in extra_args: string_vars['height'] = extra_args.pop('height') if extra_args: params = [PARAM % (key, extra_args[key]) for key in extra_args] string_vars['extra'] = "".join(params) return [nodes.raw('', CODE % (string_vars), format='html')] youtube.content = True directives.register_directive('youtube', youtube) trunk-2018.02b/doc/yade-articles.bib000066400000000000000000001160611324306050200171440ustar00rootroot00000000000000@article{Albaba2015, title={Relation between microstructure and loading applied by a granular flow to a rigid wall using DEM modeling}, author={Albaba, A and Lambert, S and Nicot, F and Chareyre, B}, journal={Granular Matter}, volume={17}, number={5}, pages={603--616}, year={2015}, doi={10.1007/s10035-015-0579-8} } @Article{AboulHosn2016, author={Aboul Hosn, R. and Sibille, L. and Benahmed, N. and Chareyre, B.}, title={Discrete numerical modeling of loose soil with spherical particles and interparticle rolling friction}, journal={Granular Matter}, year={2016}, volume={19}, number={1}, pages={4}, doi={10.1007/s10035-016-0687-0}, url={http://dx.doi.org/10.1007/s10035-016-0687-0} } @article{Bance2014, title={Micromagnetics of shape anisotropy based permanent magnets}, author={Bance, S. and Fischbacher, J. and Schrefl, T. and Zins, I. and Rieger, G. and Cassignol, C.}, journal={Journal of Magnetism and Magnetic Materials}, volume={363}, pages={121--124}, year={2014}, publisher={Elsevier} } @article{Bonilla2015, year={2015}, journal={Acta Geotechnica}, doi={10.1007/s11440-015-0374-z}, title={Rock slope stability analysis using photogrammetric data and DFN–DEM modelling}, url={http://dx.doi.org/10.1007/s11440-015-0374-z}, publisher={Springer Berlin Heidelberg}, author={Bonilla-Sierra, V. and Scholtès, L. and Donzé, F.V. and Elmouttie, M.K.}, pages={1-15} } @article{Boon2012a, title = {A new algorithm for contact detection between convex polygonal and polyhedral particles in the discrete element method}, journal = {Computers and Geotechnics}, volume = {44}, number = {0}, pages = {73 - 82}, year = {2012}, doi = {10.1016/j.compgeo.2012.03.012}, url = {http://www.sciencedirect.com/science/article/pii/S0266352X12000535}, author = {Boon,C.W. and Houlsby, G.T. and Utili, S.} } @article{Boon2012b, title = {A new contact detection algorithm for three-dimensional non-spherical particles}, journal = {Powder Technology}, year = {2013}, doi = {10.1016/j.powtec.2012.12.040}, url = {http://www.sciencedirect.com/science/article/pii/S003259101200839X}, author = {Boon,C.W. and Houlsby, G.T. and Utili, S.} } @article{Boon2014, title={New insights into the 1963 Vajont slide using 2D and 3D distinct-element method analyses}, journal={Géotechnique}, author={Boon, C.W. and Houlsby, G.T. and Utili, S.}, year={2014}, volume={64}, number={10}, pages={800--816}, publisher={Thomas Telford} } @article{Boon2015, title={A new rock slicing method based on linear programming}, author={Boon, C.W. and Houlsby, G.T. and Utili, S.}, journal={Computers and Geotechnics}, volume={65}, pages={12--29}, year={2015}, publisher={Elsevier} } @article{Bourrier2015, title={A reliability-based approach for the design of rockfall protection fences}, author={Bourrier, F. and Lambert, S. and Baroth, J.}, journal={Rock Mechanics and Rock Engineering}, volume={48}, number={1}, pages={247--259}, year={2015}, publisher={Springer} } @article{Bourrier2013, title = {Discrete modeling of granular soils reinforcement by plant roots}, journal = {Ecological Engineering}, author={Bourrier, F. and Kneib, F. and Chareyre, B. and Fourcaud, T.}, year = {2013}, doi = {10.1016/j.ecoleng.2013.05.002}, url = {http://dx.doi.org/10.1016/j.ecoleng.2013.05.002} } @Article{Catalano2014a, title = {Pore-scale modeling of fluid-particles interaction and emerging poromechanical effects}, author = {Catalano, E. and Chareyre, B. and Barthélémy, E.}, journal = {International Journal for Numerical and Analytical Methods in Geomechanics}, year = {2014}, volume = {38}, number = {1}, pages = {51--71}, doi = {10.1002/nag.2198}, note = {http://arxiv.org/pdf/1304.4895.pdf}, url = {http://dx.doi.org/10.1002/nag.2198} } @article{Chalak2017, title={Partially saturated media: from DEM simulation to thermodynamic interpretation}, author={Chalak, C. and Chareyre, B. and Nikooee, E. and Darve, F.}, journal={European Journal of Environmental and Civil Engineering}, volume={21}, number={7-8}, pages={798--820}, year={2017}, doi={10.1080/19648189.2016.1164087} } @article{Chareyre2012a, author = {Chareyre, B. and Cortis, A. and Catalano, E. and Barthélemy, E.}, title = {Pore-Scale Modeling of Viscous Flow and Induced Forces in Dense Sphere Packings}, journal = {Transport in Porous Media}, publisher = {Springer Netherlands}, volume={92}, number={2}, year={2012}, pages = {473-493}, url = {http://dx.doi.org/10.1007/s11242-011-9915-6}, doi = {10.1007/s11242-011-9915-6} } @article{Chen2007, title = {Prediction/Verification of Particle Motion in One Dimension with the Discrete-Element Method}, author = {Chen, F. and Drumm, E. C. and Guiochon, G.}, doi = {10.1061/(ASCE)1532-3641(2007)7:5(344)}, journal = {International Journal of Geomechanics, ASCE}, pages = {344--352}, volume = {7}, number = {5}, year = {2007} } @article{Chen2011a, title={Coupled discrete element and finite volume solution of two classical soil mechanics problems}, author={Chen, F. and Drumm, E. and Guiochon G.}, journal={Computers and Geotechnics}, year={2011}, doi = {10.1016/j.compgeo.2011.03.009}, url={http://www.sciencedirect.com/science/article/pii/S0266352X11000504}, publisher={Elsevier} } @article{Chen2012, author = {Chen, Jingsong and Huang, Baoshan and Chen, Feng and Shu, Xiang}, title = {Application of discrete element method to Superpave gyratory compaction}, journal = {Road Materials and Pavement Design}, volume = {13}, number = {3}, pages = {480-500}, year = {2012}, doi = {10.1080/14680629.2012.694160}, URL = {http://www.tandfonline.com/doi/abs/10.1080/14680629.2012.694160} } @article{Chen2014, title={DEM Simulation of Laboratory Compaction of Asphalt Mixtures Using an Open Source Code}, author={Chen, J. and Huang, B. and Shu, X. and Hu, C.}, journal={Journal of Materials in Civil Engineering}, year={2014}, publisher={American Society of Civil Engineers} } @article{Cheng2016, author = {Cheng, H. and Yamamoto, H. and Thoeni, K.}, doi = {10.1016/j.compgeo.2016.03.006}, journal = {Computers and Geotechnics}, pages = {170–183}, title = {{Numerical study on stress states and fabric anisotropies in soilbags using the DEM}}, url = {http://www.sciencedirect.com/science/article/pii/S0266352X1630060X}, volume = {76}, year = {2016} } @article{Cuomo2016, author = {Cuomo, S. and Chareyre, B. and d'Arista, P. and Sala, M.D. and Cascini, L.}, journal = {CATENA}, pages = {146--152}, title = {Micromechanical modelling of rainsplash erosion in unsaturated soils by Discrete Element Method}, volume = {147}, year = {2016} } @article{Dang2010a, author = {Dang, H. K. and Meguid, M. A.}, title = {Algorithm to Generate a Discrete Element Specimen with Predefined Properties}, publisher = {ASCE}, year = {2010}, journal = {International Journal of Geomechanics}, volume = {10}, number = {2}, pages = {85-91}, keywords = {Discrete elements; Geotechnical engineering; Grain size; Porosity; Algorithms; Granular materials}, doi = {10.1061/(ASCE)GM.1943-5622.0000028} } @article{Dang2010b, title = {Evaluating the performance of an explicit dynamic relaxation technique in analyzing non-linear geotechnical engineering problems}, journal = {Computers and Geotechnics}, volume = {37}, number = {1-2}, pages = {125 - 131}, year = {2010}, doi = {10.1016/j.compgeo.2009.08.004}, author = {Dang, H. K. and Meguid, M. A.} } @article{Donze2008, title={Impacts on cohesive frictional geomaterials}, author={Donzé, F.V.}, journal={European Journal of Environmental and Civil Engineering}, volume={12}, number={7-8}, pages={967--985}, year={2008} } @article{Duriez2011, author = {J. Duriez and F. Darve and F.V. Donzé}, title = {A discrete modeling-based constitutive relation for infilled rock joints}, year = {2011}, journal = {International Journal of Rock Mechanics \& Mining Sciences}, volume = {48}, number = {3}, pages = {458--468}, doi = {10.1016/j.ijrmms.2010.09.008} } @article{Duriez2013, author = {J. Duriez and F. Darve and F.V. Donzé}, title = {Incrementally non-linear plasticity applied to rock joint modelling}, journal = {International Journal for Numerical and Analytical Methods in Geomechanics}, volume = {37}, number = {5}, url = {http://dx.doi.org/10.1002/nag.1105}, doi = {10.1002/nag.1105}, pages = {453--477}, year = {2013} } @article{Duriez2016, title = {Micromechanics of wing crack propagation for different flaw properties}, author = {J. Duriez and L. Scholtès and F.V. Donzé}, journal = {Engineering Fracture Mechanics}, volume = {153}, pages = {378 -- 398}, year = {2016}, doi = {10.1016/j.engfracmech.2015.12.034} } @Article{Duriez2016b, author = {J. Duriez and R. Wan}, title = {Stress in wet granular media with interfaces via homogenization and discrete element approaches}, year = {2016},volume = {142},number = {12}, journal = {Journal of Engineering Mechanics}, doi = {10.1061/(ASCE)EM.1943-7889.0001163} } @Article{Duriez2017, author = {J. Duriez and R. Wan}, title = {Subtleties in discrete-element modelling of wet granular soils}, year = {2017},volume = {67},number = {4},pages = {365--370}, journal = {Géotechnique}, doi = {10.1680/jgeot.15.P.113} } @Article{Duriez2017b, author = {J. Duriez and R. Wan}, title = {Contact angle mechanical influence for wet granular soils}, year = {2017},volume={12},number={1},pages={67--83}, journal = {Acta Geotechnica}, doi = {10.1007/s11440-016-0500-6} } @article{Duriez2017c, title = {The micromechanical nature of stresses in triphasic granular media with interfaces}, journal = {Journal of the Mechanics and Physics of Solids}, year = {2017},volume = {99},pages = {495--511}, doi = {10.1016/j.jmps.2016.10.011}, author = {J. Duriez and M. Eghbalian and R. Wan and F. Darve} } @article{Dyck2015, title={A new approach to digital generation of spherical void phase porous media microstructures}, author={Dyck, N.J. and Straatman, A.G.}, journal={International Journal of Heat and Mass Transfer}, volume={81}, pages={470--477}, year={2015}, } @article{Effeindzourou2016, author = {Effeindzourou, A. and Chareyre, B. and Thoeni, K. and Giacomini, A. and Kneib, F.}, doi = {10.1016/j.geotexmem.2015.07.015}, journal = {Geotextiles and Geomembranes}, number = {2}, pages = {143--156}, title = {{Modelling of deformable structures in the general framework of the discrete element method}}, volume = {44}, year = {2016} } @article{Elias2014, title = {Simulation of railway ballast using crushable polyhedral particles}, journal = {Powder Technology}, volume = {264}, pages = {458--465}, year = {2014}, issn = {0032-5910}, doi = {10.1016/j.powtec.2014.05.052}, author = {Jan Eliáš}, } @article{Epifancev2013, author={Epifancev, K. and Nikulin, A. and Kovshov, S. and Mozer, S. and Brigadnov, I.}, title={Modeling of Peat Mass Process Formation Based on 3D Analysis of the Screw Machine by the Code YADE}, journal={American Journal of Mechanical Engineering}, volume={1}, number={3}, pages={73--75}, year={2013}, url={http://pubs.sciepub.com/ajme/1/3/3}, doi={10.12691/ajme-1-3-3} } @article{Epifantsev2012, title={Proizvodstvo kuskovogo torfa, ekstrudirovanie, forma zakhodnoi i kalibriruyushchei chasti fil'ery matritsy, metod diskretnykh elementov [RUS]}, author={Epifantsev, K. and Mikhailov, A. and Gladky, A.}, journal={Mining informational and analytical bulletin (scientific and technical journal)}, year={2012}, publisher={The joint-stock company Gornaya Kniga}, number = {3}, pages = {212-219}, issn = {0236-1493}, } @article{Favier2009a, title={Predicting the drag coefficient of a granular flow using the discrete element method}, author={Favier, L. and Daudon, D. and Donzé, F.V. and Mazars, J.}, journal={Journal of Statistical Mechanics: Theory and Experiment}, volume={2009}, pages={P06012}, year={2009} } @article{Favier2012, title={Rigid obstacle impacted by a supercritical cohesive granular flow using a 3D discrete element model}, author={Favier, L. and Daudon, D. and Donzé, F.V.}, journal={Cold Regions Science and Technology}, year={2012}, volume = {85}, pages = {232--241}, url = {http://dx.doi.org/10.1016/j.coldregions.2012.09.010} } @article{Gladky2014, year={2014}, issn={1434-5021}, journal={Granular Matter}, doi={10.1007/s10035-014-0527-z}, title={Comparison of different capillary bridge models for application in the discrete element method}, url={http://dx.doi.org/10.1007/s10035-014-0527-z}, publisher={Springer Berlin Heidelberg}, author={Gladkyy, Anton and Schwarze, Rüdiger}, pages={1-10}, } @article{Grujicic2013, title={Discrete Element Modeling and Analysis of Structural Collapse/Survivability of a Building Subjected to Improvised Explosive Device (IED) Attack}, author={Grujicic, M and Snipes, JS and Ramaswami, S and Yavari, R}, journal={Advances in Materials Science and Applications}, year={2013}, volume={2}, number={1}, pages={9--24} } @article {Guo2014, author = {Guo, Ning and Zhao, Jidong}, title = {A coupled FEM/DEM approach for hierarchical multiscale modelling of granular media}, journal = {International Journal for Numerical Methods in Engineering}, volume = {99}, number = {11}, url = {http://dx.doi.org/10.1002/nme.4702}, doi = {10.1002/nme.4702}, pages = {789--818}, year = {2014} } @ARTICLE{Guo2015, author = {N. Guo and J. Zhao}, title = {Multiscale insights into classical geomechanics problems}, journal = {International Journal for Numerical and Analytical Methods in Geomechanics}, year = {2015}, note = {under review} } @article{Gusenbauer2012, title={Self-organizing magnetic beads for biomedical applications}, author={Gusenbauer, M. and Kovacs, A. and Reichel, F. and Exl, L. and Bance, S. and Özelt, H. and Schrefl, T.}, journal={Journal of Magnetism and Magnetic Materials}, volume={324}, number={6}, pages={977--982}, year={2012}, publisher={Elsevier} } @article{Gusenbauer2014, title={Guided self-assembly of magnetic beads for biomedical applications}, author={Gusenbauer, M. and Nguyen, H. and Reichel, F. and Exl, L. and Bance, S. and Fischbacher, J. and Özelt, H. and Kovacs, A. and Brandl, M. and Schrefl, T.}, journal={Physica B: Condensed Matter}, volume={435}, pages={21--24}, year={2014}, publisher={Elsevier} } @article{Gusenbauer2018, title = "Simulation of magnetic particles in microfluidic channels", journal = "Journal of Magnetism and Magnetic Materials", volume = "446", number = "Supplement C", pages = "185--191", year = "2018", issn = "0304-8853", doi = "10.1016/j.jmmm.2017.09.031", url = "https://doi.org/10.1016/j.jmmm.2017.09.031", author = "Markus Gusenbauer and Thomas Schrefl", } @article{Harthong2009, author = {Harthong, B. and Jerier, J.F. and Doremus, P. and Imbault, D. and Donzé, F.V.}, doi = {10.1016/j.ijsolstr.2009.05.008}, issn = {00207683}, journal = {International Journal of Solids and Structures}, month = {September}, number = {18-19}, pages = {3357--3364}, title = {Modeling of high-density compaction of granular materials by the Discrete Element Method}, volume = {46}, year = {2009} } @article{Hadda2013, year={2013}, journal={Granular Matter}, volume={15}, number={2}, doi={10.1007/s10035-013-0402-3}, title={Micromechanical analysis of second order work in granular media}, url={http://dx.doi.org/10.1007/s10035-013-0402-3}, author={Hadda, Nejib and Nicot, François and Bourrier, Franck and Sibille, Luc and Radjai, Farhang and Darve, Félix}, pages={221--235} } @article{Hartong2012a, author = {Harthong, B. and Scholtès, L. and Donzé, F.-V.}, title = {Strength characterization of rock masses, using a coupled DEM–DFN model}, journal = {Geophysical Journal International}, volume = {191}, number = {2}, pages = {467--480}, year = {2012}, doi = {10.1111/j.1365-246X.2012.05642.x}, url = {http://dx.doi.org/10.1111/j.1365-246X.2012.05642.x} } @Article{Harthong2012b, title = {Contact impingement in packings of elastic–plastic spheres, application to powder compaction}, journal = {International Journal of Mechanical Sciences}, volume = {61}, number = {1}, pages = {32–43}, year = {2012}, author = {Harthong, B. and Jerier, J.-F. and Richefeu, V. and Chareyre, B. and Doremus, P. and Imbault, D. and Donzé, F.V.} } @article{Hassan2010, author = {Hassan, A. and Chareyre, B. and Darve, F. and Meyssonier, J. and Flin, F.}, title = {Microtomography-based Discrete Element Modelling of Creep in Snow}, journal = {Granular Matter}, year = {2010 (submitted)} } @article{Hasan2016, title={Evaluating Force Distributions within Virtual Uncemented Mine Backfill Using Discrete Element Method}, author={Hasan, Alsidqi and Karrech, Ali and Chareyre, Bruno}, journal={International Journal of Geomechanics}, pages={06016042}, year={2016}, publisher={American Society of Civil Engineers} } @article{Hadda2015, title={Microstructural self-organization in granular materials during failure}, author={Hadda, N. and Nicot, F. and Wan, R. and Darve, F.}, journal={Comptes Rendus Mécanique}, year={2015}, publisher={Elsevier} } @article{Hilton2013, title = {Drag force on a spherical intruder in a granular bed at low Froude number}, author = {Hilton, J. E. and Tordesillas, A.}, journal = {Phys. Rev. E}, volume = {88}, number = {6}, pages = {062203}, numpages = {8}, year = {2013}, month = {Dec}, publisher = {American Physical Society}, doi = {10.1103/PhysRevE.88.062203}, url = {http://link.aps.org/doi/10.1103/PhysRevE.88.062203} } @article{Jerier2009, author = {Jerier, J.-F. and Imbault, D.and Donzé, F.V. and Doremus, P.}, doi = {10.1007/s10035-008-0116-0}, journal = {Granular Matter}, year={2009}, volume={11}, title = {A geometric algorithm based on tetrahedral meshes to generate a dense polydisperse sphere packing}, } @article{Jerier2010a, author = {Jerier, J.-F. and Richefeu, V. and Imbault, D. and Donzé, F.V.}, doi = {10.1016/j.cma.2010.01.016}, issn = {00457825}, journal = {Computer Methods in Applied Mechanics and Engineering}, title = {Packing spherical discrete elements for large scale simulations}, year = {2010} } @article{Jerier2010b, title = {Study of cold powder compaction by using the discrete element method}, journal = {Powder Technology}, volume = {In Press}, year = {2010}, doi = {10.1016/j.powtec.2010.08.056}, author = {Jerier, J.-F. and Hathong, B. and Richefeu, V. and Chareyre, B. and Imbault, D. and Donzé, F.-V. and Doremus, P.} } @article{Kozicki2006a, title = {2{D} Lattice Model for Fracture in Brittle Materials}, author = {Kozicki, J. and Tejchman, J.}, journal = {Archives of Hydro-Engineering and Environmental Mechanics}, pages = {71--88}, volume = {53}, number = {2}, year = {2006}, url={https://yade-dem.org/w/images/5/54/Ahem_2006_kozicki.pdf} } @article{Kozicki2007a, title = {Effect of aggregate structure on fracture process in concrete using 2D lattice model”}, author = {Kozicki, J. and Tejchman, J.}, journal = {Archives of Mechanics}, pages = {365--384}, volume = {59}, number = {4--5}, year = {2007}, url={https://yade-dem.org/w/images/0/09/Ams_2007_kozicki_tejchman.pdf} } @article{Kozicki2008, author = {Kozicki, J. and Donzé, F.V.}, title = {A new open-source software developed for numerical simulations using discrete modeling methods}, journal = {Computer Methods in Applied Mechanics and Engineering}, volume = {197}, number = {49-50}, pages = {4429--4443}, year = {2008}, issn = {0045-7825}, doi = {10.1016/j.cma.2008.05.023}, url={https://yade-dem.org/w/images/3/30/CMAME_YADE_2008.pdf} } @article{Kozicki2009, title={YADE-OPEN DEM: an open-source software using a discrete element method to simulate granular material}, author={Kozicki, J. and Donzé, F.V.}, journal={Engineering Computations}, year={2009}, volume={26}, number={7}, pages={786--805}, url={https://yade-dem.org/w/images/8/80/EC_YADE_2008.pdf}, doi={10.1108/02644400910985170}, } @article{kozicki2014, title={Discrete simulations of a triaxial compression test for sand by DEM}, author={Kozicki, Jan and Tejchman, Jacek and Mühlhaus, Hans-Bernd}, journal={International Journal for Numerical and Analytical Methods in Geomechanics}, volume={38}, number={18}, pages={1923--1952}, year={2014}, publisher={Wiley Online Library} } @article{Kunhappan2017, title={Numerical modeling of high aspect ratio flexible fibers in inertial flows}, author={Kunhappan, D and Harthong, B and Chareyre, B and Balarac, G and Dumont, PJJ}, journal={Physics of Fluids}, year={2017}, doi = {10.3390/min7040056}, url={http://aip.scitation.org/doi/abs/10.1063/1.5001514} } @Article{Lapcevic2017, author = {Lapčević, Veljko and Torbica, Slavko}, title= {Numerical Investigation of Caved Rock Mass Friction and Fragmentation Change Influence on Gravity Flow Formation in Sublevel Caving}, journal = {Minerals}, volume = {7}, year = {2017}, number = {4}, doi = {10.3390/min7040056} } @article{Linden2016, title = {Machine learning framework for analysis of transport through complex networks in porous, granular media: A focus on permeability}, author = {van der Linden, Joost H. and Narsilio, Guillermo A. and Tordesillas, Antoinette}, journal = {Phys. Rev. E}, volume = {94}, issue = {2}, pages = {022904}, numpages = {16}, year = {2016}, month = {Aug}, publisher = {American Physical Society}, doi = {10.1103/PhysRevE.94.022904}, url = {http://link.aps.org/doi/10.1103/PhysRevE.94.022904} } @Article{Lomine2013, author = {Lominé, F. and Scholtès, L. and Sibille, L. and Poullain, P.}, title = {Modelling of fluid-solid interaction in granular media with coupled LB/DE methods: application to piping erosion}, journal = {International Journal for Numerical and Analytical Methods in Geomechanics}, volume = {37}, number = {6}, pages = {577-596}, year = {2013}, doi = {10.1002/nag.1109} } @article{Mahmoodlu2016, title={Effects of Sand Compaction and Mixing on Pore Structure and the Unsaturated Soil Hydraulic Properties}, author={Mahmoodlu, MG and Raoof, A and Sweijen, T and van Genuchten, M TH}, journal={Vadose Zone}, volume={15}, year={2016}, doi={10.2136/vzj2015.10.0136} } @Article{Marzougui2015, year={2015}, journal={Granular Matter}, doi={10.1007/s10035-015-0560-6}, title={Microscopic origins of shear stress in dense fluid–grain mixtures}, url={http://arxiv.org/pdf/1504.02652}, author={Marzougui, Donia and Chareyre, Bruno and Chauchat, Julien}, pages={1-13} } @Article{Maurin2015b, author = {Maurin, R. and Chauchat, J. and Chareyre, B. and Frey, P.}, title = {A minimal coupled fluid-discrete element model for bedload transport}, journal = {Physics of Fluids}, year = {2015}, volume = {27}, number = {11}, pages = {113302}, url = {http://arxiv.org/pdf/1605.06085.pdf}, doi = {10.1063/1.4935703} } @Article{Maurin2016, author = {Maurin, R. and Chauchat, J. and Frey, P.}, title = {Dense granular flow rheology in turbulent bedload transport}, journal = {Journal of Fluid Mechanics}, year = {2016}, volume = {804}, pages = {490–512}, url = {https://arxiv.org/abs/1602.06712}, doi = {10.1017/jfm.2016.520} } @Article{Maurin2017, author = {Maurin, R. and Chauchat, J. and Frey, P.}, title = {Dense granular flow rheology in turbulent bedload transport}, journal = {Journal of Fluid Mechanics}, year = {2017}, url = {https://arxiv.org/abs/1701.02665}, } @article{Montella2016, title = {Localized fluidization in granular materials: Theoretical and numerical study}, author = {Montellà, E. P. and Toraldo, M. and Chareyre, B. and Sibille, L.}, journal = {Phys. Rev. E}, volume = {94}, issue = {5}, pages = {052905}, numpages = {14}, year = {2016}, month = {Nov}, publisher = {American Physical Society}, doi = {10.1103/PhysRevE.94.052905}, url = {http://link.aps.org/doi/10.1103/PhysRevE.94.052905} } @Article{Nicot2011, author = {Nicot, F. and Hadda, N. and Bourrier, F. and Sibille, L. and Darve, F.}, title = {Failure mechanisms in granular media: a discrete element analysis}, journal = {Granular Matter}, volume = {13}, number = {3}, pages = {255-260}, year = {2011}, doi = {10.1007/s10035-010-0242-3} } @Article{Nicot2012, author = {Nicot, F. and Sibille, L. and Darve, F.}, title = {Failure in rate-independent granular materials as a bifurcation toward a dynamic regime}, year = {2012}, journal = {International Journal of Plasticity}, volume = {29}, pages = {136-154}, doi = {10.1016/j.ijplas.2011.08.002} } @article{Nicot2013a, title={Second-order work analysis for granular materials using a multiscale approach}, author={Nicot, F. and Hadda, N. and Darve, F.}, journal={International Journal for Numerical and Analytical Methods in Geomechanics}, year={2013}, doi={10.1002/nag.2175} } @article{Nicot2013b, title = {On the definition of the stress tensor in granular media}, journal = {International Journal of Solids and Structures}, year = {2013}, doi = {10.1016/j.ijsolstr.2013.04.001}, url = {http://www.sciencedirect.com/science/article/pii/S0020768313001492}, author = {Nicot, F. and Hadda, N. and Guessasma, M. and Fortin, J. and Millet, O.} } @article{Nitka2015, title={Modelling of concrete behaviour in uniaxial compression and tension with DEM}, author={Nitka, M. and Tejchman, J.}, journal={Granular Matter}, pages={1--20}, year={2015}, publisher={Springer} } @article{Papachristos2017, title={Intensity and volumetric characterizations of hydraulically driven fractures by hydro-mechanical simulations}, author={Papachristos, E and Scholtès, L and Donzé, F.V and Chareyre, B}, journal={International Journal of Rock Mechanics and Mining Sciences}, volume={93}, pages={163--178}, year={2017}, publisher={Elsevier}, url="https://www.researchgate.net/publication/312590922_Intensity_and_volumetric_characterizations_of_hydraulically_driven_fractures_by_hydro-mechanical_simulations?ev=prf_pub" } @article{Puckett2011, title = {Local origins of volume fraction fluctuations in dense granular materials}, author = {Puckett, J.G. and Lechenault, F. and Daniels, K.E.}, journal = {Physical Review E}, volume = {83}, number = {4}, pages = {041301}, year = {2011}, doi = {10.1103/PhysRevE.83.041301}, url = {http://link.aps.org/doi/10.1103/PhysRevE.83.041301} } @article{Sayeed2011, title={Strength and Deformation Characteristics of Granular Materials under Extremely Low to High Confining Pressures in Triaxial Compression}, author={Sayeed, M.A. and Suzuki, K. and Rahman, M.M. and Mohamad, W.H.W. and Razlan, M.A. and Ahmad, Z. and Thumrongvut, J. and Seangatith, S. and Sobhan, MA and Mofiz, SA and others}, volume={11}, number={4}, journal={International Journal of Civil & Environmental Engineering IJCEE-IJENS}, year={2011} } @article{Scholtes2009a, author = {Scholtès, L. and Chareyre, B. and Nicot, F. and Darve, F.}, title = {Micromechanics of granular materials with capillary effects}, journal = {International Journal of Engineering Science}, volume = {47}, number = {1}, pages = {64--75}, year = {2009}, doi = {10.1016/j.ijengsci.2008.07.002}, url = {http://dx.doi.org/10.1016/j.ijengsci.2008.07.002} } @article{Scholtes2009b, author = {Scholtès, L. and Hicher, P.-Y. and Chareyre, B. and Nicot, F. and Darve, F.}, title = {On the capillary stress tensor in wet granular materials}, journal = {International Journal for Numerical and Analytical Methods in Geomechanics}, volume = {33}, number = {10}, pages = {1289--1313}, year = {2009}, doi = {10.1002/nag.767}, url = {http://arxiv.org/abs/1105.1013} } @article{Scholtes2009c, author = {Scholtès, L. and Chareyre, B. and Nicot, F. and Darve, F.}, title = {Discrete modelling of capillary mechanisms in multi-phase granular media}, journal = {Computer Modeling in Engineering and Sciences}, volume = {52}, number = {3}, pages = {297--318}, year = {2009}, url = {http://arxiv.org/abs/1203.1234} } @Article{Scholtes2010, author = {Scholtès, L. and Hicher, P.-Y. and Sibille, L.}, title = {Multiscale approaches to describe mechanical responses induced by particle removal in granular materials}, journal = {Comptes Rendus Mécanique}, volume = {338}, number = {10-11}, pages = {627--638}, year = {2010}, doi = {10.1016/j.crme.2010.10.003}, url = {http://dx.doi.org/10.1016/j.crme.2010.10.003} } @article{Scholtes2011, author={Scholtès, L. and Donzé, F.V. and Khanal, M.}, title={Scale effects on strength of geomaterials, case study: Coal}, journal={Journal of the Mechanics and Physics of Solids}, volume={59}, number={5}, pages={1131--1146}, year={2011}, doi={10.1016/j.jmps.2011.01.009}, url = {http://dx.doi.org/10.1016/j.jmps.2011.01.009} } @article{Scholtes2012, author = {Scholtès, L. and Donzé, F.V.}, title = {Modelling progressive failure in fractured rock masses using a 3D Discrete Element Method}, journal = {International Journal of Rock Mechanics and Mining Sciences}, volume = {52}, pages = {18--30}, year = {2012}, doi = {10.1016/j.ijrmms.2012.02.009}, url = {http://dx.doi.org/10.1016/j.ijrmms.2012.02.009} } @article{Scholtes2013, author = {Scholtès, L. and Donzé, F.V.}, title = {A {DEM} model for soft and hard rocks: Role of grain interlocking on strength}, journal = {Journal of the Mechanics and Physics of Solids}, volume = {61}, number = {2}, pages = {352--369}, year = {2013}, doi = {10.1016/j.jmps.2012.10.005}, url = {http://dx.doi.org/10.1016/j.jmps.2012.10.005} } @article{Scholtes2015a, title={Modeling wave-induced pore pressure and effective stress in a granular seabed}, author={Scholtès, L. and Chareyre, B. and Michallet, H. and Catalano, E. and Marzougui, D.}, journal={Continuum Mechanics and Thermodynamics}, volume={27}, number={1}, pages={305--323}, year={2015}, doi={http://dx.doi.org/10.1007/s00161-014-0377-2} } @article{Scholtes2015b, title={A DEM analysis of step-path failure in jointed rock slopes}, author={Scholtès, L. and Donzé, F., V.}, journal={Comptes rendus - Mécanique}, volume={343}, number={2}, pages={155--165}, year={2015}, doi={http://dx.doi.org/10.1016/j.crme.2014.11.002} } @article{Shiu2008, title={Compaction process in concrete during missile impact: a DEM analysis}, author={Shiu, W. and Donzé, F.V. and Daudeville, L.}, journal={Computers and Concrete}, volume={5}, number={4}, pages={329--342}, year={2008} } @article{Shiu2009, title={Discrete element modelling of missile impacts on a reinforced concrete target}, author={Shiu, W. and Donzé, F.V. and Daudeville, L.}, journal={International Journal of Computer Applications in Technology}, volume={34}, number={1}, pages={33--41}, year={2009}, } @article{Smilauer2006, title = {The splendors and miseries of Yade design}, author = {Václav Šmilauer}, year = {2006}, journal = {Annual Report of Discrete Element Group for Hazard Mitigation}, url={https://yade-dem.org/w/images/a/a6/Smilauer-the_splendors_and_miseries_of_yade_design-2007.pdf} } @article{Sibille2014, title = {Internal erosion in granular media: direct numerical simulations and energy interpretation}, author = {Sibille, L. and Lominé, F. and Poullain, P. and Sail, Y. and Marot, D.}, year = {2014}, journal = {Hydrological Processes}, note = {First published online Oct. 2014}, doi={10.1002/hyp.10351}, url={http://dx.doi.org/10.1002/hyp.10351} } @article{Sibille2015, title = {Granular plasticity, a contribution from discrete mechanics}, author = {Sibille, L. and Hadda, N. and Nicot, F. and Tordesillas, A. and Darve, F.}, year = {2015}, journal = {Journal of the Mechanics and Physics of Solids}, volume={75}, pages={119--139}, doi={10.1016/j.jmps.2014.09.010}, url={http://dx.doi.org/10.1016/j.jmps.2014.09.010} } @Article{Suhr2016a, Title = {On the effect of stress dependent interparticle friction in direct shear tests}, Author = {Bettina Suhr and Klaus Six}, Journal = {Powder Technology }, Year = {2016}, Pages = {211 - 220}, Volume = {294}, Doi = {http://dx.doi.org/10.1016/j.powtec.2016.02.029} } @Article{Suhr2016b, Title = {Friction phenomena and their impact on the shear behaviour of granular material}, Author = {Suhr, Bettina and Six, Klaus}, Journal = {Computational Particle Mechanics}, Year = {2016}, Doi = {10.1007/s40571-016-0119-2}, ISSN = {2196-4386} } @article{Sweijen2016, title={The effects of swelling and porosity change on capillarity: DEM coupled with a pore-unit assembly method}, author={Sweijen, T and Nikooee, E and Hassanizadeh, S.M and Chareyre, B}, journal={Transport in porous media}, volume={113}, number={1}, pages={207--226}, year={2016}, doi={10.1007/s11242-016-0689-8} } @article{Sweijen2017, title={Grain-scale modelling of swelling granular materials; application to super absorbent polymers}, author={Sweijen, T and Chareyre, B and Hassanizadeh, SM and Karadimitriou, NK}, journal={Powder Technology}, volume={318}, pages={411--422}, year={2017}, doi={10.1016/j.powtec.2017.06.015} } @article{Sweijen2017b, title={Capillary pressure-saturation relationships for porous granular materials: Pore morphology method vs. pore unit assembly method}, author={Sweijen, T and Aslannejad, H and Hassanizadeh, SM}, journal={Advances in Water Resources}, volume={107}, pages={22-31}, year={2017}, doi={10.1016/j.advwatres.2017.06.001} } @article{Tejada2016, author={I. G. Tejada and L. Sibille and B. Chareyre}, title={Role of blockages in particle transport through homogeneous granular assemblies}, journal={EPL (Europhysics Letters)}, volume={115}, number={5}, pages={54005}, url={http://stacks.iop.org/0295-5075/115/i=5/a=54005}, year={2016} } @article{Thoeni2013, author = {K. Thoeni and C. Lambert and A. Giacomini and S.W. Sloan}, doi = {10.1016/j.compgeo.2012.10.014}, issn = {0266-352X}, journal = {Computers and Geotechnics}, pages = {158--169}, title = {{Discrete modelling of hexagonal wire meshes with a stochastically distorted contact model}}, url = {http://www.sciencedirect.com/science/article/pii/S0266352X12002121}, volume = {49}, year = {2013} } @article{Thoeni2014, author = {K. Thoeni and A. Giacomini and C. Lambert and S.W. Sloan and J.P. Carter}, doi = {10.1016/j.ijrmms.2014.02.008}, issn = {1365-1609}, journal = {International Journal of Rock Mechanics and Mining Sciences}, pages = {107--119}, title = {{A 3D discrete element modelling approach for rockfall analysis with drapery systems}}, url = {http://www.sciencedirect.com/science/article/pii/S1365160914000513}, volume = {68}, year = {2014} } @article{Tong2012, author = {Tong, A.-T. and Catalano, E. and Chareyre, B.}, title = {Pore-Scale Flow Simulations: Model Predictions Compared with Experiments on Bi-Dispersed Granular Assemblies}, doi= {10.2516/ogst/2012032}, url= {http://dx.doi.org/10.2516/ogst/2012032}, journal = {Oil & Gas Science and Technology - Rev. IFP Energies nouvelles}, year = {2012} } @article{Tran2011, title={A discrete element model of concrete under high triaxial loading}, author={Tran, V.T. and Donzé, F.V. and Marin, P.}, journal={Cement and Concrete Composites}, year={2011}, publisher={Elsevier} } @article{Tran2012, title={An Algorithm for the Propagation of Uncertainty in Soils using the Discrete Element Method}, author={Tran, V.D.H. and Meguid, M.A. and Chouinard, L.E.}, journal = {The Electronic Journal of Geotechnical Engineering}, url={http://www.ejge.com/2012/Ppr12.283alr.pdf}, year= {2012} } @article{Tran2013, title={A finite--discrete element framework for the 3D modeling of geogrid--soil interaction under pullout loading conditions}, author={Tran, V.D.H. and Meguid, M.A. and Chouinard, L.E.}, journal={Geotextiles and Geomembranes}, volume={37}, pages={1-9}, year={2013}, publisher={Elsevier}, doi = {10.1016/j.geotexmem.2013.01.003} } @article{Tran2012c, title={Discrete Element and Experimental Investigations of the Earth Pressure Distribution on Cylindrical Shafts}, author={Tran, V.D.H. and Meguid, M.A. and Chouinard, L.E.}, journal={International Journal of Geomechanics}, year={2012}, publisher={American Society of Civil Engineers}, doi = {10.1061/(ASCE)GM.1943-5622.0000277} } @article{Tran2014, title={Three-Dimensional Analysis of Geogrid-Reinforced Soil Using a Finite-Discrete Element Framework}, author={Tran, VDH and Meguid, MA and Chouinard, LE}, journal={International Journal of Geomechanics}, year={2014}, publisher={American Society of Civil Engineers} } @article{Valera2015, title={Modified algorithm for generating high volume fraction sphere packings}, author={Valera, Roberto Rosello and Morales, Irvin Perez and Vanmaercke, Simon and Morfa, Carlos Recarey and Cortes, Lucia Arguelles and Casanas, Harold Diaz-Guzman}, journal={Computational Particle Mechanics}, pages={1--12}, year={2015}, doi={10.1007/s40571-015-0045-8}, publisher={Springer} } @article{Wan2014, title={Micromechanical analysis of force transport in wet granular soils}, author={Wan, R and Khosravani, S and Pouragha, M}, journal={Vadose Zone Journal}, volume={13}, number={5}, year={2014}, doi = {10.2136/vzj2013.06.0113}, publisher={The Soil Science Society of America, Inc.} } @article{Wan2015, title = {A tensorial description of stresses in triphasic granular materials with interfaces}, journal = {Geomechanics for Energy and the Environment}, volume = {4}, pages = {73 - 87}, year = {2015}, doi = {10.1016/j.gete.2015.11.004}, author = {R. Wan and J. Duriez and F. Darve} } @article{Wang2014, title={Simulation of triaxial response of granular materials by modified DEM}, author={Wang, XiaoLiang and Li, JiaChun}, journal={Science China Physics, Mechanics \& Astronomy}, volume={57}, number={12}, pages={2297--2308}, year={2014}, publisher={Springer} } @article{Yuan2016, title={Pore-scale simulations of drainage in granular materials: finite size effects and the representative elementary volume}, author={C. Yuan and B. Chareyre and F. Darve}, journal={Adv. in Water Ressources}, volume={95}, pages={109--124}, year={2016} } @article{Yuan2017, title={A pore-scale method for hydromechanical coupling in deformable granular media}, author={C. Yuan and B. Chareyre}, journal={Computer Methods in Applied Mechanics and Engineering (in press)}, year={2017}, doi ={10.1016/j.cma.2017.02.024}, url = {https://arxiv.org/pdf/1703.02319} } @ARTICLE{Zhao2015, author = {J. Zhao and N. Guo}, title = {The interplay between anisotropy and strain localisation in granular soils: a multiscale insight}, journal = {Géotechnique}, year = {2015}, note = {under review} } @Article{Gladky2017, author="Gladkyy, Anton and Kuna, Meinhard", title="DEM simulation of polyhedral particle cracking using a combined Mohr--Coulomb--Weibull failure criterion", journal="Granular Matter", year="2017", month="May", day="12", volume="19", number="3", pages="41", issn="1434-7636", doi="10.1007/s10035-017-0731-8", url="https://doi.org/10.1007/s10035-017-0731-8" } @article{Haustein2017, title = "Discrete element modeling of deformable particles in \{YADE\} ", journal = "SoftwareX ", volume = "6", number = "", pages = "118 - 123", year = "2017", note = "", issn = "2352-7110", doi = "https://doi.org/10.1016/j.softx.2017.05.001", author = "Martin Haustein and Anton Gladkyy and Rüdiger Schwarze", } trunk-2018.02b/doc/yade-conferences.bib000066400000000000000000001170631324306050200176330ustar00rootroot00000000000000 @inproceedings{Stransky2010, title={Macroscopic elastic properties of particle models}, author={Jan Stránský and Milan Jirásek and Václav Šmilauer}, booktitle={Proceedings of the International Conference on Modelling and Simulation 2010, Prague}, year={2010}, month={June}, url={https://yade-dem.org/w/images/6/64/Stransky2010-Macroscopic-elastic-properties-of-particle-models.pdf} } @inproceedings{Smilauer2008, title={Commanding c++ with Python}, author = {Václav Šmilauer}, year={2008}, journal={ALERT Doctoral school talk}, url={https://yade-dem.org/w/images/4/40/Yade-python-aussois-2008.pdf} } @inproceedings{Smilauer2010a, title={Yade: past, present, future}, author={Václav Šmilauer}, year={2010}, month=3, booktitle={Internal seminary in Laboratoire 3S-R, Grenoble}, url={https://yade-dem.org/w/images/5/59/Eudoxos2010-yade-past-present-future.pdf}, note={`LaTeX sources `_} } @inproceedings{Chen2008a, title={Discrete Element Simulation of 1D Upward Seepage Flow with Particle-Fluid Interaction Using Coupled Open Source Software}, author={Chen, F. and Drumm, E.C. and Guiochon, G. and Suzuki, K.}, booktitle={Proceedings of The 12th International Conference of the International Association for Computer Methods and Advances in Geomechanics (IACMAG) Goa, India}, year={2008}, month={October} } @inproceedings{Chen2009b, title={3D DEM Analysis Of Graded Rock Fill Sinkhole Repair: Particle Size Effects On The Probability Of Stability}, author={Chen, F. and Drumm, E.C. and Guiochon, G.}, booktitle={Transportation Research Board Conference (Washington DC)}, year={2009}, month={January}, } @inproceedings{Dang2008a, title={An algorithm to generate a specimen for Discrete Element simulations with a predefined grain size distribution.}, author={Dang, H.K. and Mohamed, M.A.}, booktitle={61th Canadian Geotechnical Conference, Edmonton, Alberta}, year={2008}, month={September} } @inproceedings{Dang2008b, title={3D simulation of the trap door problem using the Discrete Element Method.}, author={Dang, H.K. and Mohamed, M.A.}, booktitle={61th Canadian Geotechnical Conference, Edmonton, Alberta}, year={2008}, month={September} } @inproceedings{Shiu2007a, title={Discrete element modelling of missile impacts on a reinforced concrete target}, author={W. Shiu and F.V. Donze and L. Daudeville}, booktitle={Int. Conf. on Computational Fracture and Failure of Materials and Structures (CFRAC 2007), Nantes}, year={2007}, month={June} } @inproceedings{Smilauer2007a, title={Discrete and hybrid models: Applications to concrete damage}, author={V. Šmilauer}, booktitle={Unpublished}, year={2007}, month={June}, url={https://yade-dem.org/w/images/c/c8/Smilauer-discrete_and_hybrid_models_for_concrete-2007.pdf} } @inproceedings{Kozicki2007c, title={Simulations of fracture processes in concrete using a 3D lattice model}, author={J. Kozicki and J. Tejchman}, booktitle={Int. Conf. on Computational Fracture and Failure of Materials and Structures (CFRAC 2007), Nantes}, year={2007}, month={June}, url={https://yade-dem.org/w/images/2/27/Nantes_2007_kozicki.pdf} } @inproceedings{Kozicki2007d, title={Effect of aggregate density on fracture process in concrete using 2D discrete lattice model}, author={J. Kozicki and J. Tejchman}, booktitle={Proc. Conf. Computer Methods in Mechanics (CMM 2007), Lodz-Spala}, year={2007}, month={June} } @inproceedings{Kozicki2007e, title={Modelling of a direct shear test in granular bodies with a continuum and a discrete approach}, author={J. Kozicki and J. Tejchman}, booktitle={Proc. Conf. Computer Methods in Mechanics (CMM 2007), Lodz-Spala}, year={2007}, month={June} } @inproceedings{Kozicki2007f, title={Investigations of size effect in tensile fracture of concrete using a lattice model}, author={J. Kozicki and J. Tejchman}, booktitle={Proc. Conf. Modelling of Heterogeneous Materials with Applications in Construction and Biomedical Engineering (MHM 2007), Prague}, year={2007}, pages={246--249}, month={June} } @inproceedings{Kozicki2006b, title={Modelling of fracture process in brittle materials using a lattice model}, author={J. Kozicki and J. Tejchman}, booktitle={Computational Modelling of Concrete Structures, EURO-C (eds.: G. Meschke, R. de Borst, H. Mang and N. Bicanic), Taylor anf Francis}, year={2006}, pages={139--145} } @inproceedings{Kozicki2006c, title={Lattice type fracture model for brittle materials}, author={J. Kozicki and J. Tejchman}, booktitle={35th Solid Mechanics Conference (SOLMECH 2006), Krakow}, year={2006}, pages={215--216}, month={September} } @inproceedings{Kozicki2005b, title={Simulations of fracture in concrete elements using a discrete lattice model}, author={J. Kozicki and J. Tejchman}, booktitle={Proc. Conf. Computer Methods in Mechanics (CMM 2005), Czestochowa, Poland}, year={2005}, month={June} } @inproceedings{Kozicki2005c, title={Simulation of the crack propagation in concrete with a discrete lattice model}, author={J. Kozicki and J. Tejchman}, booktitle={Proc. Conf. Analytical Models and New Concepts in Concrete and Masonry Structures (AMCM 2005), Gliwice, Poland}, year={2005}, month={June} } @inproceedings{Kozicki2004a, title={Study of Fracture Process in Concrete using a Discrete Lattice Model}, author={J. Kozicki and J. Tejchman}, booktitle={CURE Workshop, Simulations in Urban Engineering, Gdansk}, year={2004}, month={September} } @inproceedings{Kozicki2003a, title={Discrete methods to describe the behaviour of quasi-brittle and granular materials}, author={J. Kozicki and J. Tejchman}, booktitle={16th Engineering Mechanics Conference, University of Washington, Seattle, CD--ROM}, year={2003}, month={July} } @inproceedings{Kozicki2003c, title={Lattice method to describe the behaviour of quasi-brittle materials}, author={J. Kozicki and J. Tejchman}, booktitle={CURE Workshop, Effective use of building materials, Sopot}, year={2003}, pages={131--134}, month={October} } @inproceedings{Scholtes2008a, title={Capillary Effects Modelling in Unsaturated Granular Materials}, author={L. Scholtès and B. Chareyre and F. Nicot and F. Darve}, booktitle={8th World Congress on Computational Mechanics - 5th European Congress on Computational Methods in Applied Sciences and Engineering, Venice}, year={2008}, month={July} } @inproceedings{Scholtes2009e, title={Micromechanics of partialy saturated granular material}, author={Scholtes L, Chareyre B, Darve F}, booktitle={Int. Conf. on Particle Based Methods, ECCOMAS-Particles}, year={2009}, month={July} } @inproceedings{Chareyre2009, title={Micro-statics and micro-kinematics of capillary phenomena in dense granular materials}, author={Chareyre B. and Scholtès L.}, Booktitle = {{POWDERS AND GRAINS 2009: Proceedings of the 6th International Conference on Micromechanics of Granular Media. AIP Conference Proceedings}}, year={2009}, month={July}, doi = {10.1063/1.3180083}, pages={927--930} } @inproceedings{Gillibert2009, title={Curvature-driven grain segmentation: application to snow images from X-ray microtomography}, author={Gillibert L. and Flin F. and Rolland du Roscoat S. and Chareyre B. and Philip A. and Lesaffre B. and Meyssonier J.}, booktitle={IEEE Computer Society Conference on Computer Vision and Pattern Recognition (Miami, USA)}, year={2009}, month={June}, pages={927--930} } @inproceedings{Hicher2009, title={On the capillary stress tensor in wet granular materials}, author={Hicher P.-Y. and Scholtès L. and Chareyre B. and Nicot F. and Darve F.}, booktitle={Inaugural International Conference of the Engineering Mechanics Institute (EM08) - (Minneapolis, USA)}, year={2008}, month={May} } @inproceedings{Scholtes2008b, title={On the Capillary Stress Tensor in unsaturated granular materials}, author={L. Scholtès and P.-Y. Hicher and F.Nicot and B. Chareyre and F. Darve}, booktitle={EM08: Inaugural International Conference of the Engineering Mechanics Institute, Minneapolis}, year={2008}, month={April} } @inproceedings{Scholtes2007a, title={Micromechanical Modelling of Unsaturated Granular Media}, author={L. Scholtès and B. Chareyre and F. Nicot and F. Darve}, booktitle={Proceedings ECCOMAS-MHM07, Prague}, year={2007}, month={June} } @InProceedings{ Catalano2009, title = {Fluid-solid coupling in discrete models}, author = {E. Catalano, B. Chareyre, E. Barthélémy}, month = {Oct}, year = {2009}, booktitle = {Alert Geomaterials Workshop 2009}, address = {Aussois, France} } @InProceedings{ Catalano2010a, title = {A coupled model for fluid-solid interaction analysis in geomaterials}, author = {E. Catalano, B. Chareyre, E. Barthélémy}, month = {Oct}, year = {2010}, booktitle = {Alert Geomaterials Workshop 2010}, address = {Aussois, France} } @InProceedings{ Catalano2010b, title = {Pore scale modelling of Stokes flow}, author = {E. Catalano and B. Chareyre and E. Barthélémy}, editor = {GdR MeGe}, month = {Dec}, year = {2010}, booktitle = {GdR MeGe}, address = {Nantes, France} } @InProceedings{ Catalano2011a, title = {A Pore-Scale Hydro-Mechanical coupled model for geomaterials}, author = {E. Catalano and B. Chareyre and A. Cortis and E. Barthélémy}, editor = {E. Onate and D.R.J. Owen}, month = {Nov}, year = {2011}, booktitle = {II International Conference on Particle-based Methods - Fundamentals and Applications}, address = {Barcelona, Spain}, pages = {1--12}, url={http://people.3sr-grenoble.fr/users/bchareyre/pubs/CatalanoChareyreCortisBarthelemy_Particles2011.pdf} } @InProceedings{ Sari2011, title = {Investigation of Internal Erosion Processes using a Coupled DEM-Fluid Method}, author = {H. Sari and B. Chareyre and E. Catalano and P. Philippe and E. Vincens}, editor = {E. Onate and D.R.J. Owen}, month = {Nov}, year = {2011}, booktitle = {II International Conference on Particle-based Methods - Fundamentals and Applications}, address = {Barcelona, Spain}, pages = {1--11}, url={http://people.3sr-grenoble.fr/users/bchareyre/pubs/SariChareyreCatalanoPhilippeVincens_Particles2011.pdf} } @InProceedings{Favier2009b, author = {L. Favier and D. Daudon and F. Donzé and J. Mazars}, title = {Validation of a DEM granular flow model aimed at forecasting snow avalanche pressure}, year = {2009}, journal = {AIP Conference Proceedings}, volume = {1145}, number = {1}, pages = {617-620}, url = {http://link.aip.org/link/?APC/1145/617/1}, doi = {10.1063/1.3180002} } @InProceedings{ Thoeni2011, title = {Discrete Modelling of a Rockfall Protective System}, booktitle = {Particles 2011: Fundamentals and Applications}, series = {II International Conference on Particle-based Methods}, author = {K. Thoeni and C. Lambert and A. Giacomini and S.W. Sloan}, editor = {E. Onate and D.R.J. Owen}, location = {Barcelona, Spain}, year = {2011}, isbn = {978-84-89925-69-4}, url = {https://yade-dem.org/w/images/7/7d/ThoenietAlParticles2011.pdf} } @InProceedings{ Chareyre2011, title = {Numerical simulation of hydromechanical couplings by combined discrete element method and finite-volumes}, author = {B. Chareyre and E. Catalano and E. Barthélémy}, year = {2011}, booktitle = {International Conference on Flows and Mechanics in Natural Porous Media from Pore to Field Scale - Pore2Field}, address = {Rueil-Malmaison, France}, pages = {4p}, url = {http://people.3sr-grenoble.fr/users/bchareyre/pubs/IFPEN-Pore2Field-Chareyre_et_al_Meta.pdf} } @InProceedings{ Scholtes2011b, title = {Progressive failure mechanisms in jointed rock: insight from 3D DEM modelling}, booktitle = {II International Conference on Particle-based Methods - Fundamentals and Applications}, author = {L. Scholtès and F. Donzé}, editor = {E. Onate and D.R.J. Owen}, location = {Barcelona, Spain}, year = {2011}, isbn = {978-84-89925-69-4}, url = {https://yade-dem.org/w/images/3/32/ScholtesDonzeParticles2011.pdf} } @InProceedings{ Kozicki2011, title = {Numerical simulation of sand behaviour using DEM with two different descriptions of grain roughness}, booktitle = {II International Conference on Particle-based Methods - Fundamentals and Applications}, author = {J. Kozicki and J. Tejchman}, editor = {E. Onate and D.R.J. Owen}, location = {Barcelona, Spain}, year = {2011}, isbn = {978-84-89925-69-4}, url = {https://yade-dem.org/w/images/9/96/KozickyTejchmanParticles2011.pdf} } @InProceedings{ Stransky2011, title = {Calibration of particle-based models using cells with periodic boundary conditions}, booktitle = {II International Conference on Particle-based Methods - Fundamentals and Applications}, author = {J. Stransky and M. Jirasek}, editor = {E. Onate and D.R.J. Owen}, location = {Barcelona, Spain}, year = {2011}, isbn = {978-84-89925-69-4}, url = {http://yade-dem.org/publi/Stransky2011Barcelona_meta.pdf} } @InProceedings{Lomine2011, title = {A coupled discrete element - lattice Botzmann method to investigate internal erosion in soil}, booktitle = {Proc. 2nd Int. Symposium on Computational Geomechanics (ComGeo II)}, author = {Lominé, F. and Sibille, L. and Marot, D.}, editor = {Pietruszczak & Pande}, address = {Cavtat-Dubrovnik, Croatia}, year = {2011}, pages = {364-372}, } @InProceedings{Lomine2010a, title = {Soil microstructure changes induced by internal fluid flow: investigations with coupled DE/LB methods}, booktitle = {Proc. of 3rd Euromediterranean Symposium on Advances in Geomaterials and Structures, AGS'10}, author = {Lominé, F. and Scholtès, L. and Poullain, P. and Sibille, L.}, editor = {Pietruszczak & Pande}, address = {Djerba, Tunisia}, year = {2010}, } @InProceedings{Nicot2011b, title = {A discrete element analysis of collapse mechanisms in granular materials}, booktitle = {Proc. 2nd Int. Symposium on Computational Geomechanics (ComGeo II)}, author = {Nicot, F. and Hadda, N. and Bourrier, F. and Sibille, L. and Darve, F.}, editor = {Pietruszczak & Pande}, address = {Cavtat-Dubrovnik, Croatia}, year = {2011}, pages = {364-372}, } @InProceedings{Scholtes2011c, title = {A micromechanical approach to describe internal erosion effects in soils}, booktitle = {Proc. of Geomechanics and Geotechnics: from micro to macro, IS-Shanghai 2011}, author = {Scholtès, L. and Hicher, P.Y. and Sibille, L.}, editor = {Jiang et al.}, publisher = {Taylor & Francis Group}, address = {Shanghai, China}, year = {2011}, pages = {501-505}, } @InProceedings{Sibille2009, title = {Effects of internal erosion on mechanical behaviour of soils: a dem approach}, booktitle = {Proc. of International Conference on Particle-Based Methods, Particles 2009}, author = {Sibille, L. and Scholtès, L.}, editor = {E. Õnate and D.R.J. Owen}, address = {Barcelone, Spain}, year = {2009}, pages = {167-170}, } @InProceedings{Hicher2011, title = {Multiscale Modeling of Particle Removal Impact on Granular Material Behavior}, booktitle = {Engineering Mechanics Institute, EMI 2011}, author = {Hicher, P.Y and Scholtès, L. and Sibille, L.}, address = {Boston, USA}, year = {2011}, } @InProceedings{Nicot2010, title = {Multiscale modeling of instability in granular materials}, booktitle = {Engineering Mechanics Institute, EMI 2010}, author = {Nicot, F. and Sibille, L. and Daouadji, A. and Hicher, P.Y. and Darve, F.}, address = {Los Angeles, USA}, year = {2010}, } @InProceedings{Lomine2010b, title = {Modelling of fluid-solid interaction in granular media with coupled LB/DE methods: application to solid particle detachment under hydraulic loading}, booktitle = {19th Discrete Simulation of Fluid Dynamics, DSFD 2010}, author = {Lominé, F. and Poullain, P. and Sibille, L.}, address = {Roma, Italy}, year = {2010}, } @InProceedings{Modenese2012, author = "C. Modenese and S. Utili and G.T. Houlsby", title = "DEM Modelling of Elastic Adhesive Particles with Application to Lunar Soil", booktitle = "Earth and Space 2012: Engineering, Science, Construction, and Operations in Challenging Environments ¬© 2012 ASCE", year = "2012", url={http://ascelibrary.org/doi/abs/10.1061/9780784412190.006}, doi={10.1061/9780784412190.006} } @incollection{Modenese2012a, author = {Modenese, C and Utili, S and Houlsby, G T}, editor = {Wu, Chuan-Yu}, title = {{A study of the influence of surface energy on the mechanical properties of lunar soil using DEM}}, booktitle = {{Discrete Element Modelling of Particulate Media}}, publisher = {Royal Society of Chemistry}, year = {2012}, series = {Special Publication}, doi = {10.1039/9781849735032-00069}, url = {http://pubs.rsc.org/en/content/chapter/bk9781849733601-00069/978-1-84973-503-2} } @incollection{Modenese2012b, author = {Modenese, C and Utili, S and Houlsby, G T}, editor = {Wu, Chuan-Yu}, title = {{A numerical investigation of quasi-static conditions for granular media}}, booktitle = {{Discrete Element Modelling of Particulate Media}}, publisher = {Royal Society of Chemistry}, year = {2012}, series = {Special Publication}, doi = {10.1039/9781849735032-00187}, url = {http://pubs.rsc.org/en/content/chapter/bk9781849733601-00187/978-1-84973-503-2} } @incollection{Chareyre2012b, title = {The properties of some stress tensors investigated by DEM simulations}, author = {B. Chareyre and L. Scholtès and F. Darve}, month = {Aug}, year = {2012}, booktitle = {Euromech Colloquium 539; Mechanics of Unsaturated Porous Media: Effective Stress principle; from micromechanics to thermodynamics}, address = {Utrecht, Netherlands}, url = {http://www.geo.uu.nl/hydrogeology/Colloquium2012/Bruno%20Chareyre_Euromech.pdf} } @incollection{Chareyre2012c, title = {Micro-poromechanics: recent advances in numerical models and perspectives}, author = {B. Chareyre}, booktitle = {ICACM symposium 2012, The role of structure on emerging material properties}, year = {2012}, month = {June}, address = {New York, USA} } @incollection{Michallet2012, title = {Physical and numerical modelling of sand liquefaction in waves interacting with a vertical wall}, author = {H. Michallet and E. Catalano and C. Berni and V. Rameliarison and E. Barthélémy}, month = {Aug}, year = {2012}, booktitle = {ICSE6 - 6th International conference on Scour and Erosion}, address = {Paris, France} } @incollection{Hasan2010b, title = {Microtomography-based Discrete Element Modeling to Simulate Snow Microstructure Deformation}, author = {A. Hasan and B. Chareyre and J. Kozicki and F. Flin and F. Darve and J. Meyssonnier}, booktitle = {AGU Fall Meeting Abstracts}, volume = {1}, pages = {0566}, year = {2010} } @incollection{Tran2012b, title={Coupling of Random Field Theory and the Discrete Element Method in the Reliability Analysis of Geotechnical Problems}, author={Tran, V.D.H and Meguid, M.A and Chouinard, L.E}, booktitle={Canadian Society for Civil Engineering (CSCE) Annual Conference 2012, Edmonton}, year={2012}, month={May} } @incollection{Tran2012b, title={A Discrete Element Study of the Earth Pressure Distribution on Cylindrical Shafts}, author={Tran, V.D.H and Meguid, M.A and Chouinard, L.E}, booktitle={Tunnelling Association of Canada (TAC) Conference 2012, Montreal}, year={2012}, month={October} } @InProceedings{ Harthong2013, title = {Directional plastic flow and fabric dependencies in granular materials}, author = {Barthélémy Harthong and Richard G Wan}, Booktitle = {{Powders and Grains 2013: Proceedings of the 6th International Conference on Micromechanics of Granular Media. AIP Conference Proceedings}}, volume = {1542}, year = {2009}, number = {1}, month = {July}, doi = {http://dx.doi.org/10.1063/1.4811900}, pages = {193–197}, address = {Sydney, Australia}, url = {https://www.yade-dem.org/publi/APC000193.pdf} } @InProceedings{ Hilton2013b, author = {J. E. Hilton and P. W. Cleary and A. Tordesillas}, title = {Unitary stick-slip motion in granular beds}, publisher = {AIP}, year = {2013}, Booktitle = {{Powders and Grains 2013: Proceedings of the 6th International Conference on Micromechanics of Granular Media. AIP Conference Proceedings}}, volume = {1542}, number = {1}, pages = {843–846}, address = {Sydney, Australia}, url = {http://scitation.aip.org/getpdf/servlet/GetPDFServlet?filetype=pdf&id=APCPCS001542000001000843000001&idtype=cvips&doi=10.1063/1.4812063&prog=normal}, doi = {10.1063/1.4812063} } @InProceedings{ Catalano2013b, author = {Catalano E. and Chareyre B. and Barthélémy E.}, title = {DEM-PFV analysis of solid-fluid transition in granular sediments under the action of waves}, publisher = {AIP}, year = {2013}, Booktitle = {{Powders and Grains 2013: Proceedings of the 6th International Conference on Micromechanics of Granular Media. AIP Conference Proceedings}}, volume = {1542}, number = {1}, pages = {1063–1066}, address = {Sydney, Australia}, url = {http://link.aip.org/link/?APC/1542/1063/1}, doi = {10.1063/1.4812118} } @InProceedings{ Guo2013, author = {Ning Guo and Jidong Zhao}, title = {A hierarchical model for cross-scale simulation of granular media}, publisher = {AIP}, year = {2013}, Booktitle = {{Powders and Grains 2013: Proceedings of the 6th International Conference on Micromechanics of Granular Media. AIP Conference Proceedings}}, volume = {1542}, number = {1}, pages = {1222–1225}, address = {Sydney, Australia}, url = {https://www.yade-dem.org/publi/APC001222.pdf}, doi = {10.1063/1.4812158} } @InProceedings{ Hadda2013b, author = {Nejib Hadda and François Nicot and Luc Sibille and Farhang Radjai and Antoinette Tordesillas and Félix Darve}, title = {A multiscale description of failure in granular materials}, publisher = {AIP}, year = {2013}, Booktitle = {{Powders and Grains 2013: Proceedings of the 6th International Conference on Micromechanics of Granular Media. AIP Conference Proceedings}}, volume = {1542}, number = {1}, pages = {585–588}, address = {Sydney, Australia}, url = {https://www.yade-dem.org/publi/APC000585.pdf}, doi = {10.1063/1.4811999} } @InProceedings{ Kozicki2013, author = {Jan Kozicki and Jacek Tejchman and Danuta Lesniewska}, title = {Study of some micro-structural phenomena in granular shear zones}, publisher = {AIP}, year = {2013}, Booktitle = {{Powders and Grains 2013: Proceedings of the 6th International Conference on Micromechanics of Granular Media. AIP Conference Proceedings}}, volume = {1542}, number = {1}, pages = {495–498}, address = {Sydney, Australia}, url = {https://www.yade-dem.org/publi/APC000495.pdf}, doi = {10.1063/1.4811976} } @InProceedings{Maurin2013, title={Discrete element modelling of bedload transport}, author={R. Maurin and B. Chareyre and J. Chauchat and P. Frey}, booktitle={Proceedings of THESIS 2013, Two-pHase modElling for Sediment dynamIcS in Geophysical Flows}, adress = {Chatou, France}, pages={6p}, url = {https://www.yade-dem.org/publi/Maurin_et_al_THESIS_meta.pdf}, year={2013} } @InProceedings{Maurin2015, title={On granular rheology in bedload transport}, author={R. Maurin and B. Chareyre and J. Chauchat and P. Frey}, booktitle={CFM-2015}, adress = {Lyon, France}, pages={9p}, url = {http://documents.irevues.inist.fr/bitstream/handle/2042/57322/68709.pdf}, year={2015} } @InProceedings{Maurin2016, title={Dense granular flow rheology in turbulent bedload transport}, author={R. Maurin and J. Chauchat and P. Frey}, booktitle={Proceedings of THESIS 2017, Two-pHase modElling for Sediment dynamIcS in Geophysical Flows}, adress = {Tokyo, Japan}, pages={6p}, year={2016} } @InProceedings{Frey2017, title={Vertical size segregation numerical experiments in bedload sediment transport with a coupled fluid-discrete element model}, author={P. Frey and R. Maurin and L. Morchid-Alaoui and S. Gupta and J. Chauchat}, booktitle={Proceedings of Powder and Grains 2017}, adress = {Montpellier, France}, pages={4p}, url = {www.epj-conferences.org/articles/epjconf/pdf/2017/09/epjconf162329.pdf}, year={2017} } @InProceedings{Elias2013, author={Eliáš, J.}, title={Dem simulation of railway ballast using polyhedral elemental shapes}, journal={Particle-Based Methods III: Fundamentals and Applications - Proceedings of the 3rd International Conference on Particle-based MethodsFundamentals and Applications, Particles 2013}, year={2013}, pages={247-256}, url={https://www.vutbr.cz/www_base/gigadisk.php?i=95194aa9a}, document_type={Conference Paper}, source={Scopus}, } @InProceedings{Yuan2014, author={Chao Yuan and Bruno Chareyre and Félix Darve}, title={Pore-Scale Simulations of Drainage for Two-phase Flow in Dense Sphere Packings}, journal={XX . International Conference on Computational Methods in Water Resources}, year={2014}, pages={232}, url={http://cmwr14.de/images/bookofabstracts/CMWR14BookofAbstracts.pdf} } @InProceedings{Sweijen2014, author={Thomas Sweijen and Majid Hassanizadeh and Bruno Chareyre}, title={Pore-Scale modeling of swelling porous media; application to super absorbent polymers}, journal={XX . International Conference on Computational Methods in Water Resources}, year={2014}, pages={227}, url={http://cmwr14.de/images/bookofabstracts/CMWR14BookofAbstracts.pdf} } @InProceedings{Effeindzourou2015, author = {A. Effeindzourou and K. Thoeni and A. Giacomini and S.W. Sloan}, booktitle = {{Computer Methods and Recent Advances in Geomechanics}}, editor = {{F. Oka and A. Murakami and R. Uzuoka and and S. Kimoto}}, pages = {1083--1088}, publisher = {Taylor and Francis}, series = {{14th International Conference of the IACMAG}}, title = {{A discrete model for rock impacts on muckpiles}}, url = {http://dx.doi.org/10.1201/b17435-189}, year = {2015} } @inproceedings{Effeindzourou2015a, author = {Effeindzourou, A. and Thoeni, K. and Chareyre, B. and Giacomini, A.}, booktitle = {{Particle-Based Methods IV: Fundamentals and Applications}}, location = {Barcelona, Spain}, pages = {744--754}, series = {{4th International Conference on Particle-based Methods}}, title = {{A general method for modelling deformable structures in DEM}}, year = {2015} } @InCollection{Thoeni2015, author = {K. Thoeni and A. Giacomini and C. Lambert and S.W. Sloan}, booktitle = {{Engineering Geology for Society and Territory - Volume 2}}, doi = {10.1007/978-3-319-09057-3_356}, editor = {G. Lollino and D. Giordan and G.B. Crosta and J. Corominas and R. Azzam and J. Wasowski and N. Sciarra}, isbn = {978-3-319-09056-6}, pages = {2007--2011}, publisher = {Springer}, title = {{Rockfall Trajectory Analysis with Drapery Systems}}, url = {http://dx.doi.org/10.1007/978-3-319-09057-3_356}, year = {2015} } @Proceedings{1stYadeWorkshop, pages = {565}, editor = {B. Chareyre}, series = {{1st Yade Workshop - Grenoble}}, title = {{Booklet of presentations of the 1st Yade Workshop}}, url = {https://yade-dem.org/publi/1stWorkshop/booklet.pdf}, year = {2014} } @InProceedings{Kozicki2005a, title = {Discrete lattice model used to describe the fracture process of concrete}, author = {Kozicki, J.}, journal = {Discrete Element Group for Risk Mitigation Annual Report 1, Grenoble University of Joseph Fourier, France}, pages = {95--101}, year = {2005}, url={https://yade-dem.org/w/images/f/f0/Discrete_lattice_model_DEM_risk_mitigation_kozicki.pdf} } @article{Tejchman2011, title={Comparative Modelling of Shear Zone Patterns in Granular Bodies with Finite and Discrete Element Model}, author={Tejchman, J.}, journal={Advances in Bifurcation and Degradation in Geomaterials}, pages={255--260}, year={2011} } @InProceedings{Nitka2015b, title={Discrete Modelling of Micro-structural Phenomena in Granular Shear Zones}, author={Nitka, Michal and Tejchman, Jacek and Kozicki, Jan}, booktitle={Bifurcation and Degradation of Geomaterials in the New Millennium}, pages={7--12}, year={2015}, publisher={Springer} } @InProceedings{Bonilla2014, title={Coupling photogrammetric data with a discrete element model for rock slope stability assessment}, author={Bonilla-Sierra, V and Donzé, FV and Scholtès, L and Elmouttie, M}, booktitle={Rock Engineering and Rock Mechanics: Structures in and on Rock Masses}, pages={433}, year={2014}, publisher={CRC Press} } @InProceedings{Skarzynski2014, title={Two-scale model for concrete beams subjected to three point bending—numerical analyses and experiments}, author={Skarzynski, M Nitka and Tejchman, J}, booktitle={Computational Modelling of Concrete Structures}, volume={1}, pages={149}, year={2014}, publisher={CRC Press} } @InProceedings{Nitka2014, title={Discrete modeling of micro-structure evolution during concrete fracture using DEM}, author={Nitka, M and Tejchman, J}, booktitle={Computational Modelling of Concrete Structures}, volume={1}, pages={345}, year={2014}, publisher={CRC Press} } @InProceedings{Sayeed2014, title={Mechanical Behavior of Granular Materials Considering Confining Pressure Dependency}, author={Sayeed, Md Abu and Sazzad, Md Mahmud and Suzuki, Kiichi}, booktitle={GeoCongress 2012}, volume={10}, pages={9780784412121--225}, year={2014} } @InProceedings{Nicot2015, title={Micromechanical Analysis of Second Order Work in Granular Media}, author={Nicot, F and Hadda, N and Bourrier, F and Sibille, L and Tordesillas, A and Darve, F}, booktitle={Bifurcation and Degradation of Geomaterials in the New Millennium}, pages={67--74}, year={2015}, publisher={Springer} } @InProceedings{Albaba2015, title={Modeling the Impact of Granular Flow against an Obstacle}, author={Albaba, Adel and Lambert, Stéphane and Nicot, François and Chareyre, Bruno}, booktitle={Recent Advances in Modeling Landslides and Debris Flows}, pages={95--105}, year={2015}, publisher={Springer} } @InProceedings{Hadda2015b, title={A microstructural cluster-based description of diffuse and localized failures}, author={Hadda, N and Bourrier, F and Sibille, L and Nicot, F and Wan, R and Darve, F}, booktitle={Geomechanics from Micro to Macro: Proceedings of the International Symposium on Geomechanics from Micro and Macro (IS-Cambridge 2014)}, pages={169-174}, year={2015}, publisher={CRC Press/Balkema} } @InProceedings{Bourrier2015b, title={How Can Reliability-Based Approaches Improve the Design of Rockfall Protection Fences?}, author={Bourrier, Franck and Baroth, Julien and Lambert, Stéphane}, booktitle={Engineering Geology for Society and Territory-Volume 2}, pages={1985--1988}, year={2015}, publisher={Springer} } @InProceedings{Wan2015b, title={Plastic Deformations in Granular Materials with Rotation of Principal Stress Axes}, author={Wan, Richard and Hadda, Nejib}, booktitle={Bifurcation and Degradation of Geomaterials in the New Millennium}, pages={305--310}, year={2015}, publisher={Springer} } @InProceedings{Li2014, title={Constrictions and filtration of fine particles in numerical granular filters: Influence of the fabric within the material}, author={Li, W and Vincens, E and Reboul, N and Chareyre, B}, booktitle={Scour and Erosion: Proceedings of the 7th International Conference on Scour and Erosion, Perth, Australia, 2-4 December 2014}, pages={241}, year={2014}, organization={CRC Press} } @InProceedings{Uhlmann2014, title={Investigation of material removal and surface topography formation in vibratory finishing}, author={Uhlmann, Eckart and Dethlefs, Arne and Eulitz, Alexander}, journal={Procedia CIRP}, volume={14}, pages={25--30}, year={2014}, publisher={Elsevier} } @InProceedings{Nitka2014b, title={Discrete modeling of micro-structure evolution during concrete fracture using DEM}, author={Nitka, M and Tejchman, J}, journal={Computational Modelling of Concrete Structures}, volume={1}, pages={345}, year={2014}, publisher={CRC Press} } @InProceedings{Nitka2014c, title={Effect of mean grain diameter on vortices, force chains and local volume changes in granular shear zones}, author={Nitka, M and Tejchman, J and Kozicki, J and Lesniewska, D}, journal={Digital Humanitarians: How Big Data Is Changing the Face of Humanitarian Response}, pages={177}, year={2014}, publisher={CRC Press} } @incollection{Guo2014b, year={2015}, booktitle={Bifurcation and Degradation of Geomaterials in the New Millennium}, editor={Chau, Kam-Tim and Zhao, Jidong}, doi={10.1007/978-3-319-13506-9_18}, title={A Multiscale Investigation of Strain Localization in Cohesionless Sand}, url={http://dx.doi.org/10.1007/978-3-319-13506-9_18}, author={Guo, Ning and Zhao, Jidong}, pages={121-126} } @incollection{Toraldo2015, year={2015}, booktitle={Annual Report 1}, editor={Grenoble Geomechanics Group}, title={Numerical modelling of the localized fluidization in a saturated granular medium using the coupled method DEM-PFV}, url={http://people.3sr-grenoble.fr/users/bchareyre/pubs/GGG-Annual_Report_1-March_2015.pdf}, author={Toraldo, Marcella and Chareyre, Bruno and Sibille, Luc}, pages={131--138} } @InProceedings{Gladky2015a, title = {DEM simulation of the dry and weakly wetted bulk flow on a pelletizing table}, booktitle = {13th U.S. National Congress on Computational Mechanics}, author = {Gladky, Anton and Lieberwirth, Holger and Schwarze, Ruediger}, address = {San Diego, USA}, month = {July}, year = {2015}, } @InProceedings{Gladky2015b, title = {DEM simulations of weakly wetted granular materials: implementation of capillary bridge models}, booktitle = {Fourth Conference on Particle-Based Methods (PARTICLES 2015)}, author = {Gladky, Anton and Roy, Sudeshna and Weinhart, Thomas and Luding, Stefan and Schwarze, Ruediger}, address = {Barcelona, Spain}, month = {September}, url = {https://yade-dem.org/w/images/5/56/Part2015_.pdf}, year = {2015}, } @InProceedings{Suhr2015, Title = {Tribological effects in granular materials and their impact on the macroscopic material behaviour}, Author = {Suhr, Bettina and Six, Klaus}, Booktitle = {Proceedings of the IV International Conference on Particle-based Methods (PARTICLES 2015)}, Year = {2015}, Editor = {E. Oñate and M. Bischoff and D.R.J. Owen and P. Wriggers and T. Zohdi}, Pages = {878 - 889}, Publisher = {International Center for Numerical Methods in Engineering (CIMNE) Barcelona, Spain}, Url = {http://congress.cimne.com/particles2015/frontal/doc/E-book_PARTICLES_2015.pdf} } @InProceedings{Duriez2015, title = {Effective stress in unsaturated granular materials: micro-mechanical insights}, author = {J. Duriez and R. Wan}, booktitle = {Coupled Problems in Science and Engineering VI}, year = {2015}, adress = {Venice}, pages = {1232--1242}, editors= {B. Schrefler, E. Onate and M. Papadrakakis}, url = {http://congress.cimne.com/coupled2015/frontal/doc/Ebook_COUPLED_15.pdf} } @InProceedings{Duriez2017d, title = {Partially Saturated Granular Materials: Insights From Micro-Mechanical Modelling}, author = {J. Duriez and R. Wan and M. Pouragha}, booktitle = {6th {B}iot Conference on Poromechanics}, year = {2017}, adress = {Paris}, pages = {441--448}, editors= {M. Vandamme, P. Dangla, J.M. Pereira and S. Ghabezloo}, url = {https://ascelibrary.org/doi/10.1061/9780784480779.054} } @InCollection{Duriez2017e, author = {J. Duriez and R. Wan and F. Darve}, booktitle = {From Microstructure Investigations to Multiscale Modeling: Bridging the Gap}, doi = {10.1002/9781119476757.ch6}, editor = {D. Brancherie, P. Feissel, S. Bouvier and A. Ibrahimbegovic}, isbn = {9781119476757}, pages = {143--165}, publisher = {John Wiley & Sons, Inc.}, title = {Microstructural Views of Stresses in Three-Phase Granular Materials}, url = {http://onlinelibrary.wiley.com/doi/10.1002/9781119476757.ch6/summary}, year = {2017} } @InProceedings{Darve2016, title = {DEM modelling in Geomechanics: some recent breakthroughs}, author = {F. Darve and J. Duriez and R. Wan}, booktitle = {Proceedings of the 7th International Conference on Discrete Element Methods}, editor={X. Li and Y. Feng and G. Mustoe}, year = {2016}, publisher={Springer}, adress = {Dalian} } @InProceedings{Sweijen2017, author={Sweijen, T and Hassanizadeh, SM and Aslannejad, H and Leszczynski, S}, title={Grain-Scale Modelling of Swelling Granular Materials Using the Discrete Element Method and the Multi-Sphere Approximation }, journal={Sixth Biot Conference on Poromechanics }, year={2017}, pages={329-336}, doi={10.1061/9780784480779.040} } @InProceedings{Nikooee2016, author={Nikooee, E and Sweijen, T and Hassanizadeh, SM}, title={Determination of the relationship among capillary pressure, saturation and interfacial area: a pore unit assembly approach}, journal={E3S Web of Conferences}, volume={9}, year={2016}, doi={10.1051/e3sconf/20160902002} } @InProceedings{Nikooee2017, author={Nikooee, E and Habibagahi, G and Daneshian, B and Sweijen, T and Hassanizadeh, SM}, title={A grain scale model to predict retention properties of unsaturated soils}, journal={Proceedings of the 19th International COnference on Soil Mechanics and Geotechnical Engineering}, year={2017} } @inproceedings{Aboul2016b, title={A discrete numerical description of the mechanical response of soils subjected to degradation by suffusion}, author={Hosn, R Aboul and Sibille, L and Benahmed, N and Chareyre, B}, booktitle={Scour and Erosion: Proceedings of the 8th International Conference on Scour and Erosion (Oxford, UK, 12-15 September 2016)}, pages={397}, year={2016}, organization={CRC Press} } @inproceedings{Tejada2016b, title={Numerical modeling of particle migration in granular soils}, author={Tejada, IG and Sibille, L and Chareyre, B and Vincens, E}, booktitle={Scour and Erosion: Proceedings of the 8th International Conference on Scour and Erosion (Oxford, UK, 12-15 September 2016)}, pages={139}, year={2016}, organization={CRC Press} } @inproceedings{Aboul2017, title={Microscale Analysis of the Effect of Suffusion on Soil Mechanical Properties}, author={Hosn, Rodaina Aboul and Nguyen, Cong Doan and Sibille, Luc and Benahmed, Nadia and Chareyre, Bruno}, booktitle={International Workshop on Bifurcation and Degradation in Geomaterials}, pages={117--124}, year={2017}, organization={Springer, Cham} } @inproceedings{Chareyre2017, title={Toward multiscale modelings of grain-fluid systems}, author={Chareyre, Bruno and Yuan, Chao and Montella, Eduard P and Salager, Simon}, booktitle={EPJ Web of Conferences}, volume={140}, pages={09027}, year={2017}, organization={EDP Sciences}, url={https://www.epj-conferences.org/articles/epjconf/pdf/2017/09/epjconf162378.pdf} } @inproceedings{Tejada2017, title={Multiscale modeling of transport of grains through granular assemblies}, author={Tejada, Ignacio G and Sibille, Luc and Chareyre, Bruno and Zhong, Chuheng and Marot, Didier}, booktitle={EPJ Web of Conferences}, volume={140}, pages={15019}, year={2017}, organization={EDP Sciences}, url={https://www.epj-conferences.org/articles/epjconf/pdf/2017/09/epjconf162417.pdf} } @inproceedings{Montella2017, title={From continuum analytical description to discrete numerical modelling of localized fluidization in granular media}, author={i Montell{\`a}, Eduard Puig and Toraldo, Marcella and Chareyre, Bruno and Sibille, Luc}, booktitle={EPJ Web of Conferences}, volume={140}, pages={09019}, year={2017}, organization={EDP Sciences}, url={https://www.epj-conferences.org/articles/epjconf/pdf/2017/09/epjconf162069.pdf} } @inproceedings{Yuan2017b, title={Coupled flow and deformations in granular systems beyond the pendular regime}, author={Yuan, Chao and Chareyre, Bruno and Darve, Felix}, booktitle={EPJ Web of Conferences}, volume={140}, pages={09017}, year={2017}, organization={EDP Sciences}, url={https://www.epj-conferences.org/articles/epjconf/pdf/2017/09/epjconf162060.pdf} } @inproceedings{doi:10.1061/9780784480779.051, author = {B. Chareyre and E. Nikooee and C. Chalak and C. Yuan }, title = {Micromechanical Insights into the Effective Stresses}, booktitle = {Poromechanics VI}, year = {2017}, doi = {10.1061/9780784480779.051}, url = {http://ascelibrary.org/doi/abs/10.1061/9780784480779.051} } trunk-2018.02b/doc/yade-docref.bib000066400000000000000000000022441324306050200165750ustar00rootroot00000000000000 @book{yade:doc2, altnote = "\\url{http://yade-dem.org/doc/}", author = "V. Šmilauer et al.", doi = "10.5281/zenodo.34073", note = "http://yade-dem.org/doc/", title = "{{Y}ade {D}ocumentation 2nd ed}", publisher = "{The {Y}ade Project}", year = "2015" } @incollection{yade:manual2, altnote = "\\url{http://yade-dem.org/doc/}", author = "V. Šmilauer et al.", doi = "10.5281/zenodo.34043", booktitle = "{Y}ade {D}ocumentation 2nd ed", note = "http://yade-dem.org/doc/", publisher = "{The {Y}ade Project}", title = "Using and Programming", year = "2015" } @incollection{yade:background2, altnote = "\\url{http://yade-dem.org/doc/}", author = "V. Šmilauer et al.", doi = "10.5281/zenodo.34044", booktitle = "{Y}ade {D}ocumentation 2nd ed", note = "http://yade-dem.org/doc/", publisher = "{The {Y}ade Project}", title = "DEM formulation", year = "2015" } @incollection{yade:reference2, altnote = "\\url{http://yade-dem.org/doc/}", author = "V. Šmilauer et al.", doi = "10.5281/zenodo.34045", booktitle = "{Y}ade {D}ocumentation 2nd ed", note = "http://yade-dem.org/doc/", publisher = "{The {Y}ade Project}", title = "Reference Manual", year = "2015" } trunk-2018.02b/doc/yade-logo-note.png000066400000000000000000001056071324306050200172750ustar00rootroot00000000000000PNG  IHDR] pHYs  tIME  UE7tEXtCommentCreated with The GIMPd%n IDATxڬidy&.yoYY{U/@wc&H ")j %rOXqB B(&($ }+9n1=cngtTWgV=yy9E_GFeћO$ `8FQ>|yqnYK B Ѥ͆?,jh!aEq8BU0 wvwjکS:d2q]?GQ'2z4LQYJ %BB%2aC1!R4LbڦaJBe%:RIYhT*VVPF/\@)RJu孃yTl6]ǕRjhAq7Jn''Ir||)ܲ8r*P$V7p(W븛P a̼Y&,˄)(<5ZB)9J)`8e2sJR&q$ Sr9_^\nsTW޺r|t! J(7,Yd<8 ,jWy*~zrnۿ~i=̳3d6ްEHv p0F~$ID^U*,͛mkg<4Ih\Р2d.y\IшP"s !ŒRJTyzb]!R_(2?P9E"hh5%J'K-OEZ"9K@)σ0 9_bu:)emAC-\YY9:85[M˴$KK+0 8GQ4n,ˊ%X\oY ãÃ)x<~/\p<]oKcqw:%TC *Bz^U8}?<˕RPPR%$7B*N\̵*v(4!3?3ƚ&7x4M8-P2_oP9чrsN(QZY. (jpLjc)E<2='`<})e$?d6ɲl:'?Bgd45j֪5CzhQQ1JR3o^]eY$ ._8N)&3C G;nF#1MSj̛jga$RJK}Yi* pDi2/eY(\pMF'yh'k :SuiZk Ρ~PO}}mUUE2z7oYNirƙɒ4^B%VyeY=\q~eڶ=0R&gV*JiVMgny8Bq[VϳqzdJRTH),}=.LC3΄Цaj)a*L~P׶mpʡPqθe['eH˲fdZ8Lk-䔛 `` Z#MFe-HG#$e~tA.s} ? !(hT'xw:QoZkqtZ{W3Fai*㩔2N0 jP"i08:88X]]e;O(jN۝&{Y:e5p`Z 0-7M۶OֱX,4O(~?Zi yX^\.0a.]}4_ra0 jZveQq0 0 eJJ0d)T`̛=c[v Ldi/\p nj)zݞ7vvvfZ;e=^.Eei'IVzGGGRtAeD gRQFmنau2$iʲ]o߹=M0dYv]& i\2e0hkZO/N3M-sbM'"Bjuݙ7+:\9IP6~khJ` PaKxBPʈ7#!9zXTiQKRBPZ0(8c€0`ZZת(ᔱ$={v!`Ll.u,,Fcu Xܲhyj)99T*FsaaquJEſB:Zl 3666Μ>cv$E\}ߟy>i*[vQY~Բ…  Ht:2ʒ8QZeYfVn+e)֚1fCJytt4LSMᣇiV+ՂVJlB)EHsp!`Yp]e RIv=鞹} ÀiB`(PdSX@k]xAJlZ)WrcRT+UXdzLkLCgi6ysi߸$Nos^l C /<Q4CІ *e)(VHSc L,Il3'nvBFGɄ16r#RJJiVYXX(J 8˳te&:rh(Rk8NҤPض=fz0L R`0,--42ƪj.W\ RC4i? *EBıV*SeD)YRq!mSJ! C.et:xxxXhaK\aXK+PwM4ӧN !914MZ굕t*Φ%9 Gv*ݿ^=\h-s/Wftށ-Z_]oo/zөm۔҅i;i&IRD'i8`8Qj(+|4K޽[. %{dmeMpQpvCC)('sdEyT qFqdžq/tQD SK$);{fneY,t$O!wQJ5MJiL&?|QǩkN4i5ӧNH)ámٓ$MS˲vww㱒YO|R+pZCJH A#Լ1{ꩧLB&A_rx35 QQ 1MXTVl$;8OV4KuƍA;ޡtOJ ¥_ҍ&J6 P q`HS(J@CbL&?hgϞ=|f 5aj oww}u}mumkkZ{'l4]r޽{Ecc yi8RbeI r. J!%!p*~yT!?JU)t?B88%iEg:{pMR)֜A1 "4Ẅq(jUmn 2,zY ϳ^[p9>޻ ͖ R݆0Law: UNw)~j:%:eXB!5- B)viE0r\J-ʡm>yu$~f0 s +h4$sww}HR g Ys*q`$A 1=|zu^ |+qJJYiFaifYGQsO AIw#H$!AJ CE$+ZaC .8x7ncxCW*gQ)AF iuԓv1ƙSrX @( ;;qxǫVRO&B C,&,0*J>ԏn~PrK` `ư-p$Ax1`Ьلy?JAk`AG?:Ѩ7667]$v%$N5[t:L&IqgYg7zTaYVZ^v;wFQghjJ) !Lj"><yBI4F#ܼ7'~V׿h6 YI%(eaØ#MfRÊc0 CBQH<$ϟh~}y?ORyW38w`:pQ,c88! v颵K/q|#_EJ}?􇣡qF[Omxrq} Q4o$R X$<|W=”ҟ_B/Ht6Zc,NAyA(lFѨk dAD rθNbyy)ʈְ, pE)yҥzk:Hx/i,,uq|P*ހR88DO|+h49&'P B$:LϜ9S*<}_*MZ]XXXXXd:vkV|TBJxE IDATK\@4P1@$Eр`iǙ30 r\%|4-//תn,,dEQ$IDQyAaq<ϳ,t:9Mb@ɧ? 0M4 Q9$9-;|;(CE '׮OsWZ7 y62\RIurYZewַݽE$F"M$Hyf"A]]ծYIY.Q0  !p):D|i)OҙgnoClmijCbdVtaYɲ 2陧1{޹s^z^{<SdcM[Mͽ/,tp1xݹƍkk[gKj {~BJvt !qKk3xQ@2 u(h}ߗ̲lo{޵|sp!!!1P.pU W1cwW7yL)叼W TR|omu4JLFɸ^شJV`FFc~5:GUNp<)\[&PϜQ֭ۉRxqy鱳g?zCÖO?]S<&AãVpn KxpƜv޳KvПΦiŏmnl[hOJ9O(*0J+W7D\]ſXZB%X"8,I;~uEVZKιi~^OXJ),SyXOѣ,M^0NB)5Zr.m1 D@rI1:ņ՝Mg0dApq0|V M'Ε?o=?~qAQ |j Q)ci 6*e)>ԗ.\>onߺE!De2駟ppz^\.%˲?_ 1l#.9:?heZ ;,j ݻx->eYKx"TqYJ5q}zeDeZaJi荍 Cw\:8sܹo~׏Di Y^Zg@(( u<*d)=mGP+ӧOο% axu'^x7HpZղNYZhn J-ۢ dwm61ɘBrP,SVu It&R3<޾8 J9o2͖mǩֲzX5zD%)sg(r}>}JPhQ"Nm)RYݿҨ7y_]+](s|ݽP/}ll4B{9fA?d\ݓeSn/.iV+/J+er.TmZy)$-@i ֙Νa(i)5 UDTd2ГD4noYbw\p2W :Pcs"M1$BpBH3z|@)tg>jc?饥Ei<υ3O&K 'IZ-u8vkyyѨYe\ij T*VQhEɆe`Z4aF☄!cx^y*H# $܇XRT*(:ppʺROQcH޵Հ@ }Ga> Ϳ~Q*s #80`8D!sN!Nl}_ ``?I^x3Om16΂ JQf6$@jVkJ\@$hZM ЂC}ZJJJJc:M4HSĉT*4i@cJkT*,\R=*rmPʀ 1CCc9t hsãGl+ˏ>* `4(~}t:}͛7]V.JWɓO²$ƙc)]BFq퇸qCἱq_|ŋ.FQٳ?,c,eGDIQ U:zfh}}2Z(?wZ UuIDV! 4 4y:yೣ#hj5]u]Ҧ C 8W()(g3P1J$CIb A݅cs4^lFZkP@3y{;9;G~wB4Z 0-s)qp"ZV{՟| oll~+^U'¯|Je: 4 Edhۃ4 u >k ͉҄=(4+ ZIdB:t `QD"I֓08Ӝi.4 (qLDsf`[V PR@ .Âie!;{VW*7kYwVVV^}d5uVfZhݝATo&ΟG4eͅ^.i@)IwHMIr|><< _[qs"gTqP)0qOoFVW.]2fs}R 2C*H8.(C:It JRրєhJ*u@k4B†E{/5pc .:MQU#0jj.I 'ܼ9砢(yO>t:Ӱ?ߺukwww~ŏEr$(eVC!@ /"Mɽ{7qpONɼ˗/sjաq|4FjܲJ񄁞K ʕv|: !?rOj˚q8?f t) HbЏ&xşK/_xa0n޺vlyKy50-j XwR^{4Dq|R7ǷÝpy6;|" 1l ! drIizP*yejH35=iԀR]rijεSa:Efio痗kZS5r)T/zJiCH2yK>?)7ZZvhWFsBGGf8sK}Frh5AC~MF67O}3xaE/ݽld2QJ@Y{ ~u>Ow*e?A1ޙa"iIbi KtxO>8KK+_{"!=5֎F#1;gZe v:n@n7 MSE]iyitB ^~o"mp횽y}?}>Jw:P (JA*(EQ$y[-5/}߿ǼnF2I,g! I)QHE#AEV}W XXD`>{7ޗ."ӝ;KO/"}[x3LD#d M)nptw~gD<͛^7ckw巾O>?C:<5 b"D I `/.|O`!EXZ"=kt!8If\|z0Z>Qm4NsXE^7Y`]_u\"a,σ#0c<3ʗ7v휫ဈv-@Dfi q~l1$IgucZ6;>xt{y#$~n(ݦv[dEzM ><`"111KIBx栟鄝cciev d $AqQ`n?!}G1KIQ8])84AQC1D&\ҿqxGG8wNm=j/_;kZ+x7ֱU/|?U?ƴg0-q~ssK.:DIJ$IƓsnqnFqB?D(IH3)ӫֺ,k$ڝVFIt|pQRS@S+m*tڳ'Z) "9k}]dWʊSob,,RRPrc0B(()!iRցOˑ_N1}a}E1ӥ/_e_uʯUpX\JaeV'% xpf,$/¿[_0c.]i;b%6"˪8.xccuon`ͩ|އű2t:B7\4"A3D`3?8ύb4~2) <f|)=}:1f%:c2dFJF0vb<ak q2pr[w ̟AnQ8Ar J1 ?!n>5%[_V朱fyyimmuBfs^H)Rz` ssn{08ꦮ) BE$ &)IJܤ:'a4Zgv̞eA$eEq<}*HHEI !dZI@ZQ7 K*tlWQ8<Ό B`q|x8=8 k1(/PWݥ w󹤱eY;XAb~ IDATϞ>_\Z̲tc}F5;ߐ8>c)Ǔ) E(h' DZw;cE@֚M5ؠ-Lh<8&k9nEʳ(b)=AUd8vmk a46 )HIr\LΒBf3xiz&^Z4::l.FY7%9YHŸUħzv08!U5{&>?}m7͕嵗; +nÃ*7///ѿ{;;iuxpq98C5aJOUt꫊TDEA1% 1բ$!-B=AݠiP3sGG~ouM̟[9\=Y -1ya8ifu*q1b $='BEDR-vv)1nxֶngϞX_^^LǃaYVO?itwyxqqn<.1V+XER)pIְR=U5SjC}x)%)E1QbfkԂ!HEgH)CLaL㝘x2F0grP[?@ B]ڄU>mS [[0vָ߄88xZ8{GcndFXv6֩} >;7i?K_bǣ 3olGqtr2fp]X`p΃iyee…s/vrptWRYTUC5 R fY ꚃ8MtvBY8Zs8FD"XE^HDR!EHkMuF P%-QMƐ֘LPֽhg W# ocg΁W.ae(KL%2fAH2Z[U]+|^ZBn;<>y¹K7n^=hng[[J4KsG!nDkKsQiwNUO!4%peIӉ*3OYk;ec 5W%ٽ`FhAx !K5sRA^s֒1>=iԵd:EU7j/C&s \z}ڧ2''88.^@9%x0*q2gs)ow~LJEq_?T7d2]]]eߟۭ'O33BwLeRE;ۻqgo\Y[]8Rh8V֘UAɔ=+1{Ĉb q$S7oDLБՈ"(IӚ"菓tR$&.&Y$7S{`Z])NG0:A$ VWA1-gfKKtt/^h |!o3|{w{^.]<7[hpx4s;鳫W.E݃+W.1d2-hZוRp߹g8WWVV<D7vp4_쇙.sl iCZCǐ`˒SL&j~^横0gw}u'qx!Fxc !k< ]EQ|᭷A0l^i~k{G۷}ݷnlnr "!@7V7mGJu:յs+LX3+d4N ׄ1lY9 h]ZC3{\r)/Eki7nUZXΉkčE튲IIuEֲ09΁Nx,nѼ,mY~es9]i PxUu5##P=$~҄Ꚛ45mmwÏ~w}/^zd4 !R7n^;eu=zv<{VwgF4JNuaXIY=H~W79"%91ᢘN`,'IRz\jc-Fe1''tBQQ,,i*Ddyqvi< R*rR8''f=y/Αd,&ݠi?Ϟ}<10ag]uDD~4e{p}Reu:.nƇr}W|2:vz[n!D_>9_zi8<ٟ.]>_ךٷ;-kݽ<~ Gf.z2)X%I%FDiZQG?[IaXo n}>9gOZ*Ny<"&ss* "|sf"(赛==}ꚌUZ ;aF W I=<(~++k{RO$J (R^ɓ/_88>ҥUUMgoo!Ank !qu]$,6Qx0JJߚZ&2VVmQ4e鬃8 k4MTU<,{J3g`D,YFqLBйM3Q7%‚T#N.a4 &ͺvōh8./v95@XY1r.u$ xh~ȿ^ `捛rZH|ڟEEѣGOÃZd:-?ѭFkf^]]Z^^vuXmr<88RJeݝC}Gvk<53lFCXXP 7"4h\$@C,,Y&\r"/dQP %CIhꪸ|EE!$C:+T-T)ey|0o{iQÇ11} z"Xt2-/QPP;b0,PCyNBpH> Whuu+}'o}㛗.^xtk䍛;g~ax{{w<\~Y)8GO>k)+"k3׵nj3:FcTGR8ǣh4ID%)ZnSQPQPMuڲQyG#wpu c1"LpJ2JSJSf"Meac~gYw$6cucdRI),M}ksehPY`mj)/(E{7Cr.3&αRȲNf2Id4Ui*TLS$yDcɄGt^0,sjiI./S+F"E<Yg~~aiv@2۸q1M&ud +t2]X#v4`08sgpV Wx8NsoxRNK ykk铭[Ooh4 $$=zz2y.xw>Iť.nl.{i׵֍d8yzxx،#JduuhGXB&i4$HIEQVR,ɝ;"wŕōS[?9huuiucvD)EE,$  h&Q 2Izd~Jf*D0+dX֚59uMIj#Bz/HYIU/xo>IEJX)©hJvo^<녨 -'tqC&E hB1brO BPJIJױy>/w^VH⤮%AdٽGBmTb2-Ǔ3<˳٦/!E β4s+'/ 'I"fQ|\>4NkJE0mJ33yxZ " 4TU4hlHz!K0D8݈HiG]Q A^mCh8"$@N 9E3{_Xۣ~ojMLBF;wt "?Qo;:O';2\SA0cb砛lZk={+zG֢93RL'6 5im-QD!Be)F+V"R$$=[S9؂kb̨!`A`AP@ ' w{UU+z{z.\ G!Bg?G`N"Lzs$/_,9w[k;82M!&tR*j> ")=MUePf:"68R2 f/e=Cc9S*r@fmB !i%mq <`XAD@DvUk9NG1_UjDouȳx)޷!.bk勛(٘Z O&UOs^Q0̋oGɽG miQ`C`$Rp%IJV놥sA-rrjvIY[ "Y;?36$3YC9 Xܐb y #iH4$\P@E¿Joclx(ʚ xzfoO?$ڲsmfLeGn=` "胃k@xѰ,P2jm  B4JYTFYzGY)O"TJXJ*JV d8D'85[ XaAgd5UVxqPPpSD`Ia<-XU_ykT$&i97o{ozOeZXG/_z4E˛E' {?Ϟ>hc$lGi+K{{$Z-k4 0Zh$I<Ӣ,Y4 *zx4G<[=E.Nr6? ZN~t(߼|ҹ?N~ӫlH"T x'9I)*nC"GY35$jV\d9e)ʼnPm{ጓ4 LhNANի ;ɂ{ʼ#H&v-gHB)%zt.E'i7^f۟~wr2g}ϯ?^7zt%˗{=~&:#04Z7$qdwo??u]DKKs++˟^Ͼ"%űp %EII"ӧ", P(()D0i&&U:oNN Aγ62o(M9I]Y_N+Q'cޛԛ' j@݂3BDذ"h>sϢEh8i=} ?&ϷFK).^ܘ_o'B7|sp~/8%Q|zwk_ŋv?x|L%vH8Z\U 8k^m0ij}YU^E>M(E^+A$KYA>[x* WtbQ2;jYy$8&!D#~,{om)<EbhAVTvcGH!>=+ 5g4t\y{vE% tGzp-//^vqaqd8чZv{|Ykʲ ˏ,:GQ¢M)~Ycnʕ KMc>n^łd4RV8~mssmjgιɝۏ;o\ڽ}tR>~hZ "N'Hxk ,Q>K9M|mq|{w9^__lI) 8HP Ab:E9EӨeD>&_HIaE4() e bqA|g}?yAN肗ȃӔ.md(9Mς-`<@+s~'\~{GGab`Q~RFBPQi?xϟoBRJm^~qyytk9:N 1^~+޾w=KVIYEF$/ 8K]tEGDUR"DYx/ʦU%RL&b2,lZ)cRb"KE4}L%&))I \_H!˩!vV[JݭK˼4GKr)#$HͲ!E@ '?Dh ھO`'hFT qݚLʺփH[sx0`f"uǟ{zfO_;׮](Zl[&@SY|(BE>tR$Kyt@eRPFA*`qyőέƼx?;;!?Ej|RG5Z$2|hGR a{=|77]…~坱e' yώ{xfG0 `,"A [{ IB9)II"K{D:d-h'&%1+" ~_e:{kKuۃHťz? B,(rl84&Ip%I,<ٌ@J ^:qH%I&xB QJV`2᭷BHEMçm;G/nBPnP`ȇ`h0Xzv+GVAV )Rr}[$epRP!yN|†X%RR;dH@,$0 S>wQ#.[Y|k4;8..J"Moi>uߥNZ-d)w?wE$h~EvsEQaBgjmIJ9 R#kkՋvVW3yF)彏 FӘ(R V~g;{ ڸ13:!Me( 29M4ewegc[5Dgys]vwm, ~,!$$<Aؒmv횫ֽu֝oN7O!NfU##V;vD}({}A BҰ*#_u9{>ŪZ!#8B2CQJW5\B6G:ُ`57_ƙ!a+.ZΠg|QU{xK~jUxH~ާ޸qEᇟ= znWg=>Zxx4ڍ|xAt ޼ ~wwC4ڬ)kլjњE=͍ZTPsNekmdGރ PUO3;DDuĄ9lLg@Ih@9j˫18JLg]i[f}^ay ronw?ѣKݺ?~՗M/"u]:{nibtL1[=~晉˃y_hF.}~]V )Vqh5ٰo# 4QYc$ytN䣱#fPecHlFLLPr0e *J@mZ:Dj)!(P\Fh_J4t)laXׁ3Um"@PB2+Y-T8~s4[4w{w.o׫7Cp+>͛I)k+ MEL67ףŢ&ɷyR]tZ nhjc]ĺyLlZ  ̨ r.BBj5R)2 YB`jSWpjY f겙LGb@R%et@Zۍ 7\?^hw㧻Eʴ767'~>?8ܜ<{^oŧNT=#a2K߻v%ݗn|UD!Ĉ`,k̙Ae{30,ꪲdesU]\/E. @ ˴0\!&"-4MEO 0+1hTL,f 2QHɚV82*_*7ʋzX@ٲ>s̷n\|xݍx4Yc"ַ_yhM7n\gOр9DBJ B]gkԴT$@+>QE"9Aᲁ=F|eAVH)  6tmMf \RϤd]®YtGK5f^!Xᑶ M r6.4kl.Av!`BDxmf6A,gk:[&:yoBď?j!|<8u˗Ͼڕh뻻ټ|itvkc\uNrFUTc$,QesU[[c$ K|RJ+rU)5v8g55 Ppz&*dxY}EqEhZXX71TT+4OefVr;A힤{ 09< *`cm:@UYhkk|J6jjCA˖4uf @{hx♟}hVa>oB_}1/o~sxիWoxtW^߾{瑣Y2*&)}QʛsB ]`SyHc,>::0{;E @LXIƌYU%T%*`I :]WH[JEk/d(j"5h&HĨM-ihje=|_ XC:`Vm"ZȞb]^}ho<~|߯S3}ʕ󇇳۟|qGGGv^r {{ӣd< {kbC$L {hUiIWМYB)/n^]TKoƪZGs[wt,?yh䘬׷_Emo; Csw*@+tX)BwݽgmE1 cpW4j0[X=5 :@]tX՞C I:9o~߸ZakWqJ*U;D:>γ":^uiЗ釩"2b3M7=2& Um16cEdSv4_w$"J} ǫQ!b$ oѿOѲ W~z=y^zXW\f7voP?zvݜUȘLdaH3d9C&h,/W|oM9/gQ54-+b"":;^ֽxtyLxx?u<#mn:d=?G;)9 -r]2`/mlұŶK:ֵ]^.c/΅y0O?O鴍=@-AU[7f?eqO# u<OdL18hHP0Ų݃ʂH`X@@= XLdYѻ?$#ŘUuTgľ`(*f'}\̈́9Hdx|AM\uV_ߺ{}ft2W@#zurְdJՎ8'.>9>|1mo0;.aJrYp}\!קa u>n^[7Ey5:J} cڎ|p9ƶ#Sӂe5-Q&" <*i-fɈl_/O&oC-YXchW@4Bpe>"@+bonm3gh2@NwuUz}hp9vwmg6o 䆔^~{8X_?ݣze5B "PE ,K^,;O8&w2tnns[p3{ba]g.i4BFTVbfd"D(%Ͱ &0/oDD'UB᧝N@D|@~sƹC q4ZzY6sdmjbv u 1nMhQ&2fr.;B +oՙ&"{֫WޒVԪ`5?OdQ;b'@be'8F Lf >E 3r+Vw%䘘"&vΘ !@uXu@)X2X5#9<4SE5D\=4dFTMf"JD"g)B,&jћܶߝLFh?^y}w\I׹=^o\fV"Z8C hcMcNmΣuB׃T> IDATwҹ PU<pX OWʗ``$ޗ çe;! P<*,#˅{\&CD[Zf/ND-k1Z3Y9\RQQ1SR,S7щhd31 rJ[ڍG?vޣs<* i2~s]ڌ?.]^[_޸xn1oe&e">bgD@lD^~8,݃m+N5$84Ʉ}dUlX{ÛkPy PN( .m}>]߀GjE)Q03!YT7]C;^M/3aI1_~W?]S~x/Uc/d~u7BAg]Ptp*U$hNsuJRُ(H0a@PZٲն `.ْd)JEꎛ1kVM:2'tE%FD)dQ'|* !"3Ż,eK93Z>F@ӓ0+H)1ډn~wi%2;`SȽmzog%7᣼le-YPD,vqXj%T٣K1sP"5 M5$]eTYxR.sy˥gjaɑ0 dg]3dxxU(BEǖŚT0޺ymcǏ$gEηz+ jAZΥWP!Zr%l`uX AmPbi7v?WDmm6/m[ߧϔoSYhr@)b Uޕ1snZ:j:i}Lr6 Y Z`X$b1ɾqk"I}< KԒDZ6ǽ˿żџ|=k;/C!z0rԢXJ4~qUCjG"-u/*t "+#y"ODnį 'Wi5$f҉A t)y)a#&D5pY鱏Ue>wlLʤ!x2ww>U$2 ,Y8 3؈SMީex諊^9w$ j A@RTǽ /j%Dnb 2;ck;5. ~ùU$Ǫ1T>Q코sv蘝gϞ8u/Vlu>jtb$FE?=dŒSȊ .9^D_c5Y ߝ# gƔuYr%ÊRd%ouݾR4q07W9ю<;p"v:[ȋwhLX:H':+޻s\:b{?0P*CTJ*xZrO4d /t jW U'Xy}5F9WfE;l³u:#猸$";<9|A1ge՜-H,Y$%I|u9C%;|ݏ ! &\sv`,E,(a43j31!,hNt:ɺbr?r3U,ood2 W8IEXŋ*h6KSPWt/.:;S!ݕYjDe =zOb*ZEf$,Ƥ1 `,Z>f"**X\.g%h_UBlQ]M>uAչߧȭM¥q 3!\fOH'5gBFb*/-Ǩ^ouYy>_ϻ>UU;wPAIENDB`trunk-2018.02b/doc/yade-theses.bib000066400000000000000000000161631324306050200166330ustar00rootroot00000000000000@phdthesis{Kozicki2007b, title={Application of Discrete Models to Describe the Fracture Process in Brittle Materials}, author={J. Kozicki}, year={2007}, school={Gdansk University of Technology}, url={http://janek.kozicki.pl/phdthesis/kozicki_2007_PhD.pdf} } @mastersthesis{Catalano2008a, title={Infiltration effects on a partially saturated slope - An application of the Discrete Element Method and its implementation in the open-source software YADE}, author={E. Catalano}, year={2008}, school={UJF-Grenoble}, url={https://yade-dem.org/w/images/a/af/SlopeStability.pdf} } @phdthesis{Chen2009a, title={Coupled Flow Discrete Element Method Application in Granular Porous Media using Open Source Codes}, author={Chen, F.}, school = {University of Tennessee, Knoxville}, journal={Doctoral Dissertations}, pages={21}, year={2009}, url={http://trace.tennessee.edu/cgi/viewcontent.cgi?article=1051&context=utk_graddiss} } @phdthesis{Duriez2009a, title={Stabilité des massifs rocheux : une approche mécanique}, author={J. Duriez}, year={2009}, school={Institut polytechnique de Grenoble}, url={http://tel.archives-ouvertes.fr/tel-00462072/fr/} } @phdthesis{Scholtes2009d, author = { Luc Scholtès }, title = { Modélisation micromécanique des milieux granulaires partiellement saturés }, year = {2009}, school = {Institut National Polytechnique de Grenoble}, url = {http://tel.archives-ouvertes.fr/tel-00363961/en/} } @phdthesis{Smilauer2010b, title={Cohesive Particle Model using the Discrete Element Method on the Yade Platform}, author={Václav Šmilauer}, year={2010}, school={Czech Technical University in Prague, Faculty of Civil Engineering \& Université Grenoble I -- Joseph Fourier, École doctorale I-MEP2}, url={http://beta.arcig.cz/\~eudoxos/smilauer2010-phd-thesis.pdf}, note={`LaTeX sources `__} } @article{Smilauer2010c, title={Doctoral thesis statement}, journal={(PhD thesis summary)}, author={Václav Šmilauer}, year={2010}, url={http://beta.arcig.cz/\~eudoxos/smilauer2010-phd-thesis-statement.pdf}, note={`LaTeX sources `__} } @phdthesis{Jerier2009b, author = {Jerier, J.F.}, title = {Modélisation de la compression haute densité des poudres métalliques ductiles par la méthode des éléments discrets (in french)}, year = {2009}, school = {Université Grenoble I – Joseph Fourier}, url = {http://tel.archives-ouvertes.fr/tel-00443670/fr/} } @phdthesis{Tran2011b, author = {Van Tieng TRAN}, title = {Structures en béton soumises à des chargements mécaniques extrêmes: modélisation de la réponse locale par la méthode des éléments discrets (in french)}, year = {2011}, school = {Université Grenoble I – Joseph Fourier}, url = {https://yade-dem.org/w/images/2/27/VanTranTiengThesis.pdf} } @mastersthesis{Marzougui2011, author = {Marzougui, D.}, title = {Hydromechanical modeling of the transport and deformation in bed load sediment with discrete elements and finite volume}, year = {2011}, school = {Ecole Nationale d'Ingénieur de Tunis}, url = {http://yade-dem.org/publi/MasterMarzougui_meta.pdf} } @phdthesis{Chen2011b, title={Discrete Element Method (DEM) Analyses for Hot-Mix Asphalt (HMA) Mixture Compaction}, author={Chen, J.}, school = {University of Tennessee, Knoxville}, year={2011}, url = {http://trace.tennessee.edu/cgi/viewcontent.cgi?article=2102&context=utk_graddiss} } @phdthesis{Favier2009c, title={Approche numérique par éléments discrets 3D de la sollicitation d'un écoulement granulaire sur un obstacle}, school = {Université Grenoble I – Joseph Fourier}, author={Favier, L.}, year={2009} } @phdthesis{Charlas2013, author = {Benoit Charlas}, school = {Université de Grenoble}, title = {Etude du comportement mécanique d'un hydrure intermétallique utilisé pour le stockage d'hydrogène}, year = {2013}, url = {https://www.yade-dem.org/w/images/8/89/These_BenoitCharlas.pdf} } @phdthesis{Catalano2012, author = {Emanuele Catalano}, school = {Université de Grenoble}, title = {A pore-scale coupled hydromechanical model for biphasic granular media}, year = {2012}, url = {https://yade-dem.org/publi/Catalano_Thesis.pdf} } @mastersthesis{GaeblerMedack2013, author = {Gäbler, Nils and Medack, Jörg}, title = {Experimental and simulative study of particle dynamics on a chute}, year = {2013}, note = {Project work}, school = {Institute of Mechanics and Fluid Dynamics, TU Bergakademie Freiberg} } @mastersthesis{GentzAverhausVilbusch2014, author = {Gentz, Julia and Averhaus, Jan and Vilbusch, Stephan}, title = {Numerical simulation of particle movement on a pelletizing disc -- set-up and validation of a DEM model}, year = {2014}, note = {Project work}, school = {Institute of Mechanics and Fluid Dynamics, TU Bergakademie Freiberg} } @phdthesis{Guo2014c, author = {N. Guo}, title = {Multiscale characterization of the shear behavior of granular media}, school = {The Hong Kong University of Science and Technology}, year = {2014}, address = {Hong Kong} } @mastersthesis{Borrmann2014, title = {DEM-CFD Simulation: Erprobung neuer Kopplungsansätze in ausgewählten Softwarepaketen (in german)}, author = {Sebastian, Borrmann}, school = {Institute of Mechanics and Fluid Dynamics, TU Bergakademie Freiberg}, year = {2014}, month = {12} } @mastersthesis{Medack2014, title = {Untersuchungen zur Beeinflussung der örtlichen Aufgabegutverteilung auf der Schurre eines Hammerbrechers (in german)}, author = {Medack, Jörg}, school = {Institute for Mineral Processing Machines, TU Bergakademie Freiberg}, year = {2014}, month = {8} } @mastersthesis{Khosravani2014, title={An Effective Stress Equation for Unsaturated Granular Media in Pendular Regime}, author={S. Khosravani}, year={2014}, school={Department of Civil Engineering, University of Calgary}, url={http://theses.ucalgary.ca/bitstream/11023/1443/4/ucalgary_2014_Khosravani_Sarah.pdf} } @phdthesis{Maurin2015PhD, author = {Raphael Maurin}, school = {Université Grenoble Alpes}, title = {Investigation of granular behavior in bedload transport using an Eulerian-Lagrangian model}, year = {2015}, url = {https://yade-dem.org/publi/PhD_MAURIN_VF.pdf} } @mastersthesis{Morales2012a, title={Cave Back Estimation Through Discrete Element Method, Based on Production Information}, author={D. Morales}, year={2012}, school={Universidad de Chile}, url={https://dl.dropboxusercontent.com/u/7663386/0_tesis.pdf} } @phdthesis{Jakob2016, author = {Christian Jakob}, school = {TU Bergakademie Freiberg}, title = {Numerische Modellierung des Verflüssigungsverhaltens von Kippen des Braunkohlenbergbaus beim und nach dem Wiederaufgang von Grundwasser (in german with extended summary in english)}, year = {2016}, url = {http://nbn-resolving.de/urn:nbn:de:bsz:14-qucosa-218045} } @phdthesis{Stein2017, author = {Gerhard Wolfgang Stein}, school = {Ruhr-Universität Bochum}, title = {Design und Implementierung einer modularisierten Diskrete Elemente Methode Software}, year = {2017}, url = {http://hss-opus.ub.ruhr-uni-bochum.de/opus4/frontdoor/index/index/docId/5252} } trunk-2018.02b/examples/000077500000000000000000000000001324306050200150045ustar00rootroot00000000000000trunk-2018.02b/examples/2D-Tori.py000066400000000000000000000032751324306050200165450ustar00rootroot00000000000000#!/usr/local/bin/yade-trunk -x # -*- coding: utf-8 -*- #Author : Kneib François, francois.kneib@gmail.com #This script is designed to show the "circleView" features, which allow to display circles instead of spheres for 2D simulations. #/!\ this is just a DISPLAY FEATURE, computed particles still are SPHERICAL. from yade import qt X=1 Y=1 Z=1 O.periodic=True O.cell.hSize=Matrix3( X, 0, 0, 0, Y, 0, 0, 0, Z) O.materials.append(FrictMat(density=1000,young=1e5,poisson=0.5,frictionAngle=radians(20),label='sphereMat')) #Generate the 2D packing. Y is the "flat" axis. sp=pack.SpherePack() sp.makeCloud((0.,Y/2.,0.),(X,Y/2.,Z),rMean=X/50.,rRelFuzz=0.1) sp.toSimulation(material='sphereMat') O.engines=[ ForceResetter(), InsertionSortCollider([Bo1_Sphere_Aabb()]), InteractionLoop( [Ig2_Sphere_Sphere_ScGeom()], [Ip2_FrictMat_FrictMat_FrictPhys()], [Law2_ScGeom_FrictPhys_CundallStrack()] ), NewtonIntegrator(damping=0.1, gravity=(0.,0.,-10)), ] O.dt=2e-06 #Block the "flat" axis (Y), and only allow rotations around it. for i in O.bodies: i.state.blockedDOFs="yXZ" i.shape.color=(0.,0.,1.) #Fix some spheres to make the simulation "interesting". for i in range(0,int(len(O.bodies)/10)): O.bodies[i].state.blockedDOFs="xyzXYZ" O.bodies[i].shape.color=(1.,0.,0.) #Enable 3D view. qt.View() #Activate the circle (torus) view Gl1_Sphere.circleView=1 #Thickness of the circles : Gl1_Sphere.circleRelThickness=0.3 #Tell the viewer which axis is "flat", so that it can rotate the tori around the two others to make them face to the "flat" axis. Gl1_Sphere.circleAllowedRotationAxis='y' #Make the background white R=yade.qt.Renderer() R.bgColor=(1.,1.,1.) R.intrPhys=True trunk-2018.02b/examples/DeformEngine/000077500000000000000000000000001324306050200173465ustar00rootroot00000000000000trunk-2018.02b/examples/DeformEngine/LOedometricDeform.py000066400000000000000000000116511324306050200232670ustar00rootroot00000000000000#!/usr/bin/env python #encoding: ascii # Testing of the Deformation Enginge with Luding Contact Law # Modified Oedometric Test # The reference paper [Haustein2017] from yade import utils, plot, timing from yade import pack o = Omega() # Physical parameters fr = 0.3 rho = 2000 Diameter = 16.5e-3 r1 = Diameter r2 = Diameter k1 = 1005.0 kp = 10.0*k1 kc = k1 * 0.0 ks = k1 * 0.1 DeltaPMax = Diameter/3.0 Chi1 = 0.34 o.dt = 1.0e-5 particleMass = 4.0/3.0*math.pi*r1*r1*r1*rho Vi1 = math.sqrt(k1/particleMass)*DeltaPMax*Chi1 PhiF1 = DeltaPMax*(kp-k1)*(r1+r2)/(kp*2*r1*r2) #************************************* # Add material mat1 = O.materials.append(LudingMat(frictionAngle=fr, density=rho, k1=k1, kp=kp, ks=ks, kc=kc, PhiF=PhiF1, G0 = 0.0)) # Spheres for compression sp=pack.SpherePack() sp.makeCloud((-4.0*Diameter,-4.0*Diameter,-2.5*Diameter),(3.0*Diameter,3.0*Diameter,15.0*Diameter), rMean=Diameter/2.0, num=300) sp.toSimulation() ###################################################################### O.bodies.append( geom.facetBox((0,0,0), (4.0*Diameter,4.0*Diameter,4.0*Diameter), wallMask=63-32, material=mat1) ) # Add engines o.engines = [ ForceResetter(), InsertionSortCollider([Bo1_Sphere_Aabb(aabbEnlargeFactor=1.05), Bo1_Wall_Aabb(), Bo1_Facet_Aabb() ]), InteractionLoop( [Ig2_Sphere_Sphere_ScGeom(interactionDetectionFactor=1.05), Ig2_Facet_Sphere_ScGeom(), Ig2_Wall_Sphere_ScGeom()], [Ip2_LudingMat_LudingMat_LudingPhys()], [Law2_ScGeom_LudingPhys_Basic()] ), NewtonIntegrator(damping=0.1, gravity=[0, 0, -9.81]), #VTKRecorder(fileName='vtk-',recorders=['all'],iterPeriod=10000), PyRunner(command='checkForce()', realPeriod=1, label="fCheck"), DeformControl(label="DefControl") ] def checkForce(): # at the very start, unbalanced force can be low as there is only few # contacts, but it does not mean the packing is stable if O.iter < 20000: return # the rest will be run only if unbalanced is < .1 (stabilized packing) timing.reset() if unbalancedForce() > 0.2: return # add plate at upper box side highSphere = 0.0 for b in O.bodies: if highSphere < b.state.pos[2] and isinstance(b.shape, Sphere): highSphere = b.state.pos[2] else: pass O.bodies.append(wall(highSphere+0.5*Diameter, axis=2, sense=-1, material=mat1)) # without this line, the plate variable would only exist inside this # function global plate plate = O.bodies[-1] # the last particles is the plate # Wall objects are "fixed" by default, i.e. not subject to forces # prescribing a velocity will therefore make it move at constant velocity # (downwards) plate.state.vel = (0, 0, -.1) # start plotting the data now, it was not interesting before O.engines = O.engines + [PyRunner(command='addPlotData()', iterPeriod=1000)] # next time, do not call this function anymore, but the next one # (unloadPlate) instead fCheck.command = 'unloadPlate()' def unloadPlate(): # if the force on plate exceeds maximum load, start unloading # if abs(O.forces.f(plate.id)[2]) > 5e-2: if abs(O.forces.f(plate.id)[2]) > 5.0e2: plate.state.vel *= -1 # next time, do not call this function anymore, but the next one # (stopUnloading) instead fCheck.command = 'stopUnloading()' def stopUnloading(): if abs(O.forces.f(plate.id)[2]) == 0: # O.tags can be used to retrieve unique identifiers of the simulation # if running in batch, subsequent simulation would overwrite each other's output files otherwise # d (or description) is simulation description (composed of parameter values) # while the id is composed of time and process number # plot.saveDataTxt(O.tags['d.id'] + '.txt') plot.saveDataTxt('data'+ O.tags['id'] +'.txt') print timing.stats() O.pause() def addPlotData(): if not isinstance(O.bodies[-1].shape, Wall): plot.addData() return Fz = O.forces.f(plate.id)[2] plot.addData( Fz=Fz, w=plate.state.pos[2] - (-4*Diameter), unbalanced=unbalancedForce(), i=O.iter ) def defVisualizer(): with open("data.dat","a") as f: for b in O.bodies: if isinstance(b.shape, Sphere): rData = "{x},{y},{z},{r},{w}\t".format(x = b.state.pos[0], y = b.state.pos[1], z = b.state.pos[2], r = b.shape.radius + b.state.dR, w = plate.state.pos[2] ) f.write(rData) f.write("\n") O.timingEnabled=True O.run(1, True) plot.plots={'w':('Fz', None)} plot.plot() trunk-2018.02b/examples/DeformEngine/OedometricDeform.py000066400000000000000000000102411324306050200231450ustar00rootroot00000000000000#!/usr/bin/env python #encoding: ascii # Modefied Oedometric Test for particle deformation under const. volume # The reference paper [Haustein2017] from yade import utils, plot, timing from yade import pack o = Omega() # Physical parameters fr = 0.3 rho = 2000 tc = 0.002 en = 0.7 et = 0.7 Diameter = 16.5e-3 # R #************************************* o.dt = 1.0e-5 # Timestep #************************************* # Add material mat1 = O.materials.append(ViscElMat(frictionAngle=fr, tc=tc, en=en, et=et)) # Spheres for compression sp=pack.SpherePack() sp.makeCloud((-4.0*Diameter,-4.0*Diameter,-2.5*Diameter),(3.0*Diameter,3.0*Diameter,15.0*Diameter), rMean=Diameter/2.0, num=300) sp.toSimulation() ###################################################################### O.bodies.append( geom.facetBox((0,0,0), (4.0*Diameter,4.0*Diameter,4.0*Diameter), wallMask=63-32, material=mat1) ) # Add engines o.engines = [ ForceResetter(), InsertionSortCollider([Bo1_Sphere_Aabb(aabbEnlargeFactor=1.05), Bo1_Wall_Aabb(), Bo1_Facet_Aabb() ]), InteractionLoop( [Ig2_Sphere_Sphere_ScGeom(interactionDetectionFactor=1.05), Ig2_Facet_Sphere_ScGeom(), Ig2_Wall_Sphere_ScGeom()], [Ip2_ViscElMat_ViscElMat_ViscElPhys()], [Law2_ScGeom_ViscElPhys_Basic()] ), NewtonIntegrator(damping=0.1, gravity=[0, 0, -9.81]), #VTKRecorder(fileName='vtk-',recorders=['all'],iterPeriod=20000), PyRunner(command='checkForce()', realPeriod=1, label="fCheck"), DeformControl(label="DefControl") ] def checkForce(): # at the very start, unbalanced force can be low as there is only few # contacts, but it does not mean the packing is stable if O.iter < 20000: return # the rest will be run only if unbalanced is < 1.0 (stabilized packing) timing.reset() if unbalancedForce() > 0.2: return highSphere=0.0 for b in O.bodies: if highSphere < b.state.pos[2] and isinstance(b.shape, Sphere): highSphere = b.state.pos[2] else: pass # add plate at upper box side O.bodies.append(wall(highSphere+0.5*Diameter, axis=2, sense=-1, material=mat1)) # without this line, the plate variable would only exist inside this # function global plate plate = O.bodies[-1] # the last particles is the plate # Wall objects are "fixed" by default, i.e. not subject to forces # prescribing a velocity will therefore make it move at constant velocity # (downwards) plate.state.vel = (0, 0, -.01) # start plotting the data now, it was not interesting before O.engines = O.engines + [PyRunner(command='addPlotData()', iterPeriod=1000)] # next time, do not call this function anymore, but the next one # (unloadPlate) instead fCheck.command = 'unloadPlate()' def unloadPlate(): # if the force on plate exceeds maximum load, start unloading # if abs(O.forces.f(plate.id)[2]) > 5e-2: # if plate.state.pos[2] < (-4*Diameter+40.0e-3): if abs(O.forces.f(plate.id)[2]) > 5e2: plate.state.vel *= -1 # next time, do not call this function anymore, but the next one # (stopUnloading) instead fCheck.command = 'stopUnloading()' def stopUnloading(): if abs(O.forces.f(plate.id)[2]) == 0: # O.tags can be used to retrieve unique identifiers of the simulation # if running in batch, subsequent simulation would overwrite each other's output files otherwise # d (or description) is simulation description (composed of parameter values) # while the id is composed of time and process number # plot.saveDataTxt(O.tags['d.id'] + '.txt') plot.saveDataTxt('data'+ O.tags['id'] +'.txt') print timing.stats() O.pause() def addPlotData(): if not isinstance(O.bodies[-1].shape, Wall): plot.addData() return Fz = O.forces.f(plate.id)[2] plot.addData( Fz=Fz, w=plate.state.pos[2] - (-4*Diameter), # hight of the box (Bottom to upper Wall) unbalanced=unbalancedForce(), i=O.iter ) O.timingEnabled=True O.run(1, True) plot.plots={'w':('Fz', None)} plot.plot() trunk-2018.02b/examples/FEMxDEM/000077500000000000000000000000001324306050200161315ustar00rootroot00000000000000trunk-2018.02b/examples/FEMxDEM/biaxialSmooth.py000066400000000000000000000065561324306050200213220ustar00rootroot00000000000000""" Author: Ning Guo run `mv biaxialSmooth.yade.gz 0.yade.gz` to generate initial RVE packing """ from esys.escript import * from esys.finley import Rectangle from esys.weipa import saveVTK from esys.escript.pdetools import Projector from msFEM2D import MultiScale from saveGauss import saveGauss2D import time vel = -0.0001; confining=-1.e5; lx = 0.05; ly = 0.1; # sample size, 50mm by 100mm nx = 8; ny = 16; # sample discretization, 8 by 16 quadrilateral elements mydomain = Rectangle(l0=lx,l1=ly,n0=nx,n1=ny,order=2,integrationOrder=2) dim = mydomain.getDim() k = kronecker(mydomain) numg = 4*nx*ny; # number of Gauss points, 4 GP each element (reduced integration) nump = 16; # number of processes for multiprocessing prob = MultiScale(domain=mydomain,ng=numg,np=nump,random=False,rtol=1e-2,usePert=False,pert=-2.e-5,verbose=True) disp = Vector(0.,Solution(mydomain)) t=0 stress = prob.getCurrentStress() # initial stress proj = Projector(mydomain) sig = proj(stress) # project Gauss point value to nodal value sig_bounda = interpolate(sig,FunctionOnBoundary(mydomain)) # interpolate traction = matrix_mult(sig_bounda,mydomain.getNormal()) # boundary traction x = mydomain.getX() # nodal coordinate bx = FunctionOnBoundary(mydomain).getX() topSurf = whereZero(bx[1]-sup(bx[1])) tractTop = traction*topSurf # traction at top surface forceTop = integrate(tractTop,where=FunctionOnBoundary(mydomain)) # resultant force at top lengthTop = integrate(topSurf,where=FunctionOnBoundary(mydomain)) # length of top surface fout=file('./result/biaxial_surf.dat','w') fout.write('0 '+str(forceTop[1])+' '+str(lengthTop)+'\n') # Dirichlet BC positions, smooth at bottom and top, fixed at the center of bottom Dbc = whereZero(x[1])*[0,1]+whereZero(x[1]-ly)*[0,1]+whereZero(x[1])*whereZero(x[0]-.5*lx)*[1,1] # Dirichlet BC values Vbc = whereZero(x[1])*[0,0]+whereZero(x[1]-ly)*[0,vel]+whereZero(x[1])*whereZero(x[0]-.5*lx)*[0,0] # Neumann BC, constant confining pressure Nbc = whereZero(bx[0])*[-confining,0]+whereZero(bx[0]-lx)*[confining,0] time_start = time.time() while t < 100: # apply 100 load steps prob.initialize(f=Nbc, specified_u_mask=Dbc, specified_u_val=Vbc) # initialize BC t += 1 du=prob.solve(iter_max=100) # get solution: nodal displacement disp += du stress=prob.getCurrentStress() dom = prob.getDomain() # domain is updated Lagrangian formulation proj = Projector(dom) sig = proj(stress) sig_bounda = interpolate(sig,FunctionOnBoundary(dom)) traction = matrix_mult(sig_bounda,dom.getNormal()) tractTop = traction*topSurf forceTop = integrate(tractTop,where=FunctionOnBoundary(dom)) lengthTop = integrate(topSurf,where=FunctionOnBoundary(dom)) fout.write(str(t*vel/ly)+' '+str(forceTop[1])+' '+str(lengthTop)+'\n') vR=prob.getLocalVoidRatio() fabric=prob.getLocalFabric() strain = prob.getCurrentStrain() saveGauss2D(name='./result/gauss/time_'+str(t)+'.dat',strain=strain,stress=stress,fabric=fabric) volume_strain = trace(strain) dev_strain = symmetric(strain) - volume_strain*k/dim shear = sqrt(2*inner(dev_strain,dev_strain)) saveVTK("./result/vtk/biaxialSmooth_%d.vtu"%t,disp=disp,shear=shear,e=vR) prob.getCurrentPacking(pos=(),time=t,prefix='./result/packing/') time_elapse = time.time() - time_start fout.write("#Elapsed time in hours: "+str(time_elapse/3600.)+'\n') fout.close() prob.exitSimulation() trunk-2018.02b/examples/FEMxDEM/footing.msh000066400000000000000000012424201324306050200203140ustar00rootroot00000000000000$MeshFormat 2.2 0 8 $EndMeshFormat $Nodes 5151 1 0 0 0 2 0.4 0 0 3 0.6 0 0 4 0.6 -0.3 0 5 0.6 -0.4 0 6 0.4 -0.4 0 7 0 -0.4 0 8 0 -0.3 0 9 0.4 -0.3 0 10 0.009999999999981926 0 0 11 0.01999999999996121 0 0 12 0.02999999999994401 0 0 13 0.03999999999992994 0 0 14 0.04999999999991424 0 0 15 0.05999999999988688 0 0 16 0.06999999999985819 0 0 17 0.07999999999982406 0 0 18 0.08999999999979536 0 0 19 0.09999999999976666 0 0 20 0.109999999999738 0 0 21 0.1199999999997092 0 0 22 0.1299999999996806 0 0 23 0.1399999999996519 0 0 24 0.1499999999996177 0 0 25 0.1599999999995836 0 0 26 0.1699999999995549 0 0 27 0.1799999999995262 0 0 28 0.1899999999994975 0 0 29 0.1999999999994742 0 0 30 0.2099999999994975 0 0 31 0.2199999999995209 0 0 32 0.2299999999995495 0 0 33 0.2399999999995772 0 0 34 0.2499999999996016 0 0 35 0.2599999999996346 0 0 36 0.2699999999996558 0 0 37 0.2799999999996791 0 0 38 0.2899999999997154 0 0 39 0.2999999999997344 0 0 40 0.3099999999997534 0 0 41 0.3199999999997897 0 0 42 0.329999999999813 0 0 43 0.3399999999998342 0 0 44 0.3499999999998726 0 0 45 0.3599999999998915 0 0 46 0.3699999999999171 0 0 47 0.3799999999999512 0 0 48 0.3899999999999702 0 0 49 0.004999999999990909 0 0 50 0.01499999999997217 0 0 51 0.02499999999995107 0 0 52 0.0349999999999371 0 0 53 0.04499999999992209 0 0 54 0.05499999999990055 0 0 55 0.06499999999987254 0 0 56 0.07499999999984112 0 0 57 0.08499999999980971 0 0 58 0.09499999999978101 0 0 59 0.1049999999997523 0 0 60 0.1149999999997236 0 0 61 0.1249999999996949 0 0 62 0.1349999999996662 0 0 63 0.1449999999996348 0 0 64 0.1549999999996007 0 0 65 0.1649999999995692 0 0 66 0.1749999999995406 0 0 67 0.1849999999995118 0 0 68 0.1949999999994858 0 0 69 0.2049999999994868 0 0 70 0.2149999999995101 0 0 71 0.2249999999995359 0 0 72 0.2349999999995654 0 0 73 0.2449999999995872 0 0 74 0.2549999999996123 0 0 75 0.264999999999644 0 0 76 0.2749999999996651 0 0 77 0.2849999999996972 0 0 78 0.2949999999997259 0 0 79 0.304999999999745 0 0 80 0.3149999999997689 0 0 81 0.3249999999998006 0 0 82 0.3349999999998253 0 0 83 0.3449999999998541 0 0 84 0.3549999999998833 0 0 85 0.3649999999999043 0 0 86 0.3749999999999331 0 0 87 0.3849999999999645 0 0 88 0.3949999999999845 0 0 89 0.4159009150359435 0 0 90 0.4325968757860914 0 0 91 0.4501276345236667 0 0 92 0.4685349312246211 0 0 93 0.4878625928296997 0 0 94 0.5081566374754983 0 0 95 0.5294653843060497 0 0 96 0.5518395685067007 0 0 97 0.5753324619453916 0 0 98 0.4079504575179699 0 0 99 0.4242488954110194 0 0 100 0.441362255154879 0 0 101 0.4593312828741439 0 0 102 0.4781987620271604 0 0 103 0.4980096151525677 0 0 104 0.5188110108907593 0 0 105 0.5406524764064075 0 0 106 0.5635860152260457 0 0 107 0.5876662309727014 0 0 108 0.009999999999981926 -0.3 0 109 0.01999999999996121 -0.3 0 110 0.02999999999994401 -0.3 0 111 0.03999999999992994 -0.3 0 112 0.04999999999991424 -0.3 0 113 0.05999999999988688 -0.3 0 114 0.06999999999985819 -0.3 0 115 0.07999999999982406 -0.3 0 116 0.08999999999979536 -0.3 0 117 0.09999999999976666 -0.3 0 118 0.109999999999738 -0.3 0 119 0.1199999999997092 -0.3 0 120 0.1299999999996806 -0.3 0 121 0.1399999999996519 -0.3 0 122 0.1499999999996177 -0.3 0 123 0.1599999999995836 -0.3 0 124 0.1699999999995549 -0.3 0 125 0.1799999999995262 -0.3 0 126 0.1899999999994975 -0.3 0 127 0.1999999999994742 -0.3 0 128 0.2099999999994975 -0.3 0 129 0.2199999999995209 -0.3 0 130 0.2299999999995495 -0.3 0 131 0.2399999999995772 -0.3 0 132 0.2499999999996016 -0.3 0 133 0.2599999999996346 -0.3 0 134 0.2699999999996558 -0.3 0 135 0.2799999999996791 -0.3 0 136 0.2899999999997154 -0.3 0 137 0.2999999999997344 -0.3 0 138 0.3099999999997534 -0.3 0 139 0.3199999999997897 -0.3 0 140 0.329999999999813 -0.3 0 141 0.3399999999998342 -0.3 0 142 0.3499999999998726 -0.3 0 143 0.3599999999998915 -0.3 0 144 0.3699999999999171 -0.3 0 145 0.3799999999999512 -0.3 0 146 0.3899999999999702 -0.3 0 147 0.004999999999990909 -0.3 0 148 0.01499999999997217 -0.3 0 149 0.02499999999995107 -0.3 0 150 0.0349999999999371 -0.3 0 151 0.04499999999992209 -0.3 0 152 0.05499999999990055 -0.3 0 153 0.06499999999987254 -0.3 0 154 0.07499999999984112 -0.3 0 155 0.08499999999980971 -0.3 0 156 0.09499999999978101 -0.3 0 157 0.1049999999997523 -0.3 0 158 0.1149999999997236 -0.3 0 159 0.1249999999996949 -0.3 0 160 0.1349999999996662 -0.3 0 161 0.1449999999996348 -0.3 0 162 0.1549999999996007 -0.3 0 163 0.1649999999995692 -0.3 0 164 0.1749999999995406 -0.3 0 165 0.1849999999995118 -0.3 0 166 0.1949999999994858 -0.3 0 167 0.2049999999994868 -0.3 0 168 0.2149999999995101 -0.3 0 169 0.2249999999995359 -0.3 0 170 0.2349999999995654 -0.3 0 171 0.2449999999995872 -0.3 0 172 0.2549999999996123 -0.3 0 173 0.264999999999644 -0.3 0 174 0.2749999999996651 -0.3 0 175 0.2849999999996972 -0.3 0 176 0.2949999999997259 -0.3 0 177 0.304999999999745 -0.3 0 178 0.3149999999997689 -0.3 0 179 0.3249999999998006 -0.3 0 180 0.3349999999998253 -0.3 0 181 0.3449999999998541 -0.3 0 182 0.3549999999998833 -0.3 0 183 0.3649999999999043 -0.3 0 184 0.3749999999999331 -0.3 0 185 0.3849999999999645 -0.3 0 186 0.3949999999999845 -0.3 0 187 0.4159009150359435 -0.3 0 188 0.4325968757860914 -0.3 0 189 0.4501276345236667 -0.3 0 190 0.4685349312246211 -0.3 0 191 0.4878625928296997 -0.3 0 192 0.5081566374754983 -0.3 0 193 0.5294653843060497 -0.3 0 194 0.5518395685067007 -0.3 0 195 0.5753324619453916 -0.3 0 196 0.4079504575179699 -0.3 0 197 0.4242488954110194 -0.3 0 198 0.441362255154879 -0.3 0 199 0.4593312828741439 -0.3 0 200 0.4781987620271604 -0.3 0 201 0.4980096151525677 -0.3 0 202 0.5188110108907593 -0.3 0 203 0.5406524764064075 -0.3 0 204 0.5635860152260457 -0.3 0 205 0.5876662309727014 -0.3 0 206 0.009999999999981926 -0.4 0 207 0.01999999999996121 -0.4 0 208 0.02999999999994401 -0.4 0 209 0.03999999999992994 -0.4 0 210 0.04999999999991424 -0.4 0 211 0.05999999999988688 -0.4 0 212 0.06999999999985819 -0.4 0 213 0.07999999999982406 -0.4 0 214 0.08999999999979536 -0.4 0 215 0.09999999999976666 -0.4 0 216 0.109999999999738 -0.4 0 217 0.1199999999997092 -0.4 0 218 0.1299999999996806 -0.4 0 219 0.1399999999996519 -0.4 0 220 0.1499999999996177 -0.4 0 221 0.1599999999995836 -0.4 0 222 0.1699999999995549 -0.4 0 223 0.1799999999995262 -0.4 0 224 0.1899999999994975 -0.4 0 225 0.1999999999994742 -0.4 0 226 0.2099999999994975 -0.4 0 227 0.2199999999995209 -0.4 0 228 0.2299999999995495 -0.4 0 229 0.2399999999995772 -0.4 0 230 0.2499999999996016 -0.4 0 231 0.2599999999996346 -0.4 0 232 0.2699999999996558 -0.4 0 233 0.2799999999996791 -0.4 0 234 0.2899999999997154 -0.4 0 235 0.2999999999997344 -0.4 0 236 0.3099999999997534 -0.4 0 237 0.3199999999997897 -0.4 0 238 0.329999999999813 -0.4 0 239 0.3399999999998342 -0.4 0 240 0.3499999999998726 -0.4 0 241 0.3599999999998915 -0.4 0 242 0.3699999999999171 -0.4 0 243 0.3799999999999512 -0.4 0 244 0.3899999999999702 -0.4 0 245 0.004999999999990909 -0.4 0 246 0.01499999999997217 -0.4 0 247 0.02499999999995107 -0.4 0 248 0.0349999999999371 -0.4 0 249 0.04499999999992209 -0.4 0 250 0.05499999999990055 -0.4 0 251 0.06499999999987254 -0.4 0 252 0.07499999999984112 -0.4 0 253 0.08499999999980971 -0.4 0 254 0.09499999999978101 -0.4 0 255 0.1049999999997523 -0.4 0 256 0.1149999999997236 -0.4 0 257 0.1249999999996949 -0.4 0 258 0.1349999999996662 -0.4 0 259 0.1449999999996348 -0.4 0 260 0.1549999999996007 -0.4 0 261 0.1649999999995692 -0.4 0 262 0.1749999999995406 -0.4 0 263 0.1849999999995118 -0.4 0 264 0.1949999999994858 -0.4 0 265 0.2049999999994868 -0.4 0 266 0.2149999999995101 -0.4 0 267 0.2249999999995359 -0.4 0 268 0.2349999999995654 -0.4 0 269 0.2449999999995872 -0.4 0 270 0.2549999999996123 -0.4 0 271 0.264999999999644 -0.4 0 272 0.2749999999996651 -0.4 0 273 0.2849999999996972 -0.4 0 274 0.2949999999997259 -0.4 0 275 0.304999999999745 -0.4 0 276 0.3149999999997689 -0.4 0 277 0.3249999999998006 -0.4 0 278 0.3349999999998253 -0.4 0 279 0.3449999999998541 -0.4 0 280 0.3549999999998833 -0.4 0 281 0.3649999999999043 -0.4 0 282 0.3749999999999331 -0.4 0 283 0.3849999999999645 -0.4 0 284 0.3949999999999845 -0.4 0 285 0.4159009150359435 -0.4 0 286 0.4325968757860914 -0.4 0 287 0.4501276345236667 -0.4 0 288 0.4685349312246211 -0.4 0 289 0.4878625928296997 -0.4 0 290 0.5081566374754983 -0.4 0 291 0.5294653843060497 -0.4 0 292 0.5518395685067007 -0.4 0 293 0.5753324619453916 -0.4 0 294 0.4079504575179699 -0.4 0 295 0.4242488954110194 -0.4 0 296 0.441362255154879 -0.4 0 297 0.4593312828741439 -0.4 0 298 0.4781987620271604 -0.4 0 299 0.4980096151525677 -0.4 0 300 0.5188110108907593 -0.4 0 301 0.5406524764064075 -0.4 0 302 0.5635860152260457 -0.4 0 303 0.5876662309727014 -0.4 0 304 0 -0.3780024001981511 0 305 0 -0.3570523051187844 0 306 0 -0.3370998336061458 0 307 0 -0.3180974798108416 0 308 0 -0.389001200099055 0 309 0 -0.3675273526585148 0 310 0 -0.3470760693624635 0 311 0 -0.3275986567085242 0 312 0 -0.3090487399054085 0 313 0 -0.2850000000000109 0 314 0 -0.2700000000000326 0 315 0 -0.2550000000000781 0 316 0 -0.240000000000141 0 317 0 -0.2250000000001843 0 318 0 -0.2100000000002277 0 319 0 -0.1950000000002711 0 320 0 -0.1800000000003036 0 321 0 -0.165000000000347 0 322 0 -0.150000000000385 0 323 0 -0.1350000000003481 0 324 0 -0.1200000000003112 0 325 0 -0.1050000000002732 0 326 0 -0.09000000000022879 0 327 0 -0.07500000000018975 0 328 0 -0.06000000000015071 0 329 0 -0.04500000000012139 0 330 0 -0.03000000000008241 0 331 0 -0.01500000000003687 0 332 0 -0.2925000000000165 0 333 0 -0.2775000000000127 0 334 0 -0.2625000000000469 0 335 0 -0.2475000000001095 0 336 0 -0.2325000000001626 0 337 0 -0.217500000000206 0 338 0 -0.2025000000002494 0 339 0 -0.1875000000002874 0 340 0 -0.1725000000003253 0 341 0 -0.157500000000366 0 342 0 -0.1425000000003665 0 343 0 -0.1275000000003271 0 344 0 -0.1125000000002922 0 345 0 -0.09750000000025186 0 346 0 -0.08250000000021215 0 347 0 -0.06750000000017453 0 348 0 -0.05250000000012314 0 349 0 -0.03750000000010306 0 350 0 -0.02250000000007324 0 351 0 -0.00750000000004003 0 352 0.4 -0.3780024001981511 0 353 0.4 -0.3570523051187844 0 354 0.4 -0.3370998336061458 0 355 0.4 -0.3180974798108416 0 356 0.4 -0.389001200099055 0 357 0.4 -0.3675273526585148 0 358 0.4 -0.3470760693624635 0 359 0.4 -0.3275986567085242 0 360 0.4 -0.3090487399054085 0 361 0.4 -0.2850000000000109 0 362 0.4 -0.2700000000000326 0 363 0.4 -0.2550000000000781 0 364 0.4 -0.240000000000141 0 365 0.4 -0.2250000000001843 0 366 0.4 -0.2100000000002277 0 367 0.4 -0.1950000000002711 0 368 0.4 -0.1800000000003036 0 369 0.4 -0.165000000000347 0 370 0.4 -0.150000000000385 0 371 0.4 -0.1350000000003481 0 372 0.4 -0.1200000000003112 0 373 0.4 -0.1050000000002732 0 374 0.4 -0.09000000000022879 0 375 0.4 -0.07500000000018975 0 376 0.4 -0.06000000000015071 0 377 0.4 -0.04500000000012139 0 378 0.4 -0.03000000000008241 0 379 0.4 -0.01500000000003687 0 380 0.4 -0.2925000000000165 0 381 0.4 -0.2775000000000127 0 382 0.4 -0.2625000000000469 0 383 0.4 -0.2475000000001095 0 384 0.4 -0.2325000000001626 0 385 0.4 -0.217500000000206 0 386 0.4 -0.2025000000002494 0 387 0.4 -0.1875000000002874 0 388 0.4 -0.1725000000003253 0 389 0.4 -0.157500000000366 0 390 0.4 -0.1425000000003665 0 391 0.4 -0.1275000000003271 0 392 0.4 -0.1125000000002922 0 393 0.4 -0.09750000000025186 0 394 0.4 -0.08250000000021215 0 395 0.4 -0.06750000000017453 0 396 0.4 -0.05250000000012314 0 397 0.4 -0.03750000000010306 0 398 0.4 -0.02250000000007324 0 399 0.4 -0.00750000000004003 0 400 0.6 -0.3780024001981511 0 401 0.6 -0.3570523051187844 0 402 0.6 -0.3370998336061458 0 403 0.6 -0.3180974798108416 0 404 0.6 -0.389001200099055 0 405 0.6 -0.3675273526585148 0 406 0.6 -0.3470760693624635 0 407 0.6 -0.3275986567085242 0 408 0.6 -0.3090487399054085 0 409 0.6 -0.2850000000000109 0 410 0.6 -0.2700000000000326 0 411 0.6 -0.2550000000000781 0 412 0.6 -0.240000000000141 0 413 0.6 -0.2250000000001843 0 414 0.6 -0.2100000000002277 0 415 0.6 -0.1950000000002711 0 416 0.6 -0.1800000000003036 0 417 0.6 -0.165000000000347 0 418 0.6 -0.150000000000385 0 419 0.6 -0.1350000000003481 0 420 0.6 -0.1200000000003112 0 421 0.6 -0.1050000000002732 0 422 0.6 -0.09000000000022879 0 423 0.6 -0.07500000000018975 0 424 0.6 -0.06000000000015071 0 425 0.6 -0.04500000000012139 0 426 0.6 -0.03000000000008241 0 427 0.6 -0.01500000000003687 0 428 0.6 -0.2925000000000165 0 429 0.6 -0.2775000000000127 0 430 0.6 -0.2625000000000469 0 431 0.6 -0.2475000000001095 0 432 0.6 -0.2325000000001626 0 433 0.6 -0.217500000000206 0 434 0.6 -0.2025000000002494 0 435 0.6 -0.1875000000002874 0 436 0.6 -0.1725000000003253 0 437 0.6 -0.157500000000366 0 438 0.6 -0.1425000000003665 0 439 0.6 -0.1275000000003271 0 440 0.6 -0.1125000000002922 0 441 0.6 -0.09750000000025186 0 442 0.6 -0.08250000000021215 0 443 0.6 -0.06750000000017453 0 444 0.6 -0.05250000000012314 0 445 0.6 -0.03750000000010306 0 446 0.6 -0.02250000000007324 0 447 0.6 -0.00750000000004003 0 448 0.009999999999981926 -0.01500000000003687 0 449 0.01999999999996121 -0.01500000000003687 0 450 0.02999999999994402 -0.01500000000003687 0 451 0.03999999999992995 -0.01500000000003687 0 452 0.04999999999991424 -0.01500000000003687 0 453 0.05999999999988689 -0.01500000000003687 0 454 0.06999999999985819 -0.01500000000003687 0 455 0.07999999999982403 -0.01500000000003687 0 456 0.08999999999979536 -0.01500000000003687 0 457 0.09999999999976665 -0.01500000000003687 0 458 0.109999999999738 -0.01500000000003687 0 459 0.1199999999997092 -0.01500000000003687 0 460 0.1299999999996806 -0.01500000000003687 0 461 0.1399999999996519 -0.01500000000003687 0 462 0.1499999999996177 -0.01500000000003687 0 463 0.1599999999995836 -0.01500000000003688 0 464 0.1699999999995549 -0.01500000000003687 0 465 0.1799999999995262 -0.01500000000003687 0 466 0.1899999999994975 -0.01500000000003687 0 467 0.1999999999994742 -0.01500000000003687 0 468 0.2099999999994975 -0.01500000000003687 0 469 0.2199999999995209 -0.01500000000003687 0 470 0.2299999999995495 -0.01500000000003687 0 471 0.2399999999995772 -0.01500000000003687 0 472 0.2499999999996016 -0.01500000000003687 0 473 0.2599999999996347 -0.01500000000003687 0 474 0.2699999999996558 -0.01500000000003687 0 475 0.2799999999996792 -0.01500000000003687 0 476 0.2899999999997154 -0.01500000000003687 0 477 0.2999999999997344 -0.01500000000003687 0 478 0.3099999999997534 -0.01500000000003687 0 479 0.3199999999997897 -0.01500000000003687 0 480 0.329999999999813 -0.01500000000003687 0 481 0.3399999999998342 -0.01500000000003687 0 482 0.3499999999998726 -0.01500000000003687 0 483 0.3599999999998915 -0.01500000000003687 0 484 0.3699999999999171 -0.01500000000003687 0 485 0.3799999999999512 -0.01500000000003687 0 486 0.3899999999999702 -0.01500000000003687 0 487 0.009999999999981926 -0.0300000000000824 0 488 0.01999999999996121 -0.0300000000000824 0 489 0.02999999999994402 -0.0300000000000824 0 490 0.03999999999992994 -0.03000000000008241 0 491 0.04999999999991423 -0.03000000000008241 0 492 0.05999999999988688 -0.03000000000008241 0 493 0.06999999999985819 -0.03000000000008241 0 494 0.07999999999982406 -0.03000000000008241 0 495 0.08999999999979534 -0.0300000000000824 0 496 0.09999999999976665 -0.03000000000008241 0 497 0.109999999999738 -0.03000000000008241 0 498 0.1199999999997092 -0.03000000000008241 0 499 0.1299999999996806 -0.03000000000008241 0 500 0.1399999999996518 -0.03000000000008241 0 501 0.1499999999996177 -0.03000000000008241 0 502 0.1599999999995836 -0.03000000000008241 0 503 0.1699999999995549 -0.0300000000000824 0 504 0.1799999999995262 -0.0300000000000824 0 505 0.1899999999994975 -0.03000000000008241 0 506 0.1999999999994742 -0.03000000000008241 0 507 0.2099999999994975 -0.03000000000008241 0 508 0.2199999999995209 -0.03000000000008241 0 509 0.2299999999995495 -0.03000000000008241 0 510 0.2399999999995772 -0.03000000000008241 0 511 0.2499999999996016 -0.03000000000008241 0 512 0.2599999999996346 -0.03000000000008241 0 513 0.2699999999996559 -0.03000000000008241 0 514 0.2799999999996791 -0.03000000000008241 0 515 0.2899999999997154 -0.03000000000008241 0 516 0.2999999999997344 -0.03000000000008241 0 517 0.3099999999997534 -0.03000000000008241 0 518 0.3199999999997897 -0.03000000000008241 0 519 0.329999999999813 -0.03000000000008241 0 520 0.3399999999998342 -0.03000000000008241 0 521 0.3499999999998726 -0.03000000000008241 0 522 0.3599999999998915 -0.0300000000000824 0 523 0.369999999999917 -0.0300000000000824 0 524 0.3799999999999512 -0.03000000000008241 0 525 0.38999999999997 -0.0300000000000824 0 526 0.009999999999981926 -0.04500000000012139 0 527 0.0199999999999612 -0.0450000000001214 0 528 0.02999999999994401 -0.0450000000001214 0 529 0.03999999999992995 -0.04500000000012139 0 530 0.04999999999991424 -0.04500000000012139 0 531 0.05999999999988688 -0.0450000000001214 0 532 0.06999999999985819 -0.0450000000001214 0 533 0.07999999999982406 -0.04500000000012138 0 534 0.08999999999979536 -0.04500000000012138 0 535 0.09999999999976665 -0.04500000000012139 0 536 0.109999999999738 -0.04500000000012138 0 537 0.1199999999997092 -0.04500000000012139 0 538 0.1299999999996806 -0.04500000000012139 0 539 0.1399999999996519 -0.04500000000012139 0 540 0.1499999999996177 -0.0450000000001214 0 541 0.1599999999995836 -0.04500000000012139 0 542 0.1699999999995549 -0.0450000000001214 0 543 0.1799999999995262 -0.04500000000012139 0 544 0.1899999999994975 -0.0450000000001214 0 545 0.1999999999994742 -0.04500000000012139 0 546 0.2099999999994975 -0.04500000000012139 0 547 0.2199999999995209 -0.04500000000012139 0 548 0.2299999999995495 -0.0450000000001214 0 549 0.2399999999995772 -0.0450000000001214 0 550 0.2499999999996015 -0.04500000000012139 0 551 0.2599999999996347 -0.04500000000012139 0 552 0.2699999999996559 -0.04500000000012139 0 553 0.2799999999996792 -0.04500000000012139 0 554 0.2899999999997154 -0.04500000000012139 0 555 0.2999999999997344 -0.04500000000012138 0 556 0.3099999999997534 -0.04500000000012138 0 557 0.3199999999997897 -0.04500000000012139 0 558 0.3299999999998131 -0.04500000000012138 0 559 0.3399999999998341 -0.04500000000012138 0 560 0.3499999999998727 -0.04500000000012139 0 561 0.3599999999998915 -0.04500000000012138 0 562 0.3699999999999171 -0.04500000000012139 0 563 0.3799999999999511 -0.04500000000012138 0 564 0.3899999999999702 -0.04500000000012139 0 565 0.009999999999981926 -0.06000000000015071 0 566 0.01999999999996121 -0.0600000000001507 0 567 0.02999999999994401 -0.06000000000015071 0 568 0.03999999999992994 -0.06000000000015071 0 569 0.04999999999991424 -0.06000000000015071 0 570 0.05999999999988688 -0.06000000000015071 0 571 0.06999999999985819 -0.06000000000015071 0 572 0.07999999999982405 -0.0600000000001507 0 573 0.08999999999979536 -0.06000000000015072 0 574 0.09999999999976666 -0.0600000000001507 0 575 0.109999999999738 -0.0600000000001507 0 576 0.1199999999997092 -0.0600000000001507 0 577 0.1299999999996806 -0.0600000000001507 0 578 0.1399999999996519 -0.06000000000015072 0 579 0.1499999999996177 -0.06000000000015071 0 580 0.1599999999995836 -0.06000000000015071 0 581 0.1699999999995549 -0.06000000000015072 0 582 0.1799999999995262 -0.06000000000015071 0 583 0.1899999999994975 -0.0600000000001507 0 584 0.1999999999994742 -0.06000000000015071 0 585 0.2099999999994975 -0.06000000000015071 0 586 0.2199999999995209 -0.06000000000015071 0 587 0.2299999999995495 -0.06000000000015071 0 588 0.2399999999995772 -0.06000000000015071 0 589 0.2499999999996015 -0.06000000000015071 0 590 0.2599999999996346 -0.0600000000001507 0 591 0.2699999999996557 -0.06000000000015071 0 592 0.2799999999996791 -0.0600000000001507 0 593 0.2899999999997154 -0.0600000000001507 0 594 0.2999999999997344 -0.0600000000001507 0 595 0.3099999999997534 -0.0600000000001507 0 596 0.3199999999997897 -0.06000000000015071 0 597 0.329999999999813 -0.06000000000015071 0 598 0.3399999999998341 -0.0600000000001507 0 599 0.3499999999998726 -0.06000000000015071 0 600 0.3599999999998915 -0.06000000000015071 0 601 0.3699999999999171 -0.06000000000015071 0 602 0.3799999999999513 -0.0600000000001507 0 603 0.3899999999999701 -0.06000000000015071 0 604 0.009999999999981926 -0.07500000000018975 0 605 0.01999999999996121 -0.07500000000018978 0 606 0.02999999999994401 -0.07500000000018975 0 607 0.03999999999992994 -0.07500000000018976 0 608 0.04999999999991424 -0.07500000000018975 0 609 0.05999999999988687 -0.07500000000018975 0 610 0.06999999999985819 -0.07500000000018975 0 611 0.07999999999982406 -0.07500000000018975 0 612 0.08999999999979534 -0.07500000000018975 0 613 0.09999999999976666 -0.07500000000018976 0 614 0.109999999999738 -0.07500000000018975 0 615 0.1199999999997092 -0.07500000000018975 0 616 0.1299999999996806 -0.07500000000018975 0 617 0.1399999999996519 -0.07500000000018975 0 618 0.1499999999996177 -0.07500000000018975 0 619 0.1599999999995836 -0.07500000000018975 0 620 0.1699999999995549 -0.07500000000018975 0 621 0.1799999999995262 -0.07500000000018976 0 622 0.1899999999994975 -0.07500000000018975 0 623 0.1999999999994742 -0.07500000000018975 0 624 0.2099999999994975 -0.07500000000018975 0 625 0.2199999999995209 -0.07500000000018975 0 626 0.2299999999995495 -0.07500000000018975 0 627 0.2399999999995772 -0.07500000000018975 0 628 0.2499999999996016 -0.07500000000018975 0 629 0.2599999999996346 -0.07500000000018975 0 630 0.2699999999996559 -0.07500000000018975 0 631 0.2799999999996792 -0.07500000000018975 0 632 0.2899999999997154 -0.07500000000018975 0 633 0.2999999999997344 -0.07500000000018975 0 634 0.3099999999997534 -0.07500000000018975 0 635 0.3199999999997897 -0.07500000000018975 0 636 0.329999999999813 -0.07500000000018975 0 637 0.3399999999998342 -0.07500000000018975 0 638 0.3499999999998726 -0.07500000000018976 0 639 0.3599999999998915 -0.07500000000018976 0 640 0.369999999999917 -0.07500000000018975 0 641 0.3799999999999514 -0.07500000000018975 0 642 0.3899999999999701 -0.07500000000018975 0 643 0.009999999999981926 -0.09000000000022877 0 644 0.01999999999996121 -0.09000000000022879 0 645 0.02999999999994401 -0.09000000000022879 0 646 0.03999999999992994 -0.09000000000022879 0 647 0.04999999999991424 -0.09000000000022877 0 648 0.05999999999988688 -0.09000000000022881 0 649 0.06999999999985819 -0.09000000000022877 0 650 0.07999999999982405 -0.09000000000022881 0 651 0.08999999999979536 -0.09000000000022877 0 652 0.09999999999976665 -0.09000000000022879 0 653 0.109999999999738 -0.09000000000022881 0 654 0.1199999999997092 -0.0900000000002288 0 655 0.1299999999996806 -0.09000000000022879 0 656 0.1399999999996519 -0.09000000000022879 0 657 0.1499999999996177 -0.09000000000022879 0 658 0.1599999999995836 -0.09000000000022879 0 659 0.1699999999995549 -0.09000000000022881 0 660 0.1799999999995262 -0.09000000000022879 0 661 0.1899999999994975 -0.09000000000022879 0 662 0.1999999999994742 -0.09000000000022879 0 663 0.2099999999994975 -0.09000000000022881 0 664 0.2199999999995209 -0.09000000000022881 0 665 0.2299999999995495 -0.09000000000022881 0 666 0.2399999999995773 -0.09000000000022879 0 667 0.2499999999996017 -0.09000000000022879 0 668 0.2599999999996346 -0.09000000000022879 0 669 0.2699999999996558 -0.09000000000022879 0 670 0.2799999999996791 -0.09000000000022879 0 671 0.2899999999997153 -0.09000000000022877 0 672 0.2999999999997344 -0.09000000000022877 0 673 0.3099999999997533 -0.09000000000022877 0 674 0.3199999999997897 -0.09000000000022879 0 675 0.329999999999813 -0.09000000000022879 0 676 0.3399999999998342 -0.09000000000022879 0 677 0.3499999999998726 -0.09000000000022879 0 678 0.3599999999998915 -0.09000000000022877 0 679 0.3699999999999171 -0.09000000000022879 0 680 0.3799999999999513 -0.09000000000022877 0 681 0.3899999999999702 -0.09000000000022879 0 682 0.009999999999981926 -0.1050000000002732 0 683 0.01999999999996121 -0.1050000000002733 0 684 0.02999999999994401 -0.1050000000002732 0 685 0.03999999999992994 -0.1050000000002732 0 686 0.04999999999991424 -0.1050000000002732 0 687 0.05999999999988688 -0.1050000000002732 0 688 0.06999999999985819 -0.1050000000002732 0 689 0.07999999999982406 -0.1050000000002732 0 690 0.08999999999979536 -0.1050000000002732 0 691 0.09999999999976666 -0.1050000000002732 0 692 0.109999999999738 -0.1050000000002732 0 693 0.1199999999997092 -0.1050000000002732 0 694 0.1299999999996806 -0.1050000000002732 0 695 0.1399999999996519 -0.1050000000002732 0 696 0.1499999999996177 -0.1050000000002732 0 697 0.1599999999995836 -0.1050000000002732 0 698 0.1699999999995549 -0.1050000000002732 0 699 0.1799999999995262 -0.1050000000002732 0 700 0.1899999999994975 -0.1050000000002732 0 701 0.1999999999994742 -0.1050000000002732 0 702 0.2099999999994975 -0.1050000000002732 0 703 0.2199999999995209 -0.1050000000002732 0 704 0.2299999999995495 -0.1050000000002732 0 705 0.2399999999995772 -0.1050000000002732 0 706 0.2499999999996015 -0.1050000000002732 0 707 0.2599999999996346 -0.1050000000002732 0 708 0.2699999999996559 -0.1050000000002732 0 709 0.2799999999996791 -0.1050000000002732 0 710 0.2899999999997154 -0.1050000000002732 0 711 0.2999999999997344 -0.1050000000002732 0 712 0.3099999999997534 -0.1050000000002732 0 713 0.3199999999997897 -0.1050000000002732 0 714 0.329999999999813 -0.1050000000002732 0 715 0.3399999999998342 -0.1050000000002732 0 716 0.3499999999998726 -0.1050000000002732 0 717 0.3599999999998915 -0.1050000000002732 0 718 0.3699999999999171 -0.1050000000002732 0 719 0.3799999999999513 -0.1050000000002732 0 720 0.3899999999999702 -0.1050000000002732 0 721 0.009999999999981926 -0.1200000000003112 0 722 0.01999999999996121 -0.1200000000003112 0 723 0.02999999999994402 -0.1200000000003112 0 724 0.03999999999992994 -0.1200000000003112 0 725 0.04999999999991424 -0.1200000000003112 0 726 0.05999999999988688 -0.1200000000003112 0 727 0.06999999999985819 -0.1200000000003112 0 728 0.07999999999982406 -0.1200000000003112 0 729 0.08999999999979536 -0.1200000000003112 0 730 0.09999999999976666 -0.1200000000003112 0 731 0.109999999999738 -0.1200000000003112 0 732 0.1199999999997092 -0.1200000000003112 0 733 0.1299999999996806 -0.1200000000003112 0 734 0.1399999999996519 -0.1200000000003112 0 735 0.1499999999996177 -0.1200000000003112 0 736 0.1599999999995836 -0.1200000000003112 0 737 0.1699999999995549 -0.1200000000003112 0 738 0.1799999999995262 -0.1200000000003112 0 739 0.1899999999994975 -0.1200000000003112 0 740 0.1999999999994742 -0.1200000000003112 0 741 0.2099999999994975 -0.1200000000003112 0 742 0.2199999999995209 -0.1200000000003112 0 743 0.2299999999995495 -0.1200000000003112 0 744 0.2399999999995772 -0.1200000000003112 0 745 0.2499999999996016 -0.1200000000003112 0 746 0.2599999999996346 -0.1200000000003112 0 747 0.2699999999996558 -0.1200000000003112 0 748 0.2799999999996792 -0.1200000000003112 0 749 0.2899999999997154 -0.1200000000003112 0 750 0.2999999999997344 -0.1200000000003112 0 751 0.3099999999997534 -0.1200000000003112 0 752 0.3199999999997897 -0.1200000000003112 0 753 0.329999999999813 -0.1200000000003112 0 754 0.3399999999998342 -0.1200000000003112 0 755 0.3499999999998726 -0.1200000000003112 0 756 0.3599999999998915 -0.1200000000003112 0 757 0.3699999999999171 -0.1200000000003112 0 758 0.3799999999999513 -0.1200000000003112 0 759 0.3899999999999702 -0.1200000000003112 0 760 0.009999999999981926 -0.1350000000003481 0 761 0.01999999999996121 -0.1350000000003481 0 762 0.02999999999994402 -0.1350000000003481 0 763 0.03999999999992994 -0.1350000000003481 0 764 0.04999999999991424 -0.1350000000003481 0 765 0.05999999999988688 -0.1350000000003481 0 766 0.06999999999985819 -0.1350000000003481 0 767 0.07999999999982406 -0.1350000000003481 0 768 0.08999999999979536 -0.1350000000003481 0 769 0.09999999999976665 -0.1350000000003481 0 770 0.109999999999738 -0.1350000000003481 0 771 0.1199999999997092 -0.135000000000348 0 772 0.1299999999996806 -0.1350000000003481 0 773 0.1399999999996519 -0.1350000000003481 0 774 0.1499999999996177 -0.1350000000003481 0 775 0.1599999999995836 -0.1350000000003481 0 776 0.1699999999995549 -0.1350000000003481 0 777 0.1799999999995262 -0.1350000000003481 0 778 0.1899999999994975 -0.1350000000003481 0 779 0.1999999999994742 -0.1350000000003481 0 780 0.2099999999994975 -0.1350000000003481 0 781 0.2199999999995209 -0.1350000000003481 0 782 0.2299999999995495 -0.1350000000003481 0 783 0.2399999999995772 -0.1350000000003481 0 784 0.2499999999996016 -0.1350000000003481 0 785 0.2599999999996346 -0.1350000000003481 0 786 0.2699999999996559 -0.135000000000348 0 787 0.2799999999996791 -0.1350000000003481 0 788 0.2899999999997154 -0.1350000000003481 0 789 0.2999999999997344 -0.1350000000003481 0 790 0.3099999999997534 -0.1350000000003481 0 791 0.3199999999997897 -0.1350000000003481 0 792 0.329999999999813 -0.1350000000003481 0 793 0.3399999999998342 -0.1350000000003481 0 794 0.3499999999998726 -0.1350000000003481 0 795 0.3599999999998915 -0.1350000000003481 0 796 0.369999999999917 -0.1350000000003481 0 797 0.3799999999999512 -0.1350000000003481 0 798 0.3899999999999702 -0.1350000000003481 0 799 0.009999999999981926 -0.150000000000385 0 800 0.0199999999999612 -0.1500000000003851 0 801 0.02999999999994401 -0.1500000000003849 0 802 0.03999999999992994 -0.150000000000385 0 803 0.04999999999991424 -0.150000000000385 0 804 0.05999999999988688 -0.150000000000385 0 805 0.06999999999985819 -0.150000000000385 0 806 0.07999999999982405 -0.150000000000385 0 807 0.08999999999979536 -0.150000000000385 0 808 0.09999999999976666 -0.150000000000385 0 809 0.109999999999738 -0.150000000000385 0 810 0.1199999999997092 -0.1500000000003851 0 811 0.1299999999996806 -0.1500000000003849 0 812 0.1399999999996519 -0.150000000000385 0 813 0.1499999999996177 -0.150000000000385 0 814 0.1599999999995836 -0.150000000000385 0 815 0.1699999999995549 -0.150000000000385 0 816 0.1799999999995262 -0.150000000000385 0 817 0.1899999999994975 -0.150000000000385 0 818 0.1999999999994742 -0.150000000000385 0 819 0.2099999999994975 -0.150000000000385 0 820 0.2199999999995209 -0.150000000000385 0 821 0.2299999999995495 -0.150000000000385 0 822 0.2399999999995772 -0.150000000000385 0 823 0.2499999999996015 -0.150000000000385 0 824 0.2599999999996346 -0.150000000000385 0 825 0.2699999999996559 -0.150000000000385 0 826 0.2799999999996792 -0.150000000000385 0 827 0.2899999999997154 -0.150000000000385 0 828 0.2999999999997344 -0.150000000000385 0 829 0.3099999999997534 -0.150000000000385 0 830 0.3199999999997897 -0.150000000000385 0 831 0.329999999999813 -0.150000000000385 0 832 0.3399999999998342 -0.150000000000385 0 833 0.3499999999998726 -0.150000000000385 0 834 0.3599999999998915 -0.150000000000385 0 835 0.3699999999999171 -0.150000000000385 0 836 0.3799999999999512 -0.150000000000385 0 837 0.3899999999999702 -0.150000000000385 0 838 0.009999999999981926 -0.165000000000347 0 839 0.01999999999996121 -0.165000000000347 0 840 0.02999999999994401 -0.165000000000347 0 841 0.03999999999992994 -0.165000000000347 0 842 0.04999999999991424 -0.165000000000347 0 843 0.05999999999988688 -0.165000000000347 0 844 0.06999999999985818 -0.165000000000347 0 845 0.07999999999982406 -0.165000000000347 0 846 0.08999999999979536 -0.165000000000347 0 847 0.09999999999976666 -0.165000000000347 0 848 0.109999999999738 -0.165000000000347 0 849 0.1199999999997092 -0.165000000000347 0 850 0.1299999999996806 -0.165000000000347 0 851 0.1399999999996519 -0.165000000000347 0 852 0.1499999999996177 -0.165000000000347 0 853 0.1599999999995836 -0.165000000000347 0 854 0.1699999999995549 -0.165000000000347 0 855 0.1799999999995262 -0.165000000000347 0 856 0.1899999999994975 -0.165000000000347 0 857 0.1999999999994742 -0.165000000000347 0 858 0.2099999999994975 -0.165000000000347 0 859 0.2199999999995209 -0.165000000000347 0 860 0.2299999999995495 -0.165000000000347 0 861 0.2399999999995772 -0.165000000000347 0 862 0.2499999999996016 -0.165000000000347 0 863 0.2599999999996346 -0.165000000000347 0 864 0.2699999999996559 -0.165000000000347 0 865 0.2799999999996792 -0.165000000000347 0 866 0.2899999999997154 -0.165000000000347 0 867 0.2999999999997344 -0.165000000000347 0 868 0.3099999999997534 -0.165000000000347 0 869 0.3199999999997897 -0.165000000000347 0 870 0.329999999999813 -0.165000000000347 0 871 0.3399999999998342 -0.165000000000347 0 872 0.3499999999998726 -0.165000000000347 0 873 0.3599999999998915 -0.165000000000347 0 874 0.3699999999999171 -0.165000000000347 0 875 0.3799999999999512 -0.165000000000347 0 876 0.3899999999999702 -0.165000000000347 0 877 0.009999999999981926 -0.1800000000003037 0 878 0.01999999999996121 -0.1800000000003036 0 879 0.02999999999994401 -0.1800000000003036 0 880 0.03999999999992994 -0.1800000000003036 0 881 0.04999999999991424 -0.1800000000003036 0 882 0.05999999999988688 -0.1800000000003037 0 883 0.06999999999985819 -0.1800000000003036 0 884 0.07999999999982406 -0.1800000000003036 0 885 0.08999999999979536 -0.1800000000003036 0 886 0.09999999999976666 -0.1800000000003036 0 887 0.109999999999738 -0.1800000000003036 0 888 0.1199999999997092 -0.1800000000003036 0 889 0.1299999999996806 -0.1800000000003036 0 890 0.1399999999996519 -0.1800000000003036 0 891 0.1499999999996177 -0.1800000000003036 0 892 0.1599999999995836 -0.1800000000003036 0 893 0.1699999999995549 -0.1800000000003036 0 894 0.1799999999995262 -0.1800000000003036 0 895 0.1899999999994975 -0.1800000000003036 0 896 0.1999999999994742 -0.1800000000003036 0 897 0.2099999999994975 -0.1800000000003036 0 898 0.2199999999995209 -0.1800000000003036 0 899 0.2299999999995495 -0.1800000000003036 0 900 0.2399999999995773 -0.1800000000003036 0 901 0.2499999999996015 -0.1800000000003036 0 902 0.2599999999996346 -0.1800000000003036 0 903 0.2699999999996558 -0.1800000000003036 0 904 0.2799999999996792 -0.1800000000003036 0 905 0.2899999999997154 -0.1800000000003036 0 906 0.2999999999997344 -0.1800000000003036 0 907 0.3099999999997534 -0.1800000000003036 0 908 0.3199999999997897 -0.1800000000003036 0 909 0.329999999999813 -0.1800000000003036 0 910 0.3399999999998342 -0.1800000000003036 0 911 0.3499999999998726 -0.1800000000003036 0 912 0.3599999999998915 -0.1800000000003036 0 913 0.3699999999999171 -0.1800000000003036 0 914 0.3799999999999512 -0.1800000000003036 0 915 0.3899999999999702 -0.1800000000003036 0 916 0.009999999999981926 -0.1950000000002711 0 917 0.01999999999996121 -0.1950000000002711 0 918 0.029999999999944 -0.1950000000002711 0 919 0.03999999999992994 -0.1950000000002711 0 920 0.04999999999991424 -0.1950000000002711 0 921 0.05999999999988688 -0.1950000000002711 0 922 0.06999999999985819 -0.1950000000002711 0 923 0.07999999999982406 -0.1950000000002711 0 924 0.08999999999979536 -0.1950000000002712 0 925 0.09999999999976666 -0.1950000000002711 0 926 0.109999999999738 -0.1950000000002711 0 927 0.1199999999997092 -0.1950000000002712 0 928 0.1299999999996806 -0.1950000000002711 0 929 0.1399999999996519 -0.1950000000002711 0 930 0.1499999999996177 -0.1950000000002711 0 931 0.1599999999995836 -0.1950000000002711 0 932 0.1699999999995549 -0.1950000000002711 0 933 0.1799999999995262 -0.1950000000002711 0 934 0.1899999999994975 -0.1950000000002711 0 935 0.1999999999994742 -0.1950000000002711 0 936 0.2099999999994975 -0.1950000000002711 0 937 0.2199999999995209 -0.1950000000002711 0 938 0.2299999999995495 -0.1950000000002711 0 939 0.2399999999995772 -0.1950000000002711 0 940 0.2499999999996016 -0.1950000000002711 0 941 0.2599999999996346 -0.1950000000002711 0 942 0.2699999999996559 -0.1950000000002712 0 943 0.2799999999996792 -0.1950000000002711 0 944 0.2899999999997154 -0.1950000000002711 0 945 0.2999999999997344 -0.1950000000002711 0 946 0.3099999999997534 -0.1950000000002711 0 947 0.3199999999997897 -0.1950000000002711 0 948 0.329999999999813 -0.1950000000002711 0 949 0.3399999999998342 -0.1950000000002711 0 950 0.3499999999998726 -0.1950000000002711 0 951 0.3599999999998915 -0.1950000000002711 0 952 0.3699999999999171 -0.1950000000002711 0 953 0.3799999999999513 -0.1950000000002711 0 954 0.3899999999999702 -0.1950000000002711 0 955 0.009999999999981926 -0.2100000000002278 0 956 0.01999999999996121 -0.2100000000002277 0 957 0.02999999999994402 -0.2100000000002277 0 958 0.03999999999992995 -0.2100000000002277 0 959 0.04999999999991424 -0.2100000000002277 0 960 0.05999999999988687 -0.2100000000002277 0 961 0.06999999999985819 -0.2100000000002277 0 962 0.07999999999982406 -0.2100000000002277 0 963 0.08999999999979536 -0.2100000000002277 0 964 0.09999999999976666 -0.2100000000002277 0 965 0.109999999999738 -0.2100000000002277 0 966 0.1199999999997092 -0.2100000000002277 0 967 0.1299999999996806 -0.2100000000002277 0 968 0.1399999999996518 -0.2100000000002278 0 969 0.1499999999996177 -0.2100000000002277 0 970 0.1599999999995836 -0.2100000000002277 0 971 0.1699999999995549 -0.2100000000002277 0 972 0.1799999999995262 -0.2100000000002277 0 973 0.1899999999994975 -0.2100000000002277 0 974 0.1999999999994742 -0.2100000000002277 0 975 0.2099999999994975 -0.2100000000002277 0 976 0.2199999999995209 -0.2100000000002277 0 977 0.2299999999995495 -0.2100000000002277 0 978 0.2399999999995771 -0.2100000000002277 0 979 0.2499999999996015 -0.2100000000002277 0 980 0.2599999999996346 -0.2100000000002277 0 981 0.2699999999996559 -0.2100000000002277 0 982 0.2799999999996791 -0.2100000000002277 0 983 0.2899999999997154 -0.2100000000002277 0 984 0.2999999999997344 -0.2100000000002277 0 985 0.3099999999997534 -0.2100000000002277 0 986 0.3199999999997897 -0.2100000000002277 0 987 0.329999999999813 -0.2100000000002277 0 988 0.3399999999998342 -0.2100000000002277 0 989 0.3499999999998726 -0.2100000000002277 0 990 0.3599999999998915 -0.2100000000002277 0 991 0.3699999999999171 -0.2100000000002277 0 992 0.3799999999999512 -0.2100000000002277 0 993 0.3899999999999701 -0.2100000000002277 0 994 0.009999999999981926 -0.2250000000001843 0 995 0.01999999999996121 -0.2250000000001843 0 996 0.02999999999994402 -0.2250000000001844 0 997 0.03999999999992994 -0.2250000000001843 0 998 0.04999999999991424 -0.2250000000001843 0 999 0.05999999999988688 -0.2250000000001843 0 1000 0.06999999999985819 -0.2250000000001843 0 1001 0.07999999999982406 -0.2250000000001843 0 1002 0.08999999999979536 -0.2250000000001843 0 1003 0.09999999999976666 -0.2250000000001843 0 1004 0.109999999999738 -0.2250000000001843 0 1005 0.1199999999997092 -0.2250000000001843 0 1006 0.1299999999996806 -0.2250000000001843 0 1007 0.1399999999996519 -0.2250000000001843 0 1008 0.1499999999996177 -0.2250000000001843 0 1009 0.1599999999995836 -0.2250000000001843 0 1010 0.1699999999995549 -0.2250000000001843 0 1011 0.1799999999995262 -0.2250000000001843 0 1012 0.1899999999994975 -0.2250000000001843 0 1013 0.1999999999994742 -0.2250000000001843 0 1014 0.2099999999994975 -0.2250000000001843 0 1015 0.2199999999995209 -0.2250000000001843 0 1016 0.2299999999995495 -0.2250000000001843 0 1017 0.2399999999995772 -0.2250000000001843 0 1018 0.2499999999996015 -0.2250000000001843 0 1019 0.2599999999996346 -0.2250000000001843 0 1020 0.2699999999996558 -0.2250000000001843 0 1021 0.2799999999996792 -0.2250000000001843 0 1022 0.2899999999997154 -0.2250000000001843 0 1023 0.2999999999997344 -0.2250000000001843 0 1024 0.3099999999997534 -0.2250000000001843 0 1025 0.3199999999997897 -0.2250000000001843 0 1026 0.329999999999813 -0.2250000000001843 0 1027 0.3399999999998342 -0.2250000000001843 0 1028 0.3499999999998726 -0.2250000000001843 0 1029 0.3599999999998915 -0.2250000000001843 0 1030 0.3699999999999171 -0.2250000000001843 0 1031 0.3799999999999513 -0.2250000000001843 0 1032 0.3899999999999701 -0.2250000000001843 0 1033 0.009999999999981926 -0.240000000000141 0 1034 0.01999999999996121 -0.240000000000141 0 1035 0.02999999999994401 -0.240000000000141 0 1036 0.03999999999992994 -0.2400000000001409 0 1037 0.04999999999991424 -0.240000000000141 0 1038 0.05999999999988688 -0.240000000000141 0 1039 0.06999999999985819 -0.240000000000141 0 1040 0.07999999999982405 -0.2400000000001409 0 1041 0.08999999999979536 -0.2400000000001409 0 1042 0.09999999999976666 -0.240000000000141 0 1043 0.109999999999738 -0.240000000000141 0 1044 0.1199999999997092 -0.240000000000141 0 1045 0.1299999999996806 -0.240000000000141 0 1046 0.1399999999996519 -0.2400000000001409 0 1047 0.1499999999996177 -0.240000000000141 0 1048 0.1599999999995836 -0.240000000000141 0 1049 0.1699999999995549 -0.2400000000001409 0 1050 0.1799999999995262 -0.240000000000141 0 1051 0.1899999999994975 -0.240000000000141 0 1052 0.1999999999994742 -0.2400000000001409 0 1053 0.2099999999994975 -0.240000000000141 0 1054 0.2199999999995209 -0.240000000000141 0 1055 0.2299999999995495 -0.2400000000001409 0 1056 0.2399999999995772 -0.240000000000141 0 1057 0.2499999999996016 -0.240000000000141 0 1058 0.2599999999996346 -0.2400000000001409 0 1059 0.2699999999996559 -0.2400000000001409 0 1060 0.2799999999996791 -0.240000000000141 0 1061 0.2899999999997154 -0.240000000000141 0 1062 0.2999999999997344 -0.240000000000141 0 1063 0.3099999999997534 -0.240000000000141 0 1064 0.3199999999997897 -0.240000000000141 0 1065 0.329999999999813 -0.240000000000141 0 1066 0.3399999999998342 -0.240000000000141 0 1067 0.3499999999998726 -0.240000000000141 0 1068 0.3599999999998915 -0.240000000000141 0 1069 0.3699999999999171 -0.240000000000141 0 1070 0.3799999999999514 -0.240000000000141 0 1071 0.3899999999999701 -0.240000000000141 0 1072 0.009999999999981926 -0.2550000000000781 0 1073 0.01999999999996121 -0.2550000000000781 0 1074 0.02999999999994401 -0.2550000000000781 0 1075 0.03999999999992994 -0.2550000000000781 0 1076 0.04999999999991424 -0.2550000000000781 0 1077 0.05999999999988688 -0.2550000000000781 0 1078 0.06999999999985818 -0.255000000000078 0 1079 0.07999999999982405 -0.2550000000000781 0 1080 0.08999999999979536 -0.2550000000000781 0 1081 0.09999999999976665 -0.2550000000000781 0 1082 0.109999999999738 -0.2550000000000781 0 1083 0.1199999999997092 -0.2550000000000781 0 1084 0.1299999999996806 -0.2550000000000781 0 1085 0.1399999999996519 -0.2550000000000781 0 1086 0.1499999999996177 -0.2550000000000781 0 1087 0.1599999999995836 -0.2550000000000781 0 1088 0.1699999999995549 -0.2550000000000781 0 1089 0.1799999999995262 -0.2550000000000781 0 1090 0.1899999999994975 -0.2550000000000781 0 1091 0.1999999999994742 -0.2550000000000781 0 1092 0.2099999999994975 -0.2550000000000781 0 1093 0.2199999999995209 -0.2550000000000781 0 1094 0.2299999999995495 -0.2550000000000781 0 1095 0.2399999999995772 -0.2550000000000781 0 1096 0.2499999999996015 -0.2550000000000781 0 1097 0.2599999999996346 -0.2550000000000781 0 1098 0.2699999999996559 -0.2550000000000781 0 1099 0.2799999999996792 -0.2550000000000781 0 1100 0.2899999999997154 -0.2550000000000781 0 1101 0.2999999999997344 -0.2550000000000781 0 1102 0.3099999999997534 -0.2550000000000781 0 1103 0.3199999999997897 -0.2550000000000781 0 1104 0.329999999999813 -0.2550000000000781 0 1105 0.3399999999998342 -0.2550000000000781 0 1106 0.3499999999998726 -0.2550000000000781 0 1107 0.3599999999998915 -0.2550000000000781 0 1108 0.369999999999917 -0.2550000000000781 0 1109 0.3799999999999512 -0.2550000000000781 0 1110 0.3899999999999702 -0.2550000000000781 0 1111 0.009999999999981926 -0.2700000000000326 0 1112 0.01999999999996121 -0.2700000000000326 0 1113 0.02999999999994402 -0.2700000000000326 0 1114 0.03999999999992994 -0.2700000000000326 0 1115 0.04999999999991424 -0.2700000000000326 0 1116 0.05999999999988687 -0.2700000000000326 0 1117 0.06999999999985819 -0.2700000000000326 0 1118 0.07999999999982406 -0.2700000000000326 0 1119 0.08999999999979536 -0.2700000000000326 0 1120 0.09999999999976666 -0.2700000000000326 0 1121 0.109999999999738 -0.2700000000000326 0 1122 0.1199999999997092 -0.2700000000000326 0 1123 0.1299999999996806 -0.2700000000000326 0 1124 0.1399999999996518 -0.2700000000000326 0 1125 0.1499999999996177 -0.2700000000000326 0 1126 0.1599999999995836 -0.2700000000000326 0 1127 0.1699999999995549 -0.2700000000000326 0 1128 0.1799999999995262 -0.2700000000000326 0 1129 0.1899999999994975 -0.2700000000000326 0 1130 0.1999999999994742 -0.2700000000000326 0 1131 0.2099999999994975 -0.2700000000000326 0 1132 0.2199999999995209 -0.2700000000000326 0 1133 0.2299999999995495 -0.2700000000000326 0 1134 0.2399999999995772 -0.2700000000000326 0 1135 0.2499999999996015 -0.2700000000000326 0 1136 0.2599999999996347 -0.2700000000000326 0 1137 0.2699999999996558 -0.2700000000000326 0 1138 0.2799999999996792 -0.2700000000000326 0 1139 0.2899999999997154 -0.2700000000000326 0 1140 0.2999999999997344 -0.2700000000000326 0 1141 0.3099999999997534 -0.2700000000000326 0 1142 0.3199999999997897 -0.2700000000000326 0 1143 0.329999999999813 -0.2700000000000326 0 1144 0.3399999999998342 -0.2700000000000326 0 1145 0.3499999999998726 -0.2700000000000326 0 1146 0.3599999999998915 -0.2700000000000326 0 1147 0.369999999999917 -0.2700000000000326 0 1148 0.3799999999999512 -0.2700000000000326 0 1149 0.3899999999999702 -0.2700000000000326 0 1150 0.009999999999981924 -0.2850000000000109 0 1151 0.01999999999996121 -0.2850000000000109 0 1152 0.02999999999994401 -0.2850000000000109 0 1153 0.03999999999992994 -0.2850000000000107 0 1154 0.04999999999991425 -0.2850000000000109 0 1155 0.05999999999988687 -0.2850000000000109 0 1156 0.06999999999985819 -0.2850000000000108 0 1157 0.07999999999982406 -0.2850000000000109 0 1158 0.08999999999979536 -0.2850000000000109 0 1159 0.09999999999976665 -0.2850000000000109 0 1160 0.109999999999738 -0.2850000000000109 0 1161 0.1199999999997092 -0.2850000000000108 0 1162 0.1299999999996806 -0.2850000000000109 0 1163 0.1399999999996518 -0.2850000000000109 0 1164 0.1499999999996177 -0.2850000000000109 0 1165 0.1599999999995836 -0.2850000000000109 0 1166 0.1699999999995549 -0.2850000000000108 0 1167 0.1799999999995262 -0.2850000000000108 0 1168 0.1899999999994975 -0.2850000000000109 0 1169 0.1999999999994742 -0.2850000000000108 0 1170 0.2099999999994975 -0.2850000000000109 0 1171 0.2199999999995209 -0.2850000000000109 0 1172 0.2299999999995495 -0.2850000000000109 0 1173 0.2399999999995772 -0.2850000000000109 0 1174 0.2499999999996016 -0.2850000000000108 0 1175 0.2599999999996347 -0.2850000000000109 0 1176 0.2699999999996558 -0.2850000000000109 0 1177 0.2799999999996791 -0.2850000000000109 0 1178 0.2899999999997154 -0.2850000000000109 0 1179 0.2999999999997344 -0.2850000000000108 0 1180 0.3099999999997534 -0.2850000000000109 0 1181 0.3199999999997897 -0.2850000000000109 0 1182 0.329999999999813 -0.2850000000000109 0 1183 0.3399999999998342 -0.2850000000000109 0 1184 0.3499999999998726 -0.2850000000000109 0 1185 0.3599999999998915 -0.285000000000011 0 1186 0.3699999999999171 -0.2850000000000108 0 1187 0.3799999999999513 -0.2850000000000109 0 1188 0.3899999999999701 -0.2850000000000109 0 1189 0.004999999999990963 -0.007500000000018436 0 1190 0.004999999999990963 -0.01500000000003687 0 1191 0.009999999999981926 -0.007500000000018436 0 1192 0.01499999999997157 -0.007500000000018436 0 1193 0.01499999999997157 -0.01500000000003687 0 1194 0.01999999999996121 -0.007500000000018436 0 1195 0.02499999999995261 -0.007500000000018436 0 1196 0.02499999999995261 -0.01500000000003687 0 1197 0.02999999999994402 -0.007500000000018437 0 1198 0.03499999999993698 -0.007500000000018437 0 1199 0.03499999999993698 -0.01500000000003687 0 1200 0.03999999999992994 -0.007500000000018436 0 1201 0.04499999999992209 -0.007500000000018436 0 1202 0.04499999999992209 -0.01500000000003687 0 1203 0.04999999999991424 -0.007500000000018437 0 1204 0.05499999999990056 -0.007500000000018437 0 1205 0.05499999999990056 -0.01500000000003687 0 1206 0.05999999999988688 -0.007500000000018437 0 1207 0.06499999999987254 -0.007500000000018437 0 1208 0.06499999999987254 -0.01500000000003687 0 1209 0.06999999999985819 -0.007500000000018436 0 1210 0.07499999999984112 -0.007500000000018436 0 1211 0.07499999999984111 -0.01500000000003687 0 1212 0.07999999999982405 -0.007500000000018436 0 1213 0.0849999999998097 -0.007500000000018436 0 1214 0.0849999999998097 -0.01500000000003687 0 1215 0.08999999999979536 -0.007500000000018436 0 1216 0.09499999999978101 -0.007500000000018436 0 1217 0.09499999999978101 -0.01500000000003687 0 1218 0.09999999999976666 -0.007500000000018437 0 1219 0.1049999999997523 -0.007500000000018437 0 1220 0.1049999999997523 -0.01500000000003687 0 1221 0.109999999999738 -0.007500000000018436 0 1222 0.1149999999997236 -0.007500000000018436 0 1223 0.1149999999997236 -0.01500000000003687 0 1224 0.1199999999997092 -0.007500000000018436 0 1225 0.1249999999996949 -0.007500000000018436 0 1226 0.1249999999996949 -0.01500000000003687 0 1227 0.1299999999996806 -0.007500000000018436 0 1228 0.1349999999996662 -0.007500000000018436 0 1229 0.1349999999996662 -0.01500000000003687 0 1230 0.1399999999996519 -0.007500000000018435 0 1231 0.1449999999996348 -0.007500000000018435 0 1232 0.1449999999996348 -0.01500000000003687 0 1233 0.1499999999996177 -0.007500000000018437 0 1234 0.1549999999996007 -0.007500000000018437 0 1235 0.1549999999996007 -0.01500000000003688 0 1236 0.1599999999995836 -0.007500000000018438 0 1237 0.1649999999995692 -0.007500000000018438 0 1238 0.1649999999995692 -0.01500000000003687 0 1239 0.1699999999995549 -0.007500000000018436 0 1240 0.1749999999995406 -0.007500000000018436 0 1241 0.1749999999995406 -0.01500000000003687 0 1242 0.1799999999995262 -0.007500000000018436 0 1243 0.1849999999995118 -0.007500000000018436 0 1244 0.1849999999995118 -0.01500000000003687 0 1245 0.1899999999994975 -0.007500000000018436 0 1246 0.1949999999994859 -0.007500000000018436 0 1247 0.1949999999994859 -0.01500000000003687 0 1248 0.1999999999994742 -0.007500000000018436 0 1249 0.2049999999994859 -0.007500000000018436 0 1250 0.2049999999994859 -0.01500000000003687 0 1251 0.2099999999994975 -0.007500000000018436 0 1252 0.2149999999995092 -0.007500000000018436 0 1253 0.2149999999995092 -0.01500000000003687 0 1254 0.2199999999995209 -0.007500000000018436 0 1255 0.2249999999995352 -0.007500000000018436 0 1256 0.2249999999995352 -0.01500000000003687 0 1257 0.2299999999995495 -0.007500000000018436 0 1258 0.2349999999995633 -0.007500000000018436 0 1259 0.2349999999995633 -0.01500000000003687 0 1260 0.2399999999995772 -0.007500000000018436 0 1261 0.2449999999995894 -0.007500000000018436 0 1262 0.2449999999995894 -0.01500000000003687 0 1263 0.2499999999996016 -0.007500000000018436 0 1264 0.2549999999996181 -0.007500000000018436 0 1265 0.2549999999996181 -0.01500000000003687 0 1266 0.2599999999996346 -0.007500000000018436 0 1267 0.2649999999996452 -0.007500000000018436 0 1268 0.2649999999996452 -0.01500000000003687 0 1269 0.2699999999996558 -0.007500000000018436 0 1270 0.2749999999996675 -0.007500000000018436 0 1271 0.2749999999996675 -0.01500000000003687 0 1272 0.2799999999996792 -0.007500000000018436 0 1273 0.2849999999996973 -0.007500000000018436 0 1274 0.2849999999996973 -0.01500000000003687 0 1275 0.2899999999997154 -0.007500000000018436 0 1276 0.2949999999997249 -0.007500000000018436 0 1277 0.2949999999997249 -0.01500000000003687 0 1278 0.2999999999997344 -0.007500000000018436 0 1279 0.3049999999997439 -0.007500000000018436 0 1280 0.3049999999997439 -0.01500000000003687 0 1281 0.3099999999997534 -0.007500000000018436 0 1282 0.3149999999997715 -0.007500000000018436 0 1283 0.3149999999997715 -0.01500000000003687 0 1284 0.3199999999997897 -0.007500000000018436 0 1285 0.3249999999998013 -0.007500000000018436 0 1286 0.3249999999998013 -0.01500000000003687 0 1287 0.329999999999813 -0.007500000000018436 0 1288 0.3349999999998236 -0.007500000000018436 0 1289 0.3349999999998236 -0.01500000000003687 0 1290 0.3399999999998342 -0.007500000000018436 0 1291 0.3449999999998534 -0.007500000000018436 0 1292 0.3449999999998534 -0.01500000000003687 0 1293 0.3499999999998726 -0.007500000000018436 0 1294 0.3549999999998821 -0.007500000000018436 0 1295 0.3549999999998821 -0.01500000000003687 0 1296 0.3599999999998915 -0.007500000000018436 0 1297 0.3649999999999043 -0.007500000000018436 0 1298 0.3649999999999043 -0.01500000000003687 0 1299 0.3699999999999171 -0.007500000000018436 0 1300 0.3749999999999342 -0.007500000000018436 0 1301 0.3749999999999342 -0.01500000000003687 0 1302 0.3799999999999512 -0.007500000000018436 0 1303 0.3849999999999607 -0.007500000000018436 0 1304 0.3849999999999607 -0.01500000000003687 0 1305 0.3899999999999702 -0.007500000000018436 0 1306 0.3949999999999851 -0.007500000000018436 0 1307 0.3949999999999851 -0.01500000000003687 0 1308 0.004999999999990963 -0.02250000000005964 0 1309 0.004999999999990963 -0.0300000000000824 0 1310 0.009999999999981926 -0.02250000000005964 0 1311 0.01499999999997157 -0.02250000000005964 0 1312 0.01499999999997157 -0.0300000000000824 0 1313 0.01999999999996121 -0.02250000000005964 0 1314 0.02499999999995261 -0.02250000000005964 0 1315 0.02499999999995261 -0.0300000000000824 0 1316 0.02999999999994402 -0.02250000000005964 0 1317 0.03499999999993698 -0.02250000000005964 0 1318 0.03499999999993698 -0.03000000000008241 0 1319 0.03999999999992994 -0.02250000000005964 0 1320 0.04499999999992209 -0.02250000000005964 0 1321 0.04499999999992209 -0.03000000000008241 0 1322 0.04999999999991424 -0.02250000000005964 0 1323 0.05499999999990056 -0.02250000000005964 0 1324 0.05499999999990056 -0.03000000000008241 0 1325 0.05999999999988688 -0.02250000000005964 0 1326 0.06499999999987254 -0.02250000000005964 0 1327 0.06499999999987254 -0.03000000000008241 0 1328 0.06999999999985819 -0.02250000000005964 0 1329 0.07499999999984111 -0.02250000000005964 0 1330 0.07499999999984112 -0.03000000000008241 0 1331 0.07999999999982405 -0.02250000000005964 0 1332 0.0849999999998097 -0.02250000000005964 0 1333 0.0849999999998097 -0.0300000000000824 0 1334 0.08999999999979536 -0.02250000000005964 0 1335 0.094999999999781 -0.02250000000005964 0 1336 0.094999999999781 -0.03000000000008241 0 1337 0.09999999999976665 -0.02250000000005964 0 1338 0.1049999999997523 -0.02250000000005964 0 1339 0.1049999999997523 -0.03000000000008241 0 1340 0.109999999999738 -0.02250000000005964 0 1341 0.1149999999997236 -0.02250000000005964 0 1342 0.1149999999997236 -0.03000000000008241 0 1343 0.1199999999997092 -0.02250000000005964 0 1344 0.1249999999996949 -0.02250000000005964 0 1345 0.1249999999996949 -0.03000000000008241 0 1346 0.1299999999996806 -0.02250000000005964 0 1347 0.1349999999996662 -0.02250000000005964 0 1348 0.1349999999996662 -0.03000000000008241 0 1349 0.1399999999996519 -0.02250000000005964 0 1350 0.1449999999996348 -0.02250000000005964 0 1351 0.1449999999996348 -0.03000000000008241 0 1352 0.1499999999996177 -0.02250000000005964 0 1353 0.1549999999996007 -0.02250000000005964 0 1354 0.1549999999996006 -0.03000000000008241 0 1355 0.1599999999995836 -0.02250000000005964 0 1356 0.1649999999995692 -0.02250000000005964 0 1357 0.1649999999995692 -0.03000000000008241 0 1358 0.1699999999995549 -0.02250000000005964 0 1359 0.1749999999995406 -0.02250000000005964 0 1360 0.1749999999995405 -0.0300000000000824 0 1361 0.1799999999995262 -0.02250000000005964 0 1362 0.1849999999995118 -0.02250000000005964 0 1363 0.1849999999995118 -0.0300000000000824 0 1364 0.1899999999994975 -0.02250000000005964 0 1365 0.1949999999994859 -0.02250000000005964 0 1366 0.1949999999994859 -0.03000000000008241 0 1367 0.1999999999994742 -0.02250000000005964 0 1368 0.2049999999994859 -0.02250000000005964 0 1369 0.2049999999994859 -0.03000000000008241 0 1370 0.2099999999994975 -0.02250000000005964 0 1371 0.2149999999995092 -0.02250000000005964 0 1372 0.2149999999995092 -0.03000000000008241 0 1373 0.2199999999995209 -0.02250000000005964 0 1374 0.2249999999995352 -0.02250000000005964 0 1375 0.2249999999995352 -0.03000000000008241 0 1376 0.2299999999995495 -0.02250000000005964 0 1377 0.2349999999995633 -0.02250000000005964 0 1378 0.2349999999995633 -0.03000000000008241 0 1379 0.2399999999995772 -0.02250000000005964 0 1380 0.2449999999995894 -0.02250000000005964 0 1381 0.2449999999995894 -0.03000000000008241 0 1382 0.2499999999996016 -0.02250000000005964 0 1383 0.2549999999996181 -0.02250000000005964 0 1384 0.2549999999996181 -0.03000000000008241 0 1385 0.2599999999996346 -0.02250000000005964 0 1386 0.2649999999996452 -0.02250000000005964 0 1387 0.2649999999996452 -0.03000000000008241 0 1388 0.2699999999996559 -0.02250000000005964 0 1389 0.2749999999996675 -0.02250000000005964 0 1390 0.2749999999996675 -0.03000000000008241 0 1391 0.2799999999996792 -0.02250000000005964 0 1392 0.2849999999996973 -0.02250000000005964 0 1393 0.2849999999996973 -0.03000000000008241 0 1394 0.2899999999997154 -0.02250000000005964 0 1395 0.2949999999997249 -0.02250000000005964 0 1396 0.2949999999997249 -0.03000000000008241 0 1397 0.2999999999997344 -0.02250000000005964 0 1398 0.3049999999997439 -0.02250000000005964 0 1399 0.3049999999997439 -0.03000000000008241 0 1400 0.3099999999997534 -0.02250000000005964 0 1401 0.3149999999997715 -0.02250000000005964 0 1402 0.3149999999997715 -0.03000000000008241 0 1403 0.3199999999997897 -0.02250000000005964 0 1404 0.3249999999998013 -0.02250000000005964 0 1405 0.3249999999998013 -0.03000000000008241 0 1406 0.329999999999813 -0.02250000000005964 0 1407 0.3349999999998236 -0.02250000000005964 0 1408 0.3349999999998236 -0.03000000000008241 0 1409 0.3399999999998342 -0.02250000000005964 0 1410 0.3449999999998534 -0.02250000000005964 0 1411 0.3449999999998534 -0.03000000000008241 0 1412 0.3499999999998726 -0.02250000000005964 0 1413 0.3549999999998821 -0.02250000000005964 0 1414 0.3549999999998821 -0.03000000000008241 0 1415 0.3599999999998915 -0.02250000000005964 0 1416 0.3649999999999043 -0.02250000000005964 0 1417 0.3649999999999042 -0.0300000000000824 0 1418 0.3699999999999171 -0.02250000000005964 0 1419 0.3749999999999341 -0.02250000000005964 0 1420 0.3749999999999341 -0.03000000000008241 0 1421 0.3799999999999512 -0.02250000000005964 0 1422 0.3849999999999607 -0.02250000000005964 0 1423 0.3849999999999606 -0.03000000000008241 0 1424 0.3899999999999701 -0.02250000000005964 0 1425 0.394999999999985 -0.02250000000005964 0 1426 0.394999999999985 -0.03000000000008241 0 1427 0.004999999999990963 -0.0375000000001019 0 1428 0.004999999999990963 -0.04500000000012139 0 1429 0.009999999999981926 -0.0375000000001019 0 1430 0.01499999999997157 -0.0375000000001019 0 1431 0.01499999999997156 -0.0450000000001214 0 1432 0.0199999999999612 -0.0375000000001019 0 1433 0.02499999999995261 -0.0375000000001019 0 1434 0.0249999999999526 -0.0450000000001214 0 1435 0.02999999999994401 -0.0375000000001019 0 1436 0.03499999999993697 -0.0375000000001019 0 1437 0.03499999999993698 -0.04500000000012139 0 1438 0.03999999999992994 -0.0375000000001019 0 1439 0.04499999999992209 -0.0375000000001019 0 1440 0.04499999999992209 -0.04500000000012139 0 1441 0.04999999999991424 -0.0375000000001019 0 1442 0.05499999999990056 -0.0375000000001019 0 1443 0.05499999999990056 -0.0450000000001214 0 1444 0.05999999999988688 -0.0375000000001019 0 1445 0.06499999999987254 -0.03750000000010191 0 1446 0.06499999999987254 -0.0450000000001214 0 1447 0.06999999999985819 -0.03750000000010191 0 1448 0.07499999999984112 -0.0375000000001019 0 1449 0.07499999999984112 -0.04500000000012139 0 1450 0.07999999999982406 -0.0375000000001019 0 1451 0.0849999999998097 -0.03750000000010189 0 1452 0.0849999999998097 -0.04500000000012138 0 1453 0.08999999999979536 -0.03750000000010189 0 1454 0.09499999999978101 -0.0375000000001019 0 1455 0.09499999999978101 -0.04500000000012139 0 1456 0.09999999999976665 -0.0375000000001019 0 1457 0.1049999999997523 -0.0375000000001019 0 1458 0.1049999999997523 -0.04500000000012139 0 1459 0.109999999999738 -0.0375000000001019 0 1460 0.1149999999997236 -0.0375000000001019 0 1461 0.1149999999997236 -0.04500000000012139 0 1462 0.1199999999997092 -0.0375000000001019 0 1463 0.1249999999996949 -0.0375000000001019 0 1464 0.1249999999996949 -0.04500000000012139 0 1465 0.1299999999996806 -0.0375000000001019 0 1466 0.1349999999996662 -0.0375000000001019 0 1467 0.1349999999996662 -0.04500000000012139 0 1468 0.1399999999996519 -0.0375000000001019 0 1469 0.1449999999996348 -0.0375000000001019 0 1470 0.1449999999996348 -0.04500000000012139 0 1471 0.1499999999996177 -0.0375000000001019 0 1472 0.1549999999996007 -0.0375000000001019 0 1473 0.1549999999996007 -0.04500000000012139 0 1474 0.1599999999995836 -0.0375000000001019 0 1475 0.1649999999995692 -0.0375000000001019 0 1476 0.1649999999995693 -0.04500000000012139 0 1477 0.1699999999995549 -0.0375000000001019 0 1478 0.1749999999995406 -0.0375000000001019 0 1479 0.1749999999995406 -0.04500000000012139 0 1480 0.1799999999995262 -0.0375000000001019 0 1481 0.1849999999995118 -0.0375000000001019 0 1482 0.1849999999995118 -0.04500000000012139 0 1483 0.1899999999994975 -0.0375000000001019 0 1484 0.1949999999994858 -0.0375000000001019 0 1485 0.1949999999994859 -0.04500000000012139 0 1486 0.1999999999994742 -0.0375000000001019 0 1487 0.2049999999994859 -0.0375000000001019 0 1488 0.2049999999994859 -0.04500000000012139 0 1489 0.2099999999994975 -0.0375000000001019 0 1490 0.2149999999995092 -0.0375000000001019 0 1491 0.2149999999995092 -0.04500000000012139 0 1492 0.2199999999995209 -0.0375000000001019 0 1493 0.2249999999995352 -0.0375000000001019 0 1494 0.2249999999995352 -0.0450000000001214 0 1495 0.2299999999995495 -0.0375000000001019 0 1496 0.2349999999995633 -0.0375000000001019 0 1497 0.2349999999995633 -0.0450000000001214 0 1498 0.2399999999995772 -0.0375000000001019 0 1499 0.2449999999995894 -0.0375000000001019 0 1500 0.2449999999995894 -0.04500000000012139 0 1501 0.2499999999996015 -0.0375000000001019 0 1502 0.2549999999996181 -0.0375000000001019 0 1503 0.2549999999996181 -0.04500000000012139 0 1504 0.2599999999996346 -0.0375000000001019 0 1505 0.2649999999996453 -0.0375000000001019 0 1506 0.2649999999996453 -0.04500000000012139 0 1507 0.2699999999996559 -0.0375000000001019 0 1508 0.2749999999996675 -0.0375000000001019 0 1509 0.2749999999996675 -0.04500000000012139 0 1510 0.2799999999996792 -0.0375000000001019 0 1511 0.2849999999996973 -0.0375000000001019 0 1512 0.2849999999996973 -0.04500000000012139 0 1513 0.2899999999997154 -0.0375000000001019 0 1514 0.2949999999997249 -0.0375000000001019 0 1515 0.2949999999997249 -0.04500000000012139 0 1516 0.2999999999997344 -0.0375000000001019 0 1517 0.3049999999997439 -0.0375000000001019 0 1518 0.3049999999997439 -0.04500000000012138 0 1519 0.3099999999997534 -0.0375000000001019 0 1520 0.3149999999997715 -0.0375000000001019 0 1521 0.3149999999997715 -0.04500000000012139 0 1522 0.3199999999997897 -0.0375000000001019 0 1523 0.3249999999998013 -0.0375000000001019 0 1524 0.3249999999998013 -0.04500000000012139 0 1525 0.329999999999813 -0.0375000000001019 0 1526 0.3349999999998236 -0.0375000000001019 0 1527 0.3349999999998236 -0.04500000000012138 0 1528 0.3399999999998341 -0.0375000000001019 0 1529 0.3449999999998534 -0.0375000000001019 0 1530 0.3449999999998534 -0.04500000000012139 0 1531 0.3499999999998726 -0.0375000000001019 0 1532 0.3549999999998821 -0.0375000000001019 0 1533 0.3549999999998821 -0.04500000000012139 0 1534 0.3599999999998915 -0.03750000000010189 0 1535 0.3649999999999043 -0.03750000000010189 0 1536 0.3649999999999043 -0.04500000000012139 0 1537 0.3699999999999171 -0.0375000000001019 0 1538 0.3749999999999341 -0.0375000000001019 0 1539 0.3749999999999341 -0.04500000000012139 0 1540 0.3799999999999512 -0.0375000000001019 0 1541 0.3849999999999606 -0.03750000000010189 0 1542 0.3849999999999606 -0.04500000000012139 0 1543 0.3899999999999701 -0.0375000000001019 0 1544 0.3949999999999851 -0.0375000000001019 0 1545 0.3949999999999851 -0.04500000000012139 0 1546 0.004999999999990963 -0.05250000000013605 0 1547 0.004999999999990963 -0.06000000000015071 0 1548 0.009999999999981926 -0.05250000000013605 0 1549 0.01499999999997156 -0.05250000000013606 0 1550 0.01499999999997157 -0.0600000000001507 0 1551 0.0199999999999612 -0.05250000000013605 0 1552 0.02499999999995261 -0.05250000000013605 0 1553 0.02499999999995261 -0.0600000000001507 0 1554 0.02999999999994401 -0.05250000000013606 0 1555 0.03499999999993698 -0.05250000000013605 0 1556 0.03499999999993697 -0.06000000000015071 0 1557 0.03999999999992994 -0.05250000000013605 0 1558 0.04499999999992209 -0.05250000000013605 0 1559 0.04499999999992209 -0.06000000000015071 0 1560 0.04999999999991424 -0.05250000000013605 0 1561 0.05499999999990056 -0.05250000000013606 0 1562 0.05499999999990056 -0.06000000000015071 0 1563 0.05999999999988688 -0.05250000000013606 0 1564 0.06499999999987254 -0.05250000000013606 0 1565 0.06499999999987254 -0.06000000000015071 0 1566 0.06999999999985819 -0.05250000000013606 0 1567 0.07499999999984112 -0.05250000000013605 0 1568 0.07499999999984111 -0.06000000000015071 0 1569 0.07999999999982405 -0.05250000000013604 0 1570 0.0849999999998097 -0.05250000000013604 0 1571 0.0849999999998097 -0.06000000000015071 0 1572 0.08999999999979536 -0.05250000000013605 0 1573 0.09499999999978101 -0.05250000000013606 0 1574 0.09499999999978101 -0.06000000000015071 0 1575 0.09999999999976666 -0.05250000000013605 0 1576 0.1049999999997523 -0.05250000000013604 0 1577 0.1049999999997523 -0.0600000000001507 0 1578 0.109999999999738 -0.05250000000013604 0 1579 0.1149999999997236 -0.05250000000013605 0 1580 0.1149999999997236 -0.0600000000001507 0 1581 0.1199999999997092 -0.05250000000013605 0 1582 0.1249999999996949 -0.05250000000013605 0 1583 0.1249999999996949 -0.0600000000001507 0 1584 0.1299999999996806 -0.05250000000013604 0 1585 0.1349999999996662 -0.05250000000013604 0 1586 0.1349999999996662 -0.06000000000015071 0 1587 0.1399999999996519 -0.05250000000013605 0 1588 0.1449999999996348 -0.05250000000013606 0 1589 0.1449999999996348 -0.06000000000015071 0 1590 0.1499999999996177 -0.05250000000013606 0 1591 0.1549999999996007 -0.05250000000013605 0 1592 0.1549999999996007 -0.06000000000015071 0 1593 0.1599999999995836 -0.05250000000013605 0 1594 0.1649999999995693 -0.05250000000013606 0 1595 0.1649999999995692 -0.06000000000015072 0 1596 0.1699999999995549 -0.05250000000013606 0 1597 0.1749999999995406 -0.05250000000013606 0 1598 0.1749999999995406 -0.06000000000015072 0 1599 0.1799999999995262 -0.05250000000013605 0 1600 0.1849999999995118 -0.05250000000013606 0 1601 0.1849999999995118 -0.0600000000001507 0 1602 0.1899999999994975 -0.05250000000013605 0 1603 0.1949999999994859 -0.05250000000013604 0 1604 0.1949999999994859 -0.0600000000001507 0 1605 0.1999999999994742 -0.05250000000013605 0 1606 0.2049999999994859 -0.05250000000013605 0 1607 0.2049999999994859 -0.06000000000015071 0 1608 0.2099999999994975 -0.05250000000013605 0 1609 0.2149999999995092 -0.05250000000013605 0 1610 0.2149999999995092 -0.06000000000015071 0 1611 0.2199999999995209 -0.05250000000013605 0 1612 0.2249999999995352 -0.05250000000013606 0 1613 0.2249999999995352 -0.06000000000015071 0 1614 0.2299999999995495 -0.05250000000013606 0 1615 0.2349999999995633 -0.05250000000013606 0 1616 0.2349999999995633 -0.06000000000015071 0 1617 0.2399999999995772 -0.05250000000013606 0 1618 0.2449999999995894 -0.05250000000013605 0 1619 0.2449999999995894 -0.06000000000015071 0 1620 0.2499999999996015 -0.05250000000013605 0 1621 0.2549999999996181 -0.05250000000013605 0 1622 0.2549999999996181 -0.06000000000015071 0 1623 0.2599999999996346 -0.05250000000013605 0 1624 0.2649999999996452 -0.05250000000013605 0 1625 0.2649999999996452 -0.06000000000015071 0 1626 0.2699999999996558 -0.05250000000013605 0 1627 0.2749999999996675 -0.05250000000013605 0 1628 0.2749999999996674 -0.06000000000015071 0 1629 0.2799999999996791 -0.05250000000013605 0 1630 0.2849999999996972 -0.05250000000013605 0 1631 0.2849999999996972 -0.0600000000001507 0 1632 0.2899999999997154 -0.05250000000013605 0 1633 0.2949999999997249 -0.05250000000013604 0 1634 0.2949999999997249 -0.0600000000001507 0 1635 0.2999999999997344 -0.05250000000013604 0 1636 0.3049999999997439 -0.05250000000013604 0 1637 0.3049999999997439 -0.0600000000001507 0 1638 0.3099999999997534 -0.05250000000013604 0 1639 0.3149999999997715 -0.05250000000013605 0 1640 0.3149999999997716 -0.06000000000015071 0 1641 0.3199999999997897 -0.05250000000013605 0 1642 0.3249999999998014 -0.05250000000013605 0 1643 0.3249999999998013 -0.06000000000015071 0 1644 0.329999999999813 -0.05250000000013605 0 1645 0.3349999999998236 -0.05250000000013605 0 1646 0.3349999999998236 -0.06000000000015071 0 1647 0.3399999999998341 -0.05250000000013604 0 1648 0.3449999999998534 -0.05250000000013605 0 1649 0.3449999999998534 -0.06000000000015071 0 1650 0.3499999999998726 -0.05250000000013605 0 1651 0.3549999999998821 -0.05250000000013605 0 1652 0.3549999999998821 -0.06000000000015071 0 1653 0.3599999999998915 -0.05250000000013605 0 1654 0.3649999999999043 -0.05250000000013605 0 1655 0.3649999999999043 -0.06000000000015071 0 1656 0.3699999999999171 -0.05250000000013605 0 1657 0.3749999999999341 -0.05250000000013605 0 1658 0.3749999999999342 -0.06000000000015071 0 1659 0.3799999999999512 -0.05250000000013604 0 1660 0.3849999999999607 -0.05250000000013605 0 1661 0.3849999999999607 -0.06000000000015071 0 1662 0.3899999999999702 -0.05250000000013605 0 1663 0.394999999999985 -0.05250000000013605 0 1664 0.394999999999985 -0.06000000000015071 0 1665 0.004999999999990963 -0.06750000000017023 0 1666 0.004999999999990963 -0.07500000000018975 0 1667 0.009999999999981926 -0.06750000000017023 0 1668 0.01499999999997157 -0.06750000000017023 0 1669 0.01499999999997157 -0.07500000000018976 0 1670 0.01999999999996121 -0.06750000000017023 0 1671 0.02499999999995261 -0.06750000000017024 0 1672 0.02499999999995261 -0.07500000000018976 0 1673 0.02999999999994401 -0.06750000000017023 0 1674 0.03499999999993698 -0.06750000000017023 0 1675 0.03499999999993698 -0.07500000000018975 0 1676 0.03999999999992994 -0.06750000000017023 0 1677 0.04499999999992209 -0.06750000000017023 0 1678 0.04499999999992209 -0.07500000000018975 0 1679 0.04999999999991424 -0.06750000000017023 0 1680 0.05499999999990056 -0.06750000000017023 0 1681 0.05499999999990056 -0.07500000000018975 0 1682 0.05999999999988688 -0.06750000000017023 0 1683 0.06499999999987254 -0.06750000000017023 0 1684 0.06499999999987254 -0.07500000000018975 0 1685 0.06999999999985819 -0.06750000000017023 0 1686 0.07499999999984111 -0.06750000000017023 0 1687 0.07499999999984112 -0.07500000000018975 0 1688 0.07999999999982405 -0.06750000000017023 0 1689 0.0849999999998097 -0.06750000000017023 0 1690 0.0849999999998097 -0.07500000000018975 0 1691 0.08999999999979536 -0.06750000000017023 0 1692 0.09499999999978101 -0.06750000000017023 0 1693 0.09499999999978101 -0.07500000000018975 0 1694 0.09999999999976666 -0.06750000000017023 0 1695 0.1049999999997523 -0.06750000000017023 0 1696 0.1049999999997523 -0.07500000000018975 0 1697 0.109999999999738 -0.06750000000017023 0 1698 0.1149999999997236 -0.06750000000017023 0 1699 0.1149999999997236 -0.07500000000018975 0 1700 0.1199999999997092 -0.06750000000017023 0 1701 0.1249999999996949 -0.06750000000017023 0 1702 0.1249999999996949 -0.07500000000018975 0 1703 0.1299999999996806 -0.06750000000017023 0 1704 0.1349999999996662 -0.06750000000017023 0 1705 0.1349999999996662 -0.07500000000018975 0 1706 0.1399999999996519 -0.06750000000017023 0 1707 0.1449999999996348 -0.06750000000017023 0 1708 0.1449999999996348 -0.07500000000018975 0 1709 0.1499999999996177 -0.06750000000017023 0 1710 0.1549999999996007 -0.06750000000017023 0 1711 0.1549999999996007 -0.07500000000018975 0 1712 0.1599999999995836 -0.06750000000017023 0 1713 0.1649999999995692 -0.06750000000017023 0 1714 0.1649999999995692 -0.07500000000018975 0 1715 0.1699999999995549 -0.06750000000017023 0 1716 0.1749999999995406 -0.06750000000017023 0 1717 0.1749999999995406 -0.07500000000018975 0 1718 0.1799999999995262 -0.06750000000017023 0 1719 0.1849999999995118 -0.06750000000017023 0 1720 0.1849999999995118 -0.07500000000018975 0 1721 0.1899999999994975 -0.06750000000017023 0 1722 0.1949999999994859 -0.06750000000017023 0 1723 0.1949999999994859 -0.07500000000018975 0 1724 0.1999999999994742 -0.06750000000017023 0 1725 0.2049999999994859 -0.06750000000017023 0 1726 0.2049999999994859 -0.07500000000018975 0 1727 0.2099999999994975 -0.06750000000017023 0 1728 0.2149999999995092 -0.06750000000017023 0 1729 0.2149999999995092 -0.07500000000018975 0 1730 0.2199999999995209 -0.06750000000017023 0 1731 0.2249999999995352 -0.06750000000017023 0 1732 0.2249999999995352 -0.07500000000018975 0 1733 0.2299999999995495 -0.06750000000017023 0 1734 0.2349999999995633 -0.06750000000017023 0 1735 0.2349999999995633 -0.07500000000018975 0 1736 0.2399999999995772 -0.06750000000017023 0 1737 0.2449999999995894 -0.06750000000017023 0 1738 0.2449999999995894 -0.07500000000018975 0 1739 0.2499999999996015 -0.06750000000017023 0 1740 0.2549999999996181 -0.06750000000017023 0 1741 0.2549999999996181 -0.07500000000018975 0 1742 0.2599999999996346 -0.06750000000017023 0 1743 0.2649999999996452 -0.06750000000017023 0 1744 0.2649999999996452 -0.07500000000018975 0 1745 0.2699999999996558 -0.06750000000017023 0 1746 0.2749999999996675 -0.06750000000017023 0 1747 0.2749999999996675 -0.07500000000018975 0 1748 0.2799999999996791 -0.06750000000017023 0 1749 0.2849999999996973 -0.06750000000017023 0 1750 0.2849999999996973 -0.07500000000018975 0 1751 0.2899999999997154 -0.06750000000017023 0 1752 0.2949999999997249 -0.06750000000017023 0 1753 0.2949999999997249 -0.07500000000018975 0 1754 0.2999999999997344 -0.06750000000017023 0 1755 0.3049999999997439 -0.06750000000017023 0 1756 0.3049999999997439 -0.07500000000018975 0 1757 0.3099999999997534 -0.06750000000017023 0 1758 0.3149999999997716 -0.06750000000017023 0 1759 0.3149999999997715 -0.07500000000018975 0 1760 0.3199999999997897 -0.06750000000017023 0 1761 0.3249999999998013 -0.06750000000017023 0 1762 0.3249999999998013 -0.07500000000018975 0 1763 0.329999999999813 -0.06750000000017023 0 1764 0.3349999999998236 -0.06750000000017023 0 1765 0.3349999999998236 -0.07500000000018975 0 1766 0.3399999999998341 -0.06750000000017023 0 1767 0.3449999999998534 -0.06750000000017023 0 1768 0.3449999999998534 -0.07500000000018975 0 1769 0.3499999999998726 -0.06750000000017023 0 1770 0.3549999999998821 -0.06750000000017023 0 1771 0.3549999999998821 -0.07500000000018976 0 1772 0.3599999999998915 -0.06750000000017023 0 1773 0.3649999999999043 -0.06750000000017023 0 1774 0.3649999999999043 -0.07500000000018975 0 1775 0.3699999999999171 -0.06750000000017023 0 1776 0.3749999999999342 -0.06750000000017023 0 1777 0.3749999999999342 -0.07500000000018975 0 1778 0.3799999999999513 -0.06750000000017023 0 1779 0.3849999999999607 -0.06750000000017023 0 1780 0.3849999999999607 -0.07500000000018975 0 1781 0.3899999999999701 -0.06750000000017023 0 1782 0.394999999999985 -0.06750000000017023 0 1783 0.394999999999985 -0.07500000000018975 0 1784 0.004999999999990963 -0.08250000000020927 0 1785 0.004999999999990963 -0.09000000000022879 0 1786 0.009999999999981926 -0.08250000000020927 0 1787 0.01499999999997157 -0.08250000000020927 0 1788 0.01499999999997157 -0.09000000000022879 0 1789 0.01999999999996121 -0.08250000000020928 0 1790 0.02499999999995261 -0.08250000000020927 0 1791 0.02499999999995261 -0.09000000000022879 0 1792 0.02999999999994401 -0.08250000000020927 0 1793 0.03499999999993698 -0.08250000000020927 0 1794 0.03499999999993698 -0.09000000000022879 0 1795 0.03999999999992994 -0.08250000000020927 0 1796 0.04499999999992209 -0.08250000000020927 0 1797 0.04499999999992209 -0.09000000000022879 0 1798 0.04999999999991424 -0.08250000000020927 0 1799 0.05499999999990056 -0.08250000000020927 0 1800 0.05499999999990056 -0.09000000000022879 0 1801 0.05999999999988688 -0.08250000000020928 0 1802 0.06499999999987254 -0.08250000000020928 0 1803 0.06499999999987254 -0.09000000000022879 0 1804 0.06999999999985819 -0.08250000000020927 0 1805 0.07499999999984112 -0.08250000000020927 0 1806 0.07499999999984111 -0.09000000000022879 0 1807 0.07999999999982405 -0.08250000000020928 0 1808 0.0849999999998097 -0.08250000000020928 0 1809 0.0849999999998097 -0.09000000000022879 0 1810 0.08999999999979536 -0.08250000000020927 0 1811 0.09499999999978101 -0.08250000000020927 0 1812 0.09499999999978101 -0.09000000000022879 0 1813 0.09999999999976666 -0.08250000000020927 0 1814 0.1049999999997523 -0.08250000000020927 0 1815 0.1049999999997523 -0.0900000000002288 0 1816 0.109999999999738 -0.08250000000020928 0 1817 0.1149999999997236 -0.08250000000020928 0 1818 0.1149999999997236 -0.09000000000022881 0 1819 0.1199999999997092 -0.08250000000020927 0 1820 0.1249999999996949 -0.08250000000020927 0 1821 0.1249999999996949 -0.09000000000022879 0 1822 0.1299999999996806 -0.08250000000020927 0 1823 0.1349999999996662 -0.08250000000020927 0 1824 0.1349999999996662 -0.09000000000022879 0 1825 0.1399999999996519 -0.08250000000020927 0 1826 0.1449999999996348 -0.08250000000020927 0 1827 0.1449999999996348 -0.09000000000022879 0 1828 0.1499999999996177 -0.08250000000020927 0 1829 0.1549999999996007 -0.08250000000020927 0 1830 0.1549999999996007 -0.09000000000022879 0 1831 0.1599999999995836 -0.08250000000020927 0 1832 0.1649999999995692 -0.08250000000020927 0 1833 0.1649999999995692 -0.0900000000002288 0 1834 0.1699999999995549 -0.08250000000020928 0 1835 0.1749999999995406 -0.0825000000002093 0 1836 0.1749999999995406 -0.0900000000002288 0 1837 0.1799999999995262 -0.08250000000020927 0 1838 0.1849999999995118 -0.08250000000020927 0 1839 0.1849999999995119 -0.09000000000022879 0 1840 0.1899999999994975 -0.08250000000020927 0 1841 0.1949999999994859 -0.08250000000020927 0 1842 0.1949999999994859 -0.09000000000022879 0 1843 0.1999999999994742 -0.08250000000020927 0 1844 0.2049999999994859 -0.08250000000020927 0 1845 0.2049999999994859 -0.0900000000002288 0 1846 0.2099999999994975 -0.08250000000020928 0 1847 0.2149999999995092 -0.08250000000020928 0 1848 0.2149999999995092 -0.09000000000022881 0 1849 0.2199999999995209 -0.08250000000020928 0 1850 0.2249999999995352 -0.08250000000020928 0 1851 0.2249999999995352 -0.09000000000022881 0 1852 0.2299999999995495 -0.08250000000020928 0 1853 0.2349999999995633 -0.08250000000020928 0 1854 0.2349999999995634 -0.0900000000002288 0 1855 0.2399999999995772 -0.08250000000020927 0 1856 0.2449999999995894 -0.08250000000020927 0 1857 0.2449999999995895 -0.09000000000022879 0 1858 0.2499999999996016 -0.08250000000020927 0 1859 0.2549999999996181 -0.08250000000020927 0 1860 0.2549999999996181 -0.09000000000022879 0 1861 0.2599999999996346 -0.08250000000020927 0 1862 0.2649999999996452 -0.08250000000020927 0 1863 0.2649999999996452 -0.09000000000022879 0 1864 0.2699999999996559 -0.08250000000020927 0 1865 0.2749999999996675 -0.08250000000020927 0 1866 0.2749999999996675 -0.09000000000022879 0 1867 0.2799999999996792 -0.08250000000020927 0 1868 0.2849999999996973 -0.08250000000020927 0 1869 0.2849999999996972 -0.09000000000022879 0 1870 0.2899999999997154 -0.08250000000020927 0 1871 0.2949999999997248 -0.08250000000020927 0 1872 0.2949999999997248 -0.09000000000022877 0 1873 0.2999999999997344 -0.08250000000020927 0 1874 0.3049999999997439 -0.08250000000020927 0 1875 0.3049999999997438 -0.09000000000022877 0 1876 0.3099999999997534 -0.08250000000020927 0 1877 0.3149999999997715 -0.08250000000020927 0 1878 0.3149999999997715 -0.09000000000022879 0 1879 0.3199999999997897 -0.08250000000020927 0 1880 0.3249999999998013 -0.08250000000020927 0 1881 0.3249999999998013 -0.09000000000022879 0 1882 0.329999999999813 -0.08250000000020927 0 1883 0.3349999999998236 -0.08250000000020927 0 1884 0.3349999999998236 -0.09000000000022879 0 1885 0.3399999999998342 -0.08250000000020927 0 1886 0.3449999999998534 -0.08250000000020927 0 1887 0.3449999999998534 -0.09000000000022879 0 1888 0.3499999999998726 -0.08250000000020927 0 1889 0.3549999999998821 -0.08250000000020927 0 1890 0.3549999999998821 -0.09000000000022879 0 1891 0.3599999999998915 -0.08250000000020927 0 1892 0.3649999999999042 -0.08250000000020927 0 1893 0.3649999999999043 -0.09000000000022879 0 1894 0.3699999999999171 -0.08250000000020927 0 1895 0.3749999999999342 -0.08250000000020927 0 1896 0.3749999999999342 -0.09000000000022879 0 1897 0.3799999999999513 -0.08250000000020927 0 1898 0.3849999999999607 -0.08250000000020927 0 1899 0.3849999999999607 -0.09000000000022879 0 1900 0.3899999999999702 -0.08250000000020927 0 1901 0.3949999999999851 -0.08250000000020927 0 1902 0.3949999999999851 -0.09000000000022879 0 1903 0.004999999999990963 -0.097500000000251 0 1904 0.004999999999990963 -0.1050000000002732 0 1905 0.009999999999981926 -0.097500000000251 0 1906 0.01499999999997157 -0.09750000000025101 0 1907 0.01499999999997157 -0.1050000000002732 0 1908 0.01999999999996121 -0.09750000000025103 0 1909 0.02499999999995261 -0.09750000000025103 0 1910 0.02499999999995261 -0.1050000000002732 0 1911 0.02999999999994401 -0.09750000000025101 0 1912 0.03499999999993698 -0.09750000000025101 0 1913 0.03499999999993698 -0.1050000000002732 0 1914 0.03999999999992994 -0.097500000000251 0 1915 0.04499999999992209 -0.097500000000251 0 1916 0.04499999999992209 -0.1050000000002732 0 1917 0.04999999999991424 -0.097500000000251 0 1918 0.05499999999990056 -0.09750000000025103 0 1919 0.05499999999990056 -0.1050000000002732 0 1920 0.05999999999988688 -0.09750000000025101 0 1921 0.06499999999987254 -0.097500000000251 0 1922 0.06499999999987254 -0.1050000000002732 0 1923 0.06999999999985819 -0.097500000000251 0 1924 0.07499999999984111 -0.09750000000025103 0 1925 0.07499999999984112 -0.1050000000002732 0 1926 0.07999999999982405 -0.09750000000025103 0 1927 0.0849999999998097 -0.097500000000251 0 1928 0.0849999999998097 -0.1050000000002732 0 1929 0.08999999999979536 -0.09750000000025098 0 1930 0.09499999999978101 -0.097500000000251 0 1931 0.09499999999978101 -0.1050000000002732 0 1932 0.09999999999976666 -0.09750000000025101 0 1933 0.1049999999997523 -0.09750000000025103 0 1934 0.1049999999997523 -0.1050000000002732 0 1935 0.109999999999738 -0.09750000000025103 0 1936 0.1149999999997236 -0.09750000000025101 0 1937 0.1149999999997236 -0.1050000000002732 0 1938 0.1199999999997092 -0.09750000000025103 0 1939 0.1249999999996949 -0.09750000000025101 0 1940 0.1249999999996949 -0.1050000000002732 0 1941 0.1299999999996806 -0.09750000000025101 0 1942 0.1349999999996662 -0.09750000000025101 0 1943 0.1349999999996662 -0.1050000000002732 0 1944 0.1399999999996519 -0.09750000000025101 0 1945 0.1449999999996348 -0.09750000000025101 0 1946 0.1449999999996348 -0.1050000000002732 0 1947 0.1499999999996177 -0.09750000000025101 0 1948 0.1549999999996007 -0.09750000000025101 0 1949 0.1549999999996007 -0.1050000000002732 0 1950 0.1599999999995836 -0.09750000000025101 0 1951 0.1649999999995692 -0.09750000000025103 0 1952 0.1649999999995692 -0.1050000000002732 0 1953 0.1699999999995549 -0.09750000000025103 0 1954 0.1749999999995406 -0.09750000000025101 0 1955 0.1749999999995406 -0.1050000000002732 0 1956 0.1799999999995262 -0.09750000000025101 0 1957 0.1849999999995119 -0.09750000000025101 0 1958 0.1849999999995118 -0.1050000000002732 0 1959 0.1899999999994975 -0.09750000000025101 0 1960 0.1949999999994858 -0.09750000000025101 0 1961 0.1949999999994858 -0.1050000000002732 0 1962 0.1999999999994742 -0.09750000000025101 0 1963 0.2049999999994859 -0.09750000000025103 0 1964 0.2049999999994859 -0.1050000000002732 0 1965 0.2099999999994975 -0.09750000000025103 0 1966 0.2149999999995092 -0.09750000000025103 0 1967 0.2149999999995092 -0.1050000000002732 0 1968 0.2199999999995209 -0.09750000000025103 0 1969 0.2249999999995352 -0.09750000000025103 0 1970 0.2249999999995352 -0.1050000000002732 0 1971 0.2299999999995495 -0.09750000000025103 0 1972 0.2349999999995634 -0.09750000000025101 0 1973 0.2349999999995634 -0.1050000000002732 0 1974 0.2399999999995773 -0.09750000000025101 0 1975 0.2449999999995894 -0.09750000000025101 0 1976 0.2449999999995894 -0.1050000000002732 0 1977 0.2499999999996016 -0.09750000000025101 0 1978 0.2549999999996181 -0.09750000000025101 0 1979 0.2549999999996181 -0.1050000000002732 0 1980 0.2599999999996346 -0.09750000000025101 0 1981 0.2649999999996452 -0.09750000000025101 0 1982 0.2649999999996452 -0.1050000000002732 0 1983 0.2699999999996559 -0.09750000000025101 0 1984 0.2749999999996675 -0.09750000000025101 0 1985 0.2749999999996675 -0.1050000000002732 0 1986 0.2799999999996791 -0.09750000000025101 0 1987 0.2849999999996972 -0.097500000000251 0 1988 0.2849999999996973 -0.1050000000002732 0 1989 0.2899999999997154 -0.097500000000251 0 1990 0.2949999999997249 -0.097500000000251 0 1991 0.2949999999997249 -0.1050000000002732 0 1992 0.2999999999997344 -0.097500000000251 0 1993 0.3049999999997438 -0.097500000000251 0 1994 0.3049999999997439 -0.1050000000002732 0 1995 0.3099999999997534 -0.097500000000251 0 1996 0.3149999999997715 -0.09750000000025101 0 1997 0.3149999999997715 -0.1050000000002732 0 1998 0.3199999999997897 -0.09750000000025101 0 1999 0.3249999999998013 -0.09750000000025101 0 2000 0.3249999999998013 -0.1050000000002732 0 2001 0.329999999999813 -0.09750000000025101 0 2002 0.3349999999998236 -0.09750000000025101 0 2003 0.3349999999998236 -0.1050000000002732 0 2004 0.3399999999998342 -0.09750000000025101 0 2005 0.3449999999998534 -0.09750000000025101 0 2006 0.3449999999998534 -0.1050000000002732 0 2007 0.3499999999998726 -0.09750000000025101 0 2008 0.354999999999882 -0.097500000000251 0 2009 0.3549999999998821 -0.1050000000002732 0 2010 0.3599999999998915 -0.097500000000251 0 2011 0.3649999999999043 -0.09750000000025101 0 2012 0.3649999999999043 -0.1050000000002732 0 2013 0.3699999999999171 -0.09750000000025101 0 2014 0.3749999999999342 -0.097500000000251 0 2015 0.3749999999999342 -0.1050000000002732 0 2016 0.3799999999999513 -0.097500000000251 0 2017 0.3849999999999607 -0.09750000000025101 0 2018 0.3849999999999607 -0.1050000000002732 0 2019 0.3899999999999702 -0.097500000000251 0 2020 0.3949999999999851 -0.097500000000251 0 2021 0.3949999999999851 -0.1050000000002732 0 2022 0.004999999999990963 -0.1125000000002922 0 2023 0.004999999999990963 -0.1200000000003112 0 2024 0.009999999999981926 -0.1125000000002922 0 2025 0.01499999999997157 -0.1125000000002922 0 2026 0.01499999999997157 -0.1200000000003112 0 2027 0.01999999999996121 -0.1125000000002922 0 2028 0.02499999999995261 -0.1125000000002922 0 2029 0.02499999999995262 -0.1200000000003112 0 2030 0.02999999999994402 -0.1125000000002922 0 2031 0.03499999999993698 -0.1125000000002922 0 2032 0.03499999999993698 -0.1200000000003112 0 2033 0.03999999999992994 -0.1125000000002922 0 2034 0.04499999999992209 -0.1125000000002922 0 2035 0.04499999999992209 -0.1200000000003112 0 2036 0.04999999999991424 -0.1125000000002922 0 2037 0.05499999999990056 -0.1125000000002922 0 2038 0.05499999999990056 -0.1200000000003112 0 2039 0.05999999999988688 -0.1125000000002922 0 2040 0.06499999999987254 -0.1125000000002922 0 2041 0.06499999999987254 -0.1200000000003112 0 2042 0.06999999999985819 -0.1125000000002922 0 2043 0.07499999999984112 -0.1125000000002922 0 2044 0.07499999999984112 -0.1200000000003112 0 2045 0.07999999999982406 -0.1125000000002922 0 2046 0.0849999999998097 -0.1125000000002922 0 2047 0.0849999999998097 -0.1200000000003112 0 2048 0.08999999999979536 -0.1125000000002922 0 2049 0.09499999999978101 -0.1125000000002922 0 2050 0.09499999999978101 -0.1200000000003112 0 2051 0.09999999999976666 -0.1125000000002922 0 2052 0.1049999999997523 -0.1125000000002922 0 2053 0.1049999999997523 -0.1200000000003112 0 2054 0.109999999999738 -0.1125000000002922 0 2055 0.1149999999997236 -0.1125000000002922 0 2056 0.1149999999997236 -0.1200000000003112 0 2057 0.1199999999997092 -0.1125000000002922 0 2058 0.1249999999996949 -0.1125000000002922 0 2059 0.1249999999996949 -0.1200000000003112 0 2060 0.1299999999996806 -0.1125000000002922 0 2061 0.1349999999996662 -0.1125000000002922 0 2062 0.1349999999996662 -0.1200000000003112 0 2063 0.1399999999996519 -0.1125000000002922 0 2064 0.1449999999996348 -0.1125000000002922 0 2065 0.1449999999996348 -0.1200000000003112 0 2066 0.1499999999996177 -0.1125000000002922 0 2067 0.1549999999996007 -0.1125000000002922 0 2068 0.1549999999996007 -0.1200000000003112 0 2069 0.1599999999995836 -0.1125000000002922 0 2070 0.1649999999995692 -0.1125000000002922 0 2071 0.1649999999995692 -0.1200000000003112 0 2072 0.1699999999995549 -0.1125000000002922 0 2073 0.1749999999995406 -0.1125000000002922 0 2074 0.1749999999995406 -0.1200000000003112 0 2075 0.1799999999995262 -0.1125000000002922 0 2076 0.1849999999995118 -0.1125000000002922 0 2077 0.1849999999995118 -0.1200000000003112 0 2078 0.1899999999994975 -0.1125000000002922 0 2079 0.1949999999994858 -0.1125000000002922 0 2080 0.1949999999994858 -0.1200000000003112 0 2081 0.1999999999994742 -0.1125000000002922 0 2082 0.2049999999994859 -0.1125000000002922 0 2083 0.2049999999994859 -0.1200000000003112 0 2084 0.2099999999994975 -0.1125000000002922 0 2085 0.2149999999995092 -0.1125000000002922 0 2086 0.2149999999995092 -0.1200000000003112 0 2087 0.2199999999995209 -0.1125000000002922 0 2088 0.2249999999995352 -0.1125000000002922 0 2089 0.2249999999995352 -0.1200000000003112 0 2090 0.2299999999995495 -0.1125000000002922 0 2091 0.2349999999995634 -0.1125000000002922 0 2092 0.2349999999995633 -0.1200000000003112 0 2093 0.2399999999995772 -0.1125000000002922 0 2094 0.2449999999995893 -0.1125000000002922 0 2095 0.2449999999995894 -0.1200000000003112 0 2096 0.2499999999996015 -0.1125000000002922 0 2097 0.2549999999996181 -0.1125000000002922 0 2098 0.2549999999996181 -0.1200000000003112 0 2099 0.2599999999996346 -0.1125000000002922 0 2100 0.2649999999996452 -0.1125000000002922 0 2101 0.2649999999996452 -0.1200000000003112 0 2102 0.2699999999996559 -0.1125000000002922 0 2103 0.2749999999996675 -0.1125000000002922 0 2104 0.2749999999996675 -0.1200000000003112 0 2105 0.2799999999996792 -0.1125000000002922 0 2106 0.2849999999996973 -0.1125000000002922 0 2107 0.2849999999996973 -0.1200000000003112 0 2108 0.2899999999997154 -0.1125000000002922 0 2109 0.2949999999997249 -0.1125000000002922 0 2110 0.2949999999997249 -0.1200000000003112 0 2111 0.2999999999997344 -0.1125000000002922 0 2112 0.3049999999997439 -0.1125000000002922 0 2113 0.3049999999997439 -0.1200000000003112 0 2114 0.3099999999997534 -0.1125000000002922 0 2115 0.3149999999997715 -0.1125000000002922 0 2116 0.3149999999997715 -0.1200000000003112 0 2117 0.3199999999997897 -0.1125000000002922 0 2118 0.3249999999998013 -0.1125000000002922 0 2119 0.3249999999998013 -0.1200000000003112 0 2120 0.329999999999813 -0.1125000000002922 0 2121 0.3349999999998236 -0.1125000000002922 0 2122 0.3349999999998236 -0.1200000000003112 0 2123 0.3399999999998342 -0.1125000000002922 0 2124 0.3449999999998534 -0.1125000000002922 0 2125 0.3449999999998534 -0.1200000000003112 0 2126 0.3499999999998726 -0.1125000000002922 0 2127 0.3549999999998821 -0.1125000000002922 0 2128 0.3549999999998821 -0.1200000000003112 0 2129 0.3599999999998915 -0.1125000000002922 0 2130 0.3649999999999043 -0.1125000000002922 0 2131 0.3649999999999043 -0.1200000000003112 0 2132 0.3699999999999171 -0.1125000000002922 0 2133 0.3749999999999342 -0.1125000000002922 0 2134 0.3749999999999342 -0.1200000000003112 0 2135 0.3799999999999513 -0.1125000000002922 0 2136 0.3849999999999607 -0.1125000000002922 0 2137 0.3849999999999607 -0.1200000000003112 0 2138 0.3899999999999702 -0.1125000000002922 0 2139 0.3949999999999851 -0.1125000000002922 0 2140 0.3949999999999851 -0.1200000000003112 0 2141 0.004999999999990963 -0.1275000000003296 0 2142 0.004999999999990963 -0.1350000000003481 0 2143 0.009999999999981926 -0.1275000000003297 0 2144 0.01499999999997157 -0.1275000000003297 0 2145 0.01499999999997157 -0.1350000000003481 0 2146 0.01999999999996121 -0.1275000000003296 0 2147 0.02499999999995262 -0.1275000000003296 0 2148 0.02499999999995262 -0.1350000000003481 0 2149 0.02999999999994402 -0.1275000000003296 0 2150 0.03499999999993698 -0.1275000000003296 0 2151 0.03499999999993698 -0.1350000000003481 0 2152 0.03999999999992994 -0.1275000000003296 0 2153 0.04499999999992209 -0.1275000000003296 0 2154 0.04499999999992209 -0.1350000000003481 0 2155 0.04999999999991424 -0.1275000000003296 0 2156 0.05499999999990056 -0.1275000000003296 0 2157 0.05499999999990056 -0.1350000000003481 0 2158 0.05999999999988688 -0.1275000000003296 0 2159 0.06499999999987254 -0.1275000000003296 0 2160 0.06499999999987254 -0.1350000000003481 0 2161 0.06999999999985819 -0.1275000000003296 0 2162 0.07499999999984112 -0.1275000000003296 0 2163 0.07499999999984112 -0.1350000000003481 0 2164 0.07999999999982406 -0.1275000000003296 0 2165 0.0849999999998097 -0.1275000000003296 0 2166 0.0849999999998097 -0.1350000000003481 0 2167 0.08999999999979536 -0.1275000000003296 0 2168 0.09499999999978101 -0.1275000000003296 0 2169 0.09499999999978101 -0.1350000000003481 0 2170 0.09999999999976666 -0.1275000000003296 0 2171 0.1049999999997523 -0.1275000000003296 0 2172 0.1049999999997523 -0.1350000000003481 0 2173 0.109999999999738 -0.1275000000003296 0 2174 0.1149999999997236 -0.1275000000003296 0 2175 0.1149999999997236 -0.1350000000003481 0 2176 0.1199999999997092 -0.1275000000003296 0 2177 0.1249999999996949 -0.1275000000003296 0 2178 0.1249999999996949 -0.1350000000003481 0 2179 0.1299999999996806 -0.1275000000003296 0 2180 0.1349999999996662 -0.1275000000003296 0 2181 0.1349999999996662 -0.1350000000003481 0 2182 0.1399999999996519 -0.1275000000003296 0 2183 0.1449999999996348 -0.1275000000003296 0 2184 0.1449999999996348 -0.1350000000003481 0 2185 0.1499999999996177 -0.1275000000003296 0 2186 0.1549999999996007 -0.1275000000003296 0 2187 0.1549999999996007 -0.1350000000003481 0 2188 0.1599999999995836 -0.1275000000003296 0 2189 0.1649999999995692 -0.1275000000003296 0 2190 0.1649999999995692 -0.1350000000003481 0 2191 0.1699999999995549 -0.1275000000003296 0 2192 0.1749999999995406 -0.1275000000003296 0 2193 0.1749999999995406 -0.1350000000003481 0 2194 0.1799999999995262 -0.1275000000003296 0 2195 0.1849999999995118 -0.1275000000003296 0 2196 0.1849999999995118 -0.1350000000003481 0 2197 0.1899999999994975 -0.1275000000003296 0 2198 0.1949999999994859 -0.1275000000003296 0 2199 0.1949999999994859 -0.1350000000003481 0 2200 0.1999999999994742 -0.1275000000003296 0 2201 0.2049999999994859 -0.1275000000003296 0 2202 0.2049999999994859 -0.1350000000003481 0 2203 0.2099999999994975 -0.1275000000003296 0 2204 0.2149999999995092 -0.1275000000003296 0 2205 0.2149999999995092 -0.1350000000003481 0 2206 0.2199999999995209 -0.1275000000003296 0 2207 0.2249999999995352 -0.1275000000003296 0 2208 0.2249999999995352 -0.1350000000003481 0 2209 0.2299999999995495 -0.1275000000003296 0 2210 0.2349999999995633 -0.1275000000003296 0 2211 0.2349999999995633 -0.1350000000003481 0 2212 0.2399999999995772 -0.1275000000003296 0 2213 0.2449999999995894 -0.1275000000003296 0 2214 0.2449999999995894 -0.1350000000003481 0 2215 0.2499999999996016 -0.1275000000003296 0 2216 0.2549999999996181 -0.1275000000003296 0 2217 0.2549999999996181 -0.1350000000003481 0 2218 0.2599999999996346 -0.1275000000003296 0 2219 0.2649999999996452 -0.1275000000003296 0 2220 0.2649999999996452 -0.1350000000003481 0 2221 0.2699999999996559 -0.1275000000003296 0 2222 0.2749999999996675 -0.1275000000003296 0 2223 0.2749999999996675 -0.1350000000003481 0 2224 0.2799999999996792 -0.1275000000003296 0 2225 0.2849999999996973 -0.1275000000003296 0 2226 0.2849999999996973 -0.1350000000003481 0 2227 0.2899999999997154 -0.1275000000003296 0 2228 0.2949999999997249 -0.1275000000003296 0 2229 0.2949999999997249 -0.1350000000003481 0 2230 0.2999999999997344 -0.1275000000003296 0 2231 0.3049999999997439 -0.1275000000003296 0 2232 0.3049999999997439 -0.1350000000003481 0 2233 0.3099999999997534 -0.1275000000003296 0 2234 0.3149999999997715 -0.1275000000003296 0 2235 0.3149999999997715 -0.1350000000003481 0 2236 0.3199999999997897 -0.1275000000003296 0 2237 0.3249999999998013 -0.1275000000003296 0 2238 0.3249999999998013 -0.1350000000003481 0 2239 0.329999999999813 -0.1275000000003296 0 2240 0.3349999999998236 -0.1275000000003296 0 2241 0.3349999999998236 -0.1350000000003481 0 2242 0.3399999999998342 -0.1275000000003296 0 2243 0.3449999999998534 -0.1275000000003296 0 2244 0.3449999999998534 -0.1350000000003481 0 2245 0.3499999999998726 -0.1275000000003296 0 2246 0.3549999999998821 -0.1275000000003296 0 2247 0.3549999999998821 -0.1350000000003481 0 2248 0.3599999999998915 -0.1275000000003296 0 2249 0.3649999999999043 -0.1275000000003296 0 2250 0.3649999999999043 -0.1350000000003481 0 2251 0.3699999999999171 -0.1275000000003296 0 2252 0.3749999999999342 -0.1275000000003296 0 2253 0.3749999999999341 -0.1350000000003481 0 2254 0.3799999999999513 -0.1275000000003296 0 2255 0.3849999999999607 -0.1275000000003296 0 2256 0.3849999999999607 -0.1350000000003481 0 2257 0.3899999999999702 -0.1275000000003296 0 2258 0.3949999999999851 -0.1275000000003296 0 2259 0.3949999999999851 -0.1350000000003481 0 2260 0.004999999999990963 -0.1425000000003666 0 2261 0.004999999999990963 -0.150000000000385 0 2262 0.009999999999981926 -0.1425000000003666 0 2263 0.01499999999997157 -0.1425000000003665 0 2264 0.01499999999997156 -0.150000000000385 0 2265 0.0199999999999612 -0.1425000000003666 0 2266 0.02499999999995261 -0.1425000000003666 0 2267 0.02499999999995261 -0.150000000000385 0 2268 0.02999999999994402 -0.1425000000003665 0 2269 0.03499999999993698 -0.1425000000003665 0 2270 0.03499999999993698 -0.150000000000385 0 2271 0.03999999999992994 -0.1425000000003665 0 2272 0.04499999999992209 -0.1425000000003665 0 2273 0.04499999999992209 -0.150000000000385 0 2274 0.04999999999991424 -0.1425000000003665 0 2275 0.05499999999990056 -0.1425000000003665 0 2276 0.05499999999990056 -0.150000000000385 0 2277 0.05999999999988688 -0.1425000000003665 0 2278 0.06499999999987254 -0.1425000000003665 0 2279 0.06499999999987254 -0.150000000000385 0 2280 0.06999999999985819 -0.1425000000003666 0 2281 0.07499999999984112 -0.1425000000003666 0 2282 0.07499999999984111 -0.150000000000385 0 2283 0.07999999999982405 -0.1425000000003665 0 2284 0.0849999999998097 -0.1425000000003665 0 2285 0.0849999999998097 -0.150000000000385 0 2286 0.08999999999979536 -0.1425000000003665 0 2287 0.09499999999978101 -0.1425000000003665 0 2288 0.09499999999978101 -0.150000000000385 0 2289 0.09999999999976666 -0.1425000000003665 0 2290 0.1049999999997523 -0.1425000000003665 0 2291 0.1049999999997523 -0.150000000000385 0 2292 0.109999999999738 -0.1425000000003665 0 2293 0.1149999999997236 -0.1425000000003665 0 2294 0.1149999999997236 -0.150000000000385 0 2295 0.1199999999997092 -0.1425000000003666 0 2296 0.1249999999996949 -0.1425000000003666 0 2297 0.1249999999996949 -0.150000000000385 0 2298 0.1299999999996806 -0.1425000000003665 0 2299 0.1349999999996662 -0.1425000000003665 0 2300 0.1349999999996662 -0.150000000000385 0 2301 0.1399999999996519 -0.1425000000003665 0 2302 0.1449999999996348 -0.1425000000003665 0 2303 0.1449999999996348 -0.150000000000385 0 2304 0.1499999999996177 -0.1425000000003665 0 2305 0.1549999999996007 -0.1425000000003665 0 2306 0.1549999999996007 -0.150000000000385 0 2307 0.1599999999995836 -0.1425000000003665 0 2308 0.1649999999995692 -0.1425000000003665 0 2309 0.1649999999995692 -0.150000000000385 0 2310 0.1699999999995549 -0.1425000000003665 0 2311 0.1749999999995406 -0.1425000000003665 0 2312 0.1749999999995406 -0.150000000000385 0 2313 0.1799999999995262 -0.1425000000003665 0 2314 0.1849999999995118 -0.1425000000003665 0 2315 0.1849999999995118 -0.150000000000385 0 2316 0.1899999999994975 -0.1425000000003665 0 2317 0.1949999999994859 -0.1425000000003665 0 2318 0.1949999999994859 -0.150000000000385 0 2319 0.1999999999994742 -0.1425000000003665 0 2320 0.2049999999994859 -0.1425000000003665 0 2321 0.2049999999994859 -0.150000000000385 0 2322 0.2099999999994975 -0.1425000000003665 0 2323 0.2149999999995092 -0.1425000000003665 0 2324 0.2149999999995092 -0.150000000000385 0 2325 0.2199999999995209 -0.1425000000003665 0 2326 0.2249999999995352 -0.1425000000003665 0 2327 0.2249999999995352 -0.150000000000385 0 2328 0.2299999999995495 -0.1425000000003665 0 2329 0.2349999999995633 -0.1425000000003665 0 2330 0.2349999999995633 -0.150000000000385 0 2331 0.2399999999995772 -0.1425000000003665 0 2332 0.2449999999995894 -0.1425000000003665 0 2333 0.2449999999995894 -0.150000000000385 0 2334 0.2499999999996015 -0.1425000000003665 0 2335 0.2549999999996181 -0.1425000000003665 0 2336 0.2549999999996181 -0.150000000000385 0 2337 0.2599999999996346 -0.1425000000003665 0 2338 0.2649999999996452 -0.1425000000003665 0 2339 0.2649999999996452 -0.150000000000385 0 2340 0.2699999999996559 -0.1425000000003665 0 2341 0.2749999999996675 -0.1425000000003665 0 2342 0.2749999999996675 -0.150000000000385 0 2343 0.2799999999996792 -0.1425000000003665 0 2344 0.2849999999996973 -0.1425000000003665 0 2345 0.2849999999996973 -0.150000000000385 0 2346 0.2899999999997154 -0.1425000000003665 0 2347 0.2949999999997249 -0.1425000000003665 0 2348 0.2949999999997249 -0.150000000000385 0 2349 0.2999999999997344 -0.1425000000003665 0 2350 0.3049999999997439 -0.1425000000003665 0 2351 0.3049999999997439 -0.150000000000385 0 2352 0.3099999999997534 -0.1425000000003665 0 2353 0.3149999999997715 -0.1425000000003665 0 2354 0.3149999999997715 -0.150000000000385 0 2355 0.3199999999997897 -0.1425000000003665 0 2356 0.3249999999998013 -0.1425000000003665 0 2357 0.3249999999998013 -0.150000000000385 0 2358 0.329999999999813 -0.1425000000003665 0 2359 0.3349999999998236 -0.1425000000003665 0 2360 0.3349999999998236 -0.150000000000385 0 2361 0.3399999999998342 -0.1425000000003665 0 2362 0.3449999999998534 -0.1425000000003665 0 2363 0.3449999999998534 -0.150000000000385 0 2364 0.3499999999998726 -0.1425000000003665 0 2365 0.3549999999998821 -0.1425000000003665 0 2366 0.3549999999998821 -0.150000000000385 0 2367 0.3599999999998915 -0.1425000000003665 0 2368 0.3649999999999043 -0.1425000000003665 0 2369 0.3649999999999043 -0.150000000000385 0 2370 0.3699999999999171 -0.1425000000003665 0 2371 0.3749999999999342 -0.1425000000003665 0 2372 0.3749999999999342 -0.150000000000385 0 2373 0.3799999999999512 -0.1425000000003665 0 2374 0.3849999999999607 -0.1425000000003665 0 2375 0.3849999999999607 -0.150000000000385 0 2376 0.3899999999999702 -0.1425000000003665 0 2377 0.3949999999999851 -0.1425000000003665 0 2378 0.3949999999999851 -0.150000000000385 0 2379 0.004999999999990963 -0.157500000000366 0 2380 0.004999999999990963 -0.165000000000347 0 2381 0.009999999999981926 -0.157500000000366 0 2382 0.01499999999997156 -0.157500000000366 0 2383 0.01499999999997157 -0.165000000000347 0 2384 0.0199999999999612 -0.157500000000366 0 2385 0.02499999999995261 -0.157500000000366 0 2386 0.02499999999995261 -0.165000000000347 0 2387 0.02999999999994401 -0.157500000000366 0 2388 0.03499999999993698 -0.157500000000366 0 2389 0.03499999999993698 -0.165000000000347 0 2390 0.03999999999992994 -0.157500000000366 0 2391 0.04499999999992209 -0.157500000000366 0 2392 0.04499999999992209 -0.165000000000347 0 2393 0.04999999999991424 -0.157500000000366 0 2394 0.05499999999990056 -0.157500000000366 0 2395 0.05499999999990056 -0.165000000000347 0 2396 0.05999999999988688 -0.157500000000366 0 2397 0.06499999999987254 -0.157500000000366 0 2398 0.06499999999987252 -0.165000000000347 0 2399 0.06999999999985818 -0.157500000000366 0 2400 0.07499999999984111 -0.157500000000366 0 2401 0.07499999999984111 -0.165000000000347 0 2402 0.07999999999982405 -0.157500000000366 0 2403 0.0849999999998097 -0.157500000000366 0 2404 0.0849999999998097 -0.165000000000347 0 2405 0.08999999999979536 -0.157500000000366 0 2406 0.09499999999978101 -0.157500000000366 0 2407 0.09499999999978101 -0.165000000000347 0 2408 0.09999999999976666 -0.157500000000366 0 2409 0.1049999999997523 -0.157500000000366 0 2410 0.1049999999997523 -0.165000000000347 0 2411 0.109999999999738 -0.157500000000366 0 2412 0.1149999999997236 -0.157500000000366 0 2413 0.1149999999997236 -0.165000000000347 0 2414 0.1199999999997092 -0.157500000000366 0 2415 0.1249999999996949 -0.157500000000366 0 2416 0.1249999999996949 -0.165000000000347 0 2417 0.1299999999996806 -0.157500000000366 0 2418 0.1349999999996662 -0.157500000000366 0 2419 0.1349999999996662 -0.165000000000347 0 2420 0.1399999999996519 -0.157500000000366 0 2421 0.1449999999996348 -0.157500000000366 0 2422 0.1449999999996348 -0.165000000000347 0 2423 0.1499999999996177 -0.157500000000366 0 2424 0.1549999999996007 -0.157500000000366 0 2425 0.1549999999996007 -0.165000000000347 0 2426 0.1599999999995836 -0.157500000000366 0 2427 0.1649999999995692 -0.157500000000366 0 2428 0.1649999999995692 -0.165000000000347 0 2429 0.1699999999995549 -0.157500000000366 0 2430 0.1749999999995406 -0.157500000000366 0 2431 0.1749999999995406 -0.165000000000347 0 2432 0.1799999999995262 -0.157500000000366 0 2433 0.1849999999995118 -0.157500000000366 0 2434 0.1849999999995118 -0.165000000000347 0 2435 0.1899999999994975 -0.157500000000366 0 2436 0.1949999999994858 -0.157500000000366 0 2437 0.1949999999994858 -0.165000000000347 0 2438 0.1999999999994742 -0.157500000000366 0 2439 0.2049999999994859 -0.157500000000366 0 2440 0.2049999999994859 -0.165000000000347 0 2441 0.2099999999994975 -0.157500000000366 0 2442 0.2149999999995092 -0.157500000000366 0 2443 0.2149999999995092 -0.165000000000347 0 2444 0.2199999999995209 -0.157500000000366 0 2445 0.2249999999995352 -0.157500000000366 0 2446 0.2249999999995352 -0.165000000000347 0 2447 0.2299999999995495 -0.157500000000366 0 2448 0.2349999999995633 -0.157500000000366 0 2449 0.2349999999995633 -0.165000000000347 0 2450 0.2399999999995772 -0.157500000000366 0 2451 0.2449999999995894 -0.157500000000366 0 2452 0.2449999999995894 -0.165000000000347 0 2453 0.2499999999996016 -0.157500000000366 0 2454 0.2549999999996181 -0.157500000000366 0 2455 0.2549999999996181 -0.165000000000347 0 2456 0.2599999999996346 -0.157500000000366 0 2457 0.2649999999996452 -0.157500000000366 0 2458 0.2649999999996452 -0.165000000000347 0 2459 0.2699999999996559 -0.157500000000366 0 2460 0.2749999999996675 -0.157500000000366 0 2461 0.2749999999996675 -0.165000000000347 0 2462 0.2799999999996792 -0.157500000000366 0 2463 0.2849999999996973 -0.157500000000366 0 2464 0.2849999999996973 -0.165000000000347 0 2465 0.2899999999997154 -0.157500000000366 0 2466 0.2949999999997249 -0.157500000000366 0 2467 0.2949999999997249 -0.165000000000347 0 2468 0.2999999999997344 -0.157500000000366 0 2469 0.3049999999997439 -0.157500000000366 0 2470 0.3049999999997439 -0.165000000000347 0 2471 0.3099999999997534 -0.157500000000366 0 2472 0.3149999999997715 -0.157500000000366 0 2473 0.3149999999997715 -0.165000000000347 0 2474 0.3199999999997897 -0.157500000000366 0 2475 0.3249999999998013 -0.157500000000366 0 2476 0.3249999999998013 -0.165000000000347 0 2477 0.329999999999813 -0.157500000000366 0 2478 0.3349999999998236 -0.157500000000366 0 2479 0.3349999999998236 -0.165000000000347 0 2480 0.3399999999998342 -0.157500000000366 0 2481 0.3449999999998534 -0.157500000000366 0 2482 0.3449999999998534 -0.165000000000347 0 2483 0.3499999999998726 -0.157500000000366 0 2484 0.3549999999998821 -0.157500000000366 0 2485 0.3549999999998821 -0.165000000000347 0 2486 0.3599999999998915 -0.157500000000366 0 2487 0.3649999999999043 -0.157500000000366 0 2488 0.3649999999999043 -0.165000000000347 0 2489 0.3699999999999171 -0.157500000000366 0 2490 0.3749999999999342 -0.157500000000366 0 2491 0.3749999999999342 -0.165000000000347 0 2492 0.3799999999999512 -0.157500000000366 0 2493 0.3849999999999607 -0.157500000000366 0 2494 0.3849999999999607 -0.165000000000347 0 2495 0.3899999999999702 -0.157500000000366 0 2496 0.3949999999999851 -0.157500000000366 0 2497 0.3949999999999851 -0.165000000000347 0 2498 0.004999999999990963 -0.1725000000003253 0 2499 0.004999999999990963 -0.1800000000003036 0 2500 0.009999999999981926 -0.1725000000003253 0 2501 0.01499999999997157 -0.1725000000003253 0 2502 0.01499999999997157 -0.1800000000003036 0 2503 0.01999999999996121 -0.1725000000003253 0 2504 0.02499999999995261 -0.1725000000003253 0 2505 0.02499999999995261 -0.1800000000003036 0 2506 0.02999999999994401 -0.1725000000003253 0 2507 0.03499999999993698 -0.1725000000003253 0 2508 0.03499999999993698 -0.1800000000003036 0 2509 0.03999999999992994 -0.1725000000003253 0 2510 0.04499999999992209 -0.1725000000003253 0 2511 0.04499999999992209 -0.1800000000003036 0 2512 0.04999999999991424 -0.1725000000003253 0 2513 0.05499999999990056 -0.1725000000003253 0 2514 0.05499999999990056 -0.1800000000003036 0 2515 0.05999999999988688 -0.1725000000003253 0 2516 0.06499999999987252 -0.1725000000003253 0 2517 0.06499999999987254 -0.1800000000003036 0 2518 0.06999999999985818 -0.1725000000003253 0 2519 0.07499999999984112 -0.1725000000003253 0 2520 0.07499999999984112 -0.1800000000003036 0 2521 0.07999999999982406 -0.1725000000003253 0 2522 0.0849999999998097 -0.1725000000003253 0 2523 0.0849999999998097 -0.1800000000003036 0 2524 0.08999999999979536 -0.1725000000003253 0 2525 0.09499999999978101 -0.1725000000003253 0 2526 0.09499999999978101 -0.1800000000003036 0 2527 0.09999999999976666 -0.1725000000003253 0 2528 0.1049999999997523 -0.1725000000003253 0 2529 0.1049999999997523 -0.1800000000003036 0 2530 0.109999999999738 -0.1725000000003253 0 2531 0.1149999999997236 -0.1725000000003253 0 2532 0.1149999999997236 -0.1800000000003036 0 2533 0.1199999999997092 -0.1725000000003253 0 2534 0.1249999999996949 -0.1725000000003253 0 2535 0.1249999999996949 -0.1800000000003036 0 2536 0.1299999999996806 -0.1725000000003253 0 2537 0.1349999999996662 -0.1725000000003253 0 2538 0.1349999999996662 -0.1800000000003036 0 2539 0.1399999999996519 -0.1725000000003253 0 2540 0.1449999999996348 -0.1725000000003253 0 2541 0.1449999999996348 -0.1800000000003036 0 2542 0.1499999999996177 -0.1725000000003253 0 2543 0.1549999999996007 -0.1725000000003253 0 2544 0.1549999999996007 -0.1800000000003036 0 2545 0.1599999999995836 -0.1725000000003253 0 2546 0.1649999999995692 -0.1725000000003253 0 2547 0.1649999999995692 -0.1800000000003036 0 2548 0.1699999999995549 -0.1725000000003253 0 2549 0.1749999999995406 -0.1725000000003253 0 2550 0.1749999999995406 -0.1800000000003036 0 2551 0.1799999999995262 -0.1725000000003253 0 2552 0.1849999999995118 -0.1725000000003253 0 2553 0.1849999999995118 -0.1800000000003036 0 2554 0.1899999999994975 -0.1725000000003253 0 2555 0.1949999999994858 -0.1725000000003253 0 2556 0.1949999999994858 -0.1800000000003036 0 2557 0.1999999999994742 -0.1725000000003253 0 2558 0.2049999999994859 -0.1725000000003253 0 2559 0.2049999999994859 -0.1800000000003036 0 2560 0.2099999999994975 -0.1725000000003253 0 2561 0.2149999999995092 -0.1725000000003253 0 2562 0.2149999999995092 -0.1800000000003036 0 2563 0.2199999999995209 -0.1725000000003253 0 2564 0.2249999999995352 -0.1725000000003253 0 2565 0.2249999999995352 -0.1800000000003036 0 2566 0.2299999999995495 -0.1725000000003253 0 2567 0.2349999999995633 -0.1725000000003253 0 2568 0.2349999999995634 -0.1800000000003036 0 2569 0.2399999999995772 -0.1725000000003253 0 2570 0.2449999999995894 -0.1725000000003253 0 2571 0.2449999999995894 -0.1800000000003036 0 2572 0.2499999999996016 -0.1725000000003253 0 2573 0.2549999999996181 -0.1725000000003253 0 2574 0.2549999999996181 -0.1800000000003036 0 2575 0.2599999999996346 -0.1725000000003253 0 2576 0.2649999999996452 -0.1725000000003253 0 2577 0.2649999999996452 -0.1800000000003036 0 2578 0.2699999999996559 -0.1725000000003253 0 2579 0.2749999999996675 -0.1725000000003253 0 2580 0.2749999999996675 -0.1800000000003036 0 2581 0.2799999999996792 -0.1725000000003253 0 2582 0.2849999999996973 -0.1725000000003253 0 2583 0.2849999999996973 -0.1800000000003036 0 2584 0.2899999999997154 -0.1725000000003253 0 2585 0.2949999999997249 -0.1725000000003253 0 2586 0.2949999999997249 -0.1800000000003036 0 2587 0.2999999999997344 -0.1725000000003253 0 2588 0.3049999999997439 -0.1725000000003253 0 2589 0.3049999999997439 -0.1800000000003036 0 2590 0.3099999999997534 -0.1725000000003253 0 2591 0.3149999999997715 -0.1725000000003253 0 2592 0.3149999999997715 -0.1800000000003036 0 2593 0.3199999999997897 -0.1725000000003253 0 2594 0.3249999999998013 -0.1725000000003253 0 2595 0.3249999999998013 -0.1800000000003036 0 2596 0.329999999999813 -0.1725000000003253 0 2597 0.3349999999998236 -0.1725000000003253 0 2598 0.3349999999998236 -0.1800000000003036 0 2599 0.3399999999998342 -0.1725000000003253 0 2600 0.3449999999998534 -0.1725000000003253 0 2601 0.3449999999998534 -0.1800000000003036 0 2602 0.3499999999998726 -0.1725000000003253 0 2603 0.3549999999998821 -0.1725000000003253 0 2604 0.3549999999998821 -0.1800000000003036 0 2605 0.3599999999998915 -0.1725000000003253 0 2606 0.3649999999999043 -0.1725000000003253 0 2607 0.3649999999999043 -0.1800000000003036 0 2608 0.3699999999999171 -0.1725000000003253 0 2609 0.3749999999999342 -0.1725000000003253 0 2610 0.3749999999999342 -0.1800000000003036 0 2611 0.3799999999999512 -0.1725000000003253 0 2612 0.3849999999999607 -0.1725000000003253 0 2613 0.3849999999999607 -0.1800000000003036 0 2614 0.3899999999999702 -0.1725000000003253 0 2615 0.3949999999999851 -0.1725000000003253 0 2616 0.3949999999999851 -0.1800000000003036 0 2617 0.004999999999990963 -0.1875000000002874 0 2618 0.004999999999990963 -0.1950000000002711 0 2619 0.009999999999981926 -0.1875000000002874 0 2620 0.01499999999997157 -0.1875000000002874 0 2621 0.01499999999997157 -0.1950000000002711 0 2622 0.01999999999996121 -0.1875000000002874 0 2623 0.02499999999995261 -0.1875000000002873 0 2624 0.0249999999999526 -0.1950000000002711 0 2625 0.02999999999994401 -0.1875000000002874 0 2626 0.03499999999993697 -0.1875000000002874 0 2627 0.03499999999993697 -0.1950000000002711 0 2628 0.03999999999992994 -0.1875000000002874 0 2629 0.04499999999992209 -0.1875000000002874 0 2630 0.04499999999992209 -0.1950000000002711 0 2631 0.04999999999991424 -0.1875000000002874 0 2632 0.05499999999990056 -0.1875000000002874 0 2633 0.05499999999990056 -0.1950000000002711 0 2634 0.05999999999988688 -0.1875000000002874 0 2635 0.06499999999987254 -0.1875000000002874 0 2636 0.06499999999987254 -0.1950000000002711 0 2637 0.06999999999985819 -0.1875000000002874 0 2638 0.07499999999984112 -0.1875000000002874 0 2639 0.07499999999984112 -0.1950000000002711 0 2640 0.07999999999982406 -0.1875000000002874 0 2641 0.0849999999998097 -0.1875000000002874 0 2642 0.0849999999998097 -0.1950000000002711 0 2643 0.08999999999979536 -0.1875000000002874 0 2644 0.09499999999978101 -0.1875000000002874 0 2645 0.09499999999978101 -0.1950000000002711 0 2646 0.09999999999976666 -0.1875000000002873 0 2647 0.1049999999997523 -0.1875000000002874 0 2648 0.1049999999997523 -0.1950000000002711 0 2649 0.109999999999738 -0.1875000000002874 0 2650 0.1149999999997236 -0.1875000000002874 0 2651 0.1149999999997236 -0.1950000000002711 0 2652 0.1199999999997092 -0.1875000000002874 0 2653 0.1249999999996949 -0.1875000000002874 0 2654 0.1249999999996949 -0.1950000000002711 0 2655 0.1299999999996806 -0.1875000000002874 0 2656 0.1349999999996662 -0.1875000000002874 0 2657 0.1349999999996662 -0.1950000000002711 0 2658 0.1399999999996519 -0.1875000000002874 0 2659 0.1449999999996348 -0.1875000000002874 0 2660 0.1449999999996348 -0.1950000000002711 0 2661 0.1499999999996177 -0.1875000000002874 0 2662 0.1549999999996007 -0.1875000000002874 0 2663 0.1549999999996007 -0.1950000000002711 0 2664 0.1599999999995836 -0.1875000000002874 0 2665 0.1649999999995692 -0.1875000000002874 0 2666 0.1649999999995692 -0.1950000000002711 0 2667 0.1699999999995549 -0.1875000000002874 0 2668 0.1749999999995406 -0.1875000000002874 0 2669 0.1749999999995406 -0.1950000000002711 0 2670 0.1799999999995262 -0.1875000000002874 0 2671 0.1849999999995118 -0.1875000000002874 0 2672 0.1849999999995118 -0.1950000000002711 0 2673 0.1899999999994975 -0.1875000000002874 0 2674 0.1949999999994859 -0.1875000000002874 0 2675 0.1949999999994859 -0.1950000000002711 0 2676 0.1999999999994742 -0.1875000000002874 0 2677 0.2049999999994859 -0.1875000000002874 0 2678 0.2049999999994859 -0.1950000000002711 0 2679 0.2099999999994975 -0.1875000000002874 0 2680 0.2149999999995092 -0.1875000000002874 0 2681 0.2149999999995092 -0.1950000000002711 0 2682 0.2199999999995209 -0.1875000000002874 0 2683 0.2249999999995352 -0.1875000000002874 0 2684 0.2249999999995352 -0.1950000000002711 0 2685 0.2299999999995495 -0.1875000000002874 0 2686 0.2349999999995634 -0.1875000000002874 0 2687 0.2349999999995633 -0.1950000000002711 0 2688 0.2399999999995772 -0.1875000000002874 0 2689 0.2449999999995894 -0.1875000000002874 0 2690 0.2449999999995894 -0.1950000000002711 0 2691 0.2499999999996015 -0.1875000000002874 0 2692 0.2549999999996181 -0.1875000000002874 0 2693 0.2549999999996181 -0.1950000000002711 0 2694 0.2599999999996346 -0.1875000000002874 0 2695 0.2649999999996452 -0.1875000000002874 0 2696 0.2649999999996452 -0.1950000000002711 0 2697 0.2699999999996559 -0.1875000000002874 0 2698 0.2749999999996675 -0.1875000000002874 0 2699 0.2749999999996675 -0.1950000000002712 0 2700 0.2799999999996792 -0.1875000000002874 0 2701 0.2849999999996973 -0.1875000000002874 0 2702 0.2849999999996973 -0.1950000000002711 0 2703 0.2899999999997154 -0.1875000000002874 0 2704 0.2949999999997249 -0.1875000000002874 0 2705 0.2949999999997249 -0.1950000000002711 0 2706 0.2999999999997344 -0.1875000000002874 0 2707 0.3049999999997439 -0.1875000000002874 0 2708 0.3049999999997439 -0.1950000000002711 0 2709 0.3099999999997534 -0.1875000000002874 0 2710 0.3149999999997715 -0.1875000000002874 0 2711 0.3149999999997715 -0.1950000000002711 0 2712 0.3199999999997897 -0.1875000000002874 0 2713 0.3249999999998013 -0.1875000000002874 0 2714 0.3249999999998013 -0.1950000000002711 0 2715 0.329999999999813 -0.1875000000002874 0 2716 0.3349999999998236 -0.1875000000002874 0 2717 0.3349999999998236 -0.1950000000002711 0 2718 0.3399999999998342 -0.1875000000002874 0 2719 0.3449999999998534 -0.1875000000002874 0 2720 0.3449999999998534 -0.1950000000002711 0 2721 0.3499999999998726 -0.1875000000002874 0 2722 0.3549999999998821 -0.1875000000002874 0 2723 0.3549999999998821 -0.1950000000002711 0 2724 0.3599999999998915 -0.1875000000002874 0 2725 0.3649999999999043 -0.1875000000002874 0 2726 0.3649999999999043 -0.1950000000002711 0 2727 0.3699999999999171 -0.1875000000002874 0 2728 0.3749999999999342 -0.1875000000002874 0 2729 0.3749999999999342 -0.1950000000002711 0 2730 0.3799999999999513 -0.1875000000002874 0 2731 0.3849999999999607 -0.1875000000002874 0 2732 0.3849999999999607 -0.1950000000002711 0 2733 0.3899999999999702 -0.1875000000002874 0 2734 0.3949999999999851 -0.1875000000002874 0 2735 0.3949999999999851 -0.1950000000002711 0 2736 0.004999999999990963 -0.2025000000002494 0 2737 0.004999999999990963 -0.2100000000002277 0 2738 0.009999999999981926 -0.2025000000002494 0 2739 0.01499999999997157 -0.2025000000002494 0 2740 0.01499999999997157 -0.2100000000002277 0 2741 0.01999999999996121 -0.2025000000002494 0 2742 0.0249999999999526 -0.2025000000002494 0 2743 0.02499999999995261 -0.2100000000002277 0 2744 0.02999999999994401 -0.2025000000002494 0 2745 0.03499999999993698 -0.2025000000002494 0 2746 0.03499999999993698 -0.2100000000002277 0 2747 0.03999999999992994 -0.2025000000002494 0 2748 0.04499999999992209 -0.2025000000002494 0 2749 0.04499999999992209 -0.2100000000002277 0 2750 0.04999999999991424 -0.2025000000002494 0 2751 0.05499999999990056 -0.2025000000002494 0 2752 0.05499999999990056 -0.2100000000002277 0 2753 0.05999999999988688 -0.2025000000002494 0 2754 0.06499999999987254 -0.2025000000002494 0 2755 0.06499999999987254 -0.2100000000002277 0 2756 0.06999999999985819 -0.2025000000002494 0 2757 0.07499999999984112 -0.2025000000002494 0 2758 0.07499999999984112 -0.2100000000002277 0 2759 0.07999999999982406 -0.2025000000002494 0 2760 0.0849999999998097 -0.2025000000002495 0 2761 0.0849999999998097 -0.2100000000002277 0 2762 0.08999999999979536 -0.2025000000002494 0 2763 0.09499999999978101 -0.2025000000002494 0 2764 0.09499999999978101 -0.2100000000002277 0 2765 0.09999999999976666 -0.2025000000002494 0 2766 0.1049999999997523 -0.2025000000002494 0 2767 0.1049999999997523 -0.2100000000002277 0 2768 0.109999999999738 -0.2025000000002494 0 2769 0.1149999999997236 -0.2025000000002495 0 2770 0.1149999999997236 -0.2100000000002277 0 2771 0.1199999999997092 -0.2025000000002495 0 2772 0.1249999999996949 -0.2025000000002494 0 2773 0.1249999999996949 -0.2100000000002277 0 2774 0.1299999999996806 -0.2025000000002494 0 2775 0.1349999999996662 -0.2025000000002494 0 2776 0.1349999999996662 -0.2100000000002277 0 2777 0.1399999999996519 -0.2025000000002494 0 2778 0.1449999999996348 -0.2025000000002494 0 2779 0.1449999999996348 -0.2100000000002277 0 2780 0.1499999999996177 -0.2025000000002494 0 2781 0.1549999999996007 -0.2025000000002494 0 2782 0.1549999999996007 -0.2100000000002277 0 2783 0.1599999999995836 -0.2025000000002494 0 2784 0.1649999999995692 -0.2025000000002494 0 2785 0.1649999999995692 -0.2100000000002277 0 2786 0.1699999999995549 -0.2025000000002494 0 2787 0.1749999999995406 -0.2025000000002494 0 2788 0.1749999999995406 -0.2100000000002277 0 2789 0.1799999999995262 -0.2025000000002494 0 2790 0.1849999999995118 -0.2025000000002494 0 2791 0.1849999999995118 -0.2100000000002277 0 2792 0.1899999999994975 -0.2025000000002494 0 2793 0.1949999999994859 -0.2025000000002494 0 2794 0.1949999999994859 -0.2100000000002277 0 2795 0.1999999999994742 -0.2025000000002494 0 2796 0.2049999999994859 -0.2025000000002494 0 2797 0.2049999999994859 -0.2100000000002277 0 2798 0.2099999999994975 -0.2025000000002494 0 2799 0.2149999999995092 -0.2025000000002494 0 2800 0.2149999999995092 -0.2100000000002277 0 2801 0.2199999999995209 -0.2025000000002494 0 2802 0.2249999999995352 -0.2025000000002494 0 2803 0.2249999999995352 -0.2100000000002277 0 2804 0.2299999999995495 -0.2025000000002494 0 2805 0.2349999999995633 -0.2025000000002494 0 2806 0.2349999999995633 -0.2100000000002277 0 2807 0.2399999999995772 -0.2025000000002494 0 2808 0.2449999999995893 -0.2025000000002494 0 2809 0.2449999999995893 -0.2100000000002277 0 2810 0.2499999999996015 -0.2025000000002494 0 2811 0.2549999999996181 -0.2025000000002494 0 2812 0.2549999999996181 -0.2100000000002277 0 2813 0.2599999999996346 -0.2025000000002494 0 2814 0.2649999999996452 -0.2025000000002494 0 2815 0.2649999999996452 -0.2100000000002277 0 2816 0.2699999999996559 -0.2025000000002495 0 2817 0.2749999999996675 -0.2025000000002494 0 2818 0.2749999999996675 -0.2100000000002277 0 2819 0.2799999999996792 -0.2025000000002494 0 2820 0.2849999999996973 -0.2025000000002494 0 2821 0.2849999999996973 -0.2100000000002277 0 2822 0.2899999999997154 -0.2025000000002494 0 2823 0.2949999999997249 -0.2025000000002494 0 2824 0.2949999999997249 -0.2100000000002277 0 2825 0.2999999999997344 -0.2025000000002494 0 2826 0.3049999999997439 -0.2025000000002494 0 2827 0.3049999999997439 -0.2100000000002277 0 2828 0.3099999999997534 -0.2025000000002494 0 2829 0.3149999999997715 -0.2025000000002494 0 2830 0.3149999999997715 -0.2100000000002277 0 2831 0.3199999999997897 -0.2025000000002494 0 2832 0.3249999999998013 -0.2025000000002494 0 2833 0.3249999999998013 -0.2100000000002277 0 2834 0.329999999999813 -0.2025000000002494 0 2835 0.3349999999998236 -0.2025000000002494 0 2836 0.3349999999998236 -0.2100000000002277 0 2837 0.3399999999998342 -0.2025000000002494 0 2838 0.3449999999998534 -0.2025000000002494 0 2839 0.3449999999998534 -0.2100000000002277 0 2840 0.3499999999998726 -0.2025000000002494 0 2841 0.3549999999998821 -0.2025000000002494 0 2842 0.3549999999998821 -0.2100000000002277 0 2843 0.3599999999998915 -0.2025000000002494 0 2844 0.3649999999999043 -0.2025000000002494 0 2845 0.3649999999999043 -0.2100000000002277 0 2846 0.3699999999999171 -0.2025000000002494 0 2847 0.3749999999999342 -0.2025000000002494 0 2848 0.3749999999999342 -0.2100000000002277 0 2849 0.3799999999999513 -0.2025000000002494 0 2850 0.3849999999999607 -0.2025000000002494 0 2851 0.3849999999999607 -0.2100000000002277 0 2852 0.3899999999999702 -0.2025000000002494 0 2853 0.394999999999985 -0.2025000000002494 0 2854 0.394999999999985 -0.2100000000002277 0 2855 0.004999999999990963 -0.217500000000206 0 2856 0.004999999999990963 -0.2250000000001843 0 2857 0.009999999999981926 -0.217500000000206 0 2858 0.01499999999997157 -0.217500000000206 0 2859 0.01499999999997157 -0.2250000000001843 0 2860 0.01999999999996121 -0.217500000000206 0 2861 0.02499999999995261 -0.217500000000206 0 2862 0.02499999999995261 -0.2250000000001843 0 2863 0.02999999999994402 -0.217500000000206 0 2864 0.03499999999993698 -0.217500000000206 0 2865 0.03499999999993698 -0.2250000000001843 0 2866 0.03999999999992994 -0.217500000000206 0 2867 0.04499999999992209 -0.217500000000206 0 2868 0.04499999999992209 -0.2250000000001843 0 2869 0.04999999999991424 -0.217500000000206 0 2870 0.05499999999990056 -0.217500000000206 0 2871 0.05499999999990056 -0.2250000000001843 0 2872 0.05999999999988688 -0.217500000000206 0 2873 0.06499999999987254 -0.217500000000206 0 2874 0.06499999999987254 -0.2250000000001843 0 2875 0.06999999999985819 -0.217500000000206 0 2876 0.07499999999984112 -0.217500000000206 0 2877 0.07499999999984112 -0.2250000000001843 0 2878 0.07999999999982406 -0.217500000000206 0 2879 0.0849999999998097 -0.217500000000206 0 2880 0.0849999999998097 -0.2250000000001843 0 2881 0.08999999999979536 -0.217500000000206 0 2882 0.09499999999978101 -0.217500000000206 0 2883 0.09499999999978101 -0.2250000000001843 0 2884 0.09999999999976666 -0.217500000000206 0 2885 0.1049999999997523 -0.217500000000206 0 2886 0.1049999999997523 -0.2250000000001843 0 2887 0.109999999999738 -0.217500000000206 0 2888 0.1149999999997236 -0.217500000000206 0 2889 0.1149999999997236 -0.2250000000001843 0 2890 0.1199999999997092 -0.217500000000206 0 2891 0.1249999999996949 -0.217500000000206 0 2892 0.1249999999996949 -0.2250000000001843 0 2893 0.1299999999996806 -0.217500000000206 0 2894 0.1349999999996662 -0.217500000000206 0 2895 0.1349999999996662 -0.2250000000001843 0 2896 0.1399999999996519 -0.217500000000206 0 2897 0.1449999999996348 -0.217500000000206 0 2898 0.1449999999996348 -0.2250000000001843 0 2899 0.1499999999996177 -0.217500000000206 0 2900 0.1549999999996007 -0.217500000000206 0 2901 0.1549999999996007 -0.2250000000001843 0 2902 0.1599999999995836 -0.217500000000206 0 2903 0.1649999999995692 -0.217500000000206 0 2904 0.1649999999995692 -0.2250000000001843 0 2905 0.1699999999995549 -0.217500000000206 0 2906 0.1749999999995406 -0.217500000000206 0 2907 0.1749999999995405 -0.2250000000001843 0 2908 0.1799999999995262 -0.217500000000206 0 2909 0.1849999999995118 -0.217500000000206 0 2910 0.1849999999995118 -0.2250000000001843 0 2911 0.1899999999994975 -0.217500000000206 0 2912 0.1949999999994859 -0.217500000000206 0 2913 0.1949999999994859 -0.2250000000001843 0 2914 0.1999999999994742 -0.217500000000206 0 2915 0.2049999999994859 -0.217500000000206 0 2916 0.2049999999994859 -0.2250000000001843 0 2917 0.2099999999994975 -0.217500000000206 0 2918 0.2149999999995092 -0.217500000000206 0 2919 0.2149999999995092 -0.2250000000001843 0 2920 0.2199999999995209 -0.217500000000206 0 2921 0.2249999999995352 -0.217500000000206 0 2922 0.2249999999995352 -0.2250000000001843 0 2923 0.2299999999995495 -0.217500000000206 0 2924 0.2349999999995633 -0.217500000000206 0 2925 0.2349999999995633 -0.2250000000001843 0 2926 0.2399999999995772 -0.217500000000206 0 2927 0.2449999999995893 -0.217500000000206 0 2928 0.2449999999995894 -0.2250000000001843 0 2929 0.2499999999996015 -0.217500000000206 0 2930 0.2549999999996181 -0.217500000000206 0 2931 0.2549999999996181 -0.2250000000001843 0 2932 0.2599999999996346 -0.217500000000206 0 2933 0.2649999999996452 -0.217500000000206 0 2934 0.2649999999996452 -0.2250000000001843 0 2935 0.2699999999996559 -0.217500000000206 0 2936 0.2749999999996675 -0.217500000000206 0 2937 0.2749999999996675 -0.2250000000001843 0 2938 0.2799999999996792 -0.217500000000206 0 2939 0.2849999999996973 -0.217500000000206 0 2940 0.2849999999996973 -0.2250000000001843 0 2941 0.2899999999997154 -0.217500000000206 0 2942 0.2949999999997249 -0.217500000000206 0 2943 0.2949999999997249 -0.2250000000001843 0 2944 0.2999999999997344 -0.217500000000206 0 2945 0.3049999999997439 -0.217500000000206 0 2946 0.3049999999997439 -0.2250000000001843 0 2947 0.3099999999997534 -0.217500000000206 0 2948 0.3149999999997715 -0.217500000000206 0 2949 0.3149999999997715 -0.2250000000001843 0 2950 0.3199999999997897 -0.217500000000206 0 2951 0.3249999999998013 -0.217500000000206 0 2952 0.3249999999998013 -0.2250000000001843 0 2953 0.329999999999813 -0.217500000000206 0 2954 0.3349999999998236 -0.217500000000206 0 2955 0.3349999999998236 -0.2250000000001843 0 2956 0.3399999999998342 -0.217500000000206 0 2957 0.3449999999998534 -0.217500000000206 0 2958 0.3449999999998534 -0.2250000000001843 0 2959 0.3499999999998726 -0.217500000000206 0 2960 0.3549999999998821 -0.217500000000206 0 2961 0.3549999999998821 -0.2250000000001843 0 2962 0.3599999999998915 -0.217500000000206 0 2963 0.3649999999999043 -0.217500000000206 0 2964 0.3649999999999043 -0.2250000000001843 0 2965 0.3699999999999171 -0.217500000000206 0 2966 0.3749999999999342 -0.217500000000206 0 2967 0.3749999999999342 -0.2250000000001843 0 2968 0.3799999999999513 -0.217500000000206 0 2969 0.3849999999999607 -0.217500000000206 0 2970 0.3849999999999607 -0.2250000000001843 0 2971 0.3899999999999701 -0.217500000000206 0 2972 0.394999999999985 -0.217500000000206 0 2973 0.394999999999985 -0.2250000000001843 0 2974 0.004999999999990963 -0.2325000000001626 0 2975 0.004999999999990963 -0.240000000000141 0 2976 0.009999999999981926 -0.2325000000001626 0 2977 0.01499999999997157 -0.2325000000001626 0 2978 0.01499999999997157 -0.240000000000141 0 2979 0.01999999999996121 -0.2325000000001626 0 2980 0.02499999999995261 -0.2325000000001627 0 2981 0.02499999999995261 -0.240000000000141 0 2982 0.02999999999994402 -0.2325000000001627 0 2983 0.03499999999993698 -0.2325000000001627 0 2984 0.03499999999993698 -0.2400000000001409 0 2985 0.03999999999992994 -0.2325000000001626 0 2986 0.04499999999992209 -0.2325000000001626 0 2987 0.04499999999992209 -0.2400000000001409 0 2988 0.04999999999991424 -0.2325000000001626 0 2989 0.05499999999990056 -0.2325000000001626 0 2990 0.05499999999990056 -0.240000000000141 0 2991 0.05999999999988688 -0.2325000000001626 0 2992 0.06499999999987254 -0.2325000000001626 0 2993 0.06499999999987254 -0.240000000000141 0 2994 0.06999999999985819 -0.2325000000001626 0 2995 0.07499999999984112 -0.2325000000001626 0 2996 0.07499999999984111 -0.2400000000001409 0 2997 0.07999999999982405 -0.2325000000001626 0 2998 0.0849999999998097 -0.2325000000001626 0 2999 0.0849999999998097 -0.2400000000001409 0 3000 0.08999999999979536 -0.2325000000001626 0 3001 0.09499999999978101 -0.2325000000001626 0 3002 0.09499999999978101 -0.2400000000001409 0 3003 0.09999999999976666 -0.2325000000001626 0 3004 0.1049999999997523 -0.2325000000001627 0 3005 0.1049999999997523 -0.240000000000141 0 3006 0.109999999999738 -0.2325000000001627 0 3007 0.1149999999997236 -0.2325000000001627 0 3008 0.1149999999997236 -0.240000000000141 0 3009 0.1199999999997092 -0.2325000000001627 0 3010 0.1249999999996949 -0.2325000000001627 0 3011 0.1249999999996949 -0.240000000000141 0 3012 0.1299999999996806 -0.2325000000001627 0 3013 0.1349999999996662 -0.2325000000001627 0 3014 0.1349999999996662 -0.240000000000141 0 3015 0.1399999999996519 -0.2325000000001626 0 3016 0.1449999999996348 -0.2325000000001626 0 3017 0.1449999999996348 -0.240000000000141 0 3018 0.1499999999996177 -0.2325000000001627 0 3019 0.1549999999996007 -0.2325000000001627 0 3020 0.1549999999996006 -0.240000000000141 0 3021 0.1599999999995836 -0.2325000000001627 0 3022 0.1649999999995692 -0.2325000000001627 0 3023 0.1649999999995692 -0.2400000000001409 0 3024 0.1699999999995549 -0.2325000000001626 0 3025 0.1749999999995405 -0.2325000000001626 0 3026 0.1749999999995406 -0.2400000000001409 0 3027 0.1799999999995262 -0.2325000000001627 0 3028 0.1849999999995118 -0.2325000000001627 0 3029 0.1849999999995118 -0.240000000000141 0 3030 0.1899999999994975 -0.2325000000001626 0 3031 0.1949999999994859 -0.2325000000001626 0 3032 0.1949999999994859 -0.2400000000001409 0 3033 0.1999999999994742 -0.2325000000001626 0 3034 0.2049999999994859 -0.2325000000001626 0 3035 0.2049999999994859 -0.2400000000001409 0 3036 0.2099999999994975 -0.2325000000001627 0 3037 0.2149999999995092 -0.2325000000001627 0 3038 0.2149999999995092 -0.240000000000141 0 3039 0.2199999999995209 -0.2325000000001627 0 3040 0.2249999999995352 -0.2325000000001627 0 3041 0.2249999999995352 -0.240000000000141 0 3042 0.2299999999995495 -0.2325000000001626 0 3043 0.2349999999995633 -0.2325000000001626 0 3044 0.2349999999995633 -0.2400000000001409 0 3045 0.2399999999995772 -0.2325000000001626 0 3046 0.2449999999995894 -0.2325000000001626 0 3047 0.2449999999995894 -0.240000000000141 0 3048 0.2499999999996015 -0.2325000000001626 0 3049 0.2549999999996181 -0.2325000000001626 0 3050 0.2549999999996181 -0.2400000000001409 0 3051 0.2599999999996346 -0.2325000000001626 0 3052 0.2649999999996452 -0.2325000000001626 0 3053 0.2649999999996452 -0.2400000000001409 0 3054 0.2699999999996559 -0.2325000000001626 0 3055 0.2749999999996675 -0.2325000000001626 0 3056 0.2749999999996675 -0.240000000000141 0 3057 0.2799999999996792 -0.2325000000001627 0 3058 0.2849999999996973 -0.2325000000001627 0 3059 0.2849999999996973 -0.240000000000141 0 3060 0.2899999999997154 -0.2325000000001627 0 3061 0.2949999999997249 -0.2325000000001627 0 3062 0.2949999999997249 -0.240000000000141 0 3063 0.2999999999997344 -0.2325000000001627 0 3064 0.3049999999997439 -0.2325000000001627 0 3065 0.3049999999997439 -0.240000000000141 0 3066 0.3099999999997534 -0.2325000000001626 0 3067 0.3149999999997715 -0.2325000000001626 0 3068 0.3149999999997715 -0.240000000000141 0 3069 0.3199999999997897 -0.2325000000001626 0 3070 0.3249999999998013 -0.2325000000001626 0 3071 0.3249999999998013 -0.240000000000141 0 3072 0.329999999999813 -0.2325000000001627 0 3073 0.3349999999998236 -0.2325000000001627 0 3074 0.3349999999998236 -0.240000000000141 0 3075 0.3399999999998342 -0.2325000000001627 0 3076 0.3449999999998534 -0.2325000000001627 0 3077 0.3449999999998534 -0.240000000000141 0 3078 0.3499999999998726 -0.2325000000001627 0 3079 0.3549999999998821 -0.2325000000001626 0 3080 0.3549999999998821 -0.240000000000141 0 3081 0.3599999999998915 -0.2325000000001627 0 3082 0.3649999999999043 -0.2325000000001627 0 3083 0.3649999999999043 -0.240000000000141 0 3084 0.3699999999999171 -0.2325000000001626 0 3085 0.3749999999999342 -0.2325000000001626 0 3086 0.3749999999999342 -0.240000000000141 0 3087 0.3799999999999513 -0.2325000000001626 0 3088 0.3849999999999607 -0.2325000000001626 0 3089 0.3849999999999607 -0.240000000000141 0 3090 0.3899999999999701 -0.2325000000001626 0 3091 0.394999999999985 -0.2325000000001626 0 3092 0.394999999999985 -0.240000000000141 0 3093 0.004999999999990963 -0.2475000000001095 0 3094 0.004999999999990963 -0.2550000000000781 0 3095 0.009999999999981926 -0.2475000000001095 0 3096 0.01499999999997157 -0.2475000000001095 0 3097 0.01499999999997157 -0.2550000000000781 0 3098 0.01999999999996121 -0.2475000000001095 0 3099 0.02499999999995261 -0.2475000000001095 0 3100 0.02499999999995261 -0.2550000000000781 0 3101 0.02999999999994401 -0.2475000000001096 0 3102 0.03499999999993698 -0.2475000000001095 0 3103 0.03499999999993698 -0.2550000000000781 0 3104 0.03999999999992994 -0.2475000000001095 0 3105 0.04499999999992209 -0.2475000000001095 0 3106 0.04499999999992209 -0.2550000000000781 0 3107 0.04999999999991424 -0.2475000000001095 0 3108 0.05499999999990056 -0.2475000000001095 0 3109 0.05499999999990056 -0.2550000000000781 0 3110 0.05999999999988688 -0.2475000000001095 0 3111 0.06499999999987254 -0.2475000000001095 0 3112 0.06499999999987252 -0.2550000000000781 0 3113 0.06999999999985818 -0.2475000000001095 0 3114 0.07499999999984111 -0.2475000000001095 0 3115 0.07499999999984111 -0.2550000000000781 0 3116 0.07999999999982405 -0.2475000000001095 0 3117 0.0849999999998097 -0.2475000000001095 0 3118 0.0849999999998097 -0.2550000000000781 0 3119 0.08999999999979536 -0.2475000000001095 0 3120 0.09499999999978101 -0.2475000000001095 0 3121 0.09499999999978101 -0.2550000000000781 0 3122 0.09999999999976666 -0.2475000000001095 0 3123 0.1049999999997523 -0.2475000000001095 0 3124 0.1049999999997523 -0.2550000000000781 0 3125 0.109999999999738 -0.2475000000001095 0 3126 0.1149999999997236 -0.2475000000001095 0 3127 0.1149999999997236 -0.2550000000000781 0 3128 0.1199999999997092 -0.2475000000001095 0 3129 0.1249999999996949 -0.2475000000001095 0 3130 0.1249999999996949 -0.2550000000000781 0 3131 0.1299999999996806 -0.2475000000001095 0 3132 0.1349999999996662 -0.2475000000001095 0 3133 0.1349999999996662 -0.2550000000000781 0 3134 0.1399999999996519 -0.2475000000001095 0 3135 0.1449999999996348 -0.2475000000001095 0 3136 0.1449999999996348 -0.2550000000000781 0 3137 0.1499999999996177 -0.2475000000001095 0 3138 0.1549999999996006 -0.2475000000001095 0 3139 0.1549999999996007 -0.2550000000000781 0 3140 0.1599999999995836 -0.2475000000001095 0 3141 0.1649999999995692 -0.2475000000001095 0 3142 0.1649999999995692 -0.2550000000000781 0 3143 0.1699999999995549 -0.2475000000001095 0 3144 0.1749999999995406 -0.2475000000001095 0 3145 0.1749999999995406 -0.2550000000000781 0 3146 0.1799999999995262 -0.2475000000001095 0 3147 0.1849999999995118 -0.2475000000001095 0 3148 0.1849999999995118 -0.2550000000000781 0 3149 0.1899999999994975 -0.2475000000001095 0 3150 0.1949999999994859 -0.2475000000001095 0 3151 0.1949999999994859 -0.2550000000000781 0 3152 0.1999999999994742 -0.2475000000001095 0 3153 0.2049999999994859 -0.2475000000001095 0 3154 0.2049999999994859 -0.2550000000000781 0 3155 0.2099999999994975 -0.2475000000001095 0 3156 0.2149999999995092 -0.2475000000001095 0 3157 0.2149999999995092 -0.2550000000000781 0 3158 0.2199999999995209 -0.2475000000001095 0 3159 0.2249999999995352 -0.2475000000001095 0 3160 0.2249999999995352 -0.2550000000000781 0 3161 0.2299999999995495 -0.2475000000001095 0 3162 0.2349999999995633 -0.2475000000001095 0 3163 0.2349999999995633 -0.2550000000000781 0 3164 0.2399999999995772 -0.2475000000001095 0 3165 0.2449999999995894 -0.2475000000001095 0 3166 0.2449999999995894 -0.2550000000000781 0 3167 0.2499999999996015 -0.2475000000001095 0 3168 0.2549999999996181 -0.2475000000001095 0 3169 0.2549999999996181 -0.2550000000000781 0 3170 0.2599999999996346 -0.2475000000001095 0 3171 0.2649999999996452 -0.2475000000001095 0 3172 0.2649999999996452 -0.2550000000000781 0 3173 0.2699999999996559 -0.2475000000001095 0 3174 0.2749999999996675 -0.2475000000001095 0 3175 0.2749999999996675 -0.2550000000000781 0 3176 0.2799999999996792 -0.2475000000001095 0 3177 0.2849999999996973 -0.2475000000001095 0 3178 0.2849999999996973 -0.2550000000000781 0 3179 0.2899999999997154 -0.2475000000001095 0 3180 0.2949999999997249 -0.2475000000001095 0 3181 0.2949999999997249 -0.2550000000000781 0 3182 0.2999999999997344 -0.2475000000001095 0 3183 0.3049999999997439 -0.2475000000001095 0 3184 0.3049999999997439 -0.2550000000000781 0 3185 0.3099999999997534 -0.2475000000001095 0 3186 0.3149999999997715 -0.2475000000001095 0 3187 0.3149999999997715 -0.2550000000000781 0 3188 0.3199999999997897 -0.2475000000001095 0 3189 0.3249999999998013 -0.2475000000001096 0 3190 0.3249999999998013 -0.2550000000000781 0 3191 0.329999999999813 -0.2475000000001096 0 3192 0.3349999999998236 -0.2475000000001095 0 3193 0.3349999999998236 -0.2550000000000781 0 3194 0.3399999999998342 -0.2475000000001095 0 3195 0.3449999999998534 -0.2475000000001095 0 3196 0.3449999999998534 -0.2550000000000781 0 3197 0.3499999999998726 -0.2475000000001095 0 3198 0.3549999999998821 -0.2475000000001095 0 3199 0.3549999999998821 -0.2550000000000781 0 3200 0.3599999999998915 -0.2475000000001095 0 3201 0.3649999999999043 -0.2475000000001095 0 3202 0.3649999999999043 -0.2550000000000781 0 3203 0.3699999999999171 -0.2475000000001095 0 3204 0.3749999999999342 -0.2475000000001095 0 3205 0.3749999999999341 -0.2550000000000781 0 3206 0.3799999999999513 -0.2475000000001095 0 3207 0.3849999999999607 -0.2475000000001095 0 3208 0.3849999999999607 -0.2550000000000781 0 3209 0.3899999999999702 -0.2475000000001095 0 3210 0.3949999999999851 -0.2475000000001095 0 3211 0.3949999999999851 -0.2550000000000781 0 3212 0.004999999999990963 -0.2625000000000553 0 3213 0.004999999999990963 -0.2700000000000326 0 3214 0.009999999999981926 -0.2625000000000554 0 3215 0.01499999999997157 -0.2625000000000554 0 3216 0.01499999999997157 -0.2700000000000326 0 3217 0.01999999999996121 -0.2625000000000553 0 3218 0.02499999999995261 -0.2625000000000554 0 3219 0.02499999999995261 -0.2700000000000326 0 3220 0.02999999999994402 -0.2625000000000554 0 3221 0.03499999999993698 -0.2625000000000554 0 3222 0.03499999999993698 -0.2700000000000326 0 3223 0.03999999999992994 -0.2625000000000554 0 3224 0.04499999999992209 -0.2625000000000554 0 3225 0.04499999999992209 -0.2700000000000326 0 3226 0.04999999999991424 -0.2625000000000554 0 3227 0.05499999999990056 -0.2625000000000554 0 3228 0.05499999999990056 -0.2700000000000326 0 3229 0.05999999999988688 -0.2625000000000553 0 3230 0.06499999999987252 -0.2625000000000552 0 3231 0.06499999999987254 -0.2700000000000326 0 3232 0.06999999999985818 -0.2625000000000552 0 3233 0.07499999999984111 -0.2625000000000553 0 3234 0.07499999999984112 -0.2700000000000326 0 3235 0.07999999999982405 -0.2625000000000553 0 3236 0.0849999999998097 -0.2625000000000553 0 3237 0.0849999999998097 -0.2700000000000326 0 3238 0.08999999999979536 -0.2625000000000553 0 3239 0.09499999999978101 -0.2625000000000553 0 3240 0.09499999999978101 -0.2700000000000326 0 3241 0.09999999999976666 -0.2625000000000553 0 3242 0.1049999999997523 -0.2625000000000553 0 3243 0.1049999999997523 -0.2700000000000326 0 3244 0.109999999999738 -0.2625000000000553 0 3245 0.1149999999997236 -0.2625000000000553 0 3246 0.1149999999997236 -0.2700000000000326 0 3247 0.1199999999997092 -0.2625000000000553 0 3248 0.1249999999996949 -0.2625000000000553 0 3249 0.1249999999996949 -0.2700000000000326 0 3250 0.1299999999996806 -0.2625000000000553 0 3251 0.1349999999996662 -0.2625000000000553 0 3252 0.1349999999996662 -0.2700000000000326 0 3253 0.1399999999996519 -0.2625000000000553 0 3254 0.1449999999996348 -0.2625000000000553 0 3255 0.1449999999996348 -0.2700000000000326 0 3256 0.1499999999996177 -0.2625000000000553 0 3257 0.1549999999996007 -0.2625000000000553 0 3258 0.1549999999996006 -0.2700000000000326 0 3259 0.1599999999995836 -0.2625000000000553 0 3260 0.1649999999995692 -0.2625000000000553 0 3261 0.1649999999995692 -0.2700000000000326 0 3262 0.1699999999995549 -0.2625000000000553 0 3263 0.1749999999995406 -0.2625000000000553 0 3264 0.1749999999995405 -0.2700000000000326 0 3265 0.1799999999995262 -0.2625000000000553 0 3266 0.1849999999995118 -0.2625000000000553 0 3267 0.1849999999995118 -0.2700000000000326 0 3268 0.1899999999994975 -0.2625000000000553 0 3269 0.1949999999994859 -0.2625000000000553 0 3270 0.1949999999994859 -0.2700000000000326 0 3271 0.1999999999994742 -0.2625000000000553 0 3272 0.2049999999994859 -0.2625000000000553 0 3273 0.2049999999994859 -0.2700000000000326 0 3274 0.2099999999994975 -0.2625000000000553 0 3275 0.2149999999995092 -0.2625000000000553 0 3276 0.2149999999995092 -0.2700000000000326 0 3277 0.2199999999995209 -0.2625000000000553 0 3278 0.2249999999995352 -0.2625000000000553 0 3279 0.2249999999995352 -0.2700000000000326 0 3280 0.2299999999995495 -0.2625000000000553 0 3281 0.2349999999995634 -0.2625000000000553 0 3282 0.2349999999995634 -0.2700000000000326 0 3283 0.2399999999995772 -0.2625000000000553 0 3284 0.2449999999995894 -0.2625000000000553 0 3285 0.2449999999995893 -0.2700000000000326 0 3286 0.2499999999996015 -0.2625000000000553 0 3287 0.2549999999996181 -0.2625000000000553 0 3288 0.2549999999996181 -0.2700000000000326 0 3289 0.2599999999996346 -0.2625000000000553 0 3290 0.2649999999996453 -0.2625000000000553 0 3291 0.2649999999996452 -0.2700000000000326 0 3292 0.2699999999996559 -0.2625000000000553 0 3293 0.2749999999996675 -0.2625000000000553 0 3294 0.2749999999996675 -0.2700000000000326 0 3295 0.2799999999996792 -0.2625000000000553 0 3296 0.2849999999996973 -0.2625000000000553 0 3297 0.2849999999996973 -0.2700000000000326 0 3298 0.2899999999997154 -0.2625000000000553 0 3299 0.2949999999997249 -0.2625000000000553 0 3300 0.2949999999997249 -0.2700000000000326 0 3301 0.2999999999997344 -0.2625000000000553 0 3302 0.3049999999997439 -0.2625000000000553 0 3303 0.3049999999997439 -0.2700000000000326 0 3304 0.3099999999997534 -0.2625000000000553 0 3305 0.3149999999997715 -0.2625000000000553 0 3306 0.3149999999997715 -0.2700000000000326 0 3307 0.3199999999997897 -0.2625000000000553 0 3308 0.3249999999998013 -0.2625000000000553 0 3309 0.3249999999998013 -0.2700000000000326 0 3310 0.329999999999813 -0.2625000000000553 0 3311 0.3349999999998236 -0.2625000000000553 0 3312 0.3349999999998236 -0.2700000000000326 0 3313 0.3399999999998342 -0.2625000000000553 0 3314 0.3449999999998534 -0.2625000000000553 0 3315 0.3449999999998534 -0.2700000000000326 0 3316 0.3499999999998726 -0.2625000000000553 0 3317 0.3549999999998821 -0.2625000000000553 0 3318 0.3549999999998821 -0.2700000000000326 0 3319 0.3599999999998915 -0.2625000000000554 0 3320 0.3649999999999043 -0.2625000000000554 0 3321 0.3649999999999043 -0.2700000000000326 0 3322 0.369999999999917 -0.2625000000000553 0 3323 0.3749999999999341 -0.2625000000000553 0 3324 0.3749999999999341 -0.2700000000000326 0 3325 0.3799999999999512 -0.2625000000000553 0 3326 0.3849999999999607 -0.2625000000000553 0 3327 0.3849999999999607 -0.2700000000000326 0 3328 0.3899999999999702 -0.2625000000000553 0 3329 0.3949999999999851 -0.2625000000000553 0 3330 0.3949999999999851 -0.2700000000000326 0 3331 0.004999999999990963 -0.2775000000000217 0 3332 0.004999999999990962 -0.2850000000000109 0 3333 0.009999999999981926 -0.2775000000000217 0 3334 0.01499999999997156 -0.2775000000000217 0 3335 0.01499999999997156 -0.2850000000000109 0 3336 0.01999999999996121 -0.2775000000000217 0 3337 0.02499999999995261 -0.2775000000000217 0 3338 0.02499999999995261 -0.2850000000000109 0 3339 0.02999999999994402 -0.2775000000000217 0 3340 0.03499999999993698 -0.2775000000000217 0 3341 0.03499999999993698 -0.2850000000000108 0 3342 0.03999999999992994 -0.2775000000000217 0 3343 0.04499999999992209 -0.2775000000000217 0 3344 0.04499999999992209 -0.2850000000000108 0 3345 0.04999999999991424 -0.2775000000000217 0 3346 0.05499999999990056 -0.2775000000000217 0 3347 0.05499999999990056 -0.2850000000000109 0 3348 0.05999999999988687 -0.2775000000000217 0 3349 0.06499999999987254 -0.2775000000000217 0 3350 0.06499999999987254 -0.2850000000000108 0 3351 0.06999999999985819 -0.2775000000000217 0 3352 0.07499999999984112 -0.2775000000000217 0 3353 0.07499999999984112 -0.2850000000000108 0 3354 0.07999999999982406 -0.2775000000000217 0 3355 0.0849999999998097 -0.2775000000000217 0 3356 0.0849999999998097 -0.2850000000000109 0 3357 0.08999999999979536 -0.2775000000000217 0 3358 0.09499999999978101 -0.2775000000000217 0 3359 0.09499999999978101 -0.2850000000000109 0 3360 0.09999999999976666 -0.2775000000000217 0 3361 0.1049999999997523 -0.2775000000000217 0 3362 0.1049999999997523 -0.2850000000000109 0 3363 0.109999999999738 -0.2775000000000217 0 3364 0.1149999999997236 -0.2775000000000217 0 3365 0.1149999999997236 -0.2850000000000108 0 3366 0.1199999999997092 -0.2775000000000217 0 3367 0.1249999999996949 -0.2775000000000217 0 3368 0.1249999999996949 -0.2850000000000108 0 3369 0.1299999999996806 -0.2775000000000217 0 3370 0.1349999999996662 -0.2775000000000217 0 3371 0.1349999999996662 -0.2850000000000109 0 3372 0.1399999999996518 -0.2775000000000217 0 3373 0.1449999999996348 -0.2775000000000217 0 3374 0.1449999999996348 -0.2850000000000109 0 3375 0.1499999999996177 -0.2775000000000217 0 3376 0.1549999999996006 -0.2775000000000217 0 3377 0.1549999999996006 -0.2850000000000109 0 3378 0.1599999999995836 -0.2775000000000217 0 3379 0.1649999999995692 -0.2775000000000217 0 3380 0.1649999999995692 -0.2850000000000108 0 3381 0.1699999999995549 -0.2775000000000217 0 3382 0.1749999999995405 -0.2775000000000217 0 3383 0.1749999999995406 -0.2850000000000108 0 3384 0.1799999999995262 -0.2775000000000217 0 3385 0.1849999999995118 -0.2775000000000217 0 3386 0.1849999999995118 -0.2850000000000109 0 3387 0.1899999999994975 -0.2775000000000217 0 3388 0.1949999999994859 -0.2775000000000217 0 3389 0.1949999999994859 -0.2850000000000109 0 3390 0.1999999999994742 -0.2775000000000217 0 3391 0.2049999999994859 -0.2775000000000217 0 3392 0.2049999999994859 -0.2850000000000108 0 3393 0.2099999999994975 -0.2775000000000217 0 3394 0.2149999999995092 -0.2775000000000217 0 3395 0.2149999999995092 -0.2850000000000109 0 3396 0.2199999999995209 -0.2775000000000217 0 3397 0.2249999999995352 -0.2775000000000217 0 3398 0.2249999999995352 -0.2850000000000109 0 3399 0.2299999999995495 -0.2775000000000217 0 3400 0.2349999999995633 -0.2775000000000217 0 3401 0.2349999999995634 -0.2850000000000109 0 3402 0.2399999999995772 -0.2775000000000217 0 3403 0.2449999999995894 -0.2775000000000217 0 3404 0.2449999999995894 -0.2850000000000108 0 3405 0.2499999999996015 -0.2775000000000217 0 3406 0.2549999999996181 -0.2775000000000217 0 3407 0.2549999999996181 -0.2850000000000108 0 3408 0.2599999999996347 -0.2775000000000217 0 3409 0.2649999999996452 -0.2775000000000217 0 3410 0.2649999999996452 -0.2850000000000109 0 3411 0.2699999999996558 -0.2775000000000217 0 3412 0.2749999999996675 -0.2775000000000217 0 3413 0.2749999999996675 -0.2850000000000109 0 3414 0.2799999999996792 -0.2775000000000217 0 3415 0.2849999999996973 -0.2775000000000217 0 3416 0.2849999999996973 -0.2850000000000109 0 3417 0.2899999999997154 -0.2775000000000217 0 3418 0.2949999999997249 -0.2775000000000217 0 3419 0.2949999999997249 -0.2850000000000108 0 3420 0.2999999999997344 -0.2775000000000217 0 3421 0.3049999999997439 -0.2775000000000217 0 3422 0.3049999999997439 -0.2850000000000108 0 3423 0.3099999999997534 -0.2775000000000217 0 3424 0.3149999999997715 -0.2775000000000217 0 3425 0.3149999999997716 -0.2850000000000109 0 3426 0.3199999999997897 -0.2775000000000217 0 3427 0.3249999999998013 -0.2775000000000217 0 3428 0.3249999999998013 -0.2850000000000109 0 3429 0.329999999999813 -0.2775000000000217 0 3430 0.3349999999998236 -0.2775000000000217 0 3431 0.3349999999998236 -0.2850000000000109 0 3432 0.3399999999998342 -0.2775000000000217 0 3433 0.3449999999998534 -0.2775000000000217 0 3434 0.3449999999998534 -0.2850000000000109 0 3435 0.3499999999998726 -0.2775000000000217 0 3436 0.3549999999998821 -0.2775000000000217 0 3437 0.3549999999998821 -0.2850000000000109 0 3438 0.3599999999998915 -0.2775000000000218 0 3439 0.3649999999999043 -0.2775000000000218 0 3440 0.3649999999999043 -0.2850000000000109 0 3441 0.3699999999999171 -0.2775000000000217 0 3442 0.3749999999999342 -0.2775000000000217 0 3443 0.3749999999999342 -0.2850000000000108 0 3444 0.3799999999999513 -0.2775000000000217 0 3445 0.3849999999999607 -0.2775000000000217 0 3446 0.3849999999999607 -0.2850000000000109 0 3447 0.3899999999999702 -0.2775000000000217 0 3448 0.394999999999985 -0.2775000000000217 0 3449 0.394999999999985 -0.2850000000000109 0 3450 0.004999999999990962 -0.2925000000000054 0 3451 0.009999999999981926 -0.2925000000000054 0 3452 0.01499999999997157 -0.2925000000000054 0 3453 0.01999999999996121 -0.2925000000000054 0 3454 0.02499999999995261 -0.2925000000000054 0 3455 0.02999999999994401 -0.2925000000000054 0 3456 0.03499999999993698 -0.2925000000000054 0 3457 0.03999999999992994 -0.2925000000000054 0 3458 0.04499999999992209 -0.2925000000000054 0 3459 0.04999999999991424 -0.2925000000000054 0 3460 0.05499999999990056 -0.2925000000000054 0 3461 0.05999999999988688 -0.2925000000000054 0 3462 0.06499999999987254 -0.2925000000000054 0 3463 0.06999999999985819 -0.2925000000000054 0 3464 0.07499999999984112 -0.2925000000000054 0 3465 0.07999999999982406 -0.2925000000000054 0 3466 0.0849999999998097 -0.2925000000000054 0 3467 0.08999999999979536 -0.2925000000000054 0 3468 0.09499999999978101 -0.2925000000000054 0 3469 0.09999999999976666 -0.2925000000000054 0 3470 0.1049999999997523 -0.2925000000000054 0 3471 0.109999999999738 -0.2925000000000054 0 3472 0.1149999999997236 -0.2925000000000054 0 3473 0.1199999999997092 -0.2925000000000054 0 3474 0.1249999999996949 -0.2925000000000054 0 3475 0.1299999999996806 -0.2925000000000054 0 3476 0.1349999999996662 -0.2925000000000054 0 3477 0.1399999999996519 -0.2925000000000054 0 3478 0.1449999999996348 -0.2925000000000054 0 3479 0.1499999999996177 -0.2925000000000054 0 3480 0.1549999999996006 -0.2925000000000054 0 3481 0.1599999999995836 -0.2925000000000054 0 3482 0.1649999999995692 -0.2925000000000054 0 3483 0.1699999999995549 -0.2925000000000054 0 3484 0.1749999999995406 -0.2925000000000054 0 3485 0.1799999999995262 -0.2925000000000054 0 3486 0.1849999999995118 -0.2925000000000054 0 3487 0.1899999999994975 -0.2925000000000054 0 3488 0.1949999999994859 -0.2925000000000054 0 3489 0.1999999999994742 -0.2925000000000054 0 3490 0.2049999999994859 -0.2925000000000054 0 3491 0.2099999999994975 -0.2925000000000054 0 3492 0.2149999999995092 -0.2925000000000054 0 3493 0.2199999999995209 -0.2925000000000054 0 3494 0.2249999999995352 -0.2925000000000054 0 3495 0.2299999999995495 -0.2925000000000054 0 3496 0.2349999999995634 -0.2925000000000054 0 3497 0.2399999999995772 -0.2925000000000054 0 3498 0.2449999999995894 -0.2925000000000054 0 3499 0.2499999999996016 -0.2925000000000054 0 3500 0.2549999999996181 -0.2925000000000054 0 3501 0.2599999999996346 -0.2925000000000054 0 3502 0.2649999999996452 -0.2925000000000054 0 3503 0.2699999999996558 -0.2925000000000054 0 3504 0.2749999999996675 -0.2925000000000054 0 3505 0.2799999999996791 -0.2925000000000054 0 3506 0.2849999999996973 -0.2925000000000054 0 3507 0.2899999999997154 -0.2925000000000054 0 3508 0.2949999999997249 -0.2925000000000054 0 3509 0.2999999999997344 -0.2925000000000054 0 3510 0.3049999999997439 -0.2925000000000054 0 3511 0.3099999999997534 -0.2925000000000054 0 3512 0.3149999999997716 -0.2925000000000054 0 3513 0.3199999999997897 -0.2925000000000054 0 3514 0.3249999999998013 -0.2925000000000054 0 3515 0.329999999999813 -0.2925000000000054 0 3516 0.3349999999998236 -0.2925000000000054 0 3517 0.3399999999998342 -0.2925000000000054 0 3518 0.3449999999998534 -0.2925000000000054 0 3519 0.3499999999998726 -0.2925000000000054 0 3520 0.3549999999998821 -0.2925000000000055 0 3521 0.3599999999998915 -0.2925000000000055 0 3522 0.3649999999999043 -0.2925000000000054 0 3523 0.3699999999999171 -0.2925000000000054 0 3524 0.3749999999999342 -0.2925000000000054 0 3525 0.3799999999999513 -0.2925000000000054 0 3526 0.3849999999999607 -0.2925000000000054 0 3527 0.3899999999999702 -0.2925000000000054 0 3528 0.3949999999999851 -0.2925000000000054 0 3529 0.4159009150359435 -0.01500000000003687 0 3530 0.4325968757860913 -0.01500000000003687 0 3531 0.4501276345236666 -0.01500000000003687 0 3532 0.4685349312246211 -0.01500000000003687 0 3533 0.4878625928296997 -0.01500000000003687 0 3534 0.5081566374754983 -0.01500000000003687 0 3535 0.5294653843060498 -0.01500000000003687 0 3536 0.5518395685067007 -0.01500000000003687 0 3537 0.5753324619453913 -0.01500000000003687 0 3538 0.4159009150359435 -0.03000000000008241 0 3539 0.4325968757860914 -0.03000000000008241 0 3540 0.4501276345236666 -0.03000000000008241 0 3541 0.4685349312246212 -0.03000000000008241 0 3542 0.4878625928296997 -0.03000000000008241 0 3543 0.5081566374754983 -0.03000000000008241 0 3544 0.5294653843060497 -0.03000000000008241 0 3545 0.5518395685067006 -0.03000000000008241 0 3546 0.5753324619453918 -0.0300000000000824 0 3547 0.4159009150359435 -0.0450000000001214 0 3548 0.4325968757860914 -0.04500000000012138 0 3549 0.4501276345236667 -0.0450000000001214 0 3550 0.468534931224621 -0.04500000000012139 0 3551 0.4878625928296997 -0.04500000000012139 0 3552 0.5081566374754983 -0.04500000000012138 0 3553 0.5294653843060497 -0.04500000000012139 0 3554 0.5518395685067006 -0.04500000000012139 0 3555 0.5753324619453916 -0.04500000000012138 0 3556 0.4159009150359436 -0.06000000000015071 0 3557 0.4325968757860914 -0.0600000000001507 0 3558 0.4501276345236667 -0.06000000000015071 0 3559 0.4685349312246211 -0.06000000000015071 0 3560 0.4878625928296997 -0.06000000000015071 0 3561 0.5081566374754984 -0.0600000000001507 0 3562 0.5294653843060497 -0.0600000000001507 0 3563 0.5518395685067007 -0.06000000000015071 0 3564 0.5753324619453916 -0.0600000000001507 0 3565 0.4159009150359435 -0.07500000000018975 0 3566 0.4325968757860914 -0.07500000000018978 0 3567 0.4501276345236666 -0.07500000000018976 0 3568 0.4685349312246211 -0.07500000000018975 0 3569 0.4878625928296997 -0.07500000000018975 0 3570 0.5081566374754983 -0.07500000000018975 0 3571 0.5294653843060497 -0.07500000000018975 0 3572 0.5518395685067007 -0.07500000000018975 0 3573 0.5753324619453916 -0.07500000000018976 0 3574 0.4159009150359435 -0.09000000000022879 0 3575 0.4325968757860914 -0.09000000000022877 0 3576 0.4501276345236666 -0.09000000000022879 0 3577 0.4685349312246211 -0.09000000000022877 0 3578 0.4878625928296997 -0.09000000000022877 0 3579 0.5081566374754983 -0.09000000000022879 0 3580 0.5294653843060497 -0.09000000000022881 0 3581 0.5518395685067007 -0.09000000000022879 0 3582 0.5753324619453917 -0.09000000000022879 0 3583 0.4159009150359436 -0.1050000000002732 0 3584 0.4325968757860915 -0.1050000000002732 0 3585 0.4501276345236666 -0.1050000000002733 0 3586 0.4685349312246211 -0.1050000000002732 0 3587 0.4878625928296997 -0.1050000000002732 0 3588 0.5081566374754983 -0.1050000000002732 0 3589 0.5294653843060497 -0.1050000000002732 0 3590 0.5518395685067006 -0.1050000000002732 0 3591 0.5753324619453916 -0.1050000000002732 0 3592 0.4159009150359435 -0.1200000000003112 0 3593 0.4325968757860914 -0.1200000000003112 0 3594 0.4501276345236666 -0.1200000000003112 0 3595 0.4685349312246211 -0.1200000000003112 0 3596 0.4878625928296997 -0.1200000000003112 0 3597 0.5081566374754983 -0.1200000000003112 0 3598 0.5294653843060497 -0.1200000000003112 0 3599 0.5518395685067007 -0.1200000000003112 0 3600 0.5753324619453916 -0.1200000000003112 0 3601 0.4159009150359435 -0.135000000000348 0 3602 0.4325968757860914 -0.1350000000003481 0 3603 0.4501276345236666 -0.1350000000003481 0 3604 0.4685349312246213 -0.1350000000003481 0 3605 0.4878625928296997 -0.1350000000003481 0 3606 0.5081566374754983 -0.1350000000003481 0 3607 0.5294653843060497 -0.1350000000003481 0 3608 0.5518395685067006 -0.1350000000003481 0 3609 0.5753324619453917 -0.1350000000003481 0 3610 0.4159009150359435 -0.150000000000385 0 3611 0.4325968757860914 -0.150000000000385 0 3612 0.4501276345236666 -0.150000000000385 0 3613 0.4685349312246211 -0.150000000000385 0 3614 0.4878625928296997 -0.150000000000385 0 3615 0.5081566374754983 -0.150000000000385 0 3616 0.5294653843060497 -0.150000000000385 0 3617 0.5518395685067007 -0.150000000000385 0 3618 0.5753324619453916 -0.150000000000385 0 3619 0.4159009150359435 -0.165000000000347 0 3620 0.4325968757860914 -0.165000000000347 0 3621 0.4501276345236666 -0.165000000000347 0 3622 0.4685349312246211 -0.165000000000347 0 3623 0.4878625928296997 -0.165000000000347 0 3624 0.5081566374754983 -0.165000000000347 0 3625 0.5294653843060497 -0.165000000000347 0 3626 0.5518395685067007 -0.165000000000347 0 3627 0.5753324619453916 -0.165000000000347 0 3628 0.4159009150359435 -0.1800000000003037 0 3629 0.4325968757860914 -0.1800000000003036 0 3630 0.4501276345236666 -0.1800000000003036 0 3631 0.4685349312246211 -0.1800000000003036 0 3632 0.4878625928296997 -0.1800000000003036 0 3633 0.5081566374754983 -0.1800000000003036 0 3634 0.5294653843060497 -0.1800000000003036 0 3635 0.5518395685067007 -0.1800000000003036 0 3636 0.5753324619453916 -0.1800000000003036 0 3637 0.4159009150359436 -0.1950000000002711 0 3638 0.4325968757860914 -0.1950000000002712 0 3639 0.4501276345236666 -0.1950000000002711 0 3640 0.468534931224621 -0.1950000000002711 0 3641 0.4878625928296997 -0.1950000000002711 0 3642 0.5081566374754983 -0.1950000000002711 0 3643 0.5294653843060497 -0.1950000000002712 0 3644 0.5518395685067007 -0.1950000000002711 0 3645 0.5753324619453916 -0.1950000000002711 0 3646 0.4159009150359435 -0.2100000000002277 0 3647 0.4325968757860914 -0.2100000000002277 0 3648 0.4501276345236666 -0.2100000000002277 0 3649 0.4685349312246213 -0.2100000000002277 0 3650 0.4878625928296997 -0.2100000000002277 0 3651 0.5081566374754983 -0.2100000000002277 0 3652 0.5294653843060497 -0.2100000000002277 0 3653 0.5518395685067007 -0.2100000000002277 0 3654 0.5753324619453915 -0.2100000000002277 0 3655 0.4159009150359435 -0.2250000000001843 0 3656 0.4325968757860914 -0.2250000000001843 0 3657 0.4501276345236666 -0.2250000000001843 0 3658 0.4685349312246211 -0.2250000000001843 0 3659 0.4878625928296997 -0.2250000000001843 0 3660 0.5081566374754983 -0.2250000000001843 0 3661 0.5294653843060497 -0.2250000000001843 0 3662 0.5518395685067007 -0.2250000000001843 0 3663 0.5753324619453916 -0.2250000000001843 0 3664 0.4159009150359434 -0.240000000000141 0 3665 0.4325968757860914 -0.240000000000141 0 3666 0.4501276345236667 -0.2400000000001409 0 3667 0.4685349312246211 -0.2400000000001409 0 3668 0.4878625928296997 -0.240000000000141 0 3669 0.5081566374754983 -0.2400000000001409 0 3670 0.5294653843060497 -0.2400000000001409 0 3671 0.5518395685067007 -0.240000000000141 0 3672 0.5753324619453916 -0.2400000000001409 0 3673 0.4159009150359435 -0.2550000000000781 0 3674 0.4325968757860914 -0.2550000000000781 0 3675 0.4501276345236666 -0.2550000000000781 0 3676 0.4685349312246211 -0.2550000000000781 0 3677 0.4878625928296997 -0.2550000000000781 0 3678 0.5081566374754983 -0.2550000000000781 0 3679 0.5294653843060497 -0.2550000000000781 0 3680 0.5518395685067007 -0.2550000000000781 0 3681 0.5753324619453916 -0.2550000000000781 0 3682 0.4159009150359436 -0.2700000000000326 0 3683 0.4325968757860914 -0.2700000000000326 0 3684 0.4501276345236666 -0.2700000000000326 0 3685 0.4685349312246212 -0.2700000000000326 0 3686 0.4878625928296997 -0.2700000000000326 0 3687 0.5081566374754983 -0.2700000000000326 0 3688 0.5294653843060497 -0.2700000000000326 0 3689 0.5518395685067007 -0.2700000000000326 0 3690 0.5753324619453917 -0.2700000000000326 0 3691 0.4159009150359435 -0.2850000000000109 0 3692 0.4325968757860914 -0.2850000000000108 0 3693 0.4501276345236666 -0.2850000000000109 0 3694 0.4685349312246211 -0.2850000000000109 0 3695 0.4878625928296997 -0.2850000000000108 0 3696 0.5081566374754983 -0.2850000000000108 0 3697 0.5294653843060497 -0.2850000000000108 0 3698 0.5518395685067007 -0.2850000000000109 0 3699 0.5753324619453916 -0.2850000000000108 0 3700 0.4079504575179718 -0.007500000000018436 0 3701 0.4079504575179718 -0.01500000000003687 0 3702 0.4159009150359435 -0.007500000000018436 0 3703 0.4242488954110175 -0.007500000000018436 0 3704 0.4242488954110174 -0.01500000000003687 0 3705 0.4325968757860914 -0.007500000000018436 0 3706 0.441362255154879 -0.007500000000018436 0 3707 0.4413622551548789 -0.01500000000003687 0 3708 0.4501276345236666 -0.007500000000018436 0 3709 0.4593312828741438 -0.007500000000018436 0 3710 0.4593312828741438 -0.01500000000003687 0 3711 0.4685349312246211 -0.007500000000018436 0 3712 0.4781987620271604 -0.007500000000018436 0 3713 0.4781987620271604 -0.01500000000003687 0 3714 0.4878625928296997 -0.007500000000018436 0 3715 0.498009615152599 -0.007500000000018436 0 3716 0.498009615152599 -0.01500000000003687 0 3717 0.5081566374754983 -0.007500000000018436 0 3718 0.518811010890774 -0.007500000000018436 0 3719 0.5188110108907741 -0.01500000000003687 0 3720 0.5294653843060497 -0.007500000000018436 0 3721 0.5406524764063753 -0.007500000000018436 0 3722 0.5406524764063753 -0.01500000000003687 0 3723 0.5518395685067007 -0.007500000000018436 0 3724 0.5635860152260461 -0.007500000000018436 0 3725 0.563586015226046 -0.01500000000003687 0 3726 0.5753324619453915 -0.007500000000018436 0 3727 0.5876662309726957 -0.007500000000018436 0 3728 0.5876662309726957 -0.01500000000003687 0 3729 0.4079504575179718 -0.02250000000005964 0 3730 0.4079504575179718 -0.03000000000008241 0 3731 0.4159009150359435 -0.02250000000005964 0 3732 0.4242488954110174 -0.02250000000005964 0 3733 0.4242488954110174 -0.03000000000008241 0 3734 0.4325968757860914 -0.02250000000005964 0 3735 0.441362255154879 -0.02250000000005964 0 3736 0.441362255154879 -0.03000000000008241 0 3737 0.4501276345236666 -0.02250000000005964 0 3738 0.4593312828741438 -0.02250000000005964 0 3739 0.4593312828741439 -0.03000000000008241 0 3740 0.4685349312246211 -0.02250000000005964 0 3741 0.4781987620271604 -0.02250000000005964 0 3742 0.4781987620271604 -0.03000000000008241 0 3743 0.4878625928296997 -0.02250000000005964 0 3744 0.498009615152599 -0.02250000000005964 0 3745 0.498009615152599 -0.03000000000008241 0 3746 0.5081566374754983 -0.02250000000005964 0 3747 0.5188110108907741 -0.02250000000005964 0 3748 0.518811010890774 -0.03000000000008241 0 3749 0.5294653843060497 -0.02250000000005964 0 3750 0.5406524764063752 -0.02250000000005964 0 3751 0.5406524764063752 -0.03000000000008241 0 3752 0.5518395685067006 -0.02250000000005964 0 3753 0.5635860152260459 -0.02250000000005964 0 3754 0.5635860152260461 -0.03000000000008241 0 3755 0.5753324619453916 -0.02250000000005964 0 3756 0.5876662309726959 -0.02250000000005964 0 3757 0.5876662309726959 -0.03000000000008241 0 3758 0.4079504575179718 -0.0375000000001019 0 3759 0.4079504575179718 -0.04500000000012139 0 3760 0.4159009150359435 -0.0375000000001019 0 3761 0.4242488954110175 -0.0375000000001019 0 3762 0.4242488954110175 -0.04500000000012139 0 3763 0.4325968757860914 -0.0375000000001019 0 3764 0.441362255154879 -0.0375000000001019 0 3765 0.441362255154879 -0.04500000000012139 0 3766 0.4501276345236667 -0.0375000000001019 0 3767 0.4593312828741439 -0.0375000000001019 0 3768 0.4593312828741438 -0.04500000000012139 0 3769 0.4685349312246211 -0.0375000000001019 0 3770 0.4781987620271603 -0.0375000000001019 0 3771 0.4781987620271603 -0.04500000000012139 0 3772 0.4878625928296997 -0.0375000000001019 0 3773 0.498009615152599 -0.0375000000001019 0 3774 0.498009615152599 -0.04500000000012139 0 3775 0.5081566374754983 -0.0375000000001019 0 3776 0.518811010890774 -0.0375000000001019 0 3777 0.518811010890774 -0.04500000000012139 0 3778 0.5294653843060497 -0.0375000000001019 0 3779 0.5406524764063752 -0.0375000000001019 0 3780 0.5406524764063752 -0.04500000000012139 0 3781 0.5518395685067006 -0.0375000000001019 0 3782 0.5635860152260461 -0.0375000000001019 0 3783 0.5635860152260461 -0.04500000000012139 0 3784 0.5753324619453917 -0.03750000000010189 0 3785 0.5876662309726958 -0.0375000000001019 0 3786 0.5876662309726958 -0.04500000000012139 0 3787 0.4079504575179718 -0.05250000000013606 0 3788 0.4079504575179718 -0.06000000000015071 0 3789 0.4159009150359435 -0.05250000000013606 0 3790 0.4242488954110175 -0.05250000000013605 0 3791 0.4242488954110175 -0.06000000000015071 0 3792 0.4325968757860914 -0.05250000000013604 0 3793 0.441362255154879 -0.05250000000013605 0 3794 0.441362255154879 -0.06000000000015071 0 3795 0.4501276345236667 -0.05250000000013606 0 3796 0.4593312828741438 -0.05250000000013605 0 3797 0.4593312828741439 -0.06000000000015071 0 3798 0.468534931224621 -0.05250000000013605 0 3799 0.4781987620271604 -0.05250000000013605 0 3800 0.4781987620271604 -0.06000000000015071 0 3801 0.4878625928296997 -0.05250000000013605 0 3802 0.498009615152599 -0.05250000000013605 0 3803 0.4980096151525991 -0.06000000000015071 0 3804 0.5081566374754984 -0.05250000000013604 0 3805 0.5188110108907741 -0.05250000000013605 0 3806 0.5188110108907741 -0.0600000000001507 0 3807 0.5294653843060497 -0.05250000000013605 0 3808 0.5406524764063752 -0.05250000000013605 0 3809 0.5406524764063752 -0.06000000000015071 0 3810 0.5518395685067006 -0.05250000000013605 0 3811 0.5635860152260461 -0.05250000000013605 0 3812 0.5635860152260461 -0.06000000000015071 0 3813 0.5753324619453916 -0.05250000000013604 0 3814 0.5876662309726958 -0.05250000000013605 0 3815 0.5876662309726958 -0.06000000000015071 0 3816 0.4079504575179718 -0.06750000000017023 0 3817 0.4079504575179718 -0.07500000000018975 0 3818 0.4159009150359435 -0.06750000000017023 0 3819 0.4242488954110175 -0.06750000000017023 0 3820 0.4242488954110175 -0.07500000000018976 0 3821 0.4325968757860914 -0.06750000000017024 0 3822 0.441362255154879 -0.06750000000017024 0 3823 0.441362255154879 -0.07500000000018978 0 3824 0.4501276345236666 -0.06750000000017023 0 3825 0.4593312828741438 -0.06750000000017023 0 3826 0.4593312828741438 -0.07500000000018975 0 3827 0.4685349312246211 -0.06750000000017023 0 3828 0.4781987620271604 -0.06750000000017023 0 3829 0.4781987620271604 -0.07500000000018975 0 3830 0.4878625928296997 -0.06750000000017023 0 3831 0.4980096151525991 -0.06750000000017023 0 3832 0.498009615152599 -0.07500000000018975 0 3833 0.5081566374754984 -0.06750000000017023 0 3834 0.518811010890774 -0.06750000000017023 0 3835 0.518811010890774 -0.07500000000018975 0 3836 0.5294653843060497 -0.06750000000017023 0 3837 0.5406524764063752 -0.06750000000017023 0 3838 0.5406524764063752 -0.07500000000018975 0 3839 0.5518395685067007 -0.06750000000017023 0 3840 0.5635860152260461 -0.06750000000017023 0 3841 0.5635860152260461 -0.07500000000018975 0 3842 0.5753324619453916 -0.06750000000017023 0 3843 0.5876662309726958 -0.06750000000017023 0 3844 0.5876662309726958 -0.07500000000018975 0 3845 0.4079504575179718 -0.08250000000020927 0 3846 0.4079504575179718 -0.09000000000022879 0 3847 0.4159009150359435 -0.08250000000020927 0 3848 0.4242488954110174 -0.08250000000020928 0 3849 0.4242488954110174 -0.09000000000022879 0 3850 0.4325968757860914 -0.08250000000020927 0 3851 0.441362255154879 -0.08250000000020927 0 3852 0.441362255154879 -0.09000000000022879 0 3853 0.4501276345236666 -0.08250000000020927 0 3854 0.4593312828741438 -0.08250000000020927 0 3855 0.4593312828741439 -0.09000000000022879 0 3856 0.4685349312246211 -0.08250000000020927 0 3857 0.4781987620271604 -0.08250000000020927 0 3858 0.4781987620271604 -0.09000000000022877 0 3859 0.4878625928296997 -0.08250000000020927 0 3860 0.498009615152599 -0.08250000000020927 0 3861 0.498009615152599 -0.09000000000022879 0 3862 0.5081566374754983 -0.08250000000020927 0 3863 0.518811010890774 -0.08250000000020927 0 3864 0.518811010890774 -0.0900000000002288 0 3865 0.5294653843060497 -0.08250000000020928 0 3866 0.5406524764063752 -0.08250000000020928 0 3867 0.5406524764063752 -0.0900000000002288 0 3868 0.5518395685067007 -0.08250000000020927 0 3869 0.5635860152260461 -0.08250000000020927 0 3870 0.5635860152260461 -0.09000000000022879 0 3871 0.5753324619453917 -0.08250000000020927 0 3872 0.5876662309726959 -0.08250000000020927 0 3873 0.5876662309726959 -0.09000000000022879 0 3874 0.4079504575179718 -0.09750000000025101 0 3875 0.4079504575179718 -0.1050000000002732 0 3876 0.4159009150359435 -0.09750000000025101 0 3877 0.4242488954110175 -0.097500000000251 0 3878 0.4242488954110175 -0.1050000000002732 0 3879 0.4325968757860914 -0.097500000000251 0 3880 0.441362255154879 -0.09750000000025101 0 3881 0.441362255154879 -0.1050000000002732 0 3882 0.4501276345236666 -0.09750000000025103 0 3883 0.4593312828741439 -0.09750000000025101 0 3884 0.4593312828741438 -0.1050000000002732 0 3885 0.4685349312246211 -0.097500000000251 0 3886 0.4781987620271604 -0.097500000000251 0 3887 0.4781987620271604 -0.1050000000002732 0 3888 0.4878625928296997 -0.097500000000251 0 3889 0.498009615152599 -0.09750000000025101 0 3890 0.498009615152599 -0.1050000000002732 0 3891 0.5081566374754983 -0.09750000000025101 0 3892 0.518811010890774 -0.09750000000025103 0 3893 0.518811010890774 -0.1050000000002732 0 3894 0.5294653843060497 -0.09750000000025103 0 3895 0.5406524764063752 -0.097500000000251 0 3896 0.5406524764063752 -0.1050000000002732 0 3897 0.5518395685067006 -0.09750000000025101 0 3898 0.5635860152260461 -0.09750000000025101 0 3899 0.5635860152260461 -0.1050000000002732 0 3900 0.5753324619453917 -0.09750000000025101 0 3901 0.5876662309726958 -0.09750000000025101 0 3902 0.5876662309726958 -0.1050000000002732 0 3903 0.4079504575179718 -0.1125000000002922 0 3904 0.4079504575179718 -0.1200000000003112 0 3905 0.4159009150359435 -0.1125000000002922 0 3906 0.4242488954110175 -0.1125000000002922 0 3907 0.4242488954110174 -0.1200000000003112 0 3908 0.4325968757860914 -0.1125000000002922 0 3909 0.441362255154879 -0.1125000000002922 0 3910 0.441362255154879 -0.1200000000003112 0 3911 0.4501276345236666 -0.1125000000002922 0 3912 0.4593312828741438 -0.1125000000002922 0 3913 0.4593312828741438 -0.1200000000003112 0 3914 0.4685349312246211 -0.1125000000002922 0 3915 0.4781987620271604 -0.1125000000002922 0 3916 0.4781987620271604 -0.1200000000003112 0 3917 0.4878625928296997 -0.1125000000002922 0 3918 0.498009615152599 -0.1125000000002922 0 3919 0.498009615152599 -0.1200000000003112 0 3920 0.5081566374754983 -0.1125000000002922 0 3921 0.518811010890774 -0.1125000000002922 0 3922 0.518811010890774 -0.1200000000003112 0 3923 0.5294653843060497 -0.1125000000002922 0 3924 0.5406524764063752 -0.1125000000002922 0 3925 0.5406524764063752 -0.1200000000003112 0 3926 0.5518395685067006 -0.1125000000002922 0 3927 0.5635860152260461 -0.1125000000002922 0 3928 0.5635860152260461 -0.1200000000003112 0 3929 0.5753324619453916 -0.1125000000002922 0 3930 0.5876662309726958 -0.1125000000002922 0 3931 0.5876662309726958 -0.1200000000003112 0 3932 0.4079504575179718 -0.1275000000003296 0 3933 0.4079504575179718 -0.135000000000348 0 3934 0.4159009150359435 -0.1275000000003296 0 3935 0.4242488954110174 -0.1275000000003296 0 3936 0.4242488954110174 -0.135000000000348 0 3937 0.4325968757860914 -0.1275000000003296 0 3938 0.441362255154879 -0.1275000000003296 0 3939 0.441362255154879 -0.1350000000003481 0 3940 0.4501276345236666 -0.1275000000003296 0 3941 0.4593312828741438 -0.1275000000003296 0 3942 0.4593312828741439 -0.1350000000003481 0 3943 0.4685349312246212 -0.1275000000003296 0 3944 0.4781987620271605 -0.1275000000003296 0 3945 0.4781987620271605 -0.1350000000003481 0 3946 0.4878625928296997 -0.1275000000003296 0 3947 0.498009615152599 -0.1275000000003296 0 3948 0.498009615152599 -0.1350000000003481 0 3949 0.5081566374754983 -0.1275000000003296 0 3950 0.518811010890774 -0.1275000000003296 0 3951 0.518811010890774 -0.1350000000003481 0 3952 0.5294653843060497 -0.1275000000003296 0 3953 0.5406524764063752 -0.1275000000003296 0 3954 0.5406524764063752 -0.1350000000003481 0 3955 0.5518395685067006 -0.1275000000003296 0 3956 0.5635860152260461 -0.1275000000003296 0 3957 0.5635860152260461 -0.1350000000003481 0 3958 0.5753324619453917 -0.1275000000003296 0 3959 0.5876662309726959 -0.1275000000003296 0 3960 0.5876662309726959 -0.1350000000003481 0 3961 0.4079504575179718 -0.1425000000003665 0 3962 0.4079504575179718 -0.150000000000385 0 3963 0.4159009150359435 -0.1425000000003665 0 3964 0.4242488954110174 -0.1425000000003665 0 3965 0.4242488954110174 -0.150000000000385 0 3966 0.4325968757860914 -0.1425000000003665 0 3967 0.4413622551548789 -0.1425000000003665 0 3968 0.4413622551548789 -0.150000000000385 0 3969 0.4501276345236666 -0.1425000000003665 0 3970 0.4593312828741439 -0.1425000000003665 0 3971 0.4593312828741438 -0.150000000000385 0 3972 0.4685349312246212 -0.1425000000003665 0 3973 0.4781987620271604 -0.1425000000003665 0 3974 0.4781987620271604 -0.150000000000385 0 3975 0.4878625928296997 -0.1425000000003665 0 3976 0.498009615152599 -0.1425000000003665 0 3977 0.498009615152599 -0.150000000000385 0 3978 0.5081566374754983 -0.1425000000003665 0 3979 0.518811010890774 -0.1425000000003665 0 3980 0.518811010890774 -0.150000000000385 0 3981 0.5294653843060497 -0.1425000000003665 0 3982 0.5406524764063752 -0.1425000000003665 0 3983 0.5406524764063752 -0.150000000000385 0 3984 0.5518395685067006 -0.1425000000003665 0 3985 0.5635860152260461 -0.1425000000003665 0 3986 0.5635860152260461 -0.150000000000385 0 3987 0.5753324619453917 -0.1425000000003665 0 3988 0.5876662309726958 -0.1425000000003665 0 3989 0.5876662309726958 -0.150000000000385 0 3990 0.4079504575179718 -0.157500000000366 0 3991 0.4079504575179718 -0.165000000000347 0 3992 0.4159009150359435 -0.157500000000366 0 3993 0.4242488954110174 -0.157500000000366 0 3994 0.4242488954110174 -0.165000000000347 0 3995 0.4325968757860914 -0.157500000000366 0 3996 0.441362255154879 -0.157500000000366 0 3997 0.441362255154879 -0.165000000000347 0 3998 0.4501276345236666 -0.157500000000366 0 3999 0.4593312828741438 -0.157500000000366 0 4000 0.4593312828741438 -0.165000000000347 0 4001 0.4685349312246211 -0.157500000000366 0 4002 0.4781987620271604 -0.157500000000366 0 4003 0.4781987620271604 -0.165000000000347 0 4004 0.4878625928296997 -0.157500000000366 0 4005 0.498009615152599 -0.157500000000366 0 4006 0.498009615152599 -0.165000000000347 0 4007 0.5081566374754983 -0.157500000000366 0 4008 0.518811010890774 -0.157500000000366 0 4009 0.518811010890774 -0.165000000000347 0 4010 0.5294653843060497 -0.157500000000366 0 4011 0.5406524764063752 -0.157500000000366 0 4012 0.5406524764063752 -0.165000000000347 0 4013 0.5518395685067007 -0.157500000000366 0 4014 0.5635860152260461 -0.157500000000366 0 4015 0.5635860152260461 -0.165000000000347 0 4016 0.5753324619453916 -0.157500000000366 0 4017 0.5876662309726958 -0.157500000000366 0 4018 0.5876662309726958 -0.165000000000347 0 4019 0.4079504575179718 -0.1725000000003253 0 4020 0.4079504575179718 -0.1800000000003036 0 4021 0.4159009150359435 -0.1725000000003253 0 4022 0.4242488954110175 -0.1725000000003253 0 4023 0.4242488954110175 -0.1800000000003036 0 4024 0.4325968757860914 -0.1725000000003253 0 4025 0.441362255154879 -0.1725000000003253 0 4026 0.441362255154879 -0.1800000000003036 0 4027 0.4501276345236666 -0.1725000000003253 0 4028 0.4593312828741438 -0.1725000000003253 0 4029 0.4593312828741438 -0.1800000000003036 0 4030 0.4685349312246211 -0.1725000000003253 0 4031 0.4781987620271604 -0.1725000000003253 0 4032 0.4781987620271604 -0.1800000000003036 0 4033 0.4878625928296997 -0.1725000000003253 0 4034 0.498009615152599 -0.1725000000003253 0 4035 0.498009615152599 -0.1800000000003036 0 4036 0.5081566374754983 -0.1725000000003253 0 4037 0.518811010890774 -0.1725000000003253 0 4038 0.518811010890774 -0.1800000000003036 0 4039 0.5294653843060497 -0.1725000000003253 0 4040 0.5406524764063752 -0.1725000000003253 0 4041 0.5406524764063752 -0.1800000000003036 0 4042 0.5518395685067007 -0.1725000000003253 0 4043 0.5635860152260461 -0.1725000000003253 0 4044 0.5635860152260461 -0.1800000000003036 0 4045 0.5753324619453916 -0.1725000000003253 0 4046 0.5876662309726958 -0.1725000000003253 0 4047 0.5876662309726958 -0.1800000000003036 0 4048 0.4079504575179718 -0.1875000000002874 0 4049 0.4079504575179718 -0.1950000000002711 0 4050 0.4159009150359435 -0.1875000000002874 0 4051 0.4242488954110175 -0.1875000000002874 0 4052 0.4242488954110175 -0.1950000000002711 0 4053 0.4325968757860914 -0.1875000000002874 0 4054 0.441362255154879 -0.1875000000002874 0 4055 0.441362255154879 -0.1950000000002711 0 4056 0.4501276345236666 -0.1875000000002874 0 4057 0.4593312828741438 -0.1875000000002874 0 4058 0.4593312828741438 -0.1950000000002711 0 4059 0.468534931224621 -0.1875000000002874 0 4060 0.4781987620271604 -0.1875000000002874 0 4061 0.4781987620271603 -0.1950000000002711 0 4062 0.4878625928296997 -0.1875000000002874 0 4063 0.498009615152599 -0.1875000000002874 0 4064 0.498009615152599 -0.1950000000002711 0 4065 0.5081566374754983 -0.1875000000002874 0 4066 0.518811010890774 -0.1875000000002874 0 4067 0.518811010890774 -0.1950000000002711 0 4068 0.5294653843060497 -0.1875000000002874 0 4069 0.5406524764063752 -0.1875000000002874 0 4070 0.5406524764063752 -0.1950000000002711 0 4071 0.5518395685067007 -0.1875000000002874 0 4072 0.5635860152260461 -0.1875000000002874 0 4073 0.5635860152260461 -0.1950000000002711 0 4074 0.5753324619453916 -0.1875000000002874 0 4075 0.5876662309726958 -0.1875000000002874 0 4076 0.5876662309726958 -0.1950000000002711 0 4077 0.4079504575179718 -0.2025000000002494 0 4078 0.4079504575179718 -0.2100000000002277 0 4079 0.4159009150359435 -0.2025000000002494 0 4080 0.4242488954110175 -0.2025000000002494 0 4081 0.4242488954110175 -0.2100000000002277 0 4082 0.4325968757860914 -0.2025000000002494 0 4083 0.441362255154879 -0.2025000000002494 0 4084 0.441362255154879 -0.2100000000002277 0 4085 0.4501276345236666 -0.2025000000002494 0 4086 0.4593312828741438 -0.2025000000002494 0 4087 0.4593312828741439 -0.2100000000002277 0 4088 0.4685349312246211 -0.2025000000002494 0 4089 0.4781987620271605 -0.2025000000002494 0 4090 0.4781987620271605 -0.2100000000002277 0 4091 0.4878625928296997 -0.2025000000002494 0 4092 0.498009615152599 -0.2025000000002494 0 4093 0.498009615152599 -0.2100000000002277 0 4094 0.5081566374754983 -0.2025000000002494 0 4095 0.518811010890774 -0.2025000000002495 0 4096 0.518811010890774 -0.2100000000002277 0 4097 0.5294653843060497 -0.2025000000002494 0 4098 0.5406524764063752 -0.2025000000002494 0 4099 0.5406524764063752 -0.2100000000002277 0 4100 0.5518395685067007 -0.2025000000002494 0 4101 0.5635860152260461 -0.2025000000002494 0 4102 0.5635860152260461 -0.2100000000002277 0 4103 0.5753324619453915 -0.2025000000002494 0 4104 0.5876662309726957 -0.2025000000002494 0 4105 0.5876662309726957 -0.2100000000002277 0 4106 0.4079504575179718 -0.217500000000206 0 4107 0.4079504575179718 -0.2250000000001843 0 4108 0.4159009150359435 -0.217500000000206 0 4109 0.4242488954110174 -0.217500000000206 0 4110 0.4242488954110174 -0.2250000000001843 0 4111 0.4325968757860914 -0.217500000000206 0 4112 0.441362255154879 -0.217500000000206 0 4113 0.441362255154879 -0.2250000000001843 0 4114 0.4501276345236666 -0.217500000000206 0 4115 0.4593312828741439 -0.217500000000206 0 4116 0.4593312828741438 -0.2250000000001843 0 4117 0.4685349312246212 -0.217500000000206 0 4118 0.4781987620271604 -0.217500000000206 0 4119 0.4781987620271604 -0.2250000000001843 0 4120 0.4878625928296997 -0.217500000000206 0 4121 0.498009615152599 -0.217500000000206 0 4122 0.498009615152599 -0.2250000000001843 0 4123 0.5081566374754983 -0.217500000000206 0 4124 0.518811010890774 -0.217500000000206 0 4125 0.518811010890774 -0.2250000000001843 0 4126 0.5294653843060497 -0.217500000000206 0 4127 0.5406524764063752 -0.217500000000206 0 4128 0.5406524764063752 -0.2250000000001843 0 4129 0.5518395685067007 -0.217500000000206 0 4130 0.5635860152260461 -0.217500000000206 0 4131 0.5635860152260461 -0.2250000000001843 0 4132 0.5753324619453915 -0.217500000000206 0 4133 0.5876662309726958 -0.217500000000206 0 4134 0.5876662309726958 -0.2250000000001843 0 4135 0.4079504575179718 -0.2325000000001626 0 4136 0.4079504575179717 -0.240000000000141 0 4137 0.4159009150359434 -0.2325000000001626 0 4138 0.4242488954110174 -0.2325000000001626 0 4139 0.4242488954110174 -0.240000000000141 0 4140 0.4325968757860914 -0.2325000000001626 0 4141 0.4413622551548789 -0.2325000000001626 0 4142 0.441362255154879 -0.240000000000141 0 4143 0.4501276345236666 -0.2325000000001626 0 4144 0.4593312828741439 -0.2325000000001626 0 4145 0.4593312828741439 -0.2400000000001409 0 4146 0.4685349312246211 -0.2325000000001626 0 4147 0.4781987620271604 -0.2325000000001626 0 4148 0.4781987620271604 -0.240000000000141 0 4149 0.4878625928296997 -0.2325000000001626 0 4150 0.498009615152599 -0.2325000000001627 0 4151 0.498009615152599 -0.240000000000141 0 4152 0.5081566374754983 -0.2325000000001626 0 4153 0.518811010890774 -0.2325000000001626 0 4154 0.518811010890774 -0.2400000000001409 0 4155 0.5294653843060497 -0.2325000000001626 0 4156 0.5406524764063752 -0.2325000000001626 0 4157 0.5406524764063752 -0.2400000000001409 0 4158 0.5518395685067007 -0.2325000000001626 0 4159 0.5635860152260461 -0.2325000000001626 0 4160 0.5635860152260461 -0.2400000000001409 0 4161 0.5753324619453916 -0.2325000000001626 0 4162 0.5876662309726958 -0.2325000000001626 0 4163 0.5876662309726958 -0.2400000000001409 0 4164 0.4079504575179717 -0.2475000000001095 0 4165 0.4079504575179718 -0.2550000000000781 0 4166 0.4159009150359434 -0.2475000000001095 0 4167 0.4242488954110174 -0.2475000000001095 0 4168 0.4242488954110174 -0.2550000000000781 0 4169 0.4325968757860914 -0.2475000000001095 0 4170 0.441362255154879 -0.2475000000001095 0 4171 0.441362255154879 -0.2550000000000781 0 4172 0.4501276345236666 -0.2475000000001095 0 4173 0.4593312828741438 -0.2475000000001095 0 4174 0.4593312828741438 -0.2550000000000781 0 4175 0.4685349312246211 -0.2475000000001095 0 4176 0.4781987620271604 -0.2475000000001095 0 4177 0.4781987620271604 -0.2550000000000781 0 4178 0.4878625928296997 -0.2475000000001095 0 4179 0.498009615152599 -0.2475000000001095 0 4180 0.498009615152599 -0.2550000000000781 0 4181 0.5081566374754983 -0.2475000000001095 0 4182 0.518811010890774 -0.2475000000001095 0 4183 0.518811010890774 -0.2550000000000781 0 4184 0.5294653843060497 -0.2475000000001095 0 4185 0.5406524764063752 -0.2475000000001095 0 4186 0.5406524764063752 -0.2550000000000781 0 4187 0.5518395685067007 -0.2475000000001095 0 4188 0.5635860152260461 -0.2475000000001095 0 4189 0.5635860152260461 -0.2550000000000781 0 4190 0.5753324619453916 -0.2475000000001095 0 4191 0.5876662309726958 -0.2475000000001095 0 4192 0.5876662309726958 -0.2550000000000781 0 4193 0.4079504575179718 -0.2625000000000553 0 4194 0.4079504575179718 -0.2700000000000326 0 4195 0.4159009150359435 -0.2625000000000554 0 4196 0.4242488954110175 -0.2625000000000554 0 4197 0.4242488954110175 -0.2700000000000326 0 4198 0.4325968757860914 -0.2625000000000553 0 4199 0.441362255154879 -0.2625000000000553 0 4200 0.441362255154879 -0.2700000000000326 0 4201 0.4501276345236666 -0.2625000000000553 0 4202 0.4593312828741438 -0.2625000000000553 0 4203 0.4593312828741439 -0.2700000000000326 0 4204 0.4685349312246211 -0.2625000000000554 0 4205 0.4781987620271604 -0.2625000000000554 0 4206 0.4781987620271604 -0.2700000000000326 0 4207 0.4878625928296997 -0.2625000000000553 0 4208 0.498009615152599 -0.2625000000000553 0 4209 0.498009615152599 -0.2700000000000326 0 4210 0.5081566374754983 -0.2625000000000553 0 4211 0.518811010890774 -0.2625000000000553 0 4212 0.518811010890774 -0.2700000000000326 0 4213 0.5294653843060497 -0.2625000000000553 0 4214 0.5406524764063752 -0.2625000000000553 0 4215 0.5406524764063752 -0.2700000000000326 0 4216 0.5518395685067007 -0.2625000000000553 0 4217 0.5635860152260461 -0.2625000000000553 0 4218 0.5635860152260461 -0.2700000000000326 0 4219 0.5753324619453917 -0.2625000000000553 0 4220 0.5876662309726959 -0.2625000000000553 0 4221 0.5876662309726959 -0.2700000000000326 0 4222 0.4079504575179718 -0.2775000000000217 0 4223 0.4079504575179718 -0.2850000000000109 0 4224 0.4159009150359435 -0.2775000000000217 0 4225 0.4242488954110175 -0.2775000000000217 0 4226 0.4242488954110174 -0.2850000000000108 0 4227 0.4325968757860914 -0.2775000000000217 0 4228 0.4413622551548789 -0.2775000000000217 0 4229 0.4413622551548789 -0.2850000000000108 0 4230 0.4501276345236666 -0.2775000000000217 0 4231 0.4593312828741439 -0.2775000000000217 0 4232 0.4593312828741438 -0.2850000000000109 0 4233 0.4685349312246212 -0.2775000000000217 0 4234 0.4781987620271604 -0.2775000000000217 0 4235 0.4781987620271604 -0.2850000000000108 0 4236 0.4878625928296997 -0.2775000000000217 0 4237 0.498009615152599 -0.2775000000000217 0 4238 0.498009615152599 -0.2850000000000108 0 4239 0.5081566374754983 -0.2775000000000217 0 4240 0.518811010890774 -0.2775000000000217 0 4241 0.518811010890774 -0.2850000000000108 0 4242 0.5294653843060497 -0.2775000000000217 0 4243 0.5406524764063752 -0.2775000000000217 0 4244 0.5406524764063752 -0.2850000000000108 0 4245 0.5518395685067007 -0.2775000000000217 0 4246 0.5635860152260461 -0.2775000000000217 0 4247 0.5635860152260461 -0.2850000000000108 0 4248 0.5753324619453917 -0.2775000000000217 0 4249 0.5876662309726958 -0.2775000000000217 0 4250 0.5876662309726958 -0.2850000000000108 0 4251 0.4079504575179718 -0.2925000000000054 0 4252 0.4159009150359435 -0.2925000000000054 0 4253 0.4242488954110174 -0.2925000000000054 0 4254 0.4325968757860914 -0.2925000000000054 0 4255 0.441362255154879 -0.2925000000000054 0 4256 0.4501276345236666 -0.2925000000000054 0 4257 0.4593312828741439 -0.2925000000000054 0 4258 0.4685349312246211 -0.2925000000000054 0 4259 0.4781987620271604 -0.2925000000000054 0 4260 0.4878625928296997 -0.2925000000000054 0 4261 0.498009615152599 -0.2925000000000054 0 4262 0.5081566374754983 -0.2925000000000054 0 4263 0.518811010890774 -0.2925000000000054 0 4264 0.5294653843060497 -0.2925000000000054 0 4265 0.5406524764063752 -0.2925000000000054 0 4266 0.5518395685067007 -0.2925000000000054 0 4267 0.5635860152260461 -0.2925000000000054 0 4268 0.5753324619453916 -0.2925000000000054 0 4269 0.5876662309726958 -0.2925000000000054 0 4270 0.009999999999981924 -0.3180974798108416 0 4271 0.01999999999996121 -0.3180974798108416 0 4272 0.02999999999994401 -0.3180974798108416 0 4273 0.03999999999992994 -0.3180974798108415 0 4274 0.04999999999991423 -0.3180974798108416 0 4275 0.05999999999988688 -0.3180974798108416 0 4276 0.06999999999985818 -0.3180974798108416 0 4277 0.07999999999982406 -0.3180974798108415 0 4278 0.08999999999979536 -0.3180974798108416 0 4279 0.09999999999976665 -0.3180974798108416 0 4280 0.109999999999738 -0.3180974798108415 0 4281 0.1199999999997092 -0.3180974798108416 0 4282 0.1299999999996806 -0.3180974798108415 0 4283 0.1399999999996518 -0.3180974798108415 0 4284 0.1499999999996177 -0.3180974798108416 0 4285 0.1599999999995836 -0.3180974798108416 0 4286 0.1699999999995549 -0.3180974798108416 0 4287 0.1799999999995262 -0.3180974798108416 0 4288 0.1899999999994975 -0.3180974798108416 0 4289 0.1999999999994742 -0.3180974798108416 0 4290 0.2099999999994975 -0.3180974798108416 0 4291 0.2199999999995209 -0.3180974798108416 0 4292 0.2299999999995495 -0.3180974798108416 0 4293 0.2399999999995773 -0.3180974798108416 0 4294 0.2499999999996016 -0.3180974798108416 0 4295 0.2599999999996346 -0.3180974798108416 0 4296 0.2699999999996558 -0.3180974798108415 0 4297 0.2799999999996791 -0.3180974798108416 0 4298 0.2899999999997153 -0.3180974798108416 0 4299 0.2999999999997343 -0.3180974798108416 0 4300 0.3099999999997534 -0.3180974798108416 0 4301 0.3199999999997897 -0.3180974798108416 0 4302 0.329999999999813 -0.3180974798108416 0 4303 0.3399999999998342 -0.3180974798108415 0 4304 0.3499999999998726 -0.3180974798108416 0 4305 0.3599999999998915 -0.3180974798108416 0 4306 0.369999999999917 -0.3180974798108416 0 4307 0.3799999999999513 -0.3180974798108416 0 4308 0.3899999999999701 -0.3180974798108416 0 4309 0.009999999999981924 -0.3370998336061459 0 4310 0.01999999999996121 -0.3370998336061458 0 4311 0.02999999999994401 -0.3370998336061457 0 4312 0.03999999999992994 -0.3370998336061459 0 4313 0.04999999999991424 -0.337099833606146 0 4314 0.05999999999988688 -0.3370998336061458 0 4315 0.06999999999985819 -0.3370998336061459 0 4316 0.07999999999982406 -0.3370998336061458 0 4317 0.08999999999979536 -0.3370998336061458 0 4318 0.09999999999976666 -0.3370998336061458 0 4319 0.109999999999738 -0.3370998336061458 0 4320 0.1199999999997092 -0.3370998336061458 0 4321 0.1299999999996806 -0.3370998336061458 0 4322 0.1399999999996519 -0.3370998336061457 0 4323 0.1499999999996177 -0.3370998336061458 0 4324 0.1599999999995836 -0.3370998336061458 0 4325 0.1699999999995549 -0.3370998336061458 0 4326 0.1799999999995262 -0.3370998336061458 0 4327 0.1899999999994975 -0.3370998336061457 0 4328 0.1999999999994742 -0.3370998336061458 0 4329 0.2099999999994975 -0.3370998336061458 0 4330 0.2199999999995209 -0.3370998336061458 0 4331 0.2299999999995495 -0.3370998336061458 0 4332 0.2399999999995772 -0.3370998336061458 0 4333 0.2499999999996016 -0.3370998336061458 0 4334 0.2599999999996346 -0.3370998336061458 0 4335 0.2699999999996558 -0.3370998336061458 0 4336 0.2799999999996791 -0.3370998336061458 0 4337 0.2899999999997154 -0.3370998336061458 0 4338 0.2999999999997344 -0.3370998336061458 0 4339 0.3099999999997534 -0.3370998336061458 0 4340 0.3199999999997897 -0.3370998336061458 0 4341 0.3299999999998131 -0.3370998336061458 0 4342 0.3399999999998342 -0.3370998336061458 0 4343 0.3499999999998726 -0.3370998336061458 0 4344 0.3599999999998915 -0.3370998336061458 0 4345 0.3699999999999171 -0.3370998336061458 0 4346 0.3799999999999512 -0.3370998336061458 0 4347 0.3899999999999702 -0.3370998336061458 0 4348 0.009999999999981926 -0.3570523051187843 0 4349 0.01999999999996121 -0.3570523051187843 0 4350 0.02999999999994402 -0.3570523051187844 0 4351 0.03999999999992994 -0.3570523051187844 0 4352 0.04999999999991424 -0.3570523051187844 0 4353 0.05999999999988688 -0.3570523051187844 0 4354 0.06999999999985819 -0.3570523051187845 0 4355 0.07999999999982406 -0.3570523051187844 0 4356 0.08999999999979536 -0.3570523051187844 0 4357 0.09999999999976666 -0.3570523051187844 0 4358 0.109999999999738 -0.3570523051187844 0 4359 0.1199999999997092 -0.3570523051187845 0 4360 0.1299999999996806 -0.3570523051187843 0 4361 0.1399999999996519 -0.3570523051187844 0 4362 0.1499999999996177 -0.3570523051187844 0 4363 0.1599999999995836 -0.3570523051187844 0 4364 0.1699999999995549 -0.3570523051187844 0 4365 0.1799999999995262 -0.3570523051187843 0 4366 0.1899999999994975 -0.3570523051187844 0 4367 0.1999999999994742 -0.3570523051187844 0 4368 0.2099999999994975 -0.3570523051187844 0 4369 0.2199999999995209 -0.3570523051187844 0 4370 0.2299999999995495 -0.3570523051187843 0 4371 0.2399999999995772 -0.3570523051187844 0 4372 0.2499999999996016 -0.3570523051187844 0 4373 0.2599999999996346 -0.3570523051187844 0 4374 0.2699999999996558 -0.3570523051187844 0 4375 0.2799999999996791 -0.3570523051187844 0 4376 0.2899999999997154 -0.3570523051187844 0 4377 0.2999999999997344 -0.3570523051187844 0 4378 0.3099999999997534 -0.3570523051187844 0 4379 0.3199999999997897 -0.3570523051187844 0 4380 0.329999999999813 -0.3570523051187844 0 4381 0.3399999999998342 -0.3570523051187844 0 4382 0.3499999999998726 -0.3570523051187844 0 4383 0.3599999999998915 -0.3570523051187844 0 4384 0.3699999999999171 -0.3570523051187844 0 4385 0.3799999999999512 -0.3570523051187844 0 4386 0.3899999999999702 -0.3570523051187844 0 4387 0.009999999999981926 -0.3780024001981511 0 4388 0.01999999999996121 -0.3780024001981511 0 4389 0.029999999999944 -0.378002400198151 0 4390 0.03999999999992994 -0.3780024001981511 0 4391 0.04999999999991424 -0.3780024001981513 0 4392 0.05999999999988688 -0.378002400198151 0 4393 0.06999999999985818 -0.3780024001981511 0 4394 0.07999999999982406 -0.3780024001981511 0 4395 0.08999999999979536 -0.3780024001981511 0 4396 0.09999999999976665 -0.3780024001981511 0 4397 0.109999999999738 -0.3780024001981511 0 4398 0.1199999999997092 -0.3780024001981511 0 4399 0.1299999999996806 -0.3780024001981511 0 4400 0.1399999999996518 -0.3780024001981512 0 4401 0.1499999999996177 -0.3780024001981511 0 4402 0.1599999999995836 -0.3780024001981511 0 4403 0.1699999999995549 -0.3780024001981511 0 4404 0.1799999999995262 -0.3780024001981511 0 4405 0.1899999999994975 -0.3780024001981511 0 4406 0.1999999999994742 -0.3780024001981511 0 4407 0.2099999999994975 -0.3780024001981511 0 4408 0.2199999999995209 -0.3780024001981511 0 4409 0.2299999999995495 -0.3780024001981511 0 4410 0.2399999999995773 -0.3780024001981512 0 4411 0.2499999999996016 -0.3780024001981511 0 4412 0.2599999999996346 -0.3780024001981511 0 4413 0.2699999999996558 -0.3780024001981511 0 4414 0.2799999999996792 -0.3780024001981511 0 4415 0.2899999999997153 -0.3780024001981511 0 4416 0.2999999999997344 -0.3780024001981511 0 4417 0.3099999999997534 -0.3780024001981511 0 4418 0.3199999999997897 -0.3780024001981511 0 4419 0.329999999999813 -0.3780024001981511 0 4420 0.3399999999998342 -0.3780024001981511 0 4421 0.3499999999998726 -0.3780024001981511 0 4422 0.3599999999998915 -0.3780024001981511 0 4423 0.3699999999999171 -0.3780024001981511 0 4424 0.3799999999999512 -0.3780024001981511 0 4425 0.3899999999999702 -0.3780024001981511 0 4426 0.004999999999990963 -0.3090487399054208 0 4427 0.004999999999990962 -0.3180974798108416 0 4428 0.009999999999981926 -0.3090487399054208 0 4429 0.01499999999997156 -0.3090487399054208 0 4430 0.01499999999997156 -0.3180974798108416 0 4431 0.01999999999996121 -0.3090487399054208 0 4432 0.02499999999995261 -0.3090487399054208 0 4433 0.02499999999995261 -0.3180974798108416 0 4434 0.02999999999994401 -0.3090487399054208 0 4435 0.03499999999993698 -0.3090487399054208 0 4436 0.03499999999993698 -0.3180974798108416 0 4437 0.03999999999992994 -0.3090487399054208 0 4438 0.04499999999992209 -0.3090487399054208 0 4439 0.04499999999992209 -0.3180974798108416 0 4440 0.04999999999991424 -0.3090487399054208 0 4441 0.05499999999990056 -0.3090487399054208 0 4442 0.05499999999990056 -0.3180974798108416 0 4443 0.05999999999988688 -0.3090487399054208 0 4444 0.06499999999987254 -0.3090487399054208 0 4445 0.06499999999987252 -0.3180974798108416 0 4446 0.06999999999985818 -0.3090487399054208 0 4447 0.07499999999984111 -0.3090487399054208 0 4448 0.07499999999984111 -0.3180974798108416 0 4449 0.07999999999982406 -0.3090487399054208 0 4450 0.0849999999998097 -0.3090487399054208 0 4451 0.0849999999998097 -0.3180974798108416 0 4452 0.08999999999979536 -0.3090487399054208 0 4453 0.09499999999978101 -0.3090487399054208 0 4454 0.09499999999978101 -0.3180974798108416 0 4455 0.09999999999976666 -0.3090487399054208 0 4456 0.1049999999997523 -0.3090487399054208 0 4457 0.1049999999997523 -0.3180974798108416 0 4458 0.109999999999738 -0.3090487399054208 0 4459 0.1149999999997236 -0.3090487399054208 0 4460 0.1149999999997236 -0.3180974798108416 0 4461 0.1199999999997092 -0.3090487399054208 0 4462 0.1249999999996949 -0.3090487399054208 0 4463 0.1249999999996949 -0.3180974798108416 0 4464 0.1299999999996806 -0.3090487399054208 0 4465 0.1349999999996662 -0.3090487399054208 0 4466 0.1349999999996662 -0.3180974798108415 0 4467 0.1399999999996519 -0.3090487399054208 0 4468 0.1449999999996348 -0.3090487399054208 0 4469 0.1449999999996348 -0.3180974798108416 0 4470 0.1499999999996177 -0.3090487399054208 0 4471 0.1549999999996007 -0.3090487399054208 0 4472 0.1549999999996006 -0.3180974798108416 0 4473 0.1599999999995836 -0.3090487399054208 0 4474 0.1649999999995692 -0.3090487399054208 0 4475 0.1649999999995692 -0.3180974798108416 0 4476 0.1699999999995549 -0.3090487399054208 0 4477 0.1749999999995406 -0.3090487399054208 0 4478 0.1749999999995406 -0.3180974798108416 0 4479 0.1799999999995262 -0.3090487399054208 0 4480 0.1849999999995118 -0.3090487399054208 0 4481 0.1849999999995118 -0.3180974798108416 0 4482 0.1899999999994975 -0.3090487399054208 0 4483 0.1949999999994858 -0.3090487399054208 0 4484 0.1949999999994858 -0.3180974798108416 0 4485 0.1999999999994742 -0.3090487399054208 0 4486 0.2049999999994859 -0.3090487399054208 0 4487 0.2049999999994859 -0.3180974798108416 0 4488 0.2099999999994975 -0.3090487399054208 0 4489 0.2149999999995092 -0.3090487399054208 0 4490 0.2149999999995092 -0.3180974798108416 0 4491 0.2199999999995209 -0.3090487399054208 0 4492 0.2249999999995352 -0.3090487399054208 0 4493 0.2249999999995352 -0.3180974798108416 0 4494 0.2299999999995495 -0.3090487399054208 0 4495 0.2349999999995633 -0.3090487399054208 0 4496 0.2349999999995634 -0.3180974798108416 0 4497 0.2399999999995772 -0.3090487399054208 0 4498 0.2449999999995894 -0.3090487399054208 0 4499 0.2449999999995894 -0.3180974798108416 0 4500 0.2499999999996016 -0.3090487399054208 0 4501 0.2549999999996181 -0.3090487399054208 0 4502 0.2549999999996181 -0.3180974798108416 0 4503 0.2599999999996346 -0.3090487399054208 0 4504 0.2649999999996452 -0.3090487399054208 0 4505 0.2649999999996452 -0.3180974798108416 0 4506 0.2699999999996558 -0.3090487399054208 0 4507 0.2749999999996675 -0.3090487399054208 0 4508 0.2749999999996675 -0.3180974798108416 0 4509 0.2799999999996791 -0.3090487399054208 0 4510 0.2849999999996973 -0.3090487399054208 0 4511 0.2849999999996972 -0.3180974798108416 0 4512 0.2899999999997154 -0.3090487399054208 0 4513 0.2949999999997248 -0.3090487399054208 0 4514 0.2949999999997248 -0.3180974798108416 0 4515 0.2999999999997344 -0.3090487399054208 0 4516 0.3049999999997438 -0.3090487399054208 0 4517 0.3049999999997438 -0.3180974798108416 0 4518 0.3099999999997534 -0.3090487399054208 0 4519 0.3149999999997715 -0.3090487399054208 0 4520 0.3149999999997715 -0.3180974798108416 0 4521 0.3199999999997897 -0.3090487399054208 0 4522 0.3249999999998013 -0.3090487399054208 0 4523 0.3249999999998013 -0.3180974798108416 0 4524 0.329999999999813 -0.3090487399054208 0 4525 0.3349999999998236 -0.3090487399054208 0 4526 0.3349999999998236 -0.3180974798108416 0 4527 0.3399999999998342 -0.3090487399054208 0 4528 0.3449999999998534 -0.3090487399054208 0 4529 0.3449999999998534 -0.3180974798108416 0 4530 0.3499999999998726 -0.3090487399054208 0 4531 0.3549999999998821 -0.3090487399054208 0 4532 0.3549999999998821 -0.3180974798108416 0 4533 0.3599999999998915 -0.3090487399054208 0 4534 0.3649999999999043 -0.3090487399054208 0 4535 0.3649999999999043 -0.3180974798108416 0 4536 0.3699999999999171 -0.3090487399054208 0 4537 0.3749999999999341 -0.3090487399054208 0 4538 0.3749999999999342 -0.3180974798108416 0 4539 0.3799999999999513 -0.3090487399054208 0 4540 0.3849999999999607 -0.3090487399054208 0 4541 0.3849999999999607 -0.3180974798108416 0 4542 0.3899999999999702 -0.3090487399054208 0 4543 0.394999999999985 -0.3090487399054208 0 4544 0.394999999999985 -0.3180974798108416 0 4545 0.004999999999990962 -0.3275986567084938 0 4546 0.004999999999990962 -0.3370998336061458 0 4547 0.009999999999981924 -0.3275986567084938 0 4548 0.01499999999997156 -0.3275986567084938 0 4549 0.01499999999997157 -0.3370998336061458 0 4550 0.01999999999996121 -0.3275986567084937 0 4551 0.02499999999995261 -0.3275986567084937 0 4552 0.02499999999995261 -0.3370998336061457 0 4553 0.02999999999994401 -0.3275986567084936 0 4554 0.03499999999993697 -0.3275986567084936 0 4555 0.03499999999993697 -0.3370998336061458 0 4556 0.03999999999992994 -0.3275986567084937 0 4557 0.04499999999992209 -0.3275986567084938 0 4558 0.04499999999992209 -0.3370998336061459 0 4559 0.04999999999991424 -0.3275986567084938 0 4560 0.05499999999990056 -0.3275986567084938 0 4561 0.05499999999990056 -0.3370998336061459 0 4562 0.05999999999988688 -0.3275986567084937 0 4563 0.06499999999987252 -0.3275986567084937 0 4564 0.06499999999987254 -0.3370998336061459 0 4565 0.06999999999985818 -0.3275986567084938 0 4566 0.07499999999984112 -0.3275986567084938 0 4567 0.07499999999984112 -0.3370998336061459 0 4568 0.07999999999982406 -0.3275986567084936 0 4569 0.0849999999998097 -0.3275986567084937 0 4570 0.0849999999998097 -0.3370998336061458 0 4571 0.08999999999979536 -0.3275986567084937 0 4572 0.09499999999978101 -0.3275986567084937 0 4573 0.09499999999978101 -0.3370998336061458 0 4574 0.09999999999976666 -0.3275986567084937 0 4575 0.1049999999997523 -0.3275986567084936 0 4576 0.1049999999997523 -0.3370998336061458 0 4577 0.109999999999738 -0.3275986567084936 0 4578 0.1149999999997236 -0.3275986567084937 0 4579 0.1149999999997236 -0.3370998336061458 0 4580 0.1199999999997092 -0.3275986567084937 0 4581 0.1249999999996949 -0.3275986567084936 0 4582 0.1249999999996949 -0.3370998336061458 0 4583 0.1299999999996806 -0.3275986567084936 0 4584 0.1349999999996662 -0.3275986567084936 0 4585 0.1349999999996662 -0.3370998336061458 0 4586 0.1399999999996519 -0.3275986567084936 0 4587 0.1449999999996348 -0.3275986567084936 0 4588 0.1449999999996348 -0.3370998336061458 0 4589 0.1499999999996177 -0.3275986567084937 0 4590 0.1549999999996006 -0.3275986567084937 0 4591 0.1549999999996007 -0.3370998336061458 0 4592 0.1599999999995836 -0.3275986567084937 0 4593 0.1649999999995692 -0.3275986567084937 0 4594 0.1649999999995692 -0.3370998336061458 0 4595 0.1699999999995549 -0.3275986567084937 0 4596 0.1749999999995406 -0.3275986567084937 0 4597 0.1749999999995405 -0.3370998336061458 0 4598 0.1799999999995262 -0.3275986567084936 0 4599 0.1849999999995118 -0.3275986567084936 0 4600 0.1849999999995118 -0.3370998336061457 0 4601 0.1899999999994975 -0.3275986567084936 0 4602 0.1949999999994859 -0.3275986567084936 0 4603 0.1949999999994859 -0.3370998336061458 0 4604 0.1999999999994742 -0.3275986567084937 0 4605 0.2049999999994859 -0.3275986567084937 0 4606 0.2049999999994859 -0.3370998336061458 0 4607 0.2099999999994975 -0.3275986567084937 0 4608 0.2149999999995092 -0.3275986567084937 0 4609 0.2149999999995092 -0.3370998336061458 0 4610 0.2199999999995209 -0.3275986567084937 0 4611 0.2249999999995352 -0.3275986567084937 0 4612 0.2249999999995352 -0.3370998336061458 0 4613 0.2299999999995495 -0.3275986567084937 0 4614 0.2349999999995634 -0.3275986567084937 0 4615 0.2349999999995633 -0.3370998336061458 0 4616 0.2399999999995772 -0.3275986567084937 0 4617 0.2449999999995894 -0.3275986567084937 0 4618 0.2449999999995894 -0.3370998336061458 0 4619 0.2499999999996016 -0.3275986567084937 0 4620 0.2549999999996181 -0.3275986567084937 0 4621 0.2549999999996181 -0.3370998336061458 0 4622 0.2599999999996346 -0.3275986567084937 0 4623 0.2649999999996452 -0.3275986567084936 0 4624 0.2649999999996452 -0.3370998336061458 0 4625 0.2699999999996558 -0.3275986567084936 0 4626 0.2749999999996675 -0.3275986567084937 0 4627 0.2749999999996675 -0.3370998336061458 0 4628 0.2799999999996791 -0.3275986567084937 0 4629 0.2849999999996972 -0.3275986567084937 0 4630 0.2849999999996973 -0.3370998336061458 0 4631 0.2899999999997154 -0.3275986567084937 0 4632 0.2949999999997248 -0.3275986567084937 0 4633 0.2949999999997249 -0.3370998336061458 0 4634 0.2999999999997344 -0.3275986567084937 0 4635 0.3049999999997439 -0.3275986567084937 0 4636 0.3049999999997439 -0.3370998336061458 0 4637 0.3099999999997534 -0.3275986567084937 0 4638 0.3149999999997715 -0.3275986567084937 0 4639 0.3149999999997715 -0.3370998336061458 0 4640 0.3199999999997897 -0.3275986567084937 0 4641 0.3249999999998013 -0.3275986567084937 0 4642 0.3249999999998013 -0.3370998336061458 0 4643 0.329999999999813 -0.3275986567084937 0 4644 0.3349999999998236 -0.3275986567084936 0 4645 0.3349999999998236 -0.3370998336061458 0 4646 0.3399999999998342 -0.3275986567084936 0 4647 0.3449999999998534 -0.3275986567084937 0 4648 0.3449999999998534 -0.3370998336061458 0 4649 0.3499999999998726 -0.3275986567084937 0 4650 0.3549999999998821 -0.3275986567084937 0 4651 0.3549999999998821 -0.3370998336061458 0 4652 0.3599999999998915 -0.3275986567084937 0 4653 0.3649999999999042 -0.3275986567084937 0 4654 0.3649999999999043 -0.3370998336061458 0 4655 0.3699999999999171 -0.3275986567084937 0 4656 0.3749999999999342 -0.3275986567084937 0 4657 0.3749999999999342 -0.3370998336061458 0 4658 0.3799999999999513 -0.3275986567084937 0 4659 0.3849999999999607 -0.3275986567084937 0 4660 0.3849999999999607 -0.3370998336061458 0 4661 0.3899999999999702 -0.3275986567084937 0 4662 0.3949999999999851 -0.3275986567084937 0 4663 0.3949999999999851 -0.3370998336061458 0 4664 0.004999999999990962 -0.3470760693624652 0 4665 0.004999999999990963 -0.3570523051187843 0 4666 0.009999999999981926 -0.3470760693624651 0 4667 0.01499999999997157 -0.3470760693624651 0 4668 0.01499999999997157 -0.3570523051187843 0 4669 0.01999999999996121 -0.3470760693624651 0 4670 0.02499999999995261 -0.347076069362465 0 4671 0.02499999999995261 -0.3570523051187844 0 4672 0.02999999999994401 -0.3470760693624651 0 4673 0.03499999999993698 -0.3470760693624652 0 4674 0.03499999999993698 -0.3570523051187844 0 4675 0.03999999999992994 -0.3470760693624652 0 4676 0.04499999999992209 -0.3470760693624652 0 4677 0.04499999999992209 -0.3570523051187844 0 4678 0.04999999999991424 -0.3470760693624652 0 4679 0.05499999999990056 -0.3470760693624651 0 4680 0.05499999999990056 -0.3570523051187844 0 4681 0.05999999999988688 -0.3470760693624651 0 4682 0.06499999999987254 -0.3470760693624652 0 4683 0.06499999999987254 -0.3570523051187844 0 4684 0.06999999999985819 -0.3470760693624652 0 4685 0.07499999999984112 -0.3470760693624652 0 4686 0.07499999999984112 -0.3570523051187844 0 4687 0.07999999999982406 -0.3470760693624651 0 4688 0.0849999999998097 -0.3470760693624651 0 4689 0.0849999999998097 -0.3570523051187844 0 4690 0.08999999999979536 -0.3470760693624651 0 4691 0.09499999999978101 -0.3470760693624651 0 4692 0.09499999999978101 -0.3570523051187844 0 4693 0.09999999999976666 -0.3470760693624652 0 4694 0.1049999999997523 -0.3470760693624652 0 4695 0.1049999999997523 -0.3570523051187844 0 4696 0.109999999999738 -0.3470760693624651 0 4697 0.1149999999997236 -0.3470760693624651 0 4698 0.1149999999997236 -0.3570523051187844 0 4699 0.1199999999997092 -0.3470760693624652 0 4700 0.1249999999996949 -0.3470760693624652 0 4701 0.1249999999996949 -0.3570523051187844 0 4702 0.1299999999996806 -0.3470760693624651 0 4703 0.1349999999996662 -0.3470760693624651 0 4704 0.1349999999996662 -0.3570523051187844 0 4705 0.1399999999996519 -0.3470760693624651 0 4706 0.1449999999996348 -0.3470760693624651 0 4707 0.1449999999996348 -0.3570523051187844 0 4708 0.1499999999996177 -0.3470760693624651 0 4709 0.1549999999996007 -0.3470760693624651 0 4710 0.1549999999996007 -0.3570523051187844 0 4711 0.1599999999995836 -0.3470760693624651 0 4712 0.1649999999995692 -0.3470760693624651 0 4713 0.1649999999995692 -0.3570523051187844 0 4714 0.1699999999995549 -0.3470760693624651 0 4715 0.1749999999995405 -0.3470760693624651 0 4716 0.1749999999995406 -0.3570523051187843 0 4717 0.1799999999995262 -0.3470760693624651 0 4718 0.1849999999995118 -0.347076069362465 0 4719 0.1849999999995118 -0.3570523051187843 0 4720 0.1899999999994975 -0.3470760693624651 0 4721 0.1949999999994859 -0.3470760693624651 0 4722 0.1949999999994859 -0.3570523051187844 0 4723 0.1999999999994742 -0.3470760693624651 0 4724 0.2049999999994859 -0.3470760693624651 0 4725 0.2049999999994859 -0.3570523051187844 0 4726 0.2099999999994975 -0.3470760693624651 0 4727 0.2149999999995092 -0.3470760693624651 0 4728 0.2149999999995092 -0.3570523051187844 0 4729 0.2199999999995209 -0.3470760693624651 0 4730 0.2249999999995352 -0.3470760693624651 0 4731 0.2249999999995352 -0.3570523051187844 0 4732 0.2299999999995495 -0.3470760693624651 0 4733 0.2349999999995633 -0.3470760693624651 0 4734 0.2349999999995634 -0.3570523051187844 0 4735 0.2399999999995772 -0.3470760693624651 0 4736 0.2449999999995894 -0.3470760693624651 0 4737 0.2449999999995894 -0.3570523051187844 0 4738 0.2499999999996016 -0.3470760693624651 0 4739 0.2549999999996181 -0.3470760693624651 0 4740 0.2549999999996181 -0.3570523051187844 0 4741 0.2599999999996346 -0.3470760693624651 0 4742 0.2649999999996452 -0.3470760693624651 0 4743 0.2649999999996452 -0.3570523051187844 0 4744 0.2699999999996558 -0.3470760693624651 0 4745 0.2749999999996675 -0.3470760693624651 0 4746 0.2749999999996675 -0.3570523051187844 0 4747 0.2799999999996791 -0.3470760693624651 0 4748 0.2849999999996973 -0.3470760693624651 0 4749 0.2849999999996973 -0.3570523051187844 0 4750 0.2899999999997154 -0.3470760693624651 0 4751 0.2949999999997249 -0.3470760693624651 0 4752 0.2949999999997249 -0.3570523051187844 0 4753 0.2999999999997344 -0.3470760693624651 0 4754 0.3049999999997439 -0.3470760693624651 0 4755 0.3049999999997439 -0.3570523051187844 0 4756 0.3099999999997534 -0.3470760693624651 0 4757 0.3149999999997715 -0.3470760693624651 0 4758 0.3149999999997715 -0.3570523051187844 0 4759 0.3199999999997897 -0.3470760693624651 0 4760 0.3249999999998013 -0.3470760693624651 0 4761 0.3249999999998013 -0.3570523051187844 0 4762 0.329999999999813 -0.3470760693624651 0 4763 0.3349999999998236 -0.3470760693624651 0 4764 0.3349999999998236 -0.3570523051187844 0 4765 0.3399999999998342 -0.3470760693624651 0 4766 0.3449999999998534 -0.3470760693624651 0 4767 0.3449999999998534 -0.3570523051187844 0 4768 0.3499999999998726 -0.3470760693624651 0 4769 0.3549999999998821 -0.3470760693624651 0 4770 0.3549999999998821 -0.3570523051187844 0 4771 0.3599999999998915 -0.3470760693624651 0 4772 0.3649999999999043 -0.3470760693624651 0 4773 0.3649999999999043 -0.3570523051187844 0 4774 0.3699999999999171 -0.3470760693624651 0 4775 0.3749999999999342 -0.3470760693624651 0 4776 0.3749999999999342 -0.3570523051187844 0 4777 0.3799999999999512 -0.3470760693624651 0 4778 0.3849999999999607 -0.3470760693624651 0 4779 0.3849999999999607 -0.3570523051187844 0 4780 0.3899999999999702 -0.3470760693624651 0 4781 0.3949999999999851 -0.3470760693624651 0 4782 0.3949999999999851 -0.3570523051187844 0 4783 0.004999999999990963 -0.3675273526584677 0 4784 0.004999999999990963 -0.3780024001981511 0 4785 0.009999999999981926 -0.3675273526584677 0 4786 0.01499999999997157 -0.3675273526584677 0 4787 0.01499999999997157 -0.3780024001981511 0 4788 0.01999999999996121 -0.3675273526584677 0 4789 0.02499999999995261 -0.3675273526584677 0 4790 0.0249999999999526 -0.378002400198151 0 4791 0.02999999999994401 -0.3675273526584677 0 4792 0.03499999999993697 -0.3675273526584677 0 4793 0.03499999999993697 -0.3780024001981511 0 4794 0.03999999999992994 -0.3675273526584678 0 4795 0.04499999999992209 -0.3675273526584678 0 4796 0.04499999999992209 -0.3780024001981512 0 4797 0.04999999999991424 -0.3675273526584678 0 4798 0.05499999999990056 -0.3675273526584678 0 4799 0.05499999999990056 -0.3780024001981511 0 4800 0.05999999999988688 -0.3675273526584677 0 4801 0.06499999999987254 -0.3675273526584678 0 4802 0.06499999999987252 -0.378002400198151 0 4803 0.06999999999985818 -0.3675273526584678 0 4804 0.07499999999984111 -0.3675273526584677 0 4805 0.07499999999984111 -0.3780024001981511 0 4806 0.07999999999982406 -0.3675273526584678 0 4807 0.0849999999998097 -0.3675273526584678 0 4808 0.0849999999998097 -0.3780024001981511 0 4809 0.08999999999979536 -0.3675273526584678 0 4810 0.09499999999978101 -0.3675273526584678 0 4811 0.09499999999978101 -0.3780024001981511 0 4812 0.09999999999976666 -0.3675273526584678 0 4813 0.1049999999997523 -0.3675273526584678 0 4814 0.1049999999997523 -0.3780024001981511 0 4815 0.109999999999738 -0.3675273526584677 0 4816 0.1149999999997236 -0.3675273526584678 0 4817 0.1149999999997236 -0.3780024001981511 0 4818 0.1199999999997092 -0.3675273526584678 0 4819 0.1249999999996949 -0.3675273526584677 0 4820 0.1249999999996949 -0.3780024001981511 0 4821 0.1299999999996806 -0.3675273526584677 0 4822 0.1349999999996662 -0.3675273526584678 0 4823 0.1349999999996662 -0.3780024001981511 0 4824 0.1399999999996519 -0.3675273526584678 0 4825 0.1449999999996348 -0.3675273526584678 0 4826 0.1449999999996348 -0.3780024001981511 0 4827 0.1499999999996177 -0.3675273526584678 0 4828 0.1549999999996007 -0.3675273526584678 0 4829 0.1549999999996007 -0.3780024001981511 0 4830 0.1599999999995836 -0.3675273526584678 0 4831 0.1649999999995692 -0.3675273526584678 0 4832 0.1649999999995692 -0.3780024001981511 0 4833 0.1699999999995549 -0.3675273526584678 0 4834 0.1749999999995406 -0.3675273526584677 0 4835 0.1749999999995406 -0.3780024001981511 0 4836 0.1799999999995262 -0.3675273526584677 0 4837 0.1849999999995118 -0.3675273526584678 0 4838 0.1849999999995118 -0.3780024001981511 0 4839 0.1899999999994975 -0.3675273526584678 0 4840 0.1949999999994859 -0.3675273526584678 0 4841 0.1949999999994859 -0.3780024001981511 0 4842 0.1999999999994742 -0.3675273526584677 0 4843 0.2049999999994859 -0.3675273526584677 0 4844 0.2049999999994859 -0.3780024001981511 0 4845 0.2099999999994975 -0.3675273526584678 0 4846 0.2149999999995092 -0.3675273526584678 0 4847 0.2149999999995092 -0.3780024001981511 0 4848 0.2199999999995209 -0.3675273526584678 0 4849 0.2249999999995352 -0.3675273526584677 0 4850 0.2249999999995352 -0.3780024001981511 0 4851 0.2299999999995495 -0.3675273526584677 0 4852 0.2349999999995634 -0.3675273526584678 0 4853 0.2349999999995634 -0.3780024001981511 0 4854 0.2399999999995773 -0.3675273526584678 0 4855 0.2449999999995894 -0.3675273526584678 0 4856 0.2449999999995894 -0.3780024001981511 0 4857 0.2499999999996016 -0.3675273526584678 0 4858 0.2549999999996181 -0.3675273526584678 0 4859 0.2549999999996181 -0.3780024001981511 0 4860 0.2599999999996346 -0.3675273526584677 0 4861 0.2649999999996452 -0.3675273526584677 0 4862 0.2649999999996452 -0.3780024001981511 0 4863 0.2699999999996558 -0.3675273526584677 0 4864 0.2749999999996675 -0.3675273526584677 0 4865 0.2749999999996675 -0.3780024001981511 0 4866 0.2799999999996792 -0.3675273526584678 0 4867 0.2849999999996973 -0.3675273526584678 0 4868 0.2849999999996973 -0.3780024001981511 0 4869 0.2899999999997154 -0.3675273526584678 0 4870 0.2949999999997248 -0.3675273526584678 0 4871 0.2949999999997248 -0.3780024001981511 0 4872 0.2999999999997344 -0.3675273526584678 0 4873 0.3049999999997439 -0.3675273526584678 0 4874 0.3049999999997439 -0.3780024001981511 0 4875 0.3099999999997534 -0.3675273526584678 0 4876 0.3149999999997715 -0.3675273526584678 0 4877 0.3149999999997715 -0.3780024001981511 0 4878 0.3199999999997897 -0.3675273526584678 0 4879 0.3249999999998013 -0.3675273526584678 0 4880 0.3249999999998013 -0.3780024001981511 0 4881 0.329999999999813 -0.3675273526584678 0 4882 0.3349999999998236 -0.3675273526584678 0 4883 0.3349999999998236 -0.3780024001981511 0 4884 0.3399999999998342 -0.3675273526584677 0 4885 0.3449999999998534 -0.3675273526584677 0 4886 0.3449999999998534 -0.3780024001981511 0 4887 0.3499999999998726 -0.3675273526584677 0 4888 0.3549999999998821 -0.3675273526584677 0 4889 0.3549999999998821 -0.3780024001981511 0 4890 0.3599999999998915 -0.3675273526584678 0 4891 0.3649999999999043 -0.3675273526584678 0 4892 0.3649999999999043 -0.3780024001981511 0 4893 0.3699999999999171 -0.3675273526584678 0 4894 0.3749999999999342 -0.3675273526584678 0 4895 0.3749999999999342 -0.3780024001981511 0 4896 0.3799999999999512 -0.3675273526584678 0 4897 0.3849999999999607 -0.3675273526584678 0 4898 0.3849999999999607 -0.3780024001981511 0 4899 0.3899999999999702 -0.3675273526584678 0 4900 0.3949999999999851 -0.3675273526584678 0 4901 0.3949999999999851 -0.3780024001981511 0 4902 0.004999999999990963 -0.3890012000990756 0 4903 0.009999999999981926 -0.3890012000990756 0 4904 0.01499999999997157 -0.3890012000990756 0 4905 0.01999999999996121 -0.3890012000990756 0 4906 0.0249999999999526 -0.3890012000990755 0 4907 0.02999999999994401 -0.3890012000990755 0 4908 0.03499999999993698 -0.3890012000990756 0 4909 0.03999999999992994 -0.3890012000990756 0 4910 0.04499999999992209 -0.3890012000990757 0 4911 0.04999999999991424 -0.3890012000990757 0 4912 0.05499999999990056 -0.3890012000990755 0 4913 0.05999999999988688 -0.3890012000990755 0 4914 0.06499999999987252 -0.3890012000990756 0 4915 0.06999999999985818 -0.3890012000990756 0 4916 0.07499999999984112 -0.3890012000990756 0 4917 0.07999999999982406 -0.3890012000990756 0 4918 0.0849999999998097 -0.3890012000990756 0 4919 0.08999999999979536 -0.3890012000990756 0 4920 0.09499999999978101 -0.3890012000990756 0 4921 0.09999999999976666 -0.3890012000990756 0 4922 0.1049999999997523 -0.3890012000990756 0 4923 0.109999999999738 -0.3890012000990756 0 4924 0.1149999999997236 -0.3890012000990756 0 4925 0.1199999999997092 -0.3890012000990756 0 4926 0.1249999999996949 -0.3890012000990756 0 4927 0.1299999999996806 -0.3890012000990756 0 4928 0.1349999999996662 -0.3890012000990756 0 4929 0.1399999999996519 -0.3890012000990756 0 4930 0.1449999999996348 -0.3890012000990756 0 4931 0.1499999999996177 -0.3890012000990756 0 4932 0.1549999999996007 -0.3890012000990756 0 4933 0.1599999999995836 -0.3890012000990756 0 4934 0.1649999999995692 -0.3890012000990756 0 4935 0.1699999999995549 -0.3890012000990756 0 4936 0.1749999999995406 -0.3890012000990756 0 4937 0.1799999999995262 -0.3890012000990756 0 4938 0.1849999999995118 -0.3890012000990756 0 4939 0.1899999999994975 -0.3890012000990756 0 4940 0.1949999999994859 -0.3890012000990756 0 4941 0.1999999999994742 -0.3890012000990756 0 4942 0.2049999999994859 -0.3890012000990756 0 4943 0.2099999999994975 -0.3890012000990756 0 4944 0.2149999999995092 -0.3890012000990756 0 4945 0.2199999999995209 -0.3890012000990756 0 4946 0.2249999999995352 -0.3890012000990756 0 4947 0.2299999999995495 -0.3890012000990756 0 4948 0.2349999999995634 -0.3890012000990756 0 4949 0.2399999999995772 -0.3890012000990756 0 4950 0.2449999999995894 -0.3890012000990756 0 4951 0.2499999999996016 -0.3890012000990756 0 4952 0.2549999999996181 -0.3890012000990756 0 4953 0.2599999999996346 -0.3890012000990756 0 4954 0.2649999999996452 -0.3890012000990756 0 4955 0.2699999999996558 -0.3890012000990756 0 4956 0.2749999999996675 -0.3890012000990756 0 4957 0.2799999999996792 -0.3890012000990756 0 4958 0.2849999999996972 -0.3890012000990756 0 4959 0.2899999999997154 -0.3890012000990756 0 4960 0.2949999999997249 -0.3890012000990756 0 4961 0.2999999999997344 -0.3890012000990756 0 4962 0.3049999999997439 -0.3890012000990756 0 4963 0.3099999999997534 -0.3890012000990756 0 4964 0.3149999999997715 -0.3890012000990756 0 4965 0.3199999999997897 -0.3890012000990756 0 4966 0.3249999999998013 -0.3890012000990756 0 4967 0.329999999999813 -0.3890012000990756 0 4968 0.3349999999998236 -0.3890012000990756 0 4969 0.3399999999998342 -0.3890012000990756 0 4970 0.3449999999998534 -0.3890012000990756 0 4971 0.3499999999998726 -0.3890012000990756 0 4972 0.3549999999998821 -0.3890012000990756 0 4973 0.3599999999998915 -0.3890012000990756 0 4974 0.3649999999999043 -0.3890012000990756 0 4975 0.3699999999999171 -0.3890012000990756 0 4976 0.3749999999999342 -0.3890012000990756 0 4977 0.3799999999999512 -0.3890012000990756 0 4978 0.3849999999999607 -0.3890012000990756 0 4979 0.3899999999999702 -0.3890012000990756 0 4980 0.3949999999999851 -0.3890012000990756 0 4981 0.4159009150359436 -0.3180974798108415 0 4982 0.4325968757860914 -0.3180974798108416 0 4983 0.4501276345236666 -0.3180974798108416 0 4984 0.4685349312246211 -0.3180974798108415 0 4985 0.4878625928296997 -0.3180974798108416 0 4986 0.5081566374754982 -0.3180974798108416 0 4987 0.5294653843060497 -0.3180974798108416 0 4988 0.5518395685067007 -0.3180974798108416 0 4989 0.5753324619453917 -0.3180974798108416 0 4990 0.4159009150359434 -0.3370998336061459 0 4991 0.4325968757860914 -0.3370998336061457 0 4992 0.4501276345236666 -0.3370998336061457 0 4993 0.4685349312246211 -0.3370998336061458 0 4994 0.4878625928296997 -0.3370998336061458 0 4995 0.5081566374754982 -0.3370998336061458 0 4996 0.5294653843060497 -0.3370998336061458 0 4997 0.5518395685067007 -0.3370998336061458 0 4998 0.5753324619453916 -0.3370998336061458 0 4999 0.4159009150359435 -0.3570523051187843 0 5000 0.4325968757860914 -0.3570523051187844 0 5001 0.4501276345236665 -0.3570523051187844 0 5002 0.4685349312246211 -0.3570523051187844 0 5003 0.4878625928296997 -0.3570523051187844 0 5004 0.5081566374754983 -0.3570523051187844 0 5005 0.5294653843060497 -0.3570523051187844 0 5006 0.5518395685067007 -0.3570523051187844 0 5007 0.5753324619453916 -0.3570523051187844 0 5008 0.4159009150359435 -0.3780024001981512 0 5009 0.4325968757860914 -0.3780024001981511 0 5010 0.4501276345236666 -0.3780024001981511 0 5011 0.468534931224621 -0.3780024001981511 0 5012 0.4878625928296997 -0.3780024001981511 0 5013 0.5081566374754983 -0.3780024001981511 0 5014 0.5294653843060497 -0.3780024001981511 0 5015 0.5518395685067007 -0.3780024001981511 0 5016 0.5753324619453916 -0.3780024001981511 0 5017 0.4079504575179718 -0.3090487399054208 0 5018 0.4079504575179718 -0.3180974798108416 0 5019 0.4159009150359435 -0.3090487399054208 0 5020 0.4242488954110175 -0.3090487399054208 0 5021 0.4242488954110175 -0.3180974798108416 0 5022 0.4325968757860914 -0.3090487399054208 0 5023 0.441362255154879 -0.3090487399054208 0 5024 0.441362255154879 -0.3180974798108416 0 5025 0.4501276345236666 -0.3090487399054208 0 5026 0.4593312828741438 -0.3090487399054208 0 5027 0.4593312828741438 -0.3180974798108416 0 5028 0.4685349312246211 -0.3090487399054208 0 5029 0.4781987620271604 -0.3090487399054208 0 5030 0.4781987620271604 -0.3180974798108416 0 5031 0.4878625928296997 -0.3090487399054208 0 5032 0.498009615152599 -0.3090487399054208 0 5033 0.498009615152599 -0.3180974798108416 0 5034 0.5081566374754982 -0.3090487399054208 0 5035 0.518811010890774 -0.3090487399054208 0 5036 0.518811010890774 -0.3180974798108416 0 5037 0.5294653843060497 -0.3090487399054208 0 5038 0.5406524764063752 -0.3090487399054208 0 5039 0.5406524764063752 -0.3180974798108416 0 5040 0.5518395685067007 -0.3090487399054208 0 5041 0.5635860152260461 -0.3090487399054208 0 5042 0.5635860152260461 -0.3180974798108416 0 5043 0.5753324619453917 -0.3090487399054208 0 5044 0.5876662309726959 -0.3090487399054208 0 5045 0.5876662309726959 -0.3180974798108416 0 5046 0.4079504575179718 -0.3275986567084936 0 5047 0.4079504575179717 -0.3370998336061458 0 5048 0.4159009150359435 -0.3275986567084937 0 5049 0.4242488954110174 -0.3275986567084938 0 5050 0.4242488954110174 -0.3370998336061458 0 5051 0.4325968757860914 -0.3275986567084936 0 5052 0.441362255154879 -0.3275986567084936 0 5053 0.441362255154879 -0.3370998336061457 0 5054 0.4501276345236666 -0.3275986567084936 0 5055 0.4593312828741438 -0.3275986567084936 0 5056 0.4593312828741438 -0.3370998336061458 0 5057 0.4685349312246211 -0.3275986567084936 0 5058 0.4781987620271604 -0.3275986567084937 0 5059 0.4781987620271604 -0.3370998336061458 0 5060 0.4878625928296997 -0.3275986567084937 0 5061 0.498009615152599 -0.3275986567084937 0 5062 0.498009615152599 -0.3370998336061458 0 5063 0.5081566374754982 -0.3275986567084937 0 5064 0.518811010890774 -0.3275986567084937 0 5065 0.518811010890774 -0.3370998336061458 0 5066 0.5294653843060497 -0.3275986567084937 0 5067 0.5406524764063752 -0.3275986567084937 0 5068 0.5406524764063752 -0.3370998336061458 0 5069 0.5518395685067007 -0.3275986567084937 0 5070 0.5635860152260461 -0.3275986567084937 0 5071 0.5635860152260461 -0.3370998336061458 0 5072 0.5753324619453917 -0.3275986567084937 0 5073 0.5876662309726958 -0.3275986567084937 0 5074 0.5876662309726958 -0.3370998336061458 0 5075 0.4079504575179717 -0.3470760693624652 0 5076 0.4079504575179718 -0.3570523051187844 0 5077 0.4159009150359434 -0.3470760693624651 0 5078 0.4242488954110174 -0.3470760693624651 0 5079 0.4242488954110174 -0.3570523051187844 0 5080 0.4325968757860914 -0.3470760693624651 0 5081 0.441362255154879 -0.3470760693624651 0 5082 0.4413622551548789 -0.3570523051187844 0 5083 0.4501276345236666 -0.3470760693624651 0 5084 0.4593312828741438 -0.3470760693624651 0 5085 0.4593312828741438 -0.3570523051187844 0 5086 0.4685349312246211 -0.3470760693624651 0 5087 0.4781987620271604 -0.3470760693624651 0 5088 0.4781987620271604 -0.3570523051187844 0 5089 0.4878625928296997 -0.3470760693624651 0 5090 0.498009615152599 -0.3470760693624651 0 5091 0.498009615152599 -0.3570523051187844 0 5092 0.5081566374754982 -0.3470760693624651 0 5093 0.518811010890774 -0.3470760693624651 0 5094 0.518811010890774 -0.3570523051187844 0 5095 0.5294653843060497 -0.3470760693624651 0 5096 0.5406524764063752 -0.3470760693624651 0 5097 0.5406524764063752 -0.3570523051187844 0 5098 0.5518395685067007 -0.3470760693624651 0 5099 0.5635860152260461 -0.3470760693624651 0 5100 0.5635860152260461 -0.3570523051187844 0 5101 0.5753324619453916 -0.3470760693624651 0 5102 0.5876662309726958 -0.3470760693624651 0 5103 0.5876662309726958 -0.3570523051187844 0 5104 0.4079504575179718 -0.3675273526584677 0 5105 0.4079504575179718 -0.3780024001981511 0 5106 0.4159009150359435 -0.3675273526584678 0 5107 0.4242488954110175 -0.3675273526584678 0 5108 0.4242488954110175 -0.3780024001981511 0 5109 0.4325968757860914 -0.3675273526584678 0 5110 0.4413622551548789 -0.3675273526584678 0 5111 0.441362255154879 -0.3780024001981511 0 5112 0.4501276345236666 -0.3675273526584678 0 5113 0.4593312828741438 -0.3675273526584678 0 5114 0.4593312828741438 -0.3780024001981511 0 5115 0.468534931224621 -0.3675273526584678 0 5116 0.4781987620271603 -0.3675273526584678 0 5117 0.4781987620271603 -0.3780024001981511 0 5118 0.4878625928296997 -0.3675273526584678 0 5119 0.498009615152599 -0.3675273526584678 0 5120 0.498009615152599 -0.3780024001981511 0 5121 0.5081566374754983 -0.3675273526584678 0 5122 0.518811010890774 -0.3675273526584678 0 5123 0.518811010890774 -0.3780024001981511 0 5124 0.5294653843060497 -0.3675273526584678 0 5125 0.5406524764063752 -0.3675273526584678 0 5126 0.5406524764063752 -0.3780024001981511 0 5127 0.5518395685067007 -0.3675273526584678 0 5128 0.5635860152260461 -0.3675273526584678 0 5129 0.5635860152260461 -0.3780024001981511 0 5130 0.5753324619453916 -0.3675273526584678 0 5131 0.5876662309726958 -0.3675273526584678 0 5132 0.5876662309726958 -0.3780024001981511 0 5133 0.4079504575179718 -0.3890012000990756 0 5134 0.4159009150359435 -0.3890012000990756 0 5135 0.4242488954110174 -0.3890012000990756 0 5136 0.4325968757860914 -0.3890012000990756 0 5137 0.441362255154879 -0.3890012000990756 0 5138 0.4501276345236666 -0.3890012000990756 0 5139 0.4593312828741438 -0.3890012000990756 0 5140 0.4685349312246211 -0.3890012000990756 0 5141 0.4781987620271604 -0.3890012000990756 0 5142 0.4878625928296997 -0.3890012000990756 0 5143 0.498009615152599 -0.3890012000990756 0 5144 0.5081566374754983 -0.3890012000990756 0 5145 0.518811010890774 -0.3890012000990756 0 5146 0.5294653843060497 -0.3890012000990756 0 5147 0.5406524764063752 -0.3890012000990756 0 5148 0.5518395685067007 -0.3890012000990756 0 5149 0.5635860152260461 -0.3890012000990756 0 5150 0.5753324619453916 -0.3890012000990756 0 5151 0.5876662309726958 -0.3890012000990756 0 $EndNodes $Elements 2734 1 15 2 0 1 1 2 15 2 0 2 2 3 15 2 0 3 3 4 15 2 0 4 4 5 15 2 0 5 5 6 15 2 0 6 6 7 15 2 0 7 7 8 15 2 0 8 8 9 15 2 0 9 9 10 8 2 0 1 1 10 49 11 8 2 0 1 10 11 50 12 8 2 0 1 11 12 51 13 8 2 0 1 12 13 52 14 8 2 0 1 13 14 53 15 8 2 0 1 14 15 54 16 8 2 0 1 15 16 55 17 8 2 0 1 16 17 56 18 8 2 0 1 17 18 57 19 8 2 0 1 18 19 58 20 8 2 0 1 19 20 59 21 8 2 0 1 20 21 60 22 8 2 0 1 21 22 61 23 8 2 0 1 22 23 62 24 8 2 0 1 23 24 63 25 8 2 0 1 24 25 64 26 8 2 0 1 25 26 65 27 8 2 0 1 26 27 66 28 8 2 0 1 27 28 67 29 8 2 0 1 28 29 68 30 8 2 0 1 29 30 69 31 8 2 0 1 30 31 70 32 8 2 0 1 31 32 71 33 8 2 0 1 32 33 72 34 8 2 0 1 33 34 73 35 8 2 0 1 34 35 74 36 8 2 0 1 35 36 75 37 8 2 0 1 36 37 76 38 8 2 0 1 37 38 77 39 8 2 0 1 38 39 78 40 8 2 0 1 39 40 79 41 8 2 0 1 40 41 80 42 8 2 0 1 41 42 81 43 8 2 0 1 42 43 82 44 8 2 0 1 43 44 83 45 8 2 0 1 44 45 84 46 8 2 0 1 45 46 85 47 8 2 0 1 46 47 86 48 8 2 0 1 47 48 87 49 8 2 0 1 48 2 88 50 8 2 0 2 2 89 98 51 8 2 0 2 89 90 99 52 8 2 0 2 90 91 100 53 8 2 0 2 91 92 101 54 8 2 0 2 92 93 102 55 8 2 0 2 93 94 103 56 8 2 0 2 94 95 104 57 8 2 0 2 95 96 105 58 8 2 0 2 96 97 106 59 8 2 0 2 97 3 107 60 8 2 0 3 8 108 147 61 8 2 0 3 108 109 148 62 8 2 0 3 109 110 149 63 8 2 0 3 110 111 150 64 8 2 0 3 111 112 151 65 8 2 0 3 112 113 152 66 8 2 0 3 113 114 153 67 8 2 0 3 114 115 154 68 8 2 0 3 115 116 155 69 8 2 0 3 116 117 156 70 8 2 0 3 117 118 157 71 8 2 0 3 118 119 158 72 8 2 0 3 119 120 159 73 8 2 0 3 120 121 160 74 8 2 0 3 121 122 161 75 8 2 0 3 122 123 162 76 8 2 0 3 123 124 163 77 8 2 0 3 124 125 164 78 8 2 0 3 125 126 165 79 8 2 0 3 126 127 166 80 8 2 0 3 127 128 167 81 8 2 0 3 128 129 168 82 8 2 0 3 129 130 169 83 8 2 0 3 130 131 170 84 8 2 0 3 131 132 171 85 8 2 0 3 132 133 172 86 8 2 0 3 133 134 173 87 8 2 0 3 134 135 174 88 8 2 0 3 135 136 175 89 8 2 0 3 136 137 176 90 8 2 0 3 137 138 177 91 8 2 0 3 138 139 178 92 8 2 0 3 139 140 179 93 8 2 0 3 140 141 180 94 8 2 0 3 141 142 181 95 8 2 0 3 142 143 182 96 8 2 0 3 143 144 183 97 8 2 0 3 144 145 184 98 8 2 0 3 145 146 185 99 8 2 0 3 146 9 186 100 8 2 0 4 9 187 196 101 8 2 0 4 187 188 197 102 8 2 0 4 188 189 198 103 8 2 0 4 189 190 199 104 8 2 0 4 190 191 200 105 8 2 0 4 191 192 201 106 8 2 0 4 192 193 202 107 8 2 0 4 193 194 203 108 8 2 0 4 194 195 204 109 8 2 0 4 195 4 205 110 8 2 0 5 7 206 245 111 8 2 0 5 206 207 246 112 8 2 0 5 207 208 247 113 8 2 0 5 208 209 248 114 8 2 0 5 209 210 249 115 8 2 0 5 210 211 250 116 8 2 0 5 211 212 251 117 8 2 0 5 212 213 252 118 8 2 0 5 213 214 253 119 8 2 0 5 214 215 254 120 8 2 0 5 215 216 255 121 8 2 0 5 216 217 256 122 8 2 0 5 217 218 257 123 8 2 0 5 218 219 258 124 8 2 0 5 219 220 259 125 8 2 0 5 220 221 260 126 8 2 0 5 221 222 261 127 8 2 0 5 222 223 262 128 8 2 0 5 223 224 263 129 8 2 0 5 224 225 264 130 8 2 0 5 225 226 265 131 8 2 0 5 226 227 266 132 8 2 0 5 227 228 267 133 8 2 0 5 228 229 268 134 8 2 0 5 229 230 269 135 8 2 0 5 230 231 270 136 8 2 0 5 231 232 271 137 8 2 0 5 232 233 272 138 8 2 0 5 233 234 273 139 8 2 0 5 234 235 274 140 8 2 0 5 235 236 275 141 8 2 0 5 236 237 276 142 8 2 0 5 237 238 277 143 8 2 0 5 238 239 278 144 8 2 0 5 239 240 279 145 8 2 0 5 240 241 280 146 8 2 0 5 241 242 281 147 8 2 0 5 242 243 282 148 8 2 0 5 243 244 283 149 8 2 0 5 244 6 284 150 8 2 0 6 6 285 294 151 8 2 0 6 285 286 295 152 8 2 0 6 286 287 296 153 8 2 0 6 287 288 297 154 8 2 0 6 288 289 298 155 8 2 0 6 289 290 299 156 8 2 0 6 290 291 300 157 8 2 0 6 291 292 301 158 8 2 0 6 292 293 302 159 8 2 0 6 293 5 303 160 8 2 0 7 7 304 308 161 8 2 0 7 304 305 309 162 8 2 0 7 305 306 310 163 8 2 0 7 306 307 311 164 8 2 0 7 307 8 312 165 8 2 0 8 8 313 332 166 8 2 0 8 313 314 333 167 8 2 0 8 314 315 334 168 8 2 0 8 315 316 335 169 8 2 0 8 316 317 336 170 8 2 0 8 317 318 337 171 8 2 0 8 318 319 338 172 8 2 0 8 319 320 339 173 8 2 0 8 320 321 340 174 8 2 0 8 321 322 341 175 8 2 0 8 322 323 342 176 8 2 0 8 323 324 343 177 8 2 0 8 324 325 344 178 8 2 0 8 325 326 345 179 8 2 0 8 326 327 346 180 8 2 0 8 327 328 347 181 8 2 0 8 328 329 348 182 8 2 0 8 329 330 349 183 8 2 0 8 330 331 350 184 8 2 0 8 331 1 351 185 8 2 0 9 6 352 356 186 8 2 0 9 352 353 357 187 8 2 0 9 353 354 358 188 8 2 0 9 354 355 359 189 8 2 0 9 355 9 360 190 8 2 0 10 9 361 380 191 8 2 0 10 361 362 381 192 8 2 0 10 362 363 382 193 8 2 0 10 363 364 383 194 8 2 0 10 364 365 384 195 8 2 0 10 365 366 385 196 8 2 0 10 366 367 386 197 8 2 0 10 367 368 387 198 8 2 0 10 368 369 388 199 8 2 0 10 369 370 389 200 8 2 0 10 370 371 390 201 8 2 0 10 371 372 391 202 8 2 0 10 372 373 392 203 8 2 0 10 373 374 393 204 8 2 0 10 374 375 394 205 8 2 0 10 375 376 395 206 8 2 0 10 376 377 396 207 8 2 0 10 377 378 397 208 8 2 0 10 378 379 398 209 8 2 0 10 379 2 399 210 8 2 0 11 5 400 404 211 8 2 0 11 400 401 405 212 8 2 0 11 401 402 406 213 8 2 0 11 402 403 407 214 8 2 0 11 403 4 408 215 8 2 0 12 4 409 428 216 8 2 0 12 409 410 429 217 8 2 0 12 410 411 430 218 8 2 0 12 411 412 431 219 8 2 0 12 412 413 432 220 8 2 0 12 413 414 433 221 8 2 0 12 414 415 434 222 8 2 0 12 415 416 435 223 8 2 0 12 416 417 436 224 8 2 0 12 417 418 437 225 8 2 0 12 418 419 438 226 8 2 0 12 419 420 439 227 8 2 0 12 420 421 440 228 8 2 0 12 421 422 441 229 8 2 0 12 422 423 442 230 8 2 0 12 423 424 443 231 8 2 0 12 424 425 444 232 8 2 0 12 425 426 445 233 8 2 0 12 426 427 446 234 8 2 0 12 427 3 447 235 9 2 0 1 1 331 10 351 1189 49 236 9 2 0 1 10 331 448 1189 1190 1191 237 9 2 0 1 10 448 11 1191 1192 50 238 9 2 0 1 11 448 449 1192 1193 1194 239 9 2 0 1 11 449 12 1194 1195 51 240 9 2 0 1 12 449 450 1195 1196 1197 241 9 2 0 1 12 450 13 1197 1198 52 242 9 2 0 1 13 450 451 1198 1199 1200 243 9 2 0 1 13 451 14 1200 1201 53 244 9 2 0 1 14 451 452 1201 1202 1203 245 9 2 0 1 14 452 15 1203 1204 54 246 9 2 0 1 15 452 453 1204 1205 1206 247 9 2 0 1 15 453 16 1206 1207 55 248 9 2 0 1 16 453 454 1207 1208 1209 249 9 2 0 1 16 454 17 1209 1210 56 250 9 2 0 1 17 454 455 1210 1211 1212 251 9 2 0 1 17 455 18 1212 1213 57 252 9 2 0 1 18 455 456 1213 1214 1215 253 9 2 0 1 18 456 19 1215 1216 58 254 9 2 0 1 19 456 457 1216 1217 1218 255 9 2 0 1 19 457 20 1218 1219 59 256 9 2 0 1 20 457 458 1219 1220 1221 257 9 2 0 1 20 458 21 1221 1222 60 258 9 2 0 1 21 458 459 1222 1223 1224 259 9 2 0 1 21 459 22 1224 1225 61 260 9 2 0 1 22 459 460 1225 1226 1227 261 9 2 0 1 22 460 23 1227 1228 62 262 9 2 0 1 23 460 461 1228 1229 1230 263 9 2 0 1 23 461 24 1230 1231 63 264 9 2 0 1 24 461 462 1231 1232 1233 265 9 2 0 1 24 462 25 1233 1234 64 266 9 2 0 1 25 462 463 1234 1235 1236 267 9 2 0 1 25 463 26 1236 1237 65 268 9 2 0 1 26 463 464 1237 1238 1239 269 9 2 0 1 26 464 27 1239 1240 66 270 9 2 0 1 27 464 465 1240 1241 1242 271 9 2 0 1 27 465 28 1242 1243 67 272 9 2 0 1 28 465 466 1243 1244 1245 273 9 2 0 1 28 466 29 1245 1246 68 274 9 2 0 1 29 466 467 1246 1247 1248 275 9 2 0 1 29 467 30 1248 1249 69 276 9 2 0 1 30 467 468 1249 1250 1251 277 9 2 0 1 30 468 31 1251 1252 70 278 9 2 0 1 31 468 469 1252 1253 1254 279 9 2 0 1 31 469 32 1254 1255 71 280 9 2 0 1 32 469 470 1255 1256 1257 281 9 2 0 1 32 470 33 1257 1258 72 282 9 2 0 1 33 470 471 1258 1259 1260 283 9 2 0 1 33 471 34 1260 1261 73 284 9 2 0 1 34 471 472 1261 1262 1263 285 9 2 0 1 34 472 35 1263 1264 74 286 9 2 0 1 35 472 473 1264 1265 1266 287 9 2 0 1 35 473 36 1266 1267 75 288 9 2 0 1 36 473 474 1267 1268 1269 289 9 2 0 1 36 474 37 1269 1270 76 290 9 2 0 1 37 474 475 1270 1271 1272 291 9 2 0 1 37 475 38 1272 1273 77 292 9 2 0 1 38 475 476 1273 1274 1275 293 9 2 0 1 38 476 39 1275 1276 78 294 9 2 0 1 39 476 477 1276 1277 1278 295 9 2 0 1 39 477 40 1278 1279 79 296 9 2 0 1 40 477 478 1279 1280 1281 297 9 2 0 1 40 478 41 1281 1282 80 298 9 2 0 1 41 478 479 1282 1283 1284 299 9 2 0 1 41 479 42 1284 1285 81 300 9 2 0 1 42 479 480 1285 1286 1287 301 9 2 0 1 42 480 43 1287 1288 82 302 9 2 0 1 43 480 481 1288 1289 1290 303 9 2 0 1 43 481 44 1290 1291 83 304 9 2 0 1 44 481 482 1291 1292 1293 305 9 2 0 1 44 482 45 1293 1294 84 306 9 2 0 1 45 482 483 1294 1295 1296 307 9 2 0 1 45 483 46 1296 1297 85 308 9 2 0 1 46 483 484 1297 1298 1299 309 9 2 0 1 46 484 47 1299 1300 86 310 9 2 0 1 47 484 485 1300 1301 1302 311 9 2 0 1 47 485 48 1302 1303 87 312 9 2 0 1 48 485 486 1303 1304 1305 313 9 2 0 1 48 486 2 1305 1306 88 314 9 2 0 1 2 486 379 1306 1307 399 315 9 2 0 1 331 330 448 350 1308 1190 316 9 2 0 1 448 330 487 1308 1309 1310 317 9 2 0 1 448 487 449 1310 1311 1193 318 9 2 0 1 449 487 488 1311 1312 1313 319 9 2 0 1 449 488 450 1313 1314 1196 320 9 2 0 1 450 488 489 1314 1315 1316 321 9 2 0 1 450 489 451 1316 1317 1199 322 9 2 0 1 451 489 490 1317 1318 1319 323 9 2 0 1 451 490 452 1319 1320 1202 324 9 2 0 1 452 490 491 1320 1321 1322 325 9 2 0 1 452 491 453 1322 1323 1205 326 9 2 0 1 453 491 492 1323 1324 1325 327 9 2 0 1 453 492 454 1325 1326 1208 328 9 2 0 1 454 492 493 1326 1327 1328 329 9 2 0 1 454 493 455 1328 1329 1211 330 9 2 0 1 455 493 494 1329 1330 1331 331 9 2 0 1 455 494 456 1331 1332 1214 332 9 2 0 1 456 494 495 1332 1333 1334 333 9 2 0 1 456 495 457 1334 1335 1217 334 9 2 0 1 457 495 496 1335 1336 1337 335 9 2 0 1 457 496 458 1337 1338 1220 336 9 2 0 1 458 496 497 1338 1339 1340 337 9 2 0 1 458 497 459 1340 1341 1223 338 9 2 0 1 459 497 498 1341 1342 1343 339 9 2 0 1 459 498 460 1343 1344 1226 340 9 2 0 1 460 498 499 1344 1345 1346 341 9 2 0 1 460 499 461 1346 1347 1229 342 9 2 0 1 461 499 500 1347 1348 1349 343 9 2 0 1 461 500 462 1349 1350 1232 344 9 2 0 1 462 500 501 1350 1351 1352 345 9 2 0 1 462 501 463 1352 1353 1235 346 9 2 0 1 463 501 502 1353 1354 1355 347 9 2 0 1 463 502 464 1355 1356 1238 348 9 2 0 1 464 502 503 1356 1357 1358 349 9 2 0 1 464 503 465 1358 1359 1241 350 9 2 0 1 465 503 504 1359 1360 1361 351 9 2 0 1 465 504 466 1361 1362 1244 352 9 2 0 1 466 504 505 1362 1363 1364 353 9 2 0 1 466 505 467 1364 1365 1247 354 9 2 0 1 467 505 506 1365 1366 1367 355 9 2 0 1 467 506 468 1367 1368 1250 356 9 2 0 1 468 506 507 1368 1369 1370 357 9 2 0 1 468 507 469 1370 1371 1253 358 9 2 0 1 469 507 508 1371 1372 1373 359 9 2 0 1 469 508 470 1373 1374 1256 360 9 2 0 1 470 508 509 1374 1375 1376 361 9 2 0 1 470 509 471 1376 1377 1259 362 9 2 0 1 471 509 510 1377 1378 1379 363 9 2 0 1 471 510 472 1379 1380 1262 364 9 2 0 1 472 510 511 1380 1381 1382 365 9 2 0 1 472 511 473 1382 1383 1265 366 9 2 0 1 473 511 512 1383 1384 1385 367 9 2 0 1 473 512 474 1385 1386 1268 368 9 2 0 1 474 512 513 1386 1387 1388 369 9 2 0 1 474 513 475 1388 1389 1271 370 9 2 0 1 475 513 514 1389 1390 1391 371 9 2 0 1 475 514 476 1391 1392 1274 372 9 2 0 1 476 514 515 1392 1393 1394 373 9 2 0 1 476 515 477 1394 1395 1277 374 9 2 0 1 477 515 516 1395 1396 1397 375 9 2 0 1 477 516 478 1397 1398 1280 376 9 2 0 1 478 516 517 1398 1399 1400 377 9 2 0 1 478 517 479 1400 1401 1283 378 9 2 0 1 479 517 518 1401 1402 1403 379 9 2 0 1 479 518 480 1403 1404 1286 380 9 2 0 1 480 518 519 1404 1405 1406 381 9 2 0 1 480 519 481 1406 1407 1289 382 9 2 0 1 481 519 520 1407 1408 1409 383 9 2 0 1 481 520 482 1409 1410 1292 384 9 2 0 1 482 520 521 1410 1411 1412 385 9 2 0 1 482 521 483 1412 1413 1295 386 9 2 0 1 483 521 522 1413 1414 1415 387 9 2 0 1 483 522 484 1415 1416 1298 388 9 2 0 1 484 522 523 1416 1417 1418 389 9 2 0 1 484 523 485 1418 1419 1301 390 9 2 0 1 485 523 524 1419 1420 1421 391 9 2 0 1 485 524 486 1421 1422 1304 392 9 2 0 1 486 524 525 1422 1423 1424 393 9 2 0 1 486 525 379 1424 1425 1307 394 9 2 0 1 379 525 378 1425 1426 398 395 9 2 0 1 330 329 487 349 1427 1309 396 9 2 0 1 487 329 526 1427 1428 1429 397 9 2 0 1 487 526 488 1429 1430 1312 398 9 2 0 1 488 526 527 1430 1431 1432 399 9 2 0 1 488 527 489 1432 1433 1315 400 9 2 0 1 489 527 528 1433 1434 1435 401 9 2 0 1 489 528 490 1435 1436 1318 402 9 2 0 1 490 528 529 1436 1437 1438 403 9 2 0 1 490 529 491 1438 1439 1321 404 9 2 0 1 491 529 530 1439 1440 1441 405 9 2 0 1 491 530 492 1441 1442 1324 406 9 2 0 1 492 530 531 1442 1443 1444 407 9 2 0 1 492 531 493 1444 1445 1327 408 9 2 0 1 493 531 532 1445 1446 1447 409 9 2 0 1 493 532 494 1447 1448 1330 410 9 2 0 1 494 532 533 1448 1449 1450 411 9 2 0 1 494 533 495 1450 1451 1333 412 9 2 0 1 495 533 534 1451 1452 1453 413 9 2 0 1 495 534 496 1453 1454 1336 414 9 2 0 1 496 534 535 1454 1455 1456 415 9 2 0 1 496 535 497 1456 1457 1339 416 9 2 0 1 497 535 536 1457 1458 1459 417 9 2 0 1 497 536 498 1459 1460 1342 418 9 2 0 1 498 536 537 1460 1461 1462 419 9 2 0 1 498 537 499 1462 1463 1345 420 9 2 0 1 499 537 538 1463 1464 1465 421 9 2 0 1 499 538 500 1465 1466 1348 422 9 2 0 1 500 538 539 1466 1467 1468 423 9 2 0 1 500 539 501 1468 1469 1351 424 9 2 0 1 501 539 540 1469 1470 1471 425 9 2 0 1 501 540 502 1471 1472 1354 426 9 2 0 1 502 540 541 1472 1473 1474 427 9 2 0 1 502 541 503 1474 1475 1357 428 9 2 0 1 503 541 542 1475 1476 1477 429 9 2 0 1 503 542 504 1477 1478 1360 430 9 2 0 1 504 542 543 1478 1479 1480 431 9 2 0 1 504 543 505 1480 1481 1363 432 9 2 0 1 505 543 544 1481 1482 1483 433 9 2 0 1 505 544 506 1483 1484 1366 434 9 2 0 1 506 544 545 1484 1485 1486 435 9 2 0 1 506 545 507 1486 1487 1369 436 9 2 0 1 507 545 546 1487 1488 1489 437 9 2 0 1 507 546 508 1489 1490 1372 438 9 2 0 1 508 546 547 1490 1491 1492 439 9 2 0 1 508 547 509 1492 1493 1375 440 9 2 0 1 509 547 548 1493 1494 1495 441 9 2 0 1 509 548 510 1495 1496 1378 442 9 2 0 1 510 548 549 1496 1497 1498 443 9 2 0 1 510 549 511 1498 1499 1381 444 9 2 0 1 511 549 550 1499 1500 1501 445 9 2 0 1 511 550 512 1501 1502 1384 446 9 2 0 1 512 550 551 1502 1503 1504 447 9 2 0 1 512 551 513 1504 1505 1387 448 9 2 0 1 513 551 552 1505 1506 1507 449 9 2 0 1 513 552 514 1507 1508 1390 450 9 2 0 1 514 552 553 1508 1509 1510 451 9 2 0 1 514 553 515 1510 1511 1393 452 9 2 0 1 515 553 554 1511 1512 1513 453 9 2 0 1 515 554 516 1513 1514 1396 454 9 2 0 1 516 554 555 1514 1515 1516 455 9 2 0 1 516 555 517 1516 1517 1399 456 9 2 0 1 517 555 556 1517 1518 1519 457 9 2 0 1 517 556 518 1519 1520 1402 458 9 2 0 1 518 556 557 1520 1521 1522 459 9 2 0 1 518 557 519 1522 1523 1405 460 9 2 0 1 519 557 558 1523 1524 1525 461 9 2 0 1 519 558 520 1525 1526 1408 462 9 2 0 1 520 558 559 1526 1527 1528 463 9 2 0 1 520 559 521 1528 1529 1411 464 9 2 0 1 521 559 560 1529 1530 1531 465 9 2 0 1 521 560 522 1531 1532 1414 466 9 2 0 1 522 560 561 1532 1533 1534 467 9 2 0 1 522 561 523 1534 1535 1417 468 9 2 0 1 523 561 562 1535 1536 1537 469 9 2 0 1 523 562 524 1537 1538 1420 470 9 2 0 1 524 562 563 1538 1539 1540 471 9 2 0 1 524 563 525 1540 1541 1423 472 9 2 0 1 525 563 564 1541 1542 1543 473 9 2 0 1 525 564 378 1543 1544 1426 474 9 2 0 1 378 564 377 1544 1545 397 475 9 2 0 1 329 328 526 348 1546 1428 476 9 2 0 1 526 328 565 1546 1547 1548 477 9 2 0 1 526 565 527 1548 1549 1431 478 9 2 0 1 527 565 566 1549 1550 1551 479 9 2 0 1 527 566 528 1551 1552 1434 480 9 2 0 1 528 566 567 1552 1553 1554 481 9 2 0 1 528 567 529 1554 1555 1437 482 9 2 0 1 529 567 568 1555 1556 1557 483 9 2 0 1 529 568 530 1557 1558 1440 484 9 2 0 1 530 568 569 1558 1559 1560 485 9 2 0 1 530 569 531 1560 1561 1443 486 9 2 0 1 531 569 570 1561 1562 1563 487 9 2 0 1 531 570 532 1563 1564 1446 488 9 2 0 1 532 570 571 1564 1565 1566 489 9 2 0 1 532 571 533 1566 1567 1449 490 9 2 0 1 533 571 572 1567 1568 1569 491 9 2 0 1 533 572 534 1569 1570 1452 492 9 2 0 1 534 572 573 1570 1571 1572 493 9 2 0 1 534 573 535 1572 1573 1455 494 9 2 0 1 535 573 574 1573 1574 1575 495 9 2 0 1 535 574 536 1575 1576 1458 496 9 2 0 1 536 574 575 1576 1577 1578 497 9 2 0 1 536 575 537 1578 1579 1461 498 9 2 0 1 537 575 576 1579 1580 1581 499 9 2 0 1 537 576 538 1581 1582 1464 500 9 2 0 1 538 576 577 1582 1583 1584 501 9 2 0 1 538 577 539 1584 1585 1467 502 9 2 0 1 539 577 578 1585 1586 1587 503 9 2 0 1 539 578 540 1587 1588 1470 504 9 2 0 1 540 578 579 1588 1589 1590 505 9 2 0 1 540 579 541 1590 1591 1473 506 9 2 0 1 541 579 580 1591 1592 1593 507 9 2 0 1 541 580 542 1593 1594 1476 508 9 2 0 1 542 580 581 1594 1595 1596 509 9 2 0 1 542 581 543 1596 1597 1479 510 9 2 0 1 543 581 582 1597 1598 1599 511 9 2 0 1 543 582 544 1599 1600 1482 512 9 2 0 1 544 582 583 1600 1601 1602 513 9 2 0 1 544 583 545 1602 1603 1485 514 9 2 0 1 545 583 584 1603 1604 1605 515 9 2 0 1 545 584 546 1605 1606 1488 516 9 2 0 1 546 584 585 1606 1607 1608 517 9 2 0 1 546 585 547 1608 1609 1491 518 9 2 0 1 547 585 586 1609 1610 1611 519 9 2 0 1 547 586 548 1611 1612 1494 520 9 2 0 1 548 586 587 1612 1613 1614 521 9 2 0 1 548 587 549 1614 1615 1497 522 9 2 0 1 549 587 588 1615 1616 1617 523 9 2 0 1 549 588 550 1617 1618 1500 524 9 2 0 1 550 588 589 1618 1619 1620 525 9 2 0 1 550 589 551 1620 1621 1503 526 9 2 0 1 551 589 590 1621 1622 1623 527 9 2 0 1 551 590 552 1623 1624 1506 528 9 2 0 1 552 590 591 1624 1625 1626 529 9 2 0 1 552 591 553 1626 1627 1509 530 9 2 0 1 553 591 592 1627 1628 1629 531 9 2 0 1 553 592 554 1629 1630 1512 532 9 2 0 1 554 592 593 1630 1631 1632 533 9 2 0 1 554 593 555 1632 1633 1515 534 9 2 0 1 555 593 594 1633 1634 1635 535 9 2 0 1 555 594 556 1635 1636 1518 536 9 2 0 1 556 594 595 1636 1637 1638 537 9 2 0 1 556 595 557 1638 1639 1521 538 9 2 0 1 557 595 596 1639 1640 1641 539 9 2 0 1 557 596 558 1641 1642 1524 540 9 2 0 1 558 596 597 1642 1643 1644 541 9 2 0 1 558 597 559 1644 1645 1527 542 9 2 0 1 559 597 598 1645 1646 1647 543 9 2 0 1 559 598 560 1647 1648 1530 544 9 2 0 1 560 598 599 1648 1649 1650 545 9 2 0 1 560 599 561 1650 1651 1533 546 9 2 0 1 561 599 600 1651 1652 1653 547 9 2 0 1 561 600 562 1653 1654 1536 548 9 2 0 1 562 600 601 1654 1655 1656 549 9 2 0 1 562 601 563 1656 1657 1539 550 9 2 0 1 563 601 602 1657 1658 1659 551 9 2 0 1 563 602 564 1659 1660 1542 552 9 2 0 1 564 602 603 1660 1661 1662 553 9 2 0 1 564 603 377 1662 1663 1545 554 9 2 0 1 377 603 376 1663 1664 396 555 9 2 0 1 328 327 565 347 1665 1547 556 9 2 0 1 565 327 604 1665 1666 1667 557 9 2 0 1 565 604 566 1667 1668 1550 558 9 2 0 1 566 604 605 1668 1669 1670 559 9 2 0 1 566 605 567 1670 1671 1553 560 9 2 0 1 567 605 606 1671 1672 1673 561 9 2 0 1 567 606 568 1673 1674 1556 562 9 2 0 1 568 606 607 1674 1675 1676 563 9 2 0 1 568 607 569 1676 1677 1559 564 9 2 0 1 569 607 608 1677 1678 1679 565 9 2 0 1 569 608 570 1679 1680 1562 566 9 2 0 1 570 608 609 1680 1681 1682 567 9 2 0 1 570 609 571 1682 1683 1565 568 9 2 0 1 571 609 610 1683 1684 1685 569 9 2 0 1 571 610 572 1685 1686 1568 570 9 2 0 1 572 610 611 1686 1687 1688 571 9 2 0 1 572 611 573 1688 1689 1571 572 9 2 0 1 573 611 612 1689 1690 1691 573 9 2 0 1 573 612 574 1691 1692 1574 574 9 2 0 1 574 612 613 1692 1693 1694 575 9 2 0 1 574 613 575 1694 1695 1577 576 9 2 0 1 575 613 614 1695 1696 1697 577 9 2 0 1 575 614 576 1697 1698 1580 578 9 2 0 1 576 614 615 1698 1699 1700 579 9 2 0 1 576 615 577 1700 1701 1583 580 9 2 0 1 577 615 616 1701 1702 1703 581 9 2 0 1 577 616 578 1703 1704 1586 582 9 2 0 1 578 616 617 1704 1705 1706 583 9 2 0 1 578 617 579 1706 1707 1589 584 9 2 0 1 579 617 618 1707 1708 1709 585 9 2 0 1 579 618 580 1709 1710 1592 586 9 2 0 1 580 618 619 1710 1711 1712 587 9 2 0 1 580 619 581 1712 1713 1595 588 9 2 0 1 581 619 620 1713 1714 1715 589 9 2 0 1 581 620 582 1715 1716 1598 590 9 2 0 1 582 620 621 1716 1717 1718 591 9 2 0 1 582 621 583 1718 1719 1601 592 9 2 0 1 583 621 622 1719 1720 1721 593 9 2 0 1 583 622 584 1721 1722 1604 594 9 2 0 1 584 622 623 1722 1723 1724 595 9 2 0 1 584 623 585 1724 1725 1607 596 9 2 0 1 585 623 624 1725 1726 1727 597 9 2 0 1 585 624 586 1727 1728 1610 598 9 2 0 1 586 624 625 1728 1729 1730 599 9 2 0 1 586 625 587 1730 1731 1613 600 9 2 0 1 587 625 626 1731 1732 1733 601 9 2 0 1 587 626 588 1733 1734 1616 602 9 2 0 1 588 626 627 1734 1735 1736 603 9 2 0 1 588 627 589 1736 1737 1619 604 9 2 0 1 589 627 628 1737 1738 1739 605 9 2 0 1 589 628 590 1739 1740 1622 606 9 2 0 1 590 628 629 1740 1741 1742 607 9 2 0 1 590 629 591 1742 1743 1625 608 9 2 0 1 591 629 630 1743 1744 1745 609 9 2 0 1 591 630 592 1745 1746 1628 610 9 2 0 1 592 630 631 1746 1747 1748 611 9 2 0 1 592 631 593 1748 1749 1631 612 9 2 0 1 593 631 632 1749 1750 1751 613 9 2 0 1 593 632 594 1751 1752 1634 614 9 2 0 1 594 632 633 1752 1753 1754 615 9 2 0 1 594 633 595 1754 1755 1637 616 9 2 0 1 595 633 634 1755 1756 1757 617 9 2 0 1 595 634 596 1757 1758 1640 618 9 2 0 1 596 634 635 1758 1759 1760 619 9 2 0 1 596 635 597 1760 1761 1643 620 9 2 0 1 597 635 636 1761 1762 1763 621 9 2 0 1 597 636 598 1763 1764 1646 622 9 2 0 1 598 636 637 1764 1765 1766 623 9 2 0 1 598 637 599 1766 1767 1649 624 9 2 0 1 599 637 638 1767 1768 1769 625 9 2 0 1 599 638 600 1769 1770 1652 626 9 2 0 1 600 638 639 1770 1771 1772 627 9 2 0 1 600 639 601 1772 1773 1655 628 9 2 0 1 601 639 640 1773 1774 1775 629 9 2 0 1 601 640 602 1775 1776 1658 630 9 2 0 1 602 640 641 1776 1777 1778 631 9 2 0 1 602 641 603 1778 1779 1661 632 9 2 0 1 603 641 642 1779 1780 1781 633 9 2 0 1 603 642 376 1781 1782 1664 634 9 2 0 1 376 642 375 1782 1783 395 635 9 2 0 1 327 326 604 346 1784 1666 636 9 2 0 1 604 326 643 1784 1785 1786 637 9 2 0 1 604 643 605 1786 1787 1669 638 9 2 0 1 605 643 644 1787 1788 1789 639 9 2 0 1 605 644 606 1789 1790 1672 640 9 2 0 1 606 644 645 1790 1791 1792 641 9 2 0 1 606 645 607 1792 1793 1675 642 9 2 0 1 607 645 646 1793 1794 1795 643 9 2 0 1 607 646 608 1795 1796 1678 644 9 2 0 1 608 646 647 1796 1797 1798 645 9 2 0 1 608 647 609 1798 1799 1681 646 9 2 0 1 609 647 648 1799 1800 1801 647 9 2 0 1 609 648 610 1801 1802 1684 648 9 2 0 1 610 648 649 1802 1803 1804 649 9 2 0 1 610 649 611 1804 1805 1687 650 9 2 0 1 611 649 650 1805 1806 1807 651 9 2 0 1 611 650 612 1807 1808 1690 652 9 2 0 1 612 650 651 1808 1809 1810 653 9 2 0 1 612 651 613 1810 1811 1693 654 9 2 0 1 613 651 652 1811 1812 1813 655 9 2 0 1 613 652 614 1813 1814 1696 656 9 2 0 1 614 652 653 1814 1815 1816 657 9 2 0 1 614 653 615 1816 1817 1699 658 9 2 0 1 615 653 654 1817 1818 1819 659 9 2 0 1 615 654 616 1819 1820 1702 660 9 2 0 1 616 654 655 1820 1821 1822 661 9 2 0 1 616 655 617 1822 1823 1705 662 9 2 0 1 617 655 656 1823 1824 1825 663 9 2 0 1 617 656 618 1825 1826 1708 664 9 2 0 1 618 656 657 1826 1827 1828 665 9 2 0 1 618 657 619 1828 1829 1711 666 9 2 0 1 619 657 658 1829 1830 1831 667 9 2 0 1 619 658 620 1831 1832 1714 668 9 2 0 1 620 658 659 1832 1833 1834 669 9 2 0 1 620 659 621 1834 1835 1717 670 9 2 0 1 621 659 660 1835 1836 1837 671 9 2 0 1 621 660 622 1837 1838 1720 672 9 2 0 1 622 660 661 1838 1839 1840 673 9 2 0 1 622 661 623 1840 1841 1723 674 9 2 0 1 623 661 662 1841 1842 1843 675 9 2 0 1 623 662 624 1843 1844 1726 676 9 2 0 1 624 662 663 1844 1845 1846 677 9 2 0 1 624 663 625 1846 1847 1729 678 9 2 0 1 625 663 664 1847 1848 1849 679 9 2 0 1 625 664 626 1849 1850 1732 680 9 2 0 1 626 664 665 1850 1851 1852 681 9 2 0 1 626 665 627 1852 1853 1735 682 9 2 0 1 627 665 666 1853 1854 1855 683 9 2 0 1 627 666 628 1855 1856 1738 684 9 2 0 1 628 666 667 1856 1857 1858 685 9 2 0 1 628 667 629 1858 1859 1741 686 9 2 0 1 629 667 668 1859 1860 1861 687 9 2 0 1 629 668 630 1861 1862 1744 688 9 2 0 1 630 668 669 1862 1863 1864 689 9 2 0 1 630 669 631 1864 1865 1747 690 9 2 0 1 631 669 670 1865 1866 1867 691 9 2 0 1 631 670 632 1867 1868 1750 692 9 2 0 1 632 670 671 1868 1869 1870 693 9 2 0 1 632 671 633 1870 1871 1753 694 9 2 0 1 633 671 672 1871 1872 1873 695 9 2 0 1 633 672 634 1873 1874 1756 696 9 2 0 1 634 672 673 1874 1875 1876 697 9 2 0 1 634 673 635 1876 1877 1759 698 9 2 0 1 635 673 674 1877 1878 1879 699 9 2 0 1 635 674 636 1879 1880 1762 700 9 2 0 1 636 674 675 1880 1881 1882 701 9 2 0 1 636 675 637 1882 1883 1765 702 9 2 0 1 637 675 676 1883 1884 1885 703 9 2 0 1 637 676 638 1885 1886 1768 704 9 2 0 1 638 676 677 1886 1887 1888 705 9 2 0 1 638 677 639 1888 1889 1771 706 9 2 0 1 639 677 678 1889 1890 1891 707 9 2 0 1 639 678 640 1891 1892 1774 708 9 2 0 1 640 678 679 1892 1893 1894 709 9 2 0 1 640 679 641 1894 1895 1777 710 9 2 0 1 641 679 680 1895 1896 1897 711 9 2 0 1 641 680 642 1897 1898 1780 712 9 2 0 1 642 680 681 1898 1899 1900 713 9 2 0 1 642 681 375 1900 1901 1783 714 9 2 0 1 375 681 374 1901 1902 394 715 9 2 0 1 326 325 643 345 1903 1785 716 9 2 0 1 643 325 682 1903 1904 1905 717 9 2 0 1 643 682 644 1905 1906 1788 718 9 2 0 1 644 682 683 1906 1907 1908 719 9 2 0 1 644 683 645 1908 1909 1791 720 9 2 0 1 645 683 684 1909 1910 1911 721 9 2 0 1 645 684 646 1911 1912 1794 722 9 2 0 1 646 684 685 1912 1913 1914 723 9 2 0 1 646 685 647 1914 1915 1797 724 9 2 0 1 647 685 686 1915 1916 1917 725 9 2 0 1 647 686 648 1917 1918 1800 726 9 2 0 1 648 686 687 1918 1919 1920 727 9 2 0 1 648 687 649 1920 1921 1803 728 9 2 0 1 649 687 688 1921 1922 1923 729 9 2 0 1 649 688 650 1923 1924 1806 730 9 2 0 1 650 688 689 1924 1925 1926 731 9 2 0 1 650 689 651 1926 1927 1809 732 9 2 0 1 651 689 690 1927 1928 1929 733 9 2 0 1 651 690 652 1929 1930 1812 734 9 2 0 1 652 690 691 1930 1931 1932 735 9 2 0 1 652 691 653 1932 1933 1815 736 9 2 0 1 653 691 692 1933 1934 1935 737 9 2 0 1 653 692 654 1935 1936 1818 738 9 2 0 1 654 692 693 1936 1937 1938 739 9 2 0 1 654 693 655 1938 1939 1821 740 9 2 0 1 655 693 694 1939 1940 1941 741 9 2 0 1 655 694 656 1941 1942 1824 742 9 2 0 1 656 694 695 1942 1943 1944 743 9 2 0 1 656 695 657 1944 1945 1827 744 9 2 0 1 657 695 696 1945 1946 1947 745 9 2 0 1 657 696 658 1947 1948 1830 746 9 2 0 1 658 696 697 1948 1949 1950 747 9 2 0 1 658 697 659 1950 1951 1833 748 9 2 0 1 659 697 698 1951 1952 1953 749 9 2 0 1 659 698 660 1953 1954 1836 750 9 2 0 1 660 698 699 1954 1955 1956 751 9 2 0 1 660 699 661 1956 1957 1839 752 9 2 0 1 661 699 700 1957 1958 1959 753 9 2 0 1 661 700 662 1959 1960 1842 754 9 2 0 1 662 700 701 1960 1961 1962 755 9 2 0 1 662 701 663 1962 1963 1845 756 9 2 0 1 663 701 702 1963 1964 1965 757 9 2 0 1 663 702 664 1965 1966 1848 758 9 2 0 1 664 702 703 1966 1967 1968 759 9 2 0 1 664 703 665 1968 1969 1851 760 9 2 0 1 665 703 704 1969 1970 1971 761 9 2 0 1 665 704 666 1971 1972 1854 762 9 2 0 1 666 704 705 1972 1973 1974 763 9 2 0 1 666 705 667 1974 1975 1857 764 9 2 0 1 667 705 706 1975 1976 1977 765 9 2 0 1 667 706 668 1977 1978 1860 766 9 2 0 1 668 706 707 1978 1979 1980 767 9 2 0 1 668 707 669 1980 1981 1863 768 9 2 0 1 669 707 708 1981 1982 1983 769 9 2 0 1 669 708 670 1983 1984 1866 770 9 2 0 1 670 708 709 1984 1985 1986 771 9 2 0 1 670 709 671 1986 1987 1869 772 9 2 0 1 671 709 710 1987 1988 1989 773 9 2 0 1 671 710 672 1989 1990 1872 774 9 2 0 1 672 710 711 1990 1991 1992 775 9 2 0 1 672 711 673 1992 1993 1875 776 9 2 0 1 673 711 712 1993 1994 1995 777 9 2 0 1 673 712 674 1995 1996 1878 778 9 2 0 1 674 712 713 1996 1997 1998 779 9 2 0 1 674 713 675 1998 1999 1881 780 9 2 0 1 675 713 714 1999 2000 2001 781 9 2 0 1 675 714 676 2001 2002 1884 782 9 2 0 1 676 714 715 2002 2003 2004 783 9 2 0 1 676 715 677 2004 2005 1887 784 9 2 0 1 677 715 716 2005 2006 2007 785 9 2 0 1 677 716 678 2007 2008 1890 786 9 2 0 1 678 716 717 2008 2009 2010 787 9 2 0 1 678 717 679 2010 2011 1893 788 9 2 0 1 679 717 718 2011 2012 2013 789 9 2 0 1 679 718 680 2013 2014 1896 790 9 2 0 1 680 718 719 2014 2015 2016 791 9 2 0 1 680 719 681 2016 2017 1899 792 9 2 0 1 681 719 720 2017 2018 2019 793 9 2 0 1 681 720 374 2019 2020 1902 794 9 2 0 1 374 720 373 2020 2021 393 795 9 2 0 1 325 324 682 344 2022 1904 796 9 2 0 1 682 324 721 2022 2023 2024 797 9 2 0 1 682 721 683 2024 2025 1907 798 9 2 0 1 683 721 722 2025 2026 2027 799 9 2 0 1 683 722 684 2027 2028 1910 800 9 2 0 1 684 722 723 2028 2029 2030 801 9 2 0 1 684 723 685 2030 2031 1913 802 9 2 0 1 685 723 724 2031 2032 2033 803 9 2 0 1 685 724 686 2033 2034 1916 804 9 2 0 1 686 724 725 2034 2035 2036 805 9 2 0 1 686 725 687 2036 2037 1919 806 9 2 0 1 687 725 726 2037 2038 2039 807 9 2 0 1 687 726 688 2039 2040 1922 808 9 2 0 1 688 726 727 2040 2041 2042 809 9 2 0 1 688 727 689 2042 2043 1925 810 9 2 0 1 689 727 728 2043 2044 2045 811 9 2 0 1 689 728 690 2045 2046 1928 812 9 2 0 1 690 728 729 2046 2047 2048 813 9 2 0 1 690 729 691 2048 2049 1931 814 9 2 0 1 691 729 730 2049 2050 2051 815 9 2 0 1 691 730 692 2051 2052 1934 816 9 2 0 1 692 730 731 2052 2053 2054 817 9 2 0 1 692 731 693 2054 2055 1937 818 9 2 0 1 693 731 732 2055 2056 2057 819 9 2 0 1 693 732 694 2057 2058 1940 820 9 2 0 1 694 732 733 2058 2059 2060 821 9 2 0 1 694 733 695 2060 2061 1943 822 9 2 0 1 695 733 734 2061 2062 2063 823 9 2 0 1 695 734 696 2063 2064 1946 824 9 2 0 1 696 734 735 2064 2065 2066 825 9 2 0 1 696 735 697 2066 2067 1949 826 9 2 0 1 697 735 736 2067 2068 2069 827 9 2 0 1 697 736 698 2069 2070 1952 828 9 2 0 1 698 736 737 2070 2071 2072 829 9 2 0 1 698 737 699 2072 2073 1955 830 9 2 0 1 699 737 738 2073 2074 2075 831 9 2 0 1 699 738 700 2075 2076 1958 832 9 2 0 1 700 738 739 2076 2077 2078 833 9 2 0 1 700 739 701 2078 2079 1961 834 9 2 0 1 701 739 740 2079 2080 2081 835 9 2 0 1 701 740 702 2081 2082 1964 836 9 2 0 1 702 740 741 2082 2083 2084 837 9 2 0 1 702 741 703 2084 2085 1967 838 9 2 0 1 703 741 742 2085 2086 2087 839 9 2 0 1 703 742 704 2087 2088 1970 840 9 2 0 1 704 742 743 2088 2089 2090 841 9 2 0 1 704 743 705 2090 2091 1973 842 9 2 0 1 705 743 744 2091 2092 2093 843 9 2 0 1 705 744 706 2093 2094 1976 844 9 2 0 1 706 744 745 2094 2095 2096 845 9 2 0 1 706 745 707 2096 2097 1979 846 9 2 0 1 707 745 746 2097 2098 2099 847 9 2 0 1 707 746 708 2099 2100 1982 848 9 2 0 1 708 746 747 2100 2101 2102 849 9 2 0 1 708 747 709 2102 2103 1985 850 9 2 0 1 709 747 748 2103 2104 2105 851 9 2 0 1 709 748 710 2105 2106 1988 852 9 2 0 1 710 748 749 2106 2107 2108 853 9 2 0 1 710 749 711 2108 2109 1991 854 9 2 0 1 711 749 750 2109 2110 2111 855 9 2 0 1 711 750 712 2111 2112 1994 856 9 2 0 1 712 750 751 2112 2113 2114 857 9 2 0 1 712 751 713 2114 2115 1997 858 9 2 0 1 713 751 752 2115 2116 2117 859 9 2 0 1 713 752 714 2117 2118 2000 860 9 2 0 1 714 752 753 2118 2119 2120 861 9 2 0 1 714 753 715 2120 2121 2003 862 9 2 0 1 715 753 754 2121 2122 2123 863 9 2 0 1 715 754 716 2123 2124 2006 864 9 2 0 1 716 754 755 2124 2125 2126 865 9 2 0 1 716 755 717 2126 2127 2009 866 9 2 0 1 717 755 756 2127 2128 2129 867 9 2 0 1 717 756 718 2129 2130 2012 868 9 2 0 1 718 756 757 2130 2131 2132 869 9 2 0 1 718 757 719 2132 2133 2015 870 9 2 0 1 719 757 758 2133 2134 2135 871 9 2 0 1 719 758 720 2135 2136 2018 872 9 2 0 1 720 758 759 2136 2137 2138 873 9 2 0 1 720 759 373 2138 2139 2021 874 9 2 0 1 373 759 372 2139 2140 392 875 9 2 0 1 324 323 721 343 2141 2023 876 9 2 0 1 721 323 760 2141 2142 2143 877 9 2 0 1 721 760 722 2143 2144 2026 878 9 2 0 1 722 760 761 2144 2145 2146 879 9 2 0 1 722 761 723 2146 2147 2029 880 9 2 0 1 723 761 762 2147 2148 2149 881 9 2 0 1 723 762 724 2149 2150 2032 882 9 2 0 1 724 762 763 2150 2151 2152 883 9 2 0 1 724 763 725 2152 2153 2035 884 9 2 0 1 725 763 764 2153 2154 2155 885 9 2 0 1 725 764 726 2155 2156 2038 886 9 2 0 1 726 764 765 2156 2157 2158 887 9 2 0 1 726 765 727 2158 2159 2041 888 9 2 0 1 727 765 766 2159 2160 2161 889 9 2 0 1 727 766 728 2161 2162 2044 890 9 2 0 1 728 766 767 2162 2163 2164 891 9 2 0 1 728 767 729 2164 2165 2047 892 9 2 0 1 729 767 768 2165 2166 2167 893 9 2 0 1 729 768 730 2167 2168 2050 894 9 2 0 1 730 768 769 2168 2169 2170 895 9 2 0 1 730 769 731 2170 2171 2053 896 9 2 0 1 731 769 770 2171 2172 2173 897 9 2 0 1 731 770 732 2173 2174 2056 898 9 2 0 1 732 770 771 2174 2175 2176 899 9 2 0 1 732 771 733 2176 2177 2059 900 9 2 0 1 733 771 772 2177 2178 2179 901 9 2 0 1 733 772 734 2179 2180 2062 902 9 2 0 1 734 772 773 2180 2181 2182 903 9 2 0 1 734 773 735 2182 2183 2065 904 9 2 0 1 735 773 774 2183 2184 2185 905 9 2 0 1 735 774 736 2185 2186 2068 906 9 2 0 1 736 774 775 2186 2187 2188 907 9 2 0 1 736 775 737 2188 2189 2071 908 9 2 0 1 737 775 776 2189 2190 2191 909 9 2 0 1 737 776 738 2191 2192 2074 910 9 2 0 1 738 776 777 2192 2193 2194 911 9 2 0 1 738 777 739 2194 2195 2077 912 9 2 0 1 739 777 778 2195 2196 2197 913 9 2 0 1 739 778 740 2197 2198 2080 914 9 2 0 1 740 778 779 2198 2199 2200 915 9 2 0 1 740 779 741 2200 2201 2083 916 9 2 0 1 741 779 780 2201 2202 2203 917 9 2 0 1 741 780 742 2203 2204 2086 918 9 2 0 1 742 780 781 2204 2205 2206 919 9 2 0 1 742 781 743 2206 2207 2089 920 9 2 0 1 743 781 782 2207 2208 2209 921 9 2 0 1 743 782 744 2209 2210 2092 922 9 2 0 1 744 782 783 2210 2211 2212 923 9 2 0 1 744 783 745 2212 2213 2095 924 9 2 0 1 745 783 784 2213 2214 2215 925 9 2 0 1 745 784 746 2215 2216 2098 926 9 2 0 1 746 784 785 2216 2217 2218 927 9 2 0 1 746 785 747 2218 2219 2101 928 9 2 0 1 747 785 786 2219 2220 2221 929 9 2 0 1 747 786 748 2221 2222 2104 930 9 2 0 1 748 786 787 2222 2223 2224 931 9 2 0 1 748 787 749 2224 2225 2107 932 9 2 0 1 749 787 788 2225 2226 2227 933 9 2 0 1 749 788 750 2227 2228 2110 934 9 2 0 1 750 788 789 2228 2229 2230 935 9 2 0 1 750 789 751 2230 2231 2113 936 9 2 0 1 751 789 790 2231 2232 2233 937 9 2 0 1 751 790 752 2233 2234 2116 938 9 2 0 1 752 790 791 2234 2235 2236 939 9 2 0 1 752 791 753 2236 2237 2119 940 9 2 0 1 753 791 792 2237 2238 2239 941 9 2 0 1 753 792 754 2239 2240 2122 942 9 2 0 1 754 792 793 2240 2241 2242 943 9 2 0 1 754 793 755 2242 2243 2125 944 9 2 0 1 755 793 794 2243 2244 2245 945 9 2 0 1 755 794 756 2245 2246 2128 946 9 2 0 1 756 794 795 2246 2247 2248 947 9 2 0 1 756 795 757 2248 2249 2131 948 9 2 0 1 757 795 796 2249 2250 2251 949 9 2 0 1 757 796 758 2251 2252 2134 950 9 2 0 1 758 796 797 2252 2253 2254 951 9 2 0 1 758 797 759 2254 2255 2137 952 9 2 0 1 759 797 798 2255 2256 2257 953 9 2 0 1 759 798 372 2257 2258 2140 954 9 2 0 1 372 798 371 2258 2259 391 955 9 2 0 1 323 322 760 342 2260 2142 956 9 2 0 1 760 322 799 2260 2261 2262 957 9 2 0 1 760 799 761 2262 2263 2145 958 9 2 0 1 761 799 800 2263 2264 2265 959 9 2 0 1 761 800 762 2265 2266 2148 960 9 2 0 1 762 800 801 2266 2267 2268 961 9 2 0 1 762 801 763 2268 2269 2151 962 9 2 0 1 763 801 802 2269 2270 2271 963 9 2 0 1 763 802 764 2271 2272 2154 964 9 2 0 1 764 802 803 2272 2273 2274 965 9 2 0 1 764 803 765 2274 2275 2157 966 9 2 0 1 765 803 804 2275 2276 2277 967 9 2 0 1 765 804 766 2277 2278 2160 968 9 2 0 1 766 804 805 2278 2279 2280 969 9 2 0 1 766 805 767 2280 2281 2163 970 9 2 0 1 767 805 806 2281 2282 2283 971 9 2 0 1 767 806 768 2283 2284 2166 972 9 2 0 1 768 806 807 2284 2285 2286 973 9 2 0 1 768 807 769 2286 2287 2169 974 9 2 0 1 769 807 808 2287 2288 2289 975 9 2 0 1 769 808 770 2289 2290 2172 976 9 2 0 1 770 808 809 2290 2291 2292 977 9 2 0 1 770 809 771 2292 2293 2175 978 9 2 0 1 771 809 810 2293 2294 2295 979 9 2 0 1 771 810 772 2295 2296 2178 980 9 2 0 1 772 810 811 2296 2297 2298 981 9 2 0 1 772 811 773 2298 2299 2181 982 9 2 0 1 773 811 812 2299 2300 2301 983 9 2 0 1 773 812 774 2301 2302 2184 984 9 2 0 1 774 812 813 2302 2303 2304 985 9 2 0 1 774 813 775 2304 2305 2187 986 9 2 0 1 775 813 814 2305 2306 2307 987 9 2 0 1 775 814 776 2307 2308 2190 988 9 2 0 1 776 814 815 2308 2309 2310 989 9 2 0 1 776 815 777 2310 2311 2193 990 9 2 0 1 777 815 816 2311 2312 2313 991 9 2 0 1 777 816 778 2313 2314 2196 992 9 2 0 1 778 816 817 2314 2315 2316 993 9 2 0 1 778 817 779 2316 2317 2199 994 9 2 0 1 779 817 818 2317 2318 2319 995 9 2 0 1 779 818 780 2319 2320 2202 996 9 2 0 1 780 818 819 2320 2321 2322 997 9 2 0 1 780 819 781 2322 2323 2205 998 9 2 0 1 781 819 820 2323 2324 2325 999 9 2 0 1 781 820 782 2325 2326 2208 1000 9 2 0 1 782 820 821 2326 2327 2328 1001 9 2 0 1 782 821 783 2328 2329 2211 1002 9 2 0 1 783 821 822 2329 2330 2331 1003 9 2 0 1 783 822 784 2331 2332 2214 1004 9 2 0 1 784 822 823 2332 2333 2334 1005 9 2 0 1 784 823 785 2334 2335 2217 1006 9 2 0 1 785 823 824 2335 2336 2337 1007 9 2 0 1 785 824 786 2337 2338 2220 1008 9 2 0 1 786 824 825 2338 2339 2340 1009 9 2 0 1 786 825 787 2340 2341 2223 1010 9 2 0 1 787 825 826 2341 2342 2343 1011 9 2 0 1 787 826 788 2343 2344 2226 1012 9 2 0 1 788 826 827 2344 2345 2346 1013 9 2 0 1 788 827 789 2346 2347 2229 1014 9 2 0 1 789 827 828 2347 2348 2349 1015 9 2 0 1 789 828 790 2349 2350 2232 1016 9 2 0 1 790 828 829 2350 2351 2352 1017 9 2 0 1 790 829 791 2352 2353 2235 1018 9 2 0 1 791 829 830 2353 2354 2355 1019 9 2 0 1 791 830 792 2355 2356 2238 1020 9 2 0 1 792 830 831 2356 2357 2358 1021 9 2 0 1 792 831 793 2358 2359 2241 1022 9 2 0 1 793 831 832 2359 2360 2361 1023 9 2 0 1 793 832 794 2361 2362 2244 1024 9 2 0 1 794 832 833 2362 2363 2364 1025 9 2 0 1 794 833 795 2364 2365 2247 1026 9 2 0 1 795 833 834 2365 2366 2367 1027 9 2 0 1 795 834 796 2367 2368 2250 1028 9 2 0 1 796 834 835 2368 2369 2370 1029 9 2 0 1 796 835 797 2370 2371 2253 1030 9 2 0 1 797 835 836 2371 2372 2373 1031 9 2 0 1 797 836 798 2373 2374 2256 1032 9 2 0 1 798 836 837 2374 2375 2376 1033 9 2 0 1 798 837 371 2376 2377 2259 1034 9 2 0 1 371 837 370 2377 2378 390 1035 9 2 0 1 322 321 799 341 2379 2261 1036 9 2 0 1 799 321 838 2379 2380 2381 1037 9 2 0 1 799 838 800 2381 2382 2264 1038 9 2 0 1 800 838 839 2382 2383 2384 1039 9 2 0 1 800 839 801 2384 2385 2267 1040 9 2 0 1 801 839 840 2385 2386 2387 1041 9 2 0 1 801 840 802 2387 2388 2270 1042 9 2 0 1 802 840 841 2388 2389 2390 1043 9 2 0 1 802 841 803 2390 2391 2273 1044 9 2 0 1 803 841 842 2391 2392 2393 1045 9 2 0 1 803 842 804 2393 2394 2276 1046 9 2 0 1 804 842 843 2394 2395 2396 1047 9 2 0 1 804 843 805 2396 2397 2279 1048 9 2 0 1 805 843 844 2397 2398 2399 1049 9 2 0 1 805 844 806 2399 2400 2282 1050 9 2 0 1 806 844 845 2400 2401 2402 1051 9 2 0 1 806 845 807 2402 2403 2285 1052 9 2 0 1 807 845 846 2403 2404 2405 1053 9 2 0 1 807 846 808 2405 2406 2288 1054 9 2 0 1 808 846 847 2406 2407 2408 1055 9 2 0 1 808 847 809 2408 2409 2291 1056 9 2 0 1 809 847 848 2409 2410 2411 1057 9 2 0 1 809 848 810 2411 2412 2294 1058 9 2 0 1 810 848 849 2412 2413 2414 1059 9 2 0 1 810 849 811 2414 2415 2297 1060 9 2 0 1 811 849 850 2415 2416 2417 1061 9 2 0 1 811 850 812 2417 2418 2300 1062 9 2 0 1 812 850 851 2418 2419 2420 1063 9 2 0 1 812 851 813 2420 2421 2303 1064 9 2 0 1 813 851 852 2421 2422 2423 1065 9 2 0 1 813 852 814 2423 2424 2306 1066 9 2 0 1 814 852 853 2424 2425 2426 1067 9 2 0 1 814 853 815 2426 2427 2309 1068 9 2 0 1 815 853 854 2427 2428 2429 1069 9 2 0 1 815 854 816 2429 2430 2312 1070 9 2 0 1 816 854 855 2430 2431 2432 1071 9 2 0 1 816 855 817 2432 2433 2315 1072 9 2 0 1 817 855 856 2433 2434 2435 1073 9 2 0 1 817 856 818 2435 2436 2318 1074 9 2 0 1 818 856 857 2436 2437 2438 1075 9 2 0 1 818 857 819 2438 2439 2321 1076 9 2 0 1 819 857 858 2439 2440 2441 1077 9 2 0 1 819 858 820 2441 2442 2324 1078 9 2 0 1 820 858 859 2442 2443 2444 1079 9 2 0 1 820 859 821 2444 2445 2327 1080 9 2 0 1 821 859 860 2445 2446 2447 1081 9 2 0 1 821 860 822 2447 2448 2330 1082 9 2 0 1 822 860 861 2448 2449 2450 1083 9 2 0 1 822 861 823 2450 2451 2333 1084 9 2 0 1 823 861 862 2451 2452 2453 1085 9 2 0 1 823 862 824 2453 2454 2336 1086 9 2 0 1 824 862 863 2454 2455 2456 1087 9 2 0 1 824 863 825 2456 2457 2339 1088 9 2 0 1 825 863 864 2457 2458 2459 1089 9 2 0 1 825 864 826 2459 2460 2342 1090 9 2 0 1 826 864 865 2460 2461 2462 1091 9 2 0 1 826 865 827 2462 2463 2345 1092 9 2 0 1 827 865 866 2463 2464 2465 1093 9 2 0 1 827 866 828 2465 2466 2348 1094 9 2 0 1 828 866 867 2466 2467 2468 1095 9 2 0 1 828 867 829 2468 2469 2351 1096 9 2 0 1 829 867 868 2469 2470 2471 1097 9 2 0 1 829 868 830 2471 2472 2354 1098 9 2 0 1 830 868 869 2472 2473 2474 1099 9 2 0 1 830 869 831 2474 2475 2357 1100 9 2 0 1 831 869 870 2475 2476 2477 1101 9 2 0 1 831 870 832 2477 2478 2360 1102 9 2 0 1 832 870 871 2478 2479 2480 1103 9 2 0 1 832 871 833 2480 2481 2363 1104 9 2 0 1 833 871 872 2481 2482 2483 1105 9 2 0 1 833 872 834 2483 2484 2366 1106 9 2 0 1 834 872 873 2484 2485 2486 1107 9 2 0 1 834 873 835 2486 2487 2369 1108 9 2 0 1 835 873 874 2487 2488 2489 1109 9 2 0 1 835 874 836 2489 2490 2372 1110 9 2 0 1 836 874 875 2490 2491 2492 1111 9 2 0 1 836 875 837 2492 2493 2375 1112 9 2 0 1 837 875 876 2493 2494 2495 1113 9 2 0 1 837 876 370 2495 2496 2378 1114 9 2 0 1 370 876 369 2496 2497 389 1115 9 2 0 1 321 320 838 340 2498 2380 1116 9 2 0 1 838 320 877 2498 2499 2500 1117 9 2 0 1 838 877 839 2500 2501 2383 1118 9 2 0 1 839 877 878 2501 2502 2503 1119 9 2 0 1 839 878 840 2503 2504 2386 1120 9 2 0 1 840 878 879 2504 2505 2506 1121 9 2 0 1 840 879 841 2506 2507 2389 1122 9 2 0 1 841 879 880 2507 2508 2509 1123 9 2 0 1 841 880 842 2509 2510 2392 1124 9 2 0 1 842 880 881 2510 2511 2512 1125 9 2 0 1 842 881 843 2512 2513 2395 1126 9 2 0 1 843 881 882 2513 2514 2515 1127 9 2 0 1 843 882 844 2515 2516 2398 1128 9 2 0 1 844 882 883 2516 2517 2518 1129 9 2 0 1 844 883 845 2518 2519 2401 1130 9 2 0 1 845 883 884 2519 2520 2521 1131 9 2 0 1 845 884 846 2521 2522 2404 1132 9 2 0 1 846 884 885 2522 2523 2524 1133 9 2 0 1 846 885 847 2524 2525 2407 1134 9 2 0 1 847 885 886 2525 2526 2527 1135 9 2 0 1 847 886 848 2527 2528 2410 1136 9 2 0 1 848 886 887 2528 2529 2530 1137 9 2 0 1 848 887 849 2530 2531 2413 1138 9 2 0 1 849 887 888 2531 2532 2533 1139 9 2 0 1 849 888 850 2533 2534 2416 1140 9 2 0 1 850 888 889 2534 2535 2536 1141 9 2 0 1 850 889 851 2536 2537 2419 1142 9 2 0 1 851 889 890 2537 2538 2539 1143 9 2 0 1 851 890 852 2539 2540 2422 1144 9 2 0 1 852 890 891 2540 2541 2542 1145 9 2 0 1 852 891 853 2542 2543 2425 1146 9 2 0 1 853 891 892 2543 2544 2545 1147 9 2 0 1 853 892 854 2545 2546 2428 1148 9 2 0 1 854 892 893 2546 2547 2548 1149 9 2 0 1 854 893 855 2548 2549 2431 1150 9 2 0 1 855 893 894 2549 2550 2551 1151 9 2 0 1 855 894 856 2551 2552 2434 1152 9 2 0 1 856 894 895 2552 2553 2554 1153 9 2 0 1 856 895 857 2554 2555 2437 1154 9 2 0 1 857 895 896 2555 2556 2557 1155 9 2 0 1 857 896 858 2557 2558 2440 1156 9 2 0 1 858 896 897 2558 2559 2560 1157 9 2 0 1 858 897 859 2560 2561 2443 1158 9 2 0 1 859 897 898 2561 2562 2563 1159 9 2 0 1 859 898 860 2563 2564 2446 1160 9 2 0 1 860 898 899 2564 2565 2566 1161 9 2 0 1 860 899 861 2566 2567 2449 1162 9 2 0 1 861 899 900 2567 2568 2569 1163 9 2 0 1 861 900 862 2569 2570 2452 1164 9 2 0 1 862 900 901 2570 2571 2572 1165 9 2 0 1 862 901 863 2572 2573 2455 1166 9 2 0 1 863 901 902 2573 2574 2575 1167 9 2 0 1 863 902 864 2575 2576 2458 1168 9 2 0 1 864 902 903 2576 2577 2578 1169 9 2 0 1 864 903 865 2578 2579 2461 1170 9 2 0 1 865 903 904 2579 2580 2581 1171 9 2 0 1 865 904 866 2581 2582 2464 1172 9 2 0 1 866 904 905 2582 2583 2584 1173 9 2 0 1 866 905 867 2584 2585 2467 1174 9 2 0 1 867 905 906 2585 2586 2587 1175 9 2 0 1 867 906 868 2587 2588 2470 1176 9 2 0 1 868 906 907 2588 2589 2590 1177 9 2 0 1 868 907 869 2590 2591 2473 1178 9 2 0 1 869 907 908 2591 2592 2593 1179 9 2 0 1 869 908 870 2593 2594 2476 1180 9 2 0 1 870 908 909 2594 2595 2596 1181 9 2 0 1 870 909 871 2596 2597 2479 1182 9 2 0 1 871 909 910 2597 2598 2599 1183 9 2 0 1 871 910 872 2599 2600 2482 1184 9 2 0 1 872 910 911 2600 2601 2602 1185 9 2 0 1 872 911 873 2602 2603 2485 1186 9 2 0 1 873 911 912 2603 2604 2605 1187 9 2 0 1 873 912 874 2605 2606 2488 1188 9 2 0 1 874 912 913 2606 2607 2608 1189 9 2 0 1 874 913 875 2608 2609 2491 1190 9 2 0 1 875 913 914 2609 2610 2611 1191 9 2 0 1 875 914 876 2611 2612 2494 1192 9 2 0 1 876 914 915 2612 2613 2614 1193 9 2 0 1 876 915 369 2614 2615 2497 1194 9 2 0 1 369 915 368 2615 2616 388 1195 9 2 0 1 320 319 877 339 2617 2499 1196 9 2 0 1 877 319 916 2617 2618 2619 1197 9 2 0 1 877 916 878 2619 2620 2502 1198 9 2 0 1 878 916 917 2620 2621 2622 1199 9 2 0 1 878 917 879 2622 2623 2505 1200 9 2 0 1 879 917 918 2623 2624 2625 1201 9 2 0 1 879 918 880 2625 2626 2508 1202 9 2 0 1 880 918 919 2626 2627 2628 1203 9 2 0 1 880 919 881 2628 2629 2511 1204 9 2 0 1 881 919 920 2629 2630 2631 1205 9 2 0 1 881 920 882 2631 2632 2514 1206 9 2 0 1 882 920 921 2632 2633 2634 1207 9 2 0 1 882 921 883 2634 2635 2517 1208 9 2 0 1 883 921 922 2635 2636 2637 1209 9 2 0 1 883 922 884 2637 2638 2520 1210 9 2 0 1 884 922 923 2638 2639 2640 1211 9 2 0 1 884 923 885 2640 2641 2523 1212 9 2 0 1 885 923 924 2641 2642 2643 1213 9 2 0 1 885 924 886 2643 2644 2526 1214 9 2 0 1 886 924 925 2644 2645 2646 1215 9 2 0 1 886 925 887 2646 2647 2529 1216 9 2 0 1 887 925 926 2647 2648 2649 1217 9 2 0 1 887 926 888 2649 2650 2532 1218 9 2 0 1 888 926 927 2650 2651 2652 1219 9 2 0 1 888 927 889 2652 2653 2535 1220 9 2 0 1 889 927 928 2653 2654 2655 1221 9 2 0 1 889 928 890 2655 2656 2538 1222 9 2 0 1 890 928 929 2656 2657 2658 1223 9 2 0 1 890 929 891 2658 2659 2541 1224 9 2 0 1 891 929 930 2659 2660 2661 1225 9 2 0 1 891 930 892 2661 2662 2544 1226 9 2 0 1 892 930 931 2662 2663 2664 1227 9 2 0 1 892 931 893 2664 2665 2547 1228 9 2 0 1 893 931 932 2665 2666 2667 1229 9 2 0 1 893 932 894 2667 2668 2550 1230 9 2 0 1 894 932 933 2668 2669 2670 1231 9 2 0 1 894 933 895 2670 2671 2553 1232 9 2 0 1 895 933 934 2671 2672 2673 1233 9 2 0 1 895 934 896 2673 2674 2556 1234 9 2 0 1 896 934 935 2674 2675 2676 1235 9 2 0 1 896 935 897 2676 2677 2559 1236 9 2 0 1 897 935 936 2677 2678 2679 1237 9 2 0 1 897 936 898 2679 2680 2562 1238 9 2 0 1 898 936 937 2680 2681 2682 1239 9 2 0 1 898 937 899 2682 2683 2565 1240 9 2 0 1 899 937 938 2683 2684 2685 1241 9 2 0 1 899 938 900 2685 2686 2568 1242 9 2 0 1 900 938 939 2686 2687 2688 1243 9 2 0 1 900 939 901 2688 2689 2571 1244 9 2 0 1 901 939 940 2689 2690 2691 1245 9 2 0 1 901 940 902 2691 2692 2574 1246 9 2 0 1 902 940 941 2692 2693 2694 1247 9 2 0 1 902 941 903 2694 2695 2577 1248 9 2 0 1 903 941 942 2695 2696 2697 1249 9 2 0 1 903 942 904 2697 2698 2580 1250 9 2 0 1 904 942 943 2698 2699 2700 1251 9 2 0 1 904 943 905 2700 2701 2583 1252 9 2 0 1 905 943 944 2701 2702 2703 1253 9 2 0 1 905 944 906 2703 2704 2586 1254 9 2 0 1 906 944 945 2704 2705 2706 1255 9 2 0 1 906 945 907 2706 2707 2589 1256 9 2 0 1 907 945 946 2707 2708 2709 1257 9 2 0 1 907 946 908 2709 2710 2592 1258 9 2 0 1 908 946 947 2710 2711 2712 1259 9 2 0 1 908 947 909 2712 2713 2595 1260 9 2 0 1 909 947 948 2713 2714 2715 1261 9 2 0 1 909 948 910 2715 2716 2598 1262 9 2 0 1 910 948 949 2716 2717 2718 1263 9 2 0 1 910 949 911 2718 2719 2601 1264 9 2 0 1 911 949 950 2719 2720 2721 1265 9 2 0 1 911 950 912 2721 2722 2604 1266 9 2 0 1 912 950 951 2722 2723 2724 1267 9 2 0 1 912 951 913 2724 2725 2607 1268 9 2 0 1 913 951 952 2725 2726 2727 1269 9 2 0 1 913 952 914 2727 2728 2610 1270 9 2 0 1 914 952 953 2728 2729 2730 1271 9 2 0 1 914 953 915 2730 2731 2613 1272 9 2 0 1 915 953 954 2731 2732 2733 1273 9 2 0 1 915 954 368 2733 2734 2616 1274 9 2 0 1 368 954 367 2734 2735 387 1275 9 2 0 1 319 318 916 338 2736 2618 1276 9 2 0 1 916 318 955 2736 2737 2738 1277 9 2 0 1 916 955 917 2738 2739 2621 1278 9 2 0 1 917 955 956 2739 2740 2741 1279 9 2 0 1 917 956 918 2741 2742 2624 1280 9 2 0 1 918 956 957 2742 2743 2744 1281 9 2 0 1 918 957 919 2744 2745 2627 1282 9 2 0 1 919 957 958 2745 2746 2747 1283 9 2 0 1 919 958 920 2747 2748 2630 1284 9 2 0 1 920 958 959 2748 2749 2750 1285 9 2 0 1 920 959 921 2750 2751 2633 1286 9 2 0 1 921 959 960 2751 2752 2753 1287 9 2 0 1 921 960 922 2753 2754 2636 1288 9 2 0 1 922 960 961 2754 2755 2756 1289 9 2 0 1 922 961 923 2756 2757 2639 1290 9 2 0 1 923 961 962 2757 2758 2759 1291 9 2 0 1 923 962 924 2759 2760 2642 1292 9 2 0 1 924 962 963 2760 2761 2762 1293 9 2 0 1 924 963 925 2762 2763 2645 1294 9 2 0 1 925 963 964 2763 2764 2765 1295 9 2 0 1 925 964 926 2765 2766 2648 1296 9 2 0 1 926 964 965 2766 2767 2768 1297 9 2 0 1 926 965 927 2768 2769 2651 1298 9 2 0 1 927 965 966 2769 2770 2771 1299 9 2 0 1 927 966 928 2771 2772 2654 1300 9 2 0 1 928 966 967 2772 2773 2774 1301 9 2 0 1 928 967 929 2774 2775 2657 1302 9 2 0 1 929 967 968 2775 2776 2777 1303 9 2 0 1 929 968 930 2777 2778 2660 1304 9 2 0 1 930 968 969 2778 2779 2780 1305 9 2 0 1 930 969 931 2780 2781 2663 1306 9 2 0 1 931 969 970 2781 2782 2783 1307 9 2 0 1 931 970 932 2783 2784 2666 1308 9 2 0 1 932 970 971 2784 2785 2786 1309 9 2 0 1 932 971 933 2786 2787 2669 1310 9 2 0 1 933 971 972 2787 2788 2789 1311 9 2 0 1 933 972 934 2789 2790 2672 1312 9 2 0 1 934 972 973 2790 2791 2792 1313 9 2 0 1 934 973 935 2792 2793 2675 1314 9 2 0 1 935 973 974 2793 2794 2795 1315 9 2 0 1 935 974 936 2795 2796 2678 1316 9 2 0 1 936 974 975 2796 2797 2798 1317 9 2 0 1 936 975 937 2798 2799 2681 1318 9 2 0 1 937 975 976 2799 2800 2801 1319 9 2 0 1 937 976 938 2801 2802 2684 1320 9 2 0 1 938 976 977 2802 2803 2804 1321 9 2 0 1 938 977 939 2804 2805 2687 1322 9 2 0 1 939 977 978 2805 2806 2807 1323 9 2 0 1 939 978 940 2807 2808 2690 1324 9 2 0 1 940 978 979 2808 2809 2810 1325 9 2 0 1 940 979 941 2810 2811 2693 1326 9 2 0 1 941 979 980 2811 2812 2813 1327 9 2 0 1 941 980 942 2813 2814 2696 1328 9 2 0 1 942 980 981 2814 2815 2816 1329 9 2 0 1 942 981 943 2816 2817 2699 1330 9 2 0 1 943 981 982 2817 2818 2819 1331 9 2 0 1 943 982 944 2819 2820 2702 1332 9 2 0 1 944 982 983 2820 2821 2822 1333 9 2 0 1 944 983 945 2822 2823 2705 1334 9 2 0 1 945 983 984 2823 2824 2825 1335 9 2 0 1 945 984 946 2825 2826 2708 1336 9 2 0 1 946 984 985 2826 2827 2828 1337 9 2 0 1 946 985 947 2828 2829 2711 1338 9 2 0 1 947 985 986 2829 2830 2831 1339 9 2 0 1 947 986 948 2831 2832 2714 1340 9 2 0 1 948 986 987 2832 2833 2834 1341 9 2 0 1 948 987 949 2834 2835 2717 1342 9 2 0 1 949 987 988 2835 2836 2837 1343 9 2 0 1 949 988 950 2837 2838 2720 1344 9 2 0 1 950 988 989 2838 2839 2840 1345 9 2 0 1 950 989 951 2840 2841 2723 1346 9 2 0 1 951 989 990 2841 2842 2843 1347 9 2 0 1 951 990 952 2843 2844 2726 1348 9 2 0 1 952 990 991 2844 2845 2846 1349 9 2 0 1 952 991 953 2846 2847 2729 1350 9 2 0 1 953 991 992 2847 2848 2849 1351 9 2 0 1 953 992 954 2849 2850 2732 1352 9 2 0 1 954 992 993 2850 2851 2852 1353 9 2 0 1 954 993 367 2852 2853 2735 1354 9 2 0 1 367 993 366 2853 2854 386 1355 9 2 0 1 318 317 955 337 2855 2737 1356 9 2 0 1 955 317 994 2855 2856 2857 1357 9 2 0 1 955 994 956 2857 2858 2740 1358 9 2 0 1 956 994 995 2858 2859 2860 1359 9 2 0 1 956 995 957 2860 2861 2743 1360 9 2 0 1 957 995 996 2861 2862 2863 1361 9 2 0 1 957 996 958 2863 2864 2746 1362 9 2 0 1 958 996 997 2864 2865 2866 1363 9 2 0 1 958 997 959 2866 2867 2749 1364 9 2 0 1 959 997 998 2867 2868 2869 1365 9 2 0 1 959 998 960 2869 2870 2752 1366 9 2 0 1 960 998 999 2870 2871 2872 1367 9 2 0 1 960 999 961 2872 2873 2755 1368 9 2 0 1 961 999 1000 2873 2874 2875 1369 9 2 0 1 961 1000 962 2875 2876 2758 1370 9 2 0 1 962 1000 1001 2876 2877 2878 1371 9 2 0 1 962 1001 963 2878 2879 2761 1372 9 2 0 1 963 1001 1002 2879 2880 2881 1373 9 2 0 1 963 1002 964 2881 2882 2764 1374 9 2 0 1 964 1002 1003 2882 2883 2884 1375 9 2 0 1 964 1003 965 2884 2885 2767 1376 9 2 0 1 965 1003 1004 2885 2886 2887 1377 9 2 0 1 965 1004 966 2887 2888 2770 1378 9 2 0 1 966 1004 1005 2888 2889 2890 1379 9 2 0 1 966 1005 967 2890 2891 2773 1380 9 2 0 1 967 1005 1006 2891 2892 2893 1381 9 2 0 1 967 1006 968 2893 2894 2776 1382 9 2 0 1 968 1006 1007 2894 2895 2896 1383 9 2 0 1 968 1007 969 2896 2897 2779 1384 9 2 0 1 969 1007 1008 2897 2898 2899 1385 9 2 0 1 969 1008 970 2899 2900 2782 1386 9 2 0 1 970 1008 1009 2900 2901 2902 1387 9 2 0 1 970 1009 971 2902 2903 2785 1388 9 2 0 1 971 1009 1010 2903 2904 2905 1389 9 2 0 1 971 1010 972 2905 2906 2788 1390 9 2 0 1 972 1010 1011 2906 2907 2908 1391 9 2 0 1 972 1011 973 2908 2909 2791 1392 9 2 0 1 973 1011 1012 2909 2910 2911 1393 9 2 0 1 973 1012 974 2911 2912 2794 1394 9 2 0 1 974 1012 1013 2912 2913 2914 1395 9 2 0 1 974 1013 975 2914 2915 2797 1396 9 2 0 1 975 1013 1014 2915 2916 2917 1397 9 2 0 1 975 1014 976 2917 2918 2800 1398 9 2 0 1 976 1014 1015 2918 2919 2920 1399 9 2 0 1 976 1015 977 2920 2921 2803 1400 9 2 0 1 977 1015 1016 2921 2922 2923 1401 9 2 0 1 977 1016 978 2923 2924 2806 1402 9 2 0 1 978 1016 1017 2924 2925 2926 1403 9 2 0 1 978 1017 979 2926 2927 2809 1404 9 2 0 1 979 1017 1018 2927 2928 2929 1405 9 2 0 1 979 1018 980 2929 2930 2812 1406 9 2 0 1 980 1018 1019 2930 2931 2932 1407 9 2 0 1 980 1019 981 2932 2933 2815 1408 9 2 0 1 981 1019 1020 2933 2934 2935 1409 9 2 0 1 981 1020 982 2935 2936 2818 1410 9 2 0 1 982 1020 1021 2936 2937 2938 1411 9 2 0 1 982 1021 983 2938 2939 2821 1412 9 2 0 1 983 1021 1022 2939 2940 2941 1413 9 2 0 1 983 1022 984 2941 2942 2824 1414 9 2 0 1 984 1022 1023 2942 2943 2944 1415 9 2 0 1 984 1023 985 2944 2945 2827 1416 9 2 0 1 985 1023 1024 2945 2946 2947 1417 9 2 0 1 985 1024 986 2947 2948 2830 1418 9 2 0 1 986 1024 1025 2948 2949 2950 1419 9 2 0 1 986 1025 987 2950 2951 2833 1420 9 2 0 1 987 1025 1026 2951 2952 2953 1421 9 2 0 1 987 1026 988 2953 2954 2836 1422 9 2 0 1 988 1026 1027 2954 2955 2956 1423 9 2 0 1 988 1027 989 2956 2957 2839 1424 9 2 0 1 989 1027 1028 2957 2958 2959 1425 9 2 0 1 989 1028 990 2959 2960 2842 1426 9 2 0 1 990 1028 1029 2960 2961 2962 1427 9 2 0 1 990 1029 991 2962 2963 2845 1428 9 2 0 1 991 1029 1030 2963 2964 2965 1429 9 2 0 1 991 1030 992 2965 2966 2848 1430 9 2 0 1 992 1030 1031 2966 2967 2968 1431 9 2 0 1 992 1031 993 2968 2969 2851 1432 9 2 0 1 993 1031 1032 2969 2970 2971 1433 9 2 0 1 993 1032 366 2971 2972 2854 1434 9 2 0 1 366 1032 365 2972 2973 385 1435 9 2 0 1 317 316 994 336 2974 2856 1436 9 2 0 1 994 316 1033 2974 2975 2976 1437 9 2 0 1 994 1033 995 2976 2977 2859 1438 9 2 0 1 995 1033 1034 2977 2978 2979 1439 9 2 0 1 995 1034 996 2979 2980 2862 1440 9 2 0 1 996 1034 1035 2980 2981 2982 1441 9 2 0 1 996 1035 997 2982 2983 2865 1442 9 2 0 1 997 1035 1036 2983 2984 2985 1443 9 2 0 1 997 1036 998 2985 2986 2868 1444 9 2 0 1 998 1036 1037 2986 2987 2988 1445 9 2 0 1 998 1037 999 2988 2989 2871 1446 9 2 0 1 999 1037 1038 2989 2990 2991 1447 9 2 0 1 999 1038 1000 2991 2992 2874 1448 9 2 0 1 1000 1038 1039 2992 2993 2994 1449 9 2 0 1 1000 1039 1001 2994 2995 2877 1450 9 2 0 1 1001 1039 1040 2995 2996 2997 1451 9 2 0 1 1001 1040 1002 2997 2998 2880 1452 9 2 0 1 1002 1040 1041 2998 2999 3000 1453 9 2 0 1 1002 1041 1003 3000 3001 2883 1454 9 2 0 1 1003 1041 1042 3001 3002 3003 1455 9 2 0 1 1003 1042 1004 3003 3004 2886 1456 9 2 0 1 1004 1042 1043 3004 3005 3006 1457 9 2 0 1 1004 1043 1005 3006 3007 2889 1458 9 2 0 1 1005 1043 1044 3007 3008 3009 1459 9 2 0 1 1005 1044 1006 3009 3010 2892 1460 9 2 0 1 1006 1044 1045 3010 3011 3012 1461 9 2 0 1 1006 1045 1007 3012 3013 2895 1462 9 2 0 1 1007 1045 1046 3013 3014 3015 1463 9 2 0 1 1007 1046 1008 3015 3016 2898 1464 9 2 0 1 1008 1046 1047 3016 3017 3018 1465 9 2 0 1 1008 1047 1009 3018 3019 2901 1466 9 2 0 1 1009 1047 1048 3019 3020 3021 1467 9 2 0 1 1009 1048 1010 3021 3022 2904 1468 9 2 0 1 1010 1048 1049 3022 3023 3024 1469 9 2 0 1 1010 1049 1011 3024 3025 2907 1470 9 2 0 1 1011 1049 1050 3025 3026 3027 1471 9 2 0 1 1011 1050 1012 3027 3028 2910 1472 9 2 0 1 1012 1050 1051 3028 3029 3030 1473 9 2 0 1 1012 1051 1013 3030 3031 2913 1474 9 2 0 1 1013 1051 1052 3031 3032 3033 1475 9 2 0 1 1013 1052 1014 3033 3034 2916 1476 9 2 0 1 1014 1052 1053 3034 3035 3036 1477 9 2 0 1 1014 1053 1015 3036 3037 2919 1478 9 2 0 1 1015 1053 1054 3037 3038 3039 1479 9 2 0 1 1015 1054 1016 3039 3040 2922 1480 9 2 0 1 1016 1054 1055 3040 3041 3042 1481 9 2 0 1 1016 1055 1017 3042 3043 2925 1482 9 2 0 1 1017 1055 1056 3043 3044 3045 1483 9 2 0 1 1017 1056 1018 3045 3046 2928 1484 9 2 0 1 1018 1056 1057 3046 3047 3048 1485 9 2 0 1 1018 1057 1019 3048 3049 2931 1486 9 2 0 1 1019 1057 1058 3049 3050 3051 1487 9 2 0 1 1019 1058 1020 3051 3052 2934 1488 9 2 0 1 1020 1058 1059 3052 3053 3054 1489 9 2 0 1 1020 1059 1021 3054 3055 2937 1490 9 2 0 1 1021 1059 1060 3055 3056 3057 1491 9 2 0 1 1021 1060 1022 3057 3058 2940 1492 9 2 0 1 1022 1060 1061 3058 3059 3060 1493 9 2 0 1 1022 1061 1023 3060 3061 2943 1494 9 2 0 1 1023 1061 1062 3061 3062 3063 1495 9 2 0 1 1023 1062 1024 3063 3064 2946 1496 9 2 0 1 1024 1062 1063 3064 3065 3066 1497 9 2 0 1 1024 1063 1025 3066 3067 2949 1498 9 2 0 1 1025 1063 1064 3067 3068 3069 1499 9 2 0 1 1025 1064 1026 3069 3070 2952 1500 9 2 0 1 1026 1064 1065 3070 3071 3072 1501 9 2 0 1 1026 1065 1027 3072 3073 2955 1502 9 2 0 1 1027 1065 1066 3073 3074 3075 1503 9 2 0 1 1027 1066 1028 3075 3076 2958 1504 9 2 0 1 1028 1066 1067 3076 3077 3078 1505 9 2 0 1 1028 1067 1029 3078 3079 2961 1506 9 2 0 1 1029 1067 1068 3079 3080 3081 1507 9 2 0 1 1029 1068 1030 3081 3082 2964 1508 9 2 0 1 1030 1068 1069 3082 3083 3084 1509 9 2 0 1 1030 1069 1031 3084 3085 2967 1510 9 2 0 1 1031 1069 1070 3085 3086 3087 1511 9 2 0 1 1031 1070 1032 3087 3088 2970 1512 9 2 0 1 1032 1070 1071 3088 3089 3090 1513 9 2 0 1 1032 1071 365 3090 3091 2973 1514 9 2 0 1 365 1071 364 3091 3092 384 1515 9 2 0 1 316 315 1033 335 3093 2975 1516 9 2 0 1 1033 315 1072 3093 3094 3095 1517 9 2 0 1 1033 1072 1034 3095 3096 2978 1518 9 2 0 1 1034 1072 1073 3096 3097 3098 1519 9 2 0 1 1034 1073 1035 3098 3099 2981 1520 9 2 0 1 1035 1073 1074 3099 3100 3101 1521 9 2 0 1 1035 1074 1036 3101 3102 2984 1522 9 2 0 1 1036 1074 1075 3102 3103 3104 1523 9 2 0 1 1036 1075 1037 3104 3105 2987 1524 9 2 0 1 1037 1075 1076 3105 3106 3107 1525 9 2 0 1 1037 1076 1038 3107 3108 2990 1526 9 2 0 1 1038 1076 1077 3108 3109 3110 1527 9 2 0 1 1038 1077 1039 3110 3111 2993 1528 9 2 0 1 1039 1077 1078 3111 3112 3113 1529 9 2 0 1 1039 1078 1040 3113 3114 2996 1530 9 2 0 1 1040 1078 1079 3114 3115 3116 1531 9 2 0 1 1040 1079 1041 3116 3117 2999 1532 9 2 0 1 1041 1079 1080 3117 3118 3119 1533 9 2 0 1 1041 1080 1042 3119 3120 3002 1534 9 2 0 1 1042 1080 1081 3120 3121 3122 1535 9 2 0 1 1042 1081 1043 3122 3123 3005 1536 9 2 0 1 1043 1081 1082 3123 3124 3125 1537 9 2 0 1 1043 1082 1044 3125 3126 3008 1538 9 2 0 1 1044 1082 1083 3126 3127 3128 1539 9 2 0 1 1044 1083 1045 3128 3129 3011 1540 9 2 0 1 1045 1083 1084 3129 3130 3131 1541 9 2 0 1 1045 1084 1046 3131 3132 3014 1542 9 2 0 1 1046 1084 1085 3132 3133 3134 1543 9 2 0 1 1046 1085 1047 3134 3135 3017 1544 9 2 0 1 1047 1085 1086 3135 3136 3137 1545 9 2 0 1 1047 1086 1048 3137 3138 3020 1546 9 2 0 1 1048 1086 1087 3138 3139 3140 1547 9 2 0 1 1048 1087 1049 3140 3141 3023 1548 9 2 0 1 1049 1087 1088 3141 3142 3143 1549 9 2 0 1 1049 1088 1050 3143 3144 3026 1550 9 2 0 1 1050 1088 1089 3144 3145 3146 1551 9 2 0 1 1050 1089 1051 3146 3147 3029 1552 9 2 0 1 1051 1089 1090 3147 3148 3149 1553 9 2 0 1 1051 1090 1052 3149 3150 3032 1554 9 2 0 1 1052 1090 1091 3150 3151 3152 1555 9 2 0 1 1052 1091 1053 3152 3153 3035 1556 9 2 0 1 1053 1091 1092 3153 3154 3155 1557 9 2 0 1 1053 1092 1054 3155 3156 3038 1558 9 2 0 1 1054 1092 1093 3156 3157 3158 1559 9 2 0 1 1054 1093 1055 3158 3159 3041 1560 9 2 0 1 1055 1093 1094 3159 3160 3161 1561 9 2 0 1 1055 1094 1056 3161 3162 3044 1562 9 2 0 1 1056 1094 1095 3162 3163 3164 1563 9 2 0 1 1056 1095 1057 3164 3165 3047 1564 9 2 0 1 1057 1095 1096 3165 3166 3167 1565 9 2 0 1 1057 1096 1058 3167 3168 3050 1566 9 2 0 1 1058 1096 1097 3168 3169 3170 1567 9 2 0 1 1058 1097 1059 3170 3171 3053 1568 9 2 0 1 1059 1097 1098 3171 3172 3173 1569 9 2 0 1 1059 1098 1060 3173 3174 3056 1570 9 2 0 1 1060 1098 1099 3174 3175 3176 1571 9 2 0 1 1060 1099 1061 3176 3177 3059 1572 9 2 0 1 1061 1099 1100 3177 3178 3179 1573 9 2 0 1 1061 1100 1062 3179 3180 3062 1574 9 2 0 1 1062 1100 1101 3180 3181 3182 1575 9 2 0 1 1062 1101 1063 3182 3183 3065 1576 9 2 0 1 1063 1101 1102 3183 3184 3185 1577 9 2 0 1 1063 1102 1064 3185 3186 3068 1578 9 2 0 1 1064 1102 1103 3186 3187 3188 1579 9 2 0 1 1064 1103 1065 3188 3189 3071 1580 9 2 0 1 1065 1103 1104 3189 3190 3191 1581 9 2 0 1 1065 1104 1066 3191 3192 3074 1582 9 2 0 1 1066 1104 1105 3192 3193 3194 1583 9 2 0 1 1066 1105 1067 3194 3195 3077 1584 9 2 0 1 1067 1105 1106 3195 3196 3197 1585 9 2 0 1 1067 1106 1068 3197 3198 3080 1586 9 2 0 1 1068 1106 1107 3198 3199 3200 1587 9 2 0 1 1068 1107 1069 3200 3201 3083 1588 9 2 0 1 1069 1107 1108 3201 3202 3203 1589 9 2 0 1 1069 1108 1070 3203 3204 3086 1590 9 2 0 1 1070 1108 1109 3204 3205 3206 1591 9 2 0 1 1070 1109 1071 3206 3207 3089 1592 9 2 0 1 1071 1109 1110 3207 3208 3209 1593 9 2 0 1 1071 1110 364 3209 3210 3092 1594 9 2 0 1 364 1110 363 3210 3211 383 1595 9 2 0 1 315 314 1072 334 3212 3094 1596 9 2 0 1 1072 314 1111 3212 3213 3214 1597 9 2 0 1 1072 1111 1073 3214 3215 3097 1598 9 2 0 1 1073 1111 1112 3215 3216 3217 1599 9 2 0 1 1073 1112 1074 3217 3218 3100 1600 9 2 0 1 1074 1112 1113 3218 3219 3220 1601 9 2 0 1 1074 1113 1075 3220 3221 3103 1602 9 2 0 1 1075 1113 1114 3221 3222 3223 1603 9 2 0 1 1075 1114 1076 3223 3224 3106 1604 9 2 0 1 1076 1114 1115 3224 3225 3226 1605 9 2 0 1 1076 1115 1077 3226 3227 3109 1606 9 2 0 1 1077 1115 1116 3227 3228 3229 1607 9 2 0 1 1077 1116 1078 3229 3230 3112 1608 9 2 0 1 1078 1116 1117 3230 3231 3232 1609 9 2 0 1 1078 1117 1079 3232 3233 3115 1610 9 2 0 1 1079 1117 1118 3233 3234 3235 1611 9 2 0 1 1079 1118 1080 3235 3236 3118 1612 9 2 0 1 1080 1118 1119 3236 3237 3238 1613 9 2 0 1 1080 1119 1081 3238 3239 3121 1614 9 2 0 1 1081 1119 1120 3239 3240 3241 1615 9 2 0 1 1081 1120 1082 3241 3242 3124 1616 9 2 0 1 1082 1120 1121 3242 3243 3244 1617 9 2 0 1 1082 1121 1083 3244 3245 3127 1618 9 2 0 1 1083 1121 1122 3245 3246 3247 1619 9 2 0 1 1083 1122 1084 3247 3248 3130 1620 9 2 0 1 1084 1122 1123 3248 3249 3250 1621 9 2 0 1 1084 1123 1085 3250 3251 3133 1622 9 2 0 1 1085 1123 1124 3251 3252 3253 1623 9 2 0 1 1085 1124 1086 3253 3254 3136 1624 9 2 0 1 1086 1124 1125 3254 3255 3256 1625 9 2 0 1 1086 1125 1087 3256 3257 3139 1626 9 2 0 1 1087 1125 1126 3257 3258 3259 1627 9 2 0 1 1087 1126 1088 3259 3260 3142 1628 9 2 0 1 1088 1126 1127 3260 3261 3262 1629 9 2 0 1 1088 1127 1089 3262 3263 3145 1630 9 2 0 1 1089 1127 1128 3263 3264 3265 1631 9 2 0 1 1089 1128 1090 3265 3266 3148 1632 9 2 0 1 1090 1128 1129 3266 3267 3268 1633 9 2 0 1 1090 1129 1091 3268 3269 3151 1634 9 2 0 1 1091 1129 1130 3269 3270 3271 1635 9 2 0 1 1091 1130 1092 3271 3272 3154 1636 9 2 0 1 1092 1130 1131 3272 3273 3274 1637 9 2 0 1 1092 1131 1093 3274 3275 3157 1638 9 2 0 1 1093 1131 1132 3275 3276 3277 1639 9 2 0 1 1093 1132 1094 3277 3278 3160 1640 9 2 0 1 1094 1132 1133 3278 3279 3280 1641 9 2 0 1 1094 1133 1095 3280 3281 3163 1642 9 2 0 1 1095 1133 1134 3281 3282 3283 1643 9 2 0 1 1095 1134 1096 3283 3284 3166 1644 9 2 0 1 1096 1134 1135 3284 3285 3286 1645 9 2 0 1 1096 1135 1097 3286 3287 3169 1646 9 2 0 1 1097 1135 1136 3287 3288 3289 1647 9 2 0 1 1097 1136 1098 3289 3290 3172 1648 9 2 0 1 1098 1136 1137 3290 3291 3292 1649 9 2 0 1 1098 1137 1099 3292 3293 3175 1650 9 2 0 1 1099 1137 1138 3293 3294 3295 1651 9 2 0 1 1099 1138 1100 3295 3296 3178 1652 9 2 0 1 1100 1138 1139 3296 3297 3298 1653 9 2 0 1 1100 1139 1101 3298 3299 3181 1654 9 2 0 1 1101 1139 1140 3299 3300 3301 1655 9 2 0 1 1101 1140 1102 3301 3302 3184 1656 9 2 0 1 1102 1140 1141 3302 3303 3304 1657 9 2 0 1 1102 1141 1103 3304 3305 3187 1658 9 2 0 1 1103 1141 1142 3305 3306 3307 1659 9 2 0 1 1103 1142 1104 3307 3308 3190 1660 9 2 0 1 1104 1142 1143 3308 3309 3310 1661 9 2 0 1 1104 1143 1105 3310 3311 3193 1662 9 2 0 1 1105 1143 1144 3311 3312 3313 1663 9 2 0 1 1105 1144 1106 3313 3314 3196 1664 9 2 0 1 1106 1144 1145 3314 3315 3316 1665 9 2 0 1 1106 1145 1107 3316 3317 3199 1666 9 2 0 1 1107 1145 1146 3317 3318 3319 1667 9 2 0 1 1107 1146 1108 3319 3320 3202 1668 9 2 0 1 1108 1146 1147 3320 3321 3322 1669 9 2 0 1 1108 1147 1109 3322 3323 3205 1670 9 2 0 1 1109 1147 1148 3323 3324 3325 1671 9 2 0 1 1109 1148 1110 3325 3326 3208 1672 9 2 0 1 1110 1148 1149 3326 3327 3328 1673 9 2 0 1 1110 1149 363 3328 3329 3211 1674 9 2 0 1 363 1149 362 3329 3330 382 1675 9 2 0 1 314 313 1111 333 3331 3213 1676 9 2 0 1 1111 313 1150 3331 3332 3333 1677 9 2 0 1 1111 1150 1112 3333 3334 3216 1678 9 2 0 1 1112 1150 1151 3334 3335 3336 1679 9 2 0 1 1112 1151 1113 3336 3337 3219 1680 9 2 0 1 1113 1151 1152 3337 3338 3339 1681 9 2 0 1 1113 1152 1114 3339 3340 3222 1682 9 2 0 1 1114 1152 1153 3340 3341 3342 1683 9 2 0 1 1114 1153 1115 3342 3343 3225 1684 9 2 0 1 1115 1153 1154 3343 3344 3345 1685 9 2 0 1 1115 1154 1116 3345 3346 3228 1686 9 2 0 1 1116 1154 1155 3346 3347 3348 1687 9 2 0 1 1116 1155 1117 3348 3349 3231 1688 9 2 0 1 1117 1155 1156 3349 3350 3351 1689 9 2 0 1 1117 1156 1118 3351 3352 3234 1690 9 2 0 1 1118 1156 1157 3352 3353 3354 1691 9 2 0 1 1118 1157 1119 3354 3355 3237 1692 9 2 0 1 1119 1157 1158 3355 3356 3357 1693 9 2 0 1 1119 1158 1120 3357 3358 3240 1694 9 2 0 1 1120 1158 1159 3358 3359 3360 1695 9 2 0 1 1120 1159 1121 3360 3361 3243 1696 9 2 0 1 1121 1159 1160 3361 3362 3363 1697 9 2 0 1 1121 1160 1122 3363 3364 3246 1698 9 2 0 1 1122 1160 1161 3364 3365 3366 1699 9 2 0 1 1122 1161 1123 3366 3367 3249 1700 9 2 0 1 1123 1161 1162 3367 3368 3369 1701 9 2 0 1 1123 1162 1124 3369 3370 3252 1702 9 2 0 1 1124 1162 1163 3370 3371 3372 1703 9 2 0 1 1124 1163 1125 3372 3373 3255 1704 9 2 0 1 1125 1163 1164 3373 3374 3375 1705 9 2 0 1 1125 1164 1126 3375 3376 3258 1706 9 2 0 1 1126 1164 1165 3376 3377 3378 1707 9 2 0 1 1126 1165 1127 3378 3379 3261 1708 9 2 0 1 1127 1165 1166 3379 3380 3381 1709 9 2 0 1 1127 1166 1128 3381 3382 3264 1710 9 2 0 1 1128 1166 1167 3382 3383 3384 1711 9 2 0 1 1128 1167 1129 3384 3385 3267 1712 9 2 0 1 1129 1167 1168 3385 3386 3387 1713 9 2 0 1 1129 1168 1130 3387 3388 3270 1714 9 2 0 1 1130 1168 1169 3388 3389 3390 1715 9 2 0 1 1130 1169 1131 3390 3391 3273 1716 9 2 0 1 1131 1169 1170 3391 3392 3393 1717 9 2 0 1 1131 1170 1132 3393 3394 3276 1718 9 2 0 1 1132 1170 1171 3394 3395 3396 1719 9 2 0 1 1132 1171 1133 3396 3397 3279 1720 9 2 0 1 1133 1171 1172 3397 3398 3399 1721 9 2 0 1 1133 1172 1134 3399 3400 3282 1722 9 2 0 1 1134 1172 1173 3400 3401 3402 1723 9 2 0 1 1134 1173 1135 3402 3403 3285 1724 9 2 0 1 1135 1173 1174 3403 3404 3405 1725 9 2 0 1 1135 1174 1136 3405 3406 3288 1726 9 2 0 1 1136 1174 1175 3406 3407 3408 1727 9 2 0 1 1136 1175 1137 3408 3409 3291 1728 9 2 0 1 1137 1175 1176 3409 3410 3411 1729 9 2 0 1 1137 1176 1138 3411 3412 3294 1730 9 2 0 1 1138 1176 1177 3412 3413 3414 1731 9 2 0 1 1138 1177 1139 3414 3415 3297 1732 9 2 0 1 1139 1177 1178 3415 3416 3417 1733 9 2 0 1 1139 1178 1140 3417 3418 3300 1734 9 2 0 1 1140 1178 1179 3418 3419 3420 1735 9 2 0 1 1140 1179 1141 3420 3421 3303 1736 9 2 0 1 1141 1179 1180 3421 3422 3423 1737 9 2 0 1 1141 1180 1142 3423 3424 3306 1738 9 2 0 1 1142 1180 1181 3424 3425 3426 1739 9 2 0 1 1142 1181 1143 3426 3427 3309 1740 9 2 0 1 1143 1181 1182 3427 3428 3429 1741 9 2 0 1 1143 1182 1144 3429 3430 3312 1742 9 2 0 1 1144 1182 1183 3430 3431 3432 1743 9 2 0 1 1144 1183 1145 3432 3433 3315 1744 9 2 0 1 1145 1183 1184 3433 3434 3435 1745 9 2 0 1 1145 1184 1146 3435 3436 3318 1746 9 2 0 1 1146 1184 1185 3436 3437 3438 1747 9 2 0 1 1146 1185 1147 3438 3439 3321 1748 9 2 0 1 1147 1185 1186 3439 3440 3441 1749 9 2 0 1 1147 1186 1148 3441 3442 3324 1750 9 2 0 1 1148 1186 1187 3442 3443 3444 1751 9 2 0 1 1148 1187 1149 3444 3445 3327 1752 9 2 0 1 1149 1187 1188 3445 3446 3447 1753 9 2 0 1 1149 1188 362 3447 3448 3330 1754 9 2 0 1 362 1188 361 3448 3449 381 1755 9 2 0 1 313 8 1150 332 3450 3332 1756 9 2 0 1 1150 8 108 3450 147 3451 1757 9 2 0 1 1150 108 1151 3451 3452 3335 1758 9 2 0 1 1151 108 109 3452 148 3453 1759 9 2 0 1 1151 109 1152 3453 3454 3338 1760 9 2 0 1 1152 109 110 3454 149 3455 1761 9 2 0 1 1152 110 1153 3455 3456 3341 1762 9 2 0 1 1153 110 111 3456 150 3457 1763 9 2 0 1 1153 111 1154 3457 3458 3344 1764 9 2 0 1 1154 111 112 3458 151 3459 1765 9 2 0 1 1154 112 1155 3459 3460 3347 1766 9 2 0 1 1155 112 113 3460 152 3461 1767 9 2 0 1 1155 113 1156 3461 3462 3350 1768 9 2 0 1 1156 113 114 3462 153 3463 1769 9 2 0 1 1156 114 1157 3463 3464 3353 1770 9 2 0 1 1157 114 115 3464 154 3465 1771 9 2 0 1 1157 115 1158 3465 3466 3356 1772 9 2 0 1 1158 115 116 3466 155 3467 1773 9 2 0 1 1158 116 1159 3467 3468 3359 1774 9 2 0 1 1159 116 117 3468 156 3469 1775 9 2 0 1 1159 117 1160 3469 3470 3362 1776 9 2 0 1 1160 117 118 3470 157 3471 1777 9 2 0 1 1160 118 1161 3471 3472 3365 1778 9 2 0 1 1161 118 119 3472 158 3473 1779 9 2 0 1 1161 119 1162 3473 3474 3368 1780 9 2 0 1 1162 119 120 3474 159 3475 1781 9 2 0 1 1162 120 1163 3475 3476 3371 1782 9 2 0 1 1163 120 121 3476 160 3477 1783 9 2 0 1 1163 121 1164 3477 3478 3374 1784 9 2 0 1 1164 121 122 3478 161 3479 1785 9 2 0 1 1164 122 1165 3479 3480 3377 1786 9 2 0 1 1165 122 123 3480 162 3481 1787 9 2 0 1 1165 123 1166 3481 3482 3380 1788 9 2 0 1 1166 123 124 3482 163 3483 1789 9 2 0 1 1166 124 1167 3483 3484 3383 1790 9 2 0 1 1167 124 125 3484 164 3485 1791 9 2 0 1 1167 125 1168 3485 3486 3386 1792 9 2 0 1 1168 125 126 3486 165 3487 1793 9 2 0 1 1168 126 1169 3487 3488 3389 1794 9 2 0 1 1169 126 127 3488 166 3489 1795 9 2 0 1 1169 127 1170 3489 3490 3392 1796 9 2 0 1 1170 127 128 3490 167 3491 1797 9 2 0 1 1170 128 1171 3491 3492 3395 1798 9 2 0 1 1171 128 129 3492 168 3493 1799 9 2 0 1 1171 129 1172 3493 3494 3398 1800 9 2 0 1 1172 129 130 3494 169 3495 1801 9 2 0 1 1172 130 1173 3495 3496 3401 1802 9 2 0 1 1173 130 131 3496 170 3497 1803 9 2 0 1 1173 131 1174 3497 3498 3404 1804 9 2 0 1 1174 131 132 3498 171 3499 1805 9 2 0 1 1174 132 1175 3499 3500 3407 1806 9 2 0 1 1175 132 133 3500 172 3501 1807 9 2 0 1 1175 133 1176 3501 3502 3410 1808 9 2 0 1 1176 133 134 3502 173 3503 1809 9 2 0 1 1176 134 1177 3503 3504 3413 1810 9 2 0 1 1177 134 135 3504 174 3505 1811 9 2 0 1 1177 135 1178 3505 3506 3416 1812 9 2 0 1 1178 135 136 3506 175 3507 1813 9 2 0 1 1178 136 1179 3507 3508 3419 1814 9 2 0 1 1179 136 137 3508 176 3509 1815 9 2 0 1 1179 137 1180 3509 3510 3422 1816 9 2 0 1 1180 137 138 3510 177 3511 1817 9 2 0 1 1180 138 1181 3511 3512 3425 1818 9 2 0 1 1181 138 139 3512 178 3513 1819 9 2 0 1 1181 139 1182 3513 3514 3428 1820 9 2 0 1 1182 139 140 3514 179 3515 1821 9 2 0 1 1182 140 1183 3515 3516 3431 1822 9 2 0 1 1183 140 141 3516 180 3517 1823 9 2 0 1 1183 141 1184 3517 3518 3434 1824 9 2 0 1 1184 141 142 3518 181 3519 1825 9 2 0 1 1184 142 1185 3519 3520 3437 1826 9 2 0 1 1185 142 143 3520 182 3521 1827 9 2 0 1 1185 143 1186 3521 3522 3440 1828 9 2 0 1 1186 143 144 3522 183 3523 1829 9 2 0 1 1186 144 1187 3523 3524 3443 1830 9 2 0 1 1187 144 145 3524 184 3525 1831 9 2 0 1 1187 145 1188 3525 3526 3446 1832 9 2 0 1 1188 145 146 3526 185 3527 1833 9 2 0 1 1188 146 361 3527 3528 3449 1834 9 2 0 1 361 146 9 3528 186 380 1835 9 2 0 2 2 379 89 399 3700 98 1836 9 2 0 2 89 379 3529 3700 3701 3702 1837 9 2 0 2 89 3529 90 3702 3703 99 1838 9 2 0 2 90 3529 3530 3703 3704 3705 1839 9 2 0 2 90 3530 91 3705 3706 100 1840 9 2 0 2 91 3530 3531 3706 3707 3708 1841 9 2 0 2 91 3531 92 3708 3709 101 1842 9 2 0 2 92 3531 3532 3709 3710 3711 1843 9 2 0 2 92 3532 93 3711 3712 102 1844 9 2 0 2 93 3532 3533 3712 3713 3714 1845 9 2 0 2 93 3533 94 3714 3715 103 1846 9 2 0 2 94 3533 3534 3715 3716 3717 1847 9 2 0 2 94 3534 95 3717 3718 104 1848 9 2 0 2 95 3534 3535 3718 3719 3720 1849 9 2 0 2 95 3535 96 3720 3721 105 1850 9 2 0 2 96 3535 3536 3721 3722 3723 1851 9 2 0 2 96 3536 97 3723 3724 106 1852 9 2 0 2 97 3536 3537 3724 3725 3726 1853 9 2 0 2 97 3537 3 3726 3727 107 1854 9 2 0 2 3 3537 427 3727 3728 447 1855 9 2 0 2 379 378 3529 398 3729 3701 1856 9 2 0 2 3529 378 3538 3729 3730 3731 1857 9 2 0 2 3529 3538 3530 3731 3732 3704 1858 9 2 0 2 3530 3538 3539 3732 3733 3734 1859 9 2 0 2 3530 3539 3531 3734 3735 3707 1860 9 2 0 2 3531 3539 3540 3735 3736 3737 1861 9 2 0 2 3531 3540 3532 3737 3738 3710 1862 9 2 0 2 3532 3540 3541 3738 3739 3740 1863 9 2 0 2 3532 3541 3533 3740 3741 3713 1864 9 2 0 2 3533 3541 3542 3741 3742 3743 1865 9 2 0 2 3533 3542 3534 3743 3744 3716 1866 9 2 0 2 3534 3542 3543 3744 3745 3746 1867 9 2 0 2 3534 3543 3535 3746 3747 3719 1868 9 2 0 2 3535 3543 3544 3747 3748 3749 1869 9 2 0 2 3535 3544 3536 3749 3750 3722 1870 9 2 0 2 3536 3544 3545 3750 3751 3752 1871 9 2 0 2 3536 3545 3537 3752 3753 3725 1872 9 2 0 2 3537 3545 3546 3753 3754 3755 1873 9 2 0 2 3537 3546 427 3755 3756 3728 1874 9 2 0 2 427 3546 426 3756 3757 446 1875 9 2 0 2 378 377 3538 397 3758 3730 1876 9 2 0 2 3538 377 3547 3758 3759 3760 1877 9 2 0 2 3538 3547 3539 3760 3761 3733 1878 9 2 0 2 3539 3547 3548 3761 3762 3763 1879 9 2 0 2 3539 3548 3540 3763 3764 3736 1880 9 2 0 2 3540 3548 3549 3764 3765 3766 1881 9 2 0 2 3540 3549 3541 3766 3767 3739 1882 9 2 0 2 3541 3549 3550 3767 3768 3769 1883 9 2 0 2 3541 3550 3542 3769 3770 3742 1884 9 2 0 2 3542 3550 3551 3770 3771 3772 1885 9 2 0 2 3542 3551 3543 3772 3773 3745 1886 9 2 0 2 3543 3551 3552 3773 3774 3775 1887 9 2 0 2 3543 3552 3544 3775 3776 3748 1888 9 2 0 2 3544 3552 3553 3776 3777 3778 1889 9 2 0 2 3544 3553 3545 3778 3779 3751 1890 9 2 0 2 3545 3553 3554 3779 3780 3781 1891 9 2 0 2 3545 3554 3546 3781 3782 3754 1892 9 2 0 2 3546 3554 3555 3782 3783 3784 1893 9 2 0 2 3546 3555 426 3784 3785 3757 1894 9 2 0 2 426 3555 425 3785 3786 445 1895 9 2 0 2 377 376 3547 396 3787 3759 1896 9 2 0 2 3547 376 3556 3787 3788 3789 1897 9 2 0 2 3547 3556 3548 3789 3790 3762 1898 9 2 0 2 3548 3556 3557 3790 3791 3792 1899 9 2 0 2 3548 3557 3549 3792 3793 3765 1900 9 2 0 2 3549 3557 3558 3793 3794 3795 1901 9 2 0 2 3549 3558 3550 3795 3796 3768 1902 9 2 0 2 3550 3558 3559 3796 3797 3798 1903 9 2 0 2 3550 3559 3551 3798 3799 3771 1904 9 2 0 2 3551 3559 3560 3799 3800 3801 1905 9 2 0 2 3551 3560 3552 3801 3802 3774 1906 9 2 0 2 3552 3560 3561 3802 3803 3804 1907 9 2 0 2 3552 3561 3553 3804 3805 3777 1908 9 2 0 2 3553 3561 3562 3805 3806 3807 1909 9 2 0 2 3553 3562 3554 3807 3808 3780 1910 9 2 0 2 3554 3562 3563 3808 3809 3810 1911 9 2 0 2 3554 3563 3555 3810 3811 3783 1912 9 2 0 2 3555 3563 3564 3811 3812 3813 1913 9 2 0 2 3555 3564 425 3813 3814 3786 1914 9 2 0 2 425 3564 424 3814 3815 444 1915 9 2 0 2 376 375 3556 395 3816 3788 1916 9 2 0 2 3556 375 3565 3816 3817 3818 1917 9 2 0 2 3556 3565 3557 3818 3819 3791 1918 9 2 0 2 3557 3565 3566 3819 3820 3821 1919 9 2 0 2 3557 3566 3558 3821 3822 3794 1920 9 2 0 2 3558 3566 3567 3822 3823 3824 1921 9 2 0 2 3558 3567 3559 3824 3825 3797 1922 9 2 0 2 3559 3567 3568 3825 3826 3827 1923 9 2 0 2 3559 3568 3560 3827 3828 3800 1924 9 2 0 2 3560 3568 3569 3828 3829 3830 1925 9 2 0 2 3560 3569 3561 3830 3831 3803 1926 9 2 0 2 3561 3569 3570 3831 3832 3833 1927 9 2 0 2 3561 3570 3562 3833 3834 3806 1928 9 2 0 2 3562 3570 3571 3834 3835 3836 1929 9 2 0 2 3562 3571 3563 3836 3837 3809 1930 9 2 0 2 3563 3571 3572 3837 3838 3839 1931 9 2 0 2 3563 3572 3564 3839 3840 3812 1932 9 2 0 2 3564 3572 3573 3840 3841 3842 1933 9 2 0 2 3564 3573 424 3842 3843 3815 1934 9 2 0 2 424 3573 423 3843 3844 443 1935 9 2 0 2 375 374 3565 394 3845 3817 1936 9 2 0 2 3565 374 3574 3845 3846 3847 1937 9 2 0 2 3565 3574 3566 3847 3848 3820 1938 9 2 0 2 3566 3574 3575 3848 3849 3850 1939 9 2 0 2 3566 3575 3567 3850 3851 3823 1940 9 2 0 2 3567 3575 3576 3851 3852 3853 1941 9 2 0 2 3567 3576 3568 3853 3854 3826 1942 9 2 0 2 3568 3576 3577 3854 3855 3856 1943 9 2 0 2 3568 3577 3569 3856 3857 3829 1944 9 2 0 2 3569 3577 3578 3857 3858 3859 1945 9 2 0 2 3569 3578 3570 3859 3860 3832 1946 9 2 0 2 3570 3578 3579 3860 3861 3862 1947 9 2 0 2 3570 3579 3571 3862 3863 3835 1948 9 2 0 2 3571 3579 3580 3863 3864 3865 1949 9 2 0 2 3571 3580 3572 3865 3866 3838 1950 9 2 0 2 3572 3580 3581 3866 3867 3868 1951 9 2 0 2 3572 3581 3573 3868 3869 3841 1952 9 2 0 2 3573 3581 3582 3869 3870 3871 1953 9 2 0 2 3573 3582 423 3871 3872 3844 1954 9 2 0 2 423 3582 422 3872 3873 442 1955 9 2 0 2 374 373 3574 393 3874 3846 1956 9 2 0 2 3574 373 3583 3874 3875 3876 1957 9 2 0 2 3574 3583 3575 3876 3877 3849 1958 9 2 0 2 3575 3583 3584 3877 3878 3879 1959 9 2 0 2 3575 3584 3576 3879 3880 3852 1960 9 2 0 2 3576 3584 3585 3880 3881 3882 1961 9 2 0 2 3576 3585 3577 3882 3883 3855 1962 9 2 0 2 3577 3585 3586 3883 3884 3885 1963 9 2 0 2 3577 3586 3578 3885 3886 3858 1964 9 2 0 2 3578 3586 3587 3886 3887 3888 1965 9 2 0 2 3578 3587 3579 3888 3889 3861 1966 9 2 0 2 3579 3587 3588 3889 3890 3891 1967 9 2 0 2 3579 3588 3580 3891 3892 3864 1968 9 2 0 2 3580 3588 3589 3892 3893 3894 1969 9 2 0 2 3580 3589 3581 3894 3895 3867 1970 9 2 0 2 3581 3589 3590 3895 3896 3897 1971 9 2 0 2 3581 3590 3582 3897 3898 3870 1972 9 2 0 2 3582 3590 3591 3898 3899 3900 1973 9 2 0 2 3582 3591 422 3900 3901 3873 1974 9 2 0 2 422 3591 421 3901 3902 441 1975 9 2 0 2 373 372 3583 392 3903 3875 1976 9 2 0 2 3583 372 3592 3903 3904 3905 1977 9 2 0 2 3583 3592 3584 3905 3906 3878 1978 9 2 0 2 3584 3592 3593 3906 3907 3908 1979 9 2 0 2 3584 3593 3585 3908 3909 3881 1980 9 2 0 2 3585 3593 3594 3909 3910 3911 1981 9 2 0 2 3585 3594 3586 3911 3912 3884 1982 9 2 0 2 3586 3594 3595 3912 3913 3914 1983 9 2 0 2 3586 3595 3587 3914 3915 3887 1984 9 2 0 2 3587 3595 3596 3915 3916 3917 1985 9 2 0 2 3587 3596 3588 3917 3918 3890 1986 9 2 0 2 3588 3596 3597 3918 3919 3920 1987 9 2 0 2 3588 3597 3589 3920 3921 3893 1988 9 2 0 2 3589 3597 3598 3921 3922 3923 1989 9 2 0 2 3589 3598 3590 3923 3924 3896 1990 9 2 0 2 3590 3598 3599 3924 3925 3926 1991 9 2 0 2 3590 3599 3591 3926 3927 3899 1992 9 2 0 2 3591 3599 3600 3927 3928 3929 1993 9 2 0 2 3591 3600 421 3929 3930 3902 1994 9 2 0 2 421 3600 420 3930 3931 440 1995 9 2 0 2 372 371 3592 391 3932 3904 1996 9 2 0 2 3592 371 3601 3932 3933 3934 1997 9 2 0 2 3592 3601 3593 3934 3935 3907 1998 9 2 0 2 3593 3601 3602 3935 3936 3937 1999 9 2 0 2 3593 3602 3594 3937 3938 3910 2000 9 2 0 2 3594 3602 3603 3938 3939 3940 2001 9 2 0 2 3594 3603 3595 3940 3941 3913 2002 9 2 0 2 3595 3603 3604 3941 3942 3943 2003 9 2 0 2 3595 3604 3596 3943 3944 3916 2004 9 2 0 2 3596 3604 3605 3944 3945 3946 2005 9 2 0 2 3596 3605 3597 3946 3947 3919 2006 9 2 0 2 3597 3605 3606 3947 3948 3949 2007 9 2 0 2 3597 3606 3598 3949 3950 3922 2008 9 2 0 2 3598 3606 3607 3950 3951 3952 2009 9 2 0 2 3598 3607 3599 3952 3953 3925 2010 9 2 0 2 3599 3607 3608 3953 3954 3955 2011 9 2 0 2 3599 3608 3600 3955 3956 3928 2012 9 2 0 2 3600 3608 3609 3956 3957 3958 2013 9 2 0 2 3600 3609 420 3958 3959 3931 2014 9 2 0 2 420 3609 419 3959 3960 439 2015 9 2 0 2 371 370 3601 390 3961 3933 2016 9 2 0 2 3601 370 3610 3961 3962 3963 2017 9 2 0 2 3601 3610 3602 3963 3964 3936 2018 9 2 0 2 3602 3610 3611 3964 3965 3966 2019 9 2 0 2 3602 3611 3603 3966 3967 3939 2020 9 2 0 2 3603 3611 3612 3967 3968 3969 2021 9 2 0 2 3603 3612 3604 3969 3970 3942 2022 9 2 0 2 3604 3612 3613 3970 3971 3972 2023 9 2 0 2 3604 3613 3605 3972 3973 3945 2024 9 2 0 2 3605 3613 3614 3973 3974 3975 2025 9 2 0 2 3605 3614 3606 3975 3976 3948 2026 9 2 0 2 3606 3614 3615 3976 3977 3978 2027 9 2 0 2 3606 3615 3607 3978 3979 3951 2028 9 2 0 2 3607 3615 3616 3979 3980 3981 2029 9 2 0 2 3607 3616 3608 3981 3982 3954 2030 9 2 0 2 3608 3616 3617 3982 3983 3984 2031 9 2 0 2 3608 3617 3609 3984 3985 3957 2032 9 2 0 2 3609 3617 3618 3985 3986 3987 2033 9 2 0 2 3609 3618 419 3987 3988 3960 2034 9 2 0 2 419 3618 418 3988 3989 438 2035 9 2 0 2 370 369 3610 389 3990 3962 2036 9 2 0 2 3610 369 3619 3990 3991 3992 2037 9 2 0 2 3610 3619 3611 3992 3993 3965 2038 9 2 0 2 3611 3619 3620 3993 3994 3995 2039 9 2 0 2 3611 3620 3612 3995 3996 3968 2040 9 2 0 2 3612 3620 3621 3996 3997 3998 2041 9 2 0 2 3612 3621 3613 3998 3999 3971 2042 9 2 0 2 3613 3621 3622 3999 4000 4001 2043 9 2 0 2 3613 3622 3614 4001 4002 3974 2044 9 2 0 2 3614 3622 3623 4002 4003 4004 2045 9 2 0 2 3614 3623 3615 4004 4005 3977 2046 9 2 0 2 3615 3623 3624 4005 4006 4007 2047 9 2 0 2 3615 3624 3616 4007 4008 3980 2048 9 2 0 2 3616 3624 3625 4008 4009 4010 2049 9 2 0 2 3616 3625 3617 4010 4011 3983 2050 9 2 0 2 3617 3625 3626 4011 4012 4013 2051 9 2 0 2 3617 3626 3618 4013 4014 3986 2052 9 2 0 2 3618 3626 3627 4014 4015 4016 2053 9 2 0 2 3618 3627 418 4016 4017 3989 2054 9 2 0 2 418 3627 417 4017 4018 437 2055 9 2 0 2 369 368 3619 388 4019 3991 2056 9 2 0 2 3619 368 3628 4019 4020 4021 2057 9 2 0 2 3619 3628 3620 4021 4022 3994 2058 9 2 0 2 3620 3628 3629 4022 4023 4024 2059 9 2 0 2 3620 3629 3621 4024 4025 3997 2060 9 2 0 2 3621 3629 3630 4025 4026 4027 2061 9 2 0 2 3621 3630 3622 4027 4028 4000 2062 9 2 0 2 3622 3630 3631 4028 4029 4030 2063 9 2 0 2 3622 3631 3623 4030 4031 4003 2064 9 2 0 2 3623 3631 3632 4031 4032 4033 2065 9 2 0 2 3623 3632 3624 4033 4034 4006 2066 9 2 0 2 3624 3632 3633 4034 4035 4036 2067 9 2 0 2 3624 3633 3625 4036 4037 4009 2068 9 2 0 2 3625 3633 3634 4037 4038 4039 2069 9 2 0 2 3625 3634 3626 4039 4040 4012 2070 9 2 0 2 3626 3634 3635 4040 4041 4042 2071 9 2 0 2 3626 3635 3627 4042 4043 4015 2072 9 2 0 2 3627 3635 3636 4043 4044 4045 2073 9 2 0 2 3627 3636 417 4045 4046 4018 2074 9 2 0 2 417 3636 416 4046 4047 436 2075 9 2 0 2 368 367 3628 387 4048 4020 2076 9 2 0 2 3628 367 3637 4048 4049 4050 2077 9 2 0 2 3628 3637 3629 4050 4051 4023 2078 9 2 0 2 3629 3637 3638 4051 4052 4053 2079 9 2 0 2 3629 3638 3630 4053 4054 4026 2080 9 2 0 2 3630 3638 3639 4054 4055 4056 2081 9 2 0 2 3630 3639 3631 4056 4057 4029 2082 9 2 0 2 3631 3639 3640 4057 4058 4059 2083 9 2 0 2 3631 3640 3632 4059 4060 4032 2084 9 2 0 2 3632 3640 3641 4060 4061 4062 2085 9 2 0 2 3632 3641 3633 4062 4063 4035 2086 9 2 0 2 3633 3641 3642 4063 4064 4065 2087 9 2 0 2 3633 3642 3634 4065 4066 4038 2088 9 2 0 2 3634 3642 3643 4066 4067 4068 2089 9 2 0 2 3634 3643 3635 4068 4069 4041 2090 9 2 0 2 3635 3643 3644 4069 4070 4071 2091 9 2 0 2 3635 3644 3636 4071 4072 4044 2092 9 2 0 2 3636 3644 3645 4072 4073 4074 2093 9 2 0 2 3636 3645 416 4074 4075 4047 2094 9 2 0 2 416 3645 415 4075 4076 435 2095 9 2 0 2 367 366 3637 386 4077 4049 2096 9 2 0 2 3637 366 3646 4077 4078 4079 2097 9 2 0 2 3637 3646 3638 4079 4080 4052 2098 9 2 0 2 3638 3646 3647 4080 4081 4082 2099 9 2 0 2 3638 3647 3639 4082 4083 4055 2100 9 2 0 2 3639 3647 3648 4083 4084 4085 2101 9 2 0 2 3639 3648 3640 4085 4086 4058 2102 9 2 0 2 3640 3648 3649 4086 4087 4088 2103 9 2 0 2 3640 3649 3641 4088 4089 4061 2104 9 2 0 2 3641 3649 3650 4089 4090 4091 2105 9 2 0 2 3641 3650 3642 4091 4092 4064 2106 9 2 0 2 3642 3650 3651 4092 4093 4094 2107 9 2 0 2 3642 3651 3643 4094 4095 4067 2108 9 2 0 2 3643 3651 3652 4095 4096 4097 2109 9 2 0 2 3643 3652 3644 4097 4098 4070 2110 9 2 0 2 3644 3652 3653 4098 4099 4100 2111 9 2 0 2 3644 3653 3645 4100 4101 4073 2112 9 2 0 2 3645 3653 3654 4101 4102 4103 2113 9 2 0 2 3645 3654 415 4103 4104 4076 2114 9 2 0 2 415 3654 414 4104 4105 434 2115 9 2 0 2 366 365 3646 385 4106 4078 2116 9 2 0 2 3646 365 3655 4106 4107 4108 2117 9 2 0 2 3646 3655 3647 4108 4109 4081 2118 9 2 0 2 3647 3655 3656 4109 4110 4111 2119 9 2 0 2 3647 3656 3648 4111 4112 4084 2120 9 2 0 2 3648 3656 3657 4112 4113 4114 2121 9 2 0 2 3648 3657 3649 4114 4115 4087 2122 9 2 0 2 3649 3657 3658 4115 4116 4117 2123 9 2 0 2 3649 3658 3650 4117 4118 4090 2124 9 2 0 2 3650 3658 3659 4118 4119 4120 2125 9 2 0 2 3650 3659 3651 4120 4121 4093 2126 9 2 0 2 3651 3659 3660 4121 4122 4123 2127 9 2 0 2 3651 3660 3652 4123 4124 4096 2128 9 2 0 2 3652 3660 3661 4124 4125 4126 2129 9 2 0 2 3652 3661 3653 4126 4127 4099 2130 9 2 0 2 3653 3661 3662 4127 4128 4129 2131 9 2 0 2 3653 3662 3654 4129 4130 4102 2132 9 2 0 2 3654 3662 3663 4130 4131 4132 2133 9 2 0 2 3654 3663 414 4132 4133 4105 2134 9 2 0 2 414 3663 413 4133 4134 433 2135 9 2 0 2 365 364 3655 384 4135 4107 2136 9 2 0 2 3655 364 3664 4135 4136 4137 2137 9 2 0 2 3655 3664 3656 4137 4138 4110 2138 9 2 0 2 3656 3664 3665 4138 4139 4140 2139 9 2 0 2 3656 3665 3657 4140 4141 4113 2140 9 2 0 2 3657 3665 3666 4141 4142 4143 2141 9 2 0 2 3657 3666 3658 4143 4144 4116 2142 9 2 0 2 3658 3666 3667 4144 4145 4146 2143 9 2 0 2 3658 3667 3659 4146 4147 4119 2144 9 2 0 2 3659 3667 3668 4147 4148 4149 2145 9 2 0 2 3659 3668 3660 4149 4150 4122 2146 9 2 0 2 3660 3668 3669 4150 4151 4152 2147 9 2 0 2 3660 3669 3661 4152 4153 4125 2148 9 2 0 2 3661 3669 3670 4153 4154 4155 2149 9 2 0 2 3661 3670 3662 4155 4156 4128 2150 9 2 0 2 3662 3670 3671 4156 4157 4158 2151 9 2 0 2 3662 3671 3663 4158 4159 4131 2152 9 2 0 2 3663 3671 3672 4159 4160 4161 2153 9 2 0 2 3663 3672 413 4161 4162 4134 2154 9 2 0 2 413 3672 412 4162 4163 432 2155 9 2 0 2 364 363 3664 383 4164 4136 2156 9 2 0 2 3664 363 3673 4164 4165 4166 2157 9 2 0 2 3664 3673 3665 4166 4167 4139 2158 9 2 0 2 3665 3673 3674 4167 4168 4169 2159 9 2 0 2 3665 3674 3666 4169 4170 4142 2160 9 2 0 2 3666 3674 3675 4170 4171 4172 2161 9 2 0 2 3666 3675 3667 4172 4173 4145 2162 9 2 0 2 3667 3675 3676 4173 4174 4175 2163 9 2 0 2 3667 3676 3668 4175 4176 4148 2164 9 2 0 2 3668 3676 3677 4176 4177 4178 2165 9 2 0 2 3668 3677 3669 4178 4179 4151 2166 9 2 0 2 3669 3677 3678 4179 4180 4181 2167 9 2 0 2 3669 3678 3670 4181 4182 4154 2168 9 2 0 2 3670 3678 3679 4182 4183 4184 2169 9 2 0 2 3670 3679 3671 4184 4185 4157 2170 9 2 0 2 3671 3679 3680 4185 4186 4187 2171 9 2 0 2 3671 3680 3672 4187 4188 4160 2172 9 2 0 2 3672 3680 3681 4188 4189 4190 2173 9 2 0 2 3672 3681 412 4190 4191 4163 2174 9 2 0 2 412 3681 411 4191 4192 431 2175 9 2 0 2 363 362 3673 382 4193 4165 2176 9 2 0 2 3673 362 3682 4193 4194 4195 2177 9 2 0 2 3673 3682 3674 4195 4196 4168 2178 9 2 0 2 3674 3682 3683 4196 4197 4198 2179 9 2 0 2 3674 3683 3675 4198 4199 4171 2180 9 2 0 2 3675 3683 3684 4199 4200 4201 2181 9 2 0 2 3675 3684 3676 4201 4202 4174 2182 9 2 0 2 3676 3684 3685 4202 4203 4204 2183 9 2 0 2 3676 3685 3677 4204 4205 4177 2184 9 2 0 2 3677 3685 3686 4205 4206 4207 2185 9 2 0 2 3677 3686 3678 4207 4208 4180 2186 9 2 0 2 3678 3686 3687 4208 4209 4210 2187 9 2 0 2 3678 3687 3679 4210 4211 4183 2188 9 2 0 2 3679 3687 3688 4211 4212 4213 2189 9 2 0 2 3679 3688 3680 4213 4214 4186 2190 9 2 0 2 3680 3688 3689 4214 4215 4216 2191 9 2 0 2 3680 3689 3681 4216 4217 4189 2192 9 2 0 2 3681 3689 3690 4217 4218 4219 2193 9 2 0 2 3681 3690 411 4219 4220 4192 2194 9 2 0 2 411 3690 410 4220 4221 430 2195 9 2 0 2 362 361 3682 381 4222 4194 2196 9 2 0 2 3682 361 3691 4222 4223 4224 2197 9 2 0 2 3682 3691 3683 4224 4225 4197 2198 9 2 0 2 3683 3691 3692 4225 4226 4227 2199 9 2 0 2 3683 3692 3684 4227 4228 4200 2200 9 2 0 2 3684 3692 3693 4228 4229 4230 2201 9 2 0 2 3684 3693 3685 4230 4231 4203 2202 9 2 0 2 3685 3693 3694 4231 4232 4233 2203 9 2 0 2 3685 3694 3686 4233 4234 4206 2204 9 2 0 2 3686 3694 3695 4234 4235 4236 2205 9 2 0 2 3686 3695 3687 4236 4237 4209 2206 9 2 0 2 3687 3695 3696 4237 4238 4239 2207 9 2 0 2 3687 3696 3688 4239 4240 4212 2208 9 2 0 2 3688 3696 3697 4240 4241 4242 2209 9 2 0 2 3688 3697 3689 4242 4243 4215 2210 9 2 0 2 3689 3697 3698 4243 4244 4245 2211 9 2 0 2 3689 3698 3690 4245 4246 4218 2212 9 2 0 2 3690 3698 3699 4246 4247 4248 2213 9 2 0 2 3690 3699 410 4248 4249 4221 2214 9 2 0 2 410 3699 409 4249 4250 429 2215 9 2 0 2 361 9 3691 380 4251 4223 2216 9 2 0 2 3691 9 187 4251 196 4252 2217 9 2 0 2 3691 187 3692 4252 4253 4226 2218 9 2 0 2 3692 187 188 4253 197 4254 2219 9 2 0 2 3692 188 3693 4254 4255 4229 2220 9 2 0 2 3693 188 189 4255 198 4256 2221 9 2 0 2 3693 189 3694 4256 4257 4232 2222 9 2 0 2 3694 189 190 4257 199 4258 2223 9 2 0 2 3694 190 3695 4258 4259 4235 2224 9 2 0 2 3695 190 191 4259 200 4260 2225 9 2 0 2 3695 191 3696 4260 4261 4238 2226 9 2 0 2 3696 191 192 4261 201 4262 2227 9 2 0 2 3696 192 3697 4262 4263 4241 2228 9 2 0 2 3697 192 193 4263 202 4264 2229 9 2 0 2 3697 193 3698 4264 4265 4244 2230 9 2 0 2 3698 193 194 4265 203 4266 2231 9 2 0 2 3698 194 3699 4266 4267 4247 2232 9 2 0 2 3699 194 195 4267 204 4268 2233 9 2 0 2 3699 195 409 4268 4269 4250 2234 9 2 0 2 409 195 4 4269 205 428 2235 9 2 0 3 8 307 108 312 4426 147 2236 9 2 0 3 108 307 4270 4426 4427 4428 2237 9 2 0 3 108 4270 109 4428 4429 148 2238 9 2 0 3 109 4270 4271 4429 4430 4431 2239 9 2 0 3 109 4271 110 4431 4432 149 2240 9 2 0 3 110 4271 4272 4432 4433 4434 2241 9 2 0 3 110 4272 111 4434 4435 150 2242 9 2 0 3 111 4272 4273 4435 4436 4437 2243 9 2 0 3 111 4273 112 4437 4438 151 2244 9 2 0 3 112 4273 4274 4438 4439 4440 2245 9 2 0 3 112 4274 113 4440 4441 152 2246 9 2 0 3 113 4274 4275 4441 4442 4443 2247 9 2 0 3 113 4275 114 4443 4444 153 2248 9 2 0 3 114 4275 4276 4444 4445 4446 2249 9 2 0 3 114 4276 115 4446 4447 154 2250 9 2 0 3 115 4276 4277 4447 4448 4449 2251 9 2 0 3 115 4277 116 4449 4450 155 2252 9 2 0 3 116 4277 4278 4450 4451 4452 2253 9 2 0 3 116 4278 117 4452 4453 156 2254 9 2 0 3 117 4278 4279 4453 4454 4455 2255 9 2 0 3 117 4279 118 4455 4456 157 2256 9 2 0 3 118 4279 4280 4456 4457 4458 2257 9 2 0 3 118 4280 119 4458 4459 158 2258 9 2 0 3 119 4280 4281 4459 4460 4461 2259 9 2 0 3 119 4281 120 4461 4462 159 2260 9 2 0 3 120 4281 4282 4462 4463 4464 2261 9 2 0 3 120 4282 121 4464 4465 160 2262 9 2 0 3 121 4282 4283 4465 4466 4467 2263 9 2 0 3 121 4283 122 4467 4468 161 2264 9 2 0 3 122 4283 4284 4468 4469 4470 2265 9 2 0 3 122 4284 123 4470 4471 162 2266 9 2 0 3 123 4284 4285 4471 4472 4473 2267 9 2 0 3 123 4285 124 4473 4474 163 2268 9 2 0 3 124 4285 4286 4474 4475 4476 2269 9 2 0 3 124 4286 125 4476 4477 164 2270 9 2 0 3 125 4286 4287 4477 4478 4479 2271 9 2 0 3 125 4287 126 4479 4480 165 2272 9 2 0 3 126 4287 4288 4480 4481 4482 2273 9 2 0 3 126 4288 127 4482 4483 166 2274 9 2 0 3 127 4288 4289 4483 4484 4485 2275 9 2 0 3 127 4289 128 4485 4486 167 2276 9 2 0 3 128 4289 4290 4486 4487 4488 2277 9 2 0 3 128 4290 129 4488 4489 168 2278 9 2 0 3 129 4290 4291 4489 4490 4491 2279 9 2 0 3 129 4291 130 4491 4492 169 2280 9 2 0 3 130 4291 4292 4492 4493 4494 2281 9 2 0 3 130 4292 131 4494 4495 170 2282 9 2 0 3 131 4292 4293 4495 4496 4497 2283 9 2 0 3 131 4293 132 4497 4498 171 2284 9 2 0 3 132 4293 4294 4498 4499 4500 2285 9 2 0 3 132 4294 133 4500 4501 172 2286 9 2 0 3 133 4294 4295 4501 4502 4503 2287 9 2 0 3 133 4295 134 4503 4504 173 2288 9 2 0 3 134 4295 4296 4504 4505 4506 2289 9 2 0 3 134 4296 135 4506 4507 174 2290 9 2 0 3 135 4296 4297 4507 4508 4509 2291 9 2 0 3 135 4297 136 4509 4510 175 2292 9 2 0 3 136 4297 4298 4510 4511 4512 2293 9 2 0 3 136 4298 137 4512 4513 176 2294 9 2 0 3 137 4298 4299 4513 4514 4515 2295 9 2 0 3 137 4299 138 4515 4516 177 2296 9 2 0 3 138 4299 4300 4516 4517 4518 2297 9 2 0 3 138 4300 139 4518 4519 178 2298 9 2 0 3 139 4300 4301 4519 4520 4521 2299 9 2 0 3 139 4301 140 4521 4522 179 2300 9 2 0 3 140 4301 4302 4522 4523 4524 2301 9 2 0 3 140 4302 141 4524 4525 180 2302 9 2 0 3 141 4302 4303 4525 4526 4527 2303 9 2 0 3 141 4303 142 4527 4528 181 2304 9 2 0 3 142 4303 4304 4528 4529 4530 2305 9 2 0 3 142 4304 143 4530 4531 182 2306 9 2 0 3 143 4304 4305 4531 4532 4533 2307 9 2 0 3 143 4305 144 4533 4534 183 2308 9 2 0 3 144 4305 4306 4534 4535 4536 2309 9 2 0 3 144 4306 145 4536 4537 184 2310 9 2 0 3 145 4306 4307 4537 4538 4539 2311 9 2 0 3 145 4307 146 4539 4540 185 2312 9 2 0 3 146 4307 4308 4540 4541 4542 2313 9 2 0 3 146 4308 9 4542 4543 186 2314 9 2 0 3 9 4308 355 4543 4544 360 2315 9 2 0 3 307 306 4270 311 4545 4427 2316 9 2 0 3 4270 306 4309 4545 4546 4547 2317 9 2 0 3 4270 4309 4271 4547 4548 4430 2318 9 2 0 3 4271 4309 4310 4548 4549 4550 2319 9 2 0 3 4271 4310 4272 4550 4551 4433 2320 9 2 0 3 4272 4310 4311 4551 4552 4553 2321 9 2 0 3 4272 4311 4273 4553 4554 4436 2322 9 2 0 3 4273 4311 4312 4554 4555 4556 2323 9 2 0 3 4273 4312 4274 4556 4557 4439 2324 9 2 0 3 4274 4312 4313 4557 4558 4559 2325 9 2 0 3 4274 4313 4275 4559 4560 4442 2326 9 2 0 3 4275 4313 4314 4560 4561 4562 2327 9 2 0 3 4275 4314 4276 4562 4563 4445 2328 9 2 0 3 4276 4314 4315 4563 4564 4565 2329 9 2 0 3 4276 4315 4277 4565 4566 4448 2330 9 2 0 3 4277 4315 4316 4566 4567 4568 2331 9 2 0 3 4277 4316 4278 4568 4569 4451 2332 9 2 0 3 4278 4316 4317 4569 4570 4571 2333 9 2 0 3 4278 4317 4279 4571 4572 4454 2334 9 2 0 3 4279 4317 4318 4572 4573 4574 2335 9 2 0 3 4279 4318 4280 4574 4575 4457 2336 9 2 0 3 4280 4318 4319 4575 4576 4577 2337 9 2 0 3 4280 4319 4281 4577 4578 4460 2338 9 2 0 3 4281 4319 4320 4578 4579 4580 2339 9 2 0 3 4281 4320 4282 4580 4581 4463 2340 9 2 0 3 4282 4320 4321 4581 4582 4583 2341 9 2 0 3 4282 4321 4283 4583 4584 4466 2342 9 2 0 3 4283 4321 4322 4584 4585 4586 2343 9 2 0 3 4283 4322 4284 4586 4587 4469 2344 9 2 0 3 4284 4322 4323 4587 4588 4589 2345 9 2 0 3 4284 4323 4285 4589 4590 4472 2346 9 2 0 3 4285 4323 4324 4590 4591 4592 2347 9 2 0 3 4285 4324 4286 4592 4593 4475 2348 9 2 0 3 4286 4324 4325 4593 4594 4595 2349 9 2 0 3 4286 4325 4287 4595 4596 4478 2350 9 2 0 3 4287 4325 4326 4596 4597 4598 2351 9 2 0 3 4287 4326 4288 4598 4599 4481 2352 9 2 0 3 4288 4326 4327 4599 4600 4601 2353 9 2 0 3 4288 4327 4289 4601 4602 4484 2354 9 2 0 3 4289 4327 4328 4602 4603 4604 2355 9 2 0 3 4289 4328 4290 4604 4605 4487 2356 9 2 0 3 4290 4328 4329 4605 4606 4607 2357 9 2 0 3 4290 4329 4291 4607 4608 4490 2358 9 2 0 3 4291 4329 4330 4608 4609 4610 2359 9 2 0 3 4291 4330 4292 4610 4611 4493 2360 9 2 0 3 4292 4330 4331 4611 4612 4613 2361 9 2 0 3 4292 4331 4293 4613 4614 4496 2362 9 2 0 3 4293 4331 4332 4614 4615 4616 2363 9 2 0 3 4293 4332 4294 4616 4617 4499 2364 9 2 0 3 4294 4332 4333 4617 4618 4619 2365 9 2 0 3 4294 4333 4295 4619 4620 4502 2366 9 2 0 3 4295 4333 4334 4620 4621 4622 2367 9 2 0 3 4295 4334 4296 4622 4623 4505 2368 9 2 0 3 4296 4334 4335 4623 4624 4625 2369 9 2 0 3 4296 4335 4297 4625 4626 4508 2370 9 2 0 3 4297 4335 4336 4626 4627 4628 2371 9 2 0 3 4297 4336 4298 4628 4629 4511 2372 9 2 0 3 4298 4336 4337 4629 4630 4631 2373 9 2 0 3 4298 4337 4299 4631 4632 4514 2374 9 2 0 3 4299 4337 4338 4632 4633 4634 2375 9 2 0 3 4299 4338 4300 4634 4635 4517 2376 9 2 0 3 4300 4338 4339 4635 4636 4637 2377 9 2 0 3 4300 4339 4301 4637 4638 4520 2378 9 2 0 3 4301 4339 4340 4638 4639 4640 2379 9 2 0 3 4301 4340 4302 4640 4641 4523 2380 9 2 0 3 4302 4340 4341 4641 4642 4643 2381 9 2 0 3 4302 4341 4303 4643 4644 4526 2382 9 2 0 3 4303 4341 4342 4644 4645 4646 2383 9 2 0 3 4303 4342 4304 4646 4647 4529 2384 9 2 0 3 4304 4342 4343 4647 4648 4649 2385 9 2 0 3 4304 4343 4305 4649 4650 4532 2386 9 2 0 3 4305 4343 4344 4650 4651 4652 2387 9 2 0 3 4305 4344 4306 4652 4653 4535 2388 9 2 0 3 4306 4344 4345 4653 4654 4655 2389 9 2 0 3 4306 4345 4307 4655 4656 4538 2390 9 2 0 3 4307 4345 4346 4656 4657 4658 2391 9 2 0 3 4307 4346 4308 4658 4659 4541 2392 9 2 0 3 4308 4346 4347 4659 4660 4661 2393 9 2 0 3 4308 4347 355 4661 4662 4544 2394 9 2 0 3 355 4347 354 4662 4663 359 2395 9 2 0 3 306 305 4309 310 4664 4546 2396 9 2 0 3 4309 305 4348 4664 4665 4666 2397 9 2 0 3 4309 4348 4310 4666 4667 4549 2398 9 2 0 3 4310 4348 4349 4667 4668 4669 2399 9 2 0 3 4310 4349 4311 4669 4670 4552 2400 9 2 0 3 4311 4349 4350 4670 4671 4672 2401 9 2 0 3 4311 4350 4312 4672 4673 4555 2402 9 2 0 3 4312 4350 4351 4673 4674 4675 2403 9 2 0 3 4312 4351 4313 4675 4676 4558 2404 9 2 0 3 4313 4351 4352 4676 4677 4678 2405 9 2 0 3 4313 4352 4314 4678 4679 4561 2406 9 2 0 3 4314 4352 4353 4679 4680 4681 2407 9 2 0 3 4314 4353 4315 4681 4682 4564 2408 9 2 0 3 4315 4353 4354 4682 4683 4684 2409 9 2 0 3 4315 4354 4316 4684 4685 4567 2410 9 2 0 3 4316 4354 4355 4685 4686 4687 2411 9 2 0 3 4316 4355 4317 4687 4688 4570 2412 9 2 0 3 4317 4355 4356 4688 4689 4690 2413 9 2 0 3 4317 4356 4318 4690 4691 4573 2414 9 2 0 3 4318 4356 4357 4691 4692 4693 2415 9 2 0 3 4318 4357 4319 4693 4694 4576 2416 9 2 0 3 4319 4357 4358 4694 4695 4696 2417 9 2 0 3 4319 4358 4320 4696 4697 4579 2418 9 2 0 3 4320 4358 4359 4697 4698 4699 2419 9 2 0 3 4320 4359 4321 4699 4700 4582 2420 9 2 0 3 4321 4359 4360 4700 4701 4702 2421 9 2 0 3 4321 4360 4322 4702 4703 4585 2422 9 2 0 3 4322 4360 4361 4703 4704 4705 2423 9 2 0 3 4322 4361 4323 4705 4706 4588 2424 9 2 0 3 4323 4361 4362 4706 4707 4708 2425 9 2 0 3 4323 4362 4324 4708 4709 4591 2426 9 2 0 3 4324 4362 4363 4709 4710 4711 2427 9 2 0 3 4324 4363 4325 4711 4712 4594 2428 9 2 0 3 4325 4363 4364 4712 4713 4714 2429 9 2 0 3 4325 4364 4326 4714 4715 4597 2430 9 2 0 3 4326 4364 4365 4715 4716 4717 2431 9 2 0 3 4326 4365 4327 4717 4718 4600 2432 9 2 0 3 4327 4365 4366 4718 4719 4720 2433 9 2 0 3 4327 4366 4328 4720 4721 4603 2434 9 2 0 3 4328 4366 4367 4721 4722 4723 2435 9 2 0 3 4328 4367 4329 4723 4724 4606 2436 9 2 0 3 4329 4367 4368 4724 4725 4726 2437 9 2 0 3 4329 4368 4330 4726 4727 4609 2438 9 2 0 3 4330 4368 4369 4727 4728 4729 2439 9 2 0 3 4330 4369 4331 4729 4730 4612 2440 9 2 0 3 4331 4369 4370 4730 4731 4732 2441 9 2 0 3 4331 4370 4332 4732 4733 4615 2442 9 2 0 3 4332 4370 4371 4733 4734 4735 2443 9 2 0 3 4332 4371 4333 4735 4736 4618 2444 9 2 0 3 4333 4371 4372 4736 4737 4738 2445 9 2 0 3 4333 4372 4334 4738 4739 4621 2446 9 2 0 3 4334 4372 4373 4739 4740 4741 2447 9 2 0 3 4334 4373 4335 4741 4742 4624 2448 9 2 0 3 4335 4373 4374 4742 4743 4744 2449 9 2 0 3 4335 4374 4336 4744 4745 4627 2450 9 2 0 3 4336 4374 4375 4745 4746 4747 2451 9 2 0 3 4336 4375 4337 4747 4748 4630 2452 9 2 0 3 4337 4375 4376 4748 4749 4750 2453 9 2 0 3 4337 4376 4338 4750 4751 4633 2454 9 2 0 3 4338 4376 4377 4751 4752 4753 2455 9 2 0 3 4338 4377 4339 4753 4754 4636 2456 9 2 0 3 4339 4377 4378 4754 4755 4756 2457 9 2 0 3 4339 4378 4340 4756 4757 4639 2458 9 2 0 3 4340 4378 4379 4757 4758 4759 2459 9 2 0 3 4340 4379 4341 4759 4760 4642 2460 9 2 0 3 4341 4379 4380 4760 4761 4762 2461 9 2 0 3 4341 4380 4342 4762 4763 4645 2462 9 2 0 3 4342 4380 4381 4763 4764 4765 2463 9 2 0 3 4342 4381 4343 4765 4766 4648 2464 9 2 0 3 4343 4381 4382 4766 4767 4768 2465 9 2 0 3 4343 4382 4344 4768 4769 4651 2466 9 2 0 3 4344 4382 4383 4769 4770 4771 2467 9 2 0 3 4344 4383 4345 4771 4772 4654 2468 9 2 0 3 4345 4383 4384 4772 4773 4774 2469 9 2 0 3 4345 4384 4346 4774 4775 4657 2470 9 2 0 3 4346 4384 4385 4775 4776 4777 2471 9 2 0 3 4346 4385 4347 4777 4778 4660 2472 9 2 0 3 4347 4385 4386 4778 4779 4780 2473 9 2 0 3 4347 4386 354 4780 4781 4663 2474 9 2 0 3 354 4386 353 4781 4782 358 2475 9 2 0 3 305 304 4348 309 4783 4665 2476 9 2 0 3 4348 304 4387 4783 4784 4785 2477 9 2 0 3 4348 4387 4349 4785 4786 4668 2478 9 2 0 3 4349 4387 4388 4786 4787 4788 2479 9 2 0 3 4349 4388 4350 4788 4789 4671 2480 9 2 0 3 4350 4388 4389 4789 4790 4791 2481 9 2 0 3 4350 4389 4351 4791 4792 4674 2482 9 2 0 3 4351 4389 4390 4792 4793 4794 2483 9 2 0 3 4351 4390 4352 4794 4795 4677 2484 9 2 0 3 4352 4390 4391 4795 4796 4797 2485 9 2 0 3 4352 4391 4353 4797 4798 4680 2486 9 2 0 3 4353 4391 4392 4798 4799 4800 2487 9 2 0 3 4353 4392 4354 4800 4801 4683 2488 9 2 0 3 4354 4392 4393 4801 4802 4803 2489 9 2 0 3 4354 4393 4355 4803 4804 4686 2490 9 2 0 3 4355 4393 4394 4804 4805 4806 2491 9 2 0 3 4355 4394 4356 4806 4807 4689 2492 9 2 0 3 4356 4394 4395 4807 4808 4809 2493 9 2 0 3 4356 4395 4357 4809 4810 4692 2494 9 2 0 3 4357 4395 4396 4810 4811 4812 2495 9 2 0 3 4357 4396 4358 4812 4813 4695 2496 9 2 0 3 4358 4396 4397 4813 4814 4815 2497 9 2 0 3 4358 4397 4359 4815 4816 4698 2498 9 2 0 3 4359 4397 4398 4816 4817 4818 2499 9 2 0 3 4359 4398 4360 4818 4819 4701 2500 9 2 0 3 4360 4398 4399 4819 4820 4821 2501 9 2 0 3 4360 4399 4361 4821 4822 4704 2502 9 2 0 3 4361 4399 4400 4822 4823 4824 2503 9 2 0 3 4361 4400 4362 4824 4825 4707 2504 9 2 0 3 4362 4400 4401 4825 4826 4827 2505 9 2 0 3 4362 4401 4363 4827 4828 4710 2506 9 2 0 3 4363 4401 4402 4828 4829 4830 2507 9 2 0 3 4363 4402 4364 4830 4831 4713 2508 9 2 0 3 4364 4402 4403 4831 4832 4833 2509 9 2 0 3 4364 4403 4365 4833 4834 4716 2510 9 2 0 3 4365 4403 4404 4834 4835 4836 2511 9 2 0 3 4365 4404 4366 4836 4837 4719 2512 9 2 0 3 4366 4404 4405 4837 4838 4839 2513 9 2 0 3 4366 4405 4367 4839 4840 4722 2514 9 2 0 3 4367 4405 4406 4840 4841 4842 2515 9 2 0 3 4367 4406 4368 4842 4843 4725 2516 9 2 0 3 4368 4406 4407 4843 4844 4845 2517 9 2 0 3 4368 4407 4369 4845 4846 4728 2518 9 2 0 3 4369 4407 4408 4846 4847 4848 2519 9 2 0 3 4369 4408 4370 4848 4849 4731 2520 9 2 0 3 4370 4408 4409 4849 4850 4851 2521 9 2 0 3 4370 4409 4371 4851 4852 4734 2522 9 2 0 3 4371 4409 4410 4852 4853 4854 2523 9 2 0 3 4371 4410 4372 4854 4855 4737 2524 9 2 0 3 4372 4410 4411 4855 4856 4857 2525 9 2 0 3 4372 4411 4373 4857 4858 4740 2526 9 2 0 3 4373 4411 4412 4858 4859 4860 2527 9 2 0 3 4373 4412 4374 4860 4861 4743 2528 9 2 0 3 4374 4412 4413 4861 4862 4863 2529 9 2 0 3 4374 4413 4375 4863 4864 4746 2530 9 2 0 3 4375 4413 4414 4864 4865 4866 2531 9 2 0 3 4375 4414 4376 4866 4867 4749 2532 9 2 0 3 4376 4414 4415 4867 4868 4869 2533 9 2 0 3 4376 4415 4377 4869 4870 4752 2534 9 2 0 3 4377 4415 4416 4870 4871 4872 2535 9 2 0 3 4377 4416 4378 4872 4873 4755 2536 9 2 0 3 4378 4416 4417 4873 4874 4875 2537 9 2 0 3 4378 4417 4379 4875 4876 4758 2538 9 2 0 3 4379 4417 4418 4876 4877 4878 2539 9 2 0 3 4379 4418 4380 4878 4879 4761 2540 9 2 0 3 4380 4418 4419 4879 4880 4881 2541 9 2 0 3 4380 4419 4381 4881 4882 4764 2542 9 2 0 3 4381 4419 4420 4882 4883 4884 2543 9 2 0 3 4381 4420 4382 4884 4885 4767 2544 9 2 0 3 4382 4420 4421 4885 4886 4887 2545 9 2 0 3 4382 4421 4383 4887 4888 4770 2546 9 2 0 3 4383 4421 4422 4888 4889 4890 2547 9 2 0 3 4383 4422 4384 4890 4891 4773 2548 9 2 0 3 4384 4422 4423 4891 4892 4893 2549 9 2 0 3 4384 4423 4385 4893 4894 4776 2550 9 2 0 3 4385 4423 4424 4894 4895 4896 2551 9 2 0 3 4385 4424 4386 4896 4897 4779 2552 9 2 0 3 4386 4424 4425 4897 4898 4899 2553 9 2 0 3 4386 4425 353 4899 4900 4782 2554 9 2 0 3 353 4425 352 4900 4901 357 2555 9 2 0 3 304 7 4387 308 4902 4784 2556 9 2 0 3 4387 7 206 4902 245 4903 2557 9 2 0 3 4387 206 4388 4903 4904 4787 2558 9 2 0 3 4388 206 207 4904 246 4905 2559 9 2 0 3 4388 207 4389 4905 4906 4790 2560 9 2 0 3 4389 207 208 4906 247 4907 2561 9 2 0 3 4389 208 4390 4907 4908 4793 2562 9 2 0 3 4390 208 209 4908 248 4909 2563 9 2 0 3 4390 209 4391 4909 4910 4796 2564 9 2 0 3 4391 209 210 4910 249 4911 2565 9 2 0 3 4391 210 4392 4911 4912 4799 2566 9 2 0 3 4392 210 211 4912 250 4913 2567 9 2 0 3 4392 211 4393 4913 4914 4802 2568 9 2 0 3 4393 211 212 4914 251 4915 2569 9 2 0 3 4393 212 4394 4915 4916 4805 2570 9 2 0 3 4394 212 213 4916 252 4917 2571 9 2 0 3 4394 213 4395 4917 4918 4808 2572 9 2 0 3 4395 213 214 4918 253 4919 2573 9 2 0 3 4395 214 4396 4919 4920 4811 2574 9 2 0 3 4396 214 215 4920 254 4921 2575 9 2 0 3 4396 215 4397 4921 4922 4814 2576 9 2 0 3 4397 215 216 4922 255 4923 2577 9 2 0 3 4397 216 4398 4923 4924 4817 2578 9 2 0 3 4398 216 217 4924 256 4925 2579 9 2 0 3 4398 217 4399 4925 4926 4820 2580 9 2 0 3 4399 217 218 4926 257 4927 2581 9 2 0 3 4399 218 4400 4927 4928 4823 2582 9 2 0 3 4400 218 219 4928 258 4929 2583 9 2 0 3 4400 219 4401 4929 4930 4826 2584 9 2 0 3 4401 219 220 4930 259 4931 2585 9 2 0 3 4401 220 4402 4931 4932 4829 2586 9 2 0 3 4402 220 221 4932 260 4933 2587 9 2 0 3 4402 221 4403 4933 4934 4832 2588 9 2 0 3 4403 221 222 4934 261 4935 2589 9 2 0 3 4403 222 4404 4935 4936 4835 2590 9 2 0 3 4404 222 223 4936 262 4937 2591 9 2 0 3 4404 223 4405 4937 4938 4838 2592 9 2 0 3 4405 223 224 4938 263 4939 2593 9 2 0 3 4405 224 4406 4939 4940 4841 2594 9 2 0 3 4406 224 225 4940 264 4941 2595 9 2 0 3 4406 225 4407 4941 4942 4844 2596 9 2 0 3 4407 225 226 4942 265 4943 2597 9 2 0 3 4407 226 4408 4943 4944 4847 2598 9 2 0 3 4408 226 227 4944 266 4945 2599 9 2 0 3 4408 227 4409 4945 4946 4850 2600 9 2 0 3 4409 227 228 4946 267 4947 2601 9 2 0 3 4409 228 4410 4947 4948 4853 2602 9 2 0 3 4410 228 229 4948 268 4949 2603 9 2 0 3 4410 229 4411 4949 4950 4856 2604 9 2 0 3 4411 229 230 4950 269 4951 2605 9 2 0 3 4411 230 4412 4951 4952 4859 2606 9 2 0 3 4412 230 231 4952 270 4953 2607 9 2 0 3 4412 231 4413 4953 4954 4862 2608 9 2 0 3 4413 231 232 4954 271 4955 2609 9 2 0 3 4413 232 4414 4955 4956 4865 2610 9 2 0 3 4414 232 233 4956 272 4957 2611 9 2 0 3 4414 233 4415 4957 4958 4868 2612 9 2 0 3 4415 233 234 4958 273 4959 2613 9 2 0 3 4415 234 4416 4959 4960 4871 2614 9 2 0 3 4416 234 235 4960 274 4961 2615 9 2 0 3 4416 235 4417 4961 4962 4874 2616 9 2 0 3 4417 235 236 4962 275 4963 2617 9 2 0 3 4417 236 4418 4963 4964 4877 2618 9 2 0 3 4418 236 237 4964 276 4965 2619 9 2 0 3 4418 237 4419 4965 4966 4880 2620 9 2 0 3 4419 237 238 4966 277 4967 2621 9 2 0 3 4419 238 4420 4967 4968 4883 2622 9 2 0 3 4420 238 239 4968 278 4969 2623 9 2 0 3 4420 239 4421 4969 4970 4886 2624 9 2 0 3 4421 239 240 4970 279 4971 2625 9 2 0 3 4421 240 4422 4971 4972 4889 2626 9 2 0 3 4422 240 241 4972 280 4973 2627 9 2 0 3 4422 241 4423 4973 4974 4892 2628 9 2 0 3 4423 241 242 4974 281 4975 2629 9 2 0 3 4423 242 4424 4975 4976 4895 2630 9 2 0 3 4424 242 243 4976 282 4977 2631 9 2 0 3 4424 243 4425 4977 4978 4898 2632 9 2 0 3 4425 243 244 4978 283 4979 2633 9 2 0 3 4425 244 352 4979 4980 4901 2634 9 2 0 3 352 244 6 4980 284 356 2635 9 2 0 4 9 355 187 360 5017 196 2636 9 2 0 4 187 355 4981 5017 5018 5019 2637 9 2 0 4 187 4981 188 5019 5020 197 2638 9 2 0 4 188 4981 4982 5020 5021 5022 2639 9 2 0 4 188 4982 189 5022 5023 198 2640 9 2 0 4 189 4982 4983 5023 5024 5025 2641 9 2 0 4 189 4983 190 5025 5026 199 2642 9 2 0 4 190 4983 4984 5026 5027 5028 2643 9 2 0 4 190 4984 191 5028 5029 200 2644 9 2 0 4 191 4984 4985 5029 5030 5031 2645 9 2 0 4 191 4985 192 5031 5032 201 2646 9 2 0 4 192 4985 4986 5032 5033 5034 2647 9 2 0 4 192 4986 193 5034 5035 202 2648 9 2 0 4 193 4986 4987 5035 5036 5037 2649 9 2 0 4 193 4987 194 5037 5038 203 2650 9 2 0 4 194 4987 4988 5038 5039 5040 2651 9 2 0 4 194 4988 195 5040 5041 204 2652 9 2 0 4 195 4988 4989 5041 5042 5043 2653 9 2 0 4 195 4989 4 5043 5044 205 2654 9 2 0 4 4 4989 403 5044 5045 408 2655 9 2 0 4 355 354 4981 359 5046 5018 2656 9 2 0 4 4981 354 4990 5046 5047 5048 2657 9 2 0 4 4981 4990 4982 5048 5049 5021 2658 9 2 0 4 4982 4990 4991 5049 5050 5051 2659 9 2 0 4 4982 4991 4983 5051 5052 5024 2660 9 2 0 4 4983 4991 4992 5052 5053 5054 2661 9 2 0 4 4983 4992 4984 5054 5055 5027 2662 9 2 0 4 4984 4992 4993 5055 5056 5057 2663 9 2 0 4 4984 4993 4985 5057 5058 5030 2664 9 2 0 4 4985 4993 4994 5058 5059 5060 2665 9 2 0 4 4985 4994 4986 5060 5061 5033 2666 9 2 0 4 4986 4994 4995 5061 5062 5063 2667 9 2 0 4 4986 4995 4987 5063 5064 5036 2668 9 2 0 4 4987 4995 4996 5064 5065 5066 2669 9 2 0 4 4987 4996 4988 5066 5067 5039 2670 9 2 0 4 4988 4996 4997 5067 5068 5069 2671 9 2 0 4 4988 4997 4989 5069 5070 5042 2672 9 2 0 4 4989 4997 4998 5070 5071 5072 2673 9 2 0 4 4989 4998 403 5072 5073 5045 2674 9 2 0 4 403 4998 402 5073 5074 407 2675 9 2 0 4 354 353 4990 358 5075 5047 2676 9 2 0 4 4990 353 4999 5075 5076 5077 2677 9 2 0 4 4990 4999 4991 5077 5078 5050 2678 9 2 0 4 4991 4999 5000 5078 5079 5080 2679 9 2 0 4 4991 5000 4992 5080 5081 5053 2680 9 2 0 4 4992 5000 5001 5081 5082 5083 2681 9 2 0 4 4992 5001 4993 5083 5084 5056 2682 9 2 0 4 4993 5001 5002 5084 5085 5086 2683 9 2 0 4 4993 5002 4994 5086 5087 5059 2684 9 2 0 4 4994 5002 5003 5087 5088 5089 2685 9 2 0 4 4994 5003 4995 5089 5090 5062 2686 9 2 0 4 4995 5003 5004 5090 5091 5092 2687 9 2 0 4 4995 5004 4996 5092 5093 5065 2688 9 2 0 4 4996 5004 5005 5093 5094 5095 2689 9 2 0 4 4996 5005 4997 5095 5096 5068 2690 9 2 0 4 4997 5005 5006 5096 5097 5098 2691 9 2 0 4 4997 5006 4998 5098 5099 5071 2692 9 2 0 4 4998 5006 5007 5099 5100 5101 2693 9 2 0 4 4998 5007 402 5101 5102 5074 2694 9 2 0 4 402 5007 401 5102 5103 406 2695 9 2 0 4 353 352 4999 357 5104 5076 2696 9 2 0 4 4999 352 5008 5104 5105 5106 2697 9 2 0 4 4999 5008 5000 5106 5107 5079 2698 9 2 0 4 5000 5008 5009 5107 5108 5109 2699 9 2 0 4 5000 5009 5001 5109 5110 5082 2700 9 2 0 4 5001 5009 5010 5110 5111 5112 2701 9 2 0 4 5001 5010 5002 5112 5113 5085 2702 9 2 0 4 5002 5010 5011 5113 5114 5115 2703 9 2 0 4 5002 5011 5003 5115 5116 5088 2704 9 2 0 4 5003 5011 5012 5116 5117 5118 2705 9 2 0 4 5003 5012 5004 5118 5119 5091 2706 9 2 0 4 5004 5012 5013 5119 5120 5121 2707 9 2 0 4 5004 5013 5005 5121 5122 5094 2708 9 2 0 4 5005 5013 5014 5122 5123 5124 2709 9 2 0 4 5005 5014 5006 5124 5125 5097 2710 9 2 0 4 5006 5014 5015 5125 5126 5127 2711 9 2 0 4 5006 5015 5007 5127 5128 5100 2712 9 2 0 4 5007 5015 5016 5128 5129 5130 2713 9 2 0 4 5007 5016 401 5130 5131 5103 2714 9 2 0 4 401 5016 400 5131 5132 405 2715 9 2 0 4 352 6 5008 356 5133 5105 2716 9 2 0 4 5008 6 285 5133 294 5134 2717 9 2 0 4 5008 285 5009 5134 5135 5108 2718 9 2 0 4 5009 285 286 5135 295 5136 2719 9 2 0 4 5009 286 5010 5136 5137 5111 2720 9 2 0 4 5010 286 287 5137 296 5138 2721 9 2 0 4 5010 287 5011 5138 5139 5114 2722 9 2 0 4 5011 287 288 5139 297 5140 2723 9 2 0 4 5011 288 5012 5140 5141 5117 2724 9 2 0 4 5012 288 289 5141 298 5142 2725 9 2 0 4 5012 289 5013 5142 5143 5120 2726 9 2 0 4 5013 289 290 5143 299 5144 2727 9 2 0 4 5013 290 5014 5144 5145 5123 2728 9 2 0 4 5014 290 291 5145 300 5146 2729 9 2 0 4 5014 291 5015 5146 5147 5126 2730 9 2 0 4 5015 291 292 5147 301 5148 2731 9 2 0 4 5015 292 5016 5148 5149 5129 2732 9 2 0 4 5016 292 293 5149 302 5150 2733 9 2 0 4 5016 293 400 5150 5151 5132 2734 9 2 0 4 400 293 5 5151 303 404 $EndElements trunk-2018.02b/examples/FEMxDEM/footing.py000066400000000000000000000067411324306050200201600ustar00rootroot00000000000000""" Author: Ning Guo run `mv footing.yade.gz 0.yade.gz` to generate initial RVE packing """ from esys.escript import * from esys.finley import ReadGmsh from esys.weipa import saveVTK from esys.escript.pdetools import Projector from msFEM import MultiScale from saveGauss import saveGauss2D import time vel = -0.00025; surcharge=-20.e3; # surcharge equals to the initial vertical stress of the RVE packing dim = 2; B = 0.05; L = 0.6; H = 0.4; mydomain = ReadGmsh('footing.msh',numDim=dim,integrationOrder=2) # read Gmsh mesh with 6-node triangle element (2500 tri6); each element has 3 Gauss points k = kronecker(mydomain) numg = 3*2500; # number of Gauss points nump = 16; # number of processes in multiprocessing packNo=range(0,numg,50) prob = MultiScale(domain=mydomain,ng=numg,np=nump,random=False,rtol=1e-2,usePert=False,pert=-2.e-5,verbose=False) disp = Vector(0.,Solution(mydomain)) t=0 stress = prob.getCurrentStress() proj = Projector(mydomain) sig = proj(stress) sig_bounda = interpolate(sig,FunctionOnBoundary(mydomain)) traction = matrix_mult(sig_bounda,mydomain.getNormal()) x = mydomain.getX() bx = FunctionOnBoundary(mydomain).getX() footingBase = whereZero(bx[1]-sup(bx[1]))*whereNonPositive(bx[0]-B) tractFoot = traction*footingBase forceFoot = integrate(tractFoot,where=FunctionOnBoundary(mydomain)) lengthFoot = integrate(footingBase,where=FunctionOnBoundary(mydomain)) fout=file('./result/bearing.dat','w') fout.write('0 '+str(forceFoot[0])+' '+str(forceFoot[1])+' '+str(lengthFoot)+'\n') # Dirichlet BC, rollers at left and right, fixties at bottom, rigid and rough footing Dbc = whereZero(x[0])*[1,0]+whereZero(x[0]-sup(x[0]))*[1,0]+whereZero(x[1]-inf(x[1]))*[1,1]+whereZero(x[1]-sup(x[1]))*whereNonPositive(x[0]-B)*[1,1] Vbc = whereZero(x[0])*[0,0]+whereZero(x[0]-sup(x[0]))*[0,0]+whereZero(x[1]-inf(x[1]))*[0,0]+whereZero(x[1]-sup(x[1]))*whereNonPositive(x[0]-B)*[0,vel] # Neumann BC, surcharge at the rest area of the top surface Nbc = whereZero(bx[1]-sup(bx[1]))*wherePositive(bx[0]-B)*[0,surcharge] time_start = time.time() while t < 58: # apply 58 loading step; further loading would abort the program due to severe mesh distortion prob.initialize(f=Nbc, specified_u_mask=Dbc, specified_u_val=Vbc) t += 1 du=prob.solve(iter_max=100) disp += du stress=prob.getCurrentStress() dom = prob.getDomain() # domain updated (Lagrangian) proj = Projector(dom) sig = proj(stress) sig_bounda = interpolate(sig,FunctionOnBoundary(dom)) traction = matrix_mult(sig_bounda,dom.getNormal()) tractFoot = traction*footingBase forceFoot = integrate(tractFoot,where=FunctionOnBoundary(dom)) lengthFoot = integrate(footingBase,where=FunctionOnBoundary(dom)) fout.write(str(t*vel)+' '+str(forceFoot[0])+' '+str(forceFoot[1])+' '+str(lengthFoot)+'\n') vR=prob.getLocalVoidRatio() rotation=prob.getLocalAvgRotation() fabric=prob.getLocalFabric() strain = prob.getCurrentStrain() saveGauss2D(name='./result/gauss/time_'+str(t)+'.dat',strain=strain,stress=stress,fabric=fabric) volume_strain = trace(strain) dev_strain = symmetric(strain) - volume_strain*k/dim shear = sqrt(2*inner(dev_strain,dev_strain)) saveVTK("./result/vtk/footing_%d.vtu"%t,disp=disp,stress=stress,shear=shear,e=vR,rot=rotation) prob.getCurrentPacking(pos=packNo,time=t,prefix='./result/packing/') # output packing time_elapse = time.time() - time_start fout.write("#Elapsed time in hours: "+str(time_elapse/3600.)+'\n') fout.close() prob.exitSimulation() trunk-2018.02b/examples/FEMxDEM/prepareRVE.py000066400000000000000000000027551324306050200205270ustar00rootroot00000000000000from yade import pack O.materials.append(FrictMat(young=6.e8,poisson=.8,frictionAngle=.0)) sp = pack.SpherePack() size = .24 sp.makeCloud(minCorner=(0,0,.05),maxCorner=(size,size,.05),rMean=.005,rRelFuzz=.4,num=400,periodic=True,seed=1) sp.toSimulation() O.cell.hSize = Matrix3(size,0,0, 0,size,0, 0,0,.1) print len(O.bodies) for p in O.bodies: p.state.blockedDOFs = 'zXY' p.state.mass = 2650 * 0.1 * pi * p.shape.radius**2 # 0.1 = thickness of cylindrical particle inertia = 0.5 * p.state.mass * p.shape.radius**2 p.state.inertia = (.5*inertia,.5*inertia,inertia) O.dt = utils.PWaveTimeStep() print O.dt O.engines = [ ForceResetter(), InsertionSortCollider([Bo1_Sphere_Aabb()]), InteractionLoop( [Ig2_Sphere_Sphere_ScGeom()], [Ip2_FrictMat_FrictMat_FrictPhys()], [Law2_ScGeom_FrictPhys_CundallStrack()] ), PeriTriaxController( dynCell=True, goal=(-1.e5,-1.e5,0), stressMask=3, relStressTol=.001, maxUnbalanced=.001, maxStrainRate=(.5,.5,.0), doneHook='term()', label='biax' ), NewtonIntegrator(damping=.1) ] def term(): O.engines = O.engines[:3]+O.engines[4:] print getStress() print O.cell.hSize setContactFriction(0.5) O.cell.trsf=Matrix3.Identity O.cell.velGrad=Matrix3.Zero for p in O.bodies: p.state.vel = Vector3.Zero p.state.angVel = Vector3.Zero p.state.refPos = p.state.pos p.state.refOri = p.state.ori O.save('0.yade.gz') O.pause() O.run();O.wait() trunk-2018.02b/examples/FEMxDEM/retainingSmooth.py000066400000000000000000000063701324306050200216630ustar00rootroot00000000000000""" Author: Ning Guo run `mv retainingSmooth.yade.gz 0.yade.gz` to generate initial RVE packing """ from esys.escript import * from esys.finley import Rectangle from esys.weipa import saveVTK from esys.escript.pdetools import Projector from msFEM2D import MultiScale from saveGauss import saveGauss2D import time vel = -0.00017; surcharge=-2.e4; # surcharge equals to the initial vertical stress of the RVE packing; vel<0 passive failure; vel>0 active failure B = 0.4; H = 0.2; wallH = 0.17; baseH = H-wallH; # setup dimensions nx = 40; ny = 20 # discretization with 40x20 quads mydomain = Rectangle(l0=B,l1=H,n0=nx,n1=ny,order=2,integrationOrder=2) dim = mydomain.getDim() k = kronecker(mydomain) numg = 4*nx*ny; nump = 16; packNo=range(0,numg,16) disp = Vector(0.,Solution(mydomain)) prob = MultiScale(domain=mydomain,ng=numg,np=nump,random=False,rtol=1e-2,usePert=False,pert=-2.e-5,verbose=True) t=0 time_start = time.time() x = mydomain.getX() bx = FunctionOnBoundary(mydomain).getX() left = whereZero(x[0]) right = whereZero(x[0]-B) bottom = whereZero(x[1]) top = whereZero(bx[1]-H) base = whereZero(x[0]-B)*whereNegative(x[1]-baseH) wall = whereZero(x[0]-B)*whereNonNegative(x[1]-baseH) wallBF = whereZero(bx[0]-B)*whereNonNegative(bx[1]-baseH) # Dirichlet BC, all fixed in space except wall (only fixed in x direction, smooth) Dbc = left*[1,1]+base*[1,1]+bottom*[1,1]+wall*[1,0] Vbc = left*[0,0]+base*[0,0]+bottom*[0,0]+wall*[vel,0] # Neumann BC, apply surcharge at the top surface Nbc = top*[0,surcharge] stress = prob.getCurrentStress() proj = Projector(mydomain) sig = proj(stress) sig_bounda = interpolate(sig,FunctionOnBoundary(mydomain)) traction = matrix_mult(sig_bounda,mydomain.getNormal()) tract = traction*wallBF # traction on wall forceWall = integrate(tract,where=FunctionOnBoundary(mydomain)) # force on wall lengthWall = integrate(wallBF,where=FunctionOnBoundary(mydomain)) fout=file('./result/pressure.dat','w') fout.write('0 '+str(forceWall[0])+' '+str(lengthWall)+'\n') while t < 100: prob.initialize(f=Nbc,specified_u_mask=Dbc,specified_u_val=Vbc) t += 1 du=prob.solve(iter_max=100) disp += du stress=prob.getCurrentStress() dom = prob.getDomain() proj = Projector(dom) sig = proj(stress) sig_bounda = interpolate(sig,FunctionOnBoundary(dom)) traction = matrix_mult(sig_bounda,dom.getNormal()) tract = traction*wallBF forceWall = integrate(tract,where=FunctionOnBoundary(dom)) lengthWall = integrate(wallBF,where=FunctionOnBoundary(dom)) fout.write(str(t*vel)+' '+str(forceWall[0])+' '+str(lengthWall)+'\n') vR=prob.getLocalVoidRatio() rotation=prob.getLocalAvgRotation() fabric=prob.getLocalFabric() strain = prob.getCurrentStrain() saveGauss2D(name='./result/gauss/time_'+str(t)+'.dat',strain=strain,stress=stress,fabric=fabric) volume_strain = trace(strain) dev_strain = symmetric(strain) - volume_strain*k/dim shear = sqrt(2*inner(dev_strain,dev_strain)) saveVTK("./result/vtk/retainingSmooth_%d.vtu"%t,disp=disp,stress=stress,shear=shear,e=vR,rot=rotation) prob.getCurrentPacking(pos=packNo,time=t,prefix='./result/packing/') time_elapse = time.time() - time_start fout.write("#Elapsed time in hours: "+str(time_elapse/3600.)+'\n') fout.close() prob.exitSimulation() trunk-2018.02b/examples/FEMxDEM/triaxialRough.py000066400000000000000000000060111324306050200213230ustar00rootroot00000000000000""" Author: Ning Guo run `mv triaxialRough.yade.gz 0.yade.gz` to generate initial RVE packing """ from esys.escript import * from esys.finley import Brick from esys.weipa import saveVTK from esys.escript.pdetools import Projector from msFEM3D import MultiScale from saveGauss import saveGauss3D import time vel = -0.0001; confining=-1.e5; lx = 0.05; ly = 0.05; lz = 0.1; # sample dimension nx = 8; ny = 8; nz = 16; # discretization mydomain = Brick(l0=lx,l1=ly,l2=lz,n0=nx,n1=ny,n2=nz,order=2,integrationOrder=2) # 20-noded,8-Gauss hexahedral element dim = 3; k = kronecker(mydomain) numg = 8*nx*ny*nz; # number of Gauss points packNo=range(0,numg,8) prob = MultiScale(domain=mydomain,ng=numg,useMPI=True,rtol=1e-2) # mpi is activated disp = Vector(0.,Solution(mydomain)) t=0 stress = prob.getCurrentStress() proj = Projector(mydomain) sig = proj(stress) sig_bounda = interpolate(sig,FunctionOnBoundary(mydomain)) traction = matrix_mult(sig_bounda,mydomain.getNormal()) # traction on boundary x = mydomain.getX() bx = FunctionOnBoundary(mydomain).getX() topSurf = whereZero(bx[2]-lz) tractTop = traction*topSurf # traction on top surface forceTop = integrate(tractTop,where=FunctionOnBoundary(mydomain)) # force on top areaTop = integrate(topSurf,where=FunctionOnBoundary(mydomain)) fout=file('./result/resultant.dat','w') fout.write('0 '+str(forceTop[2])+' '+str(areaTop)+'\n') # Dirichlet BC, rough loading ends Dbc = whereZero(x[2])*[1,1,1]+whereZero(x[2]-lz)*[1,1,1] Vbc = whereZero(x[2])*[0,0,0]+whereZero(x[2]-lz)*[0,0,vel] # Neumann BC, constant lateral confining Nbc = whereZero(bx[0])*[-confining,0,0]+whereZero(bx[0]-sup(bx[0]))*[confining,0,0]+whereZero(bx[1])*[0,-confining,0]+whereZero(bx[1]-sup(bx[1]))*[0,confining,0] time_start = time.time() while t < 100: prob.initialize(f=Nbc, specified_u_mask=Dbc, specified_u_val=Vbc) t += 1 du=prob.solve(iter_max=100) disp += du stress=prob.getCurrentStress() dom = prob.getDomain() proj = Projector(dom) sig = proj(stress) sig_bounda = interpolate(sig,FunctionOnBoundary(dom)) traction = matrix_mult(sig_bounda,dom.getNormal()) tractTop = traction*topSurf forceTop = integrate(tractTop,where=FunctionOnBoundary(dom)) areaTop = integrate(topSurf,where=FunctionOnBoundary(dom)) fout.write(str(t*vel/lz)+' '+str(forceTop[2])+' '+str(areaTop)+'\n') vR=prob.getLocalVoidRatio() rotation=prob.getLocalAvgRotation() fabric=prob.getLocalFabric() strain = prob.getCurrentStrain() saveGauss3D(name='./result/gauss/time_'+str(t)+'.dat',strain=strain,stress=stress,fabric=fabric) volume_strain = trace(strain) dev_strain = symmetric(strain) - volume_strain*k/dim shear = sqrt(2./3.*inner(dev_strain,dev_strain)) saveVTK("./result/vtk/triaxialRough_%d.vtu"%t,disp=disp,shear=shear,e=vR,rot=rotation) prob.getCurrentPacking(pos=packNo,time=t,prefix='./result/packing/') time_elapse = time.time() - time_start fout.write("#Elapsed time in hours: "+str(time_elapse/3600.)+'\n') fout.close() prob.exitSimulation() trunk-2018.02b/examples/FEMxDEM/undrain.py000066400000000000000000000074761324306050200201610ustar00rootroot00000000000000""" Author: Ning Guo run `mv undrain.yade.gz 0.yade.gz` to generate initial RVE packing """ from esys.escript import * from esys.finley import Rectangle from esys.weipa import saveVTK from esys.escript.pdetools import Projector from msFEMup import MultiScale from saveGauss import saveGauss2D import time confining = -2.e5; pore = 1.e5 # initial pore pressure perm = 0.001**2/(180.*8.9e-4); # unscaled permeability, using KC equation kf = 2.2e9 # fluid bulk modulus dt = .1; vel = -0.0001 # time step and loading speed lx = 0.05; ly = 0.1 # sample dimension nx = 8; ny = 16 # discretization mydomain = Rectangle(l0=lx,l1=ly,n0=nx,n1=ny,order=2,integrationOrder=2) k = kronecker(mydomain); dim = 2. numg = 4*nx*ny; # no. of Gauss points mpi = True # use MPI prob = MultiScale(domain=mydomain,pore0=pore,perm=perm,kf=kf,dt=dt,ng=numg,useMPI=mpi,rtol=1.e-2) disp = Vector(0.,Solution(mydomain)) t = 0 ux = mydomain.getX() # disp. node coordinate px = ReducedSolution(mydomain).getX() # press. node coordinate bx = FunctionOnBoundary(mydomain).getX() topSurf = whereZero(bx[1]-ly) uDbc = whereZero(ux[1])*[0,1]+whereZero(ux[0]-lx/2.)*whereZero(ux[1])*[1,1]+whereZero(ux[1]-ly)*[0,1] # disp. Dirichlet BC mask vDbc = whereZero(ux[1])*[0,0]+whereZero(ux[0]-lx/2.)*whereZero(ux[1])*[0,0]+whereZero(ux[1]-ly)*[0,vel*dt] # disp. Dirichlet BC value uNbc = whereZero(bx[0])*[-confining,0]+whereZero(bx[0]-lx)*[confining,0] # disp. Neumann BC stress = prob.getCurrentStress() # effective stress at GP proj = Projector(mydomain) sig = proj(stress) # effective stress at node (reduced) sig_bound = interpolate(sig,FunctionOnBoundary(mydomain)) traction = matrix_mult(sig_bound,mydomain.getNormal()) tractTop = traction*topSurf forceTop = integrate(tractTop,where=FunctionOnBoundary(mydomain)) lengthTop = integrate(topSurf,where=FunctionOnBoundary(mydomain)) fout = file('./result/resultantForce.dat','w') fout.write('0 '+str(forceTop[1])+' '+str(lengthTop)+'\n') time_start = time.time() while t < 400: prob.initialize(f=uNbc,umsk=uDbc,uvalue=vDbc) t += 1 du = prob.solve(globalIter=10,solidIter=50) disp += du pore = prob.getCurrentPore() # pore pressure at node (reduced) flux = prob.getCurrentFlux() # Darcy flux at GP stress = prob.getCurrentStress() # effective stress at GP strain = prob.getCurrentStrain() # disp. grad at GP volume_strain = trace(strain) # volumetric strain dev_strain = symmetric(strain) - volume_strain*k/dim # deviatoric strain shear = sqrt(2.*inner(dev_strain,dev_strain)) # shear strain fab = prob.getLocalFabric() # fabric tensor at GP dev_fab = 4.*(fab-trace(fab)/dim*k) anis = sqrt(.5*inner(dev_fab,dev_fab)) p = prob.getEquivalentPorosity() # porosity at GP rot = prob.getLocalAvgRotation() # average rotation at GP saveGauss2D(name='./result/gauss/time_'+str(t)+'.dat',strain=strain,fabric=fab,stress=stress) dom = prob.getDomain() # domain updated (Lagrangian) proj = Projector(dom) flux = proj(flux) # Darcy flux at node (reduced) p = proj(p) # porosity at node (reduced) shear = proj(shear) # shear strain at node (reduced) anis = proj(anis) rot = proj(rot) saveVTK("./result/vtk/undrain_%d.vtu"%t,disp=disp,pore=pore,flux=flux,shear=shear,p=p,anis=anis,rot=rot) sig = proj(stress) # effective stress at node (reduced) sig_bound = interpolate(sig,FunctionOnBoundary(dom)) traction = matrix_mult(sig_bound,dom.getNormal()) tractTop = traction*topSurf forceTop = integrate(tractTop,where=FunctionOnBoundary(dom)) lengthTop = integrate(topSurf,where=FunctionOnBoundary(dom)) fout.write(str(t*vel*dt/ly)+' '+str(forceTop[1])+' '+str(lengthTop)+'\n') prob.getCurrentPacking(time=t,prefix='./result/packing/') time_elapse = time.time() - time_start fout.write('#Elapsed time in hours: '+str(time_elapse/3600.)+'\n') fout.close() prob.exitSimulation() trunk-2018.02b/examples/FluidCouplingLBM/000077500000000000000000000000001324306050200201035ustar00rootroot00000000000000trunk-2018.02b/examples/FluidCouplingLBM/Buoyancy_Gradient.m000066400000000000000000000076121324306050200236750ustar00rootroot00000000000000%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %Authors: Luc Sibille luc.sibille@3sr-grenoble.fr % Franck Lomine franck.lomine@insa-rennes.fr % % Script to interpret the DEM-LBM buoyancy simulation performed with the script buoyancy.py % This script was written for the OZ ALERT school in Grenoble 2011 % Script updated in 2014 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% clear all close all radFactor = 0.6; rhoSolid = 3000; gravity = 0.1; %----- reading of LBM.log file logfile='LBM.log'; fid = fopen(logfile); tline = fgetl(fid); fscanf(fid,'%s %s %s',[3,1]); fscanf(fid,'%s',[1,1]); Lx0=fscanf(fid,'%f',[1,1]); fscanf(fid,'%s',[1,1]); Ly0=fscanf(fid,'%f',[1,1]); fscanf(fid,'%s',[1,1]); depth=fscanf(fid,'%f',[1,1]); fscanf(fid,'%s',[1,1]); thickness=fscanf(fid,'%f',[1,1]); fscanf(fid,'%s',[1,1]); NbBodies=fscanf(fid,'%f',[1,1]); fscanf(fid,'%s',[1,1]); dP=fscanf(fid,'%f',[1,1]); fscanf(fid,'%s',[1,1]); Nu=fscanf(fid,'%f',[1,1]); fscanf(fid,'%s',[1,1]); rhoFluid=fscanf(fid,'%f',[1,1]); fscanf(fid,'%s',[1,1]); dx=fscanf(fid,'%f',[1,1]); fscanf(fid,'%s',[1,1]); Nx=fscanf(fid,'%f',[1,1]); fscanf(fid,'%s',[1,1]); Ny=fscanf(fid,'%f',[1,1]); fscanf(fid,'%s',[1,1]); Nz=fscanf(fid,'%f',[1,1]); tline = fgetl(fid); tline = fgetl(fid); fscanf(fid,'%s',[1,1]); tau=fscanf(fid,'%f',[1,1]); fscanf(fid,'%s',[1,1]); omega=fscanf(fid,'%f',[1,1]); fscanf(fid,'%s',[1,1]); IterMax=fscanf(fid,'%f',[1,1]); fscanf(fid,'%s',[1,1]); SaveMode=fscanf(fid,'%f',[1,1]); fscanf(fid,'%s',[1,1]); IterSave=fscanf(fid,'%f',[1,1]); fscanf(fid,'%s',[1,1]); SaveGridRatio=fscanf(fid,'%f',[1,1]); fscanf(fid,'%s',[1,1]); IntervalLBM=fscanf(fid,'%f',[1,1]); fscanf(fid,'%s',[1,1]); LBMConvergenceCriterion=fscanf(fid,'%f',[1,1]); tline = fgetl(fid); tline = fgetl(fid); fscanf(fid,'%s %s',[2,1]); LBMdt=fscanf(fid,'%f',[1,1]); fclose(fid); %------------------------------- C=dx/LBMdt; Cs2=C^2/3; path1 = './lbm-nodes/' path2 = './dem-bodies/' fiVx=dir([path1 'Vx_*']); fiRho=dir([path1 'Rho_*']); fiBodies=dir([path2 'spheres_*']); nb_file =length(fiVx) for i=1:nb_file vxfile=[path1 fiVx(i).name] ux = importdata(vxfile); ux=ux'; Vdarcy(i) = mean( ux(1,:) ); rhofile=[path1 fiRho(i).name]; rho = importdata(rhofile); rho=rho'; DP(i) = Cs2*(rho(1,90)-rho(end,90)); fid2 = fopen([path2 fiBodies(i).name],'r'); [donnees2] = fscanf(fid2,'%d %f %f %f %f %f %f %f %f %f %f %f %f %f %f %f %f',[17,inf]); id=donnees2(1,:); x=donnees2(2,:); y=donnees2(3,:); r=donnees2(5,:); fclose(fid2); clear donnees2 xmin(i)=min(x); xmax(i)=max(x); if i==1 %computation of Terzaghi critical hydraulic gradient Stotal = 0.005*(max(x+r)-min(x-r)); Ssolid = sum( pi*(r/radFactor).^2 ); Swater = Stotal-Ssolid; Wwater = rhoFluid*gravity*Swater; Wsolid = rhoSolid*gravity*Ssolid; %Wsolid = rhoSolid*gravity*sum( pi*(r/radFactor).^2 ); Wtotal = Wwater+Wsolid; HgradCrit = Wtotal/Stotal/(rhoFluid*gravity); end end L = xmax(1)-xmin(1); Hgrad = DP/(rhoFluid*gravity*L); figure(1) hold on %plot(DP,Vdarcy) plot(Hgrad,Vdarcy,'b') plot([HgradCrit HgradCrit], [0 max(Vdarcy)],'r') xlabel('Hydraulic gradient') ylabel('Darcy velocity') legend('Darcy velocity','Terzaghi critical gradient') figure(2) hold on % plot(DP,xmin,'b') % plot(DP,xmax,'r') plot(Hgrad,xmin,'b') plot(Hgrad,xmax,'g') plot([HgradCrit HgradCrit], [0 max(xmax)],'r') xlabel('Hydraulic gradient') ylabel('Max and min particle positions') legend('Top particle position','Bottom particle position','Terzaghi critical gradient') trunk-2018.02b/examples/FluidCouplingLBM/buoyancy.py000066400000000000000000000210441324306050200223070ustar00rootroot00000000000000############################################################################################################################## #Authors: Luc Sibille luc.sibille@3sr-grenoble.fr # Franck Lomine franck.lomine@insa-rennes.fr # # Script to simulate buoyancy in a granular assembly with the DEM-LBM coupling. # This script was written for a practical work in the OZ ALERT school in Grenoble 2011. # Script updated in 2014 ############################################################################################################################## from yade import pack,timing,utils rMean = 0.00035 #mean radius (m) of solid particles Gravity=-0.1 #gravity (m.s-2) that will be applied to solid particles #definition of the simulation domain (position of walls), the domain is a 3D domain but particles will be generated on a unique plane at z=0, be carefull that wall normal to z directions do not touch particles. lowerCornerW = (0.00001,0.00001,-0.002) upperCornerW = (0.00701,0.00501,0.002) #definitions of the domain for particles: positions in z direction is set to 0 to force a 2D granular assembly lowerCornerS = (0.00001,0.00001,0) upperCornerS = (0.00701,0.00501,0) nbSpheres = 70 #this not the real number of particles but number of times where we try to insert a new particle, the real number of particles can be much lower! ## Build of the engine vector O.engines=[ ForceResetter(), InsertionSortCollider([Bo1_Sphere_Aabb(),Bo1_Box_Aabb()]), InteractionLoop( [Ig2_Sphere_Sphere_ScGeom(),Ig2_Box_Sphere_ScGeom()], [Ip2_FrictMat_FrictMat_FrictPhys()], [Law2_ScGeom_FrictPhys_CundallStrack()] ), HydrodynamicsLawLBM( EngineIsActivated=False, WallYm_id=0, WallYp_id=1, WallXm_id=2, WallXp_id=3, WallZp_id=5, WallZm_id=4, useWallYm=1, useWallYp=1, YmBCType=2, YpBCType=2, useWallXm=0, useWallXp=0, XmBCType=1, XpBCType=1, LBMSavedData='spheres,velXY,rho,nodeBD', tau=1.1, dP=(0,0,0), IterSave=200, IterPrint=100, RadFactor=0.6, # The radius of particles seen by the LBM engine is reduced here by a factor RadFactor=0.6 to allow flow between particles in this 2D case. Nx=250, Rho=1000, Nu=1.0e-6, periodicity='', bc='', VbCutOff=0.0, applyForcesAndTorques=True, label="Elbm" ), NewtonIntegrator(damping=0.2,gravity=(Gravity,0.,0.),label="ENewton"), ] ############################################################################################################### # Creation of 6 walls at the limit of the simulation domain # defintiion of the material for walls O.materials.append(FrictMat(young=50e6,poisson=.5,frictionAngle=0.0,density=3000,label='walls')) ## create walls around the packing wallOversizeFactor=1.001 thickness=0.00001; #bottom box center= ((lowerCornerW[0]+upperCornerW[0])/2,lowerCornerW[1]-thickness/2.0,(lowerCornerW[2]+upperCornerW[2])/2) halfSize= (wallOversizeFactor*fabs(lowerCornerW[0]-upperCornerW[0])/2+thickness,thickness/2.0,wallOversizeFactor*fabs(lowerCornerW[2]-upperCornerW[2])/2+thickness) b1=utils.box(center=[center[0],center[1],center[2]],extents=[halfSize[0],halfSize[1],halfSize[2]],color=[0,1,0],fixed=True,wire=True,material='walls') O.bodies.append(b1) #-- #Top box center=((lowerCornerW[0]+upperCornerW[0])/2,upperCornerW[1]+thickness/2.0,(lowerCornerW[2]+upperCornerW[2])/2) halfSize =(wallOversizeFactor*fabs(lowerCornerW[0]-upperCornerW[0])/2+thickness,thickness/2.0,wallOversizeFactor*fabs(lowerCornerW[2]-upperCornerW[2])/2+thickness) b2=utils.box(center=[center[0],center[1],center[2]],extents=[halfSize[0],halfSize[1],halfSize[2]],color=[0,1,0],fixed=True,wire=True,material='walls') O.bodies.append(b2) #-- center=(lowerCornerW[0]-thickness/2.0,(lowerCornerW[1]+upperCornerW[1])/2,(lowerCornerW[2]+upperCornerW[2])/2) halfSize=(thickness/2.0,wallOversizeFactor*fabs(lowerCornerW[1]-upperCornerW[1])/2+thickness,wallOversizeFactor*fabs(lowerCornerW[2]-upperCornerW[2])/2+thickness) b3=utils.box(center=[center[0],center[1],center[2]],extents=[halfSize[0],halfSize[1],halfSize[2]],color=[0,1,0],fixed=True,wire=True,material='walls') O.bodies.append(b3) #-- center=(upperCornerW[0]+thickness/2.0,(lowerCornerW[1]+upperCornerW[1])/2,(lowerCornerW[2]+upperCornerW[2])/2) halfSize=(thickness/2.0,wallOversizeFactor*fabs(lowerCornerW[1]-upperCornerW[1])/2+thickness,wallOversizeFactor*fabs(lowerCornerW[2]-upperCornerW[2])/2+thickness) b4=utils.box(center=[center[0],center[1],center[2]],extents=[halfSize[0],halfSize[1],halfSize[2]],color=[0,1,0],fixed=True,wire=True,material='walls') O.bodies.append(b4) #-- center=((lowerCornerW[0]+upperCornerW[0])/2,(lowerCornerW[1]+upperCornerW[1])/2,lowerCornerW[2]-thickness/2.0) halfSize=(wallOversizeFactor*fabs(lowerCornerW[0]-upperCornerW[0])/2+thickness,wallOversizeFactor*fabs(lowerCornerW[1]-upperCornerW[1])/2+thickness,thickness/2.0) b5=utils.box(center=[center[0],center[1],center[2]],extents=[halfSize[0],halfSize[1],halfSize[2]],color=[0,1,0],fixed=True,wire=True,material='walls') O.bodies.append(b5) #-- center=((lowerCornerW[0]+upperCornerW[0])/2,(lowerCornerW[1]+upperCornerW[1])/2,upperCornerW[2]+thickness/2.0) halfSize=(wallOversizeFactor*fabs(lowerCornerW[0]-upperCornerW[0])/2+thickness,wallOversizeFactor*fabs(lowerCornerW[1]-upperCornerW[1])/2+thickness,thickness/2.0); b6=utils.box(center=[center[0],center[1],center[2]],extents=[halfSize[0],halfSize[1],halfSize[2]],color=[0,1,0],fixed=True,wire=True,material='walls') O.bodies.append(b6) ############################################################################################################### # Creation of the assembly of particles # defintiion of the material for particles O.materials.append(FrictMat(young=50e6,poisson=.5,frictionAngle=35*3.1415926535/180,density=3000,label='spheres')) ## use a SpherePack object to generate a random loose particles packing sp=pack.SpherePack() sp.makeCloud(lowerCornerS,upperCornerS,-1,0.33,nbSpheres,False, 0.5,seed=1) #"seed" make the "random" generation always the same sp.toSimulation(material='spheres') # loop over bodies to change their 3D inertia into 2D inertia (by default in YADE particles are 3D spheres, here we want to cylinder with a length=1). for s in O.bodies: if isinstance(s.shape,Box): continue r=s.shape.radius oldm=s.state.mass oldI=s.state.inertia m=oldm*3./4./r s.state.mass=m s.state.inertia[0] = 15./16./r*oldI[0] #inertia with respect to x and y axes are not used and the computation here is wrong s.state.inertia[1] = 15./16./r*oldI[1] #inertia with respect to x and y axes are not used and the computation here is wrong s.state.inertia[2] = 15./16./r*oldI[2] #only inertia with respect to z axis is usefull O.dt=0.00005 #use a fix value of time step, it is better to not use a time stepper computing a new step at each iteration since DEM time step is eventually fixed by the LBM engine. yade.qt.View() #to see what is happening print"___________________" print"PHASE 1" print "Settlement of particle under gravity only, fluid flow is not activated at the present time... please wait" print"___________________" O.run(60000,1) #settlement of particle under gravity only, no action of the fluid flow. #definition of a runnable python function to increase progressively the fluid pressure gradient. def IncrDP(): currentDP=Elbm.dP Elbm.dP=(currentDP[0]+1.0/10000.0,0,0) #O.engines[6].dP=currentDP+1.0/100.0 if not(O.iter%100): print "dP=", Elbm.dP[0] O.engines=O.engines+[PyRunner(iterPeriod=1,command='IncrDP()')] #Necessary to initiate correctly the LBM engine O.resetTime() #Activation of the LBM engine Elbm.EngineIsActivated=True #Cundall damping for solid particles removed (the fluid viscosity should be enough to damp the solid particles ... in general, not always...) ENewton.damping=0.0 # Now you just need to run the simulation as long as you want to ... note that if you wait to long the pressure gradient will become very large inducing great fluid velocities, and the fluid simulation may become crazy!! Look at the Mach number (M) printed in the console, it should not be too high... print"___________________" print"PHASE 2" print "Fluid flow has been activated now with a gradual increase of the pressure gradient." print "Run the simulation, until the pressure gradient is strong enough to compensate the gravity forces, then particles will mouve to the right." print"..." print"..." print "Velocity and pressure field of the fluid can be plotted with the matlab script yadeLBMvisu.m" print "Terzaghi critical hydraulic gradient can be compared with the one deduced from the simulation with the matlab script Buoyancy_Gradient.m" print"___________________" trunk-2018.02b/examples/FluidCouplingLBM/yadeLBMvisu.m000066400000000000000000000104141324306050200224450ustar00rootroot00000000000000%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %Authors: Luc Sibille luc.sibille@3sr-grenoble.fr % Franck Lomine franck.lomine@insa-rennes.fr % % Script to visualize the fluid flow simulated with LBM written for a practical work in the OZ ALERT school in Grenoble 2011. % Script updated in 2014 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% clear all close all %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %%%% The only thing you have to do is choose below the iteration number for %%%% which you want to plot the fluid flow. This iteration number should %%%% correspond to one the saving iteration appearing in the same of the %%%% files saved in ./dem-bodies or ./lbm-nodes IterNumber=600; %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% vxfile=sprintf('lbm-nodes/Vx_%.10d',IterNumber); vyfile=sprintf('lbm-nodes/Vy_%.10d',IterNumber); ux = importdata(vxfile); uy = importdata(vyfile); %ux=ux'; %uy=uy'; u = sqrt(ux.*ux+uy.*uy); rhofile=sprintf('lbm-nodes/Rho_%.10d',IterNumber); rho = importdata(rhofile); nodebdfile=sprintf('lbm-nodes/NodeBoundary_%.10d',IterNumber); nodeBD = importdata(nodebdfile); nodeBD=nodeBD'; spherefile=sprintf('dem-bodies/spheres_%.10d',IterNumber); tmpMat = importdata(spherefile); Sid=tmpMat(:,1); Sx=tmpMat(:,2); Sy=tmpMat(:,3); Sz=tmpMat(:,4); Sr=tmpMat(:,5); clear tmpMat %----- reading of LBM.log file logfile='LBM.log'; fid = fopen(logfile); tline = fgetl(fid); fscanf(fid,'%s %s %s',[3,1]); fscanf(fid,'%s',[1,1]); Lx0=fscanf(fid,'%f',[1,1]); fscanf(fid,'%s',[1,1]); Ly0=fscanf(fid,'%f',[1,1]); fscanf(fid,'%s',[1,1]); depth=fscanf(fid,'%f',[1,1]); fscanf(fid,'%s',[1,1]); thickness=fscanf(fid,'%f',[1,1]); fscanf(fid,'%s',[1,1]); NbBodies=fscanf(fid,'%f',[1,1]); fscanf(fid,'%s',[1,1]); dP=fscanf(fid,'%f',[1,1]); fscanf(fid,'%s',[1,1]); Nu=fscanf(fid,'%f',[1,1]); fscanf(fid,'%s',[1,1]); Rho=fscanf(fid,'%f',[1,1]); fscanf(fid,'%s',[1,1]); dx=fscanf(fid,'%f',[1,1]); fscanf(fid,'%s',[1,1]); Nx=fscanf(fid,'%f',[1,1]); fscanf(fid,'%s',[1,1]); Ny=fscanf(fid,'%f',[1,1]); fscanf(fid,'%s',[1,1]); Nz=fscanf(fid,'%f',[1,1]); tline = fgetl(fid); tline = fgetl(fid); fscanf(fid,'%s',[1,1]); tau=fscanf(fid,'%f',[1,1]); fscanf(fid,'%s',[1,1]); omega=fscanf(fid,'%f',[1,1]); fscanf(fid,'%s',[1,1]); IterMax=fscanf(fid,'%f',[1,1]); fscanf(fid,'%s',[1,1]); SaveMode=fscanf(fid,'%f',[1,1]); fscanf(fid,'%s',[1,1]); IterSave=fscanf(fid,'%f',[1,1]); fscanf(fid,'%s',[1,1]); SaveGridRatio=fscanf(fid,'%f',[1,1]); fscanf(fid,'%s',[1,1]); IntervalLBM=fscanf(fid,'%f',[1,1]); fscanf(fid,'%s',[1,1]); LBMConvergenceCriterion=fscanf(fid,'%f',[1,1]); tline = fgetl(fid); tline = fgetl(fid); fscanf(fid,'%s %s',[2,1]); LBMdt=fscanf(fid,'%f',[1,1]); fclose(fid); %------------------------------- fig1=figure(1) set(gcf, 'color', 'white'); clf nodeBD=flipdim(nodeBD',1); imagesc(nodeBD); title('Obstacle Boundaries'); axis equal; axis off %--------------------------------------- % VELOCITIES (colormap) %-------------------------------------- fig2=figure(2) set(gcf, 'color', 'white'); %set(gca,'ZDir','reverse') clf; hold on imagesc(u); title('Velocity colormap'); umax_current=max(max(u)); if(umax_current==0.) umax_current=umax_current+1.e-9;end; caxis([0. umax_current]); colorbar('location','southoutside'); theta = -pi:2*pi/40:pi; for aa=1:length(Sx) fill(ceil(Sx(aa)/dx)+Sr(aa)/dx*cos(theta),ceil(Sy(aa)/dx)+Sr(aa)/dx*sin(theta),[1 1 1]); end hold off; axis equal; axis off; fig3=figure(3) rhomax_current=max(max(rho)); rhomin_current=min(min(rho)); set(gcf, 'color', 'white'); %set(gca,'ZDir','reverse') clf; hold on %rho=flipdim(rho,1) imagesc(rho); title('Rho'); if(rhomax_current==rhomin_current) rhomax_current=rhomax_current+1.e-9;end; caxis([rhomin_current rhomax_current]); colorbar; theta = -pi:2*pi/40:pi; for aa=1:length(Sx) %fill(ceil(Sx(aa)/dx)+Sr(aa)/dx*cos(theta),ceil(Sy(aa)/dx)+Sr(aa)/dx*sin(theta),[1 1 1]); fill((Sx(aa)/dx)+1+ceil(Sr(aa)/dx)*cos(theta),(Sy(aa)/dx)+1+ceil(Sr(aa)/dx)*sin(theta),[1 1 1]); end hold off; axis equal; axis off; trunk-2018.02b/examples/FluidCouplingPFV/000077500000000000000000000000001324306050200201245ustar00rootroot00000000000000trunk-2018.02b/examples/FluidCouplingPFV/drainage-2PFV-Yuan_and_Chareyre_2017.py000066400000000000000000000125501324306050200270150ustar00rootroot00000000000000# encoding: utf-8 # This script demonstrates a simple case of drainage simulation using the "2PFV" two-phase model implemented in UnsaturatedEngine. # The script was used to generate the result and supplementary material (video) of [1]. The only difference is the problem size (40k particles in the paper vs. 1k (default) in this version) # [1] Yuan, C., & Chareyre, B. (2017). A pore-scale method for hydromechanical coupling in deformable granular media. Computer Methods in Applied Mechanics and Engineering, 318, 1066-1079. (http://www.sciencedirect.com/science/article/pii/S0045782516307216) import matplotlib; matplotlib.rc('axes',grid=True) from yade import pack import pylab from numpy import * utils.readParamsFromTable(seed=1,num_spheres=1000,compFricDegree = 15.0) from yade.params import table seed=table.seed num_spheres=table.num_spheres# number of spheres compFricDegree = table.compFricDegree # initial contact friction during the confining phase (will be decreased during the REFD compaction process) confiningS=-1e5 ## creat a packing with a specific particle side distribution (PSD) psdSizes,psdCumm=[.599,0.6,0.849,0.85,1.0,1.40],[0.,0.35,0.35,0.70,.70,1.] sp=pack.SpherePack() mn,mx=Vector3(0,0,0),Vector3(10,10,10) sp.makeCloud(minCorner=mn,maxCorner=mx,psdSizes=psdSizes,psdCumm=psdCumm,distributeMass=True,num=num_spheres,seed=seed) ## create material #0, which will be used as default O.materials.append(FrictMat(young=15e7,poisson=.4,frictionAngle=radians(compFricDegree),density=2600,label='spheres')) O.materials.append(FrictMat(young=15e7,poisson=.4,frictionAngle=0,density=0,label='frictionless')) ## create walls around the packing walls=aabbWalls((mn,mx),thickness=0,material='frictionless') wallIds=O.bodies.append(walls) O.bodies.append([utils.sphere(center,rad,material='spheres') for center,rad in sp]) triax=TriaxialStressController( internalCompaction=True, goal1=confiningS, goal2=confiningS, goal3=confiningS, max_vel=10, label="triax" ) newton=NewtonIntegrator(damping=0.4) O.engines=[ ForceResetter(), InsertionSortCollider([Bo1_Sphere_Aabb(),Bo1_Box_Aabb()]), InteractionLoop( [Ig2_Sphere_Sphere_ScGeom(),Ig2_Box_Sphere_ScGeom()], [Ip2_FrictMat_FrictMat_FrictPhys()], [Law2_ScGeom_FrictPhys_CundallStrack()] ), GlobalStiffnessTimeStepper(active=1,timeStepUpdateInterval=100,timestepSafetyCoefficient=0.8), triax, newton ] while 1: O.run(1000,True) unb=unbalancedForce() if unb<0.01 and abs(triax.goal1-triax.meanStress)/abs(triax.goal1)<0.001: break ############################# ## REACH NEW EQU. STATE ### ############################# finalFricDegree = 30 # contact friction during the deviatoric loading #We move to deviatoric loading, let us turn internal compaction off to keep particles sizes constant triax.internalCompaction=False # Change contact friction (remember that decreasing it would generate instantaneous instabilities) setContactFriction(radians(finalFricDegree)) while 1: O.run(1000,True) unb=unbalancedForce() if unb<0.001 and abs(triax.goal1-triax.meanStress)/abs(triax.goal1)<0.001: break triax.depth0=triax.depth triax.height0=triax.height triax.width0=triax.width O.save('1kPacking.yade') #save the packing, which can be reloaded later. O.run(1000,True) ei0=-triax.strain[0];ei1=-triax.strain[1];ei2=-triax.strain[2] si0=-triax.stress(0)[0];si1=-triax.stress(2)[1];si2=-triax.stress(4)[2] from yade import plot O.engines=O.engines+[PyRunner(iterPeriod=20,command='history()',dead=1,label='recorder')] def history(): plot.addData(e11=-triax.strain[0]-ei0, e22=-triax.strain[1]-ei1, e33=-triax.strain[2]-ei2, s11=-triax.stress(0)[0]-si0, s22=-triax.stress(2)[1]-si1, s33=-triax.stress(4)[2]-si2, pc=-unsat.bndCondValue[2], sw=unsat.getSaturation(False), i=O.iter ) plot.plots={'pc':('sw',None,'e22')} plot.plot() ####################################################### ## Drainage Test under oedometer conditions ### ####################################################### ##oedometer conditions triax.stressMask=2 triax.goal1=triax.goal3=0 goalTop=triax.stress(3)[1] triax.goal2=goalTop triax.wall_bottom_activated=0 recorder.dead=0 ##Instantiate a two-phase engine unsat=UnsaturatedEngine() meanDiameter=(O.bodies[-1].shape.radius + O.bodies[6].shape.radius) / 2. ##set boundary conditions, the drainage is controlled by decreasing W-phase pressure and keeping NW-phase pressure constant unsat.bndCondIsPressure=[0,0,1,1,0,0] unsat.bndCondValue=[0,0,-1e8,0,0,0] unsat.isPhaseTrapped=True #the W-phase can be disconnected from its reservoir unsat.initialization() unsat.surfaceTension = 10 ##start invasion, the data of normalized pc-sw-strain will be written into pcSwStrain.txt file=open('pcSwStrain.txt',"w") for pg in arange(1.0e-5,15.0,0.1): #increase gaz pressure at the top boundary unsat.bndCondValue=[0,0,(-1.0)*pg*unsat.surfaceTension/meanDiameter,0,0,0] #compute the evolution of interfaces unsat.invasion() #save the phases distribution in vtk format, to be displayed by paraview unsat.savePhaseVtk("./") #compute and apply the capillary forces on each particle unsat.computeCapillaryForce() for b in O.bodies: O.forces.setPermF(b.id, unsat.fluidForce(b.id)) #reac while 1: O.run(1000,True) unb=unbalancedForce() if unb<0.01: break file.write(str(pg)+" "+str(unsat.getSaturation(False))+" "+str(triax.strain[1]-ei1)+"\n") file.close() trunk-2018.02b/examples/FluidCouplingPFV/oedometer.py000066400000000000000000000145161324306050200224700ustar00rootroot00000000000000# -*- coding: utf-8 -*- #************************************************************************* # Copyright (C) 2010 by Bruno Chareyre * # bruno.chareyre_at_grenoble-inp.fr * # * # This program is free software; it is licensed under the terms of the * # GNU General Public License v2 or later. See file LICENSE for details. * #*************************************************************************/ ## Example script for using the DEM-PFV coupling introduced with E. Catalano, as reported in: ## * [Chareyre2012a] Chareyre, B., Cortis, A., Catalano, E., Barthélemy, E. (2012), Pore-scale modeling of viscous flow and induced forces in dense sphere packings. Transport in Porous Media (92), pages 473-493. DOI 10.1007/s11242-011-9915-6 ## http://dx.doi.org/10.1007/s11242-011-9915-6 ## * [Catalano2014a] Catalano, E., Chareyre, B., Barthélémy, E. (2013), Pore-scale modeling of fluid-particles interaction and emerging poromechanical effects. International Journal for Numerical and Analytical Methods in Geomechanics. DOI 10.1002/nag.2198 ## http://arxiv.org/pdf/1304.4895.pdf ## Also used in: ## * Tong et al.2012 (http://dx.doi.org/10.2516/ogst/2012032) ## * Sari et al 2011 (http://people.3sr-grenoble.fr/users/bchareyre/pubs/SariChareyreCatalanoPhilippeVincens_Particles2011.pdf) ## The DEM-PFV is applied here to 1D consolidation (oedometer test). The example includes the determination of oedometer modulus Ee and permeability K. ## The 1D consolidation is simulated as a coupled problem and the analytical solution corresponding to the abovementionned Ee and K is used for comparison. ## See triax-tutorial/script-session1.py for more detailed explanations of the packing generation procedure. ## ______________ First section, similar to triax-tutorial/script-session1.py _________________ from yade import pack num_spheres=1000# number of spheres young=1e6 compFricDegree = 3 # initial contact friction during the confining phase finalFricDegree = 30 # contact friction during the deviatoric loading mn,mx=Vector3(0,0,0),Vector3(1,1,1) # corners of the initial packing O.materials.append(FrictMat(young=young,poisson=0.5,frictionAngle=radians(compFricDegree),density=2600,label='spheres')) O.materials.append(FrictMat(young=young,poisson=0.5,frictionAngle=0,density=0,label='walls')) walls=aabbWalls([mn,mx],thickness=0,material='walls') wallIds=O.bodies.append(walls) sp=pack.SpherePack() sp.makeCloud(mn,mx,-1,0.3333,num_spheres,False, 0.95,seed=1) #"seed" make the "random" generation always the same sp.toSimulation(material='spheres') triax=TriaxialStressController( maxMultiplier=1.+2e4/young, # spheres growing factor (fast growth) finalMaxMultiplier=1.+2e3/young, # spheres growing factor (slow growth) thickness = 0, stressMask = 7, max_vel = 0.005, internalCompaction=True, # If true the confining pressure is generated by growing particles ) newton=NewtonIntegrator(damping=0.2) O.engines=[ ForceResetter(), InsertionSortCollider([Bo1_Sphere_Aabb(),Bo1_Box_Aabb()]), InteractionLoop( [Ig2_Sphere_Sphere_ScGeom(),Ig2_Box_Sphere_ScGeom()], [Ip2_FrictMat_FrictMat_FrictPhys()], [Law2_ScGeom_FrictPhys_CundallStrack()],label="iloop" ), FlowEngine(dead=1,label="flow"),#introduced as a dead engine for the moment, see 2nd section GlobalStiffnessTimeStepper(active=1,timeStepUpdateInterval=100,timestepSafetyCoefficient=0.8), triax, newton ] triax.goal1=triax.goal2=triax.goal3=-10000 while 1: O.run(1000, True) unb=unbalancedForce() if unb<0.001 and abs(-10000-triax.meanStress)/10000<0.001: break setContactFriction(radians(finalFricDegree)) ## ______________ Oedometer section _________________ #A. Check bulk modulus of the dry material from load/unload cycles triax.stressMask=2 triax.goal1=triax.goal3=0 triax.internalCompaction=False triax.wall_bottom_activated=False #load triax.goal2=-11000; O.run(2000,1) #unload triax.goal2=-10000; O.run(2000,1) #load triax.goal2=-11000; O.run(2000,1) e22=triax.strain[1] #unload triax.goal2=-10000; O.run(2000,1) e22=e22-triax.strain[1] modulus = 1000./abs(e22) #B. Activate flow engine and set boundary conditions in order to get permeability flow.dead=0 flow.defTolerance=0.3 flow.meshUpdateInterval=200 flow.useSolver=3 flow.permeabilityFactor=1 flow.viscosity=10 flow.bndCondIsPressure=[0,0,1,1,0,0] flow.bndCondValue=[0,0,1,0,0,0] flow.boundaryUseMaxMin=[0,0,0,0,0,0] O.dt=0.1e-3 O.dynDt=False O.run(1,1) Qin = flow.getBoundaryFlux(2) Qout = flow.getBoundaryFlux(3) permeability = abs(Qin)/1.e-4 #size is one, we compute K=V/∇H print "Qin=",Qin," Qout=",Qout," permeability=",permeability #C. now the oedometer test, drained at the top, impermeable at the bottom plate flow.bndCondIsPressure=[0,0,0,1,0,0] flow.bndCondValue=[0,0,0,0,0,0] newton.damping=0 #we want the theoretical value from Terzaghi's solution #keep in mind that we are not in an homogeneous material and the small strain #assumption is not verified => we don't expect perfect match #there can be also an overshoot of pressure in the very beginning due to dynamic effects Cv=permeability*modulus/1e4 zeroTime=O.time zeroe22 = - triax.strain[1] dryFraction=0.05 #the top layer is affected by drainage on a certain depth, we account for it here drye22 = 1000/modulus*dryFraction wetHeight=1*(1-dryFraction) def consolidation(Tv): #see your soil mechanics handbook... U=1 for k in range(50): M=pi/2*(2*k+1) U=U-2/M**2*exp(-M**2*Tv) return U triax.goal2=-11000 from yade import plot ## a function saving variables def history(): plot.addData(e22=-triax.strain[1]-zeroe22,e22_theory=drye22+(1-dryFraction)*consolidation((O.time-zeroTime)*Cv/wetHeight**2)*1000./modulus,t=O.time,p=flow.getPorePressure((0.5,0.1,0.5)),s22=-triax.stress(3)[1]-10000) #plot.addData(e22=-triax.strain[1],t=O.time,s22=-triax.stress(2)[1],p=flow.MeasurePorePressure((0.5,0.5,0.5))) O.engines=O.engines+[PyRunner(iterPeriod=200,command='history()',label='recorder')] ##make nice animations: #O.engines=O.engines+[PyRunner(iterPeriod=200,command='flow.saveVtk()')] from yade import plot plot.plots={'t':('e22','e22_theory',None,'s22','p')} plot.plot() O.saveTmp() O.timingEnabled=1 from yade import timing print "starting oedometer simulation" O.run(200,1) timing.stats() ## Make more steps to see the convergence to the stationnary solution trunk-2018.02b/examples/HydroForceEngine/000077500000000000000000000000001324306050200201765ustar00rootroot00000000000000trunk-2018.02b/examples/HydroForceEngine/INFO000066400000000000000000000032131324306050200206530ustar00rootroot00000000000000The folder example of HydroForceEngine contains three different type of examples: - One-way coupling examples, meaning that the fluid is imposed and the HydroForceEngine is used only to apply fluid forces (drag and buoyancy) on the particles from the 1D prescribed fluid profile and the fluid properties. These are: buoyantParticles.py, fluidizedBed.py and sedimentTransportExample.py. In the latter, the HydroForceEngine is also used to evaluate 1D volume-averaged solid depth profiles, which can be plotted with postProcessing_sedim.py - A two-way coupling example (sedimentTransportExample_1DRANSCoupling.py), which consider the exact same configuration as sedimentTransportExample_1DRANSCoupling.py but include a resolution of the volume-averaged 1D fluid momentum balance, i.e. a so-called 1D RANS coupling to the DEM. This coupling is the one that has been used in the papers Maurin et al (2015,2016), which can be found in the references. HydroForceEngine is therefore used here to apply the fluid forces (drag and buoyancy) on the particles from a given 1D fluid profile (basic function of HydroForceEngine when activated), evaluate 1D volume-averaged solid depth profiles (velocity, volume fraction, drag force; averageProfile() function of HydroForceEngine), and solve the 1D volume-averaged fluid momentum (fluidModel function of HydroForceEngine) from the averaged solid quantities determined. - Fluid resolution validation examples, which does not include particles and consider classical validation for the 1D fluid momentum balance resolution, i.e. poiseuille flow and logarithmic profiles. These scripts use only the fluidModel function of HydroForceEngine. trunk-2018.02b/examples/HydroForceEngine/fluidValidation/000077500000000000000000000000001324306050200233145ustar00rootroot00000000000000trunk-2018.02b/examples/HydroForceEngine/fluidValidation/fluidValidation_logProfile.py000066400000000000000000000116551324306050200311760ustar00rootroot00000000000000######################################################################################################################################################################### # Author: Raphael Maurin, raphael.maurin@imft.fr # 22/11/2017 # # Script to validate the 1D volume-averaged fluid resolution of HydroForceEngine, on the classical logarithmic profile. # No particles are simulated and only the newtonian fluid resolution is tested and compared to the analytical solution # # Consider a newtonian free-surface (iusl = 1) fluid flow driven by gravity (channel inclination angle "alpha") on a smooth bottom wall. # Impose the free-surface position fluidHeight and let the fluid resolution evolve toward equilibrium. # The turbulent stresses are modelled using an eddy viscosity concept with the classical Prandtl mixing length closure (ilm = 0) # Account for the presence of a viscous sublayer close to the wall (viscousSubLayer = 1) # # The obtained 1D fluid velocity profile (vxFluid) is compared to the analytical solution in the output figure "figProfiLog.png". It is necessary # to have a dense mesh in order to solve correctly the viscous sublayer. In addition, discrepancies can be observed close to the fluid free-surface, # as the analytical solution does not account for its presence. # ######################################################################################################################################################################### import numpy as np ################################ # PARAMETERS AND CONFIGURATION DEFINITIONS #Fluid flow characteristics alpha = 0.5e-1 #Channel inclination angle, in radian fluidHeight = 1e-1 #Fluid height, in m densFluid = 1e3 #Fluid density kg/m^3, water kinematicViscoFluid = 1e-6 #Kinematic fluid viscosity, m^2/s, water #Simulation characteristic tfin = 1e3 #Total simulated time, in s dtFluid = 2e-1 #Resolution time step, in s #Mesh ndimz = 10000 #Number of grid cells in the height. Needs to be high in order to solve accurately the viscous sublayer dz = fluidHeight/(1.0*(ndimz-1)) #spatial step between two mesh nodes # Initialization of the fluid velocity vxFluid =np.zeros(ndimz) gravityVector = Vector3(9.81*sin(alpha),0,-9.81*cos(alpha)) ################################ # CREATE THE ASSOCIATED HYDROFORCEENGINE OBJECT FOR THE FLUID RESOLUTION a = HydroForceEngine(densFluid=densFluid,viscoDyn=kinematicViscoFluid*densFluid,deltaZ=dz,nCell=ndimz,vxFluid=np.array(vxFluid),gravity=gravityVector,phiMax=0.61,turbulentViscosity = np.zeros(ndimz),iturbu=1,ilm=0,iusl=1,viscousSubLayer=1,phiPart=np.zeros(ndimz),vxPart = np.zeros(ndimz),averageDrag=np.zeros(ndimz))# Define the minimum parameters of the HydroForceEngine for the fluid resolution ################################ # FLUID RESOLUTION a.fluidResolution(tfin,dtFluid)# Call the fluid resolution associated with the defined HydroForceEngine. Use the parameters defined just above. print "\nFluid Resolution finished ! ","max(vxFluid)=",np.max(a.vxFluid),"\n" ################################ # COMPARISON TO THE ANALYTICAL SOLUTION print "Comparison to the analytical solution\n" vxFluid = np.array(a.vxFluid) ## Analytical Solution kappa=0.41 # Von Karman constant ustar=sqrt(abs(gravityVector[0])*fluidHeight) # Shear velocity dz_ap=1.*kinematicViscoFluid/ustar # Dimensionless vertical scale unit nz=int(round(fluidHeight/dz_ap)) # Number of unit over the fluid depth ztrans=11.3 #Vertical position of the transition from the viscous sublayer to the log law, in dimensionless unit z+. Take it classically as 11.3, similarly to the fluid resolution utrans=ztrans*ustar #Fluid velocity transition from the viscous sublayer to the log law, in dimensionless unit z+ z_analytic=np.linspace(0,1e0,nz)*fluidHeight #Re-create the vertical scale zplus=z_analytic*ustar/kinematicViscoFluid #Dimensionless vertical scale vxFluid_analytic =np.zeros(nz) #Initialization for i in range(nz): if (zplus[i]<=ztrans): vxFluid_analytic[i]=ustar*zplus[i] #Linear fluid profile in the viscous sub layer else: vxFluid_analytic[i]=ustar/kappa*np.log(zplus[i]/ztrans)+utrans; #Logarythmic fluid profile above ############################## #### PLOT AND COMPARISON from pylab import * from matplotlib import pyplot import matplotlib.gridspec as gridspec # Re-create the fluid resolution mesh: regular spacing, with differences at the bottom (0) and at the top (ndimz-2). dsig = np.ones(ndimz-1)*dz dsig[0]=1.5*dz dsig[-1]=0.5*dz sig=np.zeros(ndimz) for i in range(1,ndimz): sig[i]=sig[i-1]+dsig[i-1] #Figure creation: compare the analytical solution and the fluid resolution pyplot.figure(1) gs = gridspec.GridSpec(1, 1) ax1 = subplot(gs[0,0]) p1=ax1.plot(vxFluid,sig,'-ob',label='u_f') #fluid resolution results: in blue points p2=ax1.semilogy(vxFluid_analytic,z_analytic,'-r',label='u_f^ex') #Analytical solution: red line xlabel(' (m/s)') ylabel('z (m)') savefig('figProfiLog.png') show() print "finished! exit\n" exit() #Leave the simulation trunk-2018.02b/examples/HydroForceEngine/fluidValidation/fluidValidation_poiseuille.py000066400000000000000000000064371324306050200312500ustar00rootroot00000000000000######################################################################################################################################################################### # Author: Raphael Maurin, raphael.maurin@imft.fr # 22/11/2017 # # Script to validate the 1D volume-averaged fluid resolution of HydroForceEngine, without accounting for turbulence. Simulate a Poiseuille flow # No particles are simulated and only the fluid resolution is tested and compared to the analytical solution # # Consider a newtonian fluid flow of height "fluidHeight" driven by a pressure gradient (dpdx) between two fixed walls (iusl = 0, fixed condition), # without turbulence (iturbu=0). # # The obtained 1D fluid velocity profile (vxFluid) is compared to the analytical solution in the figure "figPoiseuille.png" # ######################################################################################################################################################################### import numpy as np ################################ # PARAMETERS AND CONFIGURATION DEFINITIONS #Fluid flow characteristics fluidHeight = 1. #Fluid height, i.e. gap between the two walls, in m dpdx =-0.10 #Driving pressure gradient, in Pa/m densFluid = 1e3 #Fluid density kg/m^3, water kinematicViscoFluid = 1e-6 #Kinematic fluid viscosity, m^2/s, water #Simulation characteristic tfin = 1e7 #Total simulated time, in s dtFluid = 1e4 #Resolution time step, in s #Mesh ndimz = 101 #Number of grid cells in the height dz = fluidHeight/(1.0*(ndimz-1)) #spatial step between two mesh nodes # Initialization of the fluid velocity vxFluid =np.zeros(ndimz) ################################ # CREATE THE ASSOCIATED HYDROFORCEENGINE OBJECT FOR THE FLUID RESOLUTION a = HydroForceEngine(densFluid=densFluid,viscoDyn=kinematicViscoFluid*densFluid,deltaZ=dz,nCell=ndimz,vxFluid=np.array(vxFluid),dpdx=dpdx,gravity = Vector3(0,0,-9.81),phiMax=0.61,turbulentViscosity = np.zeros(ndimz),iturbu=0,iusl=0,phiPart=np.zeros(ndimz),vxPart = np.zeros(ndimz),averageDrag=np.zeros(ndimz))# Define the minimum parameters of the HydroForceEngine for the fluid resolution ################################ # FLUID RESOLUTION a.fluidResolution(tfin,dtFluid)# Call the fluid resolution associated with the defined HydroForceEngine. Use the parameters defined just above. print "\nFluid Resolution finished ! ","max(vxFluid)=",np.max(a.vxFluid),"\n" ################################ # COMPARISON TO THE ANALYTICAL SOLUTION print "Comparison to the analytical solution\n" vxFluid = np.array(a.vxFluid) # Re-create the fluid resolution mesh: regular spacing, with differences at the bottom (0) and at the top (ndimz-2). dsig = np.ones(ndimz-1)*dz dsig[0]=1.5*dz dsig[ndimz-2]=0.5*dz sig=np.zeros(ndimz) for i in range(1,ndimz): sig[i]=sig[i-1]+dsig[i-1] # Analytical solution of the fluid velocity upois=-dpdx*1/2./(kinematicViscoFluid*densFluid)*(fluidHeight*sig-sig*sig) ## plots from pylab import * from matplotlib import pyplot import matplotlib.gridspec as gridspec figure(1) ax1 =subplot(111) p1=ax1.plot(vxFluid,sig,'-ob',label='u_f') #Simulation results = blue points p1=ax1.plot(upois,sig,'-r',label='u_f^ex',lw = 2) #Analytical result = red line xlabel('V_x^f (m/s)') ylabel('z (m)') savefig('figPoiseuille.png') show() print "finished! exit\n" exit() #Leave the simulation at the end trunk-2018.02b/examples/HydroForceEngine/oneWayCoupling/000077500000000000000000000000001324306050200231415ustar00rootroot00000000000000trunk-2018.02b/examples/HydroForceEngine/oneWayCoupling/buoyantParticles.py000066400000000000000000000126771324306050200270600ustar00rootroot00000000000000######################################################################################################################################################################### # Author: Raphael Maurin, raphael.maurin@imft.fr # 07/07/2016 # # Example script to use HydroForceEngine in order to apply buoyancy force to particles. # The fluid is supposed at rest (vxFluidPY is a zero vector) so that the particles are only submitted to a drag force opposing the motion of the particle. # Three spheres with density 1500, 1000 and 500kg/m3 are positionned at the middle of a fluid sample of density 1000 kg/m3, and let evolved with time # We observe clearly that one sphere sediment down to the bottom, another one is rising to the top of the water free-surface and the third one does not move # ############################################################################################################################################################################ #Import libraries from yade import pack, plot import math import random as rand import numpy as np ## ## Main parameters of the simulation ## #Particles diameterPart = 6e-3 #Diameter of the particles, in meter restitCoef = 0.5 #Restitution coefficient of the particles partFrictAngle = atan(0.4) #friction angle of the particles, in radian densPart1 = 1050 #density of the particles, in kg/m3 densPart2 = 1000 #density of the particles, in kg/m3 densPart3 = 950 #density of the particles, in kg/m3 #fluid densFluidPY = 1000. #Density of the fluid kinematicViscoFluid = 1e-6 #kinematic viscosity of the fluid fluidHeight = 20.*diameterPart #Water depth in m. ndimz = 1 groundPosition = 0.#Definition of the position of the ground, in m gravityVector = Vector3(0,0.0,-9.81) #Gravity vector #Particles contact law/material parameters normalStiffness = 1e4 youngMod = normalStiffness/diameterPart #Young modulus of the particles from the stiffness wanted. poissonRatio = 0.5 #poisson's ratio of the particles. Classical values, does not have much influence #Material of particle 1, 2, and 3, with different density densPart1, densPart2 and densPart3 defined above (1500, 1000 and 500kg/m3 by default) O.materials.append(ViscElMat(en=restitCoef, et=0., young=youngMod, poisson=poissonRatio, density=densPart1, frictionAngle=partFrictAngle, label='Mat1')) O.materials.append(ViscElMat(en=restitCoef, et=0., young=youngMod, poisson=poissonRatio, density=densPart2, frictionAngle=partFrictAngle, label='Mat2')) O.materials.append(ViscElMat(en=restitCoef, et=0., young=youngMod, poisson=poissonRatio, density=densPart3, frictionAngle=partFrictAngle, label='Mat3')) #Time of simulation endTime = 2. ######################## ## FRAMEWORK CREATION ## ######################## # Reference walls: build a wall at the ground and draw the position of the free-surface to have a reference for the eyes in the 3D view lowPlane = box(center= (0, 0,groundPosition),extents=(200,200,0),fixed=True,wire=False,color = (0.,1.,0.)) WaterSurface = box(center= (0,0,groundPosition+fluidHeight),extents=(200,200,0),fixed=True,wire=False,color = (0,0,1),mask = 0) O.bodies.append([lowPlane,WaterSurface]) #add to simulation id1 = O.bodies.append(sphere(center = (0,2*diameterPart,groundPosition + fluidHeight/2.), radius = diameterPart/2.,material = 'Mat1')) id2 = O.bodies.append(sphere(center = (0,0,groundPosition + fluidHeight/2.), radius = diameterPart/2.,material = 'Mat2')) id3 = O.bodies.append(sphere(center = (0,-2*diameterPart,groundPosition + fluidHeight/2.), radius = diameterPart/2.,material = 'Mat3')) # Collect the ids of the spheres which are dynamic to add a fluid force through HydroForceEngines idApplyForce = [id1,id2,id3] ######################### #### SIMULATION LOOP##### ######################### O.engines = [ # Reset the forces ForceResetter(), # Detect the potential contacts InsertionSortCollider([Bo1_Sphere_Aabb(), Bo1_Wall_Aabb(),Bo1_Facet_Aabb(),Bo1_Box_Aabb()],label='contactDetection',allowBiggerThanPeriod = True), # Calculate the different interactions InteractionLoop( [Ig2_Sphere_Sphere_ScGeom(), Ig2_Box_Sphere_ScGeom()], [Ip2_ViscElMat_ViscElMat_ViscElPhys()], [Law2_ScGeom_ViscElPhys_Basic()] ,label = 'interactionLoop'), #Apply an hydrodynamic force to the particles HydroForceEngine(densFluid = densFluidPY,viscoDyn = kinematicViscoFluid*densFluidPY,zRef = groundPosition,gravity = gravityVector,deltaZ = fluidHeight/ndimz,expoRZ = 0.,lift = False,nCell = ndimz,vCell = 1.,vxFluid = np.zeros(ndimz),phiPart = np.zeros(ndimz),vxPart = np.zeros(ndimz),vFluctX = np.zeros(len(O.bodies)),vFluctY = np.zeros(len(O.bodies)),vFluctZ = np.zeros(len(O.bodies)),ids = idApplyForce, label = 'hydroEngine'), #To plot the wall normal position of the spheres with time PyRunner(command = 'plotPos()', virtPeriod = 0.01, label = 'plot'), # Integrate the equation and calculate the new position/velocities... NewtonIntegrator(gravity=gravityVector, label='newtonIntegr') ] #save the initial configuration to be able to recharge the simulation starting configuration easily O.saveTmp() #Time step O.dt = 5e-7 #Low no purpose, in order to observe the sedimentation #Plot the wall normal position of the spheres with time def plotPos(): plot.addData(z1 = O.bodies[2].state.pos[2]/fluidHeight,z2 = O.bodies[3].state.pos[2]/fluidHeight,z3 = O.bodies[4].state.pos[2]/fluidHeight, time = O.time) if O.time>endTime: print('\nEnd of the simulation, {0}s simulated as asked!\n'.format(endTime)) O.pause() plot.plots={'time':('z1','z2','z3')} plot.plot() trunk-2018.02b/examples/HydroForceEngine/oneWayCoupling/fluidizedBed.py000066400000000000000000000233361324306050200261140ustar00rootroot00000000000000######################################################################################################################################################################### # Author: Raphael Maurin, raphael.maurin@imft.fr # 08/07/2016 # # Very simplified fluidized bed simulations in order to underline the possibility of HydroForceEngine. # Particles are deposited under gravity inside a box. Once the particles at rest, a constant fluid velocity is applied against gravity, submitting the # particles to a constant drag force. Then, a discrete random walk model is applied to mimick the effect of the turbulent fluid velocity fluctuations. # It associates to each particle a random fluid velocity fluctuations in the x, y and z directions, which are taken into account in the evaluation # of the drag applied by the fluid on the particle. The intensity of the fluctuation is based on the value of u*, which is imposed through simplifiedReynoldsStress = u*^2 # The values taken for the fluid velocity and simplifiedReynolds stress have been arbitrarily chosen to have a nice rendering # # The example allows to get familiar with HydroForceEngine and in particular with the included DRW model functions (turbulentFluctuationFluidizedBed() here) # For details on the process, it is necessary to have a look to the documentation and the C++ code in pkg/common/ForceEngine.cpp and hpp as HydroForceEngine contains # multiple parameters ############################################################################################################################################################################ # ATTENTION: to fit the formulation of HydroForceEngine, the fluid velocity can only be applied along the x axis. Therefore, the gravity is here aligned with x, and all the # configuration is defined accordingly. For the 3D visualization, two walls of the cell have been made transparent and clicking on the right xyz button after openning the # 3D view allows one to see the sample in the usual way with gravity going from top to bottom. #Import libraries from yade import pack, plot import math import random as rand import numpy as np ## ## Main parameters of the simulation ## #Particles diameterPart = 6e-3 #Diameter of the particles, in meter densPart = 2500 #density of the particles, in kg/m3 restitCoef = 0.8 #Restitution coefficient of the particles partFrictAngle = atan(0.4) #friction angle of the particles, in radian #fluid densFluidPY = 1.225 #Density of the fluid, (air) in kg/m3 kinematicViscoFluid = 1.48e-5 #kinematic viscosity of the fluid, (air) in m2/s #Configuration: inclined channel lengthCell = 100 #length cell along the gravity axis (x), in diameter widthCell = 10 #length cell along the two other axis, in diameter Nlayer = 2.5 #nb of layer of particle deposited, in diameter phiPartMax = 0.61 #Value of the dense packing solid volume fraction endTime = 10 #Time simulated (in seconds) ## ## Secondary parameters of the simulation ## expoDrag_PY = 3.1 # Richardson Zaki exponent for the hindrance function of the drag force applied to the particles ndimz = 20 #Number of cells in the height dz = widthCell*diameterPart/ndimz # Fluid discretization step in the wall-normal direction # Initialization of the main vectors vxFluidPY = np.ones(ndimz)*18.5 # Vertical fluid velocity profile: u^f = u_x^f(z) e_x, with x the streamwise direction and z the wall-normal phiPartPY = np.zeros(ndimz) # Vertical particle volume fraction profile vxPartPY = np.zeros(ndimz) # Vertical average particle velocity profile #Geometrical configuration, define useful quantities length = lengthCell*diameterPart #length of the stream, in m width = widthCell*diameterPart #width of the stream, in m groundPosition = 0. #Definition of the position of the ground, in m gravityVector = Vector3(-9.81,0.0,0.) #Gravity vector. Inclined along x to have an effect opposed to the fluid flow (which can only be along x) #Particles contact law/material parameters maxPressure = (densPart-densFluidPY)*phiPartMax*Nlayer*diameterPart*9.81#Estimated max particle pressure from the static load normalStiffness = maxPressure*diameterPart*1e4 #Evaluate the minimal normal stiffness to be in the rigid particle limit (cf Roux and Combe 2002) youngMod = normalStiffness/diameterPart #Young modulus of the particles from the stiffness wanted. poissonRatio = 0.5 #poisson's ratio of the particles. Classical values, does not have much influence O.materials.append(ViscElMat(en=restitCoef, et=0., young=youngMod, poisson=poissonRatio, density=densPart, frictionAngle=partFrictAngle, label='Mat')) ######################## ## FRAMEWORK CREATION ## ######################## # Walls to create a box lowPlane = box(center= (groundPosition, width/2.0,width/2.),extents=(0,width/2.,width/2.),fixed=True,wire=False,color = (0.,1.,0.),material = 'Mat') sidePlane1 = box(center= (length/2., width,width/2.),extents=(length/2.,0.,width/2.),fixed=True,wire=True,color = (0.,1.,0.),material = 'Mat') sidePlane2 = box(center= (length/2., 0.,width/2.),extents=(length/2.,0.,width/2.),fixed=True,wire=True,color = (0.,1.,0.),material = 'Mat') sidePlane3 = box(center= (length/2.,width/2., 0.),extents=(length/2.,width/2.,0.),fixed=True,wire=False,color = (0.,1.,0.),material = 'Mat') #Made invisible (wire = True) in order to see inside the cell sidePlane4 = box(center= (length/2.,width/2., width),extents=(length/2.,width/2.,0.),fixed=True,wire=False,color = (0.,1.,0.),material = 'Mat') #Made invisible (wire = True) in order to see inside the cell O.bodies.append([lowPlane,sidePlane1,sidePlane2,sidePlane3,sidePlane4]) #Create a loose cloud of particle inside the cell partCloud = pack.SpherePack() partVolume = pi/6.*pow(diameterPart,3) #Volume of a particle partNumber = int(Nlayer*phiPartMax*diameterPart*width*width/partVolume) #Volume of beads to obtain Nlayer layers of particles partCloud.makeCloud(minCorner=(0,0.,0),maxCorner=(length,width,width),rRelFuzz=0., rMean=diameterPart/2.0, num = partNumber) partCloud.toSimulation(material='Mat') #Send this packing to simulation with material Mat #Evaluate the deposition time considering the free-fall time of the highest particle to the ground depoTime = sqrt(length*2/9.31) # Collect the ids of the spheres which are dynamic to add a fluid force through HydroForceEngines idApplyForce = [] for b in O.bodies: if isinstance(b.shape,Sphere) and b.dynamic: idApplyForce+=[b.id] ######################### #### SIMULATION LOOP##### ######################### O.engines = [ # Reset the forces ForceResetter(), # Detect the potential contacts InsertionSortCollider([Bo1_Sphere_Aabb(), Bo1_Wall_Aabb(),Bo1_Facet_Aabb(),Bo1_Box_Aabb()],label='contactDetection',allowBiggerThanPeriod = True), # Calculate the different interactions InteractionLoop( [Ig2_Sphere_Sphere_ScGeom(), Ig2_Box_Sphere_ScGeom()], [Ip2_ViscElMat_ViscElMat_ViscElPhys()], [Law2_ScGeom_ViscElPhys_Basic()] ,label = 'interactionLoop'), #Apply an hydrodynamic force to the particles HydroForceEngine(densFluid = densFluidPY,viscoDyn = kinematicViscoFluid*densFluidPY,zRef = groundPosition,gravity = gravityVector,deltaZ = dz,expoRZ = expoDrag_PY,lift = False,nCell = ndimz,vCell = length*width*dz ,vxFluid = vxFluidPY,phiPart = phiPartPY,vxPart = vxPartPY,ids = idApplyForce,vFluctX = np.zeros(len(O.bodies)),vFluctY = np.zeros(len(O.bodies)),vFluctZ = np.zeros(len(O.bodies)), label = 'hydroEngine', dead = True), #Measurement, output files PyRunner(command = 'measure()', virtPeriod = 0.1, label = 'measurement', dead = True), # Check if the packing is stabilized, if yes activate the hydro force on the grains and the slope. PyRunner(command='gravityDeposition(depoTime)',virtPeriod = 0.01,label = 'gravDepo'), #Apply fluid turbulent fluctuations from a discrete random walk model PyRunner(command='turbulentFluctuations()',virtPeriod = 0.01,label = 'turbFluct'), #GlobalStiffnessTimeStepper, determine the time step GlobalStiffnessTimeStepper(defaultDt = 1e-4, viscEl = True,timestepSafetyCoefficient = 0.7, label = 'GSTS'), # Integrate the equation and calculate the new position/velocities... NewtonIntegrator(damping=0.2, gravity=gravityVector, label='newtonIntegr') ] #save the initial configuration to be able to recharge the simulation starting configuration easily O.saveTmp() #run #O.run() ##################################################################################################################################### ##################################################### FUNCTION DEFINITION ######################################################### ##################################################################################################################################### ###### ###### ### LET THE TIME FOR THE GRAVITY DEPOSITION AND ACTIVATE THE FLUID AT THE END ### ###### ###### def gravityDeposition(lim): if O.time0) vxPart[toto]/=phiPart[toto] #Avoid division by zero phiPart/=(endFile-startFile+1) vxFluid/=(endFile-startFile+1) ###### # PLOT # sediment transport rate as a function of time fig1 = figure() #Create a figure gs = gridspec.GridSpec(1,1) #Split the figure in 1 row and 1 column ax=subplot(gs[0,0]) #Assign the axis ax.plot(time,qs,'ok') #Plot the sediment transport rate as a function of time ax.grid() #Put a grid on the figure ax.set_xlabel('t (s)') #Assign the x axis label ax.set_ylabel(r'$Q_s^*$',rotation='horizontal')#Assign the y axis label savefig('transportRate.png') #Save the figure # Solid and fluid velocity depth profiles, solid volume fraction and sediment transport rate density depth profiles fig2 = figure() gs = gridspec.GridSpec(1,3) #Split the figure in 1 row and 3 column ax1=subplot(gs[0,0]) #ax1 = subfigure corresponding to 1st row 1st column ax2=subplot(gs[0,1]) #ax2 = subfigure corresponding to 1st row 2nd column ax3=subplot(gs[0,2]) #ax3 = subfigure corresponding to 1st row 3rd column ax1.plot(vxPart,zAxis,'-r') #Plot the solid velocity profile on first subfigure ax1.plot(vxFluid,zAxis,'-b') #Plot the fluid velocity profile on first subfigure ax2.plot(phiPart,zAxis,'-r') #Plot the solid volume fraction on second subfigure ax3.plot(phiPart*vxPart,zAxis,'-r') #Plot the sediment transport rate density on the third subfigure ax1.set_xlabel(r'$$ (m/s)') #Assign the x axis label of the first subfigure ax1.set_ylabel(r'$z/d$',rotation='horizontal') #Assign the y axis label of the first subfigure ax2.set_xlabel(r'$\phi$') #Assign the x axis label of the second subfigure ax3.set_xlabel(r'$q (m/s)$') #Assign the x axis label of the third subfigure ax2.set_yticklabels([]) #Remove the vertical scale for the second subfigure, same as first ax3.set_yticklabels([]) #Remove the vertical scale for the third subfigure, same as first ax1.grid() #Put a grid on each subfigure ax2.grid() ax3.grid() fig2.subplots_adjust(left=0.08, bottom=0.11, right=0.98, top=0.98, wspace=.1) #Adjust the spacing around the full figure (left, bottom, right, top) and between the subfigure (wspace), in percent of the figure size savefig('depthProfiles.png') #Save the figure show() #Show the two figures created trunk-2018.02b/examples/HydroForceEngine/oneWayCoupling/sedimentTransportExample.py000066400000000000000000000632551324306050200305670ustar00rootroot00000000000000######################################################################################################################################################################### # Author: Raphael Maurin, raphael.maurin@imft.fr # 24/11/2017 # # Example script to simulate sediment transport from an imposed 1D vertical fluid profile # Make use of HydroForceEngine, which is tailored to be used with a 1D RANS code, solving the fluid momentum balance as a function of the depth to determine u_x^f(z) the vertical fluid profile (vxFluidPY). # # Create a bi-periodic inclined channel with a rough bottom, and deposit a cloud of particles under gravity to form a granular bed. # Apply fluid forces from an imposed fluid profile vxFluidPY through the engine HydroForceEngine # Let evolve the problem to equilibrium and measure the granular depth profiles of particles volume fraction and particle velocity # # The force applied by the fluid on the particles are restricted to drag and buoyancy. To know the exact expressions of the latter (and for any precisions regarding an action of HydroForceEngine), please refer to the documentation of HydroForceEngine and to the c++ source code pkg/common/HydroForceEngine.cpp and hpp. # # Data can be saved and plot with the file postProcessing_sedim.py # ############################################################################################################################################################################ #Import libraries from yade import pack, plot import math import random as rand import numpy as np ## ## Main parameters of the simulation ## #Particles diameterPart = 6e-3 #Diameter of the particles, in meter densPart = 2500 #density of the particles, in kg/m3 phiPartMax = 0.61 #Value of the dense packing solid volume fraction, dimensionless restitCoef = 0.5 #Restitution coefficient of the particles, dimensionless partFrictAngle = atan(0.4) #friction angle of the particles, in radian #Fluid densFluidPY = 1000. #Density of the fluid, in kg/m3 kinematicViscoFluid = 1e-6 #kinematic viscosity of the fluid, in m^2/s waterDepth = 20.#Water depth, in diameter #Configuration: inclined channel slope = 0.05 #Inclination angle of the channel slope, in radian lengthCell = 10 #Streamwise length of the periodic cell, in diameter widthCell = 10 #Spanwise length of the periodic cell, in diameter Nlayer = 10. #nb of layer of particle, in diameter fluidHeight = (Nlayer+waterDepth)*diameterPart #Height of the flow from the bottom of the sample, in m saveData = 1 #If put to 1, at each execution of function measure() save the sediment transport rate, fluid velocity, solid volume fraction and velocity profiles for post-processing endTime = 10 #Time simulated (in seconds) ## ## Secondary parameters of the simulation ## expoDrag_PY = 3.1 # Richardson Zaki exponent for the hindrance function of the drag force applied to the particles #Discretization of the sample in ndimz wall-normal (z) steps of size dz, between the bottom of the channel and the position of the water free-surface. Should be equal to the length of the imposed fluid profile. Mesh used for HydroForceEngine. ndimz = 900 #Number of cells in the height dz = fluidHeight/(1.0*(ndimz-1)) # Fluid discretization step in the wall-normal direction # Initialization of the main vectors vxFluidPY = np.zeros(ndimz) # Vertical fluid velocity profile: u^f = u_x^f(z) e_x, with x the streamwise direction and z the wall-normal phiPartPY = np.zeros(ndimz) # Vertical particle volume fraction profile vxPartPY = np.zeros(ndimz) # Vertical average particle velocity profile #Geometrical configuration, define useful quantities height = 5*fluidHeight #heigth of the periodic cell, in m (bigger than the fluid height to take into particles jumping above the latter) length = lengthCell*diameterPart #length of the stream, in m width = widthCell*diameterPart #width of the stream, in m groundPosition = height/4.0 #Definition of the position of the ground, in m gravityVector = Vector3(9.81*sin(slope),0.0,-9.81*cos(slope)) #Gravity vector to consider a channel inclined with slope angle 'slope' #Particles contact law/material parameters maxPressure = (densPart-densFluidPY)*phiPartMax*Nlayer*diameterPart*abs(gravityVector[2]) #Estimated max particle pressure from the static load normalStiffness = maxPressure*diameterPart*1e4 #Evaluate the minimal normal stiffness to be in the rigid particle limit (cf Roux and Combe 2002) youngMod = normalStiffness/diameterPart #Young modulus of the particles from the stiffness wanted. poissonRatio = 0.5 #poisson's ratio of the particles. Classical values, does not have much influence O.materials.append(ViscElMat(en=restitCoef, et=0., young=youngMod, poisson=poissonRatio, density=densPart, frictionAngle=partFrictAngle, label='Mat')) ######################## ## FRAMEWORK CREATION ## ######################## #Definition of the semi-periodic cell O.periodic = True O.cell.setBox(length,width,height) # Reference walls: build two planes at the ground and free-surface to have a reference for the eyes in the 3D view lowPlane = box(center= (length/2.0, width/2.0,groundPosition),extents=(200,200,0),fixed=True,wire=False,color = (0.,1.,0.),material = 'Mat') WaterSurface = box(center= (length/2.0, width/2.0,groundPosition+fluidHeight),extents=(2000,width/2.0,0),fixed=True,wire=False,color = (0,0,1),material = 'Mat',mask = 0) O.bodies.append([lowPlane,WaterSurface]) #add to simulation # Regular arrangement of spheres sticked at the bottom with random height L = range(0,int(length/(diameterPart))) #The length is divided in particle diameter W = range(0,int(width/(diameterPart))) #The width is divided in particle diameter for x in L: #loop creating a set of sphere sticked at the bottom with a (uniform) random altitude comprised between 0.5 (diameter/12) and 5.5mm (11diameter/12) with steps of 0.5mm. The repartition along z is made around groundPosition. for y in W: n = rand.randrange(0,12,1)/12.0*diameterPart #Define a number between 0 and 11/12 diameter with steps of 1/12 diameter (0.5mm in the experiment) O.bodies.append(sphere((x*diameterPart, y*diameterPart,groundPosition - 11*diameterPart/12.0/2.0 + n),diameterPart/2.,color=(0,0,0),fixed = True,material = 'Mat')) #Create a loose cloud of particle inside the cell partCloud = pack.SpherePack() partVolume = pi/6.*pow(diameterPart,3) #Volume of a particle partNumber = int(Nlayer*phiPartMax*diameterPart*length*width/partVolume) #Volume of beads to obtain Nlayer layers of particles partCloud.makeCloud(minCorner=(0,0.,groundPosition+diameterPart),maxCorner=(length,width,groundPosition+fluidHeight),rRelFuzz=0., rMean=diameterPart/2.0, num = partNumber) partCloud.toSimulation(material='Mat') #Send this packing to simulation with material Mat #Evaluate the deposition time considering the free-fall time of the highest particle to the ground depoTime = sqrt(fluidHeight*2/abs(gravityVector[2])) # Collect the ids of the spheres which are dynamic to add a fluid force through HydroForceEngines idApplyForce = [] for b in O.bodies: if isinstance(b.shape,Sphere) and b.dynamic: idApplyForce+=[b.id] ######################### #### SIMULATION LOOP##### ######################### O.engines = [ # Reset the forces ForceResetter(), # Detect the potential contacts InsertionSortCollider([Bo1_Sphere_Aabb(), Bo1_Wall_Aabb(),Bo1_Facet_Aabb(),Bo1_Box_Aabb()],label='contactDetection',allowBiggerThanPeriod = True), # Calculate the different interactions InteractionLoop( [Ig2_Sphere_Sphere_ScGeom(), Ig2_Box_Sphere_ScGeom()], [Ip2_ViscElMat_ViscElMat_ViscElPhys()], [Law2_ScGeom_ViscElPhys_Basic()] ,label = 'interactionLoop'), #Apply an hydrodynamic force to the particles HydroForceEngine(densFluid = densFluidPY,viscoDyn = kinematicViscoFluid*densFluidPY,zRef = groundPosition,gravity = gravityVector,deltaZ = dz,expoRZ = expoDrag_PY,nCell = ndimz,vCell = length*width*dz ,vxFluid = vxFluidPY,phiPart = phiPartPY,vxPart = vxPartPY,ids = idApplyForce, label = 'hydroEngine', dead = True), #Measurement, output files PyRunner(command = 'measure()', virtPeriod = 0.1, label = 'measurement', dead = True), # Check if the packing is stabilized, if yes activate the hydro force on the grains and the slope. PyRunner(command='gravityDeposition(depoTime)',virtPeriod = 0.01,label = 'gravDepo'), #GlobalStiffnessTimeStepper, determine the time step GlobalStiffnessTimeStepper(defaultDt = 1e-4, viscEl = True,timestepSafetyCoefficient = 0.7, label = 'GSTS'), # Integrate the equation and calculate the new position/velocities... NewtonIntegrator(damping=0.2, gravity=gravityVector, label='newtonIntegr') ] #save the initial configuration to be able to recharge the simulation starting configuration easily O.saveTmp() #run O.run() #################################################################################################################################### #################################################### FUNCTION DEFINITION ######################################################### #################################################################################################################################### ###### ###### ### LET THE TIME FOR THE GRAVITY DEPOSITION AND ACTIVATE THE FLUID AT THE END ### ###### ###### def gravityDeposition(lim): if O.time=endTime: print('\n End of the simulation, simulated {0}s as required !\n '.format(endTime)) O.pause() #Z scale used for the possible plot at the end global zAxis for i in range(0,ndimz): zAxis[i] = i*dz/diameterPart if saveData==1: #Save data for postprocessing global fileNumber nameFile = scriptPath + '/data/'+ str(fileNumber)+'.py' # Name of the file that will be saved globalParam = ['qsMean','phiPartPY','vxPartPY','vxFluidPY','zAxis'] # Variables to save Save(nameFile, globalParam) #Save fileNumber+=1 #Increment the file number #Save data details fileNumber = 0 # Counter for the file saved if saveData==1: #If saveData option is activated, requires a folder data scriptPath = os.path.abspath(os.path.dirname(sys.argv[-1])) #Path where the script is stored if os.path.exists(scriptPath +'/data/')==False: os.mkdir(scriptPath +'/data/') else: print '\n!! Save data: overwrite the files contains in the folder data/ !!\n' #Function to save global variables in a python file which can be re-executed for post-processing def Save(filePathName, globalVariables): f = open(filePathName,'w') f.write('from numpy import *\n') for i in globalVariables: f.write(i + ' = '+repr(globals()[i]) + '\n') f.close() #Plot the dimensionless sediment transport rate as a function of time during the simulation plot.plots={'time':('SedimentRate')} plot.plot() ################ ########################################## #Imposed fluid profile, corresponding to a Shields number 0.5 vxFluidPY = np.array([ 0. , 0.06156562, 0.08728988, 0.10283756, 0.11005262, 0.11059876, 0.1060113 , 0.0976961 , 0.08701183, 0.07527384, 0.06375715, 0.05362684, 0.04587625, 0.04124045, 0.04014932, 0.04265518, 0.04835575, 0.05637364, 0.06555057, 0.07466258, 0.0825892 , 0.08848402, 0.09180331, 0.09234952, 0.09026192, 0.08598644, 0.08023121, 0.07377648, 0.0673878 , 0.06174271, 0.05740186, 0.0547775 , 0.05407727, 0.05527346, 0.05813167, 0.0622459 , 0.06708855, 0.07204081, 0.07651257, 0.07999516, 0.08215522, 0.08283083, 0.08204372, 0.07996273, 0.07689339, 0.0732465 , 0.06949481, 0.06610862, 0.06347541, 0.06185502, 0.0613583 , 0.06194208, 0.06343303, 0.0655741 , 0.06804706, 0.07051492, 0.0726754 , 0.07429343, 0.07521449, 0.07537433, 0.07479194, 0.07357723, 0.07192908, 0.07010514, 0.06839215, 0.06704851, 0.06626627, 0.06614762, 0.06669344, 0.06779834, 0.06927958, 0.07091005, 0.07245458, 0.07369861, 0.07446029, 0.07462772, 0.07419583, 0.07325704, 0.07196604, 0.07049793, 0.06903213, 0.06774085, 0.06677561, 0.0662449 , 0.06619538, 0.06661213, 0.06742787, 0.0685343 , 0.06978656, 0.07102537, 0.07210075, 0.07289378, 0.07334036, 0.07343889, 0.07323677, 0.07280684, 0.0722337 , 0.07160383, 0.07099545, 0.07047215, 0.07007524, 0.06982274, 0.06971082, 0.06971833, 0.06980796, 0.06993805, 0.07007409, 0.07019433, 0.07028775, 0.07034303, 0.07035078, 0.07031745, 0.07026673, 0.07022685, 0.070222 , 0.07026871, 0.07037337, 0.0705376 , 0.07075427, 0.07100814, 0.07128164, 0.07155703, 0.07182318, 0.07207437, 0.07230019, 0.0724788 , 0.0725801 , 0.07257863, 0.07245956, 0.07222669, 0.07189895, 0.07150785, 0.07108934, 0.07068479, 0.07033428, 0.0700743 , 0.06993614, 0.06994256, 0.07010162, 0.07040221, 0.07081663, 0.07130815, 0.07183653, 0.07236057, 0.0728381 , 0.07323606, 0.07353095, 0.07371097, 0.0737761 , 0.07373514, 0.07360549, 0.073415 , 0.07319872, 0.07299079, 0.07282058, 0.07271034, 0.07267235, 0.07270862, 0.07281215, 0.07297214, 0.07317416, 0.07340123, 0.07363392, 0.07385621, 0.07405428, 0.07421823, 0.07434827, 0.07445663, 0.07456325, 0.07469148, 0.07486482, 0.07510363, 0.0754249 , 0.07583499, 0.0763238 , 0.07687 , 0.07744048, 0.07799326, 0.07848547, 0.07887646, 0.07913544, 0.07924846, 0.07921931, 0.07907331, 0.07885561, 0.07862464, 0.07844374, 0.07837516, 0.07847163, 0.0787705 , 0.07928899, 0.08002083, 0.0809382 , 0.08199479, 0.08313004, 0.08427904, 0.08537916, 0.08637918, 0.08724493, 0.08796148, 0.08853075, 0.08897183, 0.08931631, 0.0896013 , 0.08986449, 0.09013922, 0.09045455, 0.09083446, 0.0912941 , 0.09184645, 0.09249791, 0.09325393, 0.09411605, 0.0950802 , 0.09613639, 0.09727042, 0.09846512, 0.0997008 , 0.10095958, 0.10222823, 0.10349927, 0.10477299, 0.10605354, 0.10735303, 0.10868852, 0.11007881, 0.11154475, 0.11310325, 0.11476599, 0.11653497, 0.118402 , 0.12034988, 0.12235397, 0.12438468, 0.12641134, 0.12840556, 0.13034169, 0.13220338, 0.13398571, 0.13570116, 0.13738374, 0.13909178, 0.1409052 , 0.14291927, 0.14523157, 0.14793003, 0.1510891 , 0.15476738, 0.15900948, 0.16384828, 0.16930529, 0.17539348, 0.18211907, 0.1894816 , 0.19747537, 0.20609019, 0.2153121 , 0.22512414, 0.23550562, 0.24643343, 0.25788275, 0.26982699, 0.28223862, 0.29508925, 0.3083499 , 0.32199109, 0.33598346, 0.35029802, 0.36490592, 0.37977898, 0.39488962, 0.41021114, 0.42571807, 0.4413862 , 0.45719298, 0.47311751, 0.4891401 , 0.50524264, 0.52140786, 0.53761979, 0.55386412, 0.57012801, 0.58639949, 0.60266791, 0.61892388, 0.63515886, 0.65136517, 0.66753551, 0.68366317, 0.69974232, 0.71576767, 0.73173404, 0.74763662, 0.76347108, 0.77923328, 0.79491932, 0.81052539, 0.82604798, 0.8414839 , 0.85683046, 0.87208498, 0.88724521, 0.90230903, 0.91727463, 0.93214057, 0.94690566, 0.96156893, 0.97612958, 0.99058652, 1.00493896, 1.0191864 , 1.03332831, 1.04736448, 1.06129493, 1.07511968, 1.08883882, 1.10245244, 1.11596073, 1.1293639 , 1.14266233, 1.15585634, 1.16894621, 1.18193214, 1.19481443, 1.2075934 , 1.22026932, 1.23284246, 1.24531294, 1.257681 , 1.26994706, 1.2821116 , 1.29417505, 1.30613785, 1.31800031, 1.32976287, 1.34142608, 1.3529905 , 1.36445665, 1.37582494, 1.38709576, 1.39826949, 1.40934651, 1.42032734, 1.43121251, 1.44200244, 1.45269741, 1.46329784, 1.47380421, 1.4842169 , 1.4945363 , 1.50476284, 1.51489687, 1.52493875, 1.53488882, 1.5447474 , 1.55451477, 1.56419123, 1.57377699, 1.58327231, 1.59267751, 1.60199298, 1.61121897, 1.62035577, 1.6294036 , 1.63836267, 1.64723331, 1.65601585, 1.66471064, 1.67331804, 1.68183842, 1.69027212, 1.6986195 , 1.70688092, 1.71505672, 1.72314731, 1.73115306, 1.73907436, 1.74691168, 1.75466545, 1.76233612, 1.76992419, 1.77743015, 1.78485456, 1.79219798, 1.79946097, 1.80664408, 1.81374788, 1.82077303, 1.82772023, 1.83459016, 1.84138355, 1.84810114, 1.85474367, 1.8613119 , 1.86780655, 1.87422842, 1.88057834, 1.88685715, 1.89306574, 1.89920502, 1.90527587, 1.9112792 , 1.91721589, 1.92308686, 1.92889305, 1.9346354 , 1.94031485, 1.94593232, 1.95148872, 1.956985 , 1.96242207, 1.96780085, 1.97312225, 1.97838716, 1.9835965 , 1.98875115, 1.99385197, 1.99889988, 2.00389572, 2.00884037, 2.01373468, 2.01857948, 2.02337561, 2.02812387, 2.03282505, 2.03747992, 2.04208926, 2.04665378, 2.05117424, 2.05565134, 2.06008578, 2.06447826, 2.06882943, 2.07313998, 2.07741053, 2.08164173, 2.08583421, 2.08998857, 2.09410541, 2.09818531, 2.10222885, 2.10623657, 2.11020902, 2.11414674, 2.11805024, 2.12192005, 2.12575664, 2.12956052, 2.13333217, 2.13707204, 2.14078061, 2.14445833, 2.14810563, 2.15172294, 2.1553107 , 2.1588693 , 2.16239916, 2.16590066, 2.1693742 , 2.17282014, 2.17623887, 2.17963073, 2.18299609, 2.1863353 , 2.18964869, 2.19293659, 2.19619934, 2.19943724, 2.20265063, 2.2058398 , 2.20900506, 2.2121467 , 2.21526502, 2.21836029, 2.22143281, 2.22448284, 2.22751065, 2.23051652, 2.23350069, 2.23646343, 2.23940498, 2.2423256 , 2.24522553, 2.248105 , 2.25096425, 2.25380351, 2.256623 , 2.25942296, 2.26220359, 2.26496512, 2.26770775, 2.27043169, 2.27313715, 2.27582432, 2.2784934 , 2.2811446 , 2.28377809, 2.28639406, 2.28899271, 2.2915742 , 2.29413873, 2.29668646, 2.29921756, 2.30173222, 2.30423059, 2.30671284, 2.30917913, 2.31162963, 2.31406448, 2.31648385, 2.31888789, 2.32127674, 2.32365056, 2.32600949, 2.32835368, 2.33068326, 2.33299837, 2.33529916, 2.33758576, 2.33985829, 2.34211691, 2.34436172, 2.34659286, 2.34881046, 2.35101464, 2.35320552, 2.35538322, 2.35754787, 2.35969957, 2.36183844, 2.3639646 , 2.36607817, 2.36817924, 2.37026793, 2.37234435, 2.3744086 , 2.37646079, 2.37850103, 2.3805294 , 2.38254602, 2.38455099, 2.38654439, 2.38852634, 2.39049692, 2.39245622, 2.39440435, 2.39634139, 2.39826743, 2.40018257, 2.40208688, 2.40398047, 2.40586341, 2.40773578, 2.40959768, 2.41144918, 2.41329037, 2.41512133, 2.41694214, 2.41875287, 2.4205536 , 2.42234442, 2.42412539, 2.42589659, 2.4276581 , 2.42940998, 2.43115232, 2.43288518, 2.43460863, 2.43632274, 2.43802758, 2.43972322, 2.44140973, 2.44308716, 2.4447556 , 2.4464151 , 2.44806572, 2.44970754, 2.4513406 , 2.45296499, 2.45458074, 2.45618793, 2.45778662, 2.45937686, 2.46095872, 2.46253225, 2.4640975 , 2.46565454, 2.46720342, 2.46874419, 2.47027691, 2.47180164, 2.47331842, 2.47482732, 2.47632837, 2.47782164, 2.47930717, 2.48078502, 2.48225523, 2.48371786, 2.48517295, 2.48662055, 2.48806071, 2.48949348, 2.4909189 , 2.49233702, 2.49374788, 2.49515154, 2.49654803, 2.4979374 , 2.49931969, 2.50069495, 2.50206322, 2.50342454, 2.50477896, 2.50612651, 2.50746724, 2.50880118, 2.51012839, 2.51144889, 2.51276272, 2.51406994, 2.51537057, 2.51666465, 2.51795223, 2.51923333, 2.520508 , 2.52177627, 2.52303818, 2.52429376, 2.52554305, 2.52678609, 2.52802291, 2.52925355, 2.53047803, 2.5316964 , 2.53290869, 2.53411492, 2.53531514, 2.53650937, 2.53769765, 2.53888 , 2.54005647, 2.54122708, 2.54239187, 2.54355086, 2.54470408, 2.54585156, 2.54699334, 2.54812944, 2.5492599 , 2.55038473, 2.55150398, 2.55261766, 2.55372581, 2.55482845, 2.55592562, 2.55701733, 2.55810362, 2.55918451, 2.56026002, 2.5613302 , 2.56239505, 2.56345461, 2.5645089 , 2.56555795, 2.56660178, 2.56764042, 2.56867388, 2.56970221, 2.57072541, 2.57174351, 2.57275654, 2.57376452, 2.57476747, 2.57576542, 2.57675838, 2.57774639, 2.57872946, 2.57970761, 2.58068087, 2.58164925, 2.58261279, 2.58357149, 2.58452539, 2.5854745 , 2.58641884, 2.58735843, 2.58829329, 2.58922345, 2.59014892, 2.59106972, 2.59198587, 2.59289739, 2.5938043 , 2.59470662, 2.59560436, 2.59649755, 2.5973862 , 2.59827033, 2.59914996, 2.60002511, 2.60089579, 2.60176202, 2.60262382, 2.6034812 , 2.60433419, 2.60518279, 2.60602703, 2.60686692, 2.60770247, 2.60853371, 2.60936065, 2.6101833 , 2.61100168, 2.6118158 , 2.61262569, 2.61343135, 2.6142328 , 2.61503005, 2.61582313, 2.61661203, 2.61739678, 2.6181774 , 2.61895388, 2.61972626, 2.62049454, 2.62125873, 2.62201885, 2.62277492, 2.62352694, 2.62427492, 2.62501889, 2.62575884, 2.62649481, 2.62722678, 2.62795479, 2.62867884, 2.62939894, 2.6301151 , 2.63082734, 2.63153566, 2.63224009, 2.63294062, 2.63363727, 2.63433004, 2.63501896, 2.63570404, 2.63638527, 2.63706267, 2.63773625, 2.63840603, 2.639072 , 2.63973419, 2.64039259, 2.64104723, 2.6416981 , 2.64234522, 2.64298859, 2.64362823, 2.64426414, 2.64489633, 2.64552481, 2.64614958, 2.64677067, 2.64738806, 2.64800178, 2.64861182, 2.6492182 , 2.64982092, 2.65041999, 2.65101542, 2.6516072 , 2.65219536, 2.6527799 , 2.65336081, 2.65393812, 2.65451182, 2.65508192, 2.65564842, 2.65621134, 2.65677067, 2.65732642, 2.6578786 , 2.65842722, 2.65897226, 2.65951375, 2.66005168, 2.66058606, 2.6611169 , 2.66164419, 2.66216794, 2.66268815, 2.66320484, 2.66371799, 2.66422761, 2.66473371, 2.66523629, 2.66573534, 2.66623088, 2.66672291, 2.66721142, 2.66769641, 2.6681779 , 2.66865588, 2.66913034, 2.6696013 , 2.67006875, 2.67053269, 2.67099312, 2.67145005, 2.67190346, 2.67235337, 2.67279976, 2.67324264, 2.67368201, 2.67411786, 2.6745502 , 2.67497901, 2.6754043 , 2.67582607, 2.67624431, 2.67665901, 2.67707018, 2.6774778 , 2.67788188, 2.67828241, 2.67867939, 2.6790728 , 2.67946264, 2.67984891, 2.6802316 , 2.6806107 , 2.68098621, 2.68135811, 2.6817264 , 2.68209106, 2.6824521 , 2.68280949, 2.68316323, 2.6835133 , 2.6838597 , 2.68420241, 2.68454142, 2.68487671, 2.68520828, 2.6855361 , 2.68586015, 2.68618044, 2.68649692, 2.68680959, 2.68711843, 2.68742342, 2.68772453, 2.68802174, 2.68831504, 2.68860439, 2.68888977, 2.68917115, 2.6894485 , 2.6897218 , 2.689991 , 2.69025609, 2.69051701, 2.69077374, 2.69102624, 2.69127446, 2.69151836, 2.69175789, 2.69199301, 2.69222365, 2.69244977, 2.6926713 , 2.69288819, 2.69310035, 2.69330773, 2.69351023, 2.69370778, 2.69390028, 2.69408764, 2.69426974, 2.69444647, 2.6946177 , 2.69478329, 2.69494307, 2.69509687, 2.69524449, 2.69538571, 2.69552028, 2.69564788, 2.69576818, 2.69588076, 2.6959851 , 2.69608056, 2.6961663 , 2.69624114, 2.6963033 , 2.69634954, 2.69637016, 2.69633447, 2.69633447]) if ndimz!=len(vxFluidPY): print '\n Bug: ndimz should necessarily be equal to the length of the imposed fluid profile vxFluidPY !\n' exit() trunk-2018.02b/examples/HydroForceEngine/twoWayCoupling/000077500000000000000000000000001324306050200231715ustar00rootroot00000000000000trunk-2018.02b/examples/HydroForceEngine/twoWayCoupling/postProcessing_sedim.py000066400000000000000000000121271324306050200277510ustar00rootroot00000000000000######################################################################################################################################################################### # Author: Raphael Maurin, raphael.maurin@imft.fr # 24/11/2017 # # Post processing script, to extract and plot the results of sedimentTransportExample scripts, in terms of dimensionless sediment transport, # and time-averaged depth profiles of streamwise fluid and solid velocity, solid volume fraction and sediment transport rate density. # For example of applications, see the articles Maurin et al (2015,2016), and the PhD of Maurin (2015), available in the references of YADE. # ############################################################################################################################################################################ import numpy as np import os from matplotlib.pyplot import * import matplotlib.gridspec as gridspec ####### # INPUT timeStepSave = 0.1 # Data saved every timeStepSave seconds in the simulation startFile = 0 # File where the post-processing begins endFile = 400 # File where the post-processing ends scriptPath = os.path.abspath(os.path.dirname(sys.argv[-1])) #Path where the script is stored if os.path.exists(scriptPath +'/data/')==False: #If the data folder does not exist, no data to extract, exit. print '\n There is no data to extract in this folder ! \n' exit() else: #Else, extract the first file in order to get the size of the vectors, ndimz execfile(scriptPath +'/data/0.py') ndimz = len(phiPartPY) #Size of the vectors, mesh parameter. #Initilization of the variable to extract and plot [qs,time,phiPart,vxPart,vxFluid] = [[],[],np.zeros(ndimz),np.zeros(ndimz),np.zeros(ndimz)] ######## # LOOP over time to EXTRACT the data ######## for i in range(startFile,endFile): nameFile = scriptPath +'/data/' + str(i)+'.py' #Name of the file at the considered time step if os.path.exists(nameFile)==False: #Check if the file exist print('\nThe file {0} does not exist, stop the post processing at this stage !\n'.format(nameFile)) endFile = i-1 #Last file processed break #Extract the data from at the time (file) considered. Assign the vectors to qsMean, phiPartPY, vxPartPY, vxFluidPY, and zAxis see the structure of a saved file X.py. execfile(nameFile) time+=[i*timeStepSave] #Recreate the time scale qs+=[qsMean] #Extract the sediment transport rate as a function of time phiPart+=phiPartPY #Average over time the solid volume fraction vertical profile vxPart+=vxPartPY*phiPartPY #Average over time the solid velocity profile. The spatial averaging is weighted by the particle volume so that it is necessary to re-multiply the averaging by the solid volume fraction before re-normalizing. See the paper Maurin et al (2015) in the references for more details on the averaging. vxFluid+=vxFluidPY #Average over time the fluid velocity vertical profile #Normalization toto = np.where(phiPart>0) vxPart[toto]/=phiPart[toto] #Avoid division by zero phiPart/=(endFile-startFile+1) vxFluid/=(endFile-startFile+1) ###### # PLOT # sediment transport rate as a function of time fig1 = figure() #Create a figure gs = gridspec.GridSpec(1,1) #Split the figure in 1 row and 1 column ax=subplot(gs[0,0]) #Assign the axis ax.plot(time,qs,'ok') #Plot the sediment transport rate as a function of time ax.grid() #Put a grid on the figure ax.set_xlabel('t (s)') #Assign the x axis label ax.set_ylabel(r'$Q_s^*$',rotation='horizontal')#Assign the y axis label savefig('transportRate.png') #Save the figure # Solid and fluid velocity depth profiles, solid volume fraction and sediment transport rate density depth profiles fig2 = figure() gs = gridspec.GridSpec(1,3) #Split the figure in 1 row and 3 column ax1=subplot(gs[0,0]) #ax1 = subfigure corresponding to 1st row 1st column ax2=subplot(gs[0,1]) #ax2 = subfigure corresponding to 1st row 2nd column ax3=subplot(gs[0,2]) #ax3 = subfigure corresponding to 1st row 3rd column ax1.plot(vxPart,zAxis,'-r') #Plot the solid velocity profile on first subfigure ax1.plot(vxFluid,zAxis,'-b') #Plot the fluid velocity profile on first subfigure ax2.plot(phiPart,zAxis,'-r') #Plot the solid volume fraction on second subfigure ax3.plot(phiPart*vxPart,zAxis,'-r') #Plot the sediment transport rate density on the third subfigure ax1.set_xlabel(r'$$ (m/s)') #Assign the x axis label of the first subfigure ax1.set_ylabel(r'$z/d$',rotation='horizontal') #Assign the y axis label of the first subfigure ax2.set_xlabel(r'$\phi$') #Assign the x axis label of the second subfigure ax3.set_xlabel(r'$q (m/s)$') #Assign the x axis label of the third subfigure ax2.set_yticklabels([]) #Remove the vertical scale for the second subfigure, same as first ax3.set_yticklabels([]) #Remove the vertical scale for the third subfigure, same as first ax1.grid() #Put a grid on each subfigure ax2.grid() ax3.grid() fig2.subplots_adjust(left=0.08, bottom=0.11, right=0.98, top=0.98, wspace=.1) #Adjust the spacing around the full figure (left, bottom, right, top) and between the subfigure (wspace), in percent of the figure size savefig('depthProfiles.png') #Save the figure show() #Show the two figures created trunk-2018.02b/examples/HydroForceEngine/twoWayCoupling/sedimentTransportExample_1DRANSCoupling.py000066400000000000000000000327161324306050200333260ustar00rootroot00000000000000######################################################################################################################################################################### # Author: Raphael Maurin, raphael.maurin@imft.fr # 24/11/2017 # # Same as sedimentTransportExample but solving a 1D volume averaged fluid momentum balance to determine the fluid velocity profile (i.e. DEM-1D RANS coupling) # The resolution therefore include a two-way coupling in time between the fluid and the particle behavior, meaning that the fluid is solved every "fluidResolPeriod" # and account for the presence of particles and for the momentum transfered to the particle phase through the hydrodynamic forces imposed. The fluid-particle system momentum # is therefore conserved. # # Data can be saved and plot with the file postProcessing.py # ############################################################################################################################################################################ #Import libraries from yade import pack, plot import math import random as rand import numpy as np ## ## Main parameters of the simulation ## #Particles diameterPart = 6e-3 #Diameter of the particles, in m densPart = 2500 #density of the particles, in kg/m3 phiPartMax = 0.61 #Value of the dense packing solid volume fraction, dimensionless restitCoef = 0.7 #Restitution coefficient of the particles, dimensionless partFrictAngle = atan(0.5) #friction angle of the particles, in radian #Fluid densFluidPY = 1000. #Density of the fluid, in kg/m^3 kinematicViscoFluid = 1e-6 #kinematic viscosity of the fluid, in m^2/s waterDepth = 17.5 #Water depth, in diameter dtFluid = 1e-5 #Time step for the fluid resolution, in s fluidResolPeriod = 1e-2 #Time between two fluid resolution, in s #Configuration: inclined channel slope = 0.05 #Inclination angle of the channel slope in radian lengthCell = 10 #Streamwise length of the periodic cell, in diameter widthCell = 10 #Spanwise length of the periodic cell, in diameter Nlayer = 10. #nb of layer of particle, in diameter fluidHeight = (Nlayer+waterDepth)*diameterPart #Height of the flow from the bottom of the sample, in m saveData = 1 #If put to 1, at each execution of function measure() save the sediment transport rate, fluid velocity, solid volume fraction and velocity profiles for post-processing endTime = 100 #Time simulated (in seconds) ## ## Secondary parameters of the simulation ## expoDrag_PY = 3.1 # Richardson Zaki exponent for the hindrance function of the drag force applied to the particles #Discretization of the sample in ndimz wall-normal (z) steps of size dz, between the bottom of the channel and the position of the water free-surface. Should be equal to the length of the imposed fluid profile. Mesh used for HydroForceEngine. ndimz = 900 #Number of cells in the height dz = fluidHeight/(1.0*(ndimz-1)) # Fluid discretization step in the wall-normal direction # Initialization of the main vectors vxFluidPY = np.zeros(ndimz) # Vertical fluid velocity profile: u^f = u_x^f(z) e_x, with x the streamwise direction and z the wall-normal phiPartPY = np.zeros(ndimz) # Vertical particle volume fraction profile vxPartPY = np.zeros(ndimz) # Vertical average particle velocity profile #Geometrical configuration, define useful quantities height = 5*fluidHeight #heigth of the periodic cell, in m (bigger than the fluid height to take into particles jumping above the latter) length = lengthCell*diameterPart #length of the stream, in m width = widthCell*diameterPart #width of the stream, in m groundPosition = height/4.0 #Definition of the position of the ground, in m gravityVector = Vector3(9.81*sin(slope),0.0,-9.81*cos(slope)) #Gravity vector to consider a channel inclined with slope angle 'slope' #Particles contact law/material parameters maxPressure = (densPart-densFluidPY)*phiPartMax*Nlayer*diameterPart*abs(gravityVector[2]) #Estimated max particle pressure from the static load normalStiffness = maxPressure*diameterPart*1e4 #Evaluate the minimal normal stiffness to be in the rigid particle limit (cf Roux and Combe 2002) youngMod = normalStiffness/diameterPart #Young modulus of the particles from the stiffness wanted. poissonRatio = 0.5 #poisson's ratio of the particles. Classical values, does not have much influence O.materials.append(ViscElMat(en=restitCoef, et=0., young=youngMod, poisson=poissonRatio, density=densPart, frictionAngle=partFrictAngle, label='Mat')) ######################## ## FRAMEWORK CREATION ## ######################## #Definition of the semi-periodic cell O.periodic = True O.cell.setBox(length,width,height) # Reference walls: build two planes at the ground and free-surface to have a reference for the eyes in the 3D view lowPlane = box(center= (length/2.0, width/2.0,groundPosition),extents=(200,200,0),fixed=True,wire=False,color = (0.,1.,0.),material = 'Mat') WaterSurface = box(center= (length/2.0, width/2.0,groundPosition+fluidHeight),extents=(2000,width/2.0,0),fixed=True,wire=False,color = (0,0,1),material = 'Mat',mask = 0) O.bodies.append([lowPlane,WaterSurface]) #add to simulation # Regular arrangement of spheres sticked at the bottom with random height L = range(0,int(length/(diameterPart))) #The length is divided in particle diameter W = range(0,int(width/(diameterPart))) #The width is divided in particle diameter for x in L: #loop creating a set of sphere sticked at the bottom with a (uniform) random altitude comprised between 0.5 (diameter/12) and 5.5mm (11diameter/12) with steps of 0.5mm. The repartition along z is made around groundPosition. for y in W: n = rand.randrange(0,12,1)/12.0*diameterPart #Define a number between 0 and 11/12 diameter with steps of 1/12 diameter (0.5mm in the experiment) O.bodies.append(sphere((x*diameterPart, y*diameterPart,groundPosition - 11*diameterPart/12.0/2.0 + n),diameterPart/2.,color=(0,0,0),fixed = True,material = 'Mat')) #Create a loose cloud of particle inside the cell partCloud = pack.SpherePack() partVolume = pi/6.*pow(diameterPart,3) #Volume of a particle partNumber = int(Nlayer*phiPartMax*diameterPart*length*width/partVolume) #Volume of beads to obtain Nlayer layers of particles partCloud.makeCloud(minCorner=(0,0.,groundPosition+diameterPart),maxCorner=(length,width,groundPosition+fluidHeight),rRelFuzz=0., rMean=diameterPart/2.0, num = partNumber) partCloud.toSimulation(material='Mat') #Send this packing to simulation with material Mat #Evaluate the deposition time considering the free-fall time of the highest particle to the ground depoTime = sqrt(fluidHeight*2/abs(gravityVector[2])) # Collect the ids of the spheres which are dynamic to add a fluid force through HydroForceEngines idApplyForce = [] for b in O.bodies: if isinstance(b.shape,Sphere) and b.dynamic: idApplyForce+=[b.id] ######################### #### SIMULATION LOOP##### ######################### O.engines = [ # Reset the forces ForceResetter(), # Detect the potential contacts InsertionSortCollider([Bo1_Sphere_Aabb(), Bo1_Wall_Aabb(),Bo1_Facet_Aabb(),Bo1_Box_Aabb()],label='contactDetection',allowBiggerThanPeriod = True), # Calculate the different interactions InteractionLoop( [Ig2_Sphere_Sphere_ScGeom(), Ig2_Box_Sphere_ScGeom()], [Ip2_ViscElMat_ViscElMat_ViscElPhys()], [Law2_ScGeom_ViscElPhys_Basic()] ,label = 'interactionLoop'), #Apply an hydrodynamic force to the particles HydroForceEngine(densFluid = densFluidPY,viscoDyn = kinematicViscoFluid*densFluidPY,zRef = groundPosition,gravity = gravityVector,deltaZ = dz,expoRZ = expoDrag_PY,lift = False,nCell = ndimz,vCell = length*width*dz,radiusPart=diameterPart/2.,vxFluid = np.array(vxFluidPY),phiPart = phiPartPY,vxPart = vxPartPY,ids = idApplyForce, label = 'hydroEngine', dead = True), #Solve the fluid volume-averaged 1D momentum balance, RANS 1D PyRunner(command = 'fluidModel()', virtPeriod = fluidResolPeriod, label = 'fluidRes', dead = True), #Measurement, output files PyRunner(command = 'measure()', virtPeriod = 0.1, label = 'measurement', dead = True), # Check if the packing is stabilized, if yes activate the hydro force on the grains and the slope. PyRunner(command='gravityDeposition(depoTime)',virtPeriod = 0.01,label = 'gravDepo'), #GlobalStiffnessTimeStepper, determine the time step GlobalStiffnessTimeStepper(defaultDt = 1e-4, viscEl = True,timestepSafetyCoefficient = 0.7, label = 'GSTS'), # Integrate the equation and calculate the new position/velocities... NewtonIntegrator(damping=0.2, gravity=gravityVector, label='newtonIntegr') ] #save the initial configuration to be able to recharge the simulation starting configuration easily O.saveTmp() #run O.run() #################################################################################################################################### #################################################### FUNCTION DEFINITION ######################################################### #################################################################################################################################### ###### ###### ### LET THE TIME FOR THE GRAVITY DEPOSITION AND ACTIVATE THE FLUID AT THE END ### ###### ###### def gravityDeposition(lim): if O.time=endTime: print('\n End of the simulation, simulated {0}s as required !\n '.format(endTime)) O.pause() #Evaluate the Shields number from the maximum of the Reynolds stresses evaluated in the fluid resolution shieldsNumber = max(hydroEngine.ReynoldStresses)/((densPart-densFluidPY)*diameterPart*abs(gravityVector[2])) print 'Shields number', shieldsNumber if saveData==1: #Save data for postprocessing global fileNumber nameFile = scriptPath + '/data/'+ str(fileNumber)+'.py' # Name of the file that will be saved globalParam = ['qsMean','phiPartPY','vxPartPY','vxFluidPY','zAxis'] # Variables to save Save(nameFile, globalParam) #Save fileNumber+=1 #Increment the file number #Plot the dimensionless sediment transport rate as a function of time during the simulation plot.plots={'time':('SedimentRate')} plot.plot() ################ ########################################## #Save data details fileNumber = 0 # Counter for the file saved if saveData==1: #If saveData option is activated, requires a folder data scriptPath = os.path.abspath(os.path.dirname(sys.argv[-1])) #Path where the script is stored if os.path.exists(scriptPath +'/data/')==False: os.mkdir(scriptPath +'/data/') else: print '\n!! Save data: overwrite the files contains in the folder data/ !!\n' #Function to save global variables in a python file which can be re-executed for post-processing def Save(filePathName, globalVariables): f = open(filePathName,'w') f.write('from numpy import *\n') for i in globalVariables: f.write(i + ' = '+repr(globals()[i]) + '\n') f.close() trunk-2018.02b/examples/LudingPM/000077500000000000000000000000001324306050200164635ustar00rootroot00000000000000trunk-2018.02b/examples/LudingPM/LudingPM_1.py000066400000000000000000000063051324306050200207400ustar00rootroot00000000000000#!/usr/bin/env python # encoding: utf-8 from yade import utils, plot o = Omega() fr = 0.5;rho=2000 o.dt = 0.00002 r1 = 2.0e-1 r2 = 2.0e-1 #======================================= k1 = 105.0 kp = 5.0*k1 kc = k1 DeltaPMax = 0.031 Chi1 = 0.34 Chi2 = 0.69 Chi3 = 1.1 Chi4 = 1.39 #======================================= particleMass = 4.0/3.0*math.pi*r1*r1*r1*rho Vi1 = math.sqrt(k1/particleMass)*DeltaPMax*Chi1 Vi2 = math.sqrt(k1/particleMass)*DeltaPMax*Chi2 Vi3 = math.sqrt(k1/particleMass)*DeltaPMax*Chi3 Vi4 = math.sqrt(k1/particleMass)*DeltaPMax*Chi4 PhiF1 = DeltaPMax*(kp-k1)*(r1+r2)/(kp*2*r1*r2) PhiF2 = DeltaPMax*(kp-k1)*(r1+r2)/(kp*2*r1*r2) PhiF3 = DeltaPMax*(kp-k1)*(r1+r2)/(kp*2*r1*r2) PhiF4 = DeltaPMax*(kp-k1)*(r1+r2)/(kp*2*r1*r2) #======================================= mat1 = O.materials.append(LudingMat(frictionAngle=fr,density=rho,k1=k1, kp=kp, kc=kc, PhiF=PhiF1, G0 = 0.0)) mat2 = O.materials.append(LudingMat(frictionAngle=fr,density=rho,k1=k1, kp=kp, kc=kc, PhiF=PhiF2, G0 = 0.0)) mat3 = O.materials.append(LudingMat(frictionAngle=fr,density=rho,k1=k1, kp=kp, kc=kc, PhiF=PhiF3, G0 = 0.0)) mat4 = O.materials.append(LudingMat(frictionAngle=fr,density=rho,k1=k1, kp=kp, kc=kc, PhiF=PhiF4, G0 = 0.0)) id11 = O.bodies.append(sphere(center=[0,0,0],radius=r1,material=mat1,fixed=True)) id12 = O.bodies.append(sphere(center=[0,0,(r1 + r2)],radius=r2,material=mat1,fixed=False)) id21 = O.bodies.append(sphere(center=[0,3.0*r1,0],radius=r1,material=mat2,fixed=True)) id22 = O.bodies.append(sphere(center=[0,3.0*r1,(r1 + r2)],radius=r2,material=mat2,fixed=False)) id31 = O.bodies.append(sphere(center=[0,6.0*r1,0],radius=r1,material=mat3,fixed=True)) id32 = O.bodies.append(sphere(center=[0,6.0*r1,(r1 + r2)],radius=r2,material=mat3,fixed=False)) id41 = O.bodies.append(sphere(center=[0,9.0*r1,0],radius=r1,material=mat4,fixed=True)) id42 = O.bodies.append(sphere(center=[0,9.0*r1,(r1 + r2)],radius=r2,material=mat4,fixed=False)) o.engines = [ ForceResetter(), InsertionSortCollider([Bo1_Sphere_Aabb()]), InteractionLoop( [Ig2_Sphere_Sphere_ScGeom()], [Ip2_LudingMat_LudingMat_LudingPhys()], [Law2_ScGeom_LudingPhys_Basic()], ), NewtonIntegrator(damping=0,gravity=[0,0,0]), PyRunner(command='addPlotData()',iterPeriod=1000), ] O.bodies[id12].state.vel=[0,0,-Vi1] O.bodies[id22].state.vel=[0,0,-Vi2] O.bodies[id32].state.vel=[0,0,-Vi3] O.bodies[id42].state.vel=[0,0,-Vi4] def addPlotData(): f1 = [0,0,0] f2 = [0,0,0] f3 = [0,0,0] f4 = [0,0,0] try: f1=O.forces.f(id12) f2=O.forces.f(id22) f3=O.forces.f(id32) f4=O.forces.f(id42) except: f1 = [0,0,0] f2 = [0,0,0] f3 = [0,0,0] f4 = [0,0,0] s1 = (O.bodies[id12].state.pos[2]-O.bodies[id11].state.pos[2])-(r1 + r2) s2 = (O.bodies[id22].state.pos[2]-O.bodies[id21].state.pos[2])-(r1 + r2) s3 = (O.bodies[id32].state.pos[2]-O.bodies[id31].state.pos[2])-(r1 + r2) s4 = (O.bodies[id42].state.pos[2]-O.bodies[id41].state.pos[2])-(r1 + r2) plot.addData(fc1=f1[2], sc1=-s1, fc2=f2[2], sc2=-s2, fc3=f3[2], sc3=-s3, fc4=f4[2], sc4=-s4) plot.plots={'sc1':('fc1'), 'sc2':('fc2'), 'sc3':('fc3'), 'sc4':('fc4')}; plot.plot() from yade import qt qt.View() O.run(320000) O.wait() plot.saveGnuplot('sim-data_LudigPM') trunk-2018.02b/examples/LudingPM/LudingPM_2.py000066400000000000000000000031351324306050200207370ustar00rootroot00000000000000#!/usr/bin/env python # encoding: utf-8 from yade import utils, plot o = Omega() fr = 0.5;rho=2000 o.dt = 0.00002 r1 = 2.0e-1 r2 = 2.0e-1 #======================================= k1 = 105.0 kp = 5.0*k1 kc = k1 DeltaPMax = 0.031 Chi1 = 0.34 #======================================= particleMass = 4.0/3.0*math.pi*r1*r1*r1*rho Vi1 = math.sqrt(k1/particleMass)*DeltaPMax*Chi1 PhiF1 = DeltaPMax*(kp-k1)*(r1+r2)/(kp*2*r1*r2) #======================================= mat1 = O.materials.append(LudingMat(frictionAngle=fr,density=rho,k1=k1, kp=kp, kc=kc, PhiF=PhiF1, G0 = 0.0)) id11 = O.bodies.append(sphere(center=[0,0,0],radius=r1,material=mat1,fixed=True)) id12 = O.bodies.append(sphere(center=[0,0,(r1 + r2)],radius=r2,material=mat1,fixed=False)) o.engines = [ ForceResetter(), InsertionSortCollider([Bo1_Sphere_Aabb()]), InteractionLoop( [Ig2_Sphere_Sphere_ScGeom()], [Ip2_LudingMat_LudingMat_LudingPhys()], [Law2_ScGeom_LudingPhys_Basic()], ), NewtonIntegrator(damping=0,gravity=[0,0,0]), PyRunner(command='changeVelDir()',iterPeriod=6000,label='Cvel'), PyRunner(command='addPlotData()',iterPeriod=300), ] O.bodies[id12].state.vel=[0,0,-Vi1] def changeVelDir(): O.bodies[id12].state.vel*=-1 Cvel.iterPeriod = int(Cvel.iterPeriod*1.5) def addPlotData(): f1 = [0,0,0] try: f1=O.forces.f(id12) except: f1 = [0,0,0] s1 = (O.bodies[id12].state.pos[2]-O.bodies[id11].state.pos[2])-(r1 + r2) plot.addData(fc1=f1[2], sc1=-s1) plot.plots={'sc1':('fc1')}; plot.plot() from yade import qt qt.View() O.run(150000) O.wait() plot.saveGnuplot('sim-data_LudigPM') trunk-2018.02b/examples/LudingPM/README000066400000000000000000000001231324306050200173370ustar00rootroot00000000000000Simple scripts to test LudingPM. See [Luding2008]_ ,[Singh2013]_ for more details. trunk-2018.02b/examples/PIDController.py000066400000000000000000000040051324306050200200350ustar00rootroot00000000000000#!/usr/bin/env python # encoding: utf-8 from yade import utils, plot o = Omega() fr = 0.0;rho=2000 tc = 0.001; en = 0.3; et = 0.3; o.dt = 0.02*tc mat1 = O.materials.append(ViscElMat(frictionAngle=fr, density=rho,tc=tc,en=en,et=et)) spheresID = O.bodies.append(pack.regularHexa(pack.inCylinder((0,0,-2.0),(0,0,2.0),2.0),radius=0.2,gap=0.1,color=(0,1,0),material=mat1)) idWalls = O.bodies.append(geom.facetCylinder(center=(0.0,0.0,0.0),radius = 2.05, height = 4.0, wallMask=6, material=mat1, segmentsNumber = 20, color=(0,0,1))) idTop = O.bodies.append(geom.facetCylinder(center=(0.0,0.0,0.0),radius = 2.05, height = 4.0, wallMask=1, material=mat1, segmentsNumber = 5, color=(1,0,0), wire=False)) o.engines = [ ForceResetter(), InsertionSortCollider([Bo1_Sphere_Aabb(),Bo1_Facet_Aabb()],verletDist=1.0,label='collider'), InteractionLoop( [Ig2_Sphere_Sphere_ScGeom(),Ig2_Facet_Sphere_ScGeom()], [Ip2_ViscElMat_ViscElMat_ViscElPhys()], [Law2_ScGeom_ViscElPhys_Basic()], ), NewtonIntegrator(damping=0,gravity=[0,0,-9.81],label='newtonInt'), TranslationEngine(translationAxis=[0,0,1],velocity=-2.0,ids=idTop,dead=False,label='translat'), CombinedKinematicEngine(ids=idTop,label='combEngine',dead=True) + ServoPIDController(axis=[0,0,1],maxVelocity=2.0,iterPeriod=1000,ids=idTop,target=1.0e7,kP=1.0,kI=1.0,kD=1.0) + RotationEngine(rotationAxis=(0,0,1), angularVelocity=10.0, rotateAroundZero=True, zeroPoint=(0,0,0)), PyRunner(command='addPlotData()',iterPeriod=1000, label='graph'), PyRunner(command='switchTranslationEngine()',iterPeriod=45000, nDo = 2, label='switchEng'), ] O.step() from yade import qt qt.View() r=qt.Renderer() r.bgColor=1,1,1 def addPlotData(): fMove = Vector3(0,0,0) for i in idTop: fMove += O.forces.f(i) plot.addData(z=O.iter, pMove=fMove[2], pFest=fMove[2]) def switchTranslationEngine(): print "Switch from TranslationEngine engine to ServoPIDController" translat.dead = True combEngine.dead = False plot.plots={'z':('pMove','pFest')}; plot.plot() trunk-2018.02b/examples/PeriodicBoundaries/000077500000000000000000000000001324306050200205565ustar00rootroot00000000000000trunk-2018.02b/examples/PeriodicBoundaries/cellFlipping.py000066400000000000000000000031151324306050200235400ustar00rootroot00000000000000# coding: utf-8 # 2017 Bruno Chareyre "Demonstrate cell flipping in periodic boundary conditions" from yade import pack,qt,plot O.periodic=True O.cell.Hsize=Matrix3(0.1,0,0, 0,0.1,0, 0,0,0.1) sp=pack.SpherePack() radius=5e-3 num=sp.makeCloud((0,0,0),(.1,.1,.1),radius,.2,100,periodic=True) O.bodies.append([sphere(s[0],s[1]) for s in sp]) O.engines=[ ForceResetter(), InsertionSortCollider([Bo1_Sphere_Aabb()],verletDist=.05*radius), InteractionLoop( [Ig2_Sphere_Sphere_ScGeom()], [Ip2_FrictMat_FrictMat_FrictPhys()], [Law2_ScGeom_FrictPhys_CundallStrack()] ), PeriTriaxController(dynCell=True,mass=0.2,maxUnbalanced=0.01,relStressTol=0.02,goal=[-1e4,-1e4,-1e4],stressMask=7,globUpdate=5,maxStrainRate=[1.,1.,1.],doneHook='triaxDone()',label='triax'), NewtonIntegrator(damping=.2), ] O.dt=2.e-5 phase=0 def triaxDone(): global phase if phase==0: print 'Here we are: stress',triax.stress,'strain',triax.strain,'stiffness',triax.stiff print 'Now shearing.' O.cell.velGrad=Matrix3(0,1,0, 0,0,0, 0,0,0) triax.stressMask=7 triax.goal=[-1e4,-1e4,-1e4] phase+=1 O.saveTmp() O.pause() O.run(-1,True) def addPlotData(): plot.addData(t=O.time,gamma=O.cell.trsf[0,1], unb = unbalancedForce(),s=getStress()[0,1]) O.engines=O.engines+[PyRunner(command='addPlotData()',iterPeriod=20)] plot.plots={'gamma':'unb'} plot.plot() O.engines=O.engines+[PyRunner(command='if O.cell.hSize[0,1]>O.cell.hSize[0,0]: flipCell()',iterPeriod=20)] #now click play and watch the flips happening, the evolution of stress or unbalanced force should not show any discontinuitytrunk-2018.02b/examples/PeriodicBoundaries/peri3dController_example1.py000066400000000000000000000053001324306050200261540ustar00rootroot00000000000000# peri3dController_example1.py # script, that explains funcionality and input parameters of Peri3dController from yade import pack, plot # create some material O.materials.append(CpmMat(neverDamage=True,young=25e9,frictionAngle=.7,poisson=.2,sigmaT=3e6,epsCrackOnset=1e-4,relDuctility=30)) # create periodic assembly of particles initSize=1.2 sp=pack.randomPeriPack(radius=.05,initSize=Vector3(initSize,initSize,initSize),memoizeDb='/tmp/packDb.sqlite') sp.toSimulation() # plotting #plot.live=False plot.plots={'progress':('sx','sy','sz','syz','szx','sxy',),'progress_':('ex','ey','ez','eyz','ezx','exy',)} def plotAddData(): plot.addData( progress=p3d.progress,progress_=p3d.progress, sx=p3d.stress[0],sy=p3d.stress[1],sz=p3d.stress[2], syz=p3d.stress[3],szx=p3d.stress[4],sxy=p3d.stress[5], ex=p3d.strain[0],ey=p3d.strain[1],ez=p3d.strain[2], eyz=p3d.strain[3],ezx=p3d.strain[4],exy=p3d.strain[5], ) # in how many time steps should be the goal state reached nSteps=4000 O.dt=PWaveTimeStep()/2 EnlargeFactor=1.5 EnlargeFactor=1.0 O.engines=[ ForceResetter(), InsertionSortCollider([Bo1_Sphere_Aabb(aabbEnlargeFactor=EnlargeFactor,label='bo1s')]), InteractionLoop( [Ig2_Sphere_Sphere_ScGeom(interactionDetectionFactor=EnlargeFactor,label='ig2ss')], [Ip2_CpmMat_CpmMat_CpmPhys()],[Law2_ScGeom_CpmPhys_Cpm()]), NewtonIntegrator(), Peri3dController( goal=(20e-4,-6e-4,0, -2e6,3e-4,2e6), # Vector6 of prescribed final values (xx,yy,zz, yz,zx,xy) stressMask=0b101100, # prescribed ex,ey,sz,syz,ezx,sxy; e..strain; s..stress nSteps=nSteps, # how many time steps the simulation will last # after reaching nSteps do doneHook action doneHook='print "Simulation with Peri3dController finished."; O.pause()', # the prescribed path (step,value of stress/strain) can be defined in absolute values xxPath=[(465,5e-4),(934,-5e-4),(1134,10e-4)], # or in relative values yyPath=[(2,4),(7,-2),(11,0),(14,4)], # if the goal value is 0, the absolute stress/strain values are always considered (step values remain relative) zzPath=[(5,-1e7),(10,0)], # if ##Path is not explicitly defined, it is considered as linear function between (0,0) and (nSteps,goal) # as in yzPath and xyPath # the relative values are really relative (zxPath gives the same - except of the sign from goal value - result as yyPath) zxPath=[(4,2),(14,-1),(22,0),(28,2)], xyPath=[(1,1),(2,-1),(3,1),(4,-1),(5,1)], # variables used in the first step label='p3d' ), PyRunner(command='plotAddData()',iterPeriod=1), ] O.step() bo1s.aabbEnlargeFactor=ig2ss.interactionDetectionFactor=1. O.run(); #O.wait() plot.plot(subPlots=False) trunk-2018.02b/examples/PeriodicBoundaries/peri3dController_shear.py000066400000000000000000000041561324306050200255520ustar00rootroot00000000000000# peri3dController_shear.py # script, that explains funcionality and input parameters of Peri3dController on the example of # shear test with rotated periodic cell (that enables strain localization). # The simulation is run on rotated cell to enable localization and strain softening # (you can also try simulation with different angles of rotation to pbtain different results. from yade import pack,plot,qt # define material O.materials.append(CpmMat(young=25e9,poisson=.2,sigmaT=3e6,epsCrackOnset=1e-4,relDuctility=1e-6)) # create periodic assembly of particles initSize=1.2 sp=pack.randomPeriPack(radius=.05,initSize=Vector3(initSize,initSize,initSize),memoizeDb='/tmp/packDb.sqlite') angle=0. #angle=pi/4 rot=Matrix3(cos(angle),-sin(angle),0, sin(angle),cos(angle),0, 0,0,1) sp.toSimulation(rot=rot) # plotting plot.live=False plot.plots={'iter':('sx','sy','sz','syz','szx','sxy',),'iter_':('ex','ey','ez','eyz','ezx','exy',),'exy':('sxy',)} def plotAddData(): plot.addData( iter=O.iter,iter_=O.iter, sx=p3d.stress[0],sy=p3d.stress[1],sz=p3d.stress[2], syz=p3d.stress[3],szx=p3d.stress[4],sxy=p3d.stress[5], ex=p3d.strain[0],ey=p3d.strain[1],ez=p3d.strain[2], eyz=p3d.strain[3],ezx=p3d.strain[4],exy=p3d.strain[5], ) O.dt=PWaveTimeStep()/2 # define the first part of simulation, hydrostatic compression enlargeFactor=1.5 O.engines=[ ForceResetter(), InsertionSortCollider([Bo1_Sphere_Aabb(aabbEnlargeFactor=enlargeFactor,label='bo1s')]), InteractionLoop( [Ig2_Sphere_Sphere_ScGeom(interactionDetectionFactor=enlargeFactor,label='ig2ss')], [Ip2_CpmMat_CpmMat_CpmPhys()],[Law2_ScGeom_CpmPhys_Cpm()]), NewtonIntegrator(), Peri3dController( goal=(0,0,0, 0,0,5e-3), # Vector6 of prescribed final values stressMask=0b011111, nSteps=2000, doneHook='print "Simulation with Peri3dController finished."; O.pause()', maxStrain=.5, label='p3d' ), PyRunner(command='plotAddData()',iterPeriod=1), ] O.step() bo1s.aabbEnlargeFactor=ig2ss.interactionDetectionFactor=1.0 renderer=qt.Renderer() renderer.intrPhys,renderer.shape=True,False Gl1_CpmPhys.dmgLabel=False qt.View() O.run() #plot.plot() trunk-2018.02b/examples/PeriodicBoundaries/peri3dController_triaxialCompression.py000066400000000000000000000057321324306050200305100ustar00rootroot00000000000000# peri3dController_triaxialCompression.py # script, that explains funcionality and input parameters of Peri3dController on the example of # triaxial compression. # Firstly, a hydrostatic preassure is applied, then a strain along z axis is increasing # while x- and y- stress is constant # The simulation is run on rotated cell to enable localization and strain softening # (you can also try simulation with command sp.toSimulation() with no rotation, # in this case there is almost no difference, but in script peri3dController_shear, # the cell rotation has significant effect) from yade import pack,plot,qt # define material O.materials.append(FrictMat()) # create periodic assembly of particles initSize=1.2 sp=pack.randomPeriPack(radius=.05,initSize=Vector3(initSize,initSize,initSize),memoizeDb='/tmp/packDb.sqlite') angle=0 rot=Matrix3(cos(angle),0,-sin(angle), 0,1,0, sin(angle),0,cos(angle)) sp.toSimulation(rot=rot) # plotting plot.live=False plot.plots={'iter':('sx','sy','sz','syz','szx','sxy',),'iter_':('ex','ey','ez','eyz','ezx','exy',),'ez':('sz',)} def plotAddData(): plot.addData( iter=O.iter,iter_=O.iter, sx=p3d.stress[0],sy=p3d.stress[1],sz=p3d.stress[2], syz=p3d.stress[3],szx=p3d.stress[4],sxy=p3d.stress[5], ex=p3d.strain[0],ey=p3d.strain[1],ez=p3d.strain[2], eyz=p3d.strain[3],ezx=p3d.strain[4],exy=p3d.strain[5], ) O.dt=PWaveTimeStep()/2 # define the first part of simulation, hydrostatic compression O.engines=[ ForceResetter(), InsertionSortCollider([Bo1_Sphere_Aabb()]), InteractionLoop( [Ig2_Sphere_Sphere_ScGeom()], [Ip2_FrictMat_FrictMat_FrictPhys()],[Law2_ScGeom_FrictPhys_CundallStrack()]), NewtonIntegrator(), Peri3dController( goal=(-1e7,-1e7,-1e7, 0,0,0), # Vector6 of prescribed final values stressMask=0b000111, nSteps=500, doneHook='print "Hydrostatic load reached."; O.pause()', youngEstimation=.5e9, # needed, when only nonzero prescribed values are stress maxStrain=.5, label='p3d' ), PyRunner(command='plotAddData()',iterPeriod=1), ] O.run(); O.wait() # second part, z-axis straining and constant transversal stress O.engines=[ ForceResetter(), InsertionSortCollider([Bo1_Sphere_Aabb()]), InteractionLoop( [Ig2_Sphere_Sphere_ScGeom()], [Ip2_FrictMat_FrictMat_FrictPhys()],[Law2_ScGeom_FrictPhys_CundallStrack()]), NewtonIntegrator(), Peri3dController( goal=(-1e7,-1e7,-4e-1, 0,0,0), # Vector6 of prescribed final values stressMask=0b000011, nSteps=1000, xxPath=[(0,1),(1,1)], # the first (time) zero defines the initial value of stress considered nonzero yyPath=[(0,1),(1,1)], doneHook='print "Simulation with Peri3dController finished."; O.pause()', maxStrain=.5, label='p3d', strain=p3d.strain, # continue from value reached in previous part stressIdeal=Vector6(-1e7,-1e7,0, 0,0,0), # continue from value reached in previous part ), PyRunner(command='plotAddData()',iterPeriod=1), ] O.run();O.wait() plot.plot(subPlots=False) trunk-2018.02b/examples/PeriodicBoundaries/periodic-compress.py000066400000000000000000000022421324306050200245570ustar00rootroot00000000000000''' This example shows compression of a packing with a periodic cell. ''' O.periodic=True O.cell.setBox(20,20,10) from yade import pack,timing O.materials.append(FrictMat(young=30e9,density=2400)) p=pack.SpherePack() p.makeCloud(Vector3(0,0,0),Vector3(20,20,10),1,.5,700,True) for sph in p: O.bodies.append(sphere(sph[0],sph[1])) O.timingEnabled=True O.engines=[ ForceResetter(), InsertionSortCollider([Bo1_Sphere_Aabb()],allowBiggerThanPeriod=True), InteractionLoop( [Ig2_Sphere_Sphere_ScGeom()], [Ip2_FrictMat_FrictMat_FrictPhys()], [Law2_ScGeom_FrictPhys_CundallStrack()], ), PeriIsoCompressor(charLen=.5,stresses=[-50e9,-1e8],doneHook="print 'FINISHED'; O.pause() ",keepProportions=True), NewtonIntegrator(damping=.4) ] O.dt=PWaveTimeStep() O.saveTmp() #print O.cell.refSize from yade import qt; qt.Controller(); qt.View() O.run() O.wait() timing.stats() #while True: # O.step() # now take that packing and pad some larger volume with it #sp=pack.SpherePack() #sp.fromSimulation() # take spheres from simulation; cellSize is set as well #O.reset() #print sp.cellSize #sp.cellFill((30,30,30)) #print sp.cellSize #for s in sp: # O.bodies.append(sphere(s[0],s[1])) trunk-2018.02b/examples/PeriodicBoundaries/periodic-grow.py000066400000000000000000000023661324306050200237110ustar00rootroot00000000000000"""Script that shrinks the periodic cell progressively. It prints strain and average stress (computed from total volume force) once in a while.""" from yade import timing O.engines=[ ForceResetter(), InsertionSortCollider([Bo1_Sphere_Aabb()],allowBiggerThanPeriod=True), InteractionLoop( [Ig2_Sphere_Sphere_ScGeom()], [Ip2_FrictMat_FrictMat_FrictPhys()], [Law2_ScGeom_FrictPhys_CundallStrack()], ), NewtonIntegrator(damping=.6) ] import random for i in xrange(250): O.bodies.append(sphere(Vector3(10*random.random(),10*random.random(),10*random.random()),.5+random.random())) cubeSize=20 # absolute positioning of the cell is not important O.periodic=True O.cell.setBox(cubeSize,cubeSize,cubeSize) O.dt=PWaveTimeStep() O.saveTmp() from yade import qt qt.Controller(); qt.View() O.run(200,True) rate=-1e-3*cubeSize/(O.dt*200)*Matrix3.Identity O.cell.velGrad=rate print 'Please be patient...' for i in range(0,25): O.run(2000,True) F,stiff=totalForceInVolume() dim=O.cell.refSize; A=Vector3(dim[1]*dim[2],dim[0]*dim[2],dim[0]*dim[1]) avgStress=sum([F[i]/A[i] for i in 0,1,2])/3. print 'strain',(cubeSize-dim[0])/cubeSize,'avg. stress ',avgStress,'unbalanced ',unbalancedForce() #O.timingEnabled=True; timing.reset(); O.run(200000,True); timing.stats() trunk-2018.02b/examples/PeriodicBoundaries/periodic-shear.py000066400000000000000000000021151324306050200240250ustar00rootroot00000000000000O.periodic=True O.cell.setBox(.55,.55,.55) O.bodies.append(facet([[.4,.0001,.3],[.2,.0001,.3],[.3,.2,.2]])) O.bodies.append(sphere([.3,.1,.4],.05,fixed=False)) O.bodies.append(sphere([.200001,.2000001,.4],.05,fixed=True)) O.bodies.append(sphere([.3,0,0],.1,fixed=True)) O.engines=[ ForceResetter(), InsertionSortCollider([Bo1_Sphere_Aabb(),Bo1_Facet_Aabb()],label='isc'), InteractionLoop( [Ig2_Sphere_Sphere_ScGeom(),Ig2_Facet_Sphere_ScGeom()], [Ip2_FrictMat_FrictMat_FrictPhys()], [Law2_ScGeom_FrictPhys_CundallStrack()] ), NewtonIntegrator(gravity=(0,0,-10)), #PyRunner(command='doCellFlip()',realPeriod=5) ] def doCellFlip(): flip=1 if O.cell.trsf[1,2]<0 else -1; flipCell(Matrix3(0,0,0, 0,0,flip, 0,0,0)) #g=0. #while False: # O.cellShear=Vector3(.2*sin(g),.2*cos(pi*g),.2*sin(2*g)+.2*cos(3*g)) # time.sleep(0.001) # g+=1e-3 O.cell.trsf=Matrix3(1,0,0, 0,1,.5, 0,0,1) O.dt=2e-2*PWaveTimeStep() O.step() O.saveTmp() rrr=yade.qt.Renderer() rrr.intrAllWire,rrr.bound=True,True #isc.watch1,isc.watch2=0,-1 #import yade.qt,time #v=yade.qt.View() #v.axes=True #v.grid=(True,True,True) trunk-2018.02b/examples/PeriodicBoundaries/periodic-simple-shear.py000066400000000000000000000031271324306050200253200ustar00rootroot00000000000000# coding: utf-8 # 2009 © Václav Šmilauer # 2011 ©Bruno Chareyre "Test and demonstrate use of PeriTriaxController." from yade import pack,qt O.periodic=True O.cell.setBox(.1,.1,.1) #O.cell.Hsize=Matrix3(0.1,0,0, 0,0.1,0, 0,0,0.1) sp=pack.SpherePack() radius=5e-3 num=sp.makeCloud((0,0,0),(.1,.1,.1),radius,.2,500,periodic=True) # min,max,radius,rRelFuzz,spheresInCell,periodic O.bodies.append([sphere(s[0],s[1]) for s in sp]) O.engines=[ ForceResetter(), InsertionSortCollider([Bo1_Sphere_Aabb()],verletDist=.05*radius), InteractionLoop( [Ig2_Sphere_Sphere_ScGeom()], [Ip2_FrictMat_FrictMat_FrictPhys()], [Law2_ScGeom_FrictPhys_CundallStrack()] ), PeriTriaxController(dynCell=True,mass=0.2,maxUnbalanced=0.01,relStressTol=0.02,goal=[-1e4,-1e4,0],stressMask=3,globUpdate=5,maxStrainRate=[1.,1.,1.],doneHook='triaxDone()',label='triax'), NewtonIntegrator(damping=.2), ] phase=0 def triaxDone(): global phase if phase==0: print 'Here we are: stress',triax.stress,'strain',triax.strain,'stiffness',triax.stiff print 'Now shearing.' O.cell.velGrad=Matrix3(0,6,0, 0,0,0, 0,0,0) triax.stressMask=7 triax.goal=[-1e4,-1e4,-1e4] phase+=1 O.pause() #elif phase==1: #print 'Here we are: stress',triax.stress,'strain',triax.strain,'stiffness',triax.stiff #phase+=1 ##print 'Done, pausing now.' ##O.pause() O.dt=PWaveTimeStep() #O.run(7000); #qt.View() ##r=qt.Renderer() ##r.bgColor=1,1,1 #O.wait() #O.saveTmp() #O.cell.velGrad=Matrix3(0,0,0, -6,0,0, 0,0,0) #O.run(5000); #O.wait() #O.cell.velGrad=Matrix3(0,6,0, 0,0,0, 0,0,0) #O.run(5000); trunk-2018.02b/examples/PeriodicBoundaries/periodic-simple.py000066400000000000000000000020131324306050200242110ustar00rootroot00000000000000"""Simple test of periodic collider. A few spheres falling down in gravity field and one moving accross. Includes a clump. """ from yade import timing O.engines=[ ForceResetter(), InsertionSortCollider([Bo1_Sphere_Aabb()],label='collider'), InteractionLoop( [Ig2_Sphere_Sphere_ScGeom()], [Ip2_FrictMat_FrictMat_FrictPhys()], [Law2_ScGeom_FrictPhys_CundallStrack()], ), TranslationEngine(translationAxis=(1,0,0),velocity=10,ids=[0]), NewtonIntegrator(damping=.4,gravity=[0,0,-10]) ] O.bodies.append(sphere([-4,0,11],2,fixed=True)) O.bodies.append(sphere([0,-2,5.5],2)) O.bodies.append(sphere([0,2,5.5],2)) O.bodies.appendClumped([sphere([0,4,8],.8),sphere([0,5,7],.6)]) # sets up the periodic cell O.periodic=True O.cell.setBox(10,10,10) # normally handled in by the simulation... but we want to have the rendering right before start #O.cell.postProcessAttributes() O.dt=.1*PWaveTimeStep() O.saveTmp() from yade import qt qt.Controller() qt.View() #O.timingEnabled=True; timing.reset(); O.run(200000,True); timing.stats() trunk-2018.02b/examples/PeriodicBoundaries/periodic-triax-settingHsize.py000066400000000000000000000030671324306050200265370ustar00rootroot00000000000000# coding: utf-8 # 2011 ©Bruno Chareyre "Demonstrate the compression of a periodic cell with non-trivial initial geometry." from yade import pack,qt O.periodic=True O.cell.hSize=Matrix3(1.0, -0.15, -0.10, -0.2 ,1.5, 0.3, 0.3, -0.3, 1.0) sp=pack.SpherePack() num=sp.makeCloud(hSize=O.cell.hSize, rMean=-0.01,rRelFuzz=.2, num=500,periodic=True, porosity=0.52,distributeMass=False) O.bodies.append([sphere(s[0],s[1]) for s in sp]) O.engines=[ ForceResetter(), InsertionSortCollider([Bo1_Sphere_Aabb()]), InteractionLoop( [Ig2_Sphere_Sphere_ScGeom()], [Ip2_FrictMat_FrictMat_FrictPhys()], [Law2_ScGeom_FrictPhys_CundallStrack()] ), #We compress the packing isotropicaly first PeriTriaxController( dynCell=True,mass=0.2,maxUnbalanced=0.01, relStressTol=0.01,goal=(-1e4,-1e4,-1e4), stressMask=7,globUpdate=5, maxStrainRate=(1.,1.,1.), doneHook='triaxDone()', label='triax'), NewtonIntegrator(damping=.2), #PyRunner(iterPeriod=500,command='print "strain: ",triax.strain," stress: ",triax.stress') ] O.dt=0.5*PWaveTimeStep() qt.View() phase=0 def triaxDone(): global phase if phase==0: print 'Here we are: stress',triax.stress,'strain',triax.strain #Here we reset the transformation, the compressed packing corresponds to null strain O.cell.trsf=Matrix3.Identity print 'Now εxx will go from 0 to .4 while σzz and σyy will be kept the same.' triax.stressMask=6 triax.goal=(-0.4,-1e4,-1e4) phase+=1 elif phase==1: print 'Here we are: stress',triax.stress,'strain',triax.strain print 'Done, pausing now.' O.pause() trunk-2018.02b/examples/PeriodicBoundaries/periodic-triax.py000066400000000000000000000030021324306050200240460ustar00rootroot00000000000000# coding: utf-8 # 2009 © Václav Šmilauer "Test and demonstrate use of PeriTriaxController." from yade import pack,qt O.periodic=True O.cell.hSize=Matrix3(0.1, 0, 0, 0 ,0.1, 0, 0, 0, 0.1) sp=pack.SpherePack() radius=5e-3 num=sp.makeCloud(Vector3().Zero,O.cell.refSize,radius,.2,500,periodic=True) # min,max,radius,rRelFuzz,spheresInCell,periodic O.bodies.append([sphere(s[0],s[1]) for s in sp]) O.engines=[ ForceResetter(), InsertionSortCollider([Bo1_Sphere_Aabb()],verletDist=.05*radius), InteractionLoop( [Ig2_Sphere_Sphere_ScGeom()], [Ip2_FrictMat_FrictMat_FrictPhys()], [Law2_ScGeom_FrictPhys_CundallStrack()] ), #PeriTriaxController(maxUnbalanced=0.01,relStressTol=0.02,goal=[-1e4,-1e4,0],stressMask=3,globUpdate=5,maxStrainRate=[1.,1.,1.],doneHook='triaxDone()',label='triax'), #using cell inertia PeriTriaxController(dynCell=True,mass=0.2,maxUnbalanced=0.01,relStressTol=0.02,goal=(-1e4,-1e4,0),stressMask=3,globUpdate=5,maxStrainRate=(1.,1.,1.),doneHook='triaxDone()',label='triax'), NewtonIntegrator(damping=.2), ] O.dt=PWaveTimeStep() O.run(); qt.View() phase=0 def triaxDone(): global phase if phase==0: print 'Here we are: stress',triax.stress,'strain',triax.strain,'stiffness',triax.stiff print 'Now εz will go from 0 to .2 while σx and σy will be kept the same.' triax.goal=(-1e4,-1e4,-0.2) phase+=1 elif phase==1: print 'Here we are: stress',triax.stress,'strain',triax.strain,'stiffness',triax.stiff print 'Done, pausing now.' O.pause() trunk-2018.02b/examples/PeriodicBoundaries/periodicSandPile.py000066400000000000000000000033441324306050200243520ustar00rootroot00000000000000#!/usr/bin/python # -*- coding: utf-8 -*- # © 2012 Bruno Chareyre """Script showing how large bodies can be combined with periodic boundary conditions using InsertioSortCollider::allowBiggerThanPeriod=True (1).""" from yade import pack from pylab import rand from yade import qt O.periodic=True length=0.4 height=0.6 width=0.2 thickness=0.01 O.cell.hSize=Matrix3(length, 0, 0, 0 ,3.*height, 0, 0, 0, width) O.materials.append(FrictMat(density=1,young=1e5,poisson=0.3,frictionAngle=radians(30),label='boxMat')) lowBox = box( center=(length/2.0,height-thickness/2.0,width/2.0), extents=(length*1000.0,thickness/2.0,width*1000.0) ,fixed=True,wire=False) O.bodies.append(lowBox) radius=0.01 O.materials.append(FrictMat(density=1000,young=1e4,poisson=0.3,frictionAngle=radians(30),label='sphereMat')) sp=pack.SpherePack() sp.makeCloud((0.*length,height+1.2*radius,0.25*width),(0.5*length,2*height-1.2*radius,0.75*width),-1,.2,2000,periodic=True) O.bodies.append([sphere(s[0],s[1],color=(0.6+0.15*rand(),0.5+0.15*rand(),0.15+0.15*rand())) for s in sp]) O.dt=0.2*PWaveTimeStep() O.usesTimeStepper=True newton=NewtonIntegrator(damping=0.6,gravity=(0,-10,0)) O.engines=[ ForceResetter(), #(1) This is where we all big bodies, else it would crash due to the very large bottom box: InsertionSortCollider([Bo1_Box_Aabb(),Bo1_Sphere_Aabb()],allowBiggerThanPeriod=True), InteractionLoop( [Ig2_Sphere_Sphere_ScGeom(),Ig2_Box_Sphere_ScGeom()], [Ip2_FrictMat_FrictMat_FrictPhys()], [Law2_ScGeom_FrictPhys_CundallStrack()] ), GlobalStiffnessTimeStepper(timeStepUpdateInterval=100,timestepSafetyCoefficient=0.8,defaultDt=O.dt), newton ] Gl1_Sphere.stripes=1 from yade import qt qt.View() print('Press PLAY button') trunk-2018.02b/examples/PotentialBlocks/000077500000000000000000000000001324306050200201015ustar00rootroot00000000000000trunk-2018.02b/examples/PotentialBlocks/cubePBscaled.py000077500000000000000000000440071324306050200227770ustar00rootroot00000000000000#!/usr/local/bin/yade-trunk -x # -*- encoding=utf-8 -*- # CWBoon 2015 # Some of the parameters are defunct.. # I use this for jointed rock mass, but you can use it to generate granular particles too from yade import pack import math O.engines=[ ForceResetter(), InsertionSortCollider([PotentialBlock2AABB()],verletDist=0), InteractionLoop( [Ig2_PB_PB_ScGeom()], [Ip2_FrictMat_FrictMat_KnKsPBPhys(Knormal = 1e8, Kshear = 1e8,useFaceProperties=False,calJointLength=False,twoDimension=True,unitWidth2D=1.0,viscousDamping=0.7)], [Law2_SCG_KnKsPBPhys_KnKsPBLaw(label='law',neverErase=False)] #[Ip2_FrictMat_FrictMat_FrictPhys()], #[Law2_ScGeom_FrictPhys_CundallStrack()] ), #GravityEngine(gravity=[0,-10,0]), #GlobalStiffnessTimeStepper(), NewtonIntegrator(damping=0.0,exactAsphericalRot=False,gravity=[0,-10,0]), #PotentialBlockVTKRecorder(fileName='/home/chiab/yadeNew/mosek/8Nov/BranchA/scripts2/boon/ComputersGeotechnics/vtk/1000PP',iterPeriod=100,sampleX=50,sampleY=50,sampleZ=50) ] powderDensity = 10000 distanceToCentre= 0.5 meanSize = 1.0 wallThickness = 0.5*meanSize O.materials.append(FrictMat(young=150e6,poisson=.4,frictionAngle=radians(0.0),density=powderDensity,label='frictionless')) lengthOfBase = 9.0*meanSize heightOfBase = 14.0*meanSize sp=pack.SpherePack() mn,mx=Vector3(-0.5*(lengthOfBase-wallThickness),0.5*meanSize,-0.5*(lengthOfBase-wallThickness)),Vector3(0.5*(lengthOfBase-wallThickness),7.0*heightOfBase,0.5*(lengthOfBase-wallThickness)) sphereRad = sqrt(3.0)*0.5*meanSize sp.makeCloud(mn,mx,sphereRad,0,100,False) count= 0 rPP=0.05*meanSize for s in sp: b=Body() radius=2.2 dynamic=True wire=False color=[0,0,255.0] highlight=False b.shape=PotentialBlock(k=0.2, r=0.05*meanSize, R=1.02*sphereRad, a=[1.0,-1.0,0.0,0.0,0.0,0.0], b=[0.0,0.0,1.0,-1.0,0.0,0.0], c=[0.0,0.0,0.0,0.0,1.0,-1.0], d=[distanceToCentre-rPP,distanceToCentre-rPP,distanceToCentre-rPP,distanceToCentre-rPP,distanceToCentre-rPP,distanceToCentre-rPP],isBoundary=False,color=color,wire=wire,highlight=highlight,minAabb=Vector3(sphereRad,sphereRad,sphereRad),maxAabb=Vector3(sphereRad,sphereRad,sphereRad),maxAabbRotated=Vector3(sphereRad,sphereRad,sphereRad),minAabbRotated=Vector3(sphereRad,sphereRad,sphereRad),AabbMinMax=False) length=meanSize V= 1.0 geomInert=(2./5.)*powderDensity*V*sphereRad**2 utils._commonBodySetup(b,V,Vector3(geomInert,geomInert,geomInert), material='frictionless',pos=s[0], noBound=False, resetState=True, fixed=False) b.state.pos = s[0] #s[0] stores center b.state.ori = Quaternion((random.random(),random.random(),random.random()),random.random()) #s[2] O.bodies.append(b) b.dynamic = True count =count+1 #v1 = (0, 0, 0.2) (0, 0, 1) (0,0,2.498) #v2 = (-0.0943, 0.1633, -0.0667) (-0.4714, 0.8165, -0.3334) #v3 = (0.1886 0 -0.0667) (0.9428, 0, -0.3333) #edge = 0.3266 1.6333 4.08 #volume = 0.0041 0.5132 8 r=0.1*wallThickness bbb=Body() wire=False color=[0,255,0] highlight=False bbb.shape=PotentialBlock(k=0.1, r=0.1*wallThickness, R=0.2*lengthOfBase,a=[1,-1,0,0,0,0], b=[0,0,1,-1,0,0], c=[0,0,0,0,1,-1], d=[lengthOfBase/6.0-r,lengthOfBase/6.0-r,0.5*wallThickness-r,0.5*wallThickness-r,lengthOfBase/6.0-r,lengthOfBase/6.0-r], id=count,isBoundary=True,isBoundaryPlane=[True,True,True,True,True,True],color=color ,wire=wire,highlight=highlight,AabbMinMax=True, minAabb=1.02*Vector3(lengthOfBase/6.0,0.5*wallThickness,lengthOfBase/6.0),maxAabb=1.02*Vector3(lengthOfBase/6.0,0.5*wallThickness,lengthOfBase/6.0),maxAabbRotated=1.02*Vector3(lengthOfBase/6.0,0.5*wallThickness,lengthOfBase/6.0),minAabbRotated=1.02*Vector3(lengthOfBase/6.0,0.5*wallThickness,lengthOfBase/6.0)) length=lengthOfBase V=lengthOfBase*lengthOfBase*wallThickness geomInert=(1./6.)*V*length*wallThickness utils._commonBodySetup(bbb,V,Vector3(geomInert,geomInert,geomInert), material='frictionless',pos=[0.0,0,0], noBound=False, resetState=True, fixed=True) bbb.dynamic=False bbb.state.pos = [0.0,0,0] lidID = O.bodies.append(bbb) b1=Body() wire=False color=[0,255,0] highlight=False b1.shape=PotentialBlock(k=0.1, r=0.1*wallThickness, R=0.2*lengthOfBase,a=[1,-1,0,0,0,0], b=[0,0,1,-1,0,0], c=[0,0,0,0,1,-1], d=[lengthOfBase/6.0-r,lengthOfBase/6.0-r,0.5*wallThickness-r,0.5*wallThickness-r,lengthOfBase/6.0-r,lengthOfBase/6.0-r], id=count,isBoundary=True,isBoundaryPlane=[True,True,True,True,True,True],color=color ,wire=wire,highlight=highlight,AabbMinMax=True, minAabb=1.02*Vector3(lengthOfBase/6.0,0.4*wallThickness,lengthOfBase/6.0),maxAabb=1.02*Vector3(lengthOfBase/6.0,0.4*wallThickness,lengthOfBase/6.0),maxAabbRotated=1.02*Vector3(lengthOfBase/6.0,0.5*wallThickness,lengthOfBase/6.0),minAabbRotated=1.02*Vector3(lengthOfBase/6.0,0.5*wallThickness,lengthOfBase/6.0)) length=lengthOfBase V=lengthOfBase*lengthOfBase*wallThickness geomInert=(1./6.)*V*length*wallThickness utils._commonBodySetup(b1,V,Vector3(geomInert,geomInert,geomInert), material='frictionless',pos=[0.0,0,0], noBound=False, resetState=True,fixed=True) b1.dynamic=False b1.state.pos = [lengthOfBase/3.0,0,lengthOfBase/3.0] O.bodies.append(b1) b2=Body() wire=False color=[0,255,0] highlight=False b2.shape=PotentialBlock(k=0.1, r=0.1*wallThickness, R=0.2*lengthOfBase,a=[1,-1,0,0,0,0], b=[0,0,1,-1,0,0], c=[0,0,0,0,1,-1], d=[lengthOfBase/6.0-r,lengthOfBase/6.0-r,0.5*wallThickness-r,0.5*wallThickness-r,lengthOfBase/6.0-r,lengthOfBase/6.0-r], id=count,isBoundary=True,isBoundaryPlane=[True,True,True,True,True,True],color=color ,wire=wire,highlight=highlight,AabbMinMax=True, minAabb=1.02*Vector3(lengthOfBase/6.0,0.4*wallThickness,lengthOfBase/6.0),maxAabb=1.02*Vector3(lengthOfBase/6.0,0.4*wallThickness,lengthOfBase/6.0),maxAabbRotated=1.02*Vector3(lengthOfBase/6.0,0.5*wallThickness,lengthOfBase/6.0),minAabbRotated=1.02*Vector3(lengthOfBase/6.0,0.5*wallThickness,lengthOfBase/6.0)) length=lengthOfBase V=lengthOfBase*lengthOfBase*wallThickness geomInert=(1./6.)*V*length*wallThickness utils._commonBodySetup(b2,V,Vector3(geomInert,geomInert,geomInert), material='frictionless',pos=[0.0,0,0], noBound=False, resetState=True,fixed=True) b2.dynamic=False b2.state.pos = [-lengthOfBase/3.0,0,lengthOfBase/3.0] O.bodies.append(b2) b3=Body() wire=False color=[0,255,0] highlight=False b3.shape=PotentialBlock(k=0.1, r=0.1*wallThickness, R=0.2*lengthOfBase,a=[1,-1,0,0,0,0], b=[0,0,1,-1,0,0], c=[0,0,0,0,1,-1], d=[lengthOfBase/6.0-r,lengthOfBase/6.0-r,0.5*wallThickness-r,0.5*wallThickness-r,lengthOfBase/6.0-r,lengthOfBase/6.0-r], id=count,isBoundary=True,isBoundaryPlane=[True,True,True,True,True,True],color=color ,wire=wire,highlight=highlight,AabbMinMax=True, minAabb=1.02*Vector3(lengthOfBase/6.0,0.4*wallThickness,lengthOfBase/6.0),maxAabb=1.02*Vector3(lengthOfBase/6.0,0.4*wallThickness,lengthOfBase/6.0),maxAabbRotated=1.02*Vector3(lengthOfBase/6.0,0.5*wallThickness,lengthOfBase/6.0),minAabbRotated=1.02*Vector3(lengthOfBase/6.0,0.5*wallThickness,lengthOfBase/6.0)) length=lengthOfBase V=lengthOfBase*lengthOfBase*wallThickness geomInert=(1./6.)*V*length*wallThickness utils._commonBodySetup(b3,V,Vector3(geomInert,geomInert,geomInert), material='frictionless',pos=[0.0,0,0], noBound=False, resetState=True,fixed=True) b3.dynamic=False b3.state.pos = [0.0,0,lengthOfBase/3.0] O.bodies.append(b3) b4=Body() wire=False color=[0,255,0] highlight=False b4.shape=PotentialBlock(k=0.1, r=0.1*wallThickness, R=0.2*lengthOfBase,a=[1,-1,0,0,0,0], b=[0,0,1,-1,0,0], c=[0,0,0,0,1,-1], d=[lengthOfBase/6.0-r,lengthOfBase/6.0-r,0.5*wallThickness-r,0.5*wallThickness-r,lengthOfBase/6.0-r,lengthOfBase/6.0-r], id=count,isBoundary=True,isBoundaryPlane=[True,True,True,True,True,True],color=color ,wire=wire,highlight=highlight,AabbMinMax=True, minAabb=1.02*Vector3(lengthOfBase/6.0,0.4*wallThickness,lengthOfBase/6.0),maxAabb=1.02*Vector3(lengthOfBase/6.0,0.4*wallThickness,lengthOfBase/6.0),maxAabbRotated=1.02*Vector3(lengthOfBase/6.0,0.5*wallThickness,lengthOfBase/6.0),minAabbRotated=1.02*Vector3(lengthOfBase/6.0,0.5*wallThickness,lengthOfBase/6.0)) length=lengthOfBase V=lengthOfBase*lengthOfBase*wallThickness geomInert=(1./6.)*V*length*wallThickness utils._commonBodySetup(b4,V,Vector3(geomInert,geomInert,geomInert), material='frictionless',pos=[0.0,0,0], noBound=False, resetState=True,fixed=True) b4.dynamic=False b4.state.pos = [lengthOfBase/3.0,0,-lengthOfBase/3.0] O.bodies.append(b4) b5=Body() wire=False color=[0,255,0] highlight=False b5.shape=PotentialBlock(k=0.1, r=0.1*wallThickness, R=0.2*lengthOfBase,a=[1,-1,0,0,0,0], b=[0,0,1,-1,0,0], c=[0,0,0,0,1,-1], d=[lengthOfBase/6.0-r,lengthOfBase/6.0-r,0.5*wallThickness-r,0.5*wallThickness-r,lengthOfBase/6.0-r,lengthOfBase/6.0-r], id=count,isBoundary=True,isBoundaryPlane=[True,True,True,True,True,True],color=color ,wire=wire,highlight=highlight,AabbMinMax=True, minAabb=1.02*Vector3(lengthOfBase/6.0,0.4*wallThickness,lengthOfBase/6.0),maxAabb=1.02*Vector3(lengthOfBase/6.0,0.4*wallThickness,lengthOfBase/6.0),maxAabbRotated=1.02*Vector3(lengthOfBase/6.0,0.5*wallThickness,lengthOfBase/6.0),minAabbRotated=1.02*Vector3(lengthOfBase/6.0,0.5*wallThickness,lengthOfBase/6.0)) length=lengthOfBase V=lengthOfBase*lengthOfBase*wallThickness geomInert=(1./6.)*V*length*wallThickness utils._commonBodySetup(b5,V,Vector3(geomInert,geomInert,geomInert), material='frictionless',pos=[0.0,0,0], noBound=False, resetState=True,fixed=True) b5.dynamic=False b5.state.pos = [0.0,0,-lengthOfBase/3.0] O.bodies.append(b5) b6=Body() wire=False color=[0,255,0] highlight=False b6.shape=PotentialBlock(k=0.1, r=0.1*wallThickness, R=0.2*lengthOfBase,a=[1,-1,0,0,0,0], b=[0,0,1,-1,0,0], c=[0,0,0,0,1,-1], d=[lengthOfBase/6.0-r,lengthOfBase/6.0-r,0.5*wallThickness-r,0.5*wallThickness-r,lengthOfBase/6.0-r,lengthOfBase/6.0-r], id=count,isBoundary=True,isBoundaryPlane=[True,True,True,True,True,True],color=color ,wire=wire,highlight=highlight,AabbMinMax=True, minAabb=1.02*Vector3(lengthOfBase/6.0,0.4*wallThickness,lengthOfBase/6.0),maxAabb=1.02*Vector3(lengthOfBase/6.0,0.4*wallThickness,lengthOfBase/6.0),maxAabbRotated=1.02*Vector3(lengthOfBase/6.0,0.5*wallThickness,lengthOfBase/6.0),minAabbRotated=1.02*Vector3(lengthOfBase/6.0,0.5*wallThickness,lengthOfBase/6.0)) length=lengthOfBase V=lengthOfBase*lengthOfBase*wallThickness geomInert=(1./6.)*V*length*wallThickness utils._commonBodySetup(b6,V,Vector3(geomInert,geomInert,geomInert), material='frictionless',pos=[0.0,0,0], noBound=False, resetState=True,fixed=True) b6.dynamic=False b6.state.pos = [-lengthOfBase/3.0,0.0,-lengthOfBase/3.0] O.bodies.append(b6) b7=Body() wire=False color=[0,255,0] highlight=False b7.shape=PotentialBlock(k=0.1, r=0.1*wallThickness, R=0.2*lengthOfBase,a=[1,-1,0,0,0,0], b=[0,0,1,-1,0,0], c=[0,0,0,0,1,-1], d=[lengthOfBase/6.0-r,lengthOfBase/6.0-r,0.5*wallThickness-r,0.5*wallThickness-r,lengthOfBase/6.0-r,lengthOfBase/6.0-r], id=count,isBoundary=True,isBoundaryPlane=[True,True,True,True,True,True],color=color ,wire=wire,highlight=highlight,AabbMinMax=True, minAabb=1.02*Vector3(lengthOfBase/6.0,0.4*wallThickness,lengthOfBase/6.0),maxAabb=1.02*Vector3(lengthOfBase/6.0,0.4*wallThickness,lengthOfBase/6.0),maxAabbRotated=1.02*Vector3(lengthOfBase/6.0,0.5*wallThickness,lengthOfBase/6.0),minAabbRotated=1.02*Vector3(lengthOfBase/6.0,0.5*wallThickness,lengthOfBase/6.0)) length=lengthOfBase V=lengthOfBase*lengthOfBase*wallThickness geomInert=(1./6.)*V*length*wallThickness utils._commonBodySetup(b7,V,Vector3(geomInert,geomInert,geomInert), material='frictionless',pos=[0.0,0,0], noBound=False, resetState=True,fixed=True) b7.dynamic=False b7.state.pos = [-lengthOfBase/3.0,0.0,0.0] O.bodies.append(b7) b8=Body() wire=False color=[0,255,0] highlight=False b8.shape=PotentialBlock(k=0.1, r=0.1*wallThickness, R=0.2*lengthOfBase,a=[1,-1,0,0,0,0], b=[0,0,1,-1,0,0], c=[0,0,0,0,1,-1], d=[lengthOfBase/6.0-r,lengthOfBase/6.0-r,0.5*wallThickness-r,0.5*wallThickness-r,lengthOfBase/6.0-r,lengthOfBase/6.0-r], id=count,isBoundary=True,isBoundaryPlane=[True,True,True,True,True,True],color=color ,wire=wire,highlight=highlight,AabbMinMax=True, minAabb=1.02*Vector3(lengthOfBase/6.0,0.4*wallThickness,lengthOfBase/6.0),maxAabb=1.02*Vector3(lengthOfBase/6.0,0.4*wallThickness,lengthOfBase/6.0),maxAabbRotated=1.02*Vector3(lengthOfBase/6.0,0.5*wallThickness,lengthOfBase/6.0),minAabbRotated=1.02*Vector3(lengthOfBase/6.0,0.5*wallThickness,lengthOfBase/6.0)) length=lengthOfBase V=lengthOfBase*lengthOfBase*wallThickness geomInert=(1./6.)*V*length*wallThickness utils._commonBodySetup(b8,V,Vector3(geomInert,geomInert,geomInert), material='frictionless',pos=[0.0,0,0], noBound=False, resetState=True,fixed=True) b8.dynamic=False b8.state.pos = [lengthOfBase/3.0,0.0,0.0] O.bodies.append(b8) bA=Body() wire=False color=[0,255,0] highlight=False bA.shape=PotentialBlock(k=0.1, r=0.1*wallThickness, R=0.5*heightOfBase,a=[1,-1,0,0,0,0], b=[0,0,1,-1,0,0], c=[0,0,0,0,1,-1], d=[0.5*wallThickness-r,0.5*wallThickness-r,0.5*heightOfBase-r,0.5*heightOfBase-r,0.5*lengthOfBase-r,0.5*lengthOfBase-r], id=count+1,isBoundary=True,isBoundaryPlane=[True,True,True,True,True,True],color=color ,wire=wire,highlight=highlight,AabbMinMax=True, minAabb=1.02*Vector3(0.3*wallThickness,0.5*heightOfBase,0.5*lengthOfBase),maxAabb=1.02*Vector3(0.3*wallThickness,0.5*heightOfBase,0.5*lengthOfBase),maxAabbRotated=1.02*Vector3(0.5*wallThickness,0.5*heightOfBase,0.5*lengthOfBase),minAabbRotated=1.02*Vector3(0.5*wallThickness,0.5*heightOfBase,0.5*lengthOfBase)) length=lengthOfBase V=lengthOfBase*lengthOfBase*wallThickness geomInert=(1./6.)*V*length*wallThickness utils._commonBodySetup(bA,V,Vector3(geomInert,geomInert,geomInert), material='frictionless',pos=[0.0,0,0], noBound=False, resetState=True,fixed=True) bA.dynamic=False bA.state.pos = [0.5*lengthOfBase,0.5*heightOfBase,0] O.bodies.append(bA) bB=Body() wire=False color=[0,255,0] highlight=False bB.shape=PotentialBlock(k=0.1, r=0.1*wallThickness, R=0.5*heightOfBase,a=[1,-1,0,0,0,0], b=[0,0,1,-1,0,0], c=[0,0,0,0,1,-1], d=[0.5*wallThickness-r,0.5*wallThickness-r,0.5*heightOfBase-r,0.5*heightOfBase-r,0.5*lengthOfBase-r,0.5*lengthOfBase-r], id=count+2,isBoundary=True,isBoundaryPlane=[True,True,True,True,True,True],color=color ,wire=wire,highlight=highlight,AabbMinMax=True, minAabb=1.02*Vector3(0.3*wallThickness,0.5*heightOfBase,0.5*lengthOfBase),maxAabb=1.02*Vector3(0.3*wallThickness,0.5*heightOfBase,0.5*lengthOfBase),maxAabbRotated=1.02*Vector3(0.5*wallThickness,0.5*heightOfBase,0.5*lengthOfBase),minAabbRotated=1.02*Vector3(0.5*wallThickness,0.5*heightOfBase,0.5*lengthOfBase)) length=lengthOfBase V=lengthOfBase*lengthOfBase*wallThickness geomInert=(1./6.)*V*length*wallThickness utils._commonBodySetup(bB,V,Vector3(geomInert,geomInert,geomInert), material='frictionless',pos=[0.0,0,0], noBound=False, resetState=True,fixed=True) bB.dynamic=False bB.state.pos = [-0.5*lengthOfBase,0.5*heightOfBase,0] O.bodies.append(bB) bC=Body() wire=False color=[0,255,0] highlight=False bC.shape=PotentialBlock(k=0.1, r=0.1*wallThickness, R=0.5*heightOfBase,a=[1,-1,0,0,0,0], b=[0,0,1,-1,0,0], c=[0,0,0,0,1,-1], d=[0.5*lengthOfBase-r,0.5*lengthOfBase-r,0.5*heightOfBase-r,0.5*heightOfBase-r,0.5*wallThickness-r,0.5*wallThickness-r], id=count+3,isBoundary=True,isBoundaryPlane=[True,True,True,True,True,True],color=color ,wire=wire,highlight=highlight,AabbMinMax=True, minAabb=1.02*Vector3(0.5*lengthOfBase,0.5*heightOfBase,0.3*wallThickness),maxAabb=1.02*Vector3(0.5*lengthOfBase,0.5*heightOfBase,0.3*wallThickness),maxAabbRotated=1.02*Vector3(0.5*lengthOfBase,0.5*heightOfBase,0.5*wallThickness),minAabbRotated=1.02*Vector3(0.5*lengthOfBase,0.5*heightOfBase,0.5*wallThickness)) length=lengthOfBase V=lengthOfBase*lengthOfBase*wallThickness geomInert=(1./6.)*V*length*wallThickness utils._commonBodySetup(bC,V,Vector3(geomInert,geomInert,geomInert), material='frictionless',pos=[0.0,0,0], noBound=False, resetState=True,fixed=True) bC.dynamic=False bC.state.pos = [0,0.5*heightOfBase,0.5*lengthOfBase] O.bodies.append(bC) bD=Body() wire=False color=[0,255,0] highlight=False bD.shape=PotentialBlock(k=0.1, r=0.1*wallThickness, R=0.5*heightOfBase,a=[1,-1,0,0,0,0], b=[0,0,1,-1,0,0], c=[0,0,0,0,1,-1], d=[0.5*lengthOfBase-r,0.5*lengthOfBase-r,0.5*heightOfBase-r,0.5*heightOfBase-r,0.5*wallThickness-r,0.5*wallThickness-r], id=count+4,isBoundary=True,isBoundaryPlane=[True,True,True,True,True,True],color=color ,wire=wire,highlight=highlight,AabbMinMax=True, minAabb=1.02*Vector3(0.5*lengthOfBase,0.5*heightOfBase,0.3*wallThickness),maxAabb=1.02*Vector3(0.5*lengthOfBase,0.5*heightOfBase,0.3*wallThickness),maxAabbRotated=1.02*Vector3(0.5*lengthOfBase,0.5*heightOfBase,0.5*wallThickness),minAabbRotated=1.02*Vector3(0.5*lengthOfBase,0.5*heightOfBase,0.5*wallThickness)) length=lengthOfBase V=lengthOfBase*lengthOfBase*wallThickness geomInert=(1./6.)*V*length*wallThickness utils._commonBodySetup(bD,V,Vector3(geomInert,geomInert,geomInert), material='frictionless',pos=[0.0,0,0], noBound=False, resetState=True,fixed=True) bD.dynamic=False bD.state.pos = [0.0,0.5*heightOfBase,-0.5*lengthOfBase] O.bodies.append(bD) escapeNo=0 def myAddPlotData(): global escapeNo global wallThickness global meanSize uf=utils.unbalancedForce() if isnan(uf): uf = 1.0 KE = utils.kineticEnergy() for b in O.bodies: if b.state.pos[1] < -5.0*meanSize and b.dynamic==True: escapeNo = escapeNo+1 O.bodies.erase(b.id) if O.iter>12000: removeLid() plot.addData(timeStep1=O.iter,timeStep2=O.iter,timeStep3=O.iter,timeStep4=O.iter,time=O.time,unbalancedForce=uf,kineticEn=KE,outsideNo=escapeNo) from yade import plot plot.plots={'timeStep1':('unbalancedForce'),'timeStep2':('kineticEn'),'time':('outsideNo')} O.engines=O.engines+[PyRunner(iterPeriod=10,command='myAddPlotData()')] def removeLid(): global lidID if (O.bodies[lidID]): O.bodies.erase(lidID) O.engines=O.engines+[PotentialBlockVTKRecorder(fileName='/home/boon/yadeRev/trunk/examples/PotentialParticles/vtk/cubeScaled',iterPeriod=1000,sampleX=50,sampleY=50,sampleZ=50)] #for b in O.bodies: # b.state.blockedDOFs=['rx','ry','rz','x','z'] #O.bodies[0].state.pos = [0,meanSize*10.0,0] #O.bodies[0].state.vel =[0,0.0,0] O.dt = 0.2*sqrt(O.bodies[0].state.mass*0.33333333/1.0e8) #from yade import qt #qt.Controller() #qt.View() #Gl1_PotentialBlock.sizeX = 30 #Gl1_PotentialBlock.sizeY = 30 #Gl1_PotentialBlock.sizeZ = 30 #from yade import qt #qt.View() import yade.timing O.timingEnabled = True yade.timing.reset() #O.engines[2].geomDispatcher.functors[0].timingDeltas.data #yade.timing.stats() trunk-2018.02b/examples/PotentialParticles/000077500000000000000000000000001324306050200206125ustar00rootroot00000000000000trunk-2018.02b/examples/PotentialParticles/basic.py000066400000000000000000000042071324306050200222500ustar00rootroot00000000000000O.engines=[ ForceResetter(), InsertionSortCollider([PotentialParticle2AABB()]), InteractionLoop( [Ig2_PP_PP_ScGeom()], [Ip2_FrictMat_FrictMat_KnKsPhys(Knormal = 1e14, Kshear = 1e8,useFaceProperties=False,calJointLength=False,twoDimension=True,unitWidth2D=1.0,viscousDamping=0.7)], [Law2_SCG_KnKsPhys_KnKsLaw(label='law',neverErase=False)] ), NewtonIntegrator(), ] radius = 1 pos = Vector3(0,0,0) sphere=Body() radius=1 dynamic=True wire=False color=[0,0,255.0] highlight=False d = 1*radius aabb = 1.0*radius sphere.shape=PotentialParticle( k=1, r=.1*radius, R=1.*radius, a=[1.0,-1.0,0.0,0.0,0.0,0.0], b=[0.0,0.0,1.0,-1.0,0.0,0.0], c=[0.0,0.0,0.0,0.0,1.0,-1.0], d=[d,d,d,d,d,d], minAabb=Vector3(aabb,aabb,aabb), maxAabb=Vector3(aabb,aabb,aabb), maxAabbRotated=Vector3(aabb,aabb,aabb), minAabbRotated=Vector3(aabb,aabb,aabb), id=0 ) volume=4/3.*pi*radius**3 geomInert=(2./5.)*volume*radius**2 utils._commonBodySetup(sphere,volume,Vector3(geomInert,geomInert,geomInert), material=-1,pos=pos, noBound=False, resetState=True, fixed=False, blockedDOFs="") sphere.state.pos = pos sphere.state.ori = Quaternion.Identity O.bodies.append(sphere) pos = Vector3(3,0,0) box=Body() radius=1 dynamic=True wire=False color=[0,0,255.0] highlight=False d = 1*radius aabb = 1.0*radius box.shape=PotentialParticle( k=.05, r=.05*radius, R=1.*radius, a=[1.0,-1.0,0.0,0.0,0.0,0.0], b=[0.0,0.0,1.0,-1.0,0.0,0.0], c=[0.0,0.0,0.0,0.0,1.0,-1.0], d=[d,d,d,d,d,d], minAabb=sqrt(3)*Vector3(aabb,aabb,aabb), maxAabb=sqrt(3)*Vector3(aabb,aabb,aabb), maxAabbRotated=sqrt(3)*Vector3(aabb,aabb,aabb), minAabbRotated=sqrt(3)*Vector3(aabb,aabb,aabb), id=1 ) volume=4/3.*pi*radius**3 geomInert=(2./5.)*volume*radius**2 utils._commonBodySetup(box,volume,Vector3(geomInert,geomInert,geomInert), material=-1,pos=pos, noBound=False, resetState=True, fixed=False, blockedDOFs="") box.state.pos = pos box.state.ori = Quaternion((1,2,3),1) O.bodies.append(box) O.step() sphere.state.vel = (10,0,0) O.dt = 1e-5 from yade import qt qt.View() s = 20 Gl1_PotentialParticle.sizeX = s Gl1_PotentialParticle.sizeY = s Gl1_PotentialParticle.sizeZ = s Gl1_PotentialParticle.aabbEnlargeFactor = 3 trunk-2018.02b/examples/PotentialParticles/cubePPscaled.py000066400000000000000000000416651324306050200235320ustar00rootroot00000000000000#!/usr/local/bin/yade-trunk -x # -*- encoding=utf-8 -*- # CWBoon 2015 from yade import pack import math O.engines=[ ForceResetter(), InsertionSortCollider([PotentialParticle2AABB()],verletDist=0), InteractionLoop( [Ig2_PP_PP_ScGeom()], [Ip2_FrictMat_FrictMat_KnKsPhys(Knormal = 1e8, Kshear = 1e8,useFaceProperties=False,calJointLength=False,twoDimension=True,unitWidth2D=1.0,viscousDamping=0.7)], [Law2_SCG_KnKsPhys_KnKsLaw(label='law',neverErase=False)] ), GravityEngine(gravity=[0,-10,0]), NewtonIntegrator(damping=0.0,exactAsphericalRot=False,gravity=[0,-10,0]), PotentialParticleVTKRecorder(fileName='/tmp/potentialParticleTest',iterPeriod=100,sampleX=50,sampleY=50,sampleZ=50) ] powderDensity = 10000 distanceToCentre= 0.5 meanSize = 1.0 wallThickness = 0.5*meanSize O.materials.append(FrictMat(young=150e6,poisson=.4,frictionAngle=radians(0.0),density=powderDensity,label='frictionless')) lengthOfBase = 9.0*meanSize heightOfBase = 14.0*meanSize sp=pack.SpherePack() mn,mx=Vector3(-0.5*(lengthOfBase-wallThickness),0.5*meanSize,-0.5*(lengthOfBase-wallThickness)),Vector3(0.5*(lengthOfBase-wallThickness),7.0*heightOfBase,0.5*(lengthOfBase-wallThickness)) sphereRad = sqrt(3.0)*0.5*meanSize sp.makeCloud(mn,mx,sphereRad,0,100,False) count= 0 rPP=0.05*meanSize for s in sp: b=Body() radius=2.2 dynamic=True wire=False color=[0,0,255.0] highlight=False b.shape=PotentialParticle(k=0.2, r=0.05*meanSize, R=1.02*sphereRad, a=[1.0,-1.0,0.0,0.0,0.0,0.0], b=[0.0,0.0,1.0,-1.0,0.0,0.0], c=[0.0,0.0,0.0,0.0,1.0,-1.0], d=[distanceToCentre-rPP,distanceToCentre-rPP,distanceToCentre-rPP,distanceToCentre-rPP,distanceToCentre-rPP,distanceToCentre-rPP],isBoundary=False,color=color,wire=wire,highlight=highlight,minAabb=Vector3(sphereRad,sphereRad,sphereRad),maxAabb=Vector3(sphereRad,sphereRad,sphereRad),maxAabbRotated=Vector3(sphereRad,sphereRad,sphereRad),minAabbRotated=Vector3(sphereRad,sphereRad,sphereRad),AabbMinMax=False, id=count) length=meanSize V= 1.0 geomInert=(2./5.)*powderDensity*V*sphereRad**2 utils._commonBodySetup(b,V,Vector3(geomInert,geomInert,geomInert), material='frictionless',pos=s[0], noBound=False, resetState=True, fixed=False) b.state.pos = s[0] #s[0] stores center b.state.ori = Quaternion((random.random(),random.random(),random.random()),random.random()) #s[2] O.bodies.append(b) b.dynamic = True count =count+1 r=0.1*wallThickness bbb=Body() wire=False color=[0,255,0] highlight=False bbb.shape=PotentialParticle(k=0.1, r=0.1*wallThickness, R=0.2*lengthOfBase,a=[1,-1,0,0,0,0], b=[0,0,1,-1,0,0], c=[0,0,0,0,1,-1], d=[lengthOfBase/6.0-r,lengthOfBase/6.0-r,0.5*wallThickness-r,0.5*wallThickness-r,lengthOfBase/6.0-r,lengthOfBase/6.0-r], id=count,isBoundary=True,isBoundaryPlane=[True,True,True,True,True,True],color=color ,wire=wire,highlight=highlight,AabbMinMax=True, minAabb=1.02*Vector3(lengthOfBase/6.0,0.5*wallThickness,lengthOfBase/6.0),maxAabb=1.02*Vector3(lengthOfBase/6.0,0.5*wallThickness,lengthOfBase/6.0),maxAabbRotated=1.02*Vector3(lengthOfBase/6.0,0.5*wallThickness,lengthOfBase/6.0),minAabbRotated=1.02*Vector3(lengthOfBase/6.0,0.5*wallThickness,lengthOfBase/6.0)) length=lengthOfBase V=lengthOfBase*lengthOfBase*wallThickness geomInert=(1./6.)*V*length*wallThickness utils._commonBodySetup(bbb,V,Vector3(geomInert,geomInert,geomInert), material='frictionless',pos=[0.0,0,0], noBound=False, resetState=True, fixed=True) bbb.dynamic=False bbb.state.pos = [0.0,0,0] lidID = O.bodies.append(bbb) b1=Body() wire=False color=[0,255,0] highlight=False b1.shape=PotentialParticle(k=0.1, r=0.1*wallThickness, R=0.2*lengthOfBase,a=[1,-1,0,0,0,0], b=[0,0,1,-1,0,0], c=[0,0,0,0,1,-1], d=[lengthOfBase/6.0-r,lengthOfBase/6.0-r,0.5*wallThickness-r,0.5*wallThickness-r,lengthOfBase/6.0-r,lengthOfBase/6.0-r], id=count,isBoundary=True,isBoundaryPlane=[True,True,True,True,True,True],color=color ,wire=wire,highlight=highlight,AabbMinMax=True, minAabb=1.02*Vector3(lengthOfBase/6.0,0.4*wallThickness,lengthOfBase/6.0),maxAabb=1.02*Vector3(lengthOfBase/6.0,0.4*wallThickness,lengthOfBase/6.0),maxAabbRotated=1.02*Vector3(lengthOfBase/6.0,0.5*wallThickness,lengthOfBase/6.0),minAabbRotated=1.02*Vector3(lengthOfBase/6.0,0.5*wallThickness,lengthOfBase/6.0)) length=lengthOfBase V=lengthOfBase*lengthOfBase*wallThickness geomInert=(1./6.)*V*length*wallThickness utils._commonBodySetup(b1,V,Vector3(geomInert,geomInert,geomInert), material='frictionless',pos=[0.0,0,0], noBound=False, resetState=True,fixed=True) b1.dynamic=False b1.state.pos = [lengthOfBase/3.0,0,lengthOfBase/3.0] O.bodies.append(b1) b2=Body() wire=False color=[0,255,0] highlight=False b2.shape=PotentialParticle(k=0.1, r=0.1*wallThickness, R=0.2*lengthOfBase,a=[1,-1,0,0,0,0], b=[0,0,1,-1,0,0], c=[0,0,0,0,1,-1], d=[lengthOfBase/6.0-r,lengthOfBase/6.0-r,0.5*wallThickness-r,0.5*wallThickness-r,lengthOfBase/6.0-r,lengthOfBase/6.0-r], id=count,isBoundary=True,isBoundaryPlane=[True,True,True,True,True,True],color=color ,wire=wire,highlight=highlight,AabbMinMax=True, minAabb=1.02*Vector3(lengthOfBase/6.0,0.4*wallThickness,lengthOfBase/6.0),maxAabb=1.02*Vector3(lengthOfBase/6.0,0.4*wallThickness,lengthOfBase/6.0),maxAabbRotated=1.02*Vector3(lengthOfBase/6.0,0.5*wallThickness,lengthOfBase/6.0),minAabbRotated=1.02*Vector3(lengthOfBase/6.0,0.5*wallThickness,lengthOfBase/6.0)) length=lengthOfBase V=lengthOfBase*lengthOfBase*wallThickness geomInert=(1./6.)*V*length*wallThickness utils._commonBodySetup(b2,V,Vector3(geomInert,geomInert,geomInert), material='frictionless',pos=[0.0,0,0], noBound=False, resetState=True,fixed=True) b2.dynamic=False b2.state.pos = [-lengthOfBase/3.0,0,lengthOfBase/3.0] O.bodies.append(b2) b3=Body() wire=False color=[0,255,0] highlight=False b3.shape=PotentialParticle(k=0.1, r=0.1*wallThickness, R=0.2*lengthOfBase,a=[1,-1,0,0,0,0], b=[0,0,1,-1,0,0], c=[0,0,0,0,1,-1], d=[lengthOfBase/6.0-r,lengthOfBase/6.0-r,0.5*wallThickness-r,0.5*wallThickness-r,lengthOfBase/6.0-r,lengthOfBase/6.0-r], id=count,isBoundary=True,isBoundaryPlane=[True,True,True,True,True,True],color=color ,wire=wire,highlight=highlight,AabbMinMax=True, minAabb=1.02*Vector3(lengthOfBase/6.0,0.4*wallThickness,lengthOfBase/6.0),maxAabb=1.02*Vector3(lengthOfBase/6.0,0.4*wallThickness,lengthOfBase/6.0),maxAabbRotated=1.02*Vector3(lengthOfBase/6.0,0.5*wallThickness,lengthOfBase/6.0),minAabbRotated=1.02*Vector3(lengthOfBase/6.0,0.5*wallThickness,lengthOfBase/6.0)) length=lengthOfBase V=lengthOfBase*lengthOfBase*wallThickness geomInert=(1./6.)*V*length*wallThickness utils._commonBodySetup(b3,V,Vector3(geomInert,geomInert,geomInert), material='frictionless',pos=[0.0,0,0], noBound=False, resetState=True,fixed=True) b3.dynamic=False b3.state.pos = [0.0,0,lengthOfBase/3.0] O.bodies.append(b3) b4=Body() wire=False color=[0,255,0] highlight=False b4.shape=PotentialParticle(k=0.1, r=0.1*wallThickness, R=0.2*lengthOfBase,a=[1,-1,0,0,0,0], b=[0,0,1,-1,0,0], c=[0,0,0,0,1,-1], d=[lengthOfBase/6.0-r,lengthOfBase/6.0-r,0.5*wallThickness-r,0.5*wallThickness-r,lengthOfBase/6.0-r,lengthOfBase/6.0-r], id=count,isBoundary=True,isBoundaryPlane=[True,True,True,True,True,True],color=color ,wire=wire,highlight=highlight,AabbMinMax=True, minAabb=1.02*Vector3(lengthOfBase/6.0,0.4*wallThickness,lengthOfBase/6.0),maxAabb=1.02*Vector3(lengthOfBase/6.0,0.4*wallThickness,lengthOfBase/6.0),maxAabbRotated=1.02*Vector3(lengthOfBase/6.0,0.5*wallThickness,lengthOfBase/6.0),minAabbRotated=1.02*Vector3(lengthOfBase/6.0,0.5*wallThickness,lengthOfBase/6.0)) length=lengthOfBase V=lengthOfBase*lengthOfBase*wallThickness geomInert=(1./6.)*V*length*wallThickness utils._commonBodySetup(b4,V,Vector3(geomInert,geomInert,geomInert), material='frictionless',pos=[0.0,0,0], noBound=False, resetState=True,fixed=True) b4.dynamic=False b4.state.pos = [lengthOfBase/3.0,0,-lengthOfBase/3.0] O.bodies.append(b4) b5=Body() wire=False color=[0,255,0] highlight=False b5.shape=PotentialParticle(k=0.1, r=0.1*wallThickness, R=0.2*lengthOfBase,a=[1,-1,0,0,0,0], b=[0,0,1,-1,0,0], c=[0,0,0,0,1,-1], d=[lengthOfBase/6.0-r,lengthOfBase/6.0-r,0.5*wallThickness-r,0.5*wallThickness-r,lengthOfBase/6.0-r,lengthOfBase/6.0-r], id=count,isBoundary=True,isBoundaryPlane=[True,True,True,True,True,True],color=color ,wire=wire,highlight=highlight,AabbMinMax=True, minAabb=1.02*Vector3(lengthOfBase/6.0,0.4*wallThickness,lengthOfBase/6.0),maxAabb=1.02*Vector3(lengthOfBase/6.0,0.4*wallThickness,lengthOfBase/6.0),maxAabbRotated=1.02*Vector3(lengthOfBase/6.0,0.5*wallThickness,lengthOfBase/6.0),minAabbRotated=1.02*Vector3(lengthOfBase/6.0,0.5*wallThickness,lengthOfBase/6.0)) length=lengthOfBase V=lengthOfBase*lengthOfBase*wallThickness geomInert=(1./6.)*V*length*wallThickness utils._commonBodySetup(b5,V,Vector3(geomInert,geomInert,geomInert), material='frictionless',pos=[0.0,0,0], noBound=False, resetState=True,fixed=True) b5.dynamic=False b5.state.pos = [0.0,0,-lengthOfBase/3.0] O.bodies.append(b5) b6=Body() wire=False color=[0,255,0] highlight=False b6.shape=PotentialParticle(k=0.1, r=0.1*wallThickness, R=0.2*lengthOfBase,a=[1,-1,0,0,0,0], b=[0,0,1,-1,0,0], c=[0,0,0,0,1,-1], d=[lengthOfBase/6.0-r,lengthOfBase/6.0-r,0.5*wallThickness-r,0.5*wallThickness-r,lengthOfBase/6.0-r,lengthOfBase/6.0-r], id=count,isBoundary=True,isBoundaryPlane=[True,True,True,True,True,True],color=color ,wire=wire,highlight=highlight,AabbMinMax=True, minAabb=1.02*Vector3(lengthOfBase/6.0,0.4*wallThickness,lengthOfBase/6.0),maxAabb=1.02*Vector3(lengthOfBase/6.0,0.4*wallThickness,lengthOfBase/6.0),maxAabbRotated=1.02*Vector3(lengthOfBase/6.0,0.5*wallThickness,lengthOfBase/6.0),minAabbRotated=1.02*Vector3(lengthOfBase/6.0,0.5*wallThickness,lengthOfBase/6.0)) length=lengthOfBase V=lengthOfBase*lengthOfBase*wallThickness geomInert=(1./6.)*V*length*wallThickness utils._commonBodySetup(b6,V,Vector3(geomInert,geomInert,geomInert), material='frictionless',pos=[0.0,0,0], noBound=False, resetState=True,fixed=True) b6.dynamic=False b6.state.pos = [-lengthOfBase/3.0,0.0,-lengthOfBase/3.0] O.bodies.append(b6) b7=Body() wire=False color=[0,255,0] highlight=False b7.shape=PotentialParticle(k=0.1, r=0.1*wallThickness, R=0.2*lengthOfBase,a=[1,-1,0,0,0,0], b=[0,0,1,-1,0,0], c=[0,0,0,0,1,-1], d=[lengthOfBase/6.0-r,lengthOfBase/6.0-r,0.5*wallThickness-r,0.5*wallThickness-r,lengthOfBase/6.0-r,lengthOfBase/6.0-r], id=count,isBoundary=True,isBoundaryPlane=[True,True,True,True,True,True],color=color ,wire=wire,highlight=highlight,AabbMinMax=True, minAabb=1.02*Vector3(lengthOfBase/6.0,0.4*wallThickness,lengthOfBase/6.0),maxAabb=1.02*Vector3(lengthOfBase/6.0,0.4*wallThickness,lengthOfBase/6.0),maxAabbRotated=1.02*Vector3(lengthOfBase/6.0,0.5*wallThickness,lengthOfBase/6.0),minAabbRotated=1.02*Vector3(lengthOfBase/6.0,0.5*wallThickness,lengthOfBase/6.0)) length=lengthOfBase V=lengthOfBase*lengthOfBase*wallThickness geomInert=(1./6.)*V*length*wallThickness utils._commonBodySetup(b7,V,Vector3(geomInert,geomInert,geomInert), material='frictionless',pos=[0.0,0,0], noBound=False, resetState=True,fixed=True) b7.dynamic=False b7.state.pos = [-lengthOfBase/3.0,0.0,0.0] O.bodies.append(b7) b8=Body() wire=False color=[0,255,0] highlight=False b8.shape=PotentialParticle(k=0.1, r=0.1*wallThickness, R=0.2*lengthOfBase,a=[1,-1,0,0,0,0], b=[0,0,1,-1,0,0], c=[0,0,0,0,1,-1], d=[lengthOfBase/6.0-r,lengthOfBase/6.0-r,0.5*wallThickness-r,0.5*wallThickness-r,lengthOfBase/6.0-r,lengthOfBase/6.0-r], id=count,isBoundary=True,isBoundaryPlane=[True,True,True,True,True,True],color=color ,wire=wire,highlight=highlight,AabbMinMax=True, minAabb=1.02*Vector3(lengthOfBase/6.0,0.4*wallThickness,lengthOfBase/6.0),maxAabb=1.02*Vector3(lengthOfBase/6.0,0.4*wallThickness,lengthOfBase/6.0),maxAabbRotated=1.02*Vector3(lengthOfBase/6.0,0.5*wallThickness,lengthOfBase/6.0),minAabbRotated=1.02*Vector3(lengthOfBase/6.0,0.5*wallThickness,lengthOfBase/6.0)) length=lengthOfBase V=lengthOfBase*lengthOfBase*wallThickness geomInert=(1./6.)*V*length*wallThickness utils._commonBodySetup(b8,V,Vector3(geomInert,geomInert,geomInert), material='frictionless',pos=[0.0,0,0], noBound=False, resetState=True,fixed=True) b8.dynamic=False b8.state.pos = [lengthOfBase/3.0,0.0,0.0] O.bodies.append(b8) bA=Body() wire=False color=[0,255,0] highlight=False bA.shape=PotentialParticle(k=0.1, r=0.1*wallThickness, R=0.5*heightOfBase,a=[1,-1,0,0,0,0], b=[0,0,1,-1,0,0], c=[0,0,0,0,1,-1], d=[0.5*wallThickness-r,0.5*wallThickness-r,0.5*heightOfBase-r,0.5*heightOfBase-r,0.5*lengthOfBase-r,0.5*lengthOfBase-r], id=count+1,isBoundary=True,isBoundaryPlane=[True,True,True,True,True,True],color=color ,wire=wire,highlight=highlight,AabbMinMax=True, minAabb=1.02*Vector3(0.3*wallThickness,0.5*heightOfBase,0.5*lengthOfBase),maxAabb=1.02*Vector3(0.3*wallThickness,0.5*heightOfBase,0.5*lengthOfBase),maxAabbRotated=1.02*Vector3(0.5*wallThickness,0.5*heightOfBase,0.5*lengthOfBase),minAabbRotated=1.02*Vector3(0.5*wallThickness,0.5*heightOfBase,0.5*lengthOfBase)) length=lengthOfBase V=lengthOfBase*lengthOfBase*wallThickness geomInert=(1./6.)*V*length*wallThickness utils._commonBodySetup(bA,V,Vector3(geomInert,geomInert,geomInert), material='frictionless',pos=[0.0,0,0], noBound=False, resetState=True,fixed=True) bA.dynamic=False bA.state.pos = [0.5*lengthOfBase,0.5*heightOfBase,0] O.bodies.append(bA) bB=Body() wire=False color=[0,255,0] highlight=False bB.shape=PotentialParticle(k=0.1, r=0.1*wallThickness, R=0.5*heightOfBase,a=[1,-1,0,0,0,0], b=[0,0,1,-1,0,0], c=[0,0,0,0,1,-1], d=[0.5*wallThickness-r,0.5*wallThickness-r,0.5*heightOfBase-r,0.5*heightOfBase-r,0.5*lengthOfBase-r,0.5*lengthOfBase-r], id=count+2,isBoundary=True,isBoundaryPlane=[True,True,True,True,True,True],color=color ,wire=wire,highlight=highlight,AabbMinMax=True, minAabb=1.02*Vector3(0.3*wallThickness,0.5*heightOfBase,0.5*lengthOfBase),maxAabb=1.02*Vector3(0.3*wallThickness,0.5*heightOfBase,0.5*lengthOfBase),maxAabbRotated=1.02*Vector3(0.5*wallThickness,0.5*heightOfBase,0.5*lengthOfBase),minAabbRotated=1.02*Vector3(0.5*wallThickness,0.5*heightOfBase,0.5*lengthOfBase)) length=lengthOfBase V=lengthOfBase*lengthOfBase*wallThickness geomInert=(1./6.)*V*length*wallThickness utils._commonBodySetup(bB,V,Vector3(geomInert,geomInert,geomInert), material='frictionless',pos=[0.0,0,0], noBound=False, resetState=True,fixed=True) bB.dynamic=False bB.state.pos = [-0.5*lengthOfBase,0.5*heightOfBase,0] O.bodies.append(bB) bC=Body() wire=False color=[0,255,0] highlight=False bC.shape=PotentialParticle(k=0.1, r=0.1*wallThickness, R=0.5*heightOfBase,a=[1,-1,0,0,0,0], b=[0,0,1,-1,0,0], c=[0,0,0,0,1,-1], d=[0.5*lengthOfBase-r,0.5*lengthOfBase-r,0.5*heightOfBase-r,0.5*heightOfBase-r,0.5*wallThickness-r,0.5*wallThickness-r], id=count+3,isBoundary=True,isBoundaryPlane=[True,True,True,True,True,True],color=color ,wire=wire,highlight=highlight,AabbMinMax=True, minAabb=1.02*Vector3(0.5*lengthOfBase,0.5*heightOfBase,0.3*wallThickness),maxAabb=1.02*Vector3(0.5*lengthOfBase,0.5*heightOfBase,0.3*wallThickness),maxAabbRotated=1.02*Vector3(0.5*lengthOfBase,0.5*heightOfBase,0.5*wallThickness),minAabbRotated=1.02*Vector3(0.5*lengthOfBase,0.5*heightOfBase,0.5*wallThickness)) length=lengthOfBase V=lengthOfBase*lengthOfBase*wallThickness geomInert=(1./6.)*V*length*wallThickness utils._commonBodySetup(bC,V,Vector3(geomInert,geomInert,geomInert), material='frictionless',pos=[0.0,0,0], noBound=False, resetState=True,fixed=True) bC.dynamic=False bC.state.pos = [0,0.5*heightOfBase,0.5*lengthOfBase] O.bodies.append(bC) bD=Body() wire=False color=[0,255,0] highlight=False bD.shape=PotentialParticle(k=0.1, r=0.1*wallThickness, R=0.5*heightOfBase,a=[1,-1,0,0,0,0], b=[0,0,1,-1,0,0], c=[0,0,0,0,1,-1], d=[0.5*lengthOfBase-r,0.5*lengthOfBase-r,0.5*heightOfBase-r,0.5*heightOfBase-r,0.5*wallThickness-r,0.5*wallThickness-r], id=count+4,isBoundary=True,isBoundaryPlane=[True,True,True,True,True,True],color=color ,wire=wire,highlight=highlight,AabbMinMax=True, minAabb=1.02*Vector3(0.5*lengthOfBase,0.5*heightOfBase,0.3*wallThickness),maxAabb=1.02*Vector3(0.5*lengthOfBase,0.5*heightOfBase,0.3*wallThickness),maxAabbRotated=1.02*Vector3(0.5*lengthOfBase,0.5*heightOfBase,0.5*wallThickness),minAabbRotated=1.02*Vector3(0.5*lengthOfBase,0.5*heightOfBase,0.5*wallThickness)) length=lengthOfBase V=lengthOfBase*lengthOfBase*wallThickness geomInert=(1./6.)*V*length*wallThickness utils._commonBodySetup(bD,V,Vector3(geomInert,geomInert,geomInert), material='frictionless',pos=[0.0,0,0], noBound=False, resetState=True,fixed=True) bD.dynamic=False bD.state.pos = [0.0,0.5*heightOfBase,-0.5*lengthOfBase] O.bodies.append(bD) escapeNo=0 def myAddPlotData(): global escapeNo global wallThickness global meanSize uf=utils.unbalancedForce() if isnan(uf): uf = 1.0 KE = utils.kineticEnergy() for b in O.bodies: if b.state.pos[1] < -5.0*meanSize and b.dynamic==True: escapeNo = escapeNo+1 O.bodies.erase(b.id) if O.iter>12000: removeLid() plot.addData(timeStep1=O.iter,timeStep2=O.iter,timeStep3=O.iter,timeStep4=O.iter,time=O.time,unbalancedForce=uf,kineticEn=KE,outsideNo=escapeNo) from yade import plot plot.plots={'timeStep1':('unbalancedForce'),'timeStep2':('kineticEn'),'time':('outsideNo')} O.engines=O.engines+[PyRunner(iterPeriod=10,command='myAddPlotData()')] def removeLid(): global lidID if (O.bodies[lidID]): O.bodies.erase(lidID) O.dt = 0.2*sqrt(O.bodies[0].state.mass*0.33333333/1.0e8) trunk-2018.02b/examples/ResetRandomPosition.py000066400000000000000000000035231324306050200213310ustar00rootroot00000000000000# -*- coding: utf-8 from yade import pack,export,qt import gts,os def Plane(v1,v2,v3,v4): pts = [ [Vector3(v1),Vector3(v2),Vector3(v3),Vector3(v4)] ] return pack.sweptPolylines2gtsSurface(pts,capStart=True,capEnd=True) # Parameters tc=0.001# collision time en=0.3 # normal restitution coefficient es=0.3 # tangential restitution coefficient frictionAngle=radians(35)# density=2700 # facets material facetMat=O.materials.append(ViscElMat(frictionAngle=frictionAngle,tc=tc,en=en,et=es)) # default spheres material dfltSpheresMat=O.materials.append(ViscElMat(density=density,frictionAngle=frictionAngle,tc=tc,en=en,et=es)) O.dt=.2*tc # time step Rs=0.02 # mean particle radius Rf=0.01 # dispersion (Rs±Rf*Rs) nSpheres=1000# number of particles # Create geometry pln=Plane( (-.5, -.5, 0), (.5, -.5, -.05), (.5, .5, 0), (-.5, .5, -.05) ); plnIds=O.bodies.append(pack.gtsSurface2Facets(pln,material=facetMat,color=(0,1,0))) fct=Plane( (-.25, -.25, .5), (.25, -.25, .5), (.25, .25, .5), (-.25, .25, .5) ); fctIds=O.bodies.append(pack.gtsSurface2Facets(fct,material=facetMat,color=(1,0,0),noBound=True)) # Create spheres sp=pack.SpherePack(); sp.makeCloud(Vector3(-.5, -.5, 0),Vector3(.5, .5, .2), Rs, Rf, int(nSpheres), False) spheres=O.bodies.append([sphere(s[0],s[1],color=(0.929,0.412,0.412),material=dfltSpheresMat) for s in sp]) # Create engines O.engines=[ ForceResetter(), InsertionSortCollider([Bo1_Sphere_Aabb(),Bo1_Facet_Aabb()]), InteractionLoop( [Ig2_Sphere_Sphere_ScGeom(), Ig2_Facet_Sphere_ScGeom()], [Ip2_ViscElMat_ViscElMat_ViscElPhys()], [Law2_ScGeom_ViscElPhys_Basic()], ), NewtonIntegrator(damping=0,gravity=[0,0,-9.81]), ResetRandomPosition(virtPeriod=0.01,factoryFacets=fctIds,velocity=(0,0,-2),subscribedBodies=spheres,point=(0,0,-.5),normal=(0,0,1),maxAttempts=100), ] renderer = qt.Renderer() qt.View() O.saveTmp() O.run() trunk-2018.02b/examples/ViscElMatchMaker.py000066400000000000000000000053301324306050200205010ustar00rootroot00000000000000#!/usr/bin/env python # encoding: utf-8 # This example shows, how matchmaker can be used to # set the parameters of ViscoElastic model. from yade import utils, plot o = Omega() fr = 0.5;rho=2000 tc = 0.001; en = 0.5; et = 0.5; o.dt = 0.002*tc r1 = 0.002381 r2 = 0.002381 mat1 = O.materials.append(ViscElMat(frictionAngle=fr,tc=tc,en=en,et=et,density=rho)) mat2 = O.materials.append(ViscElMat(frictionAngle=fr,tc=tc,en=en,et=et,density=rho)) mat3 = O.materials.append(ViscElMat(frictionAngle=fr,tc=tc,en=en,et=et,density=rho)) id11 = O.bodies.append(sphere(center=[0,0,0],radius=r1,material=mat1,fixed=True,color=[0,0,1])) id12 = O.bodies.append(sphere(center=[0,0,(r1+r2)],radius=r2,material=mat2,fixed=False,color=[0,0,1])) id21 = O.bodies.append(sphere(center=[3*r1,0,0],radius=r1,material=mat1,fixed=True,color=[0,1,0])) id22 = O.bodies.append(sphere(center=[3*r1,0,(r1+r2)],radius=r2,material=mat3,fixed=False,color=[0,1,0])) id31 = O.bodies.append(sphere(center=[6*r1,0,0],radius=r1,material=mat2,fixed=True,color=[1,0,0])) id32 = O.bodies.append(sphere(center=[6*r1,0,(r1+r2)],radius=r2,material=mat3,fixed=False,color=[1,0,0])) o.engines = [ ForceResetter(), InsertionSortCollider([Bo1_Sphere_Aabb()],verletDist=(r1+r2)*5.0), InteractionLoop( [Ig2_Sphere_Sphere_ScGeom()], [Ip2_ViscElMat_ViscElMat_ViscElPhys( en=MatchMaker(matches=((mat1,mat2,.9),(mat1,mat3,.5),(mat2,mat3,.1))), # Set parameters et=MatchMaker(matches=((mat1,mat2,.9),(mat1,mat3,.5),(mat2,mat3,.1))), frictAngle=MatchMaker(matches=((mat1,mat2,.1),(mat1,mat3,.2),(mat2,mat3,.3))) )], [Law2_ScGeom_ViscElPhys_Basic()], ), NewtonIntegrator(damping=0,gravity=[0,0,-9.81]), PyRunner(command='addPlotData()',iterPeriod=100), ] vel=-0.50 O.bodies[id12].state.vel=[0,0,vel] O.bodies[id22].state.vel=[0,0,vel] O.bodies[id32].state.vel=[0,0,vel] def addPlotData(): s1 = (O.bodies[id12].state.pos[2]-O.bodies[id11].state.pos[2])-(r1+r2) s2 = (O.bodies[id22].state.pos[2]-O.bodies[id11].state.pos[2])-(r1+r2) s3 = (O.bodies[id32].state.pos[2]-O.bodies[id11].state.pos[2])-(r1+r2) plot.addData(mat1mat2=s1,mat1mat3=s2,mat2mat3=s3,it=O.iter) plot.plots={'it':('mat1mat2','mat1mat3','mat2mat3')}; plot.plot() O.step() from yade import qt qt.View() print "Friction coefficient for id11 and id12 is %g"%(math.atan(O.interactions[id11,id12].phys.tangensOfFrictionAngle)) print "Friction coefficient for id21 and id22 is %g"%(math.atan(O.interactions[id21,id22].phys.tangensOfFrictionAngle)) print "Friction coefficient for id31 and id32 is %g"%(math.atan(O.interactions[id31,id32].phys.tangensOfFrictionAngle)) O.run(100000, True) #plot.saveGnuplot('sim-data_') trunk-2018.02b/examples/WireMatPM/000077500000000000000000000000001324306050200166115ustar00rootroot00000000000000trunk-2018.02b/examples/WireMatPM/wirecontacttest.py000066400000000000000000000123041324306050200224050ustar00rootroot00000000000000# -*- coding: utf-8 -*- from yade import plot, qt #### define parameters for the net # wire diameter d = 2.7/1000. # particle radius radius = d*4. # define piecewise linear stress-strain curve [Pa] strainStressValues=[(0.0019230769,2.5e8),(0.0192,3.2195e8),(0.05,3.8292e8),(0.15,5.1219e8),(0.25,5.5854e8),(0.3,5.6585e8),(0.35,5.6585e8)] # elastic material properties particleVolume = 4./3.*pow(radius,3)*pi particleMass = 3.9/1000. density = particleMass/particleVolume young = strainStressValues[0][1] / strainStressValues[0][0] poisson = 0.3 #### material definition netMat = O.materials.append( WireMat( young=young,poisson=poisson,frictionAngle=radians(30),density=density,isDoubleTwist=True,diameter=d,strainStressValues=strainStressValues,lambdaEps=0.4,lambdak=0.66) ) wireMat = O.materials.append( WireMat( young=young,poisson=poisson,frictionAngle=radians(30),density=density,isDoubleTwist=False,diameter=3.4/1000,strainStressValues=strainStressValues ) ) blocMat = O.materials.append(FrictMat(young=60e3,poisson=0.15,frictionAngle=radians(30),density=44.5/((4./3.*pi*0.02**3)*1576.))) #### define parameters for the net packing # mesh geometry mos = 0.08 a = 0.04 b = 0.04 # wire diameter d = 2.7/1000. # net dimension cornerCoord=[0,0,0] Lx = 2. Ly = 2. # properties of particles kw = {'color':[0,1,0],'wire':True,'highlight':False,'fixed':False,'material':netMat} ##### create packing for net [netpack,lx,ly] = hexaNet( radius=radius, cornerCoord=cornerCoord, xLength=Lx, yLength=Ly, mos=mos, a=a, b=b, startAtCorner=True, isSymmetric=False, **kw ) O.bodies.append(netpack) #### get bodies for single wire at the boundary in y-direction and change properties bb = uniaxialTestFeatures(axis=0) negIds,posIds=bb['negIds'],bb['posIds'] for id in negIds: O.bodies[id].material = O.materials[wireMat] O.bodies[id].shape.color = [0,0,1] for id in posIds: O.bodies[id].material = O.materials[wireMat] O.bodies[id].shape.color = [0,0,1] #### define engines to create link interactionRadius=2.8 # value has to be adjusted according to the particle size of the net and the mesh opening size of the net (check always if links are created) O.engines=[ ForceResetter(), InsertionSortCollider([Bo1_Sphere_Aabb(aabbEnlargeFactor=interactionRadius,label='aabb')]), InteractionLoop( [Ig2_Sphere_Sphere_ScGeom(interactionDetectionFactor=interactionRadius,label='Ig2ssGeom')], [Ip2_WireMat_WireMat_WirePhys(linkThresholdIteration=1,label='wire_wire'),Ip2_FrictMat_FrictMat_FrictPhys(label='block_wire')], [Law2_ScGeom_WirePhys_WirePM(linkThresholdIteration=1,label='Law_1'),Law2_ScGeom_FrictPhys_CundallStrack(label='Law_2')] ), NewtonIntegrator(damping=0.), ] #### define additional vertical interactions at the boundary for boundary wire for i in range(24)[1::2]: # odd - start at second item and take every second item createInteraction(negIds[i],negIds[i+1]) del posIds[1] posIds.append(1) for i in range(25)[::2]: # even - start at the beginning at take every second item createInteraction(posIds[i],posIds[i+1]) #### time step definition for first time step to create links O.step() ##### delete horizontal interactions for corner particles bb = uniaxialTestFeatures(axis=1) negIds,posIds,axis,crossSectionArea=bb['negIds'],bb['posIds'],bb['axis'],bb['area'] ##### delete some interactions O.interactions.erase(0,50) O.interactions.erase(0,51) O.interactions.erase(1,1250) O.interactions.erase(1,1251) #### time step definition for deleting some links which have been created by the Ig2 functor O.step() #### initializes now the interaction detection factor aabb.aabbEnlargeFactor=-1. Ig2ssGeom.interactionDetectionFactor=-1. #### define boundary conditions fixedIds=negIds movingIds=posIds for id in fixedIds: O.bodies[id].shape.color = [1,0,0] O.bodies[id].state.blockedDOFs='xyzXYZ' for id in movingIds: O.bodies[id].shape.color = [1,0,0] O.bodies[id].state.blockedDOFs='xyzXYZ' #### import block as a sphere after net has been created bloc=O.bodies.append(sphere([1.0,1.0,0.65],radius=0.15,wire=False,highlight=False,color=[1,1,0],material=blocMat)) O.bodies[bloc].state.isDamped=False # switch damping off since free fall under gravity #### plot some results plot.plots={'t':['vz',None,('f_unbal','g--')]} #plot.liveInterval=2. plot.plot(noShow=False, subPlots=False) def addPlotData(): plot.addData(t=O.time, vz=-O.bodies[bloc].state.vel[2], f_unbal=unbalancedForce(useMaxForce=False) ) #### define engines for simulation v = qt.Controller() v = qt.View() rr = qt.Renderer() rr.intrAllWire = True O.engines=[ ForceResetter(), InsertionSortCollider([Bo1_Sphere_Aabb(label='aabb')]), InteractionLoop( [Ig2_Sphere_Sphere_ScGeom(label='Ig2ssGeom')], [Ip2_WireMat_WireMat_WirePhys(label='wire_wire'),Ip2_FrictMat_FrictMat_FrictPhys(label='block_wire')], [Law2_ScGeom_WirePhys_WirePM(label='Law_1'),Law2_ScGeom_FrictPhys_CundallStrack(label='Law_2')] ), NewtonIntegrator(damping=0.2,gravity=[0,0,-9.81],label='NewtonGravity'), PyRunner(initRun=True,iterPeriod=100,command='addPlotData()'), ] #### time step definition for simulation ## critical time step proposed by Bertrand kn = 16115042 # stiffness of single wire from code, has to be changed if you change the stress-strain curve for the wire O.dt = 0.2*sqrt(particleMass/(2.*kn)) O.run(200000) trunk-2018.02b/examples/WireMatPM/wirepackings.py000066400000000000000000000061171324306050200216560ustar00rootroot00000000000000# encoding: utf-8 from yade import qt #### short description of script print 'This script shows the use of the function hexaNet of module pack (interactions are not initialised)' #### define parameters for the net # mesh geometry mos = 0.08 a = 0.04 b = 0.04 # wire diameter d = 2.7/1000. # net dimension Lx = 0.2 Ly = 0.15 # properties of particles radius = d*4. #### startAtCorner=True kw = {'color':[1,1,0],'wire':True,'highlight':False,'fixed':False,'material':-1} #### packing 1 [netpack,lx,ly] = hexaNet( radius=radius, cornerCoord=[0,0,0], xLength=Lx, yLength=Ly, mos=mos, a=a, b=b, startAtCorner=True, isSymmetric=True, **kw ) O.bodies.append(netpack) print 'Packing 1:' print 'Real net length in x-direction [m]: ', lx print 'Real net length in y-direction [m]: ', ly #### packing 2 [netpack,lx,ly] = hexaNet( radius=radius, cornerCoord=[0.4,0,0], xLength=Lx, yLength=Ly, mos=mos, a=a, b=b, startAtCorner=True, isSymmetric=False, **kw ) O.bodies.append(netpack) print 'Packing 2:' print 'Real net length in x-direction [m]: ', lx print 'Real net length in y-direction [m]: ', ly #### packing 3 [netpack,lx,ly] = hexaNet( radius=radius, cornerCoord=[0,-0.4,0], xLength=Lx, yLength=Ly+0.05, mos=mos, a=a, b=b, startAtCorner=True, isSymmetric=True, **kw ) O.bodies.append(netpack) print 'Packing 3:' print 'Real net length in x-direction [m]: ', lx print 'Real net length in y-direction [m]: ', ly #### packing 4 [netpack,lx,ly] = hexaNet( radius=radius, cornerCoord=[0.4,-0.4,0], xLength=Lx, yLength=Ly+0.05, mos=mos, a=a, b=b, startAtCorner=True, isSymmetric=False, **kw ) O.bodies.append(netpack) print 'Packing 4:' print 'Real net length in x-direction [m]: ', lx print 'Real net length in y-direction [m]: ', ly #### startAtCorner=False kw = {'color':[1,0,0],'wire':True,'highlight':False,'fixed':False,'material':-1} #### packing 1 [netpack,lx,ly] = hexaNet( radius=radius, cornerCoord=[1,0,0], xLength=Lx, yLength=Ly, mos=mos, a=a, b=b, startAtCorner=False, isSymmetric=True, **kw ) O.bodies.append(netpack) print 'Packing 1:' print 'Real net length in x-direction [m]: ', lx print 'Real net length in y-direction [m]: ', ly #### packing 2 [netpack,lx,ly] = hexaNet( radius=radius, cornerCoord=[1.4,0,0], xLength=Lx, yLength=Ly, mos=mos, a=a, b=b, startAtCorner=False, isSymmetric=False, **kw ) O.bodies.append(netpack) print 'Packing 2:' print 'Real net length in x-direction [m]: ', lx print 'Real net length in y-direction [m]: ', ly #### packing 3 [netpack,lx,ly] = hexaNet( radius=radius, cornerCoord=[1,-0.4,0], xLength=Lx, yLength=Ly+0.05, mos=mos, a=a, b=b, startAtCorner=False, isSymmetric=True, **kw ) O.bodies.append(netpack) print 'Packing 3:' print 'Real net length in x-direction [m]: ', lx print 'Real net length in y-direction [m]: ', ly #### packing 4 [netpack,lx,ly] = hexaNet( radius=radius, cornerCoord=[1.4,-0.4,0], xLength=Lx, yLength=Ly+0.05, mos=mos, a=a, b=b, startAtCorner=False, isSymmetric=False, **kw ) O.bodies.append(netpack) print 'Packing 4:' print 'Real net length in x-direction [m]: ', lx print 'Real net length in y-direction [m]: ', ly ## to see it v=qt.Controller() v=qt.View() trunk-2018.02b/examples/WireMatPM/wiretensiltest.py000066400000000000000000000112021324306050200222440ustar00rootroot00000000000000# -*- coding: utf-8 -*- from yade import plot #### short description of script print 'This script shows a tensile test of a net by using the UniaxialStrainer' #### define parameters for the net # wire diameter d = 2.7/1000. # particle radius radius = d*4. # define piecewise lineare stress-strain curve strainStressValues=[(0.0019230769,2.5e8),(0.0192,3.2195e8),(0.05,3.8292e8),(0.15,5.1219e8),(0.25,5.5854e8),(0.3,5.6585e8),(0.35,5.6585e8)] # elastic material properties particleVolume = 4./3.*pow(radius,3)*pi particleMass = 3.9/1000. density = particleMass/particleVolume young = strainStressValues[0][1] / strainStressValues[0][0] poisson = 0.3 #### material definition netMat = O.materials.append( WireMat( young=young,poisson=poisson,frictionAngle=radians(30),density=density,isDoubleTwist=True,diameter=d,strainStressValues=strainStressValues,lambdaEps=0.4,lambdak=0.66) ) wireMat = O.materials.append( WireMat( young=young,poisson=poisson,frictionAngle=radians(30),density=density,isDoubleTwist=False,diameter=3.4/1000,strainStressValues=strainStressValues ) ) #### get net packing kw = {'color':[1,1,0],'wire':True,'highlight':False,'fixed':False,'material':netMat} [netpack,lx,ly] = hexaNet( radius=radius, cornerCoord=[0,0,0], xLength=1.0, yLength=0.55, mos=0.08, a=0.04, b=0.04, startAtCorner=False, isSymmetric=True, **kw ) O.bodies.append(netpack) print 'Real net length in x-direction [m]: ', lx print 'Real net length in y-direction [m]: ', ly #### get bodies for single wire at the boundary in y-direction and change properties bb = uniaxialTestFeatures(axis=0) negIds,posIds=bb['negIds'],bb['posIds'] for id in negIds: O.bodies[id].material = O.materials[wireMat] O.bodies[id].shape.color = [0,0,1] for id in posIds: O.bodies[id].material = O.materials[wireMat] O.bodies[id].shape.color = [0,0,1] #### define engines to create link interactionRadius=2.8 O.engines=[ ForceResetter(), InsertionSortCollider([Bo1_Sphere_Aabb(aabbEnlargeFactor=interactionRadius,label='aabb')]), InteractionLoop( [Ig2_Sphere_Sphere_ScGeom(interactionDetectionFactor=interactionRadius,label='Ig2ssGeom')], [Ip2_WireMat_WireMat_WirePhys(linkThresholdIteration=1,label='interactionPhys')], [Law2_ScGeom_WirePhys_WirePM(linkThresholdIteration=1,label='interactionLaw')] ), NewtonIntegrator(damping=0.), ] #### define additional vertical interactions at the boundary createInteraction(negIds[0],negIds[2]) createInteraction(negIds[3],negIds[4]) createInteraction(negIds[5],negIds[6]) createInteraction(negIds[7],negIds[1]) createInteraction(posIds[0],posIds[2]) createInteraction(posIds[3],posIds[4]) createInteraction(posIds[5],posIds[6]) createInteraction(posIds[7],posIds[1]) #### time step definition for first time step to create links O.step() #### initialize values for UniaxialStrainer bb = uniaxialTestFeatures(axis=1) negIds,posIds,axis,crossSectionArea=bb['negIds'],bb['posIds'],bb['axis'],bb['area'] strainRateTension = 0.1 setSpeeds = True ##### delete horizontal interactions for corner particles bb = uniaxialTestFeatures(axis=1) negIds,posIds,axis,crossSectionArea=bb['negIds'],bb['posIds'],bb['axis'],bb['area'] ##### delete some interactions O.interactions.erase(0,4) O.interactions.erase(0,5) O.interactions.erase(1,154) O.interactions.erase(1,155) O.interactions.erase(2,26) O.interactions.erase(2,27) O.interactions.erase(3,176) O.interactions.erase(3,177) #### time step definition for deleting some links which have been created by the Ig2 functor O.step() #### initializes now the interaction detection factor aabb.aabbEnlargeFactor=-1. Ig2ssGeom.interactionDetectionFactor=-1. #### define engines for simulation with UniaxialStrainer O.engines = O.engines[:3] + [ UniaxialStrainer(strainRate=strainRateTension,axis=axis,asymmetry=1,posIds=posIds,negIds=negIds,crossSectionArea=crossSectionArea,blockDisplacements=True,blockRotations=False,setSpeeds=setSpeeds,label='strainer'), NewtonIntegrator(damping=0.5), PyRunner(initRun=True,iterPeriod=1000,command='addPlotData()'), ] #### plot some results plot.plots={'un':('Fn',)} plot.plot(noShow=False, subPlots=False) def addPlotData(): Fn = 0. for i in posIds: try: inter=O.interactions.withBody(i)[0] F = abs(inter.phys.normalForce[1]) except: F = 0 Fn += F un = O.bodies[O.bodies[posIds[0]].id].state.pos[1] - O.bodies[O.bodies[posIds[0]].id].state.refPos[1] if un > 0.10: O.pause() plot.addData( un=un*1000, Fn=Fn/1000 ) #### time step definition for simulation ## critical time step proposed by Bertrand kn = 16115042 # stiffness of single wire from code O.dt = 0.2*sqrt(particleMass/(2.*kn)) #### to see it from yade import qt v = qt.Controller() v = qt.View() rr = qt.Renderer() rr.intrAllWire = True trunk-2018.02b/examples/adaptiveintegrator/000077500000000000000000000000001324306050200207005ustar00rootroot00000000000000trunk-2018.02b/examples/adaptiveintegrator/simple-scene-plot-NewtonIntegrator.py000066400000000000000000000037531324306050200301310ustar00rootroot00000000000000#!/usr/bin/python # -*- coding: utf-8 -*- import matplotlib matplotlib.use('TkAgg') O.engines=[ ForceResetter(), InsertionSortCollider([Bo1_Sphere_Aabb(),Bo1_Box_Aabb()]), InteractionLoop( [Ig2_Sphere_Sphere_ScGeom(),Ig2_Box_Sphere_ScGeom()], [Ip2_FrictMat_FrictMat_FrictPhys()], [Law2_ScGeom_FrictPhys_CundallStrack()] ), NewtonIntegrator(damping=0.0,gravity=(0,0,-9.81)), ### ### NOTE this extra engine: ### ### You want snapshot to be taken every 1 sec (realTimeLim) or every 50 iterations (iterLim), ### whichever comes soones. virtTimeLim attribute is unset, hence virtual time period is not taken into account. PyRunner(iterPeriod=20,command='myAddPlotData()') ] O.bodies.append(box(center=[0,0,0],extents=[.5,.5,.5],fixed=True,color=[1,0,0])) O.bodies.append(sphere([0,0,2],1,color=[0,1,0])) O.dt=.002*PWaveTimeStep() ############################################ ##### now the part pertaining to plots ##### ############################################ from yade import plot ## we will have 2 plots: ## 1. t as function of i (joke test function) ## 2. i as function of t on left y-axis ('|||' makes the separation) and z_sph, v_sph (as green circles connected with line) and z_sph_half again as function of t plot.plots={'i':('t'),'t':('z_sph',None,('v_sph','go-'),'z_sph_half')} ## this function is called by plotDataCollector ## it should add data with the labels that we will plot ## if a datum is not specified (but exists), it will be NaN and will not be plotted def myAddPlotData(): sph=O.bodies[1] ## store some numbers under some labels plot.addData(t=O.time,i=O.iter,z_sph=sph.state.pos[2],z_sph_half=.5*sph.state.pos[2],v_sph=sph.state.vel.norm()) print "Now calling plot.plot() to show the figures. The timestep is artificially low so that you can watch graphs being updated live." plot.liveInterval=.2 plot.plot(subPlots=False) O.run(int(5./O.dt)); #plot.saveGnuplot('/tmp/a') ## you can also access the data in plot.data['i'], plot.data['t'] etc, under the labels they were saved. trunk-2018.02b/examples/adaptiveintegrator/simple-scene-plot-RungeKuttaCashKarp54.py000066400000000000000000000047651324306050200305030ustar00rootroot00000000000000#!/usr/bin/python # Burak ER # burak.er@btu.edu.tr # github.com/burak-er # Mechanical Engineering Department # Bursa Technical University # # -*- coding: utf-8 -*- import matplotlib matplotlib.use('TkAgg') # Use an integrator engine that is derived from the interface Integrator. #RungeKuttaCashKarp54Integrator integrator performs one step of simulation for the given tolerances. Whether the time step is given, it completes it then stops. integrator=RungeKuttaCashKarp54Integrator([ ForceResetter(), GeneralIntegratorInsertionSortCollider([Bo1_Sphere_Aabb(),Bo1_Box_Aabb()]), InteractionLoop( [Ig2_Sphere_Sphere_ScGeom(),Ig2_Box_Sphere_ScGeom()], [Ip2_FrictMat_FrictMat_FrictPhys()], [Law2_ScGeom_FrictPhys_CundallStrack()] ), GravityEngine(gravity=Vector3(0,0,-9.81)), PyRunner(virtPeriod=1e-99,command='myAddPlotData()')#use virtPeriod on this integrator. ]); #Tolerances can be set for the optimum accuracy integrator.rel_err=1e-6; integrator.abs_err=1e-6; O.engines=[integrator, ] O.bodies.append(box(center=[0,0,0],extents=[.5,.5,.5],fixed=True,color=[1,0,0])) O.bodies.append(sphere([0,0,2],1,color=[0,1,0])) O.dt=1e-2# this signifies the endpoint. It is not much important for the accuracy of the integration where accuracy is defined by rel_err and abs_err of the integrator. ############################################ ##### now the part pertaining to plots ##### ############################################ from yade import plot ## we will have 2 plots: ## 1. t as function of i (joke test function) ## 2. i as function of t on left y-axis ('|||' makes the separation) and z_sph, v_sph (as green circles connected with line) and z_sph_half again as function of t plot.plots={'i':('t'),'t':('z_sph',None,('v_sph','go-'),'z_sph_half')} ## this function is called by plotDataCollector ## it should add data with the labels that we will plot ## if a datum is not specified (but exists), it will be NaN and will not be plotted def myAddPlotData(): sph=O.bodies[1] ## store some numbers under some labels plot.addData(t=O.time,i=O.iter,z_sph=sph.state.pos[2],z_sph_half=.5*sph.state.pos[2],v_sph=sph.state.vel.norm()) print "Now calling plot.plot() to show the figures. The timestep is artificially low so that you can watch graphs being updated live." plot.liveInterval=.2 plot.plot(subPlots=False) print "Number of threads ", os.environ['OMP_NUM_THREADS'] O.run(int(5./O.dt)); #plot.saveGnuplot('/tmp/a') ## you can also access the data in plot.data['i'], plot.data['t'] etc, under the labels they were saved. trunk-2018.02b/examples/agglomerate/000077500000000000000000000000001324306050200172735ustar00rootroot00000000000000trunk-2018.02b/examples/agglomerate/README000066400000000000000000000013631324306050200201560ustar00rootroot00000000000000A series of scripts that prepare and agglomerate packing, i.e. cohesive elastic aggregates, each composed of dense particle packing A general idea: - create a "macro" particles as large spheres using makeCloud - "mesh" these macroparticles with smaller spheres - run compression to make the loose packing denser - save the result - use the resulting packing in actual simulation Scripts in order to run: - makeCloud.py ... a script to prepare loose packing of macro particles using makeCloud - divide.py ... a script to divide the macro particles from previous step into clump of particles - compress.py ... a script to create dense packing of agglomerates - simulation.py ... use of prepared dense packing of agglomerates in a simple simulation trunk-2018.02b/examples/agglomerate/compress.py000066400000000000000000000032321324306050200215000ustar00rootroot00000000000000###################################################################### # Compress the loose packing into dense packing. Each agglomerate is # considered as clump in this stage ###################################################################### from yade import export,ymport import random random.seed(1) # add walls first dim = (15,15,15) walls = aabbWalls(((0,0,0),(dim))) wallIds = O.bodies.append(walls) # load spheres from file, including information of their agglomerates ids attrs = [] sp = ymport.textExt('/tmp/divided.txt',format='x_y_z_r_attrs',attrs=attrs) n = max(int(a[0]) for a in attrs)+1 colors = [randomColor() for _ in xrange(n)] agglomerates = [[] for _ in xrange(n)] for s,a in zip(sp,attrs): aa = int(a[0]) s.agglomerate = aa s.shape.color = colors[aa] agglomerates[aa].append(s) for a in agglomerates: O.bodies.appendClumped(a) O.engines = [ ForceResetter(), InsertionSortCollider([Bo1_Sphere_Aabb(),Bo1_Box_Aabb()]), InteractionLoop( [Ig2_Sphere_Sphere_ScGeom(),Ig2_Box_Sphere_ScGeom()], [Ip2_FrictMat_FrictMat_FrictPhys()], [Law2_ScGeom_FrictPhys_CundallStrack()] ), TriaxialStressController( thickness = 0, stressMask = 7, internalCompaction = False, label = 'compressor', ), NewtonIntegrator(damping=.6), ] O.dt = PWaveTimeStep() # compress the sample compressor.goal1 = compressor.goal2 = compressor.goal3 = -1e-7 O.run(50000,True) compressor.goal1 = compressor.goal2 = compressor.goal3 = -1e-5 O.run(30000,True) # save the result, including information of agglomerates which the particle belongs to export.textExt('/tmp/compressed.txt','x_y_z_r_attrs',attrs=['b.agglomerate']) try: from yade import qt qt.View() except: pass trunk-2018.02b/examples/agglomerate/divide.py000066400000000000000000000022311324306050200211070ustar00rootroot00000000000000###################################################################### # A script for subdivision of macro perticles into a dense packing of # smaller particles # # Each aggragate is a dense packing, but macroscopically the packing # is loose ###################################################################### from yade import export,ymport import random random.seed(1) # to make colors always the same # load macroparticles sp = ymport.text('/tmp/cloud.txt') colors = [randomColor() for s in sp] # each macroparticle is filled randomDensePack for si,s in enumerate(sp): sphere = pack.inSphere(s.state.pos, s.shape.radius) sp1 = pack.randomDensePack( sphere, spheresInCell = 500, radius = .2, memoizeDb = '/tmp/agglomeratepackaux.db', returnSpherePack = True, ) ids = sp1.toSimulation(color=colors[si]) # add the result to simulation with uniform color for i in ids: O.bodies[i].agglomerate = si # tell each particle who is its agglomerate # save the result, including information of agglomerates which the particle belongs to export.textExt('/tmp/divided.txt','x_y_z_r_attrs',attrs=['b.agglomerate']) try: from yade import qt qt.View() except: pass trunk-2018.02b/examples/agglomerate/makeCloud.py000066400000000000000000000010211324306050200215430ustar00rootroot00000000000000###################################################################### # A script for prepare loose packing of macro particles ###################################################################### from yade import export dim = (15,15,15) # dimensions for makeCloud radius = 1 fuzz = 0.2 # use of makeCloud function sp = pack.SpherePack() sp.makeCloud((0,0,0), dim, rMean=radius, rRelFuzz=fuzz, seed=1) sp.toSimulation() # save the result export.text('/tmp/cloud.txt') try: from yade import qt qt.View() except: pass trunk-2018.02b/examples/agglomerate/simulation.py000066400000000000000000000033151324306050200220330ustar00rootroot00000000000000###################################################################### # Gravity deposition as a simple simulation illustrating creation of # cohesive contacts and deleting the unwanted between different # agglomerates # # Using CpmMat, but the same procedure can be used with any material ###################################################################### from yade import ymport wall = O.bodies.append(wall((0,0,3),2)) O.materials.append(CpmMat(neverDamage=True,frictionAngle=0)) # load spheres from file, including information of their agglomerates ids attrs = [] sp = ymport.textExt('/tmp/compressed.txt',format='x_y_z_r_attrs',attrs=attrs) n = max(int(a[0]) for a in attrs)+1 colors = [randomColor() for _ in xrange(n)] for s,a in zip(sp,attrs): aa = int(a[0]) s.agglomerate = aa s.shape.color = colors[aa] O.bodies.append(sp) factor=1.5 O.engines=[ ForceResetter(), InsertionSortCollider([Bo1_Sphere_Aabb(aabbEnlargeFactor=factor,label='bo1aabbs'),Bo1_Wall_Aabb()]), InteractionLoop( [Ig2_Sphere_Sphere_ScGeom(interactionDetectionFactor=factor,label='ig2sss'),Ig2_Wall_Sphere_ScGeom()], [Ip2_FrictMat_FrictMat_FrictPhys(),Ip2_CpmMat_CpmMat_CpmPhys(cohesiveThresholdIter=1)], [Law2_ScGeom_FrictPhys_CundallStrack(),Law2_ScGeom_CpmPhys_Cpm()] ), NewtonIntegrator(gravity=(0,0,-30)), ] O.dt = PWaveTimeStep() # create cohesive interactions, possible also between different agglomerates O.step() ig2sss.interactionDetectionFactor = bo1aabbs.aabbEnlargeFactor = 1 # delete the inter-agglomerate interactions for i in O.interactions: b1,b2 = [O.bodies[ii] for ii in (i.id1,i.id2)] if b1.agglomerate != b2.agglomerate: O.interactions.erase(i.id1,i.id2) O.step() try: from yade import qt qt.View() except: pass trunk-2018.02b/examples/baraban/000077500000000000000000000000001324306050200163725ustar00rootroot00000000000000trunk-2018.02b/examples/baraban/BicyclePedalEngine.py000066400000000000000000000035221324306050200224140ustar00rootroot00000000000000#!/usr/bin/python # -*- coding: utf-8 -*- import time ## PhysicalParameters Density=2400 frictionAngle=radians(35) tc = 0.001 en = 0.3 es = 0.3 ## Import wall's geometry facetMat=O.materials.append(ViscElMat(frictionAngle=frictionAngle,tc=tc,en=en,et=es)) # **params sets kn, cn, ks, cs sphereMat=O.materials.append(ViscElMat(density=Density,frictionAngle=frictionAngle,tc=tc,en=en,et=es)) from yade import ymport fctIds=O.bodies.append(ymport.stl('baraban.stl',color=(1,0,0),material=facetMat)) ## Spheres sphereRadius = 0.2 nbSpheres = (10,10,10) #nbSpheres = (50,50,50) for i in xrange(nbSpheres[0]): for j in xrange(nbSpheres[1]): for k in xrange(nbSpheres[2]): x = (i*2 - nbSpheres[0])*sphereRadius*1.1 y = (j*2 - nbSpheres[1])*sphereRadius*1.1 z = (k*2 - nbSpheres[2])*sphereRadius*1.1 s=sphere([x,y,z],sphereRadius,material=sphereMat) O.bodies.append(s) ## Timestep O.dt=.2*tc ## Engines O.engines=[ ## Resets forces and momenta the act on bodies ForceResetter(), ## Using bounding boxes find possible body collisions. InsertionSortCollider([Bo1_Sphere_Aabb(),Bo1_Facet_Aabb()]), ## Interactions InteractionLoop( ## Create geometry information about each potential collision. [Ig2_Sphere_Sphere_ScGeom(), Ig2_Facet_Sphere_ScGeom()], ## Create physical information about the interaction. [Ip2_ViscElMat_ViscElMat_ViscElPhys()], ## Constitutive law [Law2_ScGeom_ViscElPhys_Basic()], ), ## Apply gravity ## Cundall damping must been disabled! NewtonIntegrator(damping=0,gravity=[0,-9.81,0]), ## Saving results #VTKRecorder(virtPeriod=0.04,fileName='/tmp/stlimp-',recorders=['spheres','facets']), ## Apply kinematics to walls BicyclePedalEngine(ids=fctIds,rotationAxis=[0,0,1],radius = 1.2,angularVelocity=4.0) ] from yade import qt qt.View() #O.saveTmp() #O.run() trunk-2018.02b/examples/baraban/baraban.py000066400000000000000000000034701324306050200203360ustar00rootroot00000000000000#!/usr/bin/python # -*- coding: utf-8 -*- import time ## PhysicalParameters Density=2400 frictionAngle=radians(35) tc = 0.001 en = 0.3 es = 0.3 ## Import wall's geometry facetMat=O.materials.append(ViscElMat(frictionAngle=frictionAngle,tc=tc,en=en,et=es)) sphereMat=O.materials.append(ViscElMat(density=Density,frictionAngle=frictionAngle,tc=tc,en=en,et=es)) from yade import ymport fctIds=O.bodies.append(ymport.stl('baraban.stl',color=(1,0,0),material=facetMat)) ## Spheres sphereRadius = 0.2 nbSpheres = (10,10,10) #nbSpheres = (50,50,50) for i in xrange(nbSpheres[0]): for j in xrange(nbSpheres[1]): for k in xrange(nbSpheres[2]): x = (i*2 - nbSpheres[0])*sphereRadius*1.1 y = (j*2 - nbSpheres[1])*sphereRadius*1.1 z = (k*2 - nbSpheres[2])*sphereRadius*1.1 s=sphere([x,y,z],sphereRadius,material=sphereMat) O.bodies.append(s) ## Timestep O.dt=.2*tc ## Engines O.engines=[ ## Resets forces and momenta the act on bodies ForceResetter(), ## Using bounding boxes find possible body collisions. InsertionSortCollider([Bo1_Sphere_Aabb(),Bo1_Facet_Aabb()]), ## Interactions InteractionLoop( ## Create geometry information about each potential collision. [Ig2_Sphere_Sphere_ScGeom(), Ig2_Facet_Sphere_ScGeom()], ## Create physical information about the interaction. [Ip2_ViscElMat_ViscElMat_ViscElPhys()], ## Constitutive law [Law2_ScGeom_ViscElPhys_Basic()], ), ## Apply gravity ## Cundall damping must been disabled! NewtonIntegrator(damping=0,gravity=[0,-9.81,0]), ## Saving results #VTKRecorder(virtPeriod=0.04,fileName='/tmp/stlimp-',recorders=['spheres','facets']), ## Apply kinematics to walls RotationEngine(ids=fctIds,rotationAxis=[0,0,1],rotateAroundZero=True,angularVelocity=0.5) ] from yade import qt qt.View() #O.saveTmp() #O.run() trunk-2018.02b/examples/baraban/baraban.stl000066400000000000000000000161641324306050200205140ustar00rootroot00000000000000Binary STL output from Blender: /home/sega/YADE/data/baraban.stl 7@"@^?@@@^?@?@{@ 7@"@^?@?@{@9@23@{@ O@@^ 7@"@^9@23@{@ O@@^9@23@{@cf@@{@ ?@^O@@^cf@@{@ ?@^cf@@{@`f?@y@ =>lz@^?@^`f?@y@ =>lz@^`f?@y@>@y@ ~=߾lz@^=>lz@^>@y@ ~=߾lz@^>@y@̾@y@ @^~=߾lz@^̾@y@ @^̾@y@nf@y@ O@^ @^nf@y@ O@^nf@y@kf@y@ 7"@^O@^kf@y@ 7"@^kf@y@923@y@ @?@^7"@^923@y@ @?@^923@y@@?@y@ "7@^@?@^@?@y@ "7@^@?@y@539@w@ O@^"7@^539@w@ O@^539@w@df@w@ 롚 ?^O@^df@w@ 롚 ?^df@w@bf?w@ iz=>^롚 ?^bf?w@ iz=>^bf?w@>w@ jzM=߾^iz=>^>w@ jzM=߾^>w@̾w@ ^jzM=߾^̾w@ ^̾w@lfw@ O^^lfw@ O^lfw@jfw@ "7^O^jfw@ "7^jfw@539w@ ?@^"7^539w@ ?@^539w@@@w@ 7"^?@^@@w@ 7"^@@w@943w@ O^ 7"^943w@ O^943w@ifw@ 롚^O^ifw@ 롚^ifw@jfy@ =߾jz^롚^jfy@ =߾jz^jfy@̾y@ W=>jz^=߾jz^̾y@ W=>jz^̾y@>y@ ?^W=>jz^>y@ ?^>y@df?y@ O@^?^df?y@ O@^df?y@ef@y@ 7@"^O@^ef@y@ 7@"^ef@y@9@43y@ @@?^7@"^9@43y@ @@?^9@43y@?@@y@ "@7^@@?^?@@y@ "@7^?@@y@13@9{@ @O^"@7^13@9{@ @O^13@9{@@hf{@ @,^@O^@hf{@ @,^@hf{@@hf{@ lz@>߾^@,^@hf{@ lz@>߾^@hf{@@̾{@ mz@=>^lz@>߾^@̾{@ mz@=>^@̾{@@>{@ @?^mz@=>^@>{@ @?^@>{@@ff?{@ @O@^@?^@ff?{@ @O@^@ff?{@@ff@{@ "@7@^@O@^@ff@{@ "@7@^@ff@{@13@9@{@ ?@@@^"@7@^13@9@{@ ?@@@^13@9@{@?@?@{@ ?@@@^ 7@"@^.&^ 7@"@^O@@^.&^ O@@^?@^.&^ ?@^=>lz@^.&^ =>lz@^~=߾lz@^.&^ ~=߾lz@^ @^.&^ @^O@^.&^ O@^7"@^.&^ 7"@^@?@^.&^ @?@^"7@^.&^ "7@^O@^.&^ O@^롚 ?^.&^ 롚 ?^iz=>^.&^ iz=>^jzM=߾^.&^ jzM=߾^^.&^ ^O^.&^ O^"7^.&^ "7^?@^.&^ ?@^ 7"^.&^ 7"^O^.&^ O^롚^.&^ 롚^=߾jz^.&^ =߾jz^W=>jz^.&^ W=>jz^?^.&^ ?^O@^.&^ O@^7@"^.&^ 7@"^@@?^.&^ @@?^"@7^.&^ "@7^@O^.&^ @O^@,^.&^ @,^lz@>߾^.&^ lz@>߾^mz@=>^.&^ mz@=>^@?^.&^ @?^@O@^.&^ @O@^"@7@^.&^ "@7@^?@@@^.&^ 9@23@{@?@?@{@,,4Ӵy@ cf@@{@9@23@{@,,4Ӵy@ `f?@y@cf@@{@,,4Ӵy@ >@y@`f?@y@,,4Ӵy@ ̾@y@>@y@,,4Ӵy@ nf@y@̾@y@,,4Ӵy@ kf@y@nf@y@,,4Ӵy@ 923@y@kf@y@,,4Ӵy@ @?@y@923@y@,,4Ӵy@ 539@w@@?@y@,,4Ӵy@ df@w@539@w@,,4Ӵy@ bf?w@df@w@,,4Ӵy@ >w@bf?w@,,4Ӵy@ ̾w@>w@,,4Ӵy@ lfw@̾w@,,4Ӵy@ jfw@lfw@,,4Ӵy@ 539w@jfw@,,4Ӵy@ @@w@539w@,,4Ӵy@ 943w@@@w@,,4Ӵy@ ifw@943w@,,4Ӵy@ jfy@ifw@,,4Ӵy@ ̾y@jfy@,,4Ӵy@ >y@̾y@,,4Ӵy@ df?y@>y@,,4Ӵy@ ef@y@df?y@,,4Ӵy@ 9@43y@ef@y@,,4Ӵy@ ?@@y@9@43y@,,4Ӵy@ 13@9{@?@@y@,,4Ӵy@ @hf{@13@9{@,,4Ӵy@ @hf{@@hf{@,,4Ӵy@ @̾{@@hf{@,,4Ӵy@ @>{@@̾{@,,4Ӵy@ @ff?{@@>{@,,4Ӵy@ @ff@{@@ff?{@,,4Ӵy@ 13@9@{@@ff@{@,,4Ӵy@ ?@?@{@13@9@{@,,4Ӵy@ trunk-2018.02b/examples/baraban/rotating-cylinder.py000066400000000000000000000044051324306050200224050ustar00rootroot00000000000000#!/usr/bin/python # -*- coding: utf-8 -*- cylHt,cylRd=1,.2 nSpheres=2e4 def unitSquare(): """Return square composed of 2 facets in the xy plane, centered at zero with unit extents.""" import gts vv=[gts.Vertex(1,1,0),gts.Vertex(-1,1,0),gts.Vertex(-1,-1,0),gts.Vertex(1,-1,0)] ee=[gts.Edge(vv[0],vv[1]),gts.Edge(vv[1],vv[2]),gts.Edge(vv[2],vv[3]),gts.Edge(vv[3],vv[0]),gts.Edge(vv[0],vv[2])] surf=gts.Surface() surf.add(gts.Face(ee[0],ee[1],ee[4])); surf.add(gts.Face(ee[2],ee[3],ee[4])) return surf def unitCylinder(nDiv=24): """Returns GTS surface approximating cylinder (without caps), with of height 2 and radius 2, centered at origin, axis coincident with the z-axis. :param int nDiv: polyhedron approximating circle. """ import numpy; from yade import pack thetas=numpy.linspace(0,2*pi,nDiv,endpoint=True) ptsBase=[Vector3(cos(th),sin(th),-1) for th in thetas] ptsTop=[p+Vector3(0,0,2) for p in ptsBase] return pack.sweptPolylines2gtsSurface([ptsBase,ptsTop]) from yade import pack,timing cyl=unitCylinder(); sq=unitSquare(); sq.translate(0,0,-1); cyl.copy(sq) cyl.scale(cylRd,cylRd,.5*cylHt); cyl.rotate(1,0,0,-pi/4) # 45° anti-colckwise in the yz plane # calling gtsSurface2Facets with just "cyl" (without constructing the faces tuple) ignores 2 faces that were copy'd before; bug in pygts? cylIds=O.bodies.append(pack.gtsSurface2Facets(cyl)) sp=pack.SpherePack(); wd=cylRd*sqrt(2); rMean=(.2*wd*wd*cylHt/(nSpheres*(4/3.)*pi))**(1/3.) print 'Generating cloud…' sp.makeCloud((-wd/2,-wd/2,-.5*cylHt),(wd/2,wd/2,.5*cylHt),rMean,0,int(nSpheres),False) sp.rotate((1,0,0),-pi/4) O.bodies.append([sphere(s[0],s[1]) for s in sp]) O.engines=[ ForceResetter(), InsertionSortCollider([Bo1_Sphere_Aabb(),Bo1_Facet_Aabb(),]), InteractionLoop( [Ig2_Sphere_Sphere_ScGeom(),Ig2_Facet_Sphere_ScGeom()], [Ip2_FrictMat_FrictMat_FrictPhys()], [Law2_ScGeom_FrictPhys_CundallStrack()], ), RotationEngine(rotateAroundZero=True,zeroPoint=(0,0,0),rotationAxis=(0,1,1),angularVelocity=30*(2*pi/60),ids=cylIds,label='rotor'), NewtonIntegrator(damping=.3,gravity=(0,0,-1e3)), # gravity artificially high, to make it faster going ;-) ] O.dt=PWaveTimeStep() O.stopAtIter=int(2*pi/(rotor.angularVelocity*O.dt)) O.timingEnabled=True; timing.reset() from yade import qt qt.Controller() qt.View() trunk-2018.02b/examples/billiard.py000066400000000000000000000027061324306050200171450ustar00rootroot00000000000000#!/usr/bin/python # -*- coding: utf-8 -*- # The script was implemented to check the following task # http://math.stackexchange.com/questions/658871/perfectly-centered-break-of-a-perfectly-aligned-pool-ball-rack/659318#659318 massBall = 1.0 radiusBall = 1.0 rowsN = 5 velBall = 10.0 vBall = 4.0/3.0 * math.pi * math.pow(radiusBall, 3.0) rhoBall = massBall/vBall ## PhysicalParameters Density=rhoBall frictionAngle=0.0 kn = 10e+11 ks = 10e+11 cn = 0.0 cs = 0.0 ## Import wall's geometry param = {'kn':kn*2.0, 'ks':ks*2.0, 'cn':cn*2.0, 'cs':cs*2.0} sphereMat=O.materials.append(ViscElMat(density=Density,frictionAngle=frictionAngle,**param)) ## Spheres sphereIds = [] for r in range(0,rowsN): yPos = r*pow(3.0, 1.0/2.0)*radiusBall for i in range(r+1): xPos = i*2*radiusBall - r*pow(radiusBall, 3.0/2.0) sphereIds.append(O.bodies.append(sphere([xPos,yPos,0.0],radiusBall,material=sphereMat))) ## schlagBall schlagBall = O.bodies.append(sphere([0.0,-2.0*radiusBall,0.0],radiusBall,material=sphereMat)) O.bodies[schlagBall].state.vel=Vector3(0,velBall,0) ## Timestep O.dt=.01*PWaveTimeStep() ## Engines O.engines=[ ForceResetter(), InsertionSortCollider([Bo1_Sphere_Aabb(),Bo1_Facet_Aabb()]), InteractionLoop( [Ig2_Sphere_Sphere_ScGeom(), Ig2_Facet_Sphere_ScGeom()], [Ip2_ViscElMat_ViscElMat_ViscElPhys()], [Law2_ScGeom_ViscElPhys_Basic()], ), NewtonIntegrator(damping=0), ] from yade import qt qt.View() #O.run(int(0.3/O.dt), True) trunk-2018.02b/examples/bouncingbubble.py000066400000000000000000000013731324306050200203420ustar00rootroot00000000000000 #Simulates a 5mm Diameter bubble in water rising and colliding with another bubble of the same diameter rad = 2.5e-3 #O.materials.append(FrictMat(young=1e3,density=1000)) O.materials.append(BubbleMat(density=1000,surfaceTension=71.97e-3)) O.bodies.append([ utils.sphere(center=(0,0,0),radius=rad,fixed=True), utils.sphere((0,0,-2*rad*1.1),rad) ]) O.dt = 1e-7 O.engines=[ ForceResetter(), InsertionSortCollider([Bo1_Sphere_Aabb()]), InteractionLoop([Ig2_Sphere_Sphere_ScGeom()],[Ip2_BubbleMat_BubbleMat_BubblePhys()],[Law2_ScGeom_BubblePhys_Bubble()]), # InteractionLoop([Ig2_Sphere_Sphere_ScGeom()],[Ip2_FrictMat_FrictMat_FrictPhys()],[Law2_ScGeom_FrictPhys_CundallStrack()]), NewtonIntegrator(damping=0.1,gravity=(0,0,9.81)) ] O.saveTmp() trunk-2018.02b/examples/bulldozer/000077500000000000000000000000001324306050200170065ustar00rootroot00000000000000trunk-2018.02b/examples/bulldozer/README000066400000000000000000000011371324306050200176700ustar00rootroot00000000000000bulldozer.py and bulldozerVTK.py show how simple it is possible to simulate relatively difficult things. bulldozer.py is using Law2_ScGeom_FrictPhys_CundallStrack constitutive law for interaction between elements. It stores screenshots from simulation in /tmp/bulldozer-????.png files and creates video then. bulldozerVTK.py is using Law2_ScGeom_ViscElPhys_Basic - visco-elastic model for interaction between elements. Also it saves snapshots in VTK-format in /tmp/bulldozer-*.vtu files. If you open those with paraview (or other VTK-based) program, you can create video from them, make screenshots etc. trunk-2018.02b/examples/bulldozer/bulldozer.py000066400000000000000000000053371324306050200213720ustar00rootroot00000000000000#!/usr/bin/python # -*- coding: utf-8 -*- ### Simpificated buldozer simulation from numpy import linspace from numpy import arange import gts import itertools from yade import geom,pack ###Initial Data numKnifeParts = 10 radiusKnife=1 lengthKnife=2 buldozerHeight=1.2 radiusSph = 0.05 numBoxes = Vector3(15,5,2) gapBetweenBoxes = 0.05 sizeBox = (lengthKnife-(numBoxes[1]-1)*gapBetweenBoxes)/numBoxes[1] ### Creating the Buldozer Knife ### from facets, using GTS Knife=[] for i in linspace(pi, pi*3/2, num=numKnifeParts, endpoint=True): Knife.append(Vector3(radiusKnife*cos(i),0,radiusKnife*sin(i))) KnifeP=[Knife,[p+Vector3(0,lengthKnife,0) for p in Knife]] KnifePoly=pack.sweptPolylines2gtsSurface(KnifeP,threshold=1e-4) KnifeIDs=[] KnifeIDs=O.bodies.append(pack.gtsSurface2Facets(KnifePoly,color=(1,0,0),wire=False)) KnifeIDs+=O.bodies.append(geom.facetBox((-lengthKnife/2-radiusKnife,lengthKnife/2,-radiusKnife+buldozerHeight/2),(lengthKnife/2,lengthKnife/2,buldozerHeight/2.),wallMask=47,color=(0,1,0),wire=False)) KnifeIDs+=O.bodies.append(geom.facetBox((-lengthKnife/2-radiusKnife-lengthKnife/4.,lengthKnife/2,-radiusKnife+buldozerHeight*3./2.-buldozerHeight/4.),(lengthKnife/4.,lengthKnife/3.,buldozerHeight/4.),wallMask=47,color=(0,0,1),wire=False)) O.bodies.append(geom.facetBox((0,lengthKnife/2,radiusKnife),(lengthKnife*4,lengthKnife*4,lengthKnife),wallMask=16,color=(1,1,1),wire=False)) ### Creating the material for buldozer colorsph1=Vector3(120,234,150); colorsph2=Vector3(1,1,0); colorsph1.normalize(); colorsph2.normalize(); colorSph=colorsph1 for xyz in itertools.product(arange(0,numBoxes[0]),arange(0,numBoxes[1]),arange(0,numBoxes[2])): ids_spheres=O.bodies.appendClumped(pack.regularHexa(pack.inEllipsoid((xyz[0]*(sizeBox+gapBetweenBoxes),xyz[1]*(sizeBox+gapBetweenBoxes)+sizeBox*0.5,xyz[2]*(sizeBox+gapBetweenBoxes)-radiusKnife+sizeBox*0.6),(sizeBox/2,sizeBox/2,sizeBox/2)),radius=radiusSph,gap=0,color=colorSph)) if (colorSph==colorsph1): colorSph=colorsph2 else: colorSph=colorsph1 from yade import qt O.dt=2*PWaveTimeStep() # We do not need now a high accuracy O.engines=[ ForceResetter(), InsertionSortCollider([Bo1_Sphere_Aabb(),Bo1_Facet_Aabb(),]), InteractionLoop( [Ig2_Sphere_Sphere_ScGeom(),Ig2_Facet_Sphere_ScGeom()], [Ip2_FrictMat_FrictMat_FrictPhys()], [Law2_ScGeom_FrictPhys_CundallStrack()], ), TranslationEngine(translationAxis=[1,0,0],velocity=5,ids=KnifeIDs), # Buldozer motion NewtonIntegrator(damping=.3,gravity=(0,0,-9.8)), #qt.SnapshotEngine(iterPeriod=100,fileBase='/tmp/bulldozer-',label='snapshooter'), ] O.saveTmp() qt.Controller() qt.View() r=qt.Renderer() r.lightPos=Vector3(0,0,50) O.stopAtIter=2000 O.run() #encodeVideoFromFrames(snapshooter.savedSnapshots,out='/tmp/bulldozer.ogg',fps=2) trunk-2018.02b/examples/bulldozer/bulldozerVTK.py000066400000000000000000000060501324306050200217500ustar00rootroot00000000000000#!/usr/bin/python # -*- coding: utf-8 -*- ### Simpificated buldozer simulation with VTK recorder ### vtk-files are saved in /tmp directory with names buldozer-*.vtk from numpy import linspace from numpy import arange import gts import itertools from yade import geom,pack ###Initial Data numKnifeParts = 10 radiusKnife=1 lengthKnife=2 buldozerHeight=1.2 radiusSph = 0.05 numBoxes = Vector3(8,5,2) gapBetweenBoxes = 0.05 sizeBox = (lengthKnife-(numBoxes[1]-1)*gapBetweenBoxes)/numBoxes[1] ## PhysicalParameters Density=2400 frictionAngle=radians(35) tc = 0.001 en = 0.3 es = 0.3 ## Materials facetMat=O.materials.append(ViscElMat(frictionAngle=frictionAngle,tc=tc,en=en,et=es)) sphereMat=O.materials.append(ViscElMat(density=Density,frictionAngle=frictionAngle,tc=tc,en=en,et=es)) ### Creating the Buldozer Knife ### from facets, using GTS Knife=[] for i in linspace(pi, pi*3/2, num=numKnifeParts, endpoint=True): Knife.append(Vector3(radiusKnife*cos(i),0,radiusKnife*sin(i))) KnifeP=[Knife,[p+Vector3(0,lengthKnife,0) for p in Knife]] KnifePoly=pack.sweptPolylines2gtsSurface(KnifeP,threshold=1e-4) KnifeIDs=O.bodies.append(pack.gtsSurface2Facets(KnifePoly,color=(1,0,0),wire=False,material=facetMat)) KnifeIDs+=O.bodies.append(geom.facetBox((-lengthKnife/2-radiusKnife,lengthKnife/2,-radiusKnife+buldozerHeight/2),(lengthKnife/2,lengthKnife/2,buldozerHeight/2.),wallMask=47,color=(0,1,0),wire=False)) KnifeIDs+=O.bodies.append(geom.facetBox((-lengthKnife/2-radiusKnife-lengthKnife/4.,lengthKnife/2,-radiusKnife+buldozerHeight*3./2.-buldozerHeight/4.),(lengthKnife/4.,lengthKnife/3.,buldozerHeight/4.),wallMask=47,color=(0,0,1),wire=False)) O.bodies.append(geom.facetBox((0,0,radiusKnife),(lengthKnife*3,lengthKnife*3,lengthKnife),wallMask=16,color=(1,1,1),wire=False,material=facetMat)) ### Creating the material for buldozer colorsph1=Vector3(120,234,150); colorsph2=Vector3(0,0,1); colorsph1.normalize(); colorsph2.normalize(); colorSph=colorsph1 for xyz in itertools.product(arange(0,numBoxes[0]),arange(0,numBoxes[1]),arange(0,numBoxes[2])): ids_spheres=O.bodies.appendClumped(pack.regularHexa(pack.inEllipsoid((xyz[0]*(sizeBox+gapBetweenBoxes),xyz[1]*(sizeBox+gapBetweenBoxes)+sizeBox*0.5,xyz[2]*(sizeBox+gapBetweenBoxes)-radiusKnife+sizeBox*0.6),(sizeBox/2,sizeBox/2,sizeBox/2)),radius=radiusSph,gap=0,color=colorSph,material=sphereMat)) if (colorSph==colorsph1): colorSph=colorsph2 else: colorSph=colorsph1 O.dt=.2*tc O.engines=[ ForceResetter(), InsertionSortCollider([Bo1_Sphere_Aabb(),Bo1_Facet_Aabb(),]), InteractionLoop( [Ig2_Sphere_Sphere_ScGeom(), Ig2_Facet_Sphere_ScGeom()], [Ip2_ViscElMat_ViscElMat_ViscElPhys()], [Law2_ScGeom_ViscElPhys_Basic()], ), TranslationEngine(translationAxis=[1,0,0],velocity=2,ids=KnifeIDs), # Buldozer motion NewtonIntegrator(damping=0,gravity=[0,0,-9.8]), VTKRecorder(iterPeriod=1000,fileName='/tmp/bulldozer-',recorders=['spheres','facets']) ] O.saveTmp() from yade import qt qt.Controller() qt.View() r=qt.Renderer() r.lightPos=Vector3(0,0,50) O.run(20000) #qt.makeSimulationVideo('/tmp/buldozer.ogg',iterPeriod=1000,fps=30) trunk-2018.02b/examples/capillary/000077500000000000000000000000001324306050200167645ustar00rootroot00000000000000trunk-2018.02b/examples/capillary/capillar.py000066400000000000000000000072071324306050200211330ustar00rootroot00000000000000#!/usr/bin/env python # encoding: utf-8 # The simulation of different capillary bridge models. # Parameters are set like in experiments of [Willett2000] from yade import utils, plot o = Omega() fr = 0.5;rho=2000 tc = 0.001; en = 0.7; et = 0.7; o.dt = 0.0002*tc r = 0.002381 Gamma = 20.6*1e-3 Theta = 0 VB = 74.2*1e-12 CapillarType1 = "Willett_numeric" CapillarType2 = "Willett_analytic" CapillarType3 = "Rabinovich" CapillarType4 = "Lambert" CapillarType5 = "Weigert" CapillarType6 = "Soulie" mat1 = O.materials.append(ViscElCapMat(frictionAngle=fr,density=rho,Vb=VB,gamma=Gamma,theta=Theta,Capillar=True,CapillarType=CapillarType1,tc=tc,en=en,et=et)) mat2 = O.materials.append(ViscElCapMat(frictionAngle=fr,density=rho,Vb=VB,gamma=Gamma,theta=Theta,Capillar=True,CapillarType=CapillarType2,tc=tc,en=en,et=et)) mat3 = O.materials.append(ViscElCapMat(frictionAngle=fr,density=rho,Vb=VB,gamma=Gamma,theta=Theta,Capillar=True,CapillarType=CapillarType3,tc=tc,en=en,et=et)) mat4 = O.materials.append(ViscElCapMat(frictionAngle=fr,density=rho,Vb=VB,gamma=Gamma,theta=Theta,Capillar=True,CapillarType=CapillarType4,tc=tc,en=en,et=et)) mat5 = O.materials.append(ViscElCapMat(frictionAngle=fr,density=rho,Vb=VB,gamma=Gamma,theta=Theta,Capillar=True,CapillarType=CapillarType5,tc=tc,en=en,et=et)) mat6 = O.materials.append(ViscElCapMat(frictionAngle=fr,density=rho,Vb=VB,gamma=Gamma,theta=Theta,Capillar=True,CapillarType=CapillarType6,tc=tc,en=en,et=et)) id1 = O.bodies.append(sphere(center=[0,0,0],radius=r,material=mat1,fixed=True)) id2 = O.bodies.append(sphere(center=[0,0,2*r],radius=r,material=mat1,fixed=True)) id3 = O.bodies.append(sphere(center=[3.0*r,0,0],radius=r,material=mat2,fixed=True)) id4 = O.bodies.append(sphere(center=[3.0*r,0,2*r],radius=r,material=mat2,fixed=True)) id5 = O.bodies.append(sphere(center=[6.0*r,0,0],radius=r,material=mat3,fixed=True)) id6 = O.bodies.append(sphere(center=[6.0*r,0,2*r],radius=r,material=mat3,fixed=True)) id7 = O.bodies.append(sphere(center=[9.0*r,0,0],radius=r,material=mat4,fixed=True)) id8 = O.bodies.append(sphere(center=[9.0*r,0,2*r],radius=r,material=mat4,fixed=True)) id9 = O.bodies.append(sphere(center=[12.0*r,0,0],radius=r,material=mat5,fixed=True)) id10= O.bodies.append(sphere(center=[12.0*r,0,2*r],radius=r,material=mat5,fixed=True)) id11= O.bodies.append(sphere(center=[15.0*r,0,0],radius=r,material=mat6,fixed=True)) id12= O.bodies.append(sphere(center=[15.0*r,0,2*r],radius=r,material=mat6,fixed=True)) o.engines = [ ForceResetter(), InsertionSortCollider([Bo1_Sphere_Aabb()]), InteractionLoop( [Ig2_Sphere_Sphere_ScGeom()], [Ip2_ViscElCapMat_ViscElCapMat_ViscElCapPhys()], [Law2_ScGeom_ViscElCapPhys_Basic()], ), NewtonIntegrator(damping=0,gravity=[0,0,0]), PyRunner(command='addPlotData()',iterPeriod=100), ] vel = 0.01 O.bodies[id2].state.vel=[0,0,vel] O.bodies[id4].state.vel=[0,0,vel] O.bodies[id6].state.vel=[0,0,vel] O.bodies[id8].state.vel=[0,0,vel] O.bodies[id10].state.vel=[0,0,vel] O.bodies[id12].state.vel=[0,0,vel] def addPlotData(): f1=O.forces.f(id2) f2=O.forces.f(id4) f3=O.forces.f(id6) f4=O.forces.f(id8) f5=O.forces.f(id10) f6=O.forces.f(id12) s1 = (O.bodies[id2].state.pos[2]-O.bodies[id1].state.pos[2])-2*r sc=s1 plot.addData(Willett_numeric=-f1[2], Willett_analytic=-f2[2], Rabinovich=-f3[2], Lambert=-f4[2], Weigert=-f5[2], Soulie=-f6[2], sc=sc) plot.plots={'sc':('Willett_numeric','Willett_analytic','Rabinovich','Lambert','Weigert','Soulie')}; plot.plot() O.step() from yade import qt qt.View() O.run(250000, True) #plot.saveGnuplot('sim-data_'+CapillarType1) trunk-2018.02b/examples/capillary/liquidmigration/000077500000000000000000000000001324306050200221655ustar00rootroot00000000000000trunk-2018.02b/examples/capillary/liquidmigration/showcase.py000066400000000000000000000064561324306050200243660ustar00rootroot00000000000000#!/usr/bin/env python # encoding: utf-8 # The script implements the show case in this article [Mani2013] from yade import utils, plot o = Omega() fr = 0.5;rho=2000 tc = 0.001; en = 0.7; et = 0.7; o.dt = 1.0 r1 = 1.0 r2 = 1.0 Gamma = 20.6*1e-3 Theta = 0 VB3 = 74.2*1e-12 CapillarType = "Lambert" mat1 = O.materials.append(ViscElCapMat(frictionAngle=fr,density=rho,Vb=VB3,gamma=Gamma,theta=Theta,Capillar=True,CapillarType=CapillarType,tc=tc,en=en,et=et)) d = 0.9999 id1 = O.bodies.append(sphere(center=[0,0,0], radius=r2,material=mat1,fixed=True, color=[0,1,0])) id2 = O.bodies.append(sphere(center=[0,(r1+r2)*d,0], radius=r2,material=mat1,fixed=True, color=[0,1,0])) id3 = O.bodies.append(sphere(center=[(r1+r2)*d,(r1+r2)*d,0], radius=r2,material=mat1,fixed=True, color=[0,1,0])) id4 = O.bodies.append(sphere(center=[(r1+r2)*d,(r1+r2)*d*2,0], radius=r2,material=mat1,fixed=True, color=[0,1,0])) id5 = O.bodies.append(sphere(center=[(r1+r2)*d*2,(r1+r2)*d,0], radius=r2,material=mat1,fixed=True, color=[0,1,0])) Vf = 0.0e-1 Vfmin = 0.0e-1 O.bodies[id1].state.Vf = Vf O.bodies[id1].state.Vmin = Vfmin O.bodies[id2].state.Vf = Vf O.bodies[id2].state.Vmin = Vfmin O.bodies[id3].state.Vf = Vf O.bodies[id3].state.Vmin = Vfmin O.bodies[id4].state.Vf = Vf O.bodies[id4].state.Vmin = Vfmin O.bodies[id5].state.Vf = Vf O.bodies[id5].state.Vmin = Vfmin vel = 0.0 O.bodies[id1].state.vel=[0,0,vel] O.bodies[id2].state.vel=[0,0,-vel] O.bodies[id3].state.vel=[vel,0,0] O.bodies[id4].state.vel=[-vel,0,0] o.engines = [ ForceResetter(), InsertionSortCollider([Bo1_Sphere_Aabb()],verletDist=(r1+r2)*5.0), InteractionLoop( [Ig2_Sphere_Sphere_ScGeom()], [Ip2_ViscElCapMat_ViscElCapMat_ViscElCapPhys()], [Law2_ScGeom_ViscElCapPhys_Basic()], ), LiqControl(label='lqc'), NewtonIntegrator(damping=0,gravity=[0,0,0]), PyRunner(command='showData()',iterPeriod=1), ] def showData(): print "Step %d"%O.iter print "idB=%d, Vf=%s, Vmin=%s;"%(id1, O.bodies[id1].state.Vf, O.bodies[id1].state.Vmin) print "idB=%d, Vf=%s, Vmin=%s;"%(id2, O.bodies[id2].state.Vf, O.bodies[id2].state.Vmin) print "idB=%d, Vf=%s, Vmin=%s;"%(id3, O.bodies[id3].state.Vf, O.bodies[id3].state.Vmin) print "idB=%d, Vf=%s, Vmin=%s;"%(id4, O.bodies[id4].state.Vf, O.bodies[id4].state.Vmin) print "idB=%d, Vf=%s, Vmin=%s;"%(id5, O.bodies[id5].state.Vf, O.bodies[id5].state.Vmin) try: print "Interaction[1, 2].Vb=%s"%(O.interactions[id1,id2].phys.Vb) except: pass try: print "Interaction[2, 3].Vb=%s"%(O.interactions[id2,id3].phys.Vb) except: pass try: print "Interaction[3, 4].Vb=%s"%(O.interactions[id3,id4].phys.Vb) except: pass try: print "Interaction[3, 5].Vb=%s"%(O.interactions[id3,id5].phys.Vb) except: pass print showData() O.run(1, True) for i in range(5): O.bodies[i].state.Vf = 0 O.bodies[i].state.Vmin = 0 O.interactions[id1,id2].phys.Vmax = 5.0 lqc.addLiqInter(id1, id2, 1.0) O.interactions[id2,id3].phys.Vmax = 5.0 lqc.addLiqInter(id2, id3, 1.0) O.interactions[id3,id4].phys.Vmax = 5.0 lqc.addLiqInter(id3, id4, 1.0) O.interactions[id3,id5].phys.Vmax = 5.0 lqc.addLiqInter(id3, id5, 1.0) O.run(1, True) vel = 1.0 O.bodies[id3].state.vel=[vel,0,0] O.bodies[id4].state.vel=[vel,0,0] O.bodies[id5].state.vel=[vel,0,0] from yade import qt qt.View() trunk-2018.02b/examples/capillaryLaplaceYoung/000077500000000000000000000000001324306050200212705ustar00rootroot00000000000000trunk-2018.02b/examples/capillaryLaplaceYoung/CapillaryPhys-example.py000066400000000000000000000046741324306050200260720ustar00rootroot00000000000000#!/usr/bin/python # -*- coding: utf-8 -*- """ To run this script you need to have all 10 text files from https://yade-dem.org/wiki/CapillaryTriaxialTest in the same folder as you run this script in console! This script shows how to use Law2_ScGeom_CapillaryPhys_Capillarity. The user can switch between hertz and linear model by setting "model_type" """ model_type = 1 #1=Hertz model with capillary forces, else linear model with capillary model #some parameters: shear_modulus = 1e5 poisson_ratio = 0.3 young_modulus = 2*shear_modulus*(1+poisson_ratio) friction = 0.5 angle = atan(friction) local_damping = 0.01 viscous_normal = 0.021 viscous_shear = 0.8*viscous_normal lowercorner = Vector3(0,0,0) uppercorner = Vector3(0.002,0.002,0.004) #creating a material (FrictMat): id_SphereMat=O.materials.append(FrictMat(young=young_modulus,poisson=poisson_ratio,density=2500,frictionAngle=angle)) SphereMat=O.materials[id_SphereMat] #generate particles: sp=pack.SpherePack() sp.makeCloud(lowercorner,uppercorner,.0002,rRelFuzz=.3) O.bodies.append([sphere(c,r,material=SphereMat) for c,r in sp]) #generate boundary: O.bodies.append(geom.facetBox(uppercorner/2,uppercorner/2,wire=True,fixed=True,material=SphereMat)) #define engines: if model_type == 1:#hertz model with capillary forces O.engines=[ ForceResetter(), InsertionSortCollider([Bo1_Sphere_Aabb(),Bo1_Facet_Aabb()]), InteractionLoop( [Ig2_Sphere_Sphere_ScGeom(),Ig2_Facet_Sphere_ScGeom()], [Ip2_FrictMat_FrictMat_MindlinCapillaryPhys(label='ContactModel')],#for hertz model only [Law2_ScGeom_MindlinPhys_Mindlin()]#for hertz model only ), Law2_ScGeom_CapillaryPhys_Capillarity(capillaryPressure=10000),#for hertz model only NewtonIntegrator(damping=local_damping,gravity=(0,0,-9.81)), ] ContactModel.betan=viscous_normal ContactModel.betas=viscous_shear ContactModel.useDamping=True else: O.engines=[ ForceResetter(), InsertionSortCollider([Bo1_Sphere_Aabb(),Bo1_Facet_Aabb()]), InteractionLoop( [Ig2_Sphere_Sphere_ScGeom(),Ig2_Facet_Sphere_ScGeom()], [Ip2_FrictMat_FrictMat_CapillaryPhys()], #for linear model only [Law2_ScGeom_FrictPhys_CundallStrack()], #for linear model only ), Law2_ScGeom_CapillaryPhys_Capillarity(capillaryPressure=10000),#for linear model only NewtonIntegrator(damping=local_damping,gravity=(0,0,-9.81)), ] #set time step and run simulation: O.dt=0.5*PWaveTimeStep() from yade import qt qt.View() print('Press PLAY button') #O.run(10000,True) trunk-2018.02b/examples/capillaryLaplaceYoung/README.txt000066400000000000000000000102031324306050200227620ustar00rootroot00000000000000This folder illustrates the use of Law2_ScGeom_CapillaryPhys_Capillarity engine to describe capillary interaction between particle pairs in YADE (see also the wiki page https://www.yade-dem.org/wiki/CapillaryTriaxialTest) I. Simulation scripts examples ------------------------------ Two examples of simulations using Law2_ScGeom_CapillaryPhys_Capillarity are herein provided: - CapillaryPhys-example.py is a (simple) packing-scale simulation considering capillary interaction and gravity - capillaryBridge.py defines and let evolve a capillary bridge between two particles only II. Capillary files scripts --------------------------- As explained on the wiki page (see above) and in Law2_ScGeom_CapillaryPhys_Capillarity doc, so-called capillary files are required in order to use Law2_ScGeom_CapillaryPhys_Capillarity engine. These capillary files include capillary bridges configurations data (for positiv capillary pressure values). A version of capillary files can be downloaded from the wiki page, they consider a zero contact (wetting) angle and some given particle radius ratio. Also, a suite of three files are herein provided in order to enable users to build their own capillary files for any contact angle or particle radius ratio, or to study capillary bridges as such (file solveLiqBridge.m/py). At the moment, all these files are coded in .m, for use with MATLAB, a migration to Python is in progress with .py counterparts of some .m files. Capillary files generation requires launching writesCapFile() (in writesCapFile.m) only, after providing a couple of parameters therein (see description of writesCapFile.m below). The two other files (solveLiqBridge.m/py and solveLaplace_uc.m/py) define functions used by writesCapFile() and do not require any user input. Specifically, the roles of the three files are as follows: - solveLiqBridge.m/py solves the Laplace-Young equation for one given bridge, defined in terms of the input attributes of the solveLiqBridge function (see therein). The solveLiqBridge function is usually called by other files (see below) during capillary files generation, however it can also be executed on its own in order to study (e.g. plot) capillary bridge profile. Code comments include references to: * Duriez2017: J. Duriez and R. Wan, Contact angle mechanical influence for wet granular soils, Acta Geotechnica, 12, 2017 * Lian1993: G. Lian and C. Thornton and M. J. Adams, A Theoretical Study of the Liquid Bridge Forces between Two Rigid Spherical Bodies, Journal of Colloid and Interface Science, 161(1), 1993 * Scholtes2008 (french): L. Scholtes, Modelisation Micro-Mecanique des Milieux Granulaires Partiellement Satures, PhD Thesis from Institut polytechnique de Grenoble, 2008 * Soulie2005 (french): F. Soulie, Cohesion par capillarite et comportement mecanique de milieux granulaires, PhD Thesis from Universite Montpellier II, 2005 * Soulie2006: F. Soulie and F. Cherblanc and M. S. El Youssoufi and C. Saix, Influence of liquid bridges on the mechanical behaviour of polydisperse granular materials, International Journal for Numerical and Analytical Methods in Geomechanics, 30(3), 2006 - solveLaplace_uc.m/py calls several times solveLiqBridge in order to compute all possible bridges configurations for given contact angle, capillary pressure, and particle radius ratio. It is usually called by writesCapFile (see below) during capillary files generation, however it can also be executed on its own. In particular, it may output text files including bridges data for one given capillary pressure. - writesCapFile.m, finally, is the conductor during the capillary files generation, and is the only one that actually requires the user's attention for such task. Parameters are to be defined by the user directly in the .m file (from l. 10 to 30) before launching. Generating a complete set of 10 capillary files typically used to require few days in terms of computation time with MATLAB R2014 and much (~ 7*) more with Octave. Using MATLAB R2016b drastically increases the speed of execution (*10-30 with respect to R2014) and the incoveniency of Octave. Python 2.7.12 may be a good compromise, with a time cost ~ 6-7* the one of MATLAB R2016b. trunk-2018.02b/examples/capillaryLaplaceYoung/capillaryBridge.py000066400000000000000000000032241324306050200247400ustar00rootroot00000000000000# capillary bridge example between 2 spheres r1,r2 = 1e-4,4e-4 # better take small particles z1,z2=0,0.95*(r1+r2) O.bodies.append(sphere(center=Vector3(0,0,z1),radius=r1,dynamic=0)) O.bodies.append(sphere(center=Vector3(0,0,z2),radius=r2,dynamic=0)) O.engines=[ForceResetter() ,InsertionSortCollider([Bo1_Sphere_Aabb()]) ,InteractionLoop( [Ig2_Sphere_Sphere_ScGeom()], [Ip2_FrictMat_FrictMat_CapillaryPhys()], [Law2_ScGeom_FrictPhys_CundallStrack(neverErase=1)] ) ,Law2_ScGeom_CapillaryPhys_Capillarity(capillaryPressure=1e3) ,NewtonIntegrator() ,GlobalStiffnessTimeStepper() ,PyRunner(command='computeThings()',iterPeriod=1) ] from yade import plot def computeThings(): if O.interactions.has(0,1) and O.interactions[0,1].isReal: i = O.interactions[0,1] un = i.geom.penetrationDepth vM = i.phys.vMeniscus delta1,delta2 = i.phys.Delta1,i.phys.Delta2 fCap = i.phys.fCap.norm() nn11,nn33 = i.phys.nn11,i.phys.nn33 else: un,vM,delta1,delta2,fCap,nn11,nn33 = float('nan'),float('nan'),float('nan'),float('nan'),float('nan'),float('nan'),float('nan') plot.addData(delta1 = delta1, delta2 = delta2, fCap = fCap , it =O.iter, un = un ,vM = vM , nn11 = nn11, nn33 = nn33) plot.plots={'un':'fCap','un ':'vM',' un':('delta1','delta2'),' un ':('nn11','nn33')} plot.plot() # spheres get closer: O.bodies[1].state.vel=Vector3(0,0,-0.002) O.run(500,wait=1) # spheres move apart: O.bodies[1].state.vel=Vector3(0,0,0.02) O.run(4000,wait=1) trunk-2018.02b/examples/capillaryLaplaceYoung/solveLaplace_uc.m000066400000000000000000000074321324306050200245550ustar00rootroot00000000000000% J. Duriez (jerome.duriez@irstea.fr) function stabSol = solveLaplace_uc(theta,rRatio,uStar,delta1Cons,deltaZ,save) % Solves Laplace-Young equation for given r, uc* and theta % Making recursively use of solveLiqBridge function, the present function % identifies all possible bridges configurations for given r, uc*, and % theta % Input attributes (see also solveLiqBridge function): % theta: contact angle (deg) % rRatio: rMax / rMin for the considered particles pair % uStar: dimensionless capillary pressure % delta1Cons: vector of delta1 (see solveLiqBridge) values to consider % deltaZ: see solveLiqBridge % save: 0/1 to save text files including the capillary bridges data (if 1) % Returns a table (matrix) of capillary bridges data (see sol_u below), % possibly (in case save=1) also written in text files. % Removal of possible d1=0 since such bridge is physically impossible, and % d1 = 0 might make trouble if theta = 0 (leads to inf. initial rhoPrime): delta1Cons = delta1Cons(delta1Cons~=0); % And of d1 values such that d1 + theta > 90 delta1Cons = delta1Cons(delta1Cons + theta <= 90); %------------ All possible bridges for these r, uc*, theta: --------------- % data table initialization: sol_u = -ones(length(delta1Cons),9); % 9 col. are [dist uc(=cst) V F delta1 delta2 E nn11 nn33] minDzUsed = deltaZ; % tic for i=1:length(delta1Cons) dzUsed = deltaZ; delta1 = delta1Cons(i); [dist,vol,force,delta1,delta2,eStar,nn11,nn33,out] = solveLiqBridge(rRatio,theta,uStar,delta1,dzUsed,0,0,0); while out == 0 dzUsed = dzUsed / 2;minDzUsed = min(dzUsed,minDzUsed); % disp(['Refining deltaZ to ',num2str(dzUsed),' in solveLaplace']) [dist,vol,force,delta1,delta2,eStar,nn11,nn33,out] = solveLiqBridge(rRatio,theta,uStar,delta1,dzUsed,0,0,0); end sol_u(i,:) = [dist,uStar,vol,force,delta1,delta2,eStar,nn11,nn33]; end % timeLoop = toc; % disp(['Here, loop took ',num2str(timeLoop),' s']) % Get rid of unphysical solutions with negativ distances and/or volume: physSol = sol_u( sol_u(:,1)>=0 & sol_u(:,3)>=0,: ); % NB: vol>=0 is true for vol = Inf % Get rid of unstable physical solutions:(those with biggest volumes) % We use volume values for this purpose, see e.g. Duriez2017 distRupt = max(physSol(:,1)); % eRupt = physSol(physSol(:,1)==distRupt,7); % would not work since eRupt can be a global maximum % (when two branches are increasing with d*) vRupt = physSol(physSol(:,1)==distRupt,3); stabSol = physSol(physSol(:,3)>=vRupt,:); % ----------- Save of data in text files ----------- if save ~=0 % three text files are generated: one, "Raw", includes all computed data; % another, "Phys", restricts to physical solutions (with positiv distance % and volume); the last one, "Stab" restricts to the stable physical % solutions if minDzUsed == deltaZ strDz = ['With deltaZ = ',num2str(deltaZ)]; else strDz = ['With deltaZ between ',num2str(deltaZ),' and ',num2str(minDzUsed)]; end nomFic = ['capDataRaw_r',num2str(rRatio),'_theta',num2str(theta),'_ucStar',num2str(uStar),'.txt']; head_wE = ['dist*\tuc*\tV*\tF*\tdelta1 (deg)\tdelta2(deg)\tE* (-)\tn11 (-)\tn33 (-)\t',strDz,'\n']; writeFile(nomFic,head_wE,sol_u) nomFic = ['capDataPhys_r',num2str(rRatio),'_theta',num2str(theta),'_ucStar',num2str(uStar),'.txt']; writeFile(nomFic,head_wE,physSol) nomFic = ['capDataStab_r',num2str(rRatio),'_theta',num2str(theta),'_ucStar',num2str(uStar),'.txt']; head_woE = ['dist*\tuc*\tV*\tF*\tdelta1 (deg)\tdelta2(deg)\tn11 (-)\tn33 (-)\t',strDz,'\n']; writeFile(nomFic,head_woE,stabSol(:,[1:6,8:9])) end % --------------------------------------------------- end function writeFile(nomFic,headLine,data) fic = fopen(nomFic,'w'); fprintf(fic,headLine); fclose(fic); dlmwrite(nomFic,data,'-append','delimiter','\t') end trunk-2018.02b/examples/capillaryLaplaceYoung/solveLaplace_uc.py000066400000000000000000000117071324306050200247510ustar00rootroot00000000000000# J. Duriez (jerome.duriez@irstea.fr) # to import with yade/python/ipython solveLaplace_uc.py, or with execfile('solveLaplace_uc.py',globals()) once inside a yade/python/ipython session execfile('solveLiqBridge.py',globals()) def solveLaplace_uc(theta,rRatio,uStar,delta1Cons,deltaZ,save): # Solves Laplace-Young equation for given r=rRatio, uc*=uStar and theta # Making recursively use of solveLiqBridge function, the present function # identifies all possible bridges configurations for given r, uc*, and # theta # Input attributes (see also solveLiqBridge function): # theta: contact angle (deg) # rRatio: rMax / rMin for the considered particles pair # uStar: dimensionless capillary pressure # delta1Cons: numpy.array of delta1 (see solveLiqBridge) values to consider # deltaZ: see solveLiqBridge # save: 0/1 to save text files including the capillary bridges data (if 1) # Returns a 2D numpy.array of capillary bridges data (see sol_u below), # possibly (in case save=1) also written in text files. # Removal of possible d1=0 since such bridge is physically impossible, and # d1 = 0 might make trouble if theta = 0 (leads to inf. initial rhoPrime): delta1Cons = delta1Cons[delta1Cons!=0] # And of d1 values such that d1 + theta > 90 delta1Cons = delta1Cons[delta1Cons + theta <= 90] #------------ All possible bridges for these r, uc*, theta: --------------- # initialization of bridge configurations data: as many lines as configurations # and 9 columns being [dist uc(=cst) V F delta1 delta2 E nn11 nn33] sol_u = -numpy.ones((len(delta1Cons),9)) # list [[- 1 for j in range(9)] for j in range(len(delta1Cons)) ] would be a better choice than arrays in terms of sys.getsizeof (list of 100000*9 lines*columns~0.1 * numpy.array of 100000*9 lines*columns) # but seems much less convenient for indexing and testing signs below (list[4][:] is the same as list[:][4]....) minDzUsed = deltaZ # timeStartLoop = time.clock() for i in range(0,len(delta1Cons)): dzUsed = deltaZ delta1 = delta1Cons[i] [dist,vol,force,delta1,delta2,eStar,nn11,nn33,out] = solveLiqBridge(rRatio,theta,uStar,delta1,dzUsed,0,0,0) while out == 0: dzUsed = dzUsed / 2. minDzUsed = min(dzUsed,minDzUsed) [dist,vol,force,delta1,delta2,eStar,nn11,nn33,out] = solveLiqBridge(rRatio,theta,uStar,delta1,dzUsed,0,0,0) sol_u[i,:] = [dist,uStar,vol,force,delta1,delta2,eStar,nn11,nn33] # timeEndLoop = time.clock() # gives the same measurements than time.time() # print 'Here, loop took '+str(timeEndLoop-timeStartLoop)+' s' # Get rid of unphysical solutions with negativ distances and/or volume: physSol = sol_u[ numpy.logical_and(sol_u[:,0]>=0,sol_u[:,2]>=0),: ] # Get rid of unstable physical solutions: # We use volume values for this purpose, see e.g. Duriez2017 distRupt = max(physSol[:,0]) # eRupt = physSol(physSol(:,1)==distRupt,7) # would not work since eRupt can be a global maximum # (when two branches are increasing with d*) vRupt = physSol[physSol[:,0]==distRupt,2] stabSol = physSol[physSol[:,2]>=vRupt,:] # ----------- Save of data in text files ----------- # three text files are generated: one, "Raw", includes all computed data; # another, "Phys", restricts to physical solutions (with positiv distance # and volume); the last one, "Stab" restricts to the stable physical solutions if save !=0: if minDzUsed == deltaZ: strDz = 'With deltaZ = ' + str(deltaZ) else: strDz = 'With deltaZ between '+str(deltaZ)+' and '+str(minDzUsed) # Raw data: all liquid bridge configurations that have been computed nameFile = 'capDataRaw_r'+str(rRatio)+'_theta'+str(theta)+'_ucStar'+str(uStar)+'.txt' head_wE = 'dist*\tuc*\tV*\tF*\tdelta1 (deg)\tdelta2(deg)\tE* (-)\tn11 (-)\tn33 (-)\t' + strDz + '\n' writeFile(nameFile,head_wE,sol_u) # Physical data: after removing the configurations with negative distance or volume nameFile = 'capDataPhys_r'+str(rRatio)+'_theta'+str(theta)+'_ucStar'+str(uStar)+'.txt' writeFile(nameFile,head_wE,physSol) # Stab data: only the stable configurations, i.e. biggest volumes nameFile = 'capDataStab_r'+ str(rRatio)+'_theta'+str(theta)+'_ucStar'+str(uStar)+'.txt' head_woE = 'dist*\tuc*\tV*\tF*\tdelta1 (deg)\tdelta2(deg)\tn11 (-)\tn33 (-)\t'+strDz+'\n' columns = range(6) # just an intermediate variable for below, as range(5).extend([7,8]) is impossible.. columns.extend([7,8]) # columns.extend() as an argument of numpy.ix() would fail writeFile(nameFile,head_woE,stabSol[ numpy.ix_( range(stabSol.shape[0]) , columns ) ]) # -------------------------------------------------- return stabSol def writeFile(nomFic,headLine,data): fic = open(nomFic,'w') numpy.savetxt(fic,data,delimiter='\t',header=headLine) fic.close() trunk-2018.02b/examples/capillaryLaplaceYoung/solveLiqBridge.m000066400000000000000000000241411324306050200243630ustar00rootroot00000000000000% J. Duriez (jerome.duriez@irstea.fr) function [dist,vol,force,delta1,delta2,eStar,nn11,nn33,out] = solveLiqBridge(rRatio,theta,uStar,delta1,deltaZ,plot,plot3D,speak) %Computes one single liquid (capillary) bridge % Lian's method: iterative determination using Taylor expansion with % finite difference (FD) increment = deltaZ (a function attribute) % Attributes: rRatio = R2 / R1 >=1, theta = contact angle (deg), uStar = % dimensionless cap. pressure (uc*R2/gamma), delta1 = filling angle on % smallest particle (deg), deltaZ = increment of FD scheme (e.g. 2e-6) % Last ones = booleans to resp. plot the profile in 2D, output messages, % and plot the liq bridge in 3D % Outputs: dist = interparticle distance ; % vol = dimensionless volume (= vol% /R2^3) % force = dimensionless force = force/(2*pi*gamma*R2) % delta1/2 = filling angles on the 2 particles (degrees) % eStar = free energy/(gamma*% R2^2) % nn11 = 11-term of integral(ni nj dS) / R2^2 = 22-term % nn33 = 33-term, with 3 axis = the meniscus orientation % NB: surface / R2^2 = 2*nn11 + nn33 % out = 1 or 0 depending whether things went fine (see below): 1 if yes % close all global cstC % cstC is the dimensionless capillary force: constant all along the profile % see [9] Lian1993, (6) Duriez2017, or (2.51) Soulie2005 ~ (10) Soulie2006.. cstC = 1/rRatio * sin(radians(delta1)) * sin(radians(delta1+theta)) + 1/2 * uStar * rRatio^-2 * sin(radians(delta1))^2; % Use of cstC to get the right filling angle delta2 (# delta1 for rRatio # 1), according to e.g. (2.52) Soulie2005: if rRatio ~=1 fun = @(delta2)cstC_delta2(delta2,theta,uStar); delta2 = fzero(fun,[0 90]); % fzero does not catch multiple roots, if it occurs else delta2 = delta1; end % Discrete set of profile values: rho = -ones(30000,1); % preallocating 26000 or 30000 makes 90 profile computations take ~ 10.8s instead of ~ 14.5 % Discrete set of slope values: rhoPrime = -ones(30000,1); step = 1; % Boundary conditions on left contact line: see left (smallest) particle rho(1) = 1/rRatio * sin(radians(delta1)); rhoPrime(1) = - 1 / tan(radians(delta1+theta)); % Boundary condition on right contact line: see right (biggest) particle rhoRight = sin(radians(delta2)); %------------------------------------------------------------------------- % Finite diff. scheme to compute whole profile ie compute rho and rhoPrime % See Lian1993, Duriez2017, etc.. % Some remarks about next loop: % - this loop may extend the size of rho without problem % - just keep a simple and efficient test to stop computations. A more restrictive % one may take you beyond the right contact line and lead to divergence.. % tic while rho(step) < 1.00001*rhoRight rho2d = rhoSecond(rho(step),rhoPrime(step),uStar); % all terms are at "i" rho(step+1) = rho(step) + deltaZ * rhoPrime(step) + 1/2 * deltaZ^2 * rho2d; rhoPrime(step+1) = drho(rho(step+1),rho(step),deltaZ,rho2d); % i+1 value of rhoPrime, function of rho_{i+1}, rho_i, and rho2d_i. Used next time in loop. step=step+1; end % toc %------------------------------------------------------------------------- % Two following lines to suppress extra data due to preallocation. Keep % this order ! rhoPrime = rhoPrime(rho>0); rho = rho(rho>0); % --------------- Output computations from now on: ------------------------ % tic d1 = radians(delta1); d2 = radians(delta2); % Inter particle dimensionless distance: % see (7) Duriez2017, (33) Scholtes2008, etc. (not (5) Soulie2006..) lastZ = deltaZ*(length(rho) -1); dist = lastZ - 1/rRatio * ( 1-cos(d1) ) - ( 1-cos(d2) ); % Dimensionless capillary force: force = cstC; % Dimensionless volume: % Cf (4) Soulie2006, (34) Appendix Scholtes2008, (8) Duriez2017 etc. : vol = pi*sum(rho(2:length(rho)).^2)*deltaZ; vol = vol - pi/3 * rRatio^(-3) * ( 1-cos(d1) )^2 * ( 2+cos(d1) ); vol = vol - pi/3 * ( 1-cos(d2) )^2 * ( 2+cos(d2) ); % Capillary bridge dimensionless free energy: % (12) Duriez2017 rather than [35] Lian1993 (was for cst volume stability) dArea = rho .* ( 1+rhoPrime.^2).^(1/2); % ~ infinitesimal liquid gas area eStar = 2*pi * sum(dArea(2:length(dArea))) * deltaZ + uStar * vol; %+/- uStar changes the values but not the shape eStar = eStar - 2*pi*cos(radians(theta)) * ( (1-cos(d1))/rRatio^2 + 1 - cos(d2) ); rhoRed = rho(2:length(rho)); rhoPrRed = rhoPrime(2:length(rho)); % Surface: surf = 2*pi * sum( rhoRed .* (1 + rhoPrRed.^2).^0.5 ) * deltaZ; % Surface integral of dyadic product n*n (diagonal axisymmetric matrix): nn11 = pi * sum( rhoRed ./ (1 + rhoPrRed.^2).^0.5 ) * deltaZ; % = n22 nn33 = 2*pi * sum( rhoRed .* rhoPrRed.^2 ./ (1 + rhoPrRed.^2).^0.5 ) * deltaZ; % --------------- Miscellaneous checks and plots -------------------------- % Check whether surf = tr(integral n*n) if (surf - (2*nn11 + nn33) ) / surf > 0.0025 % strict equality beyond reach disp(surf) disp(2*nn11 + nn33) startStr = ['r = ',num2str(rRatio),';theta = ',num2str(theta),';u* = ',num2str(uStar)]; error(['Liq-gas area # tr(integral over liq-gas surf. n*n) for ',startStr,';delta1 = ',num2str(delta1),';dZ = ',num2str(deltaZ)]) end if plot==1 % && dist >=0 && vol >=0 %&& isfinite(vol) plotProfile(rho,deltaZ,rRatio,delta1,delta2,theta,cstC,uStar) end if plot3D==1 plot3Dbridge(rho,deltaZ,rRatio) end % Check if the neck has been reached: (there is a risk because of divergence possibility) if uStar~= 0 % "y0" computation according to [10] Lian1993, (or (2.54) Soulie2005 - (11) Soulie2006) rhoNeck = ( -1 + sqrt(1+2*uStar*cstC) ) / uStar; else rhoNeck = cstC; end if (min(rho) - rhoNeck) / rhoNeck > 0.002 strU = num2str(uStar); strD1 = num2str(delta1); strDz = num2str(deltaZ); if speak ~= 0 disp(['Profile with u*=',strU,', delta1=',strD1,' and deltaZ=',strDz,' has not reached its neck !']) end out = 0; else out = 1; end % toc end function angleRad = radians(angle) angleRad = angle * pi / 180; end function y = cstC_delta2(delta2,theta,uStar) % from e.g. (2.52) Soulie2005 global cstC y = sin(radians(delta2)) * sin(radians(delta2+theta)) + 1/2 * uStar * sin(radians(delta2))^2 - cstC; end function drho = drho(rho,prevRho,deltaZ,rho2d) % returns rhoPrime at i+1, see (4) Duriez2017 %drho = sqrt( ( rho/(cstK - 1/2*uStar*rho^2) )^2 - 1 );% [11] Lian1993 always positiv, which is not true drho = (rho - prevRho) / deltaZ + 1/2*deltaZ * rho2d; % NB: the rho2d term has a real influence end function rho2d = rhoSecond(rho,rhoP,uStar) % see e.g. (5) Duriez2017 rho2d = (1+rhoP^2) / rho + uStar*(1+rhoP^2)^1.5; end function plotProfile(rho,deltaZ,rRatio,delta1,delta2,theta,cstC,uStar) if uStar~= 0 % "y0" computation, see e.g. [10] Lian1993, (2.54) Soulie2005, (11) Soulie2006.. rhoNeck = ( -1 + sqrt(1+2*uStar*cstC) ) / uStar; else rhoNeck = cstC; end lastZ = deltaZ*(length(rho) -1); vecZ = 0:deltaZ:lastZ; % Toroidal profile parameters: see [15]-[17] Lian1993, for r=1 if rRatio == 1 d1Rad = radians(delta1);d2Rad = radians(delta2); thRad = radians(theta); dist = lastZ - (1-cos(d1Rad)) - (1-cos(d2Rad)); rho1 = ( dist/2. + 1 - cos(d1Rad) )/cos(d2Rad+thRad); rho2 = sin(d2Rad) - (1-sin(d2Rad+thRad)) * ( dist/2 + 1-cos(d2Rad) ) / cos(d2Rad+thRad); torProfile = rho1 + abs(rho2) - ( rho1^2 - (vecZ-lastZ/2).^2 ).^(1/2); end figure(); plot(vecZ,rho);hold on; plot([0;lastZ],[1/rRatio * sin(radians(delta1));1/rRatio * sin(radians(delta1))],'r') plot([0;lastZ],[rhoNeck;rhoNeck],'m') plot([0;lastZ],[sin(radians(delta2));sin(radians(delta2))],'g') xlabel('Interparticle axis, z') ylabel('Profile height, \rho(z)') title(['\delta_1 = ',num2str(delta1)]) if rRatio == 1 plot(vecZ(1:floor(length(rho)/50):end),torProfile(1:floor(length(rho)/50):end),'ob'); legend('Profile','Left value','Neck value','Right value','Toroidal profile') else legend('Profile','Left value','Neck value','Right value') end end function plot3Dbridge(rho,deltaZ,rRatio) nPtsAlongAxis = 200 ; % grid-step along "rho" and z axis (the x,y plane of the 3D figure in fact) nRhoVal = length(rho); rhoMax = max(rho); lastZ = deltaZ*(nRhoVal -1); % discretization of x axis of 3D figure x = -rhoMax:rhoMax/nPtsAlongAxis:rhoMax; % discretization of y axis of 3D figure (= z axis from Fig. 1 Duriez2017), % nPtsAlongAxis values, from 0 to lastZ: y = 0:lastZ / (nPtsAlongAxis-1):lastZ; % will then reach exactly lastZ alt = zeros(length(y),length(x)); for i = 1:length(y) indRho = floor( (i-1)*(nRhoVal-1)/(nPtsAlongAxis) ) +1; for j = 1:length(x) if rho(indRho)^2 - x(j)^2 >=0 alt(i,j) = rho(indRho) * sqrt(rho(indRho)^2 - x(j)^2) / rho(indRho); % else % alt(i,j) = NaN; % not pretty... 2d step with matAltToPlot % necessary end end end figure();set(gca,'FontSize',14); surf(x,y,matAltToPlot(alt),'LineStyle','none') % filled surface, with color according to altitude % surf(x,y,matAltToPlot(alt),'FaceColor','none') % just the mesh ylabel('Interparticle axis, z') hold on delta1 = asin(rRatio*rho(1)); yC1 = - cos(delta1)/rRatio; delta2 = asin(rho(nRhoVal)); yC2 = lastZ + cos(delta2); % Center of right sphere: x = -rhoMax:rhoMax/40:rhoMax; y = x + lastZ/2; altSph1 = zeros(length(x),length(x)); altSph2 = zeros(length(x),length(x)); for i=1:length(x) for j=1:length(x) alt1 = 1/rRatio^2 - x(j)^2 - (y(i)-yC1)^2; if alt1>=0 altSph1(i,j) = alt1^(1/2); end alt2 = 1 - x(j)^2 - (y(i)- yC2)^2; if alt2>=0 altSph2(i,j) = alt2^(1/2); end end end surf(x,y,matAltToPlot(altSph1),'FaceColor','none') surf(x,y,matAltToPlot(altSph2),'FaceColor','none') caxis([0,rhoMax]) grid off axis square end function retMat = matAltToPlot(matAlt) sizeY = size(matAlt,1);sizeX = size(matAlt,2); plotAlt = ones(sizeY,sizeX); for i = 2:sizeY-1 for j = 2:sizeX-1 if ~matAlt(i,j) test = matAlt(i-1,j-1) || matAlt(i-1,j) || matAlt(i-1,j+1); test = test || matAlt(i,j-1) || matAlt(i,j+1); test = test || matAlt(i+1,j-1) || matAlt(i+1,j) || matAlt(i+1,j+1); if ~test plotAlt(i,j) = 0; end end end end retMat = zeros(sizeY,sizeX); for i = 1:sizeY*sizeX if ~plotAlt(i) retMat(i) = NaN; else retMat(i) = matAlt(i); end end end trunk-2018.02b/examples/capillaryLaplaceYoung/solveLiqBridge.py000066400000000000000000000306131324306050200245600ustar00rootroot00000000000000# J. Duriez (jerome.duriez@irstea.fr) # to import with yade/python/ipython solveLiqBridge.py, or with execfile('solveLiqBridge.py',globals()) once inside a yade/python/ipython session # -- Necessary imports when launching the script directly with python/ipython, not necessary within Yade -- # import numpy # from math import sin,pi,cos,sqrt,tan # --- End of "ipython necessary imports" --- import scipy # for root finding function (see below), requires installing python-scipy package from scipy import optimize def solveLiqBridge(rRatio,theta,uStar,delta1,deltaZ,plot,plot3D,speak): #Computes one single liquid (capillary) bridge # Lian's method: iterative determination using Taylor expansion with # finite difference (FD) increment = deltaZ (a function attribute) # Attributes: rRatio = R2 / R1 >=1, theta = contact angle (deg), uStar = # dimensionless cap. pressure (uc*R2/gamma), delta1 = filling angle on # smallest particle (deg), deltaZ = increment of FD scheme (e.g. 2e-6) # Last ones = booleans to resp. plot the profile in 2D, output messages, # and plot the liq bridge in 3D # Outputs: dist = interparticle distance ; # vol = dimensionless volume (= vol /R2^3) # force = dimensionless force = force/(2*pi*gamma*R2) # delta1/2 = filling angles on the 2 particles (degrees) # eStar = free energy/(gamma* R2^2) # nn11 = 11-term of integral(ni nj dS) / R2^2 = 22-term # nn33 = 33-term, with 3 axis = the meniscus orientation # NB: surface / R2^2 = 2*nn11 + nn33 # out = 1 or 0 depending whether things went fine (see below): 1 if yes # cstC is the dimensionless capillary force: constant all along the profile # see [9] Lian1993, (6) Duriez2017, or (2.51) Soulie2005 ~ (10) Soulie2006.. cstC = 1./rRatio * sin(radians(delta1)) * sin(radians(delta1+theta)) + 1./2. * uStar * rRatio**-2 * sin(radians(delta1))**2; # Use of cstC to get the right filling angle delta2 (# delta1 for rRatio # 1), according to e.g. (2.52) Soulie2005 if rRatio !=1: delta2 = scipy.optimize.brentq(func_delta2,0.,90.,(cstC,theta,uStar)) else: delta2 = delta1 # Discrete set of profile values: rho = [] # a list, should be faster than an array for following append operations, see e.g. https://stackoverflow.com/a/10122262 or http://akuederle.com/create-numpy-array-with-for-loop # Discrete set of slope values: rhoPrime = [] step = 0 # Boundary conditions on left contact line: see left (smallest) particle rho.append( 1./rRatio * sin(radians(delta1)) ) rhoPrime.append( - 1 / tan(radians(delta1+theta)) ) # Boundary condition on right contact line: see right (biggest) particle rhoRight = sin(radians(delta2)); #------------------------------------------------------------------------- # Finite diff. scheme to compute whole profile ie compute rho and rhoPrime # See Lian1993, Duriez2017, etc.. # One remark about next loop: # - just keep a simple and efficient test to stop computations. A more restrictive one may take you beyond the right contact line and lead to divergence.. # tps = time.time() while rho[step] < 1.00001*rhoRight: rho2d = rhoSecond(rho[step],rhoPrime[step],uStar) # all terms are at "i" rho.append( rho[step] + deltaZ * rhoPrime[step] + 1./2. * deltaZ**2 * rho2d ) rhoPrime.append( drho(rho[step+1],rho[step],deltaZ,rho2d) ) # i+1 value of rhoPrime, function of rho_{i+1}, rho_i, and rho2d_i. Used next time in loop. step=step+1 #---------------------------------------------------------------------- # tpsEnd = time.time() # print 'This took'+str(tpsEnd-tps) # --------------- Output computations from now on: -------------------- # tps = time.time() # will have element-wise operations, seems convenient to switch now to arrays rho,rhoPrime = numpy.array(rho),numpy.array(rhoPrime) d1,d2 = radians(delta1),radians(delta2) # Inter particle dimensionless distance: # see (7) Duriez2017, (33) Scholtes2008, etc. (not (5) Soulie2006..) lastZ = deltaZ*(len(rho) -1) dist = lastZ - 1./rRatio * ( 1-cos(d1) ) - ( 1-cos(d2) ) # Dimensionless capillary force force = cstC # Dimensionless volume: # Cf (4) Soulie2006, (34) Appendix Scholtes2008, (8) Duriez2017 etc. : vol = pi*sum( rho[1:]**2 )*deltaZ vol = vol - pi/3 * rRatio**(-3) * ( 1-cos(d1) )**2 * ( 2+cos(d1) ) vol = vol - pi/3 * ( 1-cos(d2) )**2 * ( 2+cos(d2) ) # Capillary bridge dimensionless free energy: # (12) Duriez2017 rather than [35] Lian1993 (was for cst volume stability) dArea = rho * ( 1+rhoPrime**2)**0.5 # ~ infinitesimal liquid gas area eStar = 2*pi * sum(dArea[1:]) * deltaZ + uStar * vol #+/- uStar changes the values but not the shape eStar = eStar - 2*pi*cos(radians(theta)) * ( (1-cos(d1))/rRatio**2 + 1 - cos(d2) ) rhoRed,rhoPrRed = rho[1:len(rho)], rhoPrime[1:len(rho)] # Surface: surf = 2*pi * sum( rhoRed * (1 + rhoPrRed**2)**0.5 ) * deltaZ # Surface integral of dyadic product n*n (diagonal axisymmetric matrix): nn11 = pi * sum( rhoRed / (1 + rhoPrRed**2)**0.5 ) * deltaZ # = n22 nn33 = 2*pi * sum( rhoRed * rhoPrRed**2 / (1 + rhoPrRed**2)**0.5 ) * deltaZ # --------------- Miscellaneous checks and plots -------------------------- # Check whether surf = tr(integral n*n) if (surf - (2*nn11 + nn33) ) / surf > 0.0025: # strict equality beyond reach print 'surf =', surf print 'Vs 2*nn11 + nn33 =', 2*nn11 + nn33 startStr = 'r = ' + str(rRatio) + ';theta = '+ str(theta) + ';u* = ' + str(uStar) raise BaseException('Liq-gas area # tr(integral over liq-gas surf. n*n) for ' + startStr + ';delta1 = '+ str(delta1),';dZ = '+ str(deltaZ)) if plot==1 or plot3D==1: import matplotlib # allows to call all functions f from matplotlib with matplotlib.f from matplotlib import pyplot # as pyplot is a submodule (and not a function), this is also necessary to call pyplot functions f with pyplot.f pyplot.close('all') # matplotlib.rc('text', usetex=True) # http://matplotlib.org/api/matplotlib_configuration_api.html#matplotlib.rc and https://stackoverflow.com/questions/13338550/typing-greek-letters-etc-in-python-plots pyplot.ion() if plot==1: plotProfile(rho,deltaZ,rRatio,delta1,delta2,theta,cstC,uStar) if plot3D==1: plot3Dbridge(rho,deltaZ,rRatio) # Check if the neck has been reached: (there is a risk because of divergence possibility) if uStar!= 0: # "y0" computation according to [10] Lian1993, (or (2.54) Soulie2005 - (11) Soulie2006) rhoNeck = ( -1 + sqrt(1+2*uStar*cstC) ) / uStar else: rhoNeck = cstC if (min(rho) - rhoNeck) / rhoNeck > 0.002: strU,strD1,strDz = str(uStar),str(delta1),str(deltaZ) if speak != 0: print 'Profile with u*='+strU+', delta1='+strD1+' and deltaZ='+strDz+' has not reached its neck !' out = 0 else: out = 1 # tpsEnd = time.time() # print 'This took'+str(tpsEnd-tps) return dist,vol,force,delta1,delta2,eStar,nn11,nn33,out def radians(angle): return angle * pi / 180 def func_delta2(delta2,cstC,theta,uStar): # from e.g. (2.52) Soulie2005 return sin(radians(delta2)) * sin(radians(delta2+theta)) + 1./2. * uStar * sin(radians(delta2))**2 - cstC def drho(rho,prevRho,deltaZ,rho2d): # returns rhoPrime at i+1, see (4) Duriez2017 #drho = sqrt( ( rho/(cstK - 1./2.*uStar*rho**2) )**2 - 1 ) # [11] Lian1993 is always positiv, which is not true return (rho - prevRho) / deltaZ + 1./2.*deltaZ * rho2d; # NB: the rho2d term has a real influence def rhoSecond(rho,rhoP,uStar): # see e.g. (5) Duriez2017 return (1+rhoP**2) / rho + uStar*(1+rhoP**2)**1.5 def plotProfile(rho,deltaZ,rRatio,delta1,delta2,theta,cstC,uStar): from matplotlib import pyplot # could not achieve calling pyplot.plot from here without putting this line directly here. if uStar != 0: # "y0" computation, see e.g. [10] Lian1993, (2.54) Soulie2005, (11) Soulie2006.. rhoNeck = ( -1 + sqrt(1+2*uStar*cstC) ) / uStar else: rhoNeck = cstC lastZ = deltaZ*(len(rho) -1) vecZ = deltaZ*numpy.arange(len(rho)) #numpy.arange(a=0,b) does not include b # Toroidal profile: see [15]-[17] Lian1993, for r=1 if rRatio ==1: d1Rad,d2Rad,thRad = radians(delta1),radians(delta2),radians(theta) dist = lastZ - (1-cos(d1Rad)) - (1-cos(d2Rad)) rho1 = ( dist/2. + 1 - cos(d1Rad) )/cos(d2Rad+thRad) rho2 = sin(d2Rad) - (1-sin(d2Rad+thRad)) * ( dist/2 + 1-cos(d2Rad) ) / cos(d2Rad+thRad) torProfile = rho1 + abs(rho2) - ( rho1**2 - (vecZ-lastZ/2)**2 )**0.5 # toroidal equation pyplot.figure() pyplot.plot(vecZ,rho,label='Profile') pyplot.plot([0,lastZ],[1./rRatio * sin(radians(delta1)),1./rRatio * sin(radians(delta1))],'r',label='Left value') pyplot.plot([0,lastZ],[rhoNeck,rhoNeck],'m',label='Neck value') pyplot.plot([0,lastZ],[sin(radians(delta2)),sin(radians(delta2))],'g',label='Right value') if rRatio ==1: pyplot.plot(vecZ[::int(len(rho)/50.)],torProfile[::int(len(rho)/50.)],'ob',label='Toroidal profile'); pyplot.legend() pyplot.xlabel('Interparticle axis, z') pyplot.ylabel('Profile height, rho(z)') pyplot.title('Filling angle delta1 = '+str(delta1)+' degrees') pyplot.show() def plot3Dbridge(rho,deltaZ,rRatio): from matplotlib import pyplot # same remark as above from mpl_toolkits.mplot3d import Axes3D # to plot 3D figure, using projection='3d' below, in particular from matplotlib import cm nPtsAlongAxis = 200 # grid-step along "rho" and z axis (the x,y plane of the 3D figure in fact) nRhoVal,rhoMax = len(rho),max(rho) lastZ = deltaZ*(nRhoVal -1) # discretization of x axis of 3D figure x = numpy.arange(-rhoMax,rhoMax,rhoMax/nPtsAlongAxis) if not rhoMax in x: # most often rhoMax won't be in x, as numpy.arange usually does not include the stop attribute x=numpy.append(x,rhoMax) # discretization of y axis of 3D figure (= z axis from Fig. 1 Duriez2017), nPtsAlongAxis values, from 0 to lastZ: y = numpy.arange(nPtsAlongAxis)*lastZ/(nPtsAlongAxis-1) # the matrix, with elevation data (MATLAB-like) alt = numpy.zeros((len(y),len(x))) for i in range(len(y)): indRho = int( (i-1)*(nRhoVal-1)/(nPtsAlongAxis) ) + 1 for j in range(len(x)): if rho[indRho]**2 - x[j]**2 >=0: alt[i,j] = rho[indRho] * sqrt(rho[indRho]**2 - x[j]**2) / rho[indRho] fig = pyplot.figure() ax = fig.gca(projection='3d') x,y = numpy.meshgrid(x,y) surf = ax.plot_surface(x,y,alt,linewidth=0,cmap=cm.coolwarm,vmin = 0,vmax=rhoMax) pyplot.ylabel('Interparticle axis, z') # TODO: I can not include a colorbar.. # fig.colorbar(surf) # pyplot.colorbar(surf,ax=ax) pyplot.show() # TODO: plot the spherical particle caps, like in MATLAB... # delta1 = asin(rRatio*rho[1]); # yC1 = - cos(delta1)/rRatio; # delta2 = asin(rho(nRhoVal)); # yC2 = lastZ + cos(delta2); # Center of right sphere: # x = -rhoMax:rhoMax/40:rhoMax; # y = x + lastZ/2; # altSph1 = zeros(len(x),len(x)); # altSph2 = zeros(len(x),len(x)); # for i=1:len(x) # for j=1:len(x) # alt1 = 1./rRatio**2 - x[j]**2 - (y[i]-yC1)**2; # if alt1>=0 # altSph1[i,j] = alt1**0.5; # end # alt2 = 1 - x[j]**2 - (y[i]- yC2)**2; # if alt2>=0 # altSph2[i,j] = alt2**0.5; # end # end # end # surf(x,y,matAltToPlot(altSph1),'FaceColor','none') # surf(x,y,matAltToPlot(altSph2),'FaceColor','none') # caxis([0,rhoMax]) # grid off # axis square # end def matAltToPlot(matAlt): # should help plotting like in MATLAB TODO: actually use it... sizeY,sizeX = numpy.size(matAlt,0), numpy.size(matAlt,1) plotAlt = numpy.ones((sizeY,sizeX)) for i in range(1,sizeY-1): for j in range(1,sizeX-1): if not matAlt[i,j]: test = matAlt[i-1,j-1] or matAlt[i-1,j] or matAlt[i-1,j+1] test = test or matAlt[i,j-1] or matAlt[i,j+1] test = test or matAlt[i+1,j-1] or matAlt[i+1,j] or matAlt[i+1,j+1] if not test: plotAlt[i,j] = 0 retMat = numpy.zeros((sizeY,sizeX)) for i in range(sizeY-1): for j in range(sizeX-1): if not plotAlt[i,j]: retMat[i,j] = numpy.nan else: retMat[i,j] = matAlt[i,j] return retMat trunk-2018.02b/examples/capillaryLaplaceYoung/writesCapFile.m000066400000000000000000000251541324306050200242160ustar00rootroot00000000000000% J. Duriez (jerome.duriez@irstea.fr) function writesCapFile() % Builds capillary files for given radius ratio and contact angle % The historical template of YADE capillary files is kept: each line of % the capillary file is a bridge configuration. Configurations are % grouped by distance values, then (for any distance value) by capillary % pressure values. % The user is expected to modify l. 10 to 27 to taylor the generation to % his needs. % ---------------- Parameters to define by the user ----------------------- % Laplace-Young resolution parameters: deltaZ = 2e-6; % use 2e-6, typically theta = 0; % contact angle, in degrees dDelta1 = 0.3; % delta1 (see solveLiqBridge.m) increment between two configurations % Capillary files construction parameters: nValUc = 350; % # (at most) of capillary pressure values to consider for % one given distance nValDist = 80; % # of distance values (linearly spaced between 0 and some % rupture distance computed in Preliminary A below) to consider % See also if needed l. 147 that defines the capillary file names % List of radius ratio to consider: rRatioVec = [1 1.1 1.25 1.5 1.75 2 3 4 5 10]; % historical rRatio values in YADE % As many capillary files as length(rRatioVec) will be generated suffixe = ''; % string to append at the end of generated files names % ---- End of parameters definitions, no further action is required ! ----- % ---------------- Some data vector initializations ----------------------- % maximum possible interparticle distances for given rRatio: maxD = -ones(length(rRatioVec),1); % Private (J. Duriez) remark: for theta=0; dz=1e-5 or 2e-6; delta1=15:0.2:90; % and rRatioVec = [1;1.1;1.25;1.5;1.75;2;3;4;5], we get here: % maxD = [0.3995;0.3808;0.3567;0.3241;0.2983;0.2772;0.2199;0.1852;0.1615]; % Compares favourably with historical (L. Scholtes') data: dRupt_rMeSch.eps % initial guess (e.g. from L. Scholtes data) of maximum uc values for given rRatio: maxUc_r = [1 1493;1.1 1567;1.25 1781;1.5 2137;1.75 2469;2 2849;3 4274;4 5644;5 6987;10 14370]; % these initial guess are not mandatory but may help save some time below % ---------- Generation of all capillary files really starts here --------- for i =1:length(rRatioVec) rRatio = rRatioVec(i); % ##### Preliminary A: find maxD values ##### % It is considered to be for zero suction (corresponds ~ to Scholtes' data) % And assumed to occur for delta1 greater than 15 deg (> 30 deg in Scholtes) fprintf(['\nFor r = ',num2str(rRatio),', starting search for maximum distance\n']) % fflush(stdout); % Octave command for flush display d1here = 15:0.5:90-theta; % I got the exact same results with 15:1:90 or 15:0.2:90 data = solveLaplace_uc(theta,rRatio,0,d1here,deltaZ,0); maxD(i) = max(data(:,1)); disp(['For r = ',num2str(rRatio),', maximum distance found: maxD = ',num2str(maxD(i))]) disp(''); % fflush(stdout); % Octave command for flush display % #### Preliminary B: computing the set of d* values to consider, valDist #### % -- Following lines are for geometric exponential sequence -- % valDist was a ~ 10-exponential sequence in L. Scholtes' data, but I % (JD) think there was too much small values % valDist = -ones(nValDist,1); % valDist(1)=0;valDist(2) = 5e-5;% 1e-4 in Scholtes maybe too high (cf e.g. F* for r=3) % cst = 1 / (nValDist-2) * log(maxD(i) / valDist(2)); % valDist(3:nValDist) = valDist(2) * exp( ((3:nValDist)'-2) * cst); % -- Anyway, I (JD) think a linear sequence is just as good, or better -- incDist = maxD(i) / (nValDist - 1); valDist = 0:incDist:maxD(i); if length(valDist) ~= nValDist error('We did not construct a consistent set of distances') end % #### Preliminary C: find the greatest admissible suction for r,theta #### % It is considered to be the suction leading, for contacting particles, to % a mean filling angle <= 1 deg (was ~ 2 deg in L. Scholtes' data). d1MaxUc = 0:dDelta1:min(45,90-theta); disp('Starting search for maximum suction');%fflush(stdout); if isempty(find(maxUc_r(:,1)==rRatio,1)) index = find(maxUc_r(:,1) < rRatio,1,'last'); ucTried = floor(maxUc_r(index,2)); else ucTried = floor(maxUc_r(maxUc_r(:,1)==rRatio,2)); end data = solveLaplace_uc(0,rRatio,ucTried,d1MaxUc,deltaZ,0); % Filling angles for contact situation are, if necessary, extrapolated: deltaC = guessDeltaContact(data); while deltaC > 1 prevMaxDelta1 = max(data(:,5)); d1MaxUc = 0:dDelta1:prevMaxDelta1; ucTried = floor(ucTried*1.2); % floor because no need to take care of digits after comma here data = solveLaplace_uc(0,rRatio,ucTried,d1MaxUc,deltaZ,0); deltaC = guessDeltaContact(data); end maxUc = ucTried ; disp(['For r = ',num2str(rRatio),', maximum suction found: final uc = ',num2str(maxUc)]) disp('');%fflush(stdout); % #### End of Preliminary C #### % #### Preliminary D: choosing list of uc* values, valUc ##### % we define valUc as a geometric sequence --with 0 as first term, though-- % so that log(uc*_{i+1}) - log(uc*_i) = cst, and reaching maxUc as the last % (i.e. nValUc-th) term valUc = -ones(nValUc,1); valUc(1)=0;valUc(2) = 0.002;% 0.002 as first positiv uc* value corresponds ~ to Scholtes cst = 1 / (nValUc-2) * log(maxUc / valUc(2))/log(10); valUc(3:nValUc) = valUc(2) * 10.^( ((3:nValUc)'-2) * cst); % #### Preliminaries completed: computing data for all uc values in valUc ! ##### bigData = -ones(nValDist*nValUc,8); % will gather all data lineInBigData = 1; disp('Starting solving for all uc values');tic % fflush(stdout); output = zeros(10);iOut = 1; % these things are just to control some message display for indUc = 1:nValUc % message display: if indUc >= iOut * nValUc / length(output) && output(iOut) == 0 disp([num2str(iOut / length(output)* 100),'% of uc values just completed']) % fflush(stdout); toc;tic; output(iOut) = 0; iOut = iOut+1; end if indUc == 1 delta1Int = 0:dDelta1:90-theta; end smallData = solveLaplace_uc(theta,rRatio,valUc(indUc),delta1Int,deltaZ,0); delta1Int = 0:dDelta1:max(smallData(:,5)); % Filling bigData: nLinesSmallData = length(smallData(:,1)); bigData(lineInBigData:lineInBigData+nLinesSmallData-1,:) = smallData(:,[1:6,8:9]); lineInBigData = lineInBigData + nLinesSmallData; end disp('All uc values computed !');toc % fflush(stdout); % Cleaning and sorting the computed data: bigData = bigData(bigData(:,1)>=0,:); % removal of possible extra data, due to preallocation bigData = sortrows(bigData); % sorts according to increasing distance nomCapFic = ['M(r=',num2str(rRatio),')',suffixe]; fic = fopen(nomCapFic,'w'); % first line of the file=rRatio value: fprintf(fic,[num2str(rRatio),'\n',num2str(nValDist),'\n']);% for direct use in YADE % alternatively, you may want to save some details about the % construction. If yes, replace with the 2 following lines % fprintf(fic,[num2str(rRatio),' ; theta = ',num2str(theta),' ; dDelta1 = ',num2str(dDelta1)]); % fprintf(fic,[' ; deltaZ less or equal than ',num2str(deltaZ),'\n',num2str(nValDist),'\n']); % in this case, you may need to manually suppress these extra % characters before using the capillary file in YADE fclose(fic); % the final data have to conform valDist data. This is achieved through % interpolation, below: for indD =1:nValDist dWanted = valDist(indD); dataCapFile = interpolate(dWanted,valUc,bigData); dlmwrite(nomCapFic,dataCapFile,'-append','delimiter','\t') end end end function deltaC = guessDeltaContact(data) data = sortrows(data); % sorts the data according to increasing distance dist1 = data(1,1);dist2 = data(2,1); % the two lowest computed distances data1 = data(1,:); data2 = data(2,:); % the data for these two distances data_contact = data1 - ( data2 - data1 ) / (dist2 - dist1) * dist1; deltaC = mean(data_contact(5:6)); % mean delta1, delta2 end function outData = interpolate(dist,valUc,bigData) % input bigData is a distance sorted bridges data table (matrix), obtained % directly from solving Laplace-Young equ. It hence shows no specific % distance values pattern. % Output outData is a data table for the given distance "dist" (and many uc % values) which is obtained distance-interpolating from bigData dataGreaterDist = bigData(bigData(:,1)>= dist,:); if isempty(dataGreaterDist) % may happen for maxD, see for instance delta1 % increment = 0.5 for maxD determination (with uc*=0), vs possibly % another value for computing bigData outData = []; out = '\nWARNING: Reaching the last distance value was not possible here. Before use in YADE, you need to'; out = [out,' modify by hand the 2d line (where nValDist appears) of this capillary file:\n']; out = [out,'Just substract 1 to the nValDist value appearing therein\n']; fprintf([out,'Sorry about that..\n\n']); return end maxUc_dist = max(dataGreaterDist(:,2)); valUcOK = valUc(valUc <= maxUc_dist); nValUcOK = length(valUcOK); data_dist = -ones(nValUcOK,8); % all data for one given distance for i = 1:nValUcOK currUc = valUcOK(i); data_uc = bigData(bigData(:,2) == currUc,:); if size(data_uc,1) == 1 % one single liq bridge data is useless nValUcOK = i-1; break end if dist < min(data_uc(:,1)) data1 = data_uc(1,:); data2 = data_uc(2,:); dist1 = data1(1) ; dist2 = data2(1); data_dist(i,:) = data1 + ( data2 - data1 ) / (dist2 - dist1) * (dist - dist1); else data_dInf = data_uc(data_uc(:,1) <= dist,:); % not empty after previous if data_dInf = data_dInf(size(data_dInf,1),:); dInf = data_dInf(1); data_dSup = data_uc(data_uc(:,1) >= dist,:); % not empty either data_dSup = data_dSup(1,:); dSup = data_dSup(1); data_dist(i,:) = data_dInf + (data_dSup-data_dInf)/(dSup-dInf) * (dist-dInf); end end data_dist = data_dist(data_dist(:,2) >= 0,:); % in case we faced the break above if size(data_dist,1) > nValUcOK error('Problem here !') end if size(data_dist,1) == 0 % may happen for dist = maxD out = '\nWARNING: Reaching the last distance value was not possible here. Before use in YADE, you need to'; out = [out,' modify by hand the 2d line (where nValDist appears) of this capillary file:\n']; out = [out,'Just substract 1 to the nValDist value appearing therein\n']; fprintf([out,'Sorry about that..\n\n']); end outData = [nValUcOK 0 0 0 0 0 0 0;data_dist]; end trunk-2018.02b/examples/chained-cylinders/000077500000000000000000000000001324306050200203715ustar00rootroot00000000000000trunk-2018.02b/examples/chained-cylinders/CohesiveCylinderSphere.py000066400000000000000000000034771324306050200253640ustar00rootroot00000000000000# encoding: utf-8 from yade import qt from yade.gridpfacet import * qt.View() O.dt=5e-07 O.materials.append(CohFrictMat(young=8e5,poisson=0.3,density=4e3,frictionAngle=radians(30),normalCohesion=1e5,shearCohesion=1e5,momentRotationLaw=True,label='cylindermat')) O.materials.append(CohFrictMat(young=8e5,poisson=0.3,density=4e3,frictionAngle=radians(30),normalCohesion=1e5,shearCohesion=1e5,momentRotationLaw=False,label='spheremat')) rCyl=0.006 cylindersBodies=[] for i in arange(0,0.3,0.3/15.): cylindersBodies.append(chainedCylinder(begin=Vector3(i,0,0),radius=rCyl,end=Vector3(i+0.3/15.,0,0),fixed=False,wire=False,material='cylindermat')) ChainedState.currentChain=ChainedState.currentChain+1 O.bodies[0].state.blockedDOFs='xyzXYZ' O.bodies[-1].state.blockedDOFs='xyzXYZ' IdSphere=O.bodies.append(sphere([0.15,0,2.*rCyl],rCyl,wire=False,fixed=False,material='spheremat')) def main(): if O.iter>50000 : O.bodies[IdSphere].dynamic=False O.bodies[IdSphere].state.vel[2]=0.1 O.engines=[ ForceResetter(), InsertionSortCollider([ Bo1_ChainedCylinder_Aabb(), Bo1_Sphere_Aabb(), ]), InteractionLoop( #Geometric interactions [Ig2_ChainedCylinder_ChainedCylinder_ScGeom6D(), Ig2_Sphere_ChainedCylinder_CylScGeom6D(), #used for the cohesive sphere-cylinder interaction # Ig2_Sphere_Sphere_ScGeom6D() ], #Physical interactions [Ip2_CohFrictMat_CohFrictMat_CohFrictPhys(setCohesionNow=True,setCohesionOnNewContacts=True,label='ipf'), # Ip2_FrictMat_FrictMat_FrictPhys() ], #LContact Laws: [Law2_ScGeom6D_CohFrictPhys_CohesionMoment(), Law2_CylScGeom6D_CohFrictPhys_CohesionMoment(), #used for the cohesive sphere-cylinder interaction # Law2_ScGeom_FrictPhys_CundallStrack(), ] ), ## Motion equation NewtonIntegrator(gravity=(0,0,0),damping=0.3,label='newton'), PyRunner(command='main()',iterPeriod=1000), ] trunk-2018.02b/examples/chained-cylinders/chained-cylinder-roots.py000066400000000000000000000061251324306050200253150ustar00rootroot00000000000000#--- bruno.chareyre@hmg.inpg.fr --- #!/usr/bin/python # -*- coding: utf-8 -*- # Experiment beam-like behaviour with chained cylinders + CohFrict connexions from yade import pack from yade.gridpfacet import * young=4.0e6 poisson=3 density=1e3 frictionAngle1=radians(15) frictionAngle2=radians(15) frictionAngle3=radians(15) O.dt=5e-4 O.materials.append(FrictMat(young=4000000.0,poisson=0.5,frictionAngle=frictionAngle1,density=1600,label='spheremat')) O.materials.append(FrictMat(young=1.0e6,poisson=0.2,density=2.60e3,frictionAngle=frictionAngle2,label='walllmat')) Ns=90 sp=pack.SpherePack() #cohesive spheres crash because sphere-cylnder functor geneteragets ScGeom3D #O.materials.append(CohFrictMat(young=1.0e5,poisson=0.03,density=2.60e2,frictionAngle=frictionAngle,label='spheremat')) if os.path.exists("cloud4cylinders"+`Ns`): print "loading spheres from file" sp.load("cloud4cylinders"+`Ns`) else: print "generating spheres" Ns=sp.makeCloud(Vector3(-0.3,0.2,-1.0),Vector3(+0.3,+0.5,+1.0),-1,.2,Ns,False,0.8) sp.save("cloud4cylinders"+`Ns`) O.bodies.append([sphere(center,rad,material='spheremat') for center,rad in sp]) walls=aabbWalls((Vector3(-0.3,-0.15,-1),Vector3(+0.3,+1.0,+1)),thickness=.1,material='walllmat') wallIds=O.bodies.append(walls) O.initializers=[ BoundDispatcher([Bo1_Sphere_Aabb(),Bo1_ChainedCylinder_Aabb(),Bo1_Box_Aabb()]) ] O.engines=[ ForceResetter(), InsertionSortCollider([ Bo1_ChainedCylinder_Aabb(), Bo1_Sphere_Aabb(), Bo1_Box_Aabb() ]), InteractionLoop( [Ig2_ChainedCylinder_ChainedCylinder_ScGeom6D(), Ig2_Sphere_ChainedCylinder_CylScGeom(), Ig2_Sphere_Sphere_ScGeom(),Ig2_Box_Sphere_ScGeom()], [Ip2_CohFrictMat_CohFrictMat_CohFrictPhys(setCohesionNow=True,setCohesionOnNewContacts=False,label='ipf'),Ip2_FrictMat_FrictMat_FrictPhys()], [Law2_ScGeom6D_CohFrictPhys_CohesionMoment(label='law'),Law2_ScGeom_FrictPhys_CundallStrack(),Law2_CylScGeom_FrictPhys_CundallStrack()] ), ## Motion equation NewtonIntegrator(damping=0.05,label='newton',gravity=[1,-9.81,0]), ] #Assemble cylinders in sinusoidal shapes O.materials.append(CohFrictMat(young=young,poisson=poisson,density=3.0*density,frictionAngle=frictionAngle3,normalCohesion=1e40,shearCohesion=1e40,momentRotationLaw=True,label='cylindermat')) Ne=30 dy=0.03 dx=0.2 dz=0.2 Nc=1 #nb. of additional chains for j in range(-Nc, Nc+1): dyj = abs(float(j))*dy dxj = abs(float(j))*dx dzj = float(j)*dz for i in range(0, Ne): omega=20/float(Ne); hy=0.0; hz=0.05; hx=1.5; px=float(i)*hx/float(Ne)-0.8+dxj; py=sin(float(i)*omega)*hy+dyj; pz=cos(float(i)*omega)*hz+dzj; px2=float(i+1.)*hx/float(Ne)-0.8+dxj; py2=sin(float(i+1.)*omega)*hy+dyj; pz2=cos(float(i+1.)*omega)*hz+dzj; chainedCylinder(begin=Vector3(pz,py,px), radius=0.02,end=Vector3(pz2,py2,px2),color=Vector3(0.6,0.5,0.5),material='cylindermat') if (i == Ne-1): #close the chain with a node of size 0 print "closing chain" b=chainedCylinder(begin=Vector3(pz2,py2,px2), radius=0.02,end=Vector3(pz2,py2,px2),color=Vector3(0.6,0.5,0.5),material='cylindermat') b.state.blockedDOFs='xyzXYZ' ChainedState.currentChain=ChainedState.currentChain+1 O.saveTmp() trunk-2018.02b/examples/chained-cylinders/chained-cylinder-spring.py000066400000000000000000000035751324306050200254570ustar00rootroot00000000000000#--- bruno.chareyre@hmg.inpg.fr --- #!/usr/bin/python # -*- coding: utf-8 -*- # Experiment beam-like behaviour with chained cylinders + CohFrict connexions from yade.gridpfacet import * young=1.0e6 poisson=4 density=2.60e3 frictionAngle=radians(30) O.materials.append(CohFrictMat(young=young,poisson=poisson,density=density,frictionAngle=frictionAngle,normalCohesion=1e13,shearCohesion=1e13,momentRotationLaw=True,label='mat')) O.dt=1e-4 O.engines=[ ForceResetter(), InsertionSortCollider([ Bo1_ChainedCylinder_Aabb(), Bo1_Sphere_Aabb() ]), InteractionLoop( [Ig2_ChainedCylinder_ChainedCylinder_ScGeom6D(),Ig2_Sphere_ChainedCylinder_CylScGeom()], [Ip2_CohFrictMat_CohFrictMat_CohFrictPhys(setCohesionNow=True,setCohesionOnNewContacts=True)], [Law2_ScGeom6D_CohFrictPhys_CohesionMoment(label='law')] ), ## Motion equation NewtonIntegrator(damping=0.15,gravity=[0,-9.81,0]), PyRunner(iterPeriod=500,command='history()'), #PyRunner(iterPeriod=5000,command='if O.iter<21000 : yade.qt.center()') ] #Generate a spiral Ne=200 for i in range(0, Ne): omega=60.0/float(Ne); hy=0.10; hz=0.15; px=float(i)*(omega/60.0); py=sin(float(i)*omega)*hy; pz=cos(float(i)*omega)*hz; px2=float(i+1.)*(omega/60.0); py2=sin(float(i+1.)*omega)*hy; pz2=cos(float(i+1.)*omega)*hz; chainedCylinder(begin=Vector3(pz,py,px), radius=0.005,end=Vector3(pz2,py2,px2),color=Vector3(0.6,0.5,0.5)) def outp(id=1): for i in O.interactions: if i.id1 == 1: print i.phys.shearForce print i.phys.normalForce return i O.bodies[Ne-1].state.blockedDOFs='xyzXYZ' yade.qt.View(); #plot some results from yade import plot plot.plots={'t':('pos1',None,'vel1')} def history(): plot.addData(pos1=O.bodies[0].state.pos[1], # potential elastic energy vel1=O.bodies[0].state.vel[1], t=O.time) #yade.qt.Renderer().bound=True plot.plot(subPlots=False) O.saveTmp() #O.bodies[0].state.angVel=Vector3(0.05,0,0) trunk-2018.02b/examples/clumps/000077500000000000000000000000001324306050200163075ustar00rootroot00000000000000trunk-2018.02b/examples/clumps/addToClump-example.py000066400000000000000000000042041324306050200223460ustar00rootroot00000000000000#!/usr/bin/python # -*- coding: utf-8 -*- '''This example shows usage of addToClump() and appendClumped().''' from yade import pack,export,qt #define material for all bodies: id_Mat=O.materials.append(FrictMat(young=1e6,poisson=0.3,density=1000,frictionAngle=1)) Mat=O.materials[id_Mat] #define engines: O.engines=[ ForceResetter(), InsertionSortCollider([Bo1_Sphere_Aabb(),Bo1_Box_Aabb()]), InteractionLoop( [Ig2_Sphere_Sphere_ScGeom(),Ig2_Box_Sphere_ScGeom()], [Ip2_FrictMat_FrictMat_FrictPhys()], [Law2_ScGeom_FrictPhys_CundallStrack()] ), NewtonIntegrator(damping=0.7,gravity=[0,0,-10]) ] #create a box: id_box = O.bodies.append(box((0,0,0),(2,2,.1),fixed=True,material=Mat)) #### show how to use appendClumped(): #create 2 clumps: clump1=O.bodies.appendClumped([\ sphere([0,0,1], material=Mat, radius=0.5),\ sphere([0.2,0,1], material=Mat, radius=0.5)\ ]) clump2=O.bodies.appendClumped([\ sphere([3,1,2], material=Mat, radius=0.5),\ sphere([3.2,1,2], material=Mat, radius=0.5)\ ]) #get clump ids: id_clump1 = clump1[0] id_clump2 = clump2[0] #definition for getting informations from all clumps: def getClumpInfo(): for b in O.bodies: if b.isClump: print 'Clump ',b.id,' has following members:' keys = b.shape.members.keys() for ii in range(0,len(keys)): print '- Body ',keys[ii] print 'inertia:',b.state.inertia print 'mass:',b.state.mass,'\n' #### show how to use addToClump(): #create a new sphere: id_new=O.bodies.append(sphere([0,0.2,1], material=Mat, radius=0.5)) print '\nSTATE before adding sphere to clump ------------' getClumpInfo() #add a sphere to the clump: O.bodies.addToClump([id_new],id_clump1) print '\nSTATE after adding sphere to clump ------------' getClumpInfo() #add a clump to a clump: O.bodies.addToClump([id_clump2],id_clump1) print '\nSTATE after adding the second clump to clump ------------' getClumpInfo() #try to add clump member to a clump (should give error message): #O.bodies.addToClump(1,id_clump1) #try to add clump to the same clump (should give error message): #O.bodies.addToClump(id_clump1,id_clump1) O.dt=1e-6 print '\nPress Play button ... ' renderer = qt.Renderer() qt.View() trunk-2018.02b/examples/clumps/apply-buoyancy-clumps.py000066400000000000000000000122031324306050200231340ustar00rootroot00000000000000!/usr/bin/python # -*- coding: utf-8 -*- ''' The script shows how to include the effect of buoyancy in a particle assembly under condition of an increasing water-level. Water-level at start of the sim- ulation is at the lower boundary of the model. During the calculation particles get partly surrounded by water and buoyancy (=fluidDensity*volumeOfDisplacedWater) is increasing until particle is completely surrounded. When particle is sur- rounded volumeOfDisplacedWater is equal to the volume of the particle. For calculation of buoyancy of a clump the equivalent radius R = (3*clumpMass/(4*pi*particleDensity))^1/3 of a sphere with clump mass and clump volume V = (4*pi*R^3)/3 = clumpMass/particleDensity is used. This approximation can be used for well rounded clumps. Buoyancy is included with an additional force F_buo = -volumeOfDisplacedWater*fluidDensity*gravityAcceleration.''' #define material properties: shearModulus = 3.2e10 poissonRatio = 0.15 youngModulus = 2*shearModulus*(1+poissonRatio) angle = 0.5 #friction angle in radians rho_p = 2650 #density of particles rho_f = 5000 #density of the fluid rho_f > rho_p = floating particles v_iw = 1 #velocity of increasing water-level #model boundaries: boundaryMin = Vector3(-1.5,-1.5,0) boundaryMax = Vector3(1.5,1.5,2) #define colors: sphereColor = (.8,.8,0.)#dirty yellow clumpColor = (1.,.55,0.)#dark orange boxColor = (.1,.5,.1)#green waterColor = (.2,.2,.7)#blue #material: id_Mat=O.materials.append(FrictMat(young=youngModulus,poisson=poissonRatio,density=rho_p,frictionAngle=angle)) Mat=O.materials[id_Mat] #create assembly of spheres: sp=pack.SpherePack() sp.makeCloud(minCorner=boundaryMin,maxCorner=boundaryMax,rMean=.2,rRelFuzz=.5,num=100,periodic=False) O.bodies.append([sphere(c,r,material=Mat,color=sphereColor) for c,r in sp]) #create template for clumps and replace 10% of spheres by clumps: templateList = [clumpTemplate(relRadii=[1,.5],relPositions=[[0,0,0],[.7,0,0]])] O.bodies.replaceByClumps(templateList,[.1]) #color clumps: for b in O.bodies: if b.isClumpMember: b.shape.color=clumpColor #create boundary: O.bodies.append(geom.facetBox((0,0,1), (boundaryMax-boundaryMin)/2, fixed=True, material=Mat, color=boxColor)) #define engines: O.engines=[ ForceResetter(), InsertionSortCollider([Bo1_Sphere_Aabb(),Bo1_Facet_Aabb()]), InteractionLoop( [Ig2_Sphere_Sphere_ScGeom(),Ig2_Facet_Sphere_ScGeom()], [Ip2_FrictMat_FrictMat_MindlinPhys()], [Law2_ScGeom_MindlinPhys_Mindlin(neverErase=False)] ), NewtonIntegrator(gravity=(0,0,-10),damping=0.7,label='integrator') ] #definition to apply buoyancy: def applyBuoyancy(): global waterLevel waterLevel = (O.time-t0) * v_iw + boundaryMin[2] for b in O.bodies: zMin = 1e9 zMax = -1e9 if b.isStandalone and isinstance(b.shape,Sphere): rad = b.shape.radius zMin = b.state.pos[2] - rad dh = min((waterLevel - zMin),2*rad) #to get sure, that dh is not bigger than 2*radius elif b.isClump: #determine rad, zMin and zMax for clumps: for ii in b.shape.members.keys(): pos = O.bodies[ii].state.pos zMin = min(zMin,pos[2]-O.bodies[ii].shape.radius) zMax = max(zMax,pos[2]+O.bodies[ii].shape.radius) #get equivalent radius from clump mass: rad = ( 3*b.state.mass/(4*pi*O.bodies[b.shape.members.keys()[0]].mat.density) )**(1./3.) #get dh relative to equivalent sphere, but acting when waterLevel is between clumps z-dimensions zMin and zMax: dh = min((waterLevel - zMin)*2*rad/(zMax - zMin),2*rad) else: continue if dh > 0: F_buo = -1*(pi/3)*dh*dh*(3*rad - dh)*rho_f*integrator.gravity # = -V*rho*g O.forces.setPermF(b.id,F_buo) #STEP1: reduce overlaps from replaceByClumps() method: O.dt=1e-6 #small time step for preparation steps via calm() print '\nSTEP1 in progress. Please wait a minute ...\n' O.engines=O.engines+[PyRunner(iterPeriod=10000,command='calm()',label='calmRunner')] O.run(100000,True) #STEP2: let particles settle down calmRunner.dead=True O.dt=2e-5 print '\nSTEP2 in progress. Please wait a minute ...\n' O.run(50000,True) #start PyRunner engine to apply buoyancy: t0 = O.time waterLevel = boundaryMin[2] O.engines=O.engines+[PyRunner(iterPeriod=100,command='applyBuoyancy()',label='buoLabel')] #create 2 facets, that show water height: idsWaterFacets = [] idsWaterFacets.append(O.bodies.append(facet( \ [ boundaryMin, [boundaryMax[0],boundaryMin[1],boundaryMin[2]], [boundaryMax[0],boundaryMax[1],boundaryMin[2]] ], \ fixed=True,material=FrictMat(young=0),color=waterColor,wire=False)))#no interactions will appear idsWaterFacets.append(O.bodies.append(facet( \ [ [boundaryMax[0],boundaryMax[1],boundaryMin[2]], [boundaryMin[0],boundaryMax[1],boundaryMin[2]], boundaryMin ], \ fixed=True,material=FrictMat(young=0),color=waterColor,wire=False)))#no interactions will appear #set velocity of incr. water level to the facets: for ids in idsWaterFacets: O.bodies[ids].state.vel = [0,0,v_iw] #STEP3: simulate buoyancy with increasing water table condition O.dt=3e-5 from yade import qt qt.Controller() v=qt.View() v.eyePosition=(-7,0,2); v.upVector=(0,0,1); v.viewDir=(1,0,-.1); v.axes=True; v.sceneRadius=1.9 print '\nSTEP3 started ...\n' O.run(70000) trunk-2018.02b/examples/clumps/clump-hopper-test.py000066400000000000000000000051041324306050200222510ustar00rootroot00000000000000# -*- coding: utf-8 from yade import pack,export,qt,geom import itertools from numpy import * kinEnergyMax = 100000 # Parameters tc=0.001# collision time en=.3 # normal restitution coefficient es=.3 # tangential restitution coefficient frictionAngle=radians(35)# density=2700 # facets material facetMat=O.materials.append(ViscElMat(frictionAngle=frictionAngle,tc=tc,en=en,et=es)) # default spheres material dfltSpheresMat=O.materials.append(ViscElMat(density=density,frictionAngle=frictionAngle,tc=tc,en=en,et=es)) O.dt=.05*tc # time step Rs=0.05 # particle radius # Create geometry x0=0.; y0=0.; z0=0.; ab=.7; at=2.; h=1.; hl=h; al=at*3 zb=z0; x0b=x0-ab/2.; y0b=y0-ab/2.; x1b=x0+ab/2.; y1b=y0+ab/2. zt=z0+h; x0t=x0-at/2.; y0t=y0-at/2.; x1t=x0+at/2.; y1t=y0+at/2. zl=z0-hl;x0l=x0-al/2.; y0l=y0-al/2.; x1l=x0+al/2.; y1l=y0+al/2. vibrationPlate = O.bodies.append(geom.facetBunker((x0,y0,z0), dBunker=ab*4.0,dOutput=ab*1.7,hBunker=ab*3,hOutput=ab,hPipe=ab/3.0, wallMask=4,segmentsNumber=10,material=facetMat)) # Create engines O.engines=[ ForceResetter(), InsertionSortCollider([Bo1_Sphere_Aabb(),Bo1_Facet_Aabb()]), InteractionLoop( [Ig2_Sphere_Sphere_ScGeom(), Ig2_Facet_Sphere_ScGeom()], [Ip2_ViscElMat_ViscElMat_ViscElPhys()], [Law2_ScGeom_ViscElPhys_Basic()], ), DomainLimiter(lo=(-4,-4,-1),hi=(4,4,4),iterPeriod=5000,label='domLim'), NewtonIntegrator(damping=0, gravity=[0,0,-9.81]), PyRunner(iterPeriod=6500,command='addBodies()',nDo=7,label='addb'), PyRunner(iterPeriod=1000,command='state()',label='state'), ] numSphereGen = 0 def addBodies(): global numSphereGen # Create clumps... clumpColor=(0.0, 0.5, 0.5) for k,l in itertools.product(arange(0,10),arange(0,10)): clpId,sphId=O.bodies.appendClumped([sphere(Vector3(x0t+Rs*(k*4+2),y0t+Rs*(l*4+2),i*Rs*2+zt+ab*3),Rs,color=clumpColor,material=dfltSpheresMat) for i in xrange(4)]) numSphereGen += len(sphId) # ... and spheres spheresColor=(0.4, 0.4, 0.4) for k,l in itertools.product(arange(0,9),arange(0,9)): sphAloneId=O.bodies.append( [sphere( Vector3(x0t+Rs*(k*4+4),y0t+Rs*(l*4+4),i*Rs*2.3+zt+ab*3),Rs,color=spheresColor,material=dfltSpheresMat) for i in xrange(4) ] ) numSphereGen += len(sphAloneId) def state(): global numSphereGen print "Iter %d: Total number of generated spheres %d, removed particles %d, current particles %d, kinEnergy %g"%(O.iter, numSphereGen, domLim.nDeleted, numSphereGen-domLim.nDeleted, utils.kineticEnergy()) if (utils.kineticEnergy() > kinEnergyMax): print "Kinetic energy is over a threshold value! Error!" addBodies() from yade import qt qt.View() O.saveTmp() #O.run(50001) trunk-2018.02b/examples/clumps/clump-hopper-viscoelastic.py000066400000000000000000000056541324306050200237740ustar00rootroot00000000000000# -*- coding: utf-8 from yade import pack,export,qt import gts,os,random,itertools from numpy import * # Parameters tc=0.001# collision time en=.3 # normal restitution coefficient es=.3 # tangential restitution coefficient frictionAngle=radians(35)# density=2700 # facets material facetMat=O.materials.append(ViscElMat(frictionAngle=frictionAngle,tc=tc,en=en,et=es)) # default spheres material dfltSpheresMat=O.materials.append(ViscElMat(density=density,frictionAngle=frictionAngle,tc=tc,en=en,et=es)) O.dt=.05*tc # time step Rs=0.05 # particle radius # Create geometry x0=0.; y0=0.; z0=0.; ab=.7; at=2.; h=1.; hl=h; al=at*3 zb=z0; x0b=x0-ab/2.; y0b=y0-ab/2.; x1b=x0+ab/2.; y1b=y0+ab/2. zt=z0+h; x0t=x0-at/2.; y0t=y0-at/2.; x1t=x0+at/2.; y1t=y0+at/2. zl=z0-hl;x0l=x0-al/2.; y0l=y0-al/2.; x1l=x0+al/2.; y1l=y0+al/2. left = pack.sweptPolylines2gtsSurface([[Vector3(x0b,y0b,zb),Vector3(x0t,y0t,zt),Vector3(x0t,y1t,zt),Vector3(x0b,y1b,zb)]],capStart=True,capEnd=True) lftIds=O.bodies.append(pack.gtsSurface2Facets(left,material=facetMat,color=(0,1,0))) right = pack.sweptPolylines2gtsSurface([[Vector3(x1b,y0b,zb),Vector3(x1t,y0t,zt),Vector3(x1t,y1t,zt),Vector3(x1b,y1b,zb)]],capStart=True,capEnd=True) rgtIds=O.bodies.append(pack.gtsSurface2Facets(right,material=facetMat,color=(0,1,0))) near = pack.sweptPolylines2gtsSurface([[Vector3(x0b,y0b,zb),Vector3(x0t,y0t,zt),Vector3(x1t,y0t,zt),Vector3(x1b,y0b,zb)]],capStart=True,capEnd=True) nearIds=O.bodies.append(pack.gtsSurface2Facets(near,material=facetMat,color=(0,1,0))) far = pack.sweptPolylines2gtsSurface([[Vector3(x0b,y1b,zb),Vector3(x0t,y1t,zt),Vector3(x1t,y1t,zt),Vector3(x1b,y1b,zb)]],capStart=True,capEnd=True) farIds=O.bodies.append(pack.gtsSurface2Facets(far,material=facetMat,color=(0,1,0))) table = pack.sweptPolylines2gtsSurface([[Vector3(x0l,y0l,zl),Vector3(x0l,y1l,zl),Vector3(x1l,y1l,zl),Vector3(x1l,y0l,zl)]],capStart=True,capEnd=True) tblIds=O.bodies.append(pack.gtsSurface2Facets(table,material=facetMat,color=(0,1,0))) # Create clumps... clumpColor=(0.0, 0.5, 0.5) for k,l in itertools.product(arange(0,10),arange(0,10)): clpId,sphId=O.bodies.appendClumped([sphere(Vector3(x0t+Rs*(k*4+2),y0t+Rs*(l*4+2),i*Rs*2+zt),Rs,color=clumpColor,material=dfltSpheresMat) for i in xrange(4)]) # ... and spheres spheresColor=(0.4, 0.4, 0.4) for k,l in itertools.product(arange(0,9),arange(0,9)): sphAloneId=O.bodies.append( [sphere( Vector3(x0t+Rs*(k*4+4),y0t+Rs*(l*4+4),i*Rs*2.3+zt),Rs,color=spheresColor,material=dfltSpheresMat) for i in xrange(4) ] ) # Create engines O.engines=[ ForceResetter(), InsertionSortCollider([Bo1_Sphere_Aabb(),Bo1_Facet_Aabb()]), InteractionLoop( [Ig2_Sphere_Sphere_ScGeom(), Ig2_Facet_Sphere_ScGeom()], [Ip2_ViscElMat_ViscElMat_ViscElPhys()], [Law2_ScGeom_ViscElPhys_Basic()], ), NewtonIntegrator(damping=0, gravity=[0,0,-9.81]), #VTKRecorder(virtPeriod=0.01,fileName='/tmp/',recorders=['spheres','velocity','facets']) ] from yade import qt qt.View() O.saveTmp() trunk-2018.02b/examples/clumps/clump-inbox-viscoelastic.py000066400000000000000000000024771324306050200236160ustar00rootroot00000000000000# -*- coding: utf-8 from yade import pack,export,qt,geom import gts,os,random,itertools from numpy import * # Parameters tc=0.001# collision time en=.3 # normal restitution coefficient es=.3 # tangential restitution coefficient frictionAngle=radians(35)# density=2700 # facets material facetMat=O.materials.append(ViscElMat(frictionAngle=frictionAngle,tc=tc,en=en,et=es)) # default spheres material dfltSpheresMat=O.materials.append(ViscElMat(density=density,frictionAngle=frictionAngle,tc=tc,en=en,et=es)) O.dt=.05*tc # time step Rs=0.1 # particle radius # Create geometry box = O.bodies.append(geom.facetBox((0,0,0),(1,1,1),wallMask=31,material=facetMat)) # Create clumps... for j in xrange(10): clpId,sphId=O.bodies.appendClumped([sphere(Vector3(0,Rs*2*i,(j+1)*Rs*2),Rs,material=dfltSpheresMat) for i in xrange(4)]) # ... and spheres sphAloneId=O.bodies.append( [sphere( Vector3(0.5,Rs*2*i,(j+1)*Rs*2), Rs, material=dfltSpheresMat) for i in xrange(4) ] ) # Create engines O.engines=[ ForceResetter(), InsertionSortCollider([Bo1_Sphere_Aabb(),Bo1_Facet_Aabb()]), InteractionLoop( [Ig2_Sphere_Sphere_ScGeom(), Ig2_Facet_Sphere_ScGeom()], [Ip2_ViscElMat_ViscElMat_ViscElPhys()], [Law2_ScGeom_ViscElPhys_Basic()], ), NewtonIntegrator(damping=0,gravity=[0,0,-9.81]), ] renderer = qt.Renderer() qt.View() O.saveTmp() trunk-2018.02b/examples/clumps/clump-viscoelastic.py000066400000000000000000000055701324306050200224760ustar00rootroot00000000000000# -*- coding: utf-8 from yade import pack,export,qt import gts,os,random,itertools from numpy import * # Parameters tc=0.001# collision time en=.3 # normal restitution coefficient es=.3 # tangential restitution coefficient frictionAngle=radians(35)# density=2700 # facets material facetMat=O.materials.append(ViscElMat(frictionAngle=frictionAngle,tc=tc,en=en,et=es)) # default spheres material dfltSpheresMat=O.materials.append(ViscElMat(density=density,frictionAngle=frictionAngle,tc=tc,en=en,et=es)) O.dt=.01*tc # time step Rs=0.1 # particle radius # Create geometry plnSurf = pack.sweptPolylines2gtsSurface([[Vector3(-.5,0,0),Vector3(.5,0,0),Vector3(.5, 0, -.5),Vector3(-.5, 0, -.5)]],capStart=True,capEnd=True) plnIds=O.bodies.append(pack.gtsSurface2Facets(plnSurf,material=facetMat,color=(0,1,0))) plnSurf1 = pack.sweptPolylines2gtsSurface([[Vector3(-.5,-.5,-.5),Vector3(.5,-.5,-.5),Vector3(.5, 1.5, -.5),Vector3(-.5, 1.5, -.5)]],capStart=True,capEnd=True) plnIds1=O.bodies.append(pack.gtsSurface2Facets(plnSurf1,material=facetMat,color=(0,1,0))) # Create clumps clpId,sphId=O.bodies.appendClumped([sphere(Vector3(0,Rs*2*i,Rs*2),Rs,material=dfltSpheresMat) for i in xrange(4)]) # Create engines O.engines=[ ForceResetter(), InsertionSortCollider([Bo1_Sphere_Aabb(),Bo1_Facet_Aabb()]), InteractionLoop( [Ig2_Sphere_Sphere_ScGeom(), Ig2_Facet_Sphere_ScGeom()], [Ip2_ViscElMat_ViscElMat_ViscElPhys()], [Law2_ScGeom_ViscElPhys_Basic()], ), NewtonIntegrator(damping=0, gravity=[0,0,-9.81]), ] renderer = qt.Renderer() qt.View() O.saveTmp() clump = O.bodies[clpId] spheres = [ O.bodies[id] for id in sphId ] print '\nClump:\n======' #print '\nMaterial:' if clump.mat: print clump.mat.dict() else: print 'no material' print '\nshape:' if clump.shape: print clump.shape.dict() else: print 'no shape' print '\nState:' if clump.state: print clump.state.dict() else: print 'no state' print '\nControl:' mass = sum( [ s.state.mass for s in spheres ] ) xm_ = [ s.state.pos[0]*s.state.mass for s in spheres ] ym_ = [ s.state.pos[1]*s.state.mass for s in spheres ] zm_ = [ s.state.pos[2]*s.state.mass for s in spheres ] centroid = Vector3( sum(xm_)/mass, sum(ym_)/mass, sum(zm_)/mass ) def sphereInertiaTensor(p, m, r, c): ''' Inertia tensor sphere with position p, mass m and radus r relative point c ''' I = zeros((3,3)) rp = array(p)-array(c) I[0,0] = 2./5.*m*r*r + m*(rp[1]**2+rp[2]**2) I[1,1] = 2./5.*m*r*r + m*(rp[0]**2+rp[2]**2) I[2,2] = 2./5.*m*r*r + m*(rp[0]**2+rp[1]**2) for k,l in itertools.product(arange(0,3),arange(0,3)): if not k==l: I[k,l] = -m*rp[k]*rp[l] return I I = zeros((3,3)) for s in spheres: I += sphereInertiaTensor(s.state.pos,s.state.mass,s.shape.radius,centroid) I_eigenvalues, I_eigenvectors = linalg.eig(I) print 'mass = ', mass print 'centroid = ', centroid print 'I = \n', I print 'I_eigenvalues = ', I_eigenvalues print 'I_eigenvectors = \n', I_eigenvectors trunk-2018.02b/examples/clumps/releaseFromClump-example.py000066400000000000000000000037621324306050200235670ustar00rootroot00000000000000#!/usr/bin/python # -*- coding: utf-8 -*- '''This example shows usage of releaseFromClump() and clump().''' #define material for all bodies: id_Mat=O.materials.append(FrictMat(young=1e6,poisson=0.3,density=1000,frictionAngle=1)) Mat=O.materials[id_Mat] #define engines: O.engines=[ ForceResetter(), InsertionSortCollider([Bo1_Sphere_Aabb(),Bo1_Box_Aabb()]), InteractionLoop( [Ig2_Sphere_Sphere_ScGeom(),Ig2_Box_Sphere_ScGeom()], [Ip2_FrictMat_FrictMat_FrictPhys()], [Law2_ScGeom_FrictPhys_CundallStrack()] ), NewtonIntegrator(damping=0.7,gravity=[0,0,-10]) ] #create a box: id_box = O.bodies.append(box((0,0,0),(5,5,.1),fixed=True,material=Mat)) #### show how to use clump(): #create spheres parallel to x-axis: bodyList1 = [] for ii in range(-2,2): bodyList1.append(O.bodies.append(sphere((ii,0,1),radius=.5,material=Mat))) #create spheres parallel to y-axis: bodyList2 = [] for ii in range(-3,2): bodyList2.append(O.bodies.append(sphere((-1,ii,1.5),radius=.5,material=Mat))) #create 2 clumps, and give each clump a different color idClump1=O.bodies.clump(bodyList1) idClump2=O.bodies.clump(bodyList2) for ii in bodyList1: O.bodies[ii].shape.color=(.1,.5,.1) for ii in bodyList2: O.bodies[ii].shape.color=(.2,.2,.7) #definition for getting informations from all clumps: def getClumpInfo(): for b in O.bodies: if b.isClump: print 'Clump ',b.id,' has following members:' keys = b.shape.members.keys() for ii in range(0,len(keys)): print '- Body ',keys[ii] O.dt=1e-6 #### show how to use releaseFromClump(): print '\nSTATE before releasing spheres from clump ------------' getClumpInfo() raw_input('\nPress Play button ... and look what happens.\n Then press Pause button and press ENTER on console!') for ii in bodyList2: if ii > max(bodyList2)-2: clId = O.bodies[ii].clumpId O.bodies.releaseFromClump(ii,clId) print 'Sphere ',ii,' released from clump ',clId print '\nSTATE after releasing spheres from clump ------------' getClumpInfo() print '\nPress Play button again ... ' trunk-2018.02b/examples/clumps/replaceByClumps-example.py000066400000000000000000000045521324306050200234120ustar00rootroot00000000000000#!/usr/bin/python # -*- coding: utf-8 -*- '''This example shows usage of clumpTemplate(), replaceByClumps() and getRoundness().''' #define material for all bodies: id_Mat=O.materials.append(FrictMat(young=1e7,poisson=0.3,density=1000,frictionAngle=1)) Mat=O.materials[id_Mat] #define engines: O.engines=[ ForceResetter(), InsertionSortCollider([Bo1_Sphere_Aabb(),Bo1_Box_Aabb()]), InteractionLoop( [Ig2_Sphere_Sphere_ScGeom(),Ig2_Box_Sphere_ScGeom()], [Ip2_FrictMat_FrictMat_FrictPhys()], [Law2_ScGeom_FrictPhys_CundallStrack()] ), NewtonIntegrator(damping=0.7,gravity=[0,0,-10]) ] from yade import qt qt.Controller() qt.View() #create a box: id_box = O.bodies.append(box((0,0,0),(2,2,.1),fixed=True,material=Mat)) #create assembly of spheres: sp=pack.SpherePack() sp.makeCloud(minCorner=(-1.5,-1.5,.1),maxCorner=(1.5,1.5,2),rMean=.2,rRelFuzz=.5,num=100,periodic=False) O.bodies.append([sphere(c,r,material=Mat) for c,r in sp]) print len(sp),' particles generated.' print 'Roundness coefficient without clumps is: ',O.bodies.getRoundness() #### show how to use makeClumpTemplate(): #dyad: relRadList1 = [1,1] relPosList1 = [[1,0,0],[-1,0,0]] #peanut: relRadList2 = [.5,1,.5] relPosList2 = [[1,0,0],[0,0,0],[-1,0,0]] #stick: relRadList3 = [1,1,1,1,1] relPosList3 = [[0,1,0],[0,2,0],[0,3,0],[0,4,0],[0,5,0]] templates= [] templates.append(clumpTemplate(relRadii=relRadList1,relPositions=relPosList1)) templates.append(clumpTemplate(relRadii=relRadList2,relPositions=relPosList2)) templates.append(clumpTemplate(relRadii=relRadList3,relPositions=relPosList3)) #### show how to use replaceByClumps(): #replace by 50% dyads, 30% peanuts and 10% sticks: O.bodies.replaceByClumps(templates,[.5,.3,.1]) #### show how to use getRoundness(): #create a list of all standalone spheres: standaloneList = [] for b in O.bodies: if b.isStandalone: standaloneList.append(b.id) print 'Roundness coefficient for spheres and clumps is: ',O.bodies.getRoundness() print 'Roundness coefficient just for clumps is: ',O.bodies.getRoundness(standaloneList) O.dt=1e-6 #NOTE, that after replacing some overlaps may occur. #So after replacing calm() function may be helpful: if 0: print '\nPlease wait a minute ...\n' O.engines=O.engines+[PyRunner(iterPeriod=10000,command='calm()',label='calmRunner')] O.run(1000000,True) calmRunner.dead=True print '\nPress Play button ... ' trunk-2018.02b/examples/clumps/save-load-clumps.py000066400000000000000000000037121324306050200220400ustar00rootroot00000000000000#!/usr/bin/python # -*- coding: utf-8 -*- '''This example shows usage of save and load clumps.''' from yade import pack,export,qt,ymport #define material for all bodies: id_Mat=O.materials.append(FrictMat(young=1e6,poisson=0.3,density=1000,frictionAngle=1)) Mat=O.materials[id_Mat] #define engines: O.engines=[ ForceResetter(), InsertionSortCollider([Bo1_Sphere_Aabb(),Bo1_Box_Aabb()]), InteractionLoop( [Ig2_Sphere_Sphere_ScGeom(),Ig2_Box_Sphere_ScGeom()], [Ip2_FrictMat_FrictMat_FrictPhys()], [Law2_ScGeom_FrictPhys_CundallStrack()] ), NewtonIntegrator(damping=0.7,gravity=[0,0,-10]) ] #create a box: id_box = O.bodies.append(box((0,0,0),(2,2,.1),fixed=True,material=Mat)) #### show how to use appendClumped(): #create 2 clumps: clump1=O.bodies.appendClumped([\ sphere([0,0,1], material=Mat, radius=0.5),\ sphere([0.2,0,1], material=Mat, radius=0.5)\ ]) clump2=O.bodies.appendClumped([\ sphere([3,1,2], material=Mat, radius=0.5),\ sphere([3.2,1,2], material=Mat, radius=0.5)\ ]) #get clump ids: id_clump1 = clump1[0] id_clump2 = clump2[0] #definition for getting informations from all clumps: def getClumpInfo(): for b in O.bodies: if b.isClump: print 'Clump ',b.id,' has following members:' keys = b.shape.members.keys() for ii in range(0,len(keys)): print '- Body ',keys[ii] print 'inertia:',b.state.inertia print 'mass:',b.state.mass,'\n' #### show how to use addToClump(): #create a new sphere: id_new=O.bodies.append(sphere([0,0.2,1], material=Mat, radius=0.5)) #add a sphere to the clump: O.bodies.addToClump([id_new],id_clump1) #add a clump to a clump: O.bodies.addToClump([id_clump2],id_clump1) print '\nSTATE after adding the second clump to clump ------------' getClumpInfo() print "Save clumps" export.textClumps("savedClumps.txt") print "Load clumps" ymport.textClumps("savedClumps.txt", shift=Vector3(0,0,1.5)) print "After saving" getClumpInfo() O.dt=1e-6 print '\nPress Play button ... ' renderer = qt.Renderer() qt.View() trunk-2018.02b/examples/clumps/triax-basic-with-clumps.py000066400000000000000000000042101324306050200233360ustar00rootroot00000000000000# encoding: utf-8 # Copyright (C) 2012 by Bruno Chareyre # An update of the original script from Janek Kozicki from yade import pack from numpy import arange import itertools import random import yade.plot ## corners of the initial packing mn,mx=Vector3(0,0,0),Vector3(10,10,10) ## create material #0, which will be used as default O.materials.append(FrictMat(young=6e6,poisson=.4,frictionAngle=radians(5),density=2600)) O.materials.append(FrictMat(young=6e6,poisson=.4,frictionAngle=0,density=2600,label='frictionless')) d=8 # clumps for xyz in itertools.product(arange(0,d),arange(0,d),arange(0,d)): ids_spheres=O.bodies.appendClumped(pack.regularHexa(pack.inEllipsoid((mn[0]+xyz[0]*(mx[0]-mn[0])/d,mn[0]+xyz[1]*(mx[1]-mn[1])/d,mn[2]+xyz[2]*(mx[2]-mn[2])/d),(0.45+random.random()*0.1,0.45+random.random()*0.1,0.45+random.random()*0.1)),radius=0.15+random.random()*0.05,gap=0,color=[random.random(),random.random(),random.random()])) ## create walls around the packing walls=aabbWalls(material='frictionless') wallIds=O.bodies.append(walls) from yade import qt qt.Controller() qt.View() ## hope that we got the ids right?! triax=TriaxialCompressionEngine( wall_bottom_id=wallIds[2], wall_top_id=wallIds[3], wall_left_id=wallIds[0], wall_right_id=wallIds[1], wall_back_id=wallIds[4], wall_front_id=wallIds[5], internalCompaction=False, sigmaIsoCompaction=-50e3, sigmaLateralConfinement=-50e3, strainRate=0.02, frictionAngleDegree=30, StabilityCriterion = 0.01, max_vel=1, ) recorder=TriaxialStateRecorder( iterPeriod=20, file="./WallStresses_clumps", truncate=1 ) O.engines=[ ForceResetter(), InsertionSortCollider([Bo1_Sphere_Aabb(),Bo1_Box_Aabb()]), InteractionLoop( [Ig2_Sphere_Sphere_ScGeom(),Ig2_Box_Sphere_ScGeom()], [Ip2_FrictMat_FrictMat_FrictPhys()], [Law2_ScGeom_FrictPhys_CundallStrack()] ), GlobalStiffnessTimeStepper(timestepSafetyCoefficient=0.6), triax, recorder, # you can add TriaxialStateRecorder and such here… NewtonIntegrator(damping=.4) ] #yade.plot.plots={'eps':('sigma',)} #O.saveTmp('initial'); #def addPlotData(): # yade.plot.addData({'t':O.time,'i':O.iter,'eps':strainer.strain,'sigma':strainer.avgStress}) trunk-2018.02b/examples/concrete/000077500000000000000000000000001324306050200166065ustar00rootroot00000000000000trunk-2018.02b/examples/concrete/README000066400000000000000000000007551324306050200174750ustar00rootroot00000000000000uniax.py shows uniaxiax tension/compression test on concrete specimen with optional confinement. Run using yade-multi and confined.table (see comments in that file) to run simulation with different confinement values. Run plain uniax.py to get one unconfined tension/compression simulation. Type yade.plot.plot() to see strain/stress plot during the simulation. See comments at the beginning of uniax.py for more information. The periodic.py is uniax.py, but running on a periodic sample. trunk-2018.02b/examples/concrete/confined.table000066400000000000000000000010761324306050200214100ustar00rootroot00000000000000# # Use this table with yade-multi like this: # # yade-version-multi --gnuplot confined.gnuplot confined.table uniax.py # # and watch http://localhost:9080 while it runs # # OMP_NUM_THREADS: number of threads to use (bang at the beginning: passed as environment variable) # isoPrestress: confinement (bang at the end = parameter and value will appear in the job id) # doModes: run both tension&compression only in the unconfined case, otherwise just compression # !OMP_NUM_THREADS isoPrestress! doModes 2 0 3 2 -20e6 2 2 -50e6 2 2 -100e6 2 2 -200e6 2 2 -400e6 2 trunk-2018.02b/examples/concrete/interaction-histogram.py000066400000000000000000000023711324306050200234750ustar00rootroot00000000000000#!/usr/bin/python # -*- coding: utf-8 -*- # # demonstration of the yade.post2d module (see its documentation for details) # import pylab # the matlab-like interface of matplotlib pylab.ioff() import numpy import os.path # run uniax.py to get this file loadFile='/tmp/uniax-tension.yade.gz' if not os.path.exists(loadFile): raise RuntimeError("Run uniax.py first so that %s is created"%loadFile) O.load(loadFile) # axis normal to the plane in which we do the histogram axis=0 # x, i.e. plot the yz plane ax1,ax2=(axis+1)%3,(axis+2)%3 ## get the other two indices, i.e. 1 and 2 in this case angles,forces=[],[] for i in O.interactions: if not i.isReal: continue norm=i.geom.normal angle=atan(norm[ax2]/norm[ax1]) force=i.phys.normalForce.norm() angles.append(angle) forces.append(force) # easier: plain histogram #pylab.hist(angles,weights=forces,bins=20) # polar histogram pylab.figure() ## prepare data values,bins=numpy.histogram(angles,weights=forces,bins=20) ## prepare polar plot pylab.subplot(111,polar=True); ## plot bar chart, with the histogram data ### bins has one edge extra, remove it: [:-1] pylab.bar(left=bins[:-1],height=values,width=.7*pi/20); # predefined function pylab.figure() plotDirections(noShow=True).savefig('/tmp/a.pdf') pylab.show() trunk-2018.02b/examples/concrete/periodic.py000066400000000000000000000162771324306050200207730ustar00rootroot00000000000000#!/usr/bin/python # -*- coding: utf-8 -*- from __future__ import division from yade import plot,pack import time, sys, os, copy """ A fairly complex script performing uniaxial tension-compression test on hyperboloid-shaped specimen. Most parameters of the model (and of the setup) can be read from table using yade-multi. After the simulation setup, tension loading is run and stresses are periodically saved for plotting as well as checked for getting below the maximum value so far. This indicates failure (see stopIfDamaged function). After failure in tension, the original setup is loaded anew and the sense of loading reversed. After failure in compression, strain-stress curves are saved via plot.saveGnuplot and we exit, giving some useful information like peak stresses in tension/compression. Running this script for the first time can take long time, as the specimen is prepared using triaxial compression. Next time, however, an attempt is made to load previously-generated packing (from /tmp/triaxPackCache.sqlite) and this expensive procedure is avoided. The specimen length can be specified, its diameter is half of the length and skirt of the hyperboloid is 4/5 of the width. The particle size is constant and can be specified using the sphereRadius parameter. The 3d display has displacement scaling applied, so that the fracture looks more spectacular. The scale is 1000 for tension and 100 for compression. """ # default parameters or from table readParamsFromTable(noTableOk=True, # unknownOk=True, young=24e9, poisson=.2, sigmaT=3.5e6, frictionAngle=atan(0.8), epsCrackOnset=1e-4, relDuctility = 30, intRadius=1.5, dtSafety=.4, damping=0.2, strainRateTension=10, strainRateCompression=50, # 1=tension, 2=compression (ANDed; 3=both) doModes=3, biaxial=True, # isotropic confinement (should be negative) isoPrestress=0 ) from yade.params.table import * if 'description' in O.tags.keys(): O.tags['id']=O.tags['id']+O.tags['description'] packingFile='periCube.pickle' # got periodic packing? Memoization not (yet) supported, so just generate it if there is not the right file # Save and reuse next time. if not os.path.exists(packingFile): sp=pack.randomPeriPack(radius=.05e-3,rRelFuzz=0.,initSize=Vector3(1.5e-3,1.5e-3,1.5e-3)) dd=dict(cell=(sp.cellSize[0],sp.cellSize[1],sp.cellSize[2]),spheres=sp.toList()) import cPickle as pickle pickle.dump(dd,open(packingFile,'w')) # # load the packing (again); # import cPickle as pickle concreteId=O.materials.append(CpmMat(young=young, frictionAngle=frictionAngle, density=4800, sigmaT=sigmaT, relDuctility=relDuctility, epsCrackOnset=epsCrackOnset, poisson=poisson, isoPrestress=isoPrestress)) sphDict=pickle.load(open(packingFile)) from yade import pack sp=pack.SpherePack() sp.fromList(sphDict['spheres']) sp.cellSize=sphDict['cell'] import numpy avgRadius=numpy.average([r for c,r in sp]) O.bodies.append([sphere(c,r,color=randomColor()) for c,r in sp]) O.periodic=True O.cell.setBox(sp.cellSize) axis=2 ax1=(axis+1)%3 ax2=(axis+2)%3 O.dt=dtSafety*PWaveTimeStep() import yade.plot as yp O.engines=[ ForceResetter(), InsertionSortCollider([Bo1_Sphere_Aabb(aabbEnlargeFactor=intRadius,label='is2aabb')],verletDist=.05*avgRadius), InteractionLoop( [Ig2_Sphere_Sphere_ScGeom(interactionDetectionFactor=intRadius,label='ss2d3dg')], [Ip2_CpmMat_CpmMat_CpmPhys()], [Law2_ScGeom_CpmPhys_Cpm()], ), NewtonIntegrator(damping=damping,label='newton'), CpmStateUpdater(realPeriod=1,label='updater'), # #UniaxialStrainer(strainRate=strainRateTension,axis=axis,asymmetry=0,posIds=posIds,negIds=negIds,crossSectionArea=crossSectionArea,blockDisplacements=False,blockRotations=False,setSpeeds=setSpeeds,label='strainer'), # PeriTriaxController(goal=[1,1,1],stressMask=( (7^(1<0: O.wait(); O.loadTmp('initial') print "Reversing plot data"; plot.reverseData() maxStrainRate=Vector3(1,1,1); goal=Vector3(1,1,1); if not biaxial: # uniaxial maxStrainRate[axis]=abs(strainRateTension) if mode=='tension' else abs(strainRateCompression) maxStrainRate[ax1]=maxStrainRate[ax2]=1000*maxStrainRate[axis] goal[axis]=1 if mode=='tension' else -1; else: maxStrainRate[axis]=abs(strainRateTension) if mode=='tension' else abs(strainRateCompression) maxStrainRate[ax1]=maxStrainRate[axis] maxStrainRate[ax2]=1000*maxStrainRate[axis] goal[axis]=1 if mode=='tension' else -1; goal[ax1]=goal[axis] strainer.maxStrainRate=maxStrainRate strainer.goal=goal try: from yade import qt renderer=qt.Renderer() renderer.scaleDisplacements=True renderer.displacementScale=(1000,1000,1000) if mode=='tension' else (100,100,100) except ImportError: pass print "init done, will now run." O.step(); O.step(); # to create initial contacts # now reset the interaction radius and go ahead ss2d3dg.interactionDetectionFactor=-1. is2aabb.aabbEnlargeFactor=-1. O.run() def stopIfDamaged(): global mode if O.iter<2 or not plot.data.has_key('sigma'): return # do nothing at the very beginning sigma,eps=plot.data['sigma'],plot.data['eps'] extremum=max(sigma) if (strainer.maxStrainRate>0) else min(sigma) # FIXME: only temporary, should be .5 minMaxRatio=0.5 if mode=='tension' else 0.5 if extremum==0: return print O.tags['id'],mode,strainer.strain[axis],sigma[-1] #print 'strain',strainer['strain'],'stress',strainer['stress'] import sys; sys.stdout.flush() if abs(sigma[-1]/extremum)6e-3: if mode=='tension' and doModes & 2: # only if compression is enabled mode='compression' #O.save('/tmp/uniax-tension.xml.bz2') print "Damaged, switching to compression... "; O.pause() # important! initTest must be launched in a separate thread; # otherwise O.load would wait for the iteration to finish, # but it would wait for initTest to return and deadlock would result import thread; thread.start_new_thread(initTest,()) return else: print "Damaged, stopping." ft,fc=max(sigma),min(sigma) print 'Strengths fc=%g, ft=%g, |fc/ft|=%g'%(fc,ft,abs(fc/ft)) title=O.tags['description'] if 'description' in O.tags.keys() else O.tags['params'] print 'gnuplot',plot.saveGnuplot(O.tags['id'],title=title) print 'Bye.' # O.pause() sys.exit(0) def addPlotData(): yade.plot.addData(t=O.time,i=O.iter,eps=strainer.strain[axis],eps_=strainer.strain[axis],sigma=strainer.stress[axis]+isoPrestress,eps1=strainer.strain[ax1],eps2=strainer.strain[ax2],sig1=strainer.stress[ax1],sig2=strainer.stress[ax2],relResid=updater.avgRelResidual) initTest() waitIfBatch() trunk-2018.02b/examples/concrete/triax.py000066400000000000000000000160521324306050200203130ustar00rootroot00000000000000################################################################################ # # Triaxial test. Axial strain rate is prescribed and transverse prestress. # Test is possible on prism or cylinder # An independent c++ engine may be created from this script in the future. # ################################################################################ from yade import pack, plot import os # default parameters or from table readParamsFromTable(noTableOk=True, # type of test ['cyl','cube'] testType = 'cyl', # material parameters young = 20e9, poisson = .2, frictionAngle = 1.2, sigmaT = 1.5e6, epsCrackOnset = 1e-4, relDuctility = 30, # prestress preStress = -3e6, # axial strain rate strainRate = -100, # assamlby parameters rParticle = .075e-3, # width = 2e-3, height = 5e-3, bcCoeff = 5, # facets division nw = 24, nh = 15, # output specifications fileName = 'test', exportDir = '/tmp', runGnuplot = False, runInGui = True, ) from yade.params.table import * assert testType in ['cyl','cube'] # materials concMat = O.materials.append(CpmMat( young=young,frictionAngle=frictionAngle,poisson=poisson,sigmaT=sigmaT, epsCrackOnset=epsCrackOnset,relDuctility=relDuctility )) frictMat = O.materials.append(FrictMat( young=young,poisson=poisson,frictionAngle=frictionAngle )) # spheres pred = pack.inCylinder((0,0,0),(0,0,height),.5*width) if testType=='cyl' else pack.inAlignedBox((-.5*width,-.5*width,0),(.5*width,.5*width,height)) if testType=='cube' else None sp=SpherePack() sp = pack.randomDensePack(pred,spheresInCell=2000,radius=rParticle,memoizeDb='/tmp/triaxTestOnCylinder.sqlite',returnSpherePack=True) spheres=sp.toSimulation(color=(0,1,1),material=concMat) # bottom and top of specimen. Will have prescribed velocity bot = [O.bodies[s] for s in spheres if O.bodies[s].state.pos[2]height-rParticle*bcCoeff] vel = strainRate*(height-rParticle*2*bcCoeff) for s in bot: s.shape.color = (1,0,0) s.state.blockedDOFs = 'xyzXYZ' s.state.vel = (0,0,-vel) for s in top: s.shape.color = Vector3(0,1,0) s.state.blockedDOFs = 'xyzXYZ' s.state.vel = (0,0,vel) # facets facets = [] if testType == 'cyl': rCyl2 = .5*width / cos(pi/float(nw)) for r in xrange(nw): for h in xrange(nh): v1 = Vector3( rCyl2*cos(2*pi*(r+0)/float(nw)), rCyl2*sin(2*pi*(r+0)/float(nw)), height*(h+0)/float(nh) ) v2 = Vector3( rCyl2*cos(2*pi*(r+1)/float(nw)), rCyl2*sin(2*pi*(r+1)/float(nw)), height*(h+0)/float(nh) ) v3 = Vector3( rCyl2*cos(2*pi*(r+1)/float(nw)), rCyl2*sin(2*pi*(r+1)/float(nw)), height*(h+1)/float(nh) ) v4 = Vector3( rCyl2*cos(2*pi*(r+0)/float(nw)), rCyl2*sin(2*pi*(r+0)/float(nw)), height*(h+1)/float(nh) ) f1 = facet((v1,v2,v3),color=(0,0,1),material=frictMat) f2 = facet((v1,v3,v4),color=(0,0,1),material=frictMat) facets.extend((f1,f2)) elif testType == 'cube': nw2 = nw/4 for r in xrange(nw2): for h in xrange(nh): v11 = Vector3( -.5*width + (r+0)*width/nw2, -.5*width, height*(h+0)/float(nh) ) v12 = Vector3( -.5*width + (r+1)*width/nw2, -.5*width, height*(h+0)/float(nh) ) v13 = Vector3( -.5*width + (r+1)*width/nw2, -.5*width, height*(h+1)/float(nh) ) v14 = Vector3( -.5*width + (r+0)*width/nw2, -.5*width, height*(h+1)/float(nh) ) f11 = facet((v11,v12,v13),color=(0,0,1),material=frictMat) f12 = facet((v11,v13,v14),color=(0,0,1),material=frictMat) v21 = Vector3( +.5*width, -.5*width + (r+0)*width/nw2, height*(h+0)/float(nh) ) v22 = Vector3( +.5*width, -.5*width + (r+1)*width/nw2, height*(h+0)/float(nh) ) v23 = Vector3( +.5*width, -.5*width + (r+1)*width/nw2, height*(h+1)/float(nh) ) v24 = Vector3( +.5*width, -.5*width + (r+0)*width/nw2, height*(h+1)/float(nh) ) f21 = facet((v21,v22,v23),color=(0,0,1),material=frictMat) f22 = facet((v21,v23,v24),color=(0,0,1),material=frictMat) v31 = Vector3( +.5*width - (r+0)*width/nw2, +.5*width, height*(h+0)/float(nh) ) v32 = Vector3( +.5*width - (r+1)*width/nw2, +.5*width, height*(h+0)/float(nh) ) v33 = Vector3( +.5*width - (r+1)*width/nw2, +.5*width, height*(h+1)/float(nh) ) v34 = Vector3( +.5*width - (r+0)*width/nw2, +.5*width, height*(h+1)/float(nh) ) f31 = facet((v31,v32,v33),color=(0,0,1),material=frictMat) f32 = facet((v31,v33,v34),color=(0,0,1),material=frictMat) v41 = Vector3( -.5*width, +.5*width - (r+0)*width/nw2, height*(h+0)/float(nh) ) v42 = Vector3( -.5*width, +.5*width - (r+1)*width/nw2, height*(h+0)/float(nh) ) v43 = Vector3( -.5*width, +.5*width - (r+1)*width/nw2, height*(h+1)/float(nh) ) v44 = Vector3( -.5*width, +.5*width - (r+0)*width/nw2, height*(h+1)/float(nh) ) f41 = facet((v41,v42,v43),color=(0,0,1),material=frictMat) f42 = facet((v41,v43,v44),color=(0,0,1),material=frictMat) facets.extend((f11,f12,f21,f22,f31,f32,f41,f42)) O.bodies.append(facets) mass = O.bodies[0].state.mass for f in facets: f.state.mass = mass f.state.blockedDOFs = 'XYZz' # plots plot.plots = { 'e':('s',), } def plotAddData(): f1 = sum(O.forces.f(b.id)[2] for b in top) f2 = sum(O.forces.f(b.id)[2] for b in bot) f = .5*(f2-f1) s = f/(pi*.25*width*width) if testType=='cyl' else f/(width*width) if testType=='cube' else None e = (top[0].state.displ()[2] - bot[0].state.displ()[2]) / (height-rParticle*2*bcCoeff) plot.addData( i = O.iter, s = s, e = e, ) # apply prestress to facets def addForces(): for f in facets: n = f.shape.normal a = f.shape.area O.forces.addF(f.id,preStress*a*n) # stop condition and exit of the simulation def stopIfDamaged(maxEps=5e-3): extremum = max(abs(s) for s in plot.data['s']) s = abs(plot.data['s'][-1]) e = abs(plot.data['e'][-1]) if O.iter < 1000 or s > .5*extremum and e < maxEps: return f = os.path.join(exportDir,fileName) print 'gnuplot',plot.saveGnuplot(f,term='png') if runGnuplot: import subprocess os.chdir(exportDir) subprocess.Popen(['gnuplot',f+'.gnuplot']).wait() print 'Simulation finished' O.pause() #sys.exit(0) # results in some threading exception O.dt=.5*utils.PWaveTimeStep() enlargeFactor=1.5 O.engines=[ ForceResetter(), InsertionSortCollider([ Bo1_Sphere_Aabb(aabbEnlargeFactor=enlargeFactor,label='bo1s'), Bo1_Facet_Aabb() ]), InteractionLoop( [ Ig2_Sphere_Sphere_ScGeom(interactionDetectionFactor=enlargeFactor,label='ss2d3dg'), Ig2_Facet_Sphere_ScGeom(), ], [ Ip2_CpmMat_CpmMat_CpmPhys(cohesiveThresholdIter=O.iter+5), Ip2_FrictMat_CpmMat_FrictPhys(), Ip2_FrictMat_FrictMat_FrictPhys(), ], [ Law2_ScGeom_CpmPhys_Cpm(), Law2_ScGeom_FrictPhys_CundallStrack(), ], ), PyRunner(iterPeriod=1,command="addForces()"), NewtonIntegrator(damping=.3), CpmStateUpdater(iterPeriod=50,label='cpmStateUpdater'), PyRunner(command='plotAddData()',iterPeriod=10), PyRunner(iterPeriod=50,command='stopIfDamaged()'), ] # run one step O.step() # reset interaction detection enlargement bo1s.aabbEnlargeFactor=ss2d3dg.interactionDetectionFactor=1.0 # initialize auto-updated plot if runInGui: plot.plot() try: from yade import qt renderer=qt.Renderer() # uncomment following line to exagerate displacement #renderer.dispScale=(100,100,100) except: pass # run O.run() trunk-2018.02b/examples/concrete/uniax-post.py000066400000000000000000000033561324306050200212760ustar00rootroot00000000000000#!/usr/bin/python # -*- coding: utf-8 -*- # # demonstration of the yade.post2d module (see its documentation for details) # from yade import post2d import pylab # the matlab-like interface of matplotlib loadFile='/tmp/uniax-tension.yade.gz' if not os.path.exists(loadFile): raise RuntimeError("Run uniax.py first so that %s is created"%loadFile) O.load(loadFile) # flattener that project to the xz plane flattener=post2d.AxisFlatten(useRef=False,axis=1) # return scalar given a Body instance extractDmg=lambda b: b.state.normDmg # will call flattener.planar implicitly # the same as: extractVelocity=lambda b: flattener.planar(b,b.state['vel']) extractVelocity=lambda b: b.state.vel # create new figure pylab.figure() # plot raw damage post2d.plot(post2d.data(extractDmg,flattener)) pylab.suptitle('damage') # plot smooth damage into new figure pylab.figure(); ax,map=post2d.plot(post2d.data(extractDmg,flattener,stDev=2e-3)) pylab.suptitle('smooth damage') # show color scale pylab.colorbar(map,orientation='horizontal') # shear stress NOT WORKING (AttributeError: 'CpmState' object has no attribute 'tau' and no 'sigma'): #pylab.figure() #post2d.plot(post2d.data(lambda b: b.state.sigma,flattener)) #pylab.suptitle('sigma') #pylab.figure() #post2d.plot(post2d.data(lambda b: b.state.tau,flattener,stDev=2e-3)) #pylab.suptitle('smooth tau (in grid)') # raw velocity (vector field) plot pylab.figure(); post2d.plot(post2d.data(extractVelocity,flattener)) pylab.suptitle('velocity') # smooth velocity plot; data are sampled at regular grid pylab.figure(); ax,map=post2d.plot(post2d.data(extractVelocity,flattener,stDev=1e-3)) pylab.suptitle('smooth velocity') # save last (current) figure to file pylab.gcf().savefig('/tmp/foo.png') # show the figures pylab.show() trunk-2018.02b/examples/concrete/uniax.py000066400000000000000000000160161324306050200203100ustar00rootroot00000000000000#!/usr/bin/python # -*- coding: utf-8 -*- from __future__ import division from yade import plot,pack,timing import time, sys, os, copy #import matplotlib #matplotlib.rc('text',usetex=True) #matplotlib.rc('text.latex',preamble=r'\usepackage{concrete}\usepackage{euler}') """ A fairly complex script performing uniaxial tension-compression test on hyperboloid-shaped specimen. Most parameters of the model (and of the setup) can be read from table using yade-multi. After the simulation setup, tension loading is run and stresses are periodically saved for plotting as well as checked for getting below the maximum value so far. This indicates failure (see stopIfDamaged function). After failure in tension, the original setup is loaded anew and the sense of loading reversed. After failure in compression, strain-stress curves are saved via plot.saveGnuplot and we exit, giving some useful information like peak stresses in tension/compression. Running this script for the first time can take long time, as the specimen is prepared using triaxial compression. Next time, however, an attempt is made to load previously-generated packing (from /tmp/triaxPackCache.sqlite) and this expensive procedure is avoided. The specimen length can be specified, its diameter is half of the length and skirt of the hyperboloid is 4/5 of the width. The particle size is constant and can be specified using the sphereRadius parameter. The 3d display has displacement scaling applied, so that the fracture looks more spectacular. The scale is 1000 for tension and 100 for compression. """ # default parameters or from table readParamsFromTable(noTableOk=True, # unknownOk=True, young=24e9, poisson=.2, sigmaT=3.5e6, frictionAngle=atan(0.8), epsCrackOnset=1e-4, relDuctility=30, intRadius=1.5, dtSafety=.8, damping=0.4, strainRateTension=.05, strainRateCompression=.5, setSpeeds=True, # 1=tension, 2=compression (ANDed; 3=both) doModes=3, specimenLength=.15, sphereRadius=3.5e-3, # isotropic confinement (should be negative) isoPrestress=0, ) from yade.params.table import * if 'description' in O.tags.keys(): O.tags['id']=O.tags['id']+O.tags['description'] # make geom; the dimensions are hard-coded here; could be in param table if desired # z-oriented hyperboloid, length 20cm, diameter 10cm, skirt 8cm # using spheres 7mm of diameter concreteId=O.materials.append(CpmMat(young=young,frictionAngle=frictionAngle,poisson=poisson,density=4800,sigmaT=sigmaT,relDuctility=relDuctility,epsCrackOnset=epsCrackOnset,isoPrestress=isoPrestress)) sps=SpherePack() sp=pack.randomDensePack(pack.inHyperboloid((0,0,-.5*specimenLength),(0,0,.5*specimenLength),.25*specimenLength,.17*specimenLength),spheresInCell=2000,radius=sphereRadius,memoizeDb='/tmp/triaxPackCache.sqlite',returnSpherePack=True) #sp=pack.randomDensePack(pack.inAlignedBox((-.25*specimenLength,-.25*specimenLength,-.5*specimenLength),(.25*specimenLength,.25*specimenLength,.5*specimenLength)),spheresInCell=2000,radius=sphereRadius,memoizeDb='/tmp/triaxPackCache.sqlite',returnSpherePack=True) sp.toSimulation(material=concreteId) bb=uniaxialTestFeatures() negIds,posIds,axis,crossSectionArea=bb['negIds'],bb['posIds'],bb['axis'],bb['area'] O.dt=dtSafety*PWaveTimeStep() print 'Timestep',O.dt mm,mx=[pt[axis] for pt in aabbExtrema()] coord_25,coord_50,coord_75=mm+.25*(mx-mm),mm+.5*(mx-mm),mm+.75*(mx-mm) area_25,area_50,area_75=approxSectionArea(coord_25,axis),approxSectionArea(coord_50,axis),approxSectionArea(coord_75,axis) O.engines=[ ForceResetter(), InsertionSortCollider([Bo1_Sphere_Aabb(aabbEnlargeFactor=intRadius,label='is2aabb'),],verletDist=.05*sphereRadius), InteractionLoop( [Ig2_Sphere_Sphere_ScGeom(interactionDetectionFactor=intRadius,label='ss2sc')], [Ip2_CpmMat_CpmMat_CpmPhys()], [Law2_ScGeom_CpmPhys_Cpm()], ), NewtonIntegrator(damping=damping,label='damper'), CpmStateUpdater(realPeriod=.5), UniaxialStrainer(strainRate=strainRateTension,axis=axis,asymmetry=0,posIds=posIds,negIds=negIds,crossSectionArea=crossSectionArea,blockDisplacements=False,blockRotations=False,setSpeeds=setSpeeds,label='strainer'), PyRunner(virtPeriod=1e-6/strainRateTension,realPeriod=1,command='addPlotData()',label='plotDataCollector',initRun=True), PyRunner(realPeriod=4,command='stopIfDamaged()',label='damageChecker'), ] #O.miscParams=[Gl1_CpmPhys(dmgLabel=False,colorStrain=False,epsNLabel=False,epsT=False,epsTAxes=False,normal=False,contactLine=True)] # plot stresses in ¼, ½ and ¾ if desired as well; too crowded in the graph that includes confinement, though plot.plots={'eps':('sigma',)} #,'sigma.50')},'t':('eps')} #'sigma.25','sigma.50','sigma.75')} O.saveTmp('initial'); O.timingEnabled=False global mode mode='tension' if doModes & 1 else 'compression' def initTest(): global mode print "init" if O.iter>0: O.wait(); O.loadTmp('initial') print "Reversing plot data"; plot.reverseData() else: plot.plot(subPlots=False) strainer.strainRate=abs(strainRateTension) if mode=='tension' else -abs(strainRateCompression) try: from yade import qt renderer=qt.Renderer() renderer.dispScale=(1000,1000,1000) if mode=='tension' else (100,100,100) except ImportError: pass print "init done, will now run." O.step(); # to create initial contacts # now reset the interaction radius and go ahead ss2sc.interactionDetectionFactor=1. is2aabb.aabbEnlargeFactor=1. O.run() def stopIfDamaged(): global mode if O.iter<2 or not plot.data.has_key('sigma'): return # do nothing at the very beginning sigma,eps=plot.data['sigma'],plot.data['eps'] extremum=max(sigma) if (strainer.strainRate>0) else min(sigma) minMaxRatio=0.5 if mode=='tension' else 0.5 if extremum==0: return import sys; sys.stdout.flush() if abs(sigma[-1]/extremum)(5e-3 if isoPrestress==0 else 5e-2): if mode=='tension' and doModes & 2: # only if compression is enabled mode='compression' O.save('/tmp/uniax-tension.yade.gz') print "Saved /tmp/uniax-tension.yade.gz (for use with interaction-histogram.py and uniax-post.py)" print "Damaged, switching to compression... "; O.pause() # important! initTest must be launched in a separate thread; # otherwise O.load would wait for the iteration to finish, # but it would wait for initTest to return and deadlock would result import thread; thread.start_new_thread(initTest,()) return else: print "Damaged, stopping." ft,fc=max(sigma),min(sigma) print 'Strengths fc=%g, ft=%g, |fc/ft|=%g'%(fc,ft,abs(fc/ft)) title=O.tags['description'] if 'description' in O.tags.keys() else O.tags['params'] print 'gnuplot',plot.saveGnuplot(O.tags['id'],title=title) print 'Bye.' O.pause() #sys.exit(0) # results in some threading exception def addPlotData(): yade.plot.addData({'t':O.time,'i':O.iter,'eps':strainer.strain,'sigma':strainer.avgStress+isoPrestress, 'sigma.25':forcesOnCoordPlane(coord_25,axis)[axis]/area_25+isoPrestress, 'sigma.50':forcesOnCoordPlane(coord_50,axis)[axis]/area_50+isoPrestress, 'sigma.75':forcesOnCoordPlane(coord_75,axis)[axis]/area_75+isoPrestress, }) plot.plot(subPlots=False) #O.run() initTest() waitIfBatch() trunk-2018.02b/examples/conveyor/000077500000000000000000000000001324306050200166505ustar00rootroot00000000000000trunk-2018.02b/examples/conveyor/conveyor.geo000066400000000000000000000010621324306050200212070ustar00rootroot00000000000000acc = 100.0; wid = 500.0; len = 1500.0; h = 300.0; Point(1) = {0, 0, -wid/5.0, acc}; Point(2) = {-wid, 0, 0, acc}; Point(3) = {wid, 0, 0, acc}; Point(4) = {wid/1.1, 0, -wid/10.0, acc}; Point(5) = {-wid/1.1, 0, -wid/10.0, acc}; Point(6) = {wid/2.0, 0, +wid/10.0, acc}; Point(7) = {-wid/2.0, 0, +wid/10.0, acc}; Point(8) = {-wid, 0, h, acc}; Point(9) = {wid, 0, h, acc}; Spline(1) = {2, 5, 7, 1, 6, 4, 3}; Line(2) = {8, 2}; Line(3) = {3, 9}; Extrude {0, len, 0} { Line{2, 1, 3}; } Line(16) = {8, 9}; Line Loop(17) = {16, -3, -1, -2}; Plane Surface(18) = {17}; trunk-2018.02b/examples/conveyor/conveyor.mesh000066400000000000000000001404401324306050200213750ustar00rootroot00000000000000 MeshVersionFormatted 2 Dimension 3 Vertices 441 0 0 -100 1 -500 0 0 2 500 0 0 3 454.54545454545 0 -50 4 -454.54545454545 0 -50 5 250 0 50 6 -250 0 50 7 -500 0 300 8 500 0 300 9 -500 1500 300 10 -500 1500 0 11 -454.54545454545 1500 -50 13 -250 1500 50 14 0 1500 -100 15 250 1500 50 16 454.54545454545 1500 -50 17 500 1500 0 18 500 1500 300 20 -439.88134513266 0 -44.523673195083 1 -362.83621367891 0 7.7663721559698 1 -279.5676899846 0 48.28686863902 1 -192.63906004976 0 24.407224564095 1 -120.43749167986 0 -34.43426658243 1 -44.683026495718 0 -88.284497185875 1 44.683026492925 0 -88.284497187232 1 120.43749167789 0 -34.434266584101 1 192.63906004808 0 24.407224562925 1 279.56768998311 0 48.286868639328 1 362.83621367809 0 7.7663721565244 1 439.88134513224 0 -44.523673194873 1 -500 0 199.99999999993 2 -500 0 100.00000000007 2 500 0 99.999999999799 3 500 0 199.99999999973 3 -500 1500 199.99999999993 4 -500 1500 100.00000000007 4 -500 99.99999999974 300 5 -500 199.99999999954 300 5 -500 299.9999999991 300 5 -500 399.99999999856 300 5 -500 499.99999999802 300 5 -500 599.99999999783 300 5 -500 699.99999999767 300 5 -500 799.99999999751 300 5 -500 899.99999999735 300 5 -500 999.99999999719 300 5 -500 1099.9999999976 300 5 -500 1199.9999999982 300 5 -500 1299.9999999988 300 5 -500 1399.9999999994 300 5 -500 99.99999999974 0 6 -500 199.99999999954 0 6 -500 299.9999999991 0 6 -500 399.99999999856 0 6 -500 499.99999999802 0 6 -500 599.99999999783 0 6 -500 699.99999999767 0 6 -500 799.99999999751 0 6 -500 899.99999999735 0 6 -500 999.99999999719 0 6 -500 1099.9999999976 0 6 -500 1199.9999999982 0 6 -500 1299.9999999988 0 6 -500 1399.9999999994 0 6 -439.88134513266 1500 -44.523673195083 8 -362.83621367891 1500 7.7663721559698 8 -279.5676899846 1500 48.28686863902 8 -192.63906004976 1500 24.407224564095 8 -120.43749167986 1500 -34.43426658243 8 -44.683026495718 1500 -88.284497185875 8 44.683026492925 1500 -88.284497187232 8 120.43749167789 1500 -34.434266584101 8 192.63906004808 1500 24.407224562925 8 279.56768998311 1500 48.286868639328 8 362.83621367809 1500 7.7663721565244 8 439.88134513224 1500 -44.523673194873 8 500 99.99999999974 0 10 500 199.99999999954 0 10 500 299.9999999991 0 10 500 399.99999999856 0 10 500 499.99999999802 0 10 500 599.99999999783 0 10 500 699.99999999767 0 10 500 799.99999999751 0 10 500 899.99999999735 0 10 500 999.99999999719 0 10 500 1099.9999999976 0 10 500 1199.9999999982 0 10 500 1299.9999999988 0 10 500 1399.9999999994 0 10 500 1500 99.999999999799 12 500 1500 199.99999999973 12 500 99.99999999974 300 14 500 199.99999999954 300 14 500 299.9999999991 300 14 500 399.99999999856 300 14 500 499.99999999802 300 14 500 599.99999999783 300 14 500 699.99999999767 300 14 500 799.99999999751 300 14 500 899.99999999735 300 14 500 999.99999999719 300 14 500 1099.9999999976 300 14 500 1199.9999999982 300 14 500 1299.9999999988 300 14 500 1399.9999999994 300 14 -400 0 300 16 -300.00000000001 0 300 16 -200.00000000004 0 300 16 -100.00000000004 0 300 16 -3.3310243452433E-11 0 300 16 100.00000000002 0 300 16 200.00000000001 0 300 16 300.00000000001 0 300 16 400 0 300 16 -500 166.21866874528 159.75083113068 7 -500 437.50117236506 152.20689287958 7 -500 847.01463282802 108.35835794242 7 -500 641.54811440665 141.65047276936 7 -500 978.50960553792 115.54626313201 7 -500 1205.1158731829 155.62785850948 7 -500 1275.2154194998 134.24489795919 7 -500 1059.1178455821 159.10491705026 7 -500 80.559548921413 165.40972915315 7 -500 113.2787192178 79.530785309051 7 -500 1371.1997701567 92.47834235736 7 -500 1353.9062404729 208.03589526189 7 -500 1254.2112854197 222.28488129946 7 -500 249.35196470432 203.88033646342 7 -500 595.25949073613 87.458267312908 7 -500 597.26401796604 210.0001937624 7 -500 346.20055023987 110.69787810395 7 -500 356.85084344219 219.31650052337 7 -500 516.09027078317 225.68095328363 7 -500 511.0783337831 77.682192335459 7 -500 1023.6013345509 221.35896129905 7 -500 1072.0922729334 82.897678152178 7 -500 1141.3361476427 211.69845363885 7 -500 1182.0415109408 70.096898961194 7 -500 684.28898227798 222.3506005235 7 -500 439.81221755297 241.87863305981 7 -500 433.5270543112 67.926479981809 7 -500 1435.7815677179 153.40975585569 7 -500 1439.5833956784 69.944297514942 7 -500 1127.7703161142 135.86182216961 7 -500 143.00440867938 228.18820355302 7 -500 550.69893928777 150.92856580591 7 -500 738.18256634503 138.1205525456 7 -500 1437.937561638 232.2891302235 7 -500 64.712791520106 238.71958654122 7 -500 930.70198521745 216.73436690612 7 -500 229.17498381765 92.309971834517 7 -500 804.2836506274 231.74006879719 7 -500 674.47653904811 72.009760258069 7 -500 751.93474764327 63.697734149218 7 -258.49815801904 1029.4446306734 50.490543174729 11 309.07637276422 1171.6185479741 38.624632032514 11 -270.40838069042 432.3630421446 49.797575832967 11 -26.306785135095 946.40040159306 -95.685421778899 11 -209.97018344876 169.03774510529 35.43184071908 11 201.08759170351 177.37803505227 30.047367485698 11 -451.78233766894 185.07400082333 -49.333692609851 11 328.98271125587 611.6050344343 28.789561461469 11 311.34132002391 870.14747751944 37.623917688882 11 -447.55034424496 77.647293211656 -47.933990016274 11 407.71950478472 85.104464311252 -24.151431465974 11 407.55190260049 1416.9120732463 -24.032832326847 11 -97.422052017176 406.07097805139 -53.412800205045 11 -83.770206775152 1113.8009069829 -63.887058139507 11 206.19118130886 1011.735002295 33.215188364471 11 177.58966387898 446.38366347476 13.346917582037 11 119.9558204085 850.25993541261 -34.842583911559 11 175.99436683117 632.11986994507 12.111465695579 11 -68.155810485193 1415.7951850061 -74.793824963828 11 227.82206333622 1159.5120405972 44.172520628839 11 -177.83320932462 625.9531930534 13.534589779048 11 -97.146145060625 791.4361678918 -53.631541140496 11 -93.212553875755 688.35369809189 -56.720808906719 11 -77.040358411636 292.23411294469 -68.746861236776 11 -234.37092786247 350.94502372983 46.54610663453 11 203.44347612981 360.57451196451 31.533595378719 11 -287.18256766009 119.28686302678 46.450366032262 11 -269.48199587244 1125.1435438346 49.905009251172 11 240.24355351527 1412.88810685 48.231877048281 11 126.05598202086 1305.5534575256 -29.650935066679 11 259.89966174213 100.49209202871 50.489402140543 11 129.66047250954 106.68342009124 -26.56722844088 11 46.697698548582 990.97061970115 -87.289261550043 11 -149.4951393717 1417.3813847219 -9.5951112503981 11 52.15486248165 1254.6116946328 -84.429236720891 11 244.07980919212 274.06350743544 49.088219795251 11 420.0358721711 440.59816786922 -32.645715280822 11 -231.45106264402 1386.9049738033 45.54934014485 11 352.26473871125 263.20521892359 14.790387939939 11 280.73468247852 198.42758057584 48.03830606846 11 292.6007688904 1335.674727326 44.848300300521 11 -75.978854312104 189.74893543627 -69.49240850829 11 296.58657921327 985.9607533456 43.522671661154 11 -282.45100426722 245.21181034342 47.650731046031 11 98.839469930576 1155.4364327048 -52.285014258655 11 -163.66962936343 332.60337180353 2.2514734490401 11 -186.51908212567 439.97566477397 20.050146054677 11 1.4522518129397 1423.0442896469 -99.985727420668 11 -338.51051373454 643.3799980435 23.341680137276 11 -180.67517392215 1086.1095146987 15.705442983906 11 -202.97413221105 270.08273531094 31.240714534771 11 -249.55868635503 804.00294098247 49.94996929506 11 285.87042393378 708.47308446107 46.802276695553 11 -188.78712862376 1191.6293111452 21.689273482676 11 55.797339260116 455.73697880727 -82.392940736399 11 120.20587043 1030.2975113349 -34.630653828654 11 -24.090812569112 782.18111792408 -96.355186508028 11 -7.1072128383882 1054.0310793571 -99.664500712601 11 -66.956314108988 1001.4379215797 -75.574922686281 11 249.34685730137 623.15672871309 49.924902238811 11 -123.75987670644 527.73354371515 -31.609859183523 11 264.62598635727 432.09141387938 50.32392571906 11 100.08686689976 949.74798629097 -51.287047846811 11 -53.906937050527 591.31161694133 -83.4620814547 11 -102.69964000375 1310.6803203086 -49.180953442472 11 -6.2396948345539 248.26366943128 -99.740662744245 11 -104.72175284324 945.94630095175 -47.537138191795 11 -248.09123443661 915.5417294035 49.762427529082 11 6.4731570902513 372.78578040125 -99.72110845657 11 172.20037999608 799.81486599712 9.1325714571594 11 -121.28692164862 1203.8875763276 -33.713439908897 11 -415.22853967963 1077.8420651703 -29.3913374699 11 -141.22315436968 228.32457348158 -16.651480982012 11 7.4462599644253 83.587440752646 -99.632139812808 11 52.483239910439 169.90535986743 -84.249736032651 11 -170.7266472537 745.50251875714 7.9609625593085 11 348.16289970859 1078.9477658949 17.416853386858 11 208.55713442011 531.2110868512 34.616405500207 11 231.59806876608 917.79952773124 45.601935732933 11 425.88084607278 171.99429524159 -36.45270232898 11 151.67806186767 1410.4104382475 -7.7461039123897 11 -146.1350176708 108.06444736426 -12.453415260621 11 -130.16589366105 1023.5468692542 -26.134199908089 11 34.200901552608 882.03568944359 -92.894706615011 11 162.71248541762 942.10659606129 1.4657235837283 11 -480.39957878992 361.26974901204 -45.547631263764 11 -183.92598145533 984.41891125181 18.143056587304 11 182.24273858785 76.179882064658 16.887165343022 11 66.963611810091 71.392922984506 -75.570197268982 11 209.98304630872 717.6140932054 35.439188318634 11 57.699293536969 1085.5405492856 -81.291077075812 11 245.6197871922 808.43026773307 49.374493752924 11 281.52778398243 336.69260355381 47.862442686452 11 -249.96691906775 601.56296659448 49.996352730081 11 -159.15878602663 866.31010423584 -1.4722056472899 11 482.57558464313 786.80303061173 -43.140736728993 11 30.888610431698 554.20830937121 -94.140714057626 11 -216.71866629312 525.12372672567 39.091436612403 11 -63.324115242945 862.94261578761 -77.885184610607 11 103.3394141155 613.95769637398 -48.662131529352 11 -404.4822752824 749.15962038261 -21.851120361267 11 18.413753435568 682.07191406862 -97.830406143302 11 113.1305584928 390.34647681009 -40.588953623835 11 -251.61511078231 691.22477697689 50.161168273409 11 184.96170438063 863.82095477681 18.908884429568 11 477.89354767936 1314.4164033175 -47.543940465301 11 -25.874554778003 484.99517724402 -95.82009112161 11 441.88143922639 527.20399071122 -45.502460858978 11 121.85166237216 734.30013712281 -33.233688837231 11 -109.03329551531 609.96616944861 -43.995511530682 11 -297.59593617845 1317.2127350779 43.16794566293 11 -395.92628162112 1253.6586968279 -15.703984493996 11 -64.776150185884 93.160888145244 -76.971703985724 11 -11.764985705836 155.43643846248 -99.09469168573 11 174.3911412518 1113.56182751 10.859461346917 11 173.35935483009 1218.2235296772 10.048399117056 11 75.588382718042 277.08366866643 -69.765146108675 11 -217.63416281933 1273.1555760995 39.556438842476 11 124.86527035176 201.56371553707 -30.667415194173 11 12.24447588345 1140.9799101308 -99.020945056435 11 120.35414919124 509.57005486517 -34.504939489206 11 -465.50594522415 577.52074120516 -51.075798510848 11 53.91077209708 786.24662226099 -83.459939214777 11 477.71017500724 337.68661191211 -47.66447865145 11 254.6660160515 1088.3941432336 50.377280850383 11 -174.37535993631 1330.61449116 10.847086888783 11 263.05958471568 1249.0081302782 50.405903222674 11 70.645670324681 1383.481987147 -73.144736113871 11 210.51529933826 1321.6352678877 35.742021559126 11 161.52828490451 280.1683192443 0.49021768306379 11 -285.25995117824 1215.5749361982 46.961076822033 11 481.75404321036 1053.8061658422 -44.138694652998 11 -483.48014433658 1272.990678459 -41.892753047336 11 463.80295384905 969.43352043038 -51.083373414481 11 -40.56067725033 1227.3039037439 -90.214029912829 11 -222.05974780785 79.167127963071 41.691148458665 11 -157.96093117533 1267.1172131832 -2.4692613979466 11 -472.41375918675 1401.1060457221 -50.060354988374 11 -461.74014417468 467.79875141136 -50.993809324787 11 478.67706648534 1137.1382667593 -46.992760158858 11 92.379638978753 1447.8639186796 -57.367616999693 11 -13.339961849886 1335.5101175143 -98.842112129115 11 427.14951840648 342.05102184803 -37.252212469172 11 362.62170348002 708.69051737711 7.9121672363243 11 -460.91960579512 307.97116427716 -50.930931321154 11 -316.51451479951 340.79659287002 35.218511132902 11 295.37185319485 524.07281971907 43.939482738019 11 469.51766880878 1233.3030355863 -50.708121130288 11 -140.59017469205 1151.1921016968 -17.193688172418 11 -480.87134797353 655.20501751247 -45.087261818097 11 477.28723000053 1410.2134969609 -47.930982714784 11 487.07930725831 887.22815383318 -34.738209005138 11 485.22768890676 260.05400045885 -38.932117037524 11 433.74700000309 1338.5921868296 -41.214509919899 11 440.24706880994 739.4364546536 -44.706756394177 11 480.63927920294 563.79173863854 -45.31754403563 11 -482.50874691209 425.93418151293 -43.226471711588 11 -472.98108473004 1015.7128033459 -49.887175642466 11 -399.60261320391 543.39188928194 -18.353764549053 11 -464.06184107243 1251.701601259 -51.087217828098 11 475.38226756727 477.92332393455 -48.951502230175 11 -487.19173012822 836.5449300547 -34.439885505848 11 -439.66496307747 857.36513630247 -44.414519043483 11 439.76155970774 1070.3595275345 -44.463323103559 11 -453.64828377688 371.1582674714 -49.808067872581 11 -392.23557903461 290.86257996925 -13.038237883057 11 -458.02408161775 1189.7582430329 -50.598613151576 11 478.64499736176 87.173344117713 -47.016518003017 11 -483.73456503241 929.92831188915 -41.510037421737 11 -480.30520932533 750.06980839712 -45.636113048791 11 449.99071508236 852.51420468998 -48.791796300696 11 456.15191612805 258.64519978743 -50.301243171354 11 484.18577150827 177.79392997653 -40.793502539514 11 417.55118822314 1177.4325371887 -30.97550664823 11 -436.04182264041 652.76141598374 -42.501360028894 11 442.15482877215 1270.5563575711 -45.631828459136 11 -484.06826221682 225.89330723077 -40.984943014623 11 484.28532358328 668.4308393518 -40.628560396126 11 -414.73352363375 1415.9553568621 -29.05113665771 11 -481.54297059487 1318.0573012627 -44.376508041812 11 468.74894939394 402.80025802255 -50.821508522922 11 -446.24186047954 1331.1052351316 -47.423278047196 11 -487.06503930379 1097.2188519237 -34.775676363792 11 -422.94283201772 973.07779474436 -34.562629930155 11 -372.52586681014 102.80556488818 1.063729841025 11 383.29311554248 810.96802565904 -6.5914711790423 11 432.39570272501 620.55381221144 -40.432729216034 11 154.41721093564 573.46809238912 -5.4365683800604 11 -343.13550126179 1061.0377292092 20.54998669241 11 112.82258706563 1233.4562786351 -40.846254426353 11 365.95996639924 1269.4205335147 5.6296838759071 11 352.3566136618 376.06826161625 14.730878293716 11 -376.30977848722 424.217867603 -1.6066502201143 11 392.41181747604 998.61219504694 -13.165570200064 11 -326.27923379605 55.523106978739 30.254678542706 11 318.51263042873 781.34187454994 34.246502618765 11 -363.34350096324 1154.0331761463 7.4210977663806 11 -362.54693037665 188.64816381019 7.9629587997713 11 386.82301953169 916.38442534423 -9.1310258312517 11 -392.02660349934 1348.889509557 -12.887254213511 11 381.33719201028 524.71811031351 -5.1891938678163 11 -307.64420653543 525.10258115316 39.240336904541 11 -343.5205611163 846.28665298224 20.31354088965 11 -330.99931903314 957.81662946231 27.6726382674 11 347.4702557452 151.13770506691 17.854307307358 11 345.10507914121 459.50975467949 19.334300186977 11 -328.815722319 1414.6313507616 28.881139792988 11 326.45962010185 1418.089359143 30.158093936703 11 333.11071397002 67.346852281374 26.481879446845 11 -387.76802489025 913.1734699119 -9.8123809742012 11 368.49108549348 1355.7377760119 3.8807879599646 11 -318.81666547397 746.81079787354 34.096567007307 11 500 270.28236611415 153.159385701 15 500 180.78042327992 163.13492063487 15 500 474.0019313797 150.42708279681 15 500 587.03658565476 150.07092191938 15 500 861.17651593493 139.14052856793 15 500 704.15340852392 128.8010616776 15 500 966.64271288781 101.07558134961 15 500 1196.3349605626 135.10819539539 15 500 1289.1241361944 147.3661194078 15 500 93.922632544823 116.95902170382 15 500 112.0634920632 231.48809523803 15 500 1405.8358587109 150.94261130962 15 500 1257.7893518504 70.833333333329 15 500 1346.8046320694 70.881381628344 15 500 1348.1056615728 228.17961562068 15 500 214.61451247103 74.090136054399 15 500 1092.7578190047 113.11074906549 15 500 333.88521021504 226.50269449172 15 500 330.7349887067 75.688041084058 15 500 545.36320299409 75.152291624885 15 500 545.4507091471 225.17492088621 15 500 1026.2799448658 214.73146091449 15 500 1167.4819899339 63.8702444256 15 500 1141.5957528697 214.00305211933 15 500 435.06841370713 75.416132853242 15 500 435.59345062519 225.55190842119 15 500 233.87676366774 228.79133597881 15 500 1428.7362381578 63.584179785852 15 500 1243.6739745335 220.76896300681 15 500 48.480658136141 54.239755425904 15 500 379.92772679132 151.124207558 15 500 780.05807480932 155.52056483706 15 500 930.67390922397 213.63904920337 15 500 1430.7883040566 235.82444538601 15 500 820.14032102512 216.95573511599 15 500 1042.1817712935 55.117048242365 15 500 684.93831956774 222.07498222571 15 500 661.24467275424 69.677281796905 15 500 769.52920211466 80.85789309753 15 500 859.46968618645 64.214800603014 15 -0.81462932724889 0 97.636268535275 18 -139.85718816121 0 158.61010619842 18 138.14646657356 0 157.45607707312 18 -367.44288752686 0 155.2345449976 18 367.44288752644 0 155.23454499772 18 -231.12102700525 0 208.49061874194 18 230.87663820627 0 208.32575743857 18 -0.16292586612424 -0 -13.288205311999 18 110.3710335661 0 68.968201390013 18 -110.71184787811 0 69.085959464396 18 44.101039491352 0 205.54835382314 18 -56.987561922027 0 208.39901318911 18 209.79152991163 0 107.11232733922 18 -210.06485428202 0 107.28335692276 18 -408.88213332683 0 85.722930270628 18 408.88213332647 0 85.722930270944 18 292.29208962293 0 131.70029191028 18 -292.33611285296 0 131.72885405967 18 420.88385594015 0 220.8838559401 18 -420.88385594029 0 220.88385594027 18 -436.42338438925 0 22.995307196009 18 436.4233843894 0 22.995307196167 18 -148.39400707645 0 236.90586730686 18 142.62482885424 0 234.26603766697 18 -335.29731388756 0 219.38964562325 18 335.24924521597 0 219.35740838111 18 -77.09280682215 0 133.4328368468 18 69.963291817927 0 131.6829686574 18 -439.4417753588 0 152.3682662417 18 439.44177535861 0 152.36826624166 18 341.8840469659 0 77.512213083231 18 -341.88404696694 0 77.512213081832 18 -67.088900315855 0 6.2458496393847 18 67.088900315276 0 6.2458496384531 18 Edges 108 2 19 1 19 20 1 20 21 1 21 22 1 22 23 1 23 24 1 24 25 1 25 26 1 26 27 1 27 28 1 28 29 1 29 30 1 30 3 1 8 31 2 31 32 2 32 2 2 3 33 3 33 34 3 34 9 3 10 35 4 35 36 4 36 11 4 8 37 5 37 38 5 38 39 5 39 40 5 40 41 5 41 42 5 42 43 5 43 44 5 44 45 5 45 46 5 46 47 5 47 48 5 48 49 5 49 50 5 50 10 5 2 51 6 51 52 6 52 53 6 53 54 6 54 55 6 55 56 6 56 57 6 57 58 6 58 59 6 59 60 6 60 61 6 61 62 6 62 63 6 63 64 6 64 11 6 11 65 8 65 66 8 66 67 8 67 68 8 68 69 8 69 70 8 70 71 8 71 72 8 72 73 8 73 74 8 74 75 8 75 76 8 76 17 8 3 77 10 77 78 10 78 79 10 79 80 10 80 81 10 81 82 10 82 83 10 83 84 10 84 85 10 85 86 10 86 87 10 87 88 10 88 89 10 89 90 10 90 17 10 17 91 12 91 92 12 92 18 12 9 93 14 93 94 14 94 95 14 95 96 14 96 97 14 97 98 14 98 99 14 99 100 14 100 101 14 101 102 14 102 103 14 103 104 14 104 105 14 105 106 14 106 18 14 8 107 16 107 108 16 108 109 16 109 110 16 110 111 16 111 112 16 112 113 16 113 114 16 114 115 16 115 9 16 Triangles 801 125 32 2 7 51 125 2 7 31 32 124 7 125 124 32 7 52 125 51 7 116 124 125 7 64 126 63 7 127 128 122 7 40 141 41 7 117 134 141 7 134 41 141 7 142 54 55 7 135 117 142 7 55 135 142 7 46 136 47 7 123 136 120 7 137 60 61 7 137 123 120 7 127 122 126 7 135 55 56 7 56 130 135 7 41 134 42 7 131 42 134 7 49 127 50 7 49 128 127 7 129 39 38 7 133 39 129 7 47 138 48 7 139 61 62 7 42 140 43 7 42 131 140 7 48 128 49 7 48 138 128 7 122 128 121 7 138 121 128 7 139 62 63 7 139 122 121 7 133 40 39 7 141 40 133 7 54 132 53 7 54 142 132 7 119 140 131 7 122 63 126 7 122 139 63 7 143 36 35 7 143 127 126 7 36 144 11 7 64 144 126 7 144 64 11 7 121 145 139 7 138 145 121 7 138 123 145 7 143 144 36 7 143 126 144 7 117 141 133 7 138 47 136 7 123 138 136 7 137 61 139 7 137 145 123 7 145 137 139 7 116 146 124 7 37 146 38 7 147 134 117 7 135 147 117 7 149 35 10 7 50 149 10 7 31 150 8 7 150 37 8 7 150 31 124 7 148 140 119 7 150 146 37 7 124 146 150 7 146 129 38 7 116 129 146 7 149 50 127 7 143 149 127 7 149 143 35 7 132 133 129 7 117 133 132 7 132 142 117 7 130 119 147 7 119 131 147 7 130 147 135 7 147 131 134 7 152 132 129 7 116 152 129 7 46 45 151 7 151 120 136 7 120 151 118 7 136 46 151 7 60 137 120 7 153 118 151 7 153 45 44 7 151 45 153 7 43 153 44 7 148 153 140 7 153 148 118 7 43 140 153 7 52 152 125 7 53 152 52 7 152 116 125 7 53 132 152 7 59 120 118 7 120 59 60 7 118 58 59 7 119 154 148 7 154 119 130 7 154 56 57 7 130 56 154 7 155 58 118 7 155 148 154 7 148 155 118 7 155 57 58 7 154 57 155 7 161 195 191 11 69 174 70 11 194 191 195 11 193 68 67 11 195 161 186 11 69 189 174 11 201 179 168 11 168 202 201 11 202 180 201 11 156 205 183 11 160 206 199 11 180 206 201 11 206 180 199 11 212 177 178 11 188 213 159 11 169 214 213 11 214 159 213 11 217 171 181 11 188 218 211 11 220 174 189 11 222 159 214 11 228 179 201 11 179 228 197 11 169 238 214 11 240 170 211 11 156 242 205 11 243 186 161 11 187 243 161 11 244 25 26 11 187 244 26 11 181 248 217 11 248 181 191 11 211 246 188 11 228 206 160 11 201 206 228 11 230 244 187 11 238 205 242 11 233 215 173 11 246 213 188 11 240 218 172 11 211 218 240 11 222 214 238 11 69 68 189 11 68 193 189 11 160 199 182 11 71 70 203 11 70 174 203 11 26 243 187 11 26 27 243 11 159 239 188 11 172 218 239 11 218 188 239 11 237 228 160 11 237 197 228 11 164 198 234 11 198 170 234 11 170 240 234 11 202 158 180 11 168 216 202 11 194 248 191 11 215 163 208 11 211 200 246 11 242 156 223 11 223 207 250 11 250 231 177 11 207 231 250 11 224 168 179 11 253 216 176 11 249 253 176 11 254 177 212 11 239 254 212 11 257 178 219 11 171 258 181 11 249 259 204 11 225 260 172 11 260 240 172 11 234 240 260 11 245 208 247 11 225 245 247 11 73 184 74 11 262 216 168 11 216 262 219 11 178 257 212 11 164 234 247 11 234 260 247 11 260 225 247 11 202 253 158 11 253 202 216 11 159 222 254 11 159 254 239 11 250 177 254 11 254 222 250 11 249 176 259 11 221 179 197 11 224 179 221 11 219 262 252 11 209 183 205 11 222 238 242 11 250 222 242 11 223 250 242 11 231 178 177 11 178 231 176 11 259 176 231 11 25 244 229 11 24 25 229 11 230 229 244 11 176 265 178 11 219 265 216 11 265 219 178 11 265 176 216 11 237 268 197 11 221 269 230 11 269 229 230 11 269 221 197 11 270 200 211 11 170 270 211 11 272 224 221 11 161 274 187 11 274 230 187 11 190 275 200 11 233 276 171 11 276 258 171 11 210 258 276 11 268 269 197 11 268 229 269 11 225 172 264 11 268 23 24 11 23 268 237 11 229 268 24 11 252 276 255 11 276 252 210 11 236 73 72 11 262 168 224 11 224 210 262 11 262 210 252 11 259 231 207 11 224 258 210 11 224 272 258 11 245 264 173 11 255 173 264 11 264 245 225 11 271 270 175 11 271 200 270 11 266 273 193 11 278 264 172 11 239 278 172 11 280 175 270 11 170 280 270 11 175 282 271 11 284 185 271 11 185 284 236 11 285 181 258 11 286 183 209 11 273 286 209 11 193 281 189 11 281 193 273 11 220 189 281 11 157 282 175 11 219 252 257 11 255 257 252 11 257 255 264 11 278 239 212 11 290 226 169 11 22 291 21 11 291 182 21 11 226 292 209 11 273 292 281 11 292 273 209 11 220 292 226 11 220 281 292 11 291 160 182 11 160 291 237 11 283 190 185 11 267 286 266 11 286 273 266 11 283 185 236 11 71 203 283 11 196 284 282 11 282 284 271 11 285 161 191 11 285 274 161 11 181 285 191 11 72 296 236 11 283 296 71 11 296 283 236 11 296 72 71 11 283 297 190 11 299 208 163 11 301 180 158 11 209 304 226 11 304 209 205 11 304 169 226 11 79 80 279 11 180 301 199 11 237 22 23 11 22 237 291 11 275 213 246 11 275 246 200 11 169 213 275 11 275 290 169 11 230 272 221 11 230 274 272 11 285 272 274 11 272 285 258 11 304 238 169 11 205 238 304 11 309 261 306 11 294 314 277 11 63 62 288 11 87 295 287 11 84 307 251 11 263 316 311 11 82 311 81 11 192 316 263 11 305 56 277 11 57 56 305 11 295 88 303 11 295 87 88 11 80 81 316 11 311 316 81 11 174 297 203 11 220 297 174 11 297 283 203 11 300 241 53 11 53 241 54 11 308 79 279 11 78 79 308 11 163 215 302 11 302 215 233 11 85 307 84 11 293 11 64 11 293 65 11 11 312 55 54 11 287 319 289 11 325 317 58 11 289 326 307 11 298 327 279 11 327 308 279 11 331 303 261 11 309 331 261 11 336 279 80 11 316 336 80 11 57 325 58 11 57 305 325 11 337 335 315 11 336 298 279 11 336 192 298 11 316 192 336 11 84 251 83 11 317 325 318 11 59 58 317 11 317 324 59 11 328 77 78 11 308 328 78 11 330 277 314 11 53 332 300 11 332 53 52 11 320 241 300 11 300 321 320 11 55 294 277 11 56 55 277 11 312 294 55 11 331 329 303 11 335 64 63 11 288 335 63 11 335 293 64 11 335 288 315 11 289 86 287 11 287 86 87 11 86 289 307 11 307 85 86 11 62 315 288 11 322 315 62 11 232 329 157 11 329 295 303 11 232 319 329 11 330 305 277 11 330 325 305 11 306 261 90 11 261 89 90 11 321 199 301 11 312 54 241 11 241 320 312 11 3 323 30 11 323 3 77 11 323 77 328 11 328 308 327 11 61 338 62 11 338 322 62 11 324 313 60 11 324 60 59 11 220 226 290 11 275 190 290 11 297 290 190 11 220 290 297 11 312 320 294 11 293 335 337 11 333 83 251 11 311 82 333 11 83 333 82 11 51 165 162 11 51 332 52 11 162 332 51 11 306 17 76 11 90 17 306 11 89 303 88 11 261 303 89 11 198 280 170 11 198 232 280 11 186 27 28 11 186 243 27 11 257 278 212 11 264 278 257 11 65 334 66 11 65 293 334 11 326 251 307 11 310 251 326 11 217 233 171 11 302 233 217 11 338 227 322 11 300 162 321 11 300 332 162 11 165 19 20 11 280 157 175 11 232 157 280 11 310 333 251 11 342 263 311 11 333 342 311 11 343 233 173 11 343 255 276 11 255 343 173 11 233 343 276 11 344 156 183 11 190 345 185 11 271 345 200 11 345 271 185 11 345 190 200 11 346 331 309 11 347 248 194 11 298 347 194 11 301 348 321 11 348 320 321 11 192 347 298 11 20 340 165 11 163 342 299 11 248 347 217 11 310 341 299 11 341 310 326 11 158 348 301 11 348 294 320 11 294 348 314 11 287 295 319 11 295 329 319 11 313 339 227 11 344 227 339 11 340 162 165 11 198 349 232 11 349 319 232 11 21 350 20 11 340 350 182 11 350 340 20 11 350 21 182 11 351 247 208 11 351 299 341 11 299 351 208 11 352 267 322 11 227 352 322 11 199 353 182 11 353 340 182 11 354 164 341 11 326 354 341 11 302 356 163 11 357 158 253 11 249 357 253 11 352 344 183 11 227 344 352 11 356 342 163 11 263 342 356 11 356 192 263 11 349 289 319 11 354 198 164 11 349 198 354 11 326 289 354 11 349 354 289 11 306 167 309 11 194 327 298 11 327 194 235 11 328 327 235 11 324 339 313 11 324 317 318 11 338 313 227 11 313 338 60 11 338 61 60 11 346 282 157 11 346 196 282 11 329 346 157 11 346 329 331 11 314 204 330 11 204 357 249 11 158 357 348 11 357 314 348 11 204 314 357 11 162 353 321 11 353 162 340 11 353 199 321 11 359 156 344 11 339 359 344 11 195 360 194 11 360 235 194 11 192 361 347 11 361 217 347 11 361 192 356 11 361 302 217 11 356 302 361 11 186 360 195 11 235 360 166 11 166 323 235 11 323 328 235 11 223 358 207 11 359 223 156 11 358 223 359 11 183 286 352 11 286 267 352 11 355 337 267 11 266 355 267 11 193 362 266 11 362 355 266 11 29 30 166 11 30 323 166 11 67 362 193 11 362 67 66 11 334 355 362 11 334 362 66 11 76 167 306 11 167 76 75 11 184 236 284 11 236 184 73 11 284 196 184 11 363 184 196 11 364 166 360 11 186 364 360 11 166 364 29 11 359 365 358 11 365 359 339 11 366 346 309 11 346 366 196 11 167 366 309 11 363 366 167 11 363 196 366 11 364 28 29 11 186 28 364 11 74 363 75 11 363 74 184 11 363 167 75 11 330 204 256 11 325 330 256 11 358 318 256 11 318 325 256 11 318 339 324 11 318 365 339 11 365 318 358 11 367 256 204 11 259 367 204 11 207 367 259 11 367 207 358 11 256 367 358 11 165 2 19 11 51 2 165 11 215 245 173 11 215 208 245 11 267 315 322 11 267 337 315 11 351 164 247 11 341 164 351 11 342 310 299 11 333 310 342 11 337 334 293 11 355 334 337 11 34 377 33 15 378 34 9 15 93 378 9 15 378 377 34 15 94 378 93 15 369 377 378 15 88 380 89 15 376 380 375 15 381 90 89 15 88 390 380 15 390 375 380 15 94 394 378 15 394 369 378 15 395 90 381 15 380 381 89 15 380 376 381 15 379 381 376 15 393 96 97 15 388 370 393 15 97 388 393 15 80 392 81 15 370 387 392 15 387 81 392 15 90 395 17 15 395 91 17 15 389 102 103 15 388 97 98 15 388 371 370 15 81 387 82 15 371 387 370 15 95 394 94 15 95 385 394 15 369 394 368 15 385 368 394 15 383 79 78 15 386 79 383 15 383 369 368 15 368 386 383 15 391 103 104 15 87 390 88 15 87 384 390 15 379 376 382 15 77 383 78 15 383 377 369 15 377 383 77 15 386 80 79 15 392 80 386 15 96 385 95 15 96 393 385 15 391 396 375 15 396 391 104 15 33 397 3 15 77 397 377 15 397 77 3 15 397 33 377 15 105 396 104 15 105 382 396 15 396 376 375 15 382 376 396 15 379 92 91 15 91 395 379 15 379 395 381 15 370 398 393 15 398 370 392 15 398 386 368 15 392 386 398 15 385 398 368 15 385 393 398 15 103 391 389 15 382 105 106 15 92 401 18 15 401 106 18 15 101 102 400 15 374 400 389 15 400 374 372 15 102 389 400 15 106 401 382 15 401 379 382 15 379 401 92 15 389 384 374 15 391 375 384 15 375 390 384 15 391 384 389 15 372 402 400 15 403 86 374 15 403 384 87 15 384 403 374 15 86 403 87 15 101 402 100 15 101 400 402 15 399 402 372 15 85 374 86 15 82 405 83 15 404 98 99 15 99 100 404 15 399 404 402 15 404 399 373 15 100 402 404 15 405 82 387 15 371 405 387 15 373 405 371 15 98 404 388 15 404 371 388 15 404 373 371 15 83 406 84 15 406 399 372 15 406 373 399 15 406 83 405 15 405 373 406 15 85 407 374 15 372 407 406 15 407 372 374 15 84 407 85 15 84 406 407 15 22 421 417 18 27 416 420 18 409 417 421 18 410 420 416 18 408 441 415 18 408 415 440 18 408 440 417 18 408 416 441 18 108 109 413 18 113 114 414 18 24 415 25 18 408 419 418 18 409 413 430 18 410 431 414 18 409 421 413 18 410 414 420 18 109 430 413 18 113 414 431 18 26 416 27 18 22 417 23 18 2 32 428 18 3 429 33 18 111 112 418 18 108 413 432 18 114 433 414 18 112 431 418 18 408 434 419 18 111 418 419 18 410 418 431 18 25 415 441 18 24 440 415 18 110 111 419 18 107 432 427 18 115 426 433 18 9 34 426 18 9 426 115 18 8 427 31 18 8 107 427 18 408 417 434 18 408 435 416 18 107 108 432 18 114 115 433 18 27 420 28 18 21 421 22 18 29 429 30 18 19 428 20 18 409 430 419 18 25 441 26 18 23 440 24 18 26 441 416 18 23 417 440 18 32 422 428 18 33 429 423 18 110 419 430 18 410 435 418 18 411 432 425 18 412 424 433 18 413 425 432 18 414 433 424 18 408 418 435 18 410 416 435 18 409 434 417 18 411 427 432 18 412 433 426 18 409 419 434 18 2 428 19 18 3 30 429 18 28 420 424 18 21 425 421 18 109 110 430 18 112 113 431 18 411 439 422 18 412 423 438 18 413 421 425 18 414 424 420 18 20 428 422 18 29 423 429 18 28 438 29 18 20 439 21 18 33 437 34 18 31 436 32 18 411 422 436 18 412 437 423 18 20 422 439 18 29 438 423 18 411 425 439 18 412 438 424 18 32 436 422 18 33 423 437 18 411 436 427 18 412 426 437 18 28 424 438 18 21 439 425 18 31 427 436 18 34 437 426 18 End trunk-2018.02b/examples/conveyor/conveyor.py000066400000000000000000000031401324306050200210640ustar00rootroot00000000000000#!/usr/bin/python # -*- coding: utf-8 -*- # This example shows, how one can simulate the conveyor of # complex form, using mask-parameter in NewtonIntegrator. # The elements of conveyor are getting the velocity, but # are blocked from motion. # Conveyor element have a groupMask=5 which is after bitwise AND # operator with mask=2 in NewtonIntegrator gives 0 and prevents motion. # # So the interacting particles are getting motion from interaction # with conveyor ## PhysicalParameters Density=1000 frictionAngle=0.4 tc = 0.001 en = 0.3 es = 0.3 ## Import wall's geometry mat=O.materials.append(ViscElMat(density=Density,frictionAngle=frictionAngle,tc=tc,en=en,et=es)) sp=pack.SpherePack() sp.makeCloud((-0.3,0.05,0.05),(0.3,0.7,0.5),rMean=0.03, rRelFuzz=0.001) particles=O.bodies.append([sphere(c,r,mask=3) for c,r in sp]) from yade import ymport fctIds= O.bodies.append(ymport.gmsh('conveyor.mesh',scale=0.001,color=(1,0,0))) voxIds= O.bodies.append(utils.geom.facetBunker(center=[0,1.5,-0.7],dBunker=1.1, dOutput=0.2,hBunker=0.2,hOutput=0.2,hPipe=0.1, mask=5)) for i in fctIds: O.bodies[i].state.vel=Vector3(0,0.2,0) # Set conveyor velocity ## Timestep O.dt=.2*tc ## Engines O.engines=[ ForceResetter(), InsertionSortCollider([Bo1_Sphere_Aabb(),Bo1_Facet_Aabb()]), InteractionLoop( [Ig2_Sphere_Sphere_ScGeom(), Ig2_Facet_Sphere_ScGeom()], [Ip2_ViscElMat_ViscElMat_ViscElPhys()], [Law2_ScGeom_ViscElPhys_Basic()], ), NewtonIntegrator(damping=0,gravity=[0,0,-9.81], mask=2), DomainLimiter(lo=(-0.6,0.0,-1.0),hi=(0.6,2.0,1.0),iterPeriod=200), ] from yade import qt qt.View() #O.saveTmp() #O.run() trunk-2018.02b/examples/cylinders/000077500000000000000000000000001324306050200170005ustar00rootroot00000000000000trunk-2018.02b/examples/cylinders/bendingbeams.py000066400000000000000000000044231324306050200217730ustar00rootroot00000000000000# encoding: utf-8 "An example showing various bending beams." from yade.gridpfacet import * #### Parameter #### L=10. # length of the beam n=12 # number of nodes used to generate the beam r=L/50. # radius of the beam element #### Engines #### O.engines=[ ForceResetter(), InsertionSortCollider([Bo1_GridConnection_Aabb()]), InteractionLoop( [Ig2_GridNode_GridNode_GridNodeGeom6D()], [Ip2_CohFrictMat_CohFrictMat_CohFrictPhys(setCohesionNow=True,setCohesionOnNewContacts=False)], [Law2_ScGeom6D_CohFrictPhys_CohesionMoment()] ), NewtonIntegrator(gravity=(0,0,-10),damping=0.5,label='newton') ] #### Create materials and set different properties #### O.materials.append(CohFrictMat(young=1e6,poisson=0.3,density=1e1,frictionAngle=10,normalCohesion=1e7,shearCohesion=1e7,momentRotationLaw=False,label='mat1')) O.materials.append(CohFrictMat(young=1e6,poisson=0.3,density=1e1,frictionAngle=10,normalCohesion=1e7,shearCohesion=1e7,momentRotationLaw=True,label='mat2')) O.materials.append(CohFrictMat(young=3e6,poisson=0.3,density=1e1,frictionAngle=10,normalCohesion=1e7,shearCohesion=1e7,momentRotationLaw=True,label='mat3')) O.materials.append(CohFrictMat(young=1e7,poisson=0.3,density=1e1,frictionAngle=10,normalCohesion=1e7,shearCohesion=1e7,momentRotationLaw=True,label='mat4')) #### Vertices #### vertices1=[] vertices2=[] vertices3=[] vertices4=[] for i in range(0,n): vertices1.append( [i*L/n,0,0] ) vertices2.append( [i*L/n,1,0] ) vertices3.append( [i*L/n,2,0] ) vertices4.append( [i*L/n,3,0] ) #### Create cylinder connections #### nodesIds=[] cylIds=[] cylinderConnection(vertices1,r,nodesIds,cylIds,color=[1,0,0],highlight=False,intMaterial='mat1') cylinderConnection(vertices2,r,nodesIds,cylIds,color=[0,1,0],highlight=False,intMaterial='mat2') cylinderConnection(vertices3,r,nodesIds,cylIds,color=[0,0,1],highlight=False,intMaterial='mat3') cylinderConnection(vertices4,r,nodesIds,cylIds,color=[1,1,1],highlight=False,intMaterial='mat4') #### Set boundary conditions #### for i in range(0,4): O.bodies[nodesIds[i*n]].dynamic=False # O.bodies[nodesIds[i*n]].state.blockedDOFs='xyzXYZ' # O.bodies[nodesIds[i*n]].state.blockedDOFs='xyz' #### For viewing #### from yade import qt qt.View() #### Set a time step #### O.dt=1e-05 #### Allows to reload the simulation #### O.saveTmp() trunk-2018.02b/examples/cylinders/cylinder-cylinder.py000066400000000000000000000046641324306050200230040ustar00rootroot00000000000000# encoding: utf-8 "An example showing how to create two cylinders which are interacting." from yade.gridpfacet import * #### Parameter #### L=1. # length of the cylinder element r=0.1 # radius of the cylinder element phi=30. # friction angle E=1e6 # Young's modulus #### Engines #### O.engines=[ ForceResetter(), InsertionSortCollider([ Bo1_GridConnection_Aabb(), ]), InteractionLoop([ Ig2_GridNode_GridNode_GridNodeGeom6D(), Ig2_GridConnection_GridConnection_GridCoGridCoGeom(), ], [ Ip2_CohFrictMat_CohFrictMat_CohFrictPhys(setCohesionNow=True,setCohesionOnNewContacts=False), # internal cylinder physics Ip2_FrictMat_FrictMat_FrictPhys() # physics for external interactions, i.e., cylinder-cylinder interaction ], [ Law2_ScGeom6D_CohFrictPhys_CohesionMoment(), # contact law for "internal" cylinder forces Law2_GridCoGridCoGeom_FrictPhys_CundallStrack() # contact law for cylinder-cylinder interaction ] ), NewtonIntegrator(gravity=(-0.,0,-10),damping=0.5,label='newton'), ] #### Creat materials #### O.materials.append( CohFrictMat( young=E,poisson=0.3,density=1000,frictionAngle=radians(phi),normalCohesion=1e10,shearCohesion=1e10,momentRotationLaw=True,label='cMat' ) ) # material to create the gridConnections O.materials.append( FrictMat( young=E,poisson=0.3,density=1000,frictionAngle=radians(phi),label='fMat' ) ) # material for general interactions #### Create cylinders #### nodesIds=[] cylIds=[] cylinder((0,0,0),(L,0,0),radius=r,nodesIds=nodesIds,cylIds=cylIds, fixed=True,color=[1,0,0],intMaterial='cMat',extMaterial='fMat') cylinder((L/4,2*L/3,L),(L/4,-L/3,L),radius=r,nodesIds=nodesIds,cylIds=cylIds, fixed=False,color=[0,1,0],intMaterial='cMat',extMaterial='fMat') cylinder((0,2*L/3,L),(0,-L/3,L),radius=r,nodesIds=nodesIds,cylIds=cylIds, fixed=False,color=[0,1,0],intMaterial='cMat',extMaterial='fMat') cylinder((L/2,L/2,L),(L/2,-L/2,L),radius=r,nodesIds=nodesIds,cylIds=cylIds, fixed=False,color=[0,1,1],intMaterial='cMat',extMaterial='fMat') cylinder((3*L/4,L/3,L),(3*L/4,-2*L/3,L),radius=r,nodesIds=nodesIds,cylIds=cylIds, fixed=False,color=[0,0,1],intMaterial='cMat',extMaterial='fMat') cylinder((L,L/3,L),(L,-2*L/3,L),radius=r,nodesIds=nodesIds,cylIds=cylIds, fixed=False,color=[0,0,1],intMaterial='cMat',extMaterial='fMat') #### For viewing #### from yade import qt qt.View() Gl1_Sphere.stripes=True #### Set a time step #### O.dt=1e-06 #### Allows to reload the simulation #### O.saveTmp() trunk-2018.02b/examples/cylinders/cylinderconnection-roots.py000066400000000000000000000065731324306050200244220ustar00rootroot00000000000000#!/usr/bin/python # -*- coding: utf-8 -*- "Beam-like behaviour with cylinderConnections for roots interaction with spheres." from yade import pack from yade.gridpfacet import * #### Parameter #### young=4.0e6 poisson=3 density=1e3 frictionAngle1=radians(15) frictionAngle2=radians(15) frictionAngle3=radians(15) #### Engines #### O.engines=[ ForceResetter(), InsertionSortCollider([ Bo1_Box_Aabb(), Bo1_Sphere_Aabb(), Bo1_GridConnection_Aabb(), ]), InteractionLoop([ Ig2_Sphere_Sphere_ScGeom(), Ig2_Box_Sphere_ScGeom(), Ig2_GridNode_GridNode_GridNodeGeom6D(), Ig2_Sphere_GridConnection_ScGridCoGeom(), Ig2_GridConnection_GridConnection_GridCoGridCoGeom() ], [ Ip2_CohFrictMat_CohFrictMat_CohFrictPhys(setCohesionNow=True,setCohesionOnNewContacts=False), # internal cylinder physics Ip2_FrictMat_FrictMat_FrictPhys() # physics for external interactions, i.e., cylinder-cylinder, sphere-sphere, cylinder-sphere ], [ Law2_ScGeom_FrictPhys_CundallStrack(), # contact law for sphere-sphere Law2_ScGridCoGeom_FrictPhys_CundallStrack(), # contact law for cylinder-sphere Law2_ScGeom6D_CohFrictPhys_CohesionMoment(), # contact law for "internal" cylinder forces Law2_GridCoGridCoGeom_FrictPhys_CundallStrack() # contact law for cylinder-cylinder interaction ] ), GlobalStiffnessTimeStepper(timestepSafetyCoefficient=0.1,label='ts'), NewtonIntegrator(gravity=(1,-9.81,0),damping=0.5,label='newton'), ] #### Spheres #### O.materials.append(FrictMat(young=4.0e6,poisson=0.5,frictionAngle=frictionAngle1,density=1600,label='spheremat')) Ns=90 sp=pack.SpherePack() if os.path.exists("cloud4cylinders"+`Ns`): print "loading spheres from file" sp.load("cloud4cylinders"+`Ns`) else: print "generating spheres" Ns=sp.makeCloud(Vector3(-0.3,0.2,-1.0),Vector3(+0.3,+0.5,+1.0),-1,.2,Ns,False,0.8) sp.save("cloud4cylinders"+`Ns`) O.bodies.append([sphere(center,rad,material='spheremat') for center,rad in sp]) #### Walls #### O.materials.append(FrictMat(young=1.0e6,poisson=0.2,density=2.60e3,frictionAngle=frictionAngle2,label='wallmat')) walls=aabbWalls((Vector3(-0.3,-0.15,-1),Vector3(+0.3,+1.0,+1)),thickness=.1,material='wallmat') wallIds=O.bodies.append(walls) #Assemble cylinders in sinusoidal shapes O.materials.append( CohFrictMat( young=10*young,poisson=poisson,density=density,frictionAngle=frictionAngle3,normalCohesion=1e40,shearCohesion=1e40,momentRotationLaw=True,label='cylindermat') ) # material to create the gridConnections O.materials.append( FrictMat( young=young,poisson=poisson,density=13.0*density,frictionAngle=frictionAngle3,label='fMat' ) ) # material for general interactions with cylinders Ne=30 # number of cylinders dy=0.03 dx=0.2 dz=0.2 Nc=1 # number of additional cylinderConnection nodesIds=[] cylIds=[] for j in range(-Nc, Nc+1): dyj = abs(float(j))*dy dxj = abs(float(j))*dx dzj = float(j)*dz vertices=[] for i in range(0, Ne+1): omega=20/float(Ne); hy=0.0; hz=0.05; hx=1.5; px=float(i)*hx/float(Ne)-0.8+dxj; py=sin(float(i)*omega)*hy+dyj; pz=cos(float(i)*omega)*hz+dzj; vertices.append([pz,py,px]) cylinderConnection(vertices,0.02,nodesIds,cylIds,color=[1,0,0],highlight=False,intMaterial='cylindermat',extMaterial='fMat') O.bodies[nodesIds[-1]].state.blockedDOFs='xyzXYZ' O.bodies[nodesIds[-Ne-1]].state.blockedDOFs='xyzXYZ' #### For viewing #### from yade import qt qt.View() Gl1_Sphere.stripes=True #### Allows to reload the simulation #### O.saveTmp() trunk-2018.02b/examples/cylinders/mikado.py000066400000000000000000000064141324306050200206230ustar00rootroot00000000000000# encoding: utf-8 "An example showing how two create cylinders with random length." from yade.gridpfacet import * #### Parameters #### Lmin=3. # minimum length Lmax=6. # maximum length n=10 # number of cylinders in one direction r=0.5 # radius of the cylinder element phi=30. # friction angle E=1e6 # Young's modulus #### Engines #### O.engines=[ ForceResetter(), InsertionSortCollider([ Bo1_GridConnection_Aabb(), Bo1_PFacet_Aabb(), ]), InteractionLoop([ Ig2_GridNode_GridNode_GridNodeGeom6D(), Ig2_GridConnection_GridConnection_GridCoGridCoGeom(), # cylinder-cylinder interaction Ig2_Sphere_PFacet_ScGridCoGeom(), # needed for GridNode-pFacet interaction (why is this not included in Ig2_GridConnection_PFacet_ScGeom???) Ig2_GridConnection_PFacet_ScGeom(), # Cylinder-pFcet interaction ], [ Ip2_CohFrictMat_CohFrictMat_CohFrictPhys(setCohesionNow=True,setCohesionOnNewContacts=False), Ip2_FrictMat_FrictMat_FrictPhys() ], [ Law2_ScGeom6D_CohFrictPhys_CohesionMoment(), # contact law for "internal" cylider forces Law2_ScGridCoGeom_FrictPhys_CundallStrack(), # contact law for Cylinder-pFacet interaction Law2_GridCoGridCoGeom_FrictPhys_CundallStrack() # contact law for cylinder-cylinder interaction ] ), GlobalStiffnessTimeStepper(timestepSafetyCoefficient=0.1,label='ts'), NewtonIntegrator(gravity=(0.,0,-10),damping=0.5,label='newton'), #NewtonIntegrator(gravity=(-1.,0,-10),damping=0.5,label='newton'), ] #### Creat materials #### O.materials.append( CohFrictMat( young=E,poisson=0.3,density=1000,frictionAngle=radians(phi),normalCohesion=1e10,shearCohesion=1e10,momentRotationLaw=True,label='cMat' ) ) # material to create the gridConnections O.materials.append( FrictMat( young=E,poisson=0.3,density=1000,frictionAngle=radians(phi),label='fMat' ) ) # material for general interactions #### Create cylinders #### nodesIds=[] cylIds=[] ext=12. dxy=ext/(n-1) dL=Lmax-Lmin random.seed( 10 ) for i in range(0,n): y=-ext/2+i*dxy for i in range(0,n): x=-ext/2+i*dxy L=Lmin+dL*random.random() color=[random.random(),random.random(),random.random()] cylinder((x,y,2*r),(x,y,L+2*r),radius=r,nodesIds=nodesIds,cylIds=cylIds, fixed=False,color=color,intMaterial='cMat',extMaterial='fMat') #### Creat ground with pFacets #### color=[255./255.,102./255.,0./255.] n0=O.bodies.append( gridNode([-10,-10,0],r,wire=False,fixed=True,material='cMat',color=color) ) n1=O.bodies.append( gridNode([10,-10,0],r,wire=False,fixed=True,material='cMat',color=color) ) n2=O.bodies.append( gridNode([10,10,0],r,wire=False,fixed=True,material='cMat',color=color) ) n3=O.bodies.append( gridNode([-10,10,0],r,wire=False,fixed=True,material='cMat',color=color) ) O.bodies.append( gridConnection(n0,n1,r,color=color,material='fMat') ) O.bodies.append( gridConnection(n1,n2,r,color=color,material='fMat') ) O.bodies.append( gridConnection(n2,n0,r,color=color,material='fMat') ) O.bodies.append( gridConnection(n2,n3,r,color=color,material='fMat') ) O.bodies.append( gridConnection(n3,n0,r,color=color,material='fMat') ) O.bodies.append( pfacet(n0,n1,n2,wire=False,material='fMat',color=color) ) O.bodies.append( pfacet(n0,n2,n3,wire=False,material='fMat',color=color) ) #### For viewing #### from yade import qt qt.View() Gl1_Sphere.stripes=True #### Allows to reload the simulation #### O.saveTmp() trunk-2018.02b/examples/deformableelem/000077500000000000000000000000001324306050200177475ustar00rootroot00000000000000trunk-2018.02b/examples/deformableelem/Minimal.py000066400000000000000000000053171324306050200217150ustar00rootroot00000000000000#!/usr/bin/python # -*- coding: utf-8 -*- # Created by Burak ER from yade.deformableelementsutils import *; O=Omega() ## Create deformable elements internal materials and interaction element material mat=LinIsoRayleighDampElastMat(label='aluminiummaterial'); intermat=LinCohesiveStiffPropDampElastMat(label='intermat') intermat.youngmodulus=70e11; mat.density=2700; mat.youngmodulus=70e9 O.materials.append(intermat) O.materials.append(mat) ## Generate deformable element mesh from GMSH2 format nodesofelement1=[[0, 0 ,0],[0, 1, 0],[1 ,0 ,0],[0, 0 ,1]] nodesofelement2=[[0, 0 ,0],[0, 1, 0],[1,0 ,0],[0, 0 ,-1]] noderadius=0.1*(0.75*tetrahedronvolume(nodesofelement1))**0.33333333; [elbody, nodebodies1]=tetrahedral_element(mat,nodesofelement1,Lin4NodeTetra,radius=noderadius) [elbody, nodebodies2]=tetrahedral_element(mat,nodesofelement2,Lin4NodeTetra,radius=noderadius) nodebodies1[3].state.pos+=Vector3(0,0,0.01); interfaceelementpairs=[]; interfaceelementpairs.append([nodebodies1[0], nodebodies2[0]]); interfaceelementpairs.append([nodebodies1[1], nodebodies2[1]]); interfaceelementpairs.append([nodebodies1[2], nodebodies2[2]]); [elbody, nodebodies]=interaction_element(intermat,interfaceelementpairs,Lin4NodeTetra_Lin4NodeTetra_InteractionElement); #mesh=tetrahedral_mesh_generator('model.msh',Lin4NodeTetra,'aluminiummaterial',Lin4NodeTetra_Lin4NodeTetra_InteractionElement,'intermat') ## Define the body boundary force # x position limits of bodies that are subject to the force force_tail_body_lowerlimit=-1.01; force_tail_body_upperlimit=-0.9999; # Angular frequency of the force amplitude=100000; period=(1e-3) omega=2*pi/period; applicationperiod=period/2; ## Define the fixed boundary # x position limits of bodies that are subject to the fixed boundary fixed_tail_body_lowerlimit=0.99; fixed_tail_body_upperlimit=1.01; # Time step determines the exiting period of the integrator since the integrator performs one step from current_time to current_time+dt; using many substeps for any value of dt; then stops. O.dt=1e-9; O.engines=[ ForceResetter(), ## Apply internal force to the deformable elements and internal force of the interaction element FEInternalForceEngine([If2_Lin4NodeTetra_LinIsoRayleighDampElast(),If2_2xLin4NodeTetra_LinCohesiveStiffPropDampElastMat()]), NewtonIntegrator(damping=0,gravity=[0,0,0]), #PyRunner(virtPeriod=1e-99,command='applyforcetoelements()'), # ## Plotting data: adds plots after one step of the integrator engine #PyRunner(virtPeriod=1e-99,command='addplot()') ] from yade import plot plot.plots={'t':'vel','time':'pos','tm':'force'} plot.plot(subPlots=True) try: from yade import qt qt.View() qt.Controller() except ImportError: pass trunk-2018.02b/examples/deformableelem/MinimalTensileTest.py000066400000000000000000000141211324306050200240720ustar00rootroot00000000000000#!/usr/bin/python # -*- coding: utf-8 -*- # Created by Burak ER from yade.deformableelementsutils import *; O=Omega() pressure=70e7;# N/m^2 wx=1;#m wy=1;#m wz=1;#m ## Create deformable elements internal materials and interaction element material mat=LinIsoRayleighDampElastMat(label='aluminiummaterial'); intermat=LinCohesiveStiffPropDampElastMat(label='intermat') intermat.youngmodulus=70e11; mat.density=2700; mat.beta=0.05; mat.alpha=0.05; mat.youngmodulus=70e9; O.materials.append(intermat) O.materials.append(mat) #create points of a cube with number of 8 points p0=Vector3([0, 0 ,0]); p1=Vector3([0, wy, 0]); p2=Vector3([wx ,0 ,0]); p3=Vector3([0, 0 ,wz]); p4=Vector3([0, wy, wz]); p5=Vector3([wx, wy, 0]); p6=Vector3([wx, 0 ,wz]); p7=Vector3([wx, wy ,wz]); nodesofelement1=[p0,p1,p2,p3]; nodesofelement2=[p3,p4,p1,p7]; nodesofelement3=[p2,p5,p1,p7]; nodesofelement4=[p7,p2,p3,p6]; nodesofelement5=[p1,p2,p3,p7]; noderadius=0.1*(0.75*tetrahedronvolume(nodesofelement1))**0.33333333; [elbody1, nodebodies1]=tetrahedral_element(mat,nodesofelement1,Lin4NodeTetra,radius=noderadius) [elbody2, nodebodies2]=tetrahedral_element(mat,nodesofelement2,Lin4NodeTetra,radius=noderadius) [elbody3, nodebodies3]=tetrahedral_element(mat,nodesofelement3,Lin4NodeTetra,radius=noderadius) [elbody4, nodebodies4]=tetrahedral_element(mat,nodesofelement4,Lin4NodeTetra,radius=noderadius) [elbody5, nodebodies5]=tetrahedral_element(mat,nodesofelement5,Lin4NodeTetra,radius=noderadius) interfaceelementpairs=[]; #interface of 1-5 interfaceelementpairs.append([nodebodies1[1], nodebodies5[0]]); interfaceelementpairs.append([nodebodies1[2], nodebodies5[1]]); interfaceelementpairs.append([nodebodies1[3], nodebodies5[2]]); [elbodyinter, nodebodiesinter]=interaction_element(intermat,interfaceelementpairs,Lin4NodeTetra_Lin4NodeTetra_InteractionElement); #interface of 2-5 interfaceelementpairs=[]; interfaceelementpairs.append([nodebodies2[0], nodebodies5[2]]); interfaceelementpairs.append([nodebodies2[2], nodebodies5[0]]); interfaceelementpairs.append([nodebodies2[3], nodebodies5[3]]); [elbodyinter, nodebodiesinter]=interaction_element(intermat,interfaceelementpairs,Lin4NodeTetra_Lin4NodeTetra_InteractionElement); #interface of 3-5 interfaceelementpairs=[]; interfaceelementpairs.append([nodebodies3[0], nodebodies5[1]]); interfaceelementpairs.append([nodebodies3[2], nodebodies5[0]]); interfaceelementpairs.append([nodebodies3[3], nodebodies5[3]]); [elbodyinter, nodebodiesinter]=interaction_element(intermat,interfaceelementpairs,Lin4NodeTetra_Lin4NodeTetra_InteractionElement); #interface of 3-5 interfaceelementpairs=[]; interfaceelementpairs.append([nodebodies3[0], nodebodies5[1]]); interfaceelementpairs.append([nodebodies3[2], nodebodies5[0]]); interfaceelementpairs.append([nodebodies3[3], nodebodies5[3]]); [elbodyinter, nodebodiesinter]=interaction_element(intermat,interfaceelementpairs,Lin4NodeTetra_Lin4NodeTetra_InteractionElement); #interface of 4-5 interfaceelementpairs=[]; interfaceelementpairs.append([nodebodies4[0], nodebodies5[3]]); interfaceelementpairs.append([nodebodies4[1], nodebodies5[1]]); interfaceelementpairs.append([nodebodies4[2], nodebodies5[2]]); [elbodyinter, nodebodiesinter]=interaction_element(intermat,interfaceelementpairs,Lin4NodeTetra_Lin4NodeTetra_InteractionElement); # Time step determines the exiting period of the integrator since the integrator performs one step from current_time to current_time+dt; using many substeps for any value of dt; then stops. initpos0=O.bodies[nodebodies1[0].id].state.pos; def applyforcetoelements(): pos0=O.bodies[nodebodies3[0].id].state.pos; pos1=O.bodies[nodebodies3[1].id].state.pos; pos2=O.bodies[nodebodies3[2].id].state.pos; area1=0.5*((pos1-pos0).cross(pos2-pos0)).norm(); pos0=O.bodies[nodebodies1[0].id].state.pos; pos1=O.bodies[nodebodies1[1].id].state.pos; pos2=O.bodies[nodebodies1[2].id].state.pos; area2=0.5*((pos1-pos0).cross(pos2-pos0)).norm(); Traction=area1*pressure; O.forces.addF(nodebodies3[0].id,Vector3(0,0,-(0.3*Traction))); O.forces.addF(nodebodies3[1].id,Vector3(0,0,-(0.3*Traction))); O.forces.addF(nodebodies3[2].id,Vector3(0,0,-(0.3*Traction))); Traction=area2*pressure; O.forces.addF(nodebodies1[0].id,Vector3(0,0,-(0.3*Traction))); O.forces.addF(nodebodies1[1].id,Vector3(0,0,-(0.3*Traction))); O.forces.addF(nodebodies1[2].id,Vector3(0,0,-(0.3*Traction))); def fixboundryelements(): nodebodies1[3].state.blockedDOFs='xyzXYZ' nodebodies2[0].state.blockedDOFs='xyzXYZ' nodebodies2[1].state.blockedDOFs='xyzXYZ' nodebodies2[3].state.blockedDOFs='xyzXYZ' nodebodies3[3].state.blockedDOFs='xyzXYZ' nodebodies4[0].state.blockedDOFs='xyzXYZ' nodebodies4[2].state.blockedDOFs='xyzXYZ' nodebodies4[3].state.blockedDOFs='xyzXYZ' nodebodies5[2].state.blockedDOFs='xyzXYZ' nodebodies5[3].state.blockedDOFs='xyzXYZ' fixboundryelements(); def addplot(): vecpos=(O.bodies[nodebodies1[0].id].state.pos-initpos0) plot.addData(displacement=abs(vecpos[2]),time=O.time) ##integratoreng=RungeKuttaCashKarp54Integrator([ ## ForceResetter(), ## Apply internal force to the deformable elements and internal force of the interaction element ## FEInternalForceEngine([If2_Lin4NodeTetra_LinIsoRayleighDampElast(),If2_2xLin4NodeTetra_LinCohesiveStiffPropDampElastMat()]), ## PyRunner(virtPeriod=1e-99,command='applyforcetoelements()'), ##]) ##O.engines=[integratoreng, ## PyRunner(iterPeriod=1,command='addplot()') ##] ##integratoreng.rel_err=1e-3; ##integratoreng.abs_err=1e-3; O.engines=[ForceResetter(), PyRunner(virtPeriod=1e-99,command='applyforcetoelements()'), ## Apply internal force to the deformable elements and internal force of the interaction element FEInternalForceEngine([If2_Lin4NodeTetra_LinIsoRayleighDampElast(),If2_2xLin4NodeTetra_LinCohesiveStiffPropDampElastMat()]), NewtonIntegrator(damping=0,gravity=[0,0,0]), PyRunner(virtPeriod=1e-99,command='addplot()'), ]; O.dt=1e-7; from yade import plot plot.plots={'time':'displacement'} plot.plot(subPlots=False) try: from yade import qt qt.View() qt.Controller() except ImportError: pass trunk-2018.02b/examples/deformableelem/ReadMe000066400000000000000000000001071324306050200210250ustar00rootroot00000000000000This folder includes the simulation files for the deformable elements. trunk-2018.02b/examples/deformableelem/main.py000066400000000000000000000017411324306050200212500ustar00rootroot00000000000000#!/usr/bin/env python # -*- coding: utf-8 -*- # # untitled.py # # Copyright 2014 Burak ER # # 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 2 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, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, # MA 02110-1301, USA. # # import sys from yadeimport import * def main(): execfile("testDeformableBodies_pressure.py"); return 0 if __name__ == '__main__': main() trunk-2018.02b/examples/deformableelem/model.geo000066400000000000000000000002121324306050200215360ustar00rootroot00000000000000Merge "model.msh"; Physical Volume(100002) = {100001}; Physical Volume(100003) = {100001}; Field[1] = Max; Field[1].FieldsList = {1, -3}; trunk-2018.02b/examples/deformableelem/model.msh000066400000000000000000002161741324306050200215730ustar00rootroot00000000000000$MeshFormat 2.000000 0 8 $EndMeshFormat $Nodes 391 1 1.000000 -0.000000 0.100001 2 1.000000 0.057628 0.081449 3 1.000000 0.093943 0.033143 4 1.000000 0.095747 -0.027197 5 1.000000 0.057793 -0.081311 6 1.000000 0.000213 -0.099978 7 1.000000 -0.057462 -0.081583 8 1.000000 -0.093881 -0.033347 9 1.000000 -0.095809 0.026994 10 1.000000 -0.061597 0.078191 11 -1.000000 0.019509 0.098078 12 -1.000000 -0.040630 0.091124 13 -1.000000 -0.088702 0.045164 14 -1.000000 -0.098583 -0.014392 15 -1.000000 -0.072274 -0.068806 16 -1.000000 -0.019297 -0.098100 17 -1.000000 0.040819 -0.091026 18 -1.000000 0.088058 -0.046370 19 -1.000000 0.098604 0.014177 20 -1.000000 0.072410 0.068640 21 0.938054 0.024884 0.096449 22 0.942040 0.099428 0.005807 23 0.937511 -0.039138 0.091924 24 0.938529 -0.099197 -0.008157 25 0.937866 -0.085505 0.051146 26 -0.935772 -0.009233 0.099090 27 -0.935059 -0.098116 0.019135 28 -0.936955 0.047824 0.087279 29 -0.932001 0.012945 -0.098726 30 -0.939627 0.098408 -0.016167 31 -0.941572 0.091831 0.039309 32 0.940439 0.077604 0.062311 33 0.942163 0.084067 -0.053835 34 0.942625 0.035114 -0.093344 35 0.940410 -0.025721 -0.096193 36 0.939409 -0.076290 -0.063912 37 -0.935795 -0.065696 0.074826 38 -0.933838 -0.090360 -0.042063 39 -0.933082 -0.048316 -0.087018 40 -0.931798 0.067594 -0.073269 41 -0.871443 -0.075186 -0.065258 42 -0.869209 0.044603 -0.089003 43 -0.871593 -0.099242 -0.007698 44 0.881241 0.009830 -0.099031 45 0.875387 0.093148 0.035763 46 -0.864205 0.090445 -0.041904 47 0.878132 -0.089425 -0.043812 48 0.879260 -0.048259 -0.087047 49 -0.872222 -0.084249 0.053495 50 -0.870940 -0.021108 -0.097594 51 0.872440 0.049522 0.086374 52 -0.873039 -0.032991 0.093988 53 0.872787 -0.013273 0.098693 54 0.874853 -0.067709 0.073175 55 -0.876103 0.028308 0.095409 56 -0.881567 0.078171 0.061620 57 0.877005 -0.098192 0.018359 58 -0.811006 -0.093050 -0.036088 59 -0.812500 -0.056997 0.081965 60 -0.815107 0.006523 0.099357 61 0.802304 0.015762 0.098448 62 -0.891527 0.098734 0.012860 63 0.816626 -0.068002 -0.072933 64 -0.810476 -0.094957 0.029800 65 0.818500 -0.013640 -0.098656 66 0.800850 0.074404 0.066211 67 -0.811756 0.008702 -0.099144 68 -0.822586 0.064940 0.075446 69 -0.810348 -0.050539 -0.085830 70 0.814331 -0.098360 -0.016659 71 -0.805975 0.064981 -0.075414 72 -0.834882 0.097205 0.022391 73 -0.745277 -0.099836 -0.001669 74 -0.752836 -0.022840 -0.097068 75 -0.746826 -0.077739 0.062145 76 0.755653 -0.085163 -0.051785 77 -0.787922 0.096181 -0.025768 78 0.813475 -0.089194 0.044245 79 -0.736253 0.073409 -0.067424 80 0.811367 -0.046396 0.088044 81 0.879466 0.096680 -0.024119 82 -0.750530 -0.015133 0.098509 83 -0.770244 0.093506 0.034581 84 -0.757482 0.046185 0.088156 85 -0.760407 0.031490 -0.094445 86 0.726801 0.044933 0.088826 87 -0.746685 -0.075100 -0.065363 88 0.808824 0.099272 0.007394 89 0.745199 -0.021133 0.097586 90 0.740502 0.093959 0.033089 91 0.882076 0.063560 -0.076578 92 0.822272 0.043508 -0.089586 93 0.756728 -0.035506 -0.093225 94 0.759724 0.023852 -0.096761 95 0.753758 -0.099192 0.008204 96 -0.684587 -0.092912 -0.036540 97 -0.682890 -0.092967 0.036358 98 -0.683525 -0.043358 0.089667 99 -0.690823 -0.046726 -0.087868 100 0.695500 -0.095271 -0.028765 101 0.688133 -0.007012 0.099310 102 -0.703040 0.018413 -0.098187 103 -0.689913 0.025783 0.096175 104 -0.697577 0.081991 0.056965 105 0.818522 0.086597 -0.049102 106 0.696991 -0.058629 -0.080626 107 0.768902 0.067104 -0.073670 108 0.707442 0.065902 -0.074657 109 -0.637357 -0.006706 -0.099340 110 0.697299 0.003941 -0.099611 111 -0.620183 -0.067156 0.073628 112 -0.625681 -0.072905 -0.068037 113 -0.714981 0.098909 -0.011081 114 0.634328 0.036175 -0.093022 115 0.639987 -0.079176 -0.060395 116 -0.620134 -0.099401 0.006085 117 -0.630755 0.065920 0.074642 118 0.751088 -0.077263 0.062727 119 -0.624602 -0.003001 0.099704 120 0.688101 -0.059010 0.080314 121 0.635669 -0.026580 -0.095933 122 0.649793 0.030560 0.094727 123 -0.654341 0.071147 -0.070179 124 -0.640134 0.099222 0.007903 125 -0.557233 -0.088052 0.046381 126 -0.579739 0.088853 -0.044883 127 0.629683 -0.027527 0.095647 128 0.581685 -0.059301 -0.080074 129 0.626003 -0.080927 0.058263 130 0.692300 -0.094122 0.032552 131 0.753609 0.095390 -0.028372 132 -0.567124 -0.042053 -0.090366 133 -0.557087 -0.093671 -0.034039 134 -0.559908 -0.030373 0.094782 135 0.561913 0.006865 -0.099323 136 -0.564148 0.042555 0.090096 137 -0.581315 0.032878 -0.094024 138 -0.526354 0.062898 -0.077123 139 0.634360 -0.099712 -0.002928 140 -0.496502 0.014829 0.098539 141 -0.493214 -0.098880 0.011375 142 -0.569772 0.094919 0.029926 143 -0.498694 0.080073 0.059302 144 -0.495822 -0.060372 0.079195 145 0.564055 -0.059477 0.079931 146 0.570122 0.070487 -0.070894 147 -0.490024 -0.072037 -0.069095 148 0.559589 -0.097600 0.021087 149 0.588799 -0.093766 -0.033727 150 0.609987 0.060708 0.078920 151 -0.429987 -0.098384 -0.016411 152 -0.497960 0.096456 -0.024860 153 -0.512080 0.003199 -0.099685 154 -0.432113 -0.083936 0.054081 155 -0.463067 0.052833 -0.084603 156 -0.443006 -0.013591 -0.098662 157 -0.430686 0.060527 0.079068 158 -0.432219 -0.013252 0.098695 159 0.513552 -0.024604 0.096533 160 0.535251 -0.088219 -0.046069 161 -0.439095 0.097339 0.021947 162 -0.358500 0.096351 0.025203 163 0.526581 -0.047303 -0.087558 164 -0.413833 0.088892 -0.044809 165 0.516558 0.094321 -0.031896 166 0.573545 0.013217 0.098698 167 0.459760 0.086786 -0.048748 168 0.558115 0.076061 0.064192 169 0.499226 0.047424 0.087495 170 0.606462 0.090292 0.042190 171 0.575299 0.099666 -0.003395 172 0.511513 0.093712 0.033905 173 0.448026 0.084594 0.052851 174 0.424319 0.040181 0.091366 175 0.495768 -0.083519 0.054861 176 0.488341 -0.099013 -0.010019 177 0.463664 -0.000006 0.100000 178 0.442850 -0.098106 0.019236 179 -0.366697 0.032592 0.094110 180 0.463815 -0.047028 0.087706 181 0.427085 -0.075753 0.064567 182 0.415809 -0.024858 0.096456 183 -0.368043 -0.047413 0.087500 184 -0.367314 -0.095094 0.029349 185 0.363791 0.007696 0.099242 186 0.387540 -0.095872 0.026784 187 -0.391796 0.034105 -0.093651 188 -0.365881 -0.088273 -0.045968 189 -0.340582 0.073943 -0.066773 190 0.470979 0.099640 0.003661 191 0.448116 0.047746 -0.087321 192 -0.302643 0.063541 0.076594 193 0.482888 -0.008791 -0.099134 194 -0.373194 -0.031712 -0.094377 195 0.395939 0.038829 -0.092088 196 0.412407 0.099688 0.003171 197 0.418600 -0.093568 -0.034377 198 -0.422894 -0.067776 -0.073120 199 0.425232 -0.049264 -0.086510 200 0.363887 -0.064907 -0.075474 201 0.427612 0.003658 -0.099639 202 0.399982 0.080545 -0.058727 203 0.370545 0.078279 0.061489 204 -0.304472 -0.076581 0.063557 205 -0.304362 -0.006479 0.099362 206 -0.242267 -0.042837 0.089946 207 0.369415 -0.059852 0.079623 208 0.321689 -0.088999 0.044609 209 0.306834 -0.038380 0.092329 210 -0.301822 -0.099313 -0.006978 211 -0.304053 -0.062997 -0.077041 212 -0.317257 0.006622 -0.099348 213 0.347270 0.098824 -0.011941 214 0.375968 -0.008958 -0.099118 215 -0.288060 0.095794 0.027041 216 0.339465 0.049011 -0.086646 217 0.297140 0.092182 0.038653 218 0.273603 0.088265 -0.045982 219 -0.232031 -0.027711 -0.095590 220 -0.261934 0.046465 -0.088007 221 -0.235090 -0.092834 -0.036798 222 -0.239667 -0.093727 0.033855 223 -0.170524 -0.079409 -0.060111 224 -0.286256 0.092254 -0.038519 225 0.303363 0.037160 0.092724 226 -0.214829 0.082471 -0.056381 227 -0.190091 0.032997 -0.093987 228 0.321061 -0.019034 -0.098125 229 -0.233940 0.084623 0.052796 230 -0.240425 0.029060 0.095181 231 -0.175286 0.099158 0.008548 232 0.240908 0.068588 0.072453 233 0.235769 -0.009991 0.099016 234 -0.237455 0.099696 -0.003093 235 0.277035 0.027429 -0.095676 236 0.224595 0.056242 -0.082585 237 0.237297 0.098871 0.011467 238 0.188995 0.089481 -0.043706 239 0.180822 0.093776 0.033695 240 0.270227 -0.033538 -0.093822 241 -0.146674 0.080192 -0.059158 242 0.179673 0.042873 0.089927 243 -0.121320 0.032425 -0.094161 244 -0.152396 -0.026352 -0.096002 245 0.300283 -0.074170 -0.066496 246 0.225785 -0.077997 -0.061832 247 0.345139 -0.097165 -0.022521 248 0.274849 -0.099259 -0.007529 249 0.256545 -0.077852 0.062010 250 0.207579 -0.098932 0.010842 251 -0.075942 0.076755 -0.063346 252 -0.105687 -0.071374 -0.069902 253 -0.088204 -0.020908 -0.097654 254 0.219595 -0.005877 -0.099421 255 -0.063350 0.029740 -0.094975 256 -0.026275 -0.011388 -0.098879 257 0.122795 0.076265 0.063943 258 0.116108 0.099689 -0.003163 259 0.114689 0.067940 -0.072984 260 0.079681 0.049557 0.086354 261 -0.051242 -0.058303 -0.080894 262 0.121127 0.015191 0.098504 263 0.008279 -0.066362 -0.074280 264 0.058023 -0.007181 0.099293 265 0.030621 0.047061 0.087688 266 0.111355 -0.056753 0.082166 267 -0.006181 0.055395 -0.083234 268 0.167169 0.033838 -0.093732 269 0.168268 -0.016104 0.098414 270 -0.111125 -0.098847 -0.011706 271 -0.064140 0.093130 0.035822 272 -0.114993 -0.085413 0.051317 273 0.162715 -0.038010 -0.092466 274 -0.045234 -0.092982 -0.036310 275 0.144701 -0.093956 -0.033098 276 0.146979 -0.093627 0.034183 277 0.083125 -0.096989 0.023101 278 -0.046585 -0.094801 0.030314 279 0.069745 -0.088914 -0.044767 280 0.062913 -0.046857 -0.087797 281 0.015636 -0.099453 -0.005554 282 0.022984 -0.068195 0.072775 283 0.110633 -0.060160 -0.079369 284 -0.122681 -0.034651 0.093485 285 -0.125551 0.029958 0.094909 286 -0.120261 0.084075 0.053821 287 -0.056904 -0.056344 0.082501 288 -0.076563 -0.002208 0.099783 289 -0.068141 0.052302 0.084887 290 -0.013519 0.006988 0.099312 291 0.058593 0.047567 -0.087418 292 0.031759 -0.003688 -0.099637 293 0.102016 0.002714 -0.099733 294 0.666813 0.082187 0.056727 295 0.635890 0.099341 0.006689 296 0.189553 -0.066853 0.073877 297 -0.011521 0.076247 0.063965 298 1.000000 0.014760 0.045660 299 1.000000 0.036356 -0.011488 300 1.000000 -0.013581 -0.046360 301 1.000000 -0.038110 0.009717 302 -1.000000 0.007459 0.034783 303 -1.000000 -0.038566 -0.022250 304 -1.000000 0.024573 -0.032601 305 0.507431 0.054027 -0.083964 306 0.477853 -0.073278 -0.067582 307 0.634339 0.087489 -0.047433 308 -0.176236 -0.099015 0.010002 309 0.053071 0.090934 0.040990 310 -0.021542 0.098720 -0.013002 311 -0.181279 -0.004437 0.099563 312 -0.178757 -0.068560 0.072476 313 -0.101245 0.098976 -0.010400 314 0.050238 0.090900 -0.041052 315 0.691600 0.099023 -0.009925 316 -0.179559 0.062819 0.077187 317 0.781079 0.009803 -0.001734 318 0.691437 -0.004841 -0.000770 319 0.600206 0.010804 -0.003315 320 -0.057532 0.000023 0.008545 321 0.270814 -0.005796 -0.016022 322 -0.627542 0.040994 0.021098 323 -0.746994 -0.033005 -0.031250 324 -0.823282 -0.040889 -0.040022 325 -0.516850 -0.013858 -0.020544 326 0.135797 0.052625 -0.036957 327 0.556946 0.049544 0.034985 328 -0.736396 -0.001046 0.038838 329 0.029080 0.004934 -0.031230 330 -0.211325 -0.048400 0.000090 331 0.123240 -0.061907 0.014900 332 -0.554737 0.033315 0.013644 333 -0.932297 0.021719 -0.019643 334 0.548220 -0.050118 0.000475 335 0.203908 0.006168 0.040597 336 0.339643 0.023410 0.025700 337 -0.398892 0.032459 0.016749 338 0.815754 -0.027222 -0.046016 339 0.193934 -0.044589 0.000811 340 0.837450 0.054696 -0.012449 341 -0.686057 0.048378 0.004382 342 -0.592624 -0.034894 0.007694 343 -0.449333 -0.030421 0.010419 344 -0.116594 0.025711 -0.033930 345 -0.226825 0.037208 0.004783 346 -0.804180 0.053429 -0.022848 347 -0.372949 -0.031259 -0.021606 348 -0.291525 0.038990 -0.017743 349 0.113004 0.015500 -0.059065 350 0.122065 -0.003371 0.003186 351 -0.171371 0.032116 -0.042313 352 0.877601 -0.038440 0.018099 353 -0.868839 0.007889 -0.038486 354 0.002784 -0.047233 -0.005112 355 0.070348 -0.059675 0.029716 356 0.155214 -0.031379 0.049519 357 -0.444308 0.029668 -0.029785 358 0.525330 0.002683 0.038115 359 -0.745848 0.060368 0.014107 360 -0.668052 -0.018075 -0.008146 361 -0.273335 -0.030729 -0.023801 362 0.867291 0.007162 -0.036061 363 0.029184 0.041795 0.030179 364 0.077575 -0.006633 -0.056160 365 0.926415 0.025993 0.011034 366 0.388808 0.014089 -0.041244 367 0.443128 -0.043327 0.005115 368 -0.274478 0.035634 0.044701 369 -0.894527 0.005091 0.044055 370 -0.210570 -0.007105 -0.047497 371 0.865172 0.029656 0.036891 372 0.496320 0.053835 0.037399 373 0.427122 0.029880 0.040565 374 -0.841065 0.028342 0.059317 375 -0.330034 0.012961 0.024645 376 0.080921 0.049963 -0.015027 377 0.926090 -0.010492 -0.041197 378 -0.787506 0.043887 0.044456 379 -0.154803 -0.013214 0.013935 380 0.485460 0.013357 0.068805 381 0.387500 -0.040652 0.011265 382 0.488985 0.000379 -0.021350 383 0.184202 0.039254 -0.011229 384 -0.467121 0.037927 0.028601 385 -0.607988 0.007153 -0.038876 386 -0.248127 -0.017978 0.041536 387 -0.807655 -0.007495 0.009169 388 0.449116 0.004963 -0.050266 389 0.158397 0.025712 0.054127 390 0.060679 -0.029677 0.002048 391 0.475910 0.017244 0.028656 $EndNodes $Elements 1788 1 2 2 1 1 1 2 21 2 2 2 1 1 3 4 22 3 2 2 1 1 10 1 23 4 2 2 1 1 1 21 23 5 2 2 1 1 8 9 24 6 2 2 1 1 9 10 25 7 2 2 1 1 24 9 25 8 2 2 1 1 10 23 25 9 2 2 1 1 11 12 26 10 2 2 1 1 13 14 27 11 2 2 1 1 20 11 28 12 2 2 1 1 11 26 28 13 2 2 1 1 16 17 29 14 2 2 1 1 18 19 30 15 2 2 1 1 19 20 31 16 2 2 1 1 30 19 31 17 2 2 1 1 20 28 31 18 2 2 1 1 2 3 32 19 2 2 1 1 4 5 33 20 2 2 1 1 5 6 34 21 2 2 1 1 6 7 35 22 2 2 1 1 7 8 36 23 2 2 1 1 12 13 37 24 2 2 1 1 2 32 21 25 2 2 1 1 14 15 38 26 2 2 1 1 15 16 39 27 2 2 1 1 17 18 40 28 2 2 1 1 33 5 34 29 2 2 1 1 8 24 36 30 2 2 1 1 14 38 27 31 2 2 1 1 22 4 33 32 2 2 1 1 32 3 22 33 2 2 1 1 34 6 35 34 2 2 1 1 35 7 36 35 2 2 1 1 37 13 27 36 2 2 1 1 39 16 29 37 2 2 1 1 40 18 30 38 2 2 1 1 12 37 26 39 2 2 1 1 38 15 39 40 2 2 1 1 17 40 29 41 2 2 1 1 38 39 41 42 2 2 1 1 29 40 42 43 2 2 1 1 27 38 43 44 2 2 1 1 38 41 43 45 2 2 1 1 34 35 44 46 2 2 1 1 32 22 45 47 2 2 1 1 40 30 46 48 2 2 1 1 36 24 47 49 2 2 1 1 35 36 48 50 2 2 1 1 37 27 49 51 2 2 1 1 39 29 50 52 2 2 1 1 21 32 51 53 2 2 1 1 42 40 46 54 2 2 1 1 27 43 49 55 2 2 1 1 41 39 50 56 2 2 1 1 44 91 34 57 2 2 1 1 26 37 52 58 2 2 1 1 51 32 45 59 2 2 1 1 23 21 53 60 2 2 1 1 25 23 54 61 2 2 1 1 23 53 54 62 2 2 1 1 28 26 55 63 2 2 1 1 50 29 42 64 2 2 1 1 31 28 56 65 2 2 1 1 28 55 56 66 2 2 1 1 48 36 47 67 2 2 1 1 44 35 48 68 2 2 1 1 47 24 57 69 2 2 1 1 24 25 57 70 2 2 1 1 37 49 52 71 2 2 1 1 53 21 51 72 2 2 1 1 55 26 52 73 2 2 1 1 43 41 58 74 2 2 1 1 52 49 59 75 2 2 1 1 33 81 22 76 2 2 1 1 55 52 60 77 2 2 1 1 52 59 60 78 2 2 1 1 53 51 61 79 2 2 1 1 30 31 62 80 2 2 1 1 46 30 62 81 2 2 1 1 62 31 56 82 2 2 1 1 48 47 63 83 2 2 1 1 49 43 64 84 2 2 1 1 44 48 65 85 2 2 1 1 48 63 65 86 2 2 1 1 51 45 66 87 2 2 1 1 43 58 64 88 2 2 1 1 59 49 64 89 2 2 1 1 25 54 57 90 2 2 1 1 50 42 67 91 2 2 1 1 56 55 68 92 2 2 1 1 55 60 68 93 2 2 1 1 41 50 69 94 2 2 1 1 50 67 69 95 2 2 1 1 41 69 58 96 2 2 1 1 51 66 61 97 2 2 1 1 47 57 70 98 2 2 1 1 63 47 70 99 2 2 1 1 42 46 71 100 2 2 1 1 62 56 72 101 2 2 1 1 64 58 73 102 2 2 1 1 67 42 71 103 2 2 1 1 69 67 74 104 2 2 1 1 59 64 75 105 2 2 1 1 64 73 75 106 2 2 1 1 72 56 68 107 2 2 1 1 62 72 46 108 2 2 1 1 63 70 76 109 2 2 1 1 71 46 77 110 2 2 1 1 77 46 72 111 2 2 1 1 57 54 78 112 2 2 1 1 71 77 79 113 2 2 1 1 70 57 78 114 2 2 1 1 53 61 80 115 2 2 1 1 45 22 81 116 2 2 1 1 60 59 82 117 2 2 1 1 54 53 80 118 2 2 1 1 72 68 83 119 2 2 1 1 68 60 84 120 2 2 1 1 83 68 84 121 2 2 1 1 60 82 84 122 2 2 1 1 74 67 85 123 2 2 1 1 67 71 85 124 2 2 1 1 61 66 86 125 2 2 1 1 58 69 87 126 2 2 1 1 82 59 75 127 2 2 1 1 45 81 88 128 2 2 1 1 45 88 66 129 2 2 1 1 54 80 78 130 2 2 1 1 71 79 85 131 2 2 1 1 80 61 89 132 2 2 1 1 66 88 90 133 2 2 1 1 61 86 89 134 2 2 1 1 87 69 74 135 2 2 1 1 86 66 90 136 2 2 1 1 58 87 73 137 2 2 1 1 81 33 91 138 2 2 1 1 33 34 91 139 2 2 1 1 91 44 92 140 2 2 1 1 92 105 91 141 2 2 1 1 92 44 65 142 2 2 1 1 65 63 93 143 2 2 1 1 93 63 76 144 2 2 1 1 65 93 94 145 2 2 1 1 70 78 95 146 2 2 1 1 73 87 96 147 2 2 1 1 76 70 95 148 2 2 1 1 75 73 97 149 2 2 1 1 73 96 97 150 2 2 1 1 65 94 92 151 2 2 1 1 72 83 77 152 2 2 1 1 82 75 98 153 2 2 1 1 75 97 98 154 2 2 1 1 87 74 99 155 2 2 1 1 87 99 96 156 2 2 1 1 80 118 78 157 2 2 1 1 76 95 100 158 2 2 1 1 89 86 101 159 2 2 1 1 74 85 102 160 2 2 1 1 291 293 292 161 2 2 1 1 84 82 103 162 2 2 1 1 82 98 103 163 2 2 1 1 83 84 104 164 2 2 1 1 84 103 104 165 2 2 1 1 102 85 79 166 2 2 1 1 282 281 277 167 2 2 1 1 74 102 99 168 2 2 1 1 88 81 105 169 2 2 1 1 81 91 105 170 2 2 1 1 93 76 106 171 2 2 1 1 105 92 107 172 2 2 1 1 107 131 105 173 2 2 1 1 107 92 94 174 2 2 1 1 107 94 108 175 2 2 1 1 88 105 131 176 2 2 1 1 106 76 100 177 2 2 1 1 99 102 109 178 2 2 1 1 94 93 110 179 2 2 1 1 98 97 111 180 2 2 1 1 110 93 106 181 2 2 1 1 96 99 112 182 2 2 1 1 108 94 110 183 2 2 1 1 83 104 113 184 2 2 1 1 99 109 112 185 2 2 1 1 108 110 114 186 2 2 1 1 106 100 115 187 2 2 1 1 97 96 116 188 2 2 1 1 104 103 117 189 2 2 1 1 80 89 118 190 2 2 1 1 116 96 112 191 2 2 1 1 103 98 119 192 2 2 1 1 117 103 119 193 2 2 1 1 98 111 119 194 2 2 1 1 95 78 118 195 2 2 1 1 118 130 95 196 2 2 1 1 118 89 120 197 2 2 1 1 120 89 101 198 2 2 1 1 97 116 111 199 2 2 1 1 110 106 121 200 2 2 1 1 295 170 294 201 2 2 1 1 110 121 114 202 2 2 1 1 294 315 295 203 2 2 1 1 263 292 280 204 2 2 1 1 79 77 113 205 2 2 1 1 77 83 113 206 2 2 1 1 121 106 115 207 2 2 1 1 79 113 123 208 2 2 1 1 79 123 102 209 2 2 1 1 123 113 124 210 2 2 1 1 102 123 109 211 2 2 1 1 124 113 104 212 2 2 1 1 111 116 125 213 2 2 1 1 123 124 126 214 2 2 1 1 104 117 124 215 2 2 1 1 101 122 127 216 2 2 1 1 263 280 279 217 2 2 1 1 120 101 127 218 2 2 1 1 121 115 128 219 2 2 1 1 120 127 129 220 2 2 1 1 129 139 130 221 2 2 1 1 118 120 130 222 2 2 1 1 107 108 131 223 2 2 1 1 112 109 132 224 2 2 1 1 116 112 133 225 2 2 1 1 119 111 134 226 2 2 1 1 111 125 134 227 2 2 1 1 90 88 131 228 2 2 1 1 112 132 133 229 2 2 1 1 116 133 125 230 2 2 1 1 290 288 287 231 2 2 1 1 291 292 267 232 2 2 1 1 114 121 135 233 2 2 1 1 315 90 131 234 2 2 1 1 135 121 128 235 2 2 1 1 264 282 266 236 2 2 1 1 108 315 131 237 2 2 1 1 117 119 136 238 2 2 1 1 109 123 137 239 2 2 1 1 100 95 130 240 2 2 1 1 137 123 126 241 2 2 1 1 109 137 132 242 2 2 1 1 120 129 130 243 2 2 1 1 136 119 134 244 2 2 1 1 137 126 138 245 2 2 1 1 138 153 137 246 2 2 1 1 100 130 139 247 2 2 1 1 136 134 140 248 2 2 1 1 117 142 124 249 2 2 1 1 125 133 141 250 2 2 1 1 100 139 115 251 2 2 1 1 117 136 142 252 2 2 1 1 142 136 143 253 2 2 1 1 134 125 144 254 2 2 1 1 140 134 144 255 2 2 1 1 125 141 144 256 2 2 1 1 143 136 140 257 2 2 1 1 129 127 145 258 2 2 1 1 114 135 146 259 2 2 1 1 133 132 147 260 2 2 1 1 129 145 148 261 2 2 1 1 122 166 127 262 2 2 2 2 8 7 300 263 2 2 1 1 129 148 139 264 2 2 1 1 133 147 141 265 2 2 1 1 128 115 149 266 2 2 1 1 115 139 149 267 2 2 2 2 6 5 300 268 2 2 1 1 139 148 149 269 2 2 1 1 311 285 316 270 2 2 2 2 300 301 8 271 2 2 1 1 141 147 151 272 2 2 1 1 142 143 152 273 2 2 1 1 147 132 153 274 2 2 1 1 144 141 154 275 2 2 1 1 141 151 154 276 2 2 1 1 132 137 153 277 2 2 1 1 153 138 155 278 2 2 1 1 155 138 152 279 2 2 1 1 153 155 156 280 2 2 1 1 153 156 147 281 2 2 2 2 10 9 301 282 2 2 1 1 146 135 305 283 2 2 1 1 143 140 157 284 2 2 1 1 170 150 294 285 2 2 1 1 126 124 142 286 2 2 1 1 126 142 152 287 2 2 1 1 126 152 138 288 2 2 1 1 128 163 135 289 2 2 1 1 140 144 158 290 2 2 1 1 159 175 145 291 2 2 1 1 128 149 160 292 2 2 1 1 158 144 154 293 2 2 1 1 160 149 148 294 2 2 1 1 140 158 157 295 2 2 1 1 143 157 161 296 2 2 1 1 156 198 147 297 2 2 1 1 161 157 162 298 2 2 1 1 145 127 166 299 2 2 1 1 128 160 163 300 2 2 1 1 155 152 164 301 2 2 2 2 298 10 301 302 2 2 1 1 159 145 166 303 2 2 1 1 166 122 150 304 2 2 2 2 9 8 301 305 2 2 1 1 164 152 161 306 2 2 1 1 161 152 143 307 2 2 1 1 166 150 168 308 2 2 1 1 166 168 169 309 2 2 1 1 166 169 159 310 2 2 1 1 168 150 170 311 2 2 2 2 298 301 299 312 2 2 1 1 168 170 171 313 2 2 1 1 168 171 172 314 2 2 1 1 168 172 169 315 2 2 1 1 283 280 293 316 2 2 1 1 266 276 296 317 2 2 1 1 171 146 165 318 2 2 1 1 171 165 172 319 2 2 1 1 169 172 173 320 2 2 1 1 169 173 174 321 2 2 1 1 148 145 175 322 2 2 1 1 148 175 176 323 2 2 1 1 160 148 176 324 2 2 1 1 293 280 292 325 2 2 1 1 169 174 177 326 2 2 1 1 176 175 178 327 2 2 1 1 314 310 309 328 2 2 1 1 266 296 269 329 2 2 1 1 178 197 176 330 2 2 1 1 269 296 233 331 2 2 1 1 157 158 179 332 2 2 1 1 175 159 180 333 2 2 1 1 180 159 177 334 2 2 1 1 175 180 181 335 2 2 1 1 175 181 178 336 2 2 1 1 181 180 182 337 2 2 1 1 182 180 177 338 2 2 1 1 182 177 174 339 2 2 1 1 157 179 162 340 2 2 1 1 177 159 169 341 2 2 1 1 179 158 183 342 2 2 1 1 158 154 183 343 2 2 1 1 154 151 184 344 2 2 1 1 173 203 174 345 2 2 1 1 161 162 164 346 2 2 1 1 250 249 296 347 2 2 1 1 178 181 186 348 2 2 1 1 174 185 182 349 2 2 1 1 182 207 181 350 2 2 1 1 154 184 183 351 2 2 1 1 156 155 187 352 2 2 1 1 184 151 188 353 2 2 1 1 187 155 164 354 2 2 1 1 187 164 189 355 2 2 1 1 165 167 190 356 2 2 1 1 165 190 172 357 2 2 1 1 172 190 173 358 2 2 1 1 281 263 279 359 2 2 1 1 162 179 192 360 2 2 1 1 164 162 189 361 2 2 1 1 287 278 282 362 2 2 1 1 271 289 297 363 2 2 1 1 156 187 194 364 2 2 1 1 189 212 187 365 2 2 1 1 195 202 191 366 2 2 1 1 188 198 194 367 2 2 1 1 190 167 196 368 2 2 1 1 279 277 281 369 2 2 1 1 178 186 197 370 2 2 1 1 151 147 198 371 2 2 1 1 190 196 173 372 2 2 2 2 300 299 301 373 2 2 1 1 199 197 200 374 2 2 3 3 12 11 302 375 2 2 1 1 193 199 201 376 2 2 1 1 193 201 191 377 2 2 1 1 200 214 199 378 2 2 1 1 191 201 195 379 2 2 1 1 186 247 197 380 2 2 1 1 167 191 202 381 2 2 1 1 167 202 196 382 2 2 1 1 287 282 290 383 2 2 1 1 185 174 203 384 2 2 1 1 203 173 196 385 2 2 1 1 183 184 204 386 2 2 1 1 179 183 205 387 2 2 1 1 183 204 205 388 2 2 1 1 179 205 192 389 2 2 1 1 205 204 206 390 2 2 1 1 188 151 198 391 2 2 1 1 186 181 207 392 2 2 1 1 207 182 185 393 2 2 1 1 186 207 208 394 2 2 1 1 207 185 209 395 2 2 1 1 207 209 208 396 2 2 1 1 206 230 205 397 2 2 1 1 184 188 210 398 2 2 1 1 184 210 204 399 2 2 1 1 210 188 211 400 2 2 1 1 212 211 194 401 2 2 1 1 156 194 198 402 2 2 1 1 196 202 213 403 2 2 1 1 195 201 214 404 2 2 1 1 162 192 215 405 2 2 1 1 202 195 216 406 2 2 1 1 188 194 211 407 2 2 1 1 201 199 214 408 2 2 1 1 196 213 203 409 2 2 1 1 194 187 212 410 2 2 1 1 203 213 217 411 2 2 1 1 195 214 216 412 2 2 1 1 217 225 203 413 2 2 1 1 202 216 213 414 2 2 1 1 217 213 218 415 2 2 1 1 218 213 216 416 2 2 1 1 211 212 219 417 2 2 1 1 219 212 220 418 2 2 1 1 211 219 221 419 2 2 1 1 212 189 220 420 2 2 1 1 208 247 186 421 2 2 1 1 211 221 210 422 2 2 1 1 210 221 222 423 2 2 1 1 204 210 222 424 2 2 1 1 204 222 206 425 2 2 1 1 221 219 223 426 2 2 1 1 269 233 242 427 2 2 1 1 220 189 224 428 2 2 1 1 259 268 293 429 2 2 1 1 209 185 225 430 2 2 1 1 307 295 315 431 2 2 1 1 185 203 225 432 2 2 1 1 220 224 226 433 2 2 1 1 220 226 227 434 2 2 1 1 220 227 219 435 2 2 1 1 227 244 219 436 2 2 1 1 289 290 297 437 2 2 1 1 215 234 224 438 2 2 1 1 310 297 309 439 2 2 1 1 214 200 228 440 2 2 1 1 215 192 229 441 2 2 1 1 229 192 230 442 2 2 1 1 192 205 230 443 2 2 1 1 230 316 229 444 2 2 1 1 286 316 285 445 2 2 1 1 232 225 217 446 2 2 1 1 209 225 233 447 2 2 1 1 233 249 209 448 2 2 1 1 214 228 216 449 2 2 1 1 215 229 234 450 2 2 1 1 218 216 235 451 2 2 1 1 225 232 233 452 2 2 1 1 235 216 228 453 2 2 1 1 234 229 231 454 2 2 1 1 218 235 236 455 2 2 1 1 226 224 234 456 2 2 1 1 232 217 237 457 2 2 1 1 217 218 237 458 2 2 1 1 237 218 238 459 2 2 1 1 238 218 236 460 2 2 1 1 234 231 226 461 2 2 1 1 232 237 239 462 2 2 1 1 282 264 290 463 2 2 1 1 237 238 239 464 2 2 1 1 235 228 240 465 2 2 1 1 240 254 235 466 2 2 3 3 13 12 302 467 2 2 1 1 227 226 241 468 2 2 1 1 226 231 241 469 2 2 1 1 232 239 242 470 2 2 3 3 14 13 303 471 2 2 1 1 232 242 233 472 2 2 1 1 227 241 243 473 2 2 1 1 223 219 244 474 2 2 1 1 240 228 245 475 2 2 1 1 227 243 244 476 2 2 1 1 245 228 200 477 2 2 1 1 240 245 246 478 2 2 1 1 286 231 316 479 2 2 1 1 230 311 316 480 2 2 1 1 208 248 247 481 2 2 1 1 245 247 248 482 2 2 1 1 248 208 249 483 2 2 1 1 245 248 246 484 2 2 1 1 208 209 249 485 2 2 1 1 248 249 250 486 2 2 1 1 233 296 249 487 2 2 1 1 248 250 246 488 2 2 1 1 245 200 247 489 2 2 1 1 243 241 251 490 2 2 1 1 231 229 316 491 2 2 1 1 162 215 224 492 2 2 1 1 223 244 252 493 2 2 1 1 294 90 315 494 2 2 1 1 252 270 223 495 2 2 1 1 224 189 162 496 2 2 3 3 15 14 303 497 2 2 1 1 200 197 247 498 2 2 1 1 244 243 253 499 2 2 3 3 13 302 303 500 2 2 1 1 236 235 254 501 2 2 1 1 253 243 255 502 2 2 1 1 243 251 255 503 2 2 1 1 244 253 252 504 2 2 1 1 254 240 246 505 2 2 3 3 16 15 303 506 2 2 1 1 253 255 256 507 2 2 1 1 242 239 257 508 2 2 1 1 295 171 170 509 2 2 1 1 257 239 258 510 2 2 1 1 258 239 238 511 2 2 3 3 11 20 302 512 2 2 1 1 258 238 259 513 2 2 3 3 17 16 304 514 2 2 1 1 253 256 261 515 2 2 1 1 257 260 262 516 2 2 1 1 261 256 263 517 2 2 1 1 257 262 242 518 2 2 1 1 262 260 264 519 2 2 1 1 264 260 265 520 2 2 1 1 262 264 266 521 2 2 3 3 18 17 304 522 2 2 1 1 256 255 267 523 2 2 1 1 259 238 268 524 2 2 1 1 255 251 267 525 2 2 3 3 16 303 304 526 2 2 3 3 19 18 304 527 2 2 1 1 242 262 269 528 2 2 3 3 20 19 302 529 2 2 1 1 262 266 269 530 2 2 1 1 238 236 268 531 2 2 3 3 19 304 302 532 2 2 1 1 291 267 314 533 2 2 1 1 258 314 309 534 2 2 1 1 236 254 268 535 2 2 3 3 303 302 304 536 2 2 2 2 2 1 298 537 2 2 1 1 252 253 261 538 2 2 1 1 254 246 273 539 2 2 1 1 254 273 268 540 2 2 1 1 252 261 274 541 2 2 1 1 270 252 274 542 2 2 1 1 261 263 274 543 2 2 1 1 250 275 246 544 2 2 1 1 273 246 275 545 2 2 1 1 275 250 276 546 2 2 1 1 275 276 277 547 2 2 1 1 276 250 296 548 2 2 1 1 270 274 278 549 2 2 1 1 275 277 279 550 2 2 1 1 273 293 268 551 2 2 1 1 206 311 230 552 2 2 1 1 270 278 272 553 2 2 1 1 280 283 279 554 2 2 1 1 278 274 281 555 2 2 1 1 281 274 263 556 2 2 1 1 276 266 277 557 2 2 2 2 3 2 298 558 2 2 2 2 4 3 299 559 2 2 1 1 278 281 282 560 2 2 1 1 273 275 283 561 2 2 1 1 283 293 273 562 2 2 1 1 275 279 283 563 2 2 1 1 278 287 272 564 2 2 2 2 5 4 299 565 2 2 2 2 3 298 299 566 2 2 2 2 299 300 5 567 2 2 1 1 284 272 287 568 2 2 1 1 285 284 288 569 2 2 1 1 284 287 288 570 2 2 1 1 285 288 289 571 2 2 1 1 285 289 286 572 2 2 1 1 286 289 271 573 2 2 2 2 1 10 298 574 2 2 1 1 289 288 290 575 2 2 1 1 277 266 282 576 2 2 1 1 265 290 264 577 2 2 2 2 7 6 300 578 2 2 1 1 263 256 292 579 2 2 1 1 256 267 292 580 2 2 1 1 291 259 293 581 2 2 1 1 101 86 122 582 2 2 1 1 150 122 294 583 2 2 1 1 135 163 193 584 2 2 1 1 167 165 305 585 2 2 1 1 191 167 305 586 2 2 1 1 165 146 305 587 2 2 1 1 176 197 306 588 2 2 1 1 160 176 306 589 2 2 1 1 197 199 306 590 2 2 1 1 199 193 306 591 2 2 1 1 193 163 306 592 2 2 1 1 86 90 294 593 2 2 1 1 108 114 307 594 2 2 1 1 114 146 307 595 2 2 1 1 294 122 86 596 2 2 1 1 108 307 315 597 2 2 1 1 307 146 171 598 2 2 1 1 259 314 258 599 2 2 1 1 160 306 163 600 2 2 1 1 222 221 308 601 2 2 1 1 221 223 308 602 2 2 1 1 223 270 308 603 2 2 1 1 257 258 309 604 2 2 1 1 267 251 310 605 2 2 1 1 260 257 309 606 2 2 1 1 290 265 297 607 2 2 1 1 310 314 267 608 2 2 1 1 271 297 310 609 2 2 1 1 284 285 311 610 2 2 1 1 193 305 135 611 2 2 1 1 308 270 272 612 2 2 1 1 311 312 284 613 2 2 1 1 312 311 206 614 2 2 1 1 307 171 295 615 2 2 1 1 222 308 312 616 2 2 1 1 206 222 312 617 2 2 1 1 297 265 309 618 2 2 1 1 310 251 313 619 2 2 1 1 241 231 313 620 2 2 1 1 251 241 313 621 2 2 1 1 271 310 313 622 2 2 1 1 260 309 265 623 2 2 1 1 312 308 272 624 2 2 1 1 259 291 314 625 2 2 1 1 272 284 312 626 2 2 1 1 191 305 193 627 2 2 1 1 271 313 286 628 2 2 1 1 313 231 286 629 4 2 100001 100001 321 381 366 200 630 4 2 100001 100001 341 103 328 104 631 4 2 100001 100001 38 14 15 303 632 4 2 100001 100001 67 346 387 353 633 4 2 100001 100001 328 97 360 323 634 4 2 100001 100001 14 27 13 303 635 4 2 100001 100001 33 22 4 299 636 4 2 100001 100001 69 324 41 50 637 4 2 100001 100001 373 336 196 203 638 4 2 100001 100001 136 322 117 119 639 4 2 100001 100001 317 86 90 66 640 4 2 100001 100001 319 145 129 334 641 4 2 100001 100001 341 103 104 322 642 4 2 100001 100001 108 114 307 318 643 4 2 100001 100001 234 345 226 224 644 4 2 100001 100001 366 381 321 336 645 4 2 100001 100001 134 325 342 125 646 4 2 100001 100001 166 358 319 327 647 4 2 100001 100001 352 371 317 362 648 4 2 100001 100001 284 320 288 285 649 4 2 100001 100001 318 127 129 319 650 4 2 100001 100001 234 345 224 215 651 4 2 100001 100001 319 295 171 307 652 4 2 100001 100001 328 59 82 75 653 4 2 100001 100001 345 370 220 361 654 4 2 100001 100001 318 93 76 317 655 4 2 100001 100001 125 144 325 141 656 4 2 100001 100001 347 151 154 184 657 4 2 100001 100001 221 361 219 370 658 4 2 100001 100001 321 366 336 216 659 4 2 100001 100001 329 256 267 320 660 4 2 100001 100001 342 119 332 134 661 4 2 100001 100001 363 297 310 309 662 4 2 100001 100001 320 287 272 284 663 4 2 100001 100001 330 361 386 222 664 4 2 100001 100001 277 275 331 279 665 4 2 100001 100001 350 279 283 364 666 4 2 100001 100001 179 337 375 162 667 4 2 100001 100001 365 377 301 299 668 4 2 100001 100001 227 351 226 241 669 4 2 100001 100001 280 364 283 279 670 4 2 100001 100001 156 155 325 153 671 4 2 100001 100001 306 197 199 367 672 4 2 100001 100001 362 365 81 91 673 4 2 100001 100001 381 178 181 367 674 4 2 100001 100001 321 366 216 228 675 4 2 100001 100001 381 178 367 197 676 4 2 100001 100001 387 369 353 72 677 4 2 100001 100001 240 245 246 321 678 4 2 100001 100001 40 333 304 30 679 4 2 100001 100001 163 382 135 334 680 4 2 100001 100001 111 134 342 125 681 4 2 100001 100001 321 366 228 200 682 4 2 100001 100001 388 373 367 366 683 4 2 100001 100001 344 252 320 253 684 4 2 100001 100001 190 373 196 173 685 4 2 100001 100001 373 203 196 173 686 4 2 100001 100001 39 353 50 41 687 4 2 100001 100001 279 263 329 354 688 4 2 100001 100001 123 385 137 109 689 4 2 100001 100001 177 182 180 373 690 4 2 100001 100001 330 361 222 221 691 4 2 100001 100001 115 128 319 149 692 4 2 100001 100001 303 333 39 38 693 4 2 100001 100001 67 346 353 71 694 4 2 100001 100001 328 97 323 75 695 4 2 100001 100001 356 276 266 296 696 4 2 100001 100001 309 258 376 257 697 4 2 100001 100001 320 289 288 285 698 4 2 100001 100001 325 342 332 134 699 4 2 100001 100001 320 270 274 252 700 4 2 100001 100001 226 370 345 351 701 4 2 100001 100001 146 165 171 319 702 4 2 100001 100001 373 380 169 372 703 4 2 100001 100001 176 306 160 334 704 4 2 100001 100001 361 370 330 345 705 4 2 100001 100001 324 64 58 43 706 4 2 100001 100001 373 372 169 173 707 4 2 100001 100001 183 337 375 179 708 4 2 100001 100001 150 122 294 319 709 4 2 100001 100001 324 323 64 387 710 4 2 100001 100001 67 346 71 85 711 4 2 100001 100001 183 337 179 158 712 4 2 100001 100001 387 75 328 323 713 4 2 100001 100001 356 276 331 266 714 4 2 100001 100001 71 346 79 85 715 4 2 100001 100001 366 216 228 214 716 4 2 100001 100001 366 228 200 214 717 4 2 100001 100001 264 354 355 390 718 4 2 100001 100001 321 247 248 208 719 4 2 100001 100001 335 232 321 233 720 4 2 100001 100001 16 15 303 39 721 4 2 100001 100001 204 347 183 184 722 4 2 100001 100001 304 16 303 39 723 4 2 100001 100001 24 36 377 47 724 4 2 100001 100001 278 354 282 320 725 4 2 100001 100001 278 320 272 270 726 4 2 100001 100001 287 320 278 282 727 4 2 100001 100001 382 165 372 190 728 4 2 100001 100001 366 213 336 216 729 4 2 100001 100001 366 373 381 336 730 4 2 100001 100001 24 377 36 301 731 4 2 100001 100001 326 238 268 383 732 4 2 100001 100001 366 191 202 195 733 4 2 100001 100001 363 297 309 265 734 4 2 100001 100001 329 256 320 354 735 4 2 100001 100001 78 80 118 317 736 4 2 100001 100001 306 199 388 367 737 4 2 100001 100001 175 176 382 367 738 4 2 100001 100001 373 372 173 190 739 4 2 100001 100001 379 330 312 308 740 4 2 100001 100001 320 264 363 329 741 4 2 100001 100001 265 363 260 309 742 4 2 100001 100001 353 346 72 46 743 4 2 100001 100001 67 69 323 74 744 4 2 100001 100001 192 229 215 368 745 4 2 100001 100001 329 354 320 264 746 4 2 100001 100001 391 373 380 180 747 4 2 100001 100001 84 328 104 359 748 4 2 100001 100001 388 382 306 367 749 4 2 100001 100001 325 132 147 153 750 4 2 100001 100001 353 71 42 67 751 4 2 100001 100001 377 352 47 362 752 4 2 100001 100001 94 318 108 317 753 4 2 100001 100001 369 333 302 43 754 4 2 100001 100001 388 305 382 167 755 4 2 100001 100001 27 302 13 303 756 4 2 100001 100001 330 223 379 370 757 4 2 100001 100001 204 347 375 183 758 4 2 100001 100001 249 209 321 208 759 4 2 100001 100001 388 373 366 167 760 4 2 100001 100001 58 69 87 323 761 4 2 100001 100001 377 352 301 24 762 4 2 100001 100001 369 27 302 37 763 4 2 100001 100001 377 352 24 47 764 4 2 100001 100001 182 181 180 367 765 4 2 100001 100001 67 346 85 387 766 4 2 100001 100001 247 381 321 200 767 4 2 100001 100001 346 77 72 46 768 4 2 100001 100001 366 213 216 202 769 4 2 100001 100001 56 369 31 28 770 4 2 100001 100001 385 322 126 332 771 4 2 100001 100001 350 329 364 376 772 4 2 100001 100001 350 257 260 376 773 4 2 100001 100001 302 20 31 28 774 4 2 100001 100001 342 322 360 385 775 4 2 100001 100001 363 290 320 297 776 4 2 100001 100001 310 267 329 314 777 4 2 100001 100001 310 329 267 320 778 4 2 100001 100001 12 302 13 37 779 4 2 100001 100001 363 297 320 310 780 4 2 100001 100001 385 322 123 126 781 4 2 100001 100001 310 329 363 314 782 4 2 100001 100001 320 313 271 286 783 4 2 100001 100001 342 322 385 332 784 4 2 100001 100001 301 352 377 365 785 4 2 100001 100001 310 363 329 320 786 4 2 100001 100001 260 363 265 264 787 4 2 100001 100001 304 29 333 40 788 4 2 100001 100001 323 97 73 75 789 4 2 100001 100001 347 357 337 343 790 4 2 100001 100001 300 377 299 301 791 4 2 100001 100001 23 54 352 53 792 4 2 100001 100001 22 33 365 299 793 4 2 100001 100001 344 252 379 320 794 4 2 100001 100001 269 356 262 266 795 4 2 100001 100001 47 48 338 63 796 4 2 100001 100001 387 374 369 72 797 4 2 100001 100001 23 352 25 301 798 4 2 100001 100001 371 362 365 81 799 4 2 100001 100001 374 378 72 387 800 4 2 100001 100001 23 352 365 53 801 4 2 100001 100001 254 273 268 321 802 4 2 100001 100001 298 3 2 32 803 4 2 100001 100001 276 339 250 296 804 4 2 100001 100001 349 364 376 350 805 4 2 100001 100001 79 387 359 328 806 4 2 100001 100001 155 357 187 156 807 4 2 100001 100001 181 207 381 182 808 4 2 100001 100001 52 387 60 374 809 4 2 100001 100001 352 362 338 47 810 4 2 100001 100001 223 221 219 370 811 4 2 100001 100001 158 337 179 157 812 4 2 100001 100001 60 387 52 59 813 4 2 100001 100001 387 75 323 64 814 4 2 100001 100001 152 126 142 332 815 4 2 100001 100001 359 104 84 83 816 4 2 100001 100001 21 371 51 53 817 4 2 100001 100001 328 60 378 84 818 4 2 100001 100001 318 131 108 317 819 4 2 100001 100001 89 318 118 317 820 4 2 100001 100001 315 295 294 318 821 4 2 100001 100001 328 60 84 82 822 4 2 100001 100001 387 59 64 49 823 4 2 100001 100001 295 315 307 318 824 4 2 100001 100001 375 205 204 183 825 4 2 100001 100001 259 376 349 291 826 4 2 100001 100001 113 77 359 79 827 4 2 100001 100001 334 145 175 159 828 4 2 100001 100001 73 323 58 87 829 4 2 100001 100001 67 323 85 74 830 4 2 100001 100001 40 353 333 46 831 4 2 100001 100001 31 333 62 30 832 4 2 100001 100001 21 371 32 51 833 4 2 100001 100001 330 370 361 221 834 4 2 100001 100001 23 365 352 301 835 4 2 100001 100001 317 95 70 76 836 4 2 100001 100001 366 388 199 367 837 4 2 100001 100001 20 302 11 28 838 4 2 100001 100001 300 34 35 6 839 4 2 100001 100001 306 163 160 334 840 4 2 100001 100001 388 373 167 382 841 4 2 100001 100001 366 213 202 196 842 4 2 100001 100001 88 45 81 340 843 4 2 100001 100001 9 24 8 301 844 4 2 100001 100001 362 317 352 338 845 4 2 100001 100001 80 371 317 352 846 4 2 100001 100001 371 340 362 81 847 4 2 100001 100001 365 45 371 81 848 4 2 100001 100001 24 36 8 301 849 4 2 100001 100001 360 323 99 102 850 4 2 100001 100001 61 371 53 51 851 4 2 100001 100001 373 366 167 196 852 4 2 100001 100001 360 323 96 99 853 4 2 100001 100001 80 371 352 53 854 4 2 100001 100001 113 77 83 359 855 4 2 100001 100001 367 181 180 175 856 4 2 100001 100001 362 47 48 338 857 4 2 100001 100001 23 365 21 53 858 4 2 100001 100001 201 191 366 195 859 4 2 100001 100001 338 352 70 317 860 4 2 100001 100001 247 381 200 197 861 4 2 100001 100001 350 329 279 364 862 4 2 100001 100001 25 23 301 10 863 4 2 100001 100001 347 151 184 188 864 4 2 100001 100001 232 225 217 321 865 4 2 100001 100001 232 321 217 237 866 4 2 100001 100001 344 252 244 379 867 4 2 100001 100001 287 320 288 284 868 4 2 100001 100001 341 103 322 360 869 4 2 100001 100001 176 334 160 148 870 4 2 100001 100001 304 31 19 30 871 4 2 100001 100001 381 200 199 366 872 4 2 100001 100001 76 318 317 95 873 4 2 100001 100001 197 176 178 367 874 4 2 100001 100001 106 110 318 121 875 4 2 100001 100001 166 358 327 169 876 4 2 100001 100001 365 23 298 301 877 4 2 100001 100001 199 200 381 197 878 4 2 100001 100001 347 151 188 198 879 4 2 100001 100001 185 381 336 207 880 4 2 100001 100001 176 334 148 175 881 4 2 100001 100001 389 257 242 262 882 4 2 100001 100001 72 378 346 387 883 4 2 100001 100001 197 176 367 306 884 4 2 100001 100001 330 223 308 379 885 4 2 100001 100001 308 221 330 222 886 4 2 100001 100001 330 223 221 308 887 4 2 100001 100001 156 325 147 153 888 4 2 100001 100001 325 343 141 144 889 4 2 100001 100001 239 389 257 242 890 4 2 100001 100001 123 385 109 360 891 4 2 100001 100001 38 41 353 39 892 4 2 100001 100001 325 385 137 332 893 4 2 100001 100001 34 300 377 299 894 4 2 100001 100001 315 90 318 294 895 4 2 100001 100001 330 223 370 221 896 4 2 100001 100001 155 357 152 164 897 4 2 100001 100001 181 207 186 381 898 4 2 100001 100001 282 355 277 266 899 4 2 100001 100001 366 214 199 201 900 4 2 100001 100001 343 140 325 384 901 4 2 100001 100001 281 355 277 282 902 4 2 100001 100001 295 319 294 318 903 4 2 100001 100001 347 357 343 156 904 4 2 100001 100001 342 322 332 119 905 4 2 100001 100001 369 37 302 26 906 4 2 100001 100001 366 213 196 336 907 4 2 100001 100001 166 319 145 127 908 4 2 100001 100001 384 158 343 140 909 4 2 100001 100001 379 272 284 320 910 4 2 100001 100001 353 369 62 72 911 4 2 100001 100001 331 276 277 266 912 4 2 100001 100001 389 242 269 262 913 4 2 100001 100001 56 374 369 55 914 4 2 100001 100001 369 37 26 52 915 4 2 100001 100001 52 369 55 26 916 4 2 100001 100001 170 295 171 319 917 4 2 100001 100001 389 269 242 335 918 4 2 100001 100001 369 333 31 302 919 4 2 100001 100001 325 385 332 342 920 4 2 100001 100001 146 319 114 135 921 4 2 100001 100001 325 357 343 384 922 4 2 100001 100001 388 199 306 193 923 4 2 100001 100001 40 353 46 42 924 4 2 100001 100001 389 269 356 262 925 4 2 100001 100001 289 320 271 286 926 4 2 100001 100001 360 103 322 119 927 4 2 100001 100001 336 218 321 216 928 4 2 100001 100001 147 343 198 151 929 4 2 100001 100001 208 336 209 321 930 4 2 100001 100001 259 376 291 314 931 4 2 100001 100001 132 385 112 109 932 4 2 100001 100001 325 357 384 152 933 4 2 100001 100001 387 323 328 79 934 4 2 100001 100001 268 349 350 273 935 4 2 100001 100001 208 336 321 381 936 4 2 100001 100001 279 350 275 331 937 4 2 100001 100001 269 356 266 296 938 4 2 100001 100001 365 45 81 22 939 4 2 100001 100001 289 320 286 285 940 4 2 100001 100001 56 62 31 369 941 4 2 100001 100001 23 298 301 10 942 4 2 100001 100001 384 337 357 343 943 4 2 100001 100001 208 381 186 207 944 4 2 100001 100001 361 375 212 348 945 4 2 100001 100001 389 356 269 335 946 4 2 100001 100001 79 360 102 323 947 4 2 100001 100001 275 350 279 283 948 4 2 100001 100001 350 257 383 389 949 4 2 100001 100001 275 276 277 331 950 4 2 100001 100001 2 298 21 1 951 4 2 100001 100001 384 158 140 157 952 4 2 100001 100001 379 285 320 284 953 4 2 100001 100001 384 158 157 337 954 4 2 100001 100001 309 260 376 363 955 4 2 100001 100001 379 272 312 284 956 4 2 100001 100001 283 273 350 349 957 4 2 100001 100001 347 151 198 343 958 4 2 100001 100001 216 366 202 195 959 4 2 100001 100001 361 210 222 221 960 4 2 100001 100001 313 344 241 231 961 4 2 100001 100001 76 318 95 100 962 4 2 100001 100001 12 302 26 11 963 4 2 100001 100001 256 320 261 253 964 4 2 100001 100001 335 237 239 383 965 4 2 100001 100001 72 378 83 346 966 4 2 100001 100001 190 373 167 196 967 4 2 100001 100001 383 339 350 335 968 4 2 100001 100001 55 28 369 56 969 4 2 100001 100001 329 263 256 354 970 4 2 100001 100001 349 291 293 259 971 4 2 100001 100001 331 339 276 356 972 4 2 100001 100001 349 364 291 376 973 4 2 100001 100001 340 91 105 92 974 4 2 100001 100001 345 368 229 230 975 4 2 100001 100001 345 220 226 224 976 4 2 100001 100001 343 347 156 198 977 4 2 100001 100001 80 89 317 61 978 4 2 100001 100001 89 86 317 61 979 4 2 100001 100001 344 251 241 243 980 4 2 100001 100001 350 383 258 326 981 4 2 100001 100001 317 86 66 61 982 4 2 100001 100001 264 320 290 282 983 4 2 100001 100001 377 300 34 35 984 4 2 100001 100001 62 353 72 46 985 4 2 100001 100001 350 383 335 389 986 4 2 100001 100001 386 375 348 368 987 4 2 100001 100001 345 348 215 368 988 4 2 100001 100001 350 383 326 268 989 4 2 100001 100001 31 333 30 304 990 4 2 100001 100001 320 267 310 251 991 4 2 100001 100001 239 389 383 257 992 4 2 100001 100001 318 127 319 122 993 4 2 100001 100001 324 323 387 67 994 4 2 100001 100001 38 353 333 39 995 4 2 100001 100001 132 137 385 109 996 4 2 100001 100001 150 170 319 294 997 4 2 100001 100001 374 378 387 60 998 4 2 100001 100001 387 323 79 85 999 4 2 100001 100001 219 244 370 227 1000 4 2 100001 100001 328 60 387 378 1001 4 2 100001 100001 332 385 137 126 1002 4 2 100001 100001 286 344 320 313 1003 4 2 100001 100001 174 203 373 173 1004 4 2 100001 100001 319 121 318 114 1005 4 2 100001 100001 32 371 45 51 1006 4 2 100001 100001 370 244 219 223 1007 4 2 100001 100001 361 348 345 386 1008 4 2 100001 100001 336 218 216 213 1009 4 2 100001 100001 300 377 301 36 1010 4 2 100001 100001 325 385 342 132 1011 4 2 100001 100001 121 110 318 114 1012 4 2 100001 100001 139 318 129 319 1013 4 2 100001 100001 360 103 119 98 1014 4 2 100001 100001 324 323 67 69 1015 4 2 100001 100001 129 127 318 120 1016 4 2 100001 100001 108 317 107 94 1017 4 2 100001 100001 104 328 84 103 1018 4 2 100001 100001 100 139 318 130 1019 4 2 100001 100001 115 121 318 319 1020 4 2 100001 100001 318 131 317 90 1021 4 2 100001 100001 115 106 318 121 1022 4 2 100001 100001 185 381 207 182 1023 4 2 100001 100001 136 322 119 332 1024 4 2 100001 100001 177 182 373 174 1025 4 2 100001 100001 369 27 37 49 1026 4 2 100001 100001 375 386 205 368 1027 4 2 100001 100001 115 100 318 106 1028 4 2 100001 100001 122 319 318 294 1029 4 2 100001 100001 139 115 318 319 1030 4 2 100001 100001 183 343 347 337 1031 4 2 100001 100001 387 52 59 49 1032 4 2 100001 100001 87 69 74 323 1033 4 2 100001 100001 345 348 361 220 1034 4 2 100001 100001 345 368 230 386 1035 4 2 100001 100001 329 279 364 280 1036 4 2 100001 100001 336 218 213 217 1037 4 2 100001 100001 261 320 274 252 1038 4 2 100001 100001 353 369 387 43 1039 4 2 100001 100001 141 343 151 154 1040 4 2 100001 100001 385 322 360 123 1041 4 2 100001 100001 348 345 386 368 1042 4 2 100001 100001 175 176 367 178 1043 4 2 100001 100001 145 358 334 319 1044 4 2 100001 100001 281 355 282 354 1045 4 2 100001 100001 155 357 164 187 1046 4 2 100001 100001 9 25 301 10 1047 4 2 100001 100001 325 147 343 156 1048 4 2 100001 100001 100 115 318 139 1049 4 2 100001 100001 298 23 21 1 1050 4 2 100001 100001 133 132 147 325 1051 4 2 100001 100001 387 43 324 353 1052 4 2 100001 100001 152 126 332 138 1053 4 2 100001 100001 292 364 291 293 1054 4 2 100001 100001 278 354 320 274 1055 4 2 100001 100001 62 333 353 46 1056 4 2 100001 100001 345 230 229 316 1057 4 2 100001 100001 320 278 274 270 1058 4 2 100001 100001 23 298 10 1 1059 4 2 100001 100001 80 352 317 78 1060 4 2 100001 100001 344 251 255 320 1061 4 2 100001 100001 335 350 389 356 1062 4 2 100001 100001 361 219 212 211 1063 4 2 100001 100001 43 387 64 49 1064 4 2 100001 100001 329 263 279 280 1065 4 2 100001 100001 157 161 337 162 1066 4 2 100001 100001 145 148 129 334 1067 4 2 100001 100001 373 380 177 169 1068 4 2 100001 100001 25 352 23 54 1069 4 2 100001 100001 286 344 313 231 1070 4 2 100001 100001 264 282 355 354 1071 4 2 100001 100001 335 232 233 242 1072 4 2 100001 100001 328 97 75 98 1073 4 2 100001 100001 318 127 122 101 1074 4 2 100001 100001 287 320 272 278 1075 4 2 100001 100001 334 145 148 175 1076 4 2 100001 100001 192 229 368 230 1077 4 2 100001 100001 372 169 173 172 1078 4 2 100001 100001 345 230 311 386 1079 4 2 100001 100001 358 169 372 327 1080 4 2 100001 100001 345 348 224 215 1081 4 2 100001 100001 166 358 169 159 1082 4 2 100001 100001 388 366 201 191 1083 4 2 100001 100001 240 245 321 228 1084 4 2 100001 100001 240 254 235 321 1085 4 2 100001 100001 201 388 191 193 1086 4 2 100001 100001 388 199 193 201 1087 4 2 100001 100001 183 337 347 375 1088 4 2 100001 100001 254 236 235 321 1089 4 2 100001 100001 384 152 161 143 1090 4 2 100001 100001 347 189 375 337 1091 4 2 100001 100001 5 34 33 299 1092 4 2 100001 100001 236 218 235 321 1093 4 2 100001 100001 43 302 27 38 1094 4 2 100001 100001 218 216 235 321 1095 4 2 100001 100001 328 82 103 98 1096 4 2 100001 100001 338 362 92 65 1097 4 2 100001 100001 347 357 187 337 1098 4 2 100001 100001 341 359 328 79 1099 4 2 100001 100001 361 221 219 211 1100 4 2 100001 100001 42 71 353 46 1101 4 2 100001 100001 349 364 293 291 1102 4 2 100001 100001 298 23 365 21 1103 4 2 100001 100001 372 173 190 172 1104 4 2 100001 100001 326 268 349 350 1105 4 2 100001 100001 273 254 246 321 1106 4 2 100001 100001 254 240 246 321 1107 4 2 100001 100001 302 20 19 31 1108 4 2 100001 100001 183 343 337 158 1109 4 2 100001 100001 258 383 238 326 1110 4 2 100001 100001 239 389 242 335 1111 4 2 100001 100001 349 268 326 259 1112 4 2 100001 100001 372 190 165 172 1113 4 2 100001 100001 367 382 175 180 1114 4 2 100001 100001 353 71 346 46 1115 4 2 100001 100001 338 92 362 317 1116 4 2 100001 100001 369 387 43 49 1117 4 2 100001 100001 382 193 135 305 1118 4 2 100001 100001 362 92 91 340 1119 4 2 100001 100001 347 188 211 194 1120 4 2 100001 100001 85 102 323 79 1121 4 2 100001 100001 313 320 310 251 1122 4 2 100001 100001 304 302 19 31 1123 4 2 100001 100001 227 220 370 219 1124 4 2 100001 100001 365 371 32 21 1125 4 2 100001 100001 338 92 94 65 1126 4 2 100001 100001 365 45 22 32 1127 4 2 100001 100001 79 387 85 346 1128 4 2 100001 100001 328 323 360 79 1129 4 2 100001 100001 328 75 82 98 1130 4 2 100001 100001 365 45 32 371 1131 4 2 100001 100001 391 380 373 372 1132 4 2 100001 100001 322 104 117 103 1133 4 2 100001 100001 249 335 321 233 1134 4 2 100001 100001 271 320 310 313 1135 4 2 100001 100001 338 94 92 317 1136 4 2 100001 100001 22 365 32 299 1137 4 2 100001 100001 353 369 43 333 1138 4 2 100001 100001 267 320 255 251 1139 4 2 100001 100001 300 34 5 299 1140 4 2 100001 100001 44 48 65 362 1141 4 2 100001 100001 278 354 274 281 1142 4 2 100001 100001 313 344 320 251 1143 4 2 100001 100001 123 385 126 137 1144 4 2 100001 100001 365 32 298 21 1145 4 2 100001 100001 279 354 281 263 1146 4 2 100001 100001 338 94 93 65 1147 4 2 100001 100001 306 176 367 382 1148 4 2 100001 100001 315 108 307 318 1149 4 2 100001 100001 391 380 358 180 1150 4 2 100001 100001 338 93 94 317 1151 4 2 100001 100001 331 355 350 266 1152 4 2 100001 100001 347 189 337 187 1153 4 2 100001 100001 40 353 29 333 1154 4 2 100001 100001 209 225 233 321 1155 4 2 100001 100001 60 84 68 378 1156 4 2 100001 100001 152 155 138 325 1157 4 2 100001 100001 249 209 233 321 1158 4 2 100001 100001 244 223 370 379 1159 4 2 100001 100001 209 336 225 321 1160 4 2 100001 100001 43 353 38 41 1161 4 2 100001 100001 329 390 354 264 1162 4 2 100001 100001 163 382 334 306 1163 4 2 100001 100001 382 135 319 305 1164 4 2 100001 100001 110 108 318 114 1165 4 2 100001 100001 369 387 49 52 1166 4 2 100001 100001 256 320 255 267 1167 4 2 100001 100001 289 320 288 290 1168 4 2 100001 100001 353 42 29 50 1169 4 2 100001 100001 331 355 266 277 1170 4 2 100001 100001 357 161 152 164 1171 4 2 100001 100001 320 289 297 290 1172 4 2 100001 100001 365 298 32 299 1173 4 2 100001 100001 64 387 43 324 1174 4 2 100001 100001 382 319 135 334 1175 4 2 100001 100001 319 145 127 129 1176 4 2 100001 100001 364 283 350 349 1177 4 2 100001 100001 50 41 353 324 1178 4 2 100001 100001 331 355 277 279 1179 4 2 100001 100001 362 340 371 317 1180 4 2 100001 100001 391 358 380 372 1181 4 2 100001 100001 292 364 280 329 1182 4 2 100001 100001 376 329 364 291 1183 4 2 100001 100001 92 317 107 105 1184 4 2 100001 100001 133 342 125 116 1185 4 2 100001 100001 350 339 356 335 1186 4 2 100001 100001 171 327 319 165 1187 4 2 100001 100001 319 382 165 358 1188 4 2 100001 100001 382 358 319 334 1189 4 2 100001 100001 382 373 180 367 1190 4 2 100001 100001 365 301 298 299 1191 4 2 100001 100001 386 222 204 206 1192 4 2 100001 100001 21 365 371 53 1193 4 2 100001 100001 352 371 362 365 1194 4 2 100001 100001 365 33 22 81 1195 4 2 100001 100001 43 333 302 38 1196 4 2 100001 100001 386 204 205 206 1197 4 2 100001 100001 386 375 204 361 1198 4 2 100001 100001 278 354 281 282 1199 4 2 100001 100001 358 382 180 175 1200 4 2 100001 100001 318 127 101 120 1201 4 2 100001 100001 388 373 382 367 1202 4 2 100001 100001 362 48 377 44 1203 4 2 100001 100001 247 381 197 186 1204 4 2 100001 100001 40 353 42 29 1205 4 2 100001 100001 226 370 351 227 1206 4 2 100001 100001 361 210 211 347 1207 4 2 100001 100001 205 386 375 204 1208 4 2 100001 100001 226 370 227 220 1209 4 2 100001 100001 386 205 230 206 1210 4 2 100001 100001 361 375 204 347 1211 4 2 100001 100001 386 230 205 368 1212 4 2 100001 100001 336 185 225 203 1213 4 2 100001 100001 361 212 219 220 1214 4 2 100001 100001 208 336 381 207 1215 4 2 100001 100001 208 336 207 209 1216 4 2 100001 100001 336 225 217 203 1217 4 2 100001 100001 368 375 348 215 1218 4 2 100001 100001 336 217 225 321 1219 4 2 100001 100001 335 232 242 239 1220 4 2 100001 100001 354 274 281 263 1221 4 2 100001 100001 379 285 284 311 1222 4 2 100001 100001 80 352 54 53 1223 4 2 100001 100001 76 318 100 106 1224 4 2 100001 100001 172 327 165 372 1225 4 2 100001 100001 373 367 381 182 1226 4 2 100001 100001 212 347 211 194 1227 4 2 100001 100001 172 327 168 171 1228 4 2 100001 100001 214 366 199 200 1229 4 2 100001 100001 258 383 350 257 1230 4 2 100001 100001 225 232 233 321 1231 4 2 100001 100001 336 217 213 203 1232 4 2 100001 100001 78 352 57 54 1233 4 2 100001 100001 366 373 336 196 1234 4 2 100001 100001 73 64 323 75 1235 4 2 100001 100001 386 375 361 348 1236 4 2 100001 100001 89 101 120 318 1237 4 2 100001 100001 34 300 5 6 1238 4 2 100001 100001 242 269 233 335 1239 4 2 100001 100001 18 40 304 30 1240 4 2 100001 100001 130 118 120 318 1241 4 2 100001 100001 172 327 171 165 1242 4 2 100001 100001 216 366 195 214 1243 4 2 100001 100001 382 373 167 190 1244 4 2 100001 100001 172 169 327 372 1245 4 2 100001 100001 185 381 373 336 1246 4 2 100001 100001 386 379 312 311 1247 4 2 100001 100001 384 337 157 161 1248 4 2 100001 100001 71 77 79 346 1249 4 2 100001 100001 320 271 310 297 1250 4 2 100001 100001 137 325 153 132 1251 4 2 100001 100001 366 214 201 195 1252 4 2 100001 100001 157 384 143 140 1253 4 2 100001 100001 275 339 246 250 1254 4 2 100001 100001 329 263 280 292 1255 4 2 100001 100001 95 78 317 70 1256 4 2 100001 100001 354 274 261 320 1257 4 2 100001 100001 78 352 317 70 1258 4 2 100001 100001 327 169 172 168 1259 4 2 100001 100001 375 162 215 192 1260 4 2 100001 100001 383 339 335 321 1261 4 2 100001 100001 302 12 26 37 1262 4 2 100001 100001 323 73 97 96 1263 4 2 100001 100001 323 102 85 74 1264 4 2 100001 100001 387 75 64 59 1265 4 2 100001 100001 243 351 241 344 1266 4 2 100001 100001 298 3 32 299 1267 4 2 100001 100001 336 218 217 321 1268 4 2 100001 100001 231 229 316 345 1269 4 2 100001 100001 3 22 32 299 1270 4 2 100001 100001 80 352 78 54 1271 4 2 100001 100001 354 261 274 263 1272 4 2 100001 100001 7 300 35 6 1273 4 2 100001 100001 388 366 191 167 1274 4 2 100001 100001 209 336 207 185 1275 4 2 100001 100001 66 317 88 90 1276 4 2 100001 100001 383 339 321 273 1277 4 2 100001 100001 346 79 359 77 1278 4 2 100001 100001 383 273 321 268 1279 4 2 100001 100001 344 255 251 243 1280 4 2 100001 100001 386 379 330 312 1281 4 2 100001 100001 345 351 379 231 1282 4 2 100001 100001 369 27 49 43 1283 4 2 100001 100001 375 348 162 189 1284 4 2 100001 100001 111 98 360 119 1285 4 2 100001 100001 388 305 167 191 1286 4 2 100001 100001 128 115 319 121 1287 4 2 100001 100001 344 255 253 320 1288 4 2 100001 100001 323 73 96 87 1289 4 2 100001 100001 346 359 79 387 1290 4 2 100001 100001 383 339 273 350 1291 4 2 100001 100001 212 361 211 347 1292 4 2 100001 100001 327 171 170 168 1293 4 2 100001 100001 80 371 53 61 1294 4 2 100001 100001 108 317 131 107 1295 4 2 100001 100001 137 325 138 153 1296 4 2 100001 100001 175 367 181 178 1297 4 2 100001 100001 350 257 262 260 1298 4 2 100001 100001 318 131 90 315 1299 4 2 100001 100001 80 89 118 317 1300 4 2 100001 100001 95 318 317 118 1301 4 2 100001 100001 95 78 118 317 1302 4 2 100001 100001 354 261 256 320 1303 4 2 100001 100001 270 379 223 252 1304 4 2 100001 100001 270 379 308 223 1305 4 2 100001 100001 324 323 69 58 1306 4 2 100001 100001 379 285 311 316 1307 4 2 100001 100001 350 356 266 262 1308 4 2 100001 100001 354 256 261 263 1309 4 2 100001 100001 327 170 171 319 1310 4 2 100001 100001 67 387 85 323 1311 4 2 100001 100001 327 170 150 168 1312 4 2 100001 100001 327 150 170 319 1313 4 2 100001 100001 325 144 125 134 1314 4 2 100001 100001 329 256 292 267 1315 4 2 100001 100001 114 319 307 318 1316 4 2 100001 100001 204 347 184 210 1317 4 2 100001 100001 327 150 166 168 1318 4 2 100001 100001 376 329 291 314 1319 4 2 100001 100001 327 166 150 319 1320 4 2 100001 100001 22 3 4 299 1321 4 2 100001 100001 157 384 161 143 1322 4 2 100001 100001 368 375 215 192 1323 4 2 100001 100001 343 154 144 158 1324 4 2 100001 100001 346 77 359 83 1325 4 2 100001 100001 377 48 35 44 1326 4 2 100001 100001 325 134 140 144 1327 4 2 100001 100001 320 287 288 290 1328 4 2 100001 100001 347 189 187 212 1329 4 2 100001 100001 350 258 257 376 1330 4 2 100001 100001 33 81 365 91 1331 4 2 100001 100001 40 29 17 304 1332 4 2 100001 100001 29 16 17 304 1333 4 2 100001 100001 303 333 38 302 1334 4 2 100001 100001 56 374 72 369 1335 4 2 100001 100001 369 302 31 28 1336 4 2 100001 100001 329 263 292 256 1337 4 2 100001 100001 26 28 369 55 1338 4 2 100001 100001 39 333 304 29 1339 4 2 100001 100001 13 302 27 37 1340 4 2 100001 100001 9 25 24 301 1341 4 2 100001 100001 381 178 197 186 1342 4 2 100001 100001 280 293 283 364 1343 4 2 100001 100001 5 33 4 299 1344 4 2 100001 100001 77 346 72 83 1345 4 2 100001 100001 229 234 215 345 1346 4 2 100001 100001 2 298 32 21 1347 4 2 100001 100001 377 35 34 44 1348 4 2 100001 100001 11 302 26 28 1349 4 2 100001 100001 16 29 39 304 1350 4 2 100001 100001 303 333 302 304 1351 4 2 100001 100001 300 36 8 7 1352 4 2 100001 100001 27 14 38 303 1353 4 2 100001 100001 344 253 255 243 1354 4 2 100001 100001 359 378 83 84 1355 4 2 100001 100001 142 322 126 124 1356 4 2 100001 100001 359 378 346 83 1357 4 2 100001 100001 192 375 162 179 1358 4 2 100001 100001 71 77 346 46 1359 4 2 100001 100001 26 369 28 302 1360 4 2 100001 100001 165 327 358 372 1361 4 2 100001 100001 362 377 91 44 1362 4 2 100001 100001 369 37 52 49 1363 4 2 100001 100001 369 27 43 302 1364 4 2 100001 100001 286 344 231 379 1365 4 2 100001 100001 36 300 35 7 1366 4 2 100001 100001 79 360 328 341 1367 4 2 100001 100001 61 371 66 317 1368 4 2 100001 100001 331 355 279 350 1369 4 2 100001 100001 78 352 70 57 1370 4 2 100001 100001 94 318 317 93 1371 4 2 100001 100001 352 70 57 47 1372 4 2 100001 100001 317 105 131 107 1373 4 2 100001 100001 129 318 139 130 1374 4 2 100001 100001 243 351 344 244 1375 4 2 100001 100001 377 34 91 44 1376 4 2 100001 100001 39 353 29 50 1377 4 2 100001 100001 41 324 69 58 1378 4 2 100001 100001 367 180 182 373 1379 4 2 100001 100001 353 369 333 62 1380 4 2 100001 100001 352 54 25 57 1381 4 2 100001 100001 189 337 164 162 1382 4 2 100001 100001 147 343 151 141 1383 4 2 100001 100001 88 317 131 90 1384 4 2 100001 100001 387 378 359 328 1385 4 2 100001 100001 200 247 245 321 1386 4 2 100001 100001 189 187 164 337 1387 4 2 100001 100001 353 50 324 67 1388 4 2 100001 100001 72 378 68 83 1389 4 2 100001 100001 352 25 24 57 1390 4 2 100001 100001 303 333 304 39 1391 4 2 100001 100001 270 379 252 320 1392 4 2 100001 100001 370 345 351 379 1393 4 2 100001 100001 386 379 311 345 1394 4 2 100001 100001 352 24 25 301 1395 4 2 100001 100001 41 324 58 43 1396 4 2 100001 100001 382 167 305 165 1397 4 2 100001 100001 102 323 99 74 1398 4 2 100001 100001 208 381 321 247 1399 4 2 100001 100001 174 169 173 373 1400 4 2 100001 100001 318 131 315 108 1401 4 2 100001 100001 70 352 338 47 1402 4 2 100001 100001 113 124 341 104 1403 4 2 100001 100001 79 341 113 123 1404 4 2 100001 100001 231 344 241 351 1405 4 2 100001 100001 80 371 61 317 1406 4 2 100001 100001 94 318 93 110 1407 4 2 100001 100001 112 99 360 96 1408 4 2 100001 100001 353 50 67 42 1409 4 2 100001 100001 264 350 355 266 1410 4 2 100001 100001 341 360 322 123 1411 4 2 100001 100001 352 24 47 57 1412 4 2 100001 100001 33 365 377 91 1413 4 2 100001 100001 44 362 65 92 1414 4 2 100001 100001 170 295 319 294 1415 4 2 100001 100001 340 81 371 45 1416 4 2 100001 100001 366 373 367 381 1417 4 2 100001 100001 325 357 152 155 1418 4 2 100001 100001 328 97 98 360 1419 4 2 100001 100001 374 378 60 68 1420 4 2 100001 100001 345 316 379 311 1421 4 2 100001 100001 388 305 191 193 1422 4 2 100001 100001 343 140 158 144 1423 4 2 100001 100001 135 114 121 319 1424 4 2 100001 100001 154 151 347 343 1425 4 2 100001 100001 129 130 120 318 1426 4 2 100001 100001 43 333 38 353 1427 4 2 100001 100001 89 86 318 317 1428 4 2 100001 100001 343 147 325 141 1429 4 2 100001 100001 318 93 110 106 1430 4 2 100001 100001 145 358 319 166 1431 4 2 100001 100001 343 140 144 325 1432 4 2 100001 100001 101 122 318 86 1433 4 2 100001 100001 94 318 110 108 1434 4 2 100001 100001 86 89 318 101 1435 4 2 100001 100001 212 375 189 348 1436 4 2 100001 100001 367 366 381 199 1437 4 2 100001 100001 363 350 329 264 1438 4 2 100001 100001 319 295 307 318 1439 4 2 100001 100001 145 358 166 159 1440 4 2 100001 100001 123 360 79 341 1441 4 2 100001 100001 184 347 183 154 1442 4 2 100001 100001 347 198 188 194 1443 4 2 100001 100001 347 357 156 187 1444 4 2 100001 100001 328 60 82 59 1445 4 2 100001 100001 123 126 322 124 1446 4 2 100001 100001 357 156 325 343 1447 4 2 100001 100001 189 224 348 162 1448 4 2 100001 100001 136 322 332 142 1449 4 2 100001 100001 325 384 332 152 1450 4 2 100001 100001 352 371 365 53 1451 4 2 100001 100001 198 156 343 147 1452 4 2 100001 100001 325 342 133 132 1453 4 2 100001 100001 379 344 231 351 1454 4 2 100001 100001 363 350 264 260 1455 4 2 100001 100001 81 91 105 340 1456 4 2 100001 100001 388 305 193 382 1457 4 2 100001 100001 362 340 317 92 1458 4 2 100001 100001 314 309 376 363 1459 4 2 100001 100001 210 347 184 188 1460 4 2 100001 100001 360 98 111 97 1461 4 2 100001 100001 200 245 228 321 1462 4 2 100001 100001 222 386 312 206 1463 4 2 100001 100001 159 177 180 380 1464 4 2 100001 100001 343 154 141 144 1465 4 2 100001 100001 183 343 158 154 1466 4 2 100001 100001 332 384 325 140 1467 4 2 100001 100001 132 133 112 342 1468 4 2 100001 100001 262 350 264 266 1469 4 2 100001 100001 360 97 111 116 1470 4 2 100001 100001 210 188 211 347 1471 4 2 100001 100001 361 212 220 348 1472 4 2 100001 100001 325 156 357 155 1473 4 2 100001 100001 361 210 347 204 1474 4 2 100001 100001 317 92 107 94 1475 4 2 100001 100001 183 343 154 347 1476 4 2 100001 100001 33 377 365 299 1477 4 2 100001 100001 139 115 319 149 1478 4 2 100001 100001 118 318 89 120 1479 4 2 100001 100001 317 86 318 90 1480 4 2 100001 100001 166 122 319 127 1481 4 2 100001 100001 133 342 325 125 1482 4 2 100001 100001 313 344 251 241 1483 4 2 100001 100001 385 132 112 342 1484 4 2 100001 100001 342 119 134 111 1485 4 2 100001 100001 79 360 123 102 1486 4 2 100001 100001 342 133 112 116 1487 4 2 100001 100001 199 388 366 201 1488 4 2 100001 100001 310 363 309 314 1489 4 2 100001 100001 15 38 303 39 1490 4 2 100001 100001 236 254 268 321 1491 4 2 100001 100001 259 376 314 258 1492 4 2 100001 100001 354 264 282 320 1493 4 2 100001 100001 33 377 34 91 1494 4 2 100001 100001 342 385 360 112 1495 4 2 100001 100001 360 102 99 109 1496 4 2 100001 100001 111 342 116 125 1497 4 2 100001 100001 384 152 357 161 1498 4 2 100001 100001 33 34 377 299 1499 4 2 100001 100001 360 342 112 116 1500 4 2 100001 100001 344 244 253 243 1501 4 2 100001 100001 266 356 350 331 1502 4 2 100001 100001 113 124 123 341 1503 4 2 100001 100001 159 169 380 358 1504 4 2 100001 100001 258 309 376 314 1505 4 2 100001 100001 320 289 271 297 1506 4 2 100001 100001 229 345 215 368 1507 4 2 100001 100001 90 86 318 294 1508 4 2 100001 100001 382 373 391 180 1509 4 2 100001 100001 243 351 244 227 1510 4 2 100001 100001 135 121 128 319 1511 4 2 100001 100001 359 378 387 346 1512 4 2 100001 100001 381 367 181 182 1513 4 2 100001 100001 334 128 319 135 1514 4 2 100001 100001 167 366 191 202 1515 4 2 100001 100001 262 260 264 350 1516 4 2 100001 100001 363 350 260 376 1517 4 2 100001 100001 281 355 354 279 1518 4 2 100001 100001 128 334 319 149 1519 4 2 100001 100001 341 359 104 328 1520 4 2 100001 100001 351 231 345 226 1521 4 2 100001 100001 360 323 97 96 1522 4 2 100001 100001 376 326 259 349 1523 4 2 100001 100001 292 364 329 291 1524 4 2 100001 100001 351 231 226 241 1525 4 2 100001 100001 165 327 319 358 1526 4 2 100001 100001 379 312 272 308 1527 4 2 100001 100001 381 178 186 181 1528 4 2 100001 100001 165 382 319 305 1529 4 2 100001 100001 329 376 363 314 1530 4 2 100001 100001 317 88 131 105 1531 4 2 100001 100001 377 352 362 365 1532 4 2 100001 100001 384 337 161 357 1533 4 2 100001 100001 163 382 306 193 1534 4 2 100001 100001 159 380 180 358 1535 4 2 100001 100001 137 325 332 138 1536 4 2 100001 100001 373 380 180 177 1537 4 2 100001 100001 357 337 161 164 1538 4 2 100001 100001 205 179 375 192 1539 4 2 100001 100001 133 325 141 125 1540 4 2 100001 100001 365 377 91 362 1541 4 2 100001 100001 95 318 118 130 1542 4 2 100001 100001 348 224 215 162 1543 4 2 100001 100001 362 377 48 47 1544 4 2 100001 100001 341 103 360 328 1545 4 2 100001 100001 142 322 124 117 1546 4 2 100001 100001 379 316 345 231 1547 4 2 100001 100001 95 318 130 100 1548 4 2 100001 100001 342 322 119 360 1549 4 2 100001 100001 27 38 302 303 1550 4 2 100001 100001 138 126 332 137 1551 4 2 100001 100001 334 128 135 163 1552 4 2 100001 100001 321 216 235 228 1553 4 2 100001 100001 360 97 116 96 1554 4 2 100001 100001 128 334 149 160 1555 4 2 100001 100001 112 99 109 360 1556 4 2 100001 100001 317 340 88 105 1557 4 2 100001 100001 387 75 59 328 1558 4 2 100001 100001 226 370 220 345 1559 4 2 100001 100001 370 345 379 330 1560 4 2 100001 100001 331 339 356 350 1561 4 2 100001 100001 389 350 257 262 1562 4 2 100001 100001 312 379 284 311 1563 4 2 100001 100001 335 237 383 321 1564 4 2 100001 100001 375 179 205 183 1565 4 2 100001 100001 383 273 268 350 1566 4 2 100001 100001 351 370 244 227 1567 4 2 100001 100001 340 317 92 105 1568 4 2 100001 100001 360 102 109 123 1569 4 2 100001 100001 239 389 335 383 1570 4 2 100001 100001 189 348 220 212 1571 4 2 100001 100001 88 340 81 105 1572 4 2 100001 100001 159 177 380 169 1573 4 2 100001 100001 358 380 372 169 1574 4 2 100001 100001 249 321 248 208 1575 4 2 100001 100001 309 376 260 257 1576 4 2 100001 100001 391 358 382 180 1577 4 2 100001 100001 345 231 234 226 1578 4 2 100001 100001 165 358 382 372 1579 4 2 100001 100001 240 321 235 228 1580 4 2 100001 100001 345 231 229 234 1581 4 2 100001 100001 347 189 212 375 1582 4 2 100001 100001 351 244 370 379 1583 4 2 100001 100001 209 336 185 225 1584 4 2 100001 100001 73 323 64 58 1585 4 2 100001 100001 335 237 321 232 1586 4 2 100001 100001 391 382 358 372 1587 4 2 100001 100001 324 323 58 64 1588 4 2 100001 100001 167 165 382 190 1589 4 2 100001 100001 142 322 332 126 1590 4 2 100001 100001 167 196 366 202 1591 4 2 100001 100001 362 91 92 44 1592 4 2 100001 100001 243 351 227 241 1593 4 2 100001 100001 256 320 253 255 1594 4 2 100001 100001 351 344 244 379 1595 4 2 100001 100001 379 244 223 252 1596 4 2 100001 100001 373 372 190 391 1597 4 2 100001 100001 334 149 160 148 1598 4 2 100001 100001 276 339 275 250 1599 4 2 100001 100001 382 334 176 175 1600 4 2 100001 100001 275 273 246 339 1601 4 2 100001 100001 292 364 293 280 1602 4 2 100001 100001 376 326 350 258 1603 4 2 100001 100001 238 258 326 259 1604 4 2 100001 100001 273 321 246 339 1605 4 2 100001 100001 360 385 109 112 1606 4 2 100001 100001 179 157 337 162 1607 4 2 100001 100001 249 335 233 296 1608 4 2 100001 100001 36 300 8 301 1609 4 2 100001 100001 282 264 355 266 1610 4 2 100001 100001 371 51 66 45 1611 4 2 100001 100001 61 371 51 66 1612 4 2 100001 100001 339 296 249 250 1613 4 2 100001 100001 66 340 371 45 1614 4 2 100001 100001 66 340 45 88 1615 4 2 100001 100001 264 350 390 355 1616 4 2 100001 100001 264 350 329 390 1617 4 2 100001 100001 363 290 297 265 1618 4 2 100001 100001 66 340 88 317 1619 4 2 100001 100001 66 340 317 371 1620 4 2 100001 100001 363 290 265 264 1621 4 2 100001 100001 347 187 194 212 1622 4 2 100001 100001 333 40 46 30 1623 4 2 100001 100001 18 304 19 30 1624 4 2 100001 100001 320 287 290 282 1625 4 2 100001 100001 281 355 279 277 1626 4 2 100001 100001 388 382 193 306 1627 4 2 100001 100001 383 268 236 238 1628 4 2 100001 100001 338 48 65 63 1629 4 2 100001 100001 185 373 182 174 1630 4 2 100001 100001 368 205 192 230 1631 4 2 100001 100001 249 335 296 339 1632 4 2 100001 100001 383 236 268 321 1633 4 2 100001 100001 337 161 164 162 1634 4 2 100001 100001 383 236 218 238 1635 4 2 100001 100001 150 122 319 166 1636 4 2 100001 100001 383 218 236 321 1637 4 2 100001 100001 339 249 248 250 1638 4 2 100001 100001 267 291 292 329 1639 4 2 100001 100001 374 378 68 72 1640 4 2 100001 100001 185 373 174 203 1641 4 2 100001 100001 389 350 262 356 1642 4 2 100001 100001 356 339 296 335 1643 4 2 100001 100001 383 218 237 238 1644 4 2 100001 100001 330 361 345 386 1645 4 2 100001 100001 350 339 273 275 1646 4 2 100001 100001 383 237 218 321 1647 4 2 100001 100001 374 55 52 369 1648 4 2 100001 100001 383 237 239 238 1649 4 2 100001 100001 335 237 232 239 1650 4 2 100001 100001 55 60 374 52 1651 4 2 100001 100001 156 347 187 194 1652 4 2 100001 100001 344 252 253 244 1653 4 2 100001 100001 311 230 345 316 1654 4 2 100001 100001 345 220 224 348 1655 4 2 100001 100001 339 248 249 321 1656 4 2 100001 100001 387 67 353 324 1657 4 2 100001 100001 362 338 48 65 1658 4 2 100001 100001 185 381 182 373 1659 4 2 100001 100001 318 93 106 76 1660 4 2 100001 100001 146 319 171 307 1661 4 2 100001 100001 208 381 247 186 1662 4 2 100001 100001 339 248 246 250 1663 4 2 100001 100001 331 339 350 275 1664 4 2 100001 100001 360 103 98 328 1665 4 2 100001 100001 218 321 237 217 1666 4 2 100001 100001 196 203 336 213 1667 4 2 100001 100001 272 379 270 320 1668 4 2 100001 100001 253 320 261 252 1669 4 2 100001 100001 339 246 248 321 1670 4 2 100001 100001 272 379 308 270 1671 4 2 100001 100001 362 340 91 81 1672 4 2 100001 100001 18 40 17 304 1673 4 2 100001 100001 319 146 114 307 1674 4 2 100001 100001 341 359 113 104 1675 4 2 100001 100001 321 245 246 248 1676 4 2 100001 100001 245 247 248 321 1677 4 2 100001 100001 185 373 203 336 1678 4 2 100001 100001 322 119 103 117 1679 4 2 100001 100001 220 348 189 224 1680 4 2 100001 100001 136 322 142 117 1681 4 2 100001 100001 350 273 283 275 1682 4 2 100001 100001 74 323 99 87 1683 4 2 100001 100001 99 323 96 87 1684 4 2 100001 100001 377 47 36 48 1685 4 2 100001 100001 69 67 324 50 1686 4 2 100001 100001 56 374 55 68 1687 4 2 100001 100001 84 328 82 103 1688 4 2 100001 100001 377 36 35 48 1689 4 2 100001 100001 300 377 36 35 1690 4 2 100001 100001 113 104 359 83 1691 4 2 100001 100001 286 285 379 316 1692 4 2 100001 100001 286 379 285 320 1693 4 2 100001 100001 369 374 387 52 1694 4 2 100001 100001 325 155 138 153 1695 4 2 100001 100001 163 382 193 135 1696 4 2 100001 100001 55 60 68 374 1697 4 2 100001 100001 335 269 233 296 1698 4 2 100001 100001 258 383 257 239 1699 4 2 100001 100001 258 383 239 238 1700 4 2 100001 100001 238 326 268 259 1701 4 2 100001 100001 369 62 72 56 1702 4 2 100001 100001 166 168 169 327 1703 4 2 100001 100001 56 374 68 72 1704 4 2 100001 100001 112 360 116 96 1705 4 2 100001 100001 360 342 116 111 1706 4 2 100001 100001 361 221 211 210 1707 4 2 100001 100001 363 350 376 329 1708 4 2 100001 100001 349 293 268 259 1709 4 2 100001 100001 361 220 219 370 1710 4 2 100001 100001 359 378 84 328 1711 4 2 100001 100001 331 339 275 276 1712 4 2 100001 100001 386 311 312 206 1713 4 2 100001 100001 86 122 318 294 1714 4 2 100001 100001 325 385 132 137 1715 4 2 100001 100001 363 290 264 320 1716 4 2 100001 100001 338 65 93 63 1717 4 2 100001 100001 338 93 76 63 1718 4 2 100001 100001 156 347 194 198 1719 4 2 100001 100001 267 291 329 314 1720 4 2 100001 100001 361 204 386 222 1721 4 2 100001 100001 361 204 222 210 1722 4 2 100001 100001 378 84 68 83 1723 4 2 100001 100001 199 367 197 381 1724 4 2 100001 100001 330 386 312 222 1725 4 2 100001 100001 308 330 312 222 1726 4 2 100001 100001 338 76 93 317 1727 4 2 100001 100001 338 76 70 63 1728 4 2 100001 100001 286 379 231 316 1729 4 2 100001 100001 382 373 190 391 1730 4 2 100001 100001 356 339 276 296 1731 4 2 100001 100001 293 349 273 283 1732 4 2 100001 100001 364 283 349 293 1733 4 2 100001 100001 387 353 346 72 1734 4 2 100001 100001 134 332 136 119 1735 4 2 100001 100001 286 344 379 320 1736 4 2 100001 100001 269 296 335 356 1737 4 2 100001 100001 342 119 111 360 1738 4 2 100001 100001 386 379 345 330 1739 4 2 100001 100001 268 293 349 273 1740 4 2 100001 100001 334 149 139 319 1741 4 2 100001 100001 332 134 136 140 1742 4 2 100001 100001 386 311 206 230 1743 4 2 100001 100001 376 326 349 350 1744 4 2 100001 100001 146 305 319 135 1745 4 2 100001 100001 31 333 304 302 1746 4 2 100001 100001 59 387 328 60 1747 4 2 100001 100001 369 333 62 31 1748 4 2 100001 100001 338 70 76 317 1749 4 2 100001 100001 39 333 29 353 1750 4 2 100001 100001 62 333 46 30 1751 4 2 100001 100001 376 326 258 259 1752 4 2 100001 100001 375 337 189 162 1753 4 2 100001 100001 133 325 147 141 1754 4 2 100001 100001 319 305 146 165 1755 4 2 100001 100001 382 334 306 176 1756 4 2 100001 100001 332 142 143 136 1757 4 2 100001 100001 332 142 152 143 1758 4 2 100001 100001 332 325 134 140 1759 4 2 100001 100001 332 325 152 138 1760 4 2 100001 100001 334 128 163 160 1761 4 2 100001 100001 338 70 47 63 1762 4 2 100001 100001 361 375 347 212 1763 4 2 100001 100001 334 358 159 175 1764 4 2 100001 100001 174 177 169 373 1765 4 2 100001 100001 159 358 180 175 1766 4 2 100001 100001 353 324 41 43 1767 4 2 100001 100001 334 129 139 148 1768 4 2 100001 100001 249 335 339 321 1769 4 2 100001 100001 341 359 79 113 1770 4 2 100001 100001 384 158 337 343 1771 4 2 100001 100001 143 152 332 384 1772 4 2 100001 100001 104 124 341 322 1773 4 2 100001 100001 124 104 117 322 1774 4 2 100001 100001 140 332 143 136 1775 4 2 100001 100001 341 124 123 322 1776 4 2 100001 100001 357 337 164 187 1777 4 2 100001 100001 215 162 375 348 1778 4 2 100001 100001 368 375 192 205 1779 4 2 100001 100001 382 334 175 358 1780 4 2 100001 100001 334 139 129 319 1781 4 2 100001 100001 145 358 159 334 1782 4 2 100001 100001 334 139 149 148 1783 4 2 100001 100001 140 143 332 384 1784 4 2 100001 100001 390 354 355 279 1785 4 2 100001 100001 329 279 354 390 1786 4 2 100001 100001 390 350 279 355 1787 4 2 100001 100001 390 350 329 279 1788 4 2 100001 100001 190 382 391 372 $EndElements trunk-2018.02b/examples/deformableelem/model.stl000066400000000000000000000775731324306050200216160ustar00rootroot00000000000000solid vcg facet normal -9.935677e-07 9.804380e-02 9.951821e-01 outer loop vertex -1.000000e+00 -0.000000e+00 9.999900e-02 vertex 1.000000e+00 -0.000000e+00 1.000010e-01 vertex 1.000000e+00 1.950900e-02 9.807900e-02 endloop endfacet facet normal -5.004937e-07 9.799373e-02 9.951870e-01 outer loop vertex -1.000000e+00 1.950900e-02 9.807800e-02 vertex -1.000000e+00 -0.000000e+00 9.999900e-02 vertex 1.000000e+00 1.950900e-02 9.807900e-02 endloop endfacet facet normal -4.812623e-07 2.902623e-01 9.569471e-01 outer loop vertex -1.000000e+00 1.950900e-02 9.807800e-02 vertex 1.000000e+00 1.950900e-02 9.807900e-02 vertex 1.000000e+00 3.826800e-02 9.238900e-02 endloop endfacet facet normal -9.589455e-07 2.903089e-01 9.569330e-01 outer loop vertex 1.000000e+00 3.826800e-02 9.238900e-02 vertex -1.000000e+00 3.826800e-02 9.238700e-02 vertex -1.000000e+00 1.950900e-02 9.807800e-02 endloop endfacet facet normal -8.837795e-07 4.713902e-01 8.819247e-01 outer loop vertex -1.000000e+00 3.826800e-02 9.238700e-02 vertex 1.000000e+00 3.826800e-02 9.238900e-02 vertex 1.000000e+00 5.555700e-02 8.314800e-02 endloop endfacet facet normal -8.837795e-07 4.713902e-01 8.819247e-01 outer loop vertex 1.000000e+00 5.555700e-02 8.314800e-02 vertex -1.000000e+00 5.555700e-02 8.314600e-02 vertex -1.000000e+00 3.826800e-02 9.238700e-02 endloop endfacet facet normal -7.746253e-07 6.344065e-01 7.729996e-01 outer loop vertex -1.000000e+00 5.555700e-02 8.314600e-02 vertex 1.000000e+00 5.555700e-02 8.314800e-02 vertex 1.000000e+00 7.071100e-02 7.071100e-02 endloop endfacet facet normal -3.858854e-07 6.343759e-01 7.730248e-01 outer loop vertex 1.000000e+00 7.071100e-02 7.071100e-02 vertex -1.000000e+00 7.071100e-02 7.071000e-02 vertex -1.000000e+00 5.555700e-02 8.314600e-02 endloop endfacet facet normal -3.166859e-07 7.730043e-01 6.344008e-01 outer loop vertex -1.000000e+00 7.071100e-02 7.071000e-02 vertex 1.000000e+00 7.071100e-02 7.071100e-02 vertex 1.000000e+00 8.314700e-02 5.555800e-02 endloop endfacet facet normal -6.345283e-07 7.730249e-01 6.343758e-01 outer loop vertex 1.000000e+00 8.314700e-02 5.555800e-02 vertex -1.000000e+00 8.314700e-02 5.555600e-02 vertex -1.000000e+00 7.071100e-02 7.071000e-02 endloop endfacet facet normal -4.715036e-07 8.819247e-01 4.713902e-01 outer loop vertex -1.000000e+00 8.314700e-02 5.555600e-02 vertex 1.000000e+00 8.314700e-02 5.555800e-02 vertex 1.000000e+00 9.238800e-02 3.826900e-02 endloop endfacet facet normal -2.353234e-07 8.819134e-01 4.714115e-01 outer loop vertex 1.000000e+00 9.238800e-02 3.826900e-02 vertex -1.000000e+00 9.238800e-02 3.826800e-02 vertex -1.000000e+00 8.314700e-02 5.555600e-02 endloop endfacet facet normal -1.449192e-07 9.569328e-01 2.903093e-01 outer loop vertex -1.000000e+00 9.238800e-02 3.826800e-02 vertex 1.000000e+00 9.238800e-02 3.826900e-02 vertex 1.000000e+00 9.807900e-02 1.951000e-02 endloop endfacet facet normal -2.900945e-07 9.569372e-01 2.902951e-01 outer loop vertex 1.000000e+00 9.807900e-02 1.951000e-02 vertex -1.000000e+00 9.807900e-02 1.950800e-02 vertex -1.000000e+00 9.238800e-02 3.826800e-02 endloop endfacet facet normal -9.792566e-08 9.951871e-01 9.799337e-02 outer loop vertex -1.000000e+00 9.807900e-02 1.950800e-02 vertex 1.000000e+00 9.807900e-02 1.951000e-02 vertex 1.000000e+00 1.000000e-01 1.000000e-06 endloop endfacet facet normal -9.799335e-08 9.951870e-01 9.799335e-02 outer loop vertex 1.000000e+00 1.000000e-01 1.000000e-06 vertex -1.000000e+00 1.000000e-01 -1.000000e-06 vertex -1.000000e+00 9.807900e-02 1.950800e-02 endloop endfacet facet normal 9.799335e-08 9.951870e-01 -9.799335e-02 outer loop vertex -1.000000e+00 1.000000e-01 -1.000000e-06 vertex 1.000000e+00 1.000000e-01 1.000000e-06 vertex 1.000000e+00 9.807900e-02 -1.950800e-02 endloop endfacet facet normal 9.792566e-08 9.951871e-01 -9.799337e-02 outer loop vertex 1.000000e+00 9.807900e-02 -1.950800e-02 vertex -1.000000e+00 9.807900e-02 -1.951000e-02 vertex -1.000000e+00 1.000000e-01 -1.000000e-06 endloop endfacet facet normal 2.900945e-07 9.569372e-01 -2.902951e-01 outer loop vertex -1.000000e+00 9.807900e-02 -1.951000e-02 vertex 1.000000e+00 9.807900e-02 -1.950800e-02 vertex 1.000000e+00 9.238800e-02 -3.826800e-02 endloop endfacet facet normal 1.449192e-07 9.569328e-01 -2.903093e-01 outer loop vertex 1.000000e+00 9.238800e-02 -3.826800e-02 vertex -1.000000e+00 9.238800e-02 -3.826900e-02 vertex -1.000000e+00 9.807900e-02 -1.951000e-02 endloop endfacet facet normal 2.353234e-07 8.819134e-01 -4.714115e-01 outer loop vertex -1.000000e+00 9.238800e-02 -3.826900e-02 vertex 1.000000e+00 9.238800e-02 -3.826800e-02 vertex 1.000000e+00 8.314700e-02 -5.555600e-02 endloop endfacet facet normal 4.715036e-07 8.819247e-01 -4.713902e-01 outer loop vertex 1.000000e+00 8.314700e-02 -5.555600e-02 vertex -1.000000e+00 8.314700e-02 -5.555800e-02 vertex -1.000000e+00 9.238800e-02 -3.826900e-02 endloop endfacet facet normal 6.345283e-07 7.730249e-01 -6.343758e-01 outer loop vertex -1.000000e+00 8.314700e-02 -5.555800e-02 vertex 1.000000e+00 8.314700e-02 -5.555600e-02 vertex 1.000000e+00 7.071100e-02 -7.071000e-02 endloop endfacet facet normal 3.166859e-07 7.730043e-01 -6.344008e-01 outer loop vertex 1.000000e+00 7.071100e-02 -7.071000e-02 vertex -1.000000e+00 7.071100e-02 -7.071100e-02 vertex -1.000000e+00 8.314700e-02 -5.555800e-02 endloop endfacet facet normal 3.858854e-07 6.343759e-01 -7.730248e-01 outer loop vertex -1.000000e+00 7.071100e-02 -7.071100e-02 vertex 1.000000e+00 7.071100e-02 -7.071000e-02 vertex 1.000000e+00 5.555700e-02 -8.314600e-02 endloop endfacet facet normal 7.746253e-07 6.344065e-01 -7.729996e-01 outer loop vertex 1.000000e+00 5.555700e-02 -8.314600e-02 vertex -1.000000e+00 5.555700e-02 -8.314800e-02 vertex -1.000000e+00 7.071100e-02 -7.071100e-02 endloop endfacet facet normal 8.837795e-07 4.713902e-01 -8.819247e-01 outer loop vertex -1.000000e+00 5.555700e-02 -8.314800e-02 vertex 1.000000e+00 5.555700e-02 -8.314600e-02 vertex 1.000000e+00 3.826800e-02 -9.238700e-02 endloop endfacet facet normal 8.837795e-07 4.713902e-01 -8.819247e-01 outer loop vertex 1.000000e+00 3.826800e-02 -9.238700e-02 vertex -1.000000e+00 3.826800e-02 -9.238900e-02 vertex -1.000000e+00 5.555700e-02 -8.314800e-02 endloop endfacet facet normal 9.589455e-07 2.903089e-01 -9.569330e-01 outer loop vertex -1.000000e+00 3.826800e-02 -9.238900e-02 vertex 1.000000e+00 3.826800e-02 -9.238700e-02 vertex 1.000000e+00 1.950900e-02 -9.807800e-02 endloop endfacet facet normal 4.812623e-07 2.902623e-01 -9.569471e-01 outer loop vertex 1.000000e+00 1.950900e-02 -9.807800e-02 vertex -1.000000e+00 1.950900e-02 -9.807900e-02 vertex -1.000000e+00 3.826800e-02 -9.238900e-02 endloop endfacet facet normal 5.004937e-07 9.799373e-02 -9.951870e-01 outer loop vertex -1.000000e+00 1.950900e-02 -9.807900e-02 vertex 1.000000e+00 1.950900e-02 -9.807800e-02 vertex 1.000000e+00 0.000000e+00 -9.999900e-02 endloop endfacet facet normal 9.935677e-07 9.804380e-02 -9.951821e-01 outer loop vertex 1.000000e+00 0.000000e+00 -9.999900e-02 vertex -1.000000e+00 0.000000e+00 -1.000010e-01 vertex -1.000000e+00 1.950900e-02 -9.807900e-02 endloop endfacet facet normal 9.935726e-07 -9.799373e-02 -9.951870e-01 outer loop vertex -1.000000e+00 0.000000e+00 -1.000010e-01 vertex 1.000000e+00 0.000000e+00 -9.999900e-02 vertex 1.000000e+00 -1.950900e-02 -9.807800e-02 endloop endfacet facet normal 5.004912e-07 -9.804380e-02 -9.951821e-01 outer loop vertex 1.000000e+00 -1.950900e-02 -9.807800e-02 vertex -1.000000e+00 -1.950900e-02 -9.807900e-02 vertex -1.000000e+00 0.000000e+00 -1.000010e-01 endloop endfacet facet normal 4.812552e-07 -2.903089e-01 -9.569330e-01 outer loop vertex -1.000000e+00 -1.950900e-02 -9.807900e-02 vertex 1.000000e+00 -1.950900e-02 -9.807800e-02 vertex 1.000000e+00 -3.826800e-02 -9.238700e-02 endloop endfacet facet normal 9.589596e-07 -2.902623e-01 -9.569471e-01 outer loop vertex 1.000000e+00 -3.826800e-02 -9.238700e-02 vertex -1.000000e+00 -3.826800e-02 -9.238900e-02 vertex -1.000000e+00 -1.950900e-02 -9.807900e-02 endloop endfacet facet normal 8.837795e-07 -4.713902e-01 -8.819247e-01 outer loop vertex -1.000000e+00 -3.826800e-02 -9.238900e-02 vertex 1.000000e+00 -3.826800e-02 -9.238700e-02 vertex 1.000000e+00 -5.555700e-02 -8.314600e-02 endloop endfacet facet normal 8.837795e-07 -4.713902e-01 -8.819247e-01 outer loop vertex 1.000000e+00 -5.555700e-02 -8.314600e-02 vertex -1.000000e+00 -5.555700e-02 -8.314800e-02 vertex -1.000000e+00 -3.826800e-02 -9.238900e-02 endloop endfacet facet normal 7.746505e-07 -6.343759e-01 -7.730248e-01 outer loop vertex -1.000000e+00 -5.555700e-02 -8.314800e-02 vertex 1.000000e+00 -5.555700e-02 -8.314600e-02 vertex 1.000000e+00 -7.071100e-02 -7.071000e-02 endloop endfacet facet normal 3.858728e-07 -6.344065e-01 -7.729996e-01 outer loop vertex 1.000000e+00 -7.071100e-02 -7.071000e-02 vertex -1.000000e+00 -7.071100e-02 -7.071100e-02 vertex -1.000000e+00 -5.555700e-02 -8.314800e-02 endloop endfacet facet normal 3.166734e-07 -7.730249e-01 -6.343758e-01 outer loop vertex -1.000000e+00 -7.071100e-02 -7.071100e-02 vertex 1.000000e+00 -7.071100e-02 -7.071000e-02 vertex 1.000000e+00 -8.314700e-02 -5.555600e-02 endloop endfacet facet normal 6.345534e-07 -7.730043e-01 -6.344008e-01 outer loop vertex 1.000000e+00 -8.314700e-02 -5.555600e-02 vertex -1.000000e+00 -8.314700e-02 -5.555800e-02 vertex -1.000000e+00 -7.071100e-02 -7.071100e-02 endloop endfacet facet normal 4.715249e-07 -8.819134e-01 -4.714115e-01 outer loop vertex -1.000000e+00 -8.314700e-02 -5.555800e-02 vertex 1.000000e+00 -8.314700e-02 -5.555600e-02 vertex 1.000000e+00 -9.238800e-02 -3.826800e-02 endloop endfacet facet normal 2.353128e-07 -8.819247e-01 -4.713902e-01 outer loop vertex 1.000000e+00 -9.238800e-02 -3.826800e-02 vertex -1.000000e+00 -9.238800e-02 -3.826900e-02 vertex -1.000000e+00 -8.314700e-02 -5.555800e-02 endloop endfacet facet normal 1.449121e-07 -9.569372e-01 -2.902951e-01 outer loop vertex -1.000000e+00 -9.238800e-02 -3.826900e-02 vertex 1.000000e+00 -9.238800e-02 -3.826800e-02 vertex 1.000000e+00 -9.807900e-02 -1.950800e-02 endloop endfacet facet normal 2.901087e-07 -9.569328e-01 -2.903093e-01 outer loop vertex 1.000000e+00 -9.807900e-02 -1.950800e-02 vertex -1.000000e+00 -9.807900e-02 -1.951000e-02 vertex -1.000000e+00 -9.238800e-02 -3.826900e-02 endloop endfacet facet normal 9.792565e-08 -9.951870e-01 -9.799335e-02 outer loop vertex -1.000000e+00 -9.807900e-02 -1.951000e-02 vertex 1.000000e+00 -9.807900e-02 -1.950800e-02 vertex 1.000000e+00 -1.000000e-01 1.000000e-06 endloop endfacet facet normal 9.799336e-08 -9.951871e-01 -9.799337e-02 outer loop vertex 1.000000e+00 -1.000000e-01 1.000000e-06 vertex -1.000000e+00 -1.000000e-01 -1.000000e-06 vertex -1.000000e+00 -9.807900e-02 -1.951000e-02 endloop endfacet facet normal -9.799336e-08 -9.951871e-01 9.799337e-02 outer loop vertex -1.000000e+00 -1.000000e-01 -1.000000e-06 vertex 1.000000e+00 -1.000000e-01 1.000000e-06 vertex 1.000000e+00 -9.807900e-02 1.951000e-02 endloop endfacet facet normal -9.792565e-08 -9.951870e-01 9.799335e-02 outer loop vertex 1.000000e+00 -9.807900e-02 1.951000e-02 vertex -1.000000e+00 -9.807900e-02 1.950800e-02 vertex -1.000000e+00 -1.000000e-01 -1.000000e-06 endloop endfacet facet normal -2.901087e-07 -9.569328e-01 2.903093e-01 outer loop vertex -1.000000e+00 -9.807900e-02 1.950800e-02 vertex 1.000000e+00 -9.807900e-02 1.951000e-02 vertex 1.000000e+00 -9.238800e-02 3.826900e-02 endloop endfacet facet normal -1.449121e-07 -9.569372e-01 2.902951e-01 outer loop vertex 1.000000e+00 -9.238800e-02 3.826900e-02 vertex -1.000000e+00 -9.238800e-02 3.826800e-02 vertex -1.000000e+00 -9.807900e-02 1.950800e-02 endloop endfacet facet normal -2.353128e-07 -8.819247e-01 4.713902e-01 outer loop vertex -1.000000e+00 -9.238800e-02 3.826800e-02 vertex 1.000000e+00 -9.238800e-02 3.826900e-02 vertex 1.000000e+00 -8.314700e-02 5.555800e-02 endloop endfacet facet normal -4.715249e-07 -8.819134e-01 4.714115e-01 outer loop vertex 1.000000e+00 -8.314700e-02 5.555800e-02 vertex -1.000000e+00 -8.314700e-02 5.555600e-02 vertex -1.000000e+00 -9.238800e-02 3.826800e-02 endloop endfacet facet normal -6.345534e-07 -7.730043e-01 6.344008e-01 outer loop vertex -1.000000e+00 -8.314700e-02 5.555600e-02 vertex 1.000000e+00 -8.314700e-02 5.555800e-02 vertex 1.000000e+00 -7.071100e-02 7.071100e-02 endloop endfacet facet normal -3.166734e-07 -7.730249e-01 6.343758e-01 outer loop vertex 1.000000e+00 -7.071100e-02 7.071100e-02 vertex -1.000000e+00 -7.071100e-02 7.071000e-02 vertex -1.000000e+00 -8.314700e-02 5.555600e-02 endloop endfacet facet normal -3.858728e-07 -6.344065e-01 7.729996e-01 outer loop vertex -1.000000e+00 -7.071100e-02 7.071000e-02 vertex 1.000000e+00 -7.071100e-02 7.071100e-02 vertex 1.000000e+00 -5.555700e-02 8.314800e-02 endloop endfacet facet normal -7.746505e-07 -6.343759e-01 7.730248e-01 outer loop vertex 1.000000e+00 -5.555700e-02 8.314800e-02 vertex -1.000000e+00 -5.555700e-02 8.314600e-02 vertex -1.000000e+00 -7.071100e-02 7.071000e-02 endloop endfacet facet normal -8.837795e-07 -4.713902e-01 8.819247e-01 outer loop vertex -1.000000e+00 -5.555700e-02 8.314600e-02 vertex 1.000000e+00 -5.555700e-02 8.314800e-02 vertex 1.000000e+00 -3.826800e-02 9.238900e-02 endloop endfacet facet normal -8.837795e-07 -4.713902e-01 8.819247e-01 outer loop vertex 1.000000e+00 -3.826800e-02 9.238900e-02 vertex -1.000000e+00 -3.826800e-02 9.238700e-02 vertex -1.000000e+00 -5.555700e-02 8.314600e-02 endloop endfacet facet normal 1.000000e+00 0.000000e+00 0.000000e+00 outer loop vertex 1.000000e+00 1.950900e-02 9.807900e-02 vertex 1.000000e+00 -0.000000e+00 1.000010e-01 vertex 1.000000e+00 -1.950900e-02 9.807900e-02 endloop endfacet facet normal 1.000000e+00 0.000000e+00 0.000000e+00 outer loop vertex 1.000000e+00 1.950900e-02 9.807900e-02 vertex 1.000000e+00 -1.950900e-02 9.807900e-02 vertex 1.000000e+00 -3.826800e-02 9.238900e-02 endloop endfacet facet normal 1.000000e+00 0.000000e+00 0.000000e+00 outer loop vertex 1.000000e+00 1.950900e-02 9.807900e-02 vertex 1.000000e+00 -3.826800e-02 9.238900e-02 vertex 1.000000e+00 3.826800e-02 9.238900e-02 endloop endfacet facet normal 1.000000e+00 -0.000000e+00 0.000000e+00 outer loop vertex 1.000000e+00 -3.826800e-02 9.238900e-02 vertex 1.000000e+00 -5.555700e-02 8.314800e-02 vertex 1.000000e+00 3.826800e-02 9.238900e-02 endloop endfacet facet normal 1.000000e+00 0.000000e+00 0.000000e+00 outer loop vertex 1.000000e+00 -5.555700e-02 8.314800e-02 vertex 1.000000e+00 5.555700e-02 8.314800e-02 vertex 1.000000e+00 3.826800e-02 9.238900e-02 endloop endfacet facet normal 1.000000e+00 -0.000000e+00 0.000000e+00 outer loop vertex 1.000000e+00 -5.555700e-02 8.314800e-02 vertex 1.000000e+00 -7.071100e-02 7.071100e-02 vertex 1.000000e+00 5.555700e-02 8.314800e-02 endloop endfacet facet normal 1.000000e+00 0.000000e+00 0.000000e+00 outer loop vertex 1.000000e+00 -7.071100e-02 7.071100e-02 vertex 1.000000e+00 7.071100e-02 7.071100e-02 vertex 1.000000e+00 5.555700e-02 8.314800e-02 endloop endfacet facet normal 1.000000e+00 -0.000000e+00 0.000000e+00 outer loop vertex 1.000000e+00 -7.071100e-02 7.071100e-02 vertex 1.000000e+00 -8.314700e-02 5.555800e-02 vertex 1.000000e+00 7.071100e-02 7.071100e-02 endloop endfacet facet normal 1.000000e+00 0.000000e+00 0.000000e+00 outer loop vertex 1.000000e+00 -8.314700e-02 5.555800e-02 vertex 1.000000e+00 8.314700e-02 5.555800e-02 vertex 1.000000e+00 7.071100e-02 7.071100e-02 endloop endfacet facet normal 1.000000e+00 -0.000000e+00 0.000000e+00 outer loop vertex 1.000000e+00 -8.314700e-02 5.555800e-02 vertex 1.000000e+00 -9.238800e-02 3.826900e-02 vertex 1.000000e+00 8.314700e-02 5.555800e-02 endloop endfacet facet normal 1.000000e+00 0.000000e+00 0.000000e+00 outer loop vertex 1.000000e+00 -9.238800e-02 3.826900e-02 vertex 1.000000e+00 9.238800e-02 3.826900e-02 vertex 1.000000e+00 8.314700e-02 5.555800e-02 endloop endfacet facet normal 1.000000e+00 -0.000000e+00 0.000000e+00 outer loop vertex 1.000000e+00 -9.238800e-02 3.826900e-02 vertex 1.000000e+00 -9.807900e-02 1.951000e-02 vertex 1.000000e+00 9.238800e-02 3.826900e-02 endloop endfacet facet normal 1.000000e+00 0.000000e+00 0.000000e+00 outer loop vertex 1.000000e+00 -9.807900e-02 1.951000e-02 vertex 1.000000e+00 9.807900e-02 1.951000e-02 vertex 1.000000e+00 9.238800e-02 3.826900e-02 endloop endfacet facet normal 1.000000e+00 -0.000000e+00 0.000000e+00 outer loop vertex 1.000000e+00 -9.807900e-02 1.951000e-02 vertex 1.000000e+00 -1.000000e-01 1.000000e-06 vertex 1.000000e+00 9.807900e-02 1.951000e-02 endloop endfacet facet normal 1.000000e+00 0.000000e+00 0.000000e+00 outer loop vertex 1.000000e+00 -1.000000e-01 1.000000e-06 vertex 1.000000e+00 1.000000e-01 1.000000e-06 vertex 1.000000e+00 9.807900e-02 1.951000e-02 endloop endfacet facet normal 1.000000e+00 -0.000000e+00 0.000000e+00 outer loop vertex 1.000000e+00 -1.000000e-01 1.000000e-06 vertex 1.000000e+00 -9.807900e-02 -1.950800e-02 vertex 1.000000e+00 1.000000e-01 1.000000e-06 endloop endfacet facet normal 1.000000e+00 0.000000e+00 0.000000e+00 outer loop vertex 1.000000e+00 -9.807900e-02 -1.950800e-02 vertex 1.000000e+00 9.807900e-02 -1.950800e-02 vertex 1.000000e+00 1.000000e-01 1.000000e-06 endloop endfacet facet normal 1.000000e+00 -0.000000e+00 0.000000e+00 outer loop vertex 1.000000e+00 -9.807900e-02 -1.950800e-02 vertex 1.000000e+00 -9.238800e-02 -3.826800e-02 vertex 1.000000e+00 9.807900e-02 -1.950800e-02 endloop endfacet facet normal 1.000000e+00 0.000000e+00 0.000000e+00 outer loop vertex 1.000000e+00 -9.238800e-02 -3.826800e-02 vertex 1.000000e+00 9.238800e-02 -3.826800e-02 vertex 1.000000e+00 9.807900e-02 -1.950800e-02 endloop endfacet facet normal 1.000000e+00 -0.000000e+00 0.000000e+00 outer loop vertex 1.000000e+00 -9.238800e-02 -3.826800e-02 vertex 1.000000e+00 -8.314700e-02 -5.555600e-02 vertex 1.000000e+00 9.238800e-02 -3.826800e-02 endloop endfacet facet normal 1.000000e+00 0.000000e+00 0.000000e+00 outer loop vertex 1.000000e+00 -8.314700e-02 -5.555600e-02 vertex 1.000000e+00 8.314700e-02 -5.555600e-02 vertex 1.000000e+00 9.238800e-02 -3.826800e-02 endloop endfacet facet normal 1.000000e+00 -0.000000e+00 0.000000e+00 outer loop vertex 1.000000e+00 -8.314700e-02 -5.555600e-02 vertex 1.000000e+00 -7.071100e-02 -7.071000e-02 vertex 1.000000e+00 8.314700e-02 -5.555600e-02 endloop endfacet facet normal 1.000000e+00 0.000000e+00 0.000000e+00 outer loop vertex 1.000000e+00 -7.071100e-02 -7.071000e-02 vertex 1.000000e+00 7.071100e-02 -7.071000e-02 vertex 1.000000e+00 8.314700e-02 -5.555600e-02 endloop endfacet facet normal 1.000000e+00 -0.000000e+00 0.000000e+00 outer loop vertex 1.000000e+00 -7.071100e-02 -7.071000e-02 vertex 1.000000e+00 -5.555700e-02 -8.314600e-02 vertex 1.000000e+00 7.071100e-02 -7.071000e-02 endloop endfacet facet normal 1.000000e+00 0.000000e+00 0.000000e+00 outer loop vertex 1.000000e+00 -5.555700e-02 -8.314600e-02 vertex 1.000000e+00 5.555700e-02 -8.314600e-02 vertex 1.000000e+00 7.071100e-02 -7.071000e-02 endloop endfacet facet normal 1.000000e+00 -0.000000e+00 0.000000e+00 outer loop vertex 1.000000e+00 -5.555700e-02 -8.314600e-02 vertex 1.000000e+00 -3.826800e-02 -9.238700e-02 vertex 1.000000e+00 5.555700e-02 -8.314600e-02 endloop endfacet facet normal 1.000000e+00 0.000000e+00 0.000000e+00 outer loop vertex 1.000000e+00 -3.826800e-02 -9.238700e-02 vertex 1.000000e+00 3.826800e-02 -9.238700e-02 vertex 1.000000e+00 5.555700e-02 -8.314600e-02 endloop endfacet facet normal 1.000000e+00 -0.000000e+00 0.000000e+00 outer loop vertex 1.000000e+00 -3.826800e-02 -9.238700e-02 vertex 1.000000e+00 -1.950900e-02 -9.807800e-02 vertex 1.000000e+00 3.826800e-02 -9.238700e-02 endloop endfacet facet normal 1.000000e+00 0.000000e+00 0.000000e+00 outer loop vertex 1.000000e+00 -1.950900e-02 -9.807800e-02 vertex 1.000000e+00 1.950900e-02 -9.807800e-02 vertex 1.000000e+00 3.826800e-02 -9.238700e-02 endloop endfacet facet normal 1.000000e+00 -0.000000e+00 0.000000e+00 outer loop vertex 1.000000e+00 -1.950900e-02 -9.807800e-02 vertex 1.000000e+00 0.000000e+00 -9.999900e-02 vertex 1.000000e+00 1.950900e-02 -9.807800e-02 endloop endfacet facet normal -9.935726e-07 -9.799373e-02 9.951870e-01 outer loop vertex 1.000000e+00 -0.000000e+00 1.000010e-01 vertex -1.000000e+00 -0.000000e+00 9.999900e-02 vertex -1.000000e+00 -1.950900e-02 9.807800e-02 endloop endfacet facet normal -5.004912e-07 -9.804380e-02 9.951821e-01 outer loop vertex -1.000000e+00 -1.950900e-02 9.807800e-02 vertex 1.000000e+00 -1.950900e-02 9.807900e-02 vertex 1.000000e+00 -0.000000e+00 1.000010e-01 endloop endfacet facet normal -9.589596e-07 -2.902623e-01 9.569471e-01 outer loop vertex -1.000000e+00 -3.826800e-02 9.238700e-02 vertex 1.000000e+00 -3.826800e-02 9.238900e-02 vertex 1.000000e+00 -1.950900e-02 9.807900e-02 endloop endfacet facet normal -4.812552e-07 -2.903089e-01 9.569330e-01 outer loop vertex 1.000000e+00 -1.950900e-02 9.807900e-02 vertex -1.000000e+00 -1.950900e-02 9.807800e-02 vertex -1.000000e+00 -3.826800e-02 9.238700e-02 endloop endfacet facet normal -1.000000e+00 0.000000e+00 -0.000000e+00 outer loop vertex -1.000000e+00 -0.000000e+00 9.999900e-02 vertex -1.000000e+00 1.950900e-02 9.807800e-02 vertex -1.000000e+00 -1.950900e-02 9.807800e-02 endloop endfacet facet normal -1.000000e+00 -0.000000e+00 0.000000e+00 outer loop vertex -1.000000e+00 1.950900e-02 9.807800e-02 vertex -1.000000e+00 -3.826800e-02 9.238700e-02 vertex -1.000000e+00 -1.950900e-02 9.807800e-02 endloop endfacet facet normal -1.000000e+00 0.000000e+00 -0.000000e+00 outer loop vertex -1.000000e+00 1.950900e-02 9.807800e-02 vertex -1.000000e+00 3.826800e-02 9.238700e-02 vertex -1.000000e+00 -3.826800e-02 9.238700e-02 endloop endfacet facet normal -1.000000e+00 -0.000000e+00 0.000000e+00 outer loop vertex -1.000000e+00 3.826800e-02 9.238700e-02 vertex -1.000000e+00 -5.555700e-02 8.314600e-02 vertex -1.000000e+00 -3.826800e-02 9.238700e-02 endloop endfacet facet normal -1.000000e+00 0.000000e+00 -0.000000e+00 outer loop vertex -1.000000e+00 3.826800e-02 9.238700e-02 vertex -1.000000e+00 5.555700e-02 8.314600e-02 vertex -1.000000e+00 -5.555700e-02 8.314600e-02 endloop endfacet facet normal -1.000000e+00 -0.000000e+00 0.000000e+00 outer loop vertex -1.000000e+00 5.555700e-02 8.314600e-02 vertex -1.000000e+00 -7.071100e-02 7.071000e-02 vertex -1.000000e+00 -5.555700e-02 8.314600e-02 endloop endfacet facet normal -1.000000e+00 0.000000e+00 -0.000000e+00 outer loop vertex -1.000000e+00 5.555700e-02 8.314600e-02 vertex -1.000000e+00 7.071100e-02 7.071000e-02 vertex -1.000000e+00 -7.071100e-02 7.071000e-02 endloop endfacet facet normal -1.000000e+00 -0.000000e+00 0.000000e+00 outer loop vertex -1.000000e+00 7.071100e-02 7.071000e-02 vertex -1.000000e+00 -8.314700e-02 5.555600e-02 vertex -1.000000e+00 -7.071100e-02 7.071000e-02 endloop endfacet facet normal -1.000000e+00 0.000000e+00 -0.000000e+00 outer loop vertex -1.000000e+00 7.071100e-02 7.071000e-02 vertex -1.000000e+00 8.314700e-02 5.555600e-02 vertex -1.000000e+00 -8.314700e-02 5.555600e-02 endloop endfacet facet normal -1.000000e+00 -0.000000e+00 0.000000e+00 outer loop vertex -1.000000e+00 8.314700e-02 5.555600e-02 vertex -1.000000e+00 -9.238800e-02 3.826800e-02 vertex -1.000000e+00 -8.314700e-02 5.555600e-02 endloop endfacet facet normal -1.000000e+00 0.000000e+00 -0.000000e+00 outer loop vertex -1.000000e+00 8.314700e-02 5.555600e-02 vertex -1.000000e+00 9.238800e-02 3.826800e-02 vertex -1.000000e+00 -9.238800e-02 3.826800e-02 endloop endfacet facet normal -1.000000e+00 -0.000000e+00 0.000000e+00 outer loop vertex -1.000000e+00 9.238800e-02 3.826800e-02 vertex -1.000000e+00 -9.807900e-02 1.950800e-02 vertex -1.000000e+00 -9.238800e-02 3.826800e-02 endloop endfacet facet normal -1.000000e+00 0.000000e+00 -0.000000e+00 outer loop vertex -1.000000e+00 9.238800e-02 3.826800e-02 vertex -1.000000e+00 9.807900e-02 1.950800e-02 vertex -1.000000e+00 -9.807900e-02 1.950800e-02 endloop endfacet facet normal -1.000000e+00 -0.000000e+00 0.000000e+00 outer loop vertex -1.000000e+00 9.807900e-02 1.950800e-02 vertex -1.000000e+00 -1.000000e-01 -1.000000e-06 vertex -1.000000e+00 -9.807900e-02 1.950800e-02 endloop endfacet facet normal -1.000000e+00 0.000000e+00 -0.000000e+00 outer loop vertex -1.000000e+00 9.807900e-02 1.950800e-02 vertex -1.000000e+00 1.000000e-01 -1.000000e-06 vertex -1.000000e+00 -1.000000e-01 -1.000000e-06 endloop endfacet facet normal -1.000000e+00 -0.000000e+00 0.000000e+00 outer loop vertex -1.000000e+00 1.000000e-01 -1.000000e-06 vertex -1.000000e+00 -9.807900e-02 -1.951000e-02 vertex -1.000000e+00 -1.000000e-01 -1.000000e-06 endloop endfacet facet normal -1.000000e+00 0.000000e+00 0.000000e+00 outer loop vertex -1.000000e+00 1.000000e-01 -1.000000e-06 vertex -1.000000e+00 9.807900e-02 -1.951000e-02 vertex -1.000000e+00 -9.807900e-02 -1.951000e-02 endloop endfacet facet normal -1.000000e+00 -0.000000e+00 0.000000e+00 outer loop vertex -1.000000e+00 9.807900e-02 -1.951000e-02 vertex -1.000000e+00 -9.238800e-02 -3.826900e-02 vertex -1.000000e+00 -9.807900e-02 -1.951000e-02 endloop endfacet facet normal -1.000000e+00 0.000000e+00 0.000000e+00 outer loop vertex -1.000000e+00 9.807900e-02 -1.951000e-02 vertex -1.000000e+00 9.238800e-02 -3.826900e-02 vertex -1.000000e+00 -9.238800e-02 -3.826900e-02 endloop endfacet facet normal -1.000000e+00 -0.000000e+00 0.000000e+00 outer loop vertex -1.000000e+00 9.238800e-02 -3.826900e-02 vertex -1.000000e+00 -8.314700e-02 -5.555800e-02 vertex -1.000000e+00 -9.238800e-02 -3.826900e-02 endloop endfacet facet normal -1.000000e+00 0.000000e+00 0.000000e+00 outer loop vertex -1.000000e+00 9.238800e-02 -3.826900e-02 vertex -1.000000e+00 8.314700e-02 -5.555800e-02 vertex -1.000000e+00 -8.314700e-02 -5.555800e-02 endloop endfacet facet normal -1.000000e+00 -0.000000e+00 0.000000e+00 outer loop vertex -1.000000e+00 8.314700e-02 -5.555800e-02 vertex -1.000000e+00 -7.071100e-02 -7.071100e-02 vertex -1.000000e+00 -8.314700e-02 -5.555800e-02 endloop endfacet facet normal -1.000000e+00 0.000000e+00 0.000000e+00 outer loop vertex -1.000000e+00 8.314700e-02 -5.555800e-02 vertex -1.000000e+00 7.071100e-02 -7.071100e-02 vertex -1.000000e+00 -7.071100e-02 -7.071100e-02 endloop endfacet facet normal -1.000000e+00 -0.000000e+00 0.000000e+00 outer loop vertex -1.000000e+00 7.071100e-02 -7.071100e-02 vertex -1.000000e+00 -5.555700e-02 -8.314800e-02 vertex -1.000000e+00 -7.071100e-02 -7.071100e-02 endloop endfacet facet normal -1.000000e+00 0.000000e+00 0.000000e+00 outer loop vertex -1.000000e+00 7.071100e-02 -7.071100e-02 vertex -1.000000e+00 5.555700e-02 -8.314800e-02 vertex -1.000000e+00 -5.555700e-02 -8.314800e-02 endloop endfacet facet normal -1.000000e+00 -0.000000e+00 0.000000e+00 outer loop vertex -1.000000e+00 5.555700e-02 -8.314800e-02 vertex -1.000000e+00 -3.826800e-02 -9.238900e-02 vertex -1.000000e+00 -5.555700e-02 -8.314800e-02 endloop endfacet facet normal -1.000000e+00 0.000000e+00 0.000000e+00 outer loop vertex -1.000000e+00 5.555700e-02 -8.314800e-02 vertex -1.000000e+00 3.826800e-02 -9.238900e-02 vertex -1.000000e+00 -3.826800e-02 -9.238900e-02 endloop endfacet facet normal -1.000000e+00 -0.000000e+00 0.000000e+00 outer loop vertex -1.000000e+00 3.826800e-02 -9.238900e-02 vertex -1.000000e+00 -1.950900e-02 -9.807900e-02 vertex -1.000000e+00 -3.826800e-02 -9.238900e-02 endloop endfacet facet normal -1.000000e+00 0.000000e+00 0.000000e+00 outer loop vertex -1.000000e+00 3.826800e-02 -9.238900e-02 vertex -1.000000e+00 1.950900e-02 -9.807900e-02 vertex -1.000000e+00 -1.950900e-02 -9.807900e-02 endloop endfacet facet normal -1.000000e+00 -0.000000e+00 0.000000e+00 outer loop vertex -1.000000e+00 1.950900e-02 -9.807900e-02 vertex -1.000000e+00 0.000000e+00 -1.000010e-01 vertex -1.000000e+00 -1.950900e-02 -9.807900e-02 endloop endfacet endsolid vcg trunk-2018.02b/examples/deformableelem/testDeformableBodies.py000066400000000000000000000102051324306050200244050ustar00rootroot00000000000000#!/usr/bin/python # -*- coding: utf-8 -*- # Created by Burak ER from yade.deformableelementsutils import *; O=Omega() ## Create deformable elements internal materials and interaction element material mat=LinIsoRayleighDampElastMat(label='aluminiummaterial'); intermat=LinCohesiveStiffPropDampElastMat(label='intermat') intermat.youngmodulus=70e11; mat.density=2700; mat.youngmodulus=70e9 O.materials.append(intermat) O.materials.append(mat) ## Generate deformable element mesh from GMSH2 format mesh=tetrahedral_mesh_generator('model.msh',Lin4NodeTetra,'aluminiummaterial',Lin4NodeTetra_Lin4NodeTetra_InteractionElement,'intermat') ## Define the body boundary force # x position limits of bodies that are subject to the force force_tail_body_lowerlimit=-1.01; force_tail_body_upperlimit=-0.9999; # Angular frequency of the force amplitude=1000; period=(1e-4) omega=2*pi/period; applicationperiod=period/2; ## Define the fixed boundary # x position limits of bodies that are subject to the fixed boundary fixed_tail_body_lowerlimit=0.99; fixed_tail_body_upperlimit=1.01; def getinitialpos(): positions=[]; for body in O.bodies: positions.append(body.state.pos) return positions def getboundarybodies(): bdy=[]; for body in O.bodies: if(body.shape.dispIndex==12): if(body.state.pos[0]fixed_tail_body_lowerlimit): bdy.append(body.id) return bdy def getforcebodies(): bdy=[]; for body in O.bodies: if(body.shape.dispIndex==12): if(body.state.pos[0]force_tail_body_lowerlimit): if(O.time it is pressure applied on the one end of the rod # x position limits of bodies that are subject to the force force_tail_body_lowerlimit=-1.01; force_tail_body_upperlimit=-0.9999; # Pressure Properties #pressure=Vector3(1.5e6,0,0); pressure=Vector3(1.5e8,0,0); period=(3e-4) omega=2*pi/period; applicationperiod=period/2; # Displacement Properties(Displacement Application) boundary_displacement_vector=Vector3(0.3,0,0); ## Define the fixed boundary # x position limits of bodies that are subject to the fixed boundary fixed_tail_body_lowerlimit=0.99; fixed_tail_body_upperlimit=1.01; def getinitialpos(): positions=[]; for body in O.bodies: positions.append(body.state.pos) return positions def getboundarybodies(mesh): bdy = []; listofbodies = dict(); listofboundarybodies = dict(); for body in mesh: if (len(body) > 1): listofbodies[body[0].id] = []; for bdyy in body[1]: if (bdyy.shape.dispIndex == 12): if (bdyy.state.pos[0] < fixed_tail_body_upperlimit): if (bdyy.state.pos[0] > fixed_tail_body_lowerlimit): listofbodies[body[0].id].append(bdyy); #add node to boundary bodies of body if (listofbodies[body[0].id].index( bdyy) == 2): #if this body has three nodes of it on the boundary then add it to the traction force list. listofboundarybodies[body[0].id] = listofbodies[body[0].id]; return listofboundarybodies; def getforcebodies(mesh): bdy = []; listofbodies = dict(); listofboundarybodies = dict(); for body in mesh: if(len(body) > 1): listofbodies[body[0].id] = []; for bdyy in body[1]: if (bdyy.shape.dispIndex == 12): if (bdyy.state.pos[0] < force_tail_body_upperlimit): if (bdyy.state.pos[0] > force_tail_body_lowerlimit): listofbodies[body[0].id].append(bdyy); #add node to boundary bodies of body if (listofbodies[body[0].id].index( bdyy) == 2): #if this body has three nodes of it on the boundary then add it to the traction force list. listofboundarybodies[body[0].id] = listofbodies[body[0].id]; return listofboundarybodies; initialpositions=getinitialpos(); forcebodies=getforcebodies(modelmesh); boundarybodies=getboundarybodies(modelmesh); def displaceelements(): for deformablebody in forcebodies: for node in forcebodies[deformablebody]: if(O.time50000: O.bodies[IdSphere].dynamic=False O.bodies[IdSphere].state.vel[2]=0.1 qt.View() O.saveTmp() trunk-2018.02b/examples/grids/GridConnection_Spring.py000066400000000000000000000045431324306050200227230ustar00rootroot00000000000000#--- bruno.chareyre@hmg.inpg.fr --- #!/usr/bin/python # -*- coding: utf-8 -*- # Experiment beam-like behaviour with chained cylinders + CohFrict connexions from yade.gridpfacet import * young=1.0e10 poisson=4 density=2.60e3 frictionAngle=radians(30) O.materials.append(CohFrictMat(young=young,poisson=poisson,density=density,frictionAngle=frictionAngle,normalCohesion=1e13,shearCohesion=1e13,momentRotationLaw=True,label='mat')) O.engines=[ ForceResetter(), InsertionSortCollider([ Bo1_Sphere_Aabb(), Bo1_GridConnection_Aabb(), ]), InteractionLoop( # Geometric interactions [ Ig2_GridNode_GridNode_GridNodeGeom6D(), Ig2_Sphere_GridConnection_ScGridCoGeom(), # used for the cohesive sphere-cylinder interaction ], [ # Interaction phusics Ip2_CohFrictMat_CohFrictMat_CohFrictPhys(setCohesionNow=True,setCohesionOnNewContacts=True), ], # Interaction law [ Law2_ScGeom6D_CohFrictPhys_CohesionMoment(), Law2_ScGridCoGeom_CohFrictPhys_CundallStrack(), # used for the cohesive sphere-cylinder interaction ] ), GlobalStiffnessTimeStepper(active=1,timeStepUpdateInterval=100,timestepSafetyCoefficient=0.8,label='ts'), NewtonIntegrator(gravity=(0,-9.81,0),damping=0.15,label='newton'), PyRunner(iterPeriod=500,command='history()'), ] ### Generate a spiral ### Create all nodes first nodesIds=[] rCyl=0.005 Ne=200 for i in range(0, Ne+1): omega=60.0/float(Ne); hy=0.10; hz=0.15; px=float(i)*(omega/60.0); py=sin(float(i)*omega)*hy; pz=cos(float(i)*omega)*hz; nodesIds.append( O.bodies.append( gridNode([px,py,pz],rCyl,wire=False,fixed=False,color=Vector3(0.6,0.5,0.5),material='mat') ) ) ### Now create connection between the nodes for i,j in zip( nodesIds[:-1], nodesIds[1:]): O.bodies.append( gridConnection(i,j,rCyl,color=Vector3(0.6,0.5,0.5),material='mat') ) def outp(id=1): for i in O.interactions: if i.id1 == 1: print i.phys.shearForce print i.phys.normalForce return i O.bodies[Ne-1].state.blockedDOFs='xyzXYZ' yade.qt.View(); #plot some results from yade import plot plot.plots={'t':('pos1',None,'vel1')} def history(): plot.addData(pos1=O.bodies[0].state.pos[1], # potential elastic energy vel1=O.bodies[0].state.vel[1], t=O.time) #yade.qt.Renderer().bound=True plot.plot(subPlots=False) O.saveTmp() #O.bodies[0].state.angVel=Vector3(0.05,0,0) trunk-2018.02b/examples/grids/Simple_GridConnection_Falling.py000066400000000000000000000022461324306050200243440ustar00rootroot00000000000000# encoding: utf-8 from yade import pack,geom,qt from yade.gridpfacet import * from pylab import * O.engines=[ ForceResetter(), InsertionSortCollider([Bo1_GridConnection_Aabb()]), InteractionLoop( [Ig2_GridNode_GridNode_GridNodeGeom6D()], [Ip2_CohFrictMat_CohFrictMat_CohFrictPhys(setCohesionNow=True,setCohesionOnNewContacts=False)], [Law2_ScGeom6D_CohFrictPhys_CohesionMoment()] ), NewtonIntegrator(gravity=(0,0,-10),damping=0.1,label='newton') ] O.materials.append(CohFrictMat(young=3e2,poisson=0.3,density=1e1,frictionAngle=10,normalCohesion=1e7,shearCohesion=1e7,momentRotationLaw=True,label='mat')) ### Parameters ### L=0.1 #length [m] n=10 #number of nodes for the length [#] r=L/100. #radius color=[255./255.,102./255.,0./255.] ### Create all nodes first nodeIds=[] for i in range(0,n): nodeIds.append( O.bodies.append( gridNode([i*L/n,0,0],r,wire=False,fixed=False,material='mat',color=color) ) ) ### Create connections between the nodes connectionIds=[] for i,j in zip(nodeIds[:-1],nodeIds[1:]): connectionIds.append( O.bodies.append( gridConnection(i,j,r,color=color) ) ) ### Set a fixed node O.bodies[0].dynamic=False O.dt=1e-06 O.saveTmp() qt.View() trunk-2018.02b/examples/grids/Simple_Grid_Falling.py000066400000000000000000000025551324306050200223270ustar00rootroot00000000000000# encoding: utf-8 from yade import pack,geom,qt from yade.gridpfacet import * from pylab import * O.engines=[ ForceResetter(), InsertionSortCollider([ Bo1_GridConnection_Aabb(), ]), InteractionLoop( [Ig2_GridNode_GridNode_GridNodeGeom6D()], [Ip2_CohFrictMat_CohFrictMat_CohFrictPhys(setCohesionNow=True,setCohesionOnNewContacts=False)], [Law2_ScGeom6D_CohFrictPhys_CohesionMoment()] ), NewtonIntegrator(gravity=(0,0,-10),damping=0.1,label='newton') ] O.materials.append(CohFrictMat(young=3e2,poisson=0.3,density=1e1,frictionAngle=10,normalCohesion=1e7,shearCohesion=1e7,momentRotationLaw=True,label='spheremat')) ### Parameters of a rectangular grid ### L=0.1 #length [m] l=0.05 #width [m] nbL=10 #number of nodes for the length [#] nbl=5 #number of nodes for the width [#] r=L/100. #radius color=[255./255.,102./255.,0./255.] nodesIds=[] #Create all nodes first : for i in range(0,nbL): for j in range(0,nbl): nodesIds.append( O.bodies.append(gridNode([i*L/nbL,j*l/nbl,0],r,wire=False,fixed=False,material='spheremat',color=color)) ) #Create connection between the nodes for i in range(0,len(nodesIds)): for j in range(i+1,len(nodesIds)): dist=(O.bodies[i].state.pos - O.bodies[j].state.pos).norm() if(dist<=L/nbL*1.01): O.bodies.append( gridConnection(i,j,r,color=color) ) #Set a fixed node O.bodies[0].dynamic=False O.dt=1e-05 O.saveTmp() qt.View() trunk-2018.02b/examples/gts-horse/000077500000000000000000000000001324306050200167175ustar00rootroot00000000000000trunk-2018.02b/examples/gts-horse/gts-horse.py000066400000000000000000000040371324306050200212100ustar00rootroot00000000000000#!/usr/bin/python # -*- coding: utf-8 -*- # © 2009 Václav Šmilauer from yade import pack import gts, os.path, locale locale.setlocale(locale.LC_ALL, 'en_US.UTF-8') #gts is locale-dependend. If, for example, german locale is used, gts.read()-function does not import floats normally ''' if you get "Error: unsupported locale setting" -> type as root: "dpkg-reconfigure locales" -> choose "en_US.UTF-8" (press space to choose) ''' # coarsen the original horse if we have it # do nothing if we have the coarsened horse already if not os.path.exists('horse.coarse.gts'): if os.path.exists('horse.gts'): surf=gts.read(open('horse.gts')); surf.coarsen(1000); surf.write(open('horse.coarse.gts','w')) else: print """horse.gts not found, you need to download input data: wget http://gts.sourceforge.net/samples/horse.gts.gz gunzip horse.gts.gz """ quit() surf=gts.read(open('horse.coarse.gts')) if surf.is_closed(): pred=pack.inGtsSurface(surf) aabb=pred.aabb() dim0=aabb[1][0]-aabb[0][0]; radius=dim0/40. # get some characteristic dimension, use it for radius O.bodies.append(pack.regularHexa(pred,radius=radius,gap=radius/4.)) surf.translate(0,0,-(aabb[1][2]-aabb[0][2])) # move surface down so that facets are underneath the falling spheres O.bodies.append(pack.gtsSurface2Facets(surf,wire=True)) O.engines=[ ForceResetter(), InsertionSortCollider([Bo1_Sphere_Aabb(),Bo1_Facet_Aabb()],label='collider'), InteractionLoop( [Ig2_Sphere_Sphere_ScGeom(),Ig2_Facet_Sphere_ScGeom()], [Ip2_FrictMat_FrictMat_FrictPhys()], [Law2_ScGeom_FrictPhys_CundallStrack()], ), NewtonIntegrator(damping=.1,gravity=[0,0,-5000]), PyRunner(iterPeriod=1000,command='timing.stats(); O.pause();'), PyRunner(iterPeriod=10,command='addPlotData()') ] O.dt=.7*PWaveTimeStep() O.saveTmp() O.timingEnabled=True O.trackEnergy=True from yade import plot plot.plots={'i':('total',O.energy.keys,)} def addPlotData(): plot.addData(i=O.iter,total=O.energy.total(),**O.energy) plot.plot(subPlots=False) from yade import timing from yade import qt qt.View() trunk-2018.02b/examples/gts-horse/gts-operators.py000066400000000000000000000033021324306050200221000ustar00rootroot00000000000000""" This file shows 2 ways to fill union of triangulated surfaces: You can either use union of 2 inGtsSurface predicates or create union of surfaces using GTS calls first and use a single isGtsSurface as predicate with the united surface. The disadvantage of the predicate union | is that each sphere must fit whole in one surface or another: with padding, several points on the sphere are tested. Therefore, areas near both surfaces' boundary will not be filled at all. Note that GTS only moves references to surfaces around, therefore e.g. translating surface that is part of the union will move also the part of the united surface. Therefore, we use the copy() method for deep copy here. """ from yade import pack,qt import gts, locale locale.setlocale(locale.LC_ALL, 'en_US.UTF-8') #gts is locale-dependend. If, for example, german locale is used, gts.read()-function does not import floats normally ''' if you get "Error: unsupported locale setting" -> type as root: "dpkg-reconfigure locales" -> choose "en_US.UTF-8" (press space to choose) ''' s1=gts.read(open('horse.coarse.gts')) s2=gts.Surface(); s2.copy(s1); s2.translate(0.04,0,0) O.bodies.append(pack.gtsSurface2Facets(s1,color=(0,1,0))+pack.gtsSurface2Facets(s2,color=(1,0,0))) s12=gts.Surface(); s12.copy(s1.union(s2)); s12.translate(0,0,.1) radius=0.002 O.bodies.append(pack.gtsSurface2Facets(s12,color=(0,0,1))) qt.View() from time import time t0=time() O.bodies.append(pack.regularHexa(pack.inGtsSurface(s1) | pack.inGtsSurface(s2),radius,gap=0,color=(0,1,0))) t1=time() print 'Using predicate union: %gs'%(t1-t0) O.bodies.append(pack.regularHexa(pack.inGtsSurface(s12),radius,gap=0.,color=(1,0,0))) t2=time() print 'Using surface union: %gs'%(t2-t1) trunk-2018.02b/examples/gts-horse/gts-random-pack-obb.py000066400000000000000000000031771324306050200230300ustar00rootroot00000000000000# -*- coding: utf-8 -*- """ CAUTION: Running this script can take very long! """ from numpy import arange from yade import pack import pylab # define the section shape as polygon in 2d; repeat first point at the end to close the polygon sq2=sqrt(2) poly=((3+.1,0),(3+0,.1),(3+sq2,.1+sq2),(3+.1+sq2,sq2),(3+.1,0)) #pylab.plot(*zip(*poly)); pylab.xlim(xmin=0); pylab.grid(); pylab.title('Meridian of the revolution surface\n(close to continue)'); pylab.gca().set_aspect(aspect='equal',adjustable='box'); pylab.show() thetas=arange(0,pi/8,pi/24) pts=pack.revolutionSurfaceMeridians([poly for theta in thetas],thetas,origin=Vector3(-4,0,-1),orientation=Quaternion((0,1,0),0.0)) surf=pack.sweptPolylines2gtsSurface(pts,capStart=True,capEnd=True,threshold=1e-4) O.bodies.append(pack.gtsSurface2Facets(surf,color=(1,0,1))) # fill this solid with triaxial packing; it will compute minimum-volume oriented bounding box # to minimize the number of throw-away spheres. # It does away with about 3k spheres for radius 3e-2 sp1=SpherePack() sp1=pack.randomDensePack(pack.inGtsSurface(surf),radius=3e-2,rRelFuzz=1e-1,memoizeDb='/tmp/gts-triax.sqlite',returnSpherePack=True) sp1.toSimulation() # translate the surface away and pack it again with sphere, but without the oriented bounding box (useOBB=False) # Here, we need 20k spheres (with more or less the same result) surf.translate(0,0,1); O.bodies.append(pack.gtsSurface2Facets(surf,color=(1,0,0))) sp2=SpherePack() sp2=pack.randomDensePack(pack.inGtsSurface(surf),radius=3e-2,rRelFuzz=1e-1,memoizeDb='/tmp/gts-triax-packings.sqlite',useOBB=False,returnSpherePack=True) sp2.toSimulation() from yade import qt qt.View() trunk-2018.02b/examples/gts-horse/gts-random-pack.py000066400000000000000000000063071324306050200222660ustar00rootroot00000000000000 """ CAUTION: Running this script can take very long! """ from numpy import arange from yade import pack import pylab # define the section shape as polygon in 2d; repeat first point at the end to close the polygon poly=((1e-2,5e-2),(5e-2,2e-2),(7e-2,-2e-2),(1e-2,-5e-2),(1e-2,5e-2)) # show us the meridian shape #pylab.plot(*zip(*poly)); pylab.xlim(xmin=0); pylab.grid(); pylab.title('Meridian of the revolution surface\n(close to continue)'); pylab.gca().set_aspect(aspect='equal',adjustable='box'); pylab.show() # angles at which we want this polygon to appear thetas=arange(0,pi/2,pi/24) # create 3d points from the 2d ones, turning the 2d meridian around the +y axis # for each angle, put the poly a little bit higher (+2e-3*theta); # this is just to demonstrate that you can do whatever here as long as the resulting # meridian has the same number of points # # There is origin (translation) and orientation arguments, allowing to transform all the 3d points once computed. # # without these transformation, it would look a little simpler: # pts=pack.revolutionSurfaceMeridians([[(pt[0],pt[1]+2e-3*theta) for pt in poly] for theta in thetas],thetas # pts=pack.revolutionSurfaceMeridians([[(pt[0],pt[1]+1e-2*theta) for pt in poly] for theta in thetas],thetas,origin=Vector3(0,-.05,.1),orientation=Quaternion((1,1,0),pi/4)) # connect meridians to make surfaces # caps will close it at the beginning and the end # threshold will merge points closer than 1e-4; this is important: we want it to be closed for filling surf=pack.sweptPolylines2gtsSurface(pts,capStart=True,capEnd=True,threshold=1e-4) # add the surface as facets to the simulation, to make it visible O.bodies.append(pack.gtsSurface2Facets(surf,color=(1,0,1))) # now fill the inGtsSurface predicate constructed form the same surface with sphere packing generated by TriaxialTest # with given radius and standard deviation (see documentation of pack.randomDensePack) # # The memoizeDb will save resulting packing into given file and next time, if you run with the same # parameters (or parameters that can be scaled to the same one), # it will load the packing instead of running the triaxial compaction again. # Try running for the second time to see the speed difference! memoizeDb='/tmp/gts-triax-packings.sqlite' sp=SpherePack() sp=pack.randomDensePack(pack.inGtsSurface(surf),radius=5e-3,rRelFuzz=1e-4,memoizeDb=memoizeDb,returnSpherePack=True) sp.toSimulation() # We could also fill the horse with triaxial packing, but have nice approximation, the triaxial would run terribly long, # since horse discard most volume of its bounding box # Here, we would use a very crude one, however if 1: import gts horse=gts.read(open('horse.coarse.gts')) #; horse.scale(.25,.25,.25) O.bodies.append(pack.gtsSurface2Facets(horse)) sp=pack.randomDensePack(pack.inGtsSurface(horse),radius=5e-3,memoizeDb=memoizeDb,returnSpherePack=True) sp.toSimulation() horse.translate(.07,0,0) O.bodies.append(pack.gtsSurface2Facets(horse)) # specifying spheresInCell makes the packing periodic, with the given number of spheres, proportions being equal to that of the predicate sp=pack.randomDensePack(pack.inGtsSurface(horse),radius=1e-3,spheresInCell=2000,memoizeDb=memoizeDb,returnSpherePack=True) sp.toSimulation() trunk-2018.02b/examples/gts-horse/horse.coarse.gts000066400000000000000000000723221324306050200220370ustar00rootroot00000000000000336 1002 668 GtsSurface GtsFace GtsEdge GtsVertex 0.01632376508 0.02011445965 0.03949959237 0.01658055601 0.02674922323 0.04300259797 0.01342358396 0.04386696349 0.0605453965 0.03001315348 0.01873622255 -0.04975389892 0.03116204225 0.01487261331 -0.04662516627 0.03463446561 0.01417249157 -0.06445953302 0.03853905253 -0.06858153453 -0.05929809 0.03482282022 -0.07347322551 -0.06009816584 0.03366937294 -0.06833041545 -0.06716035938 -0.00372101118 -0.05117824214 -0.005623645339 0.007895279935 -0.05575069912 -0.005892351915 -0.0051427155 -0.06231273516 -0.02069042783 -0.004407438103 0.0225899988 -0.0146861251 -0.0003199737081 0.02192630914 -0.02218160879 -0.00413713029 0.02296543695 -0.03588216291 0.03873458014 -0.03350409582 0.02683470299 0.04167919777 -0.05209059257 0.02091048947 0.0384508985 -0.04394816916 0.03037623224 -0.00347869098 -0.0933882462 -0.05987463672 -0.007297635957 -0.085588449 -0.05685515734 -0.005108643449 -0.08776230264 -0.06592629656 0.00971348483 -0.05849589992 0.03501436738 0.01354081077 -0.06958064753 0.02776828047 0.003341884252 -0.06982264577 0.02716426742 0.03602814732 -0.06264440825 -0.06411553023 0.03861060035 -0.06676979291 -0.07423763023 -0.004846611765 -0.08361531667 -0.04846258017 -0.002386477701 -0.08901605742 -0.04964340898 0.02635812852 0.01450927868 -0.005449945347 0.02124857632 0.005886586267 -0.01502511705 0.02930646502 0.01418264739 -0.00105293662 0.004999485014 0.04869287387 0.01582247286 0.004243912062 0.06354979735 0.03968593702 0.01387909336 0.04855022447 0.01654678367 -0.009574652073 0.04456750532 -0.07629616209 -0.002485469762 0.04727735928 -0.0764080269 -0.00700769812 0.03847786397 -0.06665893437 0.0032170073 -0.07051379881 -0.0243411354 0.003533893412 -0.07682277776 -0.03010434885 -5.745529168e-05 -0.08205292028 -0.04580411502 -0.004448292266 0.05776775096 0.04979181973 -0.002383352765 0.05840379756 0.03884913681 0.001649104365 0.04848064833 0.02428435727 0.02280040553 -0.006939100398 -0.01700689858 0.006525650782 -0.001905083605 -0.01926260778 0.007140868966 -0.01530936769 -0.01866033645 -0.01982634563 0.06775149539 0.06364431428 -0.02052662065 0.06497200103 0.07184237648 -0.01519768681 0.07336705425 0.0715293586 0.01640748827 -0.05470159714 -0.004061572812 0.02539781525 -0.05123916404 -0.004418224582 0.01340618388 -0.04149645941 -0.001936511727 -0.008547100601 -0.08917646339 -0.07601223399 0.002777066181 -0.08725952582 -0.0750620646 0.03253016364 -0.01043332179 0.02464282367 0.0304216333 0.006229091894 0.01981939311 0.02379963169 0.004276035173 0.02641813304 0.021068714 -0.04731479093 0.03782239003 0.003905242236 -0.03500299482 0.03582015786 0.004519749149 -0.04728633163 0.036171524 -0.03280646749 0.08281820251 0.05149462988 -0.02756475039 0.08573123505 0.05271463003 -0.03829187523 0.09115468692 0.04143674173 0.02850202338 -0.03054564901 -0.006315949257 0.02350165238 -0.01868529666 -0.01503724716 0.01965018576 -0.03322924453 -0.008539572723 0.003739677065 -0.06921427227 -0.01054149552 -0.00232621657 -0.07547428688 -0.01626294319 0.002561009975 -0.08147905387 -0.02169793489 0.003076164201 0.04142008331 -0.0761966455 -0.01048475663 0.07223536575 0.03862131711 0.003992378901 0.06593588934 0.0451209652 -0.001399859785 0.07361016554 0.04738085301 0.03596544551 -0.05057151191 -0.02335768512 0.02889815291 -0.04556070059 -0.01670429877 0.03458402404 -0.05834573855 -0.0371353123 0.02010910444 0.03325359829 0.03825534808 0.01969706093 0.04715955569 0.04155686324 -0.004374822524 0.03051240645 -0.02705523158 -0.007495620838 0.02978022866 -0.0413146882 -0.000952619628 0.03546274646 -0.02679558677 0.02492957417 -0.05990563106 -0.005103924907 0.01611197516 -0.06303677163 0.002209156417 0.02878306107 -0.06891623602 0.006810449087 0.03078789176 -0.04533835443 0.03599761587 0.02662411434 -0.06036994096 0.03367763245 -0.006198482586 -0.06038907344 0.02623821462 0.0008375952448 -0.05729894408 0.03314770082 0.01065539068 -0.02926798544 -0.01268610203 -0.00123172177 -0.0821110564 -0.06523927604 0.002267643128 -0.08483936173 -0.06485744272 0.002435320012 -0.0287034084 -0.0104083385 0.005898167992 -0.04014503574 -0.002000862249 -0.003413185552 -0.03748934485 0.0001903625871 0.02897089094 -0.06595417837 -0.0270747464 0.02626254606 -0.0576519279 -0.02771720415 0.02815688444 -0.05859886192 -0.01099546206 -0.00569519506 -0.003667327703 -0.009867925479 -0.003664341859 -0.01099308827 -0.01318038894 -0.01057455748 -0.0149757422 -0.004342056125 0.001165074828 0.02523403306 -0.03957574567 0.04054506938 -0.05491973431 -0.0005712432394 0.03459796038 -0.0670694805 0.014086427 0.03694977413 -0.06008906668 -0.006327568557 -0.01401089929 0.061731812 0.04791715597 -0.008148017624 0.06212160805 0.05173537454 0.03793501933 -0.05819911215 -0.0302432721 0.03560587506 -0.06245449477 -0.03179501568 0.0367011475 -0.05846435141 -0.02266137752 -0.03563223221 0.09281699658 0.03718100785 -0.04395759744 0.08685333977 0.03936496067 0.03260221715 0.008892183194 0.01391709339 0.03162763347 0.02970131831 0.02390040048 0.02728308792 0.01291182131 0.02544617293 -0.03210473943 0.07806264329 0.03992804324 -0.02533315814 0.06818440412 0.04741765489 -0.01650203414 0.06427222995 0.04146414271 6.463558982e-05 -0.06502324323 -0.01986639057 0.03588887642 -0.01310150974 -0.006069052401 0.03347089759 -0.001366867552 -0.006152612083 0.03880636848 -0.006999837534 0.002448307327 0.03634549187 0.03037389935 0.0006780210525 0.03114699098 0.03489764439 -0.01010029658 0.03181243893 0.02389805249 -0.01343461117 0.03321915172 -0.06970096602 -0.07479721793 0.02617661498 -0.06543442348 -0.07512581527 0.03863791475 -0.05898871062 -0.07447204709 -0.02464585764 0.07790419383 0.03911371277 -0.003676175066 -0.08031856436 -0.04419774774 0.007343293057 -0.0690826736 0.004699082341 0.001135414165 0.00332397633 -0.0149654229 -0.01018168771 -0.008969831054 0.01620246584 -0.004067298017 0.003541697005 0.02036548799 -0.005624862137 0.01297296776 0.01362713716 0.02993126142 0.006120180121 -0.05980834193 0.0300117401 0.01043604433 -0.06707332214 0.02614749382 0.009599598604 -0.06096653469 0.04068615925 -0.03832144847 0.002255096 0.03504581195 -0.04090497767 -0.006560986967 0.001606782166 -0.06062908051 -0.002709502897 -0.04048925521 0.08225382497 0.03671803481 0.03132196986 -0.05482448464 -0.07441255765 0.02897618302 -0.0646968157 -0.06570386051 0.03944244605 -0.009950499022 0.0125642861 0.01703653311 0.00492018746 -0.01825757265 -0.006672171266 -0.02467411049 -0.007335749351 -0.01088453518 -0.02619080841 0.002504819755 -0.004774100144 0.01405735811 0.001208144152 -0.009245597269 0.02264272615 -0.009987290754 0.02942965855 0.03814804577 -0.003053383658 0.02693592434 0.04610907695 0.00513848505 0.03161183356 0.04457372099 0.01243961132 0.01749335271 0.003408425806 0.02948884943 -0.0005986910633 0.06249697317 0.0389153322 -0.01011112712 -0.002490220659 -0.001339166587 0.02831876953 -0.04114566039 -0.004324052218 0.0299062197 -0.06640669737 -0.03817618226 0.03362395169 -0.06807729632 -0.04214825059 0.03145385637 -0.07043790333 -0.05065241666 -0.01385993011 0.07856512257 0.07010909487 -0.008966036138 0.08124334878 0.07413615785 -0.009634851258 0.08377656624 0.0648067235 0.03417987295 -0.03126020576 0.002051472635 0.03887913599 -0.01975554264 0.0003483112019 0.04041715828 -0.02222707883 0.009213719072 -0.02411216744 0.08716343663 0.04001838496 -0.03624732783 0.08877194901 0.03128124926 -0.006824447279 -0.03618631285 0.01036314859 0.006109389531 0.01401156355 -0.0172745504 -0.016955121 0.08888462345 0.06025997372 0.003136454446 0.05879174051 0.06996981433 0.009364015629 0.04383896399 0.06214828201 -0.002738714681 0.05721547048 0.06819604226 0.003596156501 0.02817269302 0.03719761616 0.001175439491 0.0400173452 0.03540909225 -0.001421426994 0.0409502811 0.02679768936 0.02296542558 0.0167059551 -0.02832207696 0.02023218019 0.01721885354 -0.01877052029 0.02259148848 0.02360483443 -0.03643464206 -0.02733044091 0.08787125685 0.04692096171 -0.01399844147 0.08714155927 0.0463454293 -0.002247771816 0.04960853348 0.04956155038 0.0371112331 -0.05642993252 -0.009103748836 0.03086388637 -0.05958940644 -0.009900558094 0.008083627594 0.02943377229 -0.01652181423 0.02878488846 0.03153740072 -0.01648268425 0.007378942884 0.03703186095 -0.01198024828 -0.00711984916 0.0838714505 0.05079055561 -0.01484917366 0.08707045168 0.05273273139 0.008900663526 0.02076874679 0.04039260339 -0.01065687562 0.06781706451 0.07104192583 -0.01923213626 0.06975322838 0.07592350727 -0.00180861207 -0.0237871789 0.02633206102 0.007081781297 0.03686708502 0.05384152926 0.0002606634791 0.0300769278 -0.05264987799 -1.128720967e-05 0.03467143884 -0.05385795846 0.001993284754 0.03402992897 -0.06497843758 0.01894026904 0.04552231826 0.002338742111 -0.001477592376 -0.07066827034 0.01717140905 -0.01058369946 -0.06554564462 -0.00479891702 -0.008532601031 -0.06674792612 0.01454048954 -0.005328400137 0.02877925501 0.02858328232 -0.002489659267 0.01816406301 0.02870549243 -0.008868073212 0.02177590967 0.01659891666 0.03201213473 -0.03332058818 0.03405172573 0.02496484114 -0.03482376588 0.03693143424 -0.007084975908 0.03564502825 -0.07638398126 0.03309913736 -0.06616617067 -0.02570362467 -0.01123582043 -0.05349151222 0.02156296952 -0.00633979874 -0.03525189872 0.02863119267 -0.01001486821 -0.04111521424 0.02037054785 0.0240898149 0.01974495734 -0.07571285871 0.03668911049 0.01706817617 -0.07522452542 0.03082837721 0.02288104727 -0.07477952054 0.02004949479 -0.004877881526 0.02863728161 0.02147141403 -0.01898875044 0.03130205807 0.006868598403 -0.008743899678 0.02845800478 -0.03490374023 0.08345473139 0.03190892168 0.008286006602 0.001373361038 0.02840663722 0.02705283708 0.01619527881 -0.01904634486 0.022758065 0.01432331388 -0.03659455797 0.03933367424 -0.01642258446 0.01840400709 -0.006687503522 0.06451285043 0.03898786259 -0.009085418731 0.03245207027 -0.01436499165 0.03017300659 -0.06508189934 -0.05136735094 0.003987663327 0.02925023984 -0.03321500269 0.002677092655 0.03375018866 -0.03674315581 0.01543242064 0.05460798553 0.05507772057 -0.006421711898 0.07742526017 0.0696651736 -0.00419132276 -0.05856793964 -0.008116933705 -0.009676266086 -0.06914423615 -0.01503783708 -0.008579063342 -0.05596021705 -0.006619909699 -0.008921886058 -0.06482584683 -0.01934456468 -0.009096315992 0.04204102618 0.01160160007 -0.001243446183 0.04418645785 0.02362804039 0.02923820731 -0.05857134399 -0.03571304629 -0.002887048581 -0.0754397367 -0.02372546511 -0.007453359386 -0.07962267368 -0.03389720166 -0.004418211153 -0.08432068557 -0.0330810779 0.02811757561 0.01166375995 -0.04552155405 0.02608452271 0.01702714733 -0.05039300674 -0.004886597434 -0.08350444289 -0.02345986511 0.02169112298 0.04075759437 -0.009406391461 0.03388216058 0.008237899224 -0.05888296292 0.002132409231 -0.08893284272 -0.05841890011 0.02946293638 -0.05685698454 -0.01696938881 -0.01305732701 -0.0157875938 0.007581932394 0.005269242578 0.02758668777 -0.01804179941 0.03118551162 0.03798029219 0.02305685213 -0.001595891186 0.07680675732 0.060244239 0.007605483109 0.06676323908 0.06410015977 0.01226952149 0.06313109224 0.05174176455 0.002168516904 0.0378257627 0.04619511973 -0.001847705929 0.04890833961 0.05690814405 0.01822885995 0.02472848353 -0.01765149387 0.04227347687 -0.04626460744 0.01045022104 -0.0125827108 -0.06033001875 0.005882403099 0.03271534573 -0.07082661945 0.01008829488 -0.007930641285 -0.03393843478 0.01695082313 0.003929964985 -0.0747043599 0.01742741436 -0.0004563179936 -0.07386146954 0.00329895685 0.009455168205 -0.0213275606 0.03132971345 -0.003094427367 -0.06889170807 0.004888854831 -0.0002324813022 0.04324301091 -0.003959822551 -0.008930419313 0.03645375466 -0.003607792147 -0.00219576075 0.03672636003 -0.01519672412 -0.004890876841 0.08032221011 0.04169445033 0.03204520536 0.01216969195 0.007172154793 0.0334482328 0.01894850435 0.006412574084 -0.01053739535 -0.00121782329 0.007474316827 -0.009658087064 -0.02323201166 0.01729006577 -0.01398463733 0.06392721831 0.06528033564 -0.003849456001 0.02659364606 -0.06127373877 -0.004525240387 0.032610241 -0.05552797955 -7.321253012e-05 -0.08671894842 -0.04651054113 0.01524840974 -0.07077468336 0.01566478909 0.02910546993 0.02580914068 -0.03501947875 -0.003027918867 0.0274712287 -0.05450942447 0.02660446376 -0.07178426534 0.02078314662 0.03089218932 -0.06842592557 0.0254113444 -0.007285789227 -0.06987799181 -0.006666778386 -0.009342702668 0.02821069851 -0.01160202414 0.008309789168 -0.07466218109 0.01244918111 0.03449252159 -0.02526268162 0.02567523323 0.02693937743 0.01503796125 -0.06642532575 0.0305308809 0.01669467749 -0.06497438481 -0.001842715329 0.06835726867 0.07180223722 0.02764793923 -0.02139899706 0.02973917638 0.03400049703 0.0256575615 0.01400341786 0.02131985423 0.02976380702 -0.0168021812 -0.00324064901 -0.04495672781 0.03258885092 -0.004602734265 -0.08184784244 -0.06468988447 -0.005672196731 -0.07565345108 -0.07601500342 0.02925991119 0.02479922737 -0.02520777166 0.03269591498 0.0277623861 -0.01367485415 -4.654141262e-05 0.03928648009 -0.06696839047 -0.002767040698 0.0356435873 -0.0534366966 -0.0006501128157 -0.07325849781 -0.007626583867 0.03786301243 -0.04770031706 -0.007155020039 0.03928093361 -0.05036233226 -0.01652680834 -0.01159532294 -0.04432251675 0.000424691754 -0.005770502646 0.04434374064 0.004006854425 0.03409200069 0.01012577627 -0.07298442924 0.02693396209 0.01067269833 -0.07605978988 0.01525639847 0.05852779157 0.03919665467 0.01860558345 0.05440443651 0.04216043825 0.0215432062 0.04824932889 0.02026185737 0.03272982977 0.02047535574 -0.03832779158 0.007739006328 0.04111411938 -0.006129204093 -0.008831888102 0.034482007 0.02187978353 0.02470204879 0.04321137839 0.02812018636 0.01009625484 -0.07142165998 0.002750268743 0.03771656929 -0.03253814824 0.01613668194 -0.02570503406 0.06997745916 0.06186892063 0.03434032889 0.02097979117 -0.006163903795 -0.006326888154 -0.07210550394 -0.02454785768 -0.01319774279 -0.04906725707 0.00385722078 0.03716209013 -0.05645697252 0.02963372594 0.03911499902 -0.06043184261 0.02293037278 -0.01230511982 0.02883591172 -0.0002496826006 -0.0003834515401 0.01828587539 -0.01794697368 0.009431580276 0.05582461812 0.06562219815 0.02815722799 -0.07116956276 -0.06035016397 -0.00260950101 0.0114412311 0.0241202205 -0.004563529073 0.03402878707 -0.06824679112 0.02623861077 0.01965492023 0.03060247466 0.002319258917 -0.07963579628 -0.07542058232 -0.00710983949 0.08707816298 0.07453090766 0.03573676564 -0.06371483034 -0.05173565457 0.003082377389 0.01686611051 -0.01117955432 -0.01647276147 0.08032673271 0.03979473134 -0.02855869872 0.07398775536 0.05116857685 -0.0003623580119 0.035165732 -0.07604654345 -0.008158632395 -0.08143711256 -0.07113356096 -0.004247150314 -0.07092102127 -0.02826231462 0.04198405446 -0.0587478605 0.01143895384 1 2 3 2 1 3 4 5 6 5 4 6 7 8 8 9 9 7 10 11 11 12 12 10 13 14 13 15 15 14 16 17 16 18 18 17 19 20 20 21 19 21 22 23 22 24 23 24 25 9 26 9 25 26 19 27 19 28 28 27 29 30 29 31 31 30 32 33 33 34 32 34 35 36 37 35 37 36 38 39 40 39 38 40 41 42 42 43 43 41 44 45 44 46 46 45 47 48 48 49 47 49 50 51 52 50 52 51 53 21 54 53 21 54 55 56 57 56 57 55 58 59 60 59 58 60 61 62 63 62 61 63 32 43 42 32 64 65 66 64 65 66 67 68 68 69 67 69 36 70 35 70 71 72 73 71 73 72 74 75 76 74 75 76 77 78 78 3 77 3 79 80 79 81 80 81 82 83 82 84 83 84 85 86 85 58 86 58 87 24 88 24 87 88 89 65 89 46 65 46 90 91 90 40 40 91 92 93 94 93 94 92 95 96 95 97 97 96 98 99 100 99 98 100 101 15 14 101 102 103 104 103 102 104 105 106 47 106 105 47 107 108 107 109 109 108 110 111 63 111 63 110 112 113 114 113 114 112 115 116 117 115 117 116 12 118 38 118 12 38 119 120 120 121 121 119 22 58 22 86 122 123 124 122 123 124 125 126 126 127 127 125 128 115 117 128 129 40 90 129 130 83 130 11 11 83 99 131 98 131 132 133 134 133 134 132 74 109 74 107 135 136 135 137 137 136 64 138 139 138 64 139 67 140 140 130 67 130 111 141 115 141 115 111 142 143 126 142 143 126 112 144 144 121 121 112 30 145 30 44 145 44 146 94 147 94 147 146 148 149 149 13 148 13 44 65 44 119 65 119 150 151 151 152 152 150 100 147 100 146 57 114 114 153 57 153 42 154 154 32 155 100 155 98 75 156 75 51 156 51 157 158 159 157 158 159 160 161 161 162 160 162 163 164 165 164 165 163 110 166 167 166 110 167 147 168 168 94 131 169 131 148 169 148 61 170 170 62 171 172 173 171 173 172 174 175 175 176 174 176 177 178 178 179 177 179 166 180 181 180 166 181 182 41 182 106 106 41 114 56 183 184 183 104 104 184 185 186 187 185 187 186 181 188 181 189 189 188 11 50 11 52 1 190 190 153 1 153 191 48 191 192 192 48 102 183 132 193 133 193 194 173 172 194 195 196 195 197 196 197 123 198 150 198 123 150 199 200 201 199 200 201 202 203 204 202 204 203 85 205 206 205 206 85 207 35 207 37 208 95 158 95 158 208 209 210 211 210 211 209 66 139 212 213 212 214 214 213 215 216 217 215 216 217 115 218 128 218 125 9 26 125 215 219 217 219 220 177 177 221 220 221 47 116 105 116 222 56 222 112 112 56 223 106 223 105 79 224 224 81 225 25 143 25 143 225 226 195 227 195 227 226 97 82 82 51 51 97 3 228 228 78 89 66 49 229 160 229 160 49 230 118 67 230 67 118 231 232 233 232 233 231 234 32 234 235 235 32 157 236 157 96 96 236 237 238 239 238 239 237 240 241 241 137 240 137 142 25 127 25 142 127 239 242 242 237 187 243 186 243 67 38 95 157 244 135 244 240 135 240 245 19 91 19 245 91 96 75 246 96 75 246 247 100 247 155 248 185 169 185 248 169 184 208 95 184 77 2 113 2 77 113 108 208 108 158 170 162 160 170 122 152 249 152 249 122 250 251 252 251 250 252 154 33 110 180 253 194 194 254 254 253 175 41 175 182 166 128 128 167 178 145 255 145 255 178 17 256 256 16 257 201 200 257 84 258 258 83 5 244 5 240 236 76 236 75 259 168 147 259 260 199 199 261 261 260 254 173 189 162 188 162 262 59 193 262 59 193 200 263 263 199 264 265 264 266 265 266 267 71 73 267 268 269 31 268 269 31 221 179 252 72 250 72 49 191 173 191 49 173 14 226 226 101 180 63 247 132 132 270 247 270 38 69 271 247 271 132 76 107 181 267 188 267 270 134 148 134 270 148 272 47 272 106 273 37 274 273 274 37 102 138 256 138 256 102 164 119 164 121 240 221 241 221 246 97 39 275 69 39 69 275 139 156 66 156 258 276 276 83 174 202 202 176 179 186 186 277 179 277 278 80 274 80 278 274 279 280 279 258 280 258 5 220 240 220 200 232 281 232 200 281 176 235 41 176 235 41 180 189 81 226 81 227 113 1 265 224 224 282 282 265 113 249 77 249 157 225 225 236 276 283 276 23 23 283 89 92 89 93 144 165 222 144 222 165 45 169 131 45 184 84 258 184 198 151 59 216 262 216 163 64 163 138 54 91 90 54 55 284 284 222 55 222 285 241 241 286 285 286 210 193 210 59 104 258 29 124 29 220 220 124 286 214 6 286 214 6 72 33 71 33 103 258 229 287 287 251 229 251 160 61 57 216 216 288 57 288 223 128 223 117 113 122 113 289 289 122 179 290 290 186 291 59 291 60 292 90 293 90 292 293 30 220 220 145 28 69 275 28 186 294 186 295 295 294 184 108 37 296 297 37 297 296 281 261 281 298 298 261 139 299 300 299 139 300 139 75 139 74 42 223 223 41 20 129 20 27 27 129 167 111 167 141 298 231 231 281 121 165 94 10 10 301 301 94 73 250 302 264 302 32 264 32 99 45 133 219 133 217 303 304 304 213 213 303 207 70 272 48 191 272 305 306 306 307 307 305 129 292 20 292 220 294 220 308 294 308 263 261 188 250 267 250 309 187 309 243 228 252 78 252 190 194 172 190 81 297 80 297 82 50 50 83 36 296 296 70 310 176 234 176 310 234 309 198 243 198 147 247 311 77 249 311 298 312 298 67 312 67 173 272 138 313 313 256 306 252 306 78 11 230 118 11 9 126 289 112 116 314 115 314 172 3 171 3 299 102 138 299 120 30 120 44 29 315 315 31 316 237 238 316 200 301 200 317 317 301 318 319 318 280 319 280 205 288 284 205 284 288 88 60 22 88 60 22 265 320 282 320 34 151 198 34 49 287 169 321 248 321 242 68 68 237 251 322 322 287 123 295 186 123 241 4 4 286 127 26 155 270 298 283 261 283 155 148 305 33 33 252 305 252 8 323 8 159 323 159 15 278 278 101 164 64 71 128 71 223 1 172 179 255 255 290 174 203 78 311 306 311 40 245 322 171 322 3 109 300 300 183 109 183 130 312 83 312 203 324 219 324 203 219 154 71 276 312 325 207 37 325 277 308 294 277 5 308 308 4 34 305 134 204 134 324 324 204 121 31 120 31 204 310 202 310 136 304 136 303 174 194 190 174 320 204 320 134 153 326 1 326 23 260 283 260 113 326 288 206 45 145 291 87 291 209 209 87 124 294 232 10 12 232 254 182 254 272 272 182 317 211 211 301 327 90 293 327 229 250 328 250 328 229 197 273 273 325 325 197 25 7 25 329 329 7 280 103 212 304 297 196 296 196 119 64 190 203 329 158 329 108 220 178 122 150 46 99 76 329 76 108 69 239 69 242 215 57 21 292 330 169 330 321 58 206 162 250 251 228 322 228 52 156 121 268 268 112 331 128 166 331 80 15 102 17 266 187 266 309 239 28 17 319 318 17 81 248 226 248 282 149 282 13 332 61 332 314 314 61 231 68 231 316 316 68 193 217 318 86 85 318 105 117 280 23 280 86 86 23 68 298 92 146 89 52 52 93 234 265 234 320 205 18 18 85 70 333 70 197 197 333 266 81 81 265 88 291 34 309 282 79 79 13 313 16 148 330 261 200 93 10 11 93 115 332 21 334 292 334 162 328 314 160 59 206 320 310 262 217 47 314 325 333 18 318 316 335 316 233 233 335 98 148 235 43 136 6 303 6 241 179 40 275 330 13 321 13 313 284 313 222 174 182 159 225 225 323 167 218 218 141 260 24 312 283 185 266 266 248 102 336 336 17 327 54 327 53 165 313 227 297 152 311 285 214 136 285 285 304 315 124 184 97 195 273 232 301 133 324 174 253 182 253 181 331 276 279 279 23 185 255 185 290 277 4 4 179 333 207 238 129 238 335 129 335 233 12 170 189 14 321 193 259 271 193 259 271 52 66 28 238 219 190 223 154 45 185 198 186 46 92 311 307 225 329 331 71 267 331 307 152 269 122 269 315 122 315 211 257 257 209 313 163 210 291 137 285 199 24 24 201 14 248 6 244 269 289 34 307 151 307 184 109 309 32 61 111 247 259 161 328 161 229 236 329 335 40 158 8 201 87 215 153 195 278 195 101 216 206 332 111 49 192 334 293 53 293 334 53 335 12 15 79 201 209 301 168 278 273 91 21 136 244 180 62 213 6 302 234 265 302 296 197 284 16 16 205 140 11 140 230 55 288 184 82 323 9 97 75 274 297 255 45 314 49 336 103 319 336 319 103 326 114 9 143 143 323 300 74 158 7 317 257 211 193 124 295 335 38 62 189 299 183 245 275 275 19 269 112 211 168 171 49 238 27 320 149 134 149 264 309 227 196 259 211 146 99 171 287 153 219 285 212 146 46 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 44 68 69 70 71 72 73 74 75 76 37 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 15 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 94 139 140 141 142 143 144 145 146 147 148 131 149 102 150 151 152 153 154 110 155 156 157 158 159 122 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 184 196 197 198 199 200 68 112 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 183 219 220 221 222 223 224 64 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 196 59 241 242 243 244 245 246 247 248 249 53 250 251 252 253 254 255 256 257 242 258 117 259 260 156 261 262 227 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 38 280 281 282 283 284 285 166 286 70 287 288 289 290 291 292 293 294 147 26 295 296 291 297 298 299 300 301 302 303 120 304 305 306 307 118 308 87 309 310 311 312 313 314 315 316 317 318 319 320 84 321 71 322 98 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 340 347 348 349 350 246 351 134 328 206 281 352 353 354 355 356 357 358 359 360 361 201 362 363 364 365 366 280 367 368 369 370 371 372 282 373 374 375 211 376 377 378 379 380 381 34 200 382 215 383 234 384 385 386 237 387 388 389 390 216 391 392 393 16 394 395 396 397 271 91 398 399 400 401 354 82 402 403 404 405 218 406 407 408 409 385 261 410 411 249 412 413 414 415 416 269 417 418 419 78 420 421 422 423 424 233 425 300 426 427 381 428 429 430 431 114 432 383 126 433 434 435 436 437 351 74 438 439 434 440 81 160 441 247 442 443 444 445 446 119 447 448 449 450 451 452 453 454 138 455 341 456 457 458 360 109 459 460 461 286 462 463 443 435 158 464 465 399 466 230 467 468 469 470 471 472 473 474 475 476 477 478 401 479 480 481 482 483 484 248 235 485 486 316 487 1 488 370 489 490 491 492 493 371 460 40 437 494 335 495 496 497 498 499 500 104 501 502 503 504 505 220 398 506 507 508 191 267 509 510 412 164 511 512 101 513 514 515 516 517 176 305 502 518 519 520 414 521 522 243 523 507 524 525 526 527 528 529 77 530 531 523 116 532 533 534 535 223 536 375 537 538 539 540 541 148 542 543 544 468 545 546 62 547 548 549 550 551 552 179 553 461 554 555 369 85 2 556 557 558 372 559 367 560 561 562 563 564 565 566 567 568 569 570 80 571 43 572 573 574 575 576 577 170 578 564 579 455 580 213 581 582 583 584 427 79 585 586 587 505 588 154 298 589 590 591 592 593 594 278 76 255 595 596 597 598 599 600 601 573 602 603 604 605 407 416 442 606 607 349 608 609 610 321 611 262 612 613 614 615 88 616 89 617 75 618 619 620 621 622 609 623 624 194 625 362 626 493 627 628 629 630 596 631 429 632 633 452 634 611 635 636 326 637 144 295 638 639 543 127 387 483 229 640 641 130 642 643 225 644 645 451 180 646 647 648 649 32 338 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 491 665 666 508 667 668 533 323 366 669 670 348 671 672 673 534 674 675 557 676 677 678 519 679 296 146 359 403 337 363 436 680 565 681 682 445 683 680 684 685 686 687 688 689 690 691 113 212 511 692 540 693 694 642 695 3 696 697 545 272 698 466 635 699 700 685 530 426 103 701 358 702 643 703 704 705 706 707 151 708 709 710 711 382 712 531 465 713 708 279 714 715 604 716 717 718 4 719 569 203 462 684 720 35 721 722 723 724 137 725 189 647 136 726 273 727 728 591 729 612 730 731 721 732 733 734 735 254 736 737 498 738 488 735 658 739 276 740 181 46 741 742 743 602 744 526 745 12 746 747 748 749 654 750 751 550 752 753 754 755 756 757 758 759 760 761 762 532 763 476 592 764 287 580 177 501 765 766 562 767 69 190 731 698 768 769 770 373 771 231 299 376 772 193 48 773 588 774 775 770 776 777 347 537 778 290 601 779 20 333 621 482 669 780 781 93 277 782 83 626 699 606 411 783 784 673 785 205 54 786 787 788 178 695 613 252 6 528 678 789 389 790 690 791 471 453 394 792 793 794 608 554 776 795 796 797 655 798 799 486 800 801 186 802 803 804 73 671 777 355 343 162 805 806 807 260 808 590 809 810 92 811 541 308 812 813 814 656 809 813 121 775 440 805 815 578 630 169 707 816 106 182 500 817 818 549 600 150 819 664 820 821 275 822 823 824 825 826 827 419 828 741 97 623 829 667 801 830 831 633 832 395 833 780 222 605 415 834 447 238 749 835 836 10 641 803 837 107 336 352 838 779 839 783 840 755 140 22 814 804 841 536 782 842 61 622 820 843 723 709 274 844 808 413 467 620 727 640 302 845 759 846 825 18 847 797 848 849 850 851 221 155 852 484 45 47 188 100 853 729 854 855 457 425 856 41 459 232 393 696 857 858 781 859 860 516 861 388 228 862 863 689 577 864 865 736 24 866 867 713 496 868 364 869 870 792 871 726 843 732 872 56 873 862 207 494 874 503 860 785 703 320 487 875 614 627 377 876 877 520 527 878 879 728 857 833 187 880 648 524 108 368 881 757 264 882 883 479 652 710 884 589 309 830 490 861 885 886 492 542 378 887 790 236 888 889 497 697 890 891 892 893 470 582 745 883 823 894 594 895 896 897 898 330 746 724 423 787 410 899 374 25 760 9 858 13 900 566 165 645 901 902 903 716 719 892 463 786 904 339 905 795 767 454 692 448 715 758 906 768 711 907 694 712 365 908 504 909 350 624 894 846 714 99 499 910 700 911 598 912 761 311 420 913 914 915 911 876 916 917 918 919 920 285 312 344 173 874 921 214 922 547 522 610 380 784 923 342 518 629 815 72 725 646 33 259 439 902 914 887 441 270 924 925 926 670 900 552 525 31 872 752 514 927 5 400 307 572 239 544 928 916 666 929 930 628 867 681 283 742 922 931 706 241 932 36 829 125 933 66 934 625 405 756 935 936 937 912 495 402 774 937 149 897 938 208 688 939 924 406 866 49 446 595 851 202 683 294 864 390 885 730 384 925 95 940 198 941 778 810 847 822 942 943 691 842 944 509 802 933 945 923 878 163 946 256 428 947 948 949 293 171 865 28 574 19 662 96 23 950 850 898 791 951 86 157 884 722 634 597 686 396 952 920 915 192 930 583 953 219 473 449 954 882 942 954 955 57 513 835 581 105 927 956 853 433 65 957 854 593 958 819 959 960 529 958 289 139 63 663 961 766 265 962 659 963 636 964 965 515 966 660 965 167 327 607 584 421 881 967 317 687 968 8 458 969 361 123 931 559 952 940 743 834 481 563 896 651 848 599 929 720 615 970 472 831 951 14 971 392 740 152 168 964 841 972 325 793 245 868 973 974 975 477 718 603 128 738 976 949 55 838 977 968 978 332 586 959 199 571 907 979 570 568 133 11 637 939 7 980 560 618 39 551 947 839 750 981 919 971 908 890 657 763 975 268 772 141 638 977 175 318 616 52 982 284 521 143 983 675 984 950 135 485 957 985 986 705 567 926 431 799 356 987 988 161 956 353 989 928 639 953 751 990 934 903 438 555 29 988 948 753 873 209 324 936 430 226 991 818 251 836 30 905 992 538 944 739 197 976 734 676 909 266 304 58 517 993 994 733 832 859 962 855 893 677 789 913 693 159 979 704 794 418 995 938 984 42 319 204 969 973 115 870 996 315 263 821 17 963 444 994 185 769 980 762 970 561 450 997 982 901 195 111 998 935 840 210 904 817 322 812 889 474 981 653 397 888 464 475 967 506 90 869 798 826 943 314 432 456 478 301 224 899 985 991 999 668 856 987 701 303 132 811 510 292 844 489 827 310 993 665 800 257 946 50 941 1000 297 744 558 983 788 422 989 145 174 346 240 306 129 546 891 244 60 539 966 895 575 992 124 217 576 357 21 955 386 747 886 674 999 702 253 906 1000 288 1001 877 773 1002 998 51 972 845 1002 910 816 345 27 679 408 682 737 617 153 250 945 172 837 771 553 391 961 824 619 807 650 672 512 921 632 556 717 469 548 828 661 313 978 863 631 748 409 379 754 535 871 796 974 331 849 806 334 852 67 1001 764 879 997 404 990 986 644 258 480 329 579 875 996 765 995 587 932 417 960 585 424 649 917 918 880 142 trunk-2018.02b/examples/hdaps.py000066400000000000000000000011351324306050200164550ustar00rootroot00000000000000 # With Thinkpad laptops, control the sphere's motion by tilting the # laptop. O.bodies.append( geom.facetBox(center=(0,0,.1),extents=(.5,.5,.1),wallMask=31,color=(0,0,1))+ [sphere((0,0,.1),.04,color=(1,1,1))] ) O.engines=[ ForceResetter(), InsertionSortCollider([Bo1_Sphere_Aabb(),Bo1_Facet_Aabb()]), InteractionLoop([Ig2_Facet_Sphere_ScGeom()],[Ip2_FrictMat_FrictMat_FrictPhys()],[Law2_ScGeom_FrictPhys_CundallStrack()]), HdapsGravityEngine(calibrate=(-495,-495),calibrated=True,zeroGravity=(0,0,-1)), NewtonIntegrator(damping=.3), ] O.dt=PWaveTimeStep() import yade.qt yade.qt.View() O.run() trunk-2018.02b/examples/hourglass/000077500000000000000000000000001324306050200170135ustar00rootroot00000000000000trunk-2018.02b/examples/hourglass/.gitignore000066400000000000000000000000041324306050200207750ustar00rootroot00000000000000cpt trunk-2018.02b/examples/hourglass/hourglass.geo000066400000000000000000000013311324306050200215140ustar00rootroot00000000000000acc = 0.003; r0 = 0.0025; r1 = 0.015; h1 = 0.020; r2 = 0.017; h2 = 0.035; Point(1) = {0.0, 0.0, 0.0, acc}; Point(2) = {r0, 0.0, 0.0, acc}; Point(3) = {r1, 0.0, h1, acc}; Point(4) = {r2, 0.0, h2, acc}; Spline(1) = {2, 3, 4}; Translate {0, 0, h2} { Duplicata { Point{1}; } } Extrude {{0, 0, 1}, {0, 0, 0}, Pi/2} { Line{1}; } Rotate {{0, 0, 1}, {0, 0, 0}, Pi/2} { Duplicata { Surface{5}; } } Rotate {{0, 0, 1}, {0, 0, 0}, Pi/2} { Duplicata { Surface{6}; } } Rotate {{0, 0, 1}, {0, 0, 0}, Pi/2} { Duplicata { Surface{11}; } } Rotate {{0, 1, 0}, {0, 0, 0}, Pi} { Duplicata { Surface{5, 16, 11, 6}; } } Line Loop(39) = {4, 8, 13, 18}; Plane Surface(40) = {39}; Line Loop(41) = {38, 33, 28, 23}; Plane Surface(42) = {41}; trunk-2018.02b/examples/hourglass/hourglass.mesh000066400000000000000000003424511324306050200217110ustar00rootroot00000000000000 MeshVersionFormatted 2 Dimension 3 Vertices 1013 0 0 0 1 0.0025 0 0 2 0.015 0 0.02 3 0.017 0 0.035 4 0 0 0.035 5 1.5308084989342E-19 0.0025 0 6 9.1848509936051E-19 0.015 0.02 7 1.0409497792753E-18 0.017 0.035 8 -0.017 2.0818995585505E-18 0.035 16 -0.015 1.836970198721E-18 0.02 20 -0.0025 3.0616169978684E-19 0 21 -3.1228493378258E-18 -0.017 0.035 29 -2.7554552980815E-18 -0.015 0.02 33 -4.5924254968026E-19 -0.0025 0 34 -0.015 0 -0.02 36 -0.017 0 -0.035 37 4.2862637970157E-18 0 -0.035 41 3.2453140177405E-18 0.017 -0.035 42 1.5308084989342E-18 0.015 -0.02 46 5.2047488963763E-18 -0.015 -0.02 56 7.4091131348415E-18 -0.017 -0.035 57 0.015 1.836970198721E-18 -0.02 76 0.017 2.0818995585505E-18 -0.035 77 0.0041191109516929 0 0.0025112489820164 1 0.0057855480850599 0 0.0049914195257032 1 0.0074707094096037 0 0.0074589300274465 1 0.009149856440047 0 0.0099305297609231 1 0.010797042896004 0 0.012423499673082 1 0.012379372120708 0 0.014958015284962 1 0.01384691140468 0 0.017560371967964 1 0.015108828634607 0 0.020267446375152 1 0.016021917016366 0 0.023109946151289 1 0.016525592677476 0 0.026052904433526 1 0.016699150445624 0 0.029035081137958 1 0.01676589618242 0 0.032022340306419 1 2.5222280211618E-19 0.0041191109516929 0.0025112489820164 2 3.5426264718409E-19 0.0057855480850599 0.0049914195257032 2 4.5744901829156E-19 0.0074707094096037 0.0074589300274465 2 5.6026712009807E-19 0.009149856440047 0.0099305297609231 2 6.6112820114237E-19 0.010797042896004 0.012423499673082 2 7.5801792215397E-19 0.012379372120708 0.014958015284962 2 8.4787878649092E-19 0.01384691140468 0.017560371967964 2 9.2514893131187E-19 0.015108828634607 0.020267446375152 2 9.8105946951485E-19 0.016021917016366 0.023109946151289 2 1.0119007088242E-18 0.016525592677476 0.026052904433526 2 1.0225280570857E-18 0.016699150445624 0.029035081137958 2 1.0266150547319E-18 0.01676589618242 0.032022340306419 2 0.0017677669529698 0.001767766952963 0 3 0.016741731801209 0.0029520190203294 0.035 4 0.015974774553366 0.0058143424365211 0.035 4 0.014722431864347 0.0084999999999795 0.035 4 0.013022755533042 0.010927389364648 0.035 4 0.010927389364693 0.013022755533004 0.035 4 0.0085000000000187 0.014722431864325 0.035 4 0.0058143424365487 0.015974774553356 0.035 4 0.0029520190203422 0.016741731801207 0.035 4 -0.0029520190203294 0.016741731801209 0.035 8 -0.0058143424365211 0.015974774553366 0.035 8 -0.0084999999999795 0.014722431864347 0.035 8 -0.010927389364648 0.013022755533042 0.035 8 -0.013022755533004 0.010927389364693 0.035 8 -0.014722431864325 0.0085000000000187 0.035 8 -0.015974774553356 0.0058143424365487 0.035 8 -0.016741731801207 0.0029520190203422 0.035 8 -0.016765896182421 2.0532301094638E-18 0.032022340306439 9 -0.016699150445625 2.0450561141715E-18 0.029035081138001 9 -0.016525592677483 2.0238014176493E-18 0.02605290443359 9 -0.016021917016385 1.9621189390321E-18 0.023109946151369 9 -0.015108828634642 1.850297862628E-18 0.020267446375238 9 -0.013846911404715 1.6957575729861E-18 0.017560371968031 9 -0.01237937212074 1.5160358443118E-18 0.014958015285015 9 -0.01079704289603 1.322256402288E-18 0.012423499673123 9 -0.0091498564400673 1.1205342401986E-18 0.0099305297609531 9 -0.0074707094096183 9.148980365849E-19 0.0074589300274679 9 -0.0057855480850689 7.0852529436927E-19 0.0049914195257164 9 -0.0041191109516979 5.0444560423296E-19 0.0025112489820239 9 -0.001767766952972 0.0017677669529607 0 10 -0.016741731801209 -0.0029520190203294 0.035 13 -0.015974774553366 -0.0058143424365211 0.035 13 -0.014722431864347 -0.0084999999999795 0.035 13 -0.013022755533042 -0.010927389364648 0.035 13 -0.010927389364693 -0.013022755533004 0.035 13 -0.0085000000000187 -0.014722431864325 0.035 13 -0.0058143424365487 -0.015974774553356 0.035 13 -0.0029520190203422 -0.016741731801207 0.035 13 -3.0798451641957E-18 -0.016765896182421 0.032022340306439 14 -3.0675841712573E-18 -0.016699150445625 0.029035081138001 14 -3.0357021264739E-18 -0.016525592677483 0.02605290443359 14 -2.9431784085481E-18 -0.016021917016385 0.023109946151369 14 -2.775446793942E-18 -0.015108828634642 0.020267446375238 14 -2.5436363594792E-18 -0.013846911404715 0.017560371968031 14 -2.2740537664677E-18 -0.01237937212074 0.014958015285015 14 -1.983384603432E-18 -0.01079704289603 0.012423499673123 14 -1.6808013602979E-18 -0.0091498564400673 0.0099305297609531 14 -1.3723470548774E-18 -0.0074707094096183 0.0074589300274679 14 -1.0627879415539E-18 -0.0057855480850689 0.0049914195257164 14 -7.5666840634944E-19 -0.0041191109516979 0.0025112489820239 14 -0.0017677669529607 -0.001767766952972 0 15 0.0029520190203302 -0.016741731801209 0.035 18 0.005814342436523 -0.015974774553365 0.035 18 0.0084999999999811 -0.014722431864346 0.035 18 0.010927389364648 -0.013022755533042 0.035 18 0.013022755533002 -0.010927389364695 0.035 18 0.014722431864325 -0.0085000000000183 0.035 18 0.015974774553356 -0.0058143424365495 0.035 18 0.016741731801207 -0.0029520190203412 0.035 18 0.0017677669529725 -0.0017677669529603 0 20 -0.004119110951693 2.6617559875048E-19 -0.0025112489820165 22 -0.0057855480850602 2.2458697555549E-19 -0.0049914195257035 22 -0.0074707094096041 1.8236281681549E-19 -0.0074589300274472 22 -0.0091498564400474 1.403428894643E-19 -0.0099305297609236 22 -0.010797042896004 9.94067724021E-20 -0.012423499673083 22 -0.012379372120709 6.0662461485963E-20 -0.014958015284963 22 -0.01384691140468 2.5773777186089E-20 -0.017560371967964 22 -0.015108828634607 -2.278126933662E-21 -0.020267446375152 22 -0.016021917016366 -1.8907016689674E-20 -0.023109946151289 22 -0.016525592677476 -2.237176738811E-20 -0.026052904433526 22 -0.016699150445624 -1.5395459197089E-20 -0.029035081137957 22 -0.01676589618242 -5.0724499362285E-21 -0.032022340306418 22 -0.016741731801209 0.0029520190203294 -0.035 23 -0.015974774553366 0.0058143424365211 -0.035 23 -0.014722431864347 0.0084999999999795 -0.035 23 -0.013022755533042 0.010927389364648 -0.035 23 -0.010927389364693 0.013022755533004 -0.035 23 -0.0085000000000187 0.014722431864325 -0.035 23 -0.0058143424365487 0.015974774553356 -0.035 23 -0.0029520190203422 0.016741731801207 -0.035 23 2.8899181510807E-18 0.016765896182421 -0.032022340306439 24 2.517848401581E-18 0.016699150445625 -0.029035081138001 24 2.1562881260961E-18 0.016525592677483 -0.02605290443359 24 1.8301856720681E-18 0.016021917016385 -0.023109946151369 24 1.5546192747843E-18 0.015108828634642 -0.020267446375238 24 1.3284203229408E-18 0.013846911404715 -0.017560371968031 24 1.1344730933684E-18 0.01237937212074 -0.014958015285015 24 9.5971848214731E-19 0.01079704289603 -0.012423499673123 24 7.9621491792338E-19 0.0091498564400673 -0.0099305297609531 24 6.3836927684295E-19 0.0074707094096183 -0.0074589300274679 24 4.8159692290765E-19 0.0057855480850689 -0.0049914195257164 24 3.2149209940364E-19 0.0041191109516979 -0.0025112489820239 24 2.6568091286592E-19 -0.0041191109516929 -0.0025112489820164 27 1.0002996094212E-18 -0.0057855480850599 -0.0049914195257032 27 1.7387140827458E-18 -0.0074707094096037 -0.0074589300274465 27 2.4759118404565E-18 -0.009149856440047 -0.0099305297609231 27 3.2066041971056E-18 -0.010797042896004 -0.012423499673082 27 3.9238949360366E-18 -0.012379372120708 -0.014958015284962 27 4.6168403601549E-18 -0.01384691140468 -0.017560371967964 27 5.2643275077586E-18 -0.015108828634607 -0.020267446375152 27 5.830051616878E-18 -0.016021917016366 -0.023109946151289 27 6.293378030938E-18 -0.016525592677476 -0.026052904433526 27 6.6695424667067E-18 -0.016699150445624 -0.029035081137958 27 7.0166681697506E-18 -0.01676589618242 -0.032022340306419 27 -0.0029520190203302 -0.016741731801209 -0.035 28 -0.005814342436523 -0.015974774553365 -0.035 28 -0.0084999999999811 -0.014722431864346 -0.035 28 -0.010927389364648 -0.013022755533042 -0.035 28 -0.013022755533002 -0.010927389364695 -0.035 28 -0.014722431864325 -0.0085000000000183 -0.035 28 -0.015974774553356 -0.0058143424365495 -0.035 28 -0.016741731801207 -0.0029520190203412 -0.035 28 0.004119110951693 2.3827000548188E-19 -0.0025112489820165 32 0.00578554808506 4.8393831881269E-19 -0.0049914195257033 32 0.0074707094096038 7.3253521976764E-19 -0.0074589300274468 32 0.009149856440047 9.8019135073182E-19 -0.009930529760923 32 0.010797042896004 1.2228496298827E-18 -0.012423499673082 32 0.012379372120709 1.455373382822E-18 -0.014958015284963 32 0.013846911404681 1.6699837957958E-18 -0.017560371967965 32 0.015108828634607 1.8525759895575E-18 -0.020267446375153 32 0.016021917016366 1.9810259557194E-18 -0.02310994615129 32 0.016525592677476 2.0461731850365E-18 -0.026052904433527 32 0.016699150445624 2.0604515733685E-18 -0.029035081137959 32 0.01676589618242 2.0583025594E-18 -0.032022340306419 32 0.016741731801209 -0.0029520190203294 -0.035 33 0.015974774553366 -0.0058143424365211 -0.035 33 0.014722431864347 -0.0084999999999795 -0.035 33 0.013022755533042 -0.010927389364648 -0.035 33 0.010927389364693 -0.013022755533004 -0.035 33 0.0085000000000187 -0.014722431864325 -0.035 33 0.0058143424365487 -0.015974774553356 -0.035 33 0.0029520190203422 -0.016741731801207 -0.035 33 0.0029520190203294 0.016741731801209 -0.035 38 0.0058143424365211 0.015974774553366 -0.035 38 0.0084999999999795 0.014722431864347 -0.035 38 0.010927389364648 0.013022755533042 -0.035 38 0.013022755533004 0.010927389364693 -0.035 38 0.014722431864325 0.0085000000000187 -0.035 38 0.015974774553356 0.0058143424365487 -0.035 38 0.016741731801207 0.0029520190203422 -0.035 38 0.011817772019113 0.011545654013399 0.026014728600703 5 0.0081718158531594 0.0081620504508242 0.013605730149044 5 0.0076783346892395 0.014848961347711 0.029876032409286 5 0.0061627839564005 0.006161467019494 0.0092855906609981 5 0.0049379021124376 0.0049374899008986 0.0067457124394383 5 0.0049595756189047 0.01601095810185 0.031895911307718 5 0.0059513182921853 0.015203418326349 0.024590467310548 5 0.0060035035333312 0.013352803967054 0.019174893608037 5 0.013183866931907 0.0060685285805313 0.01890085211346 5 0.0053626682547526 0.011901886470567 0.016112136620152 5 0.011838159050646 0.0053775015390972 0.016021124055091 5 0.0028708355061004 0.011460495957204 0.014030890133515 5 0.011434318546668 0.0028754716482335 0.013991706108988 5 0.014595323357652 0.0080586338104238 0.028181398214947 5 0.0029586826795465 0.0097417596501372 0.011479166424322 5 0.0097376255787949 0.0029560624320443 0.011471999028418 5 0.014540091737667 0.0083022788062407 0.031258476444494 5 0.011423056429642 0.011156225061616 0.022890405584616 5 0.003296533488082 0.0075581377316377 0.0085949577515895 5 0.0075584922983079 0.0032955950666876 0.0085948841096562 5 0.0020533851181653 0.0062404818978725 0.006141225153329 5 0.0062405554355255 0.0020531293396744 0.0061412103938556 5 0.0026344273857878 0.0026344098160655 0.0019131611843906 5 0.0030022478250682 0.01585067958842 0.023587772980855 5 0.0088111714254618 0.012846873244006 0.021557238714553 5 0.012719694535565 0.0085581899800376 0.020846161445907 5 0.013335539752262 0.0092159617303703 0.023958654376394 5 0.015497342983452 0.004782173905333 0.023999903123553 5 0.0030399915042713 0.014673687565283 0.019964555340209 5 0.014356530213428 0.002970484636813 0.019219431144543 5 0.0099577887011926 0.0072669117593362 0.014871448774449 5 0.0072867656982308 0.0099451973799746 0.014874036399558 5 0.0080301832675644 0.011530919167178 0.017956942415788 5 0.011447573139851 0.0079769735064481 0.017764052126896 5 0.0057727422708801 0.0096065721565331 0.0130639207733 5 0.0026218215873979 0.013163416117906 0.016768933524956 5 0.0096116858665912 0.0057615874285268 0.013061789542131 5 0.013059867726167 0.0026446927372393 0.016593390097358 5 0.0034718664167542 0.016247243068875 0.027081480893645 5 0.0095306357406731 0.013623845060405 0.027272342157015 5 0.0061396312211152 0.014477911991784 0.022025909293933 5 0.0054979601014472 0.015766991146745 0.028992147668517 5 0.014598524944405 0.0058242993171874 0.021998049568085 5 0.0105207343394 0.010444369753237 0.019587789020172 5 0.0071424601935909 0.015190736053932 0.032513600421645 5 0.002703069441944 0.016504006944387 0.030280167671992 5 0.0093898001130243 0.009371480012766 0.016487955889268 5 0.013282899654764 0.0099216285168886 0.026623951601592 5 0.013525478716994 0.0098120622160165 0.029509384479015 5 0.015657343714343 0.005830345361347 0.029407719394144 5 0.0072983608651194 0.0072934070379124 0.011687422712685 5 0.011618186430634 0.011993657117766 0.028997446672854 5 0.016513109679355 0.0026020869905468 0.029884550827871 5 0.0098272807391855 0.013577167482992 0.031866081224589 5 0.015870835020447 0.0055148372980691 0.0328214484886 5 0.01658775095868 0.0026181635737226 0.032657847853908 5 0.0021372552380502 0.016656057938069 0.032648283223455 5 0.0081591758027743 0.0052395095740501 0.010747337390676 5 0.0052436241356558 0.00815695014149 0.01074786552511 5 0.0043215292855866 0.013408716169009 0.018028647759799 5 0.0079290162035355 0.014279829909216 0.024630669395006 5 0.006771888289444 0.015171346565565 0.027082124862353 5 0.01323613170101 0.0043188522934018 0.017706356225246 5 0.010017221810382 0.012887303771985 0.024566132150982 5 0.014986557434501 0.0068795874142743 0.025734684915055 5 0.01580381560367 0.0025214413398155 0.023035851141689 5 0.016172815135023 0.0036086759645303 0.026521026424252 5 0.016578185302832 0.0016551607430484 0.027905808455804 5 0.0025974692004582 0.0047286460585139 0.0044159492519593 5 0.0047712387450254 0.0025623967176233 0.004446504982117 5 0.012742554372282 0.010912785998288 0.032303764648431 5 0.0016017279691565 0.015566081315828 0.021774846535967 5 0.016217398178012 0.0041571026147987 0.031186655276687 5 0.0097214360072037 0.013590712032621 0.029507354042183 5 -0.0082717383605921 0.0082613485785634 0.016213415316446 6 -0.0066699647114874 0.0066684368986329 0.012039983500877 6 -0.0056917777372523 0.005691564889804 0.0096276979596763 6 -0.0068265892290299 0.014759311331099 0.030410378031282 6 -0.004393208759937 0.0043931326607764 0.0064826153461333 6 -0.0042476371618292 0.016041060489846 0.03218407243256 6 -0.0032154287615761 0.0032154180913616 0.0036239331905458 6 -0.0059418690885288 0.012972100729797 0.021246307883323 6 -0.01332456191408 0.0054627536768092 0.021402863101846 6 -0.0053378446511886 0.011781484189247 0.018095608953025 6 -0.011834379192803 0.0052463721865453 0.01808797729342 6 -0.011159753480252 0.011472693372095 0.029626545939085 6 -0.00374503213151 0.011249973706768 0.015596325114303 6 -0.011253237197071 0.0037380363987274 0.015595469491987 6 -0.012851792736322 0.0093636977489121 0.02879001666985 6 -0.0089669182521092 0.013015165083302 0.028130578004284 6 -0.0037194597618623 0.0099291178051743 0.013481876108284 6 -0.0099306386652822 0.0037165879346099 0.013481692404871 6 -0.0037884509392469 0.0083582753654076 0.011203090842683 6 -0.0083588699362647 0.0037874569592363 0.011203052788177 6 -0.010313931442108 0.010145361775024 0.022744156083031 6 -0.0027656113030483 0.0068806704323075 0.0081908289432826 6 -0.0068807758292609 0.0027654179459889 0.0081908225081814 6 -0.0023397113154977 0.0023397111667607 0.0014550574392565 6 -0.009266425737024 0.013275984087038 0.030571604393365 6 -0.0078146868455671 0.012334641048655 0.022796778873777 6 -0.012454145023008 0.0076374869481693 0.022769793676485 6 -0.010036856621866 0.0071347626024829 0.017325764181858 6 -0.0071525544853175 0.010023039044549 0.017326839197287 6 -0.007727041497197 0.011248669075361 0.020243652462113 6 -0.011368466834715 0.0075675642849413 0.020233516406678 6 -0.0084972993185707 0.012625292969213 0.025147533859711 6 -0.0059105502529598 0.0097729456958386 0.015441641746183 6 -0.0097792814718217 0.0059012563597329 0.015440800306456 6 -0.012628369118597 0.0085912406305926 0.025401811765537 6 -0.0019495446129657 0.016270526215821 0.027642876676209 6 -0.0085701432443774 0.005577362930186 0.013305248277138 6 -0.0055811393897819 0.0085673779133964 0.013305465968329 6 -0.013149347561508 0.0098217957020027 0.032008790844085 6 -0.010989941375857 0.012265826468383 0.032397053112351 6 -0.0018456208345705 0.011291103386076 0.01419444401089 6 -0.011291853549283 0.0018434101143583 0.014194203942445 6 -0.0058488239443457 0.014069585541814 0.024173965829745 6 -0.0024678007693866 0.012517744954497 0.016700065246297 6 -0.012525778933081 0.0024442570841018 0.016696258320126 6 -0.016147522890573 0.0024855843447317 0.027832998061215 6 -0.014188863688329 0.0059300639954027 0.024771546627925 6 -0.0075591300716283 0.0075535569889815 0.014306337216609 6 -0.0018591339124912 0.010055367945226 0.01225765630751 6 -0.0083177155119604 0.014347630468727 0.032869125460133 6 -0.010055799632198 0.0018579577219743 0.012257562442334 6 -0.0092922125655599 0.0092148741817451 0.019123752836843 6 -0.0038162309143657 0.01543859133717 0.025910681124999 6 -0.0064410217798052 0.014449357477632 0.027206860650616 6 -0.0061913451400342 0.015372113861266 0.032501426386109 6 -0.015678998876371 0.0030163521005449 0.025594484613648 6 -0.013885057868704 0.0027907016800219 0.019611789232109 6 -0.0028854864982482 0.013787556067844 0.019489368437076 6 -0.015322558429872 0.0050117349951956 0.028327835488012 6 -0.0028458277977538 0.015065637094644 0.022758795075994 6 -0.014978694657073 0.0028886694752039 0.022531848489249 6 -0.016366352282639 0.002399476647182 0.030423522547633 6 -0.01097088773736 0.010919892550347 0.02668522269199 6 -0.0019543396007733 0.0164700410623 0.030621299288307 6 -0.0041292581889972 0.015744736158318 0.028969649993132 6 -0.014371272472696 0.0074323693494772 0.030034783109166 6 -0.013967633115647 0.0074189930258002 0.027656130272334 6 -0.0017131856399201 0.016007010631695 0.025117784530601 6 -0.0018434998428334 0.016650055934251 0.032944124462655 6 -0.014791674054192 0.0073965866750131 0.032477628404384 6 -0.015733688374976 0.0049672374092065 0.031607261016803 6 -0.001795140732301 0.0055636519223309 0.0055382195383785 6 -0.0055636716835832 0.0017950976384481 0.0055382184658087 6 -0.016528421007297 0.0027058237553584 0.033150309371652 6 -0.008261348578574 -0.0082717383606026 0.016213415316475 11 -0.006668436898641 -0.0066699647114955 0.012039983500897 11 -0.0056915648898102 -0.0056917777372584 0.0096276979596913 11 -0.014759311331101 -0.0068265892290315 0.030410378031302 11 -0.0043931326607804 -0.004393208759941 0.0064826153461429 11 -0.016041060489848 -0.00424763716183 0.032184072432575 11 -0.0032154180913637 -0.0032154287615782 0.003623933190551 11 -0.012972100729812 -0.0059418690885362 0.021246307883368 11 -0.0054627536768113 -0.013324561914084 0.02140286310186 11 -0.011781484189266 -0.0053378446511975 0.018095608953069 11 -0.0052463721865486 -0.01183437919281 0.018087977293437 11 -0.011472693372097 -0.011159753480254 0.029626545939099 11 -0.011249973706791 -0.0037450321315176 0.015596325114347 11 -0.0037380363987295 -0.011253237197078 0.015595469491999 11 -0.0093636977489134 -0.012851792736323 0.028790016669862 11 -0.013015165083305 -0.0089669182521115 0.028130578004306 11 -0.0099291178051934 -0.0037194597618693 0.01348187610832 11 -0.0037165879346122 -0.0099306386652884 0.013481692404883 11 -0.0083582753654218 -0.0037884509392532 0.01120309084271 11 -0.0037874569592388 -0.0083588699362704 0.011203052788188 11 -0.010145361775031 -0.010313931442114 0.022744156083058 11 -0.0068806704323182 -0.0027656113030525 0.0081908289433013 11 -0.0027654179459903 -0.0068807758292645 0.0081908225081878 11 -0.0023397111667615 -0.0023397113154985 0.0014550574392586 11 -0.01327598408704 -0.0092664257370256 0.030571604393381 11 -0.012334641048665 -0.007814686845574 0.022796778873816 11 -0.0076374869481729 -0.012454145023013 0.022769793676505 11 -0.00713476260249 -0.010036856621876 0.017325764181882 11 -0.010023039044564 -0.0071525544853283 0.017326839197325 11 -0.011248669075373 -0.0077270414972058 0.020243652462151 11 -0.0075675642849465 -0.011368466834723 0.020233516406701 11 -0.012625292969219 -0.008497299318575 0.025147533859741 11 -0.0097729456958549 -0.0059105502529697 0.01544164174622 11 -0.0059012563597383 -0.0097792814718305 0.015440800306475 11 -0.008591240630595 -0.012628369118601 0.025401811765554 11 -0.016270526215825 -0.0019495446129669 0.027642876676253 11 -0.0055773629301913 -0.0085701432443856 0.013305248277155 11 -0.0085673779134099 -0.0055811393897907 0.013305465968357 11 -0.0098217957020034 -0.013149347561509 0.032008790844091 11 -0.012265826468384 -0.010989941375858 0.032397053112359 11 -0.011291103386102 -0.0018456208345747 0.014194444010935 11 -0.0018434101143588 -0.011291853549287 0.01419420394245 11 -0.014069585541823 -0.0058488239443505 0.024173965829787 11 -0.012517744954524 -0.002467800769392 0.016700065246349 11 -0.0024442570841026 -0.012525778933085 0.016696258320133 11 -0.0024855843447319 -0.016147522890574 0.027832998061218 11 -0.005930063995404 -0.014188863688331 0.024771546627937 11 -0.0075535569889912 -0.007559130071638 0.014306337216634 11 -0.010055367945248 -0.0018591339124951 0.012257656307546 11 -0.014347630468728 -0.0083177155119613 0.032869125460141 11 -0.0018579577219749 -0.010055799632202 0.012257562442339 11 -0.0092148741817545 -0.0092922125655694 0.019123752836874 11 -0.015438591337177 -0.0038162309143685 0.025910681125043 11 -0.014449357477636 -0.006441021779808 0.027206860650647 11 -0.015372113861267 -0.0061913451400351 0.032501426386121 11 -0.0030163521005452 -0.015678998876372 0.025594484613653 11 -0.0027907016800226 -0.013885057868707 0.019611789232117 11 -0.013787556067869 -0.0028854864982538 0.019489368437135 11 -0.005011734995196 -0.015322558429873 0.028327835488018 11 -0.015065637094661 -0.0028458277977577 0.022758795076054 11 -0.0028886694752044 -0.014978694657075 0.022531848489255 11 -0.0023994766471821 -0.016366352282639 0.030423522547635 11 -0.01091989255035 -0.010970887737363 0.02668522269201 11 -0.016470041062302 -0.0019543396007739 0.030621299288334 11 -0.015744736158321 -0.0041292581889988 0.028969649993163 11 -0.0074323693494778 -0.014371272472696 0.030034783109173 11 -0.0074189930258013 -0.013967633115649 0.027656130272345 11 -0.016007010631704 -0.0017131856399218 0.025117784530659 11 -0.016650055934252 -0.0018434998428337 0.032944124462668 11 -0.0073965866750134 -0.014791674054192 0.032477628404388 11 -0.0049672374092067 -0.015733688374976 0.031607261016806 11 -0.0055636519223384 -0.0017951407323033 0.0055382195383912 11 -0.0017950976384488 -0.0055636716835853 0.0055382184658122 11 -0.0027058237553584 -0.016528421007297 0.033150309371653 11 0.0082613485785632 -0.0082717383605925 0.016213415316446 16 0.0066684368986329 -0.0066699647114875 0.012039983500877 16 0.005691564889804 -0.0056917777372523 0.0096276979596763 16 0.014759311331089 -0.0068265892290478 0.030410378031282 16 0.0043931326607767 -0.0043932087599367 0.0064826153461333 16 0.016041060489842 -0.0042476371618441 0.03218407243256 16 0.0032154180913624 -0.0032154287615753 0.0036239331905458 16 0.012972100729795 -0.0059418690885319 0.021246307883323 16 0.0054627536768062 -0.013324561914081 0.021402863101846 16 0.011781484189246 -0.0053378446511897 0.018095608953025 16 0.0052463721865443 -0.011834379192804 0.018087977293421 16 0.011472693372078 -0.01115975348027 0.029626545939085 16 0.011249973706768 -0.0037450321315102 0.015596325114303 16 0.0037380363987272 -0.011253237197072 0.015595469491987 16 0.0093636977488961 -0.012851792736334 0.02879001666985 16 0.013015165083291 -0.008966918252123 0.028130578004284 16 0.0099291178051743 -0.0037194597618624 0.013481876108284 16 0.0037165879346098 -0.0099306386652823 0.013481692404872 16 0.0083582753654076 -0.003788450939247 0.011203090842684 16 0.0037874569592362 -0.0083588699362647 0.011203052788177 16 0.01014536177502 -0.010313931442112 0.022744156083031 16 0.0068806704323076 -0.0027656113030482 0.0081908289432826 16 0.0027654179459891 -0.0068807758292608 0.0081908225081814 16 0.0023397111667636 -0.0023397113154947 0.0014550574392565 16 0.013275984087023 -0.009266425737045 0.030571604393365 16 0.012334641048652 -0.0078146868455718 0.022796778873777 16 0.0076374869481648 -0.012454145023011 0.022769793676485 16 0.0071347626024824 -0.010036856621867 0.017325764181858 16 0.010023039044549 -0.0071525544853182 0.017326839197288 16 0.011248669075359 -0.0077270414971996 0.020243652462113 16 0.0075675642849389 -0.011368466834717 0.020233516406678 16 0.012625292969208 -0.0084972993185786 0.025147533859711 16 0.0097729456958384 -0.0059105502529602 0.015441641746184 16 0.0059012563597327 -0.009779281471822 0.015440800306456 16 0.008591240630584 -0.012628369118604 0.025401811765537 16 0.01627052621582 -0.0019495446129698 0.027642876676209 16 0.0055773629301859 -0.0085701432443775 0.013305248277138 16 0.0085673779133964 -0.0055811393897821 0.013305465968329 16 0.0098217957019754 -0.013149347561529 0.032008790844085 16 0.012265826468357 -0.010989941375886 0.032397053112351 16 0.011291103386076 -0.0018456208345706 0.01419444401089 16 0.0018434101143582 -0.011291853549283 0.014194203942445 16 0.014069585541811 -0.0058488239443512 0.024173965829745 16 0.012517744954497 -0.002467800769387 0.016700065246297 16 0.0024442570841014 -0.012525778933081 0.016696258320126 16 0.0024855843447266 -0.016147522890575 0.027832998061215 16 0.0059300639953964 -0.014188863688332 0.024771546627925 16 0.0075535569889814 -0.0075591300716286 0.014306337216609 16 0.010055367945226 -0.0018591339124912 0.01225765630751 16 0.014347630468709 -0.0083177155119898 0.032869125460133 16 0.0018579577219742 -0.010055799632198 0.012257562442334 16 0.0092148741817435 -0.0092922125655617 0.019123752836844 16 0.015438591337167 -0.0038162309143715 0.025910681124999 16 0.014449357477626 -0.0064410217798158 0.027206860650616 16 0.015372113861256 -0.0061913451400565 0.032501426386109 16 0.0030163521005405 -0.015678998876373 0.025594484613648 16 0.0027907016800207 -0.013885057868704 0.019611789232109 16 0.013787556067844 -0.0028854864982494 0.019489368437076 16 0.005011734995185 -0.015322558429877 0.028327835488012 16 0.015065637094643 -0.0028458277977563 0.022758795075994 16 0.0028886694752014 -0.014978694657074 0.022531848489249 16 0.0023994766471755 -0.01636635228264 0.030423522547633 16 0.010919892550336 -0.01097088773737 0.02668522269199 16 0.016470041062299 -0.0019543396007793 0.030621299288307 16 0.015744736158315 -0.0041292581890072 0.028969649993132 16 0.007432369349459 -0.014371272472706 0.030034783109166 16 0.0074189930257876 -0.013967633115655 0.027656130272335 16 0.016007010631694 -0.0017131856399225 0.025117784530601 16 0.01665005593425 -0.00184349984284 0.032944124462655 16 0.0073965866749882 -0.014791674054205 0.032477628404384 16 0.0049672374091909 -0.015733688374982 0.031607261016803 16 0.0055636519223311 -0.0017951407323006 0.0055382195383785 16 0.0017950976384485 -0.0055636716835831 0.0055382184658087 16 0.0027058237553482 -0.016528421007299 0.033150309371652 16 -0.0082613485785636 0.0082717383605922 -0.016213415316446 21 -0.006668436898633 0.0066699647114875 -0.012039983500877 21 -0.0056915648898041 0.0056917777372524 -0.0096276979596765 21 -0.014759311331099 0.0068265892290299 -0.030410378031282 21 -0.0043931326607768 0.0043932087599368 -0.0064826153461334 21 -0.016041060489846 0.0042476371618292 -0.03218407243256 21 -0.0032154180913624 0.0032154287615753 -0.0036239331905459 21 -0.012972100729797 0.0059418690885288 -0.021246307883323 21 -0.0054627536768092 0.01332456191408 -0.021402863101846 21 -0.011781484189247 0.0053378446511886 -0.018095608953025 21 -0.0052463721865454 0.011834379192803 -0.018087977293421 21 -0.011472693372095 0.011159753480252 -0.029626545939085 21 -0.011249973706768 0.00374503213151 -0.015596325114303 21 -0.0037380363987274 0.011253237197071 -0.015595469491987 21 -0.0093636977489121 0.012851792736322 -0.028790016669849 21 -0.013015165083302 0.0089669182521091 -0.028130578004284 21 -0.0099291178051745 0.0037194597618623 -0.013481876108285 21 -0.0037165879346099 0.0099306386652823 -0.013481692404872 21 -0.0083582753654078 0.003788450939247 -0.011203090842684 21 -0.0037874569592363 0.0083588699362647 -0.011203052788177 21 -0.010145361775024 0.010313931442108 -0.022744156083031 21 -0.0068806704323078 0.0027656113030482 -0.0081908289432829 21 -0.0027654179459891 0.0068807758292608 -0.0081908225081815 21 -0.0023397111667635 0.0023397113154948 -0.0014550574392565 21 -0.013275984087038 0.009266425737024 -0.030571604393365 21 -0.012334641048655 0.0078146868455671 -0.022796778873777 21 -0.0076374869481693 0.012454145023008 -0.022769793676485 21 -0.007134762602483 0.010036856621866 -0.017325764181858 21 -0.010023039044549 0.0071525544853176 -0.017326839197288 21 -0.011248669075361 0.007727041497197 -0.020243652462113 21 -0.0075675642849413 0.011368466834715 -0.020233516406678 21 -0.012625292969213 0.0084972993185707 -0.02514753385971 21 -0.0097729456958387 0.0059105502529599 -0.015441641746184 21 -0.005901256359733 0.0097792814718218 -0.015440800306456 21 -0.0085912406305926 0.012628369118597 -0.025401811765537 21 -0.016270526215821 0.0019495446129657 -0.027642876676208 21 -0.005577362930186 0.0085701432443775 -0.013305248277138 21 -0.0085673779133966 0.005581139389782 -0.013305465968329 21 -0.0098217957020027 0.013149347561508 -0.032008790844085 21 -0.012265826468383 0.010989941375857 -0.03239705311235 21 -0.011291103386076 0.0018456208345706 -0.01419444401089 21 -0.0018434101143583 0.011291853549283 -0.014194203942445 21 -0.014069585541814 0.0058488239443457 -0.024173965829745 21 -0.012517744954497 0.0024678007693866 -0.016700065246298 21 -0.0024442570841018 0.012525778933081 -0.016696258320126 21 -0.0024855843447317 0.016147522890573 -0.027832998061215 21 -0.0059300639954027 0.014188863688329 -0.024771546627925 21 -0.0075535569889816 0.0075591300716284 -0.014306337216609 21 -0.010055367945226 0.0018591339124912 -0.01225765630751 21 -0.014347630468727 0.0083177155119604 -0.032869125460133 21 -0.0018579577219743 0.010055799632198 -0.012257562442334 21 -0.0092148741817452 0.00929221256556 -0.019123752836844 21 -0.01543859133717 0.0038162309143657 -0.025910681124999 21 -0.014449357477632 0.0064410217798052 -0.027206860650615 21 -0.015372113861266 0.0061913451400342 -0.032501426386109 21 -0.0030163521005449 0.015678998876371 -0.025594484613648 21 -0.0027907016800219 0.013885057868704 -0.019611789232109 21 -0.013787556067844 0.0028854864982482 -0.019489368437076 21 -0.0050117349951956 0.015322558429872 -0.028327835488012 21 -0.015065637094644 0.0028458277977538 -0.022758795075994 21 -0.0028886694752039 0.014978694657073 -0.022531848489249 21 -0.002399476647182 0.016366352282639 -0.030423522547632 21 -0.010919892550347 0.01097088773736 -0.02668522269199 21 -0.0164700410623 0.0019543396007733 -0.030621299288307 21 -0.015744736158318 0.0041292581889971 -0.028969649993132 21 -0.0074323693494772 0.014371272472696 -0.030034783109166 21 -0.0074189930258002 0.013967633115647 -0.027656130272334 21 -0.016007010631695 0.0017131856399201 -0.025117784530601 21 -0.016650055934251 0.0018434998428334 -0.032944124462655 21 -0.0073965866750131 0.014791674054192 -0.032477628404384 21 -0.0049672374092065 0.015733688374976 -0.031607261016803 21 -0.0055636519223312 0.0017951407323007 -0.0055382195383788 21 -0.0017950976384485 0.0055636716835831 -0.0055382184658088 21 -0.0027058237553584 0.016528421007297 -0.033150309371652 21 -0.0082717383605816 -0.008261348578553 -0.016213415316417 26 -0.0066699647114794 -0.0066684368986249 -0.012039983500857 26 -0.0056917777372462 -0.005691564889798 -0.0096276979596615 26 -0.0068265892290297 -0.014759311331098 -0.030410378031276 26 -0.0043932087599328 -0.0043931326607728 -0.0064826153461238 26 -0.0042476371618297 -0.016041060489846 -0.032184072432558 26 -0.0032154287615732 -0.0032154180913603 -0.0036239331905407 26 -0.0059418690885262 -0.012972100729792 -0.021246307883307 26 -0.013324561914064 -0.0054627536768024 -0.021402863101799 26 -0.0053378446511852 -0.01178148418924 -0.018095608953008 26 -0.011834379192783 -0.0052463721865365 -0.018087977293376 26 -0.01115975348025 -0.011472693372094 -0.029626545939072 26 -0.0037450321315079 -0.011249973706762 -0.015596325114291 26 -0.011253237197049 -0.0037380363987198 -0.015595469491943 26 -0.012851792736319 -0.0093636977489105 -0.028790016669832 26 -0.0089669182521077 -0.0130151650833 -0.028130578004271 26 -0.00371945976186 -0.0099291178051682 -0.013481876108273 26 -0.0099306386652633 -0.0037165879346029 -0.013481692404836 26 -0.0037884509392444 -0.0083582753654019 -0.011203090842673 26 -0.0083588699362507 -0.0037874569592301 -0.011203052788151 26 -0.010313931442101 -0.010145361775018 -0.022744156083003 26 -0.0027656113030468 -0.0068806704323041 -0.0081908289432763 26 -0.0068807758292503 -0.002765417945985 -0.0081908225081631 26 -0.002339711315494 -0.0023397111667627 -0.0014550574392544 26 -0.009266425737023 -0.013275984087037 -0.030571604393356 26 -0.0078146868455634 -0.01233464104865 -0.022796778873757 26 -0.012454145022998 -0.0076374869481626 -0.022769793676446 26 -0.010036856621851 -0.0071347626024723 -0.01732576418182 26 -0.0071525544853105 -0.01002303904454 -0.017326839197263 26 -0.0077270414971916 -0.011248669075353 -0.02024365246209 26 -0.011368466834703 -0.0075675642849327 -0.02023351640664 26 -0.0084972993185681 -0.01262529296921 -0.025147533859693 26 -0.0059105502529545 -0.0097729456958298 -0.015441641746164 26 -0.0097792814718054 -0.0059012563597231 -0.01544080030642 26 -0.012628369118592 -0.0085912406305887 -0.025401811765509 26 -0.0019495446129658 -0.016270526215821 -0.027642876676206 26 -0.008570143244364 -0.0055773629301773 -0.013305248277109 26 -0.0055811393897767 -0.0085673779133883 -0.013305465968311 26 -0.013149347561507 -0.0098217957020022 -0.032008790844076 26 -0.010989941375856 -0.012265826468383 -0.032397053112344 26 -0.00184562083457 -0.011291103386073 -0.014194444010884 26 -0.011291853549258 -0.0018434101143541 -0.0141942039424 26 -0.0058488239443442 -0.014069585541811 -0.024173965829733 26 -0.0024678007693858 -0.012517744954493 -0.01670006524629 26 -0.012525778933054 -0.0024442570840964 -0.016696258320074 26 -0.01614752289057 -0.0024855843447304 -0.027832998061173 26 -0.014188863688321 -0.0059300639953986 -0.024771546627886 26 -0.0075591300716187 -0.0075535569889719 -0.014306337216583 26 -0.0018591339124906 -0.010055367945223 -0.012257656307504 26 -0.0083177155119605 -0.014347630468726 -0.032869125460129 26 -0.010055799632177 -0.0018579577219704 -0.012257562442299 26 -0.0092922125655505 -0.0092148741817358 -0.019123752836813 26 -0.0038162309143654 -0.015438591337169 -0.025910681124993 26 -0.0064410217798044 -0.01444935747763 -0.027206860650606 26 -0.0061913451400348 -0.015372113861265 -0.032501426386106 26 -0.015678998876363 -0.0030163521005424 -0.025594484613597 26 -0.013885057868679 -0.0027907016800165 -0.01961178923205 26 -0.0028854864982474 -0.013787556067841 -0.019489368437068 26 -0.015322558429869 -0.0050117349951937 -0.028327835487981 26 -0.0028458277977533 -0.015065637094642 -0.022758795075988 26 -0.014978694657056 -0.0028886694751998 -0.022531848489189 26 -0.016366352282637 -0.0023994766471812 -0.030423522547606 26 -0.010970887737357 -0.010919892550344 -0.02668522269197 26 -0.0019543396007735 -0.0164700410623 -0.030621299288306 26 -0.0041292581889971 -0.015744736158318 -0.028969649993128 26 -0.014371272472693 -0.0074323693494758 -0.030034783109148 26 -0.013967633115644 -0.0074189930257979 -0.027656130272309 26 -0.00171318563992 -0.016007010631694 -0.025117784530598 26 -0.0018434998428337 -0.016650055934251 -0.032944124462654 26 -0.014791674054191 -0.0073965866750124 -0.032477628404375 26 -0.015733688374974 -0.0049672374092057 -0.031607261016787 26 -0.0017951407323 -0.005563651922329 -0.0055382195383751 26 -0.0055636716835757 -0.0017950976384462 -0.0055382184657964 26 -0.016528421007296 -0.002705823755358 -0.033150309371641 26 0.0082613485785531 -0.0082717383605817 -0.016213415316417 31 0.0066684368986249 -0.0066699647114793 -0.012039983500857 31 0.0056915648897979 -0.0056917777372461 -0.0096276979596613 31 0.014759311331098 -0.0068265892290294 -0.030410378031276 31 0.0043931326607727 -0.0043932087599327 -0.0064826153461236 31 0.016041060489846 -0.0042476371618291 -0.032184072432558 31 0.0032154180913603 -0.0032154287615731 -0.0036239331905406 31 0.012972100729792 -0.0059418690885263 -0.021246307883307 31 0.0054627536768024 -0.013324561914064 -0.021402863101799 31 0.01178148418924 -0.0053378446511853 -0.018095608953009 31 0.0052463721865365 -0.011834379192783 -0.018087977293376 31 0.011472693372094 -0.011159753480251 -0.029626545939072 31 0.011249973706762 -0.003745032131508 -0.015596325114292 31 0.0037380363987198 -0.011253237197049 -0.015595469491943 31 0.0093636977489102 -0.01285179273632 -0.028790016669832 31 0.0130151650833 -0.0089669182521078 -0.028130578004271 31 0.0099291178051683 -0.00371945976186 -0.013481876108273 31 0.0037165879346028 -0.0099306386652632 -0.013481692404836 31 0.008358275365402 -0.0037884509392444 -0.011203090842673 31 0.00378745695923 -0.0083588699362505 -0.011203052788151 31 0.010145361775018 -0.010313931442101 -0.022744156083003 31 0.0068806704323041 -0.0027656113030468 -0.0081908289432763 31 0.002765417945985 -0.0068807758292502 -0.0081908225081627 31 0.0023397111667628 -0.0023397113154939 -0.0014550574392544 31 0.013275984087037 -0.0092664257370231 -0.030571604393356 31 0.01233464104865 -0.0078146868455636 -0.022796778873757 31 0.0076374869481626 -0.012454145022998 -0.022769793676446 31 0.0071347626024723 -0.010036856621851 -0.01732576418182 31 0.01002303904454 -0.0071525544853106 -0.017326839197263 31 0.011248669075354 -0.0077270414971918 -0.020243652462091 31 0.0075675642849327 -0.011368466834703 -0.02023351640664 31 0.01262529296921 -0.0084972993185683 -0.025147533859693 31 0.00977294569583 -0.0059105502529546 -0.015441641746165 31 0.0059012563597231 -0.0097792814718054 -0.01544080030642 31 0.0085912406305886 -0.012628369118592 -0.025401811765509 31 0.016270526215821 -0.0019495446129657 -0.027642876676207 31 0.0055773629301773 -0.008570143244364 -0.013305248277109 31 0.0085673779133884 -0.0055811393897767 -0.013305465968311 31 0.0098217957020017 -0.013149347561507 -0.032008790844076 31 0.012265826468383 -0.010989941375856 -0.032397053112344 31 0.011291103386073 -0.0018456208345701 -0.014194444010885 31 0.0018434101143541 -0.011291853549257 -0.0141942039424 31 0.014069585541811 -0.0058488239443443 -0.024173965829733 31 0.012517744954494 -0.0024678007693858 -0.01670006524629 31 0.0024442570840964 -0.012525778933054 -0.016696258320074 31 0.0024855843447303 -0.01614752289057 -0.027832998061173 31 0.0059300639953985 -0.014188863688321 -0.024771546627886 31 0.0075535569889719 -0.0075591300716188 -0.014306337216584 31 0.010055367945223 -0.0018591339124906 -0.012257656307505 31 0.014347630468726 -0.00831771551196 -0.032869125460129 31 0.0018579577219704 -0.010055799632177 -0.012257562442298 31 0.0092148741817359 -0.0092922125655506 -0.019123752836814 31 0.015438591337169 -0.0038162309143654 -0.025910681124994 31 0.01444935747763 -0.0064410217798044 -0.027206860650606 31 0.015372113861266 -0.006191345140034 -0.032501426386106 31 0.0030163521005424 -0.015678998876363 -0.025594484613597 31 0.0027907016800165 -0.013885057868679 -0.01961178923205 31 0.013787556067841 -0.0028854864982474 -0.019489368437069 31 0.0050117349951937 -0.015322558429869 -0.028327835487982 31 0.015065637094642 -0.0028458277977534 -0.022758795075989 31 0.0028886694751998 -0.014978694657056 -0.022531848489189 31 0.0023994766471813 -0.016366352282637 -0.030423522547606 31 0.010919892550344 -0.010970887737357 -0.026685222691971 31 0.0164700410623 -0.0019543396007733 -0.030621299288307 31 0.015744736158318 -0.004129258188997 -0.028969649993128 31 0.0074323693494756 -0.014371272472694 -0.030034783109148 31 0.0074189930257978 -0.013967633115644 -0.027656130272309 31 0.016007010631694 -0.00171318563992 -0.025117784530599 31 0.016650055934251 -0.0018434998428333 -0.032944124462654 31 0.0073965866750122 -0.014791674054191 -0.032477628404375 31 0.0049672374092056 -0.015733688374974 -0.031607261016787 31 0.005563651922329 -0.0017951407323 -0.0055382195383751 31 0.0017950976384462 -0.0055636716835756 -0.0055382184657961 31 0.002705823755358 -0.016528421007296 -0.033150309371641 31 0.0082717383605922 0.0082613485785636 -0.016213415316446 36 0.0066699647114875 0.006668436898633 -0.012039983500877 36 0.0056917777372523 0.0056915648898041 -0.0096276979596764 36 0.006826589229031 0.0147593113311 -0.030410378031295 36 0.004393208759937 0.0043931326607765 -0.0064826153461333 36 0.0042476371618299 0.016041060489848 -0.032184072432573 36 0.003215428761576 0.0032154180913618 -0.0036239331905459 36 0.0059418690885336 0.012972100729807 -0.021246307883352 36 0.013324561914069 0.0054627536768045 -0.021402863101814 36 0.0053378446511942 0.011781484189259 -0.018095608953053 36 0.011834379192791 0.0052463721865398 -0.018087977293392 36 0.011159753480252 0.011472693372095 -0.029626545939086 36 0.0037450321315155 0.011249973706785 -0.015596325114336 36 0.011253237197055 0.0037380363987219 -0.015595469491955 36 0.012851792736321 0.0093636977489115 -0.028790016669844 36 0.0089669182521101 0.013015165083303 -0.028130578004293 36 0.0037194597618671 0.0099291178051873 -0.013481876108308 36 0.0099306386652695 0.0037165879346052 -0.013481692404848 36 0.0037884509392507 0.0083582753654161 -0.011203090842699 36 0.0083588699362564 0.0037874569592326 -0.011203052788162 36 0.010313931442108 0.010145361775024 -0.022744156083031 36 0.0027656113030511 0.0068806704323146 -0.008190828943295 36 0.0068807758292539 0.0027654179459862 -0.0081908225081692 36 0.0023397113154971 0.0023397111667612 -0.0014550574392565 36 0.0092664257370247 0.013275984087039 -0.030571604393372 36 0.0078146868455703 0.01233464104866 -0.022796778873796 36 0.012454145023003 0.0076374869481661 -0.022769793676467 36 0.010036856621861 0.0071347626024794 -0.017325764181845 36 0.0071525544853213 0.010023039044555 -0.017326839197301 36 0.0077270414972005 0.011248669075366 -0.020243652462128 36 0.01136846683471 0.0075675642849379 -0.020233516406663 36 0.0084972993185725 0.012625292969216 -0.025147533859723 36 0.0059105502529645 0.0097729456958462 -0.0154416417462 36 0.0097792814718143 0.0059012563597285 -0.015440800306439 36 0.012628369118595 0.008591240630591 -0.025401811765526 36 0.0019495446129669 0.016270526215824 -0.02764287667625 36 0.0085701432443722 0.0055773629301826 -0.013305248277127 36 0.0055811393897854 0.0085673779134018 -0.01330546596834 36 0.013149347561508 0.0098217957020025 -0.032008790844083 36 0.010989941375857 0.012265826468384 -0.032397053112352 36 0.0018456208345742 0.011291103386099 -0.014194444010929 36 0.011291853549261 0.0018434101143547 -0.014194203942406 36 0.005848823944349 0.014069585541821 -0.024173965829774 36 0.0024678007693912 0.01251774495452 -0.016700065246341 36 0.012525778933058 0.0024442570840973 -0.016696258320082 36 0.01614752289057 0.0024855843447305 -0.027832998061177 36 0.014188863688323 0.0059300639953998 -0.024771546627898 36 0.0075591300716285 0.0075535569889816 -0.014306337216609 36 0.0018591339124945 0.010055367945244 -0.01225765630754 36 0.0083177155119609 0.014347630468727 -0.032869125460138 36 0.01005579963218 0.001857957721971 -0.012257562442304 36 0.00929221256556 0.0092148741817452 -0.019123752836844 36 0.0038162309143681 0.015438591337176 -0.025910681125037 36 0.0064410217798071 0.014449357477635 -0.027206860650637 36 0.0061913451400349 0.015372113861267 -0.032501426386118 36 0.015678998876364 0.0030163521005427 -0.025594484613603 36 0.013885057868682 0.0027907016800173 -0.019611789232059 36 0.002885486498253 0.013787556067866 -0.019489368437127 36 0.01532255842987 0.0050117349951941 -0.028327835487988 36 0.0028458277977573 0.015065637094659 -0.022758795076048 36 0.014978694657058 0.0028886694752003 -0.022531848489197 36 0.016366352282637 0.0023994766471813 -0.030423522547609 36 0.01097088773736 0.010919892550347 -0.026685222691991 36 0.0019543396007739 0.016470041062302 -0.030621299288333 36 0.0041292581889985 0.015744736158321 -0.028969649993159 36 0.014371272472694 0.0074323693494762 -0.030034783109155 36 0.013967633115645 0.0074189930257988 -0.02765613027232 36 0.0017131856399218 0.016007010631704 -0.025117784530656 36 0.0018434998428336 0.016650055934252 -0.032944124462667 36 0.014791674054191 0.0073965866750126 -0.032477628404378 36 0.015733688374975 0.0049672374092058 -0.03160726101679 36 0.0017951407323026 0.0055636519223364 -0.0055382195383877 36 0.0055636716835779 0.0017950976384466 -0.0055382184657997 36 0.016528421007296 0.002705823755358 -0.033150309371642 36 0.00025923949145075 -0.00011663598334809 0.035 40 0.0085 0.00074365363996819 0.035 40 -0.00070182862068647 0.0080219378420471 0.035 40 -0.0085 -0.00074365363996819 0.035 40 0.0048940229438203 -0.0069893891121899 0.035 40 -0.003423356312118 -0.0073740332172317 0.035 40 0.006335979884772 0.0075509267902778 0.035 40 -0.0075509267902778 0.006335979884772 0.035 40 0.00098695040275615 -0.011280894723907 0.035 40 0.0102630166097 -0.0047857232408861 0.035 40 -0.0092760662069374 -0.0064951714830238 0.035 40 0.01084480654801 0.0052366691634954 0.035 40 -0.0051736224896634 0.01104836266409 0.035 40 0.0031695040345959 0.011783543489627 0.035 40 -0.011589387625685 0.0032739314979641 0.035 40 0.0023216265175408 0.0044856466327688 0.035 40 -0.003159330010412 0.0038738604624045 0.035 40 0.0043312672665006 -0.0018365600450788 0.035 40 0.000350603068315 -0.0050135071089525 0.035 40 -0.0038951699743295 -0.0025071276223676 0.035 40 0.01260211498031 -0.0011444979005262 0.035 40 -0.012211920562194 -0.0033154447068113 0.035 40 0.0052969906154251 -0.011461873444633 0.035 40 -0.0073833162246486 -0.010613065044365 0.035 40 0.0090974589982076 -0.0090003361873647 0.035 40 -0.0032457902817501 -0.01237879869603 0.035 40 -0.0011212164879929 0.012815563100514 0.035 40 -0.0066358177087867 0.0026098716304321 0.035 40 0.0060818052746216 0.0037225194684739 0.035 40 0.0099667589025991 0.0087401185155441 0.035 40 -0.0088743336470254 0.0098635844525788 0.035 40 -0.011362608127478 0.0068568294609599 0.035 40 0.0070050781279051 0.011290844511283 0.035 40 0.013192436792901 0.0025435317475211 0.035 40 -0.013433693687571 0.00021404717692894 0.035 40 -0.0043078829629141 0.0077960727463514 0.035 40 0.0028883134588558 0.0084264385080104 0.035 40 0.0037430862799024 0.0014226769775514 0.035 40 -0.0037017488219352 0.00085555860346746 0.035 40 -0.0073817518214824 -0.0037931910559562 0.035 40 0.0017540803492459 -0.008250115506016 0.035 40 0.0080354519434517 -0.0026127644415714 0.035 40 -0.010058975039919 -0.0097915852195728 0.035 40 0.0033545776185592 -0.013611406813819 0.035 40 -0.012565835992748 -0.0062048339117576 0.035 40 0.013500751887915 -0.0038678964616523 0.035 40 -0.00096382396370058 -0.013904939123189 0.035 40 0.011560118536899 -0.0077871655989385 0.035 40 -0.0037960289523828 0.013652349005182 0.035 40 0.0013658988617245 0.014105082094652 0.035 40 -0.00053618978091126 0.0052665799530088 0.035 40 0.0079630784705792 -0.0063760619642431 0.035 40 -0.0015217520444934 -0.010046077417364 0.035 40 -0.0062105369724264 -0.0068556427252029 0.035 40 0.0082921217902417 -0.011747072540318 0.035 40 -0.0060113534293526 -0.012969232333117 0.035 40 0.0031803773811089 -0.0047673113972251 0.035 40 0.0016313409812773 -0.002329796370726 0.035 40 -0.0010182088067713 -0.002622498355167 0.035 40 -0.0022624064209363 -0.0049828488150487 0.035 40 -0.00066790147661586 0.0025101530255343 0.035 40 0.0090729971649786 0.0033851748138598 0.035 40 -0.0095229873886403 0.0017582369783214 0.035 40 -0.0024446561290734 0.01063960073593 0.035 40 0.00057139846570803 0.010917908976305 0.035 40 0.012401977820772 0.0077089511546253 0.035 40 -0.0075549470530933 0.012205780073282 0.035 40 0.0052922197474014 0.013348724191032 0.035 40 -0.013558121437294 0.0054342945177013 0.035 40 -0.00088606319097088 -0.0073513435630063 0.035 40 0.0057501479099318 -0.0044954481412524 0.035 40 0.0059613427044309 7.4928097341667E-05 0.035 40 -0.0058837876412475 -0.00096138652588127 0.035 40 0.013518413532431 0.0051361124230394 0.035 40 -0.014205345936592 0.0027103468664922 0.035 40 0.0065152644405302 -0.0090721614638847 0.035 40 -0.005098976817759 -0.0097905841661255 0.035 40 -0.011083292695448 -0.00086468594348582 0.035 40 0.011065063713722 0.0010730441580348 0.035 40 -0.0099897960704886 -0.0027276700725223 0.035 40 0.0043228968628469 -0.0094377561702408 0.035 40 0.010333116519484 -0.00098332639874304 0.035 40 0.0083842126126047 0.0059921000012437 0.035 40 -0.0092973548294103 0.0044451632967046 0.035 40 0.0045353148389714 0.0057478348617619 0.035 40 -0.0054645142650433 0.0048729631779666 0.035 40 0.0050018261209646 0.0097194286274466 0.035 40 -0.0066096138093171 0.008701165590999 0.035 40 0.014589084528392 0.00051260717288095 0.035 40 -0.014456456854344 -0.0020285484240692 0.035 40 0.006251606492146 -0.013503511843296 0.035 40 -0.0086367972218429 -0.012334624736972 0.035 40 0.0094837592675833 0.011289932969683 0.035 40 -0.011314454249168 0.009453827944135 0.035 40 -0.011929212927399 -0.0083837959957963 0.035 40 0.0105799688446 -0.010496943844872 0.035 40 -0.0038313773376436 -0.014457151956671 0.035 40 -0.0013142999397382 0.015021126168002 0.035 40 -0.0047022433097429 -0.0050285830235671 0.035 40 -0.014390143952772 -0.0040630376998977 0.035 40 0.014886736639565 -0.0014903612419277 0.035 40 0.001265944615589 -0.014507794492425 0.035 40 0.013204218690439 -0.0061510255476089 0.035 40 -0.0076887623115341 -0.0085915040139182 0.035 40 0.0015954367154633 0.001966365074402 0.035 40 -0.0092337155254721 0.0077167144890656 0.035 40 0.0077167144890656 0.0092337155254721 0.035 40 0.011274882874017 -0.0028677490236203 0.035 40 0.0031501074537528 -0.011194092639073 0.035 40 -0.010523575914875 -0.0047100929906882 0.035 40 -0.0061560570598725 0.01384616388479 0.035 40 0.0036410932966627 0.014713458154034 0.035 40 -0.0018047579176402 -0.00037611006637618 0.035 40 0.0021264882629152 -0.00024705369999498 0.035 40 0.0015362407359349 0.0064074129782865 0.035 40 -0.0025340128896783 0.0060697632579304 0.035 40 0.01118939864494 0.0031672598596615 0.035 40 -0.011569395439915 0.0011761233817256 0.035 40 -0.00034229051775831 4.7018121872357E-05 -0.035 42 -0.0084999999999999 0.00074365363996818 -0.035 42 0.0013650532292193 0.0083803495896855 -0.035 42 0.0085000000000001 -0.00074365363996819 -0.035 42 -0.0048940229438203 -0.0069893891121898 -0.035 42 0.0034233563121181 -0.0073740332172318 -0.035 42 0.0075509267902778 0.006335979884772 -0.035 42 -0.006335979884772 0.0075509267902778 -0.035 42 -0.00098695040275618 -0.011280894723907 -0.035 42 -0.0102630166097 -0.0047857232408861 -0.035 42 0.0092760662069375 -0.0064951714830237 -0.035 42 -0.01084480654801 0.0052366691634954 -0.035 42 0.0051736224896635 0.01104836266409 -0.035 42 -0.003218036934013 0.011668938252711 -0.035 42 0.011589387625685 0.0032739314979642 -0.035 42 -0.004014353367231 0.003065280157509 -0.035 42 0.003065280157509 0.0040143533672312 -0.035 42 -0.0044930021442868 -0.0021361544090211 -0.035 42 -0.00035060306831495 -0.0050135071089526 -0.035 42 0.0038951699743296 -0.0025071276223677 -0.035 42 -0.012652664538049 -0.00093388560304441 -0.035 42 -0.0052969906154251 -0.011461873444633 -0.035 42 0.012211920562194 -0.0033154447068113 -0.035 42 0.0073833162246488 -0.010613065044365 -0.035 42 -0.0090974589982076 -0.0090003361873646 -0.035 42 0.0032457902817502 -0.01237879869603 -0.035 42 0.0011212164879929 0.012815563100514 -0.035 42 -0.0014198466054339 0.0045307230213813 -0.035 42 0.0066358177087867 0.0026098716304321 -0.035 42 0.0088743336470254 0.0098635844525788 -0.035 42 -0.0098635844525788 0.0088743336470254 -0.035 42 -0.0070050781279051 0.011290844511283 -0.035 42 0.011362608127478 0.0068568294609599 -0.035 42 -0.0030941855157209 0.0082937251048878 -0.035 42 -0.012943726027254 0.0023592844419938 -0.035 42 0.013433693687571 0.00021404717692895 -0.035 42 0.0041726290813432 0.0074962780318934 -0.035 42 -0.0074410915744648 0.0041546341831715 -0.035 42 0.003712668112448 0.00085866723656208 -0.035 42 -0.0017540803492459 -0.008250115506016 -0.035 42 0.0073817518214824 -0.0037931910559562 -0.035 42 -0.0080088735696441 -0.0025833832175723 -0.035 42 0.010058975039919 -0.0097915852195727 -0.035 42 -0.0033545776185592 -0.013611406813819 -0.035 42 0.012565835992748 -0.0062048339117576 -0.035 42 -0.013504436258013 -0.0038525457111069 -0.035 42 0.00096382396370064 -0.013904939123189 -0.035 42 -0.011560118536899 -0.0077871655989385 -0.035 42 0.0037960289523828 0.013652349005182 -0.035 42 -0.0013694362450639 0.014096728943274 -0.035 42 -0.0079603663916193 -0.0063730638801615 -0.035 42 0.0015217520444934 -0.010046077417364 -0.035 42 0.0062105369724264 -0.006855642725203 -0.035 42 -0.0055869808361845 0.00060957225327739 -0.035 42 -0.0082921217902417 -0.011747072540318 -0.035 42 0.0060113534293527 -0.012969232333117 -0.035 42 -0.003174411361496 -0.0048603312535225 -0.035 42 -0.0016313409812772 -0.002329796370726 -0.035 42 0.0010043669690534 -0.0025952226709637 -0.035 42 0.0022600994479833 -0.0049783028676816 -0.035 42 -0.0028626962513717 0.00042517399309052 -0.035 42 0.0095229873886404 0.0017582369783214 -0.035 42 0.00087620092964579 0.0025603402338561 -0.035 42 0.0025497584590119 0.010516284087937 -0.035 42 -0.00049173316204463 0.010402273779999 -0.035 42 -0.010410537826456 0.002572606042536 -0.035 42 0.0075549470530933 0.012205780073282 -0.035 42 -0.012205780073282 0.0075549470530932 -0.035 42 -0.0052922197474014 0.013348724191032 -0.035 42 0.013558121437294 0.0054342945177013 -0.035 42 -0.004259199356454 0.0057247732514085 -0.035 42 0.00088606319097083 -0.0073513435630064 -0.035 42 -0.0057061352821733 -0.0045884643744934 -0.035 42 0.0058837876412476 -0.00096138652588132 -0.035 42 -0.013453136255864 0.0051405787294595 -0.035 42 0.014205345936592 0.0027103468664922 -0.035 42 -0.006515146777796 -0.0090720313929037 -0.035 42 0.0050989768177591 -0.0097905841661255 -0.035 42 0.011083292695448 -0.00086468594348586 -0.035 42 0.0099897960704887 -0.0027276700725224 -0.035 42 -0.0043228733303 -0.0094377301560446 -0.035 42 -0.010270269560103 -0.0011208931334083 -0.035 42 0.0092973548294103 0.0044451632967046 -0.035 42 0.0016022634507036 0.0061007658924657 -0.035 42 0.0054645142650433 0.0048729631779665 -0.035 42 -0.0050018261209645 0.0097194286274466 -0.035 42 -0.008701165590999 0.0066096138093171 -0.035 42 0.0066096138093171 0.008701165590999 -0.035 42 -0.014824070734542 0.0005217950060042 -0.035 42 0.014456456854344 -0.0020285484240692 -0.035 42 -0.0015810185320265 0.0022776205533354 -0.035 42 -0.006251606492146 -0.013503511843296 -0.035 42 0.0086367972218429 -0.012334624736972 -0.035 42 -0.0094714765949618 0.011305910961526 -0.035 42 0.011314454249168 0.009453827944135 -0.035 42 0.011929212927399 -0.0083837959957963 -0.035 42 -0.0105799688446 -0.010496943844872 -0.035 42 0.0038313773376436 -0.014457151956671 -0.035 42 0.0013140421129642 0.015020517337727 -0.035 42 0.0047022433097429 -0.0050285830235673 -0.035 42 0.014390143952772 -0.0040630376998977 -0.035 42 -0.014944580666362 -0.0014433310656977 -0.035 42 -0.001265944615589 -0.014507794492425 -0.035 42 -0.013204955564458 -0.0061479553974999 -0.035 42 0.0076887623115341 -0.0085915040139182 -0.035 42 0.0092337155254721 0.0077167144890656 -0.035 42 -0.0077167144890656 0.0092337155254721 -0.035 42 -0.011326784776255 -0.0028925654534687 -0.035 42 -0.0031501057385527 -0.011194090742995 -0.035 42 0.010523575914876 -0.0047100929906882 -0.035 42 0.0061560570598725 0.01384616388479 -0.035 42 -0.0036410932966627 0.014713458154034 -0.035 42 0.0018292230935437 -0.00032726494020819 -0.035 42 -0.010955439590372 0.00072415307760904 -0.035 42 -0.00079650161573864 0.0070443273809827 -0.035 42 0.011569395439915 0.0011761233817256 -0.035 42 Edges 184 2 24 1 24 25 1 25 26 1 26 27 1 27 28 1 28 29 1 29 30 1 30 31 1 31 32 1 32 33 1 33 34 1 34 35 1 35 4 1 6 36 2 36 37 2 37 38 2 38 39 2 39 40 2 40 41 2 41 42 2 42 43 2 43 44 2 44 45 2 45 46 2 46 47 2 47 8 2 2 48 3 48 6 3 4 49 4 49 50 4 50 51 4 51 52 4 52 53 4 53 54 4 54 55 4 55 56 4 56 8 4 8 57 8 57 58 8 58 59 8 59 60 8 60 61 8 61 62 8 62 63 8 63 64 8 64 9 8 9 65 9 65 66 9 66 67 9 67 68 9 68 69 9 69 70 9 70 71 9 71 72 9 72 73 9 73 74 9 74 75 9 75 76 9 76 11 9 11 77 10 77 6 10 9 78 13 78 79 13 79 80 13 80 81 13 81 82 13 82 83 13 83 84 13 84 85 13 85 12 13 12 86 14 86 87 14 87 88 14 88 89 14 89 90 14 90 91 14 91 92 14 92 93 14 93 94 14 94 95 14 95 96 14 96 97 14 97 14 14 14 98 15 98 11 15 12 99 18 99 100 18 100 101 18 101 102 18 102 103 18 103 104 18 104 105 18 105 106 18 106 4 18 2 107 20 107 14 20 11 108 22 108 109 22 109 110 22 110 111 22 111 112 22 112 113 22 113 114 22 114 115 22 115 116 22 116 117 22 117 118 22 118 119 22 119 16 22 16 120 23 120 121 23 121 122 23 122 123 23 123 124 23 124 125 23 125 126 23 126 127 23 127 18 23 18 128 24 128 129 24 129 130 24 130 131 24 131 132 24 132 133 24 133 134 24 134 135 24 135 136 24 136 137 24 137 138 24 138 139 24 139 6 24 14 140 27 140 141 27 141 142 27 142 143 27 143 144 27 144 145 27 145 146 27 146 147 27 147 148 27 148 149 27 149 150 27 150 151 27 151 21 27 21 152 28 152 153 28 153 154 28 154 155 28 155 156 28 156 157 28 157 158 28 158 159 28 159 16 28 2 160 32 160 161 32 161 162 32 162 163 32 163 164 32 164 165 32 165 166 32 166 167 32 167 168 32 168 169 32 169 170 32 170 171 32 171 23 32 23 172 33 172 173 33 173 174 33 174 175 33 175 176 33 176 177 33 177 178 33 178 179 33 179 21 33 18 180 38 180 181 38 181 182 38 182 183 38 183 184 38 184 185 38 185 186 38 186 187 38 187 23 38 Triangles 2000 56 193 55 5 199 41 40 5 29 200 28 5 27 207 26 5 207 27 203 5 207 191 192 5 206 39 38 5 39 206 202 5 191 206 192 5 24 210 2 5 210 48 2 5 210 36 6 5 48 210 6 5 208 38 37 5 38 208 206 5 192 206 208 5 26 209 25 5 209 26 207 5 207 192 209 5 220 197 219 5 198 221 218 5 223 41 199 5 197 223 199 5 29 225 200 5 225 198 200 5 194 226 211 5 229 190 193 5 205 231 213 5 231 205 212 5 232 193 190 5 193 232 55 5 193 233 229 5 189 234 219 5 234 189 218 5 235 188 214 5 201 236 235 5 238 189 222 5 189 238 224 5 239 188 235 5 190 241 232 5 243 35 4 5 49 243 4 5 47 244 8 5 244 56 8 5 245 191 207 5 245 203 224 5 203 245 207 5 191 246 206 5 202 246 222 5 246 202 206 5 244 193 56 5 211 228 194 5 217 230 196 5 230 213 196 5 212 228 195 5 218 221 234 5 220 219 234 5 222 199 202 5 200 224 203 5 238 246 191 5 238 222 246 5 245 238 191 5 224 238 245 5 235 236 239 5 189 224 218 5 198 224 200 5 198 218 224 5 222 189 219 5 222 197 199 5 219 197 222 5 42 216 43 5 217 30 31 5 223 42 41 5 42 223 216 5 30 225 29 5 225 30 217 5 54 55 232 5 195 228 216 5 211 216 228 5 205 214 188 5 205 213 214 5 214 213 230 5 221 196 213 5 196 221 198 5 221 213 231 5 195 220 212 5 220 195 197 5 212 220 231 5 195 247 197 5 223 247 216 5 247 223 197 5 247 195 216 5 212 248 228 5 229 249 190 5 249 229 226 5 198 250 196 5 217 250 225 5 250 217 196 5 250 198 225 5 253 31 32 5 194 249 226 5 248 194 228 5 251 205 188 5 205 251 212 5 248 212 251 5 231 220 234 5 221 231 234 5 235 252 201 5 252 235 214 5 204 236 201 5 230 252 214 5 252 230 215 5 215 254 252 5 254 237 252 5 229 233 226 5 203 28 200 5 28 203 27 5 40 202 199 5 202 40 39 5 46 233 47 5 233 193 244 5 244 47 233 5 253 215 230 5 217 253 230 5 253 217 31 5 254 240 237 5 240 34 35 5 35 243 240 5 241 53 54 5 241 54 232 5 188 239 227 5 251 188 227 5 249 227 190 5 252 237 201 5 255 33 34 5 255 240 254 5 240 255 34 5 33 255 254 5 256 36 210 5 24 257 210 5 256 37 36 5 25 257 24 5 37 256 208 5 192 208 256 5 257 25 209 5 209 192 257 5 194 248 249 5 248 227 249 5 248 251 227 5 242 49 50 5 242 243 49 5 204 201 237 5 204 237 242 5 33 253 32 5 253 33 254 5 215 253 254 5 259 44 43 5 259 216 211 5 216 259 43 5 44 259 211 5 260 240 243 5 260 242 237 5 242 260 243 5 240 260 237 5 242 51 204 5 242 50 51 5 52 258 51 5 204 258 236 5 258 204 51 5 257 256 210 5 257 192 256 5 52 53 258 5 53 241 258 5 239 236 258 5 241 239 258 5 227 261 190 5 241 261 239 5 261 241 190 5 261 227 239 5 226 45 211 5 44 211 45 5 46 226 233 5 226 46 45 5 39 283 38 6 283 39 280 6 283 264 266 6 280 264 283 6 284 73 74 6 73 284 281 6 264 284 266 6 264 281 284 6 280 263 264 6 263 281 264 6 285 76 11 6 76 285 268 6 77 285 11 6 36 285 6 6 285 36 268 6 285 77 6 6 58 267 57 6 265 286 277 6 286 273 277 6 271 291 290 6 292 272 289 6 263 298 281 6 279 298 295 6 298 279 281 6 299 263 280 6 299 278 294 6 278 299 280 6 301 273 286 6 41 302 40 6 278 302 274 6 303 71 72 6 303 279 275 6 269 304 287 6 42 305 41 6 306 70 71 6 308 270 288 6 262 309 294 6 309 262 295 6 40 310 39 6 280 310 278 6 310 280 39 6 265 311 286 6 312 72 73 6 312 281 279 6 281 312 73 6 313 262 290 6 262 313 289 6 305 302 41 6 274 302 305 6 303 306 71 6 303 275 306 6 302 310 40 6 302 278 310 6 312 303 72 6 279 303 312 6 292 289 313 6 290 291 313 6 299 309 263 6 299 294 309 6 309 298 263 6 295 298 309 6 301 60 61 6 293 282 287 6 293 287 304 6 282 296 288 6 288 296 308 6 262 294 290 6 274 294 278 6 271 294 274 6 271 290 294 6 295 262 289 6 295 275 279 6 295 272 275 6 289 272 295 6 272 306 275 6 305 271 274 6 270 292 288 6 292 270 272 6 282 288 292 6 291 269 287 6 269 291 271 6 287 282 291 6 273 300 276 6 61 300 301 6 300 273 301 6 316 267 58 6 316 311 265 6 267 316 265 6 68 317 67 6 318 272 270 6 271 319 269 6 321 304 269 6 308 322 270 6 319 321 269 6 318 306 272 6 46 297 45 6 318 70 306 6 70 318 69 6 305 319 271 6 42 319 305 6 319 42 43 6 300 61 62 6 282 292 313 6 291 282 313 6 311 59 60 6 60 301 311 6 311 301 286 6 317 68 322 6 67 307 66 6 317 307 67 6 59 316 58 6 59 311 316 6 320 323 307 6 282 324 296 6 324 282 293 6 265 277 315 6 293 315 277 6 293 304 315 6 46 325 297 6 265 326 267 6 326 265 315 6 326 325 267 6 297 325 326 6 47 325 46 6 326 314 297 6 321 314 304 6 315 304 314 6 315 314 326 6 276 324 273 6 324 277 273 6 296 324 276 6 324 293 277 6 320 307 317 6 317 322 308 6 308 320 317 6 328 296 276 6 328 320 308 6 296 328 308 6 329 44 45 6 329 314 321 6 330 47 8 6 330 57 267 6 57 330 8 6 330 325 47 6 267 325 330 6 327 276 300 6 327 328 276 6 327 320 328 6 44 321 43 6 321 319 43 6 329 321 44 6 66 323 65 6 323 66 307 6 322 68 69 6 318 322 69 6 322 318 270 6 62 331 300 6 331 327 300 6 320 332 323 6 332 320 327 6 63 331 62 6 63 332 331 6 327 331 332 6 37 333 36 6 333 268 36 6 334 75 76 6 268 334 76 6 335 64 9 6 65 335 9 6 333 266 268 6 266 334 268 6 335 63 64 6 335 323 332 6 323 335 65 6 332 63 335 6 37 38 333 6 266 333 283 6 38 283 333 6 74 75 334 6 334 266 284 6 284 74 334 6 297 329 45 6 297 314 329 6 73 357 74 11 357 73 354 11 357 338 340 11 354 338 357 11 358 94 95 11 94 358 355 11 338 358 340 11 338 355 358 11 354 337 338 11 337 355 338 11 359 97 14 11 97 359 342 11 98 359 14 11 76 359 11 11 359 76 342 11 359 98 11 11 79 341 78 11 339 360 351 11 360 347 351 11 345 365 364 11 366 346 363 11 337 372 355 11 353 372 369 11 372 353 355 11 373 337 354 11 373 352 368 11 352 373 354 11 375 347 360 11 71 376 72 11 352 376 348 11 377 92 93 11 377 353 349 11 343 378 361 11 70 379 71 11 380 91 92 11 382 344 362 11 336 383 368 11 383 336 369 11 72 384 73 11 354 384 352 11 384 354 73 11 339 385 360 11 386 93 94 11 386 355 353 11 355 386 94 11 387 336 364 11 336 387 363 11 379 376 71 11 348 376 379 11 377 380 92 11 377 349 380 11 376 384 72 11 376 352 384 11 386 377 93 11 353 377 386 11 366 363 387 11 364 365 387 11 373 383 337 11 373 368 383 11 383 372 337 11 369 372 383 11 375 81 82 11 367 356 361 11 367 361 378 11 356 370 362 11 362 370 382 11 336 368 364 11 348 368 352 11 345 368 348 11 345 364 368 11 369 336 363 11 369 349 353 11 369 346 349 11 363 346 369 11 346 380 349 11 379 345 348 11 344 366 362 11 366 344 346 11 356 362 366 11 365 343 361 11 343 365 345 11 361 356 365 11 347 374 350 11 82 374 375 11 374 347 375 11 390 341 79 11 390 385 339 11 341 390 339 11 89 391 88 11 392 346 344 11 345 393 343 11 395 378 343 11 382 396 344 11 393 395 343 11 392 380 346 11 66 371 67 11 392 91 380 11 91 392 90 11 379 393 345 11 70 393 379 11 393 70 69 11 374 82 83 11 356 366 387 11 365 356 387 11 385 80 81 11 81 375 385 11 385 375 360 11 391 89 396 11 88 381 87 11 391 381 88 11 80 390 79 11 80 385 390 11 394 397 381 11 356 398 370 11 398 356 367 11 339 351 389 11 367 389 351 11 367 378 389 11 66 399 371 11 339 400 341 11 400 339 389 11 400 399 341 11 371 399 400 11 65 399 66 11 400 388 371 11 395 388 378 11 389 378 388 11 389 388 400 11 350 398 347 11 398 351 347 11 370 398 350 11 398 367 351 11 394 381 391 11 391 396 382 11 382 394 391 11 402 370 350 11 402 394 382 11 370 402 382 11 403 68 67 11 403 388 395 11 404 65 9 11 404 78 341 11 78 404 9 11 404 399 65 11 341 399 404 11 401 350 374 11 401 402 350 11 401 394 402 11 68 395 69 11 395 393 69 11 403 395 68 11 87 397 86 11 397 87 381 11 396 89 90 11 392 396 90 11 396 392 344 11 83 405 374 11 405 401 374 11 394 406 397 11 406 394 401 11 84 405 83 11 84 406 405 11 401 405 406 11 75 407 76 11 407 342 76 11 408 96 97 11 342 408 97 11 409 85 12 11 86 409 12 11 407 340 342 11 340 408 342 11 409 84 85 11 409 397 406 11 397 409 86 11 406 84 409 11 75 74 407 11 340 407 357 11 74 357 407 11 95 96 408 11 408 340 358 11 358 95 408 11 371 403 67 11 371 388 403 11 431 27 26 16 27 431 428 16 412 431 414 16 412 428 431 16 94 432 95 16 432 94 429 16 432 412 414 16 429 412 432 16 411 428 412 16 429 411 412 16 97 433 14 16 433 97 416 16 433 107 14 16 433 24 2 16 24 433 416 16 107 433 2 16 415 105 106 16 434 413 425 16 421 434 425 16 439 419 438 16 420 440 437 16 446 411 429 16 446 427 443 16 427 446 429 16 411 447 428 16 426 447 442 16 447 426 428 16 421 449 434 16 450 29 28 16 450 426 422 16 92 451 93 16 427 451 423 16 452 417 435 16 453 30 29 16 91 454 92 16 418 456 436 16 457 410 442 16 410 457 443 16 458 28 27 16 458 428 426 16 428 458 27 16 459 413 434 16 93 460 94 16 429 460 427 16 460 429 94 16 410 461 438 16 461 410 437 16 450 453 29 16 450 422 453 16 454 451 92 16 423 451 454 16 458 450 28 16 426 450 458 16 451 460 93 16 451 427 460 16 437 440 461 16 439 438 461 16 457 447 411 16 442 447 457 16 446 457 411 16 446 443 457 16 103 449 102 16 430 441 435 16 435 441 452 16 444 430 436 16 444 436 456 16 442 410 438 16 442 422 426 16 442 419 422 16 438 419 442 16 410 443 437 16 423 443 427 16 420 443 423 16 420 437 443 16 454 420 423 16 419 453 422 16 440 418 436 16 418 440 420 16 436 430 440 16 417 439 435 16 439 417 419 16 430 435 439 16 448 421 424 16 448 102 449 16 421 448 449 16 415 464 105 16 459 464 413 16 464 415 413 16 465 89 88 16 420 466 418 16 467 419 417 16 452 469 417 16 470 456 418 16 469 467 417 16 454 466 420 16 445 34 33 16 91 466 454 16 466 91 90 16 467 453 419 16 467 30 453 16 30 467 31 16 102 448 101 16 440 430 461 16 430 439 461 16 104 459 103 16 449 103 459 16 449 459 434 16 89 465 470 16 455 88 87 16 455 465 88 16 464 104 105 16 459 104 464 16 471 468 455 16 472 430 444 16 430 472 441 16 425 413 463 16 463 441 425 16 452 441 463 16 473 34 445 16 474 413 415 16 413 474 463 16 473 474 415 16 473 445 474 16 473 35 34 16 462 474 445 16 462 469 452 16 452 463 462 16 462 463 474 16 472 424 421 16 425 472 421 16 472 444 424 16 441 472 425 16 455 468 465 16 470 465 456 16 468 456 465 16 444 476 424 16 468 476 456 16 476 444 456 16 32 477 33 16 462 477 469 16 35 478 4 16 106 478 415 16 478 106 4 16 473 478 35 16 473 415 478 16 424 475 448 16 476 475 424 16 468 475 476 16 469 32 31 16 467 469 31 16 469 477 32 16 471 87 86 16 87 471 455 16 89 470 90 16 470 466 90 16 466 470 418 16 479 101 448 16 475 479 448 16 480 468 471 16 468 480 475 16 479 100 101 16 480 100 479 16 479 475 480 16 481 25 24 16 416 481 24 16 96 482 97 16 482 416 97 16 483 86 12 16 99 483 12 16 414 481 416 16 482 414 416 16 100 483 99 16 471 483 480 16 483 471 86 16 100 480 483 16 26 25 481 16 481 414 431 16 431 26 481 16 96 95 482 16 414 482 432 16 95 432 482 16 477 445 33 16 462 445 477 16 111 505 110 21 505 111 502 21 505 486 488 21 502 486 505 21 506 136 137 21 136 506 503 21 486 506 488 21 486 503 506 21 502 485 486 21 485 503 486 21 77 507 6 21 139 507 490 21 507 139 6 21 507 77 11 21 507 108 490 21 108 507 11 21 121 489 120 21 487 508 499 21 508 495 499 21 493 513 512 21 514 494 511 21 485 520 503 21 501 520 517 21 520 501 503 21 521 485 502 21 521 500 516 21 500 521 502 21 523 495 508 21 113 524 112 21 500 524 496 21 525 134 135 21 525 501 497 21 491 526 509 21 114 527 113 21 528 133 134 21 530 492 510 21 484 531 516 21 531 484 517 21 112 532 111 21 502 532 500 21 532 502 111 21 487 533 508 21 534 135 136 21 534 503 501 21 503 534 136 21 535 484 512 21 484 535 511 21 527 524 113 21 496 524 527 21 525 528 134 21 525 497 528 21 524 532 112 21 524 500 532 21 534 525 135 21 501 525 534 21 514 511 535 21 512 513 535 21 521 531 485 21 521 516 531 21 531 520 485 21 517 520 531 21 523 123 124 21 515 504 509 21 515 509 526 21 504 518 510 21 510 518 530 21 484 516 512 21 496 516 500 21 493 516 496 21 493 512 516 21 517 484 511 21 517 497 501 21 517 494 497 21 511 494 517 21 494 528 497 21 527 493 496 21 492 514 510 21 514 492 494 21 504 510 514 21 513 491 509 21 491 513 493 21 509 504 513 21 495 522 498 21 124 522 523 21 522 495 523 21 538 489 121 21 538 533 487 21 489 538 487 21 131 539 130 21 540 494 492 21 493 541 491 21 543 526 491 21 530 544 492 21 541 543 491 21 540 528 494 21 118 519 117 21 540 133 528 21 133 540 132 21 527 541 493 21 114 541 527 21 541 114 115 21 522 124 125 21 504 514 535 21 513 504 535 21 533 122 123 21 123 523 533 21 533 523 508 21 539 131 544 21 130 529 129 21 539 529 130 21 122 538 121 21 122 533 538 21 542 545 529 21 504 546 518 21 546 504 515 21 487 499 537 21 515 537 499 21 515 526 537 21 118 547 519 21 487 548 489 21 548 487 537 21 548 547 489 21 519 547 548 21 119 547 118 21 548 536 519 21 543 536 526 21 537 526 536 21 537 536 548 21 498 546 495 21 546 499 495 21 518 546 498 21 546 515 499 21 542 529 539 21 539 544 530 21 530 542 539 21 550 518 498 21 550 542 530 21 518 550 530 21 551 116 117 21 551 536 543 21 552 119 16 21 552 120 489 21 120 552 16 21 552 547 119 21 489 547 552 21 549 498 522 21 549 550 498 21 549 542 550 21 116 543 115 21 543 541 115 21 551 543 116 21 129 545 128 21 545 129 529 21 544 131 132 21 540 544 132 21 544 540 492 21 125 553 522 21 553 549 522 21 542 554 545 21 554 542 549 21 126 553 125 21 126 554 553 21 549 553 554 21 109 555 108 21 555 490 108 21 556 138 139 21 490 556 139 21 557 127 18 21 128 557 18 21 555 488 490 21 488 556 490 21 557 126 127 21 557 545 554 21 545 557 128 21 554 126 557 21 109 110 555 21 488 555 505 21 110 505 555 21 137 138 556 21 556 488 506 21 506 137 556 21 519 551 117 21 519 536 551 21 143 579 142 26 579 143 576 26 579 560 562 26 576 560 579 26 580 111 110 26 111 580 577 26 560 580 562 26 560 577 580 26 576 559 560 26 559 577 560 26 98 581 11 26 108 581 564 26 581 108 11 26 581 98 14 26 581 140 564 26 140 581 14 26 153 563 152 26 561 582 573 26 582 569 573 26 567 587 586 26 588 568 585 26 559 594 577 26 575 594 591 26 594 575 577 26 595 559 576 26 595 574 590 26 574 595 576 26 597 569 582 26 145 598 144 26 574 598 570 26 599 113 112 26 599 575 571 26 565 600 583 26 146 601 145 26 602 114 113 26 604 566 584 26 558 605 590 26 605 558 591 26 144 606 143 26 576 606 574 26 606 576 143 26 561 607 582 26 608 112 111 26 608 577 575 26 577 608 111 26 609 558 586 26 558 609 585 26 601 598 145 26 570 598 601 26 599 602 113 26 599 571 602 26 598 606 144 26 598 574 606 26 608 599 112 26 575 599 608 26 588 585 609 26 586 587 609 26 595 605 559 26 595 590 605 26 605 594 559 26 591 594 605 26 597 155 156 26 589 578 583 26 589 583 600 26 578 592 584 26 584 592 604 26 558 590 586 26 570 590 574 26 567 590 570 26 567 586 590 26 591 558 585 26 591 571 575 26 591 568 571 26 585 568 591 26 568 602 571 26 601 567 570 26 566 588 584 26 588 566 568 26 578 584 588 26 587 565 583 26 565 587 567 26 583 578 587 26 569 596 572 26 156 596 597 26 596 569 597 26 612 563 153 26 612 607 561 26 563 612 561 26 116 613 117 26 614 568 566 26 567 615 565 26 617 600 565 26 604 618 566 26 615 617 565 26 614 602 568 26 150 593 149 26 614 114 602 26 114 614 115 26 601 615 567 26 146 615 601 26 615 146 147 26 596 156 157 26 578 588 609 26 587 578 609 26 607 154 155 26 155 597 607 26 607 597 582 26 613 116 618 26 117 603 118 26 613 603 117 26 154 612 153 26 154 607 612 26 616 619 603 26 578 620 592 26 620 578 589 26 561 573 611 26 589 611 573 26 589 600 611 26 150 621 593 26 561 622 563 26 622 561 611 26 622 621 563 26 593 621 622 26 151 621 150 26 622 610 593 26 617 610 600 26 611 600 610 26 611 610 622 26 572 620 569 26 620 573 569 26 592 620 572 26 620 589 573 26 616 603 613 26 613 618 604 26 604 616 613 26 624 592 572 26 624 616 604 26 592 624 604 26 625 148 149 26 625 610 617 26 626 151 21 26 626 152 563 26 152 626 21 26 626 621 151 26 563 621 626 26 623 572 596 26 623 624 572 26 623 616 624 26 148 617 147 26 617 615 147 26 625 617 148 26 118 619 119 26 619 118 603 26 618 116 115 26 614 618 115 26 618 614 566 26 157 627 596 26 627 623 596 26 616 628 619 26 628 616 623 26 158 627 157 26 158 628 627 26 623 627 628 26 141 629 140 26 629 564 140 26 630 109 108 26 564 630 108 26 119 631 16 26 631 159 16 26 629 562 564 26 562 630 564 26 631 158 159 26 631 619 628 26 619 631 119 26 628 158 631 26 141 142 629 26 562 629 579 26 142 579 629 26 110 109 630 26 630 562 580 26 580 110 630 26 593 625 149 26 593 610 625 26 163 653 162 31 653 163 650 31 653 634 636 31 650 634 653 31 654 143 142 31 143 654 651 31 634 654 636 31 634 651 654 31 650 633 634 31 633 651 634 31 107 655 14 31 140 655 638 31 655 140 14 31 655 107 2 31 655 160 638 31 160 655 2 31 173 637 172 31 635 656 647 31 656 643 647 31 641 661 660 31 662 642 659 31 633 668 651 31 649 668 665 31 668 649 651 31 669 633 650 31 669 648 664 31 648 669 650 31 671 643 656 31 165 672 164 31 648 672 644 31 673 145 144 31 673 649 645 31 639 674 657 31 166 675 165 31 676 146 145 31 678 640 658 31 632 679 664 31 679 632 665 31 164 680 163 31 650 680 648 31 680 650 163 31 635 681 656 31 682 144 143 31 682 651 649 31 651 682 143 31 683 632 660 31 632 683 659 31 675 672 165 31 644 672 675 31 673 676 145 31 673 645 676 31 672 680 164 31 672 648 680 31 682 673 144 31 649 673 682 31 662 659 683 31 660 661 683 31 669 679 633 31 669 664 679 31 679 668 633 31 665 668 679 31 671 175 176 31 663 652 657 31 663 657 674 31 652 666 658 31 658 666 678 31 632 664 660 31 644 664 648 31 641 664 644 31 641 660 664 31 665 632 659 31 665 645 649 31 665 642 645 31 659 642 665 31 642 676 645 31 675 641 644 31 640 662 658 31 662 640 642 31 652 658 662 31 661 639 657 31 639 661 641 31 657 652 661 31 643 670 646 31 176 670 671 31 670 643 671 31 686 637 173 31 686 681 635 31 637 686 635 31 148 687 149 31 688 642 640 31 641 689 639 31 691 674 639 31 678 692 640 31 689 691 639 31 688 676 642 31 170 667 169 31 688 146 676 31 146 688 147 31 675 689 641 31 166 689 675 31 689 166 167 31 670 176 177 31 652 662 683 31 661 652 683 31 681 174 175 31 175 671 681 31 681 671 656 31 687 148 692 31 149 677 150 31 687 677 149 31 174 686 173 31 174 681 686 31 690 693 677 31 652 694 666 31 694 652 663 31 635 647 685 31 663 685 647 31 663 674 685 31 170 695 667 31 635 696 637 31 696 635 685 31 696 695 637 31 667 695 696 31 171 695 170 31 696 684 667 31 691 684 674 31 685 674 684 31 685 684 696 31 646 694 643 31 694 647 643 31 666 694 646 31 694 663 647 31 690 677 687 31 687 692 678 31 678 690 687 31 698 666 646 31 698 690 678 31 666 698 678 31 699 168 169 31 699 684 691 31 700 171 23 31 700 172 637 31 172 700 23 31 700 695 171 31 637 695 700 31 697 646 670 31 697 698 646 31 697 690 698 31 168 691 167 31 691 689 167 31 699 691 168 31 150 693 151 31 693 150 677 31 692 148 147 31 688 692 147 31 692 688 640 31 177 701 670 31 701 697 670 31 690 702 693 31 702 690 697 31 178 701 177 31 178 702 701 31 697 701 702 31 161 703 160 31 703 638 160 31 704 141 140 31 638 704 140 31 151 705 21 31 705 179 21 31 703 636 638 31 636 704 638 31 705 178 179 31 705 693 702 31 693 705 151 31 702 178 705 31 161 162 703 31 636 703 653 31 162 653 703 31 142 141 704 31 704 636 654 31 654 142 704 31 667 699 169 31 667 684 699 31 136 727 137 36 727 136 724 36 727 708 710 36 724 708 727 36 728 163 162 36 163 728 725 36 708 728 710 36 708 725 728 36 724 707 708 36 707 725 708 36 48 729 2 36 160 729 712 36 729 160 2 36 729 48 6 36 729 139 712 36 139 729 6 36 181 711 180 36 709 730 721 36 730 717 721 36 715 735 734 36 736 716 733 36 707 742 725 36 723 742 739 36 742 723 725 36 743 707 724 36 743 722 738 36 722 743 724 36 745 717 730 36 134 746 135 36 722 746 718 36 747 165 164 36 747 723 719 36 713 748 731 36 133 749 134 36 750 166 165 36 752 714 732 36 706 753 738 36 753 706 739 36 135 754 136 36 724 754 722 36 754 724 136 36 709 755 730 36 756 164 163 36 756 725 723 36 725 756 163 36 757 706 734 36 706 757 733 36 749 746 134 36 718 746 749 36 747 750 165 36 747 719 750 36 746 754 135 36 746 722 754 36 756 747 164 36 723 747 756 36 736 733 757 36 734 735 757 36 743 753 707 36 743 738 753 36 753 742 707 36 739 742 753 36 745 183 184 36 737 726 731 36 737 731 748 36 726 740 732 36 732 740 752 36 706 738 734 36 718 738 722 36 715 738 718 36 715 734 738 36 739 706 733 36 739 719 723 36 739 716 719 36 733 716 739 36 716 750 719 36 749 715 718 36 714 736 732 36 736 714 716 36 726 732 736 36 735 713 731 36 713 735 715 36 731 726 735 36 717 744 720 36 184 744 745 36 744 717 745 36 760 711 181 36 760 755 709 36 711 760 709 36 168 761 169 36 762 716 714 36 715 763 713 36 765 748 713 36 752 766 714 36 763 765 713 36 762 750 716 36 129 741 130 36 762 166 750 36 166 762 167 36 749 763 715 36 133 763 749 36 763 133 132 36 744 184 185 36 726 736 757 36 735 726 757 36 755 182 183 36 183 745 755 36 755 745 730 36 761 168 766 36 169 751 170 36 761 751 169 36 182 760 181 36 182 755 760 36 764 767 751 36 726 768 740 36 768 726 737 36 709 721 759 36 737 759 721 36 737 748 759 36 129 769 741 36 709 770 711 36 770 709 759 36 770 769 711 36 741 769 770 36 128 769 129 36 770 758 741 36 765 758 748 36 759 748 758 36 759 758 770 36 720 768 717 36 768 721 717 36 740 768 720 36 768 737 721 36 764 751 761 36 761 766 752 36 752 764 761 36 772 740 720 36 772 764 752 36 740 772 752 36 773 131 130 36 773 758 765 36 774 128 18 36 774 180 711 36 180 774 18 36 774 769 128 36 711 769 774 36 771 720 744 36 771 772 720 36 771 764 772 36 131 765 132 36 765 763 132 36 773 765 131 36 170 767 171 36 767 170 751 36 766 168 167 36 762 766 167 36 766 762 714 36 185 775 744 36 775 771 744 36 764 776 767 36 776 764 771 36 186 775 185 36 186 776 775 36 771 775 776 36 138 777 139 36 777 712 139 36 778 161 160 36 712 778 160 36 171 779 23 36 779 187 23 36 777 710 712 36 710 778 712 36 779 186 187 36 779 767 776 36 767 779 171 36 776 186 779 36 138 137 777 36 710 777 727 36 137 727 777 36 162 161 778 36 778 710 728 36 728 162 778 36 741 773 130 36 741 758 773 36 788 832 826 40 805 826 832 40 804 827 831 40 789 831 827 40 795 817 808 40 796 807 818 40 49 853 813 40 9 814 854 40 833 883 856 40 781 808 851 40 783 852 807 40 788 820 832 40 789 821 831 40 796 818 840 40 49 50 853 40 9 854 64 40 52 872 809 40 60 873 810 40 818 892 840 40 52 809 845 40 60 810 846 40 785 833 856 40 54 847 812 40 62 848 811 40 54 812 872 40 62 811 873 40 803 856 883 40 786 808 862 40 787 863 807 40 795 808 864 40 796 865 807 40 786 864 808 40 787 807 865 40 784 836 820 40 798 820 836 40 821 850 831 40 820 849 832 40 781 841 808 40 783 807 842 40 808 817 851 40 807 852 818 40 791 809 862 40 782 843 815 40 792 815 843 40 782 816 844 40 793 844 816 40 794 863 811 40 788 881 823 40 789 882 825 40 788 826 881 40 789 827 882 40 103 104 827 40 12 826 85 40 790 824 874 40 790 874 822 40 888 860 820 40 889 819 859 40 887 861 821 40 99 100 823 40 79 80 824 40 105 106 825 40 81 82 822 40 791 845 809 40 103 827 875 40 85 826 876 40 788 888 820 40 790 819 889 40 789 887 821 40 794 811 848 40 49 813 868 40 9 869 814 40 780 840 892 40 100 870 823 40 82 871 822 40 52 53 872 40 60 61 873 40 50 51 853 40 63 64 854 40 812 847 866 40 793 866 847 40 53 54 872 40 61 62 873 40 792 846 867 40 810 867 846 40 51 845 853 40 63 854 848 40 786 816 864 40 787 865 815 40 57 58 828 40 8 829 56 40 54 55 847 40 62 63 848 40 786 866 816 40 793 816 866 40 792 867 815 40 787 815 867 40 59 60 846 40 51 52 845 40 782 894 816 40 782 815 895 40 79 824 879 40 106 880 825 40 790 833 819 40 795 830 840 40 783 819 852 40 799 852 819 40 781 851 821 40 797 821 851 40 801 857 869 40 814 869 857 40 813 858 868 40 800 868 858 40 806 828 843 40 792 843 828 40 806 844 829 40 793 829 844 40 795 884 817 40 804 875 827 40 805 876 826 40 797 851 817 40 799 818 852 40 797 817 893 40 799 892 818 40 809 886 862 40 57 828 877 40 8 877 829 40 811 863 885 40 798 849 820 40 797 850 821 40 802 823 870 40 803 822 871 40 802 834 855 40 805 856 835 40 804 855 834 40 803 835 856 40 795 840 884 40 4 49 868 40 9 78 869 40 783 859 819 40 784 820 860 40 781 821 861 40 101 102 834 40 83 84 835 40 799 819 878 40 781 858 841 40 783 842 857 40 806 843 844 40 782 844 843 40 807 863 842 40 808 841 862 40 786 862 886 40 796 840 830 40 841 858 896 40 842 897 857 40 806 877 828 40 806 829 877 40 784 855 831 40 804 831 855 40 785 856 832 40 805 832 856 40 797 837 836 40 780 838 837 40 798 836 837 40 798 837 838 40 798 838 839 40 799 839 838 40 793 891 829 40 792 828 890 40 787 885 863 40 816 894 864 40 815 865 895 40 801 879 824 40 800 825 880 40 784 831 850 40 785 832 849 40 782 895 830 40 782 830 894 40 790 822 883 40 803 883 822 40 798 839 849 40 797 836 850 40 793 847 891 40 792 890 846 40 785 878 833 40 784 850 836 40 785 849 839 40 796 895 865 40 795 864 894 40 81 822 874 40 80 874 824 40 788 823 888 40 790 889 824 40 789 825 887 40 99 823 881 40 105 825 882 40 802 888 823 40 801 824 889 40 800 887 825 40 4 880 106 40 78 79 879 40 785 839 878 40 819 833 878 40 797 893 837 40 799 838 892 40 101 834 870 40 83 835 871 40 12 881 826 40 104 882 827 40 802 870 834 40 803 871 835 40 791 853 845 40 780 884 840 40 794 848 854 40 813 853 896 40 791 896 853 40 814 897 854 40 794 854 897 40 102 875 834 40 804 834 875 40 805 835 876 40 84 876 835 40 100 101 870 40 82 83 871 40 791 862 841 40 794 842 863 40 80 81 874 40 104 105 882 40 12 99 881 40 58 59 890 40 55 56 891 40 102 103 875 40 84 85 876 40 8 57 877 40 802 855 860 40 784 860 855 40 58 890 828 40 56 829 891 40 795 894 830 40 796 830 895 40 780 893 884 40 801 859 857 40 783 857 859 40 781 861 858 40 800 858 861 40 790 883 833 40 4 868 880 40 78 879 869 40 810 885 867 40 787 867 885 40 799 878 839 40 786 886 866 40 812 866 886 40 780 892 838 40 780 837 893 40 59 846 890 40 55 891 847 40 791 841 896 40 794 897 842 40 810 873 885 40 809 872 886 40 811 885 873 40 812 886 872 40 801 889 859 40 802 860 888 40 800 861 887 40 817 884 893 40 801 869 879 40 800 880 868 40 813 896 858 40 814 857 897 40 906 949 944 42 923 944 949 42 922 945 948 42 907 948 945 42 914 926 936 42 913 951 935 42 899 935 951 42 913 968 925 42 120 972 932 42 23 933 973 42 950 1002 975 42 931 1012 968 42 901 971 926 42 911 962 931 42 1012 925 968 42 906 937 949 42 907 939 948 42 899 951 939 42 915 939 951 42 913 925 988 42 120 121 972 42 23 973 187 42 123 991 928 42 183 992 927 42 123 928 965 42 183 927 964 42 903 950 975 42 125 966 929 42 185 967 930 42 125 929 991 42 185 930 992 42 921 975 1002 42 904 980 926 42 914 982 926 42 904 926 982 42 902 954 937 42 916 937 954 42 931 962 1012 42 914 936 960 42 939 970 948 42 937 969 949 42 901 926 959 42 900 1012 962 42 926 971 936 42 900 961 934 42 910 934 961 42 912 980 930 42 906 1000 941 42 907 1001 943 42 911 931 983 42 906 944 1000 42 907 945 1001 42 156 157 945 42 21 944 179 42 899 963 935 42 909 935 963 42 908 942 993 42 908 993 940 42 913 935 968 42 1006 978 937 42 1007 938 977 42 1005 979 939 42 905 968 935 42 152 153 941 42 173 174 942 42 158 159 943 42 175 176 940 42 156 945 994 42 179 944 995 42 906 1006 937 42 908 938 1007 42 907 1005 939 42 912 930 967 42 936 1010 960 42 120 932 986 42 23 987 933 42 153 989 941 42 176 990 940 42 183 184 992 42 123 124 991 42 121 122 972 42 186 187 973 42 929 966 983 42 911 983 966 42 124 125 991 42 184 185 992 42 909 965 984 42 910 964 985 42 928 984 965 42 927 985 964 42 914 981 934 42 914 934 982 42 905 983 931 42 122 965 972 42 186 973 967 42 900 934 981 42 904 982 934 42 925 981 960 42 914 960 981 42 909 963 932 42 905 931 968 42 180 181 946 42 18 947 127 42 125 126 966 42 185 186 967 42 910 985 934 42 909 984 935 42 904 934 985 42 905 935 984 42 122 123 965 42 182 183 964 42 909 932 972 42 173 942 998 42 159 999 943 42 908 950 938 42 918 986 932 42 1011 918 932 42 901 938 971 42 917 971 938 42 920 976 987 42 933 987 976 42 924 946 961 42 910 961 946 42 924 962 947 42 911 947 962 42 922 994 945 42 923 995 944 42 917 936 971 42 915 958 955 42 917 1010 936 42 898 960 1010 42 180 946 996 42 18 996 947 42 930 980 1003 42 916 969 937 42 915 970 939 42 919 941 989 42 921 940 990 42 919 952 974 42 923 975 953 42 922 974 952 42 921 953 975 42 915 951 958 42 16 120 986 42 23 172 987 42 902 937 978 42 901 977 938 42 899 939 979 42 154 155 952 42 177 178 953 42 917 938 997 42 901 959 976 42 924 961 962 42 900 962 961 42 898 955 958 42 926 980 959 42 959 1013 976 42 913 958 951 42 924 996 946 42 924 947 996 42 902 974 948 42 922 948 974 42 903 975 949 42 923 949 975 42 915 955 954 42 916 954 955 42 898 956 955 42 916 955 956 42 916 956 957 42 917 957 956 42 911 1009 947 42 910 946 1008 42 904 1003 980 42 898 958 988 42 898 988 960 42 920 998 942 42 918 943 999 42 902 948 970 42 903 949 969 42 908 940 1002 42 921 1002 940 42 916 957 969 42 915 954 970 42 911 966 1009 42 910 1008 964 42 903 997 950 42 902 970 954 42 903 969 957 42 175 940 993 42 174 993 942 42 906 941 1006 42 908 1007 942 42 907 943 1005 42 152 941 1000 42 158 943 1001 42 919 1006 941 42 920 942 1007 42 918 1005 943 42 16 999 159 42 172 173 998 42 903 957 997 42 938 950 997 42 917 956 1010 42 154 952 989 42 177 953 990 42 21 1000 944 42 157 1001 945 42 919 989 952 42 921 990 953 42 909 972 965 42 912 967 973 42 933 1013 973 42 912 973 1013 42 155 994 952 42 922 952 994 42 923 953 995 42 178 995 953 42 153 154 989 42 176 177 990 42 912 959 980 42 174 175 993 42 932 963 1011 42 157 158 1001 42 21 152 1000 42 181 182 1008 42 126 127 1009 42 155 156 994 42 178 179 995 42 18 180 996 42 919 974 978 42 902 978 974 42 181 1008 946 42 127 947 1009 42 920 977 976 42 901 976 977 42 908 1002 950 42 913 988 958 42 16 986 999 42 172 998 987 42 928 1004 984 42 927 1003 985 42 905 984 1004 42 904 985 1003 42 925 960 988 42 917 997 957 42 905 1004 983 42 929 983 1004 42 899 1011 963 42 898 1010 956 42 899 979 1011 42 182 964 1008 42 126 1009 966 42 912 1013 959 42 900 981 1012 42 925 1012 981 42 927 992 1003 42 928 991 1004 42 930 1003 992 42 929 1004 991 42 919 978 1006 42 920 1007 977 42 918 979 1005 42 920 987 998 42 918 999 986 42 918 1011 979 42 933 976 1013 42 End trunk-2018.02b/examples/hourglass/hourglass.py000066400000000000000000000051161324306050200213770ustar00rootroot00000000000000#!/usr/bin/env python # encoding: utf-8 # Anton Gladky # If you want to change the number of generated particles, # change it in line 44. Do not forget yo increase the firstIterRun # in line 62 not to revolute the hourglass before particles are # settled down at the bottom. from yade import ymport import time utils.readParamsFromTable(descriptionIn = 'noDescription', frIn = 0.5, enIn=0.01, etIn=0.01, tcIn=0.0001, # Frictionangle (rad), strength-parameter rhoIn = 1500.0, # Density dumpVTKIn = 4000 # Periods of dumps in iterations ) from yade.params.table import * import shutil try: shutil.rmtree('cpt') except OSError: pass os.mkdir('cpt') folderNameBase = 'cpt/' + str(descriptionIn) folderName = folderNameBase os.mkdir(folderNameBase) o = Omega() o.dt = 0.05*tcIn rotPeriod = 0.01 rotPeriodIter = int(rotPeriod/o.dt) mat1 = O.materials.append(ViscElMat(frictionAngle=frIn, density=rhoIn,tc=tcIn, en=enIn, et=etIn,)) id_HourGl = O.bodies.append(ymport.gmsh("hourglass.mesh",scale=1.0, material=mat1,color=(0,0,1),mask=5)) o.engines = [ ForceResetter(), InsertionSortCollider([Bo1_Sphere_Aabb(),Bo1_Facet_Aabb()],verletDist=(2.0e-3),label='collider',ompThreads=1), InteractionLoop( [Ig2_Sphere_Sphere_ScGeom(),Ig2_Facet_Sphere_ScGeom()], [Ip2_ViscElMat_ViscElMat_ViscElPhys()], [Law2_ScGeom_ViscElPhys_Basic()], ), NewtonIntegrator(damping=0,gravity=Vector3(0,0,-90.81), label='newt'), CircularFactory(maxParticles=1500, #Number of generated particles center=(0.0e-3, 0.0e-3,20.0e-3), radius = (12.0e-3), PSDsizes = [0.5e-3], PSDcum = [1.0], vMin=0.1,vMax=0.1,vAngle=0,massFlowRate=0.01, normal=(0.0,0.0,-1.0),label='factorySpheres',mask=3,materialId=mat1, stopIfFailed=False), RotationEngine(ids=id_HourGl, angularVelocity = math.pi/rotPeriod, rotateAroundZero=True, rotationAxis=Vector3(1,0,0), zeroPoint=Vector3(0.,0.,0.), label='rotEng', dead=True), VTKRecorder(iterPeriod=dumpVTKIn,fileName=folderName+'/spheres-',mask=2, recorders=['spheres','velocity','colors','intr','ids','mask','materialId','stress'], label='VTK_box1'), VTKRecorder(iterPeriod=dumpVTKIn,fileName=folderName+'/facetB-',mask=4, recorders=['facets'],label='VTK_box2'), PyRunner(iterPeriod=rotPeriodIter,initRun=False,firstIterRun=13000,command='startStepRotation()'), ] def startStepRotation(): if (rotEng.dead): rotEng.dead = False else: rotEng.dead = True from yade import qt qt.View() r=qt.Renderer() trunk-2018.02b/examples/jointedCohesiveFrictionalPM/000077500000000000000000000000001324306050200223765ustar00rootroot00000000000000trunk-2018.02b/examples/jointedCohesiveFrictionalPM/README000066400000000000000000000041361324306050200232620ustar00rootroot00000000000000This folder illustrates the modelling of fractured/jointed rock media, as initiated by Luc Scholtes: how Law2_ScGeom_JCFpmPhys_JointedCohesiveFrictionalPM can be used to deal with jointed media (media containing pre-existing discontinuity surfaces e.g. fractures). For details/remarks/improvements, please send emails to lscholtes63@gmail.com or use https://answers.launchpad.net/yade/+addquestion. Also, please refer to following references (from https://www.yade-dem.org/doc/publications.html) for more details and explananations: Hartong2012a, Scholtes2012, Scholtes2013, Duriez2016 This folder includes the following files: 1-packInGtsSurface.py is a simple script used to generate a packing (parallellepiped_10.spheres) from a meshed closed surface (parallellepiped.gts). Packing generation can be done with other methods. The point here is to generate a packing and to save it in a text file (parallellepiped_10.spheres). 2-identificationSpheresOnJoint.py is a script used to identify the spheres belonging to a predefined packing (parallellepiped_10.spheres) interacting along pre-existing discontinuity surfaces defined by a meshed surface (persistentPlane30Deg.stl). Executing this script produces 2 text files containing respectively spheres from packing (parallellepiped_10_persistentPlane30Deg.spheres) and spheres attributes regarding jcfPM variables (parallellepiped_10_persistentPlane30Deg_jointedPM.spheres). 3-gravityLoading.py is a simple example showing how to use Law2_ScGeom_JCFpmPhys_JointedCohesiveFrictionalPM. It simulates the loading of a pre-fractured packing (parallellepiped_10_persistentPlane30Deg.spheres) by gravity to emphasize how the pre-existing discontinuity affects the behavior. User can play along with joint surface properties (smooth contact logic or not, joint friction angle,...) to see the effect on the simulated behavior. A more compact way of use is proposed in gravityBis.py. This script is stand-alone, and calls itself the scrit identifBis.py. This later script can be called from any script in JCFpm modelling to detect spheres that should interact according to smooth contact logic.trunk-2018.02b/examples/jointedCohesiveFrictionalPM/gravityBis.py000066400000000000000000000100121324306050200250650ustar00rootroot00000000000000# encoding: utf-8 # example of use JCFpm classes : Law2_ScGeom_JCFpmPhys_JointedCohesiveFrictionalPM... # a cubic rock massif, containing a rock joint with ~ 31 deg. dip angle. At one time, the mechanical properties of the joint are degraded, triggering a smooth sliding # definition of a predicate for use of randomDensePack() function from yade import pack dimModele = 10.0 pred = pack.inAlignedBox((0,0,0),(dimModele,dimModele,dimModele)) # the packing of spheres : def mat(): return JCFpmMat(type=1,young=1e8,poisson=0.3,frictionAngle=radians(30),density=3000,tensileStrength=1e6,cohesion=1e6,jointNormalStiffness=1e7,jointShearStiffness=1e7,jointCohesion=1e6,jointFrictionAngle=radians(20),jointDilationAngle=0.0) nSpheres = 3000.0 poros=0.13 rMeanSpheres = dimModele * pow(3.0/4.0*(1-poros)/(pi*nSpheres),1.0/3.0) print '' print 'Creating a cubic sample of spheres (may take some time and cause warnings)' print '' sp = pack.randomDensePack(pred,radius=rMeanSpheres,rRelFuzz=0.3,memoizeDb='/tmp/gts-triax-packings.sqlite',returnSpherePack=True) sp.toSimulation(color=(0.9,0.8,0.6),wire=False,material=mat) print '' print 'Sample created !' # Definition of the facets for joint's geometry import gts # joint with ~ 31 deg. dip angle v1 = gts.Vertex(0 , 0 , 0.8*dimModele) v2 = gts.Vertex(0 , dimModele , 0.8*dimModele ) v3 = gts.Vertex(dimModele , dimModele , 0.2*dimModele) v4 = gts.Vertex(dimModele , 0 , 0.2*dimModele) e1 = gts.Edge(v1,v2) e2 = gts.Edge(v2,v4) e3 = gts.Edge(v4,v1) f1 = gts.Face(e1,e2,e3) e4 = gts.Edge(v4,v3) e5 = gts.Edge(v3,v2) f2 = gts.Face(e2,e4,e5) s1 = gts.Surface() s1.add(f1) s1.add(f2) facet = gtsSurface2Facets(s1,wire = False,material=mat) O.bodies.append(facet) yade.qt.View() yade.qt.Controller() O.saveTmp() # identification of spheres onJoint, and so on: execfile('identifBis.py') dim=utils.aabbExtrema() xsup=dim[1][0] yinf=dim[0][1] ysup=dim[1][1] zinf=dim[0][2] zsup=dim[1][2] e=2*rMeanSpheres for o in O.bodies: if isinstance(o.shape,Sphere): o.shape.color=(0.9,0.8,0.6) ## to fix boundary particles on ground if o.state.pos[2]<(zinf+2*e) : o.state.blockedDOFs+='xyz' o.shape.color=(1,1,1) ## to identify indicator on top if o.state.pos[2]>(zsup-e) and o.state.pos[0]>(xsup-e) and o.state.pos[1]>((yinf+ysup-e)/2.0) and o.state.pos[1]<((yinf+ysup+e)/2) : refPoint=o.id O.bodies[refPoint].shape.highlight=True #### Engines definition O.engines=[ ForceResetter(), InsertionSortCollider([Bo1_Sphere_Aabb(),]), InteractionLoop( [Ig2_Sphere_Sphere_ScGeom()], # [Ip2_JCFpmMat_JCFpmMat_JCFpmPhys(cohesiveTresholdIteration=1,alpha=0.3,tensileStrength=1e6,cohesion=1e6,jointNormalStiffness=1e7,jointShearStiffness=1e7,jointCohesion=1e6,jointFrictionAngle=radians(20),jointDilationAngle=0.0)], [Ip2_JCFpmMat_JCFpmMat_JCFpmPhys(cohesiveTresholdIteration=1)], [Law2_ScGeom_JCFpmPhys_JointedCohesiveFrictionalPM(smoothJoint=True)] ), GlobalStiffnessTimeStepper(timestepSafetyCoefficient=0.8), PyRunner(iterPeriod=1000,initRun=False,command='jointStrengthDegradation()'), PyRunner(iterPeriod=10,initRun=True,command='dataCollector()'), NewtonIntegrator(damping=0.7,gravity=(0.,0.,-10.)), ] #### dataCollector from yade import plot plot.plots={'iterations':'v','x':'z'} def dataCollector(): R=O.bodies[refPoint] plot.addData(v=R.state.vel[2],z=R.state.pos[2],x=R.state.pos[0],iterations=O.iter,t=O.realtime) #### joint strength degradation stableIter=1000 stableVel=0.001 degrade=True def jointStrengthDegradation(): global degrade if degrade and O.iter>=stableIter and abs(O.bodies[refPoint].state.vel[2])Rmax: Rmax=o.shape.radius Rmean=R/numSpheres #### Identification of the spheres on joint (some DIY here!) -> work to do on import function textExt to directly load material properties from the ascii file inFile=open(packing+'_jointedPM.spheres','r') for line in inFile: if '#' in line : continue id = int(line.split()[0]) onJ = int(line.split()[1]) nj = int(line.split()[2]) j11 = float(line.split()[3]) j12 = float(line.split()[4]) j13 = float(line.split()[5]) j21 = float(line.split()[6]) j22 = float(line.split()[7]) j23 = float(line.split()[8]) j31 = float(line.split()[9]) j32 = float(line.split()[10]) j33 = float(line.split()[11]) O.bodies[id].state.onJoint=onJ O.bodies[id].state.joint=nj O.bodies[id].state.jointNormal1=(j11,j12,j13) O.bodies[id].state.jointNormal2=(j21,j22,j23) O.bodies[id].state.jointNormal3=(j31,j32,j33) inFile.close #### Boundary conditions e=2*Rmean Xmax=0 Ymax=0 baseBodies=[] for o in O.bodies: if isinstance(o.shape,Sphere): o.shape.color=(0.9,0.8,0.6) ## to fix boundary particles on ground if o.state.pos[1]<(yinf+2*e) : o.state.blockedDOFs+='xyz' baseBodies.append(o.id) o.shape.color=(1,1,1) ## to identify indicator on top if o.state.pos[1]>(ysup-e) and o.state.pos[0]>(xsup-e) and o.state.pos[2]>(zinf+(Z-e)/2) and o.state.pos[2]<(zsup-(Z-e)/2) : refPoint=o.id p0=o.state.pos[1] baseBodies=tuple(baseBodies) O.bodies[refPoint].shape.color=(1,0,0) #### Engines definition interactionRadius=1. # to set initial contacts to larger neighbours O.engines=[ ForceResetter(), InsertionSortCollider([Bo1_Sphere_Aabb(aabbEnlargeFactor=interactionRadius,label='is2aabb'),]), InteractionLoop( [Ig2_Sphere_Sphere_ScGeom(interactionDetectionFactor=interactionRadius,label='ss2d3dg')], [Ip2_JCFpmMat_JCFpmMat_JCFpmPhys(cohesiveTresholdIteration=1,label='interactionPhys')], [Law2_ScGeom_JCFpmPhys_JointedCohesiveFrictionalPM(smoothJoint=smoothContact,label='interactionLaw')] ), GlobalStiffnessTimeStepper(timestepSafetyCoefficient=0.8), PyRunner(iterPeriod=1000,initRun=False,command='jointStrengthDegradation()'), VTKRecorder(iterPeriod=5000,initRun=True,fileName=(output+'-'),recorders=['spheres','velocity','intr']), PyRunner(iterPeriod=10,initRun=True,command='dataCollector()'), NewtonIntegrator(damping=0.7,gravity=(0.,-9.82,0.)), ] #### dataCollector plot.plots={'iterations':('p',)} def dataCollector(): R=O.bodies[refPoint] plot.addData(v=R.state.vel[1],p=R.state.pos[1]-p0,iterations=O.iter,t=O.realtime) plot.saveDataTxt(output) #### joint strength degradation stableIter=2000 stableVel=0.001 degrade=True def jointStrengthDegradation(): global degrade if degrade and O.iter>=stableIter and abs(O.bodies[refPoint].state.vel[1])P;S. 16fxkl7\u[kZ} AG+C*o懎|?v:3+qx&tBg $ bd'$"\l"K<ߜ=>#gy/ٚ1-yUR}Yy92-B/x#,/yw;{ͦ'O+(W7>gBPd Z,"-pq^]"{88GqTd;vk_!L'_1!aie l@Ujѷ3]H!!:9#&w4m 'gD}8$!CָLKdPQ5$ h5hZd"TPik+B\pgƘɒ1n-eh޽+>j?pd:ϠC~oMHh\Rb:j'yvbaV"*&DP,BxBb!}(gX2g(ZQ%x晙'A9sUm(lX1[?3fTm'W:g#ac5ֱOϴ>~Qil"pϞ7o@]w$)ΉHP0wuC b^U0.'܅ &'d[mm5Ī~0U/n=;vC*PUY-,yX"]I9.}RBQyCC&Vf<2Μ+Q\ޖ$F dl>v2N9=Dhu$+Q,PT[nԩYA 33Ia)y_՘/4#đG$ws*ȝդ9 Bi4M@ 1IXMHNʤjk+UoxqU Ƙ޳\V5ᄏxnqt$tp1;@ bG, 9330]X( c40I3"LurB i >Wb_?C{1?Lk7}TK֟pjKSZ5c/ S<'Pe_ܼo5юp4[+sD)Wm65Ԏ#>spt, ѝ |) Cn\h45q|陴$ 2/Èo{dvc$:P^Pd]V V64{ 3dعcO ljwN*<4X݃Ɋ _s*{ H)MUI\> +Y Z,2O۔BYhpXZBP(I^` O>9366޽ɣy|Lcm KƬ.?w=s1wO᧜}nP¬<_"ܷF|3gfZ KGBU/W(3,.Ci~8K5-K}V>v2,,v'TѹD_,7 Nx)cfT*}WTfăQBmEUEZ#?[yawZ*P0 JXw2}>Č8m%Pe豼~mnZ~\K?۝{w0I؝ݸ<¦/yf iJC'[-B!/^Q#I@*:m-ݽ? l4UTOV|+#V<J34E!aXK؁̣k5z 27oMqw&'jյ75F" - yŒV Y0I&,TNw 7 LYo;p姠{To.Y6fy-Kߩ;cӄ.a9V伓'gC"sOdY,OAZ4qqHȲЍ$H MPD_AosLSz3 Tʭ$`qݤzGNħ>E![Z({O(Y_qHR5,<U=8$fKx,Bn}zݧ |Ze2V0rUգaqU{v6YbCc;1OuT!$$y>~}M $ )n ,,RM -hb@f cI8`n;GZm/4}\Q6HH zjۄɃJ9V}Zmj @EY=:?M$6$.w:l}ǿ7$p <喉%mӳIWD%pc#Yuޫ)"?уG .XϏ};z>|w2qLé3Isp2<5qY7kcG>(`fc I|n/Pv PEB 0t6 Ԗz&J%CyBKUP`_?T&C -X $_Di,K^=k͊W^n$ )b8S I|v[Gurƥt9BLWݶ-8ol 1KƬ:|x ߰6ffü6Iհ07f'~B5q"/?{Ȟ})G'f )]]"1]*Cq1{$rqhw4M\Lb\>:ıxk9Txcz(: \VW a gC  8X"  >+%!!DrxĨbnΧ$\ZH">P0P.U m< 9uB݋f)-/$I|`LRqKc˅7k:2|薉p!?Lj%o,y գ+τō5tY6fy'|osղ3=tn{Յ߸ A6^ǿ~zT߫2q׮WəBdǰ0˼\J(CCdMMS:,A*{J soPR& (ܔr3B NpV-"f0o:6ӊ;k׹xm@7n햧_?_|:c෦*UU uZ WZġZ(cAIYAbC֮$%B#Fz S9;&Eju-~Ir; khw‡>4V@qL;zGL!' { Uªg;{ȏNxYB>}{%oh4T;v~3iNΡ"PūpU5-F]B vYGק6)]&dP}$Bh"8Q%"ε Đ2U'RPt- Tዄy'pAuW5MeL(MJ%}_ /}[ժ (T1HKUb۷~+5U ( i#6;ݒpݾ2SQ ˔ J`FU}N74bO̸QVq=\C1zl8'vl 9=#c{"ŘΒ1qEo[p4';L U–%H,Ϋ nDk\9ڣs|꩙B1n=_okiIbiQ%&.ld ](TOCc7l B~$) MS ; qqk BOݺv ӔVUu诔(#wVcEyD"hѷ؁:#QqIrVE|@qE$|QcE(WNԪ2:hh%p[NLW#W4#n_)/.T#1 RfJ  7xc$1nWd\ç}DŽNκ$Py=,Y6f5z7.m9Ǫcy8}^VPz/U".aĢg OlX6ʹTFSFLއZ#zKu )̼j"qTd*FGxB1fKƬF_^K|,jt=8풘hnO ^c{fG?$&$-$.i$v)$P l)a# qlԵT`uRZo."a<,WE&M^]+"|@uF\EзGvwk0v՚tw1DCtGq::V"T6D>>z*p$L|TD~軵?-p[>gӓ oO RQ!퇪fSDŽ<Tv|h6uá6A`8x}$6˙jMBI^;wXOП˗oX>(HbHp xKE'/ +w.ؐi%{6)zxձGw`:Y6f5z7VG3;6|ӳI*n>Vӧg mc^$sf6,C\2|L#IUU#͖v:h6$a $jSVKw$:m/¸jOf c*6^;]'TŰ`;2d} ! ycPlnH~IzǦGlLZ[b♆W& JEƃwb[ iBSzÇ*zw6~NxCGO1Fd^"\.7(<8H|Եa;P[W#VGfAio(C_B N D+SAOvϘض}\)PG,|X6fuA^By-X~icbɱ\}L(-DpĂSOH>1:ļu~Aw.|.8xnŸư1T];s9@a;I}'.iJ||r@=nii}>PsCJP *7q@gչ =2t'ւK UD哿M^v$T(:H*%qw~yk2;vLݱ oc,t IDATy:,y ߖѣi ijAN)wDull'?pqD"$T&:`i1rJGoФj==Ҕϫs:(=nH5+ Bo,V&*kR-^G-q UU4;uOgJ(-hhG!6$Sj$61T"zX -%A$~:RZa\ǟ_Hc; r;C9-x T/gp.Gp`xb"q #z/) 7~z2p8Xq[)7GpKqXS)KlqbTx^;'~_ӳFu4W#KƬR=Nxr;Ѓ>3LR =M㺠~$lF$9#Ϟ=Iҭ7K(I$۪j \C Hc4~LR=\$(2ݻ'Y>Hݡ[psm*u 7uuYqȴ6KqGbn_O8vAӧg҂8$N]^B1?KXr@ 8&vz'TՊmTZk33U1qdvܘBIAd\T;6A'C`&RPcS՛5*84U8O y:ilmjh@# <PNjQ$n{&m?0 0wJL)MU;"+!3:hja |*('ju>i$c8X`dvK b(E\iuDd8Y!pjwf,^" f52')iBmQ(TmqJ\ka3P٫Z5D)xYĦ pcmSU%^BuHwugzc (;=7' E\,yz]{&#ܝ$yV[!l=;=o<3+K17[\PeZSzX\Tq"MБ#N'$f&* /U3J %z\X=--IbaAE c8_ݹh<$zCG,lu$va(84U#5?(4 󜫄sgkM`W{lUXk9 4-녷_"Í僵H~{!`hy00~W0JC}nN@%rP5tO_+e:ܧ{)}2pRIHZ:,xIJ/, E#e"8Rpx5`8$!k 9nZh~> *(ؒSxˇ>ӕb"E ޳^pMr@joUx^S.ZV,9 a )`DI3 QU1)ɭW&8f IUd==+x~c͕ƒ1%Ͼ ܝ)3+O== -ǎ>;\]2ItM:jZs^H ++t ۵LӔ՗Ztag`ӎ*oԵTbM ] {8Tķk}%"RԪ5#@⠊RAj#/8R<@\ ScʅpE8)Ctl4@$yyH~M j[UCu?v".'Gh1j$6. D ]+ߩ O7qcoF H x꩙ѯbXn4K @׭$nCw֮@_}' >2n/pV7WHTW*^Pv"my"9_,ꜛ$Bq HF<&#Dxx:*/iH[v>U*P _ݑ2Sq}T*ql6ZftʖNN2~V[o4[~$q}|xQj(U&(6+@UqÒ ;XT}CGU~_YNjB`[A$'6~ிקrR\Y鶑6e\V,Ha#v-iu<x _!ZYlmT=yARఠӁ֔ JCT]##J[U[mYѨg[>46J$N$HA^"[9E摦x0zJ*&E߭o?l77=ޣO] aۘKs=4-\^q(|nS*X"ThΞ!D>r%e\Zi$G$,w{2xKM \¿$C!uUHvUktz@LQWD ,xSxT5T:wct{C̈́WXUyN9p|B jm0(W}VOQ`k/߽JiA`!AC*p!P{WwC=[d^lq}VR},X- 2x(}õ^G8>:U}A͖K)" l|ிVaۙc lMB ;>/tp7zB!9jzّ9njFt qT=79W@ps 63 pH=^Rlj*͆)U8ma2j+~%/N'3-V Pͯ׾o!z?JQǦo|Cn'<|ʯ\jYlwtlY@s@5lPk{t3gf(hԵ}t:0 :CBՆ 4uTL:oU &+KK}_~K jފDe(.=X"o=ꠢ5y}hi1tfo}e @})vާi\." N@[m%,յCPq\uW2x;qOUÛpJ&B7!&Rg|]J(88HPA-b =[ЁJ"̋QpAGRE_(pOwH`>pi_d&iT5'wu7J i*ﭧFPԞ_[- \Z^sI9?ܣRaGo#Ν/*9y;nmMW&F"5pʉRi#aܽcej F= F%1d4x)#0hrFgy%[ gÂ"B\$ ev^7QtTT)9$Kbf F \grmѦ7o5/mzﬥLd9._igXEat:8 Epbt.rjr95Ջ՚ &2Q H:+)iB6_>nO2l<2嘱߰!?UXڙc͍=i3[Ԩ$N/ fu^TV##ҋF˜TOzg3ĂG=i\v{we=ժ*0}>./nӎ#&ٻ+ge Pښ`R Byb4 IDAT'q F7of93jU̘>2X\6ؗ?T_X,1NqՌHmH8h/dI5n&=RLٌ ÝؓezS7r>E@-b&^sa>JIJR<^&$ws`m3gCF70$mH|O̔^(L@Q0\/.z^qtC(^2k?WqByi\x*rL_=;qWu 3fEpg*Ѵ" ?Y>Ÿs ܩVGc6r x4DRm c'k'5G1ww}1W<Y`)b6+w+dh8+$MpZ\鎨Ia∠όLUgy{i=_h2窪~|?to~j軗[狱~|^տX3y`4.; qםAI+U+L>.Qt=tՊr\hKƝ䩗rEME Rx _2-IHSދ| $08w~Ur'u՚- t:lYf IW=ڧ_LC,V #'|ٴP-./˽ݗb5c \T=;o6wpã˶?d% qىe_d9SO3ɵWVḡvr%w$vZLaڵ هVc zYT[XrjwěF3єׅoοgŪè"qC#TqgۓIJ-A(͸ѷSDVV|ddYܝY 3YJsyRIyBtI WbqɂMB)~ d́Ds}yOg9Jj qߤ m<]]̘ܽW0 A~ {ya%V*!3f*_0TүXOK7ä'Đ'jM/jQT%aWcJv,9}R]yT 1tod:+<ݣ-b[#w>2lsVLIG:Q}5|:x0)ңȱXiG#ۜ!݅O;E1Ikw:ls❮uI* ~q+q>3gQPRpp|ȿ~?z{ 0߆`:.S)͎7CAyuv])J>v%J3B^pMg|gɉ/оvU9ej?7mOZKb0L2 W4-Oh,zhWLY7/<Flxb-p@vj{b(ڌFѽW}|hD'ߝW_\v+u `_>3MG~Bd7nI8q{MMb:ڧZ d"d%Rqtҹp!yC$5wy6Μ)}C8%'2^ ӍbB| Yw51Ӥ%wS'bj7~Lɒ%mkR%z=#v `QI; Mt?aT]_զcqTϥEdzL4&H''qtqvz Hz>:jxы1:ܐJǍ_IG$S;E :?p= yQܙ wk6G=u\v{j;>W*R }ڐ`ߚ5OlKy$^[`jwy)2N$ FZ2#}߯m:8KK>:j3[Vs4pw1z;H)*k !wڜ1οTd|% I NBQO%pM, "wUZ\,BP`u%ֆ$IqIס b!tl)7jXHVWc%Q[?""5c\ڭ0P ٚηOȰMK+_O[[^*Ĥps#X<ÙX*,wHr茹tms3"Jѵ=?%4!zC5-/>ΡtL]k3g=ϥZ`|Jt;Ӕ* x}><ʺwv6H(| L");|?'$Ry31ȑvO)~qDS7+>Gj^!;!EVs{nx˃8vǝQ:{7cGG0` Ze'}_+B :tАj5%LKcoʲgdLN2X2(sB+DZuDfOp~BE AX3xtΝ-l ă -YJI^(i.(ω,c*y7@Xh͂DXJ1йyfm=c@hA` a)@3x+%w 3<vǟ!h Q3L\"EDpޖT8}[c Cܽ$p>Sg#D"LЕH8+qLM85֎2-<]Ɋԗ-Aiӝx|B(M?f3wxXd铺gR휂IxRLJwE/׎n1R̾IC޺Va\tMX2<hg 3v,`RC{}B 4kH,j?=ܹTU0)£ks(ӌx+[[sc1#Z9v|w:[r\D3x3#'!cB8H=:] ӎ3(%y[Kr&3ݏKcéybi=5Ά1vM,GR8iB\8ENjwܙuTHUOr⫣v_M?޿sFW 89#G/{=Wߖ?׿NQ'`:.; xU8pೕHV,_Z2%mwyˤ)Cbd.):r,U| ~L7%{vPQUNiHZL귙%V_^D^JzD^spC|Ì~g 4bG{IA͏(lמO6w4rZ88OMmdOvח<˕/.Y^i^3&DwvjuC`sLߐ#U+c-}@ofBuwg$v!y獹4/(}RH0p8jeu%=mZc:ӕ77̰@mfR' &J5Cl!?/ 0&E~]ߕǺg~3H^DL3=KAHt3LxLS=)$~![d&WRGWt y[MLbv!\Vf ecTOάn+a:zZC!D# 2͖Ō;;W`\~c'/5ӏݯ\|t} xO-O^0x`0dwǁTyL!BŘ\- ! Y9# &GW<"P O/.0+ܽ(p8uUUhʐ&zۯ7ӡ:d9c){{[2'|+ vM٤y jMbK!ӱOd>4SNjnSbvΖͯ{K/7_CCfbظ7z7H,ah yy02Ӡ MYqT Fzw;LgwfBqd13KAK/qTXY;*@EU13?%L/O@.J2.#;*@4olMpov]";:8ŸL=5_J׿(nI>f@xcu}/ؿ@)?=y¢gH6!āb|CE!`Ɣ)C`kaN.QDys{N7JJVRziK \ڿL,=V;&(eQtc(Ez0BK̚HÍkFg뵚LL;TcԪ$`< f1Qs\yK ~W>|>T53OpVJ譔![*Alε=&K}bʌ<7me/m&9p?W `׆*bʾVQ0VjM7q+mOP5lc#I>wޥ,7~~ xNx|Zŀy0^*| AYPJ-#\\,Z(ٞq\QRB\Ff IpsB!eճ a215o\`ue}_sWEՊ$d\w\2v p[}m9%a{ FO _<0 G ':CƗo_t#E!$uL k;)SOܙ,mty!#BќF~2(l~ t8P^{%'0 Cժ&MG@fJ!cy)NO4i\hH_}b_Qxb?m<.&&)iG8#cRUw]ai2LʲjEHC=/"Y?~ @Svn94چSyeG6АoHBb5aq8>)zU f~g(&`mm%0 YL 31-22-GȃlaLl p:+1MSH畊Y[soox[*f{ַNޗf+k;a@xmbuN|/^υL!ҨGY]B^QTΜbu+d@0.2 RҞ/;,02l)`|tzޝ673ޠpw//d [(9͝J!ڵ=w534,McnJ b,\q=?,]9fL*AtfTRS'_~9EYu%cTB#zZa߿=Cy9=:YERXx 8.^((Otd6^XE5 ؾGs)qC[Y/epn8ft\pҙ>}ҽ(q^* i{jXw۾˸nB",̯~xa0+#fx;߂C.Kk6j[i|[hkB}"r|H&={@/4VٰQCLni/95O.hn`:P?5Nq|@_^0x˾W;(,-v ġCwתs`tZxs IDATɼFqpu$U_kpJLSFoV^cL&1M%} ~K]lNgt a.iHn5;]vٝ4Fx%[4շ6wTɑɌ)Q+3L. `qz]eQcَתrʐHꓢg;H"F:&} 3"LK-p6Mj3E##ʸdy۟,#pyEv4V&1 Ebd+'IDJ1x1nP#o'YI;jhR0dx }|#|zs/ྻ' pgN<\)Kwn͑!m)7OP,tw4J@sM׿ߍm?_8$1joz/f/R &K> qd6EO^kv|foڨR jp9./(tDd]oS'^,o0€y0!WowAՊJ1ą ^向,ͨT$8j>lqð*9#C6i<HB&s9[ /\yU՜ZwdB[M`b4/.j|qR<CiW*6!#KֶuOj[VR 烂1 3M#Pi1;{V22eT-?iq]08gr'+U.7ۛ`L +T$3gɀ=Fҁϊ^g6#mV/&SFO:}+3eapMj̅CfОƗ>OgpP¬/<.ίo϶4ʯZi kk$z^R `y$g=!Y .I2;j5*B(£sBV BG?-|EU3VF,äI!.1v<xdW5ha*"}%_t%?D&n7)f YQљ&HL!}ݷ1RcKqh*U!bR.'P.ZĵOsbtg]r,K*!꺤wGL$M1bD:dC4U+bF{4[9, ip9+9!SҔIfI#/NPW:jK+>eLG!!qHXQ8t.Ɣ+0{L}{Z/G);ޚ!S|>0'jΦjXr|9"U,^eq]w{Éa.Fz+]&8<=vƎ_3wK\- 4<+=u"e"tou:gꥨ~wdT7=$+nD3>74RK?H*"zRyyżെ`;w>$/ Iỳ\\1z%O/izYV"ccA}tT?AOڔx&HƜ$ ^Aڐ-k芾 ;q4%qΟd^VPܝq ALW\߿vN0|Ǜzbp٪NdA:bԠQM3vzE|}9 M;ETAJ ]Խ;;?NEA2>ww:\%^gJmҍmc:y$a+s^,;l^)Yr%ctO/ljWn{3gNScy:6n 5+MŶGQO, j gkMݮ?~.ʁqeO |PWv|zK=w;#&MWcA3e:#O!NG_<ٕ|٤= աn׷XAx,җn-k\X!f1-Wv!T*]}ZxNP~{=bqgu/fQxcޟg˽EO:p9 >pٹe_w^ahHy`HzX\*FM}wbfTjn39!|V&=@T]_y.wgrmo\~fI;fsKV9!1g{0R8@iASzk5g~*+yɌB9Ym؈ (b1.I܋1J fJH2`C.*SCZrjdpMc9赚%-bjyfSr./t|շ9x`ȀXdp:gtWa2} 5Uɤ-̈́p]xMw{=D}ET)1g+lVHMA둮ョݽ@A/^7XAjŋ~Օ\²w jUܽ2☱ MڑlH8ӣnOnw;WI~WxخbwH? Eq뽣?` </}oм;YF9hD'Edl&?)"E={^/|0CƣܻjE*eSZL*JO 3.^fXЗ~w*'{vChG^[*U%RgzL)m$1eO7Mh|o_# m>0w)^;iSȈ)S_ν0nVx&ǂvBxyҌFGe6͵\t3.N#OH!0'dpK;f)z]WXZG 'cbtbܖqķ<}pJIzۊo7\g!]Cs#mAƄ!i˞zpKzTN5]tg4nןܭ+5yߛ<}||.u]%]o9iRXtVuwvv x1'YtK&ãga?ye}9߽^g:2XfE,Y2IYv$]NKT)G=h[#U"KUtI6AK)Q$N,@ 0 00Yz;٩yUA|:[Sc=/Te| ΌC*Kg ܵp8iʒT:fzXYVsnv9|vBds~.,؞벽!(oܹqqrl$۷$o/V3LyqϼApG'\u t>V\v}-)(B?3դAvo -VWtC!\L{.VZ~ͮ-orm8tWVRC @U6u=:6%VPCle9a6+oh2CRjwz"==@43ی&lEpTAjG`3j9e;⊠z O;K.KfZK3UhF字Z`Y`:#P,ٺwzZs Ik8y"Pwf߽OLK%R] Z`FA=_ĘOnz.vm&TLFy%>"d{yQ _]MPc!eu4y۷\nwyPX]LD9${NNbz D흓ܾxn{"({<|4.C U q)wʼ65p&u1SIUz$8"._J'Z@O2Tr2$b x DlZV,$Z^Xĸ܀yla!^KLv;cbʒZkͶ:0Z ՠH+Ά5!u o{n~~tNn]bTQx Rki[8 ކnALRW3~_6ե#[c#f \m%Հ\Q( -a١:MT,Ō--uɩ L]deb`P &^clKC=.-\pcuEU'fAҽ /ul CTRw'Xt>xg}qɠϼx衻Җ9xnҼDM++3*KkUi~3H vFNUKkKKLfbTS&dYWbIJl܃qWff7 N"zm0fRO%T 9?_X.C.ua1s@x2(ȓդ`ԙGB߹ǚ..J%v#ӄC@42S ؼxSW@&%UA m{ԼmۦiQua#W2!  \ۮ]rX$Lew#"GEau ͽKeUi˙EMnE5a(:H#M6K@}~~ݹH+ֽyK2 c3 fS;Jn5<4>1[tt<۔TŨ<9jc>^-*nGvP=myMMmqN44esRCuERq]m5S9ܮ\:Hb4Q[n/~3o}cKp"۝w6̻>.~KY^+O,Ŋ\KRި$zP#W)FVVS)#+Rt!}Ӓą )5  %L%N;3]rWI`C ĠdqE kEM_ W`/K<#^͡Uosp&I՛1RDY %k?&M)qR~|T$wˊuȵA!`Z4Lኔn/) msg-p6y\W=#W/"ݒJ=/uv_zU6UK 8) kΠl\R=-wl+*07N쁩5{Qɝ['U\2ܵGT\Wr~~ϼg}q}w<*\Rm}'\=o5WEj4/\HYFC^[w#ʱZK޷o…tmCYpkBSʲr! jTNӡҫ add'uhBҖ_]/K*q5NZeɦEe\9l;ŀ*[a}#5^[-\յPPvs]UtT珈)Sߦf@T0HBSDaW%"= رfIրBT(DӁV3H6իṼ+ڂLBŤė@V`NK{a7*K_i,Qt;5qv2\ap<9xbXN@96 >( "kaEd;֗bTX'\:z9ŔesΟObVyϝs3-.VSyͥޛ )毉 4+R,R,-u} C* H:Yx:c`ؙPos~%-M@ymWG2=\Y 1RTUss.,&ڪOw@C$]Os34*/7 CbtpuzmO$3IX'9[0ДEY0 0r'n83ylhZơ(.)CX$zG ܒ呫k^jYTYoV֡3-t}c%gS_W0͔j HIn;%<afWVmӢg|69٦3mO%匔҄dǠ}֤uлlW}@]j58;G"2 7\uSVK{9 )Q\= _[)NXLq]_ Y2BtŒ/e_f1Azv|tr5r\.̟*Zx0VKT# D@<)uO7x#P> ^ن vX^Jw2'羹0ҲGvctL䌋nUʊ});6a-N7"< )IݮwD{]$ Lu ֘=0;"c^JL~ eesydSKddh8T;Aks';3 icX_GlCCvaɰd*ueb0~xƖ Q u$ |egk6W׍bt=L{6o&fS (W˓y)yYPwyKX_0.2>Ut6E'f 7-&s ~pQ&>1zna!%+h.;GdVcoܵrK/fl0W2. י`3[nM&v񦟤4nj{歲Ч}\3>g=T3 I賄 Agϕ:U0f:s&5rvז5_3b`'gCP ^B2Jh tk`@EH,Uz^ޘA) `98 )(Kv7o'{biß{~Byw IQ;0ERt=f'Ϊ:hO5+ o$o}VEaG3-CXy1HO ZAb"Hn,{$U3'RʶόȐJF6IҸ8f˝a+#($HUw OQ0&d;$O3e>~!M,mRB9fGQ$$3f:.xnon})Z`"mm+zρ+Ax4i JWm:.VA'dGb:MLڜqo4? IDATe7n >]z.s3=d qSݱWurpbFXlSNޓp-ʧg 5. E69rקlRrQP?{M7)JG%K}\3>g?.ˣXMaD.fMDg%z"~殥Ŵ{ TsAgaؾu_ !Ȳh$F&CF<#f:!u(Ψ5gdWqS}: ʢB$ddʆ2K"7m3mHuSf`P3斘qx`1] #(%?ؙڰ*Ny*RwIj*rjo{,y/<('$Na&{y t>Pow Cݏmrsxq`'ݞmYÿ >VMac[=͢8c4>tޞ8Pg9bx(S"Pűkp,:|Zt䢓{^OVXREo)d mRgwuS_ZJ5$^sK, _"cg+P\ W} }G=G&140,j* @2YT@8ulG_ؕvE'ahv=1p.)D&#Uم 噦C5YML;`-!6c恮' oc z0H LWvX\67[".~Uk΢\c@Lx5q"y!eAӱ;/ 㕓؞\t}0XҔyʹ۾8.0D~=75I 0UEDk$O%D@P`{(n}bwSpqEM!U` 5q\A4LΖF͓xO+Qp!줲CPt+N\'[mX0/~I??j,1+:pSh̀y\NQi6Xw}!Ni &2 v8#B4TnϱHp*|qZ,DkK զں*&k×!Z(?km kw+]?ׁ>O̻>.%}?a=AϷxR wcREu b ˈA!TpA߸k~n;3VvT],[_:8/'zWG|xſv仏?=3>#y:djoywf3I"ϩ8k{N5+^]#na$'iX^NY7"KK6#D\;3L{?U-<W!Q2:[<ڹq&dgcE*/1"tAa/βhBYԝ[Dp=1f%/ ZV1#$[VKzrtvf"~Cycfe9ټZR޾+%o- O%`-i>_3[ik+J ̇ ȴ(1ƗgAbn[; <)L:n'Lv=w+0{SނQXYuOAPv'[VuMQH)a8p-1lJyAFRطA H܁5o,frHD3U ֪&,3*>[KM钒G6.eɽ %ַʓL.A{=?h ]/>ϼCp^P1^E] o?񙻶54+5wVnPxdyiYN^_ ߜ,cu95[ᕒNtB&S) H<47W/\mW+I⻡voȎa3(6>Bk93AҲcBhc<6P&Cs@Y.l*Su~\LښۇCCXgmr۱b> $*}h_'}i@y%>~/j {|CzEDO}n!aakdgTڴwz 2LN ud ]X_JΗo 5Cf(GRz=ֽe;_(tWT5C Xnִi'1Y`\d9yԀ^[Ei"Ԍy4/>= ju|0dl,;ёft$X*G TvLZ2M(E7~S 'ۓOUx{ua f+|msb3 7z&N%sQ3,,s+48t"a0f8-Lfzpi0ex.j&t^Id"^.?,k"Jz `P*9A"!?]^f×P"j W6K?fe6@P`'ێx䲽s͆RiBm|rݕ=HD4VYOAj!f\ypL*m>g )y{ͧ`%ESrgu; 'xc1[ɏ0o仏?3>g>eMük}yhj*}n~FʲW},+#*{}sKK2籖훷9qfrb) ):IbFtp([WnrBLUA^Bl0GYKS#t-BժuS)8u5)yԤDۼkQu;-Y[L6 !0x $ΠooKuSacuR!Dp'oYTR^|ڐ3 x\Z1q{zIoYU!pڤD94EgWсph?G4sDm@*0(MG8Ʀ喷k_&r> hL30 TT$Mg?.ɟ1GMwkT`@yUEK F0^KBж ! 2<0!kQ`R")cia&o읹Cg*~ Nv;B/`Sls E>|_ c_?7G_>}Dy%>~/x/E]#${FՔ7YVYcj6 Z]Myb`M 8N\U Wd"#cҕeVZ]oń BAȕg)#s^8tL,-Wr4Lļ^^L@ү9%gS&*<W:Fbn-Y[Y#l^NBJ8P*8$pfcѳ)%/66xFf{ Smꂆ51*+b  9K~Ӽ}8 !%pkT?vcxJ7ٻ['񎤔Xo;?UvcE]c/~Մ hZ{gY6ӆd9`ceٕT[J [3 \s&ε .ԃEdHT ,߉Es</LOvtk9Mi[6KRɞ=Zx (~V?Dwƻx|>ϼ}x/x䑻C/Lpy͡WcdmՃ paэ&1} k7"H\XL0ï&,\'2;֛|  !ϽP?gž5jhuvj4oMpƹcM˴X!MTYҌv bW}lm5FK ,t18mFl 7I^O[@0W,PvR(yq2o-eY}  `?xb`ͭ[7zDJ*KOnw82F]zL Df6dj)a ͞eUVo@R[?M }4zԟ/>sEy%^C_({愊:d Ġ%WU;kkT8uRXwǨI1jiŷ`ZJYQ1P[Xh%1S#']اiFSSY]iyTH:|+Yd&"FC;3+{EmϥFCGk9Y&%Lm3fsCËO\f2zvD͖Y1,A08;kxf~'ښӫ]2wRZx)yQ |~[nC]Z$\bsuW oBuEpoDmזgo'SoĹ `W% 7 qF zsBY +ﹱQƅ#fJ$^2 )0\GeI hPm/1*n/U3apn)qoݕז;n*dޯݵ_;TNs~%0݂ʞX?-ꍣ[f7 ڽʇR׊oQ'IFqϼCpb^QCx!e9Y$f*Q>Zjn"훟Y40EBRyCp{Mauյ%} zCZgRX(y}tOWT2\JK}b$a('cTQS uKN@re1,hXA ݹG,EBT##HĔ,.sxpPSH]/29oh!Wo6c/̖[g7X]Դ']o ψ3uf:I~r9+w-<2+A.nҠDJrֶ byu?2Igo ȇ{0j\IB)q*7\nؑ薮a#]6  w_KzYKO.%Y pQ9iDQYFIpbe5<_^JR xK}^C_۪=:T#HK)oH]xmp1TwI=W6z 3QYy{"3+6gp(۞A{7[A/\l(˹&S HlFSu^b^B{I- coZUMh[{ {S(7)EFC3P}sEWv}2lv1Dx}ʵ\[:Fqu4yH^qeIRooҨ`g"Fn )1sT#w64[gD1,dίL~;pX*_ߝWE|pJmn[HotuS3kQ21[0+j@~yshC>ϗÓ5 嗇WWz!1!tRTwfFxTe1PҘ] -1w:3)w?uGui*J|m㫺{c$H훗4WW_y A\; ;,g–gv]mH8~_"v=I tϼ}G=Gؤ#zro9 5+3w Ews]Zu`9}3Ōu50x*W#L3uxny\Z[ss޽Ke#Wq,.{8C=LHUaegĢ)w=w:\"tOr^uGun&^J48%&K)Kצ[Ƿ,i W8$ݎݔ OV!Ⅷay%|˃zQ6/ˮ? ݍW懔x#qBzgSvy"(V-^a8TGU_ JtYX7ݮnW܊ mMRWAwlV3 }5Qx &ZAcɆ?*f2^!xi$T. .K 1%b IDAT#o1U/32`Bzp9^\CϨ,=_*SҎm,3pZL, ó|wjl߽#+Klbs.,4{sQmLXM1j *} TUhuY?y\W}yϽ7vl%oHK $$ Y<&3,@2!Ln%pXb-˶ lYn{wUmIb2PG|֭Sn=}'' D!ޓ$v5]Tw3?D<]aX*iڵ1 Cqe \IK킒ظ{ [ݭ@@u;Ժ($.z*!t_o`ƈe"ejqH&lA؄ɲYD݈w꿬 C0rΉ~s$M텷2ؘ-΀ R!q82n 7N@|@̋5ѰXӢ&33K""6evb5 Po>$938(f^Uj'% _Ru=)ɶΫRW$TN&&*e ^0 *xE¼_:}]7f#2p81r&}M2!ʡa\cɡF CfzϭuTY89$NfiNR/[ʿjavF(6]F.$e/Nk%xlN@wa4H X 96@&1oxϜbKJ~2iaf K ̬.9|ua$pIooѵ JSc6fvB\(RȈZ"N-cuOk ]`.Ɇ.P8"Z ӁqY5JRذ$ӔQfnpfxh6 2LL5U݉񭿾={ίiU%cg ]s&ʪ25$6-jd|Z}/ k>4˒(K|Zb̊]+]AHTO;5!\Z6OC+s239yoHX;x/5>٠=$+ձHxQKkeq;DEa)~35CzA~D *!拑Q?ձI5f?̫١V(ptwpȧU aT{ Ì-13 ~Ϝ.Lfq iLV8hv  cp(YT@Y?_,<漕rL4O ]\zcHTKCS|?/%kom/k7&XsEdz!Æ`)}hF3-'Œ*5MҸ/.t1g]"4ql 3Z=ah)I(s.ݯpE]N2#2ت|EԮsZxJLĬ-TQg UYn:[,ˊ\i78DvF9#xc= ~V:B(]#|%DF SerNl'} q^$ƛut\Ubކ?ᆊo_붗x~_߽ӧo|{ƫOkx/[aTDyt{g96Vbީ;sNUa1<@a|M-qA`m5iutԢHaK$O=#HӶFa0Ƶ8i5=+ ~qO+;ʢYL4{vbwf\ Ք/0Ϫ^LK{'Qj4,TC3_ Rerpt3'8☉4BhIlV47| Ţy7<;؂LD16I͎C ''e'&gèwq‚1_wq)[Y 73(m9)-gʿi:0] %voM5du(9hr(S-8Y f0{g" LbL iBHm-%ypM\ٌBK%W.w"}w/|b~ԑ^0.(ǽ dF11ay7SԂ AގbN+6z+\k++!o/:fuWw.<43HҦO%U:f HIı6Q h€CeБ .Rҫd Ηw~ va]Gh1a0TMk.WVX)a5O ? ..-K耉nn uoPWh ^:~xæO"uW4eƉg m>p˻_{]AH̹|?ZWFJ3@lxfA6ޱ [9#wBɖ4*ֶaqSgjԭ~ΞI)::Z. ӬQ_S*8CH=DZK4ϩ>XBC'f$μ85 2$ MuJq)*,=(|ڍ\I*pO/Rq}`hdtkjk9:]]7z* 1›΀,̳yW6 k$F;ND{|Ė/(J"M禓`}|~4"UɌQ$336S cdplJEkqzS$.̛ľz72'JsIaD,*t%`|sA&>H'V[n(.ڡ R{Af%q2IMX8E`y Ӳ boE9"0YiNW/t3Z@M޶'TW+%qKֿ)_dJ~{&{kt:j(u{|rz5Auw5I4|*RD}|P(v;o5?}f3ݟL:¿ 󮠂W**erʥ 4[GccbD&RJa>8Ȩo [##>;. &106nQVy{\ojv 'k[_)!#i},@ę~QTØ,$jI+VR|ckRL d3Oab5S=p3RS1߰9nmЁCt v斯P[#vs@O9L+Ē|it@q./ho^@YwœIbޓ`4b ` Hw96zKs.f&OCN@ysrMKm[--CX:jåj|*2Y&edkcRaY!fŶs̓)OrȋEoakE@֑$ VU%{ {SʷƓ+\i$*Lܷ'ݽIofixo무S1XO;O,xK}ko}Oo0fz3[v7o`yWP+G9J+rlxjF].P}HSd0<ڽkehuλKW'5_ϟHu8GKG!ǩ@> h ZN'٬ HiR5.uw=;P=K<'_썺:qm -0)Tll0#-r_slhƞ0 ʼnȩSI]SP]ۋbh !xsxlʪڥ6:xkb==s Zr$R{/9rX +a<4K fF:w@yO'[%'k5f6uԞsrΐdxcqk_ZA,<zmf_IbLH7m"K;(p[P,'>q@kj46:. pfx|1|m<*Z0cۊ B?<+5U#c2 --`q(vjtLt[)̳nm`,I{o7\կwW}˛ox^\3[v=ͻ<{n:5k-[+M1Uȼ~#z%K?>'6mw>\$3gLꊋ$IG6l8/gΜKM+x~3GnRPaTJEyDxt]QvjLO?XJiȈK}%MHwv8GUVCC~JB&]uUeqN֟ȅ.tЂP-q09!4xGR?GIH4aFê.س7v"hcg༧=[Dxxnyd &P`;TfmvW68=d Ӟ^k5KIWU@ؒ|Rx{Bo 9R ac 7MhǪK 4~$~aTY03af4Tiq;f%P?mJbQu"y$}T,p񱴽'5뀱6gyo?pjI#f1X1=rH*fK؝YZb;G˼=y{ ڍblŢ5IbD #dJ~-ffq"Wn-;6p^c I,.?fi/+O9n.$ Ilń91'/Orިna x;,}"qƹ>V$ɪ[>tnpx'l}t} ;vOsjCD>_0/_Kd޿;<::ϘC#o+jjr<0wrU{L@UU\tyϟQWWOK ]ATTO뿜9Wھq- $ؘwȅ8*}8i? f("M5r{][?Ffvr-t’:91HeNcHjA2OȩzUצ^yoqcfďUt868ĸmrxQQ))g _$\ @b_Su@d c"Ie&;ތ(sFY0#Ii+Zw0h5lֽ`;,ͯ))6V3<فJ즲y8i7Nx ЈXZf<{d8y^n=wwFשE(4z1ڌB)dRs{KT~omOԻ!gC'ZCS?c!%767- B5Cو*fӢPc-.[G{寺{#g?ĩ皛oŹ\N|7u[o=:/yEы^+s羶kE kk7?K/x篼9wSwo~fGUUS^_Ws8WDcQaTJEyx.K:yL@qliDڋ$AP E3A@gPbЁ$Hƪ_~̪Tͥ=Y +,pNEGYuħ9)k5)MFdI2dž XZ6Pym|Hd ,5D08tif ŵneɥ P2`&5pѸخʔ=.|0dΙYERLfs2aV2:|;S3/J:FO"$Y3 =8}i`su>@>\JGdUTO )ʋr3}_w}ɇny՗MOK6G^4Iŧv>,Y?}CsT&} DTs3$V,0Y>jrzg(0o&itW9})YLH IDATͲ)StvrmOh /;,TC!;zԸRll8G:^gCbYFGH1oP!ƣvhhh:SE=?j5by@q^S'piO?t T5ؗs.c VQ24)˗af=n4 *^4Žj .0 #r}K3w҈z{m0h^Io/8M<o-#8H0K A;5D9ޝ{`'F,tpnW|Gl(bYJt񘥞4icnA+>{e}ub[Ө=)ڟ_[-@MC153ˣ6oq.LBXaGShuuc`yX\nn|~)~29`r_BdKUz%vA[|Ï Igھcߏ\XyWP+DECh#*" R26f Cdy'uʔkhU]dv]N3 :3 5[D#3dgg r-8u"Uj8{yaHY0#0q<͊c n!iDF4w1Qlr; 4>j+܁Z- !";d4;8 Oz 0QȃP1{¸@̆A3RM6%H؞=i뻻7.I/ 'vz~`%<Ӷ]N h@+ař~Ìv8m&)11Qt$?o0#+Դ9c&%ga̧ m+pj!sJM՚ \ynԛ1/6T3T~`Y±ҭpOqEs/δsoK.0N`HGc$rUbݻg^Q#qʘ55[0: ]BYCsp/q sqcxhkz<ި]}Ӫӽkv$N:&abzQ$Xa $Γ~bEL=}m)}s+eG7|We?oz>3o&^|,?sGnႶq/>8mi [0lݲ쒬 `\TFNtb7k1e`xM7˭n cGN{f#0z7}uχo_jDی͛ᖖ!Slf5.~hhżu86Բ7nr07`qКqǭ[_xݵߛ8>NX}ÏEyWPA?Gx衻jx%lyK5;ahj \&}*0o6"xsk58t?pgq;s. 2lWyw&|qHjwRy!1ZQLX2nSΑ$1.=;Eitԟ' t*S<7 fXmhN1yK$`;doxoW#qw~2ݖf8a/'LȌ/㡭EO{{/6I`}ݹsIZyޮ!`I&)1lJ>%\ir%z Cyo#N@JM ':E?L-+'( rFlFo=xp,{{ RIm}i6fUՎƸ]㙭2`#¸rKa)|s3f,t{:0P?!(Eh7kV.σlAwG8ڼwά 95-g3p :&"X_f@>!XWbu]]Xf,KDnFpz=YME׹]ZY|Y}rӶu efvGtMRwCz˅#[TwTr]+EXC3(Fs,_'!H\ q^':߷xK1_Ou)YfKs .\JFҢ0A`A.r͎NTC @Ϙ_''QU0n7; Qrs4duS=BunY( wuljkCfP_)Mz*|g-;gm{ h;k=O5-zio۱w=6T8֡¼+m+3T.%Gp06naXr>]޼RU?$E:ejozxR[_ ֭푘NWh plX5ro/s #H%Bn4QQ ~0T0o6k4kSNu''< lqtvqN؀Qp48Uk{ ͫ'lg{ 'nzoWJm]81sD{DJ -8)o-VrͶ#ipڨ @i i?fw-;{℆7= `=Ӟ/Z=&Z-LWYbvjf0L 3ʺ4C q/!_ZKYvج.መ1lnNefg$'7O`ӾChu=Kk8Ɲ`;g@~k~.ָv(Ȭ2be] V=pZx:1,\cK2~/.!05s8`5zromxPaTPUl)]*̆FJZIX-$ȕhs-ۃu"55gm"GꔠoNY]#px9l 0+]-"Os",}F;8Y}avS0Kъ&}y3h}b,$IX8~q9pV}`vSxji6caHh2Vuad v4#Ή5n6Ɏ0Y:9!yk35;m@  *ǦDrJթ~nou#yK[lC暗>|gھ7غ?Kz/̿\.U&.x!&&Rn#Fy7@?pRڵT.XɐL`>!x?)NfC07c."zùWmkGёrD7k{=5eh0㭿ib˜y.cӉM1g\G͖pdI +n4V[aĤIBYt\*A2*9 T"]e[1Aq̖*p%N~7:@Ĩu!ΧG@ g\:dZ\ATlUoU1n:GSSj+YEGtʊI[s4-pS:\ߵ2? (|YW! }'T#o!%aƕeKc p\-gl7&@,v]Ɖ);6= SOd,*[n}`0Z)\a'7Ww.~/xU.yds<$_nR=D@D~Znǂo{MeR/m<2Y 鰳(0iGID'3{Ȉ ܷ5?@?-W;z/?)3W<+1}篼yテw^zvoz7uwf7?k"濊fǏ` 4>ZHI)H_Yi@pyad>R 33D1{ x`r2f{|. ҵ'઀wvs'EKOȚʚ693̖bSqC붵b/aBPޟH^e;Fx  6 dVt6J` $4 Aߺ8!ոՙqGpڑ'HG\RuCKNhB'V X:gNr5؉&^IsI!Z_r3n@2mD 1 .l陰`x薑G'BSRz'?a7 *ip[M|hYĥ% 22kT ifƶw{< yw|'>^k,, ;1,61 64qU,2hP7Oddc:1*n_~t 5/tgg+Fd:`%N)l LN ƁX04 38a<`71gGR&ɶT cw]XEyl.s7%yck#d_~{9 pVt6\/: k_IOO};z,Y |k_/;|؃=by$ =XG)f 4hdȟ#frE[b<Hb 9$}xf$|NYGʃ[!);e`b"덴c:H'^X' bxH뎧Kby( ncyt&+ۗmA)}``*!rQQƖk8^AC̖QMmLi4EwzmLYPeEjej+*\U'g~EqbZ-n$&;z"b{o]vNcS`zabf&]P9m%ljsXMuĀ+䣕'R \/}<鑸6]TZ-Ϛ"h.78>=qC|uu 3!mqti*@p?@iL0r;3sc۝\8i0Mu+cz.~z/3n $Vr nR' 2^NϙiFn>~?g.YOΝO=s?i.8e/ZYq?O˥?ov}G7LM|kE~ @m49;(T"Z.OEu9\8y2QbJcTb9w^\OLZDRҠp^vDehu/œcJ23ۓiwͭ:T9u #NX<9x|{5j p:m/t|HzhYj g3/H9*U:&gX 7N%(0Tm(I~av߯MS.HdO~KkOw}L-]G0`MՈc.%|q ,#Xx Qbjb $^n /.-C IDATKr3 ЛQ53Vz5&a #-tfyC[a^L/r?w* aܾ&KOTaF1S|AfFRst<'a9{!,fg9fC7V㢋D;Z+d^y ?NT|S~?ƒ{ V~Ga` 4G}y;hi&1#mkGr61aX)Ѣ^'w>撈(amRr ,ƎeMy2QH{!G?tDVJ#J\!)XAJ4Bj/`?fu"s+2^~ m-O4ON ,)XoFV} ka{N,coIVTCa̛2X'tE_8fKZDx(Qn&At61{ҺH^zęԘ`iƭՐފ]qe煼az(7IJ(ZFsxAdOyɄs XY{>6*<= E%aPI.hUϑk} h1{;}0W)raSY0VwS0IMg3ضb݊,#s 75 tu|N ճ6r.^tB0ըUOYjk0˂6fV8 fsJVvWYlFW IO}=Ҍ:[~s!Xx }YOX6+L`fܶ+_U/3?#~w_7/ι @y49u7sڶAIs8R,/:-kF+J$`%y7'lEs%j~PYy;e8kGN8zYJ-"a?<-4{'ί1W Q$D$Dx$OqYޘm,WN=4qYN0ƫ77/J3` #p4%pT8+59V;kRk]l.\%:%S'Gx9 ~gd,f˜˘$k1 Av [;Ҵ8d $U_, 4(9M'TNF;R8eL7Et]){pM]Jr_|@=Ø5wW5 e)IIH*!xlBp ]9[>포)|xDvkìϱ{VǶUsvcdtվ\ܷ:޴%,=}^*3oyb|!4eu&BF0zj6[xuY[kGZզG[qu"'ɍUߨ! Ʒ~m]~\쪫zsgDA|eŋu߈C-$4w <`爻9sv|Rlif&ĉQ<cdz$w>=zR|L׀+$+Nr`ij'8gNqķ֎`2`DqΚdIHq2_(UX&R=*MeU` mtDmm Η'6aN=Y8w3 _,!dG3UZ0JejNxuDI)Me$!:=aw,:0el1` %vbg>"H j͜+4cLS&'ê%y(fTXI,N3,j)`ӌ%AcihD욶=yRl{S kl2c(1qIhi+լ:0wfCf<|1|jBnoq7,~ \}b3\.>2nnqW^CPl9끺赍ƔZ͢/CypZmsEB[Gwo];:dU5 3X_j@]ٿ{+y7s @y4{nNp09q0s >u*+%xg֎.q("t;C*!X$kCI>k"woQx\c|Ǜi,kgtobi;0O 3Va40NgGY (lCR_j<Ӫ)Hid$آPѿo-UAx}uĽN?MuΌ%. S,Y`xLUSgDIGE0jĆpMbf\ eƁ^7mȸxh9>w}pnWߣSB)s fQ7bj*$X q}lͲ 3sa!z=FrN;<#m prPn:nH2G(r5Ht[pj<1 af!l,p,!˅fic².i0O+2MrJ%űI=l81̃s) 41)JasOSk2,d559AxaF?[觇qLFkEjjw8IJC; 0˱i5)7ܒeag&19eb H {Wμ3k}`xDh?KOY ΏF[I`[g?; Z%zJdfH0!kuGqpZ]|&5OCw}4Ϧ݅.^r0\fyyXudy0Oh!36}l͛–?}\hiUۡ>Q`u7.EW/,J})h0x>aor/zslYKSKb%Ÿ; 8TV*mgOR"ckr rkQĠcvcm}[ފEW~ܭyYT fMphz6~rjhq26O^N [9 NtoaѓZ1T + ZNʜ`)Gx33hP]nÕ2pI>2&MȂ55+sŅgfE X6XWKjo^l B#I4#&*yvE;[4.l^1g9&TuG?oW 6xaƮ26}uB\_ʹkqg&%3#)9򄝄0cnj1.:!1vfد8R$ollUC^__WV7uD!pJe!_rl9?Vټp HwתԔ73H-%8|#߷&,F*tqn่nesw~r:'cӦf\&7VJ;Z];'NϒXJڑcTҕvLڬA1)PkjM;=EɵE!`r2lEqr肶#G ˺s2 6W4XuĮzkOTк$gp̵ QT핆C_9UՊOV/,%3 soo@L9,OO  hf2D΋^7,qh. 9.7WD2&'/ln:@Ƶ-˕̾=e*QfY\.js5AX'Vbf9:a׬Y HF0=T ! +<X/;3㜕#eLbjt!3 .[ uSoUlBGޮU*`*FFyXae7s@ʆ[&Wg>,WΏ̬HSt"ܙm `?$(nQp8Ke ̘hR-`Ax.h>sͥ/sȦzِD45;稥'lڒyǏgI7I,Ιs<,Tr zE4cr׏W7evՀ!h#42-~vm @?d[9aF8/gUXs$UliC>6B10v mfi`[}o⡡Oτ$[;r!Im<>/ ]ɌG2(ZusmQ4!6W܃ 65{Ԯ.u5JՎ~& <` 4|@y RI: O<`yR"|݈QKmrrS?|bsdȝ-{6}vP] "K8t8f1X/sU?vNd1c2t:"/q/'|=Fi28/']a"Ks +bNX"i IB $qYa􁚠0y.:N/bboT2lvav,FwtEn- > -Ǖ1}~Bfjmu]28-Zׯ}2C-U[Kf].hu+'Ã=QSsx^ ~acK>ˍM@t Eٖ`HX:h6EN<^H :3Ce%'t2yIgo[;W?32 aXiT+hjs\¡[>:"Ht@O#njBR)'ް` kf=sW y$ޓ+L16'<&块Ne툃>I?!J,~#N;i[E.M{9/кY[ct' z4[4c .?3'#ˢ]WjNwd#N+uZo[^ _ iړO3>t JY%Ln$v0+CbKz<[`mehzld4wN,+4;Tbc*8KsEiLτ'6y'-ހ/.  :BؔQ9m{NleG2{@LJ^WSi̝fNp|kmpȈI& cbWx Rr12tal7Tjt\$&ddwΔrL}jfMmc@Z044;|a$'[?:]0xb-tiZn~@zZտh}m{2F)B42̲pL;7-&&?ɉg}cy~\>` 4|@y IIydο7>`Y6ozu13fTVqp(R)Q]R喑~,wrX\"%hQ7r,srN@bj*8I XV*n38̼z8Ϊz쓣Gp0j(BkJ@%~lv`8HX2=7TVVK,co|hMիЅ5Y"3,|6u*`xXvTq?Xc3hmS7!579TyvB_Fw0BFR['-f|[?>N(!]895 \S/t>`snwJGyn &?&$rW7޸[ :yQv7\=6>,_y:&d';n0\%{vX$9R<')z)bɜҴ'ekĞ:5uNcX%Y3cGfk#9%7,L8ԕJG`tcMё؜A0?^繽oIP}qly&R*q^Is~aKh;Su.4ɈDV[0feUf6r`fS%i؟ټy5Ug^?\ss@prs̲@ʋŦv tŊl͓E|]lj2/S#Sn"H[-S?d Л%`tn>Ov~v)o\D @Ph1Q|{d2+ ɄƒcCs潪tn-ks=֑@Rf'1pz[` d$;7}8@ 4%MmʟW@Q7hgO};~o~^}~wUm7?Pssշ7|z77@y7{9[ IDATIiD"GkGD=:e Kxw[TVbIII(9] 9ĉ,~t{Eq'T,)>oy"n!3ӖpR}RIJ8rWW~QKMR7ld YH˂ˎWUyժH$$;%#,/s卡Gb*&fԪ=q9l$2ۛ]Hu"/J}_ f`]0=ClpZ4962㊓&a,3˃? (DLsjR53z;/ \}82nj5}B:@l޹<<6wS%u?)_4;Xg< 2a0L_6=2 7Ȍ& n7jh+DTeh\R!25NiL ~@HtxFI8Ì}wd뇚dBƷ׌b IaҠYhL,ע ʩ 1+Uke2q>fR.Zye0/1l:#uG#KЅ#0ҌuxQH\BѴ/vm9P1'\ CCN,G(X8c;e*c;ji FZ @Wfk۱t2\~%B츬Pgw+5p 3˘B$- ⚮V[FGScԔ10c_g$$Ԋc /°Ʋ։p<7"Ϋc%'Ì5'PM t])$yiiTHޝI6Nò̚cj*J嶒[.)="uQt],+h2SI:Vǚu>j%X`P%R9'ewdRYɵN Ae7-!ʧڌjZjH:ĺ"cƿ=Ǹ)XX~| ~Wӯwor??~|{Y`M#n \p7)ؼA3 kn"2}>H8o|Dfvuy.3 Z=#TݟYD8mI\h'Bcir|stKiƞv3|.Kfl(8v!pOf3 U+ .XfTk`讅OÂJX8MMeBTǫȀɋͿv(MpH[C*YxZο󿮨_/.m~OlΥn& 1LRss(k(w%#:QvtG3{A H">}[^(E!%12>H 3֗ܚ`x%f=" dv,Xl]sJ!Ջ}tT::Ec!_de1؍]jFV;gl`VݪyNpJs`ǜ"Ƌf YRҠ!kAo0d2zB`vZXI]o||e,F88yhk >{lGMC{Z~aExr\̒$^]K84̻'h0su7GQ΀%qヹ]C['Y-5u~مHy^rAX0<12%hQK#cfJ%⸰4퓣 I$pj<#&'mD Ov12; ^7#dz* 5^ʧcsi!Xfj1NL̔fTxݓ6.<|Yo2#K͠#̚5Ϯ͌rQ/5 zuE:8n֏TaӬr{0L~=yg:HH@ؒ^Xk/&]B$]56^9{m0$xY( ! -.gψj4S7X[7WgC}Gyw85>1`ik bejGē8h;ƚtzv㬿"0f0,;`DmGf1] ـcaf=XZvt8/tHpLIM‰ZN1GS3bBn1( ݿ~4l۱[rcr*4 M\vhc#yy';'M2BUl{il7iˎ?]zll⿽W֬.[{oLI_6v5Ȼ~rh|*r~$;3x&&HKxO(IFLTS%8BE1{)8Qۡ0/FoII<'OXR*!s+3:FӜ*ˏnaZL^u8pBҾT)GY=pDK򳺼3+/ٚLPzw)cF٘ ff]]EIqǸ09b#&`fYFlY˽B tt?ʙY(gC@˪D!+`FݚrMP,O8?'f~ pر@EC ;9ODx0'2=  ܻ6+H, 9<t2XGޚYk'[΃Y`͚~uoss}EcXH__W(V ;DO5<8`<.>v"4)"b7[?*3b%9s9Vz8n*9Y.1@: F䕙 PP R>'PV |_Sq0('Fܹxzఱe<ϸ)"S`jr4U8$ƲCCeاׯzq8#nDx~eui˃{eX]rӰ0cĤ흴lzQ{qw-T{%|~u^0fyXM->μNX;}nqk6Tw[XdT:݃5f 1=::*kTɁ㵂7±u8Ǿ%܂Q}; rKBp٩&LQ0:kj6]W})t ?uǪDá0e M6J /9jWOfoxH-b A!`h2q) BǬ-k\Hc>m3#-P'o`8eɗ?D?g411Cш=V5Ȼ~r0|Okᤩs IS%^ a+!R/N&z6OfI r2j09es.NREHeqH $H{8$%;))kӮM% J`GL,!M%E^]G7#Q CQR:9jUr1qmv0u7B" 3W'Ynj{GFXeт"Ν@{EnY2ɳ/{7Fm2Hd9Zp 艿U(].<;@S.cAޭڎ y'pҡ@;gw>c砕 W},D_ Nbߣ>̘ ;6&99&&&59՚j wE**S,ll,,Y{`_4{+O FܻbY"qyGYe8pȣ'y966ơw!\??^A 5ykuQ*ҬRZ`r:t(kkBrxQ-DS4~TJ,SDrOXX52eID~鐄'{8ظy2e% iDXG81U2>ؖ3$G>c疖Ys5b h=:&;`I@RL=T_2֤LZ8[电0޴~tE|` 036W,M 40Ӭy[QGO0﵉-yn٬8~,4́Ӱ-%  I0IFbfFSY-CC7b3ɀ3ڜ 69p#`Q/X '9p͹%~-X6OdF0:Uxx~OYܬt^:yӀ罹.ywOz=̇)q@C 5ЏMɉի/ܶu m3C4×쒘tA9y@,rc*^÷j¶I MZx02[7: rpqh͑e)+qd4MleW8IK2ބ<n.{ |du,b2lo HEcaP%p oH3xsJ笽{VzcAQ˅>+h9::=m+Ԕ-~iJynYPإ0v+:ոiS 2{5.H٥>झ<b+ĉvw:BN^cQnE2|V'S厠 DP.kh ]]O'wX!1aj$T(#[pa9Syt̝52Փ ӀuphHs#nxvf=oww ;Do՚msI6;BL5Qy),q(b~V){~|tڝDYNET T13EfshwrT6zdfW'Lb1YŒ%53^4O4D>˸"@{k=79~w܌Y\cb: Yf ZTxh8BAK 3 ѩ) 6@h n$grydzG|qm0D׼2CZ$2Cxoi6똊mS!I(w]ycUYFj _>7q~Qb|^"o+RC$Nu_}UO3I_W?]Moyۏ{u mnvTܕ$K<!MbcThmuIOicd5+O?>Z!7v K<α8gb",;; :[n{%9Ljr%$r\6\Dg<2A,B|-5 {(:Y$%M]=ߝ7ILA.jJ'36U~I(5}TyTKAEeJ`!8rf|b}!/AE|fT;n⛁`~#mo.g*8։y[!0[ո`{H{̾^ReFF Tfřuvz wZ n &B1BKfՇr?6 xp`:=D&B`e):r}܂NԬ۸D179:;|h (.b L{fy3xM9 muOזSaCI;~/}kfmms,G\8MN8Ɍ,>VeR "7}x(6 k 7ujoe 9yEd׼^W+I^N7gi] nv?AOC oN϶[7J҂ƬT9pa뗥JaBx/聫w8Bک_{s4-3''B)UoBilK8&'B54N˜$rH6+=1a90>id4 Q㘒_5I1i/_xnIEĞ)8#ʮ# 3[M'B]i`Ƶi8'evsZXhD 6bTXcv8~Ś2߻O5q#q%p$vlGC- >qۃUΡ/.XX3CE`9jNp$tq[꽿?trm02j !Pn+[d~c!rB@ IDATjř&avSca:tN$U~9gxKsr&;}=mjnvB]V94/MLXYٕ>Uryb@V7#BtYq+BaPy~S']Gq?v\^ʰ:g_F7zery:O w߻{9mw՟% _|yXb{mRA =c4$' 4:rIi)8 yUf,Ipuß^DI9bh],NvC0O=\z _0P˫km-^R>,8"+CzrI&3Vu:=SaŰ$ֶET7>kKk^K_|«_޿jտk/_ܽg_*`}Wop5/EoyL<|۳Gw8,5Ȼgx'}{ɥiqޜ%$)茏[+W :fy)/Iܼ~tyc!-&ZdG.tUrpLM&y垉ɰ8y9aҫnVu4oxh6AHdY42cNg̨Z 1nIp'*u hzà :e qXp{$0bep$[ Y}R+5F0M2궙U!2Zm.n-ͻt+GIxxS%1T_?MڳK }st8ROnæENYs#BǦq-.tKў=fXea[w›<{j.3Ù {p4]?2R43^]na 6C)T+ېcoВN3s3!&b6SBŗ (yƇ\0J%^[:e93g.|oOwvx=VkfV*+,9g3@?ypAAOQ~c[6K[N 1o|4=m-*=!05e7uu0lؗKc br"|T=5BS}r]%˜CB,&'w0YTX*3@щN*2`f:,q846O,RYJ8lvْm r wS\-BT7vV C-#=KQa>zvi0xhMj v2YmʲGC]^mXXη l z8 M0XxIw/I&$$w1N2c;iQ$rc@ bwɀPsΤmd;[uFfլR_P~I雵]?0uNiYnLZai0 0۫!7"18<b sF_s% xYn LXOZ$ʼn; n*WoĠ8"큐әYS͎ ` ~bYo1wW-/:iP87`X xLV|w<w=y`Gް?e-SZV&F-fflpWI/藙13m6#0cȧ킠eƜzŖf'Zm `mܛ=X,!pZ_( SJ,F| }FVnR _昜iI?Z·#k+{S8^X v{@4'b0cToO'ġ*\P*)2yF$X*9~0pGE@).2ͭZv87nw09V·I) :Ɗ<3Bf T;6#y$sgNr}9Vb.ضU[&l'XKclCBlbڷ ?]%/i_5?5~&A =S߮ ~*WM o9px'<~Gkۆ5k*b%D%L85(q13c{'^pLX $bFjISaU;Nj:tWl܀FT̡%^!bΝt&pK Te`KMMrNq-!!c{-/B`[ )׬匏c` {vewAY`??U?rc bV[\^UӠ*ozv̴d<W"P jh-zHTuuzI- y{;OL)Ff_H_Z8Vse9%HD13m} lB+7|Q6z<#9oz[BK~^;nj ; 3ǀ2nk df ;k{^Ufu&Xf ;T=&* d-'̘DMVx~[ZdžE%$cycaS8"IVB2IKrˇNj׏YR0΂Gwe`6TME>Rg2AO葍U1-<'n;V%G2A)U*ؗZ xW|&#RAK 2a;.%Xn2[ Y{NLSդ#BكqN<~$Yr Vq=f!wb(yDa`T~9:t$`v}Pd<k^_k{&_Q_FɓjS##S#QzA߷rN)EƂy8$QUaP X1b !ؒ,{'*/%;uy1N]nG gpӼˇ\=1nT4T#-5 C-JS7_klZ^xsH$1 -4fl[oYZcb=N$;O`4Ӧ`t浱Yd5+AJ;[vEO|3:w%@yX j'gy_ڵ۷m($M<J%b,wRI1N{r[].8V{Hd{K$֏7<8y ^{{Ϸ=j5[Ѳ FbNvp?ٚ`&rV6`l;$=7a{7Mgl~;^<˕Q؉4IQ45G3W]XЁ֪1{[ܜx%c/b=HSL2(ݿipԛ_͌WNwQ,R]sxX>6Xѱ45gFִ|&Zfw@lEGR:qdi5') IDATrl7?{GDM9׮,1aL=avЕqR笰h;9iV8`$o 53x!Й`9 f4gz\ G`m(=!/3D+?X0kQ['^EmnTma{Q_, [?c>™ӭr xG$ iJ'#&ýn[J$aˉKf®&-I2WmD cqjTHreXAQ_ [)_R"WL2< !LC )rؼb.hqwϚ}o.7a xC?#״#ΩLNb07QpKMf3.=NʫxkZ_\xH9=<k4 鉉q , 5[Lk!i@׊777g!0qc`0^:5oG3`v/u;P`-$'r-I:&$#3aa @<6pN;qbTJr*GڌtP.21"zi&K`fUWٯ-\T9wP>̭MT MYtu ,Xr3%t,>w$ .9.i{{^ɻ^~~}~i$'E;BT[r9oQ[=3c7HJ߹d(  ŀbTpsB*љ-˒UŇMm{)+DSpbNLNZ{nqC]Sؼyj fPb0=Yp*r[l_sAFKT8g?agm#X`}] M[2k*ZUN4ltp[t+<>q3+7"fEU8, S%5'U%>.#b+H|us)-qZW-6"EuOL\Ȍz=,]zFXc]*iټ ^0ǕVc߾~ys)eeX}3,{;}#|?2ݾz:[x7/wװ`9!wyT=6 @ՐO`v)A7=9>>9egDrcl~jJ8&`J_80/ܢm3‚q&Ca]e._POKދ؇h^si};9U<}";W75|n%% _Û70NRYciǝ+lْcP6WRlsp|On^kDGܜ[R+xG ׏NNZ'p%ilDvN`S2v&0VFfV)wM-1B þd ooFx9s u6$d>:v X8ccΤ#6>e]#0Zy45i5xe\B8_',GWCTʒR+$2cf5ln*Ue'sT^3dwlH;:ٳ5 woXK a:,1tvlr籏>lh4Q|OnkX!gA2|ff}k843>]g2,7,q!.+ $fXeH6y7Bn&7Qj"&iS'aΖH_Ͷ"TEewTnBy6 wxχGy|ks Lp$ / nݜE}tۨ7O͇^.㜞5hcq)TYF6Xz&`e]i7./_gNmn]m~`x4q?$AKI)9ݽ}bd^8_#eӒJ%9朾܌ΚNfC~̉OזDP%ܿږ&sb ZISS#I XKt #1m1)$gJy?L63)'hP ^α:+W*+bB`ӍҎM38q4$ v@H1fܘ$#d\K%Y WN%'Co/Ov6߼:h}i,} 0T/{:.9 [Ńo,;|f1 w_Y胝pt*c1)XTėrAhdLolFpz߄aMkd*0 4-{qm=1 mR;0joE=ݗVo yl23Вj'8m>0iTBw '>mf'O9QuJ25& vu{>\w^11 McqP|4A_klHD}fi a o_s/7ro{ed ?zÅռDE6|{Q }^( {]'aq4p) BF3e}dzf ,lX{KR/o)MOۊZxˍb)njV3 !3fv} c绛33+Kg*uq>aUGXxy4w%U CGw B+9_3+af4X$a?׈[AeX>ǿ@Y+8Q29!iPk4Z콻kbb~>{H4qr<9^S)yOJҒzpZ_I'a ;A+dW?\*%OmwbDZ>6j xr|Vؾu%(\'і"C4 ffβVgfΙQui+s5"=Ynm-WU0 Fa4K24;k0XX┸@O|ep@nAUvmFePT2T 2gQ̀o4XkCqQ XDk,3xUebDy5l:<מ7Uq`U^y^y;^,l@s'uVU3Jo*H 7[DK<%%jut Qu!:'O8b@Đ*={T)%)f;eU|a$JH;̆U4Z1;kK0;[G6c@i˖&yn}>H!;4L c<޾`ߐ mk|um%ϬרVHvЊ+9I*aC1#ːv3Dqiz 5?W&>sۀ?_~7~RQ$za}<&鐖T{EKY$ѝ†Lф:L8Nga.ؽLF[l҄NK:RT>;ج2~Hv=91XiqDӠ cK`F3G{L{kmJY9I咎e6f;۷ ]O G[yC6OuYSqb#Dھ8Q%ga>s`J|ѓ}>|Ǜ ~ )X N{l1 D_9GBatΉu f y0^/;q:PՆ\N1PEg#*4 ۅLF0,OOEݢi7 BiJGS H}t`rvšy[6:̾ `eϨp7:ǖЭ"n,$k9ͅxʙ "Mb2#It0X fڍD}5V^?f% 7xݹl5N w]U=Zx!a:Vyn3*U72Jj7JXݸh8 lIJO[e׏DSDŽYy  Dd^w~3BP SۺXՆS?A梂}ҤH Ny3gP)թ3yHVqd#$)Xn}OՉ8یI> #yfʒhOFzLiz z>8ZOuFCNG_j]8+K^ vyuH!XAoCE?21Kx,xh)2MdBފNuyZq( Y4c۲ܲ͛nF?_Gɀ? #;Mm]jOy/>4GRQ)_x\V0_qʚ$|z9%ƃr?<5./*I4 Jې$P.;x͜+UK.f\T"i ^ Kq:IMq :戣 # a!p gM Yu@(pܼ 4dxy ~/tu?%0؜bl]3F@nO/ٗŇ9\Mr ܷ?rzǍ!33ffÐf$&\%ٰp0["eEP"xۙk<`kyn/ehtiP2,#yn;;o77 [c=Fݻ1U-_9S RE@?H4hD -sɃ|eK)‚-۶:l&+/zzǙ6Rnfl^$^nfC~@+tvp9tj.ka_Ze%>{T0L_n4 oCFlK,' 8(c|Y9.q b7/͆MY3ui=|ډ[=Yk:mvvx6Udq;hEl6\v 0\&y[C =ܞ$J.X(IZ,ff-M$LMD#IAGZ LH&|R EX1*%N w[b&SeU*?/ IDATҠ쒏 8-n>(qs ^p ˬ3cO!agF<lG#^n8G3Bnp9˞SG rh^ܔ>#FZ#$){Պq+n_٪9A/;oL6hN`i '`FeּZFj5t/KeUʊ/b\%c%0l,,I3szoR1AsCuN y9'I> χmpH<@oH}ҟ\,ϙKXfG5(0JɭmR?ByעI3366vm!`N*E]1̓jl灌V`Ccm6Sv/q}8pŬ8 xj>T+2mG:.!Z#H41Qc{3W)AKY󾊾&xOusIEk YX}"YjZ"!iqM,0t9;L3vIcD|k!qEoYȭ::. mcw<>?{2 $blh0 98~Ϊs8y;'x7..NlƟ7K&HռEmngDmX|){o{c|By8;%J;s6OJi1`>☞$15䘝%9yg]V7hʈ<)rˋ} [*q$7ʶeK̬X^'6WĠh1(Z47 L]!MxHm1)ZĖڭb0IS!ϩ׬oޮE0x4q04ׯ( Ckxޜ :n~!c0h~!t.Cf΃/ex3J7XӴǟ%@Zl2RBJƪ% xp 9RiCRزl8IG^j>}^uto5w{2wWUtB\\佨'?pg2y9sKK%-,X,D p*BHILτ4qCuXj?l#![ +g,N9贮0jĀSō8 ղk#cwop Edh0!@V2~anXYR/cizZnZZ̸+ICz]vcEYy΁`F ooNCak49%nxH `(sF{"ҷ锊Ms'r"ʘLv*}2{{<!#7Xq߮oa1JDRˆ/uG$muǰ=yFb(`FiDk#5wWYX^6@m;̘jh|]rMWTk֤p0Iei7Uv5{/KҒF6;gfX`zzܰ缜[g-Yׂ Z^,XjNOc vV5m \{nh4s! !^k5LT7CU㭗p8NzX"qq&asf.\0l{I_ɻ5|=UW]yUǃ==KKJb49 KxzMKRYdpʔy'_}뽼'AI .w?e^vx% 43;9+N-UZH'oLM*NAOn|GtĄ<@,aXk`yC  W_3lhWNlv7x(םWIn*€"hc]C))B0IS󄱰;A'qarr5m3!VVl`Ym#Mw_QV=bq7v[1 QIJlE-@igj,H63޲_P "ze'Z4MPYe拷h̿YVݡ6pbUŒV~HXҖt1lh6lgc({ 3:ܮ5`C9ykR$ʏٲϸ,!1^m+E/PoXHEeVz xa>gci"8?_*ZMs臨 p3sqJWfq(ӷ8xQ:]+NCuj|q$ ^29N(8e,)]s!M4:+@ִ޺UyzMΪ1iWa^':E\Y,^Q=aȂzlΉqA03^ʙ KFƥ%Ifhw,g ,bta}K{^ɻM{Q?A~]]*]dn"zbr*$^ޓev߭҂yOT:˂ER_`}Ø8鮉uRIu&.+slw:hJ)Jd0 2;v=ɉ򩐦JS:GG>Ũ It0߿{¼%U;mq4ȹ(X{flmBX*紾_xU_F@f!Ѱ$ь(f!HT Fa +~0Huw9=v1Bʩ@X~*f o w_UY.@/L{̎,IZ_pDH\'suv8FQUKExig:a`9ɱq[NśKbnSHUX]soo)Q͉u1 35el{5-eg:l`1˜U Ǐd:t.pprkVc|~mZY@8_ҝ.G/}[7!|t]Jtї-QYz1Vw9NIq<[V I~$ilh1X [l4 jJf6lx[}~I{1>~qBP>za}S{^ɻeNE7cNVoTo¼ffCYJ'!kf-KR sn_ ۖT>%z-ԬZI@Kʏ9 {:@bS3dK,---u'D{0jTUUJU-K!}!'\З!@3u*@_IqI$@I`v'ؖh-4\uwX*+QvC>5Z{[z3R43B@M;`jYڵ~C$Aԥyˍ;HiCb0.lfuݎVE֟e噹kr0ct.bd`Fd‚%Ձ{vb7!6yny':pͦuw+V&3Yw )ͧV_"zE9;WtIOjȻ4X=y_Tn!0=oaS{z_gs?_}Tcc!p~+:lH9ƹB t@cT) 'q $))lsu.v:7ykIno9?~4NꑑJUyr(B:;eF/p$gEYiJJҘbP:` rxFGOWk/s"F^ WFpu WBgI-=Y! 4{Sg>$G;-W Tyn93~4Ѡ '6{ُޙz>咶gZsnX$f̪-͞5w%/ݳgP;.r4#J*':`v_zݶ4V J"0<# dM֭sp.YfGѳK ,oݖѴ5=$'bEaq ZVs.lG/[IVeg0j5&B̝ q^n{o.al2N˟)毃҃8`&J*:nf.T˒ v̓M3͢_Qi&7ܸZ^2Lx ZZiŽKifǑ ̴;?1iQ+SY`mG!(gج Ouiajp!ARu|$mpF0h6$xpZ[Jgx={d#5ytt[*suh"1 d(iJ'k }F3:;fd$*0`7aڂ@!~K%*_2x^X&揫_gg&M,,LGqŋҚ!`B>`׏̇bgyTrYkc{~>ʅ:Ql%J%/p>n?2]YruΘ^>޻-,04ړ>M6Yvm1hl* ` a)@x_|mH4`a9G^*ߥom=qr+#߹Zee^"ᛯٙ hkB}%@(,6lx?8y|GK%J%@LGIqisb~>T͆5K"]*߻g)33$^ӔGS%;5Vd!BJ Bă[ba"%xU!@3#9-&bm}7g;]'3֬vqYܾ j5xT;0KewS!4R>(Jɩϐd2xl~>?)eXF6u:sCϜ_u>(P> {f;Zʕni wO4$5Q,4d>>=yd8 Į.gtٶ0[dox)|h( zp.?yG57z=+uu-U@UYb] lNccޜs!W5tnGI a+fZ?QgC!0c:neF$u^b߽9fc SαߜxRuE_Re/Bju z>'o! `v̈W']yZ}K\?+{9+{vf%.)>5t-f$Pt5m?q%g>Wʔ+.:$]pP j!MըK*pQ3$;, @# h6, F>%2<\S[.3a~.%ELON# :$! =T݌;Z3 EvtU_yKث+j=CP9ad/{Ft1\C Y y:J6yfTE]%M΃ o>ϯr<ƱBsd،ٳ 3#I4 :53$tlڭq8#9CE-:4e1OXz WOw;ڔc 2UO*-X(mxZAsas#sN/W(j^WW=QRÃbS~w[2/d4zVɟ*/~̌s6moWKUr*ULquߗw%)dJ-AeQMVSi׆lݎ@~CO>jUՊD,vdYP.TRm!KS|"'rOȹp!KSwsG"h֨dَj564Otp1T{^LW99ql&EK.bՕ,,C@wDC\%)F\kX~8;ilnP[4[-36ň{* IDATY/`:d:Y&{csj4fu竆&=sj%5c9P_L!ۖl̩?>xӝO{@3 5 >g!г=;v} mNtub8t8"8x#M?\ږcMvXI %5w,Y6yK<{m?` g8Gg-LjlbI65\.؛^w.Om͸iN-ёt?٭ ,y&hqoϟL,rM`ح#jW?cxen!燎5_J_ue|2y/UW!|{ff"jWh;IJ@LAj"o3j5dhɻ!#|;=#KSDQx3yӡ\vLM4ZJ$Zv|^.+MMjQHug0kh<})O`c~V]ޑ$rz͜޽7I@x_;'q[ ъܒ'-oFtτc6P̾ܜc+{2w2y/W|Gfh/yoޗI"mצ1֭i##|{'tV;H:U+|wﹶ7χR9zr G?Hnf\WS[.Qu ;ML+9*)lvֶEpl^ÉD?;O;k'9=Ai/ѓ5൳B꘨ߙ>JNq/L}iH =dH>.$]gk!K̮I8iV$&/dmI6|ܳ9'xCB9'<Ngi; F~eQW}+roy`~.;! ['Ļ B`YY KƜF6f8Ҍcܶi_0)^M:H67pSGׂZ<[AkSG1zlkvlxow픰[Z󁪑CG3Zɿ_,Wi\U{絃fCt{oa$/Eܰ)"f6~-7 |9drHG/-u;l_3zݞyhȭZU*I3%uyK{tEGm VHp8'/i3D}uwbf.v(ȱ̙b4|"﹩SSᢓ '$Ě6ybϨ iPQ+3 թg6>vmtx..9dorW3 W=saM>lhC7mIZGnHخV0f ^%!oL؏V#՜<52rG+# "% ov>u+a܈7s7_꒱$!kxlv혜 GR v$!cFG07M5Er%=uosT>V}b,ij=.n^-s+u` d=Όϩ0 OkB'?^6xcZc3\ׂZB+ Y?{-xuy8I1O#= =شL' 0 Ţ([|ss00pa20zANR$Έ'33ްޏ1v:lKeZӽI\!]ӭIڙo`[f̬99=<ٌ9Vi: q8<mr|N4 &ho|8v[ 3A0>ѭVljN F|)T.IU#!}샀JxsEo2|Yb㪎gf&$^8~LR__o{/~i첰%iߌܤf# dʆ]_<ƞ,'geOwe.I45.MqNkOC}6az:쮸[ hׅ7Kdp\_!ISNقl*'$Qz>Ɔ҉c*tܬp\W|iwvuRq[\a{Gupi=^4*(;ҁ"3::N[}`ύ 3  M!8aX`Qu8^I=ƪ󮻜cyH{&DbXJǡM$ÇDzv$Ʒs2ccyuC*zi|&v,Ӛa!Y̴fx߽#v)d24=a3ח$'̛ٴÿXdkM2Wc.r.ˍn'Q'jhF濭Dcw!|P,'(ye|Ybjoz8?h ^56rJhf/iR>ٹܹ'| 8{Z MTܮ\å jlZJR~!Iy]{Ġ@ IPhgõnWAM]{%d(Jn㥼%-owŎgs@ B(]X$ofuo PsfkzE!mȭ&쮸ha3b趨X{oG ֮ƅ=Z  8ի_y^8\V)%jKdlQ#!W03[riz6O%uQ%9%zyge C%<}HGG/wVT4C^^&DqqJ,,$3ۘ37o+W8!0bJ:<=s Ѡc$b*7*ڔ`I<'8M%h>3m|/3Oh2ǚKeKgM-O@ȟ, L f,fƫ+qWh8gE مD\<&! ?+Rh{)9B|BI8 Cd?3 | Ǽ9i YUG|" j%m2cpsrgEMgķ/YW#»]gT40+vl&~c3nֈ c>iRg2.|z+%xETYEAj[5Vuk׺Vp ڞXhqW1(~0cNC%2ftBl y/{e3ˣ2y/rM31x%</( l=7 r/_M^y.]Xsg>]Im%Yn~u>qr_$]d%6nZ{^+#M$C0?o+1%F'JSO{w'x'988tb!\hD=)`llώc^kԉ:=mI ϸ9\!57fN֤R|n!pQl]#_gÌ.4^@m88iSUE+[F`K,0G r[B@tˎ!xϷ&,ڼPLЖhlNoz;$uK9{v ]ϯo'r'6h!xO$ 1Ӭ̉>1 ٖ^_8٤ѴSuB6gwZ(:ӃA p6';\2Tu!٧mD3wNLÌ#Wh8]k󵰮!~$86 NPx(Ln%Sy -u;eRMaz{?gr[y9i}zqБEgiy7̆e2|Ybc9^L==u)vW8vsy吝筶;s܍ևvsMK~{|I4E›V v=>OpQ]-']zM۰ɓe$8_E^~E~D]#qýVīViM8u:*5X{D¡ե5NMX,wۻg9=&fCSDӞ(߀@qX\'ƄӁJn]]NЏ pZ`lZڥsd3Ai!l.wnbٕS}:B+%̞ޜY8@@mjN z0''Zs=?եm"o͊nY ;eO_1{mBnK6D`b{@NirT%wtu9'N4 1śeڡnMeBG!U4+HC<pB_-mfNh;J(: 0EaT":cSNC%4k]]8[Ѿl4v#j] TvrFoAc ي7 J}0\bPXL9.{BwK3x1\au-r{X&X4=;3!ШόKd"-_QB'+/օ/QM'7nz:bwYJ05k{lK<>^$KϽ ްIT10[SMcH8񙕮}զBD%͊Hrrؤ`cf|QDoݔP|gRr3mOkZP5tx6uy03. :P\qt4h!Xo_4HeQ=|M?md'8F5i..eCG&{|xdƍjd8&q胳/H*A}٘af[s$Kwd6Hn)Ro: 9YnWp.]g'w =#YS|g:J;Zwȸq2 , }y`Ԣ =W,w7z."?yY.A%/"j4ヘ.0&KIWF7)f6l`|$'E)2ijȱ@3=g>ey/v4n[a^8zl_vϱv]w^${G7e5eiJ>QcogCU+gZeώya4$dUNo~Z[t1⟫balh6--Is*v0,t5,w|=} A6P2N_%m6jv8a y6<촑5m0]bp*!UD`u3 =mD`*A0JewuVLcVj!7q԰Nnwq3̸8]'"'GZs|)MD_a f@$yƆ`Egd،F43,*qMҎH= fyff}{G%x IDAT\8We8vțYNgfdFs&=Kᕀ٥ r,K=dl.KfEnuSצq@:aΜ@iYӾs$xϒ +#ky^X&X߸=y;MKu&m5\y4"NNwKZ;$-0[ֶ807 aŸ_!vE:74ORbC@궢{s,nuw_-1@nX(ZOW*  ;%^X(hXRE{ϐV39%͢V?7rbЃ/#p?ys9.dMSQ\TwM9OcfJ%ƽvJ3%#X Y,K1צ1c!Z*s3bB*#qN"( `Gw`O0V7-D@|C>,YГ I1i[eqT|NÆ)V>Јv͓v8^?.xNv" Vs$4B%Tɼd[$B|PZXaKn1;܇W%A*UJ52|螳_^poynYFl 0[2uxų|q]ӏOŭZ5k|Y}#xy2=ԸcJ6Ld;ҔY+-5)7[G!٦UolG+eU9e6TJ 8O['D9Z~?y֢i*96 ZNlj lF3./Cz(@0' Xp(lwLƸ$붾$Ḑ$ QFXecζ]gy]yn><7@u;y/œSa|5Q7MqX -TqpV›Ef6$*er>< o{GG~i0-0S8[օu>Aq,}fNH3%md,0T=IBu~ ,SV iF~ҠFl֢:\G;\Ѷb8Z\ikW..WΆk. Gf: !7Ȼu= Im'e3!n6_BRXlǯ_7o?ݴW*Jw~ݏ=xc9 z>>>&:-**u4yܵM]ߴ"|'H nC~?FNh糴Z btum*$̰F(ͩwE3FΌ_t˜F֭(^0EVMBN0}yǞ#=kVtt9I5 fS!T.I([13c 59emM.Z[_RK~]{9\msȩR8'I\W;=Qږ;$N 3.^ iA<:]]m92|SyERyn\BGN DmëљTr3A!]ۛKYM9!޽^BZ#0=%`KtiqIc[| fT{):Y Yfz=DXn`vK|x׵iZ3 3U]\C>^VH,4(B< [6c޾\#p/xMNgD!~ӯ;o]7x-yeWY{眚tUuH/SAϋ&*A>HRA+DI9餓NwzLwUP]t{ϩJhwQz:gYzec8t8n[saq{^{Wc5V$P)ctl$/ ; #DӰ9g>I{~>7Uʜ;5|YJk7EهNFsIBۚ޾`iɊ%qcVYFwOĹ43T*?^pt9;ZOl\,W7ՕbPGb|E,,({%"\nMwN pN[@ξ"7 ٫棎C٫TXW0vu700o4N8 ?N&KD{+|>sE72huMMQby^0U1GKQr- 0մHvYL}FAd0Ɗ):C<2zN.\PSI֨Wfij'rGhwc}Yg9''ɱ&K>Uo< z$V[R -΅EEaDN}xOfƁf'd2=iO;!iW̘6wf=R_?PKWEprbciɼ44kk'[pe AE3Yf$lr;$OG!/rK8VIǪQ#|e"{aw<3m릳J[:0,O6&,H(* ~#Iv"gZ|[|K?{=+~9z#^S?_W^߮$Iooѣ'YEޫ2$R.Q7 R^y?<w$;?ASݪ|죿e$=mq1O3Z6MBfh[g2Og,gT@ i1L`,Gi̬kFA$;}76R1Dr*ulV,9HQxV((G̋zn'ˊEm|gonNqR.-JP7{'-zN99y޷Dk2??y[7Oolu-O:sVVjjR@?&b8g( Zc;|itWP\䐣űEyp;ԡ>?m ܼ8sdͩ(7KO,ive:fW:}ؼYjWFa< ;ӌ*߲]QcY^Ќ4v#_xQo $"t u8 6.O}@&ϣ5H*#8rE15J=7ޜ&_c.ԡwk$XKs+2Y+.x s ]qXɨYfs47LiWv{eގe6hs*b~ߧVF͈oC C=?KW?ۿ5K??>s]|KEVjj|FI} N P&D.3w U?|e)MMM _0?@0>9(^ܝy 5&/' Cx<-I9y{mm[2e]גy[~8ku-G)2#dML%khpŢHb.9q"klT1NM7Vt9%Ze'bE1NtE}"Md'uJĂՠBc\LslI=Ri꘯&ww'>kM=L[]Q}5T'%e^0cNV0#TSh9+08167l{$51:0L'ͺ2F%[.+ h=X_'$'=4PCWYy_ߞ3xRO$5oM+}=IjFζ[,*JNjN5 r+F|wM9L{ڮ_P f8EKdnm3*} roΧnV~\Jx0BΦꨰNO6 &a$5nx:EWDOR0ӧ*퀨AH84J,r֮u2vρs>sz8exgLl6Lzq47ϧQZ|S[ Yfψ{Rkiv}y.ʬэB[ i%$#wiYaa{K^U"$UiZEӍN5{!#ЁAs6G>uKe.+*pzҺͱyf:ǒYCHYv<\sIn8 0֍x q7BkjTg%Nyř68=2q,Ge=F|8E~Bk]NAN}Rg٧\x]"n-^ZwfV)f%XKոXBib44ȉ` f'<ޟ蕥$nv Þ[9 {.+xϥ$h,!mJ,:T"XuZykXw%WDJbQf۵u}ou-akOlQ/=tq:ݿFf2G|J46tլ7՚'^g:6f[h:|]83ww&%%ׅo?,[oWW[+j]&YdTj/¸2SP I#. &kEY S9k?\cCOqsHә8/ICKEPh c󒑷EGwg<^D:vdz yL87o B>m[O$r,kc%3{`h)yhhv?z⁁q-Û}d+f ց-x6+GS7oq{e{z&ME_v$#;J~A u80N:e1瑽Wzz#:0`Kdu1ϡ;zz'R\1jE1Mbk@+ffBdEv:ػ1aơi) ׹YW|oeO>V܇\K-qx,jbGax9G`Mݻ~_6lklƥfX5. It( ; qW8W9x7 kwHG./:"!I{"wm9>CGӫݗBI336)piq;|cX3hfHb$ lq2B~U/7{o׼c*o*=3=cF$XylZ2$5־i6KŊ `ۘ5a[>2K3|6݂*ד&ܼ#+DRM.EUWsD IDAT=|^Iǖ.dF.3 %;|캶oŹCk1ք$:9vt۶B!t3&'XS[<=lv9087T(=CK; ӳ>rt;{cE8Ukٙl)r8q-)iitJ&={Jx{i$ݹh1L/)kj89GN^Rq9 <1,Myxw+uuBmkWk'/(.@v_g,l|kZ1zeo&9:e_Y@9m( mB "iax,ХydlvXu% 9E[c[#sR$3ͮ7:Lݺ7*{ضRL&1hmv4gԉ$86cmJ7?gx\lYƢg|;iC0W\. A(B~V\ڧŗJ0'DQI<.<%e32$ '∾(ЬiJw,'0T7?^d҂, #i3 |vd_}ۯu+?a`e3QY7<>vkZrq8mp{ czz/>9Uo=7;f'FF]j +"Y;h`1IUb00j9__ȺHɪ]MNNw,Թ))@aTˈ@U3$$lSRVvaqi¼]TTͨ[C6#4=m"sT9gB1[7 ?U=:ӳ>r'b.'985Ɗba@"I"3|to/);KH<%9N0q }8_ >?INGUVf33N^Rx56OfIbٗ7F; ? Ѣ\'KO`~c4"36'gN׋U*,cf'W +*tT4ČH3먊m1̘fo =QfkZ%,U-zN%f549HޔdanW?⁁_#K`tلMufK%|FTWcF'*ֿ_]`O5*:y A7?t/_ofZrXM$TQB IUImU~{*6Ea@SPhx)kCۆQqD`kk{XޯOŸŬHSTaO?}܌n2&VV`ڸ\B,)8m@7"7jnr Bd}뮟R2q*u.D_ GEw'˛ gb&=3EᵋVWG Mf7[&c Eٜ)p/39f#R/DRLˋ%sH&}6O$پ%;t17qlkjl+kއ%ǃFҢϮ EIIBKvg˕Vp!'N9.Ab2G^idi@s⒅mHFd8ENNY/.zë'Hw.M<xsK6DīΥ[cmӴvֻ֨T)_'e 9N:zzgsz<xӬ,cC{jsZ*Ҍsc<3Y'2/0=\g;篆=:sbpFWC@?Ş>8a{pnF!p |o'N,"* lcm`?e\[3W`|zaQc?_/Hߎ7kޟľ'ec*o{ff9Ҵ"166<2V-*Q",cY/W%S5PH!~ՙg$ N&Sǣ3]ְ؍y ijY'j?jP̘%Zݘw6:M-.MW38M:346(gij.DSXX(dO ⢗R^w*pe;jۇ[&bAq'#2&МgN[#-DIppbG8Q8N4oΙN0o^\A>w}CY9hVwHg@{g{ [#Bq3$sַ@Y8W eE1HItIf'GC>Z^yH'_P=@jL-Ѭ.4#*g'MӔra5O͛#qѓy9.{" ȱ04_}pV/,I,yJ%0\=]pt4 13=ztQD~VQKlj6ֹq̨$K>r:wrE#cfCb?n*ENif4u3;ܖ)p?%$UUe?F1@,- {:mXw927FwEq0]m1I3g~c,cChS.^W |:KUdff 0tiь^cڈ32\DGEKn1),c7x*Ou`ppctzWUߴ{"&Ҕ;J`4K_슛^;nnyx>$!lkAq,E֔u5=o<7{>e=0j׿s:sx~\8>yntݮ<~O|~;>U7? y(qS{xlS99߬%P.#ol2MMn0yo oJeR۴CBbqiƂ EiFcCъQEyTQWs{AK3'MzW%CLSs7̄f^%.4JO!yhڽ{~űIjRIBNqw&]a,,ځBc8Y!h*(dGG * |G`qb I\6YgΊE9i֨o^T;X{ eӁ \{KT)d6 mg%asBok+2V^m 0\Ĵ7kaԔ҄! +HRQg@dهvcoijs,8]wXDg\f\*AYy{a7܂l55pә$>Y\+jw J[K6QұGYPȧ]xDh4@HoT9%A+iYb<}\քMs0ֲ1v2E_NQ(w zh0' F݆^6N3cf {ogfֲR('UI/ yo:~^S?'nGV'_k߯z͛<Ҳ?Ý+ƪ{5V!+daQ^+rk[E1>[d+hN[ؠNbx8Z52lkkZd<+N6;wRT0cqiTڼ5ŖL)x&|ܔҩ(fnnrBf9'c AusӇM4hr{o|nyA1 32My8 F<{1 i?Gh/Iq[ /O DGr{TOU^'+$RRn՘H %'LԑzG]qΏVJp}{H%7]undhp\ zRHpM>D><%S`^q#%9[ ~Qi =+!/; DS 'EX2k.-Z,xA3^xphm71~O }Ef,}q.Ƙ>,zAM2Ҍw:E_fFPY7mcŢʒq&q0Ymhv3s :\>-?7liqȈDgG6h: Ww{Ml뜧b~&o /YF%T$e(^N2OYY<:~~x3f{5osn|@sU6U{5V3)Wu2 wPR>n/W7 #dZ[`)׉JfU{F{ony̴HEcn4:Ts\>H&k5B3QI\ΰ\M0λяwSWeKw=xM^W~_x3|-~۸6YXo`sNՊύ9dmY^c} a#8un{6xoZʵАS-PEޓeifqBAvfU$3/'gV \j A<5=9\$:t=s(R2qCM@`ח&jG4CbMYZ]IP1974`](80oCC7~uuuSmrI HY31qTM:kDZ$!qL'O:b\ڨcFTI̠ø/J)W3ENMrK~B{ v]?w|dƎ8P`o7Lem6:" &tpR *)φDXqΚz=bƋ\\xd6sCMe;=Gָujzri9G[[TmGK 0 W G3,SE7Kx}{g\\t*Tm` kdޛ;Ąˆ5/社io5 m"m^fYƗ,eCj&gs|c$G$pr^/<ṼɭX\0914ǮW"S2%r+-PWWȟ7y^+cyj7<{bsw{{;ʋ`e.j{\"Zޘ9ο>_9jrN9d޲,kleɬA邞[RI9tlKs705},E  M.R_.}8V66zΌ4,g44t &}dsyP+đe95"jf63cEH 1TDcˎ;sV(r,~'An—bV$1ָn3%ᨴۖs!ΘfE9)άTTx@DN۴)z$v0g&/1ZDqsZ뙬:`lx1ܜSՑb\ Ol>U.o>TRgt+8Kp; j]%M02IgÉ('ywZ\aW->nj95[~g|D"8?C^7Z/$;!YQő͡[ bE==[\BZ)("L[ 4q# 3vbJip IDAT=$̼l Z|W9ǧ~O{hAn$_t2]^ Z٘w_345?y۫ o[E;. yGBٻ?}]N ιWk~9.{e"Xoﹹ4[JZA\yﻙoK[oX(DdR$cE*dGbx< ݠLZ,ǣ[ER"@($R$EH]{{/3W$c!lu-^ޗw_}[H*.z?8%I_CWb x#xպc+ DAX/ms&'"yCeXLX+_tn92U+;sEeh+rIb9Td'GaK!e+T\B֊KqR(j_ĶlX$gǪBqb+9VI]r1DEALOO&$xPL027z{JqҲzKd'Mtu yN0 3 *eCٴi3ٳXW.Bsi?ϓybjjVב1, ; ^~uk(]4G6c'|wM\$mHsF/;bgN"YEILg%;j"D[̔সL·cf|sv9sղ->mކn`fe*- a?E{yip⽹D~5&-Yn51K"0Z2%g3G~\Del2];Fsu[odtJ`m ke fJm8yhl jNˁ5:DMY8}ع8}R30xM%9DaVuťPH AN_{j|P!WDU$ʕIKOMJjZۓV4;zd H*:FՈ ~|2̬avi8 uaQ03{Z)Rrt ec3՘h31c4Q m14nl>I4,}U7F]%81y?crlYg|LTْx?:wb(P;'lk2(C_wDZN|i0|;D灃=65L@h6˿1[!Xm.IK=e@j |ʹ^%Pם,FHQ8ӿV5ə|~k/!7p02HowźY_UC$;{sȻ@aC[ggϝ9sV008fF1^O(R;B Į.UtE[CdH޺&l-5IܓeasX4[;w)YjS_%I3NK%DNGb@E{"ݝN%G^=/ݾk$9 XYZM}zzi~>TJs$ǜ&<\m.\~`✾ l儙%j4sD.C0!T8Tڙ#XKy M&,!3gf^vޛl;_Mx_ÀT '[v(n$vHT=s^p]IZJ0fI_Z S!^ ~SBu=-1MnQw ٘Ԫ1tld-3{o^Gyn`,/[g~)Ͻt#Q(cri1 _j!~]%ۚt8XI>A3|>eй S0 Z>,˹,o~'xg m7{6[c{6[v>)Z&ȇs`1maMC2]k BWZ͌4kc刼?ww+5WbXղ~(0q;p͖mǝk[QD eΝ\zv_aeۑWjѴsgNYᫍvc"f|vI9o~S[}).ay.VU2VQT3{s0)Ǖ+CO(MHS%NKj*Еha!T+T$?㘠4$rBؼg$V9ܑ??%N`I}} K!qYB`АA"W`,.dD{f}qS^Wj*I"]=)%9ǃM@0[  9ϯ07\40fl 33&c *)N!X$NzS:rK9m| rUlScYJbHu׍F#)\:>-/-~ ;ՒmSq;ܻ_ 5pٍZA7`w lP@@-z\㋬__LZ؞ /qtoe8Ck㊱d'MjKI-;TG>9s569UR f/<f3~_^oIޛ_s 6mطYGgViE ] e! YmN$>he#k)sч.uwBYf*}>`XY]}K pDvlA!{sɵK \JJܭ"uRv`V8dȲyG.Q>uC+UQ(MuƑ8iyٺ?x q* ?=W3IX^ӟKXY4e9ѥD %v1K=(~_$kå˃օJ8`7@0)x=I aԛI@V:6%Qڂ fs+˶jQ儣NNUDLTg{ րު}b 8jgoraH}e˾c[R0[TIѤg^5[7f:.*gpPrZYU#FR]8YYbTPw ~l]u9{՜|<1-K6j>[!IJ_zG *gwo?yr[2ns0K*/>uK]qv$DAG?$S|ʧTԖ 1/BoP系+A23*Ytn6b\xmzϾ9yy57lþ/-FyL {7[MK%`j}n%"buX@y .*i2WN>v'Wi¶k(@TBRh^I K.X߇P(ʳ;t䁡Io.XW f\¼pdْ`F}Lja.M8|k;rH+J:əgÈKpXJ|]ZUDͦO=uKl|x*T$*.8ǨpShqƳu77TEK|`f'EpȘWkU"aG%)XTqo>+D'bELo\C}³nڊӘ⡖U]x<̾{1sq4k42&@ס{VC6[c{6[YJ| +"o9)VЀ$md[9>X_'5^h?ӿȻFlqq~汒1gP<_h7 8(1AųV"~āoǶg$uQ;ir1gU7;G8}hKWf}J8KᴳD ;yKi4aĩٲWRv;[ =Up84-C(Y)`̘qgZ܀q=:&Jyx-.~[^Ǜﵲh?j"k1P * )CjE))QEmy5X!#x3'2zT*wjksҮLME>) ZM`_\}!nIo5lQt  d-5&I2`B1cGnJDMud3n'eM~+WE8'ޖlX܇zSS>0S(SPj%zpv[1%E|68k)QԆIovs:.0+ᅗ2ANN!g$rBl>xSf2Z9wy9o!eL oξ9ݿ@a_ g۰@޻ƁՒ+XNdY,Tr򆢞zEe: kxDǧ@6q&qnNe./;3&_;~0zz'|`K/'i.xUmUS6:ꢬq/)#@+ZEJ0?+KSz}e(87$3K}#YфPEIBVq劯TTOٕjIS-} =fv\p,?[vʕV/}?x!kjFbro8xfcu,<-qX0Y*\kV+sb&S#N//~0xS!QO +0ZuRFZ60OG ps Cuv&'lu 0[3^ⶶtr^2 :ܺW8b [0,LbmNX=bƉTec[mY 󏵀T S߫]0U`T+3s4֨Е9 3^vx~MED>$+r 9Yn2Ѱ?'|7[+4HLs/d7ohNϻr`&1бd//+`yo x577?v o a-oo/.̶#kȻh gq#6[Eh ㍐wo!oֽÜs} \f@$vi]RZ2X-J,(af[6}?3zܕ,[NS FFUf(SGsqmG ׊*!F)PLU:;ժjU%)1n==-+s!9zݝKHâaiEZq)IBϝ$]*TOzzCNrNAEfnzw8(e{XuAL2oPŕe#$Cm[eGkQ% Wf}Z-5եN wq)l !Lz;aOpZ-:ޞD"7ݕC%Z_FTE{ΑC+vFe9[es_ ׊zJvq7 & -/{;=>%q.NQmG~v]S`?i^:޼VJmU%gM1$u s`V2^,kUFT+t9{C3ڰ{3Ed9D>eh6S[TQðv9-}C1 i2bX'X_4ޞ:s_o3oyoļ@aGo Wtu}Gf#gYfN#us_,y ^^kl=^[s r4%#Cr4AF^W'lM{92Uz{ ~e1\0sOw׺QdNJ b%N dj4`fucf{:{Z-F~"3XH<9=}-机<߳VSiNi1vatw2Bꆰe.Fofvxg]G%O gzVX*3_]qq^֠SHfIKc[]r7osX66aƞD{.+qUk*Wl)*<`S䠭23 NڏiEL5f]SNkPՀ$cizua&mm Q&IOR~x&0g_ Zm;“N`CCoW޽@aoT2 cB*x6tE"okFޯݴ9Ҋ؆S>4[ۥ IDATj;0*~9M%GRr<g={ƚ-;wQv֭uh45[mԂxUuŴ}GKR҄$@ 9h8~"ޝ:vJb}C"[ ;UNKHFNr%$)}bjKtNI De8ǰxѸv 昱NDe.di֗ / SWo\Ӵiɠx5jlnUߏJBHvȽ2$%r|&;T>[+xbzGEBt8Y9<8#TZm؄ F^vxocy\Bq\](!x}f XJۙ؞ˡQ!V]<7Ei 0+J{K_{p+ UFѱX3Λc)bXmZ.`KZX==G9T{]eHR併2P8L=5KA0EOG.8*`>!Fy^qN~]ŲiBp6[^7107@om ۰kWY]n#o k9̕}¹&W0RuٖǼ~ɟ%?M8p!T&yGvgh]4gWOYYvLfvmUFsZ){噝:uŒɽ @`4ev `1(Ns0mNcv6T*@9],<ɩƥP*6)sZS1V)KcevbR&׹FȴKXB]skb8hl%gzz!8mt e^lgNM٭%̷(-g̤ܷ9d1_YU¤tL\x՚7f|?=?xşq{z•:@ ʘ!؟OM7Wä5,M:9Dzey`or ;guK=isum;hp.5FTUU etuX%`TSf:5#}51;*) Ɂ708^N=FlX2@iG!$rĝ#ss]NRY a8pYN&2+8k~Sӱ꼷9 Bhs;Y!^ ў{sm aw^fU#=63#ippR$r6PYjw2!bz-d.B^ _׼N;2"uD2+ۇss럶7,fʪ]eD!l<*&㓣NV+xϥ'h{;yDl0vm5>/4l,細FG\uvaySpcYE¥PPC0pj52'^>_]Qʾf4e˃Eh5mekMŞ0Mm.9% !ɉsXg?MB`߂Ÿht x4qHbF{,W#)pa,ΠA&.¿~`7|ۺt~ҔTMFA57xb6tt;UdK! efL?8]Ew~7`Yj}]B]N2J %Ao-C1>tg[T<͸ٖyZ{v.${*vvoqr46\ס2ɻ7vg|MpSLt'=?k׃5kk~ =;6[c{6ﲭ IǎĘ7isA[vub&\Yį yOs}UL:gJ>ƪᬋ0{رc@$l:-6[K!A+߂Q_K+Z=/`òڱsҰf+,@(%=0BPw7in.$!I|ᣟ4t5/6Qq>X߇VR9wO'&䘛 JQ9)CH,2yhYD-F4 nEA0 f\cB0^ї[Wcg* bI1B*y`M{!XZ +rUuCfss.ONFv6Ԣ14u8Od>4ຒs2i<ucb]_mKˢ_mT3,㑊4!_|{;"ٴ/5=)ۙ| ˙4m>p؃[Xz]fl푋(ʥI /e]GK\I4Hg{Tm {!6[c{6E0?E{ ^BX]S?K޳t9A(*>{ .+/[YWݵymBX4h5ޛ5e\tRZs ߱VN<X,XѱQ31 3;7XX'=yT@ WMj5%JR^#Ǹ2jՂ=攈f3Dyaa鸔$#$o6anٹбɹDN3`oU3fc؉.z}97;БNB΍;$5Cb_)}Y{asBjfmY]6*Mp.% 4_EwosIMjY2$.kF#HjڹN]_N8M"ZElr٬Q3mul6 I+w`{zkPfG}niV\BxaƸQ!,0.߹{K.lc!MQ{>{f]rg1 e]x|&Jw7{y57lþ]w"֍o#Vuz,<3^ހ YV2"N|-d60c. T'`eua9otGQQz< R7 @K"mYWg ~A'F׉c0[i,ofsf242jsN1(8:6v|X&32o[+$tS 㩾87C%%I$W_<(h{?5=h5T ;Ec\`62xAE6X0&8ԩ.tCKAp$XQ 8u 'r.u7Fc;'?nO쟙>lFaf|&_VWMb({;͎Euu;g(NpB< K=Ow$`0`4B/IB]*V9RfrdhS:9$A- 7xq`b_z<2x::KbRH\ iq!}3eɦ>B1(}F 45܂U$=tJPunۢm.WVǬL}roT ԫX@347\O Wqim*x_!]DcJF0 |?"-57Zx6*{!wo 6mط}~ѭCuF8i(q\x:yGL긫aqѫO?_Wm$ζ yA4ŜKXVv,%)`2'0oB?42{EǼ cr '`fEٶ=8:)9NT eLtX'͖USD==pҒcoBc!T8T^JM&*bVk| gg ͆["TށD()/tJșs<u!:Hb;kz8ajpfD$1`3pIl[)MA uvaH@3բsU3^ r:;4'>hr΢0{Ղmߞ7hnQ/6W:l*uזxv;nC!vw˅N(tk67lm ۰o#{PՄAYYF쎐(/vQzL]C"B I0F_hάomod gQ`xG_Y6 S(#a 3l h`ե g͇J$~FÒ1$=$&9D/uu<#O=%iO"ؕ9Qr;"&  8(j0ȱֲϡ=5LX5nZHv{@:S`B򍗯nkO^ ۵¶}lMXJVj4V# u'cWb,v{نU˪,ۄHNu8$]+.WvZOZ |lo<c$h2wwɟ_RK\LT=3uY&%9+a ʀRQ5!f[rbH8٨(=$mO5_NOLQg3Tm5ҭ`G3qKRԹLLU᷌txCN-MnPH{:; &wBSRau됷 V'ww!o3+$MཱུZvmouy57lþRQ${CV̙ip(jo|nm:J`$>V3t)`,Bi2uϭaB>ΎѫH)%GFCAq]NFҡHz9P)g{ hSxV_޻6.i +q_flf!~W;u|,{1隽' !rlUA|4'/,.|Ҵ(VDkS)n3R0asgߟvv$Z''G F8>##5wχNe1z/bؤNd"3C* '!XgM=M|k:'X!ADI1 E"K箱'?9r `̀cA6(/z:XK*/6ᙓN xU}W7 ýLBaA #1bvO8:?nܙy~nWa=FbSBxmWNLޜ8}`DC}i8UNջn*.9@QOk-Uw$SaFfGy6ޮ51|r{%*U(s%/V@i[N0"ʃPIJgpֱArX^ÖNK@δ *a *. IDATΣ[1=JA!_yts y_}ʐ/c-ݲ~kk|ӓI,!ByP5<ʂ/+ShRPʿyl4ws9{Ì҆W墛3e]$Ip`il8xysu gxX+EE}Q7=P|{ryKn͐5?qR1qDsvP{AaE?7UIbf](ϥygQ$e%P \7CW_>|.+^5I\Q8(2\ܓDX+ mccft)k (iPހK531"mžc9p#%BVv6j"i]Wwrϖh0'(&lCh?]Tc~GHK)'X>ӆ]ۭ:s*LJUʹl2A?6o(_*T"ӳJj{Ӿr-E .1Y")qx'MpCzӞ<ח^1}XA'ՊxA,g3xa@ɷN32ZP: OgOpRUˍp]g`$ԕȀ>6 ۓ{ny*_UW6uH[T+2c-ݲq _NF:fL TQu`PDmԿ^)񴮨||KRR3{l lޤ^~ݜj2.%% KYU՘U 紫sb# )@ZL)i[yz:ZuC!w̑jEXBR]!jn1k篽v qWb󕪴&󶱧2E1#3b,axz3Ko zO(8cfگ1-&j[0KאY!_rT'T5E9 5 W[5qt)UFlMx%$zƜJ Q őeГ庪΄½"SJ&ıURzrNEXH׫tu~S%t-ʥU vDQ41!wQ]Kh//[)OLl{umF5b VbQBUgS3ee=dn^*wUV1*:BENU z|gٓTیs%r>{}lJQ@xbN8p%/U>q{u,cO}rEV2 pUZHkS KKx@+rNqNm/`CEi5vCN*]䭿ylZ􄸸oLԱ5a? v񪜫W̛xaV`ţ KwF^ĠGq1iYDSNa{y{(dž'c]ڞ=)7a)ɕv ɑ;ϺގIa)>ӏzose[y!;!ܸ?w!'5=v,'/-] y_k!h__j'|ӓY{ۮ2]_X$9 ^Ph%K6)m!x"MafY!ր}/dYWyۗf9Lnv1+) yK7S7֫?v>M}kFg΀2xGȝvu8'g\z&Q1'߸6EJ#H}Koo3q  ƊA >yaNWb7Kq3L_ =$*ONvr [z~zZɉa󋖋F *bh8I>B O}8UC‚^L}[$ ku"F]kRi*⠠,p$0W3(k~7M< "6hy;08Ƿ[[ pq^8¹ f6rͳWt $9.F58 A) B%鴘]aEzDAD}oJ4̙[;HVﮏh8R$*^?L{VAy-^<+ 洣 wv8:9ǹ~"pQUu#pP.ȜjaqQ!(t9m|-خsw<'_%_jZW] y:B-kkݕ8|ꓓ>Ai2'I©mjT1^Be+S3!1+UȻ2^dMpJ94&nn:c`@,W>LEԒmhyӾG1 lxBxUjU@QQ4+";3V8.+-ƈA3.%9iY/ueoB 7*JC;'&: ^k=>uK.{>3id>Lӌ1FP^ܢCuxS]g-!'qKSAE^+9s1lݟy}&Ce_Y|t+MMl⽞h3c'r&8Ƒ@HdTs;' wd0U"S U19DGaHUx%Mhbap.@258O!R ୂ"!6smrߞtt6 ^eUkmGU,$"۞I.9 yWeeayK gI!]m/^ta-?ʀ}5SY蜚e/sN,!;ဟ-[K􌍫ݻ2]7)UԳ] A y @ 7|ʐoc-ݲF;bb{Oe,[\*c,,jfƱLOyŹWDe,?qr`7+ oj6Y"W5deL.vlNyEƱi:8D 5"l& 6 `},^m$"ttȥ)$EDVg}Y5&F3g\H&SIaES@,(*YNL k`jG#mr᢮z?=iHc+{b{Nx\@EK><2e2%bXJdSi/b7UN)zGft7|NK|#쀅=8~tb21&* f#ʁ.g3n8})Ϳj{:~ L۟.XVݾ0/di ʑի$P胞[TɾVcsO?,g#<7U) xUГХ(l]g;˘JH*i=LEN$܆Nfg5$d:4'bձX0ll*ߙU`*UR>F:U>19< 4Is,ȊEkbsU*G⼏c9ducy$DGަ -we}W~c~r ,>{~*Kqxʌ6ˆbxXó{ҷtH$* mGel+QQR5ϵZ1ƂȂӭ0# YB=S*U](‚GL<^&o>HWdjO)Rzwٔic2a;r4׷^W:ŘuuEN̢ai ?Ӏ/CSXXc=(`+ރj]DUEPOHc/+誱'&&ׅoū]xQDtUE|α.$z^wr`B]vk|qe}-X ye]{衻e߾GsZܶIV*eD6B8j酋TkYT59w$7eP\ÇN##/Gޗ?tm/xJY oE#J;BE{e~YY.Iq}˧I٢NN,S'C=@YEQ qJEff5N8 TkyX-7yG֘P^DJ[r'=k~ϑ{66IL ?]q$""OQWߘu&Oq 3=mj,#-WcO,qfsQ^Oo2%\ aTɉ-?gw*,ETC,(a}mՑsD;ai6b'*"Bj rYgI,.)C "Ƽ''+kTcxΤ DyhV儯p:ʹvZgl;ƽ^W"7;;Q b谂rL2ڏ{:)gWPB~Q)h*~8 X*nٽჇT’<+{z Jн9$hALc}yu WB r5 ylUu[ֲ׮};_T9PkRz̉V8qҷI&F,*lđCB[RAhpr;CNH@@e"{FV4&_U+T`9-z\NU %0Kĭ-KPGi ~8K"fP/7<:Hd9yNjW(*ȩ޵&Y#?{(YNxU!Hf  S && `$$P͆(&M6!aY=%GT=,-QY (& fqtOIPUط޾K O5a5&"FPdBYߧy=/5szruSY)~sR;&׮1 U>f~VCkwJ7G#E p!!aΑ;Mch&P@j|:Rq^+A8 nkU% SE{z/waӎq9/yଲX޹6":B2􌷑t(檱Vn3F*IɆ"wkUU5";=XZRQWCcWT5$ ު;^מpM"^>re[1WZȻe-{M⋏ cW+,,hf1dJU58zuwJEˉ"XΜmrtr S'}AgFL(K?O /m4:>ssG/!0lZmCz:;UV:yx1H(́se!ʣfg}RS>ՓDF6q,ՑXSȫaPPD"pE$3+:p^+6bM7s.`ԣ;* PQ\N.@r̂gמncX# "CE\=>[NKI|}I3UNQ(]b, WTh(5@qeH 3znXDy?Fʐ﷐c-ݲWkcݝ$Zk1F 1R25+$rÅH*&b#\X"1:j&%8~QRp,fy6Y _&!-m{o鋣|AbBY /R}&Ŀ )<)=jCi:=kU4 Ҽe_QT%qjQ8.G LOjU*#k1}gexcOuP0`g'jRVyb]PC~Vu2دNs4?uK' a#셊RUǔZ(lF  U#t#N ,U*;odT Y Jp6{nak#BCFT'8)WrwB:eq SPMfa^#Nh{znO@5T%y? {QeOX+`0E!n#񱿽c75MŻF>pFX/}lLi•+JP}^.׳JΚC+~e?c-ݲ#R_vǁ D1̴*>PFEH)'OjUD48̩ːwEiFޯ6i"4,Rd1Tˏj- ñ7-2UhjKf7:64:yidՃxS.b|_cTMCcL¢"˥)$TF-MBa(VQa@y40ƊzN/jP]TPU> )s)ȑ:?{V@{} *MNwoV0ө*7XA SB@3JEQ8 {OG=}G=-ϵ^<Ȏ͇kp}$ܯ@ў6V|$*0b-w:@=݁BWқ5Z*6!e|wKMgD@sE4獑L`dz,Ess:X# Fu'i/m`$x}2 ܻxS/5iUxloZYge;)m\ & *Ylc)+e| &Srؗ_T {':Yeg|JR.ǒv5"(띍t2!BޯwZZZ_}g޷FDQKKG"V}j.N4UkR!8kiET/\11 IDAT8kq9qBõN.C/y7l%n6+E˯y_OV(k-~ťYޞZ?;ʈs:q^L4,4H8/7m*hZWEiIc1VMw& *"?]op҃?!Qea%-F^\.kMal4^jm1 ԔluyF ߀;ʴ} \17PkoEzgtɴMI`yvGM "L'O;rW8B䉫D67A ްvu""E ѧ9ǔG2UoHshgdgKL9X=нEdpCNzR2 :ka=ieZ_NY DNh:d)rSȼ\=MN )-[4Гiy3e'rkd=BuÀ Qpr=kX{q>NKT8(9(Ԫ\֭o mBTU/eMB/Wk y:B-kkI"+;(&XZ(kd~dj/.zcVd8BYQ#X1*E.UU!=:Wϯ6Q5 V؏iD eAHlԽf {UGa1ysT*RtsBȶӗ+M#X[m07ժIR"FbEDyAoZ9NdfIHI=Α纸;:L =fcq[?\(*;3B=ݱ5Vc+9&^sI!`5FDP>u>R3}VGd鈰:cO"TdvhzHIUj%6|jF g" }/ Œ_[<;{f}"/QgfrFYz&E _`_t >ކi OuuCsՀ0<S^Ч#O%<aY 'ԔkMS:\LʀRuD@F ?Y4 ;>uYkEyOwŽFF@`oP}3dnIrJ+}<8 6~}v pOߗu[˲E~ %ӷe?!c-ݲ ^un[ZDV4U0!Q$TV%HyO넼(#؈FH%8_ļJ a_:pZ.'ڄ/4 ;F |#nMFo "&r&Mdsj.ST,ewMJī%Ȁt%kI0qA@}A8ń%Mq"|}rbXQ0rrV{Wx]og̬K*"H? 0\D%#<!%ʹ^gl pqxr) g}wt)w^'"t̨c8dԹBҺ&<6"6]_@>_m>Jqe*noq8ܼ^H1+ƈFnCO-@,;o|Mbi碧T".>+§e$giv*"4D*"|-/D|P\h "RĞUYCd[U ]:ᤰ _k3ԞƆwsiO;TDF"`TQaoM<αFH!h]wG_[,pK O B~ʐc-ݲR-˨$b7tzkVdOjdJp aVeiQXLDd0%C@vK¹v;5vD&-V|忮xȽ׈3MȻN3tETs؛~:77]v52T/+~zhE<\d]wuqK+WyJ5pC|f ҈ ߻{bHujZY9BqG9"|麸LH Z'/d:CzJtsFF["o*%:K9wS٭SUv3%I$ :"d9%/j{qOUn!Jڨ2 {33wMӺ1DGe>7zfk=KzPXl3,m81үj,9ojx}сSت,Ux]"|pۗcke^Q.T$)^1/V;6asTِÆt(Y<\X5a1lߑ"aXZDBL *<oi7yNGwTDXsۘʟF٪pqxe)Lk_KzUSU%਻H]EBȎrվW/59}OO_X y:B-kY 71]F1Ԁ\Ξժı,.ZMXFQCֈXZ<'IVlP"/ڂ\G7޽ytZ_Ē畵"+4 yC^[~[7 R@//{5"^(:3*A[RrloUE(&e;hb> DP3.tXo}jrSuN9R`p ,MgCYkhʉxim5>HS"%GCrtr-]z gyn8^$WYI ?@G`J?`y)ӭHKn`DypVˢ\ʵ*8^rGO(f$lbT)piOzg& ]%)XJHPQaꒋy? | 9նCM'-5ZAИE&eawIidI ݺٗ5}92^sGPԸ)0:JB<Ȳ9%<HLD B@lnps:]╋\%v(&/4F\sANPi>۔ OMr0γѣô&(P7E^)k O'0ԄgChvϏp8+EEdp֡y+D6O$pFZ59QH9'fɬU>ckJ~j$+ƿ6;X:F2<&=WGR2?{o%Wu{95n jA<&}WnVrc͛I7n8^+&of0[d4؆$1m0v@LfF cUuaS]-/zEva~?f(7 D'kJ^p"@I3]z-nù0aӘOk=17WW=!yN5LAU@8''@E@ԩS08=|#bhVyI IO*U=և6}RZxГ[sX`M:戒ozA4id 2#KZ1QKM(^<6X-YlH9u.wqL,;FGA 7VUzmo/d҂\O3-yPz>vzwmnG;0ʿI̧;MSu-64G#G\"I"6iđjR4UcVA`jFcK42H$V4JYo 6oZLkwJmi)ܚSd C," h;w9y7 CާRK K`McEa{ƛֺyMY:+[+k'tzGAEI@lUYĆ&Yzev֕JF xgd_U=YFw]+SR໢W0'3j  yʃR税#]?|Tδ͕AEyO"FPݫtu:Y1#t0x]:Q %ↂˢjU"+ YajupVTdNrs=/nGɢ#(O|~<1ܗT,4ߌZà nÀS B7bwsb瘛ScS+"1 g8!8hXzOy1hTa2e94հL |/ŊϚVrfOƕL%d1K1ac.Xl{rK|#of??6ݎvoZ5###k:5[ԌZ1+H\F#qDtsj-Ƒ1FCpPLQ@歕bD )k+ xHuHEr7Wu&|s; }) uuYA+q2hHu\bh.!_Lh&spڀ6R46+,U7HiĘ W}##F*V~Rg{RT*)֊1\lȍW]1:1R,8>MN^Q&uPͫ$Cl*YF9nzi <6 $Qe7D8F!2eE3D)ɲ\ʢ2MS -dCda ߭iWWx?-InX!ܯg\4sO TX(|b(McBXgc0?P8ȪzrzxzvG_imnG;UFY?>\*Jm;V"ɪUEHɥǏWc%ID s>, Foum_GyIchLR敝М|m;`3-y{=R  q< '>{O6T5 >wwKI9M 'VGD'ı(l,ϟ0X.a>LoLuuB,ƈ4(DT}\$$Ƙ^ߺo: [BFtzJO{k&S3oP[Liyy]h?T"D)C>BсOL_ߚqkgY{/|M#2c7m 2 #> oYdXz}om'In5eñ,yR7]7|+EwnG9Cy\JjDiwT.bjl,D>el̯=uˏ3}b=3:Tj IDATT1R6K[VM!zbEfŜ_idP2zewED8!at' #O=  ,s=g}|t,Նt1ƒe'&1P"@gR`3r {BGNy\Pcw`; %%'Ⱦ9 2 5+Ykbdj׷M*14= *:3Y0s5$: € OΣ\UUnjׯ}D0)ip*~Nyz}Gߜhw;V~{R.Iqu#~[R3o}HzXd527Q蓓c"43E pW.Q*b܈׵LS$G…c!D=ד][卾bbB)i=yJlM3&g87D>ϝ?qS{BHFF ު]W R*;Q'o]dXmաzc]JWY"((!_ɒ᧯X 4}TytddrʹѠT|WAt"llz P@.zgtd)K'vl5(:Qb-sj YcsqKXfk5v^~7@zkpسqנc"-3v]Zc-(σyuMSzo6_9?QQ&H :4cddr›Hُ֡bb+Y޳w/{e9da\XԳtvdtEGTâکZouw|`ӊhS|E:4 LOGGFBA >.e`J.*ω7(KUUU^;丯\2ey^r(WgYp꺛hu1}^UJŬzC㏆@wvzX2F!َWzb=F[G]>2ǚw NjmnG;qWW#^;" "E>#G8Idֈi/ظ%%h-&w2~zW%I1ks?%y ]Vw9y'ƽXFC.[bm'wQ0MBfϟٓyG|]mXAac6+N%X@dwg_́KUf,q /7x* IcUNlU*Α92kGjun)\/ϋliY^ #8˲ˇ'9ve*d> &1L(>]A^Wc[ 彣GAX|,NIj&4/ndFz|X$-/#DюzlvuDKH/>5A8v̕l,"xkrI _.m,-l%ntvJ,q"Q bZ~_@f%syj˩8{i`R#"42 up%߱7O7G VǝzS j0kPeXhL~K+e,ђe)g:Ѱ/ArZjtv52sx8M5HP-2{q^a:4|ǮSe{r}O {KH;U[;O7\ZuWdo{^G|bZ5(gK7  ڇ|bKɾ;zCZ `UZot+n ) sZ:/M{x~߃-AK+ώ]=G׫V\r^̂4pݥOL)ɾXaz<zpkݑe8Oqeoغ{%} O&7'ݎv㦛*LǒD|KD+8pc dՊHzdzNk,Λ)-%E*1?52pǼhb kLK` #дKڀ9(bZ~⤈]oR^,P OiK2_8b 2}.Xn{. 򤲵cRHAupnzYAPV9Teqt#\e4dЫ[:}4ҽ##w^U4}dr5r;7 z^~:UՇB~/oXb yʍ 'FG5yDa Ugܫ8ǒ\ёp6KyqZJDa8h\o z, )wdZK`C pcdPPxbs; I'131je~,jqs'H׾Qi&;DzPjr8)[2 <My x1M:?;a xenu?̥߽yT PrN5i~ 7)lT^,j_~[~qsN~s GeϮ44Vy͗n]3r>^Β(=ѸE;ʅ׽eFUPQP<6FA$ϻα8&s0(= UѰ.K)jZjD@E{=ѳe[&i"Ёy癚+gCYՎpQ3n(׮ u&[ 4$q[ v͉6yowo{՟XXm"|sO|_x~rʇ wm$@[H”gw"đ o[ p߽4sE%^H{&'B:˗I! *G.TNM9ٔzͲ9zC{WvXZwd;)kuaXzlۨjguc0vzGV7b 5~ltti~LVV3̌G[Xm#q"rˤP7%%·CL>h Aʣ;Axn 骚XxNk刺|Ƹ_y`_IP˾ u 9UVz}RJ_.˅o+'EgQWWWU˰|N)s":MpdY nA:dǼi1F5$۝}PnXtҊSf2Yu(ܯ{tֲ{+Ƈ/HkӚ*ӾbE*)gh:dBFGEѕO4>$wAijڗK>nȀzV{Ty>54QM]ʵ;}~kN@&ɇW:+28tODюv0rYđ_$>wԴƊOoaE06%2=9_*kbQȊHAްkwZ1ZNLT3a_/_&j9Ro%23K- [85JD_yr6{V~0oY-Mdt p< ˜Xisl IUgvv^3{QBbAg|Q`0g&_w s#/o[iG.SyMnP1 3&Z/y=l׷\/Iɷ.lߑxPսO/U&:p\ "6zDTgk?)ߋ]!{GG=Yde @^q: :#k!ƺ =7.*ԓ=VޯpSr<(PvfD2CI+u1vh4ڽEmoK5,rk"ɠ *F/{hkGgϧ"Jn^{d15:lֲO]2bun2&t݈wZoyY2<$ŢyݰU.P09x6y9&vW%#_ټ4pblj$ qkHBbl|ܗY_뼣H|}ilD4NLД #V|*d&-u0u 9-%bACU+ETCS%"YBdCc&7kCwSafڂ3r5HPR7m6l͵0i P4v. Nu(<=dA+'Cظ?rBP;:ʀ23 ],͘]54lwnYIbKՈ1?`Bq=fuyrEpU蜗J%O.|oa3 ; E/D;afȕOɺ^澑^Z5j?;D/qHq:ApbGVF%i ehST@eNfpU {/7Q_Ň|(H{oKDXO,U)! z2CZBaQ[|E>irEW5NGs#E:te.VsⰮyƩs4zAKuCλy}ݧ1Y.9&&v?xWw$Z"LO{z]Dq$i)X#3>V88"<{-%✖`,mx7>eWjS;BكzUO(SV=]6f"*!۝s`8b9h5|CNߧ;>PAe(ZJ7ZiDǚ4MqO昙0f6?cFǶU3:f8LIKqփzJaTןܰ3EVr@t4Ee8zXjU!ҳeȀ ;>qCxoBw}Ǩjg<^=Y\3]n΃ ,^dԳW<1S3gdNg|nȜ[Ox9ӍF;}"y؅͡"ZWn|2)RD=LκCPҔwf'sڈe_+e]pH1>L7eS+&ыaP "CEUq^ˉ {:1b)spv;(E],77qNxgtKaխ_ģ_|47<^a[NjmnG;ڑx]?Vn+FI"3>)$!X+J 9p8KbI3L% u84ձ1hJH$ _F>/-Yb2"yxpG^]Ą5M)|1JwT;īfX'}cI<&*=鵰PaBDotآ9if IPZ/.M{&2ݚ9/d4ΏIю{L]FOϒ槄Zҩ=X1iSةFkKw. # DSMSQSw_|(lc5JGUDVd">B?U*,Z$(}ZM?ƑsJkw&YK1 ǝ"gp?2,޲}-2j5#kDR*Fa5u8sk?|ٜu>NVusK:;OTY(&b/Zgze(UNL3j祆N{LVC}TUL;үUY]J_`MonDݿ}tGf%6˴rK7d4Go F'Ru]alVzW Q/?VFsbm~sMhG;Gn߹'GGŲux8K""k%sؘ$"su"KNGb S#/9FF)&Mbf测BHP*>:>IWΓυ2nWTK%2g4I0"kg!tǕ_n*a&;z 7 6 z?Yw|ߪz/. m#Dюv,?_m0b$8cHb` o &1sRk*q۴=%IISc\FR86Џ_?>OszMΊ!mhv҂k4V5DtK3 %{wW8Co!W-R+Oņo|jJ|<`)"X5jy6K1}Ҙ;8u@cmQ*bD~qa^ps(G~Kp~!w\1GkT,i2(`*Ujn7 NgPR*3#?*ёaEDVVWSxE*mP @ns0g^?7Rzȹɳg:Ea|GV*Vz\ $:4Q_ %璜&q[1xzJߜhw;юo#VrY I"q5?N&'1HavF(bF6ڠ6#;KRk4t;C"<䴂©NO wyRDp.9"2N=Z)r1kx_Ce;W=8p|e[Gȵ M){价W6)"V^ r`wB[2EV{-Iܰsٻ6%"S]"<䛷 uk~{^E ip9*ۆLwKFP/4B1G&TU~80 IF(x;we;y1Zˊ n*p7MVxldt_cco.Л|-w|5U䒟OX{z佧MoNɻhvX)yYM3-&$yJ v]kujbEr884HrvFUMO{7WXp^HS-drJ$*h6#|1^cK' ԔC$bPrY mP)H^ʉGSJ5C@4bG9[u,Hf竀Rl+yJv~d;SuH[oURst,fX#rιa_=髻vNF'/ngMxD|'e˟V{42 {*CukyO1R. LNyV^D K&BH)] 6o}^}k,c0^%CCQ$qޕ &5hCs/8vcp[p]lLDG) NQx0@y"1Wޡ`#[nO0sJ4P.`:ͥ)]]4qb҇̌q%r^Gѵ'xJZ5.ٻCǘ*"%zS*j y%kV0;ʲNcUYg)ݨPj٣s% YveiYθb8LyT1*SQ <hH:-&}HO{W+"0gE[fU牽f)i^1$WcNPeRIL߹.ۼP4076 `v 6KFЅz^{mWR6l,Wn켪]/{eyꂞw2UChy3ԛ?h{)#!~zmG7L-0J:333ͦ~dzvxfu709A3y 0{hѡ?/ i .QxYrzAT[xcVe(@%újj̼XX\ 7.k|g7U&q\4| .d[@|kAnZ%l98] h]\4vb+lܗ,< '6}2b P0ߵ}WK C8$q/rن<!tM l]9)qz9~ b!w_'7/W9W'ȻG'nA#p \5Ǭ}:7+iJ0O\ӑa2Yi6YK4$h6[Es E,+"M @UT&*iGl;`CYOKmVscLAyW:hw/j^FxXAy尻S\ugjDZ0/ƃB Jh(j}| zӡqEc hj G8@WN S}rQ?RLUݔ_18ͫO  0j;"><;a&crOG,#ɧi6I๝;7+B⥁LW%NQ<6jh`^v TqeDŽUq<`tBلϨ/sDm]G@q^h=M(o4DxIIثzj'0W,}  KaƯo{K*ÚO_bX*s|V^{Mr -WLкcY odl}SR4J<9iwBP"Y?Ayo~']1ʐ|y:GG?:oݜ|>u^yTFh5^l$E0h,# 5,#X_mKuMS8|(o4I&4֠єB$RU|awj_U=: 0/.GZ8mC MC{h9/5f(*nRRR)neQȜzS<}މJ@衙_BlҋW8xk|kN}-;rIwDdv5hg {P|{Da䰨]W{ YSmwࢮun]mt-4;+~iA1V clЂi!˰4EPAhzf p2(ء~r橚\Q^Zk ~SN>1Ɠ%4nvq[kNjiڮ2RU#@Iu}2i;JF27`'.<6kEWMNsNP*@ 5GQ <ł8h ԣmN}n4M*Y aW{הU )SrN`(pN5ʮ^ۢ+/ yՉ>G?щo떴$=Rj՚%m#D2R ՚DI+\Zu咉bZcDzFi{7LywW┞Qoj3I:Մ}# h0mЊx0d jy2VC-Uuc\^I9 y"oW]xqv}jWZ! fY6w2aбOTCw}/:̆ '@cPTw7>pA{wAQ!6/ykfm?kzT]*O\`R5=6t_qSc `ߺtTkH Ћ9;Ab,& 7Bsl+̢sbjb-Tqp^|v%%=J#fpjү$A74~4= c Fl?K z@JLuAp*qXS20ͳAiN9A>69Af6T(Yni0>܍"PQ_i/ N^&W}Dy{%Mڝr[[FJ rMKc(q(2~u6Ih-9IDQD_P$ qD_U4$F|p9hBDcw]EEpEǠC Jck@h3 Y1W *ݐtQh?޶+?dCEH{Iq(UAqϷ?K|s{-|#Cek ݅U0\@/iCHިSы&ُv| eQߘy#qm='ٓAi̍-Y,\%L(jb犛v\.1#6k4V&Bx0=/{ǖįM @šR1_;ds~?x |_#ԍQ 6D[ 2|zIA=S3 &&dy'EIMD.q\ H*pGhE3~uя~ĽܒO8⥗NxwC%Mb10s&l4 ˹s&-l$s .`#VJQP>\^*!M }?׽JhBZF#XG:qlL70^FH 5WkJz<2C*:a;^_yy.{\E+wȻ~Еϕf&]J)݂!("+e'z[(OopC<]*1E1m7nmQ{s sb0e4M(zCU}`RM?Y8Ds~wNB{ jĂnޛOwa[(xbk5U!qpjgALN] eB1 $<\U'VӣB+L2 (b mߨY_<YMgep(e7l8 :'W;=9CאV_sݬ}x_Ic`-V'"kUC+ ܍.GG/ʜ½|~>G?8OګKu"FS#z{me*2 $1028zLJeFg\l{y࠸\K&1-a,䶯~ _X516lhR1$`H/=Ҕyn&ѕݲ=EN?Q)D >'%֯;X\M?}3~[o2 _=zٵ {#uѠձ9}QI x]PmLO\p[׋I ]Ht?m/!B/>7TnMM>==r X-) 1?H ?t4} MJ1c^nҀV:fH)֧4tp&`X`ÞȖ|c)j ?AuQYnOrNMgg0W:PE_iaU!;' D|fcCSCXx)PU_8e1IbNL5$$XiTUCj@Q FmԐrJ$QL_EU_.]ViFQCzrϫY+t K{ _wX^]W aOhAuZp+|5_?"V*X ׼,];.]G%ícSW/(W"ώIzbr"%䪪HWŽW<4[JCGZA+*Eiw T7oyMB_Տ4 ~c/RE,x a_ڡq4Zo=|~!AXL<uXߛqވٌ`P?u AUEmSӛ! N@7U̎[lRdق:cy 3`#@l0xҀ9g]v>~Pc֯գ L?w}2=l(HN#~=q=wqS6bւ8zԕRƱQD]d<7DGιaS*17lna| .HAύ IDAT _F4'!WNuO_nhq舉cFu_.6(lp..fra"`&li߼-$ץYInB0QI80c2~c;T=P~׷Ŷ*ؤ@u57#'¨!#hM(!tcgfr4j:?d܆]tQ,bȎ$5c|C3l+O=b>O7 ϴFCwx%W{$'fw?яq=ß|1'YW*8& ,j9JfiI*e& h87<5>7'MU-[_k?U d-zoTUNB^VK=©zIdLRxyR 4#PSeׂB,#agwIS0שtFsȳ{R5Zwz!>.@Hv~AdA*xk! P*O]]Gocc֐QQ mߩ/N]{aV8Sҗ=)`,j 9\:vL^C⃯OIT w?0r͜XTl!@V&& CyAfW{$'fw?яĿ-q?|{xeSQ(汣.NrF1∞3.IM[@kxp^*fKK)8$cfd* cFCI1m8XNt5?weHR yѥ%rXRJQ_$5qeSTԖtpп%R`7m ehe:2l'4fSTv0Dh!D oE+箖{KOQ/3i#o=no_iWpswtXE1:o/`Ǜg&T@k'oC;>z#-=Oc3W8O^e(ץTscCS-q~SڒrNe U&|H?>4$;/VOM1q-oHIK7yQy cKz_DP X=f& 'XxH¢Ks$(KqMxX.sF բ[^lOrnU2/7ggΉ*#fb苂Uu}@MNy/42| .?KW\f~.Ƕ!7|} VfHN#~Ǐ|0:j.|.$NY6*RqLka fg%M"zCK%F7^u7~xqq56n^ˮݡ,<ܘi}0T^(tZ 1zX\8X/.Hk1WOMGjrH{fNS=%n9 0cxɟE`v.[RvZTTE0 vC8E# N-Bj5-RF`zjk2̘ O6 s[h4eӼ2̧޿=V \Ck(902iq7ؑ3K r"pNm{;ߝx}VK8"m?R2N84_}{^pGG?wK.ykeOfMbk5Qe8'$i ѣ.NlHb☪aw :, [-1\f0}k?]tI jf~ʼnQёcŪ䓬1(#DEE0Qj`sNӔ$$TW*[& N!k7(LSpN):2l@ƞgaWIDQ  ,M:r]]/.~=v[EWv%Y!6s=+'>uh6 o^yZzXA`c= |۟ϿR0KNXohCDN_et^!$a,WOMڢ*_[,O]Fuy!T 簮趟dv-H$v%/xkZÖYb\1[Oi$]?ho;k?Gwn8U'9^[jV&0cB|uW\P_x+˅]ϻ޸-^j(Y2ee'LM @ > ӭޤ]uyLkpioOpM{%^표GG?-2mKsK $yQDs% j(ל"cw*0<kYiiZX k?,TҒPFCAtFN\E(!c lQh.(Pm "@ T̀092BxŚı/ Ƚ)^smދ~Nx] 9 jVq]8 nzL)߸]}C1>> t̚EㇱiFg8(xQQuÇi _*s°!XXUS'{#ˮOW T98+qQQ|j qq+051ĸO6ۄ_nz < :6bia Xcp&Ů6v/;;٤*A"X\uD9+Z'WxX$_qبO-ԮKPܴ9jes]b!64X(r-h+%N[-ocN|J874S~GrbFyX-=y1?1 _t2=6% pp*EZ'ְ18vTEmfRJ"2}b*\YJ^Wz1^d`s*<Ūsm08HOcD42a(S z]E^ڒ [9lz#B0')ZME5 $eA ̶Һ Gw!QUtے`NEuSczc)+e iT(7`d-XPprP6'k~jU+s!&'s񣙙S MM&J!|A&QVS#&c` QṩTNn(<=nj`kabE!UTZ.;WgŪ"VUYMtI# Q24aS.Z",,"MjJ}?~w>1:j>Wh= hm P4jZÑQ㍐mܫn.h5iJP`x\0m|=8AT Ssh5R64H$|@RT 2TTEPjVӑ[$@BgOyٟovПx*/{z;, +~&?BOEV2o# c.f݉6TW7A`C_=DsEޔ'&'<8=ϩ1 SSi#w::b>L7:4V}xLt:>3 ͰXO rpj߼~z+` Y58 @E͑;}z"BU<-*ZM ccT9lX0Ӣ"04W X'r|f;:/lQHM='^r$I%Gug&%FDrd.pf>79^W>9Vy%O#91я~ķusd$v3Ib^vdQ>z44XkA%`-iI4hcX)לdZV,uJ߿p@6w8vLibp!0x4Bz Z,8كً!Rޤx d@N)ƐSJ!#-Ơ~*0KuuJeZ[`;3 /-س&@_ne ӫ.Y']w{҅E:dv7)T.T}{uѲ{'1CKf:::c{"B|fBu?z`lrb~^az+;RךT.A+W0:Rr )Mm?>f[k̐8KdzrB}rzAe(PNNC NMO"%#M(`=u ;՚h;Qqf2-oV UuZmgYlhA 3d̩cDXv1pxUP>PD`iQλ ]zx0 W"ox84Sɉ}ݏ~'8FdLlye߽Ge6Xn{vNsE/XǠauQh"I|&I45_wnJu9R80҄MӁV *6]ْrQ4)LA 8B=~BpRk9me2YFD\wQ&/P2buQ\ʜ<[o^; -mIr,֨#xϻn߹ۆNz} {O`v^ jw]uw#0n]{WbREs˚Vozl;alP(V&TA%}˵ik1:_\T͢ltaS:|0';ckUQE.k!5U&LiÈI>8@87l3}i%yLQ٢ ๖7VltI"8myVKE6[Mdz~!a-tf8sNEEIJ~v4˰ P}ZEwY<);M% be^FC6$hi,DZcjx3Ё}@3,Cp|]Ve0xGPUoBSr=}t׋E#*)xq {Z",ϋm:8!z|ԿJe$I5'm ha0AدSU ee TG֯`,)P)Qm1ڱ×98 ; 6j/2ZaP+O3fYj@~6X,5陓&߷#ː$ 3[rLV3~z*{ʖ1ĖQ Ycx} UkYť  odb*Qۀ|p!aQr:K'~|@b<0kme1cZdD7߼~Z pV @ ZOldfS[@{#[ `Yl5c wĝ 4WGsjQ^N>٬X:w"!{];B`Ϡ3vljM 'pg+Dg::-^ ⍯W{$'fw?яNu-IGz?3Y^vd £; .-IzSb-kU".jZURӔiqk*mZUWg 0V5IG '5I17C&m6=s;Tnld-8FK[Mˁcsf%8<Ȓ 4T4 '(^ނN^|YmiAj..hQ*i(5|RR KeJ){$b.?cJ=84 yÎ;cqJuׂ78p`?6 qtNJ9)zFԵ^u:>{nSOoo/`t,·Zkh[04L'!= ߼zY~PDc6| 5|r4,6Z*]H]x.ZɗO@P)1 ZߘRqf}O` 0_f ثh 8Zi,qā ;*1#\##~ЯΙs/!qÌ5$s:e t~ApFmkoJlRDWN򎿾 " ٮsVFQeBW{$'fw?яNui( eqO}jZ^~$ @˹YIb/J)eG_J%k#Dժ15-ThXfMXy8<7z'rUCB9B,NXnH@C AԖC44ܩ8D8בĚsA.IB"KpLxM8]K+Ӭ,ӁAc "Ա.xQOr:ADc kp*~*chìԤƾ+38V:ko V;+jI?Xp)8{M{Ms(*MͶOΩba@dAr ,t~u0D)4a%5Ta矼~&I@YEx K/-ʶ) IDAT Bp3* &T WLȚ @I|i*P_%T`u Ԥ( Eqr]yN*C~&'uY$JE +CX59 tn^!i"M}L97 Gop_5Sv wx^s y*C Wy +iɉ}ݏ~wysDZSuCU@ gh1#e?/Y4rUgBuo?w;k!; _=xA+ioo>G?щ6m-~~EԧfeOP $@1Dql({؜KLS@V./eemI$`to <;'XFƂ~79($E0mי(8(E5 |ZoАUV*@TLfS+C.Y*^5=W/(%w#ﬥ͖ZQ.-8a[Z=C @RkKuA(z2]'2CD7DuWo\v|/^ yyl~֍w{m݃{Ѓќxk-1\p֯%@ bnSn"{瘦a]Sאg{s0wLlQtvVovћX l X@er"k1>=6+,g8TԤIk3u9XU[ i q J;&FFGdR|_])?Zގ8B#T`j`,U =DUɰ% T3N]S} K^'ֽ{efo#91ڏ~m1`a"JCPyhTHfK%( 9;ku/jd'AFl/8 ^`j.V-y(Eo5pC#r9Y@R)K6ZgyX-ưj8^r$M+\i&L*0{!#ˤDxpP` hEN)'j!*nkҒx=reI!ҍ^t>;e䷱wrc(p ƻX'h!s8MO I8cznWcԐ~a0bO]/7w^@u'DQhTxFp6񬗜ۖtMQ͉I룟i & Wupzɪ8SA~%l&Yc}w?_}Kl%.FF71+nrY{S{\?2=boC u V\ nޓժRQ ՚$a\.hH)ͭB"y~g>R$/4TiFiӫ ᱹ:x f|>@'#L3ʠi+H$ ĉ!Y&8kU},)8K`F Ѐ,c .bt d@AccZ.U5roz%iJPt.Tbj*H^'SWwWn<"ܶklCrPvP!]%p`ʕs;={} {.`P(jlɏW*)P߳k)U?sĹxnw\bO]Tp}_~d] NWPn]L+?@-)Y8ԪSrf\sP{ =5'_~x06{+IV ##v038BM.%q^D_ĊT95ͅp/.J>K Fd 9 e"ٯ$-x@s=~GTZHƃ;Mj̻4\CFAy}ssDخ:l+srҴ@AlD9\Lxt.3֪bޝ"Xv 1پޘ,)<q&G|`{db7 D_~]>3?n] |ꬒ*Ӹ +$-f)VO@p'FTE**U3X!GK鱄,~'Iޭ~)IQ{3_Da R4( +xÐw&(MieV΍=(~R$3 02vc_+vE_/{SOY[|S~t?g?GxȇLwk? ^7/miV o~Xtݘ\p}F`Q$:OU[3khfĢT' H_dE1mD z{SaJE\n NJU@D (/`TkV *ed xRg"ȅѓ KTBKmbq6A+hH`ëHӶ3М)|(C1BJ4f(VK oDCC)k}[ۃб\c7$?3IЄ3&^a;01?SjE1w!2dGH^&3IR 0ӜB85ڲuڵkX=^+i-CT.'|ȧ'?.PiD7э+\Y*K{bV]T/BcuU2I"bۭ,$'zRDjU)RKRrձXVBn:=R"J&.*^]RA],hO+[6 BC\:o4I"KcD8z` qH87R "gRq_aw> 3૊e-kNjvFMoe65sm07?P*1msu1c0pryRh~![`ZSEJT=$x`{,oݲc$gfl2MJj!S(޶ao@#eT_v˯^sKۈz}+o-,JnrIC`rg`x',)>]WWf<'Puwǹ_pB6G5Yd3e3Ll&6[!1VȑaˉQbr+!דũcQH9en^No'3K?v'{7;oϫ~.Xl X|?iyex\w ;W]λS4LEK!a$dZp֪#KPQ+oو꫔d|L@ޅ LiH)TTEN_APyxS9_c9 9ߙ6R9ct kjժݳwժޥeq¼bifםwVuTGV]%JqvJIO֜=9Q@ၡ8M]y|syiwS5BK1;~*73{¡Sōb N?ky ; b1^*4sjmpme4p N^W,jZ. o#wA|DN⤽IOsBGI6xj-O*6pN[ Θԃ^D+O b @p`KdAdVi`_^l1+1]/NؔKᑭZ`A@vB^]L.ްk ɱLCN;uڬ]^OL4ޞ,^~эn}%4`#-7#7np TalL}xv*)!D"X4إ) j|U! PH[nX3 5l(\lP6%L3:aq0d4Zl&x(rҚBPd-.Y즦`AITH*Bŋ:]%"sb޴% ŋr9U#("Tmc*h5"R"iJ$ s$EHuYMC;J~Y/ 9Ms&n;߱&B|'Kkȳ?"}1\@u@9<➀5δBG>#WDx\ʁnp e7ڵAσ?^jPVY'E([!&v /mQ]i|w Y^?>rˏgro<Ӂ;,nYpQl"6KN|ɥYVy'XQɷv?)v4,[xmNX^޽'ޛffjx ~M4ȻcssCoJPP3V,:/P*j5ISAT`6Z,YG'śq׼W#o?(%y4fPDJYX]@wF"HJ&%EUf @FꓞF#Zb5Iƒ"(- 4@{\āN hdAK0N_&B @'H'D$ILQ6|'d/,@d;+dـU;wV# OD&8-U1[="vl0qpoP0 Ws<.Ekv֘g.tg|4& ֪fv93}'nXNm;{{WT}II@^U\k檝w~Z ם"b'QbGsNIIqkG)__u_o$1wg\_&Ͼp*2vðCYG Xu{.NB3Ʉ&S~wx]tև]6xȣdhu%qZ0Lo۠*_W2'?}/-ॿX;aw;7|UX1|Swq׭~NEF7+>$A eE釭,\&&=MԜ #)3-岴Z^ ,3kg1!".B]2&9TR7J(k5KjSPpc^Ƣ< #4@@h4!jyeېI2z RB ,BFHCa aU)>t\Y̛q+# y5 @ڢ*@ڢ'-bX5elƝmfI =[v&@1 ϧcSb`6l~_r IDATk?{V;IsNU)D,_i̐eiZEYCqE<sJy^/v^@Rvd"Aǹ0p0i6JQ{ s۷\Wo$#R~[wgF7Zy?^\n<-" "Ji&d \6SB$6=wHQ>)2w> O/߷>VrP@V2=ՊEKxR$p@DfjQSJU,թ*TLN)jiƐWgu -v{T+K*:B{q.q<3d"4ITU 7]BRT)}"QB+焈INhlrN,LAvbӦ])1|`,9o`Ik+JķnzM }eIbK@CxGvVF719xCyR.K,f}= Z }}r"S,Wby)HZuޡDžU+}SlԄM"&A4% 05U)*V (U1>av`S(fuZj-k]eTPIO ,KշʜVKI3Ue)i 4I4 %Uzͩã\".NJe){3'h8q'"Dd C SZ)\R=i[lhʘDiۀ)VDvk^9 e9YɿȬObv9W2?=Je!h6ku'؃~wTVT>p[vq޶uG 3rNNbٕLLbRq AܔPIh;h}"^MRR9dt"NDFˮy*RTU!#>' ):<$8<D/2U#"I̷O(Ow>%i#i!QDy9(i+#Ծ>'-pP*R b'BK$lۨɸ K8Q(<4[۝/q4| X.QNzÇT1A]; U$MiO:pY-&9Ӷ*uZvoWa-".T.Ql=&`F%dzk"'M,/] ho/M!KLh*2x\t'!&ȩrKO6T+7Z?0GY2z}B<<ŝB3˗V&Ɂ=^SGVqn!6 vS7-L!!HBgS;?GGSEQy%Tۋ={,\?o}ڋy7xĔw_⋞?Ȼa ya2R (VpMbMȎ3^[.;YF]>@4`x2Zs\z ? sT]}wfxwj+q łClŞB{D C+k\n6*k+ H]?"X '~ZUDH>:u67o}h:'|O.F71-.7SɅaI3iH"EAt 6`A9xQpCK弒|'^?NWl_MП˔A˕Ӵ%˝,n}Ą-KY~Ώl!hϞu"W R{ wv\k88L`φ!%5k?AnOg43e$n:\}YU+(88}\?/s#1sv p϶oe?[hG?~;w=wgvJn|Rs蜃D8R |՚WYFFIUI{ e'#MQ؄ft. *$L<^{$zbDJ!xUѣflH2ÇÒbb,# nLI3)3\2ٳ'T)(S*"J4/܍nu6|E(wDɌhbS!a$/rL) mߍU3S];sH C=NۂY].eo5&gicOo*΋*zEqze$;F{#f vն{ٲOUGl. o8w^&[t{s*?{7qi SUerwC%J{9\x ,78Hn9қ.A|Nx48ך<>^Yg@"D,UT b?I.9pKK!ySaW^|7nwuk~dǧ+w߳ @Zy+۠|||#sdOOCE~Q=_tƜ~rE*e1}ȟ:72Vs$ 7/25jMsq|"d)K%iXVx'Ȩ(%^n9Ԕ&%f\W}?ۢ uT$^x4Sْ{LH *M;L$kVP #\!esM g,Ae2d)e4;D,rƦIȍ$DZiIR.s%HLKdV}25t睇3-R1q8'łq-E.q y*gfPf3b2[I24wou>4%x/t$ow^+W$BIG~E؇ZZR!$jGF2 {|m'|,+Ugizh"أ ھ@_˹o&B|Ū5_ٸq={^pʘ p_7E>);ɹMβʼnEj/o9O%{Dw^Ӟ0"(ӊْ^9Z֠FCu)``ێ(P)o9Ҕz%Ln7b}<oz{t_VXZ|r}O.>/oGeO~ ]λqϽnKk|#l{ZD}^WI2D]QuV8>\fJ4l=K{ip`R"vՁcxl̂lP "\\9Ά, !0 ;0w:.^DI{s'լD{ssB0@8x*m7_v3@u!\uvˆTw#Uzʮ{~}~Zzkq?^KQ:I)`>+GEn97* 9!Y;!wpq^&L3V23omM/7 B_b,)TرC[Cs V9{~9ݳ~BWZ]J'C9aOvmn1)z/¹(OJRl  A33|9uh}漕g>K{ӌ/gt9ntppi÷!%h\[QRh$EGFcPCϤBLDġV={Cɋ8D[9(\FAU]ezZ5taBorL w0SpzʪR*;ydj53 "721I"JV)Dl'uCڊv Qa\;DIQ,ˋO IDATаr*l K24N vSLgd@\G]2xk5CnrRg >{GRig{+#םܰe*+Wٳw޽V% A̵;XТ9sCRpyn$q} D@%g1n<aYaZ@S` #]em{z/~Sؕﯫ ,]v`>%[߹8:2o,%=p#K}C+&=2r9J(3jvcbetgcEQ~9$U XXč,_LN(.gS$8A{ěa{0Fhzvvq=ZsPrfPʡaBy2"J=dBcbŖ,VyNNBVӇ䑣d52֫q^El[M+HQwN᜹0 Oh 8J1wR]yB+)C'@E"ۗR@ lϫPTUj v񑢐w)iFU/T2v:XIk05 B2B%;:A.Vf(; $!LL; ,xҔ8  2`*Ըop77~\V k5!輄@'@8@.|AGyoL<o(L`g2~$[M H/N>XʹHPr. +lgfY鴻gJ9Mz!8VzdUTz}F;7[4.o9fzֳ;+N >鯆GHwo*YBhp/o^C٥{l[zU1~=RPKBl-#$$\+AbRƕTpv,Ge K-80BvLl`C,;%[RKGk}sF=tk׾g9F&d=ˀ>>"o^O2^_W@ i68G}.Ե~wvgWcX{}_wG2L;\g0K\\xq@Y M\yD!`X @&Ŕ:Շ8LmFf\:D#E\IrB9]~T3!mH$Tp$w,&{62%dWqpa]^%GB熿}&6hXhZ$50ci`b781:M'<98|t3-oS'|6ޓf|jzт6?8pzrm,||?o> `YwuyW8?g,DO|rNسʏPJ)_3`<#ß"@{78y;&f?.{L;ծ%_{jY<Irmh*=m pW0@j-puF^Mb o]g-dH6uf8:4|(̋ %c(f!/Mb}Bkr\z^ÄCzP7b! 4mKFv$#g>+ryqtɮ_k>,/ך8߷{+goFkEH~DKa5:EtW GjbpTt" {\R\NO##zL lL\KO:.WuP5Y4 C=ZD#Q:e#ّF"Y5E_ ܀s;ۭOnB7~|{ĭ~> oɃ-}GE@OI7uvx?.^o/6LՌD7Fr7^_/,YÀ ?:Jf0~`%5  ϓ^w*?QﱮvNjWzW@ H s:I؟'dJ9G^"$ YQµ>Q%@¤|$X9`TwɬOSDP<#rNeDɟ2E|rYP2Rv\lAGFPoıIX,a@)y q(bmhsk TnjwC{LgtbkaZ~DV oLpv8iqgLtb ɝ89u#aQ-u]:Qctyqe < C/3~ӛ7Vj7d8" }aPm޲y *{[]_m줼Oۻ:qɷkLD,?}Eo]42_]o$?Cտ^cl˟~Fzh׿B O5?WBvNjWz4(<{vq!uqSbjеl;7jG&0qV 7hARQy0Jٍa[.woV^*/KBRá3/bF@IH 꿅1v/WJ8ϡfp:Bhɑ'7dXG#"+gu5)::2CӠi0X,_!n/ 7 àmfsBuS#צer816<. };=qǥKFw{EoF 7oۛ*Y_x?xE66޴6~3/]79x4}+Z,=E]mpߏ[jT3^}+~;|ۯMuDGC[׽u{O _]'<( }> O>-Aد+!}k׼&n%4vNjWz S_ x[7㻿7nx-j hVprᚰ4{PI%c߫x8ٹHx@AahHbiyPmх#۩(gX8^A#K$woE +Hv2٢ٜJ Ľ.u5mCt.҂/9ź]abZX#ELSNǕ B2[sT2.g羿Ow5f!0p6Y͋DuXJ¤0txh)zU,Ջ׳X4iGF"\>2iۢ/Y\Mu}2E_dG0VQ< m[`a>1u#V.˸ rF]S`Ńk 鶢LMͯT+w?3vfxxD,>E?ч}[+c`u_<$ۦ(HmcEKxho~#xy1]lڍ;U }_o?M实P2,w]=v~mdx˕+W u AM[4o {{6q '>2%iеNNo! #ͰZ!m2ZB2[{ޔRsUFwzUA`KŲ6K8orbRs*SB7$%GA'sȺr9%ti@lȒW+`wNN< ad5g4ԆE#!|А^m[-*/Jf3,]Ȏ~~ f3KKv`b1DQU[qhYZ.,.àxEmq_ ^zƺb_T8 RD9kԥ##@+lѽL n_/ ެ{ #wWRUa_p킐ki xTٵ_&SZ֨FϺ*(;rzwM$p_<'0)kp<} - a<~/^'3Ny<9kyjWzE@x|ƶe+24pիiFPK\Rƕ?}-LyIMcz,O;#X]~3t-A.0"DQ!C" ̒Dg䬃}[DʽXuTwZ+H ^X8W\b3 UVh;T cA$z=,>vxѸنF l"Xh>_C]NKFr%ztV=D{ 'C`ECbT0y`=ŲX{3.W_u_¤Ut/ttdh$g&_ol:łoS*zMiQ_ʻI^|^jnޭgiuVM;Ray?2{<|ˀ*ke߉Iirȳ~}/yqΟB#4֟?P麃}ar&$;w'!ծU0f8?Re|#m&P 8>"V> =˕+-A~[5:"AfS sކ5"eUqp GNg0ڐ~CοBMCP%Xq0qx\Aّ]A_8IZ"HKg_<|󟳴aͰ9q۳nx3֟>!۵p<#6M=Xq)͟~Ϯ|Å_~_vv*lOw?bmw?={lf࿽>wM]jWg38dMZ6 ڎ<ݔMSJ7ndi; Eya /x~[ : ,x Nй2Q$bۃ H =+ aoߐwpˆ%R3$L(Z_B"0%!We! W}\iPۄl9` zqxz| jRsdY*+ۆ9WCȘUKWha'"1!eë#>|f ET'qp0 HD] jҏ.j@jFn-ŲF 4 ȱ.x!MMB}"Hd2nd=3run|K͛CA?-Bч{nQήzn{t1Zip 4ڝ]jW]ٕT:9\$(K&T]oqgg$'[+>Oyܵ8;s"hqPp"xu;Qa(yD|c1,4A^8;&E:y1F2|ݫe&hw:qqzA%Bo)j1\RTЎ"q5/.LB+ @X#D'T!~48*^.7Ri`R똸Fq.^Jp~JG ->j۲a({wtd켰a2a/˵&pg={'>>9\Oq›޷ף>b/n߷ga?>;Fm_ct ,WB!g ^|#o>_}{rƳ>> C./?#;ׇLH~ַxr)OvNNm]뫿U^A?.K|ÏdR< < ^Ȯ]>38%&"%  0lyM}/9q0 .Kё5-F=bXL4-],e.aRe5d-^zÔ{ YyP߫_r[䌜Yٕj0 RFJPNxp쿄*'*: V؛ *8֊=py`hRS`tp1K!{m;%r8,Y(ŗ}^&֔B<'> Ņ{LM8!9coFP,%q~*Iy;8|篞32lAOu[Sx{v͘L'Ǔdr,/xIxӿ/_կ~LGGx(YQit񒗼r|cr'y|jWz\U%*}f~XD0֗ҍ  o@( InD#Xib n*B8HeTwg;b ,kٯ)}2dD PZiNAv&b1;9c~@S?<0 (K(TkE<˝. :NHN\ƔjQ6U IDATURVs X,mHgT@|n ̴nW(V5rPյɝt@ʫ&$em$%GZr aڂDml 錓|j)kЯ$" ؛[XjŸuDu-m Mî"WH"'c}c6񱑜%,zƳH '>>ە'Kaz8. ;ծW%QY1ӽ |(Ӭ`ؠKK:eT%! G6 :PC9dH"rzC(n DP#hl#0-XA,!7nZRw8C ;]1 (# rMnSky8|P2z; \tIÀutFY9IVwb[:aE˥FxÈٔC0f/ECӞy!12~8 0Csq{~Ҳ{Uv1&utqO>f۰F_5/֥ v]\z^7~wvj]jWjʺ%VGX>`2 ^ICF!4M 񼭲o>ߪQ8ҵު*YFףˁrj JqLFKLF33 jnbAm&`!>6ܸCO<}bG߫e әe,0wӀ7nx:iФ\l6e$5to$ʫb$Nzaɔ)1%4-ڎto[+\hZ/J-Gq(ZP&1!ԁ>h Yˢ\ώѯǹ̛#׾½|Gۚqs-!)B"0|IY4#,DM# r>l`JPU]a6C\"H\s}r[!`ȇ?kjxo{7Q]r?N~Xx>=.Qp#2zc]کMdv[_U_QaptBϒ(/ӓjNuHm2F 1m1D9H&1+ z1UpᆧdcdQ-|u 9ׯc"(p[@+]z xs"#E= \\tBp#Q0y@kc>p`ѯe$ u\4C$tFDKLP#W2%x@mcXhQt*\@Զ#$P"ȒTCP.‡q:c[q#XB^,m6.f Eo]\#pNJ{Yq#F4Yۋ(40:[T+1~ğpSS=lB%(^2^W~-a;Y] 0DwRfv5`C=0ú|n w]KKpv$\1S񱝞xHB*ZdC 760!>!r%^7EPW÷3nEP.^.F8Ђ?4] )A@Jt)\D]C"d^9q! i)Kt㌂Ig˥@5٣(͐ k6J:/97#ֵ{G<$i$&׽*>d"bIR)U_5 /a]?K.23yFӰ_W:x_T_Z%L&X4oÐHVg&6M'31xy1Ĝ!+WãP9|TZ*gmcёίT<@VRERj(aƐXب׮d ʳkNc4ڹXj~$,>&K=[F ^?í7OJ]/!f=.췃ڟ!]jWO^/65l;ݔ3 )1;yd؛[b"K'>1Xqevݻ>(#Xf|r];g Eٔg"!&ъmժO|rxӚʖSbiچ1-Z@|XB*3{qх!kzP[ضuOg02@6 ͋ Á ] yxPP 3X D5{[2qzi(c4 ߷D]cV+,؀B]0;gtQEh ˪8caT m˕Pq6[Q1)Kѫ8m[]U$!VGJ P)AXe_S ɮ~ڝ]jWOA2gDitƋ ՀN導0gҥK5R3CVW*7cwtnInc6(9ptT#Toͦvtv㾑WBJGCMH!`GÊr0N h5 lɴ;xt{VBa>z8QB6fKtچP' Ф" 1:#dE)&4rW9ON}: e AàKQbW2O?`"&Y!I5c䵐EOKf6xaU\,uv2Cwi#4 w I]  W[Wiji%$2voi_x?yL˾mWFkgvNjWz5g =.ڌ7 1g"!H7Ni9W&{u:R55b ԧQ7o{ٿ;$9Z`gYE͙oN7qx%jrkʽ*R"؅>f-*+ 0 s< SrW\D7-s.|Km݅.AS8K0`69b1K#DE HժhEȚ98LqDW6Zln^Ra@0%䌶)Pu:B>:TQE'ڢE˥m[oYc\2? @P|E^z:mж;=]cFu/t4f[a>|}1iؑ=? 'nI ܑ޻lڝ]jWO~~t:g miB S> VHo>A]\UZ5&V)w?h8=u1&texUI-N:hL@$d]c4psA` f2$rVmrMZ:B>Qe$F0D`99zM VP`K~ĕq@`:)ykÈAI =hhksd ͐2)G$zf~ɉ,N+ @$`@N]ϖbF*fq2ιXjAPQXHryhPĞ mð3ȷWݸԵ7=z[Bt ,SYRX!_lm}@٫˥_m7޻}\;?]jWO´^:[o~īW-j[|\c8@YV+ ׯg#`FԍF਑lYdb!eiV-2rԘ/Hә*\]=S T,à r98+E]]!ȗ OxHmʬf^ ӉM&g!lI3ɘkH$1dZXm9c!x`by\QXsC[[ ӎm&(WsEPUhlM!+F #vl,-˳a(wqW0bcU`o]Wm]gՓv~wv{W/ 5HMjkXU0i&@:K '_<(K^^=mVkzZà<ŰY9a PH .y䛴t0T&;P[-cЄ(Q~Au{B*3ʈ#iO"$hׅj8锣s\jS/DrF⎾W$$"mɄak_m1pٌubi㤫[ln2DfVgB-!;EKZ{U!񫦕MBJLMFBP×4F2=&vfU@GMp ,WщE q)TI՘YUAЇ~Y/_GoyK>C?wwݙծv d~/_6= o;6 % u N p)aIԗKX1gj-˜Jok ͫ*, Y*bqF᭳ ,9^y@ۢD@K#LXBvH~sJ+g]bBE`[`X%z#`u|[AE ]R$ѽL6\"h.1]/O|+^%;k>]R6lHgP;w'kww]=Z\)iIn9y| Mˏ|Pw  Rb]Y+4B.U!5#!..D)$+;c5=ڎ1^oC¥Ê#^Oz#\ڞyT)q>ŵf&Z:6窄W!D Wxё{1IH -Mr593ƦR&!W:˒CqP Y4^cq Q%+ @X„T~-sE (5 {)@Kf)]S`4eB.9 X֋i F^v{u <{߉|x$]}jw]=2CH\#XaÎS ^qUqoNN}CJh;4=,x]:طR(0'MGHx?z7|[O't8$G b90)#80&@(@ GX;(Dk1,tp`$#ij>"{Q+DƁϢqRٌE0F.EzPDHtPcqF ;w4 H $ {T7 CM)}kb(Enփ=K8d@rwr3a85 +9c6E!9,F$RweѧE=c9ծvj<%k;^C|u.\Ҝ檴inj[Ƣ|߫=@[,|&Tv~6@^pS6`DW!3tp)qх3--*k@ytypCu'"LqÛD30d IL{1qRpMH6xܐ{y EɁnȗ!T7(]>N$<&IӜ1+>0QZhR]Ǧ)gͅz2FD3*}qڛSRq+ NEa!zUEr.ׯYxG9׽rk< 0(LVC8$Ke.$Ajjm]K_:.d$xùkonE `ĕ (sl+%@*R?( B<&f|b(0k;ЊYF4,QdաbĶ) |$6,`ۂD,v(5\ ټ^_8ICHsVvv؎/1xoֽֽdײf$Ub#|[G ai,JӀXdڟTqzBoh OQ IDATX.Zc:dHL F ׿֒%λjSNMl6ۖ9CA3RȘmEÀQD,,^97V;ÌLqvġѯCDYr{>O]׎󾣵C޻ծ~;_|+#M0F]vtL1iQ)a+]}Rq9Pezfr̒q|7%4IRNJ]u6rօgֺ^{Ӝk{Ϟc=kOp*hHkdBTyJH~P1]51 ޡkY]ZSU&#ޅ̠څ4gHBԆ7*vvE(J7:3$:0F=Ktrb5 mBۢi"|Ӏ~iggv|by+a@R3"] 5UTwAUXSm)d)3=Kzt 6-M>q7ŝm g7+ aGq#1|PFW?F? _\_5_k=~s㨔(ZEqxZb àł9 Fbb19 mmIC2WW.UؒQ(~vp8);O RuԷ* !E;:han 9]T+ =IpIu/+ot;A,Tj` Me#e^pFCFuOjen?=yhߢozcv0A8`l"`?s?uvɱie@qHKQ,(t—:}8*Q8`dԳ;w'pq\i耗5M%r[riW׊k܅񠶹gf)5a %5 slĹ!rAÀ 9-ldG,e,KZ gRװBPghN0?'o|+kYy5\V,˾r; x^)jalz=%k zme2×q̧ɡ[j xԍ)%[.{{$={Iznp?vuS1Xh]w=<͇ݗ+a񐬃X 4MNgi,EB'MH (-4E! G2+#ٓő$l%Wk< lSg3 \*0d2LYi4V; u箕3-z$+Gttt$KXХc\B pDk4 @74hZX/:Y re7>_\_c5_kʹ\sk`ߕgbSBJ wl^0(Ó~j[ ?lDx 3 W:v 6nFw6C۱vxIN r19jK{VE괩m6v,<3YJﶮL+bP I`WʣG;^[{9/X*lD'rsǀp.]tj꽗PtY3#MJBa_c g<*H/*|0JdӄFJ(M+R͆GǦ0O0˞է%V8 uB; DcL K;<~i~na\* y&Y%} X[b@UȎodԴHg?Y3mZky5\/_)C6@_h@JIpv~ucWյw-f1`/WK3!~޿BO 엵9Ω=}}gO}q|TsFׂ8vL ItHmg3"&ÞX5׫2=*5+嫎1&CJ|~kcd =Xbsd.hq0MJ@"%G 1RVx"'ڵ 5C`PE;6E qDInР ]0JժL2=RaH\PFCYp9%򾪟؛GF_^;ґ eh5sUu0 Veұ\Y;b8gK76&eeӚR=bPzwA,z޶JG+Ӣu)T5qAwBΏ0r+[zlVX}«L@Uq T݅(D@D%JrP47\H\Jrowwaw~眪3+ԜZkMk/fySOeb3/a&0YE#ˆR3M:$3g-+f/p!g ` HWa@,U5e 7ӟz~>8ϟAèyT9w)5Sl,\9;v}u D6ĄV!Z7JģGCU6l&TېX -=[Ǝ.^q+N8칬CaS8"+RbqJqz(OHeps'ě Pv|cE3 dY* Lb)g躛%AyV*9SIR 6\5h)9imt˕_Nfk?:R~5&s5+ #ۆ٫ ķdL Mb2ZÜ4q|bxqF4ImˮA2L''ֶuEK8 I\ilAd .0/!w©SJn3o%Z}_<0I“2cΐc՗- .%gYSUO{!15Et8dtrl~AEA 06ܶfa‡H<G) 3j[, &<,{¢eP\,Je4s?>ze7jؑU?oUYtm~j עv[vԓk"X~AR%91zmq0=fRQ T 5լ_k{zbECulv}V ,Ao+cm't|lϟQ_b{NVkJ(kByp4T"Q]gmR5j, pdUt/݁ҘWۂȕ -͚vA[]^x3⪚[j)4͍ :^_ia 6KiEhO WWmZZydHE yv7=e5|,o-rPSbq8 HDq{D 7.a uDyPF䨔0fشɠ46yzw--㾥\][q,VW8N fdWYy֚s5K/;hC8Vb2J'ɓuIehON{v:#9XJT 1AD%&xU(`Ѣmy8TLK5jԥcWkҰ{*pvEA 8txrrƱ$k< %V+.XtXt8 F8WIY]WFm }qWnQ) M&1{g\$-Wf@Y )`蛖LU$שS~R#:.:NxܑXh3S;Y8Ķ ۝.$ r4F&\N3\@;*XPcde>rL1f ^\x o qI_GhVV|U~|uy˘\ϝ^kJ<1#G%E̤p88V iaK`iR^&8jn,>_{ޓ\e\#+0f?hL0ʢY,cytĮCb*-m 3QŅv" ϞT3_Pr֘1h۬ɮccH maqpEG\u&flU1nx\Ű+GOJB@bP2x& B׀(?vO{͒+5LFQk]]G7pdMC9ڎ^o0,TUo|/Z|F%kVޯfdz*}A_(m6*bٌp1dA$&?\'32q%ۛ. ]<`xp6|!POuzbܽCJb#-a]Xx35.*Ōm8  ݽUl] $zs@ێGKd!}p$K7*Wגp19 w"X9!2F2Wy6l 0 md0"po 9SwA ]gxI~p #"n8 ;jJp'3,E0; L\XSAAH{Dvj``X& ] wv:{9"d^'3)r/ ]A0D:@Uksoc(T/S\sDͪ֬kK#K3Rba|QnIq !@ť7 }3Yry XM %N@΀^Ņo6>Wjw(N-0ΩmwaC.#oԳ~tle5J./͚^r@igV`J(*[ ӝY=%*BžwKғ'=`zg$q;$9f91p=Q5 N\\jUvBv[1|TIB`7g9g,Lr匓bPSAA1N8a QIxSb]#p'2[j "(ƒ<;ŝkZk^B,yfCwR>wK":)ψ.ܽkcL`1@-ŅrK,NL ˥ rdp,PQDm8fF]Gv"2rVb.<}Q7eQQ韛~m<_Ź҆Ƨ dDpGJYTɊq2n˫YV9ׇ_r3+{/q HRyrVZZRؿXB`K;P:vJ;@Xo #Kۆ^W)eĹX[ ]ϮcYZC;nϼv隕kYy5\_?F.a/8\.(gwi0 uG |KW+./@A_~;g] ϓcFKTśtJpZ5e{e sjˆЈ PWuh0OVHM0* sv7=;sޱs~# ʂj[,^zNEJhRabĔ .0B#1K|g=ߎ.5$ܻm;ъ'<>c *[ea0 Og@Ibjie9k/Ԋx0)[%A)M(uI<xq&asľ~9+gv;,{?F xヷsR5+Zk^>?} o|CQ#T׻Yr+ra%b^.m`ױ/Z/,{w'?c>?%(אmNbճ[ Bx 53E8~ C&anjan_qEf]wI T|I[5i1%׾~#or!nwN[B3[٧ 5*(HI$ U7ʎpzcߝʥs.8rWd/4%RG>1OGGպ4Q8ecdZJRi{6#i[« Vqzxs5.G$h[#R wTo3!VdfmW+-{{o%_sB,̺?z&!c@v&a^&J h+Nm& 5f匛ʸIcnorXb@F5u@ ~" ,ekVޯf=\sRr0zleaNh p泧DMcȒcH4!..Ipn"9w5p8 p8: #81מZbi +B^ðrﱽ;ƌӋ ?9.ag@P.Pe|SY)!R"?K4gmri"Cȥ0j[s%!+n=*#OwNyι~'ɣpԸum 1 `׿7]]%GDee7(1wR +Vdij07Oqҋ0d〔ߩȉ[{afu?[. ]^v;5-l$XtHkQ->99b+=RlYG?# hZvY4~C7GֶyEzom,o>|?#cehR2#:|*\c{H|\u^\I:mt4I^Aer)h:Pu;wjM^kQs5g?ł)!l+%.W:[.:8\`6^YJHf׿][kֵH`S_h]Jo% 1 AD*5OEl֜fX;5}wGEZ)c}4J8h&!2\*a8*CTo2@CN0OtCQ9W| ԕ8Aj7 ۭd7u%=5'2 ik~tD ՏyQGqB?:Q4RXkAqDK"2aG{яU).!B4u1F׃{M7F. qIєʔ)@cFߝ _X1|?:{}oHC۝쟝',zO*j=*$`tdfwVCFc7_+떖 #¥iW}B MKe`孶ˬ@ IQ! !^N@l k& R[LEjpƨyA2Ygg o I/\Lv%c2gXdOjxiL ="!wjB0OqA7y8@v'2&e&Z0Ŀ"bMMcJvKYH`3rvZE!?;[gpn\2aKeY76,:GqD9': p{Q-^J! H:&H9K6v!x4V<-UZuӌĜAiWHeoC>l{s|=Zk^Hl!N ;vqF+MVV_rUUq]^^2,ҫ$LJ%ҚZRM81{`Q)Y4ț}.:P3kQECEVuv-pfac|lr̐R HFv8"^Rh$4tjUD6@t ;жzUZ/bd9z424lb|ƌPCawǏUԦ3q>|*L//K~YwQ`4ESYbX>|{oo[pz+sJW7a2c6ޱiB2XPv;,QE: 1ML1L5v4Vl*Nevgs^fZkVs5+)Rѷ /5@8=dx穏ֵ-6k[%x2R,"/toi\.o@W@I܂M"WFPyc\cDi4~͆c.1XnN/@Hڰ,_0f2uYdb]55/:ic=Im <e$b곧޵Pfn\]Lpp 4 Jf,AH+D%./}M9@sS8l^]nFw\Jٰd>'xƛ/{K)%ݓg$Y|~6;}?3%QA݇T -d)1`: -k 2Çأ}iyy#,GMwDCANS0j.+ƨ@(&\U.-u( *F3I"SCOc"9Ho ?esͅYy暕\sЂ&8G~ѣCKͦT>{6.{Ց2H<:ՕDXىoضEǘa>+!޵uL¿-eIP{k#,p(B,>DI4 OBɑѪ&袷dpr&'3ӈL^ MK޶v S3pj ;L0t}d@,FFNOMϞ#{PLכlon%m1瘧DkVA#E溂 c3X5Պm[&Y!E!!OO}?7lwMs_t3"pس[(3$K\ڽ$3K:M[|ٞUd0#onGSƻUXh5׬_k&s5WѯBj$s?slG&;Or0J]V]g][xةט6lM"/صL- ? U('>Io9x ^]{ߛC& y VZLWn YGW﮵%Pd j wݷjnE{yFvàVmdtP8UrGqUj0=][&MEK<+gC^w]oi#y+d~w=H^ť?h"iYw9%szZPFѵt奷-IŇjtX|ʳۭܱǏCЛ{D(c/ a Fi D$Cꅟq;aTm5d89",I(Kӣ##:$2L55o@/^/k\spewv ËZ[6FѝAg%0Sl2yY\,\Z!ipMS#)2wr0Zb"6 %h )i/E]A)õ IDAT)ь #31 |E 4{*0!uRb(Bts<|xJ>c7p1ʙVKc<>_19zOP}r$R[& {P},l]0C~9cZ.-C={X۲*lo_q}Tu6 <En!b$mX2viHD 9qCd;6DQ(8ϽZk~#?9׹#˾5瞳zc91؄H퐉Qrvu@ IYmVK?Ύ[)>C vu~p`9 U6C? g'Io]֚+'L;yO0%\LjEX4PᎫ+ȄvFBg3n;C #^.Rl(y7,#F3>G"p(5͑]$2/}]=YɂZs!])./}1ccTD3Qaj6.?}>65=v-AK.̓><}W?&vPAy}A9dm-ޥ:$Y- qIZ,}_V6>!OBR<4t;-pGXˌ>]]zOcjG11 &|>{d9U ܽ}eVp̉$A dDw&Po ZH1h rqwE Sи5{4-q6 c9\o\KfTAjX.-«o=82k&$ VC9CWYi6EG*pw@@@Jȃ}sbN2($xêuԘb#)uYI| tx 4nFH<8^']'9 7T}ӫHXki"}WF 'F#JlZ-ٶTDeRybT[T'LƼ'ļ'LbU}NfR|}nNt@] ,B dL)l x^l|lV%okw("+ګ&5M5N<^EE+!TI %-A,ph3͌]YL/DS2z∈@ Iqki.0@ Ti2OBO 3sF@m0"tIGYXC[j<]c~xh.CD^htKZ.ՂbJm=:"YFA9hB:W+IEmbb&Lxa&#?rڷ[-$Ĭjv[x #;YDJTT"dHc-$:B^3m63*zJ0$8/)mKz>ztrD7]uH@Ύ[U֧Y!ٹYZ5W^zCcsZňg@v .xw7͟ &0a WZi6c*~uȫ+yuaJ 3** j^`FNY{~??ꋲP5@m TYՀ2h ZD 2j7VH[!3GQ(aXPJD׫m0U"w8;%V)y0nĘ&AC=f)?Crmߺs -앾B=RH;+Yr$pMrjHݲ{${X"e](> &]KTN܍3"~e/"?:9=ЇH2zYp8h/@02xAY@b&/Gt9&ļ'L}w~GBHidwd4dŤ|((Ds[1h*(.ݻkOyrE1x$()ށw?^绸\7(ס? c "#,f5o;F ᔰJK%yq%\)q;m;<<=- O䉂߹ml ? r6-2rWc4#5dX+PQ"3g4V/O|%]C#f>]O=~d:BQJ+)6HJ׏*&˸b0dĶ F-}*Jg wHyu+'%[[bRw8k@sFp UC MO~o">&bb&L-ۖn8ea1)58<+{iA=Ɍ\sFy%*ZfaАCůS7 jF4`*cTY z+42kchʨ9}.IMBjhV)keG {=RI3޹c hd3mJx:gBc .b^BHP0Q*A$VK>ySÚD&+AGW|>7 &bb&LYq*3Y>طRSr?A9nFxӈՒ]dXAeZZ'@յg! HnuvH*g4"%D:9 j[2C%,eLyj̀hr' FX'h׉H^ (<}xY4lagg::g.9r$r<}tzzzz΃@I.2bfV,k./ Dc]u\:,c[gB7@Hl4JD6D'/ToɄ o~G11 &|AndՊ;,GFFx65o>~vs߳"FL(%C&ȫV]6Z9kYjRC\ru%р^JsS&&Eǽm]91GC ZU{tAFmh!3=$ ld8>^=]0dY<_J1ᇌv?>Nq%w˫ԣvBWKSF4 À"^{G'pWŒSC+3,38aYّC{ϵ5eQoP,לԫ63@ȮT&1s<`שpxl?l^i!> G$G,%G47R?3?{~/p„~G11 &|Ag>7pg4lt{{D05lZ޽,NQ|nAV{n@ FJ1'ŹX&q ?P:bϽWic=0*DJr͖ {gpgF,;I4-N္ܘRcNUZVf%}j()(g~ЯJa< 蹯PgArK$v)M]^riYDh>sFH9˅+lHvZ|{@"g=sce_d\cGɍqv(i *Z*sI2Cc4xi>g]S@J4,XQcQf.h>'Lx;;yO0?Fؙ9zo<]`RQO:?!#ah %Hav 3 (xqNZoW}!ACf[V#) ÛˀanHc2KRx1 P58LĽYΔ;oG~i,RR4`$mu!kw7]U|p|A^~\0D>< =wpunhȺuJwc=hde"gܹ[[*بHbtk![4'hv{24嬜Czf3v܌xK (8 Si#^Z\={z~xrzX*XPÍRgT"\-vf#vɞ+`**)Ph wɢFM,8|r~ozOm0`b(&=a„7:f²+9o=.kpq9;E%8o9 4%ޞ_^pCt5 VF}gB8IK@=PHXȊ6bf1xo{10cgx׻x=D'9]4^GKZ.6xLaX"`lZlfd[8?pI#THZ0 (]mDB'<;GU1g}65!ZێxԛĦe߻k77~s k&4sޢA4;9QzW/P䀸F9 }GO@%p5_0Udony<5ě~rE@8!>nIVT17ӄ tL(&=a„OMCD a9h ggqpy0dޝqrS9C]]:v f}|&Hb$KvD)(a 8H%%-H6Q΄׌[,9$ͫϤ="HM)999f. ?^8 C_ ƶ%9=;?{l4F° U;Pn'M*iVw5bgqǼh [f^7IC.#m5H@Pl%[dWθ}\-{vVM/  vٙ[.A/ݳk7";ӓ?^ 2 cl" t~(}@=&&zDɱ)fw9hp@3ܡgb`3#@)O61ǿ ^$>om73S"%={{{Q^\ g(, w}L7iI~MleMv; ¶c3JIdD 34 ^OC=*0;A!U)`kNaB %eٷӣD~?vZ9È~A[xo؝c H8:&rJcf7D9#]AGi]-LR~C$A4E7u.!e8#׶GӖŌ!֡ˆǢ|=q=%O,k/i0!$KH̿m[i? +Ši ;wg4H4F<@ Ÿ Vg 6;ƘHʙGG|DF-: 4=0cvu}q$z4:L6 I3`@)(p d ^ pcY.ݎ21=seoeC.⳹)T{ƫb%);Yb@dhw]5djHxDmwܽRvZk7 ]!6I_A|&1%7Cѡ_onBcmiex?}}.@J%=i xش-_,s패j6d dY0{HŌAJȬQfL =r*]`<;^,,͚'J PvNsnӇspwk,F"_=NŕY}۝ d/ IϠ@0̓EcG7zpm!7["z$D]h ތe&~H [~+m;WKfnWTvVb#v/g5چ>45@o6 ۭÐ xbA%r+L"3vRdҫÓ\W_L3Ib>fԠip~ÃճƧ(H g%v>_7OcHBxVv|N>&L0a¯?S?23%$co'_{ėK-!\\mggN`6gD+;-EG#x縼vQuG{n39۩,xji-c0h o+u;53u1%k5p4- .ѭ(iSY@9߾?YGS{ K2} |_4 &0p'mf~饔IJE"m[)gBt~ۭ#o=FI(|3\ QMHHƈ#J$Hpp`g^d71d~ KT)a傧d|/d pZNYe`Ϫ< \]k&^^ؒ'(@xƐ]wim6!g]#1I9kǢ.9edRl"MgO}ɒqk5R=5 %dT#AR!#%d/ZB21&\Hʄ:t] 0k鸸0H^<:>^0rYM'*"V{lC]O%3?<>8ۀTے4aq0xaPɎ1 pWvL{/bb&Lxa0{!=o@ٵ\nA4\98 ͕*$RLNuqK$r#_êQR88!ٹ[Ec IDAT mG(DzrJF8ЎJts4M*<ۭf (u>[TDać_[B_S:1YtkiPgc6Wƣ'dg3^].o8N(J _H&mXYxWb!{M^ȷC.~+]_k>/y8<`(騹%Lf3W'ăӿC$‰!cj ?\|+.JGG6dxFlQG* el>an/p_.&bb&LxaxN. Fo|? d8Ϛϭ60 d4GGs.e'yoU< ,Bj!XRx0VznwUPnQ= Ʌ=mp}Mbp")dr`9rxY֎p1{{|-ufM&j,=˅d G %G1 j[^]l@L4YF"+#mxy9ﱿg [!ב;F%ZZq.%# m`T"hҽD+V{ЋJƬa8γz3#H;!葫kO6\h " RKZw(&DeM\_]ɁS3H坳#z&2 :-L /t1&ļ'L0N@ސO&NذK-T%J6H lٌzv˥RXXWл?~߿~'?T PHH}&!%C!aa`x$߮Օn*{}|쮾rNQYOQ!2 &2ih֌ դV`0"gD`XB?h4$,\#n1JTOvԇ5<fLGs]6i~|FekJm)eeoePad)&v%JK,1&MɜeGh^m<]dpz\XҐŚo|k4utdl>q*䭃H"S|M&mٔ?T&01 &0=*IO󖐬nz#_,$6:j2Ym@#mU9J ^E3 }NkBnw_=p~àho`sC5&8#-,q桋h d]BDu" }\r >qK ۆ}N9\ч⺘0]gA%p~sAP1Q^HBwN"##h@޶F2 X H^VM $,$Ǔ% 4ovhsHy.-Jv!Ӈ'﯑!qPteϣv$S,K&Z.ZM5eW`u@D ]?蓚"c6'}D'LL{„ / _5sx#\*TlVaÑ}.@cW\2-xm7jv.ܺV}itaxk_A#3,Ctɥ0hrɄ.=,2=ᐕ]4ŝ,`v$@&hc$OR*u&rf0t Ik015Y.lrW#-yms%\*Œ'/ Y)?ٝlΪ cj@5\4yqq>0c@<zfMz%Sj9@ID9*Adw;)d}]e98Ev>=9=yxr\inP鳼Xs6{<6%sO mqAq-UX\O4P0W &`|, |ws9<@)E˥"=gsǮ 9*CY4YJLk7gNԦyvժԸoo_dYMiܾ%M->E$ K)e,lXs/F0T%>aJu?C?7I0W &=a„2#G[vvᖹϧO}6gvx#FMۧ;“qJMxy8 hZ<~wÓ!k6Ƿ}>Ga_xc'L0a‹M!Y;"C:ط+٬l#f.I<=ёu, ŠU<ť,,Ce*S1Wo8ّ]vrLyX?>zd>Cᡝy2`{P{\I1Q"k)iS5׳ck4K$ 7B ~U`b&Lxx>OAWt::L\88HWW6B-ۆY0g>k(`n'֐}Ism,l4 -{99yx>;Sۢm˓@FY/Zy/ڧ.OYD,|„_6~'~[Os&~}[y_YL{„ /}>7fÀF{ hLEBյKյzR2$f<;v[ɪX ص0 }כ1Im9z(Kɬe@¬e5ӧ&:? w0]vc\ZȀL%KK^,g`V*uia( 3 F" +|v'Otvwnt|x]+Jyp |gO?g&? ļ'L{O9?vzrrڃRR$9s D?~$Zwn/uӲI4B`߹\ m[$ (}$7Zlx|V3pu^5tcpj=٬^8a}|X*qܱ1n)THC]X,uA̶Tz]>ٕ3~+ _7YM&M/~헾y=ļ'LEs96f3R.z[HiR;Y̿i`/GGwwdK8/!!4~w75Gh,yMIMa@nlMSZ002 BTW Cԗn9A}..܌rx齰PrZ*žm2j8j5ua`@F&L>8yN`{{=< "> )g$^`n)H_yva8q.V D:m=u<:,_g@vD/ 1{XK:l}Uu^Տ!K"?YIJD%K#N8r8=L &T~V;i"E&e= X1pLѤ%HbA±NO}sV~|{׹FDX wn=:Uu^{}k7?c#`@s{޽w;~-+\@./ ɔ8Il$D$Xegi„X77||q~0a—=l]gg2F4*s3Qc@|G}=鉅A*exjQ/"AU^s0]~@(? ?q=ʎp-x 9@R!ҋK@ q. @0[(G0'QHv(L۪f<)3ȫ<:e@`}9=39TT9c}}aCy|_ l#T!gkўRME/ef~bKQ)! ]@@N" }b΀DסD53$@}OO/9>gAgܻw|͖{*Ur)"n":cSHCۨBDJ%bUvo}ᏲL5:~{\o7_5_1_/N.΁}`b&L0ܿwidaW3EۗZ y::8f߬^K^{ ZxhY)//ᑺ9, 3A"6JZaWږ C%:ꄜ]^.s rfN8: ''6D Z&<+C@дb]mr IDATPNDmmna=)! *~ONrx n wbY^n0oAťum/Xjp6˵uAQ3^<,+ D`>S ֶ֗ B($IZf6z"\YhQaPH"ҸťBRI ꕂNp"3wݻ]-5 h8:1"\tAET$[S Jwݛ,V&/nBySa}`b&L#./m> tܾ}i#[-$wzBT}$7+y.HxtlN `Z9е#!B+4x f^.앏6/.<^7o!Pt7W\߭n b + ::Qн7QUG5V+nă5@r5£ 0C pRUJ9 P: ~VHKυcŦ$5[~|Ⱥ2gF Pr ydmi0.)j(Rt+2@ gnI\.u8]Sf+OA:*6;ykw=w'H퀯KJjZ =3_'B(*&F oX{-oy4 =!1{_iy3a„/;h|{޵0g>{߸ {E,W<ΥG#PmJGxIPHh+jXwʥӫҜGF#󎛖Ef5Hw]k TEHH5[ƍI"f 3f"Ww՜%lĜfts51o4u{hTb#-K0@*M9""LĀ#̵s'\_wF!fi-S=sF!Lsϵiڵ2Xm< x%b/JSJHvvn'|Ȍbm2P[G R^|"ȏK}+D^S>h'6Vs\͐R0]C00~„ _ η>繾Okv[žMUpGm׽Y4"DA4Qb ͊ZJW˱"MiµSd8V2ت]1`$[R&"'ym0C3aҀ42Vub^8UjIިB}oH)1%~pO6Ľ\Q܅Sn,͆1jb==vSU؟J1TX'S=ٚVQ8 F;k1$CD'Lļ'LjoxS6/9|DQDː_&S*䆕 iDPѿmcw!:aZ)73ȕp!s_9 \{v-9 (M1(ΘrF56Z+`v-H=%;ӖM~"S nŠ(V"Spq3;%~̌L͆Ң a\Ɍfjɕpֶ`>l Ǐy/1҄31g\\Rth4@(c.:xyAճ[t3mZui I~}:ۣ՝ sn1|ڡ 5o|77{f_p_ CL&Lx5 ox](\dB2R`dԐ3@pg-שDZ[mS^],ۉ)AI}/V8[vz~abF#T h>;/XX} pHh jPE FLc6sZFPS:f悜JHCUgQLZy 6kb0P՜#Q6RrU9lNGlrV1D^~N xUcZ% 4CNh995}6aEBu4J=E[?T-JPyD )6Z#hN) A 6ÇFJ?&BU/f[hs6{kcNO/b*W &X?A~@ED4g>3sq 7oapS Ͽhgsa( }mx+jY>6pgszihZŬB0_r!]']']+BR3 -m{e8>N''֗ئHzR(L1H4r!!}:( u8s~(CVacxG*ߊ*NNcs]9T8s4qGP/ٳ; "R`)*s;;5⢹#Zy)Duh[ %Plu:&@ֳIlܹs4rVĈ(VX-k%F QV+Y.q^X7}ӛebhuO‹_1}%߬;a„Wo{n‰]8u1pOidXi6g~ ˥#!YB'WZ^Q=Gkna&ֈZnD.6nAF=̆!r!$RƵ#eb_tlrT6c Poo0 8keBь)3 SbȉBuu%4ζ-gr|1 gqc;[UEĽA #(Bnv .Ω'f OlbV_x@{Yv|3rzFM`1ygO Ҷv`_% ^%(g{N\\-3-q1ҝnD /\ |%Bdt躮 +rR @6[6 ج*A̓ev:7\t48 ]+*nY{h@(z/˅!1{[1elLVfsZQ>=3jIUQsSNH z* wJj^h٘3,^gV#e 5LE{{H<9Wyqi­*{{zFqa(}l(Nn l n~]LjDf{;('ĬY+Be>|y;8KP/b][޾mĽwaL1CJэ5"JUï' ל }|۾/wOw_#*ļ'L*ۿ+?F #~lٴ7t4H)S ̇Mf7S!?mk4XDkD WdKD,ِ J3ba T@`a}a"͆CDμv^z߹U|Ur|j06oq~W Ю릌 _$ԟ|͛/__333g0u#=&L0UOs ??Ϻ^j=t|ih`ٙAsiQ'YUFb#! Q+yo@Q]lF%@4Դj'36[sSKdޞnF+ѱ{\%./R!%l7vtFhTU u>zSCƬirnHB4Xf89`/RUqrKQۚVXk *͛!6 ;Z@ X- ߎmV~7C* @J;kVx94RT\iPLP[ !-o^/jSP$^-Νf8>!M(zeC- Cm?/aޟ3/__ _8?/˿~_bW57_jyO0U_& ˅'+Sf2&cμ] c-6ޠ^R\ L^5<#fNRƱZPaT>ׁSRKY.d1\V+Ӄpx*>3Ю^{/u.gf Qc4P\1Ц\ #rwAŶRRDȐ'WM\$zg(tvT 4r.Av2 6_ȼ׾&6hLq|P1 Ÿ=4306Ak<9 LHS9-_uIED枖&jap7 ;N { [߸.cGp~sMqq!3wHD0$޺ƛyy*QiJ8 _V{?lݨ?_O˄I0a«|XOEwĭ'»}'DxL{-FJť۔tAʹmѶ殕JTJ=@?>~o[\Hos-Ω as\HmuPxX~  s-jOhuu;GY JN KǪH7C?`_pf^sLw$=-0J-فuȯ0˜8kE.U2RD~ଓe6d/ո?_o[$Ҁ ~]\ H!HfAv!3%"^z톇5~[DdDAiPrCр15c23ΦڞzmRޤgc~7|Ue|ߟ3/'L ¤yO0UE{~~׾ \!!e pJ %$gQ8N\s\\9#I1\h7ג&)UaxԤN\3;f:A!(pts0"S+ÅiH;-cmdzjv\K[j3/d>C5 B_NVxВxV V;%3m ׮Y1祯VFS-23=DYgda|H*QCrl$k[. 0,s/d>LɡGbcKM>&U^,՝QE̬Z{ +,3^\]^X3|6޽'B=jh̙c7&L𕃉yO0U[ 0>sfx{ C^e-QVoNhEA۠vM#>6]̤tFa:hU *!шD4x0UeP6{jü6Q=E2Q!"4F t\W{ *hZn;Y,$D=-SbrO74QrE%(vO{,+,viٗ$KGJȼvT* Zh!"N̈́9\fFl{RvŨ=l䬓2=Y1"(Vͦ{ vQ9RU$ȲmwIdI (dzm<9w>lH^!!C3I~nvl&LyO0Ʒ~;rbQ1 \3)] #s=_hȹq+n~߷K>)$%(6UvmFʼuKP,*ܡx UKPFP9>51[f2UÝty Ҁakʺ*nk*0jVuh@Y'yxݲrj)4QszVx\CsvS>GO RK{neH\n!Tq0쯴423f2b=3hsd1.560hPaVF YrXsrF%%ʝ sZ[NHf#l[->|ᄦEz'>?3^'w7W^HOo|ӟ?)ϸ|u~m'L%ļ'L72?џ7_w.TM٬[7s4F )J61UqRZmhtK·͒%ҵz7g@F,t0}rHo\ӱp'sJ@¤\pZro^ׯk "xFr#%{:IYOH)~ %o84(7olSGN쬑1=Vj[*q(A%%=Dj10c|S RVhgI}`MH )UIKҵezB9ߣe߽, MDxB9T`Gh*ә0+0a-NOMML˄٬Cl˜Qܸw@}HqIUZt/X Kș9 qfR4 % 7eoeOX-(E*k A(FP9|utYA4#o.'"8>okYACA](>V񻾺w~ZnZ|`X ;=Ss{we2 z"IM#(SK|{À!q=%9j IDATTvh:ǧ& B01 &۾o}??/D봛ڒ;%!23 ::>ɉ)yzjggl%D"#͆nL3:Zg8fjHuuX|&{+3ɩ%/̶[fzF%R:#Pس!PE?tkG{1{L=s*P J<GP!'ӓO7Ø R0 |.lZmi(1ʈ-*9x!iM1@q//{_G,.Ӱw?~㧱f50PXH"6J-_Zpd3ZM+]#3("[.ZJWP}-h|ܿwHDGj4d.::/k;a„ &=a„}-89G0`qߺ=~ڻf|Ť*Y̙)3g2kuZ4nz[ }O>bA %vZ"AtTkŐX_͖^|q3=<С/4:eE,"Derj/K4u!x%滌&hY%4&~ 0t'k d^B40ؐL!CB?p&{ 0VA89 mșGG*rgLL`bJ,|Vu7~*WNu[ޑMoQ&3C8٥ԐW܃Y1"FT&ZLh[tU$ew>{ msl Zz%'ggj4'&=a㎉yO0Gy@.t:s+ F"Bw`_ lh̞iKҙn1s̵zFPqT"T0$.NODX*EDXb17CN"* (^t)Fgׂux`_bO%Jb>)u2ݨdWsNً\L=]Cϔnab@PdË7844pB!r$;()oD mTA~0?y?gþL>:.Qj70awt„ x=<=jq\ܼe1 H>2¤?z\wpvf((@Ja-._#,~Z1^ɮR◸87PQWAAm]6btѪꇿŅE|+-lP_q0TĊa][:Y.sK/y~K 3׮_M7MɉA(*%ڰ̗%^4e0@h;4 ~o?t?F|1|㛿iX;.>a„  )#->OvYQ:V73-'r hf44[˸K Gifv(#9`HEϬ$]J7"MƠ)W.Y{:"g!]ySbz&Tf [Ґ39sfeEۢiih~_~xxR0 ( c i&cs2d5!5i oݓ&B]NN씷K2e=9= x G=^lADE/7'%I\*MK,6`*H)J3y׻Z[޾jucCƼra6N9'&<  |w>$fWz(oCm[)D 63s_x{x1ċU%(4PNpxvn#sUsrA<@5@0F)#( QպV/J(J *NN/3 sYm-WK˸ajIxT{ű[l!c*HhC3iC&(53-*$ A|,3 \ʋdBc,ÀaGOn{. K^\J8meL/ăNθ\Hס鵣Dm  n3nvhـ:Ja1JF ^)nMQ$-*!r)'w(?MINQJw_ L{„ \?3߻V*=ۮ)/<3R1(ExO *dY']W U${߮!џ)!qyi\Y+c?AXfmOQe+<3-c!┒Shn㚄`GֶȆ }̓Y~HȔ%oIPy*CO?*D T%4zZ跴I>WJ DnԽt-"<"u'!ڝ+Q0a㊉yO0qKyWݻGwe e03f`"H(M#m+]./?cffڵ޲ʴG?pZ4ZkjI]֑]d44HPD-9>AJ⇇BlnZ-BCpClPAߓJ(xBh0mxB#Ѐ=q?re Ŧ27Zi5|!z6Q'M ֗l2!`Xc-c/9\KˬRiz8L (k&|rxx{U)0__kWkKNS␘23ײE`4CP@<1%_7Tʉem-QX⃧{ʮw؏#,ﲈ&Lx\11 &<.o.srِyqn& sƋ/mقD:Oʓ‡A寮xPBRVE6^wJۢm1^=ŀa`5L JJTT"(C,;1`oOG([nZ߳024ZC,3%a']\"u@3c@@ eJ? -T%?gH_b.VsKvo6\C"Q)qFPw$\ơƶs~%54 TJe 4 D8?Kf10g^; * /jbN'ؐR|.2)yaNJU0c6LTRjffY6 Iᾖ^n1?~ׇhw!85TD&Lbb&Lx`? K f 9{\B- BZ%XBaaD+{wv7vyOϬzmydϨȢPU4*X.bg+&qt bۍop>W=}%.oRC?j:}϶3,V:.,ZjUX.jO+]y (pV+=9)}EB.ƗkUTĀ~k#R/42ʱd5-CD8: \QT|Ξ4 Di[uzxf3Mўܪ.EUTжRlD3[JŔ2$Rlץ˞ a瞻;T颣=9a„9&=a„O,cDJȉ9y^ȋv3oGO:>x|QԎrk |;H` kv) թLuĐx0a?Z訄54[*ӪuTJ\сG+7IGk 33(!%?O~'L%ļ'Lx7_7fsq|.)Ejuv' >ਕ ] k;s9dl^5w+r"g(CiW\η~~W~0z?&=acyO0C{3 [ ġ]'W3"ZItyB3z*'`)d$挜̜uEtog:tz5!y ."@7+Ռ$K62yyiNEAZ   K/OvҘ;hPQf&aLƜ3B/5\(ޏ ;)Z=UеBz 7X>X-91fmS/o_cPJVZr,P2eh̔1h ļp#Dw(e #yvnn̉K2_ZޫWfrzt/=}Qm/?g'Ć!ibAʈpm=fTςӯGXsKMS1G/tῌ9̙}iAqE*@Uܳޜ! Tz̼rH=Cf0$.zVyVߠG 9fdz5}~m ^DLy 01 &k}> V"(,e@/PhHXMeC?Ԣ|#J=*v$z"XHHrV`LuAZܰ|^a3_ ,ⲃ;Gn NByA%g$*\XPHI RHzI6,-I1 hȲK;v?67TGep~NX ϲK1 F-oiuzdf%znVj&ӧ瞓 EXM[q.zi#.)jff5nHAk,g 67g@дH^/P:Hn揶AoPGB*?5qv+3$%D DZtꋘ2 d XWhvh[je`G*0J*Iڲ9 )1Bz"2*FK8,LezpoNͽ]#SpA+śR>3Ѷh([3"Uf+4\#E4lmE|qݬ,E.0F27 @sR ]n7Ұ ֗ 2i?o;sN:IuF[{pی=TI3Gvw;!tbuqp!w皌?Ȏp&X{1Ge!rѮ@%Eq4qT[Z\⋈,)<@^B{=Ӣк !.f.^8K~ء_޿y/$vURXҒL)j$S*9iZcǰ{71 BuoI"%^&\rē7HfŰ9L% h@*fh!V}M@6 R_6h./wI#lnD&#(wYjQsv3#-c4+?z߿JDh.XޯT^(۽C,/wЈ{ v4@lnު }FJ2?G Akeb Jۢ(Jt"r8]M&BCJy$Ae>u"R IId \_fI?Ũ*b\GVDd.q cv!ی1jhEk9fP->Uz4Iܢ"^C ٔpn7l2I)Ԡ'O:xuOYMB }ޘ ?Q$:D'øe==ƌIk3T/riηlFM+9X9l-l㱨2PYkM)8p`ŏB\p@5eUUDp57 g O[v+ 8M i">яng?skLf$&sh&N֊(fH <ʔm .>:{nrKJw IGl;Y2i-27$enϞ#gH gf5v-zMumE1zT̞}*ht3lkFt_]HuqTu`I<HBC3B1ղX'&el 93,yWhۗ4 0ˆV'$Zf#51‚{aI !T-.&R842e}OKc RTʙ(Nq7ZYOuzw0/O;.!iwLIROpU7J}5Cv?tbs#dw9t@A+_=ܼnMiXT$)L |1CGI !gO+((lpm(2qa$^+Y? d+e s ^YÐ {A{w_D䱣lj/6tۮolM]O<+Hk)VGEm'أ%qv*g(,C܈^簶Qqscu%#m}]#\+Ѽ^06*RV6 @ۗTThPaWTqA/Wpq+++.}v]pXmq-fGD@6Ybˁ`kd ucݲs_2@߻MOͰwoݎj!aM&d6pц@|ϱ!:te/ÝjԜG?}کsW>+_cxs9p啡LctA~ ]k8OdmQ\&+`*;67 ƛ[snRQQ?A3>9*3-1kw9c<*6K`;rο3{s0-",uUV+(q]o2843g<뙩lv&ڇ|&'s_ZϐOŌٰlxsp篘I,^{v,J+-:jӋPA\y w}Λ~ ZLQr&Qp㫔TA2Z3=}FGkn^-SL[,=R79 3ɩC0t!PcIdc2T*ψ,w;gg^qA_$PK%ѣk>V;f3c;'|ANCm8<{wk]GlaAR&G4 6MRMU4@x$OC1vQ+Z5kbHC6|مuJjDtOTϬs氁Gsh$ ~1#si[k3 4"%F3Nܴ]i }")yU)Llmrߒn͸5=1Lgeڭ V\B`Y}Ͻ{YF1®c ]D(/صOd'gdAfl[$=iMT5J?>9ccӞK/}ݗ>T)Hs!fݨ?wp{WUWi;B̼ 8Ƚ{^O8EM*A.7q[6qouZBzҜ+\}裹RDϩMՋRa-d1zP߮s&M`K!DΦ`{hVԢǒW_<q;MȽKJ`<)@G7606 $sqҞT 3h[I |<됒zHFh{Sj&,,hH^ ^1J}R,\c+1ǾDdg| ~ÀZEyU7>&?;q! &fAfLiS/#9'#iioI$ȧG ^S~V(H븰 lR4Gd4}ݻwG2#Ygf;Y@63ܪu|3-5TlV_!(}yn3r˒ *T.%_Ń.27%A17dcR1J{=,3L*l(n%9r]Ot=L V 5=EF`{wa}#&^[Fea.͆}gj1QfJzKԠ1@9#)3$f9ͫem$ݳ'ym?B mdaL&'[ P d rV|vakz@n Qflz|ǯw;Hh@ x BWr_ ou9g';V]:̪2TGZ * KCJQu.س[3->E]Ӌ͊lO*Vo}66IJJ~>lG2quDu:f:v3̦θ5%n}g鳟Px`^:nlyh s8,vg)lm)5~SH&ٵ;--9ggs&27dn"`I+`53ses.BzJ}3rGo.\ / r~nBo̙}Ͼgfv!rDvp[M yμ~JjffSgA<ɐؿ95 ?咰w|mS%AS 1>}ɆFa7=5ss2gF-JN=oUviCxJOcIYOLȽ{ezd$ָRlukdz6LmzUpq; z$HbuO&(U4 |Q zGtjH}Jӫ5kй?x }{/#`*8pJTj~_~a"L喇NJgf(W' +cwLLiA~^u}[rӮ]0]B8vwExckʦg@DT 'caq>I?zЄ>c0mh)wMBMT-y))=x\#v?&&I"F6 m(7lmFq;G&[q2cmDY,"Uex˽J۠x, +3T?KI?$of㜌ܻ|`sXTme<a!(yH2dg״'CֳZ Ol;)y篔l8lTb?3ٟx`$Sw}Epۗ c'됂 x\v|吉ƿFC 2MDr1Cp5Mё:>++ZZ$.wZيR|K &b0b4V3 gx +Bu3}sح{ޥeTQIqƆ˸Q3z$4ŒJD |ti0>3&2s|X|H)iGQ3JPOn[}sy˳E+ǏŢCMC!XAzaq&~W I.C7,,ʿ|Vm~^Sܳi{xnm4po8t[5GZaa<\%#I20!eQDh۱}.Jta~7IB]:٣.f!asAF4Jbp|}ĴAۈo-|-0}-X+4Cf"‹.ZP6%C]IYouUz姿1r65ĉ$v?Ht_ye$Ay{Apq+~ˇĄuD(ChMnF$#i%%5wѣc Q*Q+ đie4!(A1e6-`@cm~ںDɆNtsvr :&X uQUYSSDmX9+s󒒘D$ִr@k^[mտLX@ 'L*urp۶cdrϤ Zt beݴxUN[W~w@Kct{eSK_qF3?_1{?\>5OÖknX, >42z֘47-89r^0#$tH!C߱X`<هl\Hș93h0ǁ+=}su sb/k^s?v?ǯS\y W^uÕa, }}97q&9 ta咃6W|aOd2*Z޵KG#i[ ' r_g>^%ȄxÎeАFe0np־Hl@GUv4㖤h,Uz1CIuOH]Ij=$Y =K `zEh%կRsI"s'^F_,(x7 r欣wadyb~2OwO.LG _c6_N <{m OA=Apy+ݕ[n^XM#!}\;#ksYi8{x-bU80tb; sS\\-v<Ի۞MҭM64 hسGE@j%@Bs-A$NGLm KKt7σO.;RAcG-%){pUq< z\eLaƮ @WcjRwCX>].dJ3IDATwOGexi̠LU͇"QMLveGvZ&{ ޏcT3z}V2`Ŭ(Z0`s}ϔͰC`ٶu1O|Z07'[3&u9TdkvKh@Vg#D< JuK{@CP?ҟbaDP% F$}G30ϭ92/|޲8w _l~L 5niv޾aDμڐA-Ge۔͗o@eƾ#M*~a^bLTESic\뜱95XXԶA@"%Q&tp@ɾ*ל }GU̺Ҡ>g{r8{vnU2˘&I >QVHA9 ǏٶrFCY*.pVd3u &6q `8xm( NbBSbƇ*%.)4I}gѣ& orM+g>UɅem$%OlMݮ fHRL;>j:׍ۄ7õ`}1m{ ,F&Gin5J+Z# n8bp{6OWDU!C`Z*atW^y>G؟|1Ϲ.tzx m8gA6 o;$ɧ?um}-o}#dged ׺H/(cGR5f*F#FiS $&Mim:"jehHW}]zdO.X_eӢi > f*՞*(`,u]M }fSj&0"m{M\v;:%ź3?Ǐ]eSsgZjouW }$VWO/?>Td;u*pƫ iC( /uy;on֏+Z'lV2Qޘ{L4#$hYŅ46Pb4ҤՃ-<poc-mM_[=Yʐy0R\X(pMQr,uˑc9ػ/-.zFfFe>o%MP囨Zݍ֍}>@ gJԐFl?MC׽w0p_!U]X6>wl{W\YAf+~CmȵŌJ*%CenGyaD5rK{4ShGbͦ-R*uARj}z)vJq'P"{ܢ-3$oaq?/rj)!}R؛]U<+ampp7*Y=w>wx/uH ^{x}ƕ G( 4^q|o|@lmX;uE0BGBB1R5.^g&d}D2#j_4% g4ت-I5ɤݿ3/4_`0o/mܜL*- )x{Kfgga:up]˸ZW߈?1KP\~E_!U -d+_ NWBy]A9ʹf4)co^ '}-DШ2V sEV؎ݎ8I5à @l7RSf(>=!ЏllPKxJg~OF#Vv IQM!PнnbnI>pu7⊃_+kW,'>FƾN*wr'?;Dpw 8*PApqwG47d"zMM-B0jEE~>v=+ 7O'l[e:,.bnN)g73c_e4At}DET\ՏhXOFXRo3gc6K^2d#>nqe3߽J`c/K)࡯߫p{4!}F3=3ΦܿkoóGׯ+ ӚyApRc/,t2fM.\ ۱'Yb.hamYؖsڞhBQp5 cuZ Fprv_pJqw7$4*2#P>|hm̀sA|ByApb5p~_[]t6xwHC(waRK0_ʨqU^F] SoX i_j)&&z& %x(uŐT7b6iF Q,a3z6]&[rw kki!  8qWQcwhsvy~09͘DfQ!55DHِnFulMi6H}Dz4k@Ze7*mзh 8-_vϠ$+v$/\O/`OFؚO3-+;`>3d9s4Rs.*9^b폔aRZ-vW 8=2i|[o5z gdci#$ss:7Wm ,M+1Tx{̾7t=}Ԡ(D,27Fr.5ِ }t=f=;39Ԣ2޶vI#i_׽ůї Y9OOӏhqmg,w}}mR#7FU  ݲa$$G%67ihhFvVR䨉K{.ZxócȅQFLW$`=\U /z%"P-(u/xI>48ýApIg8W׾vKqwL ݶ8;G#ihA~4@XfJڭDb2nf[e^x%,W|Tr&$LvOvO}=o2?o%QTh$j_=db0 ApJ3 8L}$5x翺p6!$%Ȇ3rZL3ZɽFqwvxp;llݻ5Rf?-.iQQ/>?= l%\1ݲk| SG̼ xZ@⢋VRl!2 Q@2!huݻf%$QLhF\x7xLuRi@-?぀~}&iyJ6¶*W]uII={TS 8{{U}̅$0wummogoH |M 7wMԊԾu9Ғ3ض2j]Q==s@ܓ`ܱX.sIߤ;:t$;W<>|!w׆z`lk ApSUu=O .16{et3%R4uA1`^J_87U)[&meaR{(Oh)1f%䤲U'\g|ˇn{@^qEh '>IGA|7\yA&vgo'|X^\,0{٣KKiUe"Yhc%ȐXbi,~Bx+%Hep᥯8xɥ3{}FףgZ;$fO|=8LBvllL 4;4Ғ3BQV`9ޠVfE: XUA鮩:tC궕W_SV$R//7GthA>D0}ݫnȻ9iGUhXDyo8I<н[Jenl؋A:X2NHV+o\*xit 8cSA}"g&Cb4#=`.R#n3i`GD<&fPz9E5wMA˖%pɢCmA{2'ws.=ӟ;X&Oi^9g6{6~J[Mv? Kz5x7_m}RvAp&I2);w|{OG_p-#a7qY /\k8 "g0l{8l憗࣏ڣoyw xn>)V5_/Irۛ! /XAݶKb0qs/}ʥ|W{ѼSghe= 8当Swػ/>K?tSsW[>$[?fU_ ,ˎaa4w6}ck ]*dwӈp[嵏]s{BssӾ-cJ0-K {_Ks+җ.I[`1 #oTC=)P|H*"k^\C3Qˆ1+_: {'/X0vtW8\(󳟹+K7x{N$Ay?)'n# 4=Ŗ. e͜y1 1{v<]!Ż )^/C {  3]rp睇ŰITA  "a!   Wb8  H('_ {AAH|"|?Kg?GAA.DF^RL|$  q;  N  SA(   8  SA(   8  SA(   8  SA(   8  SA(   8  SA(   8  SA(   8  SA(   8  SA(   8  SA(   8  SA(   8  SA(   8  SA(   8  SA(   8  SA(   ۭcAֳU y`p0o87 y`p0o87 y`p0o87 y`p0o87 y`p0o87 Q1\IENDB`trunk-2018.02b/examples/jointedCohesiveFrictionalPM/gravityLoading_velocityField_spheres.png000066400000000000000000004376651324306050200325270ustar00rootroot00000000000000PNG  IHDR IDATxymW]Yw{&! " Pv6O?PjڂmiZ6cY(hрd@0(f ͝ {ύmys=g7goV!B!ģLB!B|S B!bȼB!2o!B!&[!BI B!bȼB!2o!B!&[!BI B!bȼB!2o!B!&[!BI B!bȼB!2o!B!&[!BI B!bȼB!2o!B!&[!BI B!bȼB!2o!B!&[!BI B!bȼB!2o!B!&[!BI B!bȼB!2o!B!&[!BI B!bȼB!2o!B!&[!BI B!bȼB!2o!B!&[!BI B!bȼB!2o!B!&[!BI B!bȼB!2o!B!&[!BI B!bȼB!2o!B!&[!BI B!bȼB!2o!B!&[!BI B!bȼB!2o!B!&[!BI B!bȼB!2o!B!&[!BI B!bȼB!2o!B!&[!BI B!bȼB!2o!B!&[!BI B!bȼB!2o!B!&[!BI B!bȼB!2o!B!&[!BI B!bȼB!2o!B!&[!BI B!bȼB!2o!B!&[!BI B!bȼB!2o!B!&[!BI B!bȼB!2o!B!&[!BI B!b Blސ<u!?9R5!ޝ4;'$| !2o!{Rߢ|`5 BB}vOÔ7fYN3V!BM˟H${Կ[[/hwC\¥C,7 2o!,| Sߟw  [Ͱ0p!В!83pf =a]b"BysJYW,!{-m;Pq|cJ:Pvg38{NX 杷 ^Sٛ '[8*(bӁt .L-ᶔVIu ![pdfb ·:|VX x|' :PY$>NBZNGK G!OC(="ͻ&GFa I mN¡i3}~.^e`6[/? -Qǵ;w(s'kҲ6x[Jp?<Dֽ~ 7t.ץr 8W2ap΁ .aFcUV;(9| $$c盬ࠧP.$iva?} !'gp!hd?𹔾E-?dB!E^iLmO%|M~4o7hk#c P7΅Y R!9N #x2 +`4<ѵk̖0L+{ӱ=P~,yXQ,#FĒ̼GV0ɼ) ɔUP#B(23n ^4̛`Kv]0 N"p%,S!S;E$0$0hżs . s.)Kʳ2Oie1q[ p\j;lZ=xGJ!y 7z/8 [[%=Ї#v^¾=.\C:-ێهSpp& ovF3! L'`%}Bº"(/EGBGOi;l -޲:?sa ²itz|XCyUoJ{Vf{—0+0o%0},,_ZZHIg߰s1ήjZ̜Wa8U:lB<:(D!ģB&Cl﫰vdN81qh شȝAͧ:sav;Gy+=*" fy;n~ vZ9pY2}pIIs)Kw9{w,݇հi4ہ3S售 aYB"BO=ᆕ5f[ Bv ;qnSflœ2ʵo̖ N[7 Cb=c{]V۷)kԼ80. QA>6[&Y:T^8v8weMyRz[ dB!xkJ؛\1#-g.뺹'X^XgIpEd#)/,[2^Ui l2L"J٘-j  we;o&V3&|ſǭ,%>/4:~5ց M+o!#BoFN).<{) [)oC&)6F0Y;7k[sIfvZaPL/ 9, sEϖNVx~lNKj}eV9{a֞o&U]`  {lO!s\R>ҋB= K!])-1bьL߶A)!W!a> ` Pc܏Oڜ Uݡ@ðV{`;,[I|X]?*ol{~* )y%Ú8N UkMv,sҁyBu]Z!+a?,Zy:!F>5-|.$\y !cSJ&YP7+_ WB^n=wa>ҳ"7^i9݇)x|jLΦ˞=ohq8ey=|֜Pf܃ aW?7b⍩[av**E[R.po*-mkcѻprRa.YwLG`z8j/\7v ͌bB)ͅIqovJvLX a9298 [aB1yxZ֥Psc|A;MZ9Xk0YFm@}8V;?&pm%kal֒8Zi8ϔfPBo[;m\} X*?aoC#)#P[!rUJݖlXͻ}KP)9ydr>3m[z4n>ςd9eƒbWl>nv1c8DX ![SN15+ݲܼGx 춧qN͒\9!{j#ʋ[Y*2|`A46}2 |via2m_C=`㜹ȜqWEp!2y !?IiRM>bHqpdV[iez6c}̬nm ^z<;+mn 7ut8Ze\ [-]:3.ܞJ#&($>վ 7Ҽ2:Pt)ߵd  |#䚃IL Zo#vղaWOp{(!8.auB#;3]>%ybXτ3Zϟ],§Fy>)](B1o!$'`Nܣ[)a"g؛;ao?Oq'6pBuy,p?§` m;}p֢oĕs}p?a8l= =V b[{Xv_ w47%2o!v˸=gB U k7z$2 b&Yҧ$yq\aQlnݰy{CVOqqʄwAgI`_aن"1~.삣0gwĂ7.X1hiA;SИY̻chkazI?}/w7 Oܘ2\W4u l7+4K O,< ~IJ;Z7P߆DܜЮ-#B<>m ΁%K>a.BG|^ƃ٨ƒLq K(,][s30~K|mOF~xpk}@ tf{6Hp2Uϯ VVzIvW[Mp' FpCT8o]}QH 0{2畺I,Pr8awͻc#2 ;O`qv7I 韩zِx^)]$B<kZ9ӕ%Δȹ ^6.fjrOBv͙FV|#ZZ&UL$y#윐3ōvסNGaߊR0=^2TO?8< ˰߾4ѻ6x WV0x`vox%:t@.)cmuÿ0 Oki6˜0^no!m7ק ɷؤȼKPJ5~5JYxbwe6/YaaJ2}4k+3s'+1k-N v|kX+tΖ|F-mVm}az&L4u8 _aҸUÖ("]JB>CprR,صɑ9tcykKOϖ,s;[`ﳨd~ȍuQͳ:k`/ڝT {BN:q onwߢ׾̈́$oZ{jڽ(YZM˰GqN˼cx:X!0].絴1jq[TW:QnN"V1Kn{{6-o U_"66BkSz$ Z6l$3fcY<UPs: ^ְXsׇp\π`EWjj9-^ʍ9 wbC82G!ͺ¡MݰvI Kj48{@@a[S^;+G0~krk7‰Aʜ%_Mkmw2QJ?& BJJ,KUY)kG{k'0oy !y|J}̂jBT`tդ>\ , y=R s,vᘕa[/Gl铆$g=n”t R4΁E_uKD&vlw'Ldښjn&] >\2$ 06a_O4h8[<*#aσ[SNU8ZGc 72SFێƱ>M;Dl砟1=Kށ~xSJ?DȼD+%ϊoꔆ68 ]X5u5v#yeeqd`qܜ80 ˪Wp?k s;WJÜ{\`^o .βEh|*|>,"l*\kvw~J /?ѮY]TNuXL *HOFí\yɴ{h1 IDAT%8q|\O0Aݖshi,,ݦc͛3.{lػ {K*_|͂[\wv_y{\0`-Dg-t3v׿wr5,M|| [a'>*CWmn_Px_Zg• z6={IFe'? mtZ5{SV'rހӰZĻ'Sk'u&B-ش\NyWp5톓j0O͇EtkDUc?j; 9+;7B[ʧv`jxO'|7þp٨*VZ>cK.q\υ]V0Yhx_|}A+=?t[a.6锷A '_++x-!(oܡn \Sʛ >y'ð fܞ{ 9'g؂;#˼ow#UG'.cb2B'RܙV+oRR^JWŵ۩K`2f),o1+7<&ցΒ̓2k+~Z}HTدj#dotT V]\z dDK ]K+ } εxCINᦱ[n ߌJ{ ƧKОx3pny0w, twމ bs)daXtL}}<~Qܼym;[{pG8ӺœxL: >:\h'@WV;kwy7[R9yM[iXAa8exg/A>oByn Tp~Ivӏ#D1Ua :'RX*WYu\7_=õf̄pby>ueIK-x̛qڍ}}n7R_fx7`LB^hX/8e|Y{ l c*K_!/gFe;> hwQㆯz{`-W^:zTJʃ!z b#=x58CcȻm_.2O5L{6pc6{K1o7͓e=oF;kSGa#vr#t6r$>z Ge[a1{y0V}!gB913nw|FA8AB;> Dwl H~3l^?z oLjGvZRKQN^m+ Mۛ^)]ل F-T|JX6lEUj[^ڳ Ah̎6'.an rVvg |>fr\">J4vnnG`' qnb/QxkIb1f ȄW݇ȧ}kH b)c{pð(c}zGsw1Tr-NL`sZڭX la_Y {aОr2oۃWz盭_Y s%8w* Ļ][cTe{'mU wd ۴ rbnTR# z;kl;w*Dl:dBME+oTVc6g^sP[ٍQ͒}Mz'd &8l.cCUx&l ؒ3ځǙ31!ׁٟN[C)[1=O4JVs>iki4DvXז瘟 ^ +q;vIFkc$: "xp|r0(uYFv8V>c~M8*%D||M4 {vBn)y !6X70-.{*uM2}GqEۀSAy˥^l{̇}X0_wn5y0Gʔ &$i[=(ٳSp1Ù sgm`,8=>Ɩ=3b .Z&=.-99ÒǽL \Fa뭧qh6vNe<駼R5<nfG=p~DVIJ0qG?Y+'nIy !6wϋ]{qO^  cKp0ǚwtl9w<ĜI8WY}.l2Tuiߵ0Wad ,:'a?$8 XSa70ޫ\AGa}\*enL)/gZ{;o3&YOC[7~ O3ww?q%8P_%wðjڝFM\1 =9}霡l _i.SpSC sbfKp ߖ׭普]wGɞmnL\K|}Ka_w{o)Kס;6:ݰpFۿQ9ax۸?a nKi Rpy !6#[:YɠtٿgAq~+4,56[ЌŹ~.({*9[CێB喞Is ;aZ>C%qX6 . ˶wMr OTx+\l?]:n%Z D>X}y> m_oROTO&d W(wׅ Euʄhp^$WhV;3Na`޹gk)CeuM_ bkǃP(i^܃CP6.]kG;d{|xqxp<;p3,[|z*xX&HgG 27f,۵(w?t,k&S>2,!Ø̇0]Γ?E;}6]Q<~7r_pEl‹V)Xlüs,Ce#,BE_=פ o7F1Ҽvya?1ͻ J0pRpy)j1K>똶6@́[ '?&zd_eh'#,K)R2s< nx<ÝVMm0kqn_0 0 zvٺn v~[_ta27ó Yl>czΙpǡBey ~ 1 N|F'=`󘯀ݖyR-tڝ KK|W;p` 6Ft~F[}>+qݨ}TanQiUa ?1fL8[cލ<My~| Z O:=|CJ+-62o!&dZ=7lv GBj&7jP&:%;'  Y|&X =mYXpb)}fY 󭜖n%B5^NnFK]pNÓCoup鸀wC.?mpvRXdcɄGuسçg=)G F;ىGـge9x;> G<kv7os{rVzu˼7%MP1A?w+-ȼOa˼=ϻ1M η O=~=.S˵1I`HwsفBwv0yjxk (zvy9&Ar=-dwP[vV8mVjkrCs3 4,*̫ ]}Ȯf;QΦ>~BYy®'ZjǛ'b`d -3ţyg Ѽo}AQ,=jA;-So?ⱈ[9fPfyp)w{-H Mκ^jx3<>͛ I1j{=i pQYQ4n QYF}V@0 RNpFyWl=.q @yC.Mssw_p ^_*0g xG]w>6*m86؇7Ѽ ֽ>5gGY7gԡ+ZU.6G-<w} {`}f}Xy1 ܠ݃[Bk>0lS%c,pa߅& ^Dbq]pmQ#χ- ؙ=m]N$(ڔV_(*7NL $֭ޫnovino]7[g,%L0pHwq x׭j> :tS>:B<֑y !6-1fWw %\ڦ-W)[=![zO?O0l 9 EyX3p쵽e2tQAbRPQqRPzmg:ؘgg}b-ǘlR%O8hC7-;w[G6t ۆgh-`h'ziw7mB}*l y&ty tjG}8qⱂ[9v|飾Ngyg8Ӱ0o^L'+W㍰L5(nx pv-ڑ,L>,6̯*OvìЁ2E0Se`)L-Sp/<*x'7[={`gonoU-+`Lp%:3Qhj#dN˹ݼp&6bs/wYANȱж%J!tyMcNކhޞ|l;>6afXfʮKl_pz\Sxl!BlB~-r\ Wkw:ރACQ5}Ϟn2% YC)=JdumNGe|.xc //>4 DLfO“`6&` IDATDp]JYZ'Xl*/Mü-7j([̗d.acz̻z%>BǙw[ߨtSÒ^xo SHQ1a=ލW >ӻټ;ݡݼPy{oWA$bs@Z~^W+V?wic=? 807O` 3}Y#܎|Oup=k%,\F'D.ZaZb%̫b`WmJ5LI{yviߺ~,bޛ`xcgqP`ɾY0d|?oFOϕH"ݖQ-o$yGvXՆ]h(Ѽ+xRMĦ@-؄ᵲ]0 & *(B|̔÷ɴ`F0W6&j#87An2)雸,@ G,d&DmGY->Lp/V¬ny7bUtdC܌8k CB #?oǀw `ދ׮ "~ҵ| {Գg;J*?8pjklmұ,_.ݼiiw.g ^5t0pjQdBM0vOKnƅFk+i VчҰ !nw:xiӜ;VKk،vWY:R7mdkvC͊F>ƙІ![~&ż+hC XNYx3*8w ? 0_u˰w ]7o7gl_nhlh4Eox|>2xu=>ڝ5Y!y)+vrCny !6'ѹcϷB:!);$[@S1!FAL0a+u!%Wbs ǍoB.o_KseN=Ą_7"`c~܍GCfI7e޹c'GKGk==yp5G0n,q6(\ܱA4[ƨ FqﹲG-W6< z`u*Cr'_. b/cr1;{`Kݵ̄ڳrW#Kh#gĬ;aZ-c=xǘw* HoX C-]z'Z`5;Ɛy]~@CWGh]p%,RS Bْ_Rq҃_mۑEǚVa 'Z2z7.;r4b6ץ1;v*w?w<]y/_UcS-6#2o!&RGɯa M넂!)sV{ CЅafO.z=7GKkuVZiw/6fKnhw;.7tޑW7'ǗN-O7t[lFdBMXrfn[YoB\7sof]Kwt$zƦK~vsBw' xGnFoτ@O u5p: roϬ%lbދ,= _?1 X~]iGwE[/bm}ͻL`bn٫SÍσ#p˿Byk)XW= OZ]oS֟>/Rz[l:dBMȰ݄S| ̼]bJvf`Mo #[Umw=kij+ߴԖ|:2Q'jw'h7pVav˾_#_nՕp&@9qhx(Mn};|/BJtV߇7We㮗 ,~jNݍl:DpS[`vvC@4,C?g>d;|!? 5;f@uQ`t=3THp#֍CXwPj$gkWoFoH?!+eK8YW&]Jq) <ױ杽6ƞG@ޖJns"ˬm>ѼQ)kDQc FؿfKiw v:vu-Vq Kk!1[O!}~G)NĦ@-؄_RE~ :p2gЁ+ ;@tLNw`‡^cVo=e4tF 'mCiop5kwLaj')]WC{vXVig`+?,zJ۹-J:[7;u.;pKjz&0 EE""*ᚮ$s Y$ " JR 0I;tG}k}oueB٧̯~kUFO\I&:/|vW:DŒZ|ey'60}[%ZV+vi̐k%2Q qux*X"*bՌsmIREpasx 1,.ka?[bRtǸϖl R7L΄?%;_8|VWXToutD}$>>S晆k3XD+G$!ܔr}T3YPX.cwfE3XH]Z VPLNuIԊ[dbڟh;W[ cʀ}2Lysjm¨/u\yh]NXe"*b%0Ko v?|$e#0#5u~#XTU yÜRzW vl^_6 v#TږymRzA4Bp("DŽ8В XO2,d"W%qp_ RT )&o1tvՖtgܔ.jk"W;%6e)WD^ńA2(әnaPh&V̇֒}c䝖;u'h[p&Iᤊ?*򮢊*Ve$A+M4O/}FoAS'b ZOR~<·9n p s,YX}$E䝚݉ɸaAJ;X4*Q(K'!3\kaZv@=fa*aetb4{N2i3kIj{H%s whKV2;fJ$~pIKI&Ϯ(<&6Gs:LrU.J_4oڂ^=^E+]T]EU<C 읋fnLx>\h3nrZA:<ր(%7K X K94E;gw*, 5\%Ða_/uu[V+P 1ɕ0n<Ƚnѓ;ycW'FaɵJxdQxx52-\i-mݞ U}<kS@ʛm=jof4GZ8_M׶<VԆջU&*򮢊*Vq$}^V1R /9rk 5ﺱ] ?*618} Zyk[M䮤 aabz My,դ5菉 mE7TXMi ^n˱oGF^k̞Ȼ*Xc$Y A-v#W573[:4v0T)%tbTfXf[&%6?tK(lh@$]Tl9*c,5c;I*&& !rw]BZdѨx׈U§ غܞ6PΨ,fn%>^5%;d{!E|qX KazbF+o*x1v_K,޻O yW3^-yxx䧿 !>-WyWQEx2ߑ$4osF m7mu{vgw`)-;̶MQc8 xd.@봿MfX;tGݒ!oQe1+S%fՊ;|CM5tatw iw NQNL 3HܻoGIrS> nˠsd@2BHKmT]{P'M~:.Ƞ6<_WVQ+/izo>tu7p9֗˥؊U?Br~x`5`j/d;΂>;$D1= րP^BL;iRw5tcX3"poed𝋎ކ xd6"9\3Vf1v+54Ԗ&ouJd=zRy &*l%P(RțXN] Ars!^HdhQj1v0| jY#̜<\o=JwKw-n3W:%_ڹ߳nd?wgss|Y3|yWQE~L1mKރ;y7 [Bi>vz` p SĜkS]m ⫦* j:Ȼ'p;0"Rtތ9Fogbuy̤YBz xgvEJSw~֋S;6{<gY5jRpv58ptw]O  Ǘ[YY\k` k%ɓܵ]􏻊7~sUv%?n}^-q%W{>+򮢊*VY3Iڂwe 7O%it:U&2]չjBĤ<."w鏙!Z&r/]wt3̄N%sSUNmI۴qaG%o/n97(5斴g$F,x6Cgq#14y| AtoNC& /unb9±#WKᤒ>nOo^PӒ]{d_FB>[d}ǿw|d ߼n]wv2eHˮݕ=V3gάm_s>Ip?nyWQEU<ޘ%8l1mSn*Gb65@ `:546`9Z#M/ ;B 7$5d{~mP~7X?U__$)E\$_.Z疿~5sw}ǝwwo>zz1?E?<`ʔACkc>y@q'~ 5xx[m^(.]քGYQ xt5GqU] Tٛ;v[=gKs,ZO%7os뭶EK/_]p׿u }@}FGǁӧϻ`-7{s{}oӾ5um{m=6pZ6ON?zd9j}HUTQ?s_ݍ)mmc+|:/[>!z0^قۚn\Dq;Gmi^=OK[WJ/B Zif? 8r-eØ0&3"{907/|g.:>Ɏov{8XKam x&k mVp ۟/`Oض*>샋z < >ieᣂe; 4u˞kOLw$=jo. x/ ?yӿ%/9_`?$ɚk|<çM w?*5͞oẟ:Ygn$Il:bn7k IDAT-lPד$Y{ޜ#>m~DPYQiUTQwXs4>fٱ$ .PT I6YMZ_M,U\5Lغ1]9(?0qV|GeσߩkX$TLw]3u'wkR>Љ]Lv6n+6W?HODłwM3R,bɪkk2kNrz}+n;`JÃ<$9|r8`n C>8J;!x^+Rϝ9sݼh҂%{?9wl_o9oޚ}zwuyg%>1d߾բ׿nc?w] UJ{xÍ-wZȻ*X]cj+4g1s4D όBBwwh¼\2&oS*i0 Xn >6lٜ>K%Es[{D-~Uˮy/\_Μ1=L6Y3K /j"*b5I=4lC. os3gOaӦ5m@'χ|y`z4[%q^/JCӲԮûa-gi* XNM5ź.\[Vg3 < jHZ0bm4NtRtIW.+S!g#}eHe܏B~OUxm+̯ØTZ$'^6-6S4~+gWQM_k{b5f|su7_wۿpW^jw| tnD X$K3Kby̦[Wc]{]ru <sU62Ĭo13GuA&΀]dd@LUuٚJk D&f:bJ 9Yaj|z]R9Isq6>3θ6{mx(wTvzv1[L;v7&۰utؼ&ۃߨ[ V|q`6(ź޷>/}<>i>g͹xu֞{ͷ=8M7ö|oƛn{*򮢊*V9M"383˅G1Li0UB2_+y]{aX$#yO,wDW '-m.]3**0en+ťٙa@$0ZiD&'s1vy`ݹP6xBXƕ:g ^|ns ^syp˾AOtߵ`٩ XVrl]aY|Rw1;8< ]& v_ IG+R<0vyL__:k^y՟>\`)iߛvyfn4?oF`͗U*򮢊*V0g^VsdYP 1MjвEs3`1l.Zp`B&{{9nDBj5ck zͥ!EV:eܦKtFۏâDlyۼ|:cW27d{7N]3OO2-SAj{v\ڹ?ϰVJVsK=ש2Fh&%wY.<2vr0kFU֫YJ$ɾ|0M6xq[mٺ5a} 3M{&Lb0Ӗ %æ00g"NE âv‬~R\%!1y0#iKUt6In[2ɨ)%2&r+Cc-f>I~i<`J>/C=\@I[8R{{ffpq9Tp8f(+w>X [Sc92K4i(l?es2X]G~WY3ong/eށrg};x>RfyWQEE4`% |R !I C9 B(`013.[x.Xӭ{Ѕ tF䝛ѹð>ڎb3W^8L >LuS`:LzgYN S(}_̥𗘼sӕOo(ey/>Qϐq;;a~.wV/X g`+/C]fJ` wO?l姷#YMDޚ%1 ?vpہgÐmp#FF&. Y!N5Y!D+H1ͮFWs^,;sUsU혁x#S{]bawҘ qZ{lqZUr`u(x?ݞ *ygoq+})x . HqQb.Cl7܄s-U"MŋX1{f__OҼ%&`Ƞm̴gJsj5d Ftj̫ˠ W"z8C 6) gW1vȻa瀩ivQx.2axo5x%˂wRhR0ލ劳K 2XNQ2}ZF>MLRUH&YxPI.> +7gx:iYvͻDJ؛iHh{nhߧii:*򮢊*Vx־ \R \3:Ό%BR{!3av;d񀭡{lh5;FL-ɑp:􉝷n8ё FPs7Ulj5ѝ2yw ;q"7,]IKQppB01v{,[_ ? ]tT >ܭ0%j.;G;&E8)vA >e콩Ԍɻ&CVyWGEUTQj{`N1.dR uCYy'r Saj0} ǣc pSΝB>ﲋ6F@6oyp ʎ|f^@@LkPz/VI]L ;]{J hV;* UĦҊ3ڝ7Sn:\z> ̏/ȣO6~3Y~F|F8:rωbU`UTrGEUTQLg}&u]cM"q P5LӰuY[sc ؄SfxÂFR(eE&J%5% &Am#7ߎw& xt'Dn?m!z Yy[{R(|bNޡ;X~tD­_5ͩX&WLzKP8ȵ ɯa")/V^#`% KFCs+K+*򮢊*VhưR\n'gOv/Lc5ma=I18E2\|"7>Hټx+%ǀAsMjPI%o'9gߩ݅B!2n\0Sb'B*5]E(_xyυ M-H}6 d>vS ;[9(iw=GY3Rމk>igN5]#kAY b,*_E+WT]EU.5iZx$oJV[ޜ=Ž+1 /:ftJmz^4ZUJ?e\v͂4Um?BNҷ_&{PU%*򮢊*V&  qX|MtGeNoEۗ4aCG&ktaMAB=%V'vXC1|LZ]M9GmS+m>% VCy/kxo"4x Z]^^WP[vasw\f-uQ$jHX/|3{N8Oe :?*ށ+{9a;qaMo?20 ]7jFQ5GUTD} N- }ޮyz;950lԋk|^J߄Z pN\p7VOv| 5**|a:pp|f5IJW)wBLFi_R4;M+MzSO򮕖!,[kv g0 DH2]WV|!|YO )dvɸ zfSI ٌk*P۳7Z[#i2-\.EmRU,y}=65he?:w`0~&.O*^1>>K&;կ:tt1fΘjGEUTQے+ PR3m ]$d/Hzsꔼ140 d*] ׂܕ:7 k1BBamO"{դ߸Vj2Kc'&Yh>`p=N9 Y%7^VֶSںZ8R aڱ\N}P[t/UO܅1@r n_RPar,ˁ͞dlagxiS^`5g}MNLCᏸ2,f-px@BG%obMżnmug@V ZirSM/z06NW0cST+V>5q=Zsc[7?Yx^njoCk_ӁoyU(~$sCbs;\I\؜ډ!M/:|ߤJMLR¯‘efro1uԥ43h e?Ӡ G֔Ba.ًg'᧶gaNPh5ir}4-5MabfqsuQa~^q% 6p=6H΅0M6Qf#eĢ;9_d+{9EGOvBɢNLs笱>=-?]{%Wo>˩BT]EU^)Ib3.s\^HQ @.3B(b¼O!&9y!^[n"r8[eۀj޹5QaZ6X.D?IƿA) \%bpyMK֞JɉTb|=tV'Ћ5HW):xZgm'6vַ9*K}>ZȸQn` Lk%|` ;~Վza֬GC?_v_TͿȻ*Xy$ɠf6/ ʸ^!l _;"PL臓6k a@cXV-3&]5]WLުkYYّ¦v/ɕZNws 5Úb8iZ1y|dѾFj[<)6l3N-sםk^N:t±L̳pq=-5\"*b5$0`S(y!!ԛBϥp$}U ߨYQnL ^:)mbrM*ٌewf=GGXZMSKX 0ח=85lYᡝ/K#xRZjHջ'y;>|,K0N_;޽?g%ҋç攆==w%CQvЮImׂ?2|~{HZ\m^X l<_y|Hs^{N_M>e/+E."*buj5p!jtn`,U3otې% &xٺuCގ\!N$N#ȒГ p^hk!^d?_=,s|ܮyTtf̦a8,~Ux(XnR`QyRΨ SXWSP8tk ت[x;m{,keӎRE2Q_V_5oClaRxWo*WdY6Y-Kl-4M,Y6sfy*򮢊*Vh>݆V"Ţy׶rVx7|f݉&N5;kyR >VuNvlp:atkT˻UurAS*pRI"<Ň7o&&㶐77r JmѸH ̒ {-wzzwfZL>Ni5&Ъ`gҀ=eǪ?K`Fa+pgIk%{? ;K̚51ãagph`r.^t`9kLv̖4UbI) ٖ̅ɻc0 JN4EUTPǰ !n̔ s5``~J]/iJ<04p)xOFrݠ70ւhL\7DDޙjRϬ&L/͋B^"oWú3$v;ly[/aw-Xe&Bs"OIlf&8y{iMHwo>;Vpsݙ 5{#qxCygr[`#\-Dchp I`xUW_lzˣ֫HT]EO< W}$l}1B./mMN\Ngr6 D۲r6/ C0 [wdz܅Spt$]WщewɩX`8[ ^y%>jڲ!;E~:1v#m,/~kvSX!8pﲷKZ½(|NN\N[Sq'~Qh , .\L&>ӴwW꼓ߝ^ݱb|h?8׏wWys?xs<5M3~Sv++_T]+Q]D fw YIspv ϒև|Y0 +erMTI`$ SO%0W[Se8&TԊÕF\ϲxx#ֶyg%5vF`v'w&w׍ՖSPǬ|~nO:VeK\Xʱ?=URcPO&{K/Â~po=w ;>[7|xxd# 7rŗ^sͷ+_,]v`v^IaT]EOUTeT5$,&09HbEa"hZ s^ؚ€/L HJsC3î@0p##jG`EīA\=}v#zbw`c{>k6r-Fawth/a{]T. BxO)v{oNxQ>G>S|;yq'|pI}x|SN~9+{|{/ǚQw+AG%?1۽ VM!EǼʾ ΄ 6Z9[=fMd1vg6Th‰a'uNz1 5 5'n+T5:q&&`׭Lޙw0'BG1[L2H w&1v ^LlY$Q+W?}/tR$~q܅wAIAv q$lq!7w&ݵ;`Dv.p 2xR1uu%«?MZ6cƴ^ߺ}W{}yo\wݵʛX)#+T+|Q'$4[ evx 5Pa:PkYBAց!{` ktM8Hk?$]01DL&X-6_&!oćXp/3GJ{ZNזYu~藬"oY VU<2vdrbo*WFq[2;5Rn.5odulv$n%ܣtWsw= f{SZuot\@.  S^|K^xL,wm؂QXGwb݁0d]w8ü>U#NOITn*XE-;qg0q__i0 ل݌?s008Y߰炃G5́@>V ~YDCD8܅g0lXtL3Yy.H.0aV9QtC-jE҉l8p-ו= sL['&Zn\;_73y\Ȼ60dVI]LAW!enI@' Sc.? ֨4*VXnj ėW/IfdPSU1 `BL1hhprO3_xn6 {̤n׼%VL.KǕ&ùv[ oR`{;-LT4 ZMJ9tM6[jsAH!`lp~Gn"̍;.MnUT2E/eyńUr&Uh8̈E"~//O5- P/i\>L/?K|K;]. 1q#b/ *;O[Tr_h^⑃j@6|RtlmrJ>#6 ѭ|wFꨙ4MĚ qC(|,Y aū3vcmcDN*(}(e,7if9eK>SGf֪JXOR! uxz.R0$ux()eFZ5?lTFUTDQ R>RKݔkeV pг߶`H+\}^ַr:C' Ba. klysڔ8}C^Oy?%xCi-k7s}eq9vý%g0ݑN X*P`uWfq98N֐)*%AQj,Rt&U%{]!Sz5{^ҫLnGfENTV'=Q5+hTʗ6cA~XGl+<>itÃmL,wO{`2oĜбFB,Ր/Ό EmoM&1$uFe%1ygz^˅`'a >:{]E;d{A {O&:[M\FN{]xtQnJqQ"ī4aNt"_1 aԉ }nφc+bebՏJ//$i(k}1v.0i$Q\H&b6 ,ӈsXX _0X}n(pX] uk{.,zZ26[e;6ݝ{6mIK̗~y;LuAQw&ĝEL\:Iyl~^ar ֨ VFe8ߌՐLg S,_X92`<|2 Nf샳5a& p,TI,K`cYd1-JAqjYDrX;_ c$}&~FΪ q퀶Ur%$r,;au7uZ0F*Mj6s,>C4RYPSL6uK88,Kz\W*-1H`Qm8G }~$jhVcQS7C+_P5>HdޞWӀL >SswI]x3ޝ`duB޵Zj͒hp jЕߓݜ23jT'%Z@ҩ|cy IDAT28|ΖU] %Y#B[KcC&"ٱqE.q\H21[fFaD^oҦ Wtl@;.R75J;ꖞ\jF Ig9n ]벺l?/K>M*^^c?́OIeV4*VUߖ$aA#u,[8y04~/Z0i½a5c`:0tŪBMLD 8]xx݆HYJ XP(1vqY[` qkv7sY:Ýqd-ɩ8 J7"k8˪km5 ;uY0Z( .3[Ԯ5W$ğe=nޡW)RO%\a~؛)vb1)XKl4~J"*VUߐ$mmB]cf=]U*p*0M!A }Ia`cX n7v2v_0ȻoZ)y`!k"fJs2 K:K=&|L1:bJƆQ5Ԑl:c8tG˸ Z|lȻ#*^Ab&TC;ژ%< šuB?\ s`zX&̃f2y'B)[Ѫ&Ygw0*y<[bQ) Əb8ު!=`2)#pk/ssipd2KnX odͰ6L;* ?/xqU =$y%RxI Ǹc{.޵` #oP/Xm'lK*ty2GUDEU4Q $3ևy0%3Sd1ldDvNmd 9aS\ϳ~{.\l[AZЄ6ӁR;pb<s&]3\)?s#zj:ӳ*@ `VDnX&ߡ?bIA`6DaN";._gXքsL nKlRw_&qĆDeZv{ ǂo694a[{듓wק&uOVvYjw{^T˓?>COIT3,Xi"^+H32'I.w5iVi#lX]7DžS&Kh1Ƀ(0Ut\޺is*M`Z695K2a-a*ab#?-X*&bMOÉStkȻeæ28GT#Fpb1EyXA Rٝ*^Tzˆla$` Č鶕 z} +m"UJn~>(եM["Qy8]3.%&KjR б2{1bjkwG1v jʫ__uxcvez>6҄ w.d*;ٚt$ 7i@h; 0Q+wgYO&tf0o4$Aa.R*Me0*ۧQUeT} wB {@R&s@k,* >Xen #v·̵RW3+H'c0h & 5P-ryY'CQ LX3X^o$p ywa1\_2 UԉrNfqsvcʚSuݞ?RJŅM b%tkĶytfW*R5"eJcsf| яcvl0qϒ_LhƦ4/_>;n*$Z[6bwB}UWFE>e \zYH>4ԅ]l~H$/@[Ɛ׍M [DmPva fMS⯰9p- vxABkw&c;-;R^fqܜݹL3w%½ X?G9j5c5 q%rvڜߟ·?= %Գބgÿݑ.MR³ռ@lp_LȻҼد"*{? Rw4 aҦsiOkϛݖyd0l\hk$/c wЄ\]t"T1yX2w&]X Ku-yU|m݅E0coR+ ݖ;̴|?C >}ϼ]lrx:),ӸkrV[SiKwФ4JP\vz)6jbR86<ڥO42x㵪]+py6Q>ؿ1hBxni+|?h&׼]t8,5c֜X,_* zͼXnGTmU~ \: ?胉wV:}s%;O>g0ylT^/5S{!^÷b(`w.C?6x'dYR_j:tj ̺kaUNٿY=Bٻ]/5Ш-ʢGF)ic.P&۫A~{)*b ߸ xOM`\|7ÜMكV Nu C >#5FoNm= pEkǒ5 s `7ėyAU%o=gU6ϕӬ'y{1rAձĹC8{$RiRUv-n_t{$#~mHt"&OSR{¼ZVN?3i[?5P{/EEUQ֋pdm6>ohˣ c^iJA PO30胫`R6|$Keo]a_3!o7骅S>BϒhT^SŁ$Xܞ^dDtjaC fP`Ad'тrl}eqzIy~J02*d2KZubBoa)sz^j6I%ۉ 2⫟C\NfHasc<9 Dn\Y,~SN7kW؆^:M"ڱJ0ۯW(v;<gT\z0YÁ MVQ_[I-_}Af_g?ڟ\ 0*ۧQUZTΓ/nY99Y9~|ÐH%j$Kc<(T[& k) ɱ3sʆP"D5I niI n ~^کK Bޞ%5S=6 su nNw ,!.P].q]KU4-uʦտ 5" vVR[iW ]~y{ωm!΀ "|U6Ç*èoF6 $YVð> +aP3^+`%i/!{/$/~0`'' ߃UVnMg@`t8-ƝpBngyr;>Sy2lBNoqexm,;.p \0$S/2`8ѳ=gg4GO]+^ W$5V'jx,67Snv&Z^Q\ (uzk2ۦ{KP.kYe6yoU{%OfT{C)o*3c(ֱ p?Oeb&3uω3=rQ!>*6EMeMk:JZ UB.7=R¦;g#T K/UkoO'r EX=,${v{ vaӦ§|niV'@RtlUꪕn6 9/x43b*\b@CUp ooFUUQ 7+6voy}!@ށ=xe5< orCĶմ q9<(9/3"o'fϰѱ]=W[-vXO"L*mXϮaA>#܌:;0,, YrVwôo)c[obʡ֗h[4L ~{hP[6\붸e*3!-@-i6Rx|8sf+쾭B}ۤ<*ޕXC{f6meu$6K`,go%y! XayW.AQOCJeVJni};p_pHJ#k䝛ىm3U \ ݱe2Ty׍:;<`H8E$I\-s{6ѮW!ab!^Zo3+~!N {ڷ6,EԴ㆓ySnu$nZ8Ɋp86u7$>9 o 6 Ne6ysU{%* w7Z":ӁO6m毸$Y M8aFX$d}1q&!K8 m &X C1osL  w5 a`ܞ 9-!^H]ҥa|}IpU^M/]~`fadn6H(|o݁0Xñ^%Yp #8@@|9aAi}֧R۷R3UJuXacl4†3Β)\/vIk}nU{%*N|c|g5N>v$}{% x<Hy;coaEI.܃݇ XeAK݈zv_"v|y9OI:K4FiDV<0%n56f=K0g]`z6!ewqX` lV?bs^𲙡fXG6o\ȗ>7J&YMB9(,7bV~s _lumWLoR }E߻]Pm=f, iIxClʥ&/RXKEnZXjzܮ"nYXl7KUҋxk܋,b!ezulZoY ݐ4Lx~H^4݉}rU{%*weqߠ-Ocǚlܴqp"{5ox]`MP:8 VF߃ᗆŋ`~)k6k,R#!OVP-p$Cg=YԈ1+7$ޡ;]=/ފ0;EI/DɎ!~-s+kRwWd IDATŅL[c){%f@+s/ anGIp%YWho:}2jk/,᧖`_s/hof|] 2)Ga ?DȻk)Y1n;v_R^Cw޸Nn|&`w͒gȷ_ZAǷuknm#(pcti#aZ< vQ;J{>|I-sn&$fXMr^7ujabo xՉBN~α N¯F8K)eoV=xú7][JAt{1wX^gG#Wd5?El!~6Y XNa5 Kju L/kχ+Rz\veufBFat/GF:O-LsӨ;u9pVF[;lF7cEaps\n^= 96moxQsdE?_Fގk0>GNy\}\Rs~Xf&¿\&[Iwٻ_cJ8fն.x;d8v@M撄&6ԮȜ90.vܸ^YouIhYԼ ONbV{tFZM3+kҴ zß#,"riCXduBmgƦ&w&׬$N A-17X"MV iT_ŝ=lF랅pƘfai/H`+Tv~C?,6v6\n{M=䦧hR`27 pq 9 C 7pj;1X=,Sw^mo sFTj^%Na-c'ð";x>56-˷ݘ6¯'v{!]^nn\W`r&U{$I0k]yٱ&pJI:Hگ+-3?aaI1}Tq+\}K v! *v'*ۧQUUܜs;̠?=F逗Ouw^`so8M>^11ς ZwM/﹓Trt훓p3g1 {,֮kRxFpə=M pO  ΁x-Df]NiFm+`{ Ft-ܟB 'AvQ1|7DcewA^7YsbHeZ$vÄ1'}Z 2DM^RD_59u) ȉK~_]WP4 Uw'*ۧQUUѐTŦ.1r]oYnB\ IДG7<nشҶYS(<{VoxSϓ9릡ۿ+nmsOI"C~kw)k1q{%4! EH5ܙ07zS+y-n4F0|\ 7 ք,낪F#)휼$FWӞn}  L|;=Iz3\il%aNŒ=0"3{`d9`{0`B^ Xb_^deJYkaӔaLopw,CdGZѳ uS+-X,93R"~IӐ'4}B >1#N~4LVdbuRgռ1^3Z1maHcVx nk^'$A3S'Mk7L=l^$x;yݴ1 /iZZ~g=[j>vQp|VX°eW$wHXa/3? cy9d33ZCt2W<_bVɅjN`p݉Q뵟ny XB[ׂ2 X!@a.|> +}Ld]{i'\ivR 'P;FJ#>~Yod+L@ MDʴj` {KڡҖ{jadpa05U4*YwnM,%=/2/mtí` Pei.]a TlHj@̈́s2X).x~?ThkH& s;>Uph,ƅIkbS9$ƚ̮{dpgiɩWEAN};lµX_jvޘҘfl`vf+!sRx%䗠Uv"yR;=Im);VBᕩu䜿$ZKuϬA {[iǮ dsSv?lGpD'aV_g()>ZKrW"Gg#Jg|f.ȻDGfF\ il%r,L4+gn>8Xil΃Ӥi,#ό kĭVډw[ K祦oX~Xҏx_{zaw&~zׅ%}j%,°LE&݁W;*Xy+T]W5(Wؽ'711S۶mBvӫ~c7|+|q[@bWco}B^xF=opaNg 0`1 LnI,Vyfvp첫wȻ*nu_uw"VBG)=>ХP0 XHvE90M% /0al ܴq7RX! |GGz s6 :"wm~Ӊh\Wj0rr5c%1y'R]SRz<0)L+CjP-nQ?G<r85n:X%z]Y5/jjr|YBd1y_G<\h];OQ*xOHV1SrJpޯ~ >kW59_.B"⽎E~kGosp@g1s&g ڃ=}߻͟&םvh4| OۣRށ"*؏{ݺ#ǯXC$%.j}vyL74 H ç  S8p%brOd` +8B;NoX+`öݲD:4T)Ȼnrpb "T΄R +>sM"IUM2ZX&̤hqQ:q HzKG4oWR'׮*1$bw8Ho /Dc~&樺Ϥ'}pڒ?ȥyK[Õ% |L/h]x[csT)pI[Ǟ O:nWK_~ϣYxhxǎbbu]8 Zƥ~P+b*lͮg RK.rHXhI GҺO!`,[w5 pmfѶPS]3i7pwV5az*g2/kbCv94 55SQW@҂Y2o#ifC"5';+i+lx9ݛ"}?'2u{Rb zk,S\ln ڕ?Iodэ~Bօ_TMˌU ˘H_ܲ]#>"tplk a Ûc֯ |/%$<==@ٹ,ˁ{}ֹׅ7Knn ? G?l˷?DEUTq~a^r1]=yR"o=fʍž~4c)YB^!Nmz a/[]$o@NMd[| moC;g= DS+IXX!TCa 繲I.neL]T6miӺUB8V'ab=5Ւ>$O{7b;Ϗ>߿Gv;7۔v"f/YfU#Go^\QT]Eeܑ{|| bd[ePb!<\s ywb;uDx?>(ߧB]v ,KFG<6z3h ?-Yo aws3lU\)ū X)6nIՅ\Q1`I=6N KyO@;XJs!{\xE2x9P)1_%mˋa$ҵ?ʤ )N_o. m1HaTRa*׵BpkQ^d<}fjX̬&N ,.}ϭSU |Crp}]#H_EJ{|0`"rSuCuatc{ٗ.{3p_~C'ypFG~X:[hx հ n>z(`.NMo5Ҕѝ?H涓p|YΜ91y1-bls}]e8ߍbwJ0`mf2b*C*e2k'XL{LCb%؉{Q31;V-fTj8+o J|$vmXZ&ox4^nZ[O$i%C%./uJ<ϼ%{mЅ7ĵzxwҫ*xD3f[k2@1yX@nbww^unLFEUTw3*ؾv{b!# wñ}2&]{Usf|GG#m[FG~aC@Vf;dCKִCv{jbXbܭqpiLN Eꂷ+؉eFޡlb,/#L˰ՐDYC,>S˗J=T#1swbD6@sqdm٣'$Ay9d3w< FM^:gF0Jk«fWxzqzm K"[Z{X&۫tsI}k@,b,ۥ>/o'_w)b>ǯ\X1c?uc6" 5,B/~S.?X렐W{tfN krC2zB~_gZ$A6>:r!xy|:p0\':J4hhZ& /b i7 J IDATBeI8ܬ@L0H >Cg^ J~fUXЀɅ)!rcu*w*LbI${8ko^;-wAZ/[ pG۶[4ĸ Cl6Lj+<^+ *p/8 Jd#7oxO5J~Uk׏z6lRzM6=Pv4x .et!ocKz31184~>`#'=k FEUTq;>o5,p} 0D^>:XȘsN,{ok=LlXI!kev#Nb0:X{\Z@ʱV.Z_{K݀J>*:R|mA~kX cl] `GDA eVg\VB^,Z[ҼR美;oӕc^%|/4SW+$(Exx3x7f+0fY/++,rwҖ+iIOݺ)LL^*cO3~,^G/lE޻{~k\s u7n+oi%Ir/LBT]Ek#")QSxb=oXkM1Yv P%aX07Z FG.~aϫBёręa9FG֟vg'pw$Jۖī~# sv #w0$) Ô&‘ 5k~]ӭsfhi^j@0Sj9Tl0 w[t2y׭ =ggJ! xCEkx%L2Qj`L#Ӯfh/-DޡTTUvjrכq]՟0 } w@}éQX;' /B*޽H$c{tu.2VKv"WO>g'; sݏ<| }bnĘ1y-1Aa7V]"st亰XkUё{c `yZkFG# K$YBנJ 7WrZ apRl~/](j/wf!4G"G%,'OlW?F AYCP9یMmR]"Z;'[a}HVbǞj}YZynck`8rxq^M^Y;ymKU{s;_ ؝Homuy~>KB(7_#SÚ΁:b$Ng|Bp6?{coŊ[lW{SsOUޑЄ*wqe)>vح|;w͂:VaqY:3þA $ZG`[vS5?ёmkayaёLYvVO)28Loy\rV= a$ׄCp*.Emp(WKj<'g݈7cOBx$t&VFVXWK4YYzK|m¿5&ZJr{X1weKYUkS)𡕯&;'t`!yszC ݱ6=^1Mz'p/wR/fd31K2h؈ olMpF16v,3+G?og>/ w~?<'3k9o0vXg<%g~c7_Ҽb_h> Z\^X;UWv51uědc{8fa`oo|}GG~>k@xXHJ3zs |օ`Mj%8(V7x ݇ |JiCEp| d46$  V`c;oX2t-ؾѭ17TO<+W,v#*7ͻ'>]ʬYs0 Ys9y3WT*]͝g? Jr#!vSK7I|ue֧Zi_ #/^T3w%g#9x Wi޻~y~ '>/t֏~ ?/'>{/g'(ڼemU=*ͻ*({|^Ϣ猲­c)IBmDEn~ab2ֿwXT3Ĭ9F(ݑ}Ͱn|y5,W>)#! ;N2puBŚa8ĚwЉ  => Æmu1bXd<) do^/';piy7J;)y(0q0= &΃v岕AT&;yN,L;s0{Q8]A Ը Bo36XkuVɡU @'"ͰN[8bj+O~-5kZ\~ſ؈"]ϘrXkiƏhOWfTa̭]ӼةS_뮻_t9d_68?u;v]EUoRՙSu./'aHX-<pY71[&00lZ`X+R6GZ/ ]m=ԑypUTvl5 W%8Bx#o}, zt@iԉ=Ce })XE/*];+lآ72y= 0 wp0|&u6Ф'rԀXk< >+ߡC"x{"7Z~fYEUP.Vo*;oudOc.w'&9׹B{=ظɬto.4EZ PnU&^lVݻ{~׾/nvSV-]:=;[[]Ew{=>~9 ab5y=V'SAWZs4FXkCp-e׭3v_:2Xۍ!߅7.{O}`Ɗ .f(O0印|;x^`\d[ժ 5P*/ik[*W;dM7Pz 0~r”9`YsbV"9y~,Aϩ}K@ VհȖ:͸jj5 ;x+m־6m|h߶'|6nR{Xy\_KnWQ`}R|F׀V+#ߢ *ͻ*pu|7EY[ABOz҈^ E&Uo730gW= ta9h'8ìLv%@^4 (^>+ݶݹ=Z)yˣῬ&{|JWp>i|;(iHJ~vvtwm-RDyWQ:$:%7 vI _b`{k= !`;:o:3p=\3:r3[ pлS8))b\̬SNoS6k v: kޗPojT-,k3.6l',yIvՌ;N7;k—;a):e"w PX"y׊){J fkwS<^6\ᙂev{p ջYՄ y`\E9Z)qԭQLI}=o}qî.ewcv4A+QwU'㗉c؝Fܪn`cCDFjxa9zwȬ+_aLS0kmAb} O*\#I PaJpҀA"%p0,1wOg";ER|.85#f,RT Y!{)wE1yHu+R XUqɛ!ܳ,r9)OVZ` itb{κC8sH헾/kq͸00]IkH]K5GPݍ]#ኼNT]EGܶ=>~ Xeu1ϷkjI'"k=6v꿝n 2sYs{&ё9xuf{ & ?W&|5'5ܸy8^Ȳ ̨xZҖ[$o!S#oϵ^O]Y+"Zlayk'(T4;6A+u{P%y>;^r4!_s"56mҥ/ת>(HmM@/T/At?5𘹽{ZrRz. ~VJ:cYToyWQ{|<&. ZlR?s| k=&u!LxB?!HZk} DĘd1B.ᴣڀ!8!"l8 N9$Έfp'GͮpH5h2fږJ=nysv׸3Pk*SV.Eb3:{΀u ˙jK_,mV's1RTՎHW {vb{)R. p7{O }ņ(5<8nH`8)ʷy誝 7S*Ew{)*ͻ*A) 1btbF2]1|peu|x68Y1Lb.[fW7ßZ;Q Ѳ>ϻ3r~]`&E4e6-Ƴذp`lGu~X:_ 9gIHESoJ/-k> #׼sI < 7+tكi4ՕwҼbֿǯAyDwfQ kn>44d |#m|`pt;a۱1"{HB,S&|XوQ#`44<0e-ztsLy*|I*:L$ `R@v8I5+oFYw($.R`VC$3Ό Up"\d7!;Bi˹u YVCqt򮕶u8ƥIw-NjimkKsb|Ka~$7#*ޭ4}]EqZVnmFt#Ab2n"7w|+ρ3|O({ұCaQ,-Il Y[BԮ}گ=|suxĔ=?S9}\ M:|A\ Kர̒ GA'/dh*~D MZEGXZrmBz6a3L"'%sXqwoyjE9W3S孶P  эgЬ5+P}0p78A#bw* $~e`pH,F9 9 vܞ!]!%4[x$t)%f$fû@XD3?4V^ZbkKJ:0 4,g5gzMO ^sCN/X3!Ђ_eoX5cMetR;vi91"gĸ_aV ?R{{W5)x.AW/%2P#;ÖCf!&5Agg7~Ks8[)yc[Ԥіa=+X NGW}+c5 RT]E}"[g!Έ3"`ʰ5y(>7(8:сFG \_>GG~ 7p$6v18&Pmyaė\jqI+v(@ ;z$\+pmHyp  &O>[&63vJ=ɻH |߆i7f!-}ShUc)xoO7o+q@qW{?7lLZƻ!^iںM]Cie)Ёȸ xR{})~YMVwvxSA v;fZcNL򕱻o ѧ oMTn}yWQz oYpCUJWݔp#,cw}vg{6:#HZRv. kI.^4y c;.>_SI'm]Dؽ|us^6O|.Z|Qx—>[1mNB Zo:]e,f&e;6yM߿.F%8 K ajg4kՐ9R_*C n۱S@xg9>UVUS[o-Kd[YO@ C`GX_~w;j+;+a쁽" gu^I|V&'lC& /[zq[,Pn7!4!y Z `7,@Ӈ-5pÆ>_?-k$/2^q+|2l}[l*rl|Ҋs'G70ibεn_ng?5SkVAX%o>aX < oЃ컽N 2>L2gYD_#8} ޳\5PNf WX>o=dB?EQz1{&]w 5DVA_? -ү\M茹󉍘ۜ7#$ᲔoNxx?˰5i􎭗 =IuVSN` U3ہHۘbk8B?% IDAT+2fz,䭔\7N~<6,s![Do7Gnۤm|slou2o_"Yȗ9<$)~>`io۶QR~Ӗڃo4.< >m7 .ϙoH-#$Xep\l˰gC[6|QA%!>>fY! g2O.E5 -C #o-Î8*Jkpj^lcfĿYk+GF |S +|=W=W }3pia 95ɪ{фjjH8/%I ؐG Xkut4o_}GkNB(79kMά5Xc-*^4vjtCU-`vNQU`x{_#V qF~uy X|SN8_@J~ jNNk}p߻&y׌߻y0uٺkpX.j))Xy[yD$Sr=3y.L _v>Mĸ<<}jh]D/ϏR tjQrB\p,3vl[VOHq' pʤ kIDZ#AYû2塚3=?M1>~C Fsg"56>n%2QMVB8~LO+[ 8||}p /*74&!3kwc݁Z&E`ޭ~6D} Z}2?XbR dG۲\/Cf ~z`x~J`]͡ ?nx:z3K"&+ {xl [絰_;:Qu<oa|7ƾm^p';aGJ\8lbg>͉[p{JpC1G'ia:a-Ҏ_-Di99ewWjNS2m}T4ހGr }5YLW6Klӽ¯|p*j1γZjlq B]'4N+/(XwOZ!T8~:>UBޥ 8 !3Zm} 1v2Nڇ6X _Ff vwܱ{)ͥ-/Rk`y.,gy; r  &x'ݑ&=^o@Gd<0utc,oK=;,:*gxZ7KلC5Xcw +Z8+b!ES ݦy/n;'{`X:[2~olAr51T36^7S lp!,l DMԍ +Wpݻc5(Ǘ$|?8ceS_9UH"w?jU1!U8a2"q8 G_Q6P]u *'"zy{~ |%Ӽ;v>=ILk&] yłw6ZĞBN d-|bSjHRZH#_&qWПAWƚIA( ޔhۄZb|%.$} hfX:Gr)+>O? y%5َ$xXAZռ1sw,'+Oa8kV[٭2=zd-4>`pUheakGAe?WcwnuPf3y?!n;m8FUXe|r4-VU$@N'A [7V6XhR;>ESvuaT&V8]'s@wb">0ީBpp6Ș{)F#a`]A"v^W8,솭n)y  ju9]2YP/uw;Yhy/I⡵2㙜ڈ0W ->y 8 GBq.҅ yW֪°0\13wJƣz % K04KԉT_+ԕk=5Т{͑O6}XLg۪EsЅg,[w0l^i'f~#+ޜ3@a0~vg-Λ_ކ,_9;UgN38ay8_y; l2E07r,#N)\`5lo@.Wg-(c;JI}$q 'c Ǎpi3\KdyG`eI 6_ZdZ]E)w%q--lXay윉LƲ:NooNSkZ0 C)-ĸ?+Uv6wkj ~>OtJ%gںtB8N m֚gn;aWma0$|P28^vr&_ j}e elr!`0ux~]]0 }Y!QÇ%eWÕAۛt";|vUvK<\-rjZ{}|_ wE),HDH3>gnet]| s[bwB޺jX[HF%;P)]by{6&BVA> iSG塁[|6JV?WCpȲ׶V@6% h{ x-Yv`Q,ĽqĂSݍ֐khUx{F~o} k2gѺ5r.%LWiiϩ+[WױGEF_0a*-p [1|І58fZ⅃Cf:4l;ÉVȟ=YnX1?cRggl*U'(G6rpe:I]Q)|!` Ɇn*{*z,M(S+[)aSv,)Z1{\Y CzM2w;v9ЅY 4a|7oxBKK5YyF޼5}f!#ZQ<.U({ h{Ak=7x_M6R4Ͽ{b0|]$PE&הhl(|&sş\WO-ꂬ EkN9SWK%>Jap*=uݓlx;M }0׫ׁ%(eL[᤮;a7LSƅ.\*;%5e".ؐ4x|#Ic8$?Y8jem#2dv ˸ɦ,BLw5NޛϬ5+,kjEt8`2|D_VüRG&SG`)=5AΉMkNPjt }#I߫H;廈vUY$d0,(nY5{tDZӒM!bw|Lv-Y/?xߦ5kkm[49^lb\Ե~ o2Y`cUr#`71PvɅzɟG2pPGZwij6>wZgDaYOmO\VIّN +nY"΋W^m:@kExfƺ2i+*xWƋr q=!u֒vJ` +ȮC8*su%{b :Lf.,)& qQ{;qWyFr&ҴiCCq8q.B˸ {6!3kIcq()i4Pl_k`߻ẓG@pڊ;e=-bXb95ox\ trSw l8HqGz8JB"uH͊0?w2og?K7O{d VO +'9p1LNv:Q9ɶ2ݰkA^^v.KY3WuQ֐wcq(*`(jwYF] !oiG{=o69TGkbAǓݫ۫cf1ş g q[}/tHjqw ƻFG{' od?P7ýalW"hǑۯ?Z!'ZɥYj-)g㩆 QݭsG­eםONޕd]?w!^Cz4j~gskGcx4תS-we3m"x͵k-P .VO2tN(HϩG>ER`&z&M!7uHؐ- ko]{ xԖ7C"SUL/o[,s9Xx_[U'O[ e%'CrʘVp|$љp],+&HeAfۉDX䩑pi_nəkmU?I#wC;@ r^wºQ`n\V,wzEsM Ǝ8[ݶXb nchqOؽ Yrm[y;]6h;;6ܔ<2>'5>NV3˰ x`AWrVݥ'pr ۥ#OSʥo,A[Y~_@a0%n͙L2ڬJ8wkIR;=+J|K\t0uv#^Wvqa*.%V39ZЖ }dof!Q}̶S{ZeU74SdpVoᳮ EFޜ5}f![UIQ ?6neCM{ ɉVa"OP:u)x31JXD a oZ +y4\e|yv~;] "tOOۖt>`X`4Jn ֐wc5FU(+q-igp_֬:p/c0[2FB9p9U3'9G( \1>>JB](N;U NGʒm^f䍱;N':ShypFW42Ycee3ϻqk b V]zߖ'LY_Oq Jxyv9gWX|'Ap-L`YN K{u20L"OyG1*y;'fNHg73{߄ҦҊ,`XsO=|ܤ5ufI;eEq)&full7vXUBׅ :y+l|3ܷg$(_[cؠgk[ KN1,Il2\Z iikޞ='^7+mqȕ8w9f0˳oŮ&|CjZ~_i7p{svy8{,C^伔ubf*X#p-ղ[?*V'|icӲ <۲J- Hchލ5I+=np,Py5nzUOK_mX @bw*2i&Hm}bN"&g߻“ ~} \NIe.]VwPVxb^z Fx<1ϮoFϪfZ$Cr1IU. +JI vb{8 쵈j 3]^S;Tpumu o%^RXTGooX&c {w/ WN2"nI{*y+IoN'䓫`X_g+&7%o 1*|vEZeo}QUMa4Ts[iom4ͻ v.{-x|0cW8_"+_BQrk^`ywy0|\⇈y6[`&3y8ZP /}L}8 Z?'eںs0,Ͱv9jwǘx÷ѼÑJҼՇaKݮ{&p37A<]8&]O;x'va*Ԡ Zi y#ڠ¯zIyˮ! s^)v,x#^7Ts[ӼѼok4k [-XhϵN7/Yw/9X Oq~nvܙJҮ-۴d4"7jOO%[W B #7͆"w6WjkYٳxh)rxqfQ˘];֫wK,Wwx(jP܎&0d/WËLJ60Xr3gEm}9g|S7V×u4Xe_ئӺqW7YıKT&zj;O_WD6EBjƬ˭?hލ5vGx"<\Wlk7Qpg MŸvjI_]/ǒIj%7cp'^_K' i6a?9]}Xy7Xc &uLƶk2ezsAgFn 1얶e>vD:Sb7GF9<,RĸMJrΟ,0?˼;9{;,rX?_鮄\ 3x>J݊,25g(<0 3s}֟~we|-H%K?!0gh\#^ﯲ{(aVSm:|, ,sQh'ZhYRV:nN`.`UB2[;!OQY"Jy{^8%U%Sb_"3?E$ؽ=%uCUPOz:߫r~4l1N,:U;sݤJq_fT_ފT֕`{,;dwḙ!M].,GeL&J8Ȣ ~%D}ݽC4Ko`'mR&2.V O%{:kVQv V%'x*mTӊ/,=ٙ0 iɝEcK0iS-kW{֐wc5VX쇰i§lJC? :Tf!Rp/QJS8q$L5n;`7Y_DA 9'I,P&߻/ wmyI"Zਕ2ꝼ'l;2v5fefحL"Ijlw-"VQ sOk+1vq̩zۤ_EٵZ|G|> } w̙[?xcBJllqqk)H$yØ qޜ5}f!kƪ, ւ]J eD_gNl\.2RXVCnGU vw@9Xˊ >lMշK vs[w9fw53ё'c!߾^ݐk= 0pZ']ͽM3oEk>% sI'CxVL34^cGO6̨~>lp5Ki9`w)ՎD[ỐT?`ۯtD8Q??= u<4xWݛϬ5Xcwku l1xtO?יZ?BTDg|k̟; 'EK|; |+%3ӈژ\31E$(y׼S<U>qp\x]Ri+g {li/OM@!NW%C>Gw-v'W@7^wmiBs<$y7};YCލ5غ8ywM>:tU[7N#Ncy~NB|+rBGN(ٱX oS phNIIdޮVE( eW1܋9{N5v{WrrS"3['pV,`$$L,(7F=*w!}5cF:(L7wd ,$c[\Ѭ!wR=Ńlx7+0rx(|$B%ή|YvKє7Ͽ\@hFڅZ y2q,糺CGe\q:vWN 0>aXcyn`]>seR.m+}}q|AE[\]>WXt9ՏcplRG@>W@@1Uqt|;Z{$؏7_ϚV̢,0/D-p% _l_[{ /*,([`w(jqlm }{*8P»@`~ kX"M$߲K*JKxM\z=Mwm%X2qpg('̶ !ߑvN^2M߀'",K5x&3zC4%|XCލ5@QMEN~\[1y{Oځ5^솛`ѡG־I"{`8R@N˲ޒ>-C0gByn.8d];ޒO]yxn[_ pprRiK=] ֐wc5YQ<ڰ Y0#0R a~ ی[+p 7Xpcl8{t~kj>8czb!3:?ut8m9-p$K.J\[ =O]uW)kzPsjPŤn U5qKbw+#s^i>awR "_(E{Iʟw / `K IV1@[`^p]R=aw'&o>?8l*{;;:+ѲJ,eej7ī:&Of6my_Ӑc y7XcϘ}p-佲n2{:`Ď;B\k5e$p#np%قScd9Їp j GRI$n߀k'XL*F_?o΂%iR=;S!|FZ{І_b%Z: _NX}Ucnkn *yL-À ;cHކp <ڽͪ: ~ږBnLK{y{ɸ& UJl&pT !RqV`NvWƾ0nu8cٜ]9!:}Jc5vGzWQ2.lP|ì nS֊xzɌEtO!S![ldq /2q7U)1FJ? dqf *"y#<M %VY%:`\ג =/1Z\OK?mu-$>~ 4+η-Z_"ؙ5s`Yf]مIa焂(/dk\˪&I+t+%gyP;cS~7Uɭ{k4縚8v'Zxcwpu {rZciMT3k y7XcVm0-P“=vBeS:E xuvU_g J[ x&cFֳ` IDAT>hӬ.cH,_RD^Z*A87FU0칭~-,JO2wk[:Ƚ+%P]p@PKgL'z8ubs`w<.n acep솫`Q0} & r@!8g:|R4 bk[^Zx=eKo Pe.2n]guïgg&W^hEzT469L$>hލ5)+ 7 vXtu( O2ٻYX6|es WzЧv./D02;~{ H0:T\‰9L=FҲWB5TG2$[_]tkʠwŚtRXN} s2m^m;L| wqFKݖ⁰!aۆz&BQ*[ e81|`vCX.ݪ:*ZϞ2Js 0Xt/\Jogm2_y"o{^B%*rR##O ]KZYWNRJ\eGcZy7Xc(~ Gp5셳,/L$I^"o57 p{eH\gL}kiq4Z3G)ױ_Zlh} YKC,f'䛠vifOq}.N{YN\dםcXpvZiAUj}CDАHaWͬFFK161c#[ىU!H {RIK菿 M.b8f>']8[+Mu[.I/[W?g%~s7~f=ɦfKߡkIis;QciəkB̃ n|,-W9ӽDςw78x>]Si<[xx:N,9,o0< a+mji5P߮Xd\l ',%Q.4 6Kq'Dڸ/K5Mu:HZ;QMmB)évF S+2H[>+r`5f9v=߷)|b<8vѵב;jN&aK >^ ^⤞CtSJ6"*jW1u*'ip>qpI]\[*{OhfbUvM^-8c$#ӌࡦyky3t[[KG(.mLJfVz.h?­֊<$o{d2BHd)xoy$ikِwckĂH<27uqhA8wŜe~?6jS>I-oʦ': o-$w 6Yסh'!ЅKp0 +8=숡4?sX\K-^N] El(wq*zWVBm;Nc3SKǣEzՖk{7ӐئZDubdĚɞӤ÷bsT_Nާw5I, kLZS5XdUߊ֭c]8pz۱aV?ѨDz&췎r;*MSO47lWh "×,ێFzh\Z8kp7 [0+ ܖ"aq1jU/DjS>!V7+$:CYwtwq]dѫvdJmݘGy~gsO^{yR1ݤ){[ 2]-f݁o!ʴI?Laj5Gd$&-d?Ns[_ 5  fWX&ȳძFބ5w&)k,9+P`'c6%u#±t3x*΂{Km=Fa ^:ny:`v\5o㈹v>-J`3ywRehU7J=Oq6oH:Y4.NN?ݧwW'6RI-*yw]^^.M]V"}1^ Bϥ4%գKBW]_k{- %όkRV+lRseGANޫvǤ){n`"=ɰ{j:5yD_?HXDFmkLZS5XUۋuB m&mEb bdJܹ|,Z&>ZH vNOLGO 2N}'Jw"B!36P{4<j?$ 1'?2W[JoAk'(#YY1/[qkdUl m 'mWĵG{*:?=h52.r7aҤ57h/ 7k9bJKk-s' 2Tܦ56KuWo/%w~ԭO 7öAMX9;'wLkMkԊڮ]lڔSW/w]`d >[ĕ7lYt"F*í7V/q1 <:-v%Jagq En\m "p[XwDnZq -[:g_OyraNމO zll:ƽ|L=#OBM]^7ߖ̯|J֯f˅T~SOكYk12G?eL-56oJq+ەqr"3-ݵ?@S8b[ K .{7k IkJB>lC Bk&F;xu]EpModPcը+pn*_arWNL޵l%{B K_JByZvCqϕ\[w?XQTblI6CL&N E(@[ƙLNwq51Sڟ9P݆JsOnZ]GE^!q($ ~^c ꓗuP=^(b,Vs񻖼K@)^ ~ʇkk4O|&Wi_va3iIc5YQ<m66'܅wv8&hQj\J2>mi  ֖%d0W>[&%w+zXU &a$9{7Y5f60 H`0،00#hZ׀e6`_ۀ Lӈe$c#.fۀA# hgz>ODd$ђɧȈ7~qD. 3N.Dv=P!&geb&><Xv>Ubwa60ls xi8Rq,mˋ ޅqq c#~)JUTŪ3j˼T `lQE)y $djϪen)-; ׎_NV@3EN0mIBq#Kޓ}0WָٽVˆa_;RJl0$`w+&oMs^&>Wy>ϙКՊ ]z>wO M2fm-9̳n͹f8 V0{>k`5섕*9$59U >e9K]8.+.S'{'XӶZ"&Uuzp7 :nIWB[4ۻ~;ύuw-;L@}-mFaJ\S^FOl;n!{ g7#q)|:,1u[dW~!+JWue Ͷ|B.̂ &MfnK^cloNH6[3r=d[M*%LZ@ )]1 ,\a9{ZCލ5آ90೰~ J KhcZ0xaPD*.d@=Vƹjg8ae0 섧<8Or^K 7nhA0lЃ$%1eߛc4Wn꟱Y l5֘5z{qP>ڸtWQ&BHx;hu;8Q6t}NL,?5hn@ deu73s+VFV+ZKK~R͒=¸sĎ8L~lx21y3 3< mʹ7roc^MDi6<[5};YCލ5֘5]"CԒ2` f=wk O !Ef:ISiN0k*4źLg y+|woC4ݞ#W!}rbAwL3w}eܸB+%~5E$doqxz֘+1Wz7'gaqߔ쭃Mmp& ¹;17uʷhU^KsBxbМ΅a{S Q>ui}kPZS5آy ~O$겿^yӆN\=s8VޡBm8VĊUBBӖJPTK+)iC JpR [j#٨#XYm#/jTIYQlj[ (Kwk'/0 6{&?YbnuTbltg w2ce]%؊:|CUfϭ}*aΆ!$%o;x2| ka([9ԖpkZ[(gP6jc˾z=eYvceJ}jhV)a7=pdIĜi{!%qr|OSS}[왪#ۛ_3<vy'[Z#$)w&?q㛀_ߋM8@i#>_:)M[1ڱ?͜1y+̓4ϴV7LhY'xc,8H?Nnu4\`9ʞL~B.82n(?4CuS^аMsR}UUgOs{>Ư#׭};XXd_c0::g'888xŕW/U?[ߟ4;5&5XdĹzZ}@J_W¼F[$qgp){cA&m;aE֧.AigO5Ї0Z+:mb\7a?O_ƝA+gfv?{b)O|ŗ}'^/~!k`Qv9Q+R7oefRd\8 #/Ӟaa/EV;5Ufbٓ&o5!o*E` lvdޙ Y$R T3?y1}|WP9a+' džo>_XNMpGl֒FWJEnbƺc<89~fM]H{3Y?PKYS, y0;dˣr=:^O,ѳڕza:|q\{B3sIZK16po=O}{GgrG_ɵ?}2{Gk' Bfjt[ÍT ;^hQ D{-x Z)T^`<¬obbcuDnɹ9|'(CfˢHB5`my/j06LI-|#سMLb1W6&%xaYZ^f~iH\K-$] ? agbۉ4r:V'/jVJǮ[k!x/3-; .OˍFA 1{楏B%حܮI0&:ūRi0w|_ 5x3Ecjz*>;{vO^a+W_ ~7!k޼7NZfe;'M޴=oPgE01pKktAjSعWdvvnhxv;s綆k:XXikD. y"7 Ugy޺Jb:fɑ\'f2qu(>B?zNov?>fRt/*lD uF6ߋ]haK'o%:@j[qybP}'.xlU9k[f zZ6A{-+&bqSd/]qAʳ ƶoܖxWHW3^6UٙXB>njZuY(&x\$L%gI/gGcɻ%жKv93&fGr3]e/]{s[z{cc?ÒBw 1[8lѕ+ Sw%3ZCލ5R?a= Swc0NsPŒ$H24>'u9d|N!*1yWF7 UKcpXɻ/~Flx'T @PurS&kQd*{/obB~+CeUcRNL>(q|1+:fK1ɡ=Ryru[I ;>RuC5_f+zJ;jl^*}7?sBGnؖǣ;Y>)+BNLMi="n1 Ȼ[m2; ]Ŏ\. 4ճ[uG2ScUU+K^CnscOT}+̛7݆ uDhvh&#]I̶ҷ#FVyDf=+*D-yʺBȼ9^(*[$-wUĽg|R/0\lQ.?.dr0Y%)75cwrzb\49Ʀ`:9)zgi-] wW<0qSeyl8zKڒ N¹>mw↢4Ym)X6]F'!oo4l E-y;(a젱~UUw{Sk.cwo9$8Y|/0τêBH`L9\߂U:Z)Z!g{0bi mC\]J&s?fMA$8nBa/&-H)npB7oUbQ%kZ\Emӟ,`طfSv`I-\e*f2^$Y6\N>+u'&V,yW6ծ3?L#:n̖n\ņՒ\n p1ym=ω knU~ =ٽl,_UUoiɪj?zWoN~ɋϸ[Cލ5X?sB#f0i0Ig…;} 9 ̭ G^T&pR_m%B< Y)EZ.H,A wu,+Qo։v-R-s9UyI-v+Sƃ|j }ל1Tp);̞kq 0dVB9v3c4 ϙ9v)^.D,:(yPTj,74;pakZ=2:\{@uy_ܵ8㙧=9Oՙ[Cލ5X? )?Ðh a{|$+6W8WFke@捾kyQ5ȷ_lpgm3l3"6k<&v|]f~H%TST~x1GzOg.춮9lK>sԚWX+pX51y*c4fdtP7f>-;ɟn8n!?@Wq>%H+R:Na/_Bsd䭧U01C<_ S#??zs/oG;n\xB!yι'zҭnYQP00sjWdlN%1L 3"*~oH4WS0π/ y'ªBEKZs2#ذN<]P ]86 +x>K#+pRkG[l꼜<9t|+REZ3pvR|Մu%';f[ǖ<^5'ik 9!ZLےc^i[TͻwpReix:oo<kem# y;P]2x= ;?~}jkpIsǎ_o/ w9/>qwk~oDk3apCuOUt׬HΓɒ!~VkX*㕃-w.\L8f#x/n[f׮<~-`ʾ]Z7˗Xy.c y7Xc;W׎y;x|&=ᶇ÷AFu!Ix | <.xF /~H2K!}y*ʥT̵e2Cұ{='޸?])c)kcQj=8*jJpRz1v2O-v"&&Ur%rh *x70ܔyl6]?;6˴}vb?m`$.KRMNxz[#ԟmupm yk м8xr,}m'G79?Ђ?9R{\k@Waa?9slv'>>]^ыS_ou.t~˿~뻁~=ÿzDUUoW#nK,֐wc5͈5/`8hq%,1^@NO]+(gE-E^kpK}|[TNw{\ۏWb=Y¯#x͉m\/1fŠzLa@LIO0~IK;9&& 6j@i`h[PukەqjX jovdBxL;%3m*6m/Kc&~ұ$[ڋ{J+"ݲrt;.yH-Ђxh&Hrln x\VH|ٹ>&{Os|Ч+'=]xg{?x"OMS'}+{Wy^nے;94k9F`wa\e aaÑVð_aQ8%(|ݦǚxdb~ce~w)_~vLFI7]eQ{a' 1(!ޜ•.c`= ¨k*WY\:`IoAy1 &e˾;n%F,1p,m}țn'xic× O&VMPtYSgZ rߑ?`r[27ы)pmȁ[50a5G^M*,6ŰVJXi( Ən\#kSM^uNwow,׿i3?qSvի8b|ŕ?z_3G QP[Y{r2|gFnۜ{D>o^(@.sC̲:qgCd`V%4E!Dʌx9!Ҝ^ZKczL),:mR]9L5ɛ?2K IDAT{:ּXL&ZWÐ +L$&0yD <GK-&0k̹oɑvGn+otke䪢XE5w\lfLEMƶ.,sz)w' ߊ# ~+0dz ǜ ow$&rANc2ډ\9^/pͽ^ Efͪǜ~Jn4kl)sEZmռ`5 {%k~X #X +e=Kc{y=ՒjX%yH~0 k*V$V">u!njД}יYpJ 83*6J4\iJTdBrp/!q4>νYS|v/4 !PG:H{|uK^kEm%4):lc]u@aͫHq.y /wjwmr&%=%񨁵\NlFLrLᘐmnDuFf4(}P%߀3F$p) 1ǿ,0srFnƖly)crpPP^X Fy^ L{G܎4[x~VWXTVXv밙Z}iUKr_FV^5o3j?[,]Uc[ߟ6P=y8[uݕiȽ϶4Y}+a޸㼌Ubdrɣdi#ۘ5wc59wÂ8|]J{pVt=/0X% 6_P!:jq {(ۦ]t" F&xp@쾬jݡNFGrNoêZ1FˤWgݵ3>a|7YU89kaX<·R9`hUfF8K.ב~+$Y߰ طL&+L'0f]Qgd5O .u~Ikr=v'YS >͒Y35T'YwY 4R'2 =x)%oa?<{ĭv/쁛ᬆpy흓5kl)"T߼52> P=mʨ tY_!E{jU"= ֓4+%W $Rp͡ޕ}>l4y~% y-+__c7&ak2ap43a R> )u S:C͌հu`ۃ[7HRDڃ!:()߅j^2?:iI-Xal&՚ܦ]=lǖ-9~` l0 ,BҙT,]QɴC ďS$j hZS5A+غV1up0' RVLu%;F XJ j` Eo;l'~"?:_q1.ֵ*͛oыgTj빮jRIZ@~5b>9Y&K^yLYIW¹MBp'ނB )7I|_ Ջ'ZАa5W))4ىI{Vf56ds~ J~j`:1>ݺ&D~..r΋|Pb5!9Sy&^myؽ~LV o}bh"fqn[cPWS^o.^]xm!X~ҚoƖcę$ϱ׈MJ׌bW|@h&Jv4U,Y;٬t3/!xm2O3FԔ.,yۃ6i'WpQm;1Uح)l :nΓHF+,yOlKy$enm:7hKY~Қ5͹1 kaa|;B$#0Y烰 #N\X|5ab}7Y<*m e6/o\{m$ I{~]yW2<{?@)rLA 9p/L.b 0<,K{r˕\w\iX -3`we m#yMT͢G[`Zjl[K_9}ˍY9~t`]Vwagݞ{%Íbrx/p$>%6Ğ0|]SutNL9jXȾ߁-G.`n B6GT . <-[>96P/9G ]4l/0ωwN֐wc5vpsth{::0|֛)7 3Up.=;` fD.H[vd>X+[|JC=0ؤZfZ;L\+ar>T0Bdܑ[C6fRudOXw [W%ٌ[͛|=g[$;6B{dF&w ZY}߲G9OoO}7-c1Aj_>u9BʿbzI:q{l47swN֐wc5,sZ%bzX+pl ¨;8]]XUc4Â28AgH=ޝAR݊iLau"VH c |,HYb3xGJR1[?QdfVJ"G%bw)I׊%wRr10̪q$&K8 /ހxT|/ uj_[7a7Y>=ߠG->7e۵۷=x`6m+پ  -T>08;9;'wOkȻ[9t$wAUVvqAQ`ڰO$m_lȻI5P d+b"d:RL`U7~Ҁ.L 赺g&pRCgb³%Ր/AciQMTQdL}X4~Xt*t v+ > W  R{Z^ ߭BJQ IH޾7Kܢ{qk4v/-{/M:!{$z5۷_7MHMLTyQC53?ixY0CwP+[ZW{,sI0zRT;Fȟ=p#$bj{Z漭 yɻ ]VH/k8uCzzn34o-9{ G6 jRVa3ƿ MH˽\Ʒ+^ثml߇ܮ1n'[y 䶼[e؎|Ae㿗swN6iƖo0 et^Vt3@ Z:CO5Z5Bf9v1zSIl\Hհ+ic|ӟ40VǾa,pƍ Z$]y6JY0Ƌ#l]@jvvir]/-6d0sU5Y]PeewVwm7)bT&`LB \k Vs[LX/M]_ a/dܵx ߲'۷9vr^RADܷ;hލ5-0^ q"~U `ZW !؞+a%p̀gݭRvBdWǚw8V~ߩsxTPn&x3A@%ۗA34X3wIJf$&q_%UKޕyvpq7"Yxhz۱m+*Ѽf‘˳4{m.fłLýu=v-uEKxI .\yNϱSm Vg%v9yu{iMEQewN`x* M|<D{g}=N37۪)AS+2% s[?Uhލ5-6΄aWѼGZ#/{$h B2G_K%\~NUsuR}s8Rd[-|'zq9O6a@J HCq[4Gwi{y8 aIF3i]*Qtꝅ98,@n=jM&9X Mp@ @?Ӱ%ύ-ۑݕhT&I1~W,nP/&?8Ykcrܱ&eѼk[a=aV"=~Rm`"gCa+.OpmN0痻U;12Vf2"OVŁJ4OML 7*$>uه6mS-ɴ03g?Њ#I44AK*ܼݺ1`3% ' 5"ݩ cR ,-Y|#wp!oۯe1_X3ԖțO=A?;Y޷5w(kV.Q@mP(pPW 1)Y#'*ɲ>|-J9y+_Zia&^cl: cFz/ wTuK;WۑPZuW(6r9Hym$oī7ȅҨ|҅hP0qI|#H]|*\;S{ 3n~ f8uɮ'doHx υL88` sHB ֒/fj IDAToT}n7mglxĤ8by)TZ︹5He) حbhs ;^Kb_yd$W&&z}JMzyaɉ~ ލ݅!kZ!ٳx)k`V@ő4gx`8y_N퀓K-͜W|i}k1K\,`UIGNLEU\|?yWp*\&pj2'njQ|0D^ %-Cbk;Y0 me-yǵ^Yc)n֤=Qw]MZ%oUaLF[}K:i K\Z 49yBј]aCiM7XcԜL|.{ ?RqB'oVX\P{`)!0(00]*x9L|5g%Y)W/e9׼qDGv6,R2[#rTZz['*Ubҏ-גw%eQq:0w`9 A"0-ccs+v^ ;tKR *~7QmW0#}iF}[~Қon|у{0#aB*dg),$]0Xn ?Sb%. 70)ǚMKc62k8j oc[-gUeo͹'C !N+`?e]}%2%oo[ΈIi4ݓUv;fp sʕu$:Մ&][2̢R}Lweu 7'w}7vX~Қon9lxw!}x {{r|o/ hAN'-K/͈*>u4`b oḠS:H 2ޕ$%Ȼw#X֬Z2wW~zr.;Ds;{F[F[2"6ϊngVp-qҤ+){{,O+{[[o]|*x/awF o +M2uC֐wc5va )8 C[hwcgWd=~)Sܼb&ZcFM*jWdN-FMm;0^TW?eݗbDdY*^EKbO\+ 4NHq,rif/re(ؑw6I"bMf ՟;o|*mo1!oe.(6Ԯmt٢"ws)8C[@;n]Mi!; 7v[wC!nnyq^k$t>8 :0, >!B's0jbD LiM+>!EB9|Ke3S0nYW=Uy7:R;_K~ԥ[^er9uDNT$X"+b:|kMz6[W&T>Iרwp~xKq-^W 1WP-IsSRLItBߣz?hqLN~^V<6 >+ۮ7Fvo![y?U֐wc5vKm3g ]|h+^pUFNhiȸMp#8Q։w/f<"Dekv*1#P3 ?%AZMC=:r-uMdi!'''VJ!mSotxn Pw*-ykw)X_b^dXrbRpk{U)%j=y(^դ/h[Y{5x';5|wڷ5Xc2ߜ;h~hGь[1f: ˹2On4< ekRúF>nZ|Vag<OƜՓ'¹\x3͋ymDTeqɰZ[H6>) ߵ};0%K½?Z"~R9%h}tĵVˣ<];jw;Ҕ&Ň+=_]P׮k2M}!kVMp| 0jeVYXEU X]M3IdB^2>*ΏU-u%3y_֤jf)Qi oRga? )XWB> `fYmgOKS^HvMS p|c. &\&S*S"mxy_*g*Ӻ@ybMИ4_5ӂs)vd!cI;.=|}YcBkZCލ5-6͹f5gcy&Plj ņo]'upVCW07q5q~d[Hd/EBg|Ks$O]xTcW\4cO݉M\pWVbL9[} )]xe,i\/[qt~iPqXdٴK+pC,%NeWQ@v=^\ۥD /j~֐!k[c޿׹3 imǐlhR?u;8`nЖazKފwV. &!*RvXx&fD͝ɠVIcdgK}e>36-Zt0w,vMa=K(k:;VWh)yfJþ +{ބ/,{Vg߿9tbA.*5*FI͛_{c$~1ޑ`/1,hl1NmY?=Xp&}\s93;ed FiOcf;0>ֽ~ttuW.f yo y]Cz 9vQpTɼe61N\jCYi IbO3;00N/8?"'nxORA \ {`6U̵ A5+%f]bɫM֞S]Eɭwzu$g}>0|B|INy Ӷ^h~|wfv?z7Y!]MP.**:@M=p(|.I |e}5c5츼 {`m<@e]3фaWjJ%LFFc0KilcJWq_XNl2%r-𝼼'V}zw-| F[7{.cV2' 5?K'0:nj+oNÏ "{r(3KXV- h@t؁ͫ? 3#[9R"3G)ugaOQ`<`{l <^^::'(vN۲Y3Bzomvڹ+vkRwkϡ{, m?> swQQjb=YO8sk#΁P{zp.l-Kn]exY5X>ZHQa:|%H+'wh( iާf諁&', 71xd mΕuD^ [l-$5s#(.kv,%Eqqҭl,WU9$wc9C {zC60.^?hM y:MWUofEEWSP.**檪g| F=3 08 v@1 x I\JōjнX`;lo oA+gհ r:wKFfr>60wB ߕ@8mka1LwS4v=܎kpՉZۜ 4uVy>C Kn{ WAݜI^%y6VU/d]P.**:^|*9yf ji0dnGV{(h273 80ȅfP=K{-aEk4mRK]8n`.1mlx`x:n}W` O/ŝߖ*8LWG-gs?3TJL3U?{ՖI.:Yhc2?=\zL;sxLQo7?˒KD\aAa'突j0pq3vsdGƬwĚQo߁oJS/y WI=>c&}e0o6HxM"ԕ]̓T9Vu 8mda[$og#~|>iv!v򉞦2o xAS}R".#9 PKUed},~oV j_K*]TTt ?LͬS~а&ps"ls}VB) ?#fJPď@QHkUO^;.U/d.ʦ9ˢjr'Vw+z Iw)vk:&!ehX{X?aИr;'Vmo!m C)Xtvw&OuvDس..:PɆwQQс(Pȝ5p4|۶iA/Yxdl6@+æ}HQ'xhjYA0;Q›`Wo)mudoỲQg3wM˲'!j&nDeաf ָ9y6m';7p#!|s+mL2jeYS=w5%p;m- W.ڮK߄u܀ NJFJyZ`{?_}^TwQQсȩ~ !A@p.`:{(!}PXM*fGua6ɀ{ђs'呀~J;ݴ=^$Vvbz8D۝;zApl62SzωfFo{pE{ vGB^&iRPcнt1s;+Z:-BUZ!n!%wzwyaՊ*>xvPI;ytVhۻT{CUȻ@mkkn1b>ou=8/'F'^@j*bx}n dޞuP5lQLO1 }qugM=r[8鞏%tذ䒧p}X=箤 Z;vJ o=ϏC`lj}ɃCtpa .&ovI|1;I,Kn3B֢A*,J }RAESẄ́9;qwwQQ5TUwɻ\PW"3;O4Rp`671sR@Z} p>H=/Zg}7~2S?a)= y 4pIJ5I-sB&LOa'vSF~37I:tSo @fJ>6X]̯ ,imFP;ܐK mCk>xq&+A|ԐᚪafYBBEEEP}zfHTr >~nf^{ξ5%*kx6D%s;.c%B̉ٺ?DHƺ|VoOZ cMZa'ZβcX-)NMV#BgMh[ .ڐc-;2/pf?p|W6@9{?E 8I0I{A6ɵׇINnW+WQT! U!YWpkGd-=ŕ%ZP|:ǼN- pp2>ՊeW;Ǹ>>zk,޼ϖ8qvwɜ*s GVhi0aOoMu3c+[ k-uh2ܽv'%guu)<[wFS!g-SJLD Obamw]BBEEE"gxLBpwK l=乩A NqriWssgZ\_{ ˿{`9۝;s6zsIw7Cb;.\5<F Vi3E3f$^VlsON:6[6dQ=\ŗoVa=x¶ u_k+Q8Of &Ld]rtmbg-X;jh6 ~ Pn4?UxnfНH}1}r[ƏM·4ʟ Ҍ2*=\zUEEyekID`"7V5",11O1@ƝmMD9` aK/O{c0TeI5X5KٙΫ~`-U |u/b+$yX}߄ꔼ8ae2 љ'oju 5)Ji[;V{ӼW|T=;*Iv{ϲ[(SI'^zCK»耵]PZR!Q<*?bVp#m68V`><ߴE8( 3 ޾J} `BޞQoz7Iќz3v!jJ0*~~z~v^&x^68j;H4#gXbje&}<*x4R o kЄo}I›p.s"#@;Geai{?go m2+_Tt5UrBEEE\}z<԰ pධŀ)P(dej׌]N/}(FW7Ѽ>ﲴIѝ,ñV=]g%}&'O:vwd j%vli =qY!fgAmʝecE .;]ۉ;s2gղ=v2aw2~Na.Xȕzs vw{vNQlN5JޫV.j9 :k$\1X\4vvgct6^)ef*y!FKynu+yqP6rRt p(,+jC1aУꨐwQQс~vᓰk֌twP)q qt>|=$YmeqGp:^0W 3w7,VlT&U}Sq>I'YWNkAW򞓨GSv^l{y39(0#_GYIMCZ[.&3YcfbͲIrkMfZ7aв${8䟇O΂E[2Rg uUy?c yo ygw3.gO fv됷bTL`&y'h$5y@o ]{+؍!-$PYA߫ݹO$4y=`w%W @(=U+7Rl-Nll+y{}&Dc틊O7THU$pX'<czUp9L٥3)!|v8ًN*=vx4#*3U텷4%r uzHN1듷i?1/|*6gw@Ss5z] Vr줂ݘg<'hxKEpT֍ϟ]qyoXZ$i/Ony lNTB\[$:7ג yLx ~h#XMP?v %p ?Φs10Ha|Bfu y:G MLƬOJZIc*=|dFf7mPb ڈdSܱLOUvbc>>r$]4Kte'ma·ծ%2߷ڧ_U8F;Ͻ$wO/=]SԾTȻTHY5y􏙂C6gƒyxħƝN aaEbak['E1 ֡nl^ vULcjͺ,G?;1s߿%mp65>-G8/& 94ʊ9Ugw@LC U|Du>Q:>QRO=yxzz Iks%K{&7seP=litH+_egXWςaNmu 2=IFNOyNQT! U!Tg%pvpg_F؁fœVoglu鴶8Iލ5~=&Ιt»x!p>ƸY &"1\!y5&I(@ebZ~b  pXs>.N ɻ݆ kCUfmmyNoCފc + om+3NNKH;S%I+]cP.**:@5sL9Bpd0oI=Tkc+Rdm]Ǧkj.%3sVYɻ63Ijg*(y#mI~&S L,ưςE ; :o[ ,t ޺awCrM*t{bʯ+Q tpv<بXe"C!ᬡB#k;Bi[?'_=h(шkBBEEEUD;"߲G *‡aTo;-c)$x-דB} vZ(-1Sx}%OH%][0ZK첊ex江k :4/ÇeK7[tSoşL K]3P.**: [ɖx-z8ÛcZ`ba+Tc]6UFK-/lz頩 5%S8>;>D~uv> a[d `+Ҍ≡4\18MG U|Q<96N qAI#[}9u܀qvWr%.| e}1BH`=}ohT{CUȻ՘PrsFȩpGp;gҒ^ OB/bK;t5߬³"CoV ;yEvV+\KJ|"0Wsaulɒ..[#V#5mV_veFw'3k¼F4om$0/nB}jWwQQс_[U[}ğ:g [̩0ooLͱIRC*}NFY۪.s[=|N S3Ľhv9&wkP2o}]n&.*3Ÿ#393U <ç9DP[zcd~8=`%mmceδynwqݧ? anL`~ˣP]FZЯ&}EEBBEEEEq``D@^#ȼ@hpkɑo==94:v\:p'f!iPbݿ?lbIu23ࣳ o2Z|lЋ^nSk|N63ς=*ɚN熷[ a=Wr.#6/!<<v=0 €Zv{8~Uuił:ԇə yo yڛ:Og-]Ŕ3g ݘ[gGǽQe'Yw( $ᝳc' YMXDû50d|ݵuEJ^ :}7q2 sY+lgx Ut~%$vƒּgm?cOȪ@SknzBEEEA}zt0og| W,¿ZfVzu*mgOBsBv'&IOmY7\,NLaъ]$鴪1޹ ,8̠ݵISSR;m*&Gα;'Vlޒ!4Q>6 yJeN1lU7ֳU#[ۯ ^aw[+zY_0F-*?yԾTȻRUgf765r+|\i(pNl^hhpUH<r\^nwHlxw^:2} v1v7Fu:б"um7{§!kHxtf:yy WT'//%#jjlgZgM6Iw8.%n$ߝ?Qt6sn +'Y$'dxw1O##V|sn>nb3+EdCUm@U 06?0G{|gF6n{]tP |pbaʬ 1vyվ]2d@m-;X 7wv˪I_46m;IyLxWhڤ{=;L4ۯN~f':k[[G YO72Z<QUs)**wQQW߿ o%Sး5w|rطo&pW8U*1s$Q9֓,tpN"1g#{YaFbscn2\71, %n=uo;$g,3G"j:ܱu"a/4?p&L2?^{wqEr0faUU?BBEEE׊U2!|*H9Ep_w4. ߆tʹGs&=!gr,j͘9Y8 s[mz|9y7x iveUbsqk&N໒H&2ʺ]s&jig1V+uΚ '|?k$pOL&m m|cTsbr'w/wZfݮEEWBBEEE׊p{Mf=Xo Y( {%,ܖT&' w0z#&$mUǶ)y'Y$gHY+VF&weu)Jt^[ ѷlvML{` &3u=ݑ^nxW:a="<ҊtRf m [ G[;E=ڗ!om@B&{ܝ +,AOo3.dxYT*佡*]TTt-iS\!14B%Ip4 s콮PCNe&mPYAc{ny'inn$zgΙەލ$5j2gX&i 3ׯCޝuqbٸnx0 {0l n9x\(wd ]!5~^a7Coܝ66WyP'nY-]R2^2M3 yo y]KÒ#ch26 m}bloSU~Ԙ#СPe,[NmAg5^G@dyMbUV[n%͆ 8by+KGzjx k>We~jș4Ҫjc|vv# Z{m̬CqPnmc>VeK7Jޭ\,aֱUf'%֘pW,dzޕ|k]Tt5]VTȻ ^ 0eZþ5sGbxO茅H >. ?wv>:^O wL3Wv$&oG`.q۱u*n/7]YB@D <֢5gSYV{TFGϳBpgn9ޛawcs=^3lG[vs0՘J yn8ޗY[E U{މJ\y헐jۢwAkIꥶtK o{%R6n>)!0wc80vp9eRA ;—B![AU{ XA bzs.{n _'z,Hbs<F&=Id"SL[e89L=1vbFFmp:m+WΰaMW{f&ycN. nwgIV{ d]JdCUȻPev@?,j2g _82 "\ѿہ5NyXm+@#&-L( N8M LaV;̰|^i<0wv UO;Ԫ[_;yK:a=퉈0|ϋN(|7PhVڤvNz8& B{cwns)\O coݘ7r-VH?UJɣ8N'-**佡*]TTtpX1:n;sfEwy)<1q&Q۔#s̵,PQYiWB:xp=lȮ: q$!o@4Y% mo[ tƞvToڸs[!y,N`?k?썿;l0"#NnE$>8fbwgtNz{aTOx]]YO Wn΢}wQQASU <>0 K;vBq'dMk۹c_p@'| w$흹WWF xR a F}Me 4gY% w4!xB|q_mܨ"Wm"|H&I!{R0G@eʨ;\(|{-بi7\UXHs:vqNW YC IDAT!--]TTBEEEQZ:).!_ej¿ 8|,í={p|8 ;o5P4)cb$;wϻw/Ɩ~,×$2aFm]Sfx\t2gǪM177NO||{WܧÛK( >1'OoeglNF#7a)?Jp:%/o0[D{]{CUȻ 1S_g`oK c }9-0Ue8R)BCQZo[4 ɻ3b;EG%A1oJ ݧ,7eAxnM/ 7(SouZűx#rIۉo^( {ǗRQ8oأ [lݳ1=ek?[|S_ 4,xKGyo܎w(ZWآP! U!(M12C-*,q5!P;ahcHԱ;y oą.VXȠ fCd3u+2YOv6،:02&#ULM\ՋXgb{ampzR^TYB&|,L#ߙGbt0SI7S@[GV[̢dl&xT\\vrLq yo yD5T θ<>i |n ;ZڞwO@w65ɻst慯|2ܬsopkd"zch0'# #䭵IИGE=^Rn7_PFgg|$GS ;Oygm${7u3۬KC6) UG_]3P.**: Ia_#bM _L>{̲ NX*=[,d:n./vVKX)T7srE|PM8L"CFc K1 c{`+|8wI238'HP7N92\L{tBjH!i+,o}>-jȹ2hX޶A9^^wQQAT'~n.012_Klm9<My3`f0OЧ"ٜy7CC8vY~}hh63~εfüdT p^gmLR=JZt~u]Ml{/˙y<WΠwXujO9a>xcn}r *^{!֎ݐ~t2jםx>fu9ϧjg ?ߖ yD)!`vL)plG 0;aղ^ ŏ;/Г`w1 $ۺ}lj*/;iP v{v։_V[e FJގeV[= v|^@S;X<3֔3/l9D%1>G oH4[g6k,RǗxi'ƞgw&R)mF_7) [_7*]TTtosP0A|/Í{(X F0ç?? >'=Hhɻ5^%at-(VT4 ۘ=19daxp:yV"s#%3M'{cCZ,7ʺ"ev36Dk:0?HGdkbcЪuHhy#'VrW$Ukje2ABe7m4"$~;WUSTT BEEEWPcx|*8X p16su`r02H I{($8`/,볳F~jW[$,BBS<,k:ߗ!v^)j W-3n!nLMP=` ln4B޽q )'2C(C/\H/Ïfy3[Ma V:Gڝ6ȎjmeV7wQQT߿'CC|10[ ]>٪]V _ #|##owmZĭsSTozs;)[ޙyMOhFNг'=٫/)<hl@Lb^Zy]\M$wQꐡG {ͩÍ}FYWۼC܆U#h fZc"+6n.͞3u7dgOswQQAV߿΃l0!Ti`рrWP8oNz,'o_S2 F+܉7ƙV4-|>oh[kŨ?PxO/ĄhxO5!n8b W3ظ S1=&E݄^<~5'Lû\Dn7ñ^xX$O..ٮmpp#?W~M/BEEE_}ڪz 8F+GKV04 zݧ…0ÌG~; Re[vq@YޝL ݛǬ aYo/kĶ+3;aħop|IjyL% ^⧿۞Kb Ni{+vOPA֝)Uux.'u|艑q<.*: h~sRtfIn6oWqUoXT]~ޯ]MW?^tUu,***:0Uy;v!Ѿn 7oHxaaw`hNH1' ;sRWCa'47Z7E䎒]~*\?԰`I㎒ m 2%#Pq .f5ꋬ;a y;I$0q| a lx?iL# eҾ5wZQz%Yr{vnTP{lSKreoE u[ѧ=w8Kw}Cy+~g)*wQQѵmU >FX6xs(sm` }C>v]a3~wY2/\ynbÒISS+{ď  3~=GP=>I_ǰfdNzR J&ħ6pϸ/Sn^8]vT3 Uqêͬ93%%_heZɡÜ^|\Nۊի^΀}z?w9w9o|W?~#NڸfT!kQ} d-kep;q1:Kh_`ӚFB ENQjR7-4x N?I]dhFGjbIJjW[I4pnvq1 ^}OyV'im%lL"tϸoNk:io׬~ xiQ<@If^q皑Vrz?\(ML7EWWyb'?O~ /*}UȻVPq݈JUì\>b%v2\fpf#[ IN»,+!Q%`3WIF#vM.6Sc݃̾m%C2Zc\X&[ibN-]6Qۙ;φ7ڔ\Xy'|r,(׺]\p/ `f> )U^)a(x>׻Eo<.µӌ}imm{ɿrW++W!kW}z-T;|}%;.#P6>t;,C{3m ykZIlu^FK5<jS,b16H5'f (wvdK7uu^}&'o% 41'xo[X}U%jW߇}&.:*]TTth v[?Ł%F`} 桅Cl[Qpۛx~g{!z-@(Mlx7ݿ̂]%oGÓcr^~dmh`z.k=Yo?v=o~RTTLu[|h.9c5h/lK̜_Uy*oӣ'ф[_^\ywRdCsn,j20ΥZ`Qy;MYTc)Α:=&0`:s΁իVBU\o p#`!.ixm\Ȼa=!o:U>Vnnw+7fO}Y***U!FaĨ_S#͍].[٫˲X; *\Ew?FNyiȕ8ОXflS v%1ob#/ Eg7⸫=GαJﰟ9)%?>OϝI}rCΗfkY!`㏮N1Č!~l U[r%Ԙb1$zs=W1*"_G~fݝ#gJfUPdfb]wkfİيڇzN= x=6_l!x>ڔ\%ou^ ao8 oЀs훼QVO.J%CU?{[huzKTv]7W!FV20tklMPN8)ʻ _~]r!$gXtf 2F3M[ò7ߛ ,m\K " [΍Ml͏Eo9D#!PM_}`qO'p#|(k;w$Խϖ<&{86ɧV4p lm𧶽>fy 0 U?k?u0ٳsݻlڼx5뿍 y]7dz 5|3 mpgcn3{5jy'xWJYL0sƾVK33U&5=p'X ~hc&ve!^N z=,^DQJgn}0oaЮ z#a_ |ǰV\#W|lg:86ۡ Zo^"#6}YwQQѺ:#›O#榶qR!B}z*LU`n ߲m[Mee~$n޽D22΃TQ-)HFnu vNݵsH_>--s#Ͱ\Pycֱq,>n4T vp%0 @;;aզfЫ?egz!eX'.n>_$nҒYZFJ'lPU'̺աgN{I37glZV<*]TTt)E60X&  ƻ0aH(?ި7yԤ82q{g^Vk fB߉K_ǶT ,\s.||,#o7e},_s޵!{ҫ]r؉#:P%:e ߇rm53J&.Yz'Jw_tRHB&ufg+[]A'o~{?So?Cthq.**o]eW,C"lp-C3ɜ$:;O< vYwOjSm@v mj`P^e//-33*l>dCCྻA]<tlYWtqnC=44 p(g=l$$oϴlx`4"9v'?Uh O~&3hǶ^mڰ)i>(*:'~+zr;|ǻ>O|X\\xi)zz'Ca+lE[>wpS `33lm65m!H8I@k(y̚]xx+k0wZ%DZ0i佮99?'خj>zwI=@Q62vN:3YΦ)g~ό{`Kڠ#֞rp\O΂zgGit٦p.Y IDAT Sed瞷ں ^PT'}Coj~a\58a^2+0F٬_/G84Oz&6}L`w g*|ns`:93\#I@?xqNxFhEskpVc]i=]cnaNB#.ںu+>|CO/kzǎm>J}]TTtGb, bM< mV86P لCY%jV%vfNgYI9A٫ɚsYs,[&$L^̜޻G[vu\ BP1Ѐ64i\ HC{t~JjM rS@[6m!L$ Usۗ}εRɮs~X9{=Z³50rj 6WQp-p? {`om۾yڧe^ kk76Cpmd977.Mgj߼CkpNT,x:w>$!ʦ…߾YMǀ{ kezhίxBjB9 &4N zj=.ë#^ɠv6}pB10=k@6Mc<0lk]n쨷3iVZ.ڗ; `L~MVۤ[~S Vˮo?dB973ݎ9W;mGU}^m}r0͍޼n/0}$XQaխrv?9c3sq,|6V !v.2o!\})V|7MXy{O=l{ ࣵM`ʇmwr]nڞv|XҶT+vSl9ϯ%ZRe2:_h&,9xzǒK@0|'-D3j u__F x&!ٴ(6qnv=lZ}SBdBy;R W:xPEư D:5NS#VyoB: )8y73Ѽ7Yuu?&zmo0(\ x-BqطB{jHC@+~ 2iI=vY;w,\u>p}Sr.p%M};'=>$ԡ|@r83'>jVGUfY@ٳy7p < x&SUna⺘p)Jb!B2r@y3`f)_{EV.\pcѼ65ϵ nCmubE {wfU;qc`dg /*'VA~m(.#:)`~{b_ttZۯy߮.{ 7ZnM1"4l×3eO/ٞXۘM])5k_"BzrTJe/MԆa`4.OjهWS2|{OcWUXgw" {. b~ pӳXpq 7fwg:|~S]nKdG'a: /xF-=9e[{̆;Q#9pW;Z5.\\ŸpO6I۶Nٻa{)ZWB B(D:Xd)vz5w"C&|xX nvJi-Se5Bcx |vs~aS`w[h{I'ӭ(ioҩUvO(\8U., B?SgY~ +v}nSV߂) X5nG7וx<ZJX4`M])xڒ}I9_y2o!xᯭT4PkS>> {]M|iJ-5MRQs&3{Rw)Oƾ~ Aֆ WAvgmu;ϹYc͹oñ ,3yn߱Q7a ~8jWBɋ6hNNxS(b"B&:2-kОBS.zs1%>1ۘwѼ'vvwvj?5gC= UeyCj[Hx|*ܮL˄YCC2 c{Ú{6ذ;ݹ#V_bߐvAjA9Ox#癓򥵞3Ә(aνh[cs@!@ /jZO^[sp[ͺr\\8{)~7dQb;5; O#KG}7gEM:G'Dwey7fލڼ {lKC`.nvS?yw,i&t})!Ʒ/JoJpݯh+[Nr o_ G-L2 E63OqfL(~+fKOͼUbG#B&6^ksu1=VUnC%OGgXބoGqs{+-&Ig >%@){oY(5|ΐZ+%w&m+ٕ_67G[؆d~<[)x?V#krv\R(C[ܞ~9H\3.w+c15R-!h}>xpR|;o5;bRzYpQ(ľ'Y_t-K!K}z}kcNS3΍#Awy යRކHg޹.{`eNB]'jj]~y^r;P_]lKp.<{-;&rφUx5,/w&py;Ad[O &|{7t.ee`"S듳w0dvb Sfьc[y Ñy !v ΃)) ZL詰d4ZW̜LC{f--Z9;X G=Ckdz1=k]vS{["Nڋ?35{Z\>3M]ꨉq;j.Kv42u%شaڦg qO 7íp6dXh)3d{κ3llAx^ثCpMf&;b)]q [q1uεY:fjMg.yo ~oi=%hٍm-ܾ!3]y݋6QdBuDɆ:5'*)3w;heo&aa([2SgJT-τϓ zJMW +߃ۖ!Z%ߋp3a۟l <VmwX$ȿ_˅;bg'S ưO?p08tղ)Zqveh`9=\a?KUݓ\v[`,!2 iԅڼXPgÇbQa-PmjlO٦ݡsJgr(z wXӁp*|8kp+eX/JbwK@Ko`VY7<+<_i5߀Oǽs){v y ޿-i;br :) ^{7gޝ-b(|7>\;as({lM0~wy{Ddn:%GS-b qW <.??!q(1p| f^|upֿ\яrdQeb฿!>weo;s+Xkg|_Gn I|[Ҕk:"e7Kȼ;3WX/1g:8ߦv \nyoºϺ/HA>$}5+M$yu>Nyw&KV)C0g_ QzzLM]p#េ\ǧal; |.E(UM+KErvu8lsq;bgr h3?v4ec{4cwS/v)g{ia|+Dt\ 8&\ղ]g6gz&z?&|2; mPy !v".=Gku%)JƵw= [-:&SV# 4w MW_huwRԋhwFo wLYy8dϿ+kx6#7ygz9NѤ>9yMX9̽p5F힆v4o !cͻlwe򻸒y2GNL_?n &Yes< 9b BPr~WJFl5.S[4w4"73 ?nsʻ%TEx|.-AXFϗ|0<9KL_m#`oh~ ΂?c~=wRn]D,ӆԐN/Sh0 ;/x\q9ڍi},{o%u: -gz"Mȼ;5kp>< ñP[b e^moޣ n905 Virɗ9u"PK\ D0W !\-iP`OC=Úx 1w#޻IGd=t]R RF+0&\x6t OF6aqaM8M-N)i[s~To ̉cu+!rFc:iIje6Xx dx&|'&mڱ uÖ.aVmPm]mֹt^ ޸> rl'7yŚ%x|Q6 S;~E@)=߂qXM-ޮ !x49݄w>Bݍ[9gA K?Ӻa 4 )W{?x"lG-< >n60^;gOC3:qN_zNj/%} cބzy83A4h׼K>|a*x<{md<xc#tcγ(|WNöR+>ӣ&o>7B[@J?XO)]6nKuwejQ1upwía6޳`hj9^gIwjuXb1,z Gwy؍Wu 1rؗM\2.V]'~$||r}~O=cpIXDS4$b̂w2mlzv1ܼU}!v2o!Į z<2WbW!w?zDygf o+g'gneO?98ȖO_ z<.gޛgKG,C>~A*nї.-PthI({?CvgQ7ciCR kR@F0lwzإɦs&BX-ο$!)E--D?Y۷!,Y Zro++?almGKvSuba#1ךE;ގcXoVhޱB.aRmv\c;/ OkS$ۉbGC0cg~&&j  v^q8$׃y7;7X0_ŧ߆N`+"8-y !v"j=>oU79)tEd_h"<=Sӥy8 D^R[|}= iG*1.`e_ 4v Wl}ʋ~oy`Ғ4o̡׬ab܃+M]'^ְa g?vpQcQXSG֗~Nb"B ܼfB3&CcZyU_XGnfv![ၶ>w3`;>Ak96l(߃Y9m)E:v.ti[|lw;7` K[s/~MmGp.;Y,a-W0Py;Ob#BrTJW8$ ngl 9Mνvi+_{N[ 1k'1oe\מyQXOC7 nJ/:& ۘwN׼χZ0:ޓ{leRn̞y# ?Eˡ{`=ݼ7-9*.IDAT4wLyofHHDm9!B][[":˰ A Un/U~*7c̔}vomàmޓnb5g Qğe[l Q] kc.eWW;׃_pUڼ ux- -vec;'/%hp,]&qkݘy˞=F_verV|s~= إȼߗKIJmŧD!@ &N)~mw7o:/ߓ~`H̄Xͻ^0]NULB$YA47f^,盋6W6X{{^cU^R%Lɰ'\SB8~ a ܽ!:, MZ$ďGI_g 2o!.bl,:1Z?3U͚G7i8A\c^gɰ6}]?`[ŽyGnVvDi>6x!wK~}]xsCad<^#Xׅ%^SƐ;alimβ}5iiYM [9H鹰gޝ&Ӝ$?Bbא{Sz),ĪN,Q36i ܭ$y/C7/*ed'yǂGA\ܷ,m+7=2C(6X+z; JBKO{r89- #p<1;tb{[)]3OmοfŏlXuW !N+dBZN 3ߑoLѼ}-b$s5oVD͖7)l@Pު&,kzO0Yn_| kS%1=k޹V[_+䬰 aRטaEr {`Cc}[K#ߝj)w /lbzpJB̛3ڼ9kMG-Ď%2زf% X2v^4h?ۘwaϼ æiu 5vܚA&nCc6ep)40[SjUX=εsw~3;Mp=/ib~0M;eyK~f!?՝΃B̕3׼(gszzڍuJtƓaY(۳>HWp!ư^\Snku+'tk֮|0 goBCP~S|){ !v9#W c 3,cps+=s):1lIW5؂.3vwO wBd`Z<e*oK2w}sUj߀7u;c)-\ ~`{&|uUl|d>ZBq_ps9vx[JoRzAhsj2'`Hw zO>1|cz`/l=.[c;3+c'12Ӽma{ ?Aϼ ;ە-ێmLݑ Y[!v4gyϦR^[c߾S=!ĽCKڼpP(5 čڊ|oK'ijl;P;evG6F1cyh)o\NslnkVm+Fm}6y;H!KDg-Į%xc`v- dWFJ'\] $ !iϼU KBJZVSBS.oцy #씺۞ycևCӵ{VH*?֐ݍ%gy%;{nj"n$珤RX%x H8:|Zs?:tU?bsN/ Bz!|!fNfvjރ@f8|nܙ^v>fYѿGuѽнyB3Ѽ)` 0E c8u{&r*tg½AS0(8ywEvςozAX{ثyy ?(Ӱ$u[<ͽ=ja֛ |d?;B1dBӄ?eֲY9/BX5S.g[>曭H?X&؈a֏f%PMXWǙDZx'Mz'ġ7+[hϷvn{jq†-9 G OC@JpIHοy!2o!CH*Xe=UuusV_S{˃UGԁf2y|yj·}0mIMbhSBn*c" /s`vnoͬ٥|oyo+0N?Ek.*ZK=m'B9#BVlu쩣u2 Xuc 87=':d҄w'm{|?)~-=OZ&:v^{; } XLNؤ߅t#緦tL?p{ͻ ށ݆Hk:3j2s'm8j'0e2= ߺg/B[qZ2 n̗BxSvuR''P8;DO[̽mلceqԻ5ٜ)x|{`i(߄[j@?pO/Bȼ%[Zv B{*߅98wLkf9ys2=_9}v xۗi뀇; B1oV*wBA-8-Y7 |sܲw*`nܱ`3æ ,cK֍;+{QM`6^܏nn!/|ww`H!G-8 )] #8RK/ޅI-wȞ\dt")Ӆͻ,X  &mf{l]M:G?8/t0c9侻vBq#B, 2 Zmo yz"r]2&Z_um}LNmǐ^8ބ:w-39; +۰PecuO !io~b7Fs;iiѿ#wd# =w&#z-[#s'^}lڡ4n[;!߸.BdBӕ?%`lz0O!XC`lxٸ ]joͿ'6~- 3&XJGlz43jG_q^ !FY^Tt%Ê5[EX01ƾSϿc#acp쁏_Vnqݭ?S7̄w܉W`^j-By+!jBә?eN8v7u`pVyf K+84nfw2 úͬI=QG5rOooQX;WE!TT.-LJχUX¹v9jkN&a!&1=50['V3oyAl>wM8z%F5o!k/u*ڹc~u*`ԥ^^gzb%&c-[ӏ}AK!C-8(`Xr3nvf[>yǠHea ~67ͼ'-2{^iYNrMwfg'\! 6qNem[`0t~ҫ`U_ w๰W˶YNn I'՞3[_MZd7gyzz'1-.GwdzgҋSUX? 0셽bl-|lecƼv(_:c|WY*=oB*dΩ7oLBt-샽xws:Sݡ}1-%is;aLc޷>gxk9$/Na0moiǂpR&}"&͋ai?/j·[~ҷ͓n 珕)=}O~B!NTDӓoH3JpnLduV)uqf?su{B$2 Bg ]M(xǙx y6Z2Ae5XVB3ީe)6U*{ qB/غe )mPٖ>r6_v499#B!jާ'7 ;so!ĽQy'xޅPnΛ0]rÞ? Fr譅L$ AM"smo$s7׿/7C42o!#R ,pdؑo/~{E|navYr[9lB!#p|~Aeo!Kr~WJ/5΀)xWl̼KȤBplJiB)W;$Leٌxv-|Hp.V`)T{]J7V$%}; 0)T ZF!(T LJLK8w)ugɹ9(a&<˼-Iki@ycΟH!> jS[!@dѶU} ĩddA tlPn,g 9˷۶֏B3M"'6٦}|t 8դt)`'ڼKͻ @7kxd%@9$b!󎜸y¶bҋ,z/X{Xv'[=g |>!;M"nɷl[ӑߓU)̻S.+\ͼBF5H~";s~)B]̻ B`޹dJ!n"H\[E)= ʃ-͛Bqȼ$RB]DJχO@BJd3| !B{8LN~B!wl$B!E-o!Bq/">o!Bqo! $B!^A}HB!ɳp;(SA!BqP? 4lp+HB!Bi|@z(B!⮑y cVS<!Byo!BqOy &|;p"B!]">noB!F]OMPx)B!- gLn$KgO!B-dB!B弅B!2o!B![!By B!bȼB!2o!B![!By B!bȼB!2o!B![!By B!bȼB!2o!B![!By B!bȼB!2o!B![!By B!bȼB!2o!B![!By B!bȼB!2o!B![!By B!bȼB!2o!B![!By B!bȼB!2o!B![!By B!bȼB!2o!B![!By B!bȼB!2o!B![!By B!bȼB!2o!B![!By B!bȼB!2o!B![!By B!bȼB!2o!B![!By B!bȼB!2o!B![!By B!bȼB!2o!B![!By B!bȼB!2o!B![!By B!bȼB!2o!B![!By B!b4|FIENDB`trunk-2018.02b/examples/jointedCohesiveFrictionalPM/identifBis.py000066400000000000000000000152471324306050200250410ustar00rootroot00000000000000# -*- coding: utf-8 -*- # ---- Script to detect spheres which are "onJoint", according to JCFpm. ----- # To be called directly within an other script, for example, with execfile('identifBis.py') # The sample (spheres + facets) has to exist already, with their JCFpmMat ### engines definition, according to our only goal that is to detect spheres concerned by joint surfaces O.engines=[ InsertionSortCollider([Bo1_Sphere_Aabb(),Bo1_Facet_Aabb()],verletDist=0), #verletDist=0 to avoid introducing NewtonIntegrator in engines list InteractionLoop( [Ig2_Sphere_Sphere_ScGeom(),Ig2_Facet_Sphere_ScGeom()], [Ip2_JCFpmMat_JCFpmMat_JCFpmPhys(cohesiveTresholdIteration=1,label='interactionPhys')], [Law2_ScGeom_JCFpmPhys_JointedCohesiveFrictionalPM(smoothJoint=True,label='interactionLaw')] ) ] O.dt=1 # whatever value is ok ############################ Identification spheres on joint #### color set for particles on joint jointcolor1=(0,1,0) jointcolor2=(1,0,0) jointcolor3=(0,0,1) jointcolor4=(1,1,1) jointcolor5=(0,0,0) O.step(); # one step to know interactions #### first step-> find spheres on facet for i in O.interactions: ##if not i.isReal : continue ### Rk: facet are only stored in id1 if isinstance(O.bodies[i.id1].shape,Facet) and isinstance(O.bodies[i.id2].shape,Sphere): vertices=O.bodies[i.id1].shape.vertices normalRef=vertices[0].cross(vertices[1]) # defines the normal to the facet normalRef nRef=normalRef/(normalRef.norm()) ## normalizes normalRef normalFacetSphere=i.geom.normal # geom.normal is oriented from id1 to id2 -> normalFacetSphere from facet (i.id1) to sphere (i.id2) if O.bodies[i.id2].state.onJoint==False : ## particles has not yet been identified as belonging to a joint plane O.bodies[i.id2].state.onJoint=True O.bodies[i.id2].state.joint=1 O.bodies[i.id2].shape.color=jointcolor1 if nRef.dot(normalFacetSphere)>=0 : O.bodies[i.id2].state.jointNormal1=nRef elif nRef.dot(normalFacetSphere)<0 : O.bodies[i.id2].state.jointNormal1=-nRef elif O.bodies[i.id2].state.onJoint==True : ## particles has already been identified as belonging to, at least, 1 facet if O.bodies[i.id2].state.joint==1 and ((O.bodies[i.id2].state.jointNormal1.cross(nRef)).norm()>0.05) : ## particles has already been identified as belonging to only 1 facet O.bodies[i.id2].state.joint=2 O.bodies[i.id2].shape.color=jointcolor2 if nRef.dot(normalFacetSphere)>=0 : O.bodies[i.id2].state.jointNormal2=nRef elif nRef.dot(normalFacetSphere)<0 : O.bodies[i.id2].state.jointNormal2=-nRef elif O.bodies[i.id2].state.joint==2 and ((O.bodies[i.id2].state.jointNormal1.cross(nRef)).norm()>0.05) and ((O.bodies[i.id2].state.jointNormal2.cross(nRef)).norm()>0.05): ## particles has already been identified as belonging to more than 1 facet O.bodies[i.id2].state.joint=3 O.bodies[i.id2].shape.color=jointcolor3 if nRef.dot(normalFacetSphere)>=0 : O.bodies[i.id2].state.jointNormal3=nRef elif nRef.dot(normalFacetSphere)<0 : O.bodies[i.id2].state.jointNormal3=-nRef elif O.bodies[i.id2].state.joint==3 and ((O.bodies[i.id2].state.jointNormal1.cross(nRef)).norm()>0.05) and ((O.bodies[i.id2].state.jointNormal2.cross(nRef)).norm()>0.05) and ((O.bodies[i.id2].state.jointNormal3.cross(nRef)).norm()>0.05): O.bodies[i.id2].state.joint=4 O.bodies[i.id2].shape.color=jointcolor5 #### second step -> find spheres interacting with spheres on facet (could be executed in the same timestep as step 1?) for j in O.interactions: #if not i.isReal : continue ## Rk: facet are only stored in id1 if isinstance(O.bodies[j.id1].shape,Facet) and isinstance(O.bodies[j.id2].shape,Sphere): vertices=O.bodies[j.id1].shape.vertices normalRef=vertices[0].cross(vertices[1]) # defines the normal to the facet normalRef nRef=normalRef/(normalRef.norm()) ## normalizes normalRef if ((O.bodies[j.id2].state.jointNormal1.cross(nRef)).norm()<0.05) : jointNormalRef=O.bodies[j.id2].state.jointNormal1 elif ((O.bodies[j.id2].state.jointNormal2.cross(nRef)).norm()<0.05) : jointNormalRef=O.bodies[j.id2].state.jointNormal2 elif ((O.bodies[j.id2].state.jointNormal3.cross(nRef)).norm()<0.05) : jointNormalRef=O.bodies[j.id2].state.jointNormal3 else : continue facetCenter=O.bodies[j.id1].state.pos #### seek for each sphere interacting with the identified sphere j.id2 for n in O.interactions.withBody(j.id2) : if isinstance(O.bodies[n.id1].shape,Sphere) and isinstance(O.bodies[n.id2].shape,Sphere): if j.id2==n.id1: # the sphere that was detected on facet (that is, j.id2) is id1 of interaction n sphOnF=n.id1 othSph=n.id2 elif j.id2==n.id2: # here, this sphere that was detected on facet (that is, j.id2) is id2 of interaction n sphOnF=n.id2 othSph=n.id1 facetSphereDir=(O.bodies[othSph].state.pos-facetCenter) if O.bodies[othSph].state.onJoint==True : if O.bodies[othSph].state.joint==3 and ((O.bodies[othSph].state.jointNormal1.cross(jointNormalRef)).norm()>0.05) and ((O.bodies[othSph].state.jointNormal2.cross(jointNormalRef)).norm()>0.05) and ((O.bodies[othSph].state.jointNormal3.cross(jointNormalRef)).norm()>0.05): O.bodies[othSph].state.joint=4 O.bodies[othSph].shape.color=jointcolor5 elif O.bodies[othSph].state.joint==2 and ((O.bodies[othSph].state.jointNormal1.cross(jointNormalRef)).norm()>0.05) and ((O.bodies[othSph].state.jointNormal2.cross(jointNormalRef)).norm()>0.05): O.bodies[othSph].state.joint=3 if facetSphereDir.dot(jointNormalRef)>=0: O.bodies[othSph].state.jointNormal3=jointNormalRef elif facetSphereDir.dot(jointNormalRef)<0: O.bodies[othSph].state.jointNormal3=-jointNormalRef elif O.bodies[othSph].state.joint==1 and ((O.bodies[othSph].state.jointNormal1.cross(jointNormalRef)).norm()>0.05) : O.bodies[othSph].state.joint=2 if facetSphereDir.dot(jointNormalRef)>=0: O.bodies[othSph].state.jointNormal2=jointNormalRef elif facetSphereDir.dot(jointNormalRef)<0: O.bodies[othSph].state.jointNormal2=-jointNormalRef elif O.bodies[othSph].state.onJoint==False : O.bodies[othSph].state.onJoint=True O.bodies[othSph].state.joint=1 O.bodies[othSph].shape.color=jointcolor4 if facetSphereDir.dot(jointNormalRef)>=0: O.bodies[othSph].state.jointNormal1=jointNormalRef elif facetSphereDir.dot(jointNormalRef)<0: O.bodies[othSph].state.jointNormal1=-jointNormalRef ##### to delete facets for b in O.bodies: if isinstance(b.shape,Facet): O.bodies.erase(b.id) O.resetTime() O.interactions.clear() print '\nIdentificationSpheresOnJoint executed ! Spheres onJoint (and so on...) detected, facets deleted, simulation may go on.\n\n' trunk-2018.02b/examples/jointedCohesiveFrictionalPM/identificationSpheresOnJoint.py000066400000000000000000000225721324306050200306040ustar00rootroot00000000000000# -*- coding: utf-8 -*- from yade import pack, utils, ymport, export packing='parallellepiped_10' DFN='persistentPlane30Deg' ############################ material definition facetMat = O.materials.append(JCFpmMat(type=0,young=1,frictionAngle=radians(1),poisson=0.4,density=1)) def sphereMat(): return JCFpmMat(type=1,young=1,frictionAngle=radians(1),density=1,poisson=1,tensileStrength=1e6,cohesion=1e6,jointNormalStiffness=1,jointShearStiffness=1,jointTensileStrength=1e6,jointCohesion=1e6,jointFrictionAngle=1) ############################ Import of the sphere assembly O.bodies.append(ymport.text(packing+'.spheres',scale=1,shift=Vector3(0,0,0),material=sphereMat)) #(-3,-4,-8) #### some preprocessing (not mandatory) dim=utils.aabbExtrema() xinf=dim[0][0] xsup=dim[1][0] yinf=dim[0][1] ysup=dim[1][1] zinf=dim[0][2] zsup=dim[1][2] R=0 Rmax=0 numSpheres=0. for o in O.bodies: if isinstance(o.shape,Sphere): o.shape.color=(0,0,1) numSpheres+=1 R+=o.shape.radius if o.shape.radius>Rmax: Rmax=o.shape.radius else : o.shape.color=(0,0,0) Rmean=R/numSpheres print 'number of spheres=', numSpheres, ' | Rmean=', Rmean, ' | dim=', dim ############################ import stl file O.bodies.append(ymport.stl(DFN+'.stl',color=(0.9,0.9,0.9),wire=False,material=facetMat)) ############################ engines definition interactionRadius=1.; O.engines=[ ForceResetter(), InsertionSortCollider([Bo1_Sphere_Aabb(aabbEnlargeFactor=interactionRadius,label='is2aabb'),Bo1_Facet_Aabb()]), InteractionLoop( [Ig2_Sphere_Sphere_ScGeom(interactionDetectionFactor=interactionRadius,label='ss2d3dg'),Ig2_Facet_Sphere_ScGeom()], [Ip2_JCFpmMat_JCFpmMat_JCFpmPhys(cohesiveTresholdIteration=1,label='interactionPhys')], [Law2_ScGeom_JCFpmPhys_JointedCohesiveFrictionalPM(smoothJoint=True,label='interactionLaw')] ), NewtonIntegrator(damping=1) ] ############################ timestep + opening yade windows O.dt=0.001*utils.PWaveTimeStep() from yade import qt v=qt.Controller() v=qt.View() ############################ Identification spheres on joint #### color set for particles on joint jointcolor1=(0,1,0) jointcolor2=(1,0,0) jointcolor3=(0,0,1) jointcolor4=(1,1,1) jointcolor5=(0,0,0) #### first step-> find spheres on facet O.step(); for i in O.interactions: ##if not i.isReal : continue if isinstance(O.bodies[i.id1].shape,Facet) and isinstance(O.bodies[i.id2].shape,Sphere): vertices=O.bodies[i.id1].shape.vertices normalRef=vertices[0].cross(vertices[1]) # defines the normal to the facet normalRef nRef=normalRef/(normalRef.norm()) ## normalizes normalRef normalFacetSphere=i.geom.normal # geom.normal is oriented from id1 to id2 -> normalFacetSphere from facet (i.id1) to sphere (i.id2) if O.bodies[i.id2].state.onJoint==False : ## particles has not yet been identified as belonging to a joint plane O.bodies[i.id2].state.onJoint=True O.bodies[i.id2].state.joint=1 O.bodies[i.id2].shape.color=jointcolor1 if nRef.dot(normalFacetSphere)>=0 : O.bodies[i.id2].state.jointNormal1=nRef elif nRef.dot(normalFacetSphere)<0 : O.bodies[i.id2].state.jointNormal1=-nRef elif O.bodies[i.id2].state.onJoint==True : ## particles has already been identified as belonging to, at least, 1 facet if O.bodies[i.id2].state.joint==1 and ((O.bodies[i.id2].state.jointNormal1.cross(nRef)).norm()>0.05) : ## particles has already been identified as belonging to only 1 facet O.bodies[i.id2].state.joint=2 O.bodies[i.id2].shape.color=jointcolor2 if nRef.dot(normalFacetSphere)>=0 : O.bodies[i.id2].state.jointNormal2=nRef elif nRef.dot(normalFacetSphere)<0 : O.bodies[i.id2].state.jointNormal2=-nRef elif O.bodies[i.id2].state.joint==2 and ((O.bodies[i.id2].state.jointNormal1.cross(nRef)).norm()>0.05) and ((O.bodies[i.id2].state.jointNormal2.cross(nRef)).norm()>0.05): ## particles has already been identified as belonging to more than 1 facet O.bodies[i.id2].state.joint=3 O.bodies[i.id2].shape.color=jointcolor3 if nRef.dot(normalFacetSphere)>=0 : O.bodies[i.id2].state.jointNormal3=nRef elif nRef.dot(normalFacetSphere)<0 : O.bodies[i.id2].state.jointNormal3=-nRef elif O.bodies[i.id2].state.joint==3 and ((O.bodies[i.id2].state.jointNormal1.cross(nRef)).norm()>0.05) and ((O.bodies[i.id2].state.jointNormal2.cross(nRef)).norm()>0.05) and ((O.bodies[i.id2].state.jointNormal3.cross(nRef)).norm()>0.05): O.bodies[i.id2].state.joint=4 O.bodies[i.id2].shape.color=jointcolor5 #### second step -> find spheres interacting with spheres on facet (could be executed in the same timestep as step 1?) for j in O.interactions: #if not i.isReal : continue if isinstance(O.bodies[j.id1].shape,Facet) and isinstance(O.bodies[j.id2].shape,Sphere): vertices=O.bodies[j.id1].shape.vertices normalRef=vertices[0].cross(vertices[1]) # defines the normal to the facet normalRef nRef=normalRef/(normalRef.norm()) ## normalizes normalRef if ((O.bodies[j.id2].state.jointNormal1.cross(nRef)).norm()<0.05) : jointNormalRef=O.bodies[j.id2].state.jointNormal1 elif ((O.bodies[j.id2].state.jointNormal2.cross(nRef)).norm()<0.05) : jointNormalRef=O.bodies[j.id2].state.jointNormal2 elif ((O.bodies[j.id2].state.jointNormal3.cross(nRef)).norm()<0.05) : jointNormalRef=O.bodies[j.id2].state.jointNormal3 else : continue facetCenter=O.bodies[j.id1].state.pos #### seek for each sphere interacting with the identified sphere j.id2 for n in O.interactions.withBody(j.id2) : if n.id1==j.id2 and isinstance(O.bodies[n.id2].shape,Sphere): facetSphereDir=(O.bodies[n.id2].state.pos-facetCenter) if O.bodies[n.id2].state.onJoint==True : if O.bodies[n.id2].state.joint==3 and ((O.bodies[n.id2].state.jointNormal1.cross(jointNormalRef)).norm()>0.05) and ((O.bodies[n.id2].state.jointNormal2.cross(jointNormalRef)).norm()>0.05) and ((O.bodies[n.id2].state.jointNormal3.cross(jointNormalRef)).norm()>0.05): O.bodies[n.id2].state.joint=4 O.bodies[n.id2].shape.color=jointcolor5 elif O.bodies[n.id2].state.joint==2 and ((O.bodies[n.id2].state.jointNormal1.cross(jointNormalRef)).norm()>0.05) and ((O.bodies[n.id2].state.jointNormal2.cross(jointNormalRef)).norm()>0.05): O.bodies[n.id2].state.joint=3 if facetSphereDir.dot(jointNormalRef)>=0: O.bodies[n.id2].state.jointNormal3=jointNormalRef elif facetSphereDir.dot(jointNormalRef)<0: O.bodies[n.id2].state.jointNormal3=-jointNormalRef elif O.bodies[n.id2].state.joint==1 and ((O.bodies[n.id2].state.jointNormal1.cross(jointNormalRef)).norm()>0.05) : O.bodies[n.id2].state.joint=2 if facetSphereDir.dot(jointNormalRef)>=0: O.bodies[n.id2].state.jointNormal2=jointNormalRef elif facetSphereDir.dot(jointNormalRef)<0: O.bodies[n.id2].state.jointNormal2=-jointNormalRef elif O.bodies[n.id2].state.onJoint==False : O.bodies[n.id2].state.onJoint=True O.bodies[n.id2].state.joint=1 O.bodies[n.id2].shape.color=jointcolor4 if facetSphereDir.dot(jointNormalRef)>=0: O.bodies[n.id2].state.jointNormal1=jointNormalRef elif facetSphereDir.dot(jointNormalRef)<0: O.bodies[n.id2].state.jointNormal1=-jointNormalRef elif n.id2==j.id2 and isinstance(O.bodies[n.id1].shape,Sphere): facetSphereDir=(O.bodies[n.id1].state.pos-facetCenter) if O.bodies[n.id1].state.onJoint==True : if O.bodies[n.id1].state.joint==3 and ((O.bodies[n.id1].state.jointNormal1.cross(jointNormalRef)).norm()>0.05) and ((O.bodies[n.id1].state.jointNormal2.cross(jointNormalRef)).norm()>0.05) and ((O.bodies[n.id1].state.jointNormal3.cross(jointNormalRef)).norm()>0.05): O.bodies[n.id1].state.joint=4 O.bodies[n.id1].shape.color=jointcolor5 elif O.bodies[n.id1].state.joint==2 and ((O.bodies[n.id1].state.jointNormal1.cross(jointNormalRef)).norm()>0.05) and ((O.bodies[n.id1].state.jointNormal2.cross(jointNormalRef)).norm()>0.05): O.bodies[n.id1].state.joint=3 if facetSphereDir.dot(jointNormalRef)>=0: O.bodies[n.id1].state.jointNormal3=jointNormalRef elif facetSphereDir.dot(jointNormalRef)<0: O.bodies[n.id1].state.jointNormal3=-jointNormalRef elif O.bodies[n.id1].state.joint==1 and ((O.bodies[n.id1].state.jointNormal1.cross(jointNormalRef)).norm()>0.05) : O.bodies[n.id1].state.joint=2 if facetSphereDir.dot(jointNormalRef)>=0: O.bodies[n.id1].state.jointNormal2=jointNormalRef elif facetSphereDir.dot(jointNormalRef)<0: O.bodies[n.id1].state.jointNormal2=-jointNormalRef elif O.bodies[n.id1].state.onJoint==False : O.bodies[n.id1].state.onJoint=True O.bodies[n.id1].state.joint=1 O.bodies[n.id1].shape.color=jointcolor4 if facetSphereDir.dot(jointNormalRef)>=0: O.bodies[n.id1].state.jointNormal1=jointNormalRef elif facetSphereDir.dot(jointNormalRef)<0: O.bodies[n.id1].state.jointNormal1=-jointNormalRef #### for visualization: #bj=0 #vert=(0.,1.,0.) #hor=(0.,1.,1.) #for o in O.bodies: #if o.state.onJoint==True : # or o.shape.name=='Facet': ##if o.shape.name=='Facet': ##o.shape.wire=True ##o.state.pos+=(0,50,0) ##bj+=1 #if o.state.jointNormal1.dot(hor)>0 : ##o.state.pos+=(0,50,0) #o.shape.color=jointcolor1 #elif o.state.jointNormal1.dot(hor)<0 : ##o.state.pos+=(0,55,0) #o.shape.color=jointcolor2 #if o.mat.type>2 : #bj+=1 #o.shape.color=jointcolor5 ##print o.state.jointNormal.dot(vert) #### Save text file with informations on each sphere export.text(packing+'_'+DFN+'.spheres') export.textExt(packing+'_'+DFN+'_jointedPM.spheres',format='jointedPM') O.wait() trunk-2018.02b/examples/jointedCohesiveFrictionalPM/jointedModel.png000066400000000000000000006613271324306050200255400ustar00rootroot00000000000000PNG  IHDR IDATxWdYrYYZazbh4R x>p 9zӣ5!Җ4Hи$P٪DZwUfĽϽU== `rYMMTfdHQD0 0 0o1~aaSކaa6 0 0aaa 0 0 c'0maa;)o0 0 LyaaN`0 0 0vSކaa6 0 0aaa 0 0 c'0maa;)o0 0 LyaaN`0 0 0vSކaa6 0 0aaa 0 0 c'0maa;)o0 0 LyaaN`0 0 0vSކaa6 0 0aaa 0 0 c'0maa;)o0 0 LyaaN`0 0 0vSކaa6 0 0aaa 0 0 c'0maa;)o0 0 LyaaN`0 0 0vSކaa6 0 0aaa 0 0 c'0maa;)o0 0 LyaaN`0 0 0vSކaa6 0 0aaa 0 0 c'0maa;)o0 0 LyaaNO0 xcNc{o32 E 07O~`RUXU4yD6p >a-LyaO?5J*K2= %Qb#<O76MaƛSކaon>h8@fw'>:-K\.e@u{OGޣwCm!F`kxkK C_+fYq^S2 ƔaƛO|tZ8'Ĥ,*i\$y!LG"(}n溦̣0ږhղ20Yš(7 [)o07%O>1+,Ks|2ЛCb'>8)K* sr :TuFH" ,,rrL,̓0gґm BzJ1O]?aw& 0djqYjAB6H}{jܝd[1F CXrtX۶PXVşᗿ4w3B+O|a0mf˂ʲWSw+sw#80c,p\`ݭթB%8ܼ)Ee"}ri{teAEu'Q!9 ׶뚊1H3?y~rp|q 0Ř6 xsQk&Ni0@f~Gc8!!"`Win=B%3Ρ~99 D"gݚN u`Щsޣ4i~ NL@wv]SUaSa,(#9&k2cp0&0:Դ*LܳM'XytD6," B@fyeUaQ`! KD@@"Mӌ9 JrD|abRӭ\i>rY,`<ǣ5ZGr߷(h"@47p$ C۲ѐ]$߿-  WZ@YBC܃ad=lp|D07 0^O>1*, Z[U|k0G^t 41˩YƦ6@rwQoꊲNn6n:<<'aiZB$S,M#O{ _r2"[*Zp<A/8ucl}1ǔej'v[FscY:v6MyǓZs^"Ճt4GDym],GDIa̔bya|SɄt$~&;t 9A1FdDa^=ΓOLǍF=yGΓwǔelb=7x6=$:1Ȟn4";&6 -ߺB( -<+*:z<ܾͳ A(""ǎ- # ѭ]s!rQn-DpbM*19l!d '"p/HDw\pGmlJnb 7 ㍌ͼ 0^.TwfQDB8t71Ʉy=$f`fYj mr@2(MVBQnok)"h|h9F.\ %fqڪRCx^%"cM1Ѻš[@XB)$0É,Ӥ<^zS#GC1qq(k9@BoϦ4`@yNǥ^ja.lmjUEAuEZ)MAACD(͓E@ ="` cDE%SH9q(ECG3rC'N!QSE%llx@$87)eT,929&54,Abk=HrL,Kp0cD$$D9qbO(HdADfy0h9/waiEURUpD%8 tI)5 7({r.d-,׿07 0^ r8b@d@j D내DD0|FCh#_l&St 9$"rH{.f3 { 9#:cیuM1* 0w}M9#O}GD1m 1͔hICbs@HI gUIUW>5RQP=[Ǿmӥ֗C_ۚ4 s {Gr |SӺb?b(a|[1aƫc?RϦ=޸Λh6ANM7nx4CVq~om{{AN#" qqGv@:^\u ֊x" -kZP 4py◶ j=2/Rdar2{'OӨsz9ayI#a/n599ɝv`EUQQpH}F8t1HWܵAU.d)mZwYi&+tLq> Dy6 7 ca7r6 {Q"ǞOY~ 7_],p8ª"};pӧ;5:k !Q!ʋ{ 4/:IWIPމoW}2◿śvQկm5f4)x=ph*)F9}&FAq!jaNwQAt@m:ӊ*j1ϝ[n[1`p@ 3uOlg30sUxL8bl$xia xsN'5C>omٵsGxpDG0Sdg϶e xlLSߙBeqH(bD9vN(UùN#['j$D#?5I#BDWe9BGiض.Gߵr>ߍIC{3IY3O/Tv:Ӵ1w=^-]irVFs#=֖5 3Ӊ8@d =O"2缨FSΙAiD DTɔFMNH_bEBu#aQ`,gٔID`k:LZ@v:$!2`nPlW<z/Ǎ]nIsԅP|EUa]x/b,eA:&s<ĉ7AԇL-aoJ>|dH>/apDߒ}IK}t=5LHٟ0a1&"d^QL]MgυTcIIkiu #<$(m.lO )[!ˆD 3f@"^AmEuOiTA$"TI/,! D`]ԡ2\:ӓwwo3ihIϤm7+ * $n=cH$B!D"8sT? C0tFFE7"n]@ rT=ޑo:8q5 Q 6 oSކa|{؇)%9~^EBٌƩlk;pe1 !F6ͪSb3:Z.dcF1&Wt.12 @桪,q8p>8xԌt"FADG>y յ7>7 XDzM 6ON'((1pd ˯Ÿxpϳ$GD暾E\N o,FA>zRc]х2D PH\ 0ωH/?cq0 vd٪eEtߩU^dv&,X,A~ k0HU'ٰ=ym" Gx`yyٜCr̲9vo?y*Jnl5pk[N0i'L_. ?i#PSGXn,ӉmY`Qg~GuNRRN#/^kz'9JFQԑm-D]t(JRCij0r)1ᙳ((h8[,=Jsqk[Ξ りtIA ƤV9\eED[M/NW(_}N)!0@O 'j$^= E z^.D,#boIdFݴ~8<\#?cRj6M6ph )oyno sPX8.ty ?!0Gx{-0 !z:Seu D>O,,gtPBh ,r&]fusꢯWD*[YL ֙Z{ֲR^m7p*QEQwC=z;KW𾺮,5Hj7T9P֭ Gx٭ {3tc~dM=蝹o/Up=?\Y̽';;gƪbwjkd !:"Q -ij ;nr.;EmB۷es3.zeY٪WHHB#%{DAF=LjqS(M:Cl{bFAp,mh6h롮IGDxrk+e! _lX8yDxLj67edؽ+F]"!.w 3d Ӫ(@C ot_+̀'NpxpS44PF5 z_ydB9mZֶD7 3DCqN;O tCt,^ʃx ! ׯl3ʹֶdaf9mEw ",wf ;to_33y 'nX`f4?r׾~H B}/lRMut{4qdtw t0G@ <#qCw/\l6~ǁ@#̷nH4;$1vnjNc2"IYE~ @1jF{8E3 ;Sކa|}zbI.?1񡇼sUWIQ;9YB$-di8$7(-Hd$^dH A:d\ | IDAT 5޾%%ۮ S_r2!A#0v2Y4$ղI+kC֕aшFCAf8)Kz|s5o1<.-ANd@N^?,+ ӨܰƀG#CoS!¶U[[L&4u}VQpH!fN="z4H{%3RkOQ$:^PSoQ݊#|z@JKz+E ]@d6@PwOolꝐV?OkgF8ȻsrC?Fax+WGr3BDD;/J*Kr$J!=8y!mgkR8 ;9j)Fp#YN!֞o}gΙ"ifnqDѷ>1Ǯ/|Y_$}1BRVc7)o0=wmlFS!`9{n!-#Br)uM<8r\|챼_7?پ^4Dz#7[#֌3ÿj 'ՐqFܵ' i a|XLxctٳSEk@YTg3 v[X=ڶf֝AN/*oi15siv3GY. ˢ@u;*BW-8F8lFUIYgCGBz t*D@?~{o"l$J-x _b5 o' ;prn4'?ʽ'?ٳ r";F3#RRNoD/ ]sԅŚ#3-,r||LN@JIp._ "{8‰ u (xZ4p@#;"(8|P3~;ոS$i=F24NEt ˂ʢ $""խJՌ}tIEw@N"IG@ -$ H4);K- LIipsUE)De~c"`Q eè;e@߯W}pfުY|4PwBOAV/Q* J v ?G&,,a{!mr\KoA$Y]_O]}k?Of1@Qk~S0 Sކ§?9}?P>z~6B…S|rZWt{KbNw+GTD'"뺛 ٘kx6Ȃ"ˍ| Y#|a߶ihh4CE+tJtJD)MMݣ%F8{6n/CYOYnɟݪ+Mi0 *S:a9ƴ{X09WWO"Qʒ7Qsv]\ɔ5EPF8$¿mܽ{`A:"GI?Q!3BۢE 9 zYrh3RYn+\0Џp1+$6b~|{HB[֎+:֗,kf9k!$A.iUX'#:xy{_:VA7"4zRڠ/Z_WMzcH#[gzG !B_d0TAx{v9 mMVbek[>kD]yK||\.)qυx`[TxW%U%)N3w]*nK^̰w8#',C-ekKNo߻A,L!_wksxB[x|gh ̄KN5?X(#k`͛\>|U7t~$CfNI|rDP_1w 1Jd}K"wCBx`SX4k~MmRm1µXVZfuJkk28|$|wH.)lut';,#8jŒn=MU"{ݷXOWC_?]8 D /rG5P۝,FJIœNb'ӦR 8tǔaS r RwH%4 U(WPRuK WESF w21ӱ,ˢŖӭC]cE6(WJW~et)"*yvIRz?c?o| vzVL>Frf`-Y.(RfVqzUyCksyLRB 4,\K_{0޴0<TĈD*8Tyca> #M7s3d'acNrjDqEcZfÇ۪Ʉ^~Y3*  ×/Ky["1"FY 5yviQy$Q" m+H(BfK&Ȁ,h2I0I 2ãU \JLDq*O/\Eu,Cx/;A"f!W5%fYJ@C:t^;;s/4EhʺM\pi:Q(` DD@kTri9x)KXk`߁y:fܶܶ cgLέͫO&q}F r {ion(6ﴏ4 '9ЫI= ,B_:K0D'йMVGs1$aƫ`0 B&."3w- ϿkMiɄ66HATgaqc1 Hpf.%`R㸠UR!9ff9zL(V,_lT#ćީam )~JͯQU'_å_H;?NӉ%p|t;Y]OZ%{B @#QK?c mRlmƫc0|ӳ-޽Ξ?].z'(DY(#Gٌ3>v؝vL#g0DV1olL,(W:^?8vly=n84PFn r)=_tί:ώDRZ aL@noɄΜ8ʕXvSo$0G@,鈌{oGBQ`UҨ]R3RLC޾GܒU#BQ|xLeIn\}So: Ai[2אof$W7}*}_Nյ]#fZn^3r(e+cᆭok KLw6;996hضIotb L]2ЈLT t\AZݮ$V[ Ai̽ik`0޸|tJ~;FXٳKA$\lGܝݻ(KLh4DTXnlPUR<4̓ߚLh2!rR|Q f6Cm>w]iXli6,#y<-=ܢ,q:@{'q/_"jY /ֶL&~^w ED^:vmcr/qRhĶe܃;ũF(1f\w^N"i&Jl' 5V3$$1ܫQwMAmi:w\cKZQu`R/195cG_r>U Ƥtc׈}" pq52b &++W,pvn%:Ȳl[LT)wO)B os@166 Évתi"QCli&]?S8Cs;RZAI Vt&w)o$6a ㍂J˗K@XC"8ztykSpxGi#U᫴Ȃ ja8tmӧ[}4: A#$qI,1",U$ENh"u^cH#I%_fg(x-;sLT.w%ĤV1 dkҹky*)UPY`> "ܽI!2Gh[~/ g3At- .HIxPEuH/kv5 #±Pe}egķKjā^Ѹ͈=Rч(YF@t`=%ӟ.%/ ֒XV'.!o:}h^[.qw ^t)k*Ȅ0ȱg!j7Lߎb7dr(8w䱒/]'_l" f0moS|s7%/.'}[f3*nKr]κ>C"IkVU#m}D`Zf |" X92{]B|9Y9s'[yZ4LcK$tD7E5mx>Cr8 $/gPG=.OիOC#yMpw%Dݠ:J|8(d\j QTp߾#666#.+f:Z/P#rgl*wD2 p`;xLiԍ*%جlJ$.+#2h謽i7W.O}b n5)uE05G ͈)ҠQ0R{%%E+'v:Z"`;q8r- k`>Gk\pL##͞=(gr=ܹxdp#|*y8F^YM%˪@/rz4fdPkaFޛvU}Z_{@hB'Z4R(AlX}MU=uxK- 6$@ t$99ל}wιVsra}ޫ˘cwjYЏxfYc| Fq`Hccɓ"Q Sks"IpHшwa+]Iޛ-fץW1p`3fW` QCNS"ST_yU!W$%{\̻G(̻GW\6=tȅ4 "vp=]vF#ʛ ժ678, !4b'Oʱc> @o-VJ#*b >\ZUϞG\:*ĸM\UH IDAT8yR&S$S)LM NFj(fsLBͬ;r!& Q=Gx QM7#QC뼾DHط&S"^[`$ami3%QDLtBA*t5U5^C4zͭWUQ8[^[wӿl'm6Kw m t9ؚ?#9mQԝ%Eho:#Ǐ˵ Cۉ:fB3"Aّ]}3Uܕ:uEB8L4-ꬿ(]0ڥݪX.uf&(rPcVWس oHB>v0xG_wD#q,G~hm?>.&URBTU%rs˽ߎ˅NB'v9#!;l5Wb( $T <1pSMi:AM[[N9v\fvt{UX,${?;6'qp ݉K3}<ꑵ9%5Z_n MM!D<%i1-dޥ) >ΝQ 5缾N -ɲ5{ ^w聣3 j-Aa]تAT(SP%QRR"yQpnJe,d9%GXIDطw#MJ%E{Jf=y#7< /m5Oc֜lSDшpq}'} "«_1Զ'Um>-G_w{x~gzyN&cSv}HaƤ*ְoG+& ` $K=]_6ԭ#ΠJ̬D5kY綄 \T)8n-lo* L.S[~@L1Xomyou3gE"팣dq[2.I+z%M6|t ;V49`=1lƃAH5ڞofb*^~O~ѣ3=3T"(Y/zt[I Ko&ki`o=AHLBBvvպEĢ!:s(ٳzV>Y=uJ.dlEc_ܿk/ }jPQfL+1[:O4ؓa=r,iI[[38P',\T䜄䪏糔3hՌEr,v\:f**(&z$▝/G+s by[NoygQVWpO-/IޙvǦy!h`̮dg~jN5kQUhc 5 ՞5INIT,'3ɪP՘71s*Tl>b[MѣG̻G 31Ύ& `Ȥaf7_߀ແ' IeŚj JǶ]!%ɄC:w.`6szŕsLLB)otu2=cc]s:u:%_窢 j)hsO|c['9L 4S]'7}C%z/99;6,D*NOo J ,q( Xd<4; YzQǀvHo-&EŅTh7RBZau&w!ݷ*',Me(rfܜ!  }*OiC b<~/P2M~S(/Q귆=z|!g=zgX83LU^SN?!nBaDGmۜb}U=!Dÿںh6l!8z4lʣQ"j¬lTt: o>-NI^'DLPsYO.uNhT 孭]U Q$;G#!7h㺆9C(ĉFFSGE`f[cs4/CH5(Ztsuﵮghu"j0; c-o:J\5f,g~&{QɂQ:"y1A YJiBYblo{݅.4*' y|^es#f'+b6"ww2!& `<ۗ -3˟شK1>?=z|g=zg;'ÁcN%'OmyJHja&3=38ȃ a^]9:~oo{ƛ%T ;vr}SAO[[>xbkr0:O'TB!:Vrá[[|>U !D.lFfnWñ Q9J ƿR"Dԩ8GB'Ec Үjjr^ EGfe6 e9*e@ V&77o)lodt(j(rTDZ)Wc =qmjz$1ޠj[;["e:dD稊RVTLުV#4V/}4vnd]UTZz"BPS:s$2Q4FL4uMm 0v\l}G_wW|ycf2B=ۼVѲ\DFdi,vn867y<"v6d#Gfb'?nPcOQ03>olעIg i#^!Yc`;GWSńn-g}Dp.Y O̸k1W)S5F9{LW٬tJUm֩.-\CQFcQ/wk,MYaBd2;|omFA+{4xm\"F^tJͯa!hze;7 unٷ]|7ZHkd/@v籇i{,e K-ɢdž^ޫy(8d-#=ѣg=z<(r,-)1|1j|.lo9 6q `8.[W9|o*h=7aW.8\Ύ>#t̒[ש8h8a,1[[[<ɓq*:QwW,mpT5 i-8Uc.1#F+ZAK=}F2sGnieL+DSz _%d;&uSʘ(3^UC±3bJ-o֜#vvN'NP8HZ_!1bkTKkjxn K78-"""i1]QvգG{3=s3>sZ668bOdp͞89ZV UDLa2:Mr7 e}\ ` fV]njf"P)y2LUW%[5ai֑Ħ0f3O`31ܪRhII)wWprk8WG ɲ#G=n^R_L"}%7[wȬ>r(O7b +)!"D*4 gR3:GD-9{BOm6z<췙Zc2ߦlO))+sd! u$5EE9BwvtCӼ#8*1vvtf,C&J5}:Deo &Tnon̦ `΍U͉։QY8c57tsג}gWS0ͷM޷̓ WZIVX(_zx;BCDUhgDBmn5RJ뢖l@)2F!\R9TΙ󤸢l+ZBh%$wtoB{Hާr[^KDhJW#Œ؏.jQ_qUQUd [g[S.{X"WOm?듮PM+tnYQKlAQRUTlIhSja3=Do?c^Y[!e'BcirC bV";y36$$=Qb*ugl ڭf?ѩSxhlRw7v fAhmJ!"͕~gط'v-lQ!1ً?o/6ev ˿ژMV5½Q H,"YSsk4!̔NP%*B""Th`qB5>-Y!%.oI3 誕95ͬU#"@; 9*/]BLD"E^qigz8x.>'9$M1aҜ.RG! '=v.#nM>LbZc_e)\z񅍞y ›ߴiڒ REG$E/a]O{F!5?v`66y2fhnA"m[;ߕH Qs]k#ac9M4s }QUYTfER(MR(+*r?Zf3j+-RHdk'K.D ASr섒8͋. v6Mk!,'Q rU1l2Sxl"SQH- UA`J/CN7D=[]{}W[ 9^ĹGv ÿ)˝SZpd*N=5H8]JIM۶䭪V1bІ,-^9t_f^ѣg=z<(6^{tu0&0vh@6ge @ DQ1f f]@rUHaND**i^0+lOƫѕ>yӹK8`I\7;&ӿYc:v\f3iKvkSFI٢ ҳX;fK$3~gǢ?eɬVqL1݅,c~*cA>| !B5FncLWT46K ucFd5&a!Q||#fSrꊿ޻Ho꠪&CR{ZWtyejIT*Կk2ˆA"PUȇn L4ڢi)Osi8_NJmB{aoΑDJbDEJ._F[d$W}͗ݲH%#! )& 6 J&O=z?zݣǃDĜCtc(U턨f5;7 E< @f#t@]$6@zǑpuM6 F:sFݫ!M'4ӱi0R q E(ViOZ~~OnDQ(!DB%3 7;uq%ZW޽wi4'h`Q LNUtVx,)9o;yR8(@j;"Y9}Ӕ;&vT1߳+JE,tL-{E90Q0TUDULCRz,!mmc|j9>IC1֛6Ҝ=f<142kgb(ѼŖSjr]u}ݮJ. j0ly Q)^q[B=03= B (T^w>(B67ɬՄ[KV}}`RňP5i@UrI=~oolJv AtcsQ]$ 9gy;lj[Jw|ix3pH!F45!^DuM#˥lm(Və4z8@yҎᒺR?zK/?vQCH2.1hwZPɓi,2(*r^2w拓-QDT]baZ8*|='J,PU&TDtdM6ʌ^oֽx|uwTGNn"n:xV\.E]P q3W|!ԚZ*2, &_"Hެ?郿@+R&~`Q.Z=z<zݣe/8&9' >E,;]BG;}:WRtߕI7h<pH0+o[yDoxz@XluF]Q]SW/5 4;hL=U#UZ.Qѐ>F\sUSpٰ99^ӲJvq{<%g/>ӷ▍Wr+mJ"@m1iOш:V%kS =9-;bO*]rR~5891eURƮTWLKwH>i8;;v:_*W8Ym]Eu?|.V`MO};1TUTQn}\4EJ{6 vwuwWo=k hPs]Si|>ts*Ƣ3J/}rUS9TND3S0>nrd<Q]绐/K,nzs׼{8 @H[)>e=+k!z^ J$utם>$'?njb4UH¶8Upno76Om:GR+rDK@;gF;TD[Q?-KBSr&!ή{pjT-(ZN:Bu $܎uF*ֽBRFz P`sYL^E@W,Ă"kd L1JJ4-4;!|Fx5s)2tXl@"_eP/e 1Z 1KS]-;OX/FU$I@Lu9q n-5I DYĈ"F"CSݵD ?h8,A=mx2;r.of,=zwjB b1dM\A|DuњkUNlQ&D5H`s8)ާ0 !V7~-k21lM{Y$cD/y|}sK4:"Y1 S~`A$Si8tx7NbΎv(kk]|NɃ e/Vv ,)3mw \l@6vs8L= ǜ CD]SX-M5EiO±E:g JDl ѵړX9GP+ڄ(v:.3 b*<*໏J2'H{%M>bMF:C()>Rq }׎F՝Wy]ώ*"U 7O~gңG/fn=3_77Ar&'>h4m<'q4""SʧŅ1ҠO\L4`6hm9_!7`@uM3~GOv7~u!_ٶ>lY=#UEuEU o!K-ŏV2|.z1H:^h-eJҤ:D5=Q 1 QTO| (v.dZ&Śigo/M(Nż%e.f!tj!kuqgX<~d2&uԣGv9E%vɢSL{R[rg 5̞JKh7ju6LUshYi6KƣYf?,m#۟UXW}dȟdrhaGU{OKѣFyqaz!U)/P F].Sm$Ĵ\X ,AE ,ȤӔa=!<} F4_*-ﴟ[ݿ[>(2WFs2+v>$aF܇f3ϷARQ07-UEQr<$%8l+Ěa00Ӂb)~h;o(Q-E@/Q;T|*E` $ ǰ4H:G!5JR*"+<26v2˫ea8^:[xk읨Ez8 & 6*ʎ:Li5y?rfPUDIL).Z"ZԘ=-Ev"AU,u>}h2漦"@JtI=xʑ$ͶO6 zeﺟ=z0̻Gb&R;FAs*S ?ƌm>uZ" .sXֿlokj42~tV3 ̺Cl#-9Iد$Ci-W%e4fmwݑ@TdR @DY›-@0em߼.fEuBr_ ?bkg2߮XsD$^?*$L/u!`0?^xlȱ^9gTDXU!L3-6o4kN>sU AE$؜pHgIYgER d;֙Vg,W~^.mtm) ĎYK?M^o~FTc`Z+MJs#*b|5ĨirңGυyqQL!j1-9XƈK\_p %pqu?tvwΉc*B]6ͦ wfi(PM1j 3olH}Xho|Gջ}`*F9J F_ɱc_r7rm^9*2y *r 9M_៿^xW*Jmv9BZ$O*Qu3LgNYƄ5D%вȢB ی 9GL!J ZjsOjĴ{)dl5<}뚭hO1gHK<'\[\\beӠitwM'clIPe~00ч?:vid:k(;mI8|Ƚc[OwM0-sEW+^:5dᒥh5gav/^uٌW_\[T CZ@&!br杇;kԶcf)J:lc苾fܕ-#GS /{Xg 45蔹ծڍk!˥&çP!{B;*pQeح?f.UUXr!wW8yh C1%Ôc:G>mQB+LJY%7.Df==zXEϼ{8p&!Qhi=m𱫽sJG: u~ӇW|ti(]k_0[o ~ʳc /9<ʏ,7{%0\OVh"*!$ H=&u,L;E>ϏѾ%j7 1BChMǪʼnko br^#S+O$h,"V7~$a1JUCAb")^wwILYT#+G8,<wuPʀTa5CQJ<*D0pT) QT0:5zƈ^޽kz񅉞yqqwcA" Dȱ0-lKu.,2Y7jCH&`塃i1i*@}H֮K$:׺G'ҙGl9_X.uPWcۯߛ /hƼCP,vuMD;>hsHR](BTPU暅"5)f*L΂l>!BImҩj w KQTMR?Z\z}6<)&ޱ,-sD=MiKZ! EgdRQ URxdήBP4NY]Sޡ6>OI/@5Uə01gq"zZf:ұcvVsJ9@fzE=5Fn=z<zݣ91k`<7 T#sCW4!D4 C(=KFzYUi/D$qJB !!Fg%Ų(GYl6*txs~R ],T",jфwLtV$9ښTRsvk>E ĵɄF o/B#Jry;LyG k)PŮ@1hD/ɟ?@=3=?r_>f&b$L3i0K1G]U*Jp±{bA\(ƪǯźP; " S?mX>`Ϧ?,ɅD& FˠK@ef.So6HQHJO.wroTzb)m9M)TLk]Y. BUSSHw]VV5?kRK&s<:|+=dތ"%YJPOnSH_=-w h3UeGoa1RՂtR]hՋH؄Fs(CVfN+?'D.A@ qȖ9iGt$vDXD9B&WKRݣGAϼ{xPny[`@tPG4K]IC1jxce)JDO{ ǽ**N<Td5@rvIm hfebŃknl_,џ85gVĘhb"TF [nfVԭQPUU Q g- j PĐ'J;VcqcEk{LkŦhZlUQUI5/R5dGDkQ9Zh!/VNBrHl]w-yxgi3䪒^e,i2{9#뉡jSB m7cMr]PM9FRV"-6:g*}DG:E#zN|DSUrcZ&*VOp iɊ{GT4v獕 k1UU/&`N`C6ejbǀzfB̻ V.cthbWs0@fnڢU`8#I5y+$*;rqH{?+{5]b5G=w",ڶ9fo051r I$%$˯mU[{omU^[{snZu NΦ daBh!vR5RI*Q }k`@l@(4$=7Ƿ\D\s\?==gwWky\53q%/p59Ho{39ms,Iev,xkvֿ1z3NeoKl[5!N U9X۪*zW GV}ds:sr{0"{3&=1ex5rkI*qVW3{}<2B+̕C-< 捽Ugqx:L3.Z,R DT }po(3o+^wk߸?Ғ Ű32xO,>pN_&S]}ӟSAha&0dkWKvH & L{„3яRE/Fmh笝g@dP$P?.z :bKUNo{۩>0Drz׋J8wWB)(g"56Lwe>~2}qv%Y^]UBf厑MjM!RNw<`u\Zi VO8&{ϹHf6qݱGD1X|46ϰf)B]=i$mNǖG3q9uv&VڙhޝD":|6̭7eM)>a3h\7폧oB1)7)`cLo bu϶XI$3)Տ̻G_r4P):1/-J&%QY1HxvR߼ݖ|x{Vk_sD/on_#<C6&bUZMb)y ȼQJ*2ã-A3M{„ ?0ጃSm KHpA{g]C8*c&oDY/^~>tkmS1{PTA.EI[ǎ CH4 !ħO#!d)#m#nf,r#%pz—ϼHun /[l8ǩAoVI6 0ES(}}&n1NuJ3b6bbfL\.9Io&O1 Gt#]>z>mJRq9ZՐMSáǬ)c˷򯝬wҍ]c o@8/F 'R<'tpg=VnrR (YXmec0a„yOp"0hΥ}#7u88RQ v/Gd@ oh]9SA E׬17ULm5j"!Lzhxh=;7+Iӧm3٣M#M1ڿ7'~I̟[4D 3:̣n^ wY-c9*ķ1FŦk) F=X%&>JBԛ/2󧇀|)͍w5 :U c-"Rl'rճv^=H12k Yv=P/~άӚR^xġ1ʒx3LSJ$qv &ļ'L8C1VFn͟yQT)!BҋЪTΩ훓W;&U(Ox7x?7s'^C3GOC>UmԋƒY=2,{ wRs,|.En4 2W͛9n\i7o7%{9|r~Py݋w_McYm eֳb2Wٟ0a`L{„3f@˫ʛoNǏ>BmՕ%\ ^@:.R3g3j#w TQd"ͤʖŃQ-H <~M$@s9^rnHh$Fo"񂫶l)1+Ͳkw_= ؈|؎t\]'UM$lHx)nֺ ~\{݋v7HGO}6QS$Aʨ~7u9{#$hIɫQ@@XC#` hJ:ҳ;y1usډ1a1?zS9 |z,Bs D˸h1H D xQ)c^pՎoY {-=̝A=6g_q3X.>z7:~kM0a„ \NWFZON|ώ7g3hZ AFnin?x7su%]Zi%w.kc׿y *v~mm+M+$b-Fh)eSQ)q޶'xrU1SX]x{,,^@^<3%{mW;_}MٝJQ4!OE {6_ȣOvJ 2r@U[Oo0&^g]A9ov}op^d6<٨ ?hdX Wǘ^pnqϣmuÄmf{z36c5O%?h9a„ 0i&XLUN燿tXe=s6X/~TMJf2FdžFż>zY{. ???[_86+eJA>aCtwGVUAf ӈ8UmH'sm9fQ$r R8R[cĺo/{(.6f@T=;41mă0 U'وycE$. UX8T*j;) 爘ح!yL<a4,۷A=k.H#ٸf, 1䥂*|9e!??ziyexe[o/[fK9S"rkNR4CDc3:=0a_yOp[=[n EGw1e="Y3#U:#v'lf0En `(*A7pv6W뎀PqFk A}syQ4Hϼbw߹tvs` ܋;9)uS_' gC1:5͞0ϽԈ"/BT^׼a}%I}esrNJ )`ϝ8 %iA ̌ɬyd+.jNsS ?^]xӞu{rs@`ɢ{c٪qh"YLحLчY<J5㝓ĘXAq S@ &#=-,a6v:,G (A,|k;_;%r„ &=aOzn5x Sb0iMS;)loz5Cc ƚ$hiOޚ쳃gBzLh[IqBEv5~ B`QZB8WlzsV|"63\:/,4N0ޞ<ɓ_GYsY)kJT8j@ F<o|kJK (ð`]>bχ=dִէ!CSb 1&jȬGRwtlA$C5oxE Ȍr_yW4N֫?D׼ ihM@}b5^ϗ h^}qBy߳|c;~3k|bwGw$7}ú~j0a_yOp7.-U([Č1U$)R=vKUnx.^w[Z)/$@g*_t}$]!yjۄc]4CCwqTmO㮫$|9}ny/P[7/2_~9oxvF4*GĘvwu{瞫wz9[93m8E>I7"QO8>yUgSDo= D 94̺Rv *zyBJx}M/&K p[fG>bW)IJQH7%:/+c"$zT%1y ?<@B!ɆHӸ%%ZsyU'ٶm}YBm$hP,qVJ˲w#}d/= x br7}s9/°UT i),!prwgBYWlyFSɴ;`ߨg43z,D\T&a ׾`?XY4֎h IÞC2ڝ5jGg?A"h51X*:+8XMY%T%s3$5tyI ]^;XJD:TOQ3^4d0ƞ}Lrgl憣h쭏V}Y/y0a0&=a>#X5Fnoz7UQmab!^#߭it5fc҈@)eAd^[ + ̰\rd<RK [' zsG1F?E-g ;day[[ WFw,$wP %nmrUSV賓xGhbL=eP0~¿1&%E)ߋ!]'%/CZ -)woy  n;Z݉ /}}@ )̃eŗ%9X̣d2 !ހfYN=WKƈ զXʳCe<R\L^`\>g|䤂cw2_zz VkgwQO0G0Lϼg+PJs#M4Bk9.SCu'm}䣹dٰXNR'} %~7Nyʙ*sljĈ9lY6J nڝK"cdiﳏ怿b̼wwug[vsxj4gbU@jHbdJ}Z IDATkIle R[oMw쭿|K W\wh*s g'SŹ= a wjEf sLM <%׼yfgd%FEN f:5>ǏiJE.9 3IJ,l2WhfJKT7$ç_X+T4%&Nǂ% 4R%2G7>W+n,rX7^uK^_?d &ļ'L8qKǚL̪boV>rx,A=as.6R?MLRO{{D4F>m!ّLVBmCWӄqP~Уۅ8ԓL>˦9=|.m+y=tJڝ*]w^Yg'9t+ѫ=m[[oϮ* L_ f?C<?^ k`IyiZY̻6yaae׸ǮGG`DRe]w &&=a Q=CK/^;PDvvmOZΩ&0(9gi졣23 di Vl?CeN@eN)aTA[>fƐ: ((m1rqbsk+j&_ E!&&9SřFŸFRpO{gmpwE f_|.JWQ~;V(sOY5%4SV2i0 K`05-vh0cߣ1>lQ+~„ 01 p`(z;_Օ5 BR)lJ@p%߰V5{|1~iљKu6_sc2h ̳K6K6Y;.\w\x9aNĬ"FG~t.XTld4f+.T3o]]g).a,{Hζ쨪KHFނBT*cEl&Gĭ a g'd4XQ YMDOugĘy(4AOMg^~(%&9~ۗviD0ak?%x x3AR 9,5O#`lcC31yZ`%5JS׳ץF)Zɔ-׼pկ9 &pcb&H֪}|E=t7$3R&/Ьy{^Mζnjqq=)`oϥV4$c͛<0<40騂9T%p'n8PȲZS<~|+:iulՂ,"T)**Yva! 7;WX]USL=0blȞzz|qi5,K6A`18s+yc<\;oK<~>u91>W5`>upTFnz_S@``S-n#fewt1bT*H& ;Di *ܷBB*#>RX&4Ab >h½|kx%[xڻ'wFE(m,,}:;>1sL4h?$A ήs2f̛Wy1CJy p 7}]YU-qM#Q$|c]}ތ }+^! VhHQy|k\)\N0=i`b˜#H:WVJd=LVGb.m랥QVL -`IwW)QW>mo_ 3d|Sg_4́|ob.g5<2nL)nq'p X_~ۊY=Ô'ĄÇ'{„ &=a™^ h>A%)Do~k\o>NO}{;[gym#y 9]1"DF-FbQ^^-)r *ٜȘ"iǝ5cCє3'/f4gBt;<jN)}1cO1^3%ȖRi[m GZHľGc63k%(gI*kudI#*h5PRbC%?h|&_agy@$a{Ƣ &R>x*¹]L2}pg[,i}|0LDDK#GPYE1#Y&L𣌉yOp½M#}sca(41m#hi^+'w<ҭw] 䪌*q{_<f{5MU$eLXȵ/yݛs59yreX.&Oɓv44ViZ[©H11& b$oqWܾ=#NJENttC߳"f3Yw֭QFeŏ?lE6JF wGXuG?Y鿌h[fOl4PL̃E1)Mߴmc_}Hb}[NQL|Sn(JQ/BeTrA%?hy5A.Se$z֖(:/FHϴVDu]=0aL{„3 O U"#ċQ¿_E?ڕ'N׿흦FDB.k㣖>jH׽5o )YL|Uox%Jj;Ц<\Ύp Sv4?پ S+Qj,{~Rf*K&̴F$\.k;d{AAJ&9?֭\HG;~ik[Iuce3F<~mߏ4|"#8^ 0}sղK+]Vn~f;޳~?3^~|D`+I/F&/fonB@>wD6uQ5SdLL,'OȝkDHjn5מ#?a„ g4>t%l7) e:vk̼Y1u[=`Sc:3TPׂ|ݕVXQP7֝mq U|ZJ|sw,Q D J l^􂝦4bd_=;RGnZ &Q8l:9EJx*ߩS/Á့u^|’]GEf tRIG{{\i%F$k=/g2 Y#bkÆR[(0f9e_p^>"Jbh]|;s&K^3ki䜳;}n;=v.{$B`Ed!kATWí늜yK#ra„ 0j dUHķٟsNpc!"n8",@,$2n5>,[[2k˶ޥznq~Ct{[soixy/Ph4Ug]Ʒ.EڦA^]U *ykA-R2J#ōynP77+{*KW+S1U)8cmLXH<][Bs͡Sbn,m>>y1IH۪/r^׽iIYWloؿ&ݒR@x SB3z~HDÎb͜/^hH`I `zm]}&2_4Q=|Xc!%\U;j¼;Id$6T|@E upS i"MA̭RUh,ɘM 7h0CI?&LyOpƏ,v#G'2RUeLH$cP Wr:m$tR,!r,{?w_~_S-:t\EYPzwR~_.SqΕqճFv FT z_Td.^я}]n&o.2 R) =vtX bd p^MlLfG..#]Vc&1bV4b[nU i5WoǔW={Ə?LF_}C-;=T!G4ҧsM !~2 \ɮ%m6[R5EU5HHzEOEydfÉفYCâ"o t4V nb)/ "Ϻbw߹L*Ikڍj%Kvx@ &LȘ ?_ڶ;Cg5IH,-h]""DU]@ &"^w]Ԉ[L! F EP e#'O߽"S<k>;5Gw,f]MuաViJpJG %7azE*:jF65|_FK%޾54Ji'Y'&+o%lIκl$G}|1Ӌr@ʁc0CsukwA "ᥩ'"e c%׼meg[ASB#C/}>kqN"|f  ~n/j6}زr j0>p)߁`„ c01 ~_m%[J27TEB48 M/S% 'Nijk"٫ZQ3mE.5-'8H'.뗕ʨO_>OʧmFܷ%Ac$ZJ90"yȃfBw f 8dmȓӃ%r JɁEbnŲ4$CubDR}%=4f{YwMP)2yLOdb43XQHm^^:'L0&=aէ\Ҷ:ɛv5oYncBݷBxSq)ITx $+j};޽*8LH!"$\BTG7svڒ J,2EGGC,m]fV8>=7AHcT4crYUX])[v5oφ I\G2\[X'MDzefh[]77 !!{ff5T(.ː1=|5>J)-厔 wo(ܦoF+av1 I|t޳tQ~j*1Q1bx*]5,{WY Q^86C~„ *&=aD #>7}v>׿r&r{{w$b )fSӟcXC'"[͉%rn2W#%w\0Cߣ"cv*R24z}+_Q08't TqOT,-uJT)}{`o1/;<=#)B":NvC˶Q"aGCH"K Ĕ&f99Р`V u;+1jFJG}3.RЗ)~ yW`9AuC.xJmN&&cJyGnDJR^?E$k/ÍYݘ0a„1&=a™_sMXAW^_Ek`.7ݢj"0ʩvp ׉'wH?!Tͬ˅ag-;#w* ;GTƇTZ2Fd(҈qρ9=MI g'n! 4"dHخ+gޅi%=d`"Jeu(F%~vHP A^qݡ_S`nhLKԥww*5$ ˺}LNF&E՗I-אoD- #ObJ>V=f9А Ȅ>mY|!([%y9QX{\,kt?XMOz"ABa>Sm6Ο_ߧs/hA}΃Go\A0a&=a™UTi܇},9aЅEH}}0Ȼ^ukSn͎SK*(ƥ]~{޻XBNOCJ D1Lнr@vHI/՛x(h%zPB9JMLM-@\D T+vwo["ਬ|˥_3 ?Gc{q{}^Jm+539G11FZϤ BE(]w/[F0YRΜ]ңK#k{Dm7|eEEhBBގg5"aCT&D;uJ vz@V^WTc8wS 똘 g:bjM + ?}?~Vi'瞣=a|sc{|*sx@)p5U,tA y=1wWn" 1ʒM'Vh߭ IDAT(T$I0oIı5:*{>Y,$7:_Od1%MfAT4W7r}w*|m!"uy64&d6fS|,7A\K0ùO֒GQwWnU\ 7{Z` 1g {uL 5YL` f$G'*k%s Ep|nm֖m_w%W0 ig7}Sn<]A^-}ϫpښJ7$$>J?Zy v,!lXu=AEr&$Z]yt?rSOtÌ";>M'Z3E.hUu'&wƜ4"_dR'L11 ~Z34p]'a?L"3#E>6Z'14̙Yjл뚙f4|3W)b7J8Q7R.1VCLA˿^FӖ[~RoOY,i TyKd)1इ/\"?_醃~M}viQij܄m"U\+ mZV7^+\1F>ߔ1s_| ~11 ~pGOxvPG y0hf},oUG=bބLš |i{lwG?uP{ɛ ^+3/7kh@H?Z#F30JNKAB`!̪`bOﱝ*-y8v%޷/H/v$|bgW=&&H+ƛ)/\_D7{R-%! nޛ[vW2SJ I`˜  @`0\UpDGuWGTTDGU5.m1XLc$0`R{[{sS#e{瞳Ϲ[{}k1+D'Ea& j&1͜(8՚1{߳b2l0Pf"$EKʘID_2GI)&(:"͏SY"檐fT5'!C=EepQ9"x3l[p%J"!DFB e-fLʠ$Ha` Uo򊼗+-S;h2Kh)w@d&ץ7#AD_?XcukDcu0?ҋTt-"(ȱc錣~γmq\G! Oζ\iD1Iu3c,M~fBL-SʤJHH!!n' "],XYrb2pծl!wt{$9(yNLyhWTԧDS@<(Kl3Fc2h]Bd٤zL&s9a洷iUQ- e\ m>N۲ vkagSTRSdTdD&$$QA(i(2ܫ|ֆ!j +1ABf'XYڎ$3:^-H@CP"$O3ٹG?`p[ ]L~r"SφHKw,y>稏7*?:udyzֈzͦ6i;jOl,IJC2ɑ#z+hd+'8w%d~ױo;Q H ߊ} СX2vNx#ho]g||jRc)7:OCt椑}ۿ c5ցy5ց߹| wL %لD3- V) $>@: /=ꑁĥ/ë("C|Q]lB _rۧ뢨]ڻmUb@T:m7P`O{_hU, 2nlq!V+?{hD$D5MLČ3 r [j icYt=4h9+E!@dB* ݓ$#ykUͭ;\]Mr@l)b];z *Y$F&yʓ/}%j*|e[w gia2(ƹbK;A; H]DTeO>s2IOhhDp2=γdO\{ކZv~tjY~2}{k,#kUx p*y>ߟ̜mmJjlR9?rr@{#%2Kl=ZL{~z~z7wO_NB.'Ωb#{* f̪R,yv waTB~?{YsJ4nqӐYj/bRW>tH$dN_wUI使X*|Q@u9 j҇.`63z>,u+QUMP J,;+]cB_+Xfzֈ36#Gԥ& m'C'81c0Hv] O~]T9gmZb!I5 '=vo{cQBtU #f|s:$_T"Hs(nDꡱB1፷Cy&g݆Єj,M 4X Eő9>!F3'ڇgJ$ա޻@w{v[~뾪4$#Td3 mAX5Z :%9hLGLP ϧ^B僃HDGĪ ܤZJu$Wn_BjG t>H%yCnvV)s~j :όvm`?c5ցy5ց+Jt|vY~])AI)< ѣ\Q "1ӔD2'epw@T u w! S Ӊn˃7$( 4 ߄E\sb6 f!Fjᚭd.]YC{OKi."LrQ}fț> {MBtK dVsºIs%6%[',^#kVo}ˡ]9rX3 AUI:$nnժ*̰&vUivsd @LPFP r;+.puDtHgxLulGn3oݬ^v]8B0f(@:5BV*A!Z\ |=C0T|uˮE۱k{7@q}HT$0&FVۄ7CŜJtyB{r3|ˆUj&2urw+R;"\`JHP 1ӶJk[ܯgLc\)Av oܶYg2$|&W*pqg[c,["qpO@~KV0c5ðF=XvwewWC"7zOfuhvPK/uC@3^&mtrH4sW_%߹._9A60eluZ\yMHIU$ժsofxݍ[zL0g %$6Q ]s;3g ?`Ag 2Z4ej՚)qLi'bPh@T{`-S!#h0_mҊ8n(lF2tJ%}, p^g@!K&;fs;rQ-c{޻rpXb.yOuUITb` Vkyn-d>D2]EL=&AǔC@P R/;7&r\no#ML LRPpͫC0,Oy; h91OKe1dw ;08 ˀ5{-.xgAr5No-eCC@XʨY4c&cdJY?6i B-썡;r}ƛwFB&:&[nڶdBqJT<J ˓?0]\Xcpy5A*wAל/4%Ԁ.pp0c A>;:1|sNdT`Sz:P=H1:RѣzՄ2G<92.BM70q/mG~Aߓ֭&mEs&*I tLJb|HĪC=W^s?| $e%CUCyLjWjQ5U&VC"k^Jpڻ:oLmm*dDՍ^}:[V956`/#+tlmlsP-Fdl& >iML k {-%t||:iqb̳ GR"%,I\>>cT נ$G1zXcy5A*Iue>|5[mIúehi 3{γՒM#!@Ob!e!zFrq@%W:t(I@2fD4ixX񊓉al{S3"CTj=rҙ, LuA:j#9O;ݰ" xz/7Mvc${>&F1yTUDأ0Ҕ)g"2 IDAT^MjՊ]cǭG27{8n I( IeV2zfUV5t* UJP4_ٿ; 8(5ky5 /\sv:@+3h2gO: }*eROZw8) 9""g)4@.!jtd2"ґ*I@&I $u!+gHc m 1%V+k'Y<g HaMvV^{VݚQ۳\p,n }ZdƃxXCDU쮎x㛿7* $&@U Ƀ Te4Vf+3XR䳟=_Yo~#]U1Pګ/rM9ɣuoAuCM2"5 l-1ᒗ-ejB,/W2?1ݦFQ>A>+Aʫ qy5XF=XDi4*d>rDO?MzC#ɘuH& 5-*"~,yYqኈ$fV}g~ \̐Ԣ%Hu7lf2q}H%ۜr>yVwĔE5]Z`>}#]ޮ9Xͼf~p}a Y%{[SРF99]>,'D"o?7$T+9aeEfN{gd특ޥ}cP: 6r.[ 8> /;7XcV#kSDت:ygaú5&Xyx7. W^Kg|dso|#+EIb/nd_BuDD%8b:D;tfhwGN\R~ΒU3#DAC %dbCmKgW  ,W=GTpֶ|WADTIIV O]_\R!`sdv)ީ=^?*|[w*DȢKcI-r!A[6'ۺ빁&H=%.Se&RLSSrߓL >$ORD >k*wM.nGˉ2\]&kZhU74ʻh2C;ˬAlYe#l`ʪ̮xvU9ƽ{-C9} !6C:ȺyU&{m5H dߨ/CNyá2A)T F7+8|xf6n[ۍ" BL8^id]!&0ǨBU3滬̜w@6|"#u?i9}Q<?\R<- s_ka{L%)C`JDr(Wjq-Erνm="ⓎMgt)DR $v92he buQW&nj<*2F?nX9P2X,2%Ƅ;۶K_6G!~I@i2|*$k|㎑b]m@Wf:gSTy ףMΚeBP*7ٳuX, ݪ򱏝|= ithWSd/ 9W#U|r;1&O8l&T4QTzJ jeYH#RR>hL BP,o"hP=+ZS&6dͱk,#kT'lBhĐ=u?y)SW=kz'b/ҭ}pG7+/Y|ë#>Q*$*Nm-TB̊du$B@ lk/dz+u0ɢ;k2ocxT#i髯\)yTLfJ B@Dqꐈ~زzQzͮCz{DTTXQ/$;j;=UCJ]U 1!&hz :d&kY+_%1TelV. ABP-׸`XAvc)@r)."+>9CtgWi$N$FvjRm/H$6m+,ͺړ 0Z,1O5HqMnz7ʐ$F'sFў#Rdq{[|XȻ޳,wGV-"ޖg=c;;2iP A]LѤFW\KG#Α"b"!/3RXc5׈՟E/4`BI` !mD}[>1TRbB0%ڳ >.)"<{M.h#2#4`RR謠^#Ft$nM|~gO[$ܸ=Hl&7\H32)8¼><\s0(5I56;c1$b݃,ea*/B:t9;Mnm -@ AA @$Km;%W^Xe]m_u_Za+ՊxdwWwvdP<\=3AU&4{VCJo<)$8_1 N;ƭ\w_ 12 +o*7z .*wGg]$6JU;M#lA6%i=J?sGy}y5;X^p˙gՒm@P};Kꊭ*4 dޟ<#|sѣj ]郡˖$Td2qZS&\2RaE}N~V.,;Dav 3l.!dƝ}|f-;oyOL'yQTܧ :i>fٽX@`q,5ɮ:7rH.HŮf="0sW»tCح*"De0[.WL@N)]Y}uĻlggkm{^=sQNYǎH9o'-)a{,.~{Z!RHA #)GNO#3Ld K^x. SHh[pVHp:m"5ZG1qEs6+}̀؋>6Pf|w ~ߔ3nLp)SFXg`]=gdлe”,ٛdHٞW\6o4AT<fw.w5d$Rg=&QEwJD܆Ҡ& ~%C**v`;oc?cΤh[d|n|Y|Yu&U`_YN]&oIv'Ծ1&xpk~0jDcu*%="{ $X,t2O󰘌9ߕo!Ki;dOyPxޏMηE`"bFEӮ#B1`>CKzP1uWoUwO5F\~u`^BR$)2&KB1q-\"tGX$܊Du {fHE @+|*J@x8@e-2|)8Aq`TnQ(MUIt)(wC3)(DJL%OĔu Oy䮿4BUHF3H9Hc2)-_4Հ^v>6+KP7ŎKJʬB0c|]Zΰ75cѷУ)7J(O[.k5":xO.pa}ud@{i i`,Rެ[;V~׼,VjS\yն P$DTEJ_ϲ u\pjSO"!ý m.ˆGNQsb=և|ͻ]gRwF^i,޿~FH&0F{%}hIOzi9⹖``X1bbQ6w""Z|VĵWOFO#=XXcZzIe%tY!- ]wˮ^3a Wc&< I[e!}>aYhi4/z܅+1m{z:d]}JBM3`JIC= rKv2MuWwa%[埓Q:";d$}ŋhIp5vA2Ei˸9ZѺ*J%o+FJQ"5o5XF=Xsy٥[@UQZCp 1C`Ϳ6%>ꂢi 9#D&DΛ:Q'>&xxs6>s7#Kwmcxl*1ن\$$SՖTB@LUj⠞&h4RTU^[ȔH ҥ՝ց.^e3\\;h@yBC ̆ĥ/bzFԋbruLL˿z] ]LYXe]rſZLD[@ yqȚVT5 YWHGDRy2H"]*v|ɋ H+Ho]00t]H}XRleR{Xc=kDcujfZ1FQ0-PBP^dPiBu@sm*ܹy}[]e|@í*b̜ XXwЀ,dUPTO i9kr mFtC|^3`mV؅¡'7 Z_]%\۫_zײ =ųTr궬;_;s}Pi?_ 3PfOqR ]?NJ0=[.CY?& )"x@dsX+՝cSSZIHFɍU}zLP=*z6As]K9wm7-d^՚{{6ky5s|\ʭݹ ꁈ,*/ ~/~M%&<=$Ȥ7'r9҅J$ =:?Y6Q+ze!(4ˠlASgSMȶ-Z O`n7\P(|!CۤT   IDATLTg>m4@ f6tc2&3kX?`9[3qcO|  eM!_)ҺpM)o-@|zՑjXc5üF=X,KMY' `yr7dg [n}r ,Ɠ4=IŁϥeB2[zrfš_ՃۿwiYT}럍eC˜>CW7Фєu񺭷{?D kؘnP͋c~?c5ցy5Ag=k~iݐ;d;ϬJ4Q sdv !V42%91׿%kdQ t aBwQ )9q^cճ #$ XۀF"u a=6I11ACF t{|,ي VA{"%ڦ V&E1L1afܮsMJ4|v2c<7Hs㖲JQl~Vd+.ߺ{&Vw)a,txܷt]ˮcIJzkc,,]=T߷ՊiљH^|⼙PfJKG7_;UJ}ܓlŔn9 bg7\U)`D+F&kt{jG]SvH"5I0,Dfl׈l=:XՒЈCK!1Ț|JN{5= [/r%+# 9HU0QɓJXI>=J* Aꐢ%يŌb~[#҈5pUCǍLx׼ޡ)1Yvwev缁(M;󚗞ٕd@UX42#@bdy0&]z x:iJ=n4y#1h,]=>}9;+$L#v=F.56"  iހd+_1V$ccI@#!)1KMHd7kXcg/~N aBs=("TbXV.P "ۧh&${\o2"8"J71REUH S`@Te&wl7Xj8qvvM3rÉD@lH3-]#4Hl-4x޻웪>f=ޭwb(" .1I}&*RF5dž;cT,\5_z[%אvWMTP{^q ]r?D" Ys8% Ĺl[w_:A@QK!^d?d'yvJE'dHnbu3_V梧\{0Xc=kDcuc[^xVV1C0G"~?zʍ, rc{MpUuo(Icꌇ|N]h j"D./,K $4 :.W\SJ3 G̘2M*NG"SD8g]0;1zf1]G~5;\eln__7ӮРip=]2$UOT)I.K3Q§`du|&xsg鵟dlhr_0?f3NDJ46lo &GOBi!嶲aV7%<[|af~QkwuTB$xf"~ޟxYL /{Zˤf﬏\JMjk5":صo4y/3[>q2-"\*M伧LxWe$>J T(=)9 Ku!M&&pxHM*L@B$ey>y&rɘ\;1P 'E2WLWkF>n}RJKU}5Kʌ D5zV4%UE<Ɗ F)!c-'wax< &{ӏjM׬Λ3]3ө+/;v "P8C1YVBcl]/2FdS=Zj=L?|&7awHF|3_P6[sox/F=X>/^hTbcy/[L'RsE~io9,ٖ gn&d- ̞M_vwՅݢT;0 I7?\(jEGfYTBj~CeW,,>~ܺ/ZsXQU@Vˬ6NCLE2j/T!2γӹ011y^I|Rtw `g@>]չ%Fץ{t> e;P9I"U}kc{nas;_ZlALJJUEL߆kk|KfޓTyz<7m*\ j 15P\ { 4L5BsN?m{%byyGfشAK6~]cUNws( 8*GAr$ xo8oVSbLp1`ЪFЕaӞ`"{ WLD`QL7~R_{# GlD^sڏ\9eCv|z+HXOXC.a%`( {BTcH fxf:9 5:t3}m/u6 F=rٳ˼MJ6ଧsS]]gqY_A1Y$쟪?e4j=#u牣p} "`wZ\,[Ѷzk䘫#R 3bDiDUe]P^ߢfے.;#U0̬f{NܿC 5ԁVjGB}ϝ0Ǹ ^_Rs :;GTZs;حsM^҅/CKKعcR$\rD@l#N-9u߇轒-h.[f$u YarO]84DY%LIqy0;&}U%8)>jY)9EIR UqUkEDjYCĨinMφ$rIvNC 5sM佶=?R3;Hwe͊s*t1Xۖ yQCK@fOA2-Klb+oX9ƌ|yl^" !K'of2=9n=s'*RM&ku4OîwIoavkRKzO= !ɩ !('z5[|š&;i}Grs §]hf|DMZɱHF'4|E>ڤf$ƨ/t݋^RW`%MbUs/R(ڐAjf^+TI|/%fg_cLG~jѡz8a_qGl'vq4vk+T5U5sy__~|kI{ȨF];s`GL߷JD{Q^6)UU)P SHHxR\nկذ<` "ߺ)K%Q,zy.)3"QT3ڠ[z}r_?+jTcz'9BD{ўyg|6 F6qgX,QUTU&8;r v6<ʪ%ffu{g옒#^Yٮv aԬlfO"~\4kUl[([aRgdR.Qlc(Rc1[޾(4y+ѨN7l󭌢\ն9xm'.}u{IrODL;A >'C uy5ԏ^5 Nբ ^4y憥BHZŸm$25珽ӻ޻U6CX6?̑G9.(9""\{/omC3\d YQf_nT_2֝q^8TI L 3$dM_BLJ%1$=v߾:92$m2D4-:Ro5(rD\]O,q"`f]u Lp҉~`$wV9Z*1(H$*z`楓GPj~ ʶTGx"Ӗߎ3OMթ*&N<=mHfYzrʏ)E^DK5d[rVQs3uMW]l0PC=k@C0$Z no޻.pկ5`m+g=n n{޳}S'tP3,>z8DoaV-r"XY ׭3g06%>,1X$Ѯ寵ZQ(PR1)!4i'"b@EsL/?ے%I\b!tʗ]2~ǻ*蠆N-ʞ؜[o̧$n۵mHeGsAuTIm|Tɔ9ɧE^hV4' -*B]7 lh~Ŋ/I@ngo|Z-%>ڵ"%RJJ4x֝A"9HI\tJďmGXߥЪ"^(;h>]I^PC5 >/?zԑi#Lm bnpBI ;fBߑNsO6锢4dB+e->9Wt%_|͆\9oV? /llHSaaeCPĘ>OF’ϙ\Rk+@B@,a>= s.Bl:^d<}:D{c~fs޶wL4DyEL W[{ìړwwKgQ%AѬY1P4vùyُP2mGDI#5wt m8smU"#=ꓟi?>ΒR(pY1]'Vc@$xrjY?" w ʮL6AU1%|+BD5ZoP@2t8BPtH21w}'f jz f%`v:M&4P8H_r:om%4KKS4a+_*rLαc QB믞Ʒt5Ƴ:?@mȯ?W5 2̣N{m@qLcU "} @-aHܠ{ RM&V!JMX,i4lni]s􊗯7-O3+9tY9/HT!M1T.LypQM*ms>?+uf;}K}*]E&7&@"qwM6?*!*#dt{f8 ^40]$3rYʎM4aʍND);1j=P?4Ieki4% olxN\r=ml&mFN}r5xBK_<{;tړ^2bw;,H}²A,s7;&{O94D!"w_0غ IDAT!WD U Kڃ; F8m)ǹJaf!MITI\fEbhC"ŕand75vblί|Z_?}Fx,ͤH(|'FukPy2J{τr"=l!QyDUЪO+A)T !?BH:ƠUE1m\qR(!ݪ?36 YG( ;tTWUUyQJqĨDk0ݜpd鑣 Tde$m=-vi/٢5!#Z eES.Ty0sZOBrfWwaM|]Q56-7hD;6h{^|S0^5oIݝ2qdj"eKr_:E̡꠫y5ë~4:]nmF1j&[m1޽s'-1ݝwEfxWnkL;6h<Ʉ*d95)Hٜsv y׽l.QepIX,+f=yb@1.l"ucu[$6e'Bi)iV&#)BifBJ\Y7]6/%˻%9TL샾>@aIs#Ĉ͏概_85]Nlwsړ5HqER92P״Xh/F@Qk7_2<ٶ"WE~vm*AޓV-fLd!F#zp-k1"$d#R5!hV|d`M_2 ̦&Je ZMT%b>RdP:\? O֪R!;L&!$pp oI~Z&cR"N9$UvX$,&ݱkkT;GN׿S1٪Ecӊ$PIU 4;pkYDPe҇xoL$E(Pjl욄(C9#)>\ mn+o~v 9(dD:[5ЅeaHE/fgir+!x7-@ 2]ï!&;4eqvRAgu&" /l7H2#=ePMhs£3Sk;?-Z#CTU4>ّ=e ]~ɤ{*< /f#.|/(p.n} @x5]j.r%'eCS~r8c59}[[e* =7)ꒀ; ʓ 9ϝD^[yF_nn^X XD"D53 C(꜡!<\T{<䧅% QyjZFEO: YryTC| bʎ{޼Ae%ed_B-([zشwsk)|jΌ-z[k~nA-؝Y Uz X {NT솿QU#-weQeA2$1*c}ܙkbbec$/’2%EmKC`^P^JG/iiLO1IfI=6A=1 m!Ht9lA=Raili AV;BC 5V[\bZ6)N>8- ǻEsƟ AME}dq.E|ߊ&$ l[C؆{۶AU!,G&s@1%>!x Ίรs^s!YgHJtJajKΊRcT.DL7\gof`3Ҏ;,J4dSNyrGQwQ{G"M6Hv1D,:@=^`b>إ%G6nAt>qVDl\&y0b"(ĢvWI_0Oc^dԠ)U@1Y'HlӅ'0huTC 5VjbR9Eri2 &5 sLumNA>" Ag>%3,N& f9* "5NɭCxU"8N36iY9!* &8>Pˆ}6giz!IMT z[~&$Zc1%b{(I79t]Rпy쒉iⓑs";!uy)>&GL_&x*[[Xm- apDt |g\i$1`PFQ}'7ӹL`P7|m(o՚æ(I ybkK/xE)& yrGYm <`&!fH5W`y0!ocqMm3rL 鐜CR 8 15!˜TFPU=C:hh6:Zɻt82ky 4j1 QԱ:G˩NP|A-=lo4I?@* {Q{ JjCұ/vl_OƜ,QY,yY%k!˘ Ta6bg>c6m T'uZ0{h"D+G2y*XSOOk<QLnL9y,mY ১W,jOz<L{bNCUŭĂErf5籪C j$5Z -lޠŖ'ot 5PC {kտ2N~ܔ5 1hJg=gCn}۵'cfGDdΚ A!mG 4~㋍ @j=Vw|VUBMSnBD˥f}Y:ٴ&oQM1"D&NpΙqǪ82-,u|Wq0D@W~lGLB`I]ZQ7Wt:eE'WOF({sY{̦fw L1:NQ޺ >B'k[&Icjb+? d;Gm Qc4 r=9WLi3>H}iYELΓsu&{g"k*ى4N7cvw ſ,|PC up׀z_7ΑPiM4E/xOӗɩsnF&{mlh26a  FW1W=&;h6V%g@RQ-WD92gU"|[g=O&"9DN@uS"ƒ_]*^111$#vbk\SUvd<"O7unl'MRwa^kztܪj%DHiOr[D)|ȤDN1H^TAD&֤3`YmN=U 9* SYZdLZ}Y~T7yL9'U"hڔJ=A TT@R,PJhv1!b<]&F*^xWQa]aڗAIͦl\X@'?Hfc!9Bk`2>iբۖE VմX(2D]VKVFc41ϋӢnmkhYF33%88UXYɄtWmkRū~v?]I~inr.ʗ^4qD[4'bĥ'{?|7l_B=#k|fSYmRh.'|GdV"Ɉҵ./t4"KzgIDN& Ԥo8 8BD$7Lq{Iv w PC( UJjF@QS93N5eTDL!H #; CEjPS9Kd,L+U4 2@xYm_6Vhc2\bbSoL;*b4˹ .!k}㿻mgײI1~f"đsH$!w+BMHOB겡3= )<ٶf{wiάpKvOD2fF)1/2VA:6e(ztjIi#|AٙA Dشk/͕/\S{޻)MD9I#=z7Xq&1,bwvXgLvA߽#Y(bv)saIT1=J7$!w+7EY纶F sg*OQpZ]n׿q5P\ {O+_p J6 Q&M>lJ N}Ro5Ƥ99H9RdC8iքHਏ~!fKNI7Sݪ n,"PA"9^HD9(Gasdd1N0QT@Fra3>+,f4 y} YA֔m^ <9# oeyw'z{?I{ښ @ĩr3k)~2Q,eF=ś厈 acFhSHɷ{pP#^|3t6Kw'rh~ɬ3D LN vkIGիⴧT)7-ţ:IBј mH"(#9lSX?v(O4D/dgȉj=ԁQ'뺫?oAImv% BӪs&B&f7-XĆBv;LG-R.ԓ` M]P <Ȕ88XA~U j'Qj>zn]/%V5jMoF"8Y 9HQDl!9d~L)1P"syUK|9yb={bU!f)-6B4_hԦ<,"%yAE=[GBdtJS#+Z_Cz챓wߴH͑iZ|04O<ňM? "* }g@!R}9GίX$RO""ܒca/w&9"rΥ[UA=Pi {^_Tfm& fi_r.y)h={bdHV~{KL`R"Z."LXM|Yg"B!`T_;/*H]+^8ǖ& BDͅD5FEkbLȸI%ݓM0P]Q.42J fW{D1#Dl{4JCޓ&'QwJ{Pۿ?sޱ$2(it٦4wpX\z3[dniv~0{16j=V+ B9ƨFGabS>.KwiLݢ 'x}iGفQ`ov2yg:,:2-!lDt1rk!gTѶx1nK.!2)(H;41U,z5Ԕ!IC --jto&M#'l_3 "K! vs#B"W`:ͪD7kk4xL.U;?$١FtEɌcgq˒jxXip%F<dB1M&Ty* fI HHK&QTo{<ΚJ_3׭݀֜Hiջ{lAqOĉ mn%wgݦ dj'W&"1W}h&LWo<5Pp {*A&"uD):xOL$%qSG_3cMCq!1!QGX-#oIWl*9csMh2L'͋jJ+_2KdG 84Ƙ){GD> bK 9%0Wy`żo6 FٌSNScSHb2LM1ɤׯ{O 5j5Xp"lm vFEYnSvFK.-sEj׬![eHe^#Zy&?Tk EHQ4c߶q5ȱy#DClP{5AZ8|Wo7=g3k+#k<:O4o *1뷷&'uMUMu "KI)ߺ#5Uwde爙Dɦ8b~W+_Ƙ(>~-J+g>UsNi<"1޴޾?6Psʵ qM5!-Q`Q2.**o.PC uyu|_ @3 ˥:(m fXapa%ɱ1XEqqpw}JDG 2pZv^#f5N M>/"D$\|iudXgY-}Ԩ_܊K_QDVho5'n[ZӟVP窩VDY"%(M"j];O>b aĿ6h_[zSs(TEI(jho ~f}INtY?mu b@Uhg0/hlokSvK#GYcr")Y̨|EN.$A=Pe {ݯ4Q Mխ/"8@UOyBJ]<]VU&3Z6'AxkK(aq}<J ׮uiv5o1Nmr{[)OQ`cM&*yƯ?hpSY>KwB2KiYIۦ*Sja,K;;`٘InS0:37߲T;UMLjzc_DwmIhflTF9gȺ$UEU38FMNpUӻnLh9Yf*Jt<ʲ{kk^?&׾響cl4)? uQ:Ձ|ڒBWmUnCkSQC .&J"d İ3f[!႟_C 5ÿ=ԁ].eUnS,shCOO?k/%"66xsSKiO>\Q#J# E,t{SJ`2_ye/nb.~ZV0Yi dBU=3KSTb"I'-OSJKGcjD{g솱i;oq|_ Km[ gYL{.8欤ХhSwx-xWA1A|jt^KD]ӗ<I=HH=AH@*CUQ1;ܰR紽*,bp8?Ikm[,,,%g0cmզm4nN4`5WN-^m7<<}KvAC yuԄ\@pp!(DthTv6mY_0tqoDQb/!RUjvOt6cU|z?/)ۦrRj63͢D`O={xL O&4% _1-DȯA:GgvODxguuL)+Vw.#V7vڌG59ǎ{v.hy v?e^T& ~s'UM9{ }m}'Ɛ=DĢa6Rl`h}<]k[֡ ʴvGe\{r͋:[zpNf` S>JuvdodPN^&d^$.քIg\|uBa yH|X,u4&f8g#y);~+_mF#B&TD!uG0&o:mkn&Ucq߽[|NyY`4dLd"I9=U Ta;uhbEр#pvmTu}5HΪk}!om[],5dEP3"cb`OvRD }dZ]_ԧQr88v`ڶ򾛗(}8!F-Hg"sE]+4*!o]>Y#ɍ|7L$&e"@kڞkKNwR) ( NB@ Q#[|t?9|*jn~|1f4*EmҶڴE(fJU0/jG~\t8m),WbPP Ae5]"X|.jPpo8v:gFOyBu|0j)::갹u{OtLɶ/O|M(U *D5%^h@CEŔm<&f2HP5DH1]ͪ4giu 5+N ' Ha4Kq۳G\l(PXRD0L2Z k#9yP9Dt6;E`!s 'iQUE% HQMD$u"*MFH/(ӤeGEɣpIEM4Nf^8G!rECb4s󓑷&wU)Op$qzHtxzj9a3;-i}:oOz C 33?ԧ|0%H߻;N&T4w~;21~jfV -2*[ 5};wcof87';Ї&D+j=3kK>Y1%ֶhHʲˑd^fqmѭM1ǔlpɪ_b6|0QPB1RXQ( 1m^Ĩ';Zd"*X8~.W\Ue 9Iz6)xζEӢi4 =ĥD$izԴ o3?֜DJڠM]VL o{b)VmI{;ri|{sg;7T)R4\DhVηu>X 5X=#r{꜁f%O︳9qlF`ޛ L=4 )jYq8!#P0EA2Tדa$D ÆRUŌqc;W5M@8Fno&@YN%G)X:<ٮ Yg!*3JY &U%xI&q޹ӛo;1])j55*iqI>Ŗf)E烮 k6m A [Wl[C AcPp짨A#*9"*ur1OBⴏa'TSi:| B\YT{8I_'+l8C 5AWX3~3ޔSkWnf.ZMެ:.L1CPUtt2混LBk ڴdMWHY]gz:_|.!3=m; X\Nu[&;/9Wk2 7- V)uk8_l D%ˊEI,8;DdH '~|$Q%G$>HQL=uȿC 5]Y/Vӯf(&:2I qU;bkZ{۶ԧ}sh䓹DPf  %!4r@WI:cC 4ЖME*j,PP$6I%NP)6֡@ ƍ&5)ɞD ZTy/IĈ3N6@m ƨ,yԭ-i[Ap`*pVe(̪mP,SQͶ(]M1b~}ڽeE/u}:˘L= QAp *OiNw|aFF#)֫$&L^OpP:8wt;IW_{ΣHJD誗mEp1_8#˄X9&O8m1q)fD)D3d; :pc:#킼 և<ώP/M~(B6lJwu(/yPOvk3ͨ9fG쨮ر@mf1!(;$ +!)Mc'hZl!HHw-ACHLy8&魟%vM \`mc}hn9MK#@0߻[:m'FL̇1?@d m5Rt9ahޛs@8gVUB&#CU0]945gV-GXQˢ ĕ 5PU {Gr O8ۿ`A+7\BZ5ɇ C6g&ţ x c(Ǭ9(z$f<>صK sr$r9)4vi]K7S<"*N1b.z_19sԖ|N:RV=&EE8By2`O@9 ++0|nEjpog»/,miDdm{n2񘮻jZUd׺a+@Zս^Ii[O,(ΐ hsҟ֏/~QjGnlW?svk55Uww{2aflxLuEe1|UY: 񍰱G5;:ʭ76x6{(ێJp_s7 $(͌ X̃hFILf2`]?krW^*wvAn:,WhrnLm񜆲B+jTUэ&^kNUJniu5|O~bYlyN5Z=ݿ@{Vxc.w37߾\cC}O/[kYxf_&)fϱBgΊ%PӘ͎ػ柏&WQ]Y9FUgHnwyp T7pd*ZDF)qOXbo?|p:ǣ7?r=6洳#64kEUFL)j!$En}VAP^>ȾU1) $ꑐ*FV몄4]vs;h7]MjFWj3b"#,2K$w=iamjqiYX PRºcL1Yb/O.䩦nWꌨ3 |wW]yT#Ϳ;=W#srpCwno b?#"xO?A} sN'CT\X O\*IDR&|~+ߣ,T{7j&=ՙSozOLR<(4{4 wﶷugGO?=/FݢTno,l&k@#R4F?y8Ru@.-)SbR^N eg^%Pp\z{կ|$l@;GU=^4!޾{7+x+CȨ׿zs-ؠ)|j>pqܯ;vvތ&hI(&fx޳#gZyJ >j{jUG^%Բs}f;,y6HFw0,z|d\si[Jdӈ*h*LFKK,E[i4ELX.^IOX|x "_"|ݏsY@jTgVQwUdx%p/>'woo+F #шBUO_|S̑ "<#4*1Fqm&x"L}U%\X4B5lzws=Qp$WkRDLe/]<2%~ ];?Dfdhy09>m>Woz&8E^A!^yɶR)54h7wȮCsЁ}ꦐbdJ\fsP97:F$ѼT5&\b;g9H[2Fs;龐2\&O`LFi[a>TjTg\9οʣyˮ]RC7Н{n٢g >teG$CxӟYW M*FCHHlVjHXŋ.AĘ4u'V}F%412жVL6\f5ӱX(ą/_OTĬRͺ92.n}Yo$d\Y9_mcA OXjTHJxγdRo@(3GuW(cxe_mبmY?=6RUItRBp*Vn!m[Y,c)hua2QL n/X|!gh[ ADD2k4mRgO{{ք:Wuh}o !VmCp>f9:EOԃ6e"JML4_>3ŧEC!΀h>{ԛ@wxdKxˮȳl &Hc`1A wܚd HS=@7)$IiOW\ia |P͒2MͣXJ4)rGeISX1)7wy!hS8oߢ7TSMu yOuv _N&?k@# 9E%a#D$Al}˫uPkyݑP#Tgn} *0P4'UPy,U ,a߸ȗ6aO ⱏigsPE>Eĝ5DM_<ۉ[Fj5yOuFיvy=!`{Zџe6'f346M6r#֖\qev>lm+m+<Q(hJWfl@ Zƒ=I`>vAɰp͋m@;E8aox!ct”\]lWm3g7خ[j"S\7Nx?tq A6̬&wp䓟8sA7M*Ӆ(s[b#k8oNxD&Y^?Bw@Zg=ޠ[Ȕgt-ۺ;sm^`y}0Ǝ~3}jӧE"FA= 6o >hysKCU+!&7ƟL5Tw8:Co'gs|wc,)"'Ol4 DB,:fME27;UETH޷җ0"4 LɌTŇs50Ȣ|cqA[+L5_򝮷2<1$}"wMdh-?dX-\9_\pr73 c QL%%.NG>=n羬yVE#Ul#bDӠmO[c'B@"Um@Uj_;ȬK!gX.ҖK8M!V6f?Gi5k)DРn%Vxɘg "XSP&; q:E]m IRbI Nv,&fZ>P9K(!F-6?BV4YF+/6޾?V_!o|큠-bDٖBRߟ|5 *wnD64{6L\70r>GSZ,]In$s1q[nI\S8ZI#o͈ǽ K%ש}b!?~N~SM5՝&=Twg﫿z Gx g0[w߿+ F5g36 ~,&qUnA x+ꋗEؓ#dM;}/oWIJ2aί渧 Ah+F7k1dl`Ze\Z`ag؝3\H(a1dҴfF ?{uGNu s4=69oDQbF R#2X,:dxlr}OVa1^yvӈuhm !ǝ?;< #-E13SsYZsQQOH-m Kk xƪj%XqU.'bO ܏/|#Q\6oTwmN5TwךTS庻o^w4BbkK+!nB>H!x4PNQxct i}HdI.MCg@U<9[Cd \DبhVK^zIw=6`R'3 -m{Πm+41跒G<WM,S#4URdYX$+Neͨ Lם@Ίw&CyGi@-\C 7CdD4-loֶQfР *AGȽUb4XݡXń0+o,|[Q11&ƄGVx(!['?>?wl>uGw);\cO缗+j-tBqɰ7*2 3+lMg;}sÉ91F]TK9K1ƣR̚5'awHP,ia $ *jbymn1Uvۨ4iE.r)\#I.fJ}CTP5)u[۷y:j6 8*?IR9Eǂ}m =TgTM{co !wpKxʓ>쬗/uYec]in5i@ DkL4&[)n<^C 0l$ڻ^moxDu#=3糝 lȽ=V|kNלXQG޵=|?g{ ]F&07roSLsݣEz.RVÆy>D$EĘ[QXCrxܱKrou{Tl>) xgߌ~T]9^ ]&:cjjh$v8K+n=O+Bkg"<9l]%*_~ _"nmf;U%v@; gա?ڿ "^U/:=}Om-SnMGlXG |DA<ǓVkʯN'SMuFyO5_w]CzQ2scއ_*^n ϮKLlUh`ݹ}<?"pIFe+`ّdkk!Q ݃ˆ»QB캁n~<b<+6ۖ;;xړ;}}2)ن2u}o8K _=(l~v[͖ sP&$wU,;5 GXgаI(gVRى%*,B*c F1R>pMUwJIt 麜~r .I-T8YYUfYj sC=S!#'EeT3Ob!Ox G{;s/2Z룓)ۘYL{3j3&{auW]yT\|i\fi/D]˽t=xPFm+~pky02Qڏv?O~HMsm)1~{psMIg;tg[mmmm5%1)*#33+Mٟ$F;MEs bǮ-wNě'"MV35, FCV}ǽ%՚ lpf 8%DLtcıcIO\ dٌ F`E #Vk9Lfs:^}<2;<]#+:eno2Og,.MuYc9>fXl m|Ax.[moMꌨjX=:P(3}Kܐߝ[ qlL1E?u}QD|?gWy7ڒ,C)F@,}BT$+ѝ-yq X:4A5HLbbb "\Hdf9pFp[x#yi 9mI es=)_uc hj@ZBY`qRowd,YKn` 72hxvR  yO[4ksM[0 natY##27&m3\̑ y,0Hm{=/|V[ggM@Ecq`X>crK/6G* <(4-kk齳A%9ĥ%0vqo yO5շwW%ŴDଳI vrE)GF!kka;tD*$5 ?lx[UqJE/L*b=8s Xep &+nWxyPIQl:\`5 vR/9R} 4BPҥZ%kLgJ\CcX̾g*pwŘ pdjd\qmV0t" HG%?dUb;q_Hj%<癋{sZ?cTɢb:j6joeݙ7\]! b} ΃d^ CJMw岍Պǎͷ#GtnxKч~k5.Ň]x>Qvao jScՔkO[%),[bq{+?i|QTAj+DބܝI`>\6l$4coWiTK<˾ͷd[js]M(9oF/{NdiU7K= ̎ײA{MjW_}{O_6*H7ݮ}Ϗ~tjb5qSM;mg'5OCza}8[|"Jxxo"B7mwl5cLNYE)^ށs!+'neFlcDž)3X!! qcVƾK7hq LCHx`+^ܗ38FQDL9RE(5`~Ƥ9XQm?G j_k_~=K~|w4] BNLٖdo;ڇd4JGM;.đJHJdHc;3X}IogwK)tI셫ҋvF?G1F_>YT8cնI ) ϟe-ICU{K^$F PmL7rTSMuFyO5շTk.MݧUz}ⲷ8 hJ`6E;^ _&:I#ag7pZQ3׮И_,X1Z)8i'Oة=ZC|G*~ ?]Gk^]Zϟ8L$TUi|k^qPR$JKN.1omȌd]nulG1 XT[Y mxѡLI2b +^;`kY1Nu$j3&=TLW^qTS"QE:,'Nأ2\wfxCTcڸƆS.=)91ɤ (怿T{-= 52$QP+E\>,\qYzURP@akv{ ې }/zS{e3݊R*zG@UB8hϤ|u@7%$|.m+d?n:ZE{M.>و{4dIET"P}ͷeӭ9Z:|!/ZfYoz(k-2TFkcJh(PUO`~jSMuքꟵ#{0(֮A!2بb5(׼`H)c>Hxȃ۫A6&ob?ܷz_m#T$j͠xHfMhmy_=ƞgG3Z@T.X.ziO-򑏭D!{4P)js7vU>f{NyF7F\گ,W3CeEs>|MA2L/2Xl].VD#"F72 =4Y db#6ivnZ{䠜")jif>1 b5[ ]flxכ7"C )ʋdLaU2\ GWђag1:j5uM5wUW Mqt^ 4}NF5׽[͝s^}M?oq࠺󬕶->A;A4E R ~.RBױu[Cw{oBfwrRiGxo$WN\%j>Q6ϰq;#kZEx7iZmKNSNJܗMJ#KݵnLZLӠ sXHuSY:B2_nNA[p(bJO~rL{Δ8辶?30ѽ܁#۶UQq͗ (ߥ7vG5 )REgI~3 A)Agͳļq4=7G_w24.̅1= KsLّ߳.ܲtef."FMHr;!Fv7GmG40H"*0株0ME$ZTT]G=]7FD0kUTX^h}dߣmqdL*n$LJOLR0˶9ԃuk+I6 Jd)yω̞Qfhd6C߻,$QV˓lH܇CScͯZEe)D  {\+뎱U>X,d1GomO?㇚FDEB`%ƬijLM{φ;(AB] Y;jM#J6ӟ}c) RϢ(41ȻHt>,kӻ2s= $H_j-@WU AڋW9G7,[[J\DT^S~^FpyapW'@ihQEEeLDȇ]qGŵmcYxQSrg+DUu4 w\1b;5,_wq$UHAYTp?'qoUj|8"BҎjiUvHr,=~J)O|g?N hݯ)fwE0fL&AᒙAdRb#"FƄ4o<~!Gv׈_!ҴQK^[Yn?̊MxdUr׼b7~gҽHR S6+/=Z>D20OmMDV}u3%5Pk fb̒c, As:f(uMhSn0܊DEeNwyN f3OPPq! ){^0\r ђ0*"B3oER)L4Is6Ee>bÂ@ H *H󀶷a9TSMkyO5՝+j kz ?a#~CHũSĜ>w,2BӜv9 $l V;П0"XwL ]xi]+$R_9*b|g^R+/nJ>䲄 3,)UѡV\ַyIȋ4J62!fk4[ w|jg:lm#_}~任v$mp W?g <ȈT)9~K5jpiȬELF!uEү>fREU_MF$y=~q5ozljYǟdި 37 #}S8[I;nGȻWQYoˌX#5+׸4 N^P]bz`/~sKw8 JYR{>{lm-XonL\k߲TSMuI=Tw6h[xLj' BA~2B[SHǼ[I>{<ѣj<^۲X|"HQfx+TO.F0΢mqM\njaUoz!xx>՞4v*ʖ܍*U%Lwn+E<ً\H=#R@[V֓!DJiFxXrgnWlt Om=zTVU]iz11FLF\zLS骢z.cDHɇ1HR87t^l8 5>wyw,A{C#(@JuW_ U n6>) ;{5!辶S׷w_rU1{(H(aD(C)ϱ,ir:q\&?ŧ|$,TP%)`tpksF2H)V4W2+0\\ұcvBD &;xKuHo>wf٩.ťbBeч8[|N9KyheȈM #xZL<|[⸭im/HΆ K u| W?Z(\X6}W<}aL0;k zxV'%C0iX\iiKH}|O׹65k͇!yl|SyW+՗]u\y9uǿjQ.n,?(1ܟۿo[}Մ.PozO _\TpIk$&%dQi Tw9d3UVTAȠ8ٍh1Q hԂ3%\Q6sqogU(=Еmtitb<ȭ{٣dj4Bb > 3N 4H:DϩF4",#OĒSiRK#-u ɘLRQ x:ʌ1[k !AS̬Rm%ED)'I-A"ɣTj 1bjů]ۙRHdX!뮧+wD /x]]zL&"0dΛV먍K׽jǟT2kegf=B␔9"R, Sv5><SkBSMuרo.~L 9`Y^ooz'ˈp⭐T*ŊbX)kmћQNeL+ c R Yluqey& 솨qP7?gQ ]8a.Tp)X߶"nR`憿"F~ov"0$; |;pi)w ǹvg6]RT$USlRGz}ϔ(AE.0(>yeeɢSz+vDW)2%/N *F&#FF[pM(1UQlDäJ7䅝|=1)wkyO5ݼ&=Tw[-V$5boRTc. b[o{?x>өf+uU 00 ~ӟ6o[(ґ0YLjϰ)҄,>viawtk%k rREeS]eF;$Vd(5B G=bRDL#R^w~$eJ>5/fx7M cY &G͆ e|JR@ O;a_poɮc?vTLTf3'%24i%Wy%D{9k$$b#Y2@Pr\ZjujuU2].c9%$& FH 6Hc @Aqq_pVfl.}ƍ>{{4KLs`Ai%WNmOyFXx͛1ULɳ"Wt3K݊I깭{#ΔuZC?=`YN$j{`eJMʞ^yZ'm6׿v/֔f)3g3q.&WW>sQՆJzKfZWUQlN%w[x[m'ZcYDV"C`3F/njѱ5cަ'K Ӄ@sX-E;am&<{r׺Ly78wߴWOZJ&ӜqDL6P?[dΒ^ q )KMQv^d}ۖ%zm}ڋ/}kL_%JN 7s^s=9bK`ywNDpmQ)tDD1j8F.FD+1Ci6rV\.0ǐ*8`/(q[I Ak[v;MC#dϚe冔Vz2^_I/X2ў捦s^^GTR(ٹHa6 GJڴk]u6W@FR.K2 HԨk]Zj&\bq&U-P,{&Zb,itD 7nw#_&;3kS_7<3*I6-W/TU컉'DN9k=%46 sY,pJnrjtk Tj+&bƭw_^!|by91GOkbCO=%伷J;oXK"EڕQ'i yx!2J MK`m2`qA0YЃ_) Jm]h}ѳ9FSL3xyӮ#Gn{}vkn@wfNzUU<h]UGaD/[bsmeԖ `k,'/9$oж4θb8ti-LF2Q٦+G{ lru5U1MW(;# 'ϼsU$ UVj,FQsJIQ 2L=` yߗw|@u)9-CdI\bmgd6 %f]Dxko/}E< IDAT1ҍ~4nƹ]X5*!s]c~K&pY͵mt6QeUQz pN'͵{K,;ME뷪J/c:f% vgB<\\F@ L6e@eĴ|>mT]мr|^̴*ᙜ]%vuyGuj' ]{sUyn[i[1j GM.s(( & rZObTxD(v]= wԪ9E>'/yEl%/*-/{P{ҟ& p\c`'t*B"luǢ5DSU5:M2BF8хoR[T(ޅ'_4RŽ6}' ] p@cIՋֵZ7sLs}+"!SQβkG ϤX_׭["wg&Ϛfu=r_F÷JDD $?IݭO*E9 Qu>X8ǙIuLAbz]Ҏk.9VG+ҜjIّƼޱ`0SY03'EMnh Rs .[] crfL$f>3/At4" JE&kT{ c p0`l8U_3O0-ɨYΏ\.PUܴj/=8-j5-=,G@^oo|㏅/|Aʳ_|oK~jlV[6jG\TEq2.IТ [LtBIɘO9!jQM!duM9q=@!VhCGzu "&P>\MFFWW9,'1s6y|kv%mK‘wB!J@ Bv쭷wi޹S5`!F>-̞S?vĘ5B2.. Hޛ/G[27#zjfO>Wܮcۿ~C΀'{ $~_)ΊL ۶ohJD =+S$@K"GeřV괱hTģc[<_5;QômSO1&9*jWD;Gmǝ}"nj%:wXSrHZ# C#6Bж.`4rHeMH5i5˚]zPXÒ[ٟd/f%sH9NX[fH6 Yqi:[@^l)i Ds9+V 1qK4QV#ͽ{hD?/D:I Χ{Fq,K0`01&Qժ"Q%];%DQ,i}Q2F5oO; )Ciƺ6)@B !"%XH%]XD|7^mz`bPQD'RCzòBDĬѡ"SD=rvI`{Qkͥ8FAGF>i{qWFCPVKDA0|. (MQNc ICg"w8wc~h܌l׀츈7F#jP4A%+ (&m}S* (H"9&U]JCЪhL篮pHf@ j}h7=mJw! ;0`yplX%CjDgm6m[VEACu{9|HjL|kk<mLDd& +b}1;:[Wc^**K* 90G_g$螾|t-Uӟ2O/]6{@D|$_mCH{gfC5XIk^U٨ZNj~tw2,%-~ EƼa; )4#¦U޴ch2ј "ATu6u/,rPI1ڽTbB k+dBY;Uhm-]{ԋX}~_ vb{K;?N {^8FsgղH}1f:p 8gd r Qו]cɾyؿ?84;a۶0Arݷ10k?=N*{mVBIjGE4,ƭxދnc^TUp1uۆA /z%{Zʁr[&Vi>LϬi/?ЊtFoUI lZmrNZXߤ㱨*v' 7]Z$m͑ D{^ >"|l&d](%`6EEˊdvujU5.z䈖 >a&͜Q@ Ѻl&?q>1mP{ 0[:0]EÑuJۜļE}2<3GOg^;?b!h|gzǜz 8Z[ш˲MA^6~OfeļM_-@vSh ^}W%8m,sy'KY|c6+έLj;>xM+eI_|&k^W{6ӿ?RSL;'Vw?w|t.11!; =ڽ{jQ9]ޠ.wc6}{땕HcUTV\( *,C=-2Pd}l7gOlGh訢Dҫg收U Țu]9/-#DATvQ74''ffwa9׋k$MxLuJ*jKEINo!i#y_y11$+EWf A$@~j~xeJE9bL9m[1{;w{3^)s8+&w~dnQuLҳ1:GC;uӕjJkæ]XaG 䘙1ʱv^W"]Y,\B7\7e&frY}z%WNncnA%^tf|ȼY7v]HC\ݒw?@vB87Z`GAy;_lZ 3wq:BjR_lʹnTHC:jv 6 :o1Q5to)ʒ&'ś<14nL%/-^w<ɘv 3VVh}]-Q0F$Wwȱq߻~<$fRWgl.OcTV b9Č7aၯ9ƍmQb 6ۮ1\}}abu=uqu)M&\Ud;C&\,Ѳs^/?]gw畓 +'!H.dHfrm3=z%q^~ɄXJj|jMn#dVQf* *Km,<ȴeKZEX1j - RW&װM"G<< )û;'yE78kk_]%AdI B* 800N8}-o2y9փ GbkEtV8XA֫YGUQScIqq>W jS꽚[11{0# ip1_Mk/l^K3of* cf>xP2/N"ά@W[iǟ?x̟yFc4p}":*#=$lۊ}n`*Vd}@jp( j(2R!rHl$Ҿ_zKڝyG ?xGދ_+_mP`La6G?U%v ŅEbi7u'!ڂ@L ffX,)^]}MY$8" KZ,+Z= ۂ@YҙcO;0DR^(>93<@ƤhO"bm>tk2QtFJ:^Ej4m=&d- "v( ̎Y#d?0Yyp"Z󫿺U@D!Bo޾*UT7}6%*laֳyxu YOf3v^.ۯe‘maUEECʁ6լm-yܳ,<1E/G h1V'ZGc. ޼5D6=tf=Nk# 5>+ T%.&!K+"8>ّ߱\p~ƒaY7b7Jg.!}I`vܵ^T2EW1}s;ιií$'4U*Hp7m[,GXNEIFlVhvO7" xD$ A~ow 8.10NPu@|ä>͸ W^A4gI+M&X_-9'>/uż6~ÏM(cmefo-%s(sFW,.iKf74R>FcU TVV9n$9L|@x %9 %"V8o-73ȏ4< {>n((D^bk/?E8G/yn(BТ0dShDA1۷&o`z[O#UfNt,vXwm4zd]axO6՗,sKFh82G5 XsE☍D)3V+3BUVmXlXNS 2 3ʈ2 %UbJ|{pQG5CӦQ"-** *Kv=EYcH6Ѧg=Wz[1F4,cjb mo\)Yb:T\8"qpMFs=|D =t/kYRIN׋zֺC'nt4@QRYD:$}:ɜ+:qi4juB/zaQX>*MVsf[4$~v@Sw˒XrRg<׾* iѢ9ystuGOצ#wv04N\|ӳzcnʔ"hȉབ,'T^J c@v8M)&X/ zH]UUȦ}yӮlSƷit_y=}=)!D\pr,`x][VO?c4֨"*O=osmI؁x|UΆ⥯ma7Meց1S\#)\"=2\{tcYA}PL%xϱ@L4o7=ڇD vJya+.=ηno|QEg;Y>Dt&>tXjfL{ѧ# cƦ5>|DGi'2;`vVŖ)h(?piׁGoA ,9ڂ}wߔ!AM^<皶sp#E 1#2Fl2_޳wk 1;GRJkʭM@66-FSIPT|- IH*[x1#OϪ"kVKW*Xh]usDHAB D{<ԟPxF((غ9 ))LyYŌy*yſa$z_&>ykG5%NSSQ0`qyp#l y{$nj* X*#jhx{{FcquS4%f[~sA{A?,F{Ӛق!{a9%ge־} nOm|dJ֦ !hYYŘQ =&)#Hpj6#R7xPl73X 7DHS,3o Mۙ=%6ɮT77b7R:uouC$L+wQV :\Laf"%.zXHhvG*lޫ¡h2M4M<|w$B4J9zmC_(8b+[`ř9,oKZivg7h<}':|ж i|=p=NuMfV/MATp+G>zxkYIwj,Mk|d=:=Ra]ɎXHm+a~ӫ\)'d@'M|+S³vzKHH߳" 9Gk, Q[c9huM_m 9N)D^4H6 !nS`VqϜ :bobk-nU&Lj3f,]+ӭ4u4uy0U,O7;Fʂ 7QD%ވ:&6T+$2N&t׽vD 8 /_x ϧ79X,=`qyp3>m})8UQ3GW1)(6m<4j$gU|9@y m[tByJQ> 7&dAYhݱ!zCJX[Ç%["tb@/ߍygMA-A{畓;?p ơ8"U2FՂ?Cj1BEJ(X{y$}۶3JQuA#(CHg83\ -6bgj8*QWUf( 3CU2Dnb9/+}*) Ed.(ZKҌQVTUzԍE^E AWڈT4f˴-SRsh|b!pl5-V\2SޕY33|tФ3`y0٫^5nbKT4j&ZMmG;@)ՎDд.rs ApxcEcÖ͌)dɺespjD-'?{a5oپǯXQ2%ҋg bBH@b:k  $d>8.oA +Q߻7>Sy29r ]wi+|5izzGLv[ף*L9ԋi}_J'#^fO>~r.6V\l}'(\p*qQ,tjG3@ܴ b"f8xj)CANY"V O^o~*]WM"YcF)&HM>eHgs(@@%d{FqFsX V̉+KA83O+bh';5;.eiP PH 34A0?`"z翢tJq,T/Kb|ݶ*cڿOF#c68DA?SUXkbV%Uʅg^}7IO+%ш L=)ʍ'X&YF`=LEOO虷زNQ끃/;(u;L<^U6 5AU]l,{PgbfGO{1^Πz-vX1?}nVCl6z+"]~sDQĖF>5ҧ(?KdnQ7XO#*((\/$U6=xP^QWdÉvD\!m#G4KLt1QnVؑMQuMl08Ǡy0 }]]]|vN# GĂ *ppG-լfD&beX=(2M1>yLZ|ozQXl|Kfn/=Mhmy'V9$Udv/WeꊾuX/1~&D^s|1Xip?7޵$>,.Լ[ $F8(5WY"** "a~ʉ &k_]{Ί(u}_\IN}=i4"Rq (aRwQ?EݏHȲwE]oi4@)/}b6PBfRxɋ/ `}]/E5tG*YuN@<1h Z]ј͠E{ ܱ*B%/YC,'DFI0)SEbbiKL1W.l4Ƽ!Ԓ>Niz/JcqԷo-kO5G=Uq箺F"z2aK9( GEAޛY|NhI>S@a Ξ$ٟ{ϖ;*z/!(^w;yM"9:tg%YA:Cv Q"K޻xb Q% 2$ķet}Mۂ5""!ɣu?zk3#hcEBP=~UꝖ:Qsg6`y07^= gPg}OΉ@d6 }H>uT(XhDVLD(%>M=SbvQ"8 J|(Dh`ʜg6]=n=`y0 rUk"${_2)+Jv锜d FV (%$#2Byeӏ||^U(K2m)M'TU31zA~+]" :uOs4bmc#^g(#?wڋIـgd@ͱ5sLnbjT]UtӇm⮦Epel&SZdBY5()x ^59ҹ]- !ƭDs$K͸~$yNY 4r wL-uL9kF+h @|Bq[to}ӨnWe?`y0ڮ'XNT"x`hHĚ BD7/bzGgL\N3ҬG6m5h.r pܔi#w~Qd鎯P+'" &9 u֓vd̓ 'Q=,0Sj%7\;A]랽4?^<. ^5|yDلm'66ZTo}1oYey){1 >X_7f%=άKsdƜ7YA{U)=ZⶅQ/ق|X`c0v6ɧ!Hu?Y/xbJ8ژm1&-.zi A22:QQp{R) Af3/, ðc֔O8D;bqcR(x| Jw0tIgk4 rIJk/c1SYRYpQ )DtvgO7F' ?=n4`-{DMCKi6B {shekf-Ae &X@k & l^Aork[4H8Vr) 1 SU8?HHt ִ iBݏU=AN;~+pUY6.ڶzn2.kD"JtɲDY7[ 8E@(اnҒ :[JUdLeI-#2'Mgc"eֲW[>𵖉3ة=Iw -+sҹ_2fJb*/& qbK#1"4)zxՑv*r6^ĞTJb'>nk򋛣&({A@߷|% l 8ёoydXpzD;dB%zSwB @| &)EAW]>޹ٔ3YI8Qe+a=>zO?xVv8kI-}bۧHIذ[Mo+]y ?|BZ} :3$zcαG$ʒ6ʢ,@)F<=B)a4( LUEg-H\PN)y;g)3ᎀ(VʤAZHM{ۥ!U}9xLF#h4>.fm_]ocX9eGv; xb`}[mY.XE{*n[-жX I)^mCl<Ԁm5묦{wزQ@E>8)']?:=%CcD#A̸l\险N}q|ӟ}Ziny5:0NrWf%=ð}K D| 6aix IDATA@ s3{0w*(%rv ٍihRI4jJ=0̣dzP`%VNⷝ+^6_e]gb栎YRf:|Dvlg2v('Y=8'?6Ȁ':F#ZՃ5E=-{?i-Wu&}"㛟fj4`vu]VZ^MWWWjan*!3A1Bx^?9'ficp|+yƉqwb$EYNJ;uw|޵C)i[cWTצɬEVgAWy/P+LsPQqxࡷtp[s^qDr$D-gG QjbZJ{ ?מB?\U7|T)İFF>H6yauM"c{ݜ須8Zg PlRlzxP ᐖiD7Ij4"i o#m$(DHE[<#-K s3,ͧ%Rv&}NQfRV"r\:uA: xoFrw=ѣc=;B*"ּDg3-K& }LQK29H&J'anosh\ol\mMV,8AUo`>*Ω(%'N=tU>uWَ beDyZ?W>vuGA ,)"q[WI>VSku%+i>ARw2N#-K "cQhZ5!th/+T%ot$JZP9yO)oL Ӌ JJ' PѸmoog>A鵚IcO'4ϛ)=;>͋.Tryk^P&HV5rD998QC㕌sGnN4( 4[ё%tZ׺ucӠMۨws'v,$UC˥EZ_dwUw viT1oxZ;Xwwm##U[' ӕ΢$b4@Zҕ݃DYo"wH/:=`i8"NesMe&DDQjKNߧ Nͼ_,\ `H"Rدcdg|]k_$'oI@@m,IIxFnQtl|=[XhDo|ѣc=ѣZc+u HDɇf!y/ScL'q( LONq CD#ҖITnԵNZUz b ;dC 㪪_F{W0=zg=z? &!(;* "@ T:GtٳWLؘsl̛g]{_^#|k`lz 9ղa4T8mM͙nδitE f8&"ÉQ}7iO}0n8Wcwڱ8";t x..Φ10hxoHw;i7e&:e oOj{CDcjкփ]]s$.RČFZ!EeKIoғ TNfV7k"/oHqɄ|1ӊܤVb0үK;[Q% x_m,/] M+MHD#=3"jE"ۼ肑sIOFtU oic=zxg=z~`zᅓR(@О=?T:DITu;pƟ,V {8#)o#cťQ4NCRdJt4m.:{SJtTstS0U;nPg?z"صFĹOq.&J[pv''ٍ+K*Nv@/" UA&H 7u+bsS*憊9"CB;8ڝlmԍXoeԙDqQVJ˓+eQaDT8 KznQQɟOsOs ]QPH3&5edu,D hul:֧O3犾h^]ؓ0dl}qJ* U,lq]=jQG=zTg=zj$! T3tqzeDM{gQtC}}*?:AQ@HtFKFwޓ jKmn/$TPM p7HR{q7d"O]cǥoPsjjNW9VӛƆN7g];&Bi}b#ZiDǦVdv`J tjf1T_zCʒ8hT"o|$8n6[D$%*9@)'zD~^G1<݋H!3 Ӊy#<囘-ځ:; Vf.H(RU{Gӿ|Ӌf '$T:-6ot:Յ %V $ jtrT/yh4沤ѱG]7 ®U>&؉$Gae#գ)[!:epm?ݻw 2c\&deq}{B񤳮y)gԑA~hb 8S>hᢡGߩP!u7iZtc[(JHA96dyL,%n  A| n4f)Tk]+]KgXHV]L^7ǜgzӇyѣŇ>4=%JOPE_`{s kTU /| uaffS>ƫs5q *IE%c;3ȍů;kkُy/_4OiQUT&bV(+DDEՊ]J?KUDž8OiHb'%:~(Dy7EMm?ލ0\3ey=2!׃hpDV=)!H;t!U+!U?쬒*:3ؽ=z<3=zc_]^^ P VuIW@+Ki4ق* (xsS)30'oyFy׷EX_@xϻ_% nG$|$gYTBP\%RxC3O/#1 peZZ!|bDjWrzi"VT%CSܜ$L$@["ˀU^;&VVɊf9T!!bu%yOmyK^>i4EAEp5o:GY?8yoScޒzT(ʙ^aU LbV1{O* *K*6<ɍD8лvau8H(UNzXnj˞ORRoQG+u r,%;քسYT#>zT:?/-3`cv<`Ak'oiӢa !BOWü=BIB)g~KEhlB١OiV(BmXiN߁L|MTj.*!p\ ]eXƆԍzo5rƈPT4FSmj/w=ztg=zy}᳟wuٮ /XV ш!mO:q  iJǏ gYuD]nOdH[UU#jVMw|ߦD%%j*h<b%%R%)FbJ8i޽ч؍]յ̄ȝ(=(q|Ԗ've>~HI$1R޷ͽ湼L ^ދGn K-:pHScH|.W xttlBhEI Y )Z5殿&Q]N=f3UZ\]{r1oE'pIAPUZרjD'M05&ISlu@Ͽ{iBϼ{1¬׺+P1l9[@FcZ+.%%xL6յdٖg߼:%a>>dH ,% <({^/LDz Rh|h ix- A٧s"%(b#;SdܢkkWědJg̫-t32TK|;3Ss|.vȣr{ݼ<&;Q]kYdL̗&N>t+z-QrM5y'YX;tbBngmP(K N1*(Sq"ۥݭ8r|:st=ۓzJYǟ=`l}huֺVZĻ!߼~ei?ƞӄyc"(k8A]'S^wU&Q<wlzKGzɱWJO8-.bd%9);^G]mlf)l%: Nߠi[9_IAB 唴bFhEr6Y9xln^d]I>/@G28l+vH$y0̰ Ktb]tq[/@<23~; MK8[ڥ?lhު^9ʶ5$hC EQ 0 Ķ K-VL!}/6Mmjmd&LB"J%Ydiif5ykɜc51^|ңOq=ǧRf~@ɘ&x0@Q7whD 4E((_p\T8zA4 h0n:;[P9A0˒\4MCG.K* sIv(]լ?ˋ4QYbPRY,,#LQ4Upֺ֍NW[ LD)>W,Uu*&r$wƬl=ݣs"1̐;Kہtf{j"|.òƶ7Dm!1oQ4GC.Rɞl~wu,tMܳ}D`@EIU1*k\H[DtnfdDA]}Zc%{C]ӗ\0ڭ;=z<׼{сbѮnPWZ8ET1D&(P3dX ~{~yW7Q>wRm"AT<e,EA"=U-M'Nh]i{jw֢`do]JϓT ~kkq]G&VRF_w-~᮪djJj긗lt%+Q͞4nѵ޳!:=<[J:r_i3p}?)&=zk%v0-u>_s8m Uu.XⰵHXI4[R[r<hD/܋~#G&O YPQgs2@vQ(_ -CпM]v=w=ZYgQ9"EC&mmfl\P7l B8|8"'t u-HFd+]K59 i ?!@Qv\&b]Z0Wצ2R8g"@s@NJ*|9Zoy&5̽i˼Mh|r8&/kݪN^)@Vy{eرn :r8geօ{;|P"!p)I'V]sk62Ѯ>v\L}HTDҹ>fMvG׍Mu4C _3~MH&끔X[}?բC_`@~Fr>T"yQ ^wEϼ{c|+&eIuEAEOv<cH| 2c?S&&l3SpƪxPF^|flSOq DnF F]k6 $ˑy{Ρ4u| u벺©B~»a~Pdn[iqs7s_GyN+&? Ў4Bv΍rp"%(;λojgkvLSY%6?nܛ4|567A<Ǵcz孌ySVb3S{N)Os6 F#* b⢠B/n&Սi󔳊"xs Lx2{餓\UlDj Q$s8ǣ~` }G"zݣG9*N 'w1agn'Z>Y2"ˬC#ώEklK8+'72e+艧o|UεgVM, ´WPuBCh9 IDATPQRruھFtdLރ8Vu!qX UduU80 hRt}C+k{/lQ| 3q0'U2LdJI\m6Mmj= n<#z/);0ڭ ٷ>9FNWi$EbtUd߂Pۘy>N@ܶЅRMEk@9L;k#u?*RXI9Y-l;ėr.Oi=#Tyo 3k:YG4tqeb "W0Lh}]կ:GK EA!heyc|Û?90|aǝRtYG?^yk){N{spGs!`̸pfa ( @Y0,nPUB&XYp@1#\䘼WOB_~})0V>~[ͬiYTb;:sMӿN( 58қwh  ovAj3I!s%I^AԤK1?GXI:=z˓1'WO=h4-u}pВt,iFch4{ UdB h8˒T{_bsZ^VHˀp^кZ.Lh8䲤AI%%e{/^|Ep83&fxM.x<p󃊂bO{H{޳9RY`wFc xOld?9;7ю#lu밶`\08i 䃶lwXnOo|DP7nHӠzSofoj >{0`@E‘YL#oư~(<;~ʂ\A.Ҡl٦"Z7&߸_[ՅU?>lGϿ{IC_ѣV4dB1i^8GVqM>N+/8o=EPbRhRz|KաCgBAp!e!P5Z 49Lg6SKi gQp bph|{Nlut({S #'{l_ilS]y"=zpĬ5!5 ]޷)!PXL:0E'[ш)bڒ,^h-m6#cX-hT:>ط]#z͋_^裣=zg=zJ+y*VRcuMRw`+LA# ꃺBˀȓ Fg7 /TΛVES}31|b/ rs(K@Q'$/ZUrwRߪ?CW_`Z>d JeI>MeCA`A@<3TA?\F/>=ѣb4ʄٻU5Z4ѐ4' E%]vLk`8,-l: t8xL&q Ws;EҢ$K b{e4;bxMDTZ=QV XeD T(MI"2_6=VIVxHZ D;V#[P8<4{1*RgZz{nuw ХٛsT+ q锪ߺn)[" b:Mk9;N\leiVWHOX0)(m;Mo$wm_|b]}b=ͥ%6;Т`d;1#޾]pNc+EuUA i'žKc}=~4Y??B捾ݣ?5zݣGRĬx*k4tiŽ}!%Z7Ғkn d(fU:䊂kuTƆsv"ڼL+@AlS@H|T!Y% C90$P ? HDI DđOQY1\AJMHihq:6׾6+{u[eηe>)~Զ˛; >?.D'; %%67ϞҔ4OoeUq$?oMAiƝa{ t!>V&; c4MG#2/M]0zgA_1ϊ=lT@e < 87RDj>XlGQ(}aJ<PRh| Us7>gw##߄LBueŔ'R{v3Z:M!R8GۏuEآ^lWwn:"@w=ٷ۝0KGXțüUA*=,^ M5,D)ҽa cczb:s5黎:xp=~w= p^HLUÏu4֍ǻ^\K^>6_j FD fO||q@-|ʘ|4/\dG/PO7b"DN"&~㱹U1]w䆛Q+Hف]GXebevfclđ|3WD n)S&<FKgJ 0nE%Yۿ_*~X\ɄʒҒe4 |k2Ŗӟ}OeuU< @vяUK'I|Gk9go,:ޛMu٘7(|sur*DNHV5 Ç !" 'HQm4 q*VTQE5lUvPd&iIH[\{yOZ"7\Zs}l̑8Pyې ՓIc6 h h9e2Gٹ/ @St~N/ErCzЖb<Ņם):?sSr'}L[QL+ޛ4hllhU0Cr+j򞼧Ѻֺ֪BUu6CX|g>sX78qBJ8?!>{mxsңG5=zt@H"oq^6RG4hLAb9ްgop.$9d̻.2Sd!(HI_rU|PSU+5FՍsŠ .Fh3]l ^T̺1S.7~) _2 "m,+cP'cjGDDkkt(j9ZG~S\&t8㣭Ph7;ʢe̝h|( ^Q+9kAt׷VHtJ>)Iާ(  ZSm 9:^O~RYUƊ0ްM^u=3=zمNea+3r鋞?ħ6ELG .69V+ʾ]ӽ{]@iD/|\{"ei4R3`L ]|_< DZN@xR6q GLXJPl?"سx0G"70u|(}Tt{\5>كɄ,^׏mS}*>oh !bfDœb+;}0U&o޽c 2N)?q:qQ0 <φUgS@L{5J~HhϗP%P.]džLB+NG>ʩGg>L= wo xssTma{.2ѯQa"Ğ]I0cA[ѴW"|N uuwqcDy٣?zݣG94^=Zyϝ|)3vbn f͙& 93kBA fՋoyz5sH7JH]`:qP&e~v=rT 6>P:8OyN!rb*^FS}h8ͻ-Lh2aly@ldaa H(f U؂2~7;f3UX^z{͖/y"\ƎH[k&:Ww[6W{6)U(ZWW,bti Qre~˻QU0%JUzƞ7 b^9_YOpLvlxA"ĝ~y7m+wHw=m^p2DB AwIjbnZL+./d{\_i0 X>`4e̜DkNq\{7op*{=WU PY̴-2429*99&vp qkZ.cjTF@ O&4(iFc]=|>/i@ɩ1e>jv,xYsTUW lIײ͘oK8D){wQVsiLjpcCrTյNuttVbKJWqޑw x5vӊ󶉽\"bG+>h7xþE;"j|U F+BUw#zݣǏ;Gqᅓј6u0pHA) *{s\$=w1ۻי xL! <?0]YAh8ш*K2ȑf^ i8* ~z?bHMiO{"wN416s%Miڦ {:Hu혬9 d,]H!i$tM}f11eA$xI$ f0`@^TT\TmuYNsKﰱ~9: ]Sn!QL5 ;p i1V`a*iJ{TJ=jrMI?Uf]E0+T0,o.[ 7~EӝtݮYI*r6gȤ,楊DC}pwkCO{Qycndi69M|.U"a1%1*J݃O}rͻ|ᢉ5,=;}.xxgmQԩi$5Yǜen5$ IDATz{L(9SK^1 Z 74ƼPIl!DٌsXceS d6 7SYōkMmm'<``bfvTγHE46J{R H2kitsS:PQ%R-رˀFt]= 1iQ֛35hUIK֍IoPuSNoVi*0]th޹Y*WYvfj1r.TD]Ay0 TIE5b?~{Qg=z/8F5 H s 9%P4ME/ {N;.o')zә@>Ob`W\4*}Lr%:U0+I*{S HS*LJ"BpbxnGݛ*APבv;&މRĬ숽x 3ف[p]kQpՑ!"D`N-T*BSdq2SYj,jaŇu{QyeYJb"UC,%DPQjo.SdRo3IEJbFZ<Ǽ-ľUʎ71S@,jfd|WE9*ZSS&} D0Ɛs6M%!+`-vs]{[;cG0ѬRU+!+gr Eo77môi;V,d:['~jϵg7w/ssSJr.>x]IFc^Zy۠{cmy|+zew>yQQJ@QlSQb`@n(fK*E2_;AE,=L.JZh4񀢙O1`[UWdm"#Ue\;B Ac`Gv"NͪBI4G4q8lU 8J>Roh) x甈D5iLJ !z:׎vDJ |+˷]-]n,ء୉ўZ'NjEIv|Lէ9rFQ^F5x5FV|޳rdi 4"JXǷ֊^G\bz8s#fo#Lt W0#+t4 NHԲFj3 JO'ʓn^y9"6%eG:<2yhiͣD Q_U%ωjt\|Nզն+^^G xrPMJbP]mec[4o(4 VMQ1}GOv šFzmaId{A̒ %޳UYW|ٜSCͲmsXcL_X x4<)#k|F#M Tf9,f0$QCPe;xOL;ew'hZ IHtFl+w]svO=wŐ'T=?nءd)R- 9:w_>ħ6UIsRUL% Rzmks'7M0+P%EOM;Q]UNjqkuyg ڶ'g(Z,%|OV Aڪ6diF^̥,J 8ح˓McV`"RߓT-|ݚHOv/"/[nW{/&G|'fL! 9a0 10<)yTs4m zf=o\8;x^*@;wճY)_:' "Z_5DC IE08qYEIf9mrEK$9 BtX7 1޵7HTL{6f^BUz`[K Q١r7E>Gtbo1*1h#eQU2g:5jjR*0>5;?'hY*Ҏ]wm"rD@Rҝ ڷDdX(t!wXՈi6гv-KTbB <wa¥[bM@;XuM=.- 6i#ٰ6[{ -ҿ;CE=%U!^Q?h^9poG_T'G+N}œFҽ*V76+IꔜZ%m7 t_2557`Ѩw3wݲf&^KK&I<q_x˛=`1~{yN>/za|;wrh5Zwxn3aE޹ /%yB=ɗn[Z)F,a #@T,*{cSi(DpT)$w/B.j/DX4:khp%+)f+)(şyk3X':MH;~&D4ЦQ30Chy0UK)Je7L࠙nUmED羚N\i=4>k#fwLe:^b:G9FHsdzGـ7iY;_*ԭLRvad󭍝K\?|ɑ#ݞvu:tL`P Hd%24R(}i2cT40^YB_[i tMڈ*};leG.y܆\^ f@^#J,x۹83y&X\<ǣ[ۤ @T]$9bCNg4T+|($Ҡ59ʦd.ɱC۪s!Q{xO>$w{9(<)7io\ 'Ni:ɔ8&<; ACDWRv{KԭaD#KꛞNv0kR"`Gdص1j}*鯲I҇eu/辝];Y=&9zmՆ ӲYA2w냹\T.gky/ U{w7}<Ϣy` 0`.Zy!Fd,Qf" ڠE*]\e$F4]߲89i׌nNhH!w_7)О*J=PsCM ȳq8ͣE5D [P3KlZm_8UUQ"I|" ˔KݙȔ$㏫iȖu. qFѦ 8.44hPUNi2fH燈32Q`%_sr~0H-NB cp<&N.h4M݋&`T.XLIIG$b4jsRWAӒA,v?~c|q)uQܾU>Eѫ<`[~Zrn!ow/ }. {1078;{G6Y)׾:8NY(6No~pyT^2]~!}BOř Nȉg 3-nP{Zc ϟ\`D9)9>3]h31o3hUl*Ũjd!{iq|ǘRK'9D!5iy/vgG,TdrNr bxCFgDLs5} \c9 DͩĊ(  ן0kN"Cr svl%;w V=xJneL 'Žx<Ⱥ:z~L%lB˵!ڑ Et<~ ׭6g=` {OL͇n97Rw$-Bm+Զ: Hi"%ϟ\YہEƨ$?bRI^6k),%5j\ݙ'-)PSKDIH<}1DPe@:ӉCQӘEFGD*hd=y >*Ѿ 졪&!3CYYiGR'Xhb 6-(QZ()AUC["ƭ/)9G#NbO0 'x. ݵm1@MKTȮ2&ORΏ=.l dHOIymK3q U&DdJ^4ic*;ER9goBDXv73BCy]urD%*D ~ME]!'VUҎ%ޝYF$roYgE L{̻V(0=OSe3ըDO/ -~TP. (k`Sh)*+Af!I̗v^G!= &Y Ъʓ1 ɓC4F01O؍W|Eڹdf͎ 1J"@nO_R"]w;\ꚶ{R޽ңO[6nY&[,O5&3o %00.k,n@h39R#ܢQ}cF4 `ٙ0Q6KS%s}fҥ5+Of)z[5fwKdFu4b0FH$I,ڰ=ߎwLMi*,$Qެ`Sh:+)+;_.buZ+r=D%F;ODhu6]? D`V">[{l^%c д83\RKԹxӗÌbvVOt>leLa&L*~jcN>NY00nIrC@;Xu;6F6 {~q&#b6hvB5Iޅus۷.\~9xO%m9"DU?D .18z\^'PP4]/%s#HDX"Sȷ;Ҿ 6LSc_< 3xsxjmP,TfVU&bJJ;;>*-t6E:&WK!onƆQӢmiNE;wzjSWF+ %XٻOs|'ض{J'ikU$6 oufn9 }||00..Ѵz?h R ~myGDz5,f18s=Fh4J&o1+GDhĘR/pOl43H4G@/x*i7i?z{y7T%pW_>]9]%FQ[nA0}\U[=)u1+*1*LP]#)Zy>Eq^L$G;}lV#B<҅MMŢmԓ%jEXV[%G{2<x~tkLoK鋩2?,oM7:m4 ڶ'|(KTftg9=`߅o|}vƙLN $9?hXE};'#Fm[4Σd~nn\@g51TjGɝWfcˎjDk3vަ^3eV',zva;9CVsfp{ʚҳt.\#=9F >uK/t23ڻQCh̵8=*sТ͙ĐnELԕ,X.#PŹgH)*>Rlr~h"?oIQMQTbĮ]Fsɖs9&kguGWVHlXzSWWi>9=}יvSn(3bո`Hb-;]_}d$to,Rt-UgTS@<10ͷ5h"5DX /Q ^ww5Š IDATRT_Jy). $%:_- wAhS IgbR`sݧ7~eX74˫%Q"D[Ҕ ̓\}36?w}݅*j#%/;><<\GT}Dwݫ=ܗ桷/kt+$m 7nNcVIDC'$3жXݗ1#2 3LE$$ʤR?l9}m5dBMqom@9mebiF(ޅ7dK~[0Knu+C  tp.ML<JZ_MݶUߒͣa.g3oi\s*0.׍UTuTHTIaF,GIACS_}jrIWDǯEI1p/q3Bt=oFr{ ڀB}40D"* ,^eWqS<K-XQXmE'"3&.gLR&5 vCRY5!" -VI$sS8?nk ڮDIs 6?e[,}nEw&=sT 1y*?_fK?e ^55吓y"sC g>e2Ij[Zo8?XIE=8{BLa&Loz,#a /]ک JP(dl{t|̓b_7*L&ʑ,*dK\mm-n%:bV@7LV (WȜr{%WAzY/ݰq"#09`sSab>K>UN?7{œ&f< +a"1%nH%]#{^!6k(YAC"T5VMGGΩ AJhQ?zʉ#+eGx zfɻ`cCVVeH mn;igI߿_^Wp$=Yy0)7f;w}6HTb="6;lA$&8Xr;DҸ/;,<_7{u,*Ls^v V] D ;wV)//W3/ !XG֞ۂYTDcg0~nQJsm<%)EMwCLj׾w`m1Rz<4XmhtѨwB T[WfȿK/Lљ9=Q(DDU1Y Tzّf+`W(J44J 0/#3D%Jr["嚆Bs=j{ 3˅:rz[qHߋYek`!9]*yˏrwl @L1]\Mybǽ9?iBeE"~o-(``!A)EK]6Eo.`ȷ3s^I`Dž 3AJCbs+Uc\'6"oAV^СhCs"ScDh5|}kQ(eD<=GƓT፯i2*cly2()w]6'd& j;!:&f5+ELJTf5ba|x`` 8aQ(uWVx:)xs3 {O "ѓOck s|/:]zr}ςs[m=^~|{U;G.h;l,ꛤK1*v UP2X_ޞd?a42aboYVF S_E#aui ]Alh5A774pRێpj l3S)UmOxO 3C {h^2J JAғѣ"!*1VV(O]AtAHIfuE!hD0DULr~!"<;zCEEVbumzN<IlrYTyGr U)֑Il;m-y@~?,8FDG8S9bnVWbkcEk;ΙsڒL9zq?lkP"maKn˝!mF4Œw[OpJ3J!)Q|iJkaŢa)慏".s4m-IࢋnljӪ\w_"g.CDLn( "{"Qдtى@H2$3LUQ]Ls=\{jl$ȮΝ|}!Ը%?D{U}wvV!sWL%1{*9&XcHJB}ruʧR/C1NT;. d idg}@Vsm[F#<1!:Ě#7.`rgjF URJ4֙r/qY/fǨƘ_QNf llѶ bBbJUg?z } sbжCW2YFC*'$7ꮜQveCŲf3][4k4cTSrBm@9gCo"S'ӠB5u^~J)M:X9j  ]0<{~D^KӵN9cejO."ė7/U%CϪ7Lh<(e_rnۚɄwcӼ3Tj;c?''4!ꊝs1sжч<+*TUUKS`"b0tɷ .{1F#kjm̄A [ /7{|fQěGi :P&by\+OWLĔ򈄔D%!xerMWj&5`{qKm>*.9{yнDH|Mm^R&<JEg ]N=~GvXbPs|kgru]ٷ_cj[8FI$>Ж6JHhZݿF5KW\$F$ !(3us_ZF\xȮȅMϩY|,\avƨmDR1PUDyG(^wHݒ'AzDJ%D.mP*j}`P4M4ɖ|4&&4 ٽ8QUwaF Wra|w:sN:;Gx|Q {nn0͙-B={KޑIv+%m;YB^8!N)f"kIY'Q΀obK'\hۢinTڍ"?Pn$~IHlVtKl~HEntϞ8Ш&3:)\\"Rem3_l} HU{"nu)zr=.R*Ɛp,!W7]LZ>qڻ+7LF-31wKl?$h(U8g ES\`;>[:5%{|=m[<"=ày0੢i] jk6qbQ#3={/Ϙ,/>so0P2|Ü<ג PmQ$wd}ODt\YOI~ b"M7i%$M>yL$ȫ>{%}EQMu U8v/Swz[=hy 1G*"s9+*=f7Yፂ=5F@mpցL*,FPkuwc!`6SswVGi/^cNFHL#.vB*wQ9(G Q#˦7뼾++TWQ!%{Kj Хts__?@u9- #=iRI1o@ Mc;:I˄<@mQ1zDot-޳jt6C2?z<D+GWP?JY]>Kɣ_gɀC0`Ch]!4:Ѵp'@94ɲꮻ ^8LV&"ӳ焙) %mnD`Nͳ̐&Y4#2[]v8N.lwrhuM(xo-3] b%GnX?\x/̉A#5ܰ%pkbUGĢLVjR(jGtÑڶX,h0%}XA"EQ&pŨ%Gʤ3"J1B@ʣ4Lj~P$󪦺]wjB~2//dԟ K\e#?_87#r#+=".D JrUSw_DzΝKHdO0`30`W2;WTw8b&GD3O~3{wzK#8'?i6u$QuB %/"{=crYcN /]%JGI{O*JL¶խ/[)yʦd3dsH$$vQE><я|h !*0[ҹ^$".UkfޚeGm2m>7ϲn:cΑxu΢YI4j3|ë? lжb*CRSJiJet?ٙ&,&]h)E@E>ҲD[(m"6FĀшy^N&IZZ #/4$_]isFm (3%g"يRP]wiN|rC! N9;$1sW,ژIfjmzgg)\[vB$KL۪y,'wNSA r]Z~̀S-*/Y(@5z&& QfЊ]M}xwdjiH.O)DmlHZg z 6ircl{oט"}bBd5!ꧩ$Ά/dR}ʕ6R] 0} L»X=r{qDDc;?鏒-VWhe'җTw*vtMc>  _wk:}>Ul;2xQ즥str[?RȐxipnnB$j/7Xi6qNjUM1{~)9<=H9.Q(F -ڠEK AC/]agB5q˪~ -[5.^[1qhIg|_*4/yU鋎 Mƴc,$eYÎE;.ZaM_fqԑ#Dt)T2;bGγ85htDi)4f{/lxɼۓ{Ubq"PfL=z!KҦK-ߚ&U(AJYS,v:q/?;MV$9grпeUD4ڶ}= {O}G^ Er 4PnWZ'4 7ͱ ELyC֌(66kUQ?'SRf_Hb*[q,ցܣݯ8S2@_%ݳzKLGm[m[\lvRV.PQM)QMд 0ٻS@$i޻L$ '."=DS}*b@H9e x={d}K`v$Bˆ{s| }q1)QD-]ި'67nlhb._,*>Yq,QHld 6.u1'8rn L.QC˵mZJI k4~S󖓯]rQ4>Y x810< :{Zx#"4gm9ziܵCFVvޛGYvUg7EdjDb0b bR@H vn{]]UvyU^U)#6 KH 03b@JeFw9gcs}!zQ`Uo"Dhw#їh[C`G?=),pj#Mw w}0Y:wG#rcb2|*RX{/-?7mnzWP՞@I@g`֒֔ :ZO3ab뭥.2,Y8$Շ=dp /|Ω`+נsΧ I!9ݟajIdJPEP|UhJޢA.},;JL.^_n ,y鲭KvAC] 'MZ'N>Wvx>bYT~A!I@]p[7}QQ~ }㫳Z4.|ODh##ln$KCGܙIIf$ RnXG2V/;Z% vƜޑ`D&Zj!ꡇp],lh֜#ϢΑs\ϵk Xu@9mpXyS6#+W0+I^Ccu#ȿL/!PKj֢;1cTW3SMn;*ڠim킞}(+z%c lfLk9c(<?P1v R/ZԍDXU-n|,UzEN1";jr:yAzq=Q~<-F,hE6x$I 13 [l~8g6~1:NdB˟$@1?$e^eZ"Z[r$``mrcp7Ny ;Ty_՝/$0Z1-m߻+9| S ՜sƬtyB#Ǫ7y"M*]|F9ftٳV^h'KggԮtDN9湻M@`%->C7h=AzӍ˃5jO]5jԏ^}Y :hOغ=vb&7XE`#JB!9:a>I%jbɤN@T663AkA 0'`ѷwL-j޷O&)G-Qx[3`a N=vA'Sj[5wb 漭~(F̩(^MI?Xuy֞H"$R $ZBS.* Ogd88\Y*Z B((2A1^bemM`VNo߶P 7d} &yQxF9Pԗ9ڿpy= [y'3XbԽ,yvMjQO]|ã5{ԨQDKQL:!L8_ްY-.8wV6VU3j{s_79R<\@kAETD"A W q\r7#R'(\2#xr [ϝ~af4m;$14w5MEϟ|oB x1jB={6$F"\BT֜|: YlrMKykW./vӟ ̶jiU-KiQEMʝ8q`vbWj#{Us2 ٌSN-&9Vg60T]251 '?㭒 oEL Ϗ|x`5ꧦy5YHiZ B?Ab 9EPsp .)G<[#`4|x_;__G8le4Q E3N$Cvw9o!@uSJb pb>+ USi]keʫ}įh^!,4DaGU֦Ft8fXZk7,٘26G|D!m#ݴ='M?+1l-.D*S2 g.\C~|)]H ":HI =f3Mi:J6;oF(FM$$$ gڼ]9gμ#vbzū6c?m"(aآ%QpdtޣFy5Oz\)3YM@1ióϘ}/[TDCLt챓o >/~{]je, au$-qdvb˶BYrьsd9g:am_7k(VyP{èwb|C(Hyj3ySjX1j}ڕt`8vv*vR1so1}$Qq{]." 8)Qw=Xi^IF8UŹ,Ui ,Y=v% }h;fa"&jrL%:Q"tJc9gFB^\(w]QU2~\U/m!P*q.pnǪ5j?8{ԨQ\JqqVs`3O_w.xo-͓BnDP`ЮR5?C~@ !+z̍39@ 5SW;o %*% .pOl˺IcӏzwDCdiO94|NI.5rTJ*IS`fg?k)"ra# H'Ys) ͚9Q_鉩_\~:!WƸh<^ܯNI))^-a=uTN' RQ.yGڠ?ᨦ1yL&ݻq $#QEl9r|TtFwp"b߈FѺ9O|o'=Ow-ZOx1R%%MDO=+?`5ꧦy5[VpD寖Gil3N [1Nr&ܓj{l?{ʱELNRm]<6F}γ~!0HH\R8\qY%Eϟ|Y\$JD.Zʰ9l` 1sd1Rb)zӮ]\gޟT7exdpHc:`읗#[^?6jZwƇ쿵y_~mu.lś4LPeFA8Mi,!i9]2ABHvJ"ke5J^oUKu;Ur Wۭ=l͇i0dB ܻmzO)&Fa?A5jOK5jA):`oD =ݴ=(X-QG#}%2zaa,p{ޘf?y/\cM!S̭:!⤗LBmYj CկZ؆D1B ) SW(ʼnjfbTRNٷO1ڎ T!%\A @GP 1}+415 }*Za$%l/T1e`^6){}5A1i {F"ab> "B1ɓ]B "J6r&%ʓɑ7&KOkzuԝy4PJvވ)gBsgJ9TEfя<+ۙy17X9Qdg37~{߃;ڛ^̍Ǝ @۩sphI_yѮwc[ߺwW[J^ );jJ$8{?"e7S}v8lŏD"&8"ty?{ϒVc)LTBXYH4` *f8giJKtȫF`.3-PPrA\$ {1m3" .goipaoPPDJspo}SBaҘ9Œ%τJIr`tj{].#hPDA=S Eٌ.hOޱ*5'y5K@Hpʉӛnn}Cp҉ӏf& Zdj]!KZ*㚭zQ|+bֵ$m1MHg:A܍'2y1R@6̳QXJ`Q+Y1C  !jz|)ZPzD=D9i4W'a8 9&>pɡi`jRwBZTE@[d!)1bĥ0}2s`$SylRI$[ů[q|]viH|mg2|5jCS5jԱNH 8Vvh;0ieNHMV{†@auc?bU+cjtT# '9^ٵk.DpSq4_I~ VʟɍX@4(_:a)~NAԘsbAg0–|lkMr[7yłR%n^s) _(/^t3scK^8m&kT ˬ|1HLY:HG3!*'ڻchxkq]I &qjh%/mAEwpR+kow)Z=1.ov2)0N ji>":&f9i9M7!2Rv'G:xG3k^:a `ֈIDr4L:S"C _C~7ʯ7pAR1[Y/*/=A_cBh~jH >?Ցu. vmO|/u昨 kĈ Z1NVVRC6Oi2.fA= Ao&8Qq((ŞϼS> ^yh&vڣ0Qݻxcj}f}ma ROP9}TfZhҞmoK&pGf_\ԖDWK: Q] $Q{xEZ<ĈJmU2I4:Q'?,.U>yB-<1<9}$}RE77Eu~ԨQ){ԨQ% <='>B'83Y!HJlkٿͼɱ(PY v<("\P=Ծr?g_mQ Nb%%R%)qS BbRwݷˋzw|;)mT6|Tqe +K={BގBT!+3?^TDk7g\̂Sϐ}RŠsDR+V1.:5j \}c&0QwF_{x"DA|$* .87V"LQJDE`Mɍ.h`;.m^[fqkHRKBAw L.zpM^*T,9a݃-bvKe=sN3Mi2!Ck׏JM#!CѶ :GVNĮ!%x!L nmIg4 iRT[Cdߠ2 |&g۵+o>WHjDH)4 o8!'eGd@z}fJ6r+ϟKҷm~IR~䬫({B]_>k;_uIyoQ"!MmaGr9@mFrO]GzjtޣFz:3snu#t{bR@flИ2{<;x_gC7n'H)3~fm 鈊s(娆Nx4Fe"b%٨[{||]>{|^:[{'b_zaޓI~DYş5tos޾=ŴWեekKޛD7lFqL;8D3OIum;hR\rцw_1#_hk&LL傈BXv/rҔTT(XLII!t:i8^wq۷ %UI* !j*_LtƩ7ɉ&Kjy[)" ȯx*Mk0&Z.~Gy5Fqp>v7mϮ&ͪoɔޡ =m_꼇6x3py]p!/bzTHMs "cq0g,9ZȘZ}bc[ȳ#-T򊖲h|VI =mѬ# 7][fBPltsLGC{@ɗ:ʭVbYRHra広hhJKwĤK >=: T nio5F62A/5عLt_=$}f-ƐG6#`َ͙[!'$OT_Lh P<Eͦ76yr3C\*;i4%zwla'vgaCWP*I#=dc[[<)rMTX3A$EEckQA:8).)}zOc: ^x׵Kb0!S}B#eԝ$}Q||4\&$ BDu+svLoiHvDJ !"2n[ہZ"4xQ?H\bBYV-jlVۍzݕ GM~OQT_= !Zjx|<vh>a@x5%/G۽r3&IJI=[HS"zyZOY,@BޣF=5:QFnrϞEiԔ<3z dG^KHx߹eō Zit1UyΊ (BٌByMHރ LD=.2v=zo*JXez<}9O|M]J1#%b},cA35I~YHUEV,#pm61{jY!`$yƀv]+&f赿em[Oa5ڴhoB  npaWg83^y;5;HY,{[[E]*ܰP4-S/ZB雴,{Z]_.Hq=j6puֱJ)3%\i x1>Fuey6j`ARU ZCxE6|{p]ueds D3ņbOcB N"Ѻƀ} {Q&ba0M}ME+,aX 7MYMILj ;oA B斈@FB&Qͱm͚36r*sdLIcRѶ˜qLL@r?ec^Ar%B} e>(F̦}I \|}ԨQ1{ԨQf3zsO~&0+5ÒvA}!*!5n}qZi/~n7MFO=~O}6ĹOBטlpvrw7cTԝ?N?iwo %i\j QcsC⑚D(>?N2*ISғO *Dk]KZ;}q*qsH A;,Nq7ʏ|.2L )wz6Sz#}a _Kad9/Y~1Kwb!R7;"x](-\ڸD S!MVkcEΒX&̻ CQu%GI4F%ƱϜMxw-i P Ĥ#?{o{GG zY' N~+{ĵP#xcA/?k~_,+2"Q^A~5̦4)ȋ"P62(.:}\"%7#VQN%"^yzLJ_+:Kx#F[C`-'S T ;3WYe]gk/"_(@JS*:ANy:=c3] A9>ޤBY"I*DV$.ڴb̠Zo4,z\)Ƃ2HKcHH{:4Tb(",Lq6q1b6s܄^uT>Q!b|.} ?jԨF=jԨen>(fg̼ް:MMǪ*)Qhł Ӆn{ۗ;͔0I[#/|~{f4`2Ʉş{W{N v|,D;{W>183sJ 켱OZLF+QC䨣骩^)~v^$%m;4&-8Bb6j 5\/~c|$"1oqűfĮ1 ;r7'^/Gֶ8~LD$9(!#+~Ufv,)WR0S^Gh>7?_QFT5:QF=84_'U & 5Wa$ƅ.zOI.׾ M&Li:Ʉ鋷u>v")O'2A7 wg7`ޣF=T5:QF=-=f83OsG'4w|3fK"9X@vpSBVRh1e˛Lh2ynkWٌf3ܯ'{oCSD/}+fT $NY((lUK 3p6s6>jUK5K_<1[^ u17ĝ;5m{P' MRv:&7c*3Z#\ GzzTe/*0k{1P3!DLB{!/kz#^4-)ejR`aq;rdK=;ߤ1S .BQ/=(e#9G8gV0M"I3;>iֶ4nuk^QXI{ɔmU=h5 1`$NA.6 4,Q4mM.߸mQeDIvEDCДOk _WCJHvt.5P&X4qTe_,FɨQI{ԨQSN?HJY5H }(%m;yfLUIꢧ^Døt߽mȗB!'(0ӏs=ADW+:t61)$x7rw>khZw}Mٿ CZ>TDRD#'qMpԶճϘſ?xfM墖|șxU@ARӚѶ@RNeАgB3!bÕN&5!y5`en؞˷Gck1]ԻB@xړۿU[9HEpa FAXIa1] _??_Gyfj09q^i!Fe0ITkz9uj.D%34 /Zњv?uC3}pMSbj]?na+̛ 5q8Q~b_03Z 濉,2oguh肾۝ͷ ؎=D$w&Ɍk)qk"p7hSD[mMyl"hO_Zr+w%ynΟ㲿*"v?H2)Ҷ:zdLIw%ݯiԨQ {ԨQ% 9:l%99̧?\p5F/~Jdz",U0ӳ| L*B)1ܲa`@Wm<('݉y0'켒 IV@1)jp f.< w&j{"mRw>i~1ł Z,of]&a'/h$H~&je5mkvX{v;RO>DP4rٯod2I~'Q[ :pU`'f7n-W>Ft->M1Wnmij2W9q?՘ rMIeQF=d4:QFO䱴ym{* zFoDPNՑGdBV~G$;- 'Ǻ#p}/If'b'Il׿A`w~ Hi,75\:'NV }Vli*QTKQ!/B?,ł&J1-fcEc>-U,bAyGMCWx5Vcin/kCkO.m)n3Ez4/ռ8mw6Z!D()2#QI0.{Jz!(rR&5Oc| AD]Zm=1T-mm)BPW7Y!OHS+[>땣F=$5:QF9Ftj;% ^3ELbt]|VD4ЇAxNNh.F IcD_?ZW2ڬg\ղɳÔL#;wJ-M愃Γ-#B\8ǟ/S&H"񚬦}0gRG3*0AUÉXRܹWŷ9 ks:N N1;&?E¤認Tv'9<|N%3d.x۶2^l3Dv8y id۝SHDI ZeяuNL̈[sVzw m[.h )8u.G&)q^YNa9?$DL*BZ,&u= m[%c%BC0-N̨Q*QJܲJEb [;;4L['Z"9qTTH D7fX7=}bc^Kyr#[1xkwYh8=3#]iQ8uM0ҿ>!d@ ;bfv䘘&sNGQi2!"f4ؗiwX7X|3Fy[DwF@`˦"`R%(ʺ$3\PS>O#%DVNIWdI;WAZ,j?W\m{)5v3Y<7巛dXߟ7sfL&xQ;E\Nb;\}&|s&O.iJzrHUR o6[?/QhtޣF:X){JɶfډY%Q !P9m_D.#$Xz솛Z9lXNC̼͓T*i ×mvdR%%h !BC_bA='}d}G[>֙Mw;OJ$'NDpt)"g8*Ss-m]qG<4PQôCկXrds-]mk3J{lU^s>! ! @ BB$,̣yA;J%J%U)WW?TI9v:866`!z da0Fy=g/?9B*UWoj:>ut{5GN78wUK) ?\ϡ3)!#Uu#jNSB^Lw!=pqɛ!׋uqʢ uneރ~<%ͳz5fP%շ#,v-;_ 9L{„ G߶s饋 Q BDH DtE/ݷy 2^qz{ۢ'YT4.yS;zn5qd,ۄ4lxDL҃SDD%6ldd.SFcTYݢ+ L]GW^6REc?"\ra;j r#eΫ$+X)d3TA jK/5Kt޹e3u pBa}m_sDb"0 F}Pp*ໂAYAuRxǐ g&l*&DDx+Ⱂ{y6%6gsExcFϷGu-=Ywt0cGd1[2H41Vh9fd#0a/0a‘A?',GR |KÏHUc ,h"`1b#mp2*m5|c.e3t K;pP }l Z<0Mk"2uvFa] Ùi!bpLd%o,\'*yٜͫCH{!\W5 }Ul=;jl"@nM3 =H>DžX*E vJ0us|X3cwtqؓd:o,$pA~^7? qo3[`bVFb*6x)'uxlxc-MI䳞gf򦡮6_:D w?}9[2Rr}хݝjF{zOX:a„0a‘w-/|۞=\Y%mIyN9Br/>}jeJFa3$X&h9/s!Bg>s>8Yjވ^GD2pY -Uj-VT۵,bwۦؕK* |C7~|>k~BQTj,6Ou,6d)ږje Ԍń/}9#'ٚږVr*s-<RcJ}1.!ץb\@ "cHFʘ;7O֙-9[J'd 1-uy2LN d!0fSIY)ﻺ\yQQӔbbz&KY(rLFJn_Aԇ;N]ӟ*x˛gܽzF'LLcb&L8b/W"ւtų3 kvә>sr>>ᄰe MVbD_4FeTPJg;3edg sj_qqM80p([Q-L=>g gy#Q;=_S.ඟ%2R*;Qےy2&@M )[ֶ޽T-FD 24n<}n3BtO٬|'B`gV5R뵜㰩k^UwKIiC/h[rx0!bp^_N +_Ulsf;{-u)\m6ùgu;.Կb.w>)d„0a‘m\tٺ@"gA`?%3hkozOd]Y&t/9[MB(bziÏ|n!si+QCDFքʬ#4jiRדO6f^gDAߕa&x8Dtn$RlX4(x(Q-J!/=Ynxw`D QJ+v:J> \UHDפ}Rq`Sqk;hbnW>ײwM$ɪDjj㣪f\6W=Nig޴m4n1Փ ѲnS.6\FeCa2X뱃U3plF?98h6CRӰTHhFb"ޓGov„ LL{„ G^Ӑ}oL} aς[tmA=(!!DO}~2 D QttHwjF?T6CCm"Uklv] .x;5#Y6tmzvkN~?>y1Rf S#~HR(WԳ0u> *ያh;K[aBL&L7~u v)ǣ]G+.B5o\o6l\]x/VUI63Zziۤ,8P&MȣUI)r6Q#p{MtٛsZ׋fwfM溙Q L{„ Gܽ@D\=)  blvjZbA}g5nq, {ko~k6_JC@y@ {ֈ_;]G;$[|ຼJU]'FVkرǒ(Vʹq֊  ^r͕łsnjjZ檆?2FBjk٪ݳMzZW+B ~fQ%_0gj U;tHTO:]I1n߈9luCuۡm{Ze;ᗽS^znPg^B\*xNcEvMdQLRT\$J䌽{ж94ݯoA V+%uԩO=iοtق5xnd{!0hW}`4Yyo*ܵfW:0`wo1|эݕB}^wn#3Sv Qvõ|lETCDդNfуۙz qŨj\Zב&UfB_j)&QQ <C4re)!gkLkYSC' Q#7!Wd* ef [ ͸N 7mkJP_jg;.[jM^;nrƐl-2|u}tE<ο+ۺ]XjeÀl6ٌbBlt_ O9來 |EΎf1edV#c󲽬ꉪMdjR}Z:I'auw_;5īGfgfXL 2L{„ G =_5 D֚70PD-*<:WnsE&#(%Oډl ޙwݼPE jxƒasJw!s7R5%;d `Se@j準< moۓOIʑ]<&3 5UE)0 LjF$\vq9%;u헾2fmf.PD}" 7u[?b603H:t7u"j擽 XS*RH^ÿa4Nx#ͽﲟ䌃jmj BHH'ఫ]+g{o/Ys;]f0u!&SHc$@9`)UAk펚0a 0>fUo1ZӀ-WX(0 a 6qy_}OqbU2f\z;{fBZçF޷WsM "Pk?Qh?v2:}x8NF ym~s^JvKBX{"!"9gQ}L4ؐKf)_\ Rn*xR'a#.MBF\1V즈` $Yị'vWgζvS9:J$Jz%&ʂ L{„ G=f`'xs_\,%0&k!XȈtf_gzOpܾtP\> GfBK2o_Dl,T[%@o8 S 95{YY^jDr{.nf8|F&7rDUe/M0r7RL._yG5 DD!S7_:'WR62ީǪx; !ɤo]u^1s0*76Rfv븵&;]=$j;zֱT f1\g+m`'o}<%㞁8Wt׾z:lT(٣l4wf6.dE@980dAɳS p?̌_oo_HMWSj^ hkj?itx3 Gn~nkˏm# @!Z ŎeP+ǹ=iXhYRdhTK^sZ=)0sIIAl/{qܻ#,=~Ӟ.D &cRa@ĮlOM®w_mqA|di`)mf`)c/u:JIU)FS%7/'`\5c*_3K '41zCuӥ>JVb)Y#b1lYގ9S!Kq7/cl 2L{„ G\vLD6ы^zDd1I8絳<03R.v>A#BI)ݳxZ*aBbUج&5RulaD8>𽤡@I/lckX2kr`73YP $ln:+)ux)yŒ.jI\.m>h>ٌC\ܴ|t/kξg=Sp pzjF "fԜnPa/ 2gw! $ɻKo* ]G1`ȦJ!olF8 jBBUw"gVw(.oeZUon3>Kj1;hF(cd䪷jQ=TKWڡCsx΄ ļ'Lp$UKyOpnOx,05C2d'Ϗ=1"epTI$7Ol(O[knӞswݺ&F-a@7o$R3,O1o{gT\^2[|5$B0l Ͳ`01Z0 v ! 6Q:5S,cfdmoUb3קkOLIdFVRgTX&bs ¯狽*i0ج|>5vjc*eID`%i[ĴWT:^*hedE0.%Rf4rV"U+UmڼxwܴPx? b OҸ Cu00XHTnfeY:ߥ䌧hO{„~a„ ?Xl-h6cb!0gg0lmN/<5>C)3mN^mqu_T5י*~7w݇)R5Dvc~| ":d-=oW"bf`6bb- |{E)SKzh z> 0.#IDbS{[&{„0a‘A9qť[?\[j˕1~O:1e$b1mmlOĶۻ'.-Z~҅ ssQkFp!Pn3awcQ'ִ{; (R5< oUwݼDtb_j&:doւr~r\072 )iжiDlABxSjysA$>>7 eu8uWϽȓK)SVy1MTWal;;vE3wL_^/xMیFH12F1۬^sf41cH2Ko뮙;DMJ}u{,gw)j1@OHizg4yOpTbb&L8PEʛ4D-nYD3o?=^._;I@uW/>z2V F.}Я$gc=lD ryi-.(:fBطQP=KS;oZH!`ADpzem~c؟#U3e/Nv^ R Pl]K)ck" RcL`]zyR泧fFn7ݐAyæb&͗κږmK11TH){ޱGÀBW|а:MZP!bm{60d{%\{\?Ջ^;LF HrXǨ&3ڎf2^o)0ԟʟS= ݈@K4{Qٻޝ"ONٻ߱CԽ@_}} -CepS,g)Mp„0aW9fo Ȁdn7)Uha,F 03rn$b`c|x^-f/u"U{18BA6AB@kה7vpbQסkkmc:u:oKf~j f LοͨihH -bm!ϋ?r.CɓZy1gXhneh"Y(Q "[ϛ=n15 5 1h@JDoY̻DiHq P %mÁ)q+Ե?!&"2J (CX(NRuu|.]#M 1ȕok4-}Xbγ9 -&fLa(%MY鲋zBק"#/ܳۉyOpbb&L8285= (sHz8yOL}foa@􊗷L'[P ŎRx,0c$qm vo)1@>.Y<в@ O~"ۖmKmKmSQZjA %>BOe;pDj 5b\?Nl1ҋ^\&Z#/LUgR1Q1V4 RcG?+X됳-ӸzL(fJǁ˒Xmж bcR +[)0CD\1Z9c_qxYλ)o@BMWd2ضff7r`Էz` MCmK>U-Fͨiu }n"$aKyG'&믙_k„ G)&=a„#'9Қ^Z+]V-ל~Cl*7MC?i{?/ΓS~acm}o\ZČoCYuܵV~tT:@@l?9ݢmG eDDz)\.z*3𽇲*iFe=9 oWh#O91մY o.Ji{<f*恀^5;\Ķw![,MHjtmU}uܥy^]deo&x _O|>]ݬHh_, ʡuAW7=WKd$iFtY=hB-efd #sϏ;YV7.{/jA \?p .x}#f_.}Z=*x L&՘  BX\ g?>)YbUݷ݃m_pI؅(N:!1u(%cK%8eH IMxphPn[j"Q IaٻI>De+phnQEPО=te|Nu1R]z{7P򋌚2rhiL#{\Aɇ)p]b$]Bv%3GT*vamkH2F\xNP2 XZDs kzR*ȂG`)>~x @"g{QW}緟MF>:}[MCMp)JLJ ]ʄ .L{„ G w޹E'njd Fr7yLr*dz @r>V3^+_1oGy`s.o1^eˁj6-\reǨvM]1k6]1g`dT>u(:tP2€q̈0D^5boRҬ3džX 'v O b)ej2BM6fʞ|R B;3=ƭTrΎ h7>%N!AV] I*~bxm#dpm<nxXhT}*eصɽm#PSyBζDeu!dVc320е9.%{)#6\%9S2Dk*x{cQ|Pbaʎ;%#]^qka >L{„ Gwܾ[ׂ_;9V?!{C!gKޚסjS=}Mof`Y3o?cpnB!"yޯM*kٱ~4OAj0",D3+Ti_)\ L1RͮF !֘`ƿH2d7.;/*> gk" Xv>ۼH_,Yjp:.uyygmO?dE6fvaP$5rOϖsTu&b1Re,xz1Nߦ(Dm驧K?kF g4I-)[k3eS5<6;y0aļ'Lpqgw.lac`%s/?ljLDDCl͗μ{~x˚( "ܝBu/gBe9ZhAH~q{UjKKɆd" [ 42uaU;WWD\`!I8_87vwwh{$_yd)0%ܚ-9yW8oW1)|p\6mK^}}8pɚ)bDlg}}4vg{`ZjW9oRkɋZ9$K [*xgr3 ^fS [3@満LEh3-PcGS,Viڨ!:%/MC1!W|}-M)_~|&kZIkXjRʖRaRwUimYŽ3R4kN[Vo3>5 ɇv8Dɷ_mDY, X,KDhcOB6OLAL3'_}& iJRBf^v,cm#G;a„_ &=a„gwܾs%ŒjB!#1y9};;6kx 1H+ ,m3s:tPH1!&W7M2LPnuy q$y̠s]hٝwE3Id>X*dbPkwS{C(D4GĆR^Ng)>~wkJPBʅ&-+n"b/;\}B Fb"Hګf|b+TT^]Z) sFJ86g֫D򸏱ɨjKMDth[ǒjI'NǍJζ\Z2O)'0D,coDi16 ^&o#b,oToݾ'HUsC)[ף.gCuR2" Jҩ:}"s!mx *L{„ -HB@=ߩkdK{sa*>UZy7T5g^~z驧5u+_|[vj4|uZOֹ.x{{ri}̖@Lim`o0#T;2L}![ DC.; ]l9g^0z6Z.!?5/_|tRޢU PmwPw=D?esyqH0bdM(ϏJ#*,޶U{q%PrHDՏ ^pJlZf5)bϤrγc;nOIjHϹl-WRl~$ lSB w&LO0a3{^93eYqxk|Hj%1ї=̙7]u,zg+iti^gxkA0;жsۻZޚILцy)1Qڍ #rh5\zOlx:]ߒG1I13lom(5R\1{Dq" &lJTF7o˼3 PvJL>1ojӮ\_hr6x?1(6kBꄢBl%!T[$i=X0^e|k~43w5Q5xb1ʼ,]2Gh?co$Px?'l/9-:J*7#j{?a([6'LpT`b&LxqםK^8*XqF'Q?_01=ü[ Go]!B '?N:v:ϓ?ƥO9_|͋J(Yv[M6LXQgIZVֱqo4gHe5xկld%ix=i0Ct gX1J.ѢRM!׷|a`6*Wcjf8yi+xAE@!#"tFŬr"~m}#`6*șPt톷nh}cg>:"8f?Xf ^SB̘^A`;;yc}X (fȹlx2B uuo3CXl G&=a„g[ ZJeI9cx$NFə1bkf3v\Y(f¹ѶԴ?cԝw,?{ֿQp*>,vYW.7nTbPv V_DŽxޢseji?_搅و]7 `1f(YڐJ#&7M7o*eT2X~ʠbL 5)œc9_ ~ YyfwO*Ώ ~2PgmlC@MYlfz߻( Bhxx/%P+qсB:kXnIE1 \ZNVDC2nID]}3؋9G'Xtc.XvYXX32G=yO{0?ϣ=_[|(=\p3/L)ҾSN{v[[#[Pcl7^u-)aj QT+90e4xI᧏w¹!65]fE^,%=f""Wg n0ђ;ΉӨSb˥MV :L{„ ,K`}hf}1AlS}% ^zZܻhkAwտԸ4"r;[P['ɽ U2S椦jل)3c.ro_S۹}~'R#?߫w7zU;tȆo jh朱2sݿU[.mg[0<8 lS_3i65JYLMnqKg_Fb_.Z _ X>(ذJ#%,-%{qk Z,miO< %s^Ӧ4eH~(*6XCBr6b\.!S J"V80_TwczJ%ZQՈH bP] }gXqPd"FS۶ZM{„ 0a3mHܓO"`NDlof{w N .0S`U_p޻7^ص4b ]dDAWZG `iP(;/88*˥lyjL DԔC7߷50!UK;-΂!2t,?3:l# h6nFs FbBDfk*&Xc۹lX^vM(Z~e)bQxB7/r^~ggfKQP@MMAQAՍ{eF8 Pzr`볎]F/: gL5 2SN0ᗁyO0]w./xw^ɞgġ"B ȡp2QpؗPdGr1X|A>㶥:s c,Uk ̠v[Gu> 4(wܕ=W4jjnpQ\UZ%?!x;AME(fHDB2|pG?@>Wɠ4>u[uu-Xi7sY9y$_9U_}IdqݨFvK;/of3j[bff)68XNJbDsگ3"kZ;y2}޹!0;.wU\1mD+yN2r]Q$[saLy7#PJF?g>= >L{„ ~Moߓ3zU㿕0D ^j 4 e5 s>fڿc#.K"f4ȶ\߷9L! 4-`Ii$vwuWU5E**1nm,ٌc3 $|}謁#\ "Isko+6bwaaf9jL~CdFZn:̘1yϘ1N=dw}?dΖBg=ޜ3(\C hg,@ XTe5@lJFʹOzl(RwO|ˏB΅UCz833w$fU7w}!P`$RTSoEvAFpi5fӰVDL-*T%%=5!.䓰jP)}ݯm.xq33eB,3`7ӉR/eMz%CqO>ëܴ"U)]K܍wޱy1c31cc=$1I!` B!# Ohn4tq_)|HmS9Cd_0A G|_>k"̐1Ȝiu#MOet%#%K _J&c_>3ݷ%%TI%3l,FT~yp nU\^j_X⮌W.Otکi|Ҁ`J]Ȧd>)b?X3oT )a&"ep EG&!~U_ zUł-GAAuT/|# ͝z⭟3י]f\ 8z5W?;Ӈ~h3f<3 8SJGH uFřzH% L93=榡/?kGI'Iƙ2L"~21ײwN&5."8/~)Sj6ˠNLh46T1 eV\%SG} }o!eV@'SR*9nj."yJ'>:u[fEAJ\Z\?Dš4 Y${j;2nxg5HRBU8T-0@,0rL,! Z#,b[bAmIyۍۿ T'vY\/:C HtDsG9|0,@/yQZjI%-Zjji'Fz#ݿ>tΘ1̼g̘XR*iG#> qVٵN;L!  ,NW)W-bDDR=' \8PNȳόw+@\yQ`q();*.|Ų4XQLO2͌-(Tv\ZATjgĢv器ݵgS2EDA dS!&pZdqi2=גŞu%X oB٬nr.hJ)GB1?J g]F`J#S;o;j8+Ä8c`b=3p/8Jrږb b7N5g!Prƌ/{ƌ>ͥnňΌƈ>Yl@)[{cF4b578D)Lo%D rkXߣͥJvBl"*boakx_?~aɴ/(4^SO=) jL_B]bKYs6"UM3f<.133fO^#Mڳib"OJ)#G:sɌK{ƌ6-Zꓭ7jZ6R6yV2bђˮMU"肷ATT&h 6ɍ'Hz5||wX&x;F%Ҥ8iO  xj\܆yǑ)aoO778x' oxM`9T-(XM0]NCED'N 2Er@6x#D5[]ldh!Tk\l7\gtF UU1n=|2FbwwWB*DqWG(g{UYр*x{|!3`bfc6&bDֶ3klr8^u^HSOû~0xٛ_\2(tUm`ȍ ׮ĈPEj9KӌQ+@i\J61қ?pG4g9Ti8sTW{2Z}$ T+x׼ږN=07룴TW":[AbJS@,DcoNGC[۳dƌf=cƌ9fc! |Ib9"ez9Mi/tNDZgĻK6E.\KNQ%dvC^@ǝm6;'vT?mHof%хlx[@^X} {o4-B+$Ȕe71h"HK!JN=GNv҉e!!@ 5]Q|[o:l6D~~!< ;ww'?9|Snf&Lj:O&ԧR7VzO=$.9T j勶!U#D\i΀R4@b,jmPR #>%f02`wW% IDATޞ~zr>(lr)K꼖">|=[3+{ƌ)`ӡii.ap}3iGyi+Z-1NBe ή/ɳIؠfmON|dkkgx`f3fxL秚~= I&ap qgmCqh"~*z72Rr˃ )2Hf`':f(H[(j:$]8-{RW],JDD7B@N 6hfzŷiW?Z2fo1k!?QszgǺxW7EӠiYgĶ%BA ?S;~[zߚ9PsoDUUE,%6vTG [y! Ͽ8?{>$1 `ur|/ekTږ-Z1_m/Y.u,{{c"qmbR { ` 1cƏ31cc[o]_vV&>9m>3C&rYwlګ]M3l` .0!,F ݑxv3_{tHr uO780)SP qs|o2IB,}/0֧vWhwDLIbS-!= 1dLzpT+pW:$D2>SƺnjPwRRlƁ Ɇ/!ջ޷ Ŕ=87\->Xlԕzf! ""#3ĕ8%wXhTއLSMN]KC{ ̻GJv T&j}OC0E>uۯ?3{ƌ5/^}6Z)g9?~k=j̰"" #h5z@\}[79 ._I޺¡  JAQuVG=M#cEQdV`xTs#f@)9,Z(Ydv!3PB]ڭ!m9D>gSsSO 1M G1I"F fi߽ dAD(ĉ&Oլb>KX5Wm O~RBbS\XHL\"vT߻&ձMNruc8Ew:dbDRYZfho}mMoYF̢ K0{;*><{rK}@=9kZzԝRgƌf=cƌ40猶2O ܧ#mr  aƳi b1qWؕcDR34&sE"l<3~W3u7r2HY+i:FVYfFQ5k_KyN B~Cj7l{<ۃSM{/Y ޺μ;ܭ3DѴc|T RR]}iVmѶTL1UraZzS.>(i(XtLIВkQW/׼DuT`仺^K.X~Ǻxf&'!RjV?rH?3~3~ Y3| QT;,>q1ʂ ^?&S`Iqx78!q­`4SVFMq0=ޯLpω/Z}Se!@Ȉ47˱V1 CNQOnG<E 0)`#-0Aij4wV|KwH6&ɳDZ9~&Lr_MW~]ۻ?-""md E414foiZ&~Af*{*zO/Pk{k !0 fxd $g1:1%vƌ嘙3~v fdqA*:l o''uƌ?L{ƌ?6qzUoJl'x\g>B)GO!(>rq A\| VMa-}&Y ~ͫj A`et|w;8'UfRr>B8ً^؈AFrZ\å1P mK<&K{ Űl|H; U횫7&O{e'1A3UůʽՅ7LjwykDfR#KŦo# १1@Ul0=;8Z,.}{-ŸT 1zdb̔n蛇yko{7ysߧz]g'Shsƌ?:{ƌ (v@`rIjLyW,s9LHO*GC3q-!e/]-$` &)Ƅkq -4XJz۝2>|QqM @`VGYCi !R.0%e28.];HQz(|Ty5XCYlw׺RFs SӨgcHYMMK)\jo2"*3ZDh!WSyj寂 gՊ Zpn#\,464%<Z摋۴s|&Kglת$؝wnXDg{ƌc31cƱݐE5[D,%*/[}c&0٦J)1ԂFx1m d UڭjRLn8ՊVKZuH/vk9;7ő2hg4OB6Vw.GA@b*Jj7b?a%e,XrAMCMMC1b`0`ךk{k;p2jDP" ~5H.fhAiQ cXM׌G(׊\DKy|b!QذpuT5'-8zXfVfL#?1(ۉf:;eb!)ә7e'؛v? "6W̘qcf3f8&\0%-&{ȩ|Bk`@B831>%s"LEUyF\,AE*(^bPFF6n.:Wh%fj"#%\p;?\ňlDC}0}5`E=6 ƇRJP~+} ~-W (EAusvUѮt!5PJ?~0|H0f]fjU9dq}yucIH}5?ndãݵ׼j3Zq2woVDs6ŊަOm?^MRv bC93 !07ZΘq cf3fknSJ˗hBF| d/;Oh;{O7ڂ':^CnfxU+3|Ki(d)N27TυG4x?W3פ3dlGdxɋ/|)yP9IQuc>IM4"7-3Fpgknέ3f3~g)K̈ T-% bяw9#eL.:zY1xJbAμ}S5t–'1uUwt|y@)lLbY6)"˹! Sc7i  W} )fPo`Fיˇ3Sw3O/yQWI8Lv/sł\l'YYXUg 9j\ br̐jIe桎G6nf 81g)>X@vA-]E_ܲsb/qS1)Ubz.@2V=]zOɖK }5l]]* 5tqhTst/T|Wd욈憚9΀dJ6OBHD'rVpَt&s_Lg8133f+ֽK.rFX .IRxNbCږ+6DLSijā׿_')֑>+~[YDC(8#7H5Yn64Dh pqKryrYBU9:t0wh8OR.Ƙ(QI)YE[vP+W!{>ϿywG\-uWݕzM]"AH(Rcb"[[Œ~O 0( %oPYJ/}0(Wnz9xoh{E'T<ֶX0$JiM )#e7$SԦhvwuWDDL1h/qZҦlC1RJ%@וY؜sX3f133fC喽.ߊ\`jNHc邗>uR$/#&OeP~[Z*S/r6f#xA,Q͂"(Z'nv|*>#V+չ5iLg&07vI I&1oqĽmѴԖ:0{B$fz[EL8Id1҃zmU_RՓr3}-Զtu[cUHQ&3rЯ9˖cfn"uov%|G>W=R4671:լ?,; R6&Ҡw5DUD}WmSMq  bdDqyTu`wlb?tHߌ3 {ƌ~HO>Eg7߹GKw|?rH=d#;I2@h7 k2MV'."6{ ^sq"vySdb2y2y#jaw.eJ#"ɥm3ϊ XJp?TUhfLsFJ'8J|"RcBK@1%/o}IK2dd%ZUq L'{t>\7%o{YqgKie1CxbՒNy]lL 1Z%&(-%ڱ7n텀,0B.x#)yRA(/%D`Ѥ#߇w-'t4f=cƌc W]WW 2`\njMZ &1-Kw}#MNئ \_T<-eK 1:_cd+j&g+_b~]A[d_GYCQk()Y醒Z K4)*h޾x;%mP(tG|Ʋ✎wh0 j^n(Ŝ;ҋ/XTOh:eDW~+O>wWu'F~rrb=*X,ۺ+XRqdl5o#!gkr-9j>ޞ߰>߾G6ӘlTڐJ{}oFդɷݝ͝llM3fӘ39Ԣx䌳Ό>]^ IDATPoc&&Oh) ?C"[-}ڀDYr֡މ/P}l=oO{ZpՌ!cqid=~&3#r:]BQJRDВ3%½+N2v-ҷ}vt1B={F8f}Nã1PD*P7#<կYspe\R -46lBSv=wbf@@A1h"Kj00!\"ԳP=]Toֶ^[pi*UZ,)g˙4FzF1J n(# HߦwFf6C>yΘ1yϘ1/t(g̡nD|"e dE q^bi$_Mr1^u f=+zĐRkĕ'W3 4Ʀ] k(eVBv5HΝ:_{*g7uA =-CffW_{곟=y մGie߽3@1~9]yŲ#뽻1=$5&"U>bSIx,9mж!you^ʮC!e,4LY*{^ňs)QԩI.܋Wɺ,Ck[XC9AyWG! 1dY@3Lv07k|$\d>.xj/g_F^_sY=mR7ktEpõ[7Eug|XU o=n(A$-bM;\?ZjVZrIM͋u ->]1y!W1qAMs(>D ;qhU $u33ZC֛1bc֔2G)#ir<llel dn@.?T){Eyc&/zQMXԧ3#|ٌ5 OL|3`:vF־_|~7\z=>nmсłBzRQ-Jc@xғrE-XP(2G39V)Ȟ$~{JD 2Oah5#\5V(,[}K" ho %#j1ZxS}H-cYP+XK)#"oqgn\Ĵ\RJ(e:(I6O{j> lj[CߥjmR9yؒmg̘qbf3f8Q(R xyJO<5,%2Dxw}w3dP,~Gl9OxFϰ#&bϢFjoaUGFeש[!ʣ_r;:j_A9ې1qTڝ>Ċ߰=7bHIs 1bŒf+?j8C3iܴcL`&p-$j <ߊƜ:Z)2%cj e:V\څ2ȳn8׎mDYd֝u^`LMJ埐eW}!@ͥ`.䱀4 : 5c$Ɣ:-rwb1>j:!GUg=c31c1;X-0EJ4Br B..ם'7Db"$dxO@jcӊ׾jLLD >`D|^pn&TtwvMv %bR2UOg=/-2jG&Ȗˁ{% J>8 FL̚ņVT*ӳXBHBAGϭ,{30V#.1a}y,fL\^2]=@*lOD{_>5'~ 1ռC˵TЋX%jz.#byfw̄{ƌ#f=cƌcݺw+W˖D-e]ɧCBwTME5y eP }*1L7) 6Oyr8[ `I QUgTMzLi5q%~&V 81ljm1[,M.ЎuEz.zmP8h1p.d6)Hxr4dD*\xo>luW.nkDɣYoU%ث/BQlD^DJĿjfb :H؂BǽyyQV[7/$y e> T7:dǟQwlPƒ]bHkrHD)'VKyηUI1ۡlNLQ,flNrzڎT2Y1' 31cӑcQp7k;唰\rIn_*dQm(D@M߽՝wo꒥H\{.!J`wlh*;Q6]?xi!q&;Pu}fj6a1$D>WJ4̙r*Q+SlE9Mg;5XK*z!IGٻ'?I8UX@&t<٪F2q?= $OA(QH{oRo}W_lZ0D(fوҟm?ﱘ-7f"/[l٘q >Ѿ?!SRLby>ڴ. Y1'31c߾ҭB!`?۳Ԏ |h̵om1si(̨ zK9T1|'IcVd"*3q|F#rds?W~]Er,,e !<hS;k%0Dl^od =|rY\e's67vOGU' sfc]V?i/ C"oNzR%- UWZРT#t\ғO |W<F14HM뒬Ol̀, bkzFdD.I>~ƌ|{ƌ|oy?|#V< {$KMr&ڻ`KLϻַ>twn@!8(B%rKQ8RS555*g,/U3SL98B0IHw0T>}k?};;&c99}>tg~ٯ?(p{s_}{C 3!Hue.Ԙ;Gw۟?vwi[L An(FUu?/'Q?}3־WhkҪPŧM<׿n>U_TcwBJWs,Tjj\kѶDDna_uïH'/B2%eX~`!](XQ(K}ZV|\{iֆuZLUn_$~#.83l"b&K5y1x{'d*R#Oc3k ,"$KS#;ÏKH `'Ӿ!J2-\/)9x?WmmCCv- B0:6Β;[8KkEJk:ri>ov5|9rjļo\cTC7nAU^zLrG[OLVӣq휬P$ xþRt+[wԎ&SI$ԕ=Âz?# 7Dz3bCȴԙÇ.lɻ=7׽\j9OD䘼trʫ6S*w./0=PꙷҰ\&Y n @& &Ȁ5ښ{w `n+&q˥,,e\|Ƈ^D/r5J3,sC'wQkDPN2|y~{Ζ2Kk[;p |-6>#0:{.: S *nzSS- Tobߧ.'"~a__nا?|hBDt ~9׷nm J6QidU3򡻎3r͋w]AcZM8MDcLDt;W_=WE2RY?0_e|(%~5`S J$dd1oA%]SBRg3ɶ1 򮏠v 󾵄<s^y'YVUvK~'+BDRM&>1|k:kv~R_:Ys3%[+g@7/ֈzx'L&H|{ۚȪS`m+);޹;F$Aȿk-m&o?ͤm귌񨛈`&R)蒅j~;"{w+K|c;po7~}{CL1@Mj`yߴmD-GV]g˥l) wӟN%"b%b[nV<؛}4 ʤo*\\Ev mkmg! #^ UKK(m: u{{/ r1UOO;į}  &-%[e6 ~E+*mL}Pi{ &oPU1|屩Kyxz_.M;s=r$\ΐ`!H(KHDvPiwhӻ_Kn[Dih}OmbE{3 m˾ {CkU{{qonYnfP؛Xm7@DWnxI\6f2Xi!?ZĚF}y\fS4k,"MWTՏIO&ij(%\rͣ[y$i0NA>W/cDlƿR bv(JRj;G_rC HӈȐ1ܪ\݆W!^\Xؗ;/YV7 2_DZ:󉈗]Q 67Q[-}]ox.iT aUŭ5UJSn>-7"@AP Î!U|>l:!"׿|3OW ufᬺN`7MIvd"kͻ9RɨNի"ĈT&Y,^$ꨉQsw.6:{wކ_5BPUUlՇG":cw%?0OANH ֢j>JD)">gQfUBGp3PSd_{ͮK ٚFR׈QD @8^SBN2}EE wuf!өĀ%% AJwW6՛9;ߵLM#AD4QZ`RL1h?[ΏE-gxB ի_qpLd2|Ӛ "bAR|T_wߏwZ ' S_l3^rM"344uy"$^ IDAT~A\@l4H˺1T(Tz=]MW7 <d$`AkQK6ПL䁣z46X=fqlgJLDguB-b&:vεnzd-fvڱ J/EOy+0o:Fk{_#HU,XXH;7\iI|@~ҏw'o9~}- &M2ƺ]ŐoF؜[D{ 1,AARi[LoQć?7>{{ꥁ"LD?ahXꭆ|rA|&IزWPL՜Ѷֶvב%7ٛ{VOC"GeZVNI#t ;wFFBFwUXj$<#]I=PoXZNw"KݝF3YV27vs)(\*E|Lb)Yz6DPQz12{_ V#>[$ վپ`2ҤdsiD (TUG>s.Y(8p ,[ X:6": cݷn=x[V~97N}\*Ų5ɲ33|EMZJuvh;k-9g~߮/%:MuE,%-κmg˅l)߼93o:t ]=Xt>FWo~C&4Mh"B1IO]B,u::tסm~!Rԭҹ?q.j>WJ -0 ;8{+V[|;rAh&r]eoٷ7MS/]䌒k^v;)9|hy틶ba;;X"]}Dtgރ7^c56׿O|B-=P=ۛ\1siOf3Sn?Ũ501"H*<{("{EE뮝j?J6z(wO?W/3-%>#X,xKZW;lTR 6fwnA ^LP`~zHvZKBH|yM]lAH?sX[uOeW^lDZ}C;v-[ܲuЂ$DtJ9K5׬~5MD岶Ƌ9:ol / 3f2tZ#Z LR4 6]%'BIH:9\Qv>{<ƺcw]$*xHoL֟.I:3'9[N&Sax'Ge-L=>dPEmԉ U޶=QN4y.܃c&:]udqs1ooJC"կ'<>N=tZy/!ҟppt:n):DBuqcch|p+ו}W_kB&> A h5Lѩ1 S(ٶmc~A`0o^5bWe_+^S_cZ2>V^GVm{AħjJ.hɮjnPvtK_w{1]FDtbo?'7 _8/]g1YL?6gln}tm;ʰClej7?ALA#j޷O6f/~KLLaU?'h6QbԪo0ڱoSRgC&Mӭ܌~ī97Cu$ē踺34#+G]ZJQzsbl kVXX.}G'Dt8l\wMt+>Hx  mbZ,_U3X0Fe#5(;F,[Vu&MGf6QFC֨E.^J8$Z7ܙ-E1qUS1߇7)u H"R$KaϞQ>匝k[;o/"lL?3-VG>f 5L~[jy=n ;b~<_{-_BLDL:@-vu a9ÌWiCg1>}>1`0qd{w5 ]?좖uwNHߡL_rjJC'c]q#r&90t?/_XYZ$DwXT ۨEBwщt6&o":#u&$燯?1"~Ep_śzg5XT/ƛtFCuWnLP_]c0#5zQbg?/xs=ub&Z[c3QӈMl$FJkݲnZF 9#F ȮWNpr_YJY:=0CzYh>pܲu bz bȊpl $79Kb7o; 3A4ϕAo-U9ޯb;ñi!8: j+Pf)R2r?iڃA%VGA8+RNG7U{7Ν,-F\GM_$ϵ;4oX*\.mgecCB@Qi|aPDCI5~\Ş}K6C@Ӡ 'ڭnTSUGLrc76qʫ6CD)2i_f9[ uɢ;g/? ::dMn}jטYPh@P A >ԉs1HW4LtF7vw/bDČI%Ilρwu O|BAM T%F_no{3匮.]G~+vX?g2ۑDta&39xpSg4R >eX-<c0hwkXv}嫥K[$}  ~Nz1QgQA+3dwI1X S!|$ QV*يXgΈ!-o&1]<Z,mZW#(Ei$g3?K]]sqPX[;<%":1yљv^y, x\+rM.cƸQޭ16U+H"b8n"N0bj*ARK HlD~Ő?FУ*Jޓ&÷}7 :3!*%f޻n.F JmKɺdHUG#uVϪ뉵⡎!-ѩ{XoLDgiXQu!(w/& 17xiP|ZLĦ\vc!ڝ>'5![  4*u:~%xx秇PL/# XMQ#JD {;RЩ_ uIb>]\ IͨMDg&o":ÔBʔ^7 !D!3XOkEwsK 1Z̈MOaVj W$ba8PAVKr_lh;iĊri`xъ eU5ZW<OUkH].]G{nIq_zYɛ@^$Ou猶[v6Lǀ%Dbp?^j,CºJP;)GG.vKNjA~/pl%dǘMDg$?ͽ8(?W_n֘ըݶ%k&WiQi4ٰgZ_-lo[סF BA^=Ն~ZMCٱRBΪ: KffF/|v AZQW~sgMRWgi>tO1 tkٌVa%D_S[V8!Uou>{Zh[xJIc>JOދMUYAD.Rw|xp$3Qd;;d)Дb?p S2?D[Yd͝{b&3SJ4ptxhM]cj 79LXBS];7|3h dF13-}жֵuK?F{1J*y+RK_ uja3O~/7,G@xhn3UWonek[7f2LLdHӈZ/| _O9JA.v-:;9H̦#FoCm$fZ ::d7߸ߏLf3f-}mstzwn/x|: ^U-~/SJxpxW^9 *4Sn!",$:Sq5lsN1${GZ%gmm׌1]gem>X s>މ )jE6XK@ljx6_g ۺ/H^LDާׁ??N  MDgek!]:SF*%Z8Jbi]gM2ϹdgO1ZS0 I\i#*nq)Ea {2hڅ*/αtH }:^.(K;喇o&!"ɛdG/^yuؘ֯&l"~kZKwQ?*}0 ;CjG+78Ԇ`yPׯ*vIE컾k˃1Ǿ)Sp}0p7sqo | ]4߬mDY9+^JSIfSL|:};T@7vd}ΟG?{ u8\^Y⢧63SxJHgԬdԩ׽nri~DDg=&o":+ŷ=9~/Y-n7h[_> zkl"&A1juXi}"?z.7IWcy- q\W%JHPmDFϻSzWm6H!H Q+EKfZLĻwƬ1z]P}?ieUE&|:?tCc榛1UXR:k;dGMD^L$ *{Uy }ch@]껮\yd8A !H &EEb"*!Jr ~d?"ҟvWpzpesnnk1Z`bC7a6s Lާ$oƟ͔#bl&ILd2I#߶ NW9J R{cĽ /l @?^ vOXIbUNCw.j369FʐaSBLa ۯ.s#ȹ۔d LD z3k"B\jӓܨJ)fq*Wdц,=89 _pq)k~$Dgޱsͨ(E|o7l)KML&A3l-l%TMD ;/,RBֶ!":[0yo#/؛1ֵο 䄔sFJ= j[2,;ֵ%4jz׾d/Y,|<خMDtaغμG& "cƾ;_1Al!l&~NJ )euQQ;zT{3/9VjmF<^=ռ}Qo~=~+fn~}՛MSK;(oֶ*v_uf$FMDta~̌? G>Ͽ| :ycqyh`\)2)y XeѶuYNrc񨛈ion!q᷀p#_o2jNvNR]lDZ1y=7,l6(MSɨ[nv:6n""z6{dłLDg"3:hEW2AW55S5-V RCά$"Gg{/yCc$Dt:cAb b[=ẙ""ztxxMt.!+ qf ݂Q7=*?L ?>|:A?5??|:A?5?"}?[l??"}?[l?p!???p!??>=O?bl??>=O?bl?l?}%?5??l?}%?5?L?X ?(>?L?X ?(>ܳ]??'B=5?ܳ]??'B=5L?W ?þ?L?W ?þ?}%?5??}%?5&><O?Zl?&><O?Zl$(5??$(5?}?al?}?alo;A?5?o;A?5L ?þL ?þܳ]?͹?trunk-2018.02b/examples/jointedCohesiveFrictionalPM/testingJoint.py000066400000000000000000000103641324306050200254350ustar00rootroot00000000000000# encoding: utf-8 # Abstract : this script defines a "rock joint" sample : two rectangular blocks separated by an horizontal joint surface. Imposing relative movements of the blocks, that are clumps, allows to test directly the behaviour of the joint, described by JCFpm model. # jerome.duriez@3sr-grenoble.fr # Mechanical properties of rock matrix and rock joint : def mat(): return JCFpmMat(type=1,young=15.e9,frictionAngle=radians(35),density=3000,poisson=0.35,tensileStrength=4.5e6,cohesion=45.e6,jointNormalStiffness=5.e7,jointShearStiffness=2.5e7,jointCohesion=0.,jointTensileStrength=0.,jointFrictionAngle=radians(35.),jointDilationAngle=0.0) # --- Creating a sample of spheres # definition of a predicate from yade import pack Lx = 10 Ly = 10 Lz = 6 pred = pack.inAlignedBox((0,0,0),(Lx,Ly,Lz)) # use of randomDensePack() function nSpheres = 1500.0 poros=0.13 # apparently the value of porosity of samples generated by pack.randomDensePack rMeanSpheres = pow(Lx*Ly*Lz*3.0/4.0*(1-poros)/(pi*nSpheres),1.0/3.0) print '\nGenerating sphere sample, be patient' sp = pack.randomDensePack(pred,radius=rMeanSpheres,rRelFuzz=0.3,memoizeDb='/tmp/gts-triax-packings.sqlite',returnSpherePack=True) sp.toSimulation(color=(0.9,0.8,0.6),wire=False,material=mat) print 'Sphere sample generated !' # --- The joint surface : half of the height import gts v1 = gts.Vertex(0 , 0 , Lz/2.0) v2 = gts.Vertex(Lx, 0 , Lz/2.0) v3 = gts.Vertex(Lx, Ly, Lz/2.0) v4 = gts.Vertex(0 , Ly, Lz/2.0) e1 = gts.Edge(v1,v2) e2 = gts.Edge(v2,v4) e3 = gts.Edge(v4,v1) f1 = gts.Face(e1,e2,e3) e4 = gts.Edge(v4,v3) e5 = gts.Edge(v3,v2) f2 = gts.Face(e2,e4,e5) s1 = gts.Surface() s1.add(f1) s1.add(f2) facet = gtsSurface2Facets(s1,wire = False,material=mat) O.bodies.append(facet) # --- Identification of spheres onJoint, and so on: execfile('identifBis.py') # --- Engines definition O.engines=[ ForceResetter(), InsertionSortCollider([Bo1_Sphere_Aabb()]), InteractionLoop( [Ig2_Sphere_Sphere_ScGeom()], [Ip2_JCFpmMat_JCFpmMat_JCFpmPhys(cohesiveTresholdIteration=1)], [Law2_ScGeom_JCFpmPhys_JointedCohesiveFrictionalPM(smoothJoint=True)]), GlobalStiffnessTimeStepper(timestepSafetyCoefficient=0.8), NewtonIntegrator(damping=0.2), PyRunner(command='afficheIt()',initRun=True,iterPeriod=1000), ] def afficheIt(): print 'It', O.iter O.step() # --- Clumping the blocks upperBlock=[] lowerBlock=[] for inte in O.interactions: if not inte.phys.isOnJoint: bod1 = O.bodies[inte.id1] bod2 = O.bodies[inte.id2] if bod1.state.pos[2]Lz/2.0: print '\n **** ERROR !!!! ******* \n\n' else: if not (bod2.id in lowerBlock): lowerBlock.append(bod2.id) bod2.shape.color=Vector3(1,0,0) else: if not (bod1.id in upperBlock): upperBlock.append(bod1.id) bod1.shape.color=Vector3(0,0,1) if bod2.state.pos[2] 0: # run simulatinon until the interaction is broken sn,st = i.phys.sigmaN, i.phys.sigmaT.norm() # store last values O.step() if O.iter > 1e6: raise RuntimeError, "TODO" # not to run forever plot.addData(sn=sn,st=st) # after the interaction is broken, save stress to plot.data and return return # run n simulations from tension to compression n = 50 for i in xrange(n): sim(i*pi/(n-1)) # plot the results plot.plots = {'sn':'st'} plot.matplotlib.pyplot.axes().set_aspect(1) plot.plot() trunk-2018.02b/examples/mortar/modelTests/shear.py000066400000000000000000000070201324306050200221060ustar00rootroot00000000000000from yade import plot ###################################################################### # Script to test brick material in shear. The material is # initially BrickMat. After failure and deletion of cohesive mortar # interaction, the simulation continues with PolyhedraMat. ###################################################################### ###################################################################### # I N P U T S ###################################################################### x,y,z = .2,.5,.7 # dimension of the bricks vDspl = -1e-3 # vertical displacement (+=tension, -=compression) hDspl = 1e-3 # horizontal displacement for shear nSteps = 1000 # each phase will take nSteps iterations ###################################################################### young = 4e9 poisson = .2 tensileStrength = .2e3 cohesion = .3e6 frictionAngle = atan(.5) compressiveStrength = 10e6 frictionAngle2 = atan(.4) # independent friction angle for PolyhedraMat for residual strength ###################################################################### O.dt = 1e-3 mortar = MortarMat(young=young,poisson=1/(2*(1+poisson)),tensileStrength=tensileStrength,cohesion=cohesion,frictionAngle=frictionAngle,compressiveStrength=compressiveStrength) polyMat = PolyhedraMat(young=young/z,poisson=1e3,frictionAngle=frictionAngle2) for mat in (mortar,polyMat): O.materials.append(mat) # two bricks with blockedDOFs, initially with MortarMat bs = b1,b2 = [polyhedron(((-x,-y,-z),(+x,-y,-z),(-x,+y,-z),(+x,+y,-z),(-x,-y,+z),(+x,-y,+z),(-x,+y,+z),(+x,+y,+z)),material=mortar) for i in (0,1)] b2.state.pos = b2.state.refPos = (0,0,2*z) for b in bs: b.state.blockedDOFs = 'xyzXYZ' O.bodies.append(bs) # function to plot the results def plotAddData(): dspl = O.bodies[1].state.displ() dn = dspl[2] ds = dspl[0] try: i = O.interactions[0,1] fn = i.phys.normalForce.norm() fs = i.phys.shearForce.norm() except (IndexError,AttributeError): fn = fs = 0. plot.addData( i = O.iter, dn = dn, ds = ds, fn = fn, fs = fs, ) # checkpoints def checkpoint(): if O.iter==nSteps: O.bodies[1].state.vel = (hDspl/(nSteps*O.dt),0,0) elif O.iter==2*nSteps: O.pause() plot.saveDataTxt('/tmp/shear.dat') # # factor to safely create interaction of just touching bricks factor=1.1 O.engines=[ ForceResetter(), InsertionSortCollider([Bo1_Polyhedra_Aabb(aabbEnlargeFactor=factor,label='bo1')]), InteractionLoop( # both ScGeom and PolyhedraGeom, MortarMat and PolyhedraMat [Ig2_Polyhedra_Polyhedra_PolyhedraGeomOrScGeom(label='ig2')], [Ip2_PolyhedraMat_PolyhedraMat_PolyhedraPhys(),Ip2_MortarMat_MortarMat_MortarPhys()], [Law2_PolyhedraGeom_PolyhedraPhys_Volumetric(),Law2_ScGeom_MortarPhys_Lourenco()], ), NewtonIntegrator(), PyRunner(iterPeriod=1,command='plotAddData()'), PyRunner(iterPeriod=1,command='checkpoint()'), ] ig2.ig2scGeom.interactionDetectionFactor = factor O.step() # after O.step, a cohesive interaction is created and we can change material of the bodies. It has no effect on existing interactions, but newly created interactions will be created form the new material for b in O.bodies: b.mat = polyMat ig2.createScGeom = False # also for new interactions, create PolyhedraGeom rather than ScGeom ig2.ig2scGeom.interactionDetectionFactor = bo1.aabbEnlargeFactor = 1 # deactivate interaction detection enlargement phys = O.interactions[0,1].phys phys.kn = young*2*x*2*y/(2*z) # sets correct kn of interaction phys.crossSection = 2*x*2*y plot.plots = {'dn':'fn','ds':'fs','i':('fn','fs')} plot.plot() b2.state.vel = (0,0,vDspl/(nSteps*O.dt)) O.run() trunk-2018.02b/examples/not-working/000077500000000000000000000000001324306050200172625ustar00rootroot00000000000000trunk-2018.02b/examples/not-working/Se3Interpolator.py000066400000000000000000000026671324306050200227040ustar00rootroot00000000000000# default parameters or from table # encoding: utf-8 """ THIS SCRIPT IS NOT WORKING! ERROR MESSAGE: Running script Se3Interpolator.py Traceback (most recent call last): File "/home/me/YADE/YADE3041/bin/yade-bzr3041", line 182, in runScript execfile(script,globals()) File "Se3Interpolator.py", line 8, in Se3Interpolator(ids=[0],goal=(Vector3(10,10,0),Quaternion(0,0,1,pi)),startIter=10,goalIter=1010,goalHook='print "Finished moving the thing!"; O.pause()'), NameError: name 'Se3Interpolator' is not defined """ O.bodies.append([ sphere([0,0,0],radius=.5), ]) O.engines=[ Se3Interpolator(ids=[0],goal=(Vector3(10,10,0),Quaternion(0,0,1,pi)),startIter=10,goalIter=1010,goalHook='print "Finished moving the thing!"; O.pause()'), ] O.dt=1e-6 print 'Initial se3:',O.bodies[0].phys['se3'] #O.saveTmp('init'); O.run(); O.wait(); #print 'Final se3:',O.bodies[0].phys['se3'] #quit() trunk-2018.02b/examples/not-working/facet-topo.py000066400000000000000000000053401324306050200216770ustar00rootroot00000000000000""" THIS SCRIPT IS NOT WORKING! ERROR MESSAGE: Running script facet-topo.py Traceback (most recent call last): File "/home/me/YADE/YADE3041/bin/yade-bzr3041", line 182, in runScript execfile(script,globals()) File "facet-topo.py", line 14, in O.step(); RuntimeError: FACET_TOPO was not enabled in Facet.hpp at compile-time. Do not use FacetTopologyAnalyzer or recompile. """ # Note: FacetTopologyAnalyzer is normally run as an initializer; # it is only for testing sake that it is in O.engines here. O.engines=[FacetTopologyAnalyzer(projectionAxis=(1,1,1),label='topo'),] # most simple case: no touch at all if 1: O.bodies.append([ facet([(0,0,0),(1,0,0),(0,1,0)]), facet([(0,0,1),(1,0,1),(0,1,1)]), ]) O.step(); assert(topo['commonEdgesFound']==0) if 1: O.bodies.clear() O.bodies.append([ facet([(0,0,0),(1,0,0),(0,1,0)]), facet([(1,1,0),(1,0,0),(0,1,0)]), ]) O.step() assert(O.bodies[0].shape['edgeAdjIds'][1]==0 and O.bodies[1].shape['edgeAdjIds'][1]==1) assert(topo['commonEdgesFound']==1) if 1: O.bodies.clear() O.bodies.append([ facet([(0,0,0),(1,0,0),(0,1,0)]), facet([(1,1,1),(1,0,0),(0,1,0)]), ]) O.step() assert(O.bodies[0].shape['edgeAdjIds'][1]==0 and O.bodies[1].shape['edgeAdjIds'][1]==1) assert(topo['commonEdgesFound']==1) assert(abs(O.bodies[0].shape['edgeAdjHalfAngle'][1]-(-.5*atan(2/sqrt(2))))<1e-6) if 1: O.bodies.clear() r=.5 # radius of the sphere nPoly=12; # try 128, it is still quite fast def sphPt(i,j): if i==0: return (0,0,-r) if i==nPoly/2: return (0,0,r) assert(i>0 and i=0 and j<=nPoly) theta=i*2*pi/nPoly rr=r*sin(theta) phi=j*2*pi/nPoly return rr*cos(phi),rr*sin(phi),-r*cos(theta) for i in range(0,nPoly/2): for j in range(0,nPoly): if i!=0: O.bodies.append(facet([sphPt(i,j),sphPt(i,j+1),sphPt(i+1,j)])) if i!=nPoly/2-1: O.bodies.append(facet([sphPt(i+1,j),sphPt(i,j+1),sphPt(i+1,j+1)])) print 'Sphere created, has',len(O.bodies),'facets' O.step() assert(topo['commonVerticesFound']==nPoly*(nPoly/2-1)+2) assert(topo['commonEdgesFound']==nPoly*((nPoly/2-1)+(nPoly/2-2)*2+2)) print topo['commonVerticesFound'],'vertices; ',topo['commonEdgesFound'],'edges' #quit() trunk-2018.02b/examples/not-working/insertion-sort-collider.py000066400000000000000000000025421324306050200244310ustar00rootroot00000000000000 """ NOTE Needs yade compiled with CGAL feature """ O.engines=[ ForceResetter(), InsertionSortCollider([Bo1_Sphere_Aabb(),Bo1_Box_Aabb(),Bo1_Facet_Aabb()]), InteractionLoop([Ig2_Facet_Sphere_ScGeom()],[Ip2_FrictMat_FrictMat_FrictPhys()],[Law2_ScGeom_FrictPhys_CundallStrack()],), NewtonIntegrator(damping=0.01,gravity=[0,0,-10]), ] mat=O.materials.append(FrictMat(young=1e3,poisson=.2,density=1000,frictionAngle=20)) O.bodies.append([ facet([[-1,-1,0],[1,-1,0],[0,1,0]],fixed=True,color=[1,0,0],material=mat), facet([[1,-1,0],[0,1,0,],[1,.5,.5]],fixed=True,material=mat) ]) import random if 1: for i in range(0,100): O.bodies.append(sphere([random.gauss(0,1),random.gauss(0,1),random.uniform(1,2)],random.uniform(.02,.05),material=mat)) O.bodies[len(O.bodies)-1].state.vel=Vector3(random.gauss(0,.1),random.gauss(0,.1),random.gauss(0,.1)) else: O.bodies.append(sphere([0,0,.6],.5),material=mat) O.dt=1e-4 O.saveTmp('init') # compare 2 colliders: if 1: O.timingEnabled=True from yade import timing for collider in InsertionSortCollider(),PersistentTriangulationCollider(haveDistantTransient=True): for i in range(2): O.loadTmp('init') replaceCollider(collider) O.run(100,True) timing.reset() O.run(50000,True) timing.stats() else: #O.run(100,True) O.step() print len(O.interactions) #O.bodies[2].phys['se3']=[-.6,0,.6,1,0,0,0] #O.step() trunk-2018.02b/examples/not-working/triax-identical-results.py000066400000000000000000000041601324306050200244150ustar00rootroot00000000000000# Test if different algorithm version give same results for TriaxialTest. # # The first run creates initial sphere packing, and saves resulting positions # after 2000 steps. Subsequent runs only save resulting positions. # # Compare output files with diff to see if the are 100% identical # """ THIS SCRIPT IS NOT WORKING! ERROR MESSAGE: Running script triax-identical-results.py Using new initial configuration in triax-identical-results-in.spheres Traceback (most recent call last): File "/home/me/YADE/YADE3041/bin/yade-bzr3041", line 182, in runScript execfile(script,globals()) File "triax-identical-results.py", line 20, in spheresToFile(inSph) AttributeError: 'module' object has no attribute 'spheresToFile' """ from os.path import exists sph='triax-identical-results' i=0; outSph='' while True: outSph='%s-out%02d.spheres'%(sph,i) if not exists(outSph): break i+=1 inSph='%s-in.spheres'%sph if exists(inSph): print "Using existing initial configuration",inSph else: TriaxialTest(noFiles=True).load() print "Using new initial configuration in",inSph spheresToFile(inSph) TriaxialTest(importFilename=inSph,noFiles=True).load() O.usesTimeStepper=False O.dt=PWaveTimeStep() # # uncomment this line to enable shear computation in ScGeom and then compare results with this line commented # [e for e in O.engines if e.name=='ElasticContactLaw'][0]['useShear']=True if 1: #for i in range(0,100): # # O.save('/tmp/a.%03d.xml'%O.iter) # O.step() O.run(2000,True) spheresToFile(outSph) print "Results saved to",outSph #quit() trunk-2018.02b/examples/packs/000077500000000000000000000000001324306050200161055ustar00rootroot00000000000000trunk-2018.02b/examples/packs/LSMGenGeo.geo000066400000000000000000000533511324306050200203300ustar00rootroot00000000000000LSMGeometry 1.2 BoundingBox -2 -2 -8 3.25 3.25 9.5 PeriodicBoundaries 0 0 0 Dimension 3D BeginParticles Simple 387 -0.9262759724 -1.058448624 0.5934789567 0.5934786407 430 0 -0.7337180112 -0.2941556215 0.261686305 0.261686205 443 0 -0.2919400221 -1.655024767 0.3194234863 0.3194233863 3362 0 -1.597798043 -0.7768410239 0.2233630403 0.2233624688 3367 0 -0.59999 -0.8958448116 2.12929564 0.5992200167 3 0 -1.391964921 -0.5277399199 1.443261338 0.5113506679 1199 0 -1.303523473 -0.378461522 2.213426408 0.2781175118 1595 0 -1.630891607 -0.7520024509 0.8072844866 0.2040833284 1701 0 -1.473634405 -0.8977706025 2.131635292 0.2744295438 2071 0 -1.738324669 -0.2735094727 2.475494346 0.2402896715 2617 0 -0.6897737455 -0.7386775455 1.317455977 0.2325528744 2779 0 -0.3255598021 -1.701021961 1.960099027 0.268103162 3298 0 -0.2514673246 -0.6440563051 1.28313265 0.2171613747 3740 0 -1.078967707 -1.293215633 1.458540899 0.3157851267 4199 0 -0.5685924975 -1.180008414 1.319693758 0.2251180157 4716 0 -0.7054084502 -1.608126148 1.185407932 0.2439615207 6427 0 -0.4924743105 -1.69433734 0.8010799675 0.203789093 6813 0 -1.754045077 -0.4038110588 2.055145099 0.2000719028 7175 0 -0.5151785117 -0.7012326592 3.043265842 0.3390804537 325 0 -0.962231283 -0.2756646064 4.013247769 0.5244118502 332 0 -1.335289698 -0.7016520479 2.911267504 0.4915853067 410 0 -0.7732274232 -1.395768394 2.982084169 0.4043646491 489 0 -0.5922428323 -1.505260376 4.111211711 0.3824212717 905 0 -0.2563610202 -1.18980911 3.72977573 0.2157576834 933 0 -1.208006186 -1.100419576 4.241440604 0.3659235663 964 0 -0.2517286553 -1.750007106 3.06863066 0.2319804216 1149 0 -1.56096933 -0.6988291978 3.65928596 0.2897404173 1382 0 -1.043726696 -0.9918682395 3.596915809 0.3080068023 1585 0 -0.5528311154 -1.066418587 3.450936823 0.209530519 1910 0 -1.204422096 -1.298088689 2.528259748 0.2292177438 2028 0 -0.5957664579 -0.9651414936 3.859422001 0.2114186961 2463 0 -1.623541329 -0.6428289417 4.196330098 0.2538282313 2497 0 -0.9144055647 -1.514733988 3.5896823 0.2306620039 10154 0 -0.59999 -0.8958448116 4.415486066 0.2987756846 5 0 -0.3333868097 -1.435713311 4.979280878 0.5260869186 593 0 -0.3869390595 -0.5216383697 4.812543505 0.2869508185 613 0 -1.44401847 -0.4371375573 5.05456164 0.4412850013 641 0 -1.089168328 -1.053773536 5.646952374 0.4845039013 723 0 -1.661940176 -0.6545762196 5.632804867 0.2137983969 742 0 -0.4529320276 -0.8754153771 5.438724869 0.2082919711 1018 0 -1.275445314 -1.134233081 4.89620894 0.2931764493 1346 0 -0.5016864535 -1.594213683 5.802222137 0.3287108834 1968 0 -0.3222980916 -0.3781010466 4.346950179 0.2045350093 2728 0 -0.8200940204 -0.7301650712 5.047344949 0.2480615333 4466 0 -0.9947366542 -1.476342708 4.636758741 0.2198053871 7855 0 -1.007227087 -0.6747282642 4.630634254 0.2120903279 9893 0 -0.59999 -0.8958448116 6.701676493 0.5841147974 7 0 -1.441516913 -0.3379964095 6.256305524 0.519387269 412 0 -0.613106955 -0.4673174149 7.368604967 0.2087289542 715 0 -1.514035726 -0.4503758418 7.547926179 0.420397118 1208 0 -1.339352941 -1.107345657 6.348412812 0.2621612916 1224 0 -0.366948236 -1.699717529 6.819606778 0.2611230707 1245 0 -1.346261372 -1.035025325 7.158457733 0.3018542204 1352 0 -1.646328535 -0.6262239876 6.926762602 0.2385930301 4118 0 -1.015101782 -1.450091996 7.129621543 0.2299149358 4312 0 -0.6069158442 -1.667721307 7.241479643 0.2252767942 7102 0 -0.5885687968 -1.653947287 6.367507256 0.2444494545 8006 0 -0.9319880763 -1.537861961 6.107310498 0.2017726662 8658 0 -0.59999 -0.8958448116 8.98786692 0.4769849779 9 0 -0.7625178103 -1.119410114 7.899884063 0.6455577237 136 0 -0.6170254293 -1.597955547 8.687024792 0.2870530314 392 0 -0.3777876158 -1.625184541 9.256771431 0.3314825939 626 0 -1.283563758 -1.114231932 8.689245668 0.3002790741 901 0 -1.383545661 -0.8666779827 9.301241906 0.3674162659 1083 0 -0.9672470348 -0.3483064734 9.411018985 0.3064264559 1271 0 -1.11009469 -1.37280456 9.124059878 0.2345242645 1309 0 -1.515044464 -0.9603767252 8.265835361 0.2062097275 2977 0 -1.7257283 -0.3719517242 9.32639077 0.2346426418 3505 0 -0.7210896758 0.2518295903 0.2849396194 0.2849389806 720 0 -0.5219332188 0.8188427283 0.3168812735 0.316880504 866 0 -0.6267014414 1.499817426 0.3745129087 0.3745124277 985 0 -1.491411099 -0.1210109023 0.5186076369 0.503686712 1241 0 -1.232790525 0.9867784654 0.4209162333 0.4209157974 1307 0 -1.66516467 0.5763802377 0.7013922437 0.2379019064 3058 0 -1.665726266 0.5347840931 0.2326665462 0.2326657945 4855 0 -0.8235390604 0.5538473599 0.6958507279 0.2352122109 5676 0 -0.59999 -0.0875544347 0.9862004266 0.5034844381 19 0 -0.8290940609 0.3796825469 2.004712427 0.6402650416 654 0 -0.7286781976 1.384201973 1.632435946 0.4357145988 919 0 -1.046654292 1.281334117 2.33857817 0.3455197812 1016 0 -1.497440158 0.8636693311 2.392143754 0.2713438497 1441 0 -0.821907421 1.226295754 0.9385256813 0.2820158185 2067 0 -1.568195617 0.345657341 1.281795065 0.3941607041 2231 0 -1.703166855 0.06307234071 1.8964408 0.2956655858 2523 0 -0.6949614877 0.8224667517 1.250988507 0.2441261591 2624 0 -0.9881824033 0.4824317442 1.174413041 0.2113582231 2808 0 -1.341543659 1.01089861 1.153635108 0.3202214227 3013 0 -0.5402968907 1.074403433 2.386953611 0.2036233682 4905 0 -1.517201372 0.8572589318 1.681972581 0.257359453 4950 0 -1.66791476 0.5470723175 2.061725538 0.2169914437 8319 0 -0.59999 -0.0875544347 3.272390853 0.3214433853 21 0 -1.06773592 0.7885526323 3.229295153 0.6726425664 340 0 -0.7498313183 -0.2319065387 2.699096425 0.2884388168 378 0 -1.159913689 0.5047100171 4.146085485 0.2914974852 423 0 -1.623108353 -0.07537419077 3.436771653 0.3751423701 455 0 -0.7228701603 0.406662772 3.937367922 0.2026511724 548 0 -1.006531296 1.476706025 3.783248891 0.2128883792 1071 0 -0.5540101882 0.217360487 2.793393276 0.2106381725 1093 0 -0.2568472026 0.5400387049 2.893880808 0.2393893004 1232 0 -1.634919884 0.291294024 2.554661101 0.3393323939 2608 0 -1.071349517 -0.06854284388 3.036998382 0.2057666828 3429 0 -1.721499294 -0.1863956281 4.034140989 0.2403704542 5213 0 -1.284440242 1.202555599 4.013785762 0.2404753326 7866 0 -1.562652944 0.8133988067 3.993690482 0.238324038 9123 0 -1.715822356 0.4051521459 3.804479398 0.2369926342 10127 0 -0.59999 -0.0875544347 5.55858128 0.6020883771 23 0 -0.429994219 1.125331669 4.39547428 0.6985268781 171 0 -0.4467819919 0.0940597591 4.627646307 0.3586898547 173 0 -0.9710816884 1.035314638 5.55095151 0.5805366054 180 0 -0.8713823598 0.4529288543 4.932408698 0.2684677247 202 0 -1.409278882 0.7740637085 4.722812298 0.3921310807 216 0 -1.196023441 1.327041891 4.847515478 0.2135194914 690 0 -1.038962219 0.01156647117 4.725190428 0.2471125072 873 0 -1.545851698 0.2326874759 5.54748723 0.3965756348 1023 0 -1.71310573 0.2073296687 4.898193139 0.2743935244 1470 0 -1.675547252 0.2697722169 4.325542314 0.3028739947 2849 0 -1.788197453 -0.1508390301 4.587825229 0.2054518948 3714 0 -1.634321016 0.7429409617 5.853087317 0.2047368737 6243 0 -0.6516530918 0.1749873187 6.617322236 0.4892602242 179 0 -0.333869133 0.6438938785 6.045180859 0.3158519419 203 0 -1.313051688 0.5324841555 7.57185568 0.5830863139 283 0 -0.4765738139 0.3866877254 7.351649296 0.2940704705 331 0 -0.6239337153 1.258586812 6.651905797 0.5952449583 411 0 -0.7984373367 0.008884555642 7.278063735 0.2076715628 503 0 -1.059851448 1.34774691 7.411929271 0.2854411774 757 0 -1.348373159 1.143794233 7.03411662 0.2318431818 969 0 -1.144879797 0.3752497363 6.067944784 0.2757177494 995 0 -1.415548988 0.860185965 6.34201818 0.3435884254 1011 0 -1.329998712 0.3379535971 6.520892972 0.2150179946 1447 0 -0.6028869667 0.8973461628 7.3980579 0.2340201177 1503 0 -1.187229022 -0.1189634243 7.051388862 0.2601804276 2027 0 -1.718608921 -0.1171455784 6.969971433 0.2774023874 2610 0 -1.162537052 0.6338522115 6.790601278 0.2189657391 5389 0 -1.721842631 0.3664166211 6.292142272 0.2396011785 7465 0 -0.59999 -0.0875544347 7.844771706 0.4004721971 25 0 -1.347877361 -0.1856271248 8.560581135 0.6394001896 142 0 -0.2784913614 -0.1326220831 8.611907905 0.4325280602 155 0 -0.6523492589 0.574830265 8.323010487 0.4181887012 165 0 -0.6634815616 0.4865137244 9.323490908 0.5862431156 224 0 -0.5494995971 1.393180508 8.73185158 0.5023675184 419 0 -0.8484330838 1.209587749 7.975086862 0.3317548962 569 0 -1.391211443 0.8300100765 8.484497502 0.3800041869 855 0 -1.561217778 0.5756630105 9.131886928 0.3360311438 921 0 -1.239284215 1.140788854 9.091792134 0.3155932233 931 0 -1.798616293 -0.004244658002 7.874401339 0.2013778601 1903 0 -1.424922186 0.05038510146 9.470127477 0.3034209255 3105 0 -1.16597074 1.362269804 8.382503778 0.2068826523 3197 0 -0.4499889302 1.740686291 1.182941232 0.2020895616 2125 0 -0.6241699454 1.656550505 2.230566593 0.2297595445 8612 0 -0.5741581667 1.589636065 3.512034091 0.3098515945 361 0 -0.5418231498 1.571111749 2.865170358 0.3380840017 468 0 -0.4132849259 1.647574726 5.247999441 0.301380782 265 0 -0.4180482459 1.650054028 5.847168175 0.2978110721 364 0 -0.5603665521 1.620699984 7.45187031 0.2851582334 1546 0 -0.4681321808 1.723034149 7.976974566 0.2061557032 8173 0 -0.1438712725 -0.4439602344 0.3538210545 0.3538202255 380 0 1.078806963 -1.001062946 0.7105549032 0.5282821933 462 0 0.3703522055 -0.7209550372 0.5268907019 0.2553628144 632 0 0.4363837776 -1.174080604 0.2697885612 0.2697884612 768 0 0.6337751971 -1.6171953 0.4902503093 0.2630497891 1174 0 -0.1333782651 -1.056090632 0.2648332113 0.2648331113 1239 0 0.8107438771 -0.448916993 0.2431835637 0.243182492 1628 0 0.9785665627 -1.510726376 0.2000311277 0.2000304656 1883 0 0.10001 -1.29999 0.9862004266 0.5316134635 53 0 0.80001 -0.8958448116 2.12929564 0.6451163013 54 0 0.1213658936 -0.4601968287 0.9608583899 0.3088324553 128 0 0.07189571618 -0.8904500628 1.634147158 0.2354240765 156 0 0.4103297352 -0.7084082128 1.362564618 0.2351434553 310 0 0.2531620879 -1.638449309 1.7770077 0.3421063054 1150 0 0.7625842688 -1.622963275 0.9420689638 0.2068059452 1627 0 0.8426328648 -0.6668818594 1.315097105 0.2017358316 2035 0 -0.2135430558 -1.765732968 1.487997364 0.2214011395 3797 0 0.7616143178 -1.509315899 1.425212541 0.2895247134 5433 0 1.230656097 -1.247364511 1.430610207 0.2477346273 9654 0 0.10001 -1.29999 3.272390853 0.3744464676 55 0 0.03642993256 -0.4894162199 3.699502533 0.5439736198 200 0 0.3210798438 -1.429767487 4.144569056 0.5346227239 206 0 0.5530655262 -0.7525604676 3.05460367 0.323235324 360 0 0.06562444005 -1.571940019 2.519607916 0.4266897162 556 0 1.337301207 -0.7602076973 3.087428071 0.461723823 563 0 -0.2454589772 -1.127440913 2.84150454 0.204158648 714 0 0.8084565411 -0.697000857 3.580743641 0.2642457994 938 0 1.168465077 -1.102218761 3.953686446 0.3937003372 1105 0 1.25488126 -0.4971305903 3.745699552 0.251944657 1165 0 0.8014147781 -1.440022062 3.399412404 0.3519913964 1678 0 1.058935808 -1.38107863 2.847736178 0.25967677 2470 0 0.3623594375 -1.762694297 3.490518306 0.2004456367 2535 0 0.843456055 -0.4448401927 4.000047853 0.2262901901 2788 0 0.5689220814 -1.283375124 2.884317086 0.2344501061 2840 0 0.7122142058 -1.622082424 2.604900096 0.2274248249 3363 0 0.06691639067 -0.9235755207 2.620398179 0.22946247 7983 0 0.4403525075 -1.736614135 3.090003792 0.2084245144 10229 0 0.80001 -0.8958448116 4.415486066 0.2320856604 56 0 0.10001 -1.29999 5.55858128 0.2100115244 57 0 -0.08102624712 -0.8499714875 5.892071191 0.3786350621 231 0 1.140998622 -1.021793638 5.014217466 0.4683530269 703 0 1.423556656 -0.8526767318 5.753124966 0.3406111086 880 0 0.5160378027 -0.5385411972 4.31239665 0.2358175307 956 0 0.1136552275 -0.7479063827 4.427135416 0.2320604777 976 0 0.04652916054 -0.7916275528 4.880381678 0.2282093867 1316 0 0.4730214003 -0.7656496555 4.716255414 0.2295108167 1405 0 0.3059329021 -0.8885739176 5.332987268 0.302394555 1801 0 1.021350355 -1.440153702 4.462330546 0.2344407154 2099 0 0.8037285946 -1.331819143 5.803742485 0.4444533801 2213 0 0.06183398199 -0.4450544364 5.25155272 0.2103669192 3131 0 0.7104821256 -1.583615791 4.825000408 0.2643074737 3229 0 0.3891501498 -1.197970969 4.88142592 0.2408260776 4461 0 0.285213327 -1.7744654 4.795437467 0.202758901 5237 0 0.2537200633 -1.728621024 5.641629384 0.2528569062 8687 0 0.80001 -0.8958448116 6.701676493 0.4288508686 58 0 0.07067970869 -0.2671120912 6.373073183 0.3921443291 181 0 1.399437813 -0.4236755039 7.295158863 0.5378335098 187 0 0.1826180963 -0.8829702401 6.448954665 0.2383866657 243 0 0.6515148624 -0.3653138955 7.083738083 0.2415851244 326 0 0.05868074496 -0.9931926682 7.354449784 0.3483210116 355 0 0.09348087445 -1.548204175 6.300676291 0.4489754931 553 0 -0.08019075703 -1.659862973 7.441494867 0.3382001446 623 0 0.5209866795 -0.6038627794 7.530111362 0.2810916071 670 0 -0.01107912878 -1.393704141 6.949113598 0.2257636414 1045 0 0.5468231983 -1.529866426 7.126696074 0.3753429442 1966 0 0.1570459378 -0.7406922603 6.869991691 0.2067743055 2311 0 0.7835614666 -1.550031343 6.476581917 0.2631735595 2401 0 1.049564698 -1.457967076 6.848861653 0.203543259 3128 0 1.301613114 -1.138807363 6.279193739 0.2705260133 3481 0 0.9890935669 -0.7649990178 6.095515562 0.2194567215 3660 0 0.4548600043 -0.68630202 6.125060613 0.2281974669 6123 0 1.46091811 -0.8930458814 6.687806938 0.2322077189 7610 0 1.115436089 -1.206597672 7.274629476 0.2952590635 10577 0 0.10001 -1.29999 7.844771706 0.2315479192 59 0 0.80001 -0.8958448116 8.98786692 0.3191399771 60 0 0.2078406252 -1.42660748 8.616945175 0.5583315862 234 0 0.6091401079 -0.5906291362 8.361144504 0.4036108558 342 0 -0.04726847768 -0.7112084239 8.293482336 0.2672010861 375 0 0.02961932123 -0.6849815024 8.774351383 0.2204806393 424 0 0.241865 -0.8508595528 7.897737462 0.2424207254 607 0 1.284718447 -0.4273965802 8.148512841 0.323204944 848 0 -0.03409801095 -0.4404166498 9.209036612 0.2823334236 887 0 0.9094957921 -0.6276818875 7.828259365 0.2092125871 1031 0 1.224762626 -0.3927923506 8.955200704 0.3400582315 1063 0 0.4052255841 -0.5154293374 8.956324597 0.2300096106 1070 0 1.262880834 -1.057697414 7.905422213 0.3527010709 1669 0 1.33634604 -0.9703546143 8.597285918 0.3485129823 1813 0 0.8749604129 -1.154927722 8.317697446 0.2216733815 1880 0 0.9992305379 -1.421061511 8.835856148 0.2627952104 1988 0 0.7104037059 -1.448926901 9.416471832 0.3862890861 2200 0 1.273126556 -1.166488891 9.219983631 0.2732832016 2244 0 0.04457365124 -1.123608035 9.464357856 0.3563107872 2513 0 0.1356120288 -0.3379865024 7.933609686 0.2825691682 2605 0 0.4442829644 -1.662024259 7.82352226 0.2684961963 5280 0 0.5194568401 -1.205545938 7.94928213 0.2109184676 6655 0 0.8201004869 -1.567268924 8.138817736 0.23113114 7904 0 0.004930190305 -1.788701752 7.968843625 0.2112913537 8300 0 0.1705062046 -1.753634569 9.342172532 0.2380948927 8356 0 1.194733648 0.7801407518 0.5731122626 0.5731112732 208 0 -0.05637742388 0.2929949866 0.3891519087 0.3891508014 258 0 0.8820346146 0.05844271225 0.2698555073 0.26985506 583 0 0.5099511624 1.001307331 0.2258914227 0.2258905164 834 0 0.3958858017 -0.1595865813 0.2629901396 0.2629900396 1577 0 0.4514644968 0.6082704925 0.4019593223 0.2087337173 3338 0 0.10001 0.3165907537 2.12929564 0.2081502858 20 0 0.80001 -0.0875544347 0.9862004266 0.4658032171 70 0 0.10001 1.124881131 0.9862004266 0.6466862073 87 0 0.06431801562 -0.1903991992 1.685549229 0.4665516828 120 0 0.1202291202 0.1723031281 1.004354197 0.2621785212 129 0 1.07225206 0.8925999573 1.739273175 0.6048438487 296 0 0.435181736 0.3867338424 1.415571252 0.2706799007 327 0 0.8244394581 0.0004730192177 1.768198799 0.321513065 516 0 0.4339121945 0.3319079717 1.887478435 0.2044027924 540 0 0.2458445032 0.7145551163 1.759464242 0.2407662655 542 0 0.4085167234 0.6437183537 2.217014388 0.2499796093 591 0 0.1114795307 1.3904918 2.158926714 0.5557968426 598 0 0.9124345035 1.491851299 0.8786192471 0.2512411908 673 0 -0.09664103818 0.4733758149 1.541294671 0.2315271939 765 0 0.9344462226 0.08289812898 2.479656637 0.4031021504 1111 0 1.45559638 0.1696656514 1.043997601 0.2408056179 1347 0 1.265537684 0.09466430582 1.461440163 0.2239585252 2468 0 -0.2449194493 0.7842917153 2.498811364 0.2252409728 3294 0 1.356528621 -0.1249996274 1.996837594 0.271054899 4883 0 0.80001 -0.0875544347 3.272390853 0.4188187983 72 0 0.10001 1.124881131 3.272390853 0.5433349542 89 0 0.06235738818 -0.1699705525 2.678264533 0.5263742698 186 0 1.159701014 0.8194312773 3.486070728 0.5800090335 246 0 -0.09585770481 0.2925781906 3.365163047 0.3167225147 255 0 0.3413657231 0.6050561928 3.940718557 0.3370808954 373 0 0.5746151231 0.01803889902 4.181544919 0.3389297424 465 0 0.7391165281 0.9294578175 2.648211399 0.3639204255 612 0 0.1833207628 0.7653884648 2.607602129 0.2170059654 648 0 1.482017789 0.6663747454 2.600158919 0.3750584758 815 0 -0.1571112191 0.2604304444 3.946743637 0.2689570036 843 0 1.379045212 0.1620015999 2.991360484 0.2714971342 1327 0 1.137739174 1.339641356 2.849449128 0.2424186307 1389 0 0.5423438822 0.4017473035 2.89469048 0.2508556924 2502 0 1.02060628 0.1107484578 3.845925654 0.2268814377 2940 0 0.4288987419 0.4337809485 3.357522944 0.2267518728 3376 0 0.10001 0.3165907537 4.415486066 0.2686157884 22 0 0.80001 -0.0875544347 5.55858128 0.6654048977 74 0 0.10001 1.124881131 5.55858128 0.4943200666 91 0 0.05444084447 0.4248567143 5.047115057 0.3738428634 140 0 0.9034783182 0.958632999 4.714661203 0.6827103294 162 0 0.0609824741 0.4082221058 5.649877859 0.2291840634 169 0 1.054539476 1.024284102 5.916027022 0.5298931425 245 0 0.1976574703 -0.2324624533 4.728105852 0.3707006081 318 0 1.415920492 -0.1661456018 4.485498175 0.5743643254 655 0 0.709686555 0.08872748242 4.707369059 0.208548188 796 0 0.10001 0.3165907537 6.701676493 0.2783395446 24 0 0.7155898061 0.4362497487 7.178581855 0.5095024863 145 0 -0.01776849151 -0.1986009552 7.219173651 0.4613200305 146 0 1.081764934 1.158196805 7.119238288 0.3021706458 159 0 0.2861486099 0.1704094817 7.736357028 0.2429617399 195 0 0.7658446759 -0.1422804636 6.560496414 0.3385844774 210 0 1.263955839 0.2568220842 7.684062582 0.2575765215 291 0 1.413784261 0.7294122267 7.215758248 0.2486542704 496 0 -0.08962808235 0.6989929884 7.062992232 0.2808938279 877 0 0.3300848619 0.5816102294 6.172182075 0.3569036968 1021 0 0.3167337744 1.063267973 6.712999183 0.3674268362 1048 0 0.5870441075 1.262308177 7.179022322 0.2069087722 1433 0 0.8697863414 1.446749394 6.620329026 0.31192043 1868 0 0.8824336776 0.3895430809 6.335905858 0.2503722934 2793 0 0.7870782199 0.7775671444 6.554736053 0.2051943723 3369 0 1.116474863 0.9977749466 6.643702839 0.2008937784 3665 0 0.10001 0.3165907537 8.98786692 0.2648952234 26 0 0.80001 -0.0875544347 7.844771706 0.3421467015 76 0 0.10001 1.124881131 7.844771706 0.629338163 93 0 -0.08522707709 0.2754142033 8.073945927 0.2697869994 168 0 0.6226050102 0.4494659865 7.909142149 0.2270693411 239 0 1.188641791 0.9606085537 7.859807521 0.4717197943 241 0 0.3735503736 0.05344557321 8.294268913 0.2854507236 266 0 0.08201052468 0.3980603829 8.513773863 0.2164827112 277 0 0.8103140451 1.104186503 8.884939559 0.630387606 399 0 0.9721466084 0.05161826295 8.496880155 0.3465080324 428 0 -0.04348651901 0.8094945952 8.820203663 0.2751559645 429 0 0.7141980671 0.1095336059 9.220246258 0.4236530092 524 0 0.2289277941 0.6245156682 9.48070281 0.3303557221 579 0 0.06181948288 0.04810548127 9.449608266 0.2705931716 585 0 -0.07124379334 1.113678528 9.28068374 0.2774187053 599 0 0.6357162059 0.4750680449 8.349353428 0.214079692 860 0 0.7160913465 1.507726363 0.3308605516 0.3308601362 367 0 -0.01500591465 1.718298326 0.2816359186 0.2816358186 2314 0 0.6220261447 1.6166776 1.553558452 0.2677852769 801 0 -0.1843449775 1.757589191 1.526816219 0.2327690423 990 0 0.8958901449 1.501259408 2.315617621 0.2517432124 1939 0 0.6538233279 1.508065617 3.868884236 0.3562999137 461 0 0.06758580007 1.727219894 3.820135215 0.271457506 494 0 0.6463125955 1.570041722 2.805268904 0.3021324301 1091 0 0.9479456114 1.523843671 3.210783344 0.205368903 1834 0 0.715438098 1.56899683 5.429103785 0.2755854601 849 0 0.2854086688 1.731445341 4.288598899 0.2451877154 1019 0 0.1356036544 1.691322711 4.998228072 0.3032498116 1806 0 0.8388736188 1.510728932 7.501845527 0.2719917396 274 0 0.1108757067 1.650520822 6.213804981 0.3457591432 1417 0 -0.07511631205 1.713605533 7.167800827 0.2847487861 1556 0 0.3673158542 1.693410792 6.76890005 0.2672095157 7565 0 0.8333120053 1.554603561 8.008049041 0.2361394654 485 0 0.5254077685 1.691972328 8.327509035 0.2283273335 1345 0 0.1632715691 1.712207696 8.683699068 0.2800250971 1496 0 0.1553543133 1.669735333 9.285228225 0.3230527721 2208 0 -0.1371321866 1.785312133 8.304253623 0.2094281869 3306 0 1.595951069 -0.7978022408 0.2157506068 0.2157498533 7284 0 1.54573283 -0.4230991496 1.262676413 0.3974064894 544 0 1.629483167 -0.5824720474 1.904864061 0.2695406902 5600 0 1.611091297 -0.784383753 3.698271599 0.2081083421 3282 0 1.629294224 -0.4871919757 5.269659621 0.2994243323 753 0 1.690927007 -0.291558122 5.815969339 0.2841207953 1296 0 1.507637389 -0.9511879385 4.439045116 0.2173813228 4382 0 1.604969024 -0.3915529166 6.434141096 0.3479578633 1746 0 1.76339995 -0.2643616751 9.014960285 0.2168935749 3181 0 1.584582912 -0.8054500625 9.084311364 0.2224586747 5062 0 1.538948487 -0.1765741587 0.4509535044 0.4509534044 292 0 1.754162235 0.3892541502 0.2031685311 0.2031682611 978 0 1.50001 0.3165907537 2.12929564 0.2117837102 71 0 1.658883082 0.5757522913 1.213839279 0.2440424982 1900 0 1.71884326 0.1581769803 1.439348714 0.2343071607 3324 0 1.614738608 0.6022445828 4.178575816 0.2766076383 862 0 1.62152421 -0.01642397375 3.567316482 0.3783925154 1288 0 1.733435841 0.4667580438 3.083070134 0.2048216753 2006 0 1.629879132 -0.2127847761 2.554630922 0.3562887113 2867 0 1.562505938 0.5502968584 5.386889759 0.3434216472 303 0 1.699371535 0.5489236359 4.659053525 0.2141719312 1000 0 1.766304934 0.01061669268 5.40231513 0.233662869 4189 0 1.50001 0.3165907537 6.701676493 0.4162784934 75 0 1.676925026 0.5205578951 7.576285862 0.2441359784 601 0 1.641922879 0.4499713982 6.014944822 0.2975348899 750 0 1.513601701 0.9274050029 6.507256699 0.2248745107 6377 0 1.50001 0.3165907537 8.98786692 0.4215529091 77 0 1.684064499 0.08847007613 8.001547325 0.2952617333 890 0 1.545624233 0.7825507661 8.482219533 0.267561402 1028 0 1.701980826 -0.1786021796 8.520519605 0.288673552 2276 0 EndParticles BeginConnect 0 EndConnect trunk-2018.02b/examples/packs/cone.mesh000066400000000000000000000401371324306050200177140ustar00rootroot00000000000000 MeshVersionFormatted 1 Dimension 3 Vertices 157 -2 -2 -3 0 -2 2 -3 0 2 2 -3 0 2 -2 -3 0 0 0 -3 0 2 -2 -1.5 0 0 0 -1.5 0 2 2 -1.5 0 -2 2 -1.5 0 -2 -2 -1.5 0 -4 -4 -7 0 0 0 -7 0 4 -4 -7 0 -4 4 -7 0 4 4 -7 0 -2.6131259297492 1.082392200301 -3 0 -2.8284271247462 2.0434764991251E-12 -3 0 -2.6131259297503 -1.0823922002982 -3 0 -1.082392200301 -2.6131259297492 -3 0 -2.0434764991251E-12 -2.8284271247462 -3 0 1.0823922002982 -2.6131259297503 -3 0 2.6131259297492 -1.082392200301 -3 0 2.8284271247462 -2.0434764991251E-12 -3 0 2.6131259297503 1.0823922002982 -3 0 1.082392200301 2.6131259297492 -3 0 2.0434764991251E-12 2.8284271247462 -3 0 -1.0823922002982 2.6131259297503 -3 0 2.6131259297492 -1.082392200301 -1.5 0 2.8284271247462 -2.0434764991251E-12 -1.5 0 2.6131259297503 1.0823922002982 -1.5 0 1.082392200301 2.6131259297492 -1.5 0 2.0434764991251E-12 2.8284271247462 -1.5 0 -1.0823922002982 2.6131259297503 -1.5 0 -2.6131259297492 1.082392200301 -1.5 0 -2.8284271247462 2.0434764991251E-12 -1.5 0 -2.6131259297503 -1.0823922002982 -1.5 0 -1.082392200301 -2.6131259297492 -1.5 0 -2.0434764991251E-12 -2.8284271247462 -1.5 0 1.0823922002982 -2.6131259297503 -1.5 0 -3.1427798335562 -4.7035024096722 -7 0 -2.1647844006021 -5.2262518594984 -7 0 -1.1035975171429 -5.5481593812864 -7 0 -4.0869529982501E-12 -5.6568542494924 -7 0 1.103597517136 -5.5481593812878 -7 0 2.1647844005965 -5.2262518595007 -7 0 3.1427798335537 -4.7035024096739 -7 0 -4.7035024096722 3.1427798335562 -7 0 -5.2262518594984 2.1647844006021 -7 0 -5.5481593812864 1.1035975171429 -7 0 -5.6568542494924 4.0869529982501E-12 -7 0 -5.5481593812878 -1.103597517136 -7 0 -5.2262518595007 -2.1647844005965 -7 0 -4.7035024096739 -3.1427798335537 -7 0 3.1427798335562 4.7035024096722 -7 0 2.1647844006021 5.2262518594984 -7 0 1.1035975171429 5.5481593812864 -7 0 4.0869529982501E-12 5.6568542494924 -7 0 -1.103597517136 5.5481593812878 -7 0 -2.1647844005965 5.2262518595007 -7 0 -3.1427798335537 4.7035024096739 -7 0 4.7035024096722 -3.1427798335562 -7 0 5.2262518594984 -2.1647844006021 -7 0 5.5481593812864 -1.1035975171429 -7 0 5.6568542494924 -4.0869529982501E-12 -7 0 5.5481593812878 1.103597517136 -7 0 5.2262518595007 2.1647844005965 -7 0 4.7035024096739 3.1427798335537 -7 0 -2.5000000000014 -2.5000000000014 -4.0000000000028 0 -3.0000000000009 -3.0000000000009 -5.0000000000019 0 -3.5000000000005 -3.5000000000005 -6.0000000000009 0 2.5000000000014 -2.5000000000014 -4.0000000000028 0 3.0000000000009 -3.0000000000009 -5.0000000000019 0 3.5000000000005 -3.5000000000005 -6.0000000000009 0 2.5000000000014 2.5000000000014 -4.0000000000028 0 3.0000000000009 3.0000000000009 -5.0000000000019 0 3.5000000000005 3.5000000000005 -6.0000000000009 0 -2.5000000000014 2.5000000000014 -4.0000000000028 0 -3.0000000000009 3.0000000000009 -5.0000000000019 0 -3.5000000000005 3.5000000000005 -6.0000000000009 0 0.55179875857147 2.7740796906432 -2.25 0 -0.55179875856798 2.7740796906439 -2.25 0 2.7740796906432 -0.55179875857147 -2.25 0 2.7740796906439 0.55179875856798 -2.25 0 -0.55179875857147 -2.7740796906432 -2.25 0 0.55179875856798 -2.7740796906439 -2.25 0 -2.7740796906432 0.55179875857147 -2.25 0 -2.7740796906439 -0.55179875856798 -2.25 0 5.0479792461586 -0.35505567377516 -6.1565576922327 0 3.3234612474655 1.9381178063516 -4.4408997959349 0 3.6142702934017 -1.5190194235244 -4.5444332014752 0 4.5063483206219 -2.543437469799 -6.3179572763921 0 4.3733777215095 0.24526028715351 -5.1946081883281 0 3.5122070514485 0.67793138703452 -4.058693376312 0 3.2470611761867 -0.7668862216033 -3.7183727616168 0 2.8565535692464 1.6984461402266 -3.6999186344499 0 2.9161694821589 -1.8409712583355 -3.8771343271828 0 4.083484027652 -2.2155446444777 -5.5701567524319 0 5.0203546348214 1.0205678755183 -6.2450699717688 0 3.8864344298386 -0.40220012269193 -4.5256017801011 0 4.8451260122149 -1.579026917017 -6.2067429646 0 4.1596393420579 1.4493085375803 -5.2294614041823 0 4.3919803804835 -0.98778292819349 -5.3663500965282 0 3.7585664236253 2.5020208457379 -5.3854412335108 0 4.4762900020786 2.3401710969288 -6.1433287682438 0 -0.28924498858354 -4.9808623675674 -6.0558702635564 0 1.9381178063516 -3.3234612474655 -4.4408997959349 0 -1.5516673232336 -3.3963609896359 -4.2807081824148 0 -2.2778430668748 -4.5063100607913 -6.1407841868101 0 0.24526028715351 -4.3733777215095 -5.1946081883281 0 0.67793138703452 -3.5122070514485 -4.058693376312 0 -0.7668862216033 -3.2470611761867 -3.7183727616168 0 1.6984461402266 -2.8565535692464 -3.6999186344499 0 -1.6296458263124 -2.8148550524294 -3.5998161888069 0 -1.3182141950721 -4.4895155980188 -5.6171654005236 0 -2.2716953470806 -3.6866203182072 -5.1240132463224 0 1.0272828673387 -5.0086026306537 -6.2306860533865 0 -0.53284104597107 -3.8941623987227 -4.5584926676028 0 -1.4394493797433 -5.0033121153324 -6.362763970178 0 1.4499599704998 -4.1576170626795 -5.2270640844519 0 -1.2207841346155 -4.0470784277258 -4.9781531769423 0 -0.6016909663055 -4.3973650493818 -5.2767589401631 0 2.5020515688442 -3.7581386810821 -5.3849617695648 0 2.340790036915 -4.4736706712761 -6.1404519845673 0 -5.0479792461586 0.35505567377516 -6.1565576922327 0 -3.3234612474655 -1.9381178063516 -4.4408997959349 0 -3.6142702934017 1.5190194235244 -4.5444332014752 0 -4.5063483206219 2.543437469799 -6.3179572763921 0 -4.3733777215095 -0.24526028715351 -5.1946081883281 0 -3.5122070514485 -0.67793138703452 -4.058693376312 0 -3.2470611761867 0.7668862216033 -3.7183727616168 0 -2.8565535692464 -1.6984461402266 -3.6999186344499 0 -2.9161694821589 1.8409712583355 -3.8771343271828 0 -4.083484027652 2.2155446444777 -5.5701567524319 0 -5.0203546348214 -1.0205678755183 -6.2450699717688 0 -3.8864344298386 0.40220012269193 -4.5256017801011 0 -4.8451260122149 1.579026917017 -6.2067429646 0 -4.1596393420579 -1.4493085375803 -5.2294614041823 0 -4.3919803804835 0.98778292819349 -5.3663500965282 0 -3.7585664236253 -2.5020208457379 -5.3854412335108 0 -4.4762900020786 -2.3401710969288 -6.1433287682438 0 0.35505567377516 5.0479792461586 -6.1565576922327 0 -1.9381178063516 3.3234612474655 -4.4408997959349 0 1.5190194235244 3.6142702934017 -4.5444332014752 0 2.543437469799 4.5063483206219 -6.3179572763921 0 -0.24526028715351 4.3733777215095 -5.1946081883281 0 -0.67793138703452 3.5122070514485 -4.058693376312 0 0.7668862216033 3.2470611761867 -3.7183727616168 0 -1.6984461402266 2.8565535692464 -3.6999186344499 0 1.8409712583355 2.9161694821589 -3.8771343271828 0 2.2155446444777 4.083484027652 -5.5701567524319 0 -1.0205678755183 5.0203546348214 -6.2450699717688 0 0.40220012269193 3.8864344298386 -4.5256017801011 0 1.579026917017 4.8451260122149 -6.2067429646 0 -1.4493085375803 4.1596393420579 -5.2294614041823 0 0.98778292819349 4.3919803804835 -5.3663500965282 0 -2.5020208457379 3.7585664236253 -5.3854412335108 0 -2.3401710969288 4.4762900020786 -6.1433287682438 0 Triangles 260 31 80 25 22 26 80 32 22 80 26 25 22 80 31 32 22 81 27 26 22 81 32 33 22 32 81 26 22 27 81 33 22 31 3 8 22 3 31 25 22 27 9 2 22 9 27 33 22 28 82 22 24 23 82 29 24 82 23 22 24 82 28 29 24 30 83 29 24 23 83 24 24 83 23 29 24 83 30 24 24 4 6 22 24 6 28 22 24 24 8 3 24 8 24 30 24 84 20 19 26 84 37 38 26 37 84 19 26 20 84 38 26 85 21 20 26 85 38 39 26 38 85 20 26 21 85 39 26 10 19 1 26 19 10 37 26 4 21 39 26 6 4 39 26 34 86 16 28 17 86 35 28 86 17 16 28 86 34 35 28 36 87 35 28 17 87 18 28 87 17 35 28 87 36 18 28 9 16 2 28 16 9 34 28 10 18 36 28 18 10 1 28 91 62 61 30 93 23 24 30 23 94 22 30 93 94 23 30 61 13 73 30 73 91 61 30 95 24 3 30 95 74 89 30 74 95 3 30 96 71 4 30 22 96 4 30 73 97 91 30 72 97 73 30 64 98 65 30 94 99 90 30 64 88 98 30 92 99 93 30 94 93 99 30 75 89 74 30 98 88 92 30 66 65 98 30 88 64 63 30 63 100 88 30 89 101 93 30 101 92 93 30 102 92 88 30 92 102 99 30 94 96 22 30 94 90 96 30 63 62 100 30 62 91 100 30 103 89 75 30 89 103 101 30 66 104 67 30 95 93 24 30 89 93 95 30 98 104 66 30 92 101 98 30 76 15 67 30 104 76 67 30 90 102 97 30 102 90 99 30 76 103 75 30 103 76 104 30 96 72 71 30 97 72 90 30 96 90 72 30 100 102 88 30 97 100 91 30 102 100 97 30 101 104 98 30 103 104 101 30 108 41 40 32 110 20 21 32 20 111 19 32 110 111 20 32 40 11 70 32 70 108 40 32 112 21 4 32 112 71 106 32 71 112 4 32 19 113 1 32 68 113 107 32 113 68 1 32 70 115 108 32 69 115 70 32 115 69 68 32 107 115 68 32 115 114 108 32 43 116 44 32 111 117 107 32 43 105 116 32 109 117 110 32 111 110 117 32 72 106 71 32 116 105 109 32 45 44 116 32 105 43 42 32 42 118 105 32 114 118 108 32 118 114 105 32 106 119 110 32 119 109 110 32 107 120 115 32 120 114 115 32 120 107 117 32 121 109 105 32 114 121 105 32 109 121 117 32 111 113 19 32 111 107 113 32 120 121 114 32 120 117 121 32 42 41 118 32 41 108 118 32 122 106 72 32 106 122 119 32 45 123 46 32 112 110 21 32 106 110 112 32 116 123 45 32 109 119 116 32 73 13 46 32 123 73 46 32 73 122 72 32 122 73 123 32 119 123 116 32 122 123 119 32 127 48 47 34 129 17 18 34 17 130 16 34 129 130 17 34 47 14 79 34 79 127 47 34 68 131 1 34 131 18 1 34 131 68 125 34 132 77 2 34 16 132 2 34 79 133 127 34 78 133 79 34 50 134 51 34 130 135 126 34 50 124 134 34 128 135 129 34 130 129 135 34 69 125 68 34 134 124 128 34 52 51 134 34 124 50 49 34 49 136 124 34 125 137 129 34 137 128 129 34 138 128 124 34 128 138 135 34 130 132 16 34 130 126 132 34 49 48 136 34 48 127 136 34 139 125 69 34 125 139 137 34 52 140 53 34 131 129 18 34 125 129 131 34 134 140 52 34 128 137 134 34 70 11 53 34 140 70 53 34 126 138 133 34 138 126 135 34 70 139 69 34 139 70 140 34 132 78 77 34 133 78 126 34 132 126 78 34 136 138 124 34 133 136 127 34 138 136 133 34 137 140 134 34 139 140 137 34 144 55 54 36 146 26 27 36 26 147 25 36 146 147 26 36 54 15 76 36 76 144 54 36 77 148 2 36 148 27 2 36 148 77 142 36 25 149 3 36 149 74 3 36 76 150 144 36 75 150 76 36 57 151 58 36 147 152 143 36 57 141 151 36 145 152 146 36 147 146 152 36 78 142 77 36 151 141 145 36 59 58 151 36 141 57 56 36 56 153 141 36 142 154 146 36 154 145 146 36 155 145 141 36 145 155 152 36 147 149 25 36 147 143 149 36 56 55 153 36 55 144 153 36 156 142 78 36 142 156 154 36 59 157 60 36 148 146 27 36 142 146 148 36 151 157 59 36 145 154 151 36 79 14 60 36 157 79 60 36 143 155 150 36 155 143 152 36 79 156 78 36 156 79 157 36 149 75 74 36 150 75 143 36 149 143 75 36 153 155 141 36 150 153 144 36 155 153 150 36 154 157 151 36 156 157 154 36 End trunk-2018.02b/examples/packs/foo.spheres000066400000000000000000000002541324306050200202640ustar00rootroot00000000000000#This is the file with the geometry, which can be imported into the simulation script -1.69 -0.880052 3.53564 1.15 -1.69 -3.880052 4.0899 0.55 -1.69 -5.741487 3.25851 0.95 trunk-2018.02b/examples/packs/packs.geo000066400000000000000000000017461324306050200177120ustar00rootroot00000000000000Point(1) = {-2, -2, -3, 1.0}; Point(2) = {-2, 2, -3, 1.0}; Point(3) = {2, 2, -3, 1.0}; Point(4) = {2, -2, -3, 1.0}; Point(5) = {0, 0, -3, 1.0}; Circle(1) = {2, 5, 1}; Circle(2) = {1, 5, 4}; Circle(3) = {4, 5, 3}; Circle(4) = {3, 5, 2}; Translate {0, 0, 1.5} { Duplicata { Line{3, 4, 1, 2}; } } Dilate {{0, 0, 1}, 2} { Duplicata { Line{2, 1, 4, 3}; } } Line(13) = {1, 18}; Line(14) = {6, 4}; Line(15) = {2, 13}; Line(16) = {3, 8}; Line(17) = {1, 19}; Line(18) = {4, 21}; Line(19) = {3, 29}; Line(20) = {2, 24}; Line Loop(21) = {6, -15, -4, 16}; Ruled Surface(22) = {21}; Line Loop(23) = {5, -16, -3, -14}; Ruled Surface(24) = {23}; Line Loop(25) = {8, 14, -2, 13}; Ruled Surface(26) = {25}; Line Loop(27) = {7, -13, -1, 15}; Ruled Surface(28) = {27}; Line Loop(29) = {3, 19, -12, -18}; Ruled Surface(30) = {29}; Line Loop(31) = {18, -9, -17, 2}; Ruled Surface(32) = {31}; Line Loop(33) = {17, -10, -20, 1}; Ruled Surface(34) = {33}; Line Loop(35) = {20, -11, -19, 4}; Ruled Surface(36) = {35}; trunk-2018.02b/examples/packs/packs.py000066400000000000000000000176771324306050200176020ustar00rootroot00000000000000#!/usr/bin/python # -*- coding: utf-8 -*- from yade import pack,ymport,export,geom,bodiesHandling """ This script demonstrates how to use 2 components of creating packings: 1. packing generators pack.regularHexa, pack.regularOrtho etc. generate vertices and filter them using predicates. (Note that this will be enhanced to irregular packings in the future) 2. predicates are functors returning True/False for points that are given by the packing generator. Their names are mostly self-explanatory, see their docstrings for meaning of their arguments. Predicates can be combined using set arithmetics to get their Intersection (p1 & p2), union (p1 | p2), difference (p1 - p2) and symmetric difference (XOR, p1 ^ p2). This is demontrated on the head (which has sphere taken off at the back and also a notch) and the body (with cylidrical hole inside). """ rad,gap=.15,.02 #Add material O.materials.append(FrictMat(young=10e9,poisson=.25,frictionAngle=0.5,density=1e3)) #Parameters, which will be passed into spheres and facets creators kw={'material':0} kwBoxes={'color':[1,0,0],'wire':False,'dynamic':False,'material':0} kwMeshes={'color':[1,1,0],'wire':True,'dynamic':False,'material':0} O.bodies.append( pack.regularHexa( (pack.inSphere((0,0,4),2)-pack.inSphere((0,-2,5),2)) & pack.notInNotch(centerPoint=(0,0,4),edge=(0,1,0),normal=(-1,1,-1),aperture=.2) ,radius=rad,gap=gap,color=(0,1,0),material=0) # head +[sphere((.8,1.9,5),radius=.2,color=(.6,.6,.6),material=0),sphere((-.8,1.9,5),radius=.2,color=(.6,.6,.6),material=0),sphere((0,2.4,4),radius=.4,color=(1,0,0),material=0)] # eyes and nose +pack.regularHexa(pack.inCylinder((-1,2.2,3.3),(1,2.2,3.3),2*rad),radius=rad,gap=gap/3,color=(0.929,0.412,0.412),material=0) #mouth ) groundId=O.bodies.append(facet([(12,0,-6),(0,12,-6,),(-12,-12,-6)],dynamic=False)) # ground for part in [ pack.regularHexa ( pack.inAlignedBox((-2,-2,-2),(2,2,2))-pack.inCylinder((0,-2,0),(0,2,0),1), radius=1.5*rad,gap=2*gap,color=(1,0,1),**kw), # body, pack.regularOrtho(pack.inEllipsoid((-1,0,-4),(1,1,2)),radius=rad,gap=0,color=(0,1,1),**kw), # left leg pack.regularHexa (pack.inCylinder((+1,1,-2.5),(0,3,-5),1),radius=rad,gap=gap,color=(0,1,1),**kw), # right leg pack.regularHexa (pack.inHyperboloid((+2,0,1),(+6,0,0),1,.5),radius=rad,gap=gap,color=(0,0,1),**kw), # right hand pack.regularOrtho(pack.inCylinder((-2,0,2),(-5,0,4),1),radius=rad,gap=gap,color=(0,0,1),**kw) # left hand ]: O.bodies.appendClumped(part) # Example of geom.facetBox usage oriBody = Quaternion(Vector3(0,0,1),(pi/3)) O.bodies.append(geom.facetBox((12,0,-6+0.9),(1,0.7,0.9),oriBody,**kwBoxes)) oriBody = Quaternion(Vector3(0,0,1),(pi/2)) O.bodies.append(geom.facetBox((0,12,-6+0.9),(1,0.7,0.9),oriBody,**kwBoxes)) oriBody = Quaternion(Vector3(0,0,1),(pi)) O.bodies.append(geom.facetBox((-12,-12,-6+0.9),(1,0.7,0.9),oriBody,**kwBoxes)) # Example of geom.facetParallelepiped usage oriBody = Quaternion(Vector3(0,0,1),(pi/3)) O.bodies.append(geom.facetParallelepiped(center=Vector3(12,0,-6+2.7),extents=Vector3(1,0.7,0.9),height=0.5, orientation=oriBody,**kwBoxes)) oriBody = Quaternion(Vector3(0,0,1),(pi/2)) O.bodies.append(geom.facetParallelepiped(center=Vector3(0,12,-6+2.7),extents=Vector3(1,0.7,0.9),height=0.5, orientation=oriBody,**kwBoxes)) oriBody = Quaternion(Vector3(0,0,1),(pi)) O.bodies.append(geom.facetParallelepiped(center=Vector3(-12,-12,-6+2.7),extents=Vector3(1,0.7,0.9),height=0.5, orientation=oriBody,**kwBoxes)) # Example of geom.facetCylinder, facetHelix and RotationEngine usage example oriBody = Quaternion(Vector3(1,0,0),(pi/2.0)) rotateIDs=O.bodies.append(geom.facetHelix((-7.0,-6.0,-5.0),radiusOuter=2.0,radiusInner=0.1,pitch=2.0,orientation=oriBody,segmentsNumber=50,angleRange=[pi*8.0,0],**kwBoxes)) O.bodies.append(geom.facetCylinder((-7.0,-12.0,-5.0),radius=2.0,height=7.0,orientation=oriBody,segmentsNumber=10,wallMask=4,**kwMeshes)) O.bodies.append(geom.facetCylinder((-7.0,-7.0,-5.0),radius=2.0,height=4.0,segmentsNumber=10,wallMask=4,angleRange=[-pi*0.2,pi*1.2],**kwMeshes)) oriBody = Quaternion(Vector3(0,0,1),(pi/2)) O.bodies.append(ymport.gmsh('cone.mesh',orientation=oriBody,**kwMeshes))#generates facets from the mesh file SpheresID=[] oriBody = Quaternion(Vector3(0,0,1),(pi/2)) SpheresID+=O.bodies.append(ymport.gengeoFile('LSMGenGeo.geo',shift=Vector3(-7.0,-7.0,0.0),scale=1.0,orientation=oriBody,color=(1,0,1),**kw)) #Demonstration of spheresPackDimensions function. The "Edge" particles are colored with blue color geometryParameters = bodiesHandling.spheresPackDimensions(SpheresID) for v in [geometryParameters['minId'],geometryParameters['maxId']]: for i in v: O.bodies[int(i)].shape.color = Vector3(0,0,1) #Example of bodiesHandling.spheresModify() hat=O.bodies.append(pack.regularOrtho(pack.inCylinder((0,0,6),(0,0,7),20*rad),radius=0.2,gap=0,color=(1,0,0))) # hat oriBody = Quaternion(Vector3(0,1,0),(pi/8)) hat_upper=O.bodies.append(bodiesHandling.spheresModify(hat,shift=(0.0,0.0,1.4),scale=0.7,orientation=oriBody,copy=True)) #Duplicate the "heart", shifting, scaling and rotating it #change the color of upper part of the hat for hatTmp in hat_upper: O.bodies[hatTmp].shape.color=(0.9,0.5,0.59) #facetBunker Demonstration #Demonstration of HarmonicMotionEngine vibrationPlate = O.bodies.append(geom.facetBunker((-7.0,-7.0,-3.0),dBunker=geometryParameters['extends'][0]*1.1,dOutput=3.0,hBunker=11.0,hOutput=1.5,hPipe=0.8,wallMask=5,segmentsNumber=20,**kwMeshes)) # spheresToFile saves coordinates and radii of all spheres of the simulation into the text file, works but disabled. Please, uncomment it, if you need #print "Saved into the OutFile " + str (export.text("OutFile")) + " spheres"; # spheresFromFile function imports coordinates and radii of all spheres of the simulation into the text file O.bodies.append(ymport.text('foo.spheres',shift=Vector3(6.0,6.0,-2.9),scale=0.7,color=(1,1,1),**kw)) #Demonstration of HarmonicRotationEngine O.bodies.append(pack.regularHexa(pack.inSphere((-15,5,-5),1.5),radius=rad*2.0,gap=rad/3.0,color=(0.5,0.5,0.1),material=0)) O.bodies.append(geom.facetBox((-15,5,-5),(2,2,2),wallMask=15,**kwMeshes)) vibrationRotationPlate = O.bodies.append(geom.facetBox((-15,5,-5),(2,2,2),wallMask=16,**kwBoxes)) O.bodies.append(wall((0,0,-10),axis=2)) O.engines=[ #SubdomainBalancer(colorize=True,initRun=True,iterPeriod=100), ForceResetter(), InsertionSortCollider([Bo1_Sphere_Aabb(),Bo1_Facet_Aabb(),Bo1_Wall_Aabb()],label='collider'), InteractionLoop( [Ig2_Sphere_Sphere_ScGeom(),Ig2_Facet_Sphere_ScGeom(),Ig2_Wall_Sphere_ScGeom()], [Ip2_FrictMat_FrictMat_FrictPhys()], [Law2_ScGeom_FrictPhys_CundallStrack()], ), NewtonIntegrator(damping=.1,exactAsphericalRot=True,gravity=(1e-2,1e-2,-1000)), RotationEngine( ids=rotateIDs, angularVelocity=100.0, rotationAxis=[0,-1,0], rotateAroundZero=1, zeroPoint=[-7.0,-6.0,-5.0]), HarmonicMotionEngine(A=[0,0,0.5], f=[0,0,20.0], fi = [0.0,0.0,pi], ids = vibrationPlate), HarmonicRotationEngine(A=0.2, f=20.0, fi = pi, rotationAxis=[1.0,0.0,0.0], rotateAroundZero = True, zeroPoint = [-15.0,3.0,-7.0], ids = vibrationRotationPlate), BoxFactory(maxParticles=300, extents=(1.0,1.0,1.0),center=(0.0,12.0,0.0),vMin=200.0,vMax=250.0, PSDsizes=(0.1, 0.2, 0.3, 0.5, 0.7), PSDcum=(0.1, 0.5, 0.8, 1.0), PSDcalculateMass=True, exactDiam=False, vAngle=pi/3.0,massFlowRate=50000.0,normal=(0.0,0.0,1.0),label='factory',mask=7,silent=True,stopIfFailed=False) ] ''' Boxfactory is an example of usage SpheresFactory. Produces PSD-dispersion: Size: Mass, % Cumulative Cum. mass mass,% 0.5-0.7 20% 100% 1.0 0.3-0.5 30% 80% 0.8 0.2-0.3 40% 50% 0.5 0.1-0.2 10% 10% 0.1 ''' # we don't care about physical accuracy here, (over)critical step is fine as long as the simulation doesn't explode O.dt=PWaveTimeStep() O.run(1,True) try: from yade import qt qt.Controller() qt.View() except ImportError: pass O.saveTmp() O.timingEnabled=True #O.run(10000,True) #from yade import timing #timing.stats() #quit() trunk-2018.02b/examples/pfacet/000077500000000000000000000000001324306050200162465ustar00rootroot00000000000000trunk-2018.02b/examples/pfacet/box.gts000066400000000000000000000004731324306050200175610ustar00rootroot000000000000008 18 12 GtsSurface GtsFace GtsEdge GtsVertex -0.5 0.5 -0.5 -0.5 0.5 0.5 -0.5 -0.5 0.5 0.5 -0.5 -0.5 0.5 0.5 -0.5 0.5 0.5 0.5 0.5 -0.5 0.5 -0.5 -0.5 -0.5 1 2 3 1 2 3 4 5 1 4 5 1 5 6 5 2 6 2 7 5 7 4 4 8 4 3 3 8 7 6 3 7 8 1 2 7 1 2 3 4 5 6 7 8 9 4 10 11 12 13 14 15 10 7 16 13 11 17 5 12 1 8 6 16 18 3 14 2 17 9 18 15 trunk-2018.02b/examples/pfacet/gts-pfacet.py000066400000000000000000000047431324306050200206650ustar00rootroot00000000000000from yade import qt from yade.gridpfacet import * import gts, os.path, locale locale.setlocale(locale.LC_ALL, 'en_US.UTF-8') # Note: gts is locale-dependend. If, for example, german locale is used, gts.read()-function does not import floats normally ''' if you get "Error: unsupported locale setting" -> type as root: "dpkg-reconfigure locales" -> choose "en_US.UTF-8" (press space to choose) ''' ################ ### ENGINES ### ################ O.engines=[ ForceResetter(), InsertionSortCollider([ Bo1_Wall_Aabb(), Bo1_PFacet_Aabb(), ],sortThenCollide=True), InteractionLoop( [ Ig2_GridNode_GridNode_GridNodeGeom6D(), Ig2_Wall_PFacet_ScGeom(),Ig2_Wall_Sphere_ScGeom() ], [ Ip2_CohFrictMat_CohFrictMat_CohFrictPhys(setCohesionNow=True,setCohesionOnNewContacts=True), Ip2_FrictMat_FrictMat_FrictPhys()], [ Law2_ScGeom6D_CohFrictPhys_CohesionMoment(), Law2_ScGeom_FrictPhys_CundallStrack(), Law2_ScGridCoGeom_FrictPhys_CundallStrack(), Law2_GridCoGridCoGeom_FrictPhys_CundallStrack() ]), GlobalStiffnessTimeStepper(timestepSafetyCoefficient=0.5,label='ts'), NewtonIntegrator(gravity=(0,-9.81,0),damping=0.1,label='newton') ] ################# ### MATERIAL ### ################# O.materials.append(CohFrictMat(young=1e8,poisson=0.3,density=2650,frictionAngle=radians(20),normalCohesion=1e100,shearCohesion=1e100,momentRotationLaw=True,label='gridNodeMat')) O.materials.append(FrictMat(young=1e8,poisson=0.3,density=2650,frictionAngle=radians(20),label='pFacetMat')) ################### ### IMPORT MESH ### ################### radius=1e-02 wire=False fixed=False z=-1.2 color=[0,0,1] nodesIds0,cylIds0,pfIds0 = gtsPFacet('octahedron.gts',shift=(0,0,0),scale=1.,radius=radius,wire=wire,fixed=fixed,materialNodes='gridNodeMat',material='pFacetMat',color=color) nodesIds1,cylIds1,pfIds1 = gtsPFacet('box.gts',shift=(3.,0.,0.),scale=2,radius=radius,wire=wire,fixed=fixed,materialNodes='gridNodeMat',material='pFacetMat',color=color) nodesIds2,cylIds2,pfIds2 = gtsPFacet('sphere.gts',shift=(6.,0.,0.),scale=1.0,radius=radius,wire=wire,fixed=fixed,materialNodes='gridNodeMat',material='pFacetMat',color=color) ##################### ##### Wall ### ##################### O.bodies.append(utils.wall(position=z,sense=0, axis=1,color=Vector3(1,0,0),material='pFacetMat')) ########## ## VIEW ## ########## qt.Controller() qtv = qt.View() qtr = qt.Renderer() qtr.light2=True qtr.lightPos=Vector3(1200,1500,500) qtr.bgColor=[1,1,1] qtv.ortho=True O.saveTmp() trunk-2018.02b/examples/pfacet/mesh-pfacet.py000066400000000000000000000064771324306050200210320ustar00rootroot00000000000000# -*- coding: utf-8 from yade import qt,plot from yade.gridpfacet import * ################## ### PARAMETERS ### ################## phi=20. E=3.*1e8 color=[255./255.,102./255.,0./255.] r=0.005 # position of imported mesh xpafet=0.22 ypafet=0.05 ################ ### ENGINES ### ################ O.engines=[ ForceResetter(), InsertionSortCollider([ Bo1_PFacet_Aabb(), ]), InteractionLoop([ Ig2_GridNode_GridNode_GridNodeGeom6D(), Ig2_GridConnection_GridConnection_GridCoGridCoGeom(), Ig2_Sphere_PFacet_ScGridCoGeom(), Ig2_PFacet_PFacet_ScGeom(), ], [ Ip2_CohFrictMat_CohFrictMat_CohFrictPhys(setCohesionNow=True,setCohesionOnNewContacts=True), Ip2_FrictMat_FrictMat_FrictPhys() ], [ Law2_ScGeom6D_CohFrictPhys_CohesionMoment(), Law2_ScGeom_FrictPhys_CundallStrack(), Law2_ScGridCoGeom_FrictPhys_CundallStrack(), Law2_GridCoGridCoGeom_FrictPhys_CundallStrack() ] ), GlobalStiffnessTimeStepper(timestepSafetyCoefficient=0.5,label='ts'), NewtonIntegrator(gravity=(0,-9.81,0),damping=0.0,label='newton'), PyRunner(iterPeriod=200,command='history()'), ] ################ ### MATERIAL ### ################ O.materials.append( CohFrictMat( young=E,poisson=0.3,density=2650,frictionAngle=radians(phi),normalCohesion=3e100,shearCohesion=3e100,momentRotationLaw=True,label='gridNodeMat' ) ) # material to create the gridConnections O.materials.append( FrictMat( young=E,poisson=0.3,density=2650,frictionAngle=radians(phi),label='pFacetMat' ) ) # material for general interactions ######################################## ### GENERATE THE WALL AND THE SPHERE ### ######################################## # FIXED WALL O.bodies.append( gridNode([-0.25,-0.22,-.25],r,wire=False,fixed=True,material='gridNodeMat',color=color) ) O.bodies.append( gridNode([.5,0.,-.25],r,wire=False,fixed=True,material='gridNodeMat',color=color) ) O.bodies.append( gridNode([-.25,-0.22,.25],r,wire=False,fixed=True,material='gridNodeMat',color=color) ) O.bodies.append( gridNode([.5,0.,.25],r,wire=False,fixed=True,material='gridNodeMat',color=color) ) O.bodies.append( gridConnection(0,1,r,color=color,material='gridNodeMat') ) O.bodies.append( gridConnection(2,3,r,color=color,material='gridNodeMat') ) O.bodies.append( gridConnection(2,1,r,color=color,material='gridNodeMat') ) O.bodies.append( gridConnection(2,0,r,color=color,material='gridNodeMat') ) O.bodies.append( gridConnection(3,1,r,color=color,material='gridNodeMat') ) O.bodies.append( pfacet(2,1,0,wire=False,material='pFacetMat',color=color) ) O.bodies.append( pfacet(2,3,1,wire=False,material='pFacetMat',color=color) ) # IMPORT MESH oriBody = Quaternion(Vector3(0,0,1),pi/2.) nodesIds,cylIds,pfIds = gmshPFacet( meshfile='sphere.mesh', shift=Vector3(xpafet,ypafet,0.), orientation=oriBody, radius=r, wire=False, fixed=False, materialNodes='gridNodeMat', material='pFacetMat', color=[1,0,0] ) ############ ### PLOT ### ############ def history(): xyz=[] for k in [0,1,2]: ksum=0 for i in nodesIds: ksum+=O.bodies[i].state.pos[k] xyz.append(ksum/len(nodesIds)) # take average value as reference plot.addData(i=O.iter,t=O.time,x=xyz[0],y=xyz[1],z=xyz[2]) plot.plots={'x':'y'} plot.plot() ########## ## VIEW ## ########## qt.Controller() qtv = qt.View() qtr = qt.Renderer() qtr.light2=True qtr.lightPos=Vector3(1200,1500,500) qtr.bgColor=[1,1,1] O.saveTmp() trunk-2018.02b/examples/pfacet/octahedron.gts000066400000000000000000000464161324306050200211260ustar00rootroot00000000000000258 768 512 GtsSurface GtsFace GtsEdge GtsVertex 0 0.625 0.625 0.1909179688 0.517578125 0.6655273438 0 0.4765625 0.7685546875 -0.3125 -0.890625 0 -0.1826171875 -0.91015625 0.1826171875 -0.3530273438 -0.8041992188 0.18359375 0.1826171875 0.91015625 0.1826171875 0 0.890625 0.3125 0.18359375 0.8041992188 0.3530273438 0 0.1494140625 0.97265625 -0.1826171875 0.1826171875 0.91015625 0 0.3125 0.890625 -0.3671875 -0.3671875 0.671875 -0.18359375 -0.3530273438 0.8041992188 -0.3530273438 -0.18359375 0.8041992188 0.625 0 0.625 0.7685546875 0 0.4765625 0.6655273438 -0.1909179688 0.517578125 -0.97265625 0 -0.1494140625 -1 0 0 -0.97265625 -0.1494140625 0 0 -0.4765625 -0.7685546875 -0.1909179688 -0.517578125 -0.6655273438 0 -0.625 -0.625 0 0.97265625 0.1494140625 -0.1494140625 0.97265625 0 0 1 0 -0.18359375 0.8041992188 0.3530273438 -0.1826171875 0.91015625 0.1826171875 -0.373046875 0.529296875 -0.529296875 -0.3671875 0.3671875 -0.671875 -0.529296875 0.373046875 -0.529296875 -0.890625 -0.3125 0 -0.91015625 -0.1826171875 0.1826171875 0.625 0.625 0 0.517578125 0.6655273438 0.1909179688 0.6655273438 0.517578125 0.1909179688 -0.8041992188 -0.18359375 -0.3530273438 -0.671875 -0.3671875 -0.3671875 -0.8041992188 -0.3530273438 -0.18359375 0.1909179688 0.6655273438 -0.517578125 0.373046875 0.529296875 -0.529296875 0.3671875 0.671875 -0.3671875 0.1909179688 -0.517578125 -0.6655273438 0.373046875 -0.529296875 -0.529296875 0.3671875 -0.3671875 -0.671875 0 -0.97265625 0.1494140625 0 -0.890625 0.3125 -0.373046875 -0.529296875 0.529296875 -0.529296875 -0.373046875 0.529296875 0.517578125 -0.1909179688 -0.6655273438 0.625 0 -0.625 0.4765625 0 -0.7685546875 0.97265625 0 -0.1494140625 1 0 0 0.97265625 0.1494140625 0 0.517578125 0.6655273438 -0.1909179688 0.3530273438 0.8041992188 -0.18359375 0.4765625 0.7685546875 0 -0.1909179688 0.517578125 0.6655273438 -0.1909179688 0.6655273438 0.517578125 -0.4765625 -0.7685546875 0 -0.517578125 -0.6655273438 0.1909179688 0 0.7685546875 0.4765625 0.1909179688 0.6655273438 0.517578125 0.3671875 0.3671875 -0.671875 0.1909179688 0.517578125 -0.6655273438 0.373046875 0.529296875 0.529296875 0.3671875 0.3671875 0.671875 0 0.890625 -0.3125 0.1826171875 0.91015625 -0.1826171875 0 0.97265625 -0.1494140625 0.91015625 0.1826171875 0.1826171875 0.97265625 0 0.1494140625 0.890625 0 0.3125 -0.18359375 -0.3530273438 -0.8041992188 -0.3530273438 -0.18359375 -0.8041992188 -0.3671875 -0.3671875 -0.671875 -0.8041992188 0.18359375 -0.3530273438 -0.91015625 0.1826171875 -0.1826171875 -0.890625 0 -0.3125 -0.4765625 0 0.7685546875 -0.3530273438 0.18359375 0.8041992188 -0.517578125 0.1909179688 0.6655273438 -0.890625 0.3125 0 -0.8041992188 0.3530273438 -0.18359375 0.1494140625 0.97265625 0 0.529296875 -0.373046875 0.529296875 0.671875 -0.3671875 0.3671875 0.8041992188 0.3530273438 -0.18359375 0.91015625 0.1826171875 -0.1826171875 0.890625 0.3125 0 0 -0.3125 0.890625 -0.1826171875 -0.1826171875 0.91015625 0 -0.1494140625 0.97265625 -0.529296875 0.529296875 0.373046875 -0.529296875 0.373046875 0.529296875 -0.671875 0.3671875 0.3671875 -0.18359375 0.3530273438 0.8041992188 -0.3671875 0.3671875 0.671875 -0.91015625 -0.1826171875 -0.1826171875 0.8041992188 -0.18359375 0.3530273438 -0.529296875 0.529296875 -0.373046875 -0.671875 0.3671875 -0.3671875 0.18359375 -0.3530273438 0.8041992188 0 -0.4765625 0.7685546875 0.529296875 -0.373046875 -0.529296875 0.1826171875 -0.91015625 0.1826171875 0.8041992188 0.18359375 0.3530273438 0.6655273438 0.1909179688 0.517578125 -0.18359375 -0.8041992188 0.3530273438 -0.3671875 -0.671875 0.3671875 -0.1909179688 -0.6655273438 0.517578125 -0.1909179688 0.6655273438 -0.517578125 0 0.625 -0.625 -0.1909179688 0.517578125 -0.6655273438 -0.373046875 0.529296875 0.529296875 -0.1909179688 -0.517578125 0.6655273438 -0.529296875 -0.529296875 0.373046875 -0.6655273438 -0.517578125 0.1909179688 0.6655273438 -0.1909179688 -0.517578125 0.7685546875 0 -0.4765625 -0.517578125 0.1909179688 -0.6655273438 -0.6655273438 0.1909179688 -0.517578125 0.3530273438 -0.18359375 -0.8041992188 0.517578125 -0.6655273438 -0.1909179688 0.529296875 -0.529296875 -0.373046875 0.6655273438 -0.517578125 -0.1909179688 0.890625 0 -0.3125 -0.517578125 -0.1909179688 0.6655273438 0.529296875 0.373046875 0.529296875 0.517578125 0.1909179688 0.6655273438 -0.8041992188 -0.3530273438 0.18359375 -0.8041992188 -0.18359375 0.3530273438 -0.18359375 0.3530273438 -0.8041992188 -0.1826171875 0.1826171875 -0.91015625 -0.3530273438 0.18359375 -0.8041992188 0.1826171875 0.1826171875 -0.91015625 0.18359375 0.3530273438 -0.8041992188 0.3530273438 0.18359375 -0.8041992188 -0.1826171875 0.91015625 -0.1826171875 -0.18359375 0.8041992188 -0.3530273438 0.8041992188 -0.3530273438 -0.18359375 0.8041992188 -0.18359375 -0.3530273438 0.91015625 -0.1826171875 -0.1826171875 0.3125 0.890625 0 0.18359375 0.3530273438 0.8041992188 0.1826171875 0.1826171875 0.91015625 0.3530273438 0.18359375 0.8041992188 0 0.1494140625 -0.97265625 -0.1494140625 0 -0.97265625 -0.517578125 -0.6655273438 -0.1909179688 -0.625 -0.625 0 -0.97265625 0 0.1494140625 -0.890625 0 0.3125 0.3671875 0.671875 0.3671875 0.529296875 0.529296875 0.373046875 0.529296875 0.529296875 -0.373046875 -0.4765625 0 -0.7685546875 0.671875 0.3671875 -0.3671875 0.6655273438 0.517578125 -0.1909179688 0 -0.7685546875 0.4765625 0.671875 -0.3671875 -0.3671875 0.4765625 -0.7685546875 0 0.625 -0.625 0 0.3530273438 -0.18359375 0.8041992188 0.3671875 -0.3671875 0.671875 0.3125 0 0.890625 0.671875 0.3671875 0.3671875 -0.3671875 0.671875 0.3671875 0.7685546875 0.4765625 0 0.8041992188 0.3530273438 0.18359375 0.1909179688 -0.517578125 0.6655273438 0.373046875 -0.529296875 0.529296875 -0.6655273438 -0.517578125 -0.1909179688 -0.7685546875 -0.4765625 0 -0.1494140625 0 0.97265625 0 0 1 -0.3671875 -0.671875 -0.3671875 -0.3530273438 -0.8041992188 -0.18359375 0 -0.625 0.625 0 -0.1494140625 -0.97265625 -0.1826171875 -0.1826171875 -0.91015625 0.3671875 -0.671875 -0.3671875 -0.7685546875 0 0.4765625 -0.6655273438 0.1909179688 0.517578125 -0.8041992188 0.18359375 0.3530273438 0.3125 -0.890625 0 0.3530273438 -0.8041992188 -0.18359375 0.1494140625 0 0.97265625 -0.517578125 -0.1909179688 -0.6655273438 -0.529296875 -0.373046875 -0.529296875 -0.373046875 -0.529296875 -0.529296875 0.3125 0 -0.890625 0.8041992188 0.18359375 -0.3530273438 0.1909179688 -0.6655273438 0.517578125 0.18359375 -0.8041992188 0.3530273438 0 0.3125 -0.890625 0 0.4765625 -0.7685546875 -0.1826171875 -0.91015625 -0.1826171875 -0.18359375 -0.8041992188 -0.3530273438 -0.7685546875 0 -0.4765625 -0.625 0 -0.625 -0.3125 0.890625 0 -0.3530273438 0.8041992188 -0.18359375 0 0.7685546875 -0.4765625 -0.97265625 0.1494140625 0 -0.3125 0 0.890625 0.1494140625 -0.97265625 0 0 -1 0 0.18359375 -0.3530273438 -0.8041992188 0.6655273438 0.1909179688 -0.517578125 0.517578125 0.1909179688 -0.6655273438 -0.6655273438 0.517578125 0.1909179688 -0.671875 -0.3671875 0.3671875 -0.3671875 0.671875 -0.3671875 -0.91015625 0.1826171875 0.1826171875 -0.6655273438 -0.1909179688 -0.517578125 0.517578125 -0.6655273438 0.1909179688 0.1826171875 -0.91015625 -0.1826171875 -0.529296875 -0.529296875 -0.373046875 0.529296875 0.373046875 -0.529296875 0.529296875 -0.529296875 0.373046875 0.6655273438 -0.517578125 0.1909179688 0.91015625 -0.1826171875 0.1826171875 0.7685546875 -0.4765625 0 -0.8041992188 0.3530273438 0.18359375 0.3530273438 -0.8041992188 0.18359375 -0.625 0.625 0 -0.4765625 0.7685546875 0 -0.517578125 0.6655273438 -0.1909179688 0.3530273438 0.8041992188 0.18359375 -0.3125 0 -0.890625 0 -0.97265625 -0.1494140625 -0.1494140625 -0.97265625 0 0.8041992188 -0.3530273438 0.18359375 0.97265625 -0.1494140625 0 -0.517578125 0.6655273438 0.1909179688 -0.3530273438 0.8041992188 0.18359375 -0.1909179688 -0.6655273438 -0.517578125 0.3671875 -0.671875 0.3671875 0 -0.7685546875 -0.4765625 0 -0.890625 -0.3125 0.18359375 -0.8041992188 -0.3530273438 0.890625 -0.3125 0 0.4765625 0 0.7685546875 0.517578125 -0.1909179688 0.6655273438 0.1909179688 -0.6655273438 -0.517578125 -0.625 0 0.625 -0.6655273438 0.517578125 -0.1909179688 -0.6655273438 -0.1909179688 0.517578125 0 -0.3125 -0.890625 0.1826171875 -0.1826171875 -0.91015625 0.1826171875 -0.1826171875 0.91015625 0.18359375 0.8041992188 -0.3530273438 -0.7685546875 0.4765625 0 0.1494140625 0 -0.97265625 0 0 -1 1 2 1 3 2 3 4 5 6 5 6 4 7 8 8 9 7 9 10 11 12 10 11 12 13 14 15 14 15 13 16 17 18 16 17 18 19 20 21 19 21 20 22 23 24 22 23 24 25 26 27 25 26 27 28 8 8 29 28 29 30 31 31 32 30 32 33 21 34 33 21 34 35 36 37 36 37 35 38 39 39 40 38 40 41 42 41 43 42 43 44 45 44 46 45 46 47 5 48 47 5 48 49 13 13 50 49 50 51 52 53 51 53 52 54 55 56 54 55 56 57 58 58 59 59 57 60 1 1 61 60 61 62 6 6 63 63 62 9 64 64 65 65 9 66 67 42 67 66 42 2 68 2 69 68 69 70 71 70 72 71 72 73 74 75 73 75 74 76 77 76 78 78 77 79 80 79 81 81 80 82 83 83 84 84 82 85 86 80 86 80 85 87 27 87 25 88 18 89 88 89 18 90 91 90 92 92 91 93 94 95 93 94 95 96 97 96 98 98 97 99 100 60 99 100 60 81 19 101 81 19 101 18 102 102 89 32 103 32 104 104 103 93 105 93 106 105 106 46 107 46 51 107 51 48 108 108 47 109 110 110 17 17 109 111 112 113 111 112 113 114 115 115 116 114 116 61 117 117 60 118 106 106 14 14 118 63 119 119 120 120 63 121 122 52 121 52 122 32 123 123 124 124 32 125 46 51 125 67 41 126 127 127 128 128 126 129 54 91 129 54 91 13 130 50 130 131 132 132 110 110 131 11 99 99 83 83 11 133 134 134 34 34 133 135 136 136 137 137 135 138 139 139 140 140 138 141 70 70 142 141 142 143 144 144 145 145 143 146 87 7 87 146 7 147 148 148 149 149 147 50 119 119 49 3 147 3 12 147 12 45 107 107 127 127 45 136 150 150 151 151 136 152 153 62 152 62 153 34 154 155 34 155 154 36 156 157 156 36 157 43 158 43 57 158 57 123 137 137 159 159 123 158 160 161 158 161 160 48 111 162 48 111 162 107 163 121 107 121 163 164 165 126 164 165 126 105 166 105 167 167 166 153 63 120 153 91 56 56 92 148 168 168 149 131 157 131 169 169 157 61 170 117 170 110 169 72 87 72 27 76 22 23 76 92 171 172 171 92 172 167 173 174 173 167 174 40 175 175 176 176 40 25 7 25 8 95 177 178 95 178 177 101 21 33 101 14 93 14 94 179 152 180 179 152 180 61 64 64 28 28 61 106 173 106 181 173 181 176 33 133 176 33 133 151 182 182 183 183 151 40 101 40 33 126 184 127 184 185 186 186 187 187 185 162 113 181 162 113 181 116 30 116 31 188 164 189 188 164 189 148 10 10 190 190 148 191 78 192 78 191 192 78 23 193 23 78 193 125 53 194 125 194 53 129 195 91 195 196 162 162 197 197 196 122 129 195 122 184 45 198 139 198 199 139 199 200 180 200 4 4 180 179 201 180 201 124 202 203 124 203 202 204 141 205 141 205 204 206 70 142 206 9 156 156 65 31 123 137 31 194 138 140 194 154 20 207 154 20 207 73 172 73 92 202 81 38 202 81 38 192 193 147 2 208 11 83 208 51 121 153 176 176 120 47 209 47 210 210 209 177 10 11 177 189 126 184 189 211 44 46 211 63 112 119 112 42 158 212 52 122 212 150 138 150 198 138 198 53 140 140 213 213 53 214 96 214 98 128 165 172 169 169 109 172 109 171 35 171 37 163 127 50 215 215 119 202 79 142 205 142 216 216 205 120 133 206 114 115 206 217 155 154 217 218 203 202 218 180 62 4 62 219 165 164 219 144 121 122 144 57 161 57 35 35 161 215 120 133 215 163 128 37 172 124 104 84 97 97 186 186 84 209 188 220 209 188 220 192 218 39 192 39 218 221 152 152 175 175 221 30 114 213 222 222 212 212 213 223 219 219 224 224 223 212 195 160 212 195 160 65 2 68 65 75 102 225 102 225 75 226 128 128 143 143 226 187 227 227 217 217 187 139 66 67 139 201 200 199 67 199 115 67 115 19 207 195 90 169 37 104 79 79 124 197 108 108 228 228 197 181 196 173 196 229 230 231 230 229 231 9 232 232 7 8 64 193 221 221 192 233 137 233 159 174 223 223 88 88 174 200 234 234 235 235 200 217 207 85 217 207 85 207 80 80 19 89 236 102 236 94 15 94 208 208 15 218 38 12 99 99 3 237 145 145 54 54 237 238 239 239 230 230 238 240 179 201 240 241 219 228 241 219 228 177 94 74 225 225 237 237 74 237 55 24 240 23 240 24 44 44 22 160 90 171 90 240 242 242 201 220 243 243 244 220 244 35 59 170 96 170 238 96 238 69 131 69 132 201 243 243 200 66 140 123 203 159 203 135 116 31 135 185 155 155 187 143 163 163 144 114 142 3 60 236 245 226 236 226 245 246 16 247 246 16 247 242 24 52 213 184 248 45 248 245 237 145 245 134 155 185 134 210 234 209 234 158 222 160 222 90 161 241 197 249 84 186 249 58 43 246 166 166 247 109 73 109 75 222 42 222 66 100 117 97 117 97 100 100 83 84 100 165 224 230 204 230 205 64 1 65 1 116 199 190 168 249 185 224 89 236 224 238 229 191 159 203 191 168 246 166 168 174 241 241 223 108 188 188 228 243 242 228 164 250 231 250 229 215 134 251 215 134 251 15 82 208 82 240 193 193 179 179 221 22 211 22 252 211 252 49 113 112 49 161 171 130 251 130 249 249 251 173 105 146 58 59 146 209 108 156 232 177 208 244 242 242 248 248 244 159 77 77 191 199 135 205 231 231 216 135 198 98 186 187 98 5 111 10 178 178 190 236 225 245 225 175 39 239 170 77 233 247 167 102 17 196 241 213 66 129 145 56 73 74 56 82 130 82 249 88 167 251 50 118 13 234 220 234 243 231 103 103 250 196 174 87 71 157 37 125 211 244 189 244 184 183 76 77 183 29 25 26 29 238 214 229 214 86 79 211 253 253 125 132 16 16 110 254 105 166 254 43 255 58 255 255 41 246 132 233 183 103 30 227 85 256 227 85 256 12 148 144 129 224 226 165 226 254 190 168 254 68 131 157 68 204 239 235 4 251 185 245 143 103 216 130 15 181 118 235 47 5 235 221 39 253 194 257 253 257 194 256 250 250 86 86 256 197 48 198 136 190 95 95 254 253 182 182 257 204 26 141 26 255 70 255 71 149 69 132 149 136 233 151 233 98 227 227 214 74 55 153 175 170 28 223 89 113 118 69 147 115 41 41 206 156 68 17 75 214 256 256 229 111 6 112 6 258 151 182 258 104 250 26 72 72 141 252 253 252 182 138 257 18 247 36 59 149 246 183 252 252 76 232 146 189 220 239 28 216 30 210 235 101 38 29 239 29 204 59 232 232 36 218 191 118 49 248 44 247 88 206 255 71 58 86 104 117 96 71 146 154 21 258 150 254 93 248 24 216 114 258 257 257 150 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 26 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 101 118 119 120 121 122 123 124 125 126 127 128 50 129 130 131 132 133 134 135 136 137 138 66 139 140 141 142 143 144 145 146 147 148 149 150 151 152 126 153 154 155 43 74 156 157 158 159 160 161 53 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 54 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 146 233 234 104 235 189 236 237 238 239 240 139 241 242 239 166 243 244 245 97 246 22 247 248 249 250 251 252 253 254 255 256 257 258 7 259 260 261 262 263 34 264 105 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 263 283 284 156 285 286 287 288 289 290 291 292 31 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 160 310 311 312 313 314 308 315 198 284 316 317 318 319 320 321 322 323 267 324 325 326 327 328 329 180 330 331 332 72 333 334 335 214 336 178 337 338 339 340 250 341 342 343 344 345 304 346 300 3 193 347 348 169 349 350 55 148 351 233 352 353 354 355 356 10 357 358 359 283 47 360 361 362 363 144 45 211 364 149 365 366 367 368 369 370 371 372 109 373 374 228 158 375 376 377 378 379 39 380 197 223 381 191 382 383 89 384 343 385 386 387 352 388 276 389 390 136 207 391 392 326 393 394 395 321 396 397 398 226 399 147 400 401 402 403 388 404 405 157 381 406 380 407 249 120 152 408 409 410 411 412 413 414 415 416 417 418 419 420 138 291 421 422 423 424 425 426 427 428 429 430 431 76 432 433 434 435 436 437 438 439 440 441 442 443 73 444 319 323 445 446 447 181 385 328 340 448 19 449 102 309 407 450 376 451 408 452 453 454 455 274 456 457 458 459 460 9 461 462 295 358 227 8 463 70 464 465 346 215 466 467 468 469 470 471 472 473 474 475 476 477 478 448 118 479 480 481 482 483 484 417 40 485 194 486 487 488 489 490 491 492 493 494 322 495 496 497 107 259 498 499 500 501 502 489 58 24 503 504 505 23 506 48 125 196 430 507 449 145 383 404 103 508 248 509 510 494 511 512 513 402 63 514 161 234 59 515 516 517 277 172 35 518 519 164 268 395 203 520 521 444 177 442 522 523 216 524 369 316 176 292 525 526 527 287 528 529 530 182 331 389 531 532 2 64 533 534 535 536 537 538 12 485 167 503 539 509 540 424 365 541 542 315 543 544 487 545 546 527 32 334 150 222 288 134 355 547 548 549 217 550 507 219 551 552 455 496 553 411 554 526 175 335 212 555 61 556 557 537 558 559 83 476 96 477 75 560 561 562 563 564 92 565 566 426 397 567 568 329 569 71 570 571 137 446 572 236 298 573 574 554 285 282 256 275 479 575 576 577 492 458 524 578 579 580 581 556 582 583 468 454 584 585 510 586 520 587 585 293 460 588 589 590 591 592 593 483 594 595 493 596 596 597 464 598 599 600 135 601 602 603 403 379 604 605 606 124 272 607 551 603 508 62 608 609 129 353 610 461 332 611 497 587 398 452 324 384 612 357 348 613 614 615 616 617 578 572 618 525 619 387 620 618 317 621 286 622 623 51 220 624 297 625 626 627 533 628 41 629 254 528 441 391 516 630 490 467 631 616 557 231 632 18 117 633 634 312 552 635 561 422 98 257 186 488 636 159 637 82 638 605 639 640 253 470 641 163 604 642 643 143 13 644 645 511 504 595 303 646 647 588 127 350 224 457 648 252 57 372 540 81 244 649 210 650 38 361 651 153 566 564 409 265 481 14 652 653 359 654 85 655 650 240 450 656 25 657 658 577 659 660 88 95 478 90 114 661 662 651 165 663 664 665 229 666 667 668 555 378 558 341 648 634 582 44 669 667 663 670 536 405 590 170 671 655 631 33 119 672 486 532 112 673 674 675 676 11 296 183 677 636 678 567 679 617 87 299 573 680 681 682 238 683 168 111 565 491 684 568 320 473 685 686 606 574 535 438 687 620 688 646 162 15 689 151 523 325 690 273 141 653 615 541 691 49 692 693 420 629 694 695 696 697 698 699 307 337 370 700 128 453 621 701 173 702 703 680 704 705 695 131 664 16 706 707 327 708 79 709 519 710 711 712 201 713 714 374 715 716 501 502 255 717 351 241 271 718 713 280 671 469 719 100 687 184 544 521 645 471 456 289 310 423 550 429 204 69 232 679 375 436 142 123 264 465 693 416 290 690 720 371 522 635 318 445 443 410 110 622 366 428 314 721 190 710 722 390 723 333 432 724 675 699 94 559 132 725 592 686 546 719 427 575 394 484 344 726 659 727 701 368 199 84 435 499 728 133 729 623 714 439 65 570 269 730 731 278 626 260 702 396 6 67 437 406 529 647 121 732 681 666 581 512 586 613 733 734 707 597 266 418 735 736 704 696 737 336 600 735 661 724 683 209 60 638 716 154 305 56 377 243 130 538 738 17 514 739 37 27 245 733 740 237 580 741 742 654 187 462 743 171 545 206 640 93 553 440 673 475 414 744 294 270 463 28 279 736 741 718 745 630 746 672 688 472 547 747 311 221 700 345 748 115 749 750 684 751 752 739 68 729 362 392 474 339 517 658 373 689 593 639 579 753 393 720 754 601 602 192 363 301 415 753 213 401 218 755 46 542 754 643 52 756 99 738 723 757 669 30 749 745 113 140 562 624 728 5 174 712 466 42 281 748 757 330 708 709 758 668 727 589 697 513 652 744 759 451 660 480 627 434 734 80 179 760 108 563 583 495 425 758 761 608 77 347 721 364 549 560 242 515 760 36 205 762 200 763 730 500 628 543 594 349 91 86 247 302 703 106 764 750 657 706 447 722 155 765 505 755 677 400 313 766 421 746 685 692 4 614 539 765 767 763 768 569 619 459 195 676 188 571 1 431 235 342 637 761 649 185 698 732 759 632 641 756 576 678 534 609 743 751 386 531 766 261 625 356 715 726 674 725 633 433 742 599 246 29 258 656 230 607 251 21 762 338 584 610 412 116 262 20 747 354 691 530 225 399 764 122 665 711 740 670 419 202 717 705 731 767 382 642 591 662 694 306 78 518 682 768 367 737 506 598 360 752 611 208 548 644 413 482 498 612 trunk-2018.02b/examples/pfacet/pfacetcreators.py000066400000000000000000000106071324306050200216310ustar00rootroot00000000000000# encoding: utf-8 from yade import qt from yade.gridpfacet import * ########################### ##### ENGINES ##### ########################### O.engines=[ ForceResetter(), InsertionSortCollider([ Bo1_Sphere_Aabb(), Bo1_Wall_Aabb(), Bo1_PFacet_Aabb(), ],sortThenCollide=True), InteractionLoop( [ Ig2_GridNode_GridNode_GridNodeGeom6D(), Ig2_Sphere_PFacet_ScGridCoGeom(), Ig2_Wall_PFacet_ScGeom(),Ig2_Wall_Sphere_ScGeom() ], [ Ip2_CohFrictMat_CohFrictMat_CohFrictPhys(setCohesionNow=True,setCohesionOnNewContacts=True), Ip2_FrictMat_FrictMat_FrictPhys()], [ Law2_ScGeom6D_CohFrictPhys_CohesionMoment(), Law2_ScGeom_FrictPhys_CundallStrack(), Law2_ScGridCoGeom_FrictPhys_CundallStrack(), Law2_GridCoGridCoGeom_FrictPhys_CundallStrack() ]), GlobalStiffnessTimeStepper(timestepSafetyCoefficient=0.8,label='ts'), NewtonIntegrator(gravity=(0,-9.81,0),damping=0.1,label='newton') ] O.materials.append(CohFrictMat(young=1e7,poisson=1,density=1e2,frictionAngle=radians(30),normalCohesion=3e7,shearCohesion=3e7,momentRotationLaw=True,label='gridNodeMat')) O.materials.append(FrictMat(young=1e7,poisson=1,density=1e2,frictionAngle=radians(30),label='gridConnectionMat')) ################################### ##### PFacet creators ##### ################################### fixed = False color=[255./255.,102./255.,0./255.] nodesIds=[] cylIds=[] #position of the node in the middle a=0.00 r=0.03 ## Option 1: pfacet(id1,id2,id3) -> based on 3 gridNodes already connected via 3 gridConnections nodesIds.append( O.bodies.append(gridNode([0,0,0],r,wire=False,fixed=fixed,material='gridNodeMat',color=color)) ) nodesIds.append( O.bodies.append(gridNode([1,0,0],r,wire=False,fixed=fixed,material='gridNodeMat',color=color)) ) nodesIds.append( O.bodies.append(gridNode([0.5,1,0],r,wire=False,fixed=fixed,material='gridNodeMat',color=color)) ) cylIds.append(O.bodies.append( gridConnection(0,1,r,color=color) )) cylIds.append(O.bodies.append( gridConnection(1,2,r,color=color) )) cylIds.append(O.bodies.append( gridConnection(2,0,r,color=color) )) O.bodies.append( pfacet(nodesIds[0],nodesIds[1],nodesIds[2],wire=False,color=color,highlight=False,material=O.materials[1]) ) ## Option 2: pfacetCreator1(vertices) -> based on 3 vertices v1=Vector3(2,0,0) v2=Vector3(3,0,0) v3=Vector3(2.5,1,0) vertices=[v1,v2,v3] pfacetCreator1(vertices,r,nodesIds=[],cylIds=[],pfIds=[],wire=False,color=color,fixed=fixed,materialNodes='gridNodeMat',material='gridConnectionMat') ## Option 3: pfacetCreator2(id1,id2,vertex) -> based on 2 gridNodes connected via a gridConnection and a vertex nodesIds.append( O.bodies.append(gridNode([4,0,0],r,wire=False,fixed=fixed,material='gridNodeMat',color=color)) ) nodesIds.append( O.bodies.append(gridNode([5,0,0],r,wire=False,fixed=fixed,material='gridNodeMat',color=color)) ) vertex=Vector3(4.5,1,0) cylIds.append(O.bodies.append( gridConnection(nodesIds[3],nodesIds[4],r,color=color) ) ) pfacetCreator2(nodesIds[3],nodesIds[4],vertex,r,nodesIds=nodesIds,wire=True,materialNodes='gridNodeMat',material='gridConnectionMat',color=color,fixed=fixed) ## Option 4: pfacetCreator3(id1,id2,id3) -> based on 3 gridNodes a = O.bodies.append(gridNode([6,0,0],r,wire=False,fixed=fixed,material='gridNodeMat',color=color)) b = O.bodies.append(gridNode([7,0,0],r,wire=False,fixed=fixed,material='gridNodeMat',color=color)) c = O.bodies.append(gridNode([6.5,1,0],r,wire=False,fixed=fixed,material='gridNodeMat',color=color)) pfacetCreator3(a,b,c,cylIds=[],pfIds=[],wire=False,material=-1,color=color) ## Option 4: pfacetCreator4(id1,id2,id3) -> based on 3 gridConnections a = O.bodies.append(gridNode([8,0,0],r,wire=False,fixed=fixed,material='gridNodeMat',color=color)) b = O.bodies.append(gridNode([9,0,0],r,wire=False,fixed=fixed,material='gridNodeMat',color=color)) c = O.bodies.append(gridNode([8.5,1,0],r,wire=False,fixed=fixed,material='gridNodeMat',color=color)) n=len(cylIds) cylIds.append(O.bodies.append( gridConnection(a,b,r,color=color) ) ) cylIds.append(O.bodies.append( gridConnection(b,c,r,color=color) ) ) cylIds.append(O.bodies.append( gridConnection(c,a,r,color=color) ) ) pfacetCreator4(cylIds[n],cylIds[n+1],cylIds[n+2],pfIds=[],wire=False,material=-1,color=color) ##################### ##### Wall ### ##################### O.bodies.append(utils.wall(position=-1,sense=0, axis=1,color=Vector3(1,0,0),material='gridConnectionMat')) qt.View() O.saveTmp() trunk-2018.02b/examples/pfacet/sphere.gts000066400000000000000000000050361324306050200202570ustar00rootroot0000000000000042 120 80 GtsSurface GtsFace GtsEdge GtsVertex 0 0 -1 -0.3090169728 0.5 -0.8090170026 0.3090169728 0.5 -0.8090170026 -1 0 0 -0.850650847 -0.5257311463 0 -0.8090170026 -0.3090169728 0.5 0.5 0.8090170026 0.3090169728 0.5 0.8090170026 -0.3090169728 0.850650847 0.5257311463 0 0 1 0 0 0.850650847 0.5257311463 -0.5 0.8090170026 0.3090169728 0 0.850650847 -0.5257311463 -0.5 0.8090170026 -0.3090169728 0 -0.850650847 0.5257311463 0 -1 0 0.5 -0.8090170026 0.3090169728 0.5257311463 0 -0.850650847 0.3090169728 -0.5 -0.8090170026 0.8090170026 -0.3090169728 -0.5 -0.5 -0.8090170026 -0.3090169728 0 -0.850650847 -0.5257311463 -0.3090169728 -0.5 -0.8090170026 -0.8090170026 -0.3090169728 -0.5 -0.5257311463 0 -0.850650847 -0.8090170026 0.3090169728 0.5 -0.8090170026 0.3090169728 -0.5 0.5 -0.8090170026 -0.3090169728 0.850650847 -0.5257311463 0 0 0 1 -0.5257311463 0 0.850650847 -0.3090169728 -0.5 0.8090170026 -0.850650847 0.5257311463 0 0.3090169728 0.5 0.8090170026 -0.3090169728 0.5 0.8090170026 -0.5 -0.8090170026 0.3090169728 0.5257311463 0 0.850650847 0.8090170026 0.3090169728 0.5 0.8090170026 -0.3090169728 0.5 0.3090169728 -0.5 0.8090170026 0.8090170026 0.3090169728 -0.5 1 0 0 1 2 2 3 3 1 4 5 5 6 4 6 7 8 7 9 9 8 10 11 12 10 12 11 13 10 13 14 10 14 15 16 17 16 17 15 18 19 20 18 19 20 21 22 23 21 22 23 24 23 25 24 23 25 6 26 26 4 27 24 27 25 28 17 28 29 29 17 30 31 31 32 30 32 33 4 26 33 34 35 34 11 11 35 1 25 25 2 36 21 36 5 5 21 21 24 11 7 10 7 16 22 21 16 2 27 27 14 14 2 19 23 19 22 37 34 38 37 34 38 38 7 7 34 35 12 17 39 39 40 40 17 15 36 16 36 8 13 3 8 13 3 41 3 18 41 3 18 9 42 41 42 41 9 32 15 36 32 6 36 38 39 37 39 39 29 6 31 31 26 42 20 20 41 14 12 14 33 33 12 24 4 24 5 42 29 39 42 16 28 4 27 8 10 20 28 29 20 28 19 22 28 33 27 37 30 40 30 40 37 32 40 15 40 35 30 35 31 30 34 42 38 1 19 23 1 18 1 2 13 9 38 32 6 26 35 12 26 8 41 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 6 28 29 30 31 26 32 33 34 35 36 37 38 29 39 40 41 42 43 44 1 45 46 47 48 25 23 10 49 50 51 52 22 53 54 55 56 57 24 58 59 60 60 61 62 12 63 42 64 65 66 16 67 68 69 70 71 72 73 74 44 31 53 75 76 77 78 79 67 80 5 46 81 59 82 34 83 64 28 84 85 86 87 76 88 89 90 68 45 52 4 91 92 93 94 83 95 32 17 15 88 11 96 30 91 50 7 97 98 99 33 57 100 101 47 92 48 38 102 96 51 101 95 103 104 105 106 78 107 35 108 109 93 99 86 103 58 110 87 20 73 111 81 94 112 56 113 18 66 107 114 3 74 37 106 104 2 115 71 110 40 108 75 116 111 114 19 112 43 113 27 117 80 79 21 98 100 13 97 69 118 85 109 41 62 49 61 116 8 90 39 119 119 118 63 115 55 14 120 72 70 9 77 120 36 84 117 54 102 89 65 82 105 trunk-2018.02b/examples/pfacet/sphere.mesh000066400000000000000000000166531324306050200204250ustar00rootroot00000000000000 MeshVersionFormatted 2 Dimension 3 Vertices 67 0 0 0 1 0.1 0 0 2 0 0.1 0 3 -0.1 0 0 4 0 -0.1 0 5 0 0 0.1 6 0 0 -0.1 7 0.092387953251167 0.038268343236416 0 1 0.070710678118792 0.070710678118518 0 1 0.038268343236596 0.092387953251093 0 1 -0.038268343236416 0.092387953251167 0 2 -0.070710678118518 0.070710678118792 0 2 -0.092387953251093 0.038268343236596 0 2 -0.092387953251167 -0.038268343236416 0 3 -0.070710678118792 -0.070710678118518 0 3 -0.038268343236596 -0.092387953251093 0 3 0.038268343236416 -0.092387953251167 0 4 0.070710678118518 -0.070710678118792 0 4 0.092387953251093 -0.038268343236596 0 4 0 0.038268343236416 0.092387953251167 6 0 0.070710678118518 0.070710678118792 6 0 0.092387953251093 0.038268343236596 6 0 -0.038268343236416 0.092387953251167 7 0 -0.070710678118518 0.070710678118792 7 0 -0.092387953251093 0.038268343236596 7 0.038268343236416 0 0.092387953251167 8 0.070710678118518 0 0.070710678118792 8 0.092387953251093 0 0.038268343236596 8 -0.038268343236416 0 0.092387953251167 9 -0.070710678118518 0 0.070710678118792 9 -0.092387953251093 0 0.038268343236596 9 0 0.038268343236416 -0.092387953251167 10 0 0.070710678118518 -0.070710678118792 10 0 0.092387953251093 -0.038268343236596 10 0.038268343236416 0 -0.092387953251167 11 0.070710678118518 0 -0.070710678118792 11 0.092387953251093 0 -0.038268343236596 11 0 -0.038268343236416 -0.092387953251167 12 0 -0.070710678118518 -0.070710678118792 12 0 -0.092387953251093 -0.038268343236596 12 -0.038268343236416 0 -0.092387953251167 13 -0.070710678118518 0 -0.070710678118792 13 -0.092387953251093 0 -0.038268343236596 13 0.077742384372375 0.044475395850299 -0.044475395850482 15 0.044475395850283 0.044475395850283 -0.077742384372498 15 0.044475395850498 0.077742384372253 -0.044475395850498 15 0.044475395850299 -0.077742384372375 -0.044475395850482 17 0.044475395850283 -0.044475395850283 -0.077742384372498 17 0.077742384372253 -0.044475395850498 -0.044475395850498 17 -0.044475395850283 -0.044475395850283 -0.077742384372498 19 -0.077742384372375 -0.044475395850299 -0.044475395850482 19 -0.044475395850498 -0.077742384372253 -0.044475395850498 19 -0.044475395850283 0.044475395850283 -0.077742384372498 21 -0.044475395850299 0.077742384372375 -0.044475395850482 21 -0.077742384372253 0.044475395850498 -0.044475395850498 21 0.044475395850299 -0.077742384372375 0.044475395850482 23 0.044475395850283 -0.044475395850283 0.077742384372498 23 0.077742384372253 -0.044475395850498 0.044475395850498 23 0.044475395850283 0.044475395850283 0.077742384372498 25 0.044475395850498 0.077742384372253 0.044475395850498 25 0.077742384372375 0.044475395850299 0.044475395850482 25 -0.044475395850283 -0.044475395850283 0.077742384372498 27 -0.077742384372375 -0.044475395850299 0.044475395850482 27 -0.044475395850498 -0.077742384372253 0.044475395850498 27 -0.044475395850283 0.044475395850283 0.077742384372498 29 -0.044475395850299 0.077742384372375 0.044475395850482 29 -0.077742384372253 0.044475395850498 0.044475395850498 29 Edges 48 2 8 1 8 9 1 9 10 1 10 3 1 3 11 2 11 12 2 12 13 2 13 4 2 4 14 3 14 15 3 15 16 3 16 5 3 5 17 4 17 18 4 18 19 4 19 2 4 6 20 6 20 21 6 21 22 6 22 3 6 6 23 7 23 24 7 24 25 7 25 5 7 6 26 8 26 27 8 27 28 8 28 2 8 6 29 9 29 30 9 30 31 9 31 4 9 7 32 10 32 33 10 33 34 10 34 3 10 7 35 11 35 36 11 36 37 11 37 2 11 7 38 12 38 39 12 39 40 12 40 5 12 7 41 13 41 42 13 42 43 13 43 4 13 Triangles 128 2 8 37 15 8 44 37 15 8 9 44 15 37 44 36 15 36 45 35 15 45 32 35 15 45 33 32 15 35 32 7 15 36 44 45 15 44 46 45 15 44 9 46 15 45 46 33 15 9 10 46 15 10 34 46 15 10 3 34 15 46 34 33 15 5 17 40 17 17 47 40 17 17 18 47 17 40 47 39 17 39 48 38 17 48 35 38 17 48 36 35 17 38 35 7 17 39 47 48 17 47 49 48 17 47 18 49 17 48 49 36 17 18 19 49 17 19 37 49 17 19 2 37 17 49 37 36 17 7 38 41 19 38 50 41 19 38 39 50 19 41 50 42 19 42 51 43 19 51 14 43 19 51 15 14 19 43 14 4 19 39 52 50 19 52 51 50 19 52 15 51 19 50 51 42 19 5 16 40 19 16 52 40 19 16 15 52 19 40 52 39 19 7 41 32 21 41 53 32 21 41 42 53 21 32 53 33 21 33 54 34 21 54 11 34 21 54 12 11 21 34 11 3 21 12 55 13 21 55 43 13 21 55 42 43 21 13 43 4 21 12 54 55 21 54 53 55 21 54 33 53 21 55 53 42 21 5 17 25 23 17 56 25 23 17 18 56 23 25 56 24 23 24 57 23 23 57 26 23 23 57 27 26 23 23 26 6 23 27 58 28 23 58 19 28 23 58 18 19 23 28 19 2 23 27 57 58 23 57 56 58 23 57 24 56 23 58 56 18 23 6 26 20 25 26 59 20 25 26 27 59 25 20 59 21 25 21 60 22 25 60 10 22 25 60 9 10 25 22 10 3 25 9 61 8 25 61 28 8 25 61 27 28 25 8 28 2 25 21 59 60 25 59 61 60 25 59 27 61 25 60 61 9 25 6 23 29 27 23 62 29 27 23 24 62 27 29 62 30 27 30 63 31 27 63 14 31 27 63 15 14 27 31 14 4 27 30 62 63 27 62 64 63 27 62 24 64 27 63 64 15 27 24 25 64 27 25 16 64 27 25 5 16 27 64 16 15 27 6 29 20 29 29 65 20 29 29 30 65 29 20 65 21 29 21 66 22 29 66 11 22 29 66 12 11 29 22 11 3 29 21 65 66 29 65 67 66 29 65 30 67 29 66 67 12 29 30 31 67 29 31 13 67 29 31 4 13 29 67 13 12 29 End trunk-2018.02b/examples/polyhedra/000077500000000000000000000000001324306050200167735ustar00rootroot00000000000000trunk-2018.02b/examples/polyhedra/ball.py000066400000000000000000000023301324306050200202550ustar00rootroot00000000000000from yade import plot, polyhedra_utils from yade import qt m = PolyhedraMat() m.density = 2600 #kg/m^3 m.young = 1E6 #Pa m.poisson = 20000/1E6 m.frictionAngle = 0.6 #rad maxLoad = 3E6 minLoad = 1E3 O.bodies.append(utils.wall(0,axis=2,sense=1, material = m)) t = polyhedra_utils.polyhedralBall(0.025, 100, m, (0,0,0)) t.state.pos = (0,0,0.5) O.bodies.append(t) def checkUnbalanced(): print "unbalanced forces = %.5f, position %f, %f, %f"%(utils.unbalancedForce(), t.state.pos[0], t.state.pos[1], t.state.pos[2]) O.engines=[ ForceResetter(), InsertionSortCollider([Bo1_Polyhedra_Aabb(),Bo1_Wall_Aabb(),Bo1_Facet_Aabb()]), InteractionLoop( [Ig2_Wall_Polyhedra_PolyhedraGeom(), Ig2_Polyhedra_Polyhedra_PolyhedraGeom(), Ig2_Facet_Polyhedra_PolyhedraGeom()], [Ip2_PolyhedraMat_PolyhedraMat_PolyhedraPhys()], # collision "physics" [Law2_PolyhedraGeom_PolyhedraPhys_Volumetric()] # contact law -- apply forces ), #GravityEngine(gravity=(0,0,-9.81)), NewtonIntegrator(damping=0.5,gravity=(0,0,-9.81)), PyRunner(command='checkUnbalanced()',realPeriod=3,label='checker') ] #O.dt=0.025*polyhedra_utils.PWaveTimeStep() O.dt=0.00025 qt.Controller() V = qt.View() O.saveTmp() #O.run() trunk-2018.02b/examples/polyhedra/clump.py000066400000000000000000000037201324306050200204670ustar00rootroot00000000000000m = PolyhedraMat() m.density = 2600 #kg/m^3 m.Ks = 20000 m.Kn = 1E9 #Pa m.frictionAngle = 0.6 #rad O.materials.append(m) ###################################################################### # basic test t1 = polyhedron(((0,0,0),(0,0,1),(0,1,0),(0,1,1),(1,0,0),(1,0,1),(1,1,0),(1,1,1))) t2 = polyhedron(((0,0,1),(0,0,2),(0,1,1),(0,1,2),(2,0,1),(2,0,2),(2,1,1),(2,1,2))) t1.state.mass = 1e-12 t1.state.inertia = (1e-12,1e-12,1e-12) t1.state.pos = (0,.5,0) t2.state.ori = ((1,2,3),1) t2.state.pos = (sqrt(2),.5,-sqrt(2)) clumpId,polys = O.bodies.appendClumped((t1,t2)) clump = O.bodies[clumpId] s = clump.state s2 = t2.state # if t1.mass and inertia are set to almost zero, clump properties should equal t2 properties print s.pos, s2.pos print s.mass, s2.mass print s.inertia, s2.inertia ###################################################################### ###################################################################### # something more real wire = False O.bodies.clear() t1 = polyhedron(((0,0,0),(0,0,1),(0,1,0),(0,1,1),(1,0,0),(1,0,1),(1,1,0),(1,1,1)),color=(0,1,0),wire=wire) t2 = polyhedron(((0,0,1),(0,0,2),(0,1,1),(0,1,2),(2,0,1),(2,0,2),(2,1,1),(2,1,2)),color=(0,1,0),wire=wire) bottom = polyhedron(((-5,-5,0),(5,-5,0),(5,5,0),(-5,5,0),(-5,-5,-1),(5,-5,-1),(5,5,-1),(-5,5,-1)),fixed=True,color=(0,1,1),wire=wire) O.bodies.append(bottom) clumpId,polys = O.bodies.appendClumped((t1,t2)) s = O.bodies[clumpId].state s.pos = (0,0,5) s.ori = Quaternion((1,1,.3),1.5) O.engines=[ ForceResetter(), InsertionSortCollider([Bo1_Polyhedra_Aabb(),Bo1_Wall_Aabb(),Bo1_Facet_Aabb()]), InteractionLoop( [Ig2_Polyhedra_Polyhedra_PolyhedraGeom()], [Ip2_PolyhedraMat_PolyhedraMat_PolyhedraPhys()], [Law2_PolyhedraGeom_PolyhedraPhys_Volumetric()], ), NewtonIntegrator(label='newton'), ] O.dt=0.00001 try: from yade import qt qt.View() except: pass O.step() newton.gravity = (0,0,-9.81) ###################################################################### trunk-2018.02b/examples/polyhedra/free-fall.py000066400000000000000000000040231324306050200212010ustar00rootroot00000000000000from yade import plot, polyhedra_utils gravel = PolyhedraMat() gravel.density = 2600 #kg/m^3 gravel.young = 1E7 #Pa gravel.poisson = 20000/1E7 gravel.frictionAngle = 0.5 #rad steel = PolyhedraMat() steel.density = 7850 #kg/m^3 steel.young = 10*gravel.young steel.poisson = gravel.poisson steel.frictionAngle = 0.4 #rad rubber = PolyhedraMat() rubber.density = 1000 #kg/m^3 rubber.young = gravel.young/10 rubber.poisson = gravel.poisson rubber.frictionAngle = 0.7 #rad O.bodies.append(polyhedra_utils.polyhedra(gravel,v=((0,0,-0.05),(0.3,0,-0.05),(0.3,0.3,-0.05),(0,0.3,-0.05),(0,0,0),(0.3,0,0),(0.3,0.3,0),(0,0.3,0)),fixed=True, color=(0.35,0.35,0.35))) #O.bodies.append(utils.wall(0,axis=1,sense=1, material = gravel)) #O.bodies.append(utils.wall(0,axis=0,sense=1, material = gravel)) #O.bodies.append(utils.wall(0.3,axis=1,sense=-1, material = gravel)) #O.bodies.append(utils.wall(0.3,axis=0,sense=-1, material = gravel)) polyhedra_utils.fillBox((0,0,0), (0.3,0.3,0.3),gravel,sizemin=[0.025,0.025,0.025],sizemax=[0.05,0.05,0.05],seed=4) def checkUnbalancedI(): print "iter %d, time elapsed %f, time step %.5e, unbalanced forces = %.5f"%(O.iter, O.realtime, O.dt, utils.unbalancedForce()) O.engines=[ ForceResetter(), InsertionSortCollider([Bo1_Polyhedra_Aabb(),Bo1_Wall_Aabb(),Bo1_Facet_Aabb()]), InteractionLoop( [Ig2_Wall_Polyhedra_PolyhedraGeom(), Ig2_Polyhedra_Polyhedra_PolyhedraGeom(), Ig2_Facet_Polyhedra_PolyhedraGeom()], [Ip2_PolyhedraMat_PolyhedraMat_PolyhedraPhys()], # collision "physics" [Law2_PolyhedraGeom_PolyhedraPhys_Volumetric()] # contact law -- apply forces ), #GravityEngine(gravity=(0,0,-9.81)), NewtonIntegrator(damping=0.3,gravity=(0,0,-9.81)), PyRunner(command='checkUnbalancedI()',realPeriod=5,label='checker') ] #O.dt=0.25*polyhedra_utils.PWaveTimeStep() O.dt=0.0025*polyhedra_utils.PWaveTimeStep() from yade import qt qt.Controller() V = qt.View() V.screenSize = (550,450) V.sceneRadius = 1 V.eyePosition = (0.7,0.5,0.1) V.upVector = (0,0,1) V.lookAt = (0.15,0.15,0.1) trunk-2018.02b/examples/polyhedra/horse.coarse.gts000066400000000000000000000723221324306050200221130ustar00rootroot00000000000000336 1002 668 GtsSurface GtsFace GtsEdge GtsVertex 0.01632376508 0.02011445965 0.03949959237 0.01658055601 0.02674922323 0.04300259797 0.01342358396 0.04386696349 0.0605453965 0.03001315348 0.01873622255 -0.04975389892 0.03116204225 0.01487261331 -0.04662516627 0.03463446561 0.01417249157 -0.06445953302 0.03853905253 -0.06858153453 -0.05929809 0.03482282022 -0.07347322551 -0.06009816584 0.03366937294 -0.06833041545 -0.06716035938 -0.00372101118 -0.05117824214 -0.005623645339 0.007895279935 -0.05575069912 -0.005892351915 -0.0051427155 -0.06231273516 -0.02069042783 -0.004407438103 0.0225899988 -0.0146861251 -0.0003199737081 0.02192630914 -0.02218160879 -0.00413713029 0.02296543695 -0.03588216291 0.03873458014 -0.03350409582 0.02683470299 0.04167919777 -0.05209059257 0.02091048947 0.0384508985 -0.04394816916 0.03037623224 -0.00347869098 -0.0933882462 -0.05987463672 -0.007297635957 -0.085588449 -0.05685515734 -0.005108643449 -0.08776230264 -0.06592629656 0.00971348483 -0.05849589992 0.03501436738 0.01354081077 -0.06958064753 0.02776828047 0.003341884252 -0.06982264577 0.02716426742 0.03602814732 -0.06264440825 -0.06411553023 0.03861060035 -0.06676979291 -0.07423763023 -0.004846611765 -0.08361531667 -0.04846258017 -0.002386477701 -0.08901605742 -0.04964340898 0.02635812852 0.01450927868 -0.005449945347 0.02124857632 0.005886586267 -0.01502511705 0.02930646502 0.01418264739 -0.00105293662 0.004999485014 0.04869287387 0.01582247286 0.004243912062 0.06354979735 0.03968593702 0.01387909336 0.04855022447 0.01654678367 -0.009574652073 0.04456750532 -0.07629616209 -0.002485469762 0.04727735928 -0.0764080269 -0.00700769812 0.03847786397 -0.06665893437 0.0032170073 -0.07051379881 -0.0243411354 0.003533893412 -0.07682277776 -0.03010434885 -5.745529168e-05 -0.08205292028 -0.04580411502 -0.004448292266 0.05776775096 0.04979181973 -0.002383352765 0.05840379756 0.03884913681 0.001649104365 0.04848064833 0.02428435727 0.02280040553 -0.006939100398 -0.01700689858 0.006525650782 -0.001905083605 -0.01926260778 0.007140868966 -0.01530936769 -0.01866033645 -0.01982634563 0.06775149539 0.06364431428 -0.02052662065 0.06497200103 0.07184237648 -0.01519768681 0.07336705425 0.0715293586 0.01640748827 -0.05470159714 -0.004061572812 0.02539781525 -0.05123916404 -0.004418224582 0.01340618388 -0.04149645941 -0.001936511727 -0.008547100601 -0.08917646339 -0.07601223399 0.002777066181 -0.08725952582 -0.0750620646 0.03253016364 -0.01043332179 0.02464282367 0.0304216333 0.006229091894 0.01981939311 0.02379963169 0.004276035173 0.02641813304 0.021068714 -0.04731479093 0.03782239003 0.003905242236 -0.03500299482 0.03582015786 0.004519749149 -0.04728633163 0.036171524 -0.03280646749 0.08281820251 0.05149462988 -0.02756475039 0.08573123505 0.05271463003 -0.03829187523 0.09115468692 0.04143674173 0.02850202338 -0.03054564901 -0.006315949257 0.02350165238 -0.01868529666 -0.01503724716 0.01965018576 -0.03322924453 -0.008539572723 0.003739677065 -0.06921427227 -0.01054149552 -0.00232621657 -0.07547428688 -0.01626294319 0.002561009975 -0.08147905387 -0.02169793489 0.003076164201 0.04142008331 -0.0761966455 -0.01048475663 0.07223536575 0.03862131711 0.003992378901 0.06593588934 0.0451209652 -0.001399859785 0.07361016554 0.04738085301 0.03596544551 -0.05057151191 -0.02335768512 0.02889815291 -0.04556070059 -0.01670429877 0.03458402404 -0.05834573855 -0.0371353123 0.02010910444 0.03325359829 0.03825534808 0.01969706093 0.04715955569 0.04155686324 -0.004374822524 0.03051240645 -0.02705523158 -0.007495620838 0.02978022866 -0.0413146882 -0.000952619628 0.03546274646 -0.02679558677 0.02492957417 -0.05990563106 -0.005103924907 0.01611197516 -0.06303677163 0.002209156417 0.02878306107 -0.06891623602 0.006810449087 0.03078789176 -0.04533835443 0.03599761587 0.02662411434 -0.06036994096 0.03367763245 -0.006198482586 -0.06038907344 0.02623821462 0.0008375952448 -0.05729894408 0.03314770082 0.01065539068 -0.02926798544 -0.01268610203 -0.00123172177 -0.0821110564 -0.06523927604 0.002267643128 -0.08483936173 -0.06485744272 0.002435320012 -0.0287034084 -0.0104083385 0.005898167992 -0.04014503574 -0.002000862249 -0.003413185552 -0.03748934485 0.0001903625871 0.02897089094 -0.06595417837 -0.0270747464 0.02626254606 -0.0576519279 -0.02771720415 0.02815688444 -0.05859886192 -0.01099546206 -0.00569519506 -0.003667327703 -0.009867925479 -0.003664341859 -0.01099308827 -0.01318038894 -0.01057455748 -0.0149757422 -0.004342056125 0.001165074828 0.02523403306 -0.03957574567 0.04054506938 -0.05491973431 -0.0005712432394 0.03459796038 -0.0670694805 0.014086427 0.03694977413 -0.06008906668 -0.006327568557 -0.01401089929 0.061731812 0.04791715597 -0.008148017624 0.06212160805 0.05173537454 0.03793501933 -0.05819911215 -0.0302432721 0.03560587506 -0.06245449477 -0.03179501568 0.0367011475 -0.05846435141 -0.02266137752 -0.03563223221 0.09281699658 0.03718100785 -0.04395759744 0.08685333977 0.03936496067 0.03260221715 0.008892183194 0.01391709339 0.03162763347 0.02970131831 0.02390040048 0.02728308792 0.01291182131 0.02544617293 -0.03210473943 0.07806264329 0.03992804324 -0.02533315814 0.06818440412 0.04741765489 -0.01650203414 0.06427222995 0.04146414271 6.463558982e-05 -0.06502324323 -0.01986639057 0.03588887642 -0.01310150974 -0.006069052401 0.03347089759 -0.001366867552 -0.006152612083 0.03880636848 -0.006999837534 0.002448307327 0.03634549187 0.03037389935 0.0006780210525 0.03114699098 0.03489764439 -0.01010029658 0.03181243893 0.02389805249 -0.01343461117 0.03321915172 -0.06970096602 -0.07479721793 0.02617661498 -0.06543442348 -0.07512581527 0.03863791475 -0.05898871062 -0.07447204709 -0.02464585764 0.07790419383 0.03911371277 -0.003676175066 -0.08031856436 -0.04419774774 0.007343293057 -0.0690826736 0.004699082341 0.001135414165 0.00332397633 -0.0149654229 -0.01018168771 -0.008969831054 0.01620246584 -0.004067298017 0.003541697005 0.02036548799 -0.005624862137 0.01297296776 0.01362713716 0.02993126142 0.006120180121 -0.05980834193 0.0300117401 0.01043604433 -0.06707332214 0.02614749382 0.009599598604 -0.06096653469 0.04068615925 -0.03832144847 0.002255096 0.03504581195 -0.04090497767 -0.006560986967 0.001606782166 -0.06062908051 -0.002709502897 -0.04048925521 0.08225382497 0.03671803481 0.03132196986 -0.05482448464 -0.07441255765 0.02897618302 -0.0646968157 -0.06570386051 0.03944244605 -0.009950499022 0.0125642861 0.01703653311 0.00492018746 -0.01825757265 -0.006672171266 -0.02467411049 -0.007335749351 -0.01088453518 -0.02619080841 0.002504819755 -0.004774100144 0.01405735811 0.001208144152 -0.009245597269 0.02264272615 -0.009987290754 0.02942965855 0.03814804577 -0.003053383658 0.02693592434 0.04610907695 0.00513848505 0.03161183356 0.04457372099 0.01243961132 0.01749335271 0.003408425806 0.02948884943 -0.0005986910633 0.06249697317 0.0389153322 -0.01011112712 -0.002490220659 -0.001339166587 0.02831876953 -0.04114566039 -0.004324052218 0.0299062197 -0.06640669737 -0.03817618226 0.03362395169 -0.06807729632 -0.04214825059 0.03145385637 -0.07043790333 -0.05065241666 -0.01385993011 0.07856512257 0.07010909487 -0.008966036138 0.08124334878 0.07413615785 -0.009634851258 0.08377656624 0.0648067235 0.03417987295 -0.03126020576 0.002051472635 0.03887913599 -0.01975554264 0.0003483112019 0.04041715828 -0.02222707883 0.009213719072 -0.02411216744 0.08716343663 0.04001838496 -0.03624732783 0.08877194901 0.03128124926 -0.006824447279 -0.03618631285 0.01036314859 0.006109389531 0.01401156355 -0.0172745504 -0.016955121 0.08888462345 0.06025997372 0.003136454446 0.05879174051 0.06996981433 0.009364015629 0.04383896399 0.06214828201 -0.002738714681 0.05721547048 0.06819604226 0.003596156501 0.02817269302 0.03719761616 0.001175439491 0.0400173452 0.03540909225 -0.001421426994 0.0409502811 0.02679768936 0.02296542558 0.0167059551 -0.02832207696 0.02023218019 0.01721885354 -0.01877052029 0.02259148848 0.02360483443 -0.03643464206 -0.02733044091 0.08787125685 0.04692096171 -0.01399844147 0.08714155927 0.0463454293 -0.002247771816 0.04960853348 0.04956155038 0.0371112331 -0.05642993252 -0.009103748836 0.03086388637 -0.05958940644 -0.009900558094 0.008083627594 0.02943377229 -0.01652181423 0.02878488846 0.03153740072 -0.01648268425 0.007378942884 0.03703186095 -0.01198024828 -0.00711984916 0.0838714505 0.05079055561 -0.01484917366 0.08707045168 0.05273273139 0.008900663526 0.02076874679 0.04039260339 -0.01065687562 0.06781706451 0.07104192583 -0.01923213626 0.06975322838 0.07592350727 -0.00180861207 -0.0237871789 0.02633206102 0.007081781297 0.03686708502 0.05384152926 0.0002606634791 0.0300769278 -0.05264987799 -1.128720967e-05 0.03467143884 -0.05385795846 0.001993284754 0.03402992897 -0.06497843758 0.01894026904 0.04552231826 0.002338742111 -0.001477592376 -0.07066827034 0.01717140905 -0.01058369946 -0.06554564462 -0.00479891702 -0.008532601031 -0.06674792612 0.01454048954 -0.005328400137 0.02877925501 0.02858328232 -0.002489659267 0.01816406301 0.02870549243 -0.008868073212 0.02177590967 0.01659891666 0.03201213473 -0.03332058818 0.03405172573 0.02496484114 -0.03482376588 0.03693143424 -0.007084975908 0.03564502825 -0.07638398126 0.03309913736 -0.06616617067 -0.02570362467 -0.01123582043 -0.05349151222 0.02156296952 -0.00633979874 -0.03525189872 0.02863119267 -0.01001486821 -0.04111521424 0.02037054785 0.0240898149 0.01974495734 -0.07571285871 0.03668911049 0.01706817617 -0.07522452542 0.03082837721 0.02288104727 -0.07477952054 0.02004949479 -0.004877881526 0.02863728161 0.02147141403 -0.01898875044 0.03130205807 0.006868598403 -0.008743899678 0.02845800478 -0.03490374023 0.08345473139 0.03190892168 0.008286006602 0.001373361038 0.02840663722 0.02705283708 0.01619527881 -0.01904634486 0.022758065 0.01432331388 -0.03659455797 0.03933367424 -0.01642258446 0.01840400709 -0.006687503522 0.06451285043 0.03898786259 -0.009085418731 0.03245207027 -0.01436499165 0.03017300659 -0.06508189934 -0.05136735094 0.003987663327 0.02925023984 -0.03321500269 0.002677092655 0.03375018866 -0.03674315581 0.01543242064 0.05460798553 0.05507772057 -0.006421711898 0.07742526017 0.0696651736 -0.00419132276 -0.05856793964 -0.008116933705 -0.009676266086 -0.06914423615 -0.01503783708 -0.008579063342 -0.05596021705 -0.006619909699 -0.008921886058 -0.06482584683 -0.01934456468 -0.009096315992 0.04204102618 0.01160160007 -0.001243446183 0.04418645785 0.02362804039 0.02923820731 -0.05857134399 -0.03571304629 -0.002887048581 -0.0754397367 -0.02372546511 -0.007453359386 -0.07962267368 -0.03389720166 -0.004418211153 -0.08432068557 -0.0330810779 0.02811757561 0.01166375995 -0.04552155405 0.02608452271 0.01702714733 -0.05039300674 -0.004886597434 -0.08350444289 -0.02345986511 0.02169112298 0.04075759437 -0.009406391461 0.03388216058 0.008237899224 -0.05888296292 0.002132409231 -0.08893284272 -0.05841890011 0.02946293638 -0.05685698454 -0.01696938881 -0.01305732701 -0.0157875938 0.007581932394 0.005269242578 0.02758668777 -0.01804179941 0.03118551162 0.03798029219 0.02305685213 -0.001595891186 0.07680675732 0.060244239 0.007605483109 0.06676323908 0.06410015977 0.01226952149 0.06313109224 0.05174176455 0.002168516904 0.0378257627 0.04619511973 -0.001847705929 0.04890833961 0.05690814405 0.01822885995 0.02472848353 -0.01765149387 0.04227347687 -0.04626460744 0.01045022104 -0.0125827108 -0.06033001875 0.005882403099 0.03271534573 -0.07082661945 0.01008829488 -0.007930641285 -0.03393843478 0.01695082313 0.003929964985 -0.0747043599 0.01742741436 -0.0004563179936 -0.07386146954 0.00329895685 0.009455168205 -0.0213275606 0.03132971345 -0.003094427367 -0.06889170807 0.004888854831 -0.0002324813022 0.04324301091 -0.003959822551 -0.008930419313 0.03645375466 -0.003607792147 -0.00219576075 0.03672636003 -0.01519672412 -0.004890876841 0.08032221011 0.04169445033 0.03204520536 0.01216969195 0.007172154793 0.0334482328 0.01894850435 0.006412574084 -0.01053739535 -0.00121782329 0.007474316827 -0.009658087064 -0.02323201166 0.01729006577 -0.01398463733 0.06392721831 0.06528033564 -0.003849456001 0.02659364606 -0.06127373877 -0.004525240387 0.032610241 -0.05552797955 -7.321253012e-05 -0.08671894842 -0.04651054113 0.01524840974 -0.07077468336 0.01566478909 0.02910546993 0.02580914068 -0.03501947875 -0.003027918867 0.0274712287 -0.05450942447 0.02660446376 -0.07178426534 0.02078314662 0.03089218932 -0.06842592557 0.0254113444 -0.007285789227 -0.06987799181 -0.006666778386 -0.009342702668 0.02821069851 -0.01160202414 0.008309789168 -0.07466218109 0.01244918111 0.03449252159 -0.02526268162 0.02567523323 0.02693937743 0.01503796125 -0.06642532575 0.0305308809 0.01669467749 -0.06497438481 -0.001842715329 0.06835726867 0.07180223722 0.02764793923 -0.02139899706 0.02973917638 0.03400049703 0.0256575615 0.01400341786 0.02131985423 0.02976380702 -0.0168021812 -0.00324064901 -0.04495672781 0.03258885092 -0.004602734265 -0.08184784244 -0.06468988447 -0.005672196731 -0.07565345108 -0.07601500342 0.02925991119 0.02479922737 -0.02520777166 0.03269591498 0.0277623861 -0.01367485415 -4.654141262e-05 0.03928648009 -0.06696839047 -0.002767040698 0.0356435873 -0.0534366966 -0.0006501128157 -0.07325849781 -0.007626583867 0.03786301243 -0.04770031706 -0.007155020039 0.03928093361 -0.05036233226 -0.01652680834 -0.01159532294 -0.04432251675 0.000424691754 -0.005770502646 0.04434374064 0.004006854425 0.03409200069 0.01012577627 -0.07298442924 0.02693396209 0.01067269833 -0.07605978988 0.01525639847 0.05852779157 0.03919665467 0.01860558345 0.05440443651 0.04216043825 0.0215432062 0.04824932889 0.02026185737 0.03272982977 0.02047535574 -0.03832779158 0.007739006328 0.04111411938 -0.006129204093 -0.008831888102 0.034482007 0.02187978353 0.02470204879 0.04321137839 0.02812018636 0.01009625484 -0.07142165998 0.002750268743 0.03771656929 -0.03253814824 0.01613668194 -0.02570503406 0.06997745916 0.06186892063 0.03434032889 0.02097979117 -0.006163903795 -0.006326888154 -0.07210550394 -0.02454785768 -0.01319774279 -0.04906725707 0.00385722078 0.03716209013 -0.05645697252 0.02963372594 0.03911499902 -0.06043184261 0.02293037278 -0.01230511982 0.02883591172 -0.0002496826006 -0.0003834515401 0.01828587539 -0.01794697368 0.009431580276 0.05582461812 0.06562219815 0.02815722799 -0.07116956276 -0.06035016397 -0.00260950101 0.0114412311 0.0241202205 -0.004563529073 0.03402878707 -0.06824679112 0.02623861077 0.01965492023 0.03060247466 0.002319258917 -0.07963579628 -0.07542058232 -0.00710983949 0.08707816298 0.07453090766 0.03573676564 -0.06371483034 -0.05173565457 0.003082377389 0.01686611051 -0.01117955432 -0.01647276147 0.08032673271 0.03979473134 -0.02855869872 0.07398775536 0.05116857685 -0.0003623580119 0.035165732 -0.07604654345 -0.008158632395 -0.08143711256 -0.07113356096 -0.004247150314 -0.07092102127 -0.02826231462 0.04198405446 -0.0587478605 0.01143895384 1 2 3 2 1 3 4 5 6 5 4 6 7 8 8 9 9 7 10 11 11 12 12 10 13 14 13 15 15 14 16 17 16 18 18 17 19 20 20 21 19 21 22 23 22 24 23 24 25 9 26 9 25 26 19 27 19 28 28 27 29 30 29 31 31 30 32 33 33 34 32 34 35 36 37 35 37 36 38 39 40 39 38 40 41 42 42 43 43 41 44 45 44 46 46 45 47 48 48 49 47 49 50 51 52 50 52 51 53 21 54 53 21 54 55 56 57 56 57 55 58 59 60 59 58 60 61 62 63 62 61 63 32 43 42 32 64 65 66 64 65 66 67 68 68 69 67 69 36 70 35 70 71 72 73 71 73 72 74 75 76 74 75 76 77 78 78 3 77 3 79 80 79 81 80 81 82 83 82 84 83 84 85 86 85 58 86 58 87 24 88 24 87 88 89 65 89 46 65 46 90 91 90 40 40 91 92 93 94 93 94 92 95 96 95 97 97 96 98 99 100 99 98 100 101 15 14 101 102 103 104 103 102 104 105 106 47 106 105 47 107 108 107 109 109 108 110 111 63 111 63 110 112 113 114 113 114 112 115 116 117 115 117 116 12 118 38 118 12 38 119 120 120 121 121 119 22 58 22 86 122 123 124 122 123 124 125 126 126 127 127 125 128 115 117 128 129 40 90 129 130 83 130 11 11 83 99 131 98 131 132 133 134 133 134 132 74 109 74 107 135 136 135 137 137 136 64 138 139 138 64 139 67 140 140 130 67 130 111 141 115 141 115 111 142 143 126 142 143 126 112 144 144 121 121 112 30 145 30 44 145 44 146 94 147 94 147 146 148 149 149 13 148 13 44 65 44 119 65 119 150 151 151 152 152 150 100 147 100 146 57 114 114 153 57 153 42 154 154 32 155 100 155 98 75 156 75 51 156 51 157 158 159 157 158 159 160 161 161 162 160 162 163 164 165 164 165 163 110 166 167 166 110 167 147 168 168 94 131 169 131 148 169 148 61 170 170 62 171 172 173 171 173 172 174 175 175 176 174 176 177 178 178 179 177 179 166 180 181 180 166 181 182 41 182 106 106 41 114 56 183 184 183 104 104 184 185 186 187 185 187 186 181 188 181 189 189 188 11 50 11 52 1 190 190 153 1 153 191 48 191 192 192 48 102 183 132 193 133 193 194 173 172 194 195 196 195 197 196 197 123 198 150 198 123 150 199 200 201 199 200 201 202 203 204 202 204 203 85 205 206 205 206 85 207 35 207 37 208 95 158 95 158 208 209 210 211 210 211 209 66 139 212 213 212 214 214 213 215 216 217 215 216 217 115 218 128 218 125 9 26 125 215 219 217 219 220 177 177 221 220 221 47 116 105 116 222 56 222 112 112 56 223 106 223 105 79 224 224 81 225 25 143 25 143 225 226 195 227 195 227 226 97 82 82 51 51 97 3 228 228 78 89 66 49 229 160 229 160 49 230 118 67 230 67 118 231 232 233 232 233 231 234 32 234 235 235 32 157 236 157 96 96 236 237 238 239 238 239 237 240 241 241 137 240 137 142 25 127 25 142 127 239 242 242 237 187 243 186 243 67 38 95 157 244 135 244 240 135 240 245 19 91 19 245 91 96 75 246 96 75 246 247 100 247 155 248 185 169 185 248 169 184 208 95 184 77 2 113 2 77 113 108 208 108 158 170 162 160 170 122 152 249 152 249 122 250 251 252 251 250 252 154 33 110 180 253 194 194 254 254 253 175 41 175 182 166 128 128 167 178 145 255 145 255 178 17 256 256 16 257 201 200 257 84 258 258 83 5 244 5 240 236 76 236 75 259 168 147 259 260 199 199 261 261 260 254 173 189 162 188 162 262 59 193 262 59 193 200 263 263 199 264 265 264 266 265 266 267 71 73 267 268 269 31 268 269 31 221 179 252 72 250 72 49 191 173 191 49 173 14 226 226 101 180 63 247 132 132 270 247 270 38 69 271 247 271 132 76 107 181 267 188 267 270 134 148 134 270 148 272 47 272 106 273 37 274 273 274 37 102 138 256 138 256 102 164 119 164 121 240 221 241 221 246 97 39 275 69 39 69 275 139 156 66 156 258 276 276 83 174 202 202 176 179 186 186 277 179 277 278 80 274 80 278 274 279 280 279 258 280 258 5 220 240 220 200 232 281 232 200 281 176 235 41 176 235 41 180 189 81 226 81 227 113 1 265 224 224 282 282 265 113 249 77 249 157 225 225 236 276 283 276 23 23 283 89 92 89 93 144 165 222 144 222 165 45 169 131 45 184 84 258 184 198 151 59 216 262 216 163 64 163 138 54 91 90 54 55 284 284 222 55 222 285 241 241 286 285 286 210 193 210 59 104 258 29 124 29 220 220 124 286 214 6 286 214 6 72 33 71 33 103 258 229 287 287 251 229 251 160 61 57 216 216 288 57 288 223 128 223 117 113 122 113 289 289 122 179 290 290 186 291 59 291 60 292 90 293 90 292 293 30 220 220 145 28 69 275 28 186 294 186 295 295 294 184 108 37 296 297 37 297 296 281 261 281 298 298 261 139 299 300 299 139 300 139 75 139 74 42 223 223 41 20 129 20 27 27 129 167 111 167 141 298 231 231 281 121 165 94 10 10 301 301 94 73 250 302 264 302 32 264 32 99 45 133 219 133 217 303 304 304 213 213 303 207 70 272 48 191 272 305 306 306 307 307 305 129 292 20 292 220 294 220 308 294 308 263 261 188 250 267 250 309 187 309 243 228 252 78 252 190 194 172 190 81 297 80 297 82 50 50 83 36 296 296 70 310 176 234 176 310 234 309 198 243 198 147 247 311 77 249 311 298 312 298 67 312 67 173 272 138 313 313 256 306 252 306 78 11 230 118 11 9 126 289 112 116 314 115 314 172 3 171 3 299 102 138 299 120 30 120 44 29 315 315 31 316 237 238 316 200 301 200 317 317 301 318 319 318 280 319 280 205 288 284 205 284 288 88 60 22 88 60 22 265 320 282 320 34 151 198 34 49 287 169 321 248 321 242 68 68 237 251 322 322 287 123 295 186 123 241 4 4 286 127 26 155 270 298 283 261 283 155 148 305 33 33 252 305 252 8 323 8 159 323 159 15 278 278 101 164 64 71 128 71 223 1 172 179 255 255 290 174 203 78 311 306 311 40 245 322 171 322 3 109 300 300 183 109 183 130 312 83 312 203 324 219 324 203 219 154 71 276 312 325 207 37 325 277 308 294 277 5 308 308 4 34 305 134 204 134 324 324 204 121 31 120 31 204 310 202 310 136 304 136 303 174 194 190 174 320 204 320 134 153 326 1 326 23 260 283 260 113 326 288 206 45 145 291 87 291 209 209 87 124 294 232 10 12 232 254 182 254 272 272 182 317 211 211 301 327 90 293 327 229 250 328 250 328 229 197 273 273 325 325 197 25 7 25 329 329 7 280 103 212 304 297 196 296 196 119 64 190 203 329 158 329 108 220 178 122 150 46 99 76 329 76 108 69 239 69 242 215 57 21 292 330 169 330 321 58 206 162 250 251 228 322 228 52 156 121 268 268 112 331 128 166 331 80 15 102 17 266 187 266 309 239 28 17 319 318 17 81 248 226 248 282 149 282 13 332 61 332 314 314 61 231 68 231 316 316 68 193 217 318 86 85 318 105 117 280 23 280 86 86 23 68 298 92 146 89 52 52 93 234 265 234 320 205 18 18 85 70 333 70 197 197 333 266 81 81 265 88 291 34 309 282 79 79 13 313 16 148 330 261 200 93 10 11 93 115 332 21 334 292 334 162 328 314 160 59 206 320 310 262 217 47 314 325 333 18 318 316 335 316 233 233 335 98 148 235 43 136 6 303 6 241 179 40 275 330 13 321 13 313 284 313 222 174 182 159 225 225 323 167 218 218 141 260 24 312 283 185 266 266 248 102 336 336 17 327 54 327 53 165 313 227 297 152 311 285 214 136 285 285 304 315 124 184 97 195 273 232 301 133 324 174 253 182 253 181 331 276 279 279 23 185 255 185 290 277 4 4 179 333 207 238 129 238 335 129 335 233 12 170 189 14 321 193 259 271 193 259 271 52 66 28 238 219 190 223 154 45 185 198 186 46 92 311 307 225 329 331 71 267 331 307 152 269 122 269 315 122 315 211 257 257 209 313 163 210 291 137 285 199 24 24 201 14 248 6 244 269 289 34 307 151 307 184 109 309 32 61 111 247 259 161 328 161 229 236 329 335 40 158 8 201 87 215 153 195 278 195 101 216 206 332 111 49 192 334 293 53 293 334 53 335 12 15 79 201 209 301 168 278 273 91 21 136 244 180 62 213 6 302 234 265 302 296 197 284 16 16 205 140 11 140 230 55 288 184 82 323 9 97 75 274 297 255 45 314 49 336 103 319 336 319 103 326 114 9 143 143 323 300 74 158 7 317 257 211 193 124 295 335 38 62 189 299 183 245 275 275 19 269 112 211 168 171 49 238 27 320 149 134 149 264 309 227 196 259 211 146 99 171 287 153 219 285 212 146 46 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 44 68 69 70 71 72 73 74 75 76 37 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 15 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 94 139 140 141 142 143 144 145 146 147 148 131 149 102 150 151 152 153 154 110 155 156 157 158 159 122 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 184 196 197 198 199 200 68 112 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 183 219 220 221 222 223 224 64 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 196 59 241 242 243 244 245 246 247 248 249 53 250 251 252 253 254 255 256 257 242 258 117 259 260 156 261 262 227 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 38 280 281 282 283 284 285 166 286 70 287 288 289 290 291 292 293 294 147 26 295 296 291 297 298 299 300 301 302 303 120 304 305 306 307 118 308 87 309 310 311 312 313 314 315 316 317 318 319 320 84 321 71 322 98 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 340 347 348 349 350 246 351 134 328 206 281 352 353 354 355 356 357 358 359 360 361 201 362 363 364 365 366 280 367 368 369 370 371 372 282 373 374 375 211 376 377 378 379 380 381 34 200 382 215 383 234 384 385 386 237 387 388 389 390 216 391 392 393 16 394 395 396 397 271 91 398 399 400 401 354 82 402 403 404 405 218 406 407 408 409 385 261 410 411 249 412 413 414 415 416 269 417 418 419 78 420 421 422 423 424 233 425 300 426 427 381 428 429 430 431 114 432 383 126 433 434 435 436 437 351 74 438 439 434 440 81 160 441 247 442 443 444 445 446 119 447 448 449 450 451 452 453 454 138 455 341 456 457 458 360 109 459 460 461 286 462 463 443 435 158 464 465 399 466 230 467 468 469 470 471 472 473 474 475 476 477 478 401 479 480 481 482 483 484 248 235 485 486 316 487 1 488 370 489 490 491 492 493 371 460 40 437 494 335 495 496 497 498 499 500 104 501 502 503 504 505 220 398 506 507 508 191 267 509 510 412 164 511 512 101 513 514 515 516 517 176 305 502 518 519 520 414 521 522 243 523 507 524 525 526 527 528 529 77 530 531 523 116 532 533 534 535 223 536 375 537 538 539 540 541 148 542 543 544 468 545 546 62 547 548 549 550 551 552 179 553 461 554 555 369 85 2 556 557 558 372 559 367 560 561 562 563 564 565 566 567 568 569 570 80 571 43 572 573 574 575 576 577 170 578 564 579 455 580 213 581 582 583 584 427 79 585 586 587 505 588 154 298 589 590 591 592 593 594 278 76 255 595 596 597 598 599 600 601 573 602 603 604 605 407 416 442 606 607 349 608 609 610 321 611 262 612 613 614 615 88 616 89 617 75 618 619 620 621 622 609 623 624 194 625 362 626 493 627 628 629 630 596 631 429 632 633 452 634 611 635 636 326 637 144 295 638 639 543 127 387 483 229 640 641 130 642 643 225 644 645 451 180 646 647 648 649 32 338 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 491 665 666 508 667 668 533 323 366 669 670 348 671 672 673 534 674 675 557 676 677 678 519 679 296 146 359 403 337 363 436 680 565 681 682 445 683 680 684 685 686 687 688 689 690 691 113 212 511 692 540 693 694 642 695 3 696 697 545 272 698 466 635 699 700 685 530 426 103 701 358 702 643 703 704 705 706 707 151 708 709 710 711 382 712 531 465 713 708 279 714 715 604 716 717 718 4 719 569 203 462 684 720 35 721 722 723 724 137 725 189 647 136 726 273 727 728 591 729 612 730 731 721 732 733 734 735 254 736 737 498 738 488 735 658 739 276 740 181 46 741 742 743 602 744 526 745 12 746 747 748 749 654 750 751 550 752 753 754 755 756 757 758 759 760 761 762 532 763 476 592 764 287 580 177 501 765 766 562 767 69 190 731 698 768 769 770 373 771 231 299 376 772 193 48 773 588 774 775 770 776 777 347 537 778 290 601 779 20 333 621 482 669 780 781 93 277 782 83 626 699 606 411 783 784 673 785 205 54 786 787 788 178 695 613 252 6 528 678 789 389 790 690 791 471 453 394 792 793 794 608 554 776 795 796 797 655 798 799 486 800 801 186 802 803 804 73 671 777 355 343 162 805 806 807 260 808 590 809 810 92 811 541 308 812 813 814 656 809 813 121 775 440 805 815 578 630 169 707 816 106 182 500 817 818 549 600 150 819 664 820 821 275 822 823 824 825 826 827 419 828 741 97 623 829 667 801 830 831 633 832 395 833 780 222 605 415 834 447 238 749 835 836 10 641 803 837 107 336 352 838 779 839 783 840 755 140 22 814 804 841 536 782 842 61 622 820 843 723 709 274 844 808 413 467 620 727 640 302 845 759 846 825 18 847 797 848 849 850 851 221 155 852 484 45 47 188 100 853 729 854 855 457 425 856 41 459 232 393 696 857 858 781 859 860 516 861 388 228 862 863 689 577 864 865 736 24 866 867 713 496 868 364 869 870 792 871 726 843 732 872 56 873 862 207 494 874 503 860 785 703 320 487 875 614 627 377 876 877 520 527 878 879 728 857 833 187 880 648 524 108 368 881 757 264 882 883 479 652 710 884 589 309 830 490 861 885 886 492 542 378 887 790 236 888 889 497 697 890 891 892 893 470 582 745 883 823 894 594 895 896 897 898 330 746 724 423 787 410 899 374 25 760 9 858 13 900 566 165 645 901 902 903 716 719 892 463 786 904 339 905 795 767 454 692 448 715 758 906 768 711 907 694 712 365 908 504 909 350 624 894 846 714 99 499 910 700 911 598 912 761 311 420 913 914 915 911 876 916 917 918 919 920 285 312 344 173 874 921 214 922 547 522 610 380 784 923 342 518 629 815 72 725 646 33 259 439 902 914 887 441 270 924 925 926 670 900 552 525 31 872 752 514 927 5 400 307 572 239 544 928 916 666 929 930 628 867 681 283 742 922 931 706 241 932 36 829 125 933 66 934 625 405 756 935 936 937 912 495 402 774 937 149 897 938 208 688 939 924 406 866 49 446 595 851 202 683 294 864 390 885 730 384 925 95 940 198 941 778 810 847 822 942 943 691 842 944 509 802 933 945 923 878 163 946 256 428 947 948 949 293 171 865 28 574 19 662 96 23 950 850 898 791 951 86 157 884 722 634 597 686 396 952 920 915 192 930 583 953 219 473 449 954 882 942 954 955 57 513 835 581 105 927 956 853 433 65 957 854 593 958 819 959 960 529 958 289 139 63 663 961 766 265 962 659 963 636 964 965 515 966 660 965 167 327 607 584 421 881 967 317 687 968 8 458 969 361 123 931 559 952 940 743 834 481 563 896 651 848 599 929 720 615 970 472 831 951 14 971 392 740 152 168 964 841 972 325 793 245 868 973 974 975 477 718 603 128 738 976 949 55 838 977 968 978 332 586 959 199 571 907 979 570 568 133 11 637 939 7 980 560 618 39 551 947 839 750 981 919 971 908 890 657 763 975 268 772 141 638 977 175 318 616 52 982 284 521 143 983 675 984 950 135 485 957 985 986 705 567 926 431 799 356 987 988 161 956 353 989 928 639 953 751 990 934 903 438 555 29 988 948 753 873 209 324 936 430 226 991 818 251 836 30 905 992 538 944 739 197 976 734 676 909 266 304 58 517 993 994 733 832 859 962 855 893 677 789 913 693 159 979 704 794 418 995 938 984 42 319 204 969 973 115 870 996 315 263 821 17 963 444 994 185 769 980 762 970 561 450 997 982 901 195 111 998 935 840 210 904 817 322 812 889 474 981 653 397 888 464 475 967 506 90 869 798 826 943 314 432 456 478 301 224 899 985 991 999 668 856 987 701 303 132 811 510 292 844 489 827 310 993 665 800 257 946 50 941 1000 297 744 558 983 788 422 989 145 174 346 240 306 129 546 891 244 60 539 966 895 575 992 124 217 576 357 21 955 386 747 886 674 999 702 253 906 1000 288 1001 877 773 1002 998 51 972 845 1002 910 816 345 27 679 408 682 737 617 153 250 945 172 837 771 553 391 961 824 619 807 650 672 512 921 632 556 717 469 548 828 661 313 978 863 631 748 409 379 754 535 871 796 974 331 849 806 334 852 67 1001 764 879 997 404 990 986 644 258 480 329 579 875 996 765 995 587 932 417 960 585 424 649 917 918 880 142 trunk-2018.02b/examples/polyhedra/horse.ele000066400000000000000000001154121324306050200206060ustar00rootroot000000000000001988 4 0 1 542 522 691 558 2 608 587 641 609 3 664 510 317 416 4 85 695 340 302 5 501 652 682 667 6 453 235 471 452 7 686 691 81 668 8 672 388 389 671 9 691 682 667 668 10 363 644 645 658 11 609 624 587 641 12 621 391 165 622 13 695 340 334 86 14 639 589 638 605 15 293 625 656 292 16 292 625 656 640 17 615 391 89 691 18 639 637 641 638 19 340 53 86 84 20 599 606 641 649 21 117 658 283 150 22 624 587 641 648 23 590 608 641 602 24 695 614 395 334 25 655 508 663 644 26 519 468 688 382 27 36 339 440 223 28 607 609 623 604 29 639 640 623 642 30 289 288 291 48 31 482 673 632 74 32 647 616 676 627 33 134 628 133 647 34 388 32 671 634 35 702 486 650 536 36 34 18 662 64 37 671 389 358 32 38 386 436 435 473 39 409 410 632 467 40 671 32 388 389 41 585 650 312 263 42 535 638 643 635 43 580 41 662 64 44 269 645 449 362 45 674 618 627 675 46 651 687 633 561 47 117 658 150 336 48 141 225 109 207 49 235 610 515 438 50 4 3 250 21 51 478 565 493 661 52 618 463 505 517 53 386 436 473 212 54 3 575 21 4 55 631 184 525 687 56 702 650 486 585 57 386 443 435 472 58 615 217 663 660 59 282 211 472 448 60 618 675 661 627 61 520 519 688 679 62 259 258 529 309 63 61 143 142 404 64 631 496 184 687 65 519 520 383 679 66 652 541 558 667 67 686 485 82 81 68 664 654 652 653 69 691 667 652 558 70 690 553 653 399 71 683 684 688 634 72 619 397 690 676 73 410 628 626 427 74 444 232 152 151 75 651 256 634 39 76 334 695 276 333 77 690 553 399 398 78 702 486 536 158 79 305 63 459 306 80 538 559 633 561 81 249 162 161 1 82 157 111 580 41 83 63 256 305 459 84 482 673 74 530 85 607 593 604 623 86 264 699 169 328 87 63 225 207 109 88 521 542 691 621 89 314 674 513 548 90 629 404 450 405 91 687 538 633 561 92 629 554 450 447 93 690 483 70 676 94 303 218 219 456 95 553 542 523 621 96 701 467 308 632 97 559 554 633 561 98 328 104 264 699 99 207 108 307 109 100 615 148 622 660 101 691 393 88 668 102 695 333 334 395 103 693 666 669 668 104 482 701 632 433 105 482 701 433 529 106 308 467 701 466 107 278 201 231 470 108 36 339 223 253 109 85 344 218 267 110 482 257 308 258 111 528 675 661 618 112 475 503 569 686 113 691 667 558 522 114 312 531 262 704 115 56 57 55 481 116 148 147 659 283 117 270 644 363 508 118 674 627 626 675 119 328 104 699 229 120 476 702 584 700 121 619 166 621 523 122 386 443 455 423 123 476 12 66 260 124 14 615 89 79 125 328 699 230 229 126 503 569 686 570 127 427 632 626 410 128 558 553 652 691 129 408 144 372 629 130 610 235 515 471 131 223 253 339 127 132 650 545 57 481 133 650 545 481 556 134 79 654 80 484 135 57 56 650 481 136 701 700 309 353 137 595 607 601 640 138 157 41 64 65 139 575 157 64 65 140 287 50 329 319 141 229 441 104 699 142 57 480 54 55 143 675 568 661 676 144 5 575 65 417 145 507 499 643 535 146 335 655 533 76 147 338 103 339 101 148 700 310 309 353 149 628 648 132 646 150 335 76 656 655 151 654 697 532 484 152 16 117 283 150 153 342 287 329 319 154 296 188 656 335 155 244 463 618 517 156 155 213 330 154 157 330 213 90 154 158 149 16 283 150 159 187 663 217 186 160 654 80 691 89 161 691 654 653 652 162 689 647 209 591 163 431 524 305 110 164 615 217 660 514 165 615 217 514 78 166 255 115 117 116 167 568 567 483 676 168 700 529 701 309 169 615 217 78 663 170 443 423 435 469 171 615 78 514 79 172 390 155 330 153 173 391 621 615 622 174 477 615 14 78 175 46 47 319 50 176 408 629 372 633 177 615 79 654 89 178 199 631 184 525 179 408 144 629 145 180 628 133 461 427 181 125 46 295 462 182 658 255 363 336 183 617 634 684 683 184 57 515 614 549 185 246 216 217 186 186 363 644 658 508 187 246 656 186 217 188 660 642 217 663 189 199 680 631 525 190 656 76 335 188 191 672 494 678 516 192 210 212 473 436 193 134 628 647 409 194 702 650 490 536 195 279 671 19 280 196 585 650 486 312 197 346 204 163 318 198 695 276 333 220 199 695 220 333 395 200 486 312 650 421 201 531 702 11 10 202 486 531 10 702 203 619 568 397 676 204 285 98 99 173 205 518 432 632 626 206 518 673 626 632 207 513 675 618 674 208 269 643 627 644 209 585 486 702 531 210 691 667 522 668 211 651 256 39 305 212 574 519 651 520 213 35 126 128 36 214 111 157 417 65 215 153 90 154 330 216 24 60 137 28 217 681 41 670 693 218 611 612 515 396 219 36 339 338 103 220 60 58 137 28 221 90 213 330 92 222 497 496 573 687 223 155 411 412 390 224 651 431 524 305 225 269 627 500 550 226 694 113 82 39 227 467 701 466 429 228 553 691 653 652 229 442 324 73 698 230 411 155 330 390 231 475 694 82 39 232 462 246 247 296 233 223 253 126 36 234 285 98 173 3 235 14 615 477 13 236 151 16 232 149 237 3 2 98 575 238 48 289 139 138 239 564 654 514 484 240 42 645 241 43 241 637 641 624 623 242 47 288 320 290 243 97 96 418 98 244 667 534 274 273 245 631 630 688 684 246 48 321 138 49 247 90 300 231 91 248 284 2 99 285 249 215 656 316 415 250 610 387 471 515 251 342 295 329 341 252 2 418 96 98 253 633 543 525 560 254 620 416 689 664 255 694 684 634 688 256 700 529 309 476 257 195 689 240 208 258 114 232 444 151 259 632 409 673 626 260 2 418 98 575 261 675 565 661 568 262 260 702 584 476 263 641 639 649 623 264 655 644 663 642 265 323 531 72 73 266 133 132 646 628 267 553 399 555 652 268 260 702 476 12 269 479 45 548 618 270 295 247 329 341 271 247 295 296 341 272 433 701 632 360 273 189 124 625 504 274 511 701 434 160 275 700 309 310 476 276 512 351 352 665 277 453 235 452 129 278 80 654 79 89 279 500 628 492 226 280 342 295 319 329 281 654 564 514 510 282 500 192 193 648 283 700 529 476 511 284 700 160 506 701 285 637 648 638 643 286 579 665 275 352 287 112 380 693 87 288 222 221 107 338 289 665 274 275 352 290 700 379 311 310 291 639 605 638 641 292 157 156 64 580 293 531 323 72 262 294 491 702 476 700 295 702 263 650 585 296 334 57 53 614 297 701 309 308 353 298 586 657 120 121 299 639 589 605 596 300 699 490 263 650 301 476 310 260 66 302 605 641 648 638 303 321 289 291 48 304 441 264 104 699 305 659 42 645 241 306 690 483 676 397 307 645 528 618 43 308 648 641 605 590 309 114 150 151 464 310 169 699 230 328 311 464 254 114 150 312 675 513 557 547 313 399 553 653 652 314 609 623 624 641 315 658 255 336 117 316 639 605 641 597 317 430 519 651 574 318 383 540 679 384 319 512 665 352 273 320 331 241 363 242 321 101 378 339 338 322 575 156 64 157 323 453 235 129 610 324 637 623 636 642 325 400 401 418 97 326 49 26 137 177 327 627 550 517 674 328 49 26 177 321 329 49 26 321 138 330 457 695 302 612 331 49 26 138 137 332 644 658 508 663 333 146 148 622 391 334 575 5 6 418 335 401 575 418 417 336 331 255 117 116 337 531 312 703 704 338 254 255 115 336 339 254 464 336 150 340 587 648 132 193 341 115 255 117 336 342 464 114 444 151 343 188 656 76 186 344 464 444 152 151 345 115 150 114 254 346 363 659 331 658 347 551 629 561 651 348 508 644 533 507 349 657 595 601 640 350 585 311 263 312 351 61 375 374 238 352 511 700 701 160 353 701 308 309 529 354 299 171 614 612 355 36 338 339 253 356 614 515 396 549 357 683 631 687 688 358 612 387 515 614 359 53 340 614 83 360 672 350 494 516 361 629 677 633 634 362 189 586 625 119 363 614 302 612 171 364 674 314 513 626 365 387 57 614 105 366 501 680 665 682 367 75 689 70 676 368 604 593 594 623 369 558 541 652 555 370 663 148 660 658 371 694 682 686 668 372 382 540 503 686 373 633 692 678 560 374 62 207 141 377 375 541 555 22 652 376 194 23 652 22 377 522 667 558 534 378 635 637 642 639 379 616 646 647 636 380 102 126 36 440 381 372 629 374 371 382 123 597 649 602 383 552 563 572 686 384 193 648 132 492 385 40 681 694 693 386 231 91 278 470 387 578 617 351 350 388 288 320 291 48 389 278 91 231 300 390 389 388 672 537 391 681 41 693 40 392 695 614 334 340 393 511 700 160 491 394 529 258 259 67 395 272 9 163 318 396 562 494 516 678 397 346 30 163 31 398 623 601 649 606 399 255 331 242 116 400 645 243 363 241 401 149 615 13 477 402 656 316 415 642 403 42 478 645 43 404 151 16 149 150 405 245 244 449 517 406 658 255 117 331 407 640 656 415 642 408 375 61 374 143 409 699 490 650 168 410 663 148 658 149 411 149 615 477 663 412 40 681 662 634 413 197 194 527 652 414 57 515 549 545 415 682 501 198 197 416 630 685 686 682 417 246 247 215 216 418 607 640 593 623 419 674 618 505 517 420 627 628 500 226 421 226 628 626 627 422 428 701 632 467 423 540 552 686 679 424 122 596 121 657 425 701 699 506 700 426 574 551 561 651 427 445 651 446 524 428 108 221 109 207 429 433 701 360 434 430 388 236 61 238 431 39 694 40 634 432 617 671 634 677 433 551 446 651 524 434 371 388 537 677 435 370 233 99 284 436 379 700 354 353 437 251 1 349 348 438 36 35 102 103 439 404 62 629 651 440 651 404 403 446 441 657 367 120 121 442 682 668 669 667 443 573 538 687 561 444 35 126 36 102 445 399 690 398 454 446 586 596 657 121 447 31 205 30 248 448 47 288 290 287 449 488 622 659 661 450 658 148 659 283 451 619 568 676 661 452 482 701 529 308 453 658 622 148 660 454 36 338 37 103 455 263 700 354 379 456 616 622 661 659 457 166 619 493 539 458 668 693 694 669 459 56 487 650 481 460 508 658 363 336 461 188 656 186 246 462 576 579 581 670 463 639 586 657 625 464 190 638 535 635 465 666 693 670 380 466 41 576 662 670 467 694 668 686 82 468 600 639 638 635 469 691 682 668 686 470 41 157 64 580 471 693 113 112 87 472 40 681 634 694 473 113 693 112 40 474 276 303 344 219 475 662 280 281 671 476 651 683 687 688 477 292 625 640 367 478 640 367 625 657 479 640 595 414 292 480 331 255 242 363 481 629 388 634 236 482 313 674 550 626 483 475 694 38 686 484 633 692 560 525 485 634 617 677 683 486 675 565 568 571 487 657 640 601 649 488 625 498 504 655 489 647 416 689 636 490 508 658 150 663 491 199 680 502 198 492 61 62 142 381 493 650 105 387 106 494 630 685 682 631 495 630 682 686 694 496 114 232 16 402 497 682 684 631 680 498 102 126 440 224 499 637 648 643 646 500 327 93 174 411 501 628 673 627 647 502 691 697 654 652 503 17 156 281 64 504 683 631 688 684 505 368 119 625 120 506 685 198 197 682 507 644 645 616 627 508 255 336 271 363 509 623 601 606 607 510 698 703 526 453 511 574 430 524 651 512 504 498 625 189 513 133 624 646 132 514 659 117 331 658 515 656 296 215 246 516 665 617 670 669 517 391 89 691 392 518 501 652 667 541 519 207 206 306 307 520 102 224 95 35 521 124 189 625 119 522 563 509 544 686 523 562 566 678 560 524 161 249 284 233 525 222 223 339 127 526 328 264 53 170 527 298 135 131 136 528 702 160 700 491 529 372 677 495 678 530 617 581 670 662 531 645 243 362 363 532 633 566 678 373 533 655 508 644 533 534 672 617 692 677 535 597 641 649 602 536 658 255 331 363 537 660 316 642 636 538 639 586 625 600 539 277 267 345 301 540 324 610 299 453 541 102 95 224 440 542 637 648 646 624 543 629 388 374 371 544 142 141 62 403 545 388 629 374 61 546 379 310 700 353 547 404 62 651 403 548 144 143 629 405 549 248 249 162 29 550 629 143 144 374 551 424 126 35 224 552 460 671 20 577 553 693 681 669 670 554 629 551 561 447 555 376 62 381 142 556 554 629 145 633 557 287 47 50 319 558 694 630 684 688 559 46 50 319 295 560 107 206 108 307 561 577 671 20 279 562 278 91 300 153 563 693 113 87 668 564 36 37 338 253 565 254 115 150 336 566 221 141 109 207 567 73 703 531 526 568 683 617 677 692 569 29 248 30 162 570 628 465 461 492 571 387 545 451 471 572 453 703 421 297 573 681 41 40 662 574 579 665 670 275 575 520 496 384 679 576 203 204 318 272 577 553 542 691 558 578 619 616 622 661 579 9 7 164 318 580 682 501 667 665 581 141 207 62 225 582 26 177 27 137 583 496 631 679 687 584 326 474 93 214 585 231 201 278 202 586 644 362 645 269 587 630 685 631 679 588 47 288 287 139 589 213 93 214 327 590 201 200 470 278 591 614 612 395 396 592 385 631 685 679 593 176 696 325 174 594 614 302 171 340 595 643 627 646 628 596 522 521 691 392 597 167 169 170 650 598 332 334 396 549 599 699 700 490 506 600 490 650 702 263 601 700 160 490 506 602 176 696 174 175 603 334 332 396 395 604 129 324 131 442 605 531 703 486 526 606 27 177 28 137 607 71 585 262 261 608 698 703 453 324 609 656 215 316 217 610 638 499 643 648 611 425 126 35 424 612 35 126 425 128 613 457 695 612 220 614 554 629 561 447 615 452 364 234 235 616 520 687 679 688 617 674 627 550 626 618 695 614 612 395 619 304 425 426 469 620 432 433 482 632 621 490 702 700 263 622 399 690 454 23 623 128 35 469 425 624 320 321 291 48 625 101 100 378 338 626 685 509 686 527 627 107 127 338 253 628 660 642 663 644 629 86 340 84 85 630 295 294 335 296 631 215 656 414 292 632 533 507 635 535 633 58 59 137 49 634 644 507 635 533 635 544 664 527 196 636 664 195 527 196 637 53 340 334 614 638 509 182 527 685 639 225 445 524 62 640 686 82 475 694 641 399 555 652 22 642 57 480 55 481 643 219 218 303 344 644 225 63 110 109 645 57 515 545 387 646 38 39 468 688 647 194 664 527 652 648 664 194 527 195 649 305 63 306 110 650 541 534 558 667 651 192 499 638 648 652 134 647 591 209 653 690 553 398 523 654 673 409 632 74 655 23 399 652 22 656 501 652 541 22 657 685 197 182 527 658 618 674 627 517 659 618 44 479 45 660 645 618 661 627 661 673 627 675 626 662 498 655 533 504 663 612 695 395 220 664 618 675 513 557 665 701 699 700 355 666 83 105 614 53 667 167 168 650 490 668 610 611 612 515 669 208 664 689 416 670 167 56 487 650 671 424 35 95 224 672 650 105 106 264 673 475 82 686 485 674 689 591 208 416 675 424 95 35 425 676 692 672 678 516 677 691 697 652 686 678 501 652 22 197 679 312 531 486 585 680 533 498 635 655 681 660 642 644 636 682 644 642 637 636 683 703 312 531 486 684 693 380 41 670 685 699 506 168 230 686 453 703 297 324 687 588 598 603 624 688 503 540 572 686 689 398 397 690 523 690 621 553 653 523 691 168 699 230 169 692 676 647 74 209 693 540 552 572 686 694 617 578 351 579 695 219 695 276 303 696 685 631 198 682 697 501 680 682 198 698 685 652 527 686 699 666 394 668 380 700 228 701 359 360 701 691 697 686 532 702 441 355 264 699 703 575 156 157 400 704 451 650 486 556 705 37 100 338 337 706 563 697 686 544 707 85 301 218 303 708 486 650 451 421 709 650 451 421 387 710 509 563 552 686 711 599 123 649 602 712 674 618 548 505 713 148 16 283 149 714 647 416 636 603 715 622 493 661 488 716 684 634 617 681 717 444 152 696 407 718 180 672 181 577 719 176 696 474 325 720 236 388 32 237 721 463 244 618 44 722 645 528 43 478 723 453 387 421 451 724 74 69 530 676 725 482 74 632 308 726 299 131 172 610 727 487 556 536 650 728 487 490 650 536 729 521 166 621 165 730 690 483 397 454 731 134 628 409 410 732 463 45 618 505 733 509 182 685 385 734 89 391 615 13 735 619 397 539 523 736 479 618 528 44 737 689 591 209 208 738 645 618 627 517 739 626 628 226 465 740 567 530 483 676 741 672 279 578 581 742 659 42 147 488 743 147 42 659 241 744 618 45 548 505 745 179 672 279 578 746 496 384 679 385 747 369 335 293 119 748 40 662 33 634 749 228 632 360 227 750 578 581 617 672 751 696 474 325 407 752 309 310 66 259 753 659 117 658 283 754 41 40 662 64 755 608 599 641 602 756 529 67 482 258 757 244 243 645 43 758 638 499 192 191 759 335 124 504 625 760 366 367 657 121 761 155 213 411 330 762 147 659 331 241 763 619 616 661 676 764 663 149 658 150 765 696 474 152 175 766 363 659 658 645 767 363 659 645 241 768 529 258 482 308 769 168 167 650 169 770 639 589 596 600 771 618 44 45 463 772 618 43 244 645 773 189 190 498 600 774 600 498 635 190 775 129 698 442 413 776 640 656 642 625 777 244 43 618 44 778 610 299 612 172 779 169 170 650 264 780 696 152 474 407 781 169 170 264 328 782 657 367 625 120 783 603 636 647 646 784 292 625 367 368 785 246 215 217 216 786 124 125 118 335 787 639 640 649 623 788 636 317 415 594 789 627 676 661 616 790 663 615 660 148 791 149 13 148 16 792 415 317 636 316 793 149 13 16 15 794 78 663 77 477 795 635 498 533 535 796 627 226 550 626 797 335 295 296 462 798 638 637 643 635 799 629 447 450 404 800 554 145 559 633 801 660 514 217 316 802 74 69 676 75 803 17 575 173 613 804 272 204 318 163 805 482 74 308 257 806 310 309 66 476 807 650 105 264 57 808 576 111 380 41 809 482 257 258 68 810 134 647 588 591 811 129 698 413 453 812 203 7 272 318 813 365 366 601 657 814 639 122 649 657 815 666 693 669 670 816 639 122 657 596 817 232 696 444 152 818 656 217 663 186 819 700 529 511 701 820 482 673 530 518 821 551 574 524 651 822 236 629 583 634 823 651 683 688 634 824 279 671 20 19 825 38 694 688 630 826 631 182 685 198 827 631 182 385 685 828 366 595 601 657 829 507 499 500 643 830 2 6 161 96 831 573 574 651 520 832 413 698 73 526 833 671 20 358 389 834 702 261 71 12 835 460 671 577 672 836 89 654 615 691 837 358 20 671 19 838 671 617 662 581 839 654 621 653 620 840 621 391 615 691 841 621 521 391 691 842 700 584 476 310 843 149 615 663 148 844 616 646 636 644 845 673 530 567 676 846 659 117 283 147 847 478 488 661 493 848 254 255 336 406 849 149 13 15 477 850 676 619 620 690 851 148 146 622 659 852 206 459 207 306 853 223 253 127 126 854 632 227 361 360 855 404 142 62 403 856 617 680 683 684 857 698 453 526 413 858 491 702 159 489 859 385 631 679 496 860 501 680 198 502 861 677 372 629 633 862 614 334 396 395 863 694 668 82 113 864 169 699 264 650 865 101 102 103 440 866 702 160 491 159 867 629 61 583 62 868 214 474 407 325 869 80 88 81 691 870 701 434 529 511 871 80 81 532 691 872 654 80 484 532 873 512 617 351 665 874 623 601 640 649 875 272 7 9 318 876 702 158 489 11 877 529 309 66 259 878 380 666 275 670 879 681 662 634 617 880 702 311 700 263 881 204 346 439 205 882 628 648 646 643 883 442 698 73 413 884 689 194 653 664 885 80 89 88 691 886 702 489 12 11 887 309 529 66 476 888 543 525 687 633 889 615 78 79 14 890 427 628 133 410 891 563 697 544 564 892 482 257 68 530 893 576 581 582 662 894 575 173 613 98 895 544 654 664 510 896 476 702 489 12 897 652 553 558 555 898 580 281 64 662 899 564 697 484 570 900 634 236 32 583 901 37 221 337 338 902 33 662 40 64 903 702 12 71 11 904 443 455 423 469 905 575 157 417 401 906 698 703 324 73 907 697 544 652 686 908 423 455 304 469 909 634 62 651 629 910 299 324 453 297 911 694 475 38 39 912 37 107 338 253 913 655 644 642 635 914 260 702 12 261 915 35 126 102 224 916 285 98 3 2 917 288 48 47 320 918 2 349 3 285 919 47 48 288 139 920 453 703 526 421 921 320 343 290 291 922 47 287 50 139 923 40 256 634 33 924 3 349 2 239 925 628 673 647 409 926 702 71 531 11 927 689 23 240 70 928 251 2 285 349 929 703 322 312 297 930 276 344 85 86 931 338 127 107 222 932 151 16 150 114 933 349 250 285 251 934 324 610 129 131 935 563 697 564 570 936 691 89 88 392 937 335 118 119 369 938 177 58 28 137 939 335 295 369 294 940 86 53 340 334 941 671 34 358 19 942 656 215 293 292 943 575 5 418 417 944 335 295 462 125 945 656 215 414 415 946 654 564 697 484 947 689 620 690 676 948 559 543 538 633 949 680 692 683 525 950 90 300 91 153 951 334 614 396 549 952 630 382 686 679 953 419 420 611 395 954 276 695 334 86 955 482 74 257 530 956 654 80 532 691 957 620 416 664 317 958 112 380 87 111 959 703 312 322 704 960 67 529 66 259 961 575 3 173 98 962 2 418 575 6 963 2 418 6 96 964 524 63 305 110 965 667 534 394 274 966 272 9 31 163 967 613 400 418 97 968 579 617 670 665 969 575 400 418 613 970 645 243 241 43 971 149 615 148 13 972 17 281 18 64 973 617 579 351 665 974 681 694 693 669 975 639 589 600 638 976 702 531 71 585 977 587 648 193 590 978 192 638 589 605 979 119 586 625 120 980 639 657 649 640 981 123 122 365 649 982 674 505 550 517 983 613 97 418 98 984 217 216 187 186 985 161 249 1 284 986 573 496 520 687 987 703 312 421 297 988 659 117 147 331 989 187 663 186 77 990 675 676 661 627 991 673 627 647 676 992 575 613 418 98 993 313 550 361 626 994 112 693 41 40 995 281 580 582 662 996 324 610 131 299 997 233 370 99 96 998 2 161 284 99 999 650 451 387 545 1000 161 2 96 99 1001 161 233 284 99 1002 129 324 698 453 1003 626 227 226 361 1004 673 675 547 626 1005 703 312 486 421 1006 304 386 455 423 1007 233 161 96 99 1008 629 236 583 61 1009 567 675 673 676 1010 437 57 480 54 1011 644 658 663 660 1012 404 629 143 405 1013 233 29 249 370 1014 630 382 679 688 1015 519 651 688 468 1016 634 62 629 583 1017 638 190 535 191 1018 641 599 649 602 1019 365 122 366 657 1020 115 16 150 117 1021 142 143 61 375 1022 62 141 142 376 1023 647 616 636 689 1024 671 18 34 19 1025 428 701 467 429 1026 674 313 550 505 1027 486 702 10 158 1028 164 9 8 7 1029 299 453 387 297 1030 667 274 666 665 1031 688 519 520 651 1032 666 667 394 274 1033 667 666 669 665 1034 624 603 636 594 1035 306 207 307 109 1036 251 1 348 284 1037 421 451 486 526 1038 175 402 696 174 1039 490 702 536 159 1040 387 299 614 612 1041 575 17 173 21 1042 444 402 174 696 1043 100 378 338 337 1044 639 605 597 596 1045 244 243 362 645 1046 299 387 453 610 1047 346 30 205 162 1048 692 516 678 560 1049 346 30 162 163 1050 671 18 281 662 1051 332 333 395 334 1052 277 267 344 345 1053 512 680 617 665 1054 400 575 418 401 1055 662 280 671 581 1056 651 683 634 633 1057 232 114 444 402 1058 624 646 636 603 1059 616 646 644 627 1060 667 669 666 668 1061 219 695 303 457 1062 453 421 526 451 1063 648 192 590 605 1064 178 321 26 289 1065 241 243 363 242 1066 270 269 644 507 1067 178 177 26 321 1068 644 269 270 362 1069 270 644 508 507 1070 540 382 383 679 1071 491 702 489 476 1072 270 644 362 363 1073 232 696 152 175 1074 295 50 319 329 1075 256 62 634 583 1076 584 310 260 476 1077 61 629 143 404 1078 56 57 650 170 1079 525 683 631 687 1080 703 486 526 421 1081 701 359 160 506 1082 247 295 462 296 1083 667 669 682 665 1084 453 452 471 451 1085 496 497 184 687 1086 689 664 653 620 1087 628 500 648 643 1088 267 344 268 85 1089 468 38 688 382 1090 636 616 620 689 1091 76 186 663 77 1092 513 618 557 479 1093 416 317 636 594 1094 325 407 444 696 1095 629 61 143 374 1096 325 444 174 696 1097 517 550 627 269 1098 691 682 686 652 1099 85 302 340 458 1100 208 664 416 196 1101 228 632 227 356 1102 168 699 169 650 1103 433 701 434 529 1104 660 644 616 636 1105 61 388 238 374 1106 466 263 700 354 1107 223 224 440 126 1108 367 368 625 120 1109 340 695 85 86 1110 586 657 625 120 1111 615 79 514 654 1112 206 221 108 207 1113 638 192 648 605 1114 311 379 700 263 1115 305 63 524 62 1116 221 377 337 141 1117 221 377 141 207 1118 101 103 339 440 1119 221 377 207 206 1120 531 703 323 704 1121 423 304 426 469 1122 377 376 62 381 1123 698 703 73 526 1124 672 181 460 537 1125 647 616 689 676 1126 663 149 150 77 1127 411 155 412 327 1128 176 696 175 474 1129 425 304 95 469 1130 647 416 603 591 1131 313 674 314 548 1132 478 488 42 659 1133 619 616 676 620 1134 702 160 159 490 1135 689 620 653 690 1136 457 219 456 303 1137 324 610 453 129 1138 487 556 650 481 1139 650 545 387 57 1140 73 703 324 323 1141 453 387 451 471 1142 648 500 628 492 1143 386 436 212 472 1144 357 356 632 428 1145 606 609 641 623 1146 628 465 492 226 1147 114 464 444 406 1148 254 464 114 406 1149 267 277 218 301 1150 488 622 165 146 1151 324 703 322 323 1152 658 616 660 644 1153 668 113 87 88 1154 622 493 488 165 1155 397 398 690 454 1156 535 507 635 643 1157 235 234 129 130 1158 615 148 13 391 1159 529 309 258 308 1160 324 322 703 297 1161 622 493 165 619 1162 699 700 355 263 1163 365 122 657 649 1164 113 82 88 668 1165 642 655 656 663 1166 587 624 609 598 1167 621 619 622 165 1168 8 164 265 52 1169 610 611 515 438 1170 166 619 621 165 1171 8 265 7 51 1172 689 240 208 209 1173 645 528 478 661 1174 702 311 263 585 1175 485 475 569 686 1176 208 664 196 195 1177 159 702 536 158 1178 326 325 174 176 1179 704 322 323 262 1180 655 335 533 504 1181 639 586 600 596 1182 639 586 596 657 1183 296 188 335 462 1184 695 303 302 85 1185 326 474 214 325 1186 473 435 386 422 1187 519 679 383 382 1188 326 474 325 176 1189 62 140 445 403 1190 664 510 416 196 1191 651 573 520 687 1192 510 664 544 196 1193 326 474 176 93 1194 675 568 676 567 1195 701 441 699 355 1196 386 436 472 435 1197 386 212 473 422 1198 503 686 572 570 1199 260 702 261 584 1200 620 616 636 660 1201 624 604 594 623 1202 654 79 514 484 1203 689 616 620 676 1204 96 2 98 99 1205 606 607 609 623 1206 701 441 355 356 1207 441 701 228 356 1208 296 188 462 246 1209 409 134 74 647 1210 134 133 588 647 1211 542 521 691 522 1212 544 654 510 564 1213 702 160 490 700 1214 346 31 163 439 1215 566 562 678 373 1216 204 346 163 439 1217 71 531 72 262 1218 441 230 699 229 1219 39 256 634 40 1220 157 111 41 65 1221 272 31 439 163 1222 586 189 625 600 1223 638 191 535 499 1224 508 150 76 663 1225 228 632 356 701 1226 492 628 132 461 1227 204 272 439 163 1228 366 122 121 657 1229 269 643 644 507 1230 4 3 349 250 1231 399 23 652 653 1232 697 532 484 570 1233 691 682 652 667 1234 166 542 521 621 1235 380 576 670 275 1236 635 637 639 638 1237 3 250 285 349 1238 388 672 537 677 1239 541 534 667 273 1240 233 249 284 370 1241 687 631 679 688 1242 647 689 209 676 1243 665 501 667 273 1244 269 643 507 500 1245 576 581 662 670 1246 501 665 512 273 1247 501 541 667 273 1248 415 623 636 594 1249 338 103 101 100 1250 499 648 500 643 1251 643 627 644 646 1252 251 2 1 284 1253 628 627 646 647 1254 285 98 2 99 1255 544 509 527 686 1256 702 261 584 585 1257 251 2 284 285 1258 3 173 285 21 1259 528 675 618 557 1260 645 644 269 627 1261 658 622 660 616 1262 651 683 633 687 1263 244 645 517 618 1264 189 498 625 600 1265 449 244 362 645 1266 686 563 572 570 1267 699 263 355 264 1268 133 628 646 647 1269 629 651 634 633 1270 134 628 410 133 1271 429 356 701 355 1272 68 530 257 69 1273 688 694 38 39 1274 430 431 524 651 1275 629 388 236 61 1276 546 566 560 633 1277 681 694 669 684 1278 269 643 500 627 1279 685 198 182 197 1280 50 46 462 295 1281 385 631 496 183 1282 502 525 560 692 1283 198 631 183 199 1284 639 637 642 623 1285 457 695 303 302 1286 617 671 662 634 1287 619 568 661 539 1288 656 296 335 293 1289 211 210 282 212 1290 441 230 229 228 1291 689 70 676 690 1292 656 296 293 215 1293 246 247 296 215 1294 111 576 580 41 1295 166 619 165 493 1296 614 302 340 695 1297 648 646 624 132 1298 327 411 174 412 1299 631 682 680 198 1300 628 133 132 461 1301 215 247 296 341 1302 369 295 335 118 1303 690 553 523 653 1304 179 672 578 350 1305 342 287 319 343 1306 512 680 665 501 1307 630 38 382 688 1308 69 70 483 676 1309 646 647 603 588 1310 514 317 660 654 1311 224 424 304 95 1312 654 510 514 317 1313 493 619 661 539 1314 654 664 317 620 1315 159 702 158 489 1316 94 455 95 304 1317 455 386 304 94 1318 75 689 676 209 1319 675 565 571 528 1320 664 654 317 510 1321 31 205 439 346 1322 409 628 626 410 1323 703 704 322 323 1324 628 465 626 427 1325 384 552 679 385 1326 156 575 64 17 1327 214 93 326 327 1328 521 391 691 392 1329 588 647 603 591 1330 150 76 663 77 1331 225 524 110 63 1332 377 376 141 62 1333 588 646 624 603 1334 13 14 15 477 1335 671 33 634 32 1336 427 632 410 428 1337 36 126 128 253 1338 293 335 294 296 1339 256 634 33 583 1340 628 648 492 132 1341 336 115 150 117 1342 296 188 246 656 1343 61 375 238 381 1344 348 251 284 252 1345 346 30 31 205 1346 119 118 335 124 1347 228 701 441 230 1348 348 249 1 162 1349 437 57 549 480 1350 624 587 648 132 1351 114 150 115 16 1352 136 299 131 298 1353 428 701 429 356 1354 701 230 359 506 1355 249 348 347 162 1356 73 703 323 531 1357 629 372 677 371 1358 575 156 400 613 1359 472 211 282 212 1360 440 339 36 103 1361 485 686 532 81 1362 553 621 653 691 1363 212 282 472 436 1364 671 388 634 677 1365 211 386 472 448 1366 683 631 684 680 1367 704 531 262 323 1368 386 211 472 212 1369 380 668 87 393 1370 430 431 651 468 1371 112 380 111 41 1372 429 701 700 355 1373 6 2 161 1 1374 6 5 96 418 1375 248 347 252 205 1376 199 680 198 631 1377 298 299 297 136 1378 380 576 41 670 1379 618 43 528 44 1380 508 658 336 150 1381 219 695 457 220 1382 478 659 42 645 1383 672 617 578 350 1384 626 227 361 632 1385 63 207 306 109 1386 145 144 629 405 1387 465 357 226 626 1388 672 180 495 494 1389 672 677 692 678 1390 658 616 644 645 1391 554 629 450 405 1392 2 161 1 284 1393 250 3 285 21 1394 347 248 252 249 1395 695 303 85 276 1396 540 552 679 384 1397 432 314 361 626 1398 645 528 661 618 1399 685 552 385 679 1400 631 182 198 183 1401 552 685 686 679 1402 387 453 421 297 1403 671 18 19 281 1404 647 416 591 689 1405 179 672 350 494 1406 672 179 180 494 1407 352 665 274 273 1408 335 295 125 118 1409 522 667 534 394 1410 464 254 336 406 1411 386 443 472 448 1412 397 568 483 676 1413 299 324 297 136 1414 429 700 701 466 1415 615 148 391 622 1416 197 194 652 22 1417 629 551 447 404 1418 651 404 446 551 1419 551 446 447 404 1420 62 142 404 61 1421 297 322 136 324 1422 665 667 274 273 1423 651 404 551 629 1424 544 664 652 527 1425 542 553 691 621 1426 348 249 284 1 1427 321 178 291 289 1428 677 633 692 678 1429 213 411 93 327 1430 515 612 614 396 1431 276 303 85 344 1432 53 340 83 84 1433 92 213 330 411 1434 419 611 396 395 1435 314 315 513 626 1436 513 618 479 548 1437 3 575 173 21 1438 131 324 299 136 1439 543 546 560 633 1440 95 304 425 424 1441 680 692 617 683 1442 58 177 49 137 1443 35 95 469 425 1444 59 58 137 60 1445 35 455 469 95 1446 202 300 231 154 1447 653 194 689 23 1448 219 695 220 276 1449 695 276 85 86 1450 57 515 387 614 1451 663 149 77 477 1452 295 247 462 329 1453 387 299 612 610 1454 299 171 612 172 1455 617 672 516 350 1456 347 348 249 252 1457 681 662 617 670 1458 700 584 310 311 1459 702 311 585 584 1460 671 617 672 677 1461 5 6 96 161 1462 672 617 516 692 1463 213 93 92 286 1464 147 146 148 659 1465 528 675 557 571 1466 165 391 621 521 1467 350 617 351 516 1468 620 416 317 636 1469 693 694 40 113 1470 433 432 360 632 1471 617 512 351 516 1472 617 502 516 692 1473 700 466 354 353 1474 170 57 650 264 1475 694 693 668 113 1476 617 512 516 502 1477 538 543 687 633 1478 92 93 213 411 1479 181 672 495 537 1480 672 494 495 678 1481 467 410 632 428 1482 680 692 525 502 1483 312 311 263 379 1484 90 91 330 153 1485 621 619 523 653 1486 465 628 461 427 1487 671 279 581 280 1488 655 335 504 625 1489 624 587 132 598 1490 427 632 428 357 1491 191 638 589 192 1492 682 652 501 197 1493 190 638 600 589 1494 634 33 583 32 1495 629 554 561 633 1496 92 90 213 286 1497 575 4 64 21 1498 651 256 305 62 1499 664 654 653 620 1500 228 632 701 360 1501 444 152 407 406 1502 445 651 524 62 1503 445 651 62 403 1504 445 651 403 446 1505 75 689 209 240 1506 500 648 499 192 1507 217 656 663 642 1508 655 644 635 533 1509 90 300 153 154 1510 145 408 546 633 1511 90 300 154 231 1512 629 677 634 388 1513 612 611 395 396 1514 431 651 468 305 1515 513 618 548 674 1516 453 387 471 610 1517 106 650 264 263 1518 679 519 688 382 1519 57 105 53 614 1520 267 277 344 218 1521 497 185 687 538 1522 313 674 548 505 1523 675 568 567 571 1524 620 416 636 689 1525 146 147 488 659 1526 635 639 625 600 1527 675 513 547 626 1528 645 269 517 627 1529 567 675 571 547 1530 463 244 245 517 1531 289 321 138 48 1532 411 213 155 327 1533 628 673 409 626 1534 517 645 244 449 1535 557 675 547 571 1536 673 627 676 675 1537 606 641 649 623 1538 690 483 454 70 1539 659 478 661 645 1540 576 41 662 580 1541 23 689 653 690 1542 632 227 356 357 1543 611 419 396 515 1544 672 389 537 460 1545 672 671 279 581 1546 34 671 358 32 1547 617 671 672 581 1548 617 581 578 579 1549 343 287 47 290 1550 671 33 34 662 1551 320 343 47 290 1552 460 671 672 389 1553 460 671 389 20 1554 701 359 434 160 1555 606 649 592 601 1556 344 267 268 345 1557 334 332 437 549 1558 303 218 456 301 1559 614 302 695 612 1560 37 221 338 107 1561 685 652 686 682 1562 387 545 471 515 1563 545 57 481 480 1564 334 57 614 549 1565 437 57 54 334 1566 17 575 64 21 1567 453 235 610 471 1568 235 419 438 515 1569 394 667 666 668 1570 573 497 687 538 1571 310 259 260 66 1572 312 531 585 262 1573 312 322 704 262 1574 658 616 645 659 1575 637 641 638 648 1576 650 451 545 556 1577 656 217 316 642 1578 369 335 294 293 1579 552 509 685 385 1580 4 34 64 21 1581 129 610 235 130 1582 269 645 517 449 1583 208 664 195 689 1584 610 129 131 130 1585 672 180 181 495 1586 648 587 641 590 1587 587 608 641 590 1588 683 525 633 687 1589 606 608 641 609 1590 427 632 357 626 1591 423 422 435 426 1592 276 277 219 344 1593 650 387 421 106 1594 663 615 477 78 1595 429 700 263 355 1596 677 672 495 678 1597 437 57 334 549 1598 393 668 87 88 1599 312 421 106 650 1600 419 611 438 515 1601 624 623 609 604 1602 404 62 61 629 1603 377 100 37 337 1604 146 391 622 165 1605 435 423 426 469 1606 156 575 17 613 1607 351 579 352 665 1608 18 281 662 64 1609 639 637 623 641 1610 246 656 217 215 1611 640 414 593 415 1612 691 697 532 654 1613 157 111 417 401 1614 380 693 668 666 1615 681 694 684 634 1616 694 682 668 669 1617 455 95 304 469 1618 686 81 82 668 1619 488 622 146 659 1620 651 39 634 688 1621 288 320 290 291 1622 113 694 40 39 1623 127 338 253 339 1624 429 263 700 466 1625 210 282 212 436 1626 443 386 435 423 1627 651 573 687 561 1628 377 221 37 206 1629 518 673 567 547 1630 74 676 209 75 1631 375 376 381 142 1632 482 701 308 632 1633 428 701 356 632 1634 665 666 275 274 1635 628 673 626 627 1636 399 690 23 653 1637 314 432 315 626 1638 67 68 482 258 1639 673 518 626 547 1640 622 493 619 661 1641 74 69 75 257 1642 194 689 240 195 1643 671 18 662 34 1644 74 69 257 530 1645 509 552 685 686 1646 520 496 679 687 1647 619 397 523 690 1648 38 382 503 686 1649 619 166 523 539 1650 475 38 503 686 1651 630 682 694 684 1652 518 315 626 547 1653 56 167 170 650 1654 528 661 565 478 1655 194 664 652 653 1656 382 540 686 679 1657 550 226 361 626 1658 372 373 678 495 1659 680 692 502 617 1660 14 615 13 89 1661 603 624 604 594 1662 444 464 152 406 1663 599 123 592 649 1664 658 622 616 659 1665 635 639 642 625 1666 631 630 679 688 1667 357 465 427 626 1668 494 562 495 678 1669 639 640 642 625 1670 644 637 643 646 1671 498 190 535 635 1672 566 633 678 560 1673 660 317 636 620 1674 7 265 8 164 1675 511 700 491 476 1676 701 308 466 353 1677 518 530 567 673 1678 637 641 648 624 1679 123 122 649 597 1680 151 16 114 232 1681 671 33 32 34 1682 595 640 414 593 1683 685 652 682 197 1684 595 607 640 593 1685 360 701 359 434 1686 620 619 653 690 1687 300 278 202 231 1688 415 640 623 593 1689 127 222 338 339 1690 508 270 271 363 1691 320 343 319 47 1692 50 295 462 329 1693 163 9 164 318 1694 78 663 217 187 1695 616 646 627 647 1696 630 685 679 686 1697 680 199 502 525 1698 236 388 237 238 1699 659 645 661 616 1700 18 34 21 64 1701 371 677 537 372 1702 516 562 678 560 1703 660 317 514 316 1704 660 514 654 615 1705 542 166 523 621 1706 622 620 615 660 1707 617 681 670 669 1708 302 171 457 612 1709 677 633 678 372 1710 617 579 670 581 1711 620 654 615 660 1712 576 579 670 275 1713 622 621 620 619 1714 633 559 543 546 1715 408 145 629 633 1716 597 639 649 641 1717 85 218 344 303 1718 603 416 636 594 1719 643 627 628 500 1720 500 648 193 492 1721 597 641 602 605 1722 666 394 275 274 1723 666 394 380 275 1724 133 624 132 598 1725 234 452 235 129 1726 57 334 53 54 1727 133 646 588 647 1728 185 543 687 538 1729 499 638 643 535 1730 315 513 626 547 1731 633 559 546 145 1732 648 192 193 590 1733 660 317 620 654 1734 691 393 668 522 1735 619 568 539 397 1736 485 532 686 569 1737 655 508 533 76 1738 363 644 362 645 1739 53 264 328 104 1740 616 622 660 620 1741 83 104 264 53 1742 23 689 240 194 1743 666 665 275 670 1744 497 184 687 185 1745 637 623 624 636 1746 631 683 525 680 1747 623 624 636 594 1748 386 422 435 423 1749 639 122 596 597 1750 639 122 597 649 1751 239 2 1 349 1752 608 606 641 599 1753 415 640 642 623 1754 372 633 678 373 1755 626 227 632 357 1756 467 409 308 632 1757 23 689 690 70 1758 263 650 312 106 1759 626 227 357 226 1760 454 23 690 70 1761 654 621 620 615 1762 235 610 438 130 1763 606 599 592 649 1764 595 366 367 657 1765 644 637 635 643 1766 640 367 657 595 1767 569 532 686 570 1768 162 249 248 347 1769 507 644 635 643 1770 640 367 595 292 1771 658 149 283 150 1772 83 105 53 264 1773 532 697 686 570 1774 563 697 570 686 1775 522 667 394 668 1776 620 621 622 615 1777 57 170 53 264 1778 380 668 393 394 1779 655 656 663 76 1780 685 652 197 527 1781 105 57 53 264 1782 660 317 316 636 1783 654 621 615 691 1784 662 280 581 582 1785 530 69 483 676 1786 691 393 522 392 1787 651 39 468 305 1788 692 502 516 560 1789 388 671 672 677 1790 157 575 417 65 1791 525 184 185 687 1792 637 644 636 646 1793 78 663 187 77 1794 658 622 659 148 1795 512 680 501 502 1796 632 432 361 626 1797 365 123 649 592 1798 601 649 365 657 1799 365 649 601 592 1800 266 52 164 9 1801 627 500 550 226 1802 598 624 604 603 1803 702 158 11 10 1804 415 593 623 594 1805 432 518 315 626 1806 387 610 612 515 1807 701 699 230 506 1808 642 415 623 636 1809 133 624 598 588 1810 313 314 626 361 1811 76 656 663 186 1812 409 410 626 632 1813 701 441 230 699 1814 220 611 395 612 1815 409 632 74 308 1816 619 690 523 653 1817 675 513 626 674 1818 672 671 577 279 1819 181 672 460 577 1820 313 674 626 314 1821 395 611 220 420 1822 293 625 292 368 1823 38 630 382 686 1824 566 408 373 633 1825 265 266 52 164 1826 619 620 653 621 1827 108 206 207 307 1828 293 625 368 119 1829 124 335 119 625 1830 625 335 293 656 1831 656 655 625 335 1832 639 657 640 625 1833 373 562 678 495 1834 71 585 531 262 1835 576 580 662 582 1836 662 280 582 281 1837 248 347 205 162 1838 651 629 561 633 1839 344 277 219 218 1840 633 408 373 372 1841 161 162 249 29 1842 161 249 233 29 1843 248 205 30 162 1844 37 103 338 100 1845 144 629 374 372 1846 375 61 142 381 1847 8 9 164 52 1848 145 629 554 405 1849 520 651 687 688 1850 75 689 240 70 1851 629 677 388 371 1852 383 520 384 679 1853 293 335 625 119 1854 623 601 607 640 1855 498 625 635 655 1856 656 640 414 292 1857 368 369 293 119 1858 155 153 154 330 1859 647 134 74 209 1860 655 508 76 663 1861 691 393 392 88 1862 567 675 547 673 1863 363 659 241 331 1864 225 62 524 63 1865 62 651 524 305 1866 63 306 110 109 1867 656 640 415 414 1868 638 190 600 635 1869 69 70 676 75 1870 673 647 74 676 1871 409 647 74 673 1872 530 673 74 676 1873 482 673 518 632 1874 479 618 557 528 1875 666 665 670 669 1876 545 57 480 549 1877 573 574 561 651 1878 228 701 230 359 1879 518 432 482 632 1880 37 221 107 206 1881 91 90 330 92 1882 681 41 662 670 1883 344 268 85 86 1884 575 157 401 400 1885 508 336 363 271 1886 598 609 604 624 1887 668 522 393 394 1888 174 93 327 326 1889 459 63 207 306 1890 642 655 635 625 1891 24 25 28 137 1892 221 107 206 108 1893 25 27 28 137 1894 619 616 620 622 1895 642 655 625 656 1896 408 566 546 633 1897 430 519 468 651 1898 64 34 33 662 1899 39 694 634 688 1900 281 156 580 64 1901 651 256 62 634 1902 689 664 195 194 1903 635 625 498 600 1904 646 637 624 636 1905 133 624 588 646 1906 316 415 642 636 1907 25 26 27 137 1908 85 218 301 267 1909 82 81 88 668 1910 288 289 139 48 1911 46 125 295 118 1912 568 565 661 539 1913 691 81 668 88 1914 565 493 661 539 1915 694 682 669 684 1916 677 372 495 537 1917 680 682 669 665 1918 682 680 669 684 1919 617 681 669 684 1920 617 680 669 665 1921 129 324 442 698 1922 680 617 669 684 1923 348 249 252 284 1924 512 680 502 617 1925 62 225 207 63 1926 62 256 305 63 1927 700 466 353 701 1928 326 93 176 174 1929 39 651 468 688 1930 699 490 168 506 1931 232 696 175 402 1932 213 93 286 214 1933 112 380 41 693 1934 321 289 138 26 1935 62 140 403 141 1936 634 683 677 633 1937 458 171 302 340 1938 36 223 440 126 1939 36 102 440 103 1940 62 140 141 225 1941 377 376 337 141 1942 62 140 225 445 1943 683 677 633 692 1944 379 312 106 263 1945 232 696 402 444 1946 642 316 660 217 1947 694 630 38 686 1948 525 683 633 692 1949 668 693 380 87 1950 644 642 635 637 1951 347 346 205 162 1952 686 691 532 81 1953 525 543 687 185 1954 343 287 319 47 1955 672 677 495 537 1956 699 700 263 490 1957 630 682 684 631 1958 654 621 691 653 1959 658 148 283 149 1960 631 182 183 385 1961 631 199 184 183 1962 699 263 264 650 1963 671 33 662 634 1964 702 311 584 700 1965 23 194 652 653 1966 37 221 377 337 1967 251 2 349 1 1968 488 478 661 659 1969 645 627 661 616 1970 17 18 21 64 1971 686 544 652 527 1972 632 432 360 361 1973 697 544 564 654 1974 650 105 57 387 1975 544 654 652 664 1976 697 544 654 652 1977 487 167 650 490 1978 3 4 349 239 1979 496 631 184 183 1980 675 565 528 661 1981 671 280 281 19 1982 179 672 180 577 1983 556 486 536 650 1984 672 179 279 577 1985 236 388 634 32 1986 190 638 589 191 1987 641 590 602 605 1988 702 71 261 585 trunk-2018.02b/examples/polyhedra/horse.node000066400000000000000000000577361324306050200210040ustar00rootroot00000000000000704 3 0 0 1 0.026263 -0.057652 -0.027717 2 0.028396 -0.057122 -0.020551 3 0.029125 -0.057308 -0.015422 4 0.028157 -0.058599 -0.010996 5 0.028898 -0.045561 -0.016704 6 0.027592 -0.051553 -0.022162 7 0.033669 -0.068330 -0.067160 8 0.038611 -0.066770 -0.074238 9 0.036028 -0.062644 -0.064116 10 0.007605 0.066763 0.064100 11 0.008389 0.062068 0.064753 12 0.009432 0.055825 0.065622 13 0.021249 0.005887 -0.015025 14 0.017036 0.004920 -0.018258 15 0.022046 0.010560 -0.018652 16 0.027053 0.016195 -0.019046 17 0.037111 -0.056430 -0.009104 18 0.036512 -0.061198 -0.004632 19 0.034637 -0.065953 0.002637 20 0.032715 -0.070827 0.010088 21 0.030864 -0.059589 -0.009901 22 0.020050 -0.004878 0.028637 23 0.017493 0.003408 0.029489 24 0.003076 0.041420 -0.076197 25 0.002472 0.037298 -0.069940 26 0.001993 0.034030 -0.064978 27 0.000942 0.034537 -0.069919 28 -0.000362 0.035166 -0.076047 29 0.034584 -0.058346 -0.037135 30 0.035167 -0.061060 -0.044517 31 0.035737 -0.063715 -0.051736 32 0.016112 -0.063037 0.002209 33 0.016407 -0.054702 -0.004062 34 0.024930 -0.059906 -0.005104 35 -0.000073 -0.086719 -0.046511 36 0.001791 -0.081604 -0.038030 37 0.003217 -0.070514 -0.024341 38 -0.003413 -0.037489 0.000190 39 0.005194 -0.039944 -0.001835 40 0.017825 -0.041393 -0.002644 41 0.028319 -0.041146 -0.004324 42 0.036346 0.030374 0.000678 43 0.034338 0.036396 0.005666 44 0.032792 0.041033 0.009507 45 0.031612 0.044574 0.012440 46 -0.000953 0.035463 -0.026796 47 -0.001831 0.035550 -0.039695 48 -0.003071 0.035847 -0.054386 49 -0.007008 0.038478 -0.066659 50 0.002677 0.033750 -0.036743 51 0.033219 -0.069701 -0.074797 52 0.038638 -0.058989 -0.074472 53 -0.010485 0.072235 0.038621 54 -0.004891 0.080322 0.041695 55 -0.003100 0.076878 0.044612 56 -0.001400 0.073610 0.047381 57 -0.005304 0.073019 0.043617 58 -0.007085 0.035645 -0.076384 59 -0.009575 0.044568 -0.076296 60 -0.002485 0.047277 -0.076408 61 0.007343 -0.069083 0.004699 62 0.002685 -0.062218 -0.001317 63 -0.004191 -0.058568 -0.008117 64 0.025576 -0.050624 -0.004412 65 0.028585 -0.043176 -0.010017 66 0.012065 0.047937 0.062274 67 0.013821 0.040610 0.057659 68 0.014980 0.031117 0.049248 69 0.016132 0.021688 0.040893 70 0.016930 0.011451 0.034308 71 0.004607 0.058099 0.068955 72 0.001074 0.062754 0.070729 73 -0.001843 0.068357 0.071802 74 0.003596 0.028173 0.037198 75 0.008901 0.020769 0.040393 76 0.008084 0.029434 -0.016522 77 0.007704 0.021798 -0.017190 78 0.007195 0.011561 -0.018085 79 0.006583 -0.000749 -0.019161 80 0.007019 -0.012655 -0.018780 81 0.008437 -0.020456 -0.016458 82 0.010655 -0.029268 -0.012686 83 -0.016502 0.064272 0.041464 84 -0.023651 0.070591 0.040760 85 -0.032105 0.078063 0.039928 86 -0.024646 0.077904 0.039114 87 0.028502 -0.030546 -0.006316 88 0.023743 -0.019257 -0.014617 89 0.022784 -0.006905 -0.017011 90 0.030531 0.016695 -0.064974 91 0.026939 0.015038 -0.066425 92 0.026582 0.015868 -0.059732 93 0.026085 0.017027 -0.050393 94 -0.003479 -0.093388 -0.059875 95 -0.002386 -0.089016 -0.049643 96 0.035965 -0.050571 -0.023358 97 0.039281 -0.050362 -0.016527 98 0.038121 -0.054006 -0.019285 99 0.036701 -0.058464 -0.022661 100 0.002561 -0.081479 -0.021698 101 0.000556 -0.084534 -0.033024 102 -0.000906 -0.086760 -0.041279 103 0.001049 -0.084486 -0.035938 104 -0.006687 0.064513 0.038988 105 -0.013625 0.061757 0.048169 106 -0.008148 0.062122 0.051735 107 -0.004247 -0.070921 -0.028262 108 -0.008229 -0.065729 -0.020666 109 -0.008738 -0.060082 -0.012536 110 -0.008579 -0.055960 -0.006620 111 0.035046 -0.040905 -0.006561 112 0.028629 -0.037706 -0.007386 113 0.019650 -0.033229 -0.008540 114 0.029260 0.024799 -0.025208 115 0.031168 0.026444 -0.018805 116 0.032696 0.027762 -0.013675 117 0.031812 0.023898 -0.013435 118 -0.003350 0.035761 -0.019827 119 -0.005377 0.036012 -0.013936 120 -0.006781 0.036187 -0.009855 121 -0.007713 0.036303 -0.007147 122 -0.008421 0.036390 -0.005089 123 -0.008930 0.036454 -0.003608 124 -0.002196 0.036726 -0.015197 125 -0.001588 0.036109 -0.020867 126 -0.003676 -0.080319 -0.044198 127 -0.003918 -0.076338 -0.037448 128 -0.000057 -0.082053 -0.045804 129 -0.006422 0.077425 0.069665 130 -0.013860 0.078565 0.070109 131 -0.015198 0.073367 0.071529 132 -0.009096 0.042041 0.011602 133 -0.008898 0.036362 0.019323 134 -0.005328 0.028779 0.028583 135 -0.019232 0.069753 0.075924 136 -0.010657 0.067817 0.071042 137 -0.000210 0.039823 -0.067602 138 -0.000027 0.036665 -0.059521 139 0.000862 0.034372 -0.048297 140 -0.010584 -0.065546 -0.004799 141 -0.007286 -0.069878 -0.006667 142 -0.002402 -0.072727 0.000460 143 0.001056 -0.074152 0.008171 144 0.003853 -0.074062 0.018709 145 0.003342 -0.069823 0.027164 146 0.029306 0.014183 -0.001053 147 0.034079 0.021282 -0.006916 148 0.026358 0.014509 -0.005450 149 0.020232 0.017219 -0.018770 150 0.018229 0.024729 -0.017652 151 0.020372 0.024176 -0.026880 152 0.022592 0.023605 -0.036435 153 0.030012 0.010436 -0.067073 154 0.034634 0.014172 -0.064460 155 0.033882 0.008238 -0.058883 156 0.037526 -0.051609 -0.008028 157 0.037863 -0.047700 -0.007155 158 0.008991 0.065685 0.060430 159 0.010649 0.064393 0.056035 160 0.012269 0.063131 0.051742 161 0.029081 -0.052566 -0.026939 162 0.029425 -0.059872 -0.038841 163 0.030117 -0.065064 -0.052042 164 0.028976 -0.064697 -0.065704 165 0.031774 0.012369 0.006356 166 0.032602 0.008892 0.013917 167 0.001595 0.069348 0.046126 168 0.003992 0.065936 0.045121 169 0.000600 0.067412 0.043598 170 -0.004088 0.069452 0.041493 171 -0.025705 0.069977 0.061869 172 -0.019829 0.071873 0.067271 173 0.036923 -0.057363 -0.015321 174 0.028118 0.011664 -0.045522 175 0.022758 0.014323 -0.036595 176 0.024406 0.015662 -0.043429 177 -0.004564 0.034029 -0.068247 178 -0.003849 0.026594 -0.061274 179 0.039115 -0.060432 0.022930 180 0.031473 -0.067861 0.025236 181 0.026604 -0.071784 0.020783 182 -0.001809 -0.023787 0.026332 183 -0.004947 -0.031727 0.027924 184 -0.005055 -0.039276 0.030272 185 -0.003241 -0.044957 0.032589 186 0.007119 0.021898 -0.016890 187 0.006109 0.014012 -0.017275 188 0.005269 0.027587 -0.018042 189 -0.001401 0.039363 -0.010651 190 -0.000784 0.041412 -0.007118 191 -0.001154 0.043426 -0.002634 192 -0.003728 0.043938 0.001068 193 -0.006496 0.043842 0.005663 194 0.008286 0.001373 0.028407 195 0.002341 0.002417 0.024537 196 -0.004067 0.003542 0.020366 197 0.007448 -0.011565 0.029102 198 0.008060 -0.024765 0.032458 199 0.003905 -0.035003 0.035820 200 0.024090 0.019745 -0.075713 201 0.030828 0.022881 -0.074779 202 0.036689 0.017068 -0.075224 203 0.034823 -0.073473 -0.060098 204 0.034256 -0.070922 -0.051610 205 0.033624 -0.068077 -0.042148 206 0.000065 -0.065023 -0.019866 207 -0.002185 -0.061611 -0.013655 208 -0.002609 0.011441 0.024120 209 -0.002607 0.018604 0.028700 210 -0.005672 -0.075653 -0.076015 211 -0.008547 -0.089177 -0.076012 212 -0.008159 -0.081437 -0.071134 213 0.032493 0.016287 -0.057646 214 0.030013 0.018736 -0.049754 215 -0.004407 0.022590 -0.014686 216 -0.000383 0.018286 -0.017947 217 0.003082 0.016866 -0.011180 218 -0.038292 0.091155 0.041437 219 -0.033087 0.088523 0.046908 220 -0.027565 0.085731 0.052715 221 -0.008782 -0.069935 -0.017577 222 -0.006817 -0.075377 -0.028617 223 -0.006294 -0.081398 -0.040374 224 -0.004847 -0.083615 -0.048463 225 -0.007972 -0.062492 -0.006642 226 0.004999 0.048693 0.015822 227 0.002887 0.053901 0.024535 228 0.000940 0.058702 0.032567 229 -0.000599 0.062497 0.038915 230 0.004244 0.063550 0.039686 231 0.030662 0.019418 -0.069291 232 0.022965 0.016706 -0.028322 233 0.035322 -0.054195 -0.029779 234 -0.008966 0.081243 0.074136 235 -0.009635 0.083777 0.064807 236 0.011151 -0.066458 0.003618 237 0.012654 -0.067857 0.002520 238 0.010096 -0.071422 0.002750 239 0.027437 -0.058239 -0.017350 240 0.008551 0.009729 0.033570 241 0.033536 0.032819 -0.005147 242 0.031147 0.034898 -0.010100 243 0.029396 0.038255 -0.002943 244 0.027936 0.042915 0.001852 245 0.026936 0.046109 0.005138 246 0.002634 0.023251 -0.017998 247 -0.000320 0.021926 -0.022182 248 0.035668 -0.063050 -0.041216 249 0.035606 -0.062455 -0.031795 250 0.031630 -0.061844 -0.015318 251 0.032597 -0.064690 -0.022155 252 0.033274 -0.066802 -0.031179 253 0.001231 -0.077512 -0.037357 254 0.029006 0.028402 -0.020543 255 0.028785 0.031537 -0.016483 256 0.007895 -0.055751 -0.005892 257 0.009084 0.029915 0.049018 258 0.009247 0.037992 0.056634 259 0.009094 0.044137 0.062283 260 0.004571 0.049137 0.064544 261 0.000970 0.053117 0.066343 262 -0.002739 0.057216 0.068196 263 -0.004448 0.057768 0.049792 264 -0.007430 0.063297 0.045468 265 0.026177 -0.065434 -0.075126 266 0.031322 -0.054824 -0.074413 267 -0.040489 0.082254 0.036718 268 -0.034904 0.083455 0.031909 269 0.018940 0.045522 0.002339 270 0.020795 0.042309 -0.005581 271 0.024672 0.036883 -0.012380 272 0.038539 -0.068582 -0.059298 273 0.034493 -0.025263 0.025675 274 0.035853 -0.028332 0.021651 275 0.037717 -0.032538 0.016137 276 -0.024112 0.087163 0.040018 277 -0.035632 0.092817 0.037181 278 0.026934 0.010673 -0.076060 279 0.041396 -0.059093 0.013795 280 0.041468 -0.057375 0.007133 281 0.040545 -0.054920 -0.000571 282 0.002319 -0.079636 -0.075421 283 0.029495 0.019909 -0.010042 284 0.033925 -0.061439 -0.024036 285 0.032219 -0.060408 -0.016157 286 0.030305 0.017586 -0.058328 287 0.001165 0.025234 -0.039576 288 0.000420 0.029226 -0.050353 289 0.001082 0.031951 -0.058496 290 -0.001052 0.026417 -0.047473 291 -0.003028 0.027471 -0.054509 292 -0.009343 0.028211 -0.011602 293 -0.008052 0.028809 -0.015617 294 -0.006412 0.029568 -0.020717 295 -0.004375 0.030512 -0.027055 296 -0.004391 0.026551 -0.020871 297 -0.013985 0.063927 0.065280 298 -0.020527 0.064972 0.071842 299 -0.019826 0.067752 0.063644 300 0.034092 0.010126 -0.072984 301 -0.043958 0.086853 0.039365 302 -0.028559 0.073988 0.051169 303 -0.036085 0.080276 0.045399 304 -0.007298 -0.085588 -0.056855 305 -0.003721 -0.051178 -0.005624 306 -0.004411 -0.056579 -0.012931 307 -0.005077 -0.062940 -0.021242 308 0.002169 0.037826 0.046195 309 -0.000200 0.044362 0.052514 310 -0.001680 0.048446 0.056461 311 -0.003941 0.051499 0.058352 312 -0.008478 0.057113 0.061482 313 0.021543 0.048249 0.020262 314 0.023093 0.045778 0.024117 315 0.024702 0.043211 0.028120 316 -0.001091 0.015374 -0.004599 317 -0.004774 0.014057 0.001208 318 0.028157 -0.071170 -0.060350 319 -0.006362 0.030046 -0.036135 320 -0.006150 0.031062 -0.047754 321 -0.004965 0.033650 -0.057501 322 -0.005934 0.061494 0.069344 323 -0.005891 0.061302 0.069039 324 -0.010384 0.067127 0.070241 325 0.032730 0.020475 -0.038328 326 0.031194 0.014985 -0.046458 327 0.032655 0.011230 -0.053354 328 -0.002140 0.067314 0.039224 329 0.000362 0.023446 -0.030175 330 0.026148 0.009600 -0.060966 331 0.031500 0.029060 -0.011870 332 -0.013998 0.087142 0.046345 333 -0.018652 0.087152 0.043434 334 -0.016473 0.080327 0.039795 335 0.001185 0.032587 -0.016485 336 0.021320 0.029764 -0.016802 337 -0.002326 -0.075474 -0.016263 338 -0.003167 -0.077066 -0.025439 339 -0.004418 -0.084321 -0.033081 340 -0.025333 0.068184 0.047418 341 -0.004311 0.022724 -0.022267 342 -0.004188 0.022895 -0.031885 343 -0.003691 0.024777 -0.043372 344 -0.030457 0.088004 0.035450 345 -0.036247 0.088772 0.031281 346 0.031209 -0.069800 -0.048679 347 0.029804 -0.066357 -0.036967 348 0.028907 -0.065374 -0.025806 349 0.028478 -0.061497 -0.017330 350 0.037456 -0.053601 0.029803 351 0.038473 -0.043151 0.030106 352 0.038482 -0.033013 0.026766 353 -0.002248 0.049608 0.049562 354 -0.003264 0.053376 0.049668 355 -0.002858 0.055345 0.043138 356 -0.001029 0.052559 0.035487 357 0.001256 0.049079 0.025928 358 0.025328 -0.067313 0.005556 359 0.013842 0.060707 0.045136 360 0.016206 0.056975 0.036336 361 0.019096 0.052251 0.027633 362 0.021868 0.041363 -0.003259 363 0.025415 0.036324 -0.010041 364 -0.007110 0.087078 0.074531 365 -0.008952 0.035898 -0.005102 366 -0.008979 0.035203 -0.006970 367 -0.009017 0.034225 -0.009601 368 -0.009078 0.032644 -0.013849 369 -0.007190 0.031672 -0.019472 370 0.037935 -0.058199 -0.030243 371 0.015248 -0.070775 0.015665 372 0.014439 -0.070208 0.021405 373 0.013541 -0.069581 0.027768 374 0.008784 -0.074397 0.012669 375 0.004283 -0.074031 0.003426 376 0.000053 -0.073369 -0.006052 377 0.003740 -0.069214 -0.010542 378 -0.004887 -0.083504 -0.023460 379 -0.004849 0.055126 0.050520 380 0.034465 -0.031363 0.003188 381 0.007511 -0.070524 -0.002655 382 -0.006256 -0.036404 0.008667 383 -0.007412 -0.034993 0.013859 384 -0.007610 -0.033407 0.017442 385 -0.005177 -0.029372 0.021171 386 -0.005109 -0.087762 -0.065926 387 -0.013004 0.064463 0.056687 388 0.015628 -0.067370 0.009744 389 0.025212 -0.067306 0.006528 390 0.029931 0.006120 -0.059808 391 0.031668 0.005365 -0.003945 392 0.034631 -0.006997 -0.006113 393 0.038324 -0.018520 -0.000843 394 0.040352 -0.022475 0.009380 395 -0.020583 0.086467 0.052725 396 -0.014849 0.087070 0.052733 397 0.027283 0.012912 0.025446 398 0.025510 0.008516 0.025941 399 0.023085 0.002531 0.026841 400 0.038513 -0.048920 -0.011449 401 0.037204 -0.045724 -0.011639 402 0.027560 0.014035 -0.031665 403 -0.005373 -0.067874 0.001942 404 -0.002587 -0.069450 0.008746 405 -0.001478 -0.070668 0.017171 406 0.028935 0.028849 -0.025183 407 0.029243 0.024736 -0.037255 408 0.008812 -0.069693 0.027488 409 0.000838 0.035196 0.031481 410 -0.001421 0.040950 0.026798 411 0.027101 0.010599 -0.053489 412 0.029055 0.008797 -0.052909 413 -0.003949 0.072528 0.070819 414 -0.009246 0.022643 -0.009987 415 -0.007135 0.018591 -0.004703 416 -0.005606 0.012997 0.013347 417 0.035333 -0.043926 -0.011811 418 0.035649 -0.047249 -0.017584 419 -0.016955 0.088885 0.060260 420 -0.022350 0.087281 0.056423 421 -0.010844 0.062956 0.057992 422 -0.004603 -0.081848 -0.064690 423 -0.005786 -0.083490 -0.061249 424 -0.005439 -0.082884 -0.050360 425 -0.004022 -0.080889 -0.051840 426 -0.004351 -0.081432 -0.059113 427 -0.001243 0.044186 0.023628 428 -0.002270 0.048538 0.032011 429 -0.003497 0.053735 0.042022 430 -0.011595 -0.044323 0.000425 431 -0.010043 -0.050312 -0.003201 432 0.022634 0.047008 0.032883 433 0.019311 0.053108 0.040535 434 0.015694 0.058415 0.046563 435 -0.001232 -0.082111 -0.065239 436 0.000223 -0.081097 -0.069412 437 -0.009358 0.083667 0.043976 438 -0.015368 0.083595 0.065309 439 0.036215 -0.068343 -0.051188 440 -0.003456 -0.086544 -0.040925 441 -0.002383 0.058404 0.038849 442 -0.007956 0.070650 0.071677 443 0.002268 -0.084839 -0.064857 444 0.030995 0.022637 -0.031768 445 -0.009976 -0.065902 0.000935 446 -0.009381 -0.066251 0.006545 447 -0.008533 -0.066748 0.014540 448 0.002777 -0.087260 -0.075062 449 0.023375 0.045848 0.003892 450 -0.004731 -0.068860 0.015958 451 -0.001596 0.076807 0.060244 452 -0.004055 0.081387 0.066615 453 -0.003882 0.077100 0.064707 454 0.023062 0.008814 0.027189 455 0.002132 -0.088933 -0.058419 456 -0.036961 0.084321 0.046976 457 -0.031105 0.079742 0.053980 458 -0.029153 0.074334 0.050046 459 0.004131 -0.060208 -0.012610 460 0.029861 -0.071274 0.015083 461 -0.005100 0.043133 0.017722 462 0.004541 0.028532 -0.026664 463 0.029521 0.045260 0.009175 464 0.021894 0.026985 -0.025660 465 0.001597 0.046237 0.020077 466 -0.003548 0.052768 0.042955 467 -0.002527 0.047091 0.035194 468 -0.007054 -0.040530 0.000295 469 0.001192 -0.083551 -0.056045 470 0.025615 0.017226 -0.070743 471 -0.005272 0.079994 0.062331 472 0.000384 -0.084186 -0.069197 473 -0.005032 -0.079362 -0.069234 474 0.024327 0.020337 -0.043370 475 -0.000077 -0.033135 -0.005275 476 0.013314 0.054661 0.058866 477 0.014053 0.012877 -0.018287 478 0.034841 0.026938 0.008443 479 0.031773 0.040853 0.015971 480 -0.005063 0.080891 0.047839 481 -0.002287 0.076345 0.050748 482 0.018910 0.041600 0.043191 483 0.023703 0.017718 0.031773 484 -0.002904 -0.006336 -0.013007 485 0.002336 -0.026122 -0.011467 486 0.004595 0.070154 0.061806 487 0.001281 0.070989 0.051485 488 0.033961 0.020580 0.002546 489 0.010942 0.060532 0.060724 490 0.005987 0.066920 0.050340 491 0.012653 0.060095 0.056018 492 -0.001620 0.045678 0.012571 493 0.033368 0.018035 0.011000 494 0.029546 -0.062391 0.030421 495 0.022024 -0.069510 0.025779 496 -0.007958 -0.037034 0.022641 497 -0.007451 -0.044230 0.026909 498 0.002645 0.039505 -0.009707 499 0.003474 0.043847 -0.000032 500 0.005178 0.045677 0.006582 501 0.021161 -0.021651 0.032199 502 0.015564 -0.037560 0.036669 503 -0.007437 -0.031062 0.001066 504 0.001707 0.037095 -0.013542 505 0.025627 0.046703 0.013582 506 0.008199 0.063644 0.046364 507 0.011571 0.042473 -0.003586 508 0.015020 0.034394 -0.013988 509 -0.007668 -0.018849 0.019429 510 -0.008355 0.004670 0.006114 511 0.014663 0.057093 0.052436 512 0.029954 -0.032704 0.034392 513 0.028432 0.041922 0.022657 514 -0.001471 0.006470 -0.008945 515 -0.009999 0.084531 0.057784 516 0.026263 -0.050595 0.035899 517 0.023131 0.046744 0.008104 518 0.022702 0.037798 0.033049 519 -0.009692 -0.040935 0.009004 520 -0.009148 -0.039827 0.015983 521 0.035353 0.002565 0.005817 522 0.039326 -0.010008 0.009267 523 0.029828 0.008425 0.020630 524 -0.012105 -0.058700 0.001746 525 0.004033 -0.043754 0.035928 526 0.000438 0.072417 0.066439 527 -0.000745 -0.007310 0.024057 528 0.033353 0.033951 0.013956 529 0.016535 0.049865 0.051404 530 0.020424 0.030269 0.037995 531 0.004280 0.066158 0.067141 532 -0.001255 -0.017766 -0.012716 533 0.007452 0.036720 -0.012169 534 0.037411 -0.018536 0.020989 535 0.004824 0.041720 -0.005625 536 0.005989 0.069235 0.056483 537 0.021451 -0.071079 0.016249 538 -0.006420 -0.052429 0.027450 539 0.031299 0.015695 0.019023 540 -0.009012 -0.031069 0.011044 541 0.029685 -0.014708 0.027161 542 0.034510 0.000692 0.015883 543 0.000835 -0.053683 0.033577 544 -0.010087 -0.006560 0.014305 545 -0.004171 0.080070 0.055266 546 0.005284 -0.063165 0.031269 547 0.028238 0.036546 0.027132 548 0.027500 0.045212 0.017923 549 -0.009282 0.084803 0.050803 550 0.015817 0.047918 0.013587 551 -0.011450 -0.060564 0.010532 552 -0.009687 -0.026047 0.015017 553 0.028888 0.001751 0.022961 554 -0.004689 -0.066128 0.020765 555 0.027693 -0.004707 0.026145 556 0.000404 0.073616 0.056360 557 0.031785 0.036667 0.020613 558 0.034200 -0.007726 0.021205 559 -0.003046 -0.061571 0.027744 560 0.011810 -0.055272 0.035706 561 -0.009951 -0.058762 0.019253 562 0.018962 -0.063179 0.031900 563 -0.012034 -0.017221 0.010529 564 -0.010788 -0.006433 0.000335 565 0.033156 0.024272 0.016654 566 0.011696 -0.064438 0.031127 567 0.026434 0.028893 0.030484 568 0.029259 0.020991 0.024940 569 -0.004583 -0.026754 -0.007164 570 -0.010255 -0.019468 -0.002548 571 0.031880 0.029937 0.022586 572 -0.010811 -0.025088 0.007184 573 -0.010626 -0.046808 0.019715 574 -0.011785 -0.049852 0.010795 575 0.028466 -0.051868 -0.013716 576 0.041203 -0.041314 0.006228 577 0.033780 -0.067081 0.017896 578 0.041288 -0.051588 0.021918 579 0.040600 -0.041620 0.019545 580 0.039746 -0.048009 -0.001838 581 0.041661 -0.050509 0.012420 582 0.041356 -0.050190 0.004682 583 0.009539 -0.062189 -0.000258 584 -0.000694 0.050838 0.061898 585 -0.003353 0.054423 0.063357 586 -0.004649 0.037544 -0.008893 587 -0.009648 0.038065 0.005257 588 -0.009151 0.028543 0.017680 589 -0.003574 0.040757 -0.003650 590 -0.007930 0.039942 0.001381 591 -0.006356 0.019892 0.021071 592 -0.009479 0.034966 -0.003348 593 -0.010334 0.024605 -0.002915 594 -0.008572 0.020252 0.003279 595 -0.009985 0.030377 -0.006807 596 -0.005947 0.038055 -0.005381 597 -0.007606 0.037598 -0.003505 598 -0.010474 0.033025 0.010113 599 -0.009363 0.036040 -0.001756 600 -0.003342 0.039388 -0.007076 601 -0.009708 0.033171 -0.004588 602 -0.008544 0.037868 -0.001510 603 -0.009740 0.023420 0.011040 604 -0.011303 0.026688 0.003865 605 -0.006329 0.039701 -0.001976 606 -0.010332 0.033693 -0.001197 607 -0.011361 0.030265 -0.001997 608 -0.009559 0.036597 0.000962 609 -0.011108 0.033261 0.002904 610 -0.018066 0.077846 0.066479 611 -0.021059 0.082902 0.061111 612 -0.024240 0.077336 0.060986 613 0.038265 -0.053212 -0.013190 614 -0.019504 0.065669 0.052438 615 0.014961 0.007014 -0.007360 616 0.014241 0.024339 0.008508 617 0.025868 -0.046177 0.019862 618 0.026525 0.039309 0.012579 619 0.023930 0.014308 0.015046 620 0.012479 0.011828 0.010170 621 0.024584 0.005257 0.008737 622 0.022634 0.014932 0.003786 623 -0.006046 0.029797 -0.000264 624 -0.005154 0.031656 0.007341 625 -0.002523 0.033475 -0.009742 626 0.013636 0.042066 0.024035 627 0.014603 0.036416 0.012541 628 0.002471 0.038000 0.017021 629 0.003686 -0.064140 0.013562 630 0.004946 -0.033122 0.010768 631 0.003700 -0.035032 0.022736 632 0.007956 0.046580 0.033315 633 0.006992 -0.059068 0.023659 634 0.011874 -0.053763 0.007284 635 0.003442 0.036016 -0.004982 636 0.001124 0.022624 0.004991 637 0.000502 0.032754 0.001369 638 -0.000456 0.038814 -0.001552 639 -0.004073 0.035555 -0.003984 640 -0.005740 0.029609 -0.005950 641 -0.006211 0.035684 0.000352 642 0.002178 0.026675 -0.004021 643 0.006443 0.038438 0.003001 644 0.013518 0.033566 -0.002129 645 0.023829 0.035384 0.003699 646 0.001824 0.031357 0.011009 647 0.002817 0.025972 0.019735 648 -0.002060 0.038924 0.004855 649 -0.007553 0.034849 -0.003209 650 -0.003546 0.067265 0.051790 651 -0.003023 -0.054392 0.007288 652 0.016229 -0.007495 0.018397 653 0.017397 0.003641 0.016646 654 0.006501 -0.000730 -0.000138 655 0.004983 0.031280 -0.009825 656 0.000052 0.026207 -0.011472 657 -0.006862 0.034367 -0.006241 658 0.020907 0.025729 -0.007738 659 0.027762 0.025039 -0.002406 660 0.009130 0.016062 -0.001261 661 0.025412 0.024852 0.012129 662 0.029802 -0.050467 0.004473 663 0.010201 0.020419 -0.011737 664 0.004181 0.003131 0.014997 665 0.028879 -0.033104 0.021979 666 0.030273 -0.029208 0.011810 667 0.027732 -0.020785 0.017126 668 0.022802 -0.023255 0.002725 669 0.021674 -0.033216 0.012922 670 0.031118 -0.039481 0.010578 671 0.028195 -0.060166 0.007060 672 0.027277 -0.060692 0.019217 673 0.014036 0.032769 0.028465 674 0.021161 0.042092 0.016799 675 0.024165 0.032280 0.019986 676 0.016510 0.021970 0.024903 677 0.016491 -0.060191 0.017193 678 0.017494 -0.060276 0.026080 679 -0.002272 -0.034802 0.015030 680 0.014971 -0.035858 0.025258 681 0.021598 -0.041818 0.007449 682 0.014690 -0.025394 0.017392 683 0.009629 -0.047129 0.019445 684 0.012804 -0.039061 0.014324 685 0.003023 -0.023545 0.018444 686 0.001471 -0.023584 0.005379 687 -0.000834 -0.045568 0.022422 688 0.001317 -0.042254 0.009652 689 0.007624 0.013328 0.022097 690 0.020217 0.010001 0.022608 691 0.020967 -0.009212 0.003067 692 0.016126 -0.050158 0.026675 693 0.024026 -0.034139 0.001943 694 0.011351 -0.033601 0.003892 695 -0.025054 0.079685 0.047447 696 0.025748 0.019353 -0.037194 697 -0.001832 -0.011969 0.001731 698 -0.004785 0.070437 0.068928 699 0.000043 0.061327 0.044695 700 0.001214 0.054476 0.050788 701 0.004630 0.051952 0.042608 702 0.004566 0.060044 0.059478 703 -0.009427 0.062579 0.066801 704 -0.006627 0.059324 0.065737 trunk-2018.02b/examples/polyhedra/horse.py000066400000000000000000000021171324306050200204660ustar00rootroot00000000000000# modified script yade/examples/gts-horse/horse.py from yade import pack,ymport import gts surf=gts.read(open('horse.coarse.gts')) pred=pack.inGtsSurface(surf) aabb=pred.aabb() dim0=aabb[1][0]-aabb[0][0]; radius=dim0/40. O.bodies.append(pack.regularHexa(pred,radius=radius,gap=radius/4.)) tetras = ymport.ele('horse.node','horse.ele',shift=(0,0,-1*(aabb[1][2]-aabb[0][2])),wire=False,color=(0,1,1),fixed=True) O.bodies.append(tetras) O.engines=[ ForceResetter(), InsertionSortCollider([Bo1_Sphere_Aabb(),Bo1_Polyhedra_Aabb()]), InteractionLoop( [Ig2_Sphere_Sphere_ScGeom(),Ig2_Sphere_Polyhedra_ScGeom(edgeCoeff=.1,vertexCoeff=.05)], [Ip2_FrictMat_FrictMat_FrictPhys()], [Law2_ScGeom_FrictPhys_CundallStrack()], ), NewtonIntegrator(damping=.1,gravity=(0,0,-5000)), PyRunner(iterPeriod=10,command='addPlotData()') ] O.dt=.4*PWaveTimeStep() O.saveTmp() O.timingEnabled=True O.trackEnergy=True from yade import plot plot.plots={'i':('total',O.energy.keys,)} def addPlotData(): plot.addData(i=O.iter,total=O.energy.total(),**O.energy) plot.plot(subPlots=False) from yade import qt qt.View() trunk-2018.02b/examples/polyhedra/irregular.py000066400000000000000000000036011324306050200213410ustar00rootroot00000000000000# gravity deposition, continuing with oedometric test after stabilization # shows also how to run parametric studies with yade-batch # The components of the batch are: # 1. table with parameters, one set of parameters per line (ccc.table) # 2. utils.readParamsFromTable which reads respective line from the parameter file # 3. the simulation muse be run using yade-batch, not yade # # $ yade-batch --job-threads=1 03-oedometric-test.table 03-oedometric-test.py # # create box with free top, and ceate loose packing inside the box from yade import plot, polyhedra_utils from yade import qt m = PolyhedraMat() m.density = 2600 #kg/m^3 m.young = 1E6 #Pa m.poisson = 20000/1E6 m.frictionAngle = 0.6 #rad O.bodies.append(utils.wall(0,axis=2,sense=1, material = m)) t = polyhedra_utils.polyhedra(m,size = (0.06,0.06,0.06),seed = 5) t.state.pos = (0.,0.,0.5) O.bodies.append(t) def checkUnbalanced(): # at the very start, unbalanced force can be low as there is only few contacts, but it does not mean the packing is stable print "unbalanced forces = %.5f, position %f, %f, %f"%(utils.unbalancedForce(), t.state.pos[0], t.state.pos[1], t.state.pos[2]) O.engines=[ ForceResetter(), InsertionSortCollider([Bo1_Polyhedra_Aabb(),Bo1_Wall_Aabb(),Bo1_Facet_Aabb()]), InteractionLoop( [Ig2_Wall_Polyhedra_PolyhedraGeom(), Ig2_Polyhedra_Polyhedra_PolyhedraGeom(), Ig2_Facet_Polyhedra_PolyhedraGeom()], [Ip2_PolyhedraMat_PolyhedraMat_PolyhedraPhys()], # collision "physics" [Law2_PolyhedraGeom_PolyhedraPhys_Volumetric()] # contact law -- apply forces ), #GravityEngine(gravity=(0,0,-9.81)), NewtonIntegrator(damping=0.5,gravity=(0,0,-9.81)), PyRunner(command='checkUnbalanced()',realPeriod=3,label='checker') ] #O.dt=0.025*polyhedra_utils.PWaveTimeStep() O.dt=0.00025 qt.Controller() V = qt.View() O.saveTmp() #O.run() #O.save('./done') utils.waitIfBatch() trunk-2018.02b/examples/polyhedra/sphere-interaction.py000066400000000000000000000016051324306050200231520ustar00rootroot00000000000000from yade import polyhedra_utils import random polyMat = PolyhedraMat(young=1e10,poisson=.05) frictMat = FrictMat(young=1e9,poisson=.2) O.materials.append((polyMat,frictMat)) poly = polyhedra_utils.polyhedra(polyMat,(1,2,3)); poly.wire=False sph1 = sphere((2,2,2),.5,material=frictMat) sph2 = sphere((-2,0,0),.5,material=frictMat) sph3 = sphere((0,-2,0),.5,material=frictMat) O.bodies.append(( poly, sph1, sph2, sph3 )) O.engines=[ ForceResetter(), InsertionSortCollider([Bo1_Polyhedra_Aabb(),Bo1_Sphere_Aabb()]), InteractionLoop( [Ig2_Sphere_Polyhedra_ScGeom()], [Ip2_FrictMat_PolyhedraMat_FrictPhys()], [Law2_ScGeom_FrictPhys_CundallStrack()], ), NewtonIntegrator(), ] O.dt = 1e-7 O.step() poly.state.blockedDOFs = 'xyzXYZ' for s in (sph1,sph2,sph3): r=random.random s.state.vel = -10*(s.state.pos+Vector3(r(),r(),r())) from yade import qt qt.View() trunk-2018.02b/examples/polyhedra/splitter.py000066400000000000000000000024161324306050200212160ustar00rootroot00000000000000from yade import polyhedra_utils gravel1 = PolyhedraMat() gravel1.IsSplitable = True gravel1.strength = 1e0 gravel2 = PolyhedraMat() gravel2.IsSplitable = True gravel2.strength = 2e0 gravel3 = PolyhedraMat() gravel3.IsSplitable = True gravel3.strength = 4e0 steel = PolyhedraMat() steel.young = 1e10 d = .05 p1 = polyhedra_utils.polyhedra(gravel1, size=(d,d,d), seed=1) p2 = polyhedra_utils.polyhedra(gravel2, size=(d,d,d), seed=1) p3 = polyhedra_utils.polyhedra(gravel3, size=(d,d,d), seed=1) p2.state.pos = (2*d,0,0) p3.state.pos = (4*d,0,0) p2.state.ori = p3.state.ori = p1.state.ori d = .035 w1 = utils.wall(+d, axis=1, sense=-1, material=steel) w2 = utils.wall(-d, axis=1, sense=+1, material=steel) v = 5e-1 w1.state.vel = (0,-v,0) w2.state.vel = (0,+v,0) O.bodies.append((p1,p2,p3,w1,w2)) O.engines=[ ForceResetter(), InsertionSortCollider([Bo1_Polyhedra_Aabb(),Bo1_Wall_Aabb()]), InteractionLoop( [Ig2_Wall_Polyhedra_PolyhedraGeom(), Ig2_Polyhedra_Polyhedra_PolyhedraGeom()], [Ip2_PolyhedraMat_PolyhedraMat_PolyhedraPhys()], [Law2_PolyhedraGeom_PolyhedraPhys_Volumetric()] ), NewtonIntegrator(), PolyhedraSplitter(iterPeriod=100), ] O.dt=1e-6 try: from yade import qt qt.Controller() v = qt.View() v.ortho = True except: pass O.run(30000) trunk-2018.02b/examples/polyhedra/tests/000077500000000000000000000000001324306050200201355ustar00rootroot00000000000000trunk-2018.02b/examples/polyhedra/tests/interactinDetectionFactor.py000066400000000000000000000012131324306050200256420ustar00rootroot00000000000000from yade import plot, polyhedra_utils from yade import qt m = PolyhedraMat() O.materials.append(m) t1,t2 = [polyhedra_utils.polyhedra(m,size=(1,1,1),seed=i) for i in (5,6)] O.bodies.append((t1,t2)) t2.state.pos = (3,0,0) t2.state.vel = (-1,0,0) factor = 2 O.engines=[ ForceResetter(), InsertionSortCollider([Bo1_Polyhedra_Aabb(aabbEnlargeFactor=factor)]), InteractionLoop( [Ig2_Polyhedra_Polyhedra_PolyhedraGeom(interactionDetectionFactor=factor)], [Ip2_PolyhedraMat_PolyhedraMat_PolyhedraPhys()], [Law2_PolyhedraGeom_PolyhedraPhys_Volumetric()] ), NewtonIntegrator(), ] O.dt=0.00001 try: qt.View() except: pass trunk-2018.02b/examples/polyhedra/tests/scGeom.py000066400000000000000000000014701324306050200217260ustar00rootroot00000000000000from yade import plot, polyhedra_utils from yade import qt scGeom = 1 m = FrictMat() if scGeom else PolyhedraMat() O.materials.append(m) t1,t2 = [polyhedron(((-1,-1,-1),(+1,-1,-1),(-1,+1,-1),(+1,+1,-1),(-1,-1,+1),(+1,-1,+1),(-1,+1,+1),(+1,+1,+1))) for i in (0,1)] O.bodies.append((t1,t2)) t2.state.pos = (3,0,0) t2.state.vel = (-1,0,0) O.engines=[ ForceResetter(), InsertionSortCollider([Bo1_Polyhedra_Aabb()]), InteractionLoop( [Ig2_Polyhedra_Polyhedra_ScGeom() if scGeom else Ig2_Polyhedra_Polyhedra_PolyhedraGeom()], [Ip2_FrictMat_FrictMat_FrictPhys() if scGeom else Ip2_PolyhedraMat_PolyhedraMat_PolyhedraPhys()], [Law2_ScGeom_FrictPhys_CundallStrack() if scGeom else Law2_PolyhedraGeom_PolyhedraPhys_Volumetric()] ), NewtonIntegrator(), ] O.dt=0.00001 try: qt.View() except: pass trunk-2018.02b/examples/polyhedra/textExport.py000066400000000000000000000003251324306050200215330ustar00rootroot00000000000000from yade import polyhedra_utils,export polyhedra_utils.fillBox((0,0,0), (0.3,0.3,0.3),defaultMaterial(),sizemin=(0.025,0.025,0.025),sizemax=(0.05,0.05,0.05),seed=4) export.textPolyhedra('/tmp/textPolyhedra.txt') trunk-2018.02b/examples/polyhedra/wall.py000066400000000000000000000035641324306050200203140ustar00rootroot00000000000000from yade import qt import numpy as np import random from yade import polyhedra_utils m = PolyhedraMat() m.density = 1000 m.young = 5E8 m.poisson = 5E6/5E8 m.frictionAngle = 0.7 size = 0.1; vertices = [[0,0,0],[size,0,0],[size,size,0],[size,size,size],[0,size,0],[0,size,size],[0,0,size],[size,0,size]] for i in range(0,10): for j in range(0,10): t = polyhedra_utils.polyhedra(m,v=vertices) t.state.pos = (0,(i+0.5)*size-5*size,(j+0.5)*size*1) O.bodies.append(t) qt.Controller() V = qt.View() V.screenSize = (750,550) V.sceneRadius = 1 V.eyePosition = (-1.5,1.5,0.5) V.lookAt = (0,-0.5,0) V.upVector = (0,0,1) O.bodies.append(utils.wall(0,axis=2,sense=1,material=m)) def checkUnbalanced(): print ('iter %d, time elapsed %f, unbalanced forces = %f'%(O.iter, O.realtime, utils.unbalancedForce())) if O.iter<200: return if utils.unbalancedForce()>0.01: return AddBall() def AddBall(): ball = polyhedra_utils.polyhedralBall(size*1, 40, m, (-size*2.5,0,size*7),mask=1) ball.shape.color = (0,0,0.9) ball.state.vel = (15,0,0) O.bodies.append(ball) checker.dead = True O.engines=[ ForceResetter(), InsertionSortCollider([Bo1_Sphere_Aabb(),Bo1_Polyhedra_Aabb(),Bo1_Wall_Aabb()]), InteractionLoop( [Ig2_Wall_Polyhedra_PolyhedraGeom(), Ig2_Polyhedra_Polyhedra_PolyhedraGeom(), Ig2_Facet_Polyhedra_PolyhedraGeom()], [Ip2_PolyhedraMat_PolyhedraMat_PolyhedraPhys()], # collision "physics" [Law2_PolyhedraGeom_PolyhedraPhys_Volumetric()] # contact law -- apply forces ), #GravityEngine(gravity=(0,0,-9.81)), NewtonIntegrator(damping=0.5,gravity=(0,0,-9.81)), PyRunner(command='checkUnbalanced()',realPeriod=3,label='checker')#, # wideo_recording #qt.SnapshotEngine(fileBase='W',iterPeriod=50,label='snapshot') ] #O.dt=.5*polyhedra_utils.PWaveTimeStep() O.dt = 0.000025 O.saveTmp() #O.run() #comment ball to see stability trunk-2018.02b/examples/polyhedraBreak/000077500000000000000000000000001324306050200177405ustar00rootroot00000000000000trunk-2018.02b/examples/polyhedraBreak/README000066400000000000000000000000571324306050200206220ustar00rootroot00000000000000The script was used for the paper [Gladk_2017] trunk-2018.02b/examples/polyhedraBreak/uniaxial_compression.py000066400000000000000000000125261324306050200245530ustar00rootroot00000000000000from yade import plot, polyhedra_utils, ymport, export import os.path utils.readParamsFromTable(descriptionIn = 'noDescription', frictionIn = 0.6, factorR = 1e4, densityIn = 2500.0, youngIn = 1e14, poissonIn = 0.3, numP = 2, C = 0.06, Z = 1, sizeB = 0.035, highB = 0.0315, odt = 1e-5, vtkP = 1000, maxIter = 200002, velT = 10e-4, velL = 2e-4, velB = 2e-4, tolerance = 0.05, startPos = 0.035, strength = 1e-2, strengthTau = 5e-3, Wei_m = 3, Wei_S0 = 8e5, Wei_V0 = 1e-9, Wei_P = 0.6, sigmaCZ = 5e9, sigmaCD = 1e10, ) from yade.params.table import * youngIn = youngIn/factorR sigmaCZ = sigmaCZ/factorR sigmaCD = sigmaCD/factorR #odt = odt*factorR mat1 = PolyhedraMat(density=densityIn, young=youngIn,poisson=poissonIn, frictionAngle=frictionIn,IsSplitable=True, strength=1e-2, strengthTau=3e-3, sigmaCZ = sigmaCZ, sigmaCD = sigmaCD, Wei_m=Wei_m, Wei_S0 = Wei_S0, Wei_V0=Wei_V0, Wei_P=Wei_P) #O.bodies.append(utils.wall(0,axis=2,sense=1, color=[0,1,1], material = mat1)) #O.bodies.append(utils.wall(-sizeB/2,axis=1,sense=1, color=[0,1,1], material = mat1)) #O.bodies.append(utils.wall(-sizeB/2,axis=0,sense=1, color=[0,1,1], material = mat1)) #t = polyhedra_utils.polyhedralBall(sizeB, 50, mat1, (0,0,0)) #t.state.pos = (0.,0.,0.020) #O.bodies.append(t) topmesh = O.bodies.append(geom.facetBox((0.,0.,startPos), (sizeB/2.0,sizeB/2.0,0.),material=mat1)) leftmesh = O.bodies.append(geom.facetBox((0.,sizeB/2.0,sizeB/2.0),(sizeB/2.0,0.,sizeB/2.0),material=mat1)) backmesh = O.bodies.append(geom.facetBox((sizeB/2.0,0.,sizeB/2.0),(0.,sizeB/2.0,sizeB/2.0),material=mat1)) vertices = [[0,0,0],[sizeB,0,0],[sizeB,sizeB,0],[sizeB,sizeB,sizeB],[0,sizeB,0],[0,sizeB,sizeB],[0,0,sizeB],[sizeB,0,sizeB]] t = polyhedra_utils.polyhedra(mat1,v=vertices, fixed=True) t.state.pos = (0,0,sizeB/2) O.bodies.append(t) """ i1 = ymport.textPolyhedra('new2.poly',shift=[0.012, 0.018, 0.0],material=mat1) importedStones = O.bodies.append(i1[1]) """ O.engines=[ ForceResetter(), InsertionSortCollider([Bo1_Polyhedra_Aabb(), Bo1_Wall_Aabb(), Bo1_Facet_Aabb()], verletDist=.05*sizeB), InteractionLoop( [Ig2_Facet_Polyhedra_PolyhedraGeom(), Ig2_Wall_Polyhedra_PolyhedraGeom(), Ig2_Polyhedra_Polyhedra_PolyhedraGeom()], [Ip2_PolyhedraMat_PolyhedraMat_PolyhedraPhys()], [Law2_PolyhedraGeom_PolyhedraPhys_Volumetric()] ), NewtonIntegrator(damping=0.8,gravity=(0,0,-9.81)), #PolyhedraSplitter(iterPeriod=1,label='Splitter'), SplitPolyMohrCoulomb(iterPeriod=10,dead=False,label='Splitter', fileName = "./StressData"), TranslationEngine(translationAxis=[0,0,-1],velocity=velT,ids=topmesh, label='TT'), TranslationEngine(translationAxis=[0,-1,0],velocity=velL,ids=leftmesh,label='TL'), TranslationEngine(translationAxis=[-1,0,0],velocity=velB,ids=backmesh,label='TB'), PyRunner(command='addPlotData()',iterPeriod=10), PyRunner(command='breakControl()',iterPeriod=10, dead=False), ] O.dt=odt fT =Vector3.Zero fL =Vector3.Zero fB =Vector3.Zero maxFT = Vector3.Zero maxFL = Vector3.Zero maxFB = Vector3.Zero def addPlotData(): global fT, maxFT global fL, maxFL global fB, maxFB fT=Vector3.Zero fL=Vector3.Zero fB=Vector3.Zero for i in topmesh: if (O.forces.f(i)): fT+=O.forces.f(i) for i in leftmesh: if (O.forces.f(i)): fL+=O.forces.f(i) for i in backmesh: if (O.forces.f(i)): fB+=O.forces.f(i) if (fT.norm() > maxFT.norm()): maxFT = fT if (fL.norm() > maxFL.norm()): maxFL = fL if (fB.norm() > maxFB.norm()): maxFB = fB SigmaT = fT[2]/(sizeB*sizeB)*factorR; uT = startPos - O.bodies[topmesh[0]].state.pos[2] SigmaL = fL[1]/(sizeB*sizeB)*factorR; uL = sizeB - O.bodies[leftmesh[0]].state.pos[1] SigmaB = fB[0]/(sizeB*sizeB)*factorR; uB = sizeB - O.bodies[backmesh[0]].state.pos[0] SigmaLT = 0. if (SigmaT): SigmaLT = SigmaL / SigmaT plot.addData(time=O.time, time2=O.time, Energy=utils.kineticEnergy(), SigmaT = SigmaT, uT = uT, SigmaL = SigmaL, uL = uL, SigmaB = SigmaB, uB = uB, SigmaLT = SigmaLT) for i in O.interactions: if (isinstance(O.bodies[i.id1].shape,Polyhedra)): print("%d\t%g\t%g\n" % (i.id1, i.geom.penetrationVolume, O.bodies[i.id1].shape.GetVolume())) if (isinstance(O.bodies[i.id2].shape,Polyhedra)): print("%d\t%g\t%g\n" % (i.id2, i.geom.penetrationVolume, O.bodies[i.id2].shape.GetVolume())) def breakControl(): if (len(O.bodies) > 7): sigmaResT = maxFT[2]/(sizeB*sizeB)*factorR uT = startPos - O.bodies[topmesh[0]].state.pos[2] print ("STOP!!!!!!!!!!!!!!!!") print ('maxFT = %g;'%(sigmaResT)) plot.saveGnuplot(descriptionIn + 'plot') if (not(os.path.isfile('./resFile'))): fWT = open('./resFile','w') fWT.write('m\tV0\tP\tSCz\tSCd\todt\tSRES\tfactorR\tU\n') fWT.close() fW = open('./resFile','a'); fW.write(('%g\t%g\t%g\t%g\t%g\t%g\t%g\t%g\t%g\n' % (Wei_m, Wei_V0, Wei_P, sigmaCZ, sigmaCD, odt, sigmaResT, factorR, uT))) fW.close() O.pause() plot.plots={'time':('Energy'),'uT':('SigmaT'),'uL':('SigmaL'),'uB':('SigmaB'),'time2':('SigmaLT')}; plot.plot() from yade import qt qt.Controller() V = qt.View() R=yade.qt.Renderer() R.bgColor=(1.,1.,1.) """ O.run(100000, True) """ trunk-2018.02b/examples/ring2d/000077500000000000000000000000001324306050200161715ustar00rootroot00000000000000trunk-2018.02b/examples/ring2d/ring.stl000066400000000000000000000063241324306050200176610ustar00rootroot00000000000000Binary STL output from Blender: /home/sega/data/yade/tmp/ring.stl @>>L >>L=D>9>L= >>LD>9>L=2>9>L 2>9>LD>9>L=l>C>L= 2>9>Ll>C>L=^>C>L ^>C>Ll>C>L=>3=L= ^>C>L>3=L=>=L >=L>3=L=?ȪL= >=L?ȪL=?i!"3L ?i!"3L?ȪL=><ǽL= ?i!"3L><ǽL=>ǽL >ǽL><ǽL=U>BCL= >ǽLU>BCL=_>CL _>CLU>BCL='>9L= _>CL'>9L=2>9L 2>9L'>9L=>L= 2>9L>L=>L >L>L=9>4ԾL= >L9>4ԾL=9>2ԾL 9>2ԾL9>4ԾL=C>^L= 9>2ԾLC>^L=C>`L C>`LC>^L==L= C>`L=L==L =L=L=4L= =L4L=L.L L.L4L=kǽL= L.LkǽL=ǽL ǽLkǽL=CjL= ǽLCjL=$C[L $C[LCjL=9EԾL= $C[L9EԾL=9,ԾL 9,ԾL9EԾL=L= 9,ԾLL=L LL=Ծ:L= LԾ:L=8Ծ9L 8Ծ9LԾ:L=I|CL= 8Ծ9LI|CL=dCL dCLI|CL=ǽL= dCLǽL=ǽL ǽLǽL= L= ǽL L=5L 5L L==L= 5L=L==L =L=L=~}C>L= =L~}C>L=W8C>L W8C>L~}C>L=cԾ9>L= W8C>LcԾ9>L=&Ծ9>L &Ծ9>LcԾ9>L=7>L= &Ծ9>L7>L=>L >L7>L=/:>L= >L/:>L=9>>L 9>>L/:>L=C5>L= 9>>LC5>L=Ch>L Ch>LC5>L=ǽ>L= Ch>Lǽ>L=]ǽ>L ]ǽ>Lǽ>L=-Dt?L= ]ǽ>L-Dt?L=wW5?L wW5?L-Dt?L==>L= wW5?L=>L=1=>L 1=>L=>L=C>>L= 1=>LC>>L=LC>S>L LC>S>LC>>L=d9>>L= LC>S>Ld9>>L=9> >L >>L=>>L9> >L >>L=9> >Ld9>>L= trunk-2018.02b/examples/ring2d/ringCundallDamping.py000066400000000000000000000035401324306050200223070ustar00rootroot00000000000000#!/usr/bin/python # -*- coding: utf-8 -*- from yade import ymport sphereRadius=0.05 ## Import wall's geometry walls = O.bodies.append(ymport.stl('ring.stl')) def fill_cylinder_with_spheres(sphereRadius,cylinderRadius,cylinderHeight,cylinderOrigin,cylinderSlope): spheresCount=0 for h in xrange(0,int(cylinderHeight/sphereRadius/2)): for r in xrange(1,int(cylinderRadius/sphereRadius/2)): dfi = asin(0.5/r)*2 for a in xrange(0,int(6.28/dfi)): x = cylinderOrigin[0]+2*r*sphereRadius*cos(dfi*a) y = cylinderOrigin[1]+2*r*sphereRadius*sin(dfi*a) z = cylinderOrigin[2]+h*2*sphereRadius s=sphere([x,y*cos(cylinderSlope)+z*sin(cylinderSlope),z*cos(cylinderSlope)-y*sin(cylinderSlope)],sphereRadius) O.bodies.append(s) spheresCount+=1 return spheresCount ## Spheres spheresCount=0 spheresCount+=fill_cylinder_with_spheres(sphereRadius,0.5,0.10,[0,0,0],radians(0)) print "Number of spheres: %d" % spheresCount ## Engines O.engines=[ ## Resets forces and momenta the act on bodies ForceResetter(), ## Using bounding boxes find possible body collisions. InsertionSortCollider([ Bo1_Sphere_Aabb(), Bo1_Facet_Aabb(), ]), InteractionLoop( [Ig2_Sphere_Sphere_ScGeom(),Ig2_Facet_Sphere_ScGeom()], [Ip2_FrictMat_FrictMat_FrictPhys()], [Law2_ScGeom_FrictPhys_CundallStrack()], ), ## Apply gravity ## NOTE: Non zero Cundall damping affected a dynamic simulation! NewtonIntegrator(damping=0.3,gravity=[0,-9.81,0]), ## Apply kinematics to walls ## angularVelocity = 0.73 rad/sec = 7 rpm RotationEngine(ids=walls,rotationAxis=[0,0,1],rotateAroundZero=True,angularVelocity=0.73) ] for b in O.bodies: if isinstance(b.shape,Sphere): b.state.blockedDOFs='z' # blocked movement along Z O.dt=0.02*PWaveTimeStep() O.saveTmp('init'); from yade import qt renderer=qt.Renderer() renderer.wire=True #qt.Controller() qt.View() O.run() trunk-2018.02b/examples/ring2d/ringSimpleViscoelastic.py000066400000000000000000000044161324306050200232320ustar00rootroot00000000000000#!/usr/bin/python # -*- coding: utf-8 -*- from yade import ymport ## Omega o=Omega() ## PhysicalParameters Density=2400 frictionAngle=radians(35) sphereRadius=0.05 tc = 0.001 en = 0.3 es = 0.3 ## Import wall's geometry facetMat=O.materials.append(ViscElMat(frictionAngle=frictionAngle,tc=tc,en=en,et=es)) sphereMat=O.materials.append(ViscElMat(density=Density,frictionAngle=frictionAngle,tc=tc,en=en,et=es)) walls = O.bodies.append(ymport.stl('ring.stl',material=facetMat)) def fill_cylinder_with_spheres(sphereRadius,cylinderRadius,cylinderHeight,cylinderOrigin,cylinderSlope): spheresCount=0 for h in xrange(0,int(cylinderHeight/sphereRadius/2)): for r in xrange(1,int(cylinderRadius/sphereRadius/2)): dfi = asin(0.5/r)*2 for a in xrange(0,int(6.28/dfi)): x = cylinderOrigin[0]+2*r*sphereRadius*cos(dfi*a) y = cylinderOrigin[1]+2*r*sphereRadius*sin(dfi*a) z = cylinderOrigin[2]+h*2*sphereRadius s=sphere([x,y*cos(cylinderSlope)+z*sin(cylinderSlope),z*cos(cylinderSlope)-y*sin(cylinderSlope)],sphereRadius,material=sphereMat) o.bodies.append(s) spheresCount+=1 return spheresCount # Spheres spheresCount=0 spheresCount+=fill_cylinder_with_spheres(sphereRadius,0.5,0.10,[0,0,0],radians(0)) print "Number of spheres: %d" % spheresCount ## Engines o.engines=[ ## Resets forces and momenta the act on bodies ForceResetter(), ## Using bounding boxes find possible body collisions. InsertionSortCollider([ Bo1_Sphere_Aabb(), Bo1_Facet_Aabb(), ]), # Interactions InteractionLoop( ## Create geometry information about each potential collision. [Ig2_Sphere_Sphere_ScGeom(), Ig2_Facet_Sphere_ScGeom()], ## Create physical information about the interaction. [Ip2_ViscElMat_ViscElMat_ViscElPhys()], ## Constitutive law [Law2_ScGeom_ViscElPhys_Basic()], ), ## Apply gravity ## Cundall damping must been disabled! NewtonIntegrator(damping=0,gravity=[0,-9.81,0]), ## Apply kinematics to walls ## angularVelocity = 0.73 rad/sec = 7 rpm RotationEngine(ids=walls,rotationAxis=[0,0,1],rotateAroundZero=True,angularVelocity=0.73) ] for b in O.bodies: if isinstance(b.shape,Sphere): b.state.blockedDOFs='zXY' O.dt=0.02*tc O.saveTmp('init'); from yade import qt renderer=qt.Renderer() renderer.wire=True #qt.Controller() qt.View() O.run() trunk-2018.02b/examples/rod-penetration/000077500000000000000000000000001324306050200201165ustar00rootroot00000000000000trunk-2018.02b/examples/rod-penetration/model.py000066400000000000000000000037101324306050200215710ustar00rootroot00000000000000#!/usr/bin/python # -*- coding: utf-8 -*- import random from yade import ymport ## PhysicalParameters ## Variant of mesh mesh = 'coarse' #mesh = 'fine' #mesh = 'tiny' ## Import geometry rod = O.bodies.append(ymport.stl('rod-'+mesh+'.stl',wire=True)) # Spheres sphereRadius = 0.01 nbSpheres = (32,11,32) print "Creating %d spheres..."%(nbSpheres[0]*nbSpheres[1]*nbSpheres[2]), for i in xrange(nbSpheres[0]): for j in xrange(nbSpheres[1]): for k in xrange(nbSpheres[2]): x = (i*2 - nbSpheres[0])*sphereRadius*1.1+sphereRadius*random.uniform(-0.1,0.1) y = -j*sphereRadius*2.2-0.01 z = (k*2 - nbSpheres[2])*sphereRadius*1.1+sphereRadius*random.uniform(-0.1,0.1) r = random.uniform(sphereRadius,sphereRadius*0.9) fixed = False color=[0.51,0.52,0.4] if (i==0 or i==nbSpheres[0]-1 or j==nbSpheres[1]-1 or k==0 or k==nbSpheres[2]-1): fixed = True color=[0.21,0.22,0.1] O.bodies.append(sphere([x,y,z],r,color=color,fixed=fixed)) print "done\n" ## Estimate time step #O.dt=PWaveTimeStep() O.dt=0.0001 ## Engines O.engines=[ ## Resets forces and momenta the act on bodies ForceResetter(), ## Using bounding boxes find possible body collisions. InsertionSortCollider([ Bo1_Sphere_Aabb(), Bo1_Facet_Aabb(), ]), InteractionLoop( [Ig2_Sphere_Sphere_ScGeom(),Ig2_Facet_Sphere_ScGeom()], [Ip2_FrictMat_FrictMat_FrictPhys()], [Law2_ScGeom_FrictPhys_CundallStrack()] ), ## Apply gravity ## Motion equation NewtonIntegrator(damping=0.3,gravity=[0,-9.81,0]), ## Apply kinematics to rod TranslationEngine(ids=rod,translationAxis=[0,-1,0],velocity=0.075), ## Save force on rod #ForceRecorder(ids=rod,file='force-'+mesh+'.dat',iterPeriod=50), ] import sys,time print "Start simulation: " + mesh nbIter=10000 from yade import qt qt.View() O.stopAtIter=nbIter O.run() #for t in xrange(2): # start=time.time();O.run(nbIter);O.wait();finish=time.time() # speed=nbIter/(finish-start); print '%g iter/sec\n'%speed #print "FINISH" #quit() trunk-2018.02b/examples/rod-penetration/rod-coarse.stl000066400000000000000000000012541324306050200227020ustar00rootroot00000000000000Binary STL output from Blender: /home/sega/YADE/data/rod_penetration/rod.stl =?̽=À;̽̽`À;̽ =?̽̽`À;̽̽?̽ =?=̽?=̽À;= =?=̽À;==;= =?̽=?==;= =?̽=;==À;̽ =À;̽=;=̽À;= =À;̽̽À;=̽`À;̽ ̽`À;̽̽À;=̽?= ̽`À;̽̽?=̽?̽ =?==?̽̽?̽ =?=̽?̽̽?= trunk-2018.02b/examples/rod-penetration/rod-fine.stl000066400000000000000000004026041324306050200223530ustar00rootroot00000000000000Created by GmshX a 6̽a 6<%?̽=%?̽8;:<̽o;̽9co;̽%?̽SL@=z>̽9c%?̽H=H<̽dZ=+=̽ >=e=̽ >=e=̽ox=;<̽H=H<̽ox=;<̽9c=o;̽H=H<̽I=%?̽}s==>̽9c=%?̽FR>̽I%?̽9c%?̽ox=;<̽I=o;̽9c=o;̽Io;̽Q4<̽9co;̽FR>̽9c%?̽SL@=z>̽9co;̽Q4<̽8;:<̽̽S>̽Kऽ>̽̽>̽=o;̽H=H<̽9c=o;̽H=H<̽=o;̽ <"<̽=h>̽=#L>̽=>̽=X94=̽=lF=̽=o=̽u=>̽[=~>̽?v\=@x>̽Kऽ>̽YO1>̽z쎽p|>̽׃=X>̽PM=>̽ۑ=>̽/(=>̽PM=>̽׃=X>̽]=/.=̽%#=@T=̽<=̽I\=̽==̽&{Ai<=̽=в=̽]=/.=̽㝢==̽c =E1=̽dZ=+=̽H=H<̽ <"<̽c =E1=̽H=H<̽==̽g=̽&{Ai<=̽gq<9C=̽g=̽<=̽1&̽B=aC>̽'I=d>̽>&>̽̽RnBK>̽>̽SL@=z>̽%?̽8;:<̽KѼR<̽o;̽(h=̽1% w=̽8;:<̽Q4<̽(h=̽8;:<̽̽B=aC>̽2=_>̽dZ=+=̽{=V=̽ >=e=̽{=V=̽=lF=̽ >=e=̽@;>̽FR>̽SL@=z>̽=>̽U=>̽=%?̽U=>̽I=%?̽=%?̽g=̽UHf<̽&{Ai<=̽g=̽gq<9C=̽UHf<̽E<>̽tл8>̽AG;>̽a 6%?̽>̽%?̽KѼR<̽a 6o;̽o;̽׃=X>̽ۑ=>̽rH=>̽?ܼ>>̽>̽u08>̽=̽bЛ=̽==̽<=̽%#=@T=̽1&̽4/>̽>&>̽<=̽1&̽'I=d>̽B=aC>̽/(=>̽׃=X>̽'I=d>̽<>̽'I=d>̽̽ >=e=̽Э=Ǎ<̽ox=;<̽k"E>̽c>̽B3A{>̽[=~>̽u=>̽=|>̽Э=Ǎ<̽=<̽=o;̽Э=Ǎ<̽I=o;̽ox=;<̽I=o;̽Э=Ǎ<̽=o;̽s>̽ 窫>̽9>̽ 窫>̽4̽9>̽l;l>̽RnBK>̽̽l;l>̽g>̽RnBK>̽=<̽Э=Ǎ<̽=X94=̽ >=e=̽=lF=̽Э=Ǎ<̽Э=Ǎ<̽=lF=̽=X94=̽T>̽PrpL>̽x2?>̽PM=>̽=S>̽=>̽˛=>̽;yg=l/>̽j=,2>̽̽>̽g|f>̽̽5^>̽̽5^>̽В02>̽̽ >̽̽5^>̽g|f>̽В02>̽^ق>̽В02>̽=x˼>̽g|f>̽=x˼>̽В02>̽̽㥛>̽ͦMْ>̽̽sh>̽=E>̽=㥛>̽=sh>̽.=V>̽=B`e>̽=`P>̽Aj=Vj>̽=B`e>̽.=V>̽̽B`e>̽T>̽̽`P>̽̽B`e>̽Xj>̽T>̽1% w=̽KѼR<̽8;:<̽1% w=̽I\=̽KѼR<̽KѼR<̽I\=̽&{Ai<=̽2=_>̽B=aC>̽<>̽<>̽E<>̽b<&r>̽b<&r>̽2=_>̽<>̽]=/.=̽<=̽h=+>̽R;U=̽<=̽%;:=̽g>̽;#<>̽u08>̽u08>̽>̽g>̽7=V]=̽{=V=̽dZ=+=̽u\=Fc=̽{=V=̽7=V]=̽UHf<̽ <"<̽a 6̽̽S>̽Qӫ\>̽NJBk|>̽Rռ>̽4/>̽6!=̽(h=̽Q4<̽6!=̽G b=̽(h=̽1% w=̽(h=̽AqdW=̽G b=̽AqdW=̽(h=̽usH>̽Qӫ\>̽FR>̽FR>̽@;>̽usH>̽b<&r>̽AG;>̽a 6<%?̽AG;>̽b<&r>̽E<>̽AG;>̽a 6%?̽a 6<%?̽U=>̽=>̽=#L>̽}s==>̽U=>̽=#L>̽U=>̽}s==>̽I=%?̽7=V]=̽dZ=+=̽c =E1=̽gq<9C=̽c =E1=̽ <"<̽gq<9C=̽̽3t=E\>̽/(=>̽3t=E\>̽=#L>̽/(=>̽=#L>̽3t=E\>̽}s==>̽usH>̽NJBk|>̽YO1>̽Kऽ>̽usH>̽YO1>̽usH>̽Kऽ>̽Qӫ\>̽AqdW=̽I\=̽1% w=̽8>NB4=̽I\=̽AqdW=̽==̽I\=̽=̽=̽I\=̽8>NB4=̽G b=̽8>NB4=̽AqdW=̽侍V=̽8>NB4=̽G b=̽̽̽;#<>̽㋼S>̽?ܼ>>̽u08>̽㋼S>̽ 窫>̽s>̽1ɜ>̽ͦMْ>̽̽㥛>̽ʩ=w>̽=S>̽PM=>̽ʩ=w>̽/(=>̽=#L>̽/(=>̽ʩ=w>̽PM=>̽3C>̽e6>̽PrpL>̽e6>̽x2?>̽PrpL>̽̽@=>̽̽?v\=@x>̽=d$>̽(#c=>̽̽`P>̽%'^C>̽̽j<>̽x2?>̽%'^C>̽T>̽%'^C>̽̽`P>̽T>̽}=Q@>̽=`P>̽=j<>̽=`P>̽}=Q@>̽.=V>̽lC2>̽̽'>̽̽j<>̽~<>̽*m7=\>̽h=+>̽<>̽~<>̽h=+>̽4*%n>̽uμk\u>̽ͼ_d>̽uμk\u>̽vZ!l>̽ͼ_d>̽Z4.Z>̽n/{R>̽ͼ_d>̽B3A{>̽lq>̽Xj>̽̽u08>̽;#<>̽iv0>̽)|5A>̽^ق>̽)|5A>̽iv0>̽s>̽9>̽;P̽@a>̽;P̽9>̽4̽7A>̽8B I>̽9P>̽n/{R>̽8B I>̽9м3=>̽)̽7A>̽9P>̽<9o>̽%]Б>̽@a>̽̽>̽2ߩ>̽g|f>̽9<>̽̽;#<>̽.=V>̽dX<*>̽̽2=_>̽'D=F>̽3t=E\>̽H.>̽̽>̽Kऽ>̽z쎽p|>̽H.>̽Kऽ>̽vnD=V0>̽*+=Ԓ>̽(#c=>̽=㥛>̽A=L>̽=T>̽g>̽~>̽RnBK>̽PrpL>̽'T>̽3C>̽@=!V>̽9e=GS>̽h4=@>̽sCul>̽YX>̽k"E>̽ͦMْ>̽sCul>̽k"E>̽YX>̽sCul>̽e^e>̽=x˼>̽;+,>̽^ق>̽ 2N=e>̽j=ch>̽9e=GS>̽.=V>̽j=ch>̽Aj=Vj>̽j=ch>̽.=V>̽9e=GS>̽[=~>̽`Z*=>̽?v\=@x>̽ <>̽`Z*=>̽=Ɂ>̽`Z*=>̽[=~>̽=Ɂ>̽2>̽zʼh>̽ S>̽|{8`>̽T>̽Xj>̽T>̽|{8`>̽PrpL>̽?v\=@x>̽¸==͓>̽u=>̽¸==͓>̽=E>̽u=>̽¸==͓>̽?v\=@x>̽(#c=>̽U>̽9>̽@a>̽9>̽U>̽s>̽w>̽RnBK>̽~>̽s{>̽̽t>̽̽'>̽Q->̽3C>̽9м3=>̽dz;Q>̽;#<>̽g>̽dz;Q>̽l;l>̽9<>̽l;l>̽dz;Q>̽g>̽;#<>̽dz;Q>̽9<>̽h4=@>̽=H0>̽K<A>̽<=̽ ԉ̽ ԉ̽h=+>̽%;:=̽sH=̽R;U=̽sH=̽%;:=̽bЛ=̽=lF=̽*{==̽=o=̽ࡽI=̽0NU˾=̽侍V=̽=@r>̽M=>̽vnD=V0>̽@&=Ẵ>̽=5^>̽= >̽p=>̽*m7=\>̽;yg=l/>̽p=>̽˛=>̽v= >̽˛=>̽p=>̽;yg=l/>̽*m7=\>̽p=>̽v= >̽E1;>̽%]Б>̽<9o>̽ <>̽E1;>̽<9o>̽=[B>̽9e=GS>̽.=V>̽q7:z>̽ 窫>̽㋼S>̽q7:z>̽u08>̽̽u08>̽q7:z>̽㋼S>̽5=>̽M=>̽?_=VW>̽ͼ_d>̽vZ!l>̽Z4.Z>̽k; w>̽|ɯ<e>̽rh;4`>̽Xj>̽lq>̽|{8`>̽lC2>̽s{>̽̽'>̽;P̽<9o>̽@a>̽%'^C>̽lC2>̽̽j<>̽%'^C>̽x2?>̽lC2>̽vZ!l>̽uμk\u>̽c>̽uμk\u>̽4*%n>̽ S>̽ &4>̽8B I>̽7A>̽ &4>̽9м3=>̽8B I>̽)̽P<42>̽7A>̽K<A>̽P<42>̽)̽rh;4`>̽)̽9P>̽8B I>̽Z4.Z>̽9P>̽n/{R>̽Z4.Z>̽8B I>̽;+,>̽iv0>̽^ق>̽?ܼ>>̽iv0>̽;+,>̽~>̽>̽F>4>̽g>̽>̽~>̽4/>̽RnBK>̽w>̽=H0>̽P<42>̽K<A>̽K<A>̽@=!V>̽h4=@>̽'T>̽9м3=>̽3C>̽n/{R>̽9м3=>̽'T>̽=[B>̽}=Q@>̽j=,2>̽.=V>̽}=Q@>̽=[B>̽sCul>̽1ɜ>̽e^e>̽ͦMْ>̽1ɜ>̽sCul>̽bЛ=̽=̽sH=̽u=̽̽X94=̽̽o=̽}s==>̽'D=F>̽9c=%?̽}s==>̽3t=E\>̽'D=F>̽iv0>̽㋼S>̽s>̽?ܼ>>̽㋼S>̽iv0>̽̽̽;#<>̽̽dX<*>̽̽`Z*=>̽=d$>̽?v\=@x>̽ <>̽=d$>̽`Z*=>̽.=V>̽@=>̽?_=VW>̽̽@=>̽.=V>̽~<>̽~<=#>̽*m7=\>̽󧘼 >̽A!1>̽zʼh>̽R;U=̽ ԉ̽=@r>̽vnD=V0>̽Rռ>̽>&>̽4/>̽>&>̽Rռ>̽tл8>̽Qӫ\>̽̽S>̽̽h>̽9c=%?̽'D=F>̽=%?̽b<&r>̽'D=F>̽2=_>̽'D=F>̽b<&r>̽=%?̽(#c=>̽œ=ў>̽vnD=V0>̽œ=ў>̽(#c=>̽=d$>̽u=̽6!=̽̽X94=̽=H0>̽;yg=l/>̽*m7=\>̽;yg=l/>̽=H0>̽h4=@>̽*m7=\>̽~<=#>̽=H0>̽H.>̽̽>̽̽>̽̽>̽2ߩ>̽̽>̽̽>̽H.>̽2ߩ>̽H.>̽z쎽p|>̽2ߩ>̽̽t>̽oH >̽̽=̽̽t>̽s{>̽oH >̽=[B>̽h4=@>̽9e=GS>̽h4=@>̽=[B>̽;yg=l/>̽=[B>̽j=,2>̽;yg=l/>̽M=>̽*+=Ԓ>̽vnD=V0>̽M=>̽5=>̽*+=Ԓ>̽[=Ϫ>̽*+=Ԓ>̽5=>̽t׮>̽Fj>̽ZP=̽Fj>̽<>̽ZP=̽=h>̽=S>̽ʩ=w>̽=#L>̽=h>̽ʩ=w>̽@=!V>̽ 2N=e>̽9e=GS>̽@=!V>̽=p>̽ 2N=e>̽@=!V>̽|ɯ<e>̽=p>̽n/{R>̽'T>̽ͼ_d>̽ͼ_d>̽'T>̽4*%n>̽zʼh>̽A!1>̽c>̽E1;>̽ <>̽̽׃=X>̽rH=>̽'I=d>̽rH=>̽̽'I=d>̽Rռ>̽NJBk|>̽@;>̽usH>̽@;>̽NJBk|>̽>̽@;>̽SL@=z>̽Rռ>̽@;>̽>̽̽9<>̽9 =>̽4̽œ=ў>̽;P̽4̽=@r>̽œ=ў>̽F>4>̽;+,>̽=x˼>̽;+,>̽F>4>̽>̽?ܼ>>̽;+,>̽>̽t׮>̽,n">̽Fj>̽ ԉ̽ ԉ̽YX>̽e^e>̽֝ >̽2>̽YX>̽=E>̽A=L>̽=㥛>̽A=L>̽=E>̽¸==͓>̽*+=Ԓ>̽A=L>̽(#c=>̽A=L>̽¸==͓>̽(#c=>̽Ǚ= >̽=t>̽==̽[=Ϫ>̽= >̽=T>̽[=Ϫ>̽@&=Ẵ>̽= >̽A=L>̽[=Ϫ>̽=T>̽7A>̽P<42>̽ &4>̽K<>̽<>̽Fj>̽~<>̽<>̽K<>̽~<>̽K<>̽~<=#>̽M=>̽.=V>̽?_=VW>̽M=>̽=@r>̽.=V>̽dX<*>̽.=V>̽4̽=@r>̽4̽.=V>̽a 6o;̽KѼR<̽UHf<̽UHf<̽KѼR<̽&{Ai<=̽̽B3A{>̽c>̽ S>̽4*%n>̽c>̽ 2N=e>̽=p>̽[=~>̽=p>̽=Ɂ>̽[=~>̽v= >̽h=+>̽*m7=\>̽v= >̽]=/.=̽h=+>̽- bΟ>̽s>̽U>̽M{c>̽c>̽k"E>̽M{c>̽YX>̽2>̽YX>̽M{c>̽k"E>̽{>̽Mv>̽F>4>̽=x˼>̽{>̽F>4>̽>̽TgS>̽Rռ>̽TgS>̽tл8>̽Rռ>̽x2?>̽MP.>̽lC2>̽tл8>̽̽>&>̽̽̽>&>̽ZP=̽5V=̽t׮>̽5V=̽ >d=̽t׮>̽L>̽Mv>̽z쎽p|>̽YO1>̽L>̽z쎽p|>̽$>̽Fj>̽,n">̽Fj>̽$>̽K<>̽=̽oH >̽ >̽zhs=̽=̽ >̽tл8>̽E<>̽̽=̽̽=̽oH >̽M{c>̽ S>̽c>̽2>̽ S>̽M{c>̽R;U=̽5V=̽ZP=̽R;U=̽sH=̽5V=̽2>̽󧘼 >̽zʼh>̽2>̽֝ >̽󧘼 >̽P<42>̽$>̽ &4>̽P<42>̽K<>̽$>̽4*%n>̽lq>̽c>̽c>̽ S>̽zʼh>̽c>̽uμk\u>̽ S>̽U>̽󧘼 >̽֝ >̽- bΟ>̽U>̽֝ >̽P<42>̽~<=#>̽K<>̽~<=#>̽P<42>̽=H0>̽==̽==̽㝢==̽*+=Ԓ>̽[=Ϫ>̽A=L>̽a 6%?̽TgS>̽>̽TgS>̽a 6%?̽AG;>̽TgS>̽AG;>̽tл8>̽Q->̽9м3=>̽,n">̽$>̽,n">̽ &4>̽,n">̽9м3=>̽ &4>̽%]Б>̽󧘼 >̽@a>̽%]Б>̽A!1>̽󧘼 >̽󧘼 >̽U>̽@a>̽ 窫>̽q7:z>̽4̽dX<*>̽q7:z>̽̽q7:z>̽dX<*>̽4̽- bΟ>̽)|5A>̽s>̽)|5A>̽- bΟ>̽e^e>̽- bΟ>̽֝ >̽e^e>̽Ǚ= >̽==̽㝢==̽Ǚ= >̽v= >̽˛=>̽=t>̽Ǚ= >̽˛=>̽8>NB4=̽0NU˾=̽=̽0NU˾=̽8>NB4=̽侍V=̽zhs=̽ >d=̽0NU˾=̽sH=̽ >d=̽5V=̽=x˼>̽g|f>̽{>̽{>̽2ߩ>̽z쎽p|>̽Mv>̽{>̽z쎽p|>̽2ߩ>̽{>̽g|f>̽<9o>̽=d$>̽ <>̽<9o>̽œ=ў>̽=d$>̽œ=ў>̽<9o>̽;P̽u=̽G b=̽6!=̽G b=̽u=̽侍V=̽̽B`e>̽̽#y>̽Xj>̽̽#y>̽B3A{>̽Xj>̽̽+>̽B3A{>̽̽#y>̽=#y>̽=B`e>̽Aj=Vj>̽=|>̽=#y>̽Aj=Vj>̽j=ch>̽ 2N=e>̽[=~>̽j=ch>̽=|>̽Aj=Vj>̽=|>̽j=ch>̽[=~>̽)̽rh;4`>̽|ɯ<e>̽@=!V>̽)̽|ɯ<e>̽K<A>̽)̽@=!V>̽{=V=̽*{==̽=lF=̽=в=̽*{==̽u\=Fc=̽*{==̽{=V=̽u\=Fc=̽?_=VW>̽0=Z>̽5=>̽@&=Ẵ>̽5=>̽0=Z>̽[=Ϫ>̽5=>̽@&=Ẵ>̽E1;>̽A!1>̽%]Б>̽A!1>̽E1;>̽c>̽̽=̽̽=̽=̽=̽ࡽI=̽̽=̽̽+>̽jՊ>̽B3A{>̽k"E>̽jՊ>̽ͦMْ>̽jՊ>̽k"E>̽B3A{>̽l= f=̽㝢==̽]=/.=̽l= f=̽v= >̽Ǚ= >̽v= >̽l= f=̽]=/.=̽㝢==̽l= f=̽Ǚ= >̽=̽P=̽sH=̽ >d=̽P=̽0NU˾=̽P=̽ >d=̽sH=̽P=̽=̽0NU˾=̽̽sh>̽jՊ>̽̽+>̽̽sh>̽ͦMْ>̽jՊ>̽Q->̽e6>̽3C>̽MP.>̽x2?>̽e6>̽qc>̽MP.>̽e6>̽e6>̽Q->̽qc>̽ࡽI=̽̽1=̽̽=̽j=,2>̽='>̽˛=>̽j=,2>̽}=Q@>̽='>̽='>̽}=Q@>̽=j<>̽˛=>̽='>̽=t>̽Mv>̽~>̽F>4>̽Mv>̽w>̽~>̽Z4.Z>̽rh;4`>̽9P>̽Z4.Z>̽vZ!l>̽rh;4`>̽̽̽̽E<>̽<>̽̽<>̽̽̽s{>̽qc>̽ >̽ >̽oH >̽s{>̽MP.>̽s{>̽lC2>̽qc>̽s{>̽MP.>̽t׮>̽ >d=̽54Fe>̽54Fe>̽zhs=̽ >̽ >d=̽zhs=̽54Fe>̽54Fe>̽ >̽qc>̽|{8`>̽'T>̽PrpL>̽4*%n>̽'T>̽|{8`>̽4*%n>̽|{8`>̽lq>̽=Ɂ>̽̽ <>̽̽=p>̽|ɯ<e>̽k; w>̽̽|ɯ<e>̽=p>̽̽=Ɂ>̽=в=̽V4=@ =̽*{==̽V4=@ =̽=в=̽㝢==̽HՐ=̽̽1=̽ࡽI=̽HՐ=̽侍V=̽u=̽侍V=̽HՐ=̽ࡽI=̽54Fe>̽zgmv>̽t׮>̽zgmv>̽,n">̽t׮>̽HՐ=̽̽o=̽̽1=̽u=̽̽o=̽HՐ=̽=o=̽V4=@ =̽=1=̽=o=̽*{==̽V4=@ =̽L>̽YO1>̽NJBk|>̽4/>̽L>̽NJBk|>̽4/>̽w>̽L>̽w>̽Mv>̽L>̽=+>̽u=>̽=E>̽u=>̽=+>̽=|>̽=#y>̽=|>̽=+>̽=E>̽=sh>̽=+>̽==̽=1=̽V4=@ =̽㝢==̽==̽V4=@ =̽,n">̽zgmv>̽Q->̽zgmv>̽qc>̽Q->̽qc>̽zgmv>̽54Fe>̽vZ!l>̽c>̽k; w>̽rh;4`>̽vZ!l>̽k; w>̽E1;>̽̽k; w>̽c>̽E1;>̽k; w>̽ࡽI=̽+!=̽0NU˾=̽zhs=̽+!=̽=̽+!=̽zhs=̽0NU˾=̽+!=̽ࡽI=̽=̽=,>̽=>̽=>̽=>̽ =>̽=>̽p=T>̽=>̽=5^>̽̽̽rH=>̽9 =>̽̽rH=>̽=,>̽PM=>̽=>̽ۑ=>̽PM=>̽=,>̽ =>̽=,>̽=>̽p=T>̽ =>̽=>̽=T>̽ =>̽p=T>̽0=Z>̽?_=VW>̽@=>̽̽9 =>̽@=>̽>{=>̽rH=>̽ۑ=>̽=q>̽=5^>̽@&=Ẵ>̽=q>̽0=Z>̽p=T>̽0=Z>̽=q>̽@&=Ẵ>̽=5^>̽=q>̽p=T>̽kL>̽̽>̽̽%?̽I%?̽kL>̽̽%?̽0=Z>̽=o>̽p=T>̽=o>̽=T>̽p=T>̽=o>̽0=Z>̽@=>̽>{=>̽9 =>̽rH=>̽9 =>̽>{=>̽@=>̽>{=>̽=T>̽=o>̽>{=>̽=o>̽@=>̽=,>̽ =>̽ۑ=>̽ =>̽>{=>̽ۑ=>̽ =>̽=T>̽>{=>̽kL>̽̽h>̽̽>̽kL>̽FR>̽Qӫ\>̽FR>̽kL>̽I%?̽Qӫ\>̽̽h>̽kL>̽)|5A>̽>̽^ق>̽>̽В02>̽^ق>̽̽l;l>̽̽̽9<>̽l;l>̽̽̽̽̽9 =>̽9<>̽e^e>̽>̽)|5A>̽>̽e^e>̽1ɜ>̽Yﶽ՟>̽>̽1ɜ>̽6/N<̽Io;̽̽o;̽̽<̽6/N<̽̽o;̽Yﶽ՟>̽̽㥛>̽̽T>̽1ɜ>̽̽㥛>̽Yﶽ՟>̽̽X94=̽6/N<̽̽<̽Q4<̽6/N<̽6!=̽6/N<̽Q4<̽Io;̽̽X94=̽6!=̽6/N<̽Yﶽ՟>̽̽T>̽̽ >̽̽ >̽В02>̽>̽>̽Yﶽ՟>̽̽ >̽̽"< <̽o;a 6<̽o;=̽)>fռ̽%?a 6̽%?̽o;̽:<8;̽o;9c̽%?=̽^j>};=̽%?9c=̽+=dZ=̽H=̽>{6̽޼>5K̽g>̽;=̽H 8̽g>̽޼>5K̽%?9c̽> 8̽޼>5K̽%?I̽> 8̽%?9c̽o;I=̽;=̽%?I=̽%?9c=̽ <̽o;I̽o;9c̽>=̽%?9c=̽^j>};=̽ <̽o;9c̽:<8;̽H5K̽%?̽%?9c̽%?̽޼>5K̽)>fռ̽޼>5K̽>{6̽)>fռ̽DF=)=̽X94==̽o==̽Ϥ>9<̽ٵ>qE=̽e>2=̽ z>`̽og>ų̽@p>Q̽w>0 =̽z>`=̽p>&=̽X>̽`>i+̽i>z`k̽t>sS̽X>̽>{6̽d>*̽X>̽t>sS̽6>.ں̽>k+̽H>@̽X>̽>k+̽`>i+̽ԫ=l"=̽=H=̽=<̽==̽ J\=̽Gi<=&{̽N>ڱ<̽>* =̽>z<̽+=dZ=̽E1=c =̽H|̽>{6̽X>̽bj>|̽i>z`k̽)>fռ̽i>z`k̽bj>|̽X>̽>{6̽bj>|̽)>fռ̽g=̽==̽Gi<=&{̽g=̽-;̽N>ڱ<̽>z<̽>-;̽i>z`k̽`>i+̽W;=%;̽fb=̽=<̽W=do:̽Ӈ=:G̽7= ̽Ӈ=:G̽W=do:̽rs=J̽>,=̽>6$G=̽Y>!<̽>6$G=̽>,=̽>& =̽>̽̽d^>$̽S>̽̽RM)<̽^j>};=̽%?=̽->'%=̽>8h=̽^j>};=̽>8h=̽>=̽^j>};=̽9>̽>̽̽h>̽̽gV==̽+=dZ=̽e= >=̽DF=)=̽gV==̽e= >=̽>̽̽=>݇̽%?̽̽=>݇̽%?I̽%?̽̽>:̽>-;̽>z<̽i>z`k̽>-;̽>:̽kM)<̽%?=̽fb=̽7= ̽==̽n>+;̽Y>!<̽N>ڱ<̽d>*̽>k+̽X>̽d>*̽H>@̽>k+̽ԫ=l"=̽=<̽Nȥ=f@<̽Nȥ=f@<̽=<̽c=d<̽g=̽fb=̽==̽g=̽=<̽fb=̽>-;̽n>+;̽N>ڱ<̽>-;̽`>i+̽n>+;̽n>+;̽>k+̽6>.ں̽`>i+̽>k+̽n>+;̽t>sS̽>ť̽d>*̽ԫ=l"=̽=|=̽=H=̽ԫ=l"=̽ =OJ\=̽=|=̽=>݇̽> 8̽%?I̽ȍ<Э=̽e= >=̽;2=̽5>a<̽Ϥ>9<̽#>O̽=>ͦ<̽>=<̽=>ͦ<̽>=̽>=<̽7>7H̽#>O̽B>ͼ̽\Ο>6̽7>7H̽B>ͼ̽,>e ̽t>X̽ z>`̽4>̽t>X̽,>e ̽og>ų̽t>X̽w>B̽t>X̽og>ų̽ z>`̽>X=̽w>0 =̽>ю=̽w>0 =̽>X=̽z>`=̽>X=̽->e =̽z>`=̽>X=̽>?<̽->e =̽=>݇̽>̽̽9>̽=>݇̽g>̽> 8̽9>̽g>̽=>݇̽%Q>O̽>H̽ >h ̽ٵ>qE=̽Ϥ>9<̽s/>,<̽ȍ<Э=̽o;I=̽o;=̽o;I=̽ȍ<Э=̽;=̽ȍ<Э=̽DF=)=̽ȍ<Э=̽X94==̽>M)<̽->'%=̽^j>};=̽>* =̽->'%=̽>M)<̽>* =̽>M)<̽>z<̽d^>$̽P>u̽>ť̽)*+>54s=̽6>=̽o:>KMM=̽m=}Y̽}>-1̽m> ꃽ̽m> ꃽ̽=T*̽m=}Y̽R,=̽Y>!<̽LE><̽>ݼ̽H>@̽$>93?̽gV==̽|]=7=̽+=dZ=̽gV==̽ =OJ\=̽|]=7=̽)>fռ̽>:̽%?a 6̽>:̽)>fռ̽i>z`k̽>:̽%?a 6<̽%?a 6̽"< <̽k8h=̽)>}=̽>=̽>$˄=̽)>}=̽>8h=̽>8h=̽->'%=̽KZ>A=̽KZ>A=̽>$˄=̽>8h=̽+=dZ=̽|]=7=̽E1=c =̽E1=c =̽* =̽KZ>A=̽->'%=̽>* =̽>6$G=̽KZ>A=̽>* =̽N>ڱ<̽Y>!<̽>* =̽Y>!<̽>6$G=̽ J\=̽qdW=A̽ w=1%̽ J\=̽Ӈ=:G̽qdW=A̽ J\=̽==̽7= ̽ J\=̽7= ̽Ӈ=:G̽>6$G=̽>$˄=̽KZ>A=̽>6$G=̽>& =̽>$˄=̽Ӈ=:G̽c=9`̽qdW=A̽Ӈ=:G̽rs=J̽c=9`̽>̽̽L>̽>̽̽P>u̽L>̽6>C̽ >3h̽A>a ̽}>-1̽s>Ű̽5^>̽̽ >̽̽s>Ű̽׭>]̽>\̽5^>=̽gK>T=̽ >=̽כ>=̽gK>T=̽6>0=̽>]=̽F>);<̽=i=̽m> ꃽ̽9 >ī̽=T*̽->e =̽Ϝk>Fw=̽z>`=̽"k> $̽,>e ̽ z>`̽>̽̽g>;̽L>̽g>;̽6>C̽L>̽->e =̽>j,<̽*q>H<̽>1j̽,>e ̽ n>냼̽B>ͼ̽l>.8̽\Ο>6̽t>̽̽>쯽̽'>̽̽LE><̽>؄/=̽>,=̽>؄/=̽LE><̽5>a<̽Ҡ=`<̽=<̽=i=̽F>);<̽Ҡ=`<̽=i=̽>,=̽!>=̽>& =̽כ>=̽">e+=̽3>=̽ Ԧ>Fez̽׭>]̽j>̽+=CA̽W;=%;̽d=-;̽W;=%;̽+=CA̽fb=̽>ю=̽JD>j<=̽>X=̽vL=f=̽DF=)=̽o==̽2> A̽w>B̽t>X̽k>ge\̽N>鑽̽m> ꃽ̽N>鑽̽k>ge\̽I8+>v̽W=do:̽D=̽rs=J̽=T*̽D=̽m=}Y̽D=̽W=do:̽m=}Y̽LE><̽C>>E<̽6>.ں̽n>+;̽C>>E<̽Y>!<̽C>>E<̽n>+;̽6>.ں̽C>>E<̽LE><̽Y>!<̽m>k̽H>@̽>ݼ̽H>@̽m>k̽6>.ں̽׭>]̽j>y̽>\̽j>y̽>H̽>\̽\> ̽B`e>̽̽`P>̽̽B`e>=̽[a\>=̽`P>=̽{>0"y=̽כ>=̽6>0=̽ٵ>qE=̽{>0"y=̽6>0=̽$>93?̽>)Z̽6>C̽>)Z̽P>u̽6>C̽dz>;̽*q>H<̽>j,<̽ n>냼̽dz>;̽>1j̽">e+=̽כ>=̽{>0"y=̽׭>]̽ Ԧ>Fez̽j>y̽7= ̽+=CA̽=W̽7= ̽fb=̽+=CA̽X94=̽̽By= ̽o=̽̽h>=̽M->Y=̽S>=̽=UA̽+=CA̽d=-;̽=W̽5=@̽W=do:̽5=@̽m=}Y̽W=do:̽m=}Y̽5=@̽}>-1̽ =棝̽By= ̽X94=̽̽)>}=̽M->Y=̽h>=̽=3>᥁̽ >T̽w>B̽w>B̽2> A̽=3>᥁̽xz>=̽>d=̽>ю=̽JD>j<=̽>ю=̽>d=̽>ݼ̽%Q>O̽>d=̽>4I=̽>=̽>d=̽">e+=̽>4I=̽>d=̽xz>=̽3>=̽>d=̽3>=̽">e+=̽ >T̽=3>᥁̽j>̽j>̽=3>᥁̽ Ԧ>Fez̽ >h ̽>O̽,>e ̽>1j̽4>̽>j,<̽->e =̽>?<̽g>+<̽s/>,<̽Ϥ>9<̽9 >ī̽t>̽̽=̽̽>쯽̽t>̽̽9 >ī̽9 >ī̽m> ꃽ̽N>鑽̽N>鑽̽>쯽̽9 >ī̽5^>̽̽g>;̽>̽̽s>Ű̽g>;̽5^>̽̽6>C̽g>;̽>\̽>\̽g>;̽s>Ű̽%c>&:̽ >3h̽=UA̽F>);<̽%c>&:̽=UA̽5=@̽=W̽+=CA̽5=@̽ >3h̽}>-1̽=UA̽ >3h̽5=@̽=UA̽5=@̽+=CA̽^>Y̽h>̽̽S>̽̽^>Y̽9>̽h>̽̽d^>$̽^>Y̽S>̽̽^>Y̽g>̽9>̽{>̽ >h ̽B>ͼ̽ >h ̽{>̽>̽O>A*;̽ >h ̽l>.8̽B>ͼ̽ >h ̽>H̽l>.8̽j>y̽ Ԧ>Fez̽l>.8̽>H̽j>y̽l>.8̽s/>,<̽~N>C<̽>=<̽~N>C<̽s/>,<̽g>+<̽~N>C<̽g>+<̽O>A*;̽>4I=̽">e+=̽>A=̽ٵ>qE=̽s/>,<̽>A=̽">e+=̽{>0"y=̽>A=̽{>0"y=̽ٵ>qE=̽>A=̽(>?<̽:>xP̽I8+>v̽o:>KMM=̽Z#>H'=̽)*+>54s=̽Ҡ=`<̽d=-;̽=<̽d=-;̽Ҡ=`<̽=UA̽Ҡ=`<̽F>);<̽=UA̽t>=̽EE>U%=̽==̽>M)<̽%?a 6<̽>:̽>M)<̽>:̽>z<̽F>);<̽I>ˢ<̽%c>&:̽F>);<̽>]=̽I>ˢ<̽^>Y̽>ť̽t>sS̽d^>$̽>ť̽^>Y̽RH<̽Ϝk>Fw=̽->e =̽*q>H<̽X>N<̽Ϝk>Fw=̽Ϝk>Fw=̽X>N<̽W>m6=̽"k> $̽ n>냼̽,>e ̽m">M=̽6>=̽)*+>54s=̽޽>G̽6>C̽>\̽>H̽޽>G̽>\̽'>=̽gK>T=̽5^>=̽gK>T=̽'>=̽6>0=̽s[>b;̽H>#̽h)Y>ϼ̽ٵ>qE=̽}>""f=̽e>2=̽W>=̽}>""f=̽6>0=̽}>""f=̽W>=̽e>2=̽}>""f=̽ٵ>qE=̽6>0=̽K>b珺̽#>O̽7>7H̽#>O̽K>b珺̽=>ͦ<̽\ >_u̽>{6̽g>̽\ >_u̽^>Y̽t>sS̽^>Y̽\ >_u̽g>̽>{6̽\ >_u̽t>sS̽x*>̽N>鑽̽I8+>v̽>װ<̽LE><̽6>.ں̽LE><̽>װ<̽5>a<̽5>a<̽_>ˋ;̽Ϥ>9<̽_>ˋ;̽g>+<̽Ϥ>9<̽>$w<̽>?<̽=>ͦ<̽4>̽]>&̽7>7H̽m>k̽>װ<̽6>.ں̽=>ͦ<̽K>b珺̽>$w<̽K>b珺̽7>7H̽]>&̽>=̽'>=̽5^>=̽>=̽>=̽'>=̽'>=̽W>=̽6>0=̽>=̽W>=̽'>=̽>װ<̽_>ˋ;̽5>a<̽\> ̽DŽg> x̽@p>Q̽PVg>u=̽[a\>=̽p>&=̽PVg>u=̽N>=̽[a\>=̽1O>0|̽DŽg> x̽\> ̽6>=̽N>=̽o:>KMM=̽"k> $̽h)Y>ϼ̽ n>냼̽h)Y>ϼ̽"k> $̽s[>b;̽>̽̽>̽̽d^>$̽P>u̽>̽̽L>̽>̽̽P>u̽d^>$̽Z#>H'=̽I>ˢ<̽>]=̽>q=̽>.#=̽m">M=̽>q=̽m">M=̽)*+>54s=̽?>̽>1j̽dz>;̽>j,<̽?>̽dz>;̽==̽==̽|%=R=̽==̽EE>U%=̽|%=R=̽z>`=̽PVg>u=̽p>&=̽PVg>u=̽Ϝk>Fw=̽W>m6=̽Ϝk>Fw=̽PVg>u=̽z>`=̽DŽg> x̽ z>`̽@p>Q̽"k> $̽DŽg> x̽s[>b;̽DŽg> x̽"k> $̽ z>`̽t>=̽>.#=̽EE>U%=̽>?<̽>$w<̽>j,<̽>$w<̽?>̽>j,<̽]>&̽4>̽>1j̽?>̽]>&̽>1j̽]>&̽?>̽>$w<̽=.>e=;̽%c>&:̽I>ˢ<̽JD>j<=̽>?<̽>X=̽JD>j<=̽>d=̽>=̽4>̽2> A̽t>X̽=3>᥁̽2> A̽\Ο>6̽(>?<̽I8+>v̽k>ge\̽m> ꃽ̽}>-1̽k>ge\̽A>a ̽k>ge\̽}>-1̽A>a ̽(>?<̽k>ge\̽>=<̽{>̽#>O̽{>̽B>ͼ̽#>O̽~N>C<̽O>A*;̽{>̽~N>C<̽{>̽>=<̽>؄/=̽!>=̽>,=̽>؄/=̽e>2=̽W>=̽>؄/=̽5>a<̽e>2=̽!>=̽>؄/=̽W>=̽ >( =̽!>=̽W>=̽>=̽ >( =̽W>=̽>$˄=̽M->Y=̽)>}=̽M->Y=̽>$˄=̽>& =̽>)Z̽>ť̽P>u̽d>*̽>)Z̽H>@̽>)Z̽$>93?̽H>@̽d>*̽>ť̽>)Z̽c=9`̽By= ̽ =棝̽By= ̽c=9`̽rs=J̽bF>[=̽W>m6=̽X>N<̽@>}<̽bF>[=̽X>N<̽vL=f=̽gV==̽DF=)=̽vL=f=̽=|=̽ =OJ\=̽gV==̽vL=f=̽ =OJ\=̽)*+>54s=̽Z#>H'=̽>q=̽Z#>H'=̽>]=̽>q=̽N>=̽PVg>u=̽W>m6=̽_>ˋ;̽O>A*;̽g>+<̽O>A*;̽_>ˋ;̽>#̽1O>0|̽:>xP̽DŽg> x̽1O>0|̽s[>b;̽s[>b;̽1O>0|̽H>#̽}">#}̽%c>&:̽=.>e=;̽e5> R̽}">#}̽=.>e=;̽N>=̽J>,=̽[a\>=̽J>,=̽`P>=̽[a\>=̽C>)̽h)Y>ϼ̽H>#̽K>i|̽`P>̽̽j<>̽̽RS>;5;̽@>}<̽X>N<̽DZ>a̽ n>냼̽h)Y>ϼ̽*q>H<̽e>cQ;̽X>N<̽:>xP̽-;>&̽I8+>v̽-;>&̽:>xP̽1O>0|̽_>ˋ;̽>$̽>$̽>ݼ̽>ˢ<̽J2><̽=.>e=;̽J2><̽@>}<̽=.>e=;̽%>c=̽>=<̽>=̽%>c=̽>A=̽s/>,<̽>=<̽%>c=̽s/>,<̽>4I=̽%>c=̽>=̽>4I=̽>A=̽%>c=̽>=̽>=̽>=̽>=̽ >( =̽>=̽E>Oe̽C>)̽e5> R̽-;>&̽x*>̽I8+>v̽]>&̽>$w<̽K>b珺̽h)Y>ϼ̽C>)̽E>Oe̽E>Oe̽DZ>a̽h)Y>ϼ̽j<>=̽J>,=̽6>=̽N>=̽6>=̽J>,=̽J>,=̽j<>=̽`P>=̽A>a ̽[2>_̽(>?<̽[2>_̽}">#}̽e5> R̽dz>;̽e>cQ;̽*q>H<̽e>cQ;̽dz>;̽ n>냼̽ n>냼̽DZ>a̽e>cQ;̽[2>_̽:>xP̽(>?<̽[2>_̽H>#̽:>xP̽[2>_̽e5> R̽C>)̽H>#̽[2>_̽C>)̽=̽̽=̽̽=T*̽9 >ī̽=̽̽=T*̽'>̽̽x*>̽j<>̽̽>쯽̽x*>̽'>̽̽>쯽̽N>鑽̽x*>̽>ݼ̽>$̽m>k̽_>ˋ;̽>װ<̽m>k̽>$̽_>ˋ;̽m>k̽}">#}̽ >3h̽%c>&:̽ >3h̽}">#}̽A>a ̽[2>_̽A>a ̽}">#}̽@>}<̽J2><̽bF>[=̽Z#>H'=̽J2><̽I>ˢ<̽o:>KMM=̽J2><̽Z#>H'=̽o:>KMM=̽bF>[=̽J2><̽`P>̽̽K>i|̽\> ̽K>i|̽1O>0|̽\> ̽e5> R̽=.>e=;̽E>Oe̽E>Oe̽=.>e=;̽@>}<̽@>}<̽RS>;5;̽E>Oe̽ =p{=̽>]=̽=i=̽=H=̽ =p{=̽=i=̽>q=̽>]=̽ =p{=̽2> A̽7>7H̽\Ο>6̽7>7H̽2> A̽4>̽EE>U%=̽ =p{=̽|%=R=̽>.#=̽>q=̽ =p{=̽ =p{=̽EE>U%=̽>.#=̽=>ͦ<̽JD>j<=̽>=̽JD>j<=̽=>ͦ<̽>?<̽>H̽%Q>O̽޽>G̽޽>G̽$>93?̽6>C̽>ݼ̽޽>G̽%Q>O̽>ݼ̽$>93?̽޽>G̽>& =̽>p=̽M->Y=̽\=̉=̽=H=̽=|=̽\=̉=̽|%=R=̽ =p{=̽=H=̽\=̉=̽ =p{=̽=T*̽(ǿ=̽D=̽(ǿ=̽rs=J̽D=̽N>=̽I> F=̽o:>KMM=̽bF>[=̽I> F=̽W>m6=̽I> F=̽bF>[=̽o:>KMM=̽I> F=̽N>=̽W>m6=̽S>=̽>p=̽>=̽S>=̽M->Y=̽>p=̽e>cQ;̽RS>;5;̽X>N<̽E>Oe̽RS>;5;̽DZ>a̽DZ>a̽RS>;5;̽e>cQ;̽ Ԧ>Fez̽\Ο>6̽l>.8̽ Ԧ>Fez̽=3>᥁̽\Ο>6̽>p=̽>& =̽!>=̽ >( =̽>p=̽!>=̽ >( =̽>=̽>p=̽>p=̽>=̽>=̽-;>&̽K>i|̽j<>̽̽x*>̽-;>&̽j<>̽̽1O>0|̽K>i|̽-;>&̽(ǿ=̽=T*̽=̽̽ >T̽T>̽̽㥛>̽̽ >T̽j>̽T>̽̽T>=̽xz>=̽㥛>=̽3>=̽xz>=̽T>=̽B`e>̽̽\> ̽@p>Q̽[a\>=̽B`e>=̽p>&=̽vL=f=̽o==̽1==̽=̽̽1=̽̽(ǿ=̽sh>̽̽w>B̽ >T̽ >T̽㥛>̽̽sh>̽̽$=07=̽=|=̽vL=f=̽1==̽$=07=̽vL=f=̽+>=̽t~>'=̽w>0 =̽t~>'=̽>ю=̽w>0 =̽j>̽+>̽̽og>ų̽j>̽w>B̽sh>̽̽w>B̽j>̽og>ų̽+>̽̽j>̽sh>̽̽d=창̽rs=J̽(ǿ=̽rs=J̽d=창̽By= ̽1=̽̽d=창̽(ǿ=̽>&̽T>̽̽j>̽׭>]̽>&̽j>̽sh>=̽t~>'=̽+>=̽o=̽̽d=창̽1=̽̽o=̽̽By= ̽d=창̽T>̽̽>&̽ >̽̽s>Ű̽ >̽̽׭>]̽>&̽׭>]̽ >̽̽ >=̽3>=̽T>=̽ >=̽gK>T=̽כ>=̽כ>=̽3>=̽ >=̽>ю=̽sh>=̽xz>=̽>ю=̽t~>'=̽sh>=̽㥛>=̽xz>=̽sh>=̽og>ų̽#y>̽̽@p>Q̽#y>̽̽og>ų̽+>̽̽@p>Q̽#y>̽̽B`e>̽̽#y>=̽w>0 =̽p>&=̽w>0 =̽#y>=̽+>=̽#y>=̽p>&=̽B`e>=̽1==̽==̽$=07=̽==̽|%=R=̽$=07=̽'>=̽%>p=̽t>=̽>.#=̽%>p=̽m">M=̽%>p=̽>.#=̽t>=̽%>p=̽'>=̽j<>=̽6>=̽%>p=̽j<>=̽%>p=̽6>=̽m">M=̽\=̉=̽$=07=̽|%=R=̽\=̉=̽=|=̽$=07=̽> =̽>=̽%?=̽%?I=̽> =̽%?=̽o;I̽Vt<̽o;̽̽Vt<̽<̽̽o;̽̽> =̽h>=̽>=̽> =̽>=̽)>}=̽>=̽> =̽%?I=̽)>}=̽h>=̽> =̽Vt<̽X94=̽̽<̽̽Vt<̽ <̽ =棝̽ <̽Vt<̽o;I̽ =棝̽X94=̽̽Vt<ռ<=a 6o;=o;=a 6%?=Sa߼Dr>=%?==o;=;=0W<=9c=o;=;L@=Zz>==%?=9c=%?=73==5K6/<=7 ==m6<=7 ==5K6/<=9co;=m6<=5K6/<=r>>=I%?=9c%?=Io;=m6<=9co;=I=%?=D=!>=9c=%?= =<=I=o;=9c=o;=9c=%?=D=!>=;L@=Zz>= =<=9c=o;=;=0W<=K=>==S>==>=5K6/<=o;=9co;=o;=5K6/<=ռ<=5K6/<=73==ռ<=IoL>=̽h>=̽>=~=7=%>=C4=W >=~==[*v==hj^F==3Vq==~==73==j'w=4>=Z1=C>=5:=^>=j'w=4>=K=>==\H>=O9>=񂽾>=@K>= d==1==Qs==O9>=j>=񂽾>=Z1=C>=3<#>=5:=^>=%==~==3Vq==`ѻl==֊~==Լ===~==֊~==[*v==ڈ==73==~==W:G*>==hj^F==ռ<=hj^F==W:G*>==~==73==W:G*>==ռ<=B7(3>=LBt>=#ռj>=;S.c==ڈ=>=kF9>=]<= ==j-G=f==<==j-G=f==]<= ===(=8ۜ==̽1====̽o==I>=X]<4>=_<*>=;L@=Zz>=R< >==%?=<<=;=0W<==o;=(=m"==7h===;=0W<=7h=== =<=;=0W<=AC0==̽<=̽X94==BC>=#tA^>=2>=D=!>=@=%>=;L@=Zz>=U>=̽>=̽%?=I%?=U>=̽%?=L<=Io;=̽o;=̽<=L<=̽o;==:5<=;S.c==h==$+>=q}>=R< >=a 6<%?==%?=a 6=񂽾>=LBt>=;"==<==ڈ=_<*>=X]<4>=%==֊~==~==%==Լ===֊~==;S.c==;"==ڈ=j>=BC>=񂽾>=j>=kF9>=9M>=>=͊>=kF9>=>=9M>=L<=m6<=Io;=L<=̽<=AC0==L<=7 ==m6<=AC0==7 ==L<=X=>=u `=p>=hm= F>=̽+>=?>=̽#y>=ec|>=?>=pH<>=̽S>=O9>=̽>=]=6>=,>=B>=7=%>=<}H1>=nB==>=7=%>=~=<}H1>=~=ST5<+*>=<}H1>=<>=:>=?;%>=)<ǘ>=:>=<>=I< >=C4=W >=$=u^>=C4=W >=I< >=~===Qs==1==<<=(=m"==;=0W<=]<= ==<==<==>/9==6DԼ8h==Լ===!=>==>==5^>=̽>=I6>=̽5^>=Zfv=N >=$=u^>=C4=W >=X=Y==$=u^>=Zfv=N >==>>==5^>== >=!=>==5^>==>>=M'>=i'>==]@>==+>==j<>=='>=̽j<>=暫+>=̽'>=ΰr=>==>>=]=>=X=>=hm= F>== >=pH<>=?>=?S>=BC>=2>=>==$+>=>=Sa߼Dr>=>==$+>=͊>=2>=Sa߼Dr>=>=͊>=:>=I>=:>=͊>==$+>=X]<4>=I>=:>=0ͼ>=)s>=ļ>>= %=;;>=s <>=)<ǘ>=ռ<==:5<=a 6o;==:5<=ռ<=hj^F===:5<=a 6=K=>=sҫ=v>=XN<>=5:=^>=3<#>=7h====w== =<=l;=ܑ`===w==7h===7h===(=m"==}A=FV==}A=FV==l;=ܑ`==7h===sҫ=v>=p={>=D=!>=@=%>=D=!>=p={>=q}>=Sa߼Dr>=a 6%?=Sa߼Dr>=q}>==$+>=a 6<%?=q}>=a 6%?=̽>=U>=IoL>=U>=r>>=IoL>=r>>=U>=I%?=#tA^>=BC>=j>=IoL>=#tA^>=j>=#tA^>=IoL>=r>>=I>=9M>=͊>=9M>=#ռj>=LBt>=5:=^>=p={>=j'w=4>=p={>=K=>=j'w=4>=K=>=p={>=sҫ=v>=]C >=6DԼ8h==wļJ}>=2A2 >=6DԼ8h==>/9==6DԼ8h==2A2 >=wļJ}>=*=hl==}A=FV==(=m"==*=hl==j-G=f==}A=FV==*=hl==ڈ>=暫+>=̽j<>==[3 >=Zfv=N >=;֕=>==+>==ݬC>==j<>==I>=nB==>=<}H1>= +>=-ļ>=ļ>>=+`w>=PB3(>=2A2 >=+`w>=#0 >=Vm>=#0 >=+`w>=2A2 >=̽S>=ƒ{>=O9>=j>=ƒ{>=IoL>=ƒ{>=j>=O9>=7=%>=Ʌj=,>=C4=W >=Zfv=N >=Ʌj=,>=;֕=>=Ʌj=,>=Zfv=N >=C4=W >==t>=-=4>=='>==+>=-=4>=;֕=>=-=4>==+>=='>= %=;;>=j=ǖ>=hm= F>=j=ǖ>== >=hm= F>==㥛>=UG=Hl>==T>=]=>=UG=Hl>=V=>=Rol>=̽t>=̽'>=Rol>=暫+>=Vm>=暫+>=Rol>=̽'>=G>=̽㥛>=̽T>=G>=p]>=3&ʪ>=`q>=?S>=3&ʪ>==+>=C=Uč>==sh>== >=C=Uč>=X=>=C=Uč>==+>=X=>=A1=>==㥛>==sh>=A1=>== >=V=>=̽ >=1ԫ>=̽T>=p]>=1ԫ>=i'>=ap>= {>= 9e>= {>=Y<s>= 9e>=63=H========I< >=H<>=~=H<>=I< >=]C >=H<>=ST5<+*>=~=;"==/.<$==<==/.<$==;"==`ѻl==/.<$==<==<==Zfv=N >=n=6==X=Y==<==I< >=$=u^>=<==<==? <==l%<_x>= {>=[:P>= {>=G>=[:P>=Ip>=q;h>=?;%>= R=/>==>=!=>=DF>=2>=#tA^>=ˀDŽ>=pH<>=)s>=hm= F>==>= %=;;>==}>== >==T>==}>=]=>==>>== >==}>==>>=)<ǘ>=z&=(>= %=;;>=#0 >=߾d==Ê >=ļ>>=:>= +>=:>=ļ>>=)s>=zw >=p]>=i'>=M'>=zw >=i'>=7;==I< >=<==I< >=7;==]C >=? <==7;==<===>==֤>=K=>==֤>==\H>=K=>=̽==O==̽==O==Qs==̽==O==̽==Ê >=暫+>=?+>=Vm>=wļJ}>=">=]C >=:>=9n>=?;%>=-ļ>=9n>=ļ>>=9n>=:>=ļ>>=m=}>= *=m>=ΰr=>=]=>=m=}>=ΰr=>=p]>=_sm>=3&ʪ>=OA>==]@>=7 vO>=B7(3>=ʅ>=LBt>=ʅ>=@K>=LBt>=<=====]<= =====X=Y==]<= ==;g}>=)<ǘ>=s <>=)<ǘ>=;g}>=:>=ļ>>=-4>=0ͼ>=-4>=ļ>>=:>=PB3(>= ">=2A2 >= ">=wļJ}>=2A2 >=9M>=>">=#ռj>=>">=9M>=I>=]<= ==6$=>===(=8ۜ==6$=>==]<= ==X=Y==̽5^>=7>=̽ >=7>=̽5^>=I6>= *=m>= #=d>=ΰr=>=SH<?>=<}H1>=ST5<+*>=]=6>=SH<?>=ST5<+*>==J/>==+>=;֕=>=H<>=">=ST5<+*>=H<>=]C >=">=:>=;g}>=-4>==o==p=2===1==j'w=4>=suD=>=Z1=C>=j'w=4>==\H>=suD=>=7>=1ԫ>=̽ >=i'>=1ԫ>=7>=1ԫ>=G>=̽T>=p]>=G>=1ԫ>=p]>=zw >=_sm>=`q>=3&ʪ>=_sm>=A<>=l%<_x>=[:P>=G>=Lt>=[:P>=C=Uč>=A1=>==sh>=C=Uč>== >=A1=>=Lt>=0ͼ>=-4>=s <>=A<>=;g}>=9n>=Ip>=?;%>=-ļ>=Ip>=9n>=-ļ>= +>=GPx>=A1=>=UG=Hl>==㥛>=V=>=UG=Hl>=A1=>=UG=Hl>==}>==T>=UG=Hl>=]=>==}>= #=d>=4=+>=ΰr=>=Z<>=3<#>=Z1=C>=_<*>=3<#>=Z<>=X=Y==n=6==6$=>==? <==/.<$==`ѻl==? <==<==/.<$==0ͼ>=ˀDŽ>=)s>==>=s <>= %=;;>==X94==|=cy===o==DF>=r>>=9c%?=#tA^>=r>>=DF>=+`w>=?+>=PB3(>=+`w>=Vm>=?+>==[3 >=-=4>==t>==[3 >=;֕=>=-=4>==J/>=Ʌj=,>=7=%>=;֕=>=Ʌj=,>==J/>=l%<_x>=Y<s>= {>=ap>=G>= {>=O==߾d==Qs==Ê >=߾d==O=====$=u^>=X=Y==X]<4>=XN<>=3<#>=XN<>=X]<4>=:>=$=u^>====<=====<==<===S>=sҫ=v>==h>=DF>=9c%?=%?=DF>=Sa߼Dr>=2>=Sa߼Dr>=DF>=%?==w==|=cy===X94===>==֤>==>= R=/>==>==>==֤>==>= R=/>==\H>==֤>= R=/>=̽t>=Ê >=̽==Rol>=Ê >=̽t>=#0 >=Ê >=Vm>=Vm>=Ê >=Rol>=====[3 >==t>====63=H===[3 >=n=6==Zfv=N >==[3 >==[3 >=63=H==n=6==̽1==̽==Qs====̽1==Qs==u `=p>==>=hm= F>=L =u>==>=u `=p>=ˀDŽ>=ec|>=pH<>=ˀDŽ>=P)Cs>=ec|>=nB==>==J/>=7=%>=d=nG>==J/>=nB==>==+>==J/>==ݬC>==ݬC>==J/>=d=nG>=̽S>=̽h>=ƒ{>=̽h>=IoL>=ƒ{>=OA>=M'>==]@>=GPx>=M'>=OA>=LBt>=񂽾>=kF9>=9M>=LBt>=kF9>=5:=^>=XN<>=@=%>=@=%>=p={>=5:=^>=@=%>=R< >=;L@=Zz>=@=%>=XN<>=R< >=m=}>=]=>=V=>=hUWd==̽X94==̽o==hUWd==AC0==̽X94====hUWd==̽o==hUWd==7 ==AC0==B>=1i-PM>=]=6>==1==p=2=====p=2==63=H=====ST5<+*>=">=]=6>=,>=">=wļJ}>=">=,>=]=6>= ">=PB3(>=s9>=,>= ">=s9>= ">=,>=wļJ}>=`q>=)s>=pH<>=?S>=`q>=pH<>= d==߾d==>/9==߾d== d==Qs==#0 >=2A2 >=߾d==߾d==2A2 >=>/9==?+>=暫+>=``h>>=?+>=``h>>=mo=>=1i-PM>=̤ \>= 9e>=̤ \>=1i-PM>=B>=̤ \>=ap>= 9e>== >=j=ǖ>=V=>=V=>=j=ǖ>=m=}>=<<=a 6=z&=(>=)<ǘ>=z&=(>=<>= *=m>=zw >=M'>= +>=M'>=GPx>= +>=_sm>=zw >= +>==]@>=Ft:>=7 vO>=Ft:>=B7(3>=7 vO>=Ft:>=ʅ>=B7(3>=I6>=ʅ>=Ft:>=s9>=B>=,>=s9>=;N>=B>=;N>=̤ \>=B>=;N>=s9>=:EdO>=suD=>==RU>=mh*=>==RU>=B,[=fu>=mh*=>=\zvVI==73==7 ==\zvVI==hUWd==3Vq==hUWd==\zvVI==7 ==73==\zvVI==3Vq==>S<ݙ>=R< >=XN<>=:>=>S<ݙ>=XN<>=8>=#ռj>=΂*>=8>=7 vO>=B7(3>=7 vO>=8>=΂*>=#ռj>=8>=B7(3>=3=0۸>=B,[=fu>=!=>==>>=3=0۸>=!=>=ϻ9==? <==`ѻl==Լ===ϻ9==`ѻl==o>=i'>=7>=o>=I6>=Ft:>=I6>=o>=7>= d==)9==%==Լ===)9==>/9==)9==Լ===%==)9== d==>/9=={O>=wvd>=:EdO>=mo=>={O>=:EdO>=<>=ȹ<@>= *=m>= #=d>=ȹ<@>=j=ȹ<@>= #=d>= *=m>=o>==]@>=i'>=Ft:>==]@>=o>=7 vO>=E~>=OA>=)s>=`q>=:>=:>=_sm>= +>=`q>=_sm>=:>=fn>=̽>=̽>=fn>=O9>=@K>=O9>=fn>=̽>=>S<ݙ>=a 6<%?=R< >=a 6<%?=>S<ݙ>=q}>=q}>=>S<ݙ>=:>=; >=j=q;h>=j=4=+>= #=d>=ap>=̤ \>=P)Cs>=B,[=fu>=3=0۸>=4=+>=3=0۸>=ΰr=>=4=+>=ΰr=>=3=0۸>==>>=™{5>=΂*>=#ռj>=7;==? <==ϻ9==ϻ9==6DԼ8h==]C >=6DԼ8h==ϻ9==Լ===7;==ϻ9==]C >=_<*>=>">=I>=>">=™{5>=#ռj>=™{5>=>">=_<*>=!=>=B,[=fu>==RU>= R=/>==RU>==\H>==RU>=suD=>==\H>==RU>= R=/>=!=>=Ip>=E~>=q;h>=E~>=; >=q;h>=OA>=E~>=GPx>=Lt>=;g}>=[:P>=;g}>=A<>=[:P>=Lt>=-4>=;g}>={O>=mo=>=``h>>=l;=ܑ`==|=cy===w===o==|=cy==p=2==|=cy==l;=ܑ`===(=8ۜ===(=8ۜ==p=2==|=cy==m=}>=z&=(>= *=m>=z&=(>=j=ǖ>= %=;;>=m=}>=j=ǖ>=z&=(>=1i-PM>=SH<?>=]=6>=SH<?>==I>=<}H1>=6$=>==p=2===(=8ۜ==n=6==63=H==6$=>==p=2==6$=>==63=H==0ͼ>=Lt>=ˀDŽ>=A<>=s <>==>=Z<>=™{5>=_<*>=fn>=̽>=̽>=ʅ>=fn>=@K>=̽>=fn>=I6>=fn>=ʅ>=I6>=΂*>=/!;>=7 vO>=/!;>=E~>=7 vO>=c;V>=™{5>=Z<>=gc<)>=6M=>=; >=6M=>=j=; >=wvd>=Y#H_>=:EdO>=̤ \>=Y#H_>=P)Cs>=gc<)>==mh*=>=Z<>==c;V>==gc<)>=c;V>=<ގT>=1i-PM>= 9e>=1i-PM>=<ގT>=SH<?>=Y<s>== 9e>==<ގT>= 9e>=Y#H_>=;N>=:EdO>=̤ \>=;N>=Y#H_>=GPx>=Ip>=-ļ>=E~>=Ip>=GPx>==I>=SH<?>=<ގT>=L =u>==Y<s>==L =u>==ti`>=/!;>=; >=E~>=suD=>=Z<>=Z1=C>==suD=>=mh*=>=Z<>=suD=>==l%<_x>==>=L =u>=L =u>=Y<s>=l%<_x>=A<>==>=l%<_x>=mo=>=s9>=PB3(>=?+>=mo=>=PB3(>=s9>=mo=>=:EdO>=q;h>=ȹ<@>=?;%>=ȹ<@>=q;h>=j=ȹ<@>=<>=?;%>=6M=>=gc<)>=mh*=>=6M=>=4=+>=j=B,[=fu>=4=+>=6M=>=B,[=fu>=6M=>=mh*=>=ˀDŽ>=G>=P)Cs>=ap>=P)Cs>=G>=ˀDŽ>=Lt>=G>=c;V>=΂*>=™{5>=/!;>=΂*>=c;V>=c;V>=gc<)>=; >=; >=/!;>=c;V>=?S>=̽㥛>=3&ʪ>=?S>=̽sh>=̽㥛>=G>=3&ʪ>=̽㥛>=̽+>=̽sh>=?S>=?>=̽+>=?S>=L =u>=d=` l>==ti`>=d=` l>=L =u>=u `=p>==`P>==j<>==ݬC>==I>=<ގT>==ti`>==ti`>=<ގT>==?>=-t>=̽#y>=-t>=ec|>=wvd>=ec|>=-t>=?>=d=nG>===Y>==ݬC>===Y>=BN=wS>=d=` l>=BN=wS>==ti`>=d=` l>=4=Y>==`P>==ݬC>===Y>=4=Y>==ݬC>=rrfd>=̽`P>=̽B`e>=4=Y>==B`e>==`P>=̽`P>={O>=``h>>=rrfd>={O>=̽`P>=``h>>=̽j<>=̽`P>=M=Tl>==#y>==B`e>==B`e>=4=Y>=M=Tl>===Y>=d=` l>=M=Tl>=4=Y>===Y>=M=Tl>=u `=p>==X{>=d=` l>==X{>=M=Tl>=d=` l>=X=>==X{>=u `=p>=nB==>==I>=BN=wS>=BN=wS>=d=nG>=nB==>==I>==ti`>=BN=wS>=d=nG>=BN=wS>===Y>={O>=rrfd>=wvd>=rrfd>=-t>=wvd>=̽#y>=rrfd>=̽B`e>=-t>=rrfd>=̽#y>=-N@m>=wvd>=ec|>=-N@m>=P)Cs>=Y#H_>=P)Cs>=-N@m>=ec|>=wvd>=-N@m>=Y#H_>==#y>=`&=>==+>=X=>=`&=>==X{>=`&=>=X=>==+>=K=>=I=%?==%?==>=K=>==%?=M=Tl>=`&=>==#y>=M=Tl>==X{>=`&=>==h>=K=>==>=D=!>=K=>=sҫ=v>=K=>=D=!>=I=%?==h>=sҫ=v>=K=>=(=m"==h=̽%?I$%?̽%?9cI%?=[%?#6=9c%?=I=%?̽ej=%?+9c=%?̽ĉ=%? =I=%?=9c=%?=%?N<̽%?=̽%?a 6<u=%?Tʋ=%?9c!Ӡ=%?6:=%?k=%?̽9c=%?̽ĉ=%? =C=%?ԟ=L=%?r=!Ӡ=%?6=%?9c=%?$%?%?yÄר%?<=%?==%?9c==%?s>=R;%?᡽Պ%?P%?yÄ5%?s%?2<%?~<5%?%?3so%?%?3s5%?%?~<<%?5%?Κ<%?Q޼?:%?5%?o%?5%??:%?Κ<%?Q޼5%? <%?r==%?l ==%?9c= =%?t==%?s>=ǥ%?ӧ̽%?I̽%?̽I%?̽ǥ%?ӧ̽%?̽̽%?I=U%?~M=̽%?=U%?~M=I%?=̽%?=d=%?==%?I==%?=I=%?=d=%?==%?==%?Iή=%?`+=%?̽ή=%?`+I=%?̽=%?̽ή=%?`+=%?Iu=%?Tʋ=%?=O=%?;<=%?a 6<O=%?;<=%?==%?s>=2;%? =<%?9=a 6<%?=O=%?;<&=%?;=%?a 6<C=%?ԟ=9c=%?==%?=ĉ=%? =9c=%?=C=%?ԟ=ή=%?`+ej=%?+I=%?̽u=%?Tʋej=%?+ή=%?`+=%?I= =%?t==%?9c=Y1%?Uܼ%?3s练Y%?`1o%?뼺%?3sY1%?Uܼ?:%?w%?R/;%?Uvx?:%?o%?w%?R<%??4=%?agڼzS=%?NN <%?Κ<%?Q޼?4=%?agڼck<%?A?:%?/;%?UvxΚ<%?Q޼?:%?ck<%?A%?A}=s%?2==%?l <&=%?;O=%?;<4x=%?e<=%?l w?ԁDo;R_Io; No;>w?zo;3;6=o;#=O]o;EO=?o;#=̽o;9c=O]o;EO=? :=o;eI=o;̽9c=o;̽?̽o;I=O]o;EO=̽o;9c=?8ңo;¯̽o;̽o;a 6?o;̽Լo;#a 6o;̽?=o;=o;H=o;a 6?䝽o;?̽o;8ңo;¯?Io; ԁDo;R_Լo;#?o;̽Io; Լo;#?o;#=̽o;=̽o;9c=?̽o;9c̽o;䝽o;??i;o;|Լ2o;;jw ұo;ኽ䝽o;??̽o;9c ұo;ኽ̽o;I?b_0o;@X=\μo;L+=3o;<?zo;3;6=b_0o;@X=3o;<?9co;̽l o;\?Io; ?l o;\?No;>wIo; ?l o;\?9co;̽Io;̽?̽o;I⪽o;髽̽o;̽?⪽o;髽Io;̽̽o;̽?w⪽o;髽?9c=o;= =o;B==o;=?==o;O= =o;B=9c=o;=?Ko;;3o;o<3o;<?⪽o;髽l o;\?Io;̽?No;>wl o;\?⪽o;髽?=o;9c=o;H=o;?=o;9c"=o;ݹ=o;9;?ݹ=o;9;=o;H=o;9c?=o;= =o;B=a 6fռ=%?a 6=%?=o;=:<8;=o;9c=%?==^j>};==%?9c==+=dZ==H==>|6=޼>5K=g>=;==H 8=g>=޼>5K=%?9c=> 8=޼>5K=%?I=> 8=%?9c=o;I==;==%?I==%?9c==7<=o;I=o;9c=>==%?9c==^j>};==7<=o;9c=:<8;=H5K=%?=%?9c=%?=޼>5K=)>fռ=޼>5K=>|6=)>fռ=k F===X94===o===E>m<=ĵ>UyE==a>Q2===f>e==3f>ך==z>8==rf>D֚=gf>e=Sz>W7=X>=`>i+=i>z`k=t>sS=X>=>|6=|Wf>W;5=x>2=Vn>=x>2=|Wf>W;5=Pn><=\>*=X>=t>sS=6>Tں=>k+=<>@=X>=>k+=`>i+=٫=[z"==u=L==cC=h<====ZJ\==Wi<=&{=u=L=====SY=B==N>ڱ<=>* ==>z<=+=dZ==E1=c ==H-;=n>+;=N>ڱ<=>-;=`>i+=n>+;=n>+;=>k+=6>Tں=`>i+=>k+=n>+;=t>sS= >ʥ=\>*=٫=[z"=====u=L==٫=[z"===L\======>݇=> 8=%?I=Ǎ<Э==e= >==;Q2==>a<=E>m<=Ğ>(=QZ>-<=>^<=QZ>-<=u>8&==>^<=Z>-=Ğ>(=N>)[ݼ=v>W&=Z>-=N>)[ݼ=>*>'C==А(>==OH> >==>=> <=QZ>-<=>=>=Z>-==>݇=>̽=9>==>݇=g>=> 8=9>=g>==>݇=P>,=>H=>+=ĵ>UyE==E>m<=>*|<=<==Ǎ<Э==o;==o;I==Ǎ<Э==;==Ǎ<Э==k F===Ǎ<Э==X94===>M)<=->'%==^j>};==>* ==->'%==>M)<=>* ==>M)<=>z<=[^>$='>u= >ʥ=>P=>..=y>7I=6=<+Y=>..=>P=>P==~v=6=<+Y= !{>劭=+>̽=#y>̽= !{>劭=9f>p=+>̽= !{>劭=Sz>W7=9f>p= !{>犭==B`e>==#y>==B`e>== !{>犭==3f>ך== !{>犭==z>8==3f>ך==+>== !{>犭==#y>==b>r== !{>犭==+>==z>8== !{>犭==b>r==B`e>̽= !{>劭=#y>̽= !{>劭=B`e>̽=rf>D֚=Sz>W7= !{>劭=rf>D֚=2R,==Y>!<=DE>,<=q >ݼ=<>@=!>w4?=cV===]=7==+=dZ==cV====L\==]=7==)>fռ=>:=%?a 6=>:=)>fռ=i>z`k=>:=%?a 6<=%?a 6="< <=p8h==)>}==>==>%˄==)>}==>8h==>8h==->'%==KZ>A==KZ>A==>%˄==>8h==+=dZ==]=7==E1=c ==E1=c ==9C=gq<="< <=f=p<=9C=gq<=E1=c ==E1=c ==]=7==f=p<=>* ==KZ>A==->'%==>* ==>6$G==KZ>A==>* ==N>ڱ<=Y>!<=>* ==Y>!<=>6$G==ZJ\==qdW=A= w=1%=ZJ\===:G=qdW=A=ZJ\=====Q= =ZJ\==Q= ==:G=>6$G==>%˄==KZ>A==>6$G==>+ ==>%˄===:G=c=[=qdW=A==:G=%==c=[=1>k<=А(>==rGA><= )>F=iA6>kDB=A>Wż=.3>s==W(>==2=>==)>=6> =@=>="GZ>P===f>e==S> H=="GZ>P==R>==3f>ך===f>e=="GZ>P==3f>ך==gf>e=ZGZ>N=S>wH=7R>=ZGZ>N=rf>D֚=ZGZ>N=gf>e=rf>D֚=>(=>P=y>7I=2>h=j<>̽='>̽=2>h=)>=@=>=j<>==2>=='>==W(>==2>==2=>==j<>̽=DG>8=`P>̽=7R>=DG>8=@=>= @G>ɲ==j<>==`P>== @G>ɲ==R>==2=>==>̽= >G=>̽='>u= >G=3>[E==>h== )>F=A>Wż=t>̽=c3>,鯽='>̽=А(>==4>>3==rGA><=4>>3==А(>==>*>'C==x>2=>s=Vn>=|>—<=x>2=Pn><=V>==t>=='>==V>==W(>==>p==oT>=V=5^>̽= >̽=oT>=V=e5>O=x>Id=5^>==?W>Z@== >==f>ܙ==?W>Z@==o,>9==T>==H>==㥛>==>ڝ==H>==Xˤ>yՙ==n>D=T>̽=㥛>̽=n>D=Л>r=+ޤ>~=>N߼=y>7I=>..=>ü=>N߼=>..=sh>̽=>~=㥛>̽=Л>r=>~=R>4Ҙ=>5==sh>==㥛>==>5==>ڝ==G>F==>P=>a==~v= W>&=B`e>̽=`P>̽= W>&=7R>=rf>D֚=B`e>̽= W>&=rf>D֚=B`e>==W>+==`P>==R>==W>+==3f>ך==W>+==B`e>==3f>ך==sh>== ]>x==+>==b>r== ]>x==G>F== ]>x==b>r==+>==nb>H=sh>̽=+>̽=nb>H=9f>p=R>4Ҙ=9f>p=nb>H=+>̽=9(>d=)>=>(=y>7I=9(>d=>(=OH> >== >w==>*>'C==W(>== >w==>p==FG>Gx=7R>=@=>=>==T>== >==>==f>ܙ==Xˤ>yՙ==R>==G>Ex==2=>==K">dr=C>=*>M:=C>=;>W<=*>M:=T>̽=l>T= >̽=e5>O=l>T=+ޤ>~=>̽=f>r<= >G=f>r<=3>[E= >G==oE<=cC=h<={\= ==DE>,<=>A/==>,==>A/==DE>,<=>a<=>,==>==>+ ==f>ܙ==w>$v==Xˤ>yՙ==J=v;=2=^=e==@>w=e5>O=+ޤ>~=|Wf>W;5=%W>F<=Pn><=%W>F<=|Wf>W;5= S>H=yW>ˊ=|Wf>W;5=Vn>=|Wf>W;5=yW>ˊ= S>H=L=y==k F===o===]=do:===q=%===~v===q=6=<+Y===q==~v=%====q=]=do:=6=<+Y====M?>E=====M?>E==SY=B=====DE>,<=C>>E<=6>Tں=n>+;=C>>E<=Y>!<=C>>E<=n>+;=6>Tں=C>>E<=DE>,<=Y>!<=2>J ==>f==S=H<\==>f==2>J ==>p==r> i=<>@=q >ݼ=<>@=r> i=6>Tں=>^<=-z>z7<=Ğ>(=>b =N>)[ݼ=Ğ>(=e5>O=ׯ>%z=x>Id=ׯ>%z=>H=x>Id=>xd==b>r==G>F==9f>p=>d=R>4Ҙ=8>Ly==f>ܙ==o,>9==ĵ>UyE==8>Ly==o,>9==!>w4?=>)Z=3>[E=>)Z='>u=3>[E=S=H<\==֑>F=={\= ==A>Wż=iA6>kDB=gF>Q7=A>Wż=O>==>h==1>k<=rGA><=5@>;=O>==rGA><=4>>3==nb>H=>~=sh>̽=R>4Ҙ=>~=nb>H=>5== ]>x==sh>==>5==G>F== ]>x== @G>ɲ==2>==j<>==2=>==2>== @G>ɲ==.3>s==2=>==G>Ex==2>h=DG>8=j<>̽=2>h=@=>=DG>8=@=>=6> =FG>Gx=c3>,鯽=2>h='>̽=)>=2>h=c3>,鯽=6> =)>=9(>d=2>==V>=='>==2>==W(>==V>== >w==.3>s==>*>'C==W(>==.3>s== >w==?W>Z@==>== >==?W>Z@==f>ܙ==>==w>$v==f>ܙ==8>Ly==W>+== @G>ɲ==`P>==R>== @G>ɲ==W>+==G>Ex=="GZ>P==S> H==R>=="GZ>P==G>Ex==DG>8= W>&=`P>̽=DG>8=7R>= W>&=ZGZ>N=FG>Gx=S>wH=ZGZ>N=7R>=FG>Gx=>==H>==T>==Xˤ>yՙ==H>==>==H>==>5==㥛>==H>==>ڝ==>5==>~=n>D=㥛>̽=Л>r=n>D=>~=n>D=l>T=T>̽=n>D=+ޤ>~=l>T=l>T=oT>=V= >̽=e5>O=oT>=V=l>T=e5>O=@>w=ׯ>%z=5@>;=%W>F<= S>H=yW>ˊ=gF>Q7= S>H=Q= =2=^==W=Q= =e==2=^=X94=̽=Ej|=ꔩ=o=̽=h>==G->Z==S>==K">dr=iA6>kDB= )>F=*>M:=iA6>kDB=K">dr=1>k<=;>W<=А(>==1>k<=*>M:=;>W<=>f== >w==OH> >==>p== >w==>f==c3>,鯽=>(=)>=֑>F==>f==OH> >==S=H<\==>f==֑>F===W=}=.=]=do:=}=.=6=<+Y=]=do:=6=<+Y=}=.=>..=u=L==S=H<\=={\= ==S=H<\==u=L==SY=B==2>J ==S=H<\==SY=B== ==Ej|=ꔩ=X94=̽=)>}==G->Z==h>==|>—<=X>T==> <=>=|>—<=> <=|>—<=>=x>2=:Y>P=>s=>=>s=>=>=>=>s=x>2=>==QZ>-<=> <=QZ>-<=>==u>8&==Z>-=>$=>=>$=Z>-=v>W&=>Z=q >ݼ=P>,=>+=>Z=P>,=䅴>s<=>*|<=E>m<=>a=t>̽==̽=c3>,鯽=t>̽=>a=>P=>(=>a=>a=>(=c3>,鯽=5^>̽=f>r<=>̽=oT>=V=f>r<=5^>̽=3>[E=f>r<=x>Id=x>Id=f>r<=oT>=V=>ߛ>u~==>ڝ==Xˤ>yՙ==>ߛ>u~==Xˤ>yՙ==w>$v==Л>r=&>~=+ޤ>~=+ޤ>~=&>~=@>w=C>=>ü=@==QZ>-<=Ğ>(=>=Ğ>(=Z>-=>=2=^=}=.==W=>ü=}=.=@==}=.=>ü=>..=@==}=.=2=^=\>Y=h>̽=S>̽=\>Y=9>=h>̽=[^>$=\>Y=S>̽=\>Y=g>=9>=>b =>+=N>)[ݼ=>+=>b =>Z=>Z=>b = >*;=>+=Nت> D9=N>)[ݼ=>+=>H=Nت> D9=ׯ>%z=@>w=Nت> D9=>H=ׯ>%z=Nت> D9=>*|<=-z>z7<=>^<=-z>z7<=>*|<=䅴>s<=-z>z7<=䅴>s<= >*;=>8==>*|<=>^<=ĵ>UyE==>*|<=>8==w>$v==8>Ly==>8==8>Ly==ĵ>UyE==>8==t>==M?>E=====M?>E==2>J ==SY=B==2>J ==M?>E==>p==V>==M?>E==t>==>p==M?>E==V>==>N߼= )>F=y>7I=K">dr= )>F=>N߼=>N߼=>ü=C>=C>=K">dr=>N߼==f>e==ta>Jp==S> H==ta>Jp==O>==S> H==ma>t=gf>e=S>wH=O>=ma>t=S>wH=>M)<=%?a 6<=>:=>M)<=>:=>z<=&>~=Л>r=R>4Ҙ=&>~=R>4Ҙ=>d=>ڝ==>ߛ>u~==G>F==G>F==>ߛ>u~==>xd==А(>==֑>F==OH> >==А(>==;>W<=֑>F==\>Y= >ʥ=t>sS=[^>$= >ʥ=\>Y=2R$=:Y>P=>=X>T==>==> <=٫=[z"==f=p<==L\==f=p<=]=7===L\==٫=[z"==Х=]<=f=p<=ma>t=I\y>.W=gf>e=\y>SX==ta>Jp===f>e==>F=3>[E=x>Id=>H=>F=x>Id=$>==?W>Z@==5^>==?W>Z@==$>==o,>9==ĵ>UyE==j>&f==a>Q2==V>==j>&f==o,>9==j>&f==V>==a>Q2==j>&f==ĵ>UyE==o,>9==\ >_u=>|6=g>=\ >_u=\>Y=t>sS=\>Y=\ >_u=g>=>|6=\ >_u=t>sS=>|@==>8==>^<=u>8&==>|@==>^<=Nت> D9=>@=N>)[ݼ=>@=v>W&=N>)[ݼ==>h<=DE>,<=6>Tں=DE>,<==>h<=>a<=>a<=\>#;=E>m<=\>#;=䅴>s<=E>m<=r> i==>h<=6>Tں=>==$>==5^>==>==>C==$>==$>==V>==o,>9==>C==V>==$>==z>8==\y>SX===f>e==I\y>.W=Sz>W7=gf>e=ņ>P=>$=X>T==Æ>`L==>===>h<=\>#;=>a<=>@=&>~=v>W&=@>w=>@=Nت> D9=@>w=&>~=>@=>̽=>̽=[^>$='>u=>̽= >G=>̽='>u=[^>$=>ߛ>u~==>|@==u>8&==>|@==w>$v==>8==>ߛ>u~==w>$v==>|@==Vn>=I\y>.W=ma>t=>s=I\y>.W=Vn>=>s=:Y>P=I\y>.W=\y>SX==Pn><=ta>Jp==\y>SX==|>—<=Pn><=X>T==|>—<=\y>SX==-z>z7<=>b =Ğ>(=-z>z7<= >*;=>b =>A/==>==>,==>A/==a>Q2==V>==>A/==>a<=a>Q2==>==>A/==V>==>==>xd==u>8&==>xd==>ߛ>u~==u>8&==Æ>`L==>xd==>==>d=>$=v>W&=&>~=>d=v>W&=>d=ņ>$=n >E ==>==V>==>C==n >E ==V>===>h==O>=S>wH=FG>Gx==>h==S>wH=FG>Gx=6> ==>h==.3>s==4>>3==>*>'C==O>==4>>3==S> H==4>>3==G>Ex==S> H==.3>s==G>Ex==4>>3==>%˄==G->Z==)>}==G->Z==>%˄==>+ ==>)Z= >ʥ='>u=\>*=>)Z=<>@=>)Z=!>w4?=<>@=\>*= >ʥ=>)Z=c=[=Ej|=ꔩ= ==Ej|=ꔩ=c=[=%==yW>ˊ=O>=A>Wż=gF>Q7=yW>ˊ=A>Wż=ma>t=yW>ˊ=Vn>=O>=yW>ˊ=ma>t=O>==%W>F<=rGA><=%W>F<=5@>;=rGA><=%W>F<=ta>Jp==Pn><=%W>F<=O>==ta>Jp==cC=h<==oE<=J=v;=L=y==cV===k F===L=y======L\==cV===L=y===L\==\>#;= >*;=䅴>s<= >*;=\>#;=>Z==oE<=}=C=J=v;=2=^=}=C=@==}=C=2=^=J=v;=}=C==oE<=@==֑>F==>X|<={\= ==>X|<==oE<={\= ==\>#;=>ޢ'=>Z=>ޢ'=q >ݼ=>Z=>==>C==>==>==n >E ==>C===̽==̽==~v=>a==̽==~v=q >ݼ=>ޢ'=r> i=\>#;==>h<=r> i=>ޢ'=\>#;=r> i=gF>Q7=5@>;= S>H=1>k<=5@>;=*>M:=5@>;=iA6>kDB=*>M:=gF>Q7=iA6>kDB=5@>;=ņ>p=Sz>W7=9f>p=ņ>d=ņ>W7=I\y>.W=ņ>.W=:Y>P=b>r==Æ>`L==z>8==Æ>`L==b>r==>xd==z>8==Æ>`L==\y>SX==\y>SX==Æ>`L==X>T==>H=P>,=>F=>F=!>w4?=3>[E=q >ݼ=>F=P>,=q >ݼ=!>w4?=>F=C>=>X|<=;>W<=>X|<=֑>F==;>W<= )>F=9(>d=y>7I= )>F==>h==9(>d==>h==6> =9(>d=tS={"=====L=y==>+ ==>p==G->Z===5==~v==̽==~v==5=%==1=̽==5==̽=ԝ=uc=%===5=%==ԝ=uc=Ej|=ꔩ=1=̽=ԝ=uc==5=>v;=C>=@==>v;==oE<=>X|<==oE<=>v;=@==C>=>v;=>X|<=S>==>p==>==S>==G->Z==>p==o=̽=ԝ=uc=1=̽=o=̽=Ej|=ꔩ=ԝ=uc=tS={"==o===1===L=y==o===tS={"==tS={"==1======tS={"==SY=B=====SY=B==tS={"=====>p==>+ ==>==n >E ==>p==>==n >E ==>==>p==>p==>==>==%?I==> ==%?==> ==>==%?== ==h>==>==> ==>==)>}==>==> ==%?I==)>}==h>==> ==p%<̽̽Q<ý̽<̽̽#}>=̽{>=̽w>=̽ƵQp%<=̽<=̽{>ý̽#}>̽̽w>̽̽3)G̽w>̽̽B]q>̽̽<=̽<=̽%<=̽w>=̽p9t> =̽B]q>=̽<̽̽-<̽%<̽̽{>ý̽w>̽̽pau>G̽pau>G̽`x>Mn̽{>ý̽ƵQ=̽{>=̽p9t> =̽Yw>G=̽p9t> =̽{>=̽<̽̽Q<ý̽-<̽Izzj=̽RZ>i=̽ |]>y=̽:K>)̽O>]A̽H>^a?̽V=(=̽Zt=&=̽`o==̽I ,=la=̽/)==̽l=߉=̽a>y̽c>(Ψ̽h>̽c>(Ψ̽a>y̽i^>v̽tY>b=̽V>=̽,^>=̽R>am=̽V>=̽O>=̽J >.:̽~>-̽J >[!̽ޙV=8̽n9D=ڦ̽(Y^=g̽\È=r=̽=Eǵ=̽0=Ҩ=̽Ƃ=F=̽*q==̽==̽*q==̽Ƃ=F=̽ Nh=W=̽oF=-ű=̽Pg==̽ Nh=W=̽oF=-ű=̽=Eǵ=̽\È=r=̽UL>5~̽L>n̽UJF>̽L>n̽0S>˞̽u R>̽0S>˞̽L>n̽UL>5~̽ V=̽Yw>G=̽{>=̽eLh< $=̽XS<=̽ƵQMn̽ ~>̽{>ý̽i^>v̽d>8̽c>(Ψ̽(K=EN=̽`=|E=̽mJ=4=̽V>N̽O>]A̽цU>C8̽O>]A̽V>N̽fQ>`̽Rzq>1?=̽|k>ɕ=̽[o>Fz=̽`x>Mn̽Is>̽ 5s>̽Is>̽)n>{̽ 5s>̽Is>̽`x>Mn̽pau>G̽j>ܘ̽ѷo>$̽ݯn>A{̽=Eǵ=̽==̽0=Ҩ=̽.<ў̽Iz V̽`)Q>Em̽UL>5~̽<p̽< ~̽| ̽ݯn>A{̽ 5s>̽)n>{̽ m> ̽ 5s>̽L>n̽ F>d̽UJF>̽5x= w=̽V=(=̽`o==̽V=(=̽5x= w=̽\È=r=̽O>]A̽EK>]̽H>^a?̽EK>]̽O>]A̽fQ>`̽H6u>=̽Gw>n=̽Rzq>1?=̽[o>Fz=̽H6u>=̽Rzq>1?=̽&u>0̽_u>T.̽ѷo>$̽_u>T.̽ݯn>A{̽ѷo>$̽)n>{̽p>7̽k>N̽pau>G̽p>7̽Is>̽p>7̽)n>{̽Is>̽ =̽Yw>G=̽z~>V=̽`x>Mn̽ y>̽ ~>̽o;Lc̽<8Ž̽o;̽̽<8Ž̽>p%<̽̽o;̽̽J >Lc=̽T>8=̽J >=̽T>8=̽#}>=̽J >=̽>p%<=̽x<8=̽o;=̽x<8=̽o;Lc=̽o;=̽#}>̽̽P> 9Ž̽J >̽̽P> 9Ž̽J >Lc̽J >̽̽<Ĕ=̽÷< 8=̽;}Ì=̽י==̽H]9̽n9̽H]DZ[=̽y>P=̽>t> F=̽6=tQ|=̽/)==̽_A=.=̽m=^x=̽/)==̽6=tQ|=̽ R8̽c>@N̽v_>=D̽ޙV=8̽m= ̽Kmf=.̽(Y^=g̽m= ̽ޙV=8̽I=̽K>^=̽R>am=̽2>̽8>[̽5>̽4>L̽8>[̽2>̽\=<.=̽=a=̽=v=̽oF=-ű=̽Ƃ=F=̽=Eǵ=̽ Nh=W=̽Ƃ=F=̽oF=-ű=̽I ,=la=̽_A=.=̽/)==̽\È=r=̽7̐==̽V=(=̽\È=r=̽0=Ҩ=̽7̐==̽=a=̽==̽=v=̽=a=̽0=Ҩ=̽==̽m=^x=̽l=߉=̽/)==̽m=^x=̽3=Jeu=̽l=߉=̽c>(Ψ̽h>f̽h>̽;S`>p̽d>8̽i^>v̽d>i=̽~^>Я=̽,^>=̽< ~̽b˞̽`)Q>Em̽W>̽0S>˞̽UL>5~̽`)Q>Em̽K><=̽<ګ=̽LoA{̽h>f̽j>ܘ̽h>̽ m> ̽)n>{̽P.i>=̽[o>Fz=̽|k>ɕ=̽/A<~=̽8<=̽÷< 8=̽==̽=r=̽=v=̽D L>ޗ½̽ F>d̽L>n̽<Ĕ=̽/A<~=̽÷< 8=̽|̽GZ> ̽i^>v̽GZ> ̽;S`>p̽i^>v̽S>h=̽X>i~=̽RZ>i=̽X>i~=̽ |]>y=̽RZ>i=̽==̽8<=̽/A<~=̽d>8̽;S`>p̽Yg>s̽~^>Я=̽d>i=̽e>_ً=̽j>ܘ̽h>f̽d>8̽h>f̽c>(Ψ̽d>8̽P.i>=̽|k>ɕ=̽d>i=̽= =̽l=߉=̽==̽l=߉=̽= =̽I ,=la=̽I ,=la=̽= =̽+"===̽h>̽h>f̽ m> ̽ݯn>A{̽ m> ̽h>f̽,^>=̽~^>Я=̽tY>b=̽Qk=[̽=-̽bLc=̽J >=̽z~>V=̽J >̽J >Lc̽ ~>̽X>i~=̽R/^>9=̽ |]>y=̽o;Lc=̽x<8=̽XS<=̽x<8=̽ƵQp%<=̽<8Ž̽o;Lc̽I<̽Q<ý̽<8Ž̽I<̽<8Ž̽Q<ý̽>p%<̽̽J >Lc̽P> 9Ž̽ ~>̽P> 9Ž̽{>ý̽ ~>̽{>ý̽P> 9Ž̽#}>̽̽T>8=̽J >Lc=̽z~>V=̽{>=̽T>8=̽z~>V=̽T>8=̽{>=̽#}>=̽+"===̽_A=.=̽I ,=la=̽^>̽̽a>jý̽bd>̽̽n>Y'̽,r>$̽t>G.̽/A<~=̽<5=̽==̽w==̽K><=̽=X=̽w==̽<ګ=̽K><=̽ ̽̽k>̽̽k>N̽B]q>̽̽p>7̽pau>G̽B]q>̽̽k>N̽p>7̽<;L=̽]9̽\TZ{̽$~>녽̽v>1 ̽cS=̽>6\=̽J >k=̽p}>)ּ̽J > ŭ̽J >k߼̽J > ŭ̽}>;̽J >>x̽ux>̽)w>~\ʼ̽p}>)ּ̽=5=̽==̽r==̽#==̽S==̽3==̽==̽=r=̽ ==̽2>̭̽̽6>ALý̽O+9>̽̽ ==̽==̽ώ==̽ ==̽=r=̽==̽==̽=Eǵ=̽==̽==̽=r=̽==̽==̽==̽ώ==̽==̽==̽Ƃ=F=̽==̽=Eǵ=̽Ƃ=F=̽%R>=̽\N>p=̽K>=̽Z0X=̽̽c=Ľ̽*q=̽̽D L>ޗ½̽%R>̽̽K>̽̽%R>̽̽D L>ޗ½̽u R>̽D L>ޗ½̽L>n̽u R>̽ Nh=W=̽Pg==̽T=L=̽<&==̽W==̽C ==̽W==̽<&==̽9'==̽W==̽=X=̽K><=̽5=̽<&=̽̽C =̽̽<{=n̽5=̽ <=̽W==̽K><=̽UL<=̽8̽̽k>N̽k>̽̽)n>{̽k>N̽h>̽qV>D鶽̽u R>̽0S>˞̽Do>Ǎ̽v>1 ̽&u>0̽Do>Ǎ̽ѷo>$̽j>ܘ̽&u>0̽ѷo>$̽Do>Ǎ̽W==̽9'==̽=X=̽ݯn>A{̽_u>T.̽ 5s>̽z>̽_u>T.̽&u>0̽H6u>=̽[o>Fz=̽r>\ɬ=̽H6u>=̽&Y|>v=̽Gw>n=̽Rzq>1?=̽o>=̽|k>ɕ=̽6[*<̽o;I̽3)̽`x>Mn̽ 5s>̽_u>T.̽z>̽ y>̽_u>T.̽ y>̽ 5s>̽Yw>G=̽y> =̽r>\ɬ=̽&Y|>v=̽H6u>=̽y> =̽y> =̽H6u>=̽r>\ɬ=̽c\<̽rZ)Xt<̽E>ɪ<̽J> ̄<̽7q5>yf<̽b1>Q*<̽{/>^<̽==̽==̽u==̽=8^̽=&:̽=\̽h=A̽=;̽C>;O̽g5>T̽ui;>i8̽q9>5ʼ̽ui;>i8̽g5>T̽"u6>;x̽r =#M̽Y=^`̽W=pЁ̽b=ͼ̽fM=B̽ j=~̽yD=l=̽˷==̽=J}=̽=T]k=̽α=d)\=̽==N=̽_;=UOK=̽α=d)\=̽yD=l=̽Q>Hζ̽6QW>m̽W>[̽e><̽)a>3<̽^>V<̽pp>NR̽u>Y;̽m>[ ̽ui;>i8̽D@>湼̽q9>5ʼ̽[N>̽[F>y̽yM>̽A=]9H̽e8='̽ӸR=ջ̽A=]9H̽fM=B̽q*1=̽`g>i㩼̽a>Aʼ̽]4`>gܑ̽)=7ߎ=̽==ࠐ=̽=C=̽x[=<;̽L=o͈9̽0C=,{;̽e8='̽L=o͈9̽ӸR=ջ̽=椏̽\"=4C̽q*1=̽e8='̽\"=4C̽1=5̽)~=/=̽_;=UOK=̽=J/=̽+>|%=̽->=̽1>ҕ=̽K.6>4=̽n4>6ԍ=̽1>ҕ=̽h=|=̽=`s=̽=J}=̽h=|=̽V=v=̽==ࠐ=̽;b=Β=̽ w=z=̽==̽=]=̽I==̽=C=̽I==̽=]=̽r.==̽~=h=̽[= =̽w8=G=̽`S:>=̽wD@>@O=̽8^>>=̽`S:>=̽n4>6ԍ=̽K.6>4=̽=eX=̽=n;=̽:=`B=̽.b&>=̽7*>M=̽#>?=̽aS>X̽BS>JH̽Y>tk̽5=2<̽q=<̽r=<̽q=<̽5=2<̽Nh=$<̽x43>G̽3>%je̽J.>KW̽3>%je̽x43>G̽ 9>ȿL̽q5c=<̽q=<̽Nh=$<̽=`s=̽a=Ql=̽=eX=̽+_E>%)̽:K>)̽H>^a?̽+_E>%)̽MI>>2̽{A>3̽0.;>"c̽3>%je̽ 9>ȿL̽Ő=sfN=̽)=2=̽=C=̽)=2=̽Ő=sfN=̽O=;=̽Z>ݲ̽aS>X̽Y>tk̽?>X̽7>̽q9>5ʼ̽D>{RI=̽@I>4=̽&C>D)=̽N>!=̽@I>4=̽zN>B=̽F&>3̽Z>̽$><諽̽i>(><̽5l>2;̽e>o;̽=T]k=̽=x=̽==̽u>Y;̽Dq>pZ̽m>[ ̽ȜH>f̽[F>y̽[N>̽k=><̽;7P=l<̽Nh=$<̽D>{RI=̽?>I^=̽G>qg=̽= r=̽ =.]j=̽z"=㠂=̽Q2><̽7q5>yf<̽{/>^<̽a>Aʼ̽Z>O̽Z>ݲ̽[F>y̽˘G>?-̽yM>̽˘G>?-̽[F>y̽D@>湼̽H]!=̽;U>@=̽P><̽Y6= ̽NV>Ď̽:=ږ̽i=*0̽NV>Ď̽Y6= ̽bg5=<̽;7P=l<̽m`8=N[<̽bg5=<̽i=<̽4=<̽;7P=l<̽T=F<̽m`8=N[<̽T=F<̽;7P=l<̽k=><̽"=b̽=椏̽q*1=̽0 >W=̽>.s=̽H>it=̽`@=3X̽Y=Gs̽zC=i#̽!>>>=̽D>{RI=̽&C>D)=̽!>>>=̽`8>+P4=̽9>^Q=̽/>=̽->=̽7*>M=̽7>̽Ɇ4>^̽Zd0>̽Kw>=K̽}>J̽ y>c̽wD@>@O=̽!D> ڋ=̽8^>>=̽=G=̽=<̽T=+=̽i=<̽=<̽4=<̽e:>6=̽/?>;=̽<@>=̽G+= ̽Y#=z(̽=̽Y#=z(̽G+= ̽zC=i#̽+>j>Ǹ<̽.n><̽Lp>ɵ<̽6E>=̽wD@>@O=̽<@>=̽[= =̽=l!=̽w8=G=̽=n;=̽=l!=̽Q=4'=̽lp=gR=̽=n;=̽=eX=̽"g>̽a>Aʼ̽`g>i㩼̽"g>̽k>̽?f>&G ̽za>P̽a>̽?f>&G ̽a>Aʼ̽a>̽Z>O̽2>/ۼ̽7>̽Zd0>̽7>̽2>/ۼ̽q9>5ʼ̽G-v>6̽Yo>/̽Dq>pZ̽MI>>2̽ ,?>N̽ 9>ȿL̽=`s=̽=K]=̽=J}=̽[>-2̽`̽Z>O̽5l>2;̽f8t>;̽pp>NR̽MI>>2̽y:>؁̽{A>3̽7>̽y:>؁̽Ɇ4>^̽K%=`I̽P=Fμ̽r =#M̽W=pЁ̽K%=`I̽r =#M̽;7P=l<̽I;M=m<̽Nh=$<̽+J==̽I;M=m<̽4=<̽aS>X̽OT>Լ̽yM>̽OT>Լ̽aS>X̽Z>ݲ̽fM=B̽@=7̼̽q*1=̽@=7̼̽fM=B̽b=ͼ̽o;.:̽9<<̽/)=̽S(">=̽#>?=̽Z'>)=̽->=̽+>|%=̽=W&¼̽=椏̽"=b̽+< =̽=G=̽T=+=̽]O7>p[2̽MI>>2̽ 9>ȿL̽]O7>p[2̽1>0̽Ɇ4>^̽M8>OŹ=̽/?>;=̽e:>6=̽ݮ=QA̽xi=zl̽ =̽ݮ=QA̽F=ԩi̽$=op̽?S>N=̽M>Z=̽S>h=̽M>Z=̽?S>N=̽zN>B=̽i1=C =̽+J==̽4=<̽3Z>,=̽\>=̽;U>@=̽H><̽ D>)Xt<̽J> ̄<̽3=p<̽i=<̽=b<̽]=*c<̽Fn=g,<̽k=><̽za>P̽ah>!̽˿d>R8̽ah>!̽za>P̽?f>&G ̽@<؁<̽{=7_<̽I2<#<̽]T>s;̽gN>';̽NP>'T:<̽]T>s;̽[>k;̽6QW>m̽/= =̽˷==̽ w=z=̽Q==̽ Y= =̽/= =̽ Y= =̽Q==̽==̽=mn̽=,̽$=op̽7ٛ=ٛ[=̽Ő=sfN=̽\=j=̽Ő=sfN=̽7ٛ=ٛ[=̽O=;=̽ =.]j=̽ ==̽z"=㠂=̽ ==̽ =.]j=̽a=Ql=̽1^=G&v̽g=^x̽ή=}Xn̽g=^x̽1^=G&v̽Y6= ̽==̽=ڵ=̽r.==̽ui;>i8̽=B>3z̽D@>湼̽=Q+h̽mo>D'n̽i=*0̽u= =̽q=<̽q5c=<̽=G=̽4=V%=̽i1=C =̽˷==̽Hd==̽=J}=̽Hd==̽˷==̽/= =̽`̽0O>̽[N>̽'H>=̽^M>v=̽G>qg=̽^M>v=̽M>Z=̽G>qg=̽.n><̽s>DN<̽Lp>ɵ<̽L>k+̽Q>Hζ̽BS>JH̽/?>;=̽E>=̽<@>=̽5=<̽E=̽,>=̽7*>M=̽p="=̽Z===̽u= =̽Y>p=̽V>xO=̽>e=̽Q>Hζ̽O>7:̽6QW>m̽gN>';̽O>7:̽H> ;̽g>v =̽)b>)=̽c>1=̽%2>=?̽%1>C̽+>ӝ̽~n<̽)y>j<̽8|><̽Sv><̽Lp>ɵ<̽s>DN<̽)y>j<̽Sv><̽s>DN<̽f K>T̽h#D>nz̽H> ;̽f K>T̽Q>Hζ̽L>k+̽2>/=̽+q5>=̽/>=̽F7>:=̽+q5>=̽e:>6=̽H=h=̽+J==̽i1=C =̽PM>̽aS>X̽yM>̽aS>X̽PM>̽BS>JH̽>.s=̽]>\=̽= r=̽}>i<̽ܤv><̽8|><̽u>Y;̽}y>C/R̽Dq>pZ̽}y>C/R̽u>Y;̽b?}>|߻̽π=[d :̽ˏ=*j̽Jg=8I̽ˏ=*j̽π=[d :̽N=X̽)=7ߎ=̽i=,=̽==ࠐ=̽=`s=̽i=,=̽a=Ql=̽1>X̽>q̽mo>D'n̽4=̽N>!=̽zN>B=̽N>!=̽QT>4=̽;U>@=̽}==̽8=.=̽ =<̽}==̽q=<̽u= =̽H>l̽BF>̽L>k+̽+J==̽]==̽q5c=<̽]==̽+J==̽H=h=̽g=^x̽=5̽B=@̽:=ږ̽=5̽Y6= ̽=5̽g=^x̽Y6= ̽jU> ̽`̽[>-2̽`̽jU> ̽0O>̽v>n<̽G=<̽[=2<̽= p<̽G=<̽=<̽G=<̽= p<̽[=2<̽Y>zH̽J > ̽J >>x̽}>;̽Y>zH̽J >>x̽J > ̽Y>zH̽b?}>|߻̽3>%je̽->jn̽J.>KW̽&>Z>t̽->jn̽+>ӝ̽!A>^e̽!=>xt̽0.;>"c̽T=L=̽_L=z=̽ Nh=W=̽.n><̽~/p>~ =̽s>DN<̽l=X̽Y=^`̽r =#M̽~\=s=̽K}>u=̽J >S=̽Om>HT̽Tls>q]̽o>ao̽ y>c̽Tls>q]̽Kw>=K̽' =X-=̽= B=̽ac=]G=̽' =X-=̽=G=̽+< =̽ =,<̽]=,g<̽=<̽Yo>/̽ m>nͼ̽`g>i㩼̽\>=̽)^>u*=̽)b>)=̽)^>u*=̽c>1=̽)b>)=̽ =z=̽=T]k=̽==̽ =z=̽˷==̽yD=l=̽c><̽e><̽^>V<̽e><̽c><̽+>j>Ǹ<̽:K>)̽=Q>t*̽O>]A̽=Q>t*̽цU>C8̽O>]A̽=Q>t*̽:K>)̽0O>̽y= =̽k==̽Q=4'=̽.==\<̽ =<̽=6<̽<~̽1̽>q̽ >f̽>1̽NV>Ď̽i=*0̽w8=G=̽q=t=̽~=h=̽ =.]j=̽=7M=̽lp=gR=̽n>Y'̽l>̽,r>$̽k>̽l>̽?f>&G ̽ >E <̽4v ><̽>+<̽=椏̽< e̽= )̽o&>̽̽T)>ý̽n,>̽̽;~,>^滽̽T)>ý̽F&>3̽@<؁<̽z<Ѩ<̽3=p<̽z<Ѩ<̽@<؁<̽#<<{<̽;b=Β=̽==̽ w=z=̽_==̽MK=H~=̽_A=.=̽MK=H~=̽6=tQ|=̽_A=.=̽8C>?̽MI>>2̽+_E>%)̽H>^a?̽8C>?̽+_E>%)̽MI>>2̽8C>?̽ ,?>N̽E'u>V=̽)y>j<̽s>DN<̽)y>j<̽E'u>V=̽}>=̽7h><̽.n><̽+>j>Ǹ<̽bx>׺̽u>Y;̽pp>NR̽<>(vt=̽8>=̽8^>>=̽8>=̽<>(vt=̽R+7>Wl=̽'z==̽w==̽=X=̽g>v =̽Ɍe>f=̽)b>)=̽0c>p<̽Ɍe>f=̽7h><̽B=E=̽;b=Β=̽7̐==̽0=Ҩ=̽B=E=̽7̐==̽\N>p=̽|L>=̽(7I>)=̽$<̽@O=̽3;>Ġ=̽<@>=̽F7>:=̽3;>Ġ=̽K.6>4=̽M=̽p=5̽X=w̽p=5̽M=̽=o̽O+9>=̽5>=̽2>=̽2>/=̽5>=̽M8>OŹ=̽*w=G;̽x[=<;̽Fn=g,<̽x[=<;̽*w=G;̽Jg=8I̽\>=̽7_>7=̽ͼY>-<̽7_>7=̽\>=̽)b>)=̽י==̽>T=̽z"=㠂=̽/>y=̽n,>=̽2>=̽/>y=̽2>/=̽,>=̽S==̽1=f5=̽r==̽1=f5=̽=5=̽r==̽-B>U{=̽'H>=̽G>qg=̽-B>U{=̽<>(vt=̽8^>>=̽P</=̽<=̽ytx̽Me>z~̽Lf>a$h̽!A>^e̽f5B>tz̽!=>xt̽ilB>ᆽ̽f5B>tz̽F>D̽ch>hYO̽c>@N̽˿d>R8̽`d3=f=̽ͲC=t=̽^H==̽9'==̽l-=p=̽=X=̽`d3=f=̽l-=p=̽ͲC=t=̽OP>̽U>J!̽;U>h̽n4>6ԍ=̽]%/>z=̽1>ҕ=̽=G=̽?%=P0=̽4=V%=̽ac=]G=̽?%=P0=̽' =X-=̽?%=P0=̽=G=̽' =X-=̽o;̽!<̽I<̽n,>=̽ )>K=̽o&>=̽.b&>=̽ )>K=̽,>=̽_==̽Y=nt=̽MK=H~=̽~nT̽H>^a?̽EK>]̽D>T̽!A>^e̽ ,?>N̽F=^=̽Ő=sfN=̽=C=̽w=!Q=̽F=^=̽=C=̽}%=5O=̽`d3=f=̽+"===̽= =̽}%=5O=̽+"===̽`d3=f=̽}%=5O=̽'z==̽=n;=̽0x=8=̽=l!=̽0x=8=̽w8=G=̽=l!=̽.b>+=̽ޓg>|=̽$a>zj=̽j=<̽j=̽=4=sU=̽6=tQ|=̽x/=>j=̽m=^x=̽x/=>j=̽2=b=̽m=^x=̽I>Ơ=̽K>^=̽O>=̽#L>Ζ=̽I>Ơ=̽O>=̽K>^=̽I>Ơ=̽6E>=̽tk>8^<̽op>p<̽i>(><̽e><̽tk>8^<̽i>(><̽wD@>@O=̽ G>=̽!D> ڋ=̽ G>=̽wD@>@O=̽6E>=̽=̽K-=]̽<{=n̽9=լ̽=̽<{=n̽"|>̽)w>~\ʼ̽G-v>6̽}>;̽"|>̽G-v>6̽N 0=0鞽̽n9D=ڦ̽K-=]̽ pZ>`̽i^>v̽a>y̽<9=̽~nC̽2>̽->̽4>L̽2>̽]57>̽2>̽4>L̽->̽2>̽%1>C̽]57>̽o>B<̽op>p<̽u>=<̽o>B<̽5l>2;̽i>(><̽op>p<̽o>B<̽i>(><̽M>Z=̽^I>FN=̽G>qg=̽^I>FN=̽D>{RI=̽G>qg=̽^I>FN=̽M>Z=̽zN>B=̽8>4y̽3>%je̽0.;>"c̽3>%je̽8>4y̽%2>=?̽->=̽2>8=̽1>ҕ=̽2>8=̽->=̽/>=̽M=̽?=$̽N=YȢ̽xi=zl̽?=$̽ =̽iq>̽ux>̽,r>$̽=:W=̽e%ý̽cX>̽̽%R>̽̽u R>̽W>%ý̽%R>̽̽m̽[>7媽̽ ~>̽[>7媽̽J >̽ ~>̽]=*c<̽.=+<̽Fn=g,<̽.=+<̽]=*c<̽ ?=d8<̽>=̽y> =̽z~>V=̽J >=̽>=̽z~>V=̽o;̽<̽o;[!̽<̽o;̽g<\̽g<6%̽o;.:̽o;[!̽o;.:̽g<6%̽9<<̽}7>D̽%1>C̽%2>=?̽c>1=̽i>p5=̽g>v =̽o>5=̽m>=̽i>p5=̽m>=̽g>v =̽i>p5=̽.n><̽j>G=̽~/p>~ =̽j>G=̽.n><̽7h><̽ǟN>#v̽U>J!̽OP>̽-F>0pk̽~/J>As̽F>D̽p="=̽9>=+=̽Z===̽6?==̽lS8= =̽<&==̽lS8= =̽9'==̽<&==̽gy>-3̽Kw>=K̽t>G.̽gy>-3̽~>-̽}>J̽Kw>=K̽gy>-3̽}>J̽v>1(=̽Hq>=̽o>5=̽4>=¹=̽T=L=̽ͲC=t=̽~/J>As̽K>v&̽F>D̽K>̽K>v&̽OP>̽GZ> ̽[>>̽;S`>p̽XDR3̽&u>0̽v>1 ̽$~>녽̽=}>R3̽v>1 ̽"]P>̽ K> V̽K>̽ K> V̽"]P>̽`)Q>Em̽@|=ٟA̽=qV̽F=\̽=qV̽=Q+h̽F=\̽(\=gD=̽5x= w=̽`o==̽==̽w==̽'z==̽w==̽==̽<5=̽h>̽:c> ̽a>y̽$hG=$=i=̽6=tQ|=̽MK=H~=̽g> zI=̽c>1=̽b>jP=̽4>L̽.>2̽->̽.>2̽4>L̽2>̽2>̽90>R;̽ +>8̽90>R;̽2>̽5>̽90>R;̽;~,>^滽̽ +>8̽(h>b=̽ޓg>|=̽nm>$`z=̽J0>̽2>̽̽n,>̽̽SW>"JB=̽X>"T=̽F]>@~A=̽@c<+P̽](̽,r>$̽ux>̽rG>=̽ilB>ᆽ̽F>D̽J >̽c~>D̽J >k߼̽c~>D̽p}>)ּ̽J >k߼̽#i]>nX=̽RZ>i=̽$a>zj=̽#i]>nX=̽b>jP=̽F]>@~A=̽b>jP=̽#i]>nX=̽$a>zj=̽W>̽hT>l̽GZ> ̽hT>l̽;U>h̽GZ> ̽hT>l̽W>̽`)Q>Em̽=&:̽=:̽=\̽=:̽=&:̽Vb=Ts;̽S=P=̽T=L=̽Pg==̽ԯ==̽\=<.=̽=v=̽uf<=̽ =Y̽9=լ̽J >[!̽v>Be̽J >̽v>Be̽J >[!̽~>-̽*a=%`=̽w=!Q=̽`=|E=̽F+=Ӑ̽9(=d̽E=WV̽)w>~\ʼ̽GNr>̽G-v>6̽Yo>/̽GNr>̽ m>nͼ̽GNr>̽Yo>/̽G-v>6̽1k>T;̽˿d>R8̽ah>!̽n>Y'̽1k>T;̽ah>!̽35=A==̽=4=sU=̽(K=EN=̽mJ=4=̽35=A==̽(K=EN=̽IJ=̽>=̽J >=̽[>7媽̽J >J̽J >̽'z==̽l-=p=̽`d3=f=̽'z==̽=X=̽l-=p=̽l>̽iq>̽,r>$̽l>̽k>̽iq>̽gg<ɘ=̽~T̽ pZ>`̽a>y̽qV>D鶽̽ pZ>`̽l]>T̽v/=(i̽#=^̽}3= X̽<̽g<6%̽o;[!̽ch>hYO̽˿d>R8̽1k>T;̽"]P>̽OP>̽;U>h̽K>̽OP>̽"]P>̽$hG=$=i=̽(K=EN=̽=4=sU=̽Zt=&=̽jZ=2=̽`o==̽Zt=&=̽_==̽jZ=2=̽HKW>\>d̽V>N̽Iu[>7V̽fQ>`̽V>N̽HKW>\>d̽e>_ً=̽ޓg>|=̽.b>+=̽5>=̽/>y=̽2>=̽5>=̽2>/=̽/>y=̽T)>ý̽J0>̽n,>̽̽T)>ý̽;~,>^滽̽J0>̽=-̽n=w)̽ =Y̽=̽N 0=0鞽̽K-=]̽=̽n=w)̽N 0=0鞽̽x/=>j=̽$hG=$=i=̽=4=sU=̽x/=>j=̽6=tQ|=̽$hG=$=i=̽:c> ̽l]>T̽a>y̽a>jý̽l]>T̽:c> ̽2>̽ +>8̽.>2̽hT>l̽"]P>̽;U>h̽`)Q>Em̽"]P>̽hT>l̽!<̽Iz+=̽ |]>y=̽R/^>9=̽$a>zj=̽ |]>y=̽.b>+=̽J >k=̽a>s=̽J >S=̽J >k=̽>6\=̽a>s=̽c\<̽G =̽J >&=̽J >I=̽==̽ԯ==̽=v=̽==̽=5=̽ԯ==̽==̽==̽<5=̽= =̽==̽==̽=̽ =Y̽n=w)̽=̽9=լ̽ =Y̽1=<>0"=̽6>w5=̽`8>+P4=̽6>|r=̽}"> !=̽Ø>^=̽գ=[*|̽=._̽F=ԩi̽N=YȢ̽=o̽M=̽0|3>#F=̽9>^Q=̽`8>+P4=̽b1>Q*<̽5>Dz;̽:1>PD%;̽=,̽(y=[̽xi=zl̽B=@̽=5̽g=^x̽B=@̽]A=6:̽=5̽[ì̽@=Rs̽H><̽NP>'T:<̽gN>';̽J> ̄<̽NP>'T:<̽H><̽ >E <̽N><̽y>v<̽==N=̽7ٛ=ٛ[=̽=T]k=̽==N=̽O=;=̽7ٛ=ٛ[=̽)=2=̽Κ==̽8=.=̽O=;=̽Κ==̽)=2=̽I!k>Cw̽Dq>pZ̽Yo>/̽I!k>Cw̽m>[ ̽Dq>pZ̽pp>NR̽Ki>lE̽5l>2;̽m>[ ̽Ki>lE̽pp>NR̽Κ==̽ =<̽8=.=̽=v̽=5̽:=ږ̽W>[̽BS>JH̽Q>Hζ̽W>[̽Y>tk̽BS>JH̽Z>ݲ̽]4`>gܑ̽a>Aʼ̽Y>tk̽]4`>gܑ̽Z>ݲ̽Z'>)=̽7*>M=̽->=̽#>?=̽7*>M=̽Z'>)=̽ͼY>-<̽;U>@=̽\>=̽i>(><̽)a>3<̽e><̽e>o;̽)a>3<̽i>(><̽1^=G&v̽F=\̽=Q+h̽ή=}Xn̽F=\̽1^=G&v̽}==̽r=<̽q=<̽ =<̽r=<̽}==̽c><̽^>j*<̽0c>p<̽^>V<̽^>j*<̽c><̽!>>>=̽1=<>0"=̽`8>+P4=̽&C>D)=̽1=<>0"=̽!>>>=̽G> =̽@I>4=̽N>!=̽G> =̽&C>D)=̽@I>4=̽Q=4'=̽:=`B=̽=n;=̽tƠ=̽믖=1̽Y=ɬ̽0C=,{;̽T=F<̽x[=<;̽0C=,{;̽m`8=N[<̽T=F<̽bg5=<̽=b<̽i=<̽m`8=N[<̽=b<̽bg5=<̽Uy]>ԅ:̽6QW>m̽[>k;̽?>X̽y:>؁̽7>̽?>X̽{A>3̽y:>؁̽+_E>%)̽ȜH>f̽:K>)̽+_E>%)̽{A>3̽ȜH>f̽r=<̽ ?=d8<̽]=*c<̽Jg=8I̽L=o͈9̽x[=<;̽ӸR=ջ̽L=o͈9̽Jg=8I̽$><諽̽!>4̽>(>G̽!>>>=̽?>I^=̽D>{RI=̽9>^Q=̽?>I^=̽!>>>=̽?>X̽D@>湼̽[F>y̽q9>5ʼ̽D@>湼̽?>X̽g5>T̽2>/ۼ̽.>q̽g5>T̽q9>5ʼ̽2>/ۼ̽]T>s;̽V>hF<̽[>k;̽NP>'T:<̽V>hF<̽]T>s;̽0.;>"c̽ ,?>N̽!A>^e̽0.;>"c̽ 9>ȿL̽ ,?>N̽]O7>p[2̽x43>G̽1>0̽ 9>ȿL̽x43>G̽]O7>p[2̽X=w̽?=$̽M=̽X=w̽ =̽?=$̽ݮ=QA̽գ=[*|̽F=ԩi̽ =̽գ=[*|̽ݮ=QA̽o>B<̽f8t>;̽5l>2;̽u>=<̽f8t>;̽o>B<̽f8t>;̽ z>A <̽}z>Z`;̽u>=<̽ z>A <̽f8t>;̽=]=̽==ࠐ=̽V=v=̽=C=̽==ࠐ=̽=]=̽]=,g<̽^=;̽=R<̽l=X̽u=O̽M=d'w̽r =#M̽u=O̽l=X̽A=]9H̽\"=4C̽e8='̽A=]9H̽q*1=̽\"=4C̽"=b̽@=7̼̽.=t̽"=b̽q*1=̽@=7̼̽= )̽\"=4C̽=椏̽1=5̽\"=4C̽= )̽5=2<̽r=<̽]=*c<̽r=<̽r=<̽5=2<̽ =z=̽ w=z=̽˷==̽==̽ w=z=̽ =z=̽^>j*<̽7_>7=̽0c>p<̽^>j*<̽ͼY>-<̽7_>7=̽@=7̼̽`@=3X̽.=t̽`g>i㩼̽I!k>Cw̽Yo>/̽h=|=̽Hd==̽V=v=̽h=|=̽=J}=̽Hd==̽yD=l=̽=K]=̽_;=UOK=̽yD=l=̽=J}=̽=K]=̽ =z=̽α=d)\=̽=T]k=̽yD=l=̽α=d)\=̽ =z=̽=w ̽X=w̽p=5̽G+= ̽=̽.=t̽G+= ̽=̽=̽$<̽)6=^l%̽H]s;̽O>7:̽gN>';̽]T>s;̽6QW>m̽O>7:̽PM>̽˘G>?-̽H>l̽yM>̽˘G>?-̽PM>̽[N>̽OT>Լ̽`̽[N>̽yM>̽OT>Լ̽bx>׺̽f8t>;̽}z>Z`;̽pp>NR̽f8t>;̽bx>׺̽=o̽tƠ=̽p=5̽˘G>?-̽=B>3z̽H>l̽˘G>?-̽D@>湼̽=B>3z̽*=ȟ=̽=]=̽V=v=̽r.==̽=]=̽*=ȟ=̽=eX=̽=K]=̽=`s=̽=eX=̽:=`B=̽=K]=̽-B>U{=̽?>I^=̽<>(vt=̽G>qg=̽?>I^=̽-B>U{=̽3=p<̽{=7_<̽@<؁<̽3=p<̽=b<̽{=7_<̽6E>=̽E>=̽K>^=̽6E>=̽<@>=̽E>=̽e:>6=̽3;>Ġ=̽F7>:=̽e:>6=̽<@>=̽3;>Ġ=̽ȜH>f̽0O>̽:K>)̽ȜH>f̽[N>̽0O>̽'=E̽}3= X̽#=^̽'=E̽VC=B̽}3= X̽k=><̽5=2<̽]=*c<̽Nh=$<̽5=2<̽k=><̽q5c=<̽I;M=m<̽+J==̽q5c=<̽Nh=$<̽I;M=m<̽"g>̽ m>nͼ̽k>̽"g>̽`g>i㩼̽ m>nͼ̽h=|=̽i=,=̽=`s=̽h=|=̽==ࠐ=̽i=,=̽Sv><̽ܤv><̽Lp>ɵ<̽Sv><̽8|><̽ܤv><̽ݮ=QA̽=,̽xi=zl̽ݮ=QA̽$=op̽=,̽`S:>=̽8>=̽n4>6ԍ=̽`S:>=̽8^>>=̽8>=̽-B>U{=̽!D> ڋ=̽'H>=̽8^>>=̽!D> ڋ=̽-B>U{=̽+>|%=̽1>ҕ=̽]%/>z=̽K.6>4=̽2>8=̽F7>:=̽K.6>4=̽1>ҕ=̽2>8=̽~=h=̽5=<̽[= =̽H> ;̽H><̽gN>';̽Z'>)=̽;%>=̽S(">=̽+>|%=̽;%>=̽Z'>)=̽`S:>=̽3;>Ġ=̽wD@>@O=̽`S:>=̽K.6>4=̽3;>Ġ=̽π=[d :̽Jg=8I̽*w=G;̽`@=3X̽G+= ̽.=t̽`@=3X̽zC=i#̽G+= ̽y= =̽=l!=̽[= =̽Q=4'=̽=l!=̽y= =̽/= =̽==̽Q==̽/= =̽ w=z=̽==̽=B>3z̽BF>̽H>l̽a =E̽s=/ʦ̽wك=;̽a =E̽=w ̽s=/ʦ̽F&>3̽ +>8̽;~,>^滽̽F&>3̽$><諽̽ +>8̽5=<̽=<̽i=<̽T=+=̽=<̽5=<̽+< =̽gg<ɘ=̽~n=̽,>=̽2>/=̽/>=̽7*>M=̽,>=̽R<@\̽C<¼̽<~̽L>k+̽PM>̽H>l̽L>k+̽BS>JH̽PM>̽QT>4=̽?S>N=̽SW>"JB=̽zN>B=̽?S>N=̽QT>4=̽^I>FN=̽@I>4=̽D>{RI=̽zN>B=̽@I>4=̽^I>FN=̽E>=̽|L>=̽K>^=̽E>=̽(7I>)=̽|L>=̽y=̽a =E̽wك=;̽u= =̽]==̽p="=̽u= =̽q5c=<̽]==̽ ==̽i=,=̽)=7ߎ=̽ ==̽a=Ql=̽i=,=̽7ٛ=ٛ[=̽=x=̽=T]k=̽\=j=̽=x=̽7ٛ=ٛ[=̽0.;>"c̽!=>xt̽8>4y̽"g>̽a>̽a>Aʼ̽"g>̽?f>&G ̽a>̽ah>!̽l>̽n>Y'̽ah>!̽?f>&G ̽l>̽OT>Լ̽Z>O̽`̽Z>ݲ̽Z>O̽OT>Լ̽G-v>6̽}y>C/R̽}>;̽G-v>6̽Dq>pZ̽}y>C/R̽= r=̽>T=̽>.s=̽= r=̽z"=㠂=̽>T=̽T=F<̽Fn=g,<̽x[=<;̽T=F<̽k=><̽Fn=g,<̽[>-2̽a>̽za>P̽Z>O̽a>̽[>-2̽@c<+P̽9<<̽\T,=̽QT>4=̽SW>"JB=̽3Z>,=̽;U>@=̽QT>4=̽i=*0̽1^=G&v̽=Q+h̽i=*0̽Y6= ̽1^=G&v̽>1̽mo>D'n̽>q̽i=*0̽mo>D'n̽>1̽=W&¼̽=̽8=̽+q5>=̽F7>:=̽/>=̽+q5>=̽2>8=̽f K>T̽O>7:̽Q>Hζ̽H> ;̽O>7:̽f K>T̽%2>=?̽->jn̽3>%je̽%2>=?̽+>ӝ̽->jn̽]O7>p[2̽y:>؁̽MI>>2̽Ɇ4>^̽y:>؁̽]O7>p[2̽}y>C/R̽Y>zH̽}>;̽}y>C/R̽b?}>|߻̽Y>zH̽M8>OŹ=̽+q5>=̽2>/=̽M8>OŹ=̽e:>6=̽+q5>=̽7h><̽c><̽0c>p<̽+>j>Ǹ<̽c><̽7h><̽ G>=̽I>Ơ=̽#L>Ζ=̽6E>=̽I>Ơ=̽ G>=̽= )̽< e̽N<@̽g<\̽ST̽8C>?̽H>^a?̽ ,?>N̽8C>?̽D>T̽3Z>,=̽)^>u*=̽\>=̽' =X-=̽<o1=̽= B=̽' =X-=̽+< =̽<o1=̽H=h=̽4=V%=̽mJ=4=̽i1=C =̽4=V%=̽H=h=̽5=<̽z<Ѩ<̽Et*̽jU> ̽цU>C8̽0O>̽jU> ̽=Q>t*̽E'u>V=̽~/p>~ =̽Hq>=̽s>DN<̽~/p>~ =̽E'u>V=̽f K>T̽BF>̽h#D>nz̽f K>T̽L>k+̽BF>̽/>y=̽ )>K=̽n,>=̽/>y=̽,>=̽ )>K=̽7_>7=̽Ɍe>f=̽0c>p<̽)b>)=̽Ɍe>f=̽7_>7=̽%2>=?̽8>4y̽}7>D̽]==̽h^=X,=̽p="=̽]==̽H=h=̽h^=X,=̽lS8= =̽4>=¹=̽9'==̽T>==̽ZZ>5=̽Q!\>/=̽s=/ʦ̽Nj=ׇ̽wك=;̽Y=ɬ̽Nj=ׇ̽s=/ʦ̽p=>_:̽f5B>tz̽ilB>ᆽ̽!=>xt̽f5B>tz̽p=>_:̽a<'̽"{=̽q1S>=̽tY>b=̽j>G=̽Ɍe>f=̽g>v =̽7h><̽Ɍe>f=̽j>G=̽rG>=̽K>v&̽K>̽F>D̽K>v&̽rG>=̽-F>0pk̽f5B>tz̽!A>^e̽F>D̽f5B>tz̽-F>0pk̽j=<`u=̽Y=nt=̽_==̽==̽}%=5O=̽= =̽'z==̽}%=5O=̽==̽4>=¹=̽l-=p=̽9'==̽ͲC=t=̽l-=p=̽4>=¹=̽@c<+P̽<3Ba̽]D̽]57>̽%1>C̽=>̽]57>̽}7>D̽ǟN>#v̽K>v&̽~/J>As̽ǟN>#v̽OP>̽K>v&̽=:W=̽Eq<ݎH=̽e=+=̽[Nr=Z9=̽=C=̽rG>=̽5"E>̽ilB>ᆽ̽g> zI=̽i>p5=̽c>1=̽#i]>nX=̽X>"T=̽RZ>i=̽F]>@~A=̽X>"T=̽#i]>nX=̽v>Be̽c~>D̽J >̽o;S̽o;.:̽/4=̽>rF=̽!>?H=̽]>\=̽!>?H=̽>rF=̽!>?H=̽]>\=̽0 >W=̽]>\=̽>.s=̽0 >W=̽>D̽1>X̽>4@̽1>X̽>D̽= >zU̽>q̽1>X̽= >zU̽>4@̽>2̽>D̽l=X̽-K=̽Y=^`̽ =,<̽=B;̽^=;̽=B;̽E=}V}̽^=;̽Yg>s̽;S`>p̽`>̽`>̽;S`>p̽[>>̽Me>z~̽Yg>s̽`>̽H;> Z̽C>;O̽>`Pں̽<>d<̽>+<̽4v ><̽>+<̽<>d<̽v>n<̽v>n<̽[=2<̽>+<̽S=P=̽^H==̽ͲC=t=̽T=L=̽S=P=̽ͲC=t=̽=-̽F+=Ӑ̽n=w)̽N 0=0鞽̽n=w)̽F+=Ӑ̽x>=̽ >=̽n% >I2=̽u =̽m= ̽wك=;̽m= ̽u =̽Kmf=.̽wك=;̽Nj=ׇ̽u =̽iq>̽)w>~\ʼ̽ux>̽iq>̽GNr>̽)w>~\ʼ̽iq>̽k>̽ m>nͼ̽iq>̽ m>nͼ̽GNr>̽Z0X=̽̽6?=̽̽KN=s̽c=Ľ̽Z0X=̽̽KN=s̽Au>+Hw̽y>Z{̽v>1 ̽}3= X̽VC=B̽6F=c\̽Y[=5̽VC=B̽zC=i#̽@=Rs̽P=Fμ̽ӽ=Ѽ̽P=Fμ̽K%=`I̽ӽ=Ѽ̽X>"T=̽SW>"JB=̽?S>N=̽S>h=̽X>"T=̽?S>N=̽X>"T=̽S>h=̽RZ>i=̽&Y|>v=̽M~> =̽Gw>n=̽M~> =̽K}>u=̽Gw>n=̽= p<̽ =<̽.==\<̽ =<̽= p<̽=<̽ =<̽=<̽]=,g<̽c>@N̽ch>hYO̽Lf>a$h̽Om>HT̽_k>Oe̽ch>hYO̽_k>Oe̽Lf>a$h̽ch>hYO̽t'=̽5=̽<{=n̽~p>A̽Kw>=K̽Tls>q]̽Om>HT̽~p>A̽Tls>q]̽b?}>|߻̽J > F̽J > ̽u>Y;̽bx>׺̽b?}>|߻̽o;k߼̽g<\̽o;̽ۻ>@o<̽J >=̽J >k<̽ۻ>@o<̽)y>j<̽}>=̽UI>=̽u==̽[>=̽u==̽UI>=̽==̽7>=̽UI>=̽[>=̽UI>=̽7>=̽a>U=̽՚>Ԁ=̽UI>=̽a>U=̽>̽̽a>+½̽1 >̽̽==̽=C=̽|==̽==̽=C=̽==̽=Su̽ώ=̽̽=̽̽_L=z=̽6?==̽Z0X==̽lS8= =̽6?==̽_L=z=̽T=L=̽4>=¹=̽_L=z=̽lS8= =̽_L=z=̽4>=¹=̽-F>0pk̽!A>^e̽D>T̽EK>]̽-F>0pk̽D>T̽-F>0pk̽EK>]̽~/J>As̽o; ŭ<̽ <Ȝ<̽o;>x<̽R/^>9=̽~^>Я=̽e>_ً=̽.b>+=̽R/^>9=̽e>_ً=̽̈́=E6̽*=s̽13=./̽]=45=̽q=t=̽w8=G=̽0x=8=̽]=45=̽w8=G=̽0x=8=̽=7M=̽]=45=̽0x=8=̽=n;=̽lp=gR=̽lp=gR=̽=7M=̽0x=8=̽h#D>nz̽p@> ̽C>>b̽h#D>nz̽BF>̽p@> ̽=B>3z̽p@> ̽BF>̽Au>+Hw̽o>ao̽Tls>q]̽ y>c̽Au>+Hw̽Tls>q]̽Au>+Hw̽ y>c̽y>Z{̽o>ao̽_k>Oe̽Om>HT̽_k>Oe̽o>ao̽3Fk>tx̽Lf>a$h̽_k>Oe̽3Fk>tx̽ z>(̽t>G.̽,r>$̽ z>(̽gy>-3̽t>G.̽~>-̽gy>-3̽ z>(̽ z>(̽v>Be̽~>-̽< e̽=椏̽=W&¼̽< e̽=W&¼̽<~̽~w`>c.^̽Iu[>7V̽v_>=D̽c>@N̽~w`>c.^̽v_>=D̽J0>̽;~,>^滽̽90>R;̭̽6>ALý̽J0>̽5>̽J0>̭̽6>ALý̽2>̽̽J0>̽90>R;̽5>̽tk>8^<̽e><̽+>j>Ǹ<̽Lp>ɵ<̽tk>8^<̽+>j>Ǹ<̽tk>8^<̽Lp>ɵ<̽op>p<̽ܤv><̽op>p<̽Lp>ɵ<̽op>p<̽ܤv><̽u>=<̽M~> =̽&Y|>v=̽J >&=̽J >I̽$~>녽̽J >S̽1̽>ƣ̽NV>Ď̽ >f̽>ƣ̽>1̽m= ̽y=̽wك=;̽y=̽m= ̽(Y^=g̽tf=ݘ̽y=̽(Y^=g̽#<<{<̽~=̽י==̽^>Ì=̽;s>=̽>T=̽י==̽;s>=̽n% >I2=̽ >=̽;s>=̽ >=̽>T=̽j>ܘ̽d>8̽Yg>s̽Yg>s̽Do>Ǎ̽j>ܘ̽3Fk>tx̽Yg>s̽Me>z~̽Do>Ǎ̽Yg>s̽3Fk>tx̽c=m=̽==̽^>Ì=̽= =̽==̽c=m=̽P==̽==̽= =̽D>5=̽ K> V̽UL>5~̽UJF>̽D>5=̽UL>5~̽=7M=̽>rF=̽]=45=̽K-=]̽t'=̽<{=n̽~>-̽J >.:̽}>J̽4v ><̽>@N1<̽<>d<̽R>am=̽K>^=̽|L>=̽R>am=̽|L>=̽T>==̽|L>=̽\N>p=̽T>==̽;b=Β=̽B=E=̽==̽=a=̽B=E=̽0=Ҩ=̽ا=H=̽B=E=̽=a=̽==̽B=E=̽ا=H=̽o;&̽o;I̽6[*<̽t>G.̽~p>A̽n>Y'̽~p>A̽t>G.̽Kw>=K̽!j̽D>5=̽UJF>̽҈<>f̽D>5=̽b?>!j̽҈<>f̽S̽D>5=̽>4@̽=qV̽@|=ٟA̽1>X̽=qV̽>4@̽=Q+h̽=qV̽mo>D'n̽1>X̽mo>D'n̽=qV̽ pZ>`̽W>̽i^>v̽W>̽ pZ>`̽0S>˞̽ pZ>`̽qV>D鶽̽0S>˞̽35=A==̽mJ=4=̽4=V%=̽35=A==̽ac=]G=̽=4=sU=̽35=A==̽?%=P0=̽ac=]G=̽4=V%=̽?%=P0=̽35=A==̽d>i=̽|k>ɕ=̽e>_ً=̽ޙV=8̽Kmf=.̽KN=s̽Kmf=.̽c=Ľ̽KN=s̽.<=W̽ޙV=8̽KN=s̽cX>=̽^>=̽ZZ>5=̽Eq<ݎH=̽= B=̽<o1=̽Eq<ݎH=̽<;L=̽e5=̽m>AL=̽>t> F=̽o>5=̽i>p5=̽m>AL=̽m>AL=̽i>p5=̽g> zI=̽fb>4=̽^>ҭ=̽Q!\>/=̽\T[̽҈<>f̽b?>!j̽҈<>f̽8>[̽4>L̽҈<>f̽4>L̽]57>̽=:̽^=;̽E=}V}̽Lf>a$h̽~w`>c.^̽c>@N̽}3= X̽6F=c\̽v/=(i̽{D=zs̽v/=(i̽6F=c\̽^>̽̽l]>T̽a>jý̽;U>h̽[>>̽GZ> ̽[>>̽;U>h̽U>J!̽U>J!̽EM\>}s̽[>>̽m ̽bd>̽̽a>jý̽bd>̽̽:c> ̽k>N̽:c> ̽h>̽k>N̽ǟN>#v̽~/J>As̽EK>]̽fQ>`̽ǟN>#v̽EK>]̽ǟN>#v̽fQ>`̽U>J!̽ <Ȝ<̽o; ŭ<̽I#<<̽HKW>\>d̽U>J!̽fQ>`̽U>J!̽HKW>\>d̽EM\>}s̽{D=zs̽\=}̽E=WV̽.==\<̽y= =̽[= =̽y= =̽.==\<̽=6<̽9=լ̽l=̽q1S>=̽O>=̽tY>b=̽q1S>=̽V>=̽ׯs>DZ[=̽>t> F=̽m>AL=̽#==̽1=f5=̽S==̽1=f5=̽#==̽=ڵ=̽1=f5=̽ԯ==̽=5=̽=ڵ=̽ԯ==̽1=f5=̽=}>R3̽z>̽&u>0̽J >I̽=}>R3̽$~>녽̽J >I̽J >&̽=}>R3̽c>1=̽F]>@~A=̽b>jP=̽c>1=̽)^>u*=̽F]>@~A=̽3Z>,=̽SW>"JB=̽F]>@~A=̽3Z>,=̽F]>@~A=̽)^>u*=̽y=̽tf=ݘ̽y=ꑽ̽y=ꑽ̽a =E̽y=̽י==̽==̽c=m=̽==̽י==̽z"=㠂=̽==̽ ==̽)=7ߎ=̽==̽z"=㠂=̽ ==̽-K=̽E=}V}̽h=A̽E=}V}̽=B;̽h=A̽"̽z>̽[>7媽̽>=̽J >J=̽J >&=̽&Y|>v=̽y> =̽>=̽J >&=̽&Y|>v=̽>=̽^>̽̽cX>̽̽W>%ý̽qV>D鶽̽W>%ý̽u R>̽qV>D鶽̽l]>T̽W>%ý̽^>̽̽W>%ý̽l]>T̽jZ=2=̽_A=.=̽+"===̽_A=.=̽jZ=2=̽_==̽za>P̽<[>-̽[>-2̽jU> ̽<[>-̽цU>C8̽[>-2̽<[>-̽jU> ̽v>1(=̽E'u>V=̽Hq>=̽v>1(=̽}>=̽E'u>V=̽p}>)ּ̽c~>D̽ux>̽ux>̽c~>D̽ z>(̽Hq>=̽m>=̽o>5=̽m>=̽Hq>=̽~/p>~ =̽m>=̽j>G=̽g>v =̽m>=̽~/p>~ =̽j>G=̽S%$><̽&>:<̽ >D<̽8$>Y*̽^&>_̽1_+>~e̽a>+½̽I>v\̽Z>̽b,>]y̽'*>HL;̽:1>PD%;̽b,>]y̽1_+>~e̽^&>_̽'*>HL;̽b,>]y̽^&>_̽>=̽<>}=̽->q=̽<`=̽=P̽ g=ː̽ g=ː̽=Ǫ̽=v̽=Ǫ̽ g=ː̽=P̽+>V;<̽{/>^<̽b1>Q*<̽+>V;<̽'*>HL;̽&>:<̽=P̽I=t̽=Ǫ̽I=t̽=J̽=Ǫ̽=~̽=J̽I=t̽O>x;̽P>1<̽ >D<̽P>1<̽T> <̽ >D<̽y>v<̽w+>X2<̽ >E <̽#>:̽ >'̽d_ >V{̽ >'̽#>:̽3 >۔̽1_+>~e̽ *>v̽8$>Y*̽->C<̽v'><̽(><̽O>x;̽5 >;̽P>1<̽<>}=̽,>=̽->q=̽! > =̽<>}=̽>=̽0>l=̽o.>=U=̽*>m=̽T,>B=̽o.>=U=̽0|3>#F=̽>(̽Ls>̽ >'̽3 >۔̽>(̽ >'̽>N̽#>:̽d_ >V{̽'>_=̽ > )=̽Y>p=̽>`Pں̽P >B̽H;> Z̽P >B̽9>V̽H;> Z̽nm=!5̽a=gc̽<`=̽ g=ː̽nm=!5̽<`=̽.>q̽81>̽g5>T̽81>̽"u6>;x̽g5>T̽Q=d̽d=vO̽=._̽mZ=7̽$Ԉ=%-̽@=̽S(">=̽F>v=̽#>?=̽V#>)l;̽O>x;̽ >D<̽xj6>X%̽h6>8̽601>D̡̽V>xO=̽O>JmM=̽>e=̽,Q">?@=̽Q>`2=̽V>xO=̽ U>F=̽S(">=̽;%>=̽[f>Y/<̽>+<̽[=2<̽7>p=̽[f>Y/<̽[=2<̽m=D<̽O=<̽.r=k<̽&7>mt_;̽xj6>X%̽:1>PD%;̽5>Dz;̽&7>mt_;̽:1>PD%;̽xj6>X%̽&7>mt_;̽l;>gn9̽->*<̽->C<̽Q2><̽{/>^<̽->*<̽Q2><̽->C<̽->*<̽v'><̽x>=̽>=̽ >=̽m2>}=̽>=̽{^>td=̽7>p=̽>=̽1o >~_=̽>2̽ >̽s >)̽8>̽>̽V>he̽k>̽8>̽V>he̽>̽8>̽> ̽O>~<̽,iJ>`<̽P><̽J= <̽I2<#<̽{=7_<̽a>U=̽J>[Q=̽՚>Ԁ=̽>Mۧ=̽J>[Q=̽! > =̽˛=KH̽=._̽d=vO̽mZ=7̽˛=KH̽d=vO̽->C<̽v1>-<̽Q2><̽v1>-<̽X7>'<̽Q2><̽7;<_c̽v<4̽x'T:<̽Q>8<̽V>hF<̽g>({̽ >f̽>q̽ >*׸=̽<>}=̽! > =̽=̽mZ=7̽@=̽=B̽mZ=7̽d=vO̽mZ=7̽=B̽$Ԉ=%-̽5z=.½̽*q=̽̽c=Ľ̽5z=.½̽u =̽=Su̽6>w5=̽ .1>y.=̽`8>+P4=̽ .1>y.=̽0|3>#F=̽`8>+P4=̽ ><̽7>p=̽1o >~_=̽7>p=̽ ><̽[f>Y/<̽׾=7̽&=?&̽$Ԉ=%-̽[,>=̽->C<̽(><̽4v ><̽\>c<̽>@N1<̽p >=̽x>=̽n% >I2=̽x>=̽p >=̽>=̽Q=d̽ž=-TX̽d=vO̽0=L̽ž=-TX̽=`̽~\=<̽>:<̽Ʈ ><̽>:<̽><̽y>v<̽R>\ ;<̽v>n<̽<>d<̽0=L̽4p=@̽׾=7̽Z2=̽<&=̽̽5=̽t'=̽Z2=̽5=̽E>ɪ<̽9C><̽,iJ>`<̽y>v<̽Y>۸<̽>:<̽Y>۸<̽y>v<̽N><̽n>=̽>:<̽Y>۸<̽S%$><̽ )><̽&>:<̽ )><̽S%$><̽v'><̽y>v<̽>̕<̽w+>X2<̽T> <̽>̕<̽><̽>̕<̽y>v<̽><̽P==̽ck==̽==̽=C=̽ck==̽L=I=̽ck==̽=C=̽==̽c>\E=̽ >F-=̽ >3==̽un=D׼̽= l̽~=P̽9([=~M̽Y[=5̽4p=@̽'>$5=̽c>\E=̽O>JmM=̽>̽>ƣ̽3 >۔̽#>:̽>̽3 >۔̽mZ=7̽=K6̽˛=KH̽=K6̽mZ=7̽=̽q?><̽E>ɪ<̽ D>)Xt<̽+<>h<̽q?><̽ D>)Xt<̽=\̽Ї=&̽=8^̽l=!=̽I==̽L=I=̽T<<[;̽I2<#<̽bxO=̽@>[=̽,Q">?@=̽@>[=̽V>xO=̽Y>p=̽b>==̽V>xO=̽Q>`2=̽V>xO=̽b>==̽O>JmM=̽w>h+=̽b>==̽Q>`2=̽T> <̽W>qo<̽ >D<̽S%$><̽W>qo<̽Ʈ ><̽W>qo<̽S%$><̽ >D<̽T,>B=̽ '>A=̽&>lU=̽ '>A=̽,Q">?@=̽&>lU=̽s >)̽` >Q̽>b#̽>G̽1>Z~̽> ̽ф=̽3=V* ̽a=$;̽Q>J̽>d̽>1P̽:><\<̽T> <̽P>1<̽:><\<̽N>>3<̽w+>X2<̽Y#=z(̽3=z5̽'=E̽VC=B̽3=z5̽zC=i#̽3=z5̽VC=B̽'=E̽3=z5̽Y#=z(̽zC=i#̽p@> ̽ y;>Ż̽C>>b̽xj6>X%̽ y;>Ż̽h6>8̽=̽ >d=̽7>=̽ >d=̽a>U=̽7>=̽>Fe=̽_C>ri=̽{^>td=̽_C>ri=̽>Fe=̽->q=̽u<>x=̽5>=̽O+9>=̽5>=̽u<>x=̽M8>OŹ=̽=$!̽z=3u̽=a̽z=3u̽=̽=a̽7q5>yf<̽Q7>iC<̽b1>Q*<̽Q7>iC<̽5>Dz;̽b1>Q*<̽Q7>iC<̽7q5>yf<̽+<>h<̽3'>`=̽ >=̽>=̽m2>}=̽3'>`=̽>=̽!D> ڋ=̽I>)ˍ=̽'H>=̽I>)ˍ=̽,M>Qi=̽'H>=̽I>)ˍ=̽!D> ڋ=̽ G>=̽>̽>H;̽V>he̽<=Y<̽/%=]K=̽/=¹=̽= >zU̽L >0J>̽>1P̽L >0J>̽= >zU̽>D̽7<뽈;̽I2<#<̽J= <̽I2<#<̽7<뽈;̽bkY=̽R+7>Wl=̽9>^Q=̽0|3>#F=̽ 4>kY=̽9>^Q=̽a=K<̽y= =̽=6<̽y= =̽a=K<̽k==̽;s>=̽*>zޞ=̽n% >I2=̽*>zޞ=̽;s>=̽^>Ì=̽XW>qټ̽a| >̽>̽J >&̽>ta̽=}>R3̽z>̽>ta̽[>7媽̽>ta̽z>̽=}>R3̽W=Om̽5=c/t̽=`̽5=c/t̽W=Om̽-=̽==̽=>^=̽^>Ì=̽Ê=l{̽B=@̽=5̽=v̽Ê=l{̽=5̽fb>ɩ̽Ki>lE̽c>B̽ۯ=̽r=̽̽=̽̽r=̽̽ۯ=̽ d=J̽w=*̽=\̽=:̽=\̽w=*̽_=0̽E=}V}̽w=*̽=:̽+<>h<̽=;>\';̽Q7>iC<̽=;>\';̽5>Dz;̽Q7>iC<̽o>=̽Cwj>R=̽|k>ɕ=̽Cwj>R=̽e>_ً=̽|k>ɕ=̽2<̽<tA;̽b} =̽X7>'<̽6>w5=̽1=<>0"=̽[;>} =̽6>w5=̽X7>'<̽[;>} =̽Fa>> <̽ >k̽O $>^]̽&>Z>t̽w >W%=̽ny>Z=̽1o >~_=̽w >W%=̽L>4=̽ >3==̽1>0̽\/>̽Ɇ4>^̽\/>̽Zd0>̽Ɇ4>^̽7><̽ۻ>@o<̽J >k<̽)e> <̽N>>3<̽2>؅;̽)e> <̽>@N1<̽\>c<̽N>>3<̽)e> <̽\>c<̽Ф=0V<̽= <̽~.=&0<̽= <̽Z=CՍ;̽~.=&0<̽uy>l=̽y>P=̽ׯs>DZ[=̽=4t̽Q=d̽=._̽գ=[*|̽=4t̽=._̽Q=d̽=4t̽=W̽Y>p=̽6:>ݡ~=̽'>_=̽6:>ݡ~=̽m2>}=̽'>_=̽ψ=^̽cN=UO̽o,=H̽%R>=̽|FT>=̽\N>p=̽T>==̽|FT>=̽ZZ>5=̽|FT>=̽T>==̽\N>p=̽X>d.=̽]=45=̽>rF=̽L>4=̽X>d.=̽>rF=̽]=45=̽X>d.=̽q=t=̽#= ̽tƠ=̽=o̽fb>4=̽d h>N=̽kh>=̽^>;̽C>;O̽=;̽[F>y̽ D><*̽?>X̽{A>3̽ D><*̽ȜH>f̽ D><*̽{A>3̽?>X̽ D><*̽[F>y̽ȜH>f̽.+=&ꊽ̽]A=6:̽(y=[̽=,̽.+=&ꊽ̽(y=[̽,>Y@̽1>0̽x43>G̽J.>KW̽,>Y@̽x43>G̽?>5a<̽E>ɪ<̽q?><̽X0>Q<̽1_+>~e̽601>D̡̽rL><̽J> ̄<̽E>ɪ<̽,iJ>`<̽rL><̽E>ɪ<̽=H"̽8= ̽*=s̽̈́=E6̽=H"̽*=s̽b?}>|߻̽Ш~>29̽J > F̽Ш~>29̽b?}>|߻̽bx>׺̽0=L̽eqp=V̽4p=@̽eqp=V̽0=L̽=`̽Do>Ǎ̽a̽v>1 ̽a̽Au>+Hw̽v>1 ̽a̽Do>Ǎ̽3Fk>tx̽I2<#<̽<ڨ5<̽@<؁<̽<ڨ5<̽s<-p<̽@<؁<̽<ڨ5<̽I2<#<̽T<<[;̽Y=Gs̽4(`=G"̽zC=i#̽4(`=G"̽Y[=5̽zC=i#̽4(`=G"̽Y=Gs̽7p=̽tp=AY-̽&=?&̽׾=7̽&=?&̽tp=AY-̽7p=̽j >l=̽>.s=̽>T=̽ >=̽j >l=̽>T=̽>.s=̽j >l=̽H>it=̽ 5=nt̽=5̽=mn̽ 5=nt̽ψ=^̽ή=}Xn̽ =cf̽=mn̽$=op̽=,[̽ =cf̽$=op̽.=^̽=o̽N=YȢ̽%w=FMû̽C>;O̽H;> Z̽V>=̽Y>z=̽,^>=̽Y>z=̽^>ҭ=̽,^>=̽&>Z>t̽^)>3a̽->jn̽^)>3a̽J.>KW̽->jn̽՚>Ԁ=̽]==̽UI>=̽]==̽==̽UI>=̽2<̽Q彃=̽X>i~=̽S>h=̽Hc>=̽^>ҭ=̽fb>4=̽kh>=̽Hc>=̽fb>4=̽ˏ=*j̽l=U̽Jg=8I̽l=U̽ӸR=ջ̽Jg=8I̽]4`>gܑ̽&e> \̽`g>i㩼̽I!k>Cw̽&e> \̽Sh>= ̽&e> \̽I!k>Cw̽`g>i㩼̽o;k=̽:̽>e=̽9>Z=̽H>it=̽9>Z=̽0 >W=̽H>it=̽Pa>s̽~w`>c.^̽Lf>a$h̽'*>HL;̽8@.>;̽:1>PD%;̽b1>Q*<̽8@.>;̽+>V;<̽8@.>;̽b1>Q*<̽:1>PD%;̽8@.>;̽'*>HL;̽+>V;<̽fb>4=̽``>\=̽d h>N=̽``>\=̽bd>=̽d h>N=̽Bs>b=̽o>=̽Rzq>1?=̽Gw>n=̽Bs>b=̽Rzq>1?=̽DA>];̽h#D>nz̽C>>b̽]>`̽W>[̽6QW>m̽Uy]>ԅ:̽]>`̽6QW>m̽W>[̽]>`̽^>+0̽o<'̽v<4̽[ì\=̽'x<̽ <Ȝ<̽<>(vt=̽q;>c=̽R+7>Wl=̽9>^Q=̽q;>c=̽?>I^=̽q;>c=̽9>^Q=̽R+7>Wl=̽q;>c=̽<>(vt=̽?>I^=̽e!>D̽O $>^]̽˄>T̽O $>^]̽e!>D̽%(>N̽un=D׼̽I=M̽(={̽N=X̽VL=t̽ˏ=*j̽lz=Qk=̽z=Ђ=̽5c=x=̽z=Ђ=̽lz=Qk=̽j=<`u=̽L=o͈9̽/1=):̽0C=,{;̽/1=):̽L=o͈9̽e8='̽v<4̽ʐ<κ̽[ì̽8= ̽[>k;̽`>6;̽Uy]>ԅ:̽e>o;̽`>6;̽)a>3<̽`>6;̽[>k;̽)a>3<̽>b#̽>q9̽s >)̽>+<̽ >+<̽ >E <̽ >+<̽N><̽ >E <̽ >+<̽>+<̽[f>Y/<̽5=|5߼̽8= ̽ =8>̽os=Ҽ̽5=|5߼̽ =8>̽O=<̽E=:<̽=R<̽ =<̽E=:<̽=6<̽ui;>i8̽;>tE̽=B>3z̽;>tE̽p@> ̽=B>3z̽N><̽=>w=̽Y>۸<̽k:>G<̽7q5>yf<̽Q2><̽IJ>l̽y>Z{̽ y>c̽D_=+ .̽W=pЁ̽Y=^`̽"==̽~=h=̽q=t=̽a=̽E=WV̽\=}̽V=X̽ۯ=̽=̽̽l;>gn9̽ y;>Ż̽xj6>X%̽l;>gn9̽C>>b̽ y;>Ż̽>2̽L >0J>̽>D̽>2̽s >)̽L >0J>̽&>lU=̽,Q">?@=̽@>[=̽ =cf̽ 5=nt̽=mn̽ψ=^̽ 5=nt̽ =cf̽fb>ɩ̽`>6;̽e>o;̽fb>ɩ̽Uy]>ԅ:̽`>6;̽VL=t̽3=V* ̽ˏ=*j̽a=$;̽3=V* ̽VL=t̽#>:̽>N̽f>d(̽>̽#>:̽f>d(̽=>^=̽*>zޞ=̽^>Ì=̽>Mۧ=̽*>zޞ=̽=>^=̽&>lU=̽o.>=U=̽T,>B=̽&>lU=̽*>m=̽o.>=U=̽/1=):̽H/=;̽0C=,{;̽a=K<̽/=¹=̽k==̽[=2<̽"==̽7>p=̽l;>gn9̽DA>];̽C>>b̽E=:<̽m=D<̽=6<̽O=<̽m=D<̽E=:<̽Ђw=B̽Up=0m̽5=c/t̽\=}̽Up=0m̽Ђw=B̽os=Ҽ̽ =8>̽ռ=ܼ̽>N̽a=gc̽nm=!5̽"==̽>=̽7>p=̽q=t=̽>=̽"==̽)y>j<̽7><̽8|><̽)y>j<̽ۻ>@o<̽7><̽e!>D̽&>'4̽%(>N̽ =̽̽V=X̽=̽̽>q9̽L >0J>̽s >)̽>1P̽L >0J>̽>q9̽z=Ђ=̽_==̽Zt=&=̽j=<`u=̽_==̽z=Ђ=̽ U>F=̽#>=̽ > )=̽ U>F=̽;%>=̽#>=̽ =cf̽cN=UO̽ψ=^̽=,[̽cN=UO̽ =cf̽@(>a;̽}z>Z`;̽ z>A <̽*>zޞ=̽p >=̽n% >I2=̽>Mۧ=̽p >=̽*>zޞ=̽j >l=̽3'>`=̽H>it=̽ >=̽3'>`=̽j >l=̽o>ao̽a̽3Fk>tx̽o>ao̽Au>+Hw̽a̽}z>Z`;̽Ш~>29̽bx>׺̽J > ŭ<̽7><̽J >k<̽i?>=̽u<>x=̽O+9>=̽u<>x=̽/?>;=̽M8>OŹ=̽Kmf=.̽5z=.½̽c=Ľ̽Kmf=.̽u =̽5z=.½̽>=̽X>d.=̽L>4=̽>=̽q=t=̽X>d.=̽cX>=̽|FT>=̽%R>=̽cX>=̽ZZ>5=̽|FT>=̽Me>z~̽Pa>s̽Lf>a$h̽Me>z~̽`>̽Pa>s̽>rF=̽D=Q3\=̽]>\=̽>rF=̽=7M=̽D=Q3\=̽#L>Ζ=̽I>)ˍ=̽ G>=̽#L>Ζ=̽,M>Qi=̽I>)ˍ=̽x)l;̽&>:<̽'*>HL;̽ >D<̽&>:<̽V#>)l;̽+>V;<̽ )><̽{/>^<̽+>V;<̽&>:<̽ )><̽>(̽>l̽Ls>̽^&>_̽V#>)l;̽'*>HL;̽>|̽>G̽k>̽>G̽!>̽1>Z~̽>=̽>Fe=̽x>=̽>=̽->q=̽>Fe=̽! > =̽p >=̽>Mۧ=̽! > =̽>=̽p >=̽81>̽X0>Q<̽"u6>;x̽^06H;̽5 >;̽O>x;̽:><\<̽5 >;̽N>>3<̽P>1<̽5 >;̽:><\<̽{^>td=̽'>_=̽m2>}=̽>G̽8>̽k>̽> ̽8>̽>G̽:><\<̽>̕<̽T> <̽w+>X2<̽>̕<̽:><\<̽Ê=l{̽=={̽B=@̽ )><̽->*<̽{/>^<̽v'><̽->*<̽ )><̽ >*׸=̽J>[Q=̽a>U=̽! > =̽J>[Q=̽ >*׸=̽8>l~̽C >®[̽1>Z~̽ >d=̽ >*׸=̽a>U=̽>Fe=̽>=̽x>=̽{^>td=̽>=̽>Fe=̽X0>Q<̽h6>8̽"u6>;x̽601>D̡̽h6>8̽X0>Q<̽ڄ=9Q̽$Ԉ=%-̽&=?&̽@=̽$Ԉ=%-̽ڄ=9Q̽=W̽W=Om̽Q=d̽=W̽-=̽W=Om̽=B̽ž=-TX̽0=L̽=B̽d=vO̽ž=-TX̽><̽W>qo<̽T> <̽><̽Ʈ ><̽W>qo<̽׾=7̽=B̽0=L̽$Ԉ=%-̽=B̽׾=7̽w >W%=̽>=̽L>4=̽1o >~_=̽>=̽w >W%=̽'>$5=̽b>==̽w>h+=̽'>$5=̽O>JmM=̽b>==̽ >+<̽ ><̽N><̽[f>Y/<̽ ><̽ >+<̽'>$5=̽ >F-=̽c>\E=̽?>5a<̽9C><̽E>ɪ<̽ʐ<κ̽<tA;̽2<̽w >W%=̽ >F-=̽ny>Z=̽ >3==̽ >F-=̽w >W%=̽eqp=V̽Up=0m̽~\=~<̽rL><̽,iJ>`<̽=K6̽=̽=*̽tp=AY-̽4p=@̽Y[=5̽׾=7̽4p=@̽tp=AY-̽A>3=̽[;>} =̽1=<>0"=̽A>3=̽Fa>> <̽[;>} =̽l=!=̽ck==̽P==̽L=I=̽ck==̽l=!=̽9([=~M̽eqp=V̽~\=.:̽J >S̽}>J̽N>>3<̽\>c<̽w+>X2<̽\>c<̽ >E <̽w+>X2<̽ >E <̽\>c<̽4v ><̽o;>x<̽'(̽c~>D̽v>Be̽J >=̽ۻ>@o<̽}>=̽``>\=̽^>=̽bd>=̽V=X̽믖=1̽tƠ=̽tƠ=̽#= ̽V=X̽r=̽̽ d=J̽S=̽̽=̽̽*q=̽̽5z=.½̽=Su̽=̽̽5z=.½̽,>Y@̽J.>KW̽%(>N̽,>Y@̽+>&̽1>0̽+>&̽,>Y@̽&>'4̽&>'4̽,>Y@̽%(>N̽+>&̽\/>̽1>0̽J > <̽J > F;̽@(>a;̽IW=̽ >3==̽!>?H=̽ >3==̽L>4=̽!>?H=̽0 >W=̽c>\E=̽ >3==̽ߧ >Ln$̽>:̽4D> ̽ *=̽=a̽ =8>̽=a̽ *=̽=$!̽ӂ>=̽=̽>=̽ >d=̽=̽ӂ>=̽<>}=̽ >*׸=̽ӂ>=̽ӂ>=̽ >*׸=̽ >d=̽:&̽J >J̽>ta̽J >J̽[>7媽̽>ta̽Vb=Ts;̽{=U<̽=R<̽{=U<̽Vb=Ts;̽Z=*<̽{=U<̽O=<̽=R<̽H'>̭̽M#>s̽!>Ϯ̽H><̽eA>:<̽ D>)Xt<̽ >k̽˄>T̽O $>^]̽_>e̽˄>T̽ >k̽"|>̽}>;̽J > ŭ̽"|>̽p}>)ּ̽)w>~\ʼ̽p}>)ּ̽"|>̽J > ŭ̽>q9̽Q>J̽>1P̽g> zI=̽(h>b=̽m>AL=̽a<'̽g<6%̽S̽[>>̽EM\>}s̽`>̽EM\>}s̽Pa>s̽` >Q̽s >)̽ >̽a| >̽` >Q̽ >̽Ep=m̽M=d'w̽u=O̽os=Ҽ̽Ep=m̽u=O̽1װ=*E=̽α=d)\=̽_;=UOK=̽α=d)\=̽1װ=*E=̽==N=̽)~=/=̽1װ=*E=̽_;=UOK=̽ 4>kY=̽0>l=̽R+7>Wl=̽0>l=̽ 4>kY=̽o.>=U=̽ 4>kY=̽0|3>#F=̽o.>=U=̽^>;̽>`Pں̽C>;O̽= >#;̽>`Pں̽^>;̽>@N1<̽= >#;̽^>;̽Cwj>R=̽ޓg>|=̽e>_ً=̽ޓg>|=̽Cwj>R=̽nm>$`z=̽Cwj>R=̽o>=̽nm>$`z=̽"u6>;x̽;>tE̽ui;>i8̽;>tE̽"u6>;x̽h6>8̽;>tE̽ y;>Ż̽p@> ̽;>tE̽h6>8̽ y;>Ż̽<&=̽̽Z2=̽6?=̽̽Z2=̽KN=s̽6?=̽̽.<=W̽KN=s̽Z2=̽.<=W̽Z2=̽t'=̽I#<<̽o; ŭ<̽o;k<̽h=A̽%w=FMû̽-K=̽%w=FMû̽h=A̽C>;O̽>6\=̽uy>l=̽a>s=̽uy>l=̽>6\=̽y>P=̽9̽@c<+P̽\Tt> F=̽v>1(=̽o>5=̽y>P=̽BEz>9=̽>t> F=̽BEz>9=̽v>1(=̽>t> F=̽6=̽a =E̽y=ꑽ̽^>j*<̽Z>d<̽ͼY>-<̽Z>d<̽^>j*<̽^>V<̽T>L<̽ͼY>-<̽Z>d<̽π=[d :̽=;̽N=X̽=;̽.=+<̽ ?=d8<̽π=[d :̽*w=G;̽=;̽*w=G;̽.=+<̽=;̽eA>:<̽+<>h<̽ D>)Xt<̽=;>\';̽+<>h<̽eA>:<̽_>e̽Q>J̽˄>T̽Q>J̽_>e̽>d̽_>e̽>w̽>d̽>:̽˄>T̽Q>J̽>:̽e!>D̽˄>T̽Ђw=B̽5=c/t̽-=̽Ђw=B̽6=̽y=ꑽ̽Ђw=B̽-=̽6=̽q1S>=̽#L>Ζ=̽O>=̽#L>Ζ=̽q1S>=̽,M>Qi=̽.+=&ꊽ̽=mn̽=5̽=mn̽.+=&ꊽ̽=,̽]A=6:̽.+=&ꊽ̽=5̽=&:̽=;̽Vb=Ts;̽=;̽=&:̽S=.;̽=;̽Z=*<̽Vb=Ts;̽u=O̽5=|5߼̽os=Ҽ̽v<4̽o<'̽xb#̽cE>̽4D> ̽` >Q̽cE>̽>b#̽^)>3a̽&>Z>t̽O $>^]̽%(>N̽^)>3a̽O $>^]̽^)>3a̽%(>N̽J.>KW̽$+>&̽H'>̭̽.>q̽M#>s̽H'>̭̽$+>&̽&>ɹ̽M#>s̽$+>&̽$+>&̽.>q̽2>/ۼ̽Zd0>̽$+>&̽2>/ۼ̽ *>v̽.>q̽H'>̭̽81>̽.>q̽ *>v̽1_+>~e̽X0>Q<̽ *>v̽81>̽ *>v̽X0>Q<̽= ;̽=R<̽Z=*<̽= ;̽S=.;̽/=%fd;̽=;̽S=.;̽= ;̽Z=*<̽=;̽= ;̽DA>];̽H> ;̽h#D>nz̽= <̽/=%fd;̽Z=CՍ;̽= ;̽/=%fd;̽= <̽= <̽Ф=0V<̽=R<̽=R<̽= ;̽= <̽~=OB̽=8^̽Ї=&̽IzA=̽T,>B=̽b)>.=̽b)>.=̽}"> !=̽,Q">?@=̽,Q">?@=̽ '>A=̽b)>.=̽]==̽՚>Ԁ=̽==̽P==̽]==̽==̽]==̽P==̽==̽k:I<$}=̽ʹ<7=̽<Ĕ=̽m<̽J > ŭ<̽}>i<̽7><̽J > ŭ<̽8|><̽Ø>^=̽Ʈ ><̽>:<̽S%$><̽Ʈ ><̽v'><̽ =<̽C=<̽r=<̽6>|r=̽0> =̽w>h+=̽Q>`2=̽,Q">?@=̽}"> !=̽Q>`2=̽6>|r=̽w>h+=̽6>|r=̽Q>`2=̽}"> !=̽J > ŭ<̽J >>x<̽}>i<̽>׺̽P >B̽>`Pں̽P >B̽C >®[̽9>V̽= >#;̽>׺̽>`Pں̽>׺̽= >#;̽2>؅;̽)e> <̽= >#;̽>@N1<̽2>؅;̽= >#;̽)e> <̽M=d'w̽Ї=&̽_=0̽Ї=&̽=\̽_=0̽=8<̽=R<̽Ф=0V<̽`=|E=̽[Nr=Z9=̽h^=X,=̽[Nr=Z9=̽p="=̽h^=X,=̽9>=+=̽p="=̽[Nr=Z9=̽3=<̽.r=k<̽O=<̽3=<̽Z=*<̽=R<̽{=U<̽Z=*<̽3=<̽O=<̽{=U<̽3=<̽=Y̽VL=t֚̽=ٝ̽=Y̽a=$;̽VL=t̽J> ̄<̽Q>8<̽NP>'T:<̽J> ̄<̽rL><̽Q>8<̽O>~<̽Q>8<̽rL><̽(=̀̽={r̽={=7̽601>D̡̽1_+>~e̽b,>]y̽:1>PD%;̽601>D̡̽b,>]y̽601>D̡̽:1>PD%;̽xj6>X%̽P><̽T>L<̽O>~<̽5"E>̽K>̽ K> V̽rG>=̽K>̽5"E>̽ K> V̽D>5=̽5"E>̽5"E>̽D>5=̽S̽s=ޡ׼̽dm=̍̽֠=Z׼̽bZ̽>|̽!>Ϯ̽Z>Z̽!>̽>|̽>|̽!>̽>G̽=Z&̽dm=̍̽Ԫ=<۩̽Ԫ=<۩̽(=̀̽=Z&̽~Z=̽>e=̽O>JmM=̽c>\E=̽9>Z=̽O>JmM=̽9>Z=̽c>\E=̽0 >W=̽ s<P<̽{̽>̽a| >̽>2̽>=)̽ >̽՚>Ԁ=̽=>^=̽==̽=>^=̽՚>Ԁ=̽J>[Q=̽>Mۧ=̽=>^=̽J>[Q=̽Y>tk̽^>+0̽]4`>gܑ̽^>+0̽Y>tk̽W>[̽Z0X==̽ Nh=W=̽_L=z=̽ Nh=W=̽Z0X==̽*q==̽I==̽l=!=̽=C=̽= =̽l=!=̽P==̽l=!=̽= =̽=C=̽> ̽>׺̽>̽P >B̽>׺̽> ̽1>Z~̽C >®[̽> ̽P >B̽> ̽C >®[̽2>؅;̽>̽>׺̽>H;̽>̽2>؅;̽N>>3<̽5 >;̽2>؅;̽2>؅;̽5 >;̽>H;̽s=ޡ׼̽Ԫ=<۩̽dm=̍̽~p>A̽1k>T;̽n>Y'̽Om>HT̽ch>hYO̽1k>T;̽~p>A̽Om>HT̽1k>T;̽{D=zs̽9(=d̽v/=(i̽{D=zs̽E=WV̽9(=d̽=={̽]A=6:̽B=@̽=H"̽̈́=E6̽W=4̽=H"̽ *=̽8= ̽F=\̽=4F̽@|=ٟA̽̈́=E6̽13=./̽=4F̽=4F̽13=./̽@|=ٟA̽Sh>= ̽m>[ ̽I!k>Cw̽m>[ ̽Sh>= ̽Ki>lE̽c>B̽Ki>lE̽Sh>= ̽>e=̽6:>ݡ~=̽Y>p=̽6:>ݡ~=̽>e=̽H>it=̽3'>`=̽m2>}=̽6:>ݡ~=̽3'>`=̽6:>ݡ~=̽H>it=̽A>3=̽&C>D)=̽G> =̽&C>D)=̽A>3=̽1=<>0"=̽G> =̽E>=̽A>3=̽8$>Y*̽>|̽k>̽EM\>}s̽HKW>\>d̽Iu[>7V̽Iu[>7V̽~w`>c.^̽EM\>}s̽~w`>c.^̽Pa>s̽EM\>}s̽ڄ=9Q̽&=?&̽7p=̽]> <̽9C><̽?>5a<̽8>4y̽!=>xt̽p=>_:̽}7>D̽p=>_:̽=>̽}7>D̽8>4y̽p=>_:̽d>i=̽,^>=̽Hc>=̽,^>=̽^>ҭ=̽Hc>=̽P.i>=̽Hc>=̽kh>=̽Hc>=̽P.i>=̽d>i=̽->C<̽[,>=̽v1>-<̽v1>-<̽6>w5=̽X7>'<̽6>w5=̽v1>-<̽+.>=̽[,>=̽+.>=̽v1>-<̽$a>zj=̽(h>b=̽b>jP=̽(h>b=̽$a>zj=̽ޓg>|=̽(h>b=̽g> zI=̽b>jP=̽>̽:=ږ̽NV>Ď̽NV>Ď̽>ƣ̽>̽=ɍ̽:=ږ̽>̽f>d(̽=ɍ̽>̽==(`<̽Ф=0V<̽~.=&0<̽=8<̽==(`<̽= ='<̽=8<̽Ф=0V<̽==(`<̽X>i~=̽2jS>彃=̽X>{=̽,M>Qi=̽q1S>=̽2jS>彃=̽2jS>彃=̽q1S>=̽X>{=̽o>=̽Dt>c=̽nm>$`z=̽Dt>c=̽o>=̽Bs>b=̽Bs>b=̽Gw>n=̽Dt>c=̽Gw>n=̽K}>u=̽Dt>c=̽.<=W̽K-=]̽n9D=ڦ̽K-=]̽.<=W̽t'=̽ޙV=8̽.<=W̽n9D=ڦ̽+"===̽^H==̽(\=gD=̽jZ=2=̽(\=gD=̽`o==̽(\=gD=̽jZ=2=̽+"===̽@|=ٟA̽>=)̽>4@̽13=./̽>=)̽@|=ٟA̽>=)̽>2̽>4@̽l=U̽ˏ=*j̽3=V* ̽O=;=̽w=Ft#=̽Κ==̽Nj=ׇ̽Y=ɬ̽믖=1̽=Su̽Nj=ׇ̽믖=1̽Nj=ׇ̽=Su̽u =̽ny>Z=̽=>w=̽1o >~_=̽ ><̽=>w=̽N><̽ ><̽1o >~_=̽=>w=̽'>$5=̽w>h+=̽0> =̽ny>Z=̽ >F-=̽0> =̽0> =̽ >F-=̽'>$5=̽vn> (̽>(̽3 >۔̽vn> (̽>񼟽̽>(̽`=|E=̽(K=EN=̽*a=%`=̽*a=%`=̽(K=EN=̽$hG=$=i=̽$hG=$=i=̽Y=nt=̽*a=%`=̽= B=̽=:W=̽ac=]G=̽= B=̽Eq<ݎH=̽=:W=̽=:W=̽2=b=̽ac=]G=̽= i=̽Zt=&=̽V=(=̽= i=̽z=Ђ=̽Zt=&=̽= i=̽5c=x=̽z=Ђ=̽+.>=̽ .1>y.=̽6>w5=̽+.>=̽b)>.=̽ .1>y.=̽0|3>#F=̽ .1>y.=̽T,>B=̽b)>.=̽T,>B=̽ .1>y.=̽F>v=̽_C>ri=̽->q=̽_C>ri=̽F>v=̽S(">=̽->q=̽,>=̽F>v=̽ > )=̽'>_=̽ U>F=̽ U>F=̽_C>ri=̽S(">=̽{^>td=̽_C>ri=̽ U>F=̽ U>F=̽'>_=̽{^>td=̽V#>E̽F&>3̽T)>ý̽&>̽%#>' ̽&>ɹ̽ *>v̽n$>y̽8$>Y*̽n$>y̽>|̽8$>Y*̽n$>y̽ *>v̽H'>̭̽>y̽Z>Z̽ f>|ۼ̽Z>Z̽>y̽!>̽(y=[̽=Ԕ̽N=YȢ̽=Ԕ̽(y=[̽]A=6:̽{>6̽>w̽\>̽+.>=̽(>l=̽b)>.=̽(>l=̽}"> !=̽b)>.=̽l>ܼ̽9J >̽8>l~̽@>j̽0E>̽̽i?>̽̽X >1̽>ƣ̽ >f̽=>21̽X >1̽ >f̽qO=o=̽|==̽=C=̽5>̽};>̭̽6>ALý̽=Z~̽ή=}Xn̽g=^x̽=Z~̽=5̽ 5=nt̽=5̽=Z~̽g=^x̽ή=}Xn̽=Z~̽ 5=nt̽O+9>̽̽o;>Ľ̽i?>̽̽o;>Ľ̽O+9>̭̽̽6>ALý̽uj=K̽ф=̽a=$;̽={r̽uj=K̽a=$;̽o;J̽5J<ڠ̽!<̽5J<ڠ̽rZ<̽l.K>=̽N>!=̽l.K>=̽G> =̽N>!=̽N~>,=̽}>=̽v>1(=̽ 'o>wd=̽m>AL=̽(h>b=̽m>AL=̽ 'o>wd=̽ׯs>DZ[=̽nm>$`z=̽ 'o>wd=̽(h>b=̽Xz=$K̽@=Rs̽>̽ >̽Xz=$K̽>̽=}̽=={̽=J̽X=w̽8=Y ̽ =̽8=Y ̽=̽ =̽un=D׼̽ב=ڼ̽= l̽.=-ܼ̽ב=ڼ̽=c̽=ڵ=̽ ==̽r.==̽ ==̽=ڵ=̽#==̽C >®[̽Hi> ̽9>V̽}"> !=̽$>=̽Ø>^=̽(><̽$>=̽(>l=̽$>=̽}"> !=̽(>l=̽b=̽M=d'w̽Ep=m̽s=ޡ׼̽v=aTż̽Ԫ=<۩̽=c̽v=aTż̽.=-ܼ̽Z=;̽Vb=Ts;̽=R<̽Z=;̽^=;̽=:̽^=;̽Z=;̽=R<̽Vb=Ts;̽Z=;̽=:̽H> ;̽( E>Q;̽H><̽( E>Q;̽eA>:<̽H><̽( E>Q;̽H> ;̽DA>];̽c=)ú̽=pº̽8!=Jһ̽c=)ú֚̽=ٝ̽Z=CՍ;̽f=h̽K=ڐ̽ d=J̽K=ڐ̽S=̽̽ d=J̽u2r=T̽ф=̽ j=~̽ф=̽u2r=T̽3=V* ̽b>=̽,>=̽<>}=̽ӂ>=̽b>=̽<>}=̽Dt>c=̽r>/o=̽nm>$`z=̽ׯs>DZ[=̽r>/o=̽uy>l=̽_;=UOK=̽*=.A=̽=J/=̽:=`B=̽*=.A=̽=K]=̽*=.A=̽_;=UOK=̽=K]=̽H/=;̽>=/<̽m`8=N[<̽>=/<̽=b<̽m`8=N[<̽X_U>|<̽P><̽;U>@=̽X_U>|<̽ͼY>-<̽T>L<̽ͼY>-<̽X_U>|<̽;U>@=̽P><̽X_U>|<̽T>L<̽##>.n=̽Y>p=̽ > )=̽ΐ=ّ̽=w ̽a =E̽6=̽ΐ=ّ̽a =E̽9(=d̽R=ሽ̽Qk=[̽=-̽R=ሽ̽F+=Ӑ̽R=ሽ̽=-̽Qk=[̽R=ሽ̽9(=d̽F+=Ӑ̽Ԫ=<۩̽m=̽(=̀̽|W!>̽8$>Y*̽k>̽V>he̽|W!>̽k>̽8$>Y*̽|W!>̽^&>_̽Ʈ ><̽>$>ځ<̽v'><̽>$>ځ<̽Ʈ ><̽Ø>^=̽>$>ځ<̽(><̽v'><̽"X>h<̽V>hF<̽Q>8<̽"X>h<̽Z>d<̽^>V<̽XS<=̽QEA̽a=gc̽>N̽d_ >V{̽>EA̽>N̽ۃ=骽̽N=YȢ̽=Ԕ̽={=7̽@=7̽(=̀̽@{=@S̽-K=̽l=X̽-K=̽@{=@S̽E=}V}̽_=0̽@{=@S̽l=X̽V>hF<̽[>]k;̽)a>3<̽[>]V<̽[>]3<̽[>k;̽Iu[>7V̽KY>νB̽v_>=D̽KY>νB̽<[>-̽v_>=D̽KY>νB̽Iu[>7V̽V>N̽ܤv><̽n@z>^<̽u>=<̽n@z>^<̽ܤv><̽}>i<̽n@z>^<̽ z>A <̽u>=<̽=>21̽F>I̽vn> (̽>q2̽>:̽Q>J̽>q2̽>q9̽>b#̽>q9̽>q2̽Q>J̽n9D=ڦ̽J=w̽(Y^=g̽g>:̽e>o;̽5l>2;̽g>:̽Ki>lE̽fb>ɩ̽Ki>lE̽g>:̽5l>2;̽e>o;̽g>:̽fb>ɩ̽=̽e=4̽֠=Z׼̽8>=̽3>W=̽n4>6ԍ=̽w\=*Q̽A=]9H̽ӸR=ջ̽A=]9H̽w\=*Q̽fM=B̽+> ̽Zd0>̽\/>̽=C=̽=ؗ=̽)=7ߎ=̽=ؗ=̽==̽)=7ߎ=̽=ؗ=̽=C=̽= =̽>:̽V#>)l;̽^&>_̽V#>)l;̽>:̽O>x;̽ή=}Xn̽Ϫ=bN̽F=\̽Ϫ=bN̽=4F̽F=\̽ 'o>wd=̽r>/o=̽ׯs>DZ[=̽ 'o>wd=̽nm>$`z=̽r>/o=̽=Y̽c=)ú̽8!=Jһ̽=Y֚̽=ٝ̽c=)ú̽[>]h<̽^>V<̽V>hF<̽"X>h<̽[>]I̽>񼟽̽vn> (̽b=̽Ї=&̽M=d'w̽QC8̽KY>νB̽V>N̽цU>C8̽<[>-̽KY>νB̽[,>=̽(>l=̽+.>=̽[,>=̽(><̽(>l=̽K=ڐ̽3=̽̽S=̽̽c=m=̽=ؗ=̽= =̽c=m=̽==̽=ؗ=̽@{=@S̽w=*̽E=}V}̽_=0̽w=*̽@{=@S̽>:̽>H;̽O>x;̽V>he̽>H;̽>:̽4D> ̽>q2̽>b#̽4D> ̽>:̽>q2̽%#>' ̽M#>s̽&>ɹ̽>̽M#>s̽%#>' ̽=̽ב=ڼ̽.=-ܼ̽=̽= l̽ב=ڼ̽l=U̽3=V* ̽u2r=T̽ф=̽I=M̽ j=~̽ф=̽(={̽I=M̽˛=KH̽X=BK̽=._̽z=3u̽e=4̽=̽v=aTż̽m=̽Ԫ=<۩̽v=aTż̽=c̽m=̽3>W=̽]%/>z=̽n4>6ԍ=̽+>&̽+> ̽\/>̽+>&̽&>̽+> ̽a>+½̽V#>E̽1 >̽̽o;>Ľ̽@>j̽i?>̽̽=Y̽8!=Jһ̽={=7̽/=%fd;̽c=)ú̽Z=CՍ;̽/=%fd;̽=pº̽c=)ú̽dm=̍̽=̽c=̽=Z&̽=̽dm=̍̽l>ܼ̽>y̽ f>|ۼ̽8>l~̽>y̽l>ܼ̽>y̽1>Z~̽!>̽8>l~̽1>Z~̽>y̽=R<̽ =<̽3=<̽=R<̽=8<̽ =<̽={r̽(=̀̽m=̽s=ޡ׼̽֠=Z׼̽e=4̽ >̽>=)̽Xz=$K̽};>̽o;>Ḽ̌̽6>ALý̽>:̽|W!>̽V>he̽^&>_̽|W!>̽>:̽!>Ϯ̽n$>y̽H'>̭̽!>Ϯ̽>|̽n$>y̽(={̽ב=ڼ̽un=D׼̽(={̽=c̽ב=ڼ̽ф=̽uj=K̽(={̽>l̽K%=`I̽W=pЁ̽Vs=i̽.=-ܼ̽v=aTż̽s=ޡ׼̽Vs=i̽v=aTż̽\>̽F>I̽{>6̽F>I̽=>21̽{>6̽]<̽o; ŭ̽o;>x̽o&>̽̽1 >̽̽V#>E̽T)>ý̽o&>̽̽V#>E̽Y>p=̽##>.n=̽@>[=̽&>lU=̽@>[=̽##>.n=̽&>lU=̽##>.n=̽*>m=̽=._̽X=BK̽F=ԩi̽5J<ڠ̽o;&̽6[*<̽o;&̽5J<ڠ̽o;J̽rZ&̽+> ̽&>ɹ̽&>̽&>ɹ̽+> ̽$+>&̽Zd0>̽+> ̽ώ=̽̽=Su̽믖=1̽C=<̽= ='<̽==(`<֚̽=ٝ̽= ;̽Z=CՍ;̽VL=t̽= ;֚̽=ٝ̽=;̽= ;̽N=X̽VL=t̽N=X̽= ;̽#= ̽ۯ=̽V=X̽F&>3̽V#>E̽Z>̽V#>E̽a>+½̽Z>̽0>l=̽3>W=̽R+7>Wl=̽3>W=̽8>=̽R+7>Wl=̽=}̽]A=6:̽=={̽]A=6:̽=}̽=Ԕ̽E>=̽,iJ>`<̽9C><̽Y=^`̽-K=̽D_=+ .̽%w=FMû̽D_=+ .̽-K=̽D_=+ .̽%w=FMû̽H;> Z̽ =<̽=*<̽.r=k<̽<=Y<̽.r=k<̽=*<̽/%=]K=̽<=Y<̽=*<̽8>[̽};>̽5>̽};>̽8>[̽b?>!j̽b?>!j̽#A>&̽};>̽=w ̽8=Y ̽X=w̽=w ̽ΐ=ّ̽8=Y ̽ӂ>=̽>=̽b>=̽b>=̽>=̽>=̽}>=̽J >[!=̽J >=̽}>=̽N~>,=̽J >[!=̽=>w=̽ny>Z=̽n>=̽ny>Z=̽0> =̽n>=̽=>w=̽n>=̽Y>۸<̽˛=KH̽=K6̽X=BK̽ψ=^̽Ϫ=bN̽ή=}Xn̽o,=H̽Ϫ=bN̽ψ=^̽̈́=E6̽=4F̽Ϫ=bN̽Ϫ=bN̽o,=H̽W=4̽Ϫ=bN̽W=4̽̈́=E6̽ j=~̽I=M̽b=ͼ̽I=M̽~=P̽b=ͼ̽~=P̽I=M̽un=D׼̽9J >̽l>ܼ̽XW>qټ̽cE>̽l>ܼ̽ f>|ۼ̽a| >̽XW>qټ̽l>ܼ̽l>ܼ̽cE>̽` >Q̽` >Q̽a| >̽l>ܼ̽ߧ >Ln$̽&>'4̽e!>D̽>:̽ߧ >Ln$̽e!>D̽O>~<̽T>L<̽Q>8<̽T>L<̽"X>h<̽Q>8<̽T>L<̽Z>d<̽"X>h<̽=8<̽= ='<̽=*<̽ =<̽=8<̽=*<̽=̽@=̽=̽9>=+=̽=C=̽)=2=̽9>=+=̽8=.=̽Z===̽8=.=̽9>=+=̽)=2=̽(7I>)=̽K>=̽\N>p=̽K>=̽(7I>)=̽0E>=̽IJ>l̽J >S̽$~>녽̽J >S̽IJ>l̽J >k̽y>Z{̽IJ>l̽$~>녽̽҈<>f̽]57>̽=>̽҈<>f̽=>̽S̽>EA̽[>̽̽a=gc̽#A>&̽b?>!j̽UJF>̽ F>d̽#A>&̽UJF>̽R/^>9=̽X>{=̽~^>Я=̽X>{=̽R/^>9=̽X>i~=̽X>{=̽tY>b=̽~^>Я=̽os=Ҽ̽ռ=ܼ̽Ep=m̽b=̽Ep=m̽c=̽( E>Q;̽DA>];̽eA>:<̽=ɍ̽nm=!5̽ g=ː̽=ɍ̽f>d(̽nm=!5̽f>d(̽>N̽nm=!5̽<>9̽>l̽>񼟽̽>񼟽̽>l̽>(̽>l̽Z>̽I>v\̽<>9̽Z>̽>l̽Ls>̽>l̽I>v\̽b F̽Ш~>29̽J > F;̽Ш~>29̽@(>a;̽J > F;̽Ш~>29̽}z>Z`;̽@(>a;̽A>3=̽E>=̽Fa>> <̽E>=̽9C><̽Fa>> <̽``>\=̽Q!\>/=̽ZZ>5=̽Q!\>/=̽``>\=̽fb>4=̽ZZ>5=̽^>=̽``>\=̽=*<̽H_=2<̽/%=]K=̽= ='<̽H_=2<̽=*<̽0> =̽6>|r=̽n>=̽7<뽈;̽r=;̽y= R̽J= <̽r=;̽7<뽈;̽X=BK̽=K6̽=*̽=Z ̽=*̽=̽=̽=̽=Z ̽==̽=%=̽;b=Β=̽=%=̽==̽=x=̽=;>\';̽&7>mt_;̽5>Dz;̽l;>gn9̽&7>mt_;̽=;>\';̽=;>\';̽DA>];̽l;>gn9̽DA>];̽=;>\';̽eA>:<̽={r̽m=̽=c̽= p<̽5=<̽[=2<̽[=2<̽5=<̽"==̽~=h=̽"==̽5=<̽f<'8s=̽:<f=̽Yz<R=̽c ̽8>l~̽9J >̽8>l~̽Hi> ̽C >®[̽9([=~M̽~\=۔̽X >1̽vn> (̽X >1̽3 >۔̽>ƣ̽X >1̽=>21̽vn> (̽p=5̽tƠ=̽Y=ɬ̽Y=ɬ̽s=/ʦ̽=w ̽=w ̽p=5̽Y=ɬ̽7>̽̽[>̽̽>EA̽y= R̽r=;̽1=5̽z=3u̽=$!̽F=J)̽,iJ>`<̽l.K>=̽P><̽l.K>=̽,iJ>`<̽E>=̽l.K>=̽E>=̽G> =̽@=Rs̽Xz=$K̽D=Bc ̽ =,<̽R>\ ;<̽=;̽s=ޡ׼̽e=4̽=Z ̽Vs=i̽s=ޡ׼̽=Z ̽+<>h<̽k:>G<̽q?><̽k:>G<̽+<>h<̽7q5>yf<̽q?><̽k:>G<̽?>5a<̽ڄ=9Q̽= l̽@=̽= l̽=̽@=̽R>\ ;<̽<>d<̽>@N1<̽R>\ ;<̽^>;̽=;̽^>;̽R>\ ;<̽>@N1<̽=̽.=-ܼ̽Vs=i̽=Z ̽=̽Vs=i̽H=z6̽ *=̽W=4̽ *=̽=H"̽W=4̽&e> \̽]4`>gܑ̽^>+0̽&e> \̽c>B̽Sh>= ̽c>B̽&e> \̽^>+0̽g>({̽>d̽>w̽>w̽{>6̽g>({̽g>({̽=>21̽ >f̽{>6̽=>21̽g>({̽Hi> ̽>l̽9>V̽4D> ̽>̽ߧ >Ln$̽>̽%#>' ̽ߧ >Ln$̽%#>' ̽&>̽ߧ >Ln$̽IJ>l̽J >S̽J >k̽IJ>l̽ y>c̽}>J̽J >S̽IJ>l̽}>J̽y=ꑽ̽tf=ݘ̽a=̽Ђw=B̽y=ꑽ̽a=̽Ђw=B̽a=̽\=}̽X7>'<̽k:>G<̽Q2><̽Fa>> <̽k:>G<̽X7>'<̽?>5a<̽k:>G<̽Fa>> <̽S>h=̽M>Z=̽^M>v=̽'H>=̽,M>Qi=̽^M>v=̽^M>v=̽2jS>彃=̽S>h=̽,M>Qi=̽2jS>彃=̽^M>v=̽@=7̽8!=Jһ̽=e̽@=7̽=e̽~=OB̽={=7̽8!=Jһ̽@=7̽ilB>ᆽ̽5"E>̽=>̽p=>_:̽ilB>ᆽ̽=>̽S̽=>̽5"E>̽Y>z=̽V>=̽R>am=̽Y>z=̽Q!\>/=̽^>ҭ=̽Y>z=̽T>==̽Q!\>/=̽R>am=̽T>==̽Y>z=̽K}>u=̽a>s=̽uy>l=̽Dt>c=̽K}>u=̽uy>l=̽Dt>c=̽uy>l=̽r>/o=̽=J̽Ê=l{̽=Ǫ̽=J̽=={̽Ê=l{̽Ê=l{̽=v̽=Ǫ̽=W̽6=̽-=̽8=Y ̽=W̽=̽=W̽ΐ=ّ̽6=̽8=Y ̽ΐ=ّ̽=W̽<tA;̽{>x<̽~>L?<̽}>i<̽ z>A <̽~>L?<̽@(>a;̽H_=2<̽$F=b =̽/%=]K=̽Ø>^=̽>=̽6>|r=̽n>=̽>=̽>:<̽>=̽n>=̽6>|r=̽>=̽Ø>^=̽>:<̽5ξ=.=̽k==̽/=¹=̽V=(=̽(Ў=Б=̽= i=̽(Ў=Б=̽=%=̽= i=̽4D> ̽>̽>̽>̽4D> ̽cE>̽=?̽F=J)̽=*̽=Z ̽=?̽=*̽%=?̽F=J)̽=kA̽= >zU̽5 >բd̽>q̽g>({̽5 >բd̽>d̽5 >բd̽g>({̽>q̽7>̽̽ >h1ý̽̽̽ >h1ý̽/>̽̽̽ͅqR̽H;> Z̽9>V̽>l̽cx>qR̽9>V̽H;> Z̽cx>qR̽D_=+ .̽r=<̽m=i<̽ ?=d8<̽~.=&0<̽m=i<̽==(`<̽>w̽j>]|̽\>̽j>]|̽[>̉̽\>̽>=)̽=\̽Xz=$K̽=\̽>=)̽13=./̽=\̽D=Bc ̽Xz=$K̽t3U=T̽`@=3X̽@=7̼̽`@=3X̽t3U=T̽Y=Gs̽b=ͼ̽t3U=T̽@=7̼̽=R<̽(=<̽]=,g<̽ =<̽(=<̽E=:<̽(=<̽ =<̽]=,g<̽(=<̽=R<̽E=:<̽=<̽r=<̽r=<̽=<̽C=<̽==(`<̽C=<̽=<̽r=<̽x=u=̽w=Ft#=̽)~=/=̽P=Fμ̽]= u̽r =#M̽u=O̽]= u̽5=|5߼̽]= u̽u=O̽r =#M̽]= u̽P=Fμ̽5=|5߼̽<`;̽o; F;̽o; <̽^06Ϯ̽Fm>ۼ̽Z>Z̽Fm>ۼ̽!>Ϯ̽M#>s̽~=P̽k=ʚ̽b=ͼ̽Y=Gs̽k=ʚ̽7p=̽K=U̽$=op̽F=ԩi̽$=op̽K=U̽=,[̽o=c~<̽R>\ ;<̽ =,<̽R>\ ;<̽o=c~<̽v>n<̽=<̽o=c~<̽ =,<̽/?>;=̽JB>=̽E>=̽JB>=̽(7I>)=̽E>=̽=;̽K6=M<̽= ;̽K6=M<̽=;̽ ?=d8<̽\=j=̽=l=̽=x=̽=l=̽=%=̽=x=̽=l=̽\=j=̽5c=x=̽=J/=̽=0=̽k==̽Q=4'=̽=0=̽:=`B=̽=0=̽Q=4'=̽k==̽>E`>M2̽za>P̽˿d>R8̽>E`>M2̽v_>=D̽<[>-̽v_>=D̽>E`>M2̽˿d>R8̽za>P̽>E`>M2̽<[>-̽>̽̽S >w̽>̽̽t3U=T̽k=ʚ̽Y=Gs̽t3U=T̽b=ͼ̽k=ʚ̽o=c~<̽G=<̽v>n<̽=<̽G=<̽o=c~<̽*=.A=̽=0=̽=J/=̽*=.A=̽:=`B=̽=0=̽K6=M<̽m=i<̽~.=&0<̽ ?=d8<̽m=i<̽K6=M<̽~>L?<̽n@z>^<̽}>i<̽ z>A <̽n@z>^<̽~>L?<̽$>=̽>$>ځ<̽Ø>^=̽$>=̽(><̽>$>ځ<̽=<̽m=i<̽r=<̽==(`<̽m=i<̽=<̽>1P̽5 >բd̽= >zU̽>1P̽>d̽5 >բd̽S >w̽a>+½̽>̽̽I>v\̽a>+½̽S >w̽_>e̽j>]|̽>w̽_>e̽ >k̽j>]|̽Z=CՍ;̽K6=M<̽~.=&0<̽Z=CՍ;̽= ;̽K6=M<̽K%=`I̽8>̽ӽ=Ѽ̽K%=`I̽>l̽8>̽/%=]K=̽5ξ=.=̽/=¹=̽ f>|ۼ̽>̽cE>̽= i=̽=l=̽5c=x=̽= i=̽=%=̽=l=̽*=s̽=\̽13=./̽*=s̽D=Bc ̽=\̽cx>qR̽W=pЁ̽D_=+ .̽>l̽W=pЁ̽cx>qR̽8>̽>l̽Hi> ̽y>P=̽~>}E=̽BEz>9=̽~>}E=̽y>P=̽>6\=̽J >>x<̽J > <̽~>L?<̽J > <̽@(>a;̽~>L?<̽<`;̽o; F̽o; F;̽<`;̽;n<&t;̽7;<_c̽;n<&t;̽<`;̽^06,=̽J >.:=̽J >[!=̽~>}E=̽J >.:=̽N~>,=̽N~>,=̽v>1(=̽BEz>9=̽BEz>9=̽~>}E=̽N~>,=̽d h>N=̽bd>=̽k>=̽~>}E=̽J >S=̽J >.:=̽>6\=̽J >S=̽~>}E=̽ >h1ý̽7>̽̽>EA̽ >h1ý̽d_ >V{̽/>̽d_ >V{̽ >h1ý̽>EA̽o&>=̽r > ]=̽1 >=̽r > ]=̽o&>=̽ )>K=̽.b&>=̽r > ]=̽ )>K=̽F>v=̽!>=̽#>?=̽!>=̽F>v=̽,>=̽!>=̽.b&>=̽#>?=̽~=OB̽=̽=Z&̽@=7̽=Z&̽(=̀̽=Z&̽@=7̽~=OB̽I>v\̽S >w̽Ls>̽S >w̽/>̽Ls>̽%=?̽=*̽F=J)̽X=BK̽=*̽%=?̽7̐==̽(Ў=Б=̽V=(=̽(Ў=Б=̽7̐==̽;b=Β=̽=%=̽(Ў=Б=̽;b=Β=̽ڄ=9Q̽~=P̽= l̽ڄ=9Q̽k=ʚ̽~=P̽ڄ=9Q̽7p=̽k=ʚ̽JB>=̽0E>=̽(7I>)=̽i?>=̽0E>=̽JB>=̽JB>=̽/?>;=̽u<>x=̽u<>x=̽i?>=̽JB>=̽z=3u̽=?̽e=4̽F=J)̽=?̽z=3u̽=Z ̽e=4̽=?̽f=h̽ۃ=骽̽=~̽8>̽>̽ӽ=Ѽ̽8>̽XW>qټ̽>̽9J >̽XW>qټ̽8>̽8>̽Hi> ̽9J >̽&>̽+>&̽&>'4̽&>̽&>'4̽ߧ >Ln$̽";=̽;%>=̽+>|%=̽+>|%=̽]%/>z=̽3(>;=̽3(>;=̽#>=̽;%>=̽K=ڐ̽f=h̽=~̽w=Ft#=̽x=u=̽$F=b =̽(Y^=g̽J=w̽tf=ݘ̽tf=ݘ̽J=w̽a=̽E=WV̽a=̽J=w̽=J/=̽5ξ=.=̽)~=/=̽5ξ=.=̽=J/=̽k==̽5ξ=.=̽x=u=̽)~=/=̽m=D<̽a=K<̽=6<̽<=Y<̽a=K<̽m=D<̽a=K<̽<=Y<̽/=¹=̽ =̽̽믖=1̽V=X̽믖=1̽ =̽̽ώ=̽̽N<@̽Q<@Y̽[ìI=̽K}>u=̽M~> =̽K}>u=̽J >I=̽J >S=̽w\=*Q̽ j=~̽fM=B̽l=U̽w\=*Q̽ӸR=ջ̽w\=*Q̽u2r=T̽ j=~̽l=U̽u2r=T̽w\=*Q̽1=5̽/1=):̽e8='̽r=;̽/1=):̽1=5̽/1=):̽r=;̽H/=;̽~=OB̽b=̽=̽c=̽=̽b=̽b=̽~=OB̽Ї=&̽#>=̽3(>;=̽ > )=̽3(>;=̽##>.n=̽ > )=̽##>.n=̽3(>;=̽*>m=̽Q<@Y̽o<'̽[ì=/<̽{=7_<̽=b<̽>=/<̽J= <̽{=7_<̽>=/<̽r=;̽J= <̽r=;̽>=/<̽H/=;̽n9D=ڦ̽N 0=0鞽̽J=w̽N 0=0鞽̽F+=Ӑ̽J=w̽J=w̽F+=Ӑ̽E=WV̽ ==̽I==̽r.==̽I==̽ ==̽L=I=̽=$!̽=kA̽F=J)̽=$!̽H=z6̽=kA̽0E>̽̽ F>d̽D L>ޗ½̽ F>d̽0E>̽̽@>j̽D L>ޗ½̽K>̽̽0E>̽̽N=YȢ̽ۃ=骽̽.=^̽f=h̽ d=J̽.=^̽ۃ=骽̽f=h̽.=^̽fb>ɩ̽]>`̽Uy]>ԅ:̽]>`̽c>B̽^>+0̽fb>ɩ̽c>B̽]>`̽v<4̽7;<_c̽ʐ<κ̽ʐ<κ̽7;<_c̽;n<&t;̽;n<&t;̽<tA;̽ʐ<κ̽w=Ft#=̽^=I=̽Κ==̽$¡=W<̽^=I=̽$F=b =̽^=I=̽$¡=W<̽Κ==̽^=I=̽w=Ft#=̽$F=b =̽<˓̽Q<@Y̽N<@̽< e̽<˓̽N<@̽o;[!=̽/<.=̽yx̽e<w?=̽Yz<R=̽]-=̽3(>;=̽]%/>z=̽3>W=̽->-=̽]%/>z=̽Dv=0̽IU<᭻̽y= R̽Dv=0̽1=5̽= )̽1=5̽Dv=0̽y= R̽IU<᭻̽Dv=0̽= )̽0=D:=̽)~=/=̽w=Ft#=̽1װ=*E=̽)~=/=̽0=D:=̽y-=̽0>l=̽*>m=̽0>l=̽->-=̽3>W=̽3(>;=̽->-=̽*>m=̽=,[̽=kA̽cN=UO̽H=z6̽o,=H̽cN=UO̽H=z6̽cN=UO̽=kA̽<~̽C<¼̽<˓̽C<¼̽<̬̽<˓̽< e̽<~̽<˓̽o;&=̽o+<+=̽o;I=̽o;J=̽Z+<=̽o;&=̽k:I<$}=̽o;&=̽Z+<=̽o;&=̽k:I<$}=̽o+<+=̽$F=b =̽x=u=̽/%=]K=̽/%=]K=̽x=u=̽5ξ=.=̽֠=Z׼̽dm=̍̽c=̽֠=Z׼̽c=̽ռ=ܼ̽ռ=ܼ̽c=̽Ep=m̽>̽̽̽̽/>̽S >w̽>̽̽/>̽qO=o=̽ ==̽#==̽=C=̽L=I=̽qO=o=̽ ==̽qO=o=̽L=I=̽zd̽@>j̽#A>&̽};>̽@>j̽o;>Ľ̽#A>&̽@>j̽};>̽X=BK̽K=U̽F=ԩi̽=kA̽=,[̽K=U̽K=U̽%=?̽=kA̽K=U̽X=BK̽%=?̽r > ]=̽.b&>=̽!>=̽!>=̽,>=̽r > ]=̽>=̽1 >=̽r > ]=̽r > ]=̽b>=̽>=̽b>=̽r > ]=̽,>=̽=}̽=J̽=~̽ۃ=骽̽=}̽=~̽=}̽ۃ=骽̽=Ԕ̽Fm>ۼ̽ f>|ۼ̽Z>Z̽>̽Fm>ۼ̽M#>s̽>̽Fm>ۼ̽>̽ f>|ۼ̽Fm>ۼ̽>̽k>=̽,vn>?=̽d h>N=̽~<=̽ʹ<7=̽Z+<=̽ʹ<7=̽~<=̽v{<&Ƭ=̽ >L̽ >'̽Ls>̽ >L̽/>̽d_ >V{̽/>̽ >L̽Ls>̽ >'̽ >L̽d_ >V{̽~<=̽ԙ<]=̽v{<&Ƭ=̽eLh< $=̽ԙ<]=̽~<=̽~<=̽Q9̽B>a̽!>4̽<>9̽>񼟽̽B>a̽B]q>=̽,vn>?=̽k>=̽F>I̽B>a̽>񼟽̽B>a̽F>I̽\>̽[>̉̽B>a̽\>̽p9t> =̽,vn>?=̽B]q>=̽<̬̽zN=̽Yw>G=̽r>\ɬ=̽Yw>G=̽o>N=̽p9t> =̽p9t> =̽o>N=̽,vn>?=̽d h>N=̽o>N=̽kh>=̽,vn>?=̽o>N=̽d h>N=̽s<:O̽]<̽o;>x̽k<撽̽->̽>(>G̽%1>C̽&+>S̽+>ӝ̽3'><撽̽&+>S̽->̽&+>S̽%1>C̽->̽5;+i̽]̽ >g̽$><諽̽!>4̽ >g̽<>9̽ >g̽!>4̽$><諽̽ >g̽Z>̽<>9̽!>4̽;">Э̽>(>G̽;">Э̽3'><撽̽>(>G̽P.i>=̽_m>u_=̽[o>Fz=̽o>N=̽_m>u_=̽kh>=̽_m>u_=̽P.i>=̽kh>=̽_m>u_=̽r>\ɬ=̽[o>Fz=̽o>N=̽r>\ɬ=̽_m>u_=̽o;S̽5;+i̽o;k̽o;S̽/ӝ̽&>b̽&>Z>t̽#>ō̽[>̉̽&>b̽&+>S̽&>b̽+>ӝ̽3'><撽̽#>ō̽&>b̽3'><撽̽&>b̽&+>S̽#>ō̽3'><撽̽;">Э̽;">Э̽B>a̽[>̉̽B>a̽;">Э̽!>4̽[>̉̽#>ō̽;">Э̽>(>G̽ +>8̽$><諽̽.>2̽>(>G̽->̽.>2̽ +>8̽>(>G̽u=̽̽<`=̽a=gc̽[>̽̽u=̽̽a=gc̽<`=̽u=̽̽=̽̽=P̽#=0̽I=t̽=P̽%=ý̽#=0̽3=̽̽#=0̽|=̽̽3=̽̽K=ڐ̽#=0̽%=ý̽|=̽̽#=0̽uj=K̽=c̽(={̽={r̽=c̽uj=K̽&>b̽!>/̽&>Z>t̽!>/̽&>b̽[>̉̽!>/̽ >k̽&>Z>t̽j>]|̽!>/̽[>̉̽j>]|̽ >k̽!>/̽=̽̽%=ý̽<`=̽%=ý̽=P̽<`=?̽o;Lc ƽo;ۢƽ̽o;̽? ƽo;ۢƽLco;̽̽o;̽?aýo;|̽o;̽o;J? ƽo;ۢƽ Qýo;Lco;̽?o;{[aýo;|̽o;J? Qýo;̽o;aýo;|? Qýo;̽o;Lc̽o;?̽o;Lc Qýo; ƽo;ۢƽ?jo;=Lco;=o;=?o; ۄo;o;v?o; 1{o;Iso;?Io;=약o;Q=&o;=?o;U=약o;Q=i;o;}=?7/o;bo;{[ o;s?͌o;Ѹ=i;o;}=약o;Q=?`o;皓$o; o;s?6o;.$o;_o;i蝽?뫽o;@6o;.X͟o;r?Jo;z뫽o;@X͟o;r?6o;.뫽o;@7/o;b?+½o;=̽o;&=̽o;J=?̽o;&=+½o;=&o;ɛ=?uo;R=o;U=°o;=?6o; o; o;v?6o; 6o;._o;i蝽?|Ľo;Ļ=̽o;=̽o;Lc=?̜o;´=o;=㡽o;6w=?׉o;Ư=}o;p=°o;=?jo;=}o;p=|Ľo;Ļ=?aýo;|!o; Qýo;?xo;̽Ao;% o;̽?o;=io;f=o;=?*o;]f%Go;gb7o;eR? 盼o;V>xo;̽ ŭo;̽?D(o;exFo;a7o;?,o;̽o;o;4?$o;S w嘽o;pZo;?tlo;Cص͸o;5쮽o;lZ?5ao;ar=CUo;C= Eo;|o=?jo;Ƽfko;Wso;?*o;]f7o;%Go;g?5Po;Ld=mȣo;"u=o;z=?߻o;xD{o;VF'Lo;T[?ˡco;=&ko;ĺ=GTo;FF=?Po;=[oo;Ƣ=ˡco;=?U o;Yj5o;w!o;̇?XAo;*R=&ko;ĺ=ˡco;=?C o;мtlo;Cص쮽o;lZ?tlo;CصC o;мo;4?o;ݼC o;м쮽o;lZ?ԛUo;Xo;?[xrýo;(v̽o; ŭ?zo;[=5Po;Ld=Vϱo;ډ=?nqo;M1{o;I_o;i蝽?&vo;⇽|տo;̽o;I?o;ݼ!o;xo;̽?>Xo;˳o;ao;˱?$Ro;=ԝ@o;%~=CUo;C=?$Ro;= c:o;ؒ=ԝ@o;%~=?m5o;Ҝ7o;Xo;7o;m5o;Ҝ?io;f=mȣo;"u=5Po;Ld=?)wko;N=$Ro;=CUo;C=?lPio;3-=$Ro;=)wko;N=?l,=o;=y=o;{=y=o;zÇ=?1D=o;p[y=o;R]=o;+?7o;xFo;aԛUo;?`o;GY%Go;gKeo; r?=o;=褦=o;I=l=o;I=?Sσ=o;-#=o;દ_‹=o;n?7o;j5o;wLo;?7o;!o;̇j5o;w?5ao;ar=)wko;N=CUo;C=?5ao;ar={o;.w=)wko;N=?rýo;(v͸o;5tlo;Cص?nPo;sr}7o;Lo;?%Go;g7o;nPo;sr}?ao;˱go;yD(o;e?½o;1,o;%0߻o;xD?o;`a=mȣo;"u=~o;Ͱ`=?o;`a=o;z=mȣo;"u=?o;/m`{o;VFYʠo;fE?'Lo;T[{o;VFo;/m`?jo;Ƽ ~o;=@`o;v?ԛUo;`o;vx̽o; ?rýo;(v̽o;>xo;cQ?o;cQ͸o;5rýo;(v?ͼo;i ŭo;̽k߼o;̽? ŭo;̽ͼo;i 盼o;V?Go;=o;=k߼o;=?>xo;=MQo;޿= ŭo;=?½o;1,o;,o;%0?̽o; ŭ<o;ڔ<̽o;>x<?=o;S=~=o;X ==o;I=?~=o;X =l=o;I=?=o;=?S=o;̽ =o;kI=o;̽? =o;k_‹=o;n =o;8?>xo;?ݕo;DZo;S>ᚽo;L?o;!ܫo; ]=?b#o;o;rc===o;[J=?#o;Cj=e>o;rc= o;|=?@o;Txo;=?j=o;ԉ;bA=o;1;g=o;(:?)Qo;w=ցTo;# =@ Mo;2k=?#o;Cj=o;Ƃ=)Qo;w=?o;Ƃ=#o;Cj= o;|=?io=o;1gj=o;γSσ=o;-?j=o;γio=o;1gp[=o; ?ѯ=o;sm="=o;.j==o;=?"=o;.j=ѯ=o;sm=)̬=o;S=?"o;$2o; o;t ? Bo;2o;nEo;>]?n=o; j=o;ԉ;g=o;(:?j=o;ԉ;n=o; -=o;:?ɌVo;6: r|mo;ǎ4ho;EI?ɌVo;6: Bo;nEo;>]?:$=o; 62=o;0g=o;(:?2=o;0:$=o; 6SQ=o;?稽o;kOo;=Nᚽo;L?Yo;H=o; =o;h=?Yo;H=ցTo;# =[o;=?:o;R=w;o; Y= q;o;X=?0;o;pW}: <?=o;==o;DW=䳅=o;<?=o;DW==o;==o;\=? ժo;Լo;~Ko;q?=o;0bD?o;`I o;?Po;=Jo;= /4o;=?GTo;FF=Jo;=ˡco;=?Jo;=Po;=ˡco;=?1zwo;"PH1o;I o;?0o;]?咽o;'=Vo; =8uo;q =?O=o;ɹ= +Z=o;{=o==o;h=?D?o;`ko;o;G}?ko;D?o;`v[o;>b?=o;G*=o;P=o;e7?o;ּo;go;?=Լo;: ժo;o;? ժo;=Լo;:Լo;~?=o;DW=Ο=o;i<ғ=o;{<?=o;>=o;0Aa=o;Ԕ?=o;I=o; =o;>x?=o;I=o;ASQ=o;?=o;`=o;=o;[!?=o;=o;`b(=o;?o;X=ցTo;# =)Qo;w=?ցTo;# =o;X=[o;=?0o;b?xo;c.o;ntԼo;~?Co;E`_$ o;KJo;YL?ۻo;=o; =0o;=?o; =ۻo;=wHo;_ԟ=?xx<=o; <?=o;F<=o;ZKo;m/i9o;h*?ZKo;m/Bo;>j\o;P>?_o;@.o;ntxo;c?.o;nt_o;@ޤo;m?Ѧ=o;<=o;k<=o; ŭ<?Ѧ=o;<#=o;R<=o;<?=o;d=Ԉ=o;]=y=o;{=?ZKo;m/NV\o;u"nEo;>]?e=o;㔽QY=o;[y=o;R?.:=o;=",=o;?=[!=o;=?Ao;;kh=o;F]==_o;^~w=?o;F]=Ao;;kh=ۀo;J=?y4o;1zwo;"Pc:o;(Ҷ?y4o;;o;Ja½ o;?1zwo;"Py4o; o;? Eo;|o=-o;5}= +o;e=?-o;5}= Eo;|o=ԝ@o;%~=?5/`o;Ho;ýSo;̽?Ho;ý5/`o;xFo;a?s$ =o;zx?!=o;2=>=o; 6=ɤ=o;3~=?b;o;=x<7=o;|b<=o; ŭ<?#=o;R<7=o;|b?`o;GYzLo; *P%Go;g?|=o;U=y=o;{=l,=o;=?t=o;e=|=o;U=l,=o;=?Po;?^"o;?~䕽o;:?[=o;t=t=o;(y=y=o;zÇ=?y=o;{=[=o;t=y=o;zÇ=?io;+`<@o;d=o; 6==o;L =ɤ=o;3~=?h=o;:==o;L =>=o; 6=?D=o;;=o;F<=o; <?=o;<=o;Fx?=o;A=o;Ij=o;|?7=o;|b<Ѧ=o;<=o; ŭ<?#=o;R<Ѧ=o;<7=o;|b<?Iy=o;k=y/=o;K"=t9=o;F=?Iy=o;k=5=o;Ϳ=y/=o;K"=?e=o; מ=o;߁ȥ=o; ?e=o; =o;מ=o;߁?.qo;C`=Wo;!\=@'fo;O=?5ao;ar=Wo;!\=.qo;C`=?o;ʷ=G˿o;=MQo;޿=?;ռo;Դ=G˿o;=o;ʷ=?6=o;g=N=o;G=8=o;P=?=o;LB=o;޴Yx=o;XS?D$=o;A?=o;޴Y=o;LB?.:o;̽C1o;[!o;̽?=o;==o;!=랻=o;F=?=o;ζ=o;iF=o;ힻ?=o;F[Dyc=o;AMv=o;+? ϡ=o;9U=o;]\=o;?P=o;D̡=bÖ=o;.m==o;\=?Ho;ý.:o;̽So;̽?Wo;0zo;.Yʠo;fE?hzo;{Fzo;.Wo;0? {o;`PWo;0,o;%0? {o;`Phzo;{FWo;0?քao;㕎^o;Lo;?|o;~_=d|o;n=lPio;3-=?|o;~_=o;=d|o;n=?^"o;?~o;/m`?o;p?-=o;: =o;> o;rc=#o;Cj=?ɼo;L===o;[J=e>o;rc=?nܫo; ]=&V̻o;B=Eo;O=?9o;oK=&V̻o;B=>ܫo; ]=?ɍo;XD1o;_߮o;\S?;=o;eȼk=o;@t4|=o;S_? z=o;lk=o;@;=o;eȼ?̔h=o;AT=o;&Mv=o;+?̔h=o; z=o;lAT=o;&?Njo;{B=o;<[=Eo;O=?Njo;{B=ɼo;L=o;<[=?o;Nqo;}o;CO޼?o;qo;o;N?=o; =j=o;p= |iao;Z?ᚽo;LZo;S>]9o;QZ?o;EsNOo;=Nxo;$;?o;EsNᚽo;LOo;=N?$=o;xL, =o;= =o;?A?3=o;ó,ђ=o; W=o;)?3=o;ó,AT=o;&ђ=o; W?o;݋o;gD?o;`?o;݋0o;o;g?V=o;<=o;iJ3=o;`?V=o;<*=o;ٻ=o;iJ??|ܫo; ]=@ Mo;2k=?Po;$"F=afo;҆9=@'fo;O=?Po;$"F=.No;5.=afo;҆9=?m6Ro;r=G=o;f!=,:o;B&=?m6Ro;r=.No;5.=G=o;f!=?i9o;h*2o;"o;$?nEo;>]2o;i9o;h*?ɌVo;6: NV\o;u"r|mo;ǎ?ɌVo;6: nEo;>]NV\o;u"?Ao; =jo;S8<8uo;q =?Ao; =?o;bH1o;o;.?v[o;>bI o;H1o;?@o;T<@o;d<%o; h<?@o;To;rc=io;P`=? o;|=e>o;rc=:o;w=?+o;k=o;X=r]o;wL=?+o;k=[o;=o;X=?Yo;H=0o;=o; =?Yo;H=[o;=0o;=?2=o;oK<$=o;xL,= =o;?d)#=o;ѓa=o;Ԕ=o;>?.qo;C`=Ao;;kh={o;.w=?.qo;C`=ۀo;J=Ao;;kh=?&o;|:7o;^6o;͹ :?Oo;=N7o;^&o;|:?/;o;y=wHo;_ԟ=b;o;=?:o;R=wHo;_ԟ=/;o;y=?=o;F<7=o;|b<=o;>x<?=o;Fbe@o;`쥽?+so;x<8o;<%o; h<?lo;f<8o;<+so;x<?y4o;rdo;5½;o;Ja½?y4o;c:o;(Ҷrdo;5½?=o;,=o;} /=o;ю)?Ѧ=o;<=o;m<=o;k<?=o;<=o;m<Ѧ=o;<?꭭o;)-;Ïo; Fz-o;];b7o;eR?i9o;h*Fz-o;];Bo;>?ˁo;<=Hlo;I=Dyo;<?ˁo;<=C}o;E'=Hlo;I=?mo;`߼o;0˳o;?mo;ּo;g`߼o;0?xb7o;eR?j\o;P>Bo;>zLo; *P?t=o;(y= z=o;`=f=o;b=?[U=o;l5w=t=o;(y=f=o;b=?=o;<=o;L =&=o;3u=?wo;P=o;v=H o;<?wo;P=H o;<o;H=?;*o;2=wo;P={Ro;\,=?^"o;?~Po;?0o;?Po;?Yo;8ג0o;?̽o;[!̽o;,o;?½o;1̽o;[!,o;?}o;CO޼o;^]o;?o;^$o;S ]o;?-=o;G=Ԉ=o;]==o;d=?-=o;G={=o;V[@=Ԉ=o;]=? z=o;`=Ԉ=o;]==o; D=?{=o;V[@==o; D=Ԉ=o;]=?NK=o;QY=o;e=o;㔽?9=o;QY=o;NK=o;?H=o;/QY=o;9=o;?&o;̽Jo;̽o;ý?\Ɩo;Aý&o;̽o;ý?=o;e7/=o;ю)=o;G?V=o;е2=o;G/=o;ю)?~=o;X =ҵ=o;jހ=l=o;I=? =o;k=o;_‹=o;n?ҵ=o;jހ==o;=l=o;I=?=o;=ҵ=o;jހ=ѯ=o;sm=?=o;Sσ=o;-_‹=o;n?Sσ=o;-=o;io=o;1g?"=o;=+>=o;[=xH"=o;ʝ=?+>=o;[="=o;=o==o;h=?+>=o;[=6=o;g=xH"=o;ʝ=?=o;Aj=o;|`=o;X8?1G=o;a=.:=o;=S=o;=?",=o;?=.:=o;=1G=o;a=?O=o;ɹ=5=o;=1G=o;a=?",=o;?=1G=o;a=5=o;=?Q =o;I=o;.:=o;S?=o;GV=o;е2Q =o;I?b(=o;=o;k߼=o;?.:o;=DDo;=So;=?DDo;={i;o; =GTo;FF=?̽o;.:=½o;m'=̽o;[!=?̽o;.:=Qeo;E|F=½o;m'=?½o;m'=Qeo;E|F={Ro;\,=?df+o;s=.:o;=[!o;=?DDo;=.:o;=df+o;s=?DDo;=df+o;s={i;o; =?$x<o;ڔ=o; 6=)̬=o;S=h=o;:=?)̬=o;S=>=o; 6==o;6L=?>=o; 6=!=o;2==o;6L=?W=o;=n=o;d=N=o;G=?ˏvo;H*Vzo;aW\o;so;Њ?2>so;ЊKeo; r^o;?t=o;e=l,=o;=Iy=o;k=?˚=o;~=t=o;e=Iy=o;k=?`o;v ~o;=@Mzo;?`o;vMzo;քao;㕎?>xo;= o;=";o;^=? o;=Ϡo; =";o;^=?5=o;=o==o;h="=o;=?o==o;h=5=o;=O=o;ɹ=?LQ=o;/=5=o;="=o;=? ϡ=o;9U#=o;દX=o;Lۚ?Ξ=o;ʫ ϡ=o;9U=o;lD? ϡ=o;9U\=o;=o;lD? ϡ=o;9UΞ=o;ʫ#=o;દ?P=o;D̡=褦=o;I=˚=o;~=?ɫ=o;͞=P=o;D̡=C=o;=?P=o;D̡==o;\=C=o;=?P=o;D̡=ɫ=o;͞=褦=o;I=?=o;LBLn=o;9F3=o;ó,?=o;)=o;LB3=o;ó,?=o;LB=o;)D$=o;A??y=o;E)=o;)ԑ=o;j?=o;)y=o;E)D$=o;A??=o;oמ=o;߁=o;?aC&?ˏvo;Hj\o;P>`o;GY?j\o;P>ˏvo;Haro;-?aro;-NV\o;u"j\o;P>?&=o;3u==o;[!==o;=?&=o;3u==o;l3==o;[!=?=o;m<&=o;3u==o;=?&=o;3u=z=o;/==o;l3=?=o;< =o;ߛ<=o;<?=o;<=o;<=o;<?o;ݝ=Qo;=o;=?C#o;s\=#0o;+=o;ݝ=?o;ݝ=ɟo;=C#o;s\=?o;=ɟo;=o;ݝ=?|տo;̽o;&̽o;I?̽o;S?o;FY]̽o;k?ݰo;Jx̽o;k?o;FY]?Ho;K`o?o;FY]'Lo;T[?Ho;K`oݰo;Jx?o;FY]?[=o;t I=R=o;N^=f=o;b=?:)o;vNo;Ĕ!Go;oZ?c?o;m;mo;&;H̻o;d-?Yo;9Mno;1mo;澮;?mo;澮;1Vo;4E;Yo;9?1Vo;4E;+Ao;8";Yo;9?;o;~i$;?;?( =o;><?o;U@ o;;o;D!;?o;Uo;d軧 o;޻?Ҷr?8o;g̵o;fNo;Ĕ?{Q=o;sa,d=o;tN=o;?{Q=o;si?=o;rsV=o;-:?:o;S^=Qo;Q#=ѻo;~(=?:o;S^=iSջo;(<Ho;W<?o;m;lMo;;mo;&;?no;3J;?(o;MsRno;ż?E=o;ȓ<?q=o;;r"=o;FO?f $ o;"?ڼo;g* o;> DJo;@?a!o;8< o;il}< o;8<? o;8< o;il}<d o;<?wGo;D<+Ao;8";1Vo;4E;?+Ao;8";wGo;D?)6=o;Ȳ<"r/=o;<=o;<?&(=o;<"r/=o;?=o;U;ɗl=o;H:;^p=o;'b<?58?ɗl=o;H:;kg=o;fosV=o;-:?7o;0Jb#:o;7;o;0?V8o;oJb#:o;[-o;Pf?ɗ8?UWo;4Mo; O`o;h?YXo;ao;˱?>Xo;Sp#o;7o;?D(o;eSp#o;ao;˱?A= u=o;E+=?m=o;>==o; D= u=o;E+=?m=o;>=y_=o;-=[=o;t I=? =o;]ڼ^ =o;}5Ǽ@)=o;?d3=o;Oۼ =o;]ڼ@)=o;?^ =o;}5Ǽ =o;]ڼ=o;bq?I=o;:=؏n=o;j=g=o;;=?io;!7o;0nQȺo;+0?7o;0io;!X[o;#?L=o;d3=o;Oۼ%N=o;[ؼ?Zo;S>3o;o;?3o;Zo;S>ݕo;D?3o;Axo;o;?Po;{Wo;Ihdo;?]o; Wo;ILo;~?=o;%Т< <=o;;ָ <ō=o;<? =o;> =o;*h=R=o;N^=?g=o;Ҁxʹ=o;lp#=o;} ?xʹ=o;lpb(=o;=o;} ?xʹ=o;lpg=o;Ҁ`=o;X8?83o;0=Qo;Q#=zo;y!=?Ho;:]o; Lo;~?=o;޴Yp=o;*7n=o;o?מ=o;߁p=o;*7n =o;{m?p=o;*7nמ=o;߁=o;o?p=o;*7n=o;޴Y =o;{m?.?o;=o;[=?/G=o;=W=o;=N=o;G=?W=o;=/G=o;=+>=o;[=?6=o;g=/G=o;=N=o;G=?-=o;:א=o;; =o;> <?=o;P?;א=o;;k=o;):?z;h;o;go:o;X0o;?go:o;Xz;h;o; <א=o;;?TCo; <Lo;ן ڼo;g* ?$ o;"o;> {ݼo;$!?=o;,F=o;5g=o;Ҁ?=o;,ԑ=o;jF=o;5?e=o;'k=o;<=n=o;O==o; D=?m=o;>=[=o;t I=n=o;O=?Y:o;,#Qwko;@Y,/o;?ۺo;a˖wko;@YY:o;,#Q?tdo;żJ;o;"IV8o;o?tdo;żۺo;a˖J;o;"I?2S=o;RL?u=o;] ?q"=o;ﻅu=o;] 2S=o;RL?q"=o;Ў=o;=Nu=o;] ?m=o;L5(=o;T@)=o;?m=o;LЎ=o;=N5(=o;T?/o;o;G "o; ۻ?/o;M$o;k;o;?o;;u/o;;a!o;8<?M$o;k;u/o;;o;;?Wao;}>=o;SڼMo; ?sRno;ż=o;SڼWao;}>?߭;A;(<=o;sx=o;Żkg=o;fo?k=o;):=o;U;=o;P?;?k=o;):h}=o;>=o;U;?k[=o; ;^p=o;'b<ɗl=o;H:;?k[=o; ;`=o;<<^p=o;'b<?Fe=o;TUWo;4? o;t ?DJo;@o;> o;D?=o;U;|x=o;&<=o;P?;?=o;U;^p=o;'b<|x=o;&<?=o;!&=o;sCd3=o;Oۼ?;=o;=`?,=o;7r="=o;a=?̽o; <̽o;>x=o;[=o==o;h=? +Z=o;{=W=o;=+>=o;[=?o;v=5Vo; =8uo;q =?o;v=wo;P=5Vo; =?o;̽ceo;Ľk߼o;̽?ͼo;iceo;Ľ;o;=?ceo;Ľͼo;ik߼o;̽?!ýo;柽̽o;&|տo;? o;s!ýo;柽|տo;?!ýo;柽 o;so;{[?\){=o;wd=o;y59^=o;n?d=o;yQY=o;H=o;/?59^=o;nd=o;yH=o;/?DU=o;Ep[=o; io=o;1g?.=o;%]=)̬=o;S=ѯ=o;sm=?Z%=o;ܑF=)̬=o;S=.=o;%]=?k3@o;#r=Y;o; x=;o;i=?Y;o; x=Kn xxso;Њ^o;?քao;㕎Mzo;2>so;Њ?=o;=o;ζ=o;lD?=o;ζ=o;=o;i?=o;(=o;n=o;i?쩨=o;==o;=C=o;=?=o;=쩨=o;==o;!=?쩨=o;=3ߟ=o;==o;!=?=o;e7*=o;PD$=o;A??*=o;P=o;޴YD$=o;A?? =o;{m=o;޴Y*=o;P?5Vo; =;*o;2=חo;~:=?咽o;'=5Vo; =חo;~:=?5Vo; =wo;P=;*o;2=?y=o;E)=o;,=o;e7?/=o;ю)=o;e7=o;,?=o;,y=o;E)ԑ=o;j?\o;,3=˼o;2 =|o;=?_{o;g1=\o;,3=|o;=?\o;,3=_{o;g1===o;[J=?So;=o;=Io;=?yo;=o;=So;=?͌o;Ѹ=o;=ԁo;E=?ԁo;E=o;=yo;=?[U=o;l5w=f=o;b=R=o;N^=?=o;m<=o;<=o;<?&=o;3u==o;m<=o;<?;o;xR'Pܫo; ]=?q:o;^=k3@o;#r=;o;i=?q:o;^=9o;oK=>ܫo; ]=?m=o;L^ =o;}5Ǽ6Xo;?˳o; o;mo;? o;˳o;>Xo;?d)#=o;ѓ"=o;a=o;Ԕ?Q:=o;yǚ"=o;d)#=o;ѓ?Yo;vio;X=t&o;e?Yo;vo;io;X=t?"=o;.j=|=o;U==o;=?|=o;U="=o;.j==o;d=?|=o;U=t=o;e==o;=?y=o;{=|=o;U==o;d=?x%=o;H=<#=o;ik4==o; }C=?x%=o;H=ɣ'=o;g^=?=o;M=?ɣ'=o;g^=>=o;*h=?=o;M=?ɣ'=o;g^=`?,=o;7r=>=o;*h=?>=o;*h=`?,=o;7r=;=o;=?{Ro;\,=o;H=½o;m'=?{Ro;\,=wo;P=o;H=?_Bso;ЊMzo;o;?Yo;v2>so;Њo;?}o;)苼o;\;Zo;!]%?}o;)ɍo;XDļo;t:?}o;).^o;W7ɍo;XD?}o;)Zo;!]%.^o;W7?j=o;γj=o;Sσ=o;-?j=o;j=o;γe=o;㔽?j=o;7}=o;l~Sσ=o;-?[y=o;Rj=o;e=o;㔽?~<9o;m= F;o;=옩;o;=?~<9o;m=/9o;T=Ϡo; =?/9o;T=~<9o;m=옩;o;=?;o;]@=;o;KwT=3=o;?pS=o; R+K=o;'>=o;?0o;Uo;}1Do;e z?7g=o;͇m=.=o;%]=ѯ=o;sm=?7g=o;͇m=ҵ=o;jހ=c=o;1x=?ҵ=o;jހ=7g=o;͇m=ѯ=o;sm=?=o;H==o;˱=I=o;=?1o;_ +o;qifo;F?_{o;g1=v'o;AN={4o;8=?v'o;AN=G=o;f!={4o;8=?=;o;(==o;Qp[=o; DU=o;E?=o;&sB=o;d=o;1Y?sB=o;d(=o;n=o;1Y? Fo;= F;o;=~<9o;m=?Ϡo; = Fo;=~<9o;m=?=o;*h=[U=o;l5w=?[U=o;l5w=>=o;*h=;=o;=?=o;S=X=o;]==o;k=?=o;k=X=o;]=c=o;1x=?7g=o;͇m=X=o;]=.=o;%]=?c=o;1x=X=o;]=7g=o;͇m=?S=o;̽x8_=o;9|k=o;̽?k=o;̽x8_=o;9|-x=o;?{l=o;=x8_=o;9|DU=o;E?-x=o;x8_=o;9|{l=o;=?h=o;:=Z%=o;ܑF=z=o;/=?=o;l3=z=o;/=Z%=o;ܑF=?=o;l3=Z%=o;ܑF=&=o;qF=?&=o;qF=Z%=o;ܑF=X=o;]=?X=o;]=Z%=o;ܑF=.=o;%]=?0o;<̽o;k<̽o;=? +Z=o;{=#x=o;YP=Tk=o;=?Tk=o;=W=o;= +Z=o;{=?Hf<?ڑ<?F-=o;?}=o;Q10=o;X&=o;?"=o;>=o;QX&=o;?Q:=o;yǚ>=o;Q"=o;?p[=o; >=o;Q\R=o;_?\R=o;_>=o;QQ:=o;yǚ?dĽo;f_=:o;d_=Qeo;E|F=?+Do;H=:o;d_=o;`a=?Qeo;E|F=:o;d_=+Do;H=? |xx=o;/=o;~!&=o;sC?=o;.:V=o;е2=o;[!?V=o;е2=o;.:Q =o;I?V=o;е2=o;`=o;[!?/=o;ю)=o;`V=o;е2?̽o;k=o;s=dĽo;f_=?dĽo;f_=o;s=:o;d_=?o;s=|o;=o;z=?:o;d_=o;s=o;z=?V+=o; 0x=o;QDU=o;E?>=o;QA=o;\10=o;?)@=o;˜A=o;\DU=o;E?̽o; F;o;{S̽o; F?̽o; F;½o;h;6o;͹ :?6o;͹ :o;{S̽o; F;=>p%<̽=Q<ý=<̽=#}>=={>==w>==ƵQp%<==<=={>ý=#}>̽=w>̽=3)G=w>̽=B]q>̽=<==<==%<==w>==6t>n==B]q>==<̽=-<=%<̽={>ý=w>̽=pau>G=pau>G=`x>Mn={>ý=ƵQ=={>==6t>n==Yw>==6t>n=={>==<̽=Q<ý=-<=z<í=-<=Q<ý=Z=.== ]t=R)==Bo===: ,=la==")===j=߉==B>Db=>>T=F>ի=$a>=c>(Ψ=mh>x=c>(Ψ=$a>=J^>m=Y>Q==V>,==29^>$==XR>-==V>,==XP>$G==J >.:= ~>-=J >[!=È===l=ǵ==2=֨==l _>;<=CY>1N=X>4=Ƃ===*q======*q===Ƃ===+Nh=r==F=nű==Pg===+Nh=r==F=nű==l=ǵ==È===O`L>/ɦ=CL>n=F>ի=CL>n=o/S>}=R>t=o/S>}=CL>n=O`L>/ɦ= -H==J@d>h3==Rb>VQ==O+9>̽==>}½=i?>̽=t=2P==@:===z=&==eS==Yw>=={>==eLh< $==XS<==ƵQMn= ~>={>ý=J^>m=xd>⌜=c>(Ψ=K;>2=B>Db==>}½=B>Db=K;>2=>>T=A(K=вN==]`=E==lJ=4==oU>HZA=CY>1N=U>HY=CY>1N=oU>HZA=X>4=8P>?=oU>HZA=U>HY=O+9>̽=U7>|~==>}½=`x>Mn=Is>=5s>=Is>=$n>{=5s>=Is>=`x>Mn=pau>G=l=ǵ==Ј=6==2=֨==-j>*=o>{=ٯn>A{=+0<ݝ=z<í=Uuw= Q>Ο=O`L>/ɦ=<p=Ñ =ٯn>A{=5s>=$n>{= m> =5s>=CL>n=yG>P =F>ի=yG>P =B>Db=F>ի=5x=w==Z=.==Bo===Z=.==5x=w==È===&u>0=_u>T.=o>{=_u>T.=ٯn>A{=o>{=nl>~$b==n>z==UGs>Sd==$n>{=p>7=k>F=pau>G=p>7=Is>=p>7=$n>{=Is>=ѽ<=n<=<=ѽ<=-<=+0<ݝ=n<=ѽ<=+0<ݝ=Lo ==Yw>==z~>S==`x>Mn= y>= ~>=o;Lc=<8Ž=o;̽=<8Ž=>p%<̽=o;̽=T>8==#}>==J >==J >Lc==T>8==J >==>p%<==x<8==o;==x<8==o;Lc==o;==#}>̽=P> 9Ž=J >̽=P> 9Ž=J >Lc=J >̽=<Ĕ==÷< 8==9}{===ʎ==Sd==Kq>)I==nl>~$b==UGs>Sd==x>uP==Kq>)I==\6=Q|==")===A=.==Q=bx==")===\6=Q|== a5=l _>;<=љa>=e>a5=d>N=l _>;<=~[>#j=U>HY=CY>1N=XP>$G==LTK>]==XR>-==;_=e= U= =tgo=_=Q=D=n=P=Vc=6=;_=e=n=P=Q=D=<Y=ki=(m=<p=0>v=7>!9=ˆ4>t=t=2P===̫=====F=nű==Ƃ===l=ǵ==+Nh=r==Ƃ===F=nű==(O>==V>==R>/1h==n,>==|(>==o&>==: ,=la==A=.==")===7>!9=K;>2=ˆ4>t=7>!9=>>T=K;>2=È===Ԑ=㙜==Z=.==È===2=֨==Ԑ=㙜===̫==Ј=6======̫==2=֨==Ј=6==Q=bx==j=߉==")===Q=bx==3=Meu==j=߉==Kq>)I===i>-H==nl>~$b==Kq>)I==en>>3===i>-H==c>(Ψ=h>f=mh>x=`>ӵ=xd>⌜=J^>m=c>C==(_>A==29^>$==Ñ}= Q>Ο=W>H=o/S>}=O`L>/ɦ= Q>Ο=K><==<ګ==LoA{=h>f=-j>*=mh>x= m> =$n>{=.A<~==6<==÷< 8==K;>2=U7>|~=ˆ4>t=K;>2==>}½=U7>|~=Ј=6== =ʼn=====yG>P =gý=B>Db=&L>"=yG>P =CL>n=<Ĕ==.A<~==÷< 8== H=>[>K=J^>m=R>/1h==V>==[Y>j==V>==f7\>nu==[Y>j=====6<==.A<~==xd>⌜=`>ӵ=f>C="T=K=5W=C~=xl=L=-j>*=h>f=xd>⌜=h>f=c>(Ψ=xd>⌜=d==能="T=K=A>=ˡ="T=K= U= =A>=ˡ=w= ==j=߉=====j=߉==w= ==: ,=la==: ,=la==w= =="==ݰ==mh>x=h>f= m> =ٯn>A{= m> =h>f=`>ӵ=J^>m=>[>K=29^>$==(_>A==Y>Q=="T=K=d==能=5W=C~=~ =ɉ=s =f=O,< =~ =ɉ=O,< =ÑLc==J >==z~>S==J >=J >Lc= ~>=V>==]>?N==f7\>nu==o;Lc==x<8==XS<==x<8==ƵQp%<==<8Ž=o;Lc=2<=Q<ý=<8Ž=2<=<8Ž=Q<ý=>p%<̽=J >Lc=P> 9Ž= ~>=P> 9Ž={>ý= ~>={>ý=P> 9Ž=#}>̽=T>8==J >Lc==z~>S=={>==T>8==z~>S==T>8=={>==#}>=="==ݰ==A=.==: ,=la==^>̽=:a>jý=bd>̽=ݷn>&'=r>=t>C.=.A<~==<5=====w===K><===X==w===<ګ==K><==&9<ݴ==4q=q==\̽=k>̽=k>F=B]q>̽=p>7=pau>G=B]q>̽=k>F=p>7=<;L==1|=I[~>fY=v>i =J >S==G}>F==J >I==cS==~>lq[==J >k==J}>Լ=J > ŭ=J >k߼=J > ŭ=;}>l=J >>x=Rx>D=Ϫv>_˼=J}>Լ=5}===S===3===z======r====== =ʼn== ===2>̽=U7>|~=O+9>̽=gý=K>̽=0E>̽=&L>"=K>̽=gý=&L>"=gý=yG>P =i?>̽=gý=0E>̽=gý=i?>̽==>}½=B>Db=gý==>}½= ======ώ=== === =ʼn=====Ј=6==l=ǵ======== =ʼn==Ј=6========ώ=========Ƃ======l=ǵ==Ƃ===%R>==eO>q==K>==Z0X=̽=8c=ý=*q=̽=&L>"=%R>̽=K>̽=%R>̽=&L>"=R>t=&L>"=CL>n=R>t=+Nh=r==Pg===T=L==<&===W===C ===W===<&===:'===W====X==K><==q==R="=&9<ݴ=C ===UL<==<==C ===W===UL<==Lo<==W===K><==UL<==<=C =̽=<̽=R="=C =̽=<=<=n<=&9<ݴ=&9<ݴ=R="=<=bd>̽=k>F=k>̽=$n>{=k>F=mh>x=V>c鶽=R>t=o/S>}=^p>㥋=v>i =&u>0=&u>0=o>{=^p>㥋=W===:'====X===4q=<屗==9=ٯn>A{=_u>T.=5s>=z>=_u>T.=&u>0=#Uv>p==`n>(==s>@٬==#Uv>p==Y|>v==y>==\*<]=o;I=3)=`x>Mn=5s>=_u>T.=z>= y>=_u>T.= y>=5s>=Yw>==y> ==s>@٬==Y|>v==#Uv>p==y> ==y> ==#Uv>p==s>@٬==[<=[<=ɋ<͓=>6==>==L>{==7kf>;=fm>Չ;=kMi>(=d8%>fa&=UB!>i=g&>=><=><=X=<=>=ˎ<=X=<=><=`> =#>3=[=:=== >2==7>==9[=Ǽ=keM==)k===I;==c?<=\= <=>nϟ==>==>6==$>=k==,=_===`M==(>L=[/>pZ=)>3k=a>:<=|H\>4;=&Z>\<=p>Z=u>(=m> =پ=}n==!&=w==!=J==پ=}n===uS==,=_==,>={%>B7|=)>3k=Ĩ=+?=ia=B =槵=>Գ=3=s==h===e)=|5==A= H=l`8==رR=*ջ=A= H=keM==1==[=W;=-L=?9=oC=~;=l`8==-L=?9=رR=*ջ=!g>ϫ=`@a>ʼ==a>= =ɝ="=J(C=1==l`8=="=J(C=l=釻=Ĩ=+?=}=p=ia=B =:>==g?>Qp==@@>(ܦ===H.=l=xE=`===(8>_葽=Y2>̊=[7>hC=8>f=$>p=_>=>g=#>3=`> =WoE>@i<=9DK>$==LL><=8=s]==G=A===xF==w====E/===+==.E> ==WoE>@i<=m?>d ==WoE>@i<=.E> ==9DK>$==>=^ >!v2=>|=^ >!v2=>=R>,="=%== &=<=.=L!<=}K>,R==YQ>L==PM>9==X=<=&=3<=g=<=&=3<=X=<=>=ˎ<= >%=,#>=>t= >%=$>p={%>B7|=@u=Z==ݮ=q==-=Bm[==@u=Z==G=A==8=s]=={=8===~u==!=J=={=8==X=R==h===J=ۀ=p=:=$=y=L>{==h>^==>6==>T>꠼=RS>TI=6Z> |=U= ,=l=xE==H.==>:<==|<=>=ˎ<==P==e= ==e)=|5==e= ===P==[D="===:=<=t={<=?==<=t={<==:=<=#Lh=<=t3c=<=t={<=#Lh=<=#>3=0=ݶ=[=:=x91>==4>z==Q8>==A^>ĺ<=X>_<=Y><=Vr=ӎN===q2==1=D===q2==Vr=ӎN=== 6==$>=k===x===+==*>x==x91>==*2>h{==E>==R@>@==@@>(ܦ==u>(=q>=XZ=m> =>tT=$>p=8>f=>tT= >:=5L!>N.@=-k=ۇ<=3P=r<=#Lh=<=i>b?<=fm>Չ;=7kf>;=O>==yV>"==5R>t===G>Id=3iM>ֆ=F>ߩ=>T>꠼=3iM>ֆ=RS>TI=Z>d=>T>꠼=6Z> |=Z>d=`@a>ʼ=d[>5e=O>=H>r=]M>U,Ǽ=d5=<=3P=r<=k]8=ީ[<=d5=<=g=<=4=X<=3P=r<=T=^F<=k]8=ީ[<=T=^F<=3P=r<=-k=ۇ<=!=[|$=W=*===p=:=x>N=$=y=x>N=p=:=R>,="=}r= =ɝ=1===><&=UB!>i=d8%>fa&==><&= >:=>. =Mw>eKfK=x>4c=>&{<= >- <=P> <==b===˾<=S=+==g=<==˾<=4=X<=,=@ =====g=<=E><==}K>,R==PM>9==K=Y=={=W.=*+=>=>=>'v=+= =!=[|$===!=[|$=+= =6E7=e(=>u=r>u=_>===V=>==s=/L>'u*=HR>J=RS>TI=^&6>w==5>==:>===~u==j =>)r==8=s]==ݮ=q==a=#6s==-=Bm[== 4p=`@a>ʼ=!g>ϫ= 4p=Lk>=lf> =љa>=R4a>ް=lf> =`@a>ʼ=R4a>ް=d[>5e=茀=S?=xl=L=*=Az=>,w==[>\Lj== >ޔ}==v>=)o> =q>=XZ=xl=L=n=q=*=Az=n=q=xl=L=5W=C~=s5>Omi=<;>Fu=[7>hC=YA>)z=<;>Fu="@>dd=>T>꠼=0T>׼=]M>U,Ǽ=U>˾=0T>׼=d[>5e=1Z>^+==e]>)==yV>"==j><=Tn><=p>AK<=H>e:=J>;=Z>O>G:=3P=r<=8M=j<=#Lh=<=(J=T==8M=j<=4=X<=R@>@=={:>oa==@@>(ܦ=={:>oa==R@>@==Q8>==keM==Np?=>ɼ=1==Np?=>ɼ=keM==9[=Ǽ=o;.:=^9Չ;=%Rt>;=p>Z=1= ==(J=T==4=X<=O>==WH>!==PM>9==WH>!==O>==9DK>$==>u= >M=r>u=-=>/=(8>_葽=[7>hC=H>/=G>02=P'M>rM&=m=>>֝,=M>>PH=9>8=>=R>gg=j>lѼ==ud<=(n=,<=-k=ۇ<=ů=p<=g=<==Td<=><؁<=y=(9_<=/<#<= >Qf.==D >==K>f1==љa>=iwh>m =e>a5=iwh>m =љa>=lf> =M>>PH=KD>U="@>dd=ݮ=q==N= ==a=#6s==:=C==z=&==@:===yC>=^ >!v2=R>,=yC>=T>ό=? >~yj=k!H>BhG=H>^=KD>U=N.>=,>=Y2>̊=N.>=L/> Л=)>=[>g=U>˾=d[>5e===!b>p=W.=*+==Ko^==Vr=ӎN==f7=%k==,#>=>=>t=>=>='=n=V==!&=w===E/==n=V==z=&==:=C==A=|=+=|M===A=|=í=\zj==s=Qq9>W=M>>PH="@>dd=!&=w===O==!=J==X=R===O==:=C==@:======[D="==(>L=4">_=5L!>N.@=4">_=(>L=)>3k=,>='>'Ћ={%>B7|=,#>='>'Ћ=)>=u=" ==t={<=t3c=<= >:=>4=>. ==b==4=%==1= ==Tn><=չs><=p>AK<= =<=D!v2==>uC=>|= >4N==>uC='>R= >k===nU>11==>$Q== >h => "=>. = >- <=>0';=P> <=T>ό=>0';=? >~yj=Ϊ=F=====$=r>[=>L=8>f= >:=>L=>4=S>1==O>==PM>9==O>==S>1==yV>"==n$n<==>:<=>=ˎ<==>:<=">$n<=P> <=><=">$n<=>=ˎ<=0fv>%<=5cz><=FC|><=0fv>%<=p>AK<=չs><=5cz><=0fv>%<=չs><=p=:=k>۳=R>,=a>X{;=>=? >~yj=$H=j==(J=T==1= ==YA>)z=`C>G=-=>/=L>{=H>r=O>=L>{=P'M>rM&=G>02=H>r=L>{=G>02=rg>==b>:#==J@d>h3===E>>n=YA>)z="@>dd=u>(=y>cO=q>=XZ=y>cO=u>(=F}>ݻ= >P= >H=>X$=@>7= >H=`> =H>^=J>t==E>>n=&>uw=t )>6,='=&>uw=,#>=)>=}>L<=v><=FC|><= =(===CH==6=== =(==t={<=u=" ==ON_/;=>F=t>Ĕ3=>F=b* >_/;= >\Q=(J=T==]===t3c=<=]===(J=T==$H=j==O0>w=s5>Omi=[7>hC=O0>w=,>=)>3k=3=s== =h==h====~u== =h==j =>)r==Tn><=,p>h ==չs><=[>g%=љa>=l _>;<=X>4=[>g%=l _>;<=љa>=[>g%=[>g=_>E=J > =J >>x=;}>l=_>E=J >>x=J > =_>E=F}>ݻ=T=L==_L===+Nh=r=== > <=><=><=}>t==G}>F==J >S==m>/T=ms>j]=in>/o=x>4c=ms>j]=Mw>eh => =ߘ >] => = >h =>. =N-=)==@:===[D="==N-=)==X=R==:=C==@:===N-=)==:=C==$>=k==D={==,=_==D={==$>=k===+==x91>==.>==4>z====K=Y=W.=*+=>|]==-=Bm[==a=#6s==>,w==>|]==a=#6s==)o> =E(m>uͼ=!g>ϫ=(8>_葽=3>=Y2>̊=#W>,=U>˾=[>g==f=x>[k===d><=Xf><=)`>?<=Xf><=d><=j><=H>r=oB>=C>Sؼ=oB>=H>r=G>02=X=<= >@<=><= >@<=X=<=g=<=<ɿ=OND=r>[= >\Q=}>D= >'1=>4=lJ=4==Mg^=e],==$H=j==Mg^=e],==lJ=4==]`=E==->\=>F= >\Q=e]>)==*_>*==b>:#==*_>*==J@d>h3==b>:#==g?>Qp==6E>İ==@@>(ܦ==H>/=C>"=G>02=z=>x=C>"=m=>>֝,=eO>q==L>ȴ==(,H>[N==A>==g?>Qp==I;>4==r>[=k6 >kh= >\Q=k6 >kh=r>[=r>u=ݷn>&'=m>=r>=Lk>=m>=lf> =>,w==;`>NB==[>\Lj==;`>NB==>,w==a=#6s==w===y#="===E/== =ɝ=c_葽=9>ꖞ=3>=r >tӹ=~>˼= >/=~>˼=r >tӹ=>|=><؁<=׶<Ө<=ů=p<=׶<Ө<=><؁<="<<{<=YA>)z=3G>h=`C>G=3G>h=YA>)z==E>>n=|(>==_*>r==B&>Y==> E==x>{L==K>f1==_===iMK=H~==A=.==iMK=H~==\6=Q|==A=.==G=A===C1==n=Q"==Bkw>a==5cz><=չs><= > =!b>p===1-x>ֺ=u>(=p>Z=%z===w====X==w==m=B=k=3=:\=B=k=w==m=ia=B =)=}Z==w===Ԑ=㙜==2=֨==)=}Z==Ԑ=㙜==T=HR>J=/L>'u*=HR>J="/K>T=Z>O>G:=us=5;=[=W;=(n=,<=[=W;=us=5;=uj=<ǹ= >US== >d==>+i===G>Id=MG>f=/L>'u*=MG>f==G>Id=C>?@+=e]>)==`>==Y><=`>==e]>)==b>:#==S=== ;=C==r=== ;=C==z===r===P</==<==yKh>O=d>N=e>a5=??>S=(8>_葽=-=>/=,=@ ===y3$======y3$==,=@ ==n=Q"==Wd3=f==DzC=w==SH=#==AvC>/;=M>>PH=m=>>֝,=M>>PH=AvC>/;=KD>U=:'===l-=p===X==Wd3=f==l-=p==DzC=w==n=S==ݮ=q==j =>)r==i=X= n=/D=)=b?= n=/D=i=X=<Y=N>Zp|=`K>=J>t=rg>==ze>n==b>:#==E=(=Y#=,=U= ,==b==??%=JQ0==4=%==b=M]G==??%=JQ0== =X-==??%=JQ0===b== =X-===~==3=s==e)=|5==o;=O!<ν=2<=CW>z=HTR>Vp=U>HY=~[>#j=CW>z=U>HY=_===Y=t==iMK=H~==n3t<=Tn><=j><=[~h>3t<=c>#<=ze>n==x2>̂=7>!9=0>v=x2>̂=L/> Л=3>==еb==/=Sj==4=sU==\6=Q|==/=Sj==Q=bx==/=Sj===еb==Q=bx==:=L =="=%==.=L!<=:=L ==U=j===+== Y=6==s==H.==s= Y=6==W={>=Ϫv>_˼=v>=;}>l={>=v>=/F>Gu==R@>@==E>==R@>@==/F>Gu==C>===8P>?=K><=P'M>rM&=H>/=K><=k!H>BhG=K><=H>/=P'M>rM&=jZ>\=J^>m=$a>=J> ==LTK>]==XP>$G==LTK>]==J> ==E>==<9==n==!H>g==C>===q>6=Rx>D=r>= W>SA==}9X>nU==]>A=={=JW==e%ý=cX>̽=%R>̽=R>t=W>%ý=%R>̽=z=&===Dj==t=2P===Dj===̫==t=2P===Dj==z=&==y#="==p> <=p>E#p<=1u>y=<=p> <=fm>Չ;=i>b?<=p>E#p<=p> <=i>b?<=l˾=\Q>E=O>=\Q>E=U>˾=#W>,=5W=C~=RZ=@dk=n=q=RZ=@dk={k=E]=n=q==ud<=8=޹ <=(n=,<=8=޹ <==ud<=u={09<=o;S===\>4媽= ~>=\>4媽=J >= ~>=< l>P<=p>E#p<=i>b?<=Xf><=< l>P<=i>b?<=>==y> ==z~>S==J >==>==z~>S==o;=(<=o;[!=(<=o;=:gR0==J@d>h3===i>-H==en>>3==i>R0===i>-H==J@d>h3==i>R0==rg>==Tn><=2j>x==,p>h ==rg>==2j>x==ze>n==|G>aj=hK>uw=O`L>/ɦ=F>ի=|G>aj=O`L>/ɦ=6?===nS8===<&===nS8===:'===<&===p="==zJ=+==ؤ=g==HTR>Vp=S>i=N>Zp|=+=|M=ճ=4===ճ=4=+=|M=煺==>3#===ʎ==L>{==>3#==>==[>\Lj==>==>3#==L>{==en>>3==l>==i>R0==l>==rg>==i>R0==T>Q崼=>ᬼ=>ּ=^&6>w==x/>,==5>==!b>p=~=Yv=W.=*+= O>=`K>=N>Zp|=hK>uw=TO>ĕ= Q>Ο=My><4=Mw>eC.=My><4= ~>-=}>KfK=Mw>e<4=}>KfK=Ub>ܮf==/a>$w==uUf>Ny==]>?N==/a>$w==f7\>nu==6>=¹==T=L==DzC=w== >h =f> =>=f> =~>˼=>=f> = >h =ߘ >] =>[>K=Y>G=`>ӵ=3R}> /=&u>0=v>i =I[~>fY=>}> /=v>i =f\=D==5x=w==Bo======w===%z===w======<5==mh>x=Ic>/ =$a>=Ā= =v= ==Mf=v= =Ā= =.=*=W>H=T>;6=>[>K=T>;6=W>H= Q>Ο=gG=>i==\6=Q|==iMK=H~== =5?==-=Bm[==> E==G}>F==t~>v==J >I==U7>|~=2>=ˆ4>t=2>=U7>|~=2>̽=I>=`K>= O>=M>-[=H>^=k!H>BhG=".>N2=0>v=ˆ4>t=׃=)=z=ן=hX=*=nl>~$b==j>Cu==n>z==5W=C~=j_F=m=RZ=@dk=Ub>ܮf==Hg>ѻa==Rb>VQ==Hg>ѻa===i>-H==Rb>VQ==P'M>rM&="iS>*-=8P>?=X>4="iS>*-=#W>,=o<2O=^<de=<@Q=# >$A== >Qf.==K>f1==# >$A== >US==>$Q==m z>=r>=Rx>D=J >=$~>=J >k߼=$~>=J}>Լ=J >k߼=]>a==Rb>VQ==]>A==Rb>VQ==]>a==Ub>ܮf==^=*=r=̽==̽=S=P==T=L==Pg===Wa=`==lw=#Q==]`=E==J >[!=Z>a=J >=Z>a=J >[!= ~>-=>=f4>Nƻ=? >~yj=f4>Nƻ=>='>R=c=+==t=2P=====7k>;=e>a5=iwh>m =ݷn>&'=7k>;=iwh>m =p5====4=sU==A(K=вN==lJ=4==p5====A(K=вN==t )>6,=,>I=0>v=L/> Л=,>I=)>=Ϫv>_˼=r>y=v>=)o> =r>y=E(m>uͼ=r>y=)o> =v>=xl=L=Km=="T=K=tgo=_=Km===q=o;J=O!<ν=o;=J >J==>==J >==\>4媽=J >J=J >=%z===l-=p==Wd3=f==%z====X==l-=p==XR>-==LTK>]==L>ȴ==m>=q>6=r>=m>=Lk>=q>6=f<˘===jZ>\=$a>=V>c鶽=jZ>\= j]>=(<=nu<&%=o;[!=>Kh>O=e>a5=7k>;="iS>*-=oU>HZA=8P>?=X>4=oU>HZA="iS>*-=gG=>i==A(K=вN==4=sU== ]t=R)==MkZ===Bo=== ]t=R)==_===MkZ===Km== U= ="T=K=tgo=_= U= =Km==/=Sj==gG=>i==4=sU==/=Sj==\6=Q|==gG=>i==x2>̂=,>I=L/> Л=x2>̂=0>v=,>I=Ic>/ = j]>=$a>=:a>jý= j]>=Ic>/ = Q>Ο=TO>ĕ=T>;6=O!<ν=z<í=2<=ki=(m=i=X=V,=Mo=J >k==}>t==J >S==J >k==~>lq[==}>t==f7\>nu==]>a==[Y>j==[<=bʿv==J >&==J >I=====c=+========z===c=+==2>=".>N2=ˆ4>t=======<5==w= ========g=<==== >@<=`=|=jG=_=í=\zj==}==$dؼ=>֓==$dؼ=t=HV==j=t=HV==}=E=(=t=HV==$dؼ==}==ټ=== =3o=/=====ټ=yN=>=>ᬼ=R>,=>=x>N=yC>=k>۳=T>ό=yC>=R>,=k>۳=:=L ==/= <=U=j==.=L!<=/= <=:=L ==>&{<=">$n<=><=>&{<=P> <=">$n<==`M===Ko^==$>=k===q2==gW=YO===CH=== 6==gW=YO===q2==@k>+w=q>=XZ=)o> =@k>+w=m> =q>=XZ=p>Z=kMi>(=fm>Չ;=m> =kMi>(=p>Z=5F=Ks?=4Z=Yl;=T=EU==H.=1= = Y=6==H.=`===1= =gW=YO==6====CH==V=>=煺==+=|M==s=t=HV=E=(==W=t=HV==s==*W==s=í=\zj= >%=_>=$>p=>t=_>= >%=>'v=>=>=>'v=>t=>===ia=B =w==m===槵=>Գ=ia=B =Qq9>W=9>8=M>>PH=3>wG=9>8=Qq9>W=>X$=>= >P=>X$=>'v=>=A>[y=C>?@+==G>Id=A>[y=?"?>) =C>?@+====%Ӽ= =3o=Y>=RS>TI=HR>J=Y>=6Z> |=RS>TI=Z>d==a>=`@a>ʼ=6Z> |==a>=Z>d=ёD>Жv==C>===!H>g==ёD>Жv=='>>L&==C>=== =(==?==<=t={<=6===?==<= =(==i>b?<=a>:<=Xf><=7kf>;=a>:<=i>b?<=>+i==>$Q== >US==>+i==>̤j==>$Q==oC=~;=T=^F<=[=W;=oC=~;=k]8=ީ[<=T=^F<=d5=<==Td<=g=<=k]8=ީ[<==Td<=d5=<==><&=5L!>N.@= >:=d8%>fa&=5L!>N.@==><&=5R>t==9DK>$==O>==5R>t==LL><=9DK>$==^X=<=u={09<==ud<=}=p==Mf=v= =m=>>֝,=(l7>=z=>x=9>8=(l7>=m=>>֝,=Y><=yV>"==e]>)==Y><=5R>t==yV>"==a>X{;=>0';= >- <=? >~yj=>0';=a>X{;=yC>=f4>Nƻ=^ >!v2=yC>=? >~yj=f4>Nƻ=]>]:=1V>̮:=|H\>4;=uj=<ǹ=-L=?9=[=W;=رR=*ջ=-L=?9=uj=<ǹ=x<>'l=oB>=z=>x=x<>'l=C>Sؼ=oB>=F>ߩ=A>[y==G>Id=Z>O>G:=1V>̮:=HR>J=Z>O>G:=S>;=1V>̮:= >ޔ}== >d==>,w== >ޔ}==>+i== >d==Ϊ=F=0=ݶ===[=:=0=ݶ=Ϊ=F=a>:<=)`>?<=Xf><=&Z>\<=)`>?<=a>:<=k>\#==>==>N ==ճ=4===w==m=煺====ճ=4=# >$A==x>{L== >US==K>f1==x>{L==# >$A=== > <=>&{<=><=d><=A^>ĺ<=c>#<=)`>?<=A^>ĺ<=d><=O0>w=Y2>̊=,>=[7>hC=Y2>̊=O0>w=-=>/=<;>Fu=YA>)z=[7>hC=<;>Fu=-=>/=>=r >tӹ=>ᬼ=>=>|=r >tӹ=p> <=%Rt>;=fm>Չ;=1u>y=<=%Rt>;=p> <=%Rt>;=z>M<=#z>`;=1u>y=<=z>M<=%Rt>;=v><=><= >@<== > <=v><=F><=><=v><== > <=*>x==&> v==3#>5==]M>U,Ǽ=3iM>ֆ=>T>꠼=]M>U,Ǽ=F>ߩ=3iM>ֆ=>r!=> = >'1=>r!=ߘ >] => =f> = >/=~>˼=ߘ >] = >/=f> ==P==h===X=R==e)=|5==h====P===~== = ===Л==e)=|5== = ===~==&=3<==|<=Z7=J<=&=3<=>=ˎ<==|<=A= H="=J(C=l`8==A= H=1=="=J(C="=}r=Np?=>ɼ=-=="=}r=1==Np?=>ɼ=5=)="=J(C= =ɝ=l=釻="=J(C=5=)=>g= >H= >P=>g=`> = >H=xk>== >ޔ}==[>\Lj=={=8===O==X=R=={=8==!=J===O==پ=}n==׫=^===uS===:=<=^X=<==ud<=?==<=^X=<==:=<=D={===E/==!&=w===+===E/==D={==8=s]==׫=^===~u==8=s]===xF==׫=^==4">_={%>B7|=$>p=)>3k={%>B7|=4">_=O0>w=[/>pZ=s5>Omi=)>3k=[/>pZ=O0>w=Np?=>ɼ=P>A=a=-==!g>ϫ=@k>+w=)o> =:>=={:>oa==^&6>w==:>==@@>(ܦ=={:>oa==E>==6E>İ==LTK>]==E>==@@>(ܦ==6E>İ==O>=0T>׼=U>˾=O>=]M>U,Ǽ=0T>׼=T>Q崼=x>N=>ᬼ=$=y=x>N=T>Q崼=J=ۀ==j=fd=o=J=ۀ=$=y==j=3=:\=ճ=4=w==m=3=:\===ճ=4=A=|=`=|=í=\zj===`=|=A=|=H>e:=Z>O>G:="/K>T=.=*=B=k=v= =.=*=3=:\=B=k=S>1==YQ>L== W>SA==PM>9==YQ>L==S>1==E><==PM>9==WH>!==پ=}n==D={==!&=w==پ=}n==,=_==D={==A=|=V=>=+=|M==s=V=>=A=|=N-=)===P==X=R==[D="===P==N-=)==1-x>ֺ=%Rt>;=#z>`;=p>Z=%Rt>;=1-x>ֺ=ů=p<=y=(9_<=><؁<=ů=p<==Td<=y=(9_<=+= =@t=v=-==+= ===@t=v=f=r>u=r>[=8>f=_>=r>u= >%='>'Ћ=,#>= >%={%>B7|='>'Ћ=B=k=}=p=v= =ia=B =}=p=B=k={:>oa==4>z==^&6>w==Q8>==4>z=={:>oa=={=8== =h===~u=={=8==h=== =h==-k=ۇ<==:=<==ud<=#Lh=<==:=<=-k=ۇ<=t3c=<=8M=j<=(J=T==t3c=<=#Lh=<=8M=j<=5>2<=S>p<= >z^#<=5>2<= >v{<=S>p<=E><== E>uY==}K>,R==fq@>8L== E>uY==E><== 4p=E(m>uͼ=Lk>= 4p=!g>ϫ=E(m>uͼ=0fv>%<=v><=p>AK<=0fv>%<=FC|><=v><=P>A=a=+= =-==U= ,==s=E=(=U= ,==H.==s=N.>=3>=L/> Л=N.>=Y2>̊=3>=>tT=>L= >:=>tT=8>f=>L===={==f=W.=*+=={===9DK>$==.E> ==WH>!==@u=Z==j =>)r==ݮ=q==@u=Z==8=s]==j =>)r==r >tӹ=>ּ=>ᬼ= >/=>ּ=r >tӹ=n=V==y#="==z=&==n=V===E/==y#="== >M= > =>uq=b* >_/;=>r!= >'1=t>Ĕ3=>r!=b* >_/;=hX=*=Ā= =׃=)=hX=*=.=*=Ā= = =<==˾<=g=<=S=+===˾<= =<=*<==f<˘==n'u*=3iM>ֆ==G>Id=RS>TI=3iM>ֆ=/L>'u*=A^>ĺ<=`>==c>#<=A^>ĺ<=Y><=`>==$A==nU>11== >Qf.==>$Q==nU>11==# >$A===q=z=ן=tgo=_==q=hX=*=z=ן==Ko^===x==$>=k==f7=%k===x===Ko^== 4p=R4a>ް=`@a>ʼ= 4p=lf> =R4a>ް=iwh>m =m>=ݷn>&'=iwh>m =lf> =m>=>tT=4">_=$>p=>tT=5L!>N.@=4">_=茀=S?=Km==xl=L=茀=S?==q=Km==v>=y>cO=;}>l=v>=q>=XZ=y>cO=T=^F<=(n=,<=[=W;=T=^F<=-k=ۇ<=(n=,<=}>D=b* >_/;= >'1= >\Q=b* >_/;=}>D=->\=k6 >kh=>uq=->\= >\Q=k6 >kh=[>g=R4a>ް=љa>=d[>5e=R4a>ް=[>g=Z>d=0T>׼=>T>꠼=Z>d=d[>5e=0T>׼=o<2O=^9^+==S>1== W>SA==1Z>^+==yV>"==S>1==1= ===˾<==b==4=X<==˾<=1= ==d5=<=8M=j<=3P=r<=d5=<=4=X<=8M=j<=L>{=\Q>E=P'M>rM&=L>{=O>=\Q>E=6E>İ==L>ȴ==LTK>]==6E>İ==(,H>[N==L>ȴ==f4>Nƻ==>uC=^ >!v2='>R==>uC=f4>Nƻ=> =>4= >'1=>. =>4=> ==><&=> "=UB!>i==><&=>. => "=Y#=,==/=e=<=~=¼=@t=v=ON>n=KD>U=H>^="@>dd=KD>U==E>>n=Qq9>W=<;>Fu=s5>Omi="@>dd=<;>Fu=Qq9>W=A>),======y3$==y>cO=_>E=;}>l=y>cO=F}>ݻ=_>E=k6 >kh= >M=>uq=r>u= >M=k6 >kh=n=S==N= ==ݮ=q=="/K>T=/L>'u*=MG>f=5=)=cԴ==5>==x/>,==n=S== =h==3=s==j =>)r== =h==n=S==1Z>^+==*_>*==e]>)==:guw=>=,#>='=>=&>uw=;`>NB==N= ===ʎ==a=#6s==N= ==;`>NB==>3#==;`>NB===ʎ==[>\Lj==;`>NB==>3#==[~h>3t<=d><=c>#<=j><=d><=[~h>3t<= =X-==U<o1=== B== =X-==*<==U<o1==$H=j==4=%==lJ=4==1= ==4=%==$H=j==??>S=`C>G=E>җ=-=>/=`C>G=??>S=oB>=C>"=z=>x=oB>=G>02=C>"=AvC>/;=C>"=H>/=m=>>֝,=C>"=AvC>/;=&>uw=,>I=t )>6,=&>uw=)>=,>I=N.>='>'Ћ=,>=N.>=)>='>'Ћ=us=5;=(n=,<=8=޹ <= =<=׶<Ө<=DBhG=AvC>/;=H>/=k!H>BhG=KD>U=AvC>/;=n=V===O==!&=w==n=V==:=C===O==M>-[=K><=8P>?=k!H>BhG=K><=M>-[=#W>,=[>g%=X>4=[>g=[>g%=#W>,= > =x>[k=>uq===x>[k= > =n=Q"===C1===y3$==x>{L== >d== >US== =(==ؤ=g===CH==u=" ==ؤ=g== =(==}>D=>L=r>[=>4=>L=}>D=չs><=,p>h ==Bkw>a==)=b?= n=/D=]o=3=]===Mg^=e],==p="==]===$H=j==Mg^=e],==`>==ze>n==c>#<=`>==b>:#==ze>n==U> ==|;Z>;== /\>==3G>h=J>t=`K>==E>>n=J>t=3G>h=nS8===6>=¹==:'===Ā= =h=+e=׃=)==Mf=h=+e=Ā= =C>===/F>Gu==I>==x>[k=->\=>uq=x2>̂=9>ꖞ=7>!9=3>=9>ꖞ=x2>̂=\Q>E="iS>*-=P'M>rM&=\Q>E=#W>,="iS>*-=B> +=??>S=E>җ=1<#'=j$i==>j=cu==Y=t==_======q%=8O==w= ==%z===q%=8O=====6>=¹==l-=p==:'===DzC=w==l-=p==6>=¹==o<2O=<`=^<de=N>Zp|=S>i= O>=ȸW> ==T>+==Y>Q==[~h>3t<=2j>x==Tn><=[~h>3t<=ze>n==2j>x==]>a==}9X>nU==[Y>j==]>A==}9X>nU==]>a=={=JW==p=TO>ĕ=hK>uw= O>=TO>ĕ=I>=Z>a=$~>=J >=/a>$w==]>a==f7\>nu==/a>$w==Ub>ܮf==]>a==8> ԡ;==.)==0;=p=:=W=`r=k>۳=o;S=o;.:=<@Q=T>ό=k>۳==.)=W=`r==.)=k>۳="=%==n=Q"==,=@ ==:=L ==n=Q"=="=%===+==n=Q"==:=L ==,=@ == &=<="=%== &=<=,=@ ==g=<= &=<=g=<=&=3<=S=P==SH=#==DzC=w==T=L==S=P==DzC=w== }=.=n=P=׃=)=n=P= }=.=Vc=6=׃=)=h=+e= }=.=8> ԡ;==>:<=P> <=8> ԡ;=>0';=T>ό=8> ԡ;=T>ό==.)=P> <=>0';=8> ԡ;=q>6=Ϫv>_˼=Rx>D=q>6=r>y=Ϫv>_˼=q>6=Lk>=E(m>uͼ=q>6=E(m>uͼ=r>y=>=a>X{;=>a=u:= >z^#<=a>X{;= >- <= >z^#<=>̵;=a>X{;=>̵;=>a=u:=a>X{;= >- <=5>2<= >z^#<= >- <=>&{<=5>2<=5>2<=>&{<== > <=F u>[w=z>|=v>i =Z0X=̽=6?=̽=C{L=H=8c=ý=Z0X=̽=C{L=H=Y|>v==t~>v==y>==t~>v==G}>F==y>==d>N=>Kh>O=Ue>\Sa=m>/T=Ki>Kb=>Kh>O=Ki>Kb=Ue>\Sa=>Kh>O=Cp>ZjA=Mw>ej]=m>/T=Cp>ZjA=ms>j]=>|]==> E==-=Bm[==x>{L==> E==>|]==>,w== >d==>|]==x>{L==>|]== >d==~>dg=J >S=J >k=J >S=~>dg=}>KfK=~>dg=x>4c=}>KfK=F}>ݻ=J > F=J > =u>(=1-x>ֺ=F}>ݻ=o;k߼=:gr<=J >==J >k<=======|===6=W========槵=>Գ=^=*=Ĩ=+?=0E>==A>==i?>==_L===6?===Z0X===nS8===6?===_L===T=L==6>=¹==_L===nS8===_L===6>=¹===$dؼ==j=$=y=T>Q崼==$dؼ=$=y=>֓==$dؼ=>ּ==$dؼ=T>Q崼=>ּ=o; ŭ<= <Ȝ<=o;>x<=]>?N==(_>A==/c>Ն==/c>Ն==/a>$w==]>?N==/c>Ն==uUf>Ny==/a>$w==P(==R="=q==>F=->\=Eo=K=Eo=K==f==cP=x>[k==f=Eo=K=x>[k=Eo=K=->\=Hg>ѻa==nl>~$b===i>-H==j>Cu==nl>~$b==Hg>ѻa==Hg>ѻa==Ub>ܮf==uUf>Ny==uUf>Ny==j>Cu==Hg>ѻa===Л==h>^==L>{==F u>[w=in>/o=ms>j]=x>4c=F u>[w=ms>j]=F u>[w=x>4c=z>|=~>dg=z>|=x>4c=_>`Z=CY>1N=l _>;<=CY>1N=_>`Z=~[>#j=d>N=_>`Z=l _>;<=in>/o=Ki>Kb=m>/T=~>˼=R>gg=>=R>gg=~>˼=>|=R>gg==>uC= >4N=R>gg=>|==>uC=r >}==D >== >Qf.==nU>11==r >}== >Qf.==nU>11== >k===k>\#==m z>=t>C.=r>=m z>=My><4=t>C.= ~>-=My><4=m z>=m z>=Z>a= ~>-=cg==I>==(O>==hP>,==(O>==I>==t~>v==Y|>v==J >&==}9X>nU== W>SA==YQ>L==R>/1h==}9X>nU==YQ>L==}9X>nU==R>/1h==[Y>j==J >I=I[~>fY=J >S=XR>-==L>ȴ==U> ==L>ȴ==eO>q==U> ==s =f=%=:|==9=6.!=(߄=%=:|=s =f=< l>P<=Xf><=j><=p>AK<=< l>P<=j><=< l>P<=p>AK<=p>E#p<=v><=p>E#p<=p>AK<=p>E#p<=v><=1u>y=<=F=Eo=K=t>Ĕ3=e=<=Eo=K==cP==/=Eo=K=e=<==/=t>Ĕ3=Eo=K=Vc=6=8c=ý=C{L=H="<<{<=*=xd>⌜=f>C= >4N='>R=$>م=u>qi:='>R=>a=u:='>R=>=>a=u:=J >.:=J >S=}>KfK= ~>-=J >.:=}>KfK=A>),==> E==K>f1==o;&=o;I=\*<]=w===)=}Z==y#="===̫==)=}Z==2=֨===Dj==)=}Z===̫==y#="==)=}Z===Dj==t>C.=Cp>ZjA=ݷn>&'=Cp>ZjA=t>C.=Mw>euY==lK>8k==}K>,R==!H>g==lK>8k==ёD>Жv==lK>8k==!H>g==(O>== E>uY==ёD>Жv==lK>8k==p5====lJ=4==4=%==p5====b=M]G==4=sU==p5====??%=JQ0==b=M]G==4=%==??%=JQ0==p5====jZ>\=W>H=J^>m=W>H=jZ>\=o/S>}=jZ>\=V>c鶽=o/S>}=cX>==^>==|;Z>;==j_F=m=V,=Mo=w8=sW=d==能=j_F=m=5W=C~=n>z==wev>==UGs>Sd==p˶==^^>== /\>==B> +=F>ի=>>T=|G>aj=F>ի=B> +=z^=M>-[=J>t=U>HY=M>-[=8P>?=HTR>Vp=M>-[=U>HY=J>t=M>-[=N>Zp|=N>Zp|=M>-[=HTR>Vp=Ue>\Sa=_>`Z=d>N=R>/1h==lK>8k==(O>==^>̽= j]>=:a>jý=)=b?=w8=sW=i=X=w8=sW=)=b?=5F=Ks?=6E7=e(=)=b?=!=[|$=6E7=e(=5F=Ks?=)=b?=".>N2=t )>6,=0>v=t )>6,=".>N2=(>'ڹ=l=3G>h=`K>=I>=E>җ=`C>G=E>җ=I>=hK>uw=`C>G=3G>h=I>=Ic>/ =bd>̽=:a>jý=bd>̽=Ic>/ =k>F=Ic>/ =mh>x=k>F= <Ȝ<=o; ŭ<=Ǚ#<<=Y>G=>[>K=T>;6==4q=÷<5M=<屗=÷<5M=\=> "= >h => "=>=j>lѼ=z=k===^==lw=#Q==Wt=x===^==z=k==Vr=ӎN===^==f7=%k==f7=%k===^==Wt=x==5}=== ;=C==S=== ;=C==5}====== ;=C==c=+==z======c=+== ;=C==Wa=`==z=k==lw=#Q==>j=cu==z=k==Wa=`==Wa=`==Y=t==>j=cu==j$}> /=z>=&u>0=J >I=>}> /=I[~>fY=J >I=J >&=>}> /=d==能=6.!=(߄=V,=Mo=6.!=(߄=ki=(m=V,=Mo=j_F=m=d==能=V,=Mo=V>,==T>+==XP>$G==Y>Q==T>+==V>,==><؁<=Er<-p<="<<{<=(8>_葽=??>S=9>ꖞ=9>ꖞ=>>T=7>!9= y>=z>=\>4媽==ʎ===~===Л===ʎ==N= ===~==n=S==3=s===~===~==N= ==n=S==>==J >J==J >&==Y|>v==y> ==>==J >&==Y|>v==>==G}>F==}>t==wev>==^>̽=cX>̽=W>%ý=V>c鶽=W>%ý=R>t=V>c鶽= j]>=W>%ý=^>̽=W>%ý= j]>=x/>,==^&6>w==4>z==4>z==.>==x/>,==MkZ===A=.=="==ݰ==A=.==MkZ===_===@>7=~=Yv=!b>p=`> =~=Yv=@>7=`> =[=:=~=Yv=!b>p=>=@>7=!b>p= > =>= >M=>= > =l>==2j>x==rg>==l>==,p>h ==2j>x==J}>Լ=$~>=Rx>D=Rx>D=$~>=m z>=CW>z=S>i=HTR>Vp=CW>z=Y>G=S>i=J@d>h3==]>A==Rb>VQ==J@d>h3==*_>*==]>A==1Z>^+== W>SA==]>A==1Z>^+==]>A==*_>*=={>=;}>l=J > ŭ={>=J}>Լ=Ϫv>_˼=J}>Լ={>=J > ŭ=<.=|=H= ==̽=f=:l=^=*=槵=>Գ=G=^=Oo='=E,=ا=Oo='=G=^=[=_=5)=e==Ӥ}=*=Az==7=y=i/= =j=w >ҷ=*:">ܼ=j>lѼ=*=X=*=Az==Ӥ}=*=Az=*=X=茀=S?=fd=o="=F T=J=ۀ=v==G=^=f=:l=G=^=v==[=_=槵=>Գ=v==f=:l=-= ==7= =j= &=<=]=qS<=.=L!<=~:>;Qr=?"?>) =A>[y==7=›=J=7ޑ=O=%>:@=*:">ܼ=w >ҷ=H= <=/<#<=y=(9_<=;==o>ǩ==>==t=HV==0˼==j==0˼=t=HV==W==0˼=fd=o==j=*>P=3+>K5ȼ=%>:@=w==*q=̽=8c=ý=f/>6==Ui->==c(>1==>g=>緽=#>3= >½=>緽=v>xD=>緽=>g=v>xD=;='=0=ݶ=#>3=;='= >½=H= =0=ݶ=;='=H= =O=3==7=-= ==7=O=3=›=J=e===D===e==<.=|=E,=ا=>{L=>u= >ý=h>^==uJ>==>6==uJ>==h>^==g<>y==v= C==7=7ޑ=O==7=v= C=y=i/=>i==D >==r >}==D >==>i==v><====2=D=== ==={==Ic==f==Ic=={==-p=FM>L<=J>;=I>U<=b&=`O=$T=c={k=E]=*=Az=$T=c=5)=e=p=<=/= <=.=L!<==<=/= <=p=<=J=ւ+=4Z=Yl;=5F=Ks?=6E7=e(=J=ւ+=5F=Ks?=4Z=Yl;=J=ւ+=n,\="=/2>w3=1>x=(l7>=9>8=/2>w3=(l7>=gW=YO==s===6===@>L<=_Z>>K0<=9>u<=@>L<=շA>,b<=\BF><=f/>6== 5>>%==Ui->==f$>=g&>=UB!>i=$>م= >Q= >4N=:@>><=m?>d ==WoE>@i<====,=Qܵ==2=D==,=Qܵ=====6=W==*:">ܼ=I(>K=f$>=I(>K=*:">ܼ=%>:@=sJ#>Ǽ==|(>==B&>Y==&F><=WoE>@i<=LL><=&F><=\BF><=շA>,b<=-q=--=4Z=Yl;=n,\="=5)=e=⛈=U=7ޑ=O=b&=`O=⛈=U=$T=c=⛈=U=5)=e=$T=c=>=>N= >P=_*>r==!*>s==B&>Y==!*>s==#>٪==B&>Y== R(7=R.>=3+>K5ȼ=Ê>'=> "=j>lѼ=> "=Ê>'=UB!>i=*:">ܼ=Ê>'=j>lѼ=f/>6==OT/>L==6>A==K=Y===={====-p=={===K=Y=zu=,==}=u=)=t=R.=>L==A>),==K>f1==D >==>L==K>f1==,>"<= >v{<=F><= >v{<=,>"<=><=K=Y=(==zu=,=}= ==e= ==[D="==}= === ==2=D==e= ==}= ==2=D==]&=B>`=>}> /=z>=B>`=\>4媽=B>`=z>=>}> /=]N>\==YQ>L==}K>,R==]N>\==lK>8k==R>/1h==lK>8k==]N>\==}K>,R==YQ>L==]N>\==R>/1h==`>Q== >k===>$Q==>̤j==`>Q==>$Q==׈= =E=G===l=F={k=E]=T=EU=4Z=Yl;=l=F=T=EU=%R>==$WT>==eO>q==U> ==$WT>==|;Z>;==$WT>==U> ==eO>q==t= o==̢===U=j==/= <=t= o==U=j==H>D====>==?y>f==x>uP==UGs>Sd==wev>==?y>f==UGs>Sd==+Y=G =s=Ϝ=n,\="=_>`Z=ݐa>ƫq=~[>#j=ݐa>ƫq=_>`Z=Ue>\Sa=X>_<=S>R<=Y><=S>R<=5R>t==Y><=QP>?ׄ<=O>y<=R>gg=If>M=j>lѼ=If>M=R>gg= >4N= 0<^=⋽=>u=_>=>t= >⋽=_>=1>+==*>0 ==Ui->==Z=vJ=zu=,=(==kMi>(=nc>=7kf>;=nc>=kMi>(=Ld>n=̟>r<=pz>r<=J >k<=?==<=ݗ=<=^X=<=fd=o= !=EL=E=څ=W=`r==| =8=\.=3#>5==%>==*>x==9>ꖞ=>>z=>>T=B> +=>>z=??>S=>>z=B> +=>>T=>>z=9>ꖞ=??>S=Z7=J<=d=<=&=3<= &=<=d=<=]=qS<=d=<= &=<=&=3<=U= ,==>?=l=xE==>?=(=Q=l=xE== ===$====D== ====i=0=ݶ=H= =0=ݶ==i===<.=|==i=H= =WV>y;=!>ҽ:=u>qi:= ='{Ļ=={:=t=R.=M=<=0r=fØ<==<=0r=fØ<=M=<=]=qS<=J>;=J]O>X<=Z>O>G:=J]O>X<=S>;=Z>O>G:=J]O>X<=J>;=FM>L<==kx=t=R.=={:=t=R.==kx==}=e====s"==M=<==<=p=<=ib>˶==u h><==Ch>ʯ==QU >ǖ=>X$= >H=>X$=QU >ǖ=>f= = ==i====Л==i===h>^===Л==4>#==>==1 >==`=|==or=jG=_=5)=e==or==Ӥ}=D>MΑ;=J>;=H>e:= ?= ==0=ݵ=W=^p>㥋=I"q> =v>i =I"q> =F u>[w=v>i =I"q> =^p>㥋=? k>=R@>@=="=>%==Q8>=='>>L&=="=>%==C>==="=>%==R@>@==C>===/<#<= <ݨ5<=><؁<= <ݨ5<=Er<-p<=><؁<= <ݨ5<=/<#<= Rݻ=A~>} 9=J > F=A~>} 9=F}>ݻ=1-x>ֺ=V>,==xEY>==29^>$==xEY>==^^>==29^>$== >k===*>3j;==k>\#==*>3j;==F!>w)==k>\#==Y><=X>_<=A^>ĺ<=)`>?<=Y><=A^>ĺ<=&>!==c(>1==Ui->==*>0 ==&>!==Ui->==*>x==)>j==x91>==)>j==.>==x91>==>g=e >*=v>xD=e >*=>g= >P==:P\==-p==Xh=.>pZ=(>L= ,>1=.>L=[/>pZ=.>wG==<=^X=<=ݗ=<=g;>:<=շA>,b<=@>L<=9>u<=g;>:<=@>L<=QP>?ׄ<=^S>7<=FM>L<=>g|== >ޔ}==xk>== 0<^=w)==ĺ#>:==c(>1==N>c>&==^^>==ib>˶==Ch>ʯ==N>c>&==ib>˶==K=zV=Rm=aF=uj=<ǹ=Rm=aF=رR=*ջ=uj=<ǹ=J>;=l D> ,+<=I>U<=o;k==:A=a=F=<=+= ==}=+==E=(=Y#=,=+===/=+==Y#=,=E=(=\BF><= L>GI<=I>U<=QP>?ׄ<= L>GI<=O>je== E>uY==fq@>8L==ib>˶==^`>:==u h><==^`>:==bd>==u h><== >/=&>V=>ּ=&>V= >/=ߘ >] ==a>=e>}=!g>ϫ=@k>+w=e>}=zg>#=e>}=@k>+w=!g>ϫ=>8z=J >S=I[~>fY=>8z=z>|=~>dg=z>|=>8z=I[~>fY=C>Sؼ=[>>z=F>ߩ=[>>z=A>[y=F>ߩ=[>>z=C>Sؼ=;>Q̼=/J>_<=\BF><=&F><=/J>_<=LL><=O><=/J>_<=&F><=>u= >ь= >M= >ь=>= >M= >ь=>u=>f=EU>8<=QP>?ׄ<=IT>y<=EU>8<=&Z>\<=^S>7<=QP>?ׄ<=EU>8<=^S>7<=VEa>+;=|H\>4;=a>:<=7kf>;=VEa>+;=a>:<=|H\>4;=VEa>+;=]>]:==\'==q=茀=S?=fq@>8L==ş:>U==z?>je==o<'=Uv<㛵=yx<= <Ȝ<=11=_=d==能=A>=ˡ=11=_=%=:|=6.!=(߄=%=:|=11=_=A>=ˡ=d==能=11=_=6.!=(߄=L4>X=s5>Omi=[/>pZ=L4>X=3>wG=Qq9>W=3>wG=L4>X=[/>pZ=s5>Omi=L4>X=Qq9>W=>=="><=><=0U=QW<==c?<==I;==c?<=0U=QW<=ĸ=:T<=z=k==z=cԂ==Wt=x==z=cԂ==z=k==>j=cu==,=_==q=vB===`M==q=vB==,=_===uS==-L=?9=+1=x:=oC=~;=+1=x:=-L=?9=l`8==Uv<㛵=Ȑ<*κ=y<=25><=W4>3<=a:><=m?>d ==:@>><=F4=s=⣊=;Lq9=)=R:=⣊=;Lq9=F4=s=K=zV==/= >Gj=t>Ĕ3=FW>;=|H\>4;=1V>̮:=S>;=FW>;=1V>̮:=|H\>4;=FW>;=&Z>\<=j*>=g&>=f$>=T > ==>==k>\#==>==T > ==i#> ==F!>w)==T > ==k>\#==C=W=`===l=xE=`===C=W=s=b0O=H> м=H>r=C>Sؼ=H> м=F>ߩ=]M>U,Ǽ=F>ߩ=H> м=C>Sؼ=H>r=H> м=]M>U,Ǽ=$T=c=n=q={k=E]=*=Az=n=q=$T=c=> ==>nϟ==>Т==U](>K==W,!>N==%>T#d==ĺ#>:==U](>K==c(>1==W,!>N==U](>K==ĺ#>:==&F><=:@>><=WoE>@i<=շA>,b<=:@>><=&F><=]=qS<=p=<=.=L!<=]=qS<=M=<=p=<=*n=.i=AS={====Xh=AS={=*n=.i==:P\=C=W=l=xE==:P\==Xh=C=W=F4=s=u=)=K=zV=t=R.=u=)=F4=s= ='{Ļ=F4=s=)=R:= ='{Ļ=t=R.=F4=s===(==K=Y=====$=(===p==kx=={:=ߘ >] =>r!=&>V=>N=e >*= >P=Ê>'=f$>=UB!>i=*:">ܼ=f$>=Ê>'=+1=x:=nE/=@;=oC=~;==i=e====<.=|=e===i= >Q=If>M= >4N=If>M=w >ҷ=j>lѼ=>r!=t>Ĕ3= >Gj=o>ǩ==H>D==>==%>==)>j==*>x===}=>֓= >Gj=>֓=>ּ=&>V=茀=S?=*=X==\'= >Gj=+===}==/=+== >Gj=5cz><=̟>r<=FC|><=5cz><=pz>r<=̟>r<=>緽=;='=#>3=>緽= >½=;='=z=cԂ==_=== ]t=R)==>j=cu==_===z=cԂ==C=W=*n=.i=s=b0O==Xh=*n=.i=C=W=^S>7<=FW>;=S>;=^S>7<=&Z>\<=FW>;=nc>=VEa>+;=7kf>;=nc>=]>]:=VEa>+;===AS={==-p===zu=,=AS={=QU >ǖ=@>7=>= >H=@>7=QU >ǖ=/>o;=#z>`;=z>M<=in>/o=I"q> =? k>=in>/o=F u>[w=I"q> =#z>`;=A~>} 9=1-x>ֺ=J > ŭ<=̟>r<=J >k<= >ý=>̽=>̽=r=̽=f=:l=S=̽=r=̽=^=*=f=:l=Vc=6=w==8c=ý=Vc=6= }=.=w===Ic==cP==f=(=Q==cP==Ic=>8z=J >k=J >S=~>dg=J >k=>8z==| ==dT=8=\.=cX>==$WT>==%R>==cX>==|;Z>;==$WT>==|G>aj=E>җ=hK>uw=B> +=E>җ=|G>aj=s<ڻ=o; F=;#==o>ǩ==>==N>==o>ǩ==4>#==Oo='==z==D=Oo='=[=_==z=E=څ="=F T=fd=o= =j=y=i/=ߓ=P"=v= C=⛈=U=b&=`O=v= C=7ޑ=O=⛈=U=3=\=›=J=jG=_=7ޑ=O=›=J=3=\=1>+==->f<=*>0 ==j*>=D+>=g&>==#9=v= C=b&=`O==#9=y=i/=v= C=%>:@=$>D~=*>P=%>:@=w >ҷ=$>D~=O=3=-= =ݵ=W=>Ƕ=}0=[=ՠ=5ǧ=cJ=H=ln=w== }=.=Ȑ<*κ=K=3+>K5ȼ=R.>=%>:@=3+>K5ȼ=I(>K=Ui->== 5>>%==1>+==-q=--=n,\="=s=Ϝ=FM>L<= L>GI<=QP>?ׄ<=FM>L<=I>U<= L>GI<=e==ox=ɵD=}=Ъg=e==s"==ox=ɵD=J]O>X<=^S>7<=S>;=J]O>X<=FM>L<=^S>7<= R=I(>K=R.>=f$>=I(>K=j*>=ox=ɵD=T=!#=}=Ъg=nc>=^>z1=]>]:=nc>=Ld>n=^>z1= <ݨ5<=2zMΑ;=l D> ,+<=J>;=/J>_<= L>GI<=\BF><=O>GI<=/J>_<=*>3j;==ĺ#>:==F!>w)== >ь=QU >ǖ=>=>f=QU >ǖ= >ь=d`>7:=e>}==a>=d`>7:=zg>#=e>}=A>==0E>==(,H>[N==o;>x<=f=$~>=Z>a=J >==pz>r<=~>Ϊ==^`>:==^>==bd>==*q=̽=w===̽==̽=w==H=ln==\'=hX=*==q=J > <=J > F;=/>o;=u=̽==̽=H= = >½=u=̽=H= = >½=[>̽=u=̽=7>̽=de>ý=̽=7>̽= >½=v>xD=[>̽= >½=7>̽=7>̽=v>xD=de>ý=o&>==sJ#>Ǽ==1 >==sJ#>Ǽ==o&>==|(>==sJ#>Ǽ==4>#==1 >===>?=Y#=,=e=<=Y#=,==>?=U= ,==cP=(=Q==>?==cP==>?=e=<==>:<=`=S@<==|<=de>ý=>̽=̽=>̽=de>ý= >ý=>{L= >ý=de>ý=:&=J >J=B>`=J >J=\>4媽=B>`=1>x=/2>w3= ,>1=3>wG=/2>w3=9>8=.>w3=3>wG= ,>1=/2>w3=.>g|==>+i== >ޔ}==>+i==>g|==>̤j==>UQ==>̤j==>g|==1<#'=nu<&%=S<=(<=S<=nu<&%=BY]>+=CW>z=~[>#j=ݐa>ƫq=BY]>+=~[>#j=CW>z=BY]>+=Y>G= >ý=>u=>̽=#>;=!>ҽ:=WV>y;="><<=#>;=WV>y;=`=S@<=Z7=J<==|<==WYG<=Z7=J<=`=S@<=`=S@<=4= ;==WYG<=Ǚ#<<=o; ŭ<=o;k<=!*>s==_*>r==VC/>Դ==VC/>Դ==x/>,==!*>s==WV>y;=F>>U<="><<=?y>f==wev>==}>t==~>lq[==?y>f==}>t==?y>f==~>lq[==x>uP===e~;=8=޹ <=u={09<=T>+==hP>,==XP>$G==Uv<㛵=o<'=s<ڻ=ʹ<7==k:I<$}==Z+<==5x=w==f\=D==Pg===f\=D==S=P==Pg===SH=#==S=P==f\=D==>u= >⋽=>f=>f=>'v=>X$=>'v=>f=>t= >⋽=>t=>f=e==}=Ъg=E=څ=e===%Ӽ===e== !=EL==%Ӽ=E=څ= !=EL=e==i#> =="><=>==i#> ==1'><="><=1'><=?N'> <="><=Ī==>Ƕ=}0=5ǧ=cJ= !=EL=fd=o==0˼==W= !=EL==0˼= !=EL==W==%Ӽ=x<>'l=;>Q̼=C>Sؼ=;>Q̼=x<>'l={5>==-p==:P\==Ic==:P\=(=Q==Ic=(=Q==:P\=l=xE=z<í={{<=J > ŭ<=}>L<=̟>r<=J > ŭ<=FC|><=k:I<$}==ʹ<7==<Ĕ==l ==XP>$G==hP>,==J> ==/F>Gu==E>==I>==/F>Gu==J> ==hP>,==I>==J> ==煺==:=ʔ===J > ŭ<=J >>x<=}>L<=L==uUf>Ny==/c>Ն==1i>L==j>Cu==uUf>Ny==1i>L==n>z==j>Cu==/==s"====/==[=ՠ=s"==s"==[=ՠ=>Ƕ=}0==<=M=<==<==c?<==<=\= <==c?<==<==<==<==<==<=-q=--=l=F=4Z=Yl;=b&=`O=l=F==#9=l=F=b&=`O={k=E]==#9=l=F=-q=--==| =p=:=J=ۀ=p=:==| =W=`r=J=ۀ="=F T==| =VC/>Դ==4>+==5>==s =f==9=<屗=O,< =s =f=<屗=z?>je==ёD>Жv== E>uY==z?>je=='>>L&==ёD>Жv=='>>L&==z?>je==_9>՛s==W=`r=8=\.==0;=W=`r==0;==.)=_<=Y><=IT>y<=&Z>\<=Y><=)`>?<=EU>8<=Y><=&Z>\<=IT>y<=Y><=EU>8<=6Z> |=d`>7:==a>=d`>7:=6Z> |=Y>=s<P<=2zP=$>D~=j'>hO= >Q=j'>hO=$>D~=Z0X===+Nh=r==_L===+Nh=r==Z0X===*q===>̤j==>UQ==z~>'==9>m<=9>u<=_Z>>K0<=g;>:<=9>u<=)6>僛<==ˡ= U= = D=wE=S>R<=X>_<=IT>y<=S>R<=LL><=5R>t==S>R<=O><=S>R<=IT>y<=O>ZjA=7k>;=ݷn>&'=m>/T=>Kh>O=7k>;=Cp>ZjA=m>/T=7k>;=]>?N==ȸW> ==(_>A==ȸW> ==]>?N==V>==ȸW> ==Y>Q==(_>A==i#> ==&>!==*>0 ==T > ==&>!==i#> ==c(>1==&>!==F!>w)==F!>w)==&>!==T > ==?N'> <=1'><=-><=*>0 ==1'><=i#> ==*>0 ==->f<=1'><=->f<=-><=1'><==z== ==D==z=m=儝== =BY]>+=`>ӵ=Y>G=zg>#=m> =@k>+w=m> =zg>#=kMi>(=Ld>n=kMi>(=zg>#=ȸW> ==hP>,==T>+==Z===i=== = ==Z===U=o==i===h>^==i===g<>y==U=o==g<>y==i==== ==e= ==2=D=== ==Z=== = === ==,=Qܵ==Z===2=D==,=Qܵ=== ==5ǧ=cJ==p=={:=-><=g)>K<=?N'> <=~=̼=/===ټ=-=Bm[== =5?==@u=Z== =5?==G=A==@u=Z===C1==G=A== =5?==E=G=׈= =s=Ϝ=s=Ϝ=׈= =ߓ=P"=H>D== >2====+Y=G =F=<=P>A=a=F=<=+Y=G =n,\="=F=<=J=ւ+=6E7=e(=n,\="=J=ւ+=F=<=1C==29^>$==N>c>&==29^>$==^^>==N>c>&==h>==N>c>&==Ch>ʯ==N>c>&==h>==c>C==> E==A>),== =5?==A>),===y3$== =5?===y3$===C1== =5?==70>(7={5>=R.>=_9>՛s==44>sa==*2>h{== O>=S>i=T>;6=T>;6=S>i=Y>G=T>;6=TO>ĕ= O>=1>x=1>=(l7>=1>x=D+>=1>=1>=R.>={5>=1>=D+>=j*>=j*>=R.>=1>==`M===/=== 6==q=vB===/===`M==-><=W4>3<=25><=-><=->f<=W4>3<=W4>3<=->f<=1>+==H=ln=h=+e==Mf=h=+e=H=ln= }=.="==ݰ==SH=#==f\=D==MkZ===f\=D==Bo===f\=D==MkZ==="==ݰ==Rm=aF=K=zV=u=)=Ϊ=F===[=:===~=Yv=[=:===Ϊ=F===$=~=Yv===W.=*+= 5>>%==6>A==K;>׷*==6>A== 5>>%==f/>6==]o=3= n=/D=W=*=v><= >@<=>L==>L==D >==v><=A>),==>L===== >@<====>L==⣊=;Lq9==e~;=)=R:=]`=E==A(K=вN==Wa=`==Wa=`==A(K=вN==gG=>i==gG=>i==Y=t==Wa=`=== B=={=JW==b=M]G=== B==p,==.>==ʁ(>"==ʁ(>"==#>٪==!*>s==x/>,==ʁ(>"==!*>s==&> v== ">r==3#>5== ">r==z~>'==3#>5==r==:=ʔ=煺==r==x=m=m=儝==+w;==/<=={m<=x<>'l=18>y={5>=1>=18>y=(l7>=18>y=1>={5>= 66>==x91>==Q8>== 66>==_9>՛s==*2>h{==x91>== 66>==*2>h{==,>ý=".>N2=2>='>R=w>#`=$>م=w>#`='>R=u>qi:=ݔ >JH<=&><="><<==\r==|======= ===\r=====^<"1?= n=/D=<Y=h{==L,>Lg==*>x==L,>Lg==&> v==*>x====槵=>Գ===:=ʔ=====槵=>Գ===v==> ==-h>p==*>\Y==o;J=O<٠=O!<ν=O<٠=[<=O!<ν=C?!> ==2>H==']#>]==C?!> ==%>==3#>5==%>==C?!> ==']#>]==GY>CZ=Y>=HR>J=1V>̮:=GY>CZ=HR>J==y;==e~;=u={09<=K6=۴= D=wE=Q=D=>=ط=K6=۴=Q=D=:>'==_9>՛s== 66>==_9>՛s==:>'=='>>L&==Q8>==:>'== 66>=== ==F=*W==5}======F=*W==[D="==F=*W=====5}===">7u<=g)>K<=&><=>̤j==Uq>id==`>Q==Uq>id==>̤j==z~>'==1r=T==}=)k===}=1r=T=u=)= D=wE=12=}=A>=ˡ=%=:|=12=}=CD'=F=12=}=%=:|=A>=ˡ=12=}= D=wE=CD'=F=/==;n=қ=[=ՠ=nE/=@;==/<=k]8=ީ[<==/<==Td<=k]8=ީ[<=.=*==\=3=:\=|(>==b.>|;==_*>r==b.>|;==|(>==n,>==1>ӕ<= >v{<=><=k>=f>C=? k>=f>C=k>=-j>*=^p>㥋=k>=? k>=͜=D==ټ= =3o=jG=_==\V=í=\zj==*W==\V=3=ZeE==\V==*W=í=\zj=(S>6ɇ==V>==(O>==(S>6ɇ==hP>,==ȸW> ==hP>,==(S>6ɇ==(O>==V>==(S>6ɇ==ȸW> ==6>A==eF<>]@==K;>׷*==fq@>8L==eF<>]@==ş:>U==eF<>]@==6>A==ş:>U==XS<==Q==5z6>܊==2>==5z6>܊==4>+==2>==$,7>=ѐ1>G=<1>:A==e~;=+ˁ=:e0;=8=޹ <=+ˁ=:e0;=us=5;=8=޹ <=d>=f>C=`>ӵ=BY]>+=d>=`>ӵ=^X=<=Ԙ=d<=u={09<=v><=Qz>1^<=1u>y=<=Qz>1^<=v><=}>L<=Qz>1^<=z>M<=1u>y=<==A=x=m=r==煺===A=r==\=Q=A= H=رR=*ջ=A= H=\=Q=keM==zu=,=NE=\=AS={=NE=\===AS={=Ĩ=+?=*= %=}=p=*= %=Ĩ=+?=c=G½=r >}==a>#==>N ==k>\#==a>#==nU>11==a>#==k>\#==>N ==a>#==r >}==nU>11===22=1= ==w=+1==;==>:<=8> ԡ;==0;==;=8> ԡ;==>:<==;=`=S@<=.3D><=MG>f=C>?@+=k>=o>{=-j>*=^p>㥋=o>{=k>=NL=F====Ɏ=~=̼=;n=қ=/===/<===C<=={m<=:>'=="=>%=='>>L&==Q8>=="=>%==:>'==&> v==%>T#d== ">r==&><=g)>K<=Iw,>.2s<==y;=u={09<=Ԙ=d<=F=*W==}= ==[D="=== ==}= ==F=*W==Q) =.3D><=C>?@+=,>ý=2>̽=n,>̽=2>=2>̽=,>ý=NE=\=Z=vJ=x=m=zu=,=Z=vJ=NE=\=z=>x=18>y=x<>'l=z=>x=(l7>=18>y=Rm=aF=u=)=1r=T=v3>^P<=9>m<=4>X?;="><<=F>>U<=ݔ >JH<==F==\V=jG=_==F=3=ZeE==\V=ݔ >JH<=">7u<=&><=b.>|;==VC/>Դ==_*>r==4>+==VC/>Դ==b.>|;===\===3=:\=3e={9====\=2>==b.>|;==n,>==2>==4>+==b.>|;===e~;=⣊=;Lq9=+ˁ=:e0;=>Ƕ=}0==Ɏ===Ī===Ɏ=>Ƕ=}0=?N'> <=g)>K<=">7u<= =Ǡ<=={m<===C<= =)<=={m<= =Ǡ<==<=Ԙ=d<=^X=<==<===C<=Ԙ=d<=$,7>=v4>x>=ѐ1>G=5z6>܊==I;>4==4>+====[=_=v==:=ʔ=[=_======s=b0O==*W=;n=қ=5ǧ=cJ=[=ՠ==p=5ǧ=cJ=;n=қ==<= =Ǡ<===C<= =Ǡ<==<=lҤ=m<=lҤ=m<==<=ݗ=<=v3>^P<=9>u<=9>m<=)6>僛<=9>u<=v3>^P<=G<=o; ŭ=o;>x=gW=YO=== ==s==== ==gW=YO=== 6===/=== === 6==d=';*==̢======"/K>T=.3D><=H>e:=.3D><="/K>T=MG>f=!A>}G=H>e:=.3D><="><<=#'>]<=#>;=#'>]<="><<=&><=&><=Iw,>.2s<=#'>]<=^=*=c=G½=Ĩ=+?=c=G½=^=*==̽= =̽=c=G½==̽=O>*==S>==N>==O<٠=o;&=\*<]=o;&=O<٠=o;J=[<=O<٠=\*<]=If>M=$>D~=w >ҷ= >Q=$>D~=If>M=3====\r==5}====\r==3===|====\r=== ==5}===z?>je==ş:>U==_9>՛s==44>sa==_9>՛s==ş:>U==g<>y==گ>X==uJ>==~>Ϊ==J >[!==J >==d`>7:=Ld>n=zg>#=O+9>==I;>4==5z6>܊==Z=vJ=m=儝=x=m=Z=vJ=(====$== =Z=vJ===$=Z=vJ== =m=儝==}=7==)k=={5>=X7>,~=;>Q̼=X7>,~={5>=70>(7=*>3j;== >k===`>Q==W,!>N==ĺ#>:==*>3j;==W,!>N==*>3j;==`>Q==T=>=$,7>=>K;>+r:=x=m==A=NE=\=V=>==A=煺===A=V=>===NE=\==A====Ɏ=Ī==e="=e="=Ī== ='{Ļ=H>D==o>ǩ==N>==d=';*======/===/====== ==zJ=+==1=D===q2==zJ=+===CH==ؤ=g===CH==zJ=+===q2==? k>=f>C=d>=ݐa>ƫq=d>=BY]>+=׸='=====0=׸='==w=+1====,>ý=n,>̽=o&>̽=,>ý=(>'ڹ=".>N2=J > F=A~>} 9=J > F;=A~>} 9=/>o;=J > F;=A~>} 9=#z>`;=/>o;==\=.=*=hX=*=hX=*==\'==\=e >*=de>ý=v>xD=e >*=>N=>{L=>{L=de>ý=e >*=^`>:== /\>==|;Z>;== /\>==^`>:==ib>˶==|;Z>;==^>==^`>:==]z^#<=S>p<=>̵;=F>>U<=>̵;=S>p<=>̵;=u>qi:=>a=u:=>̵;=WV>y;=u>qi:=F>>U<=WV>y;=>̵;=*>\Y==>UQ==xk>==>UQ==>g|==xk>==-h>p==>UQ==*>\Y==2>H==>UQ==-h>p==O,< =bʿr!= >Gj=&>V= >Gj=>֓=&>V=(,H>[N==K>==eO>q==K>==(,H>[N==0E>==f<'8s==1<f==wYz<R==c<=g;>:<=25><=25><=g;>:<=)6>僛<=շA>,b<=g;>:<=:@>><=:@>><=g;>:<=a:><=']#>]==ʁ(>"==)>j==%>==']#>]==)>j==.>==)>j==ʁ(>"==Y>=^>z1=d`>7:=^>z1=Ld>n=d`>7:="=F T==dT==| =l D> ,+<=\BF><=I>U<=\BF><=l D> ,+<=@>L<=l D> ,+<=_Z>>K0<=@>L<=;>==sJ#>Ǽ==B&>Y==4>#==;>==N>==;>==O>*==N>==sJ#>Ǽ==;>==4>#==w,=߰м======8=Ho=g;=l=釻=-= = =j===ox=ɵD=>Ƕ=}0===>Ƕ=}0=ox=ɵD=s"==O=3=ݵ=W==0==F=O=3==0==F==0=3=ZeE=3=ZeE==0====3e={9=*=X==Ӥ}==\=*=X=3e={9==\'=*=X==\="><=">7u<=><=">7u<="><=?N'> <=t= o==/= <==<==kx=%=\==}=7===}=%=\=%=\==kx==p==p=w,=߰м=%=\=K;>׷*==%8>i== 5>>%==1>+== 5>>%==%8>i==1>+==%8>i==W4>3<=W4>3<=%8>i==a:><=a:><=%8>i==m?>d ==;>==B&>Y==#>٪==O>*==;>==#>٪==A>[y=[>>z=~:>;Qr=[>>z=X7>,~=~:>;Qr=X7>,~=[>>z=;>Q̼==dT=T=!#=J=0C=g&>=D+>=d8%>fa&=D+>= ,>1=d8%>fa&= ,>1=D+>=1>x=4= ;==;==0;=8=\.=4= ;==0;=8=\.==!0;=4= ;=4= ;=`=S@<==;=:=ʔ==z=[=_==z=:=ʔ=m=儝=:=ʔ=r==m=儝=xEY>==V>,==XR>-==xEY>== /\>==^^>==xEY>==U> == /\>==XR>-==U> ==xEY>==Y>=GY>CZ=^>z1=]>]:=GY>CZ=1V>̮:=^>z1=GY>CZ=]>]:=<.=|=|=̽=E,=ا=|=̽=<.=|==̽=H>e:=!A>}G=D>MΑ;=z~>'==C?!> ==3#>5==z~>'==2>H==C?!> ==>UQ==2>H==z~>'==>x<=D~>N,?<=}>L<=z>M<=D~>N,?<=/>o;=<1>:A=U5>-@9=$,7>=>K;>+r:=U5>-@9=4>X?;=U5>-@9=>K;>+r:=$,7>=ox=ɵD==Iѻ=T=!#==Iѻ=J=0C=T=!#==Iѻ=ox=ɵD===B===U=o==6=W===<==<= =)<=Zv>Ԋ==G}>F==wev>==G}>F==Zv>Ԋ==y>==%>>Ľ=(>'ڹ=,>ý=(>'ڹ=%>>Ľ=6 >e=o&>̽=%>>Ľ=,>ý=Z=.==܎====Au==܎===\=C===Au==/]>3Ž=1 >̽=>̽=/]>3Ž=>u=6 >e=>u=/]>3Ž=>̽=1 >̽=/]>3Ž=6 >e=ip<=>N ==>==y>p<=><=,>"<=><=y>p<=>==>N ==y>p<=,>"<=9q=]ϼ=9[=Ǽ=)k==9q=]ϼ=7==E=G=7==9q=]ϼ=)k==0><=-><=25><=)6>僛<=0><=25><=&>)9=(>L=5L!>N.@=&>)9=d8%>fa&= ,>1=d8%>fa&=&>)9=5L!>N.@=(>L=&>)9= ,>1=<`;=o; F;=o; <=&/6A=a=Np?=>ɼ=P>A=a=R=?s=+Y=G =9[=Ǽ=R=?s=Np?=>ɼ=;n=қ==$ļ==p==$ļ=;n=қ=~=̼==$ļ=w,=߰м==p=l>a==i>]d==`n>(==h>==i>]d==c>C==i>]d==h>==`n>(==D*=;=\= <==WYG<=D*=;=4= ;==!0;=4= ;=D*=;==WYG<=\= <=D*=;==!0;==cL=== 6==Vr=ӎN===cL===Ko^===`M===Ko^===cL==Vr=ӎN=== 6===cL===`M==Z(>f;=9&>'=!>ҽ:=f7=%k==*=8 ===x==*=8 ==\=C===x==*=8 ==f7=%k==Wt=x==92=M==W= Y=6==W=92=M==%Ӽ=(>'ڹ= ">æ=t )>6,= ">æ='=t )>6,=g>-t=Ue>\Sa=Ki>Kb=g>-t=? k>=d>=>==w>dH==[>\Lj==w>dH==xk>==[>\Lj==1 >̽=%>>Ľ=o&>̽=1 >̽=6 >e=%>>Ľ=,=Qܵ==U=o==Z===,=Qܵ==6=W==U=o==R=?s=g=R =+Y=G =9[=Ǽ=g=R =R=?s=D~>N,?<=Qz>1^<=}>L<=z>M<=Qz>1^<=D~>N,?<=6 >e= ">æ=(>'ڹ=g=R =9q=]ϼ=E=G=9[=Ǽ=9q=]ϼ=g=R =g>-t=in>/o=? k>=Ki>Kb=in>/o=g>-t=0><=g)>K<=-><=g>-t=ݐa>ƫq=Ue>\Sa=d>=ݐa>ƫq=g>-t==Au==*=8 ==Wt=x===Au==\=C==*=8 ==D>MΑ;=!A>}G=l?>Rd;=[>==B===u====<= =<=}=<= =<= =Ǡ<=lҤ=m<= =Ǡ<= =<= =)<==<= =)<= =<=B===g<>y==U=o==J >>x<=J > <=D~>N,?<=J > <=/>o;=D~>N,?<=<`;=o; F=o; F;=<`;=8n< 't;=;uP==~>D==0z>W2==~>D==x>uP==~>lq[==u h><==bd>==k>===<==<==<==<=}=<==<==<==<==<=z #>a= >Q=$>م= >Q=z #>a=j'>hO=~>D==J >S==J >.:==~>lq[==J >S==~>D==ѐ1>G=x->7=<1>:A=1>ӕ<=><=">7u<=ݔ >JH<=1>ӕ<=">7u<=v3>^P<=4>X?;=g.>6;=Ԑ=㙜==܎===Z=.==܎===Ԑ=㙜==w===\=C==܎===w===>PJ==2>H==-h>p==2>H==>PJ==']#>]==1>ӕ<=S>p<= >v{<=1>ӕ<=F>>U<=S>p<=ݔ >JH<=F>>U<=1>ӕ<=̢====<=====<=̢===t= o===<=}=<==<==<==<=t= o==+ˁ=:e0;=K=zV=uj=<ǹ=K=zV=+ˁ=:e0;=⣊=;Lq9=+ˁ=:e0;=uj=<ǹ=us=5;=j$}G=T=>=>K;>+r:=>K;>+r:=l?>Rd;=!A>}G=>K;>+r:=4>X?;=9>m<=>K;>+r:=9>m<=l?>Rd;=z #>a=$>م=w>#`=w>#`=u>qi:=!>ҽ:=w>#`=!>ҽ:=9&>'=9&>'=z #>a=w>#`=o; F=s<ڻ=o; =^<"1?=K5ȼ=*>P=70>(7=?1>=70>(7=*>P=?1>=X7>,~=70>(7=HE4==i?>==A>==i?>==I;>4==O+9>==>i==r >}==>N ==>i==>N ==,>"<=>i==F><=v><=>i==,>"<=F><=%>T#d==W,!>N==Uq>id==`>Q==Uq>id==W,!>N== ">r==%>T#d==Uq>id==z~>'== ">r==Uq>id==<*==o+<+==9}.2s<=v3>^P<=g.>6;=Iw,>.2s<=g.>6;=#'>]<=\=Q=)k==keM==Rm=aF=\=Q=رR=*ջ=\=Q=1r=T=)k==Rm=aF=1r=T=\=Q=l=釻=+1=x:=l`8==Ho=g;=+1=x:=l=釻=+1=x:=Ho=g;=nE/=@;=ʦ<0Y=o<'=y=ط=P(==K6=۴=K6=۴=CD'=F= D=wE=OT/>L==44>sa==6>A==ş:>U==6>A==44>sa===dT==!0;=8=\.==dT=J=0C==!0;==!0;=J=0C==V;=Uv<㛵=;Dk==N>==S>==N>==}>Dk==H>D==J=0C=+=R紺==V;=+=R紺=J=0C==Iѻ=>6== >m==>nϟ== >m==>6==uJ>== >9ո== >2==H>D==+*:>=v4>x>=$,7>=+*:>=?"?>) =~:>;Qr=v4>x>=+*:>=~:>;Qr=<.=ʦ<0Y=HE;Qr=5>=v4>x>=?1>=5>=X7>,~=5>=?1>=v4>x>=5>=~:>;Qr=X7>,~=ʦ<0Y=7uk<_=o<'=P(==I2=͑=R="=׫=^===شA===uS===شA==׫=^===xF== =$=)=R:==Tb;= =$=e="= ='{Ļ=e="= =$==Tb;=)=R:= =$= ='{Ļ=<Ĕ==\<ϛ==.A<~==lhO=q.>uO=*>P=q.>uO=?1>=*>P=o; =hx=z #>a=J(>wȻ=j'>hO=J(>wȻ=z #>a=9&>'=J(>wȻ=x->7=j'>hO=:>==؜9>==g?>Qp==؜9>==I;>4==g?>Qp==؜9>==:>==5>===ټ===~=̼====ټ=͜=D=e<w?==wYz<R==1Т==S>J==S>==U=j==R=9*===+==([=F8<=F><= >v{<=6>8<=5>2<== > <=5>2<=6>8<= >v{<=F><=6>8<== > <==<="=`<====r=[%=P<ܭ==8=r=[%=l=釻=5=)=l=釻=r=[%==8=P<ܭ=r=[%=5=)=4>+==؜9>==5>==4>+==I;>4==؜9>==+*:>=T=>=?"?>) =$,7>=T=>=+*:>=yDk==S>==S>J==}>Dk== >9ո==H>D==T=>=.3D><=?"?>) =T=>=!A>}G=.3D><=̘ p==[>==7>==7>== >2==گ>X==گ>X==S4> p==7>==o;[!==o;.:==<.==o;.:==X== >9ո==uJ>== >9ո==گ>X== >2== >9ո== >m==uJ>==hb!==.E> ==m?>d ==-= =î==ݵ=W=î==-= ===>nϟ==S>J==>Т==S>J==>nϟ== >m==<ɿ=ee p==گ>X==g<>y==g<>y==B===S4> p==S4> p==B===[>===#9=ߓ=P"=y=i/=-q=--=ߓ=P"==#9=-q=--=s=Ϝ=ߓ=P"=y<5=̘)I==)^t> /==en>>3==#>٪=={>4Τ==O>*=={>4Τ==S>==O>*==S>=={>4Τ==>Т==)6>僛<=v3>^P<=0><=Iw,>.2s<=g)>K<=0><=Iw,>.2s<=0><=v3>^P<=_Z>>K0<=l D> ,+<=l?>Rd;=_Z>>K0<=l?>Rd;=9>m<=D>MΑ;=l?>Rd;=l D> ,+<=w,=߰м=î====w,=߰м==$ļ=î==î===$ļ=~=̼===î==~=̼=?1>=q.>uO=v4>x>=x->7=q.>uO=j'>hO=x->7=ѐ1>G=q.>uO=q.>uO=ѐ1>G=v4>x>==22=͜=D= =3o=׸='= ?= ==22= ?= =͜=D==22==22==w=+1=׸='=3=̽=E,=ا=|=̽=>2=>u=>{L=>2=>=>=>2=>N=>=>{L=>N=>2=L,>Lg==*2>h{==44>sa==OT/>L==U](>K==L,>Lg==44>sa==OT/>L==L,>Lg==%>T#d==&> v==L,>Lg==U](>K==%>T#d==L,>Lg==WH>!==.E> ==2A>b!==E><==WH>!==2A>b!==E=G=7====%=\===7==%=\=w,=߰м===I2=͑=C{L=H=6?=̽=>=ط=C{L=H=I2=͑=>=ط=I2=͑=P(==hZ,>G=<1>:A=x->7=9&>'=Z(>f;=hZ,>G=hZ,>G=x->7=J(>wȻ=hZ,>G=J(>wȻ=9&>'==+w;=gΰ=I:==Tb;==+w;=]=-;=gΰ=I:=gΰ=I:=e="==Tb;='=>2=>=>2=6 >e=>u=6 >e=>2= ">æ='= ">æ=>2=!>ҽ:=#>;=Z(>f;=#'>]<=Z(>f;=#>;=Z(>f;=#'>]<=g.>6;=hZ,>G=Z(>f;=g.>6;=w>dH==>==>nϟ==> ==w>dH==>nϟ==*>\Y==w>dH==> ==*>\Y==xk>==w>dH==!>==ʁ(>"==']#>]==!>=={>4Τ==#>٪==ʁ(>"==!>==#>٪==k>==:o>==u h><==~<==ʹ<7==Z+<==ʹ<7==~<==u{<&Ƭ==Ya=>_s==%8>i==K;>׷*==Ya=>_s==2A>b!==m?>d ==2A>b!==Ya=>_s==K;>׷*==%8>i==Ya=>_s==m?>d ==Q"s>ĕ==#Uv>p==y>==,p>h ==q>==Bkw>a==q>==)^t> /==Bkw>a==q>==,p>h ==l>==4>X?;=&`1>+:=g.>6;=&`1>+:=hZ,>G=g.>6;=&`1>+:=4>X?;=U5>-@9=>X==> ==>Т=={>4Τ==>X==>Т==> ==>X==-h>p==3=̽==5ý=E,=ا==5ý=G=^=E,=ا=G=A==Y=e4===xF===+==Y=e4==n=Q"==Y=e4===+===xF==Y=e4==G=A==n=Q"==c>C==XUf>ߑ==(_>A==XUf>ߑ==/c>Ն==(_>A==g?>Qp==C>˹==6E>İ==(,H>[N==C>˹==A>==C>˹==(,H>[N==6E>İ==C>˹==g?>Qp==A>=={>4==5cz><=Bkw>a=={>4==~>Ϊ==pz>r<=~>Ϊ=={>4==Bkw>a==5cz><={>4==pz>r<=O==Q=D=Vc=6=O==C{L=H=>=ط=C{L=H=O==Vc=6=Q=D=O==>=ط=x>uP==-v>>==Kq>)I==-v>>==)^t> /==Kq>)I==-v>>==x>uP==0z>W2==2A>b!==?>7==E><==?>7==2A>b!==K;>׷*==?>7==fq@>8L==E><== ,>4@==f/>6==c(>1== ,>4@==U](>K==OT/>L==U](>K== ,>4@==c(>1==f/>6== ,>4@==OT/>L==zp>==1i>L==l>a==~<==ԙ<]==u{<&Ƭ==eLh< $==ԙ<]==~<==~<==Q]@==?>7==K;>׷*==eF<>]@==fq@>8L==?>7==<1>:A=&`1>+:=U5>-@9=<1>:A=hZ,>G=&`1>+:=B]q>==:o>==k>==Zv>Ԋ==Q"s>ĕ==y>==>X==>PJ==-h>p=={>4Τ==>PJ==>X==en>>3==q>==l>==en>>3==)^t> /==q>==>.==~>D==J >.:==0z>W2==~>D==>.==>PJ==!>==']#>]==>PJ=={>4Τ==!>==>.==J >.:==J >[!==~>Ϊ==>.==J >[!==zp>==n>z==1i>L==n>z==zp>==wev>==zp>==Zv>Ԋ==wev>==<&=̽=C =̽=R="=I2=͑=<&=̽=R="==5ý=3=̽=S=̽==5ý=f=:l=G=^=f=:l==5ý=S=̽====u===6=W==u===B===6=W==Q"s>ĕ==`n>(==#Uv>p==`n>(==Q"s>ĕ==l>a===%Ӽ=92=M= =3o= =3o=92=M==22=1= =92=M= Y=6=1= ==22=92=M=ݗ=<=6===s===s==== ====="=`<=s======s===lҤ=m<=ݗ=<="=`<=lҤ=m<=s===`====w=+1=1= =s=b0O==w=+1=`====w=+1=s=b0O====ݵ=W=== ?= =͜=D= ?= ===î====ݵ=W=}>Dk==S>J== >m==}>Dk== >m== >9ո==}=p=*= %==Mf=H=ln==Mf=*= %=ώ=̽=H=ln=*= %=c=G½= =̽=*= %=ώ=̽=*= %= =̽=XUf>ߑ==l>a==1i>L==XUf>ߑ==i>]d==l>a==/c>Ն==XUf>ߑ==1i>L==c>C==i>]d==XUf>ߑ==6t>n==:o>==B]q>==̢===R=9*==U=j==R=9*==̢===d=';*===ǹ===R=9*==d=';*==0z>W2==~>Ϊ==Bkw>a==0z>W2==Bkw>a==)^t> /==>.==~>Ϊ==0z>W2==0z>W2==)^t> /==-v>>==̘ĕ==zp>==l>a==Q"s>ĕ==Zv>Ԋ==zp>===uS===شA===ǹ====ǹ====شA==R=9*===+===شA===xF==R=9*===شA===+==u h><==kl>==Ch>ʯ==:o>==kl>==u h><==hx=7uk<_=h==h>==Ch>ʯ==kl>==m>==Ch>ʯ==q>==Yw>==s>@٬==Yw>==q>==6t>n==q>==:o>==6t>n==kl>==:o>==q>==o;S= <i=o;k=o;S=<@Q= <i= <i=o;S=o;k=@(==m>==s>@٬==m>==`n>(==h>==m>==kl>==q>==m>==q>==s>@٬=ýQ<̽̽>p%<̽̽<̽=>p%<̽e=Q<̽=<̽̽#}>̽ý{>̽̽w>̽={>̽=#}>̽=w>̽Io;̽pB)<̽So;̽p_=i<̽I=o;̽S=o;̽=t>̽=w>̽=B]q>̽̽w>̽cýu>̽̽B]q>̽=<̽=<̽=%<̽<̽̽<̽̽%<̽={>̽=w>̽=t>̽=t>̽~=Dw>̽={>̽̽w>̽ý{>̽cýu>̽6nxx>̽cýu>̽ý{>̽=<̽e=Q<̽=<̽٩=z<̽=<̽e=Q<̽ýQ<̽̽<̽<̽<̽z<̽ýQ<̽؂=PYI>̽g=:nI>̽=7D>̽g=:nI>̽R=E>̽=7D>̽ߜ='=̽eB==̽!=޾=̽ѥN=1e=̽`=p=̽Pf=B[=̽=U=̽,==̽eB==̽,==̽!=޾=̽eB==̽n=)rG>̽=FBN>̽|=jK>ִ̽=-A>̽қ=C>̽n=)rG>̽֘&O'қ=C>ִ̽=-A>̽=k>>̽ =*q=̽V=jc=̽==̽Q?'V=jc=̽=*q=̽ag=g=̽=)=Zd=̽:>=~=̽ag=g=̽mP6E'=U=̽:>=~=̽,==̽L(=dE=̽=#:=̽@= T=̽<̽M/`<̽ғ<̽pB)<̽M/`<̽}@C<̽0=$<̽ʉ=<̽w=<̽0=$<̽p_=i<̽!s=!=b<̽O)c=<̽,E|=w<̽18g=ao<̽H= R>̽vy[=ϬN>̽JH=GJ>̽vy[=ϬN>̽H= R>̽|[=U>̽z<̽ <̽ýQ<̽`=K<̽٩=z<̽e=Q<̽]p~>̽6nxx>̽ý{>̽~=Dw>̽r=љ~>̽={>̽>PA#e>̽3_>̽=c>̽̽6nxx>̽ʑn5s>̽l>̽q>̽ʑn5s>̽6nxx>̽q>̽cýu>̽ko>̽ޘ5Dj>̽yan>̽=P/<̽٩=z<̽8==2<̽l=Zn<̽=P/<̽8==2<̽٩=z<̽=P/<̽=<̽z<̽'<̽?z<̽'<̽J<̽?z<̽'<̽z<̽<̽{&H''ִ=-A>̽~=:>̽=k>>̽=ڗ=̽=U=̽eB==̽gL=G=̽ѥN=1e=̽Pf=B[=̽o<̽̽wAl>̽ʑn5s>̽wAl>̽l>̽ʑn5s>̽y=V]=̽L(=dE=̽@= T=̽>'!=޾=̽Pޤ=,x=̽V=p=̽9 %V9'Pޤ=,x=̽!=޾=̽,==̽=ۅJ>̽=FBN>̽n=)rG>̽z8u>̽Qu>̽ko>̽yan>̽z8u>̽ko>̽J<̽*j<̽L<̽<̽*j<̽'<̽*j<̽J<̽'<̽=<̽l=Zn<̽=I<̽=<̽=<̽=P/<̽l=Zn<̽=<̽=P/<̽~=Dw>̽=xy>̽r=љ~>̽Ty>̽6nxx>̽]p~>̽8Ža<̽Lco;̽̽o;̽̽>p%<̽8Ža<̽̽o;̽8=g<̽=>p%<̽=o;̽Lc=o;̽8=g<̽=o;̽LcJ >̽9ŽQ>̽̽J >̽9ŽQ>̽̽#}>̽̽J >̽=#}>̽9=P>̽=J >̽9=P>̽Lc=J >̽=J >̽3_>̽1_>̽=c>̽5N >̽ ڊ>̽h8 >̽ Z=̽t=̽Ƒmd=̽$<̽%e<̽ؼ<̽[<̽ʢd6<̽̽*j=XP>̽=FBN>̽(ϤY>̽1_>̽3_>̽yvc<̽M/`<̽<̽}@C<̽M/`<̽yvc<̽0=$<̽~=#\<̽ʉ=<̽!s=!=b<̽~=#\<̽0=$<̽=S=̽+==̽=3=̽@= T=̽V=p=̽y=V]=̽R==̽mA==̽X=\.=̽R==̽:=F =̽mA==̽ۆmo =̽[<̽̽u=O8>̽=I4>̽c=4>̽u=O8>̽ A=&2>̽q=$=̽R=ܫ=̽"=Wz=̽q=$=̽R=h=̽R=ܫ=̽I& 'V=jc=̽:>=~=̽=U=̽t5&>&V=jc=̽ag=g=̽:>=~=̽/77=̽211=̽$̽=c>̽1_>̽\##=̽=̽e-b=̽1&;'қ=C>̽m(=?>̽R=E>̽Ñ&smC'қ=C>̽=k>>̽m(=?>̽u=O8>̽~=:>̽=I4>̽u=O8>̽=k>>̽~=:>̽=ڗ=̽q=$=̽"=Wz=̽eB==̽q=$=̽=ڗ=̽4 kh>̽=c>̽| g>̽>PA#e>̽t",`>̽3_>̽(<̽o<̽<̽o<̽Q<=̽=~=̽=)=Zd=̽VX',==̽:>=~=̽Pޤ=,x=̽=ۅJ>̽қ=C>̽R=E>̽n=)rG>̽қ=C>̽=ۅJ>̽o<̽yvc<̽<̽ʢd6<̽yvc<̽o<̽~=#\<̽,E|=w<̽ʉ=<̽~=#\<̽18g=ao<̽,E|=w<̽=^<̽=:<̽l=Zn<̽Bh<̽ޘ<̽J<̽4 kh>̽yan>̽ޘ5Dj>̽wAl>̽| g>̽l>̽l=7n>̽c=g>̽1}=j>̽bO=<̽ל=<̽ʉ=<̽==̽=ڗ=̽"=Wz=̽~=:>̽?$=w6>̽=I4>̽y=V]=̽Pޤ=,x=̽=)=Zd=̽V=p=̽Pޤ=,x=̽y=V]=̽h=̽ʟMC=̽]=̽ Z=̽Ƒmd=̽铽F=̽1}=j>̽c=g>̽=?ad>̽c=g>̽=b>̽=?ad>̽>PA#e>̽4 kh>̽ޘ5Dj>̽=c>̽4 kh>̽>PA#e>̽4 kh>̽| g>̽wAl>̽wAl>̽yan>̽4 kh>̽/77=̽Q<=̽(<̽(<̽Q<=̽o<̽,E|=w<̽bO=<̽ʉ=<̽$̽\f>̽|pe>̽Lc=o;̽=o;̽`=K<̽o;̽Lco;̽ <̽=J >̽Lc=J >̽r=љ~>̽LcJ >̽J >̽]p~>̽8=g<̽Lc=o;̽`=K<̽e=Q<̽8=g<̽`=K<̽8=g<̽e=Q<̽=>p%<̽Lco;̽8Ža<̽ <̽8Ža<̽ýQ<̽ <̽ýQ<̽8Ža<̽̽>p%<̽9ŽQ>̽LcJ >̽]p~>̽ý{>̽9ŽQ>̽]p~>̽9ŽQ>̽ý{>̽̽#}>̽Lc=J >̽9=P>̽r=љ~>̽9=P>̽={>̽r=љ~>̽={>̽9=P>̽=#}>̽=Io>̽V=\r>̽N=w>̽i½/_>̽̽^>̽̽bd>̽=̽Bh<̽e-b=̽=̽ޘ<̽Bh<̽=:<̽=~=̽Q==̽=^<̽=~=̽=:<̽=<̽=%<̽=I<̽=<̽=%<̽=<̽=I<̽=%<̽=<̽̽%<̽̽<̽L<̽̽%<̽*j<̽<̽̽%<̽L<̽*j<̽17=˕<̽J=<̽IR=u<̽Luz="z>̽pӇ=H}>̽=hv>̽.:=J >̽5=>̽[!=J >̽S=o;̽`=|<̽k=o;̽==̽S==̽=r=̽=2>̽?$=w6>̽=O+9>̽==̽==̽= =̽"$'==̽=:=̽=ώ=̽' %=:=̽==̽V=jc=̽=U=̽=:=̽V=jc=̽=:=̽= =̽=ώ=̽==̽= =̽=:=̽=U=̽=ڗ=̽=:=̽==̽=:=̽=ڗ=̽8AĦ$'P=6B>̽=0E>̽=i?>̽=)=Zd=̽ag=g=̽@=+U=̽̽*q=̽fýZTh=̽̽Z0X=̽=K=̽=<&=̽=C =̽=<&=̽=K=̽닽='=̽Q==̽=K=̽=:<̽̽<&=̽#=̽̽C =̽#=̽e-b=̽Bh<̽=I<̽=C =̽=<̽=K=̽=C =̽=I<̽=I<̽l=Zn<̽=:<̽=:<̽=K=̽=I<̽̽C =̽L<̽̽<̽̽C =̽#=̽L<̽J<̽L<̽Bh<̽#=̽Bh<̽L<̽h>̽̽bd>̽̽k>̽h>̽l>̽| g>̽Qu>̽4z>̽ш%v>̽ш%v>̽LXq>̽Qu>̽ko>̽Qu>̽LXq>̽4z>̽·KS{>̽ш%v>̽=hv>̽=`gq>̽!={p>̽=`gq>̽=hv>̽A*=w>̽닽='=̽=K=̽Q==̽\##=̽Xx=̽=̽3Wc<̽=̽Xx=̽z8u>̽yan>̽ʑn5s>̽l=7n>̽$=O~t>̽!=Js>̽={>̽$=O~t>̽A*=w>̽Io;̽m*<̽pB)<̽ғ<̽m*<̽@HZ<̽So;̽pB)<̽}@C<̽G=X'<̽I=o;̽p_=i<̽G=X'<̽w=<̽=RmZ<̽yvc<̽jҷ_<̽}@C<̽6nxx>̽Ty>̽ʑn5s>̽ |>̽z8u>̽Ty>̽Ty>̽z8u>̽ʑn5s>̽=xy>̽~=Dw>̽!=Js>̽$=O~t>̽={>̽=xy>̽$=O~t>̽=xy>̽!=Js>̽@HZ<̽FI<̽ғ<̽J=6V<̽=RmZ<̽w=<̽<>̽̽-̽i 5>̽T _85>̽0>̽ʏMzU>̽(ZQ>̽Clt\O>̽sU̽<>̽̽bN=̽}M=̽nr =̽7އ=.>̽=b,>̽׋=Ӿ&>̽= >̽)=%>̽=>̽y%Y>̽D @ >̽. 0>̽U<Υ<̽u,; <̽<O;ʾ<̽&&ZSE}=̽>~b=̽pՊ7f=̽v9=̽wg;=̽_;:=̽¢w=`*=̽:\=W=̽ñ]=3,=̽ɧ+5->̽.W'>̽y i'>̽>~b=̽. b=̽bM=̽&{=V=̽==ؠ=̽u=7=̽EO=>̽z8=Y>̽ O=h>̽uG=̽}M=̽bN=̽l< u=̽w< Y=̽6X=̽̽=>̽=>̽Rz&^V'jgzn=̽ `|=̽Ghg=̽a=$>̽= >̽=>̽ H=(=̽Pa=rw=̽ñ]=3,=̽Sa =̽+ =̽ꓼ6=̽Sa =̽0 =̽ u=̽9[=̽XJ1 =̽Ghg=̽<]>̽u<>ud>̽\̽ͥ3:'[H=̽ttK~=̽bM=̽|m̽̽/̽풔_ g>̽\ic>̽b60a>̽B^>̽\ic>̽ f>̽HK%}>̽.:J >̽[!J >̽.:J >̽HK%}>̽q>|>̽8h=>̽EM=>̽EO=>̽}M=̽-+o=̽$8ݚ=̽['lv3'|mE>̽1:_A>̽I?>̽ =K>̽̽}I<6Q>̽->̽ɧ+5->̽[~3>̽ɧ+5->̽->̽.W'>̽brM=̽|4,=̽fO =̽|4,=̽\ =̽fO =̽x6* L'*=TB>̽H=T_C>̽%9=)>>̽dY=s=̽Ar=P=̽[=#י=̽dY=s=̽E==̽:\=W=̽=1=̽==̽7=R=̽u<>ud>̽S<~c>̽\̽lB2̽= >̽a=$>̽N̽N< i>̽=E>̽N< i>̽=b>̽<>̽&4 =N=̽a<D=̽̽΁F>̽^fG>̽%]79'O{,A>̽mM};>̽c;>̽&" '>~b=̽t1s=̽pՊ7f=̽M3g't1s=̽>~b=̽bM=̽GP=vw>̽=M=5&~>̽ mb=̵y>̽_==̽=V=̽#$==̽==̽=k=̽[=+ͺ=̽=k=̽==̽R=h=̽C̽{:̽/<1Q>̽<X>̽{:̽\̽=(>̽=(+>̽a=$>̽ Dm~=̽S|/T=̽҃F=̽8<}V=̽*<=̽ҟ;I=̽d^= >̽*=/Z>̽A:=>̽\8̽j~̽킔̽N¼*=̽߼=̽bN=̽B'B|=̽߼=̽ZSE}=̽(ZQ>̽-TCK>̽Clt\O>̽Kć=|=̽p= =̽[=+ͺ=̽<X>̽=R>̽}I<6Q>̽ؼ<̽<̽$<̽\=̽[H=̽bM=̽J=ևT>̽=[S>̽[=Z>̽OI\t>̽cټ;ep>̽6n>̽7އ=.>̽p=~3>̽=b,>̽1A2=̽&BP=̽b#8=̽<=̽̽(ZQ>̽ʏMzU>̽ϪX>̽B^>̽T-#o[>̽Y=65>̽qm=mS9>̽Zbp=Q4>̽qm=mS9>̽Y=65>̽ZV==>̽H+>̽ɧ+5->̽y i'>̽H+>̽CD(>̽tb/>̽CD(>̽H+>̽y i'>̽p= =̽ĝ=.=̽[=+ͺ=̽΁F>̽h3w>UK>̽^fG>̽h3w>UK>̽΁F>̽o.XL>̽h3w>UK>̽d .M>̽^fG>̽(c=̽x~@m=̽%ßO=̽x~@m=̽(c=̽ttK~=̽k<Q>̽C0;RV>̽D<$V>̽1=J>̽*=/Z>̽d^= >̽g;O>̽C;=G>̽2"hM>̽g;O>̽C0;RV>̽k<Q>̽[~3>̽t3>̽/pq9>̽t3>̽[~3>̽ɧ+5->̽(ET>̽3[>֣̽jZ>̽b=̽ʎZݫ=̽҃F=̽, = W7=̽ =z=̽<<)=̽ =z=̽, = W7=̽!=~0=̽N=׻=̽ H=(=̽~1="=̽ H=(=̽N=׻=̽Pa=rw=̽<X>̽8=Y>̽=R>̽ =v=̽=k=̽I=K§=̽=k=̽ =v=̽[=+ͺ=̽[4k=5=̽Bx==̽Pa=rw=̽\C=Qg>̽n=Tc>̽S<~c>̽n=Tc>̽\C=Qg>̽g=}i>̽s =# ^>̽<X>̽\̽s =# ^>̽n=Tc>̽c!=9M_>̽ҩ?bG=̽fTOF=̽m>N]=̽ҩ?bG=̽&BP=̽1A2=̽ =Wf=̽==̽R=5=̽TV>̽@PV>̽MP>̽(ET>̽@PV>̽3[>̽fýZTh=̽-M=̽̽Z0X=̽#"=I>̽*=TB>̽=zD>̽%&&Ev2':*?>̽8 A>̽c;>̽;̽C̽/<1Q>̽C̽q}̽D<$V>̽W;nf=̽dj;)K=̽%ßO=̽dj;)K=̽W;nf=̽6X̽ >m>̽ f>̽'<"q>̽RɁ̽?<)s>̽ŝ=l=̽=V=̽_==̽C0;RV>̽IpS>̽T-#o[>̽(ZQ>̽IpS>̽2"hM>̽ =(c=̽&4 =N=̽̽$Jk?>̽@$E>̽sS'^={B>̽,o=>>̽ZV==>̽+%XG'=0 u=̽q=PQx=̽x=(=̽q=PQx=̽=0 u=̽ =(c=̽s=DL=̽@=+U=̽ag=g=̽*=@=̽ =z=̽!=~0=̽7rs=Ul=̽Pf=B[=̽`=p=̽8!=;^=̽&4 =N=̽ =(c=̽r=̽h=̽[ μ=̽æz& <)B>̽j~̽\8̽lX*=̽N¼*=̽ WA=̽&l&lX*=̽>~b=̽ZSE}=̽d .M>̽PtJ>̽^fG>̽PtJ>̽d .M>̽MP>̽.=̽97=̽? V=̽NA&8'j~̽k̽/̽Emw'^HR0w=̽jgzn=̽Ghg=̽Y,V'XJ1 =̽^HR0w=̽Ghg=̽jgzn=̽^HR0w=̽m>N]=̽k̽<]~>̽ ŭ̽Aֶ/>̽m3>̽t3>̽m3>̽Aֶ/>̽tb/>̽ɧ+5->̽Aֶ/>̽t3>̽¢w=`*=̽w=u=̽Kć=|=̽w=u=̽¢w=`*=̽ñ]=3,=̽mM};>̽yt=5>̽c;>̽yt=5>̽e4>̽c;>̽S|/T=̽i%R=̽> =̽@-%-' ̽ ̽k<Q>̽?2Q=̽<=̽-M=̽<=̽?2Q=̽ͭ:=̽L=93=̽=k=̽R=h=̽d5=̽X%$ =̽b#8=̽9>̽:*?>̽c;>̽9>̽e4>̽@3>̽e4>̽9>̽c;>̽[9Ab>̽-)J`>̽S" e>̽-)J`>̽[9Ab>֣̽jZ>̽J=6V<̽6=<{<̽=RmZ<̽6=<{<̽J=6V<̽8==2<̽D#==̽==̽==̽Ԩ4r{<̽FI<̽@HZ<̽FI<̽Ԩ4r{<̽?z<̽iq>̽~u>̽bv>̽d\>̽½"Y>̽i½/_>̽v9=̽7;CE=̽wg;=̽%;Ƶ=̽7;CE=̽K3:Q==̽7;CE=̽%;Ƶ=̽wg;=̽=k=̽:==̽I=K§=̽:==̽=k=̽L=93=̽F==̽8=(=̽#$==̽==ؠ=̽s==̽u=7=̽%;Ƶ=̽W<5=̽wg;=̽==̽}=3f=̽7=R=̽D#==̽}=3f=̽==̽/wl>̽ vr>̽Z$p>̽8h=>̽mi=>̽EM=>̽}k=G>̽mi=>̽d^= >̽ 5e>̽';;`>̽Pd>̽ 5e>̽gz,j>̽S" e>̽}k=G>̽pY=>̽]M=. >̽pY=>̽}k=G>̽m=>̽&$'ýqC>̽̽i?>̽̽0E>̽x&''̽i?>̽ýqC>̽<>̽=i=̽p= =̽Kć=|=̽p= =̽=i=̽R=5=̽m==̽=S=̽=r=̽S==̽m==̽=r=̽@'=T>̽=8O>̽=R>̽K[;UMv>̽cM61x>̽q>|>̽=o&>̽-=)>̽=n,>̽8=f,>̽-=)>̽ڸ=:/'>̽w=<>̽̽7e̽u<>ud>̽?̽S<~c>̽?̽u<>ud>̽(ݥ̽:= :>̽;=M?>̽m(=?>̽;=M?>̽:= :>̽ =:>̽Qf=<>̽?$=w6>̽~=:>̽H٥i>'nB>̽X=>̽<>̽(ϤY>̽v鯽#~Y>̽1_>̽v鯽#~Y>̽d\>̽1_>̽v鯽#~Y>̽(ϤY>̽ nT>̽=~=̽C)=p =̽Q==̽/<~=̽k<=̽==̽o=ÿ=̽R=h=̽==̽R=h=̽o=ÿ=̽R=ܫ=̽=V=̽o=ÿ=̽==̽<<̽ =zA<̽=:=g<̽~!=U<̽ =zA<̽= <̽ =zA<̽~!=U<̽=:=g<̽ ;7>̽&;T=>̽\8̽[!=J >̽o=s~>̽=J >̽&p|=̽S|/T=̽> =̽&p|=̽}M=̽$8ݚ=̽7=y/=̽닽='=̽Q==̽Ḿ&y>̽'%̽hefz>̽5e=&N>̽=%R>̽=K>̽=%R>̽5e=&N>̽J=ևT>̽T=F=̽2= A=̽!=~0=̽T=F=̽&4 =N=̽8!=;^=̽P=l>̽=Io>̽g=}i>̽=Io>̽P=l>̽V=\r>̽ :<„=̽|@̽(ϤY>̽3_>̽t",`>̽^:Q?[>̽3_>̽=o;̽= <̽`=K<̽|pe>̽Tg`_>̽uqP`>̽Tg`_>̽;0we[>̽uqP`>̽¿!<̽o;̽ <̽%5>̽m3>̽tb/>̽|pe>̽gEY/$j>̽Pd>̽SJ >̽8ͽ>̽IJ >̽4z>̽8ͽ>̽·KS{>̽97=̽仹T=̽K3:Q==̽w=<>̽b<>̽<->̽<>̽b<>̽<>̽b<>̽<>̽<->̽ uQ≯̽V>̽ nT>̽d>P5>̽g2px >̽y%Y>̽d>P5>̽F:=̽Qbh>̽F:=̽d>P5>̽y%Y>̽JH=GJ>̽#\=:YH>̽H=T_C>̽#\=:YH>̽^={B>̽H=T_C>̽g;==̽==̽=FV=̽*HO=̽v9=̽ӻ\=̽*HO=̽97=̽K3:Q==̽-=<̽~!=U<̽= <̽~!=U<̽5=<̽17=˕<̽5=<̽J=<̽17=˕<̽5=<̽~!=U<̽-=<̽d5=̽% =̽X%$ =̽;="=̽+UN==̽2=Yڃ=̽RaR5W=̽jgzn=̽m>N]=̽RaR5W=̽fTOF=̽j G=̽fTOF=̽RaR5W=̽m>N]=̽F:=̽MFH=̽T=̽ 'yw>̽K[;UMv>̽q>|>̽ 'yw>̽Ox>̽g's>̽oA=ٌ=̽p0=M=̽Z)=غ=̽U:=̽ I[ =̽HX@=̽5=*>̽z8=Y>̽EO=>̽5=*>̽4= >̽=7k>̽V=p=̽P=_=̽= -r=̽P=_=̽V=p=̽@= T=̽ v$P>̽n_$V>̽jdV>̽:7>̽X=>̽/pq9>̽‘ R=̽Ƒmd=̽puk=̽=xy>̽ª=3>̽r=љ~>̽ª=3>̽=J >̽r=љ~>̽@$E>̽/դJ>̽aFD>̽%u>̽Ty>̽]p~>̽J >̽%u>̽]p~>̽Wh<̽xrr<̽<̽= -r=̽=hX=̽7rs=Ul=̽=hX=̽= -r=̽P=_=̽*=TB>̽7=xH>̽H=T_C>̽7=xH>̽JH=GJ>̽H=T_C>̽7=xH>̽*=TB>̽#"=I>̽=oe>̽=b>̽=E>̽=b>̽=oe>̽=7k>̽RZC=Z~<̽S=o;̽.:=o;̽ʉ=<̽=+<̽w=<̽=+<̽J=6V<̽w=<̽=+<̽ʉ=<̽ל=<̽vɪ>̽·KS{>̽8ͽ>̽SJ >̽vɪ>̽8ͽ>̽r=}\>̽k=z@X>̽\=.[>̽k=z@X>̽|[=U>̽\=.[>̽k=z@X>̽r=}\>̽|=\W>̽6A9K=̽v9=̽_;:=̽v9=̽6A9K=̽ӻ\=̽+ =̽;S=̽ꓼ6=̽=F8=̽=6?=̽=<&=̽닽='=̽=F8=̽=<&=̽=x6=̽w=կ,=̽L(=dE=̽w=կ,=̽=#:=̽L(=dE=̽[~3>̽D:>̽@3>̽ ʕe>̽풔_ g>̽b60a>̽jgzn=̽~Pg=̽ `|=̽~Pg=̽puk=̽ `|=̽~Pg=̽jgzn=̽ viU=̽Lp!=̽HX@=̽ۆmo =̽HX@=̽Lp!=̽Mb-1=̽*j=XP>̽T=jgM>̽j=sS>̽=:>̽8 =>?>̽ =:>̽$*=|=̽=6y&=̽=#:=̽=6y&=̽$*=|=̽͋=;|=̽=6y&=̽cg=x5=̽=#:=̽d .M>̽]a4^R>̽MP>̽TV>̽]a4^R>̽jdV>̽]a4^R>̽TV>̽MP>̽fur==̽=A2=̽zm=ր=̽=A2=̽fur==̽=FV=̽gh'=A2=̽= -r=̽zm=ր=̽nu=2=̽N`=B=̽cg=x5=̽z]@<̽<̽ғ<̽FI<̽z]@<̽ғ<̽=?=̽닽='=̽7=y/=̽Ox>̽ z>̽OI\t>̽ z>̽Ox>̽V v>̽UADL''฽i=̽fýZTh=̽}=̽nSm>̽V0wq>̽g's>̽ A=&2>̽=/>̽=(+>̽=/>̽ A=&2>̽=I4>̽=/>̽8=f,>̽=(+>̽̽K>̽gĽAI>̽̽0E>̽r=}\>̽h=w[>̽|=\W>̽N=è>̽=>̽̽N=è>̽=b>̽=7k>̽n=)rG>̽A=QF>ִ̽=-A>̽A=QF>̽P=6B>ִ̽=-A>̽kҚ=<̽]Q=%<̽ל=<̽3Opo>̽Z$p>̽?t>̽3Opo>̽ >m>̽TbGj>̽ >m>̽3Opo>̽?t>̽Z$p>̽3Opo>̽TbGj>̽(ϤY>̽գQ>̽ nT>̽Mb-1=̽I/=̽HX@=̽oo>̽iq>̽rzk>̽2= A=̽1<=:/=̽!=~0=̽؂=PYI>̽I!=ߏN>̽g=:nI>̽R=.>̽c=4>̽ A=&2>̽R=.>̽=(>̽=b,>̽Gܗ*=̽211=̽/77=̽Xx=̽Gܗ*=̽/77=̽bO=<̽ɬz==̽͋=;|=̽=S>̽=[S>̽=FBN>̽*j=XP>̽=S>̽=FBN>̽=[S>̽=S>̽=yX>̽=#:=̽V=J=̽@= T=̽V=J=̽=#:=̽cg=x5=̽r&=c>̽:=,za>̽c!=9M_>̽:=,za>̽r&=c>̽3=-Eh>̽n=Tc>̽r&=c>̽c!=9M_>̽3o;V=̽[H=̽ҟ;I=̽U&~'&[H=̽3o;V=̽ttK~=̽6O=̽d5=̽;)=̽6O=̽%e<̽2<̽d5=̽6O=̽2<̽=[S>̽ =O>̽=FBN>̽ =O>̽|=jK>̽=FBN>̽ =O>̽=[S>̽J=ևT>̽T= :>̽:= :>̽m(=?>̽=k>>̽T= :>̽m(=?>̽m="0>̽=2>̽=n,>̽[9Ab>̽^^c>̽Vҫ]>̽^^c>̽[9Ab>̽oh>̽лiT=̽p EM=̽YC"=̽лiT=̽97=̽.=̽]=̽r L&=̽.=̽<=̽k<=̽̽=8O>̽@'=T>̽:=F =̽8/J=A<̽-=<̽5N >̽5(+>̽. >̽5(+>̽>̽. >̽211=̽z(B9=̽$̽k̽=J >̽$H>̽m3G>̽N~L>̽ߜ='=̽Ne=3=̽L=93=̽c/=KZ>̽:=,za>̽I=Z>̽:=,za>̽c/=KZ>̽c!=9M_>̽;-=3m>̽3=-Eh>̽g=}i>̽3=-Eh>̽;-=3m>̽B=7m>̽=Io>̽;-=3m>̽g=}i>̽[̽?<)s>̽̽?<)s>̽[̽(ݥ̽4Ɗ't=̽\km=̽yţV=̽ߜ='=̽;==̽eB==̽;==̽q=$=̽eB==̽ uQ>̽ׯN>̽N~L>̽[F;=̽%;Ƶ=̽K3:Q==̽S==̽c=*l=̽ŝ=l=̽c=*l=̽R=ܫ=̽ŝ=l=̽ =|=̽Ar=P=̽I=K§=̽Ar=P=̽ =|=̽ q=S+=̽Yڧ#+S=̽d?=̽ͭ:=̽d?=̽Yڧ#+S=̽ Z=̽̽<>̽-̽cټ;ep>̽VTj>̽6n>̽¿!<̽Jo;̽o;̽J=o;̽= <̽=o;̽JJ >̽%u>̽J >̽ª=3>̽J=J >̽=J >̽7=y/=̽C)=p =̽=x6=̽Q==̽C)=p =̽7=y/=̽TbGj>̽/wl>̽Z$p>̽TbGj>̽풔_ g>̽/wl>ֳ̽?'$Jk?>̽nB>̽@$E>̽Mj5'$Jk?>̽X=>̽nB>̽gL=G=̽ 7=mQV=̽ѥN=1e=̽gL=G=̽2= A=̽ 7=mQV=̽gĽAI>̽ýqC>̽̽0E>̽m3G>̽ýqC>̽gĽAI>̽nB>̽$H>̽@$E>̽nB>̽m3G>̽$H>̽g=}i>̽r&=c>̽n=Tc>̽g=}i>̽3=-Eh>̽r&=c>̽h3w>UK>̽ v$P>̽d .M>̽h3w>UK>̽o.XL>̽ v$P>̽I=Z>̽iZ= `>̽\=.[>̽:=,za>̽iZ= `>̽I=Z>̽-=)>̽m="0>̽=n,>̽-=)>̽8=f,>̽m="0>̽N`=B=̽V=J=̽cg=x5=̽=2'7rs=Ul=̽zm=ր=̽= -r=̽7&L'7rs=Ul=̽`=p=̽zm=ր=̽o=s~>̽<<>̽=J >̽ŝ=l=̽o=ÿ=̽=V=̽ŝ=l=̽R=ܫ=̽o=ÿ=̽R=.>̽=(+>̽=(>̽ A=&2>̽=(+>̽R=.>̽̽V=\r>̽[̽=z|)'R=E>̽;=M?>̽=7D>̽ɤt$'R=E>̽m(=?>̽;=M?>̽= <̽٩=z<̽`=K<̽z<̽¿!<̽ <̽<=̽ :<„=̽e̽6n>̽VTj>̽Tg`_>̽yqcZ>̽;0we[>̽V=J=̽P=_=̽@= T=̽ v$P>̽lFR>̽n_$V>̽ v$P>̽o.XL>̽lFR>̽ۆmo =̽$̽Pd>̽';;`>̽U:=̽HX@=̽I/=̽z]@<̽FI<̽ġ<̽]Q=%<̽=+<̽ל=<̽J=6V<̽=+<̽]Q=%<̽;-=3m>̽7= +s>̽B=7m>̽=Io>̽7= +s>̽;-=3m>̽o_'ִ=-A>̽Qf=<>̽~=:>̽A\2'ִ=-A>̽P=6B>̽Qf=<>̽|֜y_=̽. >̽>̽T=̽Qbh>̽F:=̽T=̽Zi'?>̽Qbh>̽ >̽/:)=̽>̽;̽̽;̽̽S<">̽e%=̽>=̽u^=̽u=7=̽={=̽R=5=̽ǁ<=۪̽<>=̽~<=̽]<Ǥ!=۪̽<>=̽ǁ<=̽_;:=̽E;=̽6A9K=̽рtB'%9=)>>̽===>̽*=TB>̽%9=)>>̽+=,8>̽===>̽Pd=1>̽+=,8>̽m9=2>̽|4,=̽&\=̽\ =̽텽A=̽&\=̽|4,=̽=oe>̽2'=c>̽z8=Y>̽J _<̽R9]<̽mɹ<̽<O;ʾ<̽R9]<̽J _<̽U<Υ<̽#O<<̽<<̽EO=>̽GVg=d>̽8h=>̽EO=>̽ O=h>̽GVg=d>̽b#8=̽;)=̽d5=̽pJ=̽;)=̽b#8=̽Vҫ]>֣̽jZ>̽[9Ab>̽Vҫ]>̽fV>֣̽jZ>̽ s>̽[k+j>̽ >m>̽ s>̽:E;\p>̽[k+j>̽=[<m>̽&*̽'<"q>̽9[=̽udvꍐ=̽b=̽Ghg=̽udvꍐ=̽9[=̽j< B>̽/̽̽Qp.(T=̽t1s=̽(c=̽Qp.(T=̽pՊ7f=̽t1s=̽ZSE}=̽\ vf=̽B|=̽a=$>̽=p >̽=(>̽=>̽=p >̽a=$>̽'V[5>̽yt=5>̽mM};>̽'V[5>̽56ke/>̽yt=5>̽R9]<̽;2<̽mɹ<̽R9]<̽gĻ&^<̽;2<̽2ż=̽ WA=̽N¼*=̽?t>̽ s>̽ >m>̽ӻ*z>̽ s>̽?t>̽h6=̽˻B8<=̽0 =̽h6=̽Qp.(T=̽˻B8<=̽3y<=̽K<0=̽8<}V=̽@Ű<*ʞ=̽K<0=̽3y<=̽&p|=̽nr =̽}M=̽> =̽nr =̽&p|=̽ĝ=.=̽#$==̽=V=̽={=̽#$==̽ĝ=.=̽N=è>̽<>̽=b>̽̽<>̽N=è>̽m`&.q'|=4Y=̽x=(=̽q=PQx=̽uG=̽-+o=̽}M=̽uG=̽y&U=̽-+o=̽03=̽v=̽~=̽˼n_>̽Vҫ]>̽^^c>̽6O=̽˼l=̽%e<̽;)=̽˼l=̽6O=̽Pt̽RɁ̽'<"q>̽lB2̽|m̽S[̽/<1Q>̽|m̽q}̽}I<6Q>̽{:̽<X>̽}I<6Q>̽/<1Q>̽{:̽&p|=̽҃F=̽S|/T=̽$8ݚ=̽҃F=̽&p|=̽'V[5>̽96>̽Gu/>̽'V[5>̽D:>̽96>̽<$ 7'O{,A>̽C\N@>̽mM};>̽^fG>̽C\N@>̽O{,A>̽*HO=̽? V=̽97=̽ӻ\=̽? V=̽*HO=̽XB*=̽ӻ\=̽6A9K=̽&{=V=̽ߞ=}=̽==ؠ=̽K<0=̽ ̽\ic>̽B^>̽9e[>̽b60a>̽\ic>̽b60a>̽˼n_>̽ ʕe>̽U>%c[F'x=(=̽2=Yڃ=̽=0 u=̽[k+j>̽ f>̽ >m>̽t1s=̽ttK~=̽(c=̽tq'bM=̽ttK~=̽t1s=̽ u=̽J _<̽mɹ<̽3=̽J _<̽ u=̽h%.K'v~N]=̽^HR0w=̽ϪX>̽9e[>̽B^>̽ʏMzU>̽9e[>̽ϪX>̽˼l=̽;S=̽%e<̽˼l=̽ꓼ6=̽;S=̽ Dm~=̽i%R=̽S|/T=̽-)J`>̽3[>̽';;`>֣̽jZ>̽3[>̽-)J`>̽1A2=̽b#8=̽X%$ =̽_==̽8=(=̽+==̽_==̽#$==̽8=(=̽dY=s=̽D=C=̽E==̽[=#י=̽D=C=̽dY=s=̽T`=w=̽ q=S+=̽fur==̽T`=w=̽[=#י=̽ q=S+=̽w=u=̽Pa=rw=̽Bx==̽ñ]=3,=̽Pa=rw=̽w=u=̽ޒF=Ã=̽:\=W=̽E==̽ñ]=3,=̽:\=W=̽ޒF=Ã=̽MP>̽n;&K>̽PtJ>̽7=R=̽J==̽=1=̽~1="=̽}=3f=̽D#==̽~1="=̽7=R=̽}=3f=̽©0=̽˻B8<=̽%ßO=̽n&&>'===>̽=zD>̽*=TB>̽N< i>̽b<>̽w=<>̽N< i>̽<>̽b<>̽s =# ^>̽S<~c>̽n=Tc>̽\̽S<~c>̽s =# ^>̽<]>̽{:̽C̽<]>̽\̽{:̽lX*=̽. b=̽>~b=̽ WA=̽. b=̽lX*=̽fSMo'uG=̽߼=̽B|=̽uG=̽bN=̽߼=̽ =Wf=̽=i=̽Bx==̽ =Wf=̽R=5=̽=i=̽O{,A>̽8 A>̽΁F>̽1&%,'O{,A>̽c;>̽8 A>̽e=5>̽qm=mS9>̽=:>̽Zbp=Q4>̽qm=mS9>̽e=5>۪̽<>=̽3<=̽~<=۪̽<>=̽<<)=̽3<=̽, = W7=̽a<D=̽&4 =N=̽<<)=̽a<D=̽, = W7=̽ =K>̽=R>̽=8O>̽ =K>̽}I<6Q>̽=R>̽N=׻=̽*]?=Q=̽>2\==̽~1="=̽*]?=Q=̽N=׻=̽lX*=̽߼=̽N¼*=̽Ys'ZSE}=̽߼=̽lX*=̽#"=I>̽ =K>̽=8O>̽=zD>̽ =K>̽#"=I>̽dY=s=̽-t==̽Ar=P=̽dY=s=̽:\=W=̽-t==̽u /z'>̽->̽g->̽.W'>̽->̽u /z'>̽ϪX>̽IpS>̽(ZQ>̽ϪX>̽T-#o[>̽IpS>̽5=*>̽EM=>̽4= >̽EO=>̽EM=>̽5=*>̽ Dm~=̽ʎZݫ=̽brM=̽ Dm~=̽҃F=̽ʎZݫ=̽YG%3'ZV==>̽H=T_C>̽^={B>̽e%1'%9=)>>̽H=T_C>̽ZV==>̽TbGj>̽\ic>̽풔_ g>̽ f>̽\ic>̽TbGj>̽ 'yw>̽HK%}>̽Ox>̽q>|>̽HK%}>̽ 'yw>̽|m̽k̽S[̽|m̽/̽k̽ڸ=:/'>̽=(+>̽8=f,>̽ڸ=:/'>̽a=$>̽=(+>̽[4k=5=̽N=׻=̽>2\==̽Pa=rw=̽N=׻=̽[4k=5=̽ Q&V&x~@m=̽ttK~=̽3o;V=̽k<Q>̽q}̽S[̽k<Q>̽D<$V>̽q}̽2"hM>̽-TCK>̽(ZQ>̽J<~X<̽̽D:>̽[~3>̽\C=Qg>̽S<~c>̽?̽==̽ĝ=.=̽=V=̽==̽[=+ͺ=̽ĝ=.=̽Kć=|=̽ =v=̽¢w=`*=̽Kć=|=̽[=+ͺ=̽ =v=̽ ̽<]~>̽k̽^<y>̽9<z>̽̽[4k=5=̽ =Wf=̽Bx==̽̽ ̽S[̽g;O>̽IpS>̽C0;RV>̽g;O>̽2"hM>̽IpS>̽v鯽#~Y≯̽V>̽d\>̽v鯽#~Y>̽ nT≯̽V>̽qm=mS9>̽,o=>>̽=:>̽qm=mS9>̽ZV==>̽,o=>>̽%ßO=̽x~@m=̽W;nf=̽ =:>̽e=5>̽=:>̽:7>̽t3>̽m3>̽/pq9>̽t3>̽:7>̽1=J>̽mi=>̽8h=>̽1=J>̽d^= >̽mi=>̽'R' <)B>̽&;T=>̽1:_A>̽B"&*)' <)B>̽\8̽&;T=>̽ =v=̽-t==̽¢w=`*=̽ =v=̽I=K§=̽-t==̽ =|=̽:==̽X1=$=̽I=K§=̽:==̽ =|=̽=i=̽w=u=̽Bx==̽Kć=|=̽w=u=̽=i=̽=R>̽8=Y>̽@'=T>̽ =O>̽5e=&N>̽|=jK>̽J=ևT>̽5e=&N>̽ =O>̽Yڧ#+S=̽?2Q=̽qkbe=̽ͭ:=̽?2Q=̽Yڧ#+S=̽H+>̽Aֶ/>̽ɧ+5->̽H+>̽tb/>̽Aֶ/>̽s =# ^>̽8=Y>̽<X>̽c!=9M_>̽8=Y>̽s =# ^>̽nB>̽ýqC>̽m3G>̽gi%'<>̽ýqC>̽nB>̽g;O>̽ ̽C;=G>̽g;O>̽k<Q>̽ ̽/r wz>̽~u>̽·KS{>̽bv>̽~u>̽/r wz>̽RYv<̽2.<̽#s<̽\@.<̽2.<̽RYv<̽,o=>>̽8 =>?>̽=:>̽*HO=̽7;CE=̽v9=̽K3:Q==̽7;CE=̽*HO=̽K3:Q==̽仹T=̽[F;=̽g=}i>̽\C=Qg>̽P=l>̽лiT=̽仹T=̽97=̽YC"=̽仹T=̽лiT=̽?̽(ݥ̽[̽<=̽̽pY=>̽D=Jr>̽=?=̽=F8=̽닽='=̽w$9';=M?>̽8 =>?>̽=7D>̽;=M?>̽ =:>̽8 =>?>̽ =(c=̽=0 u=̽8!=;^≠̽V>̽½"Y>̽d\>̽N <̽ϼ)u<̽<̼̽u>̽'%̽ vr>̽hefz>̽'%̼̽u>̽T=F=̽ 7=mQV=̽2= A=̽8!=;^=̽ 7=mQV=̽T=F=̽9>̽D:>̽:*?>̽9>̽@3>̽D:>̽лiT=̽r L&=̽p EM=̽лiT=̽.=̽r L&=̽:==̽Ne=3=̽X1=$=̽:==̽L=93=̽Ne=3=̽ 5e>̽-)J`>̽';;`>̽S" e>̽-)J`>̽ 5e>̽/<~=̽}=3f=̽==̽==̽}=3f=̽/<~=̽5=*>̽=oe>̽z8=Y>̽5=*>̽=7k>̽=oe>̽gĽAI>̽N~L>̽m3G>̽oA=ٌ=̽N=+=̽p0=M=̽^U=e=̽N=+=̽oA=ٌ=̽RaR5W=̽ viU=̽jgzn=̽j G=̽ viU=̽RaR5W=̽4=N>̽;9=$U>̽H= R>̽4=N>̽@'=T>̽;9=$U>̽ v$P>̽]a4^R>̽d .M>̽ v$P>̽jdV>̽]a4^R>̽$H>̽N~L>̽ׯN>̽=lW>̽=S>̽*j=XP>̽=lW>̽=yX>̽=S>̽ 'yw>̽V0wq>̽K[;UMv>̽ 'yw>̽g's>̽V0wq>̽m#8'=A2=̽==̽= -r=̽=FV=̽==̽=A2=̽k=z@X>̽t=R>̽|[=U>̽|=\W>̽t=R>̽k=z@X>̽k=;E=̽N`=B=̽nu=2=̽^^c>̽oh>̽VTj>̽~Pg=̽‘ R=̽puk=̽~Pg=̽ viU=̽‘ R=̽5=<̽8/J=A<̽J=<̽5=<̽-=<̽8/J=A<̽^U=e=̽m=>̽0h=p=̽pY=>̽m=>̽^U=e=̽D=Jr>̽Z)=غ=̽r0=J>̽oA=ٌ=̽Z)=غ=̽D=Jr>̽c=*l=̽"=Wz=̽R=ܫ=̽jdV>̽yqcZ>̽TV>̽n_$V>̽;0we[>̽jdV>̽;0we[>̽yqcZ>̽jdV>̽'%̽Z$p>̽ vr>̽Z$p>̽'%̽?t>̽'%̽Ḿ&y>̽?t>̽]M=. >̽4= >̽EM=>̽]M=. >̽mi=>̽}k=G>̽]M=. >̽EM=>̽mi=>̽4= >̽ ~=m >̽=7k>̽N=è>̽ ~=m >̽=>̽=7k>̽ ~=m >̽N=è>̽)L<̽N <̽;2<̽ MJd\>̽TV>̽yqcZ>̽ MJd\>̽@PV>̽TV>̽ MJd\>̽';;`>̽3[>̽3[>̽@PV>̽ MJd\>̽Mb-1=̽j G=̽fTOF=̽[<̽ԬCX<̽ʢd6<̽ԬCX<̽[<̽ I[ =̽GA<̽ʢd6<̽ԬCX<̽oh>̽nSm>̽6n>̽nSm>̽oh>̽gz,j>̽[9Ab>̽S" e>̽oh>̽S" e>̽gz,j>̽oh>̽g's>̽Ox>̽OI\t>̽6n>̽g's>̽OI\t>̽g's>̽6n>̽nSm>̽#s<̽2<̽$<̽2<̽#s<̽/+j<̽/+j<̽% =̽2<̽xrr<̽RYv<̽<̽RYv<̽$<̽<̽$<̽RYv<̽#s<̽‘ R=̽铽F=̽Ƒmd=̽铽F=̽‘ R=̽211=̽; >̽26>̽x!n>̽fTOF=̽I/=̽Mb-1=̽ҩ?bG=̽I/=̽fTOF=̽ҩ?bG=̽1A2=̽I/=̽U:=̽/+j<̽ I[ =̽U:=̽% =̽/+j<̽U:=̽X%$ =̽% =̽ԬCX<̽/+j<̽#s<̽/+j<̽ԬCX<̽ I[ =̽#s<̽2.<̽ԬCX<̽F!V=q>̽B=7m>̽7= +s>̽GP=vw>̽F!V=q>̽7= +s>̽@=+U=̽G>=*7I=̽=)=Zd=̽y=V]=̽G>=*7I=̽L(=dE=̽G>=*7I=̽y=V]=̽=)=Zd=̽ׯN>̽ uQ>̽ nT>̽=O+9>̽Qf=<>̽=i?>̽Qf=<>̽=O+9>̽?$=w6>̽&8'Qf=<>̽P=6B>̽=i?>̽#=̽b&=̽e-b=̽V=\r>̽9<z>̽N=w>̽9<z>̽V=\r>̽̽]2>̽^vн >̽i6q|>̽fur==̽zm=ր=̽T`=w=̽T`=w=̽zm=ր=̽+UN==̽b&=̽\##=̽e-b=̽\##=̽b&=̽ͭ:=̽b&=̽<=̽ͭ:=̽k=̃ =̽nu=2=̽cg=x5=̽=6y&=̽k=̃ =̽cg=x5=̽͋=;|=̽k=̃ =̽=6y&=̽kJ >̽vɪ>̽SJ >̽/r wz>̽·KS{>̽vɪ>̽Zf=~>̽S=J >̽k=J >̽S=J >̽Zf=~>̽=M=5&~>̽Zf=~>̽ mb=̵y>̽=M=5&~>̽|O<̽o;̽[!o;̽(\~}>̽kJ >̽SJ >̽kJ >̽(\~}>̽vɪ>̽vɪ>̽(\~}>̽/r wz>̽(\~}>̽q>|>̽cM61x>̽V v>̽[!J >̽J >̽[!J >̽V v>̽HK%}>̽V v>̽Ox>̽HK%}>̽ Fo;̽pC"<̽ F;o;̽K~ٺ~>̽ FJ >̽ F;J >̽$+ܣ~>̽ ŭJ >̽>xJ >̽hefz>̽$+ܣ~>̽Ḿ&y>̽̽u=̽=̽̽=̽=I>̽=>̽=̽=>̽=I>̽=`>̽==̽==̽=|=̽a6f=̽̽ =̽̽=̽½=̽̽r=̽̽S=̽̽O+9>̽%5>̽̽2>̽%5>̽:7>̽m3>̽<>̽̽O+9>̽̽i?>̽%5>̽̽O+9>̽<>̽X=>̽:7>̽<>̽%5>̽<>̽:7>̽wýQ>̽̽K>̽̽%R>̽gĽAI>̽̽K>̽wýQ>̽ uQ>̽N~L>̽wýQ>̽wýQ>̽N~L>̽gĽAI>̽̽cX>̽wýQ>̽̽%R>̽̽cX>̽½"Y>̽wýQ≯̽V>̽ uQ>̽wýQ>̽wýQ>̽½"Y≯̽V>̽=6?=̽s=DL=̽=Z0X=̽=6?=̽=F8=̽s=DL=̽=?=̽@=+U=̽s=DL=̽s=DL=̽=F8=̽=?=̽1=J>̽=k>̽*=/Z>̽!=>̽=k>̽1=J>̽;==̽ߜ='=̽L=93=̽R=h=̽;==̽L=93=̽;==̽R=h=̽q=$=̽(ϤY>̽^:Q?[>̽FV>̽2S[>̽^:Q?[>̽t",`>̽^:Q?[>̽2S[>̽FV>̽?<)s>̽^<y>̽̽^<y>̽?<)s>̽RɁ̽̽ mb=̵y>̽Luz="z>̽ mb=̵y>̽Ih=u>̽GP=vw>̽F!V=q>̽GP=vw>̽Ih=u>̽Zf=~>̽Luz="z>̽ mb=̵y>̽:= :>̽T= :>̽c=4>̽u=O8>̽T= :>̽=k>>̽T= :>̽u=O8>̽c=4>̽_u{=ok>̽o=hp>̽!={p>̽*=/Z>̽4=>̽A:=>̽G>=*7I=̽=x6=̽L(=dE=̽=x6=̽G>=*7I=̽7=y/=̽=?=̽G>=*7I=̽@=+U=̽7=y/=̽G>=*7I=̽=?=̽\=.[>̽iZ= `>̽r=}\>̽i=f>̽$t= b>̽iZ= `>̽$t= b>̽r=}\>̽iZ= `>̽i=f>̽^=Ul>̽_u{=ok>̽^=Ul>̽o=hp>̽_u{=ok>̽C;=G>̽ <)B>̽1:_A>̽C;=G>̽ ̽ <)B>̽Fm"'k̽j~̽ <)B>̽k̽ <)B>̽ ̽|mE>̽C;=G>̽1:_A>̽2"hM>̽C;=G>̽|mE>̽H)E>̽-TCK>̽|mE>̽2"hM>̽|mE>̽-TCK>̽=ۅJ>̽T=jgM>̽*j=XP>̽T=jgM>̽=ۅJ>̽R=E>̽g=:nI>̽T=jgM>̽R=E>̽?2Q=̽฽i=̽qkbe=̽fýZTh=̽฽i=̽-M=̽?2Q=̽-M=̽฽i=̽\km=̽Yڧ#+S=̽qkbe=̽\km=̽t=̽ Z=̽\km=̽ Z=̽Yڧ#+S=̽`=|<̽RZC=Z~<̽IR=u<̽RZC=Z~<̽`=|<̽S=o;̽I=J >̽pӇ=H}>̽S=J >̽&,>̽r>̽z>̽5Ә=ԙ =̽=~=̽kҚ=<̽0h=p=̽N=+=̽^U=e=̽Ne=3=̽ߜ='=̽!=޾=̽Ne=3=̽g;==̽X1=$=̽g;==̽Ne=3=̽!=޾=̽=FV=̽fur==̽ q=S+=̽=FV=̽X1=$=̽g;==̽=FV=̽ =|=̽X1=$=̽=FV=̽ q=S+=̽ =|=̽̽cX>̽̽^>̽½"Y>̽̽^>̽i½/_>̽½"Y>̽m="0>̽8=f,>̽=/>̽?$=w6>̽m="0>̽=I4>̽m="0>̽?$=w6>̽=2>̽m="0>̽=/>̽=I4>̽&=o;̽I=o;̽G=X'<̽Io;̽&o;̽m*<̽7= +s>̽=Io>̽N=w>̽|=jK>̽A=QF>̽n=)rG>̽N=+=̽>2\==̽*]?=Q=̽>2\==̽N=+=̽0h=p=̽p0=M=̽N=+=̽*]?=Q=̽==̽ =Wf=̽}=d=̽[4k=5=̽>2\==̽}=d=̽}=d=̽>2\==̽0h=p=̽[4k=5=̽}=d=̽ =Wf=̽=yX>̽[=Z>̽=[S>̽/pq9>̽X=>̽$Jk?>̽/pq9>̽$Jk?>̽D:>̽iq>̽oo>̽~u>̽LXq>̽ш%v>̽~u>̽·KS{>̽~u>̽ш%v>̽oo>̽LXq>̽~u>̽9<z>̽o=s~>̽N=w>̽.:=J >̽S=J >̽=M=5&~>̽5=>̽.:=J >̽=M=5&~>̽ =z=̽*=@=̽= <̽:=F =̽*=@=̽mA==̽-=<̽*=@=̽:=F =̽= <̽*=@=̽-=<̽(ݥ̽u<>ud>̽*̽(ݥ̽'<"q>̽?<)s>̽(ݥ̽=[<m>̽'<"q>̽(ݥ̽*̽=[<m>̽8<̽ؼ<̽%e<̽ؼ<̽8<̽<̽;S=̽+ =̽8<̽%e<̽;S=̽8<̽ϼ)u<̽ؼ<̽<̽ؼ<̽ϼ)u<̽<̽<̽ϼ)u<̽Wh<̽8/J=A<̽O)c=<̽J=<̽8/J=A<̽ka==̽O)c=<̽8/J=A<̽:=F =̽R==̽8/J=A<̽R==̽ka==̽m==̽+==̽=S=̽+==̽m==̽_==̽ŝ=l=̽m==̽S==̽ŝ=l=̽_==̽m==̽A.=8^>̽=?ad>̽=b>̽b=]>̽A.=8^>̽=b>̽/wl>̽cټ;ep>̽ vr>̽VTj>̽cټ;ep>̽/wl>̽풔_ g>̽ ʕe>̽/wl>̽/wl>̽ ʕe>̽VTj>̽cټ;ep>̼̽u>̽ vr>̼̽u>̽cټ;ep>̽OI\t>̼̽u>̽OI\t>̽ z>̽̽bd>̽ջIc>̽i½/_>̽ջIc>̽̽bd>̽h>̽| g>̽ջIc>̽h>̽>x̽|=\W>̽h=w[>̽j=sS>̽=lW>̽*j=XP>̽[F;=̽|@̽1_>̽ջIc>̽ջIc>̽1_>̽d\>̽d\>̽i½/_>̽ջIc>̽/<~=̽==̽̽iq>̽M]n>̽rzk>̽|pe>̽\f>̽gEY/$j>̽|pe>̽rzk>̽M]n>̽gEY/$j>̽rzk>̽H= R>̽I=Z>̽|[=U>̽H= R>̽;9=$U>̽I=Z>̽I=Z>̽\=.[>̽|[=U>̽JH=GJ>̽4=N>̽H= R>̽7=xH>̽4=N>̽JH=GJ>̽=8O>̽4=N>̽#"=I>̽#"=I>̽4=N>̽7=xH>̽ġ<̽=̽3Wc<̽ޘ<̽ġ<̽?z<̽ġ<̽FI<̽?z<̽ġ<̽ޘ<̽=̽p=~3>̽:= :>̽c=4>̽ =:>̽:= :>̽p=~3>̽7އ=.>̽e=5>̽p=~3>̽ =:>̽p=~3>̽e=5>̽/{ =̽h=̽]=̽h=̽/{ =̽[ μ=̽z(B9=̽211=̽‘ R=̽͋=;|=̽$*=|=̽5Ә=ԙ =̽=~=̽]Q=%<̽kҚ=<̽]Q=%<̽=^<̽8==2<̽J=6V<̽]Q=%<̽8==2<̽=^<̽]Q=%<̽=~=̽h8 >̽5(+>̽5N >̽5(+>̽h8 >̽ >̽>̽5(+>̽ >̽&,>̽z>̽x!n>̽4= >̽]M=. >̽r0=J>̽ ~=m >̽4= >̽r0=J>̽D=Jr>̽r0=J>̽]M=. >̽gz,j>̽V0wq>̽nSm>̽^=~>̽={>̽A*=w>̽I=J >̽^=~>̽pӇ=H}>̽p0=M=̽==̽Z)=غ=̽==̽p0=M=̽D#==̽ |>̽Ty>̽%u>̽=xy>̽={>̽ª=3>̽;0we[>̽7LJ[>̽uqP`>̽7LJ[>̽;0we[>̽n_$V>̽䚺̽@3>̽e4>̽@3>̽g->̽->̽[~3>̽@3>̽->̽t=R>̽vy[=ϬN>̽|[=U>̽1<=:/=̽X=\.=̽mA==̽X=\.=̽1<=:/=̽gL=G=̽1<=:/=̽2= A=̽gL=G=̽+ =̽\cc <̽8<̽Sa =̽\cc <̽+ =̽mɹ<̽\cc <̽ u=̽Sa =̽ u=̽\cc <̽w=կ,=̽=x6=̽C)=p =̽w=կ,=̽$*=|=̽=#:=̽6=̽J=̽ZS,=̽6=̽t1:l=̽J=̽vEk>'yţV=̽J=̽t=̽*?*>̽^'>̽.>̽½=̽½=̽̽r=̽a6f=̽½=̽ʮ=̽S%>̽+1$>̽p%+>̽ 0&>̽g M%>̽S%>̽Pv=̽=̽ϰ1=̽y,=̽Bu V=̽u^=̽$Rc#>̽^'>̽*?*>̽=̽*+_=̽̽=̽=̽*+_=̽y,=̽2:< }>̽/b; ~>̽ ̽3;F<̽YF<<̽ =̽W;2>̽7<.>̽ɂ;.>̽=ļvM>̽$+ܣ~>̽hefz>̽6>̽89xl>̽̽ޣ{+>̽Nӥ>̽A>̽Nӥ>̽ޣ{+>̽WX>̽ ;7>̽Hf9:>̽&;T=>̽N >̽9#>̽$>̽C,w>̽񞝽>̽ҝI>̽A>̽񞝽>̽ޣ{+>̽񞝽>̽C,w>̽ޣ{+>̽- <=̽E;=̽_;:=̽8.>̽0>̽T _85>̽$+ܣ~>̽\D}>̽Ḿ&y>̽KN}>̽/:)=̽ >̽=[>̽G=>̽=7>̽G=>̽=[>̽m=Y+>̽[<3>̽nE̽킔̽nE̽[<3>̽ʴ<->̽޼=̽ {=̽. 0>̽#Ш7>̽89xl>̽6>̽ҝI>̽ե8>̽C,w>̽5>̽w>̽i6q|>̽\ vf=̽9μR=̽pJ=̽¹)>̽=c/>̽ɂ;.>̽nE̽G̽킔̽G̽\8̽킔̽ :>̽x!n>̽z>̽x!n>̽ :>̽; >̽Zx=̽Bu V=̽y,=̽Bu V=̽Zx=̽m=̽x=̽FEa0=̽fO =̽\ =̽x=̽fO =̽]<Ǥ!=̽=̽=̽nE̽ʜ<+>̽7<.>̽ʜ<+>̽nE̽ʴ<->̽m )>̽yS(>̽zc_g">̽r=}\>̽=Na>̽h=w[>̽=Na>̽r=}\>̽$t= b>̽Qi~<̽jҷ_<̽PVh6<̽jҷ_<̽Qi~<̽}@C<̽X=>̽=r>̽ߞ=}=̽Jp>̽h>̽&,>̽h>̽r>̽&,>̽ǽs=̽89xl>̽m=̽򛸽B=̽ʮ=̽½=̽½=̽򛸽B=̽½=̽_"@>̽H)E>̽|mE>̽k̽_"@>̽|mE>̽ҪA=̽y=̽ϰ1=̽=̽E;=̽75;(ɤ=̽/,>̽C,w>̽ե8>̽0Ϩ1=̽qOe=̽4=̽g>̽% >̽WX>̽=̽|_=̽ϰ1=̽,<欩=̽@Ű<*ʞ=̽3y<=̽|O<̽<̽o;̽^׼>$<̽<̽Wh<̽ڿ=?g>̽΍=Qa>̽Ų=~g>̽ؼFX>̽Vҫ]>̽˼n_>̽sU=̽_=̽qx==̽;$>̽S<">̽f;Hp>̽=_>̽΍=Qa>̽ڿ=?g>̽=bd>̽=_>̽ڿ=?g>̽p'=c9,>̽=ZV+>̽Pd=1>̽p'=c9,>̽?9= ,>̽J0=Z&>̽Z r=̽ {=̽r=̽I]I>̽Clt\O>̽-TCK>̽H)E>̽I]I>̽-TCK>̽R3=e>̽1}=j>̽=?ad>̽D₼=̽XB*=̽`sR=̽M=6g>̽3=-Eh>̽B=7m>̽^=Ul>̽M=6g>̽B=7m>̽J0=Z&>̽:I=m)>̽P=>K$>̽:I=m)>̽J0=Z&>̽?9= ,>̽̽A̹>>̽T6>̽A̹>>̽% >̽g>̽[->̽yS(>̽m )>̽56ke/>̽[->̽m )>̽=>̽=p >̽'O=>̽=>̽{=5 >̽׋=Ӿ&>̽\=̽VpB:㮘=̽[H=̽7;Z=̽VpB:㮘=̽75;(ɤ=̽>W:i>̽*-;">̽f;Hp>̽{X;>&>̽*-;">̽$>̽/E#>̽y i'>̽.W'>̽/E#>̽Nӥ>̽WX>̽I_<}>̽!<2>̽̽U>P>̽^vн >̽U>P>̽i6q|>̽^vн >̽o<{S=̽̽ӻ*z>̽K~ٺ~>̽ӻ*z>̽w8qx>̽ s>̽f< >̽N<>̽NJ̽nD̽S<">̽;$>̽=yX>̽A=R]>̽[=Z>̽A=R]>̽΍=Qa>̽[=Z>̽A=R]>̽=yX>̽b=]>̽i%R=̽(=̽> =̽(=̽i%R=̽=̽(=̽LKV=̽> =̽5*>̽p%+>̽0>̽={=̽=$=̽F==̽s==̽=$=̽u=7=̽=$=̽s==̽F==̽=$=̽={=̽u=7=̽4=̽1|=̽&\=̽1|=̽4=̽衽R==̽(ET>̽gT>̽l!P>̽fV>̽gT>֣̽jZ>̽gT>̽fV>̽l!P>̽gT>̽(ET>֣̽jZ>̽ #=>̽L=g~>̽m=Y+>̽V0= >̽L=g~>̽x=ے>̽X=>̽u=G>̽4=>̽u=G>̽A:=>̽4=>̽u /z'>̽">с!>̽.W'>̽z=̽t1:l=̽6=̽#O<<̽+<`<̽<<̽+<`<̽#O<<̽ǁ<=̽H)E>̽G8ID>̽I]I>̽Zi'?>̽}e>̽Qbh>̽(< >̽N<>̽f< >̽. >̽ >̽5N >̽ҝI>̽ >̽ե8>̽ >̽ҝI>̽5N >̽J:I>̽ͼ&N>̽驼jO>̽<5i<̽u,; <̽U<Υ<̽<5i<̽YF<<̽3;F<̽u,; <̽<5i<̽3;F<̽==̽==ؠ=̽=.G=̽ɚ=>̽=k>̽'O=>̽!=>̽Q=Z>̽=k>̽Q=Z>̽!=>̽{=5 >̽Q=Z>̽'O=>̽=k>̽̽>̽ý>̽̽>̽ý>̽ѫgO>̽̽>̽ý>̽̽>̽T6>̽ʮ=̽/#=̽a6f=̽/#=̽踽s=̽a6f=̽Me=, >̽k= >̽x=ے>̽k= >̽Me=, >̽=>̽\=>̽C,w>̽/,>̽C,w>̽\=>̽g>̽t1:l=̽CH=̽J=̽CH=̽t1:l=̽,%=̽̽K=Y>̽=lW>̽!Т=̽y%Y>̽. 0>̽y%Y>̽!Т=̽F:=̽گ=̽;強=̽[ μ=̽/{ =̽گ=̽[ μ=̽ӻ\=̽H=̽? V=̽H=̽ӻ\=̽XB*=̽cCPj<̽̽:E;\p>̽&*̽=[<m>̽l̽&*̽-T$>̽~>̽$Rc#>̽9#>̽-T$>̽$Rc#>̽4P̽)=%>̽= >̽0>̽`.0>̽i 5>̽`.0>̽96>̽i 5>̽~=̽ ==̽yţV=̽ ==̽J=̽yţV=̽k==̽X=\.=̽nu=2=̽k=̃ =̽k==̽nu=2=̽j~̽j8< ?>̽킔̽%93'j8< ?>̽j~̽/̽=̽K7.=̽,%=̽K7.=̽=̽i%R=̽#P=.>̽m9=2>̽Y=65>̽w< Y=̽< H=̽6X̽=hv>̽!={p>̽Z{=Gt>̽Ih=u>̽Luz="z>̽=hv>̽Z{=Gt>̽Luz="z>̽R<4>̽w=<>̽7e̽w=<>̽R<4>̽N< i>̽l!P>̽m'sM>̽(ET>̽n;&K>̽m'sM>̽}(̽L;Ձ<̽J _<̽3=̽m>N]=̽7D^=̽ҩ?bG=̽7D^=̽m>N]=̽v~̽Jp>̽fÛt >̽sX+>̽ۻj'>̽*?*>̽9#>̽ۻj'>̽$>̽J=̽z/A=̽!=̽/{ =̽ige=̽yӋ?=̽ige=̽/{ =̽]=̽7LJ[>̽R`>̽uqP`>̽R`>̽7LJ[>̽2S[>̽!'=l=̽D=C=̽;="=̽H<=̽μ}=̽޼=̽μ}=̽ {=̽޼=̽YF<<̽x̽D=a>̽~ =">̽D @ >̽(z>̽. 0>̽>=̽$R=̽u^=̽T>̽ؼ w>̽߼>̽U=TQ=̽ #=>̽m=Y+>̽ #=>̽U=TQ=̽=.G=̽=>̽-^=>̽=p >̽-^=>̽'O=>̽=p >̽x=(=̽&===̽2=Yڃ=̽&===̽;="=̽2=Yڃ=̽2:< }>̽ؖ<~>̽RɁ̽/7'&"HN?>̽2%A>̽D:>̽̈́< >̽<->̽<>̽̈́< >̽N<>̽J<.M>̽<->̽̈́< >̽J<.M>̽̽W`̽S<">̽ <.>̽E<)>̽ʴ<->̽ <.>̽<+T4>̽s=1>̽m=3#>̽-=)>̽=o&>̽-=)>̽m=3#>̽ڸ=:/'>̽ޘ5Dj>̽e>̽>PA#e>̽;f=%>̽=p >̽=>̽׋=Ӿ&>̽;f=%>̽=>̽sU=ׁ̽=̽uԪ=̽\ =ׁ̽=̽x=̽ {=N>̽ #=>̽=r>̽ {=N>̽Me=, >̽x=ے>̽ WA=̽} `=̽. b=̽=Y>̽A:=>̽5=w>̽%'===>̽5v=}u>>̽=zD>̽5v=}u>>̽===>̽H=|8>̽%^'x:'5v=}u>>̽j< B>̽=zD>̽% >̽D`$">̽WX>̽mxP=̽sU=̽qx==̽mxP=̽FEa0=̽x=̽y=>̽8h=>̽GVg=d>̽y=>̽!=>̽1=J>̽8h=>̽y=>̽1=J>̽|ü[=̽ʟMC=̽h=̽r=̽|ü[=̽h=̽LKV=̽y=̽bӼ=̽y=̽;強=̽bӼ=̽mM};>̽W-<>̽'V[5>̽W-<>̽D:>̽'V[5>̽[ХUP'W-<>̽mM};>̽C\N@>̽<O;ʾ<̽;/<̽U<Υ<̽;/<̽#O<<̽U<Υ<̽Bu V=̽f =̽u^=̽f =̽e%=̽u^=̽\ vf=̽Wlc=̽B|=̽Wlc=̽KIi=̽B|=̽Wlc=̽\ vf=̽pJ=̽o>̽nl-=̽H<=̽nl-=̽o>̽@t=̽ =z=̽-<\<̽3<=̽-<\<̽ =z=̽= <̽75;(ɤ=̽5dң=̽=̽׍/=G'=̽ޒF=Ã=̽E==̽{%G/eE>̽}(̽2%A>̽}(̽{%G/eE>̽n;&K>̽]'Eҕ=̽\=̽bM=̽\=̽]'Eҕ=̽CN8=̽. b=̽]'Eҕ=̽bM=̽~ >̽x!n>̽26>̽Q>̽~ >̽26>̽4b>̽9K, >̽]2>̽i6q|>̽4b>̽]2>̽p= =̽ ̘==̽ĝ=.=̽={=̽ ̘==̽R=5=̽ ̘==̽={=̽ĝ=.=̽ ̘==̽p= =̽R=5=̽̽)̽}I<6Q>̽/<1Q>̽)̽|m̽)̽/<1Q>̽}I<6Q>̽)̽̽|m̽ +>̽+1$>̽S%>̽.8P>̽MP>̽@PV>̽MP>̽.8P>̽n;&K>̽(ET>̽.8P>̽@PV>̽ܟy=m>̽S=J >̽pӇ=H}>̽ܟy=m>̽Luz="z>̽Zf=~>̽Luz="z>̽ܟy=m>̽pӇ=H}>̽~3>̽0`*]5>̽.>̽8 A>̽\%F>̽΁F>̽ث= >̽ ==j>̽=>̽===̽=3=̽+==̽8=(=̽===̽+==̽=0E>̽= H>̽=K>̽|=jK>̽= H>̽A=QF>̽B&n=̽KIi=̽&BP=̽P&-1'B&n=̽v~̽V0= >̽x=ے>̽V0= >̽+= >̽=I>̽5=>̽2'=c>̽~ =">̽u=̽ʎZݫ=̽b=̽u=̽٣=̽텽A=̽8o\<̽jҷ_<̽yvc<̽ʢd6<̽8o\<̽yvc<̽5=>̽4&=}>̽[!=J >̽o=s~>̽4&=}>̽N=w>̽4&=}>̽o=s~>̽[!=J >̽7<.>̽2{;)>̽ɂ;.>̽{X;>&>̽2{;)>̽;$>̽M =̽b=̽҃F=̽$8ݚ=̽M =̽҃F=̽b=̽M =̽9[=̽ =Yb>̽b=]>̽=b>̽ =Yb>̽Ų=~g>̽΍=Qa>̽ZSS=̽KIi=̽Wlc=̽pJ=̽ZSS=̽Wlc=̽1=̽#=̽̽<&=̽#=̽1=̽b&=̽2=7=̽R=5=̽==̽2=7=̽&{=V=̽u=7=̽&{=V=̽2=7=̽==̽R=5=̽2=7=̽u=7=̽Y=65>̽B=a8>̽ZV==>̽B=a8>̽%9=)>>̽ZV==>̽o=$>̽=+'>̽~ =">̽=+'>̽J0=Z&>̽~ =">̽=+'>̽o=$>̽=ZV+>̽y=.<̽bO=<̽͋=;|=̽5Ә=ԙ =̽y=.<̽͋=;|=̽bO=<̽y=.<̽ל=<̽0=>̽=`>̽=I>̽̽p̽j< B>̽=zD>̽p̽ =K>̽p̽=zD>̽j< B>̽p̽̽ =K>̽pՊ7f=̽.2b=̽ZSE}=̽.2b=̽\ vf=̽ZSE}=̽nr =̽!B=̽bN=̽!B=̽2ż=̽bN=̽i;O:>̽>W:i>̽f;Hp>̽C̽Ok<G[>̽<]>̽Ok<G[>̽?\< `>̽<]>̽Ok<G[>̽C̽D<$V>̽N=T~=̽;="=̽D=C=̽N=T~=̽[=#י=̽T`=w=̽[=#י=̽N=T~=̽D=C=̽J==̽U"=ߕ=̽=1=̽)7<̽cCPj<̽\@.<̽;&=̽Y;=̽©0=̽Y;=̽3=̽©0=̽)=%>̽.=A>̽=>̽#u7'B|=̽4R|=̽uG=̽h@'4R|=̽y&U=̽uG=̽4R|=̽B|=̽KIi=̽=c/>̽;3>̽ɂ;.>̽=36>̽X=>̽4=>̽X=>̽=36>̽=r>̽YW=̽h6=̽0 =̽YW=̽Sa =̽ꓼ6=̽Sa =̽YW=̽0 =̽h6=̽YW=̽ꓼ6=̽brM=̽Oж=̽ Dm~=̽2S[>̽d+,W>̽FV>̽d+,W>̽lFR>̽FV>̽d+,W>̽2S[>̽7LJ[>̽.p%=̽|֜y_=̽>̽/:)=̽.p%=̽>̽GP=vw>̽/7=Az>̽=M=5&~>̽/7=Az>̽5=>̽=M=5&~>̽W==̽C)=p =̽=~=̽5Ә=ԙ =̽W==̽=~=̽C)=p =̽W==̽w=կ,=̽E<)>̽<)>̽7e̽<)>̽o=$>̽7e̽`o'=̽`sR=̽XB*=̽Ow=<̽k=o;̽`=|<̽!s=!=b<̽Ow=<̽`=|<̽Qܿ̽}(̽H>̽.1=*=̽ H=(=̽ޒF=Ã=̽_<==̽+<0=̽W<5=̽+<0=̽wg;=̽W<5=̽Clt\O>̽h0DI>̽驼jO>̽h0DI>̽Clt\O>̽I]I>̽-/'?<=̽|=4Y=̽q=PQx=̽%wO'?<=̽N̽&*̽:E;\p>̽Qu>̽rux>̽4z>̽ |>̽rux>̽z8u>̽rux>̽ |>̽4z>̽rux>̽Qu>̽z8u>̽E<)>̽0<ˌ'>̽ʴ<->̽= H>̽5e=&N>̽=K>̽|=jK>̽5e=&N>̽= H>̽ =Yb>̽c=g>̽Ų=~g>̽=b>̽c=g>̽ =Yb>̽FV>̽գQ>̽(ϤY>̽e>̽t",`>̽>PA#e>̽=^>̽=_>̽=bd>̽W`̽f;Hp>̽S<">̽qOe=̽衽R==̽4=̽ҪA=̽衽R==̽qOe=̽J=̽odȿ=̽ZS,=̽J=̽FEa0=̽odȿ=̽D=a>̽o=$>̽~ =">̽J0=Z&>̽5=>̽~ =">̽J0=Z&>̽P=>K$>̽5=>̽6A9K=̽E;=̽=̽;$>̽*-;">̽{X;>&>̽;$>̽f;Hp>̽*-;">̽p'=c9,>̽m9=2>̽?9= ,>̽Pd=1>̽m9=2>̽p'=c9,>̽(z>̽޼=̽. 0>̽;2<̽?<̽)L<̽;2<̽gĻ&^<̽?<̽VpB:㮘=̽ҟ;I=̽[H=̽7;Z=̽ҟ;I=̽VpB:㮘=̽} `=̽1Rڴ=̽CN8=̽ܶ<}=̽/<=̽<ͺ=̽\D}>̽?t>̽Ḿ&y>̽ӻ*z>̽?t>̽\D}>̽-^=>̽ɚ=>̽'O=>̽ث= >̽ɚ=>̽-^=>̽.=A>̽-^=>̽=>̽ث= >̽-^=>̽.=A>̽&===̽!'=l=̽;="=̽H=̽D₼=̽yӋ?=̽XB*=̽D₼=̽H=̽m'sM>̽H>̽}(̽l!P>̽H>̽m'sM>̽̽=>̽'O=>̽{=5 >̽=>̽Q=Z>̽c<:>̽5v=}u>>̽H=|8>̽Ḧ'c<:>̽j< B>̽5v=}u>>̽7D^=̽B&n=̽&BP=̽7D^=̽v~̽e=5>̽7އ=.>̽Zbp=Q4>̽e=5>̽yp=/>̽/E#>̽">с!>̽Nӥ>̽/E#>̽.W'>̽">с!>̽]'Eҕ=̽} `=̽CN8=̽. b=̽} `=̽]'Eҕ=̽m'sM>̽.8P>̽(ET>̽m'sM>̽n;&K>̽.8P>̽k= >̽=>̽ ==j>̽o<{S=̽Qܿ̽"HN?>̽{%G/eE>̽NJ̽̈́< >̽<>̽NJ̽N<>̽̈́< >̽B&=̽8]=̽XJ1 =̽-+o=̽8]=̽B&=̽W-<>̽"HN?>̽D:>̽щW.'C\N@>̽"HN?>̽W-<>̽`o'=̽1Rڴ=̽`sR=̽̽=+'>̽=ZV+>̽p'=c9,>̽J0=Z&>̽=+'>̽=I>̽+= >̽0=>̽\%F>̽o.XL>̽΁F>̽˘!<d=̽8<}V=̽7;Z=̽3y<=̽8<}V=̽˘!<d=̽GA<̽8o\<̽ʢd6<̽D=a>̽=oe>̽=E>̽2'=c>̽=oe>̽D=a>̽,'/'wU&1:_A>̽Hf9:>̽I?>̽e-G'1:_A>̽&;T=>̽Hf9:>̽v= E>̽t=K>̽؂=PYI>̽~ >̽&,>̽x!n>̽o=hp>̽Z{=Gt>̽!={p>̽o=hp>̽Ih=u>̽Z{=Gt>̽So;̽Qi~<̽ko;̽So;̽}@C<̽Qi~<̽<->̽J<.M>̽W`̽8.>̽5*>̽0>̽=E>̽N< i>̽R<4>̽d+,W>̽n_$V>̽lFR>̽7LJ[>̽n_$V>̽d+,W>̽xrr<̽)7<̽\@.<̽xrr<̽|O<̽)7<̽xrr<̽<̽|O<̽xrr<̽Wh<̽<̽=1 >̽m=3#>̽=o&>̽m=3#>̽= >̽ڸ=:/'>̽̽1 >̽T6>̽̽>̽ܟy=m>̽k=J >̽S=J >̽Zf=~>̽k=J >̽ܟy=m>̽h=w[>̽=Na>̽R=`>̽t",`>̽R`>̽2S[>̽Qi~<̽So;̽ko;̽PVh6<̽So;̽Qi~<̽P=6B>̽= H>̽=0E>̽P=6B>̽A=QF>̽= H>̽ؼ w>̽𱼏>̽߼>̽ =Yb>̽A=R]>̽b=]>̽΍=Qa>̽A=R]>̽ =Yb>̽~<=̽+<`<̽ǁ<=̽~<=̽䚺̽L=g~>̽V0= >̽G=>̽m=Y+>̽L=g~>̽$Rc#>̽ۻj'>̽9#>̽$Rc#>̽*?*>̽ۻj'>̽U斻>̽N >̽>W:i>̽Pv=̽y=̽½=̽Pv=̽ϰ1=̽y=̽ +>̽g M%>̽QQ >̽S%>̽g M%>̽ +>̽Zx=̽*+_=̽=̽y,=̽*+_=̽Zx=̽g>̽ޣ{+>̽C,w>̽WX>̽ޣ{+>̽g>̽/E#>̽D`$">̽y i'>̽/E#>̽WX>̽D`$">̽N >̽*-;">̽>W:i>̽N >̽$>̽*-;">̽qx==̽_=̽B k1h=̽mxP=̽odȿ=̽FEa0=̽qx==̽odȿ=̽mxP=̽ʜ<+>̽0<ˌ'>̽ƥ̽ʜ<+>̽ʴ<->̽0<ˌ'>̽ <.>̽[<3>̽<+T4>̽ʴ<->̽[<3>̽ <.>̽W;2>̽;3>̽ ;7>̽W;2>̽ɂ;.>̽;3>̽¹)>̽2{;)>̽{X;>&>̽¹)>̽ɂ;.>̽2{;)>̽Ə"=ׁ̽=̽\ =̽uԪ=ׁ̽=̽Ə"=̽ý>̽A̹>>̽ѫgO>̽T6>̽A̹>>̽ý>̽<v=̽3>̽;3>̽=c/>̽qT>̽驼jO>̽ͼ&N>̽#Ш7>̽>2 >̽. >̽#Ш7>̽6>̽>2 >̽u=G>̽5=w>̽A:=>̽ǽs=̽Zx=̽=̽m=̽Zx=̽ǽs=̽y=̽򛸽B=̽½=̽>2 >̽ >̽. >̽>2 >̽ե8>̽ >̽.2b=̽9μR=̽\ vf=̽ +>̽~D<">̽+1$>̽mxP=ׁ̽=̽sU=̽x=ׁ̽=̽mxP=̽J:I>̽h0DI>̽ilgD>̽J:I>̽驼jO>̽h0DI>̽꼑;R>̽qT>̽ͼ&N>̽ {=N>̽=36>̽Me=, >̽ {=N>̽=r>̽=36>̽)7<̽>̽\=>̽ѫgO>̽A̹>>̽g>̽\=>̽J:I>̽[I>̽ͼ&N>̽nD̽2{;)>̽7<.>̽nD̽;$>̽2{;)>̽h0DI>̽G8ID>̽ilgD>̽I]I>̽G8ID>̽h0DI>̽=Y>̽;==̽m=>̽5=w>̽;==̽=Y>̽x=ے>̽k= >̽+= >̽ {=N>̽L=g~>̽ #=>̽x=ے>̽L=g~>̽ {=N>̽==̽U=TQ=̽=l=̽==̽=.G=̽U=TQ=̽ż$̽l̽=[<m>̽;/<̽̽(z>̽D @ >̽.=A>̽ ==j>̽ث= >̽$+ܣ~>̽=ļvM>̽ ŭJ >̽_u{=ok>̽ӂ=ǝf>̽i=f>̽ӂ=ǝf>̽$t= b>̽i=f>̽$t= b>̽ӂ=ǝf>̽=Na>̽ F;J >̽ ̽/b; ~>̽K~ٺ~>̽ F;J >̽/b; ~>̽ ̽o=s~>̽9<z>̽T>̽̽ؼ w>̽#P=.>̽?9= ,>̽m9=2>̽#P=.>̽:I=m)>̽?9= ,>̽k=o;̽Ow=<̽S=o;̽p_=i<̽Ow=<̽!s=!=b<̽Ow=<̽p_=i<̽S=o;̽Ƒmd=̽:8z=̽puk=̽:8z=̽Ƒmd=̽t=̽5L'J=̽:8z=̽t=̽`.0>̽Gu/>̽96>̽c:=*>̽Gu/>̽`.0>̽`.0>̽0>̽p%+>̽p%+>̽c:=*>̽`.0>̽4b>̽i6q|>̽w>̽4b>̽S/>̽9K, >̽m>̽S/>̽4b>̽w>̽m>̽4b>̽ +>̽QQ >̽!v;>̽S/>̽!v;>̽9K, >̽S/>̽~D<">̽!v;>̽!v;>̽~D<">̽ +>̽t=R>̽t=K>̽vy[=ϬN>̽|_=̽=̽y,=̽u^=̽|_=̽y,=̽F==̽j==̽8=(=̽j==̽F==̽s==̽yӋ?=̽گ=̽/{ =̽D₼=̽گ=̽yӋ?=̽گ=̽bӼ=̽;強=̽^gM>̽FV>̽lFR>̽!̽0`*]5>̽~3>̽ #=>̽=.G=̽=r>̽=.G=̽ߞ=}=̽=r>̽ߞ=}=̽=.G=̽==ؠ=̽w=̽,%=̽t1:l=̽,%=̽w=̽=̽z=̽w=̽t1:l=̽=k=̽=[>̽=u=̽=[>̽=k=̽m=Y+>̽U=TQ=̽=k=̽=l=̽m=Y+>̽=k=̽U=TQ=̽=̽ýG=̽*+_=̽ýG=̽̽3=̽̽|=̽ýG=̽=̽Pv=̽ F23':8z=̽!=̽ `|=̽,P\E':8z=̽J=̽!=̽puk=̽:8z=̽ `|=̽[+=̽T=̽MFH=̽T=̽[+=̽\(r_=̽B k1h=̽\(r_=̽[+=̽͋=;|=̽ɬz==̽k=̃ =̽5*>̽ 0&>̽S%>̽p%+>̽5*>̽S%>̽ ڊ>̽U>P>̽h8 >̽U>P>̽ ڊ>̽5>̽i6q|>̽U>P>̽5>̽B=a8>̽Y=65>̽m9=2>̽+=,8>̽B=a8>̽m9=2>̽B=a8>̽+=,8>̽%9=)>>̽5=>̽ O=h>̽z8=Y>̽ O=h>̽5=>̽P=>K$>̽2'=c>̽5=>̽z8=Y>̽;強=̽}U=̽[ μ=̽=6>̽H=|8>̽===>̽+=,8>̽=6>̽===>̽=6>̽+=,8>̽Pd=1>̽̽>̽6>̽Oж=̽brM=̽fO =̽ =̽0Ϩ1=̽4=̽z/A=̽J=̽ ==̽v=̽ ==̽~=̽ ==̽v=̽^=̽^=̽z/A=̽ ==̽/#=̽ʮ=̽0Ϩ1=̽/#=̽03=̽踽s=̽h/[=̽03=̽/#=̽0Ϩ1=̽h/[=̽/#=̽(=̽03=̽~=̽03=̽(=̽踽s=̽s=1>̽Pd=1>̽=ZV+>̽=6>̽Pd=1>̽s=1>̽s=1>̽<+T4>̽H=|8>̽H=|8>̽=6>̽s=1>̽˼l=̽~g,/=̽ꓼ6=̽~g,/=̽˼l=̽;)=̽;)=̽ м@=̽~g,/=̽cU>̽9e[>̽ʏMzU>̽=k=̽=u=̽==̽==̽==̽=l=̽=l=̽=k=̽==̽؂=PYI>̽t=K>̽I!=ߏN>̽t=K>̽t=R>̽I!=ߏN>̽(̽)=%>̽=`>̽.=A>̽)=%>̽0=>̽0=>̽ ==j>̽.=A>̽=`>̽{==>̽=>̽{==>̽=`>̽)=%>̽{==>̽=1 >̽=>̽kY'\%F>̽:*?>̽aFD>̽&t&:*?>̽\%F>̽8 A>̽oA=ٌ=̽D=Jr>̽^U=e=̽pY=>̽^U=e=̽D=Jr>̽5lw.>̽e4>̽yt=5>̽e4>̽5lw.>̽g->̽56ke/>̽5lw.>̽yt=5>̽|u>&_"@>̽I?>̽8>̽d('׆&_"@>̽9d?>̽H)E>̽)'3F&M:>̽9d?>̽_"@>̽t&m'8>̽M:>̽_"@>̽.B'9d?>̽G8ID>̽H)E>̽̽6?=̽̽Z0X=̽-M=̽=cX>̽=%R>̽J=ևT>̽CN8=̽5dң=̽\=̽VpB:㮘=̽5dң=̽75;(ɤ=̽\=̽5dң=̽VpB:㮘=̽ ;7>̽;3>̽Hf9:>̽Hf9:>̽8>̽I?>̽0P,4>̽8>̽Hf9:>̽0P,4>̽Hf9:>̽;3>̽ <.>̽<)>̽E<)>̽=ZV+>̽<)>̽s=1>̽<)>̽=ZV+>̽o=$>̽s=1>̽<)>̽ <.>̽ H=(=̽.1=*=̽~1="=̽.1=*=̽7=R=̽~1="=̽7=R=̽.1=*=̽J==̽$H>̽/դJ>̽@$E>̽$H>̽ׯN>̽/դJ>̽V=J=̽=hX=̽P=_=̽10>̽sX+>̽*?*>̽.>̽10>̽*?*>̽¹)>̽sX+>̽=c/>̽¹)>̽ۻj'>̽sX+>̽¹)>̽{X;>&>̽$>̽¹)>̽$>̽ۻj'>̽10>̽=c/>̽sX+>̽0P,4>̽=c/>̽10>̽8>̽0P,4>̽10>̽Lw=̽>=̽e%=̽>=̽Lw=̽sU=̽_=̽sU=̽Lw=̽==̽ߞ=}=̽&{=V=̽==̽X=>̽ߞ=}=̽u=G>̽X=>̽==̽==̽5=w>̽u=G>̽r>̽U斻>̽z>̽; >̽(< >̽%g;>̽nD̽7<.>̽ʜ<+>̽ƥ̽nD̽ʜ<+>̽nD̽ƥ̽S<">̽k==̽R==̽X=\.=̽R==̽k==̽ka==̽ɬz==̽k==̽k=̃ =̽ɬz==̽ka==̽k==̽=3=̽===̽=|=̽M:>̽!̽9d?>̽0`*]5>̽!̽M:>̽r?>̽9d?>̽!̽VV'G8ID>̽9d?>̽r?>̽n~$}1I'ilgD>̽G8ID>̽r?>̽Ԩ4r{<̽z<̽?z<̽z<̽Ԩ4r{<̽¿!<̽Ԩ4r{<̽@HZ<̽¿!<̽ʟMC=̽μ}=̽H<=̽ʟMC=̽|ü[=̽μ}=̽ {=̽μ}=̽r=̽|ü[=̽r=̽μ}=̽Clt\O>̽cU>̽ʏMzU>̽Clt\O>̽驼jO>̽cU>̽cU>̽驼jO>̽qT>̽}S+pi;>̽i 5>̽96>̽D:>̽}S+pi;>̽96>̽QBX.'}S+pi;>̽D:>̽2%A>̽٩=z<̽6=<{<̽8==2<̽6=<{<̽٩=z<̽= <̽=RmZ<̽6=<{<̽= <̽,=&#>̽׋=Ӿ&>̽{=5 >̽{=5 >̽}=>̽,=&#>̽==̽=l=̽==̽==̽s==̽==ؠ=̽j==̽s==̽==̽==̽j==̽==̽}=>̽{=5 >̽!=>̽GVg=d>̽}=>̽y=>̽y=>̽}=>̽!=>̽c/=KZ>̽8=Y>̽c!=9M_>̽@'=T>̽8=Y>̽c/=KZ>̽c/=KZ>̽;9=$U>̽@'=T>̽I=Z>̽;9=$U>̽c/=KZ>̽½=̽̽S=̽Pv=̽c:=*>̽p%+>̽+1$>̽c:=*>̽yS(>̽Gu/>̽Gu/>̽[->̽'V[5>̽[->̽Gu/>̽yS(>̽[->̽56ke/>̽'V[5>̽i=f>̽iZ= `>̽M=6g>̽^=Ul>̽i=f>̽M=6g>̽3=-Eh>̽M=6g>̽:=,za>̽M=6g>̽iZ= `>̽:=,za>̽F!V=q>̽^=Ul>̽B=7m>̽F!V=q>̽Ih=u>̽o=hp>̽o=hp>̽^=Ul>̽F!V=q>̽\(r_=̽Zi'?>̽T=̽KN}>̽Zi'?>̽\(r_=̽/:)=̽KN}>̽\(r_=̽GA<̽\@.<̽cCPj<̽GA<̽2.<̽\@.<̽2.<̽GA<̽ԬCX<̽gEY/$j>̽M]n>̽5Dm>̽gz,j>̽5Dm>̽V0wq>̽qW=̽|ɼ7=̽W"p=̽!B=̽qW=̽2ż=̽> =̽!B=̽nr =̽!B=̽> =̽LKV=̽v= E>̽^={B>̽#\=:YH>̽vy[=ϬN>̽#\=:YH>̽JH=GJ>̽vy[=ϬN>̽t=K>̽#\=:YH>̽v= E>̽#\=:YH>̽t=K>̽<+T4>̽c<:>̽H=|8>̽c<:>̽<+T4>̽[<3>̽킔̽c<:>̽[<3>̽}k=G>̽=Y>̽m=>̽=Y>̽}k=G>̽d^= >̽A:=>̽=Y>̽d^= >̽=yX>̽=lW>̽b=]>̽b=]>̽=lW>̽A.=8^>̽A.=8^>̽=lW>̽K=Y>̽^<y>̽<]~>̽9<z>̽<<>̽9<z>̽<]~>̽,<欩=̽C<Э=̽@Ű<*ʞ=̽<v=̽C<Э=̽,<欩=̽<v=̽,<欩=̽3>̽;==̽}=d=̽0h=p=̽w>̽5>̽E>̽3<>̽E>̽5>̽ м@=̽pJ=̽9μR=̽pJ=̽ м@=̽;)=̽.2b=̽pՊ7f=̽K=̽K=̽9μR=̽.2b=̽;f=%>̽=(>̽=p >̽=(>̽;f=%>̽=b,>̽;f=%>̽׋=Ӿ&>̽=b,>̽E=Q>̽r0=J>̽Z)=غ=̽r0=J>̽E=Q>̽ ~=m >̽=>̽ ~=m >̽E=Q>̽75;(ɤ=̽˘!<d=̽7;Z=̽ ʕe>̽˼n_>̽^^c>̽VTj>̽ ʕe>̽^^c>̽ag=g=̽=Z0X=̽s=DL=̽=Z0X=̽ag=g=̽=*q=̽0Ϩ1=̽ =̽h/[=̽h/[=̽v=̽03=̽L;Ձ<̽<O;ʾ<̽J _<̽;/<̽<O;ʾ<̽L;Ձ<̽;/<̽L;Ձ<̽̽t=R>̽|=\W>̽t=R>̽F=T>̽I!=ߏN>̽̽!<2>̽I_<}>̽-̽̽!<2>̽!<2>̽̽=>̽K@>̽2%A>̽}(̽Z=%AP;'}S+pi;>̽2%A>̽K@>̽a>̽o>̽H<=̽r>̽-T$>̽U斻>̽N >̽-T$>̽9#>̽N >̽U斻>̽-T$>̽c=*l=̽S==̽==̽c=*l=̽==̽"=Wz=̽==̽c=*l=̽==̽̽u=̽ǽs=̽=̽h6=̽~g,/=̽K=̽~g,/=̽h6=̽ꓼ6=̽Xx=̽/77=̽3Wc<̽/77=̽(<̽3Wc<̽ؼFX>̽fV>̽Vҫ]>̽꼑;R>̽fV>̽ؼFX>̽ؼFX>̽qT>̽꼑;R>̽m>̽zc_g">̽S/>̽E>̽m>̽w>̽~D<">̽zc_g">̽yS(>̽zc_g">̽~D<">̽S/>̽~D<">̽c:=*>̽+1$>̽yS(>̽c:=*>̽~D<">̽[̽V=\r>̽P=l>̽\C=Qg>̽?̽P=l>̽?̽[̽P=l>̽Ə"=̽&\=̽1|=̽&\=̽Ə"=̽\ =̽&BP=̽ZSS=̽b#8=̽ZSS=̽&BP=̽KIi=̽ZSS=̽pJ=̽b#8=̽5=>̽/7=Az>̽4&=}>̽7= +s>̽/7=Az>̽GP=vw>̽/7=Az>̽7= +s>̽N=w>̽4&=}>̽/7=Az>̽N=w>̽~!=U<̽17=˕<̽@=<̽=:=g<̽~!=U<̽@=<̽s':',o=>>̽^={B>̽v= E>̽=7D>̽v= E>̽؂=PYI>̽N$;'=7D>̽8 =>?>̽v= E>̽`*',o=>>̽v= E>̽8 =>?>̽1A2=̽X%$ =̽U:=̽U:=̽I/=̽1A2=̽z(B9=̽ viU=̽j G=̽‘ R=̽ viU=̽z(B9=̼̽u>̽ z>̽hefz>̽ z>̽=ļvM>̽hefz>̽g=:nI>̽I!=ߏN>̽T=jgM>̽d?=̽Gܗ*=̽\##=̽Gܗ*=̽d?=̽铽F=̽Gܗ*=̽Xx=̽\##=̽211=̽Gܗ*=̽铽F=̽\=>̽>̽ѫgO>̽/,>̽>̽\=>̽G̽ ;7>̽\8̽W;2>̽ ;7>̽G̽G̽nE̽7<.>̽7<.>̽W;2>̽G̽Nӥ>̽">с!>̽A>̽3<>̽A>̽">с!>̽RE(>̽u /z'>̽g->̽5lw.>̽RE(>̽g->̽U&`=p=̽ѥN=1e=̽+UN==̽OT&&c6==oo=̽+UN==̽ѥN=1e=̽a(% X'+UN==̽zm=ր=̽`=p=̽x%ta*'+UN==̽c6==oo=̽2=Yڃ=̽<v=̽<ͺ=̽C<Э=̽<ͺ=̽<v=̽ܶ<}=̽<v=̽W< 8=̽_<==̽ܶ<}=̽<v=̽_<==̽*=/Z>̽=k>̽ɚ=>̽=>̽ɚ=>̽ث= >̽7e̽0<ˌ'>̽E<)>̽0<ˌ'>̽7e̽̽ƥ̽0<ˌ'>̽̽Wh<̽ϼ)u<̽^׼>$<̽W< 8=̽+<0=̽_<==̽W< 8=̽- <=̽+<0=̽wg;=̽+<0=̽_;:=̽+<0=̽- <=̽_;:=̽&ؙ '\km=̽ϰ0y=̽yţV=̽qkbe=̽฽i=̽ϰ0y=̽\km=̽qkbe=̽ϰ0y=̽4=>̽=>̽Me=, >̽=36>̽4=>̽Me=, >̽=̽w=̽y=̽y=̽(=̽=̽y=̽LKV=̽(=̽R=`>̽=?ad>̽A.=8^>̽R=`>̽R3=e>̽=?ad>̽ 7=mQV=̽c6==oo=̽ѥN=1e=̽6_%&ԧ/'=0 u=̽2=Yڃ=̽c6==oo=̽8!=;^=̽=0 u=̽c6==oo=̽c6==oo=̽ 7=mQV=̽8!=;^=̽̽QQ >̽g M%>̽Pd>̽Tg`_>̽|pe>̽Tg`_>̽Pd>̽ MJd\>̽ MJd\>̽yqcZ>̽Tg`_>̽(\~}>̽cM61x>̽bv>̽/r wz>̽(\~}>̽bv>̽%g;>̽26>̽; >̽\;3>̽26>̽%g;>̽2,ٳ'3o;V=̽*<=̽lB2̽#Ш7>̽|֜y_=̽#Ш7>̽. >̽|֜y_=̽f =̽#Ш7>̽m=̽#Ш7>̽f =̽*̽ż$̽=[<m>̽?\< `>̽ż$̽*̽ż$̽?\< `>̽;;`>̽D`$">̽CD(>̽y i'>̽T6>̽D`$">̽% >̽Lp!=̽$̽̽1 >̽̽o&>̽CD(>̽ ½">̽̽o&>̽2ȼ9>̽r?>̽!̽{">̽m>̽E>̽dJ1">̽u /z'>̽RE(>̽u /z'>̽dJ1">̽">с!>̽x=&q>̽4=>̽*=/Z>̽x=&q>̽ɚ=>̽=>̽ɚ=>̽x=&q>̽*=/Z>̽4=>̽x=&q>̽=>̽!==̽ WA=̽2ż=̷̽=̽k=̽٣=̽^=̷̽=̽٣=̽Ԇ3=̽udvꍐ=̽!=̽Ԇ3=̽^=̽٣=̽HȼvJ/>̽߼Y4>̽~3>̽PC=̽D₼=̽`sR=̽W"p=̽PC=̽`sR=̽J=o;̽!=<̽= <̽!=<̽=RmZ<̽= <̽ڠO<̽Jo;̽¿!<̽@HZ<̽ڠO<̽¿!<̽/<=̽<@V=̽<ͺ=̽><ɨ=̽/<=̽ܶ<}=̽/<=̽><ɨ=̽=1=̽<ɨ=̽ܶ<}=̽/դJ>̽㎚H>̽aFD>̽㎚H>̽\%F>̽aFD>̽Z>̽b60a>̽9e[>̽cU>̽Z>̽9e[>̽ ڊ>̽eY>̽5>̽){!=̽!=̽udvꍐ=̽?[&UL'){!=̽Ghg=̽ `|=̽Ghg=̽){!=̽udvꍐ=̽b%caR'!=̽){!=̽ `|=̽.>̽dK->̽~3>̽dK->̽.>̽^'>̽r>̽T>̽-T$>̽T>̽~>̽-T$>̽ 5e>̽7bCh>̽gz,j>̽7bCh>̽5Dm>̽gz,j>̽P$UR':*?>̽ >?>̽aFD>̽$Jk?>̽ >?>̽D:>̽w$uR' >?>̽$Jk?>̽aFD>̽ >?>̽:*?>̽D:>̽Nar>̽cM61x>̽K[;UMv>̽F:=̽8B1>=̽MFH=̽==̽(=5=̽Z)=غ=̽(=5=̽E=Q>̽Z)=غ=̽(=5=̽==̽̽U斻>̽>W:i>̽J?>̽SJ >̽.:J >̽J?>̽q>|>̽(\~}>̽q>|>̽J?>̽.:J >̽SJ >̽J?>̽(\~}>̽0P>̽!v;>̽QQ >̽!v;>̽0P>̽9K, >̽c=4>̽iK=b0>̽p=~3>̽=b,>̽iK=b0>̽R=.>̽iK=b0>̽=b,>̽p=~3>̽iK=b0>̽c=4>̽R=.>̽\cc <̽؁PR<̽8<̽İ!)=̽0 =̽˻B8<=̽İ!)=̽©0=̽3=̽©0=̽İ!)=̽˻B8<=̽0 =̽İ!)=̽3=̽[=Z>̽=Z>̽J=ևT>̽=Z>̽=cX>̽J=ևT>̽<->̽<@>̽w=<>̽̽<@>̽W`̽<@>̽̽w=<>̽<@>̽<->̽W`̽&\=̽X鐽/=̽4=̽k=̽X鐽/=̽텽A=̽X鐽/=̽&\=̽텽A=̽F=/Q>̽j=sS>̽T=jgM>̽F=/Q>̽I!=ߏN>̽F=T>̽I!=ߏN>̽F=/Q>̽T=jgM>̽j=sS>̽F=/Q>̽F=T>̽H̽*̽u<>ud>̽H̽<]>̽?\< `>̽<]>̽H̽u<>ud>̽*̽H̽?\< `>̽;=>̽(< >̽; >̽{m>̽ >̽h8 >̽^vн >̽{m>̽h8 >̽b>̽B^>̽ f>̽B^>̽b>̽T-#o[>̽\-;n;=̽©0=̽%ßO=̽©0=̽\-;n;=̽;&=̽dj;)K=̽\-;n;=̽%ßO=̽a>̽K ͼf4 >̽fÛt >̽߼>̽K ͼf4 >̽8< >̽ ==̽x=(=̽|=4Y=̽@˼B">̽|ǼJ*>̽^'>̽KN}>̽{m>̽Zi'?>̽KN}>̽ >̽{m>̽K ͼf4 >̽𱼏>̽fÛt >̽߼>̽𱼏>̽K ͼf4 >̽4P̽zfIr>̽>W:i>̽==̽><ɨ=̽<ɨ=̽{m>̽}e>̽Zi'?>̽^vн >̽}e>̽{m>̽X鐽/=̽ =̽4=̽k=̽ =̽X鐽/=̽dK->̽HȼvJ/>̽~3>̽qW=̽!==̽2ż=̽qW=̽W"p=̽!==̽dJ1">̽{">̽E>̽RE(>̽{">̽dJ1">̽z>̽zfIr>̽ :>̽z>̽U斻>̽zfIr>̽6=̽7=̽z=̽T½ڀ=̽̽ώ=̽̽ =̽a6f=̽T½ڀ=̽̽ =̽a6f=̽踽s=̽T½ڀ=̽K ͼf4 >̽(z>̽8< >̽a>̽(z>̽K ͼf4 >̽=Z>̽=^>̽=cX>̽N<>̽(< >̽;=>̽~3>̽2ȼ9>̽!̽~3>̽߼Y4>̽2ȼ9>̽W"p=̽|ɼ7=̽PC=̽eY>̽񞝽>̽A>̽3<>̽eY>̽A>̽3<>̽5>̽eY>̽zc_g">̽m>̽{">̽|ǼJ*>̽dK->̽^'>̽/դJ>̽^gM>̽㎚H>̽<@V=̽C<Э=̽<ͺ=̽u̽dJ1">̽E>̽3<>̽">с!>̽dJ1">̽5Dm>̽M]n>̽Nar>̽V0wq>̽Nar>̽K[;UMv>̽V0wq>̽5Dm>̽Nar>̽؁PR<̽<̽8<̽N <̽<̽؁PR<̽̽S=̽̽3=̽Pv=̽̽3=̽ýG=̽Pv=̽̽|=̽̽=̽*+_=̽ýG=̽̽|=̽*+_=̽E=Q>̽!<2>̽=>̽E=Q>̽̽E=Q>̽(=5=̽̽CD(>̽̽o&>̽CD(>̽̽n,>̽tb/>̽;==̽5=w>̽==̽k= >̽ ==j>̽+= >̽+= >̽ ==j>̽0=>̽ м@=̽9μR=̽K=̽~g,/=̽ м@=̽K=̽E;=̽- <=̽8;ɭ=̽8;ɭ=̽- <=̽3>̽=̽=7>̽=I>̽=̽V0= >̽G=>̽V0= >̽=̽̽1 >̽ ½">̽T6>̽ ½">̽D`$">̽T6>̽D`$">̽ ½">̽CD(>̽i;O:>̽f;Hp>̽W`̽W`̽J<.M>̽i;O:>̽3=̽Y;=̽L;Ձ<̽Y;=̽̽f< >̽NJ̽7bCh>̽Pd>̽gEY/$j>̽Pd>̽7bCh>̽ 5e>̽5Dm>̽7bCh>̽gEY/$j>̽=ļvM>̽k߼J >̽ ŭJ >̽Z>̽˼n_>̽b60a>̽˼n_>̽Z>̽ؼFX>̽Z>̽qT>̽ؼFX>̽qT>̽Z>̽cU>̽$R=̽>=̽uԪ=̽!Т=̽8B1>=̽F:=̽/c=̽~=̽yţV=̽6&AC'ϰ0y=̽/c=̽yţV=̽/c=̽(=̽~=̽m=3#>̽=1 >̽{==>̽m=3#>̽{==>̽= >̽2:< }>̽A@;w>̽/b; ~>̽&J >̽ |>̽%u>̽%u>̽JJ >̽&J >̽&=J >̽^=~>̽I=J >̽XB*=̽6A9K=̽`o'=̽6A9K=̽=̽`o'=̽|_=̽u^=̽$R=̽brM=̽u=̽|4,=̽u=̽brM=̽ʎZݫ=̽u=̽텽A=̽|4,=̽c<:>̽킔̽j8< ?>̽<&:'j< B>̽j8< ?>̽/̽X&ӧ&c<:>̽j8< ?>̽j< B>̽=_>̽=^>̽=Z>̽=_>̽[=Z>̽΍=Qa>̽=Z>̽[=Z>̽=_>̽޼=̽a>̽H<=̽޼=̽(z>̽a>̽G& '̽*q=̽}=̽fýZTh=̽'}=̽̽*q=̽̽=̽8o\<̽PVh6<̽jҷ_<̽cCPj<̽PVh6<̽8o\<̽8o\<̽GA<̽cCPj<̽ Dm~=̽K7.=̽i%R=̽Oж=̽K7.=̽ Dm~=̽ {=̽Z r=̽7=̽7=̽8B1>=̽!Т=̽1Rڴ=̽!==̽`sR=̽W"p=̽`sR=̽!==̽} `=̽ WA=̽!==̽!==̽1Rڴ=̽} `=̽z]@<̽(<̽<̽3Wc<̽(<̽z]@<̽ġ<̽3Wc<̽z]@<̽-<\<̽ =zA<̽<<̽?<ə<̽-<\<̽<<̽-<\<̽?<ə<̽3<=̽-<\<̽= <̽ =zA<̽<]~>̽ؖ<~>̽ ŭ̽ ŭ̽ؖ<~>̽>x̽ؖ<~>̽^<y>̽RɁ̽^<y>̽ؖ<~>̽<]~>̽k߼o;̽o;̽<̽^׼>$<̽k߼o;̽<̽18g=ao<̽!s=!=b<̽IR=u<̽!s=!=b<̽`=|<̽IR=u<̽~=#\<̽!s=!=b<̽18g=ao<̽9K, >̽@ɞ>̽]2>̽0P>̽@ɞ>̽9K, >̽^vн >̽]2>̽}e>̽1=̽̽<&=̽̽6?=̽1=̽̽6?=̽-M=̽<=̽1=̽-M=̽<=̽b&=̽1=̽==̽===̽j==̽===̽==̽=|=̽===̽8=(=̽j==̽1Rڴ=̽`o'=̽CN8=̽5dң=̽CN8=̽`o'=̽5dң=̽`o'=̽=̽^gM>̽lFR>̽o.XL>̽^gM>̽o.XL>̽\%F>̽\%F>̽㎚H>̽^gM>̽Nar>̽M]n>̽iq>̽iq>̽bv>̽Nar>̽bv>̽cM61x>̽Nar>̽_=̽Lw=̽/:)=̽\(r_=̽_=̽/:)=̽B k1h=̽_=̽\(r_=̽l!P>̽[I>̽H>̽F=T>̽K=Y>̽j=sS>̽F=T>̽h=w[>̽K=Y>̽R=`>̽K=Y>̽h=w[>̽A.=8^>̽K=Y>̽R=`>̽zc_g">̽{">̽m )>̽5lw.>̽56ke/>̽m )>̽m )>̽RE(>̽5lw.>̽{">̽RE(>̽m )>̽˘!<d=̽,<欩=̽3y<=̽˘!<d=̽3>$<̽ϼ)u<̽pu?I;<̽6=̽ZS,=̽MFH=̽ZS,=̽[+=̽MFH=̽A*=w>̽$=O~t>̽=`gq>̽R`>̽\f>̽uqP`>̽R`>̽e>̽\f>̽R`>̽t",`>̽e>̽;2<̽؁PR<̽mɹ<̽؁PR<̽;2<̽N <̽؁PR<̽\cc <̽mɹ<̽eY>̽ ڊ>̽5N >̽ҝI>̽eY>̽5N >̽eY>̽ҝI>̽񞝽>̽U"=ߕ=̽/<=̽=1=̽<@V=̽/<=̽U"=ߕ=̽u̽7e̽o=$>̽R<4>̽D=a>̽=E>̽R<4>̽o=$>̽D=a>̽FV>̽^gM>̽գQ>̽գQ>̽^gM>̽/դJ>̽ׯN>̽գQ>̽/դJ>̽գQ>̽ׯN>̽ nT>̽(̽.>̽0`*]5>̽10>̽0`*]5>̽8>̽M:>̽8>̽0`*]5>̽{%G/eE>̽C\N@>̽^fG>̽PtJ>̽{%G/eE>̽^fG>̽PtJ>̽n;&K>̽{%G/eE>̽#9'"HN?>̽C\N@>̽{%G/eE>̽=Na>̽ӂ=ǝf>̽R3=e>̽R=`>̽=Na>̽R3=e>̽ 0&>̽5*>̽8.>̽߼Y4>̽8.>̽T _85>̽߼Y4>̽HȼvJ/>̽8.>̽/,>̽>2 >̽6>̽>̽/,>̽6>̽>2 >̽/,>̽ե8>̽̽k>̽½im>̽h>̽l>̽½im>̽q>̽½im>̽l>̽h>̽ %]=̽. 0>̽ {=̽ %]=̽7=̽!Т=̽7=̽ %]=̽ {=̽. 0>̽ %]=̽!Т=̽cĻ@&<̽ o;̽?<̽gĻ&^<̽cĻ@&<̽?<̽g=Z !>̽l=%>̽,=&#>̽}=>̽g=Z !>̽,=&#>̽&J >̽e>̽ |>̽e>̽4z>̽ |>̽e>̽&J >̽IJ >̽\f>̽牽k>̽rzk>̽;;`>̽Rp;h>̽ż$̽=:=g<̽O=6t<̽̽6U31 >̽Jp>̽6U31 >̽&,>̽~ >̽U4̽2:< }>̽RɁ̽U4̽&*̽A@;w>̽&*̽U4̽RɁ̽2:< }>̽U4̽A@;w>̽<=̽@Ű<*ʞ=̽C<Э=̽<=̽u̽Y*|U=>̽o>̽Y*|U=>̽a>̽fÛt >̽&=J >̽oD=>̽^=~>̽={>̽oD=>̽ª=3>̽oD=>̽={>̽^=~>̽@t=̽U`=̽p EM=̽k=Pk>̽=`gq>̽1}=j>̽R3=e>̽k=Pk>̽1}=j>̽ z>̽f">̽=ļvM>̽f">̽ z>̽V v>̽f">̽k߼J >̽=ļvM>̽(c=̽ )Q=̽Qp.(T=̽˻B8<=̽ )Q=̽%ßO=̽ )Q=̽˻B8<=̽Qp.(T=̽ )Q=̽(c=̽%ßO=̽٣=̽|"=̽Ԇ3=̽|"=̽udvꍐ=̽Ԇ3=̽u)̽;;`>̽?\< `>̽==̽lH=V=̽&{=V=̽lH=V=̽==̽&{=V=̽xL! >̽}e>̽]2>̽@ɞ>̽xL! >̽]2>̽8ͽ>̽e>̽IJ >̽8ͽ>̽4z>̽e>̽Ok<G[>̽u)̽?\< `>̽Ok<G[>̽D<$V>̽u)̽oo>̽牽k>̽LXq>̽oo>̽rzk>̽牽k>̽6U31 >̽fÛt >̽Jp>̽f">̽J >̽k߼J >̽V v>̽J >̽f">̽J=J >̽oD=>̽&=J >̽J=J >̽ª=3>̽oD=>̽H<<̽=o;̽k̽̽B]q>̽½im>̽q>̽̽B]q>̽cýu>̽̽B]q>̽q>̽½im>̽#P=.>̽EVb=)>̽:I=m)>̽l=%>̽P=>K$>̽EVb=)>̽EVb=)>̽P=>K$>̽:I=m)>̽x̽0E;>̽2ȼ9>̽0E;>̽߼Y4>̽T _85>̽=bd>̽ڿ=?g>̽=k>̽@=<̽O=6t<̽=:=g<̽O=6t<̽@=<̽=i$<̽J<~X<̽O=6t<̽H<<̽H<<̽O=6t<̽=i$<̽GVg=d>̽g=Z !>̽}=>̽ O=h>̽g=Z !>̽GVg=d>̽g=Z !>̽P=>K$>̽l=%>̽P=>K$>̽g=Z !>̽ O=h>̽ko>̽LXq>̽牽k>̽e>̽牽k>̽\f>̽牽k>̽e>̽ޘ5Dj>̽ko>̽牽k>̽ޘ5Dj>̽$+ܣ~>̽>xJ >̽\D}>̽6=̽8B1>=̽7=̽8B1>=̽6=̽MFH=̽[F;=̽仹T=̽B;M=̽B;M=̽仹T=̽YC"=̽Qbh>̽xL! >̽d>P5>̽xL! >̽Qbh>̽}e>̽@ɞ>̽g2px >̽xL! >̽xL! >̽g2px >̽d>P5>̽<,=̽ ==̽|=4Y=̽<,=̽ =B=̽ ==̽O̽w8qx>̽:E;\p>̽w8qx>̽A@;w>̽:E;\p>̽K~ٺ~>̽/b; ~>̽w8qx>̽A@;w>̽w8qx>̽/b; ~>̽ҪA=̽ןS#=̽衽R==̽ןS#=̽ҪA=̽ϰ1=̽ןS#=̽ϰ1=̽|_=̽ןS#=̽|_=̽$R=̽PC=̽|ɼ7=̽bӼ=̽PC=̽bӼ=̽گ=̽PC=̽گ=̽D₼=̽Ə"=̽ןS#=̽uԪ=̽ןS#=̽$R=̽uԪ=̽ןS#=̽1|=̽衽R==̽ןS#=̽Ə"=̽1|=̽|@̽[I>̽꼑;R>̽꼑;R>̽l!P>̽fV>̽[I>̽l!P>̽꼑;R>̽C0;RV>̽u)̽D<$V>̽tb/>̽̽2>̽%5>̽̽2>̽tb/>̽̽n,>̽N<>̽;=>̽J<.M>̽J<.M>̽;=>̽i;O:>̽lH=V=̽==̽}=d=̽;==̽lH=V=̽}=d=̽lH=V=̽;==̽==̽>xo;̽?<̽ o;̽odȿ=̽B k1h=̽ZS,=̽ZS,=̽B k1h=̽[+=̽B k1h=̽odȿ=̽qx==̽b>̽;;`>̽T-#o[>̽Rp;h>̽;;`>̽b>̽[k+j>̽b>̽ f>̽ 0&>̽|ǼJ*>̽@˼B">̽|ǼJ*>̽ 0&>̽8.>̽HȼvJ/>̽|ǼJ*>̽8.>̽|ǼJ*>̽HȼvJ/>̽dK->̽<<̽k̽%>̽g2px >̽%>̽@ɞ>̽0P>̽|"=̽b=̽udvꍐ=̽b=̽|"=̽u=̽|"=̽٣=̽u=̽}U=̽w=̽z=̽}U=̽z=̽Z r=̽&'!&̽=̽ý=̽}=̽ý=̽(=̽}=̽-̽_<>>̽f< >̽r >̽y%Y>̽g2px >̽%>̽r >̽g2px >̽y%Y>̽r >̽D @ >̽}S+pi;>̽:>̽i 5>̽!m'0E;>̽:>̽K@>̽e;&q*':>̽}S+pi;>̽K@>̽?4O=̽}U=̽;強=̽?4O=̽y=̽w=̽y=̽?4O=̽;強=̽}U=̽?4O=̽w=̽"{=I)>̽7އ=.>̽׋=Ӿ&>̽,=&#>̽"{=I)>̽׋=Ӿ&>̽T-#o[>̽P;iX[>̽C0;RV>̽u)̽P;iX[>̽;;`>̽P;iX[>̽u)̽C0;RV>̽P;iX[>̽T-#o[>̽;;`>̽\"@>̽̽T>̽r~XO=̽Qp.(T=̽h6=̽r~XO=̽K=̽pՊ7f=̽K=̽r~XO=̽h6=̽Qp.(T=̽r~XO=̽pՊ7f=̽=hv>̽Ў=Dz>̽A*=w>̽^=~>̽Ў=Dz>̽pӇ=H}>̽Ў=Dz>̽^=~>̽A*=w>̽Ў=Dz>̽=hv>̽pӇ=H}>̽O̽^<=̽ A8<̽pu?I;<̽)L<̽ A8<̽>xo;̽ ŭo;̽h>̽T>̽r>̽:>̽T _85>̽i 5>̽0E;>̽T _85>̽:>̽ A8<̽?<̽>xo;̽)L<̽?<̽ A8<̽ý=̽̽ώ=̽T½ڀ=̽f&$'̽ώ=̽ý=̽̽=̽(=̽ý=̽T½ڀ=̽=o;̽H<<̽=i$<̽\"@>̽QQ >̽̽QQ >̽\"@>̽0P>̽\"@>̽%>̽0P>̽@t=̽Y*|U=>̽Q>̽Y*|U=>̽@t=̽o>̽2:< }>̽>x̽ؖ<~>̽>x̽2:< }>̽ ̽I_<}>̽_<>>̽-̽_<>>̽I_<}>̽^<=̽L̽_<>>̽^<=̽8< >̽T>̽߼>̽8< >̽\"@>̽T>̽r >̽8< >̽D @ >̽%>̽8< >̽r >̽8< >̽%>̽\"@>̽ J >̽\D}>̽>xJ >̽:E;\p>̽Rp;h>̽[k+j>̽[k+j>̽Rp;h>̽b>̽Rp;h>̽l̽ż$̽:E;\p>̽l̽Rp;h>̽Q>̽U`=̽@t=̽Q>̽26>̽U`=̽26>̽\;3>̽U`=̽.;CP=̽B;M=̽\;3>̽B;M=̽.;CP=̽vG<`=̽L̽O̽k=Pk>̽!={p>̽k=Pk>̽_u{=ok>̽!={p>̽k=Pk>̽ӂ=ǝf>̽_u{=ok>̽k=Pk>̽R3=e>̽ӂ=ǝf>̽ :>̽;=>̽; >̽i;O:>̽ :>̽zfIr>̽i;O:>̽;=>̽ :>̽YC"=̽p EM=̽U`=̽YC"=̽U`=̽B;M=̽B;M=̽U`=̽\;3>̽Y=65>̽Zbp=Q4>̽yp=/>̽yp=/>̽#P=.>̽Y=65>̽EVb=)>̽#P=.>̽yp=/>̽wvӻ%>̽ӻ*z>̽\D}>̽ӻ*z>̽wvӻ%>̽K~ٺ~>̽ J >̽wvӻ%>̽\D}>̽B=Vo>̽=k>̽ڿ=?g>̽(< >̽X<>̽%g;>̽>'K@>̽`B>̽0E;>̽X<>̽\;3>̽%g;>̽.;CP=̽\;3>̽X<>̽ FJ >̽wvӻ%>̽ J >̽ FJ >̽K~ٺ~>̽wvӻ%>̽ThI'ռ D>̽ilgD>̽r?>̽:鼝o>̽̽g M%>̽̽:鼝o>̽ؼ w>̽~ >̽Q>̽6U31 >̽Y*|U=>̽6U31 >̽Q>̽6U31 >̽Y*|U=>̽fÛt >̽H>̽`B>̽K@>̽`B>̽H>̽[I>̽(< >̽f< >̽X<>̽_<>>̽X<>̽f< >̽X<>̽_<>>̽L̽.;CP=̽X<>̽L̽yp=/>̽7އ=.>̽"{=I)>̽EVb=)>̽yp=/>̽"{=I)>̽,=&#>̽l=%>̽"{=I)>̽l=%>̽EVb=)>̽"{=I)>̽=i$<̽[!=o;̽=o;̽ ŭo;̽k߼o;̽^׼>$<̽pu?I;<̽ ŭo;̽^׼>$<̽ ŭo;̽pu?I;<̽ A8<̽B=Vo>̽=t>̽=B]q>̽B=Vo>̽=B]q>̽=k>̽8".=N<̽Տ-=<̽=i$<̽Տ-=<̽8".=N<̽RZC=Z~<̽Տ-=<̽[!=o;̽=i$<̽T< !>̽ 0&>̽@˼B">̽T< !>̽:鼝o>̽g M%>̽:鼝o>̽T< !>̽@˼B">̽ 0&>̽T< !>̽g M%>̽E==̽>.==̽׍/=G'=̽IR=u<ֽ̽>=bb<̽17=˕<̽8".=N<ֽ̽>=bb<̽RZC=Z~<ֽ̽>=bb<̽8".=N<̽17=˕<ֽ̽>=bb<̽IR=u<̽RZC=Z~<̽.:=o;̽[!=o;̽Տ-=<̽RZC=Z~<̽.:=o;̽Տ-=<̽_I >̽:鼝o>̽@˼B">̽J:I>̽ilgD>̽ռ D>̽ռ D>̽`B>̽[I>̽J:I>̽ռ D>̽[I>̽׍/=G'=̽=&=̽J==̽U"=ߕ=̽J==̽=&=̽>.==̽=&=̽׍/=G'=̽=D=̽=&=̽>.==̽=[m>̽l=7n>̽!=Js>̽_I >̽ؼ w>̽:鼝o>̽=[m>̽ڿ=?g>̽Ų=~g>̽=[m>̽B=Vo>̽ڿ=?g>̽=&=̽u.==̽E==̽>.==̽D=C=̽!'=l=̽ ==̽ =B=̽!'=l=̽ =B=̽=D=̽!'=l=̽ ==̽!'=l=̽&===̽!'=l=̽=D=̽>.==̽=Qj>̽c=g>̽l=7n>̽=Qj>̽=[m>̽Ų=~g>̽=[m>̽=Qj>̽l=7n>̽c=g>̽=Qj>̽Ų=~g>̽0E;>̽Xݼ?>̽2ȼ9>̽nwӹW'ռ D>̽Xݼ?>̽`B>̽'Xݼ?>̽0E;>̽`B>̽~=Dw>̽g=ir>̽!=Js>̽g=ir>̽=[m>̽!=Js>̽g=ir>̽~=Dw>̽=t>̽>̽sjŽ̗ >̽ѫgO>̽sjŽ̗ >̽>̽̽Xݼ?>̽r?>̽2ȼ9>̽[җN'ռ D>̽r?>̽Xݼ?>̽B=Vo>̽g=ir>̽=t>̽B=Vo>̽=[m>̽g=ir>̽sjŽ̗ >̽̽7>̽̽̽̽̽7>̽sjŽ̗ >̽̽>̽sjŽ̗ >̽̽̽̽>̽ѫgO>̽sjŽ̗ >̽~>̽T>̽h>̽h>̽_I >̽~>̽𱼏>̽h>̽Jp>̽<5i<̽Pt̽l=7n>̽1}=j>̽=o>̽=`gq>̽$=O~t>̽=`gq>̽=o>̽1}=j>̽l=7n>̽=o>̽$=O~t>̽k!>̽~>̽_I >̽@˼B">̽k!>̽_I >̽>̽𱼏>̽ؼ w>̽>̽_I >̽h>̽_I >̽>̽ؼ w>̽𱼏>̽>̽h>̽~>̽k!>̽$Rc#>̽k!>̽^'>̽$Rc#>̽^'>̽k!>̽@˼B">̽Ľ>̽̽[>̽̽7>̽̽Ľ>̽̽7>̽Ľ>̽̽u=̽̽[>̽Ľ>̽89xl>̽ǽs=̽89xl>̽Ľ>̽̽ǽs=̽̽u=̽Ľ>̽=Q<==<==>p%<='=#}>==w>=={>=̽>p%<=̽<=eýQ<=9ý{>=̽w>=̽#}>=I=o;=S=o;=p=EB)<=_i<=So;=Io;= ɧ½t>=̽B]q>=̽w>=̽<=̽%<=<=RT'=w>==B]q>=c=u>==<==%<==<=ӧý{>=½t>=̽w>=½t>=ý{>=w>=̽<=<=eýQ<=Щz<=eýQ<=<=6'$=w>=c=u>=={>=6n=xx>=={>=c=u>==Q<==<==<==<==Q<==gz<=>/f>= GKPf>=Bl>=ε==𪽐}==dۨ@==֌c==Ǚ(g==~==ε==dۨ@==~==̽*q==̽==t==t==Ǚ(g==̽*q==a%G==hZ==x@==ⵉ=<=ғ=銎<=~/=P<=p=EB)<=}=yFC<=~/=P<=퇽#<=w<=ʉ<=퇽#<=5r a<=_i<=eIQ>=II>=TgN>=TgN>=zF\T>=eIQ>=adk==AJv==It\==It\==aX==adk==_,bn<=b<={t<==gz<==Q<== <=]=p~>=={>=6n=xx>=^I<=eýQ<=Щz<=w>=ý{>=rљ~>=#w&ǢM'^P="e>=== c>==_>==q>=ʑ=n5s>=6n=xx>==l>=ʑ=n5s>==q>=6n=xx>=c=u>==q>=j>=Ön>=o>=Q=Go>=y=an>=ޘ=`Cj>=P/<=s82<=Щz<=kZn<=s82<=P/<=Щz<=<=P/<==gz<=?=jy<=='<=='<=?=jy<==J<=='<==<==gz<=It\==X"3^sG==@ OC==@ OC==aX==It\==scܗ==𪽐}==ε===<=Ӯd=R<=;o=2}<=w<=퇽#<=G/X'<=G/X'<=퇽#<=_i<==n*<=~/=P<=ғ=銎<=p=EB)<=~/=P<==n*<=I<=s82<=kZn<==J<=?=jy<=r=O<=y=an>=ʑ=n5s>=w=Al>=w=Al>=ʑ=n5s>==l>=FqZ==hZ==a%G==Ԝt==}Gr==]Ft==]Ft==dۨ@==Ԝt==+5v>=o>='8u>='8u>=o>=Ön>==8u>=Q=Go>= =Ru>=y=an>=Q=Go>==8u>=K=F>=2/,B>=,NsI>==J<=8=A<=ɾ=+<==<=='<=ɾ=+<=ɾ=+<=='<==J<=<=I<=kZn<=<=P/<=<=kZn<=P/<=<=w>=rљ~>=<ܛy>=S=y>=]=p~>=6n=xx>=8=a<==o;=Lc=o;==>p%<==o;=8=a<=&'Lc=J >==J >=9=Q>=Y$(9=Q>==J >==#}>=8Žg<=̽o;=̽>p%<=Lco;=̽o;=8Žg<=a&'9ŽP>=̽J >=LcJ >=:$̽#}>=̽J >=9ŽP>=Pn'=_>=== c>=1=_>==<==e<o>==?Cn>=+[=%<=;o=2}<=Ӯd=R<=b<=_,bn<=1zG<=~B=\==&=S===B:@===sJ==~B=\===B:@==hZ==]Ft==}Gr==Ҥ=U#Y>==_>=1=_>=(y=k<=ⵉ=<=~/=P<=}=yFC<=(y=k<=~/=P<=퇽#<=ʉ<=}͟<=5r a<=퇽#<=}͟<=5&ބ'1zG<=_,bn<=I==,NsI>="ծ]M>=P>=FqZ==֌c==]Ft==FqZ==]Ft==hZ==>/f>=|>*`>= GKPf>=;==-*==uҪ==;==uҪ==B==L&m7'}m= ==;o=2}<=+[=%<=T*'+[=%<=I==;==ldX=;]==t==ε==~==Ǚ(g==t==~== =~===k==T=2=='y =g>=1=_>=== c>=}H=#==[=z@=====scܗ==-*==;==𪽐}==scܗ==;==sz%g>=Ar̹`>=Q g>=3 =lh>=y =g>=== c>=Q g>=Ar̹`>=`>=l:'^P="e>==_>=r=(`>=׋==;<=ⵉ=<==<=p&'=<=;o=2}<=P=H==]Ft==֌c==~==dۨ@==]Ft==~===<=ⵉ=<=(y=k<=Ӯd=R<==<=(y=k<=}͟<=ʉ<={t<=}͟<={t<=b<=I<=kZn<=򴽛:<="=h<==J<=r=O<=3 =lh>=ޘ=`Cj>=y=an>=w=Al>==l>=y =g>=Ön>=j>=`g>=ˊ<=ʉ<=WH <=scܗ==ε==½ć==½&==-*==scܗ==qd>=Q g>=`>=j>=qd>=`g>=`g>=qd>=1b>=^P="e>=ޘ=`Cj>=3 =lh>=== c>=^P="e>=3 =lh>=3 =lh>=w=Al>=y =g>=w=Al>=3 =lh>=y=an>="r#0w%'Ar̹`>=Z>=`>=' =~==׋==;<=P=H==Jp'׋==;<==<=P=H=={t<=ʉ<=ˊ<==k== =~==P=H==P=H==}m= ===k==&l:';o=2}<=}m= ==P=H==u&;!'}m= ==+[=%<=ldX=;]===%F`>=5o=e>=ḃ=e>=Lco;=^I<=o;==o;== <=Lc=o;=X'J >=rљ~>=LcJ >=m'Lc=J >=]=p~>==J >= 1hu>=w>=yp>=w>= 1hu>= 1|>=8Žg<=^I<=Lco;=eýQ<=^I<=8Žg<=8Žg<=̽>p%<=eýQ<=Lc=o;== <=8=a<=8=a<== <==Q<==Q<==>p%<=8=a<=oئ"'9=Q>=]=p~>=Lc=J >=={>=]=p~>=9=Q>=s'-Β9=Q>==#}>=={>=&'LcJ >=rљ~>=9ŽP>=9ŽP>=rљ~>=ý{>=sΒý{>=̽#}>=9ŽP>=yp>=w>=sjs>='^=<_>==bd>==^>=Ҧ '===[=z@=="=h<=NK'==="=h<=r=O<=I<&B'򴽛:<=\Q==3J==;D&=^ 'I<=򴽛:<=3J==?<$'̽<=I<=̽%<=<=<=̽%<=I<=<=̽%<=<'$'=%<=8=A<==<==%<==<=ɾ=+<==%<=ɾ=+<=8=A<=2Q<=@Rp<=4t=PhUv>=}>='.:J >=[!J >= 1|>= 1hu>=QGw>= 1|>=So;=ko;=`o<=Q'ݗ@}>= ŭJ >=>xJ >=QDG½&==̽ ==̽==_N$'̽==̽ώ==½ć==½ć==t==̽==ε==t==½ć==zN½ć==̽ώ==̽ ==չH$½&==½ć==̽ ==½&==scܗ==½ć==֌c==UcU==Ǚ(g==K==̽C ==̽<&==̽<&==䋽'==K==p"w&'\Q==򴽛:<=K===<&===C ==K ===TF}7'K ==="=h<=[=z@==0S=I<=̽<=̽C ==M'K==I<=̽C ==I<=򴽛:<=kZn<=.SZ|@'򴽛:<=I<=K==j<'=C ===<=8=A<= &D'=C ==8=A<=K ====J<="=h<=8=A<=y9& :'K ===8=A<="=h<=9'=h>==k>==bd>==h>=y =g>==l>= =Ru>=ш=Pv>=|=Oz>=ш=Pv>= =Ru>=*X=q>=Q=Go>=*X=q>= =Ru>=|=Oz>=ш=Pv>==S{>=PhUv>=3ep>=bBn>=bBn>=+5v>=PhUv>=bBn>=j>=o>=+5v>=bBn>=o>=䋽'==\Q==K==}H=#========;&'(=<========8u>=ʑ=n5s>=y=an>=Ön>==[s>='8u>=z>=+5v>='8u>=I=o;=p=EB)<==n*<=ғ=銎<=K=LZ<==n*<=S=o;=}=yFC<=p=EB)<=G/X'<=_i<=Io;=G/X'<=埽lZ<=w<=(y=k<=}=yFC<=^j=_<=6n=xx>=ʑ=n5s>=S=y>==|>=S=y>==8u>=S=y>=ʑ=n5s>==8u>=<ܛy>==[s>=w>='8u>=<ܛy>=z>='8u>==[s>=<ܛy>=2/,B>=K=F>=͗l>>=͗l>>=""^:>=2/,B>=K=LZ<=ғ=銎<=Q=I<=`J V<=w<=埽lZ<=E&,==3y== 5==yO'n==%ּ===nV=>=g=Ki>=rH'=)$>==='>==9!>=z&{==!==6=='=?==m=.===M==d=N=/ʉ<#>=0K>=D`TE>=擼3hK>=;]mu>=z&q>=mnx>=<=+>=9H0=:>=EN=>=EN=>=nV=>=<=+>=B4B+>=d+K$>=H4j&>=]9==!==D S==@Q=|+G>=6=F>=PC=#aK>=Kڇ%w'===D====^==mnx>=z&q>=u>=擼3hK>=mejI>=ͼHN>=B=jց<p>=s =B=F^<z>=M(r<Ѡz>=R '*8Z/g>=d0_>=" e>= =F'>==9!>=ܚ=߆!>=94'3's >=Vw!>=̺$>=dX==R.==|,A====z&{==ʬb==~v==z&{==6==ӛ<'n`>= ּ d>=M޼N\>=h'M޼N\>= FY>=n`>=;m `>=C:d>=I=;==~q~'==RE)==q޼wy==X`==|nq==bN\<== <==~<==bN\<== ;==H0<$==3H==~q~'==;==y&P.'Q_p]Z>==z$>=*-^Ș!>='I%=)}>=[!=J >=.:=J >=<&'.:=J >=λ>=|>=I%=)}>={%YK)'#ǯ@$>=S>=%>=q޼wy==@[==h q== ؼW >=`dz >=v뼜>===='=?===M===)$>==9!>= =&2)>= =&2)>==+>==)$>=oǺm==|I;$==|;u==%ڔ<]g>=<-b>=>^< d>=;m `>=I=>^< d>=SD:k>=C:d>=a@g>=!&#F'Q_p]Z>=*-^Ș!>=WQ>=<:2>=29>=O&5>=_D& 'n`>= FY>=-{4[>=e(>==z$>=ڥK*>= t'ȧ^>= ּ d>=" e>=Х'" e>=d0_>=ȧ^>=SE;N>=d;ԍG>=juƼI>=juƼI>=:P>=SE;N>=~K == =e=:>=ydX= =>=ہ===f=R=={=`==an===ǂ==={=`==TMVd~>= 1|>=QGw>=QGw>=Oa,Sy>=TMVd~>=˼L@==V4X==?kD==˼L@==~q~'==M@====ʬb==/d==g=Ki>=Y=J >=<$q=Ǯ>=‚=>=>j=1>=<$q=Ǯ>=<$q=Ǯ>=Y=J >=‚=>=<*>=4|=e=e=RL =<*>=Sݠ== =@Y>=9S>=擼3hK>=ͼHN>=9S>=4>=W!c>=>=$N6>=eb:>=Hm>>=Hm>>=29>=$N6>=c==LÒ ==[81==Hm>>=eb:>=L^G{@>=(R>=iJ>=W N>=SoV==@[==M@==`==xdtl>=*8Z/g>=" e>=i?N.>=e(>=ڥK*>=@Q=|+G>=j_m=H>=ye=B>=Pt=D=>=ydX= =>=ye=B>=.B1>=i?N.>=ڥK*>=8ȃT>=OZ>=Mz]S>=H4j&>=d+K$>=={ >=={ >=*-^Ș!>=H4j&>=Sݠ==NYA==m====?Cn>=e<o>=~||8>=G=>=sY?>=sY?>=eb:>=~||8>=;m'--==3y==E&,==T>= FY>=(R>=(R>=W N>=T>=x-*>=#ǯ@$>=%>=ba;9A>= ;O#;>=3ú?>=Hʻ=>=[ʻBD>=3ú?>=0=8==I=97==I'=KO==KK >=6 >=񅼻#>=d=\>=;m `>=U';m `>=m=Ý;Y>=ba;9A>=d;ԍG>=z;;CnB>=ꥣ #>=#ǯ@$>=h )>=IO_=*8Z/g>=jm>=`ZE==}!;l==itd==IO_=̽o&>= W+>= W+>=x-*>=%>=d=׻>=nV=>=EN=>=‚=>="$=g==Qm===Qm===>j=1>=‚=>=cQ=&==ɪ====|/== G)==rAUA==M@==M@==~q~'== G)=='&==XV==<=?=H==M=K]==oT=]wF==?=H==0=8==I'=KO==~ =>=lC<{>=YU=8>==09>==<>>=2=En?>=|,A==R.==>$~==2=H==ǂ====f==93>'- ==<>>=M= ID>=I?j7<=@WLI<=ӃV<=fιk>=xdtl>=" e>=g=I=s =s =jց<p>=g=ݗ@}>=H|>={fxw>=";R==;\,6==IO_=x-*>=Hi/>=W[ha== q==8}==%=:==P=!==|=ʇ==Ѡ=C>=>=C>==S5>>=mͭ#'YU=8>=V<===l,!>=B< 'B.=))$>=9H0=:>==l,!>=*½ãL==Ǚ(g==UcU==~||8>=eb:>=Oh-4>==i?N.>=Oh-4>=*Ow,>=B4B+>=H4j&>=H4j&>=e(>=*Ow,>=c==[81==t==rto;== q==W[ha==rto;==NYA==%9en==&*=YU>=3=[>=P@=SW>=P=u==f=R==d= l==d= l==M=K]==P=u==~k+==X`==c==mRv==a*Y== #\$m==y===ǂ===an===܉=<==ǂ===y===@ټJU>=ͼHN>=(R>=(R>= FY>=@ټJU>=\45>=2P9>=9F\9>==$B>=4 >=&<>=U&p'=#$=}<>==dz2>==/>=T=5>=|54>=n/>=ƫs%.>=꽐==8}== q==@==8}==꽐==&+*'3's >=̺$>=] -W>=Ԩ=q{<=K=LZ<=Q=I<=Q=I<=?=jy<=Ԩ=q{<=hOC>=u8MC>=Hm>>=II>=u8MC>=hOC>=Hm>>=L^G{@>=hOC>=A=d==k= ==ۤ=ʾ==^DZIi' =]r=====ۤ=ʾ==5|l'ۤ=ʾ==k= == =]r==i=mq>=b=-v>=j~= u>==\>=^=<_>==xY>=鍯x>=fѼ:}>=ݗ@}>=Pt=D=>==<>>==9>==!:>=T=5>=q=>8>= ˼->=:ża>=a>==g==I=97==K+=]#==ֻ=Aڪ==cQ=&==N=H==N=H==c=rb==ֻ=Aڪ==<<=r<ù<=VF==0E>==i?>=''vs=i?>="=b=>=͌=2yC>=z=8===sJ===B:@==T=2===sJ==z=8==֫=jց<p>==–t" ==I==Y~6>=Sݠ==_=X^==iA{==@m>=sjs>=ѝ=zp>=@m>=23)h>=yp>=yp>=sjs>=@m>=qM;=[v>=λ>=|>=M=7x>=k%3'IE漱<=3H==;==(J&€';==\+7<=IE漱<=c=rb==N=H==dD=o==dD=o==N=H==A=d==ӧyu==adk==qLe==aX==xwQ==qLe==qLe==adk==aX==Â=={ʌ_==₷P==₷P=={ʌ_==q'¼==Es&$'=ώ=====q=>f==G' v&q=>f===,===ώ==b====,===É== 5%t /'n`>=-{4[>=%/>a>=Ҥ=U#Y>=1=_>==:Y>==:Y>=1=_>==\>==:Y>=Q="T>=Ҥ=U#Y>=3J==\Q==0pOV==A=1======M==D 'A=1==dD=o==A=d==>>=KK >=w9>=0է0ýN>=̽K>=̽%R>='s.g&̽%R>=8ȃT>=0ýN>=g%^E'YC,a>=|>*`>=kcV[>= GKPf>=|>*`>=YC,a>=Q_p]Z>=WQ>= c)>=9F\9>=2P9>=I@>=hG>=WQ>=4>=.{==[81==QԻ퇠==[>=`dz >==$B>=V=Eg>=O==?Cn>=G$==0pOV==ަ4==ڰw.==\Q==䋽'==&&kcV[>=|>*`>=DX>=M(r<Ѡz>==6J=Q>`==U1==fpG9I==EM==^==fpG9I==fpG9I==U1==EM===<=M3==_>=Ҥ=U#Y>=:1 'r=(`>==_>=z==SD[>=o;=^I<=x <='&5o=e>==%F`>=Cg=Ԇ_>='5'Cg=Ԇ_>==%F`>=6-w= [>=oh==ss==eU==ÿ=!<== <==o;=*&mO'k=^׽==D=== =]r==k=^׽==k= ===O==)&Ҫ1' =]r==k= ==k=^׽==5o=e>=FT=b>=AhY=+h>=5.'S=J >=I=J >=8=>=|=Oz>==S{>=8=>=/I=$C>=j_m=H>="=VH>=/I=$C>==<>>=Pt=D=>=4>=>=+;<>==$B>=&<>=+;<>=M===f=k=========='=?==M===L=*==k= ==A=d==L=*==cQ=&===|/==񅼻#>=6 >=%>=QH==FqZ==a%G==2/,B>=""^:>=` >>=` >>=""^:>=9F\9>==wQ>=Q="T>= ̸=V>=K<=f==ldX=;]==I==;==b===ɪ====V==N A'250<=1zG<=I===M=====A=:==&ph'c=rb==dD=o==A=:===X==,=`_===|/===g==K+=]#==%===M'=w>=λ>=|>=qM;=[v>=M'=w>==r>==x>=t= ;O#;>=ba;9A>=4*=QE==`݋==="$=g== 1k \>=-{4[>=2 V>=NܦL!' 1k \>=DX>=|>*`>=2 V>=DX>= 1k \>=juƼI>=d;ԍG>=8D>=8D>=d;ԍG>=ba;9A>=8D>=[ʻBD>=juƼI>=͎i==I==cZD==ަ4==a%G==by5==by5==a%G==x@==by5==G$==ަ4==><}h<=+=DX<=cG=Ys<=<ܛy>=rљ~>=᪽>=fɥ&7'᪽>=rљ~>=J >=%u=>=]=p~>=S=y>=&?'=J >=]=p~>=%u=>=>=C>=Ѡ=C>=s=DI>==<-b>=%ڔ<]g>=DB<=.:o;=So;=1zG<=250<=&n4<=&n4<=4t=8=>==S{>=ן4'S=J >=8=>=v=>=_`==x==ӧyu===w==|=ʇ==P=!===dz2>=T=5>=='9>=B==uҪ==R====E'==R==>9^%j>=Bl>= GKPf>= Q==}Gr==Ԝt==x==}Gr== Q=="ýC8==̽<&==̽6?==䋽'==̽<&=="ýC8==~<== <==l<==h&m=R'==}m= ==ldX=;]==ldX=;]==[= /==h&m=R'==)P=2>==/>==&->===|:==uҪ==uҪ==-*====hK==|:===='E*v' :?>=Vw!>=7;#>=[= /==ldX=;]=={E=0g.==Y=P<=ғ=銎<=ⵉ=<=Q=I<=ғ=銎<=Y=P<=YUi>=KK >=񅼻#>=..a=@W==M=K]==d= l==..a=@W==Ei= @==oT=]wF==oT=]wF==M=K]==..a=@W==??==ڰw.==䋽'===x>=@=t>=< z>=< z>=r =>==x>=ӧyu==x==Nz)==Nz)==x== q==P'=K>==0E>=b=I>=TT=IV>=?k=yU>=+]=QQ>=r<ù<=9<<=N<<=- =Q="T>=De=zS>=;v=o>=m,s=k>=i=mq>=ag:==xwQ==aX==X=L>=j_m=H>=@Q=|+G>==+======*q==;=(== =~==T=2===== =~==;=(==T=2==z=8==;=(==[y&'ˊ<=*==g {F==Mz]S>="ծ]M>=N鹽O>=N鹽O>="ծ]M>=TJ>=N鹽O>=8ȃT>=Mz]S>=@==꽐==Vp==8^&R '=ya>==bX=c>=O=V=Eg>=bX=c>=bX=c>=V=Eg>==ya>=b_==adk==ӧyu==@==Vp==I==W=>=F= >=g=Ki>=G<{==="ծ]M>=Mz]S>=P>="ծ]M>=S>=Mz]S>=R~X>=S>=͗l>>=K=F>=)D>=)D>=G=>=͗l>>==*q===Z0X===j===j===+===*q==%+7l>=23)h>=>/f>=>/f>=Bl>=%+7l>=yp>=23)h>=%+7l>=;q>={fxw>=]]9q>=]]9q>=xdtl>=;q>=T=2===k===>;==&=UH>==L>=顽=:G>=5̽==̽r====X"3^sG==@:#+==x>z[,==tx@M>=AT>=S7P>=P>=AT>=tx@M>==wQ>==L>=?J=N>=Ԝt==dۨ@==$a׌==$a׌==dۨ@==𪽐}==$a׌==Vp==Ԝt==%o]>=kcV[>=Fk3X>=Fk3X>=kcV[>=zF\T>=AJv==~K ==5zM==|,A==>$~==5zM==5zM==~K ==|,A==P=!===É==U= {==ÿ=!<==o;=J=o;=2'J=J >==J >=%u=>=Jo;=o;=x <=)'᪽>=J >=JJ >=ڰw.==ަ4==0pOV==\Q==ڰw.==0pOV==VF=jց<p>=֫=g=%ڔ<]g>=I@>=` >>=9F\9>='<b=I>==0E>=͌=2yC>=顽=:G>=b=I>=͌=2yC>=|x=C>=>=C>=&=UH>=|x=C>=&=UH>=顽=:G>=q=>f======+==x=Y==d= l==Dw=js===Qn==x=Y==Dw=js==GBCإ==̽r===ý!==hK=====ý!==|:====R==|:==R==uҪ==#<=]<߼K<=+优Œ<=QH==UcU==֌c==FqZ==QH==֌c==x <=^I<=Щz<==gz<== <=ÿ=!<=Cg=Ԇ_>=6-w= [>=c=nZ>==Qn==+߉=|P==x=Y==}m= ==h&m=R'===k==K<=f=={E=0g.==ldX=;]==Xr$'eN=t\>=vz<=<`>=FT=b>=Y=P<=>=P<=Q=I<=tQ *<=WH <=Kk1<=`J V<=tQ *<=Kk1<=;nX>=\~>=].p* >=^==EM==nZ==w'==^==nZ==bi1^'h=C>=ܚ=߆!>==9!>=v8*d'=='>=h=C>==9!>=*I⼒==q'¼==6伇==C2k;==EM==U1==X<>=e=4|=u; >=/$ <">=#$=#$@#'d=׻>=EN=>=?d=W!>=9$'6z=>=d=׻>=?d=W!>=bx>=}>=6 >=>>=y>=bx>==Q======f=k==ѡ=)>=Z=| >=g=&=>=!=F>=ѡ=)>=g=&=>=D%>=ڥK*>==z$>=d=&>==l,!>=V<==~=/ʉ<#>=<[R&>=F= >=<=+>=nV=>=ss==oh==pH}==)<9:>=lC`=}< >>=dX==|,A== =\~>=;nX>=I=97===g===>>=bx>=6 >=KK >=bx>=|Ϧ 'W!c>=4>=={ >=ݦ4&W!c>=={ >=d+K$>==;{R>=qZ<S>=%.=>=_B;=I0>=F=x>=0K>=[ʻBD>=D`TE>=X`==q޼wy==٨=='&'<*>=/ʉ<#>=~=&&&4|=<*>=~=*8Z/g>=a@g>=N3`>='&&d0_>=*8Z/g>=N3`>=@Y>=M0Y>=ȧ^>=@Y>=ȧ^>=d0_>=&a;`l<=- =@Q=|+G>=ZG=;B>=6=F>=ZG=;B>= ,=D A>= +.k>=a>=:ża>= #L:'E" >= +.k>=:ża>=B=s =;]mu>=F^<z>=B=;]mu>=k=^׽===O===^==O3'D===k=^׽===^== +&7)'G<{==M3=]]9q>={fxw>=u>={fxw>=H|>=F= >=W=>=_B;=I0>=&&C:d>=;m `>=\>=jm>=]]9q>=u>=z&q>=jm>=u>=>$?>=u8MC>=&4G>=0E===2;=~==HZ[=ɢ==]=q==an==={=`==]=q=={=`==f=R==^=O=bX=c>=s]['^=bX=c>==H''!<Y">=/ʉ<#>=<*>=8I9O'!<Y">=<*>=RL =|nq== #\$m==GZ==|nq==GZ==V4X==˼L@==M@==@[==V4X==˼L@==@[==SE;N>=;{R>==d;ԍG>=SE;N>==iJ>=(R>=ͼHN>=iJ>=ͼHN>=mejI>= =F'>==&->= =&2)>= =F'>= =&2)>==9!>=bN\<==H0<$==[=ƫs%.>=n/>=h )>=n/>=N+>=vR $'~v====ʬb==z&{==~v==ʬb==`dz >=[>=v뼜>==q&m=;m `>=>^< d>=r1& 'm=>^< d>=<-b>==O=^=<-b>==^=Ý;Y>=;{R>=s:97U>=\>=Ý;Y>=s:97U>==== =]r==M======M==='=?==qZ<S>=;{R>=Ý;Y>=qZ<S>=Ý;Y>=m=[= :?>=̺$>=e(>=H4j&>=*-^Ș!>==z$>=e(>=*-^Ș!>=0=8==K+=]#==I=97==&4G>=W N>=iJ>=ZdF`S>=ϺVV>=:P>=0K>=ZdF`S>=:P>=ш;=ar==P=u==M=K]==h q==>$~==R.==ȧ^>=M0Y>=M޼N\>='+b' ּ d>=ȧ^>=M޼N\>=SD:k>=s =I=C:d>=SD:k>=I=*Ow,>==<:2>=B4B+>=*Ow,>=<:2>=3H==== G)==3H== G)==~q~'==˼L@==?kD==RE)==~q~'==˼L@==RE)==s:97U>=;{R>=SE;N>=s:97U>=SE;N>=:P>=W!c>=9$>=>=2=H==ہ==={=`==ǂ===2=H=={=`==X=L>=@Q=|+G>=PC=#aK>=m==W[ha==eU==m==eU==ss==ZdF`S>=9S>=@Y>=@[==SoV==h q==eb:>=sY?>=L^G{@>=$N6>==Oh-4>=$N6>=Oh-4>=eb:>=";R==d==$N6>=<:2>=$N6>=29>=M޼N\>=M0Y>=@ټJU>=M޼N\>=@ټJU>= FY>=T>=2 V>=-{4[>= FY>=T>=-{4[>=/I=$C>=Pt=D=>=ye=B>=j_m=H>=/I=$C>=ye=B>=@ټJU>=M0Y>=9S>=ͼHN>=@ټJU>=9S>=W=>=g=Ki>=<$q=Ǯ>=>j=1>=W=>=<$q=Ǯ>=Qm===\Y=[>=>j=1>=xdtl>=]]9q>=jm>=xdtl>=jm>=*8Z/g>=&N 'bN\<==~<==b<<=%S'bN\<==b<<= ;==~k+== #\$m==|nq==X`==~k+==|nq==M'=w>==x>=I%=)}>=λ>=|>=M'=w>=I%=)}>=9S'|:==hK==/d==SUdO#'ʬb==|:==/d==g=%ڔ<]g>=>^< d>=I=g=>^< d>=\==–t" ==jUO==+EYC&3H==IE漱<=XV==~K ==%9en==NYA==~K ==NYA== '={ >=4>=WQ>=T%>'*-^Ș!>=={ >=WQ>=GZ== #\$m==a*Y==x-*>=ƫs%.>=h )>=x-*>=h )>=#ǯ@$>==hG>= c)>=޼bU<=+优Œ<=Ph<==X===|/==ɪ===@m>=ѝ=zp>=;]i>=23)h>=@m>=;]i>=.Z' 1k \>=|>*`>=%/>a>=R%3'-{4[>= 1k \>=%/>a>=8D>=ba;9A>=3ú?>=[ʻBD>=8D>=3ú?>=i?N.>==*Ow,>=i?N.>=*Ow,>=e(>=FL >=>>=w9>=t=ba;9A>=z;;CnB>=lC`=t=z;;CnB>=ZG=;B>=@Q=|+G>=ye=B>=ydX= =>=ZG=;B>=ye=B>==:Y>==\>= ̸=V>==:Y>= ̸=V>=Q="T>=";R==IO_`==fpG9I==\==fpG9I==^==eU==W[ha==8}==rto;==W[ha==m==NYA==rto;==m===09>==9>==<>>=/I=$C>="=VH>=M= ID>==<>>=/I=$C>=M= ID>=[>==$B>=+;<>=[>=+;<>=>=.{==oǺm==t==[81==.{==t==DgE>=II>=hOC>=L^G{@>=DgE>=hOC>=fιk>=E¼r>=;q>=fιk>=;q>=xdtl>=.B1>=~||8>=Oh-4>=.B1>=Oh-4>=i?N.>=N鹽O>=TJ>=0ýN>=8ȃT>=N鹽O>=0ýN>=cQ=&==ֻ=Aڪ===V==cQ=&===V==ɪ=== W+>=Hi/>=x-*>=KK >=YUi>=w9>=|x=C>=顽=:G>=͌=2yC>="=b=>=|x=C>=͌=2yC>=4 >=,HQ >=&<>=> '2 1'XV== <=<=sA&{ ' +.k>=3's >=] -W>= G)====@:#+==rAUA== G)==@:#+==q=>f===É===,==r=wz>==S{>=j~= u>=b=-v>=r=wz>=j~= u>=mRv==`ZE==itd==a*Y==mRv==itd==.0==wJ==x>z[,==.0==x>z[,==@:#+==h=<=X#=X<=2=Ѹ<=h=<=2=Ѹ<=br.=u<=L=*==A=d==N=H==cQ=&==L=*==N=H==)P=2>=q=>8>=T=5>==/>=)P=2>=T=5>=ب&''s=V<==g==%===\+7<=]<߼K<=IE漱<==X===f== =`==,=`_===X== =`==??==䋽'=="ýC8== ̸=V>==\>==xY>=,===6J===6J=hG>=4>=+;<>=hG>=+;<>=&<>=b_==ӧyu==Nz)===!:>=='9>=T=5>=b=I>=顽=:G>==L>=–t" ==cZD==I==_`==ӧyu==qLe==xwQ==_`==qLe==..a=@W==d= l==x=Y==Ei= @==..a=@W==x=Y==A=1===M==A=:==u'A=1==A=:==dD=o==&=UH>=?J=N>==L>=\Y=[>=XG===F=x>=͢W>=P>=S>=R~X>=͢W>=S>=M'=w>=qM;=[v>=2=kr>=M'=w>=2=kr>==r>=by5==x@==噔$==by5==噔$==G$==$a׌==𪽐}==I==Vp==$a׌==I==Or=*A==Qm==="$=g==?k=yU>=TT=IV>=c=nZ>=*ށ=:V>=?k=yU>=6-w= [>=6-w= [>=?k=yU>=c=nZ>=r<ù<=<<=,===jց<p>=jց<p>=B=6J=6J=B=M(r<Ѡz>==sJ==T=2==+߉=|P==; +̽n,>= W+>=̽o&>=eN=t\>=c=nZ>=TT=IV>=eN=t\>=TT=IV>=P@=SW>=}hQ'eN=t\>=3=[>=vz<=<`>=3=[>=eN=t\>=P@=SW>=oT=]wF==[= /=={E=0g.==?=H==oT=]wF=={E=0g.==?=H=={E=0g.==0=8===r>=@=t>==x>==?Cn>=@=t>==r>=+[=%<=Ӯd=R<=C=}-<=h'C=}-<=I==;==+[=%<=LG=Zq<=C=}-<=Ӯd=R<=X#=X<==<=s=V<=s=V<=0=#<=X#=X<= %Bz'0=#<=s=V<=%===cG=Ys<=+=DX<=h=<=h=<=+=DX<==<==<=X#=X<=h=<='&K<=f==I==;==0=#<=O+ S'K<=f==0=#<=%===K<=f==%===K+=]#==C=}-<=X#=X<=0=#<=sE 'p"'0=#<=I==;==C=}-<=X#=X<=C=}-<=2=Ѹ<=?J=N>=Q="T>==wQ>=K ===[=z@==S=)==hG>=&<>=;nX>=,HQ >=;nX>=&<>=hG>=;nX>= c)>=ݘ==c==Â==₷P==ݘ==Â==S=)==[=z@==}H=#==}H=#==֬=V===S=)==;q>=E¼r>=鍯x>=ݗ@}>={fxw>=鍯x>={fxw>=;q>=鍯x>=F'k=J >=S=J >=v=>=r=wz>=v=>==S{>=s|'g >=kJ >=SJ >=%& ҷ'SJ >=TMVd~>=g >=g >=TMVd~>=Oa,Sy>=#=<=[!=o;==o;='a \=v}>=S=J >=k=J >=t&'k=J >=v=>=a \=v}>=v=>=r=wz>=a \=v}>=a \=v}>=M=7x>=λ>=|>=ժ'r =>==J >=[!=J >= &'[!=J >=I%=)}>=r =>=r =>=I%=)}>==x>= F;o;= Fo;=q:A<=b'پo ~>= FJ >= F;J >=4'ޏ=>x= ŭ==M(r<Ѡz>=ޏ=~'$'=>==>=K=>=½==̽3==̽|==½==)_˪====47'Ѫ=U">==o&>==1 >==3===S=====I'=b@====== ===V==ֻ=Aڪ===b@==z-'=O+9>==2>==6>==6>==dz2>=='9>=*'= ===ώ===,==<'= ===,===b@===V===,==b====,===V===b@==C'"=b=>==i?>==O+9>= "'xu%=6>="=b=>==O+9>==6>=='9>="=b=>=D'u=UQ>==%R>==K>=f'D]%b=I>=u=UQ>==K>==wQ>=u=UQ>==L>=u=UQ>=b=I>==L>=D'=cX>==%R>=u=UQ>='%=cX>=u=UQ>==xY>= ̸=V>=u=UQ>==wQ>=u=UQ>= ̸=V>==xY>=̽6?==̽Z0X==*½ãL==̽6?==*½ãL=="ýC8==??==*½ãL==UcU==*½ãL==??=="ýC8==sjs>=w>=Q{iz>==I==T===V===g {F==*==!==Ҥ=U#Y>=8=^U>=z==SD[>=OOL'&?=Z>=r=(`>=z==SD[>=z==SD[>=8=^U>=&?=Z>=Ph<=+优Œ<=@WLI<=@WLI<=I?j7<=Ph<=,&I0' ]>=`>=Z>=L&e'{t<=ˊ<=g {F==w| 'g {F==_,bn<={t<=g >=Oa,Sy>=;|z>=j>=Q g>=qd>=Q g>=j>=bBn>= Q==Ԝt==Vp== Q==꽐== q== q==x== Q== Q==Vp==꽐==%/>a>=23)h>=n`>=>/f>=23)h>=%/>a>=%/>a>=|>*`>=>/f>=QH==a%G==ަ4==ަ4==ڰw.==QH==??==UcU==QH==ڰw.==??==QH==t==oǺm==`ZE==mRv==t==`ZE==c==t==~k+==t==mRv==~k+==`ZE==oǺm==|;u==`ZE==|;u==}!;l===|54>=ƫs%.>=9F\9>=""^:>=|54>=9F\9>=|54>=\45>=Hi/>=\45>=|54>===͎i==cZD====cZD==re==DB<=@Rp<=2Q<=`o<=@Rp<=DB<=DB<=So;=`o<=x'IJ >=SJ >=}>=y@or>=QGw>= 1hu>="=VH>==7I>=M= ID>=I==B==@==I==𪽐}==;==;==B==I==3J==0pOV==M ==q'M ==V<=3J==G$==M ==0pOV==&.Y>=Ar̹`>=%o]>==Ar̹`>=.Y>=S7P>=AT>=eT>=n&$J'kcV[>=%o]>=YC,a>=M`e>= GKPf>=YC,a>=M`e>=YC,a>= %pEb>=P&iU' %pEb>=YC,a>=%o]>=>9^%j>= GKPf>=M`e>=M`e>=sz%g>=>9^%j>=ns%n>=>9^%j>=sz%g>=E=!==5Y=== P=m==t'=cX>==xY>==^>=ɨ'?#=^>==xY>=^=<_>=&o;=G/X'<=Io;=I=o;==n*<=&=o;="=VH>=j_m=H>=Wx=\O>=Wx=\O>=Z=2zM>="=VH>=%0'==<=.0==I==wJ==.0==ݦ:'250<=I==.0==! c&'<=250<=.0==hK========-*==½&==5;,M%½&==̽======-*====S7P>=֍uJ>=tx@M>=,NsI>=P>=tx@M>=tx@M>=K=F>=,NsI>=֍uJ>=K=F>=tx@M>=R~X>=Mz]S>=OZ>=2=En?>=Ѡ=C>==S5>>=q=>8>==S5>>==!:>=Wx=\O>=?k=yU>=*ށ=:V>=Wx=\O>=*ށ=:V>=O=%FR>=Wx=\O>=O=%FR>=Z=2zM>=Wx=\O>=+]=QQ>=?k=yU>=i=mq>=j~= u>=;v=o>=*X=q>=j~= u>=ш=Pv>==S{>=ш=Pv>=j~= u>=;v=o>=j~= u>=*X=q>=b===%=:==ɪ===%=:===X==ɪ===t'.:J >=TMVd~>=SJ >=pR|&Ɠ' 1|>=TMVd~>=.:J >=AT>=P>=͢W>=%.=>=F=x>=XG===4*=QE=="$=g==‚=>=4*=QE==‚=>==5>=v=n==4*=QE===5>=& ';==w==\+7<=ӃV<=@WLI<=\+7<=\<=ӃV<=\+7<=T'%'w==\<=\+7<=VF'M3<}h<=<<=e<o>=O==e<o>=%ڔ<]g>=֫===O=e<o>====@=t>=e<o>==< z>=@=t>= ؼW >=v뼜>=%>=a>=񅼻#>=%>= ˼->=a>=%>=v뼜>= ˼->=%>=YUi>=񅼻#>=a>= +.k>=YUi>=a>=YUi>= +.k>=] -W>=Sl&4'=bd>=^=<_>=ջ=5Ic>='Gջ=5Ic>==h>==bd>=y =g>==h>=ջ=5Ic>=' J >=H|>=>xJ >=)&}'>xJ >=H|>=ݗ@}>=>xo;=-80<= o;=DX>=2 V>=eIQ>=eIQ>=zF\T>=DX>=DX>=zF\T>=kcV[>=z&h&y =g>=ջ=5Ic>=1=_>=3&" 'ջ=5Ic>==\>=1=_>=7;'=\>=ջ=5Ic>=^=<_>=pfy ' ]>=1b>=qd>=NH'娽]>=1b>= ]>=C&'`>= ]>=qd>=m,s=k>=\=n>=i=mq>=m,s=k>=ḃ=e>=5o=e>=AhY=+h>=m,s=k>=5o=e>=\=n>=m,s=k>=AhY=+h>=-٫B>=sY?>=G=>=TgN>=II>=DgE>=DgE>=\H>=TgN>=-٫B>=\H>=DgE>="=VH>=Z=2zM>==7I>=eT>=AT>=Z>=eT>=Z>=.Y>=,=`_==܉=<===O===O==k= ==,=`_==L=*===|/==,=`_==k= ==L=*==,=`_===>;==+߉=|P==T=2==&&>=P<=(=<====r=O<=?=jy<=>=P<=>=P<=?=jy<=Q=I<=jp '>=P<====r=O<=" e>= ּ d>=fιk>=ѝ=zp>=E¼r>=fιk>=;]i>=ѝ=zp>=fιk>=;]i>=fιk>= ּ d>=o0a,cP'%o]>=Ar̹`>= %pEb>= %pEb>=sz%g>=M`e>=sz%g>= %pEb>=Ar̹`>=u\' 'ˊ<=M ==*== }&ˊ<=WH <=M == '+4'V<=M ==WH <=x>z[,==wJ==eW'==eW'==@ OC==x>z[,==x>z[,==@ OC==X"3^sG==p8u&3J==V<=tQ *<=tQ *<=s82<=I<=`J V<=s82<=tQ *<=']'&R0'I<=3J==tQ *<=ag:==@ OC==eW'==aX==@ OC==ag:==A}>=+5v>=z>=ƘL8?'IJ >=}>=A}>=} 'IJ >=A}>=&J >=zsE'6-w= [>==%F`>=_='[>=_='[>=*ށ=:V>=6-w= [>==|>=%u=>=S=y>=<ܛy>=᪽>=z>=zF\T>=TgN>=wCT>=Fk3X>=zF\T>=wCT>=$h'ݗ@}>=fѼ:}>= ŭJ >=''.< ==$<==@=f>=fK>=,;&)'.< ==@=P@*6>=C />=܌<= o;=-80<=&&=|xwB>=fK>=?n>=|xwB>=몽$>=뫽r>=< >=Xe>=7ϲ==N Ų==/'==4@>=m@>=ռ7;>=h ]=== P=m==f=l==|܏,>=C />=ޚ2>=P@*6>=R]8>=ޚ2>=ޚ2>=C />=P@*6>=Oyb==.{==QԻ퇠==5]<#N==!':==f);==bd'a/g=>==>=K=>==#ǯ@$>=ꥣ #>=U'Ʈ['تa >=몽$>=S>='B&S>=#ǯ@$>=تa >=< >=뫽r>= >= >=뫽r>=NX >=YH==_=X^==pql w==n8D>=Xe>=< >=5Z<>=lC<{>=:$=X>=:$=X>=lC<{>=~ =>='=,====h==b(=#=='=,=====(= ====LÒ ==qE==qE==5x====J=F===== ===l=_E>=Ѡ=C>=2=En?>=l=_E>=M= ID>==7I>=2=En?>=M= ID>=l=_E>= =>=Y =U4>=f=!O >=@==="$=g==`݋===Or=*A=="$=g==@====Q5===<&==K ===K ===S=)===Q5==P@*6>=ټ@ 2>=07>=07>=ռ7;>=P@*6>=g&\ 'ų-=:θ====h==M>=է==̥'T'b(=#====h==ų-=:θ==;z==N Ų==;96==N'M>=է====h==S=Uڻ==K=>=Y =U4>=#=>=.=(>==)$>==+>=Ѫ=U">==)$>=.=(>=1w>=;}n>=uU>=͡FB==LÒ ====[81==LÒ ==͡FB==C<?'Ę!>=D%>=MO>==d==~B=\===sJ==7 =g==b<==<==&==ߖ)P==ݘ==].p* >=\~>=um[>=Y~6>=p)o>=um[>=i=<=V=&M6<=^j=_<=^j=_<=}=yFC<=i=<=5]>=c==ݘ==ݘ==ߖ)P==5]>=uU>=we>=>=>=1w>=uU>==>=c===lc=( >=Ɖj>=;}n>=?n>=]Y 'Ɖj>=Ę!>=MO>= X*<==|;u==t(<==t(<==|;u==|I;$==ITE==7ϲ====9<‡:'ITE==5x==Ҟ====5x==ITE==#=<==o;=h==1<=i<#<=><}h<=h==1<={ʌ_==Â==+ h===/ʉ<#>=N=<[R&>=/ʉ<#>==ֿ`g>=²tg>=#a>=<'>== >==>=\h'h>=–t" ==Y~6>=jUO==–t" ==\h'h>==_=qZ<S>=qZ<S>=m==h ]===f=l==o===z'!'&½_>=ֿ`g>=#a>=Pj!+'̽bd>=ֿ`g>=½_>=/[==!':==Yp==)ىB,==*==噔$==!==*==)ىB,==}E=3ep>=PhUv>=PhUv>=;|z>=)ut>=g=F= >=_B;=I0>=2-=R >=&=$Y >=(=>=6伇==q'¼==ʝc==ʝc==%ּ==6伇==>$?>=&4G>= A>=Ej;="=='=,==(= ====h=='=,==Ej;="== 0=C'o'==|==J==2==eXI:b==RW;==uU>=;}n>=jQ>=!hU=3c==2;=~==M>=է==HZ[=ɢ==2;=~==!hU=3c==VYq>=QGw>=y@or>=I^;^<=q:A<=5Ḙx<=5Ḙx<=&a;`l<=I^;^<=2= >=ѡ=)>=!=F>=&$'.G=======|==T;'pJ=F=====.G====|==@=>==.G===R~X>=OZ>=&-]>=@})'&-]>=OZ>=#a>=&-]>=娽]>=R~X>=V:%'=7>==[>=g=>=1;==}E=#=>=n=>=n=>=g=>=K=>==4|=lC<{>=X<>=4|=O===/Լ~OE>=iJ>=mejI>=/Լ~OE>=m@>=4@>=}<>=6< >=J<>=?n>=몽$>=`>=p&BY'ꥣ #>=Ę!>=`>=8==l,!>=D=h&>=D=h&>==l,!>=d=&>=3>=YdM:==y>=y>=>>=3>=4@>=ռ7;>=]I<>=]I<>= A>=4@>=""^:>=͗l>>=v7>==>=<'>==>==A}>=AT1>=z>=᪽>=AT1>=AT1>=A}>=z>==Q==f=k==Q=[==(= ==C=@==Qz=]==}< >>=lC`=j=mnx>=u>=û;}>=û;}>=u>=H|>=߶\==x@==hZ==peE==x@==߶\==F= >=2-=R >= \0=Qe>= \0=Qe>=<=+>=F= >= \0=Qe>=2-=R >=(=>=2/,B>=` >>=E>=^Ok>=ns%n>=sz%g>=^Ok>=Q g>=bBn>=sz%g>=Q g>=^Ok>=48<F>=z;;CnB>=d;ԍG>=d;ԍG>==48<F>=6伇==%ּ==s==s==%ּ==n==o%6L0'2m<>=V<==YU=8>=DE'~=V<==2m<>=]M#'z/tp==pql w==pH}==!<Y">=7;#>=aw;F'>=E>=擼3hK>=D`TE>=mejI>=擼3hK>=E>=t9و<=vr-m<=`<=`<=vr-m<=\<=vۣs+'=ý!==̽S==½z`==&` '½z`==/d===ý!==4!=j>=(=>=~ =>=C=dk<=V=&M6<=ؽ<=[<=rD>=/Լ~OE>=4@>=iJ>=/Լ~OE>=rD>=&X ?'_='[>==%F`>=U=`>=$S6'U=`>=&?=Z>=_='[>=8<^ >=6< >=5Z<>=5Z<>== >=8<^ >=Le&l:':ża>=9$>=ɽμʈ!>=}>= ؼW >=Ǯ%0 >=%>=6 >=Ǯ%0 >=Ǯ%0 >= ؼW >=%>=Ǯ%0 >=6 >=}>=Z2:Xp>= 1hu>=yp>=y@or>= 1hu>=Z2:Xp>=yp>=%+7l>=Z2:Xp>=0>=07>=ټ@ 2>=h :<8>=07>=0>=Öo>=;nX>=].p* >= c)>=;nX>=Öo>=&8G'9n==E'====N]%,'-e==E'==9n==7n&$'==~v==9n==@Rp<=b<=LD<=1zG<=4t=qZ<S>=OT|=vU*==뫽r>=Xe>==7I>=Z=2zM>=#{=N>=#{=N>=Z=2zM>=O=%FR>=eД==R.==dX==dX==b==eД==٨==R.==eД==6ͭ=>=h=C>==='>=j|6'N3`>=\>=KlZ>=e6A'=='>==)$>=C=L>=&lE'Ѫ=U">=g=>=C=L>=yQ&yp'C=L>==)$>=Ѫ=U">=-80<=>xo;=K3<=K3<=I?j7<=-80<=$<Q=====>===Y~6>=I==>=>=p)o>=Y~6>= Q"W>=:P>=ϺVV>=s:97U>=:P>= Q"W>=N 9==d! ==n==7L'N 9==3y==--==--==d! ==N 9==u*O'n==3y==N 9=== +.k>=E" >=E" >=O%>=q|!>="&L'3's >= +.k>=q|!>=~v==6==0f.==\Fc-==_=X^==Sݠ==\Fc-==m==ss==ss==_=X^==\Fc-==Sݠ==m==\Fc-==_c=p)o>=>=ޘ=`Cj>=^P="e>==~e>=f# >=p)o>=ފd$ >=f# >=we>=].p* >=QE3>=X'->=|܏,>=ݘ==₷P==Dڼ{.==Dڼ{.==₷P==*I⼒==Dڼ{.==&==ݘ==E|>=>=NX >=ꥣ #>=h )>=|&>=|&>=h )>=N+>=ϺVV>=ZdF`S>==-IZ>==-IZ>=ZdF`S>=@Y>==oER*>=d+K$>=B4B+>=R= ->=)P=2>==&->=&?=Z>=8=^U>==-W>==-W>=8=^U>=O=%FR>==-W>=_='[>=&?=Z>=M=K]==?=H==8=]==8=]==ш;=ar==M=K]==8=]==?=H==I'=KO===t/==E&,== 5==iA{==E&,===t/== 5==q(]:===t/==(=d==3===I?G===I?G===ш;=ar==(=d==Or=*A==@===z===z===f=l==Or=*A=="==٨==q޼wy=="==h q==R.==R.==٨=="==q޼wy==h q=="==܉=<==y====e===e==y===4j===="hýi2>=2P9>=\45>=ټ@ 2>=C />=0༡i,>=V<==~=<7&>=<7&>=~=<[R&>=T=&>=ܚ=߆!>==">= =F'>=ܚ=߆!>=T=&>=.< ==P=n8D>=唽4>=|,'z >=}>=SJ >=z >=g >=;|z>=;|z>=}>=z >=re==cZD==$vK==$vK==w'==re==(Ǽ(g==@[==q޼wy==(Ǽ(g==|nq==V4X==V4X==@[==(Ǽ(g==q޼wy==|nq==(Ǽ(g==eܟƳ'w==n===?d=W!>=b=##>= ̽0E>=̽K>=;^\G>=3X=u==HZ[=ɢ==an===0E===HZ[=ɢ==3X=u==an===]=q==3X=u==rP= 8>=ydX= =>=e=:>=%q*'MO>=D%>=>=%/'>=D%>==z$>=?;>=$k==Q>`==D~\=Ջ<=(y=k<=^j=_<=Ӯd=R<=(y=k<=D~\=Ջ<==+>==dz2>=}=~/>=}=~/>==dz2>==6>=bޙY>=Z>=AT>= ]>=Z>=bޙY>=AT>=͢W>=bޙY>=}sF'rݭb>=1b>=娽]>=rݭb>=#a>=²tg>=C2k;==U1==rV==H)H+==&==pN@==|h"x==N Ų==;z== ,=D A>=ZG=;B>=;=_;>=h ]===o===l===\~>=D1>=YR .>=E&,==iA{==:vBF==G$=6< >=}<>=G$=@[=X<>=őS>=5]>=}>=}>=bx>=őS>=c==5]>=őS>==L>=.=>=g=&=>= 9=3==ہ===2=H==5)=~j8<=br.=u<=C=dk<=h6==RE)==?kD==^ vS==RE)==h6==W,O>=TgN>=\H>=wCT>=TgN>=W,O>=O%>=E" >=>͹'>=%e>= ּ d>=n`>=%e>=23)h>=;]i>=;]i>= ּ d>=%e>=n`>=23)h>=%e>=D===#=}==te"===$<2'n$>=9$>=W!c>==V}>=bx>=y>=vr-m<=t9و<=ٻ<==j===Z0X==q=O==PC=#aK>=6=F>=*-==L>=0===XG===E=!==%.=>=XG===0===?n>=;}n>=Ᵹ B>=_$'̽|==̽==ý==w<=`o<=ko;=5r a<=`o<=w<=Hm>>=u8MC>=5?>=>$?>=29>=5?>=5?>=u8MC>=>$?>=5?>=29>=Hm>>=>$?>= A>=N';>= =|=Oz>=o=ux>==|>==8u>=o=ux>=o=ux>=|=Oz>==|>=o=ux>==8u>= =Ru>=>hUý,>==̽==̽u==(=d=====d=V;==d=V;=====7 =g==&*=YU>=P@=SW>=B=[Q>=ǧh;^\G>=̽K>=0ýN>=TJ>=;^\G>=0ýN>=rݭb>=²tg>=`g>=1b>=rݭb>=`g>= 3'; ==2==RW;==8=^U>=Ҥ=U#Y>=De=zS>=8=^U>=De=zS>=#= P>==~e>=^P="e>=r=(`>=-2˧̽^>=̽bd>=½_>==V}>=c==őS>=bx>==V}>=őS>=/[==Yp==|==t=O;7>= ;O#;>= =;==te"===#=}==0 =V== =;==#=}=====(=d==.=#=l== \0=Qe>=(=>=4!=j>= \0=Qe>=4!=j>=<=+>=1SW'>=3's >=q|!>=1SW'>=q|!>=O%>=*-==L>=B=[Q>=PC=#aK>=8=]==I'=KO==.=#=l==ш;=ar==8=]==.=#=l==0f.==-e==9n==~v==0f.==9n==S=o;=.:=o;=ؽ<=[<=S=o;=ؽ<=[<=V=&M6<= aH===I?G===3=== aH===0E===3X=u== aH===3X=u==]=q==pql w==_=X^==ss==pH}==pql w==ss===Qn==ہ=== 9=3==`7==%ּ==ʝc==ʉx=5>=e=:>=Pt=D=>==9>=ʉx=5>=Pt=D=>=LG=Zq<=Ӯd=R<=D~\=Ջ<==t/==q(]:==(;==iA{===t/==(;==Oyb==|I;$==oǺm==.{==Oyb==oǺm==)9'H#У=o&>=Ѫ=U">=.=(>=)ut>=;|z>=Oa,Sy>=^Ok>=bBn>=3ep>=ns%n>=^Ok>=3ep>=S=o;=k=o;=i=<=S=o;=i=<=}=yFC<=_=X^==YH==:vBF==_=X^==:vBF==iA{==e=X<>=@[==-W>=O=%FR>=*ށ=:V>=_='[>==-W>=*ށ=:V>=}}D3==peE==xwQ==}}D3==xwQ==ag:==cG=Ys<=#=<=h==1<=cG=Ys<=h==1<=><}h<=cG=Ys<=br.=u<=5)=~j8<=cG=Ys<=5)=~j8<=#=<=½z`==̽S==̽3==½==½z`==̽3====/d==½z`====½z`==½===6?===<&===Q5==q=O===Z0X===6?==!==)ىB,==}}D3==Oa,Sy>=QGw>=VYq>=`=׃==`݋===4*=QE==v=n==`=׃==4*=QE==(z >=SJ >=kJ >=&'g >=z >=kJ >=Z2:Xp>=%+7l>=Bl>=y@or>=Z2:Xp>=Bl>=% <'r=(`>=&?=Z>=U=`>=i=<=k=o;=S=o;=V=&M6<=i=<=S=o;=f=l== P=m==5Y===Or=*A==f=l==5Y===Z"hýi2>=̽2>=̽O+9>=+H &2P9>="hýi2>=̽O+9>=S%M'rݭb>=娽]>=&-]>=r:%d'#a>=rݭb>=&-]>=;==5]<#N==P[lQ'@==='=,==>====== >=NX >=j7 >=>=S>="e>=|xwB>=?n>=Ᵹ B>=|xwB>=Ᵹ B>=f>=f);==LGR== 3'; ==1;==5]<#N==f);=="e>=S>=몽$>="e>=몽$>=|xwB>=/[==LGR==f);==!':==/[==f);==}E=Ᵹ B>=;}n>=Ɖj>=MO>=jQ>=Ɖj>=jQ>=;}n>=qE==PἋ`==ܼ`A==qE==ܼ`A==5x==nR=====h==Ej;="==NA&)'S=Uڻ==e=S==kl=/@==|h"x==QԻ퇠==/'==N Ų==|h"x==/'==#'2ѻD==Yp==;96==N Ų==2ѻD==;96==g''تa >=ꥣ #>=`>=>]'تa >=`>=몽$>= =>=#=>=Y =U4>=07>=h :<8>=]I<>=07>=]I<>=ռ7;>=6< >=8<^ >=J<>=f=l==z===o===|h"x==;z==Q;)ߧ==QE3>=|܏,>=ޚ2>=R]8>=QE3>=ޚ2>===>== >=n=>==='>=C=L>=n=>=C=L>=g=>=D===7 =g== ===#=}==D=== ===f# >=].p* >=um[>=p)o>=f# >=um[>=>=>=p)o>=&v%?2a'l=О==b(=#==ų-=:θ==b&,&z/tp==pH}==mچd==w===8<^ >== >=Öo>=].p* >=we>=6ͭ=>==='>=n=>=#=>=6ͭ=>=n=>=>=ފd$ >=ݤr >=>=ݤr >=1w>=Ɖj>=?n>=`>=y &{2O'Ɖj>=`>=Ę!>= =ފd$ >=>=we>=f# >=>=ʻ<'`M==8=jUO==\h'h>=5)=~j8<=C=dk<=ؽ<=[<=Y< 5t==8=uU>=jQ>==>=#==i=====l====i==l===o===2ѻD==7ϲ==J==yb%j('|==2ѻD==J===h :<8>=N';>=]I<>=N';>= A>=D===te"===+=ڗ== ==H)H+==pN@==ƶ7%]%'`<=\<=JW==00<ܽ==#= F;J >=2w;*y}>= o;=܌<= Fo;= Fo;=܌<=q:A<=*==y>=YdM:==jL{'ޏ= ŭ==H= >= =>=f=!O >=ѡ=)>= =>=H= >=H= >=Z=| >=ѡ=)>=e=:>=BH`=4>=rP= 8>=F4>= 6>=rP= 8>=L=3>=L=3>=rP= 8>=BH`=4>=ko;=So;=w<=_i<=5r a<=w<=w<=So;=_i<=֩T%3-g'l=О==>===b(=#==gA$'>===l=О==pK =9==,HQ >=D1>=\~>=,HQ >=4 >=D1>=Y&Y'--==E&,==:vBF==II>=eIQ>=.CN>=EJ!=%>==>==>==>=%.=>=T===%.=>==>=EJ!=%>=7;#>=Vw!>=a'>= ,=D A>=g=T:>==g?>= ,=D A>==g?>=| =DF>=<>>=N =`E>==g?>=| =DF>==g?>=N =`E>=N+>=̽7>=̽=NX >=>=j7 >= ̽=̽>=R~>=R~>=fK>=j7 >=fK>=R~>="e>=>="e>=R~>==&->= =F'>=R= ->= =F'>=T=&>=R= ->=nV=>=d=׻>=mw=>=mw=>=g=Ki>=nV=>=g=Ki>=mw=>== Q>=7=i_1>=F4>= 6>=L=3>=7=i_1>=L=3>=W$E=JX->=BH`=4>=Y=.>=L=3>=W$E=JX->=L=3>=Y=.>==4>=g=T:>=,= 5>=F4>= 6>=7=i_1>=,= 5>=N=!<Y">=aw;F'>=N=aw;F'>==|'&J >=AT1>=JJ >=h!U'JJ >=AT1>=᪽>==4>=<ύ2>=K=":9>=K=":9>=<ύ2>=~0==E<,>=<[R&>=\H>=֍uJ>=W,O>=eT>=W,O>=S7P>=W,O>=֍uJ>=S7P>=mw=>=d=׻>=6z=>=~0=<>>=K=":9>=g=T:>==4>=K=":9>=g=T:>=K=":9>==g?>==g?>=K=":9>=<>>=N =`E>= =cJ>=| =DF>=6=F>= ,=D A>=| =DF>=*-==L>=6=F>=| =DF>= =cJ>=*-==L>=| =DF>=d=&>=V<==<7&>=d=&>=<7&>==,>=<[R&>=E<,>=<7&>==,>=<7&>=E<,>=d<_5>=)<9:>=#<79>=G<78>=lC`=)<9:>=)<9:>=d<_5>=G<78>=)p ==g {F==!==#{=N>=O=%FR>=8=^U>=8=^U>=#= P>=#{=N>=E<,>==c=~0=#<79>=` >=)<9:>=}< >>=#<79>=` >=#<79>=}< >>==d===sJ==+߉=|P==+߉=|P===Qn===d==.<>=#$=0s<ő>=eT>=.Y>=wCT>=wCT>=W,O>=eT>=BH`=4>=e=:>=ʉx=5>=BH`=4>=ʉx=5>=Mo="/>=TE;% !'n$>=W!c>=d+K$>=n$>=d+K$>==oER*>=n$>==oER*>=2x)>= => F==֬=V===&=S===ia== => F==&=S==/<== <==q=:ża>=ɽμʈ!>=>͹'>=E" >=ɽμʈ!>=B'J=F===u=====IH*8'T=<-b>=^=p$0'^==T=S=T==H)H+==$k====&==H)H+====&====ߖ)P== 5==3y==zӬ== 5==zӬ==Ӡ==PἋ`==b==zӬ==Ӡ==zӬ==b==<ύ2>==,>=E<,>=b==dX==Ӡ==Ӡ==dX==q(]:==q(]:== 5==Ӡ==X3'fѼ:}>=k߼J >= ŭJ >==O==܉=<===e===e===^===O===e==4j====kl=/@===^===e==kl=/@===ia==q=O== => F==o%Լr==qE==LÒ ==PἋ`==qE==o%Լr==Y=.>=BH`=4>=Mo="/>=Y=.>=Mo="/>=d=H1)>=d=H1)>=Mo="/>=l|=)>=N =`E>=<>>==N =`E>===7*K>=^< E>=9n===7*K>==9n==f==|=ʇ== 9=3== 9=3==2=H===f==1;== 3'; =="<== 3'; ==1;==f);==}< >>=^< E>=` >=<>>=~0=` >=<>>=` >===` >=^< E>='=>=g=>==1 >='P'=1 >=g=>=Ѫ=U">=Z"hýi2>=̽n,>=̽2>=K1Zj"hýi2>= W+>=̽n,>= W+>="hýi2>=Hi/>=Hi/>="hýi2>=\45>=|I;$==;.2==t(<==T==m= :'m=<-b>=T=ް̽cX>=8ȃT>=̽%R>=========Q==:$=X>=~ =>=(=>=&=$Y >=:$=X>=(=>=5Z<>=:$=X>== >=&=$Y >== >=:$=X>=&=UH>=>=C>=s=DI>=&=UH>=s=DI>=?J=N>=R~X>=娽]>=͢W>=娽]>= ]>=͢W>=͢W>= ]>=bޙY>=|h"x==Oyb==QԻ퇠==;.2==|I;$==Oyb==;.2==Oyb==Q;)ߧ==Oyb==|h"x==Q;)ߧ==h q==Y,4f==>$~==SoV==Y,4f==h q==ag:==eW'==)p ==!==}}D3==)p ==ag:==)p ==}}D3==@==B==Z==@==Z==8}==eU==Z==oh==Z==eU==8}== :?>=&&@I>=̺$>=_`==xwQ==peE==Nz)==rto;==%9en==b_==Nz)==%9en== q==rto;==Nz)==;==@= ؼW >=}>=2 >=}>=5]>=2 >=5]>=ߖ)P==;==;z==;96==3˅&XV';==;==;96==NhW'Yp==!':==;96==ͫ&*('!':==;==;96==;z==;==ȍ=j_m=H>=X=L>=Wx=\O>=X=L>=+]=QQ>=0s<ő>=}<>=J<>=0s<ő>=J<>=P<T==J<>=00<ܽ==P<T==uU>=]V>=we>=]V>=Q_p]Z>= c)>=Öo>=]V>= c)>=]V>=Öo>=we>=.CN>=eIQ>=2 V>=.CN>=2 V>=T>=W N>=.CN>=T>==է==2;=~== =;==M>=է== =;==ų-=:θ== =;==l=О==ų-=:θ==Ԩ=q{<=?=jy<==gz<==gz<=ÿ=!<=Ԩ=q{<=Ԩ=q{<=ÿ=!<=K=LZ<=VYq>=Bl>=>9^%j>=y@or>=Bl>=VYq>=|&>=Ę!>=ꥣ #>=D%>=Ę!>=|&>=|&>=N+>=D%>= =cJ>=N =`E>==7*K>=Щz<=s82<=%<{<=%<{<=x <=Щz<=埽lZ<=x <=%<{<=ϺVV>==-IZ>=KlZ>==-IZ>=@Y>=d0_>=O'䚤&d0_>=N3`>==-IZ>=`h3b&'KlZ>==-IZ>=N3`>=YU=8>=lC<{>=2m<>=- '4|=~=2m<>=2m<>=lC<{>=4|=`dz >= ؼW >=2 >=eXI:b==YdM:==I3;w/==AhY=+h>= E=fm>=\=n>=I&=k>=2=kr>= E=fm>=te"===2;=~==+=ڗ==+=ڗ==2;=~==0E===0E===3===+=ڗ==S=)==֬=V=== => F== => F===Q5==S=)==T===0===V===T===%.=>=0===LG=Zq<=C=dk<=br.=u<=LG=Zq<=br.=u<=2=Ѹ<=2=Ѹ<=C=}-<=LG=Zq<=^< E>=}< >>=j=^< E>=j=iN=Y,4f==It\==AJv==Y,4f==AJv==5zM==Y,4f==5zM==>$~===Q==`݋===`=׃==`݋====Q==Q=[==Q=[==@===`݋===Q=[==z===@===mچd==oh==E'==¦Y'E'==-e==mچd==pH}==oh==mچd==7ϲ==ITE==J==dhwR'J==ITE==Ҟ==?5'Ҟ==o'==J==h :<8>=O&5>=N';>=29>=>$?>=N';>=N';>=O&5>=29>=ϺVV>=KlZ>= Q"W>= Q"W>=KlZ>=\>=\>=s:97U>= Q"W>=Ǚ(g==*½ãL==̽Z0X==̽Z0X==̽*q==Ǚ(g==?;>=Q>`==jUO==?;>=jUO==YR .>=?;>=YR .>=D1>=b==PἋ`==o%Լr==eД==b==o%Լr==eД==o%Լr==٨==&4G>=.CN>=W N>=s==n==d! ==6伇==s==?==2w;*y}>=;]mu>=mnx>=mnx>=پo ~>=2w;*y}>=F^<z>=;]mu>=2w;*y}>=8fi'H|>= J >=û;}>=پo ~>=mnx>=û;}>= 'پo ~>=û;}>= FJ >=S' FJ >=û;}>= J >=lc=( >=c===>===d=H1)>=?d=W!>=OT=%>=C8'=2>==n,>=}=~/>=q2'%}=~/>==6>==2>=N*& ''>==z$>=Q_p]Z>=]V>=>=Q_p]Z>=MO>=>=jQ>=]V>=jQ>=>=.:=o;=[!=o;=ؽ<=[<=5)=~j8<=[!=o;=#=<=[!=o;=5)=~j8<=ؽ<=[<==w==~B=\===d===/>= =&2)>==&->==+>= =&2)>==/>==/>==dz2>==+>=!'-e==¥V==mچd==%&T.'¥V==z/tp==mچd==9H0=:>=<=+>=4!=j>=4!=j>=~ =>=YU=8>=\=wU>=0=S=S==\=wU>=ٷ'c=rb===r=====x&!ֻ=Aڪ==c=rb=====#&&ֻ=Aڪ======b@==X"3^sG==It\==Y,4f==O=lC<{>=5Z<>=5Z<>=6< >=O=G$=X<>=O=6< >=G$=O=D===d=V;==7 =g==3===(=d==d=V;==3===d=V;==+=ڗ==D===+=ڗ==d=V;===+'===(=<= =~==C#' =~==(=<=׋==;<=.=>=]=^>=!=F>=!=F>=g=&=>=.=>==AG==;==Ei= @==x=Y==+߉=|P===>;==x=Y==DgE>=L^G{@>=sY?>=sY?>=-٫B>=DgE>=/Լ~OE>=mejI>=E>=/Լ~OE>=E>=m@>=l======e=S==e=S==nR===l===l===nR===h ]===)D>=K=F>=֍uJ>=)D>=\H>=-٫B>=֍uJ>=\H>=)D>=G=>=)D>=-٫B>=d! ==C2k;==rV==rV==s==d! ==rV==?==s==9==C2k;==d! ==d! ==--==9==#{=N>=#= P>=s=DI>=s=DI>=Ѡ=C>=l=_E>=s=DI>=l=_E>==7I>==7I>=#{=N>=s=DI>=4@>= A>=rD>=rD>= A>=&4G>=&4G>=iJ>=rD>=1SW'>=O%>=|܏,>=X'->=1SW'>=|܏,>=|܏,>=O%>=>͹'>=>͹'>=C />=|܏,>=>͹'>=0༡i,>=C />===< z>=< z>===uB====re==S=Uڻ==!hU=3c==M>=է==4j====HZ[=ɢ==!hU=3c==4j====!hU=3c==kl=/@==S=Uڻ==kl=/@==!hU=3c==Qm===5Y===\Y=[>=5Y===Qm===Or=*A==\Y=[>=5Y===XG===5Y===E=!==XG===FL >=w9>=&&@I>==>=&=$Y >=EJ!=%>=_B;=I0>=%.=>=EJ!=%>=2-=R >=_B;=I0>=EJ!=%>=&=$Y >=2-=R >=EJ!=%>==3==@=>===|=====@=>===3==JƼ&c=rb==A=:===+==F'=+==A=:=====&Q'====S===+== 3'; ==RW;=="<==*==M ==噔$==G$==噔$==M ==F=!===I==V===F=!==C=@==N<%==F=!==V===Qz=]==F=!==Qz=]==C=@===W=>=F=x>=W=>=>j=1>=\Y=[>=\Y=[>=F=x>=W=>=9n=^< E>=iN=iN=0=9n=0=iN=_=QԻ퇠==[81==͡FB==QԻ퇠==͡FB==/'====7ϲ==͡FB==/'==͡FB==7ϲ===B:@==}H=#==;=(==;=(==z=8===B:@==;=(==}H=#=====fK>=f>=j7 >= >=f>=< >=f>= >=j7 >=ݤr >=ފd$ >=>=ݤr >=n8D>=< >=ݤr >=>=n8D>=#<= <=]<߼K<=0憐['IE漱<= <=XV==]<߼K<= <=IE漱<=gku==w'==nZ==gku==nZ== DW[w==uB==6==!==ý==)_˪==½==½==̽|==ý==)_˪==ý==6e==><}h<=i<#<=<<==ya>=V=Eg>=&= yd>=&= yd>=V=Eg>=I&=k>=]9==D S==vU*==vU*==D S==Ak1==K+=]#==0=8=={E=0g.==K<=f==K+=]#=={E=0g.==O&5>=h :<8>=0>=2x)>==oER*>=0>===xU==͎i==q=>f===+===É==U= {===É===+==a \=v}>=b=-v>=M=7x>=r=wz>=b=-v>=a \=v}>=HB'FT=b>=5o=e>=Cg=Ԇ_>=--'Cg=Ԇ_>=eN=t\>=FT=b>=eN=t\>=Cg=Ԇ_>=c=nZ>=Y< 5t==_c=Z=| >=H= >=lc=( >=H= >==>=R=>>=v=n===5>=R=>>=Z=| >=lc=( >=OT|=iN=j=Y=J >=g=Ki>== Q>==L>=Y=J >== Q>=.=>==L>== Q>=R=>>=lc=( >=T=>=>===v=n==T=>=T=>=lc=( >=>===T=>=v=n==R=>>=՚'X'&= yd>=vz<=<`>=_&=_>=ܦ a'_&=_>==ya>=&= yd>= ==!==]9==uB==!== ==F=== ======t0j%+X >=̽=R~>=+X >=j7 >=>=/>=̽=+X >=R~>=j7 >=+X >=5R>=w9>=YUi>=YUi>=] -W>=5R>=&&@I>=w9>=5R>=u; >=#$=;#>===h==nR===~R===Mcrd'e=S==S=Uڻ==~R===~R===nR===e=S==jAc'~R===S=Uڻ====h== ;O#;>=O;7>=ӜJ985>={=FO>=0=\=wU>={=FO>==7*K>=9n=9n=0={=FO>=Jo;=x <=!<=!<=x <=埽lZ<=u8MC>=II>=?3H>=.CN>=&4G>=?3H>=?3H>=II>=.CN>=?3H>=&4G>=u8MC>=ڠ=!O<=ÿ=!<=J=o;=K=LZ<=ÿ=!<=ڠ=!O<=4=z==|=ʇ===w== 9=3==|=ʇ==4=z==0===E=!==ƽ:=d===L>=g=&=>=F =/>=pK =9==0 =V==<+===*-==L>= =cJ>=1SW'>=X'->=L'>=L'>=X'->=Bʩ->=rAUA==X"3^sG=="Q==Y,4f==SoV=="Q=="Q==X"3^sG==Y,4f=="Q==SoV==rAUA==E<,>=c=0==w==P=!===m===m==P=!==U= {==nR===Ej;="==!>E===!>E===h ]===nR===芦B'UU=ڥK*>=D%>=D%>=N+>=I,->=Y=.>=d=H1)>=P= 5*>=P= 5*>=W$E=JX->=Y=.>=P= 5*>=d=H1)>=OT=%>=O&5>=0>=`"/>=`"/>=0>==oER*>=^/'1=۲===[>==u==_&==g {F==)p ==1G'_,bn<=g {F==_&== <==[=?d=W!>=EN=>=OT=%>=?d=W!>=_KE=">=Pq' 1|>=[!J >=~PE~>=~PE~>=w>= 1|>=:&_Nw=c======^==h&݀2'_Nw=c==kl=/@==e=S==e=S=====_Nw=c== 渦3'=^==kl=/@==_Nw=c==@==Z==B==@==R==E'==E'==Z==@==B==R==@==O= s>=qM;=[v>=M=7x>=E˝=qӑ==|=ʇ===f==E˝=qӑ===X==%=:==%=:==|=ʇ==E˝=qӑ===f===X==E˝=qӑ==a'>=Bʩ->=;0*>=;0*>=Bʩ->=:L/>=2{Es== DW[w==9==b%fkA'2{Es==--==:vBF==9==--==2{Es==Ẏ%P2'==ya>=B={\>=FT=b>=vz<=<`>=Ҝ?=sf>=Ҝ?=sf>=vz<=<`>=&= yd>=PhUv>=+5v>=eE1z>=A}>=}>=eE1z>=eE1z>=+5v>=A}>=eE1z>=}>=PhUv>=BO==w'==gku==e(J=n@>=.:=J >=S=J >=J=n@>=a \=v}>=λ>=|>=SvQ'λ>=|>=.:=J >=J=n@>=|''S=J >=a \=v}>=J=n@>=OZ>=8ȃT>=+Ľ;Z>=.ڧ+Ľ;Z>=8ȃT>=̽cX>=;]mu>=s =# g;fMp>=SD:k>=z&q>=# g;fMp>=# g;fMp>=s =SD:k>=# g;fMp>=z&q>=;]mu>=D S==!==b==)_˪==6e==b==b==!==)_˪==G]Q==nZ==EM==G]Q==C2k;==9==EM==C2k;==G]Q==uB==re==!==@;>=R]8>=P@*6>==Uv===M==m=.===Uv==@=>=========M===Uv==m=.==@=>===Uv==w==;====RE)==^ vS======;==RE)====^ vS==w=='===(= ==Qz=]==H0<$== ;==@:J==@:J==;\,6==H0<$==N3`>=a@g>=b>=wˏky'C:d>=\>=b>=b>=a@g>=C:d>=2&'b>=\>=N3`>==9H0=:>==>=4!=j>=YU=8>==>==>=9H0=:>=4!=j>= 8D7'=>=YU=8>==l,!>=Bʩ->=X'->=NJ)2>=7====Q==`=׃======Q==7===ttoO>=0K>=擼3hK>=ttoO>=9S>=ZdF`S>=ZdF`S>=0K>=ttoO>=擼3hK>=9S>=ttoO>=66='J~:Б== ;==%;<=]9==vU*==k7i==k7i==xU==]9==k7i==vU*==Xe>=a'>=L'>=Bʩ->=:L/>=Bʩ->=NJ)2>=ӜJ985>=:>= ;O#;>==">==)>=T=&>=G!h&B={\>==ya>=_&=_>=''&_&=_>=vz<=<`>=3=[>= ;==J~:Б==@:J==k7i==Xe>=唽4>=xU==k7i==唽4>=P=!==%=:==b===P=!==b====É==@;>=P@*6>=ռ7;>=m@>=@;>=ռ7;>= DW[w==nZ==G]Q== DW[w==G]Q==9==<+==0 =V===c=d<_5>=#<79>=0=d<_5>=ƽ:=d==E=!== P=m==P= 5*>=OT=%>=]JB=ӧ'>=W$E=JX->=P= 5*>=]JB=ӧ'>= +Ľ;Z>=̽cX>=̽^>===)>=l|=)>==d==4=z===w==5R>=] -W>=̺$>=&&@I>=5R>=̺$>=F =/>=g=&=>=Z=| >=R=>>=F =/>=Z=| >==5>=F =/>=R=>>=(= ==!>E===Ej;="== P=m==h ]===!>E===#<*==E<ș===O= s>=\=n>=2=kr>=qM;=[v>=O= s>=2=kr>=O= s>= E=fm>=NJ)2>=X'->=QE3>=%ƻQ7>=NJ)2>=QE3>=>͹'>=(@;%>=0༡i,>=(@;%>=>͹'>=ɽμʈ!>='===Qz=]==V===V===0==='===ƽ:=d=='===0===\=wU>===fW>=B={\>==fW>== ==]9==xU==xU==== == ====uB==Ak1==D S==6e==6e==Uý,>==Ak1==6e==D S==b===w===m==~B=\===m==U= {===ia===ia==&=S===m===m==&=S==~B=\==%o]>=Fk3X>=.Y>=.Y>=Fk3X>=wCT>=&=o;==n*<=ڠ=!O<=ڠ=!O<=J=o;=&=o;=ڠ=!O<==n*<=K=LZ<=:>=Hʻ=>=3ú?>=3ú?>= ;O#;>=:>=%ƻQ7>=Hʻ=>=:>=;#>=#$=.<>=I3;w/==;#>=.<>=!<=G/X'<=&o;=&o;=Jo;=!<=埽lZ<=G/X'<=!<==qZ<S>=_=OT|=_=iN=ZG=;B>=ydX= =>=;=_;>=;=_;>=ydX= =>=rP= 8>=F4>= 6>=;=_;>=rP= 8>====7===F===Gsզr&YH==pql w==z/tp==B8$' DW[w==YH==z/tp==gku== DW[w==z/tp==z/tp==BO==gku==z/tp==¥V==BO==:L/>=NJ)2>=ӜJ985>=:>=ӜJ985>=%ƻQ7>=%ƻQ7>=ӜJ985>=NJ)2>=`"/>=<:2>=O&5>=`"/>==oER*>=B4B+>=B4B+>=<:2>=`"/>===2 >=ߖ)P==Q=[==f=k===i==z====i==o===Q=[===i==z===T====I===>==>==I==#= ŭ=k=`=׃==v=n==7===7===v=n==>===>===F===7===Oa,Sy>=VYq>=)ut>=LÒ == n==o%Լr== n==X`==٨== n==٨==o%Լr==S=0=_=S=_==S==T=2= >=h=C>=6ͭ=>= =>=ѡ=)>=2= >= =>=2= >=#=>=6ͭ=>=#=>=2= >=E<,>=0=<ύ2>=0=~0=<ύ2>=~0=0=#<79>==Qn== 9=3==4=z===d===Qn==4=z==VS&ý==̽==6e==cL 6e==̽==Uý,>==ʻ<'`M== e=%u=>==|>=2'%u=>=&=J >=J=J >=F =/>=‚=>=Y=J >==L>=F =/>=Y=J >==5>=‚=>=F =/>=n$>=2x)>=(@;%>=w&5&ɽμʈ!>=9$>=(@;%>=I!%'(@;%>=9$>=n$>==Q5==q=O===6?== => F==q=O===Q5==&e<'=">=ܚ=߆!>=5ё=>= =;==0 =V==pK =9==l=О== =;==pK =9==xU==唽4>=͎i==͎i==唽4>=I==>=I==唽4>=唽4>=n8D>=>=>=唽4>=>=fէ&½_>=+Ľ;Z>=̽^>=12'v&½_>=#a>=OZ>=+Ľ;Z>=½_>=OZ>=E>=TJ>="ծ]M>=E>="ծ]M>=,NsI>=E>=,NsI>=2/,B>=]=^>=.=>=mw=>=6z=>=]=^>=mw=>=mw=>=.=>== Q>=[&='UU===vB&'UU===pK =9==<+==UU=>=̽>= Vpå̽>=>=R~>=D~\=Ջ<=^j=_<=V=&M6<=C=dk<=D~\=Ջ<=V=&M6<=D~\=Ջ<=C=dk<=LG=Zq<==V}>=y>=*===V}>=*==c==*==Â==c==^==w'==$vK==\==^==$vK==\==$vK==–t" ==–t" ==$vK==cZD==Y=P<=ⵉ=<=׋==;<=(=<=Y=P<=׋==;<=>=P<=Y=P<=(=<==)>=b=##>=l|=)>==">=b=##>==)>=48<F>==OT|=48<F>=lC`=z;;CnB>=j=lC`=48<F>=OT|=j=48<F>=b<=@Rp<=5r a<=5r a<=@Rp<=`o<=}͟<=b<=5r a<=k=ý==ý==Ak1==Uý,>==Ak1==ý==E|>=Lg޺$Uý,>==̽u==ý==߶\==hZ==}Gr==߶\==}Gr==x==x==_`==߶\==߶\==_`==peE===,>=k=H+>=d=&>=d=&>=k=H+>=D=h&>=V=Eg>==?Cn>=I&=k>==r>=I&=k>==?Cn>=I&=k>==r>=2=kr>=?J=N>=#= P>=De=zS>=?J=N>=s=DI>=#= P>=De=zS>=Q="T>=?J=N>=&n4<=Z<=4t=<'>=J<>=00<ܽ==J<>=<'>=#={9'.=(>==n,>==o&>==+>=}=~/>=.=(>=8'4#}=~/>==n,>=.=(>=peE==)ىB,==x@==噔$==x@==)ىB,==}}D3==)ىB,==peE==Y~6>=um[>=\h'h>=\h'h>=um[>=\~>=YR .>=\h'h>=\~>= R&'_KE=">=9H0=:>=B.=))$>=_KE=">=B.=))$>=]JB=ӧ'>=%f0'EN=>=9H0=:>=_KE=">=_KE=">=]JB=ӧ'>=OT=%>=!=F>=]=^>=5ё=>=J<==<==E<ș==J<==F=!==N<%==b<=====Y< 5t==U=`>==%F`>=ḃ=e>=U=`>=ḃ=e>==~e>=U=`>==~e>=r=(`>=I@>=E>=` >>=;^\G>=E>=I@>=;^\G>=TJ>=E>=,=;===k==h&m=R'==[= /==Ei= @==h&m=R'==h&m=R'==Ei= @===>;==*I⼒==6伇==?==*I⼒==?==pN@== ==pN@==?=={=FO>=\=wU>=="P>==fW>=="P>=\=wU>= =cJ>==7*K>=="P>==fW>=&*=YU>=="P>==7*K>={=FO>=="P>=3>=>>=FL >=3>=FL >=u; >=;#>=3>=u; >=YdM:==3>=I3;w/==I3;w/==3>=;#>=2x)>=0>=0༡i,>=0༡i,>=(@;%>=2x)>=0>=ټ@ 2>=0༡i,>=g=T:>= ,=D A>=;=_;>=;=_;>=,= 5>=g=T:>=F4>= 6>=,= 5>=;=_;>=.G===@=>==m=.==m=.=====.G===.G======J=F==O= s>=i=mq>=\=n>=i=mq>=O= s>=b=-v>=b=-v>=O= s>=M=7x>=f>=Ᵹ B>=< >=< >=Ᵹ B>=ݤr >=Ᵹ B>=1w>=ݤr >=]=q==f=R==I?G===I?G===f=R==P=u==ш;=ar==I?G===P=u==]=q==I?G=== aH====j==U= {===+===j===ia==U= {===j==q=O===ia==<==$==)>=R= ->=JȌ=10>==)>==l/>==)>=JȌ=10>=R= ->=TT=IV>=B=[Q>=P@=SW>=+]=QQ>=B=[Q>=TT=IV>=B=[Q>=X=L>=PC=#aK>=X=L>=B=[Q>=+]=QQ>=AJv==adk==b_==b_==~K ==AJv==%9en==~K ==b_==p3'O=k>==h>==jm>==l>==q>==jm>==jm>==h>==l>=[ʻBD>=Hʻ=>=62S@>=62S@>=D`TE>=[ʻBD>=\>=fѼ:}>=Q{iz>=>DDҫ&̽7>=>=KgŽV>=E|>=ý==KgŽV>=KgŽV>=>=E|>==t/>==,>=<ύ2>=<ύ2>==4>==t/>=k=H+>==,>==t/>==_>=]=^>=6z=>=5ё=>=]=^>==_>=e,= Q>=&*=YU>=B=[Q>=e,= Q>=*-==L>=="P>=="P>=&*=YU>=e,= Q>=B=[Q>=*-==L>=e,= Q>==N=]m=]m=c===I==F=!==<==<==F=!==J<==<==<===I==c>:>=Hʻ=>=%ƻQ7>=.=+>=W$E=JX->=]JB=ӧ'>=]JB=ӧ'>=B.=))$>=.=+>=3o"=U0>==4>=,= 5>=,= 5>=7=i_1>=3o"=U0>=;S<=GcC=N+>=n/>=#<=+优Œ<= %r<= %r<=+优Œ<=޼bU<=(n?>=@;>=m@>=@'֞'&=J >==|>=b=>=b=>==|>=|=Oz>=a'b=>=I=J >=&=J >=ḃ=e>=m,s=k>==gk>=2==+ h==]!»==v>=:ża>= ˼->=9$>=:ża>=v>=d=H1)>=l|=)>=r=\,&>=b=##>=?d=W!>=r=\,&>=r=\,&>=l|=)>=b=##>=r=\,&>=?d=W!>=d=H1)>=-80<=I?j7<=t4BF<=vr-m<=ٻ<=t4BF<=t4BF<=I?j7<=vr-m<=t4BF<=ٻ<=-80<=K ==JW==vO==J>=:P>=juƼI>=J>=[ʻBD>=0K>=0K>=:P>=J>=juƼI>=[ʻBD>=J>=䂦>'k=j>=ܚ=߆!>=h=C>=N&7.'5ё=>=ܚ=߆!>=k=j>=c=== ===ݮ===F===>===ݮ===ݮ=== ===F===ݮ===>===c===F^<z>=2w;*y}>=?<~>=?;>=D1>=.u>=U1==Q>`==%==%==Q>`==$k==%== ==U1==vU*==Ak1==т>=т>=Ak1==E|>=т>=뫽r>=vU*==<;>=4 >==$B>= n==<&>=<&>=r =>=< z>=% '<&>==k=&VFl>=SD:k>=a@g>=&VFl>=jm>=z&q>=z&q>=SD:k>=&VFl>=a@g>=jm>=&VFl>=Q{iz>=fѼ:}>=޼Qv>=޼Qv>=fѼ:}>=鍯x>=7X<2>=G<78>=d<_5>==a==D=======a===i==f=k==f=k==D====a======i===a==~o'8=>=I=J >=b=>=8=>=b=>=|=Oz>=;v=o>=*X=q>==gk>=;v=o>==gk>=m,s=k>=62S@>=Hʻ=>=c>:>=%==$k==H)H+== ==%==H)H+==NX >=뫽r>=т>=NX >=т>=E|>=E<ș==<==<==E<ș==<==J<== (<&>=k==J >=k&b'r =>=<&>==J >=]m=N==.=+>=B.=))$>=D=h&>=k=H+>=.=+>=D=h&>=W$E=JX->=.=+>=7=i_1>==t/>==4>=3o"=U0>==t/>=3o"=U0>=k=H+>=.u>=D1>=4 >=@;>=(n?>=R]8>=3o"=U0>=7=i_1>=.=+>=3o"=U0>=.=+>=k=H+>= ʮ<=k߼o;=o;=7X<2>=d<_5>=c=c=]m=7X<2>='$'=k>==jm>==B]q>=q{'J=q>=c=u>==B]q>=2'A+'=B]q>==jm>==q>=;w3>=:L/>=ӜJ985>=ӜJ985>=O;7>=;w3>=(n?>=m@>=E>=E>=D`TE>=(n?>=(n?>=D`TE>=62S@>=Q==LGR==/[==/[==|==Q==q̽7>=KgŽV>=̽[>=rw.̽[>=KgŽV>=ý==K3<=>xo;= ŭo;=ɼ<=K3<= ŭo;=Ph<=I?j7<=K3<=Ph<=K3<=ɼ<= F;o;=q:A<=;S<= =̽k>=ֿ`g>=2= >=!=F>=k=j>=k=j>=h=C>=2= >=k=j>=!=F>=5ё=>=-2>=v7>=H7>=-2>=e1>=v7>=/ <>=RL =e=e=@[=/ <>=/$ <">=/ <>=@[=Q=Go>==gk>=*X=q>==~e>=ḃ=e>==gk>==gk>=ޘ=`Cj>==~e>=Q=Go>=ޘ=`Cj>==gk>=ۯ'[!J >=J >=~PE~>=z#'\>=~PE~>=J >=Q{iz>=w>=~PE~>=Q{iz>=~PE~>=\>=ʝc=={ʌ_==l ==l ==`7==ʝc==o'==`7==l ==ܿ'k߼J >=\>=J >= &Z'fѼ:}>=\>=k߼J >=P/%Y/b'b=##>==">==_>=Ak''=_>=6z=>=b=##>=<"&ٖD'=_>==">=5ё=>=H7>=G=>=~||8>=~||8>=.B1>=H7>=H7>=.B1>=-2>=<;>==$B>=`dz >=eXI:b==2==]!»==]!»==YdM:==eXI:b==*==YdM:==]!»==Y< 5t==x<X==8=l|=)>=Mo="/>=Mo="/>=ʉx=5>==l/>=p<==t(<==;.2==c>:>=%ƻQ7>=QE3>=QE3>=R]8>=c>:>=Ҝ?=sf>=AhY=+h>=FT=b>= E=fm>=AhY=+h>=Ҝ?=sf>=ڥK*>=I,->=.B1>=.B1>=I,->=-2>=I,->=N+>=e1>=I,->=e1>=-2>=5Ḙx<=t9و<=&a;`l<=ٻ<=t9و<=5Ḙx<=܌<=5Ḙx<=q:A<=5Ḙx<=܌<=ٻ<=Z<=#<= %r<= %r<=N$<=Z<=޼bU<= ʮ<= %r<= ʮ<=N$<= %r<=^/>===$k==0f.==6==!==!==6==uB===;0*>=:L/>=;w3>==:L/>=aw;F'>=7;#>=;0*>=a'>=;0*>=7;#>=;0*>==aw;F'>=&*=YU>=B={\>=3=[>=B={\>=&*=YU>==fW>=B={\>=_&=_>=3=[>=>xE===ƽ:=d==ƽ:=d==(= =='===(= ==ƽ:=d==!>E=== ==rV==U1== ==?==rV==t=lC`=G<78>=t=G<78>=O;7>=G<78>=;w3>=O;7>=vO==@:J==K ==K ==@:J==J~:Б==&= yd>=I&=k>=Ҝ?=sf>=Ҝ?=sf>=I&=k>= E=fm>= ===1=۲==J=F==7'%1=۲===u==J=F==c===1=۲== ===ɼ<= ŭo;=k߼o;=޼bU<=Ph<=ɼ<= ʮ<=޼bU<=ɼ<=k߼o;= ʮ<=ɼ<=!==re==w'==!==w'==BO==ȴ<*==<+===L'>=Vw!>=L'>=3's >=1SW'>=a'>=Vw!>=L'>=[=3ep>=)ut>=ns%n>=VYq>=>9^%j>=VYq>=ns%n>=)ut>=?<~>=M(r<Ѡz>=F^<z>=ޏ=M(r<Ѡz>=?<~>=A'ޏ=?<~>=>x=R'?<~>= =>x=guҧ+b'̽0E>=;^\G>=Ž-C>=Ž-C>=;^\G>=I@>=]p:>=͗l>>=G=>=]p:>=H7>=v7>=v7>=͗l>>=]p:>=G=>=H7>=]p:>=|54>=""^:>=ݝ4>=ݝ4>=""^:>=v7>=ݝ4>=n/>=|54>="<==RW;==U)<\==E R>=`dz >=2 >=E R>===^/>=2 >===E R>=\%c=0s<ő>=gm-'޸<6o==$<==.< ==ȴ<*==$<==޸<6o==I3;w/==.<>=L9;'==Hx=\%c=n/>=ݝ4>=e1>=ݝ4>=v7>=<;>=`dz >=E R>=<;>=E R>=^/>=Z{$'̽i?>=̽0E>=Ž-C>=$k==?;>=.u>=$k==.u>=^/>=Hx=4 >=<;>=<;>=^/>=.u>=\%c=P<T==P<T==#<*==\%c=2P9>=̽i?>=Ѧ-I@>=̽i?>=Ž-C>=&(̽O+9>=̽i?>=2P9>=' =2w;*y}>= F;J >= 2ij'?<~>=2w;*y}>= =L9;'==RW;==eXI:b==eXI:b==I3;w/==L9;'==U)<\==RW;==L9;'===c>:>=R]8>=c>:>=(n?>=62S@>=d;>=/$ <">=u; >=d;>=u; >=FL >=/ <>=/$ <">=d;>=FL >=&&@I>=d;>=N<%==$<Q==6<0==J<==N<%==6<0==6<0==$<Q== n=ѝ=zp>=޼Qv>=E¼r>=޼Qv>=鍯x>=޼Qv>=ѝ=zp>=sjs>=Q{iz>=޼Qv>=sjs>=7X<2>=]m===;w3>=7X<2>=G<78>=7X<2>=;w3>=&&^'%;<=S'<=J~:Б==nS&DD'S'<=K ==J~:Б==9u'`<=JW==S'<=m]H'S'<=JW==K == ˼->=v뼜>=v>=[>=v>=v뼜>=[>=>=v>=9$>=v>=>==c====>=1=۲==c===g=>=g)'"%g=>==[>=1=۲=={K=S >=g=>==>=)P=2>=R= ->=!=ۧ3>=R= ->=JȌ=10>=!=ۧ3>===9>=ݸ=4>=ݸ=4>==l/>=ʉx=5>=^=o8>=)P=2>=!=ۧ3>=q=>8>=)P=2>=^=o8>=&<No>=ֿ`g>=̽k>=GcC=#$=Y< >=/$ <">=@[=Y< >=Y< >=#$=/$ <">= :?>=RL =t;G>=t;G>=RL =/ <>=_7B=={ʌ_==+ h==_7B==Q==l ==l =={ʌ_==_7B==+ h==Â==,y"==*==]!»==,y"==,y"==Â==*==,y"==]!»==+ h==1==w==^ vS==1==vO==JW==JW==w==1==,==}<>=Y< >=G$=Y< >=@[=_7B==+ h====Q==_7B====ݸ=4>==9>==09>=!=ۧ3>=ݸ=4>==09>=JȌ=10>==l/>=ݸ=4>=JȌ=10>=ݸ=4>=!=ۧ3>==09>=^=o8>=!=ۧ3>=t;G>=&&@I>= :?>=t;G>=/ <>=d;>=d;>=&&@I>=t;G>=U)<\==L9;'==\%c=̽B]q>=½t>=\$'No>=̽k>=̽B]q>=;\,6==@:J== JS[/== JS[/==@:J==vO== O&3'7;#>=!<Y">=z.;{>=RL = :?>=z.;{>= 'I&z.;{>=!<Y">=RL =lަځ''z.;{>= :?>=7;#>=om>==[s>=Ön>=>m>=²tg>=ֿ`g>=>m>=ֿ`g>=No>=IO_=2=En?>=^=o8>=2=En?>==S5>>=^=o8>=^=o8>==S5>>=q=>8>=ڬj>=Ön>=`g>=ڬj>=²tg>=>m>=>m>=Ön>=ڬj>=`g>=²tg>=ڬj>=GZ==a*Y==8^\I==8^\I==?kD==GZ==8^\I==a*Y==[?==w>==[s>=፹r>=፹r>==[s>=>m>=፹r>=½t>=w>==S5>>=>=C>=+=Ty>>=+=Ty>>=>=C>=|x=C>=[?== JS[/=="}(=="}(== JS[/==vO==;.2==ȍ>=+=Ty>>==!:>=No>=½t>=፹r>=No>=፹r>=>m>=|x=C>="=b=>=+=Ty>>=+=Ty>>="=b=>=='9>==!:>=+=Ty>>=='9>=#'{K=S >====7>=f=!O >=Y =U4>={K=S >=ˌ$'y#g=>={K=S >==7>=o;=[!o;=N$<=o;=N$<= ʮ<=O."<=N$<=[!o;=O."<=DB<=2Q<=2Q<=N$<=O."<=H'@`&+=>=K=>==>=Y =U4>=K=>=+=>==>=H= >=f=!O >={K=S >==>=f=!O >=.:o;=O."<=[!o;=DB<=O."<=.:o;=uPm'=>===+=>={K=S >=Y =U4>=+=>=B9'Ɓ+=>==={K=S >=d̽1 >=̽o&>=)Ľ>=tܦ[u')Ľ>=̽o&>=%>=d̽>=̽1 >=)Ľ>=S>=>=)Ľ>=Ua%$')Ľ>=%>=S>=:̽>=)Ľ>=>=trunk-2018.02b/examples/rotationalResistance.py000066400000000000000000000026711324306050200215610ustar00rootroot00000000000000#!/usr/bin/env python # encoding: utf-8 from yade import utils, plot o = Omega() fr = 0.5;rho=2000 tc = 0.001; en = 0.7; et = 0.7; o.dt = 0.0002*tc r = 0.002 mat1 = O.materials.append(ViscElMat(frictionAngle=fr,mR = 0.05, mRtype = 1, density=rho,tc=tc,en=en,et=et)) mat2 = O.materials.append(ViscElMat(frictionAngle=fr,mR = 0.05, mRtype = 2, density=rho,tc=tc,en=en,et=et)) oriBody = Quaternion(Vector3(1,0,0),(pi/28)) id1 = O.bodies.append(sphere(center=[0,0,2*r],radius=r,material=mat1)) id2 = O.bodies.append(geom.facetBox(center=(0,-16.0*r,-2*r),orientation=oriBody,extents=(r,17.0*r,0), material=mat1,color=(0,0,1))) id3 = O.bodies.append(sphere(center=[10*r,0,2*r],radius=r,material=mat2)) id4 = O.bodies.append(geom.facetBox(center=(10*r,-16.0*r,-2*r),orientation=oriBody,extents=(r,17.0*r,0), material=mat2,color=(0,0,1))) o.engines = [ ForceResetter(), InsertionSortCollider([Bo1_Sphere_Aabb(),Bo1_Facet_Aabb()]), InteractionLoop( [Ig2_Sphere_Sphere_ScGeom(),Ig2_Facet_Sphere_ScGeom()], [Ip2_ViscElMat_ViscElMat_ViscElPhys()], [Law2_ScGeom_ViscElPhys_Basic()], ), NewtonIntegrator(damping=0,gravity=[0,0,-9.81]), PyRunner(command='addPlotData()',iterPeriod=10000, dead = False, label='graph'), ] def addPlotData(): f1 = [0,0,0] s1 = O.bodies[id1].state.pos[1] s2 = O.bodies[id3].state.pos[1] plot.addData(sc=O.time, fc1=s1, fc2=s2) plot.plots={'sc':('fc1','fc2')}; plot.plot() from yade import qt qt.View() trunk-2018.02b/examples/simple-scene/000077500000000000000000000000001324306050200173705ustar00rootroot00000000000000trunk-2018.02b/examples/simple-scene/2SpheresNormVisc.py000066400000000000000000000027511324306050200231230ustar00rootroot00000000000000#!/usr/bin/env python # encoding: utf-8 # # This script plots the viscous and normal forces of the contact # of 2 spheres from yade import utils, plot, qt o = Omega() # Physical parameters fr = 0.5;rho=2000 tc = 0.0001; en = 0.7; et = 0.7; o.dt = 0.000002*tc Rad = 2.0e-3 # Add material mat1 = O.materials.append(ViscElMat(frictionAngle=fr,tc=tc,en=en,et=et)) # Add spheres id1 = O.bodies.append(sphere(center=[0,0,0],radius=Rad,material=mat1,fixed=True)) id2 = O.bodies.append(sphere(center=[0,0,(2.0*Rad)],radius=Rad,material=mat1,fixed=False)) O.bodies[id2].state.vel[2] = -1.0 # Add engines o.engines = [ ForceResetter(), InsertionSortCollider([Bo1_Sphere_Aabb()]), InteractionLoop( [Ig2_Sphere_Sphere_ScGeom()], [Ip2_ViscElMat_ViscElMat_ViscElPhys()], [Law2_ScGeom_ViscElPhys_Basic()], ), NewtonIntegrator(damping=0,gravity=[0,0,-9.81]), PyRunner(command='addPlotData()',iterPeriod=100), ] # Function to add data to plot def addPlotData(): try: delta = (O.bodies[id2].state.pos[2]-O.bodies[id1].state.pos[2])-(2*Rad) plot.addData(delta=delta, time1=O.time, time2=O.time, time3=O.time, time4=O.time, Fn = O.interactions[0,1].phys.Fn, Fv = O.interactions[0,1].phys.Fv, deltaDot = O.bodies[id2].state.vel[2] - O.bodies[id1].state.vel[2]) except: pass plot.plots={'time1':('delta'), 'time2':('deltaDot'), 'time3':('Fn'), 'time4':('Fv')}; plot.plot() O.run(1) qt.View() #O.wait() ; plot.saveGnuplot('sim-data_Sphere') trunk-2018.02b/examples/simple-scene/simple-scene-default-engines.py000066400000000000000000000007641324306050200254050ustar00rootroot00000000000000#!/usr/bin/python # -*- coding: utf-8 -*- ## Demonstrate scene construction using the default list of engines ## Note that it is no longer necessary to define O.dt ## See simple-scene.py for the complete version with explicit engines definition typedEngine("NewtonIntegrator").gravity=(0,0,-9.81) typedEngine("NewtonIntegrator").damping=0.1 O.bodies.append(box(center=[0,0,0],extents=[.5,.5,.5],color=[0,0,1],fixed=True)) O.bodies.append(sphere([0,0,2],1,color=[0,1,0])) O.save('/tmp/a.xml.bz2'); trunk-2018.02b/examples/simple-scene/simple-scene-energy-tracking.py000066400000000000000000000112571324306050200254230ustar00rootroot00000000000000#!/usr/bin/python # -*- coding: utf-8 -*- ############################################ ##### interesting parameters ##### ############################################ # Cundall non-viscous damping # try zero damping and watch total energy... damping = 0.2 # initial angular velocity angVel = 3.0 # use two spheres? two_spheres =True # sphere rotating more? rotate_in_two_directions = True ############################################ ##### material ##### ############################################ import matplotlib matplotlib.use('TkAgg') O.materials.append(CohFrictMat( young=3e8, poisson=0.3, frictionAngle=radians(30), density=2600, isCohesive=False, alphaKr=0.031, alphaKtw=0.031, momentRotationLaw=False, etaRoll=5.0, label='granular_material')) ############################################ ##### calculation loop ##### ############################################ law=Law2_ScGeom6D_CohFrictPhys_CohesionMoment(always_use_moment_law=False) g=9.81 O.trackEnergy=True O.engines=[ ForceResetter(), InsertionSortCollider([Bo1_Sphere_Aabb(),Bo1_Box_Aabb()]), InteractionLoop( [Ig2_Sphere_Sphere_ScGeom6D(),Ig2_Box_Sphere_ScGeom6D()], [Ip2_CohFrictMat_CohFrictMat_CohFrictPhys()], [law] ), GlobalStiffnessTimeStepper(active=1,timeStepUpdateInterval=50,timestepSafetyCoefficient=.0001), NewtonIntegrator(damping=damping,kinSplit=True,gravity=(0,0,-g)), PyRunner(iterPeriod=20,command='myAddPlotData()') ] O.bodies.append(box(center=[0,0,0],extents=[.5,.5,.5],fixed=True,color=[1,0,0],material='granular_material')) O.bodies.append(sphere([0,0,2],1,color=[0,1,0],material='granular_material')) if(two_spheres): O.bodies.append(sphere([0,0,4],1,color=[0,1,0],material='granular_material')) O.dt=.002*PWaveTimeStep() O.bodies[1].state.angVel[1]=angVel if(rotate_in_two_directions): O.bodies[1].state.angVel[2]=angVel ############################################ ##### now the part pertaining to plots ##### ############################################ from yade import plot ## we will have 2 plots: ## 1. t as function of i (joke test function) ## 2. i as function of t on left y-axis ('|||' makes the separation) and z_sph, v_sph (as green circles connected with line) and z_sph_half again as function of t plot.labels={'t':'time [s]', 'normal_Work':'Normal work: W=kx^2/2', 'shear_Work':'Shear work: W=kx^2/2', 'E_kin_translation':'Translation energy: E_kin=m*V^2/2', 'E_kin_rotation':'Rotation energy: E_kin=I*$\omega$^2/2', 'E_pot':'Gravitational potential: E_pot=m*g*h', 'E_plastic':'Plastic dissipation on shearing: E_pl=F*$\Delta$F/k', 'total':'total', 'total_plus_damp':'total + damping'} plot.plots={'t':( ('normal_Work','b-'), ('shear_Work','r-'), ('E_kin_translation','b-.'), ('E_kin_rotation','r-.'), ('E_plastic','c-'), ('E_pot','y-'), ('total','k:'), ('total_plus_damp','k-') )} ## this function is called by plotDataCollector ## it should add data with the labels that we will plot ## if a datum is not specified (but exists), it will be NaN and will not be plotted def myAddPlotData(): normal_Work = law.normElastEnergy() shear_Work = law.shearElastEnergy() E_kin_translation = 0 E_kin_rotation = 0 E_pot = 0 E_plastic = 0 E_tracker = dict(O.energy.items()) if(two_spheres):## for more bodies we better use the energy tracker, because it's tracking all bodies E_kin_translation = E_tracker['kinTrans'] E_kin_rotation = E_tracker['kinRot'] E_pot = E_tracker['gravWork'] else: ## for one sphere we can just calculate, and it will be correct sph=O.bodies[1] h=sph.state.pos[2] V=sph.state.vel.norm() w=sph.state.angVel.norm() m=sph.state.mass I=sph.state.inertia[0] E_kin_translation = m*V**2.0/2.0 E_kin_rotation = I*w**2.0/2.0 E_pot = m*g*h if('plastDissip' in E_tracker): E_plastic = E_tracker['plastDissip'] total = normal_Work + shear_Work + E_plastic + E_kin_translation + E_kin_rotation + E_pot total_plus_damp = 0 if(damping!=0): total_plus_damp = total + E_tracker['nonviscDamp'] else: total_plus_damp = total plot.addData( t=O.time, normal_Work = normal_Work , shear_Work = shear_Work , E_kin_translation = E_kin_translation, E_kin_rotation = E_kin_rotation , E_pot = E_pot , E_plastic = E_plastic , total = total , total_plus_damp = total_plus_damp , ) print "Now calling plot.plot() to show the figures. The timestep is artificially low so that you can watch graphs being updated live." plot.liveInterval=2 plot.plot(subPlots=False) #from yade import qt #qt.View() O.run(int(20./O.dt)); #plot.saveGnuplot('/tmp/a') ## you can also access the data in plot.data['t'], etc, under the labels they were saved. trunk-2018.02b/examples/simple-scene/simple-scene-plot.py000066400000000000000000000037521324306050200233110ustar00rootroot00000000000000#!/usr/bin/python # -*- coding: utf-8 -*- import matplotlib matplotlib.use('TkAgg') O.engines=[ ForceResetter(), InsertionSortCollider([Bo1_Sphere_Aabb(),Bo1_Box_Aabb()]), InteractionLoop( [Ig2_Sphere_Sphere_ScGeom(),Ig2_Box_Sphere_ScGeom()], [Ip2_FrictMat_FrictMat_FrictPhys()], [Law2_ScGeom_FrictPhys_CundallStrack()] ), NewtonIntegrator(damping=.2,gravity=(0,0,-9.81)), ### ### NOTE this extra engine: ### ### You want snapshot to be taken every 1 sec (realTimeLim) or every 50 iterations (iterLim), ### whichever comes soones. virtTimeLim attribute is unset, hence virtual time period is not taken into account. PyRunner(iterPeriod=20,command='myAddPlotData()') ] O.bodies.append(box(center=[0,0,0],extents=[.5,.5,.5],fixed=True,color=[1,0,0])) O.bodies.append(sphere([0,0,2],1,color=[0,1,0])) O.dt=.002*PWaveTimeStep() ############################################ ##### now the part pertaining to plots ##### ############################################ from yade import plot ## we will have 2 plots: ## 1. t as function of i (joke test function) ## 2. i as function of t on left y-axis ('|||' makes the separation) and z_sph, v_sph (as green circles connected with line) and z_sph_half again as function of t plot.plots={'i':('t'),'t':('z_sph',None,('v_sph','go-'),'z_sph_half')} ## this function is called by plotDataCollector ## it should add data with the labels that we will plot ## if a datum is not specified (but exists), it will be NaN and will not be plotted def myAddPlotData(): sph=O.bodies[1] ## store some numbers under some labels plot.addData(t=O.time,i=O.iter,z_sph=sph.state.pos[2],z_sph_half=.5*sph.state.pos[2],v_sph=sph.state.vel.norm()) print "Now calling plot.plot() to show the figures. The timestep is artificially low so that you can watch graphs being updated live." plot.liveInterval=.2 plot.plot(subPlots=False) O.run(int(2./O.dt)); #plot.saveGnuplot('/tmp/a') ## you can also access the data in plot.data['i'], plot.data['t'] etc, under the labels they were saved. trunk-2018.02b/examples/simple-scene/simple-scene.py000066400000000000000000000101651324306050200223310ustar00rootroot00000000000000#!/usr/bin/python # -*- coding: utf-8 -*- ## Omega is the super-class that orchestrates the whole program. ## It holds the entire simulation (MetaBody), takes care of loading/saving, ## starting/stopping the simulation, loads plugins and more. o=Omega() # for advaned folks: this creates default MetaBody as well ## Engines are called consecutively at each iteration. Their order matters. ## ## Some of them work by themselves (GlobalEngine, PartialEngine - the difference of these two is unimportant). ## ## MetaEngines act as dispatchers and based on the type of objects they operate on, different EngineUnits are called. o.engines=[ ## Resets forces and momenta the act on bodies ForceResetter(), ## associates bounding volume - in this case, AxisAlignedBoundingBox (Aabb) - to each body. ## Using bounding boxes created, find possible body collisions. ## These possible collisions are inserted in O.interactions container (Scene::interactions in c++). InsertionSortCollider([ Bo1_Sphere_Aabb(), Bo1_Box_Aabb(), ]), InteractionLoop( ## Decide whether the potential collisions are real; if so, create geometry information about each potential collision. ## Here, the decision about which EngineUnit to use depends on types of _both_ bodies. ## Note that there is no EngineUnit for box-box collision. They are not implemented. [Ig2_Sphere_Sphere_ScGeom(),Ig2_Box_Sphere_ScGeom()], ## Create physical information about the interaction. ## This may consist in deriving contact rigidity from elastic moduli of each body, for example. ## The purpose is that the contact may be "solved" without reference to related bodies, ## only with the information contained in contact geometry and physics. [Ip2_FrictMat_FrictMat_FrictPhys()], ## "Solver" of the contact, also called (consitutive) law. ## Based on the information in interaction physics and geometry, it applies corresponding forces on bodies in interaction. [Law2_ScGeom_FrictPhys_CundallStrack()] ), ## Apply gravity: all bodies will have gravity applied on them. ## Note the engine parameter 'gravity', a vector that gives the acceleration. ## Forces acting on bodies are damped to artificially increase energy dissipation in simulation. ## (In this model, the restitution coefficient of interaction is 1, which is not realistic.) ## This MetaEngine acts on all PhysicalActions and selects the right EngineUnit base on type of the PhysicalAction. # # note that following 4 engines (till the end) can be replaced by an optimized monolithic version: NewtonIntegrator(damping=0.1,gravity=[0,0,-9.81]) ] ## The yade.utils module contains some handy functions, like yade.utils.box and yade.utils.sphere. ## After this import, they will be accessible as utils.box and utils.sphere. ### UPDATE: utils is loaded automatically, no longer need to import #from yade import utils ## create bodies in the simulation: one box in the origin and one floating above it. ## ## The box: ## * extents: half-size of the box. [.5,.5,.5] is unit cube ## * center: position of the center of the box ## * dynamic: it is not dynamic, i.e. will not move during simulation, even if forces are applied to it o.bodies.append(box(center=[0,0,0],extents=[.5,.5,.5],color=[0,0,1],fixed=True)) ## The sphere ## ## * First two arguments are radius and center, respectively. They are used as "positional arguments" here: ## python will deduce based on where they are what they mean. ## ## It could also be written without using sphere - see gui/py/utils.py for the code of the sphere function o.bodies.append(sphere([0,0,2],1,color=[0,1,0])) ## Estimate timestep from p-wave speed and multiply it by safety factor of .2 o.dt=.01*PWaveTimeStep() ## Save the scene to file, so that it can be loaded later. Supported extension are: .xml, .xml.gz, .xml.bz2. o.save('/tmp/a.xml.bz2'); #o.run(100000); o.wait(); print o.iter/o.realtime,'iterations/sec' def onBodySelect(id): print "Selected:",id highlightNone() for i in O.interactions.withBody(id): O.bodies[i.id2 if i.id1==id else i.id1].shape.highlight=True print i.id1,i.id2,i.phys,i.geom try: from yade import qt qt.View() qt.Controller() except ImportError: pass O.run(20000) trunk-2018.02b/examples/sph/000077500000000000000000000000001324306050200155765ustar00rootroot00000000000000trunk-2018.02b/examples/sph/README000066400000000000000000000002011324306050200164470ustar00rootroot00000000000000This directory contains some scripits, which are using SPH method. This is highly experimental for the moment. <2014-04-09 Wed> trunk-2018.02b/examples/sph/box.geo000066400000000000000000000012341324306050200170620ustar00rootroot00000000000000acc = 300.0; hW = 2000.0; thickW = 50.0; Point(1) = {0, 300, 0, acc}; Point(2) = {0, -hW, 0, acc}; Point(3) = {1000.0, -hW, 0, acc}; Point(4) = {2000.0, -hW, 0, acc}; Point(5) = {2300, -hW+300, 0, acc}; Point(6) = {2300, -hW, 0, acc}; Point(7) = {3700, -hW, 0, acc}; Point(8) = {4000, -hW+300, 0, acc}; Point(9) = {4000, 300, 0, acc}; Line(1) = {1, 2}; Line(2) = {2, 3}; Line(3) = {3, 4}; Line(4) = {4, 5}; Line(5) = {5, 6}; Line(6) = {6, 7}; Line(7) = {7, 8}; Line(8) = {8, 9}; Line(9) = {9, 1}; Line Loop(10) = {1, 2, 3, 4, 5, 6, 7, 8, 9}; Plane Surface(11) = {10}; Extrude {0, 0, thickW} { Surface{11}; } trunk-2018.02b/examples/sph/box.mesh000066400000000000000000001267421324306050200172600ustar00rootroot00000000000000 MeshVersionFormatted 2 Dimension 3 Vertices 388 0 300 0 1 0 -2000 0 2 1000 -2000 0 3 2000 -2000 0 4 2300 -1700 0 5 2300 -2000 0 6 3700 -2000 0 7 4000 -1700 0 8 4000 300 0 9 0 300 50 10 0 -2000 50 11 1000 -2000 50 15 2000 -2000 50 19 2300 -1700 50 23 2300 -2000 50 27 3700 -2000 50 31 4000 -1700 50 35 4000 300 50 39 0 12.500000000903 0 1 0 -274.99999999796 0 1 0 -562.49999999681 0 1 0 -849.99999999633 0 1 0 -1137.4999999966 0 1 0 -1424.9999999969 0 1 0 -1712.4999999971 0 1 249.99999999967 -2000 0 2 499.99999999976 -2000 0 2 749.99999999991 -2000 0 2 1250 -2000 0 3 1500 -2000 0 3 1750 -2000 0 3 2150.0000000009 -1849.9999999991 0 4 2580 -2000 0 6 2860 -2000 0 6 3140 -2000 0 6 3420 -2000 0 6 3850 -1850 0 7 4000 -1414.2857142857 0 8 4000 -1128.5714285715 0 8 4000 -842.8571428572 0 8 4000 -557.1428571428 0 8 4000 -271.42857142853 0 8 4000 14.285714285733 0 8 3714.2857142857 300 0 9 3428.5714285715 300 0 9 3142.8571428572 300 0 9 2857.1428571429 300 0 9 2571.4285714287 300 0 9 2285.7142857144 300 0 9 2000.0000000001 300 0 9 1714.2857142856 300 0 9 1428.5714285713 300 0 9 1142.8571428571 300 0 9 857.1428571428 300 0 9 571.42857142853 300 0 9 285.71428571427 300 0 9 0 12.500000000903 50 13 0 -274.99999999796 50 13 0 -562.49999999681 50 13 0 -849.99999999633 50 13 0 -1137.4999999966 50 13 0 -1424.9999999969 50 13 0 -1712.4999999971 50 13 249.99999999967 -2000 50 14 499.99999999976 -2000 50 14 749.99999999991 -2000 50 14 1250 -2000 50 15 1500 -2000 50 15 1750 -2000 50 15 2150.0000000009 -1849.9999999991 50 16 2580 -2000 50 18 2860 -2000 50 18 3140 -2000 50 18 3420 -2000 50 18 3850 -1850 50 19 4000 -1414.2857142857 50 20 4000 -1128.5714285715 50 20 4000 -842.8571428572 50 20 4000 -557.1428571428 50 20 4000 -271.42857142853 50 20 4000 14.285714285733 50 20 3714.2857142857 300 50 21 3428.5714285715 300 50 21 3142.8571428572 300 50 21 2857.1428571429 300 50 21 2571.4285714287 300 50 21 2285.7142857144 300 50 21 2000.0000000001 300 50 21 1714.2857142856 300 50 21 1428.5714285713 300 50 21 1142.8571428571 300 50 21 857.1428571428 300 50 21 571.42857142853 300 50 21 285.71428571427 300 50 21 1529.4446589135 -846.19882489058 0 11 2948.8647910372 -797.27139693469 0 11 768.85477021509 -1191.4331778603 0 11 2225.3179510661 -995.96925999185 0 11 882.98506830098 -423.62886416129 0 11 2357.4759469348 -385.72537641386 0 11 3334.1697178436 -1295.4261086936 0 11 1753.5944220141 -1401.251792038 0 11 2785.9051377217 -1352.6096802644 0 11 3340.5444050499 -333.78170779744 0 11 1781.7192577219 -312.00179782218 0 11 1239.0164902535 -1479.6066037279 0 11 3406.8595304718 -826.55892132953 0 11 498.05326042224 -798.11345014194 0 11 2877.3940074457 -158.86269305865 0 11 1312.9094429575 -188.08933892446 0 11 421.26022679498 -1568.3121263798 0 11 479.3644383792 -179.9473566853 0 11 1079.615368893 -851.4864133385 0 11 2094.5747160518 -1373.0348484798 0 11 1914.3929728395 -699.98358083334 0 11 885.37357867263 -1614.8958107276 0 11 2512.5374604002 -723.15889087768 0 11 3033.6727876963 -1618.0590145742 0 11 3535.4401799374 -1637.6777328536 0 11 2120.8436812794 -73.272527652081 0 11 1870.1705689431 -1065.5436320433 0 11 1470.6887651328 -1197.6555917815 0 11 3101.6438544384 -1176.8301328472 0 11 2640.5965260745 -1681.7980989853 0 11 376.50369589741 -1138.3073016349 0 11 934.26001492933 -130.97450694126 0 11 2446.7543465715 -1314.9110827347 0 11 1519.1695021153 -1704.2615789078 0 11 1321.38510647 -490.93211495581 0 11 1856.8766695228 -1713.8800482108 0 11 3629.2731642966 -119.36920619177 0 11 3579.263470053 -1141.5591856709 0 11 2705.0374641419 -1026.2932625922 0 11 3603.2172344318 -594.03118802079 0 11 3110.049443106 -478.9767977651 0 11 2571.4746621187 -46.609234895912 0 11 1135.8000096194 -1171.5142581261 0 11 3187.7055426079 -56.57788934156 0 11 2713.4240465413 -521.10101130376 0 11 1609.7287827223 -27.757030646951 0 11 2218.2808542131 -611.73080288984 0 11 339.77434389417 -501.56273219575 0 11 1628.4095443371 -583.8378231765 0 11 1127.7531780478 -1736.4024554212 0 11 783.45663487986 -911.36971711706 0 11 632.05380854947 -1754.5359385757 0 11 3708.2839439231 -1405.4255247641 0 11 2454.7868603542 -1539.9042746893 0 11 2091.9665388526 -311.17123097013 0 11 2082.1217200227 -1635.0000625566 0 11 3273.4354982145 -1742.8740271813 0 11 3706.0339596851 -848.60652082524 0 11 722.36683469073 42.963518414442 0 11 262.94130347572 -1733.5288300386 0 11 3778.058225824 -1628.058225824 0 11 622.80394092887 -467.13330345548 0 11 1004.3260710173 -1346.6259737243 0 11 3129.5375392764 -925.21562551409 0 11 1920.0539321953 81.716682003892 0 11 264.10502811154 33.186453018978 0 11 3335.9949786522 -564.15567518578 0 11 2971.1394624533 -1381.1740234635 0 11 1351.8207521926 -1026.0372727017 0 11 215.1945587744 -921.44343644 0 11 3777.9039666689 -389.01253121797 0 11 696.60118099605 -1445.3382867397 0 11 728.35605944582 -231.74410256578 0 11 3319.3115215333 -1073.9684694836 0 11 1530.8304268418 -320.52362110518 0 11 1285.7142857142 76.103882369648 0 11 2474.4274814706 -1015.6649119318 0 11 2951.0287882931 71.331937890859 0 11 1520.501381672 -1425.3304293798 0 11 253.6973135114 -1374.8468483467 0 11 1110.952305133 -319.40091038483 0 11 1350.6011578823 -1811.1729631562 0 11 2338.1660764839 65.729833865481 0 11 2858.5107349988 -1760.0160943389 0 11 1627.2275441016 -1039.7316169914 0 11 3665.7543823172 -1786.4216673679 0 11 1923.8707999441 -1275.6821326923 0 11 1085.6123252695 -587.51500034014 0 11 3438.4243515027 68.682495248415 0 11 605.58286398704 -1059.6338500857 0 11 2609.508999673 -311.68836401073 0 11 245.98314002489 -259.25014738251 0 11 2058.4956784593 -885.34925903342 0 11 1764.0325246399 -866.84558719798 0 11 3110.001253574 -276.20095824932 0 11 2265.1599402191 -1488.4548701852 0 11 2468.3827255104 -1797.189068538 0 11 1682.7777746686 -1801.9522820407 0 11 874.99999999995 -1825.0191987092 0 11 2826.2893615091 -1545.2191605369 0 11 2072.1121891632 -1138.5574595677 0 11 798.90642055128 -666.5740256143 0 11 3363.9794589634 -1515.8092628208 0 11 2310.0022045934 -817.77285230863 0 11 956.7723965576 -1038.1425165412 0 11 203.80770423961 -694.60096326135 0 11 1332.7146260126 -731.0012415672 0 11 2736.2963545966 -809.01301810765 0 11 1912.917229461 -1489.1145205076 0 11 2848.8056048724 -1187.6725098473 0 11 1910.311811976 -490.06137012571 0 11 2755.2480385675 99.259138194832 0 11 3137.71838514 -698.10133794125 0 11 3769.8773198013 73.021229696858 0 11 2504.8730852043 -516.86975916373 0 11 1707.6394965756 -1589.2984418475 0 11 1967.7996779093 -1839.7760221533 0 11 2322.1948729117 -158.21196510554 0 11 520.41667523366 -1296.3119318412 0 11 2648.8039626677 -1229.9564880484 0 11 1694.342246968 -1234.1991991544 0 11 2142.5260679531 138.52442710411 0 11 3504.2273541441 -1399.1795629606 0 11 1015.165916226 86.098665248372 0 11 3835.4108901534 -138.50067297114 0 11 476.63184692998 107.35456591656 0 11 3046.5330873028 -1818.6891070206 0 11 3816.8158456071 -1224.5822677841 0 11 2911.6976013938 -987.04932430719 0 11 1288.2093595642 -1287.1762444129 0 11 2633.2953778388 -1455.0724689842 0 11 2909.6711019268 -606.11308583545 0 11 3503.5967245883 -1828.5311691038 0 11 3819.95043295 -657.02581520156 0 11 443.03408776235 -1816.8508568957 0 11 1922.7072374645 -140.38078415628 0 11 2263.0577707571 -1221.0987388152 0 11 165.61538782086 -1558.4628703367 0 11 3820.422655069 -1037.2353091418 0 11 3441.4789343952 -149.50414065605 0 11 1068.5780858665 -1557.7249410525 0 11 3169.8062650757 -1459.8592624457 0 11 124.99999999984 -1856.2499999986 0 11 1144.2671355981 -77.68362293098 0 11 3530.4249014986 -366.64277666758 0 11 2888.3414753778 -392.15715170384 0 11 1352.7439897812 -1626.7393704996 0 11 1529.4446589135 -846.19882489058 50 58 2948.8647910372 -797.27139693469 50 58 768.85477021509 -1191.4331778603 50 58 2225.3179510661 -995.96925999185 50 58 882.98506830098 -423.62886416129 50 58 2357.4759469348 -385.72537641386 50 58 3334.1697178436 -1295.4261086936 50 58 1753.5944220141 -1401.251792038 50 58 2785.9051377217 -1352.6096802644 50 58 3340.5444050499 -333.78170779744 50 58 1781.7192577219 -312.00179782218 50 58 1239.0164902535 -1479.6066037279 50 58 3406.8595304718 -826.55892132953 50 58 498.05326042224 -798.11345014194 50 58 2877.3940074457 -158.86269305865 50 58 1312.9094429575 -188.08933892446 50 58 421.26022679498 -1568.3121263798 50 58 479.3644383792 -179.9473566853 50 58 1079.615368893 -851.4864133385 50 58 2094.5747160518 -1373.0348484798 50 58 1914.3929728395 -699.98358083334 50 58 885.37357867263 -1614.8958107276 50 58 2512.5374604002 -723.15889087768 50 58 3033.6727876963 -1618.0590145742 50 58 3535.4401799374 -1637.6777328536 50 58 2120.8436812794 -73.272527652081 50 58 1870.1705689431 -1065.5436320433 50 58 1470.6887651328 -1197.6555917815 50 58 3101.6438544384 -1176.8301328472 50 58 2640.5965260745 -1681.7980989853 50 58 376.50369589741 -1138.3073016349 50 58 934.26001492933 -130.97450694126 50 58 2446.7543465715 -1314.9110827347 50 58 1519.1695021153 -1704.2615789078 50 58 1321.38510647 -490.93211495581 50 58 1856.8766695228 -1713.8800482108 50 58 3629.2731642966 -119.36920619177 50 58 3579.263470053 -1141.5591856709 50 58 2705.0374641419 -1026.2932625922 50 58 3603.2172344318 -594.03118802079 50 58 3110.049443106 -478.9767977651 50 58 2571.4746621187 -46.609234895912 50 58 1135.8000096194 -1171.5142581261 50 58 3187.7055426079 -56.57788934156 50 58 2713.4240465413 -521.10101130376 50 58 1609.7287827223 -27.757030646951 50 58 2218.2808542131 -611.73080288984 50 58 339.77434389417 -501.56273219575 50 58 1628.4095443371 -583.8378231765 50 58 1127.7531780478 -1736.4024554212 50 58 783.45663487986 -911.36971711706 50 58 632.05380854947 -1754.5359385757 50 58 3708.2839439231 -1405.4255247641 50 58 2454.7868603542 -1539.9042746893 50 58 2091.9665388526 -311.17123097013 50 58 2082.1217200227 -1635.0000625566 50 58 3273.4354982145 -1742.8740271813 50 58 3706.0339596851 -848.60652082524 50 58 722.36683469073 42.963518414442 50 58 262.94130347572 -1733.5288300386 50 58 3778.058225824 -1628.058225824 50 58 622.80394092887 -467.13330345548 50 58 1004.3260710173 -1346.6259737243 50 58 3129.5375392764 -925.21562551409 50 58 1920.0539321953 81.716682003892 50 58 264.10502811154 33.186453018978 50 58 3335.9949786522 -564.15567518578 50 58 2971.1394624533 -1381.1740234635 50 58 1351.8207521926 -1026.0372727017 50 58 215.1945587744 -921.44343644 50 58 3777.9039666689 -389.01253121797 50 58 696.60118099605 -1445.3382867397 50 58 728.35605944582 -231.74410256578 50 58 3319.3115215333 -1073.9684694836 50 58 1530.8304268418 -320.52362110518 50 58 1285.7142857142 76.103882369648 50 58 2474.4274814706 -1015.6649119318 50 58 2951.0287882931 71.331937890859 50 58 1520.501381672 -1425.3304293798 50 58 253.6973135114 -1374.8468483467 50 58 1110.952305133 -319.40091038483 50 58 1350.6011578823 -1811.1729631562 50 58 2338.1660764839 65.729833865481 50 58 2858.5107349988 -1760.0160943389 50 58 1627.2275441016 -1039.7316169914 50 58 3665.7543823172 -1786.4216673679 50 58 1923.8707999441 -1275.6821326923 50 58 1085.6123252695 -587.51500034014 50 58 3438.4243515027 68.682495248415 50 58 605.58286398704 -1059.6338500857 50 58 2609.508999673 -311.68836401073 50 58 245.98314002489 -259.25014738251 50 58 2058.4956784593 -885.34925903342 50 58 1764.0325246399 -866.84558719798 50 58 3110.001253574 -276.20095824932 50 58 2265.1599402191 -1488.4548701852 50 58 2468.3827255104 -1797.189068538 50 58 1682.7777746686 -1801.9522820407 50 58 874.99999999995 -1825.0191987092 50 58 2826.2893615091 -1545.2191605369 50 58 2072.1121891632 -1138.5574595677 50 58 798.90642055128 -666.5740256143 50 58 3363.9794589634 -1515.8092628208 50 58 2310.0022045934 -817.77285230863 50 58 956.7723965576 -1038.1425165412 50 58 203.80770423961 -694.60096326135 50 58 1332.7146260126 -731.0012415672 50 58 2736.2963545966 -809.01301810765 50 58 1912.917229461 -1489.1145205076 50 58 2848.8056048724 -1187.6725098473 50 58 1910.311811976 -490.06137012571 50 58 2755.2480385675 99.259138194832 50 58 3137.71838514 -698.10133794125 50 58 3769.8773198013 73.021229696858 50 58 2504.8730852043 -516.86975916373 50 58 1707.6394965756 -1589.2984418475 50 58 1967.7996779093 -1839.7760221533 50 58 2322.1948729117 -158.21196510554 50 58 520.41667523366 -1296.3119318412 50 58 2648.8039626677 -1229.9564880484 50 58 1694.342246968 -1234.1991991544 50 58 2142.5260679531 138.52442710411 50 58 3504.2273541441 -1399.1795629606 50 58 1015.165916226 86.098665248372 50 58 3835.4108901534 -138.50067297114 50 58 476.63184692998 107.35456591656 50 58 3046.5330873028 -1818.6891070206 50 58 3816.8158456071 -1224.5822677841 50 58 2911.6976013938 -987.04932430719 50 58 1288.2093595642 -1287.1762444129 50 58 2633.2953778388 -1455.0724689842 50 58 2909.6711019268 -606.11308583545 50 58 3503.5967245883 -1828.5311691038 50 58 3819.95043295 -657.02581520156 50 58 443.03408776235 -1816.8508568957 50 58 1922.7072374645 -140.38078415628 50 58 2263.0577707571 -1221.0987388152 50 58 165.61538782086 -1558.4628703367 50 58 3820.422655069 -1037.2353091418 50 58 3441.4789343952 -149.50414065605 50 58 1068.5780858665 -1557.7249410525 50 58 3169.8062650757 -1459.8592624457 50 58 124.99999999984 -1856.2499999986 50 58 1144.2671355981 -77.68362293098 50 58 3530.4249014986 -366.64277666758 50 58 2888.3414753778 -392.15715170384 50 58 1352.7439897812 -1626.7393704996 50 58 Edges 103 1 19 1 19 20 1 20 21 1 21 22 1 22 23 1 23 24 1 24 25 1 25 2 1 2 26 2 26 27 2 27 28 2 28 3 2 3 29 3 29 30 3 30 31 3 31 4 3 4 32 4 32 5 4 5 6 5 6 33 6 33 34 6 34 35 6 35 36 6 36 7 6 7 37 7 37 8 7 8 38 8 38 39 8 39 40 8 40 41 8 41 42 8 42 43 8 43 9 8 9 44 9 44 45 9 45 46 9 46 47 9 47 48 9 48 49 9 49 50 9 50 51 9 51 52 9 52 53 9 53 54 9 54 55 9 55 56 9 56 1 9 10 57 13 57 58 13 58 59 13 59 60 13 60 61 13 61 62 13 62 63 13 63 11 13 11 64 14 64 65 14 65 66 14 66 12 14 12 67 15 67 68 15 68 69 15 69 13 15 13 70 16 70 14 16 14 15 17 15 71 18 71 72 18 72 73 18 73 74 18 74 16 18 16 75 19 75 17 19 17 76 20 76 77 20 77 78 20 78 79 20 79 80 20 80 81 20 81 18 20 18 82 21 82 83 21 83 84 21 84 85 21 85 86 21 86 87 21 87 88 21 88 89 21 89 90 21 90 91 21 91 92 21 92 93 21 93 94 21 94 10 21 1 10 23 2 11 24 3 12 28 4 13 32 5 14 36 6 15 40 7 16 44 8 17 48 9 18 52 Triangles 772 162 236 123 11 123 236 101 11 33 178 124 11 99 175 126 11 23 125 164 11 110 129 169 11 44 183 208 11 23 174 125 11 115 143 188 11 1 160 56 11 1 19 160 11 184 108 125 11 127 171 231 11 107 134 161 11 21 142 186 11 112 142 156 11 115 187 141 11 148 127 190 11 125 108 164 11 101 217 132 11 99 126 167 11 141 187 198 11 5 191 148 11 95 143 201 11 51 52 140 11 131 208 183 11 30 192 128 11 171 98 231 11 107 132 152 11 101 132 168 11 30 128 176 11 20 21 186 11 127 231 190 11 107 168 132 11 52 170 140 11 44 45 183 11 129 201 143 11 95 188 143 11 211 130 31 11 101 168 123 11 46 138 183 11 100 149 141 11 33 124 191 11 107 152 134 11 112 156 167 11 117 202 139 11 137 199 157 11 110 175 129 11 33 34 178 11 129 143 169 11 41 165 228 11 130 192 31 11 199 97 157 11 109 206 136 11 116 166 146 11 110 140 170 11 115 141 205 11 8 38 155 11 134 228 165 11 48 177 136 11 115 205 143 11 111 146 166 11 105 230 140 11 139 202 226 11 45 46 183 11 19 186 160 11 107 207 158 11 122 173 215 11 124 148 191 11 117 209 141 11 127 225 214 11 127 148 225 11 19 20 186 11 7 180 227 11 161 207 107 11 5 150 32 11 46 172 138 11 41 42 165 11 134 239 161 11 104 135 161 11 100 141 209 11 122 179 163 11 117 139 209 11 141 149 205 11 3 29 144 11 109 136 185 11 51 140 159 11 108 156 142 11 105 140 169 11 95 163 179 11 113 196 145 11 4 211 31 11 102 215 173 11 104 138 189 11 113 137 163 11 108 142 200 11 100 212 149 11 132 217 147 11 104 189 135 11 110 169 140 11 109 189 138 11 109 172 206 11 109 138 172 11 22 23 164 11 127 214 171 11 5 6 191 11 234 138 104 11 23 24 174 11 117 141 198 11 113 182 196 11 32 150 211 11 140 230 159 11 48 136 206 11 54 153 218 11 126 175 238 11 38 147 155 11 236 151 197 11 98 198 187 11 100 185 212 11 136 212 185 11 54 55 153 11 27 28 146 11 48 49 177 11 97 184 213 11 105 143 205 11 116 157 166 11 113 199 137 11 7 227 36 11 97 166 157 11 118 151 236 11 126 218 153 11 108 145 196 11 112 186 142 11 99 167 156 11 21 200 142 11 133 171 214 11 116 193 144 11 106 224 157 11 104 161 239 11 123 168 158 11 97 213 166 11 137 157 224 11 3 144 193 11 119 155 147 11 120 149 212 11 105 169 143 11 8 155 37 11 108 196 156 11 46 47 172 11 131 234 239 11 108 184 145 11 35 36 151 11 131 239 165 11 50 51 159 11 114 190 231 11 122 163 224 11 116 144 235 11 29 176 144 11 106 173 224 11 108 200 164 11 137 224 163 11 111 229 146 11 130 211 150 11 111 232 154 11 113 201 182 11 106 144 241 11 96 226 202 11 28 193 146 11 103 214 225 11 133 202 171 11 30 31 192 11 106 241 173 11 122 215 179 11 130 203 210 11 116 146 193 11 121 195 187 11 106 235 144 11 29 30 176 11 111 154 229 11 117 171 202 11 130 150 203 11 5 148 190 11 126 153 167 11 97 145 184 11 112 167 153 11 111 174 232 11 103 162 204 11 129 182 201 11 26 229 154 11 97 199 145 11 113 145 199 11 9 208 43 11 9 44 208 11 119 147 217 11 52 53 170 11 37 155 180 11 26 27 229 11 5 190 150 11 107 158 168 11 6 33 191 11 112 160 186 11 123 204 162 11 112 153 220 11 114 203 150 11 109 185 240 11 98 187 195 11 105 149 230 11 96 223 158 11 123 158 223 11 38 222 147 11 120 230 149 11 7 37 180 11 119 197 151 11 113 163 201 11 139 240 185 11 120 216 159 11 2 237 25 11 112 220 160 11 110 238 175 11 103 194 162 11 27 146 229 11 53 218 170 11 105 205 149 11 122 224 173 11 50 159 216 11 102 210 203 11 114 150 190 11 119 151 227 11 121 187 188 11 34 35 221 11 102 173 210 11 119 180 155 11 133 223 202 11 21 22 200 11 118 221 151 11 144 176 241 11 132 147 222 11 128 210 173 11 55 56 220 11 170 218 238 11 109 240 189 11 129 175 182 11 115 188 187 11 121 181 195 11 38 39 222 11 99 156 196 11 125 213 184 11 56 160 220 11 35 151 221 11 3 193 28 11 99 196 182 11 40 228 152 11 139 185 209 11 99 182 175 11 40 152 233 11 26 154 237 11 100 209 185 11 118 162 194 11 136 177 212 11 22 164 200 11 47 48 206 11 132 233 152 11 128 173 241 11 124 225 148 11 124 178 194 11 134 152 228 11 111 213 174 11 40 41 228 11 39 40 233 11 36 227 151 11 135 207 161 11 95 179 188 11 114 181 203 11 135 189 240 11 125 174 213 11 96 202 223 11 138 234 183 11 96 158 207 11 34 221 178 11 98 195 231 11 55 220 153 11 116 235 157 11 24 25 232 11 121 188 179 11 49 50 216 11 42 43 219 11 4 32 211 11 53 54 218 11 98 171 198 11 95 201 163 11 106 157 235 11 120 159 230 11 114 195 181 11 25 154 232 11 117 198 171 11 25 237 154 11 118 194 178 11 124 194 225 11 2 26 237 11 47 206 172 11 130 210 192 11 131 165 219 11 103 225 194 11 111 166 213 11 42 219 165 11 102 203 181 11 104 239 234 11 134 165 239 11 128 192 210 11 118 236 162 11 120 212 177 11 133 204 223 11 103 204 214 11 110 170 238 11 120 177 216 11 121 179 215 11 133 214 204 11 102 181 215 11 118 178 221 11 49 216 177 11 24 232 174 11 126 238 218 11 121 215 181 11 128 241 176 11 96 207 226 11 131 183 234 11 119 217 197 11 101 197 217 11 135 226 207 11 114 231 195 11 101 236 197 11 131 219 208 11 119 227 180 11 43 208 219 11 123 223 204 11 132 222 233 11 139 226 240 11 135 240 226 11 39 233 222 11 19 20 57 25 57 20 58 25 60 23 61 25 60 22 23 25 61 24 62 25 61 23 24 25 10 19 57 25 10 1 19 25 20 59 58 25 20 21 59 25 59 22 60 25 59 21 22 25 25 62 24 25 25 63 62 25 11 25 2 25 11 63 25 25 11 2 64 29 2 26 64 29 26 65 64 29 27 65 26 29 65 28 66 29 65 27 28 29 66 3 12 29 66 28 3 29 12 3 67 33 3 29 67 33 68 30 69 33 30 31 69 33 69 31 13 33 31 4 13 33 67 30 68 33 67 29 30 33 13 32 70 37 13 4 32 37 32 14 70 37 32 5 14 37 14 5 15 41 5 6 15 41 6 71 15 45 6 33 71 45 33 34 71 45 71 34 72 45 34 73 72 45 34 35 73 45 36 73 35 45 74 73 36 45 16 36 7 45 16 74 36 45 7 37 16 49 16 37 75 49 75 37 17 49 37 8 17 49 17 38 76 53 17 8 38 53 38 39 76 53 76 39 77 53 81 43 18 53 43 9 18 53 77 40 78 53 77 39 40 53 40 79 78 53 41 79 40 53 79 42 80 53 79 41 42 53 80 43 81 53 42 43 80 53 18 9 82 57 9 44 82 57 44 45 82 57 82 45 83 57 83 45 84 57 45 46 84 57 46 47 84 57 84 47 85 57 49 50 88 57 49 88 87 57 51 90 89 57 51 52 90 57 54 55 92 57 92 55 93 57 93 56 94 57 93 55 56 57 48 85 47 57 86 85 48 57 49 86 48 57 87 86 49 57 89 50 51 57 89 88 50 57 52 91 90 57 52 53 91 57 91 54 92 57 91 53 54 57 94 1 10 57 94 56 1 57 309 383 270 58 270 383 248 58 71 325 271 58 246 322 273 58 61 272 311 58 257 276 316 58 82 330 355 58 61 321 272 58 262 290 335 58 10 307 94 58 10 57 307 58 331 255 272 58 274 318 378 58 254 281 308 58 59 289 333 58 259 289 303 58 262 334 288 58 295 274 337 58 272 255 311 58 248 364 279 58 246 273 314 58 288 334 345 58 14 338 295 58 242 290 348 58 89 90 287 58 278 355 330 58 68 339 275 58 318 245 378 58 254 279 299 58 248 279 315 58 68 275 323 58 58 59 333 58 274 378 337 58 254 315 279 58 90 317 287 58 82 83 330 58 276 348 290 58 242 335 290 58 358 277 69 58 248 315 270 58 84 285 330 58 247 296 288 58 71 271 338 58 254 299 281 58 259 303 314 58 264 349 286 58 284 346 304 58 257 322 276 58 71 72 325 58 276 290 316 58 79 312 375 58 277 339 69 58 346 244 304 58 256 353 283 58 263 313 293 58 257 287 317 58 262 288 352 58 17 76 302 58 281 375 312 58 86 324 283 58 262 352 290 58 258 293 313 58 252 377 287 58 286 349 373 58 83 84 330 58 57 333 307 58 254 354 305 58 269 320 362 58 271 295 338 58 264 356 288 58 274 372 361 58 274 295 372 58 57 58 333 58 16 327 374 58 308 354 254 58 14 297 70 58 84 319 285 58 79 80 312 58 281 386 308 58 251 282 308 58 247 288 356 58 269 326 310 58 264 286 356 58 288 296 352 58 12 67 291 58 256 283 332 58 89 287 306 58 255 303 289 58 252 287 316 58 242 310 326 58 260 343 292 58 13 358 69 58 249 362 320 58 251 285 336 58 260 284 310 58 255 289 347 58 247 359 296 58 279 364 294 58 251 336 282 58 257 316 287 58 256 336 285 58 256 319 353 58 256 285 319 58 60 61 311 58 274 361 318 58 14 15 338 58 381 285 251 58 61 62 321 58 264 288 345 58 260 329 343 58 70 297 358 58 287 377 306 58 86 283 353 58 92 300 365 58 273 322 385 58 76 294 302 58 383 298 344 58 245 345 334 58 247 332 359 58 283 359 332 58 92 93 300 58 65 66 293 58 86 87 324 58 244 331 360 58 252 290 352 58 263 304 313 58 260 346 284 58 16 374 74 58 244 313 304 58 265 298 383 58 273 365 300 58 255 292 343 58 259 333 289 58 246 314 303 58 59 347 289 58 280 318 361 58 263 340 291 58 253 371 304 58 251 308 386 58 270 315 305 58 244 360 313 58 284 304 371 58 12 291 340 58 266 302 294 58 267 296 359 58 252 316 290 58 17 302 75 58 255 343 303 58 84 85 319 58 278 381 386 58 255 331 292 58 73 74 298 58 278 386 312 58 88 89 306 58 261 337 378 58 269 310 371 58 263 291 382 58 67 323 291 58 253 320 371 58 255 347 311 58 284 371 310 58 258 376 293 58 277 358 297 58 258 379 301 58 260 348 329 58 253 291 388 58 243 373 349 58 66 340 293 58 250 361 372 58 280 349 318 58 68 69 339 58 253 388 320 58 269 362 326 58 277 350 357 58 263 293 340 58 268 342 334 58 253 382 291 58 67 68 323 58 258 301 376 58 264 318 349 58 277 297 350 58 14 295 337 58 273 300 314 58 244 292 331 58 259 314 300 58 258 321 379 58 250 309 351 58 276 329 348 58 64 376 301 58 244 346 292 58 260 292 346 58 18 355 81 58 18 82 355 58 266 294 364 58 90 91 317 58 75 302 327 58 64 65 376 58 14 337 297 58 254 305 315 58 15 71 338 58 259 307 333 58 270 351 309 58 259 300 367 58 261 350 297 58 256 332 387 58 245 334 342 58 252 296 377 58 243 370 305 58 270 305 370 58 76 369 294 58 267 377 296 58 16 75 327 58 266 344 298 58 260 310 348 58 286 387 332 58 267 363 306 58 11 384 63 58 259 367 307 58 257 385 322 58 250 341 309 58 65 293 376 58 91 365 317 58 252 352 296 58 269 371 320 58 88 306 363 58 249 357 350 58 261 297 337 58 266 298 374 58 268 334 335 58 72 73 368 58 249 320 357 58 266 327 302 58 280 370 349 58 59 60 347 58 265 368 298 58 291 323 388 58 279 294 369 58 275 357 320 58 93 94 367 58 317 365 385 58 256 387 336 58 276 322 329 58 262 335 334 58 268 328 342 58 76 77 369 58 246 303 343 58 272 360 331 58 94 307 367 58 73 298 368 58 12 340 66 58 246 343 329 58 78 375 299 58 286 332 356 58 246 329 322 58 78 299 380 58 64 301 384 58 247 356 332 58 265 309 341 58 283 324 359 58 60 311 347 58 85 86 353 58 279 380 299 58 275 320 388 58 271 372 295 58 271 325 341 58 281 299 375 58 258 360 321 58 78 79 375 58 77 78 380 58 74 374 298 58 282 354 308 58 242 326 335 58 261 328 350 58 282 336 387 58 272 321 360 58 243 349 370 58 285 381 330 58 243 305 354 58 72 368 325 58 245 342 378 58 93 367 300 58 263 382 304 58 62 63 379 58 268 335 326 58 87 88 363 58 80 81 366 58 13 70 358 58 91 92 365 58 245 318 345 58 242 348 310 58 253 304 382 58 267 306 377 58 261 342 328 58 63 301 379 58 264 345 318 58 63 384 301 58 265 341 325 58 271 341 372 58 11 64 384 58 85 353 319 58 277 357 339 58 278 312 366 58 250 372 341 58 258 313 360 58 80 366 312 58 249 350 328 58 251 386 381 58 281 312 386 58 275 339 357 58 265 383 309 58 267 359 324 58 280 351 370 58 250 351 361 58 257 317 385 58 267 324 363 58 268 326 362 58 280 361 351 58 249 328 362 58 265 325 368 58 87 363 324 58 62 379 321 58 273 385 365 58 268 362 328 58 275 388 323 58 243 354 373 58 278 330 381 58 266 364 344 58 248 344 364 58 282 373 354 58 261 378 342 58 248 383 344 58 278 366 355 58 266 374 327 58 81 355 366 58 270 370 351 58 279 369 380 58 286 373 387 58 282 387 373 58 77 380 369 58 End trunk-2018.02b/examples/sph/cpt/000077500000000000000000000000001324306050200163645ustar00rootroot00000000000000trunk-2018.02b/examples/sph/cpt/a000066400000000000000000000000001324306050200165150ustar00rootroot00000000000000trunk-2018.02b/examples/sph/dam_break.py000066400000000000000000000041701324306050200200570ustar00rootroot00000000000000#!/usr/bin/env python # encoding: utf-8 from yade import utils, plot, qt o = Omega() # Physical parameters fr = 0.5; rho= 1000.0 k = 1000.0 mu = 10.0 tc = 0.01; en = 0.7; et = 0.7; vel = 0.0 #Rad = 0.006 #h = 0.011 Rad = 0.015 h = 0.03 #Rad = 0.02 #h = 0.04 o.dt = 0.0005 X = 4.0 Z = 3.0 yCoeff = 4.0 SpheresX = 1.0 SpheresZ = 2.0 # Add material mat1 = O.materials.append(ViscElMat(frictionAngle=fr,density=rho, SPHmode=True, h=h, mu=mu,tc=tc, en=en, et=et, KernFunctionPressure = 1, KernFunctionVisco = 1)) idBox = pack.regularHexa(pack.inAlignedBox((0.0,-Rad*yCoeff,0.0),(X,Rad*yCoeff,Z)),radius=Rad,gap=0.0,color=(0,1,1), material=mat1, mask = 1, fixed=True) idBoxAdd = [] idSpheresAdd = [] for i in range(len(idBox)): if (((idBox[i].state.pos[0])(X-yCoeff*Rad - 2.0*Rad)) or ((idBox[i].state.pos[2])<(yCoeff*Rad)) or ((idBox[i].state.pos[2])>(Z - 2.0*Rad))): idBoxAdd.append(idBox[i]) elif (((idBox[i].state.pos[0])-Rad) and ((idBox[i].state.pos[1])<2.0*Rad)): idBox[i].shape.color = Vector3(1,0,0) idBox[i].state.fixed = False idBox[i].state.blockedDOFs = 'y' idSpheresAdd.append(idBox[i]) O.bodies.append(idBoxAdd) idSpheres = O.bodies.append(idSpheresAdd) # Add engines o.engines = [ ForceResetter(), InsertionSortCollider([Bo1_Sphere_Aabb(label='is2aabb')],ompThreads=1), InteractionLoop( [Ig2_Sphere_Sphere_ScGeom(label='ss2sc')], [Ip2_ViscElMat_ViscElMat_ViscElPhys()], [Law2_ScGeom_ViscElPhys_Basic()], ), NewtonIntegrator(damping=0.01,gravity=[0,0,-9.81]), SPHEngine(mask=1, k=k, rho0 = rho, h=h, KernFunctionDensity= 1), PyRunner(command='addPlotData()',iterPeriod=10,initRun=True,dead=False), ] enlargeF = h/Rad*1.1 print "enlargeF = %g"%enlargeF is2aabb.aabbEnlargeFactor = enlargeF ss2sc.interactionDetectionFactor = enlargeF # Function to add data to plot def addPlotData(): plot.addData(t = O.time, eKin = utils.kineticEnergy()) plot.plots={'t':('eKin')}; plot.plot() qt.View() trunk-2018.02b/examples/sph/sph_box.py000066400000000000000000000030261324306050200176130ustar00rootroot00000000000000#!/usr/bin/env python # encoding: utf-8 # This script demonstrates SPH-engine in Yade # !!! Very experimental at the moment!!! from yade import utils, plot, qt o = Omega() # Physical parameters fr = 0.5; rho=1000.0 k = 2000.0 tc = 0.001; en = 0.5; et = 0.5; Rad = 7.0e-3 h = 2*7.0e-3 o.dt = 0.00025 # Add material mat1 = O.materials.append(ViscElMat(frictionAngle=fr,density=rho, SPHmode=True, h=h, tc=tc, en=en, et=et, KernFunctionPressure = 1, KernFunctionVisco = 1)) mat2 = O.materials.append(ViscElMat(frictionAngle=fr,density=rho, SPHmode=False,tc=tc, en=en, et=et)) # Add spheres d = 0.17 idSpheres = O.bodies.append( pack.regularHexa( pack.inAlignedBox( (-d*0.5,-d*0.5,-d), # lower angle (d*0.5,d*0.5,d)), # upper angle radius=Rad,gap=0.01*Rad, mask=3, material=mat1,color=(0,1,1))) id1 = O.bodies.append(geom.facetBox((Vector3(0.0,0,0.0)), (Vector3(0.2, 0.2, 0.2)), material=mat2, mask=5, color=(1,0,0), wire=True)) # Add engines o.engines = [ ForceResetter(), InsertionSortCollider([Bo1_Sphere_Aabb(label='is2aabb'),Bo1_Facet_Aabb(label='is3aabb')]), InteractionLoop( [Ig2_Sphere_Sphere_ScGeom(label='ss2sc'),Ig2_Facet_Sphere_ScGeom()], [Ip2_ViscElMat_ViscElMat_ViscElPhys()], [Law2_ScGeom_ViscElPhys_Basic()], ), NewtonIntegrator(damping=0.0,gravity=[0,0,-9.81]), SPHEngine(mask=3, k=k, rho0 = rho, h=h, KernFunctionDensity= 1), ] enlargeF = h/Rad*1.1 print "enlargeF = %g"%enlargeF is2aabb.aabbEnlargeFactor = enlargeF ss2sc.interactionDetectionFactor = enlargeF O.step() qt.View() trunk-2018.02b/examples/sph/testKernelFunc.py000066400000000000000000000076411324306050200211140ustar00rootroot00000000000000#!/usr/bin/env python # encoding: utf-8 # This example allows to test different kernel functions from yade import utils, plot, qt o = Omega() # Physical parameters fr = 0.5; rho=1000.0 k = 1.0 mu = 100.0 tc = 0.0001; en = 0.7; et = 0.7; vel = 0.05 Rad = 15.0e-3 o.dt = 0.0001 shift = 1.0 # Add material mat1 = O.materials.append(ViscElMat(frictionAngle=fr,density=rho, SPHmode=True,mu=mu,tc=tc, en=en, et=et, KernFunctionPressure = 1, KernFunctionVisco = 1)) mat2 = O.materials.append(ViscElMat(frictionAngle=fr,density=rho, SPHmode=True,mu=mu,tc=tc, en=en, et=et, KernFunctionPressure = 1, KernFunctionVisco = 2)) mat3 = O.materials.append(ViscElMat(frictionAngle=fr,density=rho, SPHmode=True,mu=mu,tc=tc, en=en, et=et, KernFunctionPressure = 1, KernFunctionVisco = 3)) mat4 = O.materials.append(ViscElMat(frictionAngle=fr,density=rho, SPHmode=True,mu=mu,tc=tc, en=en, et=et, KernFunctionPressure = 1, KernFunctionVisco = 4)) mat5 = O.materials.append(ViscElMat(frictionAngle=fr,density=rho, SPHmode=True,mu=mu,tc=tc, en=en, et=et, KernFunctionPressure = 1, KernFunctionVisco = 5)) # Add spheres #inert = True inert = True id11 = O.bodies.append(sphere(center=[0,0,0],radius=Rad, material=mat1, mask = 1, fixed=True)) id12 = O.bodies.append(sphere(center=[0,0,(Rad*2.0*shift)],radius=Rad, material=mat1, mask = 1, fixed=inert)) id21 = O.bodies.append(sphere(center=[3.0*Rad,0,0],radius=Rad, material=mat2, mask = 1, fixed=True)) id22 = O.bodies.append(sphere(center=[3.0*Rad,0,(Rad*2.0*shift)],radius=Rad, material=mat2, mask = 1, fixed=inert)) id31 = O.bodies.append(sphere(center=[6.0*Rad,0,0],radius=Rad, material=mat3, mask = 1, fixed=True)) id32 = O.bodies.append(sphere(center=[6.0*Rad,0,(Rad*2.0*shift)],radius=Rad, material=mat3, mask = 1, fixed=inert)) id41 = O.bodies.append(sphere(center=[9.0*Rad,0,0],radius=Rad, material=mat4, mask = 1, fixed=True)) id42 = O.bodies.append(sphere(center=[9.0*Rad,0,(Rad*2.0*shift)],radius=Rad, material=mat4, mask = 1, fixed=inert)) id51 = O.bodies.append(sphere(center=[12.0*Rad,0,0],radius=Rad, material=mat5, mask = 1, fixed=True)) id52 = O.bodies.append(sphere(center=[12.0*Rad,0,(Rad*2.0*shift)],radius=Rad, material=mat5, mask = 1, fixed=inert)) vel = 0.1 O.bodies[id12].state.vel=Vector3(0,0,-vel) O.bodies[id22].state.vel=Vector3(0,0,-vel) O.bodies[id32].state.vel=Vector3(0,0,-vel) O.bodies[id42].state.vel=Vector3(0,0,-vel) O.bodies[id52].state.vel=Vector3(0,0,-vel) # Add engines o.engines = [ ForceResetter(), InsertionSortCollider([Bo1_Sphere_Aabb(label='is2aabb')]), InteractionLoop( [Ig2_Sphere_Sphere_ScGeom(label='ss2sc')], [Ip2_ViscElMat_ViscElMat_ViscElPhys()], [Law2_ScGeom_ViscElPhys_Basic()], ), NewtonIntegrator(damping=0,gravity=[0,0,-9.81]), SPHEngine(mask=1, k=k, rho0 = rho, KernFunctionDensity=1), PyRunner(command='addPlotData()',iterPeriod=1,dead=False), ] print "Time\tX\tRho\tP\tFpr " # Function to add data to plot def addPlotData(): #print "%.2f\t%.5f\t%.5f\t%.5f\t%.5f" % (O.time+O.dt, O.bodies[id2].state.pos[2], O.bodies[id2].rho, O.bodies[id2].press, O.forces.f(id2)[2]) s1 = (O.bodies[id12].state.pos[2]-O.bodies[id11].state.pos[2])-(Rad*2.0) s2 = (O.bodies[id22].state.pos[2]-O.bodies[id21].state.pos[2])-(Rad*2.0) s3 = (O.bodies[id32].state.pos[2]-O.bodies[id31].state.pos[2])-(Rad*2.0) s4 = (O.bodies[id42].state.pos[2]-O.bodies[id41].state.pos[2])-(Rad*2.0) s5 = (O.bodies[id52].state.pos[2]-O.bodies[id51].state.pos[2])-(Rad*2.0) f1 = O.forces.f(id12)[2] f2 = O.forces.f(id22)[2] f3 = O.forces.f(id32)[2] f4 = O.forces.f(id42)[2] f5 = O.forces.f(id52)[2] plot.addData(sc=O.iter, s1 = s1, s2 = s2, s3 = s3, s4 = s4, s5 = s5, fc=O.iter, f1 = f1, f2 = f2, f3 = f3, f4 = f4, f5 = f5) plot.plots={'sc':('s1', 's2', 's3', 's4', 's5'), 'fc':('f1', 'f2', 'f3', 'f4', 'f5')}; plot.plot() O.run(1, True) qt.View() O.run(1500) trunk-2018.02b/examples/sph/toystar.py000066400000000000000000000030321324306050200176530ustar00rootroot00000000000000#!/usr/bin/env python # encoding: utf-8 # This script demonstrates SPH-engine in Yade # !!! Very experimental at the moment!!! from yade import utils, plot, qt o = Omega() # Physical parameters fr = 0.5; rho=1000.0 k = 1.0 tc = 0.0001; en = 0.7; et = 0.7; Rad = 10.0e-3 h = 2*Rad o.dt = 0.00001 # Add material mat1 = O.materials.append(ViscElMat(frictionAngle=fr,density=rho, SPHmode=True,h=h,tc=tc, en=en, et=et, KernFunctionPressure = 1, KernFunctionVisco = 1)) # Add spheres d = 0.8 idSpheres = O.bodies.append( pack.regularHexa( pack.inSphere( (0,0,0), d), radius=Rad,gap=10.5*Rad, material=mat1, mask=1, color=(0,1,1))) idCentralBody = O.bodies.append(sphere(center=[0,0,0],radius=0.1*Rad, mask = 2, color = [1,0,0], fixed=True)) # Add engines o.engines = [ ForceResetter(), InsertionSortCollider([Bo1_Sphere_Aabb(label='is2aabb'),Bo1_Facet_Aabb(label='is3aabb')]), InteractionLoop( [Ig2_Sphere_Sphere_ScGeom(label='ss2sc'),Ig2_Facet_Sphere_ScGeom()], [Ip2_ViscElMat_ViscElMat_ViscElPhys()], [Law2_ScGeom_ViscElPhys_Basic()], ), CentralGravityEngine(accel=50.0, label='gr', centralBody=idCentralBody), NewtonIntegrator(damping=0.1), SPHEngine(mask=1, k=k, rho0 = rho, h=h, KernFunctionDensity= 1), VTKRecorder(iterPeriod=1000, mask=1, fileName='./cpt/spheres-', recorders=['spheres','velocity','colors','intr','ids','mask','materialId','stress']), ] enlargeF = h/Rad*1.1 print "enlargeF = %g"%enlargeF is2aabb.aabbEnlargeFactor = enlargeF ss2sc.interactionDetectionFactor = enlargeF O.step() qt.View() trunk-2018.02b/examples/sph/watercolumn.py000066400000000000000000000036761324306050200205240ustar00rootroot00000000000000#!/usr/bin/env python # encoding: utf-8 from yade import utils, plot, qt, ymport o = Omega() # Physical parameters fr = 0.0; rho=1000.0 k = 5000.0 tc = 0.001; en = 0.7; et = 0.7; vel = 0.05 Rad = 12.0e-3 h = 2*Rad o.dt = 0.0002 scaleF = 0.001 # Add material mat1 = O.materials.append(ViscElMat(frictionAngle=fr,density=rho, SPHmode=True,h=h,tc=tc, en=en, et=et, KernFunctionPressure = 1, KernFunctionVisco = 1)) mat2 = O.materials.append(ViscElMat(frictionAngle=fr,density=rho, SPHmode=False,h=h,tc=tc, en=en, et=et)) id1 = O.bodies.append(ymport.gmsh("box.mesh", scale=scaleF, material=mat2, color=(1,0,0), mask = 5, wire=True)) d = 15.0*scaleF print d idSpheres = O.bodies.append( pack.regularHexa( pack.inAlignedBox( (0, -2000.0*scaleF, 0.0), (1000*scaleF, 200*scaleF, 50.0*scaleF)), radius=d,gap=0.001*d, material=mat1, mask=3, color=(0,1,1))) print len(idSpheres) # Add engines o.engines = [ ForceResetter(), InsertionSortCollider([Bo1_Sphere_Aabb(label='is2aabb'),Bo1_Facet_Aabb(label='is3aabb')]), InteractionLoop( [Ig2_Sphere_Sphere_ScGeom(label='ss2sc'),Ig2_Facet_Sphere_ScGeom()], [Ip2_ViscElMat_ViscElMat_ViscElPhys()], [Law2_ScGeom_ViscElPhys_Basic()], ), NewtonIntegrator(damping=0.05,gravity=[0,-9.81,0]), SPHEngine(mask=3, k=k, rho0 = rho, h = h, KernFunctionDensity= 1), VTKRecorder(iterPeriod=100,fileName='./cpt/spheres-', recorders=['spheres','velocity','colors','intr','ids','mask','materialId','stress']), VTKRecorder(iterPeriod=100,fileName='./cpt/facet-', recorders=['facets'],label='VTK_box2'), PyRunner(command='addPlotData()',iterPeriod=50,dead=False), ] def addPlotData(): plot.addData(t=O.time, Ekin=utils.kineticEnergy()) plot.plots={'t':('Ekin')}; plot.plot() enlargeF = h/Rad*1.1 print "enlargeF = %g"%enlargeF is2aabb.aabbEnlargeFactor = enlargeF ss2sc.interactionDetectionFactor = enlargeF O.run(1, True) qt.View() #O.run(10000, True) #plot.saveGnuplot('sim-data_0.05') trunk-2018.02b/examples/spheresFactory.py000066400000000000000000000046551324306050200203710ustar00rootroot00000000000000#!/usr/bin/python # -*- coding: utf-8 -*- """Simple script which shows how to create an inlet. """ shotsId,steelId=O.materials.append([ FrictMat(young=50e9,density=6000,poisson=.2,label='shots'), FrictMat(young=210e9,density=7800, poisson=.3,label='steel'), ]) ## same as # # shotsId,steelId=O.materials.index('shots'),O.materials.index('steel') # O.bodies.append(geom.facetBox(center=(0,0,0),extents=(30e-3,30e-3,0),wallMask=32,wire=False,material='steel',color=(0,1,.3))) O.engines=[ ForceResetter(), InsertionSortCollider([Bo1_Sphere_Aabb(),Bo1_Facet_Aabb()],verletDist=.05*.29e-3), InteractionLoop( [Ig2_Sphere_Sphere_ScGeom(),Ig2_Facet_Sphere_ScGeom()], [Ip2_FrictMat_FrictMat_MindlinPhys( # define restitution coefficients between different pairs of material ids, see the functor's documentation for details #en=MatchMaker(fallback='zero',matches=((steelId,shotsId,.4),(shotsId,shotsId,1))) en=MatchMaker(matches=((steelId,shotsId,.4),(shotsId,shotsId,1))) )], [Law2_ScGeom_MindlinPhys_Mindlin(label='contactLaw')] ), NewtonIntegrator(damping=0), ## CircularFactory: disk if length=0 or cylinder if length>0 #CircularFactory(maxParticles=10000,radius=8e-3,length=16e-3,center=(0,-15e-3,15e-3),rMin=0.28e-3,rMax=0.29e-3,vMin=100,vMax=100,vAngle=0,massFlowRate=100./60,normal=(0,1.5,-1),label='factory',materialId=shotsId), ## BoxFactory: a line, plane or cuboid BoxFactory(maxParticles=10000,extents=(8e-3,8e-3,8e-3),center=(0,-15e-3,15e-3),rMin=0.28e-3,rMax=0.29e-3,vMin=100,vMax=100,vAngle=0,massFlowRate=100./60,normal=(0,1.5,-1),label='factory',materialId=shotsId), DomainLimiter(lo=(-30e-3,-30e-3,0),hi=(30e-3,30e-3,60e-3),iterPeriod=200), #VTKRecorder(recorders=['spheres','facets','velocity'],fileName='/tmp/nozzle-',iterPeriod=500), # run this every once in a while, to finalize the simulation at some point PyRunner(iterPeriod=10000,command='if factory.numParticles>=factory.maxParticles: O.stopAtIter=O.iter+8000; timing.stats()') ] # the timestep must be smaller because of high linear velocities of particles # we cannot use PWaveTimeStep directly, since there are no spheres generated yet O.dt=SpherePWaveTimeStep(factory.rMin,O.materials[factory.materialId].density,O.materials[factory.materialId].young) O.saveTmp() #O.timingEnabled=True from yade import timing try: from yade import qt # setup 3d view v=qt.View() v.upVector=(0,0,1); v.viewDir=(-1,0,-.3); v.center(median=False) except ImportError: pass O.run() trunk-2018.02b/examples/stl-gts/000077500000000000000000000000001324306050200164015ustar00rootroot00000000000000trunk-2018.02b/examples/stl-gts/README000066400000000000000000000004071324306050200172620ustar00rootroot00000000000000This example shows, how to perform the following conversion: GEO->STL->GTS->Yade(clumps) Then GTS-file (mesh) is importing into Yade-Script and new spheres are adding as a clump within the volume of this mesh. The script requires gmsh and libgts-bin installed. trunk-2018.02b/examples/stl-gts/cone.geo000066400000000000000000000016641324306050200200300ustar00rootroot00000000000000acc = 20.0/1000.0; D = 280.0/2.0/1000.0; d = 180.0/2.0/1000.0; h = 180.0/1000.0; Point(1) = {0.0, 0.0, 0.0, acc}; Point(2) = {0.0, D, 0.0, acc}; Point(3) = {0.0, -D, 0.0, acc}; Point(4) = {D, 0.0, 0.0, acc}; Point(5) = {-D, 0.0, 0.0, acc}; Point(6) = {0.0, 0.0, h, acc}; Circle(1) = {2, 1, 4}; Circle(2) = {4, 1, 3}; Circle(3) = {3, 1, 5}; Circle(4) = {5, 1, 2}; Line(5) = {2, 1}; Line(6) = {2, 6}; Line(7) = {4, 1}; Line(8) = {4, 6}; Line(9) = {3, 1}; Line(10) = {3, 6}; Line(11) = {5, 1}; Line(12) = {5, 6}; Line Loop(13) = {1, 7, -5}; Plane Surface(14) = {13}; Line Loop(15) = {2, 9, -7}; Plane Surface(16) = {15}; Line Loop(17) = {3, 11, -9}; Plane Surface(18) = {17}; Line Loop(19) = {4, 5, -11}; Plane Surface(20) = {19}; Line Loop(21) = {3, 12, -10}; Ruled Surface(22) = {21}; Line Loop(23) = {4, 6, -12}; Ruled Surface(24) = {23}; Line Loop(25) = {1, 8, -6}; Ruled Surface(26) = {25}; Line Loop(27) = {2, 10, -8}; Ruled Surface(28) = {27}; trunk-2018.02b/examples/stl-gts/convert2stl.sh000077500000000000000000000001121324306050200212170ustar00rootroot00000000000000#!/bin/bash gmsh -2 cone.geo -o cone.stl stl2gts -r < cone.stl > cone.gts trunk-2018.02b/examples/stl-gts/gts-stl.py000066400000000000000000000025651324306050200203600ustar00rootroot00000000000000#!/usr/bin/python # -*- coding: utf-8 -*- # © 2009 Václav Šmilauer # © 2013 Anton Gladky from yade import pack import gts, os.path, locale surf=gts.read(open('cone.gts')) if surf.is_closed(): pred=pack.inGtsSurface(surf) aabb=pred.aabb() dim0=aabb[1][0]-aabb[0][0]; radius=dim0/70. # get some characteristic dimension, use it for radius O.bodies.appendClumped(pack.regularHexa(pred,radius=radius,gap=radius/4.)) surf.translate(0,-(aabb[1][1]-aabb[0][1])/2.0,-(aabb[1][2]-aabb[0][2])) # move surface down so that facets are underneath the falling spheres O.bodies.append(pack.gtsSurface2Facets(surf,wire=True)) O.engines=[ ForceResetter(), InsertionSortCollider([Bo1_Sphere_Aabb(),Bo1_Facet_Aabb()],label='collider'), InteractionLoop( [Ig2_Sphere_Sphere_ScGeom(),Ig2_Facet_Sphere_ScGeom()], [Ip2_FrictMat_FrictMat_FrictPhys()], [Law2_ScGeom_FrictPhys_CundallStrack()], ), NewtonIntegrator(damping=.1,gravity=[0,0,-500.0]), PyRunner(iterPeriod=1000,command='timing.stats(); O.pause();'), PyRunner(iterPeriod=10,command='addPlotData()') ] O.dt=.7*PWaveTimeStep() O.saveTmp() O.timingEnabled=True O.trackEnergy=True from yade import plot plot.plots={'i':('total',O.energy.keys,)} def addPlotData(): plot.addData(i=O.iter,total=O.energy.total(),**O.energy) plot.plot(subPlots=False) from yade import timing from yade import qt qt.View() trunk-2018.02b/examples/tesselationwrapper/000077500000000000000000000000001324306050200207375ustar00rootroot00000000000000trunk-2018.02b/examples/tesselationwrapper/tesselationWrapper.py000066400000000000000000000020101324306050200251750ustar00rootroot00000000000000#!/usr/bin/python # -*- coding: utf-8 -*- #2012 Bruno Chareyre """Example usage of a TesselationWrapper object for getting microscale quantities.""" # See Catalano2014a for the definition of micro-strain # (http://dx.doi.org/10.1002/nag.2198 or free-access at arxiv http://arxiv.org/pdf/1304.4895.pdf) tt=TriaxialTest() tt.generate("test.yade") O.load("test.yade") O.run(100,True) TW=TesselationWrapper() TW.triangulate() #compute regular Delaunay triangulation, don’t construct tesselation TW.computeVolumes() #will silently tesselate the packing, then compute volume of each Voronoi cell TW.volume(10) #get volume associated to sphere of id 10 TW.setState(0) #store current positions internaly for later use as the "0" state O.run(100,True) #make particles move a little (let's hope they will!) TW.setState(1) #store current positions internaly in the "1" (deformed) state #Now we can define strain by comparing states 0 and 1, and average them at the particles scale TW.defToVtk("strain.vtk")trunk-2018.02b/examples/test/000077500000000000000000000000001324306050200157635ustar00rootroot00000000000000trunk-2018.02b/examples/test/Damping_HM.py000066400000000000000000000061251324306050200203040ustar00rootroot00000000000000# encoding: utf-8 # 2010 Chiara Modenese # Script to test the contact damping in HM (both in the normal and shear direction) #__________________________________________________________________ # Geometry sphere and box r2=1e-2 # radii [m] p2=[r2,0,0] # center positions [m] center=[-r2/2.,0,0] # center [m] extents=[r2/2.,3*r2,3*r2] # half edge lenght [m] #__________________________________________________________________ # Material young=600.0e6 # [N/m^2] poisson=0.6 density=2.60e3 # [kg/m^3] frictionAngle=26 # [°] # Append geometry and material O.materials.append(FrictMat(young=young,poisson=poisson,density=density,frictionAngle=frictionAngle)) O.bodies.append(box(center=center,extents=extents,fixed=True,wire=True)) # body id=0 O.bodies.append(sphere(p2,r2,fixed=True,wire=True)) # body id=1 m_sphere=O.bodies[1].state.mass O.bodies[0].state.mass=m_sphere # set the mass of the box the same as the mass of the sphere O.bodies[1].state.blockedDOFs='XYZ' # block particles rotations #__________________________________________________________________ # list of engines O.engines=[ ForceResetter(), InsertionSortCollider([Bo1_Sphere_Aabb(),Bo1_Box_Aabb()]), InteractionLoop( [Ig2_Sphere_Sphere_ScGeom(),Ig2_Box_Sphere_ScGeom()], [Ip2_FrictMat_FrictMat_MindlinPhys(label='damping')], [Law2_ScGeom_MindlinPhys_Mindlin(label='contactLaw')] ), ForceEngine(force=(-20,0,0),ids=[1],label='force'), NewtonIntegrator(damping=0.0), PyRunner(iterPeriod=1,command='myAddPlotData()'), ] #__________________________________________________________________ # define contact damping coefficients damping.betan=0.5 # normal direction damping.betas=0.0 # shear direction damping.useDamping=True #__________________________________________________________________ # time step O.dt=.2*PWaveTimeStep() O.saveTmp('init') #__________________________________________________________________ from yade import qt qt.View() qt.Controller() #__________________________________________________________________ # plot some results from yade import plot plot.labels=dict(Fn='$Normal\,force$',un='$Overlapping$',time='$Time$',time_='$Time$',Fs='$Shear\,force$',t='$Time$') plot.plots={'time':('un',),'time_':('Fn',),'t':('Fs')} def myAddPlotData(): i=O.interactions[0,1] plot.addData(Fn=i.phys.normalForce[0],Fs=i.phys.shearForce[1],un=i.geom.penetrationDepth,time=O.time,time_=O.time,t=O.time) # We will have: # 1) data in graphs (if you call plot.plot()) # 2) data in file (if you call plot.saveGnuplot('/home/chia/Desktop/Output/out') # 3) data in memory as plot.data['un'], plot.data['fn'], etc. under the labels they were saved O.run(1000,True) # Now test the shear direction, having previously obtained a constant normal overlapping damping.betas=0.5 force.force=(-20,5,0) # assign a force also in the shear direction O.run(1000,True) plot.plot(subPlots=False) # NOTE about the results: # In the graphs, you will see the behaviour both of the normal and shear force. As expected from the damped solution, their values oscillate around the equilibrium position, which is eventually reached after few iterations. trunk-2018.02b/examples/test/WireMatPM/000077500000000000000000000000001324306050200175705ustar00rootroot00000000000000trunk-2018.02b/examples/test/WireMatPM/net-2part-displ-unloading.py000066400000000000000000000101121324306050200250400ustar00rootroot00000000000000# -*- coding: utf-8 -*- # encoding: utf-8 from yade import ymport, qt, plot ## definition of some colors for colored text output in terminal BLUE = '\033[94m' GREEN = '\033[92m' YELLOW = '\033[93m' RED = '\033[91m' BLACK = '\033[0m' #### short description of script print BLUE+''' Simple test for two particles to test the contact law for the WireMat by unsing the '''+RED+'''StepDisplacer'''+BLUE+''' with loading and unloading. '''+BLACK #### define parameters for the net # mesh opening size mos = 80./1000. a = mos/sqrt(3) # wire diameter d = 2.7/1000. # particle radius radius = d*5. # define piecewise lineare stress-strain curve strainStressValues=[(0.0019230769,2.5e8),(0.0192,3.2195e8),(0.05,3.8292e8),(0.15,5.1219e8),(0.25,5.5854e8),(0.3,5.6585e8),(0.35,5.6585e8)] # elastic material properties particleVolume = 4./3.*pow(radius,3)*pi particleMass = 3.9/1000. density = particleMass/particleVolume young = strainStressValues[0][1] / strainStressValues[0][0] poisson = 0.3 #### material definition netMat = O.materials.append(WireMat(young=young,poisson=poisson,frictionAngle=radians(30),density=density,isDoubleTwist=False,diameter=d,strainStressValues=strainStressValues,lambdaEps=0.4,lambdak=0.21)) #### create boddies, default: dynamic=True O.bodies.append( sphere([0,0,0], radius, wire=False, color=[1,0,0], highlight=False, material=netMat) ) O.bodies.append( sphere([0,a,0], radius, wire=False, color=[0,1,0], highlight=False, material=netMat) ) FixedSphere=O.bodies[0] MovingSphere=O.bodies[1] FixedSphere.dynamic=False MovingSphere.dynamic=False def addPlotData(): if O.iter < 1: plot.addData( Fn=0., un=0. ) #plot.saveGnuplot('net-2part-displ-unloading') else: try: i=O.interactions[FixedSphere.id,MovingSphere.id] plot.addData( Fn=i.phys.normalForce.norm(), un=(O.bodies[1].state.pos[1]-O.bodies[0].state.pos[1])-a ) #plot.saveGnuplot('net-2part-displ-unloading') except: print "No interaction!" O.pause() #### define simulation to create link interactionRadius=2. O.engines = [ ForceResetter(), InsertionSortCollider( [Bo1_Sphere_Aabb(aabbEnlargeFactor=interactionRadius,label='aabb')] ), InteractionLoop( [Ig2_Sphere_Sphere_ScGeom(interactionDetectionFactor=interactionRadius,label='Ig2ssGeom')], [Ip2_WireMat_WireMat_WirePhys(linkThresholdIteration=1,label='interactionPhys')], [Law2_ScGeom_WirePhys_WirePM(linkThresholdIteration=1,label='interactionLaw')] ), NewtonIntegrator(damping=0.), PyRunner(initRun=True,iterPeriod=1,command='addPlotData()') ] #### plot some results plot.plots={'un':('Fn',)} plot.plot(noShow=False, subPlots=False) #### create link (no time step needed since loading is involved in this step) O.step() # create cohesive link (cohesiveTresholdIteration=1) #### initializes now the interaction detection factor aabb.aabbEnlargeFactor=-1. Ig2ssGeom.interactionDetectionFactor=-1. ## time step definition ## no time step definition is required since setVelocities=False in StepDisplacer #### define simulation loading O.engines = [StepDisplacer( ids=[1],mov=Vector3(0,+1e-5,0),rot=Quaternion().Identity,setVelocities=False )] + O.engines print 'Loading (press enter)' raw_input() O.run(100,True) #### define simulation unloading O.engines = [StepDisplacer( ids=[1],mov=Vector3(0,-1.3e-5,0),rot=Quaternion().Identity,setVelocities=False )] + O.engines[1:] print 'Unloading (press enter)' raw_input() O.run(50,True) #### define simulation reloading O.engines = [StepDisplacer( ids=[1],mov=Vector3(0,+1.6e-5,0),rot=Quaternion().Identity,setVelocities=False )] + O.engines[1:] print 'Reloading (press enter)' raw_input() O.run(500,True) #### define simulation unloading O.engines = [StepDisplacer( ids=[1],mov=Vector3(0,-1.45e-5,0),rot=Quaternion().Identity,setVelocities=False )] + O.engines[1:] print 'Reunloading (press enter)' raw_input() O.run(10,True) #### define simulation reloading O.engines = [StepDisplacer( ids=[1],mov=Vector3(0,+1.6e-5,0),rot=Quaternion().Identity,setVelocities=False )] + O.engines[1:] print 'Reloading (press enter)' raw_input() O.run(500,True) #### to see it v=qt.Controller() v=qt.View() rr=qt.Renderer() rr.intrAllWire=True trunk-2018.02b/examples/test/WireMatPM/net-2part-displ.py000066400000000000000000000061361324306050200230750ustar00rootroot00000000000000# -*- coding: utf-8 -*- # encoding: utf-8 from yade import ymport, qt, plot ## definition of some colors for colored text output in terminal BLUE = '\033[94m' GREEN = '\033[92m' YELLOW = '\033[93m' RED = '\033[91m' BLACK = '\033[0m' #### short description of script print BLUE+'Simple test for two particles to test contact law with '+RED+'StepDisplacer'+BLUE+'.'+BLACK #### define parameters for the net # mesh opening size mos = 80./1000. a = mos/sqrt(3) # wire diameter d = 2.7/1000. # particle radius radius = d*5. # define piecewise lineare stress-strain curve strainStressValues=[(0.0019230769,2.5e8),(0.0192,3.2195e8),(0.05,3.8292e8),(0.15,5.1219e8),(0.25,5.5854e8),(0.3,5.6585e8),(0.35,5.6585e8)] # elastic material properties particleVolume = 4./3.*pow(radius,3)*pi particleMass = 3.9/1000. density = particleMass/particleVolume young = strainStressValues[0][1] / strainStressValues[0][0] poisson = 0.3 #### material definition netMat = O.materials.append(WireMat(young=young,poisson=poisson,frictionAngle=radians(30),density=density,isDoubleTwist=False,diameter=d,strainStressValues=strainStressValues,lambdaEps=0.4,lambdak=0.21)) #### create boddies, default: dynamic=True O.bodies.append( sphere([0,0,0], radius, wire=False, color=[1,0,0], highlight=False, material=netMat, fixed=True) ) O.bodies.append( sphere([0,a,0], radius, wire=False, color=[0,1,0], highlight=False, material=netMat) ) FixedSphere=O.bodies[0] MovingSphere=O.bodies[1] FixedSphere.dynamic=False MovingSphere.dynamic=False #### define addPlotData def addPlotData(): if O.iter < 1: plot.addData( Fn=0., un=0. ) #plot.saveGnuplot('net-2part-displ') else: try: i=O.interactions[FixedSphere.id,MovingSphere.id] plot.addData( Fn=i.phys.normalForce.norm(), un=(O.bodies[1].state.pos[1]-O.bodies[0].state.pos[1])-a ) #plot.saveGnuplot('net-2part-displ') except: print "No interaction!" O.pause() #### define simulation to create link interactionRadius=2. O.engines = [ ForceResetter(), InsertionSortCollider( [Bo1_Sphere_Aabb(aabbEnlargeFactor=interactionRadius,label='aabb')] ), InteractionLoop( [Ig2_Sphere_Sphere_ScGeom(interactionDetectionFactor=interactionRadius,label='Ig2ssGeom')], [Ip2_WireMat_WireMat_WirePhys(linkThresholdIteration=1,label='interactionPhys')], [Law2_ScGeom_WirePhys_WirePM(linkThresholdIteration=1,label='interactionLaw')] ), NewtonIntegrator(damping=0.), PyRunner(initRun=True,iterPeriod=1,command='addPlotData()') ] #### plot some results plot.plots={'un':('Fn',)} plot.plot(noShow=False, subPlots=False) #### create link (no time step needed since loading is involved in this step) O.step() # create link (cohesiveTresholdIteration=1) #### initializes now the interaction detection factor aabb.aabbEnlargeFactor=-1. Ig2ssGeom.interactionDetectionFactor=-1. #### time step definition ## no time step definition is required since setVelocities=False in StepDisplacer #### define simulation loading O.engines = [StepDisplacer( ids=[1],mov=Vector3(0,+1e-5,0),rot=Quaternion().Identity,setVelocities=False )] + O.engines #### to see it v=qt.Controller() v=qt.View() rr=qt.Renderer() rr.intrAllWire=True trunk-2018.02b/examples/test/WireMatPM/net-2part-strain.py000066400000000000000000000065061324306050200232630ustar00rootroot00000000000000# -*- coding: utf-8 -*- # encoding: utf-8 from yade import ymport, qt ## definition of some colors for colored text output in terminal BLUE = '\033[94m' GREEN = '\033[92m' YELLOW = '\033[93m' RED = '\033[91m' BLACK = '\033[0m' #### short description of script print BLUE+'Simple test for two particles to test contact law with '+RED+'UniaxialStrainer'+BLUE+'.'+BLACK #### define parameters for the net # mesh opening size mos = 80./1000. a = mos/sqrt(3) # wire diameter d = 2.7/1000. # particle radius radius = d*5. # define piecewise lineare stress-strain curve strainStressValues=[(0.0019230769,2.5e8),(0.0192,3.2195e8),(0.05,3.8292e8),(0.15,5.1219e8),(0.25,5.5854e8),(0.3,5.6585e8),(0.35,5.6585e8)] # elastic material properties particleVolume = 4./3.*pow(radius,3)*pi particleMass = 3.9/1000. density = particleMass/particleVolume young = strainStressValues[0][1] / strainStressValues[0][0] poisson = 0.3 #### material definition netMat = O.materials.append(WireMat(young=young,poisson=poisson,frictionAngle=radians(30),density=density,isDoubleTwist=False,diameter=d,strainStressValues=strainStressValues,lambdaEps=0.4,lambdak=0.21)) #### create boddies, default: dynamic=True O.bodies.append( sphere([0,0,0], radius, wire=False, color=[1,0,0], highlight=False, material=netMat) ) O.bodies.append( sphere([0,a,0], radius, wire=False, color=[0,1,0], highlight=False, material=netMat) ) FixedSphere=O.bodies[0] MovingSphere=O.bodies[1] FixedSphere.dynamic=True MovingSphere.dynamic=True #### initialize values for UniaxialStrainer bb = uniaxialTestFeatures(axis=1) negIds,posIds,axis,crossSectionArea=bb['negIds'],bb['posIds'],bb['axis'],bb['area'] strainRateTension = 1./a setSpeeds = True #### define simulation to create link interactionRadius=2. O.engines = [ ForceResetter(), InsertionSortCollider( [Bo1_Sphere_Aabb(aabbEnlargeFactor=interactionRadius,label='aabb')] ), InteractionLoop( [Ig2_Sphere_Sphere_ScGeom(interactionDetectionFactor=interactionRadius,label='Ig2ssGeom')], [Ip2_WireMat_WireMat_WirePhys(linkThresholdIteration=1,label='interactionPhys')], [Law2_ScGeom_WirePhys_WirePM(linkThresholdIteration=1,label='interactionLaw')] ), NewtonIntegrator(damping=0.) ] #### create link (no time step needed since loading is involved in this step) O.step() # create cohesive link (cohesiveTresholdIteration=1) #### initializes now the interaction detection factor aabb.aabbEnlargeFactor=-1. Ig2ssGeom.interactionDetectionFactor=-1. ## time step definition O.dt = 1e-5 ## critical time step proposed by Bertrand #O.dt = 0.2*sqrt(particleMass/(2.*O.interactions[0,1].phys.kn)) #### plot some results from yade import plot plot.plots={'un':('Fn',)} plot.plot(noShow=False, subPlots=False) def addPlotData(): try: i=O.interactions[FixedSphere.id,MovingSphere.id] plot.addData( Fn=i.phys.normalForce.norm(), un=(O.bodies[1].state.pos[1]-O.bodies[0].state.pos[1])-a ) #plot.saveGnuplot('net-2part-strain') except: print "No interaction!" O.pause() #### define simulation O.engines += [UniaxialStrainer(strainRate=strainRateTension,axis=axis,asymmetry=1,posIds=posIds,negIds=negIds,crossSectionArea=crossSectionArea,blockDisplacements=True,blockRotations=True,setSpeeds=setSpeeds,label='strainer')] + [PyRunner(initRun=True,iterPeriod=1,command='addPlotData()')] #### to see it v=qt.Controller() v=qt.View() rr=qt.Renderer() rr.intrAllWire=True trunk-2018.02b/examples/test/batch/000077500000000000000000000000001324306050200170445ustar00rootroot00000000000000trunk-2018.02b/examples/test/batch/params.table000066400000000000000000000003001324306050200213310ustar00rootroot00000000000000!OMP_NUM_THREADS important! unimportant this! more # comment # comment and empty line 2 51 'hummm' 3.1415 'a' 1 51 'paaah' = 'b' = 52 = 'fff' = 'c' = = 'not' = 'd' 3 52 'yes' 2.71828 'e' trunk-2018.02b/examples/test/batch/sim.py000066400000000000000000000007321324306050200202100ustar00rootroot00000000000000 readParamsFromTable(unknownOk=True, important=6, unimportant='foo', this=-1, notInTable='notInTable' ) from yade.params import table print O.tags['description'] print 'important',table.important print 'unimportant',table.unimportant print O.tags['params'].replace(',','_'); print O.tags['defaultParams'] import time #time.sleep(5) O.engines=[PyRunner(command='time.sleep(.005)',iterPeriod=1)] O.run(1000,True) print 'finished' import sys sys.stdout.flush() sys.exit(0) trunk-2018.02b/examples/test/beam-l6geom.py000066400000000000000000000026341324306050200204350ustar00rootroot00000000000000# # Demonstrate L6Geom class with suspended hook-shaped beam fixed at one side, subject to gravity # import numpy # radius, number and distance of spheres rad,num=1,6; dist=1.9999*rad # one arm O.bodies.append([sphere((0,y,0),rad,wire=True) for y in numpy.arange(0,2*num-1,dist)]) # the lateral arm O.bodies.append([sphere((x,(num-1)*2*rad,0),rad,wire=True) for x in numpy.arange(dist,1+num/2,dist)]) # support sphere O.bodies[0].state.blockedDOFs='xyzXYZ' # small dt to see in realtime how it swings; real critical is higher, but much less than p-wave O.dt=.01*PWaveTimeStep() O.engines=[ ForceResetter(), InsertionSortCollider([Bo1_Sphere_Aabb()]), InteractionLoop([Ig2_Sphere_Sphere_L6Geom(distFactor=-1)],[Ip2_FrictMat_FrictMat_FrictPhys()],[Law2_L6Geom_FrictPhys_Linear(charLen=1)]), NewtonIntegrator(damping=0.1,gravity=(0,0,-9.81)), ] O.saveTmp() try: from yade import qt v=qt.View(); v.axes=True v.viewDir=Vector3(-0.888,-0.2,-0.4144) v.eyePosition=Vector3(18.16,8.235,5.12) v.sceneRadius=7.5 v.upVector=Vector3(-0.46,0.3885,0.798) v.screenSize=(900,900) rr=qt.Renderer() rr.intrGeom=True Gl1_L6Geom.phiScale=30; Gl1_L3Geom.uScale=20 #O.engines=O.engines+[ # qt.SnapshotEngine(fileBase=O.tmpFilename(),label='snapper',iterPeriod=300,deadTimeout=20), # PyRunner(iterPeriod=330000,command='makeVideo(snapper.snapshots,out="beam-l6geom.avi"); snapper.dead=True; O.pause()') #] except ImportError: pass O.run() trunk-2018.02b/examples/test/callback.py000066400000000000000000000011051324306050200200660ustar00rootroot00000000000000O.materials.append(FrictMat(young=30e9,poisson=.2,density=4000,frictionAngle=.5)) O.bodies.append([ sphere((0,0,0),1,fixed=True), sphere((0,0,2.1),1) ]) O.engines=[ ForceResetter(), InsertionSortCollider([Bo1_Sphere_Aabb()]), InteractionLoop( [Ig2_Sphere_Sphere_ScGeom()], [Ip2_FrictMat_FrictMat_FrictPhys()], [Law2_ScGeom_FrictPhys_CundallStrack()] ), NewtonIntegrator(label='newton',gravity=(0,0,-9.81)) ] O.dt=PWaveTimeStep() disp=typedEngine('InteractionLoop') disp.callbacks=[SumIntrForcesCb()] #newton.callbacks=[SumBodyForcesCb()] print O.numThreads O.step() trunk-2018.02b/examples/test/clump-facet.py000066400000000000000000000017371324306050200205450ustar00rootroot00000000000000O.engines=[ ForceResetter(), InsertionSortCollider([ Bo1_Sphere_Aabb(), Bo1_Facet_Aabb(), ]), InteractionLoop( [Ig2_Sphere_Sphere_ScGeom(),Ig2_Facet_Sphere_ScGeom()], [Ip2_FrictMat_FrictMat_FrictPhys()], [Law2_ScGeom_FrictPhys_CundallStrack()] ), NewtonIntegrator(), ] s1 = sphere((2,2,0),1) s2 = sphere((-2,2,0),1,fixed=True) f1 = facet(((0,0,-2),(0,0,2),(+5,0,0)),wire=False) f2 = facet(((0,0,-2),(0,0,2),(-5,0,0)),wire=False) # needs to assign (any) nonzero mass and inertia to facets. Total mass and inertia of clump itself can be assigned independently f1.state.mass = f2.state.mass = 1 f2.state.inertia = f2.state.inertia = (1,1,1) O.bodies.append((s1,s2)) clumpId,facetsId = O.bodies.appendClumped((f1,f2)) s = O.bodies[clumpId].state # now we can assign arbitrary inertia of the overall clump s.mass = 5000 s.inertia = 10000*Vector3(1,1,1) s.blockedDOFs = 'xyzXY' O.forces.setPermT(clumpId,(0,0,1e1)) O.dt = 5e-5 try: from yade import qt qt.View() except: pass trunk-2018.02b/examples/test/clump.py000066400000000000000000000017171324306050200174630ustar00rootroot00000000000000O.engines=[ ForceResetter(), InsertionSortCollider([ Bo1_Sphere_Aabb(), Bo1_Box_Aabb(), ]), InteractionLoop( [Ig2_Sphere_Sphere_ScGeom(),Ig2_Box_Sphere_ScGeom()], [Ip2_FrictMat_FrictMat_FrictPhys()], [Law2_ScGeom_FrictPhys_CundallStrack()] ), NewtonIntegrator(damping=.2,exactAsphericalRot=True,gravity=[0,0,-9.81]) ] # support O.bodies.append(box([0,0,-1.5],[3,3,.2],fixed=True)) # stand-alone sphere O.bodies.append(sphere([0,0,0],.5)) # clumps relPos=[(0,-.5,-.5),(0,.5,0),(.5,0,0),(0,0,.5)] coords=[(-2,0,0),(2,0,0),(0,2,0),(0,-2,0)] for i,cc in enumerate(coords): # This shorthand command does something like this: # O.bodies.appendClumped([sphere(...),sphere(...),sphere(...)]) # and returns tuple of clumpId,[bodyId1,bodyId2,bodyId3] clump,spheres=O.bodies.appendClumped([sphere([relPos[j][0]+coords[i][0],relPos[j][1]+coords[i][1],relPos[j][2]+coords[i][2]],.5) for j in range(0,i+1)]) print clump,spheres O.dt=PWaveTimeStep() O.saveTmp('init') trunk-2018.02b/examples/test/clumpPack.py000066400000000000000000000013411324306050200202530ustar00rootroot00000000000000# create a few clump configurations by hand from yade import pack c1=pack.SpherePack([((0,0,0),.5),((.5,0,0),.5),((0,.5,0),.3)]) c2=pack.SpherePack([((0,0,0),.5),((.7,0,0),.3),((.9,0,0),.2)]) sp=pack.SpherePack() print 'Generated # of clumps:',sp.makeClumpCloud((0,0,0),(15,15,15),[c1,c2],periodic=False) sp.toSimulation() O.bodies.append(wall(position=0,axis=2)) O.engines=[ #SubdomainBalancer(), ForceResetter(), InsertionSortCollider([Bo1_Sphere_Aabb(),Bo1_Wall_Aabb()]), InteractionLoop( [Ig2_Sphere_Sphere_ScGeom(),Ig2_Wall_Sphere_ScGeom()], [Ip2_FrictMat_FrictMat_FrictPhys()], [Law2_ScGeom_FrictPhys_CundallStrack()] ), NewtonIntegrator(damping=.4,gravity=(0,0,-100)) ] O.dt=.7*PWaveTimeStep() O.saveTmp() O.step() trunk-2018.02b/examples/test/collider-stride-triax.py000066400000000000000000000021461324306050200225520ustar00rootroot00000000000000""" Playground for tuning collider strides depending on maximum velocity. """ from yade import timing import os.path loadFrom='/tmp/triax.xml' #if not os.path.exists(loadFrom): TriaxialTest(numberOfGrains=2000,noFiles=True).generate(loadFrom) O.load(loadFrom) collider=typedEngine('InsertionSortCollider') newton=typedEngine('NewtonIntegrator') # use striding; say "if 0:" to disable striding and compare to regular runs if 1: # length by which bboxes will be made larger collider.verletDist=.2*O.bodies[100].shape.radius O.step() # filter out initialization O.timingEnabled=True totalTime=0 # run a few times 500 steps, show timings to see what is the trend # notably, the percentage of collider time should decrease as the max velocity decreases as well for i in range(0,5): O.run(1000,True) timing.stats() totalTime+=sum([e.execTime for e in O.engines]) print 'Number of interactions: %d (real ratio: %g)'%(len(O.interactions),float(O.interactions.countReal())/len(O.interactions)) print '=======================================================' timing.reset() print 'Total time: %g s'%(totalTime/1e9) #quit() trunk-2018.02b/examples/test/collider-stride.py000066400000000000000000000015131324306050200214220ustar00rootroot00000000000000# # check visually whether swept bounding boxes work as expected # mat=O.materials.append(FrictMat(young=30e9,poisson=.2,density=4000,frictionAngle=.5)) O.bodies.append([ facet([[1,0,0],[-1,2,0],[-1,-2,0]],material=mat), sphere([0,0,2],.5,color=(0,1,0),material=mat) ]) O.bodies[1].state.vel=Vector3(0,0,-1) O.dt=PWaveTimeStep() O.engines=[ ForceResetter(), InsertionSortCollider([Bo1_Sphere_Aabb(),Bo1_Facet_Aabb()],label='isc'), InteractionLoop( [Ig2_Facet_Sphere_ScGeom(),Ig2_Sphere_Sphere_ScGeom()], [Ip2_FrictMat_FrictMat_FrictPhys()], [Law2_ScGeom_FrictPhys_CundallStrack()], ), NewtonIntegrator(damping=0,gravity=[0,0,-100]) ] from yade import timing,qt try: renderer=qt.Renderer() #renderer['Body_bounding_volume']=True qt.Controller(); qt.View() except ImportError: pass O.timingEnabled=True O.saveTmp() O.step() trunk-2018.02b/examples/test/collider-sweep-simple.py000066400000000000000000000013251324306050200225430ustar00rootroot00000000000000O.bodies.append(sphere([0,0,10],.5)) #O.bodies.append(sphere([0,0,0],.5,fixed=True)) O.engines=[ ForceResetter(), InsertionSortCollider([Bo1_Sphere_Aabb(),Bo1_Facet_Aabb()],label='collider'), InteractionLoop( [Ig2_Sphere_Sphere_ScGeom(),Ig2_Facet_Sphere_ScGeom()], [Ip2_FrictMat_FrictMat_FrictPhys()], [Law2_ScGeom_FrictPhys_CundallStrack()], ), NewtonIntegrator(damping=.1,gravity=[0,0,-1e4]) ] collider.verletDist=.5 O.dt=8e-2*PWaveTimeStep() O.saveTmp() from yade import timing O.timingEnabled=True from yade import qt r=qt.Renderer() #r['Body_bounding_volume']=True v=qt.View(); qt.Controller() v.ortho=True; #v.viewDir=O.bodies[0].phys.pos; v.lookAt=O.bodies[0].phys.pos; v.upVector=(0,0,1); O.run(2,True) trunk-2018.02b/examples/test/combined-kinematic-engine.py000066400000000000000000000030621324306050200233230ustar00rootroot00000000000000box = geom.facetBox(center=(0,0,0),extents=(1,2,3), color=(0,1,0)) O.bodies.append(box) ids = [b.id for b in box] # set this parameter according to your computer power to make the simulation vizualization slower of faster coeff = 5.0 nIterPerOneCycle = int(50000/coeff) vel = 10000*coeff angVel = 8000*coeff # function for changing motion, sets transEngine.translationAxis, rotEngine.angularVelocity and rotEngine.zeroPoint def updateKinematicEngines(): part = (O.iter / nIterPerOneCycle) % 4 if part == 0: # fist part v = Vector3(1,0,0) av = 0 elif part == 1: # second part v = Vector3(0,1,0) av = angVel elif part == 2: # third part v = Vector3(-1,0,0) av = -2*angVel elif part == 3: # fourth part v = Vector3(0,-1,0) av = angVel transEngine.translationAxis = v rotEngine.angularVelocity = av rotEngine.zeroPoint += v*vel*O.dt O.engines = [ ForceResetter(), PyRunner(iterPeriod=1, command="updateKinematicEngines()" ), # construct CombinedKinematicEngine with label, add (with +) TranslationEngine and RotationEngine CombinedKinematicEngine(ids=ids,label='combEngine') + TranslationEngine(translationAxis=(1,0,0),velocity=vel) + RotationEngine(rotationAxis=(0,0,1), angularVelocity=0, rotateAroundZero=True, zeroPoint=(0,0,0)), NewtonIntegrator(), ] # get TranslationEngine and RotationEngine from CombinedKinematicEngine transEngine, rotEngine = combEngine.comb[0], combEngine.comb[1] print print 'transEngine:', transEngine print 'rotEngine:', rotEngine print try: from yade import qt qt.View() except: print 'No graphics, sorry..' O.run() trunk-2018.02b/examples/test/energy.py000066400000000000000000000032421324306050200176270ustar00rootroot00000000000000from yade import pack,plot readParamsFromTable(useL3Geom=True,nonviscDamp=0,frictAngle=0,useClumps=False,noTableOk=True) from yade.params import table if 1: sp=pack.SpherePack(); # bunch of balls, with an infinite plane just underneath if not table.useClumps: sp.makeCloud((0,0,0),(1,1,1),.05,.5); # use clumps of 2 spheres instead, to have rotation without friction else: sp.makeClumpCloud((0,0,0),(1,1,1),[pack.SpherePack([((0,0,0),.05),((0,0,.08),.02)])],periodic=False) sp.toSimulation() else: O.bodies.append(sphere((0,0,2),radius=.5)) # one single bouncing ball O.bodies.append(wall(position=0,axis=2,sense=1)) O.engines=[ ForceResetter(), InsertionSortCollider([Bo1_Sphere_Aabb(),Bo1_Wall_Aabb()]), InteractionLoop([Ig2_Sphere_Sphere_L3Geom(approxMask=0),Ig2_Wall_Sphere_L3Geom(approxMask=0)],[Ip2_FrictMat_FrictMat_FrictPhys(frictAngle=table.frictAngle)],[Law2_L3Geom_FrictPhys_ElPerfPl(noSlip=False,noBreak=False)]) if table.useL3Geom else InteractionLoop([Ig2_Sphere_Sphere_ScGeom(),Ig2_Wall_Sphere_ScGeom()],[Ip2_FrictMat_FrictMat_FrictPhys(frictAngle=table.frictAngle)],[Law2_ScGeom_FrictPhys_CundallStrack()]), NewtonIntegrator(damping=table.nonviscDamp,kinSplit=True,gravity=(0,0,-9.81)), PyRunner(iterPeriod=1,command='addPlotData()'), ] O.dt=.1*PWaveTimeStep() def addPlotData(): Ek,maxId=kineticEnergy(findMaxId=True) plot.addData(i=O.iter,total=O.energy.total(),maxId=maxId,**O.energy) # turn on energy tracking O.trackEnergy=True O.saveTmp() # the callable should return list of strings, plots will be updated automatically plot.plots={'i':[O.energy.keys,None,'total']} #from yade import timing #O.timingEnabled=True #timing.stats() plot.plot(subPlots=False) trunk-2018.02b/examples/test/exact-rot-facet.py000066400000000000000000000014701324306050200213250ustar00rootroot00000000000000# -*- encoding=utf-8 -*- ## ## TODO: verify that the code for facet & hasShear is physically correct! ## O.engines=[ ForceResetter(), InsertionSortCollider([Bo1_Sphere_Aabb(),Bo1_Box_Aabb(),Bo1_Facet_Aabb()]), IGeomDispatcher([ Ig2_Sphere_Sphere_ScGeom(), Ig2_Facet_Sphere_ScGeom(), ]), IPhysDispatcher([Ip2_FrictMat_FrictMat_FrictPhys()]), ElasticContactLaw(), RotationEngine(ids=[1],rotationAxis=[1,0,0],angularVelocity=.01), NewtonIntegrator(damping=0.2) ] scale=.1 O.bodies.append(facet([[scale,0,0],[-scale,-scale,0],[-scale,scale,0]],fixed=True,color=[1,0,0])) O.bodies.append(sphere([0,0,.99*scale],1*scale,color=[0,1,0],wire=True,fixed=True)) O.dt=.4*PWaveTimeStep() from yade import qt qt.View() renderer=qt.Renderer() renderer.intrGeom=True qt.Controller() O.step(); O.step(); O.step() O.run(20000) trunk-2018.02b/examples/test/exact-rot.py000066400000000000000000000015141324306050200202440ustar00rootroot00000000000000# -*- encoding=utf-8 -*- O.engines=[ ForceResetter(), InsertionSortCollider([Bo1_Sphere_Aabb(),Bo1_Box_Aabb()]), IGeomDispatcher([ Ig2_Sphere_Sphere_ScGeom(), Ig2_Facet_Sphere_ScGeom(), ]), IPhysDispatcher([Ip2_FrictMat_FrictMat_FrictPhys()]), ElasticContactLaw(), RotationEngine(ids=[1],rotationAxis=(1,0,0),angularVelocity=.01), RotationEngine(ids=[0],rotationAxis=(1,1,1),angularVelocity=-.02), NewtonIntegrator(damping=.2,gravity=(0,0,-9.81)) ] O.bodies.append(sphere([0,0,0],1,fixed=True,color=[1,0,0],wire=True)) O.bodies.append(sphere([0,sqrt(2),sqrt(2)],1,color=[0,1,0],wire=True)) O.dt=.01*PWaveTimeStep() O.saveTmp() #o.run(100000); o.wait(); print o.iter/o.realtime,'iterations/sec' from yade import qt qt.View() renderer=qt.Renderer() renderer.intrGeom=True qt.Controller() O.step(); O.step(); O.step() O.run(20000) trunk-2018.02b/examples/test/facet-box.py000066400000000000000000000003171324306050200202060ustar00rootroot00000000000000O.bodies.append(geom.facetBox((0,0,0),(10,5,2))) O.bodies.append(geom.facetBox((0,0,10),(10,5,2),wallMask=0b110011,wire=False)) O.bodies.append(geom.facetBox((0,10,5),(10,5,2),wallMask=0b101010,wire=False)) trunk-2018.02b/examples/test/facet-sphere-ViscElBasic-peri.py000066400000000000000000000023361324306050200237710ustar00rootroot00000000000000# -*- coding: utf-8 # Testing facet-sphere interaction in periodic case. # Pass, if the sphere is rolling from left to right through the period. sphereRadius=0.1 tc=0.001# collision time en=0.3 # normal restitution coefficient es=0.3 # tangential restitution coefficient density=2700 frictionAngle=radians(35)# facetMat=O.materials.append(ViscElMat(frictionAngle=frictionAngle,tc=tc,en=en,et=es)) sphereMat=O.materials.append(ViscElMat(density=density,frictionAngle=frictionAngle,tc=tc,en=en,et=es)) #floor n=5 s=1./n for i in range(0,n): for j in range(0,n): O.bodies.append([ facet( [(i*s,j*s,0.1),(i*s,(j+1)*s,0.1),((i+1)*s,(j+1)*s,0.1)],material=facetMat), facet( [(i*s,j*s,0.1),((i+1)*s,j*s,0.1),((i+1)*s,(j+1)*s,0.1)],material=facetMat), ]) # Spheres sphId=O.bodies.append([sphere((0.5,0.5,0.2), 0.1, material=sphereMat)]) O.bodies[sphId[-1]].state.vel=(0.5,0,0) ## Engines O.engines=[ ForceResetter(), InsertionSortCollider([Bo1_Sphere_Aabb(),Bo1_Facet_Aabb()]), InteractionLoop( [Ig2_Facet_Sphere_ScGeom()], [Ip2_ViscElMat_ViscElMat_ViscElPhys()], [Law2_ScGeom_ViscElPhys_Basic()], ), NewtonIntegrator(damping=0,gravity=[0,0,-9.81]), ] O.periodic=True O.cell.refSize=(1,1,1) O.dt=.01*tc O.saveTmp() trunk-2018.02b/examples/test/facet-sphere-ViscElBasic.py000066400000000000000000000026351324306050200230360ustar00rootroot00000000000000# -*- coding: utf-8 -*- # Testing facet-sphere interaction. # A facet is rotated around Z axis. Test pass, if a sphere at (0,0) position is not moving (because in this case no transfer moment from the facet to the sphere), but a sphere at facet's edge moves with the facet (for this sphere blocked the rotation DOFs in order to remove rolling). ## PhysicalParameters Density=2400 frictionAngle=radians(35) tc = 0.001 en = 0.3 es = 0.3 ## Import wall's geometry facetMat=O.materials.append(ViscElMat(frictionAngle=frictionAngle,tc=tc,en=en,et=es)) sphereMat=O.materials.append(ViscElMat(density=Density,frictionAngle=frictionAngle,tc=tc,en=en,et=es)) facetId=O.bodies.append(facet( [ (-1,0,0), (1,1,0), (1,-1,0)], material=facetMat,color=(1,0,0))) sphIds=O.bodies.append([ sphere( (0,0,0.1),0.1, material=sphereMat,color=(0,1,0)), sphere( (0.9,0,0.1),0.1, material=sphereMat,color=(0,1,0)) ]) O.bodies[sphIds[1]].state.blockedDOFs='XYZ' ## Timestep O.dt=.1*tc ## Engines O.engines=[ ForceResetter(), InsertionSortCollider([Bo1_Sphere_Aabb(),Bo1_Facet_Aabb()]), InteractionLoop( [Ig2_Sphere_Sphere_ScGeom(), Ig2_Facet_Sphere_ScGeom()], [Ip2_ViscElMat_ViscElMat_ViscElPhys()], [Law2_ScGeom_ViscElPhys_Basic()], ), NewtonIntegrator(damping=0,gravity=[0,0,-9.81]), RotationEngine(ids=[facetId],rotationAxis=[0,0,1],rotateAroundZero=True,angularVelocity=0.1) ] from yade import qt qt.View() O.saveTmp() #O.run() trunk-2018.02b/examples/test/facet-sphere.py000066400000000000000000000021741324306050200207070ustar00rootroot00000000000000# -*- coding: utf-8 -*- # © Václav Šmilauer # # Test case for sphere-facet interaction. O.engines=[ ForceResetter(), InsertionSortCollider([Bo1_Sphere_Aabb(),Bo1_Facet_Aabb()]), #SpatialQuickSortCollider(), InteractionLoop( [Ig2_Facet_Sphere_ScGeom()], [Ip2_FrictMat_FrictMat_FrictPhys()], [Law2_ScGeom_FrictPhys_CundallStrack()], ), NewtonIntegrator(damping=0.01,gravity=[0,0,-10]), ] O.bodies.append([ facet([[-1,-1,0],[1,-1,0],[0,1,0]],fixed=True,color=[1,0,0]), facet([[1,-1,0],[0,1,0,],[1,.5,.5]],fixed=True) ]) import random for i in range(0,100): s=sphere([random.gauss(0,1),random.gauss(0,1),random.uniform(1,2)],random.uniform(.02,.05)) s.state.vel=Vector3(random.gauss(0,.1),random.gauss(0,.1),random.gauss(0,.1)) O.bodies.append(s) O.dt=PWaveTimeStep() O.run() O.saveTmp('init') if 0: from yade import qt renderer=qt.Renderer() renderer['Interaction_geometry']=True qt.Controller() #except ImportError: pass O.saveTmp() O.loadTmp() if 0: O.timingEnabled=True from yade import timing for i in range(4): timing.reset() O.loadTmp('init') O.run(100000,True) timing.stats() quit() trunk-2018.02b/examples/test/flat-collider.py000066400000000000000000000016341324306050200210620ustar00rootroot00000000000000from yade import pack,timing #O.bodies.append([ sphere((0.2,0,0),.5,fixed=True), sphere((0.2,0.0,1.01),.5), ]) O.bodies.append(pack.regularHexa(pack.inAlignedBox((0,0,0),(10,10,1)),radius=.5,gap=0,fixed=True)) O.bodies.append(pack.regularOrtho(pack.inAlignedBox((3,3,3),(7,7,4)),radius=.05,gap=0)) O.engines=[ ForceResetter(), FlatGridCollider(step=.2,aabbMin=(0,0,0),aabbMax=(10,10,5),verletDist=0.005), # InsertionSortCollider([Bo1_Sphere_Aabb()],sweepLength=0.005), InteractionLoop( [Ig2_Sphere_Sphere_ScGeom()], [Ip2_FrictMat_FrictMat_FrictPhys()], [Law2_ScGeom_FrictPhys_CundallStrack()], ), NewtonIntegrator(damping=0.4,gravity=[0,0,-10]), ] O.dt=.6*PWaveTimeStep() O.saveTmp() #O.step() #while True: # O.step() # if len(O.interactions)>0 or O.bodies[1].state.pos[2]<.97: break print('This will take a while, drink a coffee ;)') O.timingEnabled=True O.run(5000,True) timing.stats() import sys #sys.exit(0) trunk-2018.02b/examples/test/force-move.py000066400000000000000000000003711324306050200204000ustar00rootroot00000000000000O.bodies.append(sphere([0,0,0],1,fixed=False)) O.engines=[ ForceResetter(), PyRunner(command='O.forces.addMove(0,(1e-2,0,0))',iterPeriod=1), NewtonIntegrator() ] for i in xrange(0,20): O.step() print O.forces.f(0),O.bodies[0].state.pos quit() trunk-2018.02b/examples/test/force-network-video.py000066400000000000000000000024411324306050200222270ustar00rootroot00000000000000print 30*'*'+' WARNING '+30*'*'+'\nFor hardware/driver/...? reasons related to 3d, this script might\nsometimes crash when the first snapshot is taken with message such as\n\n\tQGLContext::makeCurrent(): Failed.\n\nor\n\n\tFatal IO error 11 (Resource temporarily unavailable) on X server :0.0.\n\nA workaround is to open the 3d view by hand, rather than having it\nopen by SnapshotEngine automatically when the first snapshot is\nabout to be taken. Sometimes only the message is displayed,\nwithout crash.\n'+25*'*'+' This is a known bug. '+25*'*' TriaxialTest(noFiles=True).load() from yade import qt O.engines=O.engines+[ qt.SnapshotEngine(fileBase=O.tmpFilename(),label='snapshotter',iterPeriod=5,ignoreErrors=False), PyRunner(iterPeriod=500,command='finito()') ] rr=qt.Renderer() rr.shape,rr.intrPhys=False,True def finito(): """This function will be called after 500 steps. Since SnapshotEngine waits for a new 3d view to open, it must run after the script has finished and the command line appears (see https://bugs.launchpad.net/yade/+bug/622669). For that reason, O.run() is at the end of the script and this function will be called once we want to exit really. """ makeVideo(snapshotter.snapshots,out='/tmp/video.avi') print "Video saved in /tmp/video.avi" import sys sys.exit(0) O.run() trunk-2018.02b/examples/test/genCylLSM.py000066400000000000000000000034261324306050200201370ustar00rootroot00000000000000""" This example shows how LSMGenGeo generates Cylinder with given parameters. Be sure LSMGenGeo library is installed. The result is: 2 files: "cyl.geo" is the geometry file which can be imported into YADE with ymport.gengeoFile() function "cyl.vtk" is the VTK-filed which can be opened by any VTK-based software, for example Paraview spheres, imported into the YADE simulation, according to generated geometry http://www.access.edu.au/lsmgengeo_python_doc/current/pythonapi/html/GenGeo-module.html https://svn.esscc.uq.edu.au/svn/esys3/lsm/contrib/LSMGenGeo/ """ from yade import pack,ymport try: from GenGeo import * except ImportError: from gengeo import * import sys fileName="cyl" radius=15 length=100 minRadius=0.5 maxRadius=2.0 origin=Vector3(0.0,0.0,0.0) axis=Vector3(0.0,0.0,1.0) minPoint=Vector3(-radius,-radius,-length) maxPoint=Vector3(radius,radius,length) box=CylinderVol ( origin = origin, axis = axis, radius = radius, length = 2.0*length ) mntable=MNTable3D ( minPoint=minPoint, maxPoint=maxPoint, gridSize=2.5*maxRadius, numGroups=1 ) packer=InsertGenerator3D( minRadius=minRadius, maxRadius=maxRadius, insertFails=1000, maxIterations=1000, tolerance=1.0e-6, seed=0 ) packer.generatePacking ( volume=box, ntable=mntable, groupID=0 ) #Write data to files mntable.write(fileName+".geo",1) mntable.write(fileName+".vtu",2) #Add material O.materials.append(FrictMat(young=1e9,poisson=.25,frictionAngle=0.5,density=1e3)) #Parameters, which will be passed into spheres and facets creators kw={'material':0} #Import the GenGeo geometry directly into the YADE simulation O.bodies.append(ymport.gengeo(mntable,shift=[-1.0,-1.0,-1.0],scale=2.0,color=(1,1,0),**kw)) try: from yade import qt qt.Controller() qt.View() except ImportError: pass trunk-2018.02b/examples/test/helix.py000066400000000000000000000007141324306050200174500ustar00rootroot00000000000000# script for testing InterpolatingHelixEngine: sphere going in a sphere-like motion around bar O.bodies.append([box([0,0,0],[.005,.005,1],fixed=True),sphere([0,.1,-1],.04,fixed=False)]) O.engines=[ InterpolatingHelixEngine(ids=[1],times=[10,20,30,40,50,60,70,80,90,100],angularVelocities=[1,2,3,4,5,3,1,-1,-3,0],rotationAxis=[0,0,1],zeroPoint=[0,0,0], wrap=True,slope=.003,label='spiral'), ] O.dt=4e-6 O.saveTmp('initial') from yade import qt qt.Controller() trunk-2018.02b/examples/test/interpolating-force.py000066400000000000000000000045131324306050200223130ustar00rootroot00000000000000# encoding: utf-8 # 2009 Václav Šmilauer # script showing how to use InterpolatingDirectedForceEngine, # simply pushing one free sphere agains a fixed one with varying force # # The force evolution is sine wave, but it could really be any data from numpy import arange nPulses=4 # run for total of 4 pulses freq=10. # 5 pulses per second times=arange(0,1/freq,.01/freq) # generate 100 points equally distributed over the period (can be much more) maxMag=1e5 # maximum magnitude of applied force magnitudes=[.5*maxMag*(sin(t*(freq*2*pi))+1) for t in times] # generate points on sine wave over 1 period, but shifted up to be ∈(0,2) O.engines=[ ForceResetter(), InsertionSortCollider([Bo1_Sphere_Aabb()]), InteractionLoop( [Ig2_Sphere_Sphere_ScGeom()], [Ip2_FrictMat_FrictMat_FrictPhys()], [Law2_ScGeom_FrictPhys_CundallStrack()] ), # ids: what bodies is force applied to # direction: direction of the force (normalized automatically), constant # magnitudes: series of force magnitude # times: time points at which magnitudes are defined # wrap: continue from t0 once t_last is reached # label: automatically defines python variable of that name pointing to this engine InterpolatingDirectedForceEngine(ids=[1],direction=[0,0,-1],magnitudes=magnitudes,times=times,wrap=True,label='forcer'), # without damping, the interaction never stabilizes and oscillates wildly… try it NewtonIntegrator(damping=0.01), # collect some data to plot periodically (every 50 steps) PyRunner(iterPeriod=1,command='myAddPlotData()') ] O.bodies.append([ sphere([0,0,0],1,fixed=True,color=[1,0,0]), sphere([0,0,2],1,color=[0,1,0]) ]) # elastic timestep O.dt=.5*PWaveTimeStep() # callback for plotDataCollector import yade.plot as yp def myAddPlotData(): yp.addData(t=O.time,F_applied=forcer.force[2],supportReaction=O.forces.f(0)[2]) O.saveTmp() # try open 3d view, if not running in pure console mode try: import yade.qt yade.qt.View() except ImportError: pass # run so many steps such that prescribed number of pulses is done O.run(int((nPulses/freq)/O.dt),True) # plot the time-series of force magnitude import pylab pylab.plot(times,magnitudes,label='Force magnitude over 1 pulse'); pylab.legend(('Force magnitude',)); pylab.xlabel('t'); pylab.grid(True) # plot some recorded data yp.plots={'t':('F_applied','supportReaction')} yp.plot() trunk-2018.02b/examples/test/kinematic.py000066400000000000000000000014761324306050200203110ustar00rootroot00000000000000# Deomonstrate composition of KinematicEngine's with +: # The first engine is composed (from translation and 2 independent rotations, one global and one local) # The second engine does the same on body #1 as the first one does on #0 (the local rotation is not present) O.bodies.append([ sphere((-1,0,0),.3,color=(1,0,.5),fixed=False,wire=True), sphere((1,0,0),.3,color=(0,1,0),fixed=False,wire=True) ]) O.engines=[ # only ids of the 1st engine are used TranslationEngine(ids=[0],translationAxis=(0,0,1),velocity=.1) + RotationEngine(rotationAxis=(0,0,1),angularVelocity=1,rotateAroundZero=True) + RotationEngine(rotationAxis=(1,0,0),angularVelocity=.3,rotateAroundZero=False), HelixEngine(ids=[1],rotationAxis=(0,0,1),angularVelocity=1,linearVelocity=.1), NewtonIntegrator(warnNoForceReset=False), ] O.dt=1e-6 O.saveTmp() trunk-2018.02b/examples/test/mindlin.py000066400000000000000000000042041324306050200177670ustar00rootroot00000000000000#!/usr/bin/python # -*- coding: utf-8 -*- ## ## SCRIPT TO TEST A NEW CONSTITUTIVE LAW (MINDLIN - nonlinear elastic model) ## list of engines O.engines=[ ForceResetter(), InsertionSortCollider([Bo1_Sphere_Aabb(),Bo1_Box_Aabb()]), InteractionLoop( [Ig2_Sphere_Sphere_ScGeom(),Ig2_Box_Sphere_ScGeom()], [Ip2_FrictMat_FrictMat_MindlinPhys()], [Law2_ScGeom_MindlinPhys_Mindlin()] ), NewtonIntegrator(damping=0.0,gravity=(10,0,0)), ### ### NOTE this extra engine: ### ### You want snapshot to be taken every 1 sec (realTimeLim) or every 50 iterations (iterLim), ### whichever comes soones. virtTimeLim attribute is unset, hence virtual time period is not taken into account. PyRunner(iterPeriod=1,command='myAddPlotData()') ] ## define and append material mat=FrictMat(young=600.0e6,poisson=0.6,density=2.60e3,frictionAngle=26,label='Friction') O.materials.append(mat) ## create two spheres (one will be fixed) and append them s0=sphere([0,0,0],1,color=[0,1,0],fixed=False,wire=True,material='Friction') s1=sphere([2,0,0],1,color=[0,2,0],fixed=True,wire=True,material='Friction') O.bodies.append(s0) O.bodies.append(s1) ## time step O.dt=.2*PWaveTimeStep() O.saveTmp('Mindlin') from yade import qt qt.View() qt.Controller() ############################################ ##### now the part pertaining to plots ##### ############################################ from yade import plot ## make one plot: step as function of fn plot.plots={'un':('fn')} ## this function is called by plotDataCollector ## it should add data with the labels that we will plot ## if a datum is not specified (but exists), it will be NaN and will not be plotted def myAddPlotData(): if O.interactions[0,1].isReal: i=O.interactions[0,1] ## store some numbers under some labels plot.addData(fn=i.phys.normalForce[0],step=O.iter,un=2*s0.shape.radius-s1.state.pos[0]+s0.state.pos[0],kn=i.phys.kn) O.run(100,True); plot.plot(subPlots=False) ## We will have: ## 1) data in graphs (if you call plot.plot()) ## 2) data in file (if you call plot.saveGnuplot('/tmp/a') ## 3) data in memory as plot.data['step'], plot.data['fn'], plot.data['un'], etc. under the labels they were saved trunk-2018.02b/examples/test/multi/000077500000000000000000000000001324306050200171155ustar00rootroot00000000000000trunk-2018.02b/examples/test/multi/multi.out000066400000000000000000000000541324306050200207770ustar00rootroot00000000000000-9.81 2400 10 18.6013 -9.81 2400 10 18.6013 trunk-2018.02b/examples/test/multi/multi.py000066400000000000000000000031501324306050200206200ustar00rootroot00000000000000# # https://yade-dem.org/wiki/ScriptParametricStudy # ## read parameters from table here from yade import plot readParamsFromTable(gravity=-9.81,density=2400,initialSpeed=10,noTableOk=True) from yade.params.table import * print gravity,density,initialSpeed O.materials.append(FrictMat(young=30e9,density=density,poisson=.3)) ## use the 'density' parameter here O.engines=[ ForceResetter(), InsertionSortCollider([Bo1_Sphere_Aabb(),Bo1_Box_Aabb(),]), InteractionLoop( [Ig2_Sphere_Sphere_ScGeom(),Ig2_Box_Sphere_ScGeom()], [Ip2_FrictMat_FrictMat_FrictPhys(),], [Law2_ScGeom_FrictPhys_CundallStrack(),] ), PyRunner(iterPeriod=100,command='myAddPlotData()',label='plotDataCollector'), NewtonIntegrator(damping=0.4,gravity=(0,0,gravity))## here we use the 'gravity' parameter ] O.bodies.append([ box([0,50,0],extents=[1,50,1],fixed=True,color=[1,0,0]), sphere([0,0,10],1,color=[0,1,0]) ]) O.bodies[1].state.vel=(0,initialSpeed,0) ## assign initial velocity O.dt=.8*PWaveTimeStep() ## o.saveTmp('initial') def myAddPlotData(): s=O.bodies[1] plot.addData({'t':O.time,'y_sph':s.state.pos[1],'z_sph':s.state.pos[2]}) plot.plots={'y_sph':('z_sph',)} # run 30000 iterations O.run(20000,True) # write some results to a common file # (we rely on the fact that 2 processes will not write results at exactly the same time) # 'a' means to open for appending #file('multi.out','a').write('%s %g %g %g %g\n'%(O.tags['description'],gravity,density,initialSpeed,O.bodies[1].state.pos[1])) file('multi.out','a').write('%g %g %g %g\n'%(gravity,density,initialSpeed,O.bodies[1].state.pos[1])) print 'gnuplot',plot.saveGnuplot(O.tags['id']) trunk-2018.02b/examples/test/multi/multi.table000066400000000000000000000003431324306050200212600ustar00rootroot00000000000000description density initialSpeed ## comment here reference 2400 10 hi_v 2400 20 lo_v 2400 5 # comment there hi_rho 5000 10 hi_rho_v 5000 20 hi_rh0_lo_v 5000 5 # comment-only line trunk-2018.02b/examples/test/pack-cloud.py000066400000000000000000000005251324306050200203610ustar00rootroot00000000000000""" Generate random periodic sphere packing using SpherePack::makeCloud """ from yade import pack p=pack.SpherePack() O.cellSize=Vector3(10,10,10) print p.makeCloud(Vector3(0,0,0),Vector3(10,10,10),.5,.5,200,True) for s in p: O.bodies.append(sphere(s[0],s[1])) O.engines=[BoundDispatcher([Bo1_Sphere_Aabb()])] from yade import qt qt.View() trunk-2018.02b/examples/test/pack-inConvexPolyhedron.py000066400000000000000000000026101324306050200231050ustar00rootroot00000000000000from yade import pack import random random.seed(1) # a cube p1 = Vector3(-9,-2,-2) p2 = p1 + (1,1,1) pred1 = pack.inConvexPolyhedron(( (p1, (+1, 0, 0)), (p1, ( 0,+1, 0)), (p1, ( 0, 0,+1)), (p2, (-1, 0, 0)), (p2, ( 0,-1, 0)), (p2, ( 0, 0,-1)), )) # a tetrahedron p1 = Vector3(-7,-2,-2) p2 = p1 + (1,1,1) pred2 = pack.inConvexPolyhedron(( (p1, (+1, 0, 0)), (p1, ( 0,+1, 0)), (p1, ( 0, 0,+1)), (p2, (-1,-1,-1)), )) # random polyhedron center = Vector3(-2,-2,-2) dMin = 1 dMax = 1.2 n = 20 planes = [] for i in xrange(n): d = random.random()*(dMax-dMin) + dMin # distance of plane from center, random number in range (dMin,dMax) # http://mathworld.wolfram.com/HyperspherePointPicking.html normal = Vector3([random.gauss(0,10) for _ in xrange(3)]) # random normal vector normal.normalize() planes.append((center-d*normal,normal)) pred3 = pack.inConvexPolyhedron(planes) try: # should be ValueError, since the 3 planes does not form closed polyhedron and finding its bounds should fail pred4 = pack.inConvexPolyhedron(( ((0,0,0), (+1, 0, 0)), ((0,0,0), ( 0,+1, 0)), ((0,0,0), ( 0, 0,+1)), )) except ValueError: print 'ValueError successfully detected' else: raise RuntimeError, "ValueError should have been detected..." r = .05 for p in (pred1,pred2,pred3): O.bodies.append(pack.regularHexa(p,r,0,color=randomColor())) try: from yade import qt v = qt.View() v.axes = True except: pass trunk-2018.02b/examples/test/pack-predicates.py000066400000000000000000000005771324306050200214050ustar00rootroot00000000000000from yade.pack import * s1=inSphere((0,0,0),1) s2=inSphere((0,0,1),1) # construct using explicit composition s12=PredicateUnion(s1,s2) print s12.aabb(), s12((0,0,.5),0) # constuct the same predicate with operators print (s1|s2).aabb(), (s1|s2)((0,0,.5),0) # predicate difference: points in s1 and not in s2 print (s1-s2).aabb() print (s1-s2)((0,0,1.5),0) print (s1-s2)((0,0,-0.5),0) trunk-2018.02b/examples/test/paraview-spheres-solid-section/000077500000000000000000000000001324306050200240225ustar00rootroot00000000000000trunk-2018.02b/examples/test/paraview-spheres-solid-section/export_text.py000066400000000000000000000014211324306050200267570ustar00rootroot00000000000000from yade import pack, export pred = pack.inAlignedBox((0,0,0),(10,10,10)) O.bodies.append(pack.randomDensePack(pred,radius=1.,rRelFuzz=.5,spheresInCell=500,memoizeDb='/tmp/pack.db')) export.textExt('/tmp/test.txt',format='x_y_z_r_attrs',attrs=('b.state.pos.norm()','b.state.pos'),comment='dstN dstV_x dstV_y dstV_z') # text2vtk and text2vtkSection function can be copy-pasted from yade/py/export.py into separate py file to avoid executing yade or to use pure python export.text2vtk('/tmp/test.txt','/tmp/test.vtk') export.text2vtkSection('/tmp/test.txt','/tmp/testSection.vtk',point=(5,5,5),normal=(1,1,1)) # now open paraview, click "Tools" menu -> "Python Shell", click "Run Script", choose "pv_section.py" from this directiory # or just run python pv_section.py # and enjoy :-) trunk-2018.02b/examples/test/paraview-spheres-solid-section/pv_section.py000066400000000000000000000754601324306050200265610ustar00rootroot00000000000000try: paraview.simple except: from paraview.simple import * paraview.simple._DisableFirstRenderCameraReset() RenderView1 = CreateRenderView() RenderView1.LightSpecularColor = [1.0, 1.0, 1.0] RenderView1.KeyLightAzimuth = 10.0 RenderView1.UseTexturedBackground = 0 RenderView1.UseLight = 1 RenderView1.CameraPosition = [-10.901121443539559, -17.271355476388358, 4.309974877565033] RenderView1.FillLightKFRatio = 3.0 RenderView1.Background2 = [0.0, 0.0, 0.165] RenderView1.FillLightAzimuth = -10.0 RenderView1.LODResolution = 50.0 RenderView1.BackgroundTexture = [] RenderView1.InteractionMode = '3D' RenderView1.StencilCapable = 1 RenderView1.LightIntensity = 1.0 RenderView1.CameraFocalPoint = [4.732498500000004, 5.045734999999994, 4.874767500000023] RenderView1.ImageReductionFactor = 2 RenderView1.CameraViewAngle = 30.0 RenderView1.CameraParallelScale = 7.0538593576100945 RenderView1.EyeAngle = 2.0 RenderView1.HeadLightKHRatio = 3.0 RenderView1.StereoRender = 0 RenderView1.KeyLightIntensity = 0.75 RenderView1.BackLightAzimuth = 110.0 RenderView1.OrientationAxesInteractivity = 0 RenderView1.UseInteractiveRenderingForSceenshots = 0 RenderView1.UseOffscreenRendering = 0 RenderView1.Background = [1.0, 1.0, 1.0] RenderView1.UseOffscreenRenderingForScreenshots = 0 RenderView1.NonInteractiveRenderDelay = 2 RenderView1.CenterOfRotation = [4.7324985, 5.045735, 4.8747675] RenderView1.CameraParallelProjection = 0 RenderView1.CompressorConfig = 'vtkSquirtCompressor 0 3' RenderView1.HeadLightWarmth = 0.5 RenderView1.MaximumNumberOfPeels = 4 RenderView1.LightDiffuseColor = [1.0, 1.0, 1.0] RenderView1.StereoType = 'Red-Blue' RenderView1.DepthPeeling = 1 RenderView1.BackLightKBRatio = 3.5 RenderView1.StereoCapableWindow = 1 RenderView1.CameraViewUp = [-0.818931483577863, 0.5738512869077476, -0.006776851723502143] RenderView1.LightType = 'HeadLight' RenderView1.LightAmbientColor = [1.0, 1.0, 1.0] RenderView1.RemoteRenderThreshold = 3.0 RenderView1.CacheKey = 0.0 RenderView1.UseCache = 0 RenderView1.KeyLightElevation = 50.0 RenderView1.CenterAxesVisibility = 0 RenderView1.MaintainLuminance = 0 RenderView1.StillRenderImageReductionFactor = 1 RenderView1.BackLightWarmth = 0.5 RenderView1.FillLightElevation = -75.0 RenderView1.MultiSamples = 0 RenderView1.FillLightWarmth = 0.4 RenderView1.AlphaBitPlanes = 1 RenderView1.LightSwitch = 0 RenderView1.OrientationAxesVisibility = 0 RenderView1.CameraClippingRange = [14.893135346284218, 43.89470107765568] RenderView1.BackLightElevation = 0.0 RenderView1.ViewTime = 0.0 RenderView1.OrientationAxesOutlineColor = [1.0, 1.0, 1.0] RenderView1.LODThreshold = 5.0 RenderView1.CollectGeometryThreshold = 100.0 RenderView1.UseGradientBackground = 0 RenderView1.KeyLightWarmth = 0.6 RenderView1.OrientationAxesLabelColor = [1.0, 1.0, 1.0] test_vtk = LegacyVTKReader( guiName="test.vtk", FileNames=['/tmp/test.vtk'] ) Glyph1 = Glyph( guiName="Glyph1", RandomMode=1, GlyphTransform="Transform2", GlyphType="Sphere", MaximumNumberofPoints=5000, ScaleMode='scalar', MaskPoints=0, Vectors=['POINTS', ''], SetScaleFactor=1.0, Scalars=['POINTS', 'radius'], Orient=1 ) Glyph1.GlyphType.StartTheta = 0.0 Glyph1.GlyphType.ThetaResolution = 12 Glyph1.GlyphTransform.Rotate = [0.0, 0.0, 0.0] Glyph1.GlyphTransform.Translate = [0.0, 0.0, 0.0] Glyph1.GlyphType.PhiResolution = 12 Glyph1.GlyphType.EndTheta = 360.0 Glyph1.GlyphType.EndPhi = 180.0 Glyph1.GlyphType.Center = [0.0, 0.0, 0.0] Glyph1.GlyphType.Radius = 1.0 Glyph1.GlyphTransform.Scale = [1.0, 1.0, 1.0] Glyph1.GlyphType.StartPhi = 0.0 Clip2 = Clip( guiName="Clip2", InsideOut=0, UseValueAsOffset=0, Scalars=['POINTS', 'radius'], Value=0.0, ClipType="Plane" ) Clip2.ClipType.Normal = [1.0, 1.0, 1.0] Clip2.ClipType.Origin = [5.0, 5.0, 5.0] Clip2.ClipType.Offset = 0.0 testSection_vtk = LegacyVTKReader( guiName="testSection.vtk", FileNames=['/tmp/testSection.vtk'] ) Glyph3 = Glyph( guiName="Glyph3", ScaleMode='scalar', GlyphType="Cylinder", MaskPoints=0, RandomMode=1, Vectors=['POINTS', 'normal'], GlyphTransform="Transform2", Scalars=['POINTS', 'radius'], MaximumNumberofPoints=5000, SetScaleFactor=1.0, Orient=1 ) Glyph3.GlyphType.Capping = 1 Glyph3.GlyphType.Height = 0.01 Glyph3.GlyphTransform.Rotate = [0.0, 0.0, -90.0] Glyph3.GlyphType.Radius = 1.0 Glyph3.GlyphTransform.Translate = [0.0, 0.0, 0.0] Glyph3.GlyphType.Center = [0.0, 0.0, 0.0] Glyph3.GlyphType.Resolution = 18 Glyph3.GlyphTransform.Scale = [1.0, 1.0, 1.0] a1_radius_PiecewiseFunction = CreatePiecewiseFunction( Points=[0.0, 0.0, 0.5, 0.0, 1.0, 1.0, 0.5, 0.0] ) a1_radiusOrig_PiecewiseFunction = CreatePiecewiseFunction( Points=[0.0, 0.0, 0.5, 0.0, 1.0, 1.0, 0.5, 0.0] ) a1_radius_PVLookupTable = GetLookupTableForArray( "radius", 1, Discretize=1, RGBPoints=[0.522954, 0.0, 0.0, 1.0, 1.42914, 1.0, 0.0, 0.0], UseLogScale=0, VectorComponent=0, NanColor=[0.498039, 0.498039, 0.498039], NumberOfTableValues=256, ColorSpace='HSV', VectorMode='Magnitude', HSVWrap=0, ScalarRangeInitialized=1.0, LockScalarRange=1 ) a1_radiusOrig_PVLookupTable = GetLookupTableForArray( "radiusOrig", 1, Discretize=1, RGBPoints=[0.54491, 0.0, 0.0, 1.0, 1.41118, 1.0, 0.0, 0.0], UseLogScale=0, VectorComponent=0, NanColor=[0.498039, 0.498039, 0.498039], NumberOfTableValues=256, ColorSpace='HSV', VectorMode='Magnitude', HSVWrap=0, ScalarRangeInitialized=1.0, LockScalarRange=1 ) SetActiveSource(test_vtk) DataRepresentation1 = Show() DataRepresentation1.CubeAxesZAxisVisibility = 1 DataRepresentation1.SelectionPointLabelColor = [0.5, 0.5, 0.5] DataRepresentation1.SelectionPointFieldDataArrayName = 'radius' DataRepresentation1.SuppressLOD = 0 DataRepresentation1.CubeAxesXGridLines = 0 DataRepresentation1.CubeAxesYAxisTickVisibility = 1 DataRepresentation1.CubeAxesColor = [1.0, 1.0, 1.0] DataRepresentation1.Position = [0.0, 0.0, 0.0] DataRepresentation1.BackfaceRepresentation = 'Follow Frontface' DataRepresentation1.SelectionOpacity = 1.0 DataRepresentation1.SelectionPointLabelShadow = 0 DataRepresentation1.CubeAxesYGridLines = 0 DataRepresentation1.CubeAxesZAxisRange = [0.0, 1.0] DataRepresentation1.OrientationMode = 'Direction' DataRepresentation1.Source.TipResolution = 6 DataRepresentation1.ScaleMode = 'No Data Scaling Off' DataRepresentation1.Diffuse = 1.0 DataRepresentation1.SelectionUseOutline = 0 DataRepresentation1.CubeAxesZTitle = 'Z-Axis' DataRepresentation1.Specular = 0.1 DataRepresentation1.SelectionVisibility = 1 DataRepresentation1.InterpolateScalarsBeforeMapping = 1 DataRepresentation1.CubeAxesZAxisTickVisibility = 1 DataRepresentation1.Origin = [0.0, 0.0, 0.0] DataRepresentation1.CubeAxesVisibility = 0 DataRepresentation1.Scale = [1.0, 1.0, 1.0] DataRepresentation1.SelectionCellLabelJustification = 'Left' DataRepresentation1.DiffuseColor = [1.0, 1.0, 1.0] DataRepresentation1.SelectionCellLabelOpacity = 1.0 DataRepresentation1.Source = "Arrow" DataRepresentation1.Source.Invert = 0 DataRepresentation1.Masking = 0 DataRepresentation1.Opacity = 1.0 DataRepresentation1.LineWidth = 1.0 DataRepresentation1.MeshVisibility = 0 DataRepresentation1.Visibility = 1 DataRepresentation1.SelectionCellLabelFontSize = 18 DataRepresentation1.CubeAxesCornerOffset = 0.0 DataRepresentation1.SelectionPointLabelJustification = 'Left' DataRepresentation1.SelectionPointLabelVisibility = 0 DataRepresentation1.SelectOrientationVectors = '' DataRepresentation1.CubeAxesTickLocation = 'Inside' DataRepresentation1.CubeAxesXAxisMinorTickVisibility = 1 DataRepresentation1.CubeAxesYAxisVisibility = 1 DataRepresentation1.SelectionPointLabelFontFamily = 'Arial' DataRepresentation1.Source.ShaftResolution = 6 DataRepresentation1.CubeAxesFlyMode = 'Closest Triad' DataRepresentation1.SelectScaleArray = '' DataRepresentation1.CubeAxesYTitle = 'Y-Axis' DataRepresentation1.ColorAttributeType = 'POINT_DATA' DataRepresentation1.SpecularPower = 100.0 DataRepresentation1.Texture = [] DataRepresentation1.SelectionCellLabelShadow = 0 DataRepresentation1.AmbientColor = [1.0, 1.0, 1.0] DataRepresentation1.MapScalars = 1 DataRepresentation1.PointSize = 2.0 DataRepresentation1.Source.TipLength = 0.35 DataRepresentation1.SelectionCellLabelFormat = '' DataRepresentation1.Scaling = 0 DataRepresentation1.StaticMode = 0 DataRepresentation1.SelectionCellLabelColor = [0.0, 1.0, 0.0] DataRepresentation1.Source.TipRadius = 0.1 DataRepresentation1.EdgeColor = [0.0, 0.0, 0.5000076295109483] DataRepresentation1.CubeAxesXAxisTickVisibility = 1 DataRepresentation1.SelectionCellLabelVisibility = 0 DataRepresentation1.NonlinearSubdivisionLevel = 1 DataRepresentation1.CubeAxesXAxisRange = [0.0, 1.0] DataRepresentation1.Representation = 'Surface' DataRepresentation1.CubeAxesYAxisRange = [0.0, 1.0] DataRepresentation1.CustomBounds = [0.0, 1.0, 0.0, 1.0, 0.0, 1.0] DataRepresentation1.Orientation = [0.0, 0.0, 0.0] DataRepresentation1.CubeAxesEnableCustomAxisRange = 0 DataRepresentation1.CubeAxesXTitle = 'X-Axis' DataRepresentation1.CubeAxesInertia = 1 DataRepresentation1.BackfaceOpacity = 1.0 DataRepresentation1.SelectionCellFieldDataArrayName = 'vtkOriginalCellIds' DataRepresentation1.SelectionColor = [1.0, 0.0, 1.0] DataRepresentation1.Ambient = 0.0 DataRepresentation1.SelectionPointLabelFontSize = 18 DataRepresentation1.ScaleFactor = 0.8360145000000001 DataRepresentation1.BackfaceAmbientColor = [1.0, 1.0, 1.0] DataRepresentation1.Source.ShaftRadius = 0.03 DataRepresentation1.SelectMaskArray = '' DataRepresentation1.SelectionLineWidth = 2.0 DataRepresentation1.CubeAxesZAxisMinorTickVisibility = 1 DataRepresentation1.CubeAxesXAxisVisibility = 1 DataRepresentation1.Interpolation = 'Gouraud' DataRepresentation1.SelectionCellLabelFontFamily = 'Arial' DataRepresentation1.SelectionCellLabelItalic = 0 DataRepresentation1.CubeAxesYAxisMinorTickVisibility = 1 DataRepresentation1.CubeAxesZGridLines = 0 DataRepresentation1.SelectionPointLabelFormat = '' DataRepresentation1.SelectionPointLabelOpacity = 1.0 DataRepresentation1.BackfaceDiffuseColor = [1.0, 1.0, 1.0] DataRepresentation1.Pickable = 1 DataRepresentation1.CustomBoundsActive = [0, 0, 0] DataRepresentation1.SelectionRepresentation = 'Wireframe' DataRepresentation1.SelectionPointLabelBold = 0 DataRepresentation1.ColorArrayName = 'radius' DataRepresentation1.SelectionPointLabelItalic = 0 DataRepresentation1.AllowSpecularHighlightingWithScalarColoring = 0 DataRepresentation1.SpecularColor = [1.0, 1.0, 1.0] DataRepresentation1.LookupTable = a1_radius_PVLookupTable DataRepresentation1.SelectionPointSize = 5.0 DataRepresentation1.SelectionCellLabelBold = 0 DataRepresentation1.Orient = 0 SetActiveSource(Glyph1) DataRepresentation3 = Show() DataRepresentation3.CubeAxesZAxisVisibility = 1 DataRepresentation3.SelectionPointLabelColor = [0.5, 0.5, 0.5] DataRepresentation3.SelectionPointFieldDataArrayName = 'radius' DataRepresentation3.SuppressLOD = 0 DataRepresentation3.CubeAxesXGridLines = 0 DataRepresentation3.CubeAxesYAxisTickVisibility = 1 DataRepresentation3.CubeAxesColor = [1.0, 1.0, 1.0] DataRepresentation3.Position = [0.0, 0.0, 0.0] DataRepresentation3.BackfaceRepresentation = 'Follow Frontface' DataRepresentation3.SelectionOpacity = 1.0 DataRepresentation3.SelectionPointLabelShadow = 0 DataRepresentation3.CubeAxesYGridLines = 0 DataRepresentation3.CubeAxesZAxisRange = [0.0, 1.0] DataRepresentation3.OrientationMode = 'Direction' DataRepresentation3.Source.TipResolution = 6 DataRepresentation3.ScaleMode = 'No Data Scaling Off' DataRepresentation3.Diffuse = 1.0 DataRepresentation3.SelectionUseOutline = 0 DataRepresentation3.CubeAxesZTitle = 'Z-Axis' DataRepresentation3.Specular = 0.1 DataRepresentation3.SelectionVisibility = 1 DataRepresentation3.InterpolateScalarsBeforeMapping = 1 DataRepresentation3.CubeAxesZAxisTickVisibility = 1 DataRepresentation3.Origin = [0.0, 0.0, 0.0] DataRepresentation3.CubeAxesVisibility = 0 DataRepresentation3.Scale = [1.0, 1.0, 1.0] DataRepresentation3.SelectionCellLabelJustification = 'Left' DataRepresentation3.DiffuseColor = [1.0, 1.0, 1.0] DataRepresentation3.SelectionCellLabelOpacity = 1.0 DataRepresentation3.Source = "Arrow" DataRepresentation3.Source.Invert = 0 DataRepresentation3.Masking = 0 DataRepresentation3.Opacity = 1.0 DataRepresentation3.LineWidth = 1.0 DataRepresentation3.MeshVisibility = 0 DataRepresentation3.Visibility = 0 DataRepresentation3.SelectionCellLabelFontSize = 18 DataRepresentation3.CubeAxesCornerOffset = 0.0 DataRepresentation3.SelectionPointLabelJustification = 'Left' DataRepresentation3.SelectionPointLabelVisibility = 0 DataRepresentation3.SelectOrientationVectors = '' DataRepresentation3.CubeAxesTickLocation = 'Inside' DataRepresentation3.CubeAxesXAxisMinorTickVisibility = 1 DataRepresentation3.CubeAxesYAxisVisibility = 1 DataRepresentation3.SelectionPointLabelFontFamily = 'Arial' DataRepresentation3.Source.ShaftResolution = 6 DataRepresentation3.CubeAxesFlyMode = 'Closest Triad' DataRepresentation3.SelectScaleArray = '' DataRepresentation3.CubeAxesYTitle = 'Y-Axis' DataRepresentation3.ColorAttributeType = 'POINT_DATA' DataRepresentation3.SpecularPower = 100.0 DataRepresentation3.Texture = [] DataRepresentation3.SelectionCellLabelShadow = 0 DataRepresentation3.AmbientColor = [1.0, 1.0, 1.0] DataRepresentation3.MapScalars = 1 DataRepresentation3.PointSize = 2.0 DataRepresentation3.Source.TipLength = 0.35 DataRepresentation3.SelectionCellLabelFormat = '' DataRepresentation3.Scaling = 0 DataRepresentation3.StaticMode = 0 DataRepresentation3.SelectionCellLabelColor = [0.0, 1.0, 0.0] DataRepresentation3.Source.TipRadius = 0.1 DataRepresentation3.EdgeColor = [0.0, 0.0, 0.5000076295109483] DataRepresentation3.CubeAxesXAxisTickVisibility = 1 DataRepresentation3.SelectionCellLabelVisibility = 0 DataRepresentation3.NonlinearSubdivisionLevel = 1 DataRepresentation3.CubeAxesXAxisRange = [0.0, 1.0] DataRepresentation3.Representation = 'Surface' DataRepresentation3.CubeAxesYAxisRange = [0.0, 1.0] DataRepresentation3.CustomBounds = [0.0, 1.0, 0.0, 1.0, 0.0, 1.0] DataRepresentation3.Orientation = [0.0, 0.0, 0.0] DataRepresentation3.CubeAxesEnableCustomAxisRange = 0 DataRepresentation3.CubeAxesXTitle = 'X-Axis' DataRepresentation3.CubeAxesInertia = 1 DataRepresentation3.BackfaceOpacity = 1.0 DataRepresentation3.SelectionCellFieldDataArrayName = 'vtkOriginalCellIds' DataRepresentation3.SelectionColor = [1.0, 0.0, 1.0] DataRepresentation3.Ambient = 0.0 DataRepresentation3.SelectionPointLabelFontSize = 18 DataRepresentation3.ScaleFactor = 0.9713980622589589 DataRepresentation3.BackfaceAmbientColor = [1.0, 1.0, 1.0] DataRepresentation3.Source.ShaftRadius = 0.03 DataRepresentation3.SelectMaskArray = '' DataRepresentation3.SelectionLineWidth = 2.0 DataRepresentation3.CubeAxesZAxisMinorTickVisibility = 1 DataRepresentation3.CubeAxesXAxisVisibility = 1 DataRepresentation3.Interpolation = 'Gouraud' DataRepresentation3.SelectionCellLabelFontFamily = 'Arial' DataRepresentation3.SelectionCellLabelItalic = 0 DataRepresentation3.CubeAxesYAxisMinorTickVisibility = 1 DataRepresentation3.CubeAxesZGridLines = 0 DataRepresentation3.SelectionPointLabelFormat = '' DataRepresentation3.SelectionPointLabelOpacity = 1.0 DataRepresentation3.BackfaceDiffuseColor = [1.0, 1.0, 1.0] DataRepresentation3.Pickable = 1 DataRepresentation3.CustomBoundsActive = [0, 0, 0] DataRepresentation3.SelectionRepresentation = 'Wireframe' DataRepresentation3.SelectionPointLabelBold = 0 DataRepresentation3.ColorArrayName = 'radius' DataRepresentation3.SelectionPointLabelItalic = 0 DataRepresentation3.AllowSpecularHighlightingWithScalarColoring = 0 DataRepresentation3.SpecularColor = [1.0, 1.0, 1.0] DataRepresentation3.LookupTable = a1_radius_PVLookupTable DataRepresentation3.SelectionPointSize = 5.0 DataRepresentation3.SelectionCellLabelBold = 0 DataRepresentation3.Orient = 0 SetActiveSource(Clip2) DataRepresentation5 = Show() DataRepresentation5.CubeAxesZAxisVisibility = 1 DataRepresentation5.SelectionPointLabelColor = [0.5, 0.5, 0.5] DataRepresentation5.SelectionPointFieldDataArrayName = 'radius' DataRepresentation5.SuppressLOD = 0 DataRepresentation5.CubeAxesXGridLines = 0 DataRepresentation5.CubeAxesYAxisTickVisibility = 1 DataRepresentation5.CubeAxesColor = [1.0, 1.0, 1.0] DataRepresentation5.Position = [0.0, 0.0, 0.0] DataRepresentation5.BackfaceRepresentation = 'Follow Frontface' DataRepresentation5.SelectionOpacity = 1.0 DataRepresentation5.SelectionPointLabelShadow = 0 DataRepresentation5.CubeAxesYGridLines = 0 DataRepresentation5.CubeAxesZAxisRange = [0.0, 1.0] DataRepresentation5.OrientationMode = 'Direction' DataRepresentation5.Source.TipResolution = 6 DataRepresentation5.ScaleMode = 'No Data Scaling Off' DataRepresentation5.Diffuse = 1.0 DataRepresentation5.SelectionUseOutline = 0 DataRepresentation5.SelectionPointLabelFormat = '' DataRepresentation5.CubeAxesZTitle = 'Z-Axis' DataRepresentation5.Specular = 0.1 DataRepresentation5.SelectionVisibility = 1 DataRepresentation5.InterpolateScalarsBeforeMapping = 1 DataRepresentation5.CubeAxesZAxisTickVisibility = 1 DataRepresentation5.Origin = [0.0, 0.0, 0.0] DataRepresentation5.CubeAxesVisibility = 0 DataRepresentation5.Scale = [1.0, 1.0, 1.0] DataRepresentation5.SelectionCellLabelJustification = 'Left' DataRepresentation5.DiffuseColor = [1.0, 1.0, 1.0] DataRepresentation5.SelectionCellLabelOpacity = 1.0 DataRepresentation5.CubeAxesInertia = 1 DataRepresentation5.Source = "Arrow" DataRepresentation5.Source.Invert = 0 DataRepresentation5.Masking = 0 DataRepresentation5.Opacity = 1.0 DataRepresentation5.LineWidth = 1.0 DataRepresentation5.MeshVisibility = 0 DataRepresentation5.Visibility = 1 DataRepresentation5.SelectionCellLabelFontSize = 18 DataRepresentation5.CubeAxesCornerOffset = 0.0 DataRepresentation5.SelectionPointLabelJustification = 'Left' DataRepresentation5.SelectionPointLabelVisibility = 0 DataRepresentation5.SelectOrientationVectors = '' DataRepresentation5.CubeAxesTickLocation = 'Inside' DataRepresentation5.BackfaceDiffuseColor = [1.0, 1.0, 1.0] DataRepresentation5.CubeAxesYAxisVisibility = 1 DataRepresentation5.SelectionPointLabelFontFamily = 'Arial' DataRepresentation5.Source.ShaftResolution = 6 DataRepresentation5.CubeAxesFlyMode = 'Closest Triad' DataRepresentation5.SelectScaleArray = '' DataRepresentation5.CubeAxesYTitle = 'Y-Axis' DataRepresentation5.ColorAttributeType = 'POINT_DATA' DataRepresentation5.SpecularPower = 100.0 DataRepresentation5.Texture = [] DataRepresentation5.SelectionCellLabelShadow = 0 DataRepresentation5.AmbientColor = [1.0, 1.0, 1.0] DataRepresentation5.MapScalars = 1 DataRepresentation5.PointSize = 2.0 DataRepresentation5.Source.TipLength = 0.35 DataRepresentation5.SelectionCellLabelFormat = '' DataRepresentation5.Scaling = 0 DataRepresentation5.StaticMode = 0 DataRepresentation5.SelectionCellLabelColor = [0.0, 1.0, 0.0] DataRepresentation5.Source.TipRadius = 0.1 DataRepresentation5.EdgeColor = [0.0, 0.0, 0.5000076295109483] DataRepresentation5.CubeAxesXAxisTickVisibility = 1 DataRepresentation5.SelectionCellLabelVisibility = 0 DataRepresentation5.NonlinearSubdivisionLevel = 1 DataRepresentation5.CubeAxesXAxisRange = [0.0, 1.0] DataRepresentation5.Representation = 'Surface' DataRepresentation5.CubeAxesYAxisRange = [0.0, 1.0] DataRepresentation5.CustomBounds = [0.0, 1.0, 0.0, 1.0, 0.0, 1.0] DataRepresentation5.Orientation = [0.0, 0.0, 0.0] DataRepresentation5.CubeAxesEnableCustomAxisRange = 0 DataRepresentation5.CubeAxesXTitle = 'X-Axis' DataRepresentation5.ScalarOpacityUnitDistance = 1.1040324233388048 DataRepresentation5.BackfaceOpacity = 1.0 DataRepresentation5.SelectionPointLabelFontSize = 18 DataRepresentation5.SelectionCellFieldDataArrayName = 'vtkOriginalCellIds' DataRepresentation5.SelectionColor = [1.0, 0.0, 1.0] DataRepresentation5.Ambient = 0.0 DataRepresentation5.CubeAxesXAxisMinorTickVisibility = 1 DataRepresentation5.ScaleFactor = 0.8981979608535767 DataRepresentation5.BackfaceAmbientColor = [1.0, 1.0, 1.0] DataRepresentation5.Source.ShaftRadius = 0.03 DataRepresentation5.ScalarOpacityFunction = a1_radius_PiecewiseFunction DataRepresentation5.SelectMaskArray = '' DataRepresentation5.SelectionLineWidth = 2.0 DataRepresentation5.CubeAxesZAxisMinorTickVisibility = 1 DataRepresentation5.CubeAxesXAxisVisibility = 1 DataRepresentation5.Interpolation = 'Gouraud' DataRepresentation5.SelectMapper = 'Projected tetra' DataRepresentation5.SelectionCellLabelFontFamily = 'Arial' DataRepresentation5.SelectionCellLabelItalic = 0 DataRepresentation5.CubeAxesYAxisMinorTickVisibility = 1 DataRepresentation5.CubeAxesZGridLines = 0 DataRepresentation5.ExtractedBlockIndex = 0 DataRepresentation5.SelectionPointLabelOpacity = 1.0 DataRepresentation5.Pickable = 1 DataRepresentation5.CustomBoundsActive = [0, 0, 0] DataRepresentation5.SelectionRepresentation = 'Wireframe' DataRepresentation5.SelectionPointLabelBold = 0 DataRepresentation5.ColorArrayName = 'radius' DataRepresentation5.SelectionPointLabelItalic = 0 DataRepresentation5.AllowSpecularHighlightingWithScalarColoring = 0 DataRepresentation5.SpecularColor = [1.0, 1.0, 1.0] DataRepresentation5.LookupTable = a1_radius_PVLookupTable DataRepresentation5.SelectionPointSize = 5.0 DataRepresentation5.SelectionCellLabelBold = 0 DataRepresentation5.Orient = 0 SetActiveSource(testSection_vtk) DataRepresentation7 = Show() DataRepresentation7.CubeAxesZAxisVisibility = 1 DataRepresentation7.SelectionPointLabelColor = [0.5, 0.5, 0.5] DataRepresentation7.SelectionPointFieldDataArrayName = 'radius' DataRepresentation7.SuppressLOD = 0 DataRepresentation7.CubeAxesXGridLines = 0 DataRepresentation7.CubeAxesYAxisTickVisibility = 1 DataRepresentation7.CubeAxesColor = [1.0, 1.0, 1.0] DataRepresentation7.Position = [0.0, 0.0, 0.0] DataRepresentation7.BackfaceRepresentation = 'Follow Frontface' DataRepresentation7.SelectionOpacity = 1.0 DataRepresentation7.SelectionPointLabelShadow = 0 DataRepresentation7.CubeAxesYGridLines = 0 DataRepresentation7.CubeAxesZAxisRange = [0.0, 1.0] DataRepresentation7.OrientationMode = 'Direction' DataRepresentation7.Source.TipResolution = 6 DataRepresentation7.ScaleMode = 'No Data Scaling Off' DataRepresentation7.Diffuse = 1.0 DataRepresentation7.SelectionUseOutline = 0 DataRepresentation7.CubeAxesZTitle = 'Z-Axis' DataRepresentation7.Specular = 0.1 DataRepresentation7.SelectionVisibility = 1 DataRepresentation7.InterpolateScalarsBeforeMapping = 1 DataRepresentation7.CubeAxesZAxisTickVisibility = 1 DataRepresentation7.Origin = [0.0, 0.0, 0.0] DataRepresentation7.CubeAxesVisibility = 0 DataRepresentation7.Scale = [1.0, 1.0, 1.0] DataRepresentation7.SelectionCellLabelJustification = 'Left' DataRepresentation7.DiffuseColor = [1.0, 1.0, 1.0] DataRepresentation7.SelectionCellLabelOpacity = 1.0 DataRepresentation7.Source = "Arrow" DataRepresentation7.Source.Invert = 0 DataRepresentation7.Masking = 0 DataRepresentation7.Opacity = 1.0 DataRepresentation7.LineWidth = 1.0 DataRepresentation7.MeshVisibility = 0 DataRepresentation7.Visibility = 1 DataRepresentation7.SelectionCellLabelFontSize = 18 DataRepresentation7.CubeAxesCornerOffset = 0.0 DataRepresentation7.SelectionPointLabelJustification = 'Left' DataRepresentation7.SelectionPointLabelVisibility = 0 DataRepresentation7.SelectOrientationVectors = '' DataRepresentation7.CubeAxesTickLocation = 'Inside' DataRepresentation7.CubeAxesXAxisMinorTickVisibility = 1 DataRepresentation7.CubeAxesYAxisVisibility = 1 DataRepresentation7.SelectionPointLabelFontFamily = 'Arial' DataRepresentation7.Source.ShaftResolution = 6 DataRepresentation7.CubeAxesFlyMode = 'Closest Triad' DataRepresentation7.SelectScaleArray = '' DataRepresentation7.CubeAxesYTitle = 'Y-Axis' DataRepresentation7.ColorAttributeType = 'POINT_DATA' DataRepresentation7.SpecularPower = 100.0 DataRepresentation7.Texture = [] DataRepresentation7.SelectionCellLabelShadow = 0 DataRepresentation7.AmbientColor = [1.0, 1.0, 1.0] DataRepresentation7.MapScalars = 1 DataRepresentation7.PointSize = 2.0 DataRepresentation7.Source.TipLength = 0.35 DataRepresentation7.SelectionCellLabelFormat = '' DataRepresentation7.Scaling = 0 DataRepresentation7.StaticMode = 0 DataRepresentation7.SelectionCellLabelColor = [0.0, 1.0, 0.0] DataRepresentation7.Source.TipRadius = 0.1 DataRepresentation7.EdgeColor = [0.0, 0.0, 0.5000076295109483] DataRepresentation7.CubeAxesXAxisTickVisibility = 1 DataRepresentation7.SelectionCellLabelVisibility = 0 DataRepresentation7.NonlinearSubdivisionLevel = 1 DataRepresentation7.CubeAxesXAxisRange = [0.0, 1.0] DataRepresentation7.Representation = 'Surface' DataRepresentation7.CubeAxesYAxisRange = [0.0, 1.0] DataRepresentation7.CustomBounds = [0.0, 1.0, 0.0, 1.0, 0.0, 1.0] DataRepresentation7.Orientation = [0.0, 0.0, 0.0] DataRepresentation7.CubeAxesEnableCustomAxisRange = 0 DataRepresentation7.CubeAxesXTitle = 'X-Axis' DataRepresentation7.CubeAxesInertia = 1 DataRepresentation7.BackfaceOpacity = 1.0 DataRepresentation7.SelectionCellFieldDataArrayName = 'vtkOriginalCellIds' DataRepresentation7.SelectionColor = [1.0, 0.0, 1.0] DataRepresentation7.Ambient = 0.0 DataRepresentation7.SelectionPointLabelFontSize = 18 DataRepresentation7.ScaleFactor = 0.73055 DataRepresentation7.BackfaceAmbientColor = [1.0, 1.0, 1.0] DataRepresentation7.Source.ShaftRadius = 0.03 DataRepresentation7.SelectMaskArray = '' DataRepresentation7.SelectionLineWidth = 2.0 DataRepresentation7.CubeAxesZAxisMinorTickVisibility = 1 DataRepresentation7.CubeAxesXAxisVisibility = 1 DataRepresentation7.Interpolation = 'Gouraud' DataRepresentation7.SelectionCellLabelFontFamily = 'Arial' DataRepresentation7.SelectionCellLabelItalic = 0 DataRepresentation7.CubeAxesYAxisMinorTickVisibility = 1 DataRepresentation7.CubeAxesZGridLines = 0 DataRepresentation7.SelectionPointLabelFormat = '' DataRepresentation7.SelectionPointLabelOpacity = 1.0 DataRepresentation7.BackfaceDiffuseColor = [1.0, 1.0, 1.0] DataRepresentation7.Pickable = 1 DataRepresentation7.CustomBoundsActive = [0, 0, 0] DataRepresentation7.SelectionRepresentation = 'Wireframe' DataRepresentation7.SelectionPointLabelBold = 0 DataRepresentation7.ColorArrayName = 'radius' DataRepresentation7.SelectionPointLabelItalic = 0 DataRepresentation7.AllowSpecularHighlightingWithScalarColoring = 0 DataRepresentation7.SpecularColor = [1.0, 1.0, 1.0] DataRepresentation7.LookupTable = a1_radius_PVLookupTable DataRepresentation7.SelectionPointSize = 5.0 DataRepresentation7.SelectionCellLabelBold = 0 DataRepresentation7.Orient = 0 SetActiveSource(Glyph3) DataRepresentation8 = Show() DataRepresentation8.CubeAxesZAxisVisibility = 1 DataRepresentation8.SelectionPointLabelColor = [0.5, 0.5, 0.5] DataRepresentation8.SelectionPointFieldDataArrayName = 'radius' DataRepresentation8.SuppressLOD = 0 DataRepresentation8.CubeAxesXGridLines = 0 DataRepresentation8.CubeAxesYAxisTickVisibility = 1 DataRepresentation8.CubeAxesColor = [1.0, 1.0, 1.0] DataRepresentation8.Position = [0.0, 0.0, 0.0] DataRepresentation8.BackfaceRepresentation = 'Follow Frontface' DataRepresentation8.SelectionOpacity = 1.0 DataRepresentation8.SelectionPointLabelShadow = 0 DataRepresentation8.CubeAxesYGridLines = 0 DataRepresentation8.CubeAxesZAxisRange = [0.0, 1.0] DataRepresentation8.OrientationMode = 'Direction' DataRepresentation8.Source.TipResolution = 6 DataRepresentation8.ScaleMode = 'No Data Scaling Off' DataRepresentation8.Diffuse = 1.0 DataRepresentation8.SelectionUseOutline = 0 DataRepresentation8.CubeAxesZTitle = 'Z-Axis' DataRepresentation8.Specular = 0.1 DataRepresentation8.SelectionVisibility = 1 DataRepresentation8.InterpolateScalarsBeforeMapping = 1 DataRepresentation8.CubeAxesZAxisTickVisibility = 1 DataRepresentation8.Origin = [0.0, 0.0, 0.0] DataRepresentation8.CubeAxesVisibility = 0 DataRepresentation8.Scale = [1.0, 1.0, 1.0] DataRepresentation8.SelectionCellLabelJustification = 'Left' DataRepresentation8.DiffuseColor = [1.0, 1.0, 1.0] DataRepresentation8.SelectionCellLabelOpacity = 1.0 DataRepresentation8.Source = "Arrow" DataRepresentation8.Source.Invert = 0 DataRepresentation8.Masking = 0 DataRepresentation8.Opacity = 1.0 DataRepresentation8.LineWidth = 1.0 DataRepresentation8.MeshVisibility = 0 DataRepresentation8.Visibility = 1 DataRepresentation8.SelectionCellLabelFontSize = 18 DataRepresentation8.CubeAxesCornerOffset = 0.0 DataRepresentation8.SelectionPointLabelJustification = 'Left' DataRepresentation8.SelectionPointLabelVisibility = 0 DataRepresentation8.SelectOrientationVectors = '' DataRepresentation8.CubeAxesTickLocation = 'Inside' DataRepresentation8.CubeAxesXAxisMinorTickVisibility = 1 DataRepresentation8.CubeAxesYAxisVisibility = 1 DataRepresentation8.SelectionPointLabelFontFamily = 'Arial' DataRepresentation8.Source.ShaftResolution = 6 DataRepresentation8.CubeAxesFlyMode = 'Closest Triad' DataRepresentation8.SelectScaleArray = '' DataRepresentation8.CubeAxesYTitle = 'Y-Axis' DataRepresentation8.ColorAttributeType = 'POINT_DATA' DataRepresentation8.SpecularPower = 100.0 DataRepresentation8.Texture = [] DataRepresentation8.SelectionCellLabelShadow = 0 DataRepresentation8.AmbientColor = [1.0, 1.0, 1.0] DataRepresentation8.MapScalars = 1 DataRepresentation8.PointSize = 2.0 DataRepresentation8.Source.TipLength = 0.35 DataRepresentation8.SelectionCellLabelFormat = '' DataRepresentation8.Scaling = 0 DataRepresentation8.StaticMode = 0 DataRepresentation8.SelectionCellLabelColor = [0.0, 1.0, 0.0] DataRepresentation8.Source.TipRadius = 0.1 DataRepresentation8.EdgeColor = [0.0, 0.0, 0.5000076295109483] DataRepresentation8.CubeAxesXAxisTickVisibility = 1 DataRepresentation8.SelectionCellLabelVisibility = 0 DataRepresentation8.NonlinearSubdivisionLevel = 1 DataRepresentation8.CubeAxesXAxisRange = [0.0, 1.0] DataRepresentation8.Representation = 'Surface' DataRepresentation8.CubeAxesYAxisRange = [0.0, 1.0] DataRepresentation8.CustomBounds = [0.0, 1.0, 0.0, 1.0, 0.0, 1.0] DataRepresentation8.Orientation = [0.0, 0.0, 0.0] DataRepresentation8.CubeAxesEnableCustomAxisRange = 0 DataRepresentation8.CubeAxesXTitle = 'X-Axis' DataRepresentation8.CubeAxesInertia = 1 DataRepresentation8.BackfaceOpacity = 1.0 DataRepresentation8.SelectionCellFieldDataArrayName = 'vtkOriginalCellIds' DataRepresentation8.SelectionColor = [1.0, 0.0, 1.0] DataRepresentation8.Ambient = 0.0 DataRepresentation8.SelectionPointLabelFontSize = 18 DataRepresentation8.ScaleFactor = 0.8328240185976029 DataRepresentation8.BackfaceAmbientColor = [1.0, 1.0, 1.0] DataRepresentation8.Source.ShaftRadius = 0.03 DataRepresentation8.SelectMaskArray = '' DataRepresentation8.SelectionLineWidth = 2.0 DataRepresentation8.CubeAxesZAxisMinorTickVisibility = 1 DataRepresentation8.CubeAxesXAxisVisibility = 1 DataRepresentation8.Interpolation = 'Gouraud' DataRepresentation8.SelectionCellLabelFontFamily = 'Arial' DataRepresentation8.SelectionCellLabelItalic = 0 DataRepresentation8.CubeAxesYAxisMinorTickVisibility = 1 DataRepresentation8.CubeAxesZGridLines = 0 DataRepresentation8.SelectionPointLabelFormat = '' DataRepresentation8.SelectionPointLabelOpacity = 1.0 DataRepresentation8.BackfaceDiffuseColor = [1.0, 1.0, 1.0] DataRepresentation8.Pickable = 1 DataRepresentation8.CustomBoundsActive = [0, 0, 0] DataRepresentation8.SelectionRepresentation = 'Wireframe' DataRepresentation8.SelectionPointLabelBold = 0 DataRepresentation8.ColorArrayName = 'radiusOrig' DataRepresentation8.SelectionPointLabelItalic = 0 DataRepresentation8.AllowSpecularHighlightingWithScalarColoring = 0 DataRepresentation8.SpecularColor = [1.0, 1.0, 1.0] DataRepresentation8.LookupTable = a1_radiusOrig_PVLookupTable DataRepresentation8.SelectionPointSize = 5.0 DataRepresentation8.SelectionCellLabelBold = 0 DataRepresentation8.Orient = 0 Render() trunk-2018.02b/examples/test/performance/000077500000000000000000000000001324306050200202645ustar00rootroot00000000000000trunk-2018.02b/examples/test/performance/checkPerf.py000066400000000000000000000115551324306050200225370ustar00rootroot00000000000000# -*- coding: utf-8 from yade import pack,export,geom,timing,bodiesHandling import time,numpy radRAD=[ 23.658, #5000 elements 40.455, #25000 elements 50.97, #50000 elements 64.218, #100000 elements 80.91, #200000 elements #109.811, #500000 elements ] iterN=[ 12000, #5000 elements 2500, #25000 elements 1400, #50000 elements 800, #100000 elements 200, #200000 elements #10, #500000 elements ] coefCor=[ 110, 28, 18, 9, 2, #0.1, ] iterVel=[] testTime=[] particlesNumber=[] data=[] numberTests = 3 initIter = 1 # number of initial iterations excluded from performance check (e.g. Collider is initialised in first step therefore exclude first step from performance check) tStartAll=time.time() for z in range(numberTests): for i in range(len(radRAD)): rR = radRAD[i] nbIter=iterN[i] O.reset() tc=0.001 en=.003 es=.003 frictionAngle=radians(35) density=2300 defMat=O.materials.append(ViscElMat(density=density,frictionAngle=frictionAngle,tc=tc,en=en,et=es)) O.dt=.1*tc # time step rad=0.5 # particle radius tolerance = 0.0001 SpheresID=[] SpheresID+=O.bodies.append(pack.regularHexa(pack.inSphere((Vector3(0.0,0.0,0.0)),rad),radius=rad/rR,gap=rad/rR*0.5,material=defMat)) geometryParameters = bodiesHandling.spheresPackDimensions(SpheresID) print len(SpheresID) floorId=[] floorId+=O.bodies.append(geom.facetBox(geometryParameters['center'],geometryParameters['extends']/2.0*1.05,material=defMat)) #Floor #Calculate the mass of spheres sphMass = getSpheresVolume()*density # Create engines O.engines=[ ForceResetter(), InsertionSortCollider([Bo1_Sphere_Aabb(),Bo1_Facet_Aabb()]), InteractionLoop( [Ig2_Sphere_Sphere_ScGeom(), Ig2_Facet_Sphere_ScGeom()], [Ip2_ViscElMat_ViscElMat_ViscElPhys()], [Law2_ScGeom_ViscElPhys_Basic()], ), NewtonIntegrator(damping=0,gravity=[0,0,-9.81]), ] print "number of bodies %d"%len(O.bodies) # Initialize the collider else it is not possible to compare the results with different nbIter O.run(initIter,1) O.timingEnabled=True timing.reset() tStart=time.time() # run nbIter iterations O.run(nbIter) O.wait() tEnd=time.time() print print 'Elapsed ', tEnd-tStart, ' sec' print 'Performance ', nbIter/(tEnd-tStart), ' iter/sec' print 'Extrapolation on 1e5 iters ', (tEnd-tStart)/nbIter*1e5/3600., ' hours' print "=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*" timing.stats() iterVel += [nbIter/(tEnd-tStart)] testTime += [tEnd-tStart] particlesNumber += [len(O.bodies)] tEndAll=time.time() commonTime = tEndAll-tStartAll print "Common time ", commonTime, "s" print print print "___________________________________________________" print print "SUMMARY" print scoreIterVel=0.0 for i in range(len(radRAD)): iterAv=0.0 iterVelNumpy=numpy.empty(numberTests) for z in range(numberTests): iterVelNumpy[z]=iterVel[z*len(radRAD)+i] avgVel = numpy.average(iterVelNumpy) dispVel = numpy.std(iterVelNumpy)/numpy.average(iterVelNumpy)*100.0 if (dispVel>10.): print "Calculation velocity is unstable, try to close all programs and start performance tests again" print particlesNumber[i]," spheres, calculation velocity=",avgVel, "iter/sec +/-",dispVel,"%" data+=[[particlesNumber[i],avgVel,avgVel*dispVel/100.]] scoreIterVel+=avgVel/coefCor[i]*1000.0 print scoreIterVel = int(scoreIterVel) ## write output file for graph import subprocess cmd = "cat /proc/cpuinfo | grep \'model name\' | uniq" #processor = subprocess.check_output(cmd, shell=True).lstrip('model name\t:').strip() # needs python >=2.7.0 process = subprocess.Popen(cmd, stdout=subprocess.PIPE, shell=True) processor = process.communicate()[0].lstrip('model name\t:').strip() cmd = "cat /proc/cpuinfo | grep processor | wc -l" #cores = subprocess.check_output("cat /proc/cpuinfo | grep processor | wc -l", shell=True).strip() # needs python >=2.7.0 process = subprocess.Popen(cmd, stdout=subprocess.PIPE, shell=True) cores = process.communicate()[0].strip() header='# '+ processor + ' ('+cores+' cores)' numThreads=os.environ['OMP_NUM_THREADS'] if (len(os.environ['OMP_NUM_THREADS'])==2) else ('0'+os.environ['OMP_NUM_THREADS']) filename=version+"_j"+numThreads+".dat" #numpy.savetxt(filename,numpy.array(data),fmt=['%i','%g','%g'], header=header) # needs numpy >=1.7.0 fid = open( filename, 'w' ) fid.write(header+"\n") for l in data: fid.write("%d %f %f\n"%(l[0],l[1],l[2])) fid.close() print "Summary saved to "+filename print print "SCORE: " + str(scoreIterVel) print "Number of threads: ", os.environ['OMP_NUM_THREADS'] print "___________________________________________________" print print "CPU info:" cmd = "lscpu" #cpuinfo=subprocess.check_output(cmd, shell=True) # needs python >=2.7.0 process = subprocess.Popen(cmd, stdout=subprocess.PIPE, shell=True) cpuinfo = process.communicate()[0].lstrip('model name\t:').strip() print cpuinfo sys.exit(0) trunk-2018.02b/examples/test/performance/performance.db000066400000000000000000000100001324306050200230630ustar00rootroot00000000000000** This file contains an SQLite 2.1 database **(upW Wtablecpuscpus3CREATE TABLE cpus (id INTEGER PRIMARY KEY, CPUname MEDIUMTEXT) $&tableperformanceperformance4CREATE TABLE performance (id INTEGER PRIMARY KEY, j INTEGER, cpu INTEGER, rev INTEGER, score INTEGER, date )|L33Intel(R) Core(TM) i7 CPU 920 @ 2.67GHzAMD Phenom(tm) II X6 1090TH0 1127835262X 2127836800 3127837647 4127838042 1227834317 2227835222  3227835488 2227835222trunk-2018.02b/examples/test/periodic-geom-compare.py000066400000000000000000000027541324306050200225140ustar00rootroot00000000000000# coding: utf-8 # 2009 © Václav Šmilauer "Test and demonstrate use of PeriTriaxController." from yade import pack O.periodic=True O.cell.setBox(.1,.1,.1) O.cell.trsf=Matrix3().Identity; sp=pack.SpherePack() radius=5e-3 num=sp.makeCloud(Vector3().Zero,O.cell.refSize,radius,.6,-1,periodic=True) # min,max,radius,rRelFuzz,spheresInCell,periodic sp.toSimulation() # specify which family of geometry functors to use readParamsFromTable(noTableOk=True,geom='sc') from yade.params.table import geom if geom=='sc': loop=InteractionLoop([Ig2_Sphere_Sphere_ScGeom()],[Ip2_FrictMat_FrictMat_FrictPhys()],[Law2_ScGeom_FrictPhys_CundallStrack()]) elif geom=='l3': loop=InteractionLoop([Ig2_Sphere_Sphere_L3Geom()],[Ip2_FrictMat_FrictMat_FrictPhys()],[Law2_L3Geom_FrictPhys_ElPerfPl()]) elif geom=='l3a': loop=InteractionLoop([Ig2_Sphere_Sphere_L3Geom(approxMask=63)],[Ip2_FrictMat_FrictMat_FrictPhys()],[Law2_L3Geom_FrictPhys_ElPerfPl()]) else: raise ValueError('geom must be one of sc, d3d, l3, l3a (not %s)'%geom) O.engines=[ ForceResetter(), InsertionSortCollider([Bo1_Sphere_Aabb()],verletDist=.05*radius), loop, NewtonIntegrator(damping=.6), ] O.dt=0.5*PWaveTimeStep() try: from yade import qt qt.View() except: pass O.cell.velGrad=Matrix3(-.1,.03,0, 0,-.1,0, 0,0,-.1) O.saveTmp() #O.run(10000,True); #rrr=qt.Renderer(); rrr.intrAllWire,rrr.intrGeom=True,False if runningInBatch(): O.timingEnabled=True O.run(300000,True) O.timingEnabled from yade import timing timing.stats() trunk-2018.02b/examples/test/psd.py000066400000000000000000000034021324306050200171220ustar00rootroot00000000000000# encoding: utf-8 # # demonstrate how to generate sphere packing based on arbitrary PSD (particle size distribution) # show the difference between size-based and mass-based (≡ volume-based in our case) PSD # import matplotlib; matplotlib.rc('axes',grid=True) from yade import pack import pylab # PSD given as points of piecewise-linear function psdSizes,psdCumm=[.02,0.04,0.045,.05,.06,.08,.12],[0.,0.1,0.3,0.3,.3,.7,1.] pylab.plot(psdSizes,psdCumm,label='precribed mass PSD') sp0=pack.SpherePack(); sp0.makeCloud((0,0,0),(1,1,1),psdSizes=psdSizes,psdCumm=psdCumm,distributeMass=True) sp1=pack.SpherePack(); sp1.makeCloud((0,0,0),(1,1,1),psdSizes=psdSizes,psdCumm=psdCumm,distributeMass=True,num=5000) sp2=pack.SpherePack(); sp2.makeCloud((0,0,0),(1,1,1),psdSizes=psdSizes,psdCumm=psdCumm,distributeMass=True,num=20000) pylab.semilogx(*sp0.psd(bins=30,mass=True),label='Mass PSD of (free) %d random spheres'%len(sp0)) pylab.semilogx(*sp1.psd(bins=30,mass=True),label='Mass PSD of (imposed) %d random spheres'%len(sp1)) pylab.semilogx(*sp2.psd(bins=30,mass=True),label='Mass PSD of (imposed) %d random spheres (scaled down)'%len(sp2)) pylab.legend() # uniform distribution of size (sp3) and of mass (sp4) sp3=pack.SpherePack(); sp3.makeCloud((0,0,0),(1,1,1),rMean=0.03,rRelFuzz=2/3.,distributeMass=False); sp4=pack.SpherePack(); sp4.makeCloud((0,0,0),(1,1,1),rMean=0.03,rRelFuzz=2/3.,distributeMass=True); pylab.figure() pylab.plot(*(sp3.psd(mass=True)+('g',)+sp4.psd(mass=True)+('r',))) pylab.legend(['Mass PSD of size-uniform distribution','Mass PSD of mass-uniform distribution']) pylab.figure() pylab.plot(*(sp3.psd(mass=False)+('g',)+sp4.psd(mass=False)+('r',))) pylab.legend(['Size PSD of size-uniform distribution','Size PSD of mass-uniform distribution']) pylab.show() pylab.show()trunk-2018.02b/examples/test/qt4-attributes.py000066400000000000000000000152131324306050200212330ustar00rootroot00000000000000from PyQt4.QtCore import * from PyQt4.QtGui import * from PyQt4 import QtGui #from PyQt4 import Qwt5 import re import logging logging.basicConfig(level=logging.INFO) #from logging import debug,info,warning,error class AttrEditor(): def __init__(self,ser,attr): self.ser,self.attr=ser,attr self.hot=False self.widget=None def refresh(self): pass def update(self): pass def isHot(self,hot=True): "Called when the widget gets focus; mark it hot, change colors etc." if hot==self.hot: return self.hot=hot p=QPalette(); if hot: p.setColor(QPalette.WindowText,Qt.red); self.setPalette(p) self.repaint() print self.attr,('hot' if hot else 'cold') def sizeHint(self): return QSize(180,12) class AttrEditor_Bool(AttrEditor,QCheckBox): def __init__(self,parent,ser,attr): AttrEditor.__init__(self,ser,attr) QCheckBox.__init__(self,parent) self.clicked.connect(self.update) def refresh(self): self.setChecked(getattr(self.ser,self.attr)) def update(self): setattr(self.ser,self.attr,self.isChecked()) class AttrEditor_Int(AttrEditor,QSpinBox): def __init__(self,parent,ser,attr): AttrEditor.__init__(self,ser,attr) QSpinBox.__init__(self,parent) self.setRange(int(-1e10),int(1e10)); self.setSingleStep(1); self.valueChanged.connect(self.update) def refresh(self): self.setValue(getattr(self.ser,self.attr)) def update(self): setattr(self.ser,self.attr,self.value()); self.isHot(False) class AttrEditor_Str(AttrEditor,QLineEdit): def __init__(self,parent,ser,attr): AttrEditor.__init__(self,ser,attr) QLineEdit.__init__(self,parent) self.textEdited.connect(self.isHot) self.editingFinished.connect(self.update) def refresh(self): self.setText(getattr(self.ser,self.attr)) def update(self): setattr(self.ser,self.attr,str(self.text())); self.isHot(False) class AttrEditor_Float(AttrEditor,QLineEdit): def __init__(self,parent,ser,attr): AttrEditor.__init__(self,ser,attr) QLineEdit.__init__(self,parent) self.textEdited.connect(self.isHot) self.editingFinished.connect(self.update) def refresh(self): self.setText(str(getattr(self.ser,self.attr))) def update(self): try: setattr(self.ser,self.attr,float(self.text())) except ValueError: self.refresh() self.isHot(False) class AttrEditor_Vector3(AttrEditor,QFrame): def __init__(self,parent,ser,attr): AttrEditor.__init__(self,ser,attr) QFrame.__init__(self,parent) self.setContentsMargins(0,0,0,0) self.box=QHBoxLayout(self); self.box.setSpacing(0); self.box.setMargin(0) self.widgets=[] for i in range(3): w=QLineEdit(''); self.box.addWidget(w); self.widgets.append(w) w.textEdited.connect(self.isHot) w.editingFinished.connect(self.update) def refresh(self): val=getattr(self.ser,self.attr) for i in range(3): self.widgets[i].setText(str(val[i])) def update(self): try: val=getattr(self.ser,self.attr) for i in range(3): if self.widgets[i].isModified(): val[i]=float(self.widgets[i].text()) print 'setting',val setattr(self.ser,self.attr,val) except ValueError: self.refresh() self.isHot(False) class AttrEditor_ListStr(AttrEditor,QPlainTextEdit): def __init__(self,parent,ser,attr): AttrEditor.__init__(self,ser,attr) QPlainTextEdit.__init__(self,parent) self.setLineWrapMode(QPlainTextEdit.NoWrap) self.setFixedHeight(80) self.textChanged.connect(self.update) def refresh(self): lst=getattr(self.ser,self.attr) self.setPlainText('\n'.join(lst)) def update(self): if self.hasFocus(): self.isHot() t=self.toPlainText() setattr(self.ser,self.attr,str(t).strip().split('\n')) if not self.hasFocus(): self.isHot(False) class SerializableEditor(QWidget): "Class displaying and modifying serializable attributes of a yade object." import collections import logging # each attribute has one entry associated with itself class EntryData: def __init__(self,name,T): self.name,self.T=name,T self.lineNo,self.widget=None,None def __init__(self,ser,parent=None): "Construct window, *ser* is the object we want to show." QtGui.QWidget.__init__(self,parent) self.ser=ser self.entries=[] logging.debug('New Serializable of type %s'%ser.__class__.__name__) self.setWindowTitle(str(ser)) self.mkWidgets() self.refreshTimer=QTimer() self.refreshTimer.timeout.connect(self.refreshEvent) self.refreshTimer.start(500) def getListTypeFromDocstring(self,attr): "Guess type of array by scanning docstring for :yattrtype: and parsing its argument; ugly, but works." doc=getattr(self.ser.__class__,attr).__doc__ if doc==None: error("Attribute %s has no docstring."%attr) return None m=re.search(r':yattrtype:`([^`]*)`',doc) if not m: error("Attribute %s does not contain :yattrtype:`....` (docstring is '%s'"%(attr,doc)) return None cxxT=m.group(1) logging.debug('Got type "%s" from :yattrtype:'%cxxT) def vecTest(T,cxxT): regexp=r'^\s*(std\s*::)?\s*vector\s*<\s*(std\s*::)?\s*('+T+r')\s*>\s*$' m=re.match(regexp,cxxT) return m vecMap={ 'int':int,'long':int,'Body::id_t':long,'size_t':long, 'Real':float,'float':float,'double':float, 'Vector3r':Vector3,'Matrix3r':Matrix3, 'string':str } for T,ret in vecMap.items(): if vecTest(T,cxxT): logging.debug("Got type %s from cxx type %s"%(ret.__name__,cxxT)) return (ret,) error("Unable to guess python type from cxx type '%s'"%cxxT) return None def mkAttrEntries(self): d=self.ser.dict() for attr,val in self.ser.dict().items(): if isinstance(val,list): if len(val)==0: t=self.getListTypeFromDocstring(attr) else: t=(val[0].__class__,) # 1-tuple is list of the contained type else: t=val.__class__ #logging.debug('Attr %s is of type %s'%(attr,((t[0].__name__,) if isinstance(t,tuple) else t.__name__))) self.entries.append(self.EntryData(name=attr,T=t)) def mkWidget(self,entry): typeMap={bool:AttrEditor_Bool,str:AttrEditor_Str,int:AttrEditor_Int,float:AttrEditor_Float,Vector3:AttrEditor_Vector3,(str,):AttrEditor_ListStr} Klass=typeMap.get(entry.T,None) if not Klass: return None widget=Klass(self,self.ser,entry.name) widget.setFocusPolicy(Qt.StrongFocus) return widget def mkWidgets(self): self.mkAttrEntries() grid=QtGui.QGridLayout() for lineNo,entry in enumerate(self.entries): grid.addWidget(QtGui.QLabel(entry.name),lineNo,0) entry.widget=self.mkWidget(entry) if entry.widget: grid.addWidget(entry.widget,lineNo,1) else: grid.addWidget(QtGui.QLabel('unhandled type'),lineNo,1) self.setLayout(grid) self.refreshEvent() def refreshEvent(self): for e in self.entries: if e.widget and not e.widget.hot: e.widget.refresh() vr=VTKRecorder() s1=SerializableEditor(vr) s1.show() s2=SerializableEditor(OpenGLRenderer()) s2.show() #import time #def aa(): # print vr.dict(); time.sleep(1) trunk-2018.02b/examples/test/qt4-pyqglviewer.py000066400000000000000000000006771324306050200214330ustar00rootroot00000000000000# install pyqglviewer from https://launchpad.net/~nakednous/+ppa-packages from PyQGLViewer import * class Viewer(QGLViewer): def __init__(self): QGLViewer.__init__(self) self.renderer=OpenGLRenderer() def init(self): self.setAxisIsDrawn(True) self.setGridIsDrawn(True) def draw(self): self.renderer.render() O.bodies.append([sphere((0,0,-.4),.3),sphere((0,0,.4),.3)]) viewer=Viewer() viewer.setWindowTitle('Yade') viewer.show() trunk-2018.02b/examples/test/remove-body.py000066400000000000000000000023641324306050200205720ustar00rootroot00000000000000# -*- coding: utf-8 -*- # © Václav Šmilauer # # Test case for sphere-facet interaction. #O.bodyContainer="BodyVector" O.engines=[ ForceResetter(), InsertionSortCollider([Bo1_Sphere_Aabb(),Bo1_Facet_Aabb()],nBins=5,sweepLength=5e-3), #SpatialQuickSortCollider(), InteractionLoop( [Ig2_Facet_Sphere_ScGeom()], [Ip2_FrictMat_FrictMat_FrictPhys()], [Law2_ScGeom_FrictPhys_CundallStrack()], ), NewtonIntegrator(damping=0.01,gravity=[0,0,-10]), ] O.bodies.append([ facet([[-1,-1,0],[1,-1,0],[0,1,0]],fixed=True,color=[1,0,0]), facet([[1,-1,0],[0,1,0,],[1,.5,.5]],fixed=True) ]) #Gl1_Facet(normals=True) import random,sys def addRandomSphere(): return O.bodies.append(sphere([random.gauss(0,1),random.gauss(0,1),random.uniform(1,2)],random.uniform(.02,.05))) O.bodies[len(O.bodies)-1].state.vel=[random.gauss(0,.1),random.gauss(0,.1),random.gauss(0,.1)] for i in range(0,100): addRandomSphere() O.dt=1e-4 #O.run() O.saveTmp('init') from yade import qt qt.Controller() qt.View() if 1: for i in range(0,1000): O.run(50,True); if random.choice([True,False]): idOld=random.randint(2,len(O.bodies)-1) O.bodies.erase(idOld) print "-%d"%idOld, else: idNew=addRandomSphere() print "+%d"%idNew, sys.stdout.flush() trunk-2018.02b/examples/test/shear.py000066400000000000000000000022211324306050200174340ustar00rootroot00000000000000# # script for testing ScGeom shear computation # Runs the same 2-sphere setup for useShear=False and for useShear=True # O.bodies.append([ sphere([0,0,0],.5000001,fixed=True,color=(1,0,0)), sphere([0,0,1],.5000001,fixed=False,color=(0,0,1)) ]) O.engines=[ ForceResetter(), InsertionSortCollider([Bo1_Sphere_Aabb(),Bo1_Facet_Aabb()]), IGeomDispatcher([Ig2_Sphere_Sphere_ScGeom()]), IPhysDispatcher([Ip2_FrictMat_FrictMat_FrictPhys()]), RotationEngine(rotationAxis=[1,1,0],angularVelocity=.001,ids=[1]), ElasticContactLaw(label='elasticLaw'), PyRunner(iterPeriod=10000,command='interInfo()'), NewtonIntegrator() ] O.dt=1e-8 O.saveTmp('init') def interInfo(): i=O.interactions[0,1] if 1: print O.time,i.phys.shearForce else: r1,r2=O.bodies[0].shape.radius,O.bodies[1].shape.radius theta=[e['angularVelocity'] for e in O.engines if e.name=='RotationEngine'][0] f=.5*(r1+r2)*theta*O.time*i.phys['ks'] print O.time,i.phys.shearForce,f,i.phys.shearForce[0]/f print '=========== no shear ============' O.run(100000,True) nIter=O.iter print '============= shear =============' O.loadTmp('init') #elasticLaw['useShear']=True O.run(nIter,True) quit() trunk-2018.02b/examples/test/sphere-sphere-ViscElBasic-peri.py000066400000000000000000000017231324306050200241740ustar00rootroot00000000000000# -*- coding: utf-8 # Testing sphere-sphere interaction in periodic case. # Pass, if the spheres moves along the X axis, interacting through the period. sphereRadius=0.1 tc=0.001# collision time en=1 # normal restitution coefficient es=1 # tangential restitution coefficient density=2700 frictionAngle=radians(35)# sphereMat=O.materials.append(ViscElMat(density=density,frictionAngle=frictionAngle,tc=tc,en=en,et=es)) # Spheres sphId=O.bodies.append([ sphere( (0.4,0.5,0.5), 0.1, material=sphereMat), sphere( (0.6,0.5,0.5), 0.1, material=sphereMat) ]) O.bodies[sphId[-1]].state.vel=(0.5,0,0) O.bodies[sphId[0]].state.vel=(-0.5,0,0) ## Engines O.engines=[ ForceResetter(), InsertionSortCollider([Bo1_Sphere_Aabb(),Bo1_Facet_Aabb()]), InteractionLoop( [Ig2_Sphere_Sphere_ScGeom()], [Ip2_ViscElMat_ViscElMat_ViscElPhys()], [Law2_ScGeom_ViscElPhys_Basic()], ), NewtonIntegrator(damping=0), ] O.periodic=True O.cell.setBox(1,1,1) O.dt=.01*tc O.saveTmp() trunk-2018.02b/examples/test/subdomain-balancer.py000066400000000000000000000017641324306050200220730ustar00rootroot00000000000000from yade import pack,timing readParamsFromTable(noTableOk=True,num=12000) import yade.params.table sp=pack.SpherePack() sp.makeCloud((0,0,0),(1,1,1),.03*((12000./yade.params.table.num)**(1/3.)),.5) sp.toSimulation() O.bodies.append(wall((0,0,0),axis=2)) O.bodies.append(wall((0,0,0),axis=1)) #O.bodies.append(wall((0,0,0),axis=0)) #O.bodies.append(wall((0,2,0),axis=1)) #O.bodies.append(wall((2,0,0),axis=0)) O.engines=([SubdomainBalancer(axesOrder='xyz',colorize=True)] if 'SubdomainBalancer' in dir() else [])+[ ForceResetter(), InsertionSortCollider([Bo1_Sphere_Aabb(),Bo1_Wall_Aabb()],verletDist=.05*.05), InteractionLoop([Ig2_Sphere_Sphere_ScGeom(),Ig2_Wall_Sphere_ScGeom()],[Ip2_FrictMat_FrictMat_FrictPhys()],[Law2_ScGeom_FrictPhys_CundallStrack()]), NewtonIntegrator(gravity=(0,0,-10)), #PyRunner(iterPeriod=5000,command='O.pause(); timing.stats();') ] O.dt=PWaveTimeStep() O.timingEnabled=True #O.step(); #O.run(10000,True) timing.stats() from yade import qt qt.View() #O.step() #O.run(5000,True) trunk-2018.02b/examples/test/test-sphere-facet-corner.py000066400000000000000000000016541324306050200231540ustar00rootroot00000000000000# -*- encoding=utf-8 -*- ## PhysicalParameters Young = 7e6 Poisson = 0.2 Density=2700 # Append a material mat=O.materials.append(FrictMat(young=Young,poisson=Poisson,density=Density,frictionAngle=26)) O.bodies.append([ sphere([0,0,0.6],0.25,material=mat), facet([[-0.707,-0.707,0.1],[0,1.414,0],[1.414,0,0]],dynamic=False,color=[1,0,0],material=mat), facet([[0,1.414,0],[1.414,0,0],[0.707,0.707,-2.0]],dynamic=False,color=[1,0,0],material=mat)]) ## Engines O.engines=[ ForceResetter(), InsertionSortCollider([Bo1_Sphere_Aabb(),Bo1_Facet_Aabb()]), InteractionLoop( [Ig2_Facet_Sphere_ScGeom()], [Ip2_FrictMat_FrictMat_FrictPhys()], [Law2_ScGeom_FrictPhys_CundallStrack()], ), NewtonIntegrator(damping=.3,gravity=(0,0,-10)) ] ## Timestep O.dt=5e-6 O.saveTmp() try: from yade import qt renderer=qt.Renderer() renderer.wire=True renderer.intrPhys=True qt.Controller() except ImportError: pass trunk-2018.02b/examples/test/test-sphere-facet.py000066400000000000000000000043601324306050200216630ustar00rootroot00000000000000# -*- coding: utf-8 -*- # © Václav Šmilauer # # Test case for sphere-facet interaction preserving the original contact orientation. # Z-gravity is being increased every 4000 iterations, the sphere dives more into the facet and stabilizes, # etc. This process continues even if the sphere center passes on the other side of the facet and, # (if distant transient interactions are allowed in the collider) if the sphere passes in its entirety to # the other side of the facet. The interaction, however, still pushes in the same sense. # # After the gravity reaches some value, it is reset and the sphere should be pushed from the facet towards # its original position. When the contact on the original side is lost, the interaction should be deleted. # # # The only tunable sign places the sphere either on the top ot at the bottom of the facet # and sets gravity accordingly. It can be +1 or -1 # sign=-1 # ## PhysicalParameters Young = 7e6 Poisson = 0.2 Density=2700 # Append a material mat=O.materials.append(FrictMat(young=Young,poisson=Poisson,density=Density,frictionAngle=26)) O.engines=[ ForceResetter(), InsertionSortCollider([Bo1_Sphere_Aabb(),Bo1_Facet_Aabb()]), InteractionLoop( [Ig2_Facet_Sphere_ScGeom()], [Ip2_FrictMat_FrictMat_FrictPhys()], [Law2_ScGeom_FrictPhys_CundallStrack()], ), NewtonIntegrator(damping=.3,gravity=(0,0,-10),label='integrator'), PyRunner(iterPeriod=4000,command='setGravity()'), ] O.bodies.append([ facet([[-1,-1,0],[1,-1,0],[0,1,0]],fixed=True,color=[1,0,0],material=mat), sphere([0,0,sign*.49999],radius=.5,wire=True,material=mat), ]) O.timingEnabled=True O.saveTmp() O.dt=1e-4 print '** virgin dispatch matrix:' O.engines[2].lawDispatcher.dispMatrix() print '** class indices' O.run(1000,True) print '** used dispatch matrix' O.engines[2].lawDispatcher.dispMatrix() def setGravity(): gz=integrator.gravity[2] integrator.gravity=[0,0,1.05*gz] if abs(gz)>=2500: print "Gravity reset & slow down" O.dt=1e-6; integrator.gravity=[0,0,0] if abs(gz)>0: print gz try: from yade import qt renderer=qt.Renderer() renderer.intrGeom=True qt.Controller() except ImportError: pass from yade import timing O.run(100000,True) timing.stats() timing.reset() O.loadTmp() O.run(100000,True) timing.stats() trunk-2018.02b/examples/test/test_Ip2_FrictMat_CpmMat_FrictPhys.py000066400000000000000000000023601324306050200250540ustar00rootroot00000000000000from yade import * from yade import plot,qt import sys young=25e9 poisson=.2 sigmaT=3e6 frictionAngle=atan(1) density=4800 ## 4800 # twice the density, since porosity is about .5 (.62) epsCrackOnset=1e-4 relDuctility=300 intRadius=1.5 concMat = O.materials.append(CpmMat(young=young,poisson=poisson,density=4800,sigmaT=3e6,relDuctility=30,epsCrackOnset=1e-4,neverDamage=False)) frictMat = O.materials.append(FrictMat(young=young,poisson=poisson,density=4800)) b1 = sphere((0,0,0),1,material=concMat) b1.state.vel = Vector3(1,0,0) b2 = sphere((0,5,0),1,material=concMat) b2.state.vel = Vector3(2,-2,0) b3 = sphere((0,-4,0),1,material=frictMat) b3.state.vel = Vector3(1,3,0) b4 = facet(((2,-5,-5),(2,-5,10),(2,10,-5)),material=frictMat) O.bodies.append((b1,b2,b3,b4)) O.dt = 5e-6 O.engines=[ ForceResetter(), InsertionSortCollider([Bo1_Sphere_Aabb(),Bo1_Facet_Aabb()]), InteractionLoop( [ Ig2_Sphere_Sphere_ScGeom(), Ig2_Facet_Sphere_ScGeom() ], [ Ip2_CpmMat_CpmMat_CpmPhys(), Ip2_FrictMat_CpmMat_FrictPhys(), Ip2_FrictMat_FrictMat_FrictPhys(), ], [ Law2_ScGeom_CpmPhys_Cpm(), Law2_ScGeom_FrictPhys_CundallStrack() ] ), NewtonIntegrator(label='newton'), ] O.step() try: from yade import qt qt.View() except: O.run() trunk-2018.02b/examples/test/triax-basic.py000066400000000000000000000044241324306050200205470ustar00rootroot00000000000000# encoding: utf-8 # the script demonstrates a simple case of triaxial simulation using TriaxialCompressionEngine. More elaborated examples can be found in the triax-tutorial folder from yade import pack sp=pack.SpherePack() ## corners of the initial packing mn,mx=Vector3(0,0,0),Vector3(10,10,10) ## box between mn and mx, avg radius ± ½(20%), 2k spheres sp.makeCloud(minCorner=mn,maxCorner=mx,rRelFuzz=.2,num=2000) ## create material #0, which will be used as default O.materials.append(FrictMat(young=15e6,poisson=.4,frictionAngle=radians(30),density=2600,label='spheres')) O.materials.append(FrictMat(young=15e6,poisson=.4,frictionAngle=0,density=0,label='frictionless')) ## copy spheres from the packing into the scene ## use default material, don't care about that for now O.bodies.append([sphere(center,rad,material='spheres') for center,rad in sp]) ## create walls around the packing walls=aabbWalls(thickness=1e-10,material='frictionless') wallIds=O.bodies.append(walls) triax=TriaxialCompressionEngine( wall_bottom_id=wallIds[2], wall_top_id=wallIds[3], wall_left_id=wallIds[0], wall_right_id=wallIds[1], wall_back_id=wallIds[4], wall_front_id=wallIds[5], internalCompaction=False, ## define the rest of triax params here ## see in pkg/dem/PreProcessor/TriaxialTest.cpp:524 etc ## which are assigned in the c++ preprocessor actually sigmaIsoCompaction=-50e3, sigmaLateralConfinement=-50e3, max_vel=10, strainRate=0.01, label="triax" ) O.engines=[ ForceResetter(), InsertionSortCollider([Bo1_Sphere_Aabb(),Bo1_Box_Aabb()]), InteractionLoop( [Ig2_Sphere_Sphere_ScGeom(),Ig2_Box_Sphere_ScGeom()], [Ip2_FrictMat_FrictMat_FrictPhys()], [Law2_ScGeom_FrictPhys_CundallStrack()] ), GlobalStiffnessTimeStepper(active=1,timeStepUpdateInterval=100,timestepSafetyCoefficient=0.8), triax, # you can add TriaxialStateRecorder and such here… NewtonIntegrator(damping=.4) ] from yade import plot O.engines=O.engines[0:5]+[PyRunner(iterPeriod=20,command='history()',label='recorder')]+O.engines[5:7] def history(): plot.addData(e11=-triax.strain[0], e22=-triax.strain[1], e33=-triax.strain[2], s11=-triax.stress(0)[0], s22=-triax.stress(2)[1], s33=-triax.stress(4)[2], i=O.iter) plot.plots={'i':('e11','e22','e33',None,'s11','s22','s33')} O.saveTmp() plot.plot()trunk-2018.02b/examples/test/triax-cohesive.py000066400000000000000000000067361324306050200213030ustar00rootroot00000000000000# encoding: utf-8 # 2012 ©Bruno Chareyre # This variant of triax-basic.py shows the usage of cohesive contact laws and moments at contacts from yade import pack sp=pack.SpherePack() ## corners of the initial packing mn,mx=Vector3(0,0,0),Vector3(10,10,10) ## box between mn and mx, avg radius ± ½(20%), 2k spheres sp.makeCloud(minCorner=mn,maxCorner=mx,rRelFuzz=.2,num=600) ## create material #0, which will be used as default O.materials.append(CohFrictMat(young=15e6,poisson=0.4,density=2600,frictionAngle=radians(30),normalCohesion=1e6,shearCohesion=1e6,momentRotationLaw=True,etaRoll=0.1,label='spheres')) O.materials.append(FrictMat(young=15e6,poisson=.4,frictionAngle=0,density=0,label='frictionlessWalls')) ## copy spheres from the packing into the scene O.bodies.append([sphere(center,rad,material='spheres') for center,rad in sp]) ## create walls around the packing walls=aabbWalls(material='frictionlessWalls') wallIds=O.bodies.append(walls) triax=TriaxialCompressionEngine( wall_bottom_id=wallIds[2], wall_top_id=wallIds[3], wall_left_id=wallIds[0], wall_right_id=wallIds[1], wall_back_id=wallIds[4], wall_front_id=wallIds[5], internalCompaction=False, sigmaIsoCompaction=-50e3, sigmaLateralConfinement=-50e3, max_vel=10, strainRate=0.03, label="triax" ) O.engines=[ ForceResetter(), InsertionSortCollider([Bo1_Sphere_Aabb(),Bo1_Box_Aabb()]), InteractionLoop( #box-sphere interactions will be the simple normal-shear law, we use ScGeom for them [Ig2_Sphere_Sphere_ScGeom6D(),Ig2_Box_Sphere_ScGeom()], #Boxes will be frictional (FrictMat), so the sphere-box physics is FrictMat vs. CohFrictMat, the Ip type will be found via the inheritance tree (CohFrictMat is a FrictMat) and will result in FrictPhys interaction physics #and will result in a FrictPhys [Ip2_FrictMat_FrictMat_FrictPhys(),Ip2_CohFrictMat_CohFrictMat_CohFrictPhys(label="cohesiveIp")], #Finally, two different contact laws for sphere-box and sphere-sphere [Law2_ScGeom_FrictPhys_CundallStrack(),Law2_ScGeom6D_CohFrictPhys_CohesionMoment( useIncrementalForm=True, #useIncrementalForm is turned on as we want plasticity on the contact moments always_use_moment_law=False, #if we want "rolling" friction even if the contact is not cohesive (or cohesion is broken), we will have to turn this true somewhere label='cohesiveLaw')] ), GlobalStiffnessTimeStepper(active=1,timeStepUpdateInterval=100,timestepSafetyCoefficient=0.5), triax, NewtonIntegrator(damping=.4) ] from yade import plot O.engines=O.engines[0:5]+[PyRunner(iterPeriod=20,command='history()',label='recorder')]+O.engines[5:7] def history(): plot.addData(e11=-O.engines[4].strain[0], e22=-O.engines[4].strain[1], e33=-O.engines[4].strain[2], s11=-O.engines[4].stress(0)[0], s22=-O.engines[4].stress(2)[1], s33=-O.engines[4].stress(4)[2], i=O.iter) plot.plots={'i':(('e11',"bo"),('e22',"ro"),('e33',"go"),None,('s11',"bx"),('s22',"rx"),('s33',"gx"))} plot.plot() print "computing, be patient..." #First run without moment and without cohesion O.run(7000,True) #This will reload the autosaved compacted sample O.reload() #second run with rolling friction O.engines[2].lawDispatcher.functors[1].always_use_moment_law = True O.run(6000,True) O.reload() #third run with rolling friction + cohesion O.engines[2].lawDispatcher.functors[1].always_use_moment_law = True #We assign cohesion to all contacts at the next iteration O.engines[2].physDispatcher.functors[1].setCohesionNow = True O.run(6000,True) trunk-2018.02b/examples/test/unstructuredGrid.py000066400000000000000000000011501324306050200217070ustar00rootroot00000000000000################################################################################ # # TODO # ################################################################################ O.engines = [ InsertionSortCollider([Bo1_Tetra_Aabb(),Bo1_Facet_Aabb()]), ] ug = utils.UnstructuredGrid() v = { 1: (0,0,0), 3: (1,0,0), 5: (0,1,0), 6: (0,0,1), 2: (-1,0,0), } ct = { 1: (1,3,5), 4: (1,3,6), 8: (1,5,2,6), } ug.setup(v,ct,wire=False) ug.toSimulation() O.step() from yade import qt qt.View() def setNewCoords(): ug.setPositionsOfNodes({ 1: (-1,-1,-1), 3: (2,0,.5), 5: (0,3,0), 6: (0,0,.5), 2: (-1,0,1) }) trunk-2018.02b/examples/test/unv-read/000077500000000000000000000000001324306050200175045ustar00rootroot00000000000000trunk-2018.02b/examples/test/unv-read/shell.unv000066400000000000000000000434151324306050200213540ustar00rootroot00000000000000 -1 2411 1 0 0 11 2.0000000000000000E+00 2.0000000000000000E+00 0.0000000000000000E+00 2 0 0 11 -2.0000000000000000E+00 2.0000000000000000E+00 0.0000000000000000E+00 3 0 0 11 -2.0000000000000000E+00 -2.0000000000000000E+00 0.0000000000000000E+00 4 0 0 11 2.0000000000000000E+00 -2.0000000000000000E+00 0.0000000000000000E+00 5 0 0 11 -2.0000000000000000E+00 0.0000000000000000E+00 5.0000000000000000E+00 6 0 0 11 0.0000000000000000E+00 -2.0000000000000000E+00 5.0000000000000000E+00 7 0 0 11 2.0000000000000000E+00 0.0000000000000000E+00 5.0000000000000000E+00 8 0 0 11 0.0000000000000000E+00 2.0000000000000000E+00 4.0000000000000000E+00 9 0 0 11 0.0000000000000000E+00 2.0000000000000000E+00 0.0000000000000000E+00 10 0 0 11 -2.0000000000000000E+00 0.0000000000000000E+00 0.0000000000000000E+00 11 0 0 11 0.0000000000000000E+00 -2.0000000000000000E+00 0.0000000000000000E+00 12 0 0 11 2.0000000000000000E+00 0.0000000000000000E+00 0.0000000000000000E+00 13 0 0 11 -2.0000000000000000E+00 -1.3333333333333333E+00 1.6666666666666665E+00 14 0 0 11 -2.0000000000000000E+00 -6.6666666666666674E-01 3.3333333333333330E+00 15 0 0 11 -9.9999999999999989E-01 -1.0000000000000002E+00 5.0000000000000000E+00 16 0 0 11 6.6666666666666663E-01 -2.0000000000000000E+00 3.3333333333333335E+00 17 0 0 11 1.3333333333333333E+00 -2.0000000000000000E+00 1.6666666666666667E+00 18 0 0 11 1.0000000000000002E+00 -9.9999999999999989E-01 5.0000000000000000E+00 19 0 0 11 2.0000000000000000E+00 6.6666666666666663E-01 3.3333333333333335E+00 20 0 0 11 2.0000000000000000E+00 1.3333333333333333E+00 1.6666666666666667E+00 21 0 0 11 -6.6666666666666663E-01 2.0000000000000000E+00 2.6666666666666665E+00 22 0 0 11 -1.3333333333333333E+00 2.0000000000000000E+00 1.3333333333333335E+00 23 0 0 11 1.0000000000000000E+00 1.0000000000000000E+00 4.5000000000000000E+00 24 0 0 11 -1.0000000000000000E+00 1.0000000000000000E+00 4.5000000000000000E+00 25 0 0 11 1.0204493184023047E+00 -7.7864862082500297E-01 0.0000000000000000E+00 26 0 0 11 -1.0201187217404240E+00 -7.7812918103283835E-01 0.0000000000000000E+00 27 0 0 11 3.0453560848275885E-04 -4.8202294743143659E-01 0.0000000000000000E+00 28 0 0 11 7.3726756851835962E-01 5.7599033144961864E-01 0.0000000000000000E+00 29 0 0 11 -7.4105532977775734E-01 5.7416265686003176E-01 0.0000000000000000E+00 30 0 0 11 -4.5335881725944732E-01 -1.7106968539974061E+00 1.2783890157485245E+00 31 0 0 11 -1.2134510285411346E+00 -9.2246231036720572E-01 3.9994005896714038E+00 32 0 0 11 -6.8830573726249644E-01 -1.3864928868902922E+00 2.7549859841550672E+00 33 0 0 11 2.9354685262518415E-01 -1.7467305604786523E+00 2.1909364193444514E+00 34 0 0 11 -3.9030144358386054E-01 -1.4566594712643419E+00 3.9933632500875595E+00 35 0 0 11 1.7106968523456150E+00 -4.5335882551308387E-01 1.2783890184401399E+00 36 0 0 11 9.2246208165153232E-01 -1.2134513531061526E+00 3.9994006917316649E+00 37 0 0 11 1.3864928554125591E+00 -6.8830581491128173E-01 2.7549860091085718E+00 38 0 0 11 1.7467305560005697E+00 2.9354683729707765E-01 2.1909364243104328E+00 39 0 0 11 1.4566592554516338E+00 -3.9030178030592577E-01 3.9933633564664008E+00 40 0 0 11 -4.2824811618915049E-01 1.8389978287971704E+00 7.2413926942816254E-01 41 0 0 11 1.1661801462264525E+00 8.7072239860184431E-01 3.8450614971766779E+00 42 0 0 11 7.2901780443584108E-01 1.2662209225398113E+00 2.5182690674205359E+00 43 0 0 11 -1.4336639743190543E-01 1.6442723473109442E+00 1.6085811466545532E+00 44 0 0 11 7.8730263538445089E-01 1.6659819261076851E+00 1.0233616742521123E+00 45 0 0 11 -1.8760034743520608E+00 -3.4804595344083816E-01 8.4764344257365620E-01 46 0 0 11 -1.2112412597444577E+00 8.1931532181887712E-01 3.5964020714959761E+00 47 0 0 11 -1.5610645352608976E+00 5.0178591478731538E-01 2.0222341159297650E+00 48 0 0 11 -1.7512252703454867E+00 8.8962091633202278E-01 8.1096264863768097E-01 49 0 0 11 -6.1203710584758919E-01 6.2139416891930609E-03 4.7722723518183638E+00 50 0 0 11 3.5060158157754001E-01 -5.7446690939163791E-01 4.8816744009823605E+00 51 0 0 11 3.4509485944386387E-01 5.8653845581367858E-01 4.5890839104346739E+00 -1 -1 2412 1 11 2 1 7 2 0 0 0 1 9 2 11 2 1 7 2 0 0 0 9 2 3 11 2 1 7 2 0 0 0 2 10 4 11 2 1 7 2 0 0 0 10 3 5 11 2 1 7 2 0 0 0 3 11 6 11 2 1 7 2 0 0 0 11 4 7 11 2 1 7 2 0 0 0 4 12 8 11 2 1 7 2 0 0 0 12 1 9 11 2 1 7 2 0 0 0 3 13 10 11 2 1 7 2 0 0 0 13 14 11 11 2 1 7 2 0 0 0 14 5 12 11 2 1 7 2 0 0 0 5 15 13 11 2 1 7 2 0 0 0 15 6 14 11 2 1 7 2 0 0 0 6 16 15 11 2 1 7 2 0 0 0 16 17 16 11 2 1 7 2 0 0 0 17 4 17 11 2 1 7 2 0 0 0 6 18 18 11 2 1 7 2 0 0 0 18 7 19 11 2 1 7 2 0 0 0 7 19 20 11 2 1 7 2 0 0 0 19 20 21 11 2 1 7 2 0 0 0 20 1 22 11 2 1 7 2 0 0 0 8 21 23 11 2 1 7 2 0 0 0 21 22 24 11 2 1 7 2 0 0 0 22 2 25 11 2 1 7 2 0 0 0 7 23 26 11 2 1 7 2 0 0 0 23 8 27 11 2 1 7 2 0 0 0 8 24 28 11 2 1 7 2 0 0 0 24 5 29 41 2 1 7 3 10 26 3 30 41 2 1 7 3 4 25 12 31 41 2 1 7 3 25 4 11 32 41 2 1 7 3 11 27 25 33 41 2 1 7 3 26 27 11 34 41 2 1 7 3 11 3 26 35 41 2 1 7 3 25 28 12 36 41 2 1 7 3 12 28 1 37 41 2 1 7 3 2 29 10 38 41 2 1 7 3 29 26 10 39 41 2 1 7 3 27 26 29 40 41 2 1 7 3 28 29 9 41 41 2 1 7 3 29 2 9 42 41 2 1 7 3 1 28 9 43 41 2 1 7 3 25 27 28 44 41 2 1 7 3 29 28 27 45 41 2 1 7 3 31 15 5 46 41 2 1 7 3 31 5 14 47 41 2 1 7 3 30 11 17 48 41 2 1 7 3 13 3 30 49 41 2 1 7 3 30 3 11 50 41 2 1 7 3 33 30 17 51 41 2 1 7 3 31 14 32 52 41 2 1 7 3 32 14 13 53 41 2 1 7 3 32 13 30 54 41 2 1 7 3 31 34 15 55 41 2 1 7 3 17 11 4 56 41 2 1 7 3 32 34 31 57 41 2 1 7 3 34 6 15 58 41 2 1 7 3 16 6 34 59 41 2 1 7 3 33 16 32 60 41 2 1 7 3 17 16 33 61 41 2 1 7 3 30 33 32 62 41 2 1 7 3 34 32 16 63 41 2 1 7 3 36 18 6 64 41 2 1 7 3 36 6 16 65 41 2 1 7 3 35 12 20 66 41 2 1 7 3 35 17 4 67 41 2 1 7 3 35 4 12 68 41 2 1 7 3 17 35 37 69 41 2 1 7 3 38 35 20 70 41 2 1 7 3 36 16 37 71 41 2 1 7 3 37 16 17 72 41 2 1 7 3 36 39 18 73 41 2 1 7 3 20 12 1 74 41 2 1 7 3 37 39 36 75 41 2 1 7 3 39 7 18 76 41 2 1 7 3 19 7 39 77 41 2 1 7 3 38 19 37 78 41 2 1 7 3 20 19 38 79 41 2 1 7 3 35 38 37 80 41 2 1 7 3 39 37 19 81 41 2 1 7 3 19 41 7 82 41 2 1 7 3 21 42 43 83 41 2 1 7 3 42 44 43 84 41 2 1 7 3 42 21 8 85 41 2 1 7 3 41 23 7 86 41 2 1 7 3 21 43 22 87 41 2 1 7 3 2 22 40 88 41 2 1 7 3 40 9 2 89 41 2 1 7 3 23 41 8 90 41 2 1 7 3 20 1 44 91 41 2 1 7 3 42 8 41 92 41 2 1 7 3 40 44 9 93 41 2 1 7 3 20 44 42 94 41 2 1 7 3 42 41 19 95 41 2 1 7 3 40 43 44 96 41 2 1 7 3 1 9 44 97 41 2 1 7 3 40 22 43 98 41 2 1 7 3 42 19 20 99 41 2 1 7 3 46 24 8 100 41 2 1 7 3 46 5 24 101 41 2 1 7 3 45 48 10 102 41 2 1 7 3 45 10 3 103 41 2 1 7 3 47 14 46 104 41 2 1 7 3 47 45 13 105 41 2 1 7 3 45 3 13 106 41 2 1 7 3 48 2 10 107 41 2 1 7 3 5 46 14 108 41 2 1 7 3 47 13 14 109 41 2 1 7 3 48 22 2 110 41 2 1 7 3 22 47 21 111 41 2 1 7 3 46 8 21 112 41 2 1 7 3 46 21 47 113 41 2 1 7 3 48 47 22 114 41 2 1 7 3 45 47 48 115 41 2 1 7 3 15 6 50 116 41 2 1 7 3 50 6 18 117 41 2 1 7 3 49 15 50 118 41 2 1 7 3 8 24 51 119 41 2 1 7 3 49 24 5 120 41 2 1 7 3 49 5 15 121 41 2 1 7 3 50 18 7 122 41 2 1 7 3 49 51 24 123 41 2 1 7 3 51 23 8 124 41 2 1 7 3 50 7 51 125 41 2 1 7 3 51 7 23 126 41 2 1 7 3 51 49 50 -1 trunk-2018.02b/examples/test/unv-read/unvRead.py000066400000000000000000000002501324306050200214570ustar00rootroot00000000000000from yade import ymport facets = ymport.unv('shell.unv',shift=(100,200,300),scale=1000) O.bodies.append([f for f in facets]) try: import qt qt.View() except: pass trunk-2018.02b/examples/test/unv-read/unvReadVTKExport.py000066400000000000000000000002551324306050200232530ustar00rootroot00000000000000from yade import ymport,export f,n,e = ymport.unv('shell.unv',returnElementMap=True) O.bodies.append(f) vtk = export.VTKExporter('test') vtk.exportFacetsAsMesh(elements=e) trunk-2018.02b/examples/test/vtk-exporter/000077500000000000000000000000001324306050200204355ustar00rootroot00000000000000trunk-2018.02b/examples/test/vtk-exporter/vtkExporter.py000066400000000000000000000017571324306050200233560ustar00rootroot00000000000000from yade import export,polyhedra_utils mat = PolyhedraMat() O.bodies.append(( polyhedra_utils.polyhedra(mat,(1,2,3),0), polyhedra_utils.polyhedralBall(2,20,mat,(-2,-2,4)), )) O.bodies[-1].state.pos = (-2,-2,-2) O.bodies[-1].state.ori = Quaternion((1,1,2),1) O.bodies[-2].state.pos = (-2,-2,3) O.bodies[-2].state.ori = Quaternion((1,2,0),1) O.step() O.bodies.append(( sphere((0,0,0),1), sphere((0,3,0),1), sphere((0,2,4),2), sphere((0,5,2),1.5), facet([Vector3(0,-3,-1),Vector3(0,-2,5),Vector3(5,4,0)]), facet([Vector3(0,-3,-1),Vector3(0,-2,5),Vector3(-5,4,0)]), )) for i,j in ((0,1),(0,2),(0,3),(1,2),(1,3),(2,3)): createInteraction(i+2,j+2) vtkExporter = export.VTKExporter('/tmp/vtkExporterTesting') vtkExporter.exportSpheres(what=[('dist','b.state.pos.norm()')]) vtkExporter.exportFacets(what=[('pos','b.state.pos')]) vtkExporter.exportInteractions(what=[('kn','i.phys.kn')]) vtkExporter.exportContactPoints(what=[('nn','i.geom.normal')]) vtkExporter.exportPolyhedra(what=[('n','b.id')]) trunk-2018.02b/examples/test/vtkPeriodicCell.py000066400000000000000000000015411324306050200214210ustar00rootroot00000000000000###################################################################### # Simple script to test VTK export of periodic cell ###################################################################### # enable periodic cell O.periodic=True # insert some bodies sp = randomPeriPack(radius=1,initSize=(10,20,30),memoizeDb='/tmp/vtkPeriodicCell.sqlite') sp.toSimulation() # transform the cell a bit O.cell.hSize *= Matrix3(1,.1,.1, .1,1,0, .1,0,1) # skew the cell in xy and xz plane O.cell.hSize *= Matrix3(1,0,0, 0,.8,.6, 0,-.6,.8) # rotate it along x axis O.step() # test of export.VTKExporter from yade import export vtk1 = export.VTKExporter('/tmp/vtkPeriodicCell-VTKExporter') vtk1.exportSpheres() vtk1.exportPeriodicCell() # test of VTKReorder vtk2 = VTKRecorder(fileName='/tmp/vtkPeriodicCell-VTKRecorder-',recorders=['spheres','pericell']) vtk2() # do the export trunk-2018.02b/examples/test/wall.py000066400000000000000000000017071324306050200173010ustar00rootroot00000000000000""" Show basic wall functionality (infinite axis-aligned planes). """ O.materials.append(FrictMat(young=30e9,density=1000,poisson=.2,frictionAngle=.5)) O.bodies.append([ wall(1,axis=2,sense=-1), wall(-5,axis=0,sense=1), wall(1,axis=1), wall((1,0,0),0), sphere([0,0,0],.5), sphere([-4,-4,-3],.5) ]) Gl1_Wall(div=10) from yade import qt qt.Controller() qt.View() O.engines=[ ForceResetter(), InsertionSortCollider([Bo1_Sphere_Aabb(),Bo1_Facet_Aabb(),Bo1_Wall_Aabb()]), InteractionLoop( [Ig2_Sphere_Sphere_ScGeom(),Ig2_Facet_Sphere_ScGeom(),Ig2_Wall_Sphere_ScGeom()], [Ip2_FrictMat_FrictMat_FrictPhys()], [Law2_ScGeom_FrictPhys_CundallStrack()], ), NewtonIntegrator(damping=0.01,gravity=[1e2,1e2,1e2]), ] O.dt=PWaveTimeStep() O.save('/tmp/a.xml') O.saveTmp() O.run() #O.bodies.append([ # facet([[-1,-1,0],[1,-1,0],[0,1,0]],dynamic=False,color=[1,0,0],young=1e3), # facet([[1,-1,0],[0,1,0,],[1,.5,.5]],dynamic=False,young=1e3) #]) #import random trunk-2018.02b/examples/test/wm3-wrap.py000066400000000000000000000011441324306050200200120ustar00rootroot00000000000000# constructors, constants as static objects x,y,z,one=Vector3.UnitX,Vector3.UnitY,Vector3.UnitZ,Vector3(1,1,1) x2=Vector3(x) # conversions to sequence types list(x2) tuple(x2) # operations and operators x+y+z==one x.dot(y)==0 x.cross(y)==z # methods one.norm() # quaternions # construction (implicit conversion of 3-tuple or list of length 3 to Vector3) q1=Quaternion((0,0,1),pi/2) q2=Quaternion(Vector3(0,0,1),pi/2) q1==q2 # rotating vector q1*x==y # almost, due to rounding # rotation composition q1*q1*x # inverse rotation q1.conjugate() # convert to axis-angle representation axis,angle=q1.toAxisAngle() trunk-2018.02b/examples/tetra/000077500000000000000000000000001324306050200161235ustar00rootroot00000000000000trunk-2018.02b/examples/tetra/oneTetra.py000066400000000000000000000015261324306050200202620ustar00rootroot00000000000000################################################################################ # # Script to test tetra gl functions and prescribed motion # ################################################################################ v1 = (0,0,0) v2 = (1,0,0) v3 = (0,1,0) v4 = (0,0,1) t1 = tetra((v1,v2,v3,v4),color=(0,1,0)) O.bodies.append((t1)) def changeVelocity(): if O.iter == 50000: t1.state.vel = (-1,0,0) if O.iter == 100000: t1.state.vel = (0,1,-1) if O.iter == 150000: t1.state.vel = (0,0,0) t1.state.angMom = (0,0,10) if O.iter == 200000: O.pause() O.engines = [ ForceResetter(), InsertionSortCollider([Bo1_Tetra_Aabb()]), InteractionLoop([],[],[]), NewtonIntegrator(), PyRunner(iterPeriod=1,command="changeVelocity()"), ] O.step() try: from yade import qt qt.View() except: pass O.dt = 1e-5 t1.state.vel = (1,0,0) O.run() trunk-2018.02b/examples/tetra/oneTetraPoly.py000066400000000000000000000015361324306050200211270ustar00rootroot00000000000000################################################################################ # # Script to test tetra gl functions and prescribed motion # ################################################################################ v1 = (0,0,0) v2 = (1,0,0) v3 = (0,1,0) v4 = (0,0,1) t1 = tetraPoly((v1,v2,v3,v4),color=(0,1,0)) O.bodies.append((t1)) def changeVelocity(): if O.iter == 50000: t1.state.vel = (-1,0,0) if O.iter == 100000: t1.state.vel = (0,1,-1) if O.iter == 150000: t1.state.vel = (0,0,0) t1.state.angMom = (0,0,10) if O.iter == 200000: O.pause() O.engines = [ ForceResetter(), InsertionSortCollider([Bo1_Polyhedra_Aabb()]), InteractionLoop([],[],[]), NewtonIntegrator(), PyRunner(iterPeriod=1,command="changeVelocity()"), ] O.step() try: from yade import qt qt.View() except: pass O.dt = 1e-5 t1.state.vel = (1,0,0) O.run() trunk-2018.02b/examples/tetra/twoTetras.py000066400000000000000000000100771324306050200204760ustar00rootroot00000000000000################################################################################ # # Python script to test tetra-tetra contact detection for different possible # contact modes. Some tests are run twice to test the symmetry of the law. # # It runs several tests, making pause before each one. If run with GUI, you can # adjust the viewer # # During the test, momentum, angular momentum and kinetic energy is tracked in # plot.data. You can use plot.plot() to see the results (in sime modes the # energy is not conserved for instace). # ################################################################################ from yade import qt,plot O.materials.append(ElastMat(young=1e10)) O.dt = 4e-5 O.engines = [ ForceResetter(), InsertionSortCollider([Bo1_Tetra_Aabb()]), InteractionLoop( [Ig2_Tetra_Tetra_TTetraSimpleGeom()], [Ip2_ElastMat_ElastMat_NormPhys()], [Law2_TTetraSimpleGeom_NormPhys_Simple()] ), NewtonIntegrator(damping=0), PyRunner(iterPeriod=500,command="addPlotData()"), PyRunner(iterPeriod=1,command="runExamples()"), ] def addPlotData(): p = utils.momentum() l = utils.angularMomentum() plot.addData( i = O.iter, e = utils.kineticEnergy(), px = p[0], py = p[1], pz = p[2], lx = l[0], ly = l[1], lz = l[2], ) def prepareExample(vertices1,vertices2,vel1=(0,0,0),vel2=(0,0,0),amom1=(0,0,0),amom2=(0,0,0),label=''): O.interactions.clear() O.bodies.clear() t1 = tetra(vertices1,color=(0,1,0),wire=False) t2 = tetra(vertices2,color=(0,1,1),wire=False) O.bodies.append((t1,t2)) t1.state.vel = vel1 t2.state.vel = vel2 t1.state.angMom = amom1 t2.state.angMom = amom2 if label: print label O.pause() O.step() def runExamples(): dt = 20000 if O.iter == 1: vertices1 = ((0,0,0),(2,0,0),(0,2,0),(0,0,2)) vertices2 = ((1,1,1),(3,1,1),(1,3,1),(1,1,3)) prepareExample(vertices1,vertices2,vel2=(-1,-1,-1),label='\ntesting vertex-triangle contact...\n') if O.iter == 1*dt: plot.data = {} vertices1 = ((1,1,1),(3,1,1),(1,3,1),(1,1,3)) vertices2 = ((0,0,0),(2,0,0),(0,2,0),(0,0,2)) prepareExample(vertices1,vertices2,vel1=(-1,-1,-1),label='\ntesting vertex-triangle contact 2...\n') elif O.iter == 2*dt: vertices1 = ((0,0,0),(0,0,1.1),(1,0,1),(0,1,.9)) vertices2 = ((0,.5,1.4),(-.5,.5,.6),(-1.6,0,1),(-1.6,1.1,1)) prepareExample(vertices1,vertices2,vel2=(1,0,0),amom2=(0,10,0),label='\ntesting edge-edge contact\n') elif O.iter == 3*dt: vertices1 = ((0,0,0),(0,0,1.1),(1,0,1),(0,1,.9)) vertices2 = ((-.5,.5,.6),(0,.5,1.4),(-1.6,1.1,1),(-1.6,0,1)) prepareExample(vertices1,vertices2,vel2=(1,0,0),amom2=(0,10,0),label='\ntesting edge-edge contact\n') elif O.iter == 4*dt: vertices1 = ((.1,-.4,-.3),(-.3,-.4,2),(3,-.2,2),(-.1,3,2)) vertices2 = ((.5,1.5,2.3),(1.5,.5,2.3),(2,2,3),(0,0,3)) prepareExample(vertices1,vertices2,vel2=(0,0,-1),amom2=(0,0,0),label='\ntesting edge-triangle contact\n') elif O.iter == 5*dt: vertices1 = ((.1,-.4,-.3),(-.3,-.4,2),(3,-.2,2),(-.1,3,2)) vertices2 = ((-.5,2.5,2.3),(2.5,-.5,2.3),(2,2,3),(0,0,3)) prepareExample(vertices1,vertices2,vel2=(0,0,-1),amom2=(0,0,0),label='\ntesting edge-triangle contact 2\n') elif O.iter == 6*dt: vertices1 = ((1,0,0),(0,1,0),(0,0,1),(1,1,1)) vertices2 = ((.5,.5,1.2),(0,.1,2),(2,0,2),(0,1,2)) prepareExample(vertices1,vertices2,vel2=(0,0,-1),label='\ntesting vertex-edge contact\n') elif O.iter == 7*dt: vertices1 = ((.5,.5,1.2),(0,.1,2),(2,0,2),(0,1,2)) vertices2 = ((1,0,0),(0,1,0),(0,0,1),(1,1,1)) prepareExample(vertices1,vertices2,vel1=(0,0,-1),label='\ntesting vertex-edge contact 2\n') elif O.iter == 8*dt: vertices1 = ((0,0,0),(1,0,0),(0,1,0),(0,0,1)) vertices2 = ((0,0,1.2),(.9,.8,2),(-.7,.61,2.3),(0,-.7,2.1)) prepareExample(vertices1,vertices2,vel2=(0,0,-1),label='\ntesting vertex-edge contact\n') elif O.iter == 9*dt: vertices1 = ((0,0,1.2),(.9,.8,2),(-.7,.61,2.3),(0,-.7,2.1)) vertices2 = ((0,0,0),(1,0,0),(0,1,0),(0,0,1)) prepareExample(vertices1,vertices2,vel1=(0,0,-1),label='\ntesting vertex-edge contact 2\n') elif O.iter == 10*dt: O.pause() viewer = qt.View() plot.plots = {'i':'e','i ':('px','py','pz'),'i ':('lx','ly','lz')} O.step() O.step() O.step() trunk-2018.02b/examples/tetra/twoTetrasPoly.py000066400000000000000000000103031324306050200213320ustar00rootroot00000000000000################################################################################ # # Python script to test tetra-tetra contact detection for different possible # contact modes. Some tests are run twice to test the symmetry of the law. # # It runs several tests, making pause before each one. If run with GUI, you can # adjust the viewer # # During the test, momentum, angular momentum and kinetic energy is tracked in # plot.data. You can use plot.plot() to see the results (in sime modes the # energy is not conserved for instace). # ################################################################################ from yade import qt,plot mat = PolyhedraMat() mat.density = 2600 #kg/m^3 mat.Ks = 2000000 mat.Kn = 1E9 #Pa mat.frictionAngle = 0.5 #rad O.materials.append(mat) O.dt = 4e-5 O.engines = [ ForceResetter(), InsertionSortCollider([Bo1_Polyhedra_Aabb()]), InteractionLoop( [Ig2_Polyhedra_Polyhedra_PolyhedraGeom()], [Ip2_PolyhedraMat_PolyhedraMat_PolyhedraPhys()], [Law2_PolyhedraGeom_PolyhedraPhys_Volumetric()] ), NewtonIntegrator(damping=0), PyRunner(iterPeriod=500,command="addPlotData()"), PyRunner(iterPeriod=1,command="runExamples()"), ] def addPlotData(): p = utils.momentum() l = utils.angularMomentum() plot.addData( i = O.iter, e = utils.kineticEnergy(), px = p[0], py = p[1], pz = p[2], lx = l[0], ly = l[1], lz = l[2], ) def prepareExample(vertices1,vertices2,vel1=(0,0,0),vel2=(0,0,0),amom1=(0,0,0),amom2=(0,0,0),label=''): O.interactions.clear() O.bodies.clear() t1 = tetraPoly(vertices1,color=(0,1,0),wire=False) t2 = tetraPoly(vertices2,color=(0,1,1),wire=False) O.bodies.append((t1,t2)) t1.state.vel = vel1 t2.state.vel = vel2 t1.state.angMom = amom1 t2.state.angMom = amom2 if label: print label O.pause() O.step() def runExamples(): dt = 20000 if O.iter == 1: vertices1 = ((0,0,0),(2,0,0),(0,2,0),(0,0,2)) vertices2 = ((1,1,1),(3,1,1),(1,3,1),(1,1,3)) prepareExample(vertices1,vertices2,vel2=(-1,-1,-1),label='\ntesting vertex-triangle contact...\n') if O.iter == 1*dt: plot.data = {} vertices1 = ((1,1,1),(3,1,1),(1,3,1),(1,1,3)) vertices2 = ((0,0,0),(2,0,0),(0,2,0),(0,0,2)) prepareExample(vertices1,vertices2,vel1=(-1,-1,-1),label='\ntesting vertex-triangle contact 2...\n') elif O.iter == 2*dt: vertices1 = ((0,0,0),(0,0,1.1),(1,0,1),(0,1,.9)) vertices2 = ((0,.5,1.4),(-.5,.5,.6),(-1.6,0,1),(-1.6,1.1,1)) prepareExample(vertices1,vertices2,vel2=(1,0,0),amom2=(0,10,0),label='\ntesting edge-edge contact\n') elif O.iter == 3*dt: vertices1 = ((0,0,0),(0,0,1.1),(1,0,1),(0,1,.9)) vertices2 = ((-.5,.5,.6),(0,.5,1.4),(-1.6,1.1,1),(-1.6,0,1)) prepareExample(vertices1,vertices2,vel2=(1,0,0),amom2=(0,10,0),label='\ntesting edge-edge contact\n') elif O.iter == 4*dt: vertices1 = ((.1,-.4,-.3),(-.3,-.4,2),(3,-.2,2),(-.1,3,2)) vertices2 = ((.5,1.5,2.3),(1.5,.5,2.3),(2,2,3),(0,0,3)) prepareExample(vertices1,vertices2,vel2=(0,0,-1),amom2=(0,0,0),label='\ntesting edge-triangle contact\n') elif O.iter == 5*dt: vertices1 = ((.1,-.4,-.3),(-.3,-.4,2),(3,-.2,2),(-.1,3,2)) vertices2 = ((-.5,2.5,2.3),(2.5,-.5,2.3),(2,2,3),(0,0,3)) prepareExample(vertices1,vertices2,vel2=(0,0,-1),amom2=(0,0,0),label='\ntesting edge-triangle contact 2\n') elif O.iter == 6*dt: vertices1 = ((1,0,0),(0,1,0),(0,0,1),(1,1,1)) vertices2 = ((.5,.5,1.2),(0,.1,2),(2,0,2),(0,1,2)) prepareExample(vertices1,vertices2,vel2=(0,0,-1),label='\ntesting vertex-edge contact\n') elif O.iter == 7*dt: vertices1 = ((.5,.5,1.2),(0,.1,2),(2,0,2),(0,1,2)) vertices2 = ((1,0,0),(0,1,0),(0,0,1),(1,1,1)) prepareExample(vertices1,vertices2,vel1=(0,0,-1),label='\ntesting vertex-edge contact 2\n') elif O.iter == 8*dt: vertices1 = ((0,0,0),(1,0,0),(0,1,0),(0,0,1)) vertices2 = ((0,0,1.2),(.9,.8,2),(-.7,.61,2.3),(0,-.7,2.1)) prepareExample(vertices1,vertices2,vel2=(0,0,-1),label='\ntesting vertex-edge contact\n') elif O.iter == 9*dt: vertices1 = ((0,0,1.2),(.9,.8,2),(-.7,.61,2.3),(0,-.7,2.1)) vertices2 = ((0,0,0),(1,0,0),(0,1,0),(0,0,1)) prepareExample(vertices1,vertices2,vel1=(0,0,-1),label='\ntesting vertex-edge contact 2\n') elif O.iter == 10*dt: O.pause() viewer = qt.View() plot.plots = {'i':'e','i ':('px','py','pz'),'i ':('lx','ly','lz')} O.step() O.step() O.step() trunk-2018.02b/examples/timeStepperUsage.py000066400000000000000000000141501324306050200206450ustar00rootroot00000000000000# coding: utf-8 # 2012 © Bruno Chareyre "Test and demonstrate the use of timestepper and density scaling." from yade import pack,qt,timing O.periodic=True O.cell.hSize=Matrix3(0.1, 0, 0, 0 ,0.1, 0, 0, 0, 0.1) n=1000 sp=pack.SpherePack() num=sp.makeCloud(Vector3().Zero,O.cell.refSize,rRelFuzz=.5,num=n,periodic=True,seed=1) O.bodies.append([sphere(s[0],s[1]) for s in sp]) #make the problem more interesting by giving a smaller mass to one of the bodies, different stiffness present similar difficulties O.bodies[3].state.mass = O.bodies[n-1].state.mass*0.01 O.engines=[ ForceResetter(), InsertionSortCollider([Bo1_Sphere_Aabb()]), InteractionLoop( [Ig2_Sphere_Sphere_ScGeom()], [Ip2_FrictMat_FrictMat_FrictPhys()], [Law2_ScGeom_FrictPhys_CundallStrack()] ), GlobalStiffnessTimeStepper(timeStepUpdateInterval=10,label='ts'), PeriTriaxController(dynCell=True,mass=0.2,maxUnbalanced=0.01, relStressTol=0.01,goal=(-1e4,-1e4,0),stressMask=3,globUpdate=5, maxStrainRate=(10.,10.,10.),doneHook='triaxDone()',label='triax'), NewtonIntegrator(damping=.2,label="newton"), ] phase=0 def triaxDone(): global phase if phase==0: O.pause() O.timingEnabled=1 qt.View() O.saveTmp() #====== First, we won't use a timestepper at all, we fix O.dt using PWave timestep =========# timing.reset() O.dt=0.8*PWaveTimeStep() #for fair comparison, we use the same safety factor as in GS timestepper, allthough many scripts use 0.5 or even 0.1*PWaveTimeStep() ts.active=False O.run(100000,True);#it will actually stop before 100k iterations as soon as the packing is stable print "--------------------------------" print "Fixed dt = 0.8 * PWave timestep:" print "--------------------------------" timing.stats() #====== Now we use the timestepper to adjust dt dynamicaly =========# O.reload() timing.reset() O.dt=100000000 #or whatever ts.active=True O.run(100000,True); print "--------------------------------------------------" print "dt dynamicaly set with GlobalStiffness timesteper:" print "--------------------------------------------------" timing.stats() #====== And finaly, the timestepper with density scaling =========# O.reload() timing.reset() O.dt=1000000 #or whatever #We force dt=1. The inertia of bodies will adjusted automatically... newton.densityScaling=True #... but not that of cell, hence we have to adjust it or the problem becomes unstable triax.mass /= (0.8*PWaveTimeStep())**2 triax.maxStrainRate *= 0.8*PWaveTimeStep() O.run(1000000,True); print "--------------------------------------------------------------------" print "dt dynamicaly set with GlobalStiffness timesteper + density scaling:" print "--------------------------------------------------------------------" timing.stats() #_____ TYPICAL RESULTS (n=1000)____ #-------------------------------- #Fixed dt = 0.8 * PWave timestep: #-------------------------------- #Name Count Time Rel. time #------------------------------------------------------------------------------------------------------- #ForceResetter 58166 455809us 0.82% #InsertionSortCollider 23641 17777093us 31.92% #InteractionLoop 58166 21812997us 39.17% #"ts" 0 0us 0.00% #"triax" 58166 4329739us 7.77% #"newton" 58166 11314162us 20.32% #TOTAL 55689802us 100.00% #-------------------------------------------------- #dt dynamicaly set with GlobalStiffness timesteper: #-------------------------------------------------- #Name Count Time Rel. time #------------------------------------------------------------------------------------------------------- #ForceResetter 37471 296255us 0.82% #InsertionSortCollider 6589 5608966us 15.57% #InteractionLoop 37471 18167532us 50.44% #"ts" 3785 477777us 1.33% #"triax" 37471 3801429us 10.55% #"newton" 37471 7664305us 21.28% #TOTAL 36016267us 100.00% #WARN /home/3S-LAB/bchareyre/yade/yade-git/trunk/pkg/dem/NewtonIntegrator.cpp:282 set_densityScaling: GlobalStiffnessTimeStepper found in O.engines and adjusted to match this setting. Revert in the timestepper if you don't want the scaling adjusted automatically. #-------------------------------------------------------------------- #dt dynamicaly set with GlobalStiffness timesteper + density scaling: #-------------------------------------------------------------------- #Name Count Time Rel. time #------------------------------------------------------------------------------------------------------- #ForceResetter 31666 251568us 1.55% #InsertionSortCollider 429 429116us 2.64% #InteractionLoop 31666 8135458us 50.06% #"ts" 3168 244340us 1.50% #"triax" 31666 1282567us 7.89% #"newton" 31666 5909985us 36.36% #TOTAL 16253037us 100.00% trunk-2018.02b/examples/triax-tutorial/000077500000000000000000000000001324306050200177745ustar00rootroot00000000000000trunk-2018.02b/examples/triax-tutorial/README000066400000000000000000000006351324306050200206600ustar00rootroot00000000000000The material in this folder was used for the numerical sessions of Alert's "Olek Zinkiewicz" course on discrete mechanics, Grenoble, 2011. They show basic simulation of triaxial tests (session 1) and advanced boundary conditions for stress-strain probing (session 2). Authors: Nejib Hada, Luc Sibille, Bruno Chareyre. Yade's version should be bzr2830 or later, although some earlier versions could work as well. trunk-2018.02b/examples/triax-tutorial/script-session1.py000066400000000000000000000237511324306050200234240ustar00rootroot00000000000000# -*- coding: utf-8 -*- #************************************************************************* # Copyright (C) 2010 by Bruno Chareyre * # bruno.chareyre_at_grenoble-inp.fr * # * # This program is free software; it is licensed under the terms of the * # GNU General Public License v2 or later. See file LICENSE for details. * #*************************************************************************/ ## This script details the simulation of a triaxial test on sphere packings using Yade ## See the associated pdf file for detailed exercises ## the algorithms presented here have been used in published papers, namely: ## * Chareyre et al. 2002 (http://www.geosyntheticssociety.org/Resources/Archive/GI/src/V9I2/GI-V9-N2-Paper1.pdf) ## * Chareyre and Villard 2005 (https://yade-dem.org/w/images/1/1b/Chareyre&Villard2005_licensed.pdf) ## * Scholtès et al. 2009 (http://dx.doi.org/10.1016/j.ijengsci.2008.07.002) ## * Tong et al.2012 (http://dx.doi.org/10.2516/ogst/2012032) ## ## Most of the ideas were actually developped during my PhD. ## If you want to know more on micro-macro relations evaluated by triaxial simulations ## AND if you can read some french, it is here: http://tel.archives-ouvertes.fr/docs/00/48/68/07/PDF/Thesis.pdf from yade import pack ############################################ ### DEFINING VARIABLES AND MATERIALS ### ############################################ # The following 5 lines will be used later for batch execution nRead=readParamsFromTable( num_spheres=1000,# number of spheres compFricDegree = 30, # contact friction during the confining phase key='_triax_base_', # put you simulation's name here unknownOk=True ) from yade.params import table num_spheres=table.num_spheres# number of spheres key=table.key targetPorosity = 0.43 #the porosity we want for the packing compFricDegree = table.compFricDegree # initial contact friction during the confining phase (will be decreased during the REFD compaction process) finalFricDegree = 30 # contact friction during the deviatoric loading rate=-0.02 # loading rate (strain rate) damp=0.2 # damping coefficient stabilityThreshold=0.01 # we test unbalancedForce against this value in different loops (see below) young=5e6 # contact stiffness mn,mx=Vector3(0,0,0),Vector3(1,1,1) # corners of the initial packing ## create materials for spheres and plates O.materials.append(FrictMat(young=young,poisson=0.5,frictionAngle=radians(compFricDegree),density=2600,label='spheres')) O.materials.append(FrictMat(young=young,poisson=0.5,frictionAngle=0,density=0,label='walls')) ## create walls around the packing walls=aabbWalls([mn,mx],thickness=0,material='walls') wallIds=O.bodies.append(walls) ## use a SpherePack object to generate a random loose particles packing sp=pack.SpherePack() clumps=False #turn this true for the same example with clumps if clumps: ## approximate mean rad of the futur dense packing for latter use volume = (mx[0]-mn[0])*(mx[1]-mn[1])*(mx[2]-mn[2]) mean_rad = pow(0.09*volume/num_spheres,0.3333) ## define a unique clump type (we could have many, see clumpCloud documentation) c1=pack.SpherePack([((-0.2*mean_rad,0,0),0.5*mean_rad),((0.2*mean_rad,0,0),0.5*mean_rad)]) ## generate positions and input them in the simulation sp.makeClumpCloud(mn,mx,[c1],periodic=False) sp.toSimulation(material='spheres') O.bodies.updateClumpProperties()#get more accurate clump masses/volumes/inertia else: sp.makeCloud(mn,mx,-1,0.3333,num_spheres,False, 0.95,seed=1) #"seed" make the "random" generation always the same O.bodies.append([sphere(center,rad,material='spheres') for center,rad in sp]) #or alternatively (higher level function doing exactly the same): #sp.toSimulation(material='spheres') ############################ ### DEFINING ENGINES ### ############################ triax=TriaxialStressController( ## TriaxialStressController will be used to control stress and strain. It controls particles size and plates positions. ## this control of boundary conditions was used for instance in http://dx.doi.org/10.1016/j.ijengsci.2008.07.002 maxMultiplier=1.+2e4/young, # spheres growing factor (fast growth) finalMaxMultiplier=1.+2e3/young, # spheres growing factor (slow growth) thickness = 0, ## switch stress/strain control using a bitmask. What is a bitmask, huh?! ## Say x=1 if stess is controlled on x, else x=0. Same for for y and z, which are 1 or 0. ## Then an integer uniquely defining the combination of all these tests is: mask = x*1 + y*2 + z*4 ## to put it differently, the mask is the integer whose binary representation is xyz, i.e. ## "100" (1) means "x", "110" (3) means "x and y", "111" (7) means "x and y and z", etc. stressMask = 7, internalCompaction=True, # If true the confining pressure is generated by growing particles ) newton=NewtonIntegrator(damping=damp) O.engines=[ ForceResetter(), InsertionSortCollider([Bo1_Sphere_Aabb(),Bo1_Box_Aabb()]), InteractionLoop( [Ig2_Sphere_Sphere_ScGeom(),Ig2_Box_Sphere_ScGeom()], [Ip2_FrictMat_FrictMat_FrictPhys()], [Law2_ScGeom_FrictPhys_CundallStrack()] ), ## We will use the global stiffness of each body to determine an optimal timestep (see https://yade-dem.org/w/images/1/1b/Chareyre&Villard2005_licensed.pdf) GlobalStiffnessTimeStepper(active=1,timeStepUpdateInterval=100,timestepSafetyCoefficient=0.8), triax, TriaxialStateRecorder(iterPeriod=100,file='WallStresses'+table.key), newton ] #Display spheres with 2 colors for seeing rotations better Gl1_Sphere.stripes=0 if nRead==0: yade.qt.Controller(), yade.qt.View() ## UNCOMMENT THE FOLLOWING SECTIONS ONE BY ONE ## DEPENDING ON YOUR EDITOR, IT COULD BE DONE ## BY SELECTING THE CODE BLOCKS BETWEEN THE SUBTITLES ## AND PRESSING CTRL+SHIFT+D ####################################### ### APPLYING CONFINING PRESSURE ### ####################################### #the value of (isotropic) confining stress defines the target stress to be applied in all three directions triax.goal1=triax.goal2=triax.goal3=-10000 #while 1: #O.run(1000, True) ##the global unbalanced force on dynamic bodies, thus excluding boundaries, which are not at equilibrium #unb=unbalancedForce() #print 'unbalanced force:',unb,' mean stress: ',triax.meanStress #if unbtargetPorosity: ## we decrease friction value and apply it to all the bodies and contacts #compFricDegree = 0.95*compFricDegree #setContactFriction(radians(compFricDegree)) #print "\r Friction: ",compFricDegree," porosity:",triax.porosity, #sys.stdout.flush() ## while we run steps, triax will tend to grow particles as the packing ## keeps shrinking as a consequence of decreasing friction. Consequently ## porosity will decrease #O.run(500,1) #O.save('compactedState'+key+'.yade.gz') #print "### Compacted state saved ###" ############################## ### DEVIATORIC LOADING ### ############################## ##We move to deviatoric loading, let us turn internal compaction off to keep particles sizes constant #triax.internalCompaction=False ## Change contact friction (remember that decreasing it would generate instantaneous instabilities) #setContactFriction(radians(finalFricDegree)) ##set stress control on x and z, we will impose strain rate on y #triax.stressMask = 5 ##now goal2 is the target strain rate #triax.goal2=rate ## we define the lateral stresses during the test, here the same 10kPa as for the initial confinement. #triax.goal1=-10000 #triax.goal3=-10000 ##we can change damping here. What is the effect in your opinion? #newton.damping=0.1 ##Save temporary state in live memory. This state will be reloaded from the interface with the "reload" button. #O.saveTmp() ##################################################### ### Example of how to record and plot data ### ##################################################### #from yade import plot ### a function saving variables #def history(): #plot.addData(e11=-triax.strain[0], e22=-triax.strain[1], e33=-triax.strain[2], #ev=-triax.strain[0]-triax.strain[1]-triax.strain[2], #s11=-triax.stress(triax.wall_right_id)[0], #s22=-triax.stress(triax.wall_top_id)[1], #s33=-triax.stress(triax.wall_front_id)[2], #i=O.iter) #if 1: ## include a periodic engine calling that function in the simulation loop #O.engines=O.engines[0:5]+[PyRunner(iterPeriod=20,command='history()',label='recorder')]+O.engines[5:7] ##O.engines.insert(4,PyRunner(iterPeriod=20,command='history()',label='recorder')) #else: ## With the line above, we are recording some variables twice. We could in fact replace the previous ## TriaxialRecorder ## by our periodic engine. Uncomment the following line: #O.engines[4]=PyRunner(iterPeriod=20,command='history()',label='recorder') #O.run(100,True) ### declare what is to plot. "None" is for separating y and y2 axis #plot.plots={'i':('e11','e22','e33',None,'s11','s22','s33')} ### the traditional triaxial curves would be more like this: ##plot.plots={'e22':('s11','s22','s33',None,'ev')} ## display on the screen (doesn't work on VMware image it seems) #plot.plot() ##### PLAY THE SIMULATION HERE WITH "PLAY" BUTTON OR WITH THE COMMAND O.run(N) ##### ## In that case we can still save the data to a text file at the the end of the simulation, with: #plot.saveDataTxt('results'+key) ##or even generate a script for gnuplot. Open another terminal and type "gnuplot plotScriptKEY.gnuplot: #plot.saveGnuplot('plotScript'+key) trunk-2018.02b/examples/triax-tutorial/script-session2.py000066400000000000000000000175671324306050200234350ustar00rootroot00000000000000# -*- coding: utf-8 -*- from yade import pack num_spheres=500 ## corners of the initial packing mn,mx=Vector3(0,0,0),Vector3(1,1,1) thick = 0.01 compFricDegree = 2 rate=0.2 damp=0.1 stabilityThreshold=0.001 key='_define_a_name_' ## create material #0, which will be used as default O.materials.append(FrictMat(young=5e6,poisson=0.5,frictionAngle=radians(compFricDegree),density=2600,label='spheres')) O.materials.append(FrictMat(young=5e6,poisson=0.5,frictionAngle=0,density=0,label='walls')) ## create walls around the packing walls=aabbWalls([mn,mx],thickness=thick,material='walls') wallIds=O.bodies.append(walls) sp=pack.SpherePack() sp.makeCloud(mn,mx,-1,0.3333,num_spheres,False, 0.95) volume = (mx[0]-mn[0])*(mx[1]-mn[1])*(mx[2]-mn[2]) mean_rad = pow(0.09*volume/num_spheres,0.3333) clumps=False if clumps: c1=pack.SpherePack([((-0.2*mean_rad,0,0),0.5*mean_rad),((0.2*mean_rad,0,0),0.5*mean_rad)]) sp.makeClumpCloud((0,0,0),(1,1,1),[c1],periodic=False) O.bodies.append([sphere(center,rad,material='spheres') for center,rad in sp]) standalone,clumps=sp.getClumps() for clump in clumps: O.bodies.clump(clump) for i in clump[1:]: O.bodies[i].shape.color=O.bodies[clump[0]].shape.color #sp.toSimulation() else: O.bodies.append([sphere(center,rad,material='spheres') for center,rad in sp]) O.dt=.5*PWaveTimeStep() # initial timestep, to not explode right away O.usesTimeStepper=True triax=ThreeDTriaxialEngine( maxMultiplier=1.005, finalMaxMultiplier=1.002, thickness = thick, stressControl_1 = False, stressControl_2 = False, stressControl_3 = False, ## Independant stress values for anisotropic loadings sigma1=-10000, sigma2=-10000, sigma3=-10000, internalCompaction=True, Key=key, ) newton=NewtonIntegrator(damping=damp) O.engines=[ ForceResetter(), InsertionSortCollider([Bo1_Sphere_Aabb(),Bo1_Box_Aabb()],verletDist=-mean_rad*0.06), InteractionLoop( [Ig2_Sphere_Sphere_ScGeom(),Ig2_Box_Sphere_ScGeom()], [Ip2_FrictMat_FrictMat_FrictPhys()], [Law2_ScGeom_FrictPhys_CundallStrack()] ), GlobalStiffnessTimeStepper(active=1,timeStepUpdateInterval=100,timestepSafetyCoefficient=0.8, defaultDt=4*PWaveTimeStep()), triax, TriaxialStateRecorder(iterPeriod=100,file='WallStresses'+key), newton ] #Display spheres with 2 colors for seeing rotations better Gl1_Sphere.stripes=0 yade.qt.Controller(), yade.qt.View() while 1: O.run(1000, True) #the global unbalanced force on dynamic bodies, thus excluding boundaries, which are not at equilibrium unb=unbalancedForce() #average stress #note: triax.stress(k) returns a stress vector, so we need to keep only the normal component meanS=(triax.stress(triax.wall_right_id)[0]+triax.stress(triax.wall_top_id)[1]+triax.stress(triax.wall_front_id)[2])/3 print 'unbalanced force:',unb,' mean stress: ',meanS if unb>/Type/Annot/Rect[345.6 320.7 381.7 334.5]>> endobj 3 0 obj <>/Type/Annot/Rect[223.9 265.5 260 279.3]>> endobj 7 0 obj <>stream x]M0 c`W"eC?l'EjrȿyV!af0; sn>¥2L׷KZ{m,m pW,ݖa6x ܅.4 0,kӅs󹙾4אk󡋏K%|<`-Uڱ i bmV: C߳*f6Eבrr ʮʵ+(׌; WƵvCֽSx=+l #n_c/&LZI+0+eW[0k܏מC9/q^G3t0=<]t_ѿ=8xG=55NRӿĻsmHǝ /S\WX+e  Dq_pB8?c 0˴yì3iwާqB~u endstream endobj 9 0 obj <>stream xݼ{|SǙ?<Ϝs$]e[|mc`|;e m 8BI 5iЍ4%M۸IvIЖMҸ-/%a4o6>s$sI}޿^:gg<3yG = Wo$:2>5#o@wf_WE[B,a"awz1n; de Uycm#cƽ7fjFJ +$t}]CH?q*/Q&lg+ם_f¢PYyErfUfں9s>NbB1zÇ;N\~7^Ӌ|e-4CIrO~Ez $N֑-H#SOtɾQqr3|Ir<7$'X_Lahȝ,#EEQ#^uԷɣt?YHٸ|01ŒDZ_mܿ*t/ZGh߈x?Uw.2l.K#()t:J*Y~yY0-+;;˗-/Y|Ӣ bm-ɍцsjgϪY(/+.* 3|Y2 :Q@ZIo0 X9!"F7yo䔑ssrS w.[^m{So4ki'o+ᛔ0_D 0%gm7IoKu}-f,N;?P[^FNhua(U9QPe J4lHŗv4}e RFD+ETSjH:Vu{lr}%*oLqwײoޔ9*7Jv-+}_{$ FJ J!,؊ݷmݗ*W;iA x':}{ݩVZmluiwg[k~_g^9[@|OdFRK;3q/Y~M{KN=l_޹/ [PSp<ʺ/}}.Rb B >eDgnB[bX9-d,[^2]L}r29E뚕K#)jj[ީdfKHru6W*̞mٗlT_\:Quj1;*l90$8ӆn_J^9 4P>Χ<1E':ۗۗvuf+I`tg!5NV"o+MsR5P զNpinF2؜c p.MŢX۷җQLf94L$.iQHL9l{;ޔdmcQ"l_%n]',at 3r_/T}.ytw߾|+ܟ-`r٭~68qF+y Yfsy- wUQe!Оh*/Cet,=!=˻:_@':?ٴD:_"+Tʨ"^a%-ÈFw 2 A> Dii@V4MH34YRZ1/\/q@?E'))71z#7f*FW'S,ۤ/B_58$<959_uB%39bY`*G>sg5So: >̿A C"솠jr5T4 mr-qv^r\pכ.n&ӹ71CrL c&'îg1.>θîFwg\ tq^B_b eo|p]X7+.먋tAq6+oWɿ.V +܇.E`cHhUh=yj: W"S ..SEnV80Hc5ᢞLñ~BOy=/oh"(ڸ>b!?@{d?gT^5jjz0<㾚f 8BE\N;"빠{>X;hh^GXڑeٙvTqC)P`-haWy._V2)_3ʽB6G{ y;˷@O^jߵ.ڥ5.9_YܽAhU& {?-}E83>WLm!'V_gs ;Gnn/ޗWUvXAe dw6f,Ŗl5O˳ q_uas!-,IR\58t:G:nIOhpGB(RTq@jVMu:"s88Ag PE^^

    %sDispatcher

    '%D.basename outStr+=str(table) print outStr quit() trunk-2018.02b/scripts/checks-and-tests/law-test.py000066400000000000000000000057771324306050200221470ustar00rootroot00000000000000# # demonstrate capabilities of LawTester # # The contact is loaded as described by LawTester.path triplets: # # 1. Push spheres together, then shear, then release in the normal sense, # while keeping shear constant; slipping is taking place along the # Mohr-Coulomb plasticity surface # # 2. Load in the normal direction, then go around a square in the shear plane # # The sphere couple is oriented randomly, but the result should always be the same. # from yade import utils,plot import random random.seed() # sphere's radii r1,r2=.1,.2 # place sphere 1 at the origin pt1=Vector3(0,0,0) # random orientation of the interaction normal=Vector3(random.random()-.5,random.random()-.5,random.random()-.5) normal=Vector3.UnitX O.bodies.append([ utils.sphere(pt1,r1,wire=True,color=(.7,.7,.7)), utils.sphere(pt1+.999999*(r1+r2)*normal.normalized(),r2,wire=True,color=(0,0,0)) ]) O.engines=[ ForceResetter(), PyRunner(iterPeriod=1,command='import time; time.sleep(.05)'), InsertionSortCollider([Bo1_Sphere_Aabb()]), InteractionLoop( #[Ig2_Sphere_Sphere_ScGeom()], [Ip2_FrictMat_FrictMat_FrictPhys()], [Law2_ScGeom_FrictPhys_CundallStrack()] # ScGeom #[Ig2_Sphere_Sphere_L3Geom(approxMask=63)],[Ip2_FrictMat_FrictMat_FrictPhys()],[Law2_L3Geom_FrictPhys_ElPerfPl(noBreak=True,noSlip=False)] # L3Geom [Ig2_Sphere_Sphere_L6Geom(approxMask=63)],[Ip2_FrictMat_FrictMat_FrictPhys()],[Law2_L6Geom_FrictPhys_Linear(charLen=1)] # L6Geom ), LawTester(ids=[0,1],disPath=[(0,0,0)]*7+[(-1e-5,0,0),(-1e-5,.1,.1)],rotPath=[(0,.2,0),(0,0,0),(0,0,.2),(0,0,0),(.2,0,0),(-.2,0,0),(0,0,0)],pathSteps=[10],doneHook='tester.dead=True; O.pause();',label='tester',rotWeight=0), #LawTester(ids=[0,1],path=[ # (-1e-5,0,0),(-.1,0,0),(-.1,.1,0),(-1e-5,.1,0), # towards, shear, back to intial normal distance # (-.02,.1,.1),(-.02,-.1,.1),(-.02,-.1,-.1),(-.02,.1,-.1),(-.02,.1,.1), # go in square in the shear plane without changing normal deformation # (-1e-4,0,0) # back to the origin, but keep some overlap to not delete the interaction # ],pathSteps=[100],doneHook='tester.dead=True; O.pause()',label='tester',rotWeight=.2,idWeight=.2), NewtonIntegrator(), PyRunner(iterPeriod=1,command='addPlotData()'), ] def addPlotData(): i=O.interactions[0,1] plot.addData( un=tester.uTest[0],us1=tester.uTest[1],us2=tester.uTest[2], ung=tester.uGeom[0],us1g=tester.uGeom[1],us2g=tester.uGeom[2], phiX=tester.uTest[3],phiY=tester.uTest[4],phiZ=tester.uTest[5], phiXg=tester.uGeom[3],phiYg=tester.uGeom[4],phiZg=tester.uGeom[5], i=O.iter,Fs=i.phys.shearForce.norm(),Fn=i.phys.normalForce.norm(),Tx=O.forces.t(0)[0],Tyz=sqrt(O.forces.t(0)[1]**2+O.forces.t(0)[2]**2) ) plot.plots={'us1':('us2',),'Fn':('Fs',),'i':('un','us1','us2'),' i':('Fs','Fn','Tx','Tyz'),' i':('ung','us1g','us2g'),'i ':('phiX','phiXg','phiY','phiYg','phiZ','phiZg')} #'ung','us1g','us2g' plot.plot(subPlots=True) try: from yade import qt qt.Controller(); v=qt.View() rr=qt.Renderer() rr.extraDrawers=[GlExtra_LawTester()] rr.intrGeom=True except ImportError: pass O.dt=1 O.saveTmp() #O.run() trunk-2018.02b/scripts/checks-and-tests/serialization-benchmark.py000066400000000000000000000014201324306050200251710ustar00rootroot00000000000000import time # change this line to load your reference simulation O.load2('ref.boost.bin.gz') base='~sim~' # http://blogmag.net/blog/read/38/Print_human_readable_file_size def sizeof_fmt(num): for x in ['bytes','KB','MB','GB','TB']: if num<1024.0: return "%3.1f%s"%(num,x) num/=1024.0 def io(ext,load,noBoost=False): t0=time.time() f=base+('.yade.' if noBoost else '.boost.')+ext ((O.load if noBoost else O.load2) if load else (O.save if noBoost else O.save2))(f) print ('Loaded' if load else 'Saved '),('yade ' if noBoost else 'boost'),'%-7s '%ext,'%7s'%sizeof_fmt(os.path.getsize(f)),'%.1fs'%(time.time()-t0) for ext in ['xml','xml.bz2']: io(ext,False,True) io(ext,True,True) for ext in ['xml','xml.gz','xml.bz2','bin','bin.gz','bin.bz2']: io(ext,False) io(ext,True) trunk-2018.02b/scripts/checks-and-tests/triax-perf/000077500000000000000000000000001324306050200220765ustar00rootroot00000000000000trunk-2018.02b/scripts/checks-and-tests/triax-perf/mkTextTable.sh000066400000000000000000000006551324306050200246640ustar00rootroot00000000000000#!/bin/sh # run this to get nice text to paste into the openoffice sheet TMPS=/tmp/a$$ /tmp/b$$ /tmp/c$$ /tmp/d$$ ls *.ser?.log awk '/TOTAL/ { print $2}' *.ser?.log > /tmp/a$$ awk '/Collider/ { print $4}' *.ser?.log > /tmp/b$$ awk '/TOTAL/ { print $2}' *.par?.log > /tmp/c$$ awk '/Collider/ { print $4}' *.par?.log > /tmp/d$$ paste /tmp/a$$ /tmp/b$$ /tmp/c$$ /tmp/d$$ | sed 's/us//g' | sed 's/%//g' | sed 's/\./,/g' rm -rf $TMPS trunk-2018.02b/scripts/checks-and-tests/triax-perf/triax-perf.ods000066400000000000000000000463671324306050200247060ustar00rootroot00000000000000PK9:l9..mimetypeapplication/vnd.oasis.opendocument.spreadsheetPK9: content.xml]6Oa٢]XL$.Z4md ŀ#Ѷ]\J>bOŔo#ʖg?\<lÆKsyK!4]mUQ $a aˌ1qN=]ƐB,(E;VwmMmtOZ lZ`qQ4UԻIQwӥj?c)`k _(>dƦÄQ30ZMv`ޓ}3f);B?*3eWlCبAۤ з=5gb46-]L՚>ʦ9a_HP|9:AgivIlPQD%e'%8Z%_B,(#`Ì$`LR ҳ\bES KsTa) R QNki1؉]:TY$V%^2-rjOcC1q6[AcL(Gа\[+GO@Ĭ+wIASѸ{@Dlio}\x;޹N;՗|;w83<׼pSM՞BNsyyK$ nסQT+JfД*=v1*J={v+/ALK"I}5G @+rjkwDEqAc@3v[/IwePxlXp4@Hrd̊WTp+ ~yR~ $'gXyb}}b}W^iR$`DljUvO([ ĝ1a~]fyyMI6I@d2|AG~`/g uAlt%CÑrZou$_~<H,iRbp HjIw4X01=kfsps2;J>j=`Q?ivE@6(qm7#H9-,GR_Oʼn""ٝ1?y4JаLΡݵӂCPUҏ=d!9F?tq5ƪi'LFdϞnNktL$VQhYѲC !OLXQғ=W5l;2%54ϱuMâ'{H5Z3B"%=,H(xn},q(LfxcՋۈC5Os]Kâ8T[虡lɞ=8ؓt55OC,m]vK-nf|y'7<>hQ7?M}s>960&=l{ܭ^!r3f&P2<1?iog4#Sz~HߔCQTd蹖 隞r֠]k${sȄ9,ImjmN'A2liv YCu5ґ#l1=͜\y6(ŜWSOG(œY9zk̎>2τY2$m9҄FniɢR!yCbR,vcQ~ Qtu~3Tȸ",MwL<$\~7Ҽ^ & ( 58sBܐdEt8bZrlD een.%q'`,hOe`q iq\3.l{ݎ&s[ {]!=FP,Fmwڊ6 'h*%mQM +p'Sm۰mI*T&>T&~7lK q+%h+J̨BU%Tw8szK%AR"z=yq4Nڜ‚;%OmSLXH_w*~e8%I `k%"l4Y*csryE-z]]eT g|n5ǹQt Q /+ $njn1p^i1:~WU t\g݇D$:`|7d0T%C1_.C) $ ]7D8N-Q)sLx-&/"1oF1l-d#%iENMLؔ, reD-ҋ9)_%^lwJ31kDF)FC11W."m}RꭣߐDv)фLzj6$x~OJ *+A~l~HɪBFі`-$քZ&0 ɳ pOHο{b (knA/^~@F 0#cu sgoͮh҃bP̾0P/X~f9M?A0bbg~ucKnѡWjn{I G{dǏw>>\!RpȸM`&O !bGW}>NUtzlul}q>'#k=ȿ2C&z ҟ0 Q~Q=)+"SJٝG T7e?/`ͼ}~{Ś3{;YtӽWu!ǃnyqE׿ 9p1\M&\~r؞~~>'O[X{Oav%b6\ T"f{KC1lwr# T[.'a=ċHP(T`7tPo^`9N,| Ikh k!TO3v|`t%GeT⩈Y-,,Kq".kܬTkCvӫ[vإ<P쮓ݏӸVƬ[vɮn#|&oUcT/b'7Ih(AE 6 wP䞓[5[78Aީ:VWo܉? S xJJhZWCq3@eHSR[;K㌐">C (n^Bq=Re| NSIv8(sfRJZVM,\EaAU?Kmgync_~b]b*:3M]/ T1Q$9yu3&;ťd[\GPEǪ>v#PiDO` 9#ҋ Yb)8{؀A_g#~)Q! 4l}X:W y;G0 0A6 x4}ՎLx(5]TF0Wq+ex$6B f5o~ 䙏S++uSI]@7;7Mrt6/%Yld4޾_Wz`O~n jQ_&_#nu)hPK<- iHPK9:&,||meta.xml OpenOffice.org/3.0$Unix OpenOffice.org_project/300m15$Build-93792009-07-07T23:33:51PT00H41M24S4PK9:Thumbnails/thumbnail.pngyUT\ߗ&!{%H ] )\C !@pï==kVϚyr9wv&'i 8/,88OY>z=8x|ŒfƷ,{z?xICoBUJ_%exd*S -'&M.\.uo6y*Y~KP ? 3[SJB k]MϥV̬A7:xi7E^ypk>?|<ǵ <ɱ\/~99?]2_ z{B,|ēqR-\Q<"5z;[AwKjvи;QôMD,F*-".A6 [f6L*b?2aeR2oAA C1"^ u{QbjV3.x~㱧/]OyW;6vhX'rm~uae A<:mD2:z=&Q[!p(I%3wԼ ȀPԦWeB0" c3ar ru$F?yT!sg!gBhdu,&?r X@&N$ow_ǵBDzQy眀.̹$GG5/g+a64W њ W5_wWؿ-JN]R^z\0 z;W 7Z/[xW'_u,Ŗ ]E2;<rؓT/vKLq熛b;7C|g b* 4])<["ob;$E&ETWF'qCA<-F*-}[x[䢗Sk1"gSoʑlj!լ\<N%hy}Lm,ˌ 0( '*DvUUB=74?[R|?Ļ9 jz[kybGx~g/TV>٥Y >%{BL5`ZG[~)AM R,z)IQ{ 2@Yv{jϡs:5oQ9PL̻F]] @;?ٔ 4[-SN;"%/S8>p7{Y3ʒE{3Uر̺< +S!kD)NHcd3Ck{ tVlϪF9gJj`C+g iETnl^/>%^ xroa$n?ih5e6^ 8+*:gӦ@7 vá&!\4Ёq`C9Z]te!8|QR:j'#P9uF|=s3!Gd:6dw$-Ay=DZ4e"8]OD#R)-"]`*E\6̑108dMF_n|UlD^+hMW"* lG\.?[$ KjO.|to(F0ܢN};?߶Gǵ_].r{lTD_lǤ+%:lh=^]Izp^'<̺Q]|v6Pğ&˥7'P^Nd1*':`Ts+y^EfǢu 2 Xj*_`i\1yD(#FR}䞦$L#c{jdULޖnR2bv\I5[^Hcs_o elb>يPLT\CZ"/8q-TM--'F Ax%_(&P. Ř'M&:mV0cj֩3ҞL^ D%8 iq IDՐ_iH?*eR] vQKjx!+ƿv}i{BKӵum= q[M!8bF:hWDd uc v$Xn/B&5y޺zV 8et+I; Kx{_sS䡕O.CWWQjwJ: zVb߅)+ekʱKƀR~t$Jh[%_U/,£,|~tg%/"qC ghFhy-_6Zgbk˼ڼqú|ޕ͠yσݚ_Pfq=ٳ奻/sIͅF9x pCbu'\5v :S/,ii|*TgnYEmfV92k@ 9]%[NUmepVir!V &2=ddW F8xwP0}ISK FLd?[[TRFƂrR[꫼̰f"pκ9RگH%c->j]fD뻂 R"7GTuX$fHo3=`py6 D@ԽXnowMd %/:Нl5 ݯlD^GpX@w섨OPP ݘɆ /1=a4qm#cgV)uG v `'?":vFTWg-=ɼ˗ o_N>(o^VCRϋ{o5B.ċ̔xX#&wR۷UF?;X/ Mo3p[sA#ѷv%Z}oh0'<>Xŭy2J  Sᝉt_G lszi!*|\`3 c>&ĕWةnnR`׍ ';j8Ճ;C7!B@V~/s߀c*䶀1in7+.u[>wo] _E8ɏ`K!#P2v8_itzE*BO\_ t(.43VؚH 9mڑm=bUɖt!0A""^ h#y;: ⦤F<C>$.+041cuJ$w Ut]G)Ŏ?F;oK~~G2r`<׹ji'LPUv3]F^( d׎۲ٰ "~#Q}`E7rػ|rC{@䥖*ңM7f0 Y> n"(VA<{HX4˯rwF~Gc=|3 Ѷ @%oA4,Eh}{gElw?{txvHT kAsz+ûKKEpǶJ-E0ǰT.&;,kgRtD0N3pQ-b{I Ey%& OT S\sSՃeIǘ!zaEB P4P'Lp2ِxoYeXf3=IKXa \xwQ~hMhܱ΂ y WPZWz8`~VB%Wq"V~Q3Nt H} m3[d6"p h_=tx}:(^k!z5Ww]@Tvn[,6e2̒G0LiLfFV`>+k|VZ;H)UqŨ>1G;UqDpclX9$V17"?E9 B- -l?cY64#F&w@6M|v,SJHt~ i1%c. 4풦ڶbݡb;-x8zHHf|Y=y!qGSs(3ҤPŪ^s?e@0zgc$ށ9;%ʸ(dT`q]\@@+/9# :>h76˾#GޗѡY- R<X%[s-l/ >yNx5XnHH8cٹj(WRY ,R~U6!gv z~ aDlRAJaDG{Xb=W|:sU@5:&f=%ftgEp_K@3&4+ݗnފEDDTggB<Vhg'@n)$0Č.7;b&Um5.I*.G-XFt:AʮAqXJ.QǦvo UyJ?*tJiw8՗/;NaJm Q5J="p8e5v GnŬ7xf.牯;oY=Sh)b5fڬ2ۃCJ?V՗doQjvD%s485ӽOx'; %%ާ2SՓ>mY䩵5*'5pyIXh+:%{3M!5-CyXB}|Yz~#7U{9๭퉷b0zfB!"s$ (T÷@rPDtFpZ鮧1idGilNs|/g ͺ@l$JJZPWX,9:f$RZ/$>~l|W^}.Lϯ=[Iv_bdISaqV&Q`5.'zL[bttH."^86%5^\Fj*ŗBU$/6\BNXNtʢ $ᔗsg@gGjyҊHq䞂sU ĕeMfO7X1[av7FSZ6'9+LE3o,izTBV83{wafY0\$㧰*6(U^7'ZmoNd)O.fϒ΃ޓ!{lR+X>iӍp~GsF5*¸xj/yӌu g.00̙ޑ25z!n@KTQIt* rjolwXZ52WXz=[00iMf,T&LBA}m}%w-Y_T٦ >4D}~{8k|I0׽K@ZKfLyWb[R}.ɩtΥ~{kec9̍&0[¡ N^,(,䭠4#O2:}L/̊};~*(Q<hz ^r`2;| L>p)ذ^n@(4^Pǟׇnv?\ 9ܥH[ʤ“ͩe؟$u'`X|t0C<GX-_{7txU|/|f_Z$줷% d>hs o03[:hl`vC1۶Uq}G/f~qp|>݃èNcf4 6p˄xzpLm7j]w*uyi}.E͉Չvq{^\]10#9}Ļ/pQ1n;砟@|6ǵa;M-!Ҫ'6nI1pI<+h;G(d#zQ/ ׈el|,MiȽaq\ 닗^18x矊^{<5˯rF1}r},=4k˩s2+_8%n ?SF"8MZZs?bt< ;y`$TJP-_tcyyB}\k\WgE0xԕTb Va@ >ˢ?3upy4$vH a$gY kCi㢨gv͙ɂy!; 񧼡0kV~d2^IfN)^m [*Wz;P,9jXZb[1 HЄ-JX*Վ*2zRHJ}VmtżYNyzbG`C d u~u1.wOWo;k\s跆[- ,T΋1@.I eX1_-Itǽ5, a͒ M義8B>9 kR|ZVGeL]H2;X@"ȗr۫d8=0PPpy2r_ ^1)RH^mZڇ@^O}W@ "ֽ"]ptʻa-mdcuȢ^-\: bS61l Cb[VJP{E+H@Eh 1bTB!iQDlCGHT7nGU_ɔ#o(RjUC#/̕/J2yz;0ӫ1}Wl#h~67]> e)yvG:\"(տ ,eEI,qw D۬:9|l.],8pgqMӛϤ" aGh)gB5UEygV RTz7>#rp$qs")43š;v'(qx1d0)Ǻm:PJ;J2#k7CX0RS 1P:H< f:mO5OuӒ- -ե*89L2[u_,#s#FT]{C{\&Párw.LfH` Љر6xn֐A0 5yk̋0"K$LUor}r"IJj͚D(L5F~J](Ye>˄=}8y Ct~@iѐGC: D/AO3:{K?PSFbVſKR[^5E-B3b̤uUZ|v+<;ڐ( 7wM+X z TG.=FCWE Խq^InuGc5WmWZT7":Q>.xX)6&Msw\,57WрUb ^!u!>[6)y*/pֆQI獔B^h1#?ɝVn]KfQf6SQͼ&ybBeņpU2Y \y,wk%xe yE;IbK6>y! :SNrn.ogZZ[JI-lI6>z1Güșp<;^Q}?Q%U^5ls#(XgT8#uE=k=u7a8ßpo[٣j [/чY!ܨ֥_3\{"c16PJ?y qHorq0t/Mp9rmlh֎B#jF޸c+Sd6)WƤwa y֪UP+ 𧎢; 785V]Iʑz;^R-5֌wYj{Uc@6jl{߂fU{ȀpcD䜋zPBW p GZnV? t+Э@9>)#hhq2"M#,_ 7բd[& 0v_>?j ]m D3d(2hN流\]< #@ii>({ﭐI#6'D*Nt++@1 z UT,> H\WM?}sg+oos;HP%A ~͋nau[% ö$%-ItIz4P8# 7z5K3 G*+"%Dˠ{Hw;A =_Ң\ O%%& Hi^-{IC|P60I [7St\w5Pp ] }qoldJ§K̢/&PKUQ{'PK9:META-INF/manifest.xmlMn0">q˪Z'I?ynH[;^{ks$ tʷ|/uXYpC<(}.Fjv`jVZEzTZ/+!0J>l`|ʽkV5(Dv,Irr)RȻ0Xܦ)xt3%>ٱކmvnhC*~n,w:ݧxzS>Jb|"#m!@g<0rC CM<`{ -HM5#w⬒T6m<|ּ~PKDՀJhPK9:l9..mimetypePK9:N1= j Tcontent.xmlPK9:<- iH styles.xmlPK9:&,||/meta.xmlPK9:hg')Thumbnails/thumbnail.pngPK9:'~AConfigurations2/accelerator/current.xmlPK9:AConfigurations2/progressbar/PK9:BConfigurations2/floater/PK9:EBConfigurations2/popupmenu/PK9:}BConfigurations2/menubar/PK9:BConfigurations2/toolbar/PK9:BConfigurations2/images/Bitmaps/PK9:&CConfigurations2/statusbar/PK9:UQ{' ^Csettings.xmlPK9:DՀJhfGMETA-INF/manifest.xmlPKHtrunk-2018.02b/scripts/checks-and-tests/triax-perf/triax-perf.py000066400000000000000000000015301324306050200245300ustar00rootroot00000000000000# Performance test for running # # 1. Regular TriaxialTest with 3 independent dispatchers (geom, phys, constitutive law) # 2. TriaxialTest with InteractionLoop (common loop and functor cache) # # Run the test like this: # # yade-trunk-opt-multi -j1 triax-perf.table triax-perf.py # # The -j1 ensures that only 1 job will run at time # (even if other cores are free, access to memory is limiting if running multiple jobs at time) # # You have to collect the results by hand from log files, or run sh mkTextTable.sh and use # triax-perf.ods to get comparison # utils.readParamsFromTable(fast=False,noTableOk=True) TriaxialTest(numberOfGrains=50000,fast=fast,noFiles=True).load() O.run(10,True) # filter out initialization O.timingEnabled=True O.run(200,True) from yade import timing timing.stats() print 'ForceContainer synced %d times'%(O.bexSyncCount) trunk-2018.02b/scripts/checks-and-tests/triax-perf/triax-perf.table000066400000000000000000000002371324306050200251720ustar00rootroot00000000000000!OMP_NUM_THREADS fast description 1 False ser1 2 False ser2 3 False ser3 4 False ser4 5 False ser5 1 True par1 2 True par2 3 True par3 4 True par4 5 True par5 trunk-2018.02b/scripts/erskine3-apply.sh000077500000000000000000000042541324306050200200670ustar00rootroot00000000000000#!/bin/sh #set -x ERSKINE=./erskine3.py YADE=../ # erskine3 accepts scons variables smuggled as @VARIABLE and resulting in $VARIABLE in generated scripts; do that for $PREFIX (ac scons var) export YADE_QMAKE_PATH=@PREFIX YADECOMPILATIONPATH=$YADE ENGINE=--scons; SCRIPT=SConscript #ENGINE=--waf; SCRIPT=wscript_build # erskine2.py syntax: ### project-file [--waf|--scons] buildscript-dir > buildscript # projects are traversed recursively and corresponding waf wscript_build files are sent to the standard output $ERSKINE $ENGINE $YADE/yade-libs/*/src/*.pro $YADE/yade-libs > $YADE/yade-libs/$SCRIPT $ERSKINE $ENGINE $YADE/yade-core/src/yade.pro $YADE/yade-core > $YADE/yade-core/$SCRIPT $ERSKINE $ENGINE $YADE/yade-guis/yade-gui-qt/src/QtGUI.pro $YADE/yade-guis > $YADE/yade-guis/$SCRIPT $ERSKINE $ENGINE $YADE/yade-extra/yade-spherical-dem-simulator/src/yade-spherical-dem-simulator.pro $YADE/yade-extra > $YADE/yade-extra/$SCRIPT # packages are have separated buildscripts each $ERSKINE $ENGINE $YADE/yade-packages/yade-package-common/src/yade-package-common.pro $YADE/yade-packages/yade-package-common > $YADE/yade-packages/yade-package-common/$SCRIPT $ERSKINE $ENGINE $YADE/yade-packages/yade-package-dem/src/yade-package-dem.pro $YADE/yade-packages/yade-package-dem > $YADE/yade-packages/yade-package-dem/$SCRIPT $ERSKINE $ENGINE $YADE/yade-packages/yade-package-fem/src/yade-package-fem.pro $YADE/yade-packages/yade-package-fem > $YADE/yade-packages/yade-package-fem/$SCRIPT $ERSKINE $ENGINE $YADE/yade-packages/yade-package-lattice/src/yade-package-lattice.pro $YADE/yade-packages/yade-package-lattice > $YADE/yade-packages/yade-package-lattice/$SCRIPT $ERSKINE $ENGINE $YADE/yade-packages/yade-package-mass-spring/src/yade-package-mass-spring.pro $YADE/yade-packages/yade-package-mass-spring > $YADE/yade-packages/yade-package-mass-spring/$SCRIPT $ERSKINE $ENGINE $YADE/yade-packages/yade-package-realtime-rigidbody/src/yade-package-realtime-rigidbody.pro $YADE/yade-packages/yade-package-realtime-rigidbody > $YADE/yade-packages/yade-package-realtime-rigidbody/$SCRIPT echo Creating entry for yade-extra/Clump in $YADE/yade-extra/$SCRIPT echo "env.SConscript(dirs=['clump'])" >> $YADE/yade-extra/$SCRIPT trunk-2018.02b/scripts/erskine3.py000066400000000000000000000332701324306050200167570ustar00rootroot00000000000000#!/usr/bin/env python # -*- Encoding: utf-8 -*- """ This program should really be run by the erskine3-apply.sh script Erskine is Watt's colleague in the house of Mr. Nutting. """ import sys,pprint,string,os from logging import * from os.path import * from re import * from copy import deepcopy import pprint def warning2(msg): warning(currentPro+' '+msg) bgroup=('(','{','[') egroup=(')','}',']') # variables that do not propagate to sub-builds localVars=['TEMPLATE','SUBDIRS','HEADERS','SOURCES','FORMS','LEXSOURCES','YACCSOURCES','TARGET','DESTDIR','PROJECT'] # variables / function that are ignored if undefined (replaced by ''); for all other, a warning is printed (and are ignored anyway) undefOK=['CXXPATH','CXX','CXXFLAGS','INCPATH','IDL_COMPILER'] # internal qmake variables that may be replace with the environment ones without warning replaceOK=['YADECOMPILATIONPATH'] discardedStatements=['^isEmpty\s*\(\s*YADE_QMAKE_PATH\s*\).*$'] #discardedStatements=[] targetLangType={'yade-lib-serialization-qt':'qt3','QtGUI':'qt3'} # used to be yadeQt, but it was dropped... targetEnv={'yade-lib-serialization-qt':'env','QtGUI':'env'} # was: yade defaultEnv='env' currentPro='[none]' allEnvs=[defaultEnv] for t in targetEnv.keys(): if not targetEnv[t] in allEnvs: allEnvs.append(targetEnv[t]) allSystemLibs=['glut','boost_date_time','boost_filesystem','boost_thread'] def splitgroup(s,delims=' '): "Split string at delims, but do not break parenthesized/bracketed/curly-bracketed groups" nest=0;ret=[];lastsplit=0 for i in range(0,len(s)): if s[i] in bgroup: nest+=1 elif s[i] in egroup: nest-=1 elif (nest==0 and s[i] in delims): ret.append(s[lastsplit:i]) lastsplit=i+1 ret.append(s[lastsplit:len(s)]) return ret def parseProject(proFile,inheritedVariables={}): """Returns dictionary of variables defined in this qmake project, replacing variables, that may propagate from parent project. No functions, only a few conditionals are expanded. An important 'parser' difference is that qmake obviously allows \\ continuation _after_ #. Since it is not nice at all, it doesn't work here. Also, it would be difficult to add.""" vars=inheritedVariables # sanitize input: no comments, no \\ linebreaks (join lines), no empty lines, no leading/trailing whitespace qm=[]; l=''; nest=0 for line in proFile: m=match(r'^\s*([^#]*)(#.*)?$',line[:-1]) l+=m.group(1) l=l.rstrip() beginnest=nest if len(l)==0: continue for c in l: if c in bgroup:nest+=1 elif c in egroup:nest-=1 if l[-1]!='\\' and nest==0: qm.append(l) l='' else: if l[-1]=='\\': l=l[:-1] nest=beginnest #pprint.pprint(qm) # process statements sequentially, replace variables / functions for stat in qm: # discard what should be discarded discard=False for pat in discardedStatements: if match(pat,stat): discard=True; break if discard: continue #replace environment and qmake variables while True: m=search(r'\$\$?([a-zA-Z0-9_]+\b|\([a-zA-Z0-9_]+\)|\{[a-zA-Z0-9_]+\})',stat) # possible patters of variables if not m: break v=m.group(1) if v[0]=='{': v=v[1:-1] if os.environ.has_key(v): repl=os.environ[v] info("Substituting environment variable `%s` → `%s`"%(v,repl)) else: if not v in undefOK: warning2("Undefined environment variable `%s'."%(v)) repl='' else: if v[0]=='(': v=v[1:-1] if vars.has_key(v): repl=vars[v] else: if not v in undefOK: if os.environ.has_key(v): repl=os.environ[v] if not v in replaceOK: warning2("Undefined internal variable `%s', using environment variable instead."%(v)) else: warning2("Undefined internal variable `%s'."%(v)) repl='' else: repl='' stat=stat[:m.start()]+repl+stat[m.end():] # scons variables (namely, $PREFIX, were smuggled unexpanded by using @PREFIX instead. Change it to what it should be now. stat=stat.replace('@','$') conditional=match(r'^\s*(!?[a-zA-Z0-9_]+)\s*{(.*)}$',stat) # this is a (possibly negated) conditional; nothing complicated, please; specifically, nested conditionals do not work if conditional: cond,then=conditional.group(1,2) if cond=='win32': continue # windows stuff is discarded completely elif cond=='!win32': stat=then # otherwise we pass the whole then-clause to further treatment else: warning2("Unknown condition `%s'."%cond) assign=match(r'^\s*([a-zA-Z0-9_.]+)\s*(=|\+=)\s*(.*)$',stat) # this line is an assignment call=match(r'^\s*(!?[a-z][a-zA-Z0-9_]+)\s*(\(.*)$',stat) # this line is a "function call" if assign: param,action,val=assign.group(1,2,3) val=sub('\s+$','',val) val=splitgroup(sub('\s+',' ',val)) if not vars.has_key(param): vars[param]=[] if action =='=' or action=='+=': vars[param]+=val elif call: func,rest=call.group(1,2) if not func in undefOK: warning2("Function `%s' not implemented; arguments `%s'.\n"%(func,rest)) else: warning2("Line not parsed: `%s'.\n"%(stat)) return vars def proProcess(filename,_vars={},dir='.',nest=0): info("(%s) Processing project file `%s'."%(nest,filename)) global currentPro currentPro=filename posterityVars=[] vars=deepcopy(_vars) for l in localVars: if vars.has_key(l): del vars[l] # these are NOT propagated to sub-builds childVars=parseProject(open(filename,'r'),vars) childVars['PROJECT']=filename assert(childVars.has_key('TEMPLATE')) # otherwise the file is broken assert(len(childVars['TEMPLATE'])==1) # dtto template=childVars['TEMPLATE'][0] if template=='subdirs': # if not childVars.has_key('SUBDIRS'): #warning("Template is `subdirs' but SUBDIRS is empty.") return None for subdir in childVars['SUBDIRS']: #print "dir=%s,subdir=%s"%(dir,subdir) subPro=dir+'/'+subdir+'/'+subdir.split('/')[-1]+'.pro' # .pro file is named the same as its immediate parent directory + .pro posterityVars.append(proProcess(subPro,_vars=childVars,dir=dir+'/'+subdir,nest=nest+1)) # recurse return [posterityVars] elif template=='app' or 'lib': guessedTarget=os.path.split(filename)[-1].split('.')[0] # garbage/foo.pro -> foo explicitTarget=None if childVars.has_key('TARGET'): explicitTarget=os.path.split(childVars['TARGET'][0])[1].split('.')[0] if explicitTarget: childVars['TARGET']=explicitTarget else: childVars['TARGET']=guessedTarget #if oldTarget!=newTarget: warning("Old and new target differ: `%s' vs. `%s' (using new one)."%(oldTarget,newTarget)) return [childVars] else: warning2("%s: Unknown TEMPLATE `%s'"%(currentPro,template)) return None def listUnique(List): if len(List)<=1: return List List.sort() last=List[-1] for i in range(len(List)-2, -1, -1): if last==List[i]: del List[i] else: last=List[i] return List def processVars(V,dir): ret="" for v in V: if isinstance(v,list): ret+=processVars(v,dir) if isinstance(v,dict): ret+=scriptGen(v,dir) return ret def pythonicTarget(t): #return target name such that it is acceptable as python identifier return sub('(^[0-9]|^|[^a-zA-Z0-9_])','_',t) def scriptGen(v,dir): project=v['PROJECT'] global currentPro currentPro=project target=v['TARGET'] commentOut=False # waf doesn't like targetless sources; scons is fine with that template=v['TEMPLATE'][0] ################### ### PATHS projAbsPath=realpath(normpath(dirname(project))) dirAbsPath=realpath(normpath(dir)) prefix=commonprefix([projAbsPath,dirAbsPath]) relPath=projAbsPath[len(prefix)+1:] #warning("PATHS:\n%s\n%s →\n%s"%(projAbsPath,dirAbsPath,relPath)) assert(not isabs(relPath)) #return 'PROJECT=%s\nTARGET=%s\nSOURCES=%s\nINCLUDES=%s\nrelPath=%s\n'%(v['PROJECT'],v['TARGET'],string.join(v['SOURCES']),string.join(v['INCLUDEPATH']),relPath) ################### ### SOURCES if v.has_key('SOURCES'): sources=v['SOURCES'] else: sources=[]; if v.has_key('FORMS'): sources+=v['FORMS'] #if v.has_key('IDLS'): sources+=v['IDLS'] sources=listUnique(sources) ################### ### LIBRARIES def prependDirFile(d,f): return map(lambda x: join(d,x),f) def listLibs(ll): ret=[] for l in ll: if l[0:2]=='-l': ret.append(l[2:]) elif l=='-rdynamic': pass else: warning2("Unknown LIBS item `%s'."%l) return ret libs=[] if v.has_key('LIBS'): libs=listLibs(v['LIBS']) ################### ### INCLUDE PATHS # waf/scons don't compile from the current directory, make sure it is there; duplicata removed later if not v.has_key('INCLUDEPATH'): includePath=['.'] else: includePath=v['INCLUDEPATH']+['.'] includePath=listUnique(includePath) #print "relPath=%s,INCLUDEPATH=%s"%(relPath,includePath) def prependDirFileIfRelative(d,f): def prepIf(ff): if not isabs(ff) and ff[0]!='$': return join(d,ff) return ff return map(prepIf,f) includePath=prependDirFileIfRelative(relPath,includePath) # filter out non-existent paths; without warning, it could be a short lambda... def DelAndWarnNonexistentPath(x): if not exists(normpath(join(dirAbsPath,x))) and x[0]!='$': warning2("Include path `%s' is invalid, removed!"%(normpath(join(dirAbsPath,x)))) return False return True includePath=filter(DelAndWarnNonexistentPath,includePath) includePath=[normpath(p) for p in includePath] # remove duplicates... http://stinkpot.afraid.org:8080/tricks/index.php/2006/05/find-all-the-unique-elements-in-a-python-list/ includePath=dict([(i,1) for i in includePath]).keys() # begin building the actual script ret="## %s\n"%v['PROJECT'] if template=='lib': if v.has_key('CONFIG') and 'dll' in v['CONFIG']: targetType='shlib' else: targetType='staticlib' elif template=='app': targetType='program' if buildEngine=='waf': assert(targetType) # "TEMPLATE is neither `lib' nor `app'. if not len(sources)==0: warning2("Project `%s' has empty source list, build rule will be commented out."%project) commentOut=True langType='cpp' localLibs,systemLibs=[],[] if target in targetLangType: langType=targetLangType[target] if langType=='qt3': systemLibs.append('QT3') ret+="obj=bld.create_obj('%s','%s')\n"%(langType,targetType) ret+="obj.name='%s'\n"%target ret+="obj.target='%s'\n"%target ret+="obj.source='%s'\n"%string.join(prependDirFile(relPath,sources)) # FIXME: install not yet there #ret+="obj.install_in='%s'\n"% #includePath=filter(lambda x: exists(normpath(join(dirAbsPath,x))),includePath) ret+="obj.includes='%s'\n"%string.join(includePath) # libs if len(libs)>0: for l in libs: if l in allSystemLibs: systemLibs.append(l) else: localLibs.append(ll) if len(localLibs)>0: ret+="obj.uselib_local='%s'\n"%(string.join(localLibs)) if len(systemLibs)>0: ret+="obj.uselib='%s'\n"%(string.join(systemLibs)) elif buildEngine=='scons': env=defaultEnv if target in targetEnv: env=targetEnv[target] ret+="%s.%s('%s',%s%s"%(env,{'program':'Program','shlib':'SharedLibrary','staticlib':'StaticLibrary'}[targetType], target,fieldSep,toStr(prependDirFile(relPath,sources))) if installable.has_key((env,targetType)): installable[(env,targetType)].append(pythonicTarget(target)) else: installable[(env,targetType)]=[pythonicTarget(target),] if len(libs)>0: ret+=",%sLIBS=%s['LIBS']+%s"%(fieldSep,env,toStr(libs)) if len(includePath)>0: # using CPPPATH would override top-level settings (question posted on scons ML how to avoid this) # for now, put all paths prefixed with -I to CPPFLAGS as workaround #ret+=",%sCPPFLAGS=%s"%(fieldSep,toStr(map(lambda x: '-I'+x,includePath))) ### NO, that doesn't work since CPPPATH is relative to SConscript dir, whereas CPPFLAGS are not prepended (logically) ### try the inverse: ret+=",%sCPPPATH=%s['CPPPATH']+%s"%(fieldSep,env,toStr(includePath)) ret+=')' instDir=installDirs[targetType] if instDirTargets.has_key((env,instDir)): instDirTargets[(env,instDir)].append((ret,target)) else: instDirTargets[(env,instDir)]=[(ret,target)] else: raise ValueError('buildEngine must be waf or scons (--waf or --scons)'); if commentOut: ret='# This rule is commented out because it has no source'+sub('(^|\n)','\n#',ret) ret+='\n' return ret def masterScriptGen(V,dir): # s='' if buildEngine=='scons': s+="Import('*')\n" processVars(V,dir) # return value of processVars discarded, results passed in instDirTarget instead for env,dir in instDirTargets.keys(): idt=instDirTargets[(env,dir)] #binary targets are handles specially: they install under _filename_ postfixed if match('.*/bin$',dir): ## warhing: this is OS-specific to tell binary target! s+="%s.InstallAs(["%env+','.join(["'%s$POSTFIX'"%(join(dir,tgSpec[1])) for tgSpec in idt])+"],[\n" s+=',\n'.join([sub(r'(?m)^','\t',tgSpec[0]) for tgSpec in idt]) s+='\n])' #whereas other targets install under _filename_ into a dir that has been postfixed already else: s+="%s.Install('%s',[\n"%(env,dir) s+=',\n'.join([sub(r'(?m)^','\t',tgSpec[0]) for tgSpec in idt])+'\n])\n' #for tgSpec in idt: print tgSpec[0] elif buildEngine=='waf': s+=processVars(V,dir) return s assert(len(sys.argv)>=3) buildEngine,projects,scriptDir=sys.argv[1][2:],sys.argv[2:-1],sys.argv[-1] assert(buildEngine in ['waf','scons']) allVars=[] for project in projects: allVars.append(proProcess(project,dir=dirname(project))) # HACK: useful stuff to pass down via globals... pretty=True if pretty: def toStr(what): ret=pprint.pformat(what); n=1 # replace leading spaces (nesting) by tabs... while n: ret,n=subn(r'(?m)^(\t*) ',r'\1\t',ret) # indent all lines by one more tab ret=sub(r'(?m)^','\t',ret) # de-indent the first one return ret[1:] fieldSep='\n\t' else: toStr=str; fieldSep=''; installable={} # hash indexed by (env,targetType)->pythonicTargetName instDirTargets={} # hash indexed by (env,targetDir)->str_target_spec installDirs={'shlib':join('$PREFIX','lib','yade$POSTFIX',string.split(scriptDir,os.sep)[-1]),'staticlib':join('$PREFIX','lib','yade$POSTFIX'),'program':join('$PREFIX','bin')} print masterScriptGen(allVars,scriptDir) trunk-2018.02b/scripts/fix-plugin-names.py000066400000000000000000000010701324306050200204100ustar00rootroot00000000000000import os,re for root, dirs, files in os.walk('.'): for name in files: if not name.endswith('.cpp'): continue #if name!='BssSnowGrain.cpp': continue modified=False new=[] for l in open(root+'/'+name): m=re.match('(.*)YADE_PLUGIN\(([^)]*)\)(.*)',l) if m: modified=True plugins=''.join(['('+pl.strip()[1:-1]+')' for pl in m.group(2).split(',')]) new.append(m.group(1)+'YADE_PLUGIN('+plugins+')'+m.group(3)) else: new.append(l) if modified: print root+'/'+name f=open(root+'/'+name,'w') for l in new: f.write(l) f.close() trunk-2018.02b/scripts/gl-test.py000066400000000000000000000026621324306050200166140ustar00rootroot00000000000000# # Test script for OpenGL from python (and from other threads in general) # With GL locking, 1 view seems to work reliably, not two views, though. # # Some drivers (nvidia) raise XBadAccess if there is an attempt to access # OpenGL context concurrently from different threads. # Other drivers (intel, at least on my laptop) freeze the videocard completely # if that is the case. # # You can enable the "if False:" part (say "if True:" instead) to see what # it does in your case. # from yade import qt from yade import utils O.bodies.append(utils.box([0,0,0],[1,1,1],color=[1,0,0])) O.bodies.append(utils.sphere([0,2,0],1,color=[0,1,0])) print 'Begin here.' qt.View(); print 'Created view' glv0=qt.GLViewer(); print 'view #0' # get GLViewer instance for the primary view glv0.grid=True,True,False; print 'grid x,y' glv0.axes=True; print 'axes shown' glv0.screenSize=200,200; print 'screen size set to 200x200' glv0.center(True); print 'median center' # median center - will fallback since there are only 2 bodies ## ## !!! If you enable this, you may crash your machine !!! ## if False: glv1=qt.View(); print 'Created 2nd view' # create new view glv1.fps=True; print 'fps shown' glv1.lookAt=(0,1,0); print 'lookAt set' glv1.viewDirection=(0,-1,0); print 'viewDirection set' glv1.center(); print 'center' print 'list of views:',qt.views() # list all views we have print "CONGRATULATIONS! You passed the OpenGL test without crash or freeze :-)" trunk-2018.02b/scripts/linkdeps.py000066400000000000000000000057161324306050200170510ustar00rootroot00000000000000#!/usr/bin/python from sys import * import os,re from string import * srcRoot='./' from os.path import sep import shutil def getModule(dir): m=re.match('^.*?'+sep+'((extra|core)|((gui|lib|pkg)'+sep+'.*?))(|'+sep+'.*)$',dir) assert(m) return m.group(1).replace(sep,'-') def walkSourceFiles(): ret=[] for root, dirs, files in os.walk(srcRoot,topdown=True): for d in ('.svn','QGLViewer','triangulation','sqlite3x','miniWm3','py'): try: dirs.remove(d) except ValueError: pass for f in files: if f in ('stdafx.cpp',): continue if f.split('.')[-1] in ('cpp','cc','C'): ret+=((root,f),) return ret sources=walkSourceFiles() plugins=set() for dir,f in sources: for l in open(dir+sep+f): if 'YADE_PLUGIN' in l and getModule(dir) not in ('core',): plugins.add(f.split('.')[0]) maxIncludeLevel=4 def grepCpp(path,f,level=0): fullF=path+sep+f; baseName=f.split('.')[0] linkDeps,featureDeps=set(),set() if level==maxIncludeLevel: return set(),set() #print path,f for l in open(fullF): m=re.match('^#include<([^/]*)/(.*)>.*$',l) if m: incMod=m.group(1); incHead=m.group(2); baseName=incHead.split('.')[0]; assert(len(incHead.split('.'))==2) if incMod=='core': continue #if baseName not in plugins: print f,incHead linkDeps.add(incHead.split('.')[0]) continue m=re.match('^#include\s*"([^/]*)".*$',l) if m: inc=m.group(1); incBaseName=m.group(1).split('.')[0] if not os.path.exists(path+sep+m.group(1)): print "WARNING: file %s included from %s doesn't exist"%(m.group(1),fullF) pass else: if m.group(1).split('.')[0] not in plugins or incBaseName==baseName: linkDeps.update(grepCpp(path,m.group(1),level=level+1)[0]) continue m=re.match('^YADE_REQUIRE_FEATURE\((.*)\).*',l) if m: featureDeps.add(m.group(1)) return linkDeps,featureDeps pluginLinks,pluginFeats,pluginSrcs={},{},{} for dir,f in sources: if getModule(dir) in ('core',): continue if not getModule(dir).startswith('pkg'): continue link,feats=grepCpp(dir,f) plugin='.'.join(f.split('.')[:-1]) pluginLinks[plugin],pluginFeats[plugin],pluginSrcs[plugin]=link,feats,dir+'/'+f import shelve cache=shelve.open('linkdeps.cache') cache['pluginLinks'],cache['pluginFeats'],cache['pluginSrcs']=pluginLinks,pluginFeats,pluginSrcs pluginLinks,pluginFeats,pluginSrcs=cache['pluginLinks'],cache['pluginFeats'],cache['pluginSrcs'] pluginObjs={} for p in pluginLinks.keys(): pluginObjs[p]='packages' cache.close() def getPluginLibs(plugin): libs=set() myObj=pluginObjs[plugin] for lib in pluginLinks[plugin]: if pluginObjs[plugin]==myObj: continue try: libs.add(pluginObjs[lib]) except KeyError: print 'WARNING: plugin %s, missing lib %s'%(plugin,lib) return libs allLibs=set(); for p in pluginSrcs.keys(): allLibs.update(getPluginLibs(p)) print "\tenv.SharedLibrary('packages',Split('"+' '.join(pluginSrcs.values())+"'),LIBS=env['LIBS']+Split('"+' '.join(allLibs)+"'),CXXFLAGS=env['CXXFLAGS']+['--combine'])" #print plugin,' '.join(feats) #print f ,' '.join(feats) trunk-2018.02b/scripts/pbuilder-test-distributions.sh000066400000000000000000000015661324306050200227040ustar00rootroot00000000000000# # grab latest trunk, try building packages for various distributions # see http://yade.wikia.com/wiki/DebianPackages for configuration of the pbuilder environment # set -x TMP=/tmp/pbuild-yade-$$ mkdir $TMP cd $TMP bzr checkout --lightweight lp:yade trunk cd trunk; scripts/debian-prep jaunty; cd .. # distro not important here dpkg-source -b -I trunk DSC=yade-`cat trunk/VERSION`_1.dsc for DIST in lenny squeeze hardy jaunty karmic; do echo ============================================= TESTING $DIST =================================== echo =============================================================================================== BASETGZ=/var/cache/pbuilder/$DIST.tgz sudo pbuilder --build --basetgz $BASETGZ --debbuildopts "-j5 -Zlzma" $DSC || FAILED="$FAILED $DIST" done rm -rf $TMP if [ '$FAILED' ]; then echo "***************** Build failed for $FAILED"; exit 1; fi trunk-2018.02b/scripts/ppa/000077500000000000000000000000001324306050200154355ustar00rootroot00000000000000trunk-2018.02b/scripts/ppa/.dput.cf000066400000000000000000000002521324306050200170000ustar00rootroot00000000000000[mini] fqdn = localhost method = local incoming = /home/USER/M/Temp/archive/mini-dinstall/incoming allow_unsigned_uploads = 1 post_upload_command = mini-dinstall --batch trunk-2018.02b/scripts/ppa/.mini-dinstall.conf000066400000000000000000000006011324306050200211230ustar00rootroot00000000000000[DEFAULT] architectures = all, i386, amd64 incoming_permissions = 0750 archivedir = /home/USER/archive/ mail_on_success = 0 dynamic_reindex = 1 archive_style = flat generate_release = 1 trigger_reindex = 1 keep_old = 0 release_description = YADE Debian/Ubuntu daily packages [wheezy] [jessie] [stretch] [unstable] [precise] [saucy] [trusty] [utopic] [vivid] [wily] [xenial] trunk-2018.02b/scripts/ppa/buildppa.py000077500000000000000000000131751324306050200176210ustar00rootroot00000000000000#!/usr/bin/env python3 import argparse, os, git, shutil, sys, time parser = argparse.ArgumentParser(description='Process some integers.') parser.add_argument("-i", help="config file") parser.add_argument("-u","--update", help="update tarballs, if they are exist", action='store_true') parser.add_argument("-j", help="number of processors to build",dest='jobsNumber',default=6, type=int) args = parser.parse_args() configfile = args.i jobsNumber = args.jobsNumber infileconf = open(configfile, 'r') linesconf = infileconf.readlines() gitdebdir = linesconf[1].strip() pbdir = linesconf[4].strip() gitupsdir = linesconf[7].strip() userg = linesconf[10].strip() groupg = linesconf[13].strip() dputg = linesconf[16].strip() keyg = linesconf[19].strip() keypasspath = linesconf[22].strip() keysecrpath = linesconf[25].strip() patharchive = linesconf[28].strip() if not(os.path.isdir(gitdebdir)): raise RuntimeError('Git-debian-directory does not exists') if not(os.path.isdir(gitupsdir)): raise RuntimeError('Git-upstream-directory does not exists') if not(os.path.isdir(pbdir)): print ('Pbuilder-directory does not exists. Creating') os.mkdir(pbdir) repodeb = git.Repo(gitdebdir) repoups = git.Repo(gitupsdir) assert repodeb.bare == False assert repoups.bare == False if (repodeb.is_dirty()): print (repodeb.untracked_files) raise RuntimeError('Git-debian-repo has an uncommitted changes. Exiting.') if (repoups.is_dirty()): print (repoups.untracked_files) raise RuntimeError('Git-upstream-repo has an uncommitted changes. Exiting.') for branch in repodeb.branches: branchstr = str(branch) if (branchstr!='master'): print ("Switching to branch %s"%(branch)) repodeb.git.checkout(branch) infile = open(gitdebdir+"/pbuilder", 'r') lines = infile.readlines() mirror = lines[0].strip() components = lines[1].strip() archs = lines[2].split() keyringuse = lines[3].strip() othermirror = lines[4].strip() infile.close() for a in archs: tarball = "%s/%s_%s.tgz"%(pbdir, branchstr, a.strip()) addAllowuntrusted = "" if (othermirror!="#"): addAllowuntrusted = " --allow-untrusted " if not(os.path.isfile(tarball)): createPbTar = ('sudo pbuilder --create --distribution %s --mirror %s --components "%s" --architecture %s --debootstrapopts "--keyring=%s" --basetgz %s'% (branchstr, mirror, components, a, keyringuse, tarball)) if (othermirror!="#"): createPbTar += ' --othermirror "' + othermirror + '"' addAllowuntrusted = " --allow-untrusted " print (createPbTar) print ("Creating tarball %s"%(tarball)) os.system(createPbTar) else: print ("Tarball %s exists"%(tarball)) if (args.update): print ("Updating %s as requested" %(tarball)) updatePbTar = ('sudo pbuilder --update --basetgz %s'%(tarball)) os.system(updatePbTar) # Creating dir for building builddirup="%s_%s/"%(branchstr, a) builddirdeb="%s/build/"%(builddirup) builddirres="%s/result/"%(builddirup) #shutil.rmtree(builddirdeb,ignore_errors=True) shutil.rmtree(builddirup,ignore_errors=True) shutil.copytree(gitupsdir, builddirdeb ) shutil.rmtree(builddirdeb+".git") # Get package version versiondebian = repoups.git.describe()[0:-8] + repoups.head.commit.hexsha[0:7] + "~" + branchstr # Get package name infilepname = open(gitdebdir+"/changelog", 'r'); sourcePackName = infilepname.readlines()[0].split()[0] print (sourcePackName) os.system('cd %s; apack %s_%s.orig.tar.xz build'%(builddirup,sourcePackName,versiondebian)) shutil.copytree(gitdebdir, builddirdeb+"/debian") os.system('sed -i.bak -e s/VERSION/%s/ -e s/DISTRIBUTION/%s/ %s/debian/changelog'%(versiondebian,branch,builddirdeb)) os.system('sed -i.bak -e s/VERSIONYADEREPLACE/%s/ %s/debian/rules'%(versiondebian,builddirdeb)) os.system('cd %s; dpkg-source -b -I build'%(builddirup)) os.mkdir(builddirres) buildarch = '' if (len(archs)>1 and a != archs[0]): buildarch = '--binary-arch' #Build only arch-packages print ("Building package %s_%s"%(sourcePackName, versiondebian)) buildPackage = ('sudo pbuilder --build --architecture %s --basetgz %s %s --logfile %s/pbuilder.log --debbuildopts "-j%d" --buildresult %s %s %s/*.dsc'% (a, tarball, addAllowuntrusted, builddirup, jobsNumber, builddirres, buildarch, builddirup)) print (buildPackage) os.system(buildPackage) os.system('sudo chown %s:%s %s * -R'%(userg, groupg, builddirup)) os.system('sudo chown %s:%s %s * -R'%(userg, groupg, gitdebdir)) os.system('sudo chown %s:%s %s * -R'%(userg, groupg, gitupsdir)) os.system('cd %s ; su %s -c \'dput %s *.changes\''%(builddirres, userg, dputg)) for branch in repodeb.branches: branchstr = str(branch) if (branchstr!='master'): os.system('rm -rf %s/%s/Release.gpg ; su %s -c \'gpg --no-tty --digest-algo SHA512 --batch --default-key "%s" --detach-sign --passphrase-fd=0 --passphrase-file=%s -o %s/%s/Release.gpg %s/%s/Release\''%(patharchive, branch, userg, keyg, keypasspath, patharchive, branch, patharchive, branch)) trunk-2018.02b/scripts/ppa/ppa.config000066400000000000000000000007321324306050200174060ustar00rootroot00000000000000# git debian-directory /home/USER/dem/yade/yadedaily/ # directory, where will be created pbuilder tarballs /home/USER/dem/yade/ppa/pbuilder_new # git upstream-directrory /home/USER/dem/yade/trunk # user USER # group USER # name of dput ppa mini # Key to sign packages AA915EEB # Path to passphrase key /home/USER/dem/yade/ppa/gpg/pwdonly # Path to key secret part /home/USER/dem/yade/ppa/gpg/yadedev_sec.gpg # Path to archive (deb-repo) /home/USER/M/Temp/archive trunk-2018.02b/scripts/py2wiki.py000066400000000000000000000047351324306050200166360ustar00rootroot00000000000000#!/usr/bin/env python # encoding: utf-8 # # 2008 © Václav Šmilauer """ Import commented python source, format it for yade.wikia.com by interspersing and literal text in comments. If given a second files (which is a c++ file), the comments will be taken from the python source, but the code from the c++ file. There are special marks for comments in python: "##" for empty line or "## some text" for non-empty line (the first space is stripped) Special marks for c++ comments are //@, but their contents is flushed; they serve merely to synchronize with python comments. Adjacent comment lines and adjacent code lines will be merged. Leading tabs in the code are replaced by unicode non-breakable spaces ( would otherwise eat leading spaces of the first line) Usage 1: generate simple-scene.py formatted for wiki ./py2wiki simple-scene.py Usasge 2: generate SimpleScene.cpp formatted for wiki with comments from simple-scene.py ./py2wiki simple-scene.py ../extra/SimpleScene.cpp """ import sys,re class Text: def __init__(self,txt): self.text=[txt] class Code(Text): pass class Comment(Text): pass pg=[] commentPatterns=(r'\s*##($|\s(.*)$)',r'\s*//@((.*))$') codeLangs=('python','cpp') assert(len(sys.argv)<=3) for i in range(0,len(sys.argv)-1): pg.append([Comment('')]) sys.stderr.write(str(i)+': '+sys.argv[i+1]+'\n') for l in open(sys.argv[i+1]): l=l[:-1] m=re.match(commentPatterns[i],l) if m: ll=Comment(m.group(1)[1:] if (len(m.group(1))>0 and m.group(1)[0]==' ') else m.group(1)) else: ll=Code(l) if pg[i][-1].__class__==ll.__class__: pg[i][-1].text.append(ll.text[0]) else: pg[i].append(ll) # replace tabs by 8 _nonbreakable_ spaces (\xc2 in utf-8) in code # strip leading/trailing blank lines for p in pg: for l in p: if l.__class__==Code('').__class__: l.text=[ll.replace('\t',8*' ') for ll in l.text] while len(l.text)>0 and l.text[-1]=='': l.text=l.text[:-1] while len(l.text)>0 and l.text[0]=='': l.text=l.text[1:] table=False showCode=len(pg)-1 for i in range(len(pg[0])): ll=pg[0][i] if ll.__class__==Comment('').__class__: for l in ll.text: print l if ll.__class__==Code('').__class__: if table: print '' for j in (range(len(pg)) if table else [showCode]): print '%s'%('' if table else '') if table: print '
    ' if table else '',codeLangs[j]) #if pg[j][i].text[0][0]==' ': print ' ', for l in pg[j][i].text: print l print '%s'%('
    ' trunk-2018.02b/scripts/rename-class.py000066400000000000000000000037251324306050200176100ustar00rootroot00000000000000#!/usr/bin/python import os,re,sys,os.path if not os.path.exists('SConstruct'): raise 'Must be run from the top-level source directory' oldClass,newClass=sys.argv[1],sys.argv[2] replCnt,moveCnt,moveDirCnt=0,0,0 for root, dirs, files in os.walk('.'): for name in files: if os.path.isdir(root+'/'+name): continue if not name.endswith('pp') and name!='SConscript': continue modified=False; new=[]; fullName=root+'/'+name if fullName.startswith('./lib/') or fullName.startswith('./extra'): continue for l in open(fullName): nl,cnt=re.subn(r'\b'+oldClass+r'\b',newClass,l) if cnt>0: new.append(nl); modified=True; replCnt+=1 else: new.append(l) # write back the file if it was modified if modified: print 'Updated: ',root+'/'+name f=open(root+'/'+name,'w') for l in new: f.write(l) f.close() # try to replace the class within the filename, move using bzr if it has changed newName,cnt=re.subn(r'\b'+oldClass+r'\b',newClass,name) if cnt>0: newFullName=root+'/'+newName os.system('bzr mv '+fullName+' '+newFullName) moveCnt+=1 # walk directories and change them with bzr if they match exactly (rarely useful) for root, dirs, files in os.walk('.'): for d in dirs: if d==oldClass: os.system('bzr mv %s/%s %s/%s'%(root,oldClass,root,newClass)) moveDirCnt+=1 print "Replaced %d occurences, moved %d files and %d directories"%(replCnt,moveCnt,moveDirCnt) print "Update python scripts (if wanted) by running: perl -pi -e 's/\\b%s\\b/%s/g' `ls **/*.py **/*.rst |grep -v py/system.py`"%(oldClass,newClass) import time,pwd,socket # update python deprecation records if replCnt+moveCnt+moveDirCnt==0: print "No replaces, not updating py/system.py deprecated names map." if True: new=[] for l in open('py/system.py'): if 'END_RENAMED_CLASSES_LIST' in l: new+="\t'%s':'%s', # %s, %s@%s\n"%(oldClass,newClass,time.asctime(),pwd.getpwuid(os.getuid())[0],socket.gethostname()) new+=l f=open('py/system.py','w') for l in new: f.write(l) f.close() trunk-2018.02b/scripts/update_names.sh000077500000000000000000000054141324306050200176650ustar00rootroot00000000000000#!/bin/sh # The script modifies authors names not to get duplicated names # Modifies the git directory! git filter-branch --commit-filter ' if [ "$GIT_AUTHOR_EMAIL" = "gladk@debian.org" ]; then GIT_AUTHOR_EMAIL="gladky.anton@gmail.com"; git commit-tree "$@"; elif [ "$GIT_AUTHOR_EMAIL" = "bruno.chareyre@grenoble-inp.fr" ] then GIT_AUTHOR_EMAIL="bruno.chareyre@hmg.inpg.fr"; git commit-tree "$@"; elif [ "$GIT_AUTHOR_EMAIL" = "christian@localhost.localdomain" ] then GIT_AUTHOR_NAME="Christian Jakob"; GIT_AUTHOR_EMAIL="jakob@ifgt.tu-freiberg.de"; git commit-tree "$@"; elif [ "$GIT_AUTHOR_EMAIL" = "donia.marzougui@3sr-grenoble.fr" ] || [ "$GIT_AUTHOR_NAME" = "dmarzougui" ] || [ "$GIT_AUTHOR_NAME" = "Donia" ] || [ "$GIT_AUTHOR_EMAIL" = "marzougui.donia@hotmail.fr" ] then GIT_AUTHOR_NAME="Donia Marzougui"; GIT_AUTHOR_EMAIL="donia.marzougui@hmg.inpg.fr"; git commit-tree "$@"; elif [ "$GIT_AUTHOR_NAME" = "Francois" ] ||[ "$GIT_AUTHOR_NAME" = "françois" ] || [ "$GIT_AUTHOR_NAME" = "Francois Kneib" ] || [ "$GIT_AUTHOR_NAME" = "François Kneib" ] then GIT_AUTHOR_NAME="Francois Kneib"; GIT_AUTHOR_EMAIL="francois.kneib@gmail.com"; git commit-tree "$@"; elif [ "$GIT_AUTHOR_NAME" = "Jan Stransky" ] ||[ "$GIT_AUTHOR_NAME" = "Jan Stránský" ] || [ "$GIT_AUTHOR_NAME" = "Jan Stransky" ] || [ "$GIT_AUTHOR_NAME" = "Jan Stránský" ] then GIT_AUTHOR_NAME="Jan Stransky"; GIT_AUTHOR_EMAIL="jan.stransky@fsv.cvut.cz"; git commit-tree "$@"; elif [ "$GIT_AUTHOR_NAME" = "Jerome Duriez" ] then GIT_AUTHOR_NAME="Jerome Duriez"; GIT_AUTHOR_EMAIL="duriez@geo.hmg.inpg.fr"; git commit-tree "$@"; elif [ "$GIT_AUTHOR_NAME" = "Luc Scholtes" ] || [ "$GIT_AUTHOR_NAME" = "scholtes" ] then GIT_AUTHOR_NAME="Luc Scholtes"; GIT_AUTHOR_EMAIL="lscholtes63@gmail.com"; git commit-tree "$@"; elif [ "$GIT_AUTHOR_NAME" = "Luc Sibille" ] then GIT_AUTHOR_NAME="Luc Sibille"; GIT_AUTHOR_EMAIL="luc.sibille@3sr-grenoble.fr"; git commit-tree "$@"; elif [ "$GIT_AUTHOR_NAME" = "Raphael Maurin" ] then GIT_AUTHOR_NAME="Raphaël Maurin"; GIT_AUTHOR_EMAIL="raphael.maurin@irstea.fr"; git commit-tree "$@"; elif [ "$GIT_AUTHOR_NAME" = "Raphael Maurin" ] then GIT_AUTHOR_NAME="Raphaël Maurin"; GIT_AUTHOR_EMAIL="raphael.maurin@irstea.fr"; git commit-tree "$@"; elif [ "$GIT_AUTHOR_NAME" = "Kubeu" ] then GIT_AUTHOR_NAME="Alexander Eulitz"; GIT_AUTHOR_EMAIL="alexander.eulitz@iwf.tu-berlin.de"; git commit-tree "$@"; else git commit-tree "$@"; fi' HEAD

    Yade is located at www.yade-dem.org, which contains this documentation and wiki. Development is kindly hosted on launchpad and GitHub ; they are used for source code, bug tracking and source downloads and more.

    ӳgK+457\YQyKgK)h׵L]yK;/476:s%&8N̊FHGM5{ T#TVkQ )\2QrLɅSZ+yʧ{TK$PD3=:fV3-Ɓ1=jŹe+Kw|{S~҇CuuV ݹzFzg5ߪ?–ZOYաZAԫ}%8'y-(]M1a'pb1JFqx8eTiܾ\* 됕Z+4G2-&3z;:$ͣa6=U8E7Nn'N~PhN,;.ݴmXDg'ޤ1X|ufY","VO:gKZ[ ZWv[Q%bgl/޷$\yxC.zhsԓLz6+c=ǭe ń&>7o閞n}LKPÍ^>Jg`fGQ\Ppv!uitq)7$uaʇCf2n!t1*Έ* UU񪑪Ug.T B>$wk|x"nu-E9iL[@Ϊ*l w/Slم͚P͕pPUYzP{]VE~c]dsciy1rcaPdmܐ|%߮c/j_ sCKeF|[Y QוwYsWntC{jm\ܖQMM8rqQ1Q3K $ioDZљ=!aFa_5~́a'nc񗿬 .f7Fʋ~󚴪yZI;[+|ϓ"])io?9Q^9Bq0.P yJ4Nj69#)eNEZ-"')%S9(EbȝAIqDbH&f4Lj0%s^-!籦Uod;!^=} {2&PpG8?RJta /BCI-b$ c 4 npJ M@uˆ0!p,dStNyOU0}Vdts8tRA 5jVGy*TEAtA%_/,&zc@%}>>%9b*s\q. |>Οll(V5=o뿾ֿmX0Yp&.hȅ?Bg?nuzd}]G2?7^?y2%R׀ĴIjQ.x,||9x. ^ Ҡ CAa22# BDAܶ Tx$g᎓=/8sO0`SC)3re9X lXt؜ND|(g\ͱ|gx9#u=@I.Kp[?x!`{ІMo`r)YLʽʃW_f [XR)z{`,!v}-XÁ'܂ -PU6鰼'"04l\>x5@e$** c ײE  X3UE?º;:PW&xK,jNt*|Ϯ  p*VP1XS`Sa<JX3#@ BXR/9>8N*;Τ#NIEgC>R?OI72[J!Ϗ Вߑ?'G>'y3uzU 8.28)Pmf&GrZkoP1s)7ro]J?U!HoI\P!o($B3T{#*1ڭܸzm-1yfZ+GpPJx=,l魋q#ܗx}*܋\B&Xpb5jNW]*/YDCLW _*/YL/s Ph/ r|α%Bڃ sKNM'u*%8}%}:E}ʿ:mA TfOI8b4&VRRӦR5%QEAOEgQ8}9J¡((B18( /GFEp, +CX# |.F(>5 +Pm(X8QIwGa4 QG) (j +r6 gjl8}.,*JYU!UҮfsQz}+ejQXU;݌eR^atVҨ%J!ȴ>XE)k(7YE髊0+bb*ٓl@[{!52pXQHE't ;z,3lrҴ$ϰ 3Q8T>>JiRj2z4Jd֕D6\b`7<: nL(P)D'`DFjBm]|ɚ(",;3Y$RUUUkK nV6nܫ쿞 _NB7:8\>1 Mffo)Bo6nZlvloOwޑh7ߟnёP_c?nk&UYJϚw-'UđNݤp+fͤ32b3;gyL~&"RhFbs߈s?P"F20HBc9X>O-j o1rfTU<gȮX]fgsZ#jjc!/Pt'AnvC7{ee26 ݐS/wS); |74ݻus0ln)=VëlyX]tA~eUaku'qGutRi~ܚ1 7fgYơ>6B{35Gu/` SrBy22` +FW_!gWp5 Ѥsy8G>#7msv y\l8zf}ʣK4ǫҕ7'R(?Wږ>dκU v(Im/5G"'E.syw*}sU3[%ۗ"7:X8dK/7uwl[P0Q)? ?Dkwei=ޒحӝ'ŲmJ'T3_@ovS.ZBЕw F^uh C] J6I6 eF6Xs|`L'TzJxQ02x-JmrF3/C~PO1Ұ ޡ38=#hv$ݎC$9s8h钲a'F^JRQ3[4&$LgDAeHmwîH?AԆ(V1r1G" L:7z̔!n(S4fd`"?~aax~/!DJw q;t^:SAHĮYo}!!'5:j|ͷ[3|xMvVvjA l);Zch l3B<2+Tkr}FH|g/[,ua68ٞĊɫm L+|#df%;5Uf,ۅʛ[i)SS0kAG_F_miyU6@*G9sV_*5sJ̍vG(m˵oyhOZ<9((? \$ o-i@ܫp?z'-DmC %bR Uܱ%xwV]D".QKjV8'hל& `rEw͚mVN vLpdRǡyE?=䇐qQ^s,̚1\)#=ꇀ5x\)N%?xПO/5ߋI?c0q v{$7ιL؇(faMUٛGMj l3+, iQfV4ؙ2~[, eO?}=:g5wku7n<ZK|ܗw`gI앋6D:szr;yM^$ŞAAtL5tƩԡt.;K&utٳ9Qob"ĹydG(iFkzdTٍڻ#CJV^+RfjL6C8n{^!N|a/rIvaW~-l#X+4 C/j*rT&b*Rhi YT- eIW-B,fX2m$,ീu6KerrrrѢFl[F,1XSh[ duhW\?XpZٔ%_;Mbgg}= pY#\vmo ,~8+hr/5މz<7dtG&B= fS v8ʢrz2*QT)#iıL g1&yFDxZC3 9mG~bVꙕ==Lov>l|v?}v&oíY&TdL/Wqp0Iqea[V)$KUkgS'9;rhrp9ѹҹiՠ5_M Yl%r %ݕ6FM6-D[M}.>{s5 g u% mm8܂C5>TӛtW=Eۣ7|F"}**~/p̏8Es޸@݇\hYD,kM1bESC䡁9@0+iZ*iVS9S8!wsrt/$ttƈMY;)v33k6L;ځM@E>uK3DN4AR|\zS Cq~ bvkCPsC  :H!ސVHh*$+52bwMIT)$RJ"zƔcQY`-lv^J2 HDQ,eɲ2WN񹺶JA)}UjZ4_2(>FWdS5d $@T)G[E4lϙ=R!K a\T$m.p¶>0dʊBz=[pⳞYOu"h4P {{Jcuz\l44xh叧lfkτ- f+$ɉ-.bm\ozpTǹ9*ўFVtbh[ #G9uW1 TBߧOY[qg;=+6t{l# 4?sua{`wg_`kKq\O}͗n͕>w ,P9d6LSω04vTQbL_-z?'E: D^dPTL:;+^d6sފ%EH(cij9G"H(^l"w9o'EuȍE 2݌֍mEnV[dz(ҘD"=">+'~(qT4,{ _(Q(idY 2Ft tXO9c,^jUN0 _ҿ#O%a6bX9&xzB/R*![E"c^{1f;,o)xwmȯ8Pde*Db9B=d׫Ŏpu!rI' C/0bLDMxP<təD%ٲcx j*h}!`hӗNF7ߔ*ݰn-pSvB4Z!zR^/WN+)z뼜G vqs;LL" *@S&%LDԶ.7IDTFH<ʆ t/GR8ӄbɁXjjEdd0Gjy''cxijPUXGw%k->~W`Q-| ֫xK+!L !FeFe{ǭCy/Í{us?/Rכoh,;BEUhmr: ʒCsoUM_o׽'im˭C}LJ޴)<ZeA%5zh_]Sby7B dZ> Qm 9D:-!rie. k1q\_t D  #{)MRȡj6rӂSpD)~!& 6b-ͥԨaoW;B_#&F(cމA_QVjYHg}ŷI'=}K ڃs]%ɛ^˯9qx{Ď3i߰e*߂ q3ۨ㦷}$rAqMT[s쩭Z>)RɼY]V2 \J FAo׻1dUab=(w+,-`a;,P)_ΧU|IMޫ4z=3U:%Ԝ>iD'ٷb6ƌw1;jձ/&kc^m|Ng"̑.E/^ n;~3&}a;geҟ4˝pS%x,w{iwBWёʳIMc ˆap5'`p@O^65p=\&四ĒIB+ lmhC 7V23g SJY!i8jH& vT}}+Ƞ12Bw珵P('9&7ҋZ3E)CFo3m:Z$U18ZcLM>:# ѸIc~bVlvKw+(b?Kwg wKwr8>H|ȃ6m|~pۓmy/1&Zq6SO#((`}!հrվaiN|sa_6LlaW]=s\ x8%ycf#htpU2 FPF\pnH ' ^UU AƤ Y3ql3Oi;;/먊 j#GFXS?@3pպk};vG⎭9~v;_dclAѨw`ed0|䷖y\`{OOJ$;^j% M]&]J5@^1XvYZҧ {Zsz7oXXX[vo*WӋͻ<ė?~y'- }ofmAmӐ%xESLP@iVRYNiyȜb ʎ8ׅ딡55ĪZI@M${iH%DPB@y,\ፗQih(#U8S8ɻgC5Sc8C^Y@9s5P3sN/-s{2No%w|TϾ'qUI$$h(RKcˆv=-h3yfVÜAÊgs]O5vf_3h!a:2Δk5[o+֒[vӚ6op_P[uQbߞ^q}`uurĊc͋4sejy7kNSu:7߱x$9]pr٩Y6=۸M80?~*i 3,kjj8uLcz)5ܕ9B-* Wlxr3{y;],e[`u{*ىn~o]<#G~?qw#V4$J8o)Jܳ%(3LP.(MKΖp%8 [-IPbb6rZ.c-.˜Vw{; 4Zף~)ZX06FO͊hf3_3޲k/e7?2T%78sȷd/13ǜ Ž9Kn.k-Ʌ;_sY8hܒLh. To3Ps8ϨqiTc;3${2*%B;e R|됧xf\?m?}8SL?~y7xǨ}c]eñ!2)w-|`!۶y[хsPQ]3Alx XҜ9t,0nέFPUT ΘͪX)-6E(],K9Ζ؝db$A͉D& ??= $4)߿;H%ġI7*'l@Ĺ}1xX*1 ll2q&A V)Y)ix CD&D^_o-bid,YgpRyW4.-Xe~Ko!eP(<)Sݢ@];udDLḢXnٌNlvNts?s7/a yn> V*f{"JpdiۿiqкՃ qx?2Ŋ޺etic_$[7 oZ;>>2'޶m[Ey5VƷ  [ ]v|ㆎA֞Z<4?:?eխǽ;݀؄Y׌nd-ݢxuzo^=82afRjJ:A~H%<2FV#&E>گ1 "Ɣ Yޭʳ*"L_P e"0l5(TB ]MvQuJ;ƕVύJ#my619ǔeZyW+rUJWpS@ W\0r)YW/eXJ#d+m+% tMzm>2@sBL/5*חWex2x/KDžϳ^@|rC4G=Қ z>7Uy/nǯ#twan݉wSr̦zWk}w8GNVNLNL0|?1}}9Γ\)0=yM>J><yp[뇋x77֗ұxgxSȳt#\ ^`qkO!SwWcX:,&5rKa,xw*~!NMK> endobj 6 0 obj <> endobj 11 0 obj <>stream x]n0E|"vI$ Cb)R1q}=:󸣫Ҫ95f髛T  \=;A.1TIjw [t$}q`lQ!no~3%Ї9O}FHjP1dEMf)pBRp^W$^}v.6+Ȼ-8yD)@ =q|,99##Wgzv>stream xz{|Sבsޒ%qeKȺ,Xe,lc.C`6K;EB(ytN6!Ֆm6:{#dšD|K <BB> !E<Cۓ{%}AEo62__˷?#=wB0:2|"XXbt۩{[4?vT3rJ)lɶs%2ZJ̙Mw䒧}a3f?B >c1DπA2B>C&!r#7H >K^Z+LW,y斌;ȥ@1rѡu*DL*m*z;ގw/޷SP+Mf"A=>[g3 ZHǟ)=V܍ʥZz5#ZI~fr'G5ml#Rʖz:;׶i]ees~Erv%EU e%E@?ͱg[Y&^ըU<#-i7Ta,RH@D$5̓c x3?)9k`%%b_LT@ni^|PBlSR&bA¿"/-! h(-jX|u TzSZҜ+Md2^(*af:9,*   ).\Tv(5_98D_ߐ 1-i%T_pLg(CFtd_lMNl)qr=LںQS_IYbC83m 4Cq[ef@wO}>6gd 4.MhL͵8X\5=̓xjb,~K*OnҚ-ք{^jSBt J(D&- cڍf[?az gP*KKRP:)9Qé0Jcz%|4e]'3a[Ɉ+R$6JYbd>m_}Df/Z(1 +0 &71 δbۗ{0=DK4؝O1EWtvt[v/n`@çi5r)m@+vS7׃$= hkA+TuKnp9n4#5_lHg~RK9mjQǗJK(6QB˜kX FQBba9/vۺؘ{/g<Λn">lC3S!M5)54fqRod-oNl2|7qV)YfsyMIि{;X_V-u%XN឵dOtv?E*"!Bʈ 4#UgeB&V^!( M;G2pi9E ]!1q3=CD@R_/;TmL_赌^]1iiSI={;p!eϞGky]Z<-/ʻw)O̓ѼcM<07S>uahufD%4~*j!7׷sXƾ7E7-(+hevҗm" t+ۻKkb aSݪU`φ/[ EG^-t]N=s9uWS;G q!s+^3pn.qD6hiM,*Qu^ūTPyAQSg PwpgہNDH8~5 };v*x7tN2Y^ x7x,@C;QWeI!aPyf}} ȱh8 ٪ ȝPsN5 74GG4]*ux, L.{v25q[}Yù6lZ27?se^ UJcvghll~dx, )nc5VwՕyt`/(%_ナ{{piayBe~^7_Tvٽ4xh2&!/tTU B%*Gmz곋gbbJ ײGkkVjz5|@{xPqkx J@yUh7x+Wmać](-<5[VSC;[s\ymVAeDaj]iuސ lc@=y󮇻!OO#!zs5BҔ缇9[Ӓ ==Iz`o`*ʔlpOlUpd{>m9yCEO'}G=.OSQ(]5TE0HأEE$`n(OyN:ޥ^zr)]*,QP5{aĻKUU?Q`ƣFj`˱tYp̅\* WʡtE8o.חq,qqYˬ֔/55[!}xZS]jﶮܪ屖p45[W'TF<5k+75C%l\ 0{rZe\}uPSjS E|eypӦ5 ɫ+*ht8/H 7\.*vr@u) a>'uj Yaa@lyf.$h:iT#qJKH0,uLp,q?ޑ%xR%8$lFϕOߑu ^(SB[a}>EL/*A-Y^d чqkǢs}@z\zFHIPBI^9F>Ii/F1-}K{Y̓RRQz  nb5"L?Jn)JTPv6C%ʸej?I%zX:w R`T&r$&EۤQiBJI|D-)ob )PsZˋ9րqkך,%ߴdޚ] N+N4W2о7DMCc.[$WiTJ+W(˔&nov<;J[ybXIgJ;tU4XX7SalzFf-TöWÝ6uTȾx}cHwhgz}(ɬY\hDx Mn. +zSb/ȇiM2@ |[>|9S/嫼0 |> sq?CMdeԼyVi1ku9kaXTX=qO{2snSnʿFgS#$?;duMBo˂n#茹Fz[Э\r9M@()~"p+Z w Y[ aU~_xq0u$5 EzX8.|_`/ ~䒻GyUGVX&yyst_.,(91?pNx[)noQGDQUCMnvnfVg#;[c}EY6*GisyCY ӝ1=v\ئc-*^h%/lywΚp͎TdG"OfqwǧveY~Ӡ_}f,Μ/7t#bqWߡn+Psv]7TVp`4WlO8W6:K;1W`AڃX A-*(i[0L//U !6AXWGȼ&|}/5@9,VmW v^NmxO%x)[|Jgny!Edܺ'`.m@jUQ,(/  '+y:9Gk]k\Ttu]?wR- +m?wf׈눋]Z.zuuűJ\?#hA40Q:[ןei\/XX+O!RAN410YexST.άZUdP3쇃ui~}X0ABT'062&b\ghlN[ݭ\جЮ4^niU!6TYRsZ9]VMcTFwWTLG,?<ȇ,)ZMWNÛ'v]2x~Ռ6UWʮ[8ߟ-BۃdY!d!ngM&TQ9veT_d8Ϣ؎͇s_A6BKu$tS8/--Y/)uJ?{-)3R474ͥ TyK>$ܯ-ΠG:!7pD_]=RZGD~x鷉'TRp>:l3#>1Wœ#;۶c[Xb<1;1XjxS".v$Ɔ7׍l&]*~Yd$K3ʂ J{駄Ÿ&Ƕ#v?O;и-w]eeb[<ؑ;kk6oH(āX2#!]c[9#؝WǓȎdrtq8gϞxyyFZ[rhb01>el(}[x'9caěG=M'bry6oK$R,34<0$%IcKۮ )z2L7;H t$q= R-'dyv G!er+:FL݆"ҙ!lW>n2 7)"P(d3C6KI5he9zV˗= +g>M*-l9F"mD="YP3(dۢhI*ӜÊ.PI楤=7W+mHFb|a yn<[@ۑ+$O(JDLѹ٥d\|/qu7+OqE"ĕX} =P4d2mxG4\Lw==xq,#dgo_mOwzz#ϓ[\o.Vzߊ]o9fMM/5/C~DaOMmŞ}ASyo= O|*#t4x^wOyO|F'`y_ ]ޓx]]p yD-]4zGhFr4Kz'g"Sbb ( Aި35pd#!oHoK`XT u9/W˭F#Z۱mCu#[rޕM9j076A&ouw GW6,s6Ϛoo̤ TpZhf[R)'`wymoJ}Ot>p_]K<-TӒD@f)'+o!qOB,9$ƒ_D8RC)$8qM"mf91\ endstream endobj 12 0 obj <> endobj 10 0 obj <> endobj 5 0 obj <> endobj 4 0 obj <> endobj 14 0 obj <>stream xXˎ6 Wx] Hɒ qREU_@[ˇ(ɱbı):<a i8G>9;]ݍ".Όx^A1$_M9c!GlP)h $ ~c({5zw(T-̅ C{1e/vM%8JQ}ݰS3";j0MrP&0 u^̢E.UByp<+q̙|8E:v")|WV5s 3s:7켢&R;:(4ka,ԜE~ϩ0jMJW8&iL +5dyZ +ILEw—+'Az!3R)`r7T '@pRXS;դbq#Q+Jn6Т`\҅OƳђi_ ] Eԉ" s豘wa/ݚ)E⥔QRGY3 ^D3fJg-UMkRd645<>w_`wiԙ$0fيrWe Gr:;Ɍl6JwjjEૠkejwK^<Ѝ:xfڗH֙G NJ 1m[lHdř<]eh8$ď3F6Naۧ\6жmt^é֠85%AMbe[4:=UOw\WInORnqOzw[_"~+ QŰ5+ڰ^@<LiJ}(>y!0 C,ȵOrs`H>c=RCmu)[¥s7G㿫  ?1e\9|=PEdMQ7u.Me.wQX7RMKk!W Ujڨd[Kyq>T_͌t3I{9?n}UHLܿŕ&kp4"$m#Z I㤆Js7>/Parent 15 0 R/Annots[2 0 R 3 0 R]/Resources 4 0 R/MediaBox[0 0 595 842]/Type/Page/Contents 14 0 R>> endobj 17 0 obj <>/Type/Annot/Rect[116.3 584.9 175.8 598.7]>> endobj 18 0 obj <>/Type/Annot/Rect[150.7 659.6 196.2 673.4]>> endobj 22 0 obj <>stream x]͎0=Or H(R~&RQ3}NiE޾>E\۟obaw%1//I]~fJ0-zc]'̏iݍ'%ɾϝ>>M%͓*9󵙾5WC9 W|*zmHiߦs3\|R*Ugp~4s(54ϋjh. dYrdҚ5%b}aȯy A2woo+{MΌ9 eL ~C D?~^Lk ~x vL~ k_/XKw0H?B k =Kx~A?%/WZOCB3V3kY Fޏ~,Vkwدߢ{z~X̖пm쿮K=GO\?̎~:-<{q0y>stream xԼy|ř7^\==ӣ5I# mIdc/ ,csC,@X MB` ]dcBC Iv ξoGy{w쿿iu]S}gGb ;֝!o|c o]6|U!M_se/72@=W\aƑf3hIK^qgow,";ټqÃw^wxg6\{۳ Y|g7hKyCysaA(zdV,Vt=^? H4O$SL6/45Kmy[/Yh1Ow7Pn?{/p7zJw5^U=ڠ?ob>E ܄^@Sh)>нhz=L& PuGQ "֡Л"4~$EVxOFNTn/zJɨ z_W<3/N!JVT߀طq-лȂh+ OQt z߂Ga4vrEnGj4@ƽZnҽ!@S.k"p&:~-_Ǹ1_`VV"jGuh3}}Ft;܏u7NTPPz%{'>4 J V<|I&~03`_ak&' w#h-F @{E?F/c,ux#f wW߲S;f~0IuA?\^3vBR*|?Pqf?kb6–*v}97=ν[۠{\0_T{s`C( 2~p[t7=hzz>@8 u~-Pn?/4dZ ,d.gn^(:g7[1bor㸪źGWXD٩ƙ΢Y셳>?MPʢtz ( zxoh]wPCF%p-õx\% 1%|+ ^C?z ? ׯ;]GacL3ehiY\fQG'C묍Yv;>72\psrr>хt=+t^}|__?ǟxODExM1V?v8Wu:;w# 7;ۆWC*n Vo x n`C2tǙ1'?py'৘lS+ݪ{!7قVE;_ ;;01_~\D\Q ečkCl|\^E2~8 i<אoI#xcњ` D6_ce4@1'l cD7cv~30ez 7#7:?$[n'ٷ  ꀹ{= 4x'*0[cx?4Byn邺myd~ { 3k %9siN6A} .8u!E?}/C2Pk8rmVb6 zY^DZ F!u">4#gI<604B³LCzvI J^JjXQ;Qg6Dԉ#u;_ 4ipP{WtxHXx;zu{rWR9A{e=pEb׹WO^FԄ73tL &dELӴꢟ&JSݛqפ.J6E6m0@aIw'\7w[cwTItǎmĞ w`1X w bnUҦZ. ) rŎ``;&7yڡ1Qwꏄ'*n^;qMOz4svN6WԺu\g.=GC8 ^p_1Qd ÄQGMĹ7ᩉM0WNH]C;HWQwG?<;eC=)E$H4A\x"hl$"tBϥR6s$3VTP톁=0 j36s~8&?\1Cޕ=;}ۻX-t^=k\ zjIHu$t+T:NغY3P 1> o&~y)oD `Յ; ˇ&'Sz&:g?z,T3!Վ #C;6LV.Jd!1347էw&5wi3hsCw`kh(R?TDQId̊}Hi\&Ih8I̥14F~St?i`?/{ #>&g,<"{bN+ut\>Ӊ*VNT[–8ϡS*;uJӡOMA} [@ - A v$oZ >Noe"+VWRQfo >#F ʒ ,XMn[o:(#|=JRi83!iKzSd/{E|Ы_[ oV5]o_wu >ѷz}v.qMj{_[xT|LzLQ?)<)>%Oz~R7ԥW/-\Ѵ]}7>..^iYhq7 /}BNrY7S&;$?zg=֋儕a v*3?&? t.Xe~QdEcy>[2%xK"%MduI^V'5{AT^>dyyYsD &^H&5LVeYiCM0fxS:ųbii]ѴiiF5h ]fTp7\dt[9yrp$Nϼ }=k.fr|0mQnLcev)%(M>eBIGtFh2m-࿝17AcO TAG+QH>E_0!sƄ='}DZS4mzRj=v ւgr$)NOБGʹP#Y׻=Ch: n/H䮥E1el%'"M^٘+xoع޵}W h9ۙm2^c4dY!qZ]z^x|B9,ocXi|UZ,VV1K@ny={x@9f8)ţFQ!>q -RUl]x9_qF>{وZ&}|~&:ى7;:w9YI}Lq\d .UDM1398Ѭf3ȤT{„M&+NJ.88240ehTzCտoe#e2Q{;]yfd`d4]Q䇐_G-[6-, 5'1k14Tv(GpS^g@xT4;0e [xӦmnφ?7^ن)+oe쳟x}0~,XG]"Hmcn?{/ʰGR;ZLLHmnKBQTyWL]zPMO\n77ep]1Iޞd\L$`ePy{~ ^ktRr&TEjn}Ji8~DZR`D˽ ù\jSk "Jx&^v=zH\ڢmh^.,E&'Ɣ1˗cw&L御K ǞQ%˽{)f\p;8RO\]JOw}Aj~r{g{؝ktk[5Js,$x"5( "l C`O-EUQ%!JS$A,Q%(Jn1,"hݞTf )Ȅ?dYQB!UE I(E0P 9AeѤ^=eTƓx)ti*2E͡}ǃyzݓja8{+(r͠qL|_ڒȋOPOUcI&I<#LcKaRRjJKMRGSBj(vaa&OLx3OwN{vN+'=] ^eZtԱR KH 7$ ̥CĄA Imcex'19hlaOF 4NKHRs/J0 WzmU+kaϯ4O[ZaQ$ 4#OhȐ!Hiى$'$)UH x]CPT R)E1CxtODTFF(Ew|ԏĨ<ޢ;-m0֥p3_ mRF ج򸄥2KnWxRb6cԭnm(q=n{_dwS5&$0)>Zo! G0?;xj؝GZ@1j7sIzI&f3ꚲ?vQ=0P9@fZͺ`( <0[*¼aJ~d#K!^BA,oH[@6 PͮIHL7˩S!aEsQʳۉk0%LɉTd#$<@C5yj5:b]"* :صϸυ5Wk5w큂!6TOD |[ UxPBi^q܀ x0l710 gB'5;T:?A۸g"bS!0e;jmCRs2CU +1DOW4uݤ)>7{HҙA@Hͥ) >[pe7P4 ,u3޽o0`1f<8׺kcb@Ǚ_O&XDFxK3L%}^1a 鉰@g&zl377 biIa],1n< A^<2^+h۠NoQ(<5 >GasBAkN46 x:|:FkxSA&1_4/.+/hi hԃV(eȠڀOKIOZsIna]AިN䤜jnk-XնYb|t8z873rFލ[+g[2.f;۳rF3632+xygv|կF[dfifES7f0=y=]!%6ѳC8}$jFw/ yUHY'lZ9sz "LIvfwI2?bl}5Ns&Y6!y̚'qIx=0&s,BIGwf> Ý%zRSƚ\fTu/rr9IzKeVL*q)GkHA[9Sae' k9Z3'B1sU#y j n 2 11A§",rՠRɈ00D<4 x8QCyW]vKznm6[X}b^4۞LmΖ/=w׎2g:yX,Y4 7 l4h3g6X80glz1cv9' #3GqSʋe I]a<(cهW;ӀzQx2TGF";6[_oM-S 8ZV!d 7 S <`y9=;{ zz_:ue1q=ue D*|q3[˨l&08]gc6LiM#Z&2r $JA3+T9*#ldt!G.[fچzK2|%7~+=?34pn*yƖ++/% J?2ThM-2e;Jno]!xN!>MOmgC) J M?R zf7wou3/LZ\:hpv,dKoY8}n*C1b4ESt~삱NOfx=5@%. t s2@ WdL~!MKΕZ8WhaRjEi 6/-{nٜ ]6]deix>n\$)6=6jwimch>CfwXȐm8 ;ƺ-MOL='Q|sbbͧˠ|fo#$հ 涵/#ؕ$..[6Cohm>R`ػ9;wQ 8- Gd@d6j*dNohK 6XRA`t* Ҹ?2?\TrJH-Z L,cE n ]T&Gq# r\`mӣЅ0#.#dP{cepMAƋZ.iMҚKݞ;_te^"&++uq(q!n&8РAfm` wM!ҽɕ}k.\Y["Pu@"/ی8dgW41Po0VLq&1>n1U^;%ٌ},8KE8u܏VK~r=s=|fAк.A+nكYFgju}@4+5<' :oxu7_g+KW/ _rC۞wnyi{]Cֺ9nBYNk<$x72^[:(pTUB/WuFOki,R@Jb$fG$d2,N]:O" G`HX)Y<`-0`!=Ft뤌 jH*]UlR mXQM`%:Ǒ4Y#C\'t +onO3(ltWV b6yF{qO^ʟH303S]^^&_ެFDŽC‹hK[nG C8h28 ҇ tAr9p!縓q~\YTr]4̓Ar ?df8`Y %}o<͉X\L=s$uV2.QA?`Vc|MitN3y-9jTqI}70n/-9e'r||υ\xs>?>yteFpy/7 mWYwJ3HsMR-cєoNƵr6YkJ&4+XЅ9e&$V^a6=LUR$;E,x3,"R|$ (V.!xeM4oQYj2e3s$J5s,ܝl7mX칝ݠ٭q~\-RLd%Wf}4M= xPdE- >dh̅} V )Ya$]Yd>ڇӓXь-=f3O Q+m&L7 LGMɓyԤsmCi( 5I93Ysx0;1Gc%(I axNo[) Ma0ҀJs$韕bGʽ4ɺRw_a=@_2%H rآZnգz;a-KRq-{x@:(t?yoܟx%@w nT^JDG"5byEs-h2ú[/nj:Ģ\t}/S7v6f]fln{І|jYeK:YCH^x l$F%B /'2AlZ1FףwOf]/cEʻkN"oU4>G<$\d-z/1VHE24y?wPic ހR?hd id y]̀Qt =5dp[}5ͦ9Aؤ1U"NV{ |)'ebmeQ}p)Z[\kGmIXD]E{?Lj34j03I2c1\xO5H"b]> x7LCgQ Yn$Y~#+6̜lJQԹaHu% L|ɴ3޹bzǝZx!A-Z ȣ2FjF)`Y8j:@tLp1<jGsZxR?i>:WU(M =&ԆTRӍ30G`dAGsYY\lpwdLe^oU2׉7Yn&>,/M2 )r<djSSA 6_EP""D݌5dҊIM657q'dV@elt{XOuᡎzkF)NWȎSI!n5_b7 ЬUMi1$cc-9-P,38XԴ6Jl0_kŎF~likUU=m C1\L׮ti|-~ӆkn{msv[v6 ܦmtw١_綒.dhyZدk_:EYBV2TJۊ 7]pKw6Ҏso{NJ~`$wHߵrީ ^xE)[CV|ɶT lvIakvf5="`7TAy{"\Ma@PT3'ϑOg_j`K34-c;9`,",?Ѕ&&Y*=3a<2%Q'dɼ9ɘ- [FقR xsaWaw-ɩP*رUxi<~_ol ԪX(}]I"9JMrI)3$Ƒ^pvs݅XPд뾅x!] \ؤ:9|:;jqqN'JSc>#Va. vn= /Ձ;k:^ryexx=^[Tcuqmnuc Pbf 7?vͻ-8ŖX!zEOm~[ru[ =}.M ޣ *8GR-t:HQ!&cBa! ~a]?WN.y]pa % ):po%Y,0> pC(B(!)3|hrA tC" ;'q.`B E@pR3S{E&JLɧїPٯ%`'4aNLӀYpro3 g@Z;#ޑ``k uLpq&.<{{ 1ALQ3Tb?zGأMw 23S5Q] Kq;_qa3ڪ[˭033[BXQM9d\gDI<qՓ3Kk;C;ձȉ`Qǐ w3 Px,H¤]=uxt,|"l _xnj?q$!@0{xZ ԇ2]ƐWI!4BǐDu[Lb$6h«|xuJMY>=8:232x|1E5Gh+د0>O,9ԖH^Կ)nA[Y(eLګfNPGp-'ju,]{omԔ#s7s_r^[_t1شky<w .+G{gf ̀밓\CVH8hEAu!ǘuLFM273f߫ A[4PoBG$AǬ}<ӰfRGK_оIO0gYtc'S{Ԏ$ C 6`Y =J UG>4 -͸YyGn&0-[N}{pBN\&U"_c䩞@^R>D$)c+望Zo3+޸Ft;n|WHWWz.n]QI9g}7f={K?G@N32:(溢U[hP NS=4z%N3{dI#uem6%wDi J˖BZ/q@unQw|a2Ȗ`pt3'ə635?!xƉU-t(}qemp9)E>e{W ݉@g=Jz#r3ٝilq<E +x3zQZ#׳u$RZ/#nfccg(@eFNſ͋A v݀tAm;wFo8 nFw}V:Y |}hXx(,p8w2 D =Nפ)1C1:V;j0(>qȍ3adQ1G:Kơ pzsN&Jh⟚ q +"B\XϏt`} !;kFkLRo;A)w7|5fo4a[֦R)|tqkcoRLr_[^znY8;B 0cZxyg5$@-;81&%`Cj8YӉB:z& _@|vFɹ3q\i]IHJV$Ќ kg%/J&qHlb=1gȼM{!5>9X3[;5̹>=_Gcyw[wah+Jl 08dmm30UF54XO7C>_ pyՂ`Z&w4s,IyE2k+-U)(*Qk9TMU[9P#RXM=c)&egTnЯA!O6ԔrxZ*% Yj-%@h{zz[.B n28*גcM *[Whu3zA >`}[`b @#i\}'cVW.enZ-r sa,~l"Kx̏W]Lt@'0ϰ*Ĕ2d[~7qG輰wͱe}x g?ǯ?ovrC\(`.. .N9"HZv5 _&L˄st7LI4ܖ涕A`VS%e7"f y&J aT´ 5`$ 5OfSSS ƚ/MЛsˡ2c _& ay\k͖͡thz}>ygXTܕPZ)/4כ<Ө15/W_R5U-, Y璜J( 0 ̼b9h +ՠO (,lC+.Gnj +'JGdޓ01;sDxn4Wn4W.Wn?c mG{(D.[1cl' zr`" 3RP&[D~~)ڍHoSQCfE)`<H 0`̲6\2gzxܲ`30' ᘁ6Lno8P8C|Dn*w4x 'Of##9OOř;:kCL m@8Jg'E^"nC?ÔbD!JhߧTr 9~X9 w@K ~'lyR#G/Zց9 `E'zlZR/v\PzΝFzI:-X}jn`09χ&9_>{tݍQ>]{!;|KⲻaLb Zj~)'{i:9UKf...˶]o%j̹G 06 y]+*J$B eeapI|Yuu\/loUZ:&Vv[q?tfl1gBJʤ+!+u}s- f3i yŋnC9҆`.(r9&O\ҦnL%Yo0em=bRYw9l)s[<}A6GFȮxb0Q[I-}[J 1 DK1{38Bb"qKWK"ѷn5ُ!|  Qr\g`@6Lؙ1 JILxyNx8@V]=0 }ޛGU{g_̾Y3$I& I[}HdɎRb][ XBVZ[Z񭯯[+}VEBsΝR}|tss~=<9)op*G̡dEȼ`[*r@H8zԒwƌjCcF!v' H m.oH4@[>H*0/D/Q/ ugd ~qXSn'`%Vi<*(o+Y*zQ ?90uQ×GDl5)pșc"1&3t!f1[nC `cIV]I61JNc,R<*cRs9ʁE@vR:?>0@iѣtݧIjtN)*3! tک(.- [zt}"y'~4}8=vGlkkqYv = v{K/]krd3"{ X)ʥp`3"fA$Lf~§ kV`5ûrHӯ3[rkߗ?ӿH5OʟݻXa!D4= d)Rp[|T#hI)y@6mv}H$0ê> 膗ͮ$2zc5-EWKO hFFn(/"q?(j<9Gʌd7 05?bdL1W-}m- ߠJo b^$su[ Uf!RDz4Krk:GnI auo>qzVu]4̖#la.Rg2*iq {bڋǺJqbl2romBO/3@nA*bbJY]S'U#ݻ*{}Ɖ]rlEHCvh?0\j 0 4hv?4_oebvSDWT>$.ks aG`P)HEI0ARJwF Uـ-!k1t D(#*d:@$F^ S骨~qnbsd7ZV-dԋWH 6V:F|6k)5J?`=KuUӼiU蕼T)hD kF3ͳ%#6L .K a7E]QKԨ7#7v,@6@֐ Xf596 (NSj0 h6 @j|['ҷwl_nZXHe W≿:0msg¹/{qgOR*ڐ =,eI&)YDڂq5(X A/ۑ |dС4 VhzEG? 4l4$o;xwڏ2a|:#GFӂFw/آgWE%xAxzR$4ٴ+2ٓ7vy&rEp[ғffC9 6Y(Otŝ{ЉQ}Q%wB[d]x=-6lIt4{3ޒ4XRP"}rl_ (qU",*ˀz܅N'jL!>hh(-qqVqETVVgb䕔,.M-U66UP2݂NeRNRa"V֐Gɛ9nwgEV)7|3@sR1r8oHƼ<<?-iJdu@YL"B픛Ω9貊({#>Q)NXVNwܖJG],^Yϭk\nSsQ`n3 ΍W ?9=!*2D|9YaDF]VA*X1Mx ށOb%Q?7w1!GȺ z|(PF1r?q ]ȎH^-]a _kha:fn`T! Ơ!%2I,aaX1k&18.GBD3\p,"8h=.7nkZ=)+Oyג6-GUڣRJbŎ^SqGMt(^m)KKՊS:mNסu9ŹɾYT9 cw9Nu2 nX80TC!H`0jA۝()!}ɀ]gitX6X8*Bn p!QE{Ft_w7`Ijd: f8>F8GazřlJX>oIUE|\!;*<i{DhA%#QI$, BnbTcؽNf,n[l{4|MNxtՂ.H =:20_ U@5[)aVsn;ݰݸT`3֣|usvͿϝU4YB_7+pI(z$>hQ@ԒudkPm5:aHxVqF\rx7 ^I^)4ObFc##R ހi` ^ggI8m'es]3ϝ n#3tWRkQ@Ɂ3"᳌C ;o 3^<)ic`уLx>:#|0oNGF(T;f"9e5SY؊ FDQ1b!:iܴ^T;f)u懩o9.pc"O"c6ĘZ1O/EʋqU/ i*Gv!܃XK0])GytQ^`Y<ͨ #9ƚeR"REٚdpCҎQÏLd>zo1_7 X_sϿg f4?08iʚ?sEBڙ]cUW,>:ucX̻O܋}.]/`N=~ }_xoGZ]vuͩYc閅Uu /پl5Ã@])uo< `4_Q(s{=: 9uy'tf hy<{l,Y8=@2.O_w݊1wog$H!oLmՁ Uw[!rP] Tܘfe*W%:] 4g|U *R@قD3a|P%="tWa&!٢ln㍔LFA*ϜsG$ͱGS8+ۙw(5*}h\@|?4 ı@%!]o1Uz+C2(]~1AӞc@g=[yawuloSognN/T;xӮ)Z ~#0) jq+ +q dָPT2R)#z*EM؈:@ ž˫_IPƏ\Q"sJKЁ(i)+]Kq 0b,`]Z.Q0n3q#Ȓ!&*rJ%ۧwqEya)&;ѱ FKԀ;q?f0V 9!G:׫'_M$(;̙?OX01VRo>>erWI#%T=bTvdo;RGzXxd^O' |uEa^B ͷV67 ͛|w5=;~:% _>?>}!|җk݌4|Qf]?DxG~nGD@^#60a@rD[E<C9xc4a'DNN>T{έY0!TYz15rQ u!<2VȆbYf|bQ!IHMX47obm< Vx_ .b#M) ^IXdT'cGfkzl =]3ajGi `H}8ĢU~fm}_%ty~w})q?}4~r-C5D[JiY1bzM/Pu}a\Qܒ@ TA0Prd+JN"kDzɰi iK$vWxnTQU|*MZ@ԗWkjuA5 )ˑ_+>U j͆M?  -xw 84̜:a l6JDwO 9c e! IW/ʛfSCz[(xܒt6u|y0󁤕L1KLTKʵ9uXsT A jyN @$)c4-/>.{)j ɥo.Tf"حݍ`Y.^.18HDxFl8pS &]y_M-e]p?tX}Mq7=x}yݓyjERmU]m7V^w{݊ |BU#qH1Q+d&`1t[5+T -A9-KшAW5Q&e} L%9;AU 1*"-?! rWꜲc)p)d$dB$!C0B%~@Z Ǐi|ǭ6[1#twOOaF\”)'<&,>o#>S8MY鰒Ed)THeQQ@yKkR|Nw2k Z1L27hys9Q4pXrn!05Ј~_^vCx~"r]yE4F}/VH}Er"/V̿d*hz"5^#}CJY[@ r@@3F!lz!,SW2qJ2 uW`HW|p_Kz2gNoE, "E LIJybJoPz\2"Z'%WIZp $*DeBׁ:tJr?Q5b)Y!5@v!5)!Dl60nRc1]&($dN2 BYb&0_Pi"BtDLPwSL=m{g㿦iy|)#@Rh6-+UL ́Ɓ6Bf`I: 'G:NuH!W7F I5!H5'iD8KсfzʩGXr!cTr Ev.͓#bČ1ƫ :X"r_uy\x|7&}-CqNKho˷)}}DŽ ݔ.So~+*?+Jΐ'XL\V{q P")-z~'ADŅqP$Vە峅m1k&MGXUH^-Xy)襞^:{4\ʈ m!| 'tyw0FF] D/|_#JǼ:C$k&<@f_ !WM@ }Rۑ.IϿʬ7~ 0f a%؄!QSSCaN׳7[om{}ȵ=jcSE"La7(;9$nU<,f?hXfn6l70a6!%a}Mo21&Rt`ȝ~ ǴHFT)ND&'9|`Z4(Hd,VR-tk کZ!*,T" Pܦ˅D6Y'@r?|r;מy_/O=Co5\:tCo?ۖYwu/cSmwэzđP&?qFˎ&tA(c""9,cSB2n!D8 9;uLd%dnw9vFV#Lfkas& x~e`T0=!gx7"0wI@4'DApHأblBf&ʳt'}XrTH 7Ӟ,c܏?ŧq?9hFΟH.F>.tl7;r]>^RCsݏ($tRZmı*ʗѾh$K4I KL]_G=%AU>s(P,2TFY| >OP]\"%rlnIŻ|Su#>璮d*,^'H.)P;5w-6;[._W`C=\ <-۶˵Ly;Ob~O'9Ts|-n!W&x*v#Hȏ` >~fn$Z(Xqk>*:1oT'؏ / ֞馷JePWG8fQ,`El!ggrxd2.ׅfQDua3ȲJGjzRr\{״;^Y?)oVܖsS=c/_XD]l|cEOx$T; )[Yˆ:ƒ ڬ#G_!&sǪx.S+nn ?f4gfCjOe#`IdV/r5O eC3#t*Ć$ڗۂtGɊoqxɊx|첂5c9wB4t*lhx"BLnKȦ1^B ?E_]aW&o,؎xc AONyT,)N$u-hvwrkj 6Vs7ZJ(h*zvEx-CR!Sxߋy/n m%Q <&c@ X _LO|H]];#WV9ӐYTar}TQgka"Kz֋.%ܹ."".2DJSx W*Y޸tʂ@n͋~<{X֍dd'\8gz׏n\rֶ7NP t'Ewt 97QaJsm☲B9C"HvQ(&T3[]'{u7[gUVevgc,0A3|w.|8z‡;`4/WKU%:fTsQF'k5UON|LZIU%Bw&w$\/A#þA:E 2B# lq&]>%pYY+#d_rT0Y;k݁1j@Lk"{FG#aSTuڀG1]H=H.eQFv!p;fF :>[g>L5.vzgln?f(TdG"]#Onk"pȁ_uO=ڠP1Haz-m6 6i^<Fߌofz=FxopaR7|{N4+?x[ɿ_7 s̕tVT$\5`+9sDA2B6oIL^$28 Sx#2ܜtNT&ECŨ{x+E1F#HmB e`cR,v,eKմ i;iRmVwJ]붒#؉z¡'*IdFOhNĉ K8sIˋ|&d/P" }`R(NEd:tY#1 5,1r4/A<) \rW<(b7m%)<%T~OŎ;.x4/tm-M _ӷ~8΅z[ȺrK"REdu\#pϳ3 atc.Kc+JGL=X;It )RP0I8̎ /Ѩ8e. 8|NޅI"^O"ܘ>7zXXdիU)2{{=]y{GG#CyG,nlQtK_Ɡ5OE8CECJɹue*-/BT 3 TeeF5 a*^wY\F\H2HѲh^2"DQasX>⍤ K:<ދbzQPb DWE_Eb`ef/` n/RD&JJUVѷ/eUErBxޅyܽLNLa?,*=Si6MI ɂp2PAԩ{}x;3}~fjd}+[?_R:'CSRc >W("Q!-2b6QAYǹzlUaFYB9΀8#ꆱG2Y*Y;5~)o ԨJcx65^.4D'U{AnY^)i,AW%ݑ ST@U'~"tU)eN<_D݄!֍!S()p0{Α5;a|{xy? eii]@\%jP͜hWx` I֭4T[ IA*ip0V e7Fecuk=n- GX96 in7PBHcp6raLǜͲX`I? % W 03kxA=Y282"#~LT#/̏SWlPC@-u)d7UY={`H1?CUA?bhY 4%"5`&\._qNи a!0g?4s4 p,pC)as<3GujzPC<~s A߃݆"Ŗ%jzPC| $^B$@+vD9gSNQ}_EI^e,Vľ~k7^ cGkO#7?{+y[_&; WzИԘ-e*tvlw%;qRU$W?<᥉'ԔO4)O_rZ 2}~SV0#I~S=GXʈ,Fe`:+G3[ ЯqSV$S5N l`2/ŷ20Fb(3Hx1\Jd2镦 D&7 eAV# Ad`< 9cq&-$QXIQXE[)p5 wd`hC_20Ɛ 5 m'Cjf`hCO20 mІ?d`hC];F:R7#Oa=SH8R7 yY4 [i>-Q ;i v8xi'( iΥ'_'Z ezc #ueEmܙӋ֡N3&v3RVf HߋzS+{- \_M}Et 7X 9+P i.H-^F()'SO@ `@JW]N%́rn!`zi.8;?RW!9S;< BziLk7Asim]Ehh5^4v&o%hy xw]N ͿT5M[!\j7m1ķ{3Aۍ J9JA^DDJ=Sn{i{NiRvMkUg@ߗv_I{2((hY-`s3Gb2I{K_M.=CK&/;hkzH{Ke}SS[r.}nڗ^r|__+ڀ.>Hu@߼o~S֪Qq hJR۵z b(,綵3;tl'vtwvt7w)Wghgvm]V0}ukxM kf[=`1^1K %blf{KwGOޜ_A 3sybow+ŎU5b/[ۺL 95Y; [l[S27Xoh_B[U'Yպvt緷B3 SE:ľVy4ݫ{Iݖ57BCgwDz^7]5-AqY{O*(^ RChkVc9b$֌*;w@SFxƙڡդۡe7Y|ePf.}ξ^qYZҸuUUo ZЂ е1GF'ene?f9e 7o$HȿHf0yjư-Wrs>p.Z k 5`(6 'ᙸ3ϏK·fgd܇ CL3k>7`{}~"$z#m*Zp]-\!ٝE;ICӯ[-\29N>{ ĹqnVI=P^܀->)RO9Io{ 䉽1j~0Nb\*:.CNoO-3.O! VVt3eS fm 'Gkj5 vA%'aqիtI3*!Bitj5,HznfY7cYټVUeEjnn@wBwRPPTRU*dyzeSٯܢܡԐ z&]_ :QIz¯;`%N0[pƍ/ׁiMq=:tF-|F'_|yԃiә U^ MC%!`!x 3 d':\ ``5`%3M`4,3&9D -9X.`Pc1ָuD:b;P].V UEbU;D(I;9;38>\㌎fc`>sxGqwP%`$S~+<$ێQ'2[ >6J݊h6s`*p6FHW fHaHMfdJ 9wf0D CdZ>C їƒBnz0t+|6rcgs~׏7#4<O !x0@#@\[&DCw >|cG#<59R x>/Q?92!X$hn9x iC{aR$?JhU9ʔ TY*_%**KmV jZ֪jS3j"qQX`Ȼ V30v,l-S;g0עڹkUgk.<s0FsB%^wD {,ZkNڥs=(BȶQeo*<}1Gʟ;P휅?.("Eځ)s% 0]LGͤ#L'q-S%VO04woO> endobj 21 0 obj <> endobj 26 0 obj <>stream x]M0 x0"lr臚 d6rȿyi+` 't{~N=ٜ 6C_!b7}fJҘ{|f= qNҟq6ytI.ߏiW?&Kt|moէ|v??cʿ軥J;v65p:j{nŔӹhB 14\UGʕ; \5R u ^s5_o/rz:6cӿw-+Z;sYiCjsYW%2-W0eo KfQG_S_"WOY!59"w,ߩEcB\c3Cs?w:lTap/i!Qˣ3~M,5 endstream endobj 28 0 obj <>stream xܼ{`?~Ι3-I6 d $\Q rKD(ZAֻ%B@[jk+[ik6j[D[9Ķ̙͞33眳W _& eeK%a۲WIm>.r˖2bvWq5~ U+.ex:ZVk뵐_ˮfu_.bYZ1 +/[z4&uxK/[^eNɣ>Jt-(|^F 4ςzߋQ>xk9*yeL< k~\t?S~HL!dN7䟌XI0=̭Vf&Y'3ؙbv{; -%q=čri mv6紿Vt >xj|&G .!~Kp+|Otqk:Ϫ|$+Q{|3 Ox.ΡG XԔ5~|! Ohh0>AZ9}< $S;ݪG1@WQ=jA{@@#G8ku f(8P ؂'~T={]2O[>cЂ3`"XiPTuK@6,(1A$r kh}2=ƑCPSG28Ss{.< Wka(SϝtuNho+Njm.456sL:KcQ9P0y=naYb6 zc5 (#.FKF4qyƌ,K3,PˌHKbҗK*Pr忔T%% ulFꑥMQ謅4y@S}jz6C: Ϫi^"]g4[^af3#$q˃`w'V A:3<ԈO3╧'ab=Kg?fFp2$OjԭfѪ.onzǨ.Z6-/`tÚNq_{E*u/rY?sD[nFv̳a @p-. q9܍ݲ><bDZ&,&`$u$(Y=rɈ{V׏1A~@䱿|.&|hiQtz$">gTQ<(HA~hۥm9hpv 2#ZXK"^#d =sd\zfęӗ/A9gUv/WT>G}֢R%=KIR#RϨgA(/8]fF41T^>ՁTG;",Q  NjF+ӫj9ҖrK/=i+mjҹ^@[R%[V6]$K%=:Z9|xU ֳQ,ZxHz{ &K<s ItգQhH^SO)mRjj~(F111A=tw_xfb "Ap,zE1h~_洣N#V2 Zyu2aS I ';; ':;PS4ְ5h)9rJaH1.^ Q&fI^#*nn}5yk C+ ZJ7$ViWE1ՌrSiroRImIޒbL=}KY=8ZsA=Z98F7tG ZDbAOC"D㉰i#`%N5&l]I=׽D~ol`>7VB]c]cbV,b"/'ư0vb,KimȣG׽^iRoD4pژIǐ%,LŒ \ r9x<l !t@"TCPq:Uas55Z ,7VN#G^f.{s|~0}WOL.o,i狕[{q2yy卧?~:ןb*~Sk>nvrU3%e@nVrَ * z6fx p֟0ovl,,`V2WD1#71X>0ԧtitѕXɅpkZvz@c˥x#% ˃xBrR6#_.Y(HIZʫqVR8*`t' C퀾Bd:=K;TY8CJC y5y^761|Wv;!@iׂ!]vվrs+ؚ]~u2~QҎ&E挮?fwC9 vK>ϵ7vЃ׈]24EwC3>Ų͸DhBQ|@b8o"ybd#aFE &k4gE)˹+&G6r#v=w:@hɾ1Ȣ%ajnrVWh`hҊG;>.Li u`:گ<3U3^~r g\GM^YO%1 Z7+ȆU msT҄쉊HFRa)٬=}Ez$M&,9q+P=UC{ۚiO jlw #n͍[puJ?DВHH uAHX =44|TNtugL@\&KF@ -vh83 ьS(Q]vSKr~}ܿU{@22 ܮFphA;päY̲,__0?1N%/;~b-7ӢE = ȏ(H<*!*Zd9>Zqʉ$]2/w<,4c4gXr:JxJC;xAJCnv(6H4@o QA$ AH@v^̎dI.;%Y>A5z8S_656Ase<%qzqGG =ƹ5Vh!̽A#Й0Lh7kȁ pXWG|k!fKG.:$%7rx5gw!@!S ;&n4k0(4vje٢ߤCHtn-h.'ڽD'W.>TڥhT.û_W6^2 pR".b{߀d4|W&Ek䳚CG?f04雹".eu1O,OSe6*3SG@L6LsN >PةN*!SwbzzSe4&li2sQ#Q&@7i{A~jjl]4V ht tE c`VB1h>*IA2k1Z X4;e*QE8ڨRMFUYk5aSZ3 7qfqɭyCν]s፫c#YmZ:+ŵߺZڗs۞֓zso74U{n7;7%_bs9֐ژ *g~2ڀ:, BQ# FcZ{Uʖn>P_>}`{ٷÿ̾nV߬βrl^^j~t&-yo4hPg2גaH XAJ#Nӻ.~Ԏtz6KgbL驦M䦾 M_mz鿚nk26v.[Z!vݵtսNou:a1s\LCɕq'*rģ$#z{R?uv¿z_}{=w/*Nm{`_uϪ'?m$O*NK#!p{%| C&XjВbW7[qldMYtg8ŨQD1EZ(=@!G>m4(݇}xrfci-mUҤ؜|-0ay:K܌uƠQ:tF `O:YW.5W\]rD>*$+&= 6bS{V7B`Th'`U -zj1TcABs؝vqq7vm!j$pN}F}IT:ӽPiE<֛Ani0m3m6y_# A~gvyͣ XzX[U6wS{Z# qڟ^_u7f-P|_yooy׽:NO֔F&/'7A'sPC9{zDn.$Gxsތ"(#l{,/X#;*#hVyger]T2åȳR٠SYNIK;0M;K;%v+R)S^iNyv۱dN;S(c;Rz2k첾*k6GUK& KIM0LȥC%$3\Hx_*㣸ebv=Jqn^ [ۉ\k5j0y堛̼p,s]>Y+U=c[RLT-zOڲAxEMhʃ1,)բuQh*\mެjf+CTMݮD3;mkה97_\C.֪C!!DbV<$^F!Nu&(Ŋ׽>wh*DDB=' a2ʗk\ȩ` sGsd0;Gr"zp^P~ᨠW%2JazS)t>;?)Aj9")=c,:J!Y TASp 9`j$w$ Z6ֺ7ʃ벷f3t`PVB  CmpmWɌxٮb e'~EUW={Uޔe֚'+du s>2GX҄sѵ)ط o!~g۷xO(DA tD ب?x 0" "+QS8*WH(.XߔH}ƪW5قjƊlwFx#`$DnۈٺllGllo(h| [__Us;ݺkQ.l%qB1Ƣa  @iجfa^Pc%p4aܞ&FHXCR !!aFKj3'fb6cP64JW]ዒ\(hKp/FvQ1-bDI-;%G#*BA+p$#E:5#4 lcU: i:hh˂9y:MD9ApwUi'ï2{J}\%֚ri!g]{gMpHҫ9(L4%l+EEPx"dm f́vVJ$TKYwֆ .~buE-rWVLY\~xF{ :r )wlE|_[ MGˌ1A/{V[2Q~m:FfY"վUG-u:$4׋zsk/6ݗ{= sfS™"D@WjDvёr&iK.:|>l4_-p絘MHLP&1!Z|ȸX *_fG]Μ8G1dRJN#(KXo x:^;1^:.nz4 cbnewV,K ϹMPV't:-Q4ehk6殒OMmk}Y{C'.ַM+g9O/4NX=8y3,O||7leH,N~f|vTmsL=4jC۔:|f=h1HAmN(9*GE(&cbcllR)\GlV~G;o"^v]h S$=RPDR|BAQCxT=)%. ,y==GD,Fqc@—/v5f'jk cA¸G @*E6oG"5EW۲mYuT\t[y8;|+¶?b@h\mgWP֒q>Km:uxQ]͌6J2эE%6?ۃOM|RI'##ImbAWojS&`\r_l~1}hZ4Y+R,$p  x̡ B/ͤj(@Gu5u7FFFqcl3ʩl=ìS5,gZj6Y{ǒ'\MswEt7lhIL]v| #g~d>co Rn6R6ĸy򞢗cF 4/$ce_h#*f#si+agc~h_m!9]N/Y%/M2} hL~0iWJ8#V,a&jͦk̽ؠN%-ѯ:?l KEKڬ &m*fg>n>i֛/$`ƌ&X0kԏ2ql4 `#66p6z܋ 0r`bz|\mL-QDQBDkHljzTꍱųO# `*|_ #pꑰSy}꧄X:{(GL\|o_^ő^ 9o Yqf4b2$0Fs\9w^.-]Ʈ4,7qq/ 6w_Z/]V_m=g'Ɠ3q9F`R_^1nG(ЋM'ub:߫щz]'D7z1V2D\\"dzE!'tlH\hV^ 6A>0F Y\"R:!%1D79h!{=a2Sƛg['UݰRx}z*މ %qg(KVb}gv'<9}{d*8s u>/fgT5tb▾. S4ND//-( ȥQ]9EQt÷JX 4X%h9^A1)mom l3)|/ފ-?gGw$@=2.cnl%Xk~K%^9| mȆ5Ѐ{:h:8 57hob5ϵcոU4Ys N5ZMYͣzy#t!o%`H %Cj:w~`6&D2?yd+p: +ݘP&fp?}} D!<!<A/Rŝ(d{t3v.tTNG JTC)}}X"n4P=(F5]x»&EɝY=_ל-̤I7>},fub+܍-bײyG|O8i^<0xZ~mrY+y>;3 ;|Y9WU/`@(dX htܢ+rDOVbTe9ea_$Gސ <a0xs(M./zh( NDix'Ql<?4~Kakc fA\ eᩔ*B0\><$Aa43*Vw>>~ĉ TLNf^uj]ebZEO:}}?3*t #][r]Չwu-u\ӋgE7Hğ)m~"M}D폫gLƃ5+z ڧoثQy\xya~xHW 2ʂ:7d<7=0=4,!+p|b:ȭGs]?sәFV\D6CB)m9G~OY9Fv(7EGND!9aFpĝ Dx?ePln#Os:)1oKww8phLR_Wtf%q1o.qr8 ؠhdl1^\i%_uu.s$u 1q; zㅴl]2RnѠ|^uÝ©d*YGw VA7"CS ;@Z)C -T[dM}t}cUt_x*FJQmUzΠ3RsUa-~Ij擒6EU|p0%PuJCCZjuJ$Uէ0^ppyHXy̙kOoE 9=O[&cĘh̴էvhyH"nrB ڰӽv+g@2G| _1CnVτPtw4a 1a23ʃ!C"jS#Hz(GFfCޮ{Nq!(})L& DDrHJH y6-3J&A6&T՘د*"q%fr0̲Ñ>a!>uw|T jpt*¨\:OvoW\/̞(αN5K}pUosyw,V_/3>מ ]]`kBy8`MT VoGW;r#٨X ;HufJIHTLJ;nVRԨ+H%ŚPGJMhѢ^.fPgPf12 y§|^erN]=}Qۼ~8©z*B,deI}ķ55u5*),rl =edʑ)LS)3F9tIc W5kq\juE0&w*`"BU*hf|<o`C6s\cM [((.`E}8NUCɁf5Lʌ_y<\<$2g'ohi]Tzgj znO Nqq])$W7$xtIwX,Gs?1G PU7wLJAϩw/@@uѝihиh7ZrsSs 1#:e9&'~!-UW[[[7kra21 `lez]rE=G.ڛ`Na(<pc:-da!NL꟡:]\&ߔCȂpԚ5=RY\LO$ ~mP-|S]}6E2߉D]F2КntȂ 7#~@d -S-P\~ G\(^XT^,~ ~0w B:=@n#SpVCgTmEupJY蝈]>q%z,AjigW}ԆL,V׻pa.,ESN'|y !/]bHki3L}'vz=oY}'p~4^гti|.KjUSxICP.(J6L݈_,RGET6+!X(uW:fyb41hC@ ^I x0|$La%8)<NT_EYӱ H||b?Tid1F^Qf'ڈn3_?,1.#;,_oxӡNp ǭRm-jk1!Qu m9֛ޮn!Y Ds.hmW>7%kyμm9Cgsɀl?Y{"3tX MX>ᥠ#"c!&|C BBzG>;Nu:IDyi |BL}aYGN_BB=WŪon >Du S^ ˭p7u&WAPuV:]VEdim}=QpLβ)PQ/l.Qlϒx&/F2d~)JY1-w"VqժuVj̶^|l|^͵[fL|7nm2cɛY_ 9.ߦ\hѕx>[YZoy%lCg+V~ %O5%d7dflD7,{< L&s~m`vOT}'K J;m-VIs Ȼ;?ovWk*J^`,΋㝞?".IRےڦPP} mD0nkښ㸿6SMsӆXyLM9ڲ~?1!V),Qv?%;9, 8A^$w(W[ПTZ"Is.凷/Sz7[C>G<vޡ;zOJMf^#F߶۵E\`F{Mܣiãye2GBypN2! 3 ̄a @ $$3d0dBfZzUkW`+T{>>Z@+B_[ǁ{Zk9{2sΔe}-0G 4SLw0]ҧ ial;Oo=IXc}mf߻0GiMزLw 8p^О?Un ?xUNvDמ{U8%;;o 8h1w4w/lz蜾4>F+F쳍;tM<5<}Mydi홱';rp}?lkyaCcizGB [I̤XoR&/Ϧك{fϚ?%>|+)w>pV?y17dO98xU{SV K).]_r=cW7ι䕛o}+x3gZU+.rK!ȩs5PU嵓$ݲ\;rwe;#;W4Nc.͘5C29P/+(f‡6lKLhxK;)j5{g2efd5D~ NYK7^z:GvA}_X\2g2#WIސ)Ѷ >D ߺv>b}gkjJE" &Kl$fZt=a[ 56*1>1 4S9Ӧ}3&Z`D;yR$%Y|;aKzrel佋aXᓰ>M[ٴ?@YY-ǵ]w]fzV3Ghaa&t!فӿgwͳVܳϞ:XH'34XK dx+Y%XM9ګfR%,e eOxsbk _eJ1]d.d9]HQe4鲑KuD6tLe Yf<62T+|.Ky.leD۸lD|P%lby3A+I.ўio)沅e.Î巺[~˰q.#[uϸItޠȿ5f-3س~{+\eDv-L}.#\b+~.K~;3hf9tt9tظqT2q<9.KdCO2˳8m]ީbg82"3ˈ9eeKٜ:2,?..#?.-̎k.2OȈߵ|\/2kə.qϋ]]f~? g*[ 0xH?(Av޲ AȬB{#* _M q^ /Imo Y bh낥FH}}s4u81q=6kJgZA#؟-Zɾd-)2i}E 5hcaVEEQc džuhnZAbdZW+>u$ŵMHMǺ UkӖODڏ\K:3ƬGУ_2ȢVEV?#Qx^!Bhp V9³v.oys9mFVWN /ܢ6۹nF6kn[GF(.WGl,MZAU=R.D[+G7)"hwX)gF;u0Wv'`~E`O?\T[ )[v(C0A4]qe <%`m#m|EA^ u'(F{w k"D}pWnBQ9PWpi~;ne{+X[ЅL{ZziLZ(-REIO3 2G/BtId ? `uL)X$nh3[ħ=Y~#T;89[L!x)i)3g\͞OK֔GA" Q66D^R\iAklڪOx>;.R39flau:HN 5sC̊᭩fW\`) (=: 2"z'΂$^c]@@[3WۃȠJH:_KfdLVK*ˆj摊)_g+B!IDFUs p&c S A"ՍJy*FgR*rAʞH+H IlA] G#TF))8EA"9zRٹU;V-Є#_F<ED_ 3"?'6wSv^q.ֲ=Zld~q'),ϋfqH\Jר~Y֣fbe:fאN5SI%$#L1a1RW* 0ɅP^!+-?" Nlį*50MoajԼ(¢%IhNS)%Kd;Td J= rGѺ3eb]9:v7'@1k|(#7%6x-Cx=nPoϦ2]6Tc(oIa mdH5eujV|HCguF s Ղ1ScdT+cb *q##*첎+SUcJ֚Y\XZu,d4qO5gf~HTLuZG[>Yaaj8O9\X=O:A@I9jP+8o 1܅,H.$A8ՈmAA ^(G@,V@uA@ 9,x 0( :&ib|1}%*3!2% ]d%wI @͜fVTƒbQe2<E[5jMXw7BE#p81)HSjOՋCBBǎ:*.?V~\ =Bi%mADI+:`ZêXk:n=b5$#FC8`6` eKHI@Qp5(wV.QV1 䆭7rpFY S /mH^ TJGR*Rz)=^*$KK`D|~v!܈y{9ytN\ leU"{pp,n@tL`VlxMf.a_j|\}jΑ`%BRvaL}0j2Ύ {&ݫ.X]"\EY({# BK('Iǹ4D \Q+ o/$p-Z X=iv'ըKN XJc*clADmma^7xy^VonOܶܶ`p)Ei^́R۟Jm?-[j6F_#%PKl(D%%%%̔[@PJz5/ 3_;C)ӿzj坼ybM^`+Z`{R^ډeL&fz9-Z8B5+PCk X]Z;fE訙Lջj̪j0ꭕuC]{O,{$" ?2i5w(sЬb`^GWTo)5vF*V#}[ۧz|qWCPoUC€kgj uX4B<.5erpzR-N ,gF6ڬ.c{>HG^-2w-V=1]zFy" fȭzFfAe^]T P#j%n1}? U{ mW򛡍o1@C X嗽'"%/@ _x+gviϘ ͖GX2Q~$-<\n|'-P>Y!,K /-a)$B7qo&{Y#%Gti=W\Y#/GP40"XuG׊1#T/_0 *LL;MMmF\o昊MM9fa2g3f,31!݄c_1:3J=obв1ג6]\kI[lk%9MZ6*ֺ4cH֘pRJ4=z)HfskAr55ꝵ˛.Ptes͡G\ѷsCݐ8wG@#CGBǁ=zx"=8NŇ||>~i4Ĺ"λ`77)5'0|bїފSa* endstream endobj 27 0 obj <> endobj 25 0 obj <> endobj 30 0 obj <>stream x]M0 x"|l]5@I@9߯yx<6t{~Nlywu.X1]qLIj쮇5< endstream endobj 32 0 obj <>stream x}y|TEpU{wv}{M'=@@B@*n 38JTPq{.802jԙo$ߩNHPKsN-nsN5ZmDZ8PB6/Sߪ| nXھliV"xK dK^gBhb9TD 廡ZY(/]ٶxgC9J˫^~B6(Kj羀rBBQ{Ι(DhI޾{&VCQ PI˄JZ YFdVt=^CHN47/_PXT\??-B^R'2+TgfLҙxJ:GKha$##Կ8@T;hVoPGoP>Ȅ6$ݏ "pW%z m_p1 _ `vdG`ļ{I6fWTqJj-"JmIHݍ).TIj5 -@e0^:n$090 I*_h݁g1>clQVɣE64ME h#f0%sc{<3хbt)ڊ]=或$ǐFs"vtq2?J..yd N`ؿ>vMoS;q |?ŏ/qܕwS]nATs/p>$1a^ߟLƧ6^H(Fcᝧ0Wz}>E,qX̀  |.^x  WIVn6^gKZoԮTWk:@3jGks^@;x`dx;`4\A%)n4{ww$&W%HMSS8@N(Yƾ y?z(]5v`/.yx^6܎;2x>_(#Ud;GwI/s9\w)ǽ} |>_OKH)mNO_W-g&?NRS %*96e0E7?9~>C_`>F:l܈g9^߈wn>ůWq 0B$`"K"H>ߑ"\>WʕqxMu>s%%X]CqT lȈCyVp䏤 _N~l,<-kHI Bjү t r')fN:a!2\KЏdpڅd'o{h<բZ\ { u tD;XE Mg hP>@l ,>' 8* dԭDJrR q nws% V[Q,*l#&D"3mTI!tڂ7|܏ͨpϜvG >݇p# ag =dAF̂"5HAAL;|YZ_MC-ZRoO~re M#HQ@P5p2(̽ŝJ5qt `gH-& )2OCnxʎ؏܏p(%᎔O?_p~Aj^nAw@<z+x`sȞVŨU1 &B[:K {z 5q>ܷ.EW߄np;yur!iE87sQMx!<-AaߧL}1w0[cЗ:E +ڙrMĨ#e%EhN$ ϛq*Z&eJ9Q`+'L(BX8bAUtI X7ixOz.=)ĂT qAAϝ>>Oa6icX /u_|ca=:m]E[hu`{5f &  3.j>gXX߅u.cuAu1]ʺ.{Jm|C-,7[Hasv/u)9-n޼I9}V?Ma lGH|O#4#%&Gk41W,Ҹ6w.| uIg jƅc={DyNYro)#҈ݓezPeA;&,3 NK09AxJT͋+5bk PKS`0a!(m^0S ! R>d5hb<":)̱ /!vA Ї G~J-=2ZZFrQ,-GZhƁ1ڥ 3 6˸壺_4'<}iN9nl@]9d X+0δ0GŇ្1`KX0!6jM=o],;s[f]bˉaao`|L9wfz@7 6/Im\`D6[0@ў-%QM#Ν;u3tiS`L9 !$Z2XKK-8,G> #򬂕`u-!:_حerH(U=XĵJOpqiT i:bSSUS WMPMUJL~So/i;rZVNXQceP$ E <6Z8NxUI}/k$?H<0aF7*O(ҕU&9љ{M j3t*^yHE,|w(Z()FN 3Whp84>z^tI'S'r*^UW*Pl| .Ui,EY/Uԭ#j/]={:?l:!ړbV w4bSEY)(ȱxEY)x$PZE[YiλLc/x].oGM{?֋;fvX̎/?pH`k诲6Kj:KKN} \"N⹀&Pr-eab&!K4@,l3=2JȮ䈤kq30jkmZNrښXtxLS2'JL d0a@$^gi Ik/Hbwgkb׸@4%BI1Hӊ6_VQI[νۿT-GdM2hrNח'P%.XCs &e|4 zەXTcsA/ xփqp´ǴP4b,O L71KІ Sjk?&܁UߪUD)d 67w *mNOS`>U@hܑ#/% ǀow(L{#@7["olf u#j`f͉PU Y{>6Ak6aڨaC_5`eT9~Z.@!B!6((Gkw$|^QIFj_ &I}LA7:~a<@ ϫ>JZOZRj!D-d-p%CzY0[x"lM>dV>2J%Cԧ5>FB2bDMRcę}Ks{aw7izus/]n7XT+;V3`-b4˟W_T5h9 ezmVN0צBehe)4חJJ|D>lѨJ\'I*B~d[bR d_4cB^(/[t{jZ&JnuS13p_ŴOGsssZ_mg iQϞsSiMхYժs/Oɯi:q64ǹD&\um|]J'o:c'KrNN  Yslc))9ܜN'wyElg 4{Br VWk6 ;>}q,n4 ΤCי*^^e%ԍ;}x\?Bߝl"qƎANcw)ȩ?m_GRs@v4iԈp6*DLt*Ryҫ/-n͙w)SKz7q3Տ&fYy+O/ތMw%T>WOm3k2cL^sDkWyt<4ҢЉ/[S{^yD͇9|Dvrܥ;G9(,v:=旳fcVrm06VOUisx;BDXX*GYט=-{~v[6,tR \5rU2CõR|`ѧ uY`e.ݰtR26̇6G76ĕ;|;6ΦyOL*m%(ɒ7{so]rsݿ5I?Va6nwQƸQMQ?HN{R/z^'HO#m;z {cyV^GF N 7S\OeP7KmVStP^nI9g$ޠ::b&.b A4OF-X{vVnpzyɋ.>qôi_ Qq%\ WM/-".q3У ֒lmƽ_ѽCU[խR~ *Qoa9x4 .Tz&a)e.1IJבؠVof`<`؄/Yr%^y>ysdM5"MLTM[Z &PmըD!&{Hhi-iB8 Pl_#+j(}M~GÖY#olJ~9q'Om.9w&a\2Nlq[B rW,.Z\v܎r7ݓ{sٮܝeYDM"4 Z +qH]^◴1?QD8-aI4;5]pikvkiWy?J ǂ'3p=&hTޟogC $uNuK{R?t{E4՗[QO!i,4"ơ$t򸹬tLByS*ۤrly{gӫ7ov㍯b1GY眉y`?Fɷ[_4X)6 ݃6ygSvp.vv:v,a( JfB<۬ր{[( z-‡| :MĢ3{hAO[5 " a [~1 cXi/(cFۀ?%8 ~{9.7M3-i,-rŖY 0=m9x1^ *5J-Z  E&A0&;;^б@0V]lu%XEV ŝi5%*؎E]\Ҽ]S yT D` ,!Xf ¥`6a* $n1r -Ih5P"$e!o{Ozr^WABPHvIŷ Yw#z;O;kUT5z0\8́ԃ oE*R@Q8J-T)Ot4q@gV nPpOV&/qz]SZl/PH)~Nb~>xX#x$OG'{ jCo5N5 R/Y38CY.Vsi|H֧#O `C3>-F2>0>_f r]ǬGaѱqlڑ `Eei2S6#EډQCcџe縧5]dֶuwNk;wڿ`t[=ȍ19wt0*DGdx:JTUM:pKN0qmg3͚ޏ NQkBwWPi. :cQPDqq},(òLL60,i}sPaGhyH8C!<.AsX$ "KyHTqF(#DAA~>%&N`馋 ]Ьx' C=1Nxq|AR Bf55DHbŮ8+{ƴaߗݤUZߤ|G}F#+ : Ԥf)eVVP ۵U)31>bPf})8X0oȄ#mҚ1KrjZJErC" ZIϨٜ4w45B h0<޸r썃WP3rK&wT?%Pd rGWYt4WYuI;d?vS ;u#+\/lP3kU=H٬[!??CKXDJ~hތ? WOOG0PbQS+yX4JhM ʢHYFSEm>2[#C.P'sGba|gE(J3uf}J".W bC]&0"dK'8&g{亥kbq{}I09h3U_W{m".+/ݳp !gfϯKc<طRdt"}v܀o#f2X5$j9K<{o\1+@VO.Ձ!~:MA 03Q)=0}EԠ'Xjpxr${OFH M\Hzd<Ih6)2Eơ9S&De7 n30㽓qϤN"?l_wљ=g^D$22LRƛ$ *Ωֱ'*u?DjU2Ӡ!Ur޵M>Ԑ[Er,T\}+x5A{C;7bty _y۵`P_޹*C)_O*ceH(cqQ6i}~;,R-t:= UgjgJQ12'FQգFHgz%3n0i>i#`͜~B32*eZr5"7RX(*o@л)\m*[ۭoZy|Z| ?(UUL[/X HiBcy1'ϓC1wn,#ۘf"̜~4}ћ.Tfuj˯_Pm?VmSf;g_9n@$v+ඊMr \5"1V[ 94{.Q= A0 $$x< <(2^' >m2d&O_{ >mx}䟩4%gy޻{Xu6g&Jlqӝg_`^en=0-Zsj@w8Tu&s]PA,HTĸ4^g6%\$ƄZ?!%\%D(LI%DB s sj\OF7 oȆml|QѦ ׸Rh+i4fe(cZ6OP~z!T)@B)^ȳfpl^WL@܅B_K2 shǜ @(=W{ALw!K:dv2{P|YNA ^GwیM1^֓9t܆M,1Wg^˓0imjdk0MFxL5&u#@ԉNc _-UǍNw@=RAn`+T  d9\j;+24l1T@ET/oȑʈhtL%y ym$񺌖2H 'tf v%t3/$2󅅐F&_J/&M;5ko)&h0ʎS%uL|iɺNU2dkNIԙq=Rr,-D䝂o"&cDS2W8|y+e1PAFmffZNXc&lv_( QE4Gy e =$ƚB#7Ӏx$S6 xP@OB[7v |=$[6d DpVNZ9$HsGos/R /m#E@wLh@#,jɐx>Du١rnĥ3P&IOuAQ<^$+A?"w Vws7(Rݻڒ}z'_%3J.{_y?S>ae;@*:_SHK<;ݫ?w?~4:+[ƽCǧ*X6,T rQAA13irlSnG48[ :"fml"a^{1nIh-\NW]44[5 pG]0ОܜbM-#c%Ng"Ȋ r_=%`e齸j\uئcE/]LJ\c4 ֜XS(\[vv3; HNmfzRR+I%(>"$9$pXr`Nڠ'wb=IÌYy4cn&I] ɒ0(z$ ze;wbLN)Gcq΁$ w&Q=a[dmKF Gh?" 8ϋv1:f{+GY1.cX#g9 Fsl|b 4p!'F}62*Sov9ҍ>QS5pO}<5=OvȜO5ׇ;:~ZJf 9۾?vy6#^Wq\71 -2:z?K5g4ලKzT+E1!'+rS}z? U".B5`q vE\bk-lIIMx,Җ%w}WfQ*$Y3GplǓ;D\c/qܴ~fE2_RA3_Tw|m~<掳OdTJBg)3Nu޺<92&ǃ}+w5f[lMO8k>&c*JϘͬm ίϷLyA]7u:~4?P\lR MCjcImo2MM*x1Ip#2딩\^x74078@8"郠g/9=2ϧ>G]YcYiiiThbhRr Rn_[yWyrUu,e=UdPDO7zUu+{]z+E#ܴW=ƽeY7VGW`>y۹?t ѳ_=v֭8̈́]i5uQ#`-G"uC'z{ ](Rbӌ.:co_oX&=E)냿tMgr8&O*Nӂp=#|(WO+>NnAoUDhOM"`qKJ))#ϣo;P %yu%*#hBĚpxUUMUz-jsyڲ]k?8"~n,Xc 6.d; nFsVb'Z{#nͱ_gCH d `$ H{5AAmcVHv .j#3=Ѥ4 6fzdgLd1{~6.gG6XY;h}xX!և7c}DP:"@ xuN$1(#=|`HoK9ωrscB"1Ώnn=&"Ff[mhFl=x>"GIY߭ODy\ [}">"b1"/vL☂"qxeqkHf"Q1`Tt4S!|}t t&ONwD`'FⲻlLprkPAPDmI}& Fs 7<4޻^}Jjcm+t,VQ F0E|=f'JָewkHw,~F&F5fnzFA-$\Z$Rhz'+A-32iҎ<7 Q_o3~/'߲R xن%aCJ~-jN5 PkҿĽ3yҭ)x!Pszqt@AÏs@?~`$ڜk.C}yYk+WC T>2H*ׄ_GV||3;opz73@KTBM%h'$s(K5&<4 o RTvm1q*5zdo=#M&?8OOn[>-wW4;TZPnpmgoϭSxr/uUߩAOҿXz&N M7}wA,F0:Z\؄V< @r4 %-ڈCO:zV%֭A{u1Vyt+4Z/n(oy;_E9Qՠ.TW9[YC:ӝeq=<v{_sWGlͼa)̟g$"[Gوg3uYlı]0梛20}> @ Dl*".jT@"ciQ%cSX20A*7s@<HhJh*XqQV# >`S::7YVg1x:7C lgke`w;ٽ0381c!fpLj)fYF }BЂFpw1|*ʓԆVՉ֣vVS5t!ԷR {%3n߉ֲR -BH@O::(Z M"oԯQ乣~Ql5<$TO(0J+H5в0Z/s/2wo#??e d8Z[]utsQW͂R+(F%O'ےyj-b#Hl.f3^zg2ҋ'OYɞƗۮ||zi,ectYr;o{Yxqo]3Qv 7IK'{gﺄ}6?iL/Ֆ ͱoB6-О+ǿRiqq4sy4mu[mM{ۚm ڕ+˖wfmYsa˒ºukZ[HS[.Ѳlʅk5iԅ-kRyaIҺxMڶC7KjΝ2sH'uP\pI˪k.ږ|R'ZٲD:sag ܼzIQ ZH֭ .Zf4se˚ƶuҪuk[`hҶ՝µR{˚U1ֳuN-a5mK-hyCumҒֵ+0K:,^-; %imWR˪E3cSbݗ^&iY oߐ3teJD[)-(2ִS]ze¡I/LO9ضu:%-.n}l?@-XM فˋ T.n4wAUW)RS)g`Zjٶ=T~\ }-^̏GC:rVø4TH/dXL C|Ù8J<H2Q,ۛ*Hsݚ"jk7\|H7dj8䃴.Z.&\ Psjj''C>xP7.n/JE-cgA-mN )㧕`*ٱj|Nn0aVwìè;~~Hʟ*mejryT}ܜL>;wv7 ҝL=g4n`60&ӴhHc \X>n:7 8|i>)x'C?3䓸zV屐O2sc}ŵPmGœhVvu̇t\XO̍O|jZC1dhO5W -hHec'U`* O *H%%0N>ܗʇ's(c H\>y!-^\!P\ jk#׭0kEG5 pm>våF5YGjH 7LxܽUU,/Htwkp\t\L9 :PEurau.@F #^0a7p r`}n\ECFQB) DojO@pδ3s`̶!qn1~(cQD7c7u3ԪۥMMIY;ZJiB%'C JM/ qZ!KK9ȅK8p nɡ7;vghǃ.2yceԮh[G+k/vi,)$ )=`CDF1e<5/[yW^G񚙀QRO<.)?ҔyW\Qu;7]e+ Ce{j mC3Da9Mʢ<%-c j_fѺqZ모 C5KdVa͙P= 3IOb*7)2JVQդz\Tqҿ $I Ie9Ip3Cb6앜e?E`/Saw+aG[ut,Ulni06ykAޜO_x:D" LŪӘ㟧ccJM=>/&T{u>SBuAh$rK &iR!.#q>\0@Ӟc$fڏY 1> 4єg0W>5Ńz6փ ZUM/5k͏SJ.׃u?Q]7,ArAu\ѭZj2vŬ<(':}}.N&(VsC2P*eKf= Eg ~, \KkNq4%ͳZVQ}8f8^2 S2ỌLi5Ȕ5)CzRO,=/*y^w#-w,gay50(?߲x7{yy 'kyk@ޚ$(g^(3 ykLB6C#: 3# endstream endobj 31 0 obj <> endobj 29 0 obj <> endobj 34 0 obj <>stream x]n0 0k#kȏ̔/'Ӿgp>*SX{?;?W?Ş'Tdz(7a4_SO~ג.e s>:}oC?`}ߕ(( endstream endobj 36 0 obj <>stream x|y|T9ٗ̾&3wf2K2$%; $@@&@@-`m]K7Z[?mk[)mѪ5j[9w&~;ù9N=n ]P}֡Mh:*'hbkA<C&܁(݂ɗeq}."nt5ڂ~V݃#(MIFz݀$$l}u2foq%nfveh݄8+p9z! w#ZP+Z&t 3}8$"VC`4r\E]B!<)bO!3jlnl>o#Z+ ݁a'n!rc w}X/×8x:69|z@ @ɵx'_|$xX򌠩 n_? 4h) 29nE߀#t=Sz)d\Rxބwk]n|/3%3K:>_DGZ2%JE6].r/y&HD, HfJ$K%IДc6K> qVq|;~ D|6dK2^\\7z ~2~G@[D?c x ؛W@~g:ב}#fШݠ q>:l&_20oܼ w HtP-s$=i\vl83;^r3 jM)St-BDKdXWCcb;(efo 2eOr#vZ\j#Gj62[ΜDσ_ ]OVY#^= dvM}'vo!>кmEKAnjJeEyYiI, 9ϛq8#+nZ&Aj2*B.J"!y/}Yg|Zt@E%9>g[.q1y\w| UqunZNj|sFUU\Ёrnp5uka!|gZmz veӖhi=?(j<prMGF|(HcB} ss\H#aUB95N>lpPS+tk+/\Y>3l\fxK.vK/s<8\1p<֐Lc`KKbŢ%8yܒHzS̱"p8 D|͐a,tEٛ}ZSX0=f/D1oqRW _=ߞ{WhdPQ JCM."Ź8t[cJ;UӶ2s̔B䡨䭱S7|LXLfgTLkby,_9v$ \6 F CW]gBf(QP>!Z#H,q#xF EFjP{9f|@yHmh4}hHVh6ϥĹ.HF@@ܒ.=<#] r},M*A٫wI˭D+QRִݸt7lAbi0EFU+SFp:07,3ӧeCEe^&KMfQXSBv.(aJ^_iQ+)^d˙8ax{z~ZL[Q=o9s"}DyekC8] kT Tr D [f&oKIqa턝誅t !2hfTe| @F$_EJJW)9͎%Jp!>2N+7r6ނRZ;Wcb6ޣ:כwV(%^8d+4-i"cg(f.jeyƼl$7rOg ռj~%vs^"ߐvz 6?!rlBP5(@,HiF{0QSIy,'z'$E`` qR3jA&$ j$xQ_K5Y;Rv7y~Mu$)>17Q̓Sc^P}?W֐kZ_ypAwHfŊg=L鈺}PC*TEUMdkv HƉlZE63 އwR.hcBf2Hc@(j=-xA4Gjͻe^?@g ~UjjP9UDeuǙys=bvxP|FX$Zn6?GyT= V @s]Vp3.`W$rpP_ц6miN-U"_Pq׼Gk߆gNF\[qv~gKܹЍwS7(j{Y x<8 q`1oеH<#(:zJK:iMVΧOвѯ3RITZpօҶdԥl7 SS;IJpIJb-?n/3w ~[3Y Y6˞\;:Qt˗1;>dDlZʛ**]om,^篦7^~- f%YA<(fǕODe4]=83wKEv4pJTΩܳ64wZf Q#j52̅&BǏꀻӈq#;ď7njHvg)Ќ,eTP >%qM}3:^*$ 8!)ʨ>h,(cQܦw+ޖWD؋XbA^=ƇI:[aVZUNNSxol <ό FOϧ_/rEA}PaA)+ˋ=FΞ8 pE9IY<氣O%^ 3;WP!:Dr*ŰiD`{x9skD: *;"|VCc;SެI g)c> k.L:bL'%j r$(eh'`/?Ơr_4 %hVҹ3oy7oX]<ǷB!Qf]}VUր NeYn켲0lV_ /xMQx94ߧrWLO wf0"^؎r,Cd}Ԑg:4yZƞlLF lXMyJmPԮlGJTm P43$W$) v3C 0J1R|BF9Ϩ^U}bTq\q«28q=7V թ8cS!?vnE802i@Xۏ/s]?Xƛn .h`N֧-jnku̚m*K9p=0Ȁo@/ِܐTdfEw{{Krbrpw!nA r=)o X#gƏ}IFZn=^s[.H m-ܑsw2 .c)34am~|S/J6"ևtJ \/z~ERYm89m;_`^`_dSv-*,(e"邁LU4" `@A#g"-E3N-[|t4w g.:\_^Zl .[)3^žSgjhDT$\ys*i*]D")̑["68 9h- s؇Օhd.K2$80+BĽؔr>% %$1}̀r`pQ&qۦʫ-W+yU?՝E5>J$⌒RuTc[̈́a}m4ZZq?J,OLT ݳVT׊+6lȋ|qFh,^I5RkΓ,[*p̔Ke3勥ixog~G?̞jS|1,u}ʤ2IeF yJeϲ]ID~+0:hzBY K;:j/es'` ڃi2(g =jt.LYEÐ#ŻN^(/.HZ4kql}=o_qEh;x{ն+ͳWNɱisJˍA-:vhğ ϻ,U-U/XQdhvvwe\ٝ;t~}UE[W:^ ސ{C{hDE+e|'X))ɂ !)4OѤlV-o) f,L,u!qF*JYqF.!Oҳ+*J$"& I!qs{q6=c/Yd9X_n^&]kKJ6[!aD"s60PIv74G%AwPB$VV$Cdߴ"B"=c阎^Puk|ԬRS)<&NrB6J98;♷ey!Mw$ q0RgˢgLIx甁9' LԮD>PDz|*޼X%| J~ tʒ͂LDL=bÜ(9fגey#w7޶:ILcXmW,!mSy66::+ƺ|&[\̰bikݦyەM;.Ww̽{>zceʏU|pCu'JO]Wdm-3Ο-S{WUxK[g>T&`oFݜhL%0%F%/Ǩ!ZI$/ jurr Ί*CEEg: >E̙@aNUEC=oV0N_Z9:<'ѓ=X9.c'V3= ު+N3h&a7<>l}N<1!s tT17O(hzRom1W9-&ݒ??zwq}3_r7qJ;q' Y/\I/IO:|Ro*+dfA^LwGgFP9ӗ+G Dw[? Ni;q/&1ї~5MY(Vjm*tebZvYw-y rCPf \>#&[tԺ,SUSb+K9>\47Z^bZ-f7 =5=JΪVws燤}nhKIdO^f(9y`_5O`5V$ɬȬ'LCЋ*"`.{`'p t:F'2j-ƹ1d}-I ,eV+pp^GPbվeg})CPK#_@7q[Cq1 p0 ÑRBL1h RTOL 3zs!o<Nby۵RNuJ-^Zf[˭!jV{|9q6 E"7l?" !$2!=7L[c yMڔ&sNkQԿQ6}M) (39Np GnИO;!!|0<O9x{A"H_;`xzJz)Z kȕoa;YᠾA3<ƾΆjҺ>g~n V XfU N;H$Wob򊜜 zUS*'jsMu"SynNEENnyol Ơo6+z33M ?=1~QvMTfXƌr(d00 "C/B(;Gb|Ŭ* WMWs''3 ȇ 1 _˕Eq-D R~#pNGSԩZ1/ހi\FS}0!R'';8![t혍ʼn fO1% lBɖRo+l|Z={`ڷ+ԌUVN CI;)teWϙ%+OҘ^\`p+7K\BʱamNnVA.۲5+knU7E9>nVn_=K* )ʀL8y,B~z8)Ǫ531h+]}D22wa6 VU|&63*UmlXi,ˑ$B?)\kk7qUu a5ze=_W4DXU NūXmf:%ߚw7jR/5v^xh.trWAB`{SOI|z*KǐT֜cT2?VOϒddf-j|Me,{;J|.N) ,ymS+߲V̳fh4 /C.*dF$4fɫ焳L^Wg,dw?Ah~$WDsg ͹==sO}6}X|\B`3UUYJ+R ,Co0@҅s B2lħT*Hva܅-trrl>)#E DBO吾񙅜,z>Z:~"Zk,eLk΢O"4,X}'2Y8?C`h?)E@d NYbJ5rOz"W.FOEfBQ .KR\22$M[Y?+frg0]Oc._kGs'GnǚĪ\N2JmV6&2??c{}#+w[K~%{R*)ρm~$ņۧPf=>M̾ݿ#!VY*lDY؞0 _HD%5F&2Y dOc3*lS.Iykd2 @Y̆4q/"RC*z `i3fU NjθI%. jz&#) g<%z,{`qG^j=$3:7&tuj2&{J뿸e^D;J/evC$W=ޔ nATD^ 0u#"y,Ș&j"$\35ռ ;;z[)ajрvo<ﱜr"ᄏgOMR=أ=ߕEӞZ^i"lR7/ Q#!<p{[$@x7IA.=w0KحRqD+]%˖?Q )ש֩>Rg4ZMVCqhZ`ސ~_  oV ¯gmi?Pb^J' 3}3 (iX,0 Q!%ӰiXn [ӰCFLtG(C4LXffP\fIlI"ҰYiXVa)ԟJ24M4YRK;0B aZDŔ>70DKhL^%R> ,]?a0_nLW~}ˇ0_~6 M@ŋiK+w 1Üa^V@!9 (ZT2M,*HD Ҍ_a2~':Fa|UB4LS41нkMð_~6 OaگIKzHtm$ 8jS|4L pH#Nô>ţl:Μa]nB}s)ʧa!@C@ ?&¾tLô^1קa׻X~n藡_dԂ֠.Qmͦ5Ӡ 0}v@}u?J]wVxvf oj94mVuHЯF+aM] ȡ^O`l -}VAh61R#\߄!~[h/Pph[/Zut9S@J?_Jϼj 8a59Xt]v?^?L@6a<Jm͟XFfJ+P5 g tP*C6a'@W:AbSI0b@Z$`@qӴ:t"շMu,+S&̵_=oLqW~p{)oR\K՝aezC;]АnNs}պ6>.9^ARRrBRzx*.Ij/|2HOj ;4wt$vuR=jNHnj0%…\˚.gcOM]ܴM==C\k]}][:C-9]۸=:6w^9޽ZTs®> c\{eoO_ϪF[9?=_:ގή 븞Ut[\Fl;a=\r+{l A&Z裶c[UWvqtЍ뻶Cy0ή\QY$ֳбꁖ>nSW~ۅ-]BaSoO疕tt\sI_Ȼ7\p}5 + q=o9\׆ű6cNޮ> JJKhBXAf@) vl۸Ia&ѳӖ~k+%.Zv%0}ۛN(@V.m]bd2#)fME`"xo`'^k2GēLh6C?)Z<J_o/e){"s>(NxFؔVI{$lNcy-e_snL/Nĺ7 A{6QBu}'cv--o=GgSwɏ/TdBxH B%0OՅ8r§/F<B:H,$rB"̓#' 6م^S@qi ]#gcH3 F'5p<0$F`ggQP@zv8;7|9\=\̨13#H CD< mtV7ކ1$$G swۥW:wDdŐeOAgyغ*LvVw>Z4s6%ϐcE>YHFNBr7grB>Ia[#: B:G`xƕi7%s r):K"׆tG#P2jрTӺ[V|μ0a/\eawZ4>ùE-= NAxc/{s#J?L {{{ˊ. [sK xKSRkNxNpF`Wq M HFg21 Y&>&&/;Z/?tݞ|]9!sSvp0p#aJڮþH<!Bt)pszzA򰎎ERa|_YQ!@+"n_a8EJay&30fv/B@\-fcY a2ǘgW3)`2FQ#e<mH *g5t dcJj*B˲kafw0 o+`d )2PRFA E*a:==5@t-w^[9tzzKSC{,xW(5z *z猢#iE/;,s> endobj 33 0 obj <> endobj 20 0 obj <> endobj 19 0 obj <> endobj 37 0 obj <>stream xZI, )SRBPKɞ1sPВ4TWejURp_'uRm^O}|/?~7;ooڨO{rNo= 'N~~v L]YA_E3EyonJz64iMi?o?4;p~g\oӍfh3ffӴ-Y/ȗց &䋞I&R6 _ von(yuoBTēH(w_dEtv[uXHha!4[Y:mĜ(h2+,i =7&9I ?g͊o@@FY@ "C!4~>`i?N@zI-.D$U vФXiAe7>Ji !<"j  ^@T <} ݁wNeU(WθFM-^lݓ2{ 5 M栋Aδ\Z;;ӚJ[ŽՑ@h0κFL(hjZj~P Vp|ap\~^L;#xla]4C|j? @/LcMaZBp$16@"Y]*~Xf6#3LF~x.n0esSrT!vtgaϨ5he%p]e#2ݫ5j 8F4<9$y={;)ZAJ#"OtoL3g!#MxDt9ڋ SH uc0a}/ m҇y/"-;\D*vut ,FeǛG%rKHx2$a_Z;pv/v'lTb#P<=Nzex4bz"^^D y楏J7ɽa|PrH &W`zqH+1Lhg Mf& a.6 0݄:.J5n{ iY `u*mB6Pdžh)l00OYe0ϴcCR.}N#/d l¢zb226h)[mF_>]Gx( 6{$6,6)BDG샏}lr#ieŌZNa#&(چqxx-%E߂xvq%,τeŸoȞ$i !)'d9߼yRh Z7ggZ*L7N lL~ZP*~bSc> e5Bƞ "D'?eB)On[X,Q5amFh << q>t'WP,l-)qiڵu5䑰BZ Y "Gw10yԤR(5iuq=N^'[ E5P:A ZZ\ca|&DcSz,j4L'U=ߘU-/^bmx8 }Z~*/;<_ճ˻u FG#t鳍N5ʾbB!/ϹLm7s'I/cn!] )i5u n+ݯ`rkFahGw~ FUJ1H]c@4/=c9%EEC\hXەNA'٩kZ襳4DSx9`QBFJ[n .>ҟ$y!Qg%.Q>5aXͥ82 Af~:JCzᨫh%}+WEr'v]r(D!ZK:SIq\6JPrzOkm:/XCü=WwD&K%+P\{I#`j;}~+ Oy|&Srϕy) ::O膎 hBbeEDUdEǃB_^*Ii{`\6Ë ]p{LAF yfqx5wi0ŬkYUdtFZ?*19{ssS_6 C5_Ú::QoTQc$#DÙm?MVS@k@ C@0T̑( f0S܄+Ъ3 9 ɎӨ?lrE}"+}~ tw񮁴t1*VбsZItH}_9*)r:Lr/,3&RntM cTSoy+ر՞eэM: 4G}H X<ŁL[lȁk&ʓЁOU6d6`VJ LMt'ܱ Hh@^޻jrd.3l͙ݫNTD4Vj p \&E[ɖn"\MfY޽hRa,f +"]aj/*5efw_R9K8WShENӊّ؜Sb5A#LC^rwhDIWTt[6ghx@mrG$Iu"Y#ܨxWW&R>ҵ# )5pM*^,{?WwZ:^#6cX!;agf,}yJY@Yz~IU8; QpWdE9< _QiT"% Jq? endstream endobj 16 0 obj <>/Parent 15 0 R/Annots[17 0 R 18 0 R]/Resources 19 0 R/MediaBox[0 0 595 842]/Type/Page/Contents 37 0 R>> endobj 39 0 obj <>/Type/Annot/Rect[56 543.7 194.4 557.5]>> endobj 40 0 obj <>/Type/Annot/Rect[312.9 557.5 539.3 571.3]>> endobj 41 0 obj <>stream xXI69:*Ilh vmA2)"Kw lYKU}_m2ghiLc۳m:o}4OmO8k Nj]Dg7{1v g&p?hjIj/G&sLCެ1qz,uh )MQdsÌeky,EietZljoO6=n Cߝc5~Hl *3?q 4KZ)sereCcu?؝DYFkR0" PDinp1Н?-BwI˼'=Lq` \HN0$  ~.dKBzĵνrs{TG=)O^tGb"a>犿,HH,LfO[Jץh4qExD=K̚HOqs= Z1֞X,@:O}!um΂NwIj. wҠNɬL5@\ #. ^a;0A C,Lye[465RWCSIBpz]FAH1$ŮjV|}o*elk*]|0B;H%8D Gp$3n.g98MB*{3>&ЯS^#In N\}vQtO_`)@/Y.$36yetVǡe 5XQSj)J v"@.#F# +{e0(Ė֗6C*$&z^mHzܹH+?ԾRHhj~t2eߝLq|j XF/5?>0 ٨ *4_|p^zK_fso%ku]>/Parent 15 0 R/Annots[39 0 R 40 0 R]/Resources 19 0 R/MediaBox[0 0 595 842]/Type/Page/Contents 41 0 R>> endobj 15 0 obj <> endobj 42 0 obj <> endobj 43 0 obj <> endobj xref 0 44 0000000000 65535 f 0000030741 00000 n 0000000015 00000 n 0000000218 00000 n 0000029060 00000 n 0000029021 00000 n 0000019053 00000 n 0000000372 00000 n 0000018867 00000 n 0000000927 00000 n 0000028773 00000 n 0000019449 00000 n 0000028578 00000 n 0000019842 00000 n 0000029110 00000 n 0000134463 00000 n 0000132103 00000 n 0000030904 00000 n 0000031077 00000 n 0000128919 00000 n 0000128858 00000 n 0000066580 00000 n 0000031247 00000 n 0000066389 00000 n 0000031847 00000 n 0000091714 00000 n 0000067025 00000 n 0000091518 00000 n 0000067539 00000 n 0000112273 00000 n 0000092073 00000 n 0000112086 00000 n 0000092577 00000 n 0000128578 00000 n 0000112623 00000 n 0000128377 00000 n 0000113044 00000 n 0000128971 00000 n 0000134296 00000 n 0000132270 00000 n 0000132447 00000 n 0000132627 00000 n 0000134541 00000 n 0000134588 00000 n trailer <<9afbbb0ac3dbf797b6b16a4a86479631>]>> startxref 134747 %%EOF trunk-2018.02b/examples/triax-tutorial/session2-stress-probing.pdf000066400000000000000000002244231324306050200252220ustar00rootroot00000000000000%PDF-1.4 %äüöß 2 0 obj <> stream x\Ik _uh v{]B2@ M~$դα%\mSRT'Mד8 d}ӧ`[LP` .!Ϥ)o]I"r[xCRQiٿribX9?5f\H']SD$2; 6@U?~=fN^7v|{I!&oB8H` I j?ʡ@6!MYhh]@-'An!-Қ]*ʡm@U].-Z$/ /"H 6-f}i#+piaRdMހ! N/<C-Ul>Cm`ʞBl5OM3Cۇ8l6̱qqci["74SwyDIgsa=[i}d̈́ž1( \j>I GW[L&GKߍ&PQ.ؓC)-|{FA}N"mFmu #qv*K=I\RD(K_ybB2}LRM* omRm27Ði&! GrJr'kHAs _eژ.&JZ̈DCT"/`sf?E"ZibB3ϧVKV4Oh66imb$ziiظ$Y0cQmez֌\Dzg>ώH M+32׋̏fGD+OTpd[QF3Gp!2 *m$kOXp9wLku!eu[YLohV-mEgGb$10B{}ޫ(4=O"cF 0Rd4ֻ01 {RxmY* *cuJp"m#v@4cX%=x ֹKI$s,<f8 ?g.Y%h(} TҟC5*RsT6N$V],/mMt\$`;+=LIGŎMk'1`kplX$Whg "}>AH6q,=hSڧeX`zY tgwybQl>Ji 2%@l/A*3 8U oCYkEl7Z#d@Z ʒ6)a:my/~[t>kuߙe\S Iui-%BT"ԖS<>-}7!~DL54hK+Ɲ)W"25r'Oݻ>4X32ͳPmM%9d\UK_/rfjf"-A(Ua tVmHA=qYu0w1utr$)ѹ2j<7yD~-R5;^ɖfP309q0k48q{ȇ81RR *űchj{sJ1*ɗM9EӦ}{D֩RB(sv=:h8YPI6JMar đV;QNz A0$ӎ#T NgycDRX[g "ZhVbDRP)m]nzT'̬"i*`[atϪ$iM}dhMKf$ ͺ} 6T=2۽iPnjz$:\剗 Qs4vEQ$vPuX$04^՗Q(ح"uTcJ\.jSUҮkuXdF(w>pʊ}0a2N)wϺl/ޘ76}́/^őBV =#-eKkף̥I4GƜ/pJrގfO"#"J3|-P?dl ِh?Z3 hiOJO}0V0]R[H_reR).a/~er;Cym.Č*[c),1j /۝0:ELWLUoh{*J3z-[cr?U6Jz6ԍ_Olh`*ش+ØTXZ#<ă=q~ 30F"RVN^5N/M0-VЃfheOOw xitcYv)0 O mxRذͫցNbȶ泡CmuTZ:r zD:]8WWǭwUR\2?3PYUo԰zM9,GDyt5c{}`Yǫk$ae#?eм8!]0IF[>9:+?bzhXU/KFp`2P'T q.J:NjөX/v%PQޕTU/ˏt1 _Iog%kQr 9TDH^.˦b+ҫZ):]<_j-9 e>3KݦZ[m(G!/ʌ*MA ڌ^UA+bnWc[,pjRsyV?EBC K+e36?1Ÿa<+/a ?$%s,*Oa?6,}gn1niRɨ_f:M21ZKXtjp;Sm؟BżjGt?qv0_8B;£|֪u+r}-׸hw>joW) :bNK@ڼoǔsEI|ák_v`aW]&QY΄TqXիʹɡTí- endstream endobj 3 0 obj 4400 endobj 5 0 obj <> stream xɎ#_YZ 9i #$ $/mܫJ=1|||B>ITze=_8ޥ8s &>qݜן~; pqK9Уer7me+Mя# M+‹u ûxX[+ozSwAҞG670LJ_{JsZ8#8aeog8ѫpigZ1v) @I7\Z\T KU%&͝vxKc/dj@d  Uٝ*—UP_ԽW+"nK7IM^hxw~9hU&*]v:)\2}g$lIQTYUpܟtmj2Cgw4rQŒFr ØV EJ'7C9/<_# $afMK"l@cAC9ퟪĺ8qK`}O`FCŴf;4/v3%}"v\b9sG!p A^W{oNUE/)tIұZё0|d2%d#y9 `TolDKdTK4f9" jBb,^?g^^5FQYKrl֚ J#fdf`tJ$TRS,oN5MDrU@ cd%VAV2XJjKQ[zYfx=Al3:oøX.[y9xPkױБQF9WJ{h{6 q+ -uMt vg=Kn2Q:.Z}ؘiO[e,V >G4oň Mr&b;idbesiT~Դ :mg طC9m>_`zܷ`żWW<ҿC&SGHbL"Tf42Y5H4 =P*HX.8dm Kƨ$%z(j'*^CqU I\"lHgnNH=]X[̢ هQd,tDe_ˍ-Jyc$ӻx=T"#ҐcyZ 8Fe`Ipw677ĺ,5Ad10PUBwAaArqY'yLǟW+RHFwС޳ >/2tQekζR;g[ֳ6R4ihG/ylB6+lY;ꖋÐ#~֨ԯrswF|IKVN,tIז.`S{tj͢pMbXZFn\v`[[nRegy:HP*1NaW$r(uzp=bIvA8=P, )Q$t^jgA[S=7 <-p4Q-9CEM:Е1ۊSV:aXg#]-6v舡ߤz$!ʓ6}!WuBW,J QK۠]1vc#Rӄ*hڣag ۑuy*H:x't{Tk(&]|$|Chz c\^,@ŬHp[?`RYQ9R,^xd=i˂}OٷwB,}ܒKvJsW7edwE  ~RvΞOWHXjB|M|﮽9< I۾;~;PѮ\ ]"`r;fIe˙q ' E<;}b]VVl,o=S:87#Ϸ,ivy.Bd8UV}YX$+$C(\vZw8ߊq_^W`;%ӗesM5l{9וsڬ' ~zsM41uFnv|U_bKiA>ֽ∉K/cyu/]Ҝ[܃U ]6LG0A agxE*/4o2W~xz5* endstream endobj 6 0 obj 3360 endobj 7 0 obj < ] >> stream x흋}׻XVD{5-٦lNhPj$׽M%w+Az&vwCP- !*ˑ4t$I/.X k.O$vW3od`0 `0 `0  ў1Aј0R9 p\ĵ~FcBZ82j!?B7-@f ,ʇ;Y ]OXRE2~:ac 6Ul+P7b߯t"\֏PGp!nk36$7;:I+=hz62MP|,S t` Yy݊&f.S~M_%ӪQ]8VuV~`D/ Sz2S&Kdh(1d8.mjinnn'1j\Z1t.mG͵rYſYoHbdI34$' pě-lD'&lӱ 7w><]Nis捚"\͇ [];ଢ଼ 2V:Mϵ(ƿh[__O<7'/7j7k+PZ56?iHdЫĿ3Sc-eɟcyq+*߼ ,7jr5tIYT,v?_*ٗl$861qHjLrTD;ucWo?%Ǹ~W:f6"4i.;3n-?\J[LuJo|~c3ƫmQψ1a,]y]O*, w[+/6ۿ7a!Ϳ(ʶpGI1dmCoQk MVwk[3@>t+/~0ܙhYy-dWy Kq[)Ci7l'-4/jlUaϽtXBcsaX_~wMn#D˅V~|Xks6=*KD…8Skӆ)?0A(]&W4i7?9ݐնpS1#K6, rȿM+8.د|6Smodm|+hո6% j ߛ)m@H<8{B2$[Lp wo j_H6ߘd7kN 1?(טd00DdfaU-+yB 1F1^4&]%T">cѿALChl7xC;X -a8$ e"FNzn6FN]_p}d V|b O<Ɠ'GLw7涏sjpK}WN%H83}ެ#6 \Cl !/o):m? )±6j w$ w˽\D!2ضT?BiF =* .r(T7cXH=.VX,?<] ?C4:d'L?m엄p_%Pi`.1b_QK|0QjIv1ÚNb"^.$ȕogI~;雭*2}O f< ]i#6; rQ dF"F-o); [bdXj.aܬܭ"`vf6˶uȗWÞGV<Lkn-Cn3+Vf <4@kH,>OEm/U|Z,c+ tOmcwX2O*(K:m4貅 "l^^Tg>;2gU7#&u•coD숭L GƄN#Jn_鷱Z@„s¡PH:} Y5FU*:P~)U 8\n 7at ?܀ib1 &߿Mf*[s@?輬\`joXje[}&$Emv=ײpmV}hI+ZxIlgU|B.+CR[@J{!Dq+Zx/4u1!g?:_ z>ŏ@“_EaXjF "˗;Aodڿb bNvwHA.=vd rF닅;nF}JRbJk!T"8 T+ @?eZ g"p+Ӆ[ ۡMK$Xo/<ռ63(?늅˺jPƿ!P+2xr{ `p$ ˺j(_v&kMұi Ckߤ^N3f+  W A_jPu>vKKxBm+ЪwOFx5 ?_+/w`uMP@r3e WQL/2 d?k]:̷u`5wa]N~<f`gTj"A5JЬgA {C_Ϳ_:g+|3WJ&_uXS?2M/{v^jdbV [)kWY( R9AGh+t7Rpam| ڼZ\Ys |v.4`m J]+sjBJ}kT-*ƹ`wkat:?|X]xwEQulg1 =\w GΝq!A R@fM8amslߪdpSQg`D`iHQi(:) &.`sr;$Vy5S*0'1!1!;qL0kIєx8A10B7P0rJ8# >킄/I>֩=dMl1ߌ* _fnCN( k[HXT\ͿO!m .v @ p 7ap'{{tjR8bo)Z)?p]@59蜕C@z=$|RuS8Ǽ,Qq[I5CQgZ6ڿ7J FXzOh; XqrT|K^}YZ_1Fg 7_Wշ8 7rƸҝYȴMwH٨!ݻS8~U<$R;;pN%:73n5_|J|(}<j"LO mcmֺVgaοo-ڿLMxG]A jxֹ @Qt9 j3ER0(+$_-O^k3e:n%d)I9%z ɤeW,gTUvlD~\NNr2a'3/59lplɽƛ,HGŽ7w$1a}\g7 6LAp^W5;EQ"o8fl`S K _? K~@64% /Ki'${0a:%N :]Jj\2Mn)U<.,6B"apԟqBc`_xU"te30ruIZ+DBՅzҋ˖1nALDE X.RKfdp)3\Z'kw!k1 6U"cCowyG0`Fۋ_Kapߟ{UF ap%PKaο)?;k@YT(nP36x/Ps -H$DžKXu3H|O¯ܞaL{ p#%AIU ״`>Vb|iBM'"N;2|&oҊ 8ǚZnR}_xsW\ب>g 9I/Edbfg'70a\|+U)w_o/;7 O_{] +$0 L*_ssV.bɬhC`B;nG n.abhGY{׽94 DfS47u׼m): |?<[2@y{׽-Q}N?Q?P,}~_.<ſiYmof8O9kC5aphg^y=0y4hv0ԷLРУ}^ 0#Z] gɺOb[ yi[}s R*STDF"*+#_ZvE;ё}Q %;ޱH2l fؖ?5I*WYy8OCUm6lTLZ]􋗸|ŲO+Nä?K`բ:ۿTXi{WKkO?]"`YrHz?U]߮ b~'eY_9ِS6\{^ _ 姝npL\uduZRxrWCd$ϨhA%qduCݡ=?|_\5[Yե-F;@^OOu6U:DZ¿\K%Y^=Fc Vϟjprp!@\]=L^|. ?K[T<&ajuy8T_S e#Usz{mQlu82EyeLB`+C $?pOw߷~ri([_c=J08hkNG*4]qAegc3$]U?tOaOyW*]^Q ۿڐEe2כw!`:ʗVa^c׊7U`/kio1+x~ܳ; 읯˼a6c_h-5L?wC砽}Qcu~A{2k_$ͅtku}_nӿ¾GZ;?3LHrOΙ7kr<ݺIp%ۣ𰜼\C_=n1g`óFC+ȢW>19-x)$e֟~`֥*{P"A?t2䪌Yk4A^z?uv%|կ}UZ=asAпYe2(WUp?@#2|٣ Q" g4]mɣPeuLr믭HoC~+m7HZ~HEr#=;$]m{k/\B!>Q(qLYG׍-Yt>@6_?^eFEv03޹'l&i]} ԅtA170kiUU }D6οȨ2$/>r3 2>SlIʧWS?B\h{]+Go`0ΊqC `p/?T,v?_*܎+k>t{-^Rx3^7Z :>"M?o_3? |wSO ndOY/jy~3ONb)M=7 =3G~*Ӵ9:uoM,պ9{9Q8x$tGIړG_^:o:#X銗` }~5[y24tKpK-{% 7|OM 㿕m@S=No[g<չyur^͂n>+c07p[)RJs\sʕ[ϭ9ힽ}Op7Vz{{0_vxolelP;ԴCs"1?у8nJ^9q~+A1ɽd\țxdk=#, }иNq\VVz 7{|ղoX`ڡѳ>/d'%t&<1'RJ3}y#8ӀK#\w8HD1;2JEuGnS\.!G9'~%&tuwG!I18MXۊ|AB֯]uzTgQ/W:UfA3+0ܑ&m"7d!r:mw> % _bFoQ/{Nr [Q0-v;~ u%iڿ [Ӏu0sLwi_ 9d Ljg~Nx^])FiT6 -2.;QB=a?ij_哺%OUlkOhҘJ}֘~dEt|{0> 1_[`unSGbB*ጶdB:{$5(G$ڛRBhcpÇw]~S+ٻFP1)v}{t$r? N8Kmϝ:ih"4 pm7.#)6bB(!R[W[.Xn0F0,+hn 9#vO0bѪ;08h-c$Ȣ_n0w:(&kakvX0_N'9Bt-$ V hma}Gkdˤqסˎ_69AN/}l EjPyOQİH })Dk5)|j P8%yYU.L}ͽlUf~Ԏ`_<`;=qi.Z_'N752|1i8/WsI[ϊ&ަ!ve}`L7o5܌-tWPjr2XO%ڟWourF_P͂Z)8̿p6vQfr^z5 Y1V{Z5+oErXFuro, ˕*nlF?\S:<̙-)t_}5Qz_(V> Th>wsj:\y>l=9D`1vL2Pd2 ]SެM (pijGYuݖ)C*&ɆE AhFj̽XBi:H̪/mMvL钵ƌStama@ u1?Pca-%Q,ؾ@A%=?8&RL%32%XI5ojj"zd]r)aFH\/בV2QSQk* @BaE|04$j1-KHP˨L#W ;00 O00 O00 O00 O00 O00 O00 O00 O00 O00 O00 O00 `0 `0 `0_ endstream endobj 8 0 obj 9424 endobj 10 0 obj <> stream x{y|[Օ=ik$&˒l{($I, x͎$)dkJY e(ҤeihKtR:kL?LK'I73Os=sz=Nd?Hdt̫_ !?"2G$ͅX~Z?1eGa?"[)BT{/RviTL"[dX빓;uHXgȦ{X1rٌ ;GvND63=}ԸHH̎|CX/#c4?RcQꔓR\T34Z>`4-V[v On^~~-r{.{o.,˅Irgt !^ϓ#^l 䑿K1r'r!$?G;S+d9#qVg$J~I^Һoȝ5!:r 8AлCP&aK\5Fx wׯS.2IqJr5܎G2i8&<g,4$}j{8燄$G> »CFiŠ#GZ=B##o:ϑSj0cK8jO~a b[ړ΍I*L <*[?{@lGOGf$wlD$f--zY喕#II^ۘy|t$3V]R9"j|>{]%u9+S;=Hi LZ/N|OFIQ˩ -j/$ƕdj%MI2< 4#ͩ)0Z3$֩20)# ܄rrl"Mvw22<`ù޹*=T#'kO=EE.)ϓ B+Q,S Lqa[ֻ .N}Yc KkWȝ)cfn&P-.8te27eF\f{ tQBWX'SbPvhXcrAN39fhq sHڰdj#iwڵ A0ԇ؂>--BA[ 8Ƥ^R&Yt}/\R|!%?fg-Sn%_* n];܋zH@Tun:Nz V8lf{4: s"#&ݓ$6m7Q*:3iXhX[od`4C,ߓi8MMF ]2?jjs5<8R~Wsx>rK/FlɆwf爋l4Y.#Ǵnλ䦲xh +a#QYk4JlkZ586lJ\&Ľ pgpXԇbUZch#.iaYX4r3+LJ\ _ %M59/O{νme4f 2b'k"N : _)#1sqnnB2-ITK:8l?|P6<&<=ܻgn|B [.0 6?7aCB "|o@yڋ%["Qi9;?SrcD0};6#eV ժux^Sl`|֥j c23[8OALٔ')򯟺sJ:zXbm[Jsƒ׏?~l|֠XΜkw}`0&BB4QzV m>G'(m0S拡I>Jqq+P Mw]nH7tNr2jCl>|+?nL+\j u/g(jpkS齺06xk޹vܿfdw hubA*4+(K7\9p߱.}|P*־&q`(d "PǢ{r*s94^r( )8_N/q geHyUlu8L.%/dFmu., -[|$b:&疍G 3dh.+A( 0;y\|'LRRVj~YSUOɱ+ޒy~j. Uu y1>\-g`D>lќ{\B$8@B)!^FVuuV*:$Zg+b ˦պpf{~`ɧWLWw;m+W>cn\h&M'YWHF' a LնA@HSPxlSRx8gI*B/ھKޜW,sL¶sms̾MV}҅ xf4 h H qbP \ q頦@EUw}bn::z6﹧(UAQT L &?V›C &mٸNN/1+3b ,:Z-ڤЦZĢl e˗ * aNeÎ}bohL!韖MSƉ^6jT*uaIVgD^.z魧t)T KYf}Mrާ=O\,AϸE.MMlpYAWQ/7yLduh D}Jq@ n!3 EƸ֜3󧭸*Mwz{F$2f?QTT~Ad/ZVUY]4^o`|{۸$d^6{Rf+ފڈs0K5-kQ_@Yur bBd>"ϖGfƱ" !6TX+, .T!DяJ@%D2 \xYWL;E CHarpK0i*̆ Bo~^k8Juvu׌)oǜ]ã]E5ÙKm(X|V^”f[\'vj|kSqbNƃ(68a'WSpNmgc I%Nn5h }'e O6±\"FR5NضA֖lv8w>%6M!K:Ks&K7c,6K"N^:'z)}-=mUm-1lwr *TvU/H9'lJ!(CM!pM"-.YTS⪁"#!=on^rm+z{ |0ڳ^i&o6D[$# MTY,_w5*3Lvĵa$`\HTZo߃~KDY#&kLo[B$3+&JArqP۩S9edIܲ8c38KN_2Qcb)87ԓeYOT^&L,bh!O= חxZ6gUUb5K]WmزatkÎLoK2zCsl6W{GiceyڰC/ <|pȌJ[XRRjruDݧ zTPjQ|*qYWeЩ;ZCaO{b]З6]r! We6zdIHx(G(9( wV^+FZ]`HgVoyM*!)]nb Z%?oxޘqRNA% vm߷[ܰ ˥mhӲ!-P<29*qƫ3ߌu96;hBuav:~%7;VGcܗl/am GCbǃF9m::dFv0lآ[CqctML်ٷ_ v8u 8r@BF!%ATz-_v>+GT^s. @{0!"p(2w8 !#?""~qtbO9bD{Ůƛ5B '44a):R&.MٴI*xLFj`D{,z9| Z7g7Rzo_vZ|`1E$Sȍt~W0Q@1ٓFިTЯ(XUFe~4Q!AC2 `[e e'˞-{LR6ϛw4.YّavI( Vr1a FNC!r]-gC[ [G<uBven=kWOeY.9 0QpT p'TkW:ZyRCQFBO 缳OF,sҐfh6T蒆\xϿ~d UboA~,rM]VI\\=QsjCA0v;t,9xԷ, 4ʡcS-Z}\QhW4oix 5E;&?',A 5+$`aǤʋN Ky?.WYRSgpyuq-.-h|UɵzͽyS˥^fB caa*)(;\BIR* .' B BZqJ4Qn?ZPP;;79G'tZJ B1astŒ3A1k6̛S) ̰2̙vĪ`lվζ3crᖚz=3?:]<#fF&꡺ ƃ'$ Pgi4P십6K))'}R:)L )P"1n6 W@E.,uWBee]יHWֲΈl ;dZ6-G.)Y9\y^BJ7'\gi+V"24Nr m3wNTL\buҔtT).S*s}uaik8/(Si7nmXc޾Skut]>hhm 2GSJKGy%b*5Q ZeTUEH@Q@Bq@AЭf)ޣ} PȧZ)(U?=ꄚ6ǥj5Upݣ:9110!  NNGT7]N,3<͟)ၷSP+RB|\+<̤74iFݷkH[P򅮅)<P#ÉHa?(܁+_h}tBP~t۹/gJut}ڍ+*1#ժ9<ÄtZҦwգ*v?;PBIuP `e"j]L䢘gθ Wθ bbD L%!rRvl@6 \hgς+ִ)ou>drPtǩ`@sʱsoA9>mCs{_\ K?~ew>ͣ΅>.|d<|,6_#?瘖ȏ^[w0oZNfx.>Ƞ>J]'y <~iy&tx ;]vEl-EZ,}xARQ9s:X }rUrϑHC֏Y av:2MCX* (X 6zs#k3^i _JC9u>@~1MX~s {YΘ{,Y8 ~m~N} Q#0XQ@g,2eu:uG| Qˠ*1u(cY+ Vxj'zLé],_|dd9Yd+QZ&mn#kUX 3RV(c2R4)#?{.!_"+qu#³GmԵjLU׾3ܩޥ@uѐ[J2;{H*T--̨A۲A(βåb. 6dp~¯%Hh7̺a n`,c͕µ},|ދ-GY?x !opI[;#7u?xG&mVh fڼk6?pѣ7ėKy'WFxɗV-C{>(s` P`J'L:-{9;)"vƦssiQ|^=N=|!%f//}+K.;weXOKG7xVWd{l.[{OAUcVo?_S隊މ-7m<ʍ'iA}3&rod: *g/ /_L_*oŏbOGKy Ri~~Kyl2o<}q<'x< ,jn 5%q3ӌNr|& Іŀg;IcʳL3 9qNEx 5T&]yTVQ \DiR7e_=Z_?V(>g]rcrOG=ZKAl`}Tt@ԩ#ͫM[Z<}9mO͖~o?rT n؝ҧw<Ǣ䪈E@w*`UE{ N7᧊QXUDkUtL/AEa*NdKEP>}upo!^`Q_qWOP{DfIuݺ7x2͆{Zނ5ۛVк-ٓ{k->~q|N#mq+~ʵA#S+hI2;H ;Bf @C].m8j}55z6lP@KMDM&tBul4Ra elΒTYӇ\rL H/tkWz%>_I||xdǷ\f״M5 S9{aYm\TT6tooƀIΣYMBuUm/\.ByIIELYSYTj6>a n8b&OXO:VPd ZpyqD1brV7$D?rW1YB,ͦ UTm\Z=t;ϟ]s/(}!%}dg8rgɯ>yoXl\\c_ޒ>B+OE#2!fݟ2W@*B^tH~KO.— ~2K w=(?BfB1tpҳ,!HϗJH_) \uZ~(KSyRVW]N}YCd/|eW%wNlu@_{o˔HI<]HYƗ ,]扚Oߑ.T,'F tYsX[J3<|)]5KXOW@|L:]pOe p2OP,}؛.}鲜J.+Cl"Ue57dN5OyjTb1al$1"<,JJ*s;gّٙ΀zvavjdbN3>X;y<.tNM%FO DR7>;`ySsˆ12MpDжs425EN7# adгұcbbjt\&Fy:1ߺ{vjnlj6,t' F靓LM0wHyq;Y[bԖdb޹q$OL'{Gfvo:>ӈ;. wbב-;Jw3;95:)-$# QgݾI))ޗq4f#XxH ~*̑Qىd!Md3Hc["vg'mN# k}9_ "dL6q&۱<Q.s+x8gQ9O_qJqBla+ځY aL{sۉiNn$D))v/btXq'XBm#v|/i(Ocy2d9d6#W%[qu")yb7< O<y'z'#7AO@GzAD^W{ҽ0|̽\r=- gމ=uݿVwn_9z;5K[ Xu5|k`_֥yf)^ȉP:~XUc5^ׁkj]3쿆]zo7$\s"_L5-re콶Wz8oB)ؾ:vEN܂VWG|%b0E\Cnn4*6n(qmz]F5yz%HX+vNr\WgՉފWGk]kmZW,ZF]`?EwG;^GkzՅp+¤7]RvH;崤f4pk?-[jO*p(;#CIһac)>it'Ca@{r V؏4%| vٍ9"}/H 7ͥK-w@V|4V#榹M4`ؚFcm?\" endstream endobj 11 0 obj 11645 endobj 12 0 obj <> endobj 13 0 obj <> stream x]n0 ~KnrG 쟢d$ț?BfEqOɇ]Ί='tPϫBfg?s)SmKngD{֍ 1NQowΧq*zьƝ endstream endobj 14 0 obj <> endobj 15 0 obj <> stream x{{\\յ^ysuÜa8 0&2!!H@`AmZmښzkؗiޯ魶۴_gUo3C;pY{{c~ie`'tB~HXMI'kA2!1s4? ^'DٹG?iBoR;'ws_CԲ2xV7Mifbbdy}?'+/͝A45Ĝz~N9KL('ϑB(H7$ccd"RCbɫX ~ȃtЭ}\$H½4C# 0<Qu8|RHad+6}'ƗAބz|7J1h&Hv0V/M/D&7;x>8#|uvA2yk,JCWg [֭m^:PnRfytYuUeEpIqQ~0O }NE0gN9iQCO@,Vʁ~D_Kj M\SɤR*HCJRJ ,/.Rb}@::>%gUx yj! ~?Rxᙆzwd\X5d,."g&Mg oXv}:6 hu<#PVU*˸vU\Fҙ3wP``kgǶ3\̑%/ nx݉3!Ƶj?͋]B\# i}F+ & lD4ƙs963ހ&o }ð,5ƶmÖ8~om_[hZ^5AA8P~? ɲD{%>Vs~jj6 j6wyy`e|{|z;ul)Bny]Wu&:0Ra$V`ڰW=O+LP瀨89 88>STp(c [u u1'"J!(3@iqc`.n 1|-&ZסfE7 ?;UHwt`#%gdVEZguZxY`o=pObX#<# ݭ#YHd1 Q4 ? ztT3hfsٲH'bpOR9/s7S{69?W5 !LlVg9lӃ:z;Qi ՜?1a7`+TC49%| )˦{bxy k914^%p/{2H% MDL'Mqgb.Y n:ol¦Z1mxP9TABO :pb"%`ۙ胛uuN%g]r[r QGҀ;8Pivk3YA>xKB|!H8ǫkbbA%<%YMx3כzq{Cm=8m=zX{{ze*[Q(l,%=o:$^I*FJJWir?1&`w8(R> 6-~}Y2w z9 uPr`瓘 UJA9s k5JK3bf̡ (ӹEN;FӦ8?q=kfw6z f¡ȒRbQ-l.RVQ p)ie:XhS7֦噋*V7w*ڗ5˷~κ:)=I+P^}u!Vfuf4/Jr%8vݺ&jÞtKrKiO\',񢖏*-]G\t鰉s}TkA(*'ss±C>S+<ĒA)E=e=U܃^'uґ%5"iNEo?- uE#[-sK<Ɨ5|lV[e5r+7;?ݳj՞7o޴hY4@yfj!,<43wk<^"!r\ttDg`_WYe-3%j2d)b.Z|+ŭ'u &}ӻZLRޜ.SYh{q&i[O$aS$(/6%\ ɀ -%jD_,l(òQ vdgH] . V=XnҮ3qS~qκw/:=s9uUo}'?MAݛp\&2xu L.h!'7cϡS9\y!oZ֜x圷r499|8]E2Hu>Rm3SVۑ᰷Ue,iPD{NN6 wToIإyeܞi,kۖ(TvD֭0u y#d7n˙[0L;P>>RLW ^pϴg&@[TAD0qiZX%&b$I]¨@KaZV­p<|>+ b^E!#j6{'*r8 XTVPR^0¢TíYDT'W,][ruU W03weN8 f":7qcw&q S^|QuZu\KtZ˂}i`قJk2+i󪝅lpMB^0IiIwSQu;Wk?Ukto]P冲kv}}}\W;.th[ kY'.e=K䅽ё0A ;< /: $,`BgjBZ4ʿ+k+˚h咦%t$x0H`g;Gbyy\Pd.[۔ˍd̤zSFlD<(RA>]YGkICOqzm@ PC)?TmMQ͋+ (^~ߋU)C Z^5J/D(DFrXÂ(Q(<Q8# Q(Qx% dwG)cB[Ĺrf|(DYXGH8œ1=8 ף'-QdžPQȋ*G Qnj}^"pίGf}>(w/VF\=#FAb:Xr(dQ@FFJ lGUG"%*/QQx D>ּ>qqxD!G0TC^V5:ƣ|mH /\p[d1jӧ EmX'tfee&^5^I^U&kBć[«`|lIi/2%\c(3qhu<▩l}K_QỤj4IP^'Tr16bQ5. 0:YKrȐ-p vhIYK+`+y%@m!_g+=oΘQ~`yYujZYu3%PBStfXV@҇:?wG7}fI*o X mn}AomRvX]Ojyb;T01W%JN)|K!R" +t e+iXvmijeHFiZZ#\S[B8Fh}T-oiN]'*tW}s=l,ǕG݌/0Z".D=d$PcV!##.XhgCinS>2=T.E֊sR"pImmۿ<֥Lku1'7$@J t3Vi@7vFz hPD Hi*EQ:N mu8C+f ~-]Htv \vTj-h,?/kD$⃈l"g2Mm dW~'d 2%jK! KX=%JE%Z^-sF!Uxq'd!î9Y1 /Ɍ(<"Cd@rXV1cɵr|HO~F[e Xƫ\ DLV@@yש͏s[ݶtZLF3M:~yf${Uwtx#?UájjytזjTH #װء$_T`9n`~~9s{ RS0ܐU PFUPSrz1wع uLǛLiM|2ߛ͢!o]26=cLzt-?ꙎɺTӹR1~O*';>Papk'z&x^~i싳,D_)a_)$_Pr QnE&$'Dc%li`IH)R$IEi]*EJQkxɢx"d.+󅴐`Y>(:5^b#$s̽/\H$V*Z]*J=w꒼nԑ_&l; F#4 [T=񵩾? q=`-+F=2<\jr&utmЋ7~9چ,rVdUf,V1v2= z@.vCx;vovn6l>z* /dhKl5/;}9l/ƳOb|6s㼑`F\Ԓa-$c]DFzü**4a~glGkm tnnm9gۚIN+M9֩{2!sf^ߖ2>Fr~f]?L ePFyb&3qɖhx$;K~Y-ؖW^A+v%Q!Ҿ'k|20GNٷXXVإZ#+;ce C"4]f?.S:ՅJb?f ~Ŧñ{ۜ] @ h;w=6[˜N`W]2 D=e,@`Г;y/;%Y2KٶhJ ӂ^{mʅ깰Oz[jO}ji]b8/C|6?̽swp qy`7Y |gکVNXRZZUڷm!)kw"Z+w:aFÐ_4 cِvs2v6f9hleVq5TWt`D15.%{VMbԣx31 2?܂'`S Cy 6Y8%dzݡsv|%mRO(Fb Z6H0L 4+j٭NwP{o< tXmrB۞;f@S;lUVq'Aܚ7r ->`l< ۇz۫i}~+i Z IW:e, xfSJόNڳ 1X{{Ut`W7O/TrrE.ŹH< V{}jk'DsNC{d%(7t:Y yF-v-dt:@4A+uvӚ PS"8C}ɮ[]WjG]˥gY-49Mʌ4b*nQ?\a'KwY !`4=‘qs["0BU6 ǥÃ+;91=7w#]sɧ FUJ\27DP#|Bj}8Tc`H'NӛOeY;ŽO'Z3GWyiU)$3}k2g5;Zw.w\j؝{j'< 4z\p,@##=,U*r0bgT)ȷطpgsTe>u"& CN?*vTO0yN[qk9erpS37ˢ}@EC8[hC yMnn,)_j{$we,,(A3HBYQU&PSIYWΎ2uT֖kNC:n0D4<3,15؎2b1i Ԙ!!"p+ -hR.*"|Q=(ԏiy`=AUup-i( 7#I#[2r{ *eV0C $;t"s2@tfⶃ,=Kq޿ۡBn|۩H,=W2<GKigU.E"/pE{3. &&2 OU%oZ[M-vlցtmR-TKZW]PcV| Ub%P&4^~Mt9,քpCOu Yu3KGSA5M &@0dދaZenu\%wLe Wh O/WKCm>'إ"+_=O<"}&lAؾaĚ@5u+jW[jun,tu+6ܸPYk̘mM;"oYIH#oKdf"scrrrthw^#E,25P!unьcc35}ViH󖕕2QR;,[,o3`_r{n}pb"^ y3i{Yu>we, ?TVY[mZdfo6w8%T 9,lco ;᥻-z9D+?FM"X2&ܩ]*~OF}m/| ^R{9,{ Ǽyr "ZMbYa[X%/Wi%feLF^I.{5^/ Jb>//;Mà :LZӢbYZsChbGl^*{˄W{0feIęH᭠GBf 7S__F6P@aw87BQܮ+=?}ѵr#Dc̹y| _avM7Q\TyjZhA{! ts`&`Hsѣ&iN-xpqڰ`7 m&in-]cޠO6ͺ.=͞fMi6jMbC60<6иHMF3ہvUlFKtԨ&FDxP|RwpXi@߉U+m~%>)""% ō"}W|XZQODhŗb[=rGr'K!YC͈DX> /?D(W$zDzRT{"!+"|O p"tNjE0.NO*]"]-v#Ĩa% ~CqzX 1 .N "E*cKLTֶj"c%;]zER*^V&T>thۇ$O[ac) K$MmЏ|}h{vq׽XK\8L<?\ԣcMsHtF3oMvWh׼ėߥxd?x0VN,ܯ#Bb;?Ѱ_8D[MR6$KVpwFZ$iǻķ;'$H q\݌of~I.vG@)oB*l"|뱞\FL^4DⲹGGz ^[]auӄt,_2g\0w_ wX N}hUg$ر $L{o"f2R RIښ9")'25$MZb'K:ry8)@22p WcSp:9Rp 6bހi\$ DO`J2` qr)XK#os`=ġl YC)DgSpʿIB9gMڎ#S#7 JSRYiirr`hЄjlb|ljdlOrnibdԤ41494ohdd>41nl҇%VMC l@/PI_%p"iΑɩ D:JK=SRAiՆwRCSH<65dG }jhߐjjhrlpx%)- {uSLJ&Gvɗ Ob9^3c8ɱS''~45CnlڿsbhhzuGcKCS(F8{}:RROFN An=w)şJVI24{bL fWyamJ$3X7S*!k~Yl/v3Bv:FʿtrJGYR(F3N5l 7-%ڲv@'N˔;I9@vUmɤ4G=w`{&E7[$1S\u)YMbYhWGOsR5 q FWJ惜R|KTh)A,I|sWCօaHG$?‘k((!U~X/ٝZ=^qL5[5W3O 2ùc\5c.oKvG(Sէ')?y|ĥ,1r:5jGkM W97S镟?.e!?77>3ܷ }}S=4c+ұwKwa :_ܣ|SNN)===p?W9[}} X[no㟄O`Q^N&{}{}C:ܼDIv}K}~{& 鍇zVnKwMwM}`X;4( y9W˭ƸcV߾kEvc-Zp5M%f276M&tGBAt@tfj6:-3Cs渮uKnlko-g>uw:os3j"0`{F$u]SSv BжI<9u=&H(49)2b'C;aL&ard$w=k͚:>_/ endstream endobj 16 0 obj 11772 endobj 17 0 obj <> endobj 18 0 obj <> stream x]n0@|!ʼnR@`HA̸ٞ',dzNm YXmA\:@* WnfB[ߗƳ<7w.6nC`bQn] F0S3?7#=wxX[~A(ZKVii6 AEȫt\.T(eXrL s9Ys.x?A3SDȏ\yd#{*2"N1^^5x.kJW;dO[ϐ_Q.ԗ TS+c/Ff+OMkMoggD{֍ .N`wi,z endstream endobj 19 0 obj <> endobj 20 0 obj <> stream xz{\[ב{ހ{I ޶ c0 2cFyI6k4vmzi6ޯݴMm+Ap~_ߕs̙3gfAtj:LR,aohOh[ FDU¬ܹ‰;wٹ5BtEdGáaBVG(".,>(FDoQl 7˰+lO"Y-il29~Y"!:^ OnϡL[W JfXNT5Z]Jj7Mf R#fҩ=Pxne#䳄ߡ7R u Zc'$dM#.cTP})89ܭB*t%!!=$ =| d/&ɘf<762ABGY1'ϻ()]5)96^ .2i|H̠Vq _G($9An 1E3%Za6/Co닿I6R]̗ƣx;?B)l +;O0 /s\t흛1$FϿO(،ꝛkI䥹&0Qg_yk>G.-1c,z^ch:\7AE:Pv;]Cg}d6bHvNciťs]Y>(5[zb\~˰Ԅ:~(݁Bci٥tXiEexL) P-8j:diHpCX+!ʧIjL~gF@,- w|BI5-]8"4&ksI1԰lO*VXW<$9,f#CQ1WS#YllL@yI'~uB H-Я zGb mwڈk|}h>7G 5T|3Ƙ ݽ]RkgoMRDe7}kKAb/ccG،԰1Uo.c6{FQXnLMLԝ6)il}UZ`GRK]l>F1FFQ]ZϋRXFŘGrRΓ꾩BY&bUfa[ܘ_n/7nYRke.%%F j 6y,5p㎖܂G(ݶsRջVrN:W: f \s<^{O3ll[þs" P,E҆Hlez9!r/'#Y 2N2tI%8.8zcM0}s}lj5_#[FJᆘNjzOB 97I[KO1JYE @\kOk JśkO dhEV)s= 1 vQEϟm#Gq + "[|':0P UULocYL `~BRs|@b 3 XZ5p9+3}vXcA}?_SOe? өМ:ܟ[SFRfRXu5%ކ?{Ni c> j&.@Lueg\`3H@F N~fzL`2e֧PY"Ncj}j Z{\^Z9AWЃ* zk1;PW52=HF PLlEguƾ5~u]wl1~).*U?C>8 н6p#)f S#*{e+O VM4#a<&iJj(PC@R^úMӤ^|HRZ@W?VyҫUyrK+U:NIzS,2c>jYjFڟv8I;$Ҁ6$Dک46MV9;V9l@ c,tdv!Pk"lfӘNEۖjVSES]qo\ = #X^,`q(oJl;W$Xm,Sm6؞Ϩ7}uV'*Z' pj%Pb|fClHfEGUJieMpsuj<ۖXȠ\ht,?*b5;~JffSѡV2Z-Qh\௹k7]hA MlBtk/6I꒼j>ZmSrG ^Ǎ-a~2ג}hJః֞egy(,' `@kN@ s,u6k4Ͱ ?q͌/m Cm1|kD1 khD%'t1?Le$, eTbN*t]dPI4(iA K}۱ӗlЧrT֜U-5v#| -MZѱΐ1K:Wp2*7tWuO}/?}?< ϧdXPhux}ao} \;zh}HiwK"׸eђ~_N- dd\RP2g FTPRjV"(;ZC:ԢG{+1׃QUaHaq֭V7R JI&gZG IE g_rTɧ{C6GQO#, FO.[m*M6K[ߘY5B֥Frwר4¾tؚtzإU@իbG ;9Z#odZPU3Fb$j?hRkҍFe$F"&0M`1ڬi P3XYM@J)\ogx00^+7%(]]*sR;W$V6gsWSJ#7~E)eտO88%zd\B!!70a;}r(WcmuCc1xj{aiZbS(UK̡,PV@E)x'UxUP"jS__9ak@s ^8I/y/V & 4lVjX,ʜt9NlX]^Bx /|_{G^y/ /m^hB2eWL/*} ɤc2iLjeb7y>'x-f4W2K,ϫ([疄kYGedb2׽p {uv ȬKw2ѕ<)/PEMBz/d822鼼.Jb߸2g%-dLtRNt0܊cDElX1̙a)@DZ*4֦4 g}ޔ`3R צԓ6,B9)(waZ3_q5t-{|m]]#5Vk1}[h H$}cX!ܧ,v5h0$Y=b,}q_anf*o|>,z~ŔͨXqΣ|eVippi詗G3i6/[[h ζ}7:թz湋ڬzkSo}}X|7vg9`;bcg ˀ,Plf(6Bs:<w-ٞUvTyJ*igWv} 7čف|N]@.|R4(q)=\N7@9pgr,~]0z+`y_CRʛcr Vn %}`nl+nls ~W閚>U p{coJ`ՂAZ]VձvMSmvոrլm:DMN!)B"(s9fLSxLLL!V&9*!Xx coR5v*HFK#')ɿ,IV[L Bk,tZmZj!گG&M0*-U1 j~X1x]:)0c^}U.+ s=J"_V5wS0e,IwoJ+@!{}qoXc} Ojm?/6ۖ=LrnӺy4*^&?az:T4s\9d5=F~ Ψx9~MYK ZR"\UB"?';Mq?U +rz\Ʃ֪}Sw 6)?g?ǓSbf)}I!:tb~ږ9MK B>$|1 ȝ$&&X5$ $a0/N; '8b*1,ӝ$̠Yz$̑7 +=/$a=fV"R֐lIXGj$BnǘSɢbO٪|qlXt8grZ\  SSĸS\{85s4‘LxعilG8/vF&v-e&Gdokx*B;ʜdRJ_8EĐ 'F-@(Z"9Qchx cb)v ePx*B(eXdxlqJO]LXFÑhtڷo3$BZ_ #c;Q=0]Ot׸b#(|db$/4L 8 ЕNLLd5Bg_v YRHNxIwDC! čj֓Bq)l !:eOb_DnMs9&!c>/.3FFHbvR2F-$)%n2EdyDǍ.seF-{j{܁ Y-I%љNKT杠y EL!ڌʳTݷg yӵ$8O <.2-;t"8?usnQُhߨܚ$u\dq"͜|2)qQra?"x 2=hǗCum<"#(Jmdpj2,rH4OBhj%N>Y!| cdfWpWkv-fPF wx1n' .Pavy|p\ |o^c#a .=P^{/ſ//r\8yu*s<ϮxAX  ur\x7ow#x&ww 3ο~8?q<}{>._2ĸԟpk̻g18Xb^^xB8zb]|9 iYpR}-.z& ̓)iOd.= ',d~<I>S$JW) OOOO'qHr O3hLAW= L>2ȉG؇{G= 0pGgA>sHVn<ƒemUm@fS3 ;= LLbNNO1{x0/HgdkOڣ=JӦ泐qzGrfB y"} o zc3 =.v+;K_gLc(pN=b ;*I5vDmF -~0z.I_쀭1@{= 𐞳`eD~: E"iAȣ(#˼eNӄDYF88h9-=XDh']0 endstream endobj 21 0 obj 8115 endobj 22 0 obj <> endobj 23 0 obj <> stream x]n0 G47GZm#2)@{=V 螾 endstream endobj 24 0 obj <> endobj 25 0 obj <> stream xݼy|TǕ?ZUޗۻZVwޖݒZRKHBuZZ XA63(1x@Rwmέ:uԷNUCAҚ} ! yͶq=^0bGHɭ۴cK94 X:8ׅZZ< BR;8yk uК>x[6;, +n<*S}7yo#qN|xt`/] d0ї & )*Fl,g+ǝџ_PK¥e告JwPCF4,bN#' g޹3x/[J3<:~z2(֣s藐K_qԅE"zocO>h˷ЯqzE-6e 6z8mJ6o<ؔh~g‚Mq:BT d0|F!Ms&S_2,.Z4rZ L*$2Kztt{hy: }t&>{i:xpJd`,@HhlJ(ֶ[i8y{/#xΜLh3f|0qj t@( .|+$s3mnkMZuw&;9_/]>Y*F ?tQB!X֙N{jY$C+$AKfKldbVٺ`/@ƇA6СO8h6yk+eZ/jazo@+*y9a pMfol(&)6 EX(=I"R_fΔF_h}<|ɰ0 Ɠ6iNJZҺ k2FdoDc v:Ahe#%/o:ٿ6Iav|Ii% Js`%U4P<'?1Iw.ZuuVg.Xg4Pʯvy6CDhI_o.RUm.4K HM:)GiAl&ς˷җ~(f 5TT",{;a0MJN7*Ya2όUۄbB>(MPa&Cۅlӷ/-T RB!/L"R%~:>0|$ѹ`-Xk9d!vWYdzXU~̣zPN,YA-X8QWxN^d.0ώN(4P)ևw„hj * \^P.cT>QNj /.,R~KG8dgsZy}W|ˉJ{`Q1R9DѼQa,/u5MdrV; r7걐Prq|XEl5 ;^նS)i;ϛts~pmdϽZgqmEֶv7k:/G NvMI~':14bzLm.GA Zcm.6M8sMrM1x*G3EQ=Q{T!Rb-bKؚ9e]W∗5ʈeP7@H /*T>T f&ʴXbج *KiKxk*e1Fbs{r,-=lUKWzmw#kʙf]:YX(d[͋vG *>l*ɶm}`KR&=Y`g!D_El\"NA;El8 gp,~~K3V**\_liUoULqsVc)q_E~c>|6jOik̴H>558yI yzUB *#Y@l  LCJ ´T_]k̳g xwݍcM ":Mt6~}k@k?)\Q>ȝ?k(Xt/mvdKjހtw2+@!%Qc'=R@+. kR8x>@V׺;Cs@C4Yq/[F%-"[P)+Ez^6б+0J{J|JU* [W_A [ >. ^) ^>-o~\H.RH i翟O|[?a}?6hmr8dvc;dN4Gyq槒V §`x:c*F%Y- 1 XmQbk+QF(y:FQ<ūأP}//GFGQ|*DWDcQNb{Q~C\c Q'E(6FI~e~~(}Q<Q(0TiR]_Gqвxl-:JiTn!fN}-tlrE_i>2̱(CIsDQC%9%ۢ?%?qLm>)OF(3x3C5#ͧmaQNEItOT[eYI^ E(Yjk"z2J`$:E(tz(;Ao\~fEd0SQLh<:&1U(2\Q53Z@RߦQyyqGWrgHfؠ J{za_Y{nޖ9wT $ʙR* b ) aXMG;g S b#YO87,ik]}s:[;méC9uéR-Ls-55aC1jJH*3ӴBe ~A`gt T1VUF|VصS~~5~j-B[xՏI?C yb2QC-`x /Dxy*7.y'npۜYj1E()5`Up h 6a6,iot6޿#':Ҽ+ad҂Cd,zXF>W+Qi<-έ!WB ׅvXVjL~ Vp 7|Y&e亴95[7MOM\L2Dž=3='`Zm }WdnGF590k}N@ 8$tSJ@hi7n>m$', 2;/`J0!L IpSPS0i?A~/84^6W(vkN̤ e&?G(|S!aW7WkU/ݧz7?b_zDY7Z=,ۣ9!R=">>5(Vb]WX|L} aɗ  =0ռ+Τ78|CpVV7v ?f~w_|v]Ej:ha]6~?W뫪?޷55@ vi?YY "wf?pX 2RjfҜnmZULRdB0eA VZ-Hot6 b Mׂ-g$=-A3\m lV6bg[W9Y [돱n w߽}}}}ݭ"dҬy[SCs+r>BVP0:bq7~nM,N+yuJge3BﲲoD Ky5RD#專^_Ą_D00]ޟ%8KW~TRe>f%f/~Y )]3;Vڌ+S7mS[q/IBRaB[Y#T3!H EYNk.f"*Dkwn.nsnwsZvav)!ݎ\5 @+iS 5"oqD搈WE#_s"!IQ$uĐH^ ~ 2">,DLO? Zg"y.]r@~ZEY2uCb*2NFC"QPumLic3"PsDlE(2MǤ"dV$Dx]$-q1!rA$g䔈aV"nYEQyHIM\-LH/xS,cϋd%$2]>mc/y 6e>OUԞz"M}^l{j ~p71tOG L֙wH1rR2X~ޅ\޵E4&nMXժD xFŰZRbjh3-CtqrƬ%8P3ZV`U3 zj)w%z_Կc,m#j |ըIۋ`#weTp[!9jyŇYeFB#BRh"4baCTrB%1Wl)9GLyy-*ۭݤj*g1~iORw5/r-!F}IĺP7.9bi9 e-9;]]ڸ6*&!rSh3Pc!.d'8< F7g Z!9)H AVZJ|Xrh+֖2/,*β2T5,6VN} !fBeET%D!#ϓ~YaoݱHBۻԍ+wgW&bx'Dv-il7gtfNA,-?7?ʺt ˡdw]Wb̎DKpB(A&sWk1O,cNjqL.ǃ%'szP͗&G 橧ZPeP1o(=rP脞8/CIتue.ۜ`U%LeU;Qf>QNcQ,MoSf.39~So<*,p0+_|¡<_%u&8 ՋvmϩZRf.&~Fm3>Z!VꫭVUygzeR &>NdWT:`Pܿg^nzzō|b(vI]  m@C5"CkЯF>L/)DhhGTjU C [ƹ2YoՌF\(<0ߎ%b-4. cx|_噓x׺z7jS+&'yF&j^|kl@~-*kn;S˷NHטL͑r3W.MόP'7.:k,\;fӻhZ'9'gq?nKp_ROe˞Ottܟ~L̟A"2}> jJ`|>sL=Pd`*k crCY@(5ĕ" _:yxxe*i/]i-*ZRV/)*jۋ)yf1Pp =Mw_\;>h| )zػe2aa,^7pOrh"nb37E.\!XSQUV;jqw{i1;9sjl:y͂y{u%us1|IT:syy=`A[D=-"Q$&ē0 _JD}Gy31ucء m8P3L0E w^L5 P-;vU#|˭o=O>My4;c3wW~JNt i0&4'9 ьTo?;,bgvbe;{E>W (qsR g);J 8MRvYVYi)x)lȆ%I510f8lT8MEĩcU m@Y [*-Ģԙe>*Qyf >]_v0RFu&_}t'/OU>쟳++?6>~b'ޓwƳ7%~#=M8?ӭ$k9䘧܏8­@{{lvrL;I,$+ k7 + Bck2_IimF@a`4dp ۪[uznrΪP8Vg3B JitA/\<ִp\;t:K+ #:uDcFF(xdv;1kI;n؏R;SvqM:P1 ;bvjE1Ja'vɎv׎vSPAqʈ 3r#"p$c[. ')0=!00}Lw7z@; pHĭHxן~(!|驷w>ҽ4}4K#{B[F< #Je2 &ɥB[$aT4QDj""% 1Y,*Vɩ"6[V{!g[Mq$2꺑u3Xz &c3dύ 2(}9M}YUsM_>tzb?ItkjjڡR+[UJZ*+Ҿ4``/oE6__gl:R8mMaljJ3'h99z!|VbNEIYѦs9L- N0r~mTtVW\eԉΰ:deV%F:){ VT2uffwmj u8 Gm/bB䫊@8^xyH&'u׬ӿ<Vrur%~%Yj5QCsI”յuLUۭe\jܯWU22$BI V*Uި0[TJJ4(<~^qIQ(C*Lu)ehKr%\ ^]Sk.EJ*K :욄)PX%鰲F%K,fD|K†ym&{̶w"glq#2(YFk)p>S'Ye[8oDwG5&z/:WtBHgvkNjxk}ܹ6kE*u_jC O?Op??mE`Vk*eD(wcYnD=7OF}_zz|MSO/,8uMIw7.%\1E=輾BL{+kzB(R}\П'SzՄ\ѳZE ^&X*-#?Y7M/$t|V_+>=e/ ~<]qOI [:λ1 m94}+~㭘ï-Ξ:WԺ yisS3l{)6og 95sPtғ_qңc>znGԜ{sҮc8 ٖY=@^H?ŠL ubh]9L83Gtk sƮdJ+ ~1D՝Nw=]~B~ /ɆLP"̜ݠt:Ba`REP{ Z"".n3|4Z vkuUf*+$,#ߧ{heI,} ըZ&RudjS>;"j$ x`byycFd^jʂ8$<@w/]נ!/C#"GZqy4{a9P+ojj|s{CV>m4-#ma6\{URǺGyx<{+[yf`֤^PXҍous|jb&k!H/;,ڨ\5X#lb[Am$A1x_PA\ ˡ Jg J_{6Os]P@E.+JtSҊ`cPd`Ld Jݽ Zv_>AE {KTJP&Ð`~Ҙ1U?^hN|JE"ƥ'Oi^C&Գ0V= vsBCR7Dύ[_xXs(nd 5ng< Dy05(*uSd{e*$4En`~w0wP(hiS_ QJnD:7~4)"e4lQ8et.u:w98/9r8U7W̰(g^wL҉O8{aT{y<|)uYdf_rN\w+zx0lg\‰:q)]:,Ok grwklc9\#QGs-uJN"814-ڍTBV9dJiIsi@M)Y '; ]R7cuW}D}IUۺZmeFz,_&_9lΧnhb]U7ELÆk_>w-`jt[V -{0c:/qm_ x2}-"CFhH1L7ج)pu)V@F$X cޫ3gO( #J(V+nVJ;c4+& MX iWeiyK׊簀 .X˷D+8VdL}&{8Y#gqzAA79ȝ;e~j Y!r$ ~)rgv~*)>cʼnxw~vpzkrwN@ mXZ)CZ}6fZv-r IE &剑wy[ S$֞)$ ɳ\Q8&U% lc P`qe.gH&)eFp%2n~sS>C#B鑽<0jw8vF v ێt#ӿL=GAʕOٕ ,F_4^DIav=:j1%A@9{pgg bF,cv }UBe e@:(}\8 f|XcNh7rIuTXdKsaغ}m.5?pax >9R7h۳vG{|z#3]h "$ 2jE94th\\ 0ad$fXѱ\zZ9a&cyowNP5ViO |5vNͨ|cD1vY#oѧtxGmOK4~CœFtU6M1ZKR3΀ gCaIDVohĊ1kx{n'zX:uRxናNƝI'7O.I']\Y=)yU.KUќI\VGv;zzƝ~ߜ4E2pS3M ;Ǩ8%_98A]=dNve9i`5B{ O>o|wSFj8RlU5<*|چ=ќySNl-:ϟ146h=:/ 9omM՛=v2ԄNH1`c݇0 yA뗭/GOkܐ;OuLET-|K[[-7[¡ oal1is{:9xLujǵJL G|MMf$vCQ#{ %]*KT=f3ϦlY^{mbݛVO|yM~"`+oF-h|BtЂL}{Z@wYi‘ YhdqaZ–յؘe\;n7'>yRCNA.f #S@,D JD "Jkכo@(GJ#R$IFH]iduI\5ıD,\n1JTDT7SC3 > ծ]S'XjWooY^TQeG-=T,>~PXQkwY/0?B+lq[<4Y2ӧwՕ_ÿY|XYNq-ӗ.M`K~F VGҺ|Ga.w3yA㹋5bYFGH&)}&tŸ9ȎZ, 0QEa`ܨwR@W]6 eVA.W!+vdjث2q-/L\vݙbcV Rc% 22qtLE^Ɣs()(Y+̺L\ L\rݙU'2qL\R\$7֏9~[^ZZ?f`Kwh-%6yGׯ n/Y~@ܻ|`tڶu[7bP,g+FhӲϒyG6nAc}Eޅ[֔@֭[%K-޾-[]~̀f`t_>m||`ۀwIЖK2kdl|@u[%7u Bo!h}c[WoX3&ڷnt``3Vׯ[f`x$BUc7ݪ$5h ~w xR|4E gcJKdM|6Y%h1s>Eh-j:xA_|UR*~^zTvr mf!oy>Q9E[urK˼Ӕe@\5m?Kk>ڧkdtӜ >܂~lO5`ܺm3ӲA95bF 9(w덣dmܒfЭY+gLJ"=?kJ~L1-jv@A(ǡ _J)3[2O6tftm2.g |z/CPF묑y ˒6oKvOy ?7_jf@Yj5\;F8:#1{ 8|Oi_ ^Tδ&U$~(_N?e]ICIy[anM/L&+[hd"5C!nO Yҙ(4[Bcxl -B4:jC􃖆d*Z?+{ endstream endobj 26 0 obj 16314 endobj 27 0 obj <> endobj 28 0 obj <> stream x]n0E .E # ^-dvĒ@ }x-ЅCrft~%u޻tSGwDW]9IC~]e?*IaOG?}0GsmōʒV;:oT}8SH~2T]s.YeYV]אּL9ևPBF2`+\Sp_Ka#c,1/oȯ\5;2茜_ ӿlѿGЗoїFEaq}jk ӿ7l_ncF  ܛ./ā۠EM_H -<--F#cwgUw><6yL0qfd&& endstream endobj 29 0 obj <> endobj 30 0 obj <> stream xVoS?=َI/I`ߋ !$uŗ%(_Rjl //BH5mWhbmQJimu:~ iuXU&UZ ms_j%rDZ%p xhUF XĬIɟ~0>9z Cow@a9t@7ܓ:HF3pl XAy-ʶHPs{Qlx_x,*g=îK(Qc9^tzC^pC;x` {a'8q\+8|&U8 ކV>lTpX@ %z[%ntrg@;dik_:qS:쟱v?؝պ`@1O.SGwɾ>; sgW(Em1>O51gF|uS9x.I{Fz KXT`g$htV-zŭζ~//`~,33L[Й3N%c *3ǔ(M$.Ng 85nh4Kj,M07AW&VUgcK %J=T3M9Ϛ(feӚFoCe:y.Inlc[4޵y e S(PkZMt1iQ)[Н+\[y03\J2Uj|>ʛ)A-yccE)I 'cfjiZD-M&gN(7i7;39_$rUm%? ^;F7;DƛEE_|ˤƹam8!1RoRPRm&=9{w,N V۞;Pہr*C+ Z%&jf Pa.],Z&]X-\.D%+T+T(pՅ9ƻ}JgUyc.*x[sG^?G:=Ceߒ뇽ֵt?Xв)qc<.TֿUWӤ v\sEmhįCu)S>"tIݼIh0 pa5Iflr>~1)P^sC;M`*͚ZaKc 6@Xܔk_R)J %'rLY=!-7JrZa#Zח&_T[<+!z虭N/\ Z%T*0ffմ&\RU^~k#|E,%di+wwssY|\bK6[qEp5MY-fOto2{#W5wN%;Nz_%INKk lyiV+(ZdYٖ\YDu)a=@t5-HI] ?h?ʽsϝޜ=]ȅwӇʱ O8Fw~8…tɗJ#F&СAZ}a; /=拿p ~O֐(9N>n-Pqw-S%OV12TpNoPkT,ZѻzOܕBXr4#I=:P#.GwFcy(&)r"Eņzo=pXdžph@ltBNv @u-cYthER8)H7NIqO$r8CɁaP840<"RrtaHTܘNMGڷ ZØ:$ԨK.X{ө,ׂA/tW)#r4"'FEV9"Qtr8>N|vA!% I;2D-q9z\TqEq(Z3 =ay;R|6%/@'wȶ+5x}7 endstream endobj 31 0 obj 2281 endobj 32 0 obj <> endobj 33 0 obj <> stream x]j0yۮ ",j 1|&㶅ofbRukO^,;tF9XI=ڐSU$ n- sQ-6z;8N>.jL`> endobj 35 0 obj <> endobj 36 0 obj <> /ProcSet[/PDF/Text/ImageC/ImageI/ImageB] >> endobj 1 0 obj <>/Contents 2 0 R>> endobj 4 0 obj <>/Contents 5 0 R>> endobj 9 0 obj <> endobj 37 0 obj <> endobj 38 0 obj < /Creator /Producer /CreationDate(D:20110629142047+02'00')>> endobj xref 0 39 0000000000 65535 f 0000074346 00000 n 0000000019 00000 n 0000004490 00000 n 0000074489 00000 n 0000004511 00000 n 0000007942 00000 n 0000007963 00000 n 0000018806 00000 n 0000074632 00000 n 0000018827 00000 n 0000030559 00000 n 0000030582 00000 n 0000030788 00000 n 0000031254 00000 n 0000031580 00000 n 0000043439 00000 n 0000043462 00000 n 0000043665 00000 n 0000044126 00000 n 0000044446 00000 n 0000052648 00000 n 0000052670 00000 n 0000052880 00000 n 0000053274 00000 n 0000053536 00000 n 0000069937 00000 n 0000069960 00000 n 0000070156 00000 n 0000070677 00000 n 0000071052 00000 n 0000073419 00000 n 0000073441 00000 n 0000073632 00000 n 0000073976 00000 n 0000074174 00000 n 0000074247 00000 n 0000074737 00000 n 0000074834 00000 n trailer < ] /DocChecksum /80C16F0BF904BC6811565433EA7C4F20 >> startxref 75067 %%EOF trunk-2018.02b/examples/triax-tutorial/triax-table000066400000000000000000000001371324306050200221340ustar00rootroot00000000000000!OMP_NUM_THREADS description num_spheres compFricDegree 1 sim1 500 4 1 sim2 500 8 1 sim3 500 16trunk-2018.02b/examples/tunnel-pack.py000066400000000000000000000027121324306050200176010ustar00rootroot00000000000000#!/usr/bin/python # -*- coding: utf-8 -*- """Simple script to create tunnel with random dense packing of spheres. The tunnel is difference between an axis-aligned box and cylinder, or which axis is going through the bottom wall (-z) of the box. The tunnel hole is oriented along +y, the face is in the xz plane. The first you run this scipt, a few minutes is neede to generate the packing. It is saved in /tmp/triaxPackCache.sqlite and at next time it will be only loaded (fast). """ # set some geometry parameters: domain box size, tunnel radius, radius of particles boxSize=Vector3(5,8,5) tunnelRad=2 rSphere=.1 # construct spatial predicate as difference of box and cylinder: # (see examples/test/pack-predicates.py for details) # # https://yade-dem.org/doc/yade.pack.html?highlight=inalignedbox#yade._packPredicates.inAlignedBox # https://yade-dem.org/doc/yade.pack.html?highlight=incylinder#yade._packPredicates.inCylinder pred=pack.inAlignedBox((-.5*boxSize[0],-.5*boxSize[1],0),(.5*boxSize[0],.5*boxSize[1],boxSize[2])) - pack.inCylinder((-.5*boxSize[0],0,0),(.5*boxSize[0],0,0),tunnelRad) # Use the predicate to generate sphere packing inside # # https://yade-dem.org/doc/yade.pack.html?highlight=randomdensepack#yade.pack.randomDensePack sp=SpherePack() sp=pack.randomDensePack(pred,radius=rSphere,rRelFuzz=.3,memoizeDb='/tmp/triaxPackCache.sqlite',spheresInCell=3000,returnSpherePack=True) sp.toSimulation() # to see it from yade import qt qt.Controller() qt.View() trunk-2018.02b/gui/000077500000000000000000000000001324306050200137525ustar00rootroot00000000000000trunk-2018.02b/gui/CMakeLists.txt000066400000000000000000000101511324306050200165100ustar00rootroot00000000000000IF(${ENABLE_GUI}) INCLUDE_DIRECTORIES(${QGLVIEWER_INCLUDE_DIR}) IF(USE_QT5) SET(CMAKE_AUTOMOC ON) SET(_GLViewer_SOURCE_FILES qt5/GLViewer.cpp;qt5/_GLViewer.cpp;qt5/OpenGLManager.cpp;qt5/GLViewerDisplay.cpp;qt5/GLViewerMouse.cpp) ADD_LIBRARY(_GLViewer SHARED ${_GLViewer_SOURCE_FILES}) SET_TARGET_PROPERTIES(_GLViewer PROPERTIES PREFIX "") TARGET_LINK_LIBRARIES(_GLViewer Qt5::Widgets Qt5::Xml Qt5::OpenGL ${GLUT_LIBRARY} ${OPENGL_LIBRARY} ${QGLVIEWER_LIBRARIES} ${Boost_LIBRARIES} ${PYTHON_LIBRARIES}) IF(GL2PS_FOUND AND ENABLE_GL2PS) TARGET_LINK_LIBRARIES(_GLViewer ${GL2PS_LIBRARIES}) ENDIF(GL2PS_FOUND AND ENABLE_GL2PS) INSTALL(TARGETS _GLViewer DESTINATION ${YADE_PY_PATH}/yade/qt) FILE(GLOB filesPYQT "${CMAKE_CURRENT_SOURCE_DIR}/qt5/*.py") INSTALL(FILES ${filesPYQT} DESTINATION ${YADE_PY_PATH}/yade/qt) EXECUTE_PROCESS( COMMAND "pyrcc5" "-o" "${CMAKE_BINARY_DIR}/img_rc.py" "img.qrc" WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/qt5 RESULT_VARIABLE rv ) EXECUTE_PROCESS( COMMAND "pyuic5" "-o" "${CMAKE_BINARY_DIR}/ui_controller.py" "controller.ui" WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/qt5 RESULT_VARIABLE rv ) INSTALL(FILES ${CMAKE_BINARY_DIR}/img_rc.py ${CMAKE_BINARY_DIR}/ui_controller.py DESTINATION ${YADE_PY_PATH}/yade/qt) TARGET_LINK_LIBRARIES(_GLViewer Qt5::Widgets Qt5::Xml Qt5::OpenGL ${GLUT_LIBRARY} ${OPENGL_LIBRARY} ${QGLVIEWER_LIBRARIES} ${Boost_LIBRARIES} ${PYTHON_LIBRARIES}) IF(GL2PS_FOUND AND ENABLE_GL2PS) TARGET_LINK_LIBRARIES(_GLViewer ${GL2PS_LIBRARIES}) ENDIF(GL2PS_FOUND AND ENABLE_GL2PS) INSTALL(TARGETS _GLViewer DESTINATION ${YADE_PY_PATH}/yade/qt) FILE(GLOB filesPYQT "${CMAKE_CURRENT_SOURCE_DIR}/qt5/*.py") INSTALL(FILES ${filesPYQT} DESTINATION ${YADE_PY_PATH}/yade/qt) EXECUTE_PROCESS( COMMAND "pyrcc5" "-o" "${CMAKE_BINARY_DIR}/img_rc.py" "img.qrc" WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/qt5 RESULT_VARIABLE rv ) EXECUTE_PROCESS( COMMAND "pyuic5" "-o" "${CMAKE_BINARY_DIR}/ui_controller.py" "controller.ui" WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/qt5 RESULT_VARIABLE rv ) INSTALL(FILES ${CMAKE_BINARY_DIR}/img_rc.py ${CMAKE_BINARY_DIR}/ui_controller.py DESTINATION ${YADE_PY_PATH}/yade/qt) ELSE(USE_QT5) INCLUDE(${QT_USE_FILE}) SET(_GLViewer_MOC_HEADERS qt4/GLViewer.hpp;qt4/OpenGLManager.hpp) SET(_GLViewer_SOURCE_FILES qt4/GLViewer.cpp;qt4/_GLViewer.cpp;qt4/OpenGLManager.cpp;qt4/GLViewerDisplay.cpp;qt4/GLViewerMouse.cpp) QT4_WRAP_CPP(_GLViewer_MOC_OUTFILES ${_GLViewer_MOC_HEADERS}) ADD_LIBRARY(_GLViewer SHARED ${_GLViewer_SOURCE_FILES} ${_GLViewer_MOC_OUTFILES}) SET_TARGET_PROPERTIES(_GLViewer PROPERTIES PREFIX "") TARGET_LINK_LIBRARIES(_GLViewer ${GLUT_LIBRARY} ${OPENGL_LIBRARY} ${QT_LIBRARIES} ${QGLVIEWER_LIBRARIES} ${Boost_LIBRARIES} ${PYTHON_LIBRARIES}) IF(GL2PS_FOUND AND ENABLE_GL2PS) TARGET_LINK_LIBRARIES(_GLViewer ${GL2PS_LIBRARIES}) ENDIF(GL2PS_FOUND AND ENABLE_GL2PS) INSTALL(TARGETS _GLViewer DESTINATION ${YADE_PY_PATH}/yade/qt) FILE(GLOB filesPYQT "${CMAKE_CURRENT_SOURCE_DIR}/qt4/*.py") INSTALL(FILES ${filesPYQT} DESTINATION ${YADE_PY_PATH}/yade/qt) EXECUTE_PROCESS( COMMAND "pyrcc4" "-o" "${CMAKE_BINARY_DIR}/img_rc.py" "img.qrc" WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/qt4 RESULT_VARIABLE rv ) EXECUTE_PROCESS( COMMAND "pyuic4" "-o" "${CMAKE_BINARY_DIR}/ui_controller.py" "controller.ui" WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/qt4 RESULT_VARIABLE rv ) INSTALL(FILES ${CMAKE_BINARY_DIR}/img_rc.py ${CMAKE_BINARY_DIR}/ui_controller.py DESTINATION ${YADE_PY_PATH}/yade/qt) ENDIF(USE_QT5) ENDIF(${ENABLE_GUI}) trunk-2018.02b/gui/qt4/000077500000000000000000000000001324306050200144625ustar00rootroot00000000000000trunk-2018.02b/gui/qt4/GLViewer.cpp000066400000000000000000000555571324306050200166730ustar00rootroot00000000000000/************************************************************************* * Copyright (C) 2004 by Olivier Galizzi * * olivier.galizzi@imag.fr * * Copyright (C) 2005 by Janek Kozicki * * cosurgi@berlios.de * * * * This program is free software; it is licensed under the terms of the * * GNU General Public License v2 or later. See file LICENSE for details. * *************************************************************************/ #include"GLViewer.hpp" #include"OpenGLManager.hpp" #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef YADE_GL2PS #include #endif static unsigned initBlocked(State::DOF_NONE); CREATE_LOGGER(GLViewer); GLLock::GLLock(GLViewer* _glv): boost::try_mutex::scoped_lock(Omega::instance().renderMutex), glv(_glv){ glv->makeCurrent(); } GLLock::~GLLock(){ glv->doneCurrent(); } #define _W3 setw(3)<emitCloseView(viewId); e->accept(); } GLViewer::GLViewer(int _viewId, const shared_ptr& _renderer, QGLWidget* shareWidget): QGLViewer(/*parent*/(QWidget*)NULL,shareWidget), renderer(_renderer), viewId(_viewId) { isMoving=false; drawGrid=0; drawScale=true; timeDispMask=TIME_REAL|TIME_VIRT|TIME_ITER; cut_plane = 0; cut_plane_delta = -2; gridSubdivide = false; resize(550,550); last=-1; if(viewId==0) setWindowTitle("Primary view"); else setWindowTitle(("Secondary view #"+boost::lexical_cast(viewId)).c_str()); show(); mouseMovesCamera(); manipulatedClipPlane=-1; if(manipulatedFrame()==0) setManipulatedFrame(new qglviewer::ManipulatedFrame()); xyPlaneConstraint=shared_ptr(new qglviewer::LocalConstraint()); manipulatedFrame()->setConstraint(NULL); setKeyDescription(Qt::Key_Return,"Run simulation."); setKeyDescription(Qt::Key_A,"Toggle visibility of global axes."); setKeyDescription(Qt::Key_C,"Set scene center so that all bodies are visible; if a body is selected, center around this body."); setKeyDescription(Qt::Key_C & Qt::AltModifier,"Set scene center to median body position (same as space)"); setKeyDescription(Qt::Key_D,"Toggle time display mask"); setKeyDescription(Qt::Key_G,"Toggle grid visibility; g turns on and cycles"); setKeyDescription(Qt::Key_G & Qt::ShiftModifier ,"Hide grid."); setKeyDescription(Qt::Key_M, "Move selected object."); setKeyDescription(Qt::Key_X,"Show the xz [shift: xy] (up-right) plane (clip plane: align normal with +x)"); setKeyDescription(Qt::Key_Y,"Show the yx [shift: yz] (up-right) plane (clip plane: align normal with +y)"); setKeyDescription(Qt::Key_Z,"Show the zy [shift: zx] (up-right) plane (clip plane: align normal with +z)"); setKeyDescription(Qt::Key_Period,"Toggle grid subdivision by 10"); setKeyDescription(Qt::Key_S,"Save QGLViewer state to /tmp/qglviewerState.xml"); setKeyDescription(Qt::Key_T,"Switch orthographic / perspective camera"); setKeyDescription(Qt::Key_O,"Set narrower field of view"); setKeyDescription(Qt::Key_P,"Set wider field of view"); setKeyDescription(Qt::Key_R,"Revolve around scene center"); setKeyDescription(Qt::Key_V,"Save PDF of the current view to /tmp/yade-snapshot-0001.pdf (whichever number is available first). (Must be compiled with the gl2ps feature.)"); setPathKey(-Qt::Key_F1); setPathKey(-Qt::Key_F2); setKeyDescription(Qt::Key_Escape,"Manipulate scene (default)"); setKeyDescription(Qt::Key_F1,"Manipulate clipping plane #1"); setKeyDescription(Qt::Key_F2,"Manipulate clipping plane #2"); setKeyDescription(Qt::Key_F3,"Manipulate clipping plane #3"); setKeyDescription(Qt::Key_1,"Make the manipulated clipping plane parallel with plane #1"); setKeyDescription(Qt::Key_2,"Make the manipulated clipping plane parallel with plane #2"); setKeyDescription(Qt::Key_2,"Make the manipulated clipping plane parallel with plane #3"); setKeyDescription(Qt::Key_1 & Qt::AltModifier,"Add/remove plane #1 to/from the bound group"); setKeyDescription(Qt::Key_2 & Qt::AltModifier,"Add/remove plane #2 to/from the bound group"); setKeyDescription(Qt::Key_3 & Qt::AltModifier,"Add/remove plane #3 to/from the bound group"); setKeyDescription(Qt::Key_0,"Clear the bound group"); setKeyDescription(Qt::Key_7,"Load [Alt: save] view configuration #0"); setKeyDescription(Qt::Key_8,"Load [Alt: save] view configuration #1"); setKeyDescription(Qt::Key_9,"Load [Alt: save] view configuration #2"); setKeyDescription(Qt::Key_Space,"Center scene (same as Alt-C); clip plane: activate/deactivate"); centerScene(); } bool GLViewer::isManipulating(){ return isMoving || manipulatedClipPlane>=0; } void GLViewer::resetManipulation(){ mouseMovesCamera(); setSelectedName(-1); isMoving=false; manipulatedClipPlane=-1; } void GLViewer::startClipPlaneManipulation(int planeNo){ assert(planeNonumClipPlanes); resetManipulation(); mouseMovesManipulatedFrame(xyPlaneConstraint.get()); manipulatedClipPlane=planeNo; const Se3r se3(renderer->clipPlaneSe3[planeNo]); manipulatedFrame()->setPositionAndOrientation(qglviewer::Vec(se3.position[0],se3.position[1],se3.position[2]),qglviewer::Quaternion(se3.orientation.x(),se3.orientation.y(),se3.orientation.z(),se3.orientation.w())); string grp=strBoundGroup(); displayMessage("Manipulating clip plane #"+boost::lexical_cast(planeNo+1)+(grp.empty()?grp:" (bound planes:"+grp+")")); } string GLViewer::getState(){ QString origStateFileName=stateFileName(); string tmpFile=Omega::instance().tmpFilename(); setStateFileName(QString(tmpFile.c_str())); saveStateToFile(); setStateFileName(origStateFileName); LOG_WARN("State saved to temp file "<>ss; ret+=" "+ss;}; in.close(); boost::filesystem::remove(boost::filesystem::path(tmpFile)); return ret; } void GLViewer::setState(string state){ string tmpFile=Omega::instance().tmpFilename(); std::ofstream out(tmpFile.c_str()); if(!out.good()){ LOG_ERROR("Error opening temp file `"<key()==Qt::Key_A){ toggleAxisIsDrawn(); return; } else if(e->key()==Qt::Key_Escape){ if(!isManipulating()){ setSelectedName(-1); return; } else { resetManipulation(); displayMessage("Manipulating scene."); } } else if(e->key()==Qt::Key_Space){ if(manipulatedClipPlane>=0) {displayMessage("Clip plane #"+boost::lexical_cast(manipulatedClipPlane+1)+(renderer->clipPlaneActive[manipulatedClipPlane]?" de":" ")+"activated"); renderer->clipPlaneActive[manipulatedClipPlane]=!renderer->clipPlaneActive[manipulatedClipPlane]; } else{ centerMedianQuartile(); } } /* function keys */ else if(e->key()==Qt::Key_F1 || e->key()==Qt::Key_F2 || e->key()==Qt::Key_F3 /* || ... */ ){ int n=0; if(e->key()==Qt::Key_F1) n=1; else if(e->key()==Qt::Key_F2) n=2; else if(e->key()==Qt::Key_F3) n=3; assert(n>0); int planeId=n-1; if(planeId>=renderer->numClipPlanes) return; if(planeId!=manipulatedClipPlane) startClipPlaneManipulation(planeId); } /* numbers */ else if(e->key()==Qt::Key_0 && (e->modifiers() & Qt::AltModifier)) { boundClipPlanes.clear(); displayMessage("Cleared bound planes group.");} else if(e->key()==Qt::Key_1 || e->key()==Qt::Key_2 || e->key()==Qt::Key_3 /* || ... */ ){ int n=0; if(e->key()==Qt::Key_1) n=1; else if(e->key()==Qt::Key_2) n=2; else if(e->key()==Qt::Key_3) n=3; assert(n>0); int planeId=n-1; if(planeId>=renderer->numClipPlanes) return; // no such clipping plane if(e->modifiers() & Qt::AltModifier){ if(boundClipPlanes.count(planeId)==0) {boundClipPlanes.insert(planeId); displayMessage("Added plane #"+boost::lexical_cast(planeId+1)+" to the bound group: "+strBoundGroup());} else {boundClipPlanes.erase(planeId); displayMessage("Removed plane #"+boost::lexical_cast(planeId+1)+" from the bound group: "+strBoundGroup());} } else if(manipulatedClipPlane>=0 && manipulatedClipPlane!=planeId) { const Quaternionr& o=renderer->clipPlaneSe3[planeId].orientation; manipulatedFrame()->setOrientation(qglviewer::Quaternion(o.x(),o.y(),o.z(),o.w())); displayMessage("Copied orientation from plane #1"); } } else if(e->key()==Qt::Key_7 || e->key()==Qt::Key_8 || e->key()==Qt::Key_9){ int nn=-1; if(e->key()==Qt::Key_7)nn=0; else if(e->key()==Qt::Key_8)nn=1; else if(e->key()==Qt::Key_9)nn=2; assert(nn>=0); size_t n=(size_t)nn; if(e->modifiers() & Qt::AltModifier) saveDisplayParameters(n); else useDisplayParameters(n); } /* letters alphabetically */ else if(e->key()==Qt::Key_C && (e->modifiers() & Qt::AltModifier)){ displayMessage("Median centering"); centerMedianQuartile(); } else if(e->key()==Qt::Key_C){ // center around selected body if(selectedName() >= 0 && (*(Omega::instance().getScene()->bodies)).exists(selectedName())) setSceneCenter(manipulatedFrame()->position()); // make all bodies visible else centerScene(); } else if(e->key()==Qt::Key_D &&(e->modifiers() & Qt::AltModifier)){ Body::id_t id; if((id=Omega::instance().getScene()->selectedBody)>=0){ const shared_ptr& b=Body::byId(id); b->setDynamic(!b->isDynamic()); LOG_INFO("Body #"<isDynamic()?"":"NOT")<<" dynamic"); } } else if(e->key()==Qt::Key_D) {timeDispMask+=1; if(timeDispMask>(TIME_REAL|TIME_VIRT|TIME_ITER))timeDispMask=0; } else if(e->key()==Qt::Key_G) { if(e->modifiers() & Qt::ShiftModifier){ drawGrid=0; return; } else drawGrid++; if(drawGrid>=8) drawGrid=0; } else if (e->key()==Qt::Key_M && selectedName() >= 0){ if(!(isMoving=!isMoving)){ displayMessage("Moving done."); if (last>=0) {Body::byId(Body::id_t(last))->state->blockedDOFs=initBlocked; last=-1;} mouseMovesCamera();} else{ displayMessage("Moving selected object"); long selection = Omega::instance().getScene()->selectedBody; initBlocked=Body::byId(Body::id_t(selection))->state->blockedDOFs; last=selection; Body::byId(Body::id_t(selection))->state->blockedDOFs=State::DOF_ALL; Quaternionr& q = Body::byId(selection)->state->ori; Vector3r& v = Body::byId(selection)->state->pos; manipulatedFrame()->setPositionAndOrientation(qglviewer::Vec(v[0],v[1],v[2]),qglviewer::Quaternion(q.x(),q.y(),q.z(),q.w())); mouseMovesManipulatedFrame();} } else if (e->key() == Qt::Key_T) camera()->setType(camera()->type()==qglviewer::Camera::ORTHOGRAPHIC ? qglviewer::Camera::PERSPECTIVE : qglviewer::Camera::ORTHOGRAPHIC); else if(e->key()==Qt::Key_O) camera()->setFieldOfView(camera()->fieldOfView()*0.9); else if(e->key()==Qt::Key_P) camera()->setFieldOfView(camera()->fieldOfView()*1.1); else if(e->key()==Qt::Key_R){ // reverse the clipping plane; revolve around scene center if no clipping plane selected if(manipulatedClipPlane>=0 && manipulatedClipPlanenumClipPlanes){ /* here, we must update both manipulatedFrame orientation and renderer->clipPlaneSe3 orientation in the same way */ Quaternionr& ori=renderer->clipPlaneSe3[manipulatedClipPlane].orientation; ori=Quaternionr(AngleAxisr(Mathr::PI,Vector3r(0,1,0)))*ori; manipulatedFrame()->setOrientation(qglviewer::Quaternion(qglviewer::Vec(0,1,0),Mathr::PI)*manipulatedFrame()->orientation()); displayMessage("Plane #"+boost::lexical_cast(manipulatedClipPlane+1)+" reversed."); } else { camera()->setRevolveAroundPoint(sceneCenter()); } } else if(e->key()==Qt::Key_S){ LOG_INFO("Saving QGLViewer state to /tmp/qglviewerState.xml"); setStateFileName("/tmp/qglviewerState.xml"); saveStateToFile(); setStateFileName(QString::null); } else if(e->key()==Qt::Key_L){ LOG_INFO("Loading QGLViewer state from /tmp/qglviewerState.xml"); setStateFileName("/tmp/qglviewerState.xml"); restoreStateFromFile(); setStateFileName(QString::null); } else if(e->key()==Qt::Key_X || e->key()==Qt::Key_Y || e->key()==Qt::Key_Z){ int axisIdx=(e->key()==Qt::Key_X?0:(e->key()==Qt::Key_Y?1:2)); if(manipulatedClipPlane<0){ qglviewer::Vec up(0,0,0), vDir(0,0,0); bool alt=(e->modifiers() && Qt::ShiftModifier); up[axisIdx]=1; vDir[(axisIdx+(alt?2:1))%3]=alt?1:-1; camera()->setViewDirection(vDir); camera()->setUpVector(up); centerMedianQuartile(); } else{ // align clipping normal plane with world axis // x: (0,1,0),pi/2; y: (0,0,1),pi/2; z: (1,0,0),0 qglviewer::Vec axis(0,0,0); axis[(axisIdx+1)%3]=1; Real angle=axisIdx==2?0:Mathr::PI/2; manipulatedFrame()->setOrientation(qglviewer::Quaternion(axis,angle)); } } else if(e->key()==Qt::Key_Period) gridSubdivide = !gridSubdivide; else if(e->key()==Qt::Key_Return){ if (Omega::instance().isRunning()) Omega::instance().pause(); else Omega::instance().run(); LOG_INFO("Running..."); } #ifdef YADE_GL2PS else if(e->key()==Qt::Key_V){ for(int i=0; ;i++){ std::ostringstream fss; fss<<"/tmp/yade-snapshot-"<key()==Qt::Key_Plus ){ cut_plane = std::min(1.0, cut_plane + std::pow(10.0,(double)cut_plane_delta)); static_cast(camera())->setCuttingDistance(cut_plane); displayMessage("Cut plane: "+boost::lexical_cast(cut_plane)); }else if( e->key()==Qt::Key_Minus ){ cut_plane = std::max(0.0, cut_plane - std::pow(10.0,(double)cut_plane_delta)); static_cast(camera())->setCuttingDistance(cut_plane); displayMessage("Cut plane: "+boost::lexical_cast(cut_plane)); }else if( e->key()==Qt::Key_Slash ){ cut_plane_delta -= 1; displayMessage("Cut plane increment: 1e"+(cut_plane_delta>0?std::string("+"):std::string(""))+boost::lexical_cast(cut_plane_delta)); }else if( e->key()==Qt::Key_Asterisk ){ cut_plane_delta = std::min(1+cut_plane_delta,-1); displayMessage("Cut plane increment: 1e"+(cut_plane_delta>0?std::string("+"):std::string(""))+boost::lexical_cast(cut_plane_delta)); } #endif else if(e->key()!=Qt::Key_Escape && e->key()!=Qt::Key_Space) QGLViewer::keyPressEvent(e); updateGL(); } /* Center the scene such that periodic cell is contained in the view */ void GLViewer::centerPeriodic(){ Scene* scene=Omega::instance().getScene().get(); assert(scene->isPeriodic); Vector3r center=.5*scene->cell->getSize(); Vector3r halfSize=.5*scene->cell->getSize(); float radius=std::max(halfSize[0],std::max(halfSize[1],halfSize[2])); LOG_DEBUG("Periodic scene center="<isPeriodic){ centerPeriodic(); return; } long nBodies=scene->bodies->size(); if(nBodies<4) { LOG_DEBUG("Less than 4 bodies, median makes no sense; calling centerScene() instead."); return centerScene(); } std::vector coords[3]; for(int i=0;i<3;i++)coords[i].reserve(nBodies); FOREACH(shared_ptr b, *scene->bodies){ if(!b) continue; for(int i=0; i<3; i++) coords[i].push_back(b->state->pos[i]); } Vector3r median,interQuart; for(int i=0;i<3;i++){ sort(coords[i].begin(),coords[i].end()); median[i]=*(coords[i].begin()+nBodies/2); interQuart[i]=*(coords[i].begin()+3*nBodies/4)-*(coords[i].begin()+nBodies/4); } LOG_DEBUG("Median position is"<isPeriodic){ centerPeriodic(); return; } LOG_INFO("Select with shift, press 'm' to move."); Vector3r min,max; if(not(rb->bound)){ rb->updateBound();} min=rb->bound->min; max=rb->bound->max; bool hasNan=(std::isnan(min[0])||std::isnan(min[1])||std::isnan(min[2])||std::isnan(max[0])||std::isnan(max[1])||std::isnan(max[2])); Real minDim=std::min(max[0]-min[0],std::min(max[1]-min[1],max[2]-min[2])); if(minDim<=0 || hasNan){ // Aabb is not yet calculated... LOG_DEBUG("scene's bound not yet calculated or has zero or nan dimension(s), attempt get that from bodies' positions."); Real inf=std::numeric_limits::infinity(); min=Vector3r(inf,inf,inf); max=Vector3r(-inf,-inf,-inf); FOREACH(const shared_ptr& b, *rb->bodies){ if(!b) continue; max=max.cwiseMax(b->state->pos); min=min.cwiseMin(b->state->pos); } if(std::isinf(min[0])||std::isinf(min[1])||std::isinf(min[2])||std::isinf(max[0])||std::isinf(max[1])||std::isinf(max[2])){ LOG_DEBUG("No min/max computed from bodies either, setting cube (-1,-1,-1)×(1,1,1)"); min=-Vector3r::Ones(); max=Vector3r::Ones(); } } else {LOG_DEBUG("Using scene's Aabb");} LOG_DEBUG("Got scene box min="<selectedBody = -1; if (last>=0) { Body::byId(Body::id_t(last))->state->blockedDOFs=initBlocked; last=-1;} if(isMoving){ displayMessage("Moving finished"); mouseMovesCamera(); isMoving=false;} return; } if(selection>=0 && (*(Omega::instance().getScene()->bodies)).exists(selection)){ resetManipulation(); if (last>=0) {Body::byId(Body::id_t(last))->state->blockedDOFs=initBlocked; last=-1;} if(Body::byId(Body::id_t(selection))->isClumpMember()){ // select clump (invisible) instead of its member LOG_DEBUG("Clump member #"<clumpId; } setSelectedName(selection); LOG_DEBUG("New selection "<(selection)+(Body::byId(selection)->isClump()?" (clump)":"")); Omega::instance().getScene()->selectedBody = selection; PyGILState_STATE gstate; gstate = PyGILState_Ensure(); boost::python::object main=boost::python::import("__main__"); boost::python::object global=main.attr("__dict__"); // the try/catch block must be properly nested inside PyGILState_Ensure and PyGILState_Release try{ boost::python::eval(string("onBodySelect("+boost::lexical_cast(selection)+")").c_str(),global,global); } catch (boost::python::error_already_set const &) { LOG_DEBUG("unable to call onBodySelect. Not defined?"); } PyGILState_Release(gstate); // see https://svn.boost.org/trac/boost/ticket/2781 for exception handling } } // maybe new object will be selected. // if so, then set isDynamic of previous selection, to old value void GLViewer::endSelection(const QPoint &point){ manipulatedClipPlane=-1; QGLViewer::endSelection(point); } string GLViewer::getRealTimeString(){ ostringstream oss; boost::posix_time::time_duration t=Omega::instance().getRealTime_duration(); unsigned d=t.hours()/24,h=t.hours()%24,m=t.minutes(),s=t.seconds(); oss<<"clock "; if(d>0) oss<0) oss<<_W2<=0x020603 qreal YadeCamera::zNear() const #else float YadeCamera::zNear() const #endif { float z = distanceToSceneCenter() - zClippingCoefficient()*sceneRadius()*(1.f-2*cuttingDistance); // Prevents negative or null zNear values. const float zMin = zNearCoefficient() * zClippingCoefficient() * sceneRadius(); if (z < zMin) /* switch (type()) { case Camera::PERSPECTIVE :*/ z = zMin; /*break; case Camera::ORTHOGRAPHIC : z = 0.0; break; }*/ return z; } trunk-2018.02b/gui/qt4/GLViewer.hpp000066400000000000000000000124141324306050200166610ustar00rootroot00000000000000// Copyright (C) 2004 by Olivier Galizzi, Janek Kozicki * // © 2008 Václav Šmilauer #pragma once #ifndef Q_MOC_RUN #include #include #include #include #endif #include #include #include using std::setw; using std::setfill; using std::setprecision; /*! Class handling user interaction with the openGL rendering of simulation. * * Clipping planes: * ================ * * Clipping plane is manipulated after hitting F1, F2, .... To end the manipulation, press Escape. * * Keystrokes during clipping plane manipulation: * * space activates/deactives the clipping plane * * x,y,z aligns the plane with yz, xz, xy planes * * left-double-click aligns the plane with world coordinates system (canonical planes + 45˚ interpositions) * * 1,2,... will align the current plane with #1, #2, ... (same orientation) * * r reverses the plane (normal*=-1)a * * Keystrokes that work regardless of whether a clipping plane is being manipulated: * * Alt-1,Alt-2,... adds/removes the respective plane to bound group: * mutual positions+orientations of planes in the group are maintained when one of those planes is manipulated * * Clip plane number is 3; change YADE_RENDERER_NUM_CLIP_PLANE, complete switches "|| ..." in keyPressEvent * and recompile to have more. */ class GLViewer : public QGLViewer { Q_OBJECT friend class QGLThread; protected: shared_ptr renderer; private : bool isMoving; bool wasDynamic; float cut_plane; int cut_plane_delta; bool gridSubdivide; long last; int manipulatedClipPlane; set boundClipPlanes; shared_ptr xyPlaneConstraint; string strBoundGroup(){string ret;FOREACH(int i, boundClipPlanes) ret+=" "+boost::lexical_cast(i+1);return ret;} boost::posix_time::ptime last_user_event; public: //virtual void updateGL(void); const int viewId; void centerMedianQuartile(); int drawGrid; bool drawScale; int timeDispMask; enum{TIME_REAL=1,TIME_VIRT=2,TIME_ITER=4}; GLViewer(int viewId, const shared_ptr& renderer, QGLWidget* shareWidget=0); virtual ~GLViewer(); #if 0 virtual void paintGL(); #endif virtual void draw(); virtual void drawWithNames(); void displayMessage(const std::string& s){ QGLViewer::displayMessage(QString(s.c_str()));} void centerScene(); void centerPeriodic(); void mouseMovesCamera(); void mouseMovesManipulatedFrame(qglviewer::Constraint* c=NULL); void resetManipulation(); bool isManipulating(); void startClipPlaneManipulation(int planeNo); //! get QGLViewer state as string (XML); QGLViewer normally only supports saving state to file. string getState(); //! set QGLViewer state from string (XML); QGLVIewer normally only supports loading state from file. void setState(string); //! Load display parameters (QGLViewer and OpenGLRenderer) from Scene::dispParams[n] and use them void useDisplayParameters(size_t n); //! Save display parameters (QGOViewer and OpenGLRenderer) to Scene::dispParams[n] void saveDisplayParameters(size_t n); //! Get radius of the part of scene that fits the current view float displayedSceneRadius(); //! Get center of the part of scene that fits the current view qglviewer::Vec displayedSceneCenter(); //! Adds our attributes to the QGLViewer state that can be saved QDomElement domElement(const QString& name, QDomDocument& document) const; //! Adds our attributes to the QGLViewer state that can be restored void initFromDOMElement(const QDomElement& element); // if defined, snapshot will be saved to this file right after being drawn and the string will be reset. // this way the caller will be notified of the frame being saved successfully. string nextFrameSnapshotFilename; #ifdef YADE_GL2PS // output stream for gl2ps; initialized as needed FILE* gl2psStream; #endif boost::posix_time::ptime getLastUserEvent(); DECLARE_LOGGER; protected : virtual void keyPressEvent(QKeyEvent *e); virtual void postDraw(); // overridden in the player that doesn't get time from system clock but from the db virtual string getRealTimeString(); virtual void closeEvent(QCloseEvent *e); virtual void postSelection(const QPoint& point); virtual void endSelection(const QPoint &point); virtual void mouseDoubleClickEvent(QMouseEvent *e); virtual void wheelEvent(QWheelEvent* e); virtual void mouseMoveEvent(QMouseEvent *e); virtual void mousePressEvent(QMouseEvent *e); }; /*! Get unconditional lock on a GL view. Use if you need to manipulate GL context in some way. The ctor doesn't return until the lock has been acquired and the lock is released when the GLLock object is desctructed; */ class GLLock: public boost::try_mutex::scoped_lock{ GLViewer* glv; public: GLLock(GLViewer* _glv); ~GLLock(); }; class YadeCamera : public qglviewer::Camera { Q_OBJECT private: float cuttingDistance; public : YadeCamera():cuttingDistance(0){}; #if QGLVIEWER_VERSION>=0x020603 virtual qreal zNear() const; #else virtual float zNear() const; #endif virtual void setCuttingDistance(float s){cuttingDistance=s;}; }; trunk-2018.02b/gui/qt4/GLViewerDisplay.cpp000066400000000000000000000327751324306050200202160ustar00rootroot00000000000000/************************************************************************* * Copyright (C) 2004 by Olivier Galizzi * * olivier.galizzi@imag.fr * * Copyright (C) 2005 by Janek Kozicki * * cosurgi@berlios.de * * * * This program is free software; it is licensed under the terms of the * * GNU General Public License v2 or later. See file LICENSE for details. * *************************************************************************/ #include"GLViewer.hpp" #include"OpenGLManager.hpp" #include #include #include #include #include #include #include #include #include #include #include #include #ifdef YADE_GL2PS #include #endif void GLViewer::useDisplayParameters(size_t n){ LOG_DEBUG("Loading display parameters from #"< >& dispParams=Omega::instance().getScene()->dispParams; if(dispParams.size()<=(size_t)n){ throw std::invalid_argument(("Display parameters #"+boost::lexical_cast(n)+" don't exist (number of entries "+boost::lexical_cast(dispParams.size())+")").c_str());; return;} const shared_ptr& dp=dispParams[n]; string val; if(dp->getValue("OpenGLRenderer",val)){ istringstream oglre(val); yade::ObjectIO::load(oglre,"renderer",renderer); } else { LOG_WARN("OpenGLRenderer configuration not found in display parameters, skipped.");} if(dp->getValue("GLViewer",val)){ GLViewer::setState(val); displayMessage("Loaded view configuration #"+boost::lexical_cast(n)); } else { LOG_WARN("GLViewer configuration not found in display parameters, skipped."); } } void GLViewer::saveDisplayParameters(size_t n){ LOG_DEBUG("Saving display parameters to #"< >& dispParams=Omega::instance().getScene()->dispParams; if(dispParams.size()<=n){while(dispParams.size()<=n) dispParams.push_back(shared_ptr(new DisplayParameters));} assert(n& dp=dispParams[n]; ostringstream oglre; yade::ObjectIO::save(oglre,"renderer",renderer); dp->setValue("OpenGLRenderer",oglre.str()); dp->setValue("GLViewer",GLViewer::getState()); displayMessage("Saved view configuration ot #"+boost::lexical_cast(n)); } void GLViewer::draw() { #ifdef YADE_GL2PS if(!nextFrameSnapshotFilename.empty() && boost::algorithm::ends_with(nextFrameSnapshotFilename,".pdf")){ gl2psStream=fopen(nextFrameSnapshotFilename.c_str(),"wb"); if(!gl2psStream){ int err=errno; throw runtime_error(string("Error opening file ")+nextFrameSnapshotFilename+": "+strerror(err)); } LOG_DEBUG("Start saving snapshot to "<bodies->size(); int sortAlgo=(nBodies<100 ? GL2PS_BSP_SORT : GL2PS_SIMPLE_SORT); gl2psBeginPage(/*const char *title*/"Some title", /*const char *producer*/ "Yade", /*GLint viewport[4]*/ NULL, /*GLint format*/ GL2PS_PDF, /*GLint sort*/ sortAlgo, /*GLint options*/GL2PS_SIMPLE_LINE_OFFSET|GL2PS_USE_CURRENT_VIEWPORT|GL2PS_TIGHT_BOUNDING_BOX|GL2PS_COMPRESS|GL2PS_OCCLUSION_CULL|GL2PS_NO_BLENDING, /*GLint colormode*/ GL_RGBA, /*GLint colorsize*/0, /*GL2PSrgba *colortable*/NULL, /*GLint nr*/0, /*GLint ng*/0, /*GLint nb*/0, /*GLint buffersize*/4096*4096 /* 16MB */, /*FILE *stream*/ gl2psStream, /*const char *filename*/NULL); } #endif qglviewer::Vec vd=camera()->viewDirection(); renderer->viewDirection=Vector3r(vd[0],vd[1],vd[2]); if(Omega::instance().getScene()){ const shared_ptr& scene=Omega::instance().getScene(); int selection = selectedName(); if(selection!=-1 && (*(Omega::instance().getScene()->bodies)).exists(selection) && isMoving){ static Real lastTimeMoved(0); #if QGLVIEWER_VERSION>=0x020603 qreal v0,v1,v2; manipulatedFrame()->getPosition(v0,v1,v2); #else float v0,v1,v2; manipulatedFrame()->getPosition(v0,v1,v2); #endif if(last == selection) // delay by one redraw, so the body will not jump into 0,0,0 coords { Quaternionr& q = (*(Omega::instance().getScene()->bodies))[selection]->state->ori; Vector3r& v = (*(Omega::instance().getScene()->bodies))[selection]->state->pos; Vector3r& vel = (*(Omega::instance().getScene()->bodies))[selection]->state->vel; Vector3r& angVel = (*(Omega::instance().getScene()->bodies))[selection]->state->angVel; angVel=Vector3r::Zero(); Real dt=(scene->time-lastTimeMoved); lastTimeMoved=scene->time; if (dt!=0) { vel[0]=-(v[0]-v0)/dt; vel[1]=-(v[1]-v1)/dt; vel[2]=-(v[2]-v2)/dt;} else vel[0]=vel[1]=vel[2]=0; //FIXME: should update spin like velocity above, when the body is rotated: double q0,q1,q2,q3; manipulatedFrame()->getOrientation(q0,q1,q2,q3); q.x()=q0;q.y()=q1;q.z()=q2;q.w()=q3; } (*(Omega::instance().getScene()->bodies))[selection]->userForcedDisplacementRedrawHook(); } if(manipulatedClipPlane>=0){ assert(manipulatedClipPlanenumClipPlanes); #if QGLVIEWER_VERSION>=0x020603 qreal v0,v1,v2; manipulatedFrame()->getPosition(v0,v1,v2); #else float v0,v1,v2; manipulatedFrame()->getPosition(v0,v1,v2); #endif double q0,q1,q2,q3; manipulatedFrame()->getOrientation(q0,q1,q2,q3); Se3r newSe3(Vector3r(v0,v1,v2),Quaternionr(q0,q1,q2,q3)); newSe3.orientation.normalize(); const Se3r& oldSe3=renderer->clipPlaneSe3[manipulatedClipPlane]; FOREACH(int planeId, boundClipPlanes){ if(planeId>=renderer->numClipPlanes || !renderer->clipPlaneActive[planeId] || planeId==manipulatedClipPlane) continue; Se3r& boundSe3=renderer->clipPlaneSe3[planeId]; Quaternionr relOrient=oldSe3.orientation.conjugate()*boundSe3.orientation; relOrient.normalize(); Vector3r relPos=oldSe3.orientation.conjugate()*(boundSe3.position-oldSe3.position); boundSe3.position=newSe3.position+newSe3.orientation*relPos; boundSe3.orientation=newSe3.orientation*relOrient; boundSe3.orientation.normalize(); } renderer->clipPlaneSe3[manipulatedClipPlane]=newSe3; } scene->renderer=renderer; renderer->render(scene, selectedName()); } } void GLViewer::drawWithNames(){ qglviewer::Vec vd=camera()->viewDirection(); renderer->viewDirection=Vector3r(vd[0],vd[1],vd[2]); const shared_ptr scene(Omega::instance().getScene()); scene->renderer=renderer; renderer->scene=scene; renderer->renderShape(); } qglviewer::Vec GLViewer::displayedSceneCenter(){ return camera()->unprojectedCoordinatesOf(qglviewer::Vec(width()/2 /* pixels */ ,height()/2 /* pixels */, /*middle between near plane and far plane*/ .5)); } float GLViewer::displayedSceneRadius(){ return (camera()->unprojectedCoordinatesOf(qglviewer::Vec(width()/2,height()/2,.5))-camera()->unprojectedCoordinatesOf(qglviewer::Vec(0,0,.5))).norm(); } void GLViewer::postDraw(){ Real wholeDiameter=QGLViewer::camera()->sceneRadius()*2; renderer->viewInfo.sceneRadius=QGLViewer::camera()->sceneRadius(); qglviewer::Vec c=QGLViewer::camera()->sceneCenter(); renderer->viewInfo.sceneCenter=Vector3r(c[0],c[1],c[2]); Real dispDiameter=min(wholeDiameter,max((Real)displayedSceneRadius()*2,wholeDiameter/1e3)); // limit to avoid drawing 1e5 lines with big zoom level //qglviewer::Vec center=QGLViewer::camera()->sceneCenter(); Real gridStep=pow(10,(floor(log10(dispDiameter)-.7))); Real scaleStep=pow(10,(floor(log10(displayedSceneRadius()*2)-.7))); // unconstrained int nSegments=((int)(wholeDiameter/gridStep))+1; Real realSize=nSegments*gridStep; //LOG_TRACE("nSegments="<projectedCoordinatesOf(center+delta)-camera()->projectedCoordinatesOf(center); for(int xy=0;xy<2;xy++)extremalDxDy[xy]=(axis>0 ? min(extremalDxDy[xy],(int)screenDxDy[axis][xy]) : screenDxDy[axis][xy]); } //LOG_DEBUG("Screen offsets for axes: "<<" x("<=0){ for(int planeId=0; planeIdnumClipPlanes; planeId++){ if(!renderer->clipPlaneActive[planeId] && planeId!=manipulatedClipPlane) continue; glPushMatrix(); const Se3r& se3=renderer->clipPlaneSe3[planeId]; AngleAxisr aa(se3.orientation); glTranslatef(se3.position[0],se3.position[1],se3.position[2]); glRotated(aa.angle()*Mathr::RAD_TO_DEG,aa.axis()[0],aa.axis()[1],aa.axis()[2]); Real cff=1; if(!renderer->clipPlaneActive[planeId]) cff=.4; glColor3f(max((Real)0.,cff*cos(planeId)),max((Real)0.,cff*sin(planeId)),planeId==manipulatedClipPlane); // variable colors QGLViewer::drawGrid(realSize,2*nSegments); drawArrow(wholeDiameter/6); glPopMatrix(); } } Scene* rb=Omega::instance().getScene().get(); #define _W3 setw(3)<time; unsigned min=((unsigned)t/60),sec=(((unsigned)t)%60),msec=((unsigned)(1e3*t))%1000,usec=((unsigned long)(1e6*t))%1000,nsec=((unsigned long)(1e9*t))%1000; if(min>0) oss<<_W2<0) oss<<_W2<0) oss<<_W3<0) oss<<_W3<iter; if(rb->stopAtIter>rb->iter) oss<<" ("<iter)/rb->stopAtIter<<"%)"; QGLViewer::drawText(x,y,oss.str().c_str()); y-=lineHt; } if(drawGrid){ glColor3v(Vector3r(1,1,0)); ostringstream oss; oss<<"grid: "< #include #include #include #include #include #include #include #include #include #include #include #include #ifdef YADE_GL2PS #include #endif void GLViewer::mouseMovesCamera(){ setWheelBinding(Qt::ShiftModifier , FRAME, ZOOM); setWheelBinding(Qt::NoModifier, CAMERA, ZOOM); #if QGLVIEWER_VERSION>=0x020500 setMouseBinding(Qt::ShiftModifier, Qt::RightButton, FRAME, ZOOM); setMouseBinding(Qt::ShiftModifier, Qt::MidButton, FRAME, TRANSLATE); setMouseBinding(Qt::ShiftModifier, Qt::RightButton, FRAME, ROTATE); setMouseBinding(Qt::NoModifier, Qt::MidButton, CAMERA, ZOOM); setMouseBinding(Qt::NoModifier, Qt::LeftButton, CAMERA, ROTATE); setMouseBinding(Qt::NoModifier, Qt::RightButton, CAMERA, TRANSLATE); #else setMouseBinding(Qt::SHIFT + Qt::LeftButton, SELECT); setMouseBinding(Qt::SHIFT + Qt::LeftButton + Qt::RightButton, FRAME, ZOOM); setMouseBinding(Qt::SHIFT + Qt::MidButton, FRAME, TRANSLATE); setMouseBinding(Qt::SHIFT + Qt::RightButton, FRAME, ROTATE); setMouseBinding(Qt::LeftButton + Qt::RightButton, CAMERA, ZOOM); setMouseBinding(Qt::MidButton, CAMERA, ZOOM); setMouseBinding(Qt::LeftButton, CAMERA, ROTATE); setMouseBinding(Qt::RightButton, CAMERA, TRANSLATE); camera()->frame()->setWheelSensitivity(-1.0f); #endif }; void GLViewer::mouseMovesManipulatedFrame(qglviewer::Constraint* c){ setMouseBinding(Qt::LeftButton + Qt::RightButton, FRAME, ZOOM); setMouseBinding(Qt::MidButton, FRAME, ZOOM); setMouseBinding(Qt::LeftButton, FRAME, ROTATE); setMouseBinding(Qt::RightButton, FRAME, TRANSLATE); setWheelBinding(Qt::NoModifier , FRAME, ZOOM); manipulatedFrame()->setConstraint(c); } void GLViewer::mouseMoveEvent(QMouseEvent *e){ last_user_event = boost::posix_time::second_clock::local_time(); QGLViewer::mouseMoveEvent(e); } void GLViewer::mousePressEvent(QMouseEvent *e){ last_user_event = boost::posix_time::second_clock::local_time(); QGLViewer::mousePressEvent(e); } /* Handle double-click event; if clipping plane is manipulated, align it with the global coordinate system. * Otherwise pass the event to QGLViewer to handle it normally. * * mostly copied over from ManipulatedFrame::mouseDoubleClickEvent */ void GLViewer::mouseDoubleClickEvent(QMouseEvent *event){ last_user_event = boost::posix_time::second_clock::local_time(); if(manipulatedClipPlane<0) { /* LOG_DEBUG("Double click not on clipping plane"); */ QGLViewer::mouseDoubleClickEvent(event); return; } #if QT_VERSION >= 0x040000 if (event->modifiers() == Qt::NoModifier) #else if (event->state() == Qt::NoButton) #endif switch (event->button()){ case Qt::LeftButton: manipulatedFrame()->alignWithFrame(NULL,true); LOG_DEBUG("Aligning cutting plane"); break; default: break; // avoid warning } } void GLViewer::wheelEvent(QWheelEvent* event){ last_user_event = boost::posix_time::second_clock::local_time(); if(manipulatedClipPlane<0){ QGLViewer::wheelEvent(event); return; } assert(manipulatedClipPlanenumClipPlanes); float distStep=1e-3*sceneRadius(); float dist=event->delta()*manipulatedFrame()->wheelSensitivity()*distStep; Vector3r normal=renderer->clipPlaneSe3[manipulatedClipPlane].orientation*Vector3r(0,0,1); qglviewer::Vec newPos=manipulatedFrame()->position()+qglviewer::Vec(normal[0],normal[1],normal[2])*dist; manipulatedFrame()->setPosition(newPos); renderer->clipPlaneSe3[manipulatedClipPlane].position=Vector3r(newPos[0],newPos[1],newPos[2]); updateGL(); /* in draw, bound cutting planes will be moved as well */ } trunk-2018.02b/gui/qt4/Inspector.py000066400000000000000000000274011324306050200170060ustar00rootroot00000000000000# encoding: utf-8 from PyQt4.QtCore import * from PyQt4.QtGui import * from yade import * from yade.qt.SerializableEditor import * import yade.qt class EngineInspector(QWidget): def __init__(self,parent=None): QWidget.__init__(self,parent) grid=QGridLayout(self); grid.setSpacing(0); grid.setMargin(0) self.serEd=SeqSerializable(parent=None,getter=lambda:O.engines,setter=lambda x:setattr(O,'engines',x),serType=Engine,path='O.engines') grid.addWidget(self.serEd) self.setLayout(grid) #class MaterialsInspector(QWidget): # def __init__(self,parent=None): # QWidget.__init__(self,parent) # grid=QGridLayout(self); grid.setSpacing(0); grid.setMargin(0) # self.serEd=SeqSerializable(parent=None,getter=lambda:O.materials,setter=lambda x:setattr(O,'materials',x),serType=Engine) # grid.addWidget(self.serEd) # self.setLayout(grid) class CellInspector(QWidget): def __init__(self,parent=None): QWidget.__init__(self,parent) self.layout=QVBoxLayout(self) #; self.layout.setSpacing(0); self.layout.setMargin(0) self.periCheckBox=QCheckBox('periodic boundary',self) self.periCheckBox.clicked.connect(self.update) self.layout.addWidget(self.periCheckBox) self.scroll=QScrollArea(self); self.scroll.setWidgetResizable(True) self.layout.addWidget(self.scroll) self.setLayout(self.layout) self.refresh() self.refreshTimer=QTimer(self) self.refreshTimer.timeout.connect(self.refresh) self.refreshTimer.start(1000) def refresh(self): self.periCheckBox.setChecked(O.periodic) editor=self.scroll.widget() if not O.periodic and editor: self.scroll.takeWidget() if (O.periodic and not editor) or (editor and editor.ser!=O.cell): self.scroll.setWidget(SerializableEditor(O.cell,parent=self,showType=True,path='O.cell')) def update(self): self.scroll.takeWidget() # do this before changing periodicity, otherwise the SerializableEditor will raise exception about None object O.periodic=self.periCheckBox.isChecked() self.refresh() def makeBodyLabel(b): ret=unicode(b.id)+u' ' if not b.shape: ret+=u'⬚' else: typeMap={'Sphere':u'⚫','Facet':u'△','Wall':u'┃','Box':u'⎕','Cylinder':u'⌭','ChainedCylinder':u'☡','Clump':u'☍'} ret+=typeMap.get(b.shape.__class__.__name__,u'﹖') if not b.dynamic: ret+=u'⚓' elif b.state.blockedDOFs: ret+=u'⎈' return ret def getBodyIdFromLabel(label): try: return int(unicode(label).split()[0]) except ValueError: print 'Error with label:',unicode(label) return -1 class BodyInspector(QWidget): def __init__(self,bodyId=-1,parent=None,bodyLinkCallback=None,intrLinkCallback=None): QWidget.__init__(self,parent) v=yade.qt.views() self.bodyId=bodyId if bodyId<0 and len(v)>0 and v[0].selection>0: self.bodyId=v[0].selection self.idGlSync=self.bodyId self.bodyLinkCallback,self.intrLinkCallback=bodyLinkCallback,intrLinkCallback self.bodyIdBox=QSpinBox(self) self.bodyIdBox.setMinimum(-1) self.bodyIdBox.setMaximum(100000000) self.bodyIdBox.setValue(self.bodyId) self.intrWithCombo=QComboBox(self); self.gotoBodyButton=QPushButton(u'→ #',self) self.gotoIntrButton=QPushButton(u'→ #+#',self) # id selector topBoxWidget=QWidget(self); topBox=QHBoxLayout(topBoxWidget); topBox.setMargin(0); #topBox.setSpacing(0); hashLabel=QLabel('#',self); hashLabel.setFixedWidth(8) topBox.addWidget(hashLabel) topBox.addWidget(self.bodyIdBox) self.plusLabel=QLabel('+',self); topBox.addWidget(self.plusLabel) hashLabel2=QLabel('#',self); hashLabel2.setFixedWidth(8); topBox.addWidget(hashLabel2) topBox.addWidget(self.intrWithCombo) topBox.addStretch() topBox.addWidget(self.gotoBodyButton) topBox.addWidget(self.gotoIntrButton) topBoxWidget.setLayout(topBox) # forces display forcesWidget=QFrame(self); forcesWidget.setFrameShape(QFrame.Box); self.forceGrid=QGridLayout(forcesWidget); self.forceGrid.setVerticalSpacing(0); self.forceGrid.setHorizontalSpacing(9); self.forceGrid.setMargin(4); for i,j in itertools.product((0,1,2,3),(-1,0,1,2)): lab=QLabel(''+('force','torque','move','rot')[i]+'' if j==-1 else ''); self.forceGrid.addWidget(lab,i,j+1); if j>=0: lab.setAlignment(Qt.AlignRight) if i>1: lab.hide() # do not show forced moves and rotations by default (they will appear if non-zero) self.showMovRot=False # self.grid=QGridLayout(self); self.grid.setSpacing(0); self.grid.setMargin(0) self.grid.addWidget(topBoxWidget) self.grid.addWidget(forcesWidget) self.scroll=QScrollArea(self) self.scroll.setWidgetResizable(True) self.grid.addWidget(self.scroll) self.tryShowBody() self.bodyIdBox.valueChanged.connect(self.bodyIdSlot) self.gotoBodyButton.clicked.connect(self.gotoBodySlot) self.gotoIntrButton.clicked.connect(self.gotoIntrSlot) self.refreshTimer=QTimer(self) self.refreshTimer.timeout.connect(self.refreshEvent) self.refreshTimer.start(1000) self.intrWithCombo.addItems(['0']); self.intrWithCombo.setCurrentIndex(0); self.intrWithCombo.setMinimumWidth(80) self.setWindowTitle('Body #%d'%self.bodyId) self.gotoBodySlot() def displayForces(self): if self.bodyId<0: return try: val=[O.forces.f(self.bodyId),O.forces.t(self.bodyId),O.forces.move(self.bodyId),O.forces.rot(self.bodyId)] hasMovRot=(val[2]!=Vector3.Zero or val[3]!=Vector3.Zero) if hasMovRot!=self.showMovRot: for i,j in itertools.product((2,3),(-1,0,1,2)): if hasMovRot: self.forceGrid.itemAtPosition(i,j+1).widget().show() else: self.forceGrid.itemAtPosition(i,j+1).widget().hide() self.showMovRot=hasMovRot rows=((0,1,2,3) if hasMovRot else (0,1)) for i,j in itertools.product(rows,(0,1,2)): self.forceGrid.itemAtPosition(i,j+1).widget().setText(''+str(val[i][j])+'') except IndexError:pass def tryShowBody(self): try: b=O.bodies[self.bodyId] self.serEd=SerializableEditor(b,showType=True,parent=self,path='O.bodies[%d]'%self.bodyId) except IndexError: self.serEd=QFrame(self) self.bodyId=-1 self.scroll.setWidget(self.serEd) def changeIdSlot(self,newId): self.bodyIdBox.setValue(newId); self.bodyIdSlot() def bodyIdSlot(self): self.bodyId=self.bodyIdBox.value() self.tryShowBody() self.setWindowTitle('Body #%d'%self.bodyId) self.refreshEvent() def gotoBodySlot(self): try: id=int(getBodyIdFromLabel(self.intrWithCombo.currentText())) except ValueError: return # empty id if not self.bodyLinkCallback: self.bodyIdBox.setValue(id); self.bodyId=id else: self.bodyLinkCallback(id) def gotoIntrSlot(self): ids=self.bodyIdBox.value(),getBodyIdFromLabel(self.intrWithCombo.currentText()) if not self.intrLinkCallback: self.ii=InteractionInspector(ids) self.ii.show() else: self.intrLinkCallback(ids) def refreshEvent(self): try: O.bodies[self.bodyId] except: self.bodyId=-1 # invalidate deleted body # no body shown yet, try to get the first one... #if self.bodyId<0 and len(O.bodies)>0: #try: #print 'SET ZERO' #b=O.bodies[0]; self.bodyIdBox.setValue(0) #except IndexError: pass v=yade.qt.views() if len(v)>0 and v[0].selection!=self.bodyId: print v[0].selection,self.bodyId,self.idGlSync if self.idGlSync==self.bodyId: # changed in the viewer, reset ourselves self.bodyId=self.idGlSync=v[0].selection; self.changeIdSlot(self.bodyId) return else: v[0].selection=self.idGlSync=self.bodyId # changed here, set in the viewer meId=self.bodyIdBox.value(); pos=self.intrWithCombo.currentIndex() try: meLabel=makeBodyLabel(O.bodies[meId]) except IndexError: meLabel=u'…' self.plusLabel.setText(' '.join(meLabel.split()[1:])+' +') # do not repeat the id self.bodyIdBox.setMaximum(len(O.bodies)-1) others=[(i.id1 if i.id1!=meId else i.id2) for i in O.interactions.withBody(self.bodyIdBox.value()) if i.isReal] others.sort() self.intrWithCombo.clear() self.intrWithCombo.addItems([makeBodyLabel(O.bodies[i]) for i in others]) if pos>self.intrWithCombo.count() or pos<0: pos=0 self.intrWithCombo.setCurrentIndex(pos); other=self.intrWithCombo.itemText(pos) if other=='': self.gotoBodyButton.setEnabled(False); self.gotoIntrButton.setEnabled(False) other=u'∅' else: self.gotoBodyButton.setEnabled(True); self.gotoIntrButton.setEnabled(True) self.gotoBodyButton.setText(u'→ %s'%other) self.gotoIntrButton.setText(u'→ %s + %s'%(meLabel,other)) self.displayForces() class InteractionInspector(QWidget): def __init__(self,ids=None,parent=None,bodyLinkCallback=None): QWidget.__init__(self,parent) self.bodyLinkCallback=bodyLinkCallback self.ids=ids self.gotoId1Button=QPushButton(u'#…',self) self.gotoId2Button=QPushButton(u'#…',self) self.gotoId1Button.clicked.connect(self.gotoId1Slot) self.gotoId2Button.clicked.connect(self.gotoId2Slot) topBoxWidget=QWidget(self) topBox=QHBoxLayout(topBoxWidget) topBox.addWidget(self.gotoId1Button) labelPlus=QLabel('+',self); labelPlus.setAlignment(Qt.AlignHCenter) topBox.addWidget(labelPlus) topBox.addWidget(self.gotoId2Button) topBoxWidget.setLayout(topBox) self.setWindowTitle(u'No interaction') self.grid=QGridLayout(self); self.grid.setSpacing(0); self.grid.setMargin(0) self.grid.addWidget(topBoxWidget,0,0) self.scroll=QScrollArea(self) self.scroll.setWidgetResizable(True) self.grid.addWidget(self.scroll) self.refreshTimer=QTimer(self) self.refreshTimer.timeout.connect(self.refreshEvent) self.refreshTimer.start(1000) if self.ids: self.setupInteraction() def setupInteraction(self): try: intr=O.interactions[self.ids[0],self.ids[1]] self.serEd=SerializableEditor(intr,showType=True,parent=self.scroll,path='O.interactions[%d,%d]'%(self.ids[0],self.ids[1])) self.scroll.setWidget(self.serEd) self.gotoId1Button.setText('#'+makeBodyLabel(O.bodies[self.ids[0]])) self.gotoId2Button.setText('#'+makeBodyLabel(O.bodies[self.ids[1]])) self.setWindowTitle('Interaction #%d + #%d'%(self.ids[0],self.ids[1])) except IndexError: if self.ids: # reset view (there was an interaction) self.ids=None self.serEd=QFrame(self.scroll); self.scroll.setWidget(self.serEd) self.setWindowTitle('No interaction') self.gotoId1Button.setText(u'#…'); self.gotoId2Button.setText(u'#…'); def gotoId(self,bodyId): if self.bodyLinkCallback: self.bodyLinkCallback(bodyId) else: self.bi=BodyInspector(bodyId); self.bi.show() def gotoId1Slot(self): self.gotoId(self.ids[0]) def gotoId2Slot(self): self.gotoId(self.ids[1]) def refreshEvent(self): # no ids yet -- try getting the first interaction, if it exists if not self.ids: try: i=O.interactions.nth(0) self.ids=i.id1,i.id2 self.setupInteraction() return except IndexError: return # no interaction exists at all try: # try to fetch an existing interaction O.interactions[self.ids[0],self.ids[1]] except IndexError: self.ids=None class SimulationInspector(QWidget): def __init__(self,parent=None): QWidget.__init__(self,parent) self.setWindowTitle("Simulation Inspection") self.tabWidget=QTabWidget(self) self.engineInspector=EngineInspector(parent=None) self.bodyInspector=BodyInspector(parent=None,intrLinkCallback=self.changeIntrIds) self.intrInspector=InteractionInspector(parent=None,bodyLinkCallback=self.changeBodyId) self.cellInspector=CellInspector(parent=None) for i,name,widget in [(0,'Engines',self.engineInspector),(1,'Bodies',self.bodyInspector),(2,'Interactions',self.intrInspector),(3,'Cell',self.cellInspector)]: self.tabWidget.addTab(widget,name) grid=QGridLayout(self); grid.setSpacing(0); grid.setMargin(0) grid.addWidget(self.tabWidget) self.setLayout(grid) def changeIntrIds(self,ids): self.tabWidget.removeTab(2); self.intrInspector.close() self.intrInspector=InteractionInspector(ids=ids,parent=None,bodyLinkCallback=self.changeBodyId) self.tabWidget.insertTab(2,self.intrInspector,'Interactions') self.tabWidget.setCurrentIndex(2) def changeBodyId(self,id): self.bodyInspector.changeIdSlot(id) self.tabWidget.setCurrentIndex(1) trunk-2018.02b/gui/qt4/OpenGLManager.cpp000066400000000000000000000051631324306050200176120ustar00rootroot00000000000000#include"OpenGLManager.hpp" CREATE_LOGGER(OpenGLManager); OpenGLManager* OpenGLManager::self=NULL; OpenGLManager::OpenGLManager(QObject* parent): QObject(parent){ if(self) throw runtime_error("OpenGLManager instance already exists, uses OpenGLManager::self to retrieve it."); self=this; renderer=shared_ptr(new OpenGLRenderer); renderer->init(); connect(this,SIGNAL(createView()),this,SLOT(createViewSlot())); connect(this,SIGNAL(resizeView(int,int,int)),this,SLOT(resizeViewSlot(int,int,int))); connect(this,SIGNAL(closeView(int)),this,SLOT(closeViewSlot(int))); connect(this,SIGNAL(startTimerSignal()),this,SLOT(startTimerSlot()),Qt::QueuedConnection); } void OpenGLManager::timerEvent(QTimerEvent* event){ //cerr<<"."; boost::mutex::scoped_lock lock(viewsMutex); // when sharing the 0th view widget, it should be enough to update the primary view only //if(views.size()>0) views[0]->updateGL(); #if 1 FOREACH(const shared_ptr& view, views){ if(view) view->updateGL(); } #endif } void OpenGLManager::createViewSlot(){ boost::mutex::scoped_lock lock(viewsMutex); if(views.size()==0){ views.push_back(shared_ptr(new GLViewer(0,renderer,/*shareWidget*/(QGLWidget*)0))); } else { throw runtime_error("Secondary views not supported"); //views.push_back(shared_ptr(new GLViewer(views.size(),renderer,views[0].get()))); } } void OpenGLManager::resizeViewSlot(int id, int wd, int ht){ views[id]->resize(wd,ht); } void OpenGLManager::closeViewSlot(int id){ boost::mutex::scoped_lock lock(viewsMutex); for(size_t i=views.size()-1; (!views[i]); i--){ views.resize(i); } // delete empty views from the end if(id<0){ // close the last one existing assert(*views.rbegin()); // this should have been sanitized by the loop above views.resize(views.size()-1); // releases the pointer as well } if(id==0){ LOG_DEBUG("Closing primary view."); if(views.size()==1) views.clear(); else{ LOG_INFO("Cannot close primary view, secondary views still exist."); } } } void OpenGLManager::centerAllViews(){ boost::mutex::scoped_lock lock(viewsMutex); FOREACH(const shared_ptr& g, views){ if(!g) continue; g->centerScene(); } } void OpenGLManager::startTimerSlot(){ startTimer(50); } int OpenGLManager::waitForNewView(float timeout,bool center){ size_t origViewCount=views.size(); emitCreateView(); float t=0; while(views.size()!=origViewCount+1){ usleep(50000); t+=.05; // wait at most 5 secs if(t>=timeout) { LOG_ERROR("Timeout waiting for the new view to open, giving up."); return -1; } } if(center)(*views.rbegin())->centerScene(); return (*views.rbegin())->viewId; } trunk-2018.02b/gui/qt4/OpenGLManager.hpp000066400000000000000000000027601324306050200176170ustar00rootroot00000000000000#pragma once //#include #ifndef Q_MOC_RUN #include"GLViewer.hpp" #endif #include #include /* Singleton class managing OpenGL views, a renderer instance and timer to refresh the display. */ class OpenGLManager: public QObject{ Q_OBJECT DECLARE_LOGGER; public: static OpenGLManager* self; OpenGLManager(QObject *parent=0); // manipulation must lock viewsMutex! std::vector > views; shared_ptr renderer; // signals are protected, emitting them is therefore wrapped with such funcs void emitResizeView(int id, int wd, int ht){ emit resizeView(id,wd,ht); } void emitCreateView(){ emit createView(); } void emitStartTimer(){ emit startTimerSignal(); } void emitCloseView(int id){ emit closeView(id); } // create a new view and wait for it to become available; return the view number // if timout (in seconds) elapses without the view to come up, reports error and returns -1 int waitForNewView(float timeout=5., bool center=true); signals: void createView(); void resizeView(int id, int wd, int ht); void closeView(int id); // this is used to start timer from the main thread via postEvent (ugly) void startTimerSignal(); public slots: virtual void createViewSlot(); virtual void resizeViewSlot(int id, int wd, int ht); virtual void closeViewSlot(int id=-1); virtual void timerEvent(QTimerEvent* event); virtual void startTimerSlot(); void centerAllViews(); private: boost::mutex viewsMutex; }; trunk-2018.02b/gui/qt4/SerializableEditor.py000066400000000000000000001107221324306050200206140ustar00rootroot00000000000000# encoding: utf-8 from PyQt4.QtCore import * from PyQt4.QtGui import * from PyQt4 import QtGui import re,itertools import logging logging.trace=logging.debug logging.basicConfig(level=logging.INFO) from yade import * import yade.qt from minieigen import * seqSerializableShowType=True # show type headings in serializable sequences (takes vertical space, but makes the type hyperlinked) # BUG: cursor is moved to the beginning of the input field even if it has focus # checking for focus seems to return True always and cursor is never moved # the 'True or' part effectively disables the condition (so that the cursor is moved always), but it might be fixed in the future somehow # if True or w.hasFocus(): w.home(False) # Janek: It looks like I've fixed this BUG, please do more testing. # def makeWrapperHref(text,className,attr=None,static=False): """Create clickable HTML hyperlink to a Yade class or its attribute. :param className: name of the class to link to. :param attr: attribute to link to. If given, must exist directly in given *className*; if not given or empty, link to the class itself is created and *attr* is ignored. :return: HTML with the hyperref. """ if not static: return '%s'%(yade.qt.sphinxDocWrapperPage,className,(('.'+attr) if attr else ''),text) else: return '%s'%(yade.qt.sphinxDocWrapperPage,className,attr,text) def serializableHref(ser,attr=None,text=None): """Return HTML href to a *ser* optionally to the attribute *attr*. The class hierarchy is crawled upwards to find out in which parent class is *attr* defined, so that the href target is a valid link. In that case, only single inheritace is assumed and the first class from the top defining *attr* is used. :param ser: object of class deriving from :yref:`Serializable`, or string; if string, *attr* must be empty. :param attr: name of the attribute to link to; if empty, linke to the class itself is created. :param text: visible text of the hyperlink; if not given, either class name or attribute name without class name (when *attr* is not given) is used. :returns: HTML with the hyperref. """ # klass is a class name given as string if isinstance(ser,str): if attr: raise InvalidArgument("When *ser* is a string, *attr* must be empty (only class link can be created)") return makeWrapperHref(text if text else ser,ser) # klass is a type object if attr: klass=ser.__class__ while attr in dir(klass.__bases__[0]): klass=klass.__bases__[0] if not text: text=attr else: klass=ser.__class__ if not text: text=klass.__name__ return makeWrapperHref(text,klass.__name__,attr,static=(attr and getattr(klass,attr)==getattr(ser,attr))) class AttrEditor(): """Abstract base class handing some aspects common to all attribute editors. Holds exacly one attribute which is updated whenever it changes.""" def __init__(self,getter=None,setter=None): self.getter,self.setter=getter,setter self.hot,self.focused=False,False self.widget=None def refresh(self): pass def update(self): pass def isHot(self,hot=True): "Called when the widget gets focus; mark it hot, change colors etc." if hot==self.hot: return self.hot=hot if hot: self.setStyleSheet('QWidget { background: red }') else: self.setStyleSheet('QWidget { background: none }') def sizeHint(self): return QSize(150,12) def trySetter(self,val): try: self.setter(val) except AttributeError: self.setEnabled(False) self.isHot(False) class AttrEditor_Bool(AttrEditor,QFrame): def __init__(self,parent,getter,setter): AttrEditor.__init__(self,getter,setter) QFrame.__init__(self,parent) self.checkBox=QCheckBox(self) lay=QVBoxLayout(self); lay.setSpacing(0); lay.setMargin(0); lay.addStretch(1); lay.addWidget(self.checkBox); lay.addStretch(1) self.checkBox.clicked.connect(self.update) def refresh(self): self.checkBox.setChecked(self.getter()) def update(self): self.trySetter(self.checkBox.isChecked()) class AttrEditor_Int(AttrEditor,QSpinBox): def __init__(self,parent,getter,setter): AttrEditor.__init__(self,getter,setter) QSpinBox.__init__(self,parent) self.setRange(int(-1e9),int(1e9)); self.setSingleStep(1); self.valueChanged.connect(self.update) def refresh(self): if (not self.hasFocus()): self.setValue(self.getter()) def update(self): self.trySetter(self.value()) class AttrEditor_Str(AttrEditor,QLineEdit): def __init__(self,parent,getter,setter): AttrEditor.__init__(self,getter,setter) QLineEdit.__init__(self,parent) self.textEdited.connect(self.isHot) self.selectionChanged.connect(self.isHot) self.editingFinished.connect(self.update) def refresh(self): if (not self.hasFocus()): self.setText(self.getter()) def update(self): self.trySetter(str(self.text())) class AttrEditor_Float(AttrEditor,QLineEdit): def __init__(self,parent,getter,setter): AttrEditor.__init__(self,getter,setter) QLineEdit.__init__(self,parent) self.textEdited.connect(self.isHot) self.selectionChanged.connect(self.isHot) self.editingFinished.connect(self.update) def refresh(self): #if True or not self.hasFocus(): self.home(False) if (not self.hasFocus()): self.setText(str(self.getter())); self.home(False) def update(self): try: self.trySetter(float(self.text())) except ValueError: self.refresh() class AttrEditor_Complex(AttrEditor,QLineEdit): def __init__(self,parent,getter,setter): AttrEditor.__init__(self,getter,setter) QFrame.__init__(self,parent) self.rows,self.cols=1,2 self.setContentsMargins(0,0,0,0) self.first=True val=self.getter() self.grid=QGridLayout(self); self.grid.setSpacing(0); self.grid.setMargin(0) for row,col in itertools.product(range(self.rows),range(self.cols)): w=QLineEdit('') self.grid.addWidget(w,row,col); w.textEdited.connect(self.isHot) w.selectionChanged.connect(self.isHot) w.editingFinished.connect(self.update) def refresh(self,force=False): val=self.getter() for row,col in itertools.product(range(self.rows),range(self.cols)): w=self.grid.itemAtPosition(row,col).widget() if(self.first or force): w.setText(str(val.real if col==0 else val.imag)) #if True or not w.hasFocus: w.home(False) # make the left-most part visible, if the text is wider than the widget if (not w.hasFocus): w.setText(str(val.real if col==0 else val.imag)) w.home(False) # make the left-most part visible, if the text is wider than the widget self.first=False def update(self): try: val=self.getter() w1=self.grid.itemAtPosition(0,0).widget() w2=self.grid.itemAtPosition(0,1).widget() if w1.isModified() or w2.isModified(): val=complex(float(w1.text()),float(w2.text())) logging.debug('setting'+str(val)) self.trySetter(val) except ValueError: self.refresh(force=True) def setFocus(self): self.grid.itemAtPosition(0,0).widget().setFocus() class AttrEditor_Quaternion(AttrEditor,QFrame): def __init__(self,parent,getter,setter): AttrEditor.__init__(self,getter,setter) QFrame.__init__(self,parent) self.grid=QHBoxLayout(self); self.grid.setSpacing(0); self.grid.setMargin(0) for i in range(4): if i==3: # add vertical divider (axis | angle) f=QFrame(self); f.setFrameShape(QFrame.VLine); f.setFrameShadow(QFrame.Sunken); f.setFixedWidth(4) self.grid.addWidget(f) w=QLineEdit('') self.grid.addWidget(w); w.textEdited.connect(self.isHot) w.selectionChanged.connect(self.isHot) w.editingFinished.connect(self.update) def refresh(self): val=self.getter(); axis,angle=val.toAxisAngle() for i in (0,1,2,4): #if True or not w.hasFocus(): w.home(False) w=self.grid.itemAt(i).widget(); if (not w.hasFocus()): w.setText(str(axis[i] if i<3 else angle)); w.home(False) def update(self): try: x=[float((self.grid.itemAt(i).widget().text())) for i in (0,1,2,4)] except ValueError: self.refresh() return q=Quaternion(Vector3(x[0],x[1],x[2]),x[3]); q.normalize() # from axis-angle self.trySetter(q) def setFocus(self): self.grid.itemAt(0).widget().setFocus() class AttrEditor_Se3(AttrEditor,QFrame): def __init__(self,parent,getter,setter): AttrEditor.__init__(self,getter,setter) QFrame.__init__(self,parent) self.grid=QGridLayout(self); self.grid.setSpacing(0); self.grid.setMargin(0) for row,col in itertools.product(range(2),range(5)): # one additional column for vertical line in quaternion if (row,col)==(0,3): continue if (row,col)==(0,4): self.grid.addWidget(QLabel(u'←posori',self),row,col); continue if (row,col)==(1,3): f=QFrame(self); f.setFrameShape(QFrame.VLine); f.setFrameShadow(QFrame.Sunken); f.setFixedWidth(4); self.grid.addWidget(f,row,col); continue w=QLineEdit('') self.grid.addWidget(w,row,col); w.textEdited.connect(self.isHot) w.selectionChanged.connect(self.isHot) w.editingFinished.connect(self.update) def refresh(self): pos,ori=self.getter(); axis,angle=ori.toAxisAngle() for i in (0,1,2,4): #if True or not w.hasFocus(): w.home(False) w=self.grid.itemAtPosition(1,i).widget(); if (not w.hasFocus()): w.setText(str(axis[i] if i<3 else angle)); w.home(False) for i in (0,1,2): #if True or not w.hasFocus(): w.home(False) w=self.grid.itemAtPosition(0,i).widget(); if (not w.hasFocus()): w.setText(str(pos[i])); w.home(False) def update(self): try: q=[float((self.grid.itemAtPosition(1,i).widget().text())) for i in (0,1,2,4)] v=[float((self.grid.itemAtPosition(0,i).widget().text())) for i in (0,1,2)] except ValueError: self.refresh() return qq=Quaternion(Vector3(q[0],q[1],q[2]),q[3]); qq.normalize() # from axis-angle self.trySetter((v,qq)) def setFocus(self): self.grid.itemAtPosition(0,0).widget().setFocus() class AttrEditor_MatrixX(AttrEditor,QFrame): def __init__(self,parent,getter,setter,rows,cols,idxConverter): 'idxConverter converts row,col tuple to either (row,col), (col) etc depending on what access is used for []' AttrEditor.__init__(self,getter,setter) QFrame.__init__(self,parent) self.rows,self.cols=rows,cols self.idxConverter=idxConverter self.setContentsMargins(0,0,0,0) self.first=True val=self.getter() self.grid=QGridLayout(self); self.grid.setSpacing(0); self.grid.setMargin(0) for row,col in itertools.product(range(self.rows),range(self.cols)): w=QLineEdit('') self.grid.addWidget(w,row,col); w.textEdited.connect(self.isHot) w.selectionChanged.connect(self.isHot) w.editingFinished.connect(self.update) def refresh(self,force=False): val=self.getter() for row,col in itertools.product(range(self.rows),range(self.cols)): w=self.grid.itemAtPosition(row,col).widget() if(self.first or force): w.setText(str(val[self.idxConverter(row,col)])) if (not w.hasFocus): w.setText(str(val[self.idxConverter(row,col)])) w.home(False) # make the left-most part visible, if the text is wider than the widget self.first=False def update(self): try: val=self.getter() for row,col in itertools.product(range(self.rows),range(self.cols)): w=self.grid.itemAtPosition(row,col).widget() if w.isModified(): val[self.idxConverter(row,col)]=float(w.text()) logging.debug('setting'+str(val)) self.trySetter(val) except ValueError: self.refresh(force=True) def setFocus(self): self.grid.itemAtPosition(0,0).widget().setFocus() class AttrEditor_MatrixXi(AttrEditor,QFrame): def __init__(self,parent,getter,setter,rows,cols,idxConverter): 'idxConverter converts row,col tuple to either (row,col), (col) etc depending on what access is used for []' AttrEditor.__init__(self,getter,setter) QFrame.__init__(self,parent) self.rows,self.cols=rows,cols self.idxConverter=idxConverter self.setContentsMargins(0,0,0,0) self.grid=QGridLayout(self); self.grid.setSpacing(0); self.grid.setMargin(0) for row,col in itertools.product(range(self.rows),range(self.cols)): w=QSpinBox() w.setRange(int(-1e9),int(1e9)); w.setSingleStep(1); self.grid.addWidget(w,row,col); self.refresh() # refresh before connecting signals! for row,col in itertools.product(range(self.rows),range(self.cols)): self.grid.itemAtPosition(row,col).widget().valueChanged.connect(self.update) def refresh(self): val=self.getter() for row,col in itertools.product(range(self.rows),range(self.cols)): w=self.grid.itemAtPosition(row,col).widget().setValue(val[self.idxConverter(row,col)]) def update(self): val=self.getter(); modified=False for row,col in itertools.product(range(self.rows),range(self.cols)): w=self.grid.itemAtPosition(row,col).widget() if w.value()!=val[self.idxConverter(row,col)]: modified=True; val[self.idxConverter(row,col)]=w.value() if not modified: return logging.debug('setting'+str(val)) self.trySetter(val) def setFocus(self): self.grid.itemAtPosition(0,0).widget().setFocus() class AttrEditor_Vector6i(AttrEditor_MatrixXi): def __init__(self,parent,getter,setter): AttrEditor_MatrixXi.__init__(self,parent,getter,setter,1,6,lambda r,c:c) class AttrEditor_Vector3i(AttrEditor_MatrixXi): def __init__(self,parent,getter,setter): AttrEditor_MatrixXi.__init__(self,parent,getter,setter,1,3,lambda r,c:c) class AttrEditor_Vector2i(AttrEditor_MatrixXi): def __init__(self,parent,getter,setter): AttrEditor_MatrixXi.__init__(self,parent,getter,setter,1,2,lambda r,c:c) class AttrEditor_Vector6(AttrEditor_MatrixX): def __init__(self,parent,getter,setter): AttrEditor_MatrixX.__init__(self,parent,getter,setter,1,6,lambda r,c:c) class AttrEditor_Vector3(AttrEditor_MatrixX): def __init__(self,parent,getter,setter): AttrEditor_MatrixX.__init__(self,parent,getter,setter,1,3,lambda r,c:c) class AttrEditor_Vector2(AttrEditor_MatrixX): def __init__(self,parent,getter,setter): AttrEditor_MatrixX.__init__(self,parent,getter,setter,1,2,lambda r,c:c) class AttrEditor_Matrix3(AttrEditor_MatrixX): def __init__(self,parent,getter,setter): AttrEditor_MatrixX.__init__(self,parent,getter,setter,3,3,lambda r,c:(r,c)) class Se3FakeType: pass _fundamentalEditorMap={bool:AttrEditor_Bool,str:AttrEditor_Str,int:AttrEditor_Int,float:AttrEditor_Float,complex:AttrEditor_Complex,Quaternion:AttrEditor_Quaternion,Vector2:AttrEditor_Vector2,Vector3:AttrEditor_Vector3,Vector6:AttrEditor_Vector6,Matrix3:AttrEditor_Matrix3,Vector6i:AttrEditor_Vector6i,Vector3i:AttrEditor_Vector3i,Vector2i:AttrEditor_Vector2i,Se3FakeType:AttrEditor_Se3} _fundamentalInitValues={bool:True,str:'',int:0,float:0.0,complex:0.0j,Quaternion:Quaternion((0,1,0),0.0),Vector3:Vector3.Zero,Matrix3:Matrix3.Zero,Vector6:Vector6.Zero,Vector6i:Vector6i.Zero,Vector3i:Vector3i.Zero,Vector2i:Vector2i.Zero,Vector2:Vector2.Zero,Se3FakeType:(Vector3.Zero,Quaternion((0,1,0),0.0))} class SerQLabel(QLabel): def __init__(self,parent,label,tooltip,path): QLabel.__init__(self,parent) self.path=path self.setText(label) if tooltip or path: self.setToolTip((''+path+'
    ' if self.path else '')+(tooltip if tooltip else '')) self.linkActivated.connect(yade.qt.openUrl) def mousePressEvent(self,event): if event.button()!=Qt.MidButton: event.ignore(); return # middle button clicked, paste pasteText to clipboard cb=QApplication.clipboard() cb.setText(self.path,mode=QClipboard.Clipboard) cb.setText(self.path,mode=QClipboard.Selection) # X11 global selection buffer event.accept() class SerializableEditor(QFrame): "Class displaying and modifying serializable attributes of a yade object." import collections import logging # each attribute has one entry associated with itself class EntryData: def __init__(self,name,T,flags=0): self.name,self.T,self.flags=name,T,flags self.lineNo,self.widget=None,None def __init__(self,ser,parent=None,ignoredAttrs=set(),showType=False,path=None): "Construct window, *ser* is the object we want to show." QtGui.QFrame.__init__(self,parent) self.ser=ser self.path=(ser.label if (hasattr(ser,'label') and ser.label) else path) self.showType=showType self.hot=False self.entries=[] self.ignoredAttrs=ignoredAttrs logging.debug('New Serializable of type %s'%ser.__class__.__name__) self.setWindowTitle(str(ser)) self.mkWidgets() self.refreshTimer=QTimer(self) self.refreshTimer.timeout.connect(self.refreshEvent) self.refreshTimer.start(500) def getListTypeFromDocstring(self,attr): "Guess type of array by scanning docstring for :yattrtype: and parsing its argument; ugly, but works." doc=getattr(self.ser.__class__,attr).__doc__ if doc==None: logging.error("Attribute %s has no docstring."%attr) return None m=re.search(r':yattrtype:`([^`]*)`',doc) if not m: logging.error("Attribute %s does not contain :yattrtype:`....` (docstring is '%s'"%(attr,doc)) return None cxxT=m.group(1) logging.debug('Got type "%s" from :yattrtype:'%cxxT) def vecTest(T,cxxT): #regexp=r'^\s*(std\s*::)?\s*vector\s*<\s*(std\s*::)?\s*('+T+r')\s*>\s*$' regexp=r'^\s*(std\s*::)?\s*vector\s*<\s*(shared_ptr\s*<\s*)?\s*(std\s*::)?\s*('+T+r')(\s*>)?\s*>\s*$' m=re.match(regexp,cxxT) return m vecMap={ 'bool':bool,'int':int,'long':int,'Body::id_t':long,'size_t':long, 'Real':float,'float':float,'double':float,'complex':complex,'std::complex':complex, 'Vector6r':Vector6,'Vector6i':Vector6i,'Vector3i':Vector3i,'Vector2r':Vector2,'Vector2i':Vector2i, 'Vector3r':Vector3,'Matrix3r':Matrix3,'Se3r':Se3FakeType, 'string':str, #'BodyCallback':BodyCallback, 'IntrCallback':IntrCallback,'BoundFunctor':BoundFunctor,'IGeomFunctor':IGeomFunctor,'IPhysFunctor':IPhysFunctor,'LawFunctor':LawFunctor,'KinematicEngine':KinematicEngine, 'GlShapeFunctor':GlShapeFunctor,'GlStateFunctor':GlStateFunctor,'GlIGeomFunctor':GlIGeomFunctor,'GlIPhysFunctor':GlIPhysFunctor,'GlBoundFunctor':GlBoundFunctor,'GlExtraDrawer':GlExtraDrawer } for T,ret in vecMap.items(): if vecTest(T,cxxT): logging.debug("Got type %s from cxx type %s"%(repr(ret),cxxT)) return (ret,) logging.error("Unable to guess python type from cxx type '%s'"%cxxT) return None def mkAttrEntries(self): if self.ser==None: return try: d=self.ser.dict() except TypeError: logging.error('TypeError when getting attributes of '+str(self.ser)+',skipping. ') import traceback traceback.print_exc() attrs=self.ser.dict().keys(); attrs.sort() for attr in attrs: val=getattr(self.ser,attr) # get the value using serattr, as it might be different from what the dictionary provides (e.g. Body.blockedDOFs) t=None doc=getattr(self.ser.__class__,attr).__doc__; # some attributes are not shown if '|yhidden|' in doc: continue if attr in self.ignoredAttrs: continue # FIXME: (Janek) Implementing Quantum Mechanics makes some DEM assumptions # invalid. I think that we should rethink what base class Body contains, so # that in QM we would not need to use this hack to hide some variables. # However it is great to note that only this little 'cosmetic' hack is needed # to make Quantum Mechanics possible in yade # See also: class QuantumMechanicalState, class QuantumMechanicalBody, gui/qt4/SerializableEditor.py if hasattr(self.ser,"qtHide") and (attr in getattr(self.ser,"qtHide").split()): continue if isinstance(val,list): t=self.getListTypeFromDocstring(attr) if not t and len(val)==0: t=(val[0].__class__,) # 1-tuple is list of the contained type #if not t: raise RuntimeError('Unable to guess type of '+str(self.ser)+'.'+attr) # hack for Se3, which is returned as (Vector3,Quaternion) in python elif isinstance(val,tuple) and len(val)==2 and val[0].__class__==Vector3 and val[1].__class__==Quaternion: t=Se3FakeType else: t=val.__class__ match=re.search(':yattrflags:`\s*([0-9]+)\s*`',doc) # non-empty attribute flags=int(match.group(1)) if match else 0 #logging.debug('Attr %s is of type %s'%(attr,((t[0].__name__,) if isinstance(t,tuple) else t.__name__))) self.entries.append(self.EntryData(name=attr,T=t)) #print("name: "+str(attr)+"\tflag: "+str(flags)) def getDocstring(self,attr=None): "If attr is *None*, return docstring of the Serializable itself" doc=(getattr(self.ser.__class__,attr).__doc__ if attr else self.ser.__class__.__doc__) if not doc: return '' doc=re.sub(':y(attrtype|default|attrflags):`[^`]*`','',doc) statAttr=re.compile('^.. ystaticattr::.*$',re.MULTILINE|re.DOTALL) doc=re.sub(statAttr,'',doc) # static classes have their proper docs at the beginning, discard static memeber docs # static: attribute of the type is the same object as attribute of the instance # in that case, get docstring from the class documentation by parsing it if attr and getattr(self.ser.__class__,attr)==getattr(self.ser,attr): doc=self.getStaticAttrDocstring(attr) doc=re.sub(':yref:`([^`]*)`','\\1',doc) import textwrap wrapper=textwrap.TextWrapper(replace_whitespace=False) return wrapper.fill(textwrap.dedent(doc)) def getStaticAttrDocstring(self,attr): ret=''; c=self.ser.__class__ while hasattr(c,attr) and hasattr(c.__base__,attr): c=c.__base__ start='.. ystaticattr:: %s.%s('%(c.__name__,attr) if start in c.__doc__: ll=c.__doc__.split('\n') for i in range(len(ll)): if ll[i].startswith(start): break for i in range(i+1,len(ll)): if len(ll[i])>0 and ll[i][0] not in ' \t': break ret+=ll[i] return ret else: return '[no documentation found]' def mkWidget(self,entry): if not entry.T: return None # single fundamental object Klass=_fundamentalEditorMap.get(entry.T,None) getter,setter=lambda: getattr(self.ser,entry.name), lambda x: setattr(self.ser,entry.name,x) if Klass: widget=Klass(self,getter=getter,setter=setter) widget.setFocusPolicy(Qt.StrongFocus) if (entry.flags & AttrFlags.readonly): widget.setEnabled(False) return widget # sequences if entry.T.__class__==tuple: assert(len(entry.T)==1) # we don't handle tuples of other lenghts # sequence of serializables T=entry.T[0] if (issubclass(T,Serializable) or T==Serializable): widget=SeqSerializable(self,getter,setter,T,path=(self.path+'.'+entry.name if self.path else None),shrink=True) return widget if (T in _fundamentalEditorMap): widget=SeqFundamentalEditor(self,getter,setter,T) return widget return None # a serializable if issubclass(entry.T,Serializable) or entry.T==Serializable: obj=getattr(self.ser,entry.name) if hasattr(obj,'label') and obj.label: path=obj.label elif self.path: path=self.path+'.'+entry.name else: path=None widget=SerializableEditor(getattr(self.ser,entry.name),parent=self,showType=self.showType,path=(self.path+'.'+entry.name if self.path else None)) widget.setFrameShape(QFrame.Box); widget.setFrameShadow(QFrame.Raised); widget.setLineWidth(1) return widget return None def mkWidgets(self): self.mkAttrEntries() grid=QFormLayout() grid.setContentsMargins(2,2,2,2) grid.setVerticalSpacing(0) grid.setLabelAlignment(Qt.AlignRight) if self.showType: lab=SerQLabel(self,makeSerializableLabel(self.ser,addr=True,href=True),tooltip=self.getDocstring(),path=self.path) lab.setFrameShape(QFrame.Box); lab.setFrameShadow(QFrame.Sunken); lab.setLineWidth(2); lab.setAlignment(Qt.AlignHCenter); lab.linkActivated.connect(yade.qt.openUrl) grid.setWidget(0,QFormLayout.SpanningRole,lab) for entry in self.entries: entry.widget=self.mkWidget(entry) objPath=(self.path+'.'+entry.name) if self.path else None label=SerQLabel(self,serializableHref(self.ser,entry.name),tooltip=self.getDocstring(entry.name),path=objPath) grid.addRow(label,entry.widget if entry.widget else QLabel('unhandled type')) self.setLayout(grid) self.refreshEvent() def refreshEvent(self): for e in self.entries: if e.widget and not e.widget.hot: e.widget.refresh() def refresh(self): pass def makeSerializableLabel(ser,href=False,addr=True,boldHref=True,num=-1,count=-1): ret=u'' if num>=0: if count>=0: ret+=u'%d/%d. '%(num,count) else: ret+=u'%d. '%num if href: ret+=(u' ' if boldHref else u' ')+serializableHref(ser)+(u' ' if boldHref else u' ') else: ret+=ser.__class__.__name__+' ' if hasattr(ser,'label') and ser.label: ret+=u' “'+unicode(ser.label)+u'”' # do not show address if there is a label already elif addr: import re ss=unicode(ser); m=re.match(u'<(.*) instance at (0x.*)>',ss) if m: ret+=m.group(2) else: logging.warning(u"Serializable converted to str ('%s') does not contain 'instance at 0x…'"%ss) return ret class SeqSerializableComboBox(QFrame): def __init__(self,parent,getter,setter,serType,path=None,shrink=False): QFrame.__init__(self,parent) self.getter,self.setter,self.serType,self.path,self.shrink=getter,setter,serType,path,shrink self.layout=QVBoxLayout(self) topLineFrame=QFrame(self) topLineLayout=QHBoxLayout(topLineFrame); for l in self.layout, topLineLayout: l.setSpacing(0); l.setContentsMargins(0,0,0,0) topLineFrame.setLayout(topLineLayout) buttons=(self.newButton,self.killButton,self.upButton,self.downButton)=[QPushButton(label,self) for label in (u'☘',u'☠',u'↑',u'↓')] buttonSlots=(self.newSlot,self.killSlot,self.upSlot,self.downSlot) # same order as buttons for b in buttons: b.setStyleSheet('QPushButton { font-size: 15pt; }'); b.setFixedWidth(30); b.setFixedHeight(30) self.combo=QComboBox(self) self.combo.setSizeAdjustPolicy(QComboBox.AdjustToContents) for w in buttons[0:2]+[self.combo,]+buttons[2:4]: topLineLayout.addWidget(w) self.layout.addWidget(topLineFrame) # nested layout self.scroll=QScrollArea(self); self.scroll.setWidgetResizable(True) self.layout.addWidget(self.scroll) self.seqEdit=None # currently edited serializable self.setLayout(self.layout) self.hot=None # API compat with SerializableEditor self.setFrameShape(QFrame.Box); self.setFrameShadow(QFrame.Raised); self.setLineWidth(1) # signals for b,slot in zip(buttons,buttonSlots): b.clicked.connect(slot) self.combo.currentIndexChanged.connect(self.comboIndexSlot) self.refreshEvent() # periodic refresh self.refreshTimer=QTimer(self) self.refreshTimer.timeout.connect(self.refreshEvent) self.refreshTimer.start(1000) # 1s should be enough #print 'SeqSerializable path is',self.path def comboIndexSlot(self,ix): # different seq item selected currSeq=self.getter(); if len(currSeq)==0: ix=-1 logging.debug('%s comboIndexSlot len=%d, ix=%d'%(self.serType.__name__,len(currSeq),ix)) self.downButton.setEnabled(ix0) self.combo.setEnabled(ix>=0) if ix>=0: ser=currSeq[ix] self.seqEdit=SerializableEditor(ser,parent=self,showType=seqSerializableShowType,path=(self.path+'['+str(ix)+']') if self.path else None) self.scroll.setWidget(self.seqEdit) if self.shrink: self.sizeHint=lambda: QSize(100,1000) self.scroll.sizeHint=lambda: QSize(100,1000) self.sizePolicy().setVerticalPolicy(QSizePolicy.Expanding) self.scroll.sizePolicy().setVerticalPolicy(QSizePolicy.Expanding) self.setMinimumHeight(min(300,self.seqEdit.height()+self.combo.height()+10)) self.setMaximumHeight(100000) self.scroll.setMaximumHeight(100000) else: self.scroll.setWidget(QFrame()) if self.shrink: self.setMaximumHeight(self.combo.height()+10); self.scroll.setMaximumHeight(0) def serLabel(self,ser,i=-1): return ('' if i<0 else str(i)+'. ')+str(ser)[1:-1].replace('instance at ','') def refreshEvent(self,forceIx=-1): currSeq=self.getter() comboEnabled=self.combo.isEnabled() if comboEnabled and len(currSeq)==0: self.comboIndexSlot(-1) # force refresh, otherwise would not happen from the initially empty state ix,cnt=self.combo.currentIndex(),self.combo.count() # serializable currently being edited (which can be absent) or the one of which index is forced ser=(self.seqEdit.ser if self.seqEdit else None) if forceIx<0 else currSeq[forceIx] if comboEnabled and len(currSeq)==cnt and (ix<0 or ser==currSeq[ix]): return if not comboEnabled and len(currSeq)==0: return logging.debug(self.serType.__name__+' rebuilding list from scratch') self.combo.clear() if len(currSeq)>0: prevIx=-1 for i,s in enumerate(currSeq): self.combo.addItem(makeSerializableLabel(s,num=i,count=len(currSeq),addr=False)) if s==ser: prevIx=i if forceIx>=0: newIx=forceIx # force the index (used from newSlot to make the new element active) elif prevIx>=0: newIx=prevIx # if found what was active before, use it elif ix>=0: newIx=ix # otherwise use the previous index (e.g. after deletion) else: newIx=0 # fallback to 0 logging.debug('%s setting index %d'%(self.serType.__name__,newIx)) self.combo.setCurrentIndex(newIx) else: logging.debug('%s EMPTY, setting index 0'%(self.serType.__name__)) self.combo.setCurrentIndex(-1) self.killButton.setEnabled(len(currSeq)>0) def newSlot(self): dialog=NewSerializableDialog(self,self.serType.__name__) if not dialog.exec_(): return # cancelled ser=dialog.result() ix=self.combo.currentIndex() currSeq=self.getter(); currSeq.insert(ix,ser); self.setter(currSeq) logging.debug('%s new item created at index %d'%(self.serType.__name__,ix)) self.refreshEvent(forceIx=ix) def killSlot(self): ix=self.combo.currentIndex() currSeq=self.getter(); del currSeq[ix]; self.setter(currSeq) self.refreshEvent() def upSlot(self): i=self.combo.currentIndex() assert(i>0) currSeq=self.getter(); prev,curr=currSeq[i-1:i+1]; currSeq[i-1],currSeq[i]=curr,prev; self.setter(currSeq) self.refreshEvent(forceIx=i-1) def downSlot(self): i=self.combo.currentIndex() currSeq=self.getter(); assert(i=0 else None menu=QMenu(self) actNew,actKill,actUp,actDown=[menu.addAction(name) for name in (u'☘ New',u'☠ Remove',u'↑ Up',u'↓ Down')] if index<0: [a.setEnabled(False) for a in actKill,actUp,actDown] if index==len(seq)-1: actDown.setEnabled(False) if index==0: actUp.setEnabled(False) if field: field.setStyleSheet('QWidget { background: green }') act=menu.exec_(self.mapToGlobal(event.pos())) if field: field.setStyleSheet('QWidget { background: none }') if not act: return if act==actNew: self.newSlot(index) elif act==actKill: self.killSlot(index) elif act==actUp: self.upSlot(index) elif act==actDown: self.downSlot(index) def localPositionToIndex(self,pos): gp=self.mapToGlobal(pos) for row in range(self.form.count()/2): w,i=self.form.itemAt(row,QFormLayout.FieldRole),self.form.itemAt(row,QFormLayout.LabelRole) for wi in w.widget(),i.widget(): x0,y0,x1,y1=wi.geometry().getCoords(); globG=QRect(self.mapToGlobal(QPoint(x0,y0)),self.mapToGlobal(QPoint(x1,y1))) if globG.contains(gp): return row return -1 def newSlot(self,i): seq=self.getter(); seq.insert(i,_fundamentalInitValues.get(self.itemType,self.itemType())) self.setter(seq) self.rebuild() def killSlot(self,i): seq=self.getter(); assert(iError',errMsg) return class ItemGetter(): def __init__(self,getter,index): self.getter,self.index=getter,index def __call__(self): return self.getter()[self.index] class ItemSetter(): def __init__(self,getter,setter,index): self.getter,self.setter,self.index=getter,setter,index def __call__(self,val): seq=self.getter(); seq[self.index]=val; self.setter(seq) for i,item in enumerate(currSeq): widget=Klass(self,ItemGetter(self.getter,i),ItemSetter(self.getter,self.setter,i)) #proxy,'value') self.form.insertRow(i,'%d. '%i,widget) logging.debug('added item %d %s'%(i,str(widget))) if len(currSeq)==0: self.form.insertRow(0,'empty',QLabel('(right-click for menu)')) logging.debug('rebuilt, will refresh now') self.refreshEvent(dontRebuild=True) # avoid infinite recursion it the length would change meanwhile def refreshEvent(self,dontRebuild=False,forceIx=-1): currSeq=self.getter() if len(currSeq)!=self.form.count()/2: #rowCount(): if dontRebuild: return # length changed behind our back, just pretend nothing happened and update next time instead self.rebuild() currSeq=self.getter() for i in range(len(currSeq)): item=self.form.itemAt(i,QFormLayout.FieldRole) logging.trace('got item #%d %s'%(i,str(item.widget()))) widget=item.widget() if not widget.hot: widget.refresh() if forceIx>=0 and forceIx==i: widget.setFocus() def refresh(self): pass # SerializableEditor API trunk-2018.02b/gui/qt4/XYZ.png000066400000000000000000000007011324306050200156600ustar00rootroot00000000000000PNG  IHDR((msRGBbKGD pHYs  tIME ($`iAIDATXؿ.DAߊxD JT$^-- :DOH$!H(p.{WK$79>(/b0X@)LG+X_P 0 J8-"3fS-DO5'Nҏ1&f}ѣKqń`f , y{Bkyj땎p2,} 4tɾ>4k-$ow⼁5kl`W7 ^X'~qVɣ= ʭ"ymj^462jO0u)%P .rbIENDB`trunk-2018.02b/gui/qt4/YZX.xpm000066400000000000000000000034571324306050200157130ustar00rootroot00000000000000/* XPM */ static char * YZX_xpm[] = { "40 40 2 1", " c None", ". c #000000", " ...... ", " .. ", " .. ", " .. ", " . ", " .. ", " .. ", " ...... ", " ", " . ", " . ", " ... ", " ... ", " ... ", " ..... ", " ..... ", " . ", " . ", " . ", " . ", " . ", " . ", " . ", " . ", " . ", " . ", " ........... ", " . . ", " . .. .. . .. .. ", " . . . . .. . . ", " . .... . ..... .... ", " . .. .................... .... ", " . .. . ..... .. ", " . .... . ... .. ", " . . . . .. ", " . .. .. . .. ", " . . ", " ........... ", " ", " "}; trunk-2018.02b/gui/qt4/ZXY.png000066400000000000000000000007111324306050200156610ustar00rootroot00000000000000PNG  IHDR((msRGBbKGD pHYs  tIME )EZIIDATXؽJCAJXhi kKS 쵉 "hM".rYvc1bѠ7a=MaEI<$R)OZ 6alf6&-\WߒPy $bW)C,)R(`j3`"&h|5Q^ky )>UP*u8ݬP=/ `^b]78` .; *r Sjk+6HǠ։mcotlbo96`7LGeǨL:uV@&~mԕI 'tAIENDB`trunk-2018.02b/gui/qt4/ZXY.xpm000066400000000000000000000034571324306050200157130ustar00rootroot00000000000000/* XPM */ static char * ZXY_xpm[] = { "40 40 2 1", " c None", ". c #000000", " .. .. ", " . . ", " .... ", " .. ", " .. ", " .... ", " . . ", " .. .. ", " ", " . ", " . ", " ... ", " ... ", " ... ", " ..... ", " ..... ", " . ", " . ", " . ", " . ", " . ", " . ", " . ", " . ", " . ", " . ", " ........... ", " . . ", " . .. .. . ...... ", " . . . . .. .. ", " . .... . ..... .. ", " . .... .................... .. ", " . .. . ..... . ", " . .. . ... .. ", " . .. . .. ", " . .. . ...... ", " . . ", " ........... ", " ", " "}; trunk-2018.02b/gui/qt4/_GLViewer.cpp000066400000000000000000000214451324306050200170170ustar00rootroot00000000000000#include"GLViewer.hpp" #include"OpenGLManager.hpp" #include #include #include #include namespace py=boost::python; qglviewer::Vec tuple2vec(py::tuple t){ qglviewer::Vec ret; for(int i=0;i<3;i++){py::extract e(t[i]); if(!e.check()) throw invalid_argument("Element #"+boost::lexical_cast(i)+" is not a number"); ret[i]=e();} return ret;}; py::tuple vec2tuple(qglviewer::Vec v){return py::make_tuple(v[0],v[1],v[2]);}; class pyGLViewer{ const size_t viewNo; public: #define GLV if((OpenGLManager::self->views.size()<=viewNo) || !(OpenGLManager::self->views[viewNo])) throw runtime_error("No view #"+boost::lexical_cast(viewNo)); GLViewer* glv=OpenGLManager::self->views[viewNo].get(); pyGLViewer(size_t _viewNo=0): viewNo(_viewNo){} void close(){ GLV; QCloseEvent* e(new QCloseEvent); QApplication::postEvent(glv,e); } py::tuple get_grid(){GLV; return py::make_tuple(bool(glv->drawGrid & 1),bool(glv->drawGrid & 2),bool(glv->drawGrid & 4));} void set_grid(py::tuple t){GLV; glv->drawGrid=0; for(int i=0;i<3;i++) if(py::extract(t[i])()) glv->drawGrid+=1<camera()->upVector,glv->camera()->setUpVector); VEC_GET_SET(lookAt,glv->camera()->position()+glv->camera()->viewDirection,glv->camera()->lookAt); VEC_GET_SET(viewDir,glv->camera()->viewDirection,glv->camera()->setViewDirection); VEC_GET_SET(eyePosition,glv->camera()->position,glv->camera()->setPosition); #define BOOL_GET_SET(property,getter,setter)void set_##property(bool b){GLV; setter(b);} bool get_##property(){GLV; return getter();} BOOL_GET_SET(axes,glv->axisIsDrawn,glv->setAxisIsDrawn); BOOL_GET_SET(fps,glv->FPSIsDisplayed,glv->setFPSIsDisplayed); bool get_scale(){GLV; return glv->drawScale;} void set_scale(bool b){GLV; glv->drawScale=b;} bool get_orthographic(){GLV; return glv->camera()->type()==qglviewer::Camera::ORTHOGRAPHIC;} void set_orthographic(bool b){GLV; return glv->camera()->setType(b ? qglviewer::Camera::ORTHOGRAPHIC : qglviewer::Camera::PERSPECTIVE);} int get_selection(void){ GLV; return glv->selectedName(); } void set_selection(int s){ GLV; glv->setSelectedName(s); } #define FLOAT_GET_SET(property,getter,setter)void set_##property(Real r){GLV; setter(r);} Real get_##property(){GLV; return getter();} FLOAT_GET_SET(sceneRadius,glv->sceneRadius,glv->setSceneRadius); void fitAABB(const Vector3r& min, const Vector3r& max){GLV; glv->camera()->fitBoundingBox(qglviewer::Vec(min[0],min[1],min[2]),qglviewer::Vec(max[0],max[1],max[2]));} void fitSphere(const Vector3r& center,Real radius){GLV; glv->camera()->fitSphere(qglviewer::Vec(center[0],center[1],center[2]),radius);} void showEntireScene(){GLV; glv->camera()->showEntireScene();} void center(bool median){GLV; if(median)glv->centerMedianQuartile(); else glv->centerScene();} Vector2i get_screenSize(){GLV; return Vector2i(glv->width(),glv->height());} void set_screenSize(Vector2i t){ /*GLV;*/ OpenGLManager::self->emitResizeView(viewNo,t[0],t[1]);} string pyStr(){return string("(viewNo)+">";} void saveDisplayParameters(size_t n){GLV; glv->saveDisplayParameters(n);} void useDisplayParameters(size_t n){GLV; glv->useDisplayParameters(n);} void loadState(string filename){GLV; QString origStateFileName=glv->stateFileName(); glv->setStateFileName(QString(filename.c_str())); glv->restoreStateFromFile(); glv->saveStateToFile(); glv->setStateFileName(origStateFileName);} void saveState(string filename){GLV; QString origStateFileName=glv->stateFileName(); glv->setStateFileName(QString(filename.c_str())); glv->saveStateToFile(); glv->setStateFileName(origStateFileName);} string get_timeDisp(){GLV; const int& m(glv->timeDispMask); string ret; if(m&GLViewer::TIME_REAL) ret+='r'; if(m&GLViewer::TIME_VIRT) ret+="v"; if(m&GLViewer::TIME_ITER) ret+="i"; return ret;} void set_timeDisp(string s){GLV; int& m(glv->timeDispMask); m=0; FOREACH(char c, s){switch(c){case 'r': m|=GLViewer::TIME_REAL; break; case 'v': m|=GLViewer::TIME_VIRT; break; case 'i': m|=GLViewer::TIME_ITER; break; default: throw invalid_argument(string("Invalid flag for timeDisp: `")+c+"'");}}} void set_bgColor(const Vector3r& c){ QColor cc(255*c[0],255*c[1],255*c[2]); GLV; glv->setBackgroundColor(cc);} Vector3r get_bgColor(){ GLV; QColor c(glv->backgroundColor()); return Vector3r(c.red()/255.,c.green()/255.,c.blue()/255.);} void saveSnapshot(string filename) {GLV; glv->nextFrameSnapshotFilename = filename;} #undef GLV #undef VEC_GET_SET #undef BOOL_GET_SET #undef FLOAT_GET_SET }; // ask to create a new view and wait till it exists pyGLViewer createView(){ int id=OpenGLManager::self->waitForNewView(); if(id<0) throw std::runtime_error("Unable to open new 3d view."); return pyGLViewer((*OpenGLManager::self->views.rbegin())->viewId); } py::list getAllViews(){ py::list ret; FOREACH(const shared_ptr& v, OpenGLManager::self->views){ if(v) ret.append(pyGLViewer(v->viewId)); } return ret; }; void centerViews(void){ OpenGLManager::self->centerAllViews(); } shared_ptr getRenderer(){ return OpenGLManager::self->renderer; } BOOST_PYTHON_MODULE(_GLViewer){ YADE_SET_DOCSTRING_OPTS; OpenGLManager* glm=new OpenGLManager(); // keep this singleton object forever glm->emitStartTimer(); py::def("View",createView,"Create a new 3d view."); py::def("center",centerViews,"Center all views."); py::def("views",getAllViews,"Return list of all open :yref:`yade.qt.GLViewer` objects"); py::def("Renderer",&getRenderer,"Return the active :yref:`OpenGLRenderer` object."); py::class_("GLViewer",py::no_init) .add_property("upVector",&pyGLViewer::get_upVector,&pyGLViewer::set_upVector,"Vector that will be shown oriented up on the screen.") .add_property("lookAt",&pyGLViewer::get_lookAt,&pyGLViewer::set_lookAt,"Point at which camera is directed.") .add_property("viewDir",&pyGLViewer::get_viewDir,&pyGLViewer::set_viewDir,"Camera orientation (as vector).") .add_property("eyePosition",&pyGLViewer::get_eyePosition,&pyGLViewer::set_eyePosition,"Camera position.") .add_property("grid",&pyGLViewer::get_grid,&pyGLViewer::set_grid,"Display square grid in zero planes, as 3-tuple of bools for yz, xz, xy planes.") .add_property("fps",&pyGLViewer::get_fps,&pyGLViewer::set_fps,"Show frames per second indicator.") .add_property("axes",&pyGLViewer::get_axes,&pyGLViewer::set_axes,"Show arrows for axes.") .add_property("scale",&pyGLViewer::get_scale,&pyGLViewer::set_scale,"Scale of the view (?)") .add_property("sceneRadius",&pyGLViewer::get_sceneRadius,&pyGLViewer::set_sceneRadius,"Visible scene radius.") .add_property("ortho",&pyGLViewer::get_orthographic,&pyGLViewer::set_orthographic,"Whether orthographic projection is used; if false, use perspective projection.") .add_property("screenSize",&pyGLViewer::get_screenSize,&pyGLViewer::set_screenSize,"Size of the viewer's window, in scree pixels") .add_property("timeDisp",&pyGLViewer::get_timeDisp,&pyGLViewer::set_timeDisp,"Time displayed on in the vindow; is a string composed of characters *r*, *v*, *i* standing respectively for real time, virtual time, iteration number.") // .add_property("bgColor",&pyGLViewer::get_bgColor,&pyGLViewer::set_bgColor) // useless: OpenGLRenderer::Background_color is used via openGL directly, bypassing QGLViewer background property .def("fitAABB",&pyGLViewer::fitAABB,(py::arg("mn"),py::arg("mx")),"Adjust scene bounds so that Axis-aligned bounding box given by its lower and upper corners *mn*, *mx* fits in.") .def("fitSphere",&pyGLViewer::fitSphere,(py::arg("center"),py::arg("radius")),"Adjust scene bounds so that sphere given by *center* and *radius* fits in.") .def("showEntireScene",&pyGLViewer::showEntireScene) .def("center",&pyGLViewer::center,(py::arg("median")=true),"Center view. View is centered either so that all bodies fit inside (*median* = False), or so that 75\% of bodies fit inside (*median* = True).") .def("saveState",&pyGLViewer::saveState,(py::arg("stateFilename")=".qglviewer.xml"),"Save display parameters into a file. Saves state for both :yref:`GLViewer` and associated :yref:`OpenGLRenderer`.") .def("loadState",&pyGLViewer::loadState,(py::arg("stateFilename")=".qglviewer.xml"),"Load display parameters from file saved previously into.") .def("__repr__",&pyGLViewer::pyStr).def("__str__",&pyGLViewer::pyStr) .def("close",&pyGLViewer::close) .def("saveSnapshot",&pyGLViewer::saveSnapshot,(py::arg("filename")),"Save the current view to image file") .add_property("selection",&pyGLViewer::get_selection,&pyGLViewer::set_selection) ; } trunk-2018.02b/gui/qt4/__init__.py000066400000000000000000000251221324306050200165750ustar00rootroot00000000000000# encoding: utf-8 import yade.runtime if not yade.runtime.hasDisplay: msg = "Connecting to DISPLAY at Yade startup failed, unable to activate the qt4 interface." import os if 'YADE_BATCH' in os.environ: msg += "\nDo not import qt when running in batch mode." raise ImportError(msg) from PyQt4.QtGui import * from PyQt4 import QtCore from yade.qt.ui_controller import Ui_Controller from yade.qt.Inspector import * from yade import * import yade.system, yade.config from yade.qt._GLViewer import * maxWebWindows=1 "Number of webkit windows that will be cycled to show help on clickable objects" webWindows=[] "holds instances of QtWebKit windows; clicking an url will open it in the window that was the least recently updated" sphinxOnlineDocPath='https://www.yade-dem.org/doc/' "Base URL for the documentation. Packaged versions should change to the local installation directory." import os.path # find if we have docs installed locally from package sphinxLocalDocPath=yade.config.prefix+'/share/doc/yade'+yade.config.suffix+'-doc/html/' sphinxBuildDocPath=yade.config.sourceRoot+'/doc/sphinx/_build/html/' # we prefer the packaged documentation for this version, if installed if os.path.exists(sphinxLocalDocPath+'/index.html'): sphinxPrefix='file://'+sphinxLocalDocPath # otherwise look for documentation generated in the source tree elif os.path.exists(sphinxBuildDocPath+'/index.html'): sphinxPrefix='file://'+sphinxBuildDocPath # fallback to online docs else: sphinxPrefix=sphinxOnlineDocPath sphinxDocWrapperPage=sphinxPrefix+'/yade.wrapper.html' def openUrl(url): from PyQt4 import QtWebKit global maxWebWindows,webWindows reuseLast=False # use the last window if the class is the same and only the attribute differs try: reuseLast=(len(webWindows)>0 and str(webWindows[-1].url()).split('#')[-1].split('.')[2]==url.split('#')[-1].split('.')[2]) #print str(webWindows[-1].url()).split('#')[-1].split('.')[2],url.split('#')[-1].split('.')[2] except: pass if not reuseLast: if len(webWindows)0: self.displayCombo.insertSeparator(10000); afterSep=0 for c in yade.system.childClasses(bc) | set([bc]): inst=eval(c+'()'); if len(set(inst.dict().keys())-set(['label']))>0: self.displayCombo.addItem(c); afterSep+=1 def inspectSlot(self): self.inspector=SimulationInspector(parent=None) self.inspector.show() def setTabActive(self,what): if what=='simulation': ix=0 elif what=='display': ix=1 elif what=='generator': ix=2 elif what=='python': ix=3 else: raise ValueErorr("No such tab: "+what) self.controllerTabs.setCurrentIndex(ix) def generatorComboSlot(self,genStr): "update generator parameters when a new one is selected" gen=eval(str(genStr)+'()') self.generator=gen se=SerializableEditor(gen,parent=self.generatorArea,showType=True) self.generatorArea.setWidget(se) def pythonComboSlot(self,cmd): try: code=compile(str(cmd),'','exec') exec code in globals() except: import traceback traceback.print_exc() def generateSlot(self): filename=str(self.generatorFilenameEdit.text()) if self.generatorMemoryCheck.isChecked(): filename=':memory:'+filename print 'BUG: Saving to memory slots freezes Yade (cause unknown). Cross fingers.' #print 'Will save to ',filename self.generator.generate(filename) if self.generatorAutoCheck: O.load(filename) self.setTabActive('simulation') if len(views())==0: v=View(); v.center() def displayComboSlot(self,dispStr): ser=(self.renderer if dispStr=='OpenGLRenderer' else eval(str(dispStr)+'()')) path='yade.qt.Renderer()' if dispStr=='OpenGLRenderer' else dispStr se=SerializableEditor(ser,parent=self.displayArea,ignoredAttrs=set(['label']),showType=True,path=path) self.displayArea.setWidget(se) def loadSlot(self): f=QFileDialog.getOpenFileName(self,'Load simulation','','Yade simulations (*.xml *.xml.bz2 *.xml.gz *.yade *.yade.gz *.yade.bz2);; *.*') f=str(f) if not f: return # cancelled self.deactivateControls() O.load(f) def saveSlot(self): f=QFileDialog.getSaveFileName(self,'Save simulation','','Yade simulations (*.xml *.xml.bz2 *.xml.gz *.yade *.yade.gz *.yade.bz2);; *.*') f=str(f) if not f: return # cancelled O.save(f) def reloadSlot(self): self.deactivateControls() from yade import plot plot.splitData() O.reload() def dtFixedSlot(self): O.dt=O.dt O.dynDt=False def dtDynSlot(self): O.dt=-O.dt def dtEditNoupdateSlot(self): self.dtEditUpdate=False def dtEditedSlot(self): try: t=float(self.dtEdit.text()) O.dt=t except ValueError: pass self.dtEdit.setText(str(O.dt)) self.dtEditUpdate=True def playSlot(self): O.run() def pauseSlot(self): O.pause() def stepSlot(self): O.step() def subStepSlot(self,value): O.subStepping=bool(value) def show3dSlot(self, show): vv=views() assert(len(vv) in (0,1)) if show: if len(vv)==0: View() else: if len(vv)>0: vv[0].close() def setReferenceSlot(self): # sets reference periodic cell as well utils.setRefSe3() def centerSlot(self): for v in views(): v.center() def setViewAxes(self,dir,up): try: v=views()[0] v.viewDir=dir v.upVector=up v.center() except IndexError: pass def xyzSlot(self): self.setViewAxes((0,0,-1),(0,1,0)) def yzxSlot(self): self.setViewAxes((-1,0,0),(0,0,1)) def zxySlot(self): self.setViewAxes((0,-1,0),(1,0,0)) def refreshEvent(self): self.refreshValues() self.activateControls() def deactivateControls(self): self.realTimeLabel.setText('') self.virtTimeLabel.setText('') self.iterLabel.setText('') self.fileLabel.setText('[loading]') self.playButton.setEnabled(False) self.pauseButton.setEnabled(False) self.stepButton.setEnabled(False) self.subStepCheckbox.setEnabled(False) self.reloadButton.setEnabled(False) self.dtFixedRadio.setEnabled(False) self.dtDynRadio.setEnabled(False) self.dtEdit.setEnabled(False) self.dtEdit.setText('') self.dtEditUpdate=True def activateControls(self): hasSim=len(O.engines)>0 running=O.running if hasSim: self.playButton.setEnabled(not running) self.pauseButton.setEnabled(running) self.reloadButton.setEnabled(O.filename is not None) self.stepButton.setEnabled(not running) self.subStepCheckbox.setEnabled(not running) else: self.playButton.setEnabled(False) self.pauseButton.setEnabled(False) self.reloadButton.setEnabled(False) self.stepButton.setEnabled(False) self.subStepCheckbox.setEnabled(False) self.dtFixedRadio.setEnabled(True) self.dtDynRadio.setEnabled(O.dynDtAvailable) dynDt=O.dynDt self.dtFixedRadio.setChecked(not dynDt) self.dtDynRadio.setChecked(dynDt) if dynDt or self.dtEditUpdate: self.dtEdit.setText(str(O.dt)) if dynDt: self.dtEditUpdate=True self.dtEdit.setEnabled(not dynDt) fn=O.filename self.fileLabel.setText(fn if fn else '[no file]') def refreshValues(self): rt=int(O.realtime); t=O.time; iter=O.iter; assert(len(self.iterTimes)==len(self.iterValues)) if len(self.iterTimes)==0: self.iterTimes.append(rt); self.iterValues.append(iter); self.iterPerSec=0 # update always for the first time elif rt-self.iterTimes[-1]>self.iterPerSecTimeout: # update after a timeout if len(self.iterTimes)==1: self.iterTimes.append(self.iterTimes[0]); self.iterValues.append(self.iterValues[0]) # 2 values, first one is bogus self.iterTimes[0]=self.iterTimes[1]; self.iterValues[0]=self.iterValues[1] self.iterTimes[1]=rt; self.iterValues[1]=iter; self.iterPerSec=(self.iterValues[-1]-self.iterValues[-2])/(self.iterTimes[-1]-self.iterTimes[-2]) if not O.running: self.iterPerSec=0 stopAtIter=O.stopAtIter subStepInfo='' if O.subStepping: subStep=O.subStep if subStep==-1: subStepInfo=u'→ prologue' elif subStep>=0 and subStepepilogue
    ' else: raise RuntimeError("Invalid O.subStep value %d, should be ∈{-1,…,len(o.engines)}"%subStep) subStepInfo="
    sub %d/%d [%s]"%(subStep,len(O.engines),subStepInfo) self.subStepCheckbox.setChecked(O.subStepping) # might have been changed async if stopAtIter<=iter: self.realTimeLabel.setText('%02d:%02d:%02d'%(rt//3600,(rt%3600)//60,rt%60)) self.iterLabel.setText('#%ld, %.1f/s %s'%(iter,self.iterPerSec,subStepInfo)) else: e=int((stopAtIter-iter)*self.iterPerSec) self.realTimeLabel.setText('%02d:%02d:%02d (ETA %02d:%02d:%02d)'%(rt//3600,rt//60,rt%60,e//3600,e//60,e%60)) self.iterLabel.setText('#%ld / %ld, %.1f/s %s'%(O.iter,stopAtIter,self.iterPerSec,subStepInfo)) if t!=float('inf'): s=int(t); ms=int(t*1000)%1000; us=int(t*1000000)%1000; ns=int(t*1000000000)%1000 self.virtTimeLabel.setText(u'%03ds%03dm%03dμ%03dn'%(s,ms,us,ns)) else: self.virtTimeLabel.setText(u'[ ∞ ] ?!') self.show3dButton.setChecked(len(views())>0) def Generator(): global controller if not controller: controller=ControllerClass(); controller.show(); controller.raise_() controller.setTabActive('generator') def Controller(): global controller if not controller: controller=ControllerClass(); controller.show(); controller.raise_() controller.setTabActive('simulation') def Inspector(): global controller if not controller: controller=ControllerClass(); controller.inspectSlot() #if __name__=='__main__': # from PyQt4 import QtGui # import sys # qapp=QtGui.QApplication(sys.argv) # c=Controller().show() # qapp.exec_() trunk-2018.02b/gui/qt4/controller.ui000066400000000000000000001000301324306050200171760ustar00rootroot00000000000000 Controller 0 0 290 495 0 0 Yade :/img/yade-favicon.xpm:/img/yade-favicon.xpm 0 0 0 0 0 Simulation 0 0 6 QLayout::SetMinAndMaxSize true 0 0 0 0 Load true 0 0 0 0 Save Inspect QLayout::SetMinimumSize QFormLayout::AllNonFixedFieldsGrow 6 6 6 real 00:00:00 virt 00:000.000m000μ000n iter #0, 0.0/s true Δt fixed true false time stepper false Qt::ClickFocus Qt::Horizontal 40 20 [no file] Qt::Horizontal 40 20 6 6 0 false 5 0 18 32 32 true false false 4 0 18 ▮▮ 32 32 0 0 0 false 2 0 12 ▶▮ 32 32 7 sub-step false 5 0 22 32 32 Show 3D true Reference Center 48 48 :/img/XYZ.xpm:/img/XYZ.xpm 40 40 48 48 :/img/YZX.xpm:/img/YZX.xpm 40 40 48 48 :/img/ZXY.xpm:/img/ZXY.xpm 40 40 Display 0 0 true 0 0 284 433 Generate 0 0 200 200 0 0 true 0 0 500 500 0 0 500 500 398 336 false memory slot false false /tmp/scene.yade.gz open automatically true Generate Python Monospace IBeamCursor Qt::StrongFocus false true QComboBox::InsertAtTop 1 false <i>(Output appears in the terminal)</i> Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter Qt::Vertical 20 40 loadButton clicked() Controller loadSlot() 93 64 284 0 saveButton clicked() Controller saveSlot() 184 64 284 0 dtFixedRadio clicked() Controller dtFixedSlot() 143 170 284 0 dtDynRadio clicked() Controller dtDynSlot() 268 176 284 4 dtEdit editingFinished() Controller dtEditedSlot() 147 204 284 43 playButton clicked() Controller playSlot() 116 304 3 309 pauseButton clicked() Controller pauseSlot() 268 304 284 311 referenceButton clicked() Controller setReferenceSlot() 184 429 5 469 centerButton clicked() Controller centerSlot() 275 429 284 469 xyzButton clicked() Controller xyzSlot() 93 460 0 469 yzxButton clicked() Controller yzxSlot() 184 485 243 469 zxyButton clicked() Controller zxySlot() 275 485 284 469 generateButton clicked() Controller generateSlot() 101 60 284 469 dtEdit textEdited(QString) Controller dtEditNoupdateSlot() 147 204 284 205 dtEdit cursorPositionChanged(int,int) Controller dtEditNoupdateSlot() 147 204 284 181 generatorCombo currentIndexChanged(QString) Controller generatorComboSlot() 101 60 284 130 displayCombo currentIndexChanged(QString) Controller displayComboSlot() 101 45 284 108 pythonCombo activated(QString) Controller pythonComboSlot() 107 66 284 68 inspectButton clicked() Controller inspectSlot() 247 54 0 58 reloadButton clicked() Controller reloadSlot() 194 363 284 437 stepButton clicked() Controller stepSlot() 79 363 6 450 subStepCheckbox stateChanged(int) Controller subStepSlot() 42 376 -2 381 show3dButton toggled(bool) Controller show3dSlot(bool) 42 418 -2 422 loadSlot() saveSlot() reloadSlot() playSlot() pauseSlot() stepSlot() setReferenceSlot() centerSlot() dtFixedSlot() dtDynSlot() dtEditedSlot() xyzSlot() yzxSlot() zxySlot() generateSlot() dtEditNoupdateSlot() generatorComboSlot() displayComboSlot() pythonComboSlot() pythonEditSlot() inspectSlot() subStepSlot() show3dSlot(bool) trunk-2018.02b/gui/qt4/img.qrc000066400000000000000000000002441324306050200157450ustar00rootroot00000000000000 yade-favicon.xpm XYZ.xpm YZX.xpm ZXY.xpm trunk-2018.02b/gui/qt4/yade-favicon.png000066400000000000000000000022211324306050200175320ustar00rootroot00000000000000PNG  IHDRasRGBbKGD pHYs  tIME 65tEXtCommentCreated with GIMPWIDAT8{lu]w}\6Qa6c-É6DMϑh_&8KY1&g[u׻ |zY*#Oq羪hdOr,0ͬY8[/HG+i+wG~N7:f_~5W+LZ.67vu~yIQ=ˣqx* cD2o1y4(>7!D߻ |aj= ]^BQs/yv8ql1zqy͜68xDwـ1- 4vt؁>tb>l"q4Qyk?񸥴w"Bv]j9pgMQ++.꒎9n)CEyGv{ёy+BEHrxnְVw5uP+s0_Cs!g'O-G~.g3V FM Clbu#ċS7o",`d0h1N,2B7 wochج f61hS>qd ȵk]]!M'H̬NOwԔh4 @n,Iy8* yӹ/9um,Ox6ze-E"utA Hy9 YU.j1‘Oip㭂%b*10@N -4dD`3@TIމH2%tŎ4Q0R_DtCp},y L f=U\:gsl 8ڋVp)CM;;;G|6 PV'L@WTlSS&cfY6F4P [H8ǞN [^.E"" ʄ Tne [Z A3$Tc˻\c[?\~ZՐ[guyʀi9Iڼщk|IENDB`trunk-2018.02b/gui/qt4/yade-favicon.xpm000066400000000000000000000076341324306050200175670ustar00rootroot00000000000000/* XPM */ static char * yade_favicon_xpm[] = { "16 16 210 2", " c None", ". c #F47372", "+ c #F1B4B5", "@ c #909092", "# c #EA5A5A", "$ c #8E4D3C", "% c #746C35", "& c #616233", "* c #4B4B0E", "= c #939191", "- c #8A8A8A", "; c #808380", "> c #6F6E6D", ", c #4F4D2B", "' c #646427", ") c #383827", "! c #EB9D9E", "~ c #7F6B5D", "{ c #828500", "] c #A2A200", "^ c #838500", "/ c #404031", "( c #AEAEAE", "_ c #8C8C8C", ": c #ABAAAF", "< c #444319", "[ c #9E9C01", "} c #B4B106", "| c #908E0C", "1 c #4C4C4F", "2 c #D0BBBB", "3 c #B07E83", "4 c #706137", "5 c #B9B704", "6 c #C8C901", "7 c #686800", "8 c #4C4C51", "9 c #A3A1A3", "0 c #ABA9AC", "a c #45444A", "b c #8F8E00", "c c #A3A005", "d c #AAA809", "e c #25241B", "f c #D79090", "g c #E25E5E", "h c #FCBEC9", "i c #E6AF6F", "j c #D6B700", "k c #ACAA00", "l c #3F4000", "m c #53525D", "n c #666472", "o c #6A6A02", "p c #B7B509", "q c #A3A407", "r c #3F4008", "s c #E07B7A", "t c #E35151", "u c #DC6565", "v c #C76B67", "w c #D79E42", "x c #ABA10D", "y c #99A400", "z c #393C21", "A c #40401D", "B c #9A9A01", "C c #B6B406", "D c #737100", "E c #545157", "F c #DCA8A8", "G c #DAA5A7", "H c #D86261", "I c #D85D5D", "J c #DEC7D1", "K c #E1BE91", "L c #B5C100", "M c #848D04", "N c #818103", "O c #8C8C08", "P c #7A7A02", "Q c #504F33", "R c #827F8D", "S c #C85757", "T c #CC6666", "U c #D65757", "V c #DB5353", "W c #E59596", "X c #E58A82", "Y c #BFB500", "Z c #B4BD05", "` c #BBBA0B", " . c #A4A306", ".. c #5E5D13", "+. c #565463", "@. c #B47070", "#. c #C69392", "$. c #D57E7E", "%. c #DA2929", "&. c #ED393A", "*. c #8B3537", "=. c #949700", "-. c #9AA706", ";. c #ABB10A", ">. c #747502", ",. c #2C2B35", "'. c #B2AFBB", "). c #B86363", "!. c #C8BEBE", "~. c #DBCFCF", "{. c #DF7373", "]. c #F16466", "^. c #E79DA6", "/. c #5A5408", "(. c #EEAC06", "_. c #EBBA08", ":. c #312E0B", "<. c #7D7B8A", "[. c #8C899A", "}. c #858394", "|. c #777588", "1. c #B75E5E", "2. c #C38181", "3. c #D58383", "4. c #AB5B5A", "5. c #DF484A", "6. c #F28088", "7. c #5F4507", "8. c #FD8D08", "9. c #FFBD0C", "0. c #3D330C", "a. c #B8B5C6", "b. c #858297", "c. c #BD6464", "d. c #C24748", "e. c #CD5E5F", "f. c #D2ADAE", "g. c #DA8E91", "h. c #E73643", "i. c #5B3206", "j. c #EF8308", "k. c #EF7510", "l. c #3C2A0F", "m. c #ADAEC2", "n. c #8F8DA3", "o. c #8A88A1", "p. c #9490AB", "q. c #69687B", "r. c #C30C0C", "s. c #BD3C3F", "t. c #C6777A", "u. c #D6B7BA", "v. c #D9A2A7", "w. c #D9848C", "x. c #DBC5C3", "y. c #E49A37", "z. c #F59212", "A. c #482E17", "B. c #D5ABBA", "C. c #817A95", "D. c #A491A9", "E. c #8986A2", "F. c #8E89A7", "G. c #7B7795", "H. c #C55252", "I. c #C37072", "J. c #BE4951", "K. c #CA6A72", "L. c #CF4E5A", "M. c #D49098", "N. c #DDD2D6", "O. c #E6A489", "P. c #EB7313", "Q. c #452716", "R. c #FEC1CD", "S. c #E76B80", "T. c #F7BBC5", "U. c #CBACBA", "V. c #9292AD", "W. c #837FA0", "X. c #C62F2F", "Y. c #C73E3F", "Z. c #C53F4A", "`. c #B93344", " + c #CD3C4D", ".+ c #D75767", "++ c #E17484", "@+ c #C46559", "#+ c #DA743E", "$+ c #53241E", "%+ c #ED546E", "&+ c #E66E82", "*+ c #F3BCC6", "=+ c #E19FAF", "-+ c #B81415", ";+ c #BA4D4F", ">+ c #AD7782", ",+ c #AC4B5D", "'+ c #C09FA9", ")+ c #BB3A52", "!+ c #C14F67", "~+ c #CD5858", "{+ c #E1AEAC", "]+ c #D9D0D0", "^+ c #D25169", "/+ c #DC7C8F", "(+ c #F0C9D1", "_+ c #E6A3B2", ":+ c #A27791", "<+ c #AA98B1", ". + @ ", "# $ % & * = - ; > , ' ) ", "! ~ { ] ^ / ( _ : < [ } | 1 ", "2 3 4 5 6 7 8 9 0 a b c d e ", "f g h i j k l m n o p q r ", "s t u v w x y z A B C D E ", "F G H I J K L M N O P Q R ", "S T U V W X Y Z ` ...+. ", "@.#.$.%.&.*.=.-.;.>.,. '. ", ").!.~.{.].^./.(._.:.<.[.}.|. ", "1.2.3.4.5.6.7.8.9.0.a. b. ", "c.d.e.f.g.h.i.j.k.l.m.n.o. p.q.", "r.s.t.u.v.w.x.y.z.A.B.C.D.E.F.G.", "H.I.J.K.L.M.N.O.P.Q.R.S.T.U.V.W.", "X.Y.Z.`. +.+++@+#+$+%+&+*+=+ ", "-+;+>+,+'+)+!+~+{+]+^+/+(+_+:+<+"}; trunk-2018.02b/gui/qt5/000077500000000000000000000000001324306050200144635ustar00rootroot00000000000000trunk-2018.02b/gui/qt5/GLViewer.cpp000066400000000000000000000556501324306050200166660ustar00rootroot00000000000000/************************************************************************* * Copyright (C) 2004 by Olivier Galizzi * * olivier.galizzi@imag.fr * * Copyright (C) 2005 by Janek Kozicki * * cosurgi@berlios.de * * * * This program is free software; it is licensed under the terms of the * * GNU General Public License v2 or later. See file LICENSE for details. * *************************************************************************/ #include"GLViewer.hpp" #include"OpenGLManager.hpp" #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef YADE_GL2PS #include #endif static unsigned initBlocked(State::DOF_NONE); CREATE_LOGGER(GLViewer); GLLock::GLLock(GLViewer* _glv): boost::try_mutex::scoped_lock(Omega::instance().renderMutex), glv(_glv){ glv->makeCurrent(); } GLLock::~GLLock(){ glv->doneCurrent(); } #define _W3 setw(3)<emitCloseView(viewId); e->accept(); } GLViewer::GLViewer(int _viewId, const shared_ptr& _renderer, QGLWidget* shareWidget): QGLViewer(/*parent*/(QWidget*)NULL,shareWidget), renderer(_renderer), viewId(_viewId) { isMoving=false; drawGrid=0; drawScale=true; timeDispMask=TIME_REAL|TIME_VIRT|TIME_ITER; cut_plane = 0; cut_plane_delta = -2; gridSubdivide = false; resize(550,550); last=-1; if(viewId==0) setWindowTitle("Primary view"); else setWindowTitle(("Secondary view #"+boost::lexical_cast(viewId)).c_str()); show(); mouseMovesCamera(); manipulatedClipPlane=-1; if(manipulatedFrame()==0) setManipulatedFrame(new qglviewer::ManipulatedFrame()); xyPlaneConstraint=shared_ptr(new qglviewer::LocalConstraint()); manipulatedFrame()->setConstraint(NULL); setKeyDescription(Qt::Key_Return,"Run simulation."); setKeyDescription(Qt::Key_A,"Toggle visibility of global axes."); setKeyDescription(Qt::Key_C,"Set scene center so that all bodies are visible; if a body is selected, center around this body."); setKeyDescription(Qt::Key_C & Qt::AltModifier,"Set scene center to median body position (same as space)"); setKeyDescription(Qt::Key_D,"Toggle time display mask"); setKeyDescription(Qt::Key_G,"Toggle grid visibility; g turns on and cycles"); setKeyDescription(Qt::Key_G & Qt::ShiftModifier ,"Hide grid."); setKeyDescription(Qt::Key_M, "Move selected object."); setKeyDescription(Qt::Key_X,"Show the xz [shift: xy] (up-right) plane (clip plane: align normal with +x)"); setKeyDescription(Qt::Key_Y,"Show the yx [shift: yz] (up-right) plane (clip plane: align normal with +y)"); setKeyDescription(Qt::Key_Z,"Show the zy [shift: zx] (up-right) plane (clip plane: align normal with +z)"); setKeyDescription(Qt::Key_Period,"Toggle grid subdivision by 10"); setKeyDescription(Qt::Key_S,"Save QGLViewer state to /tmp/qglviewerState.xml"); setKeyDescription(Qt::Key_T,"Switch orthographic / perspective camera"); setKeyDescription(Qt::Key_O,"Set narrower field of view"); setKeyDescription(Qt::Key_P,"Set wider field of view"); setKeyDescription(Qt::Key_R,"Revolve around scene center"); setKeyDescription(Qt::Key_V,"Save PDF of the current view to /tmp/yade-snapshot-0001.pdf (whichever number is available first). (Must be compiled with the gl2ps feature.)"); setPathKey(-Qt::Key_F1); setPathKey(-Qt::Key_F2); setKeyDescription(Qt::Key_Escape,"Manipulate scene (default)"); setKeyDescription(Qt::Key_F1,"Manipulate clipping plane #1"); setKeyDescription(Qt::Key_F2,"Manipulate clipping plane #2"); setKeyDescription(Qt::Key_F3,"Manipulate clipping plane #3"); setKeyDescription(Qt::Key_1,"Make the manipulated clipping plane parallel with plane #1"); setKeyDescription(Qt::Key_2,"Make the manipulated clipping plane parallel with plane #2"); setKeyDescription(Qt::Key_2,"Make the manipulated clipping plane parallel with plane #3"); setKeyDescription(Qt::Key_1 & Qt::AltModifier,"Add/remove plane #1 to/from the bound group"); setKeyDescription(Qt::Key_2 & Qt::AltModifier,"Add/remove plane #2 to/from the bound group"); setKeyDescription(Qt::Key_3 & Qt::AltModifier,"Add/remove plane #3 to/from the bound group"); setKeyDescription(Qt::Key_0,"Clear the bound group"); setKeyDescription(Qt::Key_7,"Load [Alt: save] view configuration #0"); setKeyDescription(Qt::Key_8,"Load [Alt: save] view configuration #1"); setKeyDescription(Qt::Key_9,"Load [Alt: save] view configuration #2"); setKeyDescription(Qt::Key_Space,"Center scene (same as Alt-C); clip plane: activate/deactivate"); centerScene(); } bool GLViewer::isManipulating(){ return isMoving || manipulatedClipPlane>=0; } void GLViewer::resetManipulation(){ mouseMovesCamera(); setSelectedName(-1); isMoving=false; manipulatedClipPlane=-1; } void GLViewer::startClipPlaneManipulation(int planeNo){ assert(planeNonumClipPlanes); resetManipulation(); mouseMovesManipulatedFrame(xyPlaneConstraint.get()); manipulatedClipPlane=planeNo; const Se3r se3(renderer->clipPlaneSe3[planeNo]); manipulatedFrame()->setPositionAndOrientation(qglviewer::Vec(se3.position[0],se3.position[1],se3.position[2]),qglviewer::Quaternion(se3.orientation.x(),se3.orientation.y(),se3.orientation.z(),se3.orientation.w())); string grp=strBoundGroup(); displayMessage("Manipulating clip plane #"+boost::lexical_cast(planeNo+1)+(grp.empty()?grp:" (bound planes:"+grp+")")); } string GLViewer::getState(){ QString origStateFileName=stateFileName(); string tmpFile=Omega::instance().tmpFilename(); setStateFileName(QString(tmpFile.c_str())); saveStateToFile(); setStateFileName(origStateFileName); LOG_WARN("State saved to temp file "<>ss; ret+=" "+ss;}; in.close(); boost::filesystem::remove(boost::filesystem::path(tmpFile)); return ret; } void GLViewer::setState(string state){ string tmpFile=Omega::instance().tmpFilename(); std::ofstream out(tmpFile.c_str()); if(!out.good()){ LOG_ERROR("Error opening temp file `"<key()==Qt::Key_A){ toggleAxisIsDrawn(); return; } else if(e->key()==Qt::Key_Escape){ if(!isManipulating()){ setSelectedName(-1); return; } else { resetManipulation(); displayMessage("Manipulating scene."); } } else if(e->key()==Qt::Key_Space){ if(manipulatedClipPlane>=0) {displayMessage("Clip plane #"+boost::lexical_cast(manipulatedClipPlane+1)+(renderer->clipPlaneActive[manipulatedClipPlane]?" de":" ")+"activated"); renderer->clipPlaneActive[manipulatedClipPlane]=!renderer->clipPlaneActive[manipulatedClipPlane]; } else{ centerMedianQuartile(); } } /* function keys */ else if(e->key()==Qt::Key_F1 || e->key()==Qt::Key_F2 || e->key()==Qt::Key_F3 /* || ... */ ){ int n=0; if(e->key()==Qt::Key_F1) n=1; else if(e->key()==Qt::Key_F2) n=2; else if(e->key()==Qt::Key_F3) n=3; assert(n>0); int planeId=n-1; if(planeId>=renderer->numClipPlanes) return; if(planeId!=manipulatedClipPlane) startClipPlaneManipulation(planeId); } /* numbers */ else if(e->key()==Qt::Key_0 && (e->modifiers() & Qt::AltModifier)) { boundClipPlanes.clear(); displayMessage("Cleared bound planes group.");} else if(e->key()==Qt::Key_1 || e->key()==Qt::Key_2 || e->key()==Qt::Key_3 /* || ... */ ){ int n=0; if(e->key()==Qt::Key_1) n=1; else if(e->key()==Qt::Key_2) n=2; else if(e->key()==Qt::Key_3) n=3; assert(n>0); int planeId=n-1; if(planeId>=renderer->numClipPlanes) return; // no such clipping plane if(e->modifiers() & Qt::AltModifier){ if(boundClipPlanes.count(planeId)==0) {boundClipPlanes.insert(planeId); displayMessage("Added plane #"+boost::lexical_cast(planeId+1)+" to the bound group: "+strBoundGroup());} else {boundClipPlanes.erase(planeId); displayMessage("Removed plane #"+boost::lexical_cast(planeId+1)+" from the bound group: "+strBoundGroup());} } else if(manipulatedClipPlane>=0 && manipulatedClipPlane!=planeId) { const Quaternionr& o=renderer->clipPlaneSe3[planeId].orientation; manipulatedFrame()->setOrientation(qglviewer::Quaternion(o.x(),o.y(),o.z(),o.w())); displayMessage("Copied orientation from plane #1"); } } else if(e->key()==Qt::Key_7 || e->key()==Qt::Key_8 || e->key()==Qt::Key_9){ int nn=-1; if(e->key()==Qt::Key_7)nn=0; else if(e->key()==Qt::Key_8)nn=1; else if(e->key()==Qt::Key_9)nn=2; assert(nn>=0); size_t n=(size_t)nn; if(e->modifiers() & Qt::AltModifier) saveDisplayParameters(n); else useDisplayParameters(n); } /* letters alphabetically */ else if(e->key()==Qt::Key_C && (e->modifiers() & Qt::AltModifier)){ displayMessage("Median centering"); centerMedianQuartile(); } else if(e->key()==Qt::Key_C){ // center around selected body if(selectedName() >= 0 && (*(Omega::instance().getScene()->bodies)).exists(selectedName())) setSceneCenter(manipulatedFrame()->position()); // make all bodies visible else centerScene(); } else if(e->key()==Qt::Key_D &&(e->modifiers() & Qt::AltModifier)){ Body::id_t id; if((id=Omega::instance().getScene()->selectedBody)>=0){ const shared_ptr& b=Body::byId(id); b->setDynamic(!b->isDynamic()); LOG_INFO("Body #"<isDynamic()?"":"NOT")<<" dynamic"); } } else if(e->key()==Qt::Key_D) {timeDispMask+=1; if(timeDispMask>(TIME_REAL|TIME_VIRT|TIME_ITER))timeDispMask=0; } else if(e->key()==Qt::Key_G) { if(e->modifiers() & Qt::ShiftModifier){ drawGrid=0; return; } else drawGrid++; if(drawGrid>=8) drawGrid=0; } else if (e->key()==Qt::Key_M && selectedName() >= 0){ if(!(isMoving=!isMoving)){ displayMessage("Moving done."); if (last>=0) {Body::byId(Body::id_t(last))->state->blockedDOFs=initBlocked; last=-1;} mouseMovesCamera();} else{ displayMessage("Moving selected object"); long selection = Omega::instance().getScene()->selectedBody; initBlocked=Body::byId(Body::id_t(selection))->state->blockedDOFs; last=selection; Body::byId(Body::id_t(selection))->state->blockedDOFs=State::DOF_ALL; Quaternionr& q = Body::byId(selection)->state->ori; Vector3r& v = Body::byId(selection)->state->pos; manipulatedFrame()->setPositionAndOrientation(qglviewer::Vec(v[0],v[1],v[2]),qglviewer::Quaternion(q.x(),q.y(),q.z(),q.w())); mouseMovesManipulatedFrame();} } else if (e->key() == Qt::Key_T) camera()->setType(camera()->type()==qglviewer::Camera::ORTHOGRAPHIC ? qglviewer::Camera::PERSPECTIVE : qglviewer::Camera::ORTHOGRAPHIC); else if(e->key()==Qt::Key_O) camera()->setFieldOfView(camera()->fieldOfView()*0.9); else if(e->key()==Qt::Key_P) camera()->setFieldOfView(camera()->fieldOfView()*1.1); else if(e->key()==Qt::Key_R){ // reverse the clipping plane; revolve around scene center if no clipping plane selected if(manipulatedClipPlane>=0 && manipulatedClipPlanenumClipPlanes){ /* here, we must update both manipulatedFrame orientation and renderer->clipPlaneSe3 orientation in the same way */ Quaternionr& ori=renderer->clipPlaneSe3[manipulatedClipPlane].orientation; ori=Quaternionr(AngleAxisr(Mathr::PI,Vector3r(0,1,0)))*ori; manipulatedFrame()->setOrientation(qglviewer::Quaternion(qglviewer::Vec(0,1,0),Mathr::PI)*manipulatedFrame()->orientation()); displayMessage("Plane #"+boost::lexical_cast(manipulatedClipPlane+1)+" reversed."); } else { camera()->setRevolveAroundPoint(sceneCenter()); } } else if(e->key()==Qt::Key_S){ LOG_INFO("Saving QGLViewer state to /tmp/qglviewerState.xml"); setStateFileName("/tmp/qglviewerState.xml"); saveStateToFile(); setStateFileName(QString::null); } else if(e->key()==Qt::Key_L){ LOG_INFO("Loading QGLViewer state from /tmp/qglviewerState.xml"); setStateFileName("/tmp/qglviewerState.xml"); restoreStateFromFile(); setStateFileName(QString::null); } else if(e->key()==Qt::Key_X || e->key()==Qt::Key_Y || e->key()==Qt::Key_Z){ int axisIdx=(e->key()==Qt::Key_X?0:(e->key()==Qt::Key_Y?1:2)); if(manipulatedClipPlane<0){ qglviewer::Vec up(0,0,0), vDir(0,0,0); bool alt=(e->modifiers() && Qt::ShiftModifier); up[axisIdx]=1; vDir[(axisIdx+(alt?2:1))%3]=alt?1:-1; camera()->setViewDirection(vDir); camera()->setUpVector(up); centerMedianQuartile(); } else{ // align clipping normal plane with world axis // x: (0,1,0),pi/2; y: (0,0,1),pi/2; z: (1,0,0),0 qglviewer::Vec axis(0,0,0); axis[(axisIdx+1)%3]=1; Real angle=axisIdx==2?0:Mathr::PI/2; manipulatedFrame()->setOrientation(qglviewer::Quaternion(axis,angle)); } } else if(e->key()==Qt::Key_Period) gridSubdivide = !gridSubdivide; else if(e->key()==Qt::Key_Return){ if (Omega::instance().isRunning()) Omega::instance().pause(); else Omega::instance().run(); LOG_INFO("Running..."); } #ifdef YADE_GL2PS else if(e->key()==Qt::Key_V){ for(int i=0; ;i++){ std::ostringstream fss; fss<<"/tmp/yade-snapshot-"<key()==Qt::Key_Plus ){ cut_plane = std::min(1.0, cut_plane + std::pow(10.0,(double)cut_plane_delta)); static_cast(camera())->setCuttingDistance(cut_plane); displayMessage("Cut plane: "+boost::lexical_cast(cut_plane)); }else if( e->key()==Qt::Key_Minus ){ cut_plane = std::max(0.0, cut_plane - std::pow(10.0,(double)cut_plane_delta)); static_cast(camera())->setCuttingDistance(cut_plane); displayMessage("Cut plane: "+boost::lexical_cast(cut_plane)); }else if( e->key()==Qt::Key_Slash ){ cut_plane_delta -= 1; displayMessage("Cut plane increment: 1e"+(cut_plane_delta>0?std::string("+"):std::string(""))+boost::lexical_cast(cut_plane_delta)); }else if( e->key()==Qt::Key_Asterisk ){ cut_plane_delta = std::min(1+cut_plane_delta,-1); displayMessage("Cut plane increment: 1e"+(cut_plane_delta>0?std::string("+"):std::string(""))+boost::lexical_cast(cut_plane_delta)); } #endif else if(e->key()!=Qt::Key_Escape && e->key()!=Qt::Key_Space) QGLViewer::keyPressEvent(e); updateGL(); } /* Center the scene such that periodic cell is contained in the view */ void GLViewer::centerPeriodic(){ Scene* scene=Omega::instance().getScene().get(); assert(scene->isPeriodic); Vector3r center=.5*scene->cell->getSize(); Vector3r halfSize=.5*scene->cell->getSize(); float radius=std::max(halfSize[0],std::max(halfSize[1],halfSize[2])); LOG_DEBUG("Periodic scene center="<isPeriodic){ centerPeriodic(); return; } long nBodies=scene->bodies->size(); if(nBodies<4) { LOG_DEBUG("Less than 4 bodies, median makes no sense; calling centerScene() instead."); return centerScene(); } std::vector coords[3]; for(int i=0;i<3;i++)coords[i].reserve(nBodies); FOREACH(shared_ptr b, *scene->bodies){ if(!b) continue; for(int i=0; i<3; i++) coords[i].push_back(b->state->pos[i]); } Vector3r median,interQuart; for(int i=0;i<3;i++){ sort(coords[i].begin(),coords[i].end()); median[i]=*(coords[i].begin()+nBodies/2); interQuart[i]=*(coords[i].begin()+3*nBodies/4)-*(coords[i].begin()+nBodies/4); } LOG_DEBUG("Median position is"<isPeriodic){ centerPeriodic(); return; } LOG_INFO("Select with shift, press 'm' to move."); Vector3r min,max; if(not(rb->bound)){ rb->updateBound();} min=rb->bound->min; max=rb->bound->max; bool hasNan=(std::isnan(min[0])||std::isnan(min[1])||std::isnan(min[2])||std::isnan(max[0])||std::isnan(max[1])||std::isnan(max[2])); Real minDim=std::min(max[0]-min[0],std::min(max[1]-min[1],max[2]-min[2])); if(minDim<=0 || hasNan){ // Aabb is not yet calculated... LOG_DEBUG("scene's bound not yet calculated or has zero or nan dimension(s), attempt get that from bodies' positions."); Real inf=std::numeric_limits::infinity(); min=Vector3r(inf,inf,inf); max=Vector3r(-inf,-inf,-inf); FOREACH(const shared_ptr& b, *rb->bodies){ if(!b) continue; max=max.cwiseMax(b->state->pos); min=min.cwiseMin(b->state->pos); } if(std::isinf(min[0])||std::isinf(min[1])||std::isinf(min[2])||std::isinf(max[0])||std::isinf(max[1])||std::isinf(max[2])){ LOG_DEBUG("No min/max computed from bodies either, setting cube (-1,-1,-1)×(1,1,1)"); min=-Vector3r::Ones(); max=Vector3r::Ones(); } } else {LOG_DEBUG("Using scene's Aabb");} LOG_DEBUG("Got scene box min="<=0) { Body::byId(Body::id_t(last))->state->blockedDOFs=initBlocked; last=-1; Omega::instance().getScene()->selectedBody = -1;} if(isMoving){ displayMessage("Moving finished"); mouseMovesCamera(); isMoving=false; Omega::instance().getScene()->selectedBody = -1; } return; } if(selection>=0 && (*(Omega::instance().getScene()->bodies)).exists(selection)){ resetManipulation(); if (last>=0) {Body::byId(Body::id_t(last))->state->blockedDOFs=initBlocked; last=-1;} if(Body::byId(Body::id_t(selection))->isClumpMember()){ // select clump (invisible) instead of its member LOG_DEBUG("Clump member #"<clumpId; } setSelectedName(selection); LOG_DEBUG("New selection "<(selection)+(Body::byId(selection)->isClump()?" (clump)":"")); Omega::instance().getScene()->selectedBody = selection; PyGILState_STATE gstate; gstate = PyGILState_Ensure(); boost::python::object main=boost::python::import("__main__"); boost::python::object global=main.attr("__dict__"); // the try/catch block must be properly nested inside PyGILState_Ensure and PyGILState_Release try{ boost::python::eval(string("onBodySelect("+boost::lexical_cast(selection)+")").c_str(),global,global); } catch (boost::python::error_already_set const &) { LOG_DEBUG("unable to call onBodySelect. Not defined?"); } PyGILState_Release(gstate); // see https://svn.boost.org/trac/boost/ticket/2781 for exception handling } } // maybe new object will be selected. // if so, then set isDynamic of previous selection, to old value void GLViewer::endSelection(const QPoint &point){ manipulatedClipPlane=-1; QGLViewer::endSelection(point); } string GLViewer::getRealTimeString(){ ostringstream oss; boost::posix_time::time_duration t=Omega::instance().getRealTime_duration(); unsigned d=t.hours()/24,h=t.hours()%24,m=t.minutes(),s=t.seconds(); oss<<"clock "; if(d>0) oss<0) oss<<_W2<=0x020603 qreal YadeCamera::zNear() const #else float YadeCamera::zNear() const #endif { float z = distanceToSceneCenter() - zClippingCoefficient()*sceneRadius()*(1.f-2*cuttingDistance); // Prevents negative or null zNear values. const float zMin = zNearCoefficient() * zClippingCoefficient() * sceneRadius(); if (z < zMin) /* switch (type()) { case Camera::PERSPECTIVE :*/ z = zMin; /*break; case Camera::ORTHOGRAPHIC : z = 0.0; break; }*/ return z; } trunk-2018.02b/gui/qt5/GLViewer.hpp000066400000000000000000000124171324306050200166650ustar00rootroot00000000000000// Copyright (C) 2004 by Olivier Galizzi, Janek Kozicki * // © 2008 Václav Šmilauer #pragma once #ifndef Q_MOC_RUN #include #include #include #include #endif #include #include #include using std::setw; using std::setfill; using std::setprecision; /*! Class handling user interaction with the openGL rendering of simulation. * * Clipping planes: * ================ * * Clipping plane is manipulated after hitting F1, F2, .... To end the manipulation, press Escape. * * Keystrokes during clipping plane manipulation: * * space activates/deactives the clipping plane * * x,y,z aligns the plane with yz, xz, xy planes * * left-double-click aligns the plane with world coordinates system (canonical planes + 45˚ interpositions) * * 1,2,... will align the current plane with #1, #2, ... (same orientation) * * r reverses the plane (normal*=-1)a * * Keystrokes that work regardless of whether a clipping plane is being manipulated: * * Alt-1,Alt-2,... adds/removes the respective plane to bound group: * mutual positions+orientations of planes in the group are maintained when one of those planes is manipulated * * Clip plane number is 3; change YADE_RENDERER_NUM_CLIP_PLANE, complete switches "|| ..." in keyPressEvent * and recompile to have more. */ class GLViewer : public QGLViewer { Q_OBJECT friend class QGLThread; protected: shared_ptr renderer; private : bool isMoving; bool wasDynamic; float cut_plane; int cut_plane_delta; bool gridSubdivide; long last; int manipulatedClipPlane; set boundClipPlanes; shared_ptr xyPlaneConstraint; string strBoundGroup(){string ret;FOREACH(int i, boundClipPlanes) ret+=" "+boost::lexical_cast(i+1);return ret;} boost::posix_time::ptime last_user_event; public: //virtual void updateGL(void); const int viewId; void centerMedianQuartile(); int drawGrid; bool drawScale; int timeDispMask; enum{TIME_REAL=1,TIME_VIRT=2,TIME_ITER=4}; GLViewer(int viewId, const shared_ptr& renderer, QGLWidget* shareWidget=0); virtual ~GLViewer(); #if 0 virtual void paintGL(); #endif virtual void draw(); virtual void drawWithNames(); void displayMessage(const std::string& s){ QGLViewer::displayMessage(QString(s.c_str()));} void centerScene(); void centerPeriodic(); void mouseMovesCamera(); void mouseMovesManipulatedFrame(qglviewer::Constraint* c=NULL); void resetManipulation(); bool isManipulating(); void startClipPlaneManipulation(int planeNo); //! get QGLViewer state as string (XML); QGLViewer normally only supports saving state to file. string getState(); //! set QGLViewer state from string (XML); QGLVIewer normally only supports loading state from file. void setState(string); //! Load display parameters (QGLViewer and OpenGLRenderer) from Scene::dispParams[n] and use them void useDisplayParameters(size_t n); //! Save display parameters (QGOViewer and OpenGLRenderer) to Scene::dispParams[n] void saveDisplayParameters(size_t n); //! Get radius of the part of scene that fits the current view float displayedSceneRadius(); //! Get center of the part of scene that fits the current view qglviewer::Vec displayedSceneCenter(); //! Adds our attributes to the QGLViewer state that can be saved QDomElement domElement(const QString& name, QDomDocument& document) const; //! Adds our attributes to the QGLViewer state that can be restored void initFromDOMElement(const QDomElement& element); // if defined, snapshot will be saved to this file right after being drawn and the string will be reset. // this way the caller will be notified of the frame being saved successfully. string nextFrameSnapshotFilename; #ifdef YADE_GL2PS // output stream for gl2ps; initialized as needed FILE* gl2psStream; #endif boost::posix_time::ptime getLastUserEvent(); DECLARE_LOGGER; protected : virtual void keyPressEvent(QKeyEvent *e); virtual void postDraw(); // overridden in the player that doesn't get time from system clock but from the db virtual string getRealTimeString(); virtual void closeEvent(QCloseEvent *e); virtual void postSelection(const QPoint& point); virtual void endSelection(const QPoint &point); virtual void mouseDoubleClickEvent(QMouseEvent *e); virtual void wheelEvent(QWheelEvent* e); virtual void mouseMoveEvent(QMouseEvent *e); virtual void mousePressEvent(QMouseEvent *e); }; /*! Get unconditional lock on a GL view. Use if you need to manipulate GL context in some way. The ctor doesn't return until the lock has been acquired and the lock is released when the GLLock object is desctructed; */ class GLLock: public boost::try_mutex::scoped_lock{ GLViewer* glv; public: GLLock(GLViewer* _glv); ~GLLock(); }; class YadeCamera : public qglviewer::Camera { Q_OBJECT private: float cuttingDistance; public : YadeCamera():cuttingDistance(0){}; #if QGLVIEWER_VERSION>=0x020603 virtual qreal zNear() const; #else virtual float zNear() const; #endif virtual void setCuttingDistance(float s){cuttingDistance=s;}; }; trunk-2018.02b/gui/qt5/GLViewerDisplay.cpp000066400000000000000000000327751324306050200202170ustar00rootroot00000000000000/************************************************************************* * Copyright (C) 2004 by Olivier Galizzi * * olivier.galizzi@imag.fr * * Copyright (C) 2005 by Janek Kozicki * * cosurgi@berlios.de * * * * This program is free software; it is licensed under the terms of the * * GNU General Public License v2 or later. See file LICENSE for details. * *************************************************************************/ #include"GLViewer.hpp" #include"OpenGLManager.hpp" #include #include #include #include #include #include #include #include #include #include #include #include #ifdef YADE_GL2PS #include #endif void GLViewer::useDisplayParameters(size_t n){ LOG_DEBUG("Loading display parameters from #"< >& dispParams=Omega::instance().getScene()->dispParams; if(dispParams.size()<=(size_t)n){ throw std::invalid_argument(("Display parameters #"+boost::lexical_cast(n)+" don't exist (number of entries "+boost::lexical_cast(dispParams.size())+")").c_str());; return;} const shared_ptr& dp=dispParams[n]; string val; if(dp->getValue("OpenGLRenderer",val)){ istringstream oglre(val); yade::ObjectIO::load(oglre,"renderer",renderer); } else { LOG_WARN("OpenGLRenderer configuration not found in display parameters, skipped.");} if(dp->getValue("GLViewer",val)){ GLViewer::setState(val); displayMessage("Loaded view configuration #"+boost::lexical_cast(n)); } else { LOG_WARN("GLViewer configuration not found in display parameters, skipped."); } } void GLViewer::saveDisplayParameters(size_t n){ LOG_DEBUG("Saving display parameters to #"< >& dispParams=Omega::instance().getScene()->dispParams; if(dispParams.size()<=n){while(dispParams.size()<=n) dispParams.push_back(shared_ptr(new DisplayParameters));} assert(n& dp=dispParams[n]; ostringstream oglre; yade::ObjectIO::save(oglre,"renderer",renderer); dp->setValue("OpenGLRenderer",oglre.str()); dp->setValue("GLViewer",GLViewer::getState()); displayMessage("Saved view configuration ot #"+boost::lexical_cast(n)); } void GLViewer::draw() { #ifdef YADE_GL2PS if(!nextFrameSnapshotFilename.empty() && boost::algorithm::ends_with(nextFrameSnapshotFilename,".pdf")){ gl2psStream=fopen(nextFrameSnapshotFilename.c_str(),"wb"); if(!gl2psStream){ int err=errno; throw runtime_error(string("Error opening file ")+nextFrameSnapshotFilename+": "+strerror(err)); } LOG_DEBUG("Start saving snapshot to "<bodies->size(); int sortAlgo=(nBodies<100 ? GL2PS_BSP_SORT : GL2PS_SIMPLE_SORT); gl2psBeginPage(/*const char *title*/"Some title", /*const char *producer*/ "Yade", /*GLint viewport[4]*/ NULL, /*GLint format*/ GL2PS_PDF, /*GLint sort*/ sortAlgo, /*GLint options*/GL2PS_SIMPLE_LINE_OFFSET|GL2PS_USE_CURRENT_VIEWPORT|GL2PS_TIGHT_BOUNDING_BOX|GL2PS_COMPRESS|GL2PS_OCCLUSION_CULL|GL2PS_NO_BLENDING, /*GLint colormode*/ GL_RGBA, /*GLint colorsize*/0, /*GL2PSrgba *colortable*/NULL, /*GLint nr*/0, /*GLint ng*/0, /*GLint nb*/0, /*GLint buffersize*/4096*4096 /* 16MB */, /*FILE *stream*/ gl2psStream, /*const char *filename*/NULL); } #endif qglviewer::Vec vd=camera()->viewDirection(); renderer->viewDirection=Vector3r(vd[0],vd[1],vd[2]); if(Omega::instance().getScene()){ const shared_ptr& scene=Omega::instance().getScene(); int selection = selectedName(); if(selection!=-1 && (*(Omega::instance().getScene()->bodies)).exists(selection) && isMoving){ static Real lastTimeMoved(0); #if QGLVIEWER_VERSION>=0x020603 qreal v0,v1,v2; manipulatedFrame()->getPosition(v0,v1,v2); #else float v0,v1,v2; manipulatedFrame()->getPosition(v0,v1,v2); #endif if(last == selection) // delay by one redraw, so the body will not jump into 0,0,0 coords { Quaternionr& q = (*(Omega::instance().getScene()->bodies))[selection]->state->ori; Vector3r& v = (*(Omega::instance().getScene()->bodies))[selection]->state->pos; Vector3r& vel = (*(Omega::instance().getScene()->bodies))[selection]->state->vel; Vector3r& angVel = (*(Omega::instance().getScene()->bodies))[selection]->state->angVel; angVel=Vector3r::Zero(); Real dt=(scene->time-lastTimeMoved); lastTimeMoved=scene->time; if (dt!=0) { vel[0]=-(v[0]-v0)/dt; vel[1]=-(v[1]-v1)/dt; vel[2]=-(v[2]-v2)/dt;} else vel[0]=vel[1]=vel[2]=0; //FIXME: should update spin like velocity above, when the body is rotated: double q0,q1,q2,q3; manipulatedFrame()->getOrientation(q0,q1,q2,q3); q.x()=q0;q.y()=q1;q.z()=q2;q.w()=q3; } (*(Omega::instance().getScene()->bodies))[selection]->userForcedDisplacementRedrawHook(); } if(manipulatedClipPlane>=0){ assert(manipulatedClipPlanenumClipPlanes); #if QGLVIEWER_VERSION>=0x020603 qreal v0,v1,v2; manipulatedFrame()->getPosition(v0,v1,v2); #else float v0,v1,v2; manipulatedFrame()->getPosition(v0,v1,v2); #endif double q0,q1,q2,q3; manipulatedFrame()->getOrientation(q0,q1,q2,q3); Se3r newSe3(Vector3r(v0,v1,v2),Quaternionr(q0,q1,q2,q3)); newSe3.orientation.normalize(); const Se3r& oldSe3=renderer->clipPlaneSe3[manipulatedClipPlane]; FOREACH(int planeId, boundClipPlanes){ if(planeId>=renderer->numClipPlanes || !renderer->clipPlaneActive[planeId] || planeId==manipulatedClipPlane) continue; Se3r& boundSe3=renderer->clipPlaneSe3[planeId]; Quaternionr relOrient=oldSe3.orientation.conjugate()*boundSe3.orientation; relOrient.normalize(); Vector3r relPos=oldSe3.orientation.conjugate()*(boundSe3.position-oldSe3.position); boundSe3.position=newSe3.position+newSe3.orientation*relPos; boundSe3.orientation=newSe3.orientation*relOrient; boundSe3.orientation.normalize(); } renderer->clipPlaneSe3[manipulatedClipPlane]=newSe3; } scene->renderer=renderer; renderer->render(scene, selectedName()); } } void GLViewer::drawWithNames(){ qglviewer::Vec vd=camera()->viewDirection(); renderer->viewDirection=Vector3r(vd[0],vd[1],vd[2]); const shared_ptr scene(Omega::instance().getScene()); scene->renderer=renderer; renderer->scene=scene; renderer->renderShape(); } qglviewer::Vec GLViewer::displayedSceneCenter(){ return camera()->unprojectedCoordinatesOf(qglviewer::Vec(width()/2 /* pixels */ ,height()/2 /* pixels */, /*middle between near plane and far plane*/ .5)); } float GLViewer::displayedSceneRadius(){ return (camera()->unprojectedCoordinatesOf(qglviewer::Vec(width()/2,height()/2,.5))-camera()->unprojectedCoordinatesOf(qglviewer::Vec(0,0,.5))).norm(); } void GLViewer::postDraw(){ Real wholeDiameter=QGLViewer::camera()->sceneRadius()*2; renderer->viewInfo.sceneRadius=QGLViewer::camera()->sceneRadius(); qglviewer::Vec c=QGLViewer::camera()->sceneCenter(); renderer->viewInfo.sceneCenter=Vector3r(c[0],c[1],c[2]); Real dispDiameter=min(wholeDiameter,max((Real)displayedSceneRadius()*2,wholeDiameter/1e3)); // limit to avoid drawing 1e5 lines with big zoom level //qglviewer::Vec center=QGLViewer::camera()->sceneCenter(); Real gridStep=pow(10,(floor(log10(dispDiameter)-.7))); Real scaleStep=pow(10,(floor(log10(displayedSceneRadius()*2)-.7))); // unconstrained int nSegments=((int)(wholeDiameter/gridStep))+1; Real realSize=nSegments*gridStep; //LOG_TRACE("nSegments="<projectedCoordinatesOf(center+delta)-camera()->projectedCoordinatesOf(center); for(int xy=0;xy<2;xy++)extremalDxDy[xy]=(axis>0 ? min(extremalDxDy[xy],(int)screenDxDy[axis][xy]) : screenDxDy[axis][xy]); } //LOG_DEBUG("Screen offsets for axes: "<<" x("<=0){ for(int planeId=0; planeIdnumClipPlanes; planeId++){ if(!renderer->clipPlaneActive[planeId] && planeId!=manipulatedClipPlane) continue; glPushMatrix(); const Se3r& se3=renderer->clipPlaneSe3[planeId]; AngleAxisr aa(se3.orientation); glTranslatef(se3.position[0],se3.position[1],se3.position[2]); glRotated(aa.angle()*Mathr::RAD_TO_DEG,aa.axis()[0],aa.axis()[1],aa.axis()[2]); Real cff=1; if(!renderer->clipPlaneActive[planeId]) cff=.4; glColor3f(max((Real)0.,cff*cos(planeId)),max((Real)0.,cff*sin(planeId)),planeId==manipulatedClipPlane); // variable colors QGLViewer::drawGrid(realSize,2*nSegments); drawArrow(wholeDiameter/6); glPopMatrix(); } } Scene* rb=Omega::instance().getScene().get(); #define _W3 setw(3)<time; unsigned min=((unsigned)t/60),sec=(((unsigned)t)%60),msec=((unsigned)(1e3*t))%1000,usec=((unsigned long)(1e6*t))%1000,nsec=((unsigned long)(1e9*t))%1000; if(min>0) oss<<_W2<0) oss<<_W2<0) oss<<_W3<0) oss<<_W3<iter; if(rb->stopAtIter>rb->iter) oss<<" ("<iter)/rb->stopAtIter<<"%)"; QGLViewer::drawText(x,y,oss.str().c_str()); y-=lineHt; } if(drawGrid){ glColor3v(Vector3r(1,1,0)); ostringstream oss; oss<<"grid: "< #include #include #include #include #include #include #include #include #include #include #include #include #ifdef YADE_GL2PS #include #endif void GLViewer::mouseMovesCamera(){ setWheelBinding(Qt::ShiftModifier , FRAME, ZOOM); setWheelBinding(Qt::NoModifier, CAMERA, ZOOM); #if QGLVIEWER_VERSION>=0x020500 setMouseBinding(Qt::ShiftModifier, Qt::RightButton, FRAME, ZOOM); setMouseBinding(Qt::ShiftModifier, Qt::MidButton, FRAME, TRANSLATE); setMouseBinding(Qt::ShiftModifier, Qt::RightButton, FRAME, ROTATE); setMouseBinding(Qt::NoModifier, Qt::MidButton, CAMERA, ZOOM); setMouseBinding(Qt::NoModifier, Qt::LeftButton, CAMERA, ROTATE); setMouseBinding(Qt::NoModifier, Qt::RightButton, CAMERA, TRANSLATE); #else setMouseBinding(Qt::SHIFT + Qt::LeftButton, SELECT); setMouseBinding(Qt::SHIFT + Qt::LeftButton + Qt::RightButton, FRAME, ZOOM); setMouseBinding(Qt::SHIFT + Qt::MidButton, FRAME, TRANSLATE); setMouseBinding(Qt::SHIFT + Qt::RightButton, FRAME, ROTATE); setMouseBinding(Qt::LeftButton + Qt::RightButton, CAMERA, ZOOM); setMouseBinding(Qt::MidButton, CAMERA, ZOOM); setMouseBinding(Qt::LeftButton, CAMERA, ROTATE); setMouseBinding(Qt::RightButton, CAMERA, TRANSLATE); camera()->frame()->setWheelSensitivity(-1.0f); #endif }; void GLViewer::mouseMovesManipulatedFrame(qglviewer::Constraint* c){ setMouseBinding(Qt::LeftButton + Qt::RightButton, FRAME, ZOOM); setMouseBinding(Qt::MidButton, FRAME, ZOOM); setMouseBinding(Qt::LeftButton, FRAME, ROTATE); setMouseBinding(Qt::RightButton, FRAME, TRANSLATE); setWheelBinding(Qt::NoModifier , FRAME, ZOOM); manipulatedFrame()->setConstraint(c); } void GLViewer::mouseMoveEvent(QMouseEvent *e){ last_user_event = boost::posix_time::second_clock::local_time(); QGLViewer::mouseMoveEvent(e); } void GLViewer::mousePressEvent(QMouseEvent *e){ last_user_event = boost::posix_time::second_clock::local_time(); QGLViewer::mousePressEvent(e); } /* Handle double-click event; if clipping plane is manipulated, align it with the global coordinate system. * Otherwise pass the event to QGLViewer to handle it normally. * * mostly copied over from ManipulatedFrame::mouseDoubleClickEvent */ void GLViewer::mouseDoubleClickEvent(QMouseEvent *event){ last_user_event = boost::posix_time::second_clock::local_time(); if(manipulatedClipPlane<0) { /* LOG_DEBUG("Double click not on clipping plane"); */ QGLViewer::mouseDoubleClickEvent(event); return; } #if QT_VERSION >= 0x040000 if (event->modifiers() == Qt::NoModifier) #else if (event->state() == Qt::NoButton) #endif switch (event->button()){ case Qt::LeftButton: manipulatedFrame()->alignWithFrame(NULL,true); LOG_DEBUG("Aligning cutting plane"); break; default: break; // avoid warning } } void GLViewer::wheelEvent(QWheelEvent* event){ last_user_event = boost::posix_time::second_clock::local_time(); if(manipulatedClipPlane<0){ QGLViewer::wheelEvent(event); return; } assert(manipulatedClipPlanenumClipPlanes); float distStep=1e-3*sceneRadius(); float dist=event->delta()*manipulatedFrame()->wheelSensitivity()*distStep; Vector3r normal=renderer->clipPlaneSe3[manipulatedClipPlane].orientation*Vector3r(0,0,1); qglviewer::Vec newPos=manipulatedFrame()->position()+qglviewer::Vec(normal[0],normal[1],normal[2])*dist; manipulatedFrame()->setPosition(newPos); renderer->clipPlaneSe3[manipulatedClipPlane].position=Vector3r(newPos[0],newPos[1],newPos[2]); updateGL(); /* in draw, bound cutting planes will be moved as well */ } trunk-2018.02b/gui/qt5/Inspector.py000066400000000000000000000275011324306050200170100ustar00rootroot00000000000000# encoding: utf-8 from PyQt5.QtCore import * from PyQt5.QtGui import * from yade import * from yade.qt.SerializableEditor import * import yade.qt class EngineInspector(QWidget): def __init__(self,parent=None): QWidget.__init__(self,parent) grid=QGridLayout(self); grid.setSpacing(0); grid.setContentsMargins(0,0,0,0) self.serEd=SeqSerializable(parent=None,getter=lambda:O.engines,setter=lambda x:setattr(O,'engines',x),serType=Engine,path='O.engines') grid.addWidget(self.serEd) self.setLayout(grid) #class MaterialsInspector(QWidget): # def __init__(self,parent=None): # QWidget.__init__(self,parent) # grid=QGridLayout(self); grid.setSpacing(0); grid.setContentsMargins(0,0,0,0) # self.serEd=SeqSerializable(parent=None,getter=lambda:O.materials,setter=lambda x:setattr(O,'materials',x),serType=Engine) # grid.addWidget(self.serEd) # self.setLayout(grid) class CellInspector(QWidget): def __init__(self,parent=None): QWidget.__init__(self,parent) self.layout=QVBoxLayout(self) #; self.layout.setSpacing(0); self.layout.setContentsMargins(0,0,0,0) self.periCheckBox=QCheckBox('periodic boundary',self) self.periCheckBox.clicked.connect(self.update) self.layout.addWidget(self.periCheckBox) self.scroll=QScrollArea(self); self.scroll.setWidgetResizable(True) self.layout.addWidget(self.scroll) self.setLayout(self.layout) self.refresh() self.refreshTimer=QTimer(self) self.refreshTimer.timeout.connect(self.refresh) self.refreshTimer.start(1000) def refresh(self): self.periCheckBox.setChecked(O.periodic) editor=self.scroll.widget() if not O.periodic and editor: self.scroll.takeWidget() if (O.periodic and not editor) or (editor and editor.ser!=O.cell): self.scroll.setWidget(SerializableEditor(O.cell,parent=self,showType=True,path='O.cell')) def update(self): self.scroll.takeWidget() # do this before changing periodicity, otherwise the SerializableEditor will raise exception about None object O.periodic=self.periCheckBox.isChecked() self.refresh() def makeBodyLabel(b): ret=unicode(b.id)+u' ' if not b.shape: ret+=u'⬚' else: typeMap={'Sphere':u'⚫','Facet':u'△','Wall':u'┃','Box':u'⎕','Cylinder':u'⌭','ChainedCylinder':u'☡','Clump':u'☍'} ret+=typeMap.get(b.shape.__class__.__name__,u'﹖') if not b.dynamic: ret+=u'⚓' elif b.state.blockedDOFs: ret+=u'⎈' return ret def getBodyIdFromLabel(label): try: return int(unicode(label).split()[0]) except ValueError: print 'Error with label:',unicode(label) return -1 class BodyInspector(QWidget): def __init__(self,bodyId=-1,parent=None,bodyLinkCallback=None,intrLinkCallback=None): QWidget.__init__(self,parent) v=yade.qt.views() self.bodyId=bodyId if bodyId<0 and len(v)>0 and v[0].selection>0: self.bodyId=v[0].selection self.idGlSync=self.bodyId self.bodyLinkCallback,self.intrLinkCallback=bodyLinkCallback,intrLinkCallback self.bodyIdBox=QSpinBox(self) self.bodyIdBox.setMinimum(0) self.bodyIdBox.setMaximum(100000000) self.bodyIdBox.setValue(self.bodyId) self.intrWithCombo=QComboBox(self); self.gotoBodyButton=QPushButton(u'→ #',self) self.gotoIntrButton=QPushButton(u'→ #+#',self) # id selector topBoxWidget=QWidget(self); topBox=QHBoxLayout(topBoxWidget); topBox.setContentsMargins(0,0,0,0); #topBox.setSpacing(0); hashLabel=QLabel('#',self); hashLabel.setFixedWidth(8) topBox.addWidget(hashLabel) topBox.addWidget(self.bodyIdBox) self.plusLabel=QLabel('+',self); topBox.addWidget(self.plusLabel) hashLabel2=QLabel('#',self); hashLabel2.setFixedWidth(8); topBox.addWidget(hashLabel2) topBox.addWidget(self.intrWithCombo) topBox.addStretch() topBox.addWidget(self.gotoBodyButton) topBox.addWidget(self.gotoIntrButton) topBoxWidget.setLayout(topBox) # forces display forcesWidget=QFrame(self); forcesWidget.setFrameShape(QFrame.Box); self.forceGrid=QGridLayout(forcesWidget); self.forceGrid.setVerticalSpacing(0); self.forceGrid.setHorizontalSpacing(9); self.forceGrid.setContentsMargins(4,4,4,4); for i,j in itertools.product((0,1,2,3),(-1,0,1,2)): lab=QLabel(''+('force','torque','move','rot')[i]+'' if j==-1 else ''); self.forceGrid.addWidget(lab,i,j+1); if j>=0: lab.setAlignment(Qt.AlignRight) if i>1: lab.hide() # do not show forced moves and rotations by default (they will appear if non-zero) self.showMovRot=False # self.grid=QGridLayout(self); self.grid.setSpacing(0); self.grid.setContentsMargins(0,0,0,0) self.grid.addWidget(topBoxWidget) self.grid.addWidget(forcesWidget) self.scroll=QScrollArea(self) self.scroll.setWidgetResizable(True) self.grid.addWidget(self.scroll) self.tryShowBody() self.bodyIdBox.valueChanged.connect(self.bodyIdSlot) self.gotoBodyButton.clicked.connect(self.gotoBodySlot) self.gotoIntrButton.clicked.connect(self.gotoIntrSlot) self.refreshTimer=QTimer(self) self.refreshTimer.timeout.connect(self.refreshEvent) self.refreshTimer.start(1000) self.intrWithCombo.addItems(['0']); self.intrWithCombo.setCurrentIndex(0); self.intrWithCombo.setMinimumWidth(80) self.setWindowTitle('Body #%d'%self.bodyId) self.gotoBodySlot() def displayForces(self): if self.bodyId<0: return try: val=[O.forces.f(self.bodyId),O.forces.t(self.bodyId),O.forces.move(self.bodyId),O.forces.rot(self.bodyId)] hasMovRot=(val[2]!=Vector3.Zero or val[3]!=Vector3.Zero) if hasMovRot!=self.showMovRot: for i,j in itertools.product((2,3),(-1,0,1,2)): if hasMovRot: self.forceGrid.itemAtPosition(i,j+1).widget().show() else: self.forceGrid.itemAtPosition(i,j+1).widget().hide() self.showMovRot=hasMovRot rows=((0,1,2,3) if hasMovRot else (0,1)) for i,j in itertools.product(rows,(0,1,2)): self.forceGrid.itemAtPosition(i,j+1).widget().setText(''+str(val[i][j])+'') except IndexError:pass def tryShowBody(self): try: b=O.bodies[self.bodyId] self.serEd=SerializableEditor(b,showType=True,parent=self,path='O.bodies[%d]'%self.bodyId) except IndexError: self.serEd=QFrame(self) self.bodyId=-1 self.scroll.setWidget(self.serEd) def changeIdSlot(self,newId): self.bodyIdBox.setValue(newId); self.bodyIdSlot() def bodyIdSlot(self): self.bodyId=self.bodyIdBox.value() self.tryShowBody() self.setWindowTitle('Body #%d'%self.bodyId) self.refreshEvent() def gotoBodySlot(self): try: id=int(getBodyIdFromLabel(self.intrWithCombo.currentText())) except ValueError: return # empty id if not self.bodyLinkCallback: self.bodyIdBox.setValue(id); self.bodyId=id else: self.bodyLinkCallback(id) def gotoIntrSlot(self): ids=self.bodyIdBox.value(),getBodyIdFromLabel(self.intrWithCombo.currentText()) if not self.intrLinkCallback: self.ii=InteractionInspector(ids) self.ii.show() else: self.intrLinkCallback(ids) def refreshEvent(self): try: O.bodies[self.bodyId] except: self.bodyId=-1 # invalidate deleted body # no body shown yet, try to get the first one... if self.bodyId<0 and len(O.bodies)>0: try: print 'SET ZERO' b=O.bodies[0]; self.bodyIdBox.setValue(0) except IndexError: pass v=yade.qt.views() if len(v)>0 and v[0].selection!=self.bodyId: if self.idGlSync==self.bodyId: # changed in the viewer, reset ourselves self.bodyId=self.idGlSync=v[0].selection; self.changeIdSlot(self.bodyId) return else: v[0].selection=self.idGlSync=self.bodyId # changed here, set in the viewer meId=self.bodyIdBox.value(); pos=self.intrWithCombo.currentIndex() try: meLabel=makeBodyLabel(O.bodies[meId]) except IndexError: meLabel=u'…' self.plusLabel.setText(' '.join(meLabel.split()[1:])+' +') # do not repeat the id self.bodyIdBox.setMaximum(len(O.bodies)-1) others=[(i.id1 if i.id1!=meId else i.id2) for i in O.interactions.withBody(self.bodyIdBox.value()) if i.isReal] others.sort() self.intrWithCombo.clear() self.intrWithCombo.addItems([makeBodyLabel(O.bodies[i]) for i in others]) if pos>self.intrWithCombo.count() or pos<0: pos=0 self.intrWithCombo.setCurrentIndex(pos); other=self.intrWithCombo.itemText(pos) if other=='': self.gotoBodyButton.setEnabled(False); self.gotoIntrButton.setEnabled(False) other=u'∅' else: self.gotoBodyButton.setEnabled(True); self.gotoIntrButton.setEnabled(True) self.gotoBodyButton.setText(u'→ %s'%other) self.gotoIntrButton.setText(u'→ %s + %s'%(meLabel,other)) self.displayForces() class InteractionInspector(QWidget): def __init__(self,ids=None,parent=None,bodyLinkCallback=None): QWidget.__init__(self,parent) self.bodyLinkCallback=bodyLinkCallback self.ids=ids self.gotoId1Button=QPushButton(u'#…',self) self.gotoId2Button=QPushButton(u'#…',self) self.gotoId1Button.clicked.connect(self.gotoId1Slot) self.gotoId2Button.clicked.connect(self.gotoId2Slot) topBoxWidget=QWidget(self) topBox=QHBoxLayout(topBoxWidget) topBox.addWidget(self.gotoId1Button) labelPlus=QLabel('+',self); labelPlus.setAlignment(Qt.AlignHCenter) topBox.addWidget(labelPlus) topBox.addWidget(self.gotoId2Button) topBoxWidget.setLayout(topBox) self.setWindowTitle(u'No interaction') self.grid=QGridLayout(self); self.grid.setSpacing(0); self.grid.setContentsMargins(0,0,0,0) self.grid.addWidget(topBoxWidget,0,0) self.scroll=QScrollArea(self) self.scroll.setWidgetResizable(True) self.grid.addWidget(self.scroll) self.refreshTimer=QTimer(self) self.refreshTimer.timeout.connect(self.refreshEvent) self.refreshTimer.start(1000) if self.ids: self.setupInteraction() def setupInteraction(self): try: intr=O.interactions[self.ids[0],self.ids[1]] self.serEd=SerializableEditor(intr,showType=True,parent=self.scroll,path='O.interactions[%d,%d]'%(self.ids[0],self.ids[1])) self.scroll.setWidget(self.serEd) self.gotoId1Button.setText('#'+makeBodyLabel(O.bodies[self.ids[0]])) self.gotoId2Button.setText('#'+makeBodyLabel(O.bodies[self.ids[1]])) self.setWindowTitle('Interaction #%d + #%d'%(self.ids[0],self.ids[1])) except IndexError: if self.ids: # reset view (there was an interaction) self.ids=None self.serEd=QFrame(self.scroll); self.scroll.setWidget(self.serEd) self.setWindowTitle('No interaction') self.gotoId1Button.setText(u'#…'); self.gotoId2Button.setText(u'#…'); def gotoId(self,bodyId): if self.bodyLinkCallback: self.bodyLinkCallback(bodyId) else: self.bi=BodyInspector(bodyId); self.bi.show() def gotoId1Slot(self): self.gotoId(self.ids[0]) def gotoId2Slot(self): self.gotoId(self.ids[1]) def refreshEvent(self): # no ids yet -- try getting the first interaction, if it exists if not self.ids: try: i=O.interactions.nth(0) self.ids=i.id1,i.id2 self.setupInteraction() return except IndexError: return # no interaction exists at all try: # try to fetch an existing interaction O.interactions[self.ids[0],self.ids[1]] except IndexError: self.ids=None class SimulationInspector(QWidget): def __init__(self,parent=None): QWidget.__init__(self,parent) self.setWindowTitle("Simulation Inspection") self.tabWidget=QTabWidget(self) self.engineInspector=EngineInspector(parent=None) self.bodyInspector=BodyInspector(parent=None,intrLinkCallback=self.changeIntrIds) self.intrInspector=InteractionInspector(parent=None,bodyLinkCallback=self.changeBodyId) self.cellInspector=CellInspector(parent=None) for i,name,widget in [(0,'Engines',self.engineInspector),(1,'Bodies',self.bodyInspector),(2,'Interactions',self.intrInspector),(3,'Cell',self.cellInspector)]: self.tabWidget.addTab(widget,name) grid=QGridLayout(self); grid.setSpacing(0); grid.setContentsMargins(0,0,0,0) grid.addWidget(self.tabWidget) self.setLayout(grid) def changeIntrIds(self,ids): self.tabWidget.removeTab(2); self.intrInspector.close() self.intrInspector=InteractionInspector(ids=ids,parent=None,bodyLinkCallback=self.changeBodyId) self.tabWidget.insertTab(2,self.intrInspector,'Interactions') self.tabWidget.setCurrentIndex(2) def changeBodyId(self,id): self.bodyInspector.changeIdSlot(id) self.tabWidget.setCurrentIndex(1) trunk-2018.02b/gui/qt5/OpenGLManager.cpp000066400000000000000000000051631324306050200176130ustar00rootroot00000000000000#include"OpenGLManager.hpp" CREATE_LOGGER(OpenGLManager); OpenGLManager* OpenGLManager::self=NULL; OpenGLManager::OpenGLManager(QObject* parent): QObject(parent){ if(self) throw runtime_error("OpenGLManager instance already exists, uses OpenGLManager::self to retrieve it."); self=this; renderer=shared_ptr(new OpenGLRenderer); renderer->init(); connect(this,SIGNAL(createView()),this,SLOT(createViewSlot())); connect(this,SIGNAL(resizeView(int,int,int)),this,SLOT(resizeViewSlot(int,int,int))); connect(this,SIGNAL(closeView(int)),this,SLOT(closeViewSlot(int))); connect(this,SIGNAL(startTimerSignal()),this,SLOT(startTimerSlot()),Qt::QueuedConnection); } void OpenGLManager::timerEvent(QTimerEvent* event){ //cerr<<"."; boost::mutex::scoped_lock lock(viewsMutex); // when sharing the 0th view widget, it should be enough to update the primary view only //if(views.size()>0) views[0]->updateGL(); #if 1 FOREACH(const shared_ptr& view, views){ if(view) view->updateGL(); } #endif } void OpenGLManager::createViewSlot(){ boost::mutex::scoped_lock lock(viewsMutex); if(views.size()==0){ views.push_back(shared_ptr(new GLViewer(0,renderer,/*shareWidget*/(QGLWidget*)0))); } else { throw runtime_error("Secondary views not supported"); //views.push_back(shared_ptr(new GLViewer(views.size(),renderer,views[0].get()))); } } void OpenGLManager::resizeViewSlot(int id, int wd, int ht){ views[id]->resize(wd,ht); } void OpenGLManager::closeViewSlot(int id){ boost::mutex::scoped_lock lock(viewsMutex); for(size_t i=views.size()-1; (!views[i]); i--){ views.resize(i); } // delete empty views from the end if(id<0){ // close the last one existing assert(*views.rbegin()); // this should have been sanitized by the loop above views.resize(views.size()-1); // releases the pointer as well } if(id==0){ LOG_DEBUG("Closing primary view."); if(views.size()==1) views.clear(); else{ LOG_INFO("Cannot close primary view, secondary views still exist."); } } } void OpenGLManager::centerAllViews(){ boost::mutex::scoped_lock lock(viewsMutex); FOREACH(const shared_ptr& g, views){ if(!g) continue; g->centerScene(); } } void OpenGLManager::startTimerSlot(){ startTimer(50); } int OpenGLManager::waitForNewView(float timeout,bool center){ size_t origViewCount=views.size(); emitCreateView(); float t=0; while(views.size()!=origViewCount+1){ usleep(50000); t+=.05; // wait at most 5 secs if(t>=timeout) { LOG_ERROR("Timeout waiting for the new view to open, giving up."); return -1; } } if(center)(*views.rbegin())->centerScene(); return (*views.rbegin())->viewId; } trunk-2018.02b/gui/qt5/OpenGLManager.hpp000066400000000000000000000027601324306050200176200ustar00rootroot00000000000000#pragma once //#include #ifndef Q_MOC_RUN #include"GLViewer.hpp" #endif #include #include /* Singleton class managing OpenGL views, a renderer instance and timer to refresh the display. */ class OpenGLManager: public QObject{ Q_OBJECT DECLARE_LOGGER; public: static OpenGLManager* self; OpenGLManager(QObject *parent=0); // manipulation must lock viewsMutex! std::vector > views; shared_ptr renderer; // signals are protected, emitting them is therefore wrapped with such funcs void emitResizeView(int id, int wd, int ht){ emit resizeView(id,wd,ht); } void emitCreateView(){ emit createView(); } void emitStartTimer(){ emit startTimerSignal(); } void emitCloseView(int id){ emit closeView(id); } // create a new view and wait for it to become available; return the view number // if timout (in seconds) elapses without the view to come up, reports error and returns -1 int waitForNewView(float timeout=5., bool center=true); signals: void createView(); void resizeView(int id, int wd, int ht); void closeView(int id); // this is used to start timer from the main thread via postEvent (ugly) void startTimerSignal(); public slots: virtual void createViewSlot(); virtual void resizeViewSlot(int id, int wd, int ht); virtual void closeViewSlot(int id=-1); virtual void timerEvent(QTimerEvent* event); virtual void startTimerSlot(); void centerAllViews(); private: boost::mutex viewsMutex; }; trunk-2018.02b/gui/qt5/SerializableEditor.py000066400000000000000000001107751324306050200206250ustar00rootroot00000000000000# encoding: utf-8 from PyQt5.QtCore import * from PyQt5.QtGui import * from PyQt5 import QtGui from PyQt5 import QtWidgets from PyQt5.QtWidgets import * import re,itertools import logging logging.trace=logging.debug logging.basicConfig(level=logging.INFO) from yade import * import yade.qt from minieigen import * seqSerializableShowType=True # show type headings in serializable sequences (takes vertical space, but makes the type hyperlinked) # BUG: cursor is moved to the beginning of the input field even if it has focus # checking for focus seems to return True always and cursor is never moved # the 'True or' part effectively disables the condition (so that the cursor is moved always), but it might be fixed in the future somehow # if True or w.hasFocus(): w.home(False) # Janek: It looks like I've fixed this BUG, please do more testing. # def makeWrapperHref(text,className,attr=None,static=False): """Create clickable HTML hyperlink to a Yade class or its attribute. :param className: name of the class to link to. :param attr: attribute to link to. If given, must exist directly in given *className*; if not given or empty, link to the class itself is created and *attr* is ignored. :return: HTML with the hyperref. """ if not static: return '%s'%(yade.qt.sphinxDocWrapperPage,className,(('.'+attr) if attr else ''),text) else: return '%s'%(yade.qt.sphinxDocWrapperPage,className,attr,text) def serializableHref(ser,attr=None,text=None): """Return HTML href to a *ser* optionally to the attribute *attr*. The class hierarchy is crawled upwards to find out in which parent class is *attr* defined, so that the href target is a valid link. In that case, only single inheritace is assumed and the first class from the top defining *attr* is used. :param ser: object of class deriving from :yref:`Serializable`, or string; if string, *attr* must be empty. :param attr: name of the attribute to link to; if empty, linke to the class itself is created. :param text: visible text of the hyperlink; if not given, either class name or attribute name without class name (when *attr* is not given) is used. :returns: HTML with the hyperref. """ # klass is a class name given as string if isinstance(ser,str): if attr: raise InvalidArgument("When *ser* is a string, *attr* must be empty (only class link can be created)") return makeWrapperHref(text if text else ser,ser) # klass is a type object if attr: klass=ser.__class__ while attr in dir(klass.__bases__[0]): klass=klass.__bases__[0] if not text: text=attr else: klass=ser.__class__ if not text: text=klass.__name__ return makeWrapperHref(text,klass.__name__,attr,static=(attr and getattr(klass,attr)==getattr(ser,attr))) class AttrEditor(): """Abstract base class handing some aspects common to all attribute editors. Holds exacly one attribute which is updated whenever it changes.""" def __init__(self,getter=None,setter=None): self.getter,self.setter=getter,setter self.hot,self.focused=False,False self.widget=None def refresh(self): pass def update(self): pass def isHot(self,hot=True): "Called when the widget gets focus; mark it hot, change colors etc." if hot==self.hot: return self.hot=hot if hot: self.setStyleSheet('QWidget { background: red }') else: self.setStyleSheet('QWidget { background: none }') def sizeHint(self): return QSize(150,12) def trySetter(self,val): try: self.setter(val) except AttributeError: self.setEnabled(False) self.isHot(False) class AttrEditor_Bool(AttrEditor,QFrame): def __init__(self,parent,getter,setter): AttrEditor.__init__(self,getter,setter) QFrame.__init__(self,parent) self.checkBox=QCheckBox(self) lay=QVBoxLayout(self); lay.setSpacing(0); lay.addStretch(1); lay.addWidget(self.checkBox); lay.addStretch(1) self.checkBox.clicked.connect(self.update) def refresh(self): self.checkBox.setChecked(self.getter()) def update(self): self.trySetter(self.checkBox.isChecked()) class AttrEditor_Int(AttrEditor,QSpinBox): def __init__(self,parent,getter,setter): AttrEditor.__init__(self,getter,setter) QSpinBox.__init__(self,parent) self.setRange(int(-1e9),int(1e9)); self.setSingleStep(1); self.valueChanged.connect(self.update) def refresh(self): if (not self.hasFocus()): self.setValue(self.getter()) def update(self): self.trySetter(self.value()) class AttrEditor_Str(AttrEditor,QLineEdit): def __init__(self,parent,getter,setter): AttrEditor.__init__(self,getter,setter) QLineEdit.__init__(self,parent) self.textEdited.connect(self.isHot) self.selectionChanged.connect(self.isHot) self.editingFinished.connect(self.update) def refresh(self): if (not self.hasFocus()): self.setText(self.getter()) def update(self): self.trySetter(str(self.text())) class AttrEditor_Float(AttrEditor,QLineEdit): def __init__(self,parent,getter,setter): AttrEditor.__init__(self,getter,setter) QLineEdit.__init__(self,parent) self.textEdited.connect(self.isHot) self.selectionChanged.connect(self.isHot) self.editingFinished.connect(self.update) def refresh(self): #if True or not self.hasFocus(): self.home(False) if (not self.hasFocus()): self.setText(str(self.getter())); self.home(False) def update(self): try: self.trySetter(float(self.text())) except ValueError: self.refresh() class AttrEditor_Complex(AttrEditor,QLineEdit): def __init__(self,parent,getter,setter): AttrEditor.__init__(self,getter,setter) QFrame.__init__(self,parent) self.rows,self.cols=1,2 self.setContentsMargins(0,0,0,0) self.first=True val=self.getter() self.grid=QGridLayout(self); self.grid.setSpacing(0); self.grid.setContentsMargins(0,0,0,0) for row,col in itertools.product(range(self.rows),range(self.cols)): w=QLineEdit('') self.grid.addWidget(w,row,col); w.textEdited.connect(self.isHot) w.selectionChanged.connect(self.isHot) w.editingFinished.connect(self.update) def refresh(self,force=False): val=self.getter() for row,col in itertools.product(range(self.rows),range(self.cols)): w=self.grid.itemAtPosition(row,col).widget() if(self.first or force): w.setText(str(val.real if col==0 else val.imag)) #if True or not w.hasFocus: w.home(False) # make the left-most part visible, if the text is wider than the widget if (not w.hasFocus): w.setText(str(val.real if col==0 else val.imag)) w.home(False) # make the left-most part visible, if the text is wider than the widget self.first=False def update(self): try: val=self.getter() w1=self.grid.itemAtPosition(0,0).widget() w2=self.grid.itemAtPosition(0,1).widget() if w1.isModified() or w2.isModified(): val=complex(float(w1.text()),float(w2.text())) logging.debug('setting'+str(val)) self.trySetter(val) except ValueError: self.refresh(force=True) def setFocus(self): self.grid.itemAtPosition(0,0).widget().setFocus() class AttrEditor_Quaternion(AttrEditor,QFrame): def __init__(self,parent,getter,setter): AttrEditor.__init__(self,getter,setter) QFrame.__init__(self,parent) self.grid=QHBoxLayout(self); self.grid.setSpacing(0); self.grid.setContentsMargins(0,0,0,0) for i in range(4): if i==3: # add vertical divider (axis | angle) f=QFrame(self); f.setFrameShape(QFrame.VLine); f.setFrameShadow(QFrame.Sunken); f.setFixedWidth(4) self.grid.addWidget(f) w=QLineEdit('') self.grid.addWidget(w); w.textEdited.connect(self.isHot) w.selectionChanged.connect(self.isHot) w.editingFinished.connect(self.update) def refresh(self): val=self.getter(); axis,angle=val.toAxisAngle() for i in (0,1,2,4): #if True or not w.hasFocus(): w.home(False) w=self.grid.itemAt(i).widget(); if (not w.hasFocus()): w.setText(str(axis[i] if i<3 else angle)); w.home(False) def update(self): try: x=[float((self.grid.itemAt(i).widget().text())) for i in (0,1,2,4)] except ValueError: self.refresh() return q=Quaternion(Vector3(x[0],x[1],x[2]),x[3]); q.normalize() # from axis-angle self.trySetter(q) def setFocus(self): self.grid.itemAt(0).widget().setFocus() class AttrEditor_Se3(AttrEditor,QFrame): def __init__(self,parent,getter,setter): AttrEditor.__init__(self,getter,setter) QFrame.__init__(self,parent) self.grid=QGridLayout(self); self.grid.setSpacing(0); for row,col in itertools.product(range(2),range(5)): # one additional column for vertical line in quaternion if (row,col)==(0,3): continue if (row,col)==(0,4): self.grid.addWidget(QLabel(u'←posori',self),row,col); continue if (row,col)==(1,3): f=QFrame(self); f.setFrameShape(QFrame.VLine); f.setFrameShadow(QFrame.Sunken); f.setFixedWidth(4); self.grid.addWidget(f,row,col); continue w=QLineEdit('') self.grid.addWidget(w,row,col); w.textEdited.connect(self.isHot) w.selectionChanged.connect(self.isHot) w.editingFinished.connect(self.update) def refresh(self): pos,ori=self.getter(); axis,angle=ori.toAxisAngle() for i in (0,1,2,4): #if True or not w.hasFocus(): w.home(False) w=self.grid.itemAtPosition(1,i).widget(); if (not w.hasFocus()): w.setText(str(axis[i] if i<3 else angle)); w.home(False) for i in (0,1,2): #if True or not w.hasFocus(): w.home(False) w=self.grid.itemAtPosition(0,i).widget(); if (not w.hasFocus()): w.setText(str(pos[i])); w.home(False) def update(self): try: q=[float((self.grid.itemAtPosition(1,i).widget().text())) for i in (0,1,2,4)] v=[float((self.grid.itemAtPosition(0,i).widget().text())) for i in (0,1,2)] except ValueError: self.refresh() return qq=Quaternion(Vector3(q[0],q[1],q[2]),q[3]); qq.normalize() # from axis-angle self.trySetter((v,qq)) def setFocus(self): self.grid.itemAtPosition(0,0).widget().setFocus() class AttrEditor_MatrixX(AttrEditor,QFrame): def __init__(self,parent,getter,setter,rows,cols,idxConverter): 'idxConverter converts row,col tuple to either (row,col), (col) etc depending on what access is used for []' AttrEditor.__init__(self,getter,setter) QFrame.__init__(self,parent) self.rows,self.cols=rows,cols self.idxConverter=idxConverter self.setContentsMargins(0,0,0,0) self.first=True val=self.getter() self.grid=QGridLayout(self); self.grid.setSpacing(0); for row,col in itertools.product(range(self.rows),range(self.cols)): w=QLineEdit('') self.grid.addWidget(w,row,col); w.textEdited.connect(self.isHot) w.selectionChanged.connect(self.isHot) w.editingFinished.connect(self.update) def refresh(self,force=False): val=self.getter() for row,col in itertools.product(range(self.rows),range(self.cols)): w=self.grid.itemAtPosition(row,col).widget() if(self.first or force): w.setText(str(val[self.idxConverter(row,col)])) if (not w.hasFocus): w.setText(str(val[self.idxConverter(row,col)])) w.home(False) # make the left-most part visible, if the text is wider than the widget self.first=False def update(self): try: val=self.getter() for row,col in itertools.product(range(self.rows),range(self.cols)): w=self.grid.itemAtPosition(row,col).widget() if w.isModified(): val[self.idxConverter(row,col)]=float(w.text()) logging.debug('setting'+str(val)) self.trySetter(val) except ValueError: self.refresh(force=True) def setFocus(self): self.grid.itemAtPosition(0,0).widget().setFocus() class AttrEditor_MatrixXi(AttrEditor,QFrame): def __init__(self,parent,getter,setter,rows,cols,idxConverter): 'idxConverter converts row,col tuple to either (row,col), (col) etc depending on what access is used for []' AttrEditor.__init__(self,getter,setter) QFrame.__init__(self,parent) self.rows,self.cols=rows,cols self.idxConverter=idxConverter self.setContentsMargins(0,0,0,0) self.grid=QGridLayout(self); self.grid.setSpacing(0); self.grid.setContentsMargins(0,0,0,0) for row,col in itertools.product(range(self.rows),range(self.cols)): w=QSpinBox() w.setRange(int(-1e9),int(1e9)); w.setSingleStep(1); self.grid.addWidget(w,row,col); self.refresh() # refresh before connecting signals! for row,col in itertools.product(range(self.rows),range(self.cols)): self.grid.itemAtPosition(row,col).widget().valueChanged.connect(self.update) def refresh(self): val=self.getter() for row,col in itertools.product(range(self.rows),range(self.cols)): w=self.grid.itemAtPosition(row,col).widget().setValue(val[self.idxConverter(row,col)]) def update(self): val=self.getter(); modified=False for row,col in itertools.product(range(self.rows),range(self.cols)): w=self.grid.itemAtPosition(row,col).widget() if w.value()!=val[self.idxConverter(row,col)]: modified=True; val[self.idxConverter(row,col)]=w.value() if not modified: return logging.debug('setting'+str(val)) self.trySetter(val) def setFocus(self): self.grid.itemAtPosition(0,0).widget().setFocus() class AttrEditor_Vector6i(AttrEditor_MatrixXi): def __init__(self,parent,getter,setter): AttrEditor_MatrixXi.__init__(self,parent,getter,setter,1,6,lambda r,c:c) class AttrEditor_Vector3i(AttrEditor_MatrixXi): def __init__(self,parent,getter,setter): AttrEditor_MatrixXi.__init__(self,parent,getter,setter,1,3,lambda r,c:c) class AttrEditor_Vector2i(AttrEditor_MatrixXi): def __init__(self,parent,getter,setter): AttrEditor_MatrixXi.__init__(self,parent,getter,setter,1,2,lambda r,c:c) class AttrEditor_Vector6(AttrEditor_MatrixX): def __init__(self,parent,getter,setter): AttrEditor_MatrixX.__init__(self,parent,getter,setter,1,6,lambda r,c:c) class AttrEditor_Vector3(AttrEditor_MatrixX): def __init__(self,parent,getter,setter): AttrEditor_MatrixX.__init__(self,parent,getter,setter,1,3,lambda r,c:c) class AttrEditor_Vector2(AttrEditor_MatrixX): def __init__(self,parent,getter,setter): AttrEditor_MatrixX.__init__(self,parent,getter,setter,1,2,lambda r,c:c) class AttrEditor_Matrix3(AttrEditor_MatrixX): def __init__(self,parent,getter,setter): AttrEditor_MatrixX.__init__(self,parent,getter,setter,3,3,lambda r,c:(r,c)) class Se3FakeType: pass _fundamentalEditorMap={bool:AttrEditor_Bool,str:AttrEditor_Str,int:AttrEditor_Int,float:AttrEditor_Float,complex:AttrEditor_Complex,Quaternion:AttrEditor_Quaternion,Vector2:AttrEditor_Vector2,Vector3:AttrEditor_Vector3,Vector6:AttrEditor_Vector6,Matrix3:AttrEditor_Matrix3,Vector6i:AttrEditor_Vector6i,Vector3i:AttrEditor_Vector3i,Vector2i:AttrEditor_Vector2i,Se3FakeType:AttrEditor_Se3} _fundamentalInitValues={bool:True,str:'',int:0,float:0.0,complex:0.0j,Quaternion:Quaternion((0,1,0),0.0),Vector3:Vector3.Zero,Matrix3:Matrix3.Zero,Vector6:Vector6.Zero,Vector6i:Vector6i.Zero,Vector3i:Vector3i.Zero,Vector2i:Vector2i.Zero,Vector2:Vector2.Zero,Se3FakeType:(Vector3.Zero,Quaternion((0,1,0),0.0))} class SerQLabel(QLabel): def __init__(self,parent,label,tooltip,path): QLabel.__init__(self,parent) self.path=path self.setText(label) if tooltip or path: self.setToolTip((''+path+'
    ' if self.path else '')+(tooltip if tooltip else '')) self.linkActivated.connect(yade.qt.openUrl) def mousePressEvent(self,event): if event.button()!=Qt.MidButton: event.ignore(); return # middle button clicked, paste pasteText to clipboard cb=QApplication.clipboard() cb.setText(self.path,mode=QClipboard.Clipboard) cb.setText(self.path,mode=QClipboard.Selection) # X11 global selection buffer event.accept() class SerializableEditor(QFrame): "Class displaying and modifying serializable attributes of a yade object." import collections import logging # each attribute has one entry associated with itself class EntryData: def __init__(self,name,T,flags=0): self.name,self.T,self.flags=name,T,flags self.lineNo,self.widget=None,None def __init__(self,ser,parent=None,ignoredAttrs=set(),showType=False,path=None): "Construct window, *ser* is the object we want to show." QtWidgets.QFrame.__init__(self,parent) self.ser=ser self.path=(ser.label if (hasattr(ser,'label') and ser.label) else path) self.showType=showType self.hot=False self.entries=[] self.ignoredAttrs=ignoredAttrs logging.debug('New Serializable of type %s'%ser.__class__.__name__) self.setWindowTitle(str(ser)) self.mkWidgets() self.refreshTimer=QTimer(self) self.refreshTimer.timeout.connect(self.refreshEvent) self.refreshTimer.start(500) def getListTypeFromDocstring(self,attr): "Guess type of array by scanning docstring for :yattrtype: and parsing its argument; ugly, but works." doc=getattr(self.ser.__class__,attr).__doc__ if doc==None: logging.error("Attribute %s has no docstring."%attr) return None m=re.search(r':yattrtype:`([^`]*)`',doc) if not m: logging.error("Attribute %s does not contain :yattrtype:`....` (docstring is '%s'"%(attr,doc)) return None cxxT=m.group(1) logging.debug('Got type "%s" from :yattrtype:'%cxxT) def vecTest(T,cxxT): #regexp=r'^\s*(std\s*::)?\s*vector\s*<\s*(std\s*::)?\s*('+T+r')\s*>\s*$' regexp=r'^\s*(std\s*::)?\s*vector\s*<\s*(shared_ptr\s*<\s*)?\s*(std\s*::)?\s*('+T+r')(\s*>)?\s*>\s*$' m=re.match(regexp,cxxT) return m vecMap={ 'bool':bool,'int':int,'long':int,'Body::id_t':long,'size_t':long, 'Real':float,'float':float,'double':float,'complex':complex,'std::complex':complex, 'Vector6r':Vector6,'Vector6i':Vector6i,'Vector3i':Vector3i,'Vector2r':Vector2,'Vector2i':Vector2i, 'Vector3r':Vector3,'Matrix3r':Matrix3,'Se3r':Se3FakeType, 'string':str, #'BodyCallback':BodyCallback, 'IntrCallback':IntrCallback,'BoundFunctor':BoundFunctor,'IGeomFunctor':IGeomFunctor,'IPhysFunctor':IPhysFunctor,'LawFunctor':LawFunctor,'KinematicEngine':KinematicEngine, 'GlShapeFunctor':GlShapeFunctor,'GlStateFunctor':GlStateFunctor,'GlIGeomFunctor':GlIGeomFunctor,'GlIPhysFunctor':GlIPhysFunctor,'GlBoundFunctor':GlBoundFunctor,'GlExtraDrawer':GlExtraDrawer } for T,ret in vecMap.items(): if vecTest(T,cxxT): logging.debug("Got type %s from cxx type %s"%(repr(ret),cxxT)) return (ret,) logging.error("Unable to guess python type from cxx type '%s'"%cxxT) return None def mkAttrEntries(self): if self.ser==None: return try: d=self.ser.dict() except TypeError: logging.error('TypeError when getting attributes of '+str(self.ser)+',skipping. ') import traceback traceback.print_exc() attrs=self.ser.dict().keys(); attrs.sort() for attr in attrs: val=getattr(self.ser,attr) # get the value using serattr, as it might be different from what the dictionary provides (e.g. Body.blockedDOFs) t=None doc=getattr(self.ser.__class__,attr).__doc__; # some attributes are not shown if '|yhidden|' in doc: continue if attr in self.ignoredAttrs: continue # FIXME: (Janek) Implementing Quantum Mechanics makes some DEM assumptions # invalid. I think that we should rethink what base class Body contains, so # that in QM we would not need to use this hack to hide some variables. # However it is great to note that only this little 'cosmetic' hack is needed # to make Quantum Mechanics possible in yade # See also: class QuantumMechanicalState, class QuantumMechanicalBody, gui/qt4/SerializableEditor.py if hasattr(self.ser,"qtHide") and (attr in getattr(self.ser,"qtHide").split()): continue if isinstance(val,list): t=self.getListTypeFromDocstring(attr) if not t and len(val)==0: t=(val[0].__class__,) # 1-tuple is list of the contained type #if not t: raise RuntimeError('Unable to guess type of '+str(self.ser)+'.'+attr) # hack for Se3, which is returned as (Vector3,Quaternion) in python elif isinstance(val,tuple) and len(val)==2 and val[0].__class__==Vector3 and val[1].__class__==Quaternion: t=Se3FakeType else: t=val.__class__ match=re.search(':yattrflags:`\s*([0-9]+)\s*`',doc) # non-empty attribute flags=int(match.group(1)) if match else 0 #logging.debug('Attr %s is of type %s'%(attr,((t[0].__name__,) if isinstance(t,tuple) else t.__name__))) self.entries.append(self.EntryData(name=attr,T=t)) #print("name: "+str(attr)+"\tflag: "+str(flags)) def getDocstring(self,attr=None): "If attr is *None*, return docstring of the Serializable itself" doc=(getattr(self.ser.__class__,attr).__doc__ if attr else self.ser.__class__.__doc__) if not doc: return '' doc=re.sub(':y(attrtype|default|attrflags):`[^`]*`','',doc) statAttr=re.compile('^.. ystaticattr::.*$',re.MULTILINE|re.DOTALL) doc=re.sub(statAttr,'',doc) # static classes have their proper docs at the beginning, discard static memeber docs # static: attribute of the type is the same object as attribute of the instance # in that case, get docstring from the class documentation by parsing it if attr and getattr(self.ser.__class__,attr)==getattr(self.ser,attr): doc=self.getStaticAttrDocstring(attr) doc=re.sub(':yref:`([^`]*)`','\\1',doc) import textwrap wrapper=textwrap.TextWrapper(replace_whitespace=False) return wrapper.fill(textwrap.dedent(doc)) def getStaticAttrDocstring(self,attr): ret=''; c=self.ser.__class__ while hasattr(c,attr) and hasattr(c.__base__,attr): c=c.__base__ start='.. ystaticattr:: %s.%s('%(c.__name__,attr) if start in c.__doc__: ll=c.__doc__.split('\n') for i in range(len(ll)): if ll[i].startswith(start): break for i in range(i+1,len(ll)): if len(ll[i])>0 and ll[i][0] not in ' \t': break ret+=ll[i] return ret else: return '[no documentation found]' def mkWidget(self,entry): if not entry.T: return None # single fundamental object Klass=_fundamentalEditorMap.get(entry.T,None) getter,setter=lambda: getattr(self.ser,entry.name), lambda x: setattr(self.ser,entry.name,x) if Klass: widget=Klass(self,getter=getter,setter=setter) widget.setFocusPolicy(Qt.StrongFocus) if (entry.flags & AttrFlags.readonly): widget.setEnabled(False) return widget # sequences if entry.T.__class__==tuple: assert(len(entry.T)==1) # we don't handle tuples of other lenghts # sequence of serializables T=entry.T[0] if (issubclass(T,Serializable) or T==Serializable): widget=SeqSerializable(self,getter,setter,T,path=(self.path+'.'+entry.name if self.path else None),shrink=True) return widget if (T in _fundamentalEditorMap): widget=SeqFundamentalEditor(self,getter,setter,T) return widget return None # a serializable if issubclass(entry.T,Serializable) or entry.T==Serializable: obj=getattr(self.ser,entry.name) if hasattr(obj,'label') and obj.label: path=obj.label elif self.path: path=self.path+'.'+entry.name else: path=None widget=SerializableEditor(getattr(self.ser,entry.name),parent=self,showType=self.showType,path=(self.path+'.'+entry.name if self.path else None)) widget.setFrameShape(QFrame.Box); widget.setFrameShadow(QFrame.Raised); widget.setLineWidth(1) return widget return None def mkWidgets(self): self.mkAttrEntries() grid=QFormLayout() grid.setContentsMargins(2,2,2,2) grid.setVerticalSpacing(0) grid.setLabelAlignment(Qt.AlignRight) if self.showType: lab=SerQLabel(self,makeSerializableLabel(self.ser,addr=True,href=True),tooltip=self.getDocstring(),path=self.path) lab.setFrameShape(QFrame.Box); lab.setFrameShadow(QFrame.Sunken); lab.setLineWidth(2); lab.setAlignment(Qt.AlignHCenter); lab.linkActivated.connect(yade.qt.openUrl) grid.setWidget(0,QFormLayout.SpanningRole,lab) for entry in self.entries: entry.widget=self.mkWidget(entry) objPath=(self.path+'.'+entry.name) if self.path else None label=SerQLabel(self,serializableHref(self.ser,entry.name),tooltip=self.getDocstring(entry.name),path=objPath) grid.addRow(label,entry.widget if entry.widget else QLabel('unhandled type')) self.setLayout(grid) self.refreshEvent() def refreshEvent(self): for e in self.entries: if e.widget and not e.widget.hot: e.widget.refresh() def refresh(self): pass def makeSerializableLabel(ser,href=False,addr=True,boldHref=True,num=-1,count=-1): ret=u'' if num>=0: if count>=0: ret+=u'%d/%d. '%(num,count) else: ret+=u'%d. '%num if href: ret+=(u' ' if boldHref else u' ')+serializableHref(ser)+(u' ' if boldHref else u' ') else: ret+=ser.__class__.__name__+' ' if hasattr(ser,'label') and ser.label: ret+=u' “'+unicode(ser.label)+u'”' # do not show address if there is a label already elif addr: import re ss=unicode(ser); m=re.match(u'<(.*) instance at (0x.*)>',ss) if m: ret+=m.group(2) else: logging.warning(u"Serializable converted to str ('%s') does not contain 'instance at 0x…'"%ss) return ret class SeqSerializableComboBox(QFrame): def __init__(self,parent,getter,setter,serType,path=None,shrink=False): QFrame.__init__(self,parent) self.getter,self.setter,self.serType,self.path,self.shrink=getter,setter,serType,path,shrink self.layout=QVBoxLayout(self) topLineFrame=QFrame(self) topLineLayout=QHBoxLayout(topLineFrame); for l in self.layout, topLineLayout: l.setSpacing(0); l.setContentsMargins(0,0,0,0) topLineFrame.setLayout(topLineLayout) buttons=(self.newButton,self.killButton,self.upButton,self.downButton)=[QPushButton(label,self) for label in (u'☘',u'☠',u'↑',u'↓')] buttonSlots=(self.newSlot,self.killSlot,self.upSlot,self.downSlot) # same order as buttons for b in buttons: b.setStyleSheet('QPushButton { font-size: 15pt; }'); b.setFixedWidth(30); b.setFixedHeight(30) self.combo=QComboBox(self) self.combo.setSizeAdjustPolicy(QComboBox.AdjustToContents) for w in buttons[0:2]+[self.combo,]+buttons[2:4]: topLineLayout.addWidget(w) self.layout.addWidget(topLineFrame) # nested layout self.scroll=QScrollArea(self); self.scroll.setWidgetResizable(True) self.layout.addWidget(self.scroll) self.seqEdit=None # currently edited serializable self.setLayout(self.layout) self.hot=None # API compat with SerializableEditor self.setFrameShape(QFrame.Box); self.setFrameShadow(QFrame.Raised); self.setLineWidth(1) # signals for b,slot in zip(buttons,buttonSlots): b.clicked.connect(slot) self.combo.currentIndexChanged.connect(self.comboIndexSlot) self.refreshEvent() # periodic refresh self.refreshTimer=QTimer(self) self.refreshTimer.timeout.connect(self.refreshEvent) self.refreshTimer.start(1000) # 1s should be enough #print 'SeqSerializable path is',self.path def comboIndexSlot(self,ix): # different seq item selected currSeq=self.getter(); if len(currSeq)==0: ix=-1 logging.debug('%s comboIndexSlot len=%d, ix=%d'%(self.serType.__name__,len(currSeq),ix)) self.downButton.setEnabled(ix0) self.combo.setEnabled(ix>=0) if ix>=0: ser=currSeq[ix] self.seqEdit=SerializableEditor(ser,parent=self,showType=seqSerializableShowType,path=(self.path+'['+str(ix)+']') if self.path else None) self.scroll.setWidget(self.seqEdit) if self.shrink: self.sizeHint=lambda: QSize(100,1000) self.scroll.sizeHint=lambda: QSize(100,1000) self.sizePolicy().setVerticalPolicy(QSizePolicy.Expanding) self.scroll.sizePolicy().setVerticalPolicy(QSizePolicy.Expanding) self.setMinimumHeight(min(300,self.seqEdit.height()+self.combo.height()+10)) self.setMaximumHeight(100000) self.scroll.setMaximumHeight(100000) else: self.scroll.setWidget(QFrame()) if self.shrink: self.setMaximumHeight(self.combo.height()+10); self.scroll.setMaximumHeight(0) def serLabel(self,ser,i=-1): return ('' if i<0 else str(i)+'. ')+str(ser)[1:-1].replace('instance at ','') def refreshEvent(self,forceIx=-1): currSeq=self.getter() comboEnabled=self.combo.isEnabled() if comboEnabled and len(currSeq)==0: self.comboIndexSlot(-1) # force refresh, otherwise would not happen from the initially empty state ix,cnt=self.combo.currentIndex(),self.combo.count() # serializable currently being edited (which can be absent) or the one of which index is forced ser=(self.seqEdit.ser if self.seqEdit else None) if forceIx<0 else currSeq[forceIx] if comboEnabled and len(currSeq)==cnt and (ix<0 or ser==currSeq[ix]): return if not comboEnabled and len(currSeq)==0: return logging.debug(self.serType.__name__+' rebuilding list from scratch') self.combo.clear() if len(currSeq)>0: prevIx=-1 for i,s in enumerate(currSeq): self.combo.addItem(makeSerializableLabel(s,num=i,count=len(currSeq),addr=False)) if s==ser: prevIx=i if forceIx>=0: newIx=forceIx # force the index (used from newSlot to make the new element active) elif prevIx>=0: newIx=prevIx # if found what was active before, use it elif ix>=0: newIx=ix # otherwise use the previous index (e.g. after deletion) else: newIx=0 # fallback to 0 logging.debug('%s setting index %d'%(self.serType.__name__,newIx)) self.combo.setCurrentIndex(newIx) else: logging.debug('%s EMPTY, setting index 0'%(self.serType.__name__)) self.combo.setCurrentIndex(-1) self.killButton.setEnabled(len(currSeq)>0) def newSlot(self): dialog=NewSerializableDialog(self,self.serType.__name__) if not dialog.exec_(): return # cancelled ser=dialog.result() ix=self.combo.currentIndex() currSeq=self.getter(); currSeq.insert(ix,ser); self.setter(currSeq) logging.debug('%s new item created at index %d'%(self.serType.__name__,ix)) self.refreshEvent(forceIx=ix) def killSlot(self): ix=self.combo.currentIndex() currSeq=self.getter(); del currSeq[ix]; self.setter(currSeq) self.refreshEvent() def upSlot(self): i=self.combo.currentIndex() assert(i>0) currSeq=self.getter(); prev,curr=currSeq[i-1:i+1]; currSeq[i-1],currSeq[i]=curr,prev; self.setter(currSeq) self.refreshEvent(forceIx=i-1) def downSlot(self): i=self.combo.currentIndex() currSeq=self.getter(); assert(i=0 else None menu=QMenu(self) actNew,actKill,actUp,actDown=[menu.addAction(name) for name in (u'☘ New',u'☠ Remove',u'↑ Up',u'↓ Down')] if index<0: [a.setEnabled(False) for a in actKill,actUp,actDown] if index==len(seq)-1: actDown.setEnabled(False) if index==0: actUp.setEnabled(False) if field: field.setStyleSheet('QWidget { background: green }') act=menu.exec_(self.mapToGlobal(event.pos())) if field: field.setStyleSheet('QWidget { background: none }') if not act: return if act==actNew: self.newSlot(index) elif act==actKill: self.killSlot(index) elif act==actUp: self.upSlot(index) elif act==actDown: self.downSlot(index) def localPositionToIndex(self,pos): gp=self.mapToGlobal(pos) for row in range(self.form.count()/2): w,i=self.form.itemAt(row,QFormLayout.FieldRole),self.form.itemAt(row,QFormLayout.LabelRole) for wi in w.widget(),i.widget(): x0,y0,x1,y1=wi.geometry().getCoords(); globG=QRect(self.mapToGlobal(QPoint(x0,y0)),self.mapToGlobal(QPoint(x1,y1))) if globG.contains(gp): return row return -1 def newSlot(self,i): seq=self.getter(); seq.insert(i,_fundamentalInitValues.get(self.itemType,self.itemType())) self.setter(seq) self.rebuild() def killSlot(self,i): seq=self.getter(); assert(iError',errMsg) return class ItemGetter(): def __init__(self,getter,index): self.getter,self.index=getter,index def __call__(self): return self.getter()[self.index] class ItemSetter(): def __init__(self,getter,setter,index): self.getter,self.setter,self.index=getter,setter,index def __call__(self,val): seq=self.getter(); seq[self.index]=val; self.setter(seq) for i,item in enumerate(currSeq): widget=Klass(self,ItemGetter(self.getter,i),ItemSetter(self.getter,self.setter,i)) #proxy,'value') self.form.insertRow(i,'%d. '%i,widget) logging.debug('added item %d %s'%(i,str(widget))) if len(currSeq)==0: self.form.insertRow(0,'empty',QLabel('(right-click for menu)')) logging.debug('rebuilt, will refresh now') self.refreshEvent(dontRebuild=True) # avoid infinite recursion it the length would change meanwhile def refreshEvent(self,dontRebuild=False,forceIx=-1): currSeq=self.getter() if len(currSeq)!=self.form.count()/2: #rowCount(): if dontRebuild: return # length changed behind our back, just pretend nothing happened and update next time instead self.rebuild() currSeq=self.getter() for i in range(len(currSeq)): item=self.form.itemAt(i,QFormLayout.FieldRole) logging.trace('got item #%d %s'%(i,str(item.widget()))) widget=item.widget() if not widget.hot: widget.refresh() if forceIx>=0 and forceIx==i: widget.setFocus() def refresh(self): pass # SerializableEditor API trunk-2018.02b/gui/qt5/XYZ.png000066400000000000000000000007011324306050200156610ustar00rootroot00000000000000PNG  IHDR((msRGBbKGD pHYs  tIME ($`iAIDATXؿ.DAߊxD JT$^-- :DOH$!H(p.{WK$79>(/b0X@)LG+X_P 0 J8-"3fS-DO5'Nҏ1&f}ѣKqń`f , y{Bkyj땎p2,} 4tɾ>4k-$ow⼁5kl`W7 ^X'~qVɣ= ʭ"ymj^462jO0u)%P .rbIENDB`trunk-2018.02b/gui/qt5/YZX.xpm000066400000000000000000000034571324306050200157140ustar00rootroot00000000000000/* XPM */ static char * YZX_xpm[] = { "40 40 2 1", " c None", ". c #000000", " ...... ", " .. ", " .. ", " .. ", " . ", " .. ", " .. ", " ...... ", " ", " . ", " . ", " ... ", " ... ", " ... ", " ..... ", " ..... ", " . ", " . ", " . ", " . ", " . ", " . ", " . ", " . ", " . ", " . ", " ........... ", " . . ", " . .. .. . .. .. ", " . . . . .. . . ", " . .... . ..... .... ", " . .. .................... .... ", " . .. . ..... .. ", " . .... . ... .. ", " . . . . .. ", " . .. .. . .. ", " . . ", " ........... ", " ", " "}; trunk-2018.02b/gui/qt5/ZXY.png000066400000000000000000000007111324306050200156620ustar00rootroot00000000000000PNG  IHDR((msRGBbKGD pHYs  tIME )EZIIDATXؽJCAJXhi kKS 쵉 "hM".rYvc1bѠ7a=MaEI<$R)OZ 6alf6&-\WߒPy $bW)C,)R(`j3`"&h|5Q^ky )>UP*u8ݬP=/ `^b]78` .; *r Sjk+6HǠ։mcotlbo96`7LGeǨL:uV@&~mԕI 'tAIENDB`trunk-2018.02b/gui/qt5/ZXY.xpm000066400000000000000000000034571324306050200157140ustar00rootroot00000000000000/* XPM */ static char * ZXY_xpm[] = { "40 40 2 1", " c None", ". c #000000", " .. .. ", " . . ", " .... ", " .. ", " .. ", " .... ", " . . ", " .. .. ", " ", " . ", " . ", " ... ", " ... ", " ... ", " ..... ", " ..... ", " . ", " . ", " . ", " . ", " . ", " . ", " . ", " . ", " . ", " . ", " ........... ", " . . ", " . .. .. . ...... ", " . . . . .. .. ", " . .... . ..... .. ", " . .... .................... .. ", " . .. . ..... . ", " . .. . ... .. ", " . .. . .. ", " . .. . ...... ", " . . ", " ........... ", " ", " "}; trunk-2018.02b/gui/qt5/_GLViewer.cpp000066400000000000000000000214451324306050200170200ustar00rootroot00000000000000#include"GLViewer.hpp" #include"OpenGLManager.hpp" #include #include #include #include namespace py=boost::python; qglviewer::Vec tuple2vec(py::tuple t){ qglviewer::Vec ret; for(int i=0;i<3;i++){py::extract e(t[i]); if(!e.check()) throw invalid_argument("Element #"+boost::lexical_cast(i)+" is not a number"); ret[i]=e();} return ret;}; py::tuple vec2tuple(qglviewer::Vec v){return py::make_tuple(v[0],v[1],v[2]);}; class pyGLViewer{ const size_t viewNo; public: #define GLV if((OpenGLManager::self->views.size()<=viewNo) || !(OpenGLManager::self->views[viewNo])) throw runtime_error("No view #"+boost::lexical_cast(viewNo)); GLViewer* glv=OpenGLManager::self->views[viewNo].get(); pyGLViewer(size_t _viewNo=0): viewNo(_viewNo){} void close(){ GLV; QCloseEvent* e(new QCloseEvent); QApplication::postEvent(glv,e); } py::tuple get_grid(){GLV; return py::make_tuple(bool(glv->drawGrid & 1),bool(glv->drawGrid & 2),bool(glv->drawGrid & 4));} void set_grid(py::tuple t){GLV; glv->drawGrid=0; for(int i=0;i<3;i++) if(py::extract(t[i])()) glv->drawGrid+=1<camera()->upVector,glv->camera()->setUpVector); VEC_GET_SET(lookAt,glv->camera()->position()+glv->camera()->viewDirection,glv->camera()->lookAt); VEC_GET_SET(viewDir,glv->camera()->viewDirection,glv->camera()->setViewDirection); VEC_GET_SET(eyePosition,glv->camera()->position,glv->camera()->setPosition); #define BOOL_GET_SET(property,getter,setter)void set_##property(bool b){GLV; setter(b);} bool get_##property(){GLV; return getter();} BOOL_GET_SET(axes,glv->axisIsDrawn,glv->setAxisIsDrawn); BOOL_GET_SET(fps,glv->FPSIsDisplayed,glv->setFPSIsDisplayed); bool get_scale(){GLV; return glv->drawScale;} void set_scale(bool b){GLV; glv->drawScale=b;} bool get_orthographic(){GLV; return glv->camera()->type()==qglviewer::Camera::ORTHOGRAPHIC;} void set_orthographic(bool b){GLV; return glv->camera()->setType(b ? qglviewer::Camera::ORTHOGRAPHIC : qglviewer::Camera::PERSPECTIVE);} int get_selection(void){ GLV; return glv->selectedName(); } void set_selection(int s){ GLV; glv->setSelectedName(s); } #define FLOAT_GET_SET(property,getter,setter)void set_##property(Real r){GLV; setter(r);} Real get_##property(){GLV; return getter();} FLOAT_GET_SET(sceneRadius,glv->sceneRadius,glv->setSceneRadius); void fitAABB(const Vector3r& min, const Vector3r& max){GLV; glv->camera()->fitBoundingBox(qglviewer::Vec(min[0],min[1],min[2]),qglviewer::Vec(max[0],max[1],max[2]));} void fitSphere(const Vector3r& center,Real radius){GLV; glv->camera()->fitSphere(qglviewer::Vec(center[0],center[1],center[2]),radius);} void showEntireScene(){GLV; glv->camera()->showEntireScene();} void center(bool median){GLV; if(median)glv->centerMedianQuartile(); else glv->centerScene();} Vector2i get_screenSize(){GLV; return Vector2i(glv->width(),glv->height());} void set_screenSize(Vector2i t){ /*GLV;*/ OpenGLManager::self->emitResizeView(viewNo,t[0],t[1]);} string pyStr(){return string("(viewNo)+">";} void saveDisplayParameters(size_t n){GLV; glv->saveDisplayParameters(n);} void useDisplayParameters(size_t n){GLV; glv->useDisplayParameters(n);} void loadState(string filename){GLV; QString origStateFileName=glv->stateFileName(); glv->setStateFileName(QString(filename.c_str())); glv->restoreStateFromFile(); glv->saveStateToFile(); glv->setStateFileName(origStateFileName);} void saveState(string filename){GLV; QString origStateFileName=glv->stateFileName(); glv->setStateFileName(QString(filename.c_str())); glv->saveStateToFile(); glv->setStateFileName(origStateFileName);} string get_timeDisp(){GLV; const int& m(glv->timeDispMask); string ret; if(m&GLViewer::TIME_REAL) ret+='r'; if(m&GLViewer::TIME_VIRT) ret+="v"; if(m&GLViewer::TIME_ITER) ret+="i"; return ret;} void set_timeDisp(string s){GLV; int& m(glv->timeDispMask); m=0; FOREACH(char c, s){switch(c){case 'r': m|=GLViewer::TIME_REAL; break; case 'v': m|=GLViewer::TIME_VIRT; break; case 'i': m|=GLViewer::TIME_ITER; break; default: throw invalid_argument(string("Invalid flag for timeDisp: `")+c+"'");}}} void set_bgColor(const Vector3r& c){ QColor cc(255*c[0],255*c[1],255*c[2]); GLV; glv->setBackgroundColor(cc);} Vector3r get_bgColor(){ GLV; QColor c(glv->backgroundColor()); return Vector3r(c.red()/255.,c.green()/255.,c.blue()/255.);} void saveSnapshot(string filename) {GLV; glv->nextFrameSnapshotFilename = filename;} #undef GLV #undef VEC_GET_SET #undef BOOL_GET_SET #undef FLOAT_GET_SET }; // ask to create a new view and wait till it exists pyGLViewer createView(){ int id=OpenGLManager::self->waitForNewView(); if(id<0) throw std::runtime_error("Unable to open new 3d view."); return pyGLViewer((*OpenGLManager::self->views.rbegin())->viewId); } py::list getAllViews(){ py::list ret; FOREACH(const shared_ptr& v, OpenGLManager::self->views){ if(v) ret.append(pyGLViewer(v->viewId)); } return ret; }; void centerViews(void){ OpenGLManager::self->centerAllViews(); } shared_ptr getRenderer(){ return OpenGLManager::self->renderer; } BOOST_PYTHON_MODULE(_GLViewer){ YADE_SET_DOCSTRING_OPTS; OpenGLManager* glm=new OpenGLManager(); // keep this singleton object forever glm->emitStartTimer(); py::def("View",createView,"Create a new 3d view."); py::def("center",centerViews,"Center all views."); py::def("views",getAllViews,"Return list of all open :yref:`yade.qt.GLViewer` objects"); py::def("Renderer",&getRenderer,"Return the active :yref:`OpenGLRenderer` object."); py::class_("GLViewer",py::no_init) .add_property("upVector",&pyGLViewer::get_upVector,&pyGLViewer::set_upVector,"Vector that will be shown oriented up on the screen.") .add_property("lookAt",&pyGLViewer::get_lookAt,&pyGLViewer::set_lookAt,"Point at which camera is directed.") .add_property("viewDir",&pyGLViewer::get_viewDir,&pyGLViewer::set_viewDir,"Camera orientation (as vector).") .add_property("eyePosition",&pyGLViewer::get_eyePosition,&pyGLViewer::set_eyePosition,"Camera position.") .add_property("grid",&pyGLViewer::get_grid,&pyGLViewer::set_grid,"Display square grid in zero planes, as 3-tuple of bools for yz, xz, xy planes.") .add_property("fps",&pyGLViewer::get_fps,&pyGLViewer::set_fps,"Show frames per second indicator.") .add_property("axes",&pyGLViewer::get_axes,&pyGLViewer::set_axes,"Show arrows for axes.") .add_property("scale",&pyGLViewer::get_scale,&pyGLViewer::set_scale,"Scale of the view (?)") .add_property("sceneRadius",&pyGLViewer::get_sceneRadius,&pyGLViewer::set_sceneRadius,"Visible scene radius.") .add_property("ortho",&pyGLViewer::get_orthographic,&pyGLViewer::set_orthographic,"Whether orthographic projection is used; if false, use perspective projection.") .add_property("screenSize",&pyGLViewer::get_screenSize,&pyGLViewer::set_screenSize,"Size of the viewer's window, in scree pixels") .add_property("timeDisp",&pyGLViewer::get_timeDisp,&pyGLViewer::set_timeDisp,"Time displayed on in the vindow; is a string composed of characters *r*, *v*, *i* standing respectively for real time, virtual time, iteration number.") // .add_property("bgColor",&pyGLViewer::get_bgColor,&pyGLViewer::set_bgColor) // useless: OpenGLRenderer::Background_color is used via openGL directly, bypassing QGLViewer background property .def("fitAABB",&pyGLViewer::fitAABB,(py::arg("mn"),py::arg("mx")),"Adjust scene bounds so that Axis-aligned bounding box given by its lower and upper corners *mn*, *mx* fits in.") .def("fitSphere",&pyGLViewer::fitSphere,(py::arg("center"),py::arg("radius")),"Adjust scene bounds so that sphere given by *center* and *radius* fits in.") .def("showEntireScene",&pyGLViewer::showEntireScene) .def("center",&pyGLViewer::center,(py::arg("median")=true),"Center view. View is centered either so that all bodies fit inside (*median* = False), or so that 75\% of bodies fit inside (*median* = True).") .def("saveState",&pyGLViewer::saveState,(py::arg("stateFilename")=".qglviewer.xml"),"Save display parameters into a file. Saves state for both :yref:`GLViewer` and associated :yref:`OpenGLRenderer`.") .def("loadState",&pyGLViewer::loadState,(py::arg("stateFilename")=".qglviewer.xml"),"Load display parameters from file saved previously into.") .def("__repr__",&pyGLViewer::pyStr).def("__str__",&pyGLViewer::pyStr) .def("close",&pyGLViewer::close) .def("saveSnapshot",&pyGLViewer::saveSnapshot,(py::arg("filename")),"Save the current view to image file") .add_property("selection",&pyGLViewer::get_selection,&pyGLViewer::set_selection) ; } trunk-2018.02b/gui/qt5/__init__.py000066400000000000000000000254721324306050200166060ustar00rootroot00000000000000# encoding: utf-8 import yade.runtime if not yade.runtime.hasDisplay: msg = "Connecting to DISPLAY at Yade startup failed, unable to activate the qt5 interface." import os if 'YADE_BATCH' in os.environ: msg += "\nDo not import qt when running in batch mode." raise ImportError(msg) from PyQt5.QtGui import * from PyQt5 import QtCore from PyQt5 import QtGui from PyQt5.QtCore import * from PyQt5.QtWidgets import * from PyQt5 import QtWebKit, QtWebKitWidgets from yade.qt.ui_controller import Ui_Controller from yade.qt.Inspector import * from yade.qt._GLViewer import * from yade import * import yade.system, yade.config maxWebWindows=1 "Number of webkit windows that will be cycled to show help on clickable objects" webWindows=[] "holds instances of QtWebKit windows; clicking an url will open it in the window that was the least recently updated" sphinxOnlineDocPath='https://www.yade-dem.org/doc/' "Base URL for the documentation. Packaged versions should change to the local installation directory." import os.path # find if we have docs installed locally from package sphinxLocalDocPath=yade.config.prefix+'/share/doc/yade'+yade.config.suffix+'-doc/html/' sphinxBuildDocPath=yade.config.sourceRoot+'/doc/sphinx/_build/html/' # we prefer the packaged documentation for this version, if installed if os.path.exists(sphinxLocalDocPath+'/index.html'): sphinxPrefix='file://'+sphinxLocalDocPath # otherwise look for documentation generated in the source tree elif os.path.exists(sphinxBuildDocPath+'/index.html'): sphinxPrefix='file://'+sphinxBuildDocPath # fallback to online docs else: sphinxPrefix=sphinxOnlineDocPath sphinxDocWrapperPage=sphinxPrefix+'/yade.wrapper.html' def sslErrorHandler(reply, errorList): reply.ignoreSslErrors() def openUrl(url): global maxWebWindows,webWindows reuseLast=False # use the last window if the class is the same and only the attribute differs try: reuseLast=(len(webWindows)>0 and str(webWindows[-1].url()).split('#')[-1].split('.')[2]==url.split('#')[-1].split('.')[2]) #print str(webWindows[-1].url()).split('#')[-1].split('.')[2],url.split('#')[-1].split('.')[2] except: pass if not reuseLast: if len(webWindows)0: self.displayCombo.insertSeparator(10000); afterSep=0 for c in yade.system.childClasses(bc) | set([bc]): inst=eval(c+'()'); if len(set(inst.dict().keys())-set(['label']))>0: self.displayCombo.addItem(c); afterSep+=1 def inspectSlot(self): self.inspector=SimulationInspector(parent=None) self.inspector.show() def setTabActive(self,what): if what=='simulation': ix=0 elif what=='display': ix=1 elif what=='generator': ix=2 elif what=='python': ix=3 else: raise ValueErorr("No such tab: "+what) self.controllerTabs.setCurrentIndex(ix) def generatorComboSlot(self,genStr): "update generator parameters when a new one is selected" gen=eval(str(genStr)+'()') self.generator=gen se=SerializableEditor(gen,parent=self.generatorArea,showType=True) self.generatorArea.setWidget(se) def pythonComboSlot(self,cmd): try: code=compile(str(cmd),'','exec') exec code in globals() except: import traceback traceback.print_exc() def generateSlot(self): filename=str(self.generatorFilenameEdit.text()) if self.generatorMemoryCheck.isChecked(): filename=':memory:'+filename print 'BUG: Saving to memory slots freezes Yade (cause unknown). Cross fingers.' #print 'Will save to ',filename self.generator.generate(filename) if self.generatorAutoCheck: O.load(filename) self.setTabActive('simulation') if len(views())==0: v=View(); v.center() def displayComboSlot(self,dispStr): ser=(self.renderer if dispStr=='OpenGLRenderer' else eval(str(dispStr)+'()')) path='yade.qt.Renderer()' if dispStr=='OpenGLRenderer' else dispStr se=SerializableEditor(ser,parent=self.displayArea,ignoredAttrs=set(['label']),showType=True,path=path) self.displayArea.setWidget(se) def loadSlot(self): f=QFileDialog.getOpenFileName(self,'Load simulation','','Yade simulations (*.xml *.xml.bz2 *.xml.gz *.yade *.yade.gz *.yade.bz2);; *.*') f=str(f[0]) if not f: return # cancelled self.deactivateControls() O.load(f) def saveSlot(self): f=QFileDialog.getSaveFileName(self,'Save simulation','','Yade simulations (*.xml *.xml.bz2 *.xml.gz *.yade *.yade.gz *.yade.bz2);; *.*') f=str(f[0]) splf = f.split('.') if (len(splf) == 1): print('No extension is found in the file name! Added .xml.bz2') f = f+'.xml.bz2' if not f: return # cancelled O.save(f) def reloadSlot(self): self.deactivateControls() from yade import plot plot.splitData() O.reload() def dtFixedSlot(self): O.dt=O.dt O.dynDt=False def dtDynSlot(self): O.dt=-O.dt def dtEditNoupdateSlot(self): self.dtEditUpdate=False def dtEditedSlot(self): try: t=float(self.dtEdit.text()) O.dt=t except ValueError: pass self.dtEdit.setText(str(O.dt)) self.dtEditUpdate=True def playSlot(self): O.run() def pauseSlot(self): O.pause() def stepSlot(self): O.step() def subStepSlot(self,value): O.subStepping=bool(value) def show3dSlot(self, show): vv=views() assert(len(vv) in (0,1)) if show: if len(vv)==0: View() else: if len(vv)>0: vv[0].close() def setReferenceSlot(self): # sets reference periodic cell as well utils.setRefSe3() def centerSlot(self): for v in views(): v.center() def setViewAxes(self,dir,up): try: v=views()[0] v.viewDir=dir v.upVector=up v.center() except IndexError: pass def xyzSlot(self): self.setViewAxes((0,0,-1),(0,1,0)) def yzxSlot(self): self.setViewAxes((-1,0,0),(0,0,1)) def zxySlot(self): self.setViewAxes((0,-1,0),(1,0,0)) def refreshEvent(self): self.refreshValues() self.activateControls() def deactivateControls(self): self.realTimeLabel.setText('') self.virtTimeLabel.setText('') self.iterLabel.setText('') self.fileLabel.setText('[loading]') self.playButton.setEnabled(False) self.pauseButton.setEnabled(False) self.stepButton.setEnabled(False) self.subStepCheckbox.setEnabled(False) self.reloadButton.setEnabled(False) self.dtFixedRadio.setEnabled(False) self.dtDynRadio.setEnabled(False) self.dtEdit.setEnabled(False) self.dtEdit.setText('') self.dtEditUpdate=True def activateControls(self): hasSim=len(O.engines)>0 running=O.running if hasSim: self.playButton.setEnabled(not running) self.pauseButton.setEnabled(running) self.reloadButton.setEnabled(O.filename is not None) self.stepButton.setEnabled(not running) self.subStepCheckbox.setEnabled(not running) else: self.playButton.setEnabled(False) self.pauseButton.setEnabled(False) self.reloadButton.setEnabled(False) self.stepButton.setEnabled(False) self.subStepCheckbox.setEnabled(False) self.dtFixedRadio.setEnabled(True) self.dtDynRadio.setEnabled(O.dynDtAvailable) dynDt=O.dynDt self.dtFixedRadio.setChecked(not dynDt) self.dtDynRadio.setChecked(dynDt) if dynDt or self.dtEditUpdate: self.dtEdit.setText(str(O.dt)) if dynDt: self.dtEditUpdate=True self.dtEdit.setEnabled(not dynDt) fn=O.filename self.fileLabel.setText(fn if fn else '[no file]') def refreshValues(self): rt=int(O.realtime); t=O.time; iter=O.iter; assert(len(self.iterTimes)==len(self.iterValues)) if len(self.iterTimes)==0: self.iterTimes.append(rt); self.iterValues.append(iter); self.iterPerSec=0 # update always for the first time elif rt-self.iterTimes[-1]>self.iterPerSecTimeout: # update after a timeout if len(self.iterTimes)==1: self.iterTimes.append(self.iterTimes[0]); self.iterValues.append(self.iterValues[0]) # 2 values, first one is bogus self.iterTimes[0]=self.iterTimes[1]; self.iterValues[0]=self.iterValues[1] self.iterTimes[1]=rt; self.iterValues[1]=iter; self.iterPerSec=(self.iterValues[-1]-self.iterValues[-2])/(self.iterTimes[-1]-self.iterTimes[-2]) if not O.running: self.iterPerSec=0 stopAtIter=O.stopAtIter subStepInfo='' if O.subStepping: subStep=O.subStep if subStep==-1: subStepInfo=u'→ prologue' elif subStep>=0 and subStepepilogue' else: raise RuntimeError("Invalid O.subStep value %d, should be ∈{-1,…,len(o.engines)}"%subStep) subStepInfo="
    sub %d/%d [%s]"%(subStep,len(O.engines),subStepInfo) self.subStepCheckbox.setChecked(O.subStepping) # might have been changed async if stopAtIter<=iter: self.realTimeLabel.setText('%02d:%02d:%02d'%(rt//3600,(rt%3600)//60,rt%60)) self.iterLabel.setText('#%ld, %.1f/s %s'%(iter,self.iterPerSec,subStepInfo)) else: e=int((stopAtIter-iter)*self.iterPerSec) self.realTimeLabel.setText('%02d:%02d:%02d (ETA %02d:%02d:%02d)'%(rt//3600,rt//60,rt%60,e//3600,e//60,e%60)) self.iterLabel.setText('#%ld / %ld, %.1f/s %s'%(O.iter,stopAtIter,self.iterPerSec,subStepInfo)) if t!=float('inf'): s=int(t); ms=int(t*1000)%1000; us=int(t*1000000)%1000; ns=int(t*1000000000)%1000 self.virtTimeLabel.setText(u'%03ds%03dm%03dμ%03dn'%(s,ms,us,ns)) else: self.virtTimeLabel.setText(u'[ ∞ ] ?!') self.show3dButton.setChecked(len(views())>0) def Generator(): global controller if not controller: controller=ControllerClass(); controller.show(); controller.raise_() controller.setTabActive('generator') def Controller(): global controller if not controller: controller=ControllerClass(); controller.show(); controller.raise_() controller.setTabActive('simulation') def Inspector(): global controller if not controller: controller=ControllerClass(); controller.inspectSlot() trunk-2018.02b/gui/qt5/controller.ui000066400000000000000000001000301324306050200171770ustar00rootroot00000000000000 Controller 0 0 290 495 0 0 Yade :/img/yade-favicon.xpm:/img/yade-favicon.xpm 0 0 0 0 0 Simulation 0 0 6 QLayout::SetMinAndMaxSize true 0 0 0 0 Load true 0 0 0 0 Save Inspect QLayout::SetMinimumSize QFormLayout::AllNonFixedFieldsGrow 6 6 6 real 00:00:00 virt 00:000.000m000μ000n iter #0, 0.0/s true Δt fixed true false time stepper false Qt::ClickFocus Qt::Horizontal 40 20 [no file] Qt::Horizontal 40 20 6 6 0 false 5 0 18 32 32 true false false 4 0 18 ▮▮ 32 32 0 0 0 false 2 0 12 ▶▮ 32 32 7 sub-step false 5 0 22 32 32 Show 3D true Reference Center 48 48 :/img/XYZ.xpm:/img/XYZ.xpm 40 40 48 48 :/img/YZX.xpm:/img/YZX.xpm 40 40 48 48 :/img/ZXY.xpm:/img/ZXY.xpm 40 40 Display 0 0 true 0 0 284 433 Generate 0 0 200 200 0 0 true 0 0 500 500 0 0 500 500 398 336 false memory slot false false /tmp/scene.yade.gz open automatically true Generate Python Monospace IBeamCursor Qt::StrongFocus false true QComboBox::InsertAtTop 1 false <i>(Output appears in the terminal)</i> Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter Qt::Vertical 20 40 loadButton clicked() Controller loadSlot() 93 64 284 0 saveButton clicked() Controller saveSlot() 184 64 284 0 dtFixedRadio clicked() Controller dtFixedSlot() 143 170 284 0 dtDynRadio clicked() Controller dtDynSlot() 268 176 284 4 dtEdit editingFinished() Controller dtEditedSlot() 147 204 284 43 playButton clicked() Controller playSlot() 116 304 3 309 pauseButton clicked() Controller pauseSlot() 268 304 284 311 referenceButton clicked() Controller setReferenceSlot() 184 429 5 469 centerButton clicked() Controller centerSlot() 275 429 284 469 xyzButton clicked() Controller xyzSlot() 93 460 0 469 yzxButton clicked() Controller yzxSlot() 184 485 243 469 zxyButton clicked() Controller zxySlot() 275 485 284 469 generateButton clicked() Controller generateSlot() 101 60 284 469 dtEdit textEdited(QString) Controller dtEditNoupdateSlot() 147 204 284 205 dtEdit cursorPositionChanged(int,int) Controller dtEditNoupdateSlot() 147 204 284 181 generatorCombo currentIndexChanged(QString) Controller generatorComboSlot() 101 60 284 130 displayCombo currentIndexChanged(QString) Controller displayComboSlot() 101 45 284 108 pythonCombo activated(QString) Controller pythonComboSlot() 107 66 284 68 inspectButton clicked() Controller inspectSlot() 247 54 0 58 reloadButton clicked() Controller reloadSlot() 194 363 284 437 stepButton clicked() Controller stepSlot() 79 363 6 450 subStepCheckbox stateChanged(int) Controller subStepSlot() 42 376 -2 381 show3dButton toggled(bool) Controller show3dSlot(bool) 42 418 -2 422 loadSlot() saveSlot() reloadSlot() playSlot() pauseSlot() stepSlot() setReferenceSlot() centerSlot() dtFixedSlot() dtDynSlot() dtEditedSlot() xyzSlot() yzxSlot() zxySlot() generateSlot() dtEditNoupdateSlot() generatorComboSlot() displayComboSlot() pythonComboSlot() pythonEditSlot() inspectSlot() subStepSlot() show3dSlot(bool) trunk-2018.02b/gui/qt5/img.qrc000066400000000000000000000002441324306050200157460ustar00rootroot00000000000000 yade-favicon.xpm XYZ.xpm YZX.xpm ZXY.xpm trunk-2018.02b/gui/qt5/yade-favicon.png000066400000000000000000000022211324306050200175330ustar00rootroot00000000000000PNG  IHDRasRGBbKGD pHYs  tIME 65tEXtCommentCreated with GIMPWIDAT8{lu]w}\6Qa6c-É6DMϑh_&8KY1&g[u׻ |zY*#Oq羪hdOr,0ͬY8[/HG+i+wG~N7:f_~5W+LZ.67vu~yIQ=ˣqx* cD2o1y4(>7!D߻ |aj= ]^BQs/yv8ql1zqy͜68xDwـ1- 4vt؁>tb>l"q4Qyk?񸥴w"Bv]j9pgMQ++.꒎9n)CEyGv{ёy+BEHrxnְVw5uP+s0_Cs!g'O-G~.g3V FM Clbu#ċS7o",`d0h1N,2B7 wochج f61hS>qd ȵk]]!M'H̬NOwԔh4 @n,Iy8* yӹ/9um,Ox6ze-E"utA Hy9 YU.j1‘Oip㭂%b*10@N -4dD`3@TIމH2%tŎ4Q0R_DtCp},y L f=U\:gsl 8ڋVp)CM;;;G|6 PV'L@WTlSS&cfY6F4P [H8ǞN [^.E"" ʄ Tne [Z A3$Tc˻\c[?\~ZՐ[guyʀi9Iڼщk|IENDB`trunk-2018.02b/gui/qt5/yade-favicon.xpm000066400000000000000000000076341324306050200175700ustar00rootroot00000000000000/* XPM */ static char * yade_favicon_xpm[] = { "16 16 210 2", " c None", ". c #F47372", "+ c #F1B4B5", "@ c #909092", "# c #EA5A5A", "$ c #8E4D3C", "% c #746C35", "& c #616233", "* c #4B4B0E", "= c #939191", "- c #8A8A8A", "; c #808380", "> c #6F6E6D", ", c #4F4D2B", "' c #646427", ") c #383827", "! c #EB9D9E", "~ c #7F6B5D", "{ c #828500", "] c #A2A200", "^ c #838500", "/ c #404031", "( c #AEAEAE", "_ c #8C8C8C", ": c #ABAAAF", "< c #444319", "[ c #9E9C01", "} c #B4B106", "| c #908E0C", "1 c #4C4C4F", "2 c #D0BBBB", "3 c #B07E83", "4 c #706137", "5 c #B9B704", "6 c #C8C901", "7 c #686800", "8 c #4C4C51", "9 c #A3A1A3", "0 c #ABA9AC", "a c #45444A", "b c #8F8E00", "c c #A3A005", "d c #AAA809", "e c #25241B", "f c #D79090", "g c #E25E5E", "h c #FCBEC9", "i c #E6AF6F", "j c #D6B700", "k c #ACAA00", "l c #3F4000", "m c #53525D", "n c #666472", "o c #6A6A02", "p c #B7B509", "q c #A3A407", "r c #3F4008", "s c #E07B7A", "t c #E35151", "u c #DC6565", "v c #C76B67", "w c #D79E42", "x c #ABA10D", "y c #99A400", "z c #393C21", "A c #40401D", "B c #9A9A01", "C c #B6B406", "D c #737100", "E c #545157", "F c #DCA8A8", "G c #DAA5A7", "H c #D86261", "I c #D85D5D", "J c #DEC7D1", "K c #E1BE91", "L c #B5C100", "M c #848D04", "N c #818103", "O c #8C8C08", "P c #7A7A02", "Q c #504F33", "R c #827F8D", "S c #C85757", "T c #CC6666", "U c #D65757", "V c #DB5353", "W c #E59596", "X c #E58A82", "Y c #BFB500", "Z c #B4BD05", "` c #BBBA0B", " . c #A4A306", ".. c #5E5D13", "+. c #565463", "@. c #B47070", "#. c #C69392", "$. c #D57E7E", "%. c #DA2929", "&. c #ED393A", "*. c #8B3537", "=. c #949700", "-. c #9AA706", ";. c #ABB10A", ">. c #747502", ",. c #2C2B35", "'. c #B2AFBB", "). c #B86363", "!. c #C8BEBE", "~. c #DBCFCF", "{. c #DF7373", "]. c #F16466", "^. c #E79DA6", "/. c #5A5408", "(. c #EEAC06", "_. c #EBBA08", ":. c #312E0B", "<. c #7D7B8A", "[. c #8C899A", "}. c #858394", "|. c #777588", "1. c #B75E5E", "2. c #C38181", "3. c #D58383", "4. c #AB5B5A", "5. c #DF484A", "6. c #F28088", "7. c #5F4507", "8. c #FD8D08", "9. c #FFBD0C", "0. c #3D330C", "a. c #B8B5C6", "b. c #858297", "c. c #BD6464", "d. c #C24748", "e. c #CD5E5F", "f. c #D2ADAE", "g. c #DA8E91", "h. c #E73643", "i. c #5B3206", "j. c #EF8308", "k. c #EF7510", "l. c #3C2A0F", "m. c #ADAEC2", "n. c #8F8DA3", "o. c #8A88A1", "p. c #9490AB", "q. c #69687B", "r. c #C30C0C", "s. c #BD3C3F", "t. c #C6777A", "u. c #D6B7BA", "v. c #D9A2A7", "w. c #D9848C", "x. c #DBC5C3", "y. c #E49A37", "z. c #F59212", "A. c #482E17", "B. c #D5ABBA", "C. c #817A95", "D. c #A491A9", "E. c #8986A2", "F. c #8E89A7", "G. c #7B7795", "H. c #C55252", "I. c #C37072", "J. c #BE4951", "K. c #CA6A72", "L. c #CF4E5A", "M. c #D49098", "N. c #DDD2D6", "O. c #E6A489", "P. c #EB7313", "Q. c #452716", "R. c #FEC1CD", "S. c #E76B80", "T. c #F7BBC5", "U. c #CBACBA", "V. c #9292AD", "W. c #837FA0", "X. c #C62F2F", "Y. c #C73E3F", "Z. c #C53F4A", "`. c #B93344", " + c #CD3C4D", ".+ c #D75767", "++ c #E17484", "@+ c #C46559", "#+ c #DA743E", "$+ c #53241E", "%+ c #ED546E", "&+ c #E66E82", "*+ c #F3BCC6", "=+ c #E19FAF", "-+ c #B81415", ";+ c #BA4D4F", ">+ c #AD7782", ",+ c #AC4B5D", "'+ c #C09FA9", ")+ c #BB3A52", "!+ c #C14F67", "~+ c #CD5858", "{+ c #E1AEAC", "]+ c #D9D0D0", "^+ c #D25169", "/+ c #DC7C8F", "(+ c #F0C9D1", "_+ c #E6A3B2", ":+ c #A27791", "<+ c #AA98B1", ". + @ ", "# $ % & * = - ; > , ' ) ", "! ~ { ] ^ / ( _ : < [ } | 1 ", "2 3 4 5 6 7 8 9 0 a b c d e ", "f g h i j k l m n o p q r ", "s t u v w x y z A B C D E ", "F G H I J K L M N O P Q R ", "S T U V W X Y Z ` ...+. ", "@.#.$.%.&.*.=.-.;.>.,. '. ", ").!.~.{.].^./.(._.:.<.[.}.|. ", "1.2.3.4.5.6.7.8.9.0.a. b. ", "c.d.e.f.g.h.i.j.k.l.m.n.o. p.q.", "r.s.t.u.v.w.x.y.z.A.B.C.D.E.F.G.", "H.I.J.K.L.M.N.O.P.Q.R.S.T.U.V.W.", "X.Y.Z.`. +.+++@+#+$+%+&+*+=+ ", "-+;+>+,+'+)+!+~+{+]+^+/+(+_+:+<+"}; trunk-2018.02b/lib/000077500000000000000000000000001324306050200137345ustar00rootroot00000000000000trunk-2018.02b/lib/base/000077500000000000000000000000001324306050200146465ustar00rootroot00000000000000trunk-2018.02b/lib/base/Logging.hpp000066400000000000000000000033511324306050200167470ustar00rootroot00000000000000// 2006-2008 © Václav Šmilauer #pragma once /* * This file defines various useful logging-related macros - userspace stuff is * - LOG_* for actual logging, * - DECLARE_LOGGER; that should be used in class header to create separate logger for that class, * - CREATE_LOGGER(className); that must be used in class implementation file to create the static variable. * * Note that the latter 2 may change their name to something like LOG_DECLARE and LOG_CREATE, to be consistent. * Some other macros will be very likely added, to allow for easy variable tracing etc. Suggestions welcome. * * * Yade has the logging config file by default in ~/.yade-$VERSION/logging.conf. * */ #include # define _POOR_MANS_LOG(level,msg) {std::cerr< template<> const Real Math::EPSILON = DBL_EPSILON; template<> const Real Math::ZERO_TOLERANCE = 1e-20; template<> const Real Math::MAX_REAL = DBL_MAX; template<> const Real Math::PI = 4.0*atan(1.0); template<> const Real Math::TWO_PI = 2.0*Math::PI; template<> const Real Math::HALF_PI = 0.5*Math::PI; template<> const Real Math::DEG_TO_RAD = Math::PI/180.0; template<> const Real Math::RAD_TO_DEG = 180.0/Math::PI; template<> int ZeroInitializer(){ return (int)0; } template<> Real ZeroInitializer(){ return (Real)0; } #ifdef YADE_MASK_ARBITRARY bool operator==(const mask_t& g, int i) { return g == mask_t(i); } bool operator==(int i, const mask_t& g) { return g == i; } bool operator!=(const mask_t& g, int i) { return !(g == i); } bool operator!=(int i, const mask_t& g) { return g != i; } mask_t operator&(const mask_t& g, int i) { return g & mask_t(i); } mask_t operator&(int i, const mask_t& g) { return g & i; } mask_t operator|(const mask_t& g, int i) { return g | mask_t(i); } mask_t operator|(int i, const mask_t& g) { return g | i; } bool operator||(const mask_t& g, bool b) { return (g != 0) || b; } bool operator||(bool b, const mask_t& g) { return g || b; } bool operator&&(const mask_t& g, bool b) { return (g != 0) && b; } bool operator&&(bool b, const mask_t& g) { return g && b; } #endif trunk-2018.02b/lib/base/Math.hpp000066400000000000000000000334411324306050200162550ustar00rootroot00000000000000// © 2010 Václav Šmilauer #pragma once #ifdef QUAD_PRECISION using quad = long double; using Real = quad; #else using Real = double; #endif #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include using std::endl; using std::cout; using std::cerr; using std::vector; using std::string; using std::list; using std::pair; using std::min; using std::max; using std::set; using std::map; using std::type_info; using std::ifstream; using std::ofstream; using std::runtime_error; using std::logic_error; using std::invalid_argument; using std::ios; using std::ios_base; using std::fstream; using std::ostream; using std::ostringstream; using std::istringstream; using std::swap; using std::make_pair; #include #include #include #include #include #include #include #include #include #include #include using boost::shared_ptr; #ifndef FOREACH #define FOREACH BOOST_FOREACH #endif #ifndef YADE_PTR_CAST #define YADE_PTR_CAST boost::static_pointer_cast #endif #ifndef YADE_CAST #define YADE_CAST static_cast #endif #ifndef YADE_PTR_DYN_CAST #define YADE_PTR_DYN_CAST boost::dynamic_pointer_cast #endif #define EIGEN_DONT_PARALLELIZE #ifdef YADE_MASK_ARBITRARY #include #endif #include #include #include #include template using Vector2 = Eigen::Matrix; using Vector2i = Vector2; using Vector2r = Vector2; template using Vector3 = Eigen::Matrix; using Vector3i = Vector3; using Vector3r = Vector3; template using Vector6 = Eigen::Matrix; using Vector6i = Vector6; using Vector6r = Vector6; template using Matrix3 = Eigen::Matrix; using Matrix3i = Matrix3; using Matrix3r = Matrix3; template using Matrix6 = Eigen::Matrix; using Matrix6i = Matrix6; using Matrix6r = Matrix6; using Quaternionr = Eigen::Quaternion; using AngleAxisr = Eigen::AngleAxis; using AlignedBox2r = Eigen::AlignedBox; using AlignedBox3r = Eigen::AlignedBox; using Eigen::AngleAxis; using Eigen::Quaternion; // in some cases, we want to initialize types that have no default constructor (OpenMPAccumulator, for instance) // template specialization will help us here template EigenMatrix ZeroInitializer(){ return EigenMatrix::Zero(); }; template<> int ZeroInitializer(); template<> Real ZeroInitializer(); // io template std::ostream & operator<<(std::ostream &os, const Vector2& v) { os << v.x()<<" "< std::ostream & operator<<(std::ostream &os, const Vector3& v) { os << v.x()<<" "< std::ostream & operator<<(std::ostream &os, const Vector6& v) { os << v[0]<<" "< std::ostream & operator<<(std::ostream &os, const Eigen::Quaternion& q) { os< struct Math{ static const Scalar PI; static const Scalar HALF_PI; static const Scalar TWO_PI; static const Scalar MAX_REAL; static const Scalar DEG_TO_RAD; static const Scalar RAD_TO_DEG; static const Scalar EPSILON; static const Scalar ZERO_TOLERANCE; static Scalar Sign(Scalar f){ if(f<0) return -1; if(f>0) return 1; return 0; } static Scalar UnitRandom(){ return ((double)rand()/((double)(RAND_MAX))); } static Scalar SymmetricRandom(){ return 2.*(((double)rand())/((double)(RAND_MAX)))-1.; } static Scalar FastInvCos0(Scalar fValue) { Scalar fRoot = sqrt(((Scalar)1.0)-fValue); Scalar fResult = -(Scalar)0.0187293; fResult *= fValue; fResult += (Scalar)0.0742610; fResult *= fValue; fResult -= (Scalar)0.2121144; fResult *= fValue; fResult += (Scalar)1.5707288; fResult *= fRoot; return fResult; } }; using Mathr = Math; /* this was removed in eigen3, see http://forum.kde.org/viewtopic.php?f=74&t=90914 */ template void Matrix_computeUnitaryPositive(const MatrixT& in, MatrixT* unitary, MatrixT* positive){ assert(unitary); assert(positive); //Eigen::JacobiSVD svd(in, Eigen::ComputeThinU | Eigen::ComputeThinV); Eigen::JacobiSVD svd(in, Eigen::ComputeFullU | Eigen::ComputeFullV); MatrixT mU, mV, mS; mU = svd.matrixU(); mV = svd.matrixV(); mS = svd.singularValues().asDiagonal(); *unitary=mU * mV.adjoint(); *positive=mV * mS * mV.adjoint(); } template void matrixEigenDecomposition(const MatrixT& m, MatrixT& mRot, MatrixT& mDiag){ //assert(mRot); assert(mDiag); Eigen::SelfAdjointEigenSolver a(m); mRot=a.eigenvectors(); mDiag=a.eigenvalues().asDiagonal(); } /* convert Vector6r in the Voigt notation to corresponding 2nd order symmetric tensor (stored as Matrix3r) if strain is true, then multiply non-diagonal parts by .5 */ template Matrix3 voigt_toSymmTensor(const Vector6& v, bool strain=false){ Real k=(strain?.5:1.); Matrix3 ret; ret< Vector6 tensor_toVoigt(const Matrix3& m, bool strain=false){ int k=(strain?2:1); Vector6 ret; ret<::signaling_NaN()); // void quaternionToEulerAngles (const Quaternionr& q, Vector3r& eulerAngles,Real threshold=1e-6f); template void quaterniontoGLMatrix(const Quaternion& q, Scalar m[16]){ Scalar w2=2.*q.w(), x2=2.*q.x(), y2=2.*q.y(), z2=2.*q.z(); Scalar x2w=w2*q.w(), y2w=y2*q.w(), z2w=z2*q.w(); Scalar x2x=x2*q.x(), y2x=y2*q.x(), z2x=z2*q.x(); Scalar x2y=y2*q.y(), y2y=y2*q.y(), z2y=z2*q.y(); Scalar x2z=z2*q.z(), y2z=y2*q.z(), z2z=z2*q.z(); m[0]=1.-(y2y+z2z); m[4]=y2x-z2w; m[8]=z2x+y2w; m[12]=0; m[1]=y2x+z2w; m[5]=1.-(x2x+z2z); m[9]=z2y-x2w; m[13]=0; m[2]=z2x-y2w; m[6]=z2y+x2w; m[10]=1.-(x2x+y2y); m[14]=0; m[3]=0.; m[7]=0.; m[11]=0.; m[15]=1.; } // se3 template class Se3 { public : Vector3 position; Quaternion orientation; Se3(){}; Se3(Vector3 rkP, Quaternion qR){ position = rkP; orientation = qR; } Se3(const Se3& s){position = s.position;orientation = s.orientation;} Se3(Se3& a,Se3& b){ position = b.orientation.inverse()*(a.position - b.position); orientation = b.orientation.inverse()*a.orientation; } Se3 inverse(){ return Se3(-(orientation.inverse()*position), orientation.inverse());} void toGLMatrix(float m[16]){ orientation.toGLMatrix(m); m[12] = position[0]; m[13] = position[1]; m[14] = position[2];} Vector3 operator * (const Vector3& b ){return orientation*b+position;} Se3 operator * (const Quaternion& b ){return Se3(position , orientation*b);} Se3 operator * (const Se3& b ){return Se3(orientation*b.position+position,orientation*b.orientation);} }; // functions template Scalar unitVectorsAngle(const Vector3& a, const Vector3& b){ return acos(a.dot(b)); } // operators /* * Mask */ #ifdef YADE_MASK_ARBITRARY using mask_t = std::bitset; bool operator==(const mask_t& g, int i); bool operator==(int i, const mask_t& g); bool operator!=(const mask_t& g, int i); bool operator!=(int i, const mask_t& g); mask_t operator&(const mask_t& g, int i); mask_t operator&(int i, const mask_t& g); mask_t operator|(const mask_t& g, int i); mask_t operator|(int i, const mask_t& g); bool operator||(const mask_t& g, bool b); bool operator||(bool b, const mask_t& g); bool operator&&(const mask_t& g, bool b); bool operator&&(bool b, const mask_t& g); #else using mask_t = int; #endif using Se3r = Se3; /* * Serialization of math classes */ #include #include // fast serialization (no version infor and no tracking) for basic math types // http://www.boost.org/doc/libs/1_42_0/libs/serialization/doc/traits.html#bitwise BOOST_IS_BITWISE_SERIALIZABLE(Vector2r); BOOST_IS_BITWISE_SERIALIZABLE(Vector2i); BOOST_IS_BITWISE_SERIALIZABLE(Vector3r); BOOST_IS_BITWISE_SERIALIZABLE(Vector3i); BOOST_IS_BITWISE_SERIALIZABLE(Vector6r); BOOST_IS_BITWISE_SERIALIZABLE(Vector6i); BOOST_IS_BITWISE_SERIALIZABLE(Quaternionr); BOOST_IS_BITWISE_SERIALIZABLE(Se3r); BOOST_IS_BITWISE_SERIALIZABLE(Matrix3r); BOOST_IS_BITWISE_SERIALIZABLE(Matrix6r); namespace boost { namespace serialization { template void serialize(Archive & ar, Vector2r & g, const unsigned int version){ Real &x=g[0], &y=g[1]; ar & BOOST_SERIALIZATION_NVP(x) & BOOST_SERIALIZATION_NVP(y); } template void serialize(Archive & ar, Vector2i & g, const unsigned int version){ int &x=g[0], &y=g[1]; ar & BOOST_SERIALIZATION_NVP(x) & BOOST_SERIALIZATION_NVP(y); } template void serialize(Archive & ar, Vector3r & g, const unsigned int version) { Real &x=g[0], &y=g[1], &z=g[2]; ar & BOOST_SERIALIZATION_NVP(x) & BOOST_SERIALIZATION_NVP(y) & BOOST_SERIALIZATION_NVP(z); } template void serialize(Archive & ar, Vector3i & g, const unsigned int version){ int &x=g[0], &y=g[1], &z=g[2]; ar & BOOST_SERIALIZATION_NVP(x) & BOOST_SERIALIZATION_NVP(y) & BOOST_SERIALIZATION_NVP(z); } template void serialize(Archive & ar, Vector6r & g, const unsigned int version){ Real &v0=g[0], &v1=g[1], &v2=g[2], &v3=g[3], &v4=g[4], &v5=g[5]; ar & BOOST_SERIALIZATION_NVP(v0) & BOOST_SERIALIZATION_NVP(v1) & BOOST_SERIALIZATION_NVP(v2) & BOOST_SERIALIZATION_NVP(v3) & BOOST_SERIALIZATION_NVP(v4) & BOOST_SERIALIZATION_NVP(v5); } template void serialize(Archive & ar, Vector6i & g, const unsigned int version){ int &v0=g[0], &v1=g[1], &v2=g[2], &v3=g[3], &v4=g[4], &v5=g[5]; ar & BOOST_SERIALIZATION_NVP(v0) & BOOST_SERIALIZATION_NVP(v1) & BOOST_SERIALIZATION_NVP(v2) & BOOST_SERIALIZATION_NVP(v3) & BOOST_SERIALIZATION_NVP(v4) & BOOST_SERIALIZATION_NVP(v5); } template void serialize(Archive & ar, Quaternionr & g, const unsigned int version) { Real &w=g.w(), &x=g.x(), &y=g.y(), &z=g.z(); ar & BOOST_SERIALIZATION_NVP(w) & BOOST_SERIALIZATION_NVP(x) & BOOST_SERIALIZATION_NVP(y) & BOOST_SERIALIZATION_NVP(z); } template void serialize(Archive & ar, Se3r & g, const unsigned int version){ Vector3r& position=g.position; Quaternionr& orientation=g.orientation; ar & BOOST_SERIALIZATION_NVP(position) & BOOST_SERIALIZATION_NVP(orientation); } template void serialize(Archive & ar, Matrix3r & m, const unsigned int version){ Real &m00=m(0,0), &m01=m(0,1), &m02=m(0,2), &m10=m(1,0), &m11=m(1,1), &m12=m(1,2), &m20=m(2,0), &m21=m(2,1), &m22=m(2,2); ar & BOOST_SERIALIZATION_NVP(m00) & BOOST_SERIALIZATION_NVP(m01) & BOOST_SERIALIZATION_NVP(m02) & BOOST_SERIALIZATION_NVP(m10) & BOOST_SERIALIZATION_NVP(m11) & BOOST_SERIALIZATION_NVP(m12) & BOOST_SERIALIZATION_NVP(m20) & BOOST_SERIALIZATION_NVP(m21) & BOOST_SERIALIZATION_NVP(m22); } template void serialize(Archive & ar, Matrix6r & m, const unsigned int version){ Real &m00=m(0,0), &m01=m(0,1), &m02=m(0,2), &m03=m(0,3), &m04=m(0,4), &m05=m(0,5); Real &m10=m(1,0), &m11=m(1,1), &m12=m(1,2), &m13=m(1,3), &m14=m(1,4), &m15=m(1,5); Real &m20=m(2,0), &m21=m(2,1), &m22=m(2,2), &m23=m(2,3), &m24=m(2,4), &m25=m(2,5); Real &m30=m(3,0), &m31=m(3,1), &m32=m(3,2), &m33=m(3,3), &m34=m(3,4), &m35=m(3,5); Real &m40=m(4,0), &m41=m(4,1), &m42=m(4,2), &m43=m(4,3), &m44=m(4,4), &m45=m(4,5); Real &m50=m(5,0), &m51=m(5,1), &m52=m(5,2), &m53=m(5,3), &m54=m(5,4), &m55=m(5,5); ar & BOOST_SERIALIZATION_NVP(m00) & BOOST_SERIALIZATION_NVP(m01) & BOOST_SERIALIZATION_NVP(m02) & BOOST_SERIALIZATION_NVP(m03) & BOOST_SERIALIZATION_NVP(m04) & BOOST_SERIALIZATION_NVP(m05) & BOOST_SERIALIZATION_NVP(m10) & BOOST_SERIALIZATION_NVP(m11) & BOOST_SERIALIZATION_NVP(m12) & BOOST_SERIALIZATION_NVP(m13) & BOOST_SERIALIZATION_NVP(m14) & BOOST_SERIALIZATION_NVP(m15) & BOOST_SERIALIZATION_NVP(m20) & BOOST_SERIALIZATION_NVP(m21) & BOOST_SERIALIZATION_NVP(m22) & BOOST_SERIALIZATION_NVP(m23) & BOOST_SERIALIZATION_NVP(m24) & BOOST_SERIALIZATION_NVP(m25) & BOOST_SERIALIZATION_NVP(m30) & BOOST_SERIALIZATION_NVP(m31) & BOOST_SERIALIZATION_NVP(m32) & BOOST_SERIALIZATION_NVP(m33) & BOOST_SERIALIZATION_NVP(m34) & BOOST_SERIALIZATION_NVP(m35) & BOOST_SERIALIZATION_NVP(m40) & BOOST_SERIALIZATION_NVP(m41) & BOOST_SERIALIZATION_NVP(m42) & BOOST_SERIALIZATION_NVP(m43) & BOOST_SERIALIZATION_NVP(m44) & BOOST_SERIALIZATION_NVP(m45) & BOOST_SERIALIZATION_NVP(m50) & BOOST_SERIALIZATION_NVP(m51) & BOOST_SERIALIZATION_NVP(m52) & BOOST_SERIALIZATION_NVP(m53) & BOOST_SERIALIZATION_NVP(m54) & BOOST_SERIALIZATION_NVP(m55); } #ifdef YADE_MASK_ARBITRARY template void serialize(Archive & ar, mask_t& v, const unsigned int version){ std::string str = v.to_string(); ar & BOOST_SERIALIZATION_NVP(str); v = mask_t(str); } #endif } // namespace serialization } // namespace boost trunk-2018.02b/lib/base/Singleton.hpp000066400000000000000000000013161324306050200173220ustar00rootroot00000000000000// 2009 © Václav Šmilauer #pragma once #include #define FRIEND_SINGLETON(Class) friend class Singleton; // use to instantiate the self static member. #define SINGLETON_SELF(Class) template<> Class* Singleton::self=NULL; namespace { boost::mutex singleton_constructor_mutex; } template class Singleton{ protected: static T* self; // must not be method-local static variable, since it gets created in different translation units multiple times. public: static T& instance(){ if(!self) { boost::mutex::scoped_lock lock(singleton_constructor_mutex); if(!self) self=new T; } return *self; } }; trunk-2018.02b/lib/base/openmp-accu.hpp000066400000000000000000000234501324306050200175720ustar00rootroot00000000000000// 2010 © Václav Šmilauer #pragma once #include #include #include #ifdef YADE_OPENMP #include "omp.h" // O(1) access container which stores data in contiguous chunks of memory // each chunk belonging to one thread template class OpenMPArrayAccumulator{ int CLS; size_t nThreads; int perCL; // number of elements fitting inside cache line std::vector chunks; // array with pointers to the chunks of memory we have allocated; each item for one thread size_t sz; // current number of elements size_t nCL; // current number of allocated cache lines int nCL_for_N(size_t n){ return n/perCL+(n%perCL==0 ? 0 : 1); } // return number of cache lines to allocate for given number of elements public: OpenMPArrayAccumulator() : CLS(sysconf(_SC_LEVEL1_DCACHE_LINESIZE)>0 ? sysconf(_SC_LEVEL1_DCACHE_LINESIZE) : 64), nThreads(omp_get_max_threads()), perCL(CLS/sizeof(T)), chunks(nThreads,NULL), sz(0), nCL(0) { } OpenMPArrayAccumulator(size_t n): CLS(sysconf(_SC_LEVEL1_DCACHE_LINESIZE)>0 ? sysconf(_SC_LEVEL1_DCACHE_LINESIZE) : 64), nThreads(omp_get_max_threads()), perCL(CLS/sizeof(T)), chunks(nThreads,NULL), sz(0), nCL(0) { resize(n); } // change number of elements void resize(size_t n){ if(n==sz) return; // nothing to do size_t nCL_new=nCL_for_N(n); if(nCL_new>nCL){ for(size_t th=0; th(); } sz=n; } // clear (does not deallocate storage, anyway) void clear() { resize(0); } // return number of elements size_t size() const { return sz; } // get value of one element, by summing contributions of all threads T operator[](size_t ix) const { return get(ix); } T get(size_t ix) const { T ret(ZeroInitializer()); for(size_t th=0; th()); } // reset one element to ZeroInitializer void add(size_t ix, const T& diff){ chunks[omp_get_thread_num()][ix]+=diff; } void reset(size_t ix){ set(ix,ZeroInitializer()); } // get all stored data, organized first by index, then by threads; only used for debugging std::vector > getPerThreadData() const { std::vector > ret; for(size_t ix=0; ix vi; for(size_t th=0; th class OpenMPAccumulator{ // in the ctor, assume 64 bytes (arbitrary, but safe) if sysconf does not report anything meaningful // that might happen on newer proc models not yet reported by sysconf (?) // e.g. https://lists.launchpad.net/yade-dev/msg06294.html // where zero was reported, leading to FPU exception at startup int CLS; // cache line size int nThreads; int eSize; // size of an element, computed from cache line size and sizeof(T) char* data; // use void* rather than T*, since with T* the pointer arithmetics has sizeof(T) as unit, which is confusing; char* takes one byte public: // initialize storage with _zeroValue, depending on number of threads OpenMPAccumulator(): CLS(sysconf(_SC_LEVEL1_DCACHE_LINESIZE)>0 ? sysconf(_SC_LEVEL1_DCACHE_LINESIZE) : 64), nThreads(omp_get_max_threads()), eSize(CLS*(sizeof(T)/CLS+(sizeof(T)%CLS==0 ? 0 :1))) { int succ=posix_memalign(/*where allocated*/(void**)&data,/*alignment*/CLS,/*size*/ nThreads*eSize); if(succ!=0) throw std::runtime_error("OpenMPAccumulator: posix_memalign failed to allocate memory."); reset(); } ~OpenMPAccumulator() { free((void*)data); } // lock-free addition void operator+=(const T& val){ *((T*)(data+omp_get_thread_num()*eSize))+=val; } void operator-=(const T& val){ *((T*)(data+omp_get_thread_num()*eSize))-=val; } // return summary value; must not be used concurrently operator T() const { return get(); } // reset to zeroValue; must NOT be used concurrently void reset(){ for(int i=0; i(); } // this can be used to get the value from python, something like // .def_readonly("myAccu",&OpenMPAccumulator::get,"documentation") T get() const { T ret(ZeroInitializer()); for(int i=0; i getPerThreadData() const { std::vector ret; for(int i=0; i class OpenMPVector{ std::vector > vals; size_t sizeV; public: OpenMPVector() {sizeV = omp_get_max_threads(); vals.resize(sizeV);}; void push_back (const T& val) {vals[omp_get_thread_num()].push_back(val);}; size_t size() const { size_t sumSize = 0; for (size_t i=0; i= sizeV) { std::cerr<< ("Index is out of range.")<= size()) { std::cerr<< ("Index is out of range.")<= vals[t].size()) { ix-=vals[t].size(); t+=1; } return vals[t][ix]; } } void clear() { for (size_t i=0; i class OpenMPArrayAccumulator{ std::vector data; public: OpenMPArrayAccumulator(){} OpenMPArrayAccumulator(size_t n){ resize(n); } void resize(size_t s){ data.resize(s,ZeroInitializer()); } void clear(){ data.clear(); } size_t size() const { return data.size(); } T operator[](size_t ix) const { return get(ix); } T get(size_t ix) const { return data[ix]; } void add (size_t ix, const T& diff){ data[ix]+=diff; } void set(size_t ix, const T& val){ data[ix]=val; } void reset(size_t ix){ data[ix]=ZeroInitializer(); } std::vector > getPerThreadData() const { std::vector > ret; for(size_t ix=0; ix vi; vi.push_back(data[ix]); ret.push_back(vi); } return ret; } }; // single-threaded version of the accumulator above template class OpenMPAccumulator{ T data; public: void operator+=(const T& val){ data+=val; } void operator-=(const T& val){ data-=val; } operator T() const { return get(); } void reset(){ data=ZeroInitializer(); } T get() const { return data; } void set(const T& val){ data=val; } // debugging only std::vector getPerThreadData() const { std::vector ret; ret.push_back(data); return ret; } }; template using OpenMPVector=std::vector ; #endif // boost serialization BOOST_SERIALIZATION_SPLIT_FREE(OpenMPAccumulator); template void save(Archive &ar, const OpenMPAccumulator& a, unsigned int version){ int value=a.get(); ar & BOOST_SERIALIZATION_NVP(value); } template void load(Archive &ar, OpenMPAccumulator& a, unsigned int version){ int value; ar & BOOST_SERIALIZATION_NVP(value); a.set(value); } BOOST_SERIALIZATION_SPLIT_FREE(OpenMPAccumulator); template void save(Archive &ar, const OpenMPAccumulator& a, unsigned int version){ Real value=a.get(); ar & BOOST_SERIALIZATION_NVP(value); } template void load(Archive &ar, OpenMPAccumulator& a, unsigned int version){ Real value; ar & BOOST_SERIALIZATION_NVP(value); a.set(value); } BOOST_SERIALIZATION_SPLIT_FREE(OpenMPArrayAccumulator); template void save(Archive &ar, const OpenMPArrayAccumulator& a, unsigned int version){ size_t size=a.size(); ar & BOOST_SERIALIZATION_NVP(size); for(size_t i=0; i(i)).c_str(),item); } } template void load(Archive &ar, OpenMPArrayAccumulator& a, unsigned int version){ size_t size; ar & BOOST_SERIALIZATION_NVP(size); a.resize(size); for(size_t i=0; i(i)).c_str(),item); a.set(i,item); } } trunk-2018.02b/lib/computational-geometry/000077500000000000000000000000001324306050200204445ustar00rootroot00000000000000trunk-2018.02b/lib/computational-geometry/Hull2d.hpp000066400000000000000000000050151324306050200223100ustar00rootroot00000000000000// 2009 © Václav Šmilauer #include /*! Computing convex hull of a 2d cloud of points passed to the constructor, using Graham scan algorithm. Use the operator() to launch computation and get the hull as std::list. The source http://marknelson.us/2007/08/22/convex/ is gratefully acknowledged. Look there for detailed description and more information. */ class ConvexHull2d{ list raw_points, lower_partition_points, upper_partition_points, hull; Vector2r left, right; static Real direction(const Vector2r& p0, const Vector2r& p1, const Vector2r& p2) { return ((p0[0]-p1[0])*(p2[1]-p1[1]))-((p2[0]-p1[0])*(p0[1]-p1[1])); } struct Vector2r_xComparator{ bool operator()(const Vector2r& p1, const Vector2r& p2){return p1[0] build_half_hull(list& in, int factor){ vector out; in.push_back(right); out.push_back(left); while(in.size()>0){ out.push_back(in.front()); in.pop_front(); while(out.size()>=3){ size_t end=out.size()-1; if(factor*direction(out[end-2],out[end],out[end-1])<=0) out.erase(out.begin()+end-1); else break; } } return out; } public: ConvexHull2d(const list& pts){raw_points.assign(pts.begin(),pts.end());}; ConvexHull2d(const vector& pts){raw_points.assign(pts.begin(),pts.end());}; vector operator()(void){ partition_points(); vector lower_hull=build_half_hull(lower_partition_points,1); vector upper_hull=build_half_hull(upper_partition_points,-1); vector ret; ret.reserve(lower_hull.size()+upper_hull.size()-2); for(size_t i=upper_hull.size()-1; i>0; i--) ret.push_back(upper_hull[i]); size_t lsize=lower_hull.size(); for(size_t i=0; i P){ Real ret=0.; size_t n=P.size(); for(size_t i=0; i > >& scalarField, int sx, int sy, int sz) { sizeX = sx; sizeY = sy; sizeZ = sz; scalarField.resize(sx); for(int i=0;i > >& scalarField, Real iso) { isoValue = iso; nbTriangles = 0; for(int i=1;i > >& scalarField, int x,int y,int z) { static vector cellValues(8); static vector cellPositions(8); static vector vertexList(12); cellValues[0] = scalarField[x][y][z]; cellValues[1] = scalarField[x+1][y][z]; cellValues[2] = scalarField[x+1][y][z+1]; cellValues[3] = scalarField[x][y][z+1]; cellValues[4] = scalarField[x][y+1][z]; cellValues[5] = scalarField[x+1][y+1][z]; cellValues[6] = scalarField[x+1][y+1][z+1]; cellValues[7] = scalarField[x][y+1][z+1]; cellPositions[0] = positions[x][y][z]; cellPositions[1] = positions[x+1][y][z]; cellPositions[2] = positions[x+1][y][z+1]; cellPositions[3] = positions[x][y][z+1]; cellPositions[4] = positions[x][y+1][z]; cellPositions[5] = positions[x+1][y+1][z]; cellPositions[6] = positions[x+1][y+1][z+1]; cellPositions[7] = positions[x][y+1][z+1]; /* compute index in edgeArray that tells how the surface intersect the cell */ int index = 0; if (cellValues[0]>isoValue) index |= 1; if (cellValues[1]>isoValue) index |= 2; if (cellValues[2]>isoValue) index |= 4; if (cellValues[3]>isoValue) index |= 8; if (cellValues[4]>isoValue) index |= 16; if (cellValues[5]>isoValue) index |= 32; if (cellValues[6]>isoValue) index |= 64; if (cellValues[7]>isoValue) index |= 128; /* compute position of vertices where the surface interesct the cell*/ int config = edgeArray[index]; if (config == 0) /* the cell is not intersected by surface */ return; if (config & 1) interpolate(cellPositions[0], cellPositions[1], cellValues[0], cellValues[1], vertexList[0]); if (config & 2) interpolate(cellPositions[1], cellPositions[2], cellValues[1], cellValues[2], vertexList[1]); if (config & 4) interpolate(cellPositions[2], cellPositions[3], cellValues[2], cellValues[3], vertexList[2]); if (config & 8) interpolate(cellPositions[3], cellPositions[0], cellValues[3], cellValues[0], vertexList[3]); if (config & 16) interpolate(cellPositions[4], cellPositions[5], cellValues[4], cellValues[5], vertexList[4]); if (config & 32) interpolate(cellPositions[5], cellPositions[6], cellValues[5], cellValues[6], vertexList[5]); if (config & 64) interpolate(cellPositions[6], cellPositions[7], cellValues[6], cellValues[7], vertexList[6]); if (config & 128) interpolate(cellPositions[7], cellPositions[4], cellValues[7], cellValues[4], vertexList[7]); if (config & 256) interpolate(cellPositions[0], cellPositions[4], cellValues[0], cellValues[4], vertexList[8]); if (config & 512) interpolate(cellPositions[1], cellPositions[5], cellValues[1], cellValues[5], vertexList[9]); if (config & 1024) interpolate(cellPositions[2], cellPositions[6], cellValues[2], cellValues[6], vertexList[10]); if (config & 2048) interpolate(cellPositions[3], cellPositions[7], cellValues[3], cellValues[7], vertexList[11]); /* compute triangles and normals*/ int offset,i; const int * tri = triTable[index]; for (i=0; tri[i]!=-1; ++i) { offset = nbTriangles*3; index = tri[i]; triangles[offset] = vertexList[index]; computeNormal(scalarField,x,y,z,offset,index); offset++; index = tri[++i]; triangles[offset] = vertexList[index]; computeNormal(scalarField,x,y,z,offset,index); offset++; index = tri[++i]; triangles[offset] = vertexList[index]; computeNormal(scalarField,x,y,z,offset,index); nbTriangles++; } } void MarchingCube::computeNormal(const vector > >& scalarField, int x, int y, int z,int offset, int triangleNum) { switch (triangleNum) { case 0 : normals[offset] = computeNormalX(scalarField,x, y, z); break; case 1 : normals[offset] = computeNormalZ(scalarField,x+1, y, z); break; case 2 : normals[offset] = computeNormalX(scalarField,x, y, z+1); break; case 3 : normals[offset] = computeNormalZ(scalarField,x, y, z); break; case 4 : normals[offset] = computeNormalX(scalarField,x, y+1, z); break; case 5 : normals[offset] = computeNormalZ(scalarField,x+1, y+1, z); break; case 6 : normals[offset] = computeNormalX(scalarField,x, y+1, z+1); break; case 7 : normals[offset] = computeNormalZ(scalarField,x, y+1, z); break; case 8 : normals[offset] = computeNormalY(scalarField,x, y, z); break; case 9 : normals[offset] = computeNormalY(scalarField,x+1, y, z); break; case 10 : normals[offset] = computeNormalY(scalarField,x+1, y, z+1); break; case 11 : normals[offset] = computeNormalY(scalarField,x, y, z+1); break; } } void MarchingCube::interpolate(const Vector3r& vect1, const Vector3r& vect2, Real val1, Real val2, Vector3r& vect) { vect[0] = interpolateValue(val1, val2, vect1[0], vect2[0]); vect[1] = interpolateValue(val1, val2, vect1[1], vect2[1]); vect[2] = interpolateValue(val1, val2, vect1[2], vect2[2]); } Real MarchingCube::interpolateValue( Real val1, Real val2, Real val_cible1, Real val_cible2) { Real a = (val_cible2-val_cible1)/(val2-val1); Real b = val_cible1-a*val1; return a*isoValue+b; } const Vector3r& MarchingCube::computeNormalX(const vector > >& scalarField, int x, int y, int z) { static Vector3r normal; Real xyz = scalarField[x][y][z]; Real xp1yz = scalarField[x+1][y][z]; normal[0] = interpolateValue( xp1yz, xyz, scalarField[x+2][y][z]-xyz, xp1yz-scalarField[x-1][y][z] ); normal[1] = interpolateValue( xyz, xp1yz, scalarField[x][y+1][z], scalarField[x+1][y+1][z] ) - interpolateValue( xyz, xp1yz, scalarField[x][y-1][z], scalarField[x+1][y-1][z] ); normal[2] = interpolateValue( xyz, xp1yz, scalarField[x][y][z+1], scalarField[x+1][y][z+1] ) - interpolateValue( xyz, xp1yz, scalarField[x][y][z-1], scalarField[x+1][y][z-1] ); normal.normalize(); return normal; } const Vector3r& MarchingCube::computeNormalY(const vector > >& scalarField, int x, int y, int z ) { static Vector3r normal; Real xyz = scalarField[x][y][z]; Real xyp1z = scalarField[x][y+1][z]; normal[0] = interpolateValue( xyz, xyp1z, scalarField[x+1][y][z], scalarField[x+1][y+1][z] ) - interpolateValue( xyz, xyp1z, scalarField[x-1][y][z], scalarField[x-1][y+1][z] ); normal[1] = interpolateValue( xyp1z, xyz, scalarField[x][y+2][z]-xyz, xyp1z-scalarField[x][y-1][z] ); normal[2] = interpolateValue( xyz, xyp1z, scalarField[x][y][z+1], scalarField[x][y+1][z+1] ) - interpolateValue( xyz, xyp1z, scalarField[x][y][z-1], scalarField[x][y+1][z-1] ); normal.normalize(); return normal; } const Vector3r& MarchingCube::computeNormalZ(const vector > >& scalarField, int x, int y, int z) { static Vector3r normal; Real xyz = scalarField[x][y][z]; Real xyzp1 = scalarField[x][y][z+1]; normal[0] = interpolateValue( xyz, xyzp1, scalarField[x+1][y][z], scalarField[x+1][y][z+1] ) - interpolateValue( xyz, xyzp1, scalarField[x-1][y][z], scalarField[x-1][y][z+1] ); normal[1] = interpolateValue( xyz, xyzp1, scalarField[x][y+1][z], scalarField[x][y+1][z+1] ) - interpolateValue( xyz, xyzp1, scalarField[x][y-1][z], scalarField[x][y-1][z+1] ); normal[2] = interpolateValue( xyzp1, xyz, scalarField[x][y][z+2]-xyz, xyzp1-scalarField[x][y][z-1] ); normal.normalize(); return normal; } const int MarchingCube::edgeArray[256] = { 0x0 , 0x109, 0x203, 0x30a, 0x406, 0x50f, 0x605, 0x70c, 0x80c, 0x905, 0xa0f, 0xb06, 0xc0a, 0xd03, 0xe09, 0xf00, 0x190, 0x99 , 0x393, 0x29a, 0x596, 0x49f, 0x795, 0x69c, 0x99c, 0x895, 0xb9f, 0xa96, 0xd9a, 0xc93, 0xf99, 0xe90, 0x230, 0x339, 0x33 , 0x13a, 0x636, 0x73f, 0x435, 0x53c, 0xa3c, 0xb35, 0x83f, 0x936, 0xe3a, 0xf33, 0xc39, 0xd30, 0x3a0, 0x2a9, 0x1a3, 0xaa , 0x7a6, 0x6af, 0x5a5, 0x4ac, 0xbac, 0xaa5, 0x9af, 0x8a6, 0xfaa, 0xea3, 0xda9, 0xca0, 0x460, 0x569, 0x663, 0x76a, 0x66 , 0x16f, 0x265, 0x36c, 0xc6c, 0xd65, 0xe6f, 0xf66, 0x86a, 0x963, 0xa69, 0xb60, 0x5f0, 0x4f9, 0x7f3, 0x6fa, 0x1f6, 0xff , 0x3f5, 0x2fc, 0xdfc, 0xcf5, 0xfff, 0xef6, 0x9fa, 0x8f3, 0xbf9, 0xaf0, 0x650, 0x759, 0x453, 0x55a, 0x256, 0x35f, 0x55 , 0x15c, 0xe5c, 0xf55, 0xc5f, 0xd56, 0xa5a, 0xb53, 0x859, 0x950, 0x7c0, 0x6c9, 0x5c3, 0x4ca, 0x3c6, 0x2cf, 0x1c5, 0xcc , 0xfcc, 0xec5, 0xdcf, 0xcc6, 0xbca, 0xac3, 0x9c9, 0x8c0, 0x8c0, 0x9c9, 0xac3, 0xbca, 0xcc6, 0xdcf, 0xec5, 0xfcc, 0xcc , 0x1c5, 0x2cf, 0x3c6, 0x4ca, 0x5c3, 0x6c9, 0x7c0, 0x950, 0x859, 0xb53, 0xa5a, 0xd56, 0xc5f, 0xf55, 0xe5c, 0x15c, 0x55 , 0x35f, 0x256, 0x55a, 0x453, 0x759, 0x650, 0xaf0, 0xbf9, 0x8f3, 0x9fa, 0xef6, 0xfff, 0xcf5, 0xdfc, 0x2fc, 0x3f5, 0xff , 0x1f6, 0x6fa, 0x7f3, 0x4f9, 0x5f0, 0xb60, 0xa69, 0x963, 0x86a, 0xf66, 0xe6f, 0xd65, 0xc6c, 0x36c, 0x265, 0x16f, 0x66 , 0x76a, 0x663, 0x569, 0x460, 0xca0, 0xda9, 0xea3, 0xfaa, 0x8a6, 0x9af, 0xaa5, 0xbac, 0x4ac, 0x5a5, 0x6af, 0x7a6, 0xaa , 0x1a3, 0x2a9, 0x3a0, 0xd30, 0xc39, 0xf33, 0xe3a, 0x936, 0x83f, 0xb35, 0xa3c, 0x53c, 0x435, 0x73f, 0x636, 0x13a, 0x33 , 0x339, 0x230, 0xe90, 0xf99, 0xc93, 0xd9a, 0xa96, 0xb9f, 0x895, 0x99c, 0x69c, 0x795, 0x49f, 0x596, 0x29a, 0x393, 0x99 , 0x190, 0xf00, 0xe09, 0xd03, 0xc0a, 0xb06, 0xa0f, 0x905, 0x80c, 0x70c, 0x605, 0x50f, 0x406, 0x30a, 0x203, 0x109, 0x0 }; const int MarchingCube::triTable[256][16] = { {-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, { 0, 8, 3, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, { 0, 1, 9, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, { 1, 8, 3, 9, 8, 1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, { 1, 2, 10, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, { 0, 8, 3, 1, 2, 10, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, { 9, 2, 10, 0, 2, 9, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, { 2, 8, 3, 2, 10, 8, 10, 9, 8, -1, -1, -1, -1, -1, -1, -1}, { 3, 11, 2, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, { 0, 11, 2, 8, 11, 0, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, { 1, 9, 0, 2, 3, 11, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, { 1, 11, 2, 1, 9, 11, 9, 8, 11, -1, -1, -1, -1, -1, -1, -1}, { 3, 10, 1, 11, 10, 3, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, { 0, 10, 1, 0, 8, 10, 8, 11, 10, -1, -1, -1, -1, -1, -1, -1}, { 3, 9, 0, 3, 11, 9, 11, 10, 9, -1, -1, -1, -1, -1, -1, -1}, { 9, 8, 10, 10, 8, 11, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, { 4, 7, 8, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, { 4, 3, 0, 7, 3, 4, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, { 0, 1, 9, 8, 4, 7, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, { 4, 1, 9, 4, 7, 1, 7, 3, 1, -1, -1, -1, -1, -1, -1, -1}, { 1, 2, 10, 8, 4, 7, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, { 3, 4, 7, 3, 0, 4, 1, 2, 10, -1, -1, -1, -1, -1, -1, -1}, { 9, 2, 10, 9, 0, 2, 8, 4, 7, -1, -1, -1, -1, -1, -1, -1}, { 2, 10, 9, 2, 9, 7, 2, 7, 3, 7, 9, 4, -1, -1, -1, -1}, { 8, 4, 7, 3, 11, 2, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, {11, 4, 7, 11, 2, 4, 2, 0, 4, -1, -1, -1, -1, -1, -1, -1}, { 9, 0, 1, 8, 4, 7, 2, 3, 11, -1, -1, -1, -1, -1, -1, -1}, { 4, 7, 11, 9, 4, 11, 9, 11, 2, 9, 2, 1, -1, -1, -1, -1}, { 3, 10, 1, 3, 11, 10, 7, 8, 4, -1, -1, -1, -1, -1, -1, -1}, { 1, 11, 10, 1, 4, 11, 1, 0, 4, 7, 11, 4, -1, -1, -1, -1}, { 4, 7, 8, 9, 0, 11, 9, 11, 10, 11, 0, 3, -1, -1, -1, -1}, { 4, 7, 11, 4, 11, 9, 9, 11, 10, -1, -1, -1, -1, -1, -1, -1}, { 9, 5, 4, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, { 9, 5, 4, 0, 8, 3, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, { 0, 5, 4, 1, 5, 0, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, { 8, 5, 4, 8, 3, 5, 3, 1, 5, -1, -1, -1, -1, -1, -1, -1}, { 1, 2, 10, 9, 5, 4, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, { 3, 0, 8, 1, 2, 10, 4, 9, 5, -1, -1, -1, -1, -1, -1, -1}, { 5, 2, 10, 5, 4, 2, 4, 0, 2, -1, -1, -1, -1, -1, -1, -1}, { 2, 10, 5, 3, 2, 5, 3, 5, 4, 3, 4, 8, -1, -1, -1, -1}, { 9, 5, 4, 2, 3, 11, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, { 0, 11, 2, 0, 8, 11, 4, 9, 5, -1, -1, -1, -1, -1, -1, -1}, { 0, 5, 4, 0, 1, 5, 2, 3, 11, -1, -1, -1, -1, -1, -1, -1}, { 2, 1, 5, 2, 5, 8, 2, 8, 11, 4, 8, 5, -1, -1, -1, -1}, {10, 3, 11, 10, 1, 3, 9, 5, 4, -1, -1, -1, -1, -1, -1, -1}, { 4, 9, 5, 0, 8, 1, 8, 10, 1, 8, 11, 10, -1, -1, -1, -1}, { 5, 4, 0, 5, 0, 11, 5, 11, 10, 11, 0, 3, -1, -1, -1, -1}, { 5, 4, 8, 5, 8, 10, 10, 8, 11, -1, -1, -1, -1, -1, -1, -1}, { 9, 7, 8, 5, 7, 9, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, { 9, 3, 0, 9, 5, 3, 5, 7, 3, -1, -1, -1, -1, -1, -1, -1}, { 0, 7, 8, 0, 1, 7, 1, 5, 7, -1, -1, -1, -1, -1, -1, -1}, { 1, 5, 3, 3, 5, 7, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, { 9, 7, 8, 9, 5, 7, 10, 1, 2, -1, -1, -1, -1, -1, -1, -1}, {10, 1, 2, 9, 5, 0, 5, 3, 0, 5, 7, 3, -1, -1, -1, -1}, { 8, 0, 2, 8, 2, 5, 8, 5, 7, 10, 5, 2, -1, -1, -1, -1}, { 2, 10, 5, 2, 5, 3, 3, 5, 7, -1, -1, -1, -1, -1, -1, -1}, { 7, 9, 5, 7, 8, 9, 3, 11, 2, -1, -1, -1, -1, -1, -1, -1}, { 9, 5, 7, 9, 7, 2, 9, 2, 0, 2, 7, 11, -1, -1, -1, -1}, { 2, 3, 11, 0, 1, 8, 1, 7, 8, 1, 5, 7, -1, -1, -1, -1}, {11, 2, 1, 11, 1, 7, 7, 1, 5, -1, -1, -1, -1, -1, -1, -1}, { 9, 5, 8, 8, 5, 7, 10, 1, 3, 10, 3, 11, -1, -1, -1, -1}, { 5, 7, 0, 5, 0, 9, 7, 11, 0, 1, 0, 10, 11, 10, 0, -1}, {11, 10, 0, 11, 0, 3, 10, 5, 0, 8, 0, 7, 5, 7, 0, -1}, {11, 10, 5, 7, 11, 5, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, {10, 6, 5, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, { 0, 8, 3, 5, 10, 6, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, { 9, 0, 1, 5, 10, 6, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, { 1, 8, 3, 1, 9, 8, 5, 10, 6, -1, -1, -1, -1, -1, -1, -1}, { 1, 6, 5, 2, 6, 1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, { 1, 6, 5, 1, 2, 6, 3, 0, 8, -1, -1, -1, -1, -1, -1, -1}, { 9, 6, 5, 9, 0, 6, 0, 2, 6, -1, -1, -1, -1, -1, -1, -1}, { 5, 9, 8, 5, 8, 2, 5, 2, 6, 3, 2, 8, -1, -1, -1, -1}, { 2, 3, 11, 10, 6, 5, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, {11, 0, 8, 11, 2, 0, 10, 6, 5, -1, -1, -1, -1, -1, -1, -1}, { 0, 1, 9, 2, 3, 11, 5, 10, 6, -1, -1, -1, -1, -1, -1, -1}, { 5, 10, 6, 1, 9, 2, 9, 11, 2, 9, 8, 11, -1, -1, -1, -1}, { 6, 3, 11, 6, 5, 3, 5, 1, 3, -1, -1, -1, -1, -1, -1, -1}, { 0, 8, 11, 0, 11, 5, 0, 5, 1, 5, 11, 6, -1, -1, -1, -1}, { 3, 11, 6, 0, 3, 6, 0, 6, 5, 0, 5, 9, -1, -1, -1, -1}, { 6, 5, 9, 6, 9, 11, 11, 9, 8, -1, -1, -1, -1, -1, -1, -1}, { 5, 10, 6, 4, 7, 8, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, { 4, 3, 0, 4, 7, 3, 6, 5, 10, -1, -1, -1, -1, -1, -1, -1}, { 1, 9, 0, 5, 10, 6, 8, 4, 7, -1, -1, -1, -1, -1, -1, -1}, {10, 6, 5, 1, 9, 7, 1, 7, 3, 7, 9, 4, -1, -1, -1, -1}, { 6, 1, 2, 6, 5, 1, 4, 7, 8, -1, -1, -1, -1, -1, -1, -1}, { 1, 2, 5, 5, 2, 6, 3, 0, 4, 3, 4, 7, -1, -1, -1, -1}, { 8, 4, 7, 9, 0, 5, 0, 6, 5, 0, 2, 6, -1, -1, -1, -1}, { 7, 3, 9, 7, 9, 4, 3, 2, 9, 5, 9, 6, 2, 6, 9, -1}, { 3, 11, 2, 7, 8, 4, 10, 6, 5, -1, -1, -1, -1, -1, -1, -1}, { 5, 10, 6, 4, 7, 2, 4, 2, 0, 2, 7, 11, -1, -1, -1, -1}, { 0, 1, 9, 4, 7, 8, 2, 3, 11, 5, 10, 6, -1, -1, -1, -1}, { 9, 2, 1, 9, 11, 2, 9, 4, 11, 7, 11, 4, 5, 10, 6, -1}, { 8, 4, 7, 3, 11, 5, 3, 5, 1, 5, 11, 6, -1, -1, -1, -1}, { 5, 1, 11, 5, 11, 6, 1, 0, 11, 7, 11, 4, 0, 4, 11, -1}, { 0, 5, 9, 0, 6, 5, 0, 3, 6, 11, 6, 3, 8, 4, 7, -1}, { 6, 5, 9, 6, 9, 11, 4, 7, 9, 7, 11, 9, -1, -1, -1, -1}, { 10, 4, 9, 6, 4, 10, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, { 4, 10, 6, 4, 9, 10, 0, 8, 3, -1, -1, -1, -1, -1, -1, -1}, {10, 0, 1, 10, 6, 0, 6, 4, 0, -1, -1, -1, -1, -1, -1, -1}, { 8, 3, 1, 8, 1, 6, 8, 6, 4, 6, 1, 10, -1, -1, -1, -1}, { 1, 4, 9, 1, 2, 4, 2, 6, 4, -1, -1, -1, -1, -1, -1, -1}, { 3, 0, 8, 1, 2, 9, 2, 4, 9, 2, 6, 4, -1, -1, -1, -1}, { 0, 2, 4, 4, 2, 6, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, { 8, 3, 2, 8, 2, 4, 4, 2, 6, -1, -1, -1, -1, -1, -1, -1}, {10, 4, 9, 10, 6, 4, 11, 2, 3, -1, -1, -1, -1, -1, -1, -1}, { 0, 8, 2, 2, 8, 11, 4, 9, 10, 4, 10, 6, -1, -1, -1, -1}, { 3, 11, 2, 0, 1, 6, 0, 6, 4, 6, 1, 10, -1, -1, -1, -1}, { 6, 4, 1, 6, 1, 10, 4, 8, 1, 2, 1, 11, 8, 11, 1, -1}, { 9, 6, 4, 9, 3, 6, 9, 1, 3, 11, 6, 3, -1, -1, -1, -1}, { 8, 11, 1, 8, 1, 0, 11, 6, 1, 9, 1, 4, 6, 4, 1, -1}, { 3, 11, 6, 3, 6, 0, 0, 6, 4, -1, -1, -1, -1, -1, -1, -1}, { 6, 4, 8, 11, 6, 8, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, { 7, 10, 6, 7, 8, 10, 8, 9, 10, -1, -1, -1, -1, -1, -1, -1}, { 0, 7, 3, 0, 10, 7, 0, 9, 10, 6, 7, 10, -1, -1, -1, -1}, {10, 6, 7, 1, 10, 7, 1, 7, 8, 1, 8, 0, -1, -1, -1, -1}, {10, 6, 7, 10, 7, 1, 1, 7, 3, -1, -1, -1, -1, -1, -1, -1}, { 1, 2, 6, 1, 6, 8, 1, 8, 9, 8, 6, 7, -1, -1, -1, -1}, { 2, 6, 9, 2, 9, 1, 6, 7, 9, 0, 9, 3, 7, 3, 9, -1}, { 7, 8, 0, 7, 0, 6, 6, 0, 2, -1, -1, -1, -1, -1, -1, -1}, { 7, 3, 2, 6, 7, 2, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, { 2, 3, 11, 10, 6, 8, 10, 8, 9, 8, 6, 7, -1, -1, -1, -1}, { 2, 0, 7, 2, 7, 11, 0, 9, 7, 6, 7, 10, 9, 10, 7, -1}, { 1, 8, 0, 1, 7, 8, 1, 10, 7, 6, 7, 10, 2, 3, 11, -1}, {11, 2, 1, 11, 1, 7, 10, 6, 1, 6, 7, 1, -1, -1, -1, -1}, { 8, 9, 6, 8, 6, 7, 9, 1, 6, 11, 6, 3, 1, 3, 6, -1}, { 0, 9, 1, 11, 6, 7, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, { 7, 8, 0, 7, 0, 6, 3, 11, 0, 11, 6, 0, -1, -1, -1, -1}, { 7, 11, 6, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, { 7, 6, 11, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, { 3, 0, 8, 11, 7, 6, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, { 0, 1, 9, 11, 7, 6, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, { 8, 1, 9, 8, 3, 1, 11, 7, 6, -1, -1, -1, -1, -1, -1, -1}, {10, 1, 2, 6, 11, 7, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, { 1, 2, 10, 3, 0, 8, 6, 11, 7, -1, -1, -1, -1, -1, -1, -1}, { 2, 9, 0, 2, 10, 9, 6, 11, 7, -1, -1, -1, -1, -1, -1, -1}, { 6, 11, 7, 2, 10, 3, 10, 8, 3, 10, 9, 8, -1, -1, -1, -1}, { 7, 2, 3, 6, 2, 7, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, { 7, 0, 8, 7, 6, 0, 6, 2, 0, -1, -1, -1, -1, -1, -1, -1}, { 2, 7, 6, 2, 3, 7, 0, 1, 9, -1, -1, -1, -1, -1, -1, -1}, { 1, 6, 2, 1, 8, 6, 1, 9, 8, 8, 7, 6, -1, -1, -1, -1}, {10, 7, 6, 10, 1, 7, 1, 3, 7, -1, -1, -1, -1, -1, -1, -1}, {10, 7, 6, 1, 7, 10, 1, 8, 7, 1, 0, 8, -1, -1, -1, -1}, { 0, 3, 7, 0, 7, 10, 0, 10, 9, 6, 10, 7, -1, -1, -1, -1}, { 7, 6, 10, 7, 10, 8, 8, 10, 9, -1, -1, -1, -1, -1, -1, -1}, { 6, 8, 4, 11, 8, 6, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, { 3, 6, 11, 3, 0, 6, 0, 4, 6, -1, -1, -1, -1, -1, -1, -1}, { 8, 6, 11, 8, 4, 6, 9, 0, 1, -1, -1, -1, -1, -1, -1, -1}, { 9, 4, 6, 9, 6, 3, 9, 3, 1, 11, 3, 6, -1, -1, -1, -1}, { 6, 8, 4, 6, 11, 8, 2, 10, 1, -1, -1, -1, -1, -1, -1, -1}, { 1, 2, 10, 3, 0, 11, 0, 6, 11, 0, 4, 6, -1, -1, -1, -1}, { 4, 11, 8, 4, 6, 11, 0, 2, 9, 2, 10, 9, -1, -1, -1, -1}, {10, 9, 3, 10, 3, 2, 9, 4, 3, 11, 3, 6, 4, 6, 3, -1}, { 8, 2, 3, 8, 4, 2, 4, 6, 2, -1, -1, -1, -1, -1, -1, -1}, { 0, 4, 2, 4, 6, 2, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, { 1, 9, 0, 2, 3, 4, 2, 4, 6, 4, 3, 8, -1, -1, -1, -1}, { 1, 9, 4, 1, 4, 2, 2, 4, 6, -1, -1, -1, -1, -1, -1, -1}, { 8, 1, 3, 8, 6, 1, 8, 4, 6, 6, 10, 1, -1, -1, -1, -1}, {10, 1, 0, 10, 0, 6, 6, 0, 4, -1, -1, -1, -1, -1, -1, -1}, { 4, 6, 3, 4, 3, 8, 6, 10, 3, 0, 3, 9, 10, 9, 3, -1}, {10, 9, 4, 6, 10, 4, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, { 4, 9, 5, 7, 6, 11, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, { 0, 8, 3, 4, 9, 5, 11, 7, 6, -1, -1, -1, -1, -1, -1, -1}, { 5, 0, 1, 5, 4, 0, 7, 6, 11, -1, -1, -1, -1, -1, -1, -1}, {11, 7, 6, 8, 3, 4, 3, 5, 4, 3, 1, 5, -1, -1, -1, -1}, { 9, 5, 4, 10, 1, 2, 7, 6, 11, -1, -1, -1, -1, -1, -1, -1}, { 6, 11, 7, 1, 2, 10, 0, 8, 3, 4, 9, 5, -1, -1, -1, -1}, { 7, 6, 11, 5, 4, 10, 4, 2, 10, 4, 0, 2, -1, -1, -1, -1}, { 3, 4, 8, 3, 5, 4, 3, 2, 5, 10, 5, 2, 11, 7, 6, -1}, { 7, 2, 3, 7, 6, 2, 5, 4, 9, -1, -1, -1, -1, -1, -1, -1}, { 9, 5, 4, 0, 8, 6, 0, 6, 2, 6, 8, 7, -1, -1, -1, -1}, { 3, 6, 2, 3, 7, 6, 1, 5, 0, 5, 4, 0, -1, -1, -1, -1}, { 6, 2, 8, 6, 8, 7, 2, 1, 8, 4, 8, 5, 1, 5, 8, -1}, { 9, 5, 4, 10, 1, 6, 1, 7, 6, 1, 3, 7, -1, -1, -1, -1}, { 1, 6, 10, 1, 7, 6, 1, 0, 7, 8, 7, 0, 9, 5, 4, -1}, { 4, 0, 10, 4, 10, 5, 0, 3, 10, 6, 10, 7, 3, 7, 10, -1}, { 7, 6, 10, 7, 10, 8, 5, 4, 10, 4, 8, 10, -1, -1, -1, -1}, { 6, 9, 5, 6, 11, 9, 11, 8, 9, -1, -1, -1, -1, -1, -1, -1}, { 3, 6, 11, 0, 6, 3, 0, 5, 6, 0, 9, 5, -1, -1, -1, -1}, { 0, 11, 8, 0, 5, 11, 0, 1, 5, 5, 6, 11, -1, -1, -1, -1}, { 6, 11, 3, 6, 3, 5, 5, 3, 1, -1, -1, -1, -1, -1, -1, -1}, { 1, 2, 10, 9, 5, 11, 9, 11, 8, 11, 5, 6, -1, -1, -1, -1}, { 0, 11, 3, 0, 6, 11, 0, 9, 6, 5, 6, 9, 1, 2, 10, -1}, {11, 8, 5, 11, 5, 6, 8, 0, 5, 10, 5, 2, 0, 2, 5, -1}, { 6, 11, 3, 6, 3, 5, 2, 10, 3, 10, 5, 3, -1, -1, -1, -1}, { 5, 8, 9, 5, 2, 8, 5, 6, 2, 3, 8, 2, -1, -1, -1, -1}, { 9, 5, 6, 9, 6, 0, 0, 6, 2, -1, -1, -1, -1, -1, -1, -1}, { 1, 5, 8, 1, 8, 0, 5, 6, 8, 3, 8, 2, 6, 2, 8, -1}, { 1, 5, 6, 2, 1, 6, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, { 1, 3, 6, 1, 6, 10, 3, 8, 6, 5, 6, 9, 8, 9, 6, -1}, {10, 1, 0, 10, 0, 6, 9, 5, 0, 5, 6, 0, -1, -1, -1, -1}, { 0, 3, 8, 5, 6, 10, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, {10, 5, 6, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, {11, 5, 10, 7, 5, 11, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, {11, 5, 10, 11, 7, 5, 8, 3, 0, -1, -1, -1, -1, -1, -1, -1}, { 5, 11, 7, 5, 10, 11, 1, 9, 0, -1, -1, -1, -1, -1, -1, -1}, {10, 7, 5, 10, 11, 7, 9, 8, 1, 8, 3, 1, -1, -1, -1, -1}, {11, 1, 2, 11, 7, 1, 7, 5, 1, -1, -1, -1, -1, -1, -1, -1}, { 0, 8, 3, 1, 2, 7, 1, 7, 5, 7, 2, 11, -1, -1, -1, -1}, { 9, 7, 5, 9, 2, 7, 9, 0, 2, 2, 11, 7, -1, -1, -1, -1}, { 7, 5, 2, 7, 2, 11, 5, 9, 2, 3, 2, 8, 9, 8, 2, -1}, { 2, 5, 10, 2, 3, 5, 3, 7, 5, -1, -1, -1, -1, -1, -1, -1}, { 8, 2, 0, 8, 5, 2, 8, 7, 5, 10, 2, 5, -1, -1, -1, -1}, { 9, 0, 1, 5, 10, 3, 5, 3, 7, 3, 10, 2, -1, -1, -1, -1}, { 9, 8, 2, 9, 2, 1, 8, 7, 2, 10, 2, 5, 7, 5, 2, -1}, { 1, 3, 5, 3, 7, 5, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, { 0, 8, 7, 0, 7, 1, 1, 7, 5, -1, -1, -1, -1, -1, -1, -1}, { 9, 0, 3, 9, 3, 5, 5, 3, 7, -1, -1, -1, -1, -1, -1, -1}, { 9, 8, 7, 5, 9, 7, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, { 5, 8, 4, 5, 10, 8, 10, 11, 8, -1, -1, -1, -1, -1, -1, -1}, { 5, 0, 4, 5, 11, 0, 5, 10, 11, 11, 3, 0, -1, -1, -1, -1}, { 0, 1, 9, 8, 4, 10, 8, 10, 11, 10, 4, 5, -1, -1, -1, -1}, {10, 11, 4, 10, 4, 5, 11, 3, 4, 9, 4, 1, 3, 1, 4, -1}, { 2, 5, 1, 2, 8, 5, 2, 11, 8, 4, 5, 8, -1, -1, -1, -1}, { 0, 4, 11, 0, 11, 3, 4, 5, 11, 2, 11, 1, 5, 1, 11, -1}, { 0, 2, 5, 0, 5, 9, 2, 11, 5, 4, 5, 8, 11, 8, 5, -1}, { 9, 4, 5, 2, 11, 3, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, { 2, 5, 10, 3, 5, 2, 3, 4, 5, 3, 8, 4, -1, -1, -1, -1}, { 5, 10, 2, 5, 2, 4, 4, 2, 0, -1, -1, -1, -1, -1, -1, -1}, { 3, 10, 2, 3, 5, 10, 3, 8, 5, 4, 5, 8, 0, 1, 9, -1}, { 5, 10, 2, 5, 2, 4, 1, 9, 2, 9, 4, 2, -1, -1, -1, -1}, { 8, 4, 5, 8, 5, 3, 3, 5, 1, -1, -1, -1, -1, -1, -1, -1}, { 0, 4, 5, 1, 0, 5, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, { 8, 4, 5, 8, 5, 3, 9, 0, 5, 0, 3, 5, -1, -1, -1, -1}, { 9, 4, 5, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, { 4, 11, 7, 4, 9, 11, 9, 10, 11, -1, -1, -1, -1, -1, -1, -1}, { 0, 8, 3, 4, 9, 7, 9, 11, 7, 9, 10, 11, -1, -1, -1, -1}, { 1, 10, 11, 1, 11, 4, 1, 4, 0, 7, 4, 11, -1, -1, -1, -1}, { 3, 1, 4, 3, 4, 8, 1, 10, 4, 7, 4, 11, 10, 11, 4, -1}, { 4, 11, 7, 9, 11, 4, 9, 2, 11, 9, 1, 2, -1, -1, -1, -1}, { 9, 7, 4, 9, 11, 7, 9, 1, 11, 2, 11, 1, 0, 8, 3, -1}, {11, 7, 4, 11, 4, 2, 2, 4, 0, -1, -1, -1, -1, -1, -1, -1}, {11, 7, 4, 11, 4, 2, 8, 3, 4, 3, 2, 4, -1, -1, -1, -1}, { 2, 9, 10, 2, 7, 9, 2, 3, 7, 7, 4, 9, -1, -1, -1, -1}, { 9, 10, 7, 9, 7, 4, 10, 2, 7, 8, 7, 0, 2, 0, 7, -1}, { 3, 7, 10, 3, 10, 2, 7, 4, 10, 1, 10, 0, 4, 0, 10, -1}, { 1, 10, 2, 8, 7, 4, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, { 4, 9, 1, 4, 1, 7, 7, 1, 3, -1, -1, -1, -1, -1, -1, -1}, { 4, 9, 1, 4, 1, 7, 0, 8, 1, 8, 7, 1, -1, -1, -1, -1}, { 4, 0, 3, 7, 4, 3, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, { 4, 8, 7, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, { 9, 10, 8, 10, 11, 8, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, { 3, 0, 9, 3, 9, 11, 11, 9, 10, -1, -1, -1, -1, -1, -1, -1}, { 0, 1, 10, 0, 10, 8, 8, 10, 11, -1, -1, -1, -1, -1, -1, -1}, { 3, 1, 10, 11, 3, 10, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, { 1, 2, 11, 1, 11, 9, 9, 11, 8, -1, -1, -1, -1, -1, -1, -1}, { 3, 0, 9, 3, 9, 11, 1, 2, 9, 2, 11, 9, -1, -1, -1, -1}, { 0, 2, 11, 8, 0, 11, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, { 3, 2, 11, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, { 2, 3, 8, 2, 8, 10, 10, 8, 9, -1, -1, -1, -1, -1, -1, -1}, { 9, 10, 2, 0, 9, 2, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, { 2, 3, 8, 2, 8, 10, 0, 1, 8, 1, 10, 8, -1, -1, -1, -1}, { 1, 10, 2, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, { 1, 3, 8, 9, 1, 8, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, { 0, 9, 1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, { 0, 3, 8, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, {-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1} }; trunk-2018.02b/lib/computational-geometry/MarchingCube.hpp000066400000000000000000000054371324306050200235150ustar00rootroot00000000000000/************************************************************************* * Copyright (C) 2004 by Olivier Galizzi * * olivier.galizzi@imag.fr * * * * This program is free software; it is licensed under the terms of the * * GNU General Public License v2 or later. See file LICENSE for details. * *************************************************************************/ #pragma once #include #include #include #include class MarchingCube { /// ATTRIBUTES private : vector triangles; public : const vector& getTriangles() { return triangles; } private : vector normals; public : const vector& getNormals() { return normals; } private : int nbTriangles; public : int getNbTriangles() { return nbTriangles; } private : int sizeX,sizeY,sizeZ; private : Real isoValue; private : vector > > positions; private : static const int edgeArray[256]; private : static const int triTable[256][16]; Vector3r aNormal; /// PRIVATE METHOD /** triangulate cell (x,y,z) **/ private : void polygonize (const vector > >& scalarField, int x, int y, int z); /** compute normals of the triangles previously found with polygonizecalcule les normales des triangles trouver dans la case (x,y,z) @param n : indice of the first triangle to process **/ private : void computeNormal(const vector > >& scalarField, int x, int y, int z,int offset, int triangleNum); /** interpolate coordinates of point vect (that is on isosurface) from coordinates of points vect1 et vect2 **/ private : void interpolate (const Vector3r& vect1, const Vector3r& vect2, Real val1, Real val2,Vector3r& vect); /** Same as interpolate but in 1D **/ private : Real interpolateValue(Real val1, Real val2, Real val_cible1, Real val_cible2); /** Compute normal to vertice or triangle inside cell (x,y,z) **/ private : const Vector3r& computeNormalX(const vector > >& scalarField, int x, int y, int z); private : const Vector3r& computeNormalY(const vector > >& scalarField, int x, int y, int z); private : const Vector3r& computeNormalZ(const vector > >& scalarField, int x, int y, int z); /// CONSTRUCTOR/DESTRUCTOR public : MarchingCube (); public : ~MarchingCube (); /// PULIC METHODS public : void computeTriangulation(const vector > >& scalarField, Real iso); public : void init(int sx, int sy, int sz, const Vector3r& min, const Vector3r& max); public : void resizeScalarField(vector > >& scalarField, int sx, int sy, int sz); }; trunk-2018.02b/lib/factory/000077500000000000000000000000001324306050200154035ustar00rootroot00000000000000trunk-2018.02b/lib/factory/ClassFactory.cpp000066400000000000000000000077261324306050200205200ustar00rootroot00000000000000/************************************************************************* * Copyright (C) 2004 by Olivier Galizzi * * olivier.galizzi@imag.fr * * Copyright (C) 2004 by Janek Kozicki * * cosurgi@berlios.de * * * * This program is free software; it is licensed under the terms of the * * GNU General Public License v2 or later. See file LICENSE for details. * *************************************************************************/ #include "ClassFactory.hpp" #include #include CREATE_LOGGER(ClassFactory); SINGLETON_SELF(ClassFactory); class Factorable; bool ClassFactory::registerFactorable( std::string name , CreateFactorableFnPtr create, CreateSharedFactorableFnPtr createShared, CreatePureCustomFnPtr createPureCustom) { bool tmp = map.insert( FactorableCreatorsMap::value_type( name , FactorableCreators(create,createShared, createPureCustom) )).second; return tmp; } shared_ptr ClassFactory::createShared( std::string name ) { FactorableCreatorsMap::const_iterator i = map.find( name ); if( i == map.end() ) { dlm.load(name); if (dlm.isLoaded(name)) { if( map.find( name ) == map.end() ) { // Well, exception are also a way to return value, right? // This throws at startup for every .so that doesn't contain class named the same as the library. // I.e. almost everything in yade-libs and some others installed locally... // Don't log that, it would confuse users. //LOG_FATAL("Can't create class "< second.createShared ) (); } Factorable* ClassFactory::createPure( std::string name ) { FactorableCreatorsMap::const_iterator i = map.find( name ); if( i == map.end() ) { dlm.load(name); if (dlm.isLoaded(name)) { if( map.find( name ) == map.end() ) { throw std::runtime_error(("Class "+name+" not registered in the ClassFactory.").c_str()); } return createPure(name); } throw std::runtime_error(("Class "+name+" could not be factored in the ClassFactory.").c_str()); } return ( i -> second.create ) (); } void * ClassFactory::createPureCustom( std::string name ) { FactorableCreatorsMap::const_iterator i = map.find( name ); if( i == map.end() ) throw std::runtime_error(("Class "+name+" could not be factored in the ClassFactory.").c_str()); return ( i -> second.createPureCustom ) (); } bool ClassFactory::load(const string& name) { return dlm.load(name); } string ClassFactory::lastError() { return dlm.lastError(); } void ClassFactory::registerPluginClasses(const char* fileAndClasses[]){ assert(fileAndClasses[0]!=NULL); // must be file name // only filename given, no classes names explicitly if(fileAndClasses[1]==NULL){ /* strip leading path (if any; using / as path separator) and strip one suffix (if any) to get the contained class name */ string heldClass=boost::algorithm::replace_regex_copy(string(fileAndClasses[0]),boost::regex("^(.*/)?(.*?)(\\.[^.]*)?$"),string("\\2")); #ifdef YADE_DEBUG if(getenv("YADE_DEBUG")) cerr<<__FILE__<<":"<<__LINE__<<": Plugin "< #include #include "DynLibManager.hpp" /*! define the following macro to enable experimenta boost::serialization support Slows the compilation down about 2×. Python wrapper defines O.saveXML('file.xml') to try it out. Loading is not yet implemented (should be easy) */ #include #include #include #include #include // must come after all supported archive types #include #include #include #include #include #define REGISTER_FACTORABLE(name) \ inline shared_ptr< Factorable > CreateShared##name() \ { \ return shared_ptr< name > ( new name ); \ } \ inline Factorable* Create##name() \ { \ return new name; \ } \ inline void * CreatePureCustom##name() \ { \ return new name; \ } \ const bool registered##name __attribute__ ((unused)) = \ ClassFactory::instance().registerFactorable( #name , \ Create##name , \ CreateShared##name , \ CreatePureCustom##name); class Factorable; /*! \brief The class factory of Yade used for serialization purpose and also as a dynamic library loader. All classes that call the macro REGISTER_FACTORABLE in their header are registered inside the factory so it is possible to ask the factory to create an instance of that class. This is automatic because the macro should be outside the class definition, so it is called automatically when the class is loaded by the program or when a dynamic library is loaded. This ClassFactory also acts as a dynamic library loader : when you ask for an instance, either the class already exists inside the factory and a new instance is created, or the class doesn't exist inside the factory and the ClassFactory will look on the hard drive to know if your class exists inside a dynamic library. If so the library is loaded and a new instance of the class can be created. \note ClassFactory is a singleton so you can't create an instance of it because its constructor is private. You should instead use ClassFactory::instance().createShared("Rigidbody") for example. */ class ClassFactory : public Singleton { private : /// Pointer on a function that create an instance of a serializable class an return a shared pointer on it typedef shared_ptr ( *CreateSharedFactorableFnPtr )(); /// Pointer on a function that create an instance of a serializable class an return a C pointer on it typedef Factorable* ( *CreateFactorableFnPtr )(); /// Pointer on a function that create an instance of a custom class (i.e. not serializable) and return a void C pointer on it typedef void* ( *CreatePureCustomFnPtr )(); /// Description of a class that is stored inside the factory. struct FactorableCreators { CreateFactorableFnPtr create; /// Used to create a C pointer on the class (if serializable) CreateSharedFactorableFnPtr createShared; /// Used to create a shared pointer on the class (if serializable) CreatePureCustomFnPtr createPureCustom; /// Used to create a void C pointer on the class FactorableCreators() {}; FactorableCreators( CreateFactorableFnPtr c, CreateSharedFactorableFnPtr cs, CreatePureCustomFnPtr cpc) { create = c; createShared = cs; createPureCustom = cpc; }; }; /// Type of a Stl map used to map the registered class name with their FactorableCreators typedef std::map< std::string , FactorableCreators > FactorableCreatorsMap; /// The internal dynamic library manager used to load dynamic libraries when an instance of a non loaded class is ask DynLibManager dlm; /// Map that contains the name of the registered class and their description FactorableCreatorsMap map; ClassFactory() { if(getenv("YADE_DEBUG")) fprintf(stderr,"Constructing ClassFactory.\n"); } ClassFactory(const ClassFactory&); ClassFactory& operator=(const ClassFactory&); virtual ~ClassFactory() {}; DECLARE_LOGGER; public : /*! This method is used to register a Factorable class into the factory. It is called only from macro REGISTER_CLASS_TO_FACTORY \param name the name of the class \param create a pointer to a function that is able to return a C pointer on the given class \param createPureCustom a pointer to a function that is able to return a void C pointer on the given class \param verify a pointer to a function that is able to return the type_info of the given class \param type type of the class (SERIALIZABLE or CUSTOM) \param f is true is the class is a fundamental one (Vector3, Quaternion) \return true if registration is succesfull */ bool registerFactorable( std::string name , CreateFactorableFnPtr create, CreateSharedFactorableFnPtr createShared, CreatePureCustomFnPtr createPureCustom); /// Create a shared pointer on a serializable class of the given name shared_ptr createShared( std::string name ); /// Create a C pointer on a serializable class of the given name Factorable* createPure( std::string name ); /// Create a void C pointer on a class of the given name void * createPureCustom( std::string name ); /*! Mainly used by the method findType for serialization purpose. Tells if a given type is a serilializable class \param tp type info of the type to test \param fundamental is true if the given type is fundamental (Vector3,Quaternion ...) */ bool load(const string& fullFileName); std::string lastError(); void registerPluginClasses(const char* fileAndClasses[]); list pluginClasses; virtual string getClassName() const { return "Factorable"; }; virtual string getBaseClassName(int ) const { return "";}; FRIEND_SINGLETON(ClassFactory); }; /*! Macro defining what classes can be found in this plugin -- must always be used in the respective .cpp file. * A function registerThisPluginClasses_FirstPluginName is generated at every occurence. The identifier should * be unique and avoids use of __COUNTER__ which didn't appear in gcc until 4.3. */ #define _YADE_PLUGIN_BOOST_REGISTER(x,y,z) BOOST_CLASS_EXPORT_IMPLEMENT(z); BOOST_SERIALIZATION_FACTORY_0(z); #define _YADE_PLUGIN_REPEAT(x,y,z) BOOST_PP_STRINGIZE(z), // priority 500 is greater than priority for log4cxx initialization (in core/main/pyboot.cpp); therefore lo5cxx will be initialized before plugins are registered #define YADE_PLUGIN(plugins) namespace{ __attribute__((constructor)) void BOOST_PP_CAT(registerThisPluginClasses_,BOOST_PP_SEQ_HEAD(plugins)) (void){ const char* info[]={__FILE__ , BOOST_PP_SEQ_FOR_EACH(_YADE_PLUGIN_REPEAT,~,plugins) NULL}; ClassFactory::instance().registerPluginClasses(info);} } BOOST_PP_SEQ_FOR_EACH(_YADE_PLUGIN_BOOST_REGISTER,~,plugins) trunk-2018.02b/lib/factory/DynLibManager.cpp000066400000000000000000000041201324306050200205600ustar00rootroot00000000000000/************************************************************************* * Copyright (C) 2004 by Olivier Galizzi * * olivier.galizzi@imag.fr * * with help from Bronek Kozicki * * * * This program is free software; it is licensed under the terms of the * * GNU General Public License v2 or later. See file LICENSE for details. * *************************************************************************/ #include "DynLibManager.hpp" #include "ClassFactory.hpp" CREATE_LOGGER(DynLibManager); DynLibManager::DynLibManager () { autoUnload = true; } DynLibManager::~DynLibManager () { if(autoUnload) unloadAll(); } // load plugin with given filename bool DynLibManager::load (const string& lib){ if (lib.empty()) throw std::runtime_error(__FILE__ ": got empty library name to load."); void* handle = dlopen(lib.c_str(),RTLD_GLOBAL | RTLD_NOW); if (!handle) return !error(); handles[lib] = handle; return true; } // unload plugin, given full filename bool DynLibManager::unload (const string& libName) { if (isLoaded(libName)) return closeLib(libName); else return false; } bool DynLibManager::unloadAll () { std::map::iterator ith = handles.begin(); std::map::iterator ithEnd = handles.end(); for( ; ith!=ithEnd ; ++ith) if ((*ith).first.length()!=0) unload((*ith).first); return false; } bool DynLibManager::isLoaded (const string& libName) { std::map::iterator ith = handles.find(libName); return (ith!= handles.end() && (*ith).second!=NULL); } void DynLibManager::setAutoUnload ( bool enabled ) { autoUnload = enabled; } bool DynLibManager::closeLib(const string libName) { dlclose(handles[libName]); return !error(); } std::string DynLibManager::lastError() { return lastError_; } bool DynLibManager::error() { char * error = dlerror(); if (error != NULL) { lastError_ = error; } return (error!=NULL); } trunk-2018.02b/lib/factory/DynLibManager.hpp000066400000000000000000000023301324306050200205660ustar00rootroot00000000000000/************************************************************************* * Copyright (C) 2004 by Olivier Galizzi * * olivier.galizzi@imag.fr * * Copyright (C) 2004 by Bronek Kozicki * * * * This program is free software; it is licensed under the terms of the * * GNU General Public License v2 or later. See file LICENSE for details. * *************************************************************************/ #pragma once #include #include #include class DynLibManager { private : std::map handles; bool autoUnload; public : DynLibManager (); ~DynLibManager (); void addBaseDirectory(const std::string& dir); bool load(const std::string& libName); bool unload (const std::string& libName); bool isLoaded (const std::string& libName); bool unloadAll (); void setAutoUnload ( bool enabled ); std::string lastError(); DECLARE_LOGGER; private : bool closeLib(const std::string libName); bool error(); std::string lastError_; }; trunk-2018.02b/lib/factory/Factorable.hpp000066400000000000000000000050071324306050200201600ustar00rootroot00000000000000/************************************************************************* * Copyright (C) 2004 by Olivier Galizzi * * olivier.galizzi@imag.fr * * Copyright (C) 2004 by Janek Kozicki * * cosurgi@berlios.de * * * * This program is free software; it is licensed under the terms of the * * GNU General Public License v2 or later. See file LICENSE for details. * *************************************************************************/ #pragma once #include "ClassFactory.hpp" #include #include //! macro for registering both class and its base #define REGISTER_CLASS_AND_BASE(cn,bcn) REGISTER_CLASS_NAME(cn); REGISTER_BASE_CLASS_NAME(bcn); #define REGISTER_CLASS_NAME(cn) \ public : virtual string getClassName() const { return #cn; }; // FIXME[1] - that macro below should go to another class! factorable has nothing to do with inheritance tree. #define REGISTER_BASE_CLASS_NAME(bcn) \ public : virtual string getBaseClassName(unsigned int i=0) const \ { \ string token; \ vector tokens; \ string str=#bcn; \ istringstream iss(str); \ while (!iss.eof()) \ { \ iss >> token; \ tokens.push_back(token); \ } \ if (i>=token.size()) \ return ""; \ else \ return tokens[i]; \ } \ public : virtual int getBaseClassNumber() \ { \ string token; \ vector tokens; \ string str=#bcn; \ istringstream iss(str); \ while (!iss.eof()) \ { \ iss >> token; \ tokens.push_back(token); \ } \ return tokens.size(); \ } class Factorable { public : Factorable() {} virtual ~Factorable() {} virtual string getBaseClassName(unsigned int i=0) const { return "";} // FIXME[1] virtual int getBaseClassNumber() { return 0;} // FIXME[1] REGISTER_CLASS_NAME(Factorable); // FIXME - virtual function to return version, long and short description, OR // maybe just a file with the same name as class with description inside // public : virtual std::string getVersion(); // FIXME[1] - we can make a class Plugin for all that extra stuff: // shortDescription(), longDescription(), baseClassName(), baseClassNumber() }; trunk-2018.02b/lib/multimethods/000077500000000000000000000000001324306050200164525ustar00rootroot00000000000000trunk-2018.02b/lib/multimethods/DynLibDispatcher.hpp000066400000000000000000000413731324306050200223630ustar00rootroot00000000000000/************************************************************************* * Copyright (C) 2004 by Janek Kozicki * * cosurgi@berlios.de * * * * This program is free software; it is licensed under the terms of the * * GNU General Public License v2 or later. See file LICENSE for details. * *************************************************************************/ #pragma once #include #include #include #include #include #include // compat with former yade's local Loki #define TYPELIST_1 LOKI_TYPELIST_1 #define TYPELIST_2 LOKI_TYPELIST_2 #define TYPELIST_3 LOKI_TYPELIST_3 #define TYPELIST_4 LOKI_TYPELIST_4 #define TYPELIST_5 LOKI_TYPELIST_5 #define TYPELIST_7 LOKI_TYPELIST_7 #include #include #include #include struct DynLibDispatcher_Item2D { int ix1, ix2; std::string functorName; DynLibDispatcher_Item2D(int a, int b, std::string c):ix1(a),ix2(b),functorName(c){}; }; struct DynLibDispatcher_Item1D { int ix1; std::string functorName; DynLibDispatcher_Item1D(int a, std::string c):ix1(a), functorName(c){}; }; /// /// base classes involved in multiple dispatch must be derived from Indexable /// /// base template for all dispatchers /// template < class BaseClass, // a typelist with base classess involved in the dispatch (or single class, for 1D ) // FIXME: should use shared_ptr references, like this: DynLibDispatcher< TYPELIST_2( shared_ptr& , shared_ptr& ) , .... class Executor, // class which gives multivirtual function class ResultType, // type returned by multivirtual function class TList, // typelist of arguments passed to multivirtual function // WARNING: first arguments must be shared_ptr, for details see FunctorWrapper bool autoSymmetry=true /*true - the function called is always the same, only order of arguments is rearranged to make correct function call, only go() is called false - the function called is always different. arguments order is not rearranged go(), and goReverse() are called, respectively*/ > class DynLibDispatcher { // this template recursively defines a type for callBacks matrix, with required number of dimensions private: template struct Matrix { using ResultIterator = Loki::NullType; using ResultIteratorInt = Loki::NullType; }; template struct Matrix< Loki::Typelist< Head, Loki::NullType > > { using Result = vector< shared_ptr< Executor > >; using ResultInt = vector< int >; using ResultIterator = typename vector< shared_ptr< Executor > >::iterator; using ResultIteratorInt = vector< int >::iterator; }; template struct Matrix< Loki::Typelist< Head, Tail > > { // recursive typedef to get matrix of required dimensions using InsideType = typename Matrix< Tail >::Result; using InsideTypeInt = typename Matrix< Tail >::ResultInt; using Result = vector< InsideType >; using ResultInt = vector< InsideTypeInt >; using ResultIterator = typename vector< InsideType >::iterator; using ResultIteratorInt = typename vector< InsideTypeInt >::iterator; }; typedef typename Loki::TL::Append< Loki::NullType , BaseClass >::Result BaseClassList; typedef typename Loki::TL::TypeAtNonStrict::Result BaseClass1; // 1D typedef typename Loki::TL::TypeAtNonStrict::Result BaseClass2; // 2D typedef typename Matrix< BaseClassList >::ResultIterator Iterator2; // outer iterator 2D typedef typename Matrix< BaseClassList >::ResultIteratorInt IteratorInfo2; typedef typename Matrix< BaseClassList >::Result MatrixType; typedef typename Matrix< BaseClassList >::ResultInt MatrixIntType; MatrixType callBacks; // multidimensional matrix that stores functors ( 1D, 2D, 3D, 4D, ....) MatrixIntType callBacksInfo; // multidimensional matrix for extra information about functors in the matrix // currently used to remember if it is reversed functor public: DynLibDispatcher() { // FIXME - static_assert( typeid(BaseClass1) == typeid(Parm1) ); // 1D // FIXME - static_assert( typeid(BaseClass2) == typeid(Parm2) ); // 2D clearMatrix(); }; void clearMatrix() { callBacks.clear(); callBacksInfo.clear(); } shared_ptr getExecutor(shared_ptr& arg1){ int ix1; if(arg1->getClassIndex()<0) throw runtime_error("No functor for type "+arg1->getClassName()+" (index "+boost::lexical_cast(arg1->getClassIndex())+"), since the index is invalid (negative)."); if(locateMultivirtualFunctor1D(ix1,arg1)) return callBacks[ix1]; return shared_ptr(); } shared_ptr getExecutor(shared_ptr& arg1, shared_ptr& arg2){ if(arg1->getClassIndex()<0 || arg2->getClassIndex()<0) throw runtime_error("No functor for types "+arg1->getClassName()+" (index "+boost::lexical_cast(arg1->getClassIndex())+") + "+arg2->getClassName()+" (index "+boost::lexical_cast(arg2->getClassIndex())+"), since some of the indices is invalid (negative)."); int ix1,ix2; if(locateMultivirtualFunctor2D(ix1,ix2,arg1,arg2)) return callBacks[ix1][ix2]; return shared_ptr(); } shared_ptr getFunctor1D(shared_ptr& base1){ return getExecutor(base1); } /* Return pointer to the functor for two base classes given. Swap is true if the dispatch objects should be swapped before calling Executor::go. */ shared_ptr getFunctor2D(shared_ptr& base1, shared_ptr& base2, bool& swap){ int ix1, ix2; if(!locateMultivirtualFunctor2D(ix1,ix2,base1,base2)) return shared_ptr(); swap=(bool)(callBacksInfo[ix1][ix2]); return callBacks[ix1][ix2]; } /*! Return representation of the dispatch matrix as vector of int,string (i.e. index,functor name) */ vector dataDispatchMatrix1D(){ vector ret; for(size_t i=0; igetClassName())); } return ret; } /*! Return representation of the dispatch matrix as vector of int,int,string (i.e. index1,index2,functor name) */ vector dataDispatchMatrix2D(){ vector ret; for(size_t i=0; igetClassName())); } } } return ret; } /*! Dump 1d dispatch matrix to given stream. */ std::ostream& dumpDispatchMatrix1D(std::ostream& out, const string& prefix=""){ for(size_t i=0; i "<getClassName()< "<getClassName()< executor){ // create base class, to access its index. (we can't access static variable, because // the class might not exist in memory at all, and we have to load dynamic library, // so that a static variable is created and accessible) shared_ptr baseClass = YADE_PTR_CAST(ClassFactory::instance().createShared(baseClassName)); // this is a strange tweak without which it won't work. shared_ptr base = YADE_PTR_CAST(baseClass); assert(base); int& index = base->getClassIndex(); if(index == -1) std::cerr << "--------> Did you forget to call createIndex(); in constructor?\n"; assert (index != -1); int maxCurrentIndex = base->getMaxCurrentlyUsedClassIndex(); callBacks.resize( maxCurrentIndex+1 ); // make sure that there is a place for new Functor callBacks[index] = executor; }; public: void add2DEntry(string baseClassName1, string baseClassName2, shared_ptr executor){ shared_ptr baseClass1 = YADE_PTR_CAST(ClassFactory::instance().createShared(baseClassName1)); shared_ptr baseClass2 = YADE_PTR_CAST(ClassFactory::instance().createShared(baseClassName2)); shared_ptr base1 = YADE_PTR_CAST(baseClass1); shared_ptr base2 = YADE_PTR_CAST(baseClass2); assert(base1); assert(base2); int& index1 = base1->getClassIndex(); if(index1 == -1) std::cerr << "--------> Did you forget to call createIndex(); in constructor?\n"; assert (index1 != -1); int& index2 = base2->getClassIndex(); if(index2 == -1) std::cerr << "--------> Did you forget to call createIndex(); in constructor?\n"; assert(index2 != -1); if( typeid(BaseClass1) == typeid(BaseClass2) ) assert(base1->getMaxCurrentlyUsedClassIndex() == base2->getMaxCurrentlyUsedClassIndex()); int maxCurrentIndex1 = base1->getMaxCurrentlyUsedClassIndex(); int maxCurrentIndex2 = base2->getMaxCurrentlyUsedClassIndex(); callBacks.resize( maxCurrentIndex1+1 ); // resizing callBacks table callBacksInfo.resize( maxCurrentIndex1+1 ); for( Iterator2 ci = callBacks.begin() ; ci != callBacks.end() ; ++ci ) ci->resize(maxCurrentIndex2+1); for( IteratorInfo2 cii = callBacksInfo.begin() ; cii != callBacksInfo.end() ; ++cii ) cii->resize(maxCurrentIndex2+1); if( typeid(BaseClass1) == typeid(BaseClass2) ) // both base classes are the same { callBacks [index2][index1] = executor; callBacks [index1][index2] = executor; string order = baseClassName1 + " " + baseClassName2; string reverseOrder = baseClassName2 + " " + baseClassName1; if( autoSymmetry || executor->checkOrder() == order ) // if you want autoSymmetry, you don't have to DEFINE_FUNCTOR_ORDER_2D { callBacksInfo [index2][index1] = 1; // this is reversed call callBacksInfo [index1][index2] = 0; } else if( executor->checkOrder() == reverseOrder ) { callBacksInfo [index2][index1] = 0; callBacksInfo [index1][index2] = 1; // this is reversed call } else { throw std::runtime_error(("Multimethods: checkOrder: undefined dispatch order for "+executor->getClassName()).c_str()); } } else // classes are different, no symmetry possible { callBacks [index1][index2] = executor; callBacksInfo [index1][index2] = 0; } } bool locateMultivirtualFunctor1D(int& index, shared_ptr& base) { if(callBacks.empty()) return false; index = base->getClassIndex(); assert( index >= 0 && (unsigned int)( index ) < callBacks.size()); if(callBacks[index]) return true; int depth=1; int index_tmp = base->getBaseClassIndex(depth); while(1) if(index_tmp == -1) return false; else if(callBacks[index_tmp]) { // BEGIN FIXME - this should be a separate function to resize stuff //cerr << index << " " << index_tmp << endl; if( callBacksInfo.size() <= (unsigned int)index ) callBacksInfo.resize(index+1); if( callBacks.size() <= (unsigned int)index ) callBacks.resize(index+1); // END callBacksInfo[index] = callBacksInfo[index_tmp]; callBacks [index] = callBacks [index_tmp]; return true; } else index_tmp = base->getBaseClassIndex(++depth); return false; // FIXME - this line should be not needed } bool locateMultivirtualFunctor2D(int& index1, int& index2, shared_ptr& base1, shared_ptr& base2) { //#define _DISP_TRACE(msg) cerr<<"@DT@"<<__LINE__<<" "<getClassIndex(); index2 = base2->getClassIndex(); assert(index1>=0); assert(index2>=0); assert((unsigned int)(index1)getClassName()<<"="<getClassName()<<"="<getClassName()); return true; } int foundIx1,foundIx2; int maxDp1=-1, maxDp2=-1; for(int dist=1; ; dist++){ bool distTooBig=true; foundIx1=foundIx2=-1; // found no dispatch at this depth yet for(int dp1=0; dp1<=dist; dp1++){ int dp2=dist-dp1; if((maxDp1>=0 && dp1>maxDp1) || (maxDp2>=0 && dp2>maxDp2)) continue; _DISP_TRACE(" Trying indices with depths "<getClassName()); } } if(foundIx1!=-1) return true; if(distTooBig){ _DISP_TRACE("Undefined dispatch, dist="< ResultType operator() (shared_ptr& base, Args... args) { int index; if( locateMultivirtualFunctor1D(index,base) ) return (callBacks[index])->go(base, args...); else return ResultType(); } // calling multivirtual function, 2D, // symmetry handling in private struct private: template< bool useSymmetry, class BaseClassTrait1, class BaseClassTrait2, typename... Args> struct InvocationTraits { static ResultType doDispatch(shared_ptr& ex, shared_ptr base1, shared_ptr base2, Args... args) { return ex->goReverse (base1, base2, args...); } }; template< class BaseClassTrait, typename... Args> struct InvocationTraits< true , BaseClassTrait, BaseClassTrait, Args... > { static ResultType doDispatch( shared_ptr& ex , shared_ptr base1, shared_ptr base2, Args... args) { return ex->go (base2, base1, args...); } }; // calling multivirtual function, 2D, public interface public: template ResultType operator() (shared_ptr& base1, shared_ptr& base2, Args... args) { int index1, index2; if( locateMultivirtualFunctor2D(index1,index2,base1,base2) ) { if(callBacksInfo[index1][index2])// reversed { using CallTraits=InvocationTraits ; return CallTraits::doDispatch( callBacks[index1][index2] , base1, base2, args...); } else return (callBacks[index1][index2] )->go(base1, base2, args...); } else return ResultType(); } }; trunk-2018.02b/lib/multimethods/FunctorWrapper.hpp000066400000000000000000000151621324306050200221510ustar00rootroot00000000000000/************************************************************************* * Copyright (C) 2004 by Janek Kozicki * * cosurgi@berlios.de * * * * This program is free software; it is licensed under the terms of the * * GNU General Public License v2 or later. See file LICENSE for details. * *************************************************************************/ #pragma once #include #include #include // compat with former yade's local Loki #define TYPELIST_1 LOKI_TYPELIST_1 #define TYPELIST_2 LOKI_TYPELIST_2 #define TYPELIST_3 LOKI_TYPELIST_3 #define TYPELIST_4 LOKI_TYPELIST_4 #define TYPELIST_5 LOKI_TYPELIST_5 #define TYPELIST_7 LOKI_TYPELIST_7 #include /////////////////////////////////////////////////////////////////////////////////////////////////// /// base template for classes that provide virtual functions for multiple dispatch, /// /// in other words for multivirtual function call /// /// /// /// This is a base template for all classes that will provide functions for multiple dispatch. /// /// To create a new category of virtual functions you must derive from this class, inside you will /// have provided virtual functions that you must overload to get multivirtual behaviour. /// /// Depending on the number of dimensions for which you need multiple dispatch you have to overload /// different virtual functions: /// /// for 1D, you overload function [*]: /// public: virtual ResultType go( ArgumentTypeList ); /// /// for 2D, you overload functions [*]: /// public: virtual ResultType go( ArgumentTypeList ); /// public: virtual ResultType goReverse( ArgumentTypeList ); /// /// function goReverse is called only when DynLibDispatcher has autoSymmetry set to false. /// otherwise DynLibDispatcher automatically reverses first two arguments, and calls go(), /// same applies for 3D. /// /// for 3D, you overload functions (not implemented now, but easy to write) [*]: /// public: virtual ResultType go( ArgumentTypeList ); /// public: virtual ResultType go012( ArgumentTypeList ); // forwards call to go() /// public: virtual ResultType go120( ArgumentTypeList ); /// public: virtual ResultType go201( ArgumentTypeList ); /// public: virtual ResultType go021( ArgumentTypeList ); /// public: virtual ResultType go210( ArgumentTypeList ); /// /// /// Template parameters: /// /// ResultType - is the type returned by multivirtual function /// /// ArgumentTypeList - is a TypeList of arguments accepted by multivirtual function, /// ATTENTION: /// - for 1D first type in this list must be of type shared_ptr /// this first argument acts like *this, in C++ virtual functions /// /// - for 2D first and second type in this list must be shared_ptr /// those argument act like *this1 and *this2 /// /// - for 3D first, second and third type in this list must be shared_ptr /// those argument act like *this1 , *this2 and *this3 /// /// /// /// [*] /// Note about virtual function arguents ArgumentTypeList - all functions take arguments by value /// only for fundametal types and pure pointers, all other types are passed by referece. For details look /// into Loki::TypeTraits::ParameterType. For example if you your class is: /// /// class ShapeDraw : public FunctorWrapper< std::string , TYPELIST_4(boost::shared_ptr,double,char,const std::string) > /// {} /// /// then virtual function to overload is: /// public: virtual std::string go(boost::shared_ptr&,double,char,const std::string& ); /// /// references were added where necessary, to optimize call speed. /// So pay attention when you overload this function. /// template < class ResultType, // type returned by multivirtual function class ArgumentTypeList // TypeList of arguments accepted by multivirtual function, > class FunctorWrapper //: public Serializable // FIXME functor shouldn't be serializable { private : typedef Loki::FunctorImpl Impl; typedef typename Impl::Parm1 Parm1; typedef typename Impl::Parm2 Parm2; typedef typename Impl::Parm3 Parm3; typedef typename Impl::Parm4 Parm4; typedef typename Impl::Parm5 Parm5; typedef typename Impl::Parm6 Parm6; typedef typename Impl::Parm7 Parm7; ResultType error(int n) { throw std::runtime_error(("Multimethods: bad virtual call (probably go/goReverse was not overridden with the same argument types; only fundamental types and pure pointers are passed by value, all other types (including shared_ptr<>) are passed by reference); types in the call were:\n" + string("1. ") + typeid(Parm1).name() + "\n" + "2. " + typeid(Parm2).name() + "\n" + "3. " + typeid(Parm3).name() + "\n" + "4. " + typeid(Parm4).name() + "\n" + "5. " + typeid(Parm5).name() + "\n" + "6. " + typeid(Parm6).name() + "\n" + "7. " + typeid(Parm7).name() + "\n" + "number of types used in the call: " + boost::lexical_cast(n) + "\n").c_str()); } public : FunctorWrapper () {}; virtual ~FunctorWrapper () {}; virtual string checkOrder() const { return ""; }; // in following functions a second throw was added - just to bypass compiler warnings - it will never be executed. virtual ResultType go ( Parm1) { return error(1); }; virtual ResultType go ( Parm1,Parm2) { return error(2); }; virtual ResultType go ( Parm1,Parm2,Parm3) { return error(3); }; virtual ResultType go ( Parm1,Parm2,Parm3,Parm4) { return error(4); }; virtual ResultType go ( Parm1,Parm2,Parm3,Parm4,Parm5) { return error(5); }; virtual ResultType go ( Parm1,Parm2,Parm3,Parm4,Parm5,Parm6) { return error(6); }; virtual ResultType go ( Parm1,Parm2,Parm3,Parm4,Parm5,Parm6,Parm7) { return error(7); }; virtual ResultType goReverse( Parm1) { return error(1); }; virtual ResultType goReverse( Parm1,Parm2) { return error(2); }; virtual ResultType goReverse( Parm1,Parm2,Parm3) { return error(3); }; virtual ResultType goReverse( Parm1,Parm2,Parm3,Parm4) { return error(4); }; virtual ResultType goReverse( Parm1,Parm2,Parm3,Parm4,Parm5) { return error(5); }; virtual ResultType goReverse( Parm1,Parm2,Parm3,Parm4,Parm5,Parm6) { return error(6); }; virtual ResultType goReverse( Parm1,Parm2,Parm3,Parm4,Parm5,Parm6,Parm7) { return error(7); }; }; #define DEFINE_FUNCTOR_ORDER_2D(class1,class2) \ public : virtual std::string checkOrder() const \ { \ return (string(#class1)+" "+string(#class2)); \ } \ trunk-2018.02b/lib/multimethods/Indexable.hpp000066400000000000000000000147001324306050200210600ustar00rootroot00000000000000/************************************************************************* * Copyright (C) 2004 by Olivier Galizzi * * olivier.galizzi@imag.fr * * Copyright (C) 2004 by Janek Kozicki * * cosurgi@berlios.de * * * * This program is free software; it is licensed under the terms of the * * GNU General Public License v2 or later. See file LICENSE for details. * *************************************************************************/ #pragma once #include #include #include /*! \brief Abstract interface for all Indexable class. An indexable class is a class that will be managed by a MultiMethodManager. The index the function getClassIndex() returns, corresponds to the index in the matrix where the class will be handled. */ #define _THROW_NOT_OVERRIDDEN throw std::logic_error(std::string("Derived class did not override ")+__PRETTY_FUNCTION__+", use REGISTER_INDEX_COUNTER and REGISTER_CLASS_INDEX.") class Indexable { protected : void createIndex() { int& index = getClassIndex(); if(index == -1) // assign new index { index = getMaxCurrentlyUsedClassIndex()+1; // so that other dispatchers will not fall in conflict with this index incrementMaxCurrentlyUsedClassIndex(); } } public : Indexable () {}; virtual ~Indexable () {}; /// Returns the id of the current class. This id is set by a multimethod manager virtual int& getClassIndex() { _THROW_NOT_OVERRIDDEN;}; virtual const int& getClassIndex() const { _THROW_NOT_OVERRIDDEN;}; virtual int& getBaseClassIndex(int ) { _THROW_NOT_OVERRIDDEN;}; virtual const int& getBaseClassIndex(int ) const { _THROW_NOT_OVERRIDDEN;}; virtual const int& getMaxCurrentlyUsedClassIndex() const { _THROW_NOT_OVERRIDDEN;}; virtual void incrementMaxCurrentlyUsedClassIndex() { _THROW_NOT_OVERRIDDEN;}; }; #undef _THROW_NOT_OVERRIDDEN // this macro is used by classes that are a dimension in multimethod matrix #define REGISTER_CLASS_INDEX(SomeClass,BaseClass) \ public: static int& getClassIndexStatic() { static int index = -1; return index; } \ public: virtual int& getClassIndex() { return getClassIndexStatic(); } \ public: virtual const int& getClassIndex() const { return getClassIndexStatic(); } \ public: virtual int& getBaseClassIndex(int depth) { \ static boost::scoped_ptr baseClass(new BaseClass); \ if(depth == 1) return baseClass->getClassIndex(); \ else return baseClass->getBaseClassIndex(--depth); \ } \ public: virtual const int& getBaseClassIndex(int depth) const { \ static boost::scoped_ptr baseClass(new BaseClass); \ if(depth == 1) return baseClass->getClassIndex(); \ else return baseClass->getBaseClassIndex(--depth); \ } // this macro is used by base class for classes that are a dimension in multimethod matrix // to keep track of maximum number of classes of their kin. Multimethod matrix can't // count this number (ie. as a size of the matrix), as there are many multimethod matrices #define REGISTER_INDEX_COUNTER(SomeClass) \ private: static int& getClassIndexStatic() { static int index = -1; return index; }\ public: virtual int& getClassIndex() { return getClassIndexStatic(); } \ public: virtual const int& getClassIndex() const { return getClassIndexStatic(); } \ public: virtual int& getBaseClassIndex(int) { throw std::logic_error("One of the following errors was detected:\n(1) Class " #SomeClass " called createIndex() in its ctor (but it shouldn't, being a top-level indexable; only use REGISTER_INDEX_COUNTER, but not createIndex()).\n(2) Some DerivedClass deriving from " #SomeClass " forgot to use REGISTER_CLASS_INDEX(DerivedClass," #SomeClass ").\nPlease fix that and come back again." ); } \ public: virtual const int& getBaseClassIndex(int) const { throw std::logic_error("One of the following errors was detected:\n(1) Class " #SomeClass " called createIndex() in its ctor (but it shouldn't, being a top-level indexable; only use REGISTER_INDEX_COUNTER, but not createIndex()).\n(2) Some DerivedClass deriving from " #SomeClass " forgot to use REGISTER_CLASS_INDEX(DerivedClass," #SomeClass ").\nPlease fix that and come back again." ); } \ private: static int& getMaxCurrentlyUsedIndexStatic() { static int maxCurrentlyUsedIndex = -1; return maxCurrentlyUsedIndex; } \ public: virtual const int& getMaxCurrentlyUsedClassIndex() const { \ SomeClass * Indexable##SomeClass = 0; \ Indexable##SomeClass = dynamic_cast(const_cast(this)); \ if (Indexable##SomeClass) { \ assert(Indexable##SomeClass); \ } \ return getMaxCurrentlyUsedIndexStatic(); \ } \ public: virtual void incrementMaxCurrentlyUsedClassIndex() { \ SomeClass * Indexable##SomeClass = 0; \ Indexable##SomeClass = dynamic_cast(this); \ if (Indexable##SomeClass) { \ assert(Indexable##SomeClass); \ } \ int& max = getMaxCurrentlyUsedIndexStatic(); \ max++; \ } \ // macro that should be passed in the 4th argument of YADE_CLASS_BASE_ATTR_PY in the top-level indexable #define YADE_PY_TOPINDEXABLE(className) .add_property("dispIndex",&Indexable_getClassIndex,"Return class index of this instance.").def("dispHierarchy",&Indexable_getClassIndices,(boost::python::arg("names")=true),"Return list of dispatch classes (from down upwards), starting with the class instance itself, top-level indexable at last. If names is true (default), return class names rather than numerical indices.") trunk-2018.02b/lib/opengl/000077500000000000000000000000001324306050200152205ustar00rootroot00000000000000trunk-2018.02b/lib/opengl/GLUtils.cpp000066400000000000000000000042341324306050200172520ustar00rootroot00000000000000#include"GLUtils.hpp" void GLUtils::Parallelepiped(const Vector3r& a, const Vector3r& b, const Vector3r& c){ glBegin(GL_LINE_STRIP); glVertex3v(b); glVertex3v(Vector3r(Vector3r::Zero())); glVertex3v(a); glVertex3v(Vector3r(a+b)); glVertex3v(Vector3r(a+b+c)); glVertex3v(Vector3r(b+c)); glVertex3v(b); glVertex3v(Vector3r(a+b)); glEnd(); glBegin(GL_LINE_STRIP); glVertex3v(Vector3r(b+c)); glVertex3v(c); glVertex3v(Vector3r(a+c)); glVertex3v(a); glEnd(); glBegin(GL_LINES); glVertex3v(Vector3r(Vector3r::Zero())); glVertex3v(c); glEnd(); glBegin(GL_LINES); glVertex3v(Vector3r(a+c)); glVertex3v(Vector3r(a+b+c)); glEnd(); } /**** code copied over from qglviewer ****/ /*! Draws a 3D arrow along the positive Z axis. \p length, \p radius and \p nbSubdivisions define its geometry. If \p radius is negative (default), it is set to 0.05 * \p length. Use drawArrow(const Vec& from, const Vec& to, float radius, int nbSubdivisions) or change the \c ModelView matrix to place the arrow in 3D. Uses current color and does not modify the OpenGL state. */ void GLUtils::QGLViewer::drawArrow(float length, float radius, int nbSubdivisions) { static GLUquadric* quadric = gluNewQuadric(); if (radius < 0.0) radius = 0.05 * length; const float head = 2.5*(radius / length) + 0.1; const float coneRadiusCoef = 4.0 - 5.0 * head; gluCylinder(quadric, radius, radius, length * (1.0 - head/coneRadiusCoef), nbSubdivisions, 1); glTranslatef(0.0, 0.0, length * (1.0 - head)); gluCylinder(quadric, coneRadiusCoef * radius, 0.0, head * length, nbSubdivisions, 1); glTranslatef(0.0, 0.0, -length * (1.0 - head)); } /*! Draws a 3D arrow between the 3D point \p from and the 3D point \p to, both defined in the current ModelView coordinates system. See drawArrow(float length, float radius, int nbSubdivisions) for details. */ void GLUtils::QGLViewer::drawArrow(const Vector3r& from, const Vector3r& to, float radius, int nbSubdivisions) { glPushMatrix(); glTranslatef(from[0],from[1],from[2]); Quaternionr q(Quaternionr().setFromTwoVectors(Vector3r(0,0,1),to-from)); glMultMatrix(q.toRotationMatrix().data()); drawArrow((to-from).norm(), radius, nbSubdivisions); glPopMatrix(); } trunk-2018.02b/lib/opengl/GLUtils.hpp000066400000000000000000000034561324306050200172640ustar00rootroot00000000000000// © 2008 Václav Šmilauer // // header-only utility functions for GL (moved over from extra/Shop.cpp) // #pragma once #include #include #include #include struct GLUtils{ // code copied from qglviewer struct QGLViewer{ static void drawArrow(float length=1.0f, float radius=-1.0f, int nbSubdivisions=12); static void drawArrow(const Vector3r& from, const Vector3r& to, float radius=-1.0f, int nbSubdivisions=12); }; // render wire of parallelepiped with sides given by vectors a,b,c; zero corner is at origin static void Parallelepiped(const Vector3r& a, const Vector3r& b, const Vector3r& c); static void GLDrawArrow(const Vector3r& from, const Vector3r& to, const Vector3r& color=Vector3r(1,1,1)){ glEnable(GL_LIGHTING); glColor3v(color); QGLViewer::drawArrow(from,to); } static void GLDrawLine(const Vector3r& from, const Vector3r& to, const Vector3r& color=Vector3r(1,1,1)){ glEnable(GL_LIGHTING); glColor3v(color); glBegin(GL_LINES); glVertex3v(from); glVertex3v(to); glEnd(); } static void GLDrawNum(const Real& n, const Vector3r& pos, const Vector3r& color=Vector3r(1,1,1), unsigned precision=3){ std::ostringstream oss; oss< // disable temporarily //#include #define STATIC_ASSERT(arg) #include #include struct OpenGLWrapper {}; // for ctags /// Primary Templates template< typename Type > inline void glRotate ( Type, Type, Type, Type ) { STATIC_ASSERT(false); }; template< typename Type > inline void glScale ( Type, Type, Type ) { STATIC_ASSERT(false); }; template< typename Type > inline void glScalev ( const Type ) { STATIC_ASSERT(false); }; template< typename Type > inline void glTranslate ( Type, Type, Type ) { STATIC_ASSERT(false); }; template< typename Type > inline void glTranslatev ( const Type ) { STATIC_ASSERT(false); }; template< typename Type > inline void glVertex2 ( Type, Type ) { STATIC_ASSERT(false); }; template< typename Type > inline void glVertex3 ( Type, Type, Type ) { STATIC_ASSERT(false); }; template< typename Type > inline void glVertex4 ( Type, Type, Type, Type ) { STATIC_ASSERT(false); }; template< typename Type > inline void glVertex2v ( const Type ) { STATIC_ASSERT(false); }; template< typename Type > inline void glVertex3v ( const Type ) { STATIC_ASSERT(false); }; template< typename Type > inline void glVertex4v ( const Type ) { STATIC_ASSERT(false); }; template< typename Type > inline void glNormal3 ( Type, Type, Type ) { STATIC_ASSERT(false); }; template< typename Type > inline void glNormal3v ( const Type ) { STATIC_ASSERT(false); }; template< typename Type > inline void glIndex ( Type ) { STATIC_ASSERT(false); }; template< typename Type > inline void glIndexv ( Type ) { STATIC_ASSERT(false); }; template< typename Type > inline void glColor3 ( Type, Type, Type ) { STATIC_ASSERT(false); }; template< typename Type > inline void glColor4 ( Type, Type, Type, Type ) { STATIC_ASSERT(false); }; template< typename Type > inline void glColor3v ( const Type ) { STATIC_ASSERT(false); }; template< typename Type > inline void glColor4v ( const Type ) { STATIC_ASSERT(false); }; template< typename Type > inline void glTexCoord1 ( Type ) { STATIC_ASSERT(false); }; template< typename Type > inline void glTexCoord2 ( Type, Type ) { STATIC_ASSERT(false); }; template< typename Type > inline void glTexCoord3 ( Type, Type, Type ) { STATIC_ASSERT(false); }; template< typename Type > inline void glTexCoord4 ( Type, Type, Type, Type ) { STATIC_ASSERT(false); }; template< typename Type > inline void glTexCoord1v ( const Type ) { STATIC_ASSERT(false); }; template< typename Type > inline void glTexCoord2v ( const Type ) { STATIC_ASSERT(false); }; template< typename Type > inline void glTexCoord3v ( const Type ) { STATIC_ASSERT(false); }; template< typename Type > inline void glTexCoord4v ( const Type ) { STATIC_ASSERT(false); }; template< typename Type > inline void glRasterPos2 ( Type, Type ) { STATIC_ASSERT(false); }; template< typename Type > inline void glRasterPos3 ( Type, Type, Type ) { STATIC_ASSERT(false); }; template< typename Type > inline void glRasterPos4 ( Type, Type, Type, Type ) { STATIC_ASSERT(false); }; template< typename Type > inline void glRasterPos2v ( const Type ) { STATIC_ASSERT(false); }; template< typename Type > inline void glRasterPos3v ( const Type ) { STATIC_ASSERT(false); }; template< typename Type > inline void glRasterPos4v ( const Type ) { STATIC_ASSERT(false); }; template< typename Type > inline void glRect ( Type, Type, Type, Type ) { STATIC_ASSERT(false); }; template< typename Type > inline void glMaterial ( GLenum face, GLenum pname, Type param ){ STATIC_ASSERT(false); }; template< typename Type > inline void glMaterialv ( GLenum face, GLenum pname, Type param ){ STATIC_ASSERT(false); }; template< typename Type > inline void glMultMatrix (const Type*){ STATIC_ASSERT(false); }; #define LDOUBL long double /// Template Specializations template< > inline void glMultMatrix(const double* m){glMultMatrixd(m); }; template< > inline void glMultMatrix(const LDOUBL* m){double mm[16]; for(int i=0;i<16;i++)mm[i]=(double)m[i]; glMultMatrixd(mm);}; template< > inline void glRotate< double > (double angle,double x,double y, double z ) { glRotated(angle,x,y,z); }; template< > inline void glRotate< LDOUBL > (LDOUBL angle, LDOUBL x, LDOUBL y, LDOUBL z ) { glRotated((double)angle,(double)x,(double)y,(double)z); }; template< > inline void glRotate< float > (float angle,float x,float y, float z ) { glRotatef(angle,x,y,z); }; template< > inline void glScale< double > ( double x,double y, double z ) { glScaled(x,y,z); }; template< > inline void glScale< LDOUBL > ( LDOUBL x,LDOUBL y, LDOUBL z ) { glScaled(x,y,z); }; template< > inline void glScale< float > ( float x,float y,float z ) { glScalef(x,y,z); }; template< > inline void glScalev< Vector3r > ( const Vector3r v ) { glScaled(v[0],v[1],v[2]);}; template< > inline void glTranslate< double > ( double x,double y, double z ) { glTranslated(x,y,z); }; template< > inline void glTranslate< LDOUBL > ( LDOUBL x, LDOUBL y, LDOUBL z ) { glTranslated(x,y,z); }; template< > inline void glTranslate< float > ( float x,float y,float z ) { glTranslatef(x,y,z); }; template< > inline void glTranslatev< Vector3r > ( const Vector3r v ) { glTranslated(v[0],v[1],v[2]);}; template< > inline void glVertex2< double > ( double x,double y ) { glVertex2d(x,y); }; template< > inline void glVertex2< LDOUBL > ( LDOUBL x, LDOUBL y ) { glVertex2d((double)x,(double)y); }; template< > inline void glVertex2< float > ( float x,float y ) { glVertex2f(x,y); }; template< > inline void glVertex2< int > ( int x,int y ) { glVertex2i(x,y); }; template< > inline void glVertex3< double > ( double x,double y, double z ) { glVertex3d(x,y,z); }; template< > inline void glVertex3< LDOUBL > ( LDOUBL x, LDOUBL y, LDOUBL z ) { glVertex3d((double)x,(double)y,(double)z); }; template< > inline void glVertex3< float > ( float x,float y,float z ) { glVertex3f(x,y,z); }; template< > inline void glVertex3< int > ( int x, int y, int z ) { glVertex3i(x,y,z); }; template< > inline void glVertex4< double > ( double x,double y,double z, double w ){ glVertex4d(x,y,z,w); }; template< > inline void glVertex4< LDOUBL > ( LDOUBL x, LDOUBL y, LDOUBL z, LDOUBL w ){ glVertex4d((double)x,(double)y,(double)z,(double)w); }; template< > inline void glVertex4< float > ( float x,float y,float z, float w ) { glVertex4f(x,y,z,w); }; template< > inline void glVertex4< int > ( int x,int y,int z, int w ) { glVertex4i(x,y,z,w); }; // :%s/\(void \)\(gl[A-Z,a-z,0-9]\+\)\(f\)( GLfloat \([a-z]\), GLfloat \([a-z]\), GLfloat \([a-z]\) );/template< > inline \1\2< float > ( float \4,float \5,float \6 ) { \2\3(\4,\5,\6); };/ template< > inline void glVertex2v< Vector3r > ( const Vector3r v ) { glVertex2dv((double*)&v); }; template< > inline void glVertex2v< Vector3i > ( const Vector3i v ) { glVertex2iv((int*)&v); }; template< > inline void glVertex3v< Vector3r > ( const Vector3r v ) { glVertex3dv((double*)&v); }; template< > inline void glVertex3v< Vector3i > ( const Vector3i v ) { glVertex3iv((int*)&v); }; template< > inline void glVertex4v< Vector3r > ( const Vector3r v ) { glVertex4dv((double*)&v); }; template< > inline void glVertex4v< Vector3i > ( const Vector3i v ) { glVertex4iv((int*)&v); }; template< > inline void glNormal3< double > (double nx,double ny,double nz ) { glNormal3d(nx,ny,nz); }; template< > inline void glNormal3< LDOUBL > ( LDOUBL nx, LDOUBL ny, LDOUBL nz ) { glNormal3d((double)nx,(double)ny,(double)nz); }; template< > inline void glNormal3< int > (int nx,int ny,int nz ) { glNormal3i(nx,ny,nz); }; template< > inline void glNormal3v< Vector3r > ( const Vector3r v ) { glNormal3dv((double*)&v); }; template< > inline void glNormal3v< Vector3i > ( const Vector3i v ) { glNormal3iv((int*)&v); }; template< > inline void glIndex< double > ( double c ) { glIndexd(c); }; template< > inline void glIndex< LDOUBL > ( LDOUBL c ) { glIndexd((double)c); }; template< > inline void glIndex< float > ( float c ) { glIndexf(c); }; template< > inline void glIndex< int > ( int c ) { glIndexi(c); }; template< > inline void glIndex< unsigned char > ( unsigned char c ) { glIndexub(c); }; template< > inline void glIndexv ( const Vector3r c) { glIndexdv((double*)&c); } template< > inline void glIndexv ( const Vector3i c) { glIndexiv((int*)&c); } template< > inline void glColor3< double > (double red,double green,double blue ) { glColor3d(red,green,blue); }; template< > inline void glColor3< LDOUBL > ( LDOUBL red, LDOUBL green, LDOUBL blue ) { glColor3d((double)red,(double)green,(double)blue); }; template< > inline void glColor3< float > (float red,float green,float blue ) { glColor3f(red,green,blue); }; template< > inline void glColor3< int > (int red,int green,int blue ) { glColor3i(red,green,blue); }; template< > inline void glColor4< double > (double red,double green,double blue, double alpha ) { glColor4d(red,green,blue,alpha); }; template< > inline void glColor4< LDOUBL > (LDOUBL red,LDOUBL green,LDOUBL blue, LDOUBL alpha ) { glColor4d((double)red,(double)green,(double)blue,(double)alpha); }; template< > inline void glColor4< float > (float red,float green,float blue, float alpha ) { glColor4f(red,green,blue,alpha); }; template< > inline void glColor4< int > (int red,int green,int blue, int alpha ) { glColor4i(red,green,blue,alpha); }; template< > inline void glColor3v< Vector3r > ( const Vector3r v ) { glColor3dv((double*)&v); }; template< > inline void glColor3v< Vector3i > ( const Vector3i v ) { glColor3iv((int*)&v); }; template< > inline void glColor4v< Vector3r > ( const Vector3r v ) { glColor4dv((double*)&v); }; template< > inline void glColor4v< Vector3i > ( const Vector3i v ) { glColor4iv((int*)&v); }; template< > inline void glTexCoord1< double > ( double s ) { glTexCoord1d(s); }; template< > inline void glTexCoord1< LDOUBL > ( LDOUBL s ) { glTexCoord1d((double)s); }; template< > inline void glTexCoord1< float > ( float s ) { glTexCoord1f(s); }; template< > inline void glTexCoord1< int > ( int s ) { glTexCoord1i(s); }; template< > inline void glTexCoord2< double > ( double s,double t ) { glTexCoord2d(s,t); }; template< > inline void glTexCoord2< LDOUBL > ( LDOUBL s,LDOUBL t ) { glTexCoord2d((double)s,(double)t); }; template< > inline void glTexCoord2< float > ( float s,float t ) { glTexCoord2f(s,t); }; template< > inline void glTexCoord2< int > ( int s,int t ) { glTexCoord2i(s,t); }; template< > inline void glTexCoord3< double > ( double s,double t, double r ) { glTexCoord3d(s,t,r); }; template< > inline void glTexCoord3< LDOUBL > ( LDOUBL s,LDOUBL t, LDOUBL r ) { glTexCoord3d((double)s,(double)t,(double)r); }; template< > inline void glTexCoord3< float > ( float s,float t,float r ) { glTexCoord3f(s,t,r); }; template< > inline void glTexCoord3< int > ( int s, int t, int r ) { glTexCoord3i(s,t,r); }; template< > inline void glTexCoord4< double > (double s,double t,double r, double q ) { glTexCoord4d(s,t,r,q); }; template< > inline void glTexCoord4< LDOUBL > (LDOUBL s,LDOUBL t,LDOUBL r, LDOUBL q ) { glTexCoord4d((double)s,(double)t,(double)r,(double)q); }; template< > inline void glTexCoord4< float > (float s,float t,float r, float q ) { glTexCoord4f(s,t,r,q); }; template< > inline void glTexCoord4< int > (int s,int t,int r, int q ) { glTexCoord4i(s,t,r,q); }; template< > inline void glTexCoord1v< Vector3r > ( const Vector3r v ) { glTexCoord1dv((double*)&v); }; template< > inline void glTexCoord1v< Vector3i > ( const Vector3i v ) { glTexCoord1iv((int*)&v); }; template< > inline void glTexCoord2v< Vector3r > ( const Vector3r v ) { glTexCoord2dv((double*)&v); }; template< > inline void glTexCoord2v< Vector3i > ( const Vector3i v ) { glTexCoord2iv((int*)&v); }; template< > inline void glTexCoord3v< Vector3r > ( const Vector3r v ) { glTexCoord3dv((double*)&v); }; template< > inline void glTexCoord3v< Vector3i > ( const Vector3i v ) { glTexCoord3iv((int*)&v); }; template< > inline void glTexCoord4v< Vector3r > ( const Vector3r v ) { glTexCoord4dv((double*)&v); }; template< > inline void glTexCoord4v< Vector3i > ( const Vector3i v ) { glTexCoord4iv((int*)&v); }; template< > inline void glRasterPos2< double > ( double x,double y ) { glRasterPos2d(x,y); }; template< > inline void glRasterPos2< LDOUBL > ( LDOUBL x,LDOUBL y ) { glRasterPos2d((double)x,(double)y); }; template< > inline void glRasterPos2< float > ( float x,float y ) { glRasterPos2f(x,y); }; template< > inline void glRasterPos2< int > ( int x,int y ) { glRasterPos2i(x,y); }; template< > inline void glRasterPos3< double > ( double x,double y, double z ) { glRasterPos3d(x,y,z); }; template< > inline void glRasterPos3< LDOUBL > ( LDOUBL x,LDOUBL y, LDOUBL z ) { glRasterPos3d((double)x,(double)y,(double)z); }; template< > inline void glRasterPos3< float > ( float x,float y,float z ) { glRasterPos3f(x,y,z); }; template< > inline void glRasterPos3< int > ( int x, int y, int z ) { glRasterPos3i(x,y,z); }; template< > inline void glRasterPos4< double > (double x,double y,double z, double w ) { glRasterPos4d(x,y,z,w); }; template< > inline void glRasterPos4< LDOUBL > (LDOUBL x,LDOUBL y,LDOUBL z, LDOUBL w ) { glRasterPos4d((double)x,(double)y,(double)z,(double)w); }; template< > inline void glRasterPos4< float > (float x,float y,float z, float w ) { glRasterPos4f(x,y,z,w); }; template< > inline void glRasterPos4< int > (int x,int y,int z, int w ) { glRasterPos4i(x,y,z,w); }; template< > inline void glRasterPos2v< Vector3r > ( const Vector3r v ) { glRasterPos2dv((double*)&v); }; template< > inline void glRasterPos2v< Vector3i > ( const Vector3i v ) { glRasterPos2iv((int*)&v); }; // :%s/\(void \)\(gl[A-Z,a-z,0-9]\+\)\(us\)\(v\)( const GLushort \*\(v\) );/template< > inline \1\2\4< Vector3 > ( const Vector3 \5 ) { \2\3\4(\5); };/ template< > inline void glRasterPos3v< Vector3r > ( const Vector3r v ) { glRasterPos3dv((double*)&v); }; template< > inline void glRasterPos3v< Vector3i > ( const Vector3i v ) { glRasterPos3iv((int*)&v); }; template< > inline void glRasterPos4v< Vector3r > ( const Vector3r v ) { glRasterPos4dv((double*)&v); }; template< > inline void glRasterPos4v< Vector3i > ( const Vector3i v ) { glRasterPos4iv((int*)&v); }; template< > inline void glRect< double > (double x1,double y1,double x2, double y2 ) { glRectd(x1,y1,x2,y2); }; template< > inline void glRect< LDOUBL > (LDOUBL x1,LDOUBL y1,LDOUBL x2, LDOUBL y2 ) { glRectd((double)x1,(double)y1,(double)x2,(double)y2); }; template< > inline void glRect< float > (float x1,float y1,float x2,float y2 ) { glRectf(x1,y1,x2,y2); }; template< > inline void glRect< int > (int x1,int y1,int x2, int y2 ) { glRecti(x1,y1,x2,y2); }; template< > inline void glMaterial< float > ( GLenum face, GLenum pname, float param ) { glMaterialf(face,pname,param); }; template< > inline void glMaterial< double > ( GLenum face, GLenum pname, double param ) { glMaterialf(face,pname,param); }; template< > inline void glMaterial< int > ( GLenum face, GLenum pname, int param ) { glMateriali(face,pname,param); }; template< > inline void glMaterialv< Vector3r > ( GLenum face, GLenum pname, const Vector3r params ) { const GLfloat _p[3]={(float) params[0], (float) params[1], (float) params[2]}; glMaterialfv(face,pname,_p); }; template< > inline void glMaterialv< Vector3i > ( GLenum face, GLenum pname, const Vector3i params ) { glMaterialiv(face,pname,(int*)¶ms); }; template< typename Type > inline void glOneWire(Type & t, unsigned int a, unsigned int b) { glVertex3v(t->v[a]); glVertex3v(t->v[b]); } template< typename Type > inline void glOneFace(Type & t, unsigned int a, unsigned int b, unsigned int c) { const Vector3r center = (t->v[0]+t->v[1]+t->v[2]+t->v[3])*.25; Vector3r n=(t->v[b]-t->v[a]).cross(t->v[c]-t->v[a]); n.normalize(); const Vector3r faceCenter=(t->v[a]+t->v[b]+t->v[c])/3.; if((faceCenter-center).dot(n)<0) n=-n; glNormal3v(n); glVertex3v(t->v[a]); glVertex3v(t->v[b]); glVertex3v(t->v[c]); } #undef LDOUBL trunk-2018.02b/lib/pyutil/000077500000000000000000000000001324306050200152625ustar00rootroot00000000000000trunk-2018.02b/lib/pyutil/README000066400000000000000000000001231324306050200161360ustar00rootroot00000000000000numpy_boost.cpp: http://code.google.com/p/numpy-boost/source/checkout (svn rev 9) trunk-2018.02b/lib/pyutil/doc_opts.hpp000066400000000000000000000004231324306050200176040ustar00rootroot00000000000000#pragma once // macro to set the same docstring generation options in all modules // disable_cpp_signatures apparently appeared after 1.35 or 1.34 #define YADE_SET_DOCSTRING_OPTS boost::python::docstring_options docopt; docopt.enable_all(); docopt.disable_cpp_signatures(); trunk-2018.02b/lib/pyutil/gil.cpp000066400000000000000000000001731324306050200165420ustar00rootroot00000000000000#include void pyRunString(const std::string& cmd){ gilLock lock; PyRun_SimpleString(cmd.c_str()); }; trunk-2018.02b/lib/pyutil/gil.hpp000066400000000000000000000006411324306050200165470ustar00rootroot00000000000000// 2009 © Václav Šmilauer #pragma once #include #include //! class (scoped lock) managing python's Global Interpreter Lock (gil) class gilLock{ PyGILState_STATE state; public: gilLock(){ state=PyGILState_Ensure(); } ~gilLock(){ PyGILState_Release(state); } }; //! run string as python command; locks & unlocks GIL automatically void pyRunString(const std::string& cmd); trunk-2018.02b/lib/pyutil/numpy_boost.hpp000066400000000000000000000172531324306050200203610ustar00rootroot00000000000000/* Copyright (c) 2012, Michael Droettboom All rights reserved. Licensed under the BSD license. 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. * The names of its contributors may not 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 OWNER 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 __NUMPY_BOOST_HPP__ #define __NUMPY_BOOST_HPP__ #include #include #include #include #include #include /* numpy_type_map Provides a mapping from C++ datatypes to Numpy type numbers. */ namespace detail { template class numpy_type_map { public: static const int typenum; }; template<> const int numpy_type_map::typenum = NPY_FLOAT; template<> const int numpy_type_map >::typenum = NPY_CFLOAT; template<> const int numpy_type_map::typenum = NPY_DOUBLE; template<> const int numpy_type_map >::typenum = NPY_CDOUBLE; template<> const int numpy_type_map::typenum = NPY_LONGDOUBLE; template<> const int numpy_type_map >::typenum = NPY_CLONGDOUBLE; template<> const int numpy_type_map::typenum = NPY_INT8; template<> const int numpy_type_map::typenum = NPY_UINT8; template<> const int numpy_type_map::typenum = NPY_INT16; template<> const int numpy_type_map::typenum = NPY_UINT16; template<> const int numpy_type_map::typenum = NPY_INT32; template<> const int numpy_type_map::typenum = NPY_UINT32; template<> const int numpy_type_map::typenum = NPY_INT64; template<> const int numpy_type_map::typenum = NPY_UINT64; } class python_exception : public std::exception { }; /* An array that acts like a boost::multi_array, but is backed by the memory of a Numpy array. Provides nice C++ interface to a Numpy array without any copying of the data. It may be constructed one of two ways: 1) With an existing Numpy array. The boost::multi_array will then have the data, dimensions and strides of the Numpy array. 2) With a list of dimensions, in which case a new contiguous Numpy array will be created and the new boost::array will point to it. */ template class numpy_boost : public boost::multi_array_ref { public: typedef numpy_boost self_type; typedef boost::multi_array_ref super; typedef typename super::size_type size_type; typedef T* TPtr; private: PyArrayObject* array; void init_from_array(PyArrayObject* a) throw() { /* Upon calling init_from_array, a should already have been incref'd for ownership by this object. */ /* Store a reference to the Numpy array so we can DECREF it in the destructor. */ array = a; /* Point the boost::array at the Numpy array data. We don't need to worry about free'ing this pointer, because it will always point to memory allocated as part of the data of a Numpy array. That memory is managed by Python reference counting. */ super::base_ = (TPtr)PyArray_DATA(a); /* Set the storage order. It would seem like we would want to choose C or Fortran ordering here based on the flags in the Numpy array. However, those flags are purely informational, the actually information about storage order is recorded in the strides. */ super::storage_ = boost::c_storage_order(); /* Copy the dimensions from the Numpy array to the boost::array. */ boost::detail::multi_array::copy_n(PyArray_DIMS(a), NDims, super::extent_list_.begin()); /* Copy the strides from the Numpy array to the boost::array. Numpy strides are in bytes. boost::array strides are in elements, so we need to divide. */ for (size_t i = 0; i < NDims; ++i) { super::stride_list_[i] = PyArray_STRIDE(a, i) / sizeof(T); } /* index_base_list_ stores the bases of the indices in each dimension. Since we want C-style and Numpy-style zero-based indexing, just fill it with zeros. */ std::fill_n(super::index_base_list_.begin(), NDims, 0); /* We don't want any additional offsets. If they exist, Numpy has already handled that for us when calculating the data pointer and strides. */ super::origin_offset_ = 0; super::directional_offset_ = 0; /* Calculate the number of elements. This has nothing to do with memory layout. */ super::num_elements_ = std::accumulate(super::extent_list_.begin(), super::extent_list_.end(), size_type(1), std::multiplies()); } public: /* Construct from an existing Numpy array */ numpy_boost(PyObject* obj) throw () : super(NULL, std::vector(NDims, 0)), array(NULL) { PyArrayObject* a; a = (PyArrayObject*)PyArray_FromObject( obj, detail::numpy_type_map::typenum, NDims, NDims); if (a == NULL) { throw boost::python::error_already_set(); } init_from_array(a); } /* Copy constructor */ numpy_boost(const self_type &other) throw() : super(NULL, std::vector(NDims, 0)), array(NULL) { Py_INCREF(other.array); init_from_array(other.array); } /* Construct a new array based on the given dimensions */ template explicit numpy_boost(const ExtentsList& extents) throw () : super(NULL, std::vector(NDims, 0)), array(NULL) { npy_intp shape[NDims]; PyArrayObject* a; boost::detail::multi_array::copy_n(extents, NDims, shape); a = (PyArrayObject*)PyArray_SimpleNew( NDims, shape, detail::numpy_type_map::typenum); if (a == NULL) { throw boost::python::error_already_set(); } init_from_array(a); } /* Destructor */ ~numpy_boost() { /* Dereference the numpy array. */ Py_XDECREF(array); } /* Assignment operator */ void operator=(const self_type &other) throw() { Py_INCREF(other.array); Py_DECREF(array); init_from_array(other.array); } /* Return the underlying Numpy array object. [Borrowed reference] */ PyObject* py_ptr() const throw() { return (PyObject*)array; } }; #endif trunk-2018.02b/lib/pyutil/raw_constructor.hpp000066400000000000000000000017701324306050200212360ustar00rootroot00000000000000#pragma once #include // many thanks to http://markmail.org/message/s4ksg6nfspw2wxwd namespace boost { namespace python { namespace detail { template struct raw_constructor_dispatcher { raw_constructor_dispatcher(F f): f(make_constructor(f)) {} PyObject* operator()(PyObject* args, PyObject* keywords) { borrowed_reference_t* ra = borrowed_reference(args); object a(ra); return incref(object(f(object(a[0]),object(a.slice(1,len(a))),keywords ? dict(borrowed_reference(keywords)) : dict())).ptr() ); } private: object f; }; } template object raw_constructor(F f, std::size_t min_args = 0) { return detail::make_raw_function( objects::py_function(detail::raw_constructor_dispatcher(f), mpl::vector2(), min_args+1, (std::numeric_limits::max)())); } } } trunk-2018.02b/lib/serialization/000077500000000000000000000000001324306050200166115ustar00rootroot00000000000000trunk-2018.02b/lib/serialization/ObjectIO.hpp000066400000000000000000000063111324306050200207610ustar00rootroot00000000000000// 2010 © Václav Šmilauer #pragma once #include #include #include #include #include #include #include #include namespace yade{ /* Utility template functions for (de)serializing objects using boost::serialization from/to streams or files. Includes boost::math::nonfinite_num_{put,get} for gracefully handling nan's and inf's. */ struct ObjectIO{ // tell whether given filename looks like XML static bool isXmlFilename(const std::string f){ return boost::algorithm::ends_with(f,".xml") || boost::algorithm::ends_with(f,".xml.bz2") || boost::algorithm::ends_with(f,".xml.gz"); } // save to given stream and archive format template static void save(std::ostream& ofs, const string& objectTag, T& object){ std::locale default_locale(std::locale::classic(), new boost::archive::codecvt_null); std::locale locale2(default_locale, new boost::math::nonfinite_num_put); ofs.imbue(locale2); oarchive oa(ofs,boost::archive::no_codecvt); oa << boost::serialization::make_nvp(objectTag.c_str(),object); ofs.flush(); } // load from given stream and archive format template static void load(std::istream& ifs, const string& objectTag, T& object){ std::locale default_locale(std::locale::classic(), new boost::archive::codecvt_null); std::locale locale2(default_locale, new boost::math::nonfinite_num_get); ifs.imbue(locale2); iarchive ia(ifs,boost::archive::no_codecvt); ia >> boost::serialization::make_nvp(objectTag.c_str(),object); } // save to given file, guessing compression and XML/binary from extension template static void save(const string fileName, const string& objectTag, T& object){ boost::iostreams::filtering_ostream out; if(boost::algorithm::ends_with(fileName,".bz2")) out.push(boost::iostreams::bzip2_compressor()); if(boost::algorithm::ends_with(fileName,".gz")) out.push(boost::iostreams::gzip_compressor()); out.push(boost::iostreams::file_sink(fileName)); if(!out.good()) throw std::runtime_error("Error opening file "+fileName+" for writing."); if(isXmlFilename(fileName)) save(out,objectTag,object); else save(out,objectTag,object); } // load from given file, guessing compression and XML/binary from extension template static void load(const string& fileName, const string& objectTag, T& object){ boost::iostreams::filtering_istream in; if(boost::algorithm::ends_with(fileName,".bz2")) in.push(boost::iostreams::bzip2_decompressor()); if(boost::algorithm::ends_with(fileName,".gz")) in.push(boost::iostreams::gzip_decompressor()); in.push(boost::iostreams::file_source(fileName)); if(!in.good()) throw std::runtime_error("Error opening file "+fileName+" for reading."); if(isXmlFilename(fileName)) load(in,objectTag,object); else load(in,objectTag,object); } }; } trunk-2018.02b/lib/serialization/Serializable.cpp000066400000000000000000000050031324306050200217210ustar00rootroot00000000000000/************************************************************************* * Copyright (C) 2004 by Olivier Galizzi * * olivier.galizzi@imag.fr * * Copyright (C) 2004 by Janek Kozicki * * cosurgi@berlios.de * * * * This program is free software; it is licensed under the terms of the * * GNU General Public License v2 or later. See file LICENSE for details. * *************************************************************************/ #include "Serializable.hpp" void Serializable::pyRegisterClass(boost::python::object _scope) { checkPyClassRegistersItself("Serializable"); boost::python::scope thisScope(_scope); boost::python::class_, boost::noncopyable >("Serializable") .def("__str__",&Serializable::pyStr).def("__repr__",&Serializable::pyStr) .def("dict",&Serializable::pyDict,"Return dictionary of attributes.") .def("updateAttrs",&Serializable::pyUpdateAttrs,"Update object attributes from given dictionary") /* boost::python pickling support, as per http://www.boost.org/doc/libs/1_42_0/libs/python/doc/v2/pickle.html */ .def("__getstate__",&Serializable::pyDict).def("__setstate__",&Serializable::pyUpdateAttrs) .add_property("__safe_for_unpickling__",&Serializable::getClassName,"just define the attr, return some bogus data") .add_property("__getstate_manages_dict__",&Serializable::getClassName,"just define the attr, return some bogus data") // constructor with dictionary of attributes .def("__init__",boost::python::raw_constructor(Serializable_ctor_kwAttrs)) // comparison operators .def(boost::python::self == boost::python::self) .def(boost::python::self != boost::python::self) ; } void Serializable::checkPyClassRegistersItself(const std::string& thisClassName) const { if(getClassName()!=thisClassName) throw std::logic_error(("Class "+getClassName()+" does not register with YADE_CLASS_BASE_DOC_ATTR*, would not be accessible from python.").c_str()); } void Serializable::pyUpdateAttrs(const boost::python::dict& d){ boost::python::list l=d.items(); size_t ll=boost::python::len(l); if(ll==0) return; for(size_t i=0; i(l[i]); string key=boost::python::extract(t[0]); pySetAttr(key,t[1]); } callPostLoad(); } trunk-2018.02b/lib/serialization/Serializable.hpp000066400000000000000000000527211324306050200217370ustar00rootroot00000000000000/************************************************************************* * Copyright (C) 2004 by Olivier Galizzi * * olivier.galizzi@imag.fr * * Copyright (C) 2004 by Janek Kozicki * * cosurgi@berlios.de * * * * This program is free software; it is licensed under the terms of the * * GNU General Public License v2 or later. See file LICENSE for details. * *************************************************************************/ #pragma once #include #include #include #include #include template void preLoad(T&){}; template void postLoad(T& obj){}; template void preSave(T&){}; template void postSave(T&){}; // attribute flags namespace yade{ namespace Attr{ // keep in sync with py/wrapper/yadeWrapper.cpp ! enum flags { noSave=1, readonly=2, triggerPostLoad=4, hidden=8, noResize=16 }; }; }; using namespace yade; // see: // https://bugs.launchpad.net/yade/+bug/539562 // http://www.boost.org/doc/libs/1_42_0/libs/python/doc/v2/faq.html#topythonconversionfailed // for reason why the original def_readwrite will not work: // #define _PYATTR_DEF(x,thisClass,z) .def_readwrite(BOOST_PP_STRINGIZE(BOOST_PP_TUPLE_ELEM(2,0,z)),&thisClass::BOOST_PP_TUPLE_ELEM(2,0,z),BOOST_PP_TUPLE_ELEM(2,1,z)) #define _PYATTR_DEF(x,thisClass,z) _DEF_READWRITE_CUSTOM(thisClass,z) // // return reference for vector and matrix types to allow things like // O.bodies.pos[1].state.vel[2]=0 // returning value would only change copy of velocity, without propagating back to the original // // see http://www.mail-archive.com/yade-dev@lists.launchpad.net/msg03406.html // // note that for sequences (like vector<> etc), values are returned; but in case of // vector of shared_ptr's, things inside are still shared, so // O.engines[2].gravity=(0,0,9.33) will work // // OTOH got sequences of non-shared types, it sill (silently) fail: // f=Facet(); f.vertices[1][0]=4 // // see http://www.boost.org/doc/libs/1_42_0/libs/type_traits/doc/html/boost_typetraits/background.html // about how this works namespace yade{ // by default, do not return reference; return value instead template struct py_wrap_ref: public boost::false_type{}; // specialize for types that should be returned as references template<> struct py_wrap_ref: public boost::true_type{}; template<> struct py_wrap_ref: public boost::true_type{}; template<> struct py_wrap_ref: public boost::true_type{}; template<> struct py_wrap_ref: public boost::true_type{}; template<> struct py_wrap_ref: public boost::true_type{}; template<> struct py_wrap_ref: public boost::true_type{}; }; // ADL only works within the same namespace // this duplicate is for classes that are not in yade:: namespace (yet) template void make_setter_postLoad(C& instance, const T& val){ instance.*A=val; instance.callPostLoad(); } #define _DEF_READWRITE_BY_VALUE(thisClass,attr,doc)\ add_property(\ /*attr name*/ BOOST_PP_STRINGIZE(attr),\ /*read access*/ boost::python::make_getter(&thisClass::attr,boost::python::return_value_policy()),\ /*write access*/ boost::python::make_setter(&thisClass::attr,boost::python::return_value_policy()),\ /*docstring*/ doc) // not sure if this is correct: the getter works by value, the setter by reference (the default)...? #define _DEF_READWRITE_BY_VALUE_POSTLOAD(thisClass,attr,doc)\ add_property(\ /*attr name*/ BOOST_PP_STRINGIZE(attr),\ /*read access*/ boost::python::make_getter(&thisClass::attr,boost::python::return_value_policy()),\ /*write access*/ make_setter_postLoad,\ /*docstring*/ doc) #define _DEF_READONLY_BY_VALUE(thisClass,attr,doc)\ add_property(\ /*attr name*/ BOOST_PP_STRINGIZE(attr),\ /*read access*/ boost::python::make_getter(&thisClass::attr,boost::python::return_value_policy()),\ /*docstring*/ doc) /* Huh, add_static_property does not support doc argument (add_property does); if so, use add_property for now at least... */ #define _DEF_READWRITE_BY_VALUE_STATIC(thisClass,attr,doc)_DEF_READWRITE_BY_VALUE(thisClass,attr,doc) // the conditional yade::py_wrap_ref should be eliminated by compiler at compile-time, as it depends only on types, not their values // most of this could be written with templates, including flags (ints can be template args) #define _DEF_READWRITE_CUSTOM(thisClass,attr)\ if(!(_ATTR_FLG(attr) & yade::Attr::hidden)){\ bool _ro(_ATTR_FLG(attr) & Attr::readonly),\ _post(_ATTR_FLG(attr) & Attr::triggerPostLoad),\ _ref(yade::py_wrap_ref::value);\ std::string docStr(_ATTR_DOC(attr));\ docStr+=" :yattrflags:`"+boost::lexical_cast(_ATTR_FLG(attr))+"` "; \ \ if ( _ref && !_ro && !_post) _classObj.def_readwrite(_ATTR_NAM_STR(attr),&thisClass::_ATTR_NAM(attr),docStr.c_str()); \ else if ( _ref && !_ro && _post) _classObj.add_property(_ATTR_NAM_STR(attr),boost::python::make_getter(&thisClass::_ATTR_NAM(attr)),make_setter_postLoad,docStr.c_str()); \ else if ( _ref && _ro) _classObj.def_readonly(_ATTR_NAM_STR(attr),&thisClass::_ATTR_NAM(attr),docStr.c_str()); \ else if (!_ref && !_ro && !_post) _classObj._DEF_READWRITE_BY_VALUE(thisClass,_ATTR_NAM(attr),docStr.c_str()); \ else if (!_ref && !_ro && _post) _classObj._DEF_READWRITE_BY_VALUE_POSTLOAD(thisClass,_ATTR_NAM(attr),docStr.c_str()); \ else if (!_ref && _ro) _classObj._DEF_READONLY_BY_VALUE(thisClass,_ATTR_NAM(attr),docStr.c_str()); \ if(_ro && _post) std::cerr<<"WARN: " BOOST_PP_STRINGIZE(thisClass) "::" _ATTR_NAM_STR(attr) " with the yade::Attr::readonly flag also uselessly sets yade::Attr::triggerPostLoad."<(value); \ return; \ } #define _PYATTR_DEPREC_DEF(x,thisClass,z) \ .add_property(BOOST_PP_STRINGIZE(_DEPREC_OLDNAME(z)),\ &thisClass::BOOST_PP_CAT(_getDeprec_,_DEPREC_OLDNAME(z)),\ &thisClass::BOOST_PP_CAT(_setDeprec_,_DEPREC_OLDNAME(z)),\ "|ydeprecated| alias for :yref:`" BOOST_PP_STRINGIZE(_DEPREC_NEWNAME(z)) \ "<" BOOST_PP_STRINGIZE(thisClass) "." \ BOOST_PP_STRINGIZE(_DEPREC_NEWNAME(z)) ">` (" _DEPREC_COMMENT(z) ")") #define _PYHASKEY_ATTR_DEPREC(x,thisClass,z) \ if(key==BOOST_PP_STRINGIZE(_DEPREC_OLDNAME(z))) return true; /* accessors functions ussing warning */ #define _ACCESS_DEPREC(x,thisClass,z) /*getter*/ decltype(_DEPREC_NEWNAME(z)) BOOST_PP_CAT(_getDeprec_,_DEPREC_OLDNAME(z))(){_DEPREC_WARN(thisClass,z); return _DEPREC_NEWNAME(z); } /*setter*/ void BOOST_PP_CAT(_setDeprec_,_DEPREC_OLDNAME(z))(const decltype(_DEPREC_NEWNAME(z))& val){_DEPREC_WARN(thisClass,z); _DEPREC_NEWNAME(z)=val; } // loop bodies for attribute access #define _PYGET_ATTR(x,y,z) if(key==_ATTR_NAM_STR(z)) return boost::python::object(_ATTR_NAM(z)); #define _PYSET_ATTR(x,y,z) if(key==_ATTR_NAM_STR(z)) { _ATTR_NAM(z)=boost::python::extract(value); return; } #define _PYKEYS_ATTR(x,y,z) ret.append(_ATTR_NAM_STR(z)); #define _PYHASKEY_ATTR(x,y,z) if(key==_ATTR_NAM_STR(z)) return true; #define _PYDICT_ATTR(x,y,z) if(!(_ATTR_FLG(z) & yade::Attr::hidden)) ret[_ATTR_NAM_STR(z)]=boost::python::object(_ATTR_NAM(z)); #define _REGISTER_BOOST_ATTRIBUTES_REPEAT(x,y,z) if((_ATTR_FLG(z) & yade::Attr::noSave)==0) { ar & BOOST_SERIALIZATION_NVP(_ATTR_NAM(z)); } #define _REGISTER_BOOST_ATTRIBUTES(baseClass,attrs) \ friend class boost::serialization::access; \ private: template void serialize(ArchiveT & ar, unsigned int version){ \ ar & BOOST_SERIALIZATION_BASE_OBJECT_NVP(baseClass); \ /* with ADL, either the generic (empty) version above or baseClass::preLoad etc will be called (compile-time resolution) */ \ if(ArchiveT::is_loading::value) preLoad(*this); else preSave(*this); \ BOOST_PP_SEQ_FOR_EACH(_REGISTER_BOOST_ATTRIBUTES_REPEAT,~,attrs) \ if(ArchiveT::is_loading::value) postLoad(*this); else postSave(*this); \ } #define _REGISTER_ATTRIBUTES_DEPREC(thisClass,baseClass,attrs,deprec) _REGISTER_BOOST_ATTRIBUTES(baseClass,attrs) public: \ void pySetAttr(const std::string& key, const boost::python::object& value){BOOST_PP_SEQ_FOR_EACH(_PYSET_ATTR,~,attrs); BOOST_PP_SEQ_FOR_EACH(_PYSET_ATTR_DEPREC,thisClass,deprec); baseClass::pySetAttr(key,value); } \ /* list all attributes (except deprecated ones); could return boost::python::set instead*/ /* boost::python::list pyKeys() const { boost::python::list ret; BOOST_PP_SEQ_FOR_EACH(_PYKEYS_ATTR,~,attrs); ret.extend(baseClass::pyKeys()); return ret; } */ \ /* return dictionary of all acttributes and values; deprecated attributes omitted */ boost::python::dict pyDict() const { boost::python::dict ret; BOOST_PP_SEQ_FOR_EACH(_PYDICT_ATTR,~,attrs); ret.update(baseClass::pyDict()); return ret; } \ virtual void callPostLoad(void){ baseClass::callPostLoad(); postLoad(*this); } // print warning about deprecated attribute; thisClass is type name, not string #define _DEPREC_WARN(thisClass,deprec) std::cerr<<"WARN: "<,boost::python::bases,boost::noncopyable> _classObj(#thisClass,docString); _classObj.def("__init__",boost::python::raw_constructor(Serializable_ctor_kwAttrs)); BOOST_PP_SEQ_FOR_EACH(_PYATTR_DEF,thisClass,attrs); (void) _classObj BOOST_PP_SEQ_FOR_EACH(_PYATTR_DEPREC_DEF,thisClass,deprec); (void) _classObj extras ; } // use later: void must_use_both_YADE_CLASS_BASE_DOC_ATTRS_and_YADE_PLUGIN(); // #define YADE_CLASS_BASE_DOC_ATTRS_PY(thisClass,baseClass,docString,attrs,extras) YADE_CLASS_BASE_DOC_ATTRS_DEPREC_PY(thisClass,baseClass,docString,attrs,,extras) // return "type name;" (for declaration inside class body) #define _ATTR_DECL(x,y,z) _ATTR_TYP(z) _ATTR_NAM(z); // return name(default), (for initializers list); TRICKY: last one must not have the comma #define _ATTR_MAKE_INITIALIZER(x,maxIndex,i,z) BOOST_PP_TUPLE_ELEM(2,0,z)(BOOST_PP_TUPLE_ELEM(2,1,z)) BOOST_PP_COMMA_IF(BOOST_PP_NOT_EQUAL(maxIndex,i)) // attrDecl is (type,name,defaultValue,docString) #define _ATTR_MAKE_INIT_TUPLE(x,y,z) (( _ATTR_NAM(z),_ATTR_INI(z) )) #define _ATTR_NAME_ADD_DUMMY_FIELDS(x,y,z) ((/*type*/,z,/*default*/,/*flags*/,/*doc*/)) #define _STAT_NONSTAT_ATTR_PY(thisClass,attr,doc) _DEF_READWRITE_CUSTOM_STATIC(thisClass,attr,doc) /* _DEF_READWRITE_CUSTOM(thisClass,attr,doc) */ /* duplicate static and non-static attributes do not work (they apparently trigger to-python converter being added; for now, make then non-static, that's it. */ #define _STATATTR_PY(x,thisClass,z) _STAT_NONSTAT_ATTR_PY(thisClass,_ATTR_NAM(z),/*docstring*/ "|ystatic| :ydefault:`" _ATTR_INI_STR(z) "` :yattrtype:`" _ATTR_TYP_STR(z) "` " _ATTR_DOC(z)) #define _STATATTR_DECL(x,y,z) static _ATTR_TYP(z) _ATTR_NAM(z); #define _STATATTR_INITIALIZE(x,thisClass,z) thisClass::_ATTR_NAM(z)=_ATTR_INI(z); #define _STATATTR_MAKE_DOC(x,thisClass,z) ".. ystaticattr:: " BOOST_PP_STRINGIZE(thisClass) "." _ATTR_NAM_STR(z) "(=" _ATTR_INI_STR(z) ")" "\n\n\t" _ATTR_DOC(z) "\n\n" #define _STATCLASS_PY_REGISTER_CLASS(thisClass,baseClass,docString,attrs)\ virtual void pyRegisterClass(boost::python::object _scope) { checkPyClassRegistersItself(#thisClass); initSetStaticAttributesValue(); boost::python::scope thisScope(_scope); YADE_SET_DOCSTRING_OPTS; \ boost::python::class_,boost::python::bases,boost::noncopyable> _classObj(#thisClass,docString "\n\n" BOOST_PP_SEQ_FOR_EACH(_STATATTR_MAKE_DOC,thisClass,attrs) ); _classObj.def("__init__",boost::python::raw_constructor(Serializable_ctor_kwAttrs)); \ BOOST_PP_SEQ_FOR_EACH(_STATATTR_PY,thisClass,attrs); \ } #define _YADE_CLASS_PYCLASS_BASE_DOC_ATTRS_DEPREC_PY(thisClass,pyClassName,baseClass,docString,attrs,deprec,extras) \ _REGISTER_ATTRIBUTES_DEPREC(thisClass,baseClass,attrs,deprec) \ REGISTER_CLASS_AND_BASE(pyClassName,baseClass) \ /* accessors for deprecated attributes, with warnings */ BOOST_PP_SEQ_FOR_EACH(_ACCESS_DEPREC,thisClass,deprec) \ /* python class registration */ virtual void pyRegisterClass(boost::python::object _scope) { checkPyClassRegistersItself(#pyClassName); boost::python::scope thisScope(_scope); YADE_SET_DOCSTRING_OPTS; boost::python::class_,boost::python::bases,boost::noncopyable> _classObj(#pyClassName,docString); _classObj.def("__init__",boost::python::raw_constructor(Serializable_ctor_kwAttrs)); BOOST_PP_SEQ_FOR_EACH(_PYATTR_DEF,thisClass,attrs); (void) _classObj BOOST_PP_SEQ_FOR_EACH(_PYATTR_DEPREC_DEF,thisClass,deprec); (void) _classObj extras ; } /********************** USER MACROS START HERE ********************/ // attrs is (type,name,init-value,docstring) #define YADE_CLASS_BASE_DOC(klass,base,doc) YADE_CLASS_BASE_DOC_ATTRS_INIT_CTOR_PY(klass,base,doc,,,,) #define YADE_CLASS_BASE_DOC_ATTRS(klass,base,doc,attrs) YADE_CLASS_BASE_DOC_ATTRS_INIT_CTOR_PY(klass,base,doc,attrs,,,) #define YADE_CLASS_BASE_DOC_ATTRS_CTOR(klass,base,doc,attrs,ctor) YADE_CLASS_BASE_DOC_ATTRS_INIT_CTOR_PY(klass,base,doc,attrs,,ctor,) #define YADE_CLASS_BASE_DOC_ATTRS_CTOR_PY(klass,base,doc,attrs,ctor,py) YADE_CLASS_BASE_DOC_ATTRS_INIT_CTOR_PY(klass,base,doc,attrs,,ctor,py) #define YADE_CLASS_BASE_DOC_ATTRS_INIT_CTOR_PY(klass,base,doc,attrs,inits,ctor,py) YADE_CLASS_BASE_DOC_ATTRS_DEPREC_INIT_CTOR_PY(klass,base,doc,attrs,,inits,ctor,py) // the most general #define YADE_CLASS_BASE_DOC_ATTRS_DEPREC_INIT_CTOR_PY(thisClass,baseClass,docString,attrDecls,deprec,inits,ctor,extras) \ public: BOOST_PP_SEQ_FOR_EACH(_ATTR_DECL,~,attrDecls) /* attribute declarations */ \ thisClass() BOOST_PP_IF(BOOST_PP_SEQ_SIZE(inits attrDecls),:,) BOOST_PP_SEQ_FOR_EACH_I(_ATTR_MAKE_INITIALIZER,BOOST_PP_DEC(BOOST_PP_SEQ_SIZE(inits attrDecls)), inits BOOST_PP_SEQ_FOR_EACH(_ATTR_MAKE_INIT_TUPLE,~,attrDecls)) { ctor ; } /* ctor, with initialization of defaults */ \ _YADE_CLASS_BASE_DOC_ATTRS_DEPREC_PY(thisClass,baseClass,docString,BOOST_PP_SEQ_FOR_EACH(_ATTRS_EMBED_INI_TYP_IN_DOC,~,attrDecls),deprec,extras) // this one lets you give different class names in c++ and python, necessary for compatibility with c++ templates (else all instaces would have the same class name (ex. in FlowEngine.hpp) #define YADE_CLASS_PYCLASS_BASE_DOC_ATTRS_DEPREC_INIT_CTOR_PY(thisClass,pyClassName,baseClass,docString,attrDecls,deprec,inits,ctor,extras) \ public: BOOST_PP_SEQ_FOR_EACH(_ATTR_DECL,~,attrDecls) /* attribute declarations */ \ thisClass() BOOST_PP_IF(BOOST_PP_SEQ_SIZE(inits attrDecls),:,) BOOST_PP_SEQ_FOR_EACH_I(_ATTR_MAKE_INITIALIZER,BOOST_PP_DEC(BOOST_PP_SEQ_SIZE(inits attrDecls)), inits BOOST_PP_SEQ_FOR_EACH(_ATTR_MAKE_INIT_TUPLE,~,attrDecls)) { ctor ; } /* ctor, with initialization of defaults */ \ _YADE_CLASS_PYCLASS_BASE_DOC_ATTRS_DEPREC_PY(thisClass,pyClassName,baseClass,docString,BOOST_PP_SEQ_FOR_EACH(_ATTRS_EMBED_INI_TYP_IN_DOC,~,attrDecls),deprec,extras) #define REGISTER_SERIALIZABLE(name) REGISTER_FACTORABLE(name); BOOST_CLASS_EXPORT_KEY(name); // for static classes (Gl1 functors, for instance) #define YADE_CLASS_BASE_DOC_STATICATTRS(thisClass,baseClass,docString,attrs)\ public: BOOST_PP_SEQ_FOR_EACH(_STATATTR_DECL,~,attrs) /* attribute declarations */ \ /* no ctor */ \ REGISTER_CLASS_AND_BASE(thisClass,baseClass); \ _REGISTER_ATTRIBUTES_DEPREC(thisClass,baseClass,attrs,) \ /* called only at class registration, to set initial values; storage still has to be alocated in the cpp file! */ \ void initSetStaticAttributesValue(void){ BOOST_PP_SEQ_FOR_EACH(_STATATTR_INITIALIZE,thisClass,attrs); } \ _STATCLASS_PY_REGISTER_CLASS(thisClass,baseClass,docString,attrs) // used only in some exceptional cases, might disappear in the future #define REGISTER_ATTRIBUTES(baseClass,attrs) _REGISTER_ATTRIBUTES_DEPREC(_SOME_CLASS,baseClass,BOOST_PP_SEQ_FOR_EACH(_ATTR_NAME_ADD_DUMMY_FIELDS,~,attrs),) class Serializable: public Factorable { public: template void serialize(ArchiveT & ar, unsigned int version){ }; // lovely cast members like in eigen :-) template const DerivedT& cast() const { return *static_cast(this); } template DerivedT& cast(){ return *static_cast(this); } Serializable() {}; virtual ~Serializable() {}; // comparison of strong equality of 2 objects (by their address) bool operator==(const Serializable& other){ return this==&other; } bool operator!=(const Serializable& other){ return this!=&other; } void pyUpdateAttrs(const boost::python::dict& d); //static void pyUpdateAttrs(const shared_ptr&, const boost::python::dict& d); virtual void pySetAttr(const std::string& key, const boost::python::object& value){ PyErr_SetString(PyExc_AttributeError,(std::string("No such attribute: ")+key+".").c_str()); boost::python::throw_error_already_set(); }; //virtual boost::python::list pyKeys() const { return boost::python::list(); }; virtual boost::python::dict pyDict() const { return boost::python::dict(); } virtual void callPostLoad(void){ postLoad(*this); } // check whether the class registers itself or whether it calls virtual function of some base class; // that means that the class doesn't register itself properly virtual void checkPyClassRegistersItself(const std::string& thisClassName) const; // perform class registration; overridden in all classes virtual void pyRegisterClass(boost::python::object _scope); // perform any manipulation of arbitrary constructor arguments coming from python, manipulating them in-place; // the remainder is passed to the Serializable_ctor_kwAttrs of the respective class (note: args must be empty) virtual void pyHandleCustomCtorArgs(boost::python::tuple& args, boost::python::dict& kw){ return; } //! string representation of this object std::string pyStr() { return "<"+getClassName()+" instance at "+boost::lexical_cast(this)+">"; } REGISTER_CLASS_NAME(Serializable); REGISTER_BASE_CLASS_NAME(Factorable); }; // helper functions template shared_ptr Serializable_ctor_kwAttrs(boost::python::tuple& t, boost::python::dict& d){ shared_ptr instance; instance=shared_ptr(new T); instance->pyHandleCustomCtorArgs(t,d); // can change t and d in-place if(boost::python::len(t)>0) throw runtime_error("Zero (not "+boost::lexical_cast(boost::python::len(t))+") non-keyword constructor arguments required [in Serializable_ctor_kwAttrs; Serializable::pyHandleCustomCtorArgs might had changed it after your call]."); if(boost::python::len(d)>0){ instance->pyUpdateAttrs(d); instance->callPostLoad(); } return instance; } trunk-2018.02b/lib/smoothing/000077500000000000000000000000001324306050200157435ustar00rootroot00000000000000trunk-2018.02b/lib/smoothing/LinearInterpolate.hpp000066400000000000000000000022171324306050200220770ustar00rootroot00000000000000// 2008 © Václav Šmilauer #pragma once #include #include #include /* Linear interpolation function suitable only for sequential interpolation. * * Template parameter T must support: addition, subtraction, scalar multiplication. * * @param t "time" at which the interpolated variable should be evaluated. * @param tt "time" points at which values are given; must be increasing. * @param values values at "time" points specified by tt * @param pos holds lookup state * * @return value at "time" t; out of range: tt_last → value(t_last) */ template T linearInterpolate(const Real t, const std::vector& tt, const std::vector& values, size_t& pos){ assert(tt.size()==values.size()); if(t<=tt[0]){ pos=0; return values[0];} if(t>=*tt.rbegin()){ pos=tt.size()-2; return *values.rbegin();} pos=std::min(pos,tt.size()-2); while((tt[pos]>t) || (tt[pos+1]t) pos--; else pos++; } const Real& t0=tt[pos], t1=tt[pos+1]; const T& v0=values[pos], v1=values[pos+1]; return v0+(v1-v0)*((t-t0)/(t1-t0)); } trunk-2018.02b/lib/smoothing/WeightedAverage2d.hpp000066400000000000000000000262631324306050200217460ustar00rootroot00000000000000// © 2008 Václav Šmilauer #pragma once #include #include using std::vector; using std::string; template struct GridContainer{ private: Vector2r lo, hi; Vector2r cellSizes; Vector2i nCells; public: Vector2r getLo(){return lo;} Vector2r getHi(){return hi;} Vector2r getCellSize(){ return cellSizes; } vector > > grid; /* construct grid from lower left corner, upper right corner and number of cells in each direction */ GridContainer(Vector2r _lo, Vector2r _hi, Vector2i _nCells): lo(_lo), hi(_hi), nCells(_nCells){ cellSizes=Vector2r((hi[0]-lo[0])/nCells[0],(hi[1]-lo[1])/nCells[1]); grid.resize(nCells[0]); for(int i=0; i=nCells[0] || ret[1]<0 || ret[1]>=nCells[1]){ if(inGrid) *inGrid=false; else throw std::invalid_argument("Cell coordinates outside grid (xy="+boost::lexical_cast(xy[0])+","+boost::lexical_cast(xy[1])+", computed cell coordinates "+boost::lexical_cast(ret[0])+","+boost::lexical_cast(ret[1])+")."); } else {if(inGrid) *inGrid=true;} return ret; } Vector2r cell2xyMid(Vector2i cxy) const { return Vector2r(lo[0]+cellSizes[0]*(.5+cxy[0]),lo[1]+cellSizes[1]*(.5+cxy[1])); } const Vector2i& getSize() const{ return nCells;} // add new element to the right cell void add(const T& t, Vector2r xy){bool inGrid; Vector2i cxy=xy2cell(xy,&inGrid); if(!inGrid){ if(cxy[0]<0) cxy[0]=0; if(cxy[0]>=nCells[0]) cxy[0]=nCells[0]-1; if(cxy[1]<0) cxy[1]=0; if(cxy[1]>=nCells[1]) cxy[1]=nCells[1]-1; } grid[cxy[0]][cxy[1]].push_back(t);} /* Filters: return list of cells, based on some spatial criterion: rectangle, ellipse, circle (add more if you need that) */ vector rectangleFilter(Vector2r bbLo, Vector2r bbHi) const { vector ret; bool dummy; Vector2i cxymin=xy2cell(bbLo,&dummy), cxymax=xy2cell(bbHi,&dummy); for(int cx=cxymin[0]; cx<=cxymax[0]; cx++){ for(int cy=cxymin[1]; cy<=cxymax[1]; cy++){ if(cx>=0 && cx=0 && cy circleFilter(Vector2r xy, Real radius) const { return ellipseFilter(xy,Vector2r(radius,radius));} vector ellipseFilter(Vector2r xy0, Vector2r radii) const { vector rectangle=rectangleFilter(Vector2r(xy0[0]-radii[0],xy0[1]-radii[1]),Vector2r(xy0[0]+radii[0],xy0[1]+radii[1])); vector ret; bool inGrid; Vector2i cxy=xy2cell(xy0,&inGrid); FOREACH(Vector2i mid, rectangle){ // if we are in the cell where the middle is also, this cell passes the filter if(inGrid && mid[0]==cxy[0] && mid[1]==cxy[1]){ret.push_back(mid); continue;} Vector2r xyMid=cell2xyMid(mid); Vector2r xy(xyMid[0]-xy0[0],xyMid[1]-xy0[1]); // relative mid-cell coords // tricky: move the mid-cell to the corner (or aligned pt on edge) according to position WRT center if(mid[0]!=cxy[0]) xy.x()+=(mid[0] v){ vector > vvb; string ret; vvb.resize(nCells[0]); for(size_t i=0; i<(size_t)nCells[0]; i++) vvb[i].resize(nCells[1],false); FOREACH(Vector2i& vv, v) vvb[vv[0]][vv[1]]=true; for(int cy=nCells[1]-1; cy>=0; cy--){ ret+="|"; for(int cx=0; cx struct WeightedAverage{ const shared_ptr > grid; Real weightedSupportArea; // must be computed by derived class WeightedAverage(const shared_ptr >& _grid):grid(_grid){}; virtual Vector2r getPosition(const T&)=0; virtual Real getWeight(const Vector2r& refPt, const T&)=0; virtual Tvalue getValue(const T&)=0; virtual vector filterCells(const Vector2r& refPt)=0; Tvalue computeAverage(const Vector2r& refPt){ Real sumValues, sumWeights; sumValuesWeights(refPt,sumValues,sumWeights); return sumValues/sumWeights; } Tvalue computeAvgPerUnitArea(const Vector2r& refPt){ Real sumValues, sumWeights; sumValuesWeights(refPt,sumValues,sumWeights); return sumValues/weightedSupportArea; } void sumValuesWeights(const Vector2r& refPt, Real& sumValues, Real& sumWeights){ vector filtered=filterCells(refPt); sumValues=sumWeights=0; FOREACH(Vector2i cell, filtered){ FOREACH(const T& element, grid->grid[cell[0]][cell[1]]){ Real weight=getWeight(refPt,element); sumValues+=weight*getValue(element); sumWeights+=weight; } } } }; /* Class for doing template specialization of gaussian kernel average on SGDA_Scalar2d and for testing */ struct Scalar2d{ Vector2r pos; Real val; }; /* Symmetric Gaussian Distribution Average with scalar values */ struct SGDA_Scalar2d: public WeightedAverage { Real stDev, relThreshold; boost::math::normal_distribution distrib; SGDA_Scalar2d(const shared_ptr >& _grid, Real _stDev, Real _relThreshold=3): WeightedAverage(_grid), stDev(_stDev), relThreshold(_relThreshold), distrib(0,stDev) { // approximate probability density function between -stDev*relThreshold,stDev*relThreshold // it is enough to get Φ(-stDev*relThreshold) and subtract twice from 1 (symmetry) // http://en.wikipedia.org/wiki/Normal_distribution#Numerical_approximations_for_the_normal_CDF // using Abramowitz & Stegun approximation, which has error less that 7.5e-8 (fine for us) // FIXME: algorithm not correct, as it takes 1d quantile, while we would need PDF for symmetric 2d gaussian! Real clippedQuantile=boost::math::cdf(distrib,-stDev*relThreshold); Real area=M_PI*pow(relThreshold*stDev,2); // area of the support weightedSupportArea=(1-2*clippedQuantile)*area; } virtual Real getWeight(const Vector2r& meanPt, const Scalar2d& e){ Vector2r pos=getPosition(e); Real rSq=(meanPt-pos).squaredNorm(); //pow(meanPt[0]-pos[0],2)+pow(meanPt[1]-pos[1],2); if(rSq>pow(relThreshold*stDev,2)) return 0.; // discard points further than relThreshold*stDev, by default 3*stDev //return (1./(stDev*sqrt(2*M_PI)))*exp(-rSq/(2*stDev*stDev)); return boost::math::pdf(distrib,sqrt(rSq)); } vector filterCells(const Vector2r& refPt){return WeightedAverage::grid->circleFilter(refPt,stDev*relThreshold);} Real getValue(const Scalar2d& dp){return (Real)dp.val;} Vector2r getPosition(const Scalar2d& dp){return dp.pos;} }; /* simplified interface for python: * * ga=GaussAverage((0,0),(100,100),(10,10),5) * ga.add(53.444,(15.6,43.0)) * ... * ga.avg((10,10)) * ... * * */ class pyGaussAverage{ //struct Scalar2d{Vector2r pos; Real val;}; Vector2r tuple2vec2r(const boost::python::tuple& t){return Vector2r(boost::python::extract(t[0])(),boost::python::extract(t[1])());} Vector2i tuple2vec2i(const boost::python::tuple& t){return Vector2i(boost::python::extract(t[0])(),boost::python::extract(t[1])());} shared_ptr sgda; struct Poly2d{vector vertices; bool inclusive;}; vector clips; public: pyGaussAverage(boost::python::tuple lo, boost::python::tuple hi, boost::python::tuple nCells, Real stDev, Real relThreshold=3.){ shared_ptr > g(new GridContainer(tuple2vec2r(lo),tuple2vec2r(hi),tuple2vec2i(nCells))); sgda=shared_ptr(new SGDA_Scalar2d(g,stDev)); sgda->relThreshold=relThreshold; } bool pointInsidePolygon(const Vector2r&,const vector&); bool ptIsClipped(const Vector2r& pt){ FOREACH(const Poly2d& poly, clips){ bool inside=pointInsidePolygon(pt,poly.vertices); if((inside && !poly.inclusive) || (!inside && poly.inclusive)) return true; } return false; } bool addPt(Real val, boost::python::tuple pos){Scalar2d d; d.pos=tuple2vec2r(pos); if(ptIsClipped(d.pos)) return false; d.val=val; sgda->grid->add(d,d.pos); return true; } Real avg(Vector2r pt){ if(ptIsClipped(pt)) return std::numeric_limits::quiet_NaN(); return sgda->computeAverage(pt);} Real avgPerUnitArea(Vector2r pt){ if(ptIsClipped(pt)) return std::numeric_limits::quiet_NaN(); return sgda->computeAvgPerUnitArea(pt); } Real stDev_get(){return sgda->stDev;} void stDev_set(Real s){sgda->stDev=s;} Real relThreshold_get(){return sgda->relThreshold;} void relThreshold_set(Real rt){sgda->relThreshold=rt;} boost::python::tuple aabb_get(){return boost::python::make_tuple(sgda->grid->getLo(),sgda->grid->getHi());} boost::python::list clips_get(){ boost::python::list ret; FOREACH(const Poly2d& poly, clips){ boost::python::list vertices; FOREACH(const Vector2r& v, poly.vertices) vertices.append(boost::python::make_tuple(v[0],v[1])); ret.append(boost::python::make_tuple(vertices,poly.inclusive)); } return ret; } void clips_set(boost::python::list l){ /* [ ( [(x1,y1),(x2,y2),…], true), … ] */ clips.clear(); for(int i=0; i(l[i])(); boost::python::list coords=boost::python::extract(polyDesc[0]); Poly2d poly; poly.inclusive=boost::python::extract(polyDesc[1]); for(int j=0; j(coords[j]))); } clips.push_back(poly); } } boost::python::tuple data_get(){ boost::python::list x,y,val; const Vector2i& dim=sgda->grid->getSize(); for(int i=0; igrid->grid[i][j]){ x.append(element.pos[0]); y.append(element.pos[1]); val.append(element.val); } } } return boost::python::make_tuple(x,y,val); } Vector2i nCells_get(){ return sgda->grid->getSize(); } int cellNum(const Vector2i& cell){ return sgda->grid->grid[cell[0]][cell[1]].size(); } Real cellSum(const Vector2i& cell){ Real sum=0; FOREACH(const Scalar2d& v, sgda->grid->grid[cell[0]][cell[1]]) sum+=v.val; return sum; } Real cellAvg(const Vector2i& cell){ return cellSum(cell)/cellNum(cell); } Real cellArea(){ Vector2r sz=sgda->grid->getCellSize(); return sz[0]*sz[1]; } Vector2r cellDim(){ return sgda->grid->getCellSize(); } }; trunk-2018.02b/lib/triangulation/000077500000000000000000000000001324306050200166145ustar00rootroot00000000000000trunk-2018.02b/lib/triangulation/FlowBoundingSphere.hpp000066400000000000000000000206471324306050200231020ustar00rootroot00000000000000/************************************************************************* * Copyright (C) 2009 by Emanuele Catalano * * Copyright (C) 2009 by Bruno Chareyre * * Copyright (C) 2012 by Donia Marzougui * * * * This program is free software; it is licensed under the terms of the * * GNU General Public License v2 or later. See file LICENSE for details. * *************************************************************************/ #ifdef FLOW_ENGINE #pragma once #include "Network.hpp" #include "Timer.h" #include "basicVTKwritter.hpp" #include "Timer.h" typedef pair, vector > Constriction; namespace CGT { template class FlowBoundingSphere : public Network<_Tesselation> { public: typedef _Tesselation Tesselation; typedef Network _N; DECLARE_TESSELATION_TYPES(Network) //painfull, but we need that for templates inheritance... using _N::T; using _N::xMin; using _N::xMax; using _N::yMin; using _N::yMax; using _N::zMin; using _N::zMax; using _N::Rmoy; using _N::sectionArea; using _N::Height; using _N::vTotal; using _N::currentTes; using _N::debugOut; using _N::nOfSpheres; using _N::xMinId; using _N::xMaxId; using _N::yMinId; using _N::yMaxId; using _N::zMinId; using _N::zMaxId; using _N::boundsIds; using _N::cornerMin; using _N::cornerMax; using _N::VSolidTot; using _N::Vtotalissimo; using _N::vPoral; using _N::sSolidTot; using _N::vPoralPorosity; using _N::vTotalPorosity; using _N::boundaries; using _N::idOffset; using _N::vtkInfiniteVertices; using _N::vtkInfiniteCells; using _N::num_particles; using _N::boundingCells; using _N::facetVertices; using _N::facetNFictious; //same for functions using _N::defineFictiousCells; using _N::addBoundingPlanes; using _N::boundary; using _N::tesselation; using _N::surfaceSolidThroatInPore; virtual ~FlowBoundingSphere(); FlowBoundingSphere(); bool slipBoundary; double tolerance; double relax; double ks; //Hydraulic Conductivity bool clampKValues, meanKStat, distanceCorrection; bool OUTPUT_BOUDARIES_RADII; bool noCache;//flag for checking if cached values cell->unitForceVectors have been defined bool computedOnce;//flag for checking if current triangulation has been computed at least once bool pressureChanged;//are imposed pressures modified (on python side)? When it happens, we have to reApplyBoundaryConditions int errorCode; bool factorizeOnly; //Handling imposed pressures/fluxes on elements in the form of {point,value} pairs, IPCells contains the cell handles corresponding to point vector > imposedP; vector IPCells; vector > imposedF; vector IFCells; //Blocked cells, where pressure may be computed in undrained condition vector blockedCells; //Pointers to vectors used for user defined boundary pressure vector *pxpos, *ppval; void initNewTri () {noCache=true; /*isLinearSystemSet=false; areCellsOrdered=false;*/}//set flags after retriangulation bool permeabilityMap; bool computeAllCells;//exececute computeHydraulicRadius for all facets and all spheres (double cpu time but needed for now in order to define crossSections correctly) double KOptFactor; double minKdivKmean; double maxKdivKmean; int Iterations; bool rAverage; int walls_id[6]; #define parallel_forces #ifdef parallel_forces int ompThreads; vector< vector > perVertexUnitForce; vector< vector > perVertexPressure; #endif vector edgeSurfaces; vector > edgeIds; vector edgeNormalLubF; vector shearLubricationForces; vector shearLubricationTorques; vector pumpLubricationTorques; vector twistLubricationTorques; vector normalLubricationForce; vector shearLubricationBodyStress; vector normalLubricationBodyStress; vector deltaNormVel; vector deltaShearVel; vector normalV; vector surfaceDistance; vector onlySpheresInteractions; vector shearStressInteraction; vector normalStressInteraction; void Localize(); void computePermeability(); virtual void gaussSeidel (Real dt=0); virtual void resetNetwork(); virtual void resetLinearSystem();//reset both A and B in the linear system A*P=B, done typically after updating the mesh virtual void resetRHS() {};////reset only B in the linear system A*P=B, done typically after changing values of imposed pressures double kFactor; //permeability moltiplicator std::string key; //to give to consolidation files a name with iteration number // std::vector pressures; //for automatic write maximum pressures during consolidation bool tessBasedForce; //allow the force computation method to be chosen from FlowEngine Real minPermLength; //min branch length for Poiseuille double viscosity; double fluidBulkModulus; void displayStatistics(); void initializePressure ( double pZero ); bool reApplyBoundaryConditions (); void computeFacetForcesWithCache(bool onlyCache=false); void saveVtk (const char* folder); #ifdef XVIEW void dessineTriangulation ( Vue3D &Vue, RTriangulation &T ); void dessineShortTesselation ( Vue3D &Vue, Tesselation &Tes ); #endif double permeameter ( double PInf, double PSup, double Section, double DeltaY, const char *file ); double samplePermeability( double& xMin,double& xMax ,double& yMin,double& yMax,double& zMin,double& zMax); double computeHydraulicRadius (CellHandle cell, int j ); Real checkSphereFacetOverlap(const Sphere& v0, const Sphere& v1, const Sphere& v2); double dotProduct ( CVector x, CVector y ); double computeEffectiveRadius(CellHandle cell, int j); double computeEffectiveRadiusByPosRadius(const Point& posA, const double& rA, const Point& posB, const double& rB, const Point& posC, const double& rC); double computeEquivalentRadius(CellHandle cell, int j); //return the list of constriction values vector getConstrictions(); vector getConstrictionsFull(); CVector cellBarycenter(CellHandle& cell); void generateVoxelFile ( ); void computeEdgesSurfaces(); Vector3r computeViscousShearForce(const Vector3r& deltaV, const int& edge_id, const Real& Rh); Real computeNormalLubricationForce(const Real& deltaNormV, const Real& dist, const int& edge_id, const Real& eps, const Real& stiffness, const Real& dt, const Real& meanRad); Vector3r computeShearLubricationForce(const Vector3r& deltaShearV, const Real& dist, const int& edge_id, const Real& eps, const Real& centerDist, const Real& meanRad); Vector3r computePumpTorque(const Vector3r& deltaShearAngV, const Real& dist, const int& edge_id, const Real& eps, const Real& meanRad ); Vector3r computeTwistTorque(const Vector3r& deltaNormAngV, const Real& dist, const int& edge_id, const Real& eps, const Real& meanRad ); RTriangulation& buildTriangulation ( Real x, Real y, Real z, Real radius, unsigned const id ); bool isInsideSphere ( double& x, double& y, double& z ); void sliceField (const char *filename); void comsolField(); void interpolate ( Tesselation& Tes, Tesselation& NewTes ); virtual void averageRelativeCellVelocity(); void averageFluidVelocity(); void applySinusoidalPressure(RTriangulation& Tri, double amplitude, double averagePressure, double loadIntervals); void applyUserDefinedPressure(RTriangulation& Tri, vector& xpos, vector& pval); bool isOnSolid (double X, double Y, double Z); double getPorePressure (double X, double Y, double Z); void measurePressureProfile(double WallUpy, double WallDowny); double averageSlicePressure(double Y); double averagePressure(); int getCell (double X,double Y,double Z); double boundaryFlux(unsigned int boundaryId); void setBlocked(CellHandle& cell); vector averageFluidVelocityOnSphere(unsigned int Id_sph); //Solver? int useSolver;//(0 : GaussSeidel, 1:CHOLMOD) double fractionalSolidArea(CellHandle cell, int j); }; } //namespace CGT #include #ifdef LINSOLV #include "lib/triangulation/FlowBoundingSphereLinSolv.hpp" #endif /// _____ Template Implementation ____ // #include "lib/triangulation/FlowBoundingSphereLinSolv.ipp" #endif //FLOW_ENGINE trunk-2018.02b/lib/triangulation/FlowBoundingSphere.ipp000066400000000000000000001712321324306050200231000ustar00rootroot00000000000000/************************************************************************* * Copyright (C) 2009 by Emanuele Catalano * * Copyright (C) 2009 by Bruno Chareyre * * Copyright (C) 2012 by Donia Marzougui * * * * This program is free software; it is licensed under the terms of the * * GNU General Public License v2 or later. See file LICENSE for details. * *************************************************************************/ #ifdef FLOW_ENGINE // #define XVIEW #include "FlowBoundingSphere.hpp"//include after #define XVIEW #include #include #include #include #include "vector" #include #include #include #ifdef XVIEW // #include "Vue3D.h" //FIXME implicit dependencies will look for this class (out of tree) even ifndef XVIEW #endif #ifdef YADE_OPENMP #include // #define GS_OPEN_MP //It should never be defined if Yade is not using openmp #endif // #define USE_FAST_MATH 1 namespace CGT { typedef vector VectorR; //! Use this factor, or minLength, to reduce max permeability values (see usage below)) const double minLength = 0.01;//percentage of mean rad //! Factors including the effect of 1/2 symmetry in hydraulic radii const Real multSym1 = 1/pow(2,0.25); const Real multSym2 = 1/pow(4,0.25); #ifdef XVIEW Vue3D Vue1; #endif template FlowBoundingSphere::~FlowBoundingSphere() { } template FlowBoundingSphere::FlowBoundingSphere() { xMin = 1000.0, xMax = -10000.0, yMin = 1000.0, yMax = -10000.0, zMin = 1000.0, zMax = -10000.0; currentTes = 0; nOfSpheres = 0; sectionArea = 0, Height=0, vTotal=0; vtkInfiniteVertices=0, vtkInfiniteCells=0; viscosity = 1; fluidBulkModulus = 0; tessBasedForce = true; for (int i=0;i<6;i++) boundsIds[i] = 0; minPermLength=1e-6;// multiplier applied on throat radius to define a minimal throat length (escaping coincident points) slipBoundary = false;//no-slip/symmetry conditions on lateral boundaries tolerance = 1e-07; relax = 1.9; ks=0; distanceCorrection = true; clampKValues = true; meanKStat = true; KOptFactor=0; noCache=true; pressureChanged=false; computeAllCells=true;//might be turned false IF the code is reorganized (we can make a separate function to compute unitForceVectors outside compute_Permeability) AND it really matters for CPU time debugOut = true; rAverage = false; /** use the average between the effective radius (inscribed sphere in facet) and the equivalent (circle surface = facet fluid surface) **/ OUTPUT_BOUDARIES_RADII = false; rAverage = false; /** if true use the average between the effective radius (inscribed sphere in facet) and the equivalent (circle surface = facet fluid surface) **/ // areaR2Permeability=true; permeabilityMap = false; computedOnce=false; minKdivKmean=0.0001; maxKdivKmean=100.; ompThreads=1; errorCode=0; pxpos=ppval=NULL; } template void FlowBoundingSphere::resetNetwork() {T[currentTes].Clear();this->resetLinearSystem();} template void FlowBoundingSphere::resetLinearSystem() {noCache=true;} template void FlowBoundingSphere::averageRelativeCellVelocity() { if (noCache && T[!currentTes].Max_id()<=0) return; RTriangulation& Tri = T[noCache?(!currentTes):currentTes].Triangulation(); Point posAvFacet; int numCells = 0; double facetFlowRate = 0; FiniteCellsIterator cellEnd = Tri.finite_cells_end(); for ( FiniteCellsIterator cell = Tri.finite_cells_begin(); cell != cellEnd; cell++ ) { if (cell->info().isGhost) continue; cell->info().averageVelocity() =CGAL::NULL_VECTOR; numCells++; Real totFlowRate = 0;//used to acount for influxes in elements where pressure is imposed for ( int i=0; i<4; i++ ) if (!Tri.is_infinite(cell->neighbor(i))){ CVector Surfk = cell->info()-cell->neighbor(i)->info(); Real area = sqrt ( Surfk.squared_length() ); Surfk = Surfk/area; CVector branch = cell->vertex ( facetVertices[i][0] )->point().point() - cell->info(); posAvFacet = (Point) cell->info() + ( branch*Surfk ) *Surfk; facetFlowRate = (cell->info().kNorm())[i] * (cell->info().shiftedP() - cell->neighbor (i)->info().shiftedP()); totFlowRate += facetFlowRate; cell->info().averageVelocity() = cell->info().averageVelocity() + (facetFlowRate) * ( posAvFacet-CGAL::ORIGIN ); } //This is the influx term if (cell->info().Pcondition) cell->info().averageVelocity() = cell->info().averageVelocity() - (totFlowRate)*((Point) cell->info()-CGAL::ORIGIN ); //now divide by volume cell->info().averageVelocity() = cell->info().averageVelocity() /std::abs(cell->info().volume()); } } template bool FlowBoundingSphere::isOnSolid (double X, double Y, double Z) { RTriangulation& Tri = T[currentTes].Triangulation(); FiniteCellsIterator cellEnd = Tri.finiteCellsEnd(); for (FiniteCellsIterator cell = Tri.finite_cells_begin(); cell != cellEnd; cell++) { for (int i=0; i<4; i++){ double radius = sqrt(cell->vertex(i)->point().weight()); if (X < (cell->vertex(i)->point().x()+radius) && X > (cell->vertex(i)->point().x()-radius)){ if (Y < (cell->vertex(i)->point().y()+radius) && Y > (cell->vertex(i)->point().y()-radius)){ if (Z < (cell->vertex(i)->point().z()+radius) && Z > (cell->vertex(i)->point().z()-radius)){ return true;}}}}} return false; } template void FlowBoundingSphere::averageFluidVelocity() { if (noCache && T[!currentTes].Max_id()<=0) return; averageRelativeCellVelocity(); RTriangulation& Tri = T[noCache?(!currentTes):currentTes].Triangulation(); int numVertex = 0; FiniteVerticesIterator verticesEnd = Tri.finite_vertices_end(); for (FiniteVerticesIterator vIt = Tri.finite_vertices_begin(); vIt != verticesEnd; vIt++) { numVertex++;} vector volumes; vector velocityVolumes; velocityVolumes.resize(numVertex); volumes.resize(numVertex); for (FiniteVerticesIterator vIt = Tri.finite_vertices_begin(); vIt != verticesEnd; vIt++) { velocityVolumes[vIt->info().id()]=CGAL::NULL_VECTOR; volumes[vIt->info().id()]=0.f;} FiniteCellsIterator cellEnd = Tri.finiteCellsEnd(); for ( FiniteCellsIterator cell = Tri.finite_cells_begin(); cell != cellEnd; cell++ ) { if (cell->info().fictious()==0){ for (int i=0;i<4;i++){ velocityVolumes[cell->vertex(i)->info().id()] = velocityVolumes[cell->vertex(i)->info().id()] + cell->info().averageVelocity()*std::abs(cell->info().volume()); volumes[cell->vertex(i)->info().id()] = volumes[cell->vertex(i)->info().id()] + std::abs(cell->info().volume());} }} std::ofstream fluid_vel ("Velocity", std::ios::out); double Rx = (xMax-xMin) /10; double Ry = (yMax-yMin) /12; double Rz = (zMax-zMin) /20; CellHandle cellula; CVector velocity = CGAL::NULL_VECTOR; int i=0; for(double X=xMin+Rx;Xvertex(y)->info().isFictious) {velocity = velocity + (velocityVolumes[cellula->vertex(y)->info().id()]/volumes[cellula->vertex(y)->info().id()]);i++;}} }velocity = velocity/i; fluid_vel << X << " " << Y << " " << velocity << endl; }} } template vector FlowBoundingSphere::averageFluidVelocityOnSphere(unsigned int Id_sph) { //FIXME: we are computing everything again for each other Id_sph... if (noCache && T[!currentTes].Max_id()<=0) return vector(3,0); averageRelativeCellVelocity(); RTriangulation& Tri = T[noCache?(!currentTes):currentTes].Triangulation(); Real volumes; CGT::CVector velocityVolumes; vector result; result.resize(3); velocityVolumes=CGAL::NULL_VECTOR; volumes=0.f; FiniteCellsIterator cellEnd = Tri.finite_cells_end(); for ( FiniteCellsIterator cell = Tri.finite_cells_begin(); cell != cellEnd; cell++ ) { if (cell->info().fictious()==0){ for (unsigned int i=0;i<4;i++){ if (cell->vertex(i)->info().id()==Id_sph){ velocityVolumes = velocityVolumes + cell->info().averageVelocity()*std::abs(cell->info().volume()); volumes = volumes + std::abs(cell->info().volume());}}}} for (int i=0;i<3;i++) result[i] += velocityVolumes[i]/volumes; return result; } template double FlowBoundingSphere::getPorePressure (double X, double Y, double Z) { if (noCache && T[!currentTes].Max_id()<=0) return 0;//the engine never solved anything RTriangulation& Tri = T[noCache?(!currentTes):currentTes].Triangulation(); CellHandle cell = Tri.locate(CGT::Sphere(X,Y,Z)); return cell->info().p(); } template int FlowBoundingSphere::getCell (double X, double Y, double Z) { if (noCache && T[!currentTes].Max_id()<=0) {cout<<"Triangulation does not exist. Sorry."<info().id; } template void FlowBoundingSphere::measurePressureProfile(double WallUpy, double WallDowny) { if (noCache && T[!currentTes].Max_id()<=0) return;//the engine never solved anything RTriangulation& Tri = T[noCache?(!currentTes):currentTes].Triangulation(); CellHandle permeameter; std::ofstream capture ("Pressure_profile", std::ios::app); int intervals = 5; int captures = 6; double Rz = (zMax-zMin)/intervals; double Ry = (WallUpy-WallDowny)/captures; double X=(xMax+xMin)/2; double Y = WallDowny; double pressure = 0.f; int cell=0; for (int i=0; iinfo().p(); cell++; } Y += Ry; capture << pressure/cell << endl;} } template double FlowBoundingSphere::averageSlicePressure(double Y) { RTriangulation& Tri = T[currentTes].Triangulation(); double P_ave = 0.f; int n = 0; double Ry = (yMax-yMin)/30; double Rx = (xMax-xMin)/30; double Rz = (zMax-zMin)/30; for (double X=xMin; X<=xMax+Ry/10; X=X+Rx) { for (double Z=zMin; Z<=zMax+Ry/10; Z=Z+Rz) { P_ave+=Tri.locate(CGT::Sphere(X, Y, Z))->info().p(); n++; } } P_ave/=n; return P_ave; } template double FlowBoundingSphere::averagePressure() { RTriangulation& Tri = T[currentTes].Triangulation(); double P = 0.f, Ppond=0.f, Vpond=0.f; int n = 0; for (FiniteCellsIterator cell = Tri.finite_cells_begin(); cell != Tri.finite_cells_end(); cell++) { P+=cell->info().p(); n++; Ppond+=cell->info().p()*std::abs(cell->info().volume()); Vpond+=std::abs(cell->info().volume());} P/=n; Ppond/=Vpond; return Ppond; } template void FlowBoundingSphere::computeFacetForcesWithCache(bool onlyCache) { RTriangulation& Tri = T[currentTes].Triangulation(); CVector nullVect(0,0,0); //reset forces if (!onlyCache) for (FiniteVerticesIterator v = Tri.finite_vertices_begin(); v != Tri.finite_vertices_end(); ++v) v->info().forces=nullVect; #ifdef parallel_forces if (noCache) { perVertexUnitForce.clear(); perVertexPressure.clear(); perVertexUnitForce.resize(T[currentTes].maxId+1); perVertexPressure.resize(T[currentTes].maxId+1);} #endif CellHandle neighbourCell; VertexHandle mirrorVertex; CVector tempVect; //FIXME : Ema, be carefull with this (noCache), it needs to be turned true after retriangulation if (noCache) {for (VCellIterator cellIt=T[currentTes].cellHandles.begin(); cellIt!=T[currentTes].cellHandles.end(); cellIt++){ CellHandle& cell = *cellIt; //reset cache for (int k=0;k<4;k++) cell->info().unitForceVectors[k]=nullVect; for (int j=0; j<4; j++) if (!Tri.is_infinite(cell->neighbor(j))) { neighbourCell = cell->neighbor(j); const CVector& Surfk = cell->info().facetSurfaces[j]; //FIXME : later compute that fluidSurf only once in hydraulicRadius, for now keep full surface not modified in cell->info for comparison with other forces schemes //The ratio void surface / facet surface //Area of the facet (i.e. the triangle) Real area = sqrt(Surfk.squared_length()); if (area<=0) cerr <<"AREA <= 0!!"<& crossSections = cell->info().facetSphereCrossSections; //This is the cross-sectional area of the throat CVector fluidSurfk = cell->info().facetSurfaces[j]*cell->info().facetFluidSurfacesRatio[j]; /// handle fictious vertex since we can get the projected surface easily here if (cell->vertex(j)->info().isFictious) { //projection of facet on the boundary Real projSurf=std::abs(Surfk[boundary(cell->vertex(j)->info().id()).coordinate]); tempVect=-projSurf*boundary(cell->vertex(j)->info().id()).normal; cell->vertex(j)->info().forces = cell->vertex(j)->info().forces+tempVect*cell->info().p(); //define the cached value for later use with cache*p cell->info().unitForceVectors[j]=cell->info().unitForceVectors[j]+ tempVect; } /// Apply weighted forces f_k=sqRad_k/sumSqRad*f CVector facetUnitForce = -fluidSurfk*cell->info().solidSurfaces[j][3]; CVector facetForce = cell->info().p()*facetUnitForce; for (int y=0; y<3;y++) { //1st the drag (viscous) force weighted by surface of spheres in the throat cell->vertex(facetVertices[j][y])->info().forces = cell->vertex(facetVertices[j][y])->info().forces + facetForce*cell->info().solidSurfaces[j][y]; //(add to cached value) cell->info().unitForceVectors[facetVertices[j][y]]=cell->info().unitForceVectors[facetVertices[j][y]]+facetUnitForce*cell->info().solidSurfaces[j][y]; //2nd the partial integral of pore pressure, which boils down to weighting by partial cross-sectional area //uncomment to get total force / comment to get only viscous forces (Bruno) if (!cell->vertex(facetVertices[j][y])->info().isFictious) { cell->vertex(facetVertices[j][y])->info().forces = cell->vertex(facetVertices[j][y])->info().forces -facetNormal*cell->info().p()*crossSections[j][y]; //add to cached value cell->info().unitForceVectors[facetVertices[j][y]]=cell->info().unitForceVectors[facetVertices[j][y]]-facetNormal*crossSections[j][y]; } } #ifdef parallel_forces perVertexUnitForce[cell->vertex(j)->info().id()].push_back(&(cell->info().unitForceVectors[j])); perVertexPressure[cell->vertex(j)->info().id()].push_back(&(cell->info().p())); #endif } } noCache=false;//cache should always be defined after execution of this function } if (onlyCache) return; // } else {//use cached values #ifndef parallel_forces for (FiniteCellsIterator cell = Tri.finite_cells_begin(); cell != cellEnd; cell++) { for (int yy=0;yy<4;yy++) cell->vertex(yy)->info().forces = cell->vertex(yy)->info().forces + cell->info().unitForceVectors[yy]*cell->info().p();} #else #pragma omp parallel for num_threads(ompThreads) for (int vn=0; vn<= T[currentTes].maxId; vn++) { if (T[currentTes].vertexHandles[vn]==NULL) continue; VertexHandle& v = T[currentTes].vertexHandles[vn]; const int& id = v->info().id(); CVector tf (0,0,0); int k=0; for (vector::iterator c = perVertexPressure[id].begin(); c != perVertexPressure[id].end(); c++) tf = tf + (*(perVertexUnitForce[id][k++]))*(**c); v->info().forces = tf; } #endif // } if (debugOut) { CVector totalForce = nullVect; for (FiniteVerticesIterator v = Tri.finite_vertices_begin(); v != Tri.finite_vertices_end(); ++v) { if (!v->info().isFictious) totalForce = totalForce + v->info().forces; else if (boundary(v->info().id()).flowCondition==1) totalForce = totalForce + v->info().forces; } cout << "totalForce = "<< totalForce << endl;} } template void FlowBoundingSphere::applySinusoidalPressure(RTriangulation& Tri, double amplitude, double averagePressure, double loadIntervals) { double step = 1/loadIntervals; VectorCell tmpCells; tmpCells.resize(10000); VCellIterator cellsIt = tmpCells.begin(); for (double alpha=0; alpha<1.001; alpha+=step) { VCellIterator cellsEnd = Tri.incident_cells(T[currentTes].vertexHandles[yMaxId],cellsIt); for (VCellIterator it = tmpCells.begin(); it != cellsEnd; it++) { if(!Tri.is_infinite(*it)){ Point& p1 = (*it)->info(); CellHandle& cell = *it; if (p1.x()info().p() = averagePressure+amplitude; else if (p1.x()>xMax) cell->info().p() = averagePressure-amplitude; else if (p1.x()>(xMin+alpha*(xMax-xMin)) && p1.x()<(xMin+(alpha+step)*(xMax-xMin))) cell->info().p() = averagePressure + (amplitude)*(cos(alpha*M_PI)); } } } } template void FlowBoundingSphere::applyUserDefinedPressure(RTriangulation& Tri, vector& xpos, vector& pval) { if (!(xpos.size() && xpos.size()==pval.size())) {cerr << "Wrong definition of boundary pressure, check input" <info(); CellHandle& cell = *it; if (p1.x()xlast) cerr<<"udef pressure: cell out of range"<info().p() = pval[intg]*(1-frac) + pval[intg+1]*frac; } } } template CVector FlowBoundingSphere::cellBarycenter(CellHandle& cell) { CVector center ( 0,0,0 ); for ( int k=0;k<4;k++ ) center= center + 0.25* (cell->vertex(k)->point().point()-CGAL::ORIGIN); return center; } template void FlowBoundingSphere::interpolate(Tesselation& Tes, Tesselation& NewTes) { CellHandle oldCell; RTriangulation& Tri = Tes.Triangulation(); for (typename VectorCell::iterator cellIt=NewTes.cellHandles.begin(); cellIt!=NewTes.cellHandles.end(); cellIt++){ CellHandle& newCell = *cellIt; if (newCell->info().Pcondition || newCell->info().isGhost) continue; CVector center ( 0,0,0 ); if (newCell->info().fictious()==0) for ( int k=0;k<4;k++ ) center= center + 0.25* (Tes.vertex(newCell->vertex(k)->info().id())->point().point()-CGAL::ORIGIN); else { Real boundPos=0; int coord=0; for ( int k=0;k<4;k++ ) if (!newCell->vertex (k)->info().isFictious) center= center+(1./(4.-newCell->info().fictious()))*(Tes.vertex(newCell->vertex(k)->info().id())->point().point()-CGAL::ORIGIN); for ( int k=0;k<4;k++ ) if (newCell->vertex (k)->info().isFictious) { coord=boundary (newCell->vertex(k)->info().id()).coordinate; boundPos=boundary (newCell->vertex(k)->info().id()).p[coord]; center=CVector(coord==0?boundPos:center[0],coord==1?boundPos:center[1],coord==2?boundPos:center[2]); } } oldCell = Tri.locate(CGT::Sphere(center[0],center[1],center[2])); newCell->info().getInfo(oldCell->info()); // newCell->info().p() = oldCell->info().shiftedP(); } // Tes.Clear();//Don't reset to avoid segfault when getting pressure in scripts just after interpolation } template Real FlowBoundingSphere::checkSphereFacetOverlap(const Sphere& v0, const Sphere& v1, const Sphere& v2) { //First, check that v0 projection fall between v1 and v2... Real dist=(v0.point()-v1.point())*(v2.point()-v1.point()); if (dist<0) return 0; Real v1v2=(v2.point()-v1.point()).squared_length(); if (dist>v1v2) return 0; //... then, check distance Real m=(cross_product(v0.point()-v1.point(),v2.point()-v1.point())).squared_length()/v1v2; if (m void FlowBoundingSphere::setBlocked(CellHandle& cell) { RTriangulation& Tri = T[currentTes].Triangulation(); if (cell->info().Pcondition) cell->info().p() = 0; else blockedCells.push_back(cell); for (int j=0; j<4; j++) { (cell->info().kNorm())[j]= 0; (cell->neighbor(j)->info().kNorm())[Tri.mirror_index(cell, j)]= 0;} } template void FlowBoundingSphere::computePermeability() { if (debugOut) cout << "----Computing_Permeability------" << endl; RTriangulation& Tri = T[currentTes].Triangulation(); VSolidTot = 0, Vtotalissimo = 0, vPoral = 0, sSolidTot = 0, vTotalPorosity=0, vPoralPorosity=0; FiniteCellsIterator cellEnd = Tri.finite_cells_end(); CellHandle neighbourCell; double k=0, distance = 0, radius = 0; int surfneg=0; int NEG=0, POS=0, pass=0; bool ref = Tri.finite_cells_begin()->info().isvisited; Real meanK=0, STDEV=0, meanRadius=0, meanDistance=0; Real infiniteK=1e10; for (VCellIterator cellIt=T[currentTes].cellHandles.begin(); cellIt!=T[currentTes].cellHandles.end(); cellIt++){ CellHandle& cell = *cellIt; if (cell->info().blocked) { setBlocked(cell); cell->info().isvisited = !ref;} Point& p1 = cell->info(); for (int j=0; j<4; j++) { neighbourCell = cell->neighbor(j); Point& p2 = neighbourCell->info(); if (!Tri.is_infinite(neighbourCell) && (neighbourCell->info().isvisited==ref || computeAllCells)) { //compute and store the area of sphere-facet intersections for later use VertexHandle W [3]; for (int kk=0; kk<3; kk++) { W[kk] = cell->vertex(facetVertices[j][kk]); } Sphere& v0 = W[0]->point(); Sphere& v1 = W[1]->point(); Sphere& v2 = W[2]->point(); cell->info().facetSphereCrossSections[j]=CVector( W[0]->info().isFictious ? 0 : 0.5*v0.weight()*acos((v1.point()-v0.point())*(v2.point()-v0.point())/sqrt((v1.point()-v0.point()).squared_length()*(v2.point()-v0.point()).squared_length())), W[1]->info().isFictious ? 0 : 0.5*v1.weight()*acos((v0.point()-v1.point())*(v2.point()-v1.point())/sqrt((v1.point()-v0.point()).squared_length()*(v2.point()-v1.point()).squared_length())), W[2]->info().isFictious ? 0 : 0.5*v2.weight()*acos((v0.point()-v2.point())*(v1.point()-v2.point())/sqrt((v1.point()-v2.point()).squared_length()*(v2.point()-v0.point()).squared_length()))); //FIXME: it should be possible to skip completely blocked cells, currently the problem is it segfault for undefined areas // if (cell->info().blocked) continue;//We don't need permeability for blocked cells, it will be set to zero anyway pass+=1; CVector l = p1 - p2; distance = sqrt(l.squared_length()); if (!rAverage) radius = 2* computeHydraulicRadius(cell, j); else radius = (computeEffectiveRadius(cell, j)+computeEquivalentRadius(cell,j))*0.5; if (radius<0) NEG++; else POS++; if (radius==0) { cout << "INS-INS PROBLEM!!!!!!!" << endl; } Real fluidArea=0; if (distance!=0) { if (minPermLength>0 && distanceCorrection) distance=max(minPermLength*radius,distance); const CVector& Surfk = cell->info().facetSurfaces[j]; Real area = sqrt(Surfk.squared_length()); const CVector& crossSections = cell->info().facetSphereCrossSections[j]; Real S0=0; S0=checkSphereFacetOverlap(v0,v1,v2); if (S0==0) S0=checkSphereFacetOverlap(v1,v2,v0); if (S0==0) S0=checkSphereFacetOverlap(v2,v0,v1); //take absolute value, since in rare cases the surface can be negative (overlaping spheres) fluidArea=std::abs(area-crossSections[0]-crossSections[1]-crossSections[2]+S0); cell->info().facetFluidSurfacesRatio[j]=fluidArea/area; // kFactor<0 means we replace Poiseuille by Darcy localy, yielding a particle size-independent bulk conductivity if (kFactor>0) cell->info().kNorm()[j]= kFactor*(fluidArea * pow(radius,2)) / (8*viscosity*distance); else cell->info().kNorm()[j]= -kFactor * area / distance; meanDistance += distance; meanRadius += radius; meanK += (cell->info().kNorm())[j]; if (!neighbourCell->info().isGhost) (neighbourCell->info().kNorm())[Tri.mirror_index(cell, j)]= (cell->info().kNorm())[j]; if (k<0 && debugOut) {surfneg+=1; cout<<"__ k<0 __"<info().id()<<" "<info().id()<<" "<info().id()<<" "<info().isGhost) (neighbourCell->info().kNorm())[Tri.mirror_index(cell, j)]= (cell->info().kNorm())[j]; } } cell->info().isvisited = !ref; } if (debugOut) cout<<"surfneg est "<0) globalK=kFactor*meanDistance*vPoral/(sSolidTot*8.*viscosity);//An approximate value of macroscopic permeability, for clamping local values below else globalK=meanK; if (debugOut) { cout << "PassCompK = " << pass << endl; cout << "meanK = " << meanK << endl; cout << "globalK = " << globalK << endl; cout << "maxKdivKmean*globalK = " << maxKdivKmean*globalK << endl; cout << "minKdivKmean*globalK = " << minKdivKmean*globalK << endl; cout << "meanTubesRadius = " << meanRadius << endl; cout << "meanDistance = " << meanDistance << endl; } ref = Tri.finite_cells_begin()->info().isvisited; pass=0; if (clampKValues) for (VCellIterator cellIt=T[currentTes].cellHandles.begin(); cellIt!=T[currentTes].cellHandles.end(); cellIt++){ CellHandle& cell = *cellIt; for (int j=0; j<4; j++) { neighbourCell = cell->neighbor(j); if (!Tri.is_infinite(neighbourCell) && neighbourCell->info().isvisited==ref) { pass++; (cell->info().kNorm())[j] = max(minKdivKmean*globalK ,min((cell->info().kNorm())[j], maxKdivKmean*globalK)); (neighbourCell->info().kNorm())[Tri.mirror_index(cell, j)]=(cell->info().kNorm())[j]; } } } if (debugOut) cout << "PassKcorrect = " << pass << endl; if (debugOut) cout << "POS = " << POS << " NEG = " << NEG << " pass = " << pass << endl; // A loop to compute the standard deviation of the local K distribution, and use it to include/exclude K values higher then (meanK +/- K_opt_factor*STDEV) if (meanKStat) { std::ofstream k_opt_file("k_stdev.txt" ,std::ios::out); ref = Tri.finite_cells_begin()->info().isvisited; pass=0; for (FiniteCellsIterator cell = Tri.finite_cells_begin(); cell != cellEnd; cell++) { for (int j=0; j<4; j++) { neighbourCell = cell->neighbor(j); if (!Tri.is_infinite(neighbourCell) && neighbourCell->info().isvisited==ref) { pass++; STDEV += pow(((cell->info().kNorm())[j]-meanK),2); } }cell->info().isvisited = !ref; } STDEV = sqrt(STDEV/pass); if (debugOut) cout << "PassSTDEV = " << pass << endl << "STATISTIC K" << endl; double k_min = 0, k_max = meanK + KOptFactor*STDEV; cout << "Kmoy = " << meanK << " Standard Deviation = " << STDEV << endl<< "kmin = " << k_min << " kmax = " << k_max << endl; ref = Tri.finite_cells_begin()->info().isvisited; pass=0; for (FiniteCellsIterator cell = Tri.finite_cells_begin(); cell != cellEnd; cell++) { for (int j=0; j<4; j++) { neighbourCell = cell->neighbor(j); if (!Tri.is_infinite(neighbourCell) && neighbourCell->info().isvisited==ref) { pass+=1; if ((cell->info().kNorm())[j]>k_max) { (cell->info().kNorm())[j]=k_max; (neighbourCell->info().kNorm())[Tri.mirror_index(cell, j)]= (cell->info().kNorm())[j]; } k_opt_file << KOptFactor << " " << (cell->info().kNorm())[j] << endl; } }cell->info().isvisited=!ref; } if (debugOut) cout << "PassKopt = " << pass << endl; } if (debugOut) { FiniteVerticesIterator verticesEnd = Tri.finite_vertices_end(); Real Vgrains = 0; int grains=0; for (FiniteVerticesIterator vIt = Tri.finite_vertices_begin(); vIt != verticesEnd; vIt++) { if (!vIt->info().isFictious && !vIt->info().isGhost) { grains +=1; Vgrains += 1.33333333 * M_PI * pow(vIt->point().weight(),1.5);}} cout< vector FlowBoundingSphere::getConstrictions() { RTriangulation& Tri = T[currentTes].Triangulation(); vector constrictions; CellHandle neighbourCell; const FiniteCellsIterator& cellEnd = Tri.finite_cells_end(); for (FiniteCellsIterator cell = Tri.finite_cells_begin(); cell != cellEnd; cell++) { if (cell->info().isGhost) continue;// retain only the cells with barycenter in the (0,0,0) period for (int j=0; j<4; j++) { neighbourCell = cell->neighbor(j); if (cell->info().id < neighbourCell->info().id) constrictions.push_back(computeEffectiveRadius(cell, j));}} return constrictions; } template vector FlowBoundingSphere::getConstrictionsFull() { RTriangulation& Tri = T[currentTes].Triangulation(); vector constrictions; CellHandle neighbourCell; const FiniteCellsIterator& cellEnd = Tri.finite_cells_end(); for (FiniteCellsIterator cell = Tri.finite_cells_begin(); cell != cellEnd; cell++) { if (cell->info().isGhost) continue;// retain only the cells with barycenter in the (0,0,0) period for (int j=0; j<4; j++) { neighbourCell = cell->neighbor(j); if (cell->info().id < neighbourCell->info().id) { vector rn; const CVector& normal = cell->info().facetSurfaces[j]; if (!normal[0] && !normal[1] && !normal[2]) continue; rn.push_back(computeEffectiveRadius(cell, j)); rn.push_back(normal[0]); rn.push_back(normal[1]); rn.push_back(normal[2]); Constriction cons (pair(cell->info().id,neighbourCell->info().id),rn); constrictions.push_back(cons);}} } return constrictions; } template double FlowBoundingSphere::computeEffectiveRadius(CellHandle cell, int j) { RTriangulation& Tri = T[currentTes].Triangulation(); if (Tri.is_infinite(cell->neighbor(j))) return 0; Point pos[3]; //spheres pos double r[3]; //spheres radius for (int i=0; i<3; i++) { pos[i] = cell->vertex(facetVertices[j][i])->point().point(); r[i] = sqrt(cell->vertex(facetVertices[j][i])->point().weight());} double reff=computeEffectiveRadiusByPosRadius(pos[0],r[0],pos[1],r[1],pos[2],r[2]); if (reff<0) return 0;//happens very rarely, with bounding spheres most probably //if the facet involves one ore more bounding sphere, we return R with a minus sign if (cell->vertex(facetVertices[j][2])->info().isFictious || cell->vertex(facetVertices[j][1])->info().isFictious || cell->vertex(facetVertices[j][2])->info().isFictious) return -reff; else return reff; } ////compute inscribed radius independently by position and radius template double FlowBoundingSphere::computeEffectiveRadiusByPosRadius(const Point& posA, const double& rA, const Point& posB, const double& rB, const Point& posC, const double& rC) { CVector B = posB - posA; CVector x = B/sqrt(B.squared_length()); CVector C = posC - posA; CVector z = CGAL::cross_product(x,C); CVector y = CGAL::cross_product(x,z); y = y/sqrt(y.squared_length()); double b1[2]; b1[0] = B*x; b1[1] = B*y; double c1[2]; c1[0] = C*x; c1[1] = C*y; double A = ((pow(rA,2))*(1-c1[0]/b1[0])+((pow(rB,2)*c1[0])/b1[0])-pow(rC,2)+pow(c1[0],2)+pow(c1[1],2)-((pow(b1[0],2)+pow(b1[1],2))*c1[0]/b1[0]))/(2*c1[1]-2*b1[1]*c1[0]/b1[0]); double BB = (rA-rC-((rA-rB)*c1[0]/b1[0]))/(c1[1]-b1[1]*c1[0]/b1[0]); double CC = (pow(rA,2)-pow(rB,2)+pow(b1[0],2)+pow(b1[1],2))/(2*b1[0]); double D = (rA-rB)/b1[0]; double E = b1[1]/b1[0]; double F = pow(CC,2)+pow(E,2)*pow(A,2)-2*CC*E*A; double c = -F-pow(A,2)+pow(rA,2); double b = 2*rA-2*(D-BB*E)*(CC-E*A)-2*A*BB; double a = 1-pow((D-BB*E),2)-pow(BB,2); if ((pow(b,2)-4*a*c)<0){cout << "NEGATIVE DETERMINANT" << endl; } double reff = (-b+sqrt(pow(b,2)-4*a*c))/(2*a); return reff; } template double FlowBoundingSphere::computeEquivalentRadius(CellHandle cell, int j) { Real fluidSurf = sqrt(cell->info().facetSurfaces[j].squared_length())*cell->info().facetFluidSurfacesRatio[j]; return sqrt(fluidSurf/M_PI); } template double FlowBoundingSphere::computeHydraulicRadius(CellHandle cell, int j) { RTriangulation& Tri = T[currentTes].Triangulation(); if (Tri.is_infinite(cell->neighbor(j))) return 0; double Vpore = this->volumePoreVoronoiFraction(cell, j); double Ssolid = this->surfaceSolidThroat(cell, j, slipBoundary, /*reuse the same facet data*/ true); //handle symmetry (tested ok) if (slipBoundary && facetNFictious>0) { //! Include a multiplier so that permeability will be K/2 or K/4 in symmetry conditions Real mult= facetNFictious==1 ? multSym1 : multSym2; return Vpore/Ssolid*mult;} return Vpore/Ssolid; } template void FlowBoundingSphere::initializePressure( double pZero ) { RTriangulation& Tri = T[currentTes].Triangulation(); FiniteCellsIterator cellEnd = Tri.finite_cells_end(); for (FiniteCellsIterator cell = Tri.finite_cells_begin(); cell != cellEnd; cell++){ if (!cell->info().Pcondition) cell->info().p() = pZero; cell->info().dv()=0; } for (int bound=0; bound<6;bound++) { int& id = *boundsIds[bound]; boundingCells[bound].clear(); if (id<0) continue; Boundary& bi = boundary(id); if (!bi.flowCondition) { VectorCell tmpCells; tmpCells.resize(10000); VCellIterator cells_it = tmpCells.begin(); VCellIterator cells_end = Tri.incident_cells(T[currentTes].vertexHandles[id],cells_it); for (VCellIterator it = tmpCells.begin(); it != cells_end; it++){ (*it)->info().p() = bi.value;(*it)->info().Pcondition=true; boundingCells[bound].push_back(*it); } } } if (ppval && pxpos) applyUserDefinedPressure(Tri,*pxpos,*ppval); IPCells.clear(); for (unsigned int n=0; ninfo().Pcondition) cerr<<"Imposed pressure fall in a boundary condition."<info().p()=imposedP[n].second; cell->info().Pcondition=true;} pressureChanged=false; IFCells.clear(); for (unsigned int n=0; ninfo().Pcondition) cerr<<"Imposed flux fall in a pressure boundary condition."<info().Pcondition=false;} } template bool FlowBoundingSphere::reApplyBoundaryConditions() { if (!pressureChanged) return false; for (int bound=0; bound<6;bound++) { int& id = *boundsIds[bound]; if (id<0) continue; Boundary& bi = boundary(id); if (!bi.flowCondition) { for (VCellIterator it = boundingCells[bound].begin(); it != boundingCells[bound].end(); it++){ (*it)->info().p() = bi.value; (*it)->info().Pcondition=true; } } } if (ppval && pxpos) applyUserDefinedPressure(T[currentTes].Triangulation(),*pxpos,*ppval); for (unsigned int n=0; ninfo().p()=imposedP[n].second; IPCells[n]->info().Pcondition=true;} pressureChanged=false; return true; } template void FlowBoundingSphere::gaussSeidel(Real dt) { reApplyBoundaryConditions(); RTriangulation& Tri = T[currentTes].Triangulation(); int j = 0; double m, n, dp_max, p_max, sum_p, p_moy, dp, sum_dp; double compFlowFactor=0; vector previousP; previousP.resize(Tri.number_of_finite_cells()); const int num_threads=1; bool compressible= (fluidBulkModulus>0); #ifdef GS_OPEN_MP omp_set_num_threads(num_threads); #endif if(debugOut){ cout << "tolerance = " << tolerance << endl; cout << "relax = " << relax << endl;} vector t_sum_p, t_dp_max, t_sum_dp, t_p_max; t_sum_dp.resize(num_threads); t_dp_max.resize(num_threads); t_p_max.resize(num_threads); t_sum_p.resize(num_threads); FiniteCellsIterator cellEnd = Tri.finite_cells_end(); #ifdef GS_OPEN_MP vector cells_its; for (FiniteCellsIterator cell = Tri.finite_cells_begin(); cell != cellEnd; cell++) if ( !cell->info().Pcondition ) cells_its.push_back(cell); int numCells=cells_its.size(); cout<<"cells_its.size() "<info().Pcondition && !cell->info().blocked) { cell2++; #endif if (compressible && j==0) { previousP[bb]=cell->info().p(); } m=0, n=0; for (int j2=0; j2<4; j2++) { if (!Tri.is_infinite(cell->neighbor(j2))) { /// COMPRESSIBLE: if ( compressible ) { compFlowFactor = fluidBulkModulus*dt*cell->info().invVoidVolume(); m += compFlowFactor*(cell->info().kNorm())[j2] * cell->neighbor(j2)->info().p(); if (j==0) n +=compFlowFactor*(cell->info().kNorm())[j2]; } else { /// INCOMPRESSIBLE m += (cell->info().kNorm())[j2] * cell->neighbor(j2)->info().p(); if ( std::isinf(m) && j<10 ) cout << "(cell->info().kNorm())[j2] = " << (cell->info().kNorm())[j2] << " cell->neighbor(j2)->info().p() = " << cell->neighbor(j2)->info().p() << endl; if (j==0) n += (cell->info().kNorm())[j2]; } } } dp = cell->info().p(); if (n!=0 || j!=0) { if (j==0) { if (compressible) cell->info().invSumK=1/(1+n); else cell->info().invSumK=1/n; } if ( compressible ) { /// COMPRESSIBLE cell->info().p() = ( (previousP - compFlowFactor*cell->info().dv()) + m ) / n ; cell->info().p() = ( ((previousP[bb] - ((fluidBulkModulus*dt*cell->info().invVoidVolume())*(cell->info().dv()))) + m) * cell->info().invSumK - cell->info().p()) * relax + cell->info().p(); } else { /// INCOMPRESSIBLE cell->info().p() = - ( cell->info().dv() - m ) / ( n ) = ( -cell.info().dv() + m ) / n ; cell->info().p() = (- (cell->info().dv() - m) * cell->info().invSumK - cell->info().p()) * relax + cell->info().p(); } #ifdef GS_OPEN_MP #endif } dp -= cell->info().p(); #ifdef GS_OPEN_MP const int tn=omp_get_thread_num(); t_sum_dp[tn] += std::abs(dp); t_dp_max[tn]=max(t_dp_max[tn], std::abs(dp)); t_p_max[tn]= max(t_p_max[tn], std::abs(cell->info().p())); t_sum_p[tn]+= std::abs(cell->info().p()); #else dp_max = max(dp_max, std::abs(dp)); p_max = max(p_max, std::abs(cell->info().p())); sum_p += std::abs(cell->info().p()); sum_dp += std::abs(dp); #endif } } #ifdef GS_OPEN_MP for (int ii=0;ii tolerance /*&& j<4000*/ /*&& ( dp_max > tolerance )*//* &&*/ /*( j<50 )*/); #endif } if (debugOut) {cout << "pmax " << p_max << "; pmoy : " << p_moy << endl; cout << "iteration " << j <<"; erreur : " << dp_max/p_max << endl;} computedOnce=true; } template double FlowBoundingSphere::boundaryFlux(unsigned int boundaryId) { if (noCache && T[!currentTes].Max_id()<=0) return 0; bool tes = noCache?(!currentTes):currentTes; RTriangulation& Tri = T[tes].Triangulation(); double Q1=0; VectorCell tmpCells; tmpCells.resize(10000); VCellIterator cells_it = tmpCells.begin(); VCellIterator cell_up_end = Tri.incident_cells(T[tes].vertexHandles[boundaryId],cells_it); for (VCellIterator it = tmpCells.begin(); it != cell_up_end; it++) { const CellHandle& cell = *it; if (cell->info().isGhost) continue; Q1 -= cell->info().dv(); for (int j2=0; j2<4; j2++) Q1 += (cell->info().kNorm())[j2]* (cell->neighbor(j2)->info().shiftedP()-cell->info().shiftedP()); } return Q1; } template double FlowBoundingSphere::permeameter(double PInf, double PSup, double Section, double DeltaY, const char *file) { RTriangulation& Tri = T[currentTes].Triangulation(); std::ofstream kFile(file, std::ios::out); double Q2=0, Q1=0; int cellQ1=0, cellQ2=0; double p_out_max=-10000000, p_out_min=10000000, p_in_max=-100000000, p_in_min=10000000,p_out_moy=0, p_in_moy=0; VectorCell tmpCells; tmpCells.resize(10000); VCellIterator cells_it = tmpCells.begin(); VCellIterator cell_up_end = Tri.incident_cells(T[currentTes].vertexHandles[yMaxId],cells_it); for (VCellIterator it = tmpCells.begin(); it != cell_up_end; it++) { CellHandle& cell = *it; for (int j2=0; j2<4; j2++) { if (!cell->neighbor(j2)->info().Pcondition){ Q1 = Q1 + (cell->neighbor(j2)->info().kNorm())[Tri.mirror_index(cell, j2)]* (cell->neighbor(j2)->info().p()-cell->info().p()); cellQ1+=1; p_out_max = max(cell->neighbor(j2)->info().p(), p_out_max); p_out_min = min(cell->neighbor(j2)->info().p(), p_out_min); p_out_moy += cell->neighbor(j2)->info().p();} }} VectorCell tmpCells2; tmpCells2.resize(10000); VCellIterator cells_it2 = tmpCells2.begin(); VCellIterator cell_down_end = Tri.incident_cells(T[currentTes].vertexHandles[yMinId],cells_it2); for (VCellIterator it = tmpCells2.begin(); it != cell_down_end; it++) { CellHandle& cell = *it; for (int j2=0; j2<4; j2++){ if (!cell->neighbor(j2)->info().Pcondition){ Q2 = Q2 + (cell->neighbor(j2)->info().kNorm())[Tri.mirror_index(cell, j2)]* (cell->info().p()-cell->neighbor(j2)->info().p()); cellQ2+=1; p_in_max = max(cell->neighbor(j2)->info().p(), p_in_max); p_in_min = min(cell->neighbor(j2)->info().p(), p_in_min); p_in_moy += cell->neighbor(j2)->info().p();} }} double density = 1; double viscosity = viscosity; double gravity = 1; double Vdarcy = Q1/Section; double DeltaP = std::abs(PInf-PSup); double DeltaH = DeltaP/ (density*gravity); double k = viscosity*Vdarcy*DeltaY / DeltaP; /**m²**/ double Ks = k*(density*gravity)/viscosity; /**m/s**/ if (debugOut){ cout << "the maximum superior pressure is = " << p_out_max << " the min is = " << p_out_min << endl; cout << "the maximum inferior pressure is = " << p_in_max << " the min is = " << p_in_min << endl; cout << "superior average pressure is " << p_out_moy/cellQ1 << endl; cout << "inferior average pressure is " << p_in_moy/cellQ2 << endl; cout << "celle comunicanti in basso = " << cellQ2 << endl; cout << "celle comunicanti in alto = " << cellQ1 << endl; cout << "The incoming average flow rate is = " << Q2 << " m^3/s " << endl; cout << "The outgoing average flow rate is = " << Q1 << " m^3/s " << endl; cout << "The gradient of charge is = " << DeltaH/DeltaY << " [-]" << endl; cout << "Darcy's velocity is = " << Vdarcy << " m/s" < void FlowBoundingSphere::displayStatistics() { RTriangulation& Tri = T[currentTes].Triangulation(); int Zero =0, Inside=0, Fictious=0; FiniteCellsIterator cellEnd = Tri.finite_cells_end(); for (FiniteCellsIterator cell = Tri.finite_cells_begin(); cell != cellEnd; cell++) { int zeros =0; for (int j=0; j!=4; j++) { if ((cell->info().kNorm())[j]==0) { zeros+=1; } } if (zeros==4) { Zero+=1; } if (!cell->info().fictious()) { Inside+=1; } else { Fictious+=1; } } int fict=0, real=0; for (FiniteVerticesIterator v = Tri.finite_vertices_begin(); v != Tri.finite_vertices_end(); ++v) { if (v->info().isFictious) fict+=1; else real+=1; } long Vertices = Tri.number_of_vertices(); long Cells = Tri.number_of_finite_cells(); long Facets = Tri.number_of_finite_facets(); if(debugOut) {cout << "zeros = " << Zero << endl; cout << "There are " << Vertices << " vertices, dont " << fict << " fictious et " << real << " reeeeeel" << std::endl; cout << "There are " << Cells << " cells " << std::endl; cout << "There are " << Facets << " facets " << std::endl; cout << "There are " << Inside << " cells INSIDE." << endl; cout << "There are " << Fictious << " cells FICTIOUS." << endl;} num_particles = real; } template void FlowBoundingSphere::saveVtk(const char* folder) { if (noCache && T[!currentTes].Max_id()<=0) {cout<<"Triangulation does not exist. Sorry."<info().isReal() && cell->vertex(0)->info().isReal() && cell->vertex(1)->info().isReal() && cell->vertex(2)->info().isReal() && cell->vertex(3)->info().isReal(); if (!isDrawable) vtkInfiniteCells+=1; } for (FiniteVerticesIterator v = Tri.finite_vertices_begin(); v != Tri.finite_vertices_end(); ++v) { if (!v->info().isReal()) vtkInfiniteVertices+=1; else if (firstReal==-1) firstReal=vtkInfiniteVertices;} basicVTKwritter vtkfile((unsigned int) Tri.number_of_vertices()-vtkInfiniteVertices, (unsigned int) Tri.number_of_finite_cells()-vtkInfiniteCells); vtkfile.open(filename,"test"); //!TEMPORARY FIX: //paraview needs zero-based vertex ids (from 0 ... numRealVertices) //in presence of clumps vertex ids are not zero-based anymore //to fix the vkt output vertex ids will be replaced by zero-based ones (CAUTION: output vertex ids != Yade vertex ids!) map vertexIdMap; int numVertices = 0; unsigned int minId = 1; vtkfile.begin_vertices(); double x,y,z; for (FiniteVerticesIterator v = Tri.finite_vertices_begin(); v != Tri.finite_vertices_end(); ++v) { if (v->info().isReal()) { x = (double)(v->point().point()[0]); y = (double)(v->point().point()[1]); z = (double)(v->point().point()[2]); vtkfile.write_point(x,y,z); vertexIdMap[v->info().id()-firstReal] = numVertices; minId = min(minId,v->info().id()-firstReal); numVertices += 1; } } vtkfile.end_vertices(); vtkfile.begin_cells(); int id0,id1,id2,id3; for (FiniteCellsIterator cell = Tri.finite_cells_begin(); cell != Tri.finite_cells_end(); ++cell) { bool isDrawable = cell->info().isReal() && cell->vertex(0)->info().isReal() && cell->vertex(1)->info().isReal() && cell->vertex(2)->info().isReal() && cell->vertex(3)->info().isReal(); if (isDrawable) { id0 = cell->vertex(0)->info().id()-firstReal; id1 = cell->vertex(1)->info().id()-firstReal; id2 = cell->vertex(2)->info().id()-firstReal; id3 = cell->vertex(3)->info().id()-firstReal; if (minId != 0) vtkfile.write_cell(vertexIdMap[id0],vertexIdMap[id1],vertexIdMap[id2],vertexIdMap[id3]); else vtkfile.write_cell(id0, id1, id2, id3); } } vtkfile.end_cells(); if (permeabilityMap){ vtkfile.begin_data("Permeability",CELL_DATA,SCALARS,FLOAT); for (FiniteCellsIterator cell = Tri.finite_cells_begin(); cell != Tri.finite_cells_end(); ++cell) { bool isDrawable = cell->info().isReal() && cell->vertex(0)->info().isReal() && cell->vertex(1)->info().isReal() && cell->vertex(2)->info().isReal() && cell->vertex(3)->info().isReal(); if (isDrawable){vtkfile.write_data(cell->info().s);} } vtkfile.end_data();} else{ vtkfile.begin_data("Pressure",CELL_DATA,SCALARS,FLOAT); for (FiniteCellsIterator cell = Tri.finite_cells_begin(); cell != Tri.finite_cells_end(); ++cell) { bool isDrawable = cell->info().isReal() && cell->vertex(0)->info().isReal() && cell->vertex(1)->info().isReal() && cell->vertex(2)->info().isReal() && cell->vertex(3)->info().isReal(); if (isDrawable){vtkfile.write_data(cell->info().p());} } vtkfile.end_data();} if (1){ averageRelativeCellVelocity(); vtkfile.begin_data("Velocity",CELL_DATA,VECTORS,FLOAT); for (FiniteCellsIterator cell = Tri.finite_cells_begin(); cell != Tri.finite_cells_end(); ++cell) { bool isDrawable = cell->info().isReal() && cell->vertex(0)->info().isReal() && cell->vertex(1)->info().isReal() && cell->vertex(2)->info().isReal() && cell->vertex(3)->info().isReal(); if (isDrawable){vtkfile.write_data(cell->info().averageVelocity()[0],cell->info().averageVelocity()[1],cell->info().averageVelocity()[2]);} } vtkfile.end_data();} } #ifdef XVIEW template void FlowBoundingSphere::dessineTriangulation(Vue3D &Vue, RTriangulation &T) { double* Segments = NULL; long N_seg = newListeEdges(T, &Segments); Vue.Dessine_Segment(Segments, N_seg); deleteListeEdges(&Segments, N_seg); } template void FlowBoundingSphere::dessineShortTesselation(Vue3D &Vue, Tesselation &Tes) { if (!Tes.computed()) Tes.compute(); double* Segments = NULL; long N_seg = Tes.newListeShortEdges(&Segments); Vue.Dessine_Segment(Segments, N_seg); deleteListeEdges(&Segments, N_seg); } #endif template void FlowBoundingSphere::generateVoxelFile( ) { RTriangulation& Tri = T[currentTes].Triangulation(); double l = 1; int dx = 200; double eps = l/dx; std::ofstream voxelfile("MATRIX",std::ios::out); bool solid=false; for (double y=0; y<=l; y+=eps) { for (double z=0; z<=l; z+=eps) { for (double x=0; x<=l; x+=eps) { solid=false; for (FiniteVerticesIterator vIt = Tri.finite_vertices_begin(); vIt != Tri.finite_vertices_end(); vIt++) { double radius = sqrt(vIt->point().weight()); if ((sqrt(pow((x- (vIt->point()[0])),2) +pow((y- (vIt->point()[1])),2) +pow((z- (vIt->point()[2])),2))) <= radius) solid=true; } if (solid) voxelfile << 1; else voxelfile << 0; } voxelfile << endl; } } } template double FlowBoundingSphere::samplePermeability(double& xMin,double& xMax ,double& yMin,double& yMax,double& zMin,double& zMax/*, string key*/) { double Section = (xMax-xMin) * (zMax-zMin); double DeltaY = yMax-yMin; boundary(yMinId).flowCondition=0; boundary(yMaxId).flowCondition=0; boundary(yMinId).value=0; boundary(yMaxId).value=1; double pZero = std::abs((boundary(yMinId).value-boundary(yMaxId).value)/2); initializePressure( pZero ); gaussSeidel(); const char *kk = "Permeability"; return permeameter(boundary(yMinId).value, boundary(yMaxId).value, Section, DeltaY, kk); } template bool FlowBoundingSphere::isInsideSphere(double& x, double& y, double& z) { RTriangulation& Tri = T[currentTes].Triangulation(); for (FiniteVerticesIterator vIt = Tri.finite_vertices_begin(); vIt != Tri.finite_vertices_end(); vIt++) { double radius = vIt->point().weight(); if (pow((x- (vIt->point()[0])),2) +pow((y- (vIt->point()[1])),2) +pow((z- (vIt->point()[2])),2) <= radius) return true; } return false; } template void FlowBoundingSphere::sliceField(const char *filename) { /** Pressure field along one cutting plane **/ RTriangulation& Tri = T[noCache?(!currentTes):currentTes].Triangulation(); CellHandle permeameter; std::ofstream consFile(filename,std::ios::out); int intervals = 400; double Ry = (yMax-yMin) /intervals; double Rz = (zMax-zMin) /intervals; double X=0.5; for (double Y=min(yMax,yMin); Y<=max(yMax,yMin); Y=Y+std::abs(Ry)) { for (double Z=min(zMin,zMax); Z<=max(zMin,zMax); Z=Z+std::abs(Rz)) { permeameter = Tri.locate(Point(X, Y, Z)); consFile << permeameter->info().p() <<" "; } consFile << endl;} consFile << endl; consFile.close(); } template void FlowBoundingSphere::computeEdgesSurfaces() { RTriangulation& Tri = T[currentTes].Triangulation(); //first, copy interacting pairs and normal lub forces form prev. triangulation in a sorted structure for initializing the new lub. Forces vector > > lubPairs; lubPairs.resize(Tri.number_of_vertices()+1); for (unsigned int k=0; kid(),edgeIds[k].second->id())].push_back( pair (max(edgeIds[k].first->id(),edgeIds[k].second->id()),edgeNormalLubF[k])); //Now we reset the containers and initialize them edgeSurfaces.clear(); edgeIds.clear(); edgeNormalLubF.clear(); FiniteEdgesIterator ed_it; for ( FiniteEdgesIterator ed_it = Tri.finite_edges_begin(); ed_it!=Tri.finite_edges_end();ed_it++ ) { const VertexInfo& vi1=(ed_it->first)->vertex(ed_it->second)->info(); const VertexInfo& vi2=(ed_it->first)->vertex(ed_it->third)->info(); //We eliminate edges that would be periodic replications or involving two bounding objects, i.e. the min id must be non-ghost and non-fictious if (vi1.id()(&vi1,&vi2)); //For persistant edges, we must transfer the lub. force value from the older triangulation structure if (id1>id2) swap(id1,id2); unsigned int i=0; //Look for the pair (id1,id2) in lubPairs while (i Vector3r FlowBoundingSphere::computeViscousShearForce(const Vector3r& deltaV, const int& edge_id, const Real& Rh) { Vector3r tau = deltaV*viscosity/Rh; return tau * edgeSurfaces[edge_id]; } template Vector3r FlowBoundingSphere::computeShearLubricationForce(const Vector3r& deltaShearV, const Real& dist, const int& edge_id, const Real& eps, const Real& centerDist, const Real& meanRad ) { Real d = max(dist,0.) + 2.*eps*meanRad; Vector3r viscLubF = 0.5 * Mathr::PI * viscosity * (-2*meanRad + centerDist*log(centerDist/d)) * deltaShearV; return viscLubF; } template Vector3r FlowBoundingSphere::computePumpTorque(const Vector3r& deltaShearAngV, const Real& dist, const int& edge_id, const Real& eps, const Real& meanRad ) { Real d = max(dist,0.) + 2.*eps*meanRad; Vector3r viscPumpC = Mathr::PI * viscosity * pow(meanRad,3) *(3./20. * log(meanRad/d) + 63./500. * (d/meanRad) * log(meanRad/d)) * deltaShearAngV; return viscPumpC; } template Vector3r FlowBoundingSphere::computeTwistTorque(const Vector3r& deltaNormAngV, const Real& dist, const int& edge_id, const Real& eps, const Real& meanRad ) { Real d = max(dist,0.) + 2.*eps*meanRad; Vector3r twistC = Mathr::PI * viscosity * pow(meanRad,2) * d * log(meanRad/d) * deltaNormAngV; return twistC; } template Real FlowBoundingSphere::computeNormalLubricationForce(const Real& deltaNormV, const Real& dist, const int& edge_id, const Real& eps, const Real& stiffness, const Real& dt, const Real& meanRad) { //FIXME: here introduce elasticity Real d = max(dist,0.) + 2.*eps*meanRad;//account for grains roughness if (stiffness>0) { const Real k = stiffness*meanRad; Real prevForce = edgeNormalLubF[edge_id]; Real instantVisc = 1.5*Mathr::PI*pow(meanRad,2)*viscosity/(d-prevForce/k); Real normLubF = instantVisc*(deltaNormV + prevForce/(k*dt))/(1+instantVisc/(k*dt)); edgeNormalLubF[edge_id]=normLubF; return normLubF; } else { Real normLubF = (1.5*Mathr::PI*pow(meanRad,2)* viscosity* deltaNormV)/d; return normLubF; } } template double FlowBoundingSphere::fractionalSolidArea(CellHandle cell, int j) { double area; int k=0,l=0,m=0; if(j==0){k=1; l=2; m=3;} if(j==1){k=0; l=2; m=3;} if(j==2){k=1; l=0; m=3;} if(j==3){k=1; l=2; m=0;} area = this->fastSphericalTriangleArea(cell->vertex(j)->point(), cell->vertex(k)->point().point(), cell->vertex(l)-> point().point(), cell->vertex(m)-> point().point()); return area; } } //namespace CGT #endif //FLOW_ENGINE trunk-2018.02b/lib/triangulation/FlowBoundingSphereLinSolv.hpp000066400000000000000000000137421324306050200244070ustar00rootroot00000000000000/************************************************************************* * Copyright (C) 2010 by Bruno Chareyre * * * * This program is free software; it is licensed under the terms of the * * GNU General Public License v2 or later. See file LICENSE for details. * *************************************************************************/ #ifdef FLOW_ENGINE #pragma once #define CHOLMOD_LIBS //comment this if CHOLMOD is not available // #define TAUCS_LIB //comment this if TAUCS lib is not available, it will disable PARDISO lib as well #ifdef CHOLMOD_LIBS #include #include #include #include #endif #ifdef TAUCS_LIB #define TAUCS_CORE_DOUBLE #include //THIS ONE MUST ABSOLUTELY BE INCLUDED BEFORE TAUCS.H! #include #include //#include extern "C" { #include "taucs.h" } #endif #include "FlowBoundingSphere.hpp" ///_____ Declaration ____ namespace CGT { template > class FlowBoundingSphereLinSolv : public FlowType { public: DECLARE_TESSELATION_TYPES(FlowType) typedef typename FlowType::Tesselation Tesselation; using FlowType::useSolver; using FlowType::T; using FlowType::currentTes; using FlowType::boundary; using FlowType::yMinId; using FlowType::yMaxId; using FlowType::debugOut; using FlowType::tolerance; using FlowType::relax; using FlowType::fluidBulkModulus; using FlowType::reApplyBoundaryConditions; using FlowType::pressureChanged; using FlowType::computedOnce; using FlowType::resetNetwork; using FlowType::tesselation; using FlowType::resetRHS; using FlowType::factorizeOnly; // used for backgroundAction() //! TAUCS DECs vector orderedCells; bool isLinearSystemSet; bool isFullLinearSystemGSSet; bool areCellsOrdered;//true when orderedCells is filled, turn it false after retriangulation bool updatedRHS; #ifdef CHOLMOD_LIBS //Eigen's sparse matrix and solver Eigen::SparseMatrix A; typedef Eigen::Triplet ETriplet; std::vector tripletList;//The list of non-zero components in Eigen sparse matrix Eigen::CholmodDecomposition, Eigen::Lower > eSolver; bool factorizedEigenSolver; void exportMatrix(const char* filename) {ofstream f; f.open(filename); f<::InnerIterator it(A,k); it; ++it) f<< it.row()<<" "<< it.col()<<" "<nnz; ((long*)T->i)[k] = r; ((long*)T->j)[k] = c; ((double*)T->x)[k] = x; T->nnz++; } #endif #ifdef TAUCS_LIB taucs_ccs_matrix SystemMatrix; taucs_ccs_matrix* T_A; taucs_ccs_matrix* Fccs; void* F;//The taucs factor #endif int T_nnz; int ncols; int T_size; double pTime1, pTime2; int pTimeInt, pTime1N, pTime2N; double ZERO; vector T_an;//(size*5); vector T_jn;//(size+1); vector T_ia;//(size*5); vector T_f;//(size); // right-hand size vector object vector T_cells;//(size) int T_index; vector T_b; vector T_bv; vector T_x, P_x; vector bodv; vector xodv; int* perm; int* invperm; bool pardisoInitialized; //! END TAUCS DECs //! Pardiso int* ia; int* ja; double* a; int nnz; int mtype; /* Real symmetric positive def. matrix */ double* b; double* x;// the unknown vector to solve Ax=b int nrhs; /* Number of right hand sides. */ void *pt[64]; int iparm[64]; double dparm[64]; int maxfct, mnum, phase, error, msglvl, solver; int num_procs; char *var; int i; double ddum; /* Double dummy */ int idum; /* Integer dummy. */ //! end pardiso /// EXTERNAL_GS part vector > fullAvalues;//contains Kij's and 1/(sum Kij) in 5th value (for use in GuaussSeidel) vector > fullAcolumns;//contains columns numbers vector gsP;//a vector of pressures vector gsdV;//a vector of dV vector gsB;//a vector of dV public: virtual ~FlowBoundingSphereLinSolv(); FlowBoundingSphereLinSolv(); ///Linear system solve virtual int setLinearSystem(Real dt); void vectorizedGaussSeidel(Real dt); virtual int setLinearSystemFullGS(Real dt); int taucsSolveTest(); int taucsSolve(Real dt); int pardisoSolveTest(); int pardisoSolve(Real dt); int eigenSolve(Real dt); int cholmodSolve(Real dt); void copyGsToCells(); void copyCellsToGs(Real dt); void copyLinToCells(); void copyCellsToLin(Real dt); void swapFwd (double* v, int i); void swapFwd (int* v, int i); void sortV(int k1, int k2, int* is, double* ds); virtual void gaussSeidel (Real dt) { switch (useSolver) { case 0: vectorizedGaussSeidel(dt); break; case 1: taucsSolve(dt); break; case 2: pardisoSolve(dt); break; case 3: eigenSolve(dt); break; case 4: cholmodSolve(dt); break; } computedOnce=true; } virtual void resetLinearSystem(); virtual void resetRHS() {updatedRHS=false;}; }; } //namespace CGT ///_____ Implementation ____ #include "FlowBoundingSphereLinSolv.ipp" #endif trunk-2018.02b/lib/triangulation/FlowBoundingSphereLinSolv.ipp000066400000000000000000001100741324306050200244040ustar00rootroot00000000000000/************************************************************************* * Copyright (C) 2010 by Bruno Chareyre * * * * This program is free software; it is licensed under the terms of the * * GNU General Public License v2 or later. See file LICENSE for details. * *************************************************************************/ #ifdef FLOW_ENGINE // #define XVIEW #include "FlowBoundingSphereLinSolv.hpp"//include after #define XVIEW #if CGAL_VERSION_NR < CGAL_VERSION_NUMBER(4,11,0) #include "CGAL/constructions/constructions_on_weighted_points_cartesian_3.h" #endif #include #include #include #include #include #include "vector" #include #include #include #ifdef XVIEW //#include "Vue3D.h" //FIXME implicit dependencies will look for this class (out of tree) even ifndef XVIEW #endif #ifdef YADE_OPENMP #include // #define GS_OPEN_MP //It should never be defined if Yade is not using openmp #endif // #define PARDISO //comment this if pardiso lib is not available #ifdef CHOLMOD_LIBS extern "C" { void openblas_set_num_threads(int num_threads); } #endif namespace CGT { #ifdef PARDISO #ifdef AIX #define F77_FUNC(func) func #else #define F77_FUNC(func) func ## _ #endif /* PARDISO prototype. */ extern "C" int F77_FUNC(pardisoinit) (void *, int *, int *, int *, double *, int *); extern "C" int F77_FUNC(pardiso) (void *, int *, int *, int *, int *, int *, double *, int *, int *, int *, int *, int *, int *, double *, double *, int *, double *); #endif #ifdef XVIEW Vue3D Vue1; #endif template FlowBoundingSphereLinSolv<_Tesselation,FlowType>::~FlowBoundingSphereLinSolv() { #ifdef TAUCS_LIB if (Fccs) taucs_ccs_free(Fccs); #endif #ifdef SUITESPARSE_VERSION_4 if (useSolver == 4){ cholmod_l_free_sparse(&Achol, &com); cholmod_l_free_factor(&L, &com); cholmod_l_finish(&com); //cout << "Achol memory freed and cholmod finished" < FlowBoundingSphereLinSolv<_Tesselation,FlowType>::FlowBoundingSphereLinSolv(): FlowType() { useSolver=0; isLinearSystemSet=0; isFullLinearSystemGSSet=0; areCellsOrdered=0;//true when orderedCells is filled, turn it false after retriangulation updatedRHS=false; ZERO=0; #ifdef TAUCS_LIB T_A = &SystemMatrix; F = NULL;//The taucs factor Fccs = NULL;//The taucs factor in CCS format #endif pardisoInitialized=false; pTimeInt=0;pTime1N=0;pTime2N=0; pTime1=0;pTime2=0; #ifdef CHOLMOD_LIBS factorizedEigenSolver=false; numFactorizeThreads=1; numSolveThreads=1; #endif #ifdef SUITESPARSE_VERSION_4 cholmod_l_start(&com); com.useGPU=1; //useGPU; com.supernodal = CHOLMOD_AUTO; //CHOLMOD_SUPERNODAL; #endif } template void FlowBoundingSphereLinSolv<_Tesselation,FlowType>::swapFwd (double* v, int i) {double temp = v[i]; v[i]=v[i+1]; v[i+1]=temp;} template void FlowBoundingSphereLinSolv<_Tesselation,FlowType>::swapFwd (int* v, int i) {double temp = v[i]; v[i]=v[i+1]; v[i+1]=temp;} //spatial sort traits to use with a pair of CGAL::sphere pointers and integer. //template // usage : spatial_sort(pointsPtrs.begin(),pointsPtrs.end(), RTraits_for_spatial_sort()/*, CGT::RTriangulation::Weighted_point*/); #include template struct CellTraits_for_spatial_sort : public Triangulation::Geom_traits { typedef typename Triangulation::Geom_traits Gt; typedef const typename Triangulation::Finite_cells_iterator Point_3; struct Less_x_3 {bool operator()(const Point_3& p,const Point_3& q) const { return typename Gt::Less_x_3()(p->info(),q->info());} }; struct Less_y_3 {bool operator()(const Point_3& p,const Point_3& q) const { return typename Gt::Less_y_3()(p->info(),q->info());} }; struct Less_z_3 {bool operator()(const Point_3& p,const Point_3& q) const { return typename Gt::Less_z_3()(p->info(),q->info());} }; Less_x_3 less_x_3_object() const {return Less_x_3();} Less_y_3 less_y_3_object() const {return Less_y_3();} Less_z_3 less_z_3_object() const {return Less_z_3();} }; template void FlowBoundingSphereLinSolv<_Tesselation,FlowType>::resetLinearSystem() { FlowType::resetLinearSystem(); isLinearSystemSet=false; isFullLinearSystemGSSet=false; areCellsOrdered=false; #ifdef TAUCS_LIB if (F) taucs_supernodal_factor_free(F); F=NULL; if (Fccs) taucs_ccs_free(Fccs); Fccs=NULL; #endif #ifdef CHOLMOD_LIBS factorizedEigenSolver=false; #endif #ifdef PARDISO if (pardisoInitialized) { phase = -1; F77_FUNC(pardiso)(pt, &maxfct, &mnum, &mtype, &phase, &ncols, &ddum, NULL, NULL, &idum, &nrhs, iparm, &msglvl, &ddum, &ddum, &error, dparm); pardisoInitialized=false; } #endif } template int FlowBoundingSphereLinSolv<_Tesselation,FlowType>::setLinearSystem(Real dt) { RTriangulation& Tri = T[currentTes].Triangulation(); int n_cells=Tri.number_of_finite_cells(); vector clen; vector is; vector js; vector vs; if (!areCellsOrdered) { T_nnz=0; ncols=0; ///Ordered cells orderedCells.clear(); const FiniteCellsIterator cellEnd = Tri.finite_cells_end(); for (FiniteCellsIterator cell = Tri.finite_cells_begin(); cell != cellEnd; cell++) { orderedCells.push_back(cell); cell->info().index=0; if (!cell->info().Pcondition && !cell->info().blocked) ++ncols;} // //Segfault on 14.10, and useless overall since SuiteSparse has preconditionners (including metis) // spatial_sort(orderedCells.begin(),orderedCells.end(), CellTraits_for_spatial_sort()); T_cells.clear(); T_index=0; isLinearSystemSet=false; areCellsOrdered=true; } if (!isLinearSystemSet) { #ifdef TAUCS_LIB if (Fccs) taucs_ccs_free(Fccs);//delete the old factor #endif int n = 3*(ncols+1);//number of non-zero in triangular matrix is.resize(n); js.resize(n); vs.resize(n); T_x.resize(ncols); T_b.resize(ncols); T_bv.resize(ncols); bodv.resize(ncols); xodv.resize(ncols); // gsB.resize(ncols+1); T_cells.resize(ncols+1); T_nnz=0;} for (int kk=0; kkinfo().Pcondition && !cell->info().blocked) { index=cell->info().index; if (index==0) { T_cells[++T_index]=cell; cell->info().index=index=T_index; } if (!isLinearSystemSet) { //Add diagonal term is[T_nnz] = index; js[T_nnz] = index; vs[T_nnz]=0; for (int j=0;j<4;j++) if (!cell->neighbor(j)->info().blocked) vs[T_nnz]+= (cell->info().kNorm())[j]; // vs[T_nnz] = (cell->info().kNorm())[0]+ (cell->info().kNorm())[1]+ (cell->info().kNorm())[2]+ (cell->info().kNorm())[3]; if (fluidBulkModulus>0) vs[T_nnz] += (1.f/(dt*fluidBulkModulus*cell->info().invVoidVolume())); ++T_nnz; } for (int j=0; j<4; j++) { neighbourCell = cell->neighbor(j); nIndex=neighbourCell->info().index; if (Tri.is_infinite(neighbourCell)) continue; if (!isLinearSystemSet && !(neighbourCell->info().Pcondition || neighbourCell->info().blocked)) { if (nIndex==0) { T_cells[++T_index]=neighbourCell; neighbourCell->info().index=nIndex=T_index; } else if (index > nIndex) { is[T_nnz] = index; js[T_nnz] = nIndex; vs[T_nnz] = - (cell->info().kNorm())[j]; T_nnz++; } } else if (neighbourCell->info().Pcondition && !neighbourCell->info().blocked) { //ADD TO b, FIXME : use updated volume change T_b[index-1]+=cell->info().kNorm()[j]*neighbourCell->info().p(); } } } } updatedRHS = true; if (!isLinearSystemSet) { if (useSolver==1 || useSolver==2){ #ifdef TAUCS_LIB clen.resize(ncols+1); T_jn.resize(ncols+1); T_A->colptr = &T_jn[0]; T_ia.resize(T_nnz); T_A->rowind = &T_ia[0]; T_A->flags = (TAUCS_DOUBLE | TAUCS_SYMMETRIC | TAUCS_LOWER); T_an.resize(T_nnz); T_A->values.d = &T_an[0]; T_A->n = ncols; T_A->m = ncols; int i,j,k; for (j=0; jcolptr[j]) = k; k += tmp; } clen[ncols] = (T_A->colptr[ncols]) = k; /* now read matrix into data structure */ for (k=0; ktaucs_values)[clen[j]] = vs[k]; (T_A->rowind)[clen[j]] = i; clen[j] ++; // cerr<<"i="<< i <<" j="<< j<<" v="<nnz, &com); //cholmod_l_print_sparse(Achol, "Achol", &com); cholmod_l_free_triplet(&T, &com); #endif } isLinearSystemSet=true; } return ncols; } template void FlowBoundingSphereLinSolv<_Tesselation,FlowType>::copyGsToCells() {for (int ii=1; ii<=ncols; ii++) T_cells[ii]->info().p()=gsP[ii];} template void FlowBoundingSphereLinSolv<_Tesselation,FlowType>::copyCellsToGs (Real dt) { for (int ii=1; ii<=ncols; ii++){ gsP[ii]=T_cells[ii]->info().p(); gsdV[ii]= T_cells[ii]->info().dv(); if (fluidBulkModulus>0) { gsdV[ii] -= T_cells[ii]->info().p()/(fluidBulkModulus*dt*T_cells[ii]->info().invVoidVolume());} } } template void FlowBoundingSphereLinSolv<_Tesselation,FlowType>::copyLinToCells() {for (int ii=1; ii<=ncols; ii++) T_cells[ii]->info().p()=T_x[ii-1];} template void FlowBoundingSphereLinSolv<_Tesselation,FlowType>::copyCellsToLin (Real dt) { for (int ii=1; ii<=ncols; ii++) { T_bv[ii-1]=T_b[ii-1]-T_cells[ii]->info().dv(); if (fluidBulkModulus>0) T_bv[ii-1] += T_cells[ii]->info().p()/(fluidBulkModulus*dt*T_cells[ii]->info().invVoidVolume());} } /// For Gauss Seidel, we need the full matrix template // int FlowBoundingSphereLinSolv<_Tesselation,FlowType>::SetLinearSystemFullGS() int FlowBoundingSphereLinSolv<_Tesselation,FlowType>::setLinearSystemFullGS(Real dt) { //WARNING : boundary conditions (Pcondition, p values) must have been set for a correct definition RTriangulation& Tri = T[currentTes].Triangulation(); int n_cells=Tri.number_of_finite_cells(); if (!areCellsOrdered) { T_cells.clear(); T_index=0; T_nnz=0; ncols=0; const FiniteCellsIterator cellEnd = Tri.finite_cells_end(); orderedCells.clear(); for (FiniteCellsIterator cell = Tri.finite_cells_begin(); cell != cellEnd; cell++) { orderedCells.push_back(cell); if (!cell->info().Pcondition && !cell->info().blocked) ++ncols; } //FIXME: does it really help? test by commenting this "sorting" line spatial_sort(orderedCells.begin(),orderedCells.end(), CellTraits_for_spatial_sort()); // double pZero=0; // if (yMinId>=0 and yMaxId>yMinId) pZero = abs((boundary(yMinId).value-boundary(yMaxId).value)/2); gsP.resize(ncols+1); // _gsP.resize(ncols+1); gsB.resize(ncols+1); T_b.resize(ncols+1); gsdV.resize(ncols+1); fullAcolumns.resize(ncols+1); fullAvalues.resize(ncols+1); T_cells.resize(ncols+1); for (int k=0; k<=ncols;k++) { fullAcolumns[k].resize(4); fullAvalues[k].resize(5); gsdV[k]=0; // gsP[k]=pZero; } // _gsP[0]= &ZERO; gsP[0]=0; areCellsOrdered=true; isFullLinearSystemGSSet=false; } for (int k=0; k<=ncols;k++) gsB[k]=0; ///we build the full matrix + RHS here, else only the RHS in the other loop if (!isFullLinearSystemGSSet) ///Ordered cells for (int i=0; iinfo().Pcondition && !cell->info().blocked) { if (cell->info().index==0) { T_cells[++T_index]=cell; cell->info().index=T_index; } gsP[cell->info().index]=cell->info().pression; //Add diagonal term double num = (cell->info().kNorm())[0]+ (cell->info().kNorm())[1]+ (cell->info().kNorm())[2]+ (cell->info().kNorm())[3]; if (fluidBulkModulus>0) num += (1.f/(dt*fluidBulkModulus*cell->info().invVoidVolume())); fullAvalues[cell->info().index][4] = 1.f/num; ++T_nnz; for (int j=0; j<4; j++) { CellHandle neighbourCell = cell->neighbor(j); if (Tri.is_infinite(neighbourCell)) { fullAvalues[cell->info().index][j] = 0; fullAcolumns[cell->info().index][j] = &gsP[0]; continue;} if (!neighbourCell->info().Pcondition) { if (neighbourCell->info().index==0) { T_cells[++T_index]=neighbourCell; neighbourCell->info().index=T_index; } ++T_nnz; fullAvalues[cell->info().index][j] = (cell->info().kNorm())[j]; fullAcolumns[cell->info().index][j] = &gsP[neighbourCell->info().index]; } else { fullAvalues[cell->info().index][j] = 0; fullAcolumns[cell->info().index][j] = &gsP[0]; gsB[cell->info().index]+=cell->info().kNorm()[j]*neighbourCell->info().p(); } } } } ///define only the new RHS, accouting for new imposed pressures else ///Ordered cells for (int i=0; iinfo().Pcondition && !cell->info().blocked) for (int j=0; j<4; j++) { CellHandle neighbourCell = cell->neighbor(j); if (!Tri.is_infinite(neighbourCell) && neighbourCell->info().Pcondition) gsB[cell->info().index]+=cell->info().kNorm()[j]*neighbourCell->info().p(); } } isFullLinearSystemGSSet=true; return ncols; } template void FlowBoundingSphereLinSolv<_Tesselation,FlowType>::vectorizedGaussSeidel(Real dt) { // cout<<"VectorizedGaussSeidel"< t_sum_p, t_dp_max, t_sum_dp, t_p_max; t_sum_dp.resize(num_threads); t_dp_max.resize(num_threads); t_p_max.resize(num_threads); t_sum_p.resize(num_threads); #endif int j2=-1; dp_max = 0; p_max = 0; p_moy=0; dp_moy=0; sum_p=0; sum_dp=0; do { if (++j2>=10) j2=0;//compute max/mean only each 10 iterations if (j2==0) { dp_max = 0; p_max = 0; p_moy=0; dp_moy=0; sum_p=0; sum_dp=0; #ifdef GS_OPEN_MP for (int ii=0;ii void FlowBoundingSphereLinSolv<_Tesselation,FlowType>::sortV(int k1, int k2, int* is, double* ds){ for (int k=k1; k=k1 && is[kk]>is[kk+1]) { swapFwd(is,kk); swapFwd(ds,kk); --kk;} } } template int FlowBoundingSphereLinSolv<_Tesselation,FlowType>::eigenSolve(Real dt) { #ifdef CHOLMOD_LIBS if (!isLinearSystemSet || (isLinearSystemSet && reApplyBoundaryConditions()) || !updatedRHS) ncols = setLinearSystem(dt); copyCellsToLin(dt); //FIXME: we introduce new Eigen vectors, then we have to copy from/to c-arrays, can be optimized later Eigen::VectorXd eb(ncols); Eigen::VectorXd ex(ncols); for (int k=0; k0) { cerr << "something went wrong in Cholesky factorization, use LDLt as fallback this time" << eSolver.cholmod().status << endl; eSolver.setMode(Eigen::CholmodLDLt); eSolver.compute(A); } factorizedEigenSolver = true; } // backgroundAction only wants to factorize, no need to solve and copy to cells. if (!factorizeOnly){ //openblas_set_num_threads(numSolveThreads); ex = eSolver.solve(eb); for (int k=0; k int FlowBoundingSphereLinSolv<_Tesselation,FlowType>::cholmodSolve(Real dt) { #ifdef SUITESPARSE_VERSION_4 if (!isLinearSystemSet || (isLinearSystemSet && reApplyBoundaryConditions()) || !updatedRHS) ncols = setLinearSystem(dt); copyCellsToLin(dt); cholmod_dense* B = cholmod_l_zeros(ncols, 1, Achol->xtype, &com); double* B_x =(double *) B->x; for (int k=0; kx; for (int k=0; k int FlowBoundingSphereLinSolv<_Tesselation,FlowType>::taucsSolve(Real dt) { #ifdef TAUCS_LIB if (debugOut) cerr <LinSolver) : " << taucs_ctime()-t << endl; t = taucs_ctime(); //taucs_logfile("stdout");//! VERY USEFULL!!!!!!!!!!! (disable to exclude output time from taucs_ctime() measurments) taucs_double* x = &T_x[0];// the unknown vector to solve Ax=b taucs_double* bod = &bodv[0]; taucs_double* xod = &xodv[0]; if (Fccs==NULL) { if (debugOut) cerr << "_entering taucs_" << endl; // 1) Reordering taucs_ccs_order(T_A, &perm, &invperm, (char*)"metis"); if (debugOut) cerr << "_entering taucs_" << endl; taucs_ccs_matrix* Aod; if (debugOut) cerr << "_entering taucs_" << endl; Aod = taucs_ccs_permute_symmetrically(T_A, perm, invperm); if (debugOut) cerr << "Reordering : " << taucs_ctime()-t << endl; t = taucs_ctime(); // 2) Factoring F = taucs_ccs_factor_llt_mf(Aod); if (F==NULL) cerr<<"factorization failed"<Yade) : " << taucs_ctime()-t << endl; if (debugOut) cerr << "Total TAUCS time ................ : " << taucs_ctime()-t2 << endl; #else cerr<<"Flow engine not compiled with taucs, nothing computed if useSolver=1"< int FlowBoundingSphereLinSolv<_Tesselation,FlowType>::pardisoSolve(Real dt) { cerr <colptr; ja = T_A->rowind; a = T_A->values.d; if (debugOut) cerr<99) { cout <<"Pardiso..... "< int FlowBoundingSphereLinSolv<_Tesselation,FlowType>::pardisoSolveTest() { #ifndef PARDISO return 0; #else /* Matrix data. */ double t = taucs_ctime();//timer bool wasLSystemSet= isLinearSystemSet; int n = setLinearSystem(); // ncols=n;//for VectorizesGS cout<colptr; int* ja = T_A->rowind; double* a = T_A->values.d; if (!wasLSystemSet) for (int k=0; k int FlowBoundingSphereLinSolv<_Tesselation,FlowType>::taucsSolveTest() { #ifdef TAUCS_LIB cout < bodv(ncols); taucs_double* bod = &*bodv.begin(); vector xodv(ncols); taucs_double* xod = &*xodv.begin(); int* perm; int* invperm; taucs_ccs_matrix* Aod; t = taucs_ctime(); double t2 = taucs_ctime(); // 1) Reordering taucs_ccs_order(T_A, &perm, &invperm, "metis"); Aod = taucs_ccs_permute_symmetrically(T_A, perm, invperm); taucs_vec_permute(ncols, TAUCS_DOUBLE, &T_b[0], bod, perm); cout << "Reordering : " << taucs_ctime()-t << endl; t = taucs_ctime(); // 2) Factoring F = taucs_ccs_factor_llt_mf(Aod); //F = taucs_ccs_factor_llt_mf(T_A); cout << "Factoring : " << taucs_ctime()-t << endl; t = taucs_ctime(); // 3) Back substitution and reodering the solution back double t4 = taucs_ctime(); // for (int k=0;k<10;k++){ taucs_supernodal_solve_llt(F, xod, bod); // cout << "B3) Solving : " << taucs_ctime()-t << endl; t = taucs_ctime(); taucs_vec_ipermute(ncols, TAUCS_DOUBLE, xod, x, perm); // cout << "B4) Deordering : " << taucs_ctime()-t << endl; t = taucs_ctime(); // } double T4=taucs_ctime()-t4; cout << "Solving : " << T4 << endl; cout << "Low level reordered total time : " << taucs_ctime()-t2 << endl; t2 = taucs_ctime(); taucs_supernodal_factor_free(F); taucs_ccs_free(Aod); /// Using TAUCS inverse factoring // t = taucs_ctime(); t2 = taucs_ctime(); // F=taucs_ccs_factor_xxt(Aod); // cout << "C1_) inverse factoring (reordered) : " << taucs_ctime()-t << endl; t = taucs_ctime(); // taucs_ccs_solve_xxt (F,xod,bod);///REALLY TOO SLOW!! // cout << "C2) solve : " << taucs_ctime()-t << endl; t = taucs_ctime(); // taucs_vec_ipermute(ncols, TAUCS_DOUBLE, xod, x, perm); // cout << "C3) reordering : " << taucs_ctime()-t << endl; t = taucs_ctime(); // cout << "Inverse factoring total time : " << taucs_ctime()-t2 << endl; // taucs_linsolve(NULL, &F, 0, NULL, NULL, NULL, NULL); // taucs_ccs_free(Aod); // ofstream file("result.dat"); // if (!file.is_open()) cout << "problem opening file"; // for (unsigned i = 0; i < M ; i++) { // for (unsigned j = 0; j < L ; j++) file << i << " "<< j << " "<< x[index(i,j)] << endl; // file << endl << endl; // } // const FiniteCellsIterator cell_end = T[currentTes].Triangulation().finite_cells_end(); // if (debugOut) for (FiniteCellsIterator cell = T[currentTes].Triangulation().finite_cells_begin(); cell != cell_end; cell++) // { // // if (cell->info().index>0) { // cerr <<"ix "<< cell->info().index<<" "; // cerr <info().p()<<" "<info().index]<<" "<info().index-1)] <<" "<info().index-1)]<<" err="<<100*(cell->info().p()-x[max((unsigned int)0,cell->info().index-1)])/x[max((unsigned int)0,cell->info().index-1)]<<"%"<info().index==0) { // cerr <<"ix "<< cell->info().index<<" "; // cerr <info().p()<<" "<<"NaN"< #include "basicVTKwritter.hpp" //#include namespace CGT { int n_debug = 0; std::string _itoa(int i) { std::ostringstream buffer; buffer << i; return buffer.str(); } KinematicLocalisationAnalyser::KinematicLocalisationAnalyser() { sphere_discretisation = SPHERE_DISCRETISATION; linear_discretisation = LINEAR_DISCRETISATION; consecutive = false; bz2=true; TS1=new TriaxialState; TS0=new TriaxialState; } KinematicLocalisationAnalyser::~KinematicLocalisationAnalyser() { delete(TS1); delete(TS0); } KinematicLocalisationAnalyser::KinematicLocalisationAnalyser(const char* state_file1, bool usebz2) { sphere_discretisation = SPHERE_DISCRETISATION; linear_discretisation = LINEAR_DISCRETISATION; consecutive = false; bz2=true; TS1 = new(TriaxialState); TS0 = NULL; TS1->from_file(state_file1,/*use bz2?*/ bz2); } KinematicLocalisationAnalyser::KinematicLocalisationAnalyser(const char* state_file1, const char* state_file0, bool consecutive_files, bool usebz2) { consecutive = consecutive_files; bz2 = usebz2; sphere_discretisation = SPHERE_DISCRETISATION; linear_discretisation = LINEAR_DISCRETISATION; TS1 = new(TriaxialState); TS0 = new(TriaxialState); TS1->from_file(state_file1,/*use bz2?*/ bz2); TS0->from_file(state_file0,/*use bz2?*/ bz2); Delta_epsilon(3,3) = TS1->eps3 - TS0->eps3; Delta_epsilon(1,1) = TS1->eps1 - TS0->eps1; Delta_epsilon(2,2) = TS1->eps2 - TS0->eps2; } const vector& KinematicLocalisationAnalyser::computeParticlesDeformation(const char* state_file1, const char* state_file0, bool usebz2) { consecutive = false; bz2 = usebz2; TS1->from_file(state_file1,/*use bz2?*/ bz2); TS0->from_file(state_file0,/*use bz2?*/ bz2); //FIXME: redundant? Delta_epsilon(3,3) = TS1->eps3 - TS0->eps3; Delta_epsilon(1,1) = TS1->eps1 - TS0->eps1; Delta_epsilon(2,2) = TS1->eps2 - TS0->eps2; return computeParticlesDeformation(); } KinematicLocalisationAnalyser::KinematicLocalisationAnalyser(const char* base_name, int n0, int n1, bool usebz2) { file_number_1 = n1; file_number_0 = n0; base_file_name = string(base_name); consecutive = ((n1-n0)==1); bz2 = usebz2; sphere_discretisation = SPHERE_DISCRETISATION; linear_discretisation = LINEAR_DISCRETISATION; TS1 = new(TriaxialState); TS0 = new(TriaxialState); std::ostringstream file_name1, file_name0; file_name1 << (string)(base_file_name) << n1; file_name0 << (string)(base_file_name) << n0; TS1->from_file(file_name1.str().c_str(), bz2); TS0->from_file(file_name0.str().c_str(), bz2); Delta_epsilon(3,3) = TS1->eps3 - TS0->eps3; Delta_epsilon(1,1) = TS1->eps1 - TS0->eps1; Delta_epsilon(2,2) = TS1->eps2 - TS0->eps2; } void KinematicLocalisationAnalyser::SetBaseFileName(string name) { base_file_name = name; } bool KinematicLocalisationAnalyser::SetFileNumbers(int n0, int n1) { bool bf0 = false; bool bf1 = false; if (file_number_0 != n0) { if (file_number_1 != n0) { //file_name = base_file_name + n0; bf0 = TS0->from_file((base_file_name +_itoa(file_number_0)).c_str(), bz2); } else { delete(TS0); TS0 = TS1; bf0=true; TS1 = new(TriaxialState); //file_name = base_file_name + string(n1); bf1 = TS1->from_file((base_file_name + _itoa(file_number_1)).c_str(), bz2); } } else if (n1 != file_number_1) { //file_name = base_file_name + string(n1); bf0 = true; bf1 = TS1->from_file((base_file_name + _itoa(file_number_1)).c_str(), bz2);} file_number_1 = n1; file_number_0 = n0; consecutive = ((n1-n0) ==1); Delta_epsilon(3,3) = TS1->eps3 - TS0->eps3; Delta_epsilon(1,1) = TS1->eps1 - TS0->eps1; Delta_epsilon(2,2) = TS1->eps2 - TS0->eps2; return (bf0 && bf1); } void KinematicLocalisationAnalyser::SetConsecutive(bool t) { consecutive = t; } void KinematicLocalisationAnalyser::SetNO_ZERO_ID(bool t) { TS0->NO_ZERO_ID = t; TS1->NO_ZERO_ID = t; } void KinematicLocalisationAnalyser::SwitchStates(void) { TriaxialState* TStemp = TS0; TS0 = TS1; TS1 = TStemp; } vector& KinematicLocalisationAnalyser::Oriented_Filtered_edges(Real Nymin, Real Nymax, vector& filteredList) { RTriangulation& T = TS1->tesselation().Triangulation(); filteredList.clear(); Edge_iterator ed_end = T.edges_end(); for (Edge_iterator ed_it = T.edges_begin(); ed_it != ed_end; ++ed_it) { if (!T.is_infinite(*ed_it) && TS1->inside(T.segment(*ed_it).source()) && TS1->inside(T.segment(*ed_it).target())) { Segment s = T.segment(*ed_it); CVector v = s.to_vector(); Real ny = std::abs(v.y()/sqrt(s.squared_length())); if (Nymin < ny && ny <= Nymax) filteredList.push_back(ed_it); } } return filteredList; } bool KinematicLocalisationAnalyser::DefToFile(const char* state_file1, const char* state_file0, const char* output_file_name, bool usebz2) { consecutive = false; bz2 = usebz2; TS1->from_file(state_file1,/*use bz2?*/ bz2); TS0->from_file(state_file0,/*use bz2?*/ bz2); DefToFile(output_file_name); return 0; } bool KinematicLocalisationAnalyser::DefToFile(const char* output_file_name) { computeParticlesDeformation(); Tesselation& Tes = TS1->tesselation(); RTriangulation& Tri = Tes.Triangulation(); basicVTKwritter vtk(n_real_vertices, n_real_cells); vtk.open(output_file_name, "Output file generated by Yade's KinematicLocalisationAnalyser"); vtk.begin_vertices(); RTriangulation::Finite_vertices_iterator V_it = Tri.finite_vertices_begin(); bool beginWithFictious = V_it->info().isFictious; for (; V_it != Tri.finite_vertices_end(); ++V_it) if (!V_it->info().isFictious) vtk.file << V_it->point().point() << endl; vtk.end_vertices(); vtk.begin_cells(); Finite_cells_iterator cell = Tri.finite_cells_begin(); //FIXME : Preconditions : the fictious bounds are first in the list for (; cell != Tri.finite_cells_end(); ++cell) { if (!cell->info().isFictious) vtk.write_cell( cell->vertex(0)->info().id()- (beginWithFictious?n_fictious_vertices:0), cell->vertex(1)->info().id()- (beginWithFictious?n_fictious_vertices:0), cell->vertex(2)->info().id()- (beginWithFictious?n_fictious_vertices:0), cell->vertex(3)->info().id()- (beginWithFictious?n_fictious_vertices:0) ); } vtk.end_cells(); vtk.begin_data("Strain_matrix", POINT_DATA, TENSORS, FLOAT); V_it = Tri.finite_vertices_begin(); for (; V_it != Tri.finite_vertices_end(); ++V_it) { if (!V_it->info().isFictious) { Tenseur_sym3 epsilon(ParticleDeformation[V_it->info().id()]); vtk.file << ParticleDeformation[V_it->info().id()] << endl;} } vtk.end_data(); vtk.begin_data("Strain_deviator", POINT_DATA, SCALARS, FLOAT); V_it = Tri.finite_vertices_begin(); for (; V_it != Tri.finite_vertices_end(); ++V_it) { if (!V_it->info().isFictious) { Tenseur_sym3 epsilon(ParticleDeformation[V_it->info().id()]); vtk.write_data((float) epsilon.Deviatoric().Norme());} //vtk.write_data((float) epsilon.Deviatoric()(1,1)-epsilon.Deviatoric()(0,0));} } vtk.end_data(); return true; } bool KinematicLocalisationAnalyser::DistribsToFile(const char* output_file_name) { ofstream output_file(output_file_name); if (!output_file.is_open()) { cerr << "Error opening files"; return false;} output_file << "sym_grad_u_total_g (wrong averaged strain):"<< endl << Tenseur_sym3(grad_u_total_g) << endl; output_file << "Total volume = " << v_total << ", grad_u = " << endl << grad_u_total << endl << "sym_grad_u (true average strain): " << endl << Tenseur_sym3(grad_u_total) << endl; output_file << "Macro strain = " << Delta_epsilon << endl; ContactDistributionToFile(output_file); AllNeighborDistributionToFile(output_file); TS1->filter_distance = 2.0; ContactDistributionToFile(output_file); AllNeighborDistributionToFile(output_file); TS1->filter_distance = 4.0; ContactDistributionToFile(output_file); AllNeighborDistributionToFile(output_file); output_file << "Contact_fabric : "; output_file << (Tenseur_sym3) Contact_fabric(*TS1);// << endl; output_file << "Contact_anisotropy : " << Contact_anisotropy(*TS1) << endl << endl; output_file << "Neighbor_fabric : " << Neighbor_fabric(*TS1) << endl; output_file << "Neighbor_anisotropy : " << Neighbor_anisotropy(*TS1) << endl << endl; RTriangulation& T = TS1->tesselation().Triangulation(); Edge_iterator ed_end = T.edges_end(); vector edges; for (Edge_iterator ed_it = T.edges_begin(); ed_it != ed_end; ++ed_it) { if (!T.is_infinite(*ed_it)) { Segment s = T.segment(*ed_it); CVector v = s.to_vector(); Real xx = std::abs(v.z()/sqrt(s.squared_length())); if (xx>0.95) edges.push_back(ed_it); } } NormalDisplacementDistributionToFile(edges, output_file); edges.clear(); for (Edge_iterator ed_it = T.edges_begin(); ed_it != ed_end; ++ed_it) { if (!T.is_infinite(*ed_it)) { Segment s = T.segment(*ed_it); CVector v = s.to_vector(); Real xx = std::abs(v.z()/sqrt(s.squared_length())); if (xx<0.05) edges.push_back(ed_it); } } NormalDisplacementDistributionToFile(edges, output_file); edges.clear(); for (Edge_iterator ed_it = T.edges_begin(); ed_it != ed_end; ++ed_it) { if (!T.is_infinite(*ed_it)) { Segment s = T.segment(*ed_it); CVector v = s.to_vector(); Real xx = std::abs(v.z()/sqrt(s.squared_length())); if (xx>0.65 && xx<0.75) edges.push_back(ed_it); } } NormalDisplacementDistributionToFile(edges, output_file); output_file.close(); return true; } long KinematicLocalisationAnalyser::Filtered_contacts(TriaxialState& state) { long nc1 =0; TriaxialState::ContactIterator cend = state.contacts_end(); for (TriaxialState::ContactIterator cit = state.contacts_begin(); cit!=cend; ++cit) { if (state.inside((*cit)->grain1->sphere.point()) && state.inside((*cit)->grain2->sphere.point())) nc1 += 2; else if (state.inside((*cit)->grain1->sphere.point()) || state.inside((*cit)->grain2->sphere.point())) ++nc1;} return nc1; } long KinematicLocalisationAnalyser::Filtered_neighbors(TriaxialState& state) { long nv1=0; RTriangulation& T = state.tesselation().Triangulation(); Edge_iterator ed_end = T.edges_end(); for (Edge_iterator ed_it = T.edges_begin(); ed_it != ed_end; ++ed_it) { if (!T.is_infinite(*ed_it)) { Segment s(T.segment(*ed_it)); if (state.inside(s.source()) && state.inside(s.target())) nv1 += 2; else if (state.inside(s.source()) || state.inside(s.target())) ++nv1; } } return nv1; } long KinematicLocalisationAnalyser::Filtered_grains(TriaxialState& state) { long ng1 =0; TriaxialState::GrainIterator gend = state.grains_end(); for (TriaxialState::GrainIterator git = state.grains_begin(); git!=gend; ++git) { if (state.inside(git->sphere.point())) ++ng1; } return ng1; } Real KinematicLocalisationAnalyser::Filtered_volume(TriaxialState& state) { return 0; } Real KinematicLocalisationAnalyser::Contact_coordination(TriaxialState& state) { return Filtered_contacts(state) / Filtered_grains(state); } Real KinematicLocalisationAnalyser::Neighbor_coordination(TriaxialState& state) { return Filtered_neighbors(state) / Filtered_grains(state); } Tenseur_sym3 KinematicLocalisationAnalyser::Neighbor_fabric(TriaxialState& state) { RTriangulation& T = state.tesselation().Triangulation(); Edge_iterator ed_end = T.edges_end(); Tenseur_sym3 Tens; CVector v; Segment s; for (Edge_iterator ed_it = T.edges_begin(); ed_it != ed_end; ++ed_it) { if (!T.is_infinite(*ed_it)) { s = T.segment(*ed_it); if (state.inside(s.source()) && state.inside(s.target())) { v = T.segment(*ed_it).to_vector() * (1/sqrt(T.segment(*ed_it).squared_length())); for (int i=1; i<4; i++) for (int j=3; j>=i; j--) Tens(i,j) += 2*v[i-1]*v[j-1]; } else if (state.inside(s.source()) || state.inside(s.target())) { v = T.segment(*ed_it).to_vector() * (1/sqrt(T.segment(*ed_it).squared_length())); for (int i=1; i<4; i++) for (int j=3; j>=i; j--) Tens(i,j) += v[i-1]*v[j-1]; } } } Tens /= Filtered_neighbors(state); return Tens; } Tenseur_sym3 KinematicLocalisationAnalyser::Contact_fabric(TriaxialState& state) { Tenseur_sym3 Tens; CVector v; TriaxialState::ContactIterator cend = state.contacts_end(); for (TriaxialState::ContactIterator cit = state.contacts_begin(); cit!=cend; ++cit) { if (state.inside((*cit)->grain1->sphere.point()) && state.inside((*cit)->grain2->sphere.point())) { v = (*cit)->normal; for (int i=1; i<4; i++) for (int j=3; j>=i; j--) Tens(i,j) += 2*v[i-1]*v[j-1]; } else if (state.inside((*cit)->grain1->sphere.point()) || state.inside((*cit)->grain2->sphere.point())) { v = (*cit)->normal; for (int i=1; i<4; i++) for (int j=3; j>=i; j--) Tens(i,j) += v[i-1]*v[j-1];} } Tens /= Filtered_contacts(state); return Tens; } Real KinematicLocalisationAnalyser::Contact_anisotropy(TriaxialState& state) { Tenseur_sym3 tens(Contact_fabric(state)); return tens.Deviatoric().Norme()/tens.Trace(); } Real KinematicLocalisationAnalyser::Neighbor_anisotropy(TriaxialState& state) { Tenseur_sym3 tens(Neighbor_fabric(state)); return tens.Deviatoric().Norme()/tens.Trace(); } vector >& KinematicLocalisationAnalyser:: NormalDisplacementDistribution(vector& edges, vector > &row) { //cerr << "n_debug=" << n_debug++ << endl; /// DEBUG LINE /// row.clear(); row.resize(linear_discretisation+1); vector Un_values; Un_values.resize(edges.size()); Real UNmin(100000), UNmax(-100000); CVector branch, U; Real Un; Vertex_handle Vh1, Vh2; vector::iterator ed_end = edges.end(); long val_count = 0; //cerr << "n_debug=" << n_debug++ << endl; /// DEBUG LINE /// for (vector::iterator ed_it = edges.begin(); ed_it!=ed_end; ++ed_it) { Vh1= (*ed_it)->first->vertex((*ed_it)->second); Vh2= (*ed_it)->first->vertex((*ed_it)->third); branch = Vh1->point().point()- Vh2->point().point(); NORMALIZE(branch); if (consecutive) U = TS1->grain(Vh1->info().id()).translation - TS1->grain(Vh2->info().id()).translation; else { U = (TS1->grain(Vh1->info().id()).sphere.point() - TS0->grain(Vh1->info().id()).sphere.point()) - (TS1->grain(Vh2->info().id()).sphere.point() - TS0->grain(Vh2->info().id()).sphere.point()); } //Un = (U - (Delta_epsilon*branch))*branch; //Diff�rence par rapport � Un moyen Un = U*branch; UNmin = min(UNmin,Un); UNmax = max(UNmax,Un); Un_values[val_count++] = Un; //cerr << "Un=" << Un << " U=" << U << " branch=" << branch << endl; } //cerr << "n_debug=" << n_debug++ << endl; /// DEBUG LINE /// Real DUN = (UNmax-UNmin) /linear_discretisation; for (int i = 0; i <= linear_discretisation; ++i) { row[i].first = UNmin+ (i+0.5) *DUN; row[i].second = 0; } //cerr << "n_debug=" << n_debug++ << endl; /// DEBUG LINE /// val_count = val_count-1; //cerr << "nval=" << val_count << " reserved=" << edges.size() << endl; for (; val_count>=0; --val_count) { //cerr << "n_debug0=" << n_debug << endl; /// DEBUG LINE /// row[(int)((Un_values[val_count]-UNmin) /DUN)].second += 1; } //cerr << "DUN=" << DUN << " UNmin=" << UNmin << " UNmax=" << UNmax << endl; return row; //cerr << "n_debug=" << n_debug++ << endl; /// DEBUG LINE /// } ofstream& KinematicLocalisationAnalyser:: NormalDisplacementDistributionToFile(vector& edges, ofstream& output_file) { vector< pair > row; NormalDisplacementDistribution(edges, row); vector< pair >::iterator r_end = row.end(); //output part : output_file << "#Normal displacement distribution" << endl << "eps3=" << Delta_epsilon(3,3) << " eps2=" << Delta_epsilon(2,2) << " eps1=" << Delta_epsilon(1,1) << " number of neigbors: "<< edges.size() << endl << "Un_min=" << 1.5*row[0].first - 0.5*row[1].first << " Un_max=" << row[row.size()-1].first << endl; cout << "#Normal displacement distribution" << endl << "eps3=" << Delta_epsilon(3,3) << " eps2=" << Delta_epsilon(2,2) << " eps1=" << Delta_epsilon(1,1) << " number of neigbors: "<< edges.size() << endl << "Un_min=" << 1.5*row[0].first - 0.5*row[1].first << " Un_max=" << row[row.size()-1].first << endl; for (vector< pair >::iterator r_it = row.begin(); r_it != r_end; ++r_it) { output_file << r_it->first << " " << r_it->second << endl; cout << r_it->first << " " << r_it->second << endl; } output_file << endl; return output_file; } ofstream& KinematicLocalisationAnalyser:: ContactDistributionToFile(ofstream& output_file) { //cerr << "ContactDistributionToFile" << endl; vector< pair > row; row.resize(sphere_discretisation+1); Real DZ = 1.0/sphere_discretisation;//interval in term of cos(teta) long nc1=0; long nc2=0; long ng1=0; long ng2=0; //cerr << "ContactDistributionToFile05" << endl; TriaxialState::ContactIterator cend = (*TS1).contacts_end(); TriaxialState::GrainIterator gend = (*TS1).grains_end(); for (int i = 0; i <= sphere_discretisation; ++i) { row[i].first = (i+0.5) *DZ; row[i].second = 0; } for (TriaxialState::GrainIterator git = (*TS1).grains_begin(); git!=gend; ++git) { if ((*TS1).inside(git->sphere.point())) ++ng1; else ++ng2; } for (TriaxialState::ContactIterator cit = (*TS1).contacts_begin(); cit!=cend; ++cit) { if ((*TS1).inside((*cit)->grain1->sphere.point()) && (*TS1).inside((*cit)->grain2->sphere.point())) { row[(int)(std::abs((*cit)->normal.z()) /DZ)].second += 2; nc1 += 2; } else { if ((*TS1).inside((*cit)->grain1->sphere.point()) || (*TS1).inside((*cit)->grain2->sphere.point())) { row[(int)(std::abs((*cit)->normal.z()) /DZ)].second += 1; ++nc1; } //cerr << "(*cit)->normal.z(),DZ : " << (*cit)->normal.z() << " " << DZ << endl;} else ++nc2; } } //normalisation : Real normalize = 1.0/ (ng1*4*DZ*3.141592653); for (int i = 0; i <= sphere_discretisation; ++i) row[i].second *= normalize; //output part : output_file << "#Contacts distribution" << endl << "(filter dist. = " << (*TS1).filter_distance << ", "<< nc1 << " contacts, " << nc2 << " excluded contacts, for "<< ng1 <<"/"<< (ng1+ng2) << " grains)" << endl; output_file << "max_nz number_of_contacts" << endl; cerr << "#Contacts distribution (filter dist. = " << (*TS1).filter_distance << ", "<< nc1 << " contacts, " << nc2 << " excluded contacts, for "<< ng1 <<"/"<< (ng1+ng2) << " grains)" << endl; cerr << "mean_nz number_of_contacts" << endl; for (int i = 0; i <= sphere_discretisation; ++i) { output_file << row[i].first << " " << row[i].second << endl; cerr << row[i].first << " " << row[i].second << endl; } output_file << endl; return output_file; } ofstream& KinematicLocalisationAnalyser:: AllNeighborDistributionToFile(ofstream& output_file) { vector< pair > row; row.resize(sphere_discretisation); Real DZ = 1.0/sphere_discretisation; long nv1=0; long nv2=0; long nv3=0; long ng1=0; long ng2=0; for (int i = 0; i < sphere_discretisation; ++i) { row[i].first = (i+0.5) *DZ; row[i].second = 0; } TriaxialState::GrainIterator gend = (*TS1).grains_end(); for (TriaxialState::GrainIterator git = (*TS1).grains_begin(); git!=gend; ++git) { if ((*TS1).inside(git->sphere.point())) ++ng1; else ++ng2; } RTriangulation& T = (*TS1).tesselation().Triangulation(); Segment s; CVector v; for (Edge_iterator ed_it = T.edges_begin(); ed_it != T.edges_end(); ed_it++) { if (!T.is_infinite(*ed_it)) { s = T.segment(*ed_it); if ((*TS1).inside(s.source()) && (*TS1).inside(s.target())) { v = s.to_vector(); row[(int)(std::abs(v.z() /sqrt(s.squared_length())) /DZ)].second += 2; nv1 += 2; } else { if ((*TS1).inside(s.source()) || (*TS1).inside(s.target())) { v = s.to_vector(); row[(int)(std::abs(v.z() /sqrt(s.squared_length())) /DZ)].second += 1; ++nv1; } else ++nv2; } } else ++nv3; } Real normalize = 1.0/ (ng1*4*DZ*3.141592653); for (int i = 0; i < sphere_discretisation; ++i) row[i].second *= normalize; output_file << "#Neighbors distribution" << endl << "(filter dist. = " << (*TS1).filter_distance << ", "<< nv1 << " neighbors + " << nv2 << " excluded + " << nv3 << " infinite, for "<< ng1 <<"/"<< (ng1+ng2) << " grains)" << endl; output_file << "max_nz number_of_neighbors" << endl; cerr << "#Neighbors distribution" << endl << "(filter dist. = " << (*TS1).filter_distance << ", "<< nv1 << " neighbors + " << nv2 << " excluded + " << nv3 << " infinite, for "<< ng1 <<"/"<< (ng1+ng2) << " grains)" << endl; cerr << "mean_nz number_of_neighbors" << endl; for (int i = 0; i < sphere_discretisation; ++i) { output_file << row[i].first << " " << row[i].second << endl; cerr << row[i].first << " " << row[i].second << endl; } output_file << endl; return output_file; } void KinematicLocalisationAnalyser:: SetForceIncrements(void) //WARNING : This function will modify the contact lists : add virtual (lost)) contacts in state 1 and modify old_force and force in state 0, execute this function after all other force analysis functions if you want to avoid problems { //if (true) cerr << "SetForceIncrements"<< endl; // vector< pair > row; // row.resize ( sphere_discretisation ); // Real DZ = 1.0/sphere_discretisation; long Nc0 = TS0->contacts.size(); long Nc1 = TS1->contacts.size(); n_persistent = 0; n_new = 0; n_lost = 0; long lost_in_state0 = 0; for (int i = 0; i < Nc0; ++i) { TS0->contacts[i]->visited = false; if (TS0->contacts[i]->status == TriaxialState::Contact::LOST) ++lost_in_state0; } for (int i = 0; i < Nc1; ++i) TS1->contacts[i]->visited = false; //cerr << "Nc1 "<contacts[i]->status != TriaxialState::Contact::LOST) { // cerr << 2; for (int j = 0; j < Nc1; ++j) { if (TS0->contacts[i]->grain1->id == TS1->contacts[j]->grain1->id && TS0->contacts[i]->grain2->id == TS1->contacts[j]->grain2->id) { // This is a PERSISTENT contact (i.e. it is present in state 0 and 1) //TS0->contacts[i]->visited = true; TS1->contacts[j]->visited = true; //TS0->contacts[i]->status = TriaxialState::Contact::PERSISTENT; TS1->contacts[j]->status = TriaxialState::Contact::PERSISTENT; TS1->contacts[j]->old_fn = TS0->contacts[i]->fn; TS1->contacts[j]->old_fs = TS0->contacts[i]->fs; ++n_persistent; break; } else if (j+1==Nc1) { //This contact was not found in state 1, add it as a LOST contact // cerr << 3 << endl; TriaxialState::Contact* c = new TriaxialState::Contact; c->visited = true; c->status = TriaxialState::Contact::LOST; c->grain1 = TS0->contacts[i]->grain1; c->grain2 = TS0->contacts[i]->grain2; c->position = TS0->contacts[i]->position; c->normal = TS0->contacts[i]->normal; c->old_fn = TS0->contacts[i]->fn; c->fn = 0; c->old_fs = TS0->contacts[i]->fs; c->frictional_work = TS0->contacts[i]->frictional_work; c->fs = CGAL::NULL_VECTOR; TS1->contacts.push_back(c); ++Nc1; ++n_lost; break; } } } } //cerr << 4; for (int j = 0; j < Nc1; ++j) { //This contact was not visited, it is a NEW one //cerr << 5; if (!TS1->contacts[j]->visited /*&& TS1->contacts[j]->status != TriaxialState::Contact::LOST*/) { //cerr << 6; TS1->contacts[j]->status = TriaxialState::Contact::NEW; TS1->contacts[j]->old_fn = 0; TS1->contacts[j]->old_fs = CGAL::NULL_VECTOR; ++n_new; } } } void KinematicLocalisationAnalyser::SetDisplacementIncrements(void) { TriaxialState::GrainIterator gend = TS1->grains_end(); for (TriaxialState::GrainIterator git = TS1->grains_begin(); git!=gend; ++git) if (git->id >= 0) git->translation = TS1->grain(git->id).sphere.point() - TS0->grain(git->id).sphere.point(); consecutive = true; } ofstream& KinematicLocalisationAnalyser:: StrictNeighborDistributionToFile(ofstream& output_file) { return output_file; } CVector KinematicLocalisationAnalyser::Deplacement(Finite_cells_iterator cell, int facet) { CVector v(0.f, 0.f, 0.f); int id;// ident. de la particule CVector fixedPoint = 0.5*((TS0->box.base-CGAL::ORIGIN)+(TS0->box.sommet-CGAL::ORIGIN)); for (int i=0; i<4; i++) { // char msg [256]; if (i!=facet) { id = cell->vertex(i)->info().id(); CVector meanFieldDisp =CVector(TS0->grain(id).sphere.point().x(), TS0->grain(id).sphere.point().y(), TS0->grain(id).sphere.point().z())-fixedPoint; if (1){//fluctuations meanFieldDisp = CVector( meanFieldDisp[0]*Delta_epsilon(1,1), meanFieldDisp[1]*Delta_epsilon(2,2), meanFieldDisp[2]*Delta_epsilon(3,3)); } else meanFieldDisp=CVector(0,0,0); if (consecutive) v = v + TS1->grain(id).translation-meanFieldDisp; else v = v + (TS1->grain(id).sphere.point() - TS0->grain(id).sphere.point()-meanFieldDisp); } } v = v*0.333333333333; return v; } void KinematicLocalisationAnalyser::Grad_u(Finite_cells_iterator cell, int facet, CVector &V, Tenseur3& T) { CVector S = cross_product((cell->vertex(l_vertices[facet][1])->point().point()) - (cell->vertex(l_vertices[facet][0])->point().point()), (cell->vertex(l_vertices[facet][2])->point().point()) - (cell->vertex(l_vertices[facet][1])->point().point())) /2.f; Somme(T, V, S); } void KinematicLocalisationAnalyser::Grad_u(Finite_cells_iterator cell, Tenseur3& T, bool vol_divide)// Calcule le gradient de d�p. { T.reset(); CVector v; for (int facet=0; facet<4; facet++) { v = Deplacement(cell, facet); Grad_u(cell, facet, v, T); } if (vol_divide) T/= Tesselation::Volume(cell); } const vector& KinematicLocalisationAnalyser::computeParticlesDeformation(void) { Tesselation& Tes = TS1->tesselation(); RTriangulation& Tri = Tes.Triangulation(); Tenseur3 grad_u; Real v; v_total = 0; v_solid_total = 0; grad_u_total = NULL_TENSEUR3; v_total_g = 0; grad_u_total_g = NULL_TENSEUR3; Delta_epsilon(3,3) = TS1->eps3 - TS0->eps3; Delta_epsilon(1,1) = TS1->eps1 - TS0->eps1; Delta_epsilon(2,2) = TS1->eps2 - TS0->eps2; //compute Voronoi tesselation (i.e. voronoi center of each cell) if (!Tes.computed) Tes.compute(); if (ParticleDeformation.size() != (unsigned int)(Tes.Max_id() + 1)) { ParticleDeformation.clear(); ParticleDeformation.resize(Tes.Max_id() + 1); } //reset volumes and tensors of each particle n_real_vertices = 0; n_fictious_vertices=0; for (RTriangulation::Finite_vertices_iterator V_it=Tri.finite_vertices_begin(); V_it != Tri.finite_vertices_end(); V_it++) { //cerr << V_it->info().id() << endl; V_it->info().v() =0;//WARNING : this will erase previous values if some have been computed ParticleDeformation[V_it->info().id()]=NULL_TENSEUR3; if (!V_it->info().isFictious) ++n_real_vertices; else ++n_fictious_vertices; } Finite_cells_iterator cell = Tri.finite_cells_begin(); Finite_cells_iterator cell0 = Tri.finite_cells_end(); //compute grad_u and volumes of all cells in the triangulation, and assign them to each of the vertices ( volume*grad_u is added here rather than grad_u, the weighted average is computed later ) //define the number of non-fictious cells, i.e. not in contact with a boundary n_real_cells=0; for (; cell != cell0; cell++) { // calcule la norme du d�viateur dans chaque cellule cell->info().isFictious = (cell->vertex(0)->info().isFictious || cell->vertex(1)->info().isFictious || cell->vertex(2)->info().isFictious || cell->vertex(3)->info().isFictious); if (!cell->info().isFictious) { Grad_u(cell, grad_u, false); // false : don't divide by volume, here grad_u = volume of cell * average grad_u in cell, the final value is divided by the total volume later (see below) //cerr << "grad_u=" << grad_u << endl; v = Tri.tetrahedron(cell).volume(); grad_u_total += grad_u; v_total += v; ++n_real_cells; for (unsigned int index=0; index<4; index++) { cell->vertex(index)->info().v() += v; //WARNING2 : this will affect values which differ from the volumes of voronoi cells //cerr << "ParticleDeformation[cell->vertex (" << cell->vertex ( index )->info().id() << ")"<< endl; ParticleDeformation[cell->vertex(index)->info().id()] += grad_u; } } } //Do we delete volume and grad_u for particles on the border? // Tesselation::Vector_Vertex border_vertices; // Tes.Voisins(Tri.infinite_vertex(), border_vertices); // unsigned int l = border_vertices.size(); // for (unsigned int i=0; iinfo().v() =0; // // ParticleDeformation[border_vertices[i]->info().id()]=NULL_TENSEUR3; // } //Divide sum(v*grad_u) by sum(v) to get the average grad_u on each particle for (RTriangulation::Finite_vertices_iterator V_it = Tri.finite_vertices_begin(); V_it != Tri.finite_vertices_end(); V_it++) { v_total_g += V_it->info().v(); v_solid_total += 4.188790*pow(V_it->point().weight(),1.5);//4.18... = 4/3*PI; and here, weight is rad² grad_u_total_g += ParticleDeformation[V_it->info().id()]; if (V_it->info().v()) ParticleDeformation[V_it->info().id()]/=V_it->info().v(); } grad_u_total_g /= v_total_g; if(1){ cerr << "sym_grad_u_total_g (wrong averaged strain):"<< endl << Tenseur_sym3(grad_u_total_g) << endl; if (v_total) grad_u_total /= v_total; cerr << "Total volume = " << v_total << ", grad_u = " << endl << grad_u_total << endl << "sym_grad_u (true average strain): " << endl << Tenseur_sym3(grad_u_total) << endl; cerr << "Macro strain : "<< endl << Delta_epsilon << endl; } return ParticleDeformation; } Real KinematicLocalisationAnalyser::computeMacroPorosity(void) { return (1-v_solid_total/(TS1->haut*TS1->larg*TS1->prof)); } } // namespace CGT trunk-2018.02b/lib/triangulation/KinematicLocalisationAnalyser.hpp000066400000000000000000000133441324306050200252770ustar00rootroot00000000000000/************************************************************************* * Copyright (C) 2006 by Bruno Chareyre * * bruno.chareyre@hmg.inpg.fr * * * * This program is free software; it is licensed under the terms of the * * GNU General Public License v2 or later. See file LICENSE for details. * *************************************************************************/ /*! \brief computes statistics of micro-variables assuming axi-symetry. */ #pragma once #include "TriaxialState.h" #include "Tenseur3.h" namespace CGT { using std::pair; using std::ofstream; #define SPHERE_DISCRETISATION 20; //number of "teta" intervals on the unit sphere #define LINEAR_DISCRETISATION 200; //number of intervals on segments like [UNmin,UNmax] // l_vertices : defines vertices for each facet // for facet k, the vertices indices are in column k const int l_vertices [4][3] = { {1, 2, 3}, {0, 3, 2}, {3, 0, 1}, {2, 1, 0} }; class KinematicLocalisationAnalyser { public: typedef TriaxialState::Tesselation Tesselation; typedef TriaxialState::RTriangulation RTriangulation; typedef RTriangulation::Vertex_handle Vertex_handle; typedef RTriangulation::Finite_cells_iterator Finite_cells_iterator; typedef RTriangulation::Cell_handle Cell_handle; typedef RTriangulation::Edge_iterator Edge_iterator; typedef vector< pair > RGrid1D; typedef vector > RGrid2D; typedef vector > > RGrid3D; KinematicLocalisationAnalyser(); KinematicLocalisationAnalyser ( const char* state_file1, bool usebz2 = true ); KinematicLocalisationAnalyser ( const char* state_file1, const char* state_file0, bool consecutive_files = true, bool usebz2 = true); KinematicLocalisationAnalyser ( const char* base_name, int file_number0, int file_number1, bool usebz2 = true); ~KinematicLocalisationAnalyser(); void SetBaseFileName (string name); bool SetFileNumbers (int n0, int n1); void SetConsecutive (bool); void SetNO_ZERO_ID (bool); void SwitchStates (void); bool DistribsToFile (const char* output_file_name); ///Write the averaged deformation on each grain in a file (vertices and cells lists included in the file), no need to call computeParticlesDeformation() bool DefToFile (const char* output_file_name = "deformations"); bool DefToFile (const char* state_file1, const char* state_file0, const char* output_file_name="deformation.vtk", bool usebz2=false); ///Save/Load states using bz2 compression bool bz2; ofstream& ContactDistributionToFile ( ofstream& output_file ); ofstream& AllNeighborDistributionToFile ( ofstream& output_file ); ofstream& StrictNeighborDistributionToFile ( ofstream& output_file ); ofstream& NormalDisplacementDistributionToFile ( vector& edges, ofstream& output_file ); long Filtered_contacts ( TriaxialState& state ); long Filtered_neighbors ( TriaxialState& state ); long Filtered_grains ( TriaxialState& state ); Real Filtered_volume ( TriaxialState& state ); Real Contact_coordination ( TriaxialState& state ); Real Neighbor_coordination ( TriaxialState& state ); Tenseur_sym3 Neighbor_fabric ( TriaxialState& state ); Tenseur_sym3 Contact_fabric ( TriaxialState& state ); Real Contact_anisotropy ( TriaxialState& state ); Real Neighbor_anisotropy ( TriaxialState& state ); void SetForceIncrements (void); void SetDisplacementIncrements (void); ///Add surface*displacement to T void Grad_u ( Finite_cells_iterator cell, int facet, CVector &V, Tenseur3& T ); ///compute grad_u in cell (by default, T= average grad_u in cell, if !vol_divide, T=grad_u*volume void Grad_u ( Finite_cells_iterator cell, Tenseur3& T, bool vol_divide=true ); /// compute grad_u for all particles, by summing grad_u of all adjaent cells using current states const vector& computeParticlesDeformation (void); /// Do everything in one step by giving some final (file1) and initial (file0) positions const vector& computeParticlesDeformation(const char* state_file1, const char* state_file0, bool usebz2 = true); ///compute porisity from cumulated spheres volumes and positions of boxes Real computeMacroPorosity (void ); CVector Deplacement ( Cell_handle cell ); //donne le d�placement d'un sommet de voronoi CVector Deplacement ( Finite_cells_iterator cell, int facet ); //mean displacement on a facet // Calcul du tenseur d'orientation des voisins //Tenseur_sym3 Orientation_voisins (Tesselation& Tes); //Set the list of edges of a given orientation (orientation defined via the z-coordinate of the normal) vector& Oriented_Filtered_edges (Real Nymin, Real Nymax, vector& filteredList); vector >& NormalDisplacementDistribution ( vector& edges, vector >& row ); //member data int sphere_discretisation; int linear_discretisation; Tenseur_sym3 Delta_epsilon; Tenseur3 grad_u_total; vector ParticleDeformation; Tenseur3 grad_u_total_g;//grad_u averaged on extended grain cells TriaxialState *TS1, *TS0; private: int file_number_1, file_number_0; string base_file_name; //Base name of state-files, complete name is (base_name+state_number). bool consecutive; //Are the two triax states consecutive? if "false" displacements are re-computed from the two source files; if "true" one file is enough. Real v_solid_total;//solid volume in the box Real v_total;//volume of the box Real v_total_g;//summed volumes of extended grain cells long n_persistent, n_new, n_lost, n_real_cells, n_real_vertices, n_fictious_vertices; }; } // namespace CGT trunk-2018.02b/lib/triangulation/Network.hpp000066400000000000000000000116731324306050200207660ustar00rootroot00000000000000/************************************************************************* * Copyright (C) 2010 by Emanuele Catalano * * * * This program is free software; it is licensed under the terms of the * * GNU General Public License v2 or later. See file LICENSE for details. * *************************************************************************/ #ifdef FLOW_ENGINE #pragma once #include "Tesselation.h" #include "Timer.h" #include "basicVTKwritter.hpp" /** Defines class Network. Which contains the geometrical representation of a pore network on the basis of regular triangulation (using CGAL lib) The class is the base of the pore-flow model. It has basic functions to compute quantities like void volumes and solid surfaces in the triangulation's elements. The same data structure is used with different template parameters for periodic and aperiodic boundary conditions. The network is bounded by infinite planes represented in the triangulation by very large spheres (so that their surface looks flat at the scale of the network). Two triangulations are in fact contained in the network, so that a simulation can switch between them and pass data from one to the other. Otherwise, some info would be lost when the problem is retriangulated. */ namespace CGT { /// Representation of a boundary condition along an axis aligned plane. struct Boundary { Point p;//position CVector normal;//orientation Vector3r velocity;//motion int coordinate;//the axis perpendicular to the boundary bool flowCondition;//flowCondition=0, pressure is imposed // flowCondition=1, flow is imposed Real value;// value of imposed pressure bool useMaxMin;// tells if this boundary was placed following the particles (using min/max of them) or with user defined position }; template class Network { public: DECLARE_TESSELATION_TYPES(Tesselation) //see Tesselation.h virtual ~Network(); Network(); Tesselation T [2]; bool currentTes; Tesselation& tesselation() {return T[currentTes];}; double xMin, xMax, yMin, yMax, zMin, zMax, Rmoy, sectionArea, Height, vTotal; bool debugOut; int nOfSpheres; int xMinId, xMaxId, yMinId, yMaxId, zMinId, zMaxId; int* boundsIds [6]; vector boundingCells [6]; Point cornerMin; Point cornerMax; Real VSolidTot, Vtotalissimo, vPoral, sSolidTot, vPoralPorosity, vTotalPorosity; Boundary boundaries [6]; Boundary& boundary (int b) {return boundaries[b-idOffset];} short idOffset; int vtkInfiniteVertices, vtkInfiniteCells, num_particles; void addBoundingPlanes(); void addBoundingPlane (CVector Normal, int id_wall); void addBoundingPlane (Real center[3], double thickness, CVector Normal, int id_wall ); void defineFictiousCells( ); int detectFacetFictiousVertices (CellHandle& cell, int& j); double volumeSolidPore (const CellHandle& cell); double volumeSingleFictiousPore(const VertexHandle& SV1, const VertexHandle& SV2, const VertexHandle& SV3, const Point& PV1, const Point& PV2, CVector& facetSurface); double volumeDoubleFictiousPore(const VertexHandle& SV1, const VertexHandle& SV2, const VertexHandle& SV3, const Point& PV1, const Point& PV2, CVector& facetSurface); double sphericalTriangleVolume(const Sphere& ST1, const Point& PT1, const Point& PT2, const Point& PT3); double fastSphericalTriangleArea(const Sphere& STA1, const Point& STA2, const Point& STA3, const Point& PTA1); Real fastSolidAngle(const Point& STA1, const Point& PTA1, const Point& PTA2, const Point& PTA3); double volumeDoubleFictiousPore(VertexHandle SV1, VertexHandle SV2, VertexHandle SV3, Point PV1); double volumeSingleFictiousPore(VertexHandle SV1, VertexHandle SV2, VertexHandle SV3, Point PV1); double volumePoreVoronoiFraction ( CellHandle& cell, int& j, bool reuseFacetData=false); double surfaceSolidThroat( CellHandle cell, int j, bool slipBoundary, bool reuseFacetData=false); double surfaceSolidThroatInPore( CellHandle cell, int j, bool slipBoundary, bool reuseFacetData=false);// returns the solid area in the throat, keeping only that part of the throat in cell double sphericalTriangleArea ( Sphere STA1, Sphere STA2, Sphere STA3, Point PTA1 ); CVector surfaceDoubleFictiousFacet(VertexHandle fSV1, VertexHandle fSV2, VertexHandle SV3); CVector surfaceSingleFictiousFacet(VertexHandle fSV1, VertexHandle SV2, VertexHandle SV3); double surfaceSolidDoubleFictiousFacet(VertexHandle SV1, VertexHandle SV2, VertexHandle SV3); double surfaceSolidFacet(Sphere ST1, Sphere ST2, Sphere ST3); void lineSolidPore(CellHandle cell, int j); double lineSolidFacet(Sphere ST1, Sphere ST2, Sphere ST3); int facetF1, facetF2, facetRe1, facetRe2, facetRe3; int facetNFictious; double FAR; static const double ONE_THIRD; static const int facetVertices [4][3]; static const int permut3 [3][3]; static const int permut4 [4][4]; }; } //namespaceCGT #include "Network.ipp" #endif //FLOW_ENGINE trunk-2018.02b/lib/triangulation/Network.ipp000066400000000000000000000723011324306050200207620ustar00rootroot00000000000000 #ifdef FLOW_ENGINE #if CGAL_VERSION_NR < CGAL_VERSION_NUMBER(4,11,0) #include "CGAL/constructions/constructions_on_weighted_points_cartesian_3.h" #endif #include #include #include #include #include "vector" #include #include #include #define FAST namespace CGT { using std::abs; // template const double Network::FAR = 50000; template const double Network::ONE_THIRD = 1.0/3.0; template const int Network::facetVertices [4][3] = {{1,2,3},{0,2,3},{0,1,3},{0,1,2}}; template const int Network::permut3 [3][3] = {{0,1,2},{1,2,0},{2,0,1}}; template const int Network::permut4 [4][4] = {{0,1,2,3},{1,2,3,0},{2,3,0,1},{3,0,1,2}}; template Network::~Network(){} template Network::Network(){ FAR = 50000; facetF1=facetF2=facetRe1=facetRe2=facetRe3=0; // F1=F2=Re1=Re2=0; } template int Network::detectFacetFictiousVertices (CellHandle& cell, int& j) { facetNFictious = 0; int nRealVtx=0; for (int kk=0; kk<3; kk++) { if (cell->vertex(facetVertices[j][kk])->info().isFictious) { if (facetNFictious==0) facetF1=kk; else facetF2=kk; facetNFictious +=1; } else { if (nRealVtx==0) facetRe1=kk; else if (nRealVtx==1) facetRe2=kk; else if (nRealVtx==2) facetRe3=kk; nRealVtx+=1;}} return facetNFictious; } template double Network::volumePoreVoronoiFraction (CellHandle& cell, int& j, bool reuseFacetData) { Point& p1 = cell->info(); Point& p2 = cell->neighbor(j)->info(); if (!reuseFacetData) facetNFictious = detectFacetFictiousVertices (cell,j); Sphere v [3]; VertexHandle W [3]; for (int kk=0; kk<3; kk++) {W[kk] = cell->vertex(facetVertices[j][kk]);v[kk] = cell->vertex(facetVertices[j][kk])->point();} switch (facetNFictious) { case (0) : { VertexHandle& SV1 = W[0]; VertexHandle& SV2 = W[1]; VertexHandle& SV3 = W[2]; cell->info().facetSurfaces[j]=0.5*CGAL::cross_product(SV1->point().point()-SV3->point().point(),SV2->point().point()-SV3->point().point()); if (cell->info().facetSurfaces[j][0]==0 && cell->info().facetSurfaces[j][1]==0 && cell->info().facetSurfaces[j][2]==0) cerr<<"NULL FACET SURF"<info().facetSurfaces[j]*(p2-p1) > 0) cell->info().facetSurfaces[j] = -1.0*cell->info().facetSurfaces[j]; Real Vtot = abs(ONE_THIRD*cell->info().facetSurfaces[j]*(p1-p2)); Vtotalissimo += Vtot; double Vsolid1=0, Vsolid2=0; for (int i=0;i<3;i++) { Vsolid1 += sphericalTriangleVolume(v[permut3[i][0]],v[permut3[i][1]].point(),p1,p2); Vsolid2 += sphericalTriangleVolume(v[permut3[i][0]],v[permut3[i][2]].point(),p1,p2);} VSolidTot += Vsolid1 + Vsolid2; vPoral += Vtot - (Vsolid1 + Vsolid2); bool border=false; for (int i=0;i<4;i++){ if (cell->neighbor(i)->info().fictious()!=0) border=true;} if (!border) {vPoralPorosity += Vtot - (Vsolid1 + Vsolid2); vTotalPorosity += Vtot;} /**Vpore**/ return Vtot - (Vsolid1 + Vsolid2); }; break; case (1) : {return volumeSingleFictiousPore(cell->vertex(facetVertices[j][facetF1]), cell->vertex(facetVertices[j][facetRe1]), cell->vertex(facetVertices[j][facetRe2]), p1,p2, cell->info().facetSurfaces[j]);}; break; case (2) : {return volumeDoubleFictiousPore(cell->vertex(facetVertices[j][facetF1]), cell->vertex(facetVertices[j][facetF2]), cell->vertex(facetVertices[j][facetRe1]), p1,p2, cell->info().facetSurfaces[j]);}; break; default : return 0;} } template double Network::volumeSolidPore (const CellHandle& cell) { double Vsolid=0; for (int i=0;i<4;i++) { if ( !cell->vertex(permut4[i][0])->info().isFictious ) Vsolid += sphericalTriangleVolume( cell->vertex(permut4[i][0])->point(), cell->vertex(permut4[i][1])->point().point(), cell->vertex(permut4[i][2])-> point().point(), cell->vertex(permut4[i][3])-> point().point()); } return Vsolid; } template double Network::volumeSingleFictiousPore(const VertexHandle& SV1, const VertexHandle& SV2, const VertexHandle& SV3, const Point& PV1, const Point& PV2, CVector& facetSurface) { double A [3], B[3]; Boundary &bi1 = boundary(SV1->info().id()); for (int m=0;m<3;m++) {A[m]= (SV2->point())[m];} for (int m=0;m<3;m++) {B[m]= (SV3->point())[m];} A[bi1.coordinate]=bi1.p[bi1.coordinate]; B[bi1.coordinate]=bi1.p[bi1.coordinate]; Point AA(A[0],A[1],A[2]); Point BB(B[0],B[1],B[2]); facetSurface = surfaceSingleFictiousFacet(SV1,SV2,SV3); if (facetSurface*(PV2-PV1) > 0) facetSurface = -1.0*facetSurface; Real Vtot=ONE_THIRD*abs(facetSurface*(PV1-PV2)); Vtotalissimo += Vtot; Sphere A1(AA, 0); Sphere B1(BB, 0); Sphere& SW2 = SV2->point(); Sphere& SW3 = SV3->point(); Real Vsolid1 = sphericalTriangleVolume(SW2, AA, PV1, PV2)+sphericalTriangleVolume(SW2, SW3.point(), PV1, PV2); Real Vsolid2 = sphericalTriangleVolume(SW3, BB, PV1, PV2)+sphericalTriangleVolume(SW3, SW2.point(), PV1, PV2); VSolidTot += Vsolid1 + Vsolid2; vPoral += Vtot - (Vsolid1 + Vsolid2); return (Vtot - (Vsolid1 + Vsolid2)); } template double Network::volumeDoubleFictiousPore(const VertexHandle& SV1, const VertexHandle& SV2, const VertexHandle& SV3, const Point& PV1, const Point& PV2, CVector& facetSurface) { double A [3], B[3]; Boundary &bi1 = boundary(SV1->info().id()); Boundary &bi2 = boundary(SV2->info().id()); for (int m=0;m<3;m++) {A[m]=B[m]= SV3->point()[m];} A[bi1.coordinate]=bi1.p[bi1.coordinate]; B[bi2.coordinate]=bi2.p[bi2.coordinate]; Point AA(A[0],A[1],A[2]); Point BB(B[0],B[1],B[2]); facetSurface = CGAL::cross_product(SV3->point().point()-AA,SV3->point().point()-BB); if (facetSurface*(PV2-PV1) > 0) facetSurface = -1.0*facetSurface; Real Vtot = abs(facetSurface*(PV1-PV2))*ONE_THIRD; Vtotalissimo += Vtot; Real Vsolid1 = sphericalTriangleVolume(SV3->point(), AA, PV1, PV2); Real Vsolid2 = sphericalTriangleVolume(SV3->point(), BB, PV1, PV2); vPoral += (Vtot - Vsolid1 - Vsolid2); VSolidTot += Vsolid1 + Vsolid2; return (Vtot - Vsolid1 - Vsolid2); } template double Network::sphericalTriangleVolume(const Sphere& ST1, const Point& PT1, const Point& PT2, const Point& PT3) { double rayon = sqrt(ST1.weight()); if (rayon == 0.0) return 0.0; return ((ONE_THIRD * rayon) * (fastSphericalTriangleArea(ST1, PT1, PT2, PT3))) ; } template double Network::fastSphericalTriangleArea(const Sphere& STA1, const Point& STA2, const Point& STA3, const Point& PTA1) { using namespace CGAL; double rayon2 = STA1.weight(); if (rayon2 == 0.0) return 0.0; return rayon2 * fastSolidAngle(STA1.point(),STA2,STA3,PTA1); } template double Network::sphericalTriangleArea ( Sphere STA1, Sphere STA2, Sphere STA3, Point PTA1 ) { double rayon = STA1.weight(); if ( rayon == 0.0 ) return 0.0; CVector v12 = STA2.point() - STA1.point(); CVector v13 = STA3.point() - STA1.point(); CVector v14 = PTA1 - STA1.point(); double norme12 = ( v12.squared_length() ); double norme13 = ( v13.squared_length() ); double norme14 = ( v14.squared_length() ); double cosA = v12*v13 / sqrt ( ( norme13 * norme12 ) ); double cosB = v12*v14 / sqrt ( ( norme14 * norme12 ) ); double cosC = v14*v13 / sqrt ( ( norme13 * norme14 ) ); double A = acos ( cosA ); double B = acos ( cosB ); double C = acos ( cosC ); if ( A==0 || B==0 || C==0 ) return 0; double a = acos ( ( cosA - cosB * cosC ) / ( sin ( B ) * sin ( C ) ) ); double b = acos ( ( cosB - cosC * cosA ) / ( sin ( C ) * sin ( A ) ) ); double c = acos ( ( cosC - cosA * cosB ) / ( sin ( A ) * sin ( B ) ) ); double aire_triangle_spherique = rayon * ( a + b + c - M_PI ); return aire_triangle_spherique; } template Real Network::fastSolidAngle(const Point& STA1, const Point& PTA1, const Point& PTA2, const Point& PTA3) { //! This function needs to be fast because it is used heavily. Avoid using vector operations which require constructing vectors (~50% of cpu time in the non-fast version), and compute angle using the 3x faster formula of Oosterom and StrackeeVan Oosterom, A; Strackee, J (1983). "The Solid Angle of a Plane Triangle". IEEE Trans. Biom. Eng. BME-30 (2): 125-126. (or check http://en.wikipedia.org/wiki/Solid_angle) using namespace CGAL; double M[3][3]; M[0][0] = PTA1.x() - STA1.x(); M[0][1] = PTA2.x() - STA1.x(); M[0][2] = PTA3.x() - STA1.x(); M[1][0] = PTA1.y() - STA1.y(); M[1][1] = PTA2.y() - STA1.y(); M[1][2] = PTA3.y() - STA1.y(); M[2][0] = PTA1.z() - STA1.z(); M[2][1] = PTA2.z() - STA1.z(); M[2][2] = PTA3.z() - STA1.z(); double detM = M[0][0]* (M[1][1]*M[2][2]-M[2][1]*M[1][2]) + M[1][0]* (M[2][1]*M[0][2]-M[0][1]*M[2][2]) + M[2][0]* (M[0][1]*M[1][2]-M[1][1]*M[0][2]); double pv12N2 = pow(M[0][0],2) +pow(M[1][0],2) +pow(M[2][0],2); double pv13N2 = pow(M[0][1],2) +pow(M[1][1],2) +pow(M[2][1],2); double pv14N2 = pow(M[0][2],2) +pow(M[1][2],2) +pow(M[2][2],2); double pv12N = sqrt(pv12N2); double pv13N = sqrt(pv13N2); double pv14N = sqrt(pv14N2); double cp12 = (M[0][0]*M[0][1]+M[1][0]*M[1][1]+M[2][0]*M[2][1]); double cp13 = (M[0][0]*M[0][2]+M[1][0]*M[1][2]+M[2][0]*M[2][2]); double cp23 = (M[0][1]*M[0][2]+M[1][1]*M[1][2]+M[2][1]*M[2][2]); double ratio = detM/ (pv12N*pv13N*pv14N+cp12*pv14N+cp13*pv13N+cp23*pv12N); return abs(2*atan(ratio)); } template double Network::surfaceSolidThroat(CellHandle cell, int j, bool slipBoundary, bool reuseFacetData) { if (!reuseFacetData) facetNFictious=detectFacetFictiousVertices(cell,j); Point& p1 = cell->info(); Point& p2 = cell->neighbor(j)->info(); double Ssolid = 0; double Ssolid1= 0, Ssolid1n= 0, Ssolid2= 0, Ssolid2n= 0, Ssolid3= 0, Ssolid3n= 0; Sphere v [3]; VertexHandle W [3]; for (int kk=0; kk<3; kk++) { W[kk] = cell->vertex(facetVertices[j][kk]); v[kk] = cell->vertex(facetVertices[j][kk])->point();} switch (facetNFictious) { case (0) : { VertexHandle& SV1 = W[0]; VertexHandle& SV2 = W[1]; VertexHandle& SV3 = W[2]; Ssolid1 = fastSphericalTriangleArea(SV1->point(), SV2->point().point(), p1, p2); Ssolid1n = fastSphericalTriangleArea(SV1->point(), SV3->point().point(), p1, p2); cell->info().solidSurfaces[j][0]=Ssolid1+Ssolid1n; Ssolid2 = fastSphericalTriangleArea(SV2->point(),SV1->point().point(),p1, p2); Ssolid2n = fastSphericalTriangleArea(SV2->point(),SV3->point().point(),p1, p2); cell->info().solidSurfaces[j][1]=Ssolid2+Ssolid2n; Ssolid3 = fastSphericalTriangleArea(SV3->point(),SV2->point().point(),p1, p2); Ssolid3n = fastSphericalTriangleArea(SV3->point(),SV1->point().point(),p1, p2); cell->info().solidSurfaces[j][2]=Ssolid3+Ssolid3n; }; break; case (1) : { VertexHandle SV1 = cell->vertex(facetVertices[j][facetF1]); VertexHandle SV2 = cell->vertex(facetVertices[j][facetRe1]); VertexHandle SV3 = cell->vertex(facetVertices[j][facetRe2]); Boundary &bi1 = boundary(SV1->info().id()); Ssolid1 = 0; if (bi1.flowCondition && ! slipBoundary) { Ssolid1 = abs(0.5*CGAL::cross_product(p1-p2, SV2->point().point()-SV3->point().point())[bi1.coordinate]); cell->info().solidSurfaces[j][facetF1]=Ssolid1; } Ssolid2 = fastSphericalTriangleArea(SV2->point(),SV1->point().point(),p1, p2); Ssolid2n = fastSphericalTriangleArea(SV2->point(),SV3->point().point(),p1, p2); cell->info().solidSurfaces[j][facetRe1]=Ssolid2+Ssolid2n; Ssolid3 = fastSphericalTriangleArea(SV3->point(),SV2->point().point(),p1, p2); Ssolid3n = fastSphericalTriangleArea(SV3->point(),SV1->point().point(),p1, p2); cell->info().solidSurfaces[j][facetRe2]=Ssolid3+Ssolid3n; }; break; case (2) : { double A [3], B[3], C[3]; VertexHandle SV1 = cell->vertex(facetVertices[j][facetF1]); VertexHandle SV2 = cell->vertex(facetVertices[j][facetF2]); VertexHandle SV3 = cell->vertex(facetVertices[j][facetRe1]); Boundary &bi1 = boundary(SV1->info().id()); Boundary &bi2 = boundary(SV2->info().id()); for (int m=0;m<3;m++) { A[m]=B[m]=C[m]= (SV3->point())[m]; } A[bi1.coordinate]=bi1.p[bi1.coordinate]; B[bi2.coordinate]=bi2.p[bi2.coordinate]; C[bi1.coordinate]=bi1.p[bi1.coordinate]; C[bi2.coordinate]=bi2.p[bi2.coordinate]; Point AA(A[0],A[1],A[2]); Point BB(B[0],B[1],B[2]); Point CC(C[0],C[1],C[2]); Sphere A1(AA, 0); Sphere B1(BB, 0); Sphere C1(CC, 0); //FIXME : we are computing triangle area twice here, because its computed in volume_double_fictious already -> optimize Ssolid1 = fastSphericalTriangleArea(SV3->point(), AA, p1, p2); Ssolid1n = fastSphericalTriangleArea(SV3->point(), BB, p1, p2); cell->info().solidSurfaces[j][facetRe1]=Ssolid1+Ssolid1n; //area vector of triangle (p1,sphere,p2) CVector p1p2v1Surface = 0.5*CGAL::cross_product(p1-p2,SV3->point().point()-p2); if (bi1.flowCondition && ! slipBoundary) { //projection on boundary 1 Ssolid2 = abs(p1p2v1Surface[bi1.coordinate]); cell->info().solidSurfaces[j][facetF1]=Ssolid2; } else cell->info().solidSurfaces[j][facetF1]=0; if (bi2.flowCondition && ! slipBoundary) { //projection on boundary 2 Ssolid3 = abs(p1p2v1Surface[bi2.coordinate]); cell->info().solidSurfaces[j][facetF2]=Ssolid3; } else cell->info().solidSurfaces[j][facetF2]=0; }; break; } Ssolid = Ssolid1+Ssolid1n+Ssolid2+Ssolid2n+Ssolid3+Ssolid3n; if (Ssolid) cell->info().solidSurfaces[j][3]=1.0/Ssolid; else cell->info().solidSurfaces[j][3]=0; sSolidTot += Ssolid; return Ssolid; } template double Network::surfaceSolidThroatInPore(CellHandle cell, int j, bool slipBoundary, bool reuseFacetData) { if (!reuseFacetData) facetNFictious=detectFacetFictiousVertices(cell,j); Point& p1 = cell->info(); Point& p2 = cell->neighbor(j)->info(); double Ssolid1= 0, Ssolid2= 0, Ssolid3= 0; Sphere v [3]; VertexHandle W [3]; for (int kk=0; kk<3; kk++) { W[kk] = cell->vertex(facetVertices[j][kk]); v[kk] = cell->vertex(facetVertices[j][kk])->point();} switch (facetNFictious) { case (0) : { VertexHandle& SV1 = W[0]; VertexHandle& SV2 = W[1]; VertexHandle& SV3 = W[2]; Ssolid1 = fastSphericalTriangleArea(SV1->point(),SV2->point().point(),p1, SV3->point().point()); Ssolid2 = fastSphericalTriangleArea(SV2->point(),SV1->point().point(),p1, SV3->point().point()); Ssolid3 = fastSphericalTriangleArea(SV3->point(),SV2->point().point(),p1, SV1->point().point()); }; break; case (1) : { VertexHandle SV1 = cell->vertex(facetVertices[j][facetF1]); VertexHandle SV2 = cell->vertex(facetVertices[j][facetRe1]); VertexHandle SV3 = cell->vertex(facetVertices[j][facetRe2]); Boundary &bi1 = boundary(SV1->info().id()); Ssolid1 = 0; if (bi1.flowCondition && ! slipBoundary) Ssolid1 = abs(0.5*CGAL::cross_product(p1-SV2->point().point(), SV2->point().point()-SV3->point().point())[bi1.coordinate]); Ssolid2 = fastSphericalTriangleArea(SV2->point(),SV3->point().point(),p1, SV2->point().point()+bi1.normal); Ssolid3 = fastSphericalTriangleArea(SV3->point(),SV2->point().point(),p1, SV3->point().point()+bi1.normal); }; break; case (2) : { double A [3], B[3], C[3]; VertexHandle SV1 = cell->vertex(facetVertices[j][facetF1]); VertexHandle SV2 = cell->vertex(facetVertices[j][facetF2]); VertexHandle SV3 = cell->vertex(facetVertices[j][facetRe1]); Boundary &bi1 = boundary(SV1->info().id()); Boundary &bi2 = boundary(SV2->info().id()); for (int m=0;m<3;m++) {A[m]=B[m]=C[m]= (SV3->point())[m];} A[bi1.coordinate]=bi1.p[bi1.coordinate]; B[bi2.coordinate]=bi2.p[bi2.coordinate]; C[bi1.coordinate]=bi1.p[bi1.coordinate]; C[bi2.coordinate]=bi2.p[bi2.coordinate]; Point AA(A[0],A[1],A[2]); Point BB(B[0],B[1],B[2]); Point CC(C[0],C[1],C[2]); Sphere A1(AA, 0); Sphere B1(BB, 0); Sphere C1(CC, 0); //FIXME : we are computing triangle area twice here, because its computed in volume_double_fictious already -> optimize Ssolid1 = 0.5*(fastSphericalTriangleArea(SV3->point(), AA, p1, p2)+ fastSphericalTriangleArea(SV3->point(), BB, p1, p2)); CVector p1p2v1Surface = 0.5*CGAL::cross_product(p1-p2,SV3->point().point()-p2); if (bi1.flowCondition && ! slipBoundary) Ssolid2 = 0.5*abs(p1p2v1Surface[bi1.coordinate]); if (bi2.flowCondition && ! slipBoundary) Ssolid3 = 0.5*abs(p1p2v1Surface[bi2.coordinate]); }; break; } return Ssolid1+Ssolid2+Ssolid3; } template CVector Network::surfaceDoubleFictiousFacet(VertexHandle fSV1, VertexHandle fSV2, VertexHandle SV3) { //This function is correct only with axis-aligned boundaries const Boundary &bi1 = boundary(fSV1->info().id()); const Boundary &bi2 = boundary(fSV2->info().id()); double area=(bi1.p[bi1.coordinate]-SV3->point()[bi1.coordinate])*(bi2.p[bi2.coordinate]-SV3->point()[bi2.coordinate]); double surf [3] = {1,1,1}; surf[bi1.coordinate]=0; surf[bi2.coordinate]=0; return area*CVector(surf[0],surf[1],surf[2]); } template CVector Network::surfaceSingleFictiousFacet(VertexHandle fSV1, VertexHandle SV2, VertexHandle SV3) { //This function is correct only with axis-aligned boundaries const Boundary &bi1 = boundary(fSV1->info().id()); // const Boundary &bi2 = boundary ( fSV2->info().id() ); CVector mean_height = (bi1.p[bi1.coordinate]-0.5*(SV3->point()[bi1.coordinate]+SV2->point()[bi1.coordinate]))*bi1.normal; return CGAL::cross_product(mean_height,SV3->point().point()-SV2->point().point()); } template double Network::surfaceSolidDoubleFictiousFacet(VertexHandle SV1, VertexHandle SV2, VertexHandle SV3) { double A [3], B [3]; for (int m=0;m<3;m++) { A[m]=B[m]= (SV3->point())[m]; } const Boundary &bi1 = boundary(SV1->info().id()); const Boundary &bi2 = boundary(SV2->info().id()); A[bi1.coordinate]=bi1.p[bi1.coordinate]; B[bi2.coordinate]=bi2.p[bi2.coordinate]; double board1=0, board2=0, total_surface=0; for (int p=0;p<3;p++) { board1 += pow((SV3->point()[p]-A[p]),2); board2 += pow((SV3->point()[p]-B[p]),2); } total_surface = sqrt(board1 * board2); double solid_surface = surfaceSolidFacet(SV3->point(),SV2->point(),SV1->point()); return total_surface - solid_surface; } template double Network::surfaceSolidFacet(Sphere ST1, Sphere ST2, Sphere ST3) { double Area; double squared_radius = ST1.weight(); CVector v12 = ST2.point() - ST1.point(); CVector v13 = ST3.point() - ST1.point(); double norme12 = v12.squared_length(); double norme13 = v13.squared_length(); double cosA = v12*v13 / (sqrt(norme13 * norme12)); double A = acos(cosA); Area = (A/ (2*M_PI)) * (M_PI * squared_radius); return Area; } template void Network::addBoundingPlanes() { Tesselation& Tes = T[currentTes]; //FIXME: Id's order in boundsIds is done according to the enumerotation of boundaries from TXStressController.hpp, line 31. DON'T CHANGE IT! yMinId = Tes.Max_id() + 2; boundsIds[0]=&yMinId; yMaxId = Tes.Max_id() + 3; boundsIds[1]=&yMaxId; xMinId = Tes.Max_id() + 0; boundsIds[2]=&xMinId; xMaxId = Tes.Max_id() + 1; boundsIds[3]=&xMaxId; zMinId = Tes.Max_id() + 5; boundsIds[4]=&zMaxId; zMaxId = Tes.Max_id() + 6; boundsIds[5]=&zMinId; cornerMin = Point(xMin, yMin, zMin); cornerMax = Point(xMax, yMax, zMax); idOffset = Tes.Max_id() +1;//so that boundaries[vertex->id - offset] gives the ordered boundaries (also see function Boundary& boundary(int b)) addBoundingPlane (CVector(0,1,0) , yMinId); addBoundingPlane (CVector(0,-1,0) , yMaxId); addBoundingPlane (CVector(-1,0,0) , xMaxId); addBoundingPlane (CVector(1,0,0) , xMinId); addBoundingPlane (CVector(0,0,1) , zMinId); addBoundingPlane (CVector(0,0,-1) , zMaxId); // addBoundingPlanes(true); } template void Network::addBoundingPlane (CVector Normal, int id_wall) { // Tesselation& Tes = T[currentTes]; //FIXME: pre-condition: the normal is axis-aligned int Coordinate = abs(Normal[0])*0 + abs(Normal[1])*1 + abs(Normal[2])*2; double pivot = Normal[Coordinate]<0 ? cornerMax.x()*abs(Normal[0])+cornerMax.y()*abs(Normal[1])+cornerMax.z()*abs(Normal[2]) : cornerMin.x()*abs(Normal[0])+cornerMin.y()*abs(Normal[1])+cornerMin.z()*abs(Normal[2]); Real center [3] ={ 0.5*(cornerMin.x() +cornerMax.x())*(1-abs(Normal[0]))+pivot*abs(Normal[0]), 0.5*(cornerMax.y() +cornerMin.y())*(1-abs(Normal[1]))+pivot*abs(Normal[1]), 0.5*(cornerMax.z() +cornerMin.z())*(1-abs(Normal[2]))+pivot*abs(Normal[2])}; addBoundingPlane(center,0,Normal,id_wall); } template void Network::addBoundingPlane (Real center[3], double thickness, CVector Normal, int id_wall ) { Tesselation& Tes = T[currentTes]; int Coordinate = abs(Normal[0])*0 + abs(Normal[1])*1 + abs(Normal[2])*2; Tes.insert((center[0]+Normal[0]*thickness/2)*(1-abs(Normal[0])) + (center[0]+Normal[0]*thickness/2-Normal[0]*FAR*(cornerMax.y()-cornerMin.y()))*abs(Normal[0]), (center[1]+Normal[1]*thickness/2)*(1-abs(Normal[1])) + (center[1]+Normal[1]*thickness/2-Normal[1]*FAR*(cornerMax.y()-cornerMin.y()))*abs(Normal[1]), (center[2]+Normal[2]*thickness/2)*(1-abs(Normal[2])) + (center[2]+Normal[2]*thickness/2-Normal[2]*FAR*(cornerMax.y()-cornerMin.y()))*abs(Normal[2]), FAR*(cornerMax.y()-cornerMin.y()), id_wall, true); Point P (center[0],center[1],center[2]); boundaries[id_wall-idOffset].p = P; boundaries[id_wall-idOffset].normal = Normal; boundaries[id_wall-idOffset].coordinate = Coordinate; boundaries[id_wall-idOffset].flowCondition = 1; boundaries[id_wall-idOffset].value = 0; if(debugOut) cout << "A boundary -center/thick- has been created. ID = " << id_wall << " position = " << (center[0]+Normal[0]*thickness/2)*(1-abs(Normal[0])) + (center[0]+Normal[0]*thickness/2-Normal[0]*FAR*(cornerMax.y()-cornerMin.y()))*abs(Normal[0]) << " , " << (center[1]+Normal[1]*thickness/2)*(1-abs(Normal[1])) + (center[1]+Normal[1]*thickness/2-Normal[1]*FAR*(cornerMax.y()-cornerMin.y()))*abs(Normal[1]) << " , " << (center[2]+Normal[2]*thickness/2)*(1-abs(Normal[2])) + (center[2]+Normal[2]*thickness/2-Normal[2]*FAR*(cornerMax.y()-cornerMin.y()))*abs(Normal[2]) << ". Radius = " << FAR*(cornerMax.y()-cornerMin.y()) << endl; } template void Network::defineFictiousCells() { RTriangulation& Tri = T[currentTes].Triangulation(); FiniteCellsIterator cellEnd = Tri.finite_cells_end(); for (FiniteCellsIterator cell = Tri.finite_cells_begin(); cell != cellEnd; cell++) { cell->info().fictious()=0;} for (int bound=0; bound<6;bound++) { int& id = *boundsIds[bound]; if (id<0) continue; VectorCell tmpCells; tmpCells.resize(10000); VCellIterator cells_it = tmpCells.begin(); VCellIterator cells_end = Tri.incident_cells(T[currentTes].vertexHandles[id],cells_it); for (VCellIterator it = tmpCells.begin(); it != cells_end; it++) { CellHandle& cell = *it; (cell->info().fictious())+=1; cell->info().isFictious=true; } } if(debugOut) cout << "Fictious cell defined" << endl; } // double Network::sphericalTriangleArea ( Sphere STA1, Sphere STA2, Sphere STA3, Point PTA1 ) // { // double rayon = STA1.weight(); // if ( rayon == 0.0 ) return 0.0; // // CVector v12 = STA2.point() - STA1.point(); // CVector v13 = STA3.point() - STA1.point(); // CVector v14 = PTA1 - STA1.point(); // // double norme12 = ( v12.squared_length() ); // double norme13 = ( v13.squared_length() ); // double norme14 = ( v14.squared_length() ); // // double cosA = v12*v13 / sqrt ( ( norme13 * norme12 ) ); // double cosB = v12*v14 / sqrt ( ( norme14 * norme12 ) ); // double cosC = v14*v13 / sqrt ( ( norme13 * norme14 ) ); // // double A = acos ( cosA ); // double B = acos ( cosB ); // double C = acos ( cosC ); // if ( A==0 || B==0 || C==0 ) return 0; // // double a = acos ( ( cosA - cosB * cosC ) / ( sin ( B ) * sin ( C ) ) ); // double b = acos ( ( cosB - cosC * cosA ) / ( sin ( C ) * sin ( A ) ) ); // double c = acos ( ( cosC - cosA * cosB ) / ( sin ( A ) * sin ( B ) ) ); // // double aire_triangle_spherique = rayon * ( a + b + c - M_PI ); // // return aire_triangle_spherique; // } template void Network::lineSolidPore(CellHandle cell, int j) { facetNFictious=detectFacetFictiousVertices(cell,j); double lSolid = 0; //total of solidLine[j][0], solidLine[j][1], solidLine[j][2]. Sphere v [3]; VertexHandle W [3]; for (int kk=0; kk<3; kk++) { W[kk] = cell->vertex(facetVertices[j][kk]); v[kk] = cell->vertex(facetVertices[j][kk])->point();} switch (facetNFictious) { case (0) : { VertexHandle& SV1 = W[0]; VertexHandle& SV2 = W[1]; VertexHandle& SV3 = W[2]; cell->info().solidLine[j][0]=lineSolidFacet(SV1->point(), SV2->point(), SV3->point()); cell->info().solidLine[j][1]=lineSolidFacet(SV2->point(), SV3->point(), SV1->point()); cell->info().solidLine[j][2]=lineSolidFacet(SV3->point(), SV1->point(), SV2->point()); }; break; case (1) : { VertexHandle SV1 = cell->vertex(facetVertices[j][facetRe1]); VertexHandle SV2 = cell->vertex(facetVertices[j][facetRe2]); VertexHandle SV3 = cell->vertex(facetVertices[j][facetF1]); cell->info().solidLine[j][facetRe1]=lineSolidFacet(SV1->point(), SV2->point(), SV3->point()); cell->info().solidLine[j][facetRe2]=lineSolidFacet(SV2->point(), SV1->point(), SV3->point()); Boundary &bi = boundary(SV3->info().id()); double A [3], B[3]; for (int m=0;m<3;m++) {A[m]=SV1->point()[m];B[m]= SV2->point()[m];} A[bi.coordinate]=bi.p[bi.coordinate]; B[bi.coordinate]=bi.p[bi.coordinate]; Point AA(A[0],A[1],A[2]); Point BB(B[0],B[1],B[2]); CVector AB= AA-BB; cell->info().solidLine[j][facetF1]=sqrt(AB.squared_length()); }; break; case (2) : { VertexHandle SV1 = cell->vertex(facetVertices[j][facetF1]); VertexHandle SV2 = cell->vertex(facetVertices[j][facetF2]); VertexHandle SV3 = cell->vertex(facetVertices[j][facetRe1]); cell->info().solidLine[j][facetRe1]=0.5*M_PI*sqrt(SV3->point().weight()); Boundary &bi1 = boundary(SV1->info().id()); Boundary &bi2 = boundary(SV2->info().id()); double d13 = bi1.p[bi1.coordinate] - (SV3->point())[bi1.coordinate]; double d23 = bi2.p[bi2.coordinate] - (SV3->point())[bi2.coordinate]; cell->info().solidLine[j][facetF1]= abs(d23); cell->info().solidLine[j][facetF2]= abs(d13); }; break; } lSolid = cell->info().solidLine[j][0] + cell->info().solidLine[j][1] + cell->info().solidLine[j][2]; if (lSolid) cell->info().solidLine[j][3]=1.0/lSolid; else cell->info().solidLine[j][3]=0; } template double Network::lineSolidFacet(Sphere ST1, Sphere ST2, Sphere ST3) { double line; double squaredRadius = ST1.weight(); CVector v12 = ST2.point() - ST1.point(); CVector v13 = ST3.point() - ST1.point(); double norme12 = v12.squared_length(); double norme13 = v13.squared_length(); double cosA = v12*v13 / (sqrt(norme13 * norme12)); line = acos(cosA) * sqrt(squaredRadius); return line; } } //namespace CGT #endif //FLOW_ENGINE trunk-2018.02b/lib/triangulation/PeriodicFlow.hpp000066400000000000000000000554671324306050200217340ustar00rootroot00000000000000#ifdef FLOW_ENGINE #pragma once #include //include after #define XVIEW // #include "Timer.h" // #include "PeriodicTesselation.h" // #include "basicVTKwritter.hpp" // #include "Timer.h" // #include "Network.hpp" // // #ifdef XVIEW // #include "Vue3D.h" //FIXME implicit dependencies will look for this class (out of tree) even ifndef XVIEW // #endif /* TODO: - remove computePermeability(), mostly a duplicate of the non-periodic version */ namespace CGT{ // typedef CGT::FlowBoundingSphere PeriodicFlowBoundingSphere; template class PeriodicFlow : public CGT::FlowBoundingSphere<_Tesselation> { public: typedef _Tesselation Tesselation; typedef Network _N; DECLARE_TESSELATION_TYPES(Network) typedef CGT::FlowBoundingSphere<_Tesselation> BaseFlowSolver; //painfull, but we need that for templates inheritance... using _N::T; using _N::xMin; using _N::xMax; using _N::yMin; using _N::yMax; using _N::zMin; using _N::zMax; using _N::Rmoy; using _N::sectionArea; using _N::Height; using _N::vTotal; using _N::currentTes; using _N::debugOut; using _N::nOfSpheres; using _N::xMinId; using _N::xMaxId; using _N::yMinId; using _N::yMaxId; using _N::zMinId; using _N::zMaxId; using _N::boundsIds; using _N::cornerMin; using _N::cornerMax; using _N::VSolidTot; using _N::Vtotalissimo; using _N::vPoral; using _N::sSolidTot; using _N::vPoralPorosity; using _N::vTotalPorosity; using _N::boundaries; using _N::idOffset; using _N::vtkInfiniteVertices; using _N::vtkInfiniteCells; using _N::num_particles; using _N::boundingCells; using _N::facetVertices; using _N::facetNFictious; using BaseFlowSolver::noCache; using BaseFlowSolver::rAverage; using BaseFlowSolver::distanceCorrection; using BaseFlowSolver::minPermLength; using BaseFlowSolver::checkSphereFacetOverlap; using BaseFlowSolver::viscosity; using BaseFlowSolver::kFactor; using BaseFlowSolver::permeabilityMap; using BaseFlowSolver::maxKdivKmean; using BaseFlowSolver::clampKValues; using BaseFlowSolver::KOptFactor; using BaseFlowSolver::meanKStat; using BaseFlowSolver::fluidBulkModulus; using BaseFlowSolver::relax; using BaseFlowSolver::tolerance; using BaseFlowSolver::minKdivKmean; using BaseFlowSolver::resetRHS; //same for functions using _N::defineFictiousCells; using _N::addBoundingPlanes; using _N::boundary; void interpolate(Tesselation& Tes, Tesselation& NewTes); void computeFacetForcesWithCache(bool onlyCache=false); void computePermeability(); void gaussSeidel(Real dt=0); void displayStatistics(); #ifdef EIGENSPARSE_LIB //Eigen's sparse matrix for forces computation // Eigen::SparseMatrix FIntegral; // Eigen::SparseMatrix PshiftsInts; // Eigen::SparseMatrix FRHS; // Eigen::VectorXd forces; #endif }; template void PeriodicFlow<_Tesselation>::interpolate(Tesselation& Tes, Tesselation& NewTes) { CellHandle oldCell; RTriangulation& Tri = Tes.Triangulation(); for (VCellIterator cellIt=NewTes.cellHandles.begin(); cellIt!=NewTes.cellHandles.end(); cellIt++){ CellHandle& newCell = *cellIt; if (newCell->info().Pcondition || newCell->info().isGhost) continue; CVector center ( 0,0,0 ); if (newCell->info().fictious()==0) for ( int k=0;k<4;k++ ) center= center + 0.25* (Tes.vertex(newCell->vertex(k)->info().id())->point().point()-CGAL::ORIGIN); else { Real boundPos=0; int coord=0; for ( int k=0;k<4;k++ ) { if (!newCell->vertex (k)->info().isFictious) center= center+0.3333333333*(Tes.vertex(newCell->vertex(k)->info().id())->point().point()-CGAL::ORIGIN); else { coord=boundary (newCell->vertex(k)->info().id()).coordinate; boundPos=boundary (newCell->vertex(k)->info().id()).p[coord]; } } center=CVector(coord==0?boundPos:center[0],coord==1?boundPos:center[1],coord==2?boundPos:center[2]); } oldCell = Tri.locate(CGT::Sphere(center[0],center[1],center[2])); //FIXME: should use getInfo newCell->info().p() = oldCell->info().shiftedP(); } // Tes.Clear();//Don't reset to avoid segfault when getting pressure in scripts just after interpolation } template void PeriodicFlow<_Tesselation>::computeFacetForcesWithCache(bool onlyCache) { RTriangulation& Tri = T[currentTes].Triangulation(); CVector nullVect(0,0,0); static vector oldForces; if (oldForces.size()<=Tri.number_of_vertices()) oldForces.resize(Tri.number_of_vertices()+1); //reset forces for (FiniteVerticesIterator v = Tri.finite_vertices_begin(); v != Tri.finite_vertices_end(); ++v) { if (noCache) {oldForces[v->info().id()]=nullVect; v->info().forces=nullVect;} else {oldForces[v->info().id()]=v->info().forces; v->info().forces=nullVect;} } CellHandle neighbourCell; VertexHandle mirrorVertex; CVector tempVect; //FIXME : Ema, be carefull with this (noCache), it needs to be turned true after retriangulation if (noCache) { for (VCellIterator cellIt=T[currentTes].cellHandles.begin(); cellIt!=T[currentTes].cellHandles.end(); cellIt++){ CellHandle& cell = *cellIt; for (int k=0;k<4;k++) cell->info().unitForceVectors[k]=nullVect; for (int j=0; j<4; j++) if (!Tri.is_infinite(cell->neighbor(j))) { // #ifdef EIGENSPARSE_LIB // if (!cell->info().Pcondition) ++nPCells; // #endif neighbourCell = cell->neighbor(j); const CVector& Surfk = cell->info().facetSurfaces[j]; //FIXME : later compute that fluidSurf only once in hydraulicRadius, for now keep full surface not modified in cell->info for comparison with other forces schemes //The ratio void surface / facet surface Real area = sqrt(Surfk.squared_length()); CVector facetNormal = Surfk/area; const std::vector& crossSections = cell->info().facetSphereCrossSections; CVector fluidSurfk = cell->info().facetSurfaces[j]*cell->info().facetFluidSurfacesRatio[j]; /// handle fictious vertex since we can get the projected surface easily here if (cell->vertex(j)->info().isFictious) { Real projSurf=abs(Surfk[boundary(cell->vertex(j)->info().id()).coordinate]); tempVect=-projSurf*boundary(cell->vertex(j)->info().id()).normal; //define the cached value for later use with cache*p cell->info().unitForceVectors[j]=cell->info().unitForceVectors[j]+ tempVect; } /// Apply weighted forces f_k=sqRad_k/sumSqRad*f CVector facetUnitForce = -fluidSurfk*cell->info().solidSurfaces[j][3]; for (int y=0; y<3;y++) { //add to cached value cell->info().unitForceVectors[facetVertices[j][y]]=cell->info().unitForceVectors[facetVertices[j][y]]+facetUnitForce*cell->info().solidSurfaces[j][y]; //uncomment to get total force / comment to get only viscous forces (Bruno) if (!cell->vertex(facetVertices[j][y])->info().isFictious) { //add to cached value cell->info().unitForceVectors[facetVertices[j][y]]=cell->info().unitForceVectors[facetVertices[j][y]]-facetNormal*crossSections[j][y]; } } } } noCache=false;//cache should always be defined after execution of this function if (onlyCache) return; }// end if(noCache) //use cached values //First define products that will be used below for all cells: Real pDeltas [3]; for (unsigned int k=0; k<3;k++) pDeltas[k]=CellInfo::hSize[k]*CellInfo::gradP; //Then compute the forces for (VCellIterator cellIt=T[currentTes].cellHandles.begin(); cellIt!=T[currentTes].cellHandles.end(); cellIt++){ const CellHandle& cell = *cellIt; for (int yy=0;yy<4;yy++) { VertexInfo& vhi = cell->vertex(yy)->info(); Real unshiftedP = cell->info().p(); //the pressure translated to a ghost cell adjacent to the non-ghost vertex unshiftedP -= pDeltas[0]*vhi.period[0] + pDeltas[1]*vhi.period[1] +pDeltas[2]*vhi.period[2]; T[currentTes].vertexHandles[vhi.id()]->info().forces=T[currentTes].vertexHandles[vhi.id()]->info().forces + cell->info().unitForceVectors[yy]*unshiftedP; } } if (debugOut) { CVector totalForce = nullVect; for (FiniteVerticesIterator v = Tri.finite_vertices_begin(); v != Tri.finite_vertices_end(); v++) { if (!v->info().isFictious /*&& !v->info().isGhost*/ ){ totalForce = totalForce + v->info().forces;} else /*if (!v->info().isGhost)*/{ if (boundary(v->info().id()).flowCondition==1) totalForce = totalForce + v->info().forces; } } cout << "totalForce = "<< totalForce << endl;} } template void PeriodicFlow<_Tesselation>::computePermeability() { if (debugOut) cout << "----Computing_Permeability (Periodic)------" << endl; RTriangulation& Tri = T[currentTes].Triangulation(); VSolidTot = 0, Vtotalissimo = 0, vPoral = 0, sSolidTot = 0, vTotalPorosity=0, vPoralPorosity=0; CellHandle neighbourCell; double k=0, distance = 0, radius = 0; int surfneg=0; int NEG=0, POS=0, pass=0; Real meanK=0, STDEV=0, meanRadius=0, meanDistance=0; Real infiniteK=1e3; double volume_sub_pore = 0.f; VectorCell& cellHandles= T[currentTes].cellHandles; for (VCellIterator cellIt=T[currentTes].cellHandles.begin(); cellIt!=T[currentTes].cellHandles.end(); cellIt++){ CellHandle& cell = *cellIt; Point& p1 = cell->info(); if (cell->info().blocked) { this->setBlocked(cell);} if (cell->info().isGhost) {cerr<<"skipping a ghost"<neighbor(j); Point& p2 = neighbourCell->info(); if (!Tri.is_infinite(neighbourCell) /*&& (neighbour_cell->info().isvisited==ref || computeAllCells)*/) { //compute and store the area of sphere-facet intersections for later use VertexHandle W [3]; for (int kk=0; kk<3; kk++) { W[kk] = cell->vertex(facetVertices[j][kk]); } Sphere& v0 = W[0]->point(); Sphere& v1 = W[1]->point(); Sphere& v2 = W[2]->point(); #ifdef USE_FAST_MATH //FIXME : code not compiling,, do the same as in "else" assert((W[permut3[jj][1]]->point()-W[permut3[jj][0]]->point())*(W[permut3[jj][2]]->point()-W[permut3[jj][0]]->point())>=0 && (W[permut3[jj][1]]->point()-W[permut3[jj][0]]->point())*(W[permut3[jj][2]]->point()-W[permut3[jj][0]]->point())<=1); for (int jj=0;jj<3;jj++) cell->info().facetSphereCrossSections[j][jj]=0.5*W[jj]->point().weight()*Wm3::FastInvCos1((W[permut3[jj][1]]->point()-W[permut3[jj][0]]->point())*(W[permut3[jj][2]]->point()-W[permut3[jj][0]]->point())); #else cell->info().facetSphereCrossSections[j]=CVector( W[0]->info().isFictious ? 0 : 0.5*v0.weight()*acos((v1.point()-v0.point())*(v2.point()-v0.point())/sqrt((v1.point()-v0.point()).squared_length()*(v2.point()-v0.point()).squared_length())), W[1]->info().isFictious ? 0 : 0.5*v1.weight()*acos((v0.point()-v1.point())*(v2.point()-v1.point())/sqrt((v1.point()-v0.point()).squared_length()*(v2.point()-v1.point()).squared_length())), W[2]->info().isFictious ? 0 : 0.5*v2.weight()*acos((v0.point()-v2.point())*(v1.point()-v2.point())/sqrt((v1.point()-v2.point()).squared_length()*(v2.point()-v0.point()).squared_length()))); #endif //FIXME: it should be possible to skip completely blocked cells, currently the problem is it segfault for undefined areas //if (cell->info().blocked) continue;//We don't need permeability for blocked cells, it will be set to zero anyway pass+=1; CVector l = p1 - p2; distance = sqrt(l.squared_length()); if (!rAverage) radius = 2* this->computeHydraulicRadius(cell, j); else radius = (this->computeEffectiveRadius(cell, j)+this->computeEquivalentRadius(cell,j))*0.5; if (radius<0) NEG++; else POS++; if (radius==0) { cout << "INS-INS PROBLEM!!!!!!!" << endl; } Real fluidArea=0; int test=0; if (distance!=0) { if (minPermLength>0 && distanceCorrection) distance=max(minPermLength*radius,distance); const CVector& Surfk = cell->info().facetSurfaces[j]; Real area = sqrt(Surfk.squared_length()); const CVector& crossSections = cell->info().facetSphereCrossSections[j]; Real S0=0; S0=checkSphereFacetOverlap(v0,v1,v2); if (S0==0) S0=checkSphereFacetOverlap(v1,v2,v0); if (S0==0) S0=checkSphereFacetOverlap(v2,v0,v1); //take absolute value, since in rare cases the surface can be negative (overlaping spheres) fluidArea=abs(area-crossSections[0]-crossSections[1]-crossSections[2]+S0); cell->info().facetFluidSurfacesRatio[j]=fluidArea/area; // kFactor<0 means we replace Poiseuille by Darcy localy, yielding a particle size-independent bulk conductivity if (kFactor>0) cell->info().kNorm()[j]= kFactor*(fluidArea * pow(radius,2)) / (8*viscosity*distance); else cell->info().kNorm()[j]= -kFactor * area / distance; meanDistance += distance; meanRadius += radius; meanK += (cell->info().kNorm())[j]; if (k<0 && debugOut) {surfneg+=1; cout<<"__ k<0 __"<info().id()<<" "<info().id()<<" "<info().id()<<" "<info().isGhost) (neighbourCell->info().kNorm())[Tri.mirror_index(cell, j)]= (cell->info().kNorm())[j]; //The following block is correct but not very usefull, since all values are clamped below with MIN and MAX, skip for now // else {//find the real neighbor connected to our cell through periodicity // CellHandle true_neighbour_cell = cellHandles[neighbour_cell->info().baseIndex]; // for (int ii=0;ii<4;ii++) // if (true_neighbour_cell->neighbor(ii)->info().index == cell->info().index){ // (true_neighbour_cell->info().kNorm())[ii]=(cell->info().kNorm())[j]; break; // } // } if(permeabilityMap){ CellHandle c = cell; cell->info().s = cell->info().s + k*distance/fluidArea*this->volumePoreVoronoiFraction (c,j); volume_sub_pore += this->volumePoreVoronoiFraction (c,j); } } } // cell->info().isvisited = !ref; if(permeabilityMap){ cell->info().s = cell->info().s/volume_sub_pore; volume_sub_pore = 0.f; } // } } if (debugOut) cout<<"surfneg est "<neighbor(j); if (!Tri.is_infinite(neighbourCell) /*&& neighbour_cell->info().isvisited==ref*/) { pass++; STDEV += pow(((cell->info().kNorm())[j]-meanK),2); } } } STDEV = sqrt(STDEV/pass); if (debugOut) cout << "PassSTDEV = " << pass << endl; cout << "STATISTIC K" << endl; double k_min = 0, k_max = meanK + KOptFactor*STDEV; cout << "Kmoy = " << meanK << " Standard Deviation = " << STDEV << endl; cout << "kmin = " << k_min << " kmax = " << k_max << endl; pass=0; for (FiniteCellsIterator cell = Tri.finite_cells_begin(); cell != cellEnd; cell++) { for (int j=0; j<4; j++) { neighbourCell = cell->neighbor(j); if (!Tri.is_infinite(neighbourCell) /*&& neighbour_cell->info().isvisited==ref*/) { pass+=1; if ((cell->info().kNorm())[j]>k_max) { (cell->info().kNorm())[j]=k_max; (neighbourCell->info().kNorm())[Tri.mirror_index(cell, j)]= (cell->info().kNorm())[j]; } k_opt_file << KOptFactor << " " << (cell->info().kNorm())[j] << endl; } } } if (debugOut) cout << "PassKopt = " << pass << endl; } if (debugOut) { FiniteVerticesIterator verticesEnd = Tri.finite_vertices_end(); Real Vgrains = 0; int grains=0; for (FiniteVerticesIterator vIt = Tri.finite_vertices_begin(); vIt != verticesEnd; vIt++) { if (!vIt->info().isFictious && !vIt->info().isGhost) { grains +=1; Vgrains += 1.33333333 * M_PI * pow(vIt->point().weight(),1.5); } } cout< void PeriodicFlow<_Tesselation>::gaussSeidel(Real dt) { RTriangulation& Tri = T[currentTes].Triangulation(); int j = 0; double m, n, dp_max, p_max, sum_p, dp, sum_dp; double compFlowFactor=0; vector previousP; previousP.resize(Tri.number_of_finite_cells()); const int num_threads=1; bool compressible= fluidBulkModulus>0; vector t_sum_p, t_dp_max, t_sum_dp, t_p_max; t_sum_dp.resize(num_threads); t_dp_max.resize(num_threads); t_p_max.resize(num_threads); t_sum_p.resize(num_threads); FiniteCellsIterator cellEnd = Tri.finite_cells_end(); do { int cell2=0; dp_max = 0; p_max = 0; sum_p=0; sum_dp=0; int bb=-1; for (FiniteCellsIterator cell = Tri.finite_cells_begin(); cell != cellEnd; cell++) { bb++; if ( !cell->info().Pcondition && !cell->info().isGhost) { cell2++; if (compressible && j==0) previousP[bb]=cell->info().shiftedP(); m=0, n=0; for (int j2=0; j2<4; j2++) { if (!Tri.is_infinite(cell->neighbor(j2))) { if ( compressible ) { compFlowFactor = fluidBulkModulus*dt*cell->info().invVoidVolume(); m += compFlowFactor*(cell->info().kNorm())[j2]*cell->neighbor(j2)->info().shiftedP(); if (j==0) n += compFlowFactor*(cell->info().kNorm())[j2]; } else { m += (cell->info().kNorm())[j2]*cell->neighbor(j2)->info().shiftedP(); if ( std::isinf(m) && j<10 ) cout << "(cell->info().kNorm())[j2] = " << (cell->info().kNorm())[j2] << " cell->neighbor(j2)->info().shiftedP() = " << cell->neighbor(j2)->info().shiftedP() << endl; if (j==0) n += (cell->info().kNorm())[j2]; } } } dp = cell->info().p(); if (n!=0 || j!=0) { if (j==0) { if (compressible) cell->info().invSumK=1/(1+n); else cell->info().invSumK=1/n; } if ( compressible ) { cell->info().setP( ( ((previousP[bb] - ((fluidBulkModulus*dt*cell->info().invVoidVolume())*(cell->info().dv()))) + m) * cell->info().invSumK - cell->info().shiftedP()) * relax + cell->info().shiftedP()); } else { cell->info().setP((-(cell->info().dv()-m)*cell->info().invSumK-cell->info().p())*relax+cell->info().shiftedP()); } } dp -= cell->info().p(); dp_max = max(dp_max, std::abs(dp)); p_max = max(p_max, std::abs(cell->info().shiftedP())); sum_p += cell->info().shiftedP(); sum_dp += std::abs(dp); } } j++; if (j>=40000) cerr<<"\r GS not converged after 40k iterations, break"; } while ((dp_max/p_max) > tolerance && j<40000 /*&& ( dp_max > tolerance )*//* &&*/ /*( j<50 )*/); int cel=0; double Pav=0; for (FiniteCellsIterator cell = Tri.finite_cells_begin(); cell != cellEnd; cell++) { cel++; Pav+=cell->info().shiftedP(); } Pav/=cel; } template void PeriodicFlow<_Tesselation>::displayStatistics() { RTriangulation& Tri = T[currentTes].Triangulation(); int Zero =0, Inside=0, Fictious=0, ghostC=0,realC=0, ghostV=0, realV=0; FiniteCellsIterator cellEnd = Tri.finite_cells_end(); for (FiniteCellsIterator cell = Tri.finite_cells_begin(); cell != cellEnd; cell++) { int zeros =0; for (int j=0; j!=4; j++) if ((cell->info().kNorm())[j]==0) zeros+=1; if (zeros==4) Zero+=1; if (!cell->info().fictious()) Inside+=1; else Fictious+=1; if (cell->info().isGhost) ghostC+=1; else realC+=1; } int fict=0, real=0; for (FiniteVerticesIterator v = Tri.finite_vertices_begin(); v != Tri.finite_vertices_end(); ++v) { if (v->info().isFictious) fict+=1; else real+=1; } long Vertices = Tri.number_of_vertices(); long Cells = Tri.number_of_finite_cells(); long Facets = Tri.number_of_finite_facets(); if(debugOut) { cout << "zeros = " << Zero << endl; cout << "There are " << Vertices << " vertices, dont " << fict << " fictious et " << real << " reeeeeel" << std::endl; cout << "There are " << ghostV+realV << " vertices, dont " << ghostV << " ghost et " << realV << " reeeeeel" << std::endl; cout << "There are " << ghostC+realC << " cells, dont " << ghostC << " ghost et " << realC << " reeeeeel" << std::endl; cout << "There are " << Cells << " cells " << std::endl; cout << "There are " << Facets << " facets " << std::endl; cout << "There are " << Inside << " cells INSIDE." << endl; cout << "There are " << Fictious << " cells FICTIOUS." << endl; } vtkInfiniteVertices = fict; vtkInfiniteCells = Fictious; num_particles = real; } } //END NAMESPACE #ifdef LINSOLV #include "PeriodicFlowLinSolv.hpp" #endif #endif //FLOW_ENGINE trunk-2018.02b/lib/triangulation/PeriodicFlowLinSolv.hpp000066400000000000000000000070501324306050200232240ustar00rootroot00000000000000/************************************************************************* * Copyright (C) 2010 by Bruno Chareyre * * * * This program is free software; it is licensed under the terms of the * * GNU General Public License v2 or later. See file LICENSE for details. * *************************************************************************/ #pragma once #include "FlowBoundingSphere.hpp" #ifdef FLOW_ENGINE namespace CGT { template class PeriodicFlowLinSolv : public FlowBoundingSphereLinSolv<_Tesselation,PeriodicFlow<_Tesselation> > { public: typedef _Tesselation Tesselation; typedef Network _N; DECLARE_TESSELATION_TYPES(Network) typedef FlowBoundingSphereLinSolv<_Tesselation,PeriodicFlow<_Tesselation> > BaseFlowSolver; typedef typename BaseFlowSolver::ETriplet ETriplet; ///painfull, but we need that for templates inheritance... using _N::T; using _N::xMin; using _N::xMax; using _N::yMin; using _N::yMax; using _N::zMin; using _N::zMax; using _N::Rmoy; using _N::sectionArea; using _N::Height; using _N::vTotal; using _N::currentTes; using _N::debugOut; using _N::nOfSpheres; using _N::xMinId; using _N::xMaxId; using _N::yMinId; using _N::yMaxId; using _N::zMinId; using _N::zMaxId; using _N::boundsIds; using _N::cornerMin; using _N::cornerMax; using _N::VSolidTot; using _N::Vtotalissimo; using _N::vPoral; using _N::sSolidTot; using _N::vPoralPorosity; using _N::vTotalPorosity; using _N::boundaries; using _N::idOffset; using _N::vtkInfiniteVertices; using _N::vtkInfiniteCells; using _N::num_particles; using _N::boundingCells; using _N::facetVertices; using _N::facetNFictious; //same for functions using _N::defineFictiousCells; using _N::addBoundingPlanes; using _N::boundary; using BaseFlowSolver::noCache; using BaseFlowSolver::rAverage; using BaseFlowSolver::distanceCorrection; using BaseFlowSolver::minPermLength; using BaseFlowSolver::checkSphereFacetOverlap; using BaseFlowSolver::viscosity; using BaseFlowSolver::kFactor; using BaseFlowSolver::permeabilityMap; using BaseFlowSolver::maxKdivKmean; using BaseFlowSolver::clampKValues; using BaseFlowSolver::KOptFactor; using BaseFlowSolver::meanKStat; using BaseFlowSolver::fluidBulkModulus; using BaseFlowSolver::relax; using BaseFlowSolver::tolerance; using BaseFlowSolver::minKdivKmean; using BaseFlowSolver::resetRHS; using BaseFlowSolver::factorizeOnly; /// More members from LinSolv variant using BaseFlowSolver::areCellsOrdered; using BaseFlowSolver::T_nnz; using BaseFlowSolver::ncols; using BaseFlowSolver::T_cells; using BaseFlowSolver::T_index; using BaseFlowSolver::orderedCells; using BaseFlowSolver::isLinearSystemSet; using BaseFlowSolver::T_x; using BaseFlowSolver::T_b; using BaseFlowSolver::T_bv; using BaseFlowSolver::bodv; using BaseFlowSolver::xodv; using BaseFlowSolver::errorCode; using BaseFlowSolver::useSolver; using BaseFlowSolver::tripletList; using BaseFlowSolver::A; using BaseFlowSolver::gsP; using BaseFlowSolver::gsB; using BaseFlowSolver::fullAcolumns; using BaseFlowSolver::fullAvalues; using BaseFlowSolver::isFullLinearSystemGSSet; using BaseFlowSolver::gsdV; vector indices;//redirection vector containing the rank of cell so that T_cells[indices[cell->info().index]]=cell virtual ~PeriodicFlowLinSolv(); PeriodicFlowLinSolv(); ///Linear system solve virtual int setLinearSystem(Real dt=0); virtual int setLinearSystemFullGS(Real dt=0); }; } //namespace CGTF #include "PeriodicFlowLinSolv.ipp" #endif //FLOW_ENGINE trunk-2018.02b/lib/triangulation/PeriodicFlowLinSolv.ipp000066400000000000000000000177251324306050200232370ustar00rootroot00000000000000/************************************************************************* * Copyright (C) 2010 by Bruno Chareyre * * * * This program is free software; it is licensed under the terms of the * * GNU General Public License v2 or later. See file LICENSE for details. * *************************************************************************/ #ifdef FLOW_ENGINE #if CGAL_VERSION_NR < CGAL_VERSION_NUMBER(4,11,0) #include "CGAL/constructions/constructions_on_weighted_points_cartesian_3.h" #endif #include #include #include #include #include #include "vector" #include #include #include #ifdef YADE_OPENMP #include #endif namespace CGT { #ifdef PARDISO #ifdef AIX #define F77_FUNC(func) func #else #define F77_FUNC(func) func ## _ #endif /* PARDISO prototype. */ extern "C" int F77_FUNC(pardisoinit) (void *, int *, int *, int *, double *, int *); extern "C" int F77_FUNC(pardiso) (void *, int *, int *, int *, int *, int *, double *, int *, int *, int *, int *, int *, int *, double *, double *, int *, double *); #endif template PeriodicFlowLinSolv<_Tesselation>::~PeriodicFlowLinSolv() { } template PeriodicFlowLinSolv<_Tesselation>::PeriodicFlowLinSolv(): BaseFlowSolver() {} template int PeriodicFlowLinSolv<_Tesselation>::setLinearSystem(Real dt) { //WARNING : boundary conditions (Pcondition, p values) must have been set for a correct definition of RTriangulation& Tri = T[currentTes].Triangulation(); vector clen; vector is; vector js; vector vs; if (!areCellsOrdered) { T_nnz=0; ncols=0; T_cells.clear(); T_index=0; unsigned int maxindex = 0; //FIXME: this is way too large since many cells will be ghosts T_cells.resize(Tri.number_of_finite_cells()+1); ///Ordered cells orderedCells.clear(); const FiniteCellsIterator cellEnd = Tri.finite_cells_end(); for (FiniteCellsIterator cell = Tri.finite_cells_begin(); cell != cellEnd; cell++) { if (cell->info().Pcondition || cell->info().isGhost) continue; orderedCells.push_back(cell); // T_cells[++ncols]=cell; // indices[cell->info().index]=ncols; ++ncols; T_cells[cell->info().index]=cell; maxindex=max(maxindex,cell->info().index); } // spatial_sort(orderedCells.begin(),orderedCells.end(), CellTraits_for_spatial_sort());//FIXME: ordering will not work with the new "indices", which needs reordering to T_cells.resize(ncols+1); isLinearSystemSet=false; areCellsOrdered=true; } if (!isLinearSystemSet) { int n = 3*(ncols+1);//number of non-zero in triangular matrix is.resize(n); js.resize(n); vs.resize(n); T_x.resize(ncols); T_b.resize(ncols); T_bv.resize(ncols); bodv.resize(ncols); xodv.resize(ncols); T_cells.resize(ncols+1); T_nnz=0;} for (int kk=0; kkinfo().index; const CellInfo& cInfo = cell->info(); if (!isLinearSystemSet) { //Add diagonal term is[T_nnz] = index; js[T_nnz] = index; vs[T_nnz] = (cInfo.kNorm())[0]+ (cInfo.kNorm())[1]+ (cInfo.kNorm())[2]+ (cInfo.kNorm())[3]; if (vs[T_nnz]<0) cerr<<"!!!! WTF !!!"<0) vs[T_nnz] += (1.f/(dt*fluidBulkModulus*cInfo.invVoidVolume())); T_nnz++; } for (int j=0; j<4; j++) { neighbourCell = cell->neighbor(j); if (Tri.is_infinite(neighbourCell)) continue; CellInfo& nInfo = neighbourCell->info(); nIndex=nInfo.index; if (nIndex==index) { cerr<<"ERROR: nIndex==index, the cell is neighbour to itself"<< index<nIndex) { is[T_nnz] = index; js[T_nnz] = nIndex; vs[T_nnz] = - (cInfo.kNorm())[j]; if (vs[T_nnz]>0) cerr<<"!!!! WTF2 !!!"<0){ #ifdef EIGENSPARSE_LIB tripletList.clear(); tripletList.resize(T_nnz); for(int k=0;k0 not supported"< int PeriodicFlowLinSolv<_Tesselation>::setLinearSystemFullGS(Real dt) { //WARNING : boundary conditions (Pcondition, p values) must have been set for a correct definition RTriangulation& Tri = T[currentTes].Triangulation(); int n_cells=Tri.number_of_finite_cells(); if (!isFullLinearSystemGSSet){ T_cells.clear(); T_index=0;//FIXME : no need to clear if we don't re-triangulate T_nnz=0; ncols=0; const FiniteCellsIterator cellEnd = Tri.finite_cells_end(); orderedCells.clear(); T_cells.resize(n_cells+1); for (FiniteCellsIterator cell = Tri.finite_cells_begin(); cell != cellEnd; cell++) { if (cell->info().Pcondition || cell->info().isGhost) continue; ++ncols; T_cells[cell->info().index]=cell; } // spatial_sort(orderedCells.begin(),orderedCells.end(), CellTraits_for_spatial_sort()); gsP.resize(ncols+1); gsB.resize(ncols+1); T_b.resize(ncols+1); gsdV.resize(ncols+1); fullAcolumns.resize(ncols+1); fullAvalues.resize(ncols+1); T_cells.resize(ncols+1); for (int k=0; k<=ncols;k++) { fullAcolumns[k].resize(4); fullAvalues[k].resize(5); gsdV[k]=0; } gsP[0]=0; areCellsOrdered=true; } for (int k=0; k<=ncols;k++) gsB[k]=0; if (!isFullLinearSystemGSSet){ ///Ordered cells for (int i=1; i<=ncols; i++) { CellHandle& cell = T_cells[i]; ///Non-ordered cells if (!cell->info().Pcondition && !cell->info().isGhost) { //Add diagonal term fullAvalues[cell->info().index][4] = 1.f/((cell->info().kNorm())[0]+ (cell->info().kNorm())[1]+ (cell->info().kNorm())[2]+ (cell->info().kNorm())[3] + (fluidBulkModulus>0? 1.f/(dt*fluidBulkModulus*cell->info().invVoidVolume()):0)); //DUMP T_nnz++; for (int j=0; j<4; j++) { CellHandle neighbourCell = cell->neighbor(j); const CellInfo& nInfo = neighbourCell->info(); CellInfo& cInfo = cell->info(); if (Tri.is_infinite(neighbourCell)) { fullAvalues[cInfo.index][j] = 0; //We point to the pressure of the adjacent cell. If the cell is ghost, then it has the index of the real one, and then the pointer is correct fullAcolumns[cInfo.index][j] = &gsP[0]; continue;} if (!nInfo.Pcondition) { ++T_nnz; fullAvalues[cInfo.index][j] = (cInfo.kNorm())[j]; fullAcolumns[cInfo.index][j] = &gsP[nInfo.index]; //DUMP //if the adjacent cell is ghost, we account for the pressure shift in the RHS if (nInfo.isGhost){ gsB[cInfo.index]+=cInfo.kNorm()[j]*nInfo.pShift(); } } else { fullAvalues[cInfo.index][j] = 0; fullAcolumns[cInfo.index][j] = &gsP[0]; gsB[cInfo.index]+=cInfo.kNorm()[j]*nInfo.shiftedP(); } } } } } else for (int i=1; i<=ncols; i++) { CellHandle& cell = T_cells[i]; ///Non-ordered cells if (!cell->info().Pcondition && !cell->info().isGhost) { for (int j=0; j<4; j++) { CellHandle neighbourCell = cell->neighbor(j); const CellInfo& nInfo = neighbourCell->info(); CellInfo& cInfo = cell->info(); if (!nInfo.Pcondition) { if (nInfo.isGhost) gsB[cInfo.index]+=cInfo.kNorm()[j]*nInfo.pShift(); } else gsB[cInfo.index]+=cInfo.kNorm()[j]*nInfo.shiftedP(); } } } isFullLinearSystemGSSet=true; return ncols; } } //namespace CGT #endif //FLOW_ENGINE trunk-2018.02b/lib/triangulation/RegularTriangulation.h000066400000000000000000000160311324306050200231300ustar00rootroot00000000000000/************************************************************************* * Copyright (C) 2006 by Bruno Chareyre * * bruno.chareyre@hmg.inpg.fr * * * * This program is free software; it is licensed under the terms of the * * GNU General Public License v2 or later. See file LICENSE for details. * *************************************************************************/ //Define basic types from CGAL templates #pragma once #include #include #include #if CGAL_VERSION_NR < CGAL_VERSION_NUMBER(4,11,0) #include #endif #define ALPHASHAPES #ifdef ALPHASHAPES #include #include #include #endif #include #include #include #include #include #include //This include from yade let us use Eigen types #include const int facetVertices [4][3] = {{1,2,3},{0,2,3},{0,1,3},{0,1,2}}; namespace CGT { //Robust kernel typedef CGAL::Exact_predicates_inexact_constructions_kernel K; //A bit faster, but gives crash eventualy // typedef CGAL::Cartesian K; #if CGAL_VERSION_NR < CGAL_VERSION_NUMBER(4,11,0) typedef CGAL::Regular_triangulation_euclidean_traits_3 Traits; #else typedef K Traits; #endif typedef K::Point_3 Point; typedef Traits::Vector_3 CVector; typedef Traits::Segment_3 Segment; #ifndef NO_REAL_CHECK /** compilation inside yade: check that Real in yade is the same as Real we will define; otherwise it might make things go wrong badly (perhaps) **/ BOOST_STATIC_ASSERT(sizeof(Traits::RT)==sizeof(Real)); #endif #if CGAL_VERSION_NR < CGAL_VERSION_NUMBER(4,11,0) typedef Traits::RT Real; //Dans cartesian, RT = FT typedef Traits::Weighted_point Sphere; #else typedef Traits::FT Real; //Dans cartesian, RT = FT typedef Traits::Weighted_point_3 Sphere; #endif typedef Traits::Plane_3 Plane; typedef Traits::Triangle_3 Triangle; typedef Traits::Tetrahedron_3 Tetrahedron; class SimpleCellInfo : public Point { public: //"id": unique identifier of each cell, independant of other numberings used in the fluid types. // Care to initialize it if you need it, there is no magic numbering to rely on unsigned int id; Real s; bool isFictious;//true if the cell has at least one fictious bounding sphere as a vertex SimpleCellInfo (void) {isFictious=false; s=0;Point::operator= (CGAL::ORIGIN);} SimpleCellInfo& setPoint(const Point &p) { Point::operator= (p); return *this; } SimpleCellInfo& setScalar(const Real &scalar) { s=scalar; return *this; } inline Real x (void) {return Point::x();} inline Real y (void) {return Point::y();} inline Real z (void) {return Point::z();} inline Real& f (void) {return s;} //virtual function that will be defined for all classes, allowing shared function (e.g. for display of periodic and non-periodic with the unique function saveVTK) bool isReal (void) {return !isFictious;} }; class SimpleVertexInfo : public CVector { protected: Real s; unsigned int i; Real vol; public: bool isFictious; SimpleVertexInfo& setVector(const CVector &u) { CVector::operator= (u); return *this; } SimpleVertexInfo& setFloat(const float &scalar) { s=scalar; return *this; } SimpleVertexInfo& setId(const unsigned int &id) { i= id; return *this; } inline Real ux (void) {return CVector::x();} inline Real uy (void) {return CVector::y();} inline Real uz (void) {return CVector::z();} inline Real& f (void) {return s;} inline Real& v (void) {return vol;} inline const unsigned int& id (void) const {return i;} SimpleVertexInfo (void) {isFictious=false; s=0; i=0; vol=-1;} //virtual function that will be defined for all classes, allowing shared function (e.g. for display) bool isReal (void) {return !isFictious;} }; template class TriangulationTypes { public: typedef vertex_info Vertex_Info; typedef cell_info Cell_Info; #if CGAL_VERSION_NR < CGAL_VERSION_NUMBER(4,11,0) typedef CGAL::Triangulation_vertex_base_with_info_3 Vb_info; typedef CGAL::Triangulation_cell_base_with_info_3 Cb_info; #else typedef CGAL::Regular_triangulation_vertex_base_3 Vb0; typedef CGAL::Regular_triangulation_cell_base_3 Rcb; typedef CGAL::Triangulation_vertex_base_with_info_3 Vb_info; typedef CGAL::Triangulation_cell_base_with_info_3 Cb_info; #endif #ifdef ALPHASHAPES typedef CGAL::Alpha_shape_vertex_base_3 Vb; typedef CGAL::Alpha_shape_cell_base_3 Fb; typedef CGAL::Triangulation_data_structure_3 Tds; #else typedef CGAL::Triangulation_data_structure_3 Tds; #endif typedef CGAL::Triangulation_3 Triangulation; typedef CGAL::Regular_triangulation_3 RTriangulation; #ifdef ALPHASHAPES typedef CGAL::Alpha_shape_3 AlphaShape; #endif typedef typename RTriangulation::Vertex_iterator VertexIterator; typedef typename RTriangulation::Vertex_handle VertexHandle; typedef typename RTriangulation::Finite_vertices_iterator FiniteVerticesIterator; typedef typename RTriangulation::Cell_iterator CellIterator; typedef typename RTriangulation::Finite_cells_iterator FiniteCellsIterator; typedef typename RTriangulation::Cell_circulator CellCirculator; typedef typename RTriangulation::Cell_handle CellHandle; typedef typename RTriangulation::Facet Facet; typedef typename RTriangulation::Facet_iterator FacetIterator; typedef typename RTriangulation::Facet_circulator FacetCirculator; typedef typename RTriangulation::Finite_facets_iterator FiniteFacetsIterator; typedef typename RTriangulation::Locate_type LocateType; typedef typename RTriangulation::Edge_iterator EdgeIterator; typedef typename RTriangulation::Finite_edges_iterator FiniteEdgesIterator; }; typedef TriangulationTypes SimpleTriangulationTypes; } // namespace CGT typedef CGT::CVector CVector; typedef CGT::Point Point; /// Converters for Eigen and CGAL vectors inline CVector makeCgVect ( const Vector3r& yv ) {return CVector ( yv[0],yv[1],yv[2] );} inline Point makeCgPoint ( const Vector3r& yv ) {return Point ( yv[0],yv[1],yv[2] );} inline Vector3r makeVector3r ( const Point& yv ) {return Vector3r ( yv[0],yv[1],yv[2] );} inline Vector3r makeVector3r ( const CVector& yv ) {return Vector3r ( yv[0],yv[1],yv[2] );} trunk-2018.02b/lib/triangulation/Tenseur3.cpp000066400000000000000000000132341324306050200210330ustar00rootroot00000000000000#include "Tenseur3.h" #include "RegularTriangulation.h" namespace CGT { Real Tens::Norme2(void) { Real N=0; for (int i=1; i<=3; i++) for (int j=1; j<=3; j++) N+= pow(operator ()(i,j), 2); return N; } CVector operator* (Tens& tens, CVector& vect) { CVector result; result = CVector( tens(1,1)*vect.x()+ tens(1,2)*vect.y()+ tens(1,3)*vect.z(), tens(2,1)*vect.x()+ tens(2,2)*vect.y()+ tens(2,3)*vect.z(), tens(3,1)*vect.x()+ tens(3,2)*vect.y()+ tens(3,3)*vect.z() ); return result; } CVector& NormalizedCVector (CVector& vect) { vect = vect*(1.0/sqrt(pow(vect.x(),2)+pow(vect.y(),2)+pow(vect.z(),2))); return vect; } Tenseur3::Tenseur3(bool init) { if (init) for (int i=0; i<3; i++) for (int j=0; j<3; j++) T[i][j] = 0; } Tenseur3::~Tenseur3(void) { } Tenseur3::Tenseur3(const Tenseur3& source) { for (int i=0; i<3; i++) for (int j=0; j<3; j++) T[i][j] = source.T[i][j]; } Tenseur3::Tenseur3(Real a11, Real a12, Real a13, Real a21, Real a22, Real a23, Real a31, Real a32, Real a33) { T[0][0] = a11; T[0][1] = a12; T[0][2] = a13; T[1][0] = a21; T[1][1] = a22; T[1][2] = a23; T[2][0] = a31; T[2][1] = a32; T[2][2] = a33; } Tenseur3 &Tenseur3::operator=(const Tenseur3& source) { if (&source != this) for (int i=0; i<3; i++) for (int j=0; j<3; j++) T[i][j] = source.T[i][j]; return *this; } Tenseur3 &Tenseur3::operator/=(Real d) { if (d!=0) { d = 1.0/d; for (int i=0; i<3; i++) for (int j=0; j<3; j++) T[i][j] *= d; } return *this; } Tenseur3 & Tenseur3::operator +=(const Tenseur3 & source) { for (int i=0; i<3; i++) for (int j=0; j<3; j++) T[i][j] += source.T[i][j]; return *this; } /////////// Classe Tenseur_sym3 //////////// Tenseur_sym3::Tenseur_sym3(bool init) { if (init) { for (int i=0; i<6; i++) T[i] = 0; } } Tenseur_sym3::~Tenseur_sym3(void) { } Tenseur_sym3::Tenseur_sym3(const Tenseur_sym3& source) { for (int i=0; i<6; i++) { for (int i=0; i<6; i++) T[i] = source.T[i]; } } Tenseur_sym3::Tenseur_sym3(const Tenseur3& source) { for (int i=1; i<=3; i++) { T[i-1]=source(i,i); for (int j=3; j>i; j--) T[i+j] = (source(i,j)+source(j,i))*0.5; } } Tenseur_sym3::Tenseur_sym3(Real a11, Real a22, Real a33, Real a12, Real a13, Real a23) { T[0] = a11; T[1] = a22; T[2] = a33; T[3] = a12; T[4] = a13; T[5] = a23; } Tenseur_sym3 &Tenseur_sym3::operator=(const Tenseur_sym3& source) { if (&source != this) { for (int i=0; i<6; i++) T[i] = source.T[i]; } return *this; } Tenseur_sym3 &Tenseur_sym3::operator/=(Real d) { if (d!=0) { d=1.0/d; for (int i=0; i<6; i++) T[i]*= d;} return *this; } Real Tenseur_sym3::operator() (int i, int j) const { if (i==j) return T[i-1]; else return T[i+j]; } Real &Tenseur_sym3::operator() (int i, int j) { if (i==j) return T[i-1]; else return T[i+j]; } Tenseur_sym3 Tenseur_sym3::Deviatoric (void) const //retourne la partie d�viatoire { Tenseur_sym3 temp(*this); Real spheric = temp.Trace()/3; temp(1,1)-= spheric; temp(2,2)-= spheric; temp(3,3)-= spheric; return temp; } /////////// Classe Tenseur_anti3 //////////// Tenseur_anti3::Tenseur_anti3(bool init) { if (init) { for (int i=0; i<6; i++) T[i] = 0; } } Tenseur_anti3::~Tenseur_anti3(void) { } Tenseur_anti3::Tenseur_anti3(const Tenseur_anti3& source) { for (int i=0; i<6; i++) { for (int i=0; i<6; i++) T[i] = source.T[i]; } } Tenseur_anti3::Tenseur_anti3(const Tenseur3& source) { for (int i=1; i<=3; i++) { T[i-1]=0; for (int j=3; j>i; j--) T[i+j] = (source(i,j)-source(j,i))*0.5; } } Tenseur_anti3::Tenseur_anti3(Real a11, Real a22, Real a33, Real a12, Real a13, Real a23) { T[0] = a11; T[1] = a22; T[2] = a33; T[3] = a12; T[4] = a13; T[5] = a23; } Tenseur_anti3 &Tenseur_anti3::operator=(const Tenseur_anti3& source) { if (&source != this) { for (int i=0; i<6; i++) T[i] = source.T[i]; } return *this; } Tenseur_anti3 &Tenseur_anti3::operator/=(Real d) { if (d!=0) for (int i=0; i<6; i++) T[i]/= d; return *this; } Real Tenseur_anti3::operator() (int i, int j) const { if (i==j) return T[i-1]; else { if (i #include #include "RegularTriangulation.h" namespace CGT { using std::endl; using std::cout; using std::cerr; using std::string; #define NORMALIZE(vecteur) ((vecteur) = (vecteur)*(1.0/sqrt(pow((vecteur)[0],2)+pow((vecteur)[1],2)+pow((vecteur)[2],2)))) class Tens; class Tenseur3; class Tenseur_sym3; class Tenseur_anti3; CVector operator* ( Tens& tens, CVector& vect ); CVector& NormalizedCVector ( CVector& vect ); void Tenseur_produit ( CVector &v1, CVector &v2, Tenseur3 &result ); void Somme ( Tenseur3 &result, CVector &v1, CVector &v2 ); std::ostream& operator<< ( std::ostream& os,const Tenseur3& T ); std::ostream& operator<< ( std::ostream& os,const Tenseur_sym3& T ); std::ostream& operator<< ( std::ostream& os,const Tenseur_anti3& T ); class Tens { public: Tens ( void ) {} virtual ~Tens ( void ) {} virtual Real operator() ( int i, int j ) const {return 0;} Real Norme2 ( void ); Real Norme ( void ) {return sqrt ( Norme2() );} Real Trace ( void ) { return this->operator () ( 1,1 ) + this->operator () ( 2,2 ) + this->operator () ( 3,3 ); } }; class Tenseur3 : public Tens { private: Real T [3] [3]; public: Tenseur3 ( bool init = true );// Sp�cifier "false" pour �conomiser le temps d'initialisation du tableau virtual ~Tenseur3 ( void ); Tenseur3 ( const Tenseur3& source ); Tenseur3 ( Real a11, Real a12, Real a13, Real a21, Real a22, Real a23, Real a31, Real a32, Real a33 ); Tenseur3& operator= ( const Tenseur3& source ); Tenseur3& operator/= ( Real d ); Tenseur3& operator+= ( const Tenseur3& source ); Real operator() ( int i, int j ) const {return T[i-1][j-1];} Real &operator() ( int i, int j ) {return T[i-1][j-1];} virtual void reset ( void ) {for ( int i=0; i<3; i++ ) for ( int j=0; j<3; j++ ) T[i][j] = 0;} }; class Tenseur_sym3 : public Tens { private: Real T [6]; public: Tenseur_sym3 ( bool init = true );// Sp�cifier "false" pour �conomiser le temps d'initialisation du tableau ~Tenseur_sym3 ( void ); Tenseur_sym3 ( const Tenseur_sym3& source ); Tenseur_sym3 ( const Tenseur3& source ); Tenseur_sym3 ( Real a11, Real a22, Real a33, Real a12, Real a13, Real a23 ); Tenseur_sym3& operator= ( const Tenseur_sym3& source ); Tenseur_sym3& operator/= ( Real d ); Tenseur_sym3 Deviatoric ( void ) const; //retourne la partie d�viatoire Real operator() ( int i, int j ) const; Real &operator() ( int i, int j ); void reset ( void ) {for ( int i=0; i<6; i++ ) T[i] = 0;} }; class Tenseur_anti3 : public Tens { private: Real T [6]; public: Tenseur_anti3 ( bool init = true );// Sp�cifier "false" pour �conomiser le temps d'initialisation du tableau virtual ~Tenseur_anti3 ( void ); Tenseur_anti3 ( const Tenseur_anti3& source ); Tenseur_anti3 ( const Tenseur3& source ); Tenseur_anti3 ( Real a11, Real a22, Real a33, Real a12, Real a13, Real a23 ); Tenseur_anti3& operator= ( const Tenseur_anti3& source ); Tenseur_anti3& operator/= ( Real d ); Real operator() ( int i, int j ) const; //Real &operator() (int i, int j); //Supprim�e car pb. pour retourner une r�f�rence vers -T[i+j] void reset ( void ) {for ( int i=0; i<6; i++ ) T[i] = 0;} }; static const Tenseur3 NULL_TENSEUR3 ( 0,0,0,0,0,0,0,0,0 ); } trunk-2018.02b/lib/triangulation/Tesselation.h000066400000000000000000000245501324306050200212650ustar00rootroot00000000000000/************************************************************************* * Copyright (C) 2006 by Bruno Chareyre * * bruno.chareyre@hmg.inpg.fr * * * * This program is free software; it is licensed under the terms of the * * GNU General Public License v2 or later. See file LICENSE for details. * *************************************************************************/ #pragma once #include "RegularTriangulation.h" namespace CGT { //Since template inheritance does not automatically give access to the members of the base class, this macro can be used to declare all members at once. #ifdef ALPHASHAPES #define DECLARE_TESSELATION_TYPES(baseType)\ typedef typename baseType::RTriangulation RTriangulation;\ typedef typename baseType::AlphaShape AlphaShape;\ typedef typename baseType::VertexInfo VertexInfo;\ typedef typename baseType::CellInfo CellInfo;\ typedef typename baseType::VertexIterator VertexIterator;\ typedef typename baseType::VertexHandle VertexHandle;\ typedef typename baseType::FiniteVerticesIterator FiniteVerticesIterator;\ typedef typename baseType::CellIterator CellIterator;\ typedef typename baseType::FiniteCellsIterator FiniteCellsIterator;\ typedef typename baseType::CellCirculator CellCirculator;\ typedef typename baseType::CellHandle CellHandle;\ typedef typename baseType::Facet Facet;\ typedef typename baseType::FacetIterator FacetIterator;\ typedef typename baseType::FacetCirculator FacetCirculator;\ typedef typename baseType::FiniteFacetsIterator FiniteFacetsIterator;\ typedef typename baseType::LocateType LocateType;\ typedef typename baseType::EdgeIterator EdgeIterator;\ typedef typename RTriangulation::Edge Edge;\ typedef typename baseType::FiniteEdgesIterator FiniteEdgesIterator;\ typedef typename baseType::VectorVertex VectorVertex;\ typedef typename baseType::VectorCell VectorCell;\ typedef typename baseType::ListPoint ListPoint;\ typedef typename baseType::VCellIterator VCellIterator; #else #define DECLARE_TESSELATION_TYPES(baseType)\ typedef typename baseType::RTriangulation RTriangulation;\ typedef typename baseType::VertexInfo VertexInfo;\ typedef typename baseType::CellInfo CellInfo;\ typedef typename baseType::VertexIterator VertexIterator;\ typedef typename baseType::VertexHandle VertexHandle;\ typedef typename baseType::FiniteVerticesIterator FiniteVerticesIterator;\ typedef typename baseType::CellIterator CellIterator;\ typedef typename baseType::FiniteCellsIterator FiniteCellsIterator;\ typedef typename baseType::CellCirculator CellCirculator;\ typedef typename baseType::CellHandle CellHandle;\ typedef typename baseType::Facet Facet;\ typedef typename baseType::FacetIterator FacetIterator;\ typedef typename baseType::FacetCirculator FacetCirculator;\ typedef typename baseType::FiniteFacetsIterator FiniteFacetsIterator;\ typedef typename baseType::LocateType LocateType;\ typedef typename baseType::EdgeIterator EdgeIterator;\ typedef typename baseType::FiniteEdgesIterator FiniteEdgesIterator;\ typedef typename baseType::VectorVertex VectorVertex;\ typedef typename baseType::VectorCell VectorCell;\ typedef typename baseType::ListPoint ListPoint;\ typedef typename baseType::VCellIterator VCellIterator; #endif // Classe Tesselation, contient les fonctions permettant de calculer la Tessalisation // d'une RTriangulation et de stocker les centres dans chacune de ses cellules //TT is of model TriangulationTypes template class _Tesselation { public: typedef typename TT::RTriangulation RTriangulation; #ifdef ALPHASHAPES typedef typename TT::AlphaShape AlphaShape; #endif typedef typename TT::Vertex_Info VertexInfo; typedef typename TT::Cell_Info CellInfo; typedef typename RTriangulation::Vertex_iterator VertexIterator; typedef typename RTriangulation::Vertex_handle VertexHandle; typedef typename RTriangulation::Finite_vertices_iterator FiniteVerticesIterator; typedef typename RTriangulation::Cell_iterator CellIterator; typedef typename RTriangulation::Finite_cells_iterator FiniteCellsIterator; typedef typename RTriangulation::Cell_circulator CellCirculator; typedef typename RTriangulation::Cell_handle CellHandle; typedef typename RTriangulation::Facet Facet; typedef typename RTriangulation::Facet_iterator FacetIterator; typedef typename RTriangulation::Facet_circulator FacetCirculator; typedef typename RTriangulation::Finite_facets_iterator FiniteFacetsIterator; typedef typename RTriangulation::Locate_type LocateType; typedef typename RTriangulation::Edge_iterator EdgeIterator; typedef typename RTriangulation::Edge Edge; typedef typename RTriangulation::Finite_edges_iterator FiniteEdgesIterator; typedef std::vector VectorVertex; typedef std::vector VectorCell; typedef std::list ListPoint; typedef typename VectorCell::iterator VCellIterator; int maxId; protected: RTriangulation* Tri; RTriangulation* Tes; //=NULL or Tri depending on the constructor used. public: Real TotalFiniteVoronoiVolume; Real area; Real TotalInternalVoronoiVolume; Real TotalInternalVoronoiPorosity; VectorVertex vertexHandles;//This is a redirection vector to get vertex pointers by spheres id VectorCell cellHandles;//for speedup of global loops, iterating on this vector is faster than cellIterator++ bool redirected;//is vertexHandles filled with current vertex pointers? bool computed; _Tesselation(void); _Tesselation(RTriangulation &T);// : Tri(&T) { Calcule(); } ~_Tesselation(void); ///Insert a sphere VertexHandle insert(Real x, Real y, Real z, Real rad, unsigned int id, bool isFictious = false); /// move a spheres VertexHandle move (Real x, Real y, Real z, Real rad, unsigned int id); ///Fill a vector with vertexHandles[i] = handle of vertex with id=i for fast access bool redirect (void); ///Remove a sphere bool remove (unsigned int id); int Max_id (void) {return maxId;} void compute (); //Calcule le centres de Voronoi pour chaque cellule Point setCircumCenter (const CellHandle& cell, bool force=0); Point circumCenter (const Sphere& S0, const Sphere& S1, const Sphere& S2, const Sphere& S3); Point circumCenter (const CellHandle& cell); Point circumCenter (const CellHandle& cell, const short facet, const double wExt, bool& violate, Sphere& SAlpha, CVector& normal); Point circumCenter (const CellHandle& cell, const short facet, const Sphere& sExt, bool& violate); void Invalidate () {computed=false;} //Set the tesselation as "not computed" (computed=false), this will launch tesselation internaly when using functions like computeVolumes()) // N.B : compute() must be executed before the functions below are used void Clear(void); static Point Dual (const CellHandle &cell); static Plane Dual (VertexHandle S1, VertexHandle S2); static Segment Dual (FiniteFacetsIterator &facet); //G�n�re le segment dual d'une facette finie static Real Volume (FiniteCellsIterator cell); inline void AssignPartialVolume (FiniteEdgesIterator& ed_it); double computeVFacetArea (FiniteEdgesIterator ed_it); void ResetVCellVolumes (void); void computeVolumes (void);//compute volume each voronoi cell void computePorosity (void);//compute volume and porosity of each voronoi cell inline Real& Volume (unsigned int id) { return vertexHandles[id]->info().v(); } inline const VertexHandle& vertex (unsigned int id) const { return vertexHandles[id]; } // Alpha Shapes void testAlphaShape(double alpha=0); struct AlphaFace {unsigned int ids[3]; CVector normal;}; struct AlphaCap {unsigned int id; CVector normal;}; void setAlphaFaces(std::vector& faces, double alpha=0); void setExtendedAlphaCaps(std::vector& caps, double alpha, double shrinkedAlpha, bool fixedAlpha); std::vector getExtendedAlphaGraph(double alpha, double shrinkedAlpha, bool fixedAlpha); CVector alphaVoronoiFaceArea (const Edge& ed_it, const AlphaShape& as, const RTriangulation& Tri); CVector alphaVoronoiPartialCapArea (const Edge& ed_it, const AlphaShape& as,std::vector& vEdges); CVector alphaVoronoiPartialCapArea (Facet facet, const AlphaShape& as, double shrinkedAlpha, std::vector& vEdges); std::vector getAlphaVertices(double alpha=0); // FiniteCellsIterator finite_cells_begin(void);// {return Tri->finite_cells_begin();} // FiniteCellsIterator finiteCellsEnd(void);// {return Tri->finite_cells_end();} void voisins (VertexHandle v, VectorVertex& Output_vector);// {Tri->incident_vertices(v, back_inserter(Output_vector));} RTriangulation& Triangulation (void);// {return *Tri;} // bool computed (void) {return computed;} bool is_short ( FiniteFacetsIterator f_it ); inline bool is_internal ( FiniteFacetsIterator &facet );// long newListeEdges ( Real** Coordonnes ); //Genere la liste des segments de Voronoi long newListeShortEdges ( Real** Coordonnes ); //Genere la version tronquee (partie interieure) du graph de Voronoi long newListeShortEdges2 ( Real** Coordonnes ); long New_liste_adjacent_edges ( VertexHandle vertex0, Real** Coordonnes ); }; template class PeriodicTesselation : public Tesselation { public: DECLARE_TESSELATION_TYPES(Tesselation) using Tesselation::Tri; using Tesselation::vertexHandles; using Tesselation::maxId; using Tesselation::redirected; ///Insert a sphere, which can be a duplicate one from the base period if duplicateOfId>=0 VertexHandle insert(Real x, Real y, Real z, Real rad, unsigned int id, bool isFictious = false, int duplicateOfId=-1); ///Fill a vector with vertexHandles[i] = handle of vertex with id=i for fast access, contains only spheres from the base period bool redirect (void); }; } // namespace CGT #include "Tesselation.ipp" //Explicit instanciation typedef CGT::_Tesselation SimpleTesselation; trunk-2018.02b/lib/triangulation/Tesselation.ipp000066400000000000000000001517421324306050200216320ustar00rootroot00000000000000/************************************************************************* * Copyright (C) 2006 by Bruno Chareyre * * bruno.chareyre@hmg.inpg.fr * * * * This program is free software; it is licensed under the terms of the * * GNU General Public License v2 or later. See file LICENSE for details. * *************************************************************************/ //FIXME: handle that a better way #define MAX_ID 200000 namespace CGT { using std::cerr; using std::cout; using std::endl; using std::max; using std::vector; using std::ifstream; template _Tesselation::_Tesselation ( void ) { Tri = new RTriangulation; Tes = Tri; computed=false; maxId = -1; TotalFiniteVoronoiVolume=0; area=0; TotalInternalVoronoiPorosity=0; TotalInternalVoronoiVolume=0; redirected = false; //FIXME : find a better way to avoid segfault when insert() is used before resizing this vector vertexHandles.resize(MAX_ID+1,NULL); } template _Tesselation::_Tesselation ( RTriangulation &T ) : Tri ( &T ), Tes ( &T ), computed ( false ) { std::cout << "Tesselation(RTriangulation &T)" << std::endl; compute(); } template _Tesselation::~_Tesselation ( void ) { if ( Tri ) Tri->~RTriangulation(); } template void _Tesselation::Clear ( void ) { Tri->clear(); redirected = false; vertexHandles.clear(); vertexHandles.resize(MAX_ID+1,NULL); maxId=0; } template typename _Tesselation::VertexHandle _Tesselation::insert ( Real x, Real y, Real z, Real rad, unsigned int id, bool isFictious ) { VertexHandle Vh; Vh = Tri->insert(Sphere(Point(x,y,z),pow(rad,2))); if ( Vh!=NULL ) { Vh->info().setId(id); Vh->info().isFictious = isFictious; assert (vertexHandles.size()>id); vertexHandles[id] = Vh; /*if ( !isFictious ) */maxId = std::max ( maxId, (int) id ); } else cout << id << " : Vh==NULL!!"<< " id=" << id << " Point=" << Point ( x,y,z ) << " rad=" << rad << endl; return Vh; } template typename _Tesselation::VertexHandle _Tesselation::move ( Real x, Real y, Real z, Real rad, unsigned int id ) { bool fictious = vertexHandles[id]->info().isFictious; VertexHandle Vh; Vh = Tri->move_point ( vertexHandles[id], Sphere ( Point ( x,y,z ),pow ( rad,2 ) ) ); if ( Vh!=NULL ) { vertexHandles[id] = Vh; Vh->info().setId(id); Vh->info().isFictious = fictious; } else cerr << "Vh==NULL" << " id=" << id << " Point=" << Point ( x,y,z ) << " rad=" << rad << endl; return Vh; } template bool _Tesselation::redirect ( void ) { if ( !redirected ) { //Set size of the redirection vector if ( (unsigned int)maxId+1 != vertexHandles.size() ) vertexHandles.resize ( maxId+1,NULL ); maxId = 0; FiniteVerticesIterator verticesEnd = Tri->finite_vertices_end (); for ( FiniteVerticesIterator vIt = Tri->finite_vertices_begin (); vIt != verticesEnd; vIt++ ) { vertexHandles[vIt->info().id()]= vIt; maxId = max(maxId, (int) vIt->info().id()); } if ( (unsigned int)maxId+1 != vertexHandles.size() ) vertexHandles.resize ( maxId+1 ); redirected = true; } else return false; return true; } template bool _Tesselation::remove ( unsigned int id ) { redirect(); Tri->remove ( vertexHandles[id] ); return true; } template void _Tesselation::voisins ( VertexHandle v, VectorVertex& Output_vector ) { Tri->incident_vertices ( v, back_inserter ( Output_vector ) ); } template typename _Tesselation::RTriangulation & _Tesselation::Triangulation ( void ) { return *Tri; } template Real _Tesselation::Volume ( FiniteCellsIterator cell ) { return ( Tetrahedron ( cell->vertex ( 0 )->point().point(), cell->vertex ( 1 )->point().point(), cell->vertex ( 2 )->point().point(), cell->vertex ( 3 )->point().point() ) ).volume(); } template Plane _Tesselation::Dual ( VertexHandle S1, VertexHandle S2 ) { Segment seg ( S1->point(), S2->point() ); Real r = 0.5* ( 1.0 + ( ( S1->point() ).weight() * ( S1->point() ).weight() - ( S2->point() ).weight() * ( S2->point() ).weight() ) /seg.squared_length() ); return Plan ( S1->point() + seg.to_vector() *r, seg.to_vector() ); } template Point _Tesselation::Dual ( const CellHandle &cell ) { return cell->info(); } template void _Tesselation::compute () { if (!redirected) redirect(); FiniteCellsIterator cellEnd = Tri->finite_cells_end(); for ( FiniteCellsIterator cell = Tri->finite_cells_begin(); cell != cellEnd; cell++ ) cell->info().setPoint(circumCenter(cell)); // { // // const Sphere& S0 = cell->vertex ( 0 )->point(); // const Sphere& S1 = cell->vertex ( 1 )->point(); // const Sphere& S2 = cell->vertex ( 2 )->point(); // const Sphere& S3 = cell->vertex ( 3 )->point(); // Real x,y,z; // CGAL::weighted_circumcenterC3 ( // S0.point().x(), S0.point().y(), S0.point().z(), S0.weight(), // S1.point().x(), S1.point().y(), S1.point().z(), S1.weight(), // S2.point().x(), S2.point().y(), S2.point().z(), S2.weight(), // S3.point().x(), S3.point().y(), S3.point().z(), S3.weight(), // x, y, z ); // cell->info().setPoint(Point(x,y,z)); // } computed = true; } template Point _Tesselation::circumCenter (const Sphere& S0, const Sphere& S1, const Sphere& S2, const Sphere& S3) { Real x,y,z; CGAL::weighted_circumcenterC3 ( S0.point().x(), S0.point().y(), S0.point().z(), S0.weight(), S1.point().x(), S1.point().y(), S1.point().z(), S1.weight(), S2.point().x(), S2.point().y(), S2.point().z(), S2.weight(), S3.point().x(), S3.point().y(), S3.point().z(), S3.weight(), x, y, z ); return Point(x,y,z); } //construct the circumCenter of a facet vs. an external sphere of weight wExt (wExt=alpha, typically) template Point _Tesselation::circumCenter (const CellHandle& cell, const short facet, const double wExt, bool& violate, Sphere& SAlpha, CVector& normal) { const Sphere& S0 = cell->vertex ( facetVertices[facet][0] )->point(); const Sphere& S1 = cell->vertex ( facetVertices[facet][1] )->point(); const Sphere& S2 = cell->vertex ( facetVertices[facet][2] )->point(); const Sphere& Sin = cell->vertex (facet)->point(); CVector surface = 0.5*cross_product ( S0.point()-S1.point(), S0.point()-S2.point() ); //check if the surface vector is inward or outward double dotP = surface* ( S0.point()-Sin.point() ); if ( dotP<0 ) surface=-surface; double area = sqrt ( surface.squared_length() ); normal = surface/area; //unit normal // p1 = setCircumCenter(cell1);//starting point of the polygon Point vv=setCircumCenter(cell); double h1 = ( S0.point()-vv ) *normal; //orthogonal distance from Voronoi vertex to the plane in which the spheres lie, call the intersection V Point p2 = vv+ h1*normal; double sqR = ( p2-S0.point() ).squared_length(); //squared distance between V and the center of sphere 0 double temp = wExt + S0.weight() -sqR; Point OAlpha = p2+sqrt(temp)*normal;//center of the alpha sphere SAlpha=Sphere(OAlpha,wExt); p2=circumCenter(SAlpha,S0,S1,S2); violate = ((p2-vv)*normal<0); // cerr<<"circumCenter(0) "< Point _Tesselation::circumCenter (const CellHandle& cell, const short facet, const Sphere& sExt, bool& violate) { const Sphere& S0 = cell->vertex ( facetVertices[facet][0] )->point(); const Sphere& S1 = cell->vertex ( facetVertices[facet][1] )->point(); const Sphere& S2 = cell->vertex ( facetVertices[facet][2] )->point(); const Sphere& Sin = cell->vertex (facet)->point(); CVector surface = 0.5*cross_product ( S0.point()-S1.point(), S0.point()-S2.point() ); //check if the surface vector is inward or outward double dotP = surface* ( S0.point()-Sin.point() ); if ( dotP<0 ) surface=-surface; // double area = sqrt ( surface.squared_length() ); // CVector normal = surface/area; //unit normal // p1 = setCircumCenter(cell1);//starting point of the polygon Point vv=setCircumCenter(cell); Point p2=circumCenter(S0,S1,S2,sExt); violate = ((p2-vv)*(S0.point()-Sin.point())<0); return p2; } template Point _Tesselation::circumCenter (const CellHandle& cell) { const Sphere& S0 = cell->vertex ( 0 )->point(); const Sphere& S1 = cell->vertex ( 1 )->point(); const Sphere& S2 = cell->vertex ( 2 )->point(); const Sphere& S3 = cell->vertex ( 3 )->point(); return circumCenter (S0, S1, S2, S3); } template Point _Tesselation::setCircumCenter (const CellHandle& cell,bool force) { if (force or cell->info()==CGAL::ORIGIN) cell->info().setPoint(circumCenter(cell)); return (Point) cell->info(); } template std::vector _Tesselation::getAlphaVertices(double alpha) { cerr<<"Warning: this is extremely slow - only for experiments"< alphaVertices; as.get_alpha_shape_vertices(std::back_inserter(alphaVertices), AlphaShape::REGULAR); std::vector res; for (auto v=alphaVertices.begin(); v!=alphaVertices.end(); v++) res.push_back((*v)->info().id()); return res; } template void _Tesselation::testAlphaShape(double alpha) { // if (not computed) compute(); RTriangulation temp(*Tri); AlphaShape as (temp); if (!alpha) alpha=as.find_alpha_solid(); as.set_alpha(alpha); cerr << "Alpha shape computed. alpha_solid=" < cells,cells2,cells3,cells4; std::list facets,facets2,facets3; std::list alphaVertices; std::list normals; std::list normals2; std::list edges0,edges1,edges2,edges3; as.get_alpha_shape_cells(std::back_inserter(cells),AlphaShape::REGULAR); as.get_alpha_shape_cells(std::back_inserter(cells2), AlphaShape::EXTERIOR); as.get_alpha_shape_cells(std::back_inserter(cells3),AlphaShape::INTERIOR); as.get_alpha_shape_cells(std::back_inserter(cells4),AlphaShape::SINGULAR); as.get_alpha_shape_facets(std::back_inserter(facets), AlphaShape::REGULAR); as.get_alpha_shape_vertices(std::back_inserter(alphaVertices), AlphaShape::REGULAR); as.get_alpha_shape_edges(std::back_inserter(edges0), AlphaShape::INTERIOR); as.get_alpha_shape_edges(std::back_inserter(edges1), AlphaShape::REGULAR); as.get_alpha_shape_edges(std::back_inserter(edges2), AlphaShape::SINGULAR); as.get_alpha_shape_edges(std::back_inserter(edges3), AlphaShape::EXTERIOR); int finitEdges=0; for ( FiniteEdgesIterator ed_it=Tri->finite_edges_begin(); ed_it!=Tri->finite_edges_end();ed_it++ ) ++finitEdges; std::cerr<< "num regular cells "<< cells.size() <<" vs. "<info().id()<first->vertex(e->second)->info().id()<<" "<first->vertex(e->third)->info().id() <<" "<< alphaVoronoiFaceArea(*e,as,*Tri) <info().setPoint(circumCenter(*c)); // std::cerr<< "alpha cell:"<<(Point) (*c)->info()<second;//index of the facet within cell defined by f->first // std::cerr << f->first->vertex(facetVertices[idx][0])->info().id() // <<" "<< f->first->vertex(facetVertices[idx][1])->info().id() // <<" "<< f->first->vertex(facetVertices[idx][2])->info().id() << std::endl; CVector surface = 0.5*cross_product(f->first->vertex(facetVertices[idx][0])->point().point()-f->first->vertex(facetVertices[idx][1])->point().point(), f->first->vertex(facetVertices[idx][0])->point().point()-f->first->vertex(facetVertices[idx][2])->point().point()); //largest sphere double maxWeight = std::max(f->first->vertex(facetVertices[idx][0])->point().weight(),max(f->first->vertex(facetVertices[idx][1])->point().weight(), f->first->vertex(facetVertices[idx][2])->point().weight())); Point pp; Point vv; if (as.classify(f->first)==AlphaShape::INTERIOR) { pp= f->first->vertex(f->second)->point().point(); if (not computed) f->first->info().setPoint(circumCenter(f->first)); // std::cerr<< "alpha cell:"<<(Point) f->first->info()<first->info(); // std::cerr << "vv="<first->info().setPoint(Tri->dual(f->first)); // std::cerr << "found as.classify(f->first)==Alpha_shape_3::INTERIOR"<first->neighbor(f->second))!=AlphaShape::INTERIOR) std::cerr<<"_____________BIG PROB. HERE ___________"<first->neighbor(f->second)->vertex(Tri->mirror_index(f->first,f->second))->point().point(); if (not computed) f->first->neighbor(f->second)->info().setPoint(circumCenter(f->first->neighbor(f->second))); // std::cerr<< "alpha cell:"<<(Point) f->first->neighbor(f->second)->info()<first->neighbor(f->second)->info(); // std::cerr << "vv="<first->neighbor(f->second)->info().setPoint(Tri->dual(f->first->neighbor(f->second))); // std::cerr << "not an Alpha_shape_3::INTERIOR"<first->vertex(facetVertices[f->second][0])->point().point()-pp); if (dotP<0) surface=-surface; double area = sqrt(surface.squared_length()); CVector normal = surface/area; //unit normal // std::cerr <<"dotP="<first->vertex(facetVertices[idx][0])->point().point()-vv)*surface/area; //orthogonal distance from Voronoi vertex to the plane in which the spheres lie, call the intersection V Point V = vv + h1*normal; double distLiu = sqrt((V-Point(0,0,0)).squared_length()); double sqR = (V-f->first->vertex(facetVertices[idx][0])->point().point()).squared_length(); //squared distance between V and the center of sphere 0 double temp = alpha + f->first->vertex(facetVertices[idx][0])->point().weight() -sqR; if (temp<0) {temp=0; std::cerr<<"NEGATIVE TEMP!"<maxWeight) temp=maxWeight; //if alpha vertex is too far, crop double h2 = sqrt(temp);// this is now the distance from Voronoi vertex to "alpha" vertex (after cropping if needed) V = V+h2*normal; std::cerr <<"dist alpha center:"< void _Tesselation::setAlphaFaces(std::vector& faces, double alpha) { RTriangulation temp(*Tri); AlphaShape as (temp); if (!alpha) { as.set_alpha(as.find_alpha_solid()); /*cerr << "Alpha shape computed. alpha_solid=" < facets; std::list normals; as.get_alpha_shape_facets(std::back_inserter(facets), AlphaShape::REGULAR);// get the list of "contour" facets faces.resize(facets.size()); int k=0; for (auto f=facets.begin(); f!=facets.end();f++){ int idx = f->second;//index of the facet within cell defined by f->first CVector normal = 0.5*cross_product(f->first->vertex(facetVertices[idx][0])->point().point()-f->first->vertex(facetVertices[idx][1])->point().point(), f->first->vertex(facetVertices[idx][0])->point().point()-f->first->vertex(facetVertices[idx][2])->point().point()); Point pp; if (as.classify(f->first)==AlphaShape::INTERIOR) pp= f->first->vertex(f->second)->point().point(); else pp= f->first->neighbor(f->second)->vertex(Tri->mirror_index(f->first,f->second))->point().point(); //check if the normal vector is inward or outward double dotP = normal*(f->first->vertex(facetVertices[f->second][0])->point().point()-pp); if (dotP<0) normal=-normal; // set the face in the global list for (int ii=0; ii<3;ii++) faces[k].ids[ii]= f->first->vertex(facetVertices[idx][ii])->info().id(); faces[k++].normal = normal; } } template std::vector _Tesselation::getExtendedAlphaGraph (double alpha, double shrinkedAlpha, bool fixedAlpha) { std::vector vSegments; RTriangulation temp ( *Tri ); AlphaShape as ( temp ); double minAlpha=as.find_alpha_solid(); if ( !alpha ) as.set_alpha ( minAlpha ); else { as.set_alpha ( alpha ); if (alpha facets; as.get_alpha_shape_facets(std::back_inserter(facets), AlphaShape::REGULAR); for ( auto fp=facets.begin(); fp!=facets.end(); fp++ ) { Facet f = *fp; if (as.classify(f.first)!=AlphaShape::INTERIOR) f=as.mirror_facet(f); Sphere sph; bool b; CVector n; //FIXME: suboptimal, we are calculating/returning a point for no good /*Point p =*/ circumCenter(f.first,f.second,alpha,b,sph,n); VertexHandle Vh = Tri->insert(Sphere(sph.point(), shrinkedAlpha)); if ( Vh!=NULL ) Vh->info().isFictious = true; else cerr << " : __Vh==NULL__ :(" << endl;} } else {//insert one sphere per exterior/infinite cell, the radius is derived from the alpha value of the corresponding cell or 4*alpha for infinite cells double alphaRad=sqrt(alpha); double deltaAlpha=alphaRad-sqrt(shrinkedAlpha); std::list facets;// the infinite ones as.get_alpha_shape_facets(std::back_inserter(facets), AlphaShape::REGULAR); for ( auto fp=facets.begin(); fp!=facets.end(); fp++ ) { Facet f = *fp; if (as.classify(f.first)!=AlphaShape::INTERIOR) f=as.mirror_facet(f); const CellHandle& outerCell = f.first->neighbor(f.second); if (as.is_infinite(outerCell)) { Sphere sph; bool b; CVector n; /*Point p =*/ circumCenter(f.first,f.second,alpha*4,b,sph,n); VertexHandle Vh = Tri->insert(Sphere(sph.point(), pow(2*alphaRad-deltaAlpha,2))); if ( Vh!=NULL ) Vh->info().isFictious = true; else cerr << " : __Vh==NULL__ :(" << endl; } else { if (!outerCell->info().isFictious) { outerCell->info().isFictious=true; Point p = setCircumCenter(outerCell); double weight = (p-outerCell->vertex(0)->point().point()).squared_length() - outerCell->vertex(0)->point().weight(); VertexHandle Vh = Tri->insert(Sphere(p, pow(sqrt(weight)-deltaAlpha,2) )); if ( Vh!=NULL ) Vh->info().isFictious = true; else cerr << " : __Vh==NULL__ :(" << endl; } } } } for (auto e = Tri->finite_facets_begin(); e != Tri->finite_facets_end(); e++){ short countFictious=e->first->vertex(facetVertices[e->second][0])->info().isFictious + e->first->vertex(facetVertices[e->second][1])->info().isFictious + e->first->vertex(facetVertices[e->second][2])->info().isFictious; if (countFictious==1){ vSegments.push_back(makeVector3r(setCircumCenter(e->first))); vSegments.push_back(makeVector3r(setCircumCenter(e->first->neighbor(e->second)))); } } return vSegments; } template void _Tesselation::setExtendedAlphaCaps ( std::vector& faces, double alpha, double shrinkedAlpha, bool fixedAlpha ) { std::vector areas; //initialize area vectors in a list accessed via ids (hence the size), later refactored into a shorter list in "faces" areas.resize ( maxId+1,CVector ( 0,0,0 ) );// from 0 to maxId RTriangulation temp ( *Tri ); AlphaShape as ( temp ); double minAlpha=as.find_alpha_solid(); if ( !alpha ) as.set_alpha ( minAlpha ); else { as.set_alpha ( alpha ); if (alpha facets; as.get_alpha_shape_facets(std::back_inserter(facets), AlphaShape::REGULAR); for ( auto fp=facets.begin(); fp!=facets.end(); fp++ ) { Facet f = *fp; if (as.classify(f.first)!=AlphaShape::INTERIOR) f=as.mirror_facet(f); Sphere sph; bool b; CVector n; circumCenter(f.first,f.second,alpha,b,sph,n); VertexHandle Vh = Tri->insert(Sphere(sph.point(), shrinkedAlpha)); if ( Vh!=NULL ) Vh->info().isFictious = true; else cerr << " : __Vh==NULL__ :(" << endl;} } else {//insert one sphere per exterior/infinite cell, the radius is derived from the alpha value of the corresponding cell or 4*alpha for infinite cells double alphaRad=sqrt(alpha); double deltaAlpha=alphaRad-sqrt(shrinkedAlpha); std::list facets; as.get_alpha_shape_facets(std::back_inserter(facets), AlphaShape::REGULAR); for ( auto fp=facets.begin(); fp!=facets.end(); fp++ ) { Facet f = *fp; if (as.classify(f.first)!=AlphaShape::INTERIOR) f=as.mirror_facet(f); const CellHandle& outerCell = f.first->neighbor(f.second); if (as.is_infinite(outerCell)) { Sphere sph; bool b; CVector n; /*Point p =*/ circumCenter(f.first,f.second,alpha*4,b,sph,n); VertexHandle Vh = Tri->insert(Sphere(sph.point(), pow(2*alphaRad-deltaAlpha,2))); if ( Vh!=NULL ) Vh->info().isFictious = true; else cerr << " : __Vh==NULL__ :(" << endl; } else if (!outerCell->info().isFictious) { outerCell->info().isFictious=true; Point p = setCircumCenter(outerCell); double weight = (p-outerCell->vertex(0)->point().point()).squared_length() - outerCell->vertex(0)->point().weight(); VertexHandle Vh = Tri->insert(Sphere(p, pow(sqrt(weight)-deltaAlpha,2) )); if ( Vh!=NULL ) Vh->info().isFictious = true; else cerr << " : __Vh==NULL__ :(" << endl; } } } for (auto e = Tri->finite_facets_begin(); e != Tri->finite_facets_end(); e++){ short countFictious=e->first->vertex(facetVertices[e->second][0])->info().isFictious + e->first->vertex(facetVertices[e->second][1])->info().isFictious + e->first->vertex(facetVertices[e->second][2])->info().isFictious; if (countFictious==1){ short ftx=0; while (!e->first->vertex(facetVertices[e->second][ftx])->info().isFictious) ftx++; const Point& fictV = e->first->vertex(facetVertices[e->second][ftx])->point().point(); const int& id1 = e->first->vertex(facetVertices[e->second][ftx>0? ftx-1:2])->info().id(); const int& id2 = e->first->vertex(facetVertices[e->second][ftx<2? ftx+1:0])->info().id(); const Point& p1 = e->first->vertex(facetVertices[e->second][ftx>0? ftx-1:2])->point().point(); const Point& p2 = e->first->vertex(facetVertices[e->second][ftx<2? ftx+1:0])->point().point(); CVector u = setCircumCenter(e->first)-setCircumCenter(e->first->neighbor(e->second)); CVector area = 0.5*cross_product(u,p1-CGAL::ORIGIN); if (u*cross_product(p2-p1,fictV-p1)<0) area=-area;//make sure we are always circulating clockwise areas[id1]=area+areas[id1]; areas[id2]=areas[id2]-area; } } faces.clear(); faces.reserve(1.5*6*pow(as.number_of_vertices(),0.6666)); for (short id=0; id<=maxId; id++) { if (areas[id]!=CVector(0,0,0)) { AlphaCap cap; cap.id=id; cap.normal=areas[id]; // cerr << "cap: "< Segment _Tesselation::Dual ( FiniteFacetsIterator &f_it ) { return Segment ( f_it->first->info(), ( f_it->first->neighbor ( f_it->second ) )->info() ); } template double _Tesselation::computeVFacetArea ( FiniteEdgesIterator ed_it ) { CellCirculator cell0 = Tri->incident_cells ( *ed_it ); CellCirculator cell2 = cell0; if ( Tri->is_infinite ( cell2 ) ){ ++cell2; while ( Tri->is_infinite ( cell2 ) && cell2!=cell0 ) ++cell2; if ( cell2==cell0 ) return 0; } cell0=cell2++; CellCirculator cell1=cell2++; Real area = 0; while ( cell2!=cell0 ){ area+= sqrt(std::abs (( Triangle ( cell0->info(), cell1->info(), cell2->info() ) ).squared_area())) ; ++cell1; ++cell2; } return area; } template CVector _Tesselation::alphaVoronoiFaceArea (const Edge& ed_it, const AlphaShape& as, const RTriangulation& Tro) { //Overall, we calculate the area vector of the polygonal Voronoi face between two spheres, this is done by integrating x×dx double alpha = as.get_alpha(); CellCirculator cell0,cell1,cell2; cell0 = as.incident_cells ( ed_it ); cell2 = cell0; while ( as.classify(cell2)!=AlphaShape::INTERIOR ) {++cell2; if (cell2==cell0) cerr<<"infinite loop on an edge, probably singular"<vertex ( ed_it.second )->point().point();//one sphere pB = ( ed_it.first )->vertex ( ed_it.third )->point().point();//another sphere CVector AB = pB-pA; bool interior1 = true;//keep track of last cell's status bool interior2; do { ++cell2; interior2 = (as.classify(cell2)==AlphaShape::INTERIOR); if (interior2) {//easy setCircumCenter(cell2); p2 = cell2->info(); branch=p2-p1; branchArea = branchArea + cross_product(branch, p1-CGAL::ORIGIN); cerr<<"branchArea(1) "<neighbor(idx)!=cell2) {idx++; if(idx>3) cerr<<"HUUUUUUUH";} // ... then its surface vector CVector surface = 0.5*cross_product(baseCell->vertex(facetVertices[idx][0])->point().point()-baseCell->vertex(facetVertices[idx][1])->point().point(), baseCell->vertex(facetVertices[idx][0])->point().point()-baseCell->vertex(facetVertices[idx][2])->point().point()); //largest sphere double maxWeight = std::max(baseCell->vertex(facetVertices[idx][0])->point().weight(),max(baseCell->vertex(facetVertices[idx][1])->point().weight(), baseCell->vertex(facetVertices[idx][2])->point().weight())); //check if the surface vector is inward or outward double dotP = surface*(baseCell->vertex(facetVertices[idx][0])->point().point()-baseCell->vertex(idx)->point().point()); if (dotP<0) surface=-surface; double area = sqrt(surface.squared_length()); normal = surface/area; //unit normal double h1 = (baseCell->vertex(facetVertices[idx][0])->point().point()-vv)*normal; //orthogonal distance from Voronoi vertex to the plane in which the spheres lie, call the intersection V p2 = vv + h1*normal; double sqR = (p2-baseCell->vertex(facetVertices[idx][0])->point().point()).squared_length(); //squared distance between V and the center of sphere 0 double temp = alpha + baseCell->vertex(facetVertices[idx][0])->point().weight() -sqR; if (temp<0) {temp=0; std::cerr<<"NEGATIVE TEMP!"<maxWeight) temp=maxWeight; //if alpha vertex is too far, crop double h2 = sqrt(temp);// this is now the distance from Voronoi vertex to "alpha" vertex (after cropping if needed) p2 = p2+h2*normal; bool coplanar=false; if (!(interior1 or interior2)) { //VERSION 1,intersection of orthogonal planes from two branches CVector tangent = cross_product(AB,p1-vv0); tangent = tangent/sqrt(tangent.squared_length());//this is orthogonal to the _previous_ branch segment of the polygonal contour double dotP = tangent*normal; coplanar=(abs(dotP)<1e-2); CVector p1mp2=p1-p2; if (!coplanar) { //make sure the construction is not singular (no intermediate vertex) if ((p1mp2*(p1-vv0)>0) and (p1mp2*normal<0)) { p12=p1-(p1mp2)*normal/dotP*tangent; // if (((p12-p1)*p1mp2)*(p12-p2)*p1mp2)<0) {//make sure the construction is not singular (no intermediate vertex) cerr<<"p12="< CVector _Tesselation::alphaVoronoiPartialCapArea ( const Edge& ed_it, const AlphaShape& as, std::vector& vSegments ) { //Overall, a partial area vector based on only the outer part of a polygonal Voronoi face (see alphaVoronoiFaceArea), looping on REGULAR edges incident to a boundary sphere and using this function for each of them should give the contour integral of x×dx for the polygonal cap of the sphere. double alpha = as.get_alpha(); CellCirculator cell0,cell1,cell2,cell3; cell0 = as.incident_cells ( ed_it ); cell1 = cell2 = cell0; bool violate1, violate2; ++cell2; while ( as.classify(cell1) !=AlphaShape::INTERIOR or as.classify(cell2) ==AlphaShape::INTERIOR){//we want interior->exterior starting sequence ++cell1; if ( cell0==cell2++ ) cerr<<"infinite loop on an edge, probably singular"<1, cell1 is now equal to cell2 cell3=cell2; cell3++;//we have now cell0 < cell1=cell2 < cell3 while ( as.classify ( cell3 ) !=AlphaShape::INTERIOR){//we want an exterior->interior end-point, could be that cell3=cell0 and that's ok ++cell2; ++cell3; if ( cell2==cell0 ) cerr<<"infinite loop on an edge, probably singular(2)"<vertex ( ed_it.second )->point();//one sphere const Sphere& sB = ( ed_it.first )->vertex ( ed_it.third )->point();//another sphere const Point& pA = sA.point();//one sphere const Point& pB = sB.point();//another sphere //pA,pB are the spheres of the edge, (p1,p2) are iterating over the vertices of the vornonoi face, p12 is an intermediate point for convex parts Point p1,p2,p12,vv0,vv3; vv0=setCircumCenter(cell0);//start point vv3=setCircumCenter(cell3);//end point // p1 = setCircumCenter ( cell1 ); //starting point of the polygon CVector branch, normal1, normal2; CVector branchArea(0,0,0); CVector AB = pB-pA; Sphere SAlpha1, SAlpha2; Sphere SAlphaSmall1, SAlphaSmall2; Sphere sC1,sC2;//the 3rd spheres of each facet, not part of the edge bool first=true; int idx1,idx2; do { //this do-while will run twice and it should return the second time, we work with the "exit" and "enter" regions of a polyline wrt. the alpha contour CellCirculator baseCell= first ? cell0 : cell3;//it plays the role of the internal cell CellCirculator outerCell= first ? cell1 : cell2;//it plays the role of the external cell // finding the facet from baseCell to outerCell ... int& idx=first?idx1:idx2; idx=0; while ( baseCell->neighbor ( idx ) !=outerCell ) { idx++; if ( idx>3 ) cerr<<"HUUUUUUUH";} short thirdSphere=0; while ( baseCell->vertex(facetVertices[idx][thirdSphere]) == (ed_it.first)->vertex(ed_it.second) or baseCell->vertex(facetVertices[idx][thirdSphere]) == (ed_it.first)->vertex(ed_it.third)) {thirdSphere++; if ( thirdSphere>3 ) cerr<<"HUAAAUUH";} Sphere& sC=first ? sC1:sC2; sC = baseCell->vertex(facetVertices[idx][thirdSphere])->point();//forming the regular facet with sA and sB // ... then its surface vector //FIXME: for many cases this cross product is not needed if we set/use voronoi centers of alpha cells Sphere& SAlpha = first ? SAlpha1:SAlpha2; Point& p = first ? p1 : p2; bool& violate= first ? violate1 : violate2; Sphere& SAlphaSmall = first? SAlphaSmall1:SAlphaSmall2; CVector& normal = first? normal1:normal2; p = circumCenter(baseCell,idx,alpha,violate,SAlpha,normal); SAlphaSmall = Sphere(SAlpha.point(),pow(sqrt(SAlpha.weight())-0.5,2)); p = circumCenter(baseCell,idx,SAlphaSmall,violate); first=!first; } while ( !first ); first=true; do { CellCirculator baseCell= first ? cell0 : cell3;//it plays the role of the internal cell CellCirculator outerCell= first ? cell1 : cell2;//it plays the role of the external cell // finding the facet from baseCell to outerCell ... int& idx=first?idx1:idx2; Sphere& sC=first ? sC1:sC2; Sphere& SAlpha = first ? SAlpha1:SAlpha2; Point& p = first ? p1 : p2; bool& violate= first ? violate1 : violate2; Sphere& SAlphaSmall = first? SAlphaSmall1:SAlphaSmall2; Sphere& SAlphaSmallInv = first? SAlphaSmall2:SAlphaSmall1; std::vector pi; /*pi.reserve(6);*///"interior" points // cerr<<"violate "<vertex(idx)->point(),sA,sB,SAlphaSmall); if ((ppp-pppInv)*(sA.point()-sC.point())<0) infCenter=true; p = ppp; if ((p-ppp)*(sA.point()-sC.point())<0) { Point ppp2 = circumCenter(baseCell->vertex(idx)->point(),sB,sC,SAlphaSmall); Point ppp3 = circumCenter(baseCell->vertex(idx)->point(),sA,sC,SAlphaSmall); if (infCenter) { Point ppp2B = circumCenter(baseCell->vertex(idx)->point(),sB,SAlphaSmallInv,SAlphaSmall); Point ppp3B = circumCenter(baseCell->vertex(idx)->point(),sA,SAlphaSmallInv,SAlphaSmall); Vector3r u = makeVector3r(ppp2)-makeVector3r(ppp2B); Vector3r v = makeVector3r(ppp3)-makeVector3r(ppp3B); Vector3r w = makeVector3r(ppp2)-makeVector3r(ppp3); for (int j=0;j<8;j++) { vSegments.push_back(makeVector3r(ppp2B)+j*(2/23.)*u); vSegments.push_back(makeVector3r(ppp2B)+(2.*j+1)*(1/23.)*u); vSegments.push_back(makeVector3r(ppp3B)+j*(2/23.)*v); vSegments.push_back(makeVector3r(ppp3B)+(2.*j+1)*(1/23.)*v); vSegments.push_back(makeVector3r(ppp3)+j*(2/23.)*w); vSegments.push_back(makeVector3r(ppp3)+(2.*j+1)*(1/23.)*w); } } else { Vector3r u = makeVector3r(ppp2)-makeVector3r(ppp); Vector3r v = makeVector3r(ppp3)-makeVector3r(ppp); for (int j=0;j<3;j++) { vSegments.push_back(makeVector3r(ppp)+j*(2/5.)*u); vSegments.push_back(makeVector3r(ppp)+(2.*j+1)*(1/5.)*u); vSegments.push_back(makeVector3r(ppp)+j*(2/5.)*v); vSegments.push_back(makeVector3r(ppp)+(2.*j+1)*(1/5.)*v); } } // vSegments.push_back(makeVector3r(ppp)); vSegments.push_back(makeVector3r(ppp2)); // vSegments.push_back(makeVector3r(ppp)); vSegments.push_back(makeVector3r(ppp3)); } else {} // p = circumCenter(sC,sA,sB,SAlpha); for (short k=0;k<3;k++) { const int fct = facetVertices[idx][k]; const int* f = facetVertices[fct]; const CellHandle& ncell = baseCell->neighbor(fct); short nfacet = as.mirror_index(baseCell,fct); Point pp; if (0 /*as.is_infinite(ncell)*/) { pp = circumCenter(baseCell->vertex(f[0])->point(),baseCell->vertex(f[1])->point(),baseCell->vertex(f[2])->point(),SAlpha); cerr <<"INFINITE CELL"<0 );//not sure it works bool clockWise = ( cross_product(p1-vv0,p2-p1)*AB>0 ); tangent = tangent/sqrt ( tangent.squared_length() ); //this is orthogonal to the _previous_ branch segment of the polygonal contour double dotP = tangent*normal1; branchArea = branchArea+cross_product ( p2-p1, p1-CGAL::ORIGIN ); //check the orientation (we need to accumulate along the polyline with a given direction) if ( clockWise ) return 0.5*branchArea; else return -0.5*branchArea; // } cerr << "WE SHOULD NEVER REACH HERE" < CVector _Tesselation::alphaVoronoiPartialCapArea ( Facet facet, const AlphaShape& as, double shrinkedAlpha, std::vector& vSegments ) { //Overall, a partial area vector based on only the outer part of a polygonal Voronoi face (see alphaVoronoiFaceArea), looping on REGULAR edges incident to a boundary sphere and using this function for each of them should give the contour integral of x×dx for the polygonal cap of the sphere. double alpha = as.get_alpha(); Facet adjactF [4]; bool violate [4]; Point circumC [4]; Point edgeC [3]; Point mirrorC [3]; Sphere alphaSph [4]; Sphere thirdSph [3]; Sphere mirrorSph [3]; CVector norml [4]; CVector dir [3]; bool internal=true; short externalEdge; if (as.classify(facet.first)!=AlphaShape::INTERIOR) facet=as.mirror_facet(facet); adjactF[0]=facet; Point p = circumCenter(facet.first,facet.second,alpha,violate[0],alphaSph[0],norml[0]); alphaSph[0] = Sphere(alphaSph[0].point(), shrinkedAlpha); circumC[0] = circumCenter(facet.first,facet.second,alphaSph[0],violate[0]); for (int k=0;k<3;k++) { FacetCirculator f; const short& ii= facetVertices[facet.second][k]; const Sphere& sii = facet.first->vertex(ii)->point(); const short& jj= facetVertices[facet.second][k>1?0:(k+1)]; const Sphere& sjj = facet.first->vertex(jj)->point(); const short& kk= facetVertices[facet.second][k>0?(k-1):2]; thirdSph[k]=facet.first->vertex(kk)->point(); // const Sphere& skk = facet.first->vertex(kk)->point(); f = as.incident_facets(facet.first,//cell ii,//i jj,//j facet);//start FacetCirculator f0 = f; do {f++; if (f==f0) cerr <<"PROB PROB1"<first)==AlphaShape::INTERIOR) adjactF[k]=(*f); else adjactF[k]=as.mirror_facet(*f); const Facet& af = adjactF[k]; Point p = circumCenter(af.first,af.second,alpha,violate[k+1],alphaSph[k+1],norml[k+1]); short mirrorVtx = 0; while (af.first->vertex(facetVertices[af.second][mirrorVtx])== facet.first->vertex(jj) or af.first->vertex(facetVertices[af.second][mirrorVtx])== facet.first->vertex(jj)) mirrorVtx++; alphaSph[k+1] = Sphere(alphaSph[k+1].point(),shrinkedAlpha); circumC[k+1] = circumCenter(af.first,af.second,alphaSph[k+1],violate[k+1]); edgeC[k] = circumCenter(alphaSph[0],sii,sjj,alphaSph[k+1]); mirrorSph[k] = af.first->vertex(facetVertices[af.second][mirrorVtx])->point(); mirrorC[k] = circumCenter(alphaSph[0],sii,sjj,mirrorSph[k]); dir[k]=cross_product(sii.point()-sjj.point(),sii.point()-alphaSph[0].point()); dir[k]=((sii.point()-thirdSph[k].point())*dir[k])*dir[k]; if (((edgeC[k]-circumC[0])*dir[k])<0) {internal=false; externalEdge=k;} // cerr<<"edge: "<vertex(ii)->point(); const short& jj= facetVertices[facet.second][k>1?0:(k+1)]; const Sphere& sjj = facet.first->vertex(jj)->point(); const short& kk= facetVertices[facet.second][k>0?(k-1):2]; const Sphere& skk = facet.first->vertex(kk)->point(); if (((mirrorSph[k].point()-skk.point())*dir[k])>0 and ((mirrorC[k]-edgeC[k])*dir[k])<0) { Point p1 = circumCenter(alphaSph[0],sjj,mirrorSph[k],alphaSph[k+1]); Point p2 = circumCenter(alphaSph[0],mirrorSph[k],sii,alphaSph[k+1]); if (((mirrorC[k]-circumC[0])*dir[k])>0) { int NN=3; CVector u=mirrorC[k]-circumC[0]; CVector v=mirrorC[k]-p1; CVector w=mirrorC[k]-p2; for (int j=0;j0) { // cerr<<"TEST1: "<1?0:(k+1)]-p1; CVector v=edgeC[k>0?(k-1):2]-p2; for (int j=0;jvertex(ii)->point(); const short& jj= facetVertices[facet.second][k>1?0:(k+1)]; const Sphere& sjj = facet.first->vertex(jj)->point(); const short& kk= facetVertices[facet.second][k>0?(k-1):2]; const Sphere& skk = facet.first->vertex(kk)->point(); Point p1 = circumCenter(alphaSph[0],sjj,skk,alphaSph[k+1]); Point p2 = circumCenter(alphaSph[0],skk,sii,alphaSph[k+1]); Point& p1b = edgeC[k>1?0:(k+1)]; Point& p2b = edgeC[k>0?(k-1):2]; Point mirror1 = circumCenter(alphaSph[0],sjj,skk,mirrorSph[k]); Point mirror2 = circumCenter(alphaSph[0],sii,skk,mirrorSph[k]); bool ext1 = ((mirror1-p1)*dir[k]<0); bool ext2 = ((mirror2-p2)*dir[k]<0); if (ext1 && ext2) { Point p1c = circumCenter(alphaSph[0],sjj,alphaSph[k+1],mirrorSph[k]); vSegments.push_back(makeVector3r(p1c)); vSegments.push_back(makeVector3r(mirror1)); p1 = mirror1; Point p2c = circumCenter(alphaSph[0],sii,alphaSph[k+1],mirrorSph[k]); vSegments.push_back(makeVector3r(p2c)); vSegments.push_back(makeVector3r(mirror2)); p2 = mirror2; vSegments.push_back(makeVector3r(p2)); vSegments.push_back(makeVector3r(p1)); } else if (ext2 && !ext1) { Point p2c = circumCenter(alphaSph[0],sii,alphaSph[k+1],mirrorSph[k]); Point p2d = circumCenter(alphaSph[0],skk,sii,mirrorSph[k]); vSegments.push_back(makeVector3r(p2c)); vSegments.push_back(makeVector3r(mirror2)); p2 = mirror2; vSegments.push_back(makeVector3r(p2)); vSegments.push_back(makeVector3r(p2d)); } else if (ext1 && !ext2) { Point p1c = circumCenter(alphaSph[0],sjj,alphaSph[k+1],mirrorSph[k]); Point p1d = circumCenter(alphaSph[0],skk,sjj,mirrorSph[k]); vSegments.push_back(makeVector3r(p1c)); vSegments.push_back(makeVector3r(mirror1)); p1 = mirror1; vSegments.push_back(makeVector3r(p1)); vSegments.push_back(makeVector3r(p1d)); } int NN=4; CVector u=p1b-p1; CVector v=p2b-p2; for (int j=0;j ll; // const Sphere& facet.first->vertex(facetVertices[facet.second][k])->point(); for (short k=0; k<3; k++) { iCircumC[k]= circumCenter( facet.first->vertex(facet.second)->point(), facet.first->vertex(facetVertices[facet.second][k])->point(), facet.first->vertex(facetVertices[facet.second][k>1?0:(k+1)])->point(), alphaSph[0]); iEdgeC[k]= circumCenter( alphaSph[0], facet.first->vertex(facetVertices[facet.second][k])->point(), facet.first->vertex(facetVertices[facet.second][k>1?0:(k+1)])->point(), alphaSph[k+1]); } for (short k=0; k<3; k++) { if ((iCircumC[k]-iEdgeC[k])*(iEdgeC[k]-thirdSph[k].point())>0) { ll.push_back(circumCenter( facet.first->vertex(facet.second)->point(), facet.first->vertex(facetVertices[facet.second][k])->point(), alphaSph[0], alphaSph[k+1])); ll.push_back(circumCenter( facet.first->vertex(facet.second)->point(), alphaSph[0], facet.first->vertex(facetVertices[facet.second][k>1?0:(k+1)])->point(), alphaSph[k+1])); } else { ll.push_back(iCircumC[k]); } } if (ll.size()>0) vSegments.push_back(makeVector3r(ll[0])); for (short n =0;n<(ll.size()-1);n++) {vSegments.push_back(makeVector3r(ll[n+1])); vSegments.push_back(makeVector3r(ll[n+1]));} if (ll.size()>0) vSegments.push_back(makeVector3r(ll[0])); } return CVector(0,0,0); } template void _Tesselation::AssignPartialVolume ( FiniteEdgesIterator& ed_it ) { //EdgeIterator ed_it CellCirculator cell0=Tri->incident_cells ( *ed_it ); CellCirculator cell2=cell0; if ( Tri->is_infinite ( cell2 ) ) { ++cell2; while ( Tri->is_infinite ( cell2 ) && cell2!=cell0 ) ++cell2; if ( cell2==cell0 ) return; } cell0=cell2++; CellCirculator cell1=cell2++; bool isFictious1 = ( ed_it->first )->vertex ( ed_it->second )->info().isFictious; bool isFictious2 = ( ed_it->first )->vertex ( ed_it->third )->info().isFictious; Real r; while ( cell2!=cell0 ) { if ( !Tri->is_infinite ( cell1 ) && !Tri->is_infinite ( cell2 ) ) { if ( !isFictious1 ) { r = std::abs ( ( Tetrahedron ( ed_it->first->vertex ( ed_it->second )->point().point(), cell0->info(), cell1->info(), cell2->info() ) ).volume() ); ( ed_it->first )->vertex ( ed_it->second )->info().v() += r; TotalFiniteVoronoiVolume+=r; } if ( !isFictious2 ) { r = std::abs ( ( Tetrahedron ( ed_it->first->vertex ( ed_it->third )->point().point(), cell0->info(), cell1->info(), cell2->info() ) ).volume() ); ed_it->first->vertex ( ed_it->third )->info().v() +=r; TotalFiniteVoronoiVolume+=r; } } ++cell1; ++cell2; } } template void _Tesselation::ResetVCellVolumes ( void ) { for ( VertexIterator vIt = Tri->vertices_begin (); vIt != Tri->vertices_end (); vIt++ ) vIt->info().v() =0; TotalFiniteVoronoiVolume=0; TotalInternalVoronoiPorosity=0; } template void _Tesselation::computeVolumes ( void ) { if ( !computed ) compute(); ResetVCellVolumes(); for ( FiniteEdgesIterator ed_it=Tri->finite_edges_begin(); ed_it!=Tri->finite_edges_end();ed_it++ ) { AssignPartialVolume ( ed_it ); } //FIXME: find a way to compute a volume correctly for spheres of the boarders. } template void _Tesselation::computePorosity ( void ) //WARNING : This function will erase real volumes of cells { computeVolumes(); FiniteVerticesIterator verticesEnd = Tri->finite_vertices_end (); for ( FiniteVerticesIterator vIt = Tri->finite_vertices_begin (); vIt != verticesEnd; vIt++ ) { if ( vIt->info().v() && !vIt->info().isFictious ) { Real r = 4.188790 * std::pow ( ( vIt->point().weight() ),1.5 );// 4/3*PI*R³ = 4.188...*R³ TotalInternalVoronoiPorosity+=r; TotalInternalVoronoiVolume += vIt->info().v(); vIt->info().v() = ( vIt->info().v() - r ) / vIt->info().v(); } } TotalInternalVoronoiPorosity= ( TotalInternalVoronoiVolume-TotalInternalVoronoiPorosity ) /TotalInternalVoronoiVolume; } template bool _Tesselation::is_internal ( FiniteFacetsIterator &facet ) { return ( !Tri->is_infinite ( facet->first ) && !Tri->is_infinite ( facet->first->neighbor ( facet->second ) ) ); } template typename Tesselation::VertexHandle PeriodicTesselation::insert(Real x, Real y, Real z, Real rad, unsigned int id, bool isFictious, int duplicateOfId) { VertexHandle Vh; if (!Tri) cerr<<"!Tri!"<insert(Sphere(Point(x,y,z),pow(rad,2))); if ( Vh!=NULL ) { Vh->info() = id; Vh->info().isFictious = isFictious; if (duplicateOfId<0) { assert (vertexHandles.size()>id); vertexHandles[id] = Vh; maxId = std::max ( maxId, (int) id ); Vh->info().isGhost=0; } else Vh->info().isGhost=1; } else cerr << " : Vh==NULL!!" << " id=" << id << " Point=" << Point ( x,y,z ) << " rad=" << rad<<" fictious="<finite_vertices_end (); for ( FiniteVerticesIterator vIt = Tri->finite_vertices_begin (); vIt != verticesEnd; vIt++ ) { if (vIt->info().isGhost) continue; vertexHandles[vIt->info().id()]= vIt; maxId = max(maxId, (int) vIt->info().id()); } if ( (unsigned int)maxId+1 != vertexHandles.size() ) vertexHandles.resize ( maxId+1 ); redirected = true; } else return false; return true; } } //namespace CGT trunk-2018.02b/lib/triangulation/Timer.cpp000066400000000000000000000020761324306050200204050ustar00rootroot00000000000000#include #include "Timer.h" Real_timer::Real_timer() : T1(0), T2(0), elapsed(0.0), started(0.0), interv(0), running(true) { T1 = clock(); } // Member functions for Real_timer // =========================== void Real_timer::start() { //CGAL_precondition( ! running); //started = get_real_time(); T1=clock(); running = true; ++ interv; } void Real_timer::stop() { //CGAL_precondition( running); //double t = get_real_time(); //elapsed += (t - started); T2 = difftime(clock(), T1); started = 0.0; T1=0; running = false; } void Real_timer::reset() { interv = 0; elapsed = 0.0; T2=0; if (running) { //started = get_real_time(); T1=clock(); ++ interv; } else { started = 0.0; T1=0; } } double Real_timer::time() const { if (running) { clock_t T3 = clock(); return difftime(T3, T1); } return T2; } void Real_timer::top(string Texte) { clock_t T3 = clock(); double r = (double) (T3 - T1)/ CLOCKS_PER_SEC; std::cout << Texte << " .......... " << r << " s" << std::endl; reset(); } trunk-2018.02b/lib/triangulation/Timer.h000066400000000000000000000031171324306050200200470ustar00rootroot00000000000000#ifndef bru_REAL_TIMER_H #define bru_REAL_TIMER_H 1 #include #include using std::string; // SECTION: A Timer Measuring Real-Time // ======================================================================== // // DEFINITION // // A timer `t' of type Real_timer is an object with a state. It is either // running or it is stopped. The state is controlled with `t.start()' // and `t.stop()'. The timer counts the time elapsed since its creation // or last reset. It counts only the time where it is in the running // state. The time information is given in seconds. class Real_timer { private: clock_t T1; double T2; double elapsed; double started; int interv; bool running; static bool m_failed; double get_real_time() const; // in seconds double compute_precision() const; // in seconds public: Real_timer(); void start(); void stop (); void reset(); void top(std::string Texte); bool is_running() const { return running; } double time() const; int intervals() const { return interv; } double precision() const; // Returns timer precison. Computes it dynamically at first call. // Returns -1.0 if timer system call fails, which, for a proper coded // test towards precision leads to an immediate stop of an otherwise // infinite loop (fixed tolerance * total time >= precision). //double max() const { return DBL_MAX; } }; #endif // bru_REAL_TIMER_H // // EOF // trunk-2018.02b/lib/triangulation/TriaxialState.cpp000066400000000000000000000244421324306050200221040ustar00rootroot00000000000000/************************************************************************* * Copyright (C) 2006 by Bruno Chareyre * * bruno.chareyre@hmg.inpg.fr * * * * This program is free software; it is licensed under the terms of the * * GNU General Public License v2 or later. See file LICENSE for details. * *************************************************************************/ /// The info types that will be used and the namespace in which the types will be defined (a unique namespace with different info types would give name conflicts) #include "TriaxialState.h" #include #include #include #include namespace CGT { TriaxialState::TriaxialState(void) : NO_ZERO_ID(false), filter_distance(-0.1), tesselated(false) {} TriaxialState::~TriaxialState(void) { ContactIterator last = contacts_end(); for (ContactIterator it=contacts_begin(); it!=last; ++it) { if (*it) delete *it;} } Real TriaxialState::find_parameter (const char* parameter_name, ifstream& file) { string buffer; Real value; file >> buffer; bool test = (buffer == string(parameter_name)); while (!test) { buffer.clear(); file >> buffer; test = ( buffer == string(parameter_name) || file.eof()); } if (!file.eof()) file >> value; else value = 0; // cout << string(parameter_name) << value << endl; return value; } Real TriaxialState::find_parameter (const char* parameter_name, boost::iostreams::filtering_istream& file) { string buffer; Real value; file >> buffer; bool test = (buffer == string(parameter_name)); while (!test) { buffer.clear(); file >> buffer; test = ( buffer == string(parameter_name) || file.eof()); } if (!file.eof()) file >> value; else value = 0; // cout << string(parameter_name) << value << endl; return value; } Real TriaxialState::find_parameter (const char* parameter_name, const char* filename) { ifstream statefile (filename); return find_parameter(parameter_name, statefile); } void TriaxialState::reset (void) { tesselated = false; Tes.Clear(); mean_radius=0; grains.clear(); ContactIterator contacts_end = contacts.end(); for (ContactIterator it=contacts.begin(); it!=contacts_end; ++it) delete *it; contacts.clear(); contacts.resize(0); box.base = Point(1.0e10, 1.0e10, 1.0e10); box.sommet = Point(-1.0e10, -1.0e10, -1.0e10); } TriaxialState::GrainIterator TriaxialState::grains_begin (void) { GrainIterator git = grains.begin(); if (NO_ZERO_ID) return ++git; else return git; } TriaxialState::ContactIterator TriaxialState::contacts_begin (void) { return contacts.begin(); } TriaxialState::GrainIterator TriaxialState::grains_end (void) { return grains.end(); } TriaxialState::ContactIterator TriaxialState::contacts_end (void) { return contacts.end(); } TriaxialState::Grain& TriaxialState::grain (unsigned int id) { return grains[id]; } TriaxialState::Tesselation& TriaxialState::Tesselate (void) { if (!tesselated) { Tes.Clear(); GrainIterator git = grains_begin(); GrainIterator last = grains_end(); Tes.vertexHandles.resize(grains.size()+ (NO_ZERO_ID ? 1 : 0)); for (; git!=last; ++git) { if (git->id != -1 /*&& git->isSphere*/) Tes.vertexHandles[git->id] = Tes.insert(git->sphere.x(), git->sphere.y(), git->sphere.z(), git->sphere.weight(),git->id,!git->isSphere); //vh->->info() = git->translation; FIXME : this could define displacements in the triangulation itself // cerr << "Tes.insert(git->sphere.x(), git->sphere.y(), git->sphere.z(), git->sphere.weight(), git->id);" << endl; } Tes.redirected = true;//vertexHandle has been filled here, no need to do it again tesselated = true; cerr << "Triangulated Grains : " << Tes.Triangulation().number_of_vertices() << endl; } return Tes; } TriaxialState::Tesselation& TriaxialState::tesselation (void) { return Tesselate(); } bool TriaxialState::inside(Real x, Real y, Real z) { return (x >= (box.base.x()+filter_distance*mean_radius) && x <= (box.sommet.x()-filter_distance*mean_radius) && y >= (box.base.y()+filter_distance*mean_radius) && y <= (box.sommet.y()-filter_distance*mean_radius) && z >= (box.base.z()+filter_distance*mean_radius) && z <= (box.sommet.z()-filter_distance*mean_radius) ); } bool TriaxialState::inside(CVector v) { return TriaxialState::inside(v.x(), v.y(), v.z()); } bool TriaxialState::inside(Point p) { return TriaxialState::inside(p.x(), p.y(), p.z()); } bool TriaxialState::from_file(const char* filename, bool bz2) { reset(); // // Don't use bzipped files // // cout << filename << endl; // ifstream Statefile(filename); // if (!Statefile.is_open()) { // cout << "Error opening files"; // return false; // } // // Use bzipped files boost::iostreams::filtering_istream Statefile; if (bz2) { Statefile.push(boost::iostreams::bzip2_decompressor()); Statefile.push(boost::iostreams::file_source(string(filename)+".bz2"));} else Statefile.push(boost::iostreams::file_source(string(filename))); if(!Statefile.good()) {cerr << "Error opening files"; return false;} long Idg; long Ns=0;//number of spheres (excluding fictious ones)) Statefile >> Ng; //Real x, y, z, rad; //coordonn�es/rayon //Real tx, ty, tz; Point pos(CGAL::ORIGIN); mean_radius=0; CVector trans, rot; Real rad; //coordonn�es/rayon bool isSphere; grains.resize(Ng+1); //cout << "Ngrains =" << Ng << endl; if (NO_ZERO_ID) { GrainIterator git= grains.begin(); git->id=0; git->sphere = Sphere(CGAL::ORIGIN, 0); git->translation = CGAL::NULL_VECTOR; git->rotation = CGAL::NULL_VECTOR; } long i= NO_ZERO_ID ? 1 : 0; for (; i <= Ng ; ++i) { Statefile >> Idg >> pos >> rad >> trans >> rot >> isSphere; grains[Idg].id = Idg; grains[Idg].sphere = Sphere(pos, rad); grains[Idg].translation = trans; grains[Idg].rotation = rot; grains[Idg].isSphere = isSphere; box.base = Point(min(box.base.x(), pos.x()-rad), min(box.base.y(), pos.y()-rad), min(box.base.z(), pos.z()-rad)); box.sommet = Point(max(box.sommet.x(), pos.x()+rad), max(box.sommet.y(), pos.y()+rad), max(box.sommet.z(), pos.z()+rad)); if (isSphere) {mean_radius += grains[Idg].sphere.weight(); ++Ns;} //cout << "Idg: "<< Idg << " sphere: " << grains[Idg].sphere << " trans: " << grains[Idg].translation << endl; } mean_radius /= Ns;//rayon moyen //cout << filename << " loaded : " << Ng << " grains with mean radius = " << mean_radius << endl; long id1, id2; int stat; CVector c_pos, normal, old_fs, fs; Real old_fn, fn, frictional_work; Statefile >> Nc; contacts.resize(Nc); for (long i=0 ; i < Nc ; ++i) { Contact* c = new Contact; Statefile >> id1 >> id2 >> normal >> c_pos >> old_fn >> old_fs >> fn >> fs >> frictional_work >> stat; normal = (grains[id2].sphere.point()-grains[id1].sphere.point()); normal = normal/sqrt(pow(normal.x(),2)+pow(normal.y(),2)+pow(normal.z(),2)); c->grain1 = &(grains[id1]); c->grain2 = &(grains[id2]); grains[id1].contacts.push_back(c); grains[id2].contacts.push_back(c); c->normal = normal; c->position = c_pos; c->old_fn = old_fn; c->old_fs = old_fs; c->fn = fn; c->fs = fs; c->frictional_work = frictional_work; c->status = (Contact::Status) stat; if (contacts[i]) delete contacts[i]; contacts[i] = c; } //cout << "c_pos=" << contacts[10]->position << " old_fn=" << contacts[10]->old_fn << " normal=" << contacts[10]->normal << endl; //rfric = find_parameter("rfric=", Statefile);// � remettre quand les fichiers n'auront plus l'espace de trop... Eyn = find_parameter("Eyn", Statefile); Eys = find_parameter("Eys", Statefile); wszzh = find_parameter("wszzh", Statefile); wsxxd = find_parameter("wsxxd", Statefile); wsyyfa = find_parameter("wsyyfa", Statefile); eps3 = find_parameter("eps3", Statefile); eps1 = find_parameter("eps1", Statefile); eps2 = find_parameter("eps2", Statefile); porom = find_parameter("porom", Statefile); haut = find_parameter("haut", Statefile); larg = find_parameter("larg", Statefile); prof = find_parameter("prof", Statefile); ratio_f = find_parameter("ratio_f", Statefile); vit = find_parameter("vit", Statefile); // //Don't use bzipped files // Statefile.close(); //cout << endl << "wszzh= " << wszzh << endl; /*GrainIterator grains_end = grains.end(); for (GrainIterator it=grains.begin(); it!=grains_end; ++it) { if (it==grains.begin()) ++it; Vue1.Dessine_Sphere(it->sphere.x(), it->sphere.y(), it->sphere.z(), it->sphere.weight(), 10); }*/ //Vue1.Affiche(); return true; } bool TriaxialState::to_file(const char* filename, bool bz2) { //string fname (filename); // use bzipped files boost::iostreams::filtering_ostream Statefile; //if(boost::algorithm::ends_with(filename,".bz2")) Statefile.push(iostreams::bzip2_compressor()); if (bz2) { Statefile.push(boost::iostreams::bzip2_compressor()); Statefile.push(boost::iostreams::file_sink(string(filename)+".bz2"));} else Statefile.push(boost::iostreams::file_sink(string(filename))); // Don't use bzipped files //ofstream Statefile (filename); //if (!Statefile.is_open()) { if(!Statefile.good()) { cerr << "Error opening files"; return false; } long Id_max = grains.size()-1; Statefile << Id_max << endl; for (long Idg=0 ; Idg <= Id_max ; ++Idg) { Statefile << grains[Idg].id << " " << grains[Idg].sphere.point() << " " << grains[Idg].sphere.weight() << " " << grains[Idg].translation << " " << grains[Idg].rotation << " "<grain1->id << " " << contacts[i]->grain2->id << " " << contacts[i]->normal << " " << contacts[i]->position << " " << contacts[i]->old_fn << " " << contacts[i]->old_fs << " " << contacts[i]->fn << " " << contacts[i]->fs << " " << contacts[i]->frictional_work << " " << contacts[i]->status << endl; } Statefile << "Eyn " << Eyn << " Eys " << Eys << " wszzh " << wszzh << " wsxxd " << wsxxd << " wsyyfa " << wsyyfa << " eps3 " << eps3 << " eps1 " << eps1 << " eps2 " << eps2 << " porom " << porom << " haut " << haut << " larg " << larg << " prof " << prof << " ratio_f " << ratio_f << " vit " << vit << endl; // //Don't use bzipped files // Statefile.close(); return true; } } // namespace CGT trunk-2018.02b/lib/triangulation/TriaxialState.h000066400000000000000000000061451324306050200215510ustar00rootroot00000000000000/************************************************************************* * Copyright (C) 2006 by Bruno Chareyre * * bruno.chareyre@hmg.inpg.fr * * * * This program is free software; it is licensed under the terms of the * * GNU General Public License v2 or later. See file LICENSE for details. * *************************************************************************/ /** @author Bruno Chareyre */ #pragma once #include "Tesselation.h" #include #include /*! \class TriaxialState * \brief A storage class with ascii input/output for bodies, contacts, and macro-variables. Yade packings are first converted to this object type, before being processed in KinematicLocalisationAnalyser. * */ namespace CGT { using std::string; using std::min; class TriaxialState { public: typedef _Tesselation Tesselation; typedef Tesselation::RTriangulation RTriangulation; class Contact; class Grain; typedef struct {Point base; Point sommet;} Box; typedef vector VectorContact; typedef vector VectorGrain; typedef VectorContact::iterator ContactIterator; typedef VectorGrain::iterator GrainIterator; class Grain { public : int id; bool isSphere; Sphere sphere; CVector translation; CVector rotation; VectorContact contacts; Grain(void) {id=-1; isSphere=true;} }; class Contact { public : enum Status {NEW, PERSISTENT, LOST}; Grain* grain1; Grain* grain2; CVector position; CVector normal; Real fn; CVector fs; Real old_fn; CVector old_fs; Real frictional_work; bool visited; Status status; Contact(void) {visited=false; status=PERSISTENT;} }; TriaxialState(void); ~TriaxialState(void); bool from_file(const char* filename, bool bz2=false); bool to_file(const char* filename, bool bz2=false); bool inside(Real x, Real y, Real z); bool inside(CVector v); bool inside(Point p); static Real find_parameter (const char* parameter_name, const char* filename); static Real find_parameter (const char* parameter_name, boost::iostreams::filtering_istream& file); static Real find_parameter (const char* parameter_name, ifstream& file); void reset (void); GrainIterator grains_begin (void); ContactIterator contacts_begin (void); GrainIterator grains_end (void); ContactIterator contacts_end (void); Tesselation& tesselation (void); Grain& grain (unsigned int id); //Public member data : bool NO_ZERO_ID;//Is there a body with id=0? Real mean_radius; Box box; Real filter_distance; //distance de filtrage au voisinage des parois - normalis�e par le rayon moyen long Ng, Nc; Real rfric, Eyn, Eys, wszzh, wsxxd, wsyyfa, eps1, eps2, eps3, porom, haut, larg, prof, ratio_f, vit; VectorContact contacts; VectorGrain grains; private : Tesselation Tes; Tesselation& Tesselate (void); //Private member data : bool tesselated; }; } // namespace CGT trunk-2018.02b/lib/triangulation/basicVTKwritter.cpp000066400000000000000000000050541324306050200224130ustar00rootroot00000000000000#include "basicVTKwritter.hpp" bool basicVTKwritter::open(const char * filename, const char * comment) { file.open(filename,ios_base::out); if (!file) { cerr << "Cannot open file [" << filename << "]" << endl; return false; } // Header file << "# vtk DataFile Version 3.0" << endl; file << comment << endl; file << "ASCII" << endl; file << "DATASET UNSTRUCTURED_GRID" << endl; file << endl; return true; } bool basicVTKwritter::close() { file.close(); return true; } void basicVTKwritter::begin_vertices() { file << "POINTS " << nbVertices << " float" << endl; } void basicVTKwritter::begin_cells() { file << "CELLS " << nbCells << " " << 5*nbCells << endl; } void basicVTKwritter::write_point(float x, float y, float z) { file << x << " " << y << " " << z << endl; } // Note that identifiers must be defined with 0-offset void basicVTKwritter::write_cell(unsigned int id1, unsigned int id2, unsigned int id3, unsigned int id4) { file << "4 " << id1 << " " << id2 << " " << id3 << " " << id4 << endl; } void basicVTKwritter::end_vertices() { file << endl; } void basicVTKwritter::end_cells() { file << "CELL_TYPES " << nbCells << endl; for (unsigned int n = 0 ; n < nbCells ; ++n) { file << "10" << endl; } file << endl; } void basicVTKwritter::begin_data(const char * dataname, DataPosition pos, DataName name, DataType type) { switch(pos) { case POINT_DATA : if (!hasPointData) {file << "POINT_DATA " << nbVertices << endl; hasPointData=true;} break; case CELL_DATA : if (!hasCellData) {file << "CELL_DATA " << nbCells << endl; hasCellData=true;} break; } switch (name) { case SCALARS : file << "SCALARS " << dataname; break; case VECTORS : file << "VECTORS " << dataname; break; case TENSORS : file << "TENSORS " << dataname; break; } switch (type) { case INT : file << " int"; break; case FLOAT : file << " float"; break; } if (name == SCALARS) { file << " 1" << endl; file << "LOOKUP_TABLE default" << endl;} else file << endl; } void basicVTKwritter::write_data(float value) { file << value << endl; } void basicVTKwritter::write_data(float x, float y, float z) { file << x << " " << y << " " << z << endl; } void basicVTKwritter::write_data( float t11, float t12, float t13, float t21, float t22, float t23, float t31, float t32, float t33) { file << t11 << " " << t12 << " " << t13 << endl; file << t21 << " " << t22 << " " << t23 << endl; file << t31 << " " << t32 << " " << t33 << endl; file << endl; } void basicVTKwritter::end_data() { file << endl; } trunk-2018.02b/lib/triangulation/basicVTKwritter.hpp000066400000000000000000000022121324306050200224110ustar00rootroot00000000000000#pragma once #include #include enum DataPosition {POINT_DATA,CELL_DATA}; enum DataName {SCALARS,VECTORS,TENSORS}; enum DataType {INT,FLOAT}; using std::ios_base; using std::endl; using std::cout; using std::cerr; // Really simplistic struct for vtk file creation struct basicVTKwritter { std::ofstream file; unsigned int nbVertices, nbCells; bool hasPointData; bool hasCellData; basicVTKwritter(unsigned int nV, unsigned int nC) : nbVertices(nV),nbCells(nC),hasPointData(false),hasCellData(false) {} bool open(const char * filename, const char * comment); bool close(); void begin_vertices(); void write_point(float x, float y, float z); void end_vertices(); void begin_cells(); void write_cell(unsigned int id1, unsigned int id2, unsigned int id3, unsigned int id4); void end_cells(); void begin_data(const char * dataname, DataPosition pos, DataName name, DataType type); void write_data(float value); void write_data(float x, float y, float z); void write_data(float t11, float t12, float t13, float t21, float t22, float t23, float t31, float t32, float t33); void end_data(); }; trunk-2018.02b/pkg/000077500000000000000000000000001324306050200137475ustar00rootroot00000000000000trunk-2018.02b/pkg/common/000077500000000000000000000000001324306050200152375ustar00rootroot00000000000000trunk-2018.02b/pkg/common/Aabb.hpp000066400000000000000000000022101324306050200165700ustar00rootroot00000000000000/************************************************************************* * Copyright (C) 2004 by Olivier Galizzi * * olivier.galizzi@imag.fr * * * * This program is free software; it is licensed under the terms of the * * GNU General Public License v2 or later. See file LICENSE for details. * *************************************************************************/ #pragma once #include /*! Representation of bound by min and max points. This class is redundant, since it has no data members; don't delete it, though, as Bound::{min,max} might move here one day. */ class Aabb : public Bound{ public : virtual ~Aabb() {}; YADE_CLASS_BASE_DOC_ATTRS_CTOR(Aabb,Bound,"Axis-aligned bounding box, for use with :yref:`InsertionSortCollider`. (This class is quasi-redundant since min,max are already contained in :yref:`Bound` itself. That might change at some point, though.)",/*attrs*/,/*ctor*/createIndex();); REGISTER_CLASS_INDEX(Aabb,Bound); }; REGISTER_SERIALIZABLE(Aabb); trunk-2018.02b/pkg/common/Bo1_Aabb.cpp000066400000000000000000000063041324306050200172740ustar00rootroot00000000000000/************************************************************************* * Copyright (C) 2004 by Olivier Galizzi * * olivier.galizzi@imag.fr * * * * This program is free software; it is licensed under the terms of the * * GNU General Public License v2 or later. See file LICENSE for details. * *************************************************************************/ #include YADE_PLUGIN((Bo1_Sphere_Aabb)(Bo1_Facet_Aabb)(Bo1_Box_Aabb)); void Bo1_Sphere_Aabb::go(const shared_ptr& cm, shared_ptr& bv, const Se3r& se3, const Body* b){ Sphere* sphere = static_cast(cm.get()); if(!bv){ bv=shared_ptr(new Aabb); } Aabb* aabb=static_cast(bv.get()); Vector3r halfSize = (aabbEnlargeFactor>0?aabbEnlargeFactor:1.)*Vector3r(sphere->radius,sphere->radius,sphere->radius); if(!scene->isPeriodic){ aabb->min=se3.position-halfSize; aabb->max=se3.position+halfSize; return; } // adjust box size along axes so that sphere doesn't stick out of the box even if sheared (i.e. parallelepiped) if(scene->cell->hasShear()) { Vector3r refHalfSize(halfSize); const Vector3r& cos=scene->cell->getCos(); for(int i=0; i<3; i++){ //cerr<<"cos["<min = scene->cell->unshearPt(se3.position)-halfSize; aabb->max = scene->cell->unshearPt(se3.position)+halfSize; } void Bo1_Facet_Aabb::go( const shared_ptr& cm , shared_ptr& bv , const Se3r& se3 , const Body* b) { if(!bv){ bv=shared_ptr(new Aabb); } Aabb* aabb=static_cast(bv.get()); Facet* facet = static_cast(cm.get()); const Vector3r& O = se3.position; Matrix3r facetAxisT=se3.orientation.toRotationMatrix(); const vector& vertices=facet->vertices; if(!scene->isPeriodic){ aabb->min=aabb->max = O + facetAxisT * vertices[0]; for (int i=1;i<3;++i) { Vector3r v = O + facetAxisT * vertices[i]; aabb->min = aabb->min.cwiseMin(v); aabb->max = aabb->max.cwiseMax(v); } } else { Real inf=std::numeric_limits::infinity(); aabb->min=Vector3r(inf,inf,inf); aabb->max=Vector3r(-inf,-inf,-inf); for(int i=0; i<3; i++){ Vector3r v=scene->cell->unshearPt(O+facetAxisT*vertices[i]); aabb->min=aabb->min.cwiseMin(v); aabb->max=aabb->max.cwiseMax(v); } } } void Bo1_Box_Aabb::go( const shared_ptr& cm, shared_ptr& bv, const Se3r& se3, const Body* b) { Box* box = static_cast(cm.get()); if(!bv){ bv=shared_ptr(new Aabb); } Aabb* aabb=static_cast(bv.get()); if(scene->isPeriodic && scene->cell->hasShear()) throw logic_error(__FILE__ "Boxes not (yet?) supported in sheared cell."); Matrix3r r=se3.orientation.toRotationMatrix(); Vector3r halfSize(Vector3r::Zero()); for( int i=0; i<3; ++i ) for( int j=0; j<3; ++j ) halfSize[i] += std::abs( r(i,j) * box->extents[j] ); aabb->min = se3.position-halfSize; aabb->max = se3.position+halfSize; } trunk-2018.02b/pkg/common/Bo1_Aabb.hpp000066400000000000000000000060771324306050200173100ustar00rootroot00000000000000/************************************************************************* * Copyright (C) 2004 by Olivier Galizzi * * olivier.galizzi@imag.fr * * * * This program is free software; it is licensed under the terms of the * * GNU General Public License v2 or later. See file LICENSE for details. * *************************************************************************/ #pragma once #include #include #include #include #include class Bo1_Sphere_Aabb : public BoundFunctor { public : void go(const shared_ptr& cm, shared_ptr& bv, const Se3r&, const Body*); FUNCTOR1D(Sphere); YADE_CLASS_BASE_DOC_ATTRS(Bo1_Sphere_Aabb,BoundFunctor,"Functor creating :yref:`Aabb` from :yref:`Sphere`.", ((Real,aabbEnlargeFactor,((void)"deactivated",-1),,"Relative enlargement of the bounding box; deactivated if negative.\n\n.. note::\n\tThis attribute is used to create distant interaction, but is only meaningful with an :yref:`IGeomFunctor` which will not simply discard such interactions: :yref:`Ig2_Sphere_Sphere_ScGeom::interactionDetectionFactor` should have the same value as :yref:`aabbEnlargeFactor`.")) ); }; REGISTER_SERIALIZABLE(Bo1_Sphere_Aabb); /************************************************************************* * Copyright (C) 2008 by Sergei Dorofeenko * * sega@users.berlios.de * * * * This program is free software; it is licensed under the terms of the * * GNU General Public License v2 or later. See file LICENSE for details. * *************************************************************************/ class Bo1_Facet_Aabb : public BoundFunctor{ public: void go(const shared_ptr& cm, shared_ptr& bv, const Se3r& se3, const Body*); FUNCTOR1D(Facet); YADE_CLASS_BASE_DOC(Bo1_Facet_Aabb,BoundFunctor,"Creates/updates an :yref:`Aabb` of a :yref:`Facet`."); }; REGISTER_SERIALIZABLE(Bo1_Facet_Aabb); /************************************************************************* * Copyright (C) 2004 by Janek Kozicki * * cosurgi@berlios.de * * * * This program is free software; it is licensed under the terms of the * * GNU General Public License v2 or later. See file LICENSE for details. * *************************************************************************/ class Box; class Bo1_Box_Aabb : public BoundFunctor{ public: void go(const shared_ptr& cm, shared_ptr& bv, const Se3r& se3, const Body*); FUNCTOR1D(Box); YADE_CLASS_BASE_DOC(Bo1_Box_Aabb,BoundFunctor,"Create/update an :yref:`Aabb` of a :yref:`Box`."); }; REGISTER_SERIALIZABLE(Bo1_Box_Aabb); trunk-2018.02b/pkg/common/BoundaryController.hpp000066400000000000000000000006751324306050200216070ustar00rootroot00000000000000#pragma once #include class BoundaryController: public GlobalEngine{ virtual void action() { { throw std::runtime_error("BoundaryController must not be used in simulations directly (BoundaryController::action called)."); } } YADE_CLASS_BASE_DOC(BoundaryController,GlobalEngine,"Base for engines controlling boundary conditions of simulations. Not to be used directly."); }; REGISTER_SERIALIZABLE(BoundaryController); trunk-2018.02b/pkg/common/Box.hpp000066400000000000000000000017001324306050200164760ustar00rootroot00000000000000/************************************************************************* * Copyright (C) 2004 by Olivier Galizzi * * olivier.galizzi@imag.fr * * * * This program is free software; it is licensed under the terms of the * * GNU General Public License v2 or later. See file LICENSE for details. * *************************************************************************/ #pragma once #include class Box: public Shape{ public: Box(const Vector3r& _extents): extents(_extents){} virtual ~Box () {}; YADE_CLASS_BASE_DOC_ATTRS_CTOR(Box,Shape,"Box (cuboid) particle geometry. (Avoid using in new code, prefer :yref:`Facet` instead.", ((Vector3r,extents,,,"Half-size of the cuboid")), /* ctor */ createIndex(); ); REGISTER_CLASS_INDEX(Box,Shape); }; REGISTER_SERIALIZABLE(Box); trunk-2018.02b/pkg/common/Callbacks.hpp000066400000000000000000000040471324306050200176340ustar00rootroot00000000000000// 2010 © Václav Šmilauer #pragma once #include class Interaction; class Body; class Scene; class IntrCallback: public Serializable{ public: virtual ~IntrCallback() {}; // vtable typedef void(*FuncPtr)(IntrCallback*,Interaction*); // should be set at every step by InteractionLoop Scene* scene; /* At the beginning of each timestep, perform initialization and return pointer to the static member function that does the actual work. Returned value might be NULL, in which case the callback will be deactivated during that timestep. */ virtual FuncPtr stepInit(){ throw std::runtime_error("Called IntrCallback::stepInit() of the base class?"); } YADE_CLASS_BASE_DOC(IntrCallback,Serializable,"Abstract callback object which will be called for every (real) :yref:`Interaction` after the interaction has been processed by :yref:`InteractionLoop`.\n\nAt the beginning of the interaction loop, ``stepInit`` is called, initializing the object; it returns either ``NULL`` (to deactivate the callback during this time step) or pointer to function, which will then be passed (1) pointer to the callback object itself and (2) pointer to :yref:`Interaction`.\n\n.. note::\n\t(NOT YET DONE) This functionality is accessible from python by passing 4th argument to :yref:`InteractionLoop` constructor, or by appending the callback object to :yref:`InteractionLoop::callbacks`.\n"); }; REGISTER_SERIALIZABLE(IntrCallback); #ifdef YADE_BODY_CALLBACKS class BodyCallback: public Serializable{ public: virtual ~BodyCallback() {}; // vtable typedef void(*FuncPtr)(BodyCallback*,Body*); // set at every step, before stepInit() is called Scene* scene; virtual FuncPtr stepInit(){ throw std::runtime_error("Called BodyCallback::stepInit() of the base class?"); } YADE_CLASS_BASE_DOC(BodyCallback,Serializable,"Abstract callback object which will be called for every :yref:`Body` after being processed by :yref:`NewtonIntegrator`. See :yref:`IntrCallback` for details."); }; REGISTER_SERIALIZABLE(BodyCallback); #endif trunk-2018.02b/pkg/common/Collider.cpp000066400000000000000000000041571324306050200175070ustar00rootroot00000000000000/************************************************************************* * Copyright (C) 2004 by Janek Kozicki * * cosurgi@berlios.de * * * * This program is free software; it is licensed under the terms of the * * GNU General Public License v2 or later. See file LICENSE for details. * *************************************************************************/ #include YADE_PLUGIN((Collider)); int Collider::avoidSelfInteractionMask = 0 ; bool Collider::mayCollide(const Body* b1, const Body* b2){ return // might be called with deleted bodies, i.e. NULL pointers (b1!=NULL && b2!=NULL) && // only collide if at least one particle is standalone or they belong to different clumps (b1->isStandalone() || b2->isStandalone() || b1->clumpId!=b2->clumpId #ifdef YADE_SPH // If SPH-mode enabled, we do not skip interactions between clump-members // to get the correct calculation of density b->rho || true #endif ) && // do not collide clumps, since they are just containers, never interact !b1->isClump() && !b2->isClump() && // masks must have at least 1 bit in common b1->maskCompatible(b2->groupMask) && // avoid contact between particles having the same mask compatible with the avoidSelfInteractionMask. !( (b1->groupMask == b2->groupMask) && b1->maskCompatible(avoidSelfInteractionMask) ) ; } void Collider::pyHandleCustomCtorArgs(boost::python::tuple& t, boost::python::dict& d){ if(boost::python::len(t)==0) return; // nothing to do if(boost::python::len(t)!=1) throw invalid_argument(("Collider optionally takes exactly one list of BoundFunctor's as non-keyword argument for constructor ("+boost::lexical_cast(boost::python::len(t))+" non-keyword ards given instead)").c_str()); typedef std::vector > vecBound; vecBound vb=boost::python::extract(t[0])(); FOREACH(shared_ptr bf, vb) this->boundDispatcher->add(bf); t=boost::python::tuple(); // empty the args } trunk-2018.02b/pkg/common/Collider.hpp000066400000000000000000000055261324306050200175150ustar00rootroot00000000000000/************************************************************************* * Copyright (C) 2004 by Janek Kozicki * * cosurgi@berlios.de * * * * This program is free software; it is licensed under the terms of the * * GNU General Public License v2 or later. See file LICENSE for details. * *************************************************************************/ #pragma once #include #include #include #include class Collider: public GlobalEngine { public: static int avoidSelfInteractionMask; /*! Probe the Bound on a bodies presense. Returns list of body ids with which there is potential overlap. */ virtual vector probeBoundingVolume(const Bound&){throw;} /*! Tell whether given bodies may interact, for other than spatial reasons. * * Concrete collider implementations should call this function if * the bodies are in potential interaction geometrically. */ static bool mayCollide(const Body*, const Body*); /*! Invalidate all persistent data (if the collider has any), forcing reinitialization at next run. The default implementation does nothing, colliders should override it if it is applicable. Currently used from Shop::flipCell, which changes cell information for bodies. */ virtual void invalidatePersistentData(){} // ctor with functors for the integrated BoundDispatcher virtual void pyHandleCustomCtorArgs(boost::python::tuple& t, boost::python::dict& d); int get_avoidSelfInteractionMask(){return avoidSelfInteractionMask;} void set_avoidSelfInteractionMask(const int &v){avoidSelfInteractionMask = v;} YADE_CLASS_BASE_DOC_ATTRS_CTOR_PY(Collider,GlobalEngine,"Abstract class for finding spatial collisions between bodies. \n\n.. admonition:: Special constructor\n\n\tDerived colliders (unless they override ``pyHandleCustomCtorArgs``) can be given list of :yref:`BoundFunctors ` which is used to initialize the internal :yref:`boundDispatcher ` instance.", ((shared_ptr,boundDispatcher,new BoundDispatcher,Attr::readonly,":yref:`BoundDispatcher` object that is used for creating :yref:`bounds ` on collider's request as necessary.")), /*ctor*/, .add_property("avoidSelfInteractionMask",&Collider::get_avoidSelfInteractionMask,&Collider::set_avoidSelfInteractionMask,"This mask is used to avoid the interactions inside a group of particles. To do so, the particles must have the exact same mask and that mask should have one bit in common with this :yref:`avoidSelfInteractionMask` as for their binary representations.") ); }; REGISTER_SERIALIZABLE(Collider); trunk-2018.02b/pkg/common/CylScGeom6D.hpp000066400000000000000000000076531324306050200200020ustar00rootroot00000000000000// 2011 © Bruno Chareyre // 2012 © Kneib Francois #pragma once #include class CylScGeom: public ScGeom { public: /// Emulate a sphere whose position is the projection of sphere's center on cylinder sphere, and with motion linearly interpolated between nodes State fictiousState; // shared_ptr duplicate; virtual ~CylScGeom () {}; YADE_CLASS_BASE_DOC_ATTRS_CTOR(CylScGeom,ScGeom,"Geometry of a cylinder-sphere contact.", ((bool,onNode,false,,"contact on node?")) ((int,isDuplicate,0,,"this flag is turned true (1) automatically if the contact is shared between two chained cylinders. A duplicated interaction will be skipped once by the constitutive law, so that only one contact at a time is effective. If isDuplicate=2, it means one of the two duplicates has no longer geometric interaction, and should be erased by the constitutive laws.")) ((int,trueInt,-1,,"Defines the body id of the cylinder where the contact is real, when :yref:`CylScGeom::isDuplicate`>0.")) ((Vector3r,start,Vector3r::Zero(),,"position of 1st node |yupdate|")) ((Vector3r,end,Vector3r::Zero(),,"position of 2nd node |yupdate|")) ((Body::id_t,id3,0,,"id of next chained cylinder |yupdate|")) ((Real,relPos,0,,"position of the contact on the cylinder (0: node-, 1:node+) |yupdate|")), createIndex(); /*ctor*/ ); REGISTER_CLASS_INDEX(CylScGeom,ScGeom); }; REGISTER_SERIALIZABLE(CylScGeom); class CylScGeom6D: public ScGeom6D { public: virtual ~CylScGeom6D() {}; void precomputeRotations(const State& rbp1, const State& rbp2, bool isNew, bool creep=false) { initRotations(rbp1,rbp2); } void initRotations(const State& rbp1, const State& rbp2){ initialOrientation1 = rbp1.ori; initialOrientation2 = rbp2.ori; twist=0; bending=Vector3r::Zero(); twistCreep=Quaternionr(1.0,0.0,0.0,0.0); } State fictiousState; YADE_CLASS_BASE_DOC_ATTRS_INIT_CTOR_PY(CylScGeom6D,ScGeom6D,"Class representing :yref:`geometry` of two :yref:`bodies` in contact. The contact has 6 DOFs (normal, 2×shear, twist, 2xbending) and uses :yref:`ScGeom` incremental algorithm for updating shear.", ((bool,onNode,false,,"contact on node?")) ((int,isDuplicate,0,,"this flag is turned true (1) automatically if the contact is shared between two chained cylinders. A duplicated interaction will be skipped once by the constitutive law, so that only one contact at a time is effective. If isDuplicate=2, it means one of the two duplicates has no longer geometric interaction, and should be erased by the constitutive laws.")) ((int,trueInt,-1,,"Defines the body id of the cylinder where the contact is real, when :yref:`CylScGeom::isDuplicate`>0.")) ((Vector3r,start,Vector3r::Zero(),,"position of 1st node |yupdate|")) ((Vector3r,end,Vector3r::Zero(),,"position of 2nd node |yupdate|")) ((Body::id_t,id3,0,,"id of next chained cylinder |yupdate|")) ((Real,relPos,0,,"position of the contact on the cylinder (0: node-, 1:node+) |yupdate|")), /* extra initializers */, /* ctor */ createIndex();, /* py */ ); REGISTER_CLASS_INDEX(CylScGeom6D,ScGeom6D); }; REGISTER_SERIALIZABLE(CylScGeom6D); trunk-2018.02b/pkg/common/Cylinder.cpp000066400000000000000000001203541324306050200175210ustar00rootroot00000000000000// 2011 © Bruno Chareyre // 2012 © Kneib Francois #include "Cylinder.hpp" #include #ifdef YADE_OPENGL #include #endif #include Cylinder::~Cylinder(){} ChainedCylinder::~ChainedCylinder(){} ChainedState::~ChainedState(){} // Ig2_Sphere_ChainedCylinder_CylScGeom::~Ig2_Sphere_ChainedCylinder_CylScGeom() {} // Ig2_ChainedCylinder_ChainedCylinder_ScGeom6D::~Ig2_ChainedCylinder_ChainedCylinder_ScGeom6D() {} YADE_PLUGIN((Cylinder)(ChainedCylinder)(ChainedState)(Ig2_Sphere_ChainedCylinder_CylScGeom)(Ig2_Sphere_ChainedCylinder_CylScGeom6D)(Ig2_ChainedCylinder_ChainedCylinder_ScGeom6D)(Law2_CylScGeom6D_CohFrictPhys_CohesionMoment)(Law2_ChCylGeom6D_CohFrictPhys_CohesionMoment)(Law2_CylScGeom_FrictPhys_CundallStrack) #ifdef YADE_OPENGL (Gl1_Cylinder)(Gl1_ChainedCylinder) #endif (Bo1_Cylinder_Aabb)(Bo1_ChainedCylinder_Aabb) ); vector > ChainedState::chains; unsigned int ChainedState::currentChain=0; //!################## IG FUNCTORS ##################### //!Sphere-cylinder or cylinder-cylinder not implemented yet, see Ig2_ChainedCylinder_ChainedCylinder_ScGeom6D and test/chained-cylinder-spring.py bool Ig2_Sphere_ChainedCylinder_CylScGeom::go( const shared_ptr& cm1, const shared_ptr& cm2, const State& state1, const State& state2, const Vector3r& shift2, const bool& force, const shared_ptr& c) { const State* sphereSt=YADE_CAST(&state1); const ChainedState* cylinderSt=YADE_CAST(&state2); ChainedCylinder *cylinder=YADE_CAST(cm2.get()); Sphere *sphere=YADE_CAST(cm1.get()); assert(sphereSt && cylinderSt && cylinder && sphere); bool isLast = (cylinderSt->chains[cylinderSt->chainNumber].size()==(cylinderSt->rank+1)); bool isNew = !c->geom; shared_ptr scm; if (!isNew) {scm=YADE_PTR_CAST(c->geom);} bool betweenTwoCylinders = false;//defines whether the sphere's center is moving between two cylinders who have an angle>180° Vector3r segment, branch, direction;//informations about the current cylinder Real length, dist; shared_ptr statePrev; Vector3r segmentPrev=Vector3r(0,0,0),directionPrev=Vector3r(0,0,0),branchPrev=Vector3r(0,0,0); Real lengthPrev=0,distPrev=0; if (cylinderSt->rank>0){//informations about the previous cylinder statePrev = YADE_PTR_CAST (Body::byId(cylinderSt->chains[cylinderSt->chainNumber][cylinderSt->rank-1],scene)->state); segmentPrev = cylinderSt->pos-statePrev->pos; lengthPrev = segmentPrev.norm(); directionPrev = segmentPrev/lengthPrev; branchPrev = sphereSt->pos-statePrev->pos; distPrev = directionPrev.dot(branchPrev); } //FIXME : definition of segment in next line breaks periodicity shared_ptr cylinderNext; branch = sphereSt->pos-cylinderSt->pos-shift2; if (isLast){//handle the last node with length=0 segment = Vector3r(0,0,0); length = 0; direction = Vector3r(0,1,0); dist = directionPrev.dot(branch); if (dist<0) { if (isNew) return false; else if (scm->isDuplicate) { scm->isDuplicate=2; scm->penetrationDepth=-1; return true; } } } else { cylinderNext = Body::byId(cylinderSt->chains[cylinderSt->chainNumber][cylinderSt->rank+1],scene); segment = cylinderNext->state->pos-cylinderSt->pos; length = segment.norm(); direction = segment/length; dist = direction.dot(branch); if(cylinderSt->rank>0){ if(distPrev>lengthPrev && dist<0){//the sphere is touching two cylinder who have an angle>180° betweenTwoCylinders = true; } } if (!betweenTwoCylinders && (cylinderSt->rank>0 or dist>0)) { if ( segment.dot(branch) >= segment.dot(segment) or dist<0) {//position after or before the cylinder //FIXME : scm->penetrationDepth=-1 is defined to workaround interactions never being erased when scm->isDuplicate=2 on the true interaction. if (isNew) return false; else if (scm->isDuplicate) { scm->isDuplicate=2; scm->penetrationDepth=-1; return true; } } } } //Check sphere-cylinder distance Vector3r projectedP = cylinderSt->pos+shift2 + direction*dist; branch = projectedP-sphereSt->pos; if(isLast || (cylinderSt->rank==0 && dist<0)){branch = cylinderSt->pos - sphereSt->pos;} if (branch.norm() > sphere->radius+cylinder->radius) { if (isNew) return false; else if (scm->isDuplicate){ scm->isDuplicate=2; scm->penetrationDepth=-1; return true; } } if (!isNew) scm->isDuplicate = false;//reset here at each step, and recompute below //if there is a contact with the previous element in the chain, consider this one a duplicate and push data to the new contact. Two interactions will share the same geometry and physics during a timestep. if (!betweenTwoCylinders && cylinderSt->rank>0 && dist<0) { if (!isNew) { const shared_ptr intr = scene->interactions->find(c->id1,cylinderSt->chains[cylinderSt->chainNumber][cylinderSt->rank-1]); if(!intr) {cout<<"Skipping contact because collider didn't found the previous cylinder"<geom = c->geom; intr->phys = c->phys; scm=YADE_PTR_CAST(c->geom); scm->isDuplicate = 2; scm->trueInt = cylinderSt->chains[cylinderSt->chainNumber][cylinderSt->rank-1]; isNew = false; return true; } else scm->isDuplicate=true; scm->trueInt = cylinderSt->chains[cylinderSt->chainNumber][cylinderSt->rank-1]; } //similarly, make sure there is no contact with the next element in the chain //else if (!isLast && dist>length) { if (!betweenTwoCylinders && !isLast && dist>=length) { if (!isNew) { const shared_ptr intr = scene->interactions->find(c->id1,cylinderSt->chains[cylinderSt->chainNumber][cylinderSt->rank+1]); if(!intr) {cout<<"Skipping contact because collider didn't found the next cylinder."<geom = c->geom; intr->phys = c->phys; scm=YADE_PTR_CAST(c->geom); scm->isDuplicate = 2; scm->trueInt = cylinderSt->chains[cylinderSt->chainNumber][cylinderSt->rank+1]; isNew = false; return true; } else scm->isDuplicate=true; scm->trueInt = cylinderSt->chains[cylinderSt->chainNumber][cylinderSt->rank+1]; } //We didn't find any special case, do normal geometry definition if (isNew) { scm=shared_ptr(new CylScGeom()); c->geom=scm;} scm->radius1=sphere->radius; scm->radius2=cylinder->radius; if (!isLast) scm->id3=cylinderSt->chains[cylinderSt->chainNumber][cylinderSt->rank+1]; scm->start=cylinderSt->pos+shift2; scm->end=scm->start+segment; //FIXME : there should be other checks without distanceFactor? if (dist<=0 || isLast) {//We have sphere-node contact Vector3r normal=(cylinderSt->pos+shift2)-sphereSt->pos; Real norm=normal.norm(); normal /=norm; scm->relPos=0; scm->onNode=true; scm->relPos=0; scm->penetrationDepth=sphere->radius+cylinder->radius-norm; scm->contactPoint=sphereSt->pos+(sphere->radius-0.5*scm->penetrationDepth)*normal; scm->precompute(state1,state2,scene,c,normal,isNew,shift2,true);//use sphere-sphere precompute (a node is a sphere) } else {//we have sphere-cylinder contact scm->onNode=false; scm->relPos=dist/length; Real norm=branch.norm(); Vector3r normal=branch/norm; scm->penetrationDepth= sphere->radius+cylinder->radius-norm; // define a virtual sphere at the projected center scm->fictiousState.pos = projectedP; scm->fictiousState.vel = (1-scm->relPos)*cylinderSt->vel + scm->relPos*cylinderNext->state->vel; scm->fictiousState.angVel = ((1-scm->relPos)*cylinderSt->angVel + scm->relPos*cylinderNext->state->angVel).dot(direction)*direction //twist part : interpolated + segment.cross(cylinderNext->state->vel - cylinderSt->vel);// non-twist part : defined from nodes velocities if (dist>length) { scm->penetrationDepth=sphere->radius+cylinder->radius-(cylinderSt->pos+segment-sphereSt->pos).norm(); //FIXME : handle contact jump on next element } scm->contactPoint = sphereSt->pos+normal*(sphere->radius-0.5*scm->penetrationDepth); scm->precompute(state1,scm->fictiousState,scene,c,branch/norm,isNew,shift2,true);//use sphere-sphere precompute (with a virtual sphere) } return true; } bool Ig2_Sphere_ChainedCylinder_CylScGeom::goReverse( const shared_ptr& cm1, const shared_ptr& cm2, const State& state1, const State& state2, const Vector3r& shift2, const bool& force, const shared_ptr& c) { cerr<<"Ig2_Sphere_ChainedCylinder_CylScGeom::goReverse"<swapOrder(); return go(cm2,cm1,state2,state1,-shift2,force,c); } bool Ig2_Sphere_ChainedCylinder_CylScGeom6D::go( const shared_ptr& cm1, const shared_ptr& cm2, const State& state1, const State& state2, const Vector3r& shift2, const bool& force, const shared_ptr& c) { const State* sphereSt=YADE_CAST(&state1); const ChainedState* cylinderSt=YADE_CAST(&state2); ChainedCylinder *cylinder=YADE_CAST(cm2.get()); Sphere *sphere=YADE_CAST(cm1.get()); assert(sphereSt && cylinderSt && cylinder && sphere); bool isLast = (cylinderSt->chains[cylinderSt->chainNumber].size()==(cylinderSt->rank+1)); bool isNew = !c->geom; shared_ptr scm; if (!isNew) {scm=YADE_PTR_CAST(c->geom);} bool betweenTwoCylinders = false;//defines whether the sphere's center is moving between two cylinders who have an angle>180° Vector3r segment, branch, direction;//informations about the current cylinder Real length, dist; shared_ptr statePrev; Vector3r segmentPrev=Vector3r(0,0,0),directionPrev=Vector3r(0,0,0),branchPrev=Vector3r(0,0,0); Real lengthPrev=0,distPrev=0; if (cylinderSt->rank>0){//informations about the previous cylinder statePrev = YADE_PTR_CAST (Body::byId(cylinderSt->chains[cylinderSt->chainNumber][cylinderSt->rank-1],scene)->state); segmentPrev = cylinderSt->pos-statePrev->pos; lengthPrev = segmentPrev.norm(); directionPrev = segmentPrev/lengthPrev; branchPrev = sphereSt->pos-statePrev->pos; distPrev = directionPrev.dot(branchPrev); } //FIXME : definition of segment in next line breaks periodicity shared_ptr cylinderNext; branch = sphereSt->pos-cylinderSt->pos-shift2; if (isLast){//handle the last node with length=0 segment = Vector3r(0,0,0); length = 0; direction = Vector3r(0,1,0); dist = directionPrev.dot(branch); if (dist<0) { if (isNew) return false; else if (scm->isDuplicate) { scm->isDuplicate=2; return true; } } } else { cylinderNext = Body::byId(cylinderSt->chains[cylinderSt->chainNumber][cylinderSt->rank+1],scene); segment = cylinderNext->state->pos-cylinderSt->pos; length = segment.norm(); direction = segment/length; dist = direction.dot(branch); if(cylinderSt->rank>0){ if(distPrev>lengthPrev && dist<0){//the sphere is touching two cylinder who have an angle>180° betweenTwoCylinders = true; } } if (!betweenTwoCylinders && (cylinderSt->rank>0 or dist>0)) { if ( segment.dot(branch) >= segment.dot(segment) or dist<0) {//position after or before the cylinder //FIXME : scm->penetrationDepth=-1 is defined to workaround interactions never being erased when scm->isDuplicate=2 on the true interaction. if (isNew) return false; else if (scm->isDuplicate) { scm->isDuplicate=2; return true; } } } } //Check sphere-cylinder distance Vector3r projectedP = cylinderSt->pos+shift2 + direction*dist; branch = projectedP-sphereSt->pos; if(isLast || (cylinderSt->rank==0 && dist<0)){branch = cylinderSt->pos - sphereSt->pos;} if (branch.norm() > sphere->radius+cylinder->radius) { if (isNew) return false; else if (scm->isDuplicate){ scm->isDuplicate=2; return true; } } if (!isNew) scm->isDuplicate = false;//reset here at each step, and recompute below //if there is a contact with the previous element in the chain, consider this one a duplicate and push data to the new contact. Two interactions will share the same geometry and physics during a timestep. if (!betweenTwoCylinders && cylinderSt->rank>0 && dist<0) { if (!isNew) { const shared_ptr intr = scene->interactions->find(c->id1,cylinderSt->chains[cylinderSt->chainNumber][cylinderSt->rank-1]); if(!intr) {cout<<"Skipping contact because collider didn't found the previous cylinder"<geom = c->geom; intr->phys = c->phys; scm=YADE_PTR_CAST(c->geom); scm->isDuplicate = 2; scm->trueInt = cylinderSt->chains[cylinderSt->chainNumber][cylinderSt->rank-1]; isNew = false; return true; } else scm->isDuplicate=true; scm->trueInt = cylinderSt->chains[cylinderSt->chainNumber][cylinderSt->rank-1]; } //similarly, make sure there is no contact with the next element in the chain //else if (!isLast && dist>length) { if (!betweenTwoCylinders && !isLast && dist>=length) { if (!isNew) { const shared_ptr intr = scene->interactions->find(c->id1,cylinderSt->chains[cylinderSt->chainNumber][cylinderSt->rank+1]); if(!intr) {cout<<"Skipping contact because collider didn't found the next cylinder."<geom = c->geom; intr->phys = c->phys; scm=YADE_PTR_CAST(c->geom); scm->isDuplicate = 2; scm->trueInt = cylinderSt->chains[cylinderSt->chainNumber][cylinderSt->rank+1]; isNew = false; return true; } else scm->isDuplicate=true; scm->trueInt = cylinderSt->chains[cylinderSt->chainNumber][cylinderSt->rank+1]; } //We didn't find any special case, do normal geometry definition if (isNew) { scm=shared_ptr(new CylScGeom6D()); c->geom=scm;} scm->radius1=sphere->radius; scm->radius2=cylinder->radius; if (!isLast && !scm->id3) scm->id3=cylinderSt->chains[cylinderSt->chainNumber][cylinderSt->rank+1]; scm->start=cylinderSt->pos+shift2; scm->end=scm->start+segment; //FIXME : there should be other checks without distanceFactor? if (dist<=0 || isLast) {//We have sphere-node contact Vector3r normal=(cylinderSt->pos+shift2)-sphereSt->pos; Real norm=normal.norm(); normal /=norm; scm->relPos=0; scm->onNode=true; scm->relPos=0; scm->penetrationDepth=sphere->radius+cylinder->radius-norm; scm->contactPoint=sphereSt->pos+(sphere->radius-0.5*scm->penetrationDepth)*normal; scm->precompute(state1,state2,scene,c,normal,isNew,shift2,true);//use sphere-sphere precompute (a node is a sphere) } else {//we have sphere-cylinder contact scm->onNode=false; scm->relPos=dist/length; Real norm=branch.norm(); Vector3r normal=branch/norm; scm->penetrationDepth= sphere->radius+cylinder->radius-norm; // define a virtual sphere at the projected center scm->fictiousState.pos = projectedP; scm->fictiousState.vel = (1-scm->relPos)*cylinderSt->vel + scm->relPos*cylinderNext->state->vel; scm->fictiousState.angVel = ((1-scm->relPos)*cylinderSt->angVel + scm->relPos*cylinderNext->state->angVel).dot(direction)*direction //twist part : interpolated + segment.cross(cylinderNext->state->vel - cylinderSt->vel);// non-twist part : defined from nodes velocities if (dist>length) { scm->penetrationDepth=sphere->radius+cylinder->radius-(cylinderSt->pos+segment-sphereSt->pos).norm(); //FIXME : handle contact jump on next element } scm->contactPoint = sphereSt->pos+normal*(sphere->radius-0.5*scm->penetrationDepth); scm->precompute(state1,scm->fictiousState,scene,c,branch/norm,isNew,shift2,true);//use sphere-sphere precompute (with a virtual sphere) } return true; } bool Ig2_Sphere_ChainedCylinder_CylScGeom6D::goReverse( const shared_ptr& cm1, const shared_ptr& cm2, const State& state1, const State& state2, const Vector3r& shift2, const bool& force, const shared_ptr& c) { return go(cm1,cm2,state2,state1,-shift2,force,c); } bool Ig2_ChainedCylinder_ChainedCylinder_ScGeom6D::go( const shared_ptr& cm1, const shared_ptr& cm2, const State& state1, const State& state2, const Vector3r& shift2, const bool& force, const shared_ptr& c) { const ChainedState *pChain1, *pChain2; pChain1=YADE_CAST(&state1); pChain2=YADE_CAST(&state2); unsigned int sizeChain1=pChain1->chains[pChain1->chainNumber].size(); unsigned int sizeChain2=pChain2->chains[pChain2->chainNumber].size(); if (!pChain1 || !pChain2) { cerr <<"cast failed8567"<rank- (int) pChain1->rank == -1); const ChainedState& bchain1 = revert? *pChain2 : *YADE_CAST(&state1); const ChainedState& bchain2 = revert? *pChain1 : *pChain2; ChainedCylinder *bs1=static_cast(revert? cm2.get():cm1.get()); bool isLast = bchain1.chains[bchain1.chainNumber].size()==(bchain1.rank+1) || bchain2.chains[bchain2.chainNumber].size()==(bchain2.rank+1); bool isNew = !c->geom; if (pChain2->chainNumber!=pChain1->chainNumber) { shared_ptr scm; if(isLast){ return false; } shared_ptr cylinderNext1=Body::byId(pChain1->chains[pChain1->chainNumber][pChain1->rank+1],scene); shared_ptr cylinderNext2=Body::byId(pChain2->chains[pChain2->chainNumber][pChain2->rank+1],scene); //cout<id1<<" "<id2<pos, a=cylinderNext1->state->pos-A , B=pChain2->pos , b=cylinderNext2->state->pos-B; Vector3r N=a.cross(b); Vector3r normal; if(N.norm()>1e-14){ dist=std::abs(N.dot(B-A)/(N.norm())); //distance between the two LINES. //But we need to check that the intersection point is inside the two SEGMENTS ... //Projection of B to have a common plan between the two segments. Vector3r projB1=B+dist*(N/(N.norm())) , projB2=B-dist*(N/(N.norm())); Real distB1A=(projB1-A).norm() , distB2A=(projB2-A).norm() ; Vector3r projB=(distB1A<=distB2A)*projB1 + (distB1A>distB2A)*projB2; int b1=0, b2=1; //base vectors used to compute the segment intersection (if N is aligned with an axis, we can't use this axis) if(std::abs(N[1])<1e-14 && std::abs(N[2])<1e-14){b1=1;b2=2;} if(std::abs(N[0])<1e-14 && std::abs(N[2])<1e-14){b1=0;b2=2;} Real det=a[b1]*b[b2]-a[b2]*b[b1]; if(std::abs(det)>1e-14){ //Check if the two segments are intersected (using k and m) k = (b[b2]*(projB[b1]-A[b1])+b[b1]*(A[b2]-projB[b2]))/det; m = (a[b1]*(-projB[b2]+A[b2])+a[b2]*(projB[b1]-A[b1]))/det; if( k<0.0 || k>=1.0 || m<0.0 || m>=1.0 ) { //so they are not intersected dist=NaN; if(k>=1){k=1; if(!(pChain1->rank>=sizeChain1-2))insideCyl1=0; } if(k<0){k=0; if(!(pChain1->rank==0))insideCyl1=0; } if(m>=1){m=1; if(!(pChain2->rank>=sizeChain2-2))insideCyl2=0; } if(m<0){m=0; if(!(pChain2->rank==0))insideCyl2=0; } } } else cout<<"Error in Ig2_ChainedCylinder_ChainedCylinder_ScGeom6D : det==0 !!!"<1 && pChain2->rank>=sizeChain2-2) || (h1<0 && pChain2->rank==0) || (h2>1 && pChain1->rank>=sizeChain1-2) || (h2<0 && pChain1->rank==0); if(edgeEdgeContact)colinearVectors=0; if( (0<=h1 and 1>h1) or (0<=h2 and 1>h2) or edgeEdgeContact){; //Do a perfectly flat contact } else return false;//Parallel lines but edge-edge contact } ChainedCylinder *cc1=static_cast(cm1.get()); ChainedCylinder *cc2=static_cast(cm2.get()); if(std::isnan(dist)){ //now if we didn't found a suitable distance because the segments don't cross each other, we try to find a sphere-cylinder distance. Vector3r pointsToCheck[4]={A,A+a,B,B+b}; Real resultDist=dist, resultProj=dist ; int whichCaseIsCloser=-1 ; for (int i=0;i<4;i++){ //loop on the 4 cylinder's extremities and look at the extremity-cylinder distance Vector3r S=pointsToCheck[i], C=(i<2)?B:A, vec=(i<2)?b:a; Vector3r CS=S-C; Real d=CS.dot(vec)/(vec.norm()); if(d<0.) resultDist=CS.norm(); else if(d>vec.norm()) resultDist=(C+vec-S).norm(); else resultDist=(CS.cross(vec)).norm()/(vec.norm()); if(dist>resultDist or std::isnan(dist)){dist=resultDist ; whichCaseIsCloser=i; resultProj=d;} } //we know which extremity may be in contact (i), so k and m are computed to generate the right fictiousStates. insideCyl1=1 ; insideCyl2=1; //FIXME:NATCHOS ! this should be reformulated if(whichCaseIsCloser==0 || whichCaseIsCloser==1){ k=whichCaseIsCloser==0?0:1; if(resultProj<=0){ m=0; if(!(pChain2->rank==0))insideCyl2=0;} else if(resultProj>b.norm()){ m=1; if(!(pChain2->rank>=sizeChain2-2))insideCyl2=0;} else m=resultProj/(b.norm()); if(isNew && whichCaseIsCloser==1 && !(pChain1->rank>=sizeChain1-2)) return false; } else{ m=whichCaseIsCloser==2?0:1; if(resultProj<=0){ k=0; if(!(pChain1->rank==0))insideCyl1=0;} else if(resultProj>a.norm()){ k=1; if(!(pChain1->rank>=sizeChain2-2))insideCyl1=0;} else k=resultProj/(a.norm()); if(isNew && whichCaseIsCloser==3 && !(pChain2->rank>=sizeChain2-2)) return false; } } if(isNew && dist>=cc1->radius + cc2->radius) return false; //if the contact had not yet occured, return false. //FIXME:the next line sometimes causes an error in the terminal, because instead of returning false here the contact should be correctly erased. if(insideCyl1==0 || insideCyl2==0) return false; //the contact may be duplicated ... else{ //else create the geometry. if(!isNew) scm=YADE_PTR_CAST(c->geom); else { scm=shared_ptr(new ChCylGeom6D()); c->geom=scm; } scm->relPos1=colinearVectors?0.5:k ; scm->relPos2=colinearVectors?0.5:m; scm->fictiousState1.pos=A + k*a; scm->fictiousState2.pos=B + m*b; scm->fictiousState1.vel = (1-k)*pChain1->vel + k*cylinderNext1->state->vel; scm->fictiousState2.vel = (1-m)*pChain2->vel + m*cylinderNext2->state->vel; Vector3r direction = a/(a.norm()); scm->fictiousState1.angVel = ((1-k)*pChain1->angVel + k*cylinderNext1->state->angVel).dot(direction)*direction //twist part : interpolated + a.cross(cylinderNext1->state->vel - pChain1->vel);// non-twist part : defined from nodes velocities direction = b/(b.norm()); scm->fictiousState2.angVel = ((1-m)*pChain2->angVel + m*cylinderNext2->state->angVel).dot(direction)*direction //twist part : interpolated + b.cross(cylinderNext2->state->vel - pChain2->vel);// non-twist part : defined from nodes velocities scm->contactPoint = 0.5*(scm->fictiousState1.pos+scm->fictiousState2.pos); normal= scm->fictiousState2.pos - scm->fictiousState1.pos; normal=normal/(normal.norm()); scm->penetrationDepth=cc1->radius+cc2->radius-dist; scm->radius1=cc1->radius; scm->radius2=cc2->radius; scm->precompute(scm->fictiousState1,scm->fictiousState2,scene,c,normal,isNew,shift2,true); return true; } } else if (bchain2.rank-bchain1.rank != 1) {/*cerr<<"Mutual contacts in same chain between not adjacent elements, not handled*/ return false;} else{ //contact between two Cylinders within the same chain. shared_ptr scm; if(!isNew) scm=YADE_PTR_CAST(c->geom); else { scm=shared_ptr(new ScGeom6D()); c->geom=scm; } Real length=(bchain2.pos-bchain1.pos).norm(); Vector3r segt =pChain2->pos-pChain1->pos; if(isNew) {/*scm->normal=scm->prevNormal=segt/length;*/bs1->initLength=length;} if (!halfLengthContacts){ scm->radius1=revert ? 0:bs1->initLength; scm->radius2=revert ? bs1->initLength:0; scm->contactPoint=bchain2.pos; } else { scm->radius1=scm->radius2=0.5*bs1->initLength; scm->contactPoint=0.5*(bchain2.pos+bchain1.pos); } scm->penetrationDepth=bs1->initLength-length; //bs1->segment used for fast BBs and projections + display bs1->segment= bchain2.pos-bchain1.pos; #ifdef YADE_OPENGL bs1->length=length; bs1->chainedOrientation.setFromTwoVectors(Vector3r::UnitZ(),bchain1.ori.conjugate()*segt); #endif scm->precompute(state1,state2,scene,c,segt/length,isNew,shift2,true); scm->precomputeRotations(state1,state2,isNew,false); //Set values that will be considered in Ip2 functor, geometry (precomputed) is really defined with values above scm->radius1 = scm->radius2 = bs1->initLength*0.5; return true; } } bool Ig2_ChainedCylinder_ChainedCylinder_ScGeom6D::goReverse( const shared_ptr& cm1, const shared_ptr& cm2, const State& state1, const State& state2, const Vector3r& shift2, const bool& force, const shared_ptr& c) { return go(cm2,cm1,state2,state1,-shift2,force,c); } #ifdef YADE_OPENGL //!################## RENDERING ##################### bool Gl1_Cylinder::wire; bool Gl1_Cylinder::glutNormalize; int Gl1_Cylinder::glutSlices; int Gl1_Cylinder::glutStacks; int Gl1_Cylinder::glCylinderList=-1; void Gl1_Cylinder::out( Quaternionr q ) { AngleAxisr aa(q); std::cout << " axis: " << aa.axis()[0] << " " << aa.axis()[1] << " " << aa.axis()[2] << ", angle: " << aa.angle() << " | "; } void Gl1_Cylinder::go(const shared_ptr& cm, const shared_ptr& ,bool wire2, const GLViewInfo&) { Real r=(static_cast(cm.get()))->radius; Real length=(static_cast(cm.get()))->length; //glMaterialv(GL_FRONT, GL_AMBIENT_AND_DIFFUSE, Vector3f(cm->color[0],cm->color[1],cm->color[2])); glColor3v(cm->color); if(glutNormalize) glPushAttrib(GL_NORMALIZE); // glPushMatrix(); Quaternionr shift = (static_cast(cm.get()))->chainedOrientation; if (wire || wire2) drawCylinder(true, r,length,shift); else drawCylinder(false, r,length,shift); if(glutNormalize) glPopAttrib(); // glPopMatrix(); return; } void Gl1_ChainedCylinder::go(const shared_ptr& cm, const shared_ptr& state,bool wire2, const GLViewInfo&) { Real r=(static_cast(cm.get()))->radius; Real length=(static_cast(cm.get()))->length; Quaternionr shift;// = (static_cast(cm.get()))->chainedOrientation; shift.setFromTwoVectors(Vector3r::UnitZ(),state->ori.conjugate()*(static_cast(cm.get()))->segment); glColor3v(cm->color); if(glutNormalize) glPushAttrib(GL_NORMALIZE); if (wire || wire2) drawCylinder(true, r,length,shift); else drawCylinder(false, r,length,shift); if(glutNormalize) glPopAttrib(); return; } void Gl1_Cylinder::drawCylinder(bool wire, Real radius, Real length, const Quaternionr& shift) const { glPushMatrix(); GLUquadricObj *quadObj = gluNewQuadric(); gluQuadricDrawStyle(quadObj, (GLenum) (wire ? GLU_SILHOUETTE : GLU_FILL)); gluQuadricNormals(quadObj, (GLenum) GLU_SMOOTH); gluQuadricOrientation(quadObj, (GLenum) GLU_OUTSIDE); AngleAxisr aa(shift); glRotatef(aa.angle()*180.0/Mathr::PI,aa.axis()[0],aa.axis()[1],aa.axis()[2]); gluCylinder(quadObj, radius, radius, length, glutSlices,glutStacks); gluQuadricOrientation(quadObj, (GLenum) GLU_INSIDE); glutSolidSphere(radius,glutSlices,glutStacks); glTranslatef(0.0,0.0,length); glutSolidSphere(radius,glutSlices,glutStacks); // gluDisk(quadObj,0.0,radius,glutSlices,_loops); gluDeleteQuadric(quadObj); glPopMatrix(); } //!################## BOUNDS FUNCTOR ##################### #endif void Bo1_Cylinder_Aabb::go(const shared_ptr& cm, shared_ptr& bv, const Se3r& se3, const Body* b){ Cylinder* cylinder = static_cast(cm.get()); if(!bv){ bv=shared_ptr(new Aabb); } Aabb* aabb=static_cast(bv.get()); if(!scene->isPeriodic){ const Vector3r& O = se3.position; Vector3r O2 = se3.position+se3.orientation*cylinder->segment; aabb->min=aabb->max=O; for (int k=0;k<3;k++){ aabb->min[k]=min(aabb->min[k],min(O[k],O2[k])-cylinder->radius); aabb->max[k]=max(aabb->max[k],max(O[k],O2[k])+cylinder->radius); } return; } } void Bo1_ChainedCylinder_Aabb::go(const shared_ptr& cm, shared_ptr& bv, const Se3r& se3, const Body* b){ ChainedCylinder* cylinder = static_cast(cm.get()); if(!bv){ bv=shared_ptr(new Aabb); } Aabb* aabb=static_cast(bv.get()); if(!scene->isPeriodic){ const Vector3r& O = se3.position; Vector3r O2 = O+cylinder->segment; //cout<<"O="<isDuplicate==2) return false;} } Real& un=geom->penetrationDepth; phys->normalForce=phys->kn*std::max(un,(Real) 0)*geom->normal; Vector3r& shearForce = geom->rotate(phys->shearForce); const Vector3r& shearDisp = geom->shearIncrement(); shearForce -= phys->ks*shearDisp; Real maxFs = phys->normalForce.squaredNorm()*std::pow(phys->tangensOfFrictionAngle,2); if (!scene->trackEnergy){//Update force but don't compute energy terms (see below)) // PFC3d SlipModel, is using friction angle. CoulombCriterion if( shearForce.squaredNorm() > maxFs ){ Real ratio = sqrt(maxFs) / shearForce.norm(); shearForce *= ratio;} } else { //almost the same with additional Vector3r instanciated for energy tracing, duplicated block to make sure there is no cost for the instanciation of the vector when traceEnergy==false if(shearForce.squaredNorm() > maxFs){ Real ratio = sqrt(maxFs) / shearForce.norm(); Vector3r trialForce=shearForce;//store prev force for definition of plastic slip //define the plastic work input and increment the total plastic energy dissipated shearForce *= ratio; Real dissip=((1/phys->ks)*(trialForce-shearForce))/*plastic disp*/ .dot(shearForce)/*active force*/; if(dissip>0) scene->energy->add(dissip,"plastDissip",plastDissipIx,/*reset*/false); } // compute elastic energy as well scene->energy->add(0.5*(phys->normalForce.squaredNorm()/phys->kn+phys->shearForce.squaredNorm()/phys->ks),"elastPotential",elastPotentialIx,/*reset at every timestep*/true); } if (!scene->isPeriodic) { Vector3r force = -phys->normalForce-shearForce; scene->forces.addForce(id1,force); scene->forces.addTorque(id1,(geom->radius1-0.5*geom->penetrationDepth)* geom->normal.cross(force)); //FIXME : include moment due to axis-contact distance in forces on node Vector3r twist = (geom->radius2-0.5*geom->penetrationDepth)* geom->normal.cross(force); scene->forces.addForce(id2,(geom->relPos-1)*force); scene->forces.addTorque(id2,(1-geom->relPos)*twist); if (geom->relPos) { //else we are on node (or on last node - and id3 is junk) scene->forces.addForce(geom->id3,(-geom->relPos)*force); scene->forces.addTorque(geom->id3,geom->relPos*twist);} } // applyForceAtContactPoint(-phys->normalForce-shearForce, geom->contactPoint, id1, de1->se3.position, id2, de2->se3.position); else {//FIXME : periodicity not implemented here : Vector3r force = -phys->normalForce-shearForce; scene->forces.addForce(id1,force); scene->forces.addForce(id2,-force); scene->forces.addTorque(id1,(geom->radius1-0.5*geom->penetrationDepth)* geom->normal.cross(force)); scene->forces.addTorque(id2,(geom->radius2-0.5*geom->penetrationDepth)* geom->normal.cross(force)); } return true; } bool Law2_CylScGeom6D_CohFrictPhys_CohesionMoment::go(shared_ptr& ig, shared_ptr& ip, Interaction* contact) { int id1 = contact->getId1(), id2 = contact->getId2(); CylScGeom6D* geom= YADE_CAST(ig.get()); CohFrictPhys* currentContactPhysics = YADE_CAST(ip.get()); Vector3r& shearForce = currentContactPhysics->shearForce; //force tangentielle if (contact->isFresh(scene)) shearForce = Vector3r::Zero(); //contact nouveau => force tengentielle = 0,0,0 Real un = geom->penetrationDepth; //un : interpenetration Real Fn = currentContactPhysics->kn*(un-currentContactPhysics->unp); //Fn : force normale if (geom->isDuplicate) { if (id2!=geom->trueInt) { //cerr<<"skip duplicate "<isDuplicate==2) return false;} } if (currentContactPhysics->fragile && (-Fn)> currentContactPhysics->normalAdhesion) { // BREAK due to tension return false; } else { if ((-Fn)> currentContactPhysics->normalAdhesion) {//normal plasticity Fn=-currentContactPhysics->normalAdhesion; currentContactPhysics->unp = un+currentContactPhysics->normalAdhesion/currentContactPhysics->kn; if (currentContactPhysics->unpMax && currentContactPhysics->unpunpMax) return false; } currentContactPhysics->normalForce = Fn*geom->normal; Vector3r& shearForce = geom->rotate(currentContactPhysics->shearForce); const Vector3r& dus = geom->shearIncrement(); //Linear elasticity giving "trial" shear force shearForce -= currentContactPhysics->ks*dus; Real Fs = currentContactPhysics->shearForce.norm(); Real maxFs = currentContactPhysics->shearAdhesion; if (!currentContactPhysics->cohesionDisablesFriction || maxFs==0) maxFs += Fn*currentContactPhysics->tangensOfFrictionAngle; maxFs = std::max((Real) 0, maxFs); if (Fs > maxFs) {//Plasticity condition on shear force if (currentContactPhysics->fragile && !currentContactPhysics->cohesionBroken) { currentContactPhysics->SetBreakingState(); maxFs = max((Real) 0, Fn*currentContactPhysics->tangensOfFrictionAngle); } maxFs = maxFs / Fs; shearForce *= maxFs; if (Fn<0) currentContactPhysics->normalForce = Vector3r::Zero();//Vector3r::Zero() } Vector3r force = -currentContactPhysics->normalForce-shearForce; if (!scene->isPeriodic) { scene->forces.addForce(id1,force); scene->forces.addTorque(id1,(geom->radius1-0.5*geom->penetrationDepth)* geom->normal.cross(force)); //FIXME : include moment due to axis-contact distance in forces on node Vector3r twist = (geom->radius2-0.5*geom->penetrationDepth)* geom->normal.cross(force); scene->forces.addForce(id2,(geom->relPos-1)*force); scene->forces.addTorque(id2,(1-geom->relPos)*twist); if (geom->relPos) { //else we are on node (or on last node - and id3 is junk) scene->forces.addForce(geom->id3,(-geom->relPos)*force); scene->forces.addTorque(geom->id3,geom->relPos*twist);} } // applyForceAtContactPoint(-phys->normalForce-shearForce, geom->contactPoint, id1, de1->se3.position, id2, de2->se3.position); else {//FIXME : periodicity not implemented here : scene->forces.addForce(id1,force); scene->forces.addForce(id2,-force); scene->forces.addTorque(id1,(geom->radius1-0.5*geom->penetrationDepth)* geom->normal.cross(force)); scene->forces.addTorque(id2,(geom->radius2-0.5*geom->penetrationDepth)* geom->normal.cross(force)); } //applyForceAtContactPoint(-currentContactPhysics->normalForce-shearForce, geom->contactPoint, id1, de1->se3.position, id2, de2->se3.position); } return true; } bool Law2_ChCylGeom6D_CohFrictPhys_CohesionMoment::go(shared_ptr& ig, shared_ptr& ip, Interaction* contact){ int id1 = contact->getId1(), id2 = contact->getId2(); ChCylGeom6D* geom= YADE_CAST(ig.get()); CohFrictPhys* currentContactPhysics = YADE_CAST(ip.get()); /* shared_ptr state1 = YADE_PTR_CAST (Body::byId(id1,scene)->state); const shared_ptr intr = scene->interactions->find(id1,id2+1); if(!intr) {cout<<"Skipping contact because collider didn't found the next cylinder."<geom = c->geom; intr->phys = c->phys; */ Vector3r& shearForce = currentContactPhysics->shearForce; //force tangentielle if (contact->isFresh(scene)) shearForce = Vector3r::Zero(); //contact nouveau => force tengentielle = 0,0,0 Real un = geom->penetrationDepth; //un : interpenetration Real Fn = currentContactPhysics->kn*(un-currentContactPhysics->unp); //Fn : force normale if (currentContactPhysics->fragile && (-Fn)> currentContactPhysics->normalAdhesion) return false; // BREAK due to tension else { if ((-Fn)> currentContactPhysics->normalAdhesion) {//normal plasticity Fn=-currentContactPhysics->normalAdhesion; currentContactPhysics->unp = un+currentContactPhysics->normalAdhesion/currentContactPhysics->kn; if (currentContactPhysics->unpMax && currentContactPhysics->unpunpMax) return false; } currentContactPhysics->normalForce = Fn*geom->normal; Vector3r& shearForce = geom->rotate(currentContactPhysics->shearForce); const Vector3r& dus = geom->shearIncrement(); //Linear elasticity giving "trial" shear force shearForce -= currentContactPhysics->ks*dus; Real Fs = currentContactPhysics->shearForce.norm(); Real maxFs = currentContactPhysics->shearAdhesion; if (!currentContactPhysics->cohesionDisablesFriction || maxFs==0) maxFs += Fn*currentContactPhysics->tangensOfFrictionAngle; maxFs = std::max((Real) 0, maxFs); if (Fs > maxFs) {//Plasticity condition on shear force if (currentContactPhysics->fragile && !currentContactPhysics->cohesionBroken) { currentContactPhysics->SetBreakingState(); maxFs = max((Real) 0, Fn*currentContactPhysics->tangensOfFrictionAngle); } maxFs = maxFs / Fs; shearForce *= maxFs; if (Fn<0) currentContactPhysics->normalForce = Vector3r::Zero();//Vector3r::Zero() } Vector3r force = -currentContactPhysics->normalForce-shearForce; //cout<<"id1="<getId1()<<" id2="<getId2()<<" normalForce="<normalForce<<" shearForce="<isPeriodic) { Vector3r twist1 = (geom->radius1-0.5*geom->penetrationDepth)* geom->normal.cross(force); Vector3r twist2 = (geom->radius2-0.5*geom->penetrationDepth)* geom->normal.cross(force); scene->forces.addForce(id1,(1-geom->relPos1)*force); scene->forces.addTorque(id1,(1-geom->relPos1)*twist1); scene->forces.addForce(id2,-(1-geom->relPos2)*force); scene->forces.addTorque(id2,(1-geom->relPos2)*twist2); scene->forces.addForce(id1+1,geom->relPos1*force); scene->forces.addTorque(id1+1,geom->relPos1*twist1); scene->forces.addForce(id2+1,-geom->relPos2*force); scene->forces.addTorque(id2+1,geom->relPos2*twist2); } // applyForceAtContactPoint(-phys->normalForce-shearForce, geom->contactPoint, id1, de1->se3.position, id2, de2->se3.position); else {//FIXME : periodicity not implemented here : scene->forces.addForce(id1,force); scene->forces.addForce(id2,-force); scene->forces.addTorque(id1,(geom->radius1-0.5*geom->penetrationDepth)* geom->normal.cross(force)); scene->forces.addTorque(id2,(geom->radius2-0.5*geom->penetrationDepth)* geom->normal.cross(force)); } } return true; } trunk-2018.02b/pkg/common/Cylinder.hpp000066400000000000000000000477041324306050200175350ustar00rootroot00000000000000// 2011 © Bruno Chareyre // 2012 © Kneib Francois #pragma once #include #include #include #include #include #ifdef YADE_OPENGL #include #endif #include #include class Cylinder: public Sphere{ public: // Cylinder(Real _radius, Real _length): length(_length) { /*segment=Vector3r(0,0,1)*_length;*/ radius=_radius; createIndex(); } virtual ~Cylinder (); YADE_CLASS_BASE_DOC_ATTRS_CTOR(Cylinder,Sphere,"Geometry of a cylinder, as Minkowski sum of line and sphere.", // ((Real,radius,NaN,,"Radius [m]")) ((Real,length,NaN,,"Length [m]")) ((Vector3r,segment,Vector3r::Zero(),,"Length vector")), createIndex(); /*ctor*/ segment=Vector3r(0,0,1)*length; ); REGISTER_CLASS_INDEX(Cylinder,Sphere); }; REGISTER_SERIALIZABLE(Cylinder); class ChainedCylinder: public Cylinder{ public: // ChainedCylinder(Real _radius, Real _length);/*: Cylinder(_radius,_length){}*/ virtual ~ChainedCylinder (); //Keep pointers or copies of connected states? // ChainedState st1, st2; YADE_CLASS_BASE_DOC_ATTRS_CTOR(ChainedCylinder,Cylinder,"Geometry of a deformable chained cylinder, using geometry :yref:`Cylinder`.", ((Real,initLength,0,,"tensile-free length, used as reference for tensile strain")) ((Quaternionr,chainedOrientation,Quaternionr::Identity(),,"Deviation of node1 orientation from node-to-node vector")) ,createIndex();/*ctor*/ // state=shared_ptr(new ChainedState); ); REGISTER_CLASS_INDEX(ChainedCylinder,Cylinder); }; REGISTER_SERIALIZABLE(ChainedCylinder); class ChainedState: public State{ public: static vector > chains; static unsigned int currentChain; vector barContacts; vector nodeContacts; // shared_ptr statePrev; virtual ~ChainedState (); void addToChain(Body::id_t bodyId) { if (chains.size()<=currentChain) chains.resize(currentChain+1); chainNumber=currentChain; rank=chains[currentChain].size(); chains[currentChain].push_back(bodyId); bId=bodyId; // if (rank>0) statePrev = Body::byId(chains[chainNumber][rank-1],scene)->state; } void postLoad (ChainedState&){ if (bId<0) return;//state has not been chained yet if (chains.size()<=currentChain) chains.resize(currentChain+1); if (chains[currentChain].size()<=rank) chains[currentChain].resize(rank+1); chains[currentChain][rank]=bId; // if (rank>0) statePrev = Body::byId(chains[chainNumber][rank-1],scene)->state; } YADE_CLASS_BASE_DOC_ATTRS_INIT_CTOR_PY(ChainedState,State,"State of a chained bodies, containing information on connectivity in order to track contacts jumping over contiguous elements. Chains are 1D lists from which id of chained bodies are retrieved via :yref:`rank` and :yref:`chainNumber`.", ((unsigned int,rank,0,,"rank in the chain.")) ((unsigned int,chainNumber,0,,"chain id.")) ((int,bId,-1,,"id of the body containing - for postLoad operations only.")) , /* additional initializers */ /* ((pos,se3.position)) ((ori,se3.orientation)),*/ , /* ctor */ createIndex(); , /*py*/ // .def_readwrite("chains",&ChainedState::chains,"documentation") .def_readwrite("currentChain",&ChainedState::currentChain,"Current active chain (where newly created chained bodies will be appended).") .def("addToChain",&ChainedState::addToChain,(boost::python::arg("bodyId")),"Add body to current active chain") ); REGISTER_CLASS_INDEX(ChainedState,State); }; REGISTER_SERIALIZABLE(ChainedState); class Ig2_Sphere_ChainedCylinder_CylScGeom: public IGeomFunctor{ public: // virtual ~Ig2_Sphere_ChainedCylinder_CylScGeom (); virtual bool go(const shared_ptr& cm1, const shared_ptr& cm2, const State& state1, const State& state2, const Vector3r& shift2, const bool& force, const shared_ptr& c); virtual bool goReverse( const shared_ptr& cm1, const shared_ptr& cm2, const State& state1, const State& state2, const Vector3r& shift2, const bool& force, const shared_ptr& c); YADE_CLASS_BASE_DOC_ATTRS(Ig2_Sphere_ChainedCylinder_CylScGeom,IGeomFunctor,"Create/update a :yref:`ScGeom` instance representing intersection of two :yref:`Spheres`.", ((Real,interactionDetectionFactor,1,,"Enlarge both radii by this factor (if >1), to permit creation of distant interactions.")) ); FUNCTOR2D(Sphere,ChainedCylinder); DEFINE_FUNCTOR_ORDER_2D(Sphere,ChainedCylinder); }; REGISTER_SERIALIZABLE(Ig2_Sphere_ChainedCylinder_CylScGeom); class Ig2_Sphere_ChainedCylinder_CylScGeom6D: public Ig2_Sphere_ChainedCylinder_CylScGeom { public: virtual bool go(const shared_ptr& cm1, const shared_ptr& cm2, const State& state1, const State& state2, const Vector3r& shift2, const bool& force, const shared_ptr& c); virtual bool goReverse( const shared_ptr& cm1, const shared_ptr& cm2, const State& state1, const State& state2, const Vector3r& shift2, const bool& force, const shared_ptr& c); YADE_CLASS_BASE_DOC_ATTRS(Ig2_Sphere_ChainedCylinder_CylScGeom6D,Ig2_Sphere_ChainedCylinder_CylScGeom,"Create/update a :yref:`ScGeom6D` instance representing the geometry of a contact point between two :yref:`Spheres`, including relative rotations.", ((bool,updateRotations,false,,"Precompute relative rotations. Turning this false can speed up simulations when rotations are not needed in constitutive laws (e.g. when spheres are compressed without cohesion and moment in early stage of a triaxial test), but is not foolproof. Change this value only if you know what you are doing.")) ((bool,creep,false,,"Substract rotational creep from relative rotation. The rotational creep :yref:`ScGeom6D::twistCreep` is a quaternion and has to be updated inside a constitutive law, see for instance :yref:`Law2_ScGeom6D_CohFrictPhys_CohesionMoment`." )) ); FUNCTOR2D(Sphere,ChainedCylinder); // needed for the dispatcher, even if it is symmetric DEFINE_FUNCTOR_ORDER_2D(Sphere,ChainedCylinder); }; REGISTER_SERIALIZABLE(Ig2_Sphere_ChainedCylinder_CylScGeom6D); class Ig2_ChainedCylinder_ChainedCylinder_ScGeom6D: public IGeomFunctor{ public: // virtual ~Ig2_ChainedCylinder_ChainedCylinder_ScGeom6D () {}; virtual bool go(const shared_ptr& cm1, const shared_ptr& cm2, const State& state1, const State& state2, const Vector3r& shift2, const bool& force, const shared_ptr& c); virtual bool goReverse( const shared_ptr& cm1, const shared_ptr& cm2, const State& state1, const State& state2, const Vector3r& shift2, const bool& force, const shared_ptr& c); YADE_CLASS_BASE_DOC_ATTRS(Ig2_ChainedCylinder_ChainedCylinder_ScGeom6D,IGeomFunctor,"Create/update a :yref:`ScGeom` instance representing connexion between :yref:`chained cylinders`.", ((Real,interactionDetectionFactor,1,,"Enlarge both radii by this factor (if >1), to permit creation of distant interactions.")) ((bool,halfLengthContacts,true,,"If True, Cylinders nodes interact like spheres of radius 0.5*length, else one node has size length while the other has size 0. The difference is mainly the locus of rotation definition.")) ); FUNCTOR2D(ChainedCylinder,ChainedCylinder); // needed for the dispatcher, even if it is symmetric DEFINE_FUNCTOR_ORDER_2D(ChainedCylinder,ChainedCylinder); }; REGISTER_SERIALIZABLE(Ig2_ChainedCylinder_ChainedCylinder_ScGeom6D); #ifdef YADE_OPENGL class Gl1_Cylinder : public GlShapeFunctor{ private: static int glCylinderList; void subdivideTriangle(Vector3r& v1,Vector3r& v2,Vector3r& v3, int depth); void drawCylinder(bool wire, Real radius, Real length, const Quaternionr& shift=Quaternionr::Identity()) const; void initGlLists(void); public: virtual void go(const shared_ptr&, const shared_ptr&,bool,const GLViewInfo&); void out( Quaternionr q ); YADE_CLASS_BASE_DOC_STATICATTRS(Gl1_Cylinder,GlShapeFunctor,"Renders :yref:`Cylinder` object", ((bool,wire,false,,"Only show wireframe (controlled by ``glutSlices`` and ``glutStacks``.")) ((bool,glutNormalize,true,,"Fix normals for non-wire rendering")) ((int,glutSlices,8,,"Number of sphere slices.")) ((int,glutStacks,4,,"Number of sphere stacks.")) ); RENDERS(Cylinder); friend class Gl1_ChainedCylinder; }; //!This doesn't work : the 1D dispatcher will pick Gl1_Cylinder to display ChainedCylinders, workaround : add shift to cylinders (should be a variable of chained cylinders only). class Gl1_ChainedCylinder : public Gl1_Cylinder{ public: virtual void go(const shared_ptr&, const shared_ptr& state, bool,const GLViewInfo&); YADE_CLASS_BASE_DOC(Gl1_ChainedCylinder,Gl1_Cylinder,"Renders :yref:`ChainedCylinder` object including a shift for compensating flexion." ); RENDERS(ChainedCylinder); }; /* class Gl1_ChainedCylinder : public GlShapeFunctor{ private: static int glCylinderList; void subdivideTriangle(Vector3r& v1,Vector3r& v2,Vector3r& v3, int depth); void drawCylinder(bool wire, Real radius, Real length, const Quaternionr& shift=Quaternionr::Identity()) const; void initGlLists(void); public: virtual void go(const shared_ptr&, const shared_ptr&,bool,const GLViewInfo&); YADE_CLASS_BASE_DOC_STATICATTRS(Gl1_ChainedCylinder,GlShapeFunctor,"Renders :yref:`ChainedCylinder` object including a shift for compensating flexion.", ((bool,wire,false,,"Only show wireframe (controlled by ``glutSlices`` and ``glutStacks``.")) ((bool,glutNormalize,true,,"Fix normals for non-wire rendering")) ((int,glutSlices,8,,"Number of sphere slices.")) ((int,glutStacks,4,,"Number of sphere stacks.")) ); RENDERS(ChainedCylinder); };*/ #endif class Bo1_Cylinder_Aabb : public BoundFunctor { public : void go(const shared_ptr& cm, shared_ptr& bv, const Se3r&, const Body*); FUNCTOR1D(Cylinder); YADE_CLASS_BASE_DOC_ATTRS(Bo1_Cylinder_Aabb,BoundFunctor,"Functor creating :yref:`Aabb` from :yref:`Cylinder`.", ((Real,aabbEnlargeFactor,((void)"deactivated",-1),,"Relative enlargement of the bounding box; deactivated if negative.\n\n.. note::\n\tThis attribute is used to create distant interaction, but is only meaningful with an :yref:`IGeomFunctor` which will not simply discard such interactions: :yref:`Ig2_Cylinder_Cylinder_ScGeom::interactionDetectionFactor` should have the same value as :yref:`aabbEnlargeFactor`.")) ); }; REGISTER_SERIALIZABLE(Bo1_Cylinder_Aabb); class Bo1_ChainedCylinder_Aabb : public BoundFunctor { public : void go(const shared_ptr& cm, shared_ptr& bv, const Se3r&, const Body*); FUNCTOR1D(ChainedCylinder); YADE_CLASS_BASE_DOC_ATTRS(Bo1_ChainedCylinder_Aabb,BoundFunctor,"Functor creating :yref:`Aabb` from :yref:`ChainedCylinder`.", ((Real,aabbEnlargeFactor,((void)"deactivated",-1),,"Relative enlargement of the bounding box; deactivated if negative.\n\n.. note::\n\tThis attribute is used to create distant interaction, but is only meaningful with an :yref:`IGeomFunctor` which will not simply discard such interactions: :yref:`Ig2_Cylinder_Cylinder_ScGeom::interactionDetectionFactor` should have the same value as :yref:`aabbEnlargeFactor`.")) ); }; REGISTER_SERIALIZABLE(Bo1_ChainedCylinder_Aabb); class Law2_CylScGeom_FrictPhys_CundallStrack: public LawFunctor{ public: //OpenMPAccumulator plasticDissipation; virtual bool go(shared_ptr& _geom, shared_ptr& _phys, Interaction* I); //Real elasticEnergy (); //Real getPlasticDissipation(); //void initPlasticDissipation(Real initVal=0); YADE_CLASS_BASE_DOC_ATTRS_CTOR_PY(Law2_CylScGeom_FrictPhys_CundallStrack,LawFunctor,"Law for linear compression, and Mohr-Coulomb plasticity surface without cohesion.\nThis law implements the classical linear elastic-plastic law from [CundallStrack1979]_ (see also [Pfc3dManual30]_). The normal force is (with the convention of positive tensile forces) $F_n=\\min(k_n u_n, 0)$. The shear force is $F_s=k_s u_s$, the plasticity condition defines the maximum value of the shear force : $F_s^{\\max}=F_n\\tan(\\phi)$, with $\\phi$ the friction angle.\n\n.. note::\n This law uses :yref:`ScGeom`.\n\n.. note::\n This law is well tested in the context of triaxial simulation, and has been used for a number of published results (see e.g. [Scholtes2009b]_ and other papers from the same authors). It is generalised by :yref:`Law2_ScGeom6D_CohFrictPhys_CohesionMoment`, which adds cohesion and moments at contact.", ((bool,neverErase,false,,"Keep interactions even if particles go away from each other (only in case another constitutive law is in the scene, e.g. :yref:`Law2_ScGeom_CapillaryPhys_Capillarity`)")) ((bool,traceEnergy,false,Attr::hidden,"Define the total energy dissipated in plastic slips at all contacts.")) ((int,plastDissipIx,-1,(Attr::hidden|Attr::noSave),"Index for plastic dissipation (with O.trackEnergy)")) ((int,elastPotentialIx,-1,(Attr::hidden|Attr::noSave),"Index for elastic potential energy (with O.trackEnergy)")) ,, //.def("elasticEnergy",&Law2_ScGeom_FrictPhys_CundallStrack::elasticEnergy,"Compute and return the total elastic energy in all \"FrictPhys\" contacts") //.def("plasticDissipation",&Law2_ScGeom_FrictPhys_CundallStrack::getPlasticDissipation,"Total energy dissipated in plastic slips at all FrictPhys contacts. Computed only if :yref:`Law2_ScGeom_FrictPhys_CundallStrack::traceEnergy` is true.") //.def("initPlasticDissipation",&Law2_ScGeom_FrictPhys_CundallStrack::initPlasticDissipation,"Initialize cummulated plastic dissipation to a value (0 by default).") ); FUNCTOR2D(CylScGeom,FrictPhys); }; REGISTER_SERIALIZABLE(Law2_CylScGeom_FrictPhys_CundallStrack); class Law2_CylScGeom6D_CohFrictPhys_CohesionMoment: public LawFunctor { public: //OpenMPAccumulator plasticDissipation; virtual bool go(shared_ptr& _geom, shared_ptr& _phys, Interaction* I); //Real elasticEnergy (); //Real getPlasticDissipation(); //void initPlasticDissipation(Real initVal=0); YADE_CLASS_BASE_DOC_ATTRS_CTOR_PY(Law2_CylScGeom6D_CohFrictPhys_CohesionMoment,LawFunctor,"This law generalises :yref:`Law2_CylScGeom_FrictPhys_CundallStrack` by adding cohesion and moments at contact.", ((bool,neverErase,false,,"Keep interactions even if particles go away from each other (only in case another constitutive law is in the scene, e.g. :yref:`Law2_ScGeom_CapillaryPhys_Capillarity`)")) ((bool,traceEnergy,false,Attr::hidden,"Define the total energy dissipated in plastic slips at all contacts.")) ((int,plastDissipIx,-1,(Attr::hidden|Attr::noSave),"Index for plastic dissipation (with O.trackEnergy)")) ((int,elastPotentialIx,-1,(Attr::hidden|Attr::noSave),"Index for elastic potential energy (with O.trackEnergy)")) ((bool,always_use_moment_law,false,,"If true, use bending/twisting moments at all contacts. If false, compute moments only for cohesive contacts.")) ((bool,shear_creep,false,,"activate creep on the shear force, using :yref:`CohesiveFrictionalContactLaw::creep_viscosity`.")) ((bool,twist_creep,false,,"activate creep on the twisting moment, using :yref:`CohesiveFrictionalContactLaw::creep_viscosity`.")) ((bool,useIncrementalForm,false,,"use the incremental formulation to compute bending and twisting moments. Creep on the twisting moment is not included in such a case.")) ((Real,creep_viscosity,1,,"creep viscosity [Pa.s/m]. probably should be moved to Ip2_CohFrictMat_CohFrictMat_CohFrictPhys...")) ,, ); FUNCTOR2D(CylScGeom6D,CohFrictPhys); DECLARE_LOGGER; }; REGISTER_SERIALIZABLE(Law2_CylScGeom6D_CohFrictPhys_CohesionMoment); class Law2_ChCylGeom6D_CohFrictPhys_CohesionMoment: public LawFunctor { public: //OpenMPAccumulator plasticDissipation; virtual bool go(shared_ptr& _geom, shared_ptr& _phys, Interaction* I); YADE_CLASS_BASE_DOC_ATTRS_CTOR_PY(Law2_ChCylGeom6D_CohFrictPhys_CohesionMoment,LawFunctor,"Law for linear compression, and Mohr-Coulomb plasticity surface without cohesion.\nThis law implements the classical linear elastic-plastic law from [CundallStrack1979]_ (see also [Pfc3dManual30]_). The normal force is (with the convention of positive tensile forces) $F_n=\\min(k_n u_n, 0)$. The shear force is $F_s=k_s u_s$, the plasticity condition defines the maximum value of the shear force : $F_s^{\\max}=F_n\\tan(\\phi)$, with $\\phi$ the friction angle.\n\n.. note::\n This law is well tested in the context of triaxial simulation, and has been used for a number of published results (see e.g. [Scholtes2009b]_ and other papers from the same authors). It is generalised by :yref:`Law2_ScGeom6D_CohFrictPhys_CohesionMoment`, which adds cohesion and moments at contact.", ((bool,neverErase,false,,"Keep interactions even if particles go away from each other (only in case another constitutive law is in the scene, e.g. :yref:`Law2_ScGeom_CapillaryPhys_Capillarity`)")) ((bool,traceEnergy,false,Attr::hidden,"Define the total energy dissipated in plastic slips at all contacts.")) ((int,plastDissipIx,-1,(Attr::hidden|Attr::noSave),"Index for plastic dissipation (with O.trackEnergy)")) ((int,elastPotentialIx,-1,(Attr::hidden|Attr::noSave),"Index for elastic potential energy (with O.trackEnergy)")) ((bool,always_use_moment_law,false,,"If true, use bending/twisting moments at all contacts. If false, compute moments only for cohesive contacts.")) ((bool,shear_creep,false,,"activate creep on the shear force, using :yref:`CohesiveFrictionalContactLaw::creep_viscosity`.")) ((bool,twist_creep,false,,"activate creep on the twisting moment, using :yref:`CohesiveFrictionalContactLaw::creep_viscosity`.")) ((bool,useIncrementalForm,false,,"use the incremental formulation to compute bending and twisting moments. Creep on the twisting moment is not included in such a case.")) ((Real,creep_viscosity,1,,"creep viscosity [Pa.s/m]. probably should be moved to Ip2_CohFrictMat_CohFrictMat_CohFrictPhys...")) ,, ); FUNCTOR2D(ChCylGeom6D,CohFrictPhys); DECLARE_LOGGER; }; REGISTER_SERIALIZABLE(Law2_ChCylGeom6D_CohFrictPhys_CohesionMoment); // Keep this : Cylinders and ChainedCylinders will have different centers maybe. // class Bo1_ChainedCylinder_Aabb : public Bo1_Cylinder_Aabb // { // public : // void go(const shared_ptr& cm, shared_ptr& bv, const Se3r&, const Body*); // FUNCTOR1D(ChainedCylinder); // YADE_CLASS_BASE_DOC_ATTRS(Bo1_ChainedCylinder_Aabb,Bo1_Cylinder_Aabb,"Functor creating :yref:`Aabb` from :yref:`Cylinder`.", // ((Real,aabbEnlargeFactor,((void)"deactivated",-1),,"Relative enlargement of the bounding box; deactivated if negative.\n\n.. note::\n\tThis attribute is used to create distant interaction, but is only meaningful with an :yref:`IGeomFunctor` which will not simply discard such interactions: :yref:`Ig2_Cylinder_Cylinder_Dem3DofGeom::distFactor` / :yref:`Ig2_Cylinder_Cylinder_ScGeom::interactionDetectionFactor` should have the same value as :yref:`aabbEnlargeFactor`.")) // ); // }; #ifdef YADE_OPENGL REGISTER_SERIALIZABLE(Gl1_Cylinder); REGISTER_SERIALIZABLE(Gl1_ChainedCylinder); #endif trunk-2018.02b/pkg/common/Dispatching.cpp000066400000000000000000000200441324306050200202000ustar00rootroot00000000000000#include YADE_PLUGIN((BoundFunctor)(IGeomFunctor)(IPhysFunctor)(LawFunctor)(BoundDispatcher)(IGeomDispatcher)(IPhysDispatcher)(LawDispatcher)); BoundFunctor::~BoundFunctor(){}; IGeomFunctor::~IGeomFunctor(){}; IPhysFunctor::~IPhysFunctor(){}; LawFunctor::~LawFunctor(){}; /******************************************************************** BoundDispatcher *********************************************************************/ CREATE_LOGGER(BoundDispatcher); void BoundDispatcher::action() { updateScenePtr(); shared_ptr& bodies = scene->bodies; const long numBodies=(long)bodies->size(); #ifdef YADE_OPENMP #pragma omp parallel for num_threads(ompThreads>0 ? min(ompThreads,omp_get_max_threads()) : omp_get_max_threads()) #endif for(int id=0; idexists(id)) continue; // don't delete this check - Janek const shared_ptr& b=(*bodies)[id]; processBody(b); } } void BoundDispatcher::processBody(const shared_ptr& b) { shared_ptr& shape=b->shape; if(!b->isBounded() || !shape) return; if(b->bound) { Real& sweepLength = b->bound->sweepLength; if (targetInterv>=0) { Vector3r disp = b->state->pos-b->bound->refPos; Real dist = max(std::abs(disp[0]),max(std::abs(disp[1]),std::abs(disp[2]))); if (dist){ Real newLength = dist*targetInterv/(scene->iter-b->bound->lastUpdateIter); newLength = max(0.9*sweepLength,newLength);//don't decrease size too fast to prevent time consuming oscillations sweepLength=max(minSweepDistFactor*sweepDist,min(newLength,sweepDist));} else sweepLength=0; } else sweepLength=sweepDist; } #ifdef BV_FUNCTOR_CACHE if(!shape->boundFunctor){ shape->boundFunctor=this->getFunctor1D(shape); if(!shape->boundFunctor) return; } shape->boundFunctor->go(shape,b->bound,b->state->se3,b.get()); #else operator()(shape,b->bound,b->state->se3,b.get()); #endif if(!b->bound) return; // the functor did not create new bound b->bound->refPos=b->state->pos; b->bound->lastUpdateIter=scene->iter; const Real& sweepLength = b->bound->sweepLength; if(sweepLength>0){ Aabb* aabb=YADE_CAST(b->bound.get()); aabb->min-=Vector3r(sweepLength,sweepLength,sweepLength); aabb->max+=Vector3r(sweepLength,sweepLength,sweepLength); } } /******************************************************************** IGeomDispatcher *********************************************************************/ CREATE_LOGGER(IGeomDispatcher); shared_ptr IGeomDispatcher::explicitAction(const shared_ptr& b1, const shared_ptr& b2, bool force){ scene=Omega::instance().getScene().get(); // to make sure if called from outside of the loop Vector3i cellDist=Vector3i::Zero(); if(scene->isPeriodic) { for(int i=0; i<3; i++) cellDist[i]=-(int)((b2->state->pos[i]-b1->state->pos[i])/scene->cell->getSize()[i]+.5); } Vector3r shift2=scene->cell->hSize*cellDist.cast(); updateScenePtr(); if(force){ assert(b1->shape && b2->shape); shared_ptr I(new Interaction(b1->getId(),b2->getId())); I->cellDist=cellDist; // FIXME: this code is more or less duplicated from InteractionLoop :-( bool swap=false; I->functorCache.geom=getFunctor2D(b1->shape,b2->shape,swap); if(!I->functorCache.geom) throw invalid_argument("IGeomDispatcher::explicitAction could not dispatch for given types ("+b1->shape->getClassName()+","+b2->shape->getClassName()+")."); if(swap){I->swapOrder();} const shared_ptr& b1=Body::byId(I->getId1(),scene); const shared_ptr& b2=Body::byId(I->getId2(),scene); bool succ=I->functorCache.geom->go(b1->shape,b2->shape,*b1->state,*b2->state,shift2,/*force*/true,I); if(!succ) throw logic_error("Functor "+I->functorCache.geom->getClassName()+"::go returned false, even if asked to force IGeom creation. Please report bug."); return I; } else { shared_ptr I(new Interaction(b1->getId(),b2->getId())); I->cellDist=cellDist; b1->shape && b2->shape && I->functorCache.geom->go(b1->shape,b2->shape,*b1->state,*b2->state,shift2,/*force*/true,I); return I; } } void IGeomDispatcher::action(){ updateScenePtr(); shared_ptr& bodies = scene->bodies; const bool isPeriodic(scene->isPeriodic); Matrix3r cellHsize; if(isPeriodic) cellHsize=scene->cell->hSize; bool removeUnseenIntrs=(scene->interactions->iterColliderLastRun>=0 && scene->interactions->iterColliderLastRun==scene->iter); #ifdef YADE_OPENMP const long size=scene->interactions->size(); #pragma omp parallel for for(long i=0; i& I=(*scene->interactions)[i]; #else FOREACH(const shared_ptr& I, *scene->interactions){ #endif if(removeUnseenIntrs && !I->isReal() && I->iterLastSeeniter) { scene->interactions->requestErase(I); continue; } const shared_ptr& b1=(*bodies)[I->getId1()]; const shared_ptr& b2=(*bodies)[I->getId2()]; if(!b1 || !b2){ LOG_DEBUG("Body #"<<(b1?I->getId2():I->getId1())<<" vanished, erasing intr #"<getId1()<<"+#"<getId2()<<"!"); scene->interactions->requestErase(I); continue; } bool wasReal=I->isReal(); if (!b1->shape || !b2->shape) { assert(!wasReal); continue; } // some bodies do not have shape bool geomCreated; //const bool forceFalse=false; if(!isPeriodic){ geomCreated=I->functorCache.geom->go(b1->shape,b2->shape, *b1->state, *b2->state, Vector3r::Zero(), /*force*/false, I); } else{ Vector3r shift2=cellHsize*I->cellDist.cast(); geomCreated=I->functorCache.geom->go(b1->shape,b2->shape,*b1->state,*b2->state,shift2,/*force*/false,I); } // reset && erase interaction that existed but now has no geometry anymore if(wasReal && !geomCreated){ scene->interactions->requestErase(I); } } } /******************************************************************** IPhysDispatcher *********************************************************************/ void IPhysDispatcher::explicitAction(shared_ptr& pp1, shared_ptr& pp2, shared_ptr& I){ updateScenePtr(); if(!I->geom) throw invalid_argument(string(__FILE__)+": explicitAction received interaction without geom."); if(!I->functorCache.phys){ bool dummy; I->functorCache.phys=getFunctor2D(pp1,pp2,dummy); if(!I->functorCache.phys) throw invalid_argument("IPhysDispatcher::explicitAction did not find a suitable dispatch for types "+pp1->getClassName()+" and "+pp2->getClassName()); I->functorCache.phys->go(pp1,pp2,I); } } void IPhysDispatcher::action() { updateScenePtr(); shared_ptr& bodies = scene->bodies; #ifdef YADE_OPENMP const long size=scene->interactions->size(); #pragma omp parallel for for(long i=0; i& interaction=(*scene->interactions)[i]; #else FOREACH(const shared_ptr& interaction, *scene->interactions){ #endif if(interaction->geom){ shared_ptr& b1 = (*bodies)[interaction->getId1()]; shared_ptr& b2 = (*bodies)[interaction->getId2()]; bool hadPhys=(interaction->phys.get() != 0); operator()(b1->material, b2->material, interaction); assert(interaction->phys); if(!hadPhys) interaction->iterMadeReal=scene->iter; } } } /******************************************************************** LawDispatcher *********************************************************************/ CREATE_LOGGER(LawDispatcher); void LawDispatcher::action(){ updateScenePtr(); #ifdef YADE_OPENMP const long size=scene->interactions->size(); #pragma omp parallel for for(long i=0; i& I=(*scene->interactions)[i]; #else FOREACH(shared_ptr I, *scene->interactions){ #endif if(I->isReal()){ assert(I->geom); assert(I->phys); operator()(I->geom,I->phys,I.get()); if(!I->isReal() && I->isFresh(scene)) LOG_ERROR("Law functor deleted interaction that was just created. Please report bug: either this message is spurious, or the functor (or something else) is buggy."); } } } trunk-2018.02b/pkg/common/Dispatching.hpp000066400000000000000000000121361324306050200202100ustar00rootroot00000000000000#pragma once #include #include #include #include #include #include #include #include #include #include #include /******** functors *********************/ class BoundFunctor: public Functor1D< /*dispatch types*/ Shape, /*return type*/ void , /*argument types*/ TYPELIST_4(const shared_ptr&, shared_ptr&, const Se3r&, const Body*) >{ public: virtual ~BoundFunctor(); YADE_CLASS_BASE_DOC(BoundFunctor,Functor,"Functor for creating/updating :yref:`Body::bound`."); }; REGISTER_SERIALIZABLE(BoundFunctor); class IGeomFunctor: public Functor2D< /*dispatch types*/ Shape,Shape, /*return type*/ bool, /*argument types*/ TYPELIST_7(const shared_ptr&, const shared_ptr&, const State&, const State&, const Vector3r&, const bool&, const shared_ptr&) >{ public: virtual ~IGeomFunctor(); // called before every step once, from InteractionLoop (used to set Scene::flags & Scene::LOCAL_COORDS) virtual void preStep(){}; YADE_CLASS_BASE_DOC(IGeomFunctor,Functor,"Functor for creating/updating :yref:`Interaction::geom` objects."); }; REGISTER_SERIALIZABLE(IGeomFunctor); class IPhysFunctor: public Functor2D< /*dispatch types*/ Material, Material, /*retrun type*/ void, /*argument types*/ TYPELIST_3(const shared_ptr&, const shared_ptr&, const shared_ptr&) >{ public: virtual ~IPhysFunctor(); YADE_CLASS_BASE_DOC(IPhysFunctor,Functor,"Functor for creating/updating :yref:`Interaction::phys` objects from :yref:`bodies' material` properties."); }; REGISTER_SERIALIZABLE(IPhysFunctor); class LawFunctor: public Functor2D< /*dispatch types*/ IGeom,IPhys, /*return type*/ bool, /*argument types*/ TYPELIST_3(shared_ptr&, shared_ptr&, Interaction*) >{ public: virtual ~LawFunctor(); /*! Convenience functions to get forces/torques quickly. */ void addForce (const Body::id_t id, const Vector3r& f,Scene* rb){rb->forces.addForce (id,f);} void addTorque(const Body::id_t id, const Vector3r& t,Scene* rb){rb->forces.addTorque(id,t);} /*! Convenience function to apply force and torque from one force at contact point. Not sure if this is the right place for it. */ void applyForceAtContactPoint(const Vector3r& force, const Vector3r& contactPoint, const Body::id_t id1, const Vector3r& pos1, const Body::id_t id2, const Vector3r& pos2){ addForce(id1, force,scene); addTorque(id1, (contactPoint-pos1).cross(force),scene); addForce(id2,-force,scene); addTorque(id2,-(contactPoint-pos2).cross(force),scene); } YADE_CLASS_BASE_DOC(LawFunctor,Functor,"Functor for applying constitutive laws on :yref:`interactions`."); }; REGISTER_SERIALIZABLE(LawFunctor); /******** dispatchers *********************/ class BoundDispatcher: public Dispatcher1D< /* functor type*/ BoundFunctor >{ public: virtual void action(); virtual bool isActivated(){ return activated; } void processBody(const shared_ptr&); DECLARE_LOGGER; YADE_DISPATCHER1D_FUNCTOR_DOC_ATTRS_CTOR_PY(BoundDispatcher,BoundFunctor,/*optional doc*/, /*additional attrs*/ ((bool,activated,true,,"Whether the engine is activated (only should be changed by the collider)")) ((Real,sweepDist,0,,"Distance by which enlarge all bounding boxes, to prevent collider from being run at every step (only should be changed by the collider).")) ((Real,minSweepDistFactor,0.2,,"Minimal distance by which enlarge all bounding boxes; superseeds computed value of sweepDist when lower that (minSweepDistFactor x sweepDist). Updated by the collider. |yupdate|.")) ((Real,updatingDispFactor,-1,,"see :yref:`InsertionSortCollider::updatingDispFactor` |yupdate|")) ((Real,targetInterv,-1,,"see :yref:`InsertionSortCollider::targetInterv` |yupdate|")) ,/*ctor*/,/*py*/ ); }; REGISTER_SERIALIZABLE(BoundDispatcher); class IGeomDispatcher: public Dispatcher2D< /* functor type*/ IGeomFunctor, /* autosymmetry*/ false >{ bool alreadyWarnedNoCollider; public: virtual void action(); shared_ptr explicitAction(const shared_ptr& b1, const shared_ptr& b2, bool force); YADE_DISPATCHER2D_FUNCTOR_DOC_ATTRS_CTOR_PY(IGeomDispatcher,IGeomFunctor,/* doc is optional*/,/*attrs*/,/*ctor*/alreadyWarnedNoCollider=false;,/*py*/); DECLARE_LOGGER; }; REGISTER_SERIALIZABLE(IGeomDispatcher); class IPhysDispatcher: public Dispatcher2D< /*functor type*/ IPhysFunctor >{ public: virtual void action(); void explicitAction(shared_ptr& pp1, shared_ptr& pp2, shared_ptr& i); YADE_DISPATCHER2D_FUNCTOR_DOC_ATTRS_CTOR_PY(IPhysDispatcher,IPhysFunctor,/*doc is optional*/,/*attrs*/,/*ctor*/,/*py*/); }; REGISTER_SERIALIZABLE(IPhysDispatcher); class LawDispatcher: public Dispatcher2D< /*functor type*/ LawFunctor, /*autosymmetry*/ false >{ public: virtual void action(); YADE_DISPATCHER2D_FUNCTOR_DOC_ATTRS_CTOR_PY(LawDispatcher,LawFunctor,/*doc is optional*/,/*attrs*/,/*ctor*/,/*py*/); DECLARE_LOGGER; }; REGISTER_SERIALIZABLE(LawDispatcher); trunk-2018.02b/pkg/common/ElastMat.hpp000066400000000000000000000024111324306050200174600ustar00rootroot00000000000000// 2009 © Václav Šmilauer #pragma once #include /*! Elastic material */ class ElastMat: public Material{ public: virtual ~ElastMat() {}; YADE_CLASS_BASE_DOC_ATTRS_CTOR(ElastMat,Material,"Purely elastic material. The material parameters may have different meanings depending on the :yref:`IPhysFunctor` used : true Young and Poisson in :yref:`Ip2_FrictMat_FrictMat_MindlinPhys`, or contact stiffnesses in :yref:`Ip2_FrictMat_FrictMat_FrictPhys`.", ((Real,young,1e9,,"elastic modulus [Pa]. It has different meanings depending on the Ip functor.")) ((Real,poisson,.25,,"Poisson's ratio or the ratio between shear and normal stiffness [-]. It has different meanings depending on the Ip functor. ")), /*ctor*/ createIndex(); ); REGISTER_CLASS_INDEX(ElastMat,Material); }; REGISTER_SERIALIZABLE(ElastMat); /*! Granular material */ class FrictMat: public ElastMat{ public: virtual ~FrictMat() {}; YADE_CLASS_BASE_DOC_ATTRS_CTOR(FrictMat,ElastMat,"Elastic material with contact friction. See also :yref:`ElastMat`.", ((Real,frictionAngle,.5,,"Contact friction angle (in radians). Hint : use 'radians(degreesValue)' in python scripts.")), createIndex(); ); REGISTER_CLASS_INDEX(FrictMat,ElastMat); }; REGISTER_SERIALIZABLE(FrictMat); trunk-2018.02b/pkg/common/Facet.cpp000066400000000000000000000033001324306050200167610ustar00rootroot00000000000000/************************************************************************* * Copyright (C) 2008 by Sergei Dorofeenko * * sega@users.berlios.de * * * * This program is free software; it is licensed under the terms of the * * GNU General Public License v2 or later. See file LICENSE for details. * *************************************************************************/ #include "Facet.hpp" CREATE_LOGGER(Facet); Facet::~Facet() { } void Facet::postLoad(Facet&) { // if this fails, it means someone did vertices push_back, but they are resized to 3 at Facet initialization already // in the future, a fixed-size array should be used instead of vector for vertices // this is prevented by yade::serialization now IIRC if(vertices.size()!=3){ throw runtime_error(("Facet must have exactly 3 vertices (not "+boost::lexical_cast(vertices.size())+")").c_str()); } if(std::isnan(vertices[0][0])) return; // not initialized, nothing to do Vector3r e[3] = {vertices[1]-vertices[0] ,vertices[2]-vertices[1] ,vertices[0]-vertices[2]}; #define CHECK_EDGE(i) if(e[i].squaredNorm()==0){LOG_FATAL("Facet has coincident vertices "< #include // define this to have topology information about facets enabled; // it is necessary for FacetTopologyAnalyzer // #define FACET_TOPO class Facet : public Shape { public: virtual ~Facet(); // Postprocessed attributes /// Facet's normal //Vector3r nf; /// Normals of edges Vector3r ne[3]; /// Inscribing cirle radius Real icr; /// Length of the vertice vectors Real vl[3]; /// Unit vertice vectors Vector3r vu[3]; void postLoad(Facet&); void setVertices(const Vector3r& v0, const Vector3r& v1, const Vector3r& v2) { vertices[0]=v0; vertices[1]=v1; vertices[2]=v2; postLoad(*this); } YADE_CLASS_BASE_DOC_ATTRS_CTOR_PY(Facet,Shape,"Facet (triangular particle) geometry.", ((vector,vertices,vector(3,Vector3r(NaN,NaN,NaN)),(Attr::triggerPostLoad | Attr::noResize),"Vertex positions in local coordinates.")) ((Vector3r,normal,Vector3r(NaN,NaN,NaN),(Attr::readonly | Attr::noSave),"Facet's normal (in local coordinate system)")) ((Real,area,NaN,(Attr::readonly | Attr::noSave),"Facet's area")) #ifdef FACET_TOPO ((vector,edgeAdjIds,vector(3,Body::ID_NONE),,"Facet id's that are adjacent to respective edges [experimental]")) ((vector,edgeAdjHalfAngle,vector(3,0),,"half angle between normals of this facet and the adjacent facet [experimental]")) #endif , /* ctor */ createIndex();, .def("setVertices",&Facet::setVertices,"TODO") ); DECLARE_LOGGER; REGISTER_CLASS_INDEX(Facet,Shape); }; REGISTER_SERIALIZABLE(Facet); trunk-2018.02b/pkg/common/FieldApplier.hpp000066400000000000000000000010431324306050200203060ustar00rootroot00000000000000#pragma once #include #include class FieldApplier: public GlobalEngine{ virtual void action() { throw std::runtime_error("FieldApplier must not be used in simulations directly (FieldApplier::action called)."); } YADE_CLASS_BASE_DOC_ATTRS(FieldApplier,GlobalEngine,"Base for engines applying force files on particles. Not to be used directly.", ((int,fieldWorkIx,-1,(Attr::hidden|Attr::noSave),"Index for the work done by this field, if tracking energies.")) ); }; REGISTER_SERIALIZABLE(FieldApplier); trunk-2018.02b/pkg/common/ForceEngine.cpp000066400000000000000000000047101324306050200201310ustar00rootroot00000000000000// 2004 © Janek Kozicki // 2009 © Václav Šmilauer #include"ForceEngine.hpp" #include #include #include #include #include #include #include #include #include YADE_PLUGIN((ForceEngine)(InterpolatingDirectedForceEngine)(RadialForceEngine)(DragEngine)(LinearDragEngine)); void ForceEngine::action(){ FOREACH(Body::id_t id, ids){ if (!(scene->bodies->exists(id))) continue; scene->forces.addForce(id,force); } } void InterpolatingDirectedForceEngine::action(){ Real virtTime=wrap ? Shop::periodicWrap(scene->time,*times.begin(),*times.rbegin()) : scene->time; direction.normalize(); force=linearInterpolate(virtTime,times,magnitudes,_pos)*direction; ForceEngine::action(); } void RadialForceEngine::postLoad(RadialForceEngine&){ axisDir.normalize(); } void RadialForceEngine::action(){ FOREACH(Body::id_t id, ids){ if (!(scene->bodies->exists(id))) continue; const Vector3r& pos=Body::byId(id,scene)->state->pos; Vector3r radial=(pos - (axisPt+axisDir * /* t */ ((pos-axisPt).dot(axisDir)))).normalized(); if(radial.squaredNorm()==0) continue; scene->forces.addForce(id,fNorm*radial); } } void DragEngine::action(){ FOREACH(Body::id_t id, ids){ Body* b=Body::byId(id,scene).get(); if (!b) continue; if (!(scene->bodies->exists(id))) continue; const Sphere* sphere = dynamic_cast(b->shape.get()); if (sphere){ Real A = sphere->radius*sphere->radius*Mathr::PI; //Crossection of the sphere Vector3r velSphTemp = b->state->vel; Vector3r dragForce = Vector3r::Zero(); if (velSphTemp != Vector3r::Zero()) { dragForce = -0.5*Rho*A*Cd*velSphTemp.squaredNorm()*velSphTemp.normalized(); } scene->forces.addForce(id,dragForce); } } } void LinearDragEngine::action(){ FOREACH(Body::id_t id, ids){ Body* b=Body::byId(id,scene).get(); if (!b) continue; if (!(scene->bodies->exists(id))) continue; const Sphere* sphere = dynamic_cast(b->shape.get()); if (sphere){ Vector3r velSphTemp = b->state->vel; Vector3r dragForce = Vector3r::Zero(); Real b = 6*Mathr::PI*nu*sphere->radius; if (velSphTemp != Vector3r::Zero()) { dragForce = -b*velSphTemp; } scene->forces.addForce(id,dragForce); } } } trunk-2018.02b/pkg/common/ForceEngine.hpp000066400000000000000000000101761324306050200201410ustar00rootroot00000000000000// 2004 © Janek Kozicki // 2009 © Václav Šmilauer #pragma once #include class ForceEngine: public PartialEngine{ public: virtual void action(); YADE_CLASS_BASE_DOC_ATTRS(ForceEngine,PartialEngine,"Apply contact force on some particles at each step.", ((Vector3r,force,Vector3r::Zero(),,"Force to apply.")) ); }; REGISTER_SERIALIZABLE(ForceEngine); /* Engine for applying force of varying magnitude but constant direction * on subscribed bodies. times and magnitudes must have the same length, * direction (normalized automatically) gives the orientation. * * As usual with interpolating engines: the first magnitude is used before the first * time point, last magnitude is used after the last time point. Wrap specifies whether * time wraps around the last time point to the first time point. */ class InterpolatingDirectedForceEngine: public ForceEngine{ size_t _pos; public: virtual void action(); YADE_CLASS_BASE_DOC_ATTRS_CTOR(InterpolatingDirectedForceEngine,ForceEngine,"Engine for applying force of varying magnitude but constant direction on subscribed bodies. times and magnitudes must have the same length, direction (normalized automatically) gives the orientation. \n\n\ \ As usual with interpolating engines: the first magnitude is used before the first time point, last magnitude is used after the last time point. Wrap specifies whether time wraps around the last time point to the first time point.", ((vector,times,,,"Time readings [s]")) ((vector,magnitudes,,,"Force magnitudes readings [N]")) ((Vector3r,direction,Vector3r::UnitX(),,"Contact force direction (normalized automatically)")) ((bool,wrap,false,,"wrap to the beginning of the sequence if beyond the last time point")), /*ctor*/ _pos=0 ); }; REGISTER_SERIALIZABLE(InterpolatingDirectedForceEngine); struct RadialForceEngine: public PartialEngine{ virtual void action(); virtual void postLoad(RadialForceEngine&); YADE_CLASS_BASE_DOC_ATTRS(RadialForceEngine,PartialEngine,"Apply force of given magnitude directed away from spatial axis.", ((Vector3r,axisPt,Vector3r::Zero(),,"Point on axis")) ((Vector3r,axisDir,Vector3r::UnitX(),Attr::triggerPostLoad,"Axis direction (normalized automatically)")) ((Real,fNorm,0,,"Applied force magnitude")) ); }; REGISTER_SERIALIZABLE(RadialForceEngine); class DragEngine: public PartialEngine{ public: virtual void action(); YADE_CLASS_BASE_DOC_ATTRS(DragEngine,PartialEngine,"Apply `drag force `__ on some particles at each step, decelerating them proportionally to their linear velocities. The applied force reads\n\n.. math:: F_{d}=-\\frac{\\vec{v}}{|\\vec{v}|}\\frac{1}{2}\\rho|\\vec{v}|^2 C_d A\n\nwhere $\\rho$ is the medium density (:yref:`density`), $v$ is particle's velocity, $A$ is particle projected area (disc), $C_d$ is the drag coefficient (0.47 for :yref:`Sphere`), \n\n.. note:: Drag force is only applied to spherical particles, listed in ids.", ((Real,Rho,1.225,,"Density of the medium (fluid or air), by default - the density of the air.")) ((Real,Cd,0.47,,"Drag coefficient `_.")) ); }; REGISTER_SERIALIZABLE(DragEngine); class LinearDragEngine: public PartialEngine{ public: virtual void action(); YADE_CLASS_BASE_DOC_ATTRS(LinearDragEngine,PartialEngine,"Apply `viscous resistance or linear drag `__ on some particles at each step, decelerating them proportionally to their linear velocities. The applied force reads\n\n.. math:: F_{d}=-b{\\vec{v}} \n\nwhere $b$ is the linear drag, $\\vec{v}$ is particle's velocity. \n\n.. math:: b=6\\pi\\nu r \n\nwhere $\\nu$ is the medium viscosity, $r$ is the `Stokes radius `__ of the particle (but in this case we accept it equal to sphere radius for simplification), \n\n.. note:: linear drag is only applied to spherical particles, listed in ids.", ((Real,nu,0.001,,"Viscosity of the medium.")) ); }; REGISTER_SERIALIZABLE(LinearDragEngine); trunk-2018.02b/pkg/common/ForceResetter.hpp000066400000000000000000000010751324306050200205270ustar00rootroot00000000000000#pragma once #include #include class Scene; class ForceResetter: public GlobalEngine{ public: virtual void action() { scene->forces.reset(scene->iter); if(scene->trackEnergy) scene->energy->resetResettables(); } YADE_CLASS_BASE_DOC(ForceResetter,GlobalEngine,"Reset all forces stored in Scene::forces (``O.forces`` in python). Typically, this is the first engine to be run at every step. In addition, reset those energies that should be reset, if energy tracing is enabled."); }; REGISTER_SERIALIZABLE(ForceResetter); trunk-2018.02b/pkg/common/GLDrawFunctors.hpp000066400000000000000000000043131324306050200206150ustar00rootroot00000000000000// © 2004 Olivier Galizzi // © 2006 Janek Kozicki #pragma once #include #include #include #include #include #include #include #include #include #include #define RENDERS(name) public: virtual string renders() const { return #name;}; FUNCTOR1D(name); struct GLViewInfo{ GLViewInfo(): sceneCenter(Vector3r::Zero()), sceneRadius(1.){} Vector3r sceneCenter; Real sceneRadius; }; class OpenGLRenderer; #define GL_FUNCTOR(Klass,typelist,renderedType) class Klass: public Functor1D{public:\ virtual ~Klass(){};\ virtual string renders() const { throw std::runtime_error(#Klass ": unregistered gldraw class.\n"); };\ virtual void initgl(){/*WARNING: it must deal with static members, because it is called from another instance!*/};\ YADE_CLASS_BASE_DOC(Klass,Functor,"Abstract functor for rendering :yref:`" #renderedType "` objects."); \ }; REGISTER_SERIALIZABLE(Klass); #define GL_DISPATCHER(Klass,Functor) class Klass: public Dispatcher1D{public:\ YADE_DISPATCHER1D_FUNCTOR_DOC_ATTRS_CTOR_PY(Klass,Functor,/*optional doc*/,/*attrs*/,/*ctor*/,/*py*/); \ }; REGISTER_SERIALIZABLE(Klass); GL_FUNCTOR(GlBoundFunctor,TYPELIST_2(const shared_ptr&, Scene*),Bound); GL_FUNCTOR(GlShapeFunctor,TYPELIST_4(const shared_ptr&, const shared_ptr&,bool,const GLViewInfo&),Shape); GL_FUNCTOR(GlIGeomFunctor,TYPELIST_5(const shared_ptr&, const shared_ptr&, const shared_ptr&, const shared_ptr&, bool),IGeom); GL_FUNCTOR(GlIPhysFunctor,TYPELIST_5(const shared_ptr&, const shared_ptr&, const shared_ptr&, const shared_ptr&, bool),IPhys); GL_FUNCTOR(GlStateFunctor,TYPELIST_1(const shared_ptr&),State); GL_DISPATCHER(GlBoundDispatcher,GlBoundFunctor); GL_DISPATCHER(GlShapeDispatcher,GlShapeFunctor); GL_DISPATCHER(GlIGeomDispatcher,GlIGeomFunctor); GL_DISPATCHER(GlIPhysDispatcher,GlIPhysFunctor); GL_DISPATCHER(GlStateDispatcher,GlStateFunctor); #undef GL_FUNCTOR #undef GL_DISPATCHER trunk-2018.02b/pkg/common/Gl1_NormPhys.cpp000066400000000000000000000071471324306050200202360ustar00rootroot00000000000000#ifdef YADE_OPENGL #include #include #include #include #include #include YADE_PLUGIN((Gl1_NormPhys)); GLUquadric* Gl1_NormPhys::gluQuadric=NULL; Real Gl1_NormPhys::maxFn; Real Gl1_NormPhys::refRadius; Real Gl1_NormPhys::maxRadius; int Gl1_NormPhys::signFilter; int Gl1_NormPhys::slices; int Gl1_NormPhys::stacks; Real Gl1_NormPhys::maxWeakFn; int Gl1_NormPhys::weakFilter; Real Gl1_NormPhys::weakScale; void Gl1_NormPhys::go(const shared_ptr& ip, const shared_ptr& i, const shared_ptr& b1, const shared_ptr& b2, bool wireFrame){ if(!gluQuadric){ gluQuadric=gluNewQuadric(); if(!gluQuadric) throw runtime_error("Gl1_NormPhys::go unable to allocate new GLUquadric object (out of memory?)."); } NormPhys* np=static_cast(ip.get()); shared_ptr ig(i->geom); if(!ig) return; // changed meanwhile? GenericSpheresContact* geom=YADE_CAST(ig.get()); //if(!geom) cerr<<"Gl1_NormPhys: IGeom is not a GenericSpheresContact, but a "<getClassName()<normalForce.dot(geom->normal); if((signFilter>0 && fnNorm<0) || (signFilter<0 && fnNorm>0)) return; int fnSign=fnNorm>0?1:-1; fnNorm=std::abs(fnNorm); Real radiusScale=1.; // weak/strong fabric, only used if maxWeakFn is set if(!std::isnan(maxWeakFn)){ if(fnNorm*fnSign0) return; radiusScale=weakScale; } else { // strong fabric if(weakFilter<0) return; } } maxFn=max(fnNorm,maxFn); Real realMaxRadius; if(maxRadius<0){ if(geom->refR1>0) refRadius=min(geom->refR1,refRadius); if(geom->refR2>0) refRadius=min(geom->refR2,refRadius); realMaxRadius=refRadius; } else realMaxRadius=maxRadius; Real radius=radiusScale*realMaxRadius*(fnNorm/maxFn); // use logarithmic scale here? Vector3r color=Shop::scalarOnColorScale(fnNorm*fnSign,-maxFn,maxFn); # if 0 // get endpoints from body positions Vector3r p1=b1->state->pos, p2=b2->state->pos; Vector3r relPos; if(scene->isPeriodic){ relPos=p2+scene->cell->Hsize*i->cellDist.cast()-p1; p1=scene->cell->wrapShearedPt(p1); p2=p1+relPos; } else { relPos=p2-p1; } Real dist=relPos.norm(); #else // get endpoints from geom // max(r,0) handles r<0 which is the case for "radius" of the facet Vector3r cp=scene->isPeriodic? scene->cell->wrapShearedPt(geom->contactPoint) : geom->contactPoint; Vector3r p1=cp-max(geom->refR1,(Real)0.)*geom->normal; Vector3r p2=cp+max(geom->refR2,(Real)0.)*geom->normal; const Vector3r& dispScale=scene->renderer ? scene->renderer->dispScale : Vector3r::Ones(); if(dispScale!=Vector3r::Ones()){ // move p1 and p2 by the same amounts as particles themselves would be moved p1+=dispScale.cwiseProduct(Vector3r(b1->state->pos-b1->state->refPos)); p2+=dispScale.cwiseProduct(Vector3r(b2->state->pos-b2->state->refPos)); } Vector3r relPos=p2-p1; Real dist=relPos.norm(); //max(geom->refR1,0.)+max(geom->refR2,0.); #endif glDisable(GL_CULL_FACE); glPushMatrix(); glTranslatef(p1[0],p1[1],p1[2]); Quaternionr q(Quaternionr().setFromTwoVectors(Vector3r(0,0,1),relPos/dist /* normalized */)); // using Transform with OpenGL: http://eigen.tuxfamily.org/dox/TutorialGeometry.html //glMultMatrixd(Eigen::Affine3d(q).data()); glMultMatrix(Eigen::Transform(q).data()); glColor3v(color); gluCylinder(gluQuadric,radius,radius,dist,slices,stacks); glPopMatrix(); } #endif /* YADE_OPENGL */ trunk-2018.02b/pkg/common/Gl1_NormPhys.hpp000066400000000000000000000050471324306050200202400ustar00rootroot00000000000000#pragma once #include #include #include #include #include class Gl1_NormPhys: public GlIPhysFunctor{ static GLUquadric* gluQuadric; // needed for gluCylinder, initialized by ::go if no initialized yet public: virtual void go(const shared_ptr&,const shared_ptr&,const shared_ptr&,const shared_ptr&,bool wireFrame); YADE_CLASS_BASE_DOC_STATICATTRS(Gl1_NormPhys,GlIPhysFunctor,"Renders :yref:`NormPhys` objects as cylinders of which diameter and color depends on :yref:`NormPhys.normalForce` magnitude.", // changed doc maxDiameter -> maxRadius ((Real,maxFn,0,,"Value of :yref:`NormPhys.normalForce` corresponding to :yref:`maxDiameter`. This value will be increased (but *not decreased* ) automatically.")) ((Real,maxFn,0,,"Value of :yref:`NormPhys.normalForce` corresponding to :yref:`maxRadius`. This value will be increased (but *not decreased* ) automatically.")) ((int,signFilter,0,,"If non-zero, only display contacts with negative (-1) or positive (+1) normal forces; if zero, all contacts will be displayed.")) ((Real,refRadius,std::numeric_limits::infinity(),,"Reference (minimum) particle radius; used only if :yref:`maxRadius` is negative. This value will be decreased (but *not increased* ) automatically. |yupdate|")) ((Real,maxRadius,-1,,"Cylinder radius corresponding to the maximum normal force. If negative, auto-updated :yref:`refRadius` will be used instead.")) ((int,slices,6,,"Number of sphere slices; (see `glutCylinder reference `_)")) // FIXME: the link does not exist ((int,stacks,1,,"Number of sphere stacks; (see `glutCylinder reference `_)")) // strong/weak fabric attributes ((Real,maxWeakFn,NaN,,"Value that divides contacts by their normal force into the 'weak fabric' and 'strong fabric'. This value is set as side-effect by :yref:`utils.fabricTensor`.")) ((int,weakFilter,0,,"If non-zero, only display contacts belonging to the 'weak' (-1) or 'strong' (+1) fabric.")) ((Real,weakScale,1.,,"If :yref:`maxWeakFn` is set, scale radius of the weak fabric by this amount (usually smaller than 1). If zero, 1 pixel line is displayed. Colors are not affected by this value.")) ); RENDERS(NormPhys); }; REGISTER_SERIALIZABLE(Gl1_NormPhys); trunk-2018.02b/pkg/common/Gl1_PFacet.cpp000066400000000000000000000032451324306050200176140ustar00rootroot00000000000000#ifdef YADE_OPENGL #include #include // bool Gl1_PFacet::normals=false; bool Gl1_PFacet::wire=true; void Gl1_PFacet::go(const shared_ptr& cm, const shared_ptr& ,bool wire2,const GLViewInfo&) { PFacet* Pfacet = static_cast(cm.get()); vector vertices; vertices.push_back(Pfacet->node1->state->pos); vertices.push_back(Pfacet->node2->state->pos); vertices.push_back(Pfacet->node3->state->pos); Vector3r pos=Pfacet->node1->state->pos; vertices[0]=vertices[0]-pos; vertices[1]=vertices[1]-pos; vertices[2]=vertices[2]-pos; vector verticesF1 = vertices; Vector3r normal=(vertices[1]-vertices[0]).cross(vertices[2]-vertices[1]); normal.normalize(); verticesF1[0]=vertices[0] + normal*Pfacet->radius; verticesF1[1]=vertices[1] + normal*Pfacet->radius; verticesF1[2]=vertices[2] + normal*Pfacet->radius; vector verticesF2 = vertices; verticesF2[0] = vertices[0] - normal*Pfacet->radius; verticesF2[1] = vertices[1] - normal*Pfacet->radius; verticesF2[2] = vertices[2] - normal*Pfacet->radius; if(!wire2||!wire){ glDisable(GL_CULL_FACE); glColor3v(cm->color); glBegin(GL_TRIANGLES); glNormal3v(normal); // this makes every triangle different WRT the light direction; important! glVertex3v(verticesF1[0]); glVertex3v(verticesF1[1]); glVertex3v(verticesF1[2]); glEnd(); glBegin(GL_TRIANGLES); glNormal3v(Pfacet->normal); // this makes every triangle different WRT the light direction; important! glVertex3v(verticesF2[2]); glVertex3v(verticesF2[1]); glVertex3v(verticesF2[0]); glEnd(); } } YADE_PLUGIN((Gl1_PFacet)); #endiftrunk-2018.02b/pkg/common/Gl1_PFacet.hpp000066400000000000000000000010651324306050200176170ustar00rootroot00000000000000#pragma once #include #include #include #include #ifdef YADE_OPENGL class Gl1_PFacet : public GlShapeFunctor { public: virtual void go(const shared_ptr&, const shared_ptr&,bool,const GLViewInfo&); RENDERS(PFacet); YADE_CLASS_BASE_DOC_STATICATTRS(Gl1_PFacet,GlShapeFunctor,"Renders :yref:`Facet` object", ((bool,wire,false,,"Only show wireframe (controlled by ``glutSlices`` and ``glutStacks``.")) ); }; REGISTER_SERIALIZABLE(Gl1_PFacet); #endif trunk-2018.02b/pkg/common/Gl1_PotentialBlock.cpp000077500000000000000000001222721324306050200213710ustar00rootroot00000000000000/* CWBoon 2016 */ #ifdef YADE_POTENTIAL_BLOCKS #include "Gl1_PotentialBlock.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 #include #include #include #include #include #include #include #include #include #include //#include #include #include #include #if 0 void Gl1_PotentialBlock::calcMinMax(const shared_ptr& cm) { PotentialBlock* pp = static_cast(cm.get()); int planeNo = pp->d.size(); Real maxD = pp->d[0]; for (int i=0; id[i] > maxD) { maxD = pp->d[i]; } } Real R = pp->R; Real r = pp->r; Real maxTip = R; //std::max(maxD + r, R); min =-1.1*pp->minAabb; // 1.5*Vector3r(-maxTip,-maxTip,-maxTip); max = 1.1*pp->maxAabb; //-1.0*min; float dx = (max[0]-min[0])/((float)(sizeX)); float dy = (max[1]-min[1])/((float)(sizeY)); float dz = (max[2]-min[2])/((float)(sizeZ)); isoStep=Vector3r(dx,dy,dz); } void Gl1_PotentialBlock::generateScalarField(const shared_ptr& cm) { for(int i=0;i Gl1_PotentialBlock::SF; int Gl1_PotentialBlock::sizeX, Gl1_PotentialBlock::sizeY, Gl1_PotentialBlock::sizeZ; bool Gl1_PotentialBlock::store; bool Gl1_PotentialBlock::initialized; void Gl1_PotentialBlock::clearMemory(){ SF.clear(); } void Gl1_PotentialBlock::go( const shared_ptr& cm, const shared_ptr& state ,bool wire2, const GLViewInfo&){ PotentialBlock* pp = static_cast(cm.get()); int shapeId = pp->id; if(store == false){ if(SF.size()>0){SF.clear(); initialized = false; } return; } if(initialized == false ){ FOREACH(const shared_ptr& b, *scene->bodies){ if (!b) continue; const shared_ptr& cmbody=b->shape; calcMinMax(cmbody); mc.init(sizeX,sizeY,sizeZ,min,max); mc.resizeScalarField(scalarField,sizeX,sizeY,sizeZ); SF.push_back(scalarF()); generateScalarField(cmbody); mc.computeTriangulation(scalarField,0.0); SF[b->id].triangles = mc.getTriangles(); SF[b->id].normals = mc.getNormals(); SF[b->id].nbTriangles = mc.getNbTriangles(); for(int i=0; icolor[0],cm->color[1],cm->color[2])); glColor3v(cm->color); vector& triangles = SF[shapeId].triangles; //mc.getTriangles(); int nbTriangles = SF[shapeId].nbTriangles; // //mc.getNbTriangles(); vector& normals = SF[shapeId].normals; //mc.getNormals(); glDisable(GL_CULL_FACE); glEnable(GL_LIGHTING); // 2D glEnable(GL_NORMALIZE); glBegin(GL_TRIANGLES); for(int i=0;i<3*nbTriangles;++i) { glNormal3v(normals[i]); glVertex3v(triangles[i]); glNormal3v(normals[++i]); glVertex3v(triangles[i]); glNormal3v(normals[++i]); glVertex3v(triangles[i]); } glEnd(); return; } double Gl1_PotentialBlock::evaluateF(const shared_ptr& cm, double x, double y, double z){ PotentialBlock* pp = static_cast(cm.get()); Real k = pp->k; Real r = pp->r; Real R = pp->R; int planeNo = pp->a.size(); vectora; vectorb; vectorc; vectord; vectorp; Real pSum3 = 0.0; for (int i=0; ia[i]); b.push_back(pp->b[i]); c.push_back(pp->c[i]); d.push_back(pp->d[i]); Real plane = a[i]*x + b[i]*y + c[i]*z - d[i]; if (planep; double pSum2 = 0.0; if(clump==false){ Eigen::Vector3d xori(x[0],x[1],x[2]); Eigen::Vector3d xlocal = rotationMatrix*xori; xlocal[0] = rotationMatrix(0,0)*x[0] + rotationMatrix(0,1)*x[1] + rotationMatrix(0,2)*x[2]; xlocal[1] = rotationMatrix(1,0)*x[0] + rotationMatrix(1,1)*x[1] + rotationMatrix(1,2)*x[2]; xlocal[2] = rotationMatrix(2,0)*x[0] + rotationMatrix(2,1)*x[1] + rotationMatrix(2,2)*x[2]; //std::cout<<"rotationMatrix: "< pbPos = vtkSmartPointer::New(); vtkSmartPointer appendFilter = vtkSmartPointer::New(); vtkSmartPointer appendFilterID = vtkSmartPointer::New(); //vtkSmartPointer transformFilter = vtkSmartPointer::New(); //vtkSmartPointer transform = vtkSmartPointer::New(); // interactions ############################################### vtkSmartPointer intrBodyPos = vtkSmartPointer::New(); vtkSmartPointer intrCells = vtkSmartPointer::New(); vtkSmartPointer intrForceN = vtkSmartPointer::New(); intrForceN->SetNumberOfComponents(3); intrForceN->SetName("forceN"); vtkSmartPointer intrAbsForceT = vtkSmartPointer::New(); intrAbsForceT->SetNumberOfComponents(1); intrAbsForceT->SetName("absForceT"); // interactions ############################################### // interaction contact point ############################################### vtkSmartPointer pbContactPoint = vtkSmartPointer::New(); vtkSmartPointer pbCellsContact = vtkSmartPointer::New(); vtkSmartPointer pbNormalForce = vtkSmartPointer::New(); pbNormalForce->SetNumberOfComponents(3); pbNormalForce->SetName("normalForce"); //Linear velocity in Vector3 form vtkSmartPointer pbShearForce = vtkSmartPointer::New(); pbShearForce->SetNumberOfComponents(3); pbShearForce->SetName("shearForce"); //Angular velocity in Vector3 form vtkSmartPointer pbTotalForce = vtkSmartPointer::New(); pbTotalForce->SetNumberOfComponents(3); pbTotalForce->SetName("totalForce"); vtkSmartPointer pbTotalStress = vtkSmartPointer::New(); pbTotalStress->SetNumberOfComponents(3); pbTotalStress->SetName("totalStress"); vtkSmartPointer pbMobilizedShear = vtkSmartPointer::New(); pbMobilizedShear->SetNumberOfComponents(1); pbMobilizedShear->SetName("mobilizedShear"); // interactions contact point############################################### // velocity ################################################### vtkSmartPointer pbCells = vtkSmartPointer::New(); vtkSmartPointer pbLinVelVec = vtkSmartPointer::New(); pbLinVelVec->SetNumberOfComponents(3); pbLinVelVec->SetName("linVelVec"); //Linear velocity in Vector3 form vtkSmartPointer pbLinVelLen = vtkSmartPointer::New(); pbLinVelLen->SetNumberOfComponents(1); pbLinVelLen->SetName("linVelLen"); //Length (magnitude) of linear velocity vtkSmartPointer pbAngVelVec = vtkSmartPointer::New(); pbAngVelVec->SetNumberOfComponents(3); pbAngVelVec->SetName("angVelVec"); //Angular velocity in Vector3 form vtkSmartPointer pbAngVelLen = vtkSmartPointer::New(); pbAngVelLen->SetNumberOfComponents(1); pbAngVelLen->SetName("angVelLen"); //Length (magnitude) of angular velocity vtkSmartPointer pbDisplacementVec = vtkSmartPointer::New(); pbDisplacementVec->SetNumberOfComponents(3); pbDisplacementVec->SetName("Displacement"); //Linear velocity in Vector3 form // velocity #################################################### // bodyId ############################################################## //#if 0 vtkSmartPointer pbPosID = vtkSmartPointer::New(); vtkSmartPointer pbIdCells = vtkSmartPointer::New(); vtkSmartPointer blockId = vtkSmartPointer::New(); blockId->SetNumberOfComponents(1); blockId->SetName("id"); // bodyId ############################################################## //#endif int countID = 0; vtkSmartPointer textArray2[scene->bodies->size()]; vtkSmartPointer txtMapper[scene->bodies->size()]; vtkSmartPointer extrude[scene->bodies->size()]; vtkSmartPointer textActor[scene->bodies->size()]; FOREACH(const shared_ptr& b, *scene->bodies){ if (!b) continue; if (b->isClumpMember() == true ) continue; //const PotentialBlock* pb=dynamic_cast(b->shape.get()); //if(!pb) continue; if(REC_ID==true){ //#if 0 blockId->InsertNextValue(b->getId()); vtkIdType pid[1]; Vector3r pos(b->state->pos); pid[0] = pbPosID->InsertNextPoint(pos[0], pos[1], pos[2]); pbIdCells->InsertNextCell(1,pid); //#endif /* ################# Display id VTK extrusion vector ############## */ #if 0 textArray2[countID]= vtkVectorText::New(); //#if 0 int bid = b->id; std::string testString = boost::lexical_cast(bid); const char * testChar = testString.c_str(); textArray2[countID]->SetText(testChar); extrude[countID] = vtkLinearExtrusionFilter::New(); extrude[countID]->SetInputConnection( textArray2[countID]->GetOutputPort()); extrude[countID]->SetExtrusionTypeToNormalExtrusion(); extrude[countID]->SetVector(0, 0, 1.0 ); extrude[countID]->SetScaleFactor (1.0); #if 0 txtMapper[countID] = vtkPolyDataMapper::New(); txtMapper[countID]->SetInputConnection( extrude[countID]->GetOutputPort()); textActor[countID] = vtkActor::New(); textActor[countID]->SetMapper(txtMapper[countID]); textActor[countID]->RotateX(-90); textActor[countID]->SetPosition(b->state->pos[0], b->state->pos[1], b->state->pos[2]); double contactPtSize = 5.0; textActor[countID]->SetScale(3.0*contactPtSize); textActor[countID]->GetProperty()->SetColor(0.0,0.0,0.0); #endif //#if 0 //txtMapper[countID] = vtkPolyDataMapper::New(); //txtMapper[countID]->SetInputConnection( extrude[count]->GetOutputPort()); //appendFilterID->AddInputConnection(extrude[countID]-> GetOutputPort()); //vtkSmartPointer polydata = vtkSmartPointer::New(); //polydata->DeepCopy(extrude[countID]-> GetOutput()); Vector3r centre (b->state->pos[0], b->state->pos[1], b->state->pos[2]); vtkSmartPointer transformFilter = vtkSmartPointer::New(); transformFilter->SetInput( extrude[countID]-> GetOutput() ); vtkSmartPointer transform = vtkSmartPointer::New(); transformFilter->SetTransform( transform ); transform->PostMultiply(); transform->Translate (centre[0], centre[1],centre[2]); //#endif appendFilterID->AddInputConnection(transformFilter-> GetOutputPort()); //polydata->DeleteCells(); #endif countID++; } //vtkSmartPointer function = ImpFunc::New(); vtkSmartPointer boolFunction = vtkSmartPointer::New(); vtkSmartPointer* functionBool; int ImplicitBoolNo = 0; double xmin = 0.0; double xmax = 0.0; double ymin = 0.0; double ymax = 0.0; double zmin = 0.0; double zmax =0.0; Vector3r particleColour (0,0,0); if(b->isClump() == false && b->isClumpMember() == false){ const PotentialBlock* pb=dynamic_cast(b->shape.get()); if (pb->isLining==true){continue;} function->a = pb->a; function->b = pb->b; function->c = pb->c; function->d = pb->d; function->R = pb->R; function->r = pb->r; function->k = pb->k; Eigen::Matrix3d directionCos = b->state->ori.conjugate().toRotationMatrix(); int count = 0; function->clump = false; for (int i=0; i<3; i++){ for (int j=0; j<3; j++){ //function->rotationMatrix[count] = directionCos(j,i); function->rotationMatrix(i,j) = directionCos(j,i); count++; } } xmin = -pb->halfSize.x(); xmax = pb->halfSize.x(); ymin = -pb->halfSize.y(); ymax = pb->halfSize.y(); zmin = -pb->halfSize.z(); zmax = pb->halfSize.z(); particleColour = pb->color; }else if(b->isClump() == true){ //const Clump* clump = dynamic_cast(b->shape.get()); const shared_ptr clump(YADE_PTR_CAST(b->shape)); bool firstBound = true; //vtkSmartPointer functionBool [clump->ids.size()]; functionBool = new vtkSmartPointer [clump->ids.size()]; ImplicitBoolNo = clump->ids.size(); for (unsigned i=0; iids.size();i++){ const shared_ptr clumpMember = Body::byId(clump->ids[i],scene); const PotentialBlock* pbShape =dynamic_cast(clumpMember->shape.get()); functionBool[i] = vtkSmartPointer::New(); functionBool[i]->R = pbShape->R; functionBool[i]->r = pbShape->r; functionBool[i]->k = pbShape->k; functionBool[i]->clump=true; functionBool[i]->clumpMemberCentreX = clumpMember->state->pos.x()-b->state->pos.x(); functionBool[i]->clumpMemberCentreY = clumpMember->state->pos.y()-b->state->pos.y(); functionBool[i]->clumpMemberCentreZ = clumpMember->state->pos.z()-b->state->pos.z(); Eigen::Matrix3d directionCos = clumpMember->state->ori.conjugate().toRotationMatrix(); for (int j=0; ja.size();j++){ Vector3r plane = directionCos.transpose()*Vector3r(pbShape->a[j], pbShape->b[j], pbShape->c[j]); double d = pbShape->d[j]; //-1.0*(plane.x()*(b->state->pos.x()-clumpMember->state->pos.x() ) + plane.y()*(b->state->pos.y()-clumpMember->state->pos.y() ) + plane.z()*(b->state->pos.z()-clumpMember->state->pos.z() ) - pbShape->d[j]); functionBool[i]->a.push_back(plane.x() ); functionBool[i]->b.push_back(plane.y() ); functionBool[i]->c.push_back(plane.z() ); functionBool[i]->d.push_back(d ); } const Aabb* aabb = static_cast(clumpMember->bound.get()); if (firstBound == true){ xmin = aabb->min.x(); xmax = aabb->max.x(); ymin = aabb->min.y(); ymax = aabb->max.y(); zmin = aabb->min.z(); zmax = aabb->max.z(); firstBound = false; particleColour = pbShape->color; }else{ xmin=std::min(xmin,aabb->min.x() ); xmax=std::max(xmax,aabb->max.x() ); ymin=std::min(ymin,aabb->min.y() ); ymax=std::max(ymax,aabb->max.y() ); zmin=std::min(zmin,aabb->min.z() ); zmax=std::max(zmax,aabb->max.z() ); } boolFunction->AddFunction(functionBool[i]); } xmin = xmin - b->state->pos.x(); xmax = xmax - b->state->pos.x(); ymin = ymin - b->state->pos.y(); ymax = ymax - b->state->pos.y(); zmin = zmin - b->state->pos.z(); zmax = zmax - b->state->pos.z(); boolFunction->SetOperationTypeToUnion(); } vtkSmartPointer sample = vtkSampleFunction::New(); if(b->isClump() == false && b->isClumpMember() == false){ sample->SetImplicitFunction(function); }else if(b->isClump()==true){ sample->SetImplicitFunction(boolFunction); boolFunction->SetOperationTypeToUnion(); } //double xmin = -value; double xmax = value; double ymin = -value; double ymax=value; double zmin=-value; double zmax=value; //double xmin = -std::max(pb->minAabb.x(),pb->maxAabb.x()); double xmax = -xmin; double ymin = -std::max(pb->minAabb.y(),pb->maxAabb.y()); double ymax=-ymin; double zmin=-std::max(pb->minAabb.z(),pb->maxAabb.z()); double zmax=-zmin; if (twoDimension == true){ if(sampleY < 2){ ymin = 0.0; ymax = 0.0; }else if(sampleZ < 2){ zmin = 0.0; zmax = 0.0; } } sample->SetModelBounds(xmin, xmax, ymin, ymax, zmin, zmax); //sample->SetModelBounds(pb->minAabb.x(), pb->maxAabb.x(), pb->minAabb.y(), pb->maxAabb.y(), pb->minAabb.z(), pb->maxAabb.z()); int sampleXno = sampleX; int sampleYno = sampleY; int sampleZno = sampleZ; if(fabs(xmax-xmin)/static_cast(sampleX) > maxDimension) { sampleXno = static_cast(fabs(xmax-xmin)/maxDimension); } if(fabs(ymax-ymin)/static_cast(sampleY) > maxDimension) { sampleYno = static_cast(fabs(ymax-ymin)/maxDimension); } if(fabs(zmax-zmin)/static_cast(sampleZ) > maxDimension) { sampleZno = static_cast(fabs(zmax-zmin)/maxDimension); } if (twoDimension == true){ if(sampleY < 2){ sampleYno = 1; }else if(sampleZ < 2){ sampleZno = 1; } } sample->SetSampleDimensions(sampleXno,sampleYno,sampleZno); sample->ComputeNormalsOff(); //sample->Update(); vtkSmartPointer contours = vtkContourFilter::New(); contours->SetInputConnection(sample->GetOutputPort()); contours->SetNumberOfContours(1); contours->SetValue(0,0.0); vtkSmartPointer polydata = vtkSmartPointer::New(); contours->Update(); polydata->DeepCopy(contours->GetOutput()); //polydata->Update(); vtkSmartPointer pbColors = vtkSmartPointer::New(); pbColors->SetName("pbColors"); pbColors->SetNumberOfComponents(3); Vector3r color = particleColour; //Vector3r(0,100,0); if (b->isDynamic() == false){ color = Vector3r(157,157,157); } unsigned char c[3]; //c = {color[0],color[1],color[2]}; c[0]=color[0]; c[1]=color[1]; c[2]=color[2]; int nbCells=polydata->GetNumberOfPoints(); for (int i=0;iInsertNextTupleValue(c); } polydata->GetPointData()->SetScalars(pbColors); //polydata->Update(); Vector3r centre (b->state->pos[0], b->state->pos[1], b->state->pos[2]); Quaternionr orientation= b->state->ori; orientation.normalize(); AngleAxisr aa(orientation); Vector3r axis = aa.axis(); /* axis.normalize(); */ double angle = aa.angle()/3.14159*180.0; double xAxis = axis[0]; double yAxis = axis[1]; double zAxis = axis[2]; vtkSmartPointer transformFilter = vtkSmartPointer::New(); transformFilter->SetInputData( polydata ); vtkSmartPointer transform = vtkSmartPointer::New(); transformFilter->SetTransform( transform ); transform->PostMultiply(); transform->Translate (centre[0], centre[1],centre[2]); //transform->RotateWXYZ(angle,xAxis, yAxis, zAxis); //transformFilter->Update(); appendFilter->AddInputConnection(transformFilter-> GetOutputPort()); // ################## velocity #################### if(REC_VELOCITY == true){ vtkIdType pid[1]; Vector3r pos(b->state->pos); pid[0] = pbPos->InsertNextPoint(pos[0], pos[1], pos[2]); pbCells->InsertNextCell(1,pid); const Vector3r& vel = b->state->vel; float v[3]; //v = { vel[0],vel[1],vel[2] }; v[0]=vel[0]; v[1]=vel[1]; v[2]=vel[2]; pbLinVelVec->InsertNextTupleValue(v); pbLinVelLen->InsertNextValue(vel.norm()); const Vector3r& angVel = b->state->angVel; float av[3]; //av = { angVel[0],angVel[1],angVel[2] }; av[0]=angVel[0]; av[1]=angVel[1]; av[2]=angVel[2]; pbAngVelVec->InsertNextTupleValue(av); pbAngVelLen->InsertNextValue(angVel.norm()); //if(b->state->refPos.squaredNorm()>0.001){ //if initialized Vector3r displacement = pos- b->state->refPos; float disp[3]; disp[0]=displacement[0]; disp[1]=displacement[1]; disp[2]=displacement[2]; pbDisplacementVec->InsertNextTupleValue(disp); //} } // ################ velocity ########################### polydata->DeleteCells(); sample->Delete(); contours->Delete(); //function->Delete(); //#if 0 if(b->isClump()==true){ //boolFunction->Delete(); for(int i=0; i < ImplicitBoolNo; i++){ functionBool[i]->Delete(); } //boolFunction = NULL; } //#endif sample = NULL; contours = NULL; } if(REC_VELOCITY == true){ vtkSmartPointer pbUg = vtkSmartPointer::New(); pbUg->SetPoints(pbPos); pbUg->SetCells(VTK_VERTEX, pbCells); pbUg->GetPointData()->AddArray(pbLinVelVec); pbUg->GetPointData()->AddArray(pbAngVelVec); pbUg->GetPointData()->AddArray(pbLinVelLen); pbUg->GetPointData()->AddArray(pbAngVelLen); pbUg->GetPointData()->AddArray(pbDisplacementVec); vtkSmartPointer writerA = vtkSmartPointer::New(); writerA->SetDataModeToAscii(); string fv=fileName+"vel."+std::to_string(scene->iter)+".vtu"; writerA->SetFileName(fv.c_str()); writerA->SetInputData(pbUg); writerA->Write(); //writerA->Delete(); //pbUg->Delete(); } //###################### bodyId ############################### if(REC_ID == true){ #if 0 vtkSmartPointer writerA = vtkXMLPolyDataWriter::New(); writerA->SetDataModeToAscii(); string fn=fileName+"-Id."+std::to_string(scene->iter)+".vtp"; writerA->SetFileName(fn.c_str()); writerA->SetInputConnection(appendFilterID->GetOutputPort());//(extrude->GetOutputPort()); writerA->Write(); writerA->Delete(); #endif //#if 0 vtkSmartPointer pbUg = vtkSmartPointer::New(); pbUg->SetPoints(pbPosID); pbUg->SetCells(VTK_VERTEX, pbIdCells); pbUg->GetPointData()->AddArray(blockId); vtkSmartPointer writerA = vtkSmartPointer::New(); writerA->SetDataModeToAscii(); string fv=fileName+"Id."+std::to_string(scene->iter)+".vtu"; writerA->SetFileName(fv.c_str()); writerA->SetInputData(pbUg); writerA->Write(); //writerA->Delete(); //pbUg->Delete(); //#endif } #if 0 if(REC_ID== true){ int counter =0; FOREACH(const shared_ptr& b, *scene->bodies){ if (!b) {continue;} if (b->isClumpMember()==true) {continue;} textArray2[counter]->Delete(); //txtMapper[count]->Delete(); extrude[counter]->Delete(); counter++; } } #endif // ################## contact point #################### if(REC_INTERACTION == true){ int count = 0; FOREACH(const shared_ptr& I, *scene->interactions){ if(!I->isReal()) { continue; } const KnKsPBPhys* phys = YADE_CAST(I->phys.get()); const ScGeom* geom = YADE_CAST(I->geom.get()); vtkIdType pid[1]; Vector3r pos(geom->contactPoint); pid[0] = pbContactPoint->InsertNextPoint(pos[0], pos[1], pos[2]); pbCellsContact->InsertNextCell(1,pid); //intrBodyPos->InsertNextPoint(geom->contactPoint[0],geom->contactPoint[1],geom->contactPoint[2]); // gives _signed_ scalar of normal force, following the convention used in the respective constitutive law float fn[3]={phys->normalForce[0],phys->normalForce[1], phys->normalForce[2]}; float fs[3]={phys->shearForce[0], phys->shearForce[1], phys->shearForce[2]}; float totalForce[3] = {fn[0]+fs[0], fn[1]+fs[1], fn[2]+fs[2]}; float totalStress[3] = {0.0,0.0,0.0}; //{totalForce[0]/phys->contactArea, totalForce[1]/phys->contactArea, totalForce[2]/phys->contactArea}; float mobilizedShear = phys->mobilizedShear; pbTotalForce->InsertNextTupleValue(totalForce); pbMobilizedShear->InsertNextValue(mobilizedShear); pbNormalForce->InsertNextTupleValue(fn); pbShearForce->InsertNextTupleValue(fs); pbTotalStress->InsertNextTupleValue(totalStress); count++; } if(count>0){ vtkSmartPointer pbUgCP = vtkSmartPointer::New(); pbUgCP->SetPoints(pbContactPoint); pbUgCP->SetCells(VTK_VERTEX, pbCellsContact); pbUgCP->GetPointData()->AddArray(pbNormalForce); pbUgCP->GetPointData()->AddArray(pbShearForce); pbUgCP->GetPointData()->AddArray(pbTotalForce); pbUgCP->GetPointData()->AddArray(pbMobilizedShear); pbUgCP->GetPointData()->AddArray(pbTotalStress); vtkSmartPointer writerB = vtkSmartPointer::New(); writerB->SetDataModeToAscii(); string fcontact=fileName+"contactPoint."+std::to_string(scene->iter)+".vtu"; writerB->SetFileName(fcontact.c_str()); writerB->SetInputData(pbUgCP); writerB->Write(); //writerB->Delete(); //pbUgCP->Delete(); } } // ################ contact point ########################### vtkSmartPointer writer = vtkXMLPolyDataWriter::New(); writer->SetDataModeToAscii(); string fn=fileName+"-pb."+std::to_string(scene->iter)+".vtp"; writer->SetFileName(fn.c_str()); writer->SetInputConnection(appendFilter->GetOutputPort()); writer->Write(); writer->Delete(); //intrBodyPos->Delete(); //intrForceN->Delete(); //intrAbsForceT->Delete(); //pbContactPoint->Delete(); //pbCellsContact->Delete(); //pbNormalForce->Delete(); //pbShearForce->Delete(); //pbCells->Delete(); //pbLinVelVec->Delete(); //pbLinVelLen->Delete(); //pbAngVelVec->Delete(); //pbAngVelLen->Delete(); } void PotentialBlockVTKRecorder::action(){ if(fileName.size()==0) return; vtkSmartPointer pbPos = vtkSmartPointer::New(); vtkSmartPointer appendFilter = vtkSmartPointer::New(); vtkSmartPointer appendFilterID = vtkSmartPointer::New(); //vtkSmartPointer transformFilter = vtkSmartPointer::New(); //vtkSmartPointer transform = vtkSmartPointer::New(); // interactions ############################################### vtkSmartPointer intrBodyPos = vtkSmartPointer::New(); vtkSmartPointer intrCells = vtkSmartPointer::New(); vtkSmartPointer intrForceN = vtkSmartPointer::New(); intrForceN->SetNumberOfComponents(3); intrForceN->SetName("forceN"); vtkSmartPointer intrAbsForceT = vtkSmartPointer::New(); intrAbsForceT->SetNumberOfComponents(1); intrAbsForceT->SetName("absForceT"); // interactions ############################################### // interaction contact point ############################################### vtkSmartPointer pbContactPoint = vtkSmartPointer::New(); vtkSmartPointer pbCellsContact = vtkSmartPointer::New(); vtkSmartPointer pbNormalForce = vtkSmartPointer::New(); pbNormalForce->SetNumberOfComponents(3); pbNormalForce->SetName("normalForce"); //Linear velocity in Vector3 form vtkSmartPointer pbShearForce = vtkSmartPointer::New(); pbShearForce->SetNumberOfComponents(3); pbShearForce->SetName("shearForce"); //Angular velocity in Vector3 form // interactions contact point############################################### // velocity ################################################### vtkSmartPointer pbCells = vtkSmartPointer::New(); vtkSmartPointer pbLinVelVec = vtkSmartPointer::New(); pbLinVelVec->SetNumberOfComponents(3); pbLinVelVec->SetName("linVelVec"); //Linear velocity in Vector3 form vtkSmartPointer pbLinVelLen = vtkSmartPointer::New(); pbLinVelLen->SetNumberOfComponents(1); pbLinVelLen->SetName("linVelLen"); //Length (magnitude) of linear velocity vtkSmartPointer pbAngVelVec = vtkSmartPointer::New(); pbAngVelVec->SetNumberOfComponents(3); pbAngVelVec->SetName("angVelVec"); //Angular velocity in Vector3 form vtkSmartPointer pbAngVelLen = vtkSmartPointer::New(); pbAngVelLen->SetNumberOfComponents(1); pbAngVelLen->SetName("angVelLen"); //Length (magnitude) of angular velocity // velocity #################################################### // bodyId ############################################################## //#if 0 vtkSmartPointer pbPosID = vtkSmartPointer::New(); vtkSmartPointer pbIdCells = vtkSmartPointer::New(); vtkSmartPointer blockId = vtkSmartPointer::New(); blockId->SetNumberOfComponents(1); blockId->SetName("id"); // bodyId ############################################################## //#endif int countID = 0; vtkSmartPointer textArray2[scene->bodies->size()]; vtkSmartPointer txtMapper[scene->bodies->size()]; vtkSmartPointer extrude[scene->bodies->size()]; vtkSmartPointer textActor[scene->bodies->size()]; FOREACH(const shared_ptr& b, *scene->bodies){ if (!b) continue; if (b->isClump() == true) continue; const PotentialBlock* pb=dynamic_cast(b->shape.get()); if(!pb) continue; if(REC_ID==true){ //#if 0 blockId->InsertNextValue(b->getId()); vtkIdType pid[1]; Vector3r pos(b->state->pos); pid[0] = pbPosID->InsertNextPoint(pos[0], pos[1], pos[2]); pbIdCells->InsertNextCell(1,pid); //#endif countID++; } //vtkSmartPointer function = ImpFuncPB::New(); function->a = pb->a; function->b = pb->b; function->c = pb->c; function->d = pb->d; function->R = pb->R; function->r = pb->r; function->k = pb->k; Eigen::Matrix3d directionCos = b->state->ori.conjugate().toRotationMatrix(); int count = 0; for (int i=0; i<3; i++){ for (int j=0; j<3; j++){ //function->rotationMatrix[count] = directionCos(j,i); function->rotationMatrix(i,j) = directionCos(j,i); count++; } } vtkSmartPointer sample = vtkSampleFunction::New(); sample->SetImplicitFunction(function); double value = 1.05*pb->R; double xmin = -pb->halfSize.x(); double xmax = pb->halfSize.x(); double ymin = -pb->halfSize.y(); double ymax=pb->halfSize.y(); double zmin=-pb->halfSize.z(); double zmax=pb->halfSize.z(); //double xmin = -value; double xmax = value; double ymin = -value; double ymax=value; double zmin=-value; double zmax=value; //double xmin = -std::max(pb->minAabb.x(),pb->maxAabb.x()); double xmax = -xmin; double ymin = -std::max(pb->minAabb.y(),pb->maxAabb.y()); double ymax=-ymin; double zmin=-std::max(pb->minAabb.z(),pb->maxAabb.z()); double zmax=-zmin; if(twoDimension==true){ ymax = 0.0; ymin = 0.0; } sample->SetModelBounds(xmin, xmax, ymin, ymax, zmin, zmax); //sample->SetModelBounds(pb->minAabb.x(), pb->maxAabb.x(), pb->minAabb.y(), pb->maxAabb.y(), pb->minAabb.z(), pb->maxAabb.z()); int sampleXno = sampleX; int sampleYno = sampleY; int sampleZno = sampleZ; if(fabs(xmax-xmin)/static_cast(sampleX) > maxDimension) { sampleXno = static_cast(fabs(xmax-xmin)/maxDimension); } if(fabs(ymax-ymin)/static_cast(sampleY) > maxDimension) { sampleYno = static_cast(fabs(ymax-ymin)/maxDimension); } if(fabs(zmax-zmin)/static_cast(sampleZ) > maxDimension) { sampleZno = static_cast(fabs(zmax-zmin)/maxDimension); } if(twoDimension==true){sampleYno=1;} sample->SetSampleDimensions(sampleXno,sampleYno,sampleZno); sample->ComputeNormalsOff(); //sample->Update(); vtkSmartPointer contours = vtkContourFilter::New(); contours->SetInputConnection(sample->GetOutputPort()); contours->SetNumberOfContours(1); contours->SetValue(0,0.0); vtkSmartPointer polydata = vtkSmartPointer::New(); contours->Update(); polydata->DeepCopy(contours->GetOutput()); //polydata->Update(); vtkSmartPointer pbColors = vtkSmartPointer::New(); pbColors->SetName("pbColors"); pbColors->SetNumberOfComponents(3); Vector3r color = pb->color; //Vector3r(0,100,0); //if (b->isDynamic() == false){ color = Vector3r(157,157,157); } color = Vector3r(157,157,157); unsigned char c[3]; //c = {color[0],color[1],color[2]}; c[0]=color[0]; c[1]=color[1]; c[2]=color[2]; int nbCells=polydata->GetNumberOfPoints(); for (int i=0;iInsertNextTupleValue(c); } polydata->GetPointData()->SetScalars(pbColors); //polydata->Update(); Vector3r centre (b->state->pos[0], b->state->pos[1], b->state->pos[2]); Quaternionr orientation= b->state->ori; orientation.normalize(); AngleAxisr aa(orientation); Vector3r axis = aa.axis(); /* axis.normalize(); */ double angle = aa.angle()/3.14159*180.0; double xAxis = axis[0]; double yAxis = axis[1]; double zAxis = axis[2]; vtkSmartPointer transformFilter = vtkSmartPointer::New(); transformFilter->SetInputData( polydata ); vtkSmartPointer transform = vtkSmartPointer::New(); transformFilter->SetTransform( transform ); transform->PostMultiply(); transform->Translate (centre[0], centre[1],centre[2]); //transform->RotateWXYZ(angle,xAxis, yAxis, zAxis); //transformFilter->Update(); appendFilter->AddInputConnection(transformFilter-> GetOutputPort()); // ################## velocity #################### if(REC_VELOCITY == true){ vtkIdType pid[1]; Vector3r pos(b->state->pos); pid[0] = pbPos->InsertNextPoint(pos[0], pos[1], pos[2]); pbCells->InsertNextCell(1,pid); const Vector3r& vel = b->state->vel; float v[3]; //v = { vel[0],vel[1],vel[2] }; v[0]=vel[0]; v[1]=vel[1]; v[2]=vel[2]; pbLinVelVec->InsertNextTupleValue(v); pbLinVelLen->InsertNextValue(vel.norm()); const Vector3r& angVel = b->state->angVel; float av[3]; //av = { angVel[0],angVel[1],angVel[2] }; av[0]=angVel[0]; av[1]=angVel[1]; av[2]=angVel[2]; pbAngVelVec->InsertNextTupleValue(av); pbAngVelLen->InsertNextValue(angVel.norm()); } // ################ velocity ########################### polydata->DeleteCells(); sample->Delete(); contours->Delete(); //function->Delete(); sample = NULL; contours = NULL; } if(REC_VELOCITY == true){ vtkSmartPointer pbUg = vtkSmartPointer::New(); pbUg->SetPoints(pbPos); pbUg->SetCells(VTK_VERTEX, pbCells); pbUg->GetPointData()->AddArray(pbLinVelVec); pbUg->GetPointData()->AddArray(pbAngVelVec); pbUg->GetPointData()->AddArray(pbLinVelLen); pbUg->GetPointData()->AddArray(pbAngVelLen); vtkSmartPointer writerA = vtkSmartPointer::New(); writerA->SetDataModeToAscii(); string fv=fileName+"vel."+std::to_string(scene->iter)+".vtu"; writerA->SetFileName(fv.c_str()); writerA->SetInputData(pbUg); writerA->Write(); //writerA->Delete(); //pbUg->Delete(); } //###################### bodyId ############################### if(REC_ID == true){ #if 0 vtkSmartPointer writerA = vtkXMLPolyDataWriter::New(); writerA->SetDataModeToAscii(); string fn=fileName+"-Id."+std::to_string(scene->iter)+".vtp"; writerA->SetFileName(fn.c_str()); writerA->SetInputConnection(appendFilterID->GetOutputPort());//(extrude->GetOutputPort()); writerA->Write(); writerA->Delete(); #endif //#if 0 vtkSmartPointer pbUg = vtkSmartPointer::New(); pbUg->SetPoints(pbPosID); pbUg->SetCells(VTK_VERTEX, pbIdCells); pbUg->GetPointData()->AddArray(blockId); vtkSmartPointer writerA = vtkSmartPointer::New(); writerA->SetDataModeToAscii(); string fv=fileName+"Id."+std::to_string(scene->iter)+".vtu"; writerA->SetFileName(fv.c_str()); writerA->SetInputData(pbUg); writerA->Write(); //writerA->Delete(); //pbUg->Delete(); //#endif } //#if 0 // ################## contact point #################### if(REC_INTERACTION == true){ int count = 0; FOREACH(const shared_ptr& I, *scene->interactions){ if(!I->isReal()) { continue; } const KnKsPBPhys* phys = YADE_CAST(I->phys.get()); const ScGeom* geom = YADE_CAST(I->geom.get()); vtkIdType pid[1]; Vector3r pos(geom->contactPoint); pid[0] = pbContactPoint->InsertNextPoint(pos[0], pos[1], pos[2]); pbCellsContact->InsertNextCell(1,pid); //intrBodyPos->InsertNextPoint(geom->contactPoint[0],geom->contactPoint[1],geom->contactPoint[2]); // gives _signed_ scalar of normal force, following the convention used in the respective constitutive law float fn[3]={phys->normalForce[0],phys->normalForce[1], phys->normalForce[2]}; float fs[3]={phys->shearForce[0], phys->shearForce[1], phys->shearForce[2]}; pbNormalForce->InsertNextTupleValue(fn); pbShearForce->InsertNextTupleValue(fs); count++; } if(count>0){ vtkSmartPointer pbUgCP = vtkSmartPointer::New(); pbUgCP->SetPoints(pbContactPoint); pbUgCP->SetCells(VTK_VERTEX, pbCellsContact); pbUgCP->GetPointData()->AddArray(pbNormalForce); pbUgCP->GetPointData()->AddArray(pbShearForce); vtkSmartPointer writerB = vtkSmartPointer::New(); writerB->SetDataModeToAscii(); string fcontact=fileName+"contactPoint."+std::to_string(scene->iter)+".vtu"; writerB->SetFileName(fcontact.c_str()); writerB->SetInputData(pbUgCP); writerB->Write(); //writerB->Delete(); //pbUgCP->Delete(); } } //#endif // ################ contact point ########################### vtkSmartPointer writer = vtkXMLPolyDataWriter::New(); writer->SetDataModeToAscii(); string fn=fileName+"-pb."+std::to_string(scene->iter)+".vtp"; writer->SetFileName(fn.c_str()); writer->SetInputConnection(appendFilter->GetOutputPort()); writer->Write(); writer->Delete(); //intrBodyPos->Delete(); //intrForceN->Delete(); //intrAbsForceT->Delete(); //pbContactPoint->Delete(); //pbCellsContact->Delete(); //pbNormalForce->Delete(); //pbShearForce->Delete(); //pbCells->Delete(); //pbLinVelVec->Delete(); //pbLinVelLen->Delete(); //pbAngVelVec->Delete(); //pbAngVelLen->Delete(); } YADE_PLUGIN(/*(Gl1_PotentialBlock)*/(PotentialBlockVTKRecorder)(PotentialBlockVTKRecorderTunnel)); //YADE_REQUIRE_FEATURE(OPENGL) #endif // YADE_POTENTIAL_BLOCKS trunk-2018.02b/pkg/common/Gl1_PotentialBlock.hpp000077500000000000000000000131011324306050200213640ustar00rootroot00000000000000/* CWBoon 2016 */ #ifdef YADE_POTENTIAL_BLOCKS #pragma once #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 #include class ImpFuncPB : public vtkImplicitFunction { public: vtkTypeMacro(ImpFuncPB,vtkImplicitFunction); //void PrintSelf(ostream& os, vtkIndent indent); // Description // Create a new function static ImpFuncPB * New(void); vectora; vectorb; vectorc; vectord; double k; double r; double R; Eigen::Matrix3d rotationMatrix; bool clump; double clumpMemberCentreX; double clumpMemberCentreY; double clumpMemberCentreZ; // Description // Evaluate function double FunctionValue(double x[3]); double EvaluateFunction(double x[3]){ //return this->vtkImplicitFunction::EvaluateFunction(x); return FunctionValue(x); }; double EvaluateFunction(double x, double y, double z) { return this->vtkImplicitFunction::EvaluateFunction(x, y, z); }; // Description // Evaluate gradient for function void EvaluateGradient(double x[3], double n[3]){ }; // If you need to set parameters, add methods here protected: ImpFuncPB(); ~ImpFuncPB(); ImpFuncPB(const ImpFuncPB&) {} void operator=(const ImpFuncPB&) {} // Add parameters/members here if you need }; #if 0 class Gl1_PotentialBlock : public GlShapeFunctor { private : MarchingCube mc; Vector3r min,max; vector > > scalarField,weights; void generateScalarField(const shared_ptr& cm); void calcMinMax(const shared_ptr& cm); float oldIsoValue,oldIsoSec,oldIsoThick; Vector3r isoStep; public : struct Leaf{ Vector3r centre; Leaf(Vector3r pos){centre = pos;} Leaf(){centre = Vector3r(0,0,0);} }; struct scalarF{ vector > > scalarField2; vector triangles; vector normals; int nbTriangles; }; virtual void go(const shared_ptr&, const shared_ptr&,bool,const GLViewInfo&); double evaluateF(const shared_ptr& cm, double x, double y, double z); static vector SF ; void clearMemory(); RENDERS(PotentialBlock); //YADE_CLASS_BASE_DOC_STATICATTRS(Gl1_PotentialBlock,GlShapeFunctor,"Renders :yref:`Sphere` object", //(( vector, SF ," ")) //); YADE_CLASS_BASE_DOC_STATICATTRS(Gl1_PotentialBlock,GlShapeFunctor,"Renders :yref:`Sphere` object", ((int,sizeX,80,,"Number of divisions in the x direction for triangulation")) ((int,sizeY,80,,"Number of divisions in the x direction for triangulation")) ((int,sizeZ,80,,"Number of divisions in the y direction for triangulation")) ((bool,store,false,,"Number of divisions in the z direction for triangulation")) ((bool,initialized,false,,"Number of divisions in the z direction for triangulation")) ); }; REGISTER_SERIALIZABLE(Gl1_PotentialBlock); #endif class PotentialBlockVTKRecorder: public PeriodicEngine{ public: vtkSmartPointer function; virtual void action(void); YADE_CLASS_BASE_DOC_ATTRS_CTOR_PY(PotentialBlockVTKRecorder,PeriodicEngine,"Engine recording potential blocks as surfaces into files with given periodicity.", ((string,fileName,,,"File prefix to save to")) ((int,sampleX,30,,"size of contact point")) ((int,sampleY,30,,"size of contact point")) ((int,sampleZ,30,,"size of contact point")) ((double,maxDimension,30,,"size of contact point")) ((bool,twoDimension,false,,"size of contact point")) ((bool,REC_INTERACTION,false,,"contact point and forces")) ((bool,REC_COLORS,false,,"colors")) ((bool,REC_VELOCITY,false,,"velocity")) ((bool,REC_ID,true,,"id")) , function = ImpFuncPB::New(); , ); }; REGISTER_SERIALIZABLE(PotentialBlockVTKRecorder); class PotentialBlockVTKRecorderTunnel: public PeriodicEngine{ public: vtkSmartPointer function; virtual void action(void); YADE_CLASS_BASE_DOC_ATTRS_CTOR_PY(PotentialBlockVTKRecorderTunnel,PeriodicEngine,"Engine recording potential blocks as surfaces into files with given periodicity.", ((string,fileName,,,"File prefix to save to")) ((int,sampleX,30,,"size of contact point")) ((int,sampleY,30,,"size of contact point")) ((int,sampleZ,30,,"size of contact point")) ((double,maxDimension,30,,"size of contact point")) ((bool,twoDimension,false,,"size of contact point")) ((bool,REC_INTERACTION,false,,"contact point and forces")) ((bool,REC_COLORS,false,,"colors")) ((bool,REC_VELOCITY,false,,"velocity")) ((bool,REC_ID,true,,"id")) , function = ImpFuncPB::New(); , ); }; REGISTER_SERIALIZABLE(PotentialBlockVTKRecorderTunnel); #endif // YADE_POTENTIAL_BLOCKS trunk-2018.02b/pkg/common/Gl1_PotentialParticle.cpp000066400000000000000000000471321324306050200221000ustar00rootroot00000000000000/*CWBoon 2015 */ #ifdef YADE_POTENTIAL_PARTICLES #include "Gl1_PotentialParticle.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 #include #include #include #include #include #include #include #include #include //#include #include #include #include #ifdef YADE_OPENGL void Gl1_PotentialParticle::calcMinMax(const PotentialParticle& pp) { int planeNo = pp.d.size(); Real maxD = pp.d[0]; for (int i=0; i maxD) { maxD = pp.d[i]; } } min = -aabbEnlargeFactor*pp.minAabb; max = aabbEnlargeFactor*pp.maxAabb; Real dx = (max[0]-min[0])/((Real)(sizeX-1)); Real dy = (max[1]-min[1])/((Real)(sizeY-1)); Real dz = (max[2]-min[2])/((Real)(sizeZ-1)); isoStep=Vector3r(dx,dy,dz); } void Gl1_PotentialParticle::generateScalarField(const PotentialParticle& pp) { for(int i=0; i Gl1_PotentialParticle::SF; int Gl1_PotentialParticle::sizeX, Gl1_PotentialParticle::sizeY, Gl1_PotentialParticle::sizeZ; Real Gl1_PotentialParticle::aabbEnlargeFactor; bool Gl1_PotentialParticle::store; bool Gl1_PotentialParticle::initialized; void Gl1_PotentialParticle::go( const shared_ptr& cm, const shared_ptr& state ,bool wire2, const GLViewInfo&) { PotentialParticle* pp = static_cast(cm.get()); int shapeId = pp->id; if(store == false) { if(SF.size()>0) { SF.clear(); initialized = false; } } if(initialized == false ) { FOREACH(const shared_ptr& b, *scene->bodies) { if (!b) continue; PotentialParticle* cmbody = dynamic_cast(b->shape.get()); if (!cmbody) continue; calcMinMax(*cmbody); mc.init(sizeX,sizeY,sizeZ,min,max); mc.resizeScalarField(scalarField,sizeX,sizeY,sizeZ); SF.push_back(scalarF()); generateScalarField(*cmbody); mc.computeTriangulation(scalarField,0.0); SF[b->id].triangles = mc.getTriangles(); SF[b->id].normals = mc.getNormals(); SF[b->id].nbTriangles = mc.getNbTriangles(); for(unsigned int i=0; icolor[0],cm->color[1],cm->color[2])); glColor3v(cm->color); const vector& triangles = SF[shapeId].triangles; //mc.getTriangles(); int nbTriangles = SF[shapeId].nbTriangles; // //mc.getNbTriangles(); const vector& normals = SF[shapeId].normals; //mc.getNormals(); glDisable(GL_CULL_FACE); glEnable(GL_LIGHTING); // 2D glEnable(GL_NORMALIZE); glBegin(GL_TRIANGLES); for(int i=0; i<3*nbTriangles; ++i) { glNormal3v(normals[i]); glVertex3v(triangles[i]); glNormal3v(normals[++i]); glVertex3v(triangles[i]); glNormal3v(normals[++i]); glVertex3v(triangles[i]); } glEnd(); return; } Real Gl1_PotentialParticle::evaluateF(const PotentialParticle& pp, Real x, Real y, Real z) { Real k = pp.k; Real r = pp.r; Real R = pp.R; int planeNo = pp.a.size(); vectora; vectorb; vectorc; vectord; vectorp; Real pSum3 = 0.0; for (int i=0; ip; Real pSum2 = 0.0; if (!clump) { Eigen::Vector3d xori(x[0],x[1],x[2]); Eigen::Vector3d xlocal = rotationMatrix*xori; xlocal[0] = rotationMatrix(0,0)*x[0] + rotationMatrix(0,1)*x[1] + rotationMatrix(0,2)*x[2]; xlocal[1] = rotationMatrix(1,0)*x[0] + rotationMatrix(1,1)*x[1] + rotationMatrix(1,2)*x[2]; xlocal[2] = rotationMatrix(2,0)*x[0] + rotationMatrix(2,1)*x[1] + rotationMatrix(2,2)*x[2]; //std::cout<<"rotationMatrix: "< pbPos = vtkSmartPointer::New(); vtkSmartPointer appendFilter = vtkSmartPointer::New(); vtkSmartPointer appendFilterID = vtkSmartPointer::New(); //vtkSmartPointer transformFilter = vtkSmartPointer::New(); //vtkSmartPointer transform = vtkSmartPointer::New(); // interactions ############################################### vtkSmartPointer intrBodyPos = vtkSmartPointer::New(); vtkSmartPointer intrCells = vtkSmartPointer::New(); vtkSmartPointer intrForceN = vtkSmartPointer::New(); intrForceN->SetNumberOfComponents(3); intrForceN->SetName("forceN"); vtkSmartPointer intrAbsForceT = vtkSmartPointer::New(); intrAbsForceT->SetNumberOfComponents(1); intrAbsForceT->SetName("absForceT"); // interactions ############################################### // interaction contact point ############################################### vtkSmartPointer pbContactPoint = vtkSmartPointer::New(); vtkSmartPointer pbCellsContact = vtkSmartPointer::New(); vtkSmartPointer pbNormalForce = vtkSmartPointer::New(); pbNormalForce->SetNumberOfComponents(3); pbNormalForce->SetName("normalForce"); //Linear velocity in Vector3 form vtkSmartPointer pbShearForce = vtkSmartPointer::New(); pbShearForce->SetNumberOfComponents(3); pbShearForce->SetName("shearForce"); //Angular velocity in Vector3 form // interactions contact point############################################### // velocity ################################################### vtkSmartPointer pbCells = vtkSmartPointer::New(); vtkSmartPointer pbLinVelVec = vtkSmartPointer::New(); pbLinVelVec->SetNumberOfComponents(3); pbLinVelVec->SetName("linVelVec"); //Linear velocity in Vector3 form vtkSmartPointer pbLinVelLen = vtkSmartPointer::New(); pbLinVelLen->SetNumberOfComponents(1); pbLinVelLen->SetName("linVelLen"); //Length (magnitude) of linear velocity vtkSmartPointer pbAngVelVec = vtkSmartPointer::New(); pbAngVelVec->SetNumberOfComponents(3); pbAngVelVec->SetName("angVelVec"); //Angular velocity in Vector3 form vtkSmartPointer pbAngVelLen = vtkSmartPointer::New(); pbAngVelLen->SetNumberOfComponents(1); pbAngVelLen->SetName("angVelLen"); //Length (magnitude) of angular velocity // velocity #################################################### // bodyId ############################################################## vtkSmartPointer pbPosID = vtkSmartPointer::New(); vtkSmartPointer pbIdCells = vtkSmartPointer::New(); vtkSmartPointer blockId = vtkSmartPointer::New(); blockId->SetNumberOfComponents(1); blockId->SetName("id"); // bodyId ############################################################## int countID = 0; vtkSmartPointer textArray2[scene->bodies->size()]; vtkSmartPointer txtMapper[scene->bodies->size()]; vtkSmartPointer extrude[scene->bodies->size()]; vtkSmartPointer textActor[scene->bodies->size()]; FOREACH(const shared_ptr& b, *scene->bodies) { if (!b) continue; if (b->isClump() == true) continue; const PotentialParticle* pb=dynamic_cast(b->shape.get()); if(!pb) continue; if(REC_ID==true) { blockId->InsertNextValue(b->getId()); vtkIdType pid[1]; Vector3r pos(b->state->pos); pid[0] = pbPosID->InsertNextPoint(pos[0], pos[1], pos[2]); pbIdCells->InsertNextCell(1,pid); countID++; } //vtkSmartPointer function = ImpFunc::New(); function->a = pb->a; function->b = pb->b; function->c = pb->c; function->d = pb->d; function->R = pb->R; function->r = pb->r; function->k = pb->k; Eigen::Matrix3d directionCos = b->state->ori.conjugate().toRotationMatrix(); int count = 0; for (int i=0; i<3; i++) { for (int j=0; j<3; j++) { //function->rotationMatrix[count] = directionCos(j,i); function->rotationMatrix(i,j) = directionCos(j,i); count++; } } vtkSmartPointer sample = vtkSampleFunction::New(); sample->SetImplicitFunction(function); Real xmin = -std::max(pb->minAabb.x(),pb->maxAabb.x()); Real xmax = -xmin; Real ymin = -std::max(pb->minAabb.y(),pb->maxAabb.y()); Real ymax=-ymin; Real zmin=-std::max(pb->minAabb.z(),pb->maxAabb.z()); Real zmax=-zmin; if(twoDimension==true) { ymax = 0.0; ymin = 0.0; } sample->SetModelBounds(xmin, xmax, ymin, ymax, zmin, zmax); //sample->SetModelBounds(pb->minAabb.x(), pb->maxAabb.x(), pb->minAabb.y(), pb->maxAabb.y(), pb->minAabb.z(), pb->maxAabb.z()); int sampleXno = sampleX; int sampleYno = sampleY; int sampleZno = sampleZ; if(fabs(xmax-xmin)/static_cast(sampleX) > maxDimension) { sampleXno = static_cast(fabs(xmax-xmin)/maxDimension); } if(fabs(ymax-ymin)/static_cast(sampleY) > maxDimension) { sampleYno = static_cast(fabs(ymax-ymin)/maxDimension); } if(fabs(zmax-zmin)/static_cast(sampleZ) > maxDimension) { sampleZno = static_cast(fabs(zmax-zmin)/maxDimension); } if(twoDimension==true) { sampleYno=1; } sample->SetSampleDimensions(sampleXno,sampleYno,sampleZno); sample->ComputeNormalsOff(); //sample->Update(); vtkSmartPointer contours = vtkContourFilter::New(); #ifdef YADE_VTK6 contours->SetInputData(sample->GetOutput()); #else contours->SetInput(sample->GetOutput()); #endif contours->SetNumberOfContours(1); contours->SetValue(0,0.0); vtkSmartPointer polydata = vtkSmartPointer::New(); contours->Update(); polydata->DeepCopy(contours->GetOutput()); //polydata->Update(); vtkSmartPointer pbColors = vtkSmartPointer::New(); pbColors->SetName("pbColors"); pbColors->SetNumberOfComponents(3); Vector3r color = pb->color; //Vector3r(0,100,0); if (b->isDynamic() == false) { color = Vector3r(157,157,157); } unsigned char c[3]; //c = {color[0],color[1],color[2]}; c[0]=color[0]; c[1]=color[1]; c[2]=color[2]; int nbCells=polydata->GetNumberOfPoints(); for (int i=0; iInsertNextTupleValue(c); } polydata->GetPointData()->SetScalars(pbColors); //polydata->Update(); Vector3r centre (b->state->pos[0], b->state->pos[1], b->state->pos[2]); Quaternionr orientation= b->state->ori; orientation.normalize(); AngleAxisr aa(orientation); //Vector3r axis = aa.axis(); vtkSmartPointer transformFilter = vtkSmartPointer::New(); #ifdef YADE_VTK6 transformFilter->SetInputData( polydata ); #else transformFilter->SetInput( polydata ); #endif vtkSmartPointer transform = vtkSmartPointer::New(); transformFilter->SetTransform( transform ); transform->PostMultiply(); transform->Translate (centre[0], centre[1],centre[2]); //transform->RotateWXYZ(angle,xAxis, yAxis, zAxis); //transformFilter->Update(); appendFilter->AddInputConnection(transformFilter-> GetOutputPort()); // ################## velocity #################### if(REC_VELOCITY == true) { vtkIdType pid[1]; Vector3r pos(b->state->pos); pid[0] = pbPos->InsertNextPoint(pos[0], pos[1], pos[2]); pbCells->InsertNextCell(1,pid); const Vector3r& vel = b->state->vel; float v[3]; //v = { vel[0],vel[1],vel[2] }; v[0]=vel[0]; v[1]=vel[1]; v[2]=vel[2]; pbLinVelVec->InsertNextTupleValue(v); pbLinVelLen->InsertNextValue(vel.norm()); const Vector3r& angVel = b->state->angVel; float av[3]; //av = { angVel[0],angVel[1],angVel[2] }; av[0]=angVel[0]; av[1]=angVel[1]; av[2]=angVel[2]; pbAngVelVec->InsertNextTupleValue(av); pbAngVelLen->InsertNextValue(angVel.norm()); } // ################ velocity ########################### polydata->DeleteCells(); sample->Delete(); contours->Delete(); //function->Delete(); sample = NULL; contours = NULL; } if(REC_VELOCITY == true) { vtkSmartPointer pbUg = vtkSmartPointer::New(); pbUg->SetPoints(pbPos); pbUg->SetCells(VTK_VERTEX, pbCells); pbUg->GetPointData()->AddArray(pbLinVelVec); pbUg->GetPointData()->AddArray(pbAngVelVec); pbUg->GetPointData()->AddArray(pbLinVelLen); pbUg->GetPointData()->AddArray(pbAngVelLen); vtkSmartPointer writerA = vtkSmartPointer::New(); writerA->SetDataModeToAscii(); string fv=fileName+"vel."+std::to_string(scene->iter)+".vtu"; writerA->SetFileName(fv.c_str()); #ifdef YADE_VTK6 writerA->SetInputData(pbUg); #else writerA->SetInput(pbUg); #endif writerA->Write(); //writerA->Delete(); //pbUg->Delete(); } //###################### bodyId ############################### if(REC_ID == true) { vtkSmartPointer pbUg = vtkSmartPointer::New(); pbUg->SetPoints(pbPosID); pbUg->SetCells(VTK_VERTEX, pbIdCells); pbUg->GetPointData()->AddArray(blockId); vtkSmartPointer writerA = vtkSmartPointer::New(); writerA->SetDataModeToAscii(); string fv=fileName+"Id."+std::to_string(scene->iter)+".vtu"; writerA->SetFileName(fv.c_str()); #ifdef YADE_VTK6 writerA->SetInputData(pbUg); #else writerA->SetInput(pbUg); #endif writerA->Write(); //writerA->Delete(); //pbUg->Delete(); } // ################## contact point #################### if(REC_INTERACTION == true) { int count = 0; FOREACH(const shared_ptr& I, *scene->interactions) { if(!I->isReal()) { continue; } const KnKsPhys* phys = YADE_CAST(I->phys.get()); const ScGeom* geom = YADE_CAST(I->geom.get()); vtkIdType pid[1]; Vector3r pos(geom->contactPoint); pid[0] = pbContactPoint->InsertNextPoint(pos[0], pos[1], pos[2]); pbCellsContact->InsertNextCell(1,pid); //intrBodyPos->InsertNextPoint(geom->contactPoint[0],geom->contactPoint[1],geom->contactPoint[2]); // gives _signed_ scalar of normal force, following the convention used in the respective constitutive law float fn[3]= {(float)phys->normalForce[0], (float)phys->normalForce[1], (float)phys->normalForce[2]}; float fs[3]= {(float)phys->shearForce[0], (float)phys->shearForce[1], (float)phys->shearForce[2]}; pbNormalForce->InsertNextTupleValue(fn); pbShearForce->InsertNextTupleValue(fs); count++; } if(count>0) { vtkSmartPointer pbUgCP = vtkSmartPointer::New(); pbUgCP->SetPoints(pbContactPoint); pbUgCP->SetCells(VTK_VERTEX, pbCellsContact); pbUgCP->GetPointData()->AddArray(pbNormalForce); pbUgCP->GetPointData()->AddArray(pbShearForce); vtkSmartPointer writerB = vtkSmartPointer::New(); writerB->SetDataModeToAscii(); string fcontact=fileName+"contactPoint."+std::to_string(scene->iter)+".vtu"; writerB->SetFileName(fcontact.c_str()); #ifdef YADE_VTK6 writerB->SetInputData(pbUgCP); #else writerB->SetInput(pbUgCP); #endif writerB->Write(); //writerB->Delete(); //pbUgCP->Delete(); } } // ################ contact point ########################### vtkSmartPointer writer = vtkXMLPolyDataWriter::New(); writer->SetDataModeToAscii(); string fn=fileName+"-pb."+std::to_string(scene->iter)+".vtp"; writer->SetFileName(fn.c_str()); writer->SetInputConnection(appendFilter->GetOutputPort()); writer->Write(); writer->Delete(); //intrBodyPos->Delete(); //intrForceN->Delete(); //intrAbsForceT->Delete(); //pbContactPoint->Delete(); //pbCellsContact->Delete(); //pbNormalForce->Delete(); //pbShearForce->Delete(); //pbCells->Delete(); //pbLinVelVec->Delete(); //pbLinVelLen->Delete(); //pbAngVelVec->Delete(); //pbAngVelLen->Delete(); } YADE_PLUGIN((Gl1_PotentialParticle)(PotentialParticleVTKRecorder)); //YADE_REQUIRE_FEATURE(OPENGL) #endif // YADE_POTENTIAL_PARTICLES trunk-2018.02b/pkg/common/Gl1_PotentialParticle.hpp000066400000000000000000000113701324306050200221000ustar00rootroot00000000000000/*CWBoon 2015 */ #pragma once #ifdef YADE_POTENTIAL_PARTICLES #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 #include class ImpFunc : public vtkImplicitFunction { public: vtkTypeMacro(ImpFunc,vtkImplicitFunction); //void PrintSelf(ostream& os, vtkIndent indent); // Description // Create a new function static ImpFunc * New(void); vectora; vectorb; vectorc; vectord; Real k; Real r; Real R; Eigen::Matrix3d rotationMatrix; bool clump; Real clumpMemberCentreX; Real clumpMemberCentreY; Real clumpMemberCentreZ; // Description // Evaluate function Real FunctionValue(Real x[3]); Real EvaluateFunction(Real x[3]) { //return this->vtkImplicitFunction::EvaluateFunction(x); return FunctionValue(x); }; Real EvaluateFunction(Real x, Real y, Real z) { return this->vtkImplicitFunction::EvaluateFunction(x, y, z); }; // Description // Evaluate gradient for function void EvaluateGradient(Real x[3], Real n[3]) { }; // If you need to set parameters, add methods here protected: ImpFunc(); ~ImpFunc(); ImpFunc(const ImpFunc&) {} void operator=(const ImpFunc&) {} // Add parameters/members here if you need }; #ifdef YADE_OPENGL class Gl1_PotentialParticle : public GlShapeFunctor { private : MarchingCube mc; Vector3r min,max; vector > > scalarField,weights; void generateScalarField(const PotentialParticle& pp); void calcMinMax(const PotentialParticle& pp); Real oldIsoValue,oldIsoSec,oldIsoThick; Vector3r isoStep; struct Leaf { Vector3r centre; Leaf(Vector3r pos) { centre = pos; } Leaf() { centre = Vector3r(0,0,0); } }; struct scalarF { vector > > scalarField2; vector triangles; vector normals; int nbTriangles; }; Real evaluateF(const PotentialParticle& pp, Real x, Real y, Real z); static vector SF ; public : virtual void go(const shared_ptr&, const shared_ptr&,bool,const GLViewInfo&); YADE_CLASS_BASE_DOC_STATICATTRS(Gl1_PotentialParticle,GlShapeFunctor,"Renders :yref:`PotentialParticle` object", ((int,sizeX,10,,"Number of divisions in the x direction for triangulation")) ((int,sizeY,10,,"Number of divisions in the y direction for triangulation")) ((int,sizeZ,10,,"Number of divisions in the z direction for triangulation")) ((bool,store,false,,"store computed triangulation or not")) ((bool,initialized,false,,"if triangulation is initialized")) ((Real,aabbEnlargeFactor,1.3,,"some factor for displaying algorithm, try different value if you have problems with displaying")) ); RENDERS(PotentialParticle); }; REGISTER_SERIALIZABLE(Gl1_PotentialParticle); #endif // YADE_OPENGL class PotentialParticleVTKRecorder: public PeriodicEngine { public: vtkSmartPointer function; virtual void action(void); YADE_CLASS_BASE_DOC_ATTRS_CTOR_PY(PotentialParticleVTKRecorder,PeriodicEngine,"Engine recording potential blocks as surfaces into files with given periodicity.", ((string,fileName,,,"File prefix to save to")) ((int,sampleX,30,,"Number of divisions in the x direction for triangulation")) ((int,sampleY,30,,"Number of divisions in the y direction for triangulation")) ((int,sampleZ,30,,"Number of divisions in the z direction for triangulation")) ((Real,maxDimension,30,,"max dimension")) ((bool,twoDimension,false,,"2D or not")) ((bool,REC_INTERACTION,false,,"contact point and forces")) ((bool,REC_COLORS,false,,"colors")) ((bool,REC_VELOCITY,false,,"velocity")) ((bool,REC_ID,true,,"id")) , function = ImpFunc::New(); , ); }; REGISTER_SERIALIZABLE(PotentialParticleVTKRecorder); #endif // YADE_POTENTIAL_PARTICLES trunk-2018.02b/pkg/common/Gl1_Primitives.cpp000066400000000000000000000217441324306050200206110ustar00rootroot00000000000000/************************************************************************* * Copyright (C) 2004 by Olivier Galizzi * * olivier.galizzi@imag.fr * * * * Copyright (C) 2008 by Sergei Dorofeenko * * sega@users.berlios.de * * * * © 2008 Václav Šmilauer * * * * © 2008 Bruno Chareyre * * * * This program is free software; it is licensed under the terms of the * * GNU General Public License v2 or later. See file LICENSE for details. * *************************************************************************/ #ifdef YADE_OPENGL #include "Gl1_Primitives.hpp" #include #include YADE_PLUGIN((Gl1_Aabb)(Gl1_Box)(Gl1_Facet)); YADE_PLUGIN((GlBoundFunctor)(GlShapeFunctor)(GlIGeomFunctor)(GlIPhysFunctor)(GlStateFunctor) (GlBoundDispatcher)(GlShapeDispatcher)(GlIGeomDispatcher)(GlIPhysDispatcher) (GlStateDispatcher)); void Gl1_Aabb::go(const shared_ptr& bv, Scene* scene){ Aabb* aabb = static_cast(bv.get()); glColor3v(bv->color); if(!scene->isPeriodic){ glTranslatev(Vector3r(.5*(aabb->min+aabb->max))); glScalev(Vector3r(aabb->max-aabb->min)); } else { glTranslatev(Vector3r(scene->cell->shearPt(scene->cell->wrapPt(.5*(aabb->min+aabb->max))))); glMultMatrixd(scene->cell->getGlShearTrsfMatrix()); glScalev(Vector3r(aabb->max-aabb->min)); } glutWireCube(1); } void Gl1_Box::go(const shared_ptr& cg, const shared_ptr&,bool wire,const GLViewInfo&) { glColor3v(cg->color); Vector3r &extents = (static_cast(cg.get()))->extents; glScalef(2*extents[0],2*extents[1],2*extents[2]); if (wire) glutWireCube(1); else glutSolidCube(1); } bool Gl1_Facet::normals=false; void Gl1_Facet::go(const shared_ptr& cm, const shared_ptr& ,bool wire,const GLViewInfo&) { Facet* facet = static_cast(cm.get()); const vector& vertices = facet->vertices; const Vector3r* ne = facet->ne; const Real& icr = facet->icr; if(cm->wire || wire){ // facet glBegin(GL_LINE_LOOP); glColor3v(normals ? Vector3r(1,0,0): cm->color); glVertex3v(vertices[0]); glVertex3v(vertices[1]); glVertex3v(vertices[2]); glEnd(); if(!normals) return; // facet's normal glBegin(GL_LINES); glColor3(0.0,0.0,1.0); glVertex3(0.0,0.0,0.0); glVertex3v(facet->normal); glEnd(); // normal of edges glColor3(0.0,0.0,1.0); glBegin(GL_LINES); glVertex3(0.0,0.0,0.0); glVertex3v(Vector3r(icr*ne[0])); glVertex3(0.0,0.0,0.0); glVertex3v(Vector3r(icr*ne[1])); glVertex3(0.0,0.0,0.0); glVertex3v(Vector3r(icr*ne[2])); glEnd(); } else { glDisable(GL_CULL_FACE); Vector3r normal=(facet->vertices[1]-facet->vertices[0]).cross(facet->vertices[2]-facet->vertices[1]); normal.normalize(); glColor3v(cm->color); glBegin(GL_TRIANGLES); glNormal3v(normal); // this makes every triangle different WRT the light direction; important! glVertex3v(facet->vertices[0]); glVertex3v(facet->vertices[1]); glVertex3v(facet->vertices[2]); glEnd(); } } // Spheres========================================== bool Gl1_Sphere::wire; bool Gl1_Sphere::stripes; int Gl1_Sphere::glutSlices; int Gl1_Sphere::glutStacks; Real Gl1_Sphere::quality; bool Gl1_Sphere::localSpecView; bool Gl1_Sphere::circleView; Real Gl1_Sphere::circleRelThickness; vector Gl1_Sphere::vertices, Gl1_Sphere::faces; int Gl1_Sphere::glStripedSphereList=-1; int Gl1_Sphere::glGlutSphereList=-1; Real Gl1_Sphere::prevQuality=0; string Gl1_Sphere::prevDisplayMode=""; char Gl1_Sphere::circleAllowedRotationAxis; char Gl1_Sphere::prevCircleAllowedRotationAxis='z'; void Gl1_Sphere::go(const shared_ptr& cm, const shared_ptr& ,bool wire2, const GLViewInfo&) { glClearDepth(1.0f); glEnable(GL_NORMALIZE); Real r=(static_cast(cm.get()))->radius; glColor3v(cm->color); if (circleView) { bool somethingChanged = (std::abs(quality-prevQuality)>0.001 || prevDisplayMode!="torus" || prevCircleAllowedRotationAxis!=circleAllowedRotationAxis); if (somethingChanged) { prevCircleAllowedRotationAxis=circleAllowedRotationAxis; prevDisplayMode="torus"; glDeleteLists(glGlutSphereList,1); glGlutSphereList = glGenLists(1); glNewList(glGlutSphereList,GL_COMPILE); glEnable(GL_LIGHTING); glShadeModel(GL_SMOOTH); switch (tolower(circleAllowedRotationAxis)) { //rotate the torus according to the axis from which we want to look at it. case 'z':break; //Initial torus axis is z, nothing to do case 'x':glRotatef(90,0,1,0);break; case 'y':glRotatef(90,1,0,0);break; default:cerr<<"Error in Gl1_Sphere::go, circleAllowedRotationAxis should be \"x\", \"y\" or \"z\"."<0.001 || glIsList(glStripedSphereList)!=GL_TRUE || prevDisplayMode!="sphere"); if (somethingChanged) {initStripedGlList(); initGlutGlList(); prevQuality=quality;prevDisplayMode="sphere";} glScalef(r,r,r); if(stripes) glCallList(glStripedSphereList); else glCallList(glGlutSphereList); } return; } YADE_PLUGIN((Gl1_Sphere)); void Gl1_Sphere::subdivideTriangle(Vector3r& v1,Vector3r& v2,Vector3r& v3, int depth){ Vector3r v; //Change color only at the appropriate level, i.e. 8 times in total, since we draw 8 mono-color sectors one after another if (depth==int(quality) || quality<=0){ v = (v1+v2+v3)/3.0; GLfloat matEmit[4]; if (v[1]*v[0]*v[2]>0){ matEmit[0] = 0.3; matEmit[1] = 0.3; matEmit[2] = 0.3; matEmit[3] = 1.f; }else{ matEmit[0] = 0.15; matEmit[1] = 0.15; matEmit[2] = 0.15; matEmit[3] = 0.2; } glMaterialfv(GL_FRONT, GL_EMISSION, matEmit); } if (depth==1){//Then display 4 triangles Vector3r v12 = v1+v2; Vector3r v23 = v2+v3; Vector3r v31 = v3+v1; v12.normalize(); v23.normalize(); v31.normalize(); //Use TRIANGLE_STRIP for faster display of adjacent facets glBegin(GL_TRIANGLE_STRIP); glNormal3v(v1); glVertex3v(v1); glNormal3v(v31); glVertex3v(v31); glNormal3v(v12); glVertex3v(v12); glNormal3v(v23); glVertex3v(v23); glNormal3v(v2); glVertex3v(v2); glEnd(); //terminate with this triangle left behind glBegin(GL_TRIANGLES); glNormal3v(v3); glVertex3v(v3); glNormal3v(v23); glVertex3v(v23); glNormal3v(v31); glVertex3v(v31); glEnd(); return; } Vector3r v12 = v1+v2; Vector3r v23 = v2+v3; Vector3r v31 = v3+v1; v12.normalize(); v23.normalize(); v31.normalize(); subdivideTriangle(v1,v12,v31,depth-1); subdivideTriangle(v2,v23,v12,depth-1); subdivideTriangle(v3,v31,v23,depth-1); subdivideTriangle(v12,v23,v31,depth-1); } void Gl1_Sphere::initStripedGlList() { if (!vertices.size()){//Fill vectors with vertices and facets //Define 6 points for +/- coordinates vertices.push_back(Vector3r(-1,0,0));//0 vertices.push_back(Vector3r(1,0,0));//1 vertices.push_back(Vector3r(0,-1,0));//2 vertices.push_back(Vector3r(0,1,0));//3 vertices.push_back(Vector3r(0,0,-1));//4 vertices.push_back(Vector3r(0,0,1));//5 //Define 8 sectors of the sphere faces.push_back(Vector3r(3,4,1)); faces.push_back(Vector3r(3,0,4)); faces.push_back(Vector3r(3,5,0)); faces.push_back(Vector3r(3,1,5)); faces.push_back(Vector3r(2,1,4)); faces.push_back(Vector3r(2,4,0)); faces.push_back(Vector3r(2,0,5)); faces.push_back(Vector3r(2,5,1)); } //Generate the list. Only once for each qtView, or more if quality is modified. glDeleteLists(glStripedSphereList,1); glStripedSphereList = glGenLists(1); glNewList(glStripedSphereList,GL_COMPILE); glEnable(GL_LIGHTING); glShadeModel(GL_SMOOTH); // render the sphere now for (int i=0;i<8;i++) subdivideTriangle(vertices[(unsigned int)faces[i][0]],vertices[(unsigned int)faces[i][1]],vertices[(unsigned int)faces[i][2]],1+ (int) quality); glEndList(); } void Gl1_Sphere::initGlutGlList(){ //Generate the "no-stripes" display list, each time quality is modified glDeleteLists(glGlutSphereList,1); glGlutSphereList = glGenLists(1); glNewList(glGlutSphereList,GL_COMPILE); glEnable(GL_LIGHTING); glShadeModel(GL_SMOOTH); glutSolidSphere(1.0,max(quality*glutSlices,(Real)2.),max(quality*glutStacks,(Real)3.)); glEndList(); } #endif /* YADE_OPENGL */ trunk-2018.02b/pkg/common/Gl1_Primitives.hpp000066400000000000000000000122421324306050200206070ustar00rootroot00000000000000/************************************************************************* * Copyright (C) 2004 by Olivier Galizzi * * olivier.galizzi@imag.fr * * * * Copyright (C) 2008 by Sergei Dorofeenko * * sega@users.berlios.de * * * * © 2008 Václav Šmilauer * * * * © 2008 Bruno Chareyre * * * * This program is free software; it is licensed under the terms of the * * GNU General Public License v2 or later. See file LICENSE for details. * *************************************************************************/ #pragma once #include #include #include #include #include class Gl1_Aabb: public GlBoundFunctor{ public: virtual void go(const shared_ptr&, Scene*); RENDERS(Aabb); YADE_CLASS_BASE_DOC(Gl1_Aabb,GlBoundFunctor,"Render Axis-aligned bounding box (:yref:`Aabb`)."); }; REGISTER_SERIALIZABLE(Gl1_Aabb); class Gl1_Box : public GlShapeFunctor{ public : virtual void go(const shared_ptr&, const shared_ptr&,bool,const GLViewInfo&); RENDERS(Box); YADE_CLASS_BASE_DOC(Gl1_Box,GlShapeFunctor,"Renders :yref:`Box` object"); }; REGISTER_SERIALIZABLE(Gl1_Box); class Gl1_Facet : public GlShapeFunctor { public: virtual void go(const shared_ptr&, const shared_ptr&,bool,const GLViewInfo&); RENDERS(Facet); YADE_CLASS_BASE_DOC_STATICATTRS(Gl1_Facet,GlShapeFunctor,"Renders :yref:`Facet` object", ((bool,normals,false,,"In wire mode, render normals of facets and edges; facet's :yref:`colors` are disregarded in that case.")) ); }; REGISTER_SERIALIZABLE(Gl1_Facet); class Gl1_Sphere : public GlShapeFunctor{ private: // for stripes static vector vertices, faces; static int glStripedSphereList; static int glGlutSphereList; void subdivideTriangle(Vector3r& v1,Vector3r& v2,Vector3r& v3, int depth); //Generate GlList for GLUT sphere void initGlutGlList(); //Generate GlList for sliced spheres void initStripedGlList(); //for regenerating glutSphere or glutTorus list if needed static Real prevQuality; //for regenerating glutSphere or glutTorus list if needed static string prevDisplayMode; //for regenerating glutTorus list if needed static char prevCircleAllowedRotationAxis; public: virtual void go(const shared_ptr&, const shared_ptr&,bool,const GLViewInfo&); YADE_CLASS_BASE_DOC_STATICATTRS(Gl1_Sphere,GlShapeFunctor,"Renders :yref:`Sphere` object", ((Real,quality,1.0,,"Change discretization level of spheres. quality>1 for better image quality, at the price of more cpu/gpu usage, 0`_)")) ((int,glutStacks,6,(Attr::noSave | Attr::readonly),"Base number of sphere stacks, multiplied by :yref:`Gl1_Sphere::quality` before use; not used with ``stripes`` (see `glut{Solid,Wire}Sphere reference `_)")) ((bool,circleView,false,,"For 2D simulations : display tori instead of spheres, so they will appear like circles if the viewer is looking in the right direction. In this case, remember to disable perspective by pressing \"t\"-key in the viewer.")) ((Real,circleRelThickness,0.2,,"If :yref:`Gl1_Sphere::circleView` is enabled, this is the torus diameter relative to the sphere radius (i.e. the circle relative thickness).")) ((char,circleAllowedRotationAxis,'z',,"If :yref:`Gl1_Sphere::circleView` is enabled, this is the only axis ('x', 'y' or 'z') along which rotation is allowed for the 2D simulation. It allows right orientation of the tori to appear like circles in the viewer. For example, if circleAllowedRotationAxis='x' is set, blockedDOFs=\"YZ\" should also be set for all your particles.")) ); RENDERS(Sphere); }; REGISTER_SERIALIZABLE(Gl1_Sphere); trunk-2018.02b/pkg/common/GravityEngines.cpp000066400000000000000000000076371324306050200207160ustar00rootroot00000000000000/************************************************************************* * Copyright (C) 2004 by Janek Kozicki * * cosurgi@berlios.de * * * * This program is free software; it is licensed under the terms of the * * GNU General Public License v2 or later. See file LICENSE for details. * *************************************************************************/ #include #include #include #include #include YADE_PLUGIN((GravityEngine)(CentralGravityEngine)(AxialGravityEngine)(HdapsGravityEngine)); CREATE_LOGGER(GravityEngine); void GravityEngine::action(){ if (warnOnce) {warnOnce=false; LOG_WARN("GravityEngine is deprecated, consider using Newton::gravity instead (unless gravitational energy has to be tracked - not implemented with the newton attribute).")} const bool trackEnergy(scene->trackEnergy); const Real dt(scene->dt); YADE_PARALLEL_FOREACH_BODY_BEGIN(const shared_ptr& b, scene->bodies){ // skip clumps, only apply forces on their constituents if(b->isClump()) continue; if(mask!=0 && !b->maskCompatible(mask)) continue; scene->forces.addForce(b->getId(),gravity*b->state->mass); // work done by gravity is "negative", since the energy appears in the system from outside if(trackEnergy) scene->energy->add(-gravity.dot(b->state->vel)*b->state->mass*dt,"gravWork",fieldWorkIx,/*non-incremental*/false); } YADE_PARALLEL_FOREACH_BODY_END(); } void CentralGravityEngine::action(){ const Vector3r& centralPos=Body::byId(centralBody)->state->pos; FOREACH(const shared_ptr& b, *scene->bodies){ if(b->isClump() || b->getId()==centralBody) continue; // skip clumps and central body if(mask!=0 && !b->maskCompatible(mask)) continue; Real F=accel*b->state->mass; Vector3r toCenter=centralPos-b->state->pos; toCenter.normalize(); scene->forces.addForce(b->getId(),F*toCenter); if(reciprocal) scene->forces.addForce(centralBody,-F*toCenter); } } void AxialGravityEngine::action(){ FOREACH(const shared_ptr&b, *scene->bodies){ if(!b || b->isClump()) continue; if(mask!=0 && !b->maskCompatible(mask)) continue; /* http://mathworld.wolfram.com/Point-LineDistance3-Dimensional.html */ const Vector3r& x0=b->state->pos; const Vector3r& x1=axisPoint; const Vector3r x2=axisPoint+axisDirection; Vector3r closestAxisPoint=x1+(x2-x1) * /* t */ (-(x1-x0).dot(x2-x1))/((x2-x1).squaredNorm()); Vector3r toAxis=closestAxisPoint-x0; toAxis.normalize(); if(toAxis.squaredNorm()==0) continue; scene->forces.addForce(b->getId(),acceleration*b->state->mass*toAxis); } } Vector2i HdapsGravityEngine::readSysfsFile(const string& name){ char buf[256]; ifstream f(name.c_str()); if(!f.is_open()) throw std::runtime_error(("HdapsGravityEngine: unable to open file "+name).c_str()); f.read(buf,256);f.close(); const boost::regex re("\\(([0-9+-]+),([0-9+-]+)\\).*"); boost::cmatch matches; if(!boost::regex_match(buf,matches,re)) throw std::runtime_error(("HdapsGravityEngine: error parsing data from "+name).c_str()); //cerr<(matches[1]),boost::lexical_cast(matches[2])); } void HdapsGravityEngine::action(){ if(!calibrated) { calibrate=readSysfsFile(hdapsDir+"/calibrate"); calibrated=true; } Real now=PeriodicEngine::getClock(); if(now-lastReading>1e-3*msecUpdate){ Vector2i a=readSysfsFile(hdapsDir+"/position"); lastReading=now; a-=calibrate; if(std::abs(a[0]-accel[0])>updateThreshold) accel[0]=a[0]; if(std::abs(a[1]-accel[1])>updateThreshold) accel[1]=a[1]; Quaternionr trsf(AngleAxisr(.5*accel[0]*M_PI/180.,-Vector3r::UnitY())*AngleAxisr(.5*accel[1]*M_PI/180.,-Vector3r::UnitX())); gravity=trsf*zeroGravity; } GravityEngine::action(); } trunk-2018.02b/pkg/common/GravityEngines.hpp000066400000000000000000000077731324306050200207240ustar00rootroot00000000000000// 2004 © Janek Kozicki // 2007,2008 © Václav Šmilauer #pragma once #include #include #include /*! Homogeneous gravity field; applies gravity×mass force on all bodies. */ class GravityEngine: public FieldApplier{ public: virtual void action(); YADE_CLASS_BASE_DOC_ATTRS_CTOR_PY(GravityEngine,FieldApplier,"Engine applying constant acceleration to all bodies. DEPRECATED, use :yref:`Newton::gravity` unless you need energy tracking or selective gravity application using groupMask).", ((Vector3r,gravity,Vector3r::Zero(),,"Acceleration [kgms⁻²]")) ((int,gravPotIx,-1,(Attr::noSave|Attr::hidden),"Index for gravPot energy")) ((int,mask,0,,"If mask defined, only bodies with corresponding groupMask will be affected by this engine. If 0, all bodies will be affected.")) ((bool,warnOnce,true,,"For deprecation warning once.")) ,/*ctor*/,/*py*/ ); DECLARE_LOGGER; }; REGISTER_SERIALIZABLE(GravityEngine); /*! Engine attracting all bodies towards a central body (doesn't depend on distance); * * @todo This code has not been yet tested at all. */ class CentralGravityEngine: public FieldApplier { public: virtual void action(); YADE_CLASS_BASE_DOC_ATTRS_CTOR_PY(CentralGravityEngine,FieldApplier,"Engine applying acceleration to all bodies, towards a central body.", ((Body::id_t,centralBody,Body::ID_NONE,,"The :yref:`body` towards which all other bodies are attracted.")) ((Real,accel,0,,"Acceleration magnitude [kgms⁻²]")) ((bool,reciprocal,false,,"If true, acceleration will be applied on the central body as well.")) ((int,mask,0,,"If mask defined, only bodies with corresponding groupMask will be affected by this engine. If 0, all bodies will be affected.")) ,, ); }; REGISTER_SERIALIZABLE(CentralGravityEngine); /*! Apply acceleration (independent of distance) directed towards an axis. * */ class AxialGravityEngine: public FieldApplier { public: virtual void action(); YADE_CLASS_BASE_DOC_ATTRS(AxialGravityEngine,FieldApplier,"Apply acceleration (independent of distance) directed towards an axis.", ((Vector3r,axisPoint,Vector3r::Zero(),,"Point through which the axis is passing.")) ((Vector3r,axisDirection,Vector3r::UnitX(),,"direction of the gravity axis (will be normalized automatically)")) ((Real,acceleration,0,,"Acceleration magnitude [kgms⁻²]")) ((int,mask,0,,"If mask defined, only bodies with corresponding groupMask will be affected by this engine. If 0, all bodies will be affected.")) ); }; REGISTER_SERIALIZABLE(AxialGravityEngine); class HdapsGravityEngine: public GravityEngine{ public: Vector2i readSysfsFile(const std::string& name); virtual void action(); YADE_CLASS_BASE_DOC_ATTRS(HdapsGravityEngine,GravityEngine,"Read accelerometer in Thinkpad laptops (`HDAPS `__ and accordingly set gravity within the simulation. This code draws from `hdaps-gl `__ . See :ysrc:`scripts/test/hdaps.py` for an example.", ((string,hdapsDir,"/sys/devices/platform/hdaps",,"Hdaps directory; contains ``position`` (with accelerometer readings) and ``calibration`` (zero acceleration).")) ((Real,msecUpdate,50,,"How often to update the reading.")) ((int,updateThreshold,4,,"Minimum difference of reading from the file before updating gravity, to avoid jitter.")) ((Real,lastReading,-1,(Attr::hidden|Attr::noSave),"Time of the last reading.")) ((Vector2i,accel,Vector2i::Zero(),(Attr::noSave|Attr::readonly),"reading from the sysfs file")) ((Vector2i,calibrate,Vector2i::Zero(),,"Zero position; if NaN, will be read from the *hdapsDir* / calibrate.")) ((bool,calibrated,false,,"Whether *calibrate* was already updated. Do not set to ``True`` by hand unless you also give a meaningful value for *calibrate*.")) ((Vector3r,zeroGravity,Vector3r(0,0,-1),,"Gravity if the accelerometer is in flat (zero) position.")) ); }; REGISTER_SERIALIZABLE(HdapsGravityEngine); trunk-2018.02b/pkg/common/Grid.cpp000066400000000000000000000751321324306050200166400ustar00rootroot00000000000000/************************************************************************* * Copyright (C) 2012 by François Kneib francois.kneib@gmail.com * * Copyright (C) 2012 by Bruno Chareyre bruno.chareyre@hmg.inpg.fr * * This program is free software; it is licensed under the terms of the * * GNU General Public License v2 or later. See file LICENSE for details. * *************************************************************************/ #include "Grid.hpp" //!################## SHAPES ##################### GridNode::~GridNode(){} YADE_PLUGIN((GridNode)); GridConnection::~GridConnection(){} YADE_PLUGIN((GridConnection)); GridNodeGeom6D::~GridNodeGeom6D(){} YADE_PLUGIN((GridNodeGeom6D)); ScGridCoGeom::~ScGridCoGeom(){} YADE_PLUGIN((ScGridCoGeom)); GridCoGridCoGeom::~GridCoGridCoGeom(){} YADE_PLUGIN((GridCoGridCoGeom)); void GridNode::addConnection(shared_ptr GC){ ConnList.push_back(GC); } Vector3r GridConnection::getSegment(){ if (!periodic) return node2->state->pos - node1->state->pos; //else const Scene* scene=Omega::instance().getScene().get(); return node2->state->pos + scene->cell->hSize*cellDist.cast() - node1->state->pos; } Real GridConnection::getLength(){ return getSegment().norm(); } void GridNode::addPFacet(shared_ptr PF){ pfacetList.push_back(PF); } void GridConnection::addPFacet(shared_ptr PF){ pfacetList.push_back(PF); } PFacet::~PFacet(){} YADE_PLUGIN((PFacet)); //!################## IGeom Functors ##################### //! O-O bool Ig2_GridNode_GridNode_GridNodeGeom6D::go( const shared_ptr& cm1, const shared_ptr& cm2, const State& state1, const State& state2, const Vector3r& shift2, const bool& force, const shared_ptr& c) { //GridConnection* GC = static_cast(cm.get()); bool isNew = !c->geom; GridNode* GN[2]={static_cast(cm1.get()),static_cast(cm2.get())}; if (Ig2_Sphere_Sphere_ScGeom::go(cm1,cm2,state1,state2,shift2,force,c)){//the 3 DOFS from ScGeom are updated here if (isNew) {//generate a 6DOF interaction from the 3DOF one generated by Ig2_Sphere_Sphere_ScGeom shared_ptr sc (new GridNodeGeom6D()); *(YADE_PTR_CAST(sc)) = *(YADE_PTR_CAST(c->geom)); c->geom=sc; } if (updateRotations) YADE_PTR_CAST(c->geom)->precomputeRotations(state1,state2,isNew,creep); if(YADE_PTR_CAST(c->geom)->connectionBody){ //test this because the connectionBody may not have been yet initialized. YADE_PTR_CAST(c->geom)->connectionBody->state->pos=state1.pos; for (unsigned int j=0; j<2; j++){ for (unsigned int i=0; i< GN[j]->pfacetList.size() ;i++){ PFacet* Pfacet = YADE_CAST(GN[j]->pfacetList[i]->shape.get()); if(c->id1==Pfacet->node1->getId()) GN[j]->pfacetList[i]->state->pos=state1.pos; } } } return true; } else return false; } bool Ig2_GridNode_GridNode_GridNodeGeom6D::goReverse( const shared_ptr& cm1, const shared_ptr& cm2, const State& state1, const State& state2, const Vector3r& shift2, const bool& force, const shared_ptr& c) { return go(cm1,cm2,state2,state1,-shift2,force,c); } YADE_PLUGIN((Ig2_GridNode_GridNode_GridNodeGeom6D)); //! \\// bool Ig2_GridConnection_GridConnection_GridCoGridCoGeom::go( const shared_ptr& cm1, const shared_ptr& cm2, const State& state1, const State& state2, const Vector3r& shift2, const bool& force, const shared_ptr& c) { /*FIXME : /!\ Note that this geometry doesn't take care of any unwished duplicated contact or shear force following. /!\*/ GridConnection* conn1 = YADE_CAST(cm1.get()); GridConnection* conn2 = YADE_CAST(cm2.get()); State* stNode11 = conn1->node1->state.get(); State* stNode12 = conn1->node2->state.get(); State* stNode21 = conn2->node1->state.get(); State* stNode22 = conn2->node2->state.get(); if(conn1->node1==conn2->node1 || conn1->node1==conn2->node2 || conn1->node2==conn2->node1 || conn1->node2==conn2->node2){ //Two connections share at least one node, so they are contiguous => they must not interact. return false; } //There could be a contact between to connections. Check this now. bool isNew = !c->geom; Real k,m; Vector3r A=stNode11->pos, a=stNode12->pos-A; //"A" is an extremity of conn1, "a" is the connection's segment. Vector3r B=stNode21->pos, b=stNode22->pos-B; //"B" is an extremity of conn2, "b" is the connection's segment. B+=shift2;//periodicity. /* NOW STARTS THE OLD VERSION. IT SHOULD BE REMOVED LATER. Vector3r N=a.cross(b); //"N" is orthogonal to "a" and "b". It means that "N" describes the common plan between a and b. if(N.norm()>1e-14){ //If "a" and "b" are colinear, "N==0" and this is a special case. Real dist=N.dot(B-A)/(N.norm()); //here "dist" is oriented, so it's sign depends on the orientation of "N" against "AB". Vector3r pB=B-dist*(N/(N.norm())); //"pB" is the projection of the point "B" in the plane defined by his normal vector "N". //Now we have pB, so we will compute the intersection of two segments into a plane. int b0, b1; //2 base vectors used to compute the segment intersection. For more accuracy and to avoid det==0, don't choose the axis where N is max. if(std::abs(N[0])1e-14){ //Now compute k and m, who are the parameters (relative position on the connections) of the intersection on conn1 ("A" and "a") and conn2 ("B" and "b") respectively. k = (b[b1]*(pB[b0]-A[b0])+b[b0]*(A[b1]-pB[b1]))/det; m = (a[b0]*(-pB[b1]+A[b1])+a[b1]*(pB[b0]-A[b0]))/det; //This is a little bit tricky : if we haven't 0radius + conn2->radius - (A+k*a - (B+m*b)).norm(); shared_ptr scm; if(isNew){ if(penetrationDepth<0)return false; scm=shared_ptr(new GridCoGridCoGeom()); c->geom=scm; } else scm=YADE_PTR_CAST(c->geom); //k and m are used to compute almost everything... //Fictious states (spheres) are generated at k or m of each connection, they will handle the contact. scm->relPos1=k ; scm->relPos2=m; scm->fictiousState1.pos=A + k*a ; scm->fictiousState2.pos=B + m*b; scm->radius1 = conn1->radius ; scm->radius2 = conn2->radius; scm->fictiousState1.vel = (1-k)*stNode11->vel + k*stNode12->vel; scm->fictiousState2.vel = (1-m)*stNode21->vel + m*stNode22->vel; Vector3r direction = a/(a.norm()); scm->fictiousState1.angVel = ((1-k)*stNode11->angVel + k*stNode12->angVel).dot(direction)*direction //twist part : interpolated + a.cross(stNode12->vel - stNode11->vel);// non-twist part : defined from nodes velocities direction = b/(b.norm()); scm->fictiousState2.angVel = ((1-m)*stNode21->angVel + m*stNode22->angVel).dot(direction)*direction //twist part : interpolated + b.cross(stNode22->vel - stNode21->vel);// non-twist part : defined from nodes velocities Vector3r normal= scm->fictiousState2.pos - scm->fictiousState1.pos; normal/=normal.norm(); scm->contactPoint = scm->fictiousState1.pos + (scm->radius1-0.5*penetrationDepth)*normal; scm->penetrationDepth=penetrationDepth; scm->precompute(scm->fictiousState1,scm->fictiousState2,scene,c,normal,isNew,shift2,true); return true; } bool Ig2_GridConnection_GridConnection_GridCoGridCoGeom::goReverse( const shared_ptr& cm1, const shared_ptr& cm2, const State& state1, const State& state2, const Vector3r& shift2, const bool& force, const shared_ptr& c) { return go(cm1,cm2,state2,state1,-shift2,force,c); } YADE_PLUGIN((Ig2_GridConnection_GridConnection_GridCoGridCoGeom)); //! O/ bool Ig2_Sphere_GridConnection_ScGridCoGeom::go( const shared_ptr& cm1, const shared_ptr& cm2, const State& state1, const State& state2, const Vector3r& shift2, const bool& force, const shared_ptr& c) { // Useful variables : const State* sphereSt = YADE_CAST(&state1); Sphere* sphere = YADE_CAST(cm1.get()); GridConnection* gridCo = YADE_CAST(cm2.get()); GridNode* gridNo1 = YADE_CAST(gridCo->node1->shape.get()); GridNode* gridNo2 = YADE_CAST(gridCo->node2->shape.get()); State* gridNo1St = YADE_CAST(gridCo->node1->state.get()); State* gridNo2St = YADE_CAST(gridCo->node2->state.get()); bool isNew = !c->geom; shared_ptr scm; if (!isNew) scm = YADE_PTR_CAST(c->geom); else {scm = shared_ptr(new ScGridCoGeom());} Vector3r segt = gridCo->getSegment(); Real len = gridCo->getLength(); Vector3r spherePos = sphereSt->pos - shift2; Vector3r branch = spherePos - gridNo1St->pos; Vector3r branchN = spherePos - gridNo2St->pos; for(int i=0;i<3;i++){ if(std::abs(branch[i])<1e-14) branch[i]=0.0; if(std::abs(branchN[i])<1e-14) branchN[i]=0.0; } Real relPos = branch.dot(segt)/(len*len); if(scm->isDuplicate==2 && scm->trueInt!=c->id2)return true; //the contact will be deleted into the Law, no need to compute here. scm->isDuplicate=0; scm->trueInt=-1; /* The 4 conditions below are used to avoid double contact between a sphere and two cylinders, and to follow contact properties when the sphere is sliding along different consecutive GridConnections. If none of these conditions are satisfied, the classic contact will be done at the bottom of the Ig2. Else the contact may be copied (if sliding), deleted (if just copied and/or duplicated) and the return statement may be used to abort the Ig2. The first and the second conditions detect if a sphere's projections is outside the connection. So the contact : - have to be created if the projection is outside all neighbours and not already created. - have to be ignored if the projection is inside at least one neighbour. - if the contact is sliding out to another connection (detected via isNew), mark it as duplicated (it will be ignored by the law and imported (copied) by the new contact). The third and the fourth conditions detect if a sphere's projections is inside the connection. So if the contact is new and : - is before the middle of the connection, we search an old contact that may have slided from one of the previous connections. If we find one, we import it here. - is after the middle of the connection, we search an old contact that may have slided from one of the following connections. If we find one, we import it here. */ if(relPos<=0){ // if the sphere projection is BEFORE the segment ... if(gridNo1->ConnList.size()>1){// if the node is not an extremity of the Grid (only one connection) for(int unsigned i=0;iConnList.size();i++){ // ... loop on all the Connections of the same Node ... GridConnection* GC = (GridConnection*)gridNo1->ConnList[i]->shape.get(); if(GC==gridCo)continue;// self comparison. Vector3r segtCandidate1 = GC->node1->state->pos - gridNo1St->pos; // (be sure of the direction of segtPrev to compare relPosPrev.) Vector3r segtCandidate2 = GC->node2->state->pos - gridNo1St->pos; Vector3r segtPrev = segtCandidate1.norm()>segtCandidate2.norm() ? segtCandidate1:segtCandidate2; for(int j=0;j<3;j++){ if(std::abs(segtPrev[j])<1e-14) segtPrev[j]=0.0; } Real relPosPrev = (branch.dot(segtPrev))/(segtPrev.norm()*segtPrev.norm()); // ... and check whether the sphere projection is before the neighbours connections too. if(relPosPrev<=0){ //if the sphere projection is outside both the current Connection AND this neighbouring connection, then create the interaction if the neighbour did not already do it before. const shared_ptr intr = scene->interactions->find(c->id1,gridNo1->ConnList[i]->getId()); if(intr && intr->isReal()){ shared_ptr intrGeom=YADE_PTR_CAST(intr->geom); if(!(intrGeom->isDuplicate==1)){ //skip contact. if (isNew) return false; else {scm->isDuplicate=1;/*cout<<"Declare "<id1<<"-"<id2<<" as duplicated."<id1<<"-"<id2<<" may be copied and will be deleted now."<isDuplicate=1; scm->trueInt=-1; return true; } } } } } //Exactly the same but in the case the sphere projection is AFTER the segment. else if(relPos>=1){ if(gridNo2->ConnList.size()>1){ for(int unsigned i=0;iConnList.size();i++){ GridConnection* GC = (GridConnection*)gridNo2->ConnList[i]->shape.get(); if(GC==gridCo)continue;// self comparison. Vector3r segtCandidate1 = GC->node1->state->pos - gridNo2St->pos; Vector3r segtCandidate2 = GC->node2->state->pos - gridNo2St->pos; Vector3r segtNext = segtCandidate1.norm()>segtCandidate2.norm() ? segtCandidate1:segtCandidate2; for(int j=0;j<3;j++){ if(std::abs(segtNext[j])<1e-14) segtNext[j]=0.0; } Real relPosNext = (branchN.dot(segtNext))/(segtNext.norm()*segtNext.norm()); if(relPosNext<=0){ //if the sphere projection is outside both the current Connection AND this neighbouring connection, then create the interaction if the neighbour did not already do it before. const shared_ptr intr = scene->interactions->find(c->id1,gridNo2->ConnList[i]->getId()); if(intr && intr->isReal()){ shared_ptr intrGeom=YADE_PTR_CAST(intr->geom); if(!(intrGeom->isDuplicate==1)){ if (isNew) return false; else {scm->isDuplicate=1;/*cout<<"Declare "<id1<<"-"<id2<<" as duplicated."<id1<<"-"<id2<<" may be copied and will be deleted now."<isDuplicate=1 ; scm->trueInt=-1 ; return true; } } } } } else if (relPos<=0.5){ if(gridNo1->ConnList.size()>1){// if the node is not an extremity of the Grid (only one connection) for(int unsigned i=0;iConnList.size();i++){ // ... loop on all the Connections of the same Node ... GridConnection* GC = (GridConnection*)gridNo1->ConnList[i]->shape.get(); if(GC==gridCo)continue;// self comparison. Vector3r segtCandidate1 = GC->node1->state->pos - gridNo1St->pos; // (be sure of the direction of segtPrev to compare relPosPrev.) Vector3r segtCandidate2 = GC->node2->state->pos - gridNo1St->pos; Vector3r segtPrev = segtCandidate1.norm()>segtCandidate2.norm() ? segtCandidate1:segtCandidate2; for(int j=0;j<3;j++){ if(std::abs(segtPrev[j])<1e-14) segtPrev[j]=0.0; } Real relPosPrev = (branch.dot(segtPrev))/(segtPrev.norm()*segtPrev.norm()); if(relPosPrev<=0){ //the sphere projection is inside the current Connection and outide this neighbour connection. const shared_ptr intr = scene->interactions->find(c->id1,gridNo1->ConnList[i]->getId()); if( intr && intr->isReal() ){// if an ineraction exist between the sphere and the previous connection, import parameters. scm=YADE_PTR_CAST(intr->geom); if(isNew){ // cout<<"Copying contact geom and phys from "<id1<<"-"<id2<<" to here ("<id1<<"-"<id2<<")"<geom=scm; c->phys=intr->phys; c->iterMadeReal=intr->iterMadeReal; } scm->trueInt=c->id2; scm->isDuplicate=2; //command the old contact deletion. isNew=0; break; } } } } } else if (relPos>0.5){ if(gridNo2->ConnList.size()>1){ for(int unsigned i=0;iConnList.size();i++){ GridConnection* GC = (GridConnection*)gridNo2->ConnList[i]->shape.get(); if(GC==gridCo)continue;// self comparison. Vector3r segtCandidate1 = GC->node1->state->pos - gridNo2St->pos; Vector3r segtCandidate2 = GC->node2->state->pos - gridNo2St->pos; Vector3r segtNext = segtCandidate1.norm()>segtCandidate2.norm() ? segtCandidate1:segtCandidate2; for(int j=0;j<3;j++){ if(std::abs(segtNext[j])<1e-14) segtNext[j]=0.0; } Real relPosNext = (branchN.dot(segtNext))/(segtNext.norm()*segtNext.norm()); if(relPosNext<=0){ //the sphere projection is inside the current Connection and outide this neighbour connection. const shared_ptr intr = scene->interactions->find(c->id1,gridNo2->ConnList[i]->getId()); if( intr && intr->isReal() ){// if an ineraction exist between the sphere and the previous connection, import parameters. scm=YADE_PTR_CAST(intr->geom); if(isNew){ // cout<<"Copying contact geom and phys from "<id1<<"-"<id2<<" to here ("<id1<<"-"<id2<<")"<geom=scm; c->phys=intr->phys; c->iterMadeReal=intr->iterMadeReal; } scm->trueInt=c->id2; scm->isDuplicate=2; //command the old contact deletion. isNew=0; break; } } } } } relPos=relPos<0?0:relPos; //min value of relPos : 0 relPos=relPos>1?1:relPos; //max value of relPos : 1 Vector3r fictiousPos=gridNo1St->pos+relPos*segt; Vector3r branchF = fictiousPos - spherePos; Real dist = branchF.norm(); if(isNew && (dist > (sphere->radius + gridCo->radius))) return false; // Create the geometry : if(isNew) c->geom=scm; scm->radius1=sphere->radius; scm->radius2=gridCo->radius; scm->id3=gridCo->node1->getId(); scm->id4=gridCo->node2->getId(); scm->relPos=relPos; Vector3r normal=branchF/dist; scm->penetrationDepth = sphere->radius+gridCo->radius-dist; scm->fictiousState.pos = fictiousPos; scm->contactPoint = spherePos + normal*(scm->radius1 - 0.5*scm->penetrationDepth); scm->fictiousState.vel = (1-relPos)*gridNo1St->vel + relPos*gridNo2St->vel; scm->fictiousState.angVel = ((1-relPos)*gridNo1St->angVel + relPos*gridNo2St->angVel).dot(segt/len)*segt/len //twist part : interpolated + segt.cross(gridNo2St->vel - gridNo1St->vel);// non-twist part : defined from nodes velocities scm->precompute(state1,scm->fictiousState,scene,c,normal,isNew,shift2,true);//use sphere-sphere precompute (with a virtual sphere) return true; } bool Ig2_Sphere_GridConnection_ScGridCoGeom::goReverse( const shared_ptr& cm1, const shared_ptr& cm2, const State& state1, const State& state2, const Vector3r& shift2, const bool& force, const shared_ptr& c) { c->swapOrder(); return go(cm2,cm1,state2,state1,-shift2,force,c); } YADE_PLUGIN((Ig2_Sphere_GridConnection_ScGridCoGeom)); //!################## Laws ##################### //! O/ bool Law2_ScGridCoGeom_FrictPhys_CundallStrack::go(shared_ptr& ig, shared_ptr& ip, Interaction* contact){ int id1 = contact->getId1(), id2 = contact->getId2(); ScGridCoGeom* geom= static_cast(ig.get()); FrictPhys* phys = static_cast(ip.get()); if(geom->penetrationDepth <0){ if (neverErase) { phys->shearForce = Vector3r::Zero(); phys->normalForce = Vector3r::Zero();} else return false;} if (geom->isDuplicate) { if (id2!=geom->trueInt) { //cerr<<"skip duplicate "<isDuplicate==2) return false; return true; } } Real& un=geom->penetrationDepth; phys->normalForce=phys->kn*std::max(un,(Real) 0)*geom->normal; // std::cout<< "phys->shearForce1= "<shearForce<rotate(phys->shearForce); // std::cout<< "phys->shearForce2= "<shearForce<<", geom->shearIncrement()= "<shearIncrement()<<", phys->ks= "<ks<shearIncrement(); shearForce -= phys->ks*shearDisp; Real maxFs = phys->normalForce.squaredNorm()*std::pow(phys->tangensOfFrictionAngle,2); // std::cout<< "shearForce3= "<normalForce="<normalForce<trackEnergy){//Update force but don't compute energy terms (see below)) // PFC3d SlipModel, is using friction angle. CoulombCriterion if( shearForce.squaredNorm() > maxFs ){ Real ratio = sqrt(maxFs) / shearForce.norm(); shearForce *= ratio;} } else { //almost the same with additional Vector3r instanciated for energy tracing, duplicated block to make sure there is no cost for the instanciation of the vector when traceEnergy==false if(shearForce.squaredNorm() > maxFs){ Real ratio = sqrt(maxFs) / shearForce.norm(); Vector3r trialForce=shearForce;//store prev force for definition of plastic slip //define the plastic work input and increment the total plastic energy dissipated shearForce *= ratio; Real dissip=((1/phys->ks)*(trialForce-shearForce))/*plastic disp*/ .dot(shearForce)/*active force*/; if(dissip>0) scene->energy->add(dissip,"plastDissip",plastDissipIx,/*reset*/false); } // compute elastic energy as well scene->energy->add(0.5*(phys->normalForce.squaredNorm()/phys->kn+phys->shearForce.squaredNorm()/phys->ks),"elastPotential",elastPotentialIx,/*reset at every timestep*/true); } Vector3r force = -phys->normalForce-shearForce; // std::cout<< "id1= "<isDuplicate==2) return false; return true; } } Vector3r& shearForce = phys->shearForce; if (contact->isFresh(scene) && geom->isDuplicate!=2) shearForce = Vector3r::Zero(); Real un = geom->penetrationDepth; Real Fn = phys->kn*(un-phys->unp); if (phys->fragile && (-Fn)> phys->normalAdhesion) { // BREAK due to tension return false; } else { if ((-Fn)> phys->normalAdhesion) {//normal plasticity Fn=-phys->normalAdhesion; phys->unp = un+phys->normalAdhesion/phys->kn; if (phys->unpMax && phys->unpunpMax) return false; } phys->normalForce = Fn*geom->normal; Vector3r& shearForce = geom->rotate(phys->shearForce); const Vector3r& dus = geom->shearIncrement(); //Linear elasticity giving "trial" shear force shearForce -= phys->ks*dus; Real Fs = phys->shearForce.norm(); Real maxFs = phys->shearAdhesion; if (!phys->cohesionDisablesFriction || maxFs==0) maxFs += Fn*phys->tangensOfFrictionAngle; maxFs = std::max((Real) 0, maxFs); if (Fs > maxFs) {//Plasticity condition on shear force if (phys->fragile && !phys->cohesionBroken) { phys->SetBreakingState(); maxFs = max((Real) 0, Fn*phys->tangensOfFrictionAngle); } maxFs = maxFs / Fs; Vector3r trialForce=shearForce; shearForce *= maxFs; if (scene->trackEnergy){ Real dissip=((1/phys->ks)*(trialForce-shearForce))/*plastic disp*/ .dot(shearForce)/*active force*/; if(dissip>0) scene->energy->add(dissip,"plastDissip",plastDissipIx,/*reset*/false);} if (Fn<0) phys->normalForce = Vector3r::Zero();//Vector3r::Zero() } Vector3r force = -phys->normalForce-shearForce; scene->forces.addForce(id1,force); scene->forces.addTorque(id1,(geom->radius1-0.5*geom->penetrationDepth)* geom->normal.cross(force)); Vector3r twist = (geom->radius2-0.5*geom->penetrationDepth)* geom->normal.cross(force); scene->forces.addForce(geom->id3,(geom->relPos-1)*force); scene->forces.addTorque(geom->id3,(1-geom->relPos)*twist); scene->forces.addForce(geom->id4,(-geom->relPos)*force); scene->forces.addTorque(geom->id4,geom->relPos*twist); return true; } } YADE_PLUGIN((Law2_ScGridCoGeom_CohFrictPhys_CundallStrack)); bool Law2_GridCoGridCoGeom_FrictPhys_CundallStrack::go(shared_ptr& ig, shared_ptr& ip, Interaction* contact){ int id1 = contact->getId1(), id2 = contact->getId2(); id_t id11 = (static_cast((&Body::byId(id1)->shape)->get()))->node1->getId(); id_t id12 = (static_cast((&Body::byId(id1)->shape)->get()))->node2->getId(); id_t id21 = (static_cast((&Body::byId(id2)->shape)->get()))->node1->getId(); id_t id22 = (static_cast((&Body::byId(id2)->shape)->get()))->node2->getId(); GridCoGridCoGeom* geom= static_cast(ig.get()); FrictPhys* phys = static_cast(ip.get()); if(geom->penetrationDepth <0){ if (neverErase) { phys->shearForce = Vector3r::Zero(); phys->normalForce = Vector3r::Zero();} else return false;} Real& un=geom->penetrationDepth; phys->normalForce=phys->kn*std::max(un,(Real) 0)*geom->normal; Vector3r& shearForce = geom->rotate(phys->shearForce); const Vector3r& shearDisp = geom->shearIncrement(); shearForce -= phys->ks*shearDisp; Real maxFs = phys->normalForce.squaredNorm()*std::pow(phys->tangensOfFrictionAngle,2); if (!scene->trackEnergy && !traceEnergy){//Update force but don't compute energy terms (see below)) // PFC3d SlipModel, is using friction angle. CoulombCriterion if( shearForce.squaredNorm() > maxFs ){ Real ratio = sqrt(maxFs) / shearForce.norm(); shearForce *= ratio;} } else { //almost the same with additional Vector3r instatinated for energy tracing, //duplicated block to make sure there is no cost for the instanciation of the vector when traceEnergy==false if(shearForce.squaredNorm() > maxFs){ Real ratio = sqrt(maxFs) / shearForce.norm(); Vector3r trialForce=shearForce;//store prev force for definition of plastic slip //define the plastic work input and increment the total plastic energy dissipated shearForce *= ratio; Real dissip=((1/phys->ks)*(trialForce-shearForce))/*plastic disp*/ .dot(shearForce)/*active force*/; if (traceEnergy) plasticDissipation += dissip; else if(dissip>0) scene->energy->add(dissip,"plastDissip",plastDissipIx,/*reset*/false); } // compute elastic energy as well scene->energy->add(0.5*(phys->normalForce.squaredNorm()/phys->kn+phys->shearForce.squaredNorm()/phys->ks),"elastPotential",elastPotentialIx,/*reset at every timestep*/true); } Vector3r force = -phys->normalForce-shearForce; Vector3r torque1 = (geom->radius1-0.5*geom->penetrationDepth)* geom->normal.cross(force); Vector3r torque2 = (geom->radius2-0.5*geom->penetrationDepth)* geom->normal.cross(force); scene->forces.addForce(id11,(1-geom->relPos1)*force); scene->forces.addForce(id12,geom->relPos1*force); scene->forces.addForce(id21,-(1-geom->relPos2)*force); scene->forces.addForce(id22,-geom->relPos2*force); scene->forces.addTorque(id11,(1-geom->relPos1)*torque1); scene->forces.addTorque(id12,geom->relPos1*torque1); scene->forces.addTorque(id21,(1-geom->relPos2)*torque2); scene->forces.addTorque(id22,geom->relPos2*torque2); return true; } YADE_PLUGIN((Law2_GridCoGridCoGeom_FrictPhys_CundallStrack)); //!################## Bounds ##################### void Bo1_GridConnection_Aabb::go(const shared_ptr& cm, shared_ptr& bv, const Se3r& se3, const Body* b){ GridConnection* GC = static_cast(cm.get()); if(!bv){ bv=shared_ptr(new Aabb); } Aabb* aabb=static_cast(bv.get()); Vector3r O = YADE_CAST(GC->node1->state.get())->pos; Vector3r O2 = YADE_CAST(GC->node2->state.get())->pos; if(!scene->isPeriodic){ for (int k=0;k<3;k++){ aabb->min[k]=min(O[k],O2[k])-GC->radius; aabb->max[k]=max(O[k],O2[k])+GC->radius; } return; } else{ O = scene->cell->unshearPt(O); O2 = scene->cell->unshearPt(O2); O2 = O2 + scene->cell->hSize*GC->cellDist.cast(); for (int k=0;k<3;k++){ aabb->min[k]=min(O[k],O2[k])-GC->radius; aabb->max[k]=max(O[k],O2[k])+GC->radius; } } } YADE_PLUGIN((Bo1_GridConnection_Aabb)); trunk-2018.02b/pkg/common/Grid.hpp000066400000000000000000000404351324306050200166430ustar00rootroot00000000000000/************************************************************************* * Copyright (C) 2012 by François Kneib francois.kneib@gmail.com * * Copyright (C) 2012 by Bruno Chareyre bruno.chareyre@hmg.inpg.fr * * This program is free software; it is licensed under the terms of the * * GNU General Public License v2 or later. See file LICENSE for details. * *************************************************************************/ /* TABLE OF CONTENT, and the minimum you need to understand. - 2 new shapes for grids : GridNode (vertices) and GridConnection (edges) - 2 new contact geometries : * GridNodeGeom6D to handle GridNode-GridNode contacts (the internal behaviour of the grid) * ScGridCoGeom to handle Sphere-GridConnection contacts (the interaction between the grid and an external sphere) Note : the Sphere-Grid contacts are always handled by the GridConnections, but the forces are applied on the related GridNodes. Note : there is no contact between two GridConnections, they must be linked with GridNodes. - The 2 related Ig2 : * Ig2_GridNode_GridNode_GridNodeGeom6D (doing almost the same than Ig2_Sphere_Sphere_ScGeom6D) * Ig2_Sphere_GridConnection_ScGridCoGeom (the biggest part of the code, it handles contact detection and history when a sphere is sliding on the grid over consecutive GridConnections) - The Law2_ScGridCoGeom_FrictPhys_CundallStrack who handles the elastic frictional Sphere-GridConnection contact. The GridNode-GridNode law is Law2_ScGeom6D_CohFrictPhys_CohesionMoment by inheritance. */ #pragma once #include "Sphere.hpp" #include #include #include #include #include #include #include #ifdef YADE_OPENGL #include #endif //!################## SHAPES ##################### class GridConnection: public Sphere{ public: virtual ~GridConnection(); Real getLength(); Vector3r getSegment(); void addPFacet(shared_ptr PF); YADE_CLASS_BASE_DOC_ATTRS_CTOR_PY(GridConnection,Sphere,"GridConnection shape (see [Effeindzourou2016]_, [Bourrier2013]_). Component of a grid designed to link two :yref:`GridNodes`. It is highly recommended to use :yref:`yade.gridpfacet.gridConnection` to generate correct :yref:`GridConnections`.", ((shared_ptr , node1 , ,,"First :yref:`Body` the GridConnection is connected to.")) ((shared_ptr , node2 , ,,"Second :yref:`Body` the GridConnection is connected to.")) ((bool, periodic, false,,"true if two nodes from different periods are connected.")) ((vector >,pfacetList,,,"List of :yref:`PFacet` the GridConnection is connected to.")) ((Vector3i , cellDist , Vector3i(0,0,0),,"Distance of bodies in cell size units, if using periodic boundary conditions. Note that periodic boundary conditions for GridConnections have not yet been fully implemented.")), createIndex();, /*ctor*/ /*py*/ .def("addPFacet",&GridConnection::addPFacet,(boost::python::arg("Body")),"Add a PFacet to the GridConnection.") ); REGISTER_CLASS_INDEX(GridConnection,Sphere); }; REGISTER_SERIALIZABLE(GridConnection); class GridNode: public Sphere{ public: virtual ~GridNode(); void addConnection(shared_ptr GC); void addPFacet(shared_ptr PF); YADE_CLASS_BASE_DOC_ATTRS_CTOR_PY(GridNode,Sphere,"GridNode shape, component of a grid.\nTo create a Grid, place the nodes first, they will define the spacial discretisation of it. It is highly recommended to use :yref:`yade.gridpfacet.gridNode` to generate correct :yref:`GridNodes`. Note that the GridNodes should only be in an Interaction with other GridNodes. The Sphere-Grid contact is only handled by the :yref:`GridConnections`.", ((vector >,pfacetList,,,"List of :yref:`PFacets` the GridConnection is connected to.")) ((vector >,ConnList,,,"List of :yref:`GridConnections` the GridNode is connected to.")), /*ctor*/ createIndex();, /*py*/ .def("addConnection",&GridNode::addConnection,(boost::python::arg("Body")),"Add a GridConnection to the GridNode.") .def("addPFacet",&GridNode::addPFacet,(boost::python::arg("Body")),"Add a PFacet to the GridNode.") ); REGISTER_CLASS_INDEX(GridNode,Sphere); }; REGISTER_SERIALIZABLE(GridNode); //!################## PFacet SHAPE ##################### class PFacet : public Shape { public: virtual ~PFacet(); /// Normals of edges Vector3r ne[3]; /// Inscribing cirle radius Real icr; /// Length of the vertice vectors Real vl[3]; /// Unit vertice vectors Vector3r vu[3]; YADE_CLASS_BASE_DOC_ATTRS_CTOR(PFacet,Shape,"PFacet (particle facet) geometry (see [Effeindzourou2016]_, [Effeindzourou2015a]_). It is highly recommended to use the helper functions in :yref:`yade.gridpfacet` (e.g., gridpfacet.pfacetCreator1-4) to generate correct :yref:`PFacet` elements.", ((shared_ptr , node1 , ,,"First :yref:`Body` the Pfacet is connected to.")) ((shared_ptr , node2 , ,,"Second :yref:`Body` the Pfacet is connected to.")) ((shared_ptr , node3 , ,,"third :yref:`Body` the Pfacet is connected to.")) ((shared_ptr , conn1 , ,,"First :yref:`Body` the Pfacet is connected to.")) ((shared_ptr , conn2 , ,,"Second :yref:`Body` the Pfacet is connected to.")) ((shared_ptr , conn3 , ,,"third :yref:`Body` the Pfacet is connected to.")) ((Vector3r,normal,Vector3r(NaN,NaN,NaN),(Attr::readonly | Attr::noSave),"PFacet's normal (in local coordinate system)")) ((Real,radius,-1,,"PFacet's radius")) ((Real,area,NaN,(Attr::readonly | Attr::noSave),"PFacet's area")) ((Vector3i , cellDist , Vector3i(0,0,0),,"Distance of bodies in cell size units, if using periodic boundary conditions. Note that periodic boundary conditions for PFacets have not yet been fully implemented.")) , /* ctor */ createIndex(); ); DECLARE_LOGGER; REGISTER_CLASS_INDEX(PFacet,Shape); }; REGISTER_SERIALIZABLE(PFacet); //!################## Contact Geometry ##################### //! O-O class GridNodeGeom6D: public ScGeom6D { public: virtual ~GridNodeGeom6D(); YADE_CLASS_BASE_DOC_ATTRS_INIT_CTOR_PY(GridNodeGeom6D,ScGeom6D,"Geometry of a :yref:`GridNode`-:yref:`GridNode` contact. Inherits almost everything from :yref:`ScGeom6D`.", ((shared_ptr, connectionBody,,,"Reference to the :yref:`GridNode` :yref:`Body` who is linking the two :yref:`GridNodes`.")), /* extra initializers */, /* ctor */ createIndex();, /* py */ ); REGISTER_CLASS_INDEX(GridNodeGeom6D,ScGeom6D); }; REGISTER_SERIALIZABLE(GridNodeGeom6D); //! O/ class ScGridCoGeom: public ScGeom6D { public: /// Emulate a sphere whose position is the projection of sphere's center on cylinder sphere, and with motion linearly interpolated between nodes State fictiousState; virtual ~ScGridCoGeom (); YADE_CLASS_BASE_DOC_ATTRS_CTOR(ScGridCoGeom,ScGeom6D,"Geometry of a :yref:`GridConnection`-:yref:`Sphere` contact.", ((int,isDuplicate,0,,"this flag is turned true (1) automatically if the contact is shared between two Connections. A duplicated interaction will be skipped once by the constitutive law, so that only one contact at a time is effective. If isDuplicate=2, it means one of the two duplicates has no longer geometric interaction, and should be erased by the constitutive laws.")) ((int,trueInt,-1,,"Defines the body id of the :yref:`GridConnection` where the contact is real, when :yref:`ScGridCoGeom::isDuplicate`>0.")) ((int,id3,0,,"id of the first :yref:`GridNode`. |yupdate|")) ((int,id4,0,,"id of the second :yref:`GridNode`. |yupdate|")) ((int,id5,-1,,"id of the third :yref:`GridNode`. |yupdate|")) ((Vector3r,weight,Vector3r(0,0,0),,"barycentric coordinates of the projection point |yupdate|")) ((Real,relPos,0,,"position of the contact on the connection (0: node-, 1:node+) |yupdate|")), createIndex(); /*ctor*/ ); REGISTER_CLASS_INDEX(ScGridCoGeom,ScGeom6D); }; REGISTER_SERIALIZABLE(ScGridCoGeom); //! -|- class GridCoGridCoGeom: public ScGeom { public: /// Emulate a sphere whose position is the projection of sphere's center on cylinder sphere, and with motion linearly interpolated between nodes State fictiousState1,fictiousState2; virtual ~GridCoGridCoGeom (); YADE_CLASS_BASE_DOC_ATTRS_CTOR(GridCoGridCoGeom,ScGeom,"Geometry of a :yref:`GridConnection`-:yref:`GridConnection` contact.", ((Real,relPos1,0,,"position of the contact on the first connection (0: node-, 1:node+) |yupdate|")) ((Real,relPos2,0,,"position of the contact on the first connection (0: node-, 1:node+) |yupdate|")), createIndex(); /*ctor*/ ); REGISTER_CLASS_INDEX(ScGridCoGeom,ScGeom); }; REGISTER_SERIALIZABLE(GridCoGridCoGeom); //!################## IGeom Functors ##################### //! O-O class Ig2_GridNode_GridNode_GridNodeGeom6D: public Ig2_Sphere_Sphere_ScGeom{ public: virtual bool go(const shared_ptr& cm1, const shared_ptr& cm2, const State& state1, const State& state2, const Vector3r& shift2, const bool& force, const shared_ptr& c); virtual bool goReverse( const shared_ptr& cm1, const shared_ptr& cm2, const State& state1, const State& state2, const Vector3r& shift2, const bool& force, const shared_ptr& c); YADE_CLASS_BASE_DOC_ATTRS(Ig2_GridNode_GridNode_GridNodeGeom6D,Ig2_Sphere_Sphere_ScGeom,"Create/update a :yref:`GridNodeGeom6D` instance representing the geometry of a contact point between two :yref:`GridNode`, including relative rotations.", ((bool,updateRotations,true,,"Precompute relative rotations. Turning this false can speed up simulations when rotations are not needed in constitutive laws (e.g. when spheres are compressed without cohesion and moment in early stage of a triaxial test), but is not foolproof. Change this value only if you know what you are doing.")) ((bool,creep,false,,"Substract rotational creep from relative rotation. The rotational creep :yref:`ScGeom6D::twistCreep` is a quaternion and has to be updated inside a constitutive law, see for instance :yref:`Law2_ScGeom6D_CohFrictPhys_CohesionMoment`.")) ); FUNCTOR2D(GridNode,GridNode); // needed for the dispatcher, even if it is symmetric DEFINE_FUNCTOR_ORDER_2D(GridNode,GridNode); }; REGISTER_SERIALIZABLE(Ig2_GridNode_GridNode_GridNodeGeom6D); //! -/- class Ig2_GridConnection_GridConnection_GridCoGridCoGeom: public IGeomFunctor{ public: virtual bool go(const shared_ptr& cm1, const shared_ptr& cm2, const State& state1, const State& state2, const Vector3r& shift2, const bool& force, const shared_ptr& c); virtual bool goReverse( const shared_ptr& cm1, const shared_ptr& cm2, const State& state1, const State& state2, const Vector3r& shift2, const bool& force, const shared_ptr& c); YADE_CLASS_BASE_DOC_ATTRS(Ig2_GridConnection_GridConnection_GridCoGridCoGeom,IGeomFunctor,"Create/update a :yref:`GridCoGridCoGeom` instance representing the geometry of a contact point between two :yref:`GridConnection` , including relative rotations.", ); FUNCTOR2D(GridConnection,GridConnection); DEFINE_FUNCTOR_ORDER_2D(GridConnection,GridConnection); }; REGISTER_SERIALIZABLE(Ig2_GridConnection_GridConnection_GridCoGridCoGeom); //! O/ class Ig2_Sphere_GridConnection_ScGridCoGeom: public IGeomFunctor{ public: virtual bool go(const shared_ptr& cm1, const shared_ptr& cm2, const State& state1, const State& state2, const Vector3r& shift2, const bool& force, const shared_ptr& c); virtual bool goReverse( const shared_ptr& cm1, const shared_ptr& cm2, const State& state1, const State& state2, const Vector3r& shift2, const bool& force, const shared_ptr& c); YADE_CLASS_BASE_DOC_ATTRS(Ig2_Sphere_GridConnection_ScGridCoGeom,IGeomFunctor,"Create/update a :yref:`ScGridCoGeom6D` instance representing the geometry of a contact point between a :yref:`GricConnection` and a :yref:`Sphere` including relative rotations.", ((Real,interactionDetectionFactor,1,,"Enlarge both radii by this factor (if >1), to permit creation of distant interactions.")) ); FUNCTOR2D(Sphere,GridConnection); DEFINE_FUNCTOR_ORDER_2D(Sphere,GridConnection); }; REGISTER_SERIALIZABLE(Ig2_Sphere_GridConnection_ScGridCoGeom); //!################## Laws ##################### //! O/ class Law2_ScGridCoGeom_FrictPhys_CundallStrack: public LawFunctor{ public: virtual bool go(shared_ptr& _geom, shared_ptr& _phys, Interaction* I); YADE_CLASS_BASE_DOC_ATTRS_CTOR_PY(Law2_ScGridCoGeom_FrictPhys_CundallStrack,LawFunctor,"Law between a frictional :yref:`GridConnection` and a frictional :yref:`Sphere`. Almost the same than :yref:`Law2_ScGeom_FrictPhys_CundallStrack`, but the force is divided and applied on the two :yref:`GridNodes` only.", ((bool,neverErase,false,,"Keep interactions even if particles go away from each other (only in case another constitutive law is in the scene, e.g. :yref:`Law2_ScGeom_CapillaryPhys_Capillarity`)")) ((int,plastDissipIx,-1,(Attr::hidden|Attr::noSave),"Index for plastic dissipation (with O.trackEnergy)")) ((int,elastPotentialIx,-1,(Attr::hidden|Attr::noSave),"Index for elastic potential energy (with O.trackEnergy)")) ,, ); FUNCTOR2D(ScGridCoGeom,FrictPhys); }; REGISTER_SERIALIZABLE(Law2_ScGridCoGeom_FrictPhys_CundallStrack); class Law2_ScGridCoGeom_CohFrictPhys_CundallStrack: public LawFunctor{ public: virtual bool go(shared_ptr& _geom, shared_ptr& _phys, Interaction* I); YADE_CLASS_BASE_DOC_ATTRS_CTOR_PY(Law2_ScGridCoGeom_CohFrictPhys_CundallStrack,LawFunctor,"Law between a cohesive frictional :yref:`GridConnection` and a cohesive frictional :yref:`Sphere`. Almost the same than :yref:`Law2_ScGeom6D_CohFrictPhys_CohesionMoment`, but THE ROTATIONAL MOMENTS ARE NOT COMPUTED.", ((bool,neverErase,false,,"Keep interactions even if particles go away from each other (only in case another constitutive law is in the scene, e.g. :yref:`Law2_ScGeom_CapillaryPhys_Capillarity`)")) ((int,plastDissipIx,-1,(Attr::hidden|Attr::noSave),"Index for plastic dissipation (with O.trackEnergy)")) ,, ); FUNCTOR2D(ScGridCoGeom,CohFrictPhys); }; REGISTER_SERIALIZABLE(Law2_ScGridCoGeom_CohFrictPhys_CundallStrack); //! -/- class Law2_GridCoGridCoGeom_FrictPhys_CundallStrack: public Law2_ScGeom_FrictPhys_CundallStrack{ public: virtual bool go(shared_ptr& _geom, shared_ptr& _phys, Interaction* I); YADE_CLASS_BASE_DOC_ATTRS(Law2_GridCoGridCoGeom_FrictPhys_CundallStrack,Law2_ScGeom_FrictPhys_CundallStrack,"Frictional elastic contact law between two :yref:`gridConnection` . See :yref:`Law2_ScGeom_FrictPhys_CundallStrack` for more details.", /*ATTRS*/ ); FUNCTOR2D(GridCoGridCoGeom,FrictPhys); DECLARE_LOGGER; }; REGISTER_SERIALIZABLE(Law2_GridCoGridCoGeom_FrictPhys_CundallStrack); //!################## Bounds ##################### class Bo1_GridConnection_Aabb : public BoundFunctor { public : void go(const shared_ptr& cm, shared_ptr& bv, const Se3r&, const Body*); FUNCTOR1D(GridConnection); YADE_CLASS_BASE_DOC_ATTRS(Bo1_GridConnection_Aabb,BoundFunctor,"Functor creating :yref:`Aabb` from a :yref:`GridConnection`.", ((Real,aabbEnlargeFactor,((void)"deactivated",-1),,"Relative enlargement of the bounding box; deactivated if negative.")) ); }; REGISTER_SERIALIZABLE(Bo1_GridConnection_Aabb); //!################## Rendering ##################### #ifdef YADE_OPENGL class Gl1_GridConnection : public GlShapeFunctor{ private: //static int glCylinderList; //void subdivideTriangle(Vector3r& v1,Vector3r& v2,Vector3r& v3, int depth); void drawCylinder(bool wire, Real radius, Real length, const Quaternionr& shift=Quaternionr::Identity()); //void initGlLists(void); public: virtual void go(const shared_ptr&, const shared_ptr&,bool,const GLViewInfo&); void out( Quaternionr q ); YADE_CLASS_BASE_DOC_STATICATTRS(Gl1_GridConnection,GlShapeFunctor,"Renders :yref:`Cylinder` object", ((bool,wire,false,,"Only show wireframe (controlled by ``glutSlices`` and ``glutStacks``.")) ((bool,glutNormalize,true,,"Fix normals for non-wire rendering")) ((int,glutSlices,8,,"Number of cylinder slices.")) ((int,glutStacks,4,,"Number of cylinder stacks.")) ); RENDERS(GridConnection); }; REGISTER_SERIALIZABLE(Gl1_GridConnection); #endif trunk-2018.02b/pkg/common/Grid_GUI.cpp000066400000000000000000000053571324306050200173460ustar00rootroot00000000000000/************************************************************************* * Copyright (C) 2012 by François Kneib francois.kneib@gmail.com * * Copyright (C) 2012 by Bruno Chareyre bruno.chareyre@hmg.inpg.fr * * This program is free software; it is licensed under the terms of the * * GNU General Public License v2 or later. See file LICENSE for details. * *************************************************************************/ #include "Grid.hpp" #ifdef YADE_OPENGL #include //!################## Rendering ##################### bool Gl1_GridConnection::wire; bool Gl1_GridConnection::glutNormalize; int Gl1_GridConnection::glutSlices; int Gl1_GridConnection::glutStacks; void Gl1_GridConnection::out( Quaternionr q ) { AngleAxisr aa(q); std::cout << " axis: " << aa.axis()[0] << " " << aa.axis()[1] << " " << aa.axis()[2] << ", angle: " << aa.angle() << " | "; } void Gl1_GridConnection::go(const shared_ptr& cm, const shared_ptr& st ,bool wire2, const GLViewInfo&) { GridConnection *GC=static_cast(cm.get()); Real r=GC->radius; Real length=GC->getLength(); const shared_ptr intr = scene->interactions->find((int)GC->node1->getId(),(int)GC->node2->getId()); Vector3r segt = GC->node2->state->pos - GC->node1->state->pos; if (scene->isPeriodic && intr) segt+=scene->cell->intrShiftPos(intr->cellDist); //glMaterialv(GL_FRONT, GL_AMBIENT_AND_DIFFUSE, Vector3f(cm->color[0],cm->color[1],cm->color[2])); glColor3v(cm->color); if(glutNormalize) glPushAttrib(GL_NORMALIZE); // glPushMatrix(); Quaternionr shift; shift.setFromTwoVectors(Vector3r::UnitZ(),segt); if(intr){drawCylinder(wire || wire2, r,length,shift);} // if (intr && scene->isPeriodic) { glTranslatef(-segt[0],-segt[1],-segt[2]); drawCylinder(wire || wire2, r,length,-shift);} if(glutNormalize) glPopAttrib(); // glPopMatrix(); return; } void Gl1_GridConnection::drawCylinder(bool wire, Real radius, Real length, const Quaternionr& shift) { glPushMatrix(); GLUquadricObj *quadObj = gluNewQuadric(); gluQuadricDrawStyle(quadObj, (GLenum) (wire ? GLU_SILHOUETTE : GLU_FILL)); gluQuadricNormals(quadObj, (GLenum) GLU_SMOOTH); gluQuadricOrientation(quadObj, (GLenum) GLU_OUTSIDE); AngleAxisr aa(shift); glRotatef(aa.angle()*180.0/Mathr::PI,aa.axis()[0],aa.axis()[1],aa.axis()[2]); gluCylinder(quadObj, radius, radius, length, glutSlices,glutStacks); gluQuadricOrientation(quadObj, (GLenum) GLU_INSIDE); //glutSolidSphere(radius,glutSlices,glutStacks); glTranslatef(0.0,0.0,length); //glutSolidSphere(radius,glutSlices,glutStacks); // gluDisk(quadObj,0.0,radius,glutSlices,_loops); gluDeleteQuadric(quadObj); glPopMatrix(); } YADE_PLUGIN((Gl1_GridConnection)); #endif trunk-2018.02b/pkg/common/HydroForceEngine.cpp000066400000000000000000000772111324306050200211450ustar00rootroot00000000000000// 2017 © Raphael Maurin // 2017 © Julien Chauchat #include"HydroForceEngine.hpp" #include #include #include #include #include #include #include #include #include YADE_PLUGIN((HydroForceEngine)); void HydroForceEngine::action(){ /* Application of hydrodynamical forces */ Vector3r gravityBuoyancy = gravity; if (steadyFlow==true) gravityBuoyancy[0] = 0.;// If the fluid flow is steady, no streamwise buoyancy contribution from gravity FOREACH(Body::id_t id, ids){ Body* b=Body::byId(id,scene).get(); if (!b) continue; if (!(scene->bodies->exists(id))) continue; const Sphere* sphere = dynamic_cast(b->shape.get()); if (sphere){ Vector3r posSphere = b->state->pos;//position vector of the sphere int p = floor((posSphere[2]-zRef)/deltaZ); //cell number in which the particle is if ((p=0)) { Vector3r liftForce = Vector3r::Zero(); Vector3r dragForce = Vector3r::Zero(); Vector3r convAccForce = Vector3r::Zero(); //deterministic version // Vector3r vRel = Vector3r(vxFluid[p],0,0) - b->state->vel;//fluid-particle relative velocity Vector3r vRel = Vector3r(vxFluid[p]+vFluctX[id],vFluctY[id],vFluctZ[id]) - b->state->vel;//fluid-particle relative velocity //Drag force calculation if (vRel.norm()!=0.0) { dragForce = 0.5*densFluid*Mathr::PI*pow(sphere->radius,2.0)*(0.44*vRel.norm()+24.4*viscoDyn/(densFluid*sphere->radius*2))*pow(1-phiPart[p],-expoRZ)*vRel; } //lift force calculation due to difference of fluid pressure between top and bottom of the particle int intRadius = floor(sphere->radius/deltaZ); if ((p+intRadius0)&&(lift==true)) { double vRelTop = vxFluid[p+intRadius] - b->state->vel[0]; // relative velocity of the fluid wrt the particle at the top of the particle double vRelBottom = vxFluid[p-intRadius] - b->state->vel[0]; // same at the bottom liftForce[2] = 0.5*densFluid*Mathr::PI*pow(sphere->radius,2.0)*Cl*(vRelTop*vRelTop-vRelBottom*vRelBottom); } //buoyant weight force calculation Vector3r buoyantForce = -4.0/3.0*Mathr::PI*pow(sphere->radius,3.0)*densFluid*gravityBuoyancy; if (convAccOption==true){convAccForce[0] = - convAcc[p];} //add the hydro forces to the particle scene->forces.addForce(id,dragForce+liftForce+buoyantForce+convAccForce); } } } } void HydroForceEngine::averageProfile(){ //Initialization int minZ; int maxZ; int numLayer; double deltaCenter; double zInf; double zSup; double volPart; Vector3r uRel = Vector3r::Zero(); Vector3r fDrag = Vector3r::Zero(); int nMax = nCell; vector velAverageX(nMax,0.0); vector velAverageY(nMax,0.0); vector velAverageZ(nMax,0.0); vector phiAverage(nMax,0.0); vector dragAverage(nMax,0.0); vector phiAverage1(nMax,0.0); vector dragAverage1(nMax,0.0); vector velAverageX1(nMax,0.0); vector velAverageY1(nMax,0.0); vector velAverageZ1(nMax,0.0); vector phiAverage2(nMax,0.0); vector dragAverage2(nMax,0.0); vector velAverageX2(nMax,0.0); vector velAverageY2(nMax,0.0); vector velAverageZ2(nMax,0.0); //Loop over the particles FOREACH(const shared_ptr& b, *Omega::instance().getScene()->bodies){ shared_ptr s=YADE_PTR_DYN_CAST(b->shape); if(!s) continue; const double zPos = b->state->pos[2]-zRef; int Np = floor(zPos/deltaZ); //Define the layer number with 0 corresponding to zRef. Let the z position wrt to zero, that way all z altitude are positive. (otherwise problem with volPart evaluation) if ((b->state->blockedDOFs==State::DOF_ALL)&&(zPos > s->radius)) continue;// to remove contribution from the fixed particles on the sidewalls. // Relative fluid/particle velocity using also the associated fluid vel. fluct. if ((Np>=0)&&(Npid], vFluctY[b->id],vFluctZ[b->id]) - b->state->vel; // Drag force with a Dallavalle formulation (drag coef.) and Richardson-Zaki Correction (hindrance effect) fDrag = 0.5*Mathr::PI*pow(s->radius,2.0)*densFluid*(0.44*uRel.norm()+24.4*viscoDyn/(densFluid*2.0*s->radius))*pow((1-phiPart[Np]),-expoRZ)*uRel; } else fDrag = Vector3r::Zero(); minZ= floor((zPos-s->radius)/deltaZ); maxZ= floor((zPos+s->radius)/deltaZ); deltaCenter = zPos - Np*deltaZ; // Loop over the cell in which the particle is contained numLayer = minZ; while (numLayer<=maxZ){ if ((numLayer>=0)&&(numLayerradius) zInf = -s->radius; if (zSup>s->radius) zSup = s->radius; //Analytical formulation of the volume of a slice of sphere volPart = Mathr::PI*pow(s->radius,2)*(zSup - zInf +(pow(zInf,3)-pow(zSup,3))/(3*pow(s->radius,2))); phiAverage[numLayer]+=volPart; velAverageX[numLayer]+=volPart*b->state->vel[0]; velAverageY[numLayer]+=volPart*b->state->vel[1]; velAverageZ[numLayer]+=volPart*b->state->vel[2]; dragAverage[numLayer]+=volPart*fDrag[0]; if (twoSize==true){ if (s->radius==radiusPart1){ phiAverage1[numLayer]+=volPart; dragAverage1[numLayer]+=volPart*fDrag[0]; velAverageX1[numLayer]+=volPart*b->state->vel[0]; velAverageY1[numLayer]+=volPart*b->state->vel[1]; velAverageZ1[numLayer]+=volPart*b->state->vel[2]; } if (s->radius==radiusPart2){ phiAverage2[numLayer]+=volPart; dragAverage2[numLayer]+=volPart*fDrag[0]; velAverageX2[numLayer]+=volPart*b->state->vel[0]; velAverageY2[numLayer]+=volPart*b->state->vel[1]; velAverageZ2[numLayer]+=volPart*b->state->vel[2]; } } } numLayer+=1; } } //Normalized the weighted velocity by the volume of particles contained inside the cell for(int n=0;nbodies->size()){ size=scene->bodies->size(); vFluctX.resize(size); vFluctY.resize(size); vFluctZ.resize(size); } /* reset stored values to zero */ memset(& vFluctX[0],0,size); memset(& vFluctY[0],0,size); memset(& vFluctZ[0],0,size); /* Create a random number generator rnd() with a gaussian distribution of mean 0 and stdev 1.0 */ /* see http://www.boost.org/doc/libs/1_55_0/doc/html/boost_random/reference.html and the chapter 7 of Numerical Recipes in C, second edition (1992) for more details */ static boost::minstd_rand0 randGen((int)TimingInfo::getNow(true)); static boost::normal_distribution dist(0.0, 1.0); static boost::variate_generator > rnd(randGen,dist); double rand1 = 0.0; double rand2 = 0.0; double rand3 = 0.0; /* Attribute a fluid velocity fluctuation to each body above the bed elevation */ FOREACH(Body::id_t id, ids){ Body* b=Body::byId(id,scene).get(); if (!b) continue; if (!(scene->bodies->exists(id))) continue; const Sphere* sphere = dynamic_cast(b->shape.get()); if (sphere){ Vector3r posSphere = b->state->pos;//position vector of the sphere int p = floor((posSphere[2]-zRef)/deltaZ); //cell number in which the particle is // If the particle is inside the water and above the bed elevation, so inside the turbulent flow, evaluate a turbulent fluid velocity fluctuation which will be used to apply the drag. // The fluctuation magnitude is linked to the value of the Reynolds stress tensor at the given position, a kind of local friction velocity ustar // The fluctuations along wall-normal and streamwise directions are correlated in order to be consistent with the formulation of the Reynolds stress tensor and to recover the result // that the magnitude of the fluctuation along streamwise = 2*along wall normal if ((pbedElevation)) { // Remove the particles outside of the flow and inside the granular bed, they are not submitted to turbulent fluctuations. double uStar2 = ReynoldStresses[p]/densFluid; if (uStar2>0.0){ double uStar = sqrt(uStar2); rand1 = rnd(); rand2 = rnd(); rand3 = -rand1 + rnd();// x and z fluctuation are correlated as measured by Nezu 1977 and as expected from the formulation of the Reynolds stress tensor. vFluctZ[id] = rand1*uStar; vFluctY[id] = rand2*uStar; vFluctX[id] = rand3*uStar; } } else{ vFluctZ[id] = 0.0; vFluctY[id] = 0.0; vFluctX[id] = 0.0; } } } } /* Alternative Velocity fluctuation model, same as turbulentFluctuation model but with a time step associated with the fluctuation generation depending on z */ /* Should be executed in the python script at a period dtFluct corresponding to the smallest value of the fluctTime vector */ /* Should be initialized before running HydroForceEngine */ void HydroForceEngine::turbulentFluctuationBIS(){ int idPartMax = vFluctX.size(); double rand1 = 0.0; double rand2 = 0.0; double rand3 = 0.0; /* Create a random number generator rnd() with a gaussian distribution of mean 0 and stdev 1.0 */ /* see http://www.boost.org/doc/libs/1_55_0/doc/html/boost_random/reference.html and the chapter 7 of Numerical Recipes in C, second edition (1992) for more details */ static boost::minstd_rand0 randGen((int)TimingInfo::getNow(true)); static boost::normal_distribution dist(0.0, 1.0); static boost::variate_generator > rnd(randGen,dist); //Loop on the particles for(int idPart=0;idPartbodies->exists(idPart))) continue; const Sphere* sphere = dynamic_cast(b->shape.get()); double uStar = 0.0; if (sphere){ Vector3r posSphere = b->state->pos;//position vector of the sphere int p = floor((posSphere[2]-zRef)/deltaZ); //cell number in which the particle is if (ReynoldStresses[p]>0.0) uStar = sqrt(ReynoldStresses[p]/densFluid); // Remove the particles outside of the flow and inside the granular bed, they are not submitted to turbulent fluctuations. if ((pbedElevation)) { rand1 = rnd(); rand2 = rnd(); rand3 = -rand1 + rnd(); // x and z fluctuation are correlated as measured by Nezu 1977 and as expected from the formulation of the Reynolds stress tensor. vFluctZ[idPart] = rand1*uStar; vFluctY[idPart] = rand2*uStar; vFluctX[idPart] = rand3*uStar; // Limit the value to avoid the application of fluctuations in the viscous sublayer const double zPos = max(b->state->pos[2]-zRef-bedElevation,11.6*viscoDyn/densFluid/uStar); // Time of application of the fluctuation as a function of depth from kepsilon model if (uStar>0.0) fluctTime[idPart]=min(0.33*0.41*zPos/uStar,10.); } else{ vFluctZ[idPart] = 0.0; vFluctY[idPart] = 0.0; vFluctX[idPart] = 0.0; fluctTime[idPart] = 0.0; } } } } } /* Velocity fluctuation determination. To execute at a given period*/ /* Should be initialized before running HydroForceEngine */ void HydroForceEngine::turbulentFluctuationFluidizedBed(){ /* check size */ size_t size=vFluctX.size(); if(sizebodies->size()){ size=scene->bodies->size(); vFluctX.resize(size); vFluctY.resize(size); vFluctZ.resize(size); } /* reset stored values to zero */ memset(& vFluctX[0],0,size); memset(& vFluctY[0],0,size); memset(& vFluctZ[0],0,size); /* Create a random number generator rnd() with a gaussian distribution of mean 0 and stdev 1.0 */ /* see http://www.boost.org/doc/libs/1_55_0/doc/html/boost_random/reference.html and the chapter 7 of Numerical Recipes in C, second edition (1992) for more details */ static boost::minstd_rand0 randGen((int)TimingInfo::getNow(true)); static boost::normal_distribution dist(0.0, 1.0); static boost::variate_generator > rnd(randGen,dist); double rand1 = 0.0; double rand2 = 0.0; double rand3 = 0.0; /* Attribute a fluid velocity fluctuation to each body above the bed elevation */ FOREACH(Body::id_t id, ids){ Body* b=Body::byId(id,scene).get(); if (!b) continue; if (!(scene->bodies->exists(id))) continue; const Sphere* sphere = dynamic_cast(b->shape.get()); if (sphere){ Vector3r posSphere = b->state->pos;//position vector of the sphere int p = floor((posSphere[2]-zRef)/deltaZ); //cell number in which the particle is // If the particle is inside the water and above the bed elevation, so inside the turbulent flow, evaluate a turbulent fluid velocity fluctuation which will be used to apply the drag. // The fluctuation magnitude is linked to the value of the Reynolds stress tensor at the given position, a kind of local friction velocity ustar if ((p0.)) { // Remove the particles outside of the flow double uStar2 = ReynoldStresses[p]/densFluid; if (uStar2>0.0){ double uStar = sqrt(uStar2); rand1 = rnd(); rand2 = rnd(); rand3 = rnd(); vFluctZ[id] = rand1*uStar; vFluctY[id] = rand2*uStar; vFluctX[id] = rand3*uStar; } } else{ vFluctZ[id] = 0.0; vFluctY[id] = 0.0; vFluctX[id] = 0.0; } } } } /////////////////////// /* Fluid Resolution */ /////////////////////// // Fluid resolution routine: 1D vertical volume-averaged fluid momentum balance resolution void HydroForceEngine::fluidResolution(double tfin,double dt) { //Variables declaration int j; double dummy,dn1,ds1,dn2,ds2,dzn,dzs,dzm,alphafp,alphafwn,alphafws,viscof,dz,as,an,ap1; vector sig(nCell,0.0), dsig(nCell-1,0.), beta(nCell,0.), ufn(nCell,0.), wallFriction(nCell,0.),viscoft(nCell,0.),ufnp(nCell,0.),a(nCell,0.),b(nCell,0.),c(nCell,0.),s(nCell,0.),alphaf(nCell,0.),alphas(nCell,0.),us(nCell,0.); //Initialisation double time=0; ufn = vxFluid; //Assign the global variable vxFluid to ufn, i.e. the last fluid velocity profile evaluated us = vxPart; alphas = phiPart; viscof = viscoDyn/densFluid; // compute the kinematic viscosity dz = deltaZ; double imp=0.5; // Implicitation factor of the lateral sink term due to wall friction for (j=0;j/rhof/(uf - up), not changing during the fluid resolution // //Initialization // taufsi.resize(nCell); memset(& taufsi[0],0,nCell); //Resize and initialize taufsi // double lim = 1e-5, dragTerm=0., partVolume=1., partVolume1=1., partVolume2=1.; // // Evaluate particles volume // if (twoSize==true){ // partVolume1 = 4./3.*Mathr::PI*pow(radiusPart1,3); // partVolume2 = 4./3.*Mathr::PI*pow(radiusPart2,3); // } // else partVolume = 4./3.*Mathr::PI*pow(radiusPart,3); // // Compute taufsi // taufsi[0] = 0.; // for(int i=1;i lm(nCell,0.); // compute the fluid height double fluidHeight = sig[nCell-1]; // Eddy viscosity if (iturbu==0) { // 0 : No turbulence for(j=0;j=epsilon)&&(q ddem(nCell,0.), ddfm(nCell,0.); double dddiv; // downward sweep dddiv=b[0]; ddem[0]=-c[0]/dddiv; ddfm[0]=s[0]/dddiv; for (i=1;i<=nCell-2;i++){ dddiv=b[i]+a[i]*ddem[i-1]; ddem[i]=-c[i]/dddiv; ddfm[i]=(s[i]-a[i]*ddfm[i-1])/dddiv; } // upward sweep dddiv=b[nCell-1]+a[nCell-1]*ddem[nCell-2]; ddfm[nCell-1]=(s[nCell-1]-a[nCell-1]*ddfm[nCell-2])/dddiv; ufnp[nCell-1]=ddfm[nCell-1]; for (ii=2;ii0) ReynoldStresses[j] = densFluid*viscoft[j]*(ufn[j+1]-ufn[j])/dsig[j]; } ReynoldStresses[nCell-1] = ReynoldStresses[nCell-2];turbulentViscosity[nCell-1]=viscoft[nCell-1]; } ///////////////////////////////////////////////////////////////////////// END OF HydroForceEngine::fluidResolution // Compute the effective viscosity accounting for the presence of particles void HydroForceEngine::calbeta(vector beta_in) { int j; // viscosity amplification factor if (irheolf==0) // 0 : Viscosite du fluide pur { for(j=0;j<=nCell-1;j++) beta_in[j]=1.; } else if (irheolf==1) // 1 : Viscosite d'Einstein { for(j=0;j<=nCell-1;j++) beta_in[j]=1.+2.5*phiPart[j]; } } // Compute the eddy viscosity, turbulentViscosity void HydroForceEngine::calviscotlm(vector ufn_in,double viscof_in,vector viscoft_in,vector sig_in,vector dsig_in) { int j; double lm[nCell],sum,alphasmid,dudz,ustar,dzm; // compute the fluid height double fluidHeight = sig_in[nCell-1]; // Eddy viscosity if (iturbu==0) // 0 : No turbulence { for(j=0;j<=nCell-1;j++) viscoft_in[j]=0.; } else if (iturbu==1) // 1 : Turbulence activated { if (ilm==0) // 0 : Prandtl mixing length { lm[0]=0.; for(j=1;j<=nCell-1;j++) lm[j]=kappa*sig_in[j]; } else if (ilm==1) // 1 : Parabolic profile (free surface flows) { lm[0]=0.; for(j=1;j<=nCell-1;j++) lm[j]=kappa*sig_in[j]*sqrt(1.-sig_in[j]/fluidHeight); lm[nCell-1]=0.; } else if (ilm==2) // 2 : Li and Sawamoto (1995) integral of concentration profile { sum = 0.; lm[0]=0.; for(j=1;j<=nCell-1;j++) { alphasmid=std::min(0.5*(phiPart[j-1]+phiPart[j]),phiMax); sum=sum+kappa*(phiMax-alphasmid)/phiMax*dsig_in[j-1]; lm[j]=sum; } lm[nCell-1]=lm[nCell-2]; } // Compute the velocity gradient and the mixing length for(j=1;j ddam1,vector ddam2,vector ddam3, vector ddbm,vector ddxm) //void HydroForceEngine::doubleq(double ddam1[],double ddam2[],double ddam3[], double ddbm[],double ddxm[]) { /* reosolution of tridiagonal system am1,2,3[n] - Tridiagonal matrix coefficients bm[n] - RHS vector xm[n] - Solution vector em[n], fm[n) - working arrays n - algebraic system size */ cout<<"b"< ddem(nCell),ddfm(nCell); // downward sweep dddiv=ddam2[0]; ddem[0]=-ddam3[0]/dddiv; ddfm[0]=ddbm[0]/dddiv; for (i=1;i<=nCell-2;i++) { dddiv=ddam2[i]+ddam1[i]*ddem[i-1]; ddem[i]=-ddam3[i]/dddiv; ddfm[i]=(ddbm[i]-ddam1[i]*ddfm[i-1])/dddiv; } // upward sweep dddiv=ddam2[nCell-1]+ddam1[nCell-1]*ddem[nCell-2]; ddfm[nCell-1]=(ddbm[nCell-1]-ddam1[nCell-1]*ddfm[nCell-2])/dddiv; ddxm[nCell-1]=ddfm[nCell-1]; for (ii=2;ii<=nCell;ii++) { i=nCell-ii; ddxm[i]=ddem[i]*ddxm[i+1]+ddfm[i]; } cout<<"c"< ufn_in,double channelWidth_in,double viscof_in,vector wallFriction_in){ int maxiter,q,j; double Re, epsilon, ff, ffold,delta; maxiter = 100; //Maximum number iteration for the resolution epsilon = 1e-3; //Tolerance for the equation resolution for (j=0;j<=nCell-1;j++){ Re = max(1e-10,fabs(ufn_in[j])*channelWidth_in/viscof_in); ffold=pow(0.32,-2); //Initial guess of the friction factor delta=1e10; //Initialize at a random value greater than epsilon q=0; while ((delta>=epsilon)&&(q // 2017 © Julien Chauchat #pragma once #include class HydroForceEngine: public PartialEngine{ private: void calbeta(vector beta_in); void calviscotlm(vector ufn_in,double viscof_in,vector viscoft_in,vector sig_in,vector dsig_in); // void doubleq(double ddam1[],double ddam2[],double ddam3[], double ddbm[],double ddxm[]); void doubleq(vector ddam1,vector ddam2,vector ddam3, vector ddbm,vector ddxm); void computeTaufsi(double dt_in); void calWallFriction(vector ufn_in,double channelWidth_in,double viscof_in,vector wallFriction_in); public: void averageProfile(); void turbulentFluctuation(); void turbulentFluctuationBIS(); void turbulentFluctuationFluidizedBed(); void fluidResolution(double tfin,double dt); public: virtual void action(); YADE_CLASS_BASE_DOC_ATTRS_CTOR_PY(HydroForceEngine,PartialEngine,"Engine performing a coupling of the DEM with a volume-averaged 1D fluid resolution to simulate steady uniform unidirectional fluid flow. It has been developed and used to model steady uniform gravity-driven turbulent bedload transport [Maurin2015b]_ [Maurin2016]_ [Maurin2017]_, but can be also used in its current state for laminar or pressure-driven configurations. The details of the model can be found in [Maurin2015b]_ and [Maurin2015PhD]_. \n The engine can be decomposed in three different parts:\n (i) It applies the fluid force on the particles imposed by the fluid velocity profiles and fluid properties,\n (ii) It evaluates averaged solid depth profiles necessary for the fluid force application and for the fluid resolution,\n (iii) It solve the volume-averaged 1D fluid momentum balance. \nThe three different functions are detailed below: \n\n (i) Fluid force on particles \n Apply to each particles, buoyancy, drag and lift force due to a 1D fluid flow. The applied drag force reads\n\n $F_{d}=\\frac{1}{2} C_d A\\rho^f|\\vec{v_f - v}| \\vec{v_f - v}$ \n\n where $\\rho$ is the fluid density (:yref:`densFluid`), $v$ is particle's velocity, $v_f$ is the velocity of the fluid at the particle center (taken from the fluid velocity profile :yref:`vxFluid`), $A = \\pi d^2/4$ is particle projected area (disc), $C_d$ is the drag coefficient. The formulation of the drag coefficient depends on the local particle reynolds number and the solid volume fraction. The formulation of the drag is [Dallavalle1948]_ [RevilBaudard2013]_ with a correction of Richardson-Zaki [Richardson1954]_ to take into account the hindrance effect. This law is classical in sediment transport. It is possible to activate a fluctuation of the drag force for each particle which account for the turbulent fluctuation of the fluid velocity (:yref:`velFluct`). Three simple discrete random walk model have been implemented for the turbulent velocity fluctuation. The main one (turbulentFluctuations) takes as input the Reynolds stress tensor $R^f_{xz}$ as a function of the depth, and allows to recover the main property of the fluctuations by imposing $ (z) = (z)/\\rho^f$. It requires as input $(z)$ called :yref:`ReynoldStresses` in the code. \n The formulation of the lift is taken from [Wiberg1985]_ and is such that : \n\n $F_{L}=\\frac{1}{2} C_L A\\rho^f((v_f - v)^2_{top} - (v_f - v)^2_{bottom})$ \n\n Where the subscript top and bottom means evaluated at the top (respectively the bottom) of the sphere considered. This formulation of the lift account for the difference of pressure at the top and the bottom of the particle inside a turbulent shear flow. As this formulation is controversial when approaching the threshold of motion [Schmeeckle2007]_ it is possible to desactivate it with the variable :yref:`lift`.\n The buoyancy is taken into account through the buoyant weight : \n\n $F_{buoyancy}= - \\rho^f V^p g$ \n\n, where g is the gravity vector along the vertical, and $V^p$ is the volume of the particle. In the case where the fluid flow is steady and uniform, the buoyancy reduces to its wall-normal component (see [Maurin2017]_ for a full explanation), and one should put :yref:`steadyFlow` to true in order to kill the streamwise component. \n\n (ii) Averaged solid depth profiles\n The function averageProfile evaluates the volume averaged depth profiles (1D) of particle velocity, particle solid volume fraction and particle drag force. It uses a volume-weighting average following [Maurin2015PhD]_[Maurin2015b]_, i.e. the average of a variable $A^p$ associated to particles at a given discretized wall-normal position $z$ is given by: \n\n $\\left< A \\right>^s(z) = \\displaystyle \\frac{\\displaystyle \\sum_{p|z^p\\in[z-dz/2,z+dz/2]} A^p(t) V^p_z}{\\displaystyle \\sum_{p|z^p\\in[z-dz/2,z+dz/2]} V^p_z}$\n\n Where the sums are over the particles contained inside the slice between the wall-normal position $z-dz/2$ and $z+dz/2$, and $V^p$ represents the part of the volume of the given particle effectively contained inside the slice. For more details, see [Maurin2015PhD]_. \n\n (iii) 1D volume-average fluid resolution\n The fluid resolution is based on the resolution of the 1D volume-averaged fluid momentum balance. It assumes by definition (unidirectional) that the fluid flow is steady and uniform. It is the same fluid resolution as [RevilBaudard2013]_. Details can be found in this paper and in [Maurin2015PhD]_ [Maurin2015b]_.\n\n The three different component can be used independently, e.g. applying a fluid force due to an imposed fluid profile or solving the fluid momentum balance for a given concentration of particles.", //// General parameters ((bool,twoSize,false,,"Option to activate when considering two particle size in the simulation. When activated evaluate the average solid volume fraction and drag force for the two type of particles of diameter diameterPart1 and diameterPart2 independently.")) ((Vector3r,gravity,Vector3r(0,0,-9.81),,"Gravity vector")) ((bool,convAccOption,false,,"To activate the convective acceleration")) //// Mesh parameters ((int,nCell,0,,"Number of cell in the depth")) ((double,zRef,0.,,"Position of the reference point which correspond to the first value of the fluid velocity, i.e. to the ground.")) ((double,deltaZ,,,"Height of the discretization cell.")) ((double,vCell,,,"Volume of averaging cell")) /// Fluid Resolution parameters ((vector,vxFluid,,,"Discretized streamwise fluid velocity depth profile at t")) ((double,densFluid,1000,,"Density of the fluid, by default - density of water")) ((double,viscoDyn,1e-3,,"Dynamic viscosity of the fluid, by default - viscosity of water")) ((double,radiusPart,0.,,"Reference particle radius")) ((double,dpdx,0.,,"pressure gradient along streamwise direction")) ((vector,turbulentViscosity,vector(1000),,"Fluid Resolution: turbulent viscocity as a function of the depth")) ((double,phiMax,0.64,,"Fluid resolution: maximum solid volume fraction. ")) ((int,irheolf,0,,"Fluid resolution: effective fluid viscosity option: 0: pure fluid viscosity, 1: Einstein viscosity. ")) ((int,iturbu,1,,"Fluid resolution: activate the turbulence resolution, 1, or not, 0")) ((int,ilm,2,,"Fluid resolution: type of mixing length resolution applied: 0: classical Prandtl mixing length, 1: Prandtl mixing length with free-surface effects, 2: Damp turbulence accounting for the presence of particles [Li1995]_, see [RevilBaudard2013]_ for more details.")) ((int,iusl,1,,"Fluid resolution: option to set the boundary condition at the top of the fluid, 0: Dirichlet, fixed ($u=0$ en $z=h$), 1: Neumann, free-surface ($du/dz=0$ en $z=h$).")) ((double,kappa,0.41,,"Fluid resolution: Von Karman constant. Can be tuned to account for the effect of particles on the fluid turbulence, see e.g. [RevilBaudard2015]_")) ((int,viscousSubLayer,0,,"Fluid resolution: solve the viscous sublayer close to the bottom boundary if set to 1")) ((bool,fluidWallFriction,false,,"Fluid resolution: if set to true, introduce a sink term to account for the fluid friction at the wall, see [Maurin2015]_ for details. Requires to set the width of the channel. It might slow down significantly the calculation.")) ((double,channelWidth,1.,,"Fluid resolution: Channel width for the evaluation of the fluid wall friction inside the fluid resolution.")) //// Particle averaged depth profiles ((vector,phiPart,,,"Discretized solid volume fraction depth profile. Can be taken as input parameter or evaluated directly inside the engine, calling from python the averageProfile() function")) ((vector,vxPart,,,"Discretized streamwise solid velocity depth profile. Can be taken as input parameter, or evaluated directly inside the engine, calling from python the averageProfile() function")) ((vector,vyPart,,,"Discretized spanwise solid velocity depth profile. No role in the engine, output parameter. For practical reason, it can be evaluated directly inside the engine, calling from python the averageProfile() method of the engine")) ((vector,vzPart,,,"Discretized normal solid velocity depth profile. No role in the engine, output parameter. For practical reason, it can be evaluated directly inside the engine, calling from python the averageProfile() method of the engine")) ((vector,averageDrag,,,"Discretized average drag depth profile. No role in the engine, output parameter. For practical reason, it can be evaluated directly inside the engine, calling from python the averageProfile() method of the engine")) ((double,radiusPart1,0.,,"Radius of the particles of type 1. Useful only when :yref:`twoSize` is set to True.")) ((double,radiusPart2,0.,,"Radius of the particles of type 2. Useful only when :yref:`twoSize` is set to True.")) ((vector,phiPart1,,,"Discretized solid volume fraction depth profile of particles of type 1. Evaluated when :yref:`twoSize` is set to True.")) ((vector,phiPart2,,,"Discretized solid volume fraction depth profile of particles of type 2. Evaluated when :yref:`twoSize` is set to True.")) ((vector,averageDrag1,,,"Discretized average drag depth profile of particles of type 1. Evaluated when :yref:`twoSize` is set to True.")) ((vector,averageDrag2,,,"Discretized average drag depth profile of particles of type 2. Evaluated when :yref:`twoSize` is set to True.")) ((vector,vxPart1,,,"Discretized solid streamwise velocity depth profile of particles of type 1. Evaluated when :yref:`twoSize` is set to True.")) ((vector,vxPart2,,,"Discretized solid streamwise velocity depth profile of particles of type 2. Evaluated when :yref:`twoSize` is set to True.")) ((vector,vyPart1,,,"Discretized solid spanwise velocity depth profile of particles of type 1. Evaluated when :yref:`twoSize` is set to True.")) ((vector,vyPart2,,,"Discretized solid spanwise velocity depth profile of particles of type 2. Evaluated when :yref:`twoSize` is set to True.")) ((vector,vzPart1,,,"Discretized solid wall-normal velocity depth profile of particles of type 1. Evaluated when :yref:`twoSize` is set to True.")) ((vector,vzPart2,,,"Discretized solid wall-normal velocity depth profile of particles of type 2. Evaluated when :yref:`twoSize` is set to True.")) //// DRW fluid velocity fluctuations model parameters ((bool,velFluct,false,,"If true, activate the determination of turbulent fluid velocity fluctuation for the next time step only at the position of each particle, using a simple discrete random walk (DRW) model based on the Reynolds stresses profile (:yref:`ReynoldStresses`)")) ((vector,vFluctX,,,"Vector associating a streamwise fluid velocity fluctuation to each particle. Fluctuation calculated in the C++ code from the discrete random walk model")) ((vector,vFluctY,,,"Vector associating a spanwise fluid velocity fluctuation to each particle. Fluctuation calculated in the C++ code from the discrete random walk model")) ((vector,vFluctZ,,,"Vector associating a normal fluid velocity fluctuation to each particle. Fluctuation calculated in the C++ code from the discrete random walk model")) ((vector,ReynoldStresses,vector(1000),,"Vector of size equal to :yref:`nCell` containing the Reynolds stresses as a function of the depth. ReynoldStresses(z) $= \\rho^f (z)^2$")) ((double,bedElevation,0.,,"Elevation of the bed above which the fluid flow is turbulent and the particles undergo turbulent velocity fluctuation.")) ((vector,fluctTime,,,"Vector containing the time of life of the fluctuations associated to each particles.")) ((double,dtFluct,,,"Execution time step of the turbulent fluctuation model.")) //// Fluid-particle interactions ((double,expoRZ,3.1,,"Value of the Richardson-Zaki exponent, for the drag correction due to hindrance")) ((bool,lift,false,,"Option to activate or not the evaluation of the lift")) ((double,Cl,0.2,,"Value of the lift coefficient taken from [Wiberg1985]_")) ((vector,convAcc,0,,"Convective acceleration, depth dependent")) ((vector,taufsi,vector(1000),,"Fluid Resolution: Create Taufsi/rhof = dragTerm/(rhof(vf-vxp)) to transmit to the fluid code")) ((vector,sig_cpp,vector(1000),,"test")) ((vector,dsig_cpp,vector(1000),,"test")) ((bool,steadyFlow,true,,"Condition to modify the buoyancy force according to the physical difference between a fluid at rest and a steady fluid flow. For more details see [Maurin2017]_")) ,/*ctor*/ ,/*py*/ .def("averageProfile",&HydroForceEngine::averageProfile,"Compute and store the particle velocity (:yref:`vxPart`, :yref:`vyPart`, :yref:`vzPart`) and solid volume fraction (:yref:`phiPart`) depth profile. For each defined cell z, the k component of the average particle velocity reads: \n\n $^z= \\sum_p V^p v_k^p/\\sum_p V^p$,\n\n where the sum is made over the particles contained in the cell, $v_k^p$ is the k component of the velocity associated to particle p, and $V^p$ is the part of the volume of the particle p contained inside the cell. This definition allows to smooth the averaging, and is equivalent to taking into account the center of the particles only when there is a lot of particles in each cell. As for the solid volume fraction, it is evaluated in the same way: for each defined cell z, it reads: \n\n $<\\phi>^z= \\frac{1}{V_{cell}}\\sum_p V^p$, where $V_{cell}$ is the volume of the cell considered, and $V^p$ is the volume of particle p contained in cell z.\n This function gives depth profiles of average velocity and solid volume fraction, returning the average quantities in each cell of height dz, from the reference horizontal plane at elevation :yref:`zRef` (input parameter) until the plane of elevation :yref:`zRef` plus :yref:`nCell` times :yref:`deltaZ` (input parameters). When the option :yref:`twoSize` is set to True, evaluate in addition the average drag (:yref:`averageDrag1` and :yref:`averageDrag2`) and solid volume fraction (:yref:`phiPart1` and :yref:`phiPart2`) depth profiles considering only the particles of radius respectively :yref:`radiusPart1` and :yref:`radiusPart2` in the averaging.") .def("fluidResolution",&HydroForceEngine::fluidResolution,"Solve the 1D volume-averaged fluid momentum balance on the defined mesh (:yref:`nCell`, :yref:`deltaZ`) from the volume-averaged solid profiles (:yref:`phiPart`,:yref:`vxPart`,:yref:`averageDrag`), which can be evaluated with the averageProfile function.") .def("turbulentFluctuation",&HydroForceEngine::turbulentFluctuation,"Apply a discrete random walk model to the evaluation of the drag force to account for the fluid velocity turbulent fluctuations. Very simple model applying fluctuations from the values of the Reynolds stresses in order to recover the property $ (z) = (z)/\\rho^f$. The random fluctuations are modified over a time scale given by the eddy turn over time.") .def("turbulentFluctuationBIS",&HydroForceEngine::turbulentFluctuationBIS,"Apply turbulent fluctuation to the problem similarly to turbulentFluctuation but with an update of the fluctuation depending on the particle position.") .def("turbulentFluctuationFluidizedBed",&HydroForceEngine::turbulentFluctuationFluidizedBed,"Same as turbulentFluctuations but adapted to the example case of the fluidized bed.") ); }; REGISTER_SERIALIZABLE(HydroForceEngine); trunk-2018.02b/pkg/common/InsertionSortCollider.cpp000066400000000000000000000627071324306050200222570ustar00rootroot00000000000000// 2009 © Václav Šmilauer // 2013 © Bruno Chareyre #include"InsertionSortCollider.hpp" #include #include #include #include #include #include #include #ifdef YADE_OPENMP #include #endif YADE_PLUGIN((InsertionSortCollider)) CREATE_LOGGER(InsertionSortCollider); // called by the insertion sort if 2 bodies swapped their bounds in such a way that a new overlap may appear void InsertionSortCollider::handleBoundInversion(Body::id_t id1, Body::id_t id2, InteractionContainer* interactions, Scene*){ assert(!periodic); assert(id1!=id2); if (spatialOverlap(id1,id2) && Collider::mayCollide(Body::byId(id1,scene).get(),Body::byId(id2,scene).get()) && !interactions->found(id1,id2)) interactions->insert(shared_ptr(new Interaction(id1,id2))); } void InsertionSortCollider::insertionSort(VecBounds& v, InteractionContainer* interactions, Scene*, bool doCollide){ assert(!periodic); assert(v.size==(long)v.vec.size()); for(long i=1; i=0 && v[j]>viInit){ v[j+1]=v[j]; // no collisions without bounding boxes // also, do not collide body with itself; it sometimes happens for facets aligned perpendicular to an axis, for reasons that are not very clear // see https://bugs.launchpad.net/yade/+bug/669095 // skip bounds with same isMin flags, since inversion doesn't imply anything in that case if(isMin && !v[j].flags.isMin && doCollide && viInitBB && v[j].flags.hasBB && (viInit.id!=v[j].id)) { /*if (isMin)*/ handleBoundInversion(viInit.id,v[j].id,interactions,scene); // else handleBoundSplit(viInit.id,v[j].id,interactions,scene); } j--; } v[j+1]=viInit; } } //Periodic version, only for non-periodic case at the moment (feel free to implement for the periodic case...) void InsertionSortCollider::insertionSortParallel(VecBounds& v, InteractionContainer* interactions, Scene*, bool doCollide){ #ifdef YADE_OPENMP assert(!periodic); assert(v.size==(long)v.vec.size()); if (ompThreads<=1) return insertionSort(v,interactions, scene, doCollide); Real chunksVerlet = 4*verletDist;//is 2* the theoretical requirement? if (chunksVerlet<=0) {LOG_ERROR("Parallel insertion sort needs verletDist>0");} ///chunks defines subsets of the bounds lists, we make sure they are not too small wrt. verlet dist. std::vector chunks; unsigned nChunks = ompThreads; unsigned chunkSize = unsigned(v.size/nChunks)+1; for(unsigned n=0; n1){ bool changeChunks=false; for(unsigned n=1; n(v[chunks[n]].coord-v[chunks[n-1]].coord)) changeChunks=true; if (!changeChunks) break; nChunks--; chunkSize = unsigned(v.size/nChunks)+1; chunks.clear(); for(unsigned n=0; n > > newInteractions; newInteractions.resize(ompThreads,std::vector >()); for (int kk=0; kk0 ? min(ompThreads,omp_get_max_threads()) : omp_get_max_threads()) for (unsigned k=0; k=chunks[k] && v[j]>viInit){ v[j+1]=v[j]; if(isMin && !v[j].flags.isMin && doCollide && viInitBB && v[j].flags.hasBB && (viInit.id!=v[j].id)) { const Body::id_t& id1 = v[j].id; const Body::id_t& id2 = viInit.id; if (spatialOverlap(id1,id2) && Collider::mayCollide(Body::byId(id1,scene).get(),Body::byId(id2,scene).get()) && !interactions->found(id1,id2)) newInteractions[threadNum].push_back(std::pair(v[j].id,viInit.id)); } j--; } v[j+1]=viInit; } } ///In the second sort, the chunks are connected consistently. ///If sorting requires to move a bound past half-chunk, the algorithm is not thread safe, /// if it happens we run the 1-thread sort at the end bool parallelFailed=false; #pragma omp parallel for schedule(dynamic,1) num_threads(ompThreads>0 ? min(ompThreads,omp_get_max_threads()) : omp_get_max_threads()) for (unsigned k=1; k=halfChunkStart && viInitfound(id1,id2)) newInteractions[threadNum].push_back(std::pair(v[j].id,viInit.id));} j--; } v[j+1]=viInit; if (j=halfChunkEnd) parallelFailed=true; } /// Now insert interactions sequentially for (int n=0;nfound(newInteractions[n][k].first,newInteractions[n][k].second))*/ //Not needed, already checked above interactions->insert(shared_ptr(new Interaction(newInteractions[n][k].first,newInteractions[n][k].second))); /// If some bounds traversed more than a half-chunk, we complete colliding with the sequential sort if (parallelFailed) return insertionSort(v,interactions, scene, doCollide); #endif } vector InsertionSortCollider::probeBoundingVolume(const Bound& bv){ if(periodic){ throw invalid_argument("InsertionSortCollider::probeBoundingVolume: handling periodic boundary not implemented."); } vector ret; for( vector::iterator it=BB[0].vec.begin(),et=BB[0].vec.end(); it < et; ++it) { if (it->coord > bv.max[0]) break; if (!it->flags.isMin || !it->flags.hasBB) continue; int offset = 3*it->id; const shared_ptr& b=Body::byId(it->id,scene); if(!b || !b->bound) continue; const Real& sweepLength = b->bound->sweepLength; Vector3r disp = b->state->pos - b->bound->refPos; if (!(maxima[offset]-sweepLength+disp[0] < bv.min[0] || minima[offset]+sweepLength+disp[0] > bv.max[0] || minima[offset+1]+sweepLength+disp[1] > bv.max[1] || maxima[offset+1]-sweepLength+disp[1] < bv.min[1] || minima[offset+2]+sweepLength+disp[2] > bv.max[2] || maxima[offset+2]-sweepLength+disp[2] < bv.min[2] )) { ret.push_back(it->id); } } return ret; } // STRIDE bool InsertionSortCollider::isActivated(){ // activated if number of bodies changes (hence need to refresh collision information) // or the time of scheduled run already came, or we were never scheduled yet if(!strideActive) return true; if(!newton) return true; if(fastestBodyMaxDist<0){fastestBodyMaxDist=0; return true;} fastestBodyMaxDist=newton->maxVelocitySq; if(fastestBodyMaxDist>=1 || fastestBodyMaxDist==0) return true; if((size_t)BB[0].size!=2*scene->bodies->size()) return true; if(scene->interactions->dirty) return true; if(scene->doSort) { scene->doSort=false; return true; } return false; } void InsertionSortCollider::action(){ #ifdef ISC_TIMING timingDeltas->start(); #endif long nBodies=(long)scene->bodies->size(); InteractionContainer* interactions=scene->interactions.get(); scene->interactions->iterColliderLastRun=-1; #ifdef YADE_OPENMP if (ompThreads<=0) ompThreads = omp_get_max_threads(); #endif // periodicity changed, force reinit if(scene->isPeriodic != periodic){ for(int i=0; i<3; i++) BB[i].vec.clear(); periodic=scene->isPeriodic; } // pre-conditions // adjust storage size bool doInitSort=false; if (doSort) { doInitSort=true; doSort=false; } if(BB[0].size!=2*nBodies){ long BBsize=BB[0].size; LOG_DEBUG("Resize bounds containers from "<200 || BBsize==0) doInitSort=true; assert((BBsize%2)==0); for(int i=0; i<3; i++){ BB[i].vec.reserve(2*nBodies); // add lower and upper bounds; coord is not important, will be updated from bb shortly for(long id=BBsize/2; idbodies->size()); //Increase the size of force container. scene->forces.addMaxId(2*scene->bodies->size()); // update periodicity assert(BB[0].axis==0); assert(BB[1].axis==1); assert(BB[2].axis==2); if(periodic) { for(int i=0; i<3; i++) BB[i].updatePeriodicity(scene); invSizes=Vector3r(1./scene->cell->getSize()[0],1./scene->cell->getSize()[1],1./scene->cell->getSize()[2]);} if(verletDist<0){ Real minR=std::numeric_limits::infinity(); FOREACH(const shared_ptr& b, *scene->bodies){ if(!b || !b->shape) continue; Sphere* s=dynamic_cast(b->shape.get()); if(!s) continue; minR=min(s->radius,minR); } if (std::isinf(minR)) LOG_WARN("verletDist is set to 0 because no spheres were found. It will result in suboptimal performances, consider setting a positive verletDist in your script."); // if no spheres, disable stride verletDist=std::isinf(minR) ? 0 : std::abs(verletDist)*minR; } // if interactions are dirty, force reinitialization if(scene->interactions->dirty){ doInitSort=true; scene->interactions->dirty=false; } // update bounds via boundDispatcher boundDispatcher->scene=scene; boundDispatcher->sweepDist=verletDist; boundDispatcher->minSweepDistFactor=minSweepDistFactor; boundDispatcher->targetInterv=targetInterv; boundDispatcher->updatingDispFactor=updatingDispFactor; boundDispatcher->action(); ISC_CHECKPOINT("boundDispatcher"); // STRIDE if(verletDist>0){ // get NewtonIntegrator, to ask for the maximum velocity value if(!newton){ FOREACH(shared_ptr& e, scene->engines){ newton=YADE_PTR_DYN_CAST(e); if(newton) break; } if(!newton){ throw runtime_error("InsertionSortCollider.verletDist>0, but unable to locate NewtonIntegrator within O.engines."); } } } // STRIDE // get us ready for strides, if they were deactivated if(!strideActive && verletDist>0 && newton->maxVelocitySq>=0) strideActive=true; if(strideActive){ assert(verletDist>0); assert(strideActive); assert(newton->maxVelocitySq>=0); newton->updatingDispFactor=updatingDispFactor; } else boundDispatcher->sweepDist=0; ISC_CHECKPOINT("bound"); // copy bounds along given axis into our arrays #pragma omp parallel for schedule(guided) num_threads(ompThreads>0 ? min(ompThreads,omp_get_max_threads()) : omp_get_max_threads()) for(long i=0; i<2*nBodies; i++){ // const long cacheIter = scene->iter; for(int j=0; j<3; j++){ VecBounds& BBj=BB[j]; Bounds& BBji = BBj[i]; const Body::id_t id=BBji.id; const shared_ptr& b=Body::byId(id,scene); if(b){ const shared_ptr& bv=b->bound; // coordinate is min/max if has bounding volume, otherwise both are the position. Add periodic shift so that we are inside the cell // watch out for the parentheses around ?: within ?: (there was unwanted conversion of the Reals to bools!) BBji.coord=((BBji.flags.hasBB=((bool)bv)) ? (BBji.flags.isMin ? bv->min[j] : bv->max[j]) : (b->state->pos[j])) - (periodic ? BBj.cellDim*BBji.period : 0.); // if initializing periodic, shift coords & record the period into BBj[i].period if(doInitSort && periodic) BBji.coord=cellWrap(BBji.coord,0,BBj.cellDim,BBji.period); // for each body, copy its minima and maxima, for quick checks of overlaps later //bounds have been all updated when j==0, we can safely copy them here when j==1 if (BBji.flags.isMin && j==1 &&bv) { memcpy(&minima[3*id],&bv->min,3*sizeof(Real)); memcpy(&maxima[3*id],&bv->max,3*sizeof(Real)); } } else { BBj[i].flags.hasBB=false; /* for vanished body, keep the coordinate as-is, to minimize inversions. */ } } } ISC_CHECKPOINT("copy"); // remove interactions which have disconnected bounds and are not real (will run parallel if YADE_OPENMP) interactions->conditionalyEraseNonReal(*this,scene); ISC_CHECKPOINT("erase"); // sort // the regular case if(!doInitSort && !sortThenCollide){ /* each inversion in insertionSort calls may add interaction */ //1000 bodies is heuristic minimum above which parallel sort is called if(!periodic) for(int i=0; i<3; i++) { #ifdef YADE_OPENMP if (ompThreads<=1 || nBodies<1000) insertionSort(BB[i],interactions,scene); else insertionSortParallel(BB[i],interactions,scene);} #else insertionSort(BB[i],interactions,scene);} #endif else for(int i=0; i<3; i++) insertionSortPeri(BB[i],interactions,scene); } // create initial interactions (much slower) else { if(doInitSort){ // the initial sort is in independent in 3 dimensions, may be run in parallel; it seems that there is no time gain running in parallel, though // important to reset loInx for periodic simulation (!!) // #pragma omp parallel for schedule(dynamic,1) num_threads(min(ompThreads,3)) for(int i=0; i<3; i++) { BB[i].loIdx=0; std::sort(BB[i].vec.begin(),BB[i].vec.end()); } numReinit++; } else { // sortThenCollide if(!periodic) for(int i=0; i<3; i++) insertionSort(BB[i],interactions,scene,false); else for(int i=0; i<3; i++) insertionSortPeri(BB[i],interactions,scene,false); } // traverse the container along requested axis assert(sortAxis==0 || sortAxis==1 || sortAxis==2); VecBounds& V=BB[sortAxis]; // go through potential aabb collisions, create interactions as necessary if(!periodic){ #ifdef YADE_OPENMP std::vector > > newInts; newInts.resize(ompThreads,std::vector >()); for (int kk=0; kk(iid,jid)); #else if (!interactions->found(iid,jid)) interactions->insert(shared_ptr(new Interaction(iid,jid))); #endif } } } //go through newly created candidates sequentially, duplicates coming from different threads may exist so we check existence with found() #ifdef YADE_OPENMP for (int n=0;nfound(newInts[n][k].first,newInts[n][k].second)) interactions->insert(shared_ptr(new Interaction(newInts[n][k].first,newInts[n][k].second))); #endif } else { // periodic case: see comments above for(long i=0; i<2*nBodies; i++){ if(!(V[i].flags.isMin && V[i].flags.hasBB)) continue; const Body::id_t& iid=V[i].id; // we might wrap over the periodic boundary here; that's why the condition is different from the aperiodic case for(long j=V.norm(i+1); V[j].id!=iid; j=V.norm(j+1)){ const Body::id_t& jid=V[j].id; if(!(V[j].flags.isMin && V[j].flags.hasBB)) continue; handleBoundInversionPeri(iid,jid,interactions,scene); } } } } ISC_CHECKPOINT("sort&collide"); } // return floating value wrapped between x0 and x1 and saving period number to period Real InsertionSortCollider::cellWrap(const Real x, const Real x0, const Real x1, int& period){ Real xNorm=(x-x0)/(x1-x0); period=(int)floor(xNorm); // some people say this is very slow; probably optimized by gcc, however (google suggests) return x0+(xNorm-period)*(x1-x0); } // return coordinate wrapped to 0…x1, relative to x0; don't care about period Real InsertionSortCollider::cellWrapRel(const Real x, const Real x0, const Real x1){ Real xNorm=(x-x0)/(x1-x0); return (xNorm-floor(xNorm))*(x1-x0); } //NOTE: possible improvements: // 1) (not only periodic) keep a mask defining overlaps in directions 1,2,3, and compare the sum instead of checking overlap in three directions everytime there is an inversion. (maybe not possible? does it need a N² table?!!) // 2) use norm() only when needed (first and last elements, mainly, can be treated as special cases) void InsertionSortCollider::insertionSortPeri(VecBounds& v, InteractionContainer* interactions, Scene*, bool doCollide){ assert(periodic); long &loIdx=v.loIdx; const long &size=v.size; /* We have to visit each bound at least once (first condition), but this is not enough. The correct ordering in the begining of the list needs a second pass to connect begin and end consistently (the second condition). Strictly the second condition should include "+ (v.norm(j+1)==loIdx ? v.cellDim : 0)" but it is ok as is since the shift is added inside the loop. */ for(long _i=0; (_i(vi.coord + /* wrap for elt just below split */ (v.norm(j+1)==loIdx ? v.cellDim : 0))){ int j1=v.norm(j+1); // OK, now if many bodies move at the same pace through the cell and at one point, there is inversion, // this can happen without any side-effects if (false && v[j].coord>2*v.cellDim){ // this condition is not strictly necessary, but the loop of insertionSort would have to run more times. // Since size of particle is required to be < .5*cellDim, this would mean simulation explosion anyway LOG_FATAL("Body #"< v.cellDim; i= v.norm(--i)) {v[i].period+=1; v[i].coord-=v.cellDim; loIdx=i;} } // called by the insertion sort if 2 bodies swapped their bounds void InsertionSortCollider::handleBoundInversionPeri(Body::id_t id1, Body::id_t id2, InteractionContainer* interactions, Scene*){ assert(periodic); if (interactions->found(id1,id2)) return;// we want to _create_ new ones, we don't care about existing ones Vector3i periods(Vector3i::Zero()); bool overlap=spatialOverlapPeri(id1,id2,scene,periods); if (overlap && Collider::mayCollide(Body::byId(id1,scene).get(),Body::byId(id2,scene).get())){ shared_ptr newI=shared_ptr(new Interaction(id1,id2)); newI->cellDist=periods; interactions->insert(newI); } } /* Performance hint ================ Since this function is called (most the time) from insertionSort, we actually already know what is the overlap status in that one dimension, including periodicity information; both values could be passed down as a parameters, avoiding 1 of 3 loops here. We do some floats math here, so the speedup could noticeable; doesn't concertn the non-periodic variant, where it is only plain comparisons taking place. */ //! return true if bodies bb overlap in all 3 dimensions bool InsertionSortCollider::spatialOverlapPeri(Body::id_t id1, Body::id_t id2,Scene* scene, Vector3i& periods) const { assert(periodic); assert(id1!=id2); // programming error, or weird bodies (too large?) for(int axis=0; axis<3; axis++){ Real dim=scene->cell->getSize()[axis]; // LOG_DEBUG("dim["<max, and with +1 shift for id1->min so that body1's bounds cover an interval [shiftedMin; 1] at the end of a b1-centric period Real lmin = (minima[3*id2+axis]-maxima[3*id1+axis])*invSizes[axis]; Real lmax = (maxima[3*id2+axis]-maxima[3*id1+axis])*invSizes[axis]; Real shiftedMin = (minima[3*id1+axis]-maxima[3*id1+axis])*invSizes[axis]+1.; if((lmax-lmin)>0.5 || shiftedMin<0){ if (allowBiggerThanPeriod) {periods[axis]=0; continue;} else { LOG_FATAL("Body #"<<((lmax-lmin)>0.5?id2:id1)<<" spans over half of the cell size "<= shiftedMin) {periods[axis]=-period1-1; continue;} // none of the above, exit return false; } return true; } boost::python::tuple InsertionSortCollider::dumpBounds(){ boost::python::list bl[3]; // 3 bound lists, inserted into the tuple at the end for(int axis=0; axis<3; axis++){ VecBounds& V=BB[axis]; if(periodic){ for(long i=0; iisPeriodic); assert(axis>=0 && axis <=2); cellDim=scene->cell->getSize()[axis]; } trunk-2018.02b/pkg/common/InsertionSortCollider.hpp000066400000000000000000000345011324306050200222530ustar00rootroot00000000000000// 2009 © Václav Šmilauer // 2013 © Bruno Chareyre #pragma once #include #include class InteractionContainer; /*! Periodic collider notes. Architecture ============ Values from bounding boxes are added information about period in which they are. Their container (VecBounds) holds position of where the space wraps. The sorting algorithm is changed in such way that periods are changed when body crosses cell boundary. Interaction::cellDist holds information about relative cell coordinates of the 2nd body relative to the 1st one. Dispatchers (IGeomDispatcher and InteractionLoop) use this information to pass modified position of the 2nd body to IGeomFunctors. Since properly behaving IGeomFunctor's and LawFunctor's do not take positions directly from Body::state, the interaction is computed with the periodic positions. Positions of bodies (in the sense of Body::state) and their natural bboxes are not wrapped to the periodic cell, they can be anywhere (but not "too far" in the sense of int overflow). Since Interaction::cellDists holds cell coordinates, it is possible to change the cell boundaries at runtime. This should make it easy to implement some stress control on the top. Clumps do not interfere with periodicity in any way. Rendering --------- OpenGLRenderer renders Shape at all periodic positions that touch the periodic cell (i.e. Bounds crosses its boundary). It seems to affect body selection somehow, but that is perhaps not related at all. Periodicity control =================== c++: Scene::isPeriodic, Scene::cellSize python: O.periodicCell=((0,0,0),(10,10,10) # activates periodic boundary O.periodicCell=() # deactivates periodic boundary Requirements ============ * By default, no body can have Aabb larger than about .499*cellSize. Exception is thrown if that is false. Large bodies are accepted if allowBiggerThanPeriod (experimental) * Constitutive law must not get body positions from Body::state directly. If it does, it uses Interaction::cellDist to compute periodic position. * No body can get further away than MAXINT periods. It will do horrible things if there is overflow. Not checked at the moment. * Body cannot move over distance > cellSize in one step. Since body size is limited as well, that would mean simulation explosion. Exception is thrown if the movement is upwards. If downwards, it is not handled at all. Possible performance improvements & bugs ======================================== * PeriodicInsertionSortCollider::{cellWrap,cellWrapRel} OpenGLRenderer::{wrapCell,wrapCellPt} Shop::PeriodicWrap are all very similar functions. They should be put into separate header and included from all those places. */ // #define this macro to enable timing within this engine // #define ISC_TIMING #ifdef ISC_TIMING #define ISC_CHECKPOINT(cpt) timingDeltas->checkpoint(cpt) #else #define ISC_CHECKPOINT(cpt) #endif class NewtonIntegrator; class Integrator; class GeneralIntegratorInsertionSortCollider;// Forward decleration of child to decleare it as friend class InsertionSortCollider: public Collider{ friend class GeneralIntegratorInsertionSortCollider; //! struct for storing bounds of bodies struct Bounds{ //! coordinate along the given sortAxis Real coord; //! id of the body this bound belongs to Body::id_t id; //! periodic cell coordinate int period; //! is it the minimum (true) or maximum (false) bound? struct {bool hasBB:true, isMin:true;} flags; Bounds(Real coord_, Body::id_t id_, bool isMin): coord(coord_), id(id_), period(0){ flags.isMin=isMin; } bool operator<(const Bounds& b) const { /* handle special case of zero-width bodies, which could otherwise get min/max swapped in the unstable std::sort */ if(id==b.id && coord==b.coord) return flags.isMin; return coord(const Bounds& b) const { if(id==b.id && coord==b.coord) return !flags.isMin; return coord>b.coord; } }; // we need this to find out about current maxVelocitySq shared_ptr newton; // if False, no type of striding is used // if True, then either verletDist XOR nBins is set bool strideActive; struct VecBounds{ // axis set in the ctor int axis; std::vector vec; Real cellDim; // cache vector size(), update at every step in action() long size; // index of the lowest coordinate element, before which the container wraps long loIdx; Bounds& operator[](long idx){ assert(idx=0); return vec[idx]; } const Bounds& operator[](long idx) const { assert(idx=0); return vec[idx]; } // update number of bodies, periodic properties and size from Scene void updatePeriodicity(Scene* ); // normalize given index to the right range (wraps around) long norm(long i) const { if(i<0) i+=size; long ret=i%size; assert(ret>=0 && ret maxima, minima; //! Whether the Scene was periodic (to detect the change, which shouldn't happen, but shouldn't crash us either) bool periodic; //! Store inverse sizes to avoid repeated divisions within loops Vector3r invSizes; // return python representation of the BB struct, as ([...],[...],[...]). boost::python::tuple dumpBounds(); /*! sorting routine; insertion sort is very fast for strongly pre-sorted lists, which is our case http://en.wikipedia.org/wiki/Insertion_sort has the algorithm and other details */ void insertionSort(VecBounds& v,InteractionContainer*,Scene*,bool doCollide=true); void insertionSortParallel(VecBounds& v,InteractionContainer*,Scene*,bool doCollide=true); void handleBoundInversion(Body::id_t,Body::id_t,InteractionContainer*,Scene*); // periodic variants void insertionSortPeri(VecBounds& v,InteractionContainer*,Scene*,bool doCollide=true); void handleBoundInversionPeri(Body::id_t,Body::id_t,InteractionContainer*,Scene*); void handleBoundSplit(Body::id_t,Body::id_t,InteractionContainer*,Scene*); bool spatialOverlapPeri(Body::id_t,Body::id_t,Scene*,Vector3i&) const; inline bool spatialOverlap(const Body::id_t& id1, const Body::id_t& id2) const { assert(!periodic); return (minima[3*id1+0]<=maxima[3*id2+0]) && (maxima[3*id1+0]>=minima[3*id2+0]) && (minima[3*id1+1]<=maxima[3*id2+1]) && (maxima[3*id1+1]>=minima[3*id2+1]) && (minima[3*id1+2]<=maxima[3*id2+2]) && (maxima[3*id1+2]>=minima[3*id2+2]); } static Real cellWrap(const Real, const Real, const Real, int&); static Real cellWrapRel(const Real, const Real, const Real); public: //! Predicate called from loop within InteractionContainer::erasePending bool shouldBeErased(Body::id_t id1, Body::id_t id2, Scene* rb) const { if(!periodic) return !spatialOverlap(id1,id2); else { Vector3i periods; return !spatialOverlapPeri(id1,id2,rb,periods); } } virtual bool isActivated(); // force reinitialization at next run virtual void invalidatePersistentData(){ for(int i=0; i<3; i++){ BB[i].vec.clear(); BB[i].size=0; }} vector probeBoundingVolume(const Bound&); virtual void action(); YADE_CLASS_BASE_DOC_ATTRS_CTOR_PY(InsertionSortCollider,Collider,"\ Collider with O(n log(n)) complexity, using :yref:`Aabb` for bounds.\ \n\n\ At the initial step, Bodies' bounds (along :yref:`sortAxis`) are first std::sort'ed along this (sortAxis) axis, then collided. The initial sort has :math:`O(n^2)` complexity, see `Colliders' performance `_ for some information (There are scripts in examples/collider-perf for measurements). \ \n\n \ Insertion sort is used for sorting the bound list that is already pre-sorted from last iteration, where each inversion calls checkOverlap which then handles either overlap (by creating interaction if necessary) or its absence (by deleting interaction if it is only potential). \ \n\n \ Bodies without bounding volume (such as clumps) are handled gracefully and never collide. Deleted bodies are handled gracefully as well.\ \n\n \ This collider handles periodic boundary conditions. There are some limitations, notably:\ \n\n \ #. No body can have Aabb larger than cell's half size in that respective dimension. You get exception if it does and gets in interaction. One way to explicitly by-pass this restriction is offered by ``allowBiggerThanPeriod``, which can be turned on to insert a floor in the form of a very large box for instance (see examples/periodicSandPile.py). \ \n\n \ #. No body can travel more than cell's distance in one step; this would mean that the simulation is numerically exploding, and it is only detected in some cases.\ \n\n \ **Stride** can be used to avoid running collider at every step by enlarging the particle's bounds, tracking their displacements and only re-run if they might have gone out of that bounds (see `Verlet list `_ for brief description and background) . This requires cooperation from :yref:`NewtonIntegrator` as well as :yref:`BoundDispatcher`, which will be found among engines automatically (exception is thrown if they are not found).\ \n\n \ If you wish to use strides, set ``verletDist`` (length by which bounds will be enlarged in all directions) to some value, e.g. 0.05 × typical particle radius. This parameter expresses the tradeoff between many potential interactions (running collider rarely, but with longer exact interaction resolution phase) and few potential interactions (running collider more frequently, but with less exact resolutions of interactions); it depends mainly on packing density and particle radius distribution.\ \n\n \ If ``targetInterv`` is >1, not all particles will have their bound enlarged by ``verletDist``; instead, they will have bounds increased by a length in order to trigger a new colliding after ``targetInterv`` iteration, assuming they move at almost constant velocity. Ideally in this method, all particles would reach their bounds at the sime iteration. This is of course not the case as soon as velocities fluctuate in time. :yref:`Bound::sweepLength` is tuned on the basis of the displacement recorded between the last two runs of the collider. In this situation, ``verletDist`` defines the maximum sweep length.\ ", ((int,sortAxis,0,,"Axis for the initial contact detection.")) ((bool,allowBiggerThanPeriod,false,,"If true, tests on bodies sizes will be disabled, and the simulation will run normaly even if bodies larger than period are found. It can be useful when the periodic problem include e.g. a floor modelized with wall/box/facet.\nBe sure you know what you are doing if you touch this flag. The result is undefined if one large body moves out of the (0,0,0) period.")) ((bool,sortThenCollide,false,,"Separate sorting and colliding phase; it is MUCH slower, but all interactions are processed at every step; this effectively makes the collider non-persistent, not remembering last state. (The default behavior relies on the fact that inversions during insertion sort are overlaps of bounding boxes that just started/ceased to exist, and only processes those; this makes the collider much more efficient.)")) ((int,targetInterv,50,,"(experimental) Target number of iterations between bound update, used to define a smaller sweep distance for slower grains if >0, else always use 1*verletDist. Useful in simulations with strong velocity contrasts between slow bodies and fast bodies.")) ((Real,overlapTolerance,1e-7,,"Tolerance on determining overlap. In rare cases different parts of the code can inconsistently lead to different results in terms of overlap, with false negative by spatialOverlapPeri possibly leading to nasty bugs in contact detection (false positive are harmless). This tolerance is to avoid false negative, the value can be understood as relative to 1 (i.e. independent of particle size or any other reference length). The default should be ok.")) ((Real,updatingDispFactor,-1,,"(experimental) Displacement factor used to trigger bound update: the bound is updated only if updatingDispFactor*disp>sweepDist when >0, else all bounds are updated.")) ((Real,verletDist,((void)"Automatically initialized",-.5),,"Length by which to enlarge particle bounds, to avoid running collider at every step. Stride disabled if zero. Negative value will trigger automatic computation, so that the real value will be *verletDist* × minimum spherical particle radius; if there are no spherical particles, it will be disabled. The actual length added to one bound can be only a fraction of verletDist when :yref:`InsertionSortCollider::targetInterv` is > 0.")) ((Real,minSweepDistFactor,0.1,,"Minimal distance by which enlarge all bounding boxes; superseeds computed value of verletDist when lower that (minSweepDistFactor x verletDist).")) ((Real,fastestBodyMaxDist,-1,,"Normalized maximum displacement of the fastest body since last run; if >= 1, we could get out of bboxes and will trigger full run. |yupdate|")) ((int,numReinit,0,Attr::readonly,"Cummulative number of bound array re-initialization.")) ((Real,useless,,,"for compatibility of scripts defining the old collider's attributes - see deprecated attributes")) ((bool,doSort,false,,"Do forced resorting of interactions.")) , /* ctor */ #ifdef ISC_TIMING timingDeltas=shared_ptr(new TimingDeltas); #endif for(int i=0; i<3; i++) BB[i].axis=i; periodic=false; strideActive=false; , /* py */ .def_readonly("strideActive",&InsertionSortCollider::strideActive,"Whether striding is active (read-only; for debugging). |yupdate|") .def_readonly("periodic",&InsertionSortCollider::periodic,"Whether the collider is in periodic mode (read-only; for debugging) |yupdate|") .def("dumpBounds",&InsertionSortCollider::dumpBounds,"Return representation of the internal sort data. The format is ``([...],[...],[...])`` for 3 axes, where each ``...`` is a list of entries (bounds). The entry is a tuple with the fllowing items:\n\n* coordinate (float)\n* body id (int), but negated for negative bounds\n* period numer (int), if the collider is in the periodic regime.") ); DECLARE_LOGGER; }; REGISTER_SERIALIZABLE(InsertionSortCollider); trunk-2018.02b/pkg/common/InteractionLoop.cpp000066400000000000000000000152031324306050200210550ustar00rootroot00000000000000#include"InteractionLoop.hpp" YADE_PLUGIN((InteractionLoop)); CREATE_LOGGER(InteractionLoop); void InteractionLoop::pyHandleCustomCtorArgs(boost::python::tuple& t, boost::python::dict& d){ if(boost::python::len(t)==0) return; // nothing to do if(boost::python::len(t)!=3) throw invalid_argument("Exactly 3 lists of functors must be given"); // parse custom arguments (3 lists) and do in-place modification of args using vecGeom = std::vector >; using vecPhys = std::vector >; using vecLaw = std::vector >; vecGeom vg=boost::python::extract(t[0])(); vecPhys vp=boost::python::extract(t[1])(); vecLaw vl=boost::python::extract(t[2])(); for(const auto gf : vg) this->geomDispatcher->add(gf); for(const auto pf : vp) this->physDispatcher->add(pf); for(const auto cf : vl) this->lawDispatcher->add(cf); t=boost::python::tuple(); // empty the args; not sure if this is OK, as there is some refcounting in raw_constructor code } void InteractionLoop::action(){ // update Scene* of the dispatchers lawDispatcher->scene=scene; physDispatcher->scene=scene; geomDispatcher->scene=scene; // ask dispatchers to update Scene* of their functors geomDispatcher->updateScenePtr(); physDispatcher->updateScenePtr(); lawDispatcher->updateScenePtr(); /* initialize callbacks; they return pointer (used only in this timestep) to the function to be called returning NULL deactivates the callback in this timestep */ // pair of callback object and pointer to the function to be called vector callbackPtrs; for (const auto cb : callbacks){ cb->scene=scene; callbackPtrs.push_back(cb->stepInit()); } assert(callbackPtrs.size()==callbacks.size()); const size_t callbacksSize=callbacks.size(); // cache transformed cell size Matrix3r cellHsize = Matrix3r::Zero(); if(scene->isPeriodic) { cellHsize=scene->cell->hSize; } // force removal of interactions that were not encountered by the collider // (only for some kinds of colliders; see comment for InteractionContainer::iterColliderLastRun) const bool removeUnseenIntrs=(scene->interactions->iterColliderLastRun>=0 && scene->interactions->iterColliderLastRun==scene->iter); #ifdef YADE_OPENMP const long size=scene->interactions->size(); #pragma omp parallel for schedule(guided) num_threads(ompThreads>0 ? min(ompThreads,omp_get_max_threads()) : omp_get_max_threads()) for(long i=0; i& I=(*scene->interactions)[i]; #else for (const auto & I : *scene->interactions){ #endif if(removeUnseenIntrs && !I->isReal() && I->iterLastSeeniter) { eraseAfterLoop(I->getId1(),I->getId2()); continue; } const shared_ptr& b1_=Body::byId(I->getId1(),scene); const shared_ptr& b2_=Body::byId(I->getId2(),scene); if(!b1_ || !b2_){ LOG_DEBUG("Body #"<<(b1_?I->getId2():I->getId1())<<" vanished, erasing intr #"<getId1()<<"+#"<getId2()<<"!"); scene->interactions->requestErase(I); continue; } // Skip interaction with clumps if (b1_->isClump() || b2_->isClump()) { continue; } // we know there is no geometry functor already, take the short path if(!I->functorCache.geomExists) { assert(!I->isReal()); continue; } // no interaction geometry for either of bodies; no interaction possible if(!b1_->shape || !b2_->shape) { assert(!I->isReal()); continue; } bool swap=false; // IGeomDispatcher if(!I->functorCache.geom){ I->functorCache.geom=geomDispatcher->getFunctor2D(b1_->shape,b2_->shape,swap); // returns NULL ptr if no functor exists; remember that and shortcut if(!I->functorCache.geom) { I->functorCache.geomExists=false; continue; } } // arguments for the geom functor are in the reverse order (dispatcher would normally call goReverse). // we don't remember the fact that is reverse, so we swap bodies within the interaction // and can call go in all cases if(swap){I->swapOrder();} // body pointers must be updated, in case we swapped const shared_ptr& b1=swap?b2_:b1_; const shared_ptr& b2=swap?b1_:b2_; assert(I->functorCache.geom); bool wasReal=I->isReal(); bool geomCreated; if(!scene->isPeriodic){ geomCreated=I->functorCache.geom->go(b1->shape,b2->shape, *b1->state, *b2->state, Vector3r::Zero(), /*force*/false, I); } else { // handle periodicity Vector3r shift2=cellHsize*I->cellDist.cast(); // in sheared cell, apply shear on the mutual position as well geomCreated=I->functorCache.geom->go(b1->shape,b2->shape,*b1->state,*b2->state,shift2,/*force*/false,I); } if(!geomCreated){ if(wasReal) LOG_WARN("IGeomFunctor returned false on existing interaction!"); if(wasReal) scene->interactions->requestErase(I); // fully created interaction without geometry is reset and perhaps erased in the next step continue; // in any case don't care about this one anymore } // IPhysDispatcher if(!I->functorCache.phys){ I->functorCache.phys=physDispatcher->getFunctor2D(b1->material,b2->material,swap); assert(!swap); // InteractionPhysicsEngineUnits are symmetric } if(!I->functorCache.phys){ throw std::runtime_error("Undefined or ambiguous IPhys dispatch for types "+b1->material->getClassName()+" and "+b2->material->getClassName()+"."); } I->functorCache.phys->go(b1->material,b2->material,I); assert(I->phys); if(!wasReal) I->iterMadeReal=scene->iter; // mark the interaction as created right now // LawDispatcher // populating constLaw cache must be done after geom and physics dispatchers have been called, since otherwise the interaction // would not have geom and phys yet. if(!I->functorCache.constLaw){ I->functorCache.constLaw=lawDispatcher->getFunctor2D(I->geom,I->phys,swap); if(!I->functorCache.constLaw){ LOG_FATAL("None of given Law2 functors can handle interaction #"<getId1()<<"+"<getId2()<<", types geom:"<geom->getClassName()<<"="<geom->getClassIndex()<<" and phys:"<phys->getClassName()<<"="<phys->getClassIndex()<<" (LawDispatcher::getFunctor2D returned empty functor)"); exit(1); } assert(!swap); // reverse call would make no sense, as the arguments are of different types } assert(I->functorCache.constLaw); //If the functor return false, the interaction is reset if (!I->functorCache.constLaw->go(I->geom,I->phys,I.get())) scene->interactions->requestErase(I); // process callbacks for this interaction if(!I->isReal()) continue; // it is possible that Law2_ functor called requestErase, hence this check for(size_t i=0; i #pragma once #include #include #include #ifdef USE_TIMING_DELTAS #define TIMING_DELTAS_CHECKPOINT(cpt) timingDeltas->checkpoint(cpt) #define TIMING_DELTAS_START() timingDeltas->start() #else #define TIMING_DELTAS_CHECKPOINT(cpt) #define TIMING_DELTAS_START() #endif class InteractionLoop: public GlobalEngine { bool alreadyWarnedNoCollider; using idPair = std::pair; // store interactions that should be deleted after loop in action, not later #ifdef YADE_OPENMP std::vector > eraseAfterLoopIds; void eraseAfterLoop(Body::id_t id1,Body::id_t id2){ eraseAfterLoopIds[omp_get_thread_num()].push_back(idPair(id1,id2)); } #else list eraseAfterLoopIds; void eraseAfterLoop(Body::id_t id1,Body::id_t id2){ eraseAfterLoopIds.push_back(idPair(id1,id2)); } #endif public: virtual void pyHandleCustomCtorArgs(boost::python::tuple& t, boost::python::dict& d); virtual void action(); YADE_CLASS_BASE_DOC_ATTRS_CTOR_PY(InteractionLoop,GlobalEngine,"Unified dispatcher for handling interaction loop at every step, for parallel performance reasons.\n\n.. admonition:: Special constructor\n\n\tConstructs from 3 lists of :yref:`Ig2`, :yref:`Ip2`, :yref:`Law` functors respectively; they will be passed to interal dispatchers, which you might retrieve.", ((shared_ptr,geomDispatcher,new IGeomDispatcher,Attr::readonly,":yref:`IGeomDispatcher` object that is used for dispatch.")) ((shared_ptr,physDispatcher,new IPhysDispatcher,Attr::readonly,":yref:`IPhysDispatcher` object used for dispatch.")) ((shared_ptr,lawDispatcher,new LawDispatcher,Attr::readonly,":yref:`LawDispatcher` object used for dispatch.")) ((vector >,callbacks,,,":yref:`Callbacks` which will be called for every :yref:`Interaction`, if activated.")) ((bool, eraseIntsInLoop, false,,"Defines if the interaction loop should erase pending interactions, else the collider takes care of that alone (depends on what collider is used).")) , /*ctor*/ alreadyWarnedNoCollider=false; #ifdef YADE_OPENMP eraseAfterLoopIds.resize(omp_get_max_threads()); #endif , /*py*/ ); DECLARE_LOGGER; }; REGISTER_SERIALIZABLE(InteractionLoop); trunk-2018.02b/pkg/common/KinematicEngines.cpp000066400000000000000000000147461324306050200211740ustar00rootroot00000000000000 #include #include #include #include YADE_PLUGIN((KinematicEngine)(CombinedKinematicEngine)(TranslationEngine)(HarmonicMotionEngine)(RotationEngine)(HelixEngine)(InterpolatingHelixEngine)(HarmonicRotationEngine)(ServoPIDController)(BicyclePedalEngine)); CREATE_LOGGER(KinematicEngine); void KinematicEngine::action(){ if (ids.size()>0) { FOREACH(Body::id_t id,ids){ assert(id<(Body::id_t)scene->bodies->size()); Body* b=Body::byId(id,scene).get(); if(b) b->state->vel=b->state->angVel=Vector3r::Zero(); } apply(ids); } else { LOG_WARN("The list of ids is empty! Can't move any body."); } } void CombinedKinematicEngine::action(){ if (ids.size()>0) { // reset first FOREACH(Body::id_t id,ids){ assert(id<(Body::id_t)scene->bodies->size()); Body* b=Body::byId(id,scene).get(); if(b) b->state->vel=b->state->angVel=Vector3r::Zero(); } // apply one engine after another FOREACH(const shared_ptr& e, comb){ if (e->dead) continue; e->scene=scene; e->apply(ids); } } else { LOG_WARN("The list of ids is empty! Can't move any body."); } } const shared_ptr CombinedKinematicEngine::fromTwo(const shared_ptr& first, const shared_ptr& second){ shared_ptr ret(new CombinedKinematicEngine); ret->ids=first->ids; ret->comb.push_back(first); ret->comb.push_back(second); return ret; } void TranslationEngine::apply(const vector& ids){ if (ids.size()>0) { #ifdef YADE_OPENMP const long size=ids.size(); #pragma omp parallel for schedule(static) for(long i=0; ibodies->size()); Body* b=Body::byId(id,scene).get(); if(!b) continue; b->state->vel+=velocity*translationAxis; } } else { LOG_WARN("The list of ids is empty! Can't move any body."); } } void HarmonicMotionEngine::apply(const vector& ids){ if (ids.size()>0) { Vector3r w = f*2.0*Mathr::PI; //Angular frequency Vector3r velocity = (((w*scene->time + fi).array().sin())*(-1.0)); velocity = velocity.cwiseProduct(A); velocity = velocity.cwiseProduct(w); FOREACH(Body::id_t id,ids){ assert(id<(Body::id_t)scene->bodies->size()); Body* b=Body::byId(id,scene).get(); if(!b) continue; b->state->vel+=velocity; } } else { LOG_WARN("The list of ids is empty! Can't move any body."); } } void InterpolatingHelixEngine::apply(const vector& ids){ Real virtTime=wrap ? Shop::periodicWrap(scene->time,*times.begin(),*times.rbegin()) : scene->time; angularVelocity=linearInterpolate(virtTime,times,angularVelocities,_pos); linearVelocity=angularVelocity*slope; HelixEngine::apply(ids); } void HelixEngine::apply(const vector& ids){ if (ids.size()>0) { const Real& dt=scene->dt; angleTurned+=angularVelocity*dt; shared_ptr bodies = scene->bodies; FOREACH(Body::id_t id,ids){ assert(id<(Body::id_t)bodies->size()); Body* b=Body::byId(id,scene).get(); if(!b) continue; b->state->vel+=linearVelocity*rotationAxis; } rotateAroundZero=true; RotationEngine::apply(ids); } else { LOG_WARN("The list of ids is empty! Can't move any body."); } } void RotationEngine::apply(const vector& ids){ if (ids.size()>0) { #ifdef YADE_OPENMP const long size=ids.size(); #pragma omp parallel for schedule(static) for(long i=0; ibodies->size()); Body* b=Body::byId(id,scene).get(); if(!b) continue; b->state->angVel+=rotationAxis*angularVelocity; if(rotateAroundZero){ const Vector3r l=b->state->pos-zeroPoint; Quaternionr q(AngleAxisr(angularVelocity*scene->dt,rotationAxis)); Vector3r newPos=q*l+zeroPoint; b->state->vel+=Vector3r(newPos-b->state->pos)/scene->dt; } } } else { LOG_WARN("The list of ids is empty! Can't move any body."); } } void HarmonicRotationEngine::apply(const vector& ids){ const Real& time=scene->time; Real w = f*2.0*Mathr::PI; //Angular frequency angularVelocity = -1.0*A*w*sin(w*time + fi); RotationEngine::apply(ids); } void ServoPIDController::apply(const vector& ids){ if (iterPrevStart < 0 or ((scene->iter-iterPrevStart)>=iterPeriod)) { Vector3r tmpForce = Vector3r::Zero(); if (ids.size()>0) { FOREACH(Body::id_t id,ids){ assert(id<(Body::id_t)scene->bodies->size()); tmpForce += scene->forces.getForce(id); } } else { LOG_WARN("The list of ids is empty!"); } axis.normalize(); tmpForce = tmpForce.cwiseProduct(axis); // Take into account given axis errorCur = tmpForce.norm() - target; // Find error const Real pTerm = errorCur*kP; // Calculate proportional term iTerm += errorCur*kI; // Calculate integral term const Real dTerm = (errorCur-errorPrev)*kD; // Calculate derivative term errorPrev = errorCur; // Save the current value of the error curVel = (pTerm + iTerm + dTerm); // Calculate current velocity if (std::abs(curVel) > std::abs(maxVelocity)) { curVel*=std::abs(maxVelocity)/std::abs(curVel); } iterPrevStart = scene->iter; current = tmpForce; } translationAxis = axis; velocity = curVel; TranslationEngine::apply(ids); } void BicyclePedalEngine::apply(const vector& ids){ if (ids.size()>0) { Quaternionr qRotateZVec(Quaternionr().setFromTwoVectors(Vector3r(0,0,1), rotationAxis)); Vector3r newPos = Vector3r(cos(fi + angularVelocity*scene->dt)*radius, sin(fi + angularVelocity*scene->dt)*radius, 0.0); Vector3r oldPos = Vector3r(cos(fi)*radius, sin(fi)*radius, 0.0); Vector3r newVel = (oldPos - newPos)/scene->dt; fi += angularVelocity*scene->dt; newVel = qRotateZVec*newVel; #ifdef YADE_OPENMP const long size=ids.size(); #pragma omp parallel for schedule(static) for(long i=0; ibodies->size()); Body* b=Body::byId(id,scene).get(); if(!b) continue; b->state->vel+=newVel; } } else { LOG_WARN("The list of ids is empty! Can't move any body."); } } trunk-2018.02b/pkg/common/KinematicEngines.hpp000066400000000000000000000204201324306050200211630ustar00rootroot00000000000000 #pragma once #include #include struct KinematicEngine; struct CombinedKinematicEngine: public PartialEngine{ virtual void action(); YADE_CLASS_BASE_DOC_ATTRS_CTOR_PY(CombinedKinematicEngine,PartialEngine,"Engine for applying combined displacements on pre-defined bodies. Constructed using ``+`` operator on regular :yref:`KinematicEngines`. The ``ids`` operated on are those of the first engine in the combination (assigned automatically).", ((vector >,comb,,,"Kinematic engines that will be combined by this one, run in the order given.")) , /* ctor */ , /* py */ .def("__add__",&CombinedKinematicEngine::appendOne) ); // exposed as operator + in python static const shared_ptr appendOne(const shared_ptr& self, const shared_ptr& other){ self->comb.push_back(other); return self; } static const shared_ptr fromTwo(const shared_ptr& first, const shared_ptr& second); }; REGISTER_SERIALIZABLE(CombinedKinematicEngine); struct KinematicEngine: public PartialEngine{ virtual void action(); virtual void apply(const vector& ids){ LOG_ERROR("KinematicEngine::apply called, derived class ("<` and :yref:`angular velocity` of all subscribed bodies is reset before the ``apply`` method is called, it should therefore only increment those quantities.", /* attrs*/, /* ctor */, /* py */ .def("__add__",&CombinedKinematicEngine::fromTwo) ) DECLARE_LOGGER; }; REGISTER_SERIALIZABLE(KinematicEngine); struct TranslationEngine: public KinematicEngine{ virtual void apply(const vector& ids); void postLoad(TranslationEngine&){ translationAxis.normalize(); } YADE_CLASS_BASE_DOC_ATTRS(TranslationEngine,KinematicEngine,"Engine applying translation motion (by setting linear velocity) to subscribed bodies.", ((Real,velocity,,,"Scalar value of the imposed velocity [m/s]. Imposed vector velocity is :yref:`velocity` * :yref:`axis`")) ((Vector3r,translationAxis,,Attr::triggerPostLoad,"Direction of imposed translation [Vector3]")) ); }; REGISTER_SERIALIZABLE(TranslationEngine); struct HarmonicMotionEngine: public KinematicEngine{ virtual void apply(const vector& ids); YADE_CLASS_BASE_DOC_ATTRS(HarmonicMotionEngine,KinematicEngine,"This engine implements the harmonic oscillation of bodies. http://en.wikipedia.org/wiki/Simple_harmonic_motion#Dynamics_of_simple_harmonic_motion", ((Vector3r,A,Vector3r::Zero(),,"Amplitude [m]")) ((Vector3r,f,Vector3r::Zero(),,"Frequency [hertz]")) ((Vector3r,fi,Vector3r(Mathr::PI/2.0, Mathr::PI/2.0, Mathr::PI/2.0),,"Initial phase [radians]. By default, the body oscillates around initial position.")) ); }; REGISTER_SERIALIZABLE(HarmonicMotionEngine); struct RotationEngine: public KinematicEngine{ virtual void apply(const vector& ids); void postLoad(RotationEngine&){ rotationAxis.normalize(); } YADE_CLASS_BASE_DOC_ATTRS(RotationEngine,KinematicEngine,"Engine applying rotation (by setting angular velocity) to subscribed bodies. If :yref:`rotateAroundZero` is set, then each body is also displaced around :yref:`zeroPoint`.", ((Real,angularVelocity,0,,"Angular velocity. [rad/s]")) ((Vector3r,rotationAxis,Vector3r::UnitX(),Attr::triggerPostLoad,"Axis of rotation (direction); will be normalized automatically.")) ((bool,rotateAroundZero,false,,"If True, bodies will not rotate around their centroids, but rather around ``zeroPoint``.")) ((Vector3r,zeroPoint,Vector3r::Zero(),,"Point around which bodies will rotate if ``rotateAroundZero`` is True")) ); }; REGISTER_SERIALIZABLE(RotationEngine); struct HelixEngine:public RotationEngine{ virtual void apply(const vector& ids); YADE_CLASS_BASE_DOC_ATTRS(HelixEngine,RotationEngine,"Engine applying both rotation and translation, along the same axis, whence the name HelixEngine", ((Real,linearVelocity,0,,"Linear velocity [m/s]")) ((Real,angleTurned,0,,"How much have we turned so far. |yupdate| [rad]")) ); }; REGISTER_SERIALIZABLE(HelixEngine); struct InterpolatingHelixEngine: public HelixEngine{ virtual void apply(const vector& ids); YADE_CLASS_BASE_DOC_ATTRS(InterpolatingHelixEngine,HelixEngine,"Engine applying spiral motion, finding current angular velocity by linearly interpolating in times and velocities and translation by using slope parameter. \n\n The interpolation assumes the margin value before the first time point and last value after the last time point. If wrap is specified, time will wrap around the last times value to the first one (note that no interpolation between last and first values is done).", ((vector,times,,,"List of time points at which velocities are given; must be increasing [s]")) ((vector,angularVelocities,,,"List of angular velocities; manadatorily of same length as times. [rad/s]")) ((bool,wrap,false,,"Wrap t if t>times_n, i.e. t_wrapped=t-N*(times_n-times_0)")) ((Real,slope,0,,"Axial translation per radian turn (can be negative) [m/rad]")) ((size_t,_pos,0,(Attr::hidden),"holder of interpolation state, should not be touched by user")) ); }; REGISTER_SERIALIZABLE(InterpolatingHelixEngine); struct HarmonicRotationEngine: public RotationEngine{ virtual void apply(const vector& ids); YADE_CLASS_BASE_DOC_ATTRS(HarmonicRotationEngine,RotationEngine,"This engine implements the harmonic-rotation oscillation of bodies. http://en.wikipedia.org/wiki/Simple_harmonic_motion#Dynamics_of_simple_harmonic_motion ; please, set dynamic=False for bodies, droven by this engine, otherwise amplitude will be 2x more, than awaited.", ((Real,A,0,,"Amplitude [rad]")) ((Real,f,0,,"Frequency [hertz]")) ((Real,fi,Mathr::PI/2.0,,"Initial phase [radians]. By default, the body oscillates around initial position.")) ); }; REGISTER_SERIALIZABLE(HarmonicRotationEngine); struct ServoPIDController: public TranslationEngine{ virtual void apply(const vector& ids); YADE_CLASS_BASE_DOC_ATTRS_CTOR_PY(ServoPIDController,TranslationEngine,"PIDController servo-engine for applying prescribed force on bodies. http://en.wikipedia.org/wiki/PID_controller", ((Real,maxVelocity,0.0,,"Velocity [m/s]")) ((Vector3r,axis,Vector3r::Zero(),,"Unit vector along which apply the velocity [-]")) ((Real,target,0.0,,"Target value for the controller [N]")) ((Vector3r,current,Vector3r::Zero(),,"Current value for the controller [N]")) ((Real,kP,0.0,,"Proportional gain/coefficient for the PID-controller [-]")) ((Real,kI,0.0,,"Integral gain/coefficient for the PID-controller [-]")) ((Real,kD,0.0,,"Derivative gain/coefficient for the PID-controller [-]")) ((Real,iTerm,0.0,,"Integral term [N]")) ((Real,curVel,0.0,,"Current applied velocity [m/s]")) ((Real,errorCur,0.0,,"Current error [N]")) ((Real,errorPrev,0.0,,"Previous error [N]")) ((long,iterPeriod,100.0,,"Periodicity criterion of velocity correlation [-]")) ((long,iterPrevStart,-1.0,,"Previous iteration of velocity correlation [-]")) /* attrs*/, /* ctor */, /* py */ ) DECLARE_LOGGER; }; REGISTER_SERIALIZABLE(ServoPIDController); struct BicyclePedalEngine: public KinematicEngine{ virtual void apply(const vector& ids); void postLoad(BicyclePedalEngine&){ rotationAxis.normalize(); } YADE_CLASS_BASE_DOC_ATTRS(BicyclePedalEngine,KinematicEngine,"Engine applying the linear motion of ``bicycle pedal`` e.g. moving points around the axis without rotation", ((Real,angularVelocity,0,,"Angular velocity. [rad/s]")) ((Vector3r,rotationAxis,Vector3r::UnitX(),Attr::triggerPostLoad,"Axis of rotation (direction); will be normalized automatically.")) ((Real,radius,-1.0,,"Rotation radius. [m]")) ((Real,fi,Mathr::PI/2.0,,"Initial phase [radians]")) ); }; REGISTER_SERIALIZABLE(BicyclePedalEngine); trunk-2018.02b/pkg/common/MatchMaker.cpp000066400000000000000000000033661324306050200177670ustar00rootroot00000000000000// 2010 © Václav Šmilauer #include YADE_PLUGIN((MatchMaker)); Real MatchMaker::operator()(int id1, int id2, Real val1, Real val2) const { const int minId = std::min(id1, id2); const int maxId = std::max(id1, id2); const auto foundMatchItem = matchSet.find (std::make_pair(minId, maxId)); if ( foundMatchItem != matchSet.end() ) { return foundMatchItem->second; } else { if(fbNeedsValues && (std::isnan(val1) || std::isnan(val2))) { throw std::invalid_argument("MatchMaker: no match for ("+boost::lexical_cast(id1)+ ","+boost::lexical_cast(id2)+"), and values required for algo computation '"+ algo+"' not specified."); } return computeFallback(val1,val2); } } void MatchMaker::postLoad(MatchMaker&){ if(algo=="val") { fbPtr=&MatchMaker::fbVal; fbNeedsValues=false; } else if(algo=="zero"){ fbPtr=&MatchMaker::fbZero;fbNeedsValues=false; } else if(algo=="avg") { fbPtr=&MatchMaker::fbAvg; fbNeedsValues=true; } else if(algo=="min") { fbPtr=&MatchMaker::fbMin; fbNeedsValues=true; } else if(algo=="max") { fbPtr=&MatchMaker::fbMax; fbNeedsValues=true; } else if(algo=="harmAvg") { fbPtr=&MatchMaker::fbHarmAvg; fbNeedsValues=true; } else throw std::invalid_argument("MatchMaker:: algo '"+algo+"' not recognized (possible values: val, avg, min, max, harmAvg)."); // Fill matchSet with values for a fast coefficient search for (const auto & m : matches) { const int minId = std::min((int)m[0], (int)m[1]); const int maxId = std::max((int)m[0], (int)m[1]); const auto idPair = std::make_pair(minId, maxId); matchSet.insert(std::make_pair(idPair, m[2])); } } Real MatchMaker::computeFallback(Real v1, Real v2) const { return (this->*MatchMaker::fbPtr)(v1,v2); } trunk-2018.02b/pkg/common/MatchMaker.hpp000066400000000000000000000074761324306050200200020ustar00rootroot00000000000000// 2010 © Václav Šmilauer #pragma once #include #include namespace py = boost::python; /* Future optimizations, in postLoad: 1. Use matches to update lookup table/hash for faster matching, instead of traversing matches every time 2. Use algo to update fbPtr, instead of string-comparison of algo every time */ class MatchMaker: public Serializable { Real fbZero(Real v1, Real v2) const{ return 0.; } Real fbAvg(Real v1, Real v2) const{ return (v1+v2)/2.; } Real fbMin(Real v1, Real v2) const{ return min(v1,v2); } Real fbMax(Real v1, Real v2) const{ return max(v1,v2); } Real fbHarmAvg(Real v1, Real v2) const { return 2*v1*v2/(v1+v2); } Real fbVal(Real v1, Real v2) const { return val; } Real (MatchMaker::*fbPtr)(Real,Real) const; // whether the current fbPtr function needs valid input values in order to give meaningful result. // must be kept in sync with fbPtr, which is done in postLoad bool fbNeedsValues; // unordered set for a fast coefficient search boost::unordered_map, Real> matchSet; public: virtual ~MatchMaker() {}; MatchMaker(std::string _algo): algo(_algo){ postLoad(*this); } MatchMaker(Real _val): algo("val"), val(_val){ postLoad(*this); } Real computeFallback(Real val1, Real val2) const ; void postLoad(MatchMaker&); // return value looking up matches for id1+id2 (the order is arbitrary) // if no match is found, use val1,val2 and algo strategy to compute new value. // if no match is found and val1 or val2 are not given, throw exception Real operator()(const int id1, const int id2, const Real val1=NaN, const Real val2=NaN) const; YADE_CLASS_BASE_DOC_ATTRS_CTOR_PY(MatchMaker,Serializable,"Class matching pair of ids to return pre-defined (for a pair of ids defined in :yref:`matches`) or derived value (computed using :yref:`algo`) of a scalar parameter. It can be called (``id1``, ``id2``, ``val1=NaN``, ``val2=NaN``) in both python and c++. \n\n.. note:: There is a :ref:`converter ` from python number defined for this class, which creates a new :yref:`MatchMaker` returning the value of that number; instead of giving the object instance therefore, you can only pass the number value and it will be converted automatically.", ((std::vector,matches,,Attr::readonly,"Array of ``(id1,id2,value)`` items; queries matching ``id1`` + ``id2`` or ``id2`` + ``id1`` will return ``value``")) ((std::string,algo,"avg",Attr::triggerPostLoad,"Alogorithm used to compute value when no match for ids is found. Possible values are\n\n* 'avg' (arithmetic average)\n* 'min' (minimum value)\n* 'max' (maximum value)\n* 'harmAvg' (harmonic average)\n\nThe following algo algorithms do *not* require meaningful input values in order to work:\n\n* 'val' (return value specified by :yref:`val`)\n* 'zero' (always return 0.)\n\n")) ((Real,val,NaN,,"Constant value returned if there is no match and :yref:`algo` is ``val``")) , fbPtr=&MatchMaker::fbAvg; fbNeedsValues=true; /* keep in sync with the algo value for algo */ , /*py*/ .def("__call__",&MatchMaker::operator(),(py::arg("id1"),py::arg("id2"),py::arg("val1")=NaN,py::arg("val2")=NaN),"Ask the instance for scalar value for given pair *id1*,*id2* (the order is irrelevant). Optionally, *val1*, *val2* can be given so that if there is no :yref:`match`, return value can be computed using given :yref:`algo`. If there is no match and *val1*, *val2* are not given, an exception is raised.") .def("computeFallback",&MatchMaker::computeFallback,(py::arg("val1"),py::arg("val2")),"Compute algo value for *val1* and *val2*, using algorithm specified by :yref:`algo`.") ); }; REGISTER_SERIALIZABLE(MatchMaker); trunk-2018.02b/pkg/common/NormShearPhys.hpp000066400000000000000000000023471324306050200205200ustar00rootroot00000000000000// © 2007 Janek Kozicki // © 2008 Václav Šmilauer #pragma once #include class NormPhys:public IPhys { public: virtual ~NormPhys() {}; virtual Vector3r getRotStiffness() {return Vector3r::Zero();} YADE_CLASS_BASE_DOC_ATTRS_CTOR(NormPhys,IPhys,"Abstract class for interactions that have normal stiffness.", ((Real,kn,0,,"Normal stiffness")) ((Vector3r,normalForce,Vector3r::Zero(),,"Normal force after previous step (in global coordinates), as sustained by particle #2 (from particle #1).")), createIndex(); ); REGISTER_CLASS_INDEX(NormPhys,IPhys); }; REGISTER_SERIALIZABLE(NormPhys); class NormShearPhys: public NormPhys{ public: virtual ~NormShearPhys() {}; YADE_CLASS_BASE_DOC_ATTRS_CTOR(NormShearPhys,NormPhys, "Abstract class for interactions that have shear stiffnesses, in addition to normal stiffness. This class is used in the PFC3d-style stiffness timestepper.", ((Real,ks,0,,"Shear stiffness")) ((Vector3r,shearForce,Vector3r::Zero(),,"Shear force after previous step (in global coordinates), as sustained by particle #2 (from particle #1).")), createIndex(); ); REGISTER_CLASS_INDEX(NormShearPhys,NormPhys); }; REGISTER_SERIALIZABLE(NormShearPhys); trunk-2018.02b/pkg/common/OpenGLRenderer.cpp000066400000000000000000000416151324306050200205650ustar00rootroot00000000000000// © 2004 Olivier Galizzi // © 2008 Václav Šmilauer #ifdef YADE_OPENGL #include "OpenGLRenderer.hpp" #include #include #include #include #include #include #include #include YADE_PLUGIN((OpenGLRenderer)(GlExtraDrawer)); CREATE_LOGGER(OpenGLRenderer); void GlExtraDrawer::render(){ throw runtime_error("GlExtraDrawer::render called from class "+getClassName()+". (did you forget to override it in the derived class?)"); } bool OpenGLRenderer::initDone=false; const int OpenGLRenderer::numClipPlanes; OpenGLRenderer::~OpenGLRenderer(){} void OpenGLRenderer::init(){ typedef std::pair strDldPair; // necessary as FOREACH, being macro, cannot have the "," inside the argument (preprocessor does not parse templates) FOREACH(const strDldPair& item, Omega::instance().getDynlibsDescriptor()){ // if (Omega::instance().isInheritingFrom_recursive(item.first,"GlStateFunctor")) stateFunctorNames.push_back(item.first); if (Omega::instance().isInheritingFrom_recursive(item.first,"GlBoundFunctor")) boundFunctorNames.push_back(item.first); if (Omega::instance().isInheritingFrom_recursive(item.first,"GlShapeFunctor")) shapeFunctorNames.push_back(item.first); if (Omega::instance().isInheritingFrom_recursive(item.first,"GlIGeomFunctor")) geomFunctorNames.push_back(item.first); if (Omega::instance().isInheritingFrom_recursive(item.first,"GlIPhysFunctor")) physFunctorNames.push_back(item.first); } initgl(); // creates functor objects in the proper sense clipPlaneNormals.resize(numClipPlanes); static bool glutInitDone=false; if(!glutInitDone){ glutInit(&Omega::instance().origArgc,Omega::instance().origArgv); /* transparent spheres (still not working): glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGBA | GLUT_DEPTH | GLUT_MULTISAMPLE | GLUT_ALPHA); glEnable(GL_BLEND); glBlendFunc(GL_SRC_ALPHA, GL_ONE); */ glutInitDone=true; } initDone=true; } void OpenGLRenderer::setBodiesRefSe3(){ LOG_DEBUG("(re)initializing reference positions and orientations."); FOREACH(const shared_ptr& b, *scene->bodies) if(b && b->state) { b->state->refPos=b->state->pos; b->state->refOri=b->state->ori; } scene->cell->refHSize=scene->cell->hSize; } template void OpenGLRenderer::setupDispatcher(const vector & names, DispatcherT & dispatcher) { dispatcher.clearMatrix(); for(const auto & s : names) { shared_ptr f(boost::static_pointer_cast(ClassFactory::instance().createShared(s))); f->initgl(); dispatcher.add(f); } } void OpenGLRenderer::initgl(){ LOG_DEBUG("(re)initializing GL for gldraw methods.\n"); setupDispatcher (boundFunctorNames, boundDispatcher); setupDispatcher (shapeFunctorNames, shapeDispatcher); setupDispatcher (geomFunctorNames, geomDispatcher); setupDispatcher (physFunctorNames, physDispatcher); } bool OpenGLRenderer::pointClipped(const Vector3r& p){ if(numClipPlanes<1) return false; for(int i=0;ibodies->size()!=bodyDisp.size()) { bodyDisp.resize(scene->bodies->size()); for (unsigned k=0; kbodies->size(); k++) bodyDisp[k].hidden=0;} bool scaleRotations=(rotScale!=1.0); bool scaleDisplacements=(dispScale!=Vector3r::Ones()); FOREACH(const shared_ptr& b, *scene->bodies){ if(!b || !b->state) continue; size_t id=b->getId(); const Vector3r& pos=b->state->pos; const Vector3r& refPos=b->state->refPos; const Quaternionr& ori=b->state->ori; const Quaternionr& refOri=b->state->refOri; Vector3r cellPos=(!scene->isPeriodic ? pos : scene->cell->wrapShearedPt(pos)); // inside the cell if periodic, same as pos otherwise bodyDisp[id].isDisplayed=!pointClipped(cellPos); // if no scaling and no periodic, return quickly if(!(scaleDisplacements||scaleRotations||scene->isPeriodic)){ bodyDisp[id].pos=pos; bodyDisp[id].ori=ori; continue; } // apply scaling bodyDisp[id].pos=cellPos; // point of reference (inside the cell for periodic) if(scaleDisplacements) bodyDisp[id].pos+=dispScale.cwiseProduct(Vector3r(pos-refPos)); // add scaled translation to the point of reference if(!scaleRotations) bodyDisp[id].ori=ori; else{ Quaternionr relRot=refOri.conjugate()*ori; AngleAxisr aa(relRot); aa.angle()*=rotScale; bodyDisp[id].ori=refOri*Quaternionr(aa); } } } // draw periodic cell, if active void OpenGLRenderer::drawPeriodicCell(){ if(!scene->isPeriodic) return; glColor3v(cellColor); glPushMatrix(); // Vector3r size=scene->cell->getSize(); const Matrix3r& hSize=scene->cell->hSize; if(dispScale!=Vector3r::Ones()){ const Matrix3r& refHSize(scene->cell->refHSize); Matrix3r scaledHSize; for(int i=0; i<3; i++) scaledHSize.col(i)=refHSize.col(i)+dispScale.cwiseProduct(Vector3r(hSize.col(i)-refHSize.col(i))); GLUtils::Parallelepiped(scaledHSize.col(0),scaledHSize.col(1),scaledHSize.col(2)); } else { GLUtils::Parallelepiped(hSize.col(0),hSize.col(1),hSize.col(2)); } glPopMatrix(); } void OpenGLRenderer::resetSpecularEmission(){ glMateriali(GL_FRONT, GL_SHININESS, 80); const GLfloat glutMatSpecular[4]={0.3,0.3,0.3,0.5}; const GLfloat glutMatEmit[4]={0.2,0.2,0.2,1.0}; glMaterialfv(GL_FRONT,GL_SPECULAR,glutMatSpecular); glMaterialfv(GL_FRONT,GL_EMISSION,glutMatEmit); } void OpenGLRenderer::render(const shared_ptr& _scene,Body::id_t selection){ gilLock lockgil; if(!initDone) init(); assert(initDone); selId = selection; scene=_scene; // assign scene inside functors boundDispatcher.updateScenePtr(); geomDispatcher.updateScenePtr(); physDispatcher.updateScenePtr(); shapeDispatcher.updateScenePtr(); // stateDispatcher.updateScenePtr(); // just to make sure, since it is not initialized by default if(!scene->bound) scene->bound=shared_ptr(new Aabb); // recompute emissive light colors for highlighted bodies Real now=TimingInfo::getNow(/*even if timing is disabled*/true)*1e-9; highlightEmission0[0]=highlightEmission0[1]=highlightEmission0[2]=.8*normSquare(now,1); highlightEmission1[0]=highlightEmission1[1]=highlightEmission0[2]=.5*normSaw(now,2); // clipping assert(clipPlaneNormals.size()==(size_t)numClipPlanes); for(size_t i=0;i<(size_t)numClipPlanes; i++){ // someone could have modified those from python and truncate the vectors; fill those here in that case if(i==clipPlaneSe3.size()) clipPlaneSe3.push_back(Se3r(Vector3r::Zero(),Quaternionr::Identity())); if(i==clipPlaneActive.size()) clipPlaneActive.push_back(false); if(i==clipPlaneNormals.size()) clipPlaneNormals.push_back(Vector3r::UnitX()); // end filling stuff modified from python if(clipPlaneActive[i]) clipPlaneNormals[i]=clipPlaneSe3[i].orientation*Vector3r(0,0,1); /* glBegin(GL_LINES);glVertex3v(clipPlaneSe3[i].position);glVertex3v(clipPlaneSe3[i].position+clipPlaneNormals[i]);glEnd(); */ } // set displayed Se3 of body (scaling) and isDisplayed (clipping) setBodiesDispInfo(); glClearColor(bgColor[0],bgColor[1],bgColor[2],1.0); // set light sources glLightModelf(GL_LIGHT_MODEL_TWO_SIDE,1); // important: do lighting calculations on both sides of polygons const GLfloat pos[4] = {(float) lightPos[0], (float) lightPos[1], (float) lightPos[2],1.0}; const GLfloat ambientColor[4]={0.2,0.2,0.2,1.0}; const GLfloat specularColor[4]={1,1,1,1.f}; const GLfloat diffuseLight[4] = { (float) lightColor[0], (float) lightColor[1], (float) lightColor[2], 1.0f }; glLightfv(GL_LIGHT0, GL_POSITION,pos); glLightfv(GL_LIGHT0, GL_SPECULAR, specularColor); glLightfv(GL_LIGHT0, GL_AMBIENT, ambientColor); glLightfv(GL_LIGHT0, GL_DIFFUSE, diffuseLight); if (light1) glEnable(GL_LIGHT0); else glDisable(GL_LIGHT0); const GLfloat pos2[4] = {(float) light2Pos[0], (float) light2Pos[1], (float) light2Pos[2],1.0}; const GLfloat ambientColor2[4]={0.0,0.0,0.0,1.0}; const GLfloat specularColor2[4]={1,1,0.6,1.f}; const GLfloat diffuseLight2[4] = { (float) light2Color[0], (float) light2Color[1], (float) light2Color[2], 1.0f }; glLightfv(GL_LIGHT1, GL_POSITION,pos2); glLightfv(GL_LIGHT1, GL_SPECULAR, specularColor2); glLightfv(GL_LIGHT1, GL_AMBIENT, ambientColor2); glLightfv(GL_LIGHT1, GL_DIFFUSE, diffuseLight2); if (light2) glEnable(GL_LIGHT1); else glDisable(GL_LIGHT1); glEnable(GL_LIGHTING); glEnable(GL_CULL_FACE); // http://www.sjbaker.org/steve/omniv/opengl_lighting.html glColorMaterial(GL_FRONT,GL_AMBIENT_AND_DIFFUSE); glEnable(GL_COLOR_MATERIAL); //Shared material settings resetSpecularEmission(); drawPeriodicCell(); if (dof || id) renderDOF_ID(); if (bound) renderBound(); if (shape) renderShape(); if (intrAllWire) renderAllInteractionsWire(); if (intrGeom) renderIGeom(); if (intrPhys) renderIPhys(); FOREACH(const shared_ptr d, extraDrawers){ if(d->dead) continue; glPushMatrix(); d->scene=scene.get(); d->render(); glPopMatrix(); } } void OpenGLRenderer::renderAllInteractionsWire(){ FOREACH(const shared_ptr& i, *scene->interactions){ if(!i->functorCache.geomExists) continue; glColor3v(i->isReal()? Vector3r(0,1,0) : Vector3r(.5,0,1)); Vector3r p1=Body::byId(i->getId1(),scene)->state->pos; const Vector3r& size=scene->cell->getSize(); Vector3r shift2(i->cellDist[0]*size[0],i->cellDist[1]*size[1],i->cellDist[2]*size[2]); // in sheared cell, apply shear on the mutual position as well shift2=scene->cell->shearPt(shift2); Vector3r rel=Body::byId(i->getId2(),scene)->state->pos+shift2-p1; if(scene->isPeriodic) p1=scene->cell->wrapShearedPt(p1); glBegin(GL_LINES); glVertex3v(p1);glVertex3v(Vector3r(p1+rel));glEnd(); } } void OpenGLRenderer::renderDOF_ID(){ const GLfloat ambientColorSelected[4]={10.0,0.0,0.0,1.0}; const GLfloat ambientColorUnselected[4]={0.5,0.5,0.5,1.0}; FOREACH(const shared_ptr b, *scene->bodies){ if(!b) continue; if(b->shape && ((b->getGroupMask() & mask) || b->getGroupMask()==0)){ if(!id && b->state->blockedDOFs==0) continue; if(selId==b->getId()){glLightModelfv(GL_LIGHT_MODEL_AMBIENT,ambientColorSelected);} { // write text glColor3f(1.0-bgColor[0],1.0-bgColor[1],1.0-bgColor[2]); unsigned d = b->state->blockedDOFs; std::string sDof = std::string()+(((d&State::DOF_X )!=0)?"x":"")+(((d&State::DOF_Y )!=0)?"y":" ")+(((d&State::DOF_Z )!=0)?"z":"")+(((d&State::DOF_RX)!=0)?"X":"")+(((d&State::DOF_RY)!=0)?"Y":"")+(((d&State::DOF_RZ)!=0)?"Z":""); std::string sId = boost::lexical_cast(b->getId()); std::string str; if(dof && id) sId += " "; if(id) str += sId; if(dof) str += sDof; const Vector3r& h(selId==b->getId() ? highlightEmission0 : Vector3r(1,1,1)); glColor3v(h); GLUtils::GLDrawText(str,bodyDisp[b->id].pos,h); } if(selId == b->getId()){glLightModelfv(GL_LIGHT_MODEL_AMBIENT,ambientColorUnselected);} } } } void OpenGLRenderer::renderIGeom(){ geomDispatcher.scene=scene.get(); geomDispatcher.updateScenePtr(); { boost::mutex::scoped_lock lock(scene->interactions->drawloopmutex); FOREACH(const shared_ptr& I, *scene->interactions){ if(!I->geom) continue; // avoid refcount manipulations if the interaction is not real anyway shared_ptr ig(I->geom); // keep reference so that ig does not disappear suddenly while being rendered if(!ig) continue; const shared_ptr& b1=Body::byId(I->getId1(),scene), b2=Body::byId(I->getId2(),scene); if(!(bodyDisp[I->getId1()].isDisplayed||bodyDisp[I->getId2()].isDisplayed)) continue; glPushMatrix(); geomDispatcher(ig,I,b1,b2,intrWire); glPopMatrix(); } } } void OpenGLRenderer::renderIPhys(){ physDispatcher.scene=scene.get(); physDispatcher.updateScenePtr(); { boost::mutex::scoped_lock lock(scene->interactions->drawloopmutex); FOREACH(const shared_ptr& I, *scene->interactions){ shared_ptr ip(I->phys); if(!ip) continue; const shared_ptr& b1=Body::byId(I->getId1(),scene), b2=Body::byId(I->getId2(),scene); Body::id_t id1=I->getId1(), id2=I->getId2(); if(!(bodyDisp[id1].isDisplayed||bodyDisp[id2].isDisplayed)) continue; glPushMatrix(); physDispatcher(ip,I,b1,b2,intrWire); glPopMatrix(); } } } void OpenGLRenderer::renderBound(){ boundDispatcher.scene=scene.get(); boundDispatcher.updateScenePtr(); FOREACH(const shared_ptr& b, *scene->bodies){ if(!b || !b->bound) continue; if(!bodyDisp[b->getId()].isDisplayed or bodyDisp[b->getId()].hidden) continue; if(b->bound && ((b->getGroupMask()&mask) || b->getGroupMask()==0)){ glPushMatrix(); boundDispatcher(b->bound,scene.get()); glPopMatrix(); } } // since we remove the functor as Scene doesn't inherit from Body anymore, hardcore the rendering routine here // for periodic scene, renderPeriodicCell is called separately if(!scene->isPeriodic){ if(!scene->bound) scene->updateBound(); glColor3v(Vector3r(0,1,0)); Vector3r size=scene->bound->max-scene->bound->min; Vector3r center=.5*(scene->bound->min+scene->bound->max); glPushMatrix(); glTranslatev(center); glScalev(size); glutWireCube(1); glPopMatrix(); } } // this function is called for both rendering as well as // in the selection mode // nice reading on OpenGL selection // http://glprogramming.com/red/chapter13.html void OpenGLRenderer::renderShape(){ shapeDispatcher.scene=scene.get(); shapeDispatcher.updateScenePtr(); // instead of const shared_ptr&, get proper shared_ptr; // Less efficient in terms of performance, since memory has to be written (not measured, though), // but it is still better than crashes if the body gets deleted meanwile. FOREACH(shared_ptr b, *scene->bodies){ if(!b || !b->shape) continue; if(!(bodyDisp[b->getId()].isDisplayed and !bodyDisp[b->getId()].hidden)) continue; Vector3r pos=bodyDisp[b->getId()].pos; Quaternionr ori=bodyDisp[b->getId()].ori; if(!b->shape || !((b->getGroupMask()&mask) || b->getGroupMask()==0)) continue; // ignored in non-selection mode, use it always glPushName(b->id); bool highlight=(b->id==selId || (b->clumpId>=0 && b->clumpId==selId) || b->shape->highlight); glPushMatrix(); AngleAxisr aa(ori); glTranslatef(pos[0],pos[1],pos[2]); glRotatef(aa.angle()*Mathr::RAD_TO_DEG,aa.axis()[0],aa.axis()[1],aa.axis()[2]); if(highlight){ // set hightlight // different color for body highlighted by selection and by the shape attribute const Vector3r& h((selId==b->id||(b->clumpId>=0 && selId==b->clumpId)) ? highlightEmission0 : highlightEmission1); glMaterialv(GL_FRONT_AND_BACK,GL_EMISSION,h); glMaterialv(GL_FRONT_AND_BACK,GL_SPECULAR,h); shapeDispatcher(b->shape,b->state,wire || b->shape->wire,viewInfo); // reset highlight resetSpecularEmission(); } else { // no highlight; in case previous functor fiddled with glMaterial resetSpecularEmission(); shapeDispatcher(b->shape,b->state,wire || b->shape->wire,viewInfo); } glPopMatrix(); if(highlight){ if(!b->bound || wire || b->shape->wire) GLUtils::GLDrawInt(b->getId(),pos); else { // move the label towards the camera by the bounding box so that it is not hidden inside the body const Vector3r& mn=b->bound->min; const Vector3r& mx=b->bound->max; const Vector3r& p=pos; Vector3r ext(viewDirection[0]>0?p[0]-mn[0]:p[0]-mx[0],viewDirection[1]>0?p[1]-mn[1]:p[1]-mx[1],viewDirection[2]>0?p[2]-mn[2]:p[2]-mx[2]); // signed extents towards the camera Vector3r dr=-1.01*(viewDirection.dot(ext)*viewDirection); GLUtils::GLDrawInt(b->getId(),pos+dr,Vector3r::Ones()); } } // if the body goes over the cell margin, draw it in positions where the bbox overlaps with the cell in wire // precondition: pos is inside the cell. if(b->bound && scene->isPeriodic && ghosts){ const Vector3r& cellSize(scene->cell->getSize()); pos=scene->cell->unshearPt(pos); // remove the shear component // traverse all periodic cells around the body, to see if any of them touches Vector3r halfSize=b->bound->max-b->bound->min; halfSize*=.5; Vector3r pmin,pmax; Vector3i i; for(i[0]=-1; i[0]<=1; i[0]++) for(i[1]=-1;i[1]<=1; i[1]++) for(i[2]=-1; i[2]<=1; i[2]++){ if(i[0]==0 && i[1]==0 && i[2]==0) continue; // middle; already rendered above Vector3r pos2=pos+Vector3r(cellSize[0]*i[0],cellSize[1]*i[1],cellSize[2]*i[2]); // shift, but without shear! pmin=pos2-halfSize; pmax=pos2+halfSize; if(pmin[0]<=cellSize[0] && pmax[0]>=0 && pmin[1]<=cellSize[1] && pmax[1]>=0 && pmin[2]<=cellSize[2] && pmax[2]>=0) { Vector3r pt=scene->cell->shearPt(pos2); if(pointClipped(pt)) continue; glLoadName(b->id); glPushMatrix(); glTranslatev(pt); glRotatef(aa.angle()*Mathr::RAD_TO_DEG,aa.axis()[0],aa.axis()[1],aa.axis()[2]); shapeDispatcher(b->shape,b->state,/*wire*/ true, viewInfo); glPopMatrix(); } } } glPopName(); } } #endif /* YADE_OPENGL */ trunk-2018.02b/pkg/common/OpenGLRenderer.hpp000066400000000000000000000137631324306050200205750ustar00rootroot00000000000000// © 2004 Olivier Galizzi // © 2008 Václav Šmilauer #pragma once #include #include #include #include struct GlExtraDrawer: public Serializable{ Scene* scene; virtual void render(); YADE_CLASS_BASE_DOC_ATTRS(GlExtraDrawer,Serializable,"Performing arbitrary OpenGL drawing commands; called from :yref:`OpenGLRenderer` (see :yref:`OpenGLRenderer.extraDrawers`) once regular rendering routines will have finished.\n\nThis class itself does not render anything, derived classes should override the *render* method.", ((bool,dead,false,,"Deactivate the object (on error/exception).")) ); }; REGISTER_SERIALIZABLE(GlExtraDrawer); class OpenGLRenderer : public Serializable { public: static const int numClipPlanes=3; bool pointClipped(const Vector3r& p); vector clipPlaneNormals; void setBodiesDispInfo(); static bool initDone; Vector3r viewDirection; // updated from GLViewer regularly GLViewInfo viewInfo; // update from GLView regularly Vector3r highlightEmission0; Vector3r highlightEmission1; // normalized saw signal with given periodicity, with values ∈ 〈0,1〉 */ Real normSaw(Real t, Real period){ Real xi=(t-period*((int)(t/period)))/period; /* normalized value, (0-1〉 */ return (xi<.5?2*xi:2-2*xi); } Real normSquare(Real t, Real period){ Real xi=(t-period*((int)(t/period)))/period; /* normalized value, (0-1〉 */ return (xi<.5?0:1); } void drawPeriodicCell(); void setBodiesRefSe3(); struct BodyDisp{ Vector3r pos; Quaternionr ori; bool isDisplayed; bool hidden; }; //! display data for individual bodies vector bodyDisp; void hide(Body::id_t id) {if ((unsigned int) id // stateFunctorNames, boundFunctorNames, shapeFunctorNames, geomFunctorNames, physFunctorNames; DECLARE_LOGGER; public : // updated after every call to render shared_ptr scene; void init(); void initgl(); void render(const shared_ptr& scene, Body::id_t selection=Body::id_t(-1)); void pyRender(){render(Omega::instance().getScene());} void renderDOF_ID(); void renderIPhys(); void renderIGeom(); void renderBound(); // called also to render selectable entitites; void renderShape(); void renderAllInteractionsWire(); template void setupDispatcher(const vector & names, DispatcherT & dispatcher); YADE_CLASS_BASE_DOC_ATTRS_CTOR_PY(OpenGLRenderer,Serializable,"Class responsible for rendering scene on OpenGL devices.", ((Vector3r,dispScale,((void)"disable scaling",Vector3r::Ones()),,"Artificially enlarge (scale) dispalcements from bodies' :yref:`reference positions` by this relative amount, so that they become better visible (independently in 3 dimensions). Disbled if (1,1,1).")) ((Real,rotScale,((void)"disable scaling",1.),,"Artificially enlarge (scale) rotations of bodies relative to their :yref:`reference orientation`, so the they are better visible.")) ((Vector3r,lightPos,Vector3r(75,130,0),,"Position of OpenGL light source in the scene.")) ((Vector3r,light2Pos,Vector3r(-130,75,30),,"Position of secondary OpenGL light source in the scene.")) ((Vector3r,lightColor,Vector3r(0.6,0.6,0.6),,"Per-color intensity of primary light (RGB).")) ((Vector3r,light2Color,Vector3r(0.5,0.5,0.1),,"Per-color intensity of secondary light (RGB).")) ((Vector3r,cellColor,Vector3r(1,1,0),,"Color of the periodic cell (RGB).")) ((Vector3r,bgColor,Vector3r(.2,.2,.2),,"Color of the background canvas (RGB)")) ((bool,wire,false,,"Render all bodies with wire only (faster)")) ((bool,light1,true,,"Turn light 1 on.")) ((bool,light2,true,,"Turn light 2 on.")) ((bool,dof,false,,"Show which degrees of freedom are blocked for each body")) ((bool,id,false,,"Show body id's")) ((bool,bound,false,,"Render body :yref:`Bound`")) ((bool,shape,true,,"Render body :yref:`Shape`")) ((bool,intrWire,false,,"If rendering interactions, use only wires to represent them.")) ((bool,intrGeom,false,,"Render :yref:`Interaction::geom` objects.")) ((bool,intrPhys,false,,"Render :yref:`Interaction::phys` objects")) ((bool,ghosts,true,,"Render objects crossing periodic cell edges by cloning them in multiple places (periodic simulations only).")) ((int,mask,((void)"draw everything",~0),,"Bitmask for showing only bodies where ((mask & :yref:`Body::mask`)!=0)")) ((Body::id_t,selId,Body::ID_NONE,,"Id of particle that was selected by the user.")) ((vector,clipPlaneSe3,vector(numClipPlanes,Se3r(Vector3r::Zero(),Quaternionr::Identity())),,"Position and orientation of clipping planes")) ((vector,clipPlaneActive,vector(numClipPlanes,false),,"Activate/deactivate respective clipping planes")) ((vector >,extraDrawers,,,"Additional rendering components (:yref:`GlExtraDrawer`).")) ((bool,intrAllWire,false,,"Draw wire for all interactions, blue for potential and green for real ones (mostly for debugging)")), /*ctor*/, /*py*/ .def("setRefSe3",&OpenGLRenderer::setBodiesRefSe3,"Make current positions and orientation reference for scaleDisplacements and scaleRotations.") .def("render",&OpenGLRenderer::pyRender,"Render the scene in the current OpenGL context.") .def("hideBody",&OpenGLRenderer::hide,(boost::python::arg("id")),"Hide body from id (see :yref:`OpenGLRenderer::showBody`)") .def("showBody",&OpenGLRenderer::show,(boost::python::arg("id")),"Make body visible (see :yref:`OpenGLRenderer::hideBody`)") ); }; REGISTER_SERIALIZABLE(OpenGLRenderer); trunk-2018.02b/pkg/common/PFacet.cpp000066400000000000000000001050041324306050200171050ustar00rootroot00000000000000/***************************************************************************** * Copyright (C) 2015 by Anna Effeindzourou anna.effeindzourou@gmail.com * * Copyright (C) 2015 by Bruno Chareyre bruno.chareyre@hmg.inpg.fr * * Copyright (C) 2015 by Klaus Thoeni klaus.thoeni@gmail.com * * This program is free software; it is licensed under the terms of the * * GNU General Public License v2 or later. See file LICENSE for details. * ******************************************************************************/ #include "PFacet.hpp" #ifdef YADE_OPENGL #include #endif //!################## IGeom Functors ##################### // Function used in order to calculate the projection of the sphere on the PFacet element. The function returns if the the porjection is on the inside the triangle and the barycentric coordinates of the projection P on PFacet the element. boost::tuple Ig2_Sphere_PFacet_ScGridCoGeom::projection( const shared_ptr& cm2, const State& state1) { const State* sphereSt = YADE_CAST(&state1); PFacet* Pfacet = YADE_CAST(cm2.get()); vector vertices; vertices.push_back(Pfacet->node1->state->pos); vertices.push_back(Pfacet->node2->state->pos); vertices.push_back(Pfacet->node3->state->pos); Vector3r center=vertices[0]+((vertices[2]-vertices[0])*(vertices[1]-vertices[0]).norm()+(vertices[1]-vertices[0])*(vertices[2]-vertices[0]).norm())/((vertices[1]-vertices[0]).norm()+(vertices[2]-vertices[1]).norm()+(vertices[0]-vertices[2]).norm()); Vector3r e[3] = {vertices[1]-vertices[0] ,vertices[2]-vertices[1] ,vertices[0]-vertices[2]}; Vector3r normal = e[0].cross(e[1])/((e[0].cross(e[1])).norm()); // Vector3r centerS=sphereSt->pos+shift2;//FIXME: periodicity? const Vector3r& centerS=sphereSt->pos; Vector3r cl=centerS-center; Real dist=normal.dot(cl); if (dist<0) {normal=-normal; dist=-dist;} Vector3r P =center+(cl - dist*normal); Vector3r v0 = vertices[1] - vertices[0]; Vector3r v1 = vertices[2] - vertices[0]; Vector3r v2 = P - vertices[0]; // Compute dot products Real dot00 = v0.dot(v0); Real dot01 = v0.dot(v1); Real dot02 = v0.dot(v2); Real dot11 = v1.dot(v1); Real dot12 = v1.dot(v2); // Compute the barycentric coordinates of the projection P Real invDenom = 1 / (dot00 * dot11 - dot01 * dot01); Real p1 = (dot11 * dot02 - dot01 * dot12) * invDenom; Real p2 = (dot00 * dot12 - dot01 * dot02) * invDenom; Real p3 = 1-p1-p2; // Check if P is in triangle bool isintriangle= (p1 > 0) && (p2 > 0) && (p1 + p2 < 1); return boost::make_tuple(P,isintriangle,p1,p2,p3); } bool Ig2_Sphere_PFacet_ScGridCoGeom::go( const shared_ptr& cm1, const shared_ptr& cm2, const State& state1, const State& state2, const Vector3r& shift2, const bool& force, const shared_ptr& c) { TIMING_DELTAS_START(); Sphere* sphere = YADE_CAST(cm1.get()); PFacet* Pfacet = YADE_CAST(cm2.get()); const State* sphereSt = YADE_CAST(&state1); Real sphereRadius = sphere->radius; Real PFacetradius=Pfacet->radius; vector vertices; vertices.push_back(Pfacet->node1->state->pos); vertices.push_back(Pfacet->node2->state->pos); vertices.push_back(Pfacet->node3->state->pos); Vector3r center=vertices[0]+((vertices[2]-vertices[0])*(vertices[1]-vertices[0]).norm()+(vertices[1]-vertices[0])*(vertices[2]-vertices[0]).norm())/((vertices[1]-vertices[0]).norm()+(vertices[2]-vertices[1]).norm()+(vertices[0]-vertices[2]).norm()); Vector3r e[3] = {vertices[1]-vertices[0] ,vertices[2]-vertices[1] ,vertices[0]-vertices[2]}; Vector3r normal = e[0].cross(e[1])/((e[0].cross(e[1])).norm()); // Vector3r centerS=sphereSt->pos+shift2;//FIXME: periodicity? const Vector3r& centerS=sphereSt->pos; Vector3r cl=centerS-center; Real dist=normal.dot(cl); shared_ptr scm; bool isNew = !(c->geom); if (c->geom) scm = YADE_PTR_CAST(c->geom); else scm = shared_ptr(new ScGridCoGeom()); if(scm->isDuplicate==2 && scm->trueInt!=c->id2)return true; //the contact will be deleted into the Law, no need to compute here. scm->isDuplicate=0; scm->trueInt=-1; if (std::abs(dist)>(PFacetradius+sphereRadius) && !c->isReal() && !force) { // no contact, but only if there was no previous contact; ortherwise, the constitutive law is responsible for setting Interaction::isReal=false TIMING_DELTAS_CHECKPOINT("Ig2_Sphere_PFacet_ScGridCoGeom"); return false; } if (dist<0) {normal=-normal; dist=-dist;} boost::tuple projectionres = projection(cm2, state1); Vector3r P = boost::get<0>(projectionres); bool isintriangle = boost::get<1>(projectionres); Real p1 = boost::get<2>(projectionres); Real p2 = boost::get<3>(projectionres); Real p3 = boost::get<4>(projectionres); shared_ptr GridList[3]={Pfacet->conn1,Pfacet->conn2,Pfacet->conn3}; // Check if the projection of the contact point is inside the triangle bool isconn1=((p1 > 0) && (p2 <= 0) && (p1 + p2 < 1))||((p1 > 0) && (p2 <= 0) && (p1 + p2 >= 1)); bool isconn2=((p1 > 0) && (p2 > 0) && (p1 + p2 >= 1))||((p1 <= 0) && (p2 > 0) && (p1 + p2 >= 1)); bool isconn3=((p1 <= 0) && (p2 > 0) && (p1 + p2 < 1))||((p1 <= 0) && (p2 <= 0) && (p1 + p2 < 1)); Real penetrationDepth=0; int connnum=-1; GridNode* GridNodeList[3]={ YADE_CAST(Pfacet->node1->shape.get()), YADE_CAST(Pfacet->node2->shape.get()),YADE_CAST(Pfacet->node3->shape.get())}; // If the contact projection is in the triangle, a search for an old contact is performed to export the information regarding the contact (phys) if (isintriangle){ if(isNew){ for (int unsigned i=0; i<3;i++){ for(int unsigned j=0;jpfacetList.size();j++){ if(GridNodeList[i]->pfacetList[j]->getId()!=c->id2){ boost::tuple projectionPrev = projection(GridNodeList[i]->pfacetList[j]->shape,state1); bool isintrianglePrev = boost::get<1>(projectionPrev); if(!isintrianglePrev){ //if(!isintrianglePrev) const shared_ptr intr = scene->interactions->find(c->id1,GridNodeList[i]->pfacetList[j]->getId()); if( intr && intr->isReal() ){// if an interaction exist between the sphere and the previous pfacet, import parameters. // cout<<"Copying contact with pfacet geom and phys from "<id1<<"-"<id2<<" to here ("<id1<<"-"<id2<<")"<(intr->geom); c->geom=scm; c->phys=intr->phys; c->iterMadeReal=intr->iterMadeReal;// iteration from the time when the contact became real scm->trueInt=c->id2; scm->isDuplicate=2; //command the old contact deletion. isNew=0; break; } } } } } } } else{ // identification of the cylinder possibly in contact with the sphere if (isconn1) connnum=0; if (isconn2) connnum=1; if (isconn3) connnum=2; // check if the identified cylinder previously is in contact with the sphere if (connnum!=-1){ //verify if there is a contact between the a neighbouring PFacet, avoid double contacts for (int unsigned i=0; i<3;i++){ for(int unsigned j=0;jpfacetList.size();j++){ if(GridNodeList[i]->pfacetList[j]->getId()!=c->id2){ boost::tuple projectionPrev = projection(GridNodeList[i]->pfacetList[j]->shape,state1); bool isintrianglePrev = boost::get<1>(projectionPrev); if(isintrianglePrev){ const shared_ptr intr = scene->interactions->find(c->id1,GridNodeList[i]->pfacetList[j]->getId()); if( intr && intr->isReal() ){// if an interaction exist between the sphere and the previous pfacet, import parameters. if (isNew){ return false;} else { scm->isDuplicate=1 ; scm->trueInt=-1 ; return true; } } } } } } //SPhere-cylinder contact const State* sphereSt = YADE_CAST(&state1); GridConnection* gridCo = YADE_CAST(GridList[connnum]->shape.get()); GridNode* gridNo1 = YADE_CAST(gridCo->node1->shape.get()); GridNode* gridNo2 = YADE_CAST(gridCo->node2->shape.get()); State* gridNo1St = YADE_CAST(gridCo->node1->state.get()); State* gridNo2St = YADE_CAST(gridCo->node2->state.get()); Vector3r segt = gridCo->getSegment(); Real len = gridCo->getLength(); Vector3r spherePos = sphereSt->pos; Vector3r branch = spherePos - gridNo1St->pos; Vector3r branchN = spherePos - gridNo2St->pos; for(int i=0;i<3;i++){ if(std::abs(branch[i])<1e-14) branch[i]=0.0; if(std::abs(branchN[i])<1e-14) branchN[i]=0.0; } Real relPos = branch.dot(segt)/(len*len); bool SGr=true; if(relPos<=0){ // if the sphere projection is BEFORE the segment ... if(gridNo1->ConnList.size()>1){// if the node is not an extremity of the Grid (only one connection) for(int unsigned i=0;iConnList.size();i++){ // ... loop on all the Connections of the same Node ... GridConnection* GC = (GridConnection*)gridNo1->ConnList[i]->shape.get(); if(GC==gridCo)continue;// self comparison. Vector3r segtCandidate1 = GC->node1->state->pos - gridNo1St->pos; // (be sure of the direction of segtPrev to compare relPosPrev.) Vector3r segtCandidate2 = GC->node2->state->pos - gridNo1St->pos; Vector3r segtPrev = segtCandidate1.norm()>segtCandidate2.norm() ? segtCandidate1:segtCandidate2; for(int j=0;j<3;j++){ if(std::abs(segtPrev[j])<1e-14) segtPrev[j]=0.0; } Real relPosPrev = (branch.dot(segtPrev))/(segtPrev.norm()*segtPrev.norm()); // ... and check whether the sphere projection is before the neighbours connections too. if(relPosPrev<=0){//if the sphere projection is outside both the current Connection AND this neighbouring connection, then create the interaction if the neighbour did not already do it before. for(int unsigned j=0;jpfacetList.size();j++){ if(GC->pfacetList[j]->getId()!=c->id2){ const shared_ptr intr = scene->interactions->find(c->id1,GC->pfacetList[j]->getId()); if(intr && intr->isReal()){ shared_ptr intrGeom=YADE_PTR_CAST(intr->geom); if(!(intrGeom->isDuplicate==1)){ //skip contact. if (isNew) {SGr=false;} else {scm->isDuplicate=1;}/*cout<<"Declare "<id1<<"-"<id2<<" as duplicated."<id1<<"-"<id2<<" HAVE to be copied and deleted NOW."<isDuplicate=1; scm->trueInt=-1; //trueInt id de l'objet avec lequel il y a un contact -1 = rien faire return true; } } } } } //Exactly the same but in the case the sphere projection is AFTER the segment. else if(relPos>=1){ if(gridNo2->ConnList.size()>1){ for(int unsigned i=0;iConnList.size();i++){ GridConnection* GC = (GridConnection*)gridNo2->ConnList[i]->shape.get(); if(GC==gridCo)continue;// self comparison. Vector3r segtCandidate1 = GC->node1->state->pos - gridNo2St->pos; Vector3r segtCandidate2 = GC->node2->state->pos - gridNo2St->pos; Vector3r segtNext = segtCandidate1.norm()>segtCandidate2.norm() ? segtCandidate1:segtCandidate2; for(int j=0;j<3;j++){ if(std::abs(segtNext[j])<1e-14) segtNext[j]=0.0; } Real relPosNext = (branchN.dot(segtNext))/(segtNext.norm()*segtNext.norm()); if(relPosNext<=0){ //if the sphere projection is outside both the current Connection AND this neighbouring connection, then create the interaction if the neighbour did not already do it before. for(int unsigned j=0;jpfacetList.size();j++){ if(GC->pfacetList[j]->getId()!=c->id2){ const shared_ptr intr = scene->interactions->find(c->id1,GC->pfacetList[j]->getId()); if(intr && intr->isReal()){ shared_ptr intrGeom=YADE_PTR_CAST(intr->geom); if(!(intrGeom->isDuplicate==1)){ if (isNew) SGr=false; else scm->isDuplicate=1;/*cout<<"Declare "<id1<<"-"<id2<<" as duplicated."<id1<<"-"<id2<<" HAVE to be copied and deleted NOW."<isDuplicate=1 ; scm->trueInt=-1 ; return true; } } } } } else if (relPos<=0.5){ if(gridNo1->ConnList.size()>1){// if the node is not an extremity of the Grid (only one connection) for(int unsigned i=0;iConnList.size();i++){ // ... loop on all the Connections of the same Node ... GridConnection* GC = (GridConnection*)gridNo1->ConnList[i]->shape.get(); if(GC==gridCo)continue;// self comparison. Vector3r segtCandidate1 = GC->node1->state->pos - gridNo1St->pos; // (be sure of the direction of segtPrev to compare relPosPrev.) Vector3r segtCandidate2 = GC->node2->state->pos - gridNo1St->pos; Vector3r segtPrev = segtCandidate1.norm()>segtCandidate2.norm() ? segtCandidate1:segtCandidate2; for(int j=0;j<3;j++){ if(std::abs(segtPrev[j])<1e-14) segtPrev[j]=0.0; } Real relPosPrev = (branch.dot(segtPrev))/(segtPrev.norm()*segtPrev.norm()); if(relPosPrev<=0){ //the sphere projection is inside the current Connection and outide this neighbour connection. for(int unsigned j=0;jpfacetList.size();j++){ if(GC->pfacetList[j]->getId()!=c->id2){ const shared_ptr intr = scene->interactions->find(c->id1,GC->pfacetList[j]->getId()); if( intr && intr->isReal() ){// if an ineraction exist between the sphere and the previous connection, import parameters. // cout<<"1Copying contact geom and phys from "<id1<<"-"<id2<<" to here ("<id1<<"-"<id2<<")"<(intr->geom); if(isNew){ c->geom=scm; c->phys=intr->phys; c->iterMadeReal=intr->iterMadeReal; } scm->trueInt=c->id2; scm->isDuplicate=2; //command the old contact deletion. isNew=0; break; } } } } } } } else if (relPos>0.5){ if(gridNo2->ConnList.size()>1){ for(int unsigned i=0;iConnList.size();i++){ GridConnection* GC = (GridConnection*)gridNo2->ConnList[i]->shape.get(); if(GC==gridCo)continue;// self comparison. Vector3r segtCandidate1 = GC->node1->state->pos - gridNo2St->pos; Vector3r segtCandidate2 = GC->node2->state->pos - gridNo2St->pos; Vector3r segtNext = segtCandidate1.norm()>segtCandidate2.norm() ? segtCandidate1:segtCandidate2; for(int j=0;j<3;j++){ if(std::abs(segtNext[j])<1e-14) segtNext[j]=0.0; } Real relPosNext = (branchN.dot(segtNext))/(segtNext.norm()*segtNext.norm()); if(relPosNext<=0){ //the sphere projection is inside the current Connection and outside this neighbour connection. for(int unsigned j=0;jpfacetList.size();j++){ if(GC->pfacetList[j]->getId()!=c->id2){ const shared_ptr intr = scene->interactions->find(c->id1,GC->pfacetList[j]->getId()); if( intr && intr->isReal() ){// if an ineraction exist between the sphere and the previous connection, import parameters. scm=YADE_PTR_CAST(intr->geom); if(isNew){ c->geom=scm; c->phys=intr->phys; c->iterMadeReal=intr->iterMadeReal; } scm->trueInt=c->id2; scm->isDuplicate=2; //command the old contact deletion. break; } } } } } } } if(SGr){ relPos=relPos<0?0:relPos; //min value of relPos : 0 relPos=relPos>1?1:relPos; //max value of relPos : 1 Vector3r fictiousPos=gridNo1St->pos+relPos*segt; Vector3r branchF = fictiousPos - spherePos; Real dist = branchF.norm(); bool SG= !(isNew && (dist > (sphere->radius + gridCo->radius))); if(SG){ // Create the geometry : if(isNew) c->geom=scm; scm->radius1=sphere->radius; scm->radius2=gridCo->radius; scm->id3=gridCo->node1->getId(); scm->id4=gridCo->node2->getId(); scm->relPos=relPos; Vector3r normal=branchF/dist; scm->penetrationDepth = sphere->radius+gridCo->radius-dist; scm->fictiousState.pos = fictiousPos; scm->contactPoint = spherePos + normal*(scm->radius1 - 0.5*scm->penetrationDepth); scm->fictiousState.vel = (1-relPos)*gridNo1St->vel + relPos*gridNo2St->vel; scm->fictiousState.angVel = ((1-relPos)*gridNo1St->angVel + relPos*gridNo2St->angVel).dot(segt/len)*segt/len //twist part : interpolated + segt.cross(gridNo2St->vel - gridNo1St->vel);// non-twist part : defined from nodes velocities scm->precompute(state1,scm->fictiousState,scene,c,normal,isNew,shift2,true);//use sphere-sphere precompute (with a virtual sphere) return true; } } } else{//taking into account the shadow zone for (int unsigned i=0; i<3;i++){ for(int unsigned j=0;jpfacetList.size();j++){ if(GridNodeList[i]->pfacetList[j]->getId()!=c->id2){ boost::tuple projectionPrev = projection(GridNodeList[i]->pfacetList[j]->shape,state1); bool isintrianglePrev = boost::get<1>(projectionPrev); if(!isintrianglePrev){ //if the sphere projection is outside both the current PFacet AND this neighbouring PFacet, then create the interaction if the neighbour did not already do it before. const shared_ptr intr = scene->interactions->find(c->id1,GridNodeList[i]->pfacetList[j]->getId()); if(intr && intr->isReal()){ shared_ptr intrGeom=YADE_PTR_CAST(intr->geom); if(!(intrGeom->isDuplicate==1)){ //skip contact. if (isNew) { return false;} else { scm->isDuplicate=1; }/*cout<<"Declare "<id1<<"-"<id2<<" as duplicated."<id1<<"-"<id2<<" HAVE to be copied and deleted NOW."<isDuplicate=1; //scm->isDuplicate=1; scm->trueInt=-1; //trueInt id de l'objet avec lequel il y a un contact -1 = rien faire return true; } } } } } } } if(isintriangle){ penetrationDepth = sphereRadius + PFacetradius - std::abs(dist); normal.normalize(); if (penetrationDepth>0 || c->isReal() ){ if(isNew) c->geom=scm; scm->radius1=sphereRadius; scm->radius2=PFacetradius; scm->id3=Pfacet->node1->getId(); scm->id4=Pfacet->node2->getId(); scm->id5=Pfacet->node3->getId(); scm->weight[0]=p1; scm->weight[1]=p2; scm->weight[2]=p3; scm->penetrationDepth = penetrationDepth; scm->fictiousState.pos = P; scm->contactPoint = centerS - (PFacetradius-0.5*penetrationDepth)*normal; scm->fictiousState.vel = (p1*Pfacet->node1->state->vel + p2*Pfacet->node2->state->vel +p3*Pfacet->node3->state->vel); scm->fictiousState.angVel = (p1*Pfacet->node1->state->angVel + p2*Pfacet->node2->state->angVel+ p3*Pfacet->node3->state->angVel); scm->precompute(state1,scm->fictiousState,scene,c,-normal,isNew,shift2,true);//use sphere-sphere precompute (with a virtual sphere) TIMING_DELTAS_CHECKPOINT("Ig2_Sphere_PFacet_ScGridCoGeom"); return true; } } TIMING_DELTAS_CHECKPOINT("Ig2_Sphere_PFacet_ScGridCoGeom"); return false; } bool Ig2_Sphere_PFacet_ScGridCoGeom::goReverse( const shared_ptr& cm1, const shared_ptr& cm2, const State& state1, const State& state2, const Vector3r& shift2, const bool& force, const shared_ptr& c) { c->swapOrder(); return go(cm2,cm1,state2,state1,-shift2,force,c); } YADE_PLUGIN((Ig2_Sphere_PFacet_ScGridCoGeom)); bool Ig2_GridConnection_PFacet_ScGeom::go( const shared_ptr& cm1, const shared_ptr& cm2, const State& state1, const State& state2, const Vector3r& shift2, const bool& force, const shared_ptr& c) { GridConnection* gridCo = YADE_CAST(cm1.get()); PFacet* Pfacet = YADE_CAST(cm2.get()); if(gridCo->node1==Pfacet->node1 || gridCo->node1==Pfacet->node2|| gridCo->node1==Pfacet->node3 || gridCo->node2==Pfacet->node1 || gridCo->node2==Pfacet->node2|| gridCo->node2==Pfacet->node3){return false;} Body::id_t idNode1=gridCo->node1->getId(); Body::id_t idNode2=gridCo->node2->getId(); Body::id_t ids2[3]={Pfacet->conn1->getId(),Pfacet->conn2->getId(),Pfacet->conn3->getId()}; Body::id_t id1=c->id1; Body::id_t id2=c->id2; if (!scene->interactions->found(idNode1,id2)){ shared_ptr scm1 (new Interaction(idNode1,id2)); scene->interactions->insert(scm1); } if (!scene->interactions->found(idNode2,id2)){ shared_ptr scm2 (new Interaction(idNode2,id2)); scene->interactions->insert(scm2); } for (int i=0; i<3; i++){ int entier=i; ostringstream oss; string chaine = "scm"; oss << chaine << entier; string chaine1=oss.str(); if (!scene->interactions->found(id1,ids2[i])){ shared_ptr chaine1 (new Interaction(id1,ids2[i])); scene->interactions->insert(chaine1); } } return(false); } bool Ig2_GridConnection_PFacet_ScGeom::goReverse( const shared_ptr& cm1, const shared_ptr& cm2, const State& state1, const State& state2, const Vector3r& shift2, const bool& force, const shared_ptr& c) { return go(cm1,cm2,state2,state1,-shift2,force,c); } YADE_PLUGIN((Ig2_GridConnection_PFacet_ScGeom)); bool Ig2_PFacet_PFacet_ScGeom::go( const shared_ptr& cm1, const shared_ptr& cm2, const State& state1, const State& state2, const Vector3r& shift2, const bool& force, const shared_ptr& c) { PFacet* Pfacet1 = YADE_CAST(cm1.get()); PFacet* Pfacet2 = YADE_CAST(cm2.get()); Body::id_t id1=c->id1; Body::id_t id2=c->id2; if(Pfacet1->node1==Pfacet2->node1 || Pfacet1->node1==Pfacet2->node2|| Pfacet1->node1==Pfacet2->node3 || Pfacet1->node2==Pfacet2->node1 || Pfacet1->node2==Pfacet2->node2|| Pfacet1->node2==Pfacet2->node3 || Pfacet1->node3==Pfacet2->node1 || Pfacet1->node3==Pfacet2->node2|| Pfacet1->node3==Pfacet2->node3 ){ return false;} boost::tuple p1 = Ig2_Sphere_PFacet_ScGridCoGeom::projection(cm1,* Pfacet2->node1->state); boost::tuple p2 = Ig2_Sphere_PFacet_ScGridCoGeom::projection(cm1,* Pfacet2->node2->state); boost::tuple p3 = Ig2_Sphere_PFacet_ScGridCoGeom::projection(cm1,* Pfacet2->node3->state); boost::tuple p4 = Ig2_Sphere_PFacet_ScGridCoGeom::projection(cm2,* Pfacet1->node1->state); boost::tuple p5 = Ig2_Sphere_PFacet_ScGridCoGeom::projection(cm2,* Pfacet1->node2->state); boost::tuple p6 = Ig2_Sphere_PFacet_ScGridCoGeom::projection(cm2,* Pfacet1->node3->state); bool isintriangle1 = boost::get<1>(p1); bool isintriangle2 = boost::get<1>(p2); bool isintriangle3 = boost::get<1>(p3); bool isintriangle4 = boost::get<1>(p4); bool isintriangle5 = boost::get<1>(p5); bool isintriangle6 = boost::get<1>(p6); bool istriangleNodes2P1 = (isintriangle1==true) && (isintriangle2==true) && (isintriangle3==true); bool istriangleNodes1P2 = (isintriangle4==true) && (isintriangle5==true) && (isintriangle6==true); if(istriangleNodes1P2 ){ Body::id_t idNode11=Pfacet1->node1->getId(); Body::id_t idNode12=Pfacet1->node2->getId(); Body::id_t idNode13=Pfacet1->node3->getId(); if (!scene->interactions->found(idNode11,id2)){ shared_ptr scm1 (new Interaction(idNode11,id2)); scene->interactions->insert(scm1); } if (!scene->interactions->found(idNode12,id2)){ shared_ptr scm2 (new Interaction(idNode12,id2)); scene->interactions->insert(scm2); } if (!scene->interactions->found(idNode13,id2)){ shared_ptr scm3 (new Interaction(idNode13,id2)); scene->interactions->insert(scm3); } } else if(istriangleNodes2P1 ){ Body::id_t idNode21=Pfacet2->node1->getId(); Body::id_t idNode22=Pfacet2->node2->getId(); Body::id_t idNode23=Pfacet2->node3->getId(); if (!scene->interactions->found(idNode21,id1)){ shared_ptr scm1 (new Interaction(idNode21,id1)); scene->interactions->insert(scm1); } if (!scene->interactions->found(idNode22,id1)){ shared_ptr scm2 (new Interaction(idNode22,id1)); scene->interactions->insert(scm2); } if (!scene->interactions->found(idNode23,id1)){ shared_ptr scm3 (new Interaction(idNode23,id1)); scene->interactions->insert(scm3); } } else{ vector vertices1={Pfacet1->node1->state->pos,Pfacet1->node2->state->pos,Pfacet1->node3->state->pos}; vector vertices2={Pfacet2->node1->state->pos,Pfacet2->node2->state->pos,Pfacet2->node3->state->pos}; GridConnection* gridCo1P1 = (GridConnection*) Pfacet1->conn1->shape.get(); GridConnection* gridCo2P1 = (GridConnection*) Pfacet1->conn2->shape.get(); GridConnection* gridCo3P1 = (GridConnection*) Pfacet1->conn3->shape.get(); GridConnection* gridCo1P2 = (GridConnection*) Pfacet2->conn1->shape.get(); GridConnection* gridCo2P2 = (GridConnection*) Pfacet2->conn2->shape.get(); GridConnection* gridCo3P2 = (GridConnection*) Pfacet2->conn3->shape.get(); State* gridNo1StgridCo1P1 = YADE_CAST(gridCo1P1->node1->state.get()); State* gridNo2StgridCo1P1 = YADE_CAST(gridCo1P1->node2->state.get()); State* gridNo1StgridCo2P1 = YADE_CAST(gridCo2P1->node1->state.get()); State* gridNo2StgridCo2P1 = YADE_CAST(gridCo2P1->node2->state.get()); State* gridNo1StgridCo3P1 = YADE_CAST(gridCo3P1->node1->state.get()); State* gridNo2StgridCo3P1 = YADE_CAST(gridCo3P1->node2->state.get()); State* gridNo1StgridCo1P2 = YADE_CAST(gridCo1P2->node1->state.get()); State* gridNo2StgridCo1P2 = YADE_CAST(gridCo1P2->node2->state.get()); State* gridNo1StgridCo2P2 = YADE_CAST(gridCo2P2->node1->state.get()); State* gridNo2StgridCo2P2 = YADE_CAST(gridCo2P2->node2->state.get()); State* gridNo1StgridCo3P2 = YADE_CAST(gridCo3P2->node1->state.get()); State* gridNo2StgridCo3P2 = YADE_CAST(gridCo3P2->node2->state.get()); Vector3r seg11=(gridNo1StgridCo1P1->pos - gridNo2StgridCo1P1->pos)/(gridNo1StgridCo1P1->pos - gridNo2StgridCo1P1->pos).norm(); Vector3r seg12=(gridNo1StgridCo2P1->pos - gridNo2StgridCo2P1->pos)/(gridNo1StgridCo2P1->pos - gridNo2StgridCo2P1->pos).norm() ; Vector3r seg13=(gridNo1StgridCo3P1->pos - gridNo2StgridCo3P1->pos)/(gridNo1StgridCo3P1->pos - gridNo2StgridCo3P1->pos).norm(); Vector3r seg21=(gridNo1StgridCo1P2->pos - gridNo2StgridCo1P2->pos)/(gridNo1StgridCo1P2->pos - gridNo2StgridCo1P2->pos).norm(); Vector3r seg22=(gridNo1StgridCo2P2->pos - gridNo2StgridCo2P2->pos)/(gridNo1StgridCo2P2->pos - gridNo2StgridCo2P2->pos).norm(); Vector3r seg23=(gridNo1StgridCo3P2->pos - gridNo2StgridCo3P2->pos)/(gridNo1StgridCo3P2->pos - gridNo2StgridCo3P2->pos).norm(); Real normal1seg21seg11= seg21.dot(seg11); Real normal1seg21seg12= seg21.dot(seg12); Real normal1seg21seg13= seg21.dot(seg13); Real normal1seg22seg11= seg22.dot(seg11); Real normal1seg22seg12= seg22.dot(seg12); Real normal1seg22seg13= seg22.dot(seg13); Real normal1seg23seg11= seg23.dot(seg11); Real normal1seg23seg12= seg23.dot(seg12); Real normal1seg23seg13= seg23.dot(seg13); Body::id_t ids1[3]={Pfacet1->conn1->getId(),Pfacet1->conn2->getId(),Pfacet1->conn3->getId()}; Body::id_t ids2[3]={Pfacet2->conn1->getId(),Pfacet2->conn2->getId(),Pfacet2->conn3->getId()}; Body::id_t c1 =-1; Body::id_t c2 =-1; if(std::abs(normal1seg21seg11)==1){c1=ids2[0];c2=ids1[0];} if(std::abs(normal1seg21seg12)==1){c1=ids2[0];c2=ids1[1];} if(std::abs(normal1seg21seg13)==1){c1=ids2[0];c2=ids1[2];} if(std::abs(normal1seg22seg11)==1){c1=ids2[1];c2=ids1[0];} if(std::abs(normal1seg22seg12)==1){c1=ids2[1];c2=ids1[1];} if(std::abs(normal1seg22seg13)==1){c1=ids2[1];c2=ids1[2];} if(std::abs(normal1seg23seg11)==1){c1=ids2[2];c2=ids1[0];} if(std::abs(normal1seg23seg12)==1){c1=ids2[2];c2=ids1[1];} if(std::abs(normal1seg23seg13)==1){c1=ids2[2];c2=ids1[2];} if( (isintriangle1==false) && (isintriangle2==false) && (isintriangle3==false) && (isintriangle4==false) && (isintriangle5==false) && (isintriangle6==false)){ if((c1!=-1)and (c2!=-1) and(Body::byId(c1)->getGroupMask()!=0)and(Body::byId(c2)->getGroupMask()!=0)){ ostringstream oss; string chaine = "scm"; if (!scene->interactions->found(c1,c2)){ shared_ptr chaine (new Interaction(c1,c2)); scene->interactions->insert(chaine); } } else{ for (int i=0; i<3; i++){ for (int j=0; j<3; j++){ int entier = j+i*3; ostringstream oss; string chaine = "scm"; oss << chaine << entier; string chaine1=oss.str(); if (!scene->interactions->found(ids1[i],ids2[j])){ if((Body::byId(ids1[i])->getGroupMask()!=0)and(Body::byId(ids2[j])->getGroupMask()!=0)){ shared_ptr chaine1 (new Interaction(ids1[i],ids2[j])); scene->interactions->insert(chaine1); } } } } } } else{ Body::id_t idNode11=Pfacet1->node1->getId(); Body::id_t idNode12=Pfacet1->node2->getId(); Body::id_t idNode13=Pfacet1->node3->getId(); if (!scene->interactions->found(idNode11,id2)){ shared_ptr scm1 (new Interaction(idNode11,id2)); scene->interactions->insert(scm1); } if (!scene->interactions->found(idNode12,id2)){ shared_ptr scm2 (new Interaction(idNode12,id2)); scene->interactions->insert(scm2); } if (!scene->interactions->found(idNode13,id2)){ shared_ptr scm3 (new Interaction(idNode13,id2)); scene->interactions->insert(scm3); } Body::id_t idNode21=Pfacet2->node1->getId(); Body::id_t idNode22=Pfacet2->node2->getId(); Body::id_t idNode23=Pfacet2->node3->getId(); if (!scene->interactions->found(idNode21,id1)){ shared_ptr scm1 (new Interaction(idNode21,id1)); scene->interactions->insert(scm1); } if (!scene->interactions->found(idNode22,id1)){ shared_ptr scm2 (new Interaction(idNode22,id1)); scene->interactions->insert(scm2); } if (!scene->interactions->found(idNode23,id1)){ shared_ptr scm3 (new Interaction(idNode23,id1)); scene->interactions->insert(scm3); } Body::id_t ids1[3]={Pfacet1->conn1->getId(),Pfacet1->conn2->getId(),Pfacet1->conn3->getId()}; Body::id_t ids2[3]={Pfacet2->conn1->getId(),Pfacet2->conn2->getId(),Pfacet2->conn3->getId()}; for (int i=0; i<3; i++){ for (int j=0; j<3; j++){ int entier = j+i*3; ostringstream oss; string chaine = "scm"; oss << chaine << entier; string chaine1=oss.str(); if (!scene->interactions->found(ids1[i],ids2[j])){ if((Body::byId(ids1[i])->getGroupMask()!=0)and(Body::byId(ids2[j])->getGroupMask()!=0)){ shared_ptr chaine1 (new Interaction(ids1[i],ids2[j])); scene->interactions->insert(chaine1); } } } } } } return(false); } bool Ig2_PFacet_PFacet_ScGeom::goReverse( const shared_ptr& cm1, const shared_ptr& cm2, const State& state1, const State& state2, const Vector3r& shift2, const bool& force, const shared_ptr& c) { return go(cm1,cm2,state2,state1,-shift2,force,c); } YADE_PLUGIN((Ig2_PFacet_PFacet_ScGeom)); /********* Wall + Sphere **********/ bool Ig2_Wall_PFacet_ScGeom::go(const shared_ptr& cm1, const shared_ptr& cm2, const State& state1, const State& state2, const Vector3r& shift2, const bool& force, const shared_ptr& c){ PFacet* Pfacet = YADE_CAST(cm2.get()); Body::id_t idNode1=Pfacet->node1->getId(); Body::id_t idNode2=Pfacet->node2->getId(); Body::id_t idNode3=Pfacet->node3->getId(); Body::id_t id1=c->id1; if (!scene->interactions->found(id1,idNode1)){ shared_ptr scm1 (new Interaction(id1,idNode1)); scene->interactions->insert(scm1); } if (!scene->interactions->found(id1,idNode2)){ shared_ptr scm2 (new Interaction(id1,idNode2)); scene->interactions->insert(scm2); } if (!scene->interactions->found(id1,idNode3)){ shared_ptr scm3 (new Interaction(id1,idNode3)); scene->interactions->insert(scm3); } return(false); } YADE_PLUGIN((Ig2_Wall_PFacet_ScGeom)); //!################## Bounds ##################### void Bo1_PFacet_Aabb::go(const shared_ptr& cm, shared_ptr& bv, const Se3r& se3, const Body* b) { PFacet* Pfacet = YADE_CAST(cm.get()); if(!bv){ bv=shared_ptr(new Aabb); } Aabb* aabb=static_cast(bv.get()); Vector3r O = Pfacet->node1->state->pos; Vector3r O2 =Pfacet->node2->state->pos; Vector3r O3 =Pfacet->node3->state->pos; if(!scene->isPeriodic){ for (int k=0;k<3;k++){ aabb->min[k] = min(min(O[k],O2[k]),O3[k]) - Pfacet->radius; aabb->max[k] = max(max(O[k],O2[k]),O3[k]) + Pfacet->radius; } return; } else{ O = scene->cell->unshearPt(O); O2 = scene->cell->unshearPt(O2); O3= scene->cell->unshearPt(O3); Vector3r T=scene->cell->hSize*Pfacet->cellDist.cast(); O = O + T; O2 = O2 + T; O3 = O3 + T; for (int k=0;k<3;k++){ aabb->min[k] = min(min(O[k],O2[k]),O3[k]) - Pfacet->radius; aabb->max[k] = max(max(O[k],O2[k]),O3[k]) + Pfacet->radius; } } } YADE_PLUGIN((Bo1_PFacet_Aabb)); trunk-2018.02b/pkg/common/PFacet.hpp000066400000000000000000000145601324306050200171200ustar00rootroot00000000000000/***************************************************************************** * Copyright (C) 2015 by Anna Effeindzourou anna.effeindzourou@gmail.com * * Copyright (C) 2015 by Bruno Chareyre bruno.chareyre@hmg.inpg.fr * * Copyright (C) 2015 by Klaus Thoeni klaus.thoeni@gmail.com * * This program is free software; it is licensed under the terms of the * * GNU General Public License v2 or later. See file LICENSE for details. * ******************************************************************************/ #pragma once #include #include #include #include #include #include #include #include #include #include #include #include #ifdef YADE_OPENGL #include #endif //!################## IGeom Functors ################## //! O/ class Ig2_Sphere_PFacet_ScGridCoGeom: public Ig2_Sphere_GridConnection_ScGridCoGeom{ public : boost::tuple projection( const shared_ptr& cm2, const State& state1); virtual bool go(const shared_ptr& cm1, const shared_ptr& cm2, const State& state1, const State& state2, const Vector3r& shift2, const bool& force, const shared_ptr& c); virtual bool goReverse( const shared_ptr& cm1, const shared_ptr& cm2, const State& state1, const State& state2, const Vector3r& shift2, const bool& force, const shared_ptr& c); YADE_CLASS_BASE_DOC_ATTRS(Ig2_Sphere_PFacet_ScGridCoGeom,Ig2_Sphere_GridConnection_ScGridCoGeom,"Create/update a :yref:`ScGridCoGeom` instance representing intersection of :yref:`PFacet` and :yref:`Sphere`.", ((Real,shrinkFactor,((void)"no shrinking",0),,"The radius of the inscribed circle of the facet is decreased by the value of the sphere's radius multipled by *shrinkFactor*. From the definition of contact point on the surface made of facets, the given surface is not continuous and becomes in effect surface covered with triangular tiles, with gap between the separate tiles equal to the sphere's radius multiplied by 2×*shrinkFactor*. If zero, no shrinking is done.")) ); DECLARE_LOGGER; FUNCTOR2D(Sphere,PFacet); DEFINE_FUNCTOR_ORDER_2D(Sphere,PFacet); }; REGISTER_SERIALIZABLE(Ig2_Sphere_PFacet_ScGridCoGeom); class Ig2_GridConnection_PFacet_ScGeom: public Ig2_Sphere_GridConnection_ScGridCoGeom{ public : virtual bool go(const shared_ptr& cm1, const shared_ptr& cm2, const State& state1, const State& state2, const Vector3r& shift2, const bool& force, const shared_ptr& c); virtual bool goReverse( const shared_ptr& cm1, const shared_ptr& cm2, const State& state1, const State& state2, const Vector3r& shift2, const bool& force, const shared_ptr& c); YADE_CLASS_BASE_DOC_ATTRS(Ig2_GridConnection_PFacet_ScGeom,Ig2_Sphere_GridConnection_ScGridCoGeom,"Create/update a :yref:`ScGeom` instance representing intersection of :yref:`Facet` and :yref:`GridConnection`.", ((Real,shrinkFactor,((void)"no shrinking",0),,"The radius of the inscribed circle of the facet is decreased by the value of the sphere's radius multipled by *shrinkFactor*. From the definition of contact point on the surface made of facets, the given surface is not continuous and becomes in effect surface covered with triangular tiles, with gap between the separate tiles equal to the sphere's radius multiplied by 2×*shrinkFactor*. If zero, no shrinking is done.")) ); DECLARE_LOGGER; FUNCTOR2D(GridConnection,PFacet); DEFINE_FUNCTOR_ORDER_2D(GridConnection,PFacet); }; REGISTER_SERIALIZABLE(Ig2_GridConnection_PFacet_ScGeom); //! O/ class Ig2_PFacet_PFacet_ScGeom: public Ig2_Sphere_PFacet_ScGridCoGeom{ public : virtual bool go(const shared_ptr& cm1, const shared_ptr& cm2, const State& state1, const State& state2, const Vector3r& shift2, const bool& force, const shared_ptr& c); virtual bool goReverse( const shared_ptr& cm1, const shared_ptr& cm2, const State& state1, const State& state2, const Vector3r& shift2, const bool& force, const shared_ptr& c); YADE_CLASS_BASE_DOC_ATTRS(Ig2_PFacet_PFacet_ScGeom,Ig2_Sphere_PFacet_ScGridCoGeom,"Create/update a :yref:`ScGridCoGeom` instance representing intersection of :yref:`Facet` and :yref:`Sphere`.", ((Real,shrinkFactor,((void)"no shrinking",0),,"The radius of the inscribed circle of the facet is decreased by the value of the sphere's radius multipled by *shrinkFactor*. From the definition of contact point on the surface made of facets, the given surface is not continuous and becomes in effect surface covered with triangular tiles, with gap between the separate tiles equal to the sphere's radius multiplied by 2×*shrinkFactor*. If zero, no shrinking is done.")) ); DECLARE_LOGGER; FUNCTOR2D(PFacet,PFacet); DEFINE_FUNCTOR_ORDER_2D(PFacet,PFacet); }; REGISTER_SERIALIZABLE(Ig2_PFacet_PFacet_ScGeom); /********* Wall + Sphere **********/ class Ig2_Wall_PFacet_ScGeom: public Ig2_Wall_Sphere_ScGeom{ public: virtual bool go(const shared_ptr& cm1, const shared_ptr& cm2, const State& state1, const State& state2, const Vector3r& shift2, const bool& force, const shared_ptr& c); YADE_CLASS_BASE_DOC_ATTRS(Ig2_Wall_PFacet_ScGeom,Ig2_Wall_Sphere_ScGeom,"Create/update a :yref:`ScGeom` instance representing intersection of :yref:`Wall` and :yref:`PFacet`.", ); FUNCTOR2D(Wall,PFacet); DEFINE_FUNCTOR_ORDER_2D(Wall,PFacet); }; REGISTER_SERIALIZABLE(Ig2_Wall_PFacet_ScGeom); //!################## Bounds ##################### class Bo1_PFacet_Aabb : public BoundFunctor { public : void go(const shared_ptr& cm, shared_ptr& bv, const Se3r&, const Body*); FUNCTOR1D(PFacet); YADE_CLASS_BASE_DOC_ATTRS(Bo1_PFacet_Aabb,BoundFunctor,"Functor creating :yref:`Aabb` from a :yref:`PFacet`.", ((Real,aabbEnlargeFactor,((void)"deactivated",-1),,"Relative enlargement of the bounding box; deactivated if negative.")) ); }; REGISTER_SERIALIZABLE(Bo1_PFacet_Aabb); trunk-2018.02b/pkg/common/ParallelEngine.cpp000066400000000000000000000037661324306050200206410ustar00rootroot00000000000000#include YADE_PLUGIN((ParallelEngine)); #ifdef YADE_OPENMP #include #endif //! ParallelEngine's pseudo-ctor (factory), taking nested lists of slave engines (might be moved to real ctor perhaps) shared_ptr ParallelEngine_ctor_list(const boost::python::list& slaves){ shared_ptr instance(new ParallelEngine); instance->slaves_set(slaves); return instance; } void ParallelEngine::action(){ // openMP warns if the iteration variable is unsigned... const int size=(int)slaves.size(); #ifdef YADE_OPENMP //nested parallel regions are disabled by default on some platforms, we enable them since some of the subengine may be also parallel omp_set_nested(1); #pragma omp parallel for num_threads(ompThreads) #endif for(int i=0; i& e, slaves[i]) { //cerr<<"["<getClassName()<<"]"; e->scene=scene; if(!e->dead && e->isActivated()) e->action(); } } } void ParallelEngine::slaves_set(const boost::python::list& slaves2){ int len=boost::python::len(slaves2); slaves.clear(); for(int i=0; i > > serialGroup(slaves2[i]); if (serialGroup.check()){ slaves.push_back(serialGroup()); continue; } boost::python::extract > serialAlone(slaves2[i]); if (serialAlone.check()){ vector > aloneWrap; aloneWrap.push_back(serialAlone()); slaves.push_back(aloneWrap); continue; } PyErr_SetString(PyExc_TypeError,"List elements must be either\n (a) sequences of engines to be executed one after another\n(b) alone engines."); boost::python::throw_error_already_set(); } } boost::python::list ParallelEngine::slaves_get(){ boost::python::list ret; FOREACH(vector >& grp, slaves){ if(grp.size()==1) ret.append(boost::python::object(grp[0])); else ret.append(boost::python::object(grp)); } return ret; } trunk-2018.02b/pkg/common/ParallelEngine.hpp000066400000000000000000000020521324306050200206310ustar00rootroot00000000000000#pragma once #include class ParallelEngine; shared_ptr ParallelEngine_ctor_list(const boost::python::list& slaves); class ParallelEngine: public Engine { public: typedef vector > > slaveContainer; virtual void action(); virtual bool isActivated(){return true;} // py access boost::python::list slaves_get(); void slaves_set(const boost::python::list& slaves); YADE_CLASS_BASE_DOC_ATTRS_CTOR_PY(ParallelEngine,Engine,"Engine for running other Engine in parallel.", ((slaveContainer,slaves,,,"[will be overridden]")) , /*ctor*/ ompThreads=2; , /*py*/ .def("__init__",boost::python::make_constructor(ParallelEngine_ctor_list),"Construct from (possibly nested) list of slaves.") .add_property("slaves",&ParallelEngine::slaves_get,&ParallelEngine::slaves_set,"List of lists of Engines; each top-level group will be run in parallel with other groups, while Engines inside each group will be run sequentially, in given order."); ); }; REGISTER_SERIALIZABLE(ParallelEngine); trunk-2018.02b/pkg/common/PeriodicEngines.hpp000066400000000000000000000076041324306050200210260ustar00rootroot00000000000000// 2008, 2009 © Václav Šmilauer #pragma once #include #include #include #include class PeriodicEngine: public GlobalEngine { public: static Real getClock(){ timeval tp; gettimeofday(&tp,NULL); return tp.tv_sec+tp.tv_usec/1e6; } virtual ~PeriodicEngine() {}; // vtable virtual bool isActivated(){ const Real& virtNow=scene->time; Real realNow=getClock(); const long& iterNow=scene->iter; if((firstIterRun > 0) && (nDone==0)) { if((firstIterRun > 0) && (firstIterRun == iterNow)) { realLast=realNow; virtLast=virtNow; iterLast=iterNow; nDone++; return true; } return false; } if (iterNow0 && virtNow-virtLast>=virtPeriod) || (realPeriod>0 && realNow-realLast>=realPeriod) || (iterPeriod>0 && iterNow-iterLast>=iterPeriod))){ realLast=realNow; virtLast=virtNow; iterLast=iterNow; nDone++; return true; } if(nDone==0){ realLast=realNow; virtLast=virtNow; iterLast=iterNow; nDone++; if(initRun) return true; return false; } return false; } YADE_CLASS_BASE_DOC_ATTRS_CTOR(PeriodicEngine,GlobalEngine, "Run Engine::action with given fixed periodicity real time (=wall clock time, computation time), \ virtual time (simulation time), iteration number), by setting any of those criteria \ (virtPeriod, realPeriod, iterPeriod) to a positive value. They are all negative (inactive)\ by default.\n\n\ \ The number of times this engine is activated can be limited by setting nDo>0. If the number of activations \ will have been already reached, no action will be called even if an active period has elapsed. \n\n\ \ If initRun is set (false by default), the engine will run when called for the first time; otherwise it will only \ start counting period (realLast etc interal variables) from that point, but without actually running, and will run \ only once a period has elapsed since the initial run. \n\n\ \ This class should not be used directly; rather, derive your own engine which you want to be run periodically. \n\n\ \ Derived engines should override Engine::action(), which will be called periodically. If the derived Engine \ overrides also Engine::isActivated, it should also take in account return value from PeriodicEngine::isActivated, \ since otherwise the periodicity will not be functional. \n\n\ \ Example with :yref:`PyRunner`, which derives from PeriodicEngine; likely to be encountered in python scripts:: \n\n\ \ PyRunner(realPeriod=5,iterPeriod=10000,command='print O.iter') \n\n\ \ will print iteration number every 10000 iterations or every 5 seconds of wall clock time, whiever comes first since it was \ last run.", ((Real,virtPeriod,((void)"deactivated",0),,"Periodicity criterion using virtual (simulation) time (deactivated if <= 0)")) ((Real,realPeriod,((void)"deactivated",0),,"Periodicity criterion using real (wall clock, computation, human) time (deactivated if <=0)")) ((long,iterPeriod,((void)"deactivated",0),,"Periodicity criterion using step number (deactivated if <= 0)")) ((long,nDo,((void)"deactivated",-1),,"Limit number of executions by this number (deactivated if negative)")) ((bool,initRun,false,,"Run the first time we are called as well.")) ((long,firstIterRun,0,,"Sets the step number, at each an engine should be executed for the first time (disabled by default).")) ((Real,virtLast,0,,"Tracks virtual time of last run |yupdate|.")) ((Real,realLast,0,,"Tracks real time of last run |yupdate|.")) ((long,iterLast,0,,"Tracks step number of last run |yupdate|.")) ((long,nDone,0,,"Track number of executions (cummulative) |yupdate|.")), /* this will be put inside the ctor */ realLast=getClock(); ); }; REGISTER_SERIALIZABLE(PeriodicEngine); trunk-2018.02b/pkg/common/PersistentTriangulationCollider.cpp000066400000000000000000000077111324306050200243300ustar00rootroot00000000000000/************************************************************************* * Copyright (C) 2009 by Bruno Chareyre * * * * This program is free software; it is licensed under the terms of the * * GNU General Public License v2 or later. See file LICENSE for details. * *************************************************************************/ #ifdef YADE_CGAL #include"PersistentTriangulationCollider.hpp" #include #include #include #include #include // PersistentTriangulationCollider::PersistentTriangulationCollider() : Collider() // { // haveDistantTransient=false; // isTriangulated = false; // Tes = new ( TesselationWrapper ); // // nbObjects=0; // xBounds.clear(); // yBounds.clear(); // zBounds.clear(); // minima.clear(); // maxima.clear(); // } PersistentTriangulationCollider::~PersistentTriangulationCollider() { delete Tes; } void PersistentTriangulationCollider::action () { // update bounds boundDispatcher->scene=scene; boundDispatcher->action(); shared_ptr bodies=scene->bodies; bool triangulationIteration = false; //BEGIN VORONOI TESSELATION if ( !isTriangulated || scene->iter % 50 == 0 ) { //TesselationWrapper Tes; Tes->clear(); FOREACH(const shared_ptr& b, *scene->bodies){ if(!b || !b->isDynamic()) continue; //means "is it a sphere (not a wall)" const Sphere* s = YADE_CAST(b->shape.get()); Tes->insert ( b->state->pos[0],b->state->pos[1],b->state->pos[2], s->radius, b->getId() ); } Tes->addBoundingPlanes(); isTriangulated = true; triangulationIteration = true; //} // else // { // //if (scene->iter % 100 == 0) { // if (1) { // Tes->RemoveBoundingPlanes(); // BodyContainer::iterator bi = bodies->begin(); // BodyContainer::iterator biEnd = bodies->end(); // for ( ; bi!=biEnd ; ++bi ) // { // if ( ( *bi )->isDynamic() ) // {//means "is it a sphere (not a wall)" // const Sphere* s = YADE_CAST ( ( *bi )->shape.get() ); // const RigidBodyParameters* p = YADE_CAST ( ( *bi )->physicalParameters.get() ); // Tes->move ( p->se3.position[0],p->se3.position[1],p->se3.position[2], s->radius, ( *bi )->getId() ); // // else { // // const Box* s = YADE_CAST((*bi)->shape.get()); // // const RigidBodyParameters* p = YADE_CAST((*bi)->physicalParameters.get()); // // Tes.move(p->se3.position[0],p->se3.position[1],p->se3.position[2], s->radius, (*bi)->getId()); // // } // } // } // Tes->AddBoundingPlanes(); // } } //ENDOF VORONOI TESSELATION interactions = scene->interactions; if ( triangulationIteration ) { std::pair interaction_pair; unsigned int& id1 = interaction_pair.first; unsigned int& id2 = interaction_pair.second; unsigned int numberOfInteractions = Tes->NumberOfFacets(true); for ( unsigned int i=0; inextFacet ( interaction_pair ); // look if the pair (id1,id2) already exists in the overleppingBB collection const shared_ptr& interaction=interactions->find ( Body::id_t ( id1 ),Body::id_t ( id2 ) ); bool found= ( interaction!=0 );//Bruno's Hack // inserts the pair p=(id1,id2) if the two Aabb overlaps and if p does not exists in the overlappingBB if ( !found ) { interactions->insert ( Body::id_t ( id1 ),Body::id_t ( id2 ) ); //cerr << "inserted " << id1 << "-" << id2<iter will be deleted by InteractionLoop, if the collider was run at that step as well interaction->iterLastSeen=scene->iter; // removes the pair p=(id1,id2) if the two Aabb do not overlapp any more and if p already exists in the overlappingBB } } } YADE_PLUGIN((PersistentTriangulationCollider)); #endif /* YADE_CGAL */ trunk-2018.02b/pkg/common/PersistentTriangulationCollider.hpp000066400000000000000000000041241324306050200243300ustar00rootroot00000000000000/************************************************************************* * Copyright (C) 2004 by Olivier Galizzi * * olivier.galizzi@imag.fr * * * * This program is free software; it is licensed under the terms of the * * GNU General Public License v2 or later. See file LICENSE for details. * *************************************************************************/ #pragma once #include #include #include /*! \brief Collision detection engine based on regular triangulation. This engine is using CGAL library (see http://www.cgal.org/) It is still experimental. Uncoment lines in sconscript file to compile it and just change the name of the collider in an existing xml and it will (should) work. Also needed : uncommenting lines in core/Interaction.cpp and core/Interaction.hpp (see NOTE:TriangulationCollider */ class TesselationWrapper; class PersistentTriangulationCollider : public Collider { private : TesselationWrapper* Tes; public : virtual ~PersistentTriangulationCollider(); /// return a list "interactions" of pairs of Body which bounding spheres are overlapping void action(); //! this flag is used to check if the packing has been triangulated bool isTriangulated; shared_ptr interactions; YADE_CLASS_BASE_DOC_ATTRS_CTOR_PY( PersistentTriangulationCollider,Collider,"Collision detection engine based on regular triangulation. Handles spheres and flat boundaries (considered as infinite-sized bounding spheres).", ((bool,haveDistantTransient,false,,"Keep distant interactions? If True, don't delete interactions once bodies don't overlap anymore; constitutive laws will be responsible for requesting deletion. If False, delete as soon as there is no object penetration.")) , isTriangulated = false; Tes = new (TesselationWrapper); ,); }; REGISTER_SERIALIZABLE(PersistentTriangulationCollider); trunk-2018.02b/pkg/common/PyRunner.hpp000066400000000000000000000013021324306050200175260ustar00rootroot00000000000000// 2008 © Václav Šmilauer #pragma once #include #include #include #include class PyRunner: public PeriodicEngine { public : /* virtual bool isActivated: not overridden, PeriodicEngine handles that */ virtual void action(){ if(command.size()>0) pyRunString(command); } YADE_CLASS_BASE_DOC_ATTRS(PyRunner,PeriodicEngine, "Execute a python command periodically, with defined (and adjustable) periodicity. See :yref:`PeriodicEngine` documentation for details.", ((string,command,"",,"Command to be run by python interpreter. Not run if empty.")) ); }; REGISTER_SERIALIZABLE(PyRunner); trunk-2018.02b/pkg/common/Recorder.hpp000066400000000000000000000026531324306050200175230ustar00rootroot00000000000000// 2009 © Václav Šmilauer #pragma once #include class Recorder: public PeriodicEngine{ void openAndCheck() { assert(!out.is_open()); std::string fileTemp = file; if (addIterNum) fileTemp+="-" + boost::lexical_cast(scene->iter); if(fileTemp.empty()) throw ios_base::failure(__FILE__ ": Empty filename."); out.open(fileTemp.c_str(), truncate ? fstream::trunc : fstream::app); if(!out.good()) throw ios_base::failure(__FILE__ ": I/O error opening file `"+fileTemp+"'."); } protected: //! stream object that derived engines should write to std::ofstream out; public: virtual ~Recorder() {}; virtual bool isActivated(){ if(PeriodicEngine::isActivated()){ if(!out.is_open()) openAndCheck(); return true; } return false; } YADE_CLASS_BASE_DOC_ATTRS(Recorder,PeriodicEngine,"Engine periodically storing some data to (one) external file. In addition PeriodicEngine, it handles opening the file as needed. See :yref:`PeriodicEngine` for controlling periodicity.", ((std::string,file,,,"Name of file to save to; must not be empty.")) ((bool,truncate,false,,"Whether to delete current file contents, if any, when opening (false by default)")) ((bool,addIterNum,false,,"Adds an iteration number to the file name, when the file was created. Useful for creating new files at each call (false by default)")) ); }; REGISTER_SERIALIZABLE(Recorder); trunk-2018.02b/pkg/common/ResetRandomPosition.cpp000066400000000000000000000107421324306050200217170ustar00rootroot00000000000000/************************************************************************* * Copyright (C) 2009 by Sergei Dorofeenko * * sega@users.berlios.de * * * * This program is free software; it is licensed under the terms of the * * GNU General Public License v2 or later. See file LICENSE for details. * *************************************************************************/ #ifdef pi #undef pi #endif #include #include #include #include #include #include //#include #include"ResetRandomPosition.hpp" YADE_PLUGIN((ResetRandomPosition)); CREATE_LOGGER(ResetRandomPosition); boost::variate_generator > ResetRandomPosition::randomUnit(boost::mt19937(),boost::uniform_real<>(0,1)); boost::variate_generator > ResetRandomPosition::randomSymmetricUnit(boost::mt19937(),boost::uniform_real<>(-1,1)); void ResetRandomPosition::action() { if (first_run) { FOREACH(shared_ptr eng, scene->engines) { bI=dynamic_cast(eng.get()); if (bI) break; } if (!bI) { LOG_FATAL("Can't find Collider." ); return; } iGME=dynamic_cast(scene->engineByName("IGeomDispatcher").get()); if (!iGME) { InteractionLoop* iDsp=dynamic_cast(scene->engineByName("InteractionLoop").get()); if (!iDsp) { LOG_FATAL("Can't find nor IGeomDispatcher nor InteractionLoop." ); return; } iGME=dynamic_cast(iDsp->geomDispatcher.get()); if (!iGME) { LOG_FATAL("Can't find IGeomDispatcher." ); return; } } first_run=false; randomFacet= shared_ptr(new RandomInt(boost::minstd_rand(),boost::uniform_int<>(0,factoryFacets.size()-1))); } shiftedBodies.clear(); FOREACH(int id, subscribedBodies) { shared_ptr b = Body::byId(id); State* rb = b->state.get(); Vector3r& position = rb->se3.position; if ( (position-point).dot(normal) < 0 ) { Vector3r backup_pos = position; bool is_overlap; for (int attempt=0; attempt(b->shape.get())->radius; Bound bv; bv.min = Vector3r(position[0]-r, position[1]-r, position[2]-r); bv.max = Vector3r(position[0]+r, position[1]+r, position[2]+r); is_overlap=false; // Test overlap with already shifted bodies FOREACH(shared_ptr sb, shiftedBodies) { if (iGME->explicitAction(b,sb,/*force*/false)->geom) { is_overlap=true; break; } } if (is_overlap) continue; // new attempt // Test overlap with other bodies vector probedBodies=bI->probeBoundingVolume(bv); FOREACH(Body::id_t id, probedBodies){ if (iGME->explicitAction(b,Body::byId(id),/*force*/false)->geom){ is_overlap=true; break; } } if (is_overlap) continue; // new attempt break; } if (is_overlap) { LOG_WARN("Can't placing sphere during " << maxAttempts << " attempts."); position=backup_pos; return; } rb->vel = Vector3r(// velocity[0]+velocityRange[0]*randomSymmetricUnit(), velocity[1]+velocityRange[1]*randomSymmetricUnit(), velocity[2]+velocityRange[2]*randomSymmetricUnit()); rb->angVel = Vector3r(// angularVelocity[0]+angularVelocityRange[0]*randomSymmetricUnit(), angularVelocity[1]+angularVelocityRange[1]*randomSymmetricUnit(), angularVelocity[2]+angularVelocityRange[2]*randomSymmetricUnit()); shiftedBodies.push_back(b); } } // next sphere } Vector3r ResetRandomPosition::generatePositionOnSurface() { Body::id_t facetId = factoryFacets[(*randomFacet)()]; Real t1 = randomUnit(); Real t2 = randomUnit()*(1-t1); shared_ptr facet = Body::byId(facetId); Facet* ifacet = static_cast(facet->shape.get()); return t1*(ifacet->vertices[1]-ifacet->vertices[0])+t2*(ifacet->vertices[2]-ifacet->vertices[0])+ifacet->vertices[0]+facet->state->se3.position; } Vector3r ResetRandomPosition::generatePositionInVolume() { Vector3r p1 = generatePositionOnSurface(); Vector3r p2 = generatePositionOnSurface(); Real t = randomUnit(); return p1+t*(p2-p1); } trunk-2018.02b/pkg/common/ResetRandomPosition.hpp000066400000000000000000000062511324306050200217240ustar00rootroot00000000000000/************************************************************************* * Copyright (C) 2009 by Sergei Dorofeenko * * sega@users.berlios.de * * * * This program is free software; it is licensed under the terms of the * * GNU General Public License v2 or later. See file LICENSE for details. * *************************************************************************/ #pragma once #include #include #include #include #include #include /// @brief Produces spheres over the course of a simulation. class ResetRandomPosition : public PeriodicEngine { public: /// @brief Create one sphere per call. virtual void action(); private: /// @brief Pointer to Collider. /// It is necessary in order to probe the bounding volume for new sphere. Collider* bI; /// @brief Pointer to IGeomDispatcher. /// It is necessary in order to detect a real overlap with other bodies. IGeomDispatcher* iGME; std::vector > shiftedBodies; bool first_run; //bool generateNewPosition(const shared_ptr& b, Vector3r& new_position); Vector3r generatePositionOnSurface(); Vector3r generatePositionInVolume(); typedef boost::variate_generator > RandomInt; shared_ptr randomFacet; static boost::variate_generator > randomUnit; static boost::variate_generator > randomSymmetricUnit; DECLARE_LOGGER; YADE_CLASS_BASE_DOC_ATTRS_CTOR(ResetRandomPosition,PeriodicEngine,"Creates spheres during simulation, placing them at random positions. Every time called, one new sphere will be created and inserted in the simulation.", ((vector,factoryFacets,,,"The geometry of the section where spheres will be placed; they will be placed on facets or in volume between them depending on *volumeSection* flag.")) ((std::vector,subscribedBodies,,,"Affected bodies.")) ((Vector3r,point,Vector3r::Zero(),,"??")) ((Vector3r,normal,Vector3r(0,1,0),,"??")) ((bool,volumeSection,((void)"define factory by facets.",false),,"Create new spheres inside factory volume rather than on its surface.")) ((int,maxAttempts,20,,"Max attempts to place sphere. If placing the sphere in certain random position would cause an overlap with any other physical body in the model, SpheresFactory will try to find another position.")) ((Vector3r,velocity,Vector3r::Zero(),,"Mean velocity of spheres.")) ((Vector3r,velocityRange,Vector3r::Zero(),,"Half size of a velocities distribution interval. New sphere will have random velocity within the range velocity±velocityRange.")) ((Vector3r,angularVelocity,Vector3r::Zero(),,"Mean angularVelocity of spheres.")) ((Vector3r,angularVelocityRange,Vector3r::Zero(),,"Half size of a angularVelocity distribution interval. New sphere will have random angularVelocity within the range angularVelocity±angularVelocityRange.")), first_run=true; ); }; REGISTER_SERIALIZABLE(ResetRandomPosition); trunk-2018.02b/pkg/common/SPHEngine.cpp000066400000000000000000000217021324306050200175250ustar00rootroot00000000000000#ifdef YADE_SPH #include"SPHEngine.hpp" #include #include #include #include #include void SPHEngine::action(){ { YADE_PARALLEL_FOREACH_BODY_BEGIN(const shared_ptr& b, scene->bodies){ if(mask>0 && (b->groupMask & mask)==0) continue; this->calculateSPHRho(b); b->state->press=std::max(0.0, k*(b->state->rho - b->state->rho0)); } YADE_PARALLEL_FOREACH_BODY_END(); } } void SPHEngine::calculateSPHRho(const shared_ptr& b) { if (b->state->rho0<0) { b->state->rho0 = rho0; } if (not b->isClump()) { Real rho = 0; // Pointer to kernel function KernelFunction kernelFunctionCurDensity = returnKernelFunction (KernFunctionDensity, Norm); // Calculate rho for every particle for(Body::MapId2IntrT::iterator it=b->intrs.begin(),end=b->intrs.end(); it!=end; ++it) { const shared_ptr b2 = Body::byId((*it).first,scene); Sphere* s=dynamic_cast(b->shape.get()); if(!s) continue; if (((*it).second)->geom and ((*it).second)->phys) { const ScGeom geom = *(YADE_PTR_CAST(((*it).second)->geom)); const ViscElPhys phys=*(YADE_PTR_CAST(((*it).second)->phys)); Real Mass = b2->state->mass; if (Mass == 0) Mass = b->state->mass; const Real SmoothDist = (b2->state->pos - b->state->pos).norm(); // [Monaghan1992], (2.7) (3.8) rho += b2->state->mass*kernelFunctionCurDensity(SmoothDist, h); } } // Self mass contribution rho += b->state->mass*kernelFunctionCurDensity(0.0, h); b->state->rho = rho; } } Real smoothkernelLucy(const double & r, const double & h) { if (r<=h && h>0) { // Lucy Kernel function, [Lucy1977] (27) const Real r_h = r / h; return 105./(16.*M_PI*h*h*h) * (1. + 3. * r_h) * std::pow((1. - r_h), 3); } else { return 0; } } Real smoothkernelLucyGrad(const double & r, const double & h) { if (r<=h && h>0) { // 1st derivative of Lucy Kernel function, [Lucy1977] (27) return 105./(16.*M_PI*h*h*h) * (-12. * r) * std::pow((h - r), 2) / ( h * h * h * h ); } else { return 0; } } Real smoothkernelLucyLapl(const double & r, const double & h) { if (r<=h && h>0) { // 2nd derivative of Lucy Kernel function, [Lucy1977] (27) return 105./(16.*M_PI*h*h*h) * (-12.) / (h * h * h * h) * (h * h - 2. * r * h + 3. * r * r); } else { return 0; } } //========================================================================= Real smoothkernelBSpline1(const double & r, const double & h) { // BSpline Kernel function, [Monaghan1985] (21) if (r<=2.0*h && h>0) { const Real coefA = 3. / (2. * M_PI * h * h * h); const Real r_h = r / h; if (r<=h) { return coefA * (2. /3. - r_h * r_h + 1. / 2. * r_h * r_h * r_h); } else { return coefA / 6. * std::pow((2. - r_h), 3); } } else { return 0; } } Real smoothkernelBSpline1Grad(const double & r, const double & h) { // 1st derivative of BSpline Kernel function, [Monaghan1985] (21) if (r<=2.*h && h>0) { const Real coefA = 3. / (2. * M_PI * h * h * h); const Real r_h = r / h; if (r<=h) { return coefA * ( -r_h ) * (2. - 3. / 2. * r_h); } else { return coefA * (-1. / 2.) * std::pow((2. - r_h), 2); } } else { return 0; } } Real smoothkernelBSpline1Lapl(const double & r, const double & h) { // 2nd derivative of BSpline Kernel function, [Monaghan1985] (21) if (r<=2.0*h && h>0) { const Real coefA = 3. / (2. * M_PI * h * h * h); const Real r_h = r / h; if (r<=h) { return coefA * (-2. + 3. * r_h); } else { return coefA * (2. - r_h); } } else { return 0; } } //========================================================================= Real smoothkernelBSpline2(const double & r, const double & h) { // BSpline Kernel function, [Monaghan1985] (22) if (r<=2.0*h && h>0) { const Real coefA = 3. / (4. * M_PI * h * h * h); const Real r_h = r / h; if (r<=h) { return coefA * (10. /3. - 7. * r_h * r_h + 4 * r_h * r_h * r_h); } else { return coefA * std::pow((2. - r_h), 2)*((5. - 4. * r_h) / 3.); } } else { return 0; } } Real smoothkernelBSpline2Grad(const double & r, const double & h) { // 1st derivative of BSpline Kernel function, [Monaghan1985] (22) if (r<=2.0*h && h>0) { const Real coefA = 3. / (4. * M_PI * h * h * h); const Real r_h = r / h; if (r<=h) { return coefA * (-2.) / (h * h) * ( 7. * r - 6. * r * r_h); } else { return coefA * 2. / h * (-6. + 7. * r_h - 2. * std::pow(r_h, 2) ); } } else { return 0; } } Real smoothkernelBSpline2Lapl(const double & r, const double & h) { // 2nd derivative of BSpline Kernel function, [Monaghan1985] (22) if (r<=2.0*h && h>0) { const Real coefA = 3. / (4. * M_PI * h * h * h); const Real r_h = r / h; if (r<=h) { return coefA * (-2.) / (h * h) * ( 7. - 12. * r_h); } else { return coefA * 2. / (h * h) * ( 7. - 4. * r_h); } } else { return 0; } } //========================================================================= KernelFunction returnKernelFunction(const int a, const typeKernFunctions typeF) { return returnKernelFunction(a, a, typeF); } KernelFunction returnKernelFunction(const int a, const int b, const typeKernFunctions typeF) { if (a != b) { throw runtime_error("Kernel types should be equal!"); } if (a==Lucy) { if (typeF==Norm) { return smoothkernelLucy; } else if (typeF==Grad) { return smoothkernelLucyGrad; } else if (typeF==Lapl) { return smoothkernelLucyLapl; } else { KERNELFUNCDESCR } } else if (a==BSpline1) { if (typeF==Norm) { return smoothkernelBSpline1; } else if (typeF==Grad) { return smoothkernelBSpline1Grad; } else if (typeF==Lapl) { return smoothkernelBSpline1Lapl; } else { KERNELFUNCDESCR } } else if (a==BSpline2) { if (typeF==Norm) { return smoothkernelBSpline2; } else if (typeF==Grad) { return smoothkernelBSpline2Grad; } else if (typeF==Lapl) { return smoothkernelBSpline2Lapl; } else { KERNELFUNCDESCR } } else { KERNELFUNCDESCR } } bool computeForceSPH(shared_ptr& _geom, shared_ptr& _phys, Interaction* I, Vector3r & force) { const ScGeom& geom=*static_cast(_geom.get()); Scene* scene=Omega::instance().getScene().get(); ViscElPhys& phys=*static_cast(_phys.get()); const int id1 = I->getId1(); const int id2 = I->getId2(); const BodyContainer& bodies = *scene->bodies; if (bodies[id1]->isClumpMember() and bodies[id2]->isClumpMember() and bodies[id1]->clumpId==bodies[id2]->clumpId) { //If 2 bodies belong to the same clump, do not calculate forces force = Vector3r::Zero(); return true; } ////////////////////////////////////////////////////////////////// // Copy-paste // Handle periodicity. const Vector3r shift2 = scene->isPeriodic ? scene->cell->intrShiftPos(I->cellDist): Vector3r::Zero(); const Vector3r shiftVel = scene->isPeriodic ? scene->cell->intrShiftVel(I->cellDist): Vector3r::Zero(); const State& de1 = *static_cast(bodies[id1]->state.get()); const State& de2 = *static_cast(bodies[id2]->state.get()); const Vector3r c1x = (geom.contactPoint - de1.pos); const Vector3r c2x = (geom.contactPoint - de2.pos - shift2); const Vector3r relativeVelocity = (de1.vel+de1.angVel.cross(c1x)) - (de2.vel+de2.angVel.cross(c2x)) + shiftVel; const Real normalVelocity = geom.normal.dot(relativeVelocity); // Copy-paste ////////////////////////////////////////////////////////////////// const Real Mass1 = bodies[id1]->state->mass; const Real Mass2 = bodies[id2]->state->mass; const Real Rho1 = bodies[id1]->state->rho; const Real Rho2 = bodies[id2]->state->rho; const Vector3r xixj = de2.pos - de1.pos; if ( phys.kernelFunctionCurrentPressure(xixj.norm(), phys.h)) { Real fpressure = 0.0; if (Rho1!=0.0 and Rho2!=0.0) { // from [Monaghan1992], (3.3), multiply by Mass2, because we need a force, not du/dt fpressure = - Mass1 * Mass2 * ( bodies[id1]->state->press/(Rho1*Rho1) + bodies[id2]->state->press/(Rho2*Rho2) ) * phys.kernelFunctionCurrentPressure(xixj.norm(), phys.h); } Vector3r fvisc = Vector3r::Zero(); if (Rho1!=0.0 and Rho2!=0.0) { // from [Morris1997], (22), multiply by Mass2, because we need a force, not du/dt fvisc = phys.mu * Mass1 * Mass2 * (-normalVelocity*geom.normal)/(Rho1*Rho2) * 1 / (xixj.norm()) * phys.kernelFunctionCurrentPressure(xixj.norm(), phys.h); //phys.kernelFunctionCurrentVisco(xixj.norm(), phys.h); } force = fpressure*geom.normal + fvisc; return true; } else { return false; } } YADE_PLUGIN((SPHEngine)); #endif trunk-2018.02b/pkg/common/SPHEngine.hpp000066400000000000000000000045411324306050200175340ustar00rootroot00000000000000#ifdef YADE_SPH #pragma once #include #include typedef Real (* KernelFunction)(const double & r, const double & h); enum KernFunctions {Lucy=1,BSpline1=2,BSpline2=3}; #define KERNELFUNCDESCR throw runtime_error("Type of kernel function undefined! The following kernel functions are available: Lucy=1 ([Lucy1977]_ (27)), BSpline1=2 ([Monaghan1985]_ (21)), BSpline2=3 ([Monaghan1985]_ (22))."); enum typeKernFunctions {Norm, Grad, Lapl}; class SPHEngine: public PartialEngine{ public: void calculateSPHRho(const shared_ptr& b); virtual void action(); YADE_CLASS_BASE_DOC_ATTRS(SPHEngine,PartialEngine,"Apply given torque (momentum) value at every subscribed particle, at every step. ", ((int, mask,-1,, "Bitmask for SPH-particles.")) ((Real,k,-1,, "Gas constant for SPH-interactions (only for SPH-model). See Mueller [Mueller2003]_ .")) // [Mueller2003], (11) ((Real,rho0,-1,, "Rest density. See Mueller [Mueller2003]_ .")) // [Mueller2003], (1) ((Real,h,-1,, "Core radius. See Mueller [Mueller2003]_ .")) // [Mueller2003], (1) ((int,KernFunctionDensity, Lucy,, "Kernel function for density calculation (by default - Lucy). The following kernel functions are available: Lucy=1 ([Lucy1977]_ (27)), BSpline1=2 ([Monaghan1985]_ (21)), BSpline2=3 ([Monaghan1985]_ (22)).")) ); }; REGISTER_SERIALIZABLE(SPHEngine); Real smoothkernelLucy(const double & r, const double & h); Real smoothkernelLucyGrad(const double & r, const double & h); Real smoothkernelLucyLapl(const double & r, const double & h); Real smoothkernelBSpline1(const double & r, const double & h); Real smoothkernelBSpline1Grad(const double & r, const double & h); Real smoothkernelBSpline1Lapl(const double & r, const double & h); Real smoothkernelBSpline2(const double & r, const double & h); Real smoothkernelBSpline2Grad(const double & r, const double & h); Real smoothkernelBSpline2Lapl(const double & r, const double & h); KernelFunction returnKernelFunction(const int a, const int b, const typeKernFunctions typeF); KernelFunction returnKernelFunction(const int a, const typeKernFunctions typeF); bool computeForceSPH(shared_ptr& _geom, shared_ptr& _phys, Interaction* I, Vector3r & force); #endif trunk-2018.02b/pkg/common/SpatialQuickSortCollider.cpp000066400000000000000000000046661324306050200226770ustar00rootroot00000000000000/************************************************************************* * Copyright (C) 2008 by Sergei Dorofeenko * * sega@users.berlios.de * * * * This program is free software; it is licensed under the terms of the * * GNU General Public License v2 or later. See file LICENSE for details. * *************************************************************************/ #include "SpatialQuickSortCollider.hpp" #include #include #include #include YADE_PLUGIN((SpatialQuickSortCollider)); void SpatialQuickSortCollider::action() { if(scene->isPeriodic){ throw runtime_error("SpatialQuickSortCollider doesn't handle periodic boundaries."); } // update bounds boundDispatcher->scene=scene; boundDispatcher->action(); const shared_ptr& bodies = scene->bodies; // This collider traverses all interactions at every step, therefore all interactions // that were requested for erase might be erased here and will be recreated if necessary. scene->interactions->eraseNonReal(); size_t nbElements=bodies->size(); if (nbElements!=rank.size()) { size_t n = rank.size(); rank.resize(nbElements); for (; n(new AABBBound); } Vector3r min,max; int i=0; FOREACH(const shared_ptr& b, *bodies){ if(!b->bound) continue; min = b->bound->min; max = b->bound->max; rank[i]->id = b->getId(); rank[i]->min = min; rank[i]->max = max; i++; } const shared_ptr& interactions=scene->interactions; scene->interactions->iterColliderLastRun=scene->iter; sort(rank.begin(), rank.end(), xBoundComparator()); // sorting along X int id,id2; size_t j; shared_ptr interaction; for(int i=0,e=nbElements-1; iid; min = rank[i]->min; max = rank[i]->max; j=i; while(++jmin[0] > max[0]) break; if ( rank[j]->min[1] < max[1] && rank[j]->max[1] > min[1] && rank[j]->min[2] < max[2] && rank[j]->max[2] > min[2]) { id2=rank[j]->id; if ( (interaction = interactions->find(Body::id_t(id),Body::id_t(id2))) == 0) { interaction = shared_ptr(new Interaction(id,id2) ); interactions->insert(interaction); } interaction->iterLastSeen=scene->iter; } } } } trunk-2018.02b/pkg/common/SpatialQuickSortCollider.hpp000066400000000000000000000026461324306050200227000ustar00rootroot00000000000000/************************************************************************* * Copyright (C) 2008 by Sergei Dorofeenko * * sega@users.berlios.de * * * * This program is free software; it is licensed under the terms of the * * GNU General Public License v2 or later. See file LICENSE for details. * *************************************************************************/ #pragma once #include #include #include class SpatialQuickSortCollider : public Collider { protected: struct AABBBound { Vector3r min,max; int id; }; class xBoundComparator { public: bool operator() (shared_ptr b1, shared_ptr b2) { return b1->min[0] < b2->min[0]; } }; vector > rank; public: virtual void action(); YADE_CLASS_BASE_DOC(SpatialQuickSortCollider,Collider,"Collider using quicksort along axes at each step, using :yref:`Aabb` bounds. \n\n Its performance is lower than that of :yref:`InsertionSortCollider` (see `Colliders' performance `_), but the algorithm is simple enought to make it good for checking other collider's correctness."); DECLARE_LOGGER; }; REGISTER_SERIALIZABLE(SpatialQuickSortCollider); trunk-2018.02b/pkg/common/Sphere.hpp000066400000000000000000000010751324306050200172010ustar00rootroot00000000000000#pragma once #include // HACK to work around https://bugs.launchpad.net/yade/+bug/528509 // see comments there for explanation namespace yade{ class Sphere: public Shape{ public: Sphere(Real _radius): radius(_radius){ createIndex(); } virtual ~Sphere () {}; YADE_CLASS_BASE_DOC_ATTRS_CTOR(Sphere,Shape,"Geometry of spherical particle.", ((Real,radius,NaN,,"Radius [m]")), createIndex(); /*ctor*/ ); REGISTER_CLASS_INDEX(Sphere,Shape); }; } // necessary using namespace yade; // must be outside yade namespace REGISTER_SERIALIZABLE(Sphere); trunk-2018.02b/pkg/common/StepDisplacer.hpp000066400000000000000000000032311324306050200205110ustar00rootroot00000000000000// 2008 © Václav Šmilauer #pragma once #include #include #include class StepDisplacer: public PartialEngine { public: virtual void action() { FOREACH(Body::id_t id, ids){ const shared_ptr& b=Body::byId(id,scene); if(setVelocities){ const Real& dt=scene->dt; b->state->vel=mov/dt; AngleAxisr aa(rot); aa.axis().normalize(); b->state->angVel=aa.axis()*aa.angle()/dt; LOG_DEBUG("Angular velocity set to "<` is supposed to be used, so that, thanks to this Engine, the bodies will have the prescribed jump over one iteration (dt).")) ); DECLARE_LOGGER; }; REGISTER_SERIALIZABLE(StepDisplacer); trunk-2018.02b/pkg/common/TorqueEngine.hpp000066400000000000000000000020351324306050200203550ustar00rootroot00000000000000/************************************************************************* * Copyright (C) 2008 by Janek Kozicki * * cosurgi@berlios.de * * * * This program is free software; it is licensed under the terms of the * * GNU General Public License v2 or later. See file LICENSE for details. * *************************************************************************/ #pragma once #include #include class TorqueEngine: public PartialEngine{ public: virtual void action() { FOREACH(const Body::id_t id, ids){ // check that body really exists? scene->forces.addTorque(id,moment); } } YADE_CLASS_BASE_DOC_ATTRS(TorqueEngine,PartialEngine,"Apply given torque (momentum) value at every subscribed particle, at every step.", ((Vector3r,moment,Vector3r::Zero(),,"Torque value to be applied.")) ); }; REGISTER_SERIALIZABLE(TorqueEngine); trunk-2018.02b/pkg/common/Wall.cpp000066400000000000000000000040021324306050200166360ustar00rootroot00000000000000// © 2009 Václav Šmilauer #include #include YADE_PLUGIN((Wall)(Bo1_Wall_Aabb) #ifdef YADE_OPENGL (Gl1_Wall) #endif ); Wall::~Wall(){} // vtable void Bo1_Wall_Aabb::go(const shared_ptr& cm, shared_ptr& bv, const Se3r& se3, const Body* b){ Wall* wall=static_cast(cm.get()); if(!bv){ bv=shared_ptr(new Aabb); } Aabb* aabb=static_cast(bv.get()); if(scene->isPeriodic && scene->cell->hasShear()) throw logic_error(__FILE__ "Walls not supported in sheared cell."); const Real& inf=std::numeric_limits::infinity(); aabb->min=Vector3r(-inf,-inf,-inf); aabb->min[wall->axis]=se3.position[wall->axis]; aabb->max=Vector3r( inf, inf, inf); aabb->max[wall->axis]=se3.position[wall->axis]; } #ifdef YADE_OPENGL #include int Gl1_Wall::div=20; void Gl1_Wall::go(const shared_ptr& cm, const shared_ptr& pp, bool, const GLViewInfo& glinfo){ Wall* wall=static_cast(cm.get()); int ax0=wall->axis,ax1=(wall->axis+1)%3,ax2=(wall->axis+2)%3; Vector3r a1,b1,a2,b2; // beginnings (a) and endings (b) of lines in both senses (0,1) // compensate for our position, since the functor is called with transformation to the wall se3 already, but we really want to be centered in the middle of the scene Real mn1=glinfo.sceneCenter[ax1]-glinfo.sceneRadius-pp->se3.position[ax1]; Real mn2=glinfo.sceneCenter[ax2]-glinfo.sceneRadius-pp->se3.position[ax2]; Real step=2*glinfo.sceneRadius/div; //cerr<<"center "<& cm, shared_ptr& bv, const Se3r& se3, const Body*); FUNCTOR1D(Wall); YADE_CLASS_BASE_DOC(Bo1_Wall_Aabb,BoundFunctor,"Creates/updates an :yref:`Aabb` of a :yref:`Wall`"); }; REGISTER_SERIALIZABLE(Bo1_Wall_Aabb); #ifdef YADE_OPENGL #include class Gl1_Wall: public GlShapeFunctor{ public: virtual void go(const shared_ptr&, const shared_ptr&,bool,const GLViewInfo&); RENDERS(Wall); YADE_CLASS_BASE_DOC_STATICATTRS(Gl1_Wall,GlShapeFunctor,"Renders :yref:`Wall` object", ((int,div,20,,"Number of divisions of the wall inside visible scene part.")) ); }; REGISTER_SERIALIZABLE(Gl1_Wall); #endif trunk-2018.02b/pkg/common/ZECollider.cpp000066400000000000000000000105271324306050200177440ustar00rootroot00000000000000// 2011 © Bruno Chareyre #ifdef YADE_CGAL #include"ZECollider.hpp" #include #include #include #include #include #include #include YADE_PLUGIN((ZECollider)) CREATE_LOGGER(ZECollider); InteractionContainer* ZECollider::interactions = NULL; Scene* ZECollider::sscene = NULL; void ZECollider::handleOverlap(const CGBox& a, const CGBox& b){ const Body::id_t& id1=a.handle()->id; const Body::id_t& id2=b.handle()->id; //existing interaction? if (interactions->found(id1,id2)) return; //if it doesn't exist and bounds overlap, create a virtual interaction else if (Collider::mayCollide(Body::byId(id1,sscene).get(),Body::byId(id2,sscene).get())) interactions->insert(shared_ptr(new Interaction(id1,id2))); } // STRIDE bool ZECollider::isActivated(){ // activated if number of bodies changes (hence need to refresh collision information) // or the time of scheduled run already came, or we were never scheduled yet if(!strideActive) return true; if(!newton) return true; if(fastestBodyMaxDist<0){fastestBodyMaxDist=0; return true;} fastestBodyMaxDist=newton->maxVelocitySq; if(fastestBodyMaxDist>=1 || fastestBodyMaxDist==0) return true; if(scene->interactions->dirty) return true; return false; } void ZECollider::action(){ #ifdef ISC_TIMING timingDeltas->start(); #endif interactions=scene->interactions.get(); sscene = scene; scene->interactions->iterColliderLastRun=-1; // periodicity changed, force reinit if(scene->isPeriodic != periodic){ // for(int i=0; i<3; i++) BB[i].vec.clear(); periodic=scene->isPeriodic; } if(verletDist<0){ Real minR=std::numeric_limits::infinity(); FOREACH(const shared_ptr& b, *scene->bodies){ if(!b || !b->shape) continue; Sphere* s=dynamic_cast(b->shape.get()); if(!s) continue; minR=min(s->radius,minR); } // if no spheres, disable stride verletDist=std::isinf(minR) ? 0 : std::abs(verletDist)*minR; } // update bounds via boundDispatcher boundDispatcher->scene=scene; boundDispatcher->sweepDist=verletDist; boundDispatcher->targetInterv=targetInterv; boundDispatcher->updatingDispFactor=updatingDispFactor; boundDispatcher->action(); // if interactions are dirty, force reinitialization if(scene->interactions->dirty){ // doInitSort=true; scene->interactions->dirty=false; } // STRIDE if(verletDist>0){ // get NewtonIntegrator, to ask for the maximum velocity value if(!newton){ FOREACH(shared_ptr& e, scene->engines){ newton=YADE_PTR_DYN_CAST(e); if(newton) break; } if(!newton){ throw runtime_error("ZECollider.verletDist>0, but unable to locate NewtonIntegrator within O.engines."); } } } ISC_CHECKPOINT("init"); // STRIDE // get us ready for strides, if they were deactivated if(!strideActive && verletDist>0 && newton->maxVelocitySq>=0){ // maxVelocitySq is a really computed value strideActive=true; } if(strideActive){ assert(verletDist>0); assert(strideActive); assert(newton->maxVelocitySq>=0); newton->updatingDispFactor=updatingDispFactor; } else { /* !strideActive */ boundDispatcher->sweepDist=0; } ISC_CHECKPOINT("bound"); // // erase virtuals the lazy way // // FIXME: suboptimal since some of them will re-created just below, needs smarter erase by checking overlaps // interactions->eraseNonReal(); // ISC_CHECKPOINT("eraseNonReal"); // copy bounds into our arrays // std::vector boxesIdx; // boxesIdx.resize(scene->bodies.size()); boxes.clear(); FOREACH(shared_ptr& b, *scene->bodies){ if(b){ shared_ptr& bv=b->bound; if(bv) { boxes.push_back(CGBox(CGBbox(bv->min[0],bv->min[1],bv->min[2],bv->max[0],bv->max[1],bv->max[2]),b)); // boxesIdx[b->id] } } } ISC_CHECKPOINT("copy"); // erase virtuals the lazy way // FIXME: suboptimal since some of them will re-created just below, needs smarter erase by checking overlaps // interactions->conditionalyEraseNonReal(*this,scene); interactions->eraseNonReal(); ISC_CHECKPOINT("conditionalyEraseNonReal"); // collide CGAL::box_self_intersection_d( boxes.begin(), boxes.end(), handleOverlap); ISC_CHECKPOINT("collide"); } #endif trunk-2018.02b/pkg/common/ZECollider.hpp000066400000000000000000000117021324306050200177450ustar00rootroot00000000000000// 2011 © Bruno Chareyre #pragma once #include #include class InteractionContainer; // #define this macro to enable timing within this engine #define ISC_TIMING #ifdef ISC_TIMING #define ISC_CHECKPOINT(cpt) timingDeltas->checkpoint(cpt) #else #define ISC_CHECKPOINT(cpt) #endif class NewtonIntegrator; #include #include #include #include #include #include #include #include #include #include typedef CGAL::Bbox_3 CGBbox; // typedef CGAL::Box_intersection_d::Box_with_handle_d > CGBox; typedef CGAL::Box_intersection_d::Box_with_handle_d > CGBox_noId; class CGBox: public CGBox_noId { public: Body::id_t bid; CGBox(CGBbox box, shared_ptr& body): CGBox_noId(box, body) {} }; // class CGBox; class ZECollider: public Collider{ //! struct for storing bounds of bodies std::vector boxes; // we need this to find out about current maxVelocitySq shared_ptr newton; // if False, no type of striding is used // if True, then either verletDist XOR nBins is set bool strideActive; bool periodic; private: static InteractionContainer* interactions; static Scene* sscene; static void handleOverlap(const CGBox& a, const CGBox& b); //FIXME: this will not work if bodies are not numbered from 0 to N with increment 1 inline bool spatialOverlap(const Body::id_t& id1, const Body::id_t& id2) const { assert(!scene->isPeriodic); assert(boxes.size()); const CGBox& b1 = boxes[id1]; const CGBox& b2 = boxes[id2]; return (b1.min_coord(0)<=b2.max_coord(0)) && (b1.max_coord(0)>=b2.min_coord(0)) && (b1.min_coord(1)<=b2.max_coord(1)) && (b1.max_coord(1)>=b2.min_coord(1)) && (b1.min_coord(2)<=b2.max_coord(2)) && (b1.max_coord(2)>=b2.min_coord(2)); } public: //! Predicate called from loop within InteractionContainer::conditionalyEraseNonReal() bool shouldBeErased(Body::id_t id1, Body::id_t id2, Scene* rb) const {return !spatialOverlap(id1,id2);} virtual bool isActivated(); // force reinitialization at next run virtual void invalidatePersistentData(){} // vector probeBoundingVolume(const Bound&); virtual void action(); YADE_CLASS_BASE_DOC_ATTRS_CTOR_PY(ZECollider,Collider,"\ Collider with O(n log(n)) complexity, using a CGAL algorithm from Zomorodian and Edelsbrunner [Kettner2011]_ (http://www.cgal.org/Manual/beta/doc_html/cgal_manual/Box_intersection_d/Chapter_main.html)", ((int,sortAxis,0,,"Axis for the initial contact detection.")) ((bool,sortThenCollide,false,,"Separate sorting and colliding phase; it is MUCH slower, but all interactions are processed at every step; this effectively makes the collider non-persistent, not remembering last state. (The default behavior relies on the fact that inversions during insertion sort are overlaps of bounding boxes that just started/ceased to exist, and only processes those; this makes the collider much more efficient.)")) // ((bool,oriVerlet,true,,"Compare Verlet distance with displacement instead of velocity (only used if nBins<=0)")) ((int,targetInterv,30,,"(experimental) Target number of iterations between bound update, used to define a smaller sweep distance for slower grains if >0, else always use 1*verletDist. Useful in simulations with strong velocity contrasts between slow bodies and fast bodies.")) ((Real,updatingDispFactor,-1,,"(experimental) Displacement factor used to trigger bound update: the bound is updated only if updatingDispFactor*disp>sweepDist when >0, else all bounds are updated.")) ((Real,verletDist,((void)"Automatically initialized",-.15),,"Length by which to enlarge particle bounds, to avoid running collider at every step. Stride disabled if zero. Negative value will trigger automatic computation, so that the real value will be *verletDist* × minimum spherical particle radius; if there are no spherical particles, it will be disabled.")) ((Real,fastestBodyMaxDist,-1,,"Maximum displacement of the fastest body since last run; if >= verletDist, we could get out of bboxes and will trigger full run. DEPRECATED, was only used without bins. |yupdate|")) ((int,numReinit,0,Attr::readonly,"Cummulative number of bound array re-initialization.")), /* ctor */ #ifdef ISC_TIMING timingDeltas=shared_ptr(new TimingDeltas); #endif periodic=false; strideActive=false; , /* py */ .def_readonly("strideActive",&ZECollider::strideActive,"Whether striding is active (read-only; for debugging). |yupdate|") .def_readonly("periodic",&ZECollider::periodic,"Whether the collider is in periodic mode (read-only; for debugging) |yupdate|") ); DECLARE_LOGGER; }; REGISTER_SERIALIZABLE(ZECollider); trunk-2018.02b/pkg/common/common.cpp000066400000000000000000000020541324306050200172340ustar00rootroot00000000000000// ======================================================= // Some plugins from removed CPP-fiels #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include YADE_PLUGIN((IntrCallback) #ifdef YADE_BODY_CALLBACK (BodyCallback) #endif ); YADE_PLUGIN((ForceResetter)(TorqueEngine)(FieldApplier)(BoundaryController) (NormPhys)(NormShearPhys)(Recorder)(CylScGeom6D)(CylScGeom)(Box) (StepDisplacer)(GenericSpheresContact) (PeriodicEngine)(Sphere)(Aabb)(ElastMat)(FrictMat)(PyRunner) ); trunk-2018.02b/pkg/dem/000077500000000000000000000000001324306050200145145ustar00rootroot00000000000000trunk-2018.02b/pkg/dem/BlockGen.cpp000077500000000000000000003432141324306050200167160ustar00rootroot00000000000000/*CWBoon 2016 */ /* Please cite: */ /* CW Boon, GT Houlsby, S Utili (2015). A new rock slicing method based on linear programming. Computers and Geotechnics 65, 12-29. */ /* The numerical library is changed from CPLEX to CLP because subscription to the academic initiative is required to use CPLEX for free */ #ifdef YADE_POTENTIAL_BLOCKS #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 // random #include #include #include #include #include //#include #include "BlockGen.hpp" #include #include #include /* IpOpt */ #include #include CREATE_LOGGER(BlockGen); YADE_PLUGIN((BlockGen)); //using namespace boost; //using namespace std; BlockGen::~BlockGen () {} std::ofstream BlockGen::output("BlockGenFindExtreme.txt", fstream::trunc); bool BlockGen::generate(string& message) { scene = shared_ptr(new Scene); shared_ptr body; positionRootBody(scene); createActors(scene); /* Create domain: start with one big block */ vector blk; Vector3r firstBlockCentre(0,0,0); firstBlockCentre.x() = 0.0; //0.5*(boundarySizeXmax + boundarySizeXmin); firstBlockCentre.y() = 0.0; //0.5*(boundarySizeYmax + boundarySizeYmin); firstBlockCentre.z() = 0.0; //0.5*(boundarySizeZmax + boundarySizeZmin); blk.push_back(Block(firstBlockCentre,kForPP, rForPP, RForPP)); Real xmin = fabs(firstBlockCentre.x() - boundarySizeXmin); Real xmax = fabs(-firstBlockCentre.x() + boundarySizeXmax); Real ymin = fabs(firstBlockCentre.y() - boundarySizeYmin); Real ymax = fabs(-firstBlockCentre.y() + boundarySizeYmax); Real zmin = fabs(firstBlockCentre.z() - boundarySizeZmin); Real zmax = fabs(-firstBlockCentre.z() + boundarySizeZmax); blk[0].a.push_back(1.0); blk[0].a.push_back(-1.0); blk[0].a.push_back(0.0); blk[0].a.push_back(0.0); blk[0].a.push_back(0.0); blk[0].a.push_back(0.0); blk[0].b.push_back(0.0); blk[0].b.push_back(0.0); blk[0].b.push_back(1.0); blk[0].b.push_back(-1.0); blk[0].b.push_back(0.0); blk[0].b.push_back(0.0); blk[0].c.push_back(0.0); blk[0].c.push_back(0.0); blk[0].c.push_back(0.0); blk[0].c.push_back(0.0); blk[0].c.push_back(1.0); blk[0].c.push_back(-1.0); blk[0].d.push_back(xmax); blk[0].d.push_back(xmin); blk[0].d.push_back(ymax); blk[0].d.push_back(ymin); blk[0].d.push_back(zmax); blk[0].d.push_back(zmin); blk[0].redundant.push_back(false); blk[0].redundant.push_back(false); blk[0].redundant.push_back(false); blk[0].redundant.push_back(false); blk[0].redundant.push_back(false); blk[0].redundant.push_back(false); blk[0].JRC.push_back(15.0);blk[0].JRC.push_back(15.0);blk[0].JRC.push_back(15.0);blk[0].JRC.push_back(15.0);blk[0].JRC.push_back(15.0);blk[0].JRC.push_back(15.0); blk[0].JCS.push_back(pow(10,6));blk[0].JCS.push_back(pow(10,6));blk[0].JCS.push_back(pow(10,6));blk[0].JCS.push_back(pow(10,6));blk[0].JCS.push_back(pow(10,6));blk[0].JCS.push_back(pow(10,6)); blk[0].sigmaC.push_back(pow(10,6));blk[0].sigmaC.push_back(pow(10,6));blk[0].sigmaC.push_back(pow(10,6));blk[0].sigmaC.push_back(pow(10,6));blk[0].sigmaC.push_back(pow(10,6));blk[0].sigmaC.push_back(pow(10,6)); blk[0].phi_r.push_back(40.0);blk[0].phi_r.push_back(40.0);blk[0].phi_r.push_back(40.0);blk[0].phi_r.push_back(40.0);blk[0].phi_r.push_back(40.0);blk[0].phi_r.push_back(40.0); blk[0].phi_b.push_back(40.0);blk[0].phi_b.push_back(40.0);blk[0].phi_b.push_back(40.0);blk[0].phi_b.push_back(40.0);blk[0].phi_b.push_back(40.0);blk[0].phi_b.push_back(40.0); blk[0].asperity.push_back(5.0);blk[0].asperity.push_back(5.0);blk[0].asperity.push_back(5.0);blk[0].asperity.push_back(5.0);blk[0].asperity.push_back(5.0);blk[0].asperity.push_back(5.0); blk[0].cohesion.push_back(0.0);blk[0].cohesion.push_back(0.0);blk[0].cohesion.push_back(0.0);blk[0].cohesion.push_back(0.0);blk[0].cohesion.push_back(0.0);blk[0].cohesion.push_back(0.0); blk[0].tension.push_back(0.0);blk[0].tension.push_back(0.0);blk[0].tension.push_back(0.0);blk[0].tension.push_back(0.0);blk[0].tension.push_back(0.0);blk[0].tension.push_back(0.0); blk[0].isBoundaryPlane.push_back(false);blk[0].isBoundaryPlane.push_back(false);blk[0].isBoundaryPlane.push_back(false);blk[0].isBoundaryPlane.push_back(false);blk[0].isBoundaryPlane.push_back(false);blk[0].isBoundaryPlane.push_back(false); blk[0].lambda0.push_back(0.0);blk[0].lambda0.push_back(0.0);blk[0].lambda0.push_back(0.0);blk[0].lambda0.push_back(0.0);blk[0].lambda0.push_back(0.0);blk[0].lambda0.push_back(0.0); blk[0].heatCapacity.push_back(0.0);blk[0].heatCapacity.push_back(0.0);blk[0].heatCapacity.push_back(0.0);blk[0].heatCapacity.push_back(0.0);blk[0].heatCapacity.push_back(0.0);blk[0].heatCapacity.push_back(0.0); blk[0].hwater.push_back(-1.0);blk[0].hwater.push_back(-1.0);blk[0].hwater.push_back(-1.0);blk[0].hwater.push_back(-1.0);blk[0].hwater.push_back(-1.0);blk[0].hwater.push_back(-1.0); blk[0].intactRock.push_back(false);blk[0].intactRock.push_back(false);blk[0].intactRock.push_back(false);blk[0].intactRock.push_back(false);blk[0].intactRock.push_back(false);blk[0].intactRock.push_back(false); blk[0].jointType.push_back(0); blk[0].jointType.push_back(0); blk[0].jointType.push_back(0); blk[0].jointType.push_back(0); blk[0].jointType.push_back(0); blk[0].jointType.push_back(0); /* List of Discontinuities */ vector joint; /* Read boundary size */ Real boundarySize = max(max(fabs(boundarySizeXmax-boundarySizeXmin),fabs(boundarySizeYmax-boundarySizeYmin)),fabs(boundarySizeZmax-boundarySizeZmin)); Real dip = 0.0;Real dipdir = 0.0; /*Python input for discontinuities */ for(unsigned int i=0; i 180.0){ dipdirN = dipdir - 180.0; }else{ dipdirN = dipdir + 180.0; } Real dipRad = dipN/180.0*PI; Real dipdirRad = dipdirN/180.0*PI; Real a = cos(dipdirRad)*cos(dipRad); Real b = sin(dipdirRad)*cos(dipRad); Real c = sin(dipRad); Real l = sqrt(a*a + b*b +c*c); joint.push_back(Discontinuity(globalOrigin)); int i = joint.size()-1; jointNo=i; joint[i].a = a/l; joint[i].b = b/l; joint[i].c = c/l; joint[i].d = 0.0; planeNormal = Vector3r(a/l,b/l,c/l); //std::cout<<"planeNormal: "< 180.0){ dipdirN = dipdir - 180.0; }else{ dipdirN = dipdir + 180.0; } Real dipRad = dipN/180.0*PI; Real dipdirRad = dipdirN/180.0*PI; Real a = cos(dipdirRad)*cos(dipRad); Real b = sin(dipdirRad)*cos(dipRad); Real c = sin(dipRad); Real l = sqrt(a*a + b*b +c*c); joint.push_back(Discontinuity(globalOrigin)); int i = joint.size()-1; jointNo=i; joint[i].a = a/l; joint[i].b = b/l; joint[i].c = c/l; joint[i].d = 0.0; planeNormal = Vector3r(a/l,b/l,c/l); //std::cout<<"planeNormal: "< 180.0){ dipdirN = dipdir - 180.0; }else{ dipdirN = dipdir + 180.0; } Real dipRad = dipN/180.0*PI; Real dipdirRad = dipdirN/180.0*PI; a = cos(dipdirRad)*cos(dipRad); b = sin(dipdirRad)*cos(dipRad); c = sin(dipRad); l = sqrt(a*a + b*b +c*c); planeNormal = Vector3r(a/l,b/l,c/l); //save for later //std::cout<<"planeNormal1: "<(j); joint[i].phi_b = phi_b; joint[i].phi_r = phi_r; joint[i].JRC = JRC; joint[i].JCS = JCS; joint[i].asperity = asperity; joint[i].sigmaC = sigmaC; joint[i].cohesion = cohesion; joint[i].tension = tension; joint.push_back(Discontinuity(firstBlockCentre)); i = joint.size()-1; joint[i].a = -a/l; joint[i].b = -b/l; joint[i].c = -c/l; joint[i].d =spacing*static_cast(j); joint[i].phi_b = phi_b; joint[i].phi_r = phi_r; joint[i].JRC = JRC; joint[i].JCS = JCS; joint[i].asperity = asperity; joint[i].sigmaC = sigmaC; joint[i].cohesion = cohesion; joint[i].tension = tension; } count = 0; } } } if(jointProbabilistic){ /* Read csv file for info on discontinuities */ const char *filenameChar = filenameProbabilistic.c_str(); ifstream file ( filenameChar ); // declare file stream: http://www.cplusplus.com/reference/iostream/ifstream/ string value; /* skip first line */ getline ( file, value); int count = 0; int linecount = 0; Real dip = 0.0;Real dipdir = 0.0; Vector3r jointCentre(0,0,0); const double PI = std::atan(1.0)*4; int boundaryNo = 0; int boundaryCount = 0; int abdCount=0; Vector3r planeNormal(0,0,0); int jointNo = 0; double persistenceA=0; double persistenceB=0; double spacing = 0; boost::normal_distribution<> nd(0.0, 1.0); boost::variate_generator > generator(boost::mt19937(time(0)),nd); while ( file.good() ) { //std::cout<<"reading file, count"< 180.0){ dipdirN = dipdir - 180.0; }else{ dipdirN = dipdir + 180.0; } if(probabilisticOrientation == true && dip < 120.0 && dip > 60.0 ){ double perturb = gen_normal_3(generator); dipN = dipN + perturb; std::cout<<"perturb: "<(valueDouble); } if(count == 17){count = 0; /* to include comments */ if(intactRockDegradation==true){ joint[jointNo].intactRock = true; } } } } if(slopeFace){ /* Read csv file for info on discontinuities */ const char *filenameChar = filenameSlopeFace.c_str(); ifstream file ( filenameChar ); // declare file stream: http://www.cplusplus.com/reference/iostream/ifstream/ string value; /* skip first line */ getline ( file, value); int count = 0; Vector3r jointCentre(0,0,0); const double PI = std::atan(1.0)*4; Vector3r planeNormal(0,0,0); int jointNo = 0; while ( file.good() ) { count ++; getline ( file, value, ';' ); // read a string until next comma: http://www.cplusplus.com/reference/string/getline/ //std::cout<<"count: "< 180.0){ dipdirN = dipdir - 180.0; }else{ dipdirN = dipdir + 180.0; } Real dipRad = dipN/180.0*PI; Real dipdirRad = dipdirN/180.0*PI; Real a = cos(dipdirRad)*cos(dipRad); Real b = sin(dipdirRad)*cos(dipRad); Real c = sin(dipRad); Real l = sqrt(a*a + b*b +c*c); joint.push_back(Discontinuity(globalOrigin)); int i = joint.size()-1; jointNo=i; joint[i].a = a/l; joint[i].b = b/l; joint[i].c = c/l; joint[i].d = 0.0; planeNormal = Vector3r(a/l,b/l,c/l); //std::cout<<"planeNormal: "< twoRadiiDist ){subMemberIter++; continue;} /* std::cout<<"centroidDist: "< maxd){maxd= fabs(blkA.d[h]);} } if(converge== false){ blkA.tooSmall = true; bool inside = checkCentroid(blkA,blkA.centre); std::cout<<"blki inside: "< maxd){maxd=fabs( blkB.d[h]);} } if(converge== false){ blkB.tooSmall = true; bool inside = checkCentroid(blkB,blkB.centre); std::cout<<"blkNo inside: "<0.0){ blkA.isBoundary = true; }else{ blkB.isBoundary = true; } } double RblkA = 0.0; Vector3r tempCentreA(0,0,0); double RblkB = 0.0; Vector3r tempCentreB(0,0,0); /* Prune blocks that are too small or too elongated */ bool tooSmall = false; if(joint[j].throughGoing == false){ double conditioningFactor = 1.0; Real minX = 0.0; Real minY = 0.0; Real minZ = 0.0; Real maxX = 0.0; Real maxY = 0.0; Real maxZ = 0.0; Discontinuity plane=Discontinuity(Vector3r(0,0,0)); plane.a=directionA.x(); //1.0; plane.b=directionA.y(); //0.0; plane.c=directionA.z(); //0.0; plane.d=1.2*boundarySizeXmax; Vector3r falseVertex (0.0,0.0,0.0); if (contactBoundaryLPCLP(plane, blkA,falseVertex) ){ minX = directionA.dot(falseVertex); //falseVertex.x(); }else{tooSmall=true;} tempCentreA = tempCentreA + falseVertex; plane=Discontinuity(Vector3r(0,0,0)); plane.a=directionB.x(); //0.0; plane.b=directionB.y(); //1.0; plane.c=directionB.z(); //0.0; plane.d=1.2*boundarySizeYmax; if (contactBoundaryLPCLP(plane, blkA,falseVertex) ){ minY = directionB.dot(falseVertex); //falseVertex.y(); }else{tooSmall=true;} tempCentreA = tempCentreA + falseVertex; plane=Discontinuity(Vector3r(0,0,0)); plane.a=directionC.x(); //0.0; plane.b=directionC.y(); //0.0; plane.c=directionC.z(); //1.0; plane.d=1.2*boundarySizeZmax; if (contactBoundaryLPCLP(plane, blkA,falseVertex) ){ minZ = directionC.dot(falseVertex); //falseVertex.z(); }else{tooSmall=true;} tempCentreA = tempCentreA + falseVertex; plane=Discontinuity(Vector3r(0,0,0)); plane.a=-directionA.x(); //-1.0; plane.b=-directionA.y(); //0.0; plane.c=-directionA.z(); //0.0; plane.d=1.2*boundarySizeXmin; if (contactBoundaryLPCLP(plane, blkA,falseVertex) ){ maxX = directionA.dot(falseVertex); //falseVertex.x(); }else{tooSmall=true;} tempCentreA = tempCentreA + falseVertex; plane=Discontinuity(Vector3r(0,0,0)); plane.a=-directionB.x(); //0.0; plane.b=-directionB.y(); //-1.0; plane.c=-directionB.z(); //0.0; plane.d=1.2*boundarySizeYmin; if (contactBoundaryLPCLP(plane, blkA,falseVertex) ){ maxY = directionB.dot(falseVertex); //falseVertex.y(); }else{tooSmall=true;} tempCentreA = tempCentreA + falseVertex; plane=Discontinuity(Vector3r(0,0,0)); plane.a=-directionC.x(); // 0.0; plane.b=-directionC.y(); //0.0; plane.c=-directionC.z(); //-1.0; plane.d=1.2*boundarySizeZmin; if (contactBoundaryLPCLP(plane,blkA,falseVertex) ){ maxZ = directionC.dot(falseVertex); //falseVertex.z(); }else{tooSmall=true;} tempCentreA = tempCentreA + falseVertex; Real maxXoverall = fabs(maxX-minX); Real maxYoverall = fabs(maxY-minY); Real maxZoverall = fabs(maxZ-minZ); tempCentreA = 1.0/6.0*tempCentreA; Vector3r tempA(0,0,0); double chebyshevRa = inscribedSphereCLP(blkA, tempA, twoDimension); tempCentreA = tempA; //std::cout<<"chebyshevRa: "< maxRatio || maxYoverall/(2.0*chebyshevRa) > maxRatio || maxZoverall/(2.0*chebyshevRa) > maxRatio){ tooSmall = true; } plane=Discontinuity(Vector3r(0,0,0)); plane.a=directionA.x(); //1.0; plane.b=directionA.y(); //0.0; plane.c=directionA.z(); //0.0; plane.d=1.2*boundarySizeXmax; if (contactBoundaryLPCLP(plane, blkB,falseVertex) ){ minX = directionA.dot(falseVertex); //falseVertex.x(); }else{tooSmall=true;} tempCentreB = tempCentreB + falseVertex; plane=Discontinuity(Vector3r(0,0,0)); plane.a=directionB.x(); //0.0; plane.b=directionB.y(); //1.0; plane.c=directionB.z(); //0.0; plane.d=1.2*boundarySizeYmax; if (contactBoundaryLPCLP(plane, blkB,falseVertex) ){ minY = directionB.dot(falseVertex); //falseVertex.y(); }else{tooSmall=true;} tempCentreB = tempCentreB + falseVertex; plane=Discontinuity(Vector3r(0,0,0)); plane.a=directionC.x(); //0.0; plane.b=directionC.y(); //0.0; plane.c=directionC.z(); //1.0; plane.d=1.2*boundarySizeZmax; if (contactBoundaryLPCLP(plane, blkB,falseVertex) ){ minZ = directionC.dot(falseVertex); //falseVertex.z(); }else{tooSmall=true;} tempCentreB = tempCentreB + falseVertex; plane=Discontinuity(Vector3r(0,0,0)); plane.a=-directionA.x(); //-1.0; plane.b=-directionA.y(); //0.0; plane.c=-directionA.z(); //0.0; plane.d=1.2*boundarySizeXmin; if (contactBoundaryLPCLP(plane, blkB,falseVertex) ){ maxX = directionA.dot(falseVertex); //falseVertex.x(); }else{tooSmall=true;} tempCentreB = tempCentreB + falseVertex; plane=Discontinuity(Vector3r(0,0,0)); plane.a=-directionB.x(); //0.0; plane.b=-directionB.y(); //-1.0; plane.c=-directionB.z(); //0.0; plane.d=1.2*boundarySizeYmin; if (contactBoundaryLPCLP(plane, blkB,falseVertex) ){ maxY = directionB.dot(falseVertex); //falseVertex.y(); }else{tooSmall=true;} tempCentreB = tempCentreB + falseVertex; plane=Discontinuity(Vector3r(0,0,0)); plane.a=-directionC.x(); //0.0; plane.b=-directionC.y(); //0.0; plane.c=-directionC.z(); //-1.0; plane.d=1.2*boundarySizeZmin; if (contactBoundaryLPCLP(plane,blkB,falseVertex) ){ maxZ = directionC.dot(falseVertex); //falseVertex.z(); }else{tooSmall=true;} tempCentreB = tempCentreB + falseVertex; maxXoverall = fabs(maxX-minX); maxYoverall = fabs(maxY-minY); maxZoverall = fabs(maxZ-minZ); tempCentreB = 1.0/6.0*tempCentreB; Vector3r tempB(0,0,0); double chebyshevRb = inscribedSphereCLP(blkB, tempB, twoDimension); tempCentreB = tempB; //std::cout<<"chebyshevRb: "< maxRatio || maxYoverall/(2.0*chebyshevRb) > maxRatio || maxZoverall/(2.0*chebyshevRb) > maxRatio){ tooSmall = true; } } if(tooSmall== false){ #if 0 /* Identify redundant planes */ for(int k=0; k0){ blk[i].subMembers.push_back(Block(blkB.centre,kForPP,rForPP,RForPP )); int subMemberCount = blk[i].subMembers.size()-1; blk[i].subMembers[subMemberIter] = blkA; blk[i].subMembers[subMemberCount] = blkB; blk[i].subMembers[subMemberIter].R = RblkA; blk[i].subMembers[subMemberCount].R = RblkB; blk[i].subMembers[subMemberIter].tempCentre = tempCentreA; blk[i].subMembers[subMemberCount].tempCentre = tempCentreB; }else{ blk[i].subMembers.push_back(Block(blkA.centre,kForPP,rForPP,RForPP )); blk[i].subMembers.push_back(Block(blkB.centre,kForPP,rForPP,RForPP )); int subMemberCount = blk[i].subMembers.size()-2; blk[i].subMembers[subMemberCount] = blkA; blk[i].subMembers[subMemberCount+1] = blkB; blk[i].subMembers[subMemberCount].R = RblkA; blk[i].subMembers[subMemberCount+1].R = RblkB; blk[i].subMembers[subMemberCount].tempCentre = tempCentreA; blk[i].subMembers[subMemberCount+1].tempCentre = tempCentreB; } }else{ blk.push_back(Block(blkB.centre,kForPP,rForPP,RForPP )); blk[blkNo] = blkB; blk[i] = blkA; blk[blkNo].R = RblkB; blk[i].R=RblkA; blk[blkNo].tempCentre = tempCentreB; blk[i].tempCentre = tempCentreA; } } }/* outer if-braces for detected */ subMemberIter++; }while(subMemberIter0){ for(int j=0; j maxd){maxd= fabs(blk[i].subMembers[j].d[h]);} } if(converge== false){ blk[i].subMembers[j].tooSmall = true; bool inside = checkCentroid(blk[i].subMembers[j],blk[i].subMembers[j].centre); std::cout<<"blki inside: "< maxd){maxd= fabs(blk[i].d[h]);} } if(converge== false){ blk[i].tooSmall = true; bool inside = checkCentroid(blk[i],blk[i].centre); std::cout<<"blki inside: "<0){ //#if 0 shared_ptr clumpBody=shared_ptr(new Body()); shared_ptr clump=shared_ptr(new Clump()); clumpBody->shape=clump; clumpBody->setDynamic(true); clumpBody->setBounded(false); // Body::id_t clumpId=scene->bodies->insert(clumpBody); // std::cout<<"clumpId: "< memberId; int clumpMemberCount = 0; for(int j=0; jbodies->insert(body); Body::id_t lastId=(Body::id_t)scene->bodies->insert(body); /* new */ memberId.push_back(lastId); //Clump::add(clumpBody, /* body*/ Body::byId(lastId,scene) ); //clump->ids.push_back(lastId); clumpMemberCount++; } //std::cout<<"Generating progress.... sub-block no: "< 1){ Body::id_t clumpId=scene->bodies->insert(clumpBody); //std::cout<<"ok 1"<ids.push_back(memberId[j]); //std::cout<<"ok 3"<bodies->insert(body); } } } scene->dt=defaultDt; //globalStiffnessTimeStepper->defaultDt=0.000001; //defaultDt; //scene->updateBound(); //scene->bound->min = Vector3r(5,5,5); //scene->bound->max = -1.0*Vector3r(5,5,5); std::cout<<"complete "<& body, struct Block block, int number ){ //std::cout<<"createBlockBegin"<(new Body); shared_ptr aabb(new Aabb); shared_ptr pBlock(new PotentialBlock); pBlock->minAabbRotated = 1.02*min; pBlock->maxAabbRotated = 1.02*max; body->setDynamic(true); Real volume = 0.0; Real Ixx = 0.0; Real Iyy = 0.0; Real Izz = 0.0; Real Ixy = 0.0; Real Ixz = 0.0; Real Iyz = 0.0; Vector3r boundingDist = Vector3r(fabs(max[0]+min[0]),fabs(max[1]+min[1]),fabs(max[2]+min[2])); //std::cout<<"beforeInertia"<state->mass = blockVol*density; //blockVol pBlock->volume = blockVol; char jobz = 'V'; char uplo = 'L'; int N=3; double A[9]; int lda=3; double eigenValues[3]; double work[15]; int lwork = 15; int info = 0; A[0] = Ixx; A[1]=Ixy; A[2]=Ixz; A[3]=Ixy; A[4]= Iyy; A[5]=Iyz; A[6]=Ixz; A[7]=Iyz; A[8]=Izz; dsyev_(&jobz, &uplo, &N, &A[0], &lda, &eigenValues[0], &work[0], &lwork, &info); Vector3r eigenVec1 (A[0],A[1],A[2]); eigenVec1.normalize(); Vector3r eigenVec2 (A[3],A[4],A[5]); eigenVec2.normalize(); Vector3r eigenVec3 (A[6],A[7],A[8]); eigenVec3.normalize(); Eigen::Matrix3d lapackEigenVec; lapackEigenVec(0,0) = eigenVec1[0]; lapackEigenVec(0,1) = eigenVec2[0]; lapackEigenVec(0,2)=eigenVec3[0]; lapackEigenVec(1,0) = eigenVec1[1]; lapackEigenVec(1,1) = eigenVec2[1]; lapackEigenVec(1,2)=eigenVec3[1]; lapackEigenVec(2,0) = eigenVec1[2]; lapackEigenVec(2,1) = eigenVec2[2]; lapackEigenVec(2,2)=eigenVec3[2]; /* make sure diagonals are positive */ if(lapackEigenVec(0,0)<0.0){ lapackEigenVec(0,0) = -lapackEigenVec(0,0); lapackEigenVec(1,0) = -lapackEigenVec(1,0); lapackEigenVec(2,0) = -lapackEigenVec(2,0); } if(lapackEigenVec(1,1)<0.0){ lapackEigenVec(0,1) = -lapackEigenVec(0,1); lapackEigenVec(1,1) = -lapackEigenVec(1,1); lapackEigenVec(2,1) = -lapackEigenVec(2,1); } if(lapackEigenVec(2,2)<0.0){ lapackEigenVec(0,2) = -lapackEigenVec(0,2); lapackEigenVec(1,2) = -lapackEigenVec(1,2); lapackEigenVec(2,2) = -lapackEigenVec(2,2); } Eigen::Matrix3d lapackEigenVal = Eigen::Matrix3d::Zero(); lapackEigenVal(0,0) = eigenValues[0]; lapackEigenVal(1,1) = eigenValues[1]; lapackEigenVal(2,2) = eigenValues[2]; //std::cout<<"Ixx: "<state->inertia= inertiaFactor*Vector3r(lapackEigenVal(0,0)*density,lapackEigenVal(1,1)*density,lapackEigenVal(2,2)*density); }else{ double maxInertia = std::max(std::max(std::max(lapackEigenVal(0,0),lapackEigenVal(1,1)),lapackEigenVal(2,2)),2.0/5.0*body->state->mass/density*minSize*minSize); body->state->inertia= inertiaFactor*Vector3r(maxInertia*density,maxInertia*density,maxInertia*density); } body->state->pos=block.centre; body->state->ori = q.conjugate(); shared_ptr mat(new FrictMat); mat->frictionAngle = frictionDeg * Mathr::PI/180.0; aabb->color = Vector3r(0,1,0); //std::cout<<"afterInertia"<a.push_back(plane.x()); block.a[i] = plane.x(); pBlock->b.push_back(plane.y()); block.b[i] = plane.y(); pBlock->c.push_back(plane.z()); block.c[i] = plane.z(); pBlock->d.push_back( block.d[i] /* newd */); pBlock->addPlaneStruct(); //#if 0 tempA(i,0) = block.a[i]; tempA(i,1) = block.b[i]; tempA(i,2) = block.c[i]; tempD(i,0) = block.d[i] + block.r; //#endif pBlock->JRC.push_back(block.JRC[i]); pBlock->JCS.push_back(block.JCS[i]); pBlock->sigmaC.push_back(block.sigmaC[i]); pBlock->phi_r.push_back(block.phi_r[i]); pBlock->phi_b.push_back(block.phi_b[i]); pBlock->asperity.push_back(block.asperity[i]); pBlock->cohesion.push_back(block.cohesion[i]); pBlock->tension.push_back(block.tension[i]); pBlock->isBoundaryPlane.push_back(block.isBoundaryPlane[i]); pBlock->lambda0.push_back(block.lambda0[i]); pBlock->heatCapacity.push_back(block.heatCapacity[i]); pBlock->hwater.push_back(block.hwater[i]); pBlock->intactRock.push_back(block.intactRock[i]); pBlock->jointType.push_back(block.jointType[i]); if(i>10000){std::cout<<"i: "<Amatrix =tempA; pBlock->Dmatrix = tempD; //std::cout<<"blockgen, A: "<Amatrix<<", D: "<Dmatrix<oriAabb = body->state->ori; pBlock->r = block.r;// + initialOverlap; pBlock->k = block.k; pBlock->AabbMinMax=true; pBlock->minAabb = 1.02*min; pBlock->maxAabb = 1.02*max; pBlock->R = std::max(max.norm(),min.norm()); //findExtreme(block); //block.R; //tempMax.norm(); // //if(min.norm()/max.norm() <0.125 || min.norm()/max.norm() > 8.0){return false;} pBlock->id =number; pBlock->color = Vector3r(Mathr::UnitRandom(),Mathr::UnitRandom(),Mathr::UnitRandom()); //std::max(std::max(maxXoverall, maxYoverall),maxZoverall) ; // if(block.isBoundary == true){ pBlock->color = Vector3r(0,100,0); body->setDynamic(false); } pBlock->isBoundary = block.isBoundary; body->bound = aabb; body->material = mat; //if (exactRotation == false){ // double sphereInertia = 2.0/5.0*body->state->mass*pBlock->R*pBlock->R; // body->state->inertia= inertiaFactor*Vector3r(sphereInertia,sphereInertia,sphereInertia); //} //#if 0 //std::cout<<"beforeVertices"<a[i],pBlock->b[i], pBlock->c[i]); Vector3r plane2 = Vector3r(pBlock->a[j],pBlock->b[j], pBlock->c[j]); Vector3r plane3 = Vector3r(pBlock->a[k],pBlock->b[k], pBlock->c[k]); Vector3r centre = body->state->pos; double d1 = pBlock->d[i]+pBlock->r; double d2 = pBlock->d[j]+pBlock->r; double d3 = pBlock->d[k]+pBlock->r; D[0]=d1; D[1]=d2; D[2]=d3; Ax[0]=plane1.x(); Ax[3]=plane1.y(); Ax[6]=plane1.z(); Aplanes(0,0)= Ax[0]; Aplanes(0,1) = Ax[3]; Aplanes(0,2) = Ax[6]; Ax[1]=plane2.x(); Ax[4]=plane2.y(); Ax[7]=plane2.z(); Aplanes(1,0) = Ax[1]; Aplanes(1,1) = Ax[4];Aplanes(1,2) = Ax[7]; Ax[2]=plane3.x(); Ax[5]=plane3.y(); Ax[8]=plane3.z(); Aplanes(2,0) = Ax[2]; Aplanes(2,1) = Ax[5];Aplanes(2,2) = Ax[8]; bool parallel = false; if (fabs(plane1.dot(plane2))<1.0002 && fabs(plane1.dot(plane2))>0.9998){parallel = true;} if (fabs(plane1.dot(plane3))<1.0002 && fabs(plane1.dot(plane3))>0.9998){parallel = true;} if (fabs(plane2.dot(plane3))<1.0002 && fabs(plane2.dot(plane3))>0.9998){parallel = true;} double det = Aplanes.determinant(); if(fabs(det)>pow(10,-15) ){ int ipiv[3]; int bColNo=1; int info=0; /* LU */ int three =3; dgesv_( &three, &bColNo, Ax, &three, ipiv, D, &three, &info); if (info!=0){ //std::cout<<"linear algebra error"<a[i]*vertex.x() + pBlock->b[i]*vertex.y() + pBlock->c[i]*vertex.z() - pBlock->d[i]- pBlock->r; if (plane>pow(10,-3)){inside = false;} } if (inside == true){ //std::cout<<"vertex: "<verticesCD.push_back(vertex); int vertexID = pBlock->verticesCD.size()-1; pBlock->addVertexStruct(); pBlock->vertexStruct[vertexID].planeID.push_back(i); /*Note that the planeIDs are arranged from small to large! */ pBlock->vertexStruct[vertexID].planeID.push_back(j); /* planeIDs are arranged in the same sequence as [a,b,c] and d */ pBlock->vertexStruct[vertexID].planeID.push_back(k); /* vertices store information on planeIDs */ /*Planes */ pBlock->planeStruct[i].vertexID.push_back(vertexID); /* planes store information on vertexIDs */ pBlock->planeStruct[j].vertexID.push_back(vertexID); pBlock->planeStruct[k].vertexID.push_back(vertexID); } } } } } } //#endif #if 0 int vertexNo = pBlock->verticesCD.size(); int edgeCount=0; for (int i=0; ivertexStruct[i].planeID[0]; int v2a = pBlock->vertexStruct[i].planeID[1]; int v3a = pBlock->vertexStruct[i].planeID[2]; int v1b = pBlock->vertexStruct[j].planeID[0]; int v2b = pBlock->vertexStruct[j].planeID[1]; int v3b = pBlock->vertexStruct[j].planeID[2]; if( (v1a != v1b && v2a == v2b && v3a == v3b) || (v1a == v1b && v2a != v2b && v3a == v3b) || (v1a == v1b && v2a == v2b && v3a != v3b) ){ double length = ( pBlock->verticesCD[i] - pBlock->verticesCD[j] ).norm(); if(lengthaddEdgeStruct(); pBlock->edgeStruct[edgeCount].vertexID.push_back(i); /* edges store information on vertexIDs */ pBlock->edgeStruct[edgeCount].vertexID.push_back(j); pBlock->vertexStruct[i].edgeID.push_back(edgeCount); /* vertices store information on edgeIDs */ pBlock->vertexStruct[j].edgeID.push_back(edgeCount); edgeCount++; } } } #endif body->setAspherical(true); body->shape = pBlock; //std::cout<<"BLOCKGEN pBlock->verticesCD.size() "<verticesCD.size()<<", pBlock->vertexStruct.size(): "<vertexStruct.size()<& scene){ shared_ptr interactionGeometryDispatcher(new IGeomDispatcher); shared_ptr cd(new Ig2_PB_PB_ScGeom); cd-> stepAngle=calAreaStep; cd->twoDimension = twoDimension; interactionGeometryDispatcher->add(cd); shared_ptr interactionPhysicsDispatcher(new IPhysDispatcher); shared_ptr ss(new Ip2_FrictMat_FrictMat_KnKsPBPhys); ss->Knormal = Kn; ss->Kshear = Ks; ss->kn_i = Kn; ss->ks_i = Ks; ss->viscousDamping = viscousDamping; ss->useOverlapVol = useOverlapVol; ss->useFaceProperties = useFaceProperties; ss->unitWidth2D = unitWidth2D; ss->calJointLength = calJointLength; ss->twoDimension = twoDimension; ss->brittleLength = brittleLength; ss->u_peak = peakDisplacement; ss->maxClosure = maxClosure; interactionPhysicsDispatcher->add(ss); //shared_ptr gravityCondition(new GravityEngine); //gravityCondition->gravity = gravity; if (useGlobalStiffnessTimeStepper){ globalStiffnessTimeStepper=shared_ptr(new GlobalStiffnessTimeStepper); globalStiffnessTimeStepper->timeStepUpdateInterval = timeStepUpdateInterval; globalStiffnessTimeStepper->defaultDt = defaultDt; } scene->engines.clear(); scene->engines.push_back(shared_ptr(new ForceResetter)); shared_ptr collider(new InsertionSortCollider); collider->verletDist = 0.1*minSize; collider->boundDispatcher->add(new PotentialBlock2AABB); scene->engines.push_back(collider); shared_ptr ids(new InteractionLoop); ids->geomDispatcher=interactionGeometryDispatcher; ids->physDispatcher=interactionPhysicsDispatcher; ids->lawDispatcher=shared_ptr(new LawDispatcher); shared_ptr see(new Law2_SCG_KnKsPBPhys_KnKsPBLaw); see->traceEnergy = traceEnergy; see->Talesnick = Talesnick; see->neverErase = neverErase; ids->lawDispatcher->add(see); scene->engines.push_back(ids); //scene->engines.push_back(globalStiffnessTimeStepper); //scene->engines.push_back(gravityCondition); shared_ptr newton(new NewtonIntegrator); newton->damping=dampingMomentum; newton->gravity= gravity; //Vector3r(0,0,9.81); // FIXME: // newton->damping3DEC=damp3DEC; newton->exactAsphericalRot=exactRotation; scene->engines.push_back(newton); //scene->initializers.clear(); } Real BlockGen::evaluateFNoSphere(struct Block block, Vector3r presentTrial){ Real x = presentTrial[0]-block.centre[0]; Real y = presentTrial[1]-block.centre[1]; Real z = presentTrial[2]-block.centre[2]; int planeNo = block.a.size(); vectora; vectorb; vectorc; vectord; vectorp; Real pSum2 = 0.0; for (int i=0; i vertices; Vector3r pointInside = Vector3r(0,0,0); double totalVolume=0; int planeNo = block.a.size(); double D[3]; double Ax[9]; Eigen::Matrix3d Aplanes; Vector3r centroid(0,0,0); for (int i=0; i0.9998){parallel = true;} if (fabs(plane1.dot(plane3))<1.0002 && fabs(plane1.dot(plane3))>0.9998){parallel = true;} if (fabs(plane2.dot(plane3))<1.0002 && fabs(plane2.dot(plane3))>0.9998){parallel = true;} double det = Aplanes.determinant(); if(fabs(det)>pow(10,-15) ){ //if (parallel == false){ int ipiv[3]; int bColNo=1; int info=0; /* LU */ int three =3; dgesv_( &three, &bColNo, Ax, &three, ipiv, D, &three, &info); if (info!=0){ //std::cout<<"linear algebra error"<pow(10,-3)){inside = false;} } if (inside == true){ vertices.push_back(vertex); } } } } } } vector verticesOnPlane; vector oriVerticesOnPlane; for (int j=0; j0){ verticesOnPlane.clear(); oriVerticesOnPlane.clear(); } for (int i=0; i orderedVerticesOnPlane; vector oriOrderedVerticesOnPlane; int h = 0; int k = 1; int m =2; Vector3r pt1 = verticesOnPlane[h]; Vector3r pt2 = verticesOnPlane[k]; Vector3r pt3 = verticesOnPlane[m]; orderedVerticesOnPlane.push_back(pt1); oriOrderedVerticesOnPlane.push_back(oriVerticesOnPlane[0]); int counter = 1; while(counter(orderedVerticesOnPlane.size()); oriBaseOnPolygon = oriBaseOnPolygon/static_cast(oriOrderedVerticesOnPlane.size()); int lastEntry = orderedVerticesOnPlane.size(); vertexOnPlane(lastEntry,0)=orderedVerticesOnPlane[0].x(); vertexOnPlane(lastEntry,1)=orderedVerticesOnPlane[0].y(); //vertexOnPlane(lastEntry,2)=orderedVerticesOnPlane[0].z(); //std::cout<<"vertexOnPlane0: "< vertices; Vector3r pointInside = Vector3r(0,0,0); double totalVolume=0; int planeNo = block.a.size(); double D[3]; double Ax[9]; Eigen::Matrix3d Aplanes; Vector3r centroid(0,0,0); for (int i=0; i0.9998){parallel = true;} if (fabs(plane1.dot(plane3))<1.0002 && fabs(plane1.dot(plane3))>0.9998){parallel = true;} if (fabs(plane2.dot(plane3))<1.0002 && fabs(plane2.dot(plane3))>0.9998){parallel = true;} double det = Aplanes.determinant(); if(fabs(det)>pow(10,-15) ){ //if (parallel == false){ int ipiv[3]; int bColNo=1; int info=0; /* LU */ int three =3; dgesv_( &three, &bColNo, Ax, &three, ipiv, D, &three, &info); if (info!=0){ //std::cout<<"linear algebra error"<pow(10,-3)){inside = false;} } if (inside == true){ vertices.push_back(vertex); } } } } } } vector verticesOnPlane; vector oriVerticesOnPlane; for (int j=0; j0){ verticesOnPlane.clear(); oriVerticesOnPlane.clear(); } for (int i=0; i orderedVerticesOnPlane; vector oriOrderedVerticesOnPlane; int h = 0; int k = 1; int m =2; Vector3r pt1 = verticesOnPlane[h]; Vector3r pt2 = verticesOnPlane[k]; Vector3r pt3 = verticesOnPlane[m]; orderedVerticesOnPlane.push_back(pt1); oriOrderedVerticesOnPlane.push_back(oriVerticesOnPlane[0]); int counter = 1; while(counter(orderedVerticesOnPlane.size()); oriBaseOnPolygon = oriBaseOnPolygon/static_cast(oriOrderedVerticesOnPlane.size()); int lastEntry = orderedVerticesOnPlane.size(); vertexOnPlane(lastEntry,0)=orderedVerticesOnPlane[0].x(); vertexOnPlane(lastEntry,1)=orderedVerticesOnPlane[0].y(); //vertexOnPlane(lastEntry,2)=orderedVerticesOnPlane[0].z(); //std::cout<<"vertexOnPlane0: "<-pow(10,-12) || convergeSuccess !=0 ){ // delete & model2; return false; }else{ //delete & model2; return true; } } bool BlockGen::contactBoundaryLPCLP(struct Discontinuity joint, struct Block block, Vector3r& touchingPt){ if(block.tooSmall == true){return false;} Vector3r solution(0,0,0); /* Parameters for particles A and B */ int planeNoA = block.a.size(); /* Variables to keep things neat */ int NUMCON = planeNoA /*block inequality */; int NUMVAR = 3/*3D */; double s = 0.0; double xlocalA=0; double ylocalA = 0; double zlocalA = 0; Vector3r localA (0,0,0); Vector3r xGlobalA (0,0,0); /* LINEAR CONSTRAINTS */ ClpSimplex model2; model2.setOptimizationDirection(1); // Create space for 3 columns and 10000 rows int numberRows = NUMCON; int numberColumns = NUMVAR; model2.resize(0, numberColumns); // Columns - objective was packed model2.setObjectiveCoefficient(0, joint.a); model2.setObjectiveCoefficient(1, joint.b); model2.setObjectiveCoefficient(2, joint.c); model2.setColumnLower(0, -COIN_DBL_MAX); model2.setColumnLower(1, -COIN_DBL_MAX); model2.setColumnLower(2, -COIN_DBL_MAX); model2.setColumnUpper(0, COIN_DBL_MAX); model2.setColumnUpper(1, COIN_DBL_MAX); model2.setColumnUpper(2, COIN_DBL_MAX); double rowLower[numberRows]; double rowUpper[numberRows]; // Rows for(int i=0; iassignMatrix(true, numberRows, numberColumns, numberElements,&aval[0], &asub[0], &aptrb[0], &columnCount[0]); model2.setLogLevel(0); matrix->assignMatrix(true, numberRows, numberColumns, numberElements,elements, rows, starts, lengths); ClpPackedMatrix * clpMatrix = new ClpPackedMatrix(matrix); model2.replaceMatrix(clpMatrix, true); } model2.primal(); // model2.writeMps("contactBoundary.mps"); // Print column solution int numberColumns = model2.numberColumns(); // Alternatively getColSolution() double * columnPrimal = model2.primalColumnSolution(); xGlobalA = Vector3r(columnPrimal[0],columnPrimal[1],columnPrimal[2]); touchingPt = xGlobalA; int convergeSuccess = model2.status(); if(convergeSuccess==3 || convergeSuccess==4 ){ //delete & model2; return false; }else{ //delete & model2; return true; } return true; } bool BlockGen::checkRedundancyLPCLP(struct Discontinuity joint, struct Block block, Vector3r& touchingPt){ if(block.tooSmall == true){return false;} Vector3r solution(0,0,0); /* Parameters for particles A and B */ int planeNoA = block.a.size(); /* Variables to keep things neat */ int NUMCON = planeNoA /*block inequality */; int NUMVAR = 3/*3D */; double s = 0.0; double xlocalA=0; double ylocalA = 0; double zlocalA = 0; Vector3r localA (0,0,0); Vector3r xGlobalA (0,0,0); ClpSimplex model2; { model2.setOptimizationDirection(1); // Create space for 3 columns and 10000 rows int numberRows = NUMCON; int numberColumns = NUMVAR; // This is fully dense - but would not normally be so int numberElements = numberRows * numberColumns; // Arrays will be set to default values model2.resize(numberRows, numberColumns); double * elements = new double [numberElements]; CoinBigIndex * starts = new CoinBigIndex [numberColumns+1]; int * rows = new int [numberElements];; int * lengths = new int[numberColumns]; // Now fill in - totally unsafe but .... double * columnLower = model2.columnLower(); double * columnUpper = model2.columnUpper(); double * objective = model2.objective(); double * rowLower = model2.rowLower(); double * rowUpper = model2.rowUpper(); // Columns - objective was packed objective[0] = -joint.a; objective[1] = -joint.b; objective[2] = -joint.c; for (int k = 0; k < numberColumns; k++){ columnLower[k]= -COIN_DBL_MAX; columnUpper[k] = COIN_DBL_MAX; } // Rows for(int i=0; iassignMatrix(true, numberRows, numberColumns, numberElements,&aval[0], &asub[0], &aptrb[0], &columnCount[0]); model2.setLogLevel(0); matrix->assignMatrix(true, numberRows, numberColumns, numberElements,elements, rows, starts, lengths); ClpPackedMatrix * clpMatrix = new ClpPackedMatrix(matrix); model2.replaceMatrix(clpMatrix, true); } model2.scaling(0); model2.dual(); // model2.writeMps("redundancy.mps"); // Print column solution int numberColumns = model2.numberColumns(); // Alternatively getColSolution() double * columnPrimal = model2.primalColumnSolution(); xGlobalA = Vector3r(columnPrimal[0],columnPrimal[1],columnPrimal[2]); touchingPt = xGlobalA; Real f = touchingPt.x()*joint.a + touchingPt.y()*joint.b + touchingPt.z()*joint.c - joint.d-block.r; if(fabs(f)>pow(10,-3) ){ //delete & model2; return false; }else{ //delete & model2; return true; } } double BlockGen::inscribedSphereCLP(struct Block block, Vector3r& initialPoint, bool twoDimension){ /* minimise s */ /* s.t. Ax - s <= d*/ bool converge = true; /* Parameters for particles A and B */ double s = 0.0; /* get value of x[3] after optimization */ int planeNoA = block.a.size(); vector plane2Dno; if (twoDimension == true){ for (int i=0; i0.99 ){ plane2Dno.push_back(i); //std::cout<<"2d: "<& scene) { } #endif // YADE_POTENTIAL_BLOCKS trunk-2018.02b/pkg/dem/BlockGen.hpp000077500000000000000000000235331324306050200167220ustar00rootroot00000000000000/*CWBoon 2016 */ /* Please cite: */ /* CW Boon, GT Houlsby, S Utili (2015). A new rock slicing method based on linear programming. Computers and Geotechnics 65, 12-29. */ /* The numerical library is changed from CPLEX to CLP because subscription to the academic initiative is required to use CPLEX for free */ #ifdef YADE_POTENTIAL_BLOCKS #pragma once #include #include #include #include #include #include #include #include #include #include #include #include #include class GlobalStiffnessTimeStepper; class BlockGen : public FileGenerator { private : void createActors(shared_ptr& scene); void positionRootBody(shared_ptr& scene); shared_ptr globalStiffnessTimeStepper; protected: std::ofstream output2; std::string myfile; std::string Key; static std::ofstream output; public : ~BlockGen (); bool generate(string&); template double gen_normal_3(T &generator){ return generator();} struct Discontinuity{ Vector3r centre; Discontinuity(Vector3r pos){centre = pos; persistence = false; phi_b = 30.0; phi_r= 30.0; JRC=15; JCS = pow(10,6); asperity = 5; sigmaC = JCS;isBoundary= false;sliceBoundaries=false; lambda0=0.0; heatCapacity=0.0;hwater=-1.0;intactRock=false; throughGoing = false; constructionJoints=false;jointType=0;} Real a; Real b; Real c; Real d; Real a_p; Real b_p; Real c_p; Real d_p; bool persistence; bool isBoundary; bool sliceBoundaries; bool constructionJoints; vector persistence_a; vector persistence_b; vector persistence_c; vector persistence_d; /* Joint properties */ double phi_b; double phi_r; double JRC; double JCS; double asperity; double sigmaC; double cohesion; double tension; double lambda0; double heatCapacity; double hwater; bool intactRock; bool throughGoing; int jointType; }; struct Planes{ vector vertexID; }; struct Block{ Vector3r tempCentre; Vector3r centre; Block(Vector3r pos, Real kPP, Real rPP, Real RPP){centre = pos; k=kPP; r=rPP; R=RPP; tooSmall=false;isBoundary=false;tempCentre=pos;} vector a; vector b; vector c; vector d; vector redundant; vector isBoundaryPlane; bool isBoundary; vector subMembers; vector falseVertex; vector node; Real gridVol; Real r; Real R; Real k; bool tooSmall; /* Joint properties */ vector phi_b; vector phi_r; vector JRC; vector JCS; vector asperity; vector sigmaC; vector cohesion; vector tension; vector lambda0; vector heatCapacity; vector hwater; vector intactRock; vector jointType; vector planes; }; double getSignedArea(const Vector3r pt1,const Vector3r pt2, const Vector3r pt3); double getDet(const Eigen::MatrixXd A); double getCentroidTetrahedron(const Eigen::MatrixXd A); bool createBlock(shared_ptr& body, struct BlockGen::Block block, int no); bool contactDetection(struct BlockGen::Discontinuity joint, struct BlockGen::Block block, Vector3r& touchingPt); bool contactDetectionLPCLP(struct BlockGen::Discontinuity joint, struct BlockGen::Block block, Vector3r& touchingPt); bool contactDetectionLPCLPglobal(struct BlockGen::Discontinuity joint, struct BlockGen::Block block, Vector3r& touchingPt); bool checkRedundancyLPCLP(struct BlockGen::Discontinuity joint, struct BlockGen::Block block, Vector3r& touchingPt); bool startingPointFeasibility(struct BlockGen::Block block, Vector3r& initialPoint); double inscribedSphereCLP(struct BlockGen::Block block, Vector3r& initialPoint, bool twoDimension); bool contactBoundaryLPCLP(struct BlockGen::Discontinuity joint, struct BlockGen::Block block, Vector3r& touchingPt); bool contactBoundaryLPCLPslack(struct BlockGen::Discontinuity joint, struct BlockGen::Block block, Vector3r& touchingPt); void calculateInertia(struct Block block, Real& Ixx, Real& Iyy, Real& Izz,Real& Ixy, Real& Ixz, Real& Iyz); Vector3r calCentroid(struct Block block, double & blockVol); bool checkCentroid(struct Block block, Vector3r presentTrial); Real evaluateFNoSphere(struct Block block, Vector3r presentTrial); YADE_CLASS_BASE_DOC_ATTRS_INIT_CTOR_PY( BlockGen,FileGenerator,"Prepare a scene for triaxial tests. See full documentation at http://yade-dem.org/wiki/TriaxialTest." , /* public */ ((Real,dampingMomentum,0.2,,"Coefficient of Cundal-Non-Viscous damping (applied on on the 3 components of torques)")) ((Real, maxClosure, 0.0002, ,"vmi")) ((Real, peakDisplacement, 0.02, ,"vmi")) ((double, brittleLength, 2.0, ,"brittle rock")) ((Real,damp3DEC,0.8,,"Coefficient of Cundal-Non-Viscous damping (applied on on the 3 components of torques)")) ((Real,unitWidth2D,1.0,,"out of plane distance")) ((Real,density,2600,,"density of blocks")) ((Real,Kn,pow(10,-5),,"density of blocks")) ((Real,Ks,pow(10,-5),,"density of blocks")) ((Real,frictionDeg,18.0,,"friction angle [°]")) ((Vector3r,globalOrigin,Vector3r(0.0,0.0,0.0),,"friction angle [°]")) ((Real,inertiaFactor,1.0,,"to avoid wobbling")) ((Real,rForPP,0.1,,"r")) ((Real,kForPP,0.01,,"k")) ((Real,RForPP,3.5,,"R")) ((int,numberOfGrids,1,,"R")) ((bool,probabilisticOrientation,false,,"use random number generator")) ((bool,Talesnick,false,,"R")) ((bool,neverErase,false,,"erase non interacting contacts")) ((bool,calJointLength,false,,"whether to calculate jointLength")) ((bool,twoDimension,false,,"whether it is 2D")) ((Real,shrinkFactor,1.0,,"ratio to shrink r")) ((Real,viscousDamping,0.8,,"viscous damping")) ((bool, intactRockDegradation, false, ,"brittle rock")) ((Real,initialOverlap,0.0,,"initial overlap between blocks")) ((Vector3r,gravity,Vector3r(0.0,-9.81,0.0),,"gravity")) ((Real,defaultDt,-1,,"Max time-step. Used as initial value if defined. Latter adjusted by the time stepper.")) ((int,timeStepUpdateInterval,50,,"interval for :yref:`GlobalStiffnessTimeStepper`")) ((bool,traceEnergy,true,,"friction angle [°]")) ((bool,exactRotation,true,,"friction angle [°]")) ((Real,minSize,50.0,,"length X of domain")) ((Real,maxRatio,3.0,,"length X of domain")) ((Real,boundarySizeXmax,1.0,,"length X of domain")) ((Real,boundarySizeYmax,1.0,,"length Y of domain")) ((Real,boundarySizeZmax,1.0,,"length Z of domain")) ((Real,boundarySizeXmin,1.0,,"length X of domain")) ((Real,boundarySizeYmin,1.0,,"length Y of domain")) ((Real,boundarySizeZmin,1.0,,"length Z of domain")) ((Vector3r,directionA,Vector3r(1,0,0),,"length Z of domain")) ((Vector3r,directionB,Vector3r(0,1,0),,"length Z of domain")) ((Vector3r,directionC,Vector3r(0,0,1),,"length Z of domain")) ((Real,calAreaStep,10.0,,"length Z of domain")) ((Real,extremeDist,0.5,,"boundary to base calculation of octree algorithms")) //FIXME remove when not needed anymore ((Real,subdivisionRatio,0.1,,"smallest size/boundary of octree algorithms")) ((vector,joint_a,,,"a")) ((vector,joint_b,,,"b")) ((vector,joint_c,,,"c")) ((vector,joint_d,,,"d")) /* check for different joint types */ ((bool, persistentPlanes,false,,"check persistence")) ((bool, jointProbabilistic,false,,"check for filename jointProbabilistic")) ((bool, opening,false,,"check for filename opening")) ((bool, boundaries,false,,"check for filename boundaries")) ((bool, slopeFace,false,,"check for filename boundaries")) ((bool, sliceBoundaries,false,,"check for filename boundaries")) ((bool, useGlobalStiffnessTimeStepper,false,,"check for filename boundaries")) /* which contact law to use */ ((bool, useBartonBandis,false,,"whether to use Barton Bandis contact law")) ((bool, useFaceProperties, false, , "use face properties")) ((bool, useOverlapVol,false,,"check for filename opening")) ((std::string,filenamePersistentPlanes,"/home/booncw/yade/BranchA/scripts/Tunnel/jointPersistent.csv",,"filename to look for joint properties")) ((std::string,filenameProbabilistic,"/home/booncw/yade/BranchA/scripts/boon/Tunnel/jointProbabilistic.csv",,"filename to look for joint with probabilistic models")) ((std::string,filenameBoundaries,"/home/booncw/yade/BranchA/scripts/boon/Tunnel/boundaries.csv",,"filename to look for joint with probabilistic models")) ((std::string,filenameOpening,"/home/booncw/yade/BranchA/scripts/boon/Tunnel/opening.csv",,"filename to look for joint outline of joints")) ((std::string,filenameSlopeFace,"/home/booncw/yade/BranchA/scripts/boon/Tunnel/opening.csv",,"filename to look for joint outline of joints")) ((std::string,filenameSliceBoundaries,"/home/booncw/yade/BranchA/scripts/boon/Tunnel/sliceBoundaries.csv",,"filename to look for joint outline of joints")) //((double*, array_a,,,"a")) , /* init */ , /* constructor for private */ Key =""; myfile = "./BlkGen"+Key; output2.open(myfile.c_str(), fstream::app); , //.def("setContactProperties",&TriaxialCompressionEngine::setContactProperties,"Assign a new friction angle (degrees) to dynamic bodies and relative interactions") ); DECLARE_LOGGER; }; #ifdef __cplusplus extern "C" { #endif void dgesv_(const int *N, const int *nrhs, double *Hessian, const int *lda, int *ipiv, double *gradient, const int *ldb, int *info); void dsyev_(const char *jobz, const char *uplo, const int *N, double *A, const int *lda, double *W, double *work, int *lwork, int *info); #ifdef __cplusplus }; #endif REGISTER_SERIALIZABLE(BlockGen); #endif // YADE_POTENTIAL_BLOCKS trunk-2018.02b/pkg/dem/BubbleMat.cpp000066400000000000000000000071351324306050200170630ustar00rootroot00000000000000#include "BubbleMat.hpp" YADE_PLUGIN((BubbleMat)(Ip2_BubbleMat_BubbleMat_BubblePhys)(BubblePhys)(Law2_ScGeom_BubblePhys_Bubble)); /********************** Ip2_BubbleMat_BubbleMat_BubblePhys ****************************/ CREATE_LOGGER(Ip2_BubbleMat_BubbleMat_BubblePhys); void Ip2_BubbleMat_BubbleMat_BubblePhys::go(const shared_ptr& m1, const shared_ptr& m2, const shared_ptr& interaction){ // phys already exists if (interaction->phys) return; shared_ptr phys(new BubblePhys()); interaction->phys = phys; } /********************** BubblePhys ****************************/ CREATE_LOGGER(BubblePhys); void BubblePhys::computeCoeffs(Real pctMaxForce, Real surfaceTension, Real c1) { Real Fmax = pctMaxForce*c1*rAvg; Real logPct = log(pctMaxForce/4); Dmax = pctMaxForce*rAvg*logPct; Real dfdd = c1/(logPct+1); coeffB = dfdd/Fmax; coeffA = Fmax/exp(coeffB*Dmax); fN = 0.1*Fmax; } Real BubblePhys::computeForce(Real separation, Real surfaceTension, Real rAvg, int newtonIter, Real newtonTol, Real c1, Real fN, BubblePhys* phys) { if (separation >= phys->Dmax) { Real f,df,g,retOld,residual; Real c2 = 1./(4*c1*rAvg); Real ret = fN; int i = 0; do { //[Chan2011], Loop solves modified form of equation (25) using Newton-Raphson method retOld = ret; g = log(ret*c2); f = separation*c1 - ret*g; df = g+1; ret += f/df; if(ret <= 0.0){ //Need to make sure ret > 0, otherwise the next iteration will try to evaluate the natural logarithm of a negative number, which results in NaN ret = 0.9*fabs(ret); residual = newtonTol*2; //Also, if, by chance, retOld = 0.9*ret it would cause the loop to exit based on the boolean residual > newtonTol, so we force the next iteration by setting residual = newtonTol*2 } else {residual = fabs(ret - retOld)/retOld;} if (i++ > newtonIter) { throw runtime_error("BubblePhys::computeForce: no convergence\n"); } } while (residual > newtonTol); return ret; } else { //Artificial Extension of [Chan2011] equation 25 to approximiate behaviour outside the valid regime (large penetration cases) return phys->coeffA*exp(phys->coeffB*separation); } } /********************** Law2_ScGeom_BubblePhys_Bubble ****************************/ CREATE_LOGGER(Law2_ScGeom_BubblePhys_Bubble); bool Law2_ScGeom_BubblePhys_Bubble::go(shared_ptr& _geom, shared_ptr& _phys, Interaction* I){ ScGeom* geom=static_cast(_geom.get()); BubblePhys* phys=static_cast(_phys.get()); if(geom->penetrationDepth <= 0.0) { return false; } if (I->isFresh(scene)) { c1 = 2*Mathr::PI*surfaceTension; phys->rAvg = .5*(geom->refR1 + geom->refR2); phys->computeCoeffs(pctMaxForce,surfaceTension,c1); } Real &fN = phys->fN; fN = phys->computeForce(-geom->penetrationDepth, surfaceTension, phys->rAvg, phys->newtonIter, phys->newtonTol, c1, fN, phys); phys->fN = fN; Vector3r &normalForce = phys->normalForce; normalForce = fN*geom->normal; int id1 = I->getId1(), id2 = I->getId2(); if (!scene->isPeriodic) { State* b1 = Body::byId(id1,scene)->state.get(); State* b2 = Body::byId(id2,scene)->state.get(); applyForceAtContactPoint(-normalForce, geom->contactPoint , id1, b1->se3.position, id2, b2->se3.position); } else { scene->forces.addForce(id1,-normalForce); scene->forces.addForce(id2,normalForce); scene->forces.addTorque(id1,(geom->radius1-0.5*geom->penetrationDepth)* geom->normal.cross(normalForce)); scene->forces.addTorque(id2,(geom->radius2-0.5*geom->penetrationDepth)* geom->normal.cross(normalForce)); } return true; } trunk-2018.02b/pkg/dem/BubbleMat.hpp000066400000000000000000000100761324306050200170660ustar00rootroot00000000000000#pragma once #include #include #include #include namespace py=boost::python; /********************** BubbleMat ****************************/ class BubbleMat : public Material { public: YADE_CLASS_BASE_DOC_ATTRS_CTOR(BubbleMat,Material,"material for bubble interactions, for use with other Bubble classes", ((Real,surfaceTension,0.07197,,"The surface tension in the fluid surrounding the bubbles. The default value is that of water at 25 degrees Celcius.")) , createIndex(); density=1000; ); REGISTER_CLASS_INDEX(BubbleMat,Material); }; REGISTER_SERIALIZABLE(BubbleMat); /********************** BubblePhys ****************************/ class BubblePhys : public IPhys { private: Real coeffA,coeffB; //Coefficents for artificial curve public: void computeCoeffs(Real pctMaxForce,Real surfaceTension, Real c1); static Real computeForce(Real separation, Real surfaceTension, Real rAvg, int newtonIter, Real newtonTol, Real c1, Real fN, BubblePhys* phys); virtual ~BubblePhys(){}; YADE_CLASS_BASE_DOC_ATTRS_CTOR_PY(BubblePhys,IPhys,"Physics of bubble-bubble interactions, for use with BubbleMat", ((Vector3r,normalForce,Vector3r::Zero(),,"Normal force")) ((Real,surfaceTension,NaN,,"Surface tension of the surrounding liquid")) ((Real,fN,NaN,,"Contact normal force")) ((Real,rAvg,NaN,,"Average radius of the two interacting bubbles")) ((Real,Dmax,NaN,,"Maximum penetrationDepth of the bubbles before the force displacement curve changes to an artificial exponential curve. Setting this value will have no effect. See Law2_ScGeom_BubblePhys_Bubble::pctMaxForce for more information")) ((Real,newtonIter,50,,"Maximum number of force iterations allowed")) ((Real,newtonTol,1e-6,,"Convergence criteria for force iterations")) , createIndex(); , .def("computeForce",&BubblePhys::computeForce,"Computes the normal force acting between the two interacting bubbles using the Newton-Rhapson method") .staticmethod("computeForce") ); DECLARE_LOGGER; REGISTER_CLASS_INDEX(BubblePhys,IPhys); }; REGISTER_SERIALIZABLE(BubblePhys); /********************** Ip2_BubbleMat_BubbleMat_BubblePhys ****************************/ class Ip2_BubbleMat_BubbleMat_BubblePhys : public IPhysFunctor{ public: virtual void go(const shared_ptr& m1, const shared_ptr& m2, const shared_ptr& interaction); FUNCTOR2D(BubbleMat,BubbleMat); DECLARE_LOGGER; YADE_CLASS_BASE_DOC_ATTRS(Ip2_BubbleMat_BubbleMat_BubblePhys,IPhysFunctor,"Generates bubble interactions.Used in the contact law Law2_ScGeom_BubblePhys_Bubble.", ); }; REGISTER_SERIALIZABLE(Ip2_BubbleMat_BubbleMat_BubblePhys); /********************** Law2_ScGeom_BubblePhys_Bubble ****************************/ class Law2_ScGeom_BubblePhys_Bubble : public LawFunctor{ private: Real c1; //Coeff used for many contacts public: virtual bool go(shared_ptr& _geom, shared_ptr& _phys, Interaction* interaction); FUNCTOR2D(GenericSpheresContact,BubblePhys); YADE_CLASS_BASE_DOC_ATTRS_CTOR_PY(Law2_ScGeom_BubblePhys_Bubble,LawFunctor,"Constitutive law for Bubble model.", ((Real,pctMaxForce,0.1,,"Chan[2011] states the contact law is valid only for small interferences; therefore an exponential force-displacement curve models the contact stiffness outside that regime (large penetration). This artificial stiffening ensures that bubbles will not pass through eachother or completely overlap during the simulation. The maximum force is Fmax = (2*pi*surfaceTension*rAvg). pctMaxForce is the percentage of the maximum force dictates the separation threshold, Dmax, for each contact. Penetrations less than Dmax calculate the reaction force from the derived contact law, while penetrations equal to or greater than Dmax calculate the reaction force from the artificial exponential curve.")) ((Real,surfaceTension,0.07197,,"The surface tension in the liquid surrounding the bubbles. The default value is that of water at 25 degrees Celcius.")) , /*ctor*/, ); DECLARE_LOGGER; }; REGISTER_SERIALIZABLE(Law2_ScGeom_BubblePhys_Bubble); trunk-2018.02b/pkg/dem/CapillaryPhys.cpp000066400000000000000000000030461324306050200200070ustar00rootroot00000000000000#include #include YADE_PLUGIN((CapillaryPhys)(Ip2_FrictMat_FrictMat_CapillaryPhys)); // The following code was moved from Ip2_FrictMat_FrictMat_CapillaryPhys.cpp void Ip2_FrictMat_FrictMat_CapillaryPhys::go( const shared_ptr& b1 //FrictMat , const shared_ptr& b2 // FrictMat , const shared_ptr& interaction) { ScGeom* geom = YADE_CAST(interaction->geom.get()); if(geom) { if(!interaction->phys) { const shared_ptr& sdec1 = YADE_PTR_CAST(b1); const shared_ptr& sdec2 = YADE_PTR_CAST(b2); if (!interaction->phys) interaction->phys = shared_ptr(new CapillaryPhys()); const shared_ptr& contactPhysics = YADE_PTR_CAST(interaction->phys); Real Ea = sdec1->young; Real Eb = sdec2->young; Real Va = sdec1->poisson; Real Vb = sdec2->poisson; Real Da = geom->radius1; // FIXME - multiply by factor of sphere interaction distance (so sphere interacts at bigger range that its geometrical size) Real Db = geom->radius2; // FIXME - as above Real fa = sdec1->frictionAngle; Real fb = sdec2->frictionAngle; Real Kn = 2*Ea*Da*Eb*Db/(Ea*Da+Eb*Db);//harmonic average of two stiffnesses Real Ks = 2*Ea*Da*Va*Eb*Db*Vb/(Ea*Da*Va+Eb*Db*Va);//harmonic average of two stiffnesses with ks=V*kn for each sphere contactPhysics->tangensOfFrictionAngle = std::tan(std::min(fa,fb)); contactPhysics->kn = Kn; contactPhysics->ks = Ks; } } }; trunk-2018.02b/pkg/dem/CapillaryPhys.hpp000066400000000000000000000067421324306050200200220ustar00rootroot00000000000000/************************************************************************* * Copyright (C) 2006 by luc Scholtes * * luc.scholtes@hmg.inpg.fr * * * * This program is free software; it is licensed under the terms of the * * GNU General Public License v2 or later. See file LICENSE for details. * *************************************************************************/ #pragma once #include #include #include class CapillaryPhys : public FrictPhys { public : int currentIndexes [4]; // used for faster interpolation (stores previous positions in tables) virtual ~CapillaryPhys() {}; YADE_CLASS_BASE_DOC_ATTRS_INIT_CTOR_PY(CapillaryPhys,FrictPhys,"Physics (of interaction) for :yref:`Law2_ScGeom_CapillaryPhys_Capillarity`.", ((bool,meniscus,false,Attr::readonly,"True when a meniscus with a non-zero liquid volume (:yref:`vMeniscus`) has been computed for this interaction")) ((bool,isBroken,false,,"Might be set to true by the user to make liquid bridge inactive (capillary force is zero)")) ((Real,capillaryPressure,0.,,"Value of the capillary pressure Uc. Defined as Ugas-Uliquid, obtained from :yref:`corresponding Law2 parameter`")) ((Real,vMeniscus,0.,,"Volume of the meniscus")) ((Real,Delta1,0.,,"Defines the surface area wetted by the meniscus on the smallest grains of radius R1 (R1` + :yref:`nn33`.")) ((Real,nn33,0.,,":math:`\\iint_A n_3 n_3 \\, dS`, $A$ being the liquid-gas surface of the meniscus, $\\vec n$ the associated normal, and $(1,2,3)$ a local basis with $3$ the meniscus orientation (:yref:`ScGeom.normal`). NB: $A$ = 2 :yref:`nn11` + :yref:`nn33`.")) ,, createIndex();currentIndexes[0]=currentIndexes[1]=currentIndexes[2]=currentIndexes[3]=0; , ); REGISTER_CLASS_INDEX(CapillaryPhys,FrictPhys); }; REGISTER_SERIALIZABLE(CapillaryPhys); class Ip2_FrictMat_FrictMat_CapillaryPhys : public IPhysFunctor { public : virtual void go( const shared_ptr& b1, const shared_ptr& b2, const shared_ptr& interaction); FUNCTOR2D(FrictMat,FrictMat); YADE_CLASS_BASE_DOC_ATTRS_CTOR(Ip2_FrictMat_FrictMat_CapillaryPhys,IPhysFunctor, "RelationShips to use with :yref:`Law2_ScGeom_CapillaryPhys_Capillarity`.\n\n In these RelationShips all the interaction attributes are computed. \n\n.. warning::\n\tas in the others :yref:`Ip2 functors`, most of the attributes are computed only once, when the interaction is new.",,;); }; REGISTER_SERIALIZABLE(Ip2_FrictMat_FrictMat_CapillaryPhys); trunk-2018.02b/pkg/dem/CapillaryPhys1.cpp000066400000000000000000000036461324306050200200760ustar00rootroot00000000000000//keep this #ifdef as long as you don't really want to realize a final version publicly, it will save compilation time for everyone else //when you want it compiled, you can just uncomment the following line // #define CAPILLARYPHYS1 #ifdef CAPILLARYPHYS1 #include #include #include #include CapillaryPhys1::~CapillaryPhys1() { } YADE_PLUGIN((CapillaryPhys1)); YADE_PLUGIN((Ip2_FrictMat_FrictMat_CapillaryPhys1)); void Ip2_FrictMat_FrictMat_CapillaryPhys1::go( const shared_ptr& b1 //FrictMat , const shared_ptr& b2 // FrictMat , const shared_ptr& interaction) { ScGeom* geom = YADE_CAST(interaction->geom.get()); if(geom) { if(!interaction->phys) { const shared_ptr& sdec1 = YADE_PTR_CAST(b1); const shared_ptr& sdec2 = YADE_PTR_CAST(b2); if (!interaction->phys) interaction->phys = shared_ptr(new CapillaryPhys1()); const shared_ptr& contactPhysics = YADE_PTR_CAST(interaction->phys); Real Ea = sdec1->young; Real Eb = sdec2->young; Real Va = sdec1->poisson; Real Vb = sdec2->poisson; Real Da = geom->radius1; // FIXME - multiply by factor of sphere interaction distance (so sphere interacts at bigger range that its geometrical size) Real Db = geom->radius2; // FIXME - as above Real fa = sdec1->frictionAngle; Real fb = sdec2->frictionAngle; Real Kn = 2*Ea*Da*Eb*Db/(Ea*Da+Eb*Db);//harmonic average of two stiffnesses Real Ks = 2*Ea*Da*Va*Eb*Db*Vb/(Ea*Da*Va+Eb*Db*Va);//harmonic average of two stiffnesses with ks=V*kn for each sphere contactPhysics->tangensOfFrictionAngle = std::tan(std::min(fa,fb)); contactPhysics->kn = Kn; contactPhysics->ks = Ks; contactPhysics->computeBridge =computeDefault; } } }; #endif //CAPILLARYPHYS1 trunk-2018.02b/pkg/dem/CapillaryPhys1.hpp000066400000000000000000000124661324306050200201030ustar00rootroot00000000000000/************************************************************************* * Copyright (C) 2006 by luc Scholtes * * luc.scholtes@hmg.inpg.fr * * * * This program is free software; it is licensed under the terms of the * * GNU General Public License v2 or later. See file LICENSE for details. * *************************************************************************/ #pragma once #include #include #include #include class MeniscusPhysicalData { public: double R; double volume; double distance; double surface; double energy; double force; double succion; double delta1; double delta2; double arcLength; bool ending; //default ctor MeniscusPhysicalData() : R(0), volume(0), distance(0), surface(0), energy(0), force(0), succion(0), delta1(0), delta2(0), arcLength(0), ending(1) {} //ctor with input values MeniscusPhysicalData(const double& r, const double& v, const double& d, const double& s, const double& e, const double& f, const double& p, const double& a1, const double& a2, const double& arc, bool end) : R(r), volume(v), distance(d), surface(s), energy(e), force(f), succion(p), delta1(a1), delta2(a2), arcLength(arc), ending(end) {} //a minimal list of operators for the interpolation //these operators are requirements for the DataType template parameter of interpolate() //FIXME: algebra operations must include energy, perimeter, and any other new variable for including them in the interpolation MeniscusPhysicalData& operator+= (const MeniscusPhysicalData& m2) { R+=m2.R; volume+=m2.volume; distance+=m2.distance; surface+=m2.surface; energy+=m2.energy; force+=m2.force; succion+=m2.succion; delta1+=m2.delta1; delta2+=m2.delta2; arcLength+=m2.arcLength; ending=(ending && m2.ending); return *this;} MeniscusPhysicalData operator* (const double& fact) const { return MeniscusPhysicalData(fact*R, fact*volume, fact*distance, fact*surface, fact*energy, fact*force, fact*succion, fact*delta1, fact*delta2, fact*arcLength, ending);} const MeniscusPhysicalData& operator= (const MeniscusPhysicalData& m1) { R=m1.R; volume=m1.volume; distance=m1.distance; surface=m1.surface; energy=m1.energy; force=m1.force; succion=m1.succion; delta1=m1.delta1; delta2=m1.delta2; arcLength=m1.arcLength; ending=m1.ending; return *this;} }; //The structure for the meniscus: physical properties + cached values for fast interpolation class Meniscus { public: typedef MeniscusPhysicalData Data; Data data; //the variables of Laplace's problem DT::Cell_handle cell; //pointer to the last location in the triangulation, for faster locate() std::vector normals;// 4 normals relative to the current cell Meniscus() : data(), cell(DT::Cell_handle()), normals(std::vector()) {} }; class CapillaryPhys1 : public FrictPhys { public : int currentIndexes [4]; // used for faster interpolation (stores previous positions in tables) Meniscus m; virtual ~CapillaryPhys1(); YADE_CLASS_BASE_DOC_ATTRS_CTOR(CapillaryPhys1,FrictPhys,"Physics (of interaction) for Law2_ScGeom_CapillaryPhys_Capillarity.", ((bool,meniscus,false,,"Presence of a meniscus if true")) ((bool,isBroken,false,,"If true, capillary force is zero and liquid bridge is inactive.")) ((bool,computeBridge,true,,"If true, capillary bridge will be computed if not it will be ignored.")) ((Real,capillaryPressure,0.,,"Value of the capillary pressure Uc defines as Ugas-Uliquid")) ((Real,vMeniscus,0.,,"Volume of the menicus")) ((Real,Delta1,0.,,"Defines the surface area wetted by the meniscus on the smallest grains of radius R1 (R1& b1, const shared_ptr& b2, const shared_ptr& interaction); FUNCTOR2D(FrictMat,FrictMat); YADE_CLASS_BASE_DOC_ATTRS_CTOR(Ip2_FrictMat_FrictMat_CapillaryPhys1,IPhysFunctor, "RelationShips to use with Law2_ScGeom_CapillaryPhys_Capillarity1\n\n In these RelationShips all the interaction attributes are computed. \n\n.. warning::\n\tas in the others :yref:`Ip2 functors`, most of the attributes are computed only once, when the interaction is new.", ((bool,computeDefault,true,,"bool to assign the default value of computeBridge.")),; ); }; REGISTER_SERIALIZABLE(Ip2_FrictMat_FrictMat_CapillaryPhys1); trunk-2018.02b/pkg/dem/CapillaryStressRecorder.cpp000066400000000000000000000120101324306050200220240ustar00rootroot00000000000000/************************************************************************* * Copyright (C) 2006 by luc scholtes * * luc.scholtes@hmg.inpg.fr * * * * This program is free software; it is licensed under the terms of the * * GNU General Public License v2 or later. See file LICENSE for details. * *************************************************************************/ #include "CapillaryStressRecorder.hpp" #include #include #include #include #include #include #include YADE_PLUGIN((CapillaryStressRecorder)); CREATE_LOGGER(CapillaryStressRecorder); void CapillaryStressRecorder::action() { shared_ptr& bodies = scene->bodies; // at the beginning of the file; write column titles if(out.tellp()==0){ out<<"iteration Scap11 Scap22 Scap33 Scap12 Scap13 Scap23 Uc Sr w"< >::iterator itFirst = scene->engines.begin(); vector >::iterator itLast = scene->engines.end(); for ( ;itFirst!=itLast; ++itFirst ) { if ( ( *itFirst )->getClassName() == "TriaxialCompressionEngine") { LOG_DEBUG ( "stress controller engine found" ); triaxialCompressionEngine = YADE_PTR_CAST ( *itFirst ); } } if ( !triaxialCompressionEngine ) { LOG_ERROR ("Stress controller engine not found, the recorder cannot be used."); return;} } Real f1_cap_x=0, f1_cap_y=0, f1_cap_z=0, x1=0, y1=0, z1=0, x2=0, y2=0, z2=0; Real sig11_cap=0, sig22_cap=0, sig33_cap=0, sig12_cap=0, sig13_cap=0, sig23_cap=0, Vwater = 0, capillaryPressure = 0; int j = 0; InteractionContainer::iterator ii = scene->interactions->begin(); InteractionContainer::iterator iiEnd = scene->interactions->end(); for( ; ii!=iiEnd ; ++ii ) { if ((*ii)->isReal()) { const shared_ptr& interaction = *ii; CapillaryPhys* meniscusParameters = static_cast(interaction->phys.get()); if (meniscusParameters->meniscus) { j=j+1; unsigned int id1 = interaction -> getId1(); unsigned int id2 = interaction -> getId2(); Vector3r fcap = meniscusParameters->fCap; f1_cap_x=fcap[0]; f1_cap_y=fcap[1]; f1_cap_z=fcap[2]; Body* de1 = (*bodies)[id1].get(); Body* de2 = (*bodies)[id2].get(); x1 = de1->state->pos[0]; y1 = de1->state->pos[1]; z1 = de1->state->pos[2]; x2 = de2->state->pos[0]; y2 = de2->state->pos[1]; z2 = de2->state->pos[2]; /// capillary stresses from contact forces sig11_cap = sig11_cap + f1_cap_x*(x1 - x2); sig22_cap = sig22_cap + f1_cap_y*(y1 - y2); sig33_cap = sig33_cap + f1_cap_z*(z1 - z2); sig12_cap = sig12_cap + f1_cap_x*(y1 - y2); sig13_cap = sig13_cap + f1_cap_x*(z1 - z2); sig23_cap = sig23_cap + f1_cap_y*(z1 - z2); /// Liquid volume Vwater += meniscusParameters->vMeniscus; capillaryPressure=meniscusParameters->capillaryPressure; } } } /// Sample volume Real V = ( triaxialCompressionEngine->height ) * ( triaxialCompressionEngine->width ) * ( triaxialCompressionEngine->depth ); /// Solid volume Real Vs = 0, Rbody = 0, SR = 0; BodyContainer::iterator bi = bodies->begin(); BodyContainer::iterator biEnd = bodies->end(); for ( ; bi!=biEnd; ++bi) { shared_ptr b = *bi; if (b->shape->getClassIndex()!=Sphere::getClassIndexStatic()) continue; Sphere* sphere = static_cast(b->shape.get()); if (sphere) { Rbody = sphere->radius; SR+=Rbody; Vs += 1.333*Mathr::PI*(Rbody*Rbody*Rbody); } } Real Vv = V - Vs; Real Sr = 100*Vwater/Vv; if (Sr>100) Sr=100; Real w = 100*Vwater/V; if (w>(100*Vv/V)) w=100*(Vv/V); /// homogeneized capillary stresses Real SIG_11_cap=0, SIG_22_cap=0, SIG_33_cap=0, SIG_12_cap=0, SIG_13_cap=0, SIG_23_cap=0; SIG_11_cap = sig11_cap/V; SIG_22_cap = sig22_cap/V; SIG_33_cap = sig33_cap/V; SIG_12_cap = sig12_cap/V; SIG_13_cap = sig13_cap/V; SIG_23_cap = sig23_cap/V; out << boost::lexical_cast ( scene->iter ) << " " << boost::lexical_cast(SIG_11_cap) << " " << boost::lexical_cast(SIG_22_cap) << " " << boost::lexical_cast(SIG_33_cap) << " " << boost::lexical_cast(SIG_12_cap) << " " << boost::lexical_cast(SIG_13_cap)<< " " << boost::lexical_cast(SIG_23_cap)<< " " << boost::lexical_cast(capillaryPressure) << " " << boost::lexical_cast(Sr)<< " " << boost::lexical_cast(w)<< " " << endl; } trunk-2018.02b/pkg/dem/CapillaryStressRecorder.hpp000066400000000000000000000022271324306050200220420ustar00rootroot00000000000000/************************************************************************* * Copyright (C) 2006 by luc Scholtes * * luc.scholtes@hmg.inpg.fr * * * * This program is free software; it is licensed under the terms of the * * GNU General Public License v2 or later. See file LICENSE for details. * *************************************************************************/ #pragma once #include #include #include class TriaxialCompressionEngine; class CapillaryStressRecorder : public Recorder { private : shared_ptr triaxialCompressionEngine; public : virtual void action(); YADE_CLASS_BASE_DOC_ATTRS_CTOR(CapillaryStressRecorder,Recorder,"Records information from capillary meniscii on samples submitted to triaxial compressions. Classical sign convention (tension positiv) is used for capillary stresses. -> New formalism needs to be tested!!!",,initRun=true;); DECLARE_LOGGER; }; REGISTER_SERIALIZABLE(CapillaryStressRecorder); trunk-2018.02b/pkg/dem/CapillaryTriaxialTest.cpp000066400000000000000000000445351324306050200215110ustar00rootroot00000000000000/************************************************************************* * Copyright (C) 2006 by Bruno Chareyre * * bruno.chareyre@hmg.inpg.fr * * Copyright (C) 2006 by luc Scholtes * * luc.scholtes@hmg.inpg.fr * * * * This program is free software; it is licensed under the terms of the * * GNU General Public License v2 or later. See file LICENSE for details. * *************************************************************************/ #include "CapillaryTriaxialTest.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 // random #include #include #include #include typedef pair BasicSphere; //! generate a list of non-overlapping spheres string GenerateCloud_water(vector& sphere_list, Vector3r lowerCorner, Vector3r upperCorner, long number, Real rad_std_dev, Real porosity); CapillaryTriaxialTest::~CapillaryTriaxialTest () { } bool CapillaryTriaxialTest::generate(std::string& message) { // unsigned int startId=boost::numeric::bounds::highest(), endId=0; // record forces from group 2 message=""; scene = shared_ptr(new Scene); createActors(scene); positionRootBody(scene); shared_ptr body; if(boxWalls) { // bottom box Vector3r center = Vector3r( (lowerCorner[0]+upperCorner[0])/2, lowerCorner[1]-thickness/2.0, (lowerCorner[2]+upperCorner[2])/2); Vector3r halfSize = Vector3r( wallOversizeFactor*std::abs(lowerCorner[0]-upperCorner[0])/2+thickness, thickness/2.0, wallOversizeFactor*std::abs(lowerCorner[2]-upperCorner[2])/2+thickness); createBox(body,center,halfSize,wall_bottom_wire); if(wall_bottom) { scene->bodies->insert(body); triaxialcompressionEngine->wall_bottom_id = body->getId(); //triaxialStateRecorder->wall_bottom_id = body->getId(); } //forcerec->id = body->getId(); // top box center = Vector3r( (lowerCorner[0]+upperCorner[0])/2, upperCorner[1]+thickness/2.0, (lowerCorner[2]+upperCorner[2])/2); halfSize = Vector3r( wallOversizeFactor*std::abs(lowerCorner[0]-upperCorner[0])/2+thickness, thickness/2.0, wallOversizeFactor*std::abs(lowerCorner[2]-upperCorner[2])/2+thickness); createBox(body,center,halfSize,wall_top_wire); if(wall_top) { scene->bodies->insert(body); triaxialcompressionEngine->wall_top_id = body->getId(); //triaxialStateRecorder->wall_top_id = body->getId(); } // box 1 center = Vector3r( lowerCorner[0]-thickness/2.0, (lowerCorner[1]+upperCorner[1])/2, (lowerCorner[2]+upperCorner[2])/2); halfSize = Vector3r( thickness/2.0, wallOversizeFactor*std::abs(lowerCorner[1]-upperCorner[1])/2+thickness, wallOversizeFactor*std::abs(lowerCorner[2]-upperCorner[2])/2+thickness); createBox(body,center,halfSize,wall_1_wire); if(wall_1) { scene->bodies->insert(body); triaxialcompressionEngine->wall_left_id = body->getId(); //triaxialStateRecorder->wall_left_id = body->getId(); } // box 2 center = Vector3r( upperCorner[0]+thickness/2.0, (lowerCorner[1]+upperCorner[1])/2, (lowerCorner[2]+upperCorner[2])/2); halfSize = Vector3r( thickness/2.0, wallOversizeFactor*std::abs(lowerCorner[1]-upperCorner[1])/2+thickness, wallOversizeFactor*std::abs(lowerCorner[2]-upperCorner[2])/2+thickness); createBox(body,center,halfSize,wall_2_wire); if(wall_2) { scene->bodies->insert(body); triaxialcompressionEngine->wall_right_id = body->getId(); //triaxialStateRecorder->wall_right_id = body->getId(); } // box 3 center = Vector3r( (lowerCorner[0]+upperCorner[0])/2, (lowerCorner[1]+upperCorner[1])/2, lowerCorner[2]-thickness/2.0); halfSize = Vector3r( wallOversizeFactor*std::abs(lowerCorner[0]-upperCorner[0])/2+thickness, wallOversizeFactor*std::abs(lowerCorner[1]-upperCorner[1])/2+thickness, thickness/2.0); createBox(body,center,halfSize,wall_3_wire); if(wall_3) { scene->bodies->insert(body); triaxialcompressionEngine->wall_back_id = body->getId(); //triaxialStateRecorder->wall_back_id = body->getId(); } // box 4 center = Vector3r( (lowerCorner[0]+upperCorner[0])/2, (lowerCorner[1]+upperCorner[1])/2, upperCorner[2]+thickness/2.0); halfSize = Vector3r( wallOversizeFactor*std::abs(lowerCorner[0]-upperCorner[0])/2+thickness, wallOversizeFactor*std::abs(lowerCorner[1]-upperCorner[1])/2+thickness, thickness/2.0); createBox(body,center,halfSize,wall_3_wire); if(wall_4) { scene->bodies->insert(body); triaxialcompressionEngine->wall_front_id = body->getId(); //triaxialStateRecorder->wall_front_id = body->getId(); } } //convert the original sphere vector (with clump info) to a BasicSphere vector. vector sphere_list; typedef boost::tuple tupleVector3rRealInt; if(importFilename!=""){ vector >sphereListClumpInfo = Shop::loadSpheresFromFile(importFilename,lowerCorner,upperCorner); FOREACH(tupleVector3rRealInt t, sphereListClumpInfo){ sphere_list.push_back(make_pair(boost::get<0>(t),boost::get<1>(t))); }; } else message+=GenerateCloud_water(sphere_list, lowerCorner, upperCorner, numberOfGrains, Rdispersion, 0.75); vector::iterator it = sphere_list.begin(); vector::iterator it_end = sphere_list.end(); for (;it!=it_end; ++it) { cerr << "sphere (" << it->first << " " << it->second << ")"<first,it->second,false,true); scene->bodies->insert(body); } return true; // return "Generated a sample inside box of dimensions: (" // + boost::lexical_cast(lowerCorner[0]) + "," // + boost::lexical_cast(lowerCorner[1]) + "," // + boost::lexical_cast(lowerCorner[2]) + ") and (" // + boost::lexical_cast(upperCorner[0]) + "," // + boost::lexical_cast(upperCorner[1]) + "," // + boost::lexical_cast(upperCorner[2]) + ")."; } void CapillaryTriaxialTest::createSphere(shared_ptr& body, Vector3r position, Real radius, bool big, bool dynamic ) { body = shared_ptr(new Body); body->groupMask=2; shared_ptr physics(new FrictMat); shared_ptr aabb(new Aabb); shared_ptr iSphere(new Sphere); Quaternionr q(Mathr::SymmetricRandom(),Mathr::SymmetricRandom(),Mathr::SymmetricRandom(),Mathr::SymmetricRandom()); q.normalize(); // q.FromAxisAngle( Vector3r(0,0,1),0); body->setDynamic(dynamic); body->state->angVel = Vector3r(0,0,0); body->state->vel = Vector3r(0,0,0); body->state->mass = 4.0/3.0*Mathr::PI*radius*radius*radius*density; body->state->inertia = Vector3r( 2.0/5.0*body->state->mass*radius*radius, 2.0/5.0*body->state->mass*radius*radius, 2.0/5.0*body->state->mass*radius*radius); body->state->se3 = Se3r(position,q); physics->young = sphereYoungModulus; physics->poisson = sphereKsDivKn; physics->frictionAngle = sphereFrictionDeg * Mathr::PI/180.0; if((!dynamic) && (!boxWalls)) { physics->young = boxYoungModulus; physics->poisson = boxKsDivKn; physics->frictionAngle = boxFrictionDeg * Mathr::PI/180.0; } aabb->color = Vector3r(0,1,0); iSphere->radius = radius; iSphere->color = Vector3r(Mathr::UnitRandom(),Mathr::UnitRandom(),Mathr::UnitRandom()); iSphere->wire = false; body->shape = iSphere; body->bound = aabb; body->material = physics; } void CapillaryTriaxialTest::createBox(shared_ptr& body, Vector3r position, Vector3r extents, bool wire) { body = shared_ptr(new Body); body->groupMask=2; shared_ptr physics(new FrictMat); shared_ptr aabb(new Aabb); shared_ptr iBox(new Box); body->setDynamic(false); body->state->angVel = Vector3r(0,0,0); body->state->vel = Vector3r(0,0,0); body->state->mass = 0; //physics->mass = extents[0]*extents[1]*extents[2]*density*2; body->state->inertia = Vector3r( body->state->mass*(extents[1]*extents[1]+extents[2]*extents[2])/3 , body->state->mass*(extents[0]*extents[0]+extents[2]*extents[2])/3 , body->state->mass*(extents[1]*extents[1]+extents[0]*extents[0])/3 ); // physics->mass = 0; // physics->inertia = Vector3r(0,0,0); body->state->se3 = Se3r(position,Quaternionr::Identity()); physics->young = boxYoungModulus; physics->poisson = boxKsDivKn; physics->frictionAngle = boxFrictionDeg * Mathr::PI/180.0; aabb->color = Vector3r(1,0,0); iBox->extents = extents; iBox->color = Vector3r(1,1,1); iBox->wire = wire; body->bound = aabb; body->shape = iBox; body->material = physics; } void CapillaryTriaxialTest::createActors(shared_ptr& scene) { Real distanceFactor = 1.3;//Create potential interactions as soon as the distance is less than factor*(rad1+rad2) shared_ptr interactionGeometryDispatcher(new IGeomDispatcher); shared_ptr iS2IS4SContactGeometry(new Ig2_Sphere_Sphere_ScGeom); iS2IS4SContactGeometry->interactionDetectionFactor = distanceFactor;//Detect potential distant interaction (meniscii) interactionGeometryDispatcher->add(iS2IS4SContactGeometry); interactionGeometryDispatcher->add(new Ig2_Box_Sphere_ScGeom); shared_ptr interactionPhysicsDispatcher(new IPhysDispatcher); // interactionPhysicsDispatcher->add("Ip2_FrictMat_FrictMat_FrictPhys"); // Unhandled exception: St13runtime_error : Class `Ip2_FrictMat_FrictMat_FrictPhys' could not be cast to required 2D Functor /// OLD //interactionPhysicsDispatcher->add("BodyMacroParameters","BodyMacroParameters","MacroMicroElasticRelationshipsWater"); /// NEW shared_ptr ss(new Ip2_FrictMat_FrictMat_CapillaryPhys); interactionPhysicsDispatcher->add(ss); shared_ptr boundDispatcher = shared_ptr(new BoundDispatcher); shared_ptr collider(new InsertionSortCollider); shared_ptr bo1sphere(new Bo1_Sphere_Aabb); bo1sphere->aabbEnlargeFactor = distanceFactor;//Detect potential distant interaction (meniscii) collider->boundDispatcher->add(bo1sphere); collider->boundDispatcher->add(new Bo1_Box_Aabb); shared_ptr gravityCondition(new GravityEngine); gravityCondition->gravity = gravity; shared_ptr globalStiffnessTimeStepper(new GlobalStiffnessTimeStepper); globalStiffnessTimeStepper->timeStepUpdateInterval = timeStepUpdateInterval; globalStiffnessTimeStepper->defaultDt = defaultDt; shared_ptr elasticContactLaw(new ElasticContactLaw); elasticContactLaw->neverErase = true; // capillary shared_ptr capillaryCohesiveLaw(new Law2_ScGeom_CapillaryPhys_Capillarity); capillaryCohesiveLaw->capillaryPressure = capillaryPressure; // capillaryCohesiveLaw->fusionDetection = fusionDetection; // capillaryCohesiveLaw->binaryFusion = binaryFusion; //shared_ptr stiffnesscounter(new StiffnessCounter); //stiffnesscounter->interval = timeStepUpdateInterval; // moving walls to regulate the stress applied + compress when the packing is dense an stable //cerr << "triaxialcompressionEngine = shared_ptr (new TriaxialCompressionEngine);" << std::endl; triaxialcompressionEngine = shared_ptr (new TriaxialCompressionEngine); triaxialcompressionEngine-> stiffnessUpdateInterval = wallStiffnessUpdateInterval;// = stiffness update interval triaxialcompressionEngine-> radiusControlInterval = radiusControlInterval;// = stiffness update interval triaxialcompressionEngine-> sigmaIsoCompaction = sigmaIsoCompaction; triaxialcompressionEngine-> sigmaLateralConfinement = sigmaLateralConfinement; triaxialcompressionEngine->max_vel = maxWallVelocity; triaxialcompressionEngine-> thickness = thickness; triaxialcompressionEngine->strainRate = strainRate; triaxialcompressionEngine->StabilityCriterion = StabilityCriterion; triaxialcompressionEngine->autoCompressionActivation = autoCompressionActivation; triaxialcompressionEngine->internalCompaction = internalCompaction; triaxialcompressionEngine->maxMultiplier = maxMultiplier; triaxialcompressionEngine->finalMaxMultiplier = finalMaxMultiplier; triaxialcompressionEngine->Key = Key; triaxialcompressionEngine->frictionAngleDegree = sphereFrictionDeg; //cerr << "fin de section triaxialcompressionEngine = shared_ptr (new TriaxialCompressionEngine);" << std::endl; // recording global stress triaxialStateRecorder = shared_ptr(new TriaxialStateRecorder); triaxialStateRecorder-> file = WallStressRecordFile + Key; triaxialStateRecorder-> iterPeriod = recordIntervalIter; //triaxialStateRecorderer-> thickness = thickness; // recording capillary stress capillaryStressRecorder = shared_ptr(new CapillaryStressRecorder); capillaryStressRecorder -> file = capillaryStressRecordFile + Key; capillaryStressRecorder -> iterPeriod = recordIntervalIter; #if 0 // moving walls to regulate the stress applied //cerr << "triaxialstressController = shared_ptr (new TriaxialStressController);" << std::endl; triaxialstressController = shared_ptr (new TriaxialStressController); triaxialstressController-> stiffnessUpdateInterval = 20;// = recordIntervalIter triaxialstressController-> sigma_iso = sigma_iso; triaxialstressController-> max_vel = 0.0001; triaxialstressController-> thickness = thickness; triaxialstressController->wall_bottom_activated = false; triaxialstressController->wall_top_activated = false; //cerr << "fin de sezction triaxialstressController = shared_ptr (new TriaxialStressController);" << std::endl; #endif scene->engines.clear(); scene->engines.push_back(shared_ptr(new ForceResetter)); // scene->engines.push_back(sdecTimeStepper); scene->engines.push_back(collider); scene->engines.push_back(interactionGeometryDispatcher); scene->engines.push_back(interactionPhysicsDispatcher); scene->engines.push_back(elasticContactLaw); // capillary //scene->engines.push_back(stiffnesscounter); //scene->engines.push_back(stiffnessMatrixTimeStepper); scene->engines.push_back(globalStiffnessTimeStepper); if(water) { scene->engines.push_back(capillaryCohesiveLaw); scene->engines.push_back(capillaryStressRecorder); } scene->engines.push_back(triaxialcompressionEngine); scene->engines.push_back(triaxialStateRecorder); //scene->engines.push_back(gravityCondition); scene->engines.push_back(shared_ptr (new NewtonIntegrator)); //if(!rotationBlocked) // scene->engines.push_back(orientationIntegrator); //scene->engines.push_back(triaxialstressController); } void CapillaryTriaxialTest::positionRootBody(shared_ptr& scene) { } string GenerateCloud_water(vector& sphere_list, Vector3r lowerCorner, Vector3r upperCorner, long number, Real rad_std_dev, Real porosity) { typedef boost::minstd_rand StdGenerator; static StdGenerator generator; static boost::variate_generator > random1(generator, boost::uniform_real<>(0,1)); // static boost::variate_generator > // randomN(generator, boost::normal_distribution<>(aggregateMeanRadius,aggregateSigmaRadius)); sphere_list.clear(); long tries = 1000; //nb of tries for positionning the next sphere Vector3r dimensions = upperCorner - lowerCorner; Real mean_radius = std::pow(dimensions.x()*dimensions.y()*dimensions.z()*(1-porosity)/(3.1416*1.3333*number),0.333333); //cerr << mean_radius; Real Rmin=mean_radius, Rmax=mean_radius; std::cerr << "generating aggregates ... "; long t, i; for (i=0; i (sphere_list[j].first-s.first).squaredNorm()) overlap=true; if (!overlap){ sphere_list.push_back(s); Rmin = std::min(Rmin,s.second); Rmax = std::max(Rmax,s.second); break;} } if (t==tries) return "More than " + boost::lexical_cast(tries) + " tries while generating sphere number " + boost::lexical_cast(i+1) + "/" + boost::lexical_cast(number) + "."; } return "Generated a sample with " + boost::lexical_cast(number) + "spheres inside box of dimensions: (" + boost::lexical_cast(dimensions[0]) + "," + boost::lexical_cast(dimensions[1]) + "," + boost::lexical_cast(dimensions[2]) + ")." + " mean radius=" + boost::lexical_cast(mean_radius) + + " Rmin =" + boost::lexical_cast(Rmin) + + " Rmax =" + boost::lexical_cast(Rmax) + "."; } YADE_PLUGIN((CapillaryTriaxialTest)); trunk-2018.02b/pkg/dem/CapillaryTriaxialTest.hpp000066400000000000000000000225711324306050200215120ustar00rootroot00000000000000/************************************************************************* * Copyright (C) 2006 by Bruno Chareyre * * bruno.chareyre@hmg.inpg.fr * * Copyright (C) 2006 by luc Scholtes * * luc.scholtes@hmg.inpg.fr * * * * This program is free software; it is licensed under the terms of the * * GNU General Public License v2 or later. See file LICENSE for details. * *************************************************************************/ #pragma once #include #include class ForceRecorder; class AveragePositionRecorder; class VelocityRecorder; class TriaxialStressController; class TriaxialCompressionEngine; class TriaxialStateRecorder; class CapillaryStressRecorder; class GlobalStiffnessTimeStepper; /*! \brief Triaxial test on unsaturated sphere packings This preprocessor is a variant of TriaxialTest, including the model of capillary forces developed as part of the PhD of Luc Scholtès. See the documentation of Law2_ScGeom_CapillaryPhys_Capillarity or the main page https://yade-dem.org/wiki/CapillaryTriaxialTest, for more details. Results obtained with this preprocessor were reported for instance in "Scholtes et al. Micromechanics of granular materials with capillary effects. International Journal of Engineering Science 2009,(47)1, 64-75." */ class CapillaryTriaxialTest : public FileGenerator { private : Vector3r gravity; Vector3r spheresColor; bool wall_top ,wall_bottom ,wall_1 ,wall_2 ,wall_3 ,wall_4 ,wall_top_wire ,wall_bottom_wire ,wall_1_wire ,wall_2_wire ,wall_3_wire ,wall_4_wire ,spheresRandomColor; shared_ptr triaxialcompressionEngine; shared_ptr triaxialstressController; shared_ptr triaxialStateRecorder; shared_ptr capillaryStressRecorder; shared_ptr globalStiffnessTimeStepper; void createBox(shared_ptr& body, Vector3r position, Vector3r extents,bool wire); void createSphere(shared_ptr& body, Vector3r position, Real radius,bool big,bool dynamic); void createActors(shared_ptr& scene); void positionRootBody(shared_ptr& scene); typedef pair BasicSphere; public : ~CapillaryTriaxialTest (); bool generate(std::string& message); YADE_CLASS_BASE_DOC_ATTRS_INIT_CTOR_PY( CapillaryTriaxialTest,FileGenerator,"This preprocessor is a variant of TriaxialTest, including the model of capillary forces developed as part of the PhD of Luc Scholtès. See the documentation of Law2_ScGeom_CapillaryPhys_Capillarity or the main page https://yade-dem.org/wiki/CapillaryTriaxialTest, for more details.\n\n Results obtained with this preprocessor were reported for instance in 'Scholtes et al. Micromechanics of granular materials with capillary effects. International Journal of Engineering Science 2009,(47)1, 64-75.'" , ((Vector3r,lowerCorner,Vector3r(0,0,0),,"Lower corner of the box.")) ((Vector3r,upperCorner,Vector3r(1,1,1),,"Upper corner of the box.")) ((string,importFilename,"",,"File with positions and sizes of spheres.")) ((string,Key,"",,"A code that is added to output filenames.")) ((string,fixedBoxDims,"",,"string that contains some subset (max. 2) of {'x','y','z'} ; contains axes will have box dimension hardcoded, even if box is scaled as mean_radius is prescribed: scaling will be applied on the rest.")) ((Real,capillaryPressure,0,,"Define succion in the packing [Pa]. This is the value used in the capillary model.")) ((bool,water,true,,"activate capillary model")) ((bool,fusionDetection,false,,"test overlaps between liquid bridges on modify forces if overlaps exist")) ((bool,binaryFusion,true,,"Defines how overlapping bridges affect the capillary forces (see :yref:`CapillaryTriaxialTest::fusionDetection`). If binary=true, the force is null as soon as there is an overlap detected, if not, the force is divided by the number of overlaps.")) ((string,WallStressRecordFile,"./WallStressesWater"+Key,,"")) ((string,capillaryStressRecordFile,"./capStresses"+Key,,"")) ((string,contactStressRecordFile,"./contStresses"+Key,,"")) ((bool,internalCompaction,false,,"flag for choosing between moving boundaries or increasing particles sizes during the compaction stage.")) ((bool,biaxial2dTest,false,,"FIXME : what is that?")) ((bool,fixedPoroCompaction,false,,"flag to choose an isotropic compaction until a fixed porosity choosing a same translation speed for the six walls")) ((bool,autoCompressionActivation,true,,"Do we just want to generate a stable packing under isotropic pressure (false) or do we want the triaxial loading to start automatically right after compaction stage (true)?")) ((bool,autoUnload,true,,"auto adjust the isotropic stress state from :yref:`TriaxialTest::sigmaIsoCompaction` to :yref:`TriaxialTest::sigmaLateralConfinement` if they have different values. See docs for :yref:`TriaxialCompressionEngine::autoUnload`")) ((bool,autoStopSimulation,false,,"freeze the simulation when conditions are reached (don't activate this if you want to be able to run/stop from Qt GUI)")) ((bool,noFiles,false,,"Do not create any files during run (.xml, .spheres, wall stress records)")) ((bool,facetWalls,false,,"Use facets for boundaries (not tested)")) ((bool,wallWalls,false,,"Use walls for boundaries (not tested)")) ((bool,boxWalls,true,,"Use boxes for boundaries (recommended).")) ((Real,fixedPorosity,1,,"FIXME : what is that?")) ((Real,thickness,0.001,,"thickness of boundaries. It is arbitrary and should have no effect")) ((Real,maxMultiplier,1.01,,"max multiplier of diameters during internal compaction (initial fast increase)")) ((Real,finalMaxMultiplier,1.001,,"max multiplier of diameters during internal compaction (secondary precise adjustment)")) ((Real,Rdispersion,0.3,,"Normalized standard deviation of generated sizes.")) ((Real,radiusMean,-1,,"Mean radius. If negative (default), autocomputed to as a function of box size and :yref:`TriaxialTest::numberOfGrains`")) ((Real,sphereYoungModulus,15000000.0,,"Stiffness of spheres.")) ((Real,sphereKsDivKn,0.5,,"Ratio of shear vs. normal contact stiffness for spheres.")) ((Real,sphereFrictionDeg,18.0,,"Friction angle [°] of spheres assigned just before triaxial testing.")) ((Real,compactionFrictionDeg,sphereFrictionDeg,,"Friction angle [°] of spheres during compaction (different values result in different porosities)]. This value is overridden by :yref:`TriaxialTest::sphereFrictionDeg` before triaxial testing.")) ((Real,boxYoungModulus,15000000.0,,"Stiffness of boxes.")) ((Real,maxWallVelocity,10,,"max velocity of boundaries. Usually useless, but can help stabilizing the system in some cases.")) ((Real,boxKsDivKn,0.5,,"Ratio of shear vs. normal contact stiffness for boxes.")) ((Real,boxFrictionDeg,0.0,,"Friction angle [°] of boundaries contacts.")) ((Real,density,2600,,"density of spheres")) ((Real,strainRate,1,,"Strain rate in triaxial loading.")) ((Real,defaultDt,0.0001,,"Max time-step. Used as initial value if defined. Latter adjusted by the time stepper.")) ((Real,dampingForce,0.2,,"Coefficient of Cundal-Non-Viscous damping (applied on on the 3 components of forces)")) ((Real,dampingMomentum,0.2,,"Coefficient of Cundal-Non-Viscous damping (applied on on the 3 components of torques)")) ((Real,StabilityCriterion,0.01,,"Value of unbalanced force for which the system is considered stable. Used in conditionals to switch between loading stages.")) ((Real,wallOversizeFactor,1.3,,"Make boundaries larger than the packing to make sure spheres don't go out during deformation.")) ((Real,sigmaIsoCompaction,-50000,,"Confining stress during isotropic compaction (< 0 for real - compressive - compaction).")) ((Real,sigmaLateralConfinement,-50000,,"Lateral stress during triaxial loading (< 0 for classical compressive cases). An isotropic unloading is performed if the value is not equal to :yref:`CapillaryTriaxialTest::SigmaIsoCompaction`.")) ((int,timeStepUpdateInterval,50,,"interval for :yref:`GlobalStiffnessTimeStepper`")) ((int,timeStepOutputInterval,50,,"interval for outputing general information on the simulation (stress,unbalanced force,...)")) ((int,wallStiffnessUpdateInterval,10,,"interval for updating the stiffness of sample/boundaries contacts")) ((int,radiusControlInterval,10,,"interval between size changes when growing spheres.")) ((int,numberOfGrains,400,,"Number of generated spheres.")) ((int,recordIntervalIter,20,,"interval between file outputs")) , /* init */ , /* constructor */ wall_top = true; wall_bottom = true; wall_1 = true; wall_2 = true; wall_3 = true; wall_4 = true; wall_top_wire = true; wall_bottom_wire = true; wall_1_wire = true; wall_2_wire = true; wall_3_wire = true; wall_4_wire = true; spheresColor = Vector3r(0.8,0.3,0.3); spheresRandomColor = false; WallStressRecordFile = "./WallStresses"+Key; gravity = Vector3r(0,-9.81,0); , //.def("setContactProperties",&TriaxialCompressionEngine::setContactProperties,"Assign a new friction angle (degrees) to dynamic bodies and relative interactions") ); // DECLARE_LOGGER; }; REGISTER_SERIALIZABLE(CapillaryTriaxialTest); trunk-2018.02b/pkg/dem/CohesiveFrictionalContactLaw.cpp000066400000000000000000000335521324306050200227700ustar00rootroot00000000000000/************************************************************************* * Copyright (C) 2007 by Bruno Chareyre * * Copyright (C) 2008 by Janek Kozicki * * * * This program is free software; it is licensed under the terms of the * * GNU General Public License v2 or later. See file LICENSE for details. * *************************************************************************/ #include "CohesiveFrictionalContactLaw.hpp" #include #include #include YADE_PLUGIN((CohesiveFrictionalContactLaw)(Law2_ScGeom6D_CohFrictPhys_CohesionMoment)(CohFrictMat)(CohFrictPhys)(Ip2_CohFrictMat_CohFrictMat_CohFrictPhys)); CREATE_LOGGER(Law2_ScGeom6D_CohFrictPhys_CohesionMoment); Real Law2_ScGeom6D_CohFrictPhys_CohesionMoment::getPlasticDissipation() {return (Real) plasticDissipation;} void Law2_ScGeom6D_CohFrictPhys_CohesionMoment::initPlasticDissipation(Real initVal) {plasticDissipation.reset(); plasticDissipation+=initVal;} Vector3r CohFrictPhys::getRotStiffness() {return Vector3r(ktw,kr,kr);} Real Law2_ScGeom6D_CohFrictPhys_CohesionMoment::normElastEnergy() { Real normEnergy=0; FOREACH(const shared_ptr& I, *scene->interactions){ if(!I->isReal()) continue; CohFrictPhys* phys = YADE_CAST(I->phys.get()); if (phys) { normEnergy += 0.5*(phys->normalForce.squaredNorm()/phys->kn); } } return normEnergy; } Real Law2_ScGeom6D_CohFrictPhys_CohesionMoment::shearElastEnergy() { Real shearEnergy=0; FOREACH(const shared_ptr& I, *scene->interactions){ if(!I->isReal()) continue; CohFrictPhys* phys = YADE_CAST(I->phys.get()); if (phys) { shearEnergy += 0.5*(phys->shearForce.squaredNorm()/phys->ks); } } return shearEnergy; } Real Law2_ScGeom6D_CohFrictPhys_CohesionMoment::bendingElastEnergy() { Real bendingEnergy=0; FOREACH(const shared_ptr& I, *scene->interactions){ if(!I->isReal()) continue; CohFrictPhys* phys = YADE_CAST(I->phys.get()); if (phys) { bendingEnergy += 0.5*(phys->moment_bending.squaredNorm()/phys->kr); } } return bendingEnergy; } Real Law2_ScGeom6D_CohFrictPhys_CohesionMoment::twistElastEnergy() { Real twistEnergy=0; FOREACH(const shared_ptr& I, *scene->interactions){ if(!I->isReal()) continue; CohFrictPhys* phys = YADE_CAST(I->phys.get()); if (phys) { twistEnergy += 0.5*(phys->moment_twist.squaredNorm()/phys->ktw); } } return twistEnergy; } Real Law2_ScGeom6D_CohFrictPhys_CohesionMoment::totalElastEnergy() { Real totalEnergy=0; FOREACH(const shared_ptr& I, *scene->interactions){ if(!I->isReal()) continue; CohFrictPhys* phys = YADE_CAST(I->phys.get()); if (phys) { totalEnergy += 0.5*(phys->normalForce.squaredNorm()/phys->kn); totalEnergy += 0.5*(phys->shearForce.squaredNorm()/phys->ks); totalEnergy += 0.5*(phys->moment_bending.squaredNorm()/phys->kr); totalEnergy += 0.5*(phys->moment_twist.squaredNorm()/phys->ktw); } } return totalEnergy; } void CohesiveFrictionalContactLaw::action() { if(!functor) functor=shared_ptr(new Law2_ScGeom6D_CohFrictPhys_CohesionMoment); functor->always_use_moment_law = always_use_moment_law; functor->shear_creep=shear_creep; functor->twist_creep=twist_creep; functor->creep_viscosity=creep_viscosity; functor->scene=scene; FOREACH(const shared_ptr& I, *scene->interactions){ if(!I->isReal()) continue; functor->go(I->geom, I->phys, I.get());} } bool Law2_ScGeom6D_CohFrictPhys_CohesionMoment::go(shared_ptr& ig, shared_ptr& ip, Interaction* contact) { const Real& dt = scene->dt; const int &id1 = contact->getId1(); const int &id2 = contact->getId2(); ScGeom6D* geom = YADE_CAST (ig.get()); CohFrictPhys* phys = YADE_CAST (ip.get()); Vector3r& shearForce = phys->shearForce; if (contact->isFresh(scene)) shearForce = Vector3r::Zero(); Real un = geom->penetrationDepth; Real Fn = phys->kn*(un-phys->unp); if (phys->fragile && (-Fn)> phys->normalAdhesion) { // BREAK due to tension return false; } else { if ((-Fn)> phys->normalAdhesion) {//normal plasticity Fn=-phys->normalAdhesion; phys->unp = un+phys->normalAdhesion/phys->kn; if (phys->unpMax>=0 && -phys->unp>phys->unpMax) // Actually unpMax should be defined as a function of the average particule sizes for instance return false; } phys->normalForce = Fn*geom->normal; State* de1 = Body::byId(id1,scene)->state.get(); State* de2 = Body::byId(id2,scene)->state.get(); ///////////////////////// CREEP START /////////// if (shear_creep) shearForce -= phys->ks*(shearForce*dt/creep_viscosity); ///////////////////////// CREEP END //////////// Vector3r& shearForce = geom->rotate(phys->shearForce); const Vector3r& dus = geom->shearIncrement(); //Linear elasticity giving "trial" shear force shearForce -= phys->ks*dus; Real Fs = phys->shearForce.norm(); Real maxFs = phys->shearAdhesion; if (!phys->cohesionDisablesFriction || maxFs==0) maxFs += Fn*phys->tangensOfFrictionAngle; maxFs = std::max((Real) 0, maxFs); if (Fs > maxFs) {//Plasticity condition on shear force if (phys->fragile && !phys->cohesionBroken) { phys->SetBreakingState(); maxFs = max((Real) 0, Fn*phys->tangensOfFrictionAngle); } maxFs = maxFs / Fs; Vector3r trialForce=shearForce; shearForce *= maxFs; if (scene->trackEnergy || traceEnergy){ Real sheardissip=((1/phys->ks)*(trialForce-shearForce))/*plastic disp*/ .dot(shearForce)/*active force*/; if(sheardissip>0) { plasticDissipation+=sheardissip; if (scene->trackEnergy) scene->energy->add(sheardissip,"shearDissip",shearDissipIx,/*reset*/false);} } if (Fn<0) phys->normalForce = Vector3r::Zero();//Vector3r::Zero() } //Apply the force applyForceAtContactPoint(-phys->normalForce-shearForce, geom->contactPoint, id1, de1->se3.position, id2, de2->se3.position + (scene->isPeriodic ? scene->cell->intrShiftPos(contact->cellDist): Vector3r::Zero())); /// Moment law /// if (phys->momentRotationLaw && (!phys->cohesionBroken || always_use_moment_law)) { if (!useIncrementalForm){ if (twist_creep) { Real viscosity_twist = creep_viscosity * std::pow((2 * std::min(geom->radius1,geom->radius2)),2) / 16.0; Real angle_twist_creeped = geom->getTwist() * (1 - dt/viscosity_twist); Quaternionr q_twist(AngleAxisr(geom->getTwist(),geom->normal)); Quaternionr q_twist_creeped(AngleAxisr(angle_twist_creeped,geom->normal)); Quaternionr q_twist_delta(q_twist_creeped * q_twist.conjugate()); geom->twistCreep = geom->twistCreep * q_twist_delta; } phys->moment_twist = (geom->getTwist()*phys->ktw)*geom->normal; phys->moment_bending = geom->getBending() * phys->kr; } else{ // Use incremental formulation to compute moment_twis and moment_bending (no twist_creep is applied) if (twist_creep) throw std::invalid_argument("Law2_ScGeom6D_CohFrictPhys_CohesionMoment: no twis creep is included if the incremental form for the rotations is used."); Vector3r relAngVel = geom->getRelAngVel(de1,de2,dt); // *** Bending ***// Vector3r relAngVelBend = relAngVel - geom->normal.dot(relAngVel)*geom->normal; // keep only the bending part Vector3r relRotBend = relAngVelBend*dt; // relative rotation due to rolling behaviour // incremental formulation for the bending moment (as for the shear part) Vector3r& momentBend = phys->moment_bending; momentBend = geom->rotate(momentBend); // rotate moment vector (updated) momentBend = momentBend-phys->kr*relRotBend; // ---------------------------------------------------------------------------------------- // *** Torsion ***// Vector3r relAngVelTwist = geom->normal.dot(relAngVel)*geom->normal; Vector3r relRotTwist = relAngVelTwist*dt; // component of relative rotation along n FIXME: sign? // incremental formulation for the torsional moment Vector3r& momentTwist = phys->moment_twist; momentTwist = geom->rotate(momentTwist); // rotate moment vector (updated) momentTwist = momentTwist-phys->ktw*relRotTwist; // FIXME: sign? } /// Plasticity /// // limit rolling moment to the plastic value, if required if (phys->maxRollPl>=0.){ // do we want to apply plasticity? Real RollMax = phys->maxRollPl*phys->normalForce.norm(); if (!useIncrementalForm) LOG_WARN("If :yref:`Law2_ScGeom6D_CohFrictPhys_CohesionMoment::useIncrementalForm` is false, then plasticity will not be applied correctly (the total formulation would not reproduce irreversibility)."); Real scalarRoll = phys->moment_bending.norm(); if (scalarRoll>RollMax){ // fix maximum rolling moment Real ratio = RollMax/scalarRoll; phys->moment_bending *= ratio; if (scene->trackEnergy){ Real bendingdissip=((1/phys->kr)*(scalarRoll-RollMax)*RollMax)/*active force*/; if(bendingdissip>0) scene->energy->add(bendingdissip,"bendingDissip",bendingDissipIx,/*reset*/false);} } } // limit twisting moment to the plastic value, if required if (phys->maxTwistPl>=0.){ // do we want to apply plasticity? Real TwistMax = phys->maxTwistPl*phys->normalForce.norm(); if (!useIncrementalForm) LOG_WARN("If :yref:`Law2_ScGeom6D_CohFrictPhys_CohesionMoment::useIncrementalForm` is false, then plasticity will not be applied correctly (the total formulation would not reproduce irreversibility)."); Real scalarTwist= phys->moment_twist.norm(); if (scalarTwist>TwistMax){ // fix maximum rolling moment Real ratio = TwistMax/scalarTwist; phys->moment_twist *= ratio; if (scene->trackEnergy){ Real twistdissip=((1/phys->ktw)*(scalarTwist-TwistMax)*TwistMax)/*active force*/; if(twistdissip>0) scene->energy->add(twistdissip,"twistDissip",twistDissipIx,/*reset*/false);} } } // Apply moments now Vector3r moment = phys->moment_twist + phys->moment_bending; scene->forces.addTorque(id1,-moment); scene->forces.addTorque(id2, moment); } /// Moment law END /// } return true; } void Ip2_CohFrictMat_CohFrictMat_CohFrictPhys::go(const shared_ptr& b1 // CohFrictMat , const shared_ptr& b2 // CohFrictMat , const shared_ptr& interaction) { CohFrictMat* sdec1 = static_cast(b1.get()); CohFrictMat* sdec2 = static_cast(b2.get()); ScGeom6D* geom = YADE_CAST(interaction->geom.get()); //Create cohesive interractions only once if (setCohesionNow && cohesionDefinitionIteration==-1) cohesionDefinitionIteration=scene->iter; if (setCohesionNow && cohesionDefinitionIteration!=-1 && cohesionDefinitionIteration!=scene->iter) { cohesionDefinitionIteration = -1; setCohesionNow = 0;} if (geom) { const auto normalAdhPreCalculated = (normalCohesion) ? (*normalCohesion)(b1->id,b2->id) : std::min(sdec1->normalCohesion,sdec2->normalCohesion); const auto shearAdhPreCalculated = (shearCohesion) ? (*shearCohesion)(b1->id,b2->id) : std::min(sdec1->shearCohesion,sdec2->shearCohesion); if (!interaction->phys) { interaction->phys = shared_ptr(new CohFrictPhys()); CohFrictPhys* contactPhysics = YADE_CAST(interaction->phys.get()); Real Ea = sdec1->young; Real Eb = sdec2->young; Real Va = sdec1->poisson; Real Vb = sdec2->poisson; Real Da = geom->radius1; Real Db = geom->radius2; Real fa = sdec1->frictionAngle; Real fb = sdec2->frictionAngle; Real Kn = 2.0*Ea*Da*Eb*Db/(Ea*Da+Eb*Db);//harmonic average of two stiffnesses // harmonic average of alphas parameters Real AlphaKr, AlphaKtw; if (sdec1->alphaKr && sdec2->alphaKr) AlphaKr = 2.0*sdec1->alphaKr*sdec2->alphaKr/(sdec1->alphaKr+sdec2->alphaKr); else AlphaKr = 0; if (sdec1->alphaKtw && sdec2->alphaKtw) AlphaKtw = 2.0*sdec1->alphaKtw*sdec2->alphaKtw/(sdec1->alphaKtw+sdec2->alphaKtw); else AlphaKtw=0; Real Ks; if (Va && Vb) Ks = 2.0*Ea*Da*Va*Eb*Db*Vb/(Ea*Da*Va+Eb*Db*Vb);//harmonic average of two stiffnesses with ks=V*kn for each sphere else Ks=0; contactPhysics->kr = Da*Db*Ks*AlphaKr; contactPhysics->ktw = Da*Db*Ks*AlphaKtw; contactPhysics->tangensOfFrictionAngle = std::tan(std::min(fa,fb)); if ((setCohesionOnNewContacts || setCohesionNow) && sdec1->isCohesive && sdec2->isCohesive) { contactPhysics->cohesionBroken = false; contactPhysics->normalAdhesion = normalAdhPreCalculated*pow(std::min(Db, Da),2); contactPhysics->shearAdhesion = shearAdhPreCalculated*pow(std::min(Db, Da),2); geom->initRotations(*(Body::byId(interaction->getId1(),scene)->state),*(Body::byId(interaction->getId2(),scene)->state)); contactPhysics->fragile=(sdec1->fragile || sdec2->fragile); } contactPhysics->kn = Kn; contactPhysics->ks = Ks; contactPhysics->maxRollPl = min(sdec1->etaRoll*Da,sdec2->etaRoll*Db); contactPhysics->maxTwistPl = min(sdec1->etaTwist*Da,sdec2->etaTwist*Db); contactPhysics->momentRotationLaw=(sdec1->momentRotationLaw && sdec2->momentRotationLaw); } else {// !isNew, but if setCohesionNow, all contacts are initialized like if they were newly created CohFrictPhys* contactPhysics = YADE_CAST(interaction->phys.get()); if ((setCohesionNow && sdec1->isCohesive && sdec2->isCohesive) || contactPhysics->initCohesion) { contactPhysics->cohesionBroken = false; contactPhysics->normalAdhesion = normalAdhPreCalculated*pow(std::min(geom->radius2, geom->radius1),2); contactPhysics->shearAdhesion = shearAdhPreCalculated*pow(std::min(geom->radius2, geom->radius1),2); geom->initRotations(*(Body::byId(interaction->getId1(),scene)->state),*(Body::byId(interaction->getId2(),scene)->state)); contactPhysics->fragile=(sdec1->fragile || sdec2->fragile); contactPhysics->initCohesion=false; } } } }; trunk-2018.02b/pkg/dem/CohesiveFrictionalContactLaw.hpp000066400000000000000000000311521324306050200227670ustar00rootroot00000000000000/************************************************************************* * Copyright (C) 2007 by Bruno Chareyre * * Copyright (C) 2008 by Janek Kozicki * * * * This program is free software; it is licensed under the terms of the * * GNU General Public License v2 or later. See file LICENSE for details. * *************************************************************************/ #pragma once #include #include #include #include #include #include #include #include // The following code was moved from CohFrictMat.hpp class CohFrictMat : public FrictMat { public : virtual ~CohFrictMat () {}; /// Serialization YADE_CLASS_BASE_DOC_ATTRS_CTOR(CohFrictMat,FrictMat,"", ((bool,isCohesive,true,,"")) ((Real,alphaKr,2.0,,"Dimensionless rolling stiffness.")) ((Real,alphaKtw,2.0,,"Dimensionless twist stiffness.")) ((Real,etaRoll,-1.,,"Dimensionless rolling (aka 'bending') strength. If negative, rolling moment will be elastic.")) ((Real,etaTwist,-1.,,"Dimensionless twisting strength. If negative, twist moment will be elastic.")) ((Real,normalCohesion,-1,,"Tensile strength, homogeneous to a pressure. If negative the normal force is purely elastic.")) ((Real,shearCohesion,-1,,"Shear strength, homogeneous to a pressure. If negative the shear force is purely elastic.")) ((bool,fragile,true,,"do cohesion disappear when contact strength is exceeded")) ((bool,momentRotationLaw,false,,"Use bending/twisting moment at contact. The contact will have moments only if both bodies have this flag true. See :yref:`CohFrictPhys::cohesionDisablesFriction` for details.")) , createIndex(); ); /// Indexable REGISTER_CLASS_INDEX(CohFrictMat,FrictMat); }; REGISTER_SERIALIZABLE(CohFrictMat); // The following code was moved from CohFrictPhys.hpp class CohFrictPhys : public FrictPhys { public : virtual ~CohFrictPhys() {}; void SetBreakingState() {cohesionBroken = true; normalAdhesion = 0; shearAdhesion = 0;}; virtual Vector3r getRotStiffness(); YADE_CLASS_BASE_DOC_ATTRS_CTOR(CohFrictPhys,FrictPhys,"", ((bool,cohesionDisablesFriction,false,,"is shear strength the sum of friction and adhesion or only adhesion?")) ((bool,cohesionBroken,true,,"is cohesion active? Set to false at the creation of a cohesive contact, and set to true when a fragile contact is broken")) ((bool,fragile,true,,"do cohesion disappear when contact strength is exceeded?")) ((Real,kr,0,,"rotational stiffness [N.m/rad]")) ((Real,ktw,0,,"twist stiffness [N.m/rad]")) ((Real,maxRollPl,0.0,,"Coefficient of rolling friction (negative means elastic).")) ((Real,maxTwistPl,0.0,,"Coefficient of twisting friction (negative means elastic).")) ((Real,normalAdhesion,0,,"tensile strength")) ((Real,shearAdhesion,0,,"cohesive part of the shear strength (a frictional term might be added depending on :yref:`CohFrictPhys::cohesionDisablesFriction`)")) ((Real,unp,0,,"plastic normal displacement, only used for tensile behaviour and if :yref:`CohFrictPhys::fragile` =false.")) ((Real,unpMax,0,,"maximum value of plastic normal displacement (counted positively), after that the interaction breaks even if :yref:`CohFrictPhys::fragile` =false. A negative value (i.e. -1) means no maximum.")) ((bool,momentRotationLaw,false,,"use bending/twisting moment at contacts. See :yref:`Law2_ScGeom6D_CohFrictPhys_CohesionMoment::always_use_moment_law` for details.")) ((bool,initCohesion,false,,"Initialize the cohesive behaviour with current state as equilibrium state (same as :yref:`Ip2_CohFrictMat_CohFrictMat_CohFrictPhys::setCohesionNow` but acting on only one interaction)")) ((Real,creep_viscosity,-1,,"creep viscosity [Pa.s/m].")) // internal attributes ((Vector3r,moment_twist,Vector3r(0,0,0),,"Twist moment")) ((Vector3r,moment_bending,Vector3r(0,0,0),,"Bending moment")) , createIndex(); ); /// Indexable REGISTER_CLASS_INDEX(CohFrictPhys,FrictPhys); }; REGISTER_SERIALIZABLE(CohFrictPhys); class Law2_ScGeom6D_CohFrictPhys_CohesionMoment: public LawFunctor{ public: OpenMPAccumulator plasticDissipation; Real normElastEnergy(); Real shearElastEnergy(); Real bendingElastEnergy(); Real twistElastEnergy(); Real totalElastEnergy(); Real getPlasticDissipation(); void initPlasticDissipation(Real initVal=0); virtual bool go(shared_ptr& _geom, shared_ptr& _phys, Interaction* I); YADE_CLASS_BASE_DOC_ATTRS_CTOR_PY(Law2_ScGeom6D_CohFrictPhys_CohesionMoment,LawFunctor,"Law for linear traction-compression-bending-twisting, with cohesion+friction and Mohr-Coulomb plasticity surface. This law adds adhesion and moments to :yref:`Law2_ScGeom_FrictPhys_CundallStrack`.\n\nThe normal force is (with the convention of positive tensile forces) $F_n=min(k_n*(u_n-u_n^p), a_n)$, with $a_n$ the normal adhesion and $u_n^p$ the plastic part of normal displacement. The shear force is $F_s=k_s*u_s$, the plasticity condition defines the maximum value of the shear force, by default $F_s^{max}=F_n*tan(\\phi)+a_s$, with $\\phi$ the friction angle and $a_s$ the shear adhesion. If :yref:`CohFrictPhys::cohesionDisableFriction` is True, friction is ignored as long as adhesion is active, and the maximum shear force is only $F_s^{max}=a_s$.\n\nIf the maximum tensile or maximum shear force is reached and :yref:`CohFrictPhys::fragile` =True (default), the cohesive link is broken, and $a_n, a_s$ are set back to zero. If a tensile force is present, the contact is lost, else the shear strength is $F_s^{max}=F_n*tan(\\phi)$. If :yref:`CohFrictPhys::fragile` =False, the behaviour is perfectly plastic, and the shear strength is kept constant.\n\nIf :yref:`Law2_ScGeom6D_CohFrictPhys_CohesionMoment::momentRotationLaw` =True, bending and twisting moments are computed using a linear law with moduli respectively $k_t$ and $k_r$, so that the moments are : $M_b=k_b*\\Theta_b$ and $M_t=k_t*\\Theta_t$, with $\\Theta_{b,t}$ the relative rotations between interacting bodies (details can be found in [Bourrier2013]_). The maximum value of moments can be defined and takes the form of rolling friction. Cohesive -type moment may also be included in the future.\n\nCreep at contact is implemented in this law, as defined in [Hassan2010]_. If activated, there is a viscous behaviour of the shear and twisting components, and the evolution of the elastic parts of shear displacement and relative twist is given by $du_{s,e}/dt=-F_s/\\nu_s$ and $d\\Theta_{t,e}/dt=-M_t/\\nu_t$.", ((bool,neverErase,false,,"Keep interactions even if particles go away from each other (only in case another constitutive law is in the scene, e.g. :yref:`Law2_ScGeom_CapillaryPhys_Capillarity`)")) ((bool,always_use_moment_law,false,,"If true, use bending/twisting moments at all contacts. If false, compute moments only for cohesive contacts.")) ((bool,shear_creep,false,,"activate creep on the shear force, using :yref:`CohesiveFrictionalContactLaw::creep_viscosity`.")) ((bool,twist_creep,false,,"activate creep on the twisting moment, using :yref:`CohesiveFrictionalContactLaw::creep_viscosity`.")) ((bool,traceEnergy,false,,"Define the total energy dissipated in plastic slips at contacts. Note that it will not reflect any energy associated to de-bonding, as it may occur for fragile contacts, nor does it include plastic dissipation in traction.")) ((bool,useIncrementalForm,false,,"use the incremental formulation to compute bending and twisting moments. Creep on the twisting moment is not included in such a case.")) ((int,shearDissipIx,-1,(Attr::hidden|Attr::noSave),"Index for shear dissipation (with O.trackEnergy)")) ((int,bendingDissipIx,-1,(Attr::hidden|Attr::noSave),"Index for bending dissipation (with O.trackEnergy)")) ((int,twistDissipIx,-1,(Attr::hidden|Attr::noSave),"Index for twist dissipation (with O.trackEnergy)")) ((Real,creep_viscosity,1,,"creep viscosity [Pa.s/m]. probably should be moved to Ip2_CohFrictMat_CohFrictMat_CohFrictPhys.")) ,, .def("normElastEnergy",&Law2_ScGeom6D_CohFrictPhys_CohesionMoment::normElastEnergy,"Compute normal elastic energy.") .def("shearElastEnergy",&Law2_ScGeom6D_CohFrictPhys_CohesionMoment::shearElastEnergy,"Compute shear elastic energy.") .def("bendingElastEnergy",&Law2_ScGeom6D_CohFrictPhys_CohesionMoment::bendingElastEnergy,"Compute bending elastic energy.") .def("twistElastEnergy",&Law2_ScGeom6D_CohFrictPhys_CohesionMoment::twistElastEnergy,"Compute twist elastic energy.") .def("elasticEnergy",&Law2_ScGeom6D_CohFrictPhys_CohesionMoment::totalElastEnergy,"Compute total elastic energy.") .def("plasticDissipation",&Law2_ScGeom6D_CohFrictPhys_CohesionMoment::getPlasticDissipation,"Total energy dissipated in plastic slips at all CohFrictPhys contacts. Computed only if :yref:`Law2_ScGeom_FrictPhys_CundallStrack::traceEnergy` is true.") .def("initPlasticDissipation",&Law2_ScGeom6D_CohFrictPhys_CohesionMoment::initPlasticDissipation,"Initialize cummulated plastic dissipation to a value (0 by default).") ); FUNCTOR2D(ScGeom6D,CohFrictPhys); DECLARE_LOGGER; }; REGISTER_SERIALIZABLE(Law2_ScGeom6D_CohFrictPhys_CohesionMoment); class CohesiveFrictionalContactLaw : public GlobalEngine { shared_ptr functor; public : long iter;/// used for checking if new iteration void action(); YADE_CLASS_BASE_DOC_ATTRS(CohesiveFrictionalContactLaw,GlobalEngine,"[DEPRECATED] Loop over interactions applying :yref:`Law2_ScGeom6D_CohFrictPhys_CohesionMoment` on all interactions.\n\n.. note::\n Use :yref:`InteractionLoop` and :yref:`Law2_ScGeom6D_CohFrictPhys_CohesionMoment` instead of this class for performance reasons.", ((bool,neverErase,false,,"Keep interactions even if particles go away from each other (only in case another constitutive law is in the scene, e.g. :yref:`Law2_ScGeom_CapillaryPhys_Capillarity`)")) ((bool,always_use_moment_law,false,,"If true, use bending/twisting moments at all contacts. If false, compute moments only for cohesive contacts.")) ((bool,shear_creep,false,,"activate creep on the shear force, using :yref:`CohesiveFrictionalContactLaw::creep_viscosity`.")) ((bool,twist_creep,false,,"activate creep on the twisting moment, using :yref:`CohesiveFrictionalContactLaw::creep_viscosity`.")) ((Real,creep_viscosity,false,,"creep viscosity [Pa.s/m]. probably should be moved to Ip2_CohFrictMat_CohFrictMat_CohFrictPhys...")) ); }; REGISTER_SERIALIZABLE(CohesiveFrictionalContactLaw); // The following code was moved from Ip2_CohFrictMat_CohFrictMat_CohFrictPhys.hpp class Ip2_CohFrictMat_CohFrictMat_CohFrictPhys : public IPhysFunctor { public : virtual void go( const shared_ptr& b1, const shared_ptr& b2, const shared_ptr& interaction); int cohesionDefinitionIteration; YADE_CLASS_BASE_DOC_ATTRS_CTOR(Ip2_CohFrictMat_CohFrictMat_CohFrictPhys,IPhysFunctor, "Generates cohesive-frictional interactions with moments, used in the contact law :yref:`Law2_ScGeom6D_CohFrictPhys_CohesionMoment`. The normal/shear stiffness and friction definitions are the same as in :yref:`Ip2_FrictMat_FrictMat_FrictPhys`, check the documentation there for details.\n\nAdhesions related to the normal and the shear components are calculated from :yref:`CohFrictMat::normalCohesion` ($C_n$) and :yref:`CohFrictMat::shearCohesion` ($C_s$). For particles of size $R_1$,$R_2$ the adhesion will be $a_i=C_i min(R_1,R_2)^2$, $i=n,s$.\n\nTwist and rolling stiffnesses are proportional to the shear stiffness through dimensionless factors alphaKtw and alphaKr, such that the rotational stiffnesses are defined by $k_s \\alpha_i R_1 R_2$, $i=tw\\,r$", ((bool,setCohesionNow,false,,"If true, assign cohesion to all existing contacts in current time-step. The flag is turned false automatically, so that assignment is done in the current timestep only.")) ((bool,setCohesionOnNewContacts,false,,"If true, assign cohesion at all new contacts. If false, only existing contacts can be cohesive (also see :yref:`Ip2_CohFrictMat_CohFrictMat_CohFrictPhys::setCohesionNow`), and new contacts are only frictional.")) ((shared_ptr,normalCohesion,,,"Instance of :yref:`MatchMaker` determining tensile strength")) ((shared_ptr,shearCohesion,,,"Instance of :yref:`MatchMaker` determining cohesive part of the shear strength (a frictional term might be added depending on :yref:`CohFrictPhys::cohesionDisablesFriction`)")) , cohesionDefinitionIteration = -1; ); FUNCTOR2D(CohFrictMat,CohFrictMat); }; REGISTER_SERIALIZABLE(Ip2_CohFrictMat_CohFrictMat_CohFrictPhys); trunk-2018.02b/pkg/dem/CohesiveTriaxialTest.cpp000066400000000000000000000350421324306050200213270ustar00rootroot00000000000000/************************************************************************* * Copyright (C) 2007 by Bruno Chareyre * * bruno.chareyre@hmg.inpg.fr * * * * This program is free software; it is licensed under the terms of the * * GNU General Public License v2 or later. See file LICENSE for details. * *************************************************************************/ #include "CohesiveTriaxialTest.hpp" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include // random #include #include #include #include typedef pair BasicSphere; //! make a list of spheres non-overlapping sphere string GenerateCloud_cohesive(vector& sphere_list, Vector3r lowerCorner, Vector3r upperCorner, long number, Real rad_std_dev, Real porosity); CohesiveTriaxialTest::~CohesiveTriaxialTest (){} bool CohesiveTriaxialTest::generate(std::string& message) { // unsigned int startId=boost::numeric::bounds::highest(), endId=0; // record forces from group 2 scene = shared_ptr(new Scene); createActors(scene); positionRootBody(scene); shared_ptr body; if(boxWalls) { // bottom box Vector3r center = Vector3r( (lowerCorner[0]+upperCorner[0])/2, lowerCorner[1]-thickness/2.0, (lowerCorner[2]+upperCorner[2])/2); Vector3r halfSize = Vector3r( 1.5*std::abs(lowerCorner[0]-upperCorner[0])/2+thickness, thickness/2.0, 1.5*std::abs(lowerCorner[2]-upperCorner[2])/2+thickness); createBox(body,center,halfSize,wall_bottom_wire); if(wall_bottom) { scene->bodies->insert(body); triaxialcompressionEngine->wall_bottom_id = body->getId(); //triaxialStateRecorder->wall_bottom_id = body->getId(); // forcerec->startId = body->getId(); // forcerec->endId = body->getId(); } //forcerec->id = body->getId(); // top box center = Vector3r( (lowerCorner[0]+upperCorner[0])/2, upperCorner[1]+thickness/2.0, (lowerCorner[2]+upperCorner[2])/2); halfSize = Vector3r( 1.5*std::abs(lowerCorner[0]-upperCorner[0])/2+thickness, thickness/2.0, 1.5*std::abs(lowerCorner[2]-upperCorner[2])/2+thickness); createBox(body,center,halfSize,wall_top_wire); if(wall_top) { scene->bodies->insert(body); triaxialcompressionEngine->wall_top_id = body->getId(); //triaxialStateRecorder->wall_top_id = body->getId(); } // box 1 center = Vector3r( lowerCorner[0]-thickness/2.0, (lowerCorner[1]+upperCorner[1])/2, (lowerCorner[2]+upperCorner[2])/2); halfSize = Vector3r( thickness/2.0, 1.5*std::abs(lowerCorner[1]-upperCorner[1])/2+thickness, 1.5*std::abs(lowerCorner[2]-upperCorner[2])/2+thickness); createBox(body,center,halfSize,wall_1_wire); if(wall_1) { scene->bodies->insert(body); triaxialcompressionEngine->wall_left_id = body->getId(); //triaxialStateRecorder->wall_left_id = body->getId(); } // box 2 center = Vector3r( upperCorner[0]+thickness/2.0, (lowerCorner[1]+upperCorner[1])/2, (lowerCorner[2]+upperCorner[2])/2); halfSize = Vector3r( thickness/2.0, 1.5*std::abs(lowerCorner[1]-upperCorner[1])/2+thickness, 1.5*std::abs(lowerCorner[2]-upperCorner[2])/2+thickness); createBox(body,center,halfSize,wall_2_wire); if(wall_2) { scene->bodies->insert(body); triaxialcompressionEngine->wall_right_id = body->getId(); //triaxialStateRecorder->wall_right_id = body->getId(); } // box 3 center = Vector3r( (lowerCorner[0]+upperCorner[0])/2, (lowerCorner[1]+upperCorner[1])/2, lowerCorner[2]-thickness/2.0); halfSize = Vector3r( 1.5*std::abs(lowerCorner[0]-upperCorner[0])/2+thickness, 1.5*std::abs(lowerCorner[1]-upperCorner[1])/2+thickness, thickness/2.0); createBox(body,center,halfSize,wall_3_wire); if(wall_3) { scene->bodies->insert(body); triaxialcompressionEngine->wall_back_id = body->getId(); //triaxialStateRecorder->wall_back_id = body->getId(); } // box 4 center = Vector3r( (lowerCorner[0]+upperCorner[0])/2, (lowerCorner[1]+upperCorner[1])/2, upperCorner[2]+thickness/2.0); halfSize = Vector3r( 1.5*std::abs(lowerCorner[0]-upperCorner[0])/2+thickness, 1.5*std::abs(lowerCorner[1]-upperCorner[1])/2+thickness, thickness/2.0); createBox(body,center,halfSize,wall_3_wire); if(wall_4) { scene->bodies->insert(body); triaxialcompressionEngine->wall_front_id = body->getId(); //triaxialStateRecorder->wall_front_id = body->getId(); } } //convert the original sphere vector (with clump info) to a BasicSphere vector. vector sphere_list; typedef boost::tuple tupleVector3rRealInt; if(importFilename!=""){ vector >sphereListClumpInfo = Shop::loadSpheresFromFile(importFilename,lowerCorner,upperCorner); FOREACH(tupleVector3rRealInt t, sphereListClumpInfo){ sphere_list.push_back(make_pair(boost::get<0>(t),boost::get<1>(t))); }; } else message=GenerateCloud_cohesive(sphere_list, lowerCorner, upperCorner, numberOfGrains, radiusDeviation, 0.75); vector::iterator it = sphere_list.begin(); vector::iterator it_end = sphere_list.end(); for (;it!=it_end; ++it) { cerr << "sphere (" << it->first << " " << it->second << endl; createSphere(body,it->first,it->second,true); scene->bodies->insert(body); } return true; } void CohesiveTriaxialTest::createSphere(shared_ptr& body, Vector3r position, Real radius, bool dynamic ) { body = shared_ptr(new Body); body->groupMask=2; shared_ptr physics(new CohFrictMat); shared_ptr aabb(new Aabb); shared_ptr iSphere(new Sphere); Quaternionr q(Mathr::SymmetricRandom(),Mathr::SymmetricRandom(),Mathr::SymmetricRandom(),Mathr::SymmetricRandom()); q.normalize(); body->state->blockedDOFs=State::DOF_NONE; body->state->angVel = Vector3r(0,0,0); body->state->vel = Vector3r(0,0,0); body->state->mass = 4.0/3.0*Mathr::PI*radius*radius*radius*density; body->state->inertia = Vector3r( 2.0/5.0*body->state->mass*radius*radius, 2.0/5.0*body->state->mass*radius*radius, 2.0/5.0*body->state->mass*radius*radius); body->state->se3 = Se3r(position,q); physics->young = sphereYoungModulus; physics->poisson = sphereKsDivKn; physics->frictionAngle = sphereFrictionDeg * Mathr::PI/180.0; physics->shearCohesion = shearCohesion; physics->normalCohesion = normalCohesion; physics->momentRotationLaw = 1; if((!dynamic) && (!boxWalls)) { physics->young = boxYoungModulus; physics->poisson = boxKsDivKn; physics->frictionAngle = boxFrictionDeg * Mathr::PI/180.0; } aabb->color = Vector3r(0,1,0); iSphere->radius = radius; iSphere->color = Vector3r(Mathr::UnitRandom(),Mathr::UnitRandom(),Mathr::UnitRandom()); iSphere->wire = false; body->shape = iSphere; body->bound = aabb; body->material = physics; } void CohesiveTriaxialTest::createBox(shared_ptr& body, Vector3r position, Vector3r extents, bool wire) { body = shared_ptr(new Body); body->groupMask=2; shared_ptr physics(new CohFrictMat); shared_ptr aabb(new Aabb); shared_ptr iBox(new Box); body->state->blockedDOFs=State::DOF_ALL; body->state->angVel = Vector3r(0,0,0); body->state->vel = Vector3r(0,0,0); body->state->mass = 0; //physics->mass = extents[0]*extents[1]*extents[2]*density*2; body->state->inertia = Vector3r( body->state->mass*(extents[1]*extents[1]+extents[2]*extents[2])/3 , body->state->mass*(extents[0]*extents[0]+extents[2]*extents[2])/3 , body->state->mass*(extents[1]*extents[1]+extents[0]*extents[0])/3 ); // physics->mass = 0; // physics->inertia = Vector3r(0,0,0); body->state->se3 = Se3r(position,Quaternionr::Identity()); physics->young = boxYoungModulus; physics->poisson = boxKsDivKn; physics->frictionAngle = boxFrictionDeg * Mathr::PI/180.0; physics->shearCohesion = 0; physics->normalCohesion = 0; physics->momentRotationLaw = 0; aabb->color = Vector3r(1,0,0); iBox->extents = extents; iBox->color = Vector3r(1,1,1); iBox->wire = wire; body->bound = aabb; body->shape = iBox; body->material = physics; } void CohesiveTriaxialTest::createActors(shared_ptr& scene) { shared_ptr interactionGeometryDispatcher(new IGeomDispatcher); shared_ptr s1(new Ig2_Sphere_Sphere_ScGeom6D); interactionGeometryDispatcher->add(s1); shared_ptr s2(new Ig2_Box_Sphere_ScGeom6D); interactionGeometryDispatcher->add(s2); shared_ptr cohesiveFrictionalRelationships = shared_ptr (new Ip2_CohFrictMat_CohFrictMat_CohFrictPhys); cohesiveFrictionalRelationships->setCohesionOnNewContacts = setCohesionOnNewContacts; shared_ptr interactionPhysicsDispatcher(new IPhysDispatcher); interactionPhysicsDispatcher->add(cohesiveFrictionalRelationships); shared_ptr collider(new InsertionSortCollider); collider->boundDispatcher->add(new Bo1_Sphere_Aabb); collider->boundDispatcher->add(new Bo1_Box_Aabb); shared_ptr newton(new NewtonIntegrator); newton->damping=dampingMomentum; shared_ptr globalStiffnessTimeStepper(new GlobalStiffnessTimeStepper); globalStiffnessTimeStepper->timeStepUpdateInterval = timeStepUpdateInterval; globalStiffnessTimeStepper->defaultDt = defaultDt; globalStiffnessTimeStepper->timestepSafetyCoefficient = 0.2; shared_ptr cohesiveFrictionalContactLaw(new CohesiveFrictionalContactLaw); triaxialcompressionEngine = shared_ptr (new TriaxialCompressionEngine); triaxialcompressionEngine-> stiffnessUpdateInterval = wallStiffnessUpdateInterval;// = stiffness update interval triaxialcompressionEngine-> radiusControlInterval = radiusControlInterval;// = stiffness update interval triaxialcompressionEngine-> sigma_iso = sigmaIsoCompaction; triaxialcompressionEngine-> sigmaLateralConfinement = sigmaLateralConfinement; triaxialcompressionEngine-> sigmaIsoCompaction = sigmaIsoCompaction; triaxialcompressionEngine-> max_vel = 1; triaxialcompressionEngine-> thickness = thickness; triaxialcompressionEngine->strainRate = strainRate; triaxialcompressionEngine->StabilityCriterion = StabilityCriterion; triaxialcompressionEngine->autoCompressionActivation = autoCompressionActivation; triaxialcompressionEngine->internalCompaction = internalCompaction; triaxialcompressionEngine->maxMultiplier = maxMultiplier; // recording global stress triaxialStateRecorder = shared_ptr(new TriaxialStateRecorder); triaxialStateRecorder-> file = WallStressRecordFile; triaxialStateRecorder-> iterPeriod = recordIntervalIter; //triaxialStateRecorder-> thickness = thickness; scene->engines.clear(); scene->engines.push_back(shared_ptr(new ForceResetter)); scene->engines.push_back(collider); scene->engines.push_back(interactionGeometryDispatcher); scene->engines.push_back(interactionPhysicsDispatcher); scene->engines.push_back(cohesiveFrictionalContactLaw); scene->engines.push_back(triaxialcompressionEngine); scene->engines.push_back(globalStiffnessTimeStepper); scene->engines.push_back(triaxialStateRecorder); //scene->engines.push_back(hydraulicForceEngine);//<-------------HYDRAULIC ENGINE HERE scene->engines.push_back(newton); } void CohesiveTriaxialTest::positionRootBody(shared_ptr& scene) { shared_ptr aabb(new Aabb); aabb->color = Vector3r(0,0,1); } string GenerateCloud_cohesive(vector& sphere_list, Vector3r lowerCorner, Vector3r upperCorner, long number, Real rad_std_dev, Real porosity) { typedef boost::minstd_rand StdGenerator; static StdGenerator generator; static boost::variate_generator > random1(generator, boost::uniform_real<>(0,1)); // static boost::variate_generator > // randomN(generator, boost::normal_distribution<>(aggregateMeanRadius,aggregateSigmaRadius)); sphere_list.clear(); long tries = 1000; //nb of tries for positionning the next sphere Vector3r dimensions = upperCorner - lowerCorner; Real mean_radius = std::pow(dimensions.x()*dimensions.y()*dimensions.z()*(1-porosity)/(3.1416*1.3333*number),0.333333); //cerr << mean_radius; std::cerr << "generating aggregates ... "; long t, i; for (i=0; i (sphere_list[j].first-s.first).squaredNorm()) overlap=true; if (!overlap) { sphere_list.push_back(s); break; } } if (t==tries) return "More than " + boost::lexical_cast(tries) + " tries while generating sphere number " + boost::lexical_cast(i+1) + "/" + boost::lexical_cast(number) + "."; } return "Generated a sample with " + boost::lexical_cast(number) + "spheres inside box of dimensions: (" + boost::lexical_cast(dimensions[0]) + "," + boost::lexical_cast(dimensions[1]) + "," + boost::lexical_cast(dimensions[2]) + ")."; } YADE_PLUGIN((CohesiveTriaxialTest)); trunk-2018.02b/pkg/dem/CohesiveTriaxialTest.hpp000066400000000000000000000202401324306050200213260ustar00rootroot00000000000000/************************************************************************* * Copyright (C) 2007 by Bruno Chareyre * * bruno.chareyre@hmg.inpg.fr * * * * This program is free software; it is licensed under the terms of the * * GNU General Public License v2 or later. See file LICENSE for details. * *************************************************************************/ #pragma once #include #include class TriaxialStressController; class TriaxialCompressionEngine; class TriaxialStateRecorder; /*! \brief Triaxial test on unsaturated sphere packings This preprocessor is a variant of TriaxialTest using the cohesive-frictional contact law with moments. See TriaxialTest documentation. */ class CohesiveTriaxialTest : public FileGenerator { private : Vector3r gravity; Vector3r spheresColor; bool wall_top ,wall_bottom ,wall_1 ,wall_2 ,wall_3 ,wall_4 ,wall_top_wire ,wall_bottom_wire ,wall_1_wire ,wall_2_wire ,wall_3_wire ,wall_4_wire ,spheresRandomColor; shared_ptr triaxialcompressionEngine; shared_ptr triaxialstressController; shared_ptr triaxialStateRecorder; void createBox(shared_ptr& body, Vector3r position, Vector3r extents,bool wire); void createSphere(shared_ptr& body, Vector3r position, Real radius,bool dynamic); void createActors(shared_ptr& scene); void positionRootBody(shared_ptr& scene); public : //CohesiveTriaxialTest (); ~CohesiveTriaxialTest (); virtual bool generate(std::string& message); YADE_CLASS_BASE_DOC_ATTRS_INIT_CTOR_PY( CohesiveTriaxialTest,FileGenerator,"This preprocessor is a variant of TriaxialTest using the cohesive-frictional contact law with moments. It sets up a scene for cohesive triaxial tests. See full documentation at http://yade-dem.org/wiki/TriaxialTest.\n\n Cohesion is initially 0 by default. The suggested usage is to define cohesion values in a second step, after isotropic compaction : define shear and normal cohesions in :yref:`Ip2_CohFrictMat_CohFrictMat_CohFrictPhys`, then turn :yref:`Ip2_CohFrictMat_CohFrictMat_CohFrictPhys`::setCohesionNow true to assign them at each contact at next iteration." , ((Vector3r,lowerCorner,Vector3r(0,0,0),,"Lower corner of the box.")) ((Vector3r,upperCorner,Vector3r(1,1,1),,"Upper corner of the box.")) ((string,importFilename,"",,"File with positions and sizes of spheres.")) ((string,Key,"",,"A code that is added to output filenames.")) ((string,fixedBoxDims,"",,"string that contains some subset (max. 2) of {'x','y','z'} ; contains axes will have box dimension hardcoded, even if box is scaled as mean_radius is prescribed: scaling will be applied on the rest.")) ((string,WallStressRecordFile,"./CohesiveWallStresses"+Key,,"")) ((bool,internalCompaction,false,,"flag for choosing between moving boundaries or increasing particles sizes during the compaction stage.")) ((bool,biaxial2dTest,false,,"FIXME : what is that?")) ((bool,fixedPoroCompaction,false,,"flag to choose an isotropic compaction until a fixed porosity choosing a same translation speed for the six walls")) ((bool,autoCompressionActivation,true,,"Do we just want to generate a stable packing under isotropic pressure (false) or do we want the triaxial loading to start automatically right after compaction stage (true)?")) ((bool,autoUnload,true,,"auto adjust the isotropic stress state from :yref:`TriaxialTest`::sigmaIsoCompaction to :yref:`TriaxialTest::sigmaLateralConfinement` if they have different values. See docs for :yref:`TriaxialCompressionEngine::autoUnload`")) ((bool,autoStopSimulation,false,,"freeze the simulation when conditions are reached (don't activate this if you want to be able to run/stop from Qt GUI)")) ((bool,noFiles,false,,"Do not create any files during run (.xml, .spheres, wall stress records)")) ((bool,facetWalls,false,,"Use facets for boundaries (not tested)")) ((bool,wallWalls,false,,"Use walls for boundaries (not tested)")) ((bool,boxWalls,true,,"Use boxes for boundaries (recommended).")) ((Real,fixedPorosity,1,,"FIXME : what is that?")) ((Real,thickness,0.001,,"thickness of boundaries. It is arbitrary and should have no effect")) ((Real,maxMultiplier,1.01,,"max multiplier of diameters during internal compaction (initial fast increase)")) ((Real,finalMaxMultiplier,1.001,,"max multiplier of diameters during internal compaction (secondary precise adjustment)")) ((Real,radiusDeviation,0.3,,"Normalized standard deviation of generated sizes.")) ((Real,radiusMean,-1,,"Mean radius. If negative (default), autocomputed to as a function of box size and :yref:`TriaxialTest::numberOfGrains`")) ((Real,sphereYoungModulus,15000000.0,,"Stiffness of spheres.")) ((Real,sphereKsDivKn,0.5,,"Ratio of shear vs. normal contact stiffness for spheres.")) ((Real,sphereFrictionDeg,18.0,,"Friction angle [°] of spheres assigned just before triaxial testing.")) ((Real,compactionFrictionDeg,sphereFrictionDeg,,"Friction angle [°] of spheres during compaction (different values result in different porosities)]. This value is overridden by :yref:`TriaxialTest::sphereFrictionDeg` before triaxial testing.")) ((Real,normalCohesion,0,,"Material parameter used to define contact strength in tension.")) ((Real,shearCohesion,0,,"Material parameter used to define shear strength of contacts.")) ((bool,setCohesionOnNewContacts,false,,"create cohesionless (False) or cohesive (True) interactions for new contacts.")) ((Real,boxYoungModulus,15000000.0,,"Stiffness of boxes.")) ((Real,maxWallVelocity,10,,"max velocity of boundaries. Usually useless, but can help stabilizing the system in some cases.")) ((Real,boxKsDivKn,0.5,,"Ratio of shear vs. normal contact stiffness for boxes.")) ((Real,boxFrictionDeg,0.0,,"Friction angle [°] of boundaries contacts.")) ((Real,density,2600,,"density of spheres")) ((Real,strainRate,0.1,,"Strain rate in triaxial loading.")) ((Real,defaultDt,0.001,,"Max time-step. Used as initial value if defined. Latter adjusted by the time stepper.")) ((Real,dampingForce,0.2,,"Coefficient of Cundal-Non-Viscous damping (applied on on the 3 components of forces)")) ((Real,dampingMomentum,0.2,,"Coefficient of Cundal-Non-Viscous damping (applied on on the 3 components of torques)")) ((Real,StabilityCriterion,0.01,,"Value of unbalanced force for which the system is considered stable. Used in conditionals to switch between loading stages.")) ((Real,wallOversizeFactor,1.3,,"Make boundaries larger than the packing to make sure spheres don't go out during deformation.")) ((Real,sigmaIsoCompaction,-50000,,"Confining stress during isotropic compaction (< 0 for real - compressive - compaction).")) ((Real,sigmaLateralConfinement,-50000,,"Lateral stress during triaxial loading (< 0 for classical compressive cases). An isotropic unloading is performed if the value is not equal to :yref:`TriaxialTest::sigmaIsoCompaction`.")) ((int,timeStepUpdateInterval,50,,"interval for :yref:`GlobalStiffnessTimeStepper`")) ((int,wallStiffnessUpdateInterval,10,,"interval for updating the stiffness of sample/boundaries contacts")) ((int,radiusControlInterval,10,,"interval between size changes when growing spheres.")) ((int,numberOfGrains,400,,"Number of generated spheres.")) ((int,recordIntervalIter,20,,"interval between file outputs")) , /* init */ , /* constructor */ wall_top = true; wall_bottom = true; wall_1 = true; wall_2 = true; wall_3 = true; wall_4 = true; wall_top_wire = true; wall_bottom_wire = true; wall_1_wire = true; wall_2_wire = true; wall_3_wire = true; wall_4_wire = true; spheresColor = Vector3r(0.8,0.3,0.3); spheresRandomColor = false; WallStressRecordFile = "./CohesiveWallStresses"+Key; gravity = Vector3r(0,-9.81,0); , //.def("setContactProperties",&TriaxialCompressionEngine::setContactProperties,"Assign a new friction angle (degrees) to dynamic bodies and relative interactions") ); }; REGISTER_SERIALIZABLE(CohesiveTriaxialTest); trunk-2018.02b/pkg/dem/ConcretePM.cpp000066400000000000000000000573201324306050200172260ustar00rootroot00000000000000// 2007,2008 © Václav Šmilauer #include"ConcretePM.hpp" #include #include #include #include #include #include #include YADE_PLUGIN((CpmState)(CpmMat)(Ip2_CpmMat_CpmMat_CpmPhys)(Ip2_FrictMat_CpmMat_FrictPhys)(CpmPhys)(Law2_ScGeom_CpmPhys_Cpm) #ifdef YADE_OPENGL (Gl1_CpmPhys) #endif (CpmStateUpdater)); /********************** Ip2_CpmMat_CpmMat_CpmPhys ****************************/ CREATE_LOGGER(Ip2_FrictMat_CpmMat_FrictPhys); void Ip2_FrictMat_CpmMat_FrictPhys::go(const shared_ptr& pp1, const shared_ptr& pp2, const shared_ptr& interaction){ TIMING_DELTAS_START(); const shared_ptr& mat1 = YADE_PTR_CAST(pp1); const shared_ptr& mat2 = YADE_PTR_CAST(pp2); Ip2_FrictMat_FrictMat_FrictPhys iPhysFunctor = Ip2_FrictMat_FrictMat_FrictPhys(); iPhysFunctor.frictAngle = frictAngle; iPhysFunctor.go(mat1,mat2,interaction); TIMING_DELTAS_CHECKPOINT("end of Ip2_FritPhys"); } CREATE_LOGGER(Ip2_CpmMat_CpmMat_CpmPhys); void Ip2_CpmMat_CpmMat_CpmPhys::go(const shared_ptr& pp1, const shared_ptr& pp2, const shared_ptr& interaction){ // no updates of an already existing contact necessary if (interaction->phys) return; TIMING_DELTAS_START(); shared_ptr cpmPhys(new CpmPhys()); interaction->phys = cpmPhys; CpmMat* mat1 = YADE_CAST(pp1.get()); CpmMat* mat2 = YADE_CAST(pp2.get()); // check unassigned values if (!mat1->neverDamage) { assert(!std::isnan(mat1->sigmaT)); assert(!std::isnan(mat1->epsCrackOnset)); assert(!std::isnan(mat1->relDuctility)); } if (!mat2->neverDamage) { assert(!std::isnan(mat2->sigmaT)); assert(!std::isnan(mat2->epsCrackOnset)); assert(!std::isnan(mat2->relDuctility)); } cpmPhys->damLaw = mat1->damLaw; // bodies sharing the same material; no averages necessary if (mat1->id>=0 && mat1->id == mat2->id) { cpmPhys->E = mat1->young; cpmPhys->G = mat1->young*mat1->poisson; cpmPhys->tanFrictionAngle = tan(mat1->frictionAngle); cpmPhys->undamagedCohesion = mat1->sigmaT; cpmPhys->isCohesive = (cohesiveThresholdIter < 0 || scene->iter < cohesiveThresholdIter); #define _CPATTR(a) cpmPhys->a=mat1->a _CPATTR(epsCrackOnset); _CPATTR(relDuctility); _CPATTR(equivStrainShearContrib); _CPATTR(neverDamage); _CPATTR(dmgTau); _CPATTR(dmgRateExp); _CPATTR(plTau); _CPATTR(plRateExp); _CPATTR(isoPrestress); #undef _CPATTR } else { // averaging over both materials #define _AVGATTR(a) cpmPhys->a=.5*(mat1->a+mat2->a) Real e = (!E) ? .5*(mat1->young + mat2->young) : (*E)(mat1->id,mat2->id,mat1->young,mat2->young); cpmPhys->E = e; cpmPhys->G = .5*(mat1->poisson + mat2->poisson)*cpmPhys->E; cpmPhys->tanFrictionAngle = tan(.5*(mat1->frictionAngle + mat2->frictionAngle)); cpmPhys->undamagedCohesion = .5*(mat1->sigmaT + mat2->sigmaT); cpmPhys->isCohesive = (cohesiveThresholdIter < 0 || scene->iter < cohesiveThresholdIter); _AVGATTR(epsCrackOnset); _AVGATTR(relDuctility); _AVGATTR(equivStrainShearContrib); cpmPhys->neverDamage = (mat1->neverDamage || mat2->neverDamage); _AVGATTR(dmgTau); _AVGATTR(dmgRateExp); _AVGATTR(plTau); _AVGATTR(plRateExp); _AVGATTR(isoPrestress); #undef _AVGATTR } // NOTE: some params are not assigned until in Law2_ScGeom_CpmPhys_Cpm, since they need geometry as well; those are: // crossSection, kn, ks, refLength TIMING_DELTAS_CHECKPOINT("end of Ip2_CpmPhys"); } /********************** CpmPhys ****************************/ CREATE_LOGGER(CpmPhys); // !! at least one virtual function in the .cpp file CpmPhys::~CpmPhys(){}; long CpmPhys::cummBetaIter=0, CpmPhys::cummBetaCount=0; Real CpmPhys::solveBeta(const Real c, const Real N){ #ifdef YADE_DEBUG cummBetaCount++; #endif const int maxIter = 20; const Real maxError = 1e-12; Real f, ret = 0.; for(int i = 0; i < maxIter; i++){ #ifdef YADE_DEBUG cummBetaIter++; #endif Real aux = c*exp(N*ret)+exp(ret); f = log(aux); if (std::abs(f) < maxError) return ret; Real df = (c*N*exp(N*ret)+exp(ret))/aux; ret -= f/df; } LOG_FATAL("No convergence after "<= epsN*omega) { // unloading, no viscous stress dmgStrain = epsN*omega; LOG_TRACE("Elastic/unloading, no viscous overstress"); return 0.; } Real c = epsCrackOnset*(1-omega)*pow(dmgTau/dt,dmgRateExp)*pow(epsN*omega-dmgStrain,dmgRateExp-1.); Real beta = solveBeta(c,dmgRateExp); Real deltaDmgStrain = (epsN*omega-dmgStrain)*exp(beta); dmgStrain += deltaDmgStrain; LOG_TRACE("deltaDmgStrain="<& I, *scene->interactions){ if(!I->isReal()) continue; CpmPhys* phys = dynamic_cast(I->phys.get()); if(phys) { ret += 0.5*phys->normalForce.squaredNorm()/((1-(phys->epsN>0?phys->omega:0))*phys->kn); ret += 0.5*phys->shearForce.squaredNorm()/phys->ks; } } return ret; #endif } #ifdef YADE_DEBUG #define CPM_YADE_DEBUG_A \ if(std::isnan(epsN)){\ /*LOG_FATAL("refLength="<refLength<<"; pos1="<se31.position<<"; pos2="<se32.position<<"; displacementN="<displacementN());*/ \ throw runtime_error("!! epsN==NaN !!");\ } #else #define CPM_YADE_DEBUG_A #endif #define YADE_VERIFY(condition) if(!(condition)){LOG_FATAL("Verification `"<<#condition<<"' failed!"); LOG_FATAL("in interaction #"<getId1()<<"+#"<getId2()); Omega::instance().saveSimulation("/tmp/verificationFailed.xml"); throw;} #define NNAN(a) YADE_VERIFY(!std::isnan(a)); #define NNANV(v) YADE_VERIFY(!std::isnan(v[0])); assert(!std::isnan(v[1])); assert(!std::isnan(v[2])); bool Law2_ScGeom_CpmPhys_Cpm::go(shared_ptr& _geom, shared_ptr& _phys, Interaction* I){ TIMING_DELTAS_START(); ScGeom* geom=static_cast(_geom.get()); CpmPhys* phys=static_cast(_phys.get()); /* just the first time */ if (I->isFresh(scene)) { const shared_ptr b1 = Body::byId(I->id1,scene); const shared_ptr b2 = Body::byId(I->id2,scene); const int sphereIndex = Sphere::getClassIndexStatic(); const int facetIndex = Facet::getClassIndexStatic(); const int wallIndex = Wall::getClassIndexStatic(); const int boxIndex = Box::getClassIndexStatic(); const int b1index = b1->shape->getClassIndex(); const int b2index = b2->shape->getClassIndex(); if (b1index == sphereIndex && b2index == sphereIndex) { // both bodies are spheres const Vector3r& pos1 = Body::byId(I->id1,scene)->state->pos; const Vector3r& pos2 = Body::byId(I->id2,scene)->state->pos; Real minRad = (geom->refR1 <= 0? geom->refR2 : (geom->refR2 <=0? geom->refR1 : std::min(geom->refR1,geom->refR2))); Vector3r shift2 = scene->isPeriodic? Vector3r(scene->cell->hSize*I->cellDist.cast()) : Vector3r::Zero(); phys->refLength = (pos2 - pos1 + shift2).norm(); phys->crossSection = Mathr::PI*pow(minRad,2); phys->refPD = geom->refR1 + geom->refR2 - phys->refLength; } else if (b1index == facetIndex || b2index == facetIndex || b1index == wallIndex || b2index == wallIndex || b1index == boxIndex || b2index == boxIndex) { // one body is facet or wall or box shared_ptr sphere, plane; if (b1index == facetIndex || b1index == wallIndex || b1index == boxIndex) { plane = b1; sphere = b2; } else { plane = b2; sphere = b1; } Real rad = ( (Sphere*) sphere->shape.get() )->radius; phys->refLength = rad; phys->crossSection = Mathr::PI*pow(rad,2); phys->refPD = 0.; } phys->kn = phys->crossSection*phys->E/phys->refLength; phys->ks = phys->crossSection*phys->G/phys->refLength; phys->epsFracture = phys->epsCrackOnset*phys->relDuctility; } /* shorthands */ Real& epsN(phys->epsN); Vector3r& epsT(phys->epsT); Vector3r& epsTPl(phys->epsTPl); Real& kappaD(phys->kappaD); /* Real& epsPlSum(phys->epsPlSum); */ const Real& E(phys->E); \ const Real& undamagedCohesion(phys->undamagedCohesion); const Real& tanFrictionAngle(phys->tanFrictionAngle); const Real& G(phys->G); const Real& crossSection(phys->crossSection); const Real& omegaThreshold(this->omegaThreshold); const Real& epsCrackOnset(phys->epsCrackOnset); Real& relResidualStrength(phys->relResidualStrength); /*const Real& relDuctility(phys->relDuctility); */ const Real& epsFracture(phys->epsFracture); const int& damLaw(phys->damLaw); const bool& neverDamage(phys->neverDamage); Real& omega(phys->omega); Real& sigmaN(phys->sigmaN); Vector3r& sigmaT(phys->sigmaT); Real& Fn(phys->Fn); Vector3r& Fs(phys->Fs); /* for python access */ const bool& isCohesive(phys->isCohesive); #ifdef CPM_MATERIAL_MODEL const Real& dt = scene->dt; const Real& dmgTau(phys->dmgTau); const Real& plTau(phys->plTau); const Real& yieldLogSpeed(this->yieldLogSpeed); const int& yieldSurfType(this->yieldSurfType); const Real& yieldEllipseShift(this->yieldEllipseShift); #endif Real& epsNPl(phys->epsNPl); const Real& epsSoft(this->epsSoft); const Real& relKnSoft(this->relKnSoft); TIMING_DELTAS_CHECKPOINT("GO A"); epsN = - (-phys->refPD + geom->penetrationDepth) / phys->refLength; //epsT = geom->rotate(epsT); geom->rotate(epsT); //epsT += geom->shearIncrement() / (phys->refLength + phys->refPD) ; epsT += geom->shearIncrement() / phys->refLength; /* debugging */ CPM_YADE_DEBUG_A NNAN(epsN); NNANV(epsT); /* constitutive law */ #ifdef CPM_MATERIAL_MODEL CPM_MATERIAL_MODEL #else /* simplified public model */ epsN += phys->isoPrestress/E; /* very simplified version of the constitutive law */ Real xi2 = std::pow(phys->equivStrainShearContrib,2); Real epsNorm = std::sqrt(std::pow(std::max(epsN-epsNPl,0.),2)+xi2*epsT.squaredNorm()); kappaD = std::max(epsNorm,kappaD); /* internal variable, max positive strain (non-decreasing) */ omega = isCohesive? phys->funcG(kappaD,epsCrackOnset,epsFracture,neverDamage,damLaw) : 1.; /* damage variable (non-decreasing, as funcG is also non-decreasing) */ sigmaN = (1-(epsN-epsNPl>0?omega:0))*E*(epsN-epsNPl); /* damage taken in account in tension only */ if((epsSoft<0) && (epsN-epsNPlsigmaN){ /*assert(sigmaNSoft>sigmaN);*/ epsNPl+=(sigmaN-sigmaNSoft)/E; sigmaN=sigmaNSoft; } } sigmaT = G*(epsT-epsTPl); /* trial stress */ Real yieldSigmaT = std::max((Real)0.,undamagedCohesion*(1-omega)-sigmaN*tanFrictionAngle); /* Mohr-Coulomb law with damage */ if (sigmaT.squaredNorm() > yieldSigmaT*yieldSigmaT) { Real scale = yieldSigmaT/sigmaT.norm(); sigmaT *= scale; /* stress return */ epsTPl += sigmaT*(1-scale)/G; } relResidualStrength = isCohesive? (kappaDisoPrestress; NNAN(sigmaN); NNANV(sigmaT); NNAN(crossSection); if (!neverDamage) { NNAN(kappaD); NNAN(epsFracture); NNAN(omega); } /* handle broken contacts */ if (epsN>0. && ((isCohesive && omega>omegaThreshold) || !isCohesive)) { /* if (isCohesive) { */ const shared_ptr& body1 = Body::byId(I->getId1(),scene), body2 = Body::byId(I->getId2(),scene); assert(body1); assert(body2); const shared_ptr& st1 = YADE_PTR_CAST(body1->state), st2 = YADE_PTR_CAST(body2->state); /* nice article about openMP::critical vs. scoped locks: http://www.thinkingparallel.com/2006/08/21/scoped-locking-vs-critical-in-openmp-a-personal-shootout/ */ { boost::mutex::scoped_lock lock(st1->updateMutex); st1->numBrokenCohesive += 1; /* st1->epsPlBroken += epsPlSum; */ } { boost::mutex::scoped_lock lock(st2->updateMutex); st2->numBrokenCohesive += 1; /* st2->epsPlBroken += epsPlSum; */ } /* } */ return false; } Fn = sigmaN*crossSection; phys->normalForce = -Fn*geom->normal; Fs = sigmaT*crossSection; phys->shearForce = -Fs; TIMING_DELTAS_CHECKPOINT("GO B"); Body::id_t id1 = I->getId1(); Body::id_t id2 = I->getId2(); State* b1 = Body::byId(id1,scene)->state.get(); State* b2 = Body::byId(id2,scene)->state.get(); Vector3r f = -phys->normalForce - phys->shearForce; if (!scene->isPeriodic) { applyForceAtContactPoint(f, geom->contactPoint , id1, b1->se3.position, id2, b2->se3.position); } else { scene->forces.addForce(id1,f); scene->forces.addForce(id2,-f); scene->forces.addTorque(id1,(geom->radius1+.5*(phys->refPD-geom->penetrationDepth))*geom->normal.cross(f)); scene->forces.addTorque(id2,(geom->radius2+.5*(phys->refPD-geom->penetrationDepth))*geom->normal.cross(f)); } TIMING_DELTAS_CHECKPOINT("rest"); return true; } #ifdef YADE_OPENGL /********************** Gl1_CpmPhys ****************************/ #include #include CREATE_LOGGER(Gl1_CpmPhys); bool Gl1_CpmPhys::contactLine=true; bool Gl1_CpmPhys::dmgLabel=true; bool Gl1_CpmPhys::dmgPlane=false; bool Gl1_CpmPhys::epsNLabel=true; bool Gl1_CpmPhys::epsT=false; bool Gl1_CpmPhys::epsTAxes=false; bool Gl1_CpmPhys::normal=false; Real Gl1_CpmPhys::colorStrainRatio=-1; void Gl1_CpmPhys::go(const shared_ptr& ip, const shared_ptr& i, const shared_ptr& b1, const shared_ptr& b2, bool wireFrame){ const shared_ptr& phys = boost::static_pointer_cast(ip); const shared_ptr& geom = YADE_PTR_CAST(i->geom); // FIXME: get the scene for periodicity; ugly! Scene* scene=Omega::instance().getScene().get(); //Vector3r lineColor(phys->omega,1-phys->omega,0.0); /* damaged links red, undamaged green */ Vector3r lineColor = Shop::scalarOnColorScale(1.-phys->relResidualStrength); if(colorStrainRatio>0) lineColor = Shop::scalarOnColorScale(phys->epsN/(phys->epsCrackOnset*colorStrainRatio)); // FIXME: should be computed by the renderer; for now, use the real values //Vector3r pos1=geom->se31.position, pos2=geom->se32.position; Vector3r pos1 = scene->bodies->operator[](i->id1)->state->pos, pos2 = scene->bodies->operator[](i->id2)->state->pos; if (scene->isPeriodic) { Vector3r dPos = pos2 - pos1; pos1=scene->cell->wrapShearedPt(pos1); Vector3r shift2 = scene->isPeriodic? Vector3r(scene->cell->hSize*i->cellDist.cast()) : Vector3r::Zero();\ pos2 = pos1 + dPos + shift2; //phys->refLength = (pos2 - pos1 + shift2).norm(); //pos2=pos1+(geom->se32.position-geom->se31.position); } /* if (scene->isPeriodic) { Vector3r temp = pos2 - pos1; pos1 = scene->cell->wrapShearedPt(pos1); pos2 = pos1 + temp; } */ if (contactLine) GLUtils::GLDrawLine(pos1,pos2,lineColor); if (dmgLabel) { GLUtils::GLDrawNum(phys->omega,0.5*(pos1+pos2),lineColor); } else if (epsNLabel) { GLUtils::GLDrawNum(phys->epsN,0.5*(pos1+pos2),lineColor); } if (phys->omega>0 && dmgPlane) { Real halfSize = sqrt(1-phys->relResidualStrength)*.5*.705*sqrt(phys->crossSection); Vector3r midPt = .5*Vector3r(pos1+pos2); glDisable(GL_CULL_FACE); glPushMatrix(); glTranslatev(midPt); Quaternionr q; q.setFromTwoVectors(Vector3r::UnitZ(),geom->normal); AngleAxisr aa(q); glRotatef(aa.angle()*Mathr::RAD_TO_DEG,aa.axis()[0],aa.axis()[1],aa.axis()[2]); glBegin(GL_POLYGON); glColor3v(lineColor); glVertex3d(halfSize,0.,0.); glVertex3d(.5*halfSize,.866*halfSize,0.); glVertex3d(-.5*halfSize,.866*halfSize,0.); glVertex3d(-halfSize,0.,0.); glVertex3d(-.5*halfSize,-.866*halfSize,0.); glVertex3d(.5*halfSize,-.866*halfSize,0.); glEnd(); glPopMatrix(); } Vector3r cp = boost::static_pointer_cast(i->geom)->contactPoint; if (scene->isPeriodic) {cp = scene->cell->wrapShearedPt(cp);} if (epsT) { Real maxShear = (phys->undamagedCohesion-phys->sigmaN*phys->tanFrictionAngle)/phys->G; Real relShear = phys->epsT.norm()/maxShear; Real scale = phys->refLength; Vector3r dirShear = phys->epsT; dirShear.normalize(); if(epsTAxes){ GLUtils::GLDrawLine(cp-Vector3r(scale,0,0),cp+Vector3r(scale,0,0)); GLUtils::GLDrawLine(cp-Vector3r(0,scale,0),cp+Vector3r(0,scale,0)); GLUtils::GLDrawLine(cp-Vector3r(0,0,scale),cp+Vector3r(0,0,scale)); } GLUtils::GLDrawArrow(cp,cp+dirShear*relShear*scale,Vector3r(1.,0.,0.)); GLUtils::GLDrawLine(cp+dirShear*relShear*scale,cp+dirShear*scale,Vector3r(.3,.3,.3)); /* normal strain */ GLUtils::GLDrawArrow(cp,cp+geom->normal*(phys->epsN/maxShear),Vector3r(0.,1.,0.)); } //if(normal) GLUtils::GLDrawArrow(cp,cp+geom->normal*.5*phys->equilibriumDist,Vector3r(0.,1.,0.)); } #endif /********************** CpmStateUpdater ****************************/ CREATE_LOGGER(CpmStateUpdater); //Real CpmStateUpdater::maxOmega=0.; //Real CpmStateUpdater::avgRelResidual=0.; void CpmStateUpdater::update(Scene* _scene){ Scene *scene = _scene? _scene : Omega::instance().getScene().get(); vector bodyStats; bodyStats.resize(scene->bodies->size()); assert(bodyStats[0].nCohLinks == 0); // should be initialized by dfault ctor avgRelResidual = 0; Real nAvgRelResidual = 0; Matrix3r identity = Matrix3r::Identity(); Real dmg; Matrix3r incr; FOREACH(const shared_ptr& I, *scene->interactions){ if (!I) continue; if (!I->isReal()) continue; shared_ptr phys = YADE_PTR_DYN_CAST(I->phys); if (!phys) continue; const Body::id_t id1 = I->getId1(), id2 = I->getId2(); GenericSpheresContact* geom = YADE_CAST(I->geom.get()); const Vector3r& n = geom->normal; const Real& Fn = phys->Fn; const Vector3r& Fs = phys->Fs; //stress[i,j] += geom->refLength*(Fn*n[i]*n[j]+0.5*(Fs[i]*n[j]+Fs[j]*n[i])); //stress += geom->refLength*(Fn*outer(n,n)+.5*(outer(Fs,n)+outer(n,Fs))); Matrix3r stress = phys->refLength*(Fn*n*n.transpose()+.5*(Fs*n.transpose()+n*Fs.transpose())); bodyStats[id1].stress += stress; bodyStats[id2].stress += stress; bodyStats[id1].nLinks++; bodyStats[id2].nLinks++; if (!phys->isCohesive) continue; bodyStats[id1].nCohLinks++; bodyStats[id1].dmgSum += (1-phys->relResidualStrength); // bodyStats[id1].epsPlSum += phys->epsPlSum; bodyStats[id2].nCohLinks++; bodyStats[id2].dmgSum += (1-phys->relResidualStrength); // bodyStats[id2].epsPlSum += phys->epsPlSum; maxOmega = std::max(maxOmega,phys->omega); avgRelResidual += phys->relResidualStrength; nAvgRelResidual += 1; for (int i=0; i<3; i++) { for (int j=0; j<3; j++) { dmg = 1-phys->relResidualStrength; incr = -identity*dmg*1.5 + n*n.transpose()*dmg*7.5; bodyStats[id1].damageTensor += incr; bodyStats[id2].damageTensor += incr; } } } // Real tr; FOREACH(shared_ptr B, *scene->bodies){ if (!B) continue; const Body::id_t& id = B->getId(); // add damaged contacts that have already been deleted CpmState* state = dynamic_cast(B->state.get()); if (!state) continue; state->stress = bodyStats[id].stress; int cohLinksWhenever = bodyStats[id].nCohLinks+state->numBrokenCohesive; if (cohLinksWhenever>0) { state->normDmg = (bodyStats[id].dmgSum+state->numBrokenCohesive)/cohLinksWhenever; // state->normEpsPl = (bodyStats[id].epsPlSum+state->epsPlBroken)/cohLinksWhenever; if (state->normDmg>1) { LOG_WARN("#"<damageTensor = bodyStats[id].damageTensor / cohLinksWhenever; } else { state->normDmg = 0; /*state->normEpsPl=0;*/ state->damageTensor = Matrix3r::Zero(); } B->shape->color = Vector3r(state->normDmg,1-state->normDmg,B->state->blockedDOFs==State::DOF_ALL?0:1); nAvgRelResidual += 0.5*state->numBrokenCohesive; // add half or broken interactions, other body has the other half Sphere* sphere = dynamic_cast(B->shape.get()); if(!sphere) continue; Real& r = sphere->radius; state->stress = bodyStats[id].stress/(4/3.*Mathr::PI*r*r*r/.62)*.5; } avgRelResidual /= nAvgRelResidual; } #undef YADE_VERIFY #undef NNAN #undef NNANV trunk-2018.02b/pkg/dem/ConcretePM.hpp000066400000000000000000000477731324306050200172460ustar00rootroot00000000000000// 2008 © Václav Šmilauer /* === HIGH LEVEL OVERVIEW OF CPM === Concrete Particle Model (ConcretePM, Cpm) is a set of classes for modelling mechanical behavior of concrete. Several classes are needed for Cpm. 1. CpmMat (Cpm material) deriving from BodyMacroParameters, which additionally has some information about damage on the body, cummulative plastic strain etc. 2. Ip2_CpmMat_CpmMat_CpmPhys is 2-ary functor for creating CpmPhys from CpmMat's of 2 bodies that collide. Some parameters of the CpmPhys created are computed from CpmMat's, others are passed as parameters of the functor. 3. CpmPhys (Cpm (interaction)Physics) holds various parameters as well as internal variables of the contact that can change as result of plasticity, damage, viscosity. 4. Law2_ScGeom_CpmPhys_Cpm is constitutive law that takes geometry of the interaction (ScGeom) and CpmPhys, computing forces on both bodies and updating contact variables. The model itself is defined in the macro CPM_MATERIAL_MODEL, but due to commercial reasons, those about 30 lines of code cannot be disclosed now and the macro is defined in an external file. The model will be, however, described in enough detail in my thesis (once it is written), along with calibration procedures; it features damage, plasticity and viscosity and is quite tunable (rigidity, poisson's ratio, compressive/tensile strength ratio, fracture energy, behavior under confinement, rate-dependence). There are other classes, which are not strictly necessary: * CpmGlobalCharacteristics computes a few information about individual bodies based on interactions they are involved in. It is probably quite useless now since volumetricStrain is not used in the constitutive law anymore. * Gl1_CpmPhys draws interaction physics (color for damage and a few other); rarely used, though. * CpmStateUpdater changes bodies' colors depending on average damage of their interactions and number of interactions that were already fully broken and have disappeared. This engine contains its own loop (2 loops, more precisely) over all bodies and is run periodically to update colors. */ #pragma once #include #include #include #include #include #include #include #include namespace py=boost::python; /********************************************************************************* * * C P M S T A T E * *********************************************************************************/ /* Cpm state information about each body. None of that is used for computation (at least not now), only for post-processing. */ class CpmState: public State { YADE_CLASS_BASE_DOC_ATTRS_CTOR(CpmState,State,"State information about body use by :yref:`cpm-model`.\n\nNone of that is used for computation (at least not now), only for post-processing.", ((Real,epsVolumetric,0,,"Volumetric strain around this body (unused for now)")) ((int,numBrokenCohesive,0,,"Number of (cohesive) contacts that damaged completely")) ((int,numContacts,0,,"Number of contacts with this body")) ((Real,normDmg,0,,"Average damage including already deleted contacts (it is really not damage, but 1-relResidualStrength now)")) //((Real,epsPlBroken,0,,"Plastic strain on contacts already deleted (bogus values)")) //((Real,normEpsPl,0,,"Sum of plastic strains normalized by number of contacts (bogus values)")) ((Matrix3r,stress,Matrix3r::Zero(),,"Stress tensor of the spherical particle (under assumption that particle volume = pi*r*r*r*4/3.) for packing fraction 0.62")) ((Matrix3r,damageTensor,Matrix3r::Zero(),,"Damage tensor computed with microplane theory averaging. state.damageTensor.trace() = state.normDmg")), /*ctor*/ createIndex(); ); REGISTER_CLASS_INDEX(CpmState,State); }; REGISTER_SERIALIZABLE(CpmState); /********************************************************************************* * * C P M M A T * *********************************************************************************/ /* This class holds information associated with each body */ class CpmMat: public FrictMat { public: virtual shared_ptr newAssocState() const { return shared_ptr(new CpmState); } virtual bool stateTypeOk(State* s) const { return (bool)dynamic_cast(s); } YADE_CLASS_BASE_DOC_ATTRS_CTOR(CpmMat,FrictMat,"Concrete material, for use with other Cpm classes. \n\n.. note::\n\n\t:yref:`Density` is initialized to 4800 kgm⁻³automatically, which gives approximate 2800 kgm⁻³ on 0.5 density packing.\n\nConcrete Particle Model (CPM)\n\n\n:yref:`CpmMat` is particle material, :yref:`Ip2_CpmMat_CpmMat_CpmPhys` averages two particles' materials, creating :yref:`CpmPhys`, which is then used in interaction resultion by :yref:`Law2_ScGeom_CpmPhys_Cpm`. :yref:`CpmState` is associated to :yref:`CpmMat` and keeps state defined on particles rather than interactions (such as number of completely damaged interactions).\n\nThe model is contained in externally defined macro CPM_MATERIAL_MODEL, which features damage in tension, plasticity in shear and compression and rate-dependence. For commercial reasons, rate-dependence and compression-plasticity is not present in reduced version of the model, used when CPM_MATERIAL_MODEL is not defined. The full model will be described in detail in my (Václav Šmilauer) thesis along with calibration procedures (rigidity, poisson's ratio, compressive/tensile strength ratio, fracture energy, behavior under confinement, rate-dependent behavior).\n\nEven the public model is useful enough to run simulation on concrete samples, such as :ysrc:`uniaxial tension-compression test`.", ((Real,sigmaT,NaN,,"Initial cohesion [Pa]")) ((bool,neverDamage,false,,"If true, no damage will occur (for testing only).")) ((Real,epsCrackOnset,NaN,,"Limit elastic strain [-]")) ((Real,relDuctility,NaN,,"relative ductility of bonds in normal direction")) ((Real,equivStrainShearContrib,0,,"Coefficient of shear contribution to equivalent strain")) ((int,damLaw,1,,"Law for damage evolution in uniaxial tension. 0 for linear stress-strain softening branch, 1 (default) for exponential damage evolution law")) ((Real,dmgTau,((void)"deactivated if negative",-1),,"Characteristic time for normal viscosity. [s]")) ((Real,dmgRateExp,0,,"Exponent for normal viscosity function. [-]")) ((Real,plTau,((void)"deactivated if negative",-1),,"Characteristic time for visco-plasticity. [s]")) ((Real,plRateExp,0,,"Exponent for visco-plasticity function. [-]")) ((Real,isoPrestress,0,,"Isotropic prestress of the whole specimen. [Pa]")), createIndex(); density=4800; ); REGISTER_CLASS_INDEX(CpmMat,FrictMat); }; REGISTER_SERIALIZABLE(CpmMat); /********************************************************************************* * * C P M P H Y S * *********************************************************************************/ /*! @brief representation of a single interaction of the CPM type: storage for relevant parameters. * * Evolution of the contact is governed by Law2_ScGeom_CpmPhys_Cpm: * that includes damage effects and chages of parameters inside CpmPhys. * */ class CpmPhys: public NormShearPhys { public: static long cummBetaIter, cummBetaCount; /*! auxiliary variable for visualization, recalculated in Law2_ScGeom_CpmPhys_Cpm at every iteration */ // Fn and Fs are also stored as Vector3r normalForce, shearForce in NormShearPhys Real omega = 0, Fn = 0, sigmaN, epsN = 0, relResidualStrength, kappaD = 0, epsNPl = 0; Vector3r sigmaT, Fs, epsTPl, epsT; static Real solveBeta(const Real c, const Real N); Real computeDmgOverstress(Real dt); Real computeViscoplScalingFactor(Real sigmaTNorm, Real sigmaTYield,Real dt); /* damage evolution law */ static Real funcG(const Real& kappaD, const Real& epsCrackOnset, const Real& epsFracture, const bool& neverDamage, const int& damLaw); static Real funcGDKappa(const Real& kappaD, const Real& epsCrackOnset, const Real& epsFracture, const bool& neverDamage, const int& damLaw); /* inverse damage evolution law */ static Real funcGInv(const Real& omega, const Real& epsCrackOnset, const Real& epsFracture, const bool& neverDamage, const int& damLaw); void setDamage(Real dmg); void setRelResidualStrength(Real r); virtual ~CpmPhys(); #define _ZERO_VECTOR3R(v) v = Vector3r::Zero() YADE_CLASS_BASE_DOC_ATTRS_CTOR_PY(CpmPhys,NormShearPhys,"Representation of a single interaction of the Cpm type: storage for relevant parameters.\n\n Evolution of the contact is governed by :yref:`Law2_ScGeom_CpmPhys_Cpm`, that includes damage effects and chages of parameters inside CpmPhys. See :yref:`cpm-model` for details.", ((Real,E,NaN,,"normal modulus (stiffness / crossSection) [Pa]")) ((Real,G,NaN,,"shear modulus [Pa]")) ((Real,tanFrictionAngle,NaN,,"tangens of internal friction angle [-]")) ((Real,undamagedCohesion,NaN,,"virgin material cohesion [Pa]")) ((Real,crossSection,NaN,,"equivalent cross-section associated with this contact [m²]")) ((Real,refLength,NaN,,"initial length of interaction [m]")) ((Real,refPD,NaN,,"initial penetration depth of interaction [m] (used with ScGeom)")) ((Real,epsCrackOnset,NaN,,"strain at which the material starts to behave non-linearly")) ((Real,relDuctility,NaN,,"Relative ductility of bonds in normal direction")) ((Real,epsFracture,NaN,,"strain at which the bond is fully broken [-]")) ((Real,equivStrainShearContrib,NaN,,"Coefficient of shear contribution to equivalent strain")) ((Real,dmgTau,-1,,"characteristic time for damage (if non-positive, the law without rate-dependence is used)")) ((Real,dmgRateExp,0,,"exponent in the rate-dependent damage evolution")) ((Real,dmgStrain,0,,"damage strain (at previous or current step)")) ((Real,dmgOverstress,0,,"damage viscous overstress (at previous step or at current step)")) ((Real,plTau,-1,,"characteristic time for viscoplasticity (if non-positive, no rate-dependence for shear)")) ((Real,plRateExp,0,,"exponent in the rate-dependent viscoplasticity")) ((Real,isoPrestress,0,,"\"prestress\" of this link (used to simulate isotropic stress)")) ((bool,neverDamage,false,,"the damage evolution function will always return virgin state")) ((int,damLaw,1,,"Law for softening part of uniaxial tension. 0 for linear, 1 for exponential (default)")) //((Real,epsPlSum,0,,"cummulative shear plastic strain measure (scalar) on this contact")) ((bool,isCohesive,false,,"if not cohesive, interaction is deleted when distance is greater than zero.")) , // ctors createIndex(); epsN = 0; _ZERO_VECTOR3R(epsT); _ZERO_VECTOR3R(Fs); _ZERO_VECTOR3R(epsTPl); , .def_readonly("omega",&CpmPhys::omega,"Damage internal variable |yupdate|") .def_readonly("Fn",&CpmPhys::Fn,"Magnitude of normal force |yupdate|") .def_readonly("Fs",&CpmPhys::Fs,"Magnitude of shear force |yupdate|") .def_readonly("epsN",&CpmPhys::epsN,"Current normal strain |yupdate|") .def_readonly("epsT",&CpmPhys::epsT,"Current shear strain |yupdate|") .def_readonly("sigmaN",&CpmPhys::sigmaN,"Current normal stress |yupdate|") .def_readonly("sigmaT",&CpmPhys::sigmaT,"Current shear stress |yupdate|") .def_readonly("kappaD",&CpmPhys::kappaD,"Up to now maximum normal strain (semi-norm), non-decreasing in time |yupdate|") .def_readonly("epsNPl",&CpmPhys::epsNPl,"normal plastic strain (initially zero) |yupdate|") .def_readonly("epsTPl",&CpmPhys::epsTPl,"shear plastic strain (initially zero) |yupdate|") .def_readonly("relResidualStrength",&CpmPhys::relResidualStrength,"Relative residual strength |yupdate|") .def_readonly("cummBetaIter",&CpmPhys::cummBetaIter,"Cummulative number of iterations inside CpmMat::solveBeta (for debugging).") .def_readonly("cummBetaCount",&CpmPhys::cummBetaCount,"Cummulative number of calls of CpmMat::solveBeta (for debugging).") .def("funcG",&CpmPhys::funcG,(py::arg("kappaD"),py::arg("epsCrackOnset"),py::arg("epsFracture"),py::arg("neverDamage")=false,py::arg("damLaw")=1),"Damage evolution law, evaluating the $\\omega$ parameter. $\\kappa_D$ is historically maximum strain, *epsCrackOnset* ($\\varepsilon_0$) = :yref:`CpmPhys.epsCrackOnset`, *epsFracture* = :yref:`CpmPhys.epsFracture`; if *neverDamage* is ``True``, the value returned will always be 0 (no damage). TODO") .staticmethod("funcG") .def("funcGInv",&CpmPhys::funcGInv,(py::arg("omega"),py::arg("epsCrackOnset"),py::arg("epsFracture"),py::arg("neverDamage")=false,py::arg("damLaw")=1),"Inversion of damage evolution law, evaluating the $\\kappa_D$ parameter. $\\omega$ is damage, for other parameters see funcG function") .staticmethod("funcGInv") .def("setDamage",&CpmPhys::setDamage,"TODO") .def("setRelResidualStrength",&CpmPhys::setRelResidualStrength,"TODO") ); #undef _ZERO_VECTOR3R DECLARE_LOGGER; REGISTER_CLASS_INDEX(CpmPhys,NormShearPhys); }; REGISTER_SERIALIZABLE(CpmPhys); /********************************************************************************* * * I P 2 * *********************************************************************************/ /*! @brief Convert macroscopic properties to CpmPhys with corresponding parameters. * * */ class Ip2_CpmMat_CpmMat_CpmPhys: public IPhysFunctor{ public: virtual void go(const shared_ptr& pp1, const shared_ptr& pp2, const shared_ptr& interaction); FUNCTOR2D(CpmMat,CpmMat); DECLARE_LOGGER; YADE_CLASS_BASE_DOC_ATTRS(Ip2_CpmMat_CpmMat_CpmPhys,IPhysFunctor,"Convert 2 :yref:`CpmMat` instances to :yref:`CpmPhys` with corresponding parameters. Uses simple (arithmetic) averages if material are different. Simple copy of parameters is performed if the :yref:`material` is shared between both particles. See :yref:`cpm-model` for detals.", ((long,cohesiveThresholdIter,10,,"Should new contacts be cohesive? They will before this iter#, they will not be afterwards. If 0, they will never be. If negative, they will always be created as cohesive (10 by default).")) ((shared_ptr,E,,,"Instance of :yref:`MatchMaker` determining how to compute interaction's normal modulus. If ``None``, average value is used.")) ); }; REGISTER_SERIALIZABLE(Ip2_CpmMat_CpmMat_CpmPhys); class Ip2_FrictMat_CpmMat_FrictPhys: public IPhysFunctor{ public: virtual void go(const shared_ptr& pp1, const shared_ptr& pp2, const shared_ptr& interaction); FUNCTOR2D(FrictMat,CpmMat); DECLARE_LOGGER; YADE_CLASS_BASE_DOC_ATTRS(Ip2_FrictMat_CpmMat_FrictPhys,IPhysFunctor,"Convert :yref:`CpmMat` instance and :yref:`FrictMat` instance to :yref:`FrictPhys` with corresponding parameters (young, poisson, frictionAngle). Uses simple (arithmetic) averages if material parameters are different.", ((shared_ptr,frictAngle,,,"See :yref:`Ip2_FrictMat_FrictMat_FrictPhys`.")) ); }; REGISTER_SERIALIZABLE(Ip2_FrictMat_CpmMat_FrictPhys); /********************************************************************************* * * L A W 2 * *********************************************************************************/ class Law2_ScGeom_CpmPhys_Cpm: public LawFunctor{ public: virtual bool go(shared_ptr& _geom, shared_ptr& _phys, Interaction* I); Real elasticEnergy(); Real yieldSigmaTMagnitude(Real sigmaN, Real omega, Real undamagedCohesion, Real tanFrictionAngle) { #ifdef CPM_MATERIAL_MODEL return CPM_YIELD_SIGMA_T_MAGNITUDE(sigmaN); #else //return max((Real)0.,undamagedCohesion*(1-omega)-sigmaN*tanFrictionAngle); throw std::runtime_error("Full CPM model not available in this build"); #endif } //void go(shared_ptr& _geom, shared_ptr& _phys, Interaction* I); FUNCTOR2D(GenericSpheresContact,CpmPhys); YADE_CLASS_BASE_DOC_ATTRS_CTOR_PY(Law2_ScGeom_CpmPhys_Cpm,LawFunctor,"Constitutive law for the :yref:`cpm-model`.", ((int,yieldSurfType,2,,"yield function: 0: mohr-coulomb (original); 1: parabolic; 2: logarithmic, 3: log+lin_tension, 4: elliptic, 5: elliptic+log")) ((Real,yieldLogSpeed,.1,,"scaling in the logarithmic yield surface (should be <1 for realistic results; >=0 for meaningful results)")) ((Real,yieldEllipseShift,NaN,,"horizontal scaling of the ellipse (shifts on the +x axis as interactions with +y are given)")) ((Real,omegaThreshold,((void)">=1. to deactivate, i.e. never delete any contacts",1.),,"damage after which the contact disappears (<1), since omega reaches 1 only for strain →+∞")) ((Real,epsSoft,((void)"approximates confinement (for -3e-3) -20MPa precisely, -100MPa a little over, -200 and -400 are OK (secant)",1.),,"Strain at which softening in compression starts (non-negative to deactivate). The default value is such that plasticity does not occur")) ((Real,relKnSoft,.3,,"Relative rigidity of the softening branch in compression (0=perfect elastic-plastic, <0 softening, >0 hardening)")), /*ctor*/, .def("yieldSigmaTMagnitude",&Law2_ScGeom_CpmPhys_Cpm::yieldSigmaTMagnitude,(py::arg("sigmaN"),py::arg("omega"),py::arg("undamagedCohesion"),py::arg("tanFrictionAngle")),"Return radius of yield surface for given material and state parameters; uses attributes of the current instance (*yieldSurfType* etc), change them before calling if you need that.") .def("elasticEnergy",&Law2_ScGeom_CpmPhys_Cpm::elasticEnergy,"Compute and return the total elastic energy in all \"CpmPhys\" contacts") ); DECLARE_LOGGER; }; REGISTER_SERIALIZABLE(Law2_ScGeom_CpmPhys_Cpm); /********************************************************************************* * * C P M O P E N G L * *********************************************************************************/ #ifdef YADE_OPENGL #include class Gl1_CpmPhys: public GlIPhysFunctor { public: virtual void go(const shared_ptr&,const shared_ptr&,const shared_ptr&,const shared_ptr&,bool wireFrame); virtual ~Gl1_CpmPhys() {}; RENDERS(CpmPhys); DECLARE_LOGGER; YADE_CLASS_BASE_DOC_STATICATTRS(Gl1_CpmPhys,GlIPhysFunctor,"Render :yref:`CpmPhys` objects of interactions.", ((bool,contactLine,true,,"Show contact line")) ((bool,dmgLabel,true,,"Numerically show contact damage parameter")) ((bool,dmgPlane,false,,"[what is this?]")) ((bool,epsT,false,,"Show shear strain ")) ((bool,epsTAxes,false,,"Show axes of shear plane ")) ((bool,normal,false,,"Show contact normal")) ((Real,colorStrainRatio,-1,,"If positive, set the interaction (wire) color based on $\\eps_N$ normalized by $\\eps_0$ × :yref:`colorStrainRatio` ($\\eps_0$ = :yref:`CpmPhys.epsCrackOnset` ). Otherwise, color based on the residual strength.")) ((bool,epsNLabel,false,,"Numerically show normal strain")) ); }; REGISTER_SERIALIZABLE(Gl1_CpmPhys); #endif /********************************************************************************* * * C P M S T A T E U P D A T E R * *********************************************************************************/ class CpmStateUpdater: public PeriodicEngine { struct BodyStats{ int nCohLinks; int nLinks; Real dmgSum /*, epsPlSum*/; Matrix3r stress; Matrix3r damageTensor; BodyStats(): nCohLinks(0), nLinks(0), dmgSum(0.) /*, epsPlSum(0.)*/, stress(Matrix3r::Zero()), damageTensor(Matrix3r::Zero()) {} }; public: virtual void action(){ update(scene); } void update(Scene* rb=NULL); YADE_CLASS_BASE_DOC_ATTRS_CTOR(CpmStateUpdater,PeriodicEngine,"Update :yref:`CpmState` of bodies based on state variables in :yref:`CpmPhys` of interactions with this bod. In particular, bodies' colors and :yref:`CpmState::normDmg` depending on average :yref:`damage` of their interactions and number of interactions that were already fully broken and have disappeared is updated. This engine contains its own loop (2 loops, more precisely) over all bodies and should be run periodically to update colors during the simulation, if desired.", ((Real,avgRelResidual,NaN,,"Average residual strength at last run.")) ((Real,maxOmega,NaN,,"Globally maximum damage parameter at last run.")), initRun=true; ); DECLARE_LOGGER; }; REGISTER_SERIALIZABLE(CpmStateUpdater); trunk-2018.02b/pkg/dem/DelaunayInterpolation.hpp000066400000000000000000000161401324306050200215410ustar00rootroot00000000000000/************************************************************************* * Copyright (C) 2013 by Bruno Chareyre * * * * This program is free software; it is licensed under the terms of the * * GNU General Public License v2 or later. See file LICENSE for details. * *************************************************************************/ #pragma once #include #include #include #include #include #include ///////////////////////////////////////////////////////////////////// /* TYPES: Triangulation_vertex_base_with_id_3: we redefine a vertex base including an index for each vertex (available in CGAL in 2D but not in 3D), MeniscusPhysicalData: the physical variables describing a capillary bridge, with a few algebraic operators for computing weighted averages Meniscus: a structure combining MeniscusPhysicalData with some cached data allowing faster operations in multiple queries (pointer to the last cell found and its normals) FUNCTIONS: getIncidentVtxWeights: an interpolation algorithm which is 20x faster than the natural neighbor interpolation of CGAL. returns a list of vertices incident to a query point and their respective weights interpolate: uses the results of getIncidentVtxWeights combined with a data array to return a weighted average, may be used with arbitrary data types provided they have the required algebraic operators main: example usage */ ///////////////////////////////////////////////////////////////////// namespace CGAL { //Vertex base including an index for each vertex, adapted from CGAL::Triangulation_vertex_base_with_id_2 template < typename GT, typename Vb = Triangulation_vertex_base_with_info_3 > class Triangulation_vertex_base_with_id_3 : public Vb { int _id; public: typedef typename Vb::Cell_handle Cell_handle; typedef typename Vb::Point Point; template < typename TDS3 > struct Rebind_TDS { typedef typename Vb::template Rebind_TDS::Other Vb3; typedef Triangulation_vertex_base_with_id_3 Other; }; Triangulation_vertex_base_with_id_3(): Vb() {} Triangulation_vertex_base_with_id_3(const Point & p): Vb(p) {} Triangulation_vertex_base_with_id_3(const Point & p, Cell_handle c): Vb(p, c) {} Triangulation_vertex_base_with_id_3(Cell_handle c): Vb(c) {} unsigned int id() const { return this->info(); } unsigned int& id() { return this->info(); } }; // The function returning vertices and their weights for an arbitrary point in R3 space. // The returned triplet contains: // - the output iterator pointing to the filled vector of vertices // - the sum of weights for normalisation // - a bool telling if the query point was located inside the convex hull of the data points template Triple< OutputIterator, // iterator with value type std::pair typename Dt::Geom_traits::FT, // Should provide 0 and 1 bool > getIncidentVtxWeights(const Dt& dt, const typename Dt::Geom_traits::Point_3& Q, OutputIterator nn_out, typename Dt::Geom_traits::FT & norm_coeff, std::vector& normals, typename Dt::Cell_handle& start = CGAL_TYPENAME_DEFAULT_ARG Dt::Cell_handle()) { //helpful array for permutations const int comb [6] = {1, 2, 3, 0, 1, 2}; typedef typename Dt::Geom_traits Gt; typedef typename Dt::Cell_handle Cell_handle; typedef typename Dt::Locate_type Locate_type; typedef typename Gt::FT Coord_type; CGAL_triangulation_precondition (dt.dimension()== 3); Locate_type lt; int li, lj; Cell_handle c = dt.locate( Q, lt, li, lj, start); bool updateNormals = (c!=start || normals.size()<4); if (updateNormals) normals.clear(); if ( lt == Dt::VERTEX ) { *nn_out++= std::make_pair(c->vertex(li),Coord_type(1)); return make_triple(nn_out,norm_coeff=Coord_type(1),true); } else if (dt.is_infinite(c)) return make_triple(nn_out, Coord_type(1), false);//point outside the convex-hull norm_coeff=0; for ( int k=0;k<4;k++ ) { if (updateNormals) { normals.push_back(cross_product(c->vertex(comb[k])->point()-c->vertex(comb[k+1])->point(),c->vertex(comb[k])->point()-c->vertex(comb[k+2])->point())); normals[k] = normals[k]/ ((c->vertex(k)->point()-c->vertex(comb[k])->point())*normals[k]); } Coord_type closeness = ((Q-c->vertex(comb[k])->point())*normals[k]); Coord_type w = closeness; *nn_out++= std::make_pair(c->vertex(k),w); norm_coeff += w; } start = c; return make_triple(nn_out,norm_coeff,true); } } //END NAMESPACE CGAL typedef CGAL::Exact_predicates_inexact_constructions_kernel K; typedef CGAL::Delaunay_triangulation_3::Geom_traits Traits; typedef CGAL::Triangulation_vertex_base_with_id_3 Vb; typedef CGAL::Triangulation_cell_base_3 Cb; typedef CGAL::Triangulation_data_structure_3 Tds; typedef CGAL::Delaunay_triangulation_3 DT; typedef std::vector< std::pair< DT::Vertex_handle, K::FT> > Vertex_weight_vector; template typename DataOwner::Data interpolate1 (const Dt& dt, const typename Dt::Geom_traits::Point_3& Q, DataOwner& owner, const std::vector& rawData, bool reset) { K::FT norm; Vertex_weight_vector coords; if (reset) owner.cell = dt.cells_begin(); CGAL::Triple,K::FT, bool> result = CGAL::getIncidentVtxWeights(dt, Q,std::back_inserter(coords), norm, owner.normals , owner.cell); typename DataOwner::Data data = typename DataOwner::Data();//initialize null solution if (!result.third) return data;// out of the convex hull, we return the null solution //else, we compute the weighted sum for (unsigned int k=0; kid()]*coords[k].second); if (!data.ending) return data*(1./result.second); else return typename DataOwner::Data(); } template typename DataOwner::Data interpolate2 (const Dt& dt, const typename Dt::Geom_traits::Point_3& Q, DataOwner& owner, const std::vector& rawData, bool reset) { K::FT norm; Vertex_weight_vector coords; if (reset) owner.cell = dt.cells_begin(); CGAL::Triple,K::FT, bool> result = CGAL::getIncidentVtxWeights(dt, Q,std::back_inserter(coords), norm, owner.normals , owner.cell); typename DataOwner::Data data = typename DataOwner::Data();//initialize null solution if (!result.third) return data;// out of the convex hull, we return the null solution //else, we compute the weighted sum for (unsigned int k=0; kid()]*coords[k].second); if (!data.ending) return data*(1./result.second); else return typename DataOwner::Data(); } trunk-2018.02b/pkg/dem/DemXDofGeom.hpp000066400000000000000000000020031324306050200173160ustar00rootroot00000000000000// 2009 © Václav Šmilauer #pragma once #include /*! Abstract class that unites ScGeom and L3Geom, created for the purposes of GlobalStiffnessTimeStepper. It might be removed in the future. */ class GenericSpheresContact: public IGeom{ YADE_CLASS_BASE_DOC_ATTRS_CTOR(GenericSpheresContact,IGeom, "Class uniting :yref:`ScGeom` and :yref:`L3Geom`, for the purposes of :yref:`GlobalStiffnessTimeStepper`. (It might be removed in the future). Do not use this class directly.", ((Vector3r,normal,,,"Unit vector oriented along the interaction, from particle #1, towards particle #2. |yupdate|")) ((Vector3r,contactPoint,,,"some reference point for the interaction (usually in the middle). |ycomp|")) ((Real,refR1,,,"Reference radius of particle #1. |ycomp|")) ((Real,refR2,,,"Reference radius of particle #2. |ycomp|")), createIndex(); ); REGISTER_CLASS_INDEX(GenericSpheresContact,IGeom); virtual ~GenericSpheresContact() {}; }; REGISTER_SERIALIZABLE(GenericSpheresContact); trunk-2018.02b/pkg/dem/Disp2DPropLoadEngine.cpp000066400000000000000000000173561324306050200211100ustar00rootroot00000000000000/************************************************************************* * Copyright (C) 2008 by Jerome Duriez * * jerome.duriez@hmg.inpg.fr * * * * This program is free software; it is licensed under the terms of the * * GNU General Public License v2 or later. See file LICENSE for details. * *************************************************************************/ #include "Disp2DPropLoadEngine.hpp" #include #include #include #include #include YADE_PLUGIN((Disp2DPropLoadEngine)); void Disp2DPropLoadEngine::postLoad(Disp2DPropLoadEngine&) { std::string outputFile="DirSearch" + Key + "Yade"; ofile.open(outputFile.c_str(), std::ios::app); if (!boost::filesystem::exists( outputFile.c_str() )) ofile<<"theta (!angle in plane (gamma,-du) ) dtau (kPa) dsigma (kPa) dgamma (m) du (m) tau0 (kPa) sigma0 (kPa) d2W coordSs0 coordTot0 coordSsF coordTotF (Yade)" << endl; } void Disp2DPropLoadEngine::action() { if(LOG) cerr << "debut applyCondi !!" << endl; leftbox = Body::byId(id_boxleft); rightbox = Body::byId(id_boxright); frontbox = Body::byId(id_boxfront); backbox = Body::byId(id_boxback); topbox = Body::byId(id_topbox); boxbas = Body::byId(id_boxbas); if(firstIt) { it_begin=scene->iter; H0=topbox->state->pos.y(); X0=topbox->state->pos.x(); Vector3r F_sup=scene->forces.getForce(id_topbox); Fn0=F_sup.y(); Ft0=F_sup.x(); Real OnlySsInt=0 // the half number of real sphere-sphere (only) interactions, at the beginning of the perturbation ,TotInt=0 // the half number of all the real interactions, at the beginning of the perturbation ; InteractionContainer::iterator ii = scene->interactions->begin(); InteractionContainer::iterator iiEnd = scene->interactions->end(); for( ; ii!=iiEnd ; ++ii ) { if ((*ii)->isReal()) { TotInt++; const shared_ptr& b1 = Body::byId( (*ii)->getId1() ); const shared_ptr& b2 = Body::byId( (*ii)->getId2() ); if ( (b1->isDynamic()) && (b2->isDynamic()) ) OnlySsInt++; } } coordSs0 = OnlySsInt/8590; // 8590 is the number of spheres in the CURRENT case coordTot0 = TotInt / 8596; // 8596 is the number of bodies in the CURRENT case firstIt=false; } if ( (scene->iter-it_begin) < nbre_iter) { letDisturb(); } else if ( (scene->iter-it_begin) == nbre_iter) { stopMovement(); string fileName=Key + "DR"+boost::lexical_cast (nbre_iter)+"ItAtV_"+boost::lexical_cast (v)+"done.xml"; // Omega::instance().saveSimulation ( fileName ); saveData(); } } void Disp2DPropLoadEngine::letDisturb() { const Real& dt = scene->dt; dgamma=cos(theta*Mathr::PI/180.0)*v*dt; dh=sin(theta*Mathr::PI/180.0)*v*dt; Real Ysup = topbox->state->pos.y(); Real Ylat = leftbox->state->pos.y(); // Changes in vertical and horizontal position : topbox->state->pos += Vector3r(dgamma,dh,0); leftbox->state->pos += Vector3r(dgamma/2.0,dh/2.0,0); rightbox->state->pos += Vector3r(dgamma/2.0,dh/2.0,0); Real Ysup_mod = topbox->state->pos.y(); Real Ylat_mod = leftbox->state->pos.y(); // with the corresponding velocities : topbox->state->vel=Vector3r(dgamma/dt,dh/dt,0); leftbox->state->vel = Vector3r((dgamma/dt)/2.0,dh/(2.0*dt),0); rightbox->state->vel = Vector3r((dgamma/dt)/2.0,dh/(2.0*dt),0); // Then computation of the angle of the rotation to be done : computeAlpha(); if (alpha == Mathr::PI/2.0) // Case of the very beginning { dalpha = - atan( dgamma / (Ysup_mod -Ylat_mod) ); } else { Real A = (Ysup_mod - Ylat_mod) * 2.0*tan(alpha) / (2.0*(Ysup - Ylat) + dgamma*tan(alpha) ); dalpha = atan( (A - tan(alpha))/(1.0 + A * tan(alpha))); } Quaternionr qcorr(AngleAxisr(dalpha,Vector3r::UnitZ())); if(LOG) cout << "Quaternion associe a la rotation incrementale : " << qcorr.w() << " " << qcorr.x() << " " << qcorr.y() << " " << qcorr.z() << endl; // On applique la rotation en changeant l'orientation des plaques, leurs vang et en affectant donc alpha leftbox->state->ori = qcorr*leftbox->state->ori; leftbox->state->angVel = Vector3r(0,0,1)*dalpha/dt; rightbox->state->ori = qcorr*leftbox->state->ori; rightbox->state->angVel = Vector3r(0,0,1)*dalpha/dt; } void Disp2DPropLoadEngine::computeAlpha() { Quaternionr orientationLeftBox,orientationRightBox; orientationLeftBox = leftbox->state->ori; orientationRightBox = rightbox->state->ori; if(orientationLeftBox.matrix()!=orientationRightBox.matrix()) { cout << "WARNING !!! your lateral boxes have not the same orientation, you're not in the case of a box imagined for creating these engines" << endl; } AngleAxisr aa(orientationLeftBox); alpha=Mathr::PI/2.0-aa.angle(); // right if the initial orientation of the body (on the beginning of the simulation) is q =(1,0,0,0) = FromAxisAngle((0,0,1),0) } void Disp2DPropLoadEngine::stopMovement() { // annulation de la vitesse de la plaque du haut topbox->state->vel = Vector3r(0,0,0); // de la plaque gauche leftbox->state->vel = Vector3r(0,0,0); leftbox->state->angVel = Vector3r(0,0,0); // de la plaque droite rightbox->state->vel = Vector3r(0,0,0); rightbox->state->angVel = Vector3r(0,0,0); } void Disp2DPropLoadEngine::saveData() { Real Xleft = leftbox->state->pos.x() + (YADE_CAST(leftbox->shape.get()))->extents.x(); Real Xright = rightbox->state->pos.x() - (YADE_CAST(rightbox->shape.get()))->extents.x(); Real Zfront = frontbox->state->pos.z() - YADE_CAST(frontbox->shape.get())->extents.z(); Real Zback = backbox->state->pos.z() + (YADE_CAST(backbox->shape.get()))->extents.z(); Real Scontact = (Xright-Xleft)*(Zfront-Zback); // that's so the value of section at the middle of the height of the box InteractionContainer::iterator ii = scene->interactions->begin(); InteractionContainer::iterator iiEnd = scene->interactions->end(); Real OnlySsInt=0 // the half number of real sphere-sphere (only) interactions, at the end of the perturbation ,TotInt=0 // the half number of all the real interactions, at the end of the perturbation ; for( ; ii!=iiEnd ; ++ii ) { if ((*ii)->isReal()) { TotInt++; const shared_ptr& b1 = Body::byId( (*ii)->getId1() ); const shared_ptr& b2 = Body::byId( (*ii)->getId2() ); if ( (b1->isDynamic()) && (b2->isDynamic()) ) OnlySsInt++; } } Real coordSs = OnlySsInt/8590, // 8590 is the number of spheres in the CURRENT case coordTot = TotInt / 8596; // 8596 is the number of bodies in the CURRENT case Vector3r F_sup = scene->forces.getForce(id_topbox); Real dFn=F_sup.y()-Fn0 // OK pour le signe ,dFt=(F_sup.x()-Ft0) ,du=-( topbox->state->pos.y() - H0 ) // OK pour le signe (>0 en compression) ,dgamma=topbox->state->pos.x()-X0 ,SigN0 = (Fn0/Scontact)/1000 // en kPa, pour comparer à Fortran ,Tau0 = -(Ft0/Scontact)/1000 // en kPa, pour comparer à Fortran, Ok pour le signe, cf p. Yade29 ,dSigN= (dFn/Scontact)/1000 // Ok pour le signe ,dTau = -(dFt/Scontact)/1000 // Ok pour le signe, idem que Tau0 ,d2W = dSigN * du + dTau * dgamma ; ofile << boost::lexical_cast (theta) << " "<< boost::lexical_cast (dTau) << " " << boost::lexical_cast (dSigN) << " " << boost::lexical_cast (dgamma)<<" " << boost::lexical_cast (du) << " " << boost::lexical_cast (Tau0) << " " << boost::lexical_cast (SigN0) << " " << boost::lexical_cast (d2W) << " " << boost::lexical_cast (coordSs0) << " " << boost::lexical_cast (coordTot0) << " " << boost::lexical_cast (coordSs) << " " << boost::lexical_cast (coordTot) < #include #include class Disp2DPropLoadEngine : public BoundaryController { private : Real dgamma // the increment of horizontal displacement in one timestep, part of the disturbation ,dh // the increment of vertical displacement in one timestep, part of the disturbation ,H0 // the height of the top box, at the beginnig of the application of the disturbation ,X0 // the X-position of the top box, at the beginnig of the application of the disturbation ,Fn0,Ft0// the normal and tangential force acting on the top box, at... ,coordSs0,coordTot0 ; std::ofstream ofile; Real alpha // angle from the lower plate to the left box (trigo wise), as in other shear Engines, but here the Engine is able to find itself its value ! ,dalpha // the increment over alpha ; bool firstIt;// true if this is the first iteration, false else. int it_begin// the number of the it at which the computation starts ; shared_ptr leftbox; shared_ptr rightbox; shared_ptr frontbox; shared_ptr backbox; shared_ptr topbox; shared_ptr boxbas; void saveData(); void letDisturb(); void stopMovement(); // to cancel all the velocities public : void action() ,computeAlpha() ; void postLoad(Disp2DPropLoadEngine&); YADE_CLASS_BASE_DOC_ATTRS_CTOR(Disp2DPropLoadEngine,BoundaryController, "Disturbs a simple shear sample in a given displacement direction\n\nThis engine allows one to apply, on a simple shear sample, a loading controlled by du/dgamma = cste, which is equivalent to du + cste' * dgamma = 0 (proportionnal path loadings).\nTo do so, the upper plate of the simple shear box is moved in a given direction (corresponding to a given du/dgamma), whereas lateral plates are moved so that the box remains closed.\nThis engine can easily be used to perform directionnal probes, with a python script launching successivly the same .xml which contains this engine, after having modified the direction of loading (see *theta* attribute). That's why this Engine contains a *saveData* procedure which can save data on the state of the sample at the end of the loading (in case of successive loadings - for successive directions - through a python script, each line would correspond to one direction of loading).", ((Body::id_t,id_topbox,3,,"the id of the upper wall")) ((Body::id_t,id_boxbas,1,,"the id of the lower wall")) ((Body::id_t,id_boxleft,0,,"the id of the left wall")) ((Body::id_t,id_boxright,2,,"the id of the right wall")) ((Body::id_t,id_boxfront,5,,"the id of the wall in front of the sample")) ((Body::id_t,id_boxback,4,,"the id of the wall at the back of the sample")) ((Real,theta,0.0,,"the angle, in a (gamma,h=-u) plane from the gamma - axis to the perturbation vector (trigo wise) [degrees]")) ((Real,v,0.0,,"the speed at which the perturbation is imposed. In case of samples which are more sensitive to normal loadings than tangential ones, one possibility is to take v = V_shear - | (V_shear-V_comp)*sin(theta) | => v=V_shear in shear; V_comp in compression [m/s]")) ((int,nbre_iter,0,,"the number of iterations of loading to perform")) ((string,Key,"",,"string to add at the names of the saved files, and of the output file filled by *saveData*")) ((bool,LOG,false,,"boolean controling the output of messages on the screen")), firstIt=true; alpha=Mathr::PI/2.0; ) }; REGISTER_SERIALIZABLE(Disp2DPropLoadEngine); trunk-2018.02b/pkg/dem/DomainLimiter.cpp000066400000000000000000000354721324306050200177700ustar00rootroot00000000000000#include #include #include YADE_PLUGIN((DomainLimiter)(LawTester) #ifdef YADE_OPENGL (GlExtra_LawTester)(GlExtra_OctreeCubes) #endif ); void DomainLimiter::action(){ std::list out; FOREACH(const shared_ptr& b, *scene->bodies){ if((!b) or ((mask>0) and ((b->groupMask & mask)==0))) continue; const Sphere* sphere = dynamic_cast(b->shape.get()); if (sphere){ //Delete only spheres const Vector3r& p(b->state->pos); if(p[0]hi[0] || p[1]hi[1] || p[2]hi[2]) { out.push_back(b->id); nDeleted++; mDeleted+=b->state->mass; Real r = sphere->radius; vDeleted+=(4/3.)*Mathr::PI*pow(r,3); } } } FOREACH(Body::id_t id, out){ scene->bodies->erase(id,false); } } #include #include #include #include #include #include CREATE_LOGGER(LawTester); void LawTester::postLoad(LawTester&){ if(ids.size()==0) return; // uninitialized object, don't do nothing at all if(ids.size()!=2) throw std::invalid_argument("LawTester.ids: exactly two values must be given."); if(disPath.empty() && rotPath.empty()) throw invalid_argument("LawTester.{disPath,rotPath}: at least one point must be given."); if(pathSteps.empty()) throw invalid_argument("LawTester.pathSteps: at least one value must be given."); size_t pathSize=max(disPath.size(),rotPath.size()); // update path points _path.clear(); _path.push_back(Vector6r::Zero()); for(size_t i=0; i()=Vector3r(i()=Vector3r(i Inew=scene->interactions->find(ids[0],ids[1]); string strIds("##"+boost::lexical_cast(ids[0])+"+"+boost::lexical_cast(ids[1])); // interaction not found at initialization if(!I && (!Inew || !Inew->isReal())){ LOG_WARN("Interaction "<isReal())) throw std::runtime_error("LawTester: interaction "+strIds+" was deleted"+(Inew?" (is not real anymore).":".")); // different interaction object if(I && Inew && I!=Inew) throw std::logic_error("LawTester: interacion "+strIds+" is a different object now?!"); assert(Inew); bool doInit=(!I); if(doInit) I=Inew; id1=I->getId1(); id2=I->getId2(); // test object types GenericSpheresContact* gsc=dynamic_cast(I->geom.get()); ScGeom* scGeom=dynamic_cast(I->geom.get()); L3Geom* l3Geom=dynamic_cast(I->geom.get()); L6Geom* l6Geom=dynamic_cast(I->geom.get()); ScGeom6D* scGeom6d=dynamic_cast(I->geom.get()); bool hasRot=(l6Geom || scGeom6d); //NormShearPhys* phys=dynamic_cast(I->phys.get()); //Disabled because of warning if(!gsc) throw std::invalid_argument("LawTester: IGeom of "+strIds+" not a GenericSpheresContact."); if(!scGeom && !l3Geom) throw std::invalid_argument("LawTester: IGeom of "+strIds+" is neither ScGeom, nor L3Geom (or L6Geom)."); assert(!((bool)scGeom && (bool)l3Geom)); // nonsense // get body objects State *state1=Body::byId(id1,scene)->state.get(), *state2=Body::byId(id2,scene)->state.get(); scene->forces.sync(); if(state1->blockedDOFs!=State::DOF_ALL) { LOG_INFO("Blocking all DOFs for #"<blockedDOFs=State::DOF_ALL;} if(state2->blockedDOFs!=State::DOF_ALL) { LOG_INFO("Blocking all DOFs for #"<blockedDOFs=State::DOF_ALL;} if(step-1>*(_pathT.rbegin())){ LOG_INFO("Last step done, setting zero velocities on #"<vel=state1->angVel=state2->vel=state2->angVel=Vector3r::Zero(); uTest=uTestNext; if(doneHook.empty()){ LOG_INFO("No doneHook set, dying."); dead=true; } else{ LOG_INFO("Running doneHook: "<()=Vector3r(NaN,NaN,NaN); if(!l3Geom){ // IGeom's that don't have local axes axX=gsc->normal; /* just in case */ axX.normalize(); if(doInit){ // initialization of the new interaction -- define local axes // take vector in the y or z direction, depending on its length; arbitrary, but one of them is sure to be non-zero axY=axX.cross(std::abs(axX[1])trsf); axX=trsf.row(0); axY=trsf.row(1); axZ=trsf.row(2); uGeom.head<3>()=l3Geom->u; if(l6Geom) uGeom.tail<3>()=l6Geom->phi; } // perform all shearing by translation, as it does not induce bending if(hasRot && rotWeight!=0){ LOG_INFO("LawTester.rotWeight set to 0 (was "<contactPoint; refLength=gsc->refR1+gsc->refR2; renderLength=.5*refLength; // here we go ahead, finally Vector6r uu=linearInterpolate(step,_pathT,_path,_interpPos); Vector6r dUU=uu-uuPrev; uuPrev=uu; Vector3r dU(dUU.head<3>()), dPhi(dUU.tail<3>()); //Vector3r dU=u-uPrev.head<3>(); uPrev.head<3>()=u; //Vector3r dPhi=phi-uPrev.tail<3>(); uPrev.tail<3>()=phi; if(displIsRel){ LOG_DEBUG("Relative displacement diff is "<refR1+gsc->refR2<<")"); dU*=refLength; } LOG_DEBUG("Absolute diff is: displacement "<()+=dU; uTestNext.tail<3>()+=dPhi; // reset velocities where displacement is controlled //for(int i=0; i<3; i++){ if(forceControl[i]==0){ state1.vel[i]=0; state2.vel[i]=0; } // shear is applied as rotation of id2: dε=r₁dθ → dθ=dε/r₁; Vector3r vel[2],angVel[2]; //State* states[]={state1,state2}; for(int i=0; i<2; i++){ int sign=(i==0?-1:1); Real weight=(i==0?1-idWeight:idWeight); // FIXME: this should not use refR1, but real CP-particle distance perhaps? Real radius=(i==0?gsc->refR1:gsc->refR2); Real relRad=radius/refLength; // signed and weighted displacement/rotation to be applied on this sphere (reversed for #0) // some rotations must cancel the sign, by multiplying by sign again Vector3r ddU=sign*dU*weight; // twist can be still distributed with idWeight (!) Vector3r ddPhi=sign*dPhi*(1-relRad); /* shear angles must distribute to both, otherwise it would induce shear */ ddPhi[0]=sign*dPhi[0]*weight; // twist can be still distributed with idWeight vel[i]=angVel[i]=Vector3r::Zero(); // normal displacement vel[i]+=axX*ddU[0]/scene->dt; // shear rotation // multiplication by sign cancels sign in ddU, since rotation is non-symmetric (to increase shear, both spheres have the same rotation) // (unlike shear displacement, which is symmetric) // rotation around Z (which gives y-shear) must be inverted: +ry gives +εzm while -rz gives +εy Real rotZ=-sign*rotWeight*ddU[1]/radius, rotY=sign*rotWeight*ddU[2]/radius; angVel[i]+=(rotY*axY+rotZ*axZ)/scene->dt; // shear displacement // angle that is traversed by a sphere in order to give desired ddU when displaced on the branch of r1+r2 // FIXME: is the branch value correct here?! Real arcAngleY=atan((1-rotWeight)*ddU[1]/radius), arcAngleZ=atan((1-rotWeight)*ddU[2]/radius); // same, but without the atan, which can be disregarded for small increments: // Real arcAngleY=(1-rotWeight)*ddU[1]/radius, arcAngleZ=(1-rotWeight)*ddU[2]/radius; vel[i]+=axY*radius*sin(arcAngleY)/scene->dt; vel[i]+=axZ*radius*sin(arcAngleZ)/scene->dt; // compensate distance increase caused by motion along the perpendicular axis // cos(argAngle*) is always positive, regardless of the orientation // and the compensation is always in the -εx sense (-sign → +1 for #0, -1 for #1) vel[i]+=-sign*axX*radius*((1-cos(arcAngleY))+(1-cos(arcAngleZ)))/scene->dt; // rotation, convert from local to global angVel[i]+=trsf.transpose()*ddPhi; LOG_DEBUG("vel="<renderLength<=0) return; glColor3v(Vector3r(1,0,1)); // switch to local coordinates glTranslatev(tester->contPt); //glMultMatrixd(Eigen::Affine3d(tester->trsf.transpose()).data()); glMultMatrix(Eigen::Transform(tester->trsf.transpose()).data()); glDisable(GL_LIGHTING); //glColor3v(Vector3r(1,0,1)); //glBegin(GL_LINES); glVertex3v(Vector3r::Zero()); glVertex3v(.1*Vector3r::Ones()); glEnd(); //GLUtils::GLDrawText(string("This is the contact point!"),Vector3r::Zero(),Vector3r(1,0,1)); // local axes glLineWidth(2.); for(int i=0; i<3; i++){ Vector3r pt=Vector3r::Zero(); pt[i]=.5*tester->renderLength; Vector3r color=.3*Vector3r::Ones(); color[i]=1; GLUtils::GLDrawLine(Vector3r::Zero(),pt,color); GLUtils::GLDrawText(string(i==0?"x":(i==1?"y":"z")),pt,color); } // put the origin to the initial (no-shear) point, so that the current point appears at the contact point glTranslatev(Vector3r(0,tester->uTestNext[1],tester->uTestNext[2])); const int t(tester->step); const vector& TT(tester->_pathT); const vector& VV(tester->_path); size_t numSegments=TT.size(); const Vector3r colorBefore=Vector3r(.7,1,.7), colorAfter=Vector3r(1,.7,.7); // scale displacement, if they have the strain meaning Real scale=1; if(tester->displIsRel) scale=tester->refLength; // find maximum displacement, draw axes in the shear plane Real displMax=0; FOREACH(const Vector6r& v, VV) displMax=max(v.head<3>().squaredNorm(),displMax); displMax=1.2*scale*sqrt(displMax); glLineWidth(1.); GLUtils::GLDrawLine(Vector3r(0,-displMax,0),Vector3r(0,displMax,0),Vector3r(.5,0,0)); GLUtils::GLDrawLine(Vector3r(0,0,-displMax),Vector3r(0,0,displMax),Vector3r(.5,0,0)); // draw displacement path glLineWidth(4.); for(size_t segment=0; segment()*scale, &to=-VV[segment+1].head<3>()*scale; // current segment if(t>t0 && t>data[i]; } OctreeBox ob; Vector3r mn(data[0],data[1],data[2]), mx(data[3],data[4],data[5]); ob.center=.5*(mn+mx); ob.extents=(.5*(mx-mn)); ob.level=(int)data[6]; ob.fill=(int)data[7]; // for(int i=0; i<=ob.level; i++) cerr<<"\t"; cerr<fillRangeDraw[1]) continue; if(ob.levellevelRangeDraw[1]) continue; bool doFill=(ob.fill>=fillRangeFill[0] && ob.fill<=fillRangeFill[1] && (ob.fill!=0 || !noFillZero)); // -2: empty // -1: recursion limit, empty // 0: subdivided // 1: recursion limit, full // 2: full Vector3r color=(ob.fill==-2?Vector3r(1,0,0):(ob.fill==-1?Vector3r(1,1,0):(ob.fill==0?Vector3r(0,0,1):(ob.fill==1)?Vector3r(0,1,0):(ob.fill==2)?Vector3r(0,1,1):Vector3r(1,1,1)))); glColor3v(color); glPushMatrix(); glTranslatev(ob.center); glScalef(2*ob.extents[0],2*ob.extents[1],2*ob.extents[2]); if (doFill) glutSolidCube(1); else glutWireCube(1); glPopMatrix(); } } #endif /* YADE_OPENGL */ trunk-2018.02b/pkg/dem/DomainLimiter.hpp000066400000000000000000000206641324306050200177720ustar00rootroot00000000000000 #include #include #include class DomainLimiter: public PeriodicEngine{ public: virtual void action(); YADE_CLASS_BASE_DOC_ATTRS(DomainLimiter,PeriodicEngine,"Delete particles that are out of axis-aligned box given by *lo* and *hi*.", ((Vector3r,lo,Vector3r(0,0,0),,"Lower corner of the domain.")) ((Vector3r,hi,Vector3r(0,0,0),,"Upper corner of the domain.")) ((long,nDeleted,0,Attr::readonly,"Cummulative number of particles deleted.")) ((Real,mDeleted,0,,"Mass of deleted particles.")) ((Real,vDeleted,0,,"Volume of deleted particles.")) ((int,mask,-1,,"If mask is defined, only particles with corresponding groupMask will be deleted.")) ); }; REGISTER_SERIALIZABLE(DomainLimiter); class LawTester: public PartialEngine{ Body::id_t id1,id2; // shorthands for local use public: void init(); virtual void action(); void postLoad(LawTester&); void warnDeprec(const string& s1, const string& s2){ if(!warnedDeprecPtRot){ warnedDeprecPtRot=true; LOG_WARN("LawTester."<(); } Vector3r get_ptGeom(){ warnDeprec("ptGeom","uGeom.head()"); return uGeom.head<3>(); } Vector3r get_rotOurs(){ warnDeprec("rotOurs","uTest.tail()"); return uTest.tail<3>(); } Vector3r get_rotGeom(){ warnDeprec("rotGeom","uGeom.tail()"); return uGeom.tail<3>(); } DECLARE_LOGGER; YADE_CLASS_BASE_DOC_ATTRS_INIT_CTOR_PY(LawTester,PartialEngine,"Prescribe and apply deformations of an interaction in terms of local normal and shear displacements and rotations (using either :yref:`disPpath` and :yref:`rotPath` [or :yref:`path` in the future]). Supported :yref:`IGeom` types are :yref:`ScGeom`, :yref:`L3Geom` and :yref:`L6Geom`. \n\nSee :ysrc:`scripts/test/law-test.py` for an example.", ((vector,disPath,,Attr::triggerPostLoad,"Loading path, where each Vector3 contains desired normal displacement and two components of the shear displacement (in local coordinate system, which is being tracked automatically. If shorter than :yref:`rotPath`, the last value is repeated.")) ((vector,rotPath,,Attr::triggerPostLoad,"Rotational components of the loading path, where each item contains torsion and two bending rotations in local coordinates. If shorter than :yref:`path`, the last value is repeated.")) ((vector,hooks,,,"Python commands to be run when the corresponding point in path is reached, before doing other things in that particular step. See also :yref:`doneHook`. ")) ((Vector6r,uGeom,Vector6r::Zero(),,"Current generalized displacements (3 displacements, 3 rotations), as stored in the interation itself. They should corredpond to :yref:`uTest`, otherwise a bug is indicated.")) ((Vector6r,uTest,Vector6r::Zero(),,"Current generalized displacements (3 displacements, 3 rotations), as they should be according to this :yref:`LawTester`. Should correspond to :yref:`uGeom`.")) ((Vector6r,uTestNext,Vector6r::Zero(),Attr::hidden,"The value of uTest in the next step; since uTest is computed before uGeom is updated (in the next time step), uTest and uGeom would be always shifted by one timestep.")) ((bool,warnedDeprecPtRot,false,Attr::hidden,"Flag to say that the user was already warned about using deprecated ptOurg/ptGeom/rotOurs/rotGeom.")) ((Vector3r,shearTot,Vector3r::Zero(),Attr::hidden,"Current displacement in global coordinates; only used internally with ScGeom.")) ((bool,displIsRel,true,,"Whether displacement values in *disPath* are normalized by reference contact length (r1+r2 for 2 spheres).")) //((Vector3i,forceControl,Vector3i::Zero(),,"Select which components of path (non-zero value) have force (stress) rather than displacement (strain) meaning.")) ((vector,pathSteps,((void)"(constant step)",vector(1,1)),Attr::triggerPostLoad,"Step number for corresponding values in :yref:`path`; if shorter than path, distance between last 2 values is used for the rest.")) ((vector,_pathT,,(Attr::readonly|Attr::noSave),"Time value corresponding to points on path, computed from *pathSteps*. Length is always the same as path.")) ((vector,_path,,(Attr::readonly|Attr::noSave),"Generalized displacement path values, computed from *disPath* and *rotPath* by appending zero initial value, and possibly repeating the last value to make lengths of *disPath* and *rotPath* match.")) ((shared_ptr,I,,(Attr::hidden),"Interaction object being tracked.")) // axX, axY, axZ could be replaced by references to rows in trsf; perhaps do that in the future (in such a case, trsf would not be noSave) ((Vector3r,axX,,Attr::hidden,"Local x-axis in global coordinates (normal of the contact) |yupdate|")) ((Vector3r,axY,,Attr::hidden,"Local y-axis in global coordinates; perpendicular to axX; initialized arbitrarily, but tracked to be consistent. |yupdate|")) ((Vector3r,axZ,,(Attr::hidden|Attr::noSave),"Local z-axis in global coordinates; computed from axX and axY. |yupdate|")) ((Matrix3r,trsf,,(Attr::noSave|Attr::readonly),"Transformation matrix for the local coordinate system. |yupdate|")) ((size_t,_interpPos,0,(Attr::readonly|Attr::hidden),"State cookie for the interpolation routine.")) ((Vector6r,uuPrev,Vector6r::Zero(),(Attr::readonly),"Generalized displacement values reached in the previous step, for knowing which increment to apply in the current step.")) ((int,step,1,,"Step number in which this engine is active; determines position in path, using pathSteps.")) ((string,doneHook,,,"Python command (as string) to run when end of the path is achieved. If empty, the engine will be set :yref:`dead`.")) ((Real,renderLength,0,,"Characteristic length for the purposes of rendering, set equal to the smaller radius.")) ((Real,refLength,0,(Attr::readonly),"Reference contact length, for rendering only.")) ((Vector3r,contPt,Vector3r::Zero(),Attr::hidden,"Contact point (for rendering only)")) ((Real,idWeight,1,,"Float, usually ∈〈0,1〉, determining on how are displacements distributed between particles (0 for id1, 1 for id2); intermediate values will apply respective part to each of them. This parameter is ignored with 6-DoFs :yref:`IGeom`.")) ((Real,rotWeight,1,,"Float ∈〈0,1〉 determining whether shear displacement is applied as rotation or displacement on arc (0 is displacement-only, 1 is rotation-only). Not effective when mutual rotation is specified.")) // reset force components along individual axes, instead of blocking DOFs which have no specific direction (for the force control) , /* init */ , /* ctor */ , /* py */ .add_property("ptOurs",&LawTester::get_ptOurs,"first 3 components of uTest |ydeprecated|") .add_property("ptGeom",&LawTester::get_ptGeom,"first 3 components of uGeom |ydeprecated|") .add_property("rotOurs",&LawTester::get_rotOurs,"last 3 components of uTest |ydeprecated|") .add_property("rotGeom",&LawTester::get_rotGeom,"last 3 components of uGeom |ydeprecated|") ); }; REGISTER_SERIALIZABLE(LawTester); #ifdef YADE_OPENGL #include class GlExtra_LawTester: public GlExtraDrawer{ public: DECLARE_LOGGER; virtual void render(); YADE_CLASS_BASE_DOC_ATTRS(GlExtra_LawTester,GlExtraDrawer,"Find an instance of :yref:`LawTester` and show visually its data.", ((shared_ptr,tester,,,"Associated :yref:`LawTester` object.")) ); }; REGISTER_SERIALIZABLE(GlExtra_LawTester); class GlExtra_OctreeCubes: public GlExtraDrawer{ public: struct OctreeBox{ Vector3r center, extents; int fill; int level; }; std::vector boxes; void postLoad(GlExtra_OctreeCubes&); virtual void render(); YADE_CLASS_BASE_DOC_ATTRS(GlExtra_OctreeCubes,GlExtraDrawer,"Render boxed read from file", ((string,boxesFile,,Attr::triggerPostLoad,"File to read boxes from; ascii files with ``x0 y0 z0 x1 y1 z1 c`` records, where ``c`` is an integer specifying fill (0 for wire, 1 for filled).")) ((Vector2i,fillRangeFill,Vector2i(2,2),,"Range of fill indices that will be filled.")) ((Vector2i,fillRangeDraw,Vector2i(-2,2),,"Range of fill indices that will be rendered.")) ((Vector2i,levelRangeDraw,Vector2i(-2,2),,"Range of levels that will be rendered.")) ((bool,noFillZero,true,,"Do not fill 0-fill boxed (those that are further subdivided)")) ); }; REGISTER_SERIALIZABLE(GlExtra_OctreeCubes); #endif trunk-2018.02b/pkg/dem/ElasticContactLaw.cpp000066400000000000000000000122271324306050200205700ustar00rootroot00000000000000/************************************************************************* * Copyright (C) 2005 by Bruno Chareyre bruno.chareyre@hmg.inpg.fr * * * * This program is free software; it is licensed under the terms of the * * GNU General Public License v2 or later. See file LICENSE for details. * *************************************************************************/ #include"ElasticContactLaw.hpp" #include #include #include #include #include YADE_PLUGIN((Law2_ScGeom_FrictPhys_CundallStrack)(Law2_ScGeom_ViscoFrictPhys_CundallStrack)(ElasticContactLaw)); #if 1 Real Law2_ScGeom_FrictPhys_CundallStrack::getPlasticDissipation() {return (Real) plasticDissipation;} void Law2_ScGeom_FrictPhys_CundallStrack::initPlasticDissipation(Real initVal) {plasticDissipation.reset(); plasticDissipation+=initVal;} Real Law2_ScGeom_FrictPhys_CundallStrack::elasticEnergy() { Real energy=0; FOREACH(const shared_ptr& I, *scene->interactions){ if(!I->isReal()) continue; FrictPhys* phys = dynamic_cast(I->phys.get()); if(phys) { energy += 0.5*(phys->normalForce.squaredNorm()/phys->kn + phys->shearForce.squaredNorm()/phys->ks);} } return energy; } #endif void ElasticContactLaw::action() { if(!functor) functor=shared_ptr(new Law2_ScGeom_FrictPhys_CundallStrack); functor->neverErase=neverErase; functor->scene=scene; FOREACH(const shared_ptr& I, *scene->interactions){ if(!I->isReal()) continue; #ifdef YADE_DEBUG // these checks would be redundant in the functor (LawDispatcher does that already) if(!dynamic_cast(I->geom.get()) || !dynamic_cast(I->phys.get())) continue; #endif functor->go(I->geom, I->phys, I.get()); } } CREATE_LOGGER(Law2_ScGeom_FrictPhys_CundallStrack); bool Law2_ScGeom_FrictPhys_CundallStrack::go(shared_ptr& ig, shared_ptr& ip, Interaction* contact){ int id1 = contact->getId1(), id2 = contact->getId2(); ScGeom* geom= static_cast(ig.get()); FrictPhys* phys = static_cast(ip.get()); if(geom->penetrationDepth <0){ if (neverErase) { phys->shearForce = Vector3r::Zero(); phys->normalForce = Vector3r::Zero();} else return false; } Real& un=geom->penetrationDepth; phys->normalForce=phys->kn*std::max(un,(Real) 0)*geom->normal; Vector3r& shearForce = geom->rotate(phys->shearForce); const Vector3r& shearDisp = geom->shearIncrement(); shearForce -= phys->ks*shearDisp; Real maxFs = phys->normalForce.squaredNorm()*std::pow(phys->tangensOfFrictionAngle,2); if (!scene->trackEnergy && !traceEnergy){//Update force but don't compute energy terms (see below)) // PFC3d SlipModel, is using friction angle. CoulombCriterion if( shearForce.squaredNorm() > maxFs ){ Real ratio = sqrt(maxFs) / shearForce.norm(); shearForce *= ratio;} } else { //almost the same with additional Vector3r instatinated for energy tracing, //duplicated block to make sure there is no cost for the instanciation of the vector when traceEnergy==false if(shearForce.squaredNorm() > maxFs){ Real ratio = sqrt(maxFs) / shearForce.norm(); Vector3r trialForce=shearForce;//store prev force for definition of plastic slip //define the plastic work input and increment the total plastic energy dissipated shearForce *= ratio; Real dissip=((1/phys->ks)*(trialForce-shearForce))/*plastic disp*/ .dot(shearForce)/*active force*/; if (traceEnergy) plasticDissipation += dissip; else if(dissip>0) scene->energy->add(dissip,"plastDissip",plastDissipIx,/*reset*/false); } // compute elastic energy as well scene->energy->add(0.5*(phys->normalForce.squaredNorm()/phys->kn+phys->shearForce.squaredNorm()/phys->ks),"elastPotential",elastPotentialIx,/*reset at every timestep*/true); } if (!scene->isPeriodic && !sphericalBodies) { State* de1 = Body::byId(id1,scene)->state.get(); State* de2 = Body::byId(id2,scene)->state.get(); applyForceAtContactPoint(-phys->normalForce-shearForce, geom->contactPoint, id1, de1->se3.position, id2, de2->se3.position);} else {//we need to use correct branches in the periodic case, the following apply for spheres only Vector3r force = -phys->normalForce-shearForce; scene->forces.addForce(id1,force); scene->forces.addForce(id2,-force); scene->forces.addTorque(id1,(geom->radius1-0.5*geom->penetrationDepth)* geom->normal.cross(force)); scene->forces.addTorque(id2,(geom->radius2-0.5*geom->penetrationDepth)* geom->normal.cross(force)); } return true; } bool Law2_ScGeom_ViscoFrictPhys_CundallStrack::go(shared_ptr& ig, shared_ptr& ip, Interaction* contact){ ScGeom* geom= static_cast(ig.get()); ViscoFrictPhys* phys = static_cast(ip.get()); if (shearCreep) { const Real& dt=scene->dt; geom->rotate(phys->creepedShear); phys->creepedShear+= creepStiffness*phys->ks*(phys->shearForce-phys->creepedShear)*dt/viscosity; phys->shearForce -= phys->ks*((phys->shearForce-phys->creepedShear)*dt/viscosity);} return Law2_ScGeom_FrictPhys_CundallStrack::go(ig,ip,contact); } trunk-2018.02b/pkg/dem/ElasticContactLaw.hpp000066400000000000000000000111231324306050200205670ustar00rootroot00000000000000/************************************************************************* * Copyright (C) 2005 by Bruno Chareyre bruno.chareyre@hmg.inpg.fr * * * * This program is free software; it is licensed under the terms of the * * GNU General Public License v2 or later. See file LICENSE for details. * *************************************************************************/ #pragma once #include #include #include #include #include class Law2_ScGeom_FrictPhys_CundallStrack: public LawFunctor{ public: OpenMPAccumulator plasticDissipation; virtual bool go(shared_ptr& _geom, shared_ptr& _phys, Interaction* I); Real elasticEnergy (); Real getPlasticDissipation(); void initPlasticDissipation(Real initVal=0); YADE_CLASS_BASE_DOC_ATTRS_CTOR_PY(Law2_ScGeom_FrictPhys_CundallStrack,LawFunctor,"Law for linear compression, and Mohr-Coulomb plasticity surface without cohesion.\nThis law implements the classical linear elastic-plastic law from [CundallStrack1979]_ (see also [Pfc3dManual30]_). The normal force is (with the convention of positive tensile forces) $F_n=\\min(k_n u_n, 0)$. The shear force is $F_s=k_s u_s$, the plasticity condition defines the maximum value of the shear force : $F_s^{\\max}=F_n\\tan(\\phi)$, with $\\phi$ the friction angle.\n\nThis law is well tested in the context of triaxial simulation, and has been used for a number of published results (see e.g. [Scholtes2009b]_ and other papers from the same authors). It is generalised by :yref:`Law2_ScGeom6D_CohFrictPhys_CohesionMoment`, which adds cohesion and moments at contact.", ((bool,neverErase,false,,"Keep interactions even if particles go away from each other (only in case another constitutive law is in the scene, e.g. :yref:`Law2_ScGeom_CapillaryPhys_Capillarity`)")) ((bool,sphericalBodies,true,,"If true, compute branch vectors from radii (faster), else use contactPoint-position. Turning this flag true is safe for sphere-sphere contacts and a few other specific cases. It will give wrong values of torques on facets or boxes.")) ((bool,traceEnergy,false,,"Define the total energy dissipated in plastic slips at all contacts. This will trace only plastic energy in this law, see O.trackEnergy for a more complete energies tracing")) ((int,plastDissipIx,-1,(Attr::hidden|Attr::noSave),"Index for plastic dissipation (with O.trackEnergy)")) ((int,elastPotentialIx,-1,(Attr::hidden|Attr::noSave),"Index for elastic potential energy (with O.trackEnergy)")) ,, .def("elasticEnergy",&Law2_ScGeom_FrictPhys_CundallStrack::elasticEnergy,"Compute and return the total elastic energy in all \"FrictPhys\" contacts") .def("plasticDissipation",&Law2_ScGeom_FrictPhys_CundallStrack::getPlasticDissipation,"Total energy dissipated in plastic slips at all FrictPhys contacts. Computed only if :yref:`Law2_ScGeom_FrictPhys_CundallStrack::traceEnergy` is true.") .def("initPlasticDissipation",&Law2_ScGeom_FrictPhys_CundallStrack::initPlasticDissipation,"Initialize cummulated plastic dissipation to a value (0 by default).") ); FUNCTOR2D(ScGeom,FrictPhys); DECLARE_LOGGER; }; REGISTER_SERIALIZABLE(Law2_ScGeom_FrictPhys_CundallStrack); class Law2_ScGeom_ViscoFrictPhys_CundallStrack: public Law2_ScGeom_FrictPhys_CundallStrack{ public: virtual bool go(shared_ptr& _geom, shared_ptr& _phys, Interaction* I); YADE_CLASS_BASE_DOC_ATTRS_CTOR_PY(Law2_ScGeom_ViscoFrictPhys_CundallStrack,Law2_ScGeom_FrictPhys_CundallStrack,"Law similar to :yref:`Law2_ScGeom_FrictPhys_CundallStrack` with the addition of shear creep at contacts.", ((bool,shearCreep,false,," ")) ((Real,viscosity,1,," ")) ((Real,creepStiffness,1,," ")) ,, ); FUNCTOR2D(ScGeom,ViscoFrictPhys); DECLARE_LOGGER; }; REGISTER_SERIALIZABLE(Law2_ScGeom_ViscoFrictPhys_CundallStrack); class ElasticContactLaw : public GlobalEngine{ shared_ptr functor; public : void action(); YADE_CLASS_BASE_DOC_ATTRS(ElasticContactLaw,GlobalEngine,"[DEPRECATED] Loop over interactions applying :yref:`Law2_ScGeom_FrictPhys_CundallStrack` on all interactions.\n\n.. note::\n Use :yref:`InteractionLoop` and :yref:`Law2_ScGeom_FrictPhys_CundallStrack` instead of this class for performance reasons.", ((bool,neverErase,false,,"Keep interactions even if particles go away from each other (only in case another constitutive law is in the scene, e.g. :yref:`Law2_ScGeom_CapillaryPhys_Capillarity`)")) ); }; REGISTER_SERIALIZABLE(ElasticContactLaw); trunk-2018.02b/pkg/dem/ElectrostaticMat.cpp000066400000000000000000000106461324306050200204760ustar00rootroot00000000000000// 2016 © William Chèvremont #include"ElectrostaticMat.hpp" YADE_PLUGIN((ElectrostaticMat)(Ip2_ElectrostaticMat_ElectrostaticMat_ElectrostaticPhys)(ElectrostaticPhys)(Law2_ScGeom_ElectrostaticPhys)) ElectrostaticPhys::ElectrostaticPhys(const CohFrictPhys & obj) : CohFrictPhys(obj), DebyeLength(1.e-6), InterConst(1.e-10), A(1.e-19), eps(0.001) { } CREATE_LOGGER(Ip2_ElectrostaticMat_ElectrostaticMat_ElectrostaticPhys); void Ip2_ElectrostaticMat_ElectrostaticMat_ElectrostaticPhys::go(const shared_ptr& material1, const shared_ptr& material2, const shared_ptr& interaction){ if (interaction->phys) return; // Inheritance Ip2_CohFrictMat_CohFrictMat_CohFrictPhys::go(material1,material2,interaction); CohFrictPhys* ph = YADE_CAST(interaction->phys.get()); // Cast to Electrostatic shared_ptr phys(new ElectrostaticPhys(*ph)); interaction->phys = phys; // Electrostatic behaviour const Real kB(1.38064852e-23); /* J/K Boltzmann*/ const Real nA(6.02214086e26); /* 1/kg Avogadro*/ const Real e(1.60217662e-19); /* A.s Electron charge*/ const Real VacPerm(8.854187817e-12); /* F/m Permittivity of vacuum*/ const Real pi(acos(-1)); Real sc(0); // Sum of charges Temp += 273.15; // to Kelvin if(DebyeLength == 0) { for(vector::const_iterator it=Ions.begin();it < Ions.end();it++) { sc += it->x()*it->x()*it->y(); } if(sc == 0) { LOG_ERROR("Null sum of charge. Assuming Debye Length as 1micron (Debye Length in totaly pure water, pH=7)"); phys->DebyeLength=1e-6; } else { Real tmp = RelPerm*VacPerm*kB*Temp/(e*e*nA*sc); phys->DebyeLength = pow(tmp,0.5); } } else { phys->DebyeLength = DebyeLength; } phys->A = A; phys->eps = eps; if(A == 0) LOG_ERROR("Hamaker constant (A) is null. Van Der Waals force will not be calculated"); //std::cout << "Debye length^-1: " << phys->DebyeLength << std::endl; if(Z == 0) phys->InterConst = 64.*pi*RelPerm*VacPerm*pow(kB*Temp/e*tanh(z*e*SurfCharge/(4.*kB*Temp)),2); else phys->InterConst = Z; if(phys->InterConst == 0) LOG_ERROR("Interaction constant is null. Set SurfCharge and Temp or Z. Double Layer Electrostatic Interaction will not be calculated."); } CREATE_LOGGER(ElectrostaticPhys); ElectrostaticPhys::~ElectrostaticPhys(){}; /********************** Law2_ScGeom_ElectrostaticPhys ****************************/ CREATE_LOGGER(Law2_ScGeom_ElectrostaticPhys); bool Law2_ScGeom_ElectrostaticPhys::go(shared_ptr& iGeom, shared_ptr& iPhys, Interaction* interaction){ // Inheritance Law2_ScGeom6D_CohFrictPhys_CohesionMoment::go(iGeom,iPhys,interaction); // Geometric ScGeom* geom=static_cast(iGeom.get()); ElectrostaticPhys* phys=static_cast(iPhys.get()); // Get bodies properties Body::id_t id1 = interaction->getId1(); Body::id_t id2 = interaction->getId2(); const shared_ptr b1 = Body::byId(id1,scene); const shared_ptr b2 = Body::byId(id2,scene); State* s1 = b1->state.get(); State* s2 = b2->state.get(); // Terms in the force Real& a1(geom->radius1); Real& a2(geom->radius2); Real r((s1->se3.position-s2->se3.position).norm()); Real D(r-a1-a2); Real K(1./phys->DebyeLength); Vector3r& normalForce(phys->normalForce); // No longer interact if distance is 10*DebyeLenght if(D > 10*phys->DebyeLength) return false; // Rugosity correction D = max(D, phys->eps*(a1+a2)/2); /* constitutive law */ Real DLEF(0.); // Double Layer Electrostatic Force Real VdW(0.); // Van Der Waals Force DLEF = phys->InterConst*K*exp(-K*D); VdW = -phys->A/(6.*pow(D,2)); normalForce = (VdW + DLEF)*a1*a2/(a1+a2)*geom->normal/geom->normal.norm(); phys->kn = (phys->A/3./pow(D,3.)+pow(K,2.)*phys->InterConst*exp(-K*D))*a1*a2/(a1+a2); // Stiffness scene->forces.addForce(id1,normalForce); scene->forces.addForce(id2,-normalForce); return true; } trunk-2018.02b/pkg/dem/ElectrostaticMat.hpp000066400000000000000000000112171324306050200204760ustar00rootroot00000000000000// 2016 © William Chèvremont #pragma once #include #include #include #include #include #include #include #include #include namespace py=boost::python; class ElectrostaticMat: public CohFrictMat { public: YADE_CLASS_BASE_DOC_ATTRS_CTOR(ElectrostaticMat,CohFrictMat,"Electrostatic material, used in Ip2_ElectrostaticMat_ElectrostaticMat_ElectrostaticPhys and Law2_ScGeom_ElectrostaticPhys.", //((Real,charge,0,,"Surface potential [mV]"))/*OLD*/ //((Real,DebyeCoef,0.05,,"Proportion of the radius that is the Debye length"))/*OLD*/ , createIndex(); ); REGISTER_CLASS_INDEX(ElectrostaticMat,CohFrictMat); }; REGISTER_SERIALIZABLE(ElectrostaticMat); class ElectrostaticPhys: public CohFrictPhys { public: ElectrostaticPhys(CohFrictPhys const& ); // "copy" constructor virtual ~ElectrostaticPhys(); YADE_CLASS_BASE_DOC_ATTRS_CTOR_PY(ElectrostaticPhys,CohFrictPhys,"IPhys class containing parameters of ElectrostaticMat. Used by Law2_ScGeom_ElectrostaticPhys.", ((Real,DebyeLength,1e-8,,"Debye Length [m]")) ((Real,InterConst,1e-12,,"Double layer interaction constant [J]")) ((Real,A,1e-19,,"Hamaker constant [J]")) ((Real, eps, 0.001,,"Rugosity [-]")) , // ctors createIndex();, .def_readonly("DebyeLength",&ElectrostaticPhys::DebyeLength,"Debye Length kappa^-1 [m]") .def_readonly("InterConst",&ElectrostaticPhys::InterConst,"Interaction Constant Z [J]") .def_readonly("A",&ElectrostaticPhys::A,"Hamaker Constant A [J]") ); DECLARE_LOGGER; REGISTER_CLASS_INDEX(ElectrostaticPhys,CohFrictPhys); }; REGISTER_SERIALIZABLE(ElectrostaticPhys); class Ip2_ElectrostaticMat_ElectrostaticMat_ElectrostaticPhys: public Ip2_CohFrictMat_CohFrictMat_CohFrictPhys{ public: virtual void go(const shared_ptr& material1, const shared_ptr& material2, const shared_ptr& interaction); FUNCTOR2D(ElectrostaticMat,ElectrostaticMat); DECLARE_LOGGER; YADE_CLASS_BASE_DOC_ATTRS_CTOR_PY(Ip2_ElectrostaticMat_ElectrostaticMat_ElectrostaticPhys,Ip2_CohFrictMat_CohFrictMat_CohFrictPhys,"Ip2 creating ElectrostaticPhys from two ElectrostaticMat instances.", ((Real,DebyeLength,0,,"Debye length [m]. If 0, will be calculated from fluid properties")) ((Real,SurfCharge,50,,"Surface potential [mV]")) ((Real,Temp,20,,"Temperature into the fluid [°C]")) ((Real,RelPerm,1,,"Relative permittivity of the fluid [-]")) ((Real,A,1e-19,,"Hamaker constant [J]")) ((Real,Z,0,,"Interaction constant [N]. If 0, will be calculated from termal properties")) ((Real,z,0,,"Surface ion valency [-]")) ((Real, eps, 0.001,,"Rugosity [-]")) ((vector,Ions,vector({Vector2r(-1,1),Vector2r(1,1)}),,"List of ions's charge and concentration (default is: 1mol/l Na(+1)Cl(-1): [(+1,1),(-1,1)]")) ,, ); }; REGISTER_SERIALIZABLE(Ip2_ElectrostaticMat_ElectrostaticMat_ElectrostaticPhys); class Law2_ScGeom_ElectrostaticPhys: public Law2_ScGeom6D_CohFrictPhys_CohesionMoment{ public: bool go(shared_ptr& iGeom, shared_ptr& iPhys, Interaction* interaction); FUNCTOR2D(GenericSpheresContact,ElectrostaticPhys); YADE_CLASS_BASE_DOC_ATTRS_CTOR_PY(Law2_ScGeom_ElectrostaticPhys,Law2_ScGeom6D_CohFrictPhys_CohesionMoment,"Material law for electrostatic interaction according to [Mari2013]_.",,, // ((Real,f_VdW,0,,"Computed Van Der Waals Force")) // ((Real,f_DLE,0,,"Computed Double Layer Electrostatic Force")),, // .def_readonly("f_VdW",&Law2_ScGeom_ElectrostaticPhys::f_VdW,"Computed VanDerWaals Force") // .def_readonly("f_DLE",&Law2_ScGeom_ElectrostaticPhys::f_DLE,"Computed Double Layer Electrostatic Force") // ONLY FOR DEBUGGING PURPOSE BETWEEN 2 PARTICLES ); DECLARE_LOGGER; }; REGISTER_SERIALIZABLE(Law2_ScGeom_ElectrostaticPhys); trunk-2018.02b/pkg/dem/FacetTopologyAnalyzer.cpp000066400000000000000000000155101324306050200215070ustar00rootroot00000000000000#include"FacetTopologyAnalyzer.hpp" #include #include #include CREATE_LOGGER(FacetTopologyAnalyzer); YADE_PLUGIN((FacetTopologyAnalyzer)); #ifndef FACET_TOPO void FacetTopologyAnalyzer::action(){ throw runtime_error("FACET_TOPO was not enabled in Facet.hpp at compile-time. Do not use FacetTopologyAnalyzer or recompile."); } #else void FacetTopologyAnalyzer::action(){ commonEdgesFound=0; LOG_DEBUG("Projection axis for analysis is "< > vv; // minimum facet edge length (tolerance scale) Real minSqLen=numeric_limits::infinity(); FOREACH(const shared_ptr& b, *scene->bodies){ shared_ptr f=YADE_PTR_DYN_CAST(b->shape); if(!f) continue; const Vector3r& pos=b->state->pos; for(size_t i=0; i<3; i++){ vv.push_back(shared_ptr(new VertexData(b->getId(),i,f->vertices[i]+pos,(f->vertices[i]+pos).dot(projectionAxis)))); minSqLen=min(minSqLen,(f->vertices[i]-f->vertices[(i+1)%3]).squaredNorm()); } } LOG_DEBUG("Added data for "<coord-vv[i]->coord)<=tolerance){ shared_ptr &vi=vv[i], &vj=vv[j]; if(std::abs(vi->pos[0]-vj->pos[0])<=tolerance && std::abs(vi->pos[1]-vj->pos[1])<=tolerance && std::abs(vi->pos[2]-vj->pos[2])<=tolerance && (vi->pos-vj->pos).squaredNorm()<=tolerance*tolerance){ // OK, these two vertices touch // LOG_TRACE("Vertices "<id<<"/"<vertexNo<<" and "<id<<"/"<vertexNo<<" close enough."); // add vertex to the nextIndetical of the one that has lower index; the one that is added will have isLowestIndex=false if(vi->indexindex){ vi->nextIdentical.push_back(vj); vj->isLowestIndex=false; } else{ vj->nextIdentical.push_back(vi); vi->isLowestIndex=false; } } } } // identity chains start always at lower indices, this way we get all of them sort(vv.begin(),vv.end(),VertexIndexComparator()); int maxVertexId=0; FOREACH(shared_ptr& v, vv){ if(v->vertexId<0){ assert(v->isLowestIndex); v->vertexId=maxVertexId++; } FOREACH(shared_ptr& vNext, v->nextIdentical){ vNext->vertexId=v->vertexId; } if(v->vertexId>=0) continue; // already assigned } LOG_DEBUG("Found "< > topo(scene->bodies->size()); // initialized with the default ctor FOREACH(shared_ptr& v, vv){ if(!topo[v->id]) topo[v->id]=shared_ptr(new FacetTopology(v->id)); topo[v->id]->vertices[v->vertexNo]=v->vertexId; } // make sure all facets have their vertex id's assigned // add non-empty ones to topo1 that will be used for adjacency search afterwards vector > topo1; for(size_t i=0; i t=topo[i]; if(!t) continue; if(t->vertices[0]<0 || t->vertices[1]<0 || t->vertices[2]<0){ LOG_FATAL("Facet #"<vertices[0]<<","<vertices[1]<<","<vertices[2]); throw logic_error("Facet vertex has no integrized vertex assigned?!"); } topo1.push_back(t); } std::sort(topo1.begin(),topo1.end(),FacetTopology::MinVertexComparator()); size_t nTopo=topo1.size(); for(size_t i=0; i& ti(topo1[i]); long tiMaxVertex=ti->maxVertex(); while(++j &tj(topo1[j]); /* since facets are sorted by their min vertex id, we know that it is safe to skip all the rest as soon as max vertex of ti one is smaller than min vertex of tj, as i<=j */ if(tj->minVertex()>tiMaxVertex) break; vector vvv; // array of common vertices for(size_t k=0; k<3; k++){ if (ti->vertices[k]==tj->vertices[0]) vvv.push_back(ti->vertices[k]); else if(ti->vertices[k]==tj->vertices[1]) vvv.push_back(ti->vertices[k]); else if(ti->vertices[k]==tj->vertices[2]) vvv.push_back(ti->vertices[k]); } if(vvv.size()<2) continue; assert(vvv.size()!=3 /* same coords? nonsense*/ ); assert(vvv.size()==2); // from here, we know ti and tj are adjacent vector edge(2,0); int &ei(edge[0]),&ej(edge[1]); // identify what edge are we at, for both facets for(int k=0; k<2; k++){ for(edge[k]=0; edge[k]<3; edge[k]++){ const shared_ptr& tt( k==0 ? ti : tj); size_t v1=tt->vertices[edge[k]],v2=tt->vertices[(edge[k]+1)%3]; if((vvv[0]==v1 && vvv[1]==v2) || (vvv[0]==v2 && vvv[1]==v1)) break; if(edge[k]==2){ LOG_FATAL("No edge identified for 2 vertices "<id<<" is: "<vertices[0]<<","<vertices[1]<<","<vertices[2]<<")"); throw logic_error("No edge identified for given vertices."); } } } // add adjacency information to the facet itself Facet *f1=YADE_CAST((*scene->bodies)[ti->id]->shape.get()), *f2=YADE_CAST((*scene->bodies)[tj->id]->shape.get()); f1->edgeAdjIds[ei]=ti->id; f2->edgeAdjIds[ej]=tj->id; // normals are in the sense of vertex rotation (right-hand rule); therefore, if vertices of the adjacent edge are opposite on each facet, normals are in the same direction bool invNormals=(ti->vertices[ei]==tj->vertices[ej]); assert( ( invNormals && (ti->vertices[ ei ]==tj->vertices[ej]) && (ti->vertices[(ei+1)%3]==tj->vertices[(ej+1)%3]) ) || (!invNormals && (ti->vertices[(ei+1)%3]==tj->vertices[ej]) && (ti->vertices[ ei ]==tj->vertices[(ej+1)%3]) )); // angle between normals const shared_ptr& b1=Body::byId(ti->id,scene); const shared_ptr& b2=Body::byId(tj->id,scene); Vector3r n1g=b1->state->ori*f1->normal, n2g=b2->state->ori*f2->normal; //TRVAR2(n1g,n2g); Vector3r contEdge1g=b1->state->ori*(f1->vertices[(ei+1)%3]-f1->vertices[ei]); // vector of the edge of contact in global coords Quaternionr q12; q12.setFromTwoVectors(n1g,(invNormals?-1.:1.)*n2g); AngleAxisr aa12(q12); Real halfAngle=.5*aa12.angle(); assert(halfAngle>=0 && halfAngle<=Mathr::HALF_PI); if(aa12.axis().dot(contEdge1g)<0 /* convex contact from the side of +n1 */ ) halfAngle*=-1.; f1->edgeAdjHalfAngle[ei]=halfAngle; f2->edgeAdjHalfAngle[ej]=(invNormals ? -halfAngle : halfAngle); commonEdgesFound++; LOG_TRACE("Found adjacent #"<id<<"+#"<id<<"; common edges "< #pragma once #include #include /*! Initializer for filling adjacency geometry data for facets. * * Common vertices and common edges are identified and mutual angle between facet faces * is written to Facet instances. * If facets don't move with respect to each other, this must be done only at the beginning. */ class FacetTopologyAnalyzer: public GlobalEngine{ struct VertexData{ VertexData(Body::id_t _id, int _vertexNo, Vector3r _pos, Real _coord): id(_id), vertexNo(_vertexNo), coord(_coord), pos(_pos){index=3*id+vertexNo; isLowestIndex=true; vertexId=-1;} //! Facet (body id) that we represent Body::id_t id; //! vertex number within this Facet int vertexNo; //! projected coordinate along projectionAxis Real coord; //! global coordinates Vector3r pos; //! index of this vertex (canonical ordering) size_t index; //! is this vertex the first one in the "identity" graph? bool isLowestIndex; //! vertices that are "identical" with this one, but have higer indices vector > nextIdentical; //! id of vertex, once they have id's assigned long vertexId; }; struct VertexComparator{ bool operator()(const shared_ptr& v1, const shared_ptr& v2){return v1->coordcoord;} }; struct VertexIndexComparator{ bool operator()(const shared_ptr& v1, const shared_ptr& v2){return v1->indexindex;} }; struct FacetTopology{ FacetTopology(Body::id_t _id): id(_id){vertices[0]=vertices[1]=vertices[2]=-1;} //! integrized vertices long vertices[3]; //! facet id, for back reference Body::id_t id; long minVertex(){return min(vertices[0],min(vertices[1],vertices[2]));} long maxVertex(){return max(vertices[0],max(vertices[1],vertices[2]));} struct MinVertexComparator{ bool operator()(const shared_ptr& t1, const shared_ptr& t2){ return t1->minVertex()minVertex();} }; }; public: void action(); YADE_CLASS_BASE_DOC_ATTRS(FacetTopologyAnalyzer,GlobalEngine,"Initializer for filling adjacency geometry data for facets.\n\nCommon vertices and common edges are identified and mutual angle between facet faces is written to Facet instances. If facets don't move with respect to each other, this must be done only at the beginng.", ((Vector3r,projectionAxis,Vector3r::UnitX(),,"Axis along which to do the initial vertex sort")) ((Real,relTolerance,1e-4,,"maximum distance of 'identical' vertices, relative to minimum facet size")) ((long,commonEdgesFound,0,,"how many common edges were identified during last run. |yupdate|")) ((long,commonVerticesFound,0,,"how many common vertices were identified during last run. |yupdate|")) ); DECLARE_LOGGER; }; REGISTER_SERIALIZABLE(FacetTopologyAnalyzer); trunk-2018.02b/pkg/dem/FlatGridCollider.cpp000066400000000000000000000113761324306050200204020ustar00rootroot00000000000000// 2010 © Václav Šmilauer #include #include #include #include //#include YADE_PLUGIN((FlatGridCollider)); CREATE_LOGGER(FlatGridCollider); bool FlatGridCollider::isActivated(){ // keep interactions trequested for deletion as potential (forget removal requests) // scene->interactions->clearPendingErase(); if(!newton) return true; // handle verlet distance fastestBodyMaxDist+=sqrt(newton->maxVelocitySq)*scene->dt; if(fastestBodyMaxDist>verletDist) return true; return false; } void FlatGridCollider::action(){ if(!newton){ FOREACH(const shared_ptr& e, scene->engines){ newton=YADE_PTR_DYN_CAST(e); if(newton) break; } if(!newton){ throw runtime_error("FlatGridCollider: Unable to find NewtonIntegrator in engines."); } } fastestBodyMaxDist=0; // make interaction loop delete unseen potential interactions scene->interactions->iterColliderLastRun=scene->iter; // adjust grid if necessary updateGrid(); FOREACH(const shared_ptr& b, *scene->bodies){ if(!b) continue; // deleted bodies updateBodyCells(b); } updateCollisions(); } // called from the ctor void FlatGridCollider::initIndices(){ sphereIdx=facetIdx=wallIdx=boxIdx=-1; sphereIdx=Sphere::getClassIndexStatic(); LOG_DEBUG("sphereIdx="<& b){ if(!b->shape) return; const Shape* shape(b->shape.get()); // Sphere if(shape->getClassIndex()==sphereIdx){ Real r=static_cast(shape)->radius+verletDist; const Vector3r& C=b->state->pos; // create "bounding cells" and traverse them one by one; integrized coords can be _outside_ grid, they will be forced to closest cells before insertion Vector3i cMn=grid.pt2int(C-Vector3r(r,r,r)), cMx=grid.pt2int(C+Vector3r(r,r,r)), cC=grid.pt2int(C), cPt; LOG_TRACE("Sphere #"<id<<", C="<id<<" to cell ("<getClassName()+"!"); } void FlatGridCollider::updateCollisions(){ shared_ptr& intrs=scene->interactions; const long& iter=scene->iter; // create interactions for all combinations of bodies within one cell FOREACH(const Grid::idVector& v, grid.data){ size_t sz=v.size(); for(size_t i=0; i& I=intrs->find(id1,id2); if(I){ I->iterLastSeen=iter; continue; } // no interaction yet if(!Collider::mayCollide(Body::byId(id1,scene).get(),Body::byId(id2,scene).get())) continue; intrs->insert(shared_ptr(new Interaction(id1,id2))); LOG_TRACE("Created new interaction #"< #include class NewtonIntegrator; class FlatGridCollider: public Collider{ struct Grid{ typedef std::vector idVector; Vector3i size; Vector3r mn, mx; Real step; // convert point into its integral coordinates (can be outside grid, use fitGrid to coords inside) Vector3i pt2int(const Vector3r& pt){ Vector3i ret; for(int i=0;i<3;i++)ret[i]=floor((pt[i]-mn[1])/step); return ret; } std::vector data; // force integral coordinate inside (0,sz-1) int fit(int i, int sz) const { return max(0,min(i,sz-1)); } Vector3i fitGrid(const Vector3i& v){ return Vector3i(fit(v[0],size[0]),fit(v[1],size[1]),fit(v[2],size[2])); } // linearize x,y,z → n in data vector size_t lin(int x, int y, int z) const { return fit(x,size[0])+size[0]*fit(y,size[1])+size[0]*size[1]*fit(z,size[2]); } // return vector of ids at (x,y,z) idVector& operator()(int x, int y, int z){ return data[lin(x,y,z)];} idVector& operator()(const Vector3i& v){ return data[lin(v[0],v[1],v[2])];} const idVector& operator()(int x, int y, int z) const { return data[lin(x,y,z)];} }; Grid grid; int sphereIdx, facetIdx, wallIdx, boxIdx; // needed for maxVelSq shared_ptr newton; // track maximum distance of fastest body, at every step in isActivated Real fastestBodyMaxDist; void initIndices(); void updateGrid(); void updateBodyCells(const shared_ptr& b); void updateCollisions(); virtual void action(); virtual bool isActivated(); DECLARE_LOGGER; YADE_CLASS_BASE_DOC_ATTRS_CTOR(FlatGridCollider,Collider,"Non-optimized grid collider, storing grid as dense flat array. Each body is assigned to (possibly multiple) cells, which are arranged in regular grid between *aabbMin* and *aabbMax*, with cell size *step* (same in all directions). Bodies outsize (*aabbMin*, *aabbMax*) are handled gracefully, assigned to closest cells (this will create spurious potential interactions). *verletDist* determines how much is each body enlarged to avoid collision detection at every step.\n\n.. note::\n\tThis collider keeps all cells in linear memory array, therefore will be memory-inefficient for sparse simulations.\n\n.. warning::\n\tobjects :yref:`Body::bound` are not used, :yref:`BoundFunctors` are not used either: assigning cells to bodies is hard-coded internally. Currently handles :yref:`Shapes` are: :yref:`Sphere`.\n\n.. note::\n\tPeriodic boundary is not handled (yet).\n\n", ((Real,verletDist,0,,"Length by which enlarge space occupied by each particle; avoids running collision detection at every step.")) ((Vector3r,aabbMin,Vector3r::Zero(),,"Lower corner of grid.")) ((Vector3r,aabbMax,Vector3r::Zero(),,"Upper corner of grid (approximate, might be rouded up to *minStep*.")) ((Real,step,0,,"Step in the grid (cell size)")), initIndices(); ); }; REGISTER_SERIALIZABLE(FlatGridCollider); trunk-2018.02b/pkg/dem/ForceTorqueRecorder.cpp000066400000000000000000000017441324306050200211520ustar00rootroot00000000000000#include"ForceTorqueRecorder.hpp" YADE_PLUGIN((ForceRecorder)(TorqueRecorder)); CREATE_LOGGER(ForceRecorder); void ForceRecorder::action(){ totalForce=Vector3r::Zero(); FOREACH(Body::id_t id, ids){ if (!(scene->bodies->exists(id))) continue; totalForce+=scene->forces.getForce(id); }; //Save data to a file out<iter<<" "<bodies->exists(id))) continue; Body* b=Body::byId(id,scene).get(); Vector3r tmpPos = b->state->pos; Vector3r radiusVector = tmpAxis.cross(tmpAxis.cross(tmpPos - zeroPoint)); totalTorque+=tmpAxis.dot(scene->forces.getTorque(id)+radiusVector.cross(scene->forces.getForce(id))); }; //Save data to a file out<iter<<" "< #include class ForceRecorder: public Recorder { public: virtual void action(); YADE_CLASS_BASE_DOC_ATTRS_CTOR_PY(ForceRecorder,Recorder,"Engine saves the resultant force affecting to bodies, listed in `ids`. For instance, can be useful for defining the forces, which affects to _buldozer_ during its work.", ((std::vector,ids,,,"List of bodies whose state will be measured")) ((Vector3r,totalForce,Vector3r::Zero(),,"Resultant force, returning by the function.")) , initRun=true; , ); DECLARE_LOGGER; }; REGISTER_SERIALIZABLE(ForceRecorder); class TorqueRecorder: public Recorder { public: virtual void action(); YADE_CLASS_BASE_DOC_ATTRS_CTOR(TorqueRecorder,Recorder,"Engine saves the total torque according to the given axis and ZeroPoint, the force is taken from bodies, listed in `ids` For instance, can be useful for defining the torque, which affects on ball mill during its work.", ((std::vector,ids,,,"List of bodies whose state will be measured")) ((Vector3r,rotationAxis,Vector3r::UnitX(),,"Rotation axis")) ((Vector3r,zeroPoint,Vector3r::Zero(),,"Point of rotation center")) ((Real,totalTorque,0,,"Resultant torque, returning by the function.")), initRun=true; ); DECLARE_LOGGER; }; REGISTER_SERIALIZABLE(TorqueRecorder); trunk-2018.02b/pkg/dem/FrictPhys.cpp000066400000000000000000000060461324306050200171410ustar00rootroot00000000000000#include "FrictPhys.hpp" #include YADE_PLUGIN((FrictPhys)(ViscoFrictPhys)(Ip2_FrictMat_FrictMat_ViscoFrictPhys)(Ip2_FrictMat_FrictMat_FrictPhys)); // The following code was moved from Ip2_FrictMat_FrictMat_FrictPhys.hpp void Ip2_FrictMat_FrictMat_FrictPhys::go( const shared_ptr& b1 , const shared_ptr& b2 , const shared_ptr& interaction) { if(interaction->phys) return; const shared_ptr& mat1 = YADE_PTR_CAST(b1); const shared_ptr& mat2 = YADE_PTR_CAST(b2); Real Ra,Rb;//Vector3r normal; assert(dynamic_cast(interaction->geom.get()));//only in debug mode GenericSpheresContact* sphCont=YADE_CAST(interaction->geom.get()); Ra=sphCont->refR1>0?sphCont->refR1:sphCont->refR2; Rb=sphCont->refR2>0?sphCont->refR2:sphCont->refR1; interaction->phys = shared_ptr(new FrictPhys()); const shared_ptr& contactPhysics = YADE_PTR_CAST(interaction->phys); Real Ea = mat1->young; Real Eb = mat2->young; Real Va = mat1->poisson; Real Vb = mat2->poisson; //harmonic average of the two stiffnesses when (2*Ri*Ei) is the stiffness of a contact point on sphere "i" Real Kn = 2*Ea*Ra*Eb*Rb/(Ea*Ra+Eb*Rb); //same for shear stiffness Real Ks = 2*Ea*Ra*Va*Eb*Rb*Vb/(Ea*Ra*Va+Eb*Rb*Vb); Real frictionAngle = (!frictAngle) ? std::min(mat1->frictionAngle,mat2->frictionAngle) : (*frictAngle)(mat1->id,mat2->id,mat1->frictionAngle,mat2->frictionAngle); contactPhysics->tangensOfFrictionAngle = std::tan(frictionAngle); contactPhysics->kn = Kn; contactPhysics->ks = Ks; }; void Ip2_FrictMat_FrictMat_ViscoFrictPhys::go( const shared_ptr& b1 , const shared_ptr& b2 , const shared_ptr& interaction) { if(interaction->phys) return; const shared_ptr& mat1 = YADE_PTR_CAST(b1); const shared_ptr& mat2 = YADE_PTR_CAST(b2); interaction->phys = shared_ptr(new ViscoFrictPhys()); const shared_ptr& contactPhysics = YADE_PTR_CAST(interaction->phys); Real Ea = mat1->young; Real Eb = mat2->young; Real Va = mat1->poisson; Real Vb = mat2->poisson; Real Ra,Rb;//Vector3r normal; assert(dynamic_cast(interaction->geom.get()));//only in debug mode GenericSpheresContact* sphCont=YADE_CAST(interaction->geom.get()); Ra=sphCont->refR1>0?sphCont->refR1:sphCont->refR2; Rb=sphCont->refR2>0?sphCont->refR2:sphCont->refR1; //harmonic average of the two stiffnesses when (Ri.Ei/2) is the stiffness of a contact point on sphere "i" Real Kn = 2*Ea*Ra*Eb*Rb/(Ea*Ra+Eb*Rb); //same for shear stiffness Real Ks = 2*Ea*Ra*Va*Eb*Rb*Vb/(Ea*Ra*Va+Eb*Rb*Vb); Real frictionAngle = (!frictAngle) ? std::min(mat1->frictionAngle,mat2->frictionAngle) : (*frictAngle)(mat1->id,mat2->id,mat1->frictionAngle,mat2->frictionAngle); contactPhysics->tangensOfFrictionAngle = std::tan(frictionAngle); contactPhysics->kn = Kn; contactPhysics->ks = Ks; }; trunk-2018.02b/pkg/dem/FrictPhys.hpp000066400000000000000000000106161324306050200171440ustar00rootroot00000000000000/************************************************************************* * Copyright (C) 2007 by Bruno CHAREYRE * * bruno.chareyre@hmg.inpg.fr * * * * This program is free software; it is licensed under the terms of the * * GNU General Public License v2 or later. See file LICENSE for details. * *************************************************************************/ #pragma once #include #include #include #include class FrictPhys: public NormShearPhys { public : virtual ~FrictPhys() {}; YADE_CLASS_BASE_DOC_ATTRS_CTOR(FrictPhys,NormShearPhys,"The simple linear elastic-plastic interaction with friction angle, like in the traditional [CundallStrack1979]_", ((Real,tangensOfFrictionAngle,NaN,,"tan of angle of friction")), createIndex() ); REGISTER_CLASS_INDEX(FrictPhys,NormShearPhys); }; REGISTER_SERIALIZABLE(FrictPhys); class ViscoFrictPhys: public FrictPhys { public : virtual ~ViscoFrictPhys() {}; YADE_CLASS_BASE_DOC_ATTRS_CTOR(ViscoFrictPhys,FrictPhys,"Temporary version of :yref:`FrictPhys` for compatibility reasons", ((Vector3r,creepedShear,Vector3r(0,0,0),(Attr::readonly),"Creeped force (parallel)")), createIndex() ); REGISTER_CLASS_INDEX(ViscoFrictPhys,FrictPhys); }; REGISTER_SERIALIZABLE(ViscoFrictPhys); // The following code was moved from Ip2_FrictMat_FrictMat_FrictPhys.hpp class Ip2_FrictMat_FrictMat_FrictPhys: public IPhysFunctor{ public: virtual void go(const shared_ptr& b1, const shared_ptr& b2, const shared_ptr& interaction); FUNCTOR2D(FrictMat,FrictMat); YADE_CLASS_BASE_DOC_ATTRS(Ip2_FrictMat_FrictMat_FrictPhys,IPhysFunctor,"Create a :yref:`FrictPhys` from two :yref:`FrictMats`. The compliance of one sphere under point load is defined here as $1/(E.D)$, with $E$ the stiffness of the sphere and $D$ its diameter. The compliance of the contact itself will be the sum of compliances from each sphere, i.e. $1/(E_1.D_1)+1/(E_2.D_2)$ in the general case, or $2/(E.D)$ in the special case of equal sizes and equal stiffness. Note that summing compliances corresponds to an harmonic average of stiffnesss (as in e.g. [Scholtes2009a]_), which is how kn is actually computed in the :yref:`Ip2_FrictMat_FrictMat_FrictPhys` functor:\n\n $k_n = \\frac{E_1D_1*E_2D_2}{E_1D_1+E_2D_2}=\\frac{k_1*k_2}{k_1+k_2}$, with $k_i=E_iD_i$.\n\n The shear stiffness ks of one sphere is defined via the material parameter :yref:`ElastMat::poisson`, as ks=poisson*kn, and the resulting shear stiffness of the interaction will be also an harmonic average. In the case of a contact between a :yref:`ViscElMat` and a :yref:`FrictMat`, be sure to set :yref:`FrictMat::young` and :yref:`FrictMat::poisson`, otherwise the default value will be used.", ((shared_ptr,frictAngle,,,"Instance of :yref:`MatchMaker` determining how to compute interaction's friction angle. If ``None``, minimum value is used.")) ); }; REGISTER_SERIALIZABLE(Ip2_FrictMat_FrictMat_FrictPhys); class Ip2_FrictMat_FrictMat_ViscoFrictPhys: public Ip2_FrictMat_FrictMat_FrictPhys{ public: virtual void go(const shared_ptr& b1, const shared_ptr& b2, const shared_ptr& interaction); FUNCTOR2D(FrictMat,FrictMat); YADE_CLASS_BASE_DOC_ATTRS(Ip2_FrictMat_FrictMat_ViscoFrictPhys,Ip2_FrictMat_FrictMat_FrictPhys,"Create a :yref:`FrictPhys` from two :yref:`FrictMats`. The compliance of one sphere under symetric point loads is defined here as 1/(E.r), with E the stiffness of the sphere and r its radius, and corresponds to a compliance 1/(2.E.r)=1/(E.D) from each contact point. The compliance of the contact itself will be the sum of compliances from each sphere, i.e. 1/(E.D1)+1/(E.D2) in the general case, or 1/(E.r) in the special case of equal sizes. Note that summing compliances corresponds to an harmonic average of stiffnesss, which is how kn is actually computed in the :yref:`Ip2_FrictMat_FrictMat_FrictPhys` functor.\n\nThe shear stiffness ks of one sphere is defined via the material parameter :yref:`ElastMat::poisson`, as ks=poisson*kn, and the resulting shear stiffness of the interaction will be also an harmonic average.", ); }; REGISTER_SERIALIZABLE(Ip2_FrictMat_FrictMat_ViscoFrictPhys); trunk-2018.02b/pkg/dem/FrictViscoPM.cpp000066400000000000000000000252761324306050200175440ustar00rootroot00000000000000/************************************************************************* * Copyright (C) 2014 by Klaus Thoeni * * klaus.thoeni@newcastle.edu.au * * * * This program is free software; it is licensed under the terms of the * * GNU General Public License v2 or later. See file LICENSE for details. * *************************************************************************/ #include"FrictViscoPM.hpp" #include #include #include YADE_PLUGIN((FrictViscoMat)(FrictViscoPhys)(Ip2_FrictViscoMat_FrictViscoMat_FrictViscoPhys)(Ip2_FrictMat_FrictViscoMat_FrictViscoPhys)(Law2_ScGeom_FrictViscoPhys_CundallStrackVisco)); FrictViscoMat::~FrictViscoMat(){} /********************** Ip2_FrictViscoMat_FrictMat_FrictViscoPhys ****************************/ CREATE_LOGGER(FrictViscoPhys); FrictViscoPhys::~FrictViscoPhys(){}; /********************** Ip2_FrictViscoMat_FrictMat_FrictViscoPhys ****************************/ CREATE_LOGGER(Ip2_FrictViscoMat_FrictViscoMat_FrictViscoPhys); void Ip2_FrictViscoMat_FrictViscoMat_FrictViscoPhys::go(const shared_ptr& b1, const shared_ptr& b2, const shared_ptr& interaction){ LOG_TRACE( "Ip2_FrictViscoMat_FrictViscoMat_FrictViscoPhys::go - contact law" ); if(interaction->phys) return; const shared_ptr& mat1 = YADE_PTR_CAST(b1); const shared_ptr& mat2 = YADE_PTR_CAST(b2); interaction->phys = shared_ptr(new FrictViscoPhys()); const shared_ptr& contactPhysics = YADE_PTR_CAST(interaction->phys); Real Ea = mat1->young; Real Eb = mat2->young; Real Va = mat1->poisson; Real Vb = mat2->poisson; Real Ra,Rb; assert(dynamic_cast(interaction->geom.get()));//only in debug mode GenericSpheresContact* sphCont=YADE_CAST(interaction->geom.get()); Ra=sphCont->refR1>0?sphCont->refR1:sphCont->refR2; Rb=sphCont->refR2>0?sphCont->refR2:sphCont->refR1; // calculate stiffness from MatchMaker or use harmonic avarage as usual if not given Real Kn = (kn) ? (*kn)(mat1->id,mat2->id) : 2.*Ea*Ra*Eb*Rb/(Ea*Ra+Eb*Rb); Real Ks = (kRatio) ? (*kRatio)(mat1->id,mat2->id)*Kn : 2.*Ea*Ra*Va*Eb*Rb*Vb/(Ea*Ra*Va+Eb*Rb*Vb); Real frictionAngle = (!frictAngle) ? std::min(mat1->frictionAngle,mat2->frictionAngle) : (*frictAngle)(mat1->id,mat2->id,mat1->frictionAngle,mat2->frictionAngle); contactPhysics->tangensOfFrictionAngle = std::tan(frictionAngle); contactPhysics->kn = Kn; contactPhysics->ks = Ks; /************************/ /* DAMPING COEFFICIENTS */ /************************/ Real betana = mat1->betan; Real betanb = mat2->betan; // inclusion of local viscous damping if (betana!=0 || betanb!=0){ Body::id_t ida = interaction->getId1(); // get id body 1 Body::id_t idb = interaction->getId2(); // get id body 2 State* dea = Body::byId(ida,scene)->state.get(); State* deb = Body::byId(idb,scene)->state.get(); const shared_ptr& ba=Body::byId(ida,scene); const shared_ptr& bb=Body::byId(idb,scene); Real mbar = (!ba->isDynamic() && bb->isDynamic()) ? deb->mass : ((!bb->isDynamic() && ba->isDynamic()) ? dea->mass : (dea->mass*deb->mass / (dea->mass + deb->mass))); // get equivalent mass if both bodies are dynamic, if not set it equal to the one of the dynamic body TRVAR2(Kn,mbar); contactPhysics->cn_crit = 2.*sqrt(mbar*Kn); // Critical damping coefficient (normal direction) contactPhysics->cn = contactPhysics->cn_crit * ( (betana!=0 && betanb!=0) ? ((betana+betanb)/2.) : ( (betanb==0) ? betana : betanb )); // Damping normal coefficient } else contactPhysics->cn=0.; TRVAR1(contactPhysics->cn); } /********************** Ip2_FrictViscoMat_FrictMat_FrictViscoPhys ****************************/ CREATE_LOGGER(Ip2_FrictMat_FrictViscoMat_FrictViscoPhys); void Ip2_FrictMat_FrictViscoMat_FrictViscoPhys::go(const shared_ptr& b1, const shared_ptr& b2, const shared_ptr& interaction){ LOG_TRACE( "Ip2_FrictMat_FrictViscoMat_FrictViscoPhys::go - contact law" ); if(interaction->phys) return; const shared_ptr& mat1 = YADE_PTR_CAST(b1); const shared_ptr& mat2 = YADE_PTR_CAST(b2); interaction->phys = shared_ptr(new FrictViscoPhys()); const shared_ptr& contactPhysics = YADE_PTR_CAST(interaction->phys); Real Ea = mat1->young; Real Eb = mat2->young; Real Va = mat1->poisson; Real Vb = mat2->poisson; Real Ra,Rb; assert(dynamic_cast(interaction->geom.get()));//only in debug mode GenericSpheresContact* sphCont=YADE_CAST(interaction->geom.get()); Ra=sphCont->refR1>0?sphCont->refR1:sphCont->refR2; Rb=sphCont->refR2>0?sphCont->refR2:sphCont->refR1; // calculate stiffness from MatchMaker or use harmonic avarage as usual if not given Real Kn = (kn) ? (*kn)(mat1->id,mat2->id) : 2.*Ea*Ra*Eb*Rb/(Ea*Ra+Eb*Rb); Real Ks = (kRatio) ? (*kRatio)(mat1->id,mat2->id)*Kn : 2.*Ea*Ra*Va*Eb*Rb*Vb/(Ea*Ra*Va+Eb*Rb*Vb); Real frictionAngle = (!frictAngle) ? std::min(mat1->frictionAngle,mat2->frictionAngle) : (*frictAngle)(mat1->id,mat2->id,mat1->frictionAngle,mat2->frictionAngle); contactPhysics->tangensOfFrictionAngle = std::tan(frictionAngle); contactPhysics->kn = Kn; contactPhysics->ks = Ks; /************************/ /* DAMPING COEFFICIENTS */ /************************/ Real betanb = mat2->betan; // inclusion of local viscous damping if (betanb!=0){ Body::id_t ida = interaction->getId1(); // get id body 1 Body::id_t idb = interaction->getId2(); // get id body 2 State* dea = Body::byId(ida,scene)->state.get(); State* deb = Body::byId(idb,scene)->state.get(); const shared_ptr& ba=Body::byId(ida,scene); const shared_ptr& bb=Body::byId(idb,scene); Real mbar = (!ba->isDynamic() && bb->isDynamic()) ? deb->mass : ((!bb->isDynamic() && ba->isDynamic()) ? dea->mass : (dea->mass*deb->mass / (dea->mass + deb->mass))); // get equivalent mass if both bodies are dynamic, if not set it equal to the one of the dynamic body TRVAR2(Kn,mbar); contactPhysics->cn_crit = 2.*sqrt(mbar*Kn); // Critical damping coefficient (normal direction) contactPhysics->cn = contactPhysics->cn_crit * betanb; // Damping normal coefficient } else contactPhysics->cn=0.; TRVAR1(contactPhysics->cn); } /********************** Law2_ScGeom_FrictViscoPhys_CundallStrackVisco ****************************/ // #if 1 Real Law2_ScGeom_FrictViscoPhys_CundallStrackVisco::getPlasticDissipation() {return (Real) plasticDissipation;} void Law2_ScGeom_FrictViscoPhys_CundallStrackVisco::initPlasticDissipation(Real initVal) {plasticDissipation.reset(); plasticDissipation+=initVal;} Real Law2_ScGeom_FrictViscoPhys_CundallStrackVisco::elasticEnergy() { Real energy=0; FOREACH(const shared_ptr& I, *scene->interactions){ if(!I->isReal()) continue; FrictPhys* phys = dynamic_cast(I->phys.get()); if(phys) { energy += 0.5*(phys->normalForce.squaredNorm()/phys->kn + phys->shearForce.squaredNorm()/phys->ks);} } return energy; } // #endif CREATE_LOGGER(Law2_ScGeom_FrictViscoPhys_CundallStrackVisco); bool Law2_ScGeom_FrictViscoPhys_CundallStrackVisco::go(shared_ptr& ig, shared_ptr& ip, Interaction* contact) { LOG_TRACE( "Law2_ScGeom_FrictViscoPhys_CundallStrackVisco::go - create interaction physics" ); int id1 = contact->getId1(), id2 = contact->getId2(); ScGeom* geom= static_cast(ig.get()); FrictViscoPhys* phys = static_cast(ip.get()); if(geom->penetrationDepth <0){ if (neverErase) { phys->shearForce = Vector3r::Zero(); phys->normalForce = Vector3r::Zero();} else return false; } Real& un=geom->penetrationDepth; phys->normalForce = phys->kn*std::max(un,(Real) 0) * geom->normal; /************************/ /* DAMPING CONTRIBUTION */ /************************/ // define shifts to handle periodicity const Vector3r shift2 = scene->isPeriodic ? scene->cell->intrShiftPos(contact->cellDist): Vector3r::Zero(); const Vector3r shiftVel = scene->isPeriodic ? scene->cell->intrShiftVel(contact->cellDist): Vector3r::Zero(); State* de1 = Body::byId(id1,scene)->state.get(); State* de2 = Body::byId(id2,scene)->state.get(); // get incident velocity Vector3r incidentV = geom->getIncidentVel(de1, de2, scene->dt, shift2, shiftVel); Vector3r incidentVn = geom->normal.dot(incidentV)*geom->normal; // contact normal velocity phys->normalViscous = phys->cn*incidentVn; phys->normalForce -= phys->normalViscous; // shear force Vector3r& shearForce = geom->rotate(phys->shearForce); const Vector3r& shearDisp = geom->shearIncrement(); shearForce -= phys->ks*shearDisp; Real maxFs = phys->normalForce.squaredNorm()*std::pow(phys->tangensOfFrictionAngle,2); if (!scene->trackEnergy && !traceEnergy){//Update force but don't compute energy terms (see below)) // PFC3d SlipModel, is using friction angle. CoulombCriterion if( shearForce.squaredNorm() > maxFs ){ Real ratio = sqrt(maxFs) / shearForce.norm(); shearForce *= ratio;} } else { //almost the same with additional Vector3r instatinated for energy tracing, //duplicated block to make sure there is no cost for the instanciation of the vector when traceEnergy==false if(shearForce.squaredNorm() > maxFs){ Real ratio = sqrt(maxFs) / shearForce.norm(); Vector3r trialForce=shearForce;//store prev force for definition of plastic slip //define the plastic work input and increment the total plastic energy dissipated shearForce *= ratio; Real dissip=((1/phys->ks)*(trialForce-shearForce))/*plastic disp*/ .dot(shearForce)/*active force*/; if (traceEnergy) plasticDissipation += dissip; else if(dissip>0) scene->energy->add(dissip,"plastDissip",plastDissipIx,/*reset*/false); } // compute elastic energy as well scene->energy->add(0.5*(phys->normalForce.squaredNorm()/phys->kn+phys->shearForce.squaredNorm()/phys->ks),"elastPotential",elastPotentialIx,/*reset at every timestep*/true); } if (!scene->isPeriodic && !sphericalBodies) { applyForceAtContactPoint(-phys->normalForce-shearForce, geom->contactPoint, id1, de1->se3.position, id2, de2->se3.position);} else {//we need to use correct branches in the periodic case, the following apply for spheres only Vector3r force = -phys->normalForce-shearForce; scene->forces.addForce(id1,force); scene->forces.addForce(id2,-force); scene->forces.addTorque(id1,(geom->radius1-0.5*geom->penetrationDepth)* geom->normal.cross(force)); scene->forces.addTorque(id2,(geom->radius2-0.5*geom->penetrationDepth)* geom->normal.cross(force)); } return true; } trunk-2018.02b/pkg/dem/FrictViscoPM.hpp000066400000000000000000000173461324306050200175500ustar00rootroot00000000000000/************************************************************************* * Copyright (C) 2014 by Klaus Thoeni * * klaus.thoeni@newcastle.edu.au * * * * This program is free software; it is licensed under the terms of the * * GNU General Public License v2 or later. See file LICENSE for details. * *************************************************************************/ /** === OVERVIEW OF FrictViscoPM === A particle model for friction and viscous damping in normal direction. The damping coefficient has to be defined with the material whereas the contact stiffness kn and ks/kn can be defined with the Ip2 functor. Remarks: - maybe there is a better way of implementing this without copying from ElasticContactLaw - maybe we can combine some ideas of this contact law with other contact laws */ #pragma once #include #include #include #include #include #include /** This class holds information associated with each body */ class FrictViscoMat: public FrictMat { public: virtual ~FrictViscoMat(); YADE_CLASS_BASE_DOC_ATTRS_CTOR(FrictViscoMat,FrictMat,"Material for use with the FrictViscoPM classes", ((Real,betan,0.,,"Fraction of the viscous damping coefficient in normal direction equal to $\\frac{c_{n}}{C_{n,crit}}$.")) , createIndex(); ); DECLARE_LOGGER; REGISTER_CLASS_INDEX(FrictViscoMat,FrictMat); }; REGISTER_SERIALIZABLE(FrictViscoMat); /** This class holds information associated with each interaction */ class FrictViscoPhys: public FrictPhys { public: virtual ~FrictViscoPhys(); YADE_CLASS_BASE_DOC_ATTRS_CTOR_PY(FrictViscoPhys,FrictPhys,"Representation of a single interaction of the FrictViscoPM type, storage for relevant parameters", ((Real,cn_crit,NaN,,"Normal viscous constant for ctitical damping defined as $\\c_{n}=C_{n,crit}\\beta_n$.")) ((Real,cn,NaN,,"Normal viscous constant defined as $\\c_{n}=c_{n,crit}\\beta_n$.")) ((Vector3r,normalViscous,Vector3r::Zero(),,"Normal viscous component")) , createIndex(); , ); DECLARE_LOGGER; REGISTER_CLASS_INDEX(FrictViscoPhys,FrictPhys); }; REGISTER_SERIALIZABLE(FrictViscoPhys); /** 2d functor creating IPhys (Ip2) taking FrictViscoMat and FrictViscoMat of 2 bodies, returning type FrictViscoPhys */ class Ip2_FrictViscoMat_FrictViscoMat_FrictViscoPhys: public IPhysFunctor{ public: virtual void go(const shared_ptr& pp1, const shared_ptr& pp2, const shared_ptr& interaction); FUNCTOR2D(FrictViscoMat,FrictViscoMat); YADE_CLASS_BASE_DOC_ATTRS(Ip2_FrictViscoMat_FrictViscoMat_FrictViscoPhys,IPhysFunctor,"Converts 2 :yref:`FrictViscoMat` instances to :yref:`FrictViscoPhys` with corresponding parameters. Basically this functor corresponds to :yref:`Ip2_FrictMat_FrictMat_FrictPhys` with the only difference that damping in normal direction can be considered.", ((shared_ptr,kn,,,"Instance of :yref:`MatchMaker` determining how to compute interaction's normal contact stiffnesses. If this value is not given the elastic properties (i.e. young) of the two colliding materials are used to calculate the stiffness.")) ((shared_ptr,kRatio,,,"Instance of :yref:`MatchMaker` determining how to compute interaction's shear contact stiffnesses. If this value is not given the elastic properties (i.e. poisson) of the two colliding materials are used to calculate the stiffness.")) ((shared_ptr,frictAngle,,,"Instance of :yref:`MatchMaker` determining how to compute interaction's friction angle. If ``None``, minimum value is used.")) ); DECLARE_LOGGER; }; REGISTER_SERIALIZABLE(Ip2_FrictViscoMat_FrictViscoMat_FrictViscoPhys); /** 2d functor creating IPhys (Ip2) taking FrictMat and FrictViscoMat of 2 bodies, returning type FrictViscoPhys */ class Ip2_FrictMat_FrictViscoMat_FrictViscoPhys: public IPhysFunctor{ public: virtual void go(const shared_ptr& pp1, const shared_ptr& pp2, const shared_ptr& interaction); FUNCTOR2D(FrictMat,FrictViscoMat); DEFINE_FUNCTOR_ORDER_2D(FrictMat,FrictViscoMat); YADE_CLASS_BASE_DOC_ATTRS(Ip2_FrictMat_FrictViscoMat_FrictViscoPhys,IPhysFunctor,"Converts a :yref:`FrictMat` and :yref:`FrictViscoMat` instance to :yref:`FrictViscoPhys` with corresponding parameters. Basically this functor corresponds to :yref:`Ip2_FrictMat_FrictMat_FrictPhys` with the only difference that damping in normal direction can be considered.", ((shared_ptr,kn,,,"Instance of :yref:`MatchMaker` determining how to compute interaction's normal contact stiffnesses. If this value is not given the elastic properties (i.e. young) of the two colliding materials are used to calculate the stiffness.")) ((shared_ptr,kRatio,,,"Instance of :yref:`MatchMaker` determining how to compute interaction's shear contact stiffnesses. If this value is not given the elastic properties (i.e. poisson) of the two colliding materials are used to calculate the stiffness.")) ((shared_ptr,frictAngle,,,"Instance of :yref:`MatchMaker` determining how to compute interaction's friction angle. If ``None``, minimum value is used.")) ); DECLARE_LOGGER; }; REGISTER_SERIALIZABLE(Ip2_FrictMat_FrictViscoMat_FrictViscoPhys); /** 2d functor creating the interaction law (Law2) based on SphereContactGeometry (ScGeom) and FrictViscoPhys of 2 bodies, returning type FrictViscoPM */ class Law2_ScGeom_FrictViscoPhys_CundallStrackVisco: public LawFunctor{ public: OpenMPAccumulator plasticDissipation; virtual bool go(shared_ptr& _geom, shared_ptr& _phys, Interaction* I); Real elasticEnergy (); Real getPlasticDissipation(); void initPlasticDissipation(Real initVal=0); YADE_CLASS_BASE_DOC_ATTRS_CTOR_PY(Law2_ScGeom_FrictViscoPhys_CundallStrackVisco,LawFunctor,"Constitutive law for the FrictViscoPM. Corresponds to :yref:`Law2_ScGeom_FrictPhys_CundallStrack` with the only difference that viscous damping in normal direction can be considered.", ((bool,neverErase,false,,"Keep interactions even if particles go away from each other (only in case another constitutive law is in the scene, e.g. :yref:`Law2_ScGeom_CapillaryPhys_Capillarity`)")) ((bool,sphericalBodies,true,,"If true, compute branch vectors from radii (faster), else use contactPoint-position. Turning this flag true is safe for sphere-sphere contacts and a few other specific cases. It will give wrong values of torques on facets or boxes.")) ((bool,traceEnergy,false,,"Define the total energy dissipated in plastic slips at all contacts. This will trace only plastic energy in this law, see O.trackEnergy for a more complete energies tracing")) ((int,plastDissipIx,-1,(Attr::hidden|Attr::noSave),"Index for plastic dissipation (with O.trackEnergy)")) ((int,elastPotentialIx,-1,(Attr::hidden|Attr::noSave),"Index for elastic potential energy (with O.trackEnergy)")) ,, .def("elasticEnergy",&Law2_ScGeom_FrictViscoPhys_CundallStrackVisco::elasticEnergy,"Compute and return the total elastic energy in all \"FrictViscoPhys\" contacts") .def("plasticDissipation",&Law2_ScGeom_FrictViscoPhys_CundallStrackVisco::getPlasticDissipation,"Total energy dissipated in plastic slips at all FrictPhys contacts. Computed only if :yref:Law2_ScGeom_FrictViscoPhys_CundallStrackVisco::traceEnergy` is true.") .def("initPlasticDissipation",&Law2_ScGeom_FrictViscoPhys_CundallStrackVisco::initPlasticDissipation,"Initialize cummulated plastic dissipation to a value (0 by default).") ); FUNCTOR2D(ScGeom,FrictViscoPhys); DECLARE_LOGGER; }; REGISTER_SERIALIZABLE(Law2_ScGeom_FrictViscoPhys_CundallStrackVisco); trunk-2018.02b/pkg/dem/GeneralIntegratorInsertionSortCollider.cpp000066400000000000000000000224361324306050200250640ustar00rootroot00000000000000// 2009 © Václav Šmilauer #include"GeneralIntegratorInsertionSortCollider.hpp" #include #include #include #include #include #include #include #include #include YADE_PLUGIN((GeneralIntegratorInsertionSortCollider)) CREATE_LOGGER(GeneralIntegratorInsertionSortCollider); // STRIDE bool GeneralIntegratorInsertionSortCollider::isActivated(){ // activated if number of bodies changes (hence need to refresh collision information) // or the time of scheduled run already came, or we were never scheduled yet if(!strideActive) return true; if(!integrator) return true; if(fastestBodyMaxDist<0){fastestBodyMaxDist=0; return true;} fastestBodyMaxDist=integrator->maxVelocitySq; if(fastestBodyMaxDist>=1 || fastestBodyMaxDist==0) return true; if((size_t)BB[0].size!=2*scene->bodies->size()) return true; if(scene->interactions->dirty) return true; if(scene->doSort) { scene->doSort=false; return true; } return false; } void GeneralIntegratorInsertionSortCollider::action(){ #ifdef ISC_TIMING timingDeltas->start(); #endif long nBodies=(long)scene->bodies->size(); InteractionContainer* interactions=scene->interactions.get(); scene->interactions->iterColliderLastRun=-1; // periodicity changed, force reinit if(scene->isPeriodic != periodic){ for(int i=0; i<3; i++) BB[i].vec.clear(); periodic=scene->isPeriodic; } // pre-conditions // adjust storage size bool doInitSort=false; if (doSort) { doInitSort=true; doSort=false; } if(BB[0].size!=2*nBodies){ long BBsize=BB[0].size; LOG_DEBUG("Resize bounds containers from "<200 || BBsize==0) doInitSort=true; assert((BBsize%2)==0); for(int i=0; i<3; i++){ BB[i].vec.reserve(2*nBodies); // add lower and upper bounds; coord is not important, will be updated from bb shortly for(long id=BBsize/2; idbodies->size()); // update periodicity assert(BB[0].axis==0); assert(BB[1].axis==1); assert(BB[2].axis==2); if(periodic) for(int i=0; i<3; i++) BB[i].updatePeriodicity(scene); if(verletDist<0){ Real minR=std::numeric_limits::infinity(); FOREACH(const shared_ptr& b, *scene->bodies){ if(!b || !b->shape) continue; Sphere* s=dynamic_cast(b->shape.get()); if(!s) continue; minR=min(s->radius,minR); } if (std::isinf(minR)) LOG_ERROR("verletDist is set to 0 because no spheres were found. It will result in suboptimal performances, consider setting a positive verletDist in your script."); // if no spheres, disable stride verletDist=std::isinf(minR) ? 0 : std::abs(verletDist)*minR; } // update bounds via boundDispatcher boundDispatcher->scene=scene; boundDispatcher->sweepDist=verletDist; boundDispatcher->minSweepDistFactor=minSweepDistFactor; boundDispatcher->targetInterv=targetInterv; boundDispatcher->updatingDispFactor=updatingDispFactor; boundDispatcher->action(); // if interactions are dirty, force reinitialization if(scene->interactions->dirty){ doInitSort=true; scene->interactions->dirty=false; } // STRIDE if(verletDist>0){ // get the Integrator, to ask for the maximum velocity value if(!integrator){ FOREACH(shared_ptr& e, scene->engines){ integrator=YADE_PTR_DYN_CAST(e); if(integrator) break; } if(!integrator){ throw runtime_error("InsertionSortCollider.verletDist>0, but unable to locate any Integrator within O.engines."); } } } ISC_CHECKPOINT("init"); // STRIDE // get us ready for strides, if they were deactivated if(!strideActive && verletDist>0 && integrator->maxVelocitySq>=0){ // maxVelocitySq is a really computed value strideActive=true; } if(strideActive){ assert(verletDist>0); assert(strideActive); assert(integrator->maxVelocitySq>=0); integrator->updatingDispFactor=updatingDispFactor; } else { /* !strideActive */ boundDispatcher->sweepDist=0; } ISC_CHECKPOINT("bound"); // copy bounds along given axis into our arrays for(long i=0; i<2*nBodies; i++){ for(int j=0; j<3; j++){ VecBounds& BBj=BB[j]; const Body::id_t id=BBj[i].id; const shared_ptr& b=Body::byId(id,scene); if(b){ const shared_ptr& bv=b->bound; // coordinate is min/max if has bounding volume, otherwise both are the position. Add periodic shift so that we are inside the cell // watch out for the parentheses around ?: within ?: (there was unwanted conversion of the Reals to bools!) BBj[i].coord=((BBj[i].flags.hasBB=((bool)bv)) ? (BBj[i].flags.isMin ? bv->min[j] : bv->max[j]) : (b->state->pos[j])) - (periodic ? BBj.cellDim*BBj[i].period : 0.); } else { BBj[i].flags.hasBB=false; /* for vanished body, keep the coordinate as-is, to minimize inversions. */ } // if initializing periodic, shift coords & record the period into BBj[i].period if(doInitSort && periodic) { BBj[i].coord=cellWrap(BBj[i].coord,0,BBj.cellDim,BBj[i].period); } } } // for each body, copy its minima and maxima, for quick checks of overlaps later BOOST_STATIC_ASSERT(sizeof(Vector3r)==3*sizeof(Real)); for(Body::id_t id=0; id& b=Body::byId(id,scene); if(b){ const shared_ptr& bv=b->bound; if(bv) { memcpy(&minima[3*id],&bv->min,3*sizeof(Real)); memcpy(&maxima[3*id],&bv->max,3*sizeof(Real)); } // ⇐ faster than 6 assignments else{ const Vector3r& pos=b->state->pos; memcpy(&minima[3*id],&pos,3*sizeof(Real)); memcpy(&maxima[3*id],&pos,3*sizeof(Real)); } } else { memset(&minima[3*id],0,3*sizeof(Real)); memset(&maxima[3*id],0,3*sizeof(Real)); } } ISC_CHECKPOINT("copy"); // process interactions that the constitutive law asked to be erased // interactions->erasePending(*this,scene); interactions->conditionalyEraseNonReal(*this,scene); ISC_CHECKPOINT("erase"); // sort // the regular case if(!doInitSort && !sortThenCollide){ /* each inversion in insertionSort calls handleBoundInversion, which in turns may add/remove interaction */ if(!periodic) for(int i=0; i<3; i++) insertionSort(BB[i],interactions,scene); else for(int i=0; i<3; i++) insertionSortPeri(BB[i],interactions,scene); } // create initial interactions (much slower) else { if(doInitSort){ // the initial sort is in independent in 3 dimensions, may be run in parallel; it seems that there is no time gain running in parallel, though // important to reset loInx for periodic simulation (!!) for(int i=0; i<3; i++) { BB[i].loIdx=0; std::sort(BB[i].vec.begin(),BB[i].vec.end()); } numReinit++; } else { // sortThenCollide if(!periodic) for(int i=0; i<3; i++) insertionSort(BB[i],interactions,scene,false); else for(int i=0; i<3; i++) insertionSortPeri(BB[i],interactions,scene,false); } // traverse the container along requested axis assert(sortAxis==0 || sortAxis==1 || sortAxis==2); VecBounds& V=BB[sortAxis]; // go through potential aabb collisions, create interactions as necessary if(!periodic){ for(long i=0; i<2*nBodies; i++){ // start from the lower bound (i.e. skipping upper bounds) // skip bodies without bbox, because they don't collide if(!(V[i].flags.isMin && V[i].flags.hasBB)) continue; const Body::id_t& iid=V[i].id; // go up until we meet the upper bound for(long j=i+1; /* handle case 2. of swapped min/max */ j<2*nBodies && V[j].id!=iid; j++){ const Body::id_t& jid=V[j].id; // take 2 of the same condition (only handle collision [min_i..max_i]+min_j, not [min_i..max_i]+min_i (symmetric) if(!V[j].flags.isMin) continue; /* abuse the same function here; since it does spatial overlap check first, it is OK to use it */ handleBoundInversion(iid,jid,interactions,scene); assert(j<2*nBodies-1); } } } else { // periodic case: see comments above for(long i=0; i<2*nBodies; i++){ if(!(V[i].flags.isMin && V[i].flags.hasBB)) continue; const Body::id_t& iid=V[i].id; long cnt=0; // we might wrap over the periodic boundary here; that's why the condition is different from the aperiodic case for(long j=V.norm(i+1); V[j].id!=iid; j=V.norm(j+1)){ const Body::id_t& jid=V[j].id; if(!V[j].flags.isMin) continue; handleBoundInversionPeri(iid,jid,interactions,scene); if(cnt++>2*(long)nBodies){ LOG_FATAL("Uninterrupted loop in the initial sort?"); throw std::logic_error("loop??"); } } } } } ISC_CHECKPOINT("sort&collide"); } trunk-2018.02b/pkg/dem/GeneralIntegratorInsertionSortCollider.hpp000066400000000000000000000023011324306050200250560ustar00rootroot00000000000000// 2014 Burak ER #pragma once #include /*! Adaptive Integration Sort Collider: Changing the Integrator dependence from Newton Integrator to Arbitrary Integrators. Arbitrary integrators should use the Integrator interface. */ #ifdef ISC_TIMING #define ISC_CHECKPOINT(cpt) timingDeltas->checkpoint(cpt) #else #define ISC_CHECKPOINT(cpt) #endif class Integrator; class GeneralIntegratorInsertionSortCollider: public InsertionSortCollider{ // we need this to find out about current maxVelocitySq shared_ptr integrator; // if False, no type of striding is used public: virtual bool isActivated(); //override this function to change NewtonIntegrator dependency. virtual void action(); //override this function to change behaviour with the NewtonIntegrator dependency. YADE_CLASS_BASE_DOC(GeneralIntegratorInsertionSortCollider,InsertionSortCollider," This class is the adaptive version of the InsertionSortCollider and changes the NewtonIntegrator dependency of the collider algorithms to the Integrator interface which is more general."); DECLARE_LOGGER; }; REGISTER_SERIALIZABLE(GeneralIntegratorInsertionSortCollider); trunk-2018.02b/pkg/dem/GlobalStiffnessTimeStepper.cpp000066400000000000000000000227221324306050200224740ustar00rootroot00000000000000/************************************************************************* * Copyright (C) 2006 by Bruno Chareyre * * bruno.chareyre@hmg.inpg.fr * * * * This program is free software; it is licensed under the terms of the * * GNU General Public License v2 or later. See file LICENSE for details. * *************************************************************************/ #include"GlobalStiffnessTimeStepper.hpp" #include #include #include #include #include #include #include #include CREATE_LOGGER(GlobalStiffnessTimeStepper); YADE_PLUGIN((GlobalStiffnessTimeStepper)); GlobalStiffnessTimeStepper::~GlobalStiffnessTimeStepper() {} void GlobalStiffnessTimeStepper::findTimeStepFromBody(const shared_ptr& body, Scene * ncb) { State* sdec=body->state.get(); Vector3r& stiffness= stiffnesses[body->getId()]; Vector3r& Rstiffness=Rstiffnesses[body->getId()]; if(body->isClump()) {// if clump, we sum stifnesses of all members const shared_ptr& clump=YADE_PTR_CAST(body->shape); FOREACH(Clump::MemberMap::value_type& B, clump->members){ const shared_ptr& b = Body::byId(B.first,scene); stiffness+=stiffnesses[b->getId()]; Rstiffness+=Rstiffnesses[b->getId()]; if (viscEl == true){ viscosities[body->getId()]+=viscosities[b->getId()]; Rviscosities[body->getId()]+=Rviscosities[b->getId()]; } } } if(!sdec || stiffness==Vector3r::Zero()){ if (densityScaling) { if (sdec->densityScaling<=0) sdec->densityScaling = timestepSafetyCoefficient*pow(defaultDt/targetDt,2.0); else sdec->densityScaling = max(0.99*sdec->densityScaling, timestepSafetyCoefficient*pow(defaultDt/targetDt,2.0));} return; // not possible to compute! } //Determine the elastic minimum eigenperiod (and if required determine also the viscous one separately and take the minimum of the two) //Elastic Real dtx, dty, dtz; Real dt = max( max (stiffness.x(), stiffness.y()), stiffness.z() ); if (dt!=0) { dt = sdec->mass/dt; computedSomething = true;}//dt = squared eigenperiod of translational motion else dt = Mathr::MAX_REAL; if (Rstiffness.x()!=0) { dtx = sdec->inertia.x()/Rstiffness.x(); computedSomething = true;}//dtx = squared eigenperiod of rotational motion around x else dtx = Mathr::MAX_REAL; if (Rstiffness.y()!=0) { dty = sdec->inertia.y()/Rstiffness.y(); computedSomething = true;} else dty = Mathr::MAX_REAL; if (Rstiffness.z()!=0) { dtz = sdec->inertia.z()/Rstiffness.z(); computedSomething = true;} else dtz = Mathr::MAX_REAL; Real Rdt = std::min( std::min (dtx, dty), dtz );//Rdt = smallest squared eigenperiod for elastic rotational motions dt = 1.41044*timestepSafetyCoefficient*std::sqrt(std::min(dt,Rdt));//1.41044 = sqrt(2) //Viscous if (viscEl == true){ Vector3r& viscosity = viscosities[body->getId()]; Vector3r& Rviscosity = Rviscosities[body->getId()]; Real dtx_visc, dty_visc, dtz_visc; Real dt_visc = max(max(viscosity.x(), viscosity.y()), viscosity.z() ); if (dt_visc!=0) { dt_visc = sdec->mass/dt_visc; computedSomething = true;}//dt = eigenperiod of the viscous translational motion else {dt_visc = Mathr::MAX_REAL;} if (Rviscosity.x()!=0) { dtx_visc = sdec->inertia.x()/Rviscosity.x(); computedSomething = true;}//dtx = eigenperiod of viscous rotational motion around x else dtx_visc = Mathr::MAX_REAL; if (Rviscosity.y()!=0) { dty_visc = sdec->inertia.y()/Rviscosity.y(); computedSomething = true;} else dty_visc = Mathr::MAX_REAL; if (Rviscosity.z()!=0) { dtz_visc = sdec->inertia.z()/Rviscosity.z(); computedSomething = true;} else dtz_visc = Mathr::MAX_REAL; Real Rdt_visc = std::min( std::min (dtx_visc, dty_visc), dtz_visc );//Rdt = smallest squared eigenperiod for viscous rotational motions dt_visc = 2*timestepSafetyCoefficient*std::min(dt_visc,Rdt_visc); //Take the minimum between the elastic and viscous minimum eigenperiod. dt = std::min(dt,dt_visc); } //if there is a target dt, then we apply density scaling on the body, the inertia used in Newton will be mass/scaling, the weight is unmodified if (densityScaling) { sdec->densityScaling = min(sdec->densityScaling*1.01, pow(dt /targetDt,2.0)); newDt=targetDt; } //else we update dt normaly else {newDt = std::min(dt,newDt);} } bool GlobalStiffnessTimeStepper::isActivated() { return (active && ((!computedOnce) || (scene->iter % timeStepUpdateInterval == 0) || (scene->iter < (long int) 2) )); } void GlobalStiffnessTimeStepper::computeTimeStep(Scene* ncb) { // for some reason, this line is necessary to have correct functioning (no idea _why_) // see scripts/test/compare-identical.py, run with or without active=active. active=active; if (defaultDt<0) defaultDt= timestepSafetyCoefficient*Shop::PWaveTimeStep(Omega::instance().getScene()); computeStiffnesses(ncb); shared_ptr& bodies = ncb->bodies; newDt = Mathr::MAX_REAL; computedSomething=false; BodyContainer::iterator bi = bodies->begin(); BodyContainer::iterator biEnd = bodies->end(); for( ; bi!=biEnd ; ++bi ){ shared_ptr b = *bi; if (b->isDynamic() && !b->isClumpMember()) findTimeStepFromBody(b, ncb); } if(densityScaling) (newDt=targetDt); if(computedSomething || densityScaling){ previousDt = min ( min(newDt , maxDt), 1.05*previousDt );// at maximum, dt will be multiplied by 1.05 in one iterration, this is to prevent brutal switches from 0.000... to 1 in some computations scene->dt=previousDt; computedOnce = true;} else if (!computedOnce) scene->dt=defaultDt; // LOG_INFO("computed timestep " << newDt << // (scene->dt==newDt ? string(", applied") : // string(", BUT timestep is ")+boost::lexical_cast(scene->dt))<<"."); } void GlobalStiffnessTimeStepper::computeStiffnesses(Scene* rb){ /* check size */ size_t size=stiffnesses.size(); if(sizebodies->size()){ size=rb->bodies->size(); stiffnesses.resize(size); Rstiffnesses.resize(size); if (viscEl == true){ viscosities.resize(size); Rviscosities.resize(size); } } /* reset stored values */ memset(& stiffnesses[0],0,sizeof(Vector3r)*size); memset(&Rstiffnesses[0],0,sizeof(Vector3r)*size); if (viscEl == true){ memset(& viscosities[0],0,sizeof(Vector3r)*size); memset(&Rviscosities[0],0,sizeof(Vector3r)*size); } FOREACH(const shared_ptr& contact, *rb->interactions){ if(!contact->isReal()) continue; GenericSpheresContact* geom=YADE_CAST(contact->geom.get()); assert(geom); NormShearPhys* phys=YADE_CAST(contact->phys.get()); assert(phys); // all we need for getting stiffness Vector3r& normal=geom->normal; Real& kn=phys->kn; Real& ks=phys->ks; Real& radius1=geom->refR1; Real& radius2=geom->refR2; Real fn = (static_cast (contact->phys.get()))->normalForce.squaredNorm(); if (fn==0) continue;//Is it a problem with some laws? I still don't see why. //Diagonal terms of the translational stiffness matrix Vector3r diag_stiffness = Vector3r(std::pow(normal.x(),2),std::pow(normal.y(),2),std::pow(normal.z(),2)); diag_stiffness *= kn-ks; diag_stiffness = diag_stiffness + Vector3r(1,1,1)*ks; //diagonal terms of the rotational stiffness matrix // Vector3r branch1 = currentContactGeometry->normal*currentContactGeometry->radius1; // Vector3r branch2 = currentContactGeometry->normal*currentContactGeometry->radius2; Vector3r diag_Rstiffness = Vector3r(std::pow(normal.y(),2)+std::pow(normal.z(),2), std::pow(normal.x(),2)+std::pow(normal.z(),2), std::pow(normal.x(),2)+std::pow(normal.y(),2)); diag_Rstiffness *= ks; // If contact moments are present, add the diagonal of (n⊗n*k_twist + (I-n⊗n)*k_roll = (k_twist-k_roll)*n⊗n + I*k_roll ) : Vector3r kr = (static_cast (contact->phys.get()))->getRotStiffness(); //get the vector (k_twist,k_roll,k_roll) Vector3r nn (std::pow(normal.x(),2),std::pow(normal.y(),2),std::pow(normal.z(),2));//n⊗n Vector3r diag_Mstiffness= (kr[0]-kr[1])*nn + Vector3r(1,1,1)*kr[1]; stiffnesses [contact->getId1()]+=diag_stiffness; Rstiffnesses[contact->getId1()]+=diag_Rstiffness*pow(radius1,2)+ diag_Mstiffness; stiffnesses [contact->getId2()]+=diag_stiffness; Rstiffnesses[contact->getId2()]+=diag_Rstiffness*pow(radius2,2)+ diag_Mstiffness; //Same for the Viscous part, if required if (viscEl == true){ ViscElPhys* viscPhys = YADE_CAST(contact->phys.get()); assert(viscPhys); Real& cn=viscPhys->cn; Real& cs=viscPhys->cs; //Diagonal terms of the translational viscous matrix Vector3r diag_viscosity = Vector3r(std::pow(normal.x(),2),std::pow(normal.y(),2),std::pow(normal.z(),2)); diag_viscosity *= cn-cs; diag_viscosity = diag_viscosity + Vector3r(1,1,1)*cs; //diagonal terms of the rotational viscous matrix Vector3r diag_Rviscosity = Vector3r(std::pow(normal.y(),2)+std::pow(normal.z(),2), std::pow(normal.x(),2)+std::pow(normal.z(),2), std::pow(normal.x(),2)+std::pow(normal.y(),2)); diag_Rviscosity *= cs; // Add the contact stiffness matrix to the two particles one viscosities [contact->getId1()]+=diag_viscosity; Rviscosities[contact->getId1()]+=diag_Rviscosity*pow(radius1,2); viscosities [contact->getId2()]+=diag_viscosity; Rviscosities[contact->getId2()]+=diag_Rviscosity*pow(radius2,2); } } } trunk-2018.02b/pkg/dem/GlobalStiffnessTimeStepper.hpp000066400000000000000000000070611324306050200225000ustar00rootroot00000000000000/************************************************************************* * Copyright (C) 2006 by Bruno Chareyre * * bruno.chareyre@hmg.inpg.fr * * * * This program is free software; it is licensed under the terms of the * * GNU General Public License v2 or later. See file LICENSE for details. * *************************************************************************/ #pragma once #include /*! \brief Compute the critical timestep of the leap-frog scheme based on global stiffness of bodies See usage details in TriaxialTest documentation (TriaxialTest is also a good example of how to use this class) */ class Interaction; class BodyContainer; class Scene; class GlobalStiffnessTimeStepper : public TimeStepper { private : vector stiffnesses; vector Rstiffnesses; vector viscosities; vector Rviscosities; void computeStiffnesses(Scene*); Real newDt; bool computedSomething, computedOnce; void findTimeStepFromBody(const shared_ptr& body, Scene * ncb); public : virtual ~GlobalStiffnessTimeStepper(); virtual void computeTimeStep(Scene*); virtual bool isActivated(); YADE_CLASS_BASE_DOC_ATTRS_CTOR( GlobalStiffnessTimeStepper,TimeStepper,"An engine assigning the time-step as a fraction of the minimum eigen-period in the problem. The derivation is detailed in the chapter on `DEM formulation `_. The viscEl option enables to evaluate the timestep in a similar way for the visco-elastic contact law :yref:`Law2_ScGeom_ViscElPhys_Basic`, more detail in :yref:`GlobalStiffnessTimestepper::viscEl`. ", ((Real,defaultDt,-1,,"used as the initial value of the timestep (especially useful in the first steps when no contact exist). If negative, it will be defined by :yref:`utils.PWaveTimeStep` * :yref:`GlobalStiffnessTimeStepper::timestepSafetyCoefficient`")) ((Real,maxDt,Mathr::MAX_REAL,,"if positive, used as max value of the timestep whatever the computed value")) ((Real,previousDt,1,,"last computed dt |yupdate|")) ((Real,timestepSafetyCoefficient,0.8,,"safety factor between the minimum eigen-period and the final assigned dt (less than 1)")) ((bool,densityScaling,false,,"|yupdate| don't modify this value if you don't plan to modify the scaling factor manually for some bodies. In most cases, it is enough to set :yref:`NewtonIntegrator::densityScaling` and let this one be adjusted automatically.")) ((Real,targetDt,1,,"if :yref:`NewtonIntegrator::densityScaling` is active, this value will be used as the simulation timestep and the scaling will use this value of dt as the target value. The value of targetDt is arbitrary and should have no effect in the result in general. However if some bodies have imposed velocities, for instance, they will move more or less per each step depending on this value.")) ((bool,viscEl,false,,"To use with :yref:`ViscElPhys`. if True, evaluate separetly the minimum eigen-period in the problem considering only the elastic contribution on one hand (spring only), and only the viscous contribution on the other hand (dashpot only). Take then the minimum of the two and use the safety coefficient :yref:`GlobalStiffnessTimestepper::timestepSafetyCoefficient` to take into account the possible coupling between the two contribution.")), computedOnce=false;) DECLARE_LOGGER; }; REGISTER_SERIALIZABLE(GlobalStiffnessTimeStepper); trunk-2018.02b/pkg/dem/HertzMindlin.cpp000066400000000000000000000670271324306050200176430ustar00rootroot00000000000000// 2010 © Chiara Modenese #include"HertzMindlin.hpp" #include #include #include YADE_PLUGIN( (MindlinPhys) (Ip2_FrictMat_FrictMat_MindlinPhys) (Law2_ScGeom_MindlinPhys_MindlinDeresiewitz) (Law2_ScGeom_MindlinPhys_HertzWithLinearShear) (Law2_ScGeom_MindlinPhys_Mindlin) (MindlinCapillaryPhys) (Ip2_FrictMat_FrictMat_MindlinCapillaryPhys) ); Real Law2_ScGeom_MindlinPhys_Mindlin::getfrictionDissipation() {return (Real) frictionDissipation;} Real Law2_ScGeom_MindlinPhys_Mindlin::getshearEnergy() {return (Real) shearEnergy;} Real Law2_ScGeom_MindlinPhys_Mindlin::getnormDampDissip() {return (Real) normDampDissip;} Real Law2_ScGeom_MindlinPhys_Mindlin::getshearDampDissip() {return (Real) shearDampDissip;} /******************** Ip2_FrictMat_FrictMat_MindlinPhys *******/ CREATE_LOGGER(Ip2_FrictMat_FrictMat_MindlinPhys); void Ip2_FrictMat_FrictMat_MindlinPhys::go(const shared_ptr& b1,const shared_ptr& b2, const shared_ptr& interaction){ if(interaction->phys) return; // no updates of an already existing contact necessary shared_ptr contactPhysics(new MindlinPhys()); interaction->phys = contactPhysics; FrictMat* mat1 = YADE_CAST(b1.get()); FrictMat* mat2 = YADE_CAST(b2.get()); /* from interaction physics */ Real Ea = mat1->young; Real Eb = mat2->young; Real Va = mat1->poisson; Real Vb = mat2->poisson; Real fa = mat1->frictionAngle; Real fb = mat2->frictionAngle; /* from interaction geometry */ GenericSpheresContact* scg = YADE_CAST(interaction->geom.get()); Real Da = scg->refR1>0 ? scg->refR1 : scg->refR2; Real Db = scg->refR2; //Vector3r normal=scg->normal; //The variable set but not used /* calculate stiffness coefficients */ Real Ga = Ea/(2*(1+Va)); Real Gb = Eb/(2*(1+Vb)); Real G = (Ga+Gb)/2; // average of shear modulus Real V = (Va+Vb)/2; // average of poisson's ratio Real E = Ea*Eb/((1.-std::pow(Va,2))*Eb+(1.-std::pow(Vb,2))*Ea); // Young modulus Real R = Da*Db/(Da+Db); // equivalent radius Real Rmean = (Da+Db)/2.; // mean radius Real Kno = 4./3.*E*sqrt(R); // coefficient for normal stiffness Real Kso = 2*sqrt(4*R)*G/(2-V); // coefficient for shear stiffness Real frictionAngle = (!frictAngle) ? std::min(fa,fb) : (*frictAngle)(mat1->id,mat2->id,mat1->frictionAngle,mat2->frictionAngle); Real Adhesion = 4.*Mathr::PI*R*gamma; // calculate adhesion force as predicted by DMT theory /* pass values calculated from above to MindlinPhys */ contactPhysics->tangensOfFrictionAngle = std::tan(frictionAngle); //contactPhysics->prevNormal = scg->normal; // used to compute relative rotation contactPhysics->kno = Kno; // this is just a coeff contactPhysics->kso = Kso; // this is just a coeff contactPhysics->adhesionForce = Adhesion; contactPhysics->kr = krot; contactPhysics->ktw = ktwist; contactPhysics->maxBendPl = eta*Rmean; // does this make sense? why do we take Rmean? /* compute viscous coefficients */ if(en && betan) throw std::invalid_argument("Ip2_FrictMat_FrictMat_MindlinPhys: only one of en, betan can be specified."); if(es && betas) throw std::invalid_argument("Ip2_FrictMat_FrictMat_MindlinPhys: only one of es, betas can be specified."); // en or es specified, just compute alpha, otherwise alpha remains 0 if(en || es){ Real logE = log((*en)(mat1->id,mat2->id)); contactPhysics->alpha = -sqrt(5/6.)*2*logE/sqrt(pow(logE,2)+pow(Mathr::PI,2))*sqrt(2*E*sqrt(R)); // (see Tsuji, 1992), also [Antypov2011] eq. 17 } // betan specified, use that value directly; otherwise give zero else{ contactPhysics->betan=betan ? (*betan)(mat1->id,mat2->id) : 0; contactPhysics->betas=betas ? (*betas)(mat1->id,mat2->id) : contactPhysics->betan; } } /* Function to count the number of adhesive contacts in the simulation at each time step */ Real Law2_ScGeom_MindlinPhys_Mindlin::contactsAdhesive() // It is returning something rather than zero only if includeAdhesion is set to true { Real contactsAdhesive=0; FOREACH(const shared_ptr& I, *scene->interactions){ if(!I->isReal()) continue; MindlinPhys* phys = dynamic_cast(I->phys.get()); if (phys->isAdhesive) {contactsAdhesive += 1;} } return contactsAdhesive; } /* Function which returns the ratio between the number of sliding contacts to the total number at a given time */ Real Law2_ScGeom_MindlinPhys_Mindlin::ratioSlidingContacts() { Real ratio(0); int count(0); FOREACH(const shared_ptr& I, *scene->interactions){ if(!I->isReal()) continue; MindlinPhys* phys = dynamic_cast(I->phys.get()); if (phys->isSliding) {ratio+=1;} count++; } ratio/=count; return ratio; } /* Function to get the NORMAL elastic potential energy of the system */ Real Law2_ScGeom_MindlinPhys_Mindlin::normElastEnergy() { Real normEnergy=0; FOREACH(const shared_ptr& I, *scene->interactions){ if(!I->isReal()) continue; ScGeom* scg = dynamic_cast(I->geom.get()); MindlinPhys* phys = dynamic_cast(I->phys.get()); if (phys) { if (includeAdhesion) {normEnergy += (std::pow(scg->penetrationDepth,5./2.)*2./5.*phys->kno - phys->adhesionForce*scg->penetrationDepth);} else {normEnergy += std::pow(scg->penetrationDepth,5./2.)*2./5.*phys->kno;} // work done in the normal direction. NOTE: this is the integral } } return normEnergy; } /* Function to get the adhesion energy of the system */ Real Law2_ScGeom_MindlinPhys_Mindlin::adhesionEnergy() { Real adhesionEnergy=0; FOREACH(const shared_ptr& I, *scene->interactions){ if(!I->isReal()) continue; ScGeom* scg = dynamic_cast(I->geom.get()); MindlinPhys* phys = dynamic_cast(I->phys.get()); if (phys && includeAdhesion) { Real R=scg->radius1*scg->radius2/(scg->radius1+scg->radius2); Real gammapi=phys->adhesionForce/(4.*R); adhesionEnergy += gammapi*pow(phys->radius,2);} // note that contact radius is calculated if we calculate energy components } return adhesionEnergy; } bool Law2_ScGeom_MindlinPhys_MindlinDeresiewitz::go(shared_ptr& ig, shared_ptr& ip, Interaction* contact){ Body::id_t id1(contact->getId1()), id2(contact->getId2()); ScGeom* geom = static_cast(ig.get()); MindlinPhys* phys=static_cast(ip.get()); const Real uN=geom->penetrationDepth; if (uN<0) { if (neverErase) {phys->shearForce = phys->normalForce = Vector3r::Zero(); phys->kn=phys->ks=0; return true;} else {return false;} } // normal force Real Fn=phys->kno*pow(uN,3/2.); phys->normalForce=Fn*geom->normal; // exactly zero would not work with the shear formulation, and would give zero shear force anyway if(Fn==0) return true; //phys->kn=3./2.*phys->kno*std::pow(uN,0.5); // update stiffness, not needed // contact radius Real R=geom->radius1*geom->radius2/(geom->radius1+geom->radius2); phys->radius=pow(Fn*pow(R,3/2.)/phys->kno,1/3.); // shear force: transform, but keep the old value for now geom->rotate(phys->usTotal); //Vector3r usOld=phys->usTotal; //The variable set but not used Vector3r dUs=geom->shearIncrement(); phys->usTotal-=dUs; #if 0 Vector3r shearIncrement; shearIncrement=geom->shearIncrement(); Fs-=ks*shearIncrement; // Mohr-Coulomb slip Real maxFs2=pow(Fn,2)*pow(phys->tangensOfFrictionAngle,2); if(Fs.squaredNorm()>maxFs2) Fs*=sqrt(maxFs2)/Fs.norm(); #endif // apply forces Vector3r f=-phys->normalForce-phys->shearForce; scene->forces.addForce(id1,f); scene->forces.addForce(id2,-f); scene->forces.addTorque(id1,(geom->radius1-.5*geom->penetrationDepth)*geom->normal.cross(f)); scene->forces.addTorque(id2,(geom->radius2-.5*geom->penetrationDepth)*geom->normal.cross(f)); return true; } bool Law2_ScGeom_MindlinPhys_HertzWithLinearShear::go(shared_ptr& ig, shared_ptr& ip, Interaction* contact){ Body::id_t id1(contact->getId1()), id2(contact->getId2()); ScGeom* geom = static_cast(ig.get()); MindlinPhys* phys=static_cast(ip.get()); const Real uN=geom->penetrationDepth; if (uN<0) { if (neverErase) {phys->shearForce = phys->normalForce = Vector3r::Zero(); phys->kn=phys->ks=0; return true;} else return false; } // normal force Real Fn=phys->kno*pow(uN,3/2.); phys->normalForce=Fn*geom->normal; //phys->kn=3./2.*phys->kno*std::pow(uN,0.5); // update stiffness, not needed // shear force Vector3r& Fs=geom->rotate(phys->shearForce); Real ks= nonLin>0 ? phys->kso*std::pow(uN,0.5) : phys->kso; Vector3r shearIncrement; if(nonLin>1){ State *de1=Body::byId(id1,scene)->state.get(), *de2=Body::byId(id2,scene)->state.get(); Vector3r shiftVel=scene->isPeriodic ? Vector3r(scene->cell->velGrad*scene->cell->hSize*contact->cellDist.cast()) : Vector3r::Zero(); Vector3r shift2 = scene->isPeriodic ? Vector3r(scene->cell->hSize*contact->cellDist.cast()): Vector3r::Zero(); Vector3r incidentV = geom->getIncidentVel(de1, de2, scene->dt, shift2, shiftVel, /*preventGranularRatcheting*/ nonLin>2 ); Vector3r incidentVn = geom->normal.dot(incidentV)*geom->normal; // contact normal velocity Vector3r incidentVs = incidentV-incidentVn; // contact shear velocity shearIncrement=incidentVs*scene->dt; } else { shearIncrement=geom->shearIncrement(); } Fs-=ks*shearIncrement; // Mohr-Coulomb slip Real maxFs2=pow(Fn,2)*pow(phys->tangensOfFrictionAngle,2); if(Fs.squaredNorm()>maxFs2) Fs*=sqrt(maxFs2)/Fs.norm(); // apply forces Vector3r f=-phys->normalForce-phys->shearForce; /* should be a reference returned by geom->rotate */ assert(phys->shearForce==Fs); scene->forces.addForce(id1,f); scene->forces.addForce(id2,-f); scene->forces.addTorque(id1,(geom->radius1-.5*geom->penetrationDepth)*geom->normal.cross(f)); scene->forces.addTorque(id2,(geom->radius2-.5*geom->penetrationDepth)*geom->normal.cross(f)); return true; } /******************** Law2_ScGeom_MindlinPhys_Mindlin *********/ CREATE_LOGGER(Law2_ScGeom_MindlinPhys_Mindlin); bool Law2_ScGeom_MindlinPhys_Mindlin::go(shared_ptr& ig, shared_ptr& ip, Interaction* contact){ const Real& dt = scene->dt; // get time step Body::id_t id1 = contact->getId1(); // get id body 1 Body::id_t id2 = contact->getId2(); // get id body 2 State* de1 = Body::byId(id1,scene)->state.get(); State* de2 = Body::byId(id2,scene)->state.get(); ScGeom* scg = static_cast(ig.get()); MindlinPhys* phys = static_cast(ip.get()); const shared_ptr& b1=Body::byId(id1,scene); const shared_ptr& b2=Body::byId(id2,scene); bool useDamping=(phys->betan!=0. || phys->betas!=0. || phys->alpha!=0.); bool LinDamp=true; if (phys->alpha!=0.) {LinDamp=false;} // use non linear damping // tangential and normal stiffness coefficients, recomputed from betan,betas at every step Real cn=0, cs=0; /****************/ /* NORMAL FORCE */ /****************/ Real uN = scg->penetrationDepth; // get overlapping if (uN<0) { if (neverErase) {phys->shearForce = phys->normalForce = Vector3r::Zero(); phys->kn=phys->ks=0; return true;} else return false; } /* Hertz-Mindlin's formulation (PFC) Note that the normal stiffness here is a secant value (so as it is cannot be used in the GSTS) In the first place we get the normal force and then we store kn to be passed to the GSTS */ Real Fn = phys->kno*std::pow(uN,1.5); // normal Force (scalar) if (includeAdhesion) { Fn -= phys->adhesionForce; // include adhesion force to account for the effect of Van der Waals interactions phys->isAdhesive = (Fn<0); // set true the bool to count the number of adhesive contacts } phys->normalForce = Fn*scg->normal; // normal Force (vector) if (calcEnergy){ Real R=scg->radius1*scg->radius2/(scg->radius1+scg->radius2); phys->radius=pow((Fn+(includeAdhesion?phys->adhesionForce:0.))*pow(R,3/2.)/phys->kno,1/3.); // attribute not used anywhere, we do not need it } /*******************************/ /* TANGENTIAL NORMAL STIFFNESS */ /*******************************/ phys->kn = 3./2.*phys->kno*std::pow(uN,0.5); // here we store the value of kn to compute the time step /******************************/ /* TANGENTIAL SHEAR STIFFNESS */ /******************************/ phys->ks = phys->kso*std::pow(uN,0.5); // get tangential stiffness (this is a tangent value, so we can pass it to the GSTS) /************************/ /* DAMPING COEFFICIENTS */ /************************/ // Inclusion of local damping if requested // viscous damping is defined for both linear and non-linear elastic case if (useDamping && LinDamp){ Real mbar = (!b1->isDynamic() && b2->isDynamic()) ? de2->mass : ((!b2->isDynamic() && b1->isDynamic()) ? de1->mass : (de1->mass*de2->mass / (de1->mass + de2->mass))); // get equivalent mass if both bodies are dynamic, if not set it equal to the one of the dynamic body //Real mbar = de1->mass*de2->mass / (de1->mass + de2->mass); // equivalent mass Real Cn_crit = 2.*sqrt(mbar*phys->kn); // Critical damping coefficient (normal direction) Real Cs_crit = 2.*sqrt(mbar*phys->ks); // Critical damping coefficient (shear direction) // Note: to compare with the analytical solution you provide cn and cs directly (since here we used a different method to define c_crit) cn = Cn_crit*phys->betan; // Damping normal coefficient cs = Cs_crit*phys->betas; // Damping tangential coefficient if(phys->kn<0 || phys->ks<0){ cerr<<"Negative stiffness kn="<kn<<" ks="<ks<<" for ##"<getId()<<"+"<getId()<<", step "<iter<isDynamic() && b2->isDynamic()) ? de2->mass : ((!b2->isDynamic() && b1->isDynamic()) ? de1->mass : (de1->mass*de2->mass / (de1->mass + de2->mass))); // get equivalent mass if both bodies are dynamic, if not set it equal to the one of the dynamic body cn = phys->alpha*sqrt(mbar)*pow(uN,0.25); // normal viscous coefficient, see also [Antypov2011] eq. 10 cs = cn; // same value for shear viscous coefficient } /***************/ /* SHEAR FORCE */ /***************/ Vector3r& shearElastic = phys->shearElastic; // reference for shearElastic force // Define shifts to handle periodicity const Vector3r shift2 = scene->isPeriodic ? scene->cell->intrShiftPos(contact->cellDist): Vector3r::Zero(); const Vector3r shiftVel = scene->isPeriodic ? scene->cell->intrShiftVel(contact->cellDist): Vector3r::Zero(); // 1. Rotate shear force shearElastic = scg->rotate(shearElastic); Vector3r prev_FsElastic = shearElastic; // save shear force at previous time step // 2. Get incident velocity, get shear and normal components Vector3r incidentV = scg->getIncidentVel(de1, de2, dt, shift2, shiftVel, preventGranularRatcheting); Vector3r incidentVn = scg->normal.dot(incidentV)*scg->normal; // contact normal velocity Vector3r incidentVs = incidentV - incidentVn; // contact shear velocity // 3. Get shear force (incrementally) shearElastic = shearElastic - phys->ks*(incidentVs*dt); /**************************************/ /* VISCOUS DAMPING (Normal direction) */ /**************************************/ // normal force must be updated here before we apply the Mohr-Coulomb criterion if (useDamping){ // get normal viscous component phys->normalViscous = cn*incidentVn; Vector3r normTemp = phys->normalForce - phys->normalViscous; // temporary normal force // viscous force should not exceed the value of current normal force, i.e. no attraction force should be permitted if particles are non-adhesive // if particles are adhesive, then fixed the viscous force at maximum equal to the adhesion force // *** enforce normal force to zero if no adhesion is permitted *** if (phys->adhesionForce==0.0 || !includeAdhesion){ if (normTemp.dot(scg->normal)<0.0){ phys->normalForce = Vector3r::Zero(); phys->normalViscous = phys->normalViscous + normTemp; // normal viscous force is such that the total applied force is null - it is necessary to compute energy correctly! } else{phys->normalForce -= phys->normalViscous;} } else if (includeAdhesion && phys->adhesionForce!=0.0){ // *** limit viscous component to the max adhesive force *** if (normTemp.dot(scg->normal)<0.0 && (phys->normalViscous.norm() > phys->adhesionForce) ){ Real normVisc = phys->normalViscous.norm(); Vector3r normViscVector = phys->normalViscous/normVisc; phys->normalViscous = phys->adhesionForce*normViscVector; phys->normalForce -= phys->normalViscous; } // *** apply viscous component - in the presence of adhesion *** else {phys->normalForce -= phys->normalViscous;} } if (calcEnergy) {normDampDissip += phys->normalViscous.dot(incidentVn*dt);} // calc dissipation of energy due to normal damping } /*************************************/ /* SHEAR DISPLACEMENT (elastic only) */ /*************************************/ Vector3r& us_elastic = phys->usElastic; us_elastic = scg->rotate(us_elastic); // rotate vector Vector3r prevUs_el = us_elastic; // store previous elastic shear displacement (already rotated) us_elastic -= incidentVs*dt; // add shear increment /****************************************/ /* SHEAR DISPLACEMENT (elastic+plastic) */ /****************************************/ Vector3r& us_total = phys->usTotal; us_total = scg->rotate(us_total); // rotate vector Vector3r prevUs_tot = us_total; // store previous total shear displacement (already rotated) us_total -= incidentVs*dt; // add shear increment NOTE: this vector is not passed into the failure criterion, hence it holds also the plastic part of the shear displacement bool noShearDamp = false; // bool to decide whether we need to account for shear damping dissipation or not /********************/ /* MOHR-COULOMB law */ /********************/ phys->isSliding=false; phys->shearViscous=Vector3r::Zero(); // reset so that during sliding, the previous values is not there Fn = phys->normalForce.norm(); if (!includeAdhesion) { Real maxFs = Fn*phys->tangensOfFrictionAngle; if (shearElastic.squaredNorm() > maxFs*maxFs){ phys->isSliding=true; noShearDamp = true; // no damping is added in the shear direction, hence no need to account for shear damping dissipation Real ratio = maxFs/shearElastic.norm(); shearElastic *= ratio; phys->shearForce = shearElastic; /*store only elastic shear displacement*/ us_elastic*= ratio; if (calcEnergy) {frictionDissipation += (us_total-prevUs_tot).dot(shearElastic);} // calculate energy dissipation due to sliding behavior } else if (useDamping){ // add current contact damping if we do not slide and if damping is requested phys->shearViscous = cs*incidentVs; // get shear viscous component phys->shearForce = shearElastic - phys->shearViscous;} else if (!useDamping) {phys->shearForce = shearElastic;} // update the shear force at the elastic value if no damping is present and if we passed MC } else { // Mohr-Coulomb formulation adpated due to the presence of adhesion (see Thornton, 1991). Real maxFs = phys->tangensOfFrictionAngle*(phys->adhesionForce+Fn); // adhesionForce already included in normalForce (above) if (shearElastic.squaredNorm() > maxFs*maxFs){ phys->isSliding=true; noShearDamp = true; // no damping is added in the shear direction, hence no need to account for shear damping dissipation Real ratio = maxFs/shearElastic.norm(); shearElastic *= ratio; phys->shearForce = shearElastic; /*store only elastic shear displacement*/ us_elastic *= ratio; if (calcEnergy) {frictionDissipation += (us_total-prevUs_tot).dot(shearElastic);} // calculate energy dissipation due to sliding behavior } else if (useDamping){ // add current contact damping if we do not slide and if damping is requested phys->shearViscous = cs*incidentVs; // get shear viscous component phys->shearForce = shearElastic - phys->shearViscous;} else if (!useDamping) {phys->shearForce = shearElastic;} // update the shear force at the elastic value if no damping is present and if we passed MC } /************************/ /* SHEAR ELASTIC ENERGY */ /************************/ // NOTE: shear elastic energy calculation must come after the MC criterion, otherwise displacements and forces are not updated if (calcEnergy) { shearEnergy += (us_elastic-prevUs_el).dot((shearElastic+prev_FsElastic)/2.); // NOTE: no additional energy if we perform sliding since us_elastic and prevUs_el will hold the same value (in fact us_elastic is only keeping the elastic part). We work out the area of the trapezium. } /**************************************************/ /* VISCOUS DAMPING (energy term, shear direction) */ /**************************************************/ if (useDamping){ // get normal viscous component (the shear one is calculated inside Mohr-Coulomb criterion, see above) if (calcEnergy) {if (!noShearDamp) {shearDampDissip += phys->shearViscous.dot(incidentVs*dt);}} // calc energy dissipation due to viscous linear damping } /****************/ /* APPLY FORCES */ /****************/ if (!scene->isPeriodic) applyForceAtContactPoint(-phys->normalForce - phys->shearForce, scg->contactPoint , id1, de1->se3.position, id2, de2->se3.position); else { // in scg we do not wrap particles positions, hence "applyForceAtContactPoint" cannot be used Vector3r force = -phys->normalForce - phys->shearForce; scene->forces.addForce(id1,force); scene->forces.addForce(id2,-force); scene->forces.addTorque(id1,(scg->radius1-0.5*scg->penetrationDepth)* scg->normal.cross(force)); scene->forces.addTorque(id2,(scg->radius2-0.5*scg->penetrationDepth)* scg->normal.cross(force)); } /********************************************/ /* MOMENT CONTACT LAW */ /********************************************/ if (includeMoment){ // *** Bending ***// // new code to compute relative particle rotation (similar to the way the shear is computed) // use scg function to compute relAngVel Vector3r relAngVel = scg->getRelAngVel(de1,de2,dt); //Vector3r relAngVel = (b2->state->angVel-b1->state->angVel); Vector3r relAngVelBend = relAngVel - scg->normal.dot(relAngVel)*scg->normal; // keep only the bending part Vector3r relRot = relAngVelBend*dt; // relative rotation due to rolling behaviour // incremental formulation for the bending moment (as for the shear part) Vector3r& momentBend = phys->momentBend; momentBend = scg->rotate(momentBend); // rotate moment vector (updated) momentBend = momentBend-phys->kr*relRot; // add incremental rolling to the rolling vector // ---------------------------------------------------------------------------------------- // *** Torsion ***// Vector3r relAngVelTwist = scg->normal.dot(relAngVel)*scg->normal; Vector3r relRotTwist = relAngVelTwist*dt; // component of relative rotation along n // incremental formulation for the torsional moment Vector3r& momentTwist = phys->momentTwist; momentTwist = scg->rotate(momentTwist); // rotate moment vector (updated) momentTwist = momentTwist-phys->ktw*relRotTwist; #if 0 // code to compute the relative particle rotation if (includeMoment){ Real rMean = (scg->radius1+scg->radius2)/2.; // sliding motion Vector3r duS1 = scg->radius1*(phys->prevNormal-scg->normal); Vector3r duS2 = scg->radius2*(scg->normal-phys->prevNormal); // rolling motion Vector3r duR1 = scg->radius1*dt*b1->state->angVel.cross(scg->normal); Vector3r duR2 = -scg->radius2*dt*b2->state->angVel.cross(scg->normal); // relative position of the old contact point with respect to the new one Vector3r relPosC1 = duS1+duR1; Vector3r relPosC2 = duS2+duR2; Vector3r duR = (relPosC1+relPosC2)/2.; // incremental displacement vector (same radius is temporarily assumed) // check wheter rolling will be present, if not do nothing Vector3r x=scg->normal.cross(duR); Vector3r normdThetaR(Vector3r::Zero()); // initialize if(x.squaredNorm()==0) { /* no rolling */ } else { Vector3r normdThetaR = x/x.norm(); // moment unit vector phys->dThetaR = duR.norm()/rMean*normdThetaR;} // incremental rolling // incremental formulation for the bending moment (as for the shear part) Vector3r& momentBend = phys->momentBend; momentBend = scg->rotate(momentBend); // rotate moment vector momentBend = momentBend+phys->kr*phys->dThetaR; // add incremental rolling to the rolling vector FIXME: is the sign correct? #endif // check plasticity condition (only bending part for the moment) Real MomentMax = phys->maxBendPl*phys->normalForce.norm(); Real scalarMoment = phys->momentBend.norm(); if (MomentMax>0){ if(scalarMoment > MomentMax) { Real ratio = MomentMax/scalarMoment; // to fix the moment to its yielding value phys->momentBend *= ratio; } } // apply moments Vector3r moment = phys->momentTwist+phys->momentBend; scene->forces.addTorque(id1,-moment); scene->forces.addTorque(id2,moment); } return true; // update variables //phys->prevNormal = scg->normal; } // The following code was moved from Ip2_FrictMat_FrictMat_MindlinCapillaryPhys.cpp void Ip2_FrictMat_FrictMat_MindlinCapillaryPhys::go( const shared_ptr& b1 //FrictMat , const shared_ptr& b2 // FrictMat , const shared_ptr& interaction) { if(interaction->phys) return; // no updates of an already existing contact necessary shared_ptr contactPhysics(new MindlinCapillaryPhys()); interaction->phys = contactPhysics; FrictMat* mat1 = YADE_CAST(b1.get()); FrictMat* mat2 = YADE_CAST(b2.get()); /* from interaction physics */ Real Ea = mat1->young; Real Eb = mat2->young; Real Va = mat1->poisson; Real Vb = mat2->poisson; Real fa = mat1->frictionAngle; Real fb = mat2->frictionAngle; /* from interaction geometry */ GenericSpheresContact* scg = YADE_CAST(interaction->geom.get()); Real Da = scg->refR1>0 ? scg->refR1 : scg->refR2; Real Db = scg->refR2; //Vector3r normal=scg->normal; //The variable set but not used /* calculate stiffness coefficients */ Real Ga = Ea/(2*(1+Va)); Real Gb = Eb/(2*(1+Vb)); Real G = (Ga+Gb)/2; // average of shear modulus Real V = (Va+Vb)/2; // average of poisson's ratio Real E = Ea*Eb/((1.-std::pow(Va,2))*Eb+(1.-std::pow(Vb,2))*Ea); // Young modulus Real R = Da*Db/(Da+Db); // equivalent radius Real Rmean = (Da+Db)/2.; // mean radius Real Kno = 4./3.*E*sqrt(R); // coefficient for normal stiffness Real Kso = 2*sqrt(4*R)*G/(2-V); // coefficient for shear stiffness Real frictionAngle = std::min(fa,fb); Real Adhesion = 4.*Mathr::PI*R*gamma; // calculate adhesion force as predicted by DMT theory /* pass values calculated from above to MindlinCapillaryPhys */ contactPhysics->tangensOfFrictionAngle = std::tan(frictionAngle); //mindlinPhys->prevNormal = scg->normal; // used to compute relative rotation contactPhysics->kno = Kno; // this is just a coeff contactPhysics->kso = Kso; // this is just a coeff contactPhysics->adhesionForce = Adhesion; contactPhysics->kr = krot; contactPhysics->ktw = ktwist; contactPhysics->maxBendPl = eta*Rmean; // does this make sense? why do we take Rmean? /* compute viscous coefficients */ if(en && betan) throw std::invalid_argument("Ip2_FrictMat_FrictMat_MindlinCapillaryPhys: only one of en, betan can be specified."); if(es && betas) throw std::invalid_argument("Ip2_FrictMat_FrictMat_MindlinCapillaryPhys: only one of es, betas can be specified."); // en or es specified, just compute alpha, otherwise alpha remains 0 if(en || es){ Real logE = log((*en)(mat1->id,mat2->id)); contactPhysics->alpha = -sqrt(5/6.)*2*logE/sqrt(pow(logE,2)+pow(Mathr::PI,2))*sqrt(2*E*sqrt(R)); // (see Tsuji, 1992) } // betan specified, use that value directly; otherwise give zero else{ contactPhysics->betan=betan ? (*betan)(mat1->id,mat2->id) : 0; contactPhysics->betas=betas ? (*betas)(mat1->id,mat2->id) : contactPhysics->betan; } }; trunk-2018.02b/pkg/dem/HertzMindlin.hpp000066400000000000000000000336661324306050200176520ustar00rootroot00000000000000// 2010 © Chiara Modenese // /* === HIGH LEVEL OVERVIEW OF MINDLIN === Mindlin is a set of classes to include the Hertz-Mindlin formulation for the contact stiffnesses. The DMT formulation is also considered (for adhesive particles, rigid and small bodies). */ #pragma once #include #include #include #include #include #include #include #include #include /******************** MindlinPhys *********************************/ class MindlinPhys: public FrictPhys{ public: virtual ~MindlinPhys() {}; YADE_CLASS_BASE_DOC_ATTRS_CTOR(MindlinPhys,FrictPhys,"Representation of an interaction of the Hertz-Mindlin type.", ((Real,kno,0.0,,"Constant value in the formulation of the normal stiffness")) ((Real,kso,0.0,,"Constant value in the formulation of the tangential stiffness")) ((Real,kr,0.0,,"Rotational stiffness")) ((Real,ktw,0.0,,"Rotational stiffness")) ((Real,maxBendPl,0.0,,"Coefficient to determine the maximum plastic moment to apply at the contact")) ((Vector3r,normalViscous,Vector3r::Zero(),,"Normal viscous component")) ((Vector3r,shearViscous,Vector3r::Zero(),,"Shear viscous component")) ((Vector3r,shearElastic,Vector3r::Zero(),,"Total elastic shear force")) ((Vector3r,usElastic,Vector3r::Zero(),,"Total elastic shear displacement (only elastic part)")) ((Vector3r,usTotal,Vector3r::Zero(),,"Total elastic shear displacement (elastic+plastic part)")) //((Vector3r,prevNormal,Vector3r::Zero(),,"Save previous contact normal to compute relative rotation")) ((Vector3r,momentBend,Vector3r::Zero(),,"Artificial bending moment to provide rolling resistance in order to account for some degree of interlocking between particles")) ((Vector3r,momentTwist,Vector3r::Zero(),,"Artificial twisting moment (no plastic condition can be applied at the moment)")) //((Vector3r,dThetaR,Vector3r::Zero(),,"Incremental rolling vector")) ((Real,radius,NaN,,"Contact radius (only computed with :yref:`Law2_ScGeom_MindlinPhys_Mindlin::calcEnergy`)")) //((Real,gamma,0.0,"Surface energy parameter [J/m^2] per each unit contact surface, to derive DMT formulation from HM")) ((Real,adhesionForce,0.0,,"Force of adhesion as predicted by DMT")) ((bool,isAdhesive,false,,"bool to identify if the contact is adhesive, that is to say if the contact force is attractive")) ((bool,isSliding,false,,"check if the contact is sliding (useful to calculate the ratio of sliding contacts)")) // Contact damping ratio as for linear elastic contact law ((Real,betan,0.0,,"Normal Damping Ratio. Fraction of the viscous damping coefficient (normal direction) equal to $\\frac{c_{n}}{C_{n,crit}}$.")) ((Real,betas,0.0,,"Shear Damping Ratio. Fraction of the viscous damping coefficient (shear direction) equal to $\\frac{c_{s}}{C_{s,crit}}$.")) // Contact damping ratio for non-linear elastic contact law (of Hertz-Mindlin type) ((Real,alpha,0.0,,"Constant coefficient to define contact viscous damping for non-linear elastic force-displacement relationship.")) // temporary ((Vector3r,prevU,Vector3r::Zero(),,"Previous local displacement; only used with :yref:`Law2_L3Geom_FrictPhys_HertzMindlin`.")) ((Vector2r,Fs,Vector2r::Zero(),,"Shear force in local axes (computed incrementally)")) , createIndex()); REGISTER_CLASS_INDEX(MindlinPhys,FrictPhys); DECLARE_LOGGER; }; REGISTER_SERIALIZABLE(MindlinPhys); /******************** Ip2_FrictMat_FrictMat_MindlinPhys *******/ class Ip2_FrictMat_FrictMat_MindlinPhys: public IPhysFunctor{ public : virtual void go(const shared_ptr& b1, const shared_ptr& b2, const shared_ptr& interaction); FUNCTOR2D(FrictMat,FrictMat); YADE_CLASS_BASE_DOC_ATTRS( Ip2_FrictMat_FrictMat_MindlinPhys,IPhysFunctor,"Calculate some physical parameters needed to obtain \ the normal and shear stiffnesses according to the Hertz-Mindlin formulation (as implemented in PFC).\n\n\ Viscous parameters can be specified either using coefficients of restitution ($e_n$, $e_s$) or viscous \ damping ratio ($\\beta_n$, $\\beta_s$). The following rules apply:\n#. If the $\\beta_n$ ($\\beta_s$) \ ratio is given, it is assigned to :yref:`MindlinPhys.betan` (:yref:`MindlinPhys.betas`) directly.\n#. \ If $e_n$ is given, :yref:`MindlinPhys.betan` is computed using $\\beta_n=-(\\log e_n)/\\sqrt{\\pi^2+(\\log e_n)^2}$. \ The same applies to $e_s$, :yref:`MindlinPhys.betas`.\n#. It is an error (exception) to specify both $e_n$ \ and $\\beta_n$ ($e_s$ and $\\beta_s$).\n#. If neither $e_n$ nor $\\beta_n$ is given, zero value \ for :yref:`MindlinPhys.betan` is used; there will be no viscous effects.\n#.If neither $e_s$ nor $\\beta_s$ \ is given, the value of :yref:`MindlinPhys.betan` is used for :yref:`MindlinPhys.betas` as well.\n\nThe \ $e_n$, $\\beta_n$, $e_s$, $\\beta_s$ are :yref:`MatchMaker` objects; they can be constructed from float \ values to always return constant value.\n\nSee :ysrc:`scripts/test/shots.py` for an example of specifying \ $e_n$ based on combination of parameters.", ((Real,gamma,0.0,,"Surface energy parameter [J/m^2] per each unit contact surface, to derive DMT formulation from HM")) ((Real,eta,0.0,,"Coefficient to determine the plastic bending moment")) ((Real,krot,0.0,,"Rotational stiffness for moment contact law")) ((Real,ktwist,0.0,,"Torsional stiffness for moment contact law")) ((shared_ptr,en,,,"Normal coefficient of restitution $e_n$.")) ((shared_ptr,es,,,"Shear coefficient of restitution $e_s$.")) ((shared_ptr,betan,,,"Normal viscous damping ratio $\\beta_n$.")) ((shared_ptr,betas,,,"Shear viscous damping ratio $\\beta_s$.")) ((shared_ptr,frictAngle,,,"Instance of :yref:`MatchMaker` determining how to compute the friction angle of an interaction. If ``None``, minimum value is used.")) ); DECLARE_LOGGER; }; REGISTER_SERIALIZABLE(Ip2_FrictMat_FrictMat_MindlinPhys); class Law2_ScGeom_MindlinPhys_MindlinDeresiewitz: public LawFunctor{ public: virtual bool go(shared_ptr&, shared_ptr&, Interaction*); FUNCTOR2D(ScGeom,MindlinPhys); YADE_CLASS_BASE_DOC_ATTRS(Law2_ScGeom_MindlinPhys_MindlinDeresiewitz,LawFunctor, "Hertz-Mindlin contact law with partial slip solution, as described in [Thornton1991]_.", ((bool,neverErase,false,,"Keep interactions even if particles go away from each other (only in case another constitutive law is in the scene, e.g. :yref:`Law2_ScGeom_CapillaryPhys_Capillarity`)")) ); }; REGISTER_SERIALIZABLE(Law2_ScGeom_MindlinPhys_MindlinDeresiewitz); class Law2_ScGeom_MindlinPhys_HertzWithLinearShear: public LawFunctor{ public: virtual bool go(shared_ptr&, shared_ptr&, Interaction*); FUNCTOR2D(ScGeom,MindlinPhys); YADE_CLASS_BASE_DOC_ATTRS(Law2_ScGeom_MindlinPhys_HertzWithLinearShear,LawFunctor, "Constitutive law for the Hertz formulation (using :yref:`MindlinPhys.kno`) and linear beahvior in shear (using :yref:`MindlinPhys.kso` for stiffness and :yref:`FrictPhys.tangensOfFrictionAngle`). \n\n.. note:: No viscosity or damping. If you need those, look at :yref:`Law2_ScGeom_MindlinPhys_Mindlin`, which also includes non-linear Mindlin shear.", ((bool,neverErase,false,,"Keep interactions even if particles go away from each other (only in case another constitutive law is in the scene, e.g. :yref:`Law2_ScGeom_CapillaryPhys_Capillarity`)")) ((int,nonLin,0,,"Shear force nonlinearity (the value determines how many features of the non-linearity are taken in account). 1: ks as in HM 2: shearElastic increment computed as in HM 3. granular ratcheting disabled.")) ); }; REGISTER_SERIALIZABLE(Law2_ScGeom_MindlinPhys_HertzWithLinearShear); /******************** Law2_ScGeom_MindlinPhys_Mindlin *********/ class Law2_ScGeom_MindlinPhys_Mindlin: public LawFunctor{ public: virtual bool go(shared_ptr& _geom, shared_ptr& _phys, Interaction* I); Real normElastEnergy(); Real adhesionEnergy(); Real getfrictionDissipation(); Real getshearEnergy(); Real getnormDampDissip(); Real getshearDampDissip(); Real contactsAdhesive(); Real ratioSlidingContacts(); FUNCTOR2D(ScGeom,MindlinPhys); YADE_CLASS_BASE_DOC_ATTRS_INIT_CTOR_PY(Law2_ScGeom_MindlinPhys_Mindlin,LawFunctor,"Constitutive law for the Hertz-Mindlin formulation. It includes non linear elasticity in the normal direction as predicted by Hertz for two non-conforming elastic contact bodies. In the shear direction, instead, it reseambles the simplified case without slip discussed in Mindlin's paper, where a linear relationship between shear force and tangential displacement is provided. Finally, the Mohr-Coulomb criterion is employed to established the maximum friction force which can be developed at the contact. Moreover, it is also possible to include the effect of linear viscous damping through the definition of the parameters $\\beta_{n}$ and $\\beta_{s}$.", ((bool,preventGranularRatcheting,true,,"bool to avoid granular ratcheting")) ((bool,includeAdhesion,false,,"bool to include the adhesion force following the DMT formulation. If true, also the normal elastic energy takes into account the adhesion effect.")) ((bool,calcEnergy,false,,"bool to calculate energy terms (shear potential energy, dissipation of energy due to friction and dissipation of energy due to normal and tangential damping)")) ((bool,includeMoment,false,,"bool to consider rolling resistance (if :yref:`Ip2_FrictMat_FrictMat_MindlinPhys::eta` is 0.0, no plastic condition is applied.)")) ((bool,neverErase,false,,"Keep interactions even if particles go away from each other (only in case another constitutive law is in the scene, e.g. :yref:`Law2_ScGeom_CapillaryPhys_Capillarity`)")) //((bool,LinDamp,true,,"bool to activate linear viscous damping (if false, en and es have to be defined in place of betan and betas)")) ((OpenMPAccumulator,frictionDissipation,,Attr::noSave,"Energy dissipation due to sliding")) ((OpenMPAccumulator,shearEnergy,,Attr::noSave,"Shear elastic potential energy")) ((OpenMPAccumulator,normDampDissip,,Attr::noSave,"Energy dissipated by normal damping")) ((OpenMPAccumulator,shearDampDissip,,Attr::noSave,"Energy dissipated by tangential damping")) , /* init */ , /* ctor */ , /* py */ .def("contactsAdhesive",&Law2_ScGeom_MindlinPhys_Mindlin::contactsAdhesive,"Compute total number of adhesive contacts.") .def("ratioSlidingContacts",&Law2_ScGeom_MindlinPhys_Mindlin::ratioSlidingContacts,"Return the ratio between the number of contacts sliding to the total number at a given time.") .def("normElastEnergy",&Law2_ScGeom_MindlinPhys_Mindlin::normElastEnergy,"Compute normal elastic potential energy. It handles the DMT formulation if :yref:`Law2_ScGeom_MindlinPhys_Mindlin::includeAdhesion` is set to true.") ); DECLARE_LOGGER; }; REGISTER_SERIALIZABLE(Law2_ScGeom_MindlinPhys_Mindlin); // The following code was moved from Ip2_FrictMat_FrictMat_MindlinCapillaryPhys.hpp class MindlinCapillaryPhys : public MindlinPhys { public : int currentIndexes [4]; // used for faster interpolation (stores previous positions in tables) virtual ~MindlinCapillaryPhys() {}; YADE_CLASS_BASE_DOC_ATTRS_INIT_CTOR_PY(MindlinCapillaryPhys,MindlinPhys,"Adds capillary physics to Mindlin's interaction physics.", ((bool,meniscus,false,Attr::readonly,"True when a meniscus with a non-zero liquid volume (:yref:`vMeniscus`) has been computed for this interaction")) ((bool,isBroken,false,,"Might be set to true by the user to make liquid bridge inactive (capillary force is zero)")) ((Real,capillaryPressure,0.,,"Value of the capillary pressure Uc. Defined as Ugas-Uliquid, obtained from :yref:`corresponding Law2 parameter`")) ((Real,vMeniscus,0.,,"Volume of the meniscus")) ((Real,Delta1,0.,,"Defines the surface area wetted by the meniscus on the smallest grains of radius R1 (R1& b1, const shared_ptr& b2, const shared_ptr& interaction); FUNCTOR2D(FrictMat,FrictMat); YADE_CLASS_BASE_DOC_ATTRS(Ip2_FrictMat_FrictMat_MindlinCapillaryPhys,IPhysFunctor, "RelationShips to use with Law2_ScGeom_CapillaryPhys_Capillarity\n\n In these RelationShips all the interaction attributes are computed. \n\n.. warning::\n\tas in the others :yref:`Ip2 functors`, most of the attributes are computed only once, when the interaction is new.", ((Real,gamma,0.0,,"Surface energy parameter [J/m^2] per each unit contact surface, to derive DMT formulation from HM")) ((Real,eta,0.0,,"Coefficient to determine the plastic bending moment")) ((Real,krot,0.0,,"Rotational stiffness for moment contact law")) ((Real,ktwist,0.0,,"Torsional stiffness for moment contact law")) ((shared_ptr,en,,,"Normal coefficient of restitution $e_n$.")) ((shared_ptr,es,,,"Shear coefficient of restitution $e_s$.")) ((shared_ptr,betan,,,"Normal viscous damping ratio $\\beta_n$.")) ((shared_ptr,betas,,,"Shear viscous damping ratio $\\beta_s$.")) ); DECLARE_LOGGER; }; REGISTER_SERIALIZABLE(Ip2_FrictMat_FrictMat_MindlinCapillaryPhys); trunk-2018.02b/pkg/dem/Ig2_Box_Sphere_ScGeom.cpp000066400000000000000000000163341324306050200212230ustar00rootroot00000000000000/************************************************************************* * Copyright (C) 2004 by Olivier Galizzi * * olivier.galizzi@imag.fr * * Copyright (C) 2004 by Janek Kozicki * * cosurgi@berlios.de * * Copyright (C) 2006 by Bruno Chareyre * * bruno.chareyre@hmg.inpg.fr * * * * This program is free software; it is licensed under the terms of the * * GNU General Public License v2 or later. See file LICENSE for details. * *************************************************************************/ #include"Ig2_Box_Sphere_ScGeom.hpp" #include #include #include #include #include bool Ig2_Box_Sphere_ScGeom::go(const shared_ptr& cm1, const shared_ptr& cm2, const State& state1, const State& state2, const Vector3r& shift2, const bool& force, const shared_ptr& c) { const Se3r& se31=state1.se3; const Se3r& se32=state2.se3; bool inside=true; Vector3r pt1,pt2,normal; Real depth; Box* obb = static_cast(cm1.get()); Sphere* s = static_cast(cm2.get()); Vector3r extents = obb->extents; // FIXME: do we need rotation matrix? Can't quaternion do just fine? Matrix3r boxAxisT=se31.orientation.toRotationMatrix(); Matrix3r boxAxis = boxAxisT.transpose(); Vector3r relPos21 = se32.position+shift2-se31.position; // relative position of centroids // cOnBox_boxLocal is the sphere centroid (in box-local coordinates), but projected onto box if it is outside. // _boxLocal means that ROTATION is local and origin is in box's origin Vector3r cOnBox_boxLocal=boxAxis*relPos21; if (cOnBox_boxLocal[0]<-extents[0]){cOnBox_boxLocal[0]=-extents[0]; inside=false; } if (cOnBox_boxLocal[0]> extents[0]){cOnBox_boxLocal[0]= extents[0]; inside=false; } if (cOnBox_boxLocal[1]<-extents[1]){cOnBox_boxLocal[1]=-extents[1]; inside=false; } if (cOnBox_boxLocal[1]> extents[1]){cOnBox_boxLocal[1]= extents[1]; inside=false; } if (cOnBox_boxLocal[2]<-extents[2]){cOnBox_boxLocal[2]=-extents[2]; inside=false; } if (cOnBox_boxLocal[2]> extents[2]){cOnBox_boxLocal[2]= extents[2]; inside=false; } shared_ptr scm; if (inside){ // sphere center inside box. find largest `cOnBox_boxLocal' value: // minCBoxDist_index is the coordinate index that minimizes extents[minCBoxDist_index]-std::abs(cOnBox_boxLocal[minCBoxDist_index] (sphere center closest to box boundary) // where cOnBox_boxLocal is minimal (i.e. where sphere center is closest perpendicularly to the box) Real minCBoxDist=extents[0]-std::abs(cOnBox_boxLocal[0]); int minCBoxDist_index=0; for (int i=1; i<3; i++){Real tt=extents[i]-std::abs(cOnBox_boxLocal[i]); if (tt0)?1.0:-1.0; normal = boxAxisT*normal_boxLocal; normal.normalize(); // se32 is sphere's se3 /* * * _--(pt1)-_ BOX * +------~-----x-----~----------------+ * | / ^ \ | * | / | (normal)*minCBoxDist | * | | x | | * | \ | c ≡ se32->position | * | \ | / | * | ~ | / SPHERE | * | ^~~ x ~~^ | * | (pt2) | * +-----------------------------------+ * * */ pt1 = se32.position+normal*minCBoxDist; pt2 = se32.position-normal*s->radius; Vector3r normal = pt1-pt2; normal.normalize(); bool isNew=!c->geom; if (isNew) scm = shared_ptr(new ScGeom()); else scm = YADE_PTR_CAST(c->geom); // contact point is in the middle of overlapping volumes //(in the direction of penetration, which is normal to the box surface closest to sphere center) of overlapping volumes scm->contactPoint = 0.5*(pt1+pt2); // scm->normal = normal; scm->penetrationDepth = (pt1-pt2).norm(); scm->radius1 = s->radius; scm->radius2 = s->radius; c->geom = scm; scm->precompute(state1,state2,scene,c,normal,isNew,shift2,true); } else { // outside Vector3r cOnBox_box = boxAxisT*cOnBox_boxLocal; // projection of sphere's center on closest box surface - relative to box's origin, but GLOBAL orientation! Vector3r cOnBox_sphere = cOnBox_box-relPos21; // same, but origin in sphere's center depth=s->radius-cOnBox_sphere.norm(); if (depth<0 && !c->isReal() && !force) return false; /* * +-----------------------------------+ * | | * | se31->position | * | pt2 × | * | × / cOnBox_box | * | pt1 / | * +------~-----×-----~----------------+ * / ^ \ * / | cOnBox_sphere * | × | * \ c ≡ se32->position * \ / * ~ / * ^~~ ~ ~~^ * */ pt1=cOnBox_box+se31.position; cOnBox_sphere.normalize(); // we want only direction in the following pt2=se32.position+shift2+cOnBox_sphere*s->radius; bool isNew=!c->geom; if (isNew) scm = shared_ptr(new ScGeom()); else scm = YADE_PTR_CAST(c->geom); scm->contactPoint = 0.5*(pt1+pt2); scm->penetrationDepth = depth; scm->radius1 = s->radius; scm->radius2 = s->radius; c->geom = scm; //FIXME : NOT TESTED : do we precompute correctly if boxes are moving? scm->precompute(state1,state2,scene,c,-cOnBox_sphere,isNew,shift2,false); } return true; } bool Ig2_Box_Sphere_ScGeom::goReverse(const shared_ptr& cm1, const shared_ptr& cm2, const State& state1, const State& state2, const Vector3r& shift2, const bool& force, const shared_ptr& c) { c->swapOrder(); return go(cm2,cm1,state2,state1,-shift2,force,c); } YADE_PLUGIN((Ig2_Box_Sphere_ScGeom)); bool Ig2_Box_Sphere_ScGeom6D::go(const shared_ptr& cm1, const shared_ptr& cm2, const State& state1, const State& state2, const Vector3r& shift2, const bool& force, const shared_ptr& c) { bool isNew = !c->geom; if (Ig2_Box_Sphere_ScGeom::go(cm1,cm2,state1,state2,shift2,force,c)) { if (isNew) {//generate a 6DOF interaction from the 3DOF one generated by Ig2_Box_Sphere_ScGeom shared_ptr sc(new ScGeom6D()); *(YADE_PTR_CAST(sc)) = *(YADE_PTR_CAST(c->geom)); c->geom=sc; } YADE_PTR_CAST(c->geom)->precomputeRotations(state1,state2,isNew,false); return true; } else return false; } bool Ig2_Box_Sphere_ScGeom6D::goReverse(const shared_ptr& cm1, const shared_ptr& cm2, const State& state1, const State& state2, const Vector3r& shift2, const bool& force, const shared_ptr& c) { c->swapOrder(); return go(cm2,cm1,state2,state1,-shift2,force,c); } YADE_PLUGIN((Ig2_Box_Sphere_ScGeom6D)); trunk-2018.02b/pkg/dem/Ig2_Box_Sphere_ScGeom.hpp000066400000000000000000000047621324306050200212320ustar00rootroot00000000000000/************************************************************************* * Copyright (C) 2004 by Olivier Galizzi * * olivier.galizzi@imag.fr * * Copyright (C) 2004 by Janek Kozicki * * cosurgi@berlios.de * * Copyright (C) 2006 by Bruno Chareyre * * bruno.chareyre@hmg.inpg.fr * * * * This program is free software; it is licensed under the terms of the * * GNU General Public License v2 or later. See file LICENSE for details. * *************************************************************************/ #pragma once #include #include #include class Ig2_Box_Sphere_ScGeom : public IGeomFunctor { public : virtual bool go(const shared_ptr& cm1, const shared_ptr& cm2, const State& state1, const State& state2, const Vector3r& shift2, const bool& force, const shared_ptr& c); virtual bool goReverse( const shared_ptr& cm1, const shared_ptr& cm2, const State& state1, const State& state2, const Vector3r& shift2, const bool& force, const shared_ptr& c); YADE_CLASS_BASE_DOC(Ig2_Box_Sphere_ScGeom,IGeomFunctor,"Create an interaction geometry :yref:`ScGeom` from :yref:`Box` and :yref:`Sphere`, representing the box with a projected virtual sphere of same radius.") FUNCTOR2D(Box,Sphere); DEFINE_FUNCTOR_ORDER_2D(Box,Sphere); }; REGISTER_SERIALIZABLE(Ig2_Box_Sphere_ScGeom); class Ig2_Box_Sphere_ScGeom6D : public Ig2_Box_Sphere_ScGeom { public : virtual bool go(const shared_ptr& cm1, const shared_ptr& cm2, const State& state1, const State& state2, const Vector3r& shift2, const bool& force, const shared_ptr& c); virtual bool goReverse(const shared_ptr& cm1, const shared_ptr& cm2, const State& state1, const State& state2, const Vector3r& shift2, const bool& force, const shared_ptr& c); YADE_CLASS_BASE_DOC(Ig2_Box_Sphere_ScGeom6D,Ig2_Box_Sphere_ScGeom,"Create an interaction geometry :yref:`ScGeom6D` from :yref:`Box` and :yref:`Sphere`, representing the box with a projected virtual sphere of same radius.") FUNCTOR2D(Box,Sphere); DEFINE_FUNCTOR_ORDER_2D(Box,Sphere); }; REGISTER_SERIALIZABLE(Ig2_Box_Sphere_ScGeom6D); trunk-2018.02b/pkg/dem/Ig2_Facet_Sphere_ScGeom.cpp000066400000000000000000000152621324306050200215140ustar00rootroot00000000000000/************************************************************************* * Copyright (C) 2008 by Sergei Dorofeenko * * sega@users.berlios.de * * * * This program is free software; it is licensed under the terms of the * * GNU General Public License v2 or later. See file LICENSE for details. * *************************************************************************/ #include"Ig2_Facet_Sphere_ScGeom.hpp" #include #include #include #include #include #include #include YADE_PLUGIN((Ig2_Facet_Sphere_ScGeom)(Ig2_Facet_Sphere_ScGeom6D)(Ig2_Wall_Sphere_ScGeom)); CREATE_LOGGER(Ig2_Facet_Sphere_ScGeom); bool Ig2_Facet_Sphere_ScGeom::go(const shared_ptr& cm1, const shared_ptr& cm2, const State& state1, const State& state2, const Vector3r& shift2, const bool& force, const shared_ptr& c) { TIMING_DELTAS_START(); const Se3r& se31=state1.se3; const Se3r& se32=state2.se3; Facet* facet = static_cast(cm1.get()); /* could be written as (needs to be tested): * Vector3r cl=se31.orientation.Conjugate()*(se32.position-se31.position); */ Matrix3r facetAxisT=se31.orientation.toRotationMatrix(); Matrix3r facetAxis = facetAxisT.transpose(); // local orientation Vector3r cl = facetAxis*(se32.position + shift2 - se31.position); // "contact line" in facet-local coords // // BEGIN everything in facet-local coordinates // Vector3r normal = facet->normal; Real L = normal.dot(cl); if (L<0) {normal=-normal; L=-L; } Real sphereRadius = static_cast(cm2.get())->radius; if (L>sphereRadius && !c->isReal() && !force) { // no contact, but only if there was no previous contact; ortherwise, the constitutive law is responsible for setting Interaction::isReal=false TIMING_DELTAS_CHECKPOINT("Ig2_Facet_Sphere_ScGeom"); return false; } Vector3r cp = cl - L*normal; const Vector3r* ne = facet->ne; Real penetrationDepth=0; Real bm = ne[0].dot(cp); int m=0; for (int i=1; i<3; ++i) { Real b=ne[i].dot(cp); if (bmicr-sh; if (icr<0) { LOG_WARN("a radius of a facet's inscribed circle less than zero! So, shrinkFactor is too large and would be reduced to zero."); shrinkFactor=0; icr = facet->icr; sh = 0; } if (bmicr) // contact with vertex m // cp = facet->vertices[m]; cp = facet->vu[m]*(facet->vl[m]-sh); else if (cp.dot(ne[m=(m+1>2)?0:m+1])>icr) // contact with vertex m+1 // cp = facet->vertices[(m+1>2)?0:m+1]; cp = facet->vu[m]*(facet->vl[m]-sh); normal = cl-cp; Real norm=normal.norm(); normal/=norm; penetrationDepth = sphereRadius - norm; } // // END everything in facet-local coordinates // if (penetrationDepth>0 || c->isReal()) { shared_ptr scm; bool isNew = !c->geom; if (c->geom) scm = YADE_PTR_CAST(c->geom); else scm = shared_ptr(new ScGeom()); normal = facetAxisT*normal; // in global orientation scm->contactPoint = se32.position + shift2 - (sphereRadius-0.5*penetrationDepth)*normal; scm->penetrationDepth = penetrationDepth; scm->radius1 = 2*sphereRadius; scm->radius2 = sphereRadius; if (isNew) c->geom = scm; scm->precompute(state1,state2,scene,c,normal,isNew,shift2,false/*avoidGranularRatcheting only for sphere-sphere*/); TIMING_DELTAS_CHECKPOINT("Ig2_Facet_Sphere_ScGeom"); return true; } TIMING_DELTAS_CHECKPOINT("Ig2_Facet_Sphere_ScGeom"); return false; } bool Ig2_Facet_Sphere_ScGeom::goReverse( const shared_ptr& cm1, const shared_ptr& cm2, const State& state1, const State& state2, const Vector3r& shift2, const bool& force, const shared_ptr& c) { c->swapOrder(); //LOG_WARN("Swapped interaction order for "<getId2()<<"&"<getId1()); return go(cm2,cm1,state2,state1,-shift2,force,c); } bool Ig2_Facet_Sphere_ScGeom6D::go(const shared_ptr& cm1, const shared_ptr& cm2, const State& state1, const State& state2, const Vector3r& shift2, const bool& force, const shared_ptr& c) { bool isNew = !c->geom; if (Ig2_Facet_Sphere_ScGeom::go(cm1,cm2,state1,state2,shift2,force,c)) { if (isNew) {//generate a 6DOF interaction from the 3DOF one generated by Ig2_Facet_Sphere_ScGeom shared_ptr sc(new ScGeom6D()); *(YADE_PTR_CAST(sc)) = *(YADE_PTR_CAST(c->geom)); c->geom=sc; } YADE_PTR_CAST(c->geom)->precomputeRotations(state1,state2,isNew,false); return true; } else return false; } bool Ig2_Facet_Sphere_ScGeom6D::goReverse( const shared_ptr& cm1, const shared_ptr& cm2, const State& state1, const State& state2, const Vector3r& shift2, const bool& force, const shared_ptr& c) { c->swapOrder(); return go(cm2,cm1,state2,state1,-shift2,force,c); } /********* Wall + Sphere **********/ bool Ig2_Wall_Sphere_ScGeom::go(const shared_ptr& cm1, const shared_ptr& cm2, const State& state1, const State& state2, const Vector3r& shift2, const bool& force, const shared_ptr& c){ Wall* wall=static_cast(cm1.get()); const Real radius=static_cast(cm2.get())->radius; const int& ax(wall->axis); Real dist=(state2.pos)[ax]+shift2[ax]-state1.pos[ax]; // signed "distance" between centers if(!c->isReal() && std::abs(dist)>radius && !force) { return false; }// wall and sphere too far from each other // contact point is sphere center projected onto the wall Vector3r contPt=state2.pos+shift2; contPt[ax]=state1.pos[ax]; Vector3r normal(0.,0.,0.); // wall interacting from both sides: normal depends on sphere's position assert(wall->sense==-1 || wall->sense==0 || wall->sense==1); if(wall->sense==0) normal[ax]=dist>0?1.:-1.; else normal[ax]=wall->sense==1?1.:-1; bool isNew=!c->geom; if(isNew) c->geom=shared_ptr(new ScGeom()); const shared_ptr& ws=YADE_PTR_CAST(c->geom); ws->radius1=ws->radius2=radius; // do the same as for facet-sphere: wall's "radius" is the same as the sphere's radius ws->contactPoint=contPt; ws->penetrationDepth=-(std::abs(dist)-radius); // ws->normal is assigned by precompute ws->precompute(state1,state2,scene,c,normal,isNew,shift2,noRatch); return true; } trunk-2018.02b/pkg/dem/Ig2_Facet_Sphere_ScGeom.hpp000066400000000000000000000066451324306050200215260ustar00rootroot00000000000000/************************************************************************* * Copyright (C) 2008 by Sergei Dorofeenko * * sega@users.berlios.de * * * * This program is free software; it is licensed under the terms of the * * GNU General Public License v2 or later. See file LICENSE for details. * *************************************************************************/ #pragma once #include #include #include #include #include class Ig2_Facet_Sphere_ScGeom : public IGeomFunctor { public : virtual bool go(const shared_ptr& cm1, const shared_ptr& cm2, const State& state1, const State& state2, const Vector3r& shift2, const bool& force, const shared_ptr& c); virtual bool goReverse( const shared_ptr& cm1, const shared_ptr& cm2, const State& state1, const State& state2, const Vector3r& shift2, const bool& force, const shared_ptr& c); YADE_CLASS_BASE_DOC_ATTRS(Ig2_Facet_Sphere_ScGeom,IGeomFunctor,"Create/update a :yref:`ScGeom` instance representing intersection of :yref:`Facet` and :yref:`Sphere`.", ((Real,shrinkFactor,((void)"no shrinking",0),,"The radius of the inscribed circle of the facet is decreased by the value of the sphere's radius multipled by *shrinkFactor*. From the definition of contact point on the surface made of facets, the given surface is not continuous and becomes in effect surface covered with triangular tiles, with gap between the separate tiles equal to the sphere's radius multiplied by 2×*shrinkFactor*. If zero, no shrinking is done.")) ); DECLARE_LOGGER; FUNCTOR2D(Facet,Sphere); DEFINE_FUNCTOR_ORDER_2D(Facet,Sphere); }; REGISTER_SERIALIZABLE(Ig2_Facet_Sphere_ScGeom); class Ig2_Facet_Sphere_ScGeom6D : public Ig2_Facet_Sphere_ScGeom { public : virtual bool go(const shared_ptr& cm1, const shared_ptr& cm2, const State& state1, const State& state2, const Vector3r& shift2, const bool& force, const shared_ptr& c); virtual bool goReverse( const shared_ptr& cm1, const shared_ptr& cm2, const State& state1, const State& state2, const Vector3r& shift2, const bool& force, const shared_ptr& c); YADE_CLASS_BASE_DOC(Ig2_Facet_Sphere_ScGeom6D,Ig2_Facet_Sphere_ScGeom,"Create an interaction geometry :yref:`ScGeom6D` from :yref:`Facet` and :yref:`Sphere`, representing the Facet with a projected virtual sphere of same radius.") FUNCTOR2D(Facet,Sphere); DEFINE_FUNCTOR_ORDER_2D(Facet,Sphere); }; REGISTER_SERIALIZABLE(Ig2_Facet_Sphere_ScGeom6D); class Ig2_Wall_Sphere_ScGeom: public IGeomFunctor{ public: virtual bool go(const shared_ptr& cm1, const shared_ptr& cm2, const State& state1, const State& state2, const Vector3r& shift2, const bool& force, const shared_ptr& c); YADE_CLASS_BASE_DOC_ATTRS(Ig2_Wall_Sphere_ScGeom,IGeomFunctor,"Create/update a :yref:`ScGeom` instance representing intersection of :yref:`Wall` and :yref:`Sphere`.", ((bool,noRatch,true,,"Avoid granular ratcheting")) ); FUNCTOR2D(Wall,Sphere); DEFINE_FUNCTOR_ORDER_2D(Wall,Sphere); }; REGISTER_SERIALIZABLE(Ig2_Wall_Sphere_ScGeom); trunk-2018.02b/pkg/dem/Ig2_PB_PB_ScGeom.cpp000077500000000000000000002756121324306050200200600ustar00rootroot00000000000000/*CWBoon 2016 */ /* Please cite: */ /* CW Boon, GT Houlsby, S Utili (2013). A new algorithm for contact detection between convex polygonal and polyhedral particles in the discrete element method. Computers and Geotechnics 44, 73-82. */ /* The numerical library is changed from CPLEX to CLP because subscription to the academic initiative is required to use CPLEX for free */ #ifdef YADE_POTENTIAL_BLOCKS #include"Ig2_PB_PB_ScGeom.hpp" #include #include //#include #include #include #include #include #include #include #include #include #include #include #include #include #include YADE_PLUGIN((Ig2_PB_PB_ScGeom) //#ifdef YADE_OPENGL // (Gl1_Ig2_PB_PB_ScGeom) // #endif ); CREATE_LOGGER(Ig2_PB_PB_ScGeom); bool Ig2_PB_PB_ScGeom::go(const shared_ptr& cm1,const shared_ptr& cm2,const State& state1, const State& state2, const Vector3r& shift2, const bool& force,const shared_ptr& c) { PotentialBlock *s1=static_cast(cm1.get()); PotentialBlock *s2=static_cast(cm2.get()); /* Short circuit if both particles are boundary particles */ if((s1->isBoundary==true)&&(s2->isBoundary==true)) {return false;} if((s1->isLining==true)&&(s2->isLining==true)) {return false;} if((s1->erase==true)&&(s2->isLining==true)) {return false;} if((s1->isLining==true)&&(s2->erase==true)) {return false;} bool hasGeom = false; Vector3r contactPt(0,0,0); shared_ptr scm; shared_ptr phys; double stepBisection = 0.001*std::min(s1->R,s2->R); if(stepBisectionR<<", R2: "<R<<", stepBisection: "<getId1()<<", id2: "<getId2()<geom){ hasGeom = true; scm=YADE_PTR_CAST(c->geom); if (scm->penetrationDepth>stepBisection ){ stepBisection = 0.5*scm->penetrationDepth;} if(stepBisectionpenetrationDepth<contactPoint; }else{ scm=shared_ptr(new ScGeom()); c->geom=scm; contactPt = 0.5*(state1.pos+state2.pos); } bool hasPhys=false; if(c->phys){ phys=YADE_PTR_CAST(c->phys); hasPhys=true; shearDir = phys->shearDir; //normalization should take place in the Contact Law } if(s1->isLining == true || s2->isLining==true){ double overlap = 0.0; if (s1->isLining == true){ contactPt = state1.pos; fA= evaluatePB(cm2,state2, contactPt); if(fA<0.0){ contact = true; avgNormal = contactPt; //getNormal(cm2,state2,contactPt); // avgNormal.normalize(); //avgNormal = -avgNormal; Vector3r step = avgNormal*stepBisection; int locationStuck = 5; getPtOnParticle2(cm2,state2,contactPt, -1.0*step, ptOnP2); overlap = (ptOnP2-contactPt).norm();// overlap; //avgNormal = contactPt; //avgNormal.normalize(); }else{contact=false;} }else{ contactPt = state2.pos; fB= evaluatePB(cm1,state1, contactPt); if (fB < 0.0){ contact = true; avgNormal = contactPt; //getNormal(cm1,state1,contactPt); // avgNormal.normalize(); avgNormal = -avgNormal; Vector3r step = avgNormal*stepBisection; int locationStuck = 5; getPtOnParticle2(cm1,state1,contactPt, 1.0*step, ptOnP1); overlap = (ptOnP1-contactPt).norm();// overlap; //avgNormal = contactPt; //avgNormal.normalize(); //avgNormal = -avgNormal; }else{contact=false;} } if (contact == true || c->isReal() || force){ if (contact == true){ scm->precompute(state1,state2,scene,c,avgNormal,!(hasGeom),Vector3r(0,0,0)/*shift 2 */, false /* avoidGranularRatcheting */); //Assign contact point and normal after precompute!!!! scm->contactPoint = contactPt; scm->penetrationDepth=overlap; if(std::isnan(avgNormal.norm())){std::cout<<"avgNormal: "<normal = avgNormal; if(hasPhys){ phys->normal= avgNormal; phys->ptOnP1 = contactPt; phys->ptOnP1 = ptOnP1; phys->ptOnP2 = ptOnP2; phys->initial1 = scm->contactPoint; phys->gap = scm->penetrationDepth; phys->tension = std::max(s1->tension[0],s2->tension[0]); phys->cohesion = std::max(s1->cohesion[0],s2->cohesion[0]); phys->jointLength = std::max(s1->liningLength,s2->liningLength); phys->contactArea = phys->jointLength*phys->unitWidth2D; } }else{ //scm->normal = Vector3r(0,0,0); scm->precompute(state1,state2,scene,c,avgNormal,!(hasGeom),Vector3r(0,0,0)/*shift 2 */, false /* avoidGranularRatcheting */); //Assign contact point and normal after precompute!!!! scm->contactPoint = contactPt;scm->penetrationDepth=-1.0; //scm->normal = Vector3r(0,0,0); } return true; }else{ scm->contactPoint = contactPt;scm->penetrationDepth=-1.0;scm->normal = Vector3r(0,0,0); return false; } } bool convergeFeasibility = true; fA= evaluatePB(cm1,state1, contactPt); fB = evaluatePB(cm2,state2, contactPt); //if (fA < -pow(10.0,-6) && fB < -pow(10.0,-6) ){ // contact = true; //}else{ contact = startingPointFeasibilityCLP( cm1, state1, cm2, state2, contactPt, convergeFeasibility); //} fA= evaluatePB(cm1,state1, contactPt); fB = evaluatePB(cm2,state2, contactPt); if (fA*fB<0.0){ std::cout<<"after clp fA: "<isBoundary: "<isBoundary<<", s2->isBoundary: "<isBoundary<isReal() || force){ if (contact == true){ normalP1 = getNormal(cm1,state1,contactPt); normalP1.normalize(); normalP2 = getNormal(cm2,state2,contactPt); normalP2.normalize(); //if(s1->isBoundary==true){avgNormal=normalP1;}else if(s2->isBoundary==true){avgNormal=-normalP2;}else{avgNormal = (normalP1 - normalP2);} #if 0 if(s1->isBoundary==true){avgNormal=normalP1;}else if(s2->isBoundary==true){avgNormal=-normalP2;}else{ avgNormal = (normalP1 - normalP2); } #endif avgNormal = (normalP1 - normalP2); avgNormal.normalize(); if(s1->fixedNormal==true){ avgNormal = s1->boundaryNormal;} if(s2->fixedNormal==true){ avgNormal = -s2->boundaryNormal;} Vector3r step = avgNormal*stepBisection; int locationStuck = 2; getPtOnParticle2(cm1,state1,contactPt, step, ptOnP1); getPtOnParticle2(cm2,state2,contactPt, -1.0*step, ptOnP2); Real penetrationDepth = (ptOnP2-ptOnP1).norm();// overlap; //std::cout<<"contactpoint: "<normal= avgNormal; phys->ptOnP1 = ptOnP1; phys->ptOnP2 = ptOnP2; phys->initial1 = scm->contactPoint; phys->gap = scm->penetrationDepth; bool calJointLength = phys->calJointLength; double jointLength = phys->jointLength; int smallerID = 1; bool twoD = phys->twoDimension; shearDir = phys->shearDir; shearDir.normalize(); /* Get contact area */ phys->prevJointLength = jointLength; double prevContactArea = phys->contactArea; phys->contactArea = getAreaPolygon2(cm1, state1, cm2, state2, contactPt, avgNormal, smallerID,calJointLength,shearDir,jointLength, twoD); //polygon2 if(jointLength < pow(10,-11) || std::isnan(jointLength) || calJointLength == false ) {jointLength = 1.0; /*std::min(s1->R,s2->R);*/} phys->jointLength = jointLength; phys->smallerID = smallerID; /* Get plane physical properties */ double phi_b1=0.0; double phi_b2=0.0; double phi_r1=0.0; double phi_r2=0.0; double JRC1=0.0; double JRC2=0.0; double JSC1=0.0; double JSC2=0.0; double cohesion1=0.0; double cohesion2=0.0; double sigmaC1=0.0; double sigmaC2=0.0; double asperity1=0.0;double asperity2=0.0;double tension1=0.0;double tension2=0.0; int jointType1 = 0; double lambda01=0.0;double lambda02=0.0;double heatCapacity1=0.0; double heatCapacity2 = 0.0; double hwater1 = 0.0; double hwater2 = 0.0; bool intactRock1=false; bool intactRock2=false;int noActive1 = 0; int noActive2 =0; int jointType2 = 0; if(phys->useFaceProperties ){ double f1 = evaluatePhys(cm1, state1, contactPt, phi_b1, phi_r1, JRC1, JSC1, cohesion1, sigmaC1, asperity1, tension1,lambda01,heatCapacity1,hwater1,intactRock1, noActive1, jointType1); double f2 = evaluatePhys(cm2, state2, contactPt, phi_b2, phi_r2, JRC2, JSC2, cohesion2, sigmaC2, asperity2, tension2,lambda02, heatCapacity2,hwater2,intactRock2, noActive2, jointType2); if(s1->isBoundary == true){ phys->phi_b = phi_b1; phys->phi_r = phi_r1; phys->JRC =JRC1; phys->JCS = JSC1; phys->cohesion = cohesion1; phys->sigmaC = sigmaC1; phys->asperity = asperity1; phys->tension = tension1; phys->lambda0 = lambda01; phys->heatCapacities = heatCapacity1; phys->hwater = hwater1; phys->jointType = jointType1; }else if(s2->isBoundary == true){ phys->phi_b = phi_b2; phys->phi_r = phi_r2; phys->JRC = JRC2; phys->JCS = JSC2; phys->cohesion = cohesion2; phys->sigmaC = sigmaC2; phys->asperity = asperity2; phys->tension = tension2; phys->lambda0 = lambda02; phys->heatCapacities = heatCapacity2; phys->hwater = hwater2; phys->jointType = jointType2; }else{ phys->phi_b = std::min(phi_b1,phi_b2); phys->phi_r = std::min(phi_r1,phi_r2); phys->JRC = 0.5*(JRC1+JRC2); phys->JCS = 0.5*(JSC1+JSC2); phys->cohesion = std::min(cohesion1,cohesion2); phys->sigmaC = 0.5*(sigmaC1 + sigmaC2); phys->asperity = 0.5*(asperity1+asperity2); phys->tension = std::min(tension1,tension2); phys->lambda0 = std::max(lambda01,lambda02); phys->heatCapacities = std::max(heatCapacity1,heatCapacity2); phys->hwater = std::max(hwater1,hwater2); phys->intactRock = true; phys->jointType = std::max(jointType1,jointType2); if(intactRock1==false || intactRock2 == false){ phys->intactRock = false;} } if(noActive1 == 1 || noActive2 == 1){phys->rockJointContact = true;}else{phys->rockJointContact = false; } } } scm->precompute(state1,state2,scene,c,avgNormal,!(hasGeom),Vector3r(0,0,0)/*shift 2 */, false /* avoidGranularRatcheting */); //Assign contact point and normal after precompute!!!! scm->contactPoint = contactPt; scm->penetrationDepth=penetrationDepth; if(std::isnan(avgNormal.norm())){std::cout<<"avgNormal: "<normal = avgNormal; }else{ //scm->normal = Vector3r(0,0,0); scm->precompute(state1,state2,scene,c,avgNormal,!(hasGeom),Vector3r(0,0,0)/*shift 2 */, false /* avoidGranularRatcheting */); //Assign contact point and normal after precompute!!!! scm->contactPoint = contactPt;scm->penetrationDepth=-1.0; //scm->normal = Vector3r(0,0,0); } return true; }else{ scm->contactPoint = contactPt;scm->penetrationDepth=-1.0;scm->normal = Vector3r(0,0,0); return false; } return false; } double Ig2_PB_PB_ScGeom::evaluatePhys(const shared_ptr& cm1, const State& state1, const Vector3r newTrial, double& phi_b, double& phi_r, double& JRC, double& JSC, double& cohesion, double& sigmaC, double& asperity, double& tension,double& lambda0,double& heatCapacity, double &hwater, bool &intactRock, int &activePlanesNo, int& jointType){ PotentialBlock *s1=static_cast(cm1.get()); ///////////////////Transforming trial values to local frame of particles////////////////// Vector3r tempP1 = newTrial - state1.pos; /* Direction cosines */ Vector3r localP1 = state1.ori.conjugate()*tempP1; Real x = localP1.x(); Real y = localP1.y(); Real z = localP1.z(); int planeNo = s1->a.size(); if (s1->phi_b.size() == 0){ return 0.0; } Real pSum2 = 0.0; int activeNo = 0; intactRock = true; for (int i=0; ia[i]*x + s1->b[i]*y + s1->c[i]*z - s1->d[i]; if (planehwater[i]; phi_b = s1->phi_b[i]; phi_r = s1->phi_r[i]; tension = s1->tension[i]; cohesion = s1->cohesion[i]; heatCapacity =s1->heatCapacity[i]; jointType = s1->jointType[i]; } JRC += s1->JRC[i]; JSC += s1->JCS[i]; sigmaC += s1->sigmaC[i]; asperity += s1->asperity[i]; lambda0 +=s1->lambda0[i]; hwater = std::max(hwater,s1->hwater[i]); phi_b = std::min(phi_b,s1->phi_b[i]); phi_r = std::min(phi_r,s1->phi_r[i]); tension = std::min(tension,s1->tension[i]); cohesion = std::min(cohesion,s1->cohesion[i]); heatCapacity =std::max(heatCapacity, s1->heatCapacity[i]); jointType = std::max(jointType,s1->jointType[i]); if(s1->intactRock[i] == false){intactRock = false;} activeNo++; //if(lambda0>pow(10,-5)){std::cout<<"lambda0: "<lambda0: "<lambda0[i]<<", i: "<0){ phi_b = phi_b; phi_r = phi_r; JRC = JRC/static_cast(activeNo); JSC = JSC/static_cast(activeNo); JRC = JRC/static_cast(activeNo); cohesion = cohesion; sigmaC = sigmaC/static_cast(activeNo); asperity = asperity/static_cast(activeNo); tension = tension; lambda0 = lambda0/static_cast(activeNo); heatCapacity = heatCapacity; jointType = jointType; hwater = hwater; }else{ phi_b = s1->phi_b[0]; phi_r = s1->phi_r[0]; JRC = s1->JRC[0]; JSC = s1->JCS[0]; cohesion = s1->cohesion[0]; sigmaC = s1->sigmaC[0]; asperity = s1->asperity[0]; tension = s1->tension[0]; lambda0 = s1->lambda0[0]; heatCapacity = 0.0; jointType = jointType; hwater = -1.0; } Real r = s1->r; Real R = s1->R; Real k = s1->k; /* Additional sphere */ Real sphere = (pow(x,2) + pow(y,2) + pow(z,2))/pow(R,2); /* Complete potential particle */ Real f = pSum2 - pow(r,2); activePlanesNo = activeNo; return f; } double Ig2_PB_PB_ScGeom::getSignedArea(const Vector3r pt1, const Vector3r pt2, const Vector3r pt3){ /* if positive, counter clockwise, 2nd point makes a larger angle */ /* if negative, clockwise, 3rd point makes a larger angle */ Eigen::MatrixXd triangle(4,2); triangle(0,0) = pt1.x(); triangle(0,1) = pt1.y(); // triangle(0,2) = pt1.z(); triangle(1,0) = pt2.x(); triangle(1,1) = pt2.y(); // triangle(1,2) = pt2.z(); triangle(2,0) = pt3.x(); triangle(2,1) = pt3.y(); // triangle(2,2) = pt3.z(); triangle(3,0) = pt1.x(); triangle(3,1) = pt1.y(); // triangle(3,2) = pt1.z(); double determinant = getDet(triangle); return determinant; //triangle.determinant(); } double Ig2_PB_PB_ScGeom::getAreaPolygon2(const shared_ptr& cm1, const State& state1, const shared_ptr& cm2, const State& state2,const Vector3r contactPoint, const Vector3r contactNormal, int& smaller, bool calJointLength, Vector3r shearDir, double& jointLength, bool twoD){ const double PI = 3.14159265358979323846; int countParticleA = 0; int countParticleB = 0;double areaTri = 0.0; PotentialBlock *s1=static_cast(cm1.get()); PotentialBlock *s2=static_cast(cm2.get()); double bisectionStepSize = 1.0;//*std::min(s1->R, s2->R); Vector3r ptOnBoundary = contactPoint; Matrix3r rotationMatrix; int count = 1; Vector3r orthogonalDir = Vector3r(contactNormal.y(), -contactNormal.x(),0.0); if(orthogonalDir.norm() 0.0){ /*counter-clockwise */ newSearchDir = -newSearchDir; } if (count == 0){ firstAxis = searchDirOri.cross(newSearchDir); } searchDirOri = newSearchDir; searchDir = searchDirOri;//*bisectionStepSize; prevPoint = ptOnBoundary; Vector3r ptOnP1(0,0,0); Vector3r ptOnP2(0,0,0); if( getPtOnParticleAreaNormal(cm1,state1,ptOnBoundary, searchDir, prevNoA, ptOnP1, searchDirA, newNoA)){ prevNoA = newNoA; }else{ std::cout<<"no intersection A? "< pow(10,-6)) { if( p1.norm()= 2){ area1(0,1) = prevPoint.x();area1(0,2) = ptOnBoundary.x(); area2(0,1) = prevPoint.y();area2(0,2) = ptOnBoundary.y(); area3(0,1) = prevPoint.z(); area3(0,2) = ptOnBoundary.z(); area1(1,1) = prevPoint.y();area1(1,2) = ptOnBoundary.y(); area2(1,1) = prevPoint.z();area2(1,2) = ptOnBoundary.z(); area3(1,1) = prevPoint.x(); area3(1,2) = ptOnBoundary.x(); area1(2,1) = 1.0; area1(2,2) = 1.0; area2(2,1) = 1.0; area2(2,2) = 1.0; area3(2,1) = 1.0; area3(2,2) = 1.0; areaTri += 0.5*sqrt(( pow(area1.determinant(),2) + pow(area2.determinant(),2) + pow(area3.determinant(),2) )); } if(count==1){ secondPoint = ptOnBoundary; } //#if 0 if(count==10){ distanceBackup = (newPt-secondPoint).norm(); areaTri = 0.0; } if(count>12){ if ( fabs((newPt-secondPoint).norm() - distanceBackup )60){ std::cout<<"area polygon count: "< tol);// && (newPt-secondPoint).norm() > tol) ); //std::cout<<"area polygon count: "< pow(10,-5)){searchDir = jointLength*searchDir;} Vector3r ptOnP1(0,0,0); Vector3r ptOnP2(0,0,0); getPtOnParticleArea(cm1,state1,contactPoint, searchDir, ptOnP1); getPtOnParticleArea(cm2,state2,contactPoint, searchDir, ptOnP2); if( (ptOnP1 - contactPoint).squaredNorm() < (ptOnP2 - contactPoint).squaredNorm() ){ ptOnBoundary1 = ptOnP1; }else{ ptOnBoundary1 = ptOnP2; } getPtOnParticleArea(cm1,state1,contactPoint, -searchDir, ptOnP1); getPtOnParticleArea(cm2,state2,contactPoint, -searchDir, ptOnP2); if( (ptOnP1 - contactPoint).squaredNorm() < (ptOnP2 - contactPoint).squaredNorm() ){ ptOnBoundary2 = ptOnP1; }else{ ptOnBoundary2 = ptOnP2; } jointLength = (ptOnBoundary1- ptOnBoundary2).norm(); } //std::cout<<"searchDir: "< countParticleB){smaller =1;}else{smaller =2;} return areaTri; } bool Ig2_PB_PB_ScGeom::getPtOnParticleAreaNormal(const shared_ptr& cm1, const State& state1, const Vector3r previousPt, const Vector3r prevDir, const int prevNo, Vector3r& newPt, Vector3r& newNormal, int& newNo){ //intersect a line and a plane other than the ID of the previous plane PotentialBlock *s1=static_cast(cm1.get()); //Matrix3r Q1 = state1.ori.toRotationMatrix(); Quaternionr Q1 = state1.ori; int planeNo = s1->a.size(); Vector3r intersection (0,0,0); double closestDistance = pow(10,11); newNo = -1; double fA = evaluatePB(cm1,state1, previousPt); //std::cout<<"fA: "<a[i], s1->b[i], s1->c[i]); planeNormal = Q1*planeNormal; double d = -1.0*( planeNormal.x()*(-state1.pos.x()) + planeNormal.y()*(-state1.pos.y()) + planeNormal.z()*(-state1.pos.z()) - s1->d[i] -s1->r); double dotProd = planeNormal.dot(prevDir); if(Mathr::Sign(dotProd) > 0.0 && fabs(dotProd) > pow(10.0,-3) ){ //planeNormal and searchDirection converges double u = (planeNormal.dot(previousPt) - d)/(planeNormal.dot(-prevDir)); Vector3r newPoint = previousPt + u*prevDir; if (fabs(planeNormal.dot(newPoint) - d ) > pow(10,-5) ){ std::cout<<"distance to plane: "<< fabs(planeNormal.dot(newPoint) - d ) <<", planeNormal: "<a[j], s1->b[j], s1->c[j]); planeNormal1 = Q1*planeNormal1; double d1 = -1.0*( planeNormal1.x()*(-state1.pos.x()) + planeNormal1.y()*(-state1.pos.y()) + planeNormal1.z()*(-state1.pos.z()) - s1->d[j] -s1->r); double check1 = planeNormal1.dot(newPoint) - d1; double check1ori = planeNormal1.dot(previousPt) - d1; double dotCheck = planeNormal1.dot(prevDir); std::cout<<"j: "<0.0001){std::cout<<"check : "<& cm1, const State& state1, Vector3r midPoint, Vector3r normal, Vector3r& ptOnParticle){ PotentialBlock *s1=static_cast(cm1.get()); ptOnParticle = midPoint; Real f = evaluatePB(cm1,state1, ptOnParticle); //SecondOrderCorrection(cm1,state1, ptOnParticle); Real fprevious = f; Real fOri =evaluatePB(cm1,state1,ptOnParticle); int counter = 0; //normal.normalize(); Vector3r step = normal*Mathr::Sign(f) *-1.0; Vector3r bracketA(0,0,0); Vector3r bracketB(0,0,0); Vector3r prevPt = ptOnParticle; do{ prevPt = ptOnParticle; ptOnParticle += step; fprevious = f; f = evaluatePB(cm1,state1, ptOnParticle); //SecondOrderCorrection(cm1,state1, ptOnParticle); counter++; if (counter == 50000 ){ //LOG_WARN("Initial point searching exceeded 500 iterations!"); std::cout<<"ptonparticleArea search exceeded 50000 iterations! step:"< 0.0 ); bracketA = ptOnParticle; bracketB = prevPt; //ptOnParticle -step; Vector3r zero(0,0,0); BrentZeroSurf(cm1,state1,bracketA, bracketB, zero); ptOnParticle = zero; //if( fabs(f)>0.1){std::cout<<"getInitial point f:"<& cm1, const State& state1, const shared_ptr& cm2, const State& state2, Vector3r& contactPt){ bool converge = true; PotentialBlock *s1=static_cast(cm1.get()); PotentialBlock *s2=static_cast(cm2.get()); int planeNoA = s1->a.size(); int planeNoB = s2->a.size(); int totalPlanes = planeNoA+planeNoB; Matrix3r QA = state1.ori.toRotationMatrix(); /*direction cosine */ Matrix3r QB = state2.ori.toRotationMatrix(); /*direction cosine */ double blasQ1[3*3]; double blasQ2[3*3]; double blasP1Q [planeNoA*3]; double d1 [planeNoA]; double blasP2Q [planeNoB*3]; double d2 [planeNoB]; int blasCount = 0; for(int i=0; i<3; i++){ for(int j=0; j<3; j++){ blasQ1[blasCount]=QA(j,i); blasQ2[blasCount]=QB(j,i); blasCount++; } } double blasP1[planeNoA*3]; for(int i=0; ia[i]; blasP1[3*i+1]=s1->b[i]; blasP1[3*i+2]=s1->c[i]; d1[i] = s1->d[i] + s1->r; } double blasP2[planeNoB*3]; for(int i=0; ia[i]; blasP2[3*i+1]=s2->b[i]; blasP2[3*i+2]=s2->c[i]; d2[i] = s2->d[i] + s2->r; } int incx =1; int incy=1; int blas3 = 3; char blasNT = 'N'; char blasT= 'T'; double blas0 = 0.0; double blas1 = 1.0; double blasNeg1 = -1.0; double HessTemp[3*totalPlanes]; dgemm_(&blasNT, &blasNT, &blas3, &planeNoA, &blas3, &blas1, &blasQ1[0], &blas3, &blasP1[0], &blas3, &blas0, &blasP1Q[0], &blas3); /*Matrix mulitplication Q1*P1 */ dgemm_(&blasNT, &blasNT, &blas3, &planeNoB, &blas3, &blas1, &blasQ2[0], &blas3, &blasP2[0], &blas3, &blas0, &blasP2Q[0], &blas3); /*Matrix mulitplication Q2*P2 */ for(unsigned int k=0; k orival+ backtrack*0.01*blasFprime){ backtrack *= 0.5; dcopy_(&varNo, &xx[0], &incx, &blasNewX[0], &incy); daxpy_(&varNo, &backtrack, &blasStep[0], &incx, &blasNewX[0], &incy); /* d = B - A*x */ // dcopy_(&totalPlanes, &B[0], &incx, &D[0], &incy); // dgemv_(&blasT, &blas3, &totalPlanes, &blasNeg1, &Atranspose[0], &blas3, &blasNewX[0], &incx, &blas1, &D[0], &incy); val=0.0; for(int i=0; i49){std::cout<<"custom analytic center iter: "<& cm1, const State& state1, const shared_ptr& cm2, const State& state2, Vector3r &contactPoint, bool &convergeFeasibility){ //timingDeltas->start(); Vector3r xGlobal (0,0,0); double rescale = 1.0; bool converge = true; /* minimise s */ /* s.t. Ax - s <= d*/ PotentialBlock *s1=static_cast(cm1.get()); PotentialBlock *s2=static_cast(cm2.get()); /* Parameters for particles A and B */ int planeNoA = s1->a.size(); int planeNoB = s2->a.size(); Matrix3r Q1 = state1.ori.toRotationMatrix().transpose(); //(state1.ori.conjugate()).toRotationMatrix(); Matrix3r Q2 = state2.ori.toRotationMatrix().transpose(); //(state2.ori.conjugate()).toRotationMatrix(); Eigen::MatrixXd A1 = Eigen::MatrixXd::Zero(planeNoA,3); for (int i=0; i < planeNoA; i++){ A1(i,0) = s1->a[i]/rescale; A1(i,1) = s1->b[i]/rescale; A1(i,2) = s1->c[i]/rescale; } Eigen::MatrixXd A2 = Eigen::MatrixXd::Zero(planeNoB,3); for (int i=0; i < planeNoB; i++){ A2(i,0) = s2->a[i]/rescale; A2(i,1) =s2->b[i]/rescale; A2(i,2) = s2->c[i]/rescale; } Eigen::MatrixXd AQ1 = A1*Q1; Eigen::MatrixXd AQ2 = A2*Q2; Eigen::MatrixXd pos1(3,1); pos1(0,0) = rescale*state1.pos.x(); pos1(1,0) = rescale*state1.pos.y(); pos1(2,0) = rescale*state1.pos.z(); Eigen::MatrixXd Q1pos1 = AQ1*pos1; Eigen::MatrixXd pos2(3,1); pos2(0,0) = rescale*state2.pos.x(); pos2(1,0) = rescale*state2.pos.y(); pos2(2,0) = rescale*state2.pos.z(); Eigen::MatrixXd Q2pos2 = AQ2*pos2; // Eigen::MatrixXd xinit(3,1); Eigen::MatrixXd b1 = Q1pos1;// - AQ1*xinit; Eigen::MatrixXd b2 = Q2pos2;// - AQ2*xinit; double constraintInit[planeNoA+planeNoB]; for (int i=0; id[i] + s1->r + Q1pos1(i,0); } for(int i=0; id[i] + s2->r + Q2pos2(i,0); } for (int k = 0; k < numberRows; k++) { rowLower[k] = -COIN_DBL_MAX; } for (int i = 0; i < planeNoA;i++){ int rowIndex[] = {0, 1, 2, 3}; double rowValue[] = {AQ1(i,0), AQ1(i,1), AQ1(i,2), -1.0}; model2.addRow(4, rowIndex, rowValue,rowLower[i], rowUpper[i]); } for (int i = 0; i < planeNoB;i++){ int rowIndex[] = {0, 1, 2, 3}; double rowValue[] = {AQ2(i,0), AQ2(i,1), AQ2(i,2), -1.0}; model2.addRow(4, rowIndex, rowValue,rowLower[planeNoA+i], rowUpper[planeNoA+i]); } model2.setLogLevel(0); model2.scaling(0); model2.setPrimalTolerance(pow(10,-9)) ; model2.primal(); //model2.writeMps("a.mps"); // Alternatively getColSolution() double * columnPrimal = model2.primalColumnSolution(); xGlobal = Vector3r(columnPrimal[0],columnPrimal[1],columnPrimal[2])/rescale; contactPoint = xGlobal; s = columnPrimal[3]; if(s>-pow(10,-15) || model2.status()!=0 ){ return false; }else{ return true; } } void Ig2_PB_PB_ScGeom::BrentZeroSurf(const shared_ptr& cm1, const State& state1, const Vector3r bracketA, const Vector3r bracketB, Vector3r& zero){ Real a = 0.0; Real b = 1.0; Real t = pow(10,-16); Real m = 0.0; Real p = 0.0; Real q = 0.0; Real r = 0.0; Real s = 0.0; Real c = 0.0; Real d = 0.0; Real e = 0.0; Vector3r bracketRange = bracketB- bracketA; Real fa = evaluatePB(cm1,state1, bracketA); //evaluateFNoSphere(cm1,state1, bracketA); // Real fb = evaluatePB(cm1,state1, bracketB); //evaluateFNoSphere(cm1,state1, bracketB); // if(fa*fb > 0.00001){ std::cout<<"fa: "< 0.0){ c = a; fc = fa; d = b-a; e = d; } if(fabs(fc) < fabs(fb) ){ a = b; b = c; c = a; fa = fb; fb = fc; fc = fa; } } tol = 2.0*DBL_EPSILON*fabs(b) + t; m = 0.5*(c-b); if (fabs(m) > tol && fabs(fb) > pow(10,-13) ){ if(fabs(e) < tol || fabs(fa) <= fabs(fb) ){ d = m; e = d; }else{ s = fb/fa; if(fabs(a - c) 0.0){ q = -q; }else{ p = -p; } s = e; e = d; if(( 2.0*p < (3.0*m*q - fabs(tol*q))) && (p < fabs(0.5*s*q)) ){ d = p/q; }else{ d = m; e = d; } } a = b; fa = fb; Real h = 0.0; if(fabs(d) >tol){ h = d; }else if(m > 0.0){ h = tol; }else{ h = - tol; } b = b + h; // h*m_unitVec; zero = bracketA + b*bracketRange; fb = evaluatePB(cm1,state1, zero); //evaluateFNoSphere(cm1,state1, zero); // }else{ zero = bracketA + b*bracketRange; //std::cout<<"b: "< tol && fabs(fb) > pow(10,-13) ); } /* Routine to get value of function (constraint) at a particular position */ double Ig2_PB_PB_ScGeom::evaluatePB(const shared_ptr& cm1, const State& state1, const Vector3r newTrial){ PotentialBlock *s1=static_cast(cm1.get()); ///////////////////Transforming trial values to local frame of particles////////////////// Vector3r tempP1 = newTrial - state1.pos; /* Direction cosines */ //state1.ori.normalize(); Vector3r localP1 = state1.ori.conjugate()*tempP1; Real x = localP1.x(); Real y = localP1.y(); Real z = localP1.z(); int planeNo = s1->a.size(); Real r = s1->r; int insideCount = 0; for (int i=0; ia[i]*x + s1->b[i]*y + s1->c[i]*z - s1->d[i]-r; //-pow(10,-10); if (Mathr::Sign(plane)*1.0<0.0){ insideCount++; } } /* Complete potential particle */ Real f = 1.0; if (insideCount == planeNo){ f = -1.0;} return f; } Vector3r Ig2_PB_PB_ScGeom::getNormal(const shared_ptr& cm1, const State& state1, const Vector3r newTrial){ PotentialBlock *s1=static_cast(cm1.get()); ///////////////////Transforming trial values to local frame of particles////////////////// Vector3r tempP1 = newTrial - state1.pos; /* Direction cosines */ Vector3r localP1(0,0,0); localP1 = state1.ori.conjugate()*tempP1; //the body has been rotated by state.ori. So I will need to rotate the frame by state.ori too Real x = localP1[0]; Real y = localP1[1]; Real z = localP1[2]; ////////////////////////////// assembling potential planes particle 1////////////////////////////// int planeNo = s1->a.size(); vectorp; Real pSum2 = 0.0; bool isSphere = true; int minNo = 0; double closestDistance = pow(10,12); for (int i=0; ia[i]*x + s1->b[i]*y + s1->c[i]*z -s1-> d[i]; if (planea[i],s1->b[i],s1->c[i]); double dist = normal.dot(Vector3r(0,1,0)); if(fabs(dist)<0.99 ){minNo = i; closestDistance = fabs(plane); } }else{ minNo = i; closestDistance = fabs(plane); } } plane = 0.0; }else{isSphere = false;} p.push_back(plane); pSum2 += pow(p[i],2); } Real r = s1->r; Real R = s1->R; Real k = s1->k; /* Additional sphere */ Real sphere = (pow(x,2) + pow(y,2) + pow(z,2))/pow(R,2); /* Complete potential particle */ Real f = (1.0-k)*(pSum2/pow(r,2)-1.0)+k*(sphere -1.0); //////////////////////////////// local first derivitive particle 1 ///////////////////////////////////// Real pdxSum = 0.0; Real pdySum = 0.0; Real pdzSum = 0.0; for (int i=0; ia[i]*p[i]); pdySum += (s1->b[i]*p[i]); pdzSum += (s1->c[i]*p[i]); } Real fdx = 2.0 * pdxSum; Real fdy = 2.0 * pdySum; Real fdz = 2.0 * pdzSum; if (isSphere == true){ fdx = s1->a[minNo];//2.0*x; // fdy = s1->b[minNo]; //2.0*y; // fdz = s1->c[minNo]; //2.0*z; // } ///////////////////// Assembling rotation matrix Qt for particle 1 ////////////////////////////// Matrix3r Q1=Eigen::Matrix3d::Zero(); //if(state1.ori==Quaternionr(1,0,0,0)){ // Q1.setIdentity(); //}else{ Real q0 = state1.ori.w(); Real q1 = state1.ori.x(); Real q2 = state1.ori.y(); Real q3 = state1.ori.z(); Q1(0,0) = 2.0*pow(q0,2.0) -1.0 + 2.0*pow(q1,2.0); Q1(0,1) = 2.0*q1*q2 + 2.0*q0*q3; Q1(0,2) = 2.0*q1*q3 - 2.0*q0*q2; Q1(1,0) = 2.0*q1*q2 - 2.0*q0*q3; Q1(1,1) = 2.0*pow(q0,2.0) -1.0 + 2.0*pow(q2,2.0); Q1(1,2) = 2.0*q2*q3 + 2.0*q0*q1; Q1(2,0) = 2.0*q1*q3 + 2.0*q0*q2; Q1(2,1) = 2.0*q2*q3 - 2.0*q0*q1; Q1(2,2) = 2.0*pow(q0,2) -1.0 + 2.0*pow(q3,2.0); //Matrix3r Q1n = state1.ori.toRotationMatrix(); //Matrix3r Q1c = state1.ori.conjugate().toRotationMatrix(); //std::cout<<"Q1: "<& cm1, const State& state1, const shared_ptr& cm2, const State& state2, Vector3r &contactPt){ PotentialBlock *s1=static_cast(cm1.get()); PotentialBlock *s2=static_cast(cm2.get()); Vector3r xGlobal(0.0,0.0,0.0); /* Parameters for particles A and B */ double rA = s1->r; double kA = s1->k; double RA = s1->R; double rB = s2->r; double kB = s2->k; double RB = s2->R; int planeNoA = s1->a.size(); int planeNoB = s2->a.size(); /* Variables to keep things neat */ double ksA = RA/sqrt(kA); double kpA = rA/sqrt(1.0 - kA); double ksB = RB/sqrt(kB); double kpB = rB/sqrt(1.0 - kB); int NUMCON = 3 + 1+ planeNoA + planeNoB; int NUMVAR = 6 + 2 + planeNoA + planeNoB; /* LINEAR CONSTRAINTS */ MSKboundkeye bkc[NUMCON]; bkc[0] = MSK_BK_FX; bkc[1] = MSK_BK_FX; bkc[2] = MSK_BK_FX; bkc[3] = MSK_BK_FX; for(int i=0; id[i]; }; for(int i=0; id[i]; }; /* BOUNDS */ MSKboundkeye bkx[NUMVAR ]; bkx[0] = MSK_BK_FR; bkx[1] = MSK_BK_FR; bkx[2] = MSK_BK_FR; bkx[3] = MSK_BK_FR; bkx[4] = MSK_BK_FR; bkx[5] = MSK_BK_FR; bkx[6] = MSK_BK_FR; bkx[7] = MSK_BK_FR; for(int i=0; i aval; vector aptrb; vector aptre; vector asub; Matrix3r Q1 = state1.ori.toRotationMatrix(); Matrix3r Q2 = state2.ori.toRotationMatrix(); int totalCount = 0; /* column 0 xA*/ aptrb.push_back(0); int count = 0; if(fabs(Q1(0,0))>pow(10,-15) ){ aval.push_back(ksA*Q1(0,0)); asub.push_back(0); count++; } if(fabs(Q1(1,0) )>pow(10,-15) ){ aval.push_back(ksA*Q1(1,0)); asub.push_back(1); count++; } if(fabs(Q1(2,0) )>pow(10,-15) ){ aval.push_back(ksA*Q1(2,0)); asub.push_back(2); count++; } for(int i=0; i < planeNoA; i++){ if(fabs(s1->a[i]) > pow(10,-15) ){ aval.push_back(ksA*s1->a[i]); asub.push_back(4+i); count++; } } aptre.push_back(count); totalCount += count; /* column 1 yA*/ aptrb.push_back(aptre[0]); count = 0; if(fabs(Q1(0,1) )>pow(10,-15) ){ aval.push_back(ksA*Q1(0,1)); asub.push_back(0); count++; } if(fabs(Q1(1,1))>pow(10,-15) ){ aval.push_back(ksA*Q1(1,1)); asub.push_back(1); count++; } if(fabs(Q1(2,1) )>pow(10,-15) ){ aval.push_back(ksA*Q1(2,1)); asub.push_back(2); count++; } for(int i=0; i < planeNoA; i++){ if(fabs(s1->b[i]) > pow(10,-15) ){ aval.push_back(ksA*s1->b[i]); asub.push_back(4+i); count++; } } aptre.push_back(aptrb[1] + count); totalCount += count; /* column 2 zA*/ aptrb.push_back(aptre[1]); count = 0; if(fabs(Q1(0,2) )>pow(10,-15) ){ aval.push_back(ksA*Q1(0,2)); asub.push_back(0); count++; } if(fabs(Q1(1,2))>pow(10,-15) ){ aval.push_back(ksA*Q1(1,2)); asub.push_back(1); count++; } if(fabs(Q1(2,2) )>pow(10,-15) ){ aval.push_back(ksA*Q1(2,2)); asub.push_back(2); count++; } for(int i=0; i < planeNoA; i++){ if(fabs(s1->c[i]) > pow(10,-15) ){ aval.push_back(ksA*s1->c[i]); asub.push_back(4+i); count++; } } aptre.push_back(aptrb[2] + count); totalCount += count; /* column 3 xB*/ aptrb.push_back(aptre[2]); count = 0; if(fabs(Q2(0,0))>pow(10,-15) ){ aval.push_back(-ksB*Q2(0,0)); asub.push_back(0); count++; } if(fabs(Q2(1,0) )>pow(10,-15) ){ aval.push_back(-ksB*Q2(1,0)); asub.push_back(1); count++; } if(fabs(Q2(2,0) )>pow(10,-15) ){ aval.push_back(-ksB*Q2(2,0)); asub.push_back(2); count++; } for(int i=0; i < planeNoB; i++){ if(fabs(s2->a[i]) > pow(10,-15) ){ aval.push_back(ksB*s2->a[i]); asub.push_back(4+planeNoA +i); count++; } } aptre.push_back(aptrb[3] + count); totalCount += count; /* column 4 yB*/ aptrb.push_back(aptre[3]); count = 0; if(fabs(Q2(0,1))>pow(10,-15) ){ aval.push_back(-ksB*Q2(0,1)); asub.push_back(0); count++; } if(fabs(Q2(1,1) )>pow(10,-15) ){ aval.push_back(-ksB*Q2(1,1)); asub.push_back(1); count++; } if(fabs(Q2(2,1) )>pow(10,-15) ){ aval.push_back(-ksB*Q2(2,1)); asub.push_back(2); count++; } for(int i=0; i < planeNoB; i++){ if(fabs(s2->b[i]) > pow(10,-15) ){ aval.push_back(ksB*s2->b[i]); asub.push_back(4+planeNoA+i); count++; } } aptre.push_back(aptrb[4] + count); totalCount += count; /* column 5 zB*/ aptrb.push_back(aptre[4]); count = 0; if(fabs(Q2(0,2))>pow(10,-15) ){ aval.push_back(-ksB*Q2(0,2)); asub.push_back(0); count++; } if(fabs(Q2(1,2) )>pow(10,-15) ){ aval.push_back(-ksB*Q2(1,2)); asub.push_back(1); count++; } if(fabs(Q2(2,2) )>pow(10,-15) ){ aval.push_back(-ksB*Q2(2,2)); asub.push_back(2); count++; } for(int i=0; i < planeNoB; i++){ if(fabs(s2->c[i]) > pow(10,-15) ){ aval.push_back(ksB*s2->c[i]); asub.push_back(4+planeNoA+i); count++; } } aptre.push_back(aptrb[5] + count); totalCount += count; /*column 6 */ aptrb.push_back(aptre[5]); aval.push_back(1.0); asub.push_back(3); aptre.push_back(aptrb[6]+1); totalCount += 1; /*column 7 */ aptrb.push_back(aptre[6]); aval.push_back(-1.0); asub.push_back(3); aptre.push_back(aptrb[7]+1); totalCount += 1; /*column 8 + i*/ for(int i=0; i < planeNoA; i++){ aptrb.push_back(aptre[8-1+i]); aval.push_back(-1.0*kpA); asub.push_back(4 + i); aptre.push_back(aptrb[8+i]+1); } totalCount += planeNoA; /*column 8 + planeNoA + i */ for(int i=0; i < planeNoB; i++){ aptrb.push_back(aptre[ 8 + planeNoA-1+i]); aval.push_back(-1.0*kpB); asub.push_back(4 + planeNoA + i); aptre.push_back(aptrb[8 + planeNoA+i]+1); } totalCount += planeNoB; MSKidxt i,j,csubA[1+ 3 + planeNoA], csubB[1+ 3 + planeNoB] ; double xx[NUMVAR]; MSKenv_t & env=mosekEnv; MSKtask_t task; MSKrescodee & r=mosekTaskEnv; if ( r==MSK_RES_OK ) { /* Directs the log stream to the 'printstr' function. */ // MSK_linkfunctoenvstream(env,MSK_STREAM_LOG,NULL,NULL); //printstr // MSK_linkfunctoenvstream(env,MSK_STREAM_LOG,NULL,NULL); } if ( r==MSK_RES_OK ) { /* Create the optimization task. */ r = MSK_maketask(env,NUMCON,NUMVAR,&task); if ( r==MSK_RES_OK ) { // MSK_linkfunctotaskstream(task,MSK_STREAM_LOG,NULL,printstr); /* Give MOSEK an estimate of the size of the input data. This is done to increase the speed of inputting data. However, it is optional. */ if (r == MSK_RES_OK) r = MSK_putmaxnumvar(task,NUMVAR); if (r == MSK_RES_OK) r = MSK_putmaxnumcon(task,NUMCON); int NUMANZ = totalCount; if (r == MSK_RES_OK) r = MSK_putmaxnumanz(task,NUMANZ); /* APBend 'NUMCON' empty constraints. The constraints will initially have no bounds. */ if ( r == MSK_RES_OK ) r = MSK_aPBend(task,MSK_ACC_CON,NUMCON); /* APBend 'NUMVAR' variables. The variables will initially be fixed at zero (x=0). */ if ( r == MSK_RES_OK ) r = MSK_aPBend(task,MSK_ACC_VAR,NUMVAR); /* Optionally add a constant term to the objective. */ if ( r ==MSK_RES_OK ) r = MSK_putcfix(task,0.0); for(j=0; j& cm1, const State& state1, Vector3r midPoint, Vector3r normal, Vector3r& ptOnParticle){ PotentialBlock *s1=static_cast(cm1.get()); ptOnParticle = midPoint; Real f = evaluatePB(cm1,state1, ptOnParticle);//evaluateFNoSphere(cm1,state1, ptOnParticle); // Real fprevious = f; int counter = 0; //normal.normalize(); Vector3r step = normal*Mathr::Sign(f) *-1.0; Vector3r bracketA(0,0,0); Vector3r bracketB(0,0,0); do{ ptOnParticle += step; fprevious = f; f = evaluatePB(cm1,state1, ptOnParticle); //evaluateFNoSphere(cm1,state1, ptOnParticle); // counter++; if (counter == 50000 ){ //LOG_WARN("Initial point searching exceeded 500 iterations!"); std::cout<<"ptonparticle2 search exceeded 50000 iterations! step:"< 0.0 ); bracketA = ptOnParticle; bracketB = ptOnParticle -step; Vector3r zero(0,0,0); BrentZeroSurf(cm1,state1,bracketA, bracketB, zero); ptOnParticle = zero; //if( fabs(f)>0.1){std::cout<<"getInitial point f:"<& cm1, const State& state1, const shared_ptr& cm2, const State& state2, Vector3r &contactPt, bool warmstart){ //timingDeltas->start(); PotentialBlock *s1=static_cast(cm1.get()); PotentialBlock *s2=static_cast(cm2.get()); Vector3r xGlobal(0.0,0.0,0.0); int iter = 0; int totalIter = 0; /* Parameters for particles A and B */ double rA = s1->r;double kA = s1->k;double RA = s1->R; double rB = s2->r;double kB = s2->k;double RB = s2->R; int planeNoA = s1->a.size();int planeNoB = s2->a.size(); int varNo = 3+1+planeNoA+planeNoB; int planeNoAB = planeNoA + planeNoB; int varNo2 = varNo*varNo; int planeNoA3 = 3+planeNoA; int planeNoB3=3+planeNoB; int planeNoA2 =planeNoA*planeNoA; int planeNoB2=planeNoB*planeNoB; Matrix3r QA = state1.ori.conjugate().toRotationMatrix(); /*direction cosine */ Matrix3r QB = state2.ori.conjugate().toRotationMatrix(); /*direction cosine */ int blas3 = 3; char blasNT = 'N'; char blasT= 'T'; int blas1planeNoA = std::max(1,planeNoA); int blas1planeNoB = std::max(1,planeNoB); int blas1planeNoAB = std::max(1,planeNoAB); double blas0 = 0.0; double blas1 = 1.0; double blasNeg1 = -1.0; double blasQA[9]; double blasQB[9]; double blasPosA[3]; double blasPosB[3]; double blasContactPt[3]; double blasC[varNo]; for (int i=0; ia[i]*blasLocalP1[0] + s1->b[i]*blasLocalP1[1] + s1->c[i]*blasLocalP1[2] - s1->d[i]; if (planea[i]; blasP1[i+planeNoA] = s1->b[i]; blasP1[i+2*planeNoA] = s1->c[i]; blasD1[i] = s1->d[i]; } //Eigen::MatrixXd P1Q = P1*QA; //transA = 'N'; transB = 'N'; blasM = planeNoA; blasN = 3; blasK = 3; // blasLDA = std::max(1,blasM); blasLDC = std::max(1,blasM); blasLDB = 3; blasAlpha=1.0; blasBeta = 0.0; //dgemm_(&transA, &transB, &blasM, &blasN, &blasK, &blasAlpha, &blasP1[0], &blasLDA, &blasQA[0], &blasLDB, &blasBeta, &blasP1Q[0], &blasLDC); dgemm_(&blasNT, &blasNT, &planeNoA, &blas3, &blas3, &blas1, &blasP1[0], &blas1planeNoA, &blasQA[0], &blasLDB, &blas0, &blasP1Q[0], &blas1planeNoA); Real sphereA = (pow(blasLocalP1[0],2) + pow(blasLocalP1[1],2) + pow(blasLocalP1[2],2))/pow(RA,2); Real sA = (1.0-kA)*(pertSumA2/pow(rA,2) - 1.0)+kA*(sphereA -1.0); /* fB */ double blasTemPB2[3]; double blasLocalP2[3]; for(int i=0; i<3; i++){ blasTemPB2[i] = blasContactPt[i] - blasPosB[i]; } //transA = 'N'; blasM = 3; blasN = 3; blasLDA = 3; blasAlpha=1.0; blasBeta = 0.0; //dgemv_(&transA, &blasM, &blasN, &blasAlpha, &blasQB[0], &blasLDA, &blasTemPB2[0], &incx, &blasBeta, &blasLocalP2[0], &incy); dgemv_(&blasNT, &blas3, &blas3, &blas1, &blasQB[0], &blas3, &blasTemPB2[0], &incx, &blas0, &blasLocalP2[0], &incy); // Vector3r temPB2 = contactPt - posB; // Vector3r localP2 = QB*temPB2; /*P2Q*/ //Eigen::MatrixXd P2 = Eigen::MatrixXd::Zero(planeNoB,3); /*d2*/ //Eigen::MatrixXd d2 = Eigen::MatrixXd::Zero(planeNoB,1); double blasP2[planeNoB*3]; double blasD2[planeNoB]; double blasP2Q[planeNoB*3]; double pertSumB2 = 0.0; for (int i=0; ia[i]*blasLocalP2[0] + s2->b[i]*blasLocalP2[1] + s2->c[i]*blasLocalP2[2] - s2->d[i]; if (planea[i]; blasP2[i+planeNoB] = s2->b[i]; blasP2[i+2*planeNoB] = s2->c[i]; blasD2[i] = s2->d[i]; } //Eigen::MatrixXd P2Q = P2*QB; //transA = 'N'; transB = 'N'; blasM = planeNoB; blasN = 3; blasK = 3; //blasLDA = std::max(1,blasM); blasLDC = std::max(1,blasM); blasLDB = 3; blasAlpha=1.0; blasBeta = 0.0; // dgemm_(&transA, &transB, &blasM, &blasN, &blasK, &blasAlpha, &blasP2[0], &blasLDA, &blasQB[0], &blasLDB, &blasBeta, &blasP2Q[0], &blasLDC); dgemm_(&blasNT, &blasNT, &planeNoB, &blas3, &blas3, &blasAlpha, &blasP2[0], &blas1planeNoB, &blasQB[0], &blas3, &blasBeta, &blasP2Q[0], &blas1planeNoB); Real sphereB = (pow(blasLocalP2[0],2) + pow(blasLocalP2[1],2) + pow(blasLocalP2[2],2))/pow(RB,2); Real sB = (1.0-kB)*(pertSumB2/pow(rB,2) - 1.0)+kB*(sphereB -1.0); //sPert = fabs(fA-fB); s = std::max(sqrt(fabs(sA+1.0)), sqrt(fabs(sB+1.0))) + sPert; //sqrt(fA+fB+2.0)+sPert; //sqrt(std::max(fabs(fA+1.0), fabs(fB+1.0))) + sPert; // //x[3] = s; blasX[3]= s; /////////////////// algebra formulation of the SOCP /////////////////// /* c */ // Eigen::MatrixXd c=Eigen::MatrixXd::Zero(varNo,1); // c[3] = 1.0; double blasA1[(3+planeNoA)*varNo]; double blasA2[(3+planeNoB)*varNo]; /* Second order cone constraints */ /* A1 */ //Eigen::MatrixXd A1(3+planeNoA,varNo); //Matrix3r QAs=kAs*QA; //cwise() double blasQAs[9]; int noElements=9; double scaleFactor = kAs; dcopy_(&noElements, &blasQA[0], &incx, &blasQAs[0], &incx); dscal_(&noElements, &scaleFactor, &blasQAs[0], &incx); // A1 << QAs,Eigen::MatrixXd::Zero(3,1+planeNoAB), // Eigen::MatrixXd::Zero(planeNoA,4),kAp*Eigen::MatrixXd::Identity(planeNoA, planeNoA),Eigen::MatrixXd::Zero(planeNoA,planeNoB); memset(blasA1,0.0,sizeof(blasA1)); for (int i=0; i<3; i++){ blasA1[i] = blasQAs[i]; blasA1[i+planeNoA3] = blasQAs[i+3]; blasA1[i+2*planeNoA3] = blasQAs[i+6]; } for (int i=0; icheckpoint("setup"); while(totalIter<500){ penalty = 1.0/t; /* Newton's method */ /* s=x[3]; */ s=blasX[3]; /* temp variables */ /* w1 = A1*x + b1; */ noElements = 3+planeNoA; dcopy_(&noElements, &blasB1[0], &incx, &blasW1[0], &incy); //transA = 'N'; blasM = 3+planeNoA; blasN = varNo; //blasLDA = 3+planeNoA; blasAlpha = 1.0; blasBeta = 1.0; incx =1; incy=1; //dgemv_(&transA, &blasM, &blasN, &blasAlpha, &blasA1[0], &blasLDA, &blasX[0], &incx, &blasBeta, &blasW1[0], &incy); dgemv_(&blasNT, &planeNoA3, &varNo, &blas1, &blasA1[0], &planeNoA3, &blasX[0], &incx, &blas1, &blasW1[0], &incy); /* w2 = A2*x + b2; */ noElements = 3+planeNoB; dcopy_(&noElements, &blasB2[0], &incx, &blasW2[0], &incy); //transA = 'N'; blasM = 3+planeNoB; blasN = varNo; //blasLDA = 3+planeNoB; blasAlpha = 1.0; blasBeta = 1.0; //dgemv_(&transA, &blasM, &blasN, &blasAlpha, &blasA2[0], &blasLDA, &blasX[0], &incx, &blasBeta, &blasW2[0], &incy); dgemv_(&blasNT, &planeNoB3, &varNo, &blas1, &blasA2[0], &planeNoB3, &blasX[0], &incx, &blas1, &blasW2[0], &incy); /* u1 = s*s - w1.dot(w1); */ /* u2 = s*s - w2.dot(w2); */ //noElements = 3+planeNoA; blasW1dot = ddot_(&planeNoA3, &blasW1[0], &incx, &blasW1[0], &incy); //noElements = 3+planeNoB; blasW2dot = ddot_(&planeNoB3, &blasW2[0], &incx, &blasW2[0], &incy); u1 =s*s -blasW1dot; u2 =s*s -blasW2dot; /* wL = bL - AL*x; */ noElements = planeNoAB; dcopy_(&noElements, &blasBL[0], &incx, &blasWL[0], &incy); //transA = 'N'; blasN = varNo; //blasM = planeNoAB; blasLDA = std::max(1,planeNoAB); blasAlpha = -1.0; blasBeta = 1.0; //dgemv_(&transA, &blasM, &blasN, &blasAlpha, &blasAL[0], &blasLDA, &blasX[0], &incx, &blasBeta, &blasWL[0], &incy); dgemv_(&blasNT, &planeNoAB, &varNo, &blasNeg1, &blasAL[0], &blas1planeNoAB, &blasX[0], &incx, &blas1, &blasWL[0], &incy); for (int i=0; icheckpoint("assemble H and g"); //std::cout<<"before Chol, totalIter: "<checkpoint("Cholesky"); //fprime = step.transpose()*g; //noElements = varNo; blasFprime = ddot_(&varNo, &blasStep[0], &incx, &blasGrad[0], &incy); wLlogsum = 0.0; for (int i=0; i0){ minWL = blasWL[0]; } for(int i=0; i0){ minWL = blasWL[0]; } for(int i=0; icheckpoint("barrier search"); wLlogsum = 0.0; for (int i=0; i val + backtrack*0.01*blasFprime){ backtrack = 0.5*backtrack; //for (int i = 0; icheckpoint("line search"); noElements = varNo; dcopy_(&noElements, &blasNewX[0], &incx, &blasX[0], &incy); if(blasFprime >0.0){ std::cout<<"count: "<0.001 ){std::cout<<"inside fA-fB: "<checkpoint("newton"); return true; } t = std::min(t*mu, (2.0*m+1.0)/tol); iter = 0; totalIter = totalIter+1; } if(totalIter>100){ std::cout<<"totalIter: "<checkpoint("complete"); } return ( true ); } #endif // YADE_POTENTIAL_BLOCKS trunk-2018.02b/pkg/dem/Ig2_PB_PB_ScGeom.hpp000077500000000000000000000150521324306050200200530ustar00rootroot00000000000000/*CWBoon 2016 */ /* Please cite: */ /* CW Boon, GT Houlsby, S Utili (2013). A new algorithm for contact detection between convex polygonal and polyhedral particles in the discrete element method. Computers and Geotechnics 44, 73-82. */ /* The numerical library is changed from CPLEX to CLP because subscription to the academic initiative is required to use CPLEX for free */ #ifdef YADE_POTENTIAL_BLOCKS #pragma once #include #include #include #include #include #include #include # if 0 #include #include #include #include #endif #include #include #include #include #include //#include "/home/boon/coin-Clp/Clp/src/ClpSimplex.hpp" //#include "/home/boon/coin-Clp/CoinUtils/src/CoinHelperFunctions.hpp" //#include "/home/boon/coin-Clp/CoinUtils/src/CoinTime.hpp" //#include "/home/boon/coin-Clp/CoinUtils/src/CoinBuild.hpp" //#include "/home/boon/coin-Clp/CoinUtils/src/CoinModel.hpp" #include #include class Ig2_PB_PB_ScGeom: public IGeomFunctor { //protected: //static std::ofstream output; #if 0 std::string myfile; std::string Key; MSKrescodee mosekTaskEnv; MSKenv_t mosekEnv; #endif public : virtual bool go(const shared_ptr& cm1, const shared_ptr& cm2, const State& state1, const State& se32, const Vector3r& shift2, const bool& force, const shared_ptr& c); double getSignedArea(const Vector3r pt1,const Vector3r pt2, const Vector3r pt3); double evaluatePB(const shared_ptr& cm1, const State& state1, const Vector3r newTrial); void getPtOnParticle2(const shared_ptr& cm1, const State& state1, Vector3r previousPt, Vector3r normal, Vector3r& newlocalPoint); void getPtOnParticleArea(const shared_ptr& cm1, const State& state1, Vector3r previousPt, Vector3r normal, Vector3r& newlocalPoint); bool getPtOnParticleAreaNormal(const shared_ptr& cm1, const State& state1, const Vector3r previousPt, const Vector3r prevDir, const int prevNo, Vector3r& newlocalPoint, Vector3r& newNormal, int& newNo); bool contactPtMosekF2(const shared_ptr& cm1, const State& state1, const shared_ptr& cm2, const State& state2, Vector3r &contactPt); double getDet(const Eigen::MatrixXd A); bool customSolve(const shared_ptr& cm1, const State& state1, const shared_ptr& cm2, const State& state2, Vector3r &contactPt, bool warmstart); double evaluatePhys(const shared_ptr& cm1, const State& state1, const Vector3r newTrial, double& phi_b, double& phi_r, double& JRC, double& JSC, double& cohesion, double& sigmaC, double& asperity, double& tension, double &lambda0, double &heatCapacity, double &hwater, bool &intactRock, int &activePlanesNo, int &jointType); Vector3r getNormal(const shared_ptr& cm1, const State& state1, const Vector3r newTrial); void BrentZeroSurf(const shared_ptr& cm1, const State& state1, const Vector3r bracketA, const Vector3r bracketB, Vector3r& zero); bool startingPointFeasibilityCLP(const shared_ptr& cm1, const State& state1, const shared_ptr& cm2, const State& state2, Vector3r &contactPoint, bool &convergeFeasibility); bool customSolveAnalyticCentre(const shared_ptr& cm1, const State& state1, const shared_ptr& cm2, const State& state2, Vector3r& contactPt); double getAreaPolygon2(const shared_ptr& cm1, const State& state1, const shared_ptr& cm2, const State& state2, const Vector3r contactPt, const Vector3r contactNormal, int& smaller, bool calJointLength, Vector3r shearDir, double& jointLength, bool twoDimension); ///////////////////////////////////////////////////////////////////////////////////////////////////// YADE_CLASS_BASE_DOC_ATTRS_CTOR(Ig2_PB_PB_ScGeom,IGeomFunctor,"PB", ((double, accuracyTol, pow(10,-7),, "accuracy desired, tolerance criteria for SOCP")) ((double, stepAngle, pow(10,-2),, "accuracy desired, tolerance criteria for SOCP")) ((double,interactionDetectionFactor,1.0,,"bool to avoid granular ratcheting")) ((Vector3r, twoDdir, Vector3r(0,1,0),, "to get radius of curvature")) ((bool,twoDimension,false,,"bool to avoid granular ratcheting")), //((std::string,myfile,"./PotentialBlocks"+"","string")), //timingDeltas=shared_ptr(new TimingDeltas); //mosekTaskEnv = MSK_makeenv(&mosekEnv,NULL,NULL,NULL,NULL); //mosekTaskEnv = MSK_initenv(mosekEnv); ); FUNCTOR2D(PotentialBlock,PotentialBlock); // needed for the dispatcher, even if it is symmetric DEFINE_FUNCTOR_ORDER_2D(PotentialBlock,PotentialBlock); DECLARE_LOGGER; }; REGISTER_SERIALIZABLE(Ig2_PB_PB_ScGeom); #ifdef __cplusplus extern "C" { #endif /* LAPACK LU */ //int dgesv(int varNo, int varNo2, double *H, int varNo3, int *pivot, double* g, int varNo4, int info){ extern void dgesv_(const int *N, const int *nrhs, double *Hessian, const int *lda, int *ipiv, double *gradient, const int *ldb, int *info); // int ans; // dgesv_(&varNo, &varNo2, H, &varNo3, pivot,g, &varNo4, &ans); // return ans; //} /* LAPACK Cholesky */ extern void dpbsv_(const char *uplo, const int *n, const int *kd, const int *nrhs, double *AB, const int *ldab, double *B, const int *ldb, int *info); /* LAPACK QR */ extern void dgels_(const char *Trans, const int *m, const int *n, const int *nrhs, double *A, const int *lda, double *B, const int *ldb, const double *work, const int *lwork, int *info); /*BLAS */ extern void dgemm_(const char *transA, const char *transB, const int *m, const int *n, const int *k, const double *alpha, double *A, const int *lda, double *B, const int *ldb, const double *beta, double *C, const int *ldc); extern void dgemv_(const char *trans, const int *m, const int *n, const double *alpha, double *A, const int *lda, double *x, const int *incx, const double *beta, double *y, const int *incy); extern void dcopy_(const int *N, double *x, const int *incx, double *y, const int *incy); extern double ddot_(const int *N, double *x, const int *incx, double *y, const int *incy); extern void daxpy_(const int *N, const double *da, double *dx, const int *incx, double *dy, const int *incy); extern void dscal_(const int *N, const double *alpha, double *x, const int *incx); void dsyev_(const char *jobz, const char *uplo, const int *N, double *A, const int *lda, double *W, double *work, int *lwork, int *info); #ifdef __cplusplus }; #endif #endif // YADE_POTENTIAL_BLOCKS trunk-2018.02b/pkg/dem/Ig2_PP_PP_ScGeom.cpp000066400000000000000000001642341324306050200201060ustar00rootroot00000000000000/*CWBoon 2015 */ /* C.W. Boon, G.T. Houlsby, S. Utili (2013). A new contact detection algorithm for three-dimensional non-spherical particles. Powder Technology, 248, pp 94-102. */ /* code for calling MOSEK was for ver 6. Please uncomment if you have the licence */ #ifdef YADE_POTENTIAL_PARTICLES #include "Ig2_PP_PP_ScGeom.hpp" #include #include //#include #include #include #include #include #include #include #include #include #include #include #include #include #include YADE_PLUGIN((Ig2_PP_PP_ScGeom) //#ifdef YADE_OPENGL // (Gl1_Ig2_PP_PP_ScGeom) // #endif ); CREATE_LOGGER(Ig2_PP_PP_ScGeom); bool Ig2_PP_PP_ScGeom::go(const shared_ptr& cm1,const shared_ptr& cm2,const State& state1, const State& state2, const Vector3r& shift2, const bool& force,const shared_ptr& c) { PotentialParticle *s1=static_cast(cm1.get()); PotentialParticle *s2=static_cast(cm2.get()); /* Short circuit if both particles are boundary particles */ if((s1->isBoundary==true)&&(s2->isBoundary==true)) { return false; } bool hasGeom = false; Vector3r contactPt(0,0,0); shared_ptr scm; Real stepBisection = 0.001*std::min(s1->R,s2->R); if(stepBisectionR<<", R2: "<R<<", stepBisection: "<getId1()<<", id2: "<getId2()<geom) { hasGeom = true; scm=YADE_PTR_CAST(c->geom); if (scm->penetrationDepth>stepBisection ) { stepBisection = 0.5*scm->penetrationDepth; } if(stepBisectionpenetrationDepth<contactPoint; } else { scm=shared_ptr(new ScGeom()); c->geom=scm; contactPt = 0.5*(state1.pos+state2.pos); } converge = true; fA= evaluatePP(cm1,state1, contactPt); fB = evaluatePP(cm2,state2, contactPt); //Default does not have warmstart //if(fA < 0.0 && fB <0.0){ // converge = customSolve(cm1,state1,cm2,state2,contactPt,true); //}else{ converge = customSolve(cm1,state1,cm2,state2,contactPt,false); //} // if you have mosek uncomment this. Mosek is more robust but slightly slower as an external library #ifdef YADE_MOSEK /* Mosek */ if( converge==false ) { //std::cout<<"mosek used"<start(); if (fA < 0.0 && fB < 0.0 && converge == true) { contact = true; } else { contact = false; contactPt = 0.5*(state1.pos+state2.pos); } //////////////////////////////////////////////////////////* PASS VARIABLES TO ScGeom Functor *///////////////////////////////////////////////////////////// //* 1. Get overlap *// //* 2. Get information on active planes. //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// if (contact == true || c->isReal() || force) { if (contact == true) { normalP1 = getNormal(cm1,state1,contactPt); normalP1.normalize(); normalP2 = getNormal(cm2,state2,contactPt); normalP2.normalize(); //if(s1->isBoundary==true){avgNormal=normalP1;}else if(s2->isBoundary==true){avgNormal=-normalP2;}else{avgNormal = (normalP1 - normalP2);} avgNormal = (normalP1 - normalP2); avgNormal.normalize(); if(s1->fixedNormal==true) { avgNormal = s1->boundaryNormal; } if(s2->fixedNormal==true) { avgNormal = -s2->boundaryNormal; } Vector3r step = avgNormal*stepBisection; //int locationStuck = 2; getPtOnParticle2(cm1,state1,contactPt, step, ptOnP1); getPtOnParticle2(cm2,state2,contactPt, -1.0*step, ptOnP2); vector points; Real penetrationDepth = (ptOnP2-ptOnP1).norm();// overlap; //std::cout<<"contactpoint: "<precompute(state1,state2,scene,c,avgNormal,!(hasGeom),Vector3r(0,0,0)/*shift 2 */, false /* avoidGranularRatcheting */); //Assign contact point and normal after precompute!!!! scm->contactPoint = contactPt; scm->penetrationDepth=penetrationDepth; if(std::isnan(avgNormal.norm())) { //std::cout<<"avgNormal: "<normal = avgNormal; } else { //scm->normal = Vector3r(0,0,0); scm->precompute(state1,state2,scene,c,avgNormal,!(hasGeom),Vector3r(0,0,0)/*shift 2 */, false /* avoidGranularRatcheting */); //Assign contact point and normal after precompute!!!! scm->contactPoint = contactPt; scm->penetrationDepth=-1.0; //scm->normal = Vector3r(0,0,0); } return true; } else { scm->contactPoint = contactPt; scm->penetrationDepth=-1.0; scm->normal = Vector3r(0,0,0); return false; } return false; } void Ig2_PP_PP_ScGeom::BrentZeroSurf(const shared_ptr& cm1, const State& state1, const Vector3r bracketA, const Vector3r bracketB, Vector3r& zero) { Real a = 0.0; Real b = 1.0; Real t = pow(10,-16); Real m = 0.0; Real p = 0.0; Real q = 0.0; Real r = 0.0; Real s = 0.0; Real c = 0.0; Real d = 0.0; Real e = 0.0; Vector3r bracketRange = bracketB- bracketA; Real fa = evaluatePP(cm1,state1, bracketA); //evaluateFNoSphere(cm1,state1, bracketA); // Real fb = evaluatePP(cm1,state1, bracketB); //evaluateFNoSphere(cm1,state1, bracketB); // if(fa*fb > 0.00001) { //std::cout<<"fa: "< 0.0) { c = a; fc = fa; d = b-a; e = d; } if(fabs(fc) < fabs(fb) ) { a = b; b = c; c = a; fa = fb; fb = fc; fc = fa; } } tol = 2.0*DBL_EPSILON*fabs(b) + t; m = 0.5*(c-b); if (fabs(m) > tol && fabs(fb) > pow(10,-13) ) { if(fabs(e) < tol || fabs(fa) <= fabs(fb) ) { d = m; e = d; } else { s = fb/fa; if(fabs(a - c) 0.0) { q = -q; } else { p = -p; } s = e; e = d; if(( 2.0*p < (3.0*m*q - fabs(tol*q))) && (p < fabs(0.5*s*q)) ) { d = p/q; } else { d = m; e = d; } } a = b; fa = fb; Real h = 0.0; if(fabs(d) >tol) { h = d; } else if(m > 0.0) { h = tol; } else { h = - tol; } b = b + h; // h*m_unitVec; zero = bracketA + b*bracketRange; fb = evaluatePP(cm1,state1, zero); //evaluateFNoSphere(cm1,state1, zero); // } else { zero = bracketA + b*bracketRange; //std::cout<<"b: "< tol && fabs(fb) > pow(10,-13) ); } /* Routine to get value of function (constraint) at a particular position */ Real Ig2_PP_PP_ScGeom::evaluatePP(const shared_ptr& cm1, const State& state1, const Vector3r newTrial) { PotentialParticle *s1=static_cast(cm1.get()); ///////////////////Transforming trial values to local frame of particles////////////////// Vector3r tempP1 = newTrial - state1.pos; /* Direction cosines */ //state1.ori.normalize(); Vector3r localP1 = state1.ori.conjugate()*tempP1; Real x = localP1.x(); Real y = localP1.y(); Real z = localP1.z(); int planeNo = s1->a.size(); Real pSum2 = 0.0; for (int i=0; ia[i]*x + s1->b[i]*y + s1->c[i]*z - s1->d[i]; if (planer; Real R = s1->R; Real k = s1->k; /* Additional sphere */ Real sphere = (pow(x,2) + pow(y,2) + pow(z,2))/pow(R,2); /* Complete potential particle */ Real f = (1.0-k)*(pSum2/pow(r,2) - 1.0)+k*(sphere -1.0); return f; } Vector3r Ig2_PP_PP_ScGeom::getNormal(const shared_ptr& cm1, const State& state1, const Vector3r newTrial) { PotentialParticle *s1=static_cast(cm1.get()); ///////////////////Transforming trial values to local frame of particles////////////////// Vector3r tempP1 = newTrial - state1.pos; /* Direction cosines */ Vector3r localP1(0,0,0); localP1 = state1.ori.conjugate()*tempP1; //the body has been rotated by state.ori. So I will need to rotate the frame by state.ori too Real x = localP1[0]; Real y = localP1[1]; Real z = localP1[2]; ////////////////////////////// assembling potential planes particle 1////////////////////////////// int planeNo = s1->a.size(); vectorp; Real pSum2 = 0.0; for (int i=0; ia[i]*x + s1->b[i]*y + s1->c[i]*z -s1-> d[i]; if (planer; Real R = s1->R; Real k = s1->k; /* Additional sphere */ //Real sphere = (pow(x,2) + pow(y,2) + pow(z,2))/pow(R,2); /* Complete potential particle */ //Real f = (1.0-k)*(pSum2/pow(r,2)-1.0)+k*(sphere -1.0); //////////////////////////////// local first derivitive particle 1 ///////////////////////////////////// Real pdxSum = 0.0; Real pdySum = 0.0; Real pdzSum = 0.0; for (int i=0; ia[i]*p[i]); pdySum += (s1->b[i]*p[i]); pdzSum += (s1->c[i]*p[i]); } Real fdx = 2.0*(1.0-k) * pdxSum/pow(r,2) + 2.0*k*x/pow(R,2); Real fdy = 2.0*(1.0-k) * pdySum/pow(r,2) + 2.0*k*y/pow(R,2); Real fdz = 2.0*(1.0-k) * pdzSum/pow(r,2) + 2.0*k*z/pow(R,2); ///////////////////// Assembling rotation matrix Qt for particle 1 ////////////////////////////// Matrix3r Q1=Eigen::Matrix3d::Zero();; //if(state1.ori==Quaternionr(1,0,0,0)){ // Q1.setIdentity(); //}else{ Real q0 = state1.ori.w(); Real q1 = state1.ori.x(); Real q2 = state1.ori.y(); Real q3 = state1.ori.z(); Q1(0,0) = 2.0*pow(q0,2.0) -1.0 + 2.0*pow(q1,2.0); Q1(0,1) = 2.0*q1*q2 + 2.0*q0*q3; Q1(0,2) = 2.0*q1*q3 - 2.0*q0*q2; Q1(1,0) = 2.0*q1*q2 - 2.0*q0*q3; Q1(1,1) = 2.0*pow(q0,2.0) -1.0 + 2.0*pow(q2,2.0); Q1(1,2) = 2.0*q2*q3 + 2.0*q0*q1; Q1(2,0) = 2.0*q1*q3 + 2.0*q0*q2; Q1(2,1) = 2.0*q2*q3 - 2.0*q0*q1; Q1(2,2) = 2.0*pow(q0,2) -1.0 + 2.0*pow(q3,2.0); //Matrix3r Q1n = state1.ori.toRotationMatrix(); //Matrix3r Q1c = state1.ori.conjugate().toRotationMatrix(); //std::cout<<"Q1: "<& cm1, const State& state1, const shared_ptr& cm2, const State& state2, Vector3r &contactPt) { PotentialParticle *s1=static_cast(cm1.get()); PotentialParticle *s2=static_cast(cm2.get()); Vector3r xGlobal(0.0,0.0,0.0); /* Parameters for particles A and B */ Real rA = s1->r; Real kA = s1->k; Real RA = s1->R; Real rB = s2->r; Real kB = s2->k; Real RB = s2->R; int planeNoA = s1->a.size(); int planeNoB = s2->a.size(); /* Variables to keep things neat */ Real ksA = RA/sqrt(kA); Real kpA = rA/sqrt(1.0 - kA); Real ksB = RB/sqrt(kB); Real kpB = rB/sqrt(1.0 - kB); int NUMCON = 3 + 1+ planeNoA + planeNoB; int NUMVAR = 6 + 2 + planeNoA + planeNoB; /* LINEAR CONSTRAINTS */ MSKboundkeye bkc[NUMCON]; bkc[0] = MSK_BK_FX; bkc[1] = MSK_BK_FX; bkc[2] = MSK_BK_FX; bkc[3] = MSK_BK_FX; for(int i=0; id[i]; }; for(int i=0; id[i]; }; /* BOUNDS */ MSKboundkeye bkx[NUMVAR ]; bkx[0] = MSK_BK_FR; bkx[1] = MSK_BK_FR; bkx[2] = MSK_BK_FR; bkx[3] = MSK_BK_FR; bkx[4] = MSK_BK_FR; bkx[5] = MSK_BK_FR; bkx[6] = MSK_BK_FR; bkx[7] = MSK_BK_FR; for(int i=0; i aval; vector aptrb; vector aptre; vector asub; Matrix3r Q1 = state1.ori.toRotationMatrix(); Matrix3r Q2 = state2.ori.toRotationMatrix(); int totalCount = 0; /* column 0 xA*/ aptrb.push_back(0); int count = 0; if(fabs(Q1(0,0))>pow(10,-15) ) { aval.push_back(ksA*Q1(0,0)); asub.push_back(0); count++; } if(fabs(Q1(1,0) )>pow(10,-15) ) { aval.push_back(ksA*Q1(1,0)); asub.push_back(1); count++; } if(fabs(Q1(2,0) )>pow(10,-15) ) { aval.push_back(ksA*Q1(2,0)); asub.push_back(2); count++; } for(int i=0; i < planeNoA; i++) { if(fabs(s1->a[i]) > pow(10,-15) ) { aval.push_back(ksA*s1->a[i]); asub.push_back(4+i); count++; } } aptre.push_back(count); totalCount += count; /* column 1 yA*/ aptrb.push_back(aptre[0]); count = 0; if(fabs(Q1(0,1) )>pow(10,-15) ) { aval.push_back(ksA*Q1(0,1)); asub.push_back(0); count++; } if(fabs(Q1(1,1))>pow(10,-15) ) { aval.push_back(ksA*Q1(1,1)); asub.push_back(1); count++; } if(fabs(Q1(2,1) )>pow(10,-15) ) { aval.push_back(ksA*Q1(2,1)); asub.push_back(2); count++; } for(int i=0; i < planeNoA; i++) { if(fabs(s1->b[i]) > pow(10,-15) ) { aval.push_back(ksA*s1->b[i]); asub.push_back(4+i); count++; } } aptre.push_back(aptrb[1] + count); totalCount += count; /* column 2 zA*/ aptrb.push_back(aptre[1]); count = 0; if(fabs(Q1(0,2) )>pow(10,-15) ) { aval.push_back(ksA*Q1(0,2)); asub.push_back(0); count++; } if(fabs(Q1(1,2))>pow(10,-15) ) { aval.push_back(ksA*Q1(1,2)); asub.push_back(1); count++; } if(fabs(Q1(2,2) )>pow(10,-15) ) { aval.push_back(ksA*Q1(2,2)); asub.push_back(2); count++; } for(int i=0; i < planeNoA; i++) { if(fabs(s1->c[i]) > pow(10,-15) ) { aval.push_back(ksA*s1->c[i]); asub.push_back(4+i); count++; } } aptre.push_back(aptrb[2] + count); totalCount += count; /* column 3 xB*/ aptrb.push_back(aptre[2]); count = 0; if(fabs(Q2(0,0))>pow(10,-15) ) { aval.push_back(-ksB*Q2(0,0)); asub.push_back(0); count++; } if(fabs(Q2(1,0) )>pow(10,-15) ) { aval.push_back(-ksB*Q2(1,0)); asub.push_back(1); count++; } if(fabs(Q2(2,0) )>pow(10,-15) ) { aval.push_back(-ksB*Q2(2,0)); asub.push_back(2); count++; } for(int i=0; i < planeNoB; i++) { if(fabs(s2->a[i]) > pow(10,-15) ) { aval.push_back(ksB*s2->a[i]); asub.push_back(4+planeNoA +i); count++; } } aptre.push_back(aptrb[3] + count); totalCount += count; /* column 4 yB*/ aptrb.push_back(aptre[3]); count = 0; if(fabs(Q2(0,1))>pow(10,-15) ) { aval.push_back(-ksB*Q2(0,1)); asub.push_back(0); count++; } if(fabs(Q2(1,1) )>pow(10,-15) ) { aval.push_back(-ksB*Q2(1,1)); asub.push_back(1); count++; } if(fabs(Q2(2,1) )>pow(10,-15) ) { aval.push_back(-ksB*Q2(2,1)); asub.push_back(2); count++; } for(int i=0; i < planeNoB; i++) { if(fabs(s2->b[i]) > pow(10,-15) ) { aval.push_back(ksB*s2->b[i]); asub.push_back(4+planeNoA+i); count++; } } aptre.push_back(aptrb[4] + count); totalCount += count; /* column 5 zB*/ aptrb.push_back(aptre[4]); count = 0; if(fabs(Q2(0,2))>pow(10,-15) ) { aval.push_back(-ksB*Q2(0,2)); asub.push_back(0); count++; } if(fabs(Q2(1,2) )>pow(10,-15) ) { aval.push_back(-ksB*Q2(1,2)); asub.push_back(1); count++; } if(fabs(Q2(2,2) )>pow(10,-15) ) { aval.push_back(-ksB*Q2(2,2)); asub.push_back(2); count++; } for(int i=0; i < planeNoB; i++) { if(fabs(s2->c[i]) > pow(10,-15) ) { aval.push_back(ksB*s2->c[i]); asub.push_back(4+planeNoA+i); count++; } } aptre.push_back(aptrb[5] + count); totalCount += count; /*column 6 */ aptrb.push_back(aptre[5]); aval.push_back(1.0); asub.push_back(3); aptre.push_back(aptrb[6]+1); totalCount += 1; /*column 7 */ aptrb.push_back(aptre[6]); aval.push_back(-1.0); asub.push_back(3); aptre.push_back(aptrb[7]+1); totalCount += 1; /*column 8 + i*/ for(int i=0; i < planeNoA; i++) { aptrb.push_back(aptre[8-1+i]); aval.push_back(-1.0*kpA); asub.push_back(4 + i); aptre.push_back(aptrb[8+i]+1); } totalCount += planeNoA; /*column 8 + planeNoA + i */ for(int i=0; i < planeNoB; i++) { aptrb.push_back(aptre[ 8 + planeNoA-1+i]); aval.push_back(-1.0*kpB); asub.push_back(4 + planeNoA + i); aptre.push_back(aptrb[8 + planeNoA+i]+1); } totalCount += planeNoB; MSKidxt i,j,csubA[1+ 3 + planeNoA], csubB[1+ 3 + planeNoB] ; Real xx[NUMVAR]; MSKenv_t & env=mosekEnv; MSKtask_t task; MSKrescodee & r=mosekTaskEnv; if ( r==MSK_RES_OK ) /* Directs the log stream to the 'printstr' function. */ // MSK_linkfunctoenvstream(env,MSK_STREAM_LOG,NULL,NULL); //printstr // MSK_linkfunctoenvstream(env,MSK_STREAM_LOG,NULL,NULL); f ( r==MSK_RES_OK ) /* Create the optimization task. */ r = MSK_maketask(env,NUMCON,NUMVAR,&task); if ( r==MSK_RES_OK ) { // MSK_linkfunctotaskstream(task,MSK_STREAM_LOG,NULL,printstr); /* Give MOSEK an estimate of the size of the input data. This is done to increase the speed of inputting data. However, it is optional. */ if (r == MSK_RES_OK) r = MSK_putmaxnumvar(task,NUMVAR); if (r == MSK_RES_OK) r = MSK_putmaxnumcon(task,NUMCON); int NUMANZ = totalCount; if (r == MSK_RES_OK) r = MSK_putmaxnumanz(task,NUMANZ); /* Append 'NUMCON' empty constraints. The constraints will initially have no bounds. */ if ( r == MSK_RES_OK ) r = MSK_append(task,MSK_ACC_CON,NUMCON); /* Append 'NUMVAR' variables. The variables will initially be fixed at zero (x=0). */ if ( r == MSK_RES_OK ) r = MSK_append(task,MSK_ACC_VAR,NUMVAR); /* Optionally add a constant term to the objective. */ if ( r ==MSK_RES_OK ) r = MSK_putcfix(task,0.0); for(j=0; j& cm1, const State& state1, Vector3r midPoint, Vector3r normal, Vector3r& ptOnParticle) { //PotentialParticle *s1=static_cast(cm1.get()); ptOnParticle = midPoint; Real f = evaluatePP(cm1,state1, ptOnParticle);//evaluateFNoSphere(cm1,state1, ptOnParticle); // Real fprevious = f; int counter = 0; //normal.normalize(); Vector3r step = normal*Mathr::Sign(f) *-1.0; Vector3r bracketA(0,0,0); Vector3r bracketB(0,0,0); do { ptOnParticle += step; fprevious = f; f = evaluatePP(cm1,state1, ptOnParticle); //evaluateFNoSphere(cm1,state1, ptOnParticle); // counter++; if (counter == 50000 ) { //LOG_WARN("Initial point searching exceeded 500 iterations!"); //std::cout<<"ptonparticle2 search exceeded 50000 iterations! step:"< 0.0 ); bracketA = ptOnParticle; bracketB = ptOnParticle -step; Vector3r zero(0,0,0); BrentZeroSurf(cm1,state1,bracketA, bracketB, zero); ptOnParticle = zero; //if( fabs(f)>0.1){std::cout<<"getInitial point f:"<& cm1, const State& state1, const shared_ptr& cm2, const State& state2, Vector3r &contactPt, bool warmstart) { //timingDeltas->start(); PotentialParticle *s1=static_cast(cm1.get()); PotentialParticle *s2=static_cast(cm2.get()); Vector3r xGlobal(0.0,0.0,0.0); int iter = 0; int totalIter = 0; /* Parameters for particles A and B */ Real rA = s1->r; Real kA = s1->k; Real RA = s1->R; Real rB = s2->r; Real kB = s2->k; Real RB = s2->R; int planeNoA = s1->a.size(); int planeNoB = s2->a.size(); int varNo = 3+1+planeNoA+planeNoB; int planeNoAB = planeNoA + planeNoB; int varNo2 = varNo*varNo; int planeNoA3 = 3+planeNoA; int planeNoB3=3+planeNoB; //int planeNoA2 =planeNoA*planeNoA; //int planeNoB2=planeNoB*planeNoB; Matrix3r QA = state1.ori.conjugate().toRotationMatrix(); /*direction cosine */ Matrix3r QB = state2.ori.conjugate().toRotationMatrix(); /*direction cosine */ int blas3 = 3; char blasNT = 'N'; char blasT= 'T'; int blas1planeNoA = std::max(1,planeNoA); int blas1planeNoB = std::max(1,planeNoB); int blas1planeNoAB = std::max(1,planeNoAB); Real blas0 = 0.0; Real blas1 = 1.0; Real blasNeg1 = -1.0; Real blasQA[9]; Real blasQB[9]; Real blasPosA[3]; Real blasPosB[3]; Real blasContactPt[3]; Real blasC[varNo]; for (int i=0; ia[i]*blasLocalP1[0] + s1->b[i]*blasLocalP1[1] + s1->c[i]*blasLocalP1[2] - s1->d[i]; if (planea[i]; blasP1[i+planeNoA] = s1->b[i]; blasP1[i+2*planeNoA] = s1->c[i]; blasD1[i] = s1->d[i]; } //Eigen::MatrixXd P1Q = P1*QA; //transA = 'N'; transB = 'N'; blasM = planeNoA; blasN = 3; blasK = 3; // blasLDA = std::max(1,blasM); blasLDC = std::max(1,blasM); blasLDB = 3; blasAlpha=1.0; blasBeta = 0.0; //dgemm_(&transA, &transB, &blasM, &blasN, &blasK, &blasAlpha, &blasP1[0], &blasLDA, &blasQA[0], &blasLDB, &blasBeta, &blasP1Q[0], &blasLDC); dgemm_(&blasNT, &blasNT, &planeNoA, &blas3, &blas3, &blas1, &blasP1[0], &blas1planeNoA, &blasQA[0], &blasLDB, &blas0, &blasP1Q[0], &blas1planeNoA); Real sphereA = (pow(blasLocalP1[0],2) + pow(blasLocalP1[1],2) + pow(blasLocalP1[2],2))/pow(RA,2); Real sA = (1.0-kA)*(pertSumA2/pow(rA,2) - 1.0)+kA*(sphereA -1.0); /* fB */ Real blasTempP2[3]; Real blasLocalP2[3]; for(int i=0; i<3; i++) { blasTempP2[i] = blasContactPt[i] - blasPosB[i]; } //transA = 'N'; blasM = 3; blasN = 3; blasLDA = 3; blasAlpha=1.0; blasBeta = 0.0; //dgemv_(&transA, &blasM, &blasN, &blasAlpha, &blasQB[0], &blasLDA, &blasTempP2[0], &incx, &blasBeta, &blasLocalP2[0], &incy); dgemv_(&blasNT, &blas3, &blas3, &blas1, &blasQB[0], &blas3, &blasTempP2[0], &incx, &blas0, &blasLocalP2[0], &incy); // Vector3r tempP2 = contactPt - posB; // Vector3r localP2 = QB*tempP2; /*P2Q*/ //Eigen::MatrixXd P2 = Eigen::MatrixXd::Zero(planeNoB,3); /*d2*/ //Eigen::MatrixXd d2 = Eigen::MatrixXd::Zero(planeNoB,1); Real blasP2[planeNoB*3]; Real blasD2[planeNoB]; Real blasP2Q[planeNoB*3]; Real pertSumB2 = 0.0; for (int i=0; ia[i]*blasLocalP2[0] + s2->b[i]*blasLocalP2[1] + s2->c[i]*blasLocalP2[2] - s2->d[i]; if (planea[i]; blasP2[i+planeNoB] = s2->b[i]; blasP2[i+2*planeNoB] = s2->c[i]; blasD2[i] = s2->d[i]; } //Eigen::MatrixXd P2Q = P2*QB; //transA = 'N'; transB = 'N'; blasM = planeNoB; blasN = 3; blasK = 3; //blasLDA = std::max(1,blasM); blasLDC = std::max(1,blasM); blasLDB = 3; blasAlpha=1.0; blasBeta = 0.0; // dgemm_(&transA, &transB, &blasM, &blasN, &blasK, &blasAlpha, &blasP2[0], &blasLDA, &blasQB[0], &blasLDB, &blasBeta, &blasP2Q[0], &blasLDC); dgemm_(&blasNT, &blasNT, &planeNoB, &blas3, &blas3, &blasAlpha, &blasP2[0], &blas1planeNoB, &blasQB[0], &blas3, &blasBeta, &blasP2Q[0], &blas1planeNoB); Real sphereB = (pow(blasLocalP2[0],2) + pow(blasLocalP2[1],2) + pow(blasLocalP2[2],2))/pow(RB,2); Real sB = (1.0-kB)*(pertSumB2/pow(rB,2) - 1.0)+kB*(sphereB -1.0); //sPert = fabs(fA-fB); s = std::max(sqrt(fabs(sA+1.0)), sqrt(fabs(sB+1.0))) + sPert; //sqrt(fA+fB+2.0)+sPert; //sqrt(std::max(fabs(fA+1.0), fabs(fB+1.0))) + sPert; // //x[3] = s; blasX[3]= s; /////////////////// algebra formulation of the SOCP /////////////////// /* c */ // Eigen::MatrixXd c=Eigen::MatrixXd::Zero(varNo,1); // c[3] = 1.0; Real blasA1[(3+planeNoA)*varNo]; Real blasA2[(3+planeNoB)*varNo]; /* Second order cone constraints */ /* A1 */ //Eigen::MatrixXd A1(3+planeNoA,varNo); //Matrix3r QAs=kAs*QA; //cwise() Real blasQAs[9]; int noElements=9; Real scaleFactor = kAs; dcopy_(&noElements, &blasQA[0], &incx, &blasQAs[0], &incx); dscal_(&noElements, &scaleFactor, &blasQAs[0], &incx); // A1 << QAs,Eigen::MatrixXd::Zero(3,1+planeNoAB), // Eigen::MatrixXd::Zero(planeNoA,4),kAp*Eigen::MatrixXd::Identity(planeNoA, planeNoA),Eigen::MatrixXd::Zero(planeNoA,planeNoB); memset(blasA1,0.0,sizeof(blasA1)); for (int i=0; i<3; i++) { blasA1[i] = blasQAs[i]; blasA1[i+planeNoA3] = blasQAs[i+3]; blasA1[i+2*planeNoA3] = blasQAs[i+6]; } for (int i=0; icheckpoint("setup"); while(totalIter<500) { penalty = 1.0/t; /* Newton's method */ /* s=x[3]; */ s=blasX[3]; /* temp variables */ /* w1 = A1*x + b1; */ noElements = 3+planeNoA; dcopy_(&noElements, &blasB1[0], &incx, &blasW1[0], &incy); //transA = 'N'; blasM = 3+planeNoA; blasN = varNo; //blasLDA = 3+planeNoA; blasAlpha = 1.0; blasBeta = 1.0; incx =1; incy=1; //dgemv_(&transA, &blasM, &blasN, &blasAlpha, &blasA1[0], &blasLDA, &blasX[0], &incx, &blasBeta, &blasW1[0], &incy); dgemv_(&blasNT, &planeNoA3, &varNo, &blas1, &blasA1[0], &planeNoA3, &blasX[0], &incx, &blas1, &blasW1[0], &incy); /* w2 = A2*x + b2; */ noElements = 3+planeNoB; dcopy_(&noElements, &blasB2[0], &incx, &blasW2[0], &incy); //transA = 'N'; blasM = 3+planeNoB; blasN = varNo; //blasLDA = 3+planeNoB; blasAlpha = 1.0; blasBeta = 1.0; //dgemv_(&transA, &blasM, &blasN, &blasAlpha, &blasA2[0], &blasLDA, &blasX[0], &incx, &blasBeta, &blasW2[0], &incy); dgemv_(&blasNT, &planeNoB3, &varNo, &blas1, &blasA2[0], &planeNoB3, &blasX[0], &incx, &blas1, &blasW2[0], &incy); /* u1 = s*s - w1.dot(w1); */ /* u2 = s*s - w2.dot(w2); */ //noElements = 3+planeNoA; blasW1dot = ddot_(&planeNoA3, &blasW1[0], &incx, &blasW1[0], &incy); //noElements = 3+planeNoB; blasW2dot = ddot_(&planeNoB3, &blasW2[0], &incx, &blasW2[0], &incy); u1 =s*s -blasW1dot; u2 =s*s -blasW2dot; /* wL = bL - AL*x; */ noElements = planeNoAB; dcopy_(&noElements, &blasBL[0], &incx, &blasWL[0], &incy); //transA = 'N'; blasN = varNo; //blasM = planeNoAB; blasLDA = std::max(1,planeNoAB); blasAlpha = -1.0; blasBeta = 1.0; //dgemv_(&transA, &blasM, &blasN, &blasAlpha, &blasAL[0], &blasLDA, &blasX[0], &incx, &blasBeta, &blasWL[0], &incy); dgemv_(&blasNT, &planeNoAB, &varNo, &blasNeg1, &blasAL[0], &blas1planeNoAB, &blasX[0], &incx, &blas1, &blasWL[0], &incy); for (int i=0; icheckpoint("assemble H and g"); //std::cout<<"before Chol, totalIter: "<checkpoint("Cholesky"); //fprime = step.transpose()*g; //noElements = varNo; blasFprime = ddot_(&varNo, &blasStep[0], &incx, &blasGrad[0], &incy); wLlogsum = 0.0; for (int i=0; i0) { minWL = blasWL[0]; } for(int i=0; i0) { minWL = blasWL[0]; } for(int i=0; icheckpoint("barrier search"); wLlogsum = 0.0; for (int i=0; i val + backtrack*0.01*blasFprime) { backtrack = 0.5*backtrack; //for (int i = 0; icheckpoint("line search"); noElements = varNo; dcopy_(&noElements, &blasNewX[0], &incx, &blasX[0], &incy); if(blasFprime >0.0) { //std::cout<<"count: "<0.001 ) { //std::cout<<"inside fA-fB: "<checkpoint("newton"); return true; } t = std::min(t*mu, (2.0*m+1.0)/tol); iter = 0; totalIter = totalIter+1; } if(totalIter>100) { //std::cout<<"totalIter: "<checkpoint("complete"); } return ( true ); } #endif // YADE_POTENTIAL_PARTICLES trunk-2018.02b/pkg/dem/Ig2_PP_PP_ScGeom.hpp000066400000000000000000000105131324306050200201010ustar00rootroot00000000000000/*CWBoon 2015 */ /* C.W. Boon, G.T. Houlsby, S. Utili (2013). A new contact detection algorithm for three-dimensional non-spherical particles. Powder Technology, 248, pp 94-102. */ /* code for calling MOSEK was for ver 6. Please uncomment if you have the licence */ #pragma once #ifdef YADE_POTENTIAL_PARTICLES #include #include #include #include #include #include #include #ifdef YADE_MOSEK #include #include #include #include #endif class Ig2_PP_PP_ScGeom: public IGeomFunctor { #ifdef YADE_MOSEK protected: std::string myfile; std::string Key; MSKrescodee mosekTaskEnv; MSKenv_t mosekEnv; #endif public : virtual bool go(const shared_ptr& cm1, const shared_ptr& cm2, const State& state1, const State& se32, const Vector3r& shift2, const bool& force, const shared_ptr& c); Real evaluatePP(const shared_ptr& cm1, const State& state1, const Vector3r newTrial); void getPtOnParticle2(const shared_ptr& cm1, const State& state1, Vector3r previousPt, Vector3r normal, Vector3r& newlocalPoint); bool contactPtMosekF2(const shared_ptr& cm1, const State& state1, const shared_ptr& cm2, const State& state2, Vector3r &contactPt); bool customSolve(const shared_ptr& cm1, const State& state1, const shared_ptr& cm2, const State& state2, Vector3r &contactPt, bool warmstart); Vector3r getNormal(const shared_ptr& cm1, const State& state1, const Vector3r newTrial); void BrentZeroSurf(const shared_ptr& cm1, const State& state1, const Vector3r bracketA, const Vector3r bracketB, Vector3r& zero); ///////////////////////////////////////////////////////////////////////////////////////////////////// YADE_CLASS_BASE_DOC_ATTRS_CTOR(Ig2_PP_PP_ScGeom,IGeomFunctor,"EXPERIMENTAL. IGeom functor for PotentialParticle - PotentialParticle pair", ((Real, accuracyTol, pow(10,-7),, "accuracy desired, tolerance criteria for SOCP")) ((Real,interactionDetectionFactor,1.0,,"bool to avoid granular ratcheting")), //((std::string,myfile,"./PotentialParticles"+"","string")), //timingDeltas=shared_ptr(new TimingDeltas); //mosekTaskEnv = MSK_makeenv(&mosekEnv,NULL,NULL,NULL,NULL); //mosekTaskEnv = MSK_initenv(mosekEnv); ); FUNCTOR2D(PotentialParticle,PotentialParticle); // needed for the dispatcher, even if it is symmetric DEFINE_FUNCTOR_ORDER_2D(PotentialParticle,PotentialParticle); DECLARE_LOGGER; }; REGISTER_SERIALIZABLE(Ig2_PP_PP_ScGeom); #ifdef __cplusplus extern "C" { #endif /* LAPACK LU */ //int dgesv(int varNo, int varNo2, Real *H, int varNo3, int *pivot, Real* g, int varNo4, int info){ extern void dgesv_(const int *N, const int *nrhs, Real *Hessian, const int *lda, int *ipiv, Real *gradient, const int *ldb, int *info); // int ans; // dgesv_(&varNo, &varNo2, H, &varNo3, pivot,g, &varNo4, &ans); // return ans; //} /* LAPACK Cholesky */ extern void dpbsv_(const char *uplo, const int *n, const int *kd, const int *nrhs, Real *AB, const int *ldab, Real *B, const int *ldb, int *info); /* LAPACK QR */ extern void dgels_(const char *Trans, const int *m, const int *n, const int *nrhs, Real *A, const int *lda, Real *B, const int *ldb, const Real *work, const int *lwork, int *info); /*BLAS */ extern void dgemm_(const char *transA, const char *transB, const int *m, const int *n, const int *k, const Real *alpha, Real *A, const int *lda, Real *B, const int *ldb, const Real *beta, Real *C, const int *ldc); extern void dgemv_(const char *trans, const int *m, const int *n, const Real *alpha, Real *A, const int *lda, Real *x, const int *incx, const Real *beta, Real *y, const int *incy); extern void dcopy_(const int *N, Real *x, const int *incx, Real *y, const int *incy); extern Real ddot_(const int *N, Real *x, const int *incx, Real *y, const int *incy); extern void daxpy_(const int *N, const Real *da, Real *dx, const int *incx, Real *dy, const int *incy); extern void dscal_(const int *N, const Real *alpha, Real *x, const int *incx); void dsyev_(const char *jobz, const char *uplo, const int *N, Real *A, const int *lda, Real *W, Real *work, int *lwork, int *info); #ifdef __cplusplus }; #endif #endif // YADE_POTENTIAL_PARTICLES trunk-2018.02b/pkg/dem/Ig2_Sphere_Sphere_ScGeom.cpp000066400000000000000000000066121324306050200217170ustar00rootroot00000000000000// © 2004 Janek Kozicki // © 2007 Bruno Chareyre // © 2008 Václav Šmilauer #include"Ig2_Sphere_Sphere_ScGeom.hpp" #include #include #include #include #include #include bool Ig2_Sphere_Sphere_ScGeom::go(const shared_ptr& cm1, const shared_ptr& cm2, const State& state1, const State& state2, const Vector3r& shift2, const bool& force, const shared_ptr& c) { TIMING_DELTAS_START(); const Se3r& se31=state1.se3; const Se3r& se32=state2.se3; const Sphere *s1=static_cast(cm1.get()), *s2=static_cast(cm2.get()); Vector3r normal=(se32.position+shift2)-se31.position; if (!c->isReal() && !force) {//don't fast-check distance if geometry will be updated anyway Real penetrationDepthSq=pow(interactionDetectionFactor*(s1->radius+s2->radius),2) - normal.squaredNorm(); if (penetrationDepthSq<0) { TIMING_DELTAS_CHECKPOINT("Ig2_Sphere_Sphere_ScGeom"); return false; } } shared_ptr scm; bool isNew = !c->geom; if(!isNew) scm=YADE_PTR_CAST(c->geom); else { scm=shared_ptr(new ScGeom()); c->geom=scm; } Real norm=normal.norm(); normal/=norm; // normal is unit vector now #ifdef YADE_DEBUG if(norm==0) throw runtime_error(("Zero distance between spheres #"+boost::lexical_cast(c->getId1())+" and #"+boost::lexical_cast(c->getId2())+".").c_str()); #endif Real penetrationDepth=s1->radius+s2->radius-norm; scm->contactPoint=se31.position+(s1->radius-0.5*penetrationDepth)*normal;//0.5*(pt1+pt2); scm->penetrationDepth=penetrationDepth; scm->radius1=s1->radius; scm->radius2=s2->radius; scm->precompute(state1,state2,scene,c,normal,isNew,shift2,avoidGranularRatcheting); TIMING_DELTAS_CHECKPOINT("Ig2_Sphere_Sphere_ScGeom"); return true; } bool Ig2_Sphere_Sphere_ScGeom::goReverse( const shared_ptr& cm1, const shared_ptr& cm2, const State& state1, const State& state2, const Vector3r& shift2, const bool& force, const shared_ptr& c) { return go(cm1,cm2,state2,state1,-shift2,force,c); } YADE_PLUGIN((Ig2_Sphere_Sphere_ScGeom)); bool Ig2_Sphere_Sphere_ScGeom6D::go( const shared_ptr& cm1, const shared_ptr& cm2, const State& state1, const State& state2, const Vector3r& shift2, const bool& force, const shared_ptr& c) { bool isNew = !c->geom; if (Ig2_Sphere_Sphere_ScGeom::go(cm1,cm2,state1,state2,shift2,force,c)){//the 3 DOFS from ScGeom are updated here if (isNew) {//generate a 6DOF interaction from the 3DOF one generated by Ig2_Sphere_Sphere_ScGeom shared_ptr sc (new ScGeom6D()); *(YADE_PTR_CAST(sc)) = *(YADE_PTR_CAST(c->geom)); c->geom=sc;} if (updateRotations) YADE_PTR_CAST(c->geom)->precomputeRotations(state1,state2,isNew,creep); return true; } else return false; } bool Ig2_Sphere_Sphere_ScGeom6D::goReverse( const shared_ptr& cm1, const shared_ptr& cm2, const State& state1, const State& state2, const Vector3r& shift2, const bool& force, const shared_ptr& c) { return go(cm1,cm2,state2,state1,-shift2,force,c); } YADE_PLUGIN((Ig2_Sphere_Sphere_ScGeom6D)); trunk-2018.02b/pkg/dem/Ig2_Sphere_Sphere_ScGeom.hpp000066400000000000000000000116311324306050200217210ustar00rootroot00000000000000// © 2004 Janek Kozicki // © 2007 Bruno Chareyre // © 2008 Václav Šmilauer #pragma once #include #include class Ig2_Sphere_Sphere_ScGeom: public IGeomFunctor{ public: virtual bool go(const shared_ptr& cm1, const shared_ptr& cm2, const State& state1, const State& state2, const Vector3r& shift2, const bool& force, const shared_ptr& c); virtual bool goReverse( const shared_ptr& cm1, const shared_ptr& cm2, const State& state1, const State& state2, const Vector3r& shift2, const bool& force, const shared_ptr& c); YADE_CLASS_BASE_DOC_ATTRS(Ig2_Sphere_Sphere_ScGeom,IGeomFunctor, "Create/update a :yref:`ScGeom` instance representing the geometry of a contact point between two :yref:`Spheres` s.", ((Real,interactionDetectionFactor,1,,"Enlarge both radii by this factor (if >1), to permit creation of distant interactions.\n\nInteractionGeometry will be computed when interactionDetectionFactor*(rad1+rad2) > distance.\n\n.. note::\n\t This parameter is functionally coupled with :yref:`Bo1_Sphere_Aabb::aabbEnlargeFactor`, which will create larger bounding boxes and should be of the same value.")) ((bool,avoidGranularRatcheting,true,,"Define relative velocity so that ratcheting is avoided. It applies for sphere-sphere contacts. It eventualy also apply for sphere-emulating interactions (i.e. convertible into the ScGeom type), if the virtual sphere's motion is defined correctly (see e.g. :yref:`Ig2_Sphere_ChainedCylinder_CylScGeom`).\n\n" "Short explanation of what we want to avoid :\n\n" "Numerical ratcheting is best understood considering a small elastic cycle at a contact between two grains : assuming b1 is fixed, impose this displacement to b2 :\n\n" "#. translation *dx* in the normal direction\n" "#. rotation *a*\n" "#. translation *-dx* (back to the initial position)\n" "#. rotation *-a* (back to the initial orientation)\n\n\n" "If the branch vector used to define the relative shear in rotation×branch is not constant (typically if it is defined from the vector center→contactPoint), then the shear displacement at the end of this cycle is not zero: rotations *a* and *-a* are multiplied by branches of different lengths.\n\n" "It results in a finite contact force at the end of the cycle even though the positions and orientations are unchanged, in total contradiction with the elastic nature of the problem. It could also be seen as an *inconsistent energy creation or loss*. Given that DEM simulations tend to generate oscillations around equilibrium (damped mass-spring), it can have a significant impact on the evolution of the packings, resulting for instance in slow creep in iterations under constant load.\n\n" "The solution adopted here to avoid ratcheting is as proposed by McNamara and co-workers. They analyzed the ratcheting problem in detail - even though they comment on the basis of a cycle that differs from the one shown above. One will find interesting discussions in e.g. [McNamara2008]_, even though solution it suggests is not fully applied here (equations of motion are not incorporating alpha, in contradiction with what is suggested by McNamara et al.).\n\n" )) ); FUNCTOR2D(Sphere,Sphere); // needed for the dispatcher, even if it is symmetric DEFINE_FUNCTOR_ORDER_2D(Sphere,Sphere); }; REGISTER_SERIALIZABLE(Ig2_Sphere_Sphere_ScGeom); class Ig2_Sphere_Sphere_ScGeom6D: public Ig2_Sphere_Sphere_ScGeom{ public: virtual bool go(const shared_ptr& cm1, const shared_ptr& cm2, const State& state1, const State& state2, const Vector3r& shift2, const bool& force, const shared_ptr& c); virtual bool goReverse( const shared_ptr& cm1, const shared_ptr& cm2, const State& state1, const State& state2, const Vector3r& shift2, const bool& force, const shared_ptr& c); YADE_CLASS_BASE_DOC_ATTRS(Ig2_Sphere_Sphere_ScGeom6D,Ig2_Sphere_Sphere_ScGeom,"Create/update a :yref:`ScGeom6D` instance representing the geometry of a contact point between two :yref:`Spheres`, including relative rotations.", ((bool,updateRotations,true,,"Precompute relative rotations. Turning this false can speed up simulations when rotations are not needed in constitutive laws (e.g. when spheres are compressed without cohesion and moment in early stage of a triaxial test), but is not foolproof. Change this value only if you know what you are doing.")) ((bool,creep,false,,"Substract rotational creep from relative rotation. The rotational creep :yref:`ScGeom6D::twistCreep` is a quaternion and has to be updated inside a constitutive law, see for instance :yref:`Law2_ScGeom6D_CohFrictPhys_CohesionMoment`." )) ); FUNCTOR2D(Sphere,Sphere); // needed for the dispatcher, even if it is symmetric DEFINE_FUNCTOR_ORDER_2D(Sphere,Sphere); }; REGISTER_SERIALIZABLE(Ig2_Sphere_Sphere_ScGeom6D); trunk-2018.02b/pkg/dem/InelastCohFrictPM.cpp000066400000000000000000000265671324306050200205160ustar00rootroot00000000000000#include "InelastCohFrictPM.hpp" YADE_PLUGIN((InelastCohFrictMat)(InelastCohFrictPhys)(Ip2_2xInelastCohFrictMat_InelastCohFrictPhys)(Law2_ScGeom6D_InelastCohFrictPhys_CohesionMoment)); void Ip2_2xInelastCohFrictMat_InelastCohFrictPhys::go(const shared_ptr& b1 // InelastCohFrictMat , const shared_ptr& b2 // InelastCohFrictMat , const shared_ptr& interaction) { InelastCohFrictMat* sdec1 = static_cast(b1.get()); InelastCohFrictMat* sdec2 = static_cast(b2.get()); ScGeom6D* geom = YADE_CAST(interaction->geom.get()); //FIXME : non cohesive contact are not implemented, it would be useful to use setCohesionNow, setCohesionOnNewContacts etc ... if (geom) { if (!interaction->phys) { interaction->phys = shared_ptr(new InelastCohFrictPhys()); InelastCohFrictPhys* contactPhysics = YADE_CAST(interaction->phys.get()); Real pi = 3.14159265; Real r1 = geom->radius1; Real r2 = geom->radius2; Real f1 = sdec1->frictionAngle; Real f2 = sdec2->frictionAngle; contactPhysics->tangensOfFrictionAngle = tan(min(f1,f2)); // harmonic average of modulus contactPhysics->knC = 2.0*sdec1->compressionModulus*r1*sdec2->compressionModulus*r2/(sdec1->compressionModulus*r1+sdec2->compressionModulus*r2); contactPhysics->knT = 2.0*sdec1->tensionModulus*r1*sdec2->tensionModulus*r2/(sdec1->tensionModulus*r1+sdec2->tensionModulus*r2); contactPhysics->ks = 2.0*sdec1->shearModulus*r1*sdec2->shearModulus*r2/(sdec1->shearModulus*r1+sdec2->shearModulus*r2); // harmonic average of coeficients for bending and twist coeficients Real AlphaKr = 2.0*sdec1->alphaKr*sdec2->alphaKr/(sdec1->alphaKr+sdec2->alphaKr); Real AlphaKtw = 2.0*sdec1->alphaKtw*sdec2->alphaKtw/(sdec1->alphaKtw+sdec2->alphaKtw); contactPhysics->kr = r1*r2*contactPhysics->ks*AlphaKr; contactPhysics->ktw = r1*r2*contactPhysics->ks*AlphaKtw; contactPhysics->kTCrp = contactPhysics->knT*min(sdec1->creepTension,sdec2->creepTension); contactPhysics->kRCrp = contactPhysics->kr*min(sdec1->creepBending,sdec2->creepBending); contactPhysics->kTwCrp = contactPhysics->ktw*min(sdec1->creepTwist,sdec2->creepTwist); contactPhysics->kRUnld = contactPhysics->kr*min(sdec1->unloadBending,sdec2->unloadBending); contactPhysics->kTUnld = contactPhysics->knT*min(sdec1->unloadTension,sdec2->unloadTension); contactPhysics->kTwUnld = contactPhysics->ktw*min(sdec1->unloadTwist,sdec2->unloadTwist); contactPhysics->maxElC = min(sdec1->sigmaCompression,sdec2->sigmaCompression)*pow(min(r2, r1),2); contactPhysics->maxElT = min(sdec1->sigmaTension,sdec2->sigmaTension)*pow(min(r2, r1),2); contactPhysics->maxElB = min(sdec1->nuBending,sdec2->nuBending)*pow(min(r2, r1),3); contactPhysics->maxElTw = min(sdec1->nuTwist,sdec2->nuTwist)*pow(min(r2, r1),3); contactPhysics->shearAdhesion = min(sdec1->shearCohesion,sdec2->shearCohesion)*pow(min(r1, r2),2); contactPhysics->maxExten = min(sdec1->epsilonMaxTension*r1,sdec2->epsilonMaxTension*r2); contactPhysics->maxContract = min(sdec1->epsilonMaxCompression*r1,sdec2->epsilonMaxCompression*r2); contactPhysics->maxBendMom = min(sdec1->etaMaxBending,sdec2->etaMaxBending)*pow(min(r2, r1),3); contactPhysics->maxTwist = 2*pi*min(sdec1->etaMaxTwist,sdec2->etaMaxTwist); } } }; Real Law2_ScGeom6D_InelastCohFrictPhys_CohesionMoment::normElastEnergy() { //FIXME : this have to be checked and adapted Real normEnergy=0; FOREACH(const shared_ptr& I, *scene->interactions){ if(!I->isReal()) continue; InelastCohFrictPhys* phys = YADE_CAST(I->phys.get()); if (phys) { normEnergy += 0.5*(phys->normalForce.squaredNorm()/phys->kn); } } return normEnergy; } Real Law2_ScGeom6D_InelastCohFrictPhys_CohesionMoment::shearElastEnergy() { //FIXME : this have to be checked and adapted Real shearEnergy=0; FOREACH(const shared_ptr& I, *scene->interactions){ if(!I->isReal()) continue; InelastCohFrictPhys* phys = YADE_CAST(I->phys.get()); if (phys) { shearEnergy += 0.5*(phys->shearForce.squaredNorm()/phys->ks); } } return shearEnergy; } bool Law2_ScGeom6D_InelastCohFrictPhys_CohesionMoment::go(shared_ptr& ig, shared_ptr& ip, Interaction* contact) { //FIXME : non cohesive contact are not implemented, it would be useful to use setCohesionNow, setCohesionOnNewContacts etc ... const int &id1 = contact->getId1(); const int &id2 = contact->getId2(); const Real& dt = scene->dt; ScGeom6D* geom = YADE_CAST (ig.get()); InelastCohFrictPhys* phys = YADE_CAST (ip.get()); if (contact->isFresh(scene)) phys->shearForce = Vector3r::Zero(); Real un = geom->penetrationDepth-phys->unp; Real Fn; State* de1 = Body::byId(id1,scene)->state.get(); State* de2 = Body::byId(id2,scene)->state.get(); if(un<=0){/// tension /// if(-un>phys->maxExten || phys->isBroken){//plastic failure. phys->isBroken=1; phys->normalForce=phys->shearForce=phys->moment_twist=phys->moment_bending=Vector3r(0,0,0); return false; } Fn=phys->knT*un; //elasticity if(-Fn>phys->maxElT || phys->onPlastT){ //so we are on plastic deformation. phys->onPlastT=1; phys->onPlastC=1; //if plasticity is reached on tension, set it to compression too. if(phys->maxCrpRchdT[0]kTUnld*(un-phys->maxCrpRchdT[0])+phys->maxCrpRchdT[1]; } else{//loading on plastic deformation : creep. Fn = -phys->maxElT+phys->kTCrp*(un+phys->maxElT/phys->knT); phys->maxCrpRchdT[0]=un; //new maximum is reached. phys->maxCrpRchdT[1]=Fn; } if (Fn>0){ //so the contact just passed the equilibrium state, set new "unp" who stores the plastic equilibrium state. phys->unp=geom->penetrationDepth; phys->maxCrpRchdT[0]=1e20; phys->maxElT=0; } } else{ //elasticity phys->maxCrpRchdT[0]=un; phys->maxCrpRchdT[1]=Fn; } } else{/// compression /// similar to tension. if(un>phys->maxContract || phys->isBroken){ phys->isBroken=1; phys->normalForce=phys->shearForce=phys->moment_twist=phys->moment_bending=Vector3r(0,0,0); if(geom->penetrationDepth<=0){ //do not erase the contact while penetrationDepth<0 because it would be recreated at next timestep. return false; } return true; } Fn=phys->knC*un; if(Fn>phys->maxElC || phys->onPlastC){ phys->onPlastC=1; if(phys->maxCrpRchdC[0]>un){ Fn = phys->kTUnld*(un-phys->maxCrpRchdC[0])+phys->maxCrpRchdC[1]; } else{ Fn = phys->maxElC+phys->kTCrp*(un-phys->maxElC/phys->knC); phys->maxCrpRchdC[0]=un; phys->maxCrpRchdC[1]=Fn; } if (Fn<0){ phys->unp=geom->penetrationDepth; phys->maxCrpRchdC[0]=-1e20; phys->maxElC=0; } } else{ phys->maxCrpRchdC[0]=un; phys->maxCrpRchdC[1]=Fn; } } /// Shear /// Vector3r shearForce = geom->rotate(phys->shearForce); const Vector3r& dus = geom->shearIncrement(); //Linear elasticity giving "trial" shear force shearForce += phys->ks*dus; Real Fs = shearForce.norm(); Real maxFs = phys->shearAdhesion; if (maxFs==0)maxFs = Fn*phys->tangensOfFrictionAngle; maxFs = std::max((Real) 0, maxFs); if (Fs > maxFs) {//Plasticity condition on shear force if (!phys->cohesionBroken) { phys->cohesionBroken=1; phys->shearAdhesion=0; maxFs = max((Real) 0, Fn*phys->tangensOfFrictionAngle); } maxFs = maxFs / Fs; shearForce *= maxFs; } //rotational moment are only applied if the cohesion is not broken. /// Twist /// the twist law is driven by twist displacement ("getTwist()"). if(!phys->cohesionBroken){ Real twist = geom->getTwist() - phys->twp; Real twistM=twist*phys->ktw; //elastic twist moment. bool sgnChanged=0; //whether the twist moment just passed the equilibrium state. if(!contact->isFresh(scene) && phys->moment_twist.dot(twistM*geom->normal)<0)sgnChanged=1; if(std::abs(twist)>phys->maxTwist){ phys->cohesionBroken=1; twistM=0; } else{ if(std::abs(twistM)>phys->maxElTw || phys->onPlastTw){ //plastic deformation. phys->onPlastTw=1; if(std::abs(phys->maxCrpRchdTw[0])>std::abs(twist)){ //unloading/reloading twistM = phys->kTwUnld*(twist-phys->maxCrpRchdTw[0])+phys->maxCrpRchdTw[1]; } else{//creep loading. int sign = twist<0?-1:1; twistM = sign*phys->maxElTw+phys->kTwCrp*(twist-sign*phys->maxElTw/phys->ktw); //creep phys->maxCrpRchdTw[0]=twist; //new maximum reached phys->maxCrpRchdTw[1]=twistM; } if(sgnChanged){ phys->maxElTw=0; phys->twp=geom->getTwist(); phys->maxCrpRchdTw[0]=0; } } else{ //elasticity phys->maxCrpRchdTw[0]=twist; phys->maxCrpRchdTw[1]=twistM; } } phys->moment_twist = twistM * geom->normal; } else phys->moment_twist=Vector3r(0,0,0); /// Bending /// incremental form. if(!phys->cohesionBroken){ Vector3r bendM = phys->moment_bending; Vector3r relAngVel = geom->getRelAngVel(de1,de2,dt); Vector3r relRotBend = (relAngVel - geom->normal.dot(relAngVel)*geom->normal)*dt; // relative rotation due to rolling behaviour bendM = geom->rotate(phys->moment_bending); // rotate moment vector (updated) phys->pureCreep=geom->rotate(phys->pureCreep); // pure creep is updated to compute the damage. Vector3r bendM_elast = bendM-phys->kr*relRotBend; if(bendM_elast.norm()>phys->maxElB || phys->onPlastB){ // plastic behavior phys->onPlastB=1; bendM=bendM-phys->kDam*relRotBend; //trial bending if(bendM.norm()moment_bending.norm()){ // if bending decreased, we are unloading ... bendM = bendM+phys->kDam*relRotBend-phys->kRUnld*relRotBend; // ... so undo bendM and apply unload coefficient. Vector3r newPureCreep = phys->pureCreep-phys->kRCrp*relRotBend; // trial pure creep. phys->pureCreep = newPureCreep.norm()pureCreep.norm()?newPureCreep:phys->pureCreep+phys->kRCrp*relRotBend; // while unloading, pure creep must decrease. phys->kDam=phys->kr+(phys->kRCrp-phys->kr)*(phys->maxCrpRchdB.norm()-phys->maxElB)/(phys->maxBendMom-phys->maxElB); // compute the damage coefficient. } else{ // bending increased, so we are loading (bendM has to be unchanged). Vector3r newPureCreep = phys->pureCreep-phys->kRCrp*relRotBend; phys->pureCreep = newPureCreep.norm()>phys->pureCreep.norm()?newPureCreep:phys->pureCreep+phys->kRCrp*relRotBend; // while loading, pure creep must increase. if(phys->pureCreep.norm()pureCreep; // bending moment can't be greather than pure creep. if(phys->pureCreep.norm()>phys->maxCrpRchdB.norm()) phys->maxCrpRchdB=phys->pureCreep; // maxCrpRchdB must follow the maximum of pure creep. if(phys->pureCreep.norm()>phys->maxBendMom){ phys->cohesionBroken=1; bendM=bendM_elast=Vector3r(0,0,0); } } phys->moment_bending=bendM; } else{//elasticity phys->pureCreep=phys->moment_bending=phys->maxCrpRchdB=bendM_elast; phys->kDam=phys->kRCrp; } } phys->shearForce=shearForce; phys->normalForce=-Fn*geom->normal; applyForceAtContactPoint(phys->normalForce+phys->shearForce, geom->contactPoint, id1, de1->se3.position, id2, de2->se3.position + (scene->isPeriodic ? scene->cell->intrShiftPos(contact->cellDist): Vector3r::Zero())); scene->forces.addTorque(id1,-phys->moment_bending-phys->moment_twist); scene->forces.addTorque(id2,phys->moment_bending+phys->moment_twist); return true; } trunk-2018.02b/pkg/dem/InelastCohFrictPM.hpp000066400000000000000000000160611324306050200205070ustar00rootroot00000000000000/************************************************************************* * Copyright (C) 2012 by Ignacio Olmedo nolmedo.manich@gmail.com * * Copyright (C) 2012 by François Kneib francois.kneib@gmail.com * * This program is free software; it is licensed under the terms of the * * GNU General Public License v2 or later. See file LICENSE for details. * *************************************************************************/ #pragma once #include #include #include #include "CohesiveFrictionalContactLaw.hpp" class InelastCohFrictMat : public FrictMat { public : virtual ~InelastCohFrictMat () {}; /// Serialization YADE_CLASS_BASE_DOC_ATTRS_CTOR(InelastCohFrictMat,FrictMat,"", ((Real,tensionModulus,0.0,,"Tension elasticity modulus")) ((Real,compressionModulus,0.0,,"Compresion elasticity modulus")) ((Real,shearModulus,0.0,,"shear elasticity modulus")) ((Real,alphaKr,2.0,,"Dimensionless coefficient used for the rolling stiffness.")) ((Real,alphaKtw,2.0,,"Dimensionless coefficient used for the twist stiffness.")) ((Real,nuBending,0.0,,"Bending elastic stress limit")) ((Real,nuTwist,0.0,,"Twist elastic stress limit")) ((Real,sigmaTension,0.0,,"Tension elastic stress limit")) ((Real,sigmaCompression,0.0,,"Compression elastic stress limit")) ((Real,shearCohesion,0.0,,"Shear elastic stress limit")) ((Real,creepTension,0.0,,"Tension/compression creeping coefficient. Usual values between 0 and 1.")) ((Real,creepBending,0.0,,"Bending creeping coefficient. Usual values between 0 and 1.")) ((Real,creepTwist,0.0,,"Twist creeping coefficient. Usual values between 0 and 1.")) ((Real,unloadTension,0.0,,"Tension/compression plastic unload coefficient. Usual values between 0 and +infinity.")) ((Real,unloadBending,0.0,,"Bending plastic unload coefficient. Usual values between 0 and +infinity.")) ((Real,unloadTwist,0.0,,"Twist plastic unload coefficient. Usual values between 0 and +infinity.")) ((Real,epsilonMaxTension,0.0,,"Maximal plastic strain tension")) ((Real,epsilonMaxCompression,0.0,,"Maximal plastic strain compression")) ((Real,etaMaxBending,0.0,,"Maximal plastic bending strain")) ((Real,etaMaxTwist,0.0,,"Maximal plastic twist strain")), createIndex(); ); /// Indexable REGISTER_CLASS_INDEX(InelastCohFrictMat,FrictMat); }; REGISTER_SERIALIZABLE(InelastCohFrictMat); class InelastCohFrictPhys : public FrictPhys { public : virtual ~InelastCohFrictPhys() {}; YADE_CLASS_BASE_DOC_ATTRS_CTOR(InelastCohFrictPhys,FrictPhys,"", ((bool,cohesionBroken,false,,"is cohesion active? will be set false when a fragile contact is broken")) ((Real,knT,0,,"tension stiffness")) ((Real,knC,0,,"compression stiffness")) ((Real,ktw,0,,"twist shear stiffness")) ((Real,ks,0,,"shear stiffness")) ((Real,kr,0,,"bending stiffness")) ((Real,maxElB,0.0,,"Maximum bending elastic moment.")) ((Real,maxElTw,0.0,,"Maximum twist elastic moment.")) ((Real,maxElT,0.0,,"Maximum tension elastic force.")) ((Real,maxElC,0.0,,"Maximum compression elastic force.")) ((Real,shearAdhesion,0,,"Maximum elastic shear force (cohesion).")) ((Real,kTCrp,0.0,,"Tension/compression creep stiffness")) ((Real,kRCrp,0.0,,"Bending creep stiffness")) ((Real,kTwCrp,0.0,,"Twist creep stiffness")) ((Real,kTUnld,0.0,,"Tension/compression plastic unload stiffness")) ((Real,kRUnld,0.0,,"Bending plastic unload stiffness")) ((Real,kTwUnld,0.0,,"Twist plastic unload stiffness")) ((Real,maxExten,0.0,,"Plastic failure extension (stretching).")) ((Real,maxContract,0.0,,"Plastic failure contraction (shrinkage).")) ((Real,maxBendMom,0.0,,"Plastic failure bending moment.")) ((Real,maxTwist,0.0,,"Plastic failure twist angle")) ((bool,isBroken,false,,"true if compression plastic fracture achieved")) ((Real,unp,0,,"plastic normal penetration depth describing the equilibrium state.")) ((Real,twp,0,,"plastic twist penetration depth describing the equilibrium state.")) ((bool,onPlastB,false,Attr::readonly,"true if plasticity achieved on bending")) ((bool,onPlastTw,false,Attr::readonly,"true if plasticity achieved on twisting")) ((bool,onPlastT,false,Attr::readonly,"true if plasticity achieved on traction")) ((bool,onPlastC,false,Attr::readonly,"true if plasticity achieved on compression")) ((Vector2r,maxCrpRchdT,Vector2r(0,0),Attr::readonly,"maximal extension reached on plastic deformation. maxCrpRchdT[0] stores un and maxCrpRchdT[1] stores Fn.")) ((Vector2r,maxCrpRchdC,Vector2r(0,0),Attr::readonly,"maximal compression reached on plastic deformation. maxCrpRchdC[0] stores un and maxCrpRchdC[1] stores Fn.")) ((Vector2r,maxCrpRchdTw,Vector2r(0,0),Attr::readonly,"maximal twist reached on plastic deformation. maxCrpRchdTw[0] stores twist angle and maxCrpRchdTw[1] stores twist moment.")) ((Vector3r,maxCrpRchdB,Vector3r(0,0,0),Attr::readonly,"maximal bending moment reached on plastic deformation.")) ((Vector3r,moment_twist,Vector3r(0,0,0),(Attr::readonly),"Twist moment")) ((Vector3r,moment_bending,Vector3r(0,0,0),(Attr::readonly),"Bending moment")) ((Vector3r,pureCreep,Vector3r(0,0,0),(Attr::readonly),"Pure creep curve, used for comparison in calculation.")) ((Real,kDam,0,(Attr::readonly),"Damage coefficient on bending, computed from maximum bending moment reached and pure creep behaviour. Its values will vary between :yref:`InelastCohFrictPhys::kr` and :yref:`InelastCohFrictPhys::kRCrp` .")) // internal attributes , createIndex(); ); /// Indexable REGISTER_CLASS_INDEX(InelastCohFrictPhys,FrictPhys); }; REGISTER_SERIALIZABLE(InelastCohFrictPhys); class Ip2_2xInelastCohFrictMat_InelastCohFrictPhys : public IPhysFunctor { public : virtual void go(const shared_ptr& b1, const shared_ptr& b2, const shared_ptr& interaction); int cohesionDefinitionIteration; YADE_CLASS_BASE_DOC_ATTRS_CTOR(Ip2_2xInelastCohFrictMat_InelastCohFrictPhys,IPhysFunctor, "Generates cohesive-frictional interactions with moments. Used in the contact law :yref:`Law2_ScGeom6D_InelastCohFrictPhys_CohesionMoment`.", , cohesionDefinitionIteration = -1; ); FUNCTOR2D(InelastCohFrictMat,InelastCohFrictMat); }; REGISTER_SERIALIZABLE(Ip2_2xInelastCohFrictMat_InelastCohFrictPhys); class Law2_ScGeom6D_InelastCohFrictPhys_CohesionMoment: public LawFunctor{ public: Real normElastEnergy(); Real shearElastEnergy(); virtual bool go(shared_ptr& _geom, shared_ptr& _phys, Interaction* I); YADE_CLASS_BASE_DOC_ATTRS_CTOR_PY(Law2_ScGeom6D_InelastCohFrictPhys_CohesionMoment,LawFunctor,"This law is currently under developpement. Final version and documentation will come before the end of 2014.", ,, .def("normElastEnergy",&Law2_ScGeom6D_InelastCohFrictPhys_CohesionMoment::normElastEnergy,"Compute normal elastic energy.") .def("shearElastEnergy",&Law2_ScGeom6D_InelastCohFrictPhys_CohesionMoment::shearElastEnergy,"Compute shear elastic energy.") ); FUNCTOR2D(ScGeom6D,InelastCohFrictPhys); DECLARE_LOGGER; }; REGISTER_SERIALIZABLE(Law2_ScGeom6D_InelastCohFrictPhys_CohesionMoment); trunk-2018.02b/pkg/dem/Integrator.cpp000066400000000000000000000237321324306050200173450ustar00rootroot00000000000000#include #include #include #ifdef YADE_OPENMP #include #endif YADE_PLUGIN((Integrator)); void observer::operator()( const stateVector& x , Real t ) const { this->integrator->scene->time=t; this->integrator->setCurrentStates(x); } //! Integrator's pseudo-ctor (factory), taking nested lists of slave engines (might be moved to real ctor perhaps) void Integrator::action(){ } void Integrator::system(const stateVector& currentstates, stateVector& derivatives, Real time) { #ifdef YADE_OPENMP //prevent https://bugs.launchpad.net/yade/+bug/923929 ensureSync(); #endif //Calculate orientation maxVelocitySq=-1; setCurrentStates(currentstates); scene->time=time; const int size=(int)slaves.size(); for(int i=0; i& e, slaves[i]) { e->scene=scene; if(!e->dead && e->isActivated()) e->action(); } } derivatives=getSceneStateDot(); /* std::cout<bodies->size(); scene->forces.sync(); accumstatedotofthescene.resize(2*scene->bodies->size()*7); YADE_PARALLEL_FOREACH_BODY_BEGIN(const shared_ptr& b, scene->bodies){ const Body::id_t& id=b->getId(); Vector3r force=Vector3r::Zero(); Vector3r vel_current; Vector3r moment=Vector3r::Zero(); Vector3r angvel_current; Quaternionr ori_current; Quaternionr angvelquat; Quaternionr oridot_current; if(!b->isClumpMember()) { Real mass=b->state->mass; Vector3r inertia=b->state->inertia; vel_current=b->state->vel; angvel_current=b->state->angVel; ori_current=b->state->ori; // clumps forces if(b->isClump()) { b->shape->cast().addForceTorqueFromMembers(b->state.get(),scene,force,moment); #ifdef YADE_OPENMP //it is safe here, since only one thread will read/write scene->forces.addTorqueUnsynced(id,moment); scene->forces.addForceUnsynced(id,force); #else scene->forces.addTorque(id,moment); scene->forces.addForce(id,force); #endif } force=scene->forces.getForce(id); moment=scene->forces.getTorque(id); /* * Calculation of accelerations * */ force[0]=force[0]/mass; force[1]= force[1]/mass; force[2]= force[2]/mass; //Calculate linear acceleration moment[0]=moment[0]/inertia[0]; moment[1]= moment[1]/inertia[1]; moment[2]= moment[2]/inertia[2]; //Calculate angular acceleration //Check for fixation /* This code block needs optimization */ string str="xyzXYZ"; // Very very very hard coding!!!!! Fixation seems not handled fine by state structure, should be improved. for(int i=0; i<3; i++) if(b->state->blockedDOFs_vec_get().find(str[i]) != std::string::npos){ force[i]=0;vel_current[i]=0;} for(int i=3; i<6; i++) if(b->state->blockedDOFs_vec_get().find(str[i]) != std::string::npos){ moment[i-3]=0;angvel_current[i-3]=0;} angvelquat = Quaternionr(0.0,angvel_current[0],angvel_current[1],angvel_current[2]); oridot_current=Quaternionr(0.5*(angvelquat*ori_current).coeffs()); // if (densityScaling) accel*=state->densityScaling; } else { //is clump member force=Vector3r::Zero(); moment=Vector3r::Zero(); vel_current=Vector3r::Zero(); angvel_current=Vector3r::Zero(); oridot_current=Quaternionr(0,0,0,0);// zero change in quaternion with respect to time } /*Orientation differantion is straight forward.*/ accumstatedotofthescene[id*7+0]=vel_current[0]; accumstatedotofthescene[(id+numberofscenebodies)*7+0]=force[0]; accumstatedotofthescene[id*7+1]=vel_current[1]; accumstatedotofthescene[(id+numberofscenebodies)*7+1]=force[1]; accumstatedotofthescene[id*7+2]=vel_current[2]; accumstatedotofthescene[(id+numberofscenebodies)*7+2]=force[2]; accumstatedotofthescene[id*7+3]=oridot_current.w(); accumstatedotofthescene[(id+numberofscenebodies)*7+3]=moment[0]; accumstatedotofthescene[id*7+4]=oridot_current.x(); accumstatedotofthescene[(id+numberofscenebodies)*7+4]=moment[1]; accumstatedotofthescene[id*7+5]=oridot_current.y(); accumstatedotofthescene[(id+numberofscenebodies)*7+5]=moment[2]; accumstatedotofthescene[id*7+6]=oridot_current.z(); accumstatedotofthescene[(id+numberofscenebodies)*7+6]=0; } YADE_PARALLEL_FOREACH_BODY_END(); } catch(std::exception& e){ LOG_FATAL("Unhandled exception at Integrator::getSceneStateDot the exception information : "<bodies->size(); accumstateofthescene.resize(2*scene->bodies->size()*7); YADE_PARALLEL_FOREACH_BODY_BEGIN(const shared_ptr& b, scene->bodies){ const Body::id_t& id=b->getId(); Vector3r pos_current=b->state->pos; Vector3r vel_current=b->state->vel; Quaternionr ori=b->state->ori; Vector3r angvel=b->state->angVel; accumstateofthescene[id*7+0]=pos_current[0]; accumstateofthescene[(id+numberofscenebodies)*7+0]=vel_current[0]; accumstateofthescene[id*7+1]=pos_current[1]; accumstateofthescene[(id+numberofscenebodies)*7+1]=vel_current[1]; accumstateofthescene[id*7+2]=pos_current[2]; accumstateofthescene[(id+numberofscenebodies)*7+2]=vel_current[2]; accumstateofthescene[id*7+3]=ori.w(); accumstateofthescene[(id+numberofscenebodies)*7+3]=angvel[0]; accumstateofthescene[id*7+4]=ori.x(); accumstateofthescene[(id+numberofscenebodies)*7+4]=angvel[1]; accumstateofthescene[id*7+5]=ori.y(); accumstateofthescene[(id+numberofscenebodies)*7+5]=angvel[2]; accumstateofthescene[id*7+6]=ori.z(); accumstateofthescene[(id+numberofscenebodies)*7+6]=0; } YADE_PARALLEL_FOREACH_BODY_END(); } catch(std::exception& e){ LOG_FATAL("Unhandled exception at Integrator::getCurrentStates the exception information : "<bodies->size(); //Zero max velocity for each thread #ifdef YADE_OPENMP FOREACH(Real& thrMaxVSq, threadMaxVelocitySq) { thrMaxVSq=0; } #endif YADE_PARALLEL_FOREACH_BODY_BEGIN(const shared_ptr& b, scene->bodies){ if(b->isClumpMember()) continue; const Body::id_t& id=b->getId(); Vector3r pos_current; pos_current<state->pos=pos_current; b->state->vel=vel_current; b->state->ori=ori; //std::cout<<"Setting orientation to "<state->ori.normalize(); //Normalize orientation //std::cout<<"Setting angvel to "<state->angVel=angvel; #ifdef YADE_OPENMP Real& thrMaxVSq=threadMaxVelocitySq[omp_get_thread_num()]; thrMaxVSq=max(thrMaxVSq,b->state->vel.squaredNorm()); #else maxVelocitySq=max(maxVelocitySq,b->state->vel.squaredNorm());// Set maximum velocity of the scene #endif if(b->isClump()) Clump::moveMembers(b,scene,this); } YADE_PARALLEL_FOREACH_BODY_END(); #ifdef YADE_OPENMP FOREACH(const Real& thrMaxVSq, threadMaxVelocitySq) { maxVelocitySq=max(maxVelocitySq,thrMaxVSq); } #endif } catch(std::exception& e){ LOG_FATAL("Unhandled exception at Integrator::setCurrentStates the exception information : "<& b, scene->bodies){ // if(b->isClump()) continue; scene->forces.addForce(b->getId(),Vector3r(0,0,0)); } YADE_PARALLEL_FOREACH_BODY_END(); syncEnsured=true; } #endif void Integrator::saveMaximaDisplacement(const shared_ptr& b){ if (!b->bound) return;//clumps for instance, have no bounds, hence not saved Vector3r disp=b->state->pos-b->bound->refPos; Real maxDisp=max(std::abs(disp[0]),max(std::abs(disp[1]),std::abs(disp[2]))); if (!maxDisp || maxDispbound->sweepLength) {/*b->bound->isBounding = (updatingDispFactor>0 && (updatingDispFactor*maxDisp)bound->sweepLength);*/ maxDisp=0.5;//not 0, else it will be seen as "not updated" by the collider, but less than 1 means no colliding } else {/*b->bound->isBounding = false;*/ maxDisp=2;/*2 is more than 1, enough to trigger collider*/} maxVelocitySq=max(maxVelocitySq,maxDisp); } void Integrator::slaves_set(const boost::python::list& slaves2){ std::cout<<"Adding slaves"; int len=boost::python::len(slaves2); slaves.clear(); for(int i=0; i > > serialGroup(slaves2[i]); if (serialGroup.check()){ slaves.push_back(serialGroup()); continue; } boost::python::extract > serialAlone(slaves2[i]); if (serialAlone.check()){ vector > aloneWrap; aloneWrap.push_back(serialAlone()); slaves.push_back(aloneWrap); continue; } PyErr_SetString(PyExc_TypeError,"Engines that are given to Integrator should be in two cases (a) in an ordered group, (b) alone engines"); boost::python::throw_error_already_set(); } } boost::python::list Integrator::slaves_get(){ boost::python::list ret; FOREACH(vector >& grp, slaves){ if(grp.size()==1) ret.append(boost::python::object(grp[0])); else ret.append(boost::python::object(grp)); } return ret; } trunk-2018.02b/pkg/dem/Integrator.hpp000066400000000000000000000060561324306050200173520ustar00rootroot00000000000000#pragma once #include class Integrator; typedef std::vector stateVector;// Currently, we are unable to use Eigen library within odeint /*Observer used to update the state of the scene*/ class observer { Integrator* integrator; public: observer(Integrator* _in):integrator(_in){} void operator()( const stateVector& /* x */ , Real /* t */ ) const; }; //[ ode_wrapper template< class Obj , class Mem > class ode_wrapper { Obj m_obj; Mem m_mem; public: ode_wrapper( Obj obj , Mem mem ) : m_obj( obj ) , m_mem( mem ) { } template< class State , class Deriv , class Time > void operator()( const State &x , Deriv &dxdt , Time t ) { (m_obj.*m_mem)( x , dxdt , t ); } }; template< class Obj , class Mem > ode_wrapper< Obj , Mem > make_ode_wrapper( Obj obj , Mem mem ) { return ode_wrapper< Obj , Mem >( obj , mem ); } //] class Integrator: public TimeStepper { public: stateVector accumstateofthescene;//pos+vel stateVector accumstatedotofthescene;//only the accelerations stateVector resetstate;//last state before integration attempt Real timeresetvalue; inline void evaluateQuaternions(const stateVector &); //evaluate quaternions after integration typedef vector > > slaveContainer; #ifdef YADE_OPENMP vector threadMaxVelocitySq; #endif virtual void action(); virtual void system(const stateVector&, stateVector&, Real); //System function to calculate the derivatives of states virtual bool isActivated(){return true;} // py access boost::python::list slaves_get(); stateVector& getSceneStateDot(); bool saveCurrentState(Scene const* ourscene);//Before any integration attempt state of the scene should be saved. bool resetLastState(void);//Before any integration attempt state of the scene should be saved. void slaves_set(const boost::python::list& slaves); stateVector& getCurrentStates(void); bool setCurrentStates(stateVector); Real updatingDispFactor;//(experimental) Displacement factor used to trigger bound update: the bound is updated only if updatingDispFactor*disp>sweepDist when >0, else all bounds are updated. void saveMaximaDisplacement(const shared_ptr& b); #ifdef YADE_OPENMP void ensureSync(); bool syncEnsured; #endif YADE_CLASS_BASE_DOC_ATTRS_CTOR_PY(Integrator,TimeStepper,"Integration Engine Interface.", ((slaveContainer,slaves,,,"[will be overridden]")) ((Real,integrationsteps,,,"all integrationsteps count as all succesfull substeps")) ((Real,maxVelocitySq,NaN,,"store square of max. velocity, for informative purposes; computed again at every step. |yupdate|")) , /*ctor*/ #ifdef YADE_OPENMP threadMaxVelocitySq.resize(omp_get_max_threads()); syncEnsured=false; #endif , /*py*/ .add_property("slaves",&Integrator::slaves_get,&Integrator::slaves_set,"List of lists of Engines to calculate the force acting on the particles; to obtain the derivatives of the states, engines inside will be run sequentially."); ); }; REGISTER_SERIALIZABLE(Integrator); trunk-2018.02b/pkg/dem/Ip2_ElastMat.cpp000066400000000000000000000043601324306050200174470ustar00rootroot00000000000000#include "Ip2_ElastMat.hpp" #include #include YADE_PLUGIN((Ip2_ElastMat_ElastMat_NormPhys)(Ip2_ElastMat_ElastMat_NormShearPhys)); void Ip2_ElastMat_ElastMat_NormPhys::go( const shared_ptr& b1 , const shared_ptr& b2 , const shared_ptr& interaction) { if(interaction->phys) return; const shared_ptr& mat1 = YADE_PTR_CAST(b1); const shared_ptr& mat2 = YADE_PTR_CAST(b2); Real Ea = mat1->young; Real Eb = mat2->young; interaction->phys = shared_ptr(new NormPhys()); const shared_ptr& phys = YADE_PTR_CAST(interaction->phys); Real Kn; const GenericSpheresContact* geom=dynamic_cast(interaction->geom.get()); if (geom) { Real Ra,Rb;//Vector3r normal; Ra=geom->refR1>0?geom->refR1:geom->refR2; Rb=geom->refR2>0?geom->refR2:geom->refR1; //harmonic average of the two stiffnesses when (Ri.Ei/2) is the stiffness of a contact point on sphere "i" Kn = 2*Ea*Ra*Eb*Rb/(Ea*Ra+Eb*Rb); } else { Kn = 2*Ea*Eb/(Ea+Eb); } phys->kn = Kn; }; void Ip2_ElastMat_ElastMat_NormShearPhys::go( const shared_ptr& b1 , const shared_ptr& b2 , const shared_ptr& interaction) { if(interaction->phys) return; const shared_ptr& mat1 = YADE_PTR_CAST(b1); const shared_ptr& mat2 = YADE_PTR_CAST(b2); Real Ea = mat1->young; Real Eb = mat2->young; Real Va = mat1->poisson; Real Vb = mat2->poisson; interaction->phys = shared_ptr(new NormShearPhys()); const shared_ptr& phys = YADE_PTR_CAST(interaction->phys); Real Kn=0.0, Ks=0.0; GenericSpheresContact* geom=dynamic_cast(interaction->geom.get()); if (geom) { Real Ra,Rb;//Vector3r normal; Ra=geom->refR1>0?geom->refR1:geom->refR2; Rb=geom->refR2>0?geom->refR2:geom->refR1; //harmonic average of the two stiffnesses when (Ri.Ei/2) is the stiffness of a contact point on sphere "i" Kn = 2*Ea*Ra*Eb*Rb/(Ea*Ra+Eb*Rb); Ks = 2*Ea*Ra*Va*Eb*Rb*Vb/(Ea*Ra*Va+Eb*Rb*Vb); } else { Kn = 2*Ea*Eb/(Ea+Eb); Kn = 2*Ea*Va*Eb*Vb/(Ea*Va+Eb*Vb); } phys->kn = Kn; phys->ks = Ks; }; trunk-2018.02b/pkg/dem/Ip2_ElastMat.hpp000066400000000000000000000017761324306050200174640ustar00rootroot00000000000000 #include #include #include class Ip2_ElastMat_ElastMat_NormPhys: public IPhysFunctor{ public: virtual void go(const shared_ptr& b1, const shared_ptr& b2, const shared_ptr& interaction); FUNCTOR2D(ElastMat,ElastMat); YADE_CLASS_BASE_DOC_ATTRS(Ip2_ElastMat_ElastMat_NormPhys,IPhysFunctor,"Create a :yref:`NormPhys` from two :yref:`ElastMats`. TODO. EXPERIMENTAL", ); }; REGISTER_SERIALIZABLE(Ip2_ElastMat_ElastMat_NormPhys); class Ip2_ElastMat_ElastMat_NormShearPhys: public IPhysFunctor{ public: virtual void go(const shared_ptr& b1, const shared_ptr& b2, const shared_ptr& interaction); FUNCTOR2D(ElastMat,ElastMat); YADE_CLASS_BASE_DOC_ATTRS(Ip2_ElastMat_ElastMat_NormShearPhys,IPhysFunctor,"Create a :yref:`NormShearPhys` from two :yref:`ElastMats`. TODO. EXPERIMENTAL", ); }; REGISTER_SERIALIZABLE(Ip2_ElastMat_ElastMat_NormShearPhys); trunk-2018.02b/pkg/dem/JointedCohesiveFrictionalPM.cpp000066400000000000000000000441771324306050200225670ustar00rootroot00000000000000/* LucScholtes2010 */ #include"JointedCohesiveFrictionalPM.hpp" #include #include #include YADE_PLUGIN((JCFpmMat)(JCFpmState)(JCFpmPhys)(Ip2_JCFpmMat_JCFpmMat_JCFpmPhys)(Law2_ScGeom_JCFpmPhys_JointedCohesiveFrictionalPM)); /********************** Law2_ScGeom_JCFpmPhys_JointedCohesiveFrictionalPM ****************************/ CREATE_LOGGER(Law2_ScGeom_JCFpmPhys_JointedCohesiveFrictionalPM); bool Law2_ScGeom_JCFpmPhys_JointedCohesiveFrictionalPM::go(shared_ptr& ig, shared_ptr& ip, Interaction* contact){ const int &id1 = contact->getId1(); const int &id2 = contact->getId2(); ScGeom* geom = static_cast(ig.get()); JCFpmPhys* phys = static_cast(ip.get()); Body* b1 = Body::byId(id1,scene).get(); Body* b2 = Body::byId(id2,scene).get(); Real Dtensile=phys->FnMax/phys->kn; string fileCracks = "cracks_"+Key+".txt"; /// Defines the interparticular distance used for computation Real D = 0; /*this is for setting the equilibrium distance between all cohesive elements at the first contact detection*/ if ( contact->isFresh(scene) ) { phys->normalForce = Vector3r::Zero(); phys->shearForce = Vector3r::Zero(); if ((smoothJoint) && (phys->isOnJoint)) { phys->jointNormal = geom->normal.dot(phys->jointNormal)*phys->jointNormal; //to set the joint normal colinear with the interaction normal phys->jointNormal.normalize(); phys->initD = std::abs((b1->state->pos - b2->state->pos).dot(phys->jointNormal)); // to set the initial gap as the equilibrium gap } else { phys->initD = geom->penetrationDepth; } } if ( smoothJoint && phys->isOnJoint ) { if ( phys->more || ( phys-> jointCumulativeSliding > (2*min(geom->radius1,geom->radius2)) ) ) { if (!neverErase) return false; else { phys->shearForce = Vector3r::Zero(); phys->normalForce = Vector3r::Zero(); phys->isCohesive =0; phys->FnMax = 0; phys->FsMax = 0; return true; } } else { D = phys->initD - std::abs((b1->state->pos - b2->state->pos).dot(phys->jointNormal)); } } else { D = geom->penetrationDepth - phys->initD; } phys->crackJointAperture = D<0? -D : 0.; // for DFNFlow /* Determination of interaction */ if (D < 0) { //tensile configuration if ( !phys->isCohesive) { if (!neverErase) return false; else { phys->shearForce = Vector3r::Zero(); phys->normalForce = Vector3r::Zero(); phys->isCohesive =0; phys->FnMax = 0; phys->FsMax = 0; return true; } } if ( phys->isCohesive && (phys->FnMax>0) && (std::abs(D)>Dtensile) ) { nbTensCracks++; phys->isCohesive = 0; phys->FnMax = 0; phys->FsMax = 0; /// Do we need both the following lines? phys->breakOccurred = true; // flag to trigger remesh for DFNFlowEngine phys->isBroken = true; // flag for DFNFlowEngine // update body state with the number of broken bonds -> do we really need that? JCFpmState* st1=dynamic_cast(b1->state.get()); JCFpmState* st2=dynamic_cast(b2->state.get()); st1->nbBrokenBonds++; st2->nbBrokenBonds++; st1->damageIndex+=1.0/st1->nbInitBonds; st2->damageIndex+=1.0/st2->nbInitBonds; Real scalarNF=phys->normalForce.norm(); Real scalarSF=phys->shearForce.norm(); totalTensCracksE+=0.5*( ((scalarNF*scalarNF)/phys->kn) + ((scalarSF*scalarSF)/phys->ks) ); totalCracksSurface += phys->crossSection; if (recordCracks){ std::ofstream file (fileCracks.c_str(), !cracksFileExist ? std::ios::trunc : std::ios::app); if(file.tellp()==0){ file <<"iter time p0 p1 p2 type size norm0 norm1 norm2 nrg"<isOnJoint)) { crackNormal=phys->jointNormal; } else {crackNormal=geom->normal;} file << boost::lexical_cast ( scene->iter ) << " " << boost::lexical_cast ( scene->time ) <<" "<< boost::lexical_cast ( geom->contactPoint[0] ) <<" "<< boost::lexical_cast ( geom->contactPoint[1] ) <<" "<< boost::lexical_cast ( geom->contactPoint[2] ) <<" "<< 1 <<" "<< boost::lexical_cast ( 0.5*(geom->radius1+geom->radius2) ) <<" "<< boost::lexical_cast ( crackNormal[0] ) <<" "<< boost::lexical_cast ( crackNormal[1] ) <<" "<< boost::lexical_cast ( crackNormal[2] ) <<" "<< boost::lexical_cast ( 0.5*( ((scalarNF*scalarNF)/phys->kn) + ((scalarSF*scalarSF)/phys->ks) ) ) <shearForce = Vector3r::Zero(); phys->normalForce = Vector3r::Zero(); return true; } } } /* NormalForce */ Real Fn = 0; Fn = phys->kn*D; /* ShearForce */ Vector3r& shearForce = phys->shearForce; Real jointSliding=0; if ((smoothJoint) && (phys->isOnJoint)) { /// incremental formulation (OK?) Vector3r relativeVelocity = (b2->state->vel - b1->state->vel); // angVel are not taken into account as particles on joint don't rotate ???? Vector3r slidingVelocity = relativeVelocity - phys->jointNormal.dot(relativeVelocity)*phys->jointNormal; Vector3r incrementalSliding = slidingVelocity*scene->dt; shearForce -= phys->ks*incrementalSliding; jointSliding = incrementalSliding.norm(); phys->jointCumulativeSliding += jointSliding; } else { shearForce = geom->rotate(phys->shearForce); const Vector3r& incrementalShear = geom->shearIncrement(); shearForce -= phys->ks*incrementalShear; } /* Mohr-Coulomb criterion */ Real maxFs = phys->FsMax + Fn*phys->tanFrictionAngle; Real scalarShearForce = shearForce.norm(); if (scalarShearForce > maxFs) { if (scalarShearForce != 0) shearForce*=maxFs/scalarShearForce; else shearForce=Vector3r::Zero(); if ((smoothJoint) && (phys->isOnJoint)) {phys->dilation=phys->jointCumulativeSliding*phys->tanDilationAngle-D; phys->initD+=(jointSliding*phys->tanDilationAngle);} // if (!phys->isCohesive) { // nbSlips++; // totalSlipE+=((1./phys->ks)*(trialForce-shearForce))/*plastic disp*/.dot(shearForce)/*active force*/; // // if ( (recordSlips) && (maxFs!=0) ) { // std::ofstream file (fileCracks.c_str(), !cracksFileExist ? std::ios::trunc : std::ios::app); // if(file.tellp()==0){ file <<"iter time p0 p1 p2 type size norm0 norm1 norm2 nrg"<isOnJoint)) { crackNormal=phys->jointNormal; } else {crackNormal=geom->normal;} // file << boost::lexical_cast ( scene->iter ) <<" " << boost::lexical_cast ( scene->time ) <<" "<< boost::lexical_cast ( geom->contactPoint[0] ) <<" "<< boost::lexical_cast ( geom->contactPoint[1] ) <<" "<< boost::lexical_cast ( geom->contactPoint[2] ) <<" "<< 0 <<" "<< boost::lexical_cast ( 0.5*(geom->radius1+geom->radius2) ) <<" "<< boost::lexical_cast ( crackNormal[0] ) <<" "<< boost::lexical_cast ( crackNormal[1] ) <<" "<< boost::lexical_cast ( crackNormal[2] ) <<" "<< boost::lexical_cast ( ((1./phys->ks)*(trialForce-shearForce)).dot(shearForce) ) << endl; // } // cracksFileExist=true; // } if ( phys->isCohesive ) { nbShearCracks++; phys->isCohesive = 0; phys->FnMax = 0; phys->FsMax = 0; /// Do we need both the following lines? phys->breakOccurred = true; // flag to trigger remesh for DFNFlowEngine phys->isBroken = true; // flag for DFNFlowEngine // update body state with the number of broken bonds -> do we really need that? JCFpmState* st1=dynamic_cast(b1->state.get()); JCFpmState* st2=dynamic_cast(b2->state.get()); st1->nbBrokenBonds++; st2->nbBrokenBonds++; st1->damageIndex+=1.0/st1->nbInitBonds; st2->damageIndex+=1.0/st2->nbInitBonds; Real scalarNF=phys->normalForce.norm(); Real scalarSF=phys->shearForce.norm(); totalShearCracksE+=0.5*( ((scalarNF*scalarNF)/phys->kn) + ((scalarSF*scalarSF)/phys->ks) ); totalCracksSurface += phys->crossSection; if (recordCracks){ std::ofstream file (fileCracks.c_str(), !cracksFileExist ? std::ios::trunc : std::ios::app); if(file.tellp()==0){ file <<"iter time p0 p1 p2 type size norm0 norm1 norm2 nrg"<isOnJoint)) { crackNormal=phys->jointNormal; } else {crackNormal=geom->normal;} file << boost::lexical_cast ( scene->iter ) << " " << boost::lexical_cast ( scene->time ) <<" "<< boost::lexical_cast ( geom->contactPoint[0] ) <<" "<< boost::lexical_cast ( geom->contactPoint[1] ) <<" "<< boost::lexical_cast ( geom->contactPoint[2] ) <<" "<< 2 <<" "<< boost::lexical_cast ( 0.5*(geom->radius1+geom->radius2) ) <<" "<< boost::lexical_cast ( crackNormal[0] ) <<" "<< boost::lexical_cast ( crackNormal[1] ) <<" "<< boost::lexical_cast ( crackNormal[2] ) <<" "<< boost::lexical_cast ( 0.5*( ((scalarNF*scalarNF)/phys->kn) + ((scalarSF*scalarSF)/phys->ks) ) ) < actually, not necesarily because of the near neighbour interaction: there could be a gap between the bonded particles and thus a broken contact may not be frictional at the next timestep if the detection is done for strictly contacting particles...) -> to TEST // if (!neverErase) return false; // else { // phys->shearForce = Vector3r::Zero(); // phys->normalForce = Vector3r::Zero(); // return true; // } // option 2: delete contact if in tension // shearForce *= Fn*phys->tanFrictionAngle/scalarShearForce; // now or at the next timestep? should not be very different -> to TEST if ( D < 0 ) { // spheres do not touch if (!neverErase) return false; else { phys->shearForce = Vector3r::Zero(); phys->normalForce = Vector3r::Zero(); return true; } } } } /* Apply forces */ if ((smoothJoint) && (phys->isOnJoint)) { phys->normalForce = Fn*phys->jointNormal; } else { phys->normalForce = Fn*geom->normal; } Vector3r f = phys->normalForce + shearForce; /// applyForceAtContactPoint computes torque also and, for now, we don't want rotation for particles on joint (some errors in calculation due to specific geometry) //applyForceAtContactPoint(f, geom->contactPoint, I->getId2(), b2->state->pos, I->getId1(), b1->state->pos, scene); scene->forces.addForce (id1,-f); scene->forces.addForce (id2, f); // simple solution to avoid torque computation for particles interacting on a smooth joint if ( (phys->isOnJoint)&&(smoothJoint) ) return true; /// those lines are needed if rootBody->forces.addForce and rootBody->forces.addMoment are used instead of applyForceAtContactPoint -> NOTE need to check for accuracy!!! scene->forces.addTorque(id1,(geom->radius1-0.5*geom->penetrationDepth)* geom->normal.cross(-f)); scene->forces.addTorque(id2,(geom->radius2-0.5*geom->penetrationDepth)* geom->normal.cross(-f)); return true; } CREATE_LOGGER(Ip2_JCFpmMat_JCFpmMat_JCFpmPhys); void Ip2_JCFpmMat_JCFpmMat_JCFpmPhys::go(const shared_ptr& b1, const shared_ptr& b2, const shared_ptr& interaction){ /* avoid updates of interaction if it already exists */ if( interaction->phys ) return; ScGeom* geom=dynamic_cast(interaction->geom.get()); assert(geom); const shared_ptr& yade1 = YADE_PTR_CAST(b1); const shared_ptr& yade2 = YADE_PTR_CAST(b2); JCFpmState* st1=dynamic_cast(Body::byId(interaction->getId1(),scene)->state.get()); JCFpmState* st2=dynamic_cast(Body::byId(interaction->getId2(),scene)->state.get()); shared_ptr contactPhysics(new JCFpmPhys()); /* From material properties */ Real E1 = yade1->young; Real E2 = yade2->young; Real v1 = yade1->poisson; Real v2 = yade2->poisson; Real f1 = yade1->frictionAngle; Real f2 = yade2->frictionAngle; Real rf1 = yade1->residualFrictionAngle>=0? yade1->residualFrictionAngle: yade1->frictionAngle; Real rf2 = yade2->residualFrictionAngle>=0? yade2->residualFrictionAngle: yade2->frictionAngle; Real SigT1 = yade1->tensileStrength; Real SigT2 = yade2->tensileStrength; Real Coh1 = yade1->cohesion; Real Coh2 = yade2->cohesion; /* From interaction geometry */ Real R1= geom->radius1; Real R2= geom->radius2; contactPhysics->crossSection = Mathr::PI*pow(min(R1,R2),2); /* Pass values to JCFpmPhys. In case of a "jointed" interaction, the following values will be replaced by other ones later (in few if(){} blocks)*/ // elastic properties contactPhysics->kn = 2.*E1*R1*E2*R2/(E1*R1+E2*R2); ( (v1==0)&&(v2==0) )? contactPhysics->ks=0 : contactPhysics->ks = 2.*E1*R1*v1*E2*R2*v2/(E1*R1*v1+E2*R2*v2); // cohesive properties ///to set if the contact is cohesive or not if ( ((cohesiveTresholdIteration < 0) || (scene->iter < cohesiveTresholdIteration)) && (std::min(SigT1,SigT2)>0 || std::min(Coh1,Coh2)>0) && (yade1->type == yade2->type)){ contactPhysics->isCohesive=true; st1->nbInitBonds++; st2->nbInitBonds++; } if ( contactPhysics->isCohesive ) { contactPhysics->FnMax = std::min(SigT1,SigT2)*contactPhysics->crossSection; contactPhysics->FsMax = std::min(Coh1,Coh2)*contactPhysics->crossSection; } // do we need that? // else { // contactPhysics->FnMax = 0.; // contactPhysics->FsMax = 0.; // } // frictional properties contactPhysics->isCohesive? contactPhysics->tanFrictionAngle = std::tan(std::min(f1,f2)) : contactPhysics->tanFrictionAngle = std::tan(std::min(rf1,rf2)); /// +++ Jointed interactions ->NOTE: geom->normal is oriented from 1 to 2 / jointNormal from plane to sphere if ( st1->onJoint && st2->onJoint ) { if ( (((st1->jointNormal1.cross(st2->jointNormal1)).norm()<0.1) && (st1->jointNormal1.dot(st2->jointNormal1)<0)) || (((st1->jointNormal1.cross(st2->jointNormal2)).norm()<0.1) && (st1->jointNormal1.dot(st2->jointNormal2)<0)) || (((st1->jointNormal1.cross(st2->jointNormal3)).norm()<0.1) && (st1->jointNormal1.dot(st2->jointNormal3)<0)) ) { contactPhysics->isOnJoint = true; contactPhysics->jointNormal = st1->jointNormal1; } else if ( (((st1->jointNormal2.cross(st2->jointNormal1)).norm()<0.1) && (st1->jointNormal2.dot(st2->jointNormal1)<0)) || (((st1->jointNormal2.cross(st2->jointNormal2)).norm()<0.1) && (st1->jointNormal2.dot(st2->jointNormal2)<0)) || (((st1->jointNormal2.cross(st2->jointNormal3)).norm()<0.1) && (st1->jointNormal2.dot(st2->jointNormal3)<0)) ) { contactPhysics->isOnJoint = true; contactPhysics->jointNormal = st1->jointNormal2; } else if ( (((st1->jointNormal3.cross(st2->jointNormal1)).norm()<0.1) && (st1->jointNormal3.dot(st2->jointNormal1)<0)) || (((st1->jointNormal3.cross(st2->jointNormal2)).norm()<0.1) && (st1->jointNormal3.dot(st2->jointNormal2)<0)) || (((st1->jointNormal3.cross(st2->jointNormal3)).norm()<0.1) && (st1->jointNormal3.dot(st2->jointNormal3)<0)) ) { contactPhysics->isOnJoint = true; contactPhysics->jointNormal = st1->jointNormal3; } else if ( (st1->joint>3 || st2->joint>3) && ( ( ((st1->jointNormal1.cross(st2->jointNormal1)).norm()>0.1) && ((st1->jointNormal1.cross(st2->jointNormal2)).norm()>0.1) && ((st1->jointNormal1.cross(st2->jointNormal3)).norm()>0.1) ) || ( ((st1->jointNormal2.cross(st2->jointNormal1)).norm()>0.1) && ((st1->jointNormal2.cross(st2->jointNormal2)).norm()>0.1) && ((st1->jointNormal2.cross(st2->jointNormal3)).norm()>0.1) ) || ( ((st1->jointNormal3.cross(st2->jointNormal1)).norm()>0.1) && ((st1->jointNormal3.cross(st2->jointNormal2)).norm()>0.1) && ((st1->jointNormal3.cross(st2->jointNormal3)).norm()>0.1) ) ) ) { contactPhysics->isOnJoint = true; contactPhysics->more = true; contactPhysics->jointNormal = geom->normal; } } ///to specify joint properties if ( contactPhysics->isOnJoint ) { Real jf1 = yade1->jointFrictionAngle; Real jf2 = yade2->jointFrictionAngle; Real jkn1 = yade1->jointNormalStiffness; Real jkn2 = yade2->jointNormalStiffness; Real jks1 = yade1->jointShearStiffness; Real jks2 = yade2->jointShearStiffness; Real jdil1 = yade1->jointDilationAngle; Real jdil2 = yade2->jointDilationAngle; Real jcoh1 = yade1->jointCohesion; Real jcoh2 = yade2->jointCohesion; Real jSigT1 = yade1->jointTensileStrength; Real jSigT2 = yade2->jointTensileStrength; contactPhysics->tanFrictionAngle = std::tan(std::min(jf1,jf2)); //contactPhysics->kn = jointNormalStiffness*2.*R1*R2/(R1+R2); // very first expression from Luc //contactPhysics->kn = (jkn1+jkn2)/2.0*2.*R1*R2/(R1+R2); // after putting jointNormalStiffness in material contactPhysics->kn = ( jkn1 + jkn2 ) /2.0 * contactPhysics->crossSection; // for a size independant expression contactPhysics->ks = ( jks1 + jks2 ) /2.0 * contactPhysics->crossSection; // for a size independant expression contactPhysics->tanDilationAngle = std::tan(std::min(jdil1,jdil2)); ///to set if the contact is cohesive or not if ( ((cohesiveTresholdIteration < 0) || (scene->iter < cohesiveTresholdIteration)) && (std::min(jcoh1,jcoh2)>0 || std::min(jSigT1,jSigT2)>0) ) { contactPhysics->isCohesive=true; st1->nbInitBonds++; st2->nbInitBonds++; } else { contactPhysics->isCohesive=false; contactPhysics->FnMax=0; contactPhysics->FsMax=0; } if ( contactPhysics->isCohesive ) { contactPhysics->FnMax = std::min(jSigT1,jSigT2)*contactPhysics->crossSection; contactPhysics->FsMax = std::min(jcoh1,jcoh2)*contactPhysics->crossSection; } } interaction->phys = contactPhysics; } JCFpmPhys::~JCFpmPhys(){} trunk-2018.02b/pkg/dem/JointedCohesiveFrictionalPM.hpp000066400000000000000000000242341324306050200225640ustar00rootroot00000000000000/* lucScholtes2010 */ #pragma once #include #include #include #include /** This class holds information associated with each body state*/ class JCFpmState: public State { YADE_CLASS_BASE_DOC_ATTRS_CTOR(JCFpmState,State,"JCFpm state information about each body.", ((int,nbInitBonds,0,,"Number of initial bonds. [-]")) ((int,nbBrokenBonds,0,,"Number of broken bonds. [-]")) ((Real,damageIndex,0,,"Ratio of broken bonds over initial bonds. [-]")) ((bool,onJoint,false,,"Identifies if the particle is on a joint surface.")) ((int,joint,0,,"Indicates the number of joint surfaces to which the particle belongs (0-> no joint, 1->1 joint, etc..). [-]")) ((Vector3r,jointNormal1,Vector3r::Zero(),,"Specifies the normal direction to the joint plane 1. Rk: the ideal here would be to create a vector of vector wich size is defined by the joint integer (as much joint normals as joints). However, it needs to make the pushback function works with python since joint detection is done through a python script. lines 272 to 312 of cpp file should therefore be adapted. [-]")) ((Vector3r,jointNormal2,Vector3r::Zero(),,"Specifies the normal direction to the joint plane 2. [-]")) ((Vector3r,jointNormal3,Vector3r::Zero(),,"Specifies the normal direction to the joint plane 3. [-]")) , createIndex(); ); REGISTER_CLASS_INDEX(JCFpmState,State); }; REGISTER_SERIALIZABLE(JCFpmState); /** This class holds information associated with each body */ class JCFpmMat: public FrictMat { public: virtual shared_ptr newAssocState() const { return shared_ptr(new JCFpmState); } virtual bool stateTypeOk(State* s) const { return (bool)dynamic_cast(s); } YADE_CLASS_BASE_DOC_ATTRS_CTOR(JCFpmMat,FrictMat,"Possibly jointed, cohesive frictional material, for use with other JCFpm classes", ((int,type,0,,"If particles of two different types interact, it will be with friction only (no cohesion).[-]")) ((Real,tensileStrength,0.,,"Defines the maximum admissible normal force in traction in the matrix (:yref:`FnMax` = tensileStrength * :yref:`crossSection`). [Pa]")) ((Real,cohesion,0.,,"Defines the maximum admissible tangential force in shear, for Fn=0, in the matrix (:yref:`FsMax` = cohesion * :yref:`crossSection`). [Pa]")) ((Real,residualFrictionAngle,-1.,,"Defines the residual friction angle (when contacts are not cohesive). residualFrictionAngle=frictionAngle if not specified. [degrees]")) ((Real,jointNormalStiffness,0.,,"Defines the normal stiffness on the joint surface. [Pa/m]")) ((Real,jointShearStiffness,0.,,"Defines the shear stiffness on the joint surface. [Pa/m]")) ((Real,jointTensileStrength,0.,,"Defines the :yref:`maximum admissible normal force in traction` on the joint surface. [Pa]")) ((Real,jointCohesion,0.,,"Defines the :yref:`maximum admissible tangential force in shear`, for Fn=0, on the joint surface. [Pa]")) ((Real,jointDilationAngle,0,,"Defines the dilatancy of the joint surface (only valid for :yref:`smooth contact logic`). [rad]")) ((Real,jointFrictionAngle,-1,,"Defines Coulomb friction on the joint surface. [rad]")) , createIndex(); ); REGISTER_CLASS_INDEX(JCFpmMat,FrictMat); }; REGISTER_SERIALIZABLE(JCFpmMat); /** This class holds information associated with each interaction */ class JCFpmPhys: public NormShearPhys { public: virtual ~JCFpmPhys(); YADE_CLASS_BASE_DOC_ATTRS_CTOR_PY(JCFpmPhys,NormShearPhys,"Representation of a single interaction of the JCFpm type, storage for relevant parameters", ((Real,initD,0.,,"equilibrium distance for interacting particles. Computed as the interparticular distance at first contact detection.")) ((bool,isBroken,false,,"flag for broken interactions")) ((bool,isCohesive,false,,"If false, particles interact in a frictional way. If true, particles are bonded regarding the given :yref:`cohesion` and :yref:`tensile strength` (or their jointed variants).")) ((bool,more,false,,"specifies if the interaction is crossed by more than 3 joints. If true, interaction is deleted (temporary solution).")) ((bool,isOnJoint,false,,"defined as true when both interacting particles are :yref:`on joint` and are in opposite sides of the joint surface. In this case, mechanical parameters of the interaction are derived from the ''joint...'' material properties of the particles. Furthermore, the normal of the interaction may be re-oriented (see :yref:`Law2_ScGeom_JCFpmPhys_JointedCohesiveFrictionalPM.smoothJoint`).")) ((bool,isOnSlot,false,,"defined as true when interaction is located in the perforation slot (surface).")) ((Real,tanFrictionAngle,0.,,"tangent of Coulomb friction angle for this interaction (auto. computed). [-]")) ((Real,crossSection,0.,,"crossSection=pi*Rmin^2. [m2]")) ((Real,FnMax,0.,,"positiv value computed from :yref:`tensile strength` (or joint variant) to define the maximum admissible normal force in traction: Fn >= -FnMax. [N]")) ((Real,FsMax,0.,,"computed from :yref:`cohesion` (or jointCohesion) to define the maximum admissible tangential force in shear, for Fn=0. [N]")) ((Vector3r,jointNormal,Vector3r::Zero(),,"normal direction to the joint, deduced from e.g. :yref:``.")) ((Real,jointCumulativeSliding,0.,,"sliding distance for particles interacting on a joint. Used, when :yref:`` is true, to take into account dilatancy due to shearing. [-]")) ((Real,tanDilationAngle,0.,,"tangent of the angle defining the dilatancy of the joint surface (auto. computed from :yref:`JCFpmMat.jointDilationAngle`). [-]")) ((Real,dilation,0.,,"defines the normal displacement in the joint after sliding treshold. [m]")) ((Real,crackJointAperture,0.,,"Relative displacement between 2 spheres (in case of a crack it is equivalent of the crack aperture)")) ((bool,breakOccurred,0,,"Flag used to trigger retriangulation as soon as a cohesive bond breaks in FlowEngine (for DFNFlow use only)")) , createIndex(); , ); DECLARE_LOGGER; REGISTER_CLASS_INDEX(JCFpmPhys,NormShearPhys); }; REGISTER_SERIALIZABLE(JCFpmPhys); /** 2d functor creating InteractionPhysics (Ip2) taking JCFpmMat and JCFpmMat of 2 bodies, returning type JCFpmPhys */ class Ip2_JCFpmMat_JCFpmMat_JCFpmPhys: public IPhysFunctor{ public: virtual void go(const shared_ptr& pp1, const shared_ptr& pp2, const shared_ptr& interaction); FUNCTOR2D(JCFpmMat,JCFpmMat); DECLARE_LOGGER; YADE_CLASS_BASE_DOC_ATTRS(Ip2_JCFpmMat_JCFpmMat_JCFpmPhys,IPhysFunctor,"Converts 2 :yref:`JCFpmMat` instances to one :yref:`JCFpmPhys` instance, with corresponding parameters. See :yref:`JCFpmMat` and [Duriez2016]_ for details", ((int,cohesiveTresholdIteration,1,,"should new contacts be cohesive? If strictly negativ, they will in any case. If positiv, they will before this iter, they won't afterward.")) ); }; REGISTER_SERIALIZABLE(Ip2_JCFpmMat_JCFpmMat_JCFpmPhys); /** 2d functor creating the interaction law (Law2) based on SphereContactGeometry (ScGeom) and JCFpmPhys of 2 bodies, returning type JointedCohesiveFrictionalPM */ class Law2_ScGeom_JCFpmPhys_JointedCohesiveFrictionalPM: public LawFunctor{ public: virtual bool go(shared_ptr& _geom, shared_ptr& _phys, Interaction* I); FUNCTOR2D(ScGeom,JCFpmPhys); YADE_CLASS_BASE_DOC_ATTRS(Law2_ScGeom_JCFpmPhys_JointedCohesiveFrictionalPM,LawFunctor,"Interaction law for cohesive frictional material, e.g. rock, possibly presenting joint surfaces, that can be mechanically described with a smooth contact logic [Ivars2011]_ (implemented in Yade in [Scholtes2012]_). See examples/jointedCohesiveFrictionalPM for script examples. Joint surface definitions (through stl meshes or direct definition with gts module) are illustrated there.", ((bool,smoothJoint,false,,"if true, interactions of particles belonging to joint surface (:yref:`JCFpmPhys.isOnJoint`) are handled according to a smooth contact logic [Ivars2011]_, [Scholtes2012]_.")) ((bool,neverErase,false,,"Keep interactions even if particles go away from each other (only in case another constitutive law is in the scene")) ((bool,cracksFileExist,false,,"if true (and if :yref:`recordCracks`), data are appended to an existing 'cracksKey' text file; otherwise its content is reset.")) ((string,Key,"",,"string specifying the name of saved file 'cracks___.txt', when :yref:`recordCracks` is true.")) ((bool,recordCracks,false,,"if true, data about interactions that lose their cohesive feature are stored in the text file cracksKey.txt (see :yref:`Key` and :yref:`cracksFileExist`). It contains 9 columns: the break iteration, the 3 coordinates of the contact point, the type (1 means shear break, while 0 corresponds to tensile break), the ''cross section'' (mean radius of the 2 spheres) and the 3 coordinates of the contact normal.")) ((int,nbTensCracks,0,,"number of tensile microcracks.")) ((int,nbShearCracks,0,,"number of shear microcracks.")) ((Real,totalTensCracksE,0.,,"calculate the overall energy dissipated by interparticle microcracking in tension.")) ((Real,totalShearCracksE,0.,,"calculate the overall energy dissipated by interparticle microcracking in shear.")) ((Real,totalCracksSurface,0.,,"calculate the total cracked surface.")) // ((bool,recordSlips,false,,"if true, data about frictional interactions that slip are stored in a text file cracksKey.txt.")) // ((int,nbSlips,0,,"number of slips.")) // ((Real,totalSlipE,0.,,"calculate the overall energy dissipated by interparticle friction.")) ); DECLARE_LOGGER; }; REGISTER_SERIALIZABLE(Law2_ScGeom_JCFpmPhys_JointedCohesiveFrictionalPM); trunk-2018.02b/pkg/dem/KinemC__Engine.cpp000066400000000000000000000105411324306050200200130ustar00rootroot00000000000000/************************************************************************* * Copyright (C) 2008 by Jerome Duriez * * jerome.duriez@hmg.inpg.fr * * * * This program is free software; it is licensed under the terms of the * * GNU General Public License v2 or later. See file LICENSE for details. * *************************************************************************/ #include YADE_PLUGIN((KinemCTDEngine)(KinemCNDEngine)(KinemCNLEngine)(KinemCNSEngine)); void KinemCTDEngine::action() { KinemSimpleShearBox::getBoxes_Dt(); scene->forces.sync(); Real current_NormalForce=(scene->forces.getForce(id_topbox)).y(); KinemSimpleShearBox::computeScontact(); current_sigma=current_NormalForce/(1000.0*Scontact); // so we have the current value of sigma, in kPa if( ((compSpeed > 0) && (current_sigma < targetSigma)) || ((compSpeed < 0) && (current_sigma > targetSigma)) ) { if(temoin!=0) temoin=0; letMove(0.0,-compSpeed*dt); } else if (temoin==0) { stopMovement(); string f; if (compSpeed > 0) f="Sigmax_"; else f="Sigmin_"; Omega::instance().saveSimulation(Key + f +boost::lexical_cast (floor(targetSigma)) + "kPaReached.xml"); temoin=1; } for(unsigned int j=0;j0)&&(current_sigma > sigma_save[j]) ) || ((compSpeed<0)&&(current_sigma < sigma_save[j])) ) && (temoin_save[j]==0)) { stopMovement(); Omega::instance().saveSimulation(Key + "SigInt_" +boost::lexical_cast (floor(current_sigma)) + "kPareached.xml"); temoin_save[j]=1; } } } void KinemCNDEngine::action() { KinemSimpleShearBox::getBoxes_Dt(); if( ((shearSpeed > 0) && (gamma<=gammalim)) || ((shearSpeed < 0) /*&& (gamma>=gammalim)*/ ) ) { if(temoinfin!=0) temoinfin=0; letMove(shearSpeed * dt,0); gamma+=shearSpeed * dt; } else { stopMovement(); if(temoinfin==0) { Omega::instance().saveSimulation(Key + "endShear.xml"); temoinfin=1; } } for(unsigned int j=0;j0)&&(gamma > gamma_save[j]) ) || ((shearSpeed<0)&&(gamma < gamma_save[j])) ) && (temoin_save[j]==0) ) { stopMovement(); // reset of all the speeds before the save Omega::instance().saveSimulation(Key+"_"+boost::lexical_cast (floor(gamma*1000)) + "mmsheared.xml"); temoin_save[j]=1; } } } void KinemCNLEngine::action() { if(LOG) cout << "debut applyCondi du CNCEngine !!" << endl; KinemSimpleShearBox::getBoxes_Dt(); if(LOG) cout << "gamma = " << boost::lexical_cast(gamma) << " et gammalim = " << boost::lexical_cast(gammalim) << endl; if(gamma<=gammalim) { if(LOG) cout << "Je suis bien dans la partie gamma < gammalim" << endl; if(temoin==0) { if(LOG) cout << "Je veux maintenir la Force a f0 = : " << f0 << endl; temoin=1; } computeDY(0.0); letMove(shearSpeed*dt,deltaH); gamma+=shearSpeed * dt; } else if (temoin<2) { stopMovement(); it_stop=scene->iter; cout << "Shear stopped : gammaLim reached at it "<< it_stop << endl; temoin=2; } else if (temoin==2 && (scene->iter==(it_stop+5000)) ) { Omega::instance().saveSimulation(Key + "endShear" +boost::lexical_cast ( scene->iter ) + ".xml"); Omega::instance().pause(); } for(unsigned int j=0;j gamma_save[j]) && (temoin_save[j]==0)) { stopMovement(); // reset of all the speeds before the save Omega::instance().saveSimulation(Key+"_"+boost::lexical_cast (floor(gamma*1000)) +"_" +boost::lexical_cast (floor(gamma*10000)-10*floor(gamma*1000))+ "mmsheared.xml"); temoin_save[j]=1; } } } void KinemCNSEngine::action() { if(LOG) cerr << "debut applyCondi !!" << endl; KinemSimpleShearBox::getBoxes_Dt(); if(gamma<=gammalim) { computeDY(KnC); letMove(shearSpeed * dt,deltaH); gamma+=shearSpeed * dt; if(temoin==0) { temoin=1; } } else if (temoin<2) { stopMovement(); it_stop=scene->iter; cout << "Cisaillement arrete : gammaLim atteint a l'iteration "<< it_stop << endl; temoin=2; } else if (temoin==2 && (scene->iter==(it_stop+5000)) ) { Omega::instance().saveSimulation(Key + "finCis" +boost::lexical_cast (scene->iter ) + ".xml"); Omega::instance().pause(); } } trunk-2018.02b/pkg/dem/KinemC__Engine.hpp000066400000000000000000000161071324306050200200240ustar00rootroot00000000000000/************************************************************************* * Copyright (C) 2008 by Jerome Duriez * * jerome.duriez@hmg.inpg.fr * * * * This program is free software; it is licensed under the terms of the * * GNU General Public License v2 or later. See file LICENSE for details. * *************************************************************************/ #pragma once #include class KinemCTDEngine : public KinemSimpleShearBox { private : Real current_sigma // Computed in kPa ; int temoin; public : virtual ~KinemCTDEngine() {}; void action(); // ; YADE_CLASS_BASE_DOC_ATTRS_CTOR(KinemCTDEngine,KinemSimpleShearBox, "To compress a simple shear sample by moving the upper box in a vertical way only, so that the tangential displacement (defined by the horizontal gap between the upper and lower boxes) remains constant (thus, the CTD = Constant Tangential Displacement).\n \t The lateral boxes move also to keep always contact. All that until this box is submitted to a given stress (:yref:`targetSigma`). Moreover saves are executed at each value of stresses stored in the vector :yref:`sigma_save`, and at :yref:`targetSigma`", ((Real,compSpeed,0.0,,"(vertical) speed of the upper box : >0 for real compression, <0 for unloading [$m/s$]")) ((std::vector,sigma_save,,,"vector with the values of sigma at which a save of the simulation should be performed [$kPa$]")) ((Real,targetSigma,0.0,,"the value of sigma at which the compression should stop [$kPa$]")), temoin=0; ) }; REGISTER_SERIALIZABLE(KinemCTDEngine); class KinemCNDEngine : public KinemSimpleShearBox { private : int temoinfin; public : void action(); protected : YADE_CLASS_BASE_DOC_ATTRS_CTOR(KinemCNDEngine,KinemSimpleShearBox, "To apply a Constant Normal Displacement (CND) shear for a parallelogram box\n\n \tThis engine, designed for simulations implying a simple shear box (:yref:`SimpleShear` Preprocessor or scripts/simpleShear.py), allows one to perform a constant normal displacement shear, by translating horizontally the upper plate, while the lateral ones rotate so that they always keep contact with the lower and upper walls.", ((Real,shearSpeed,0.0,,"the speed at which the shear is performed : speed of the upper plate [m/s]")) ((Real,gammalim,0.0,,"the value of the tangential displacement at wich the displacement is stopped [m]")) ((Real,gamma,0.0,,"the current value of the tangential displacement")) ((std::vector,gamma_save,,,"vector with the values of gamma at which a save of the simulation is performed [m]")), temoinfin=0; ); }; REGISTER_SERIALIZABLE(KinemCNDEngine); class KinemCNLEngine : public KinemSimpleShearBox { private : int temoin,// utile pour savoir ou on en est it_stop ; public : void action() ; YADE_CLASS_BASE_DOC_ATTRS_CTOR(KinemCNLEngine,KinemSimpleShearBox, "To apply a constant normal stress shear (i.e. Constant Normal Load : CNL) for a parallelogram box (simple shear box : :yref:`SimpleShear` Preprocessor or scripts/simpleShear.py)\n\nThis engine allows one to translate horizontally the upper plate while the lateral ones rotate so that they always keep contact with the lower and upper walls.\n\nIn fact the upper plate can move not only horizontally but also vertically, so that the normal stress acting on it remains constant (this constant value is not chosen by the user but is the one that exists at the beginning of the simulation)\n\nThe right vertical displacements which will be allowed are computed from the rigidity Kn of the sample over the wall (so to cancel a deltaSigma, a normal dplt deltaSigma*S/(Kn) is set)\n\nThe movement is moreover controlled by the user via a :yref:`shearSpeed` which will be the speed of the upper wall, and by a maximum value of horizontal displacement :yref:`gammalim`, after which the shear stops.\n\n.. note::\n\tNot only the positions of walls are updated but also their speeds, which is all but useless considering the fact that in the contact laws these velocities of bodies are used to compute values of tangential relative displacements.\n\n.. warning::\n\tBecause of this last point, if you want to use later saves of simulations executed with this Engine, but without that stopMovement was executed, your boxes will keep their speeds => you will have to cancel them 'by hand' in the .xml.\n", ((Real,shearSpeed,0.0,,"the speed at wich the shearing is performed : speed of the upper plate [m/s]")) ((Real,gammalim,0.0,,"the value of tangential displacement (of upper plate) at wich the shearing is stopped [m]")) ((Real,gamma,0.0,,"current value of tangential displacement [m]")) ((std::vector,gamma_save,,,"vector with the values of gamma at which a save of the simulation is performed [m]")), temoin=0; it_stop=0; ); }; REGISTER_SERIALIZABLE(KinemCNLEngine); class KinemCNSEngine : public KinemSimpleShearBox { private : int temoin,it_stop ; public : void action() ; YADE_CLASS_BASE_DOC_ATTRS_CTOR(KinemCNSEngine,KinemSimpleShearBox, "To apply a Constant Normal Stifness (CNS) shear for a parallelogram box (simple shear)\n\nThis engine, useable in simulations implying one deformable parallelepipedic box, allows one to translate horizontally the upper plate while the lateral ones rotate so that they always keep contact with the lower and upper walls. The upper plate can move not only horizontally but also vertically, so that the normal rigidity defined by DeltaF(upper plate)/DeltaU(upper plate) = constant (= :yref:`KnC` defined by the user).\n\nThe movement is moreover controlled by the user via a :yref:`shearSpeed` which is the horizontal speed of the upper wall, and by a maximum value of horizontal displacement :yref:`gammalim` (of the upper plate), after which the shear stops.\n\n.. note::\n\t not only the positions of walls are updated but also their speeds, which is all but useless considering the fact that in the contact laws these velocities of bodies are used to compute values of tangential relative displacements.\n\n.. warning::\n\tBut, because of this last point, if you want to use later saves of simulations executed with this Engine, but without that stopMovement was executed, your boxes will keep their speeds => you will have to cancel them by hand in the .xml", ((Real,shearSpeed,0.0,,"the speed at wich the shearing is performed : speed of the upper plate [m/s]")) ((Real,gammalim,0.0,,"the value of tangential displacement (of upper plate) at wich the shearing is stopped [m]")) ((Real,gamma,0.0,,"current value of tangential displacement [m]")) ((Real,KnC,10.0e6,,"the normal rigidity chosen by the user [MPa/mm] - the conversion in Pa/m will be made")), temoin=0; it_stop=0; ); }; REGISTER_SERIALIZABLE(KinemCNSEngine); trunk-2018.02b/pkg/dem/KinemSimpleShearBox.cpp000066400000000000000000000131511324306050200210720ustar00rootroot00000000000000/************************************************************************* * Copyright (C) 2010 by Jerome Duriez * * jerome.duriez@hmg.inpg.fr * * * * This program is free software; it is licensed under the terms of the * * GNU General Public License v2 or later. See file LICENSE for details. * *************************************************************************/ #include #include #include #include YADE_PLUGIN((KinemSimpleShearBox)) void KinemSimpleShearBox::computeAlpha() { Quaternionr orientationLeftBox,orientationRightBox; orientationLeftBox = leftbox->state->ori; orientationRightBox = rightbox->state->ori; if(orientationLeftBox.matrix()!=orientationRightBox.matrix()) { cout << "WARNING !!! your lateral boxes have not the same orientation, you're not in the case of a box imagined for creating these engines" << endl; } Real angle; AngleAxisr aa(orientationLeftBox); angle = aa.angle(); // orientationLeftBox.ToAxisAngle(axis,angle); alpha=Mathr::PI/2.0-angle; // right if the initial orientation of the body (on the beginning of the simulation) is q =(1,0,0,0) = FromAxisAngle((0,0,1),0) } void KinemSimpleShearBox::computeScontact() { Real Xleft = leftbox->state->pos.x() + (YADE_CAST(leftbox->shape.get()))->extents.x(); Real Xright = rightbox->state->pos.x() - (YADE_CAST(rightbox->shape.get()))->extents.x(); Real Zfront = frontbox->state->pos.z() - YADE_CAST(frontbox->shape.get())->extents.z(); Real Zback = backbox->state->pos.z() + (YADE_CAST(backbox->shape.get()))->extents.z(); Scontact = (Xright-Xleft)*(Zfront-Zback); // that's so the value of section at the middle of the height of the box } void KinemSimpleShearBox::letMove(Real dX, Real dY) { if(LOG) cout << "It : " << scene->iter << endl; // const Real& dt = scene->dt; // dt value obtained by getBoxes_Dt Real Ysup = topbox->state->pos.y(); Real Ylat = leftbox->state->pos.y(); // Changes in vertical and horizontal velocities : topbox->state->vel = Vector3r(dX/dt,dY/dt,0); leftbox->state->vel = Vector3r(dX/(2.0 * dt),dY/(2.0 * dt),0); rightbox->state->vel = Vector3r(dX/(2.0 * dt),dY/(2.0*dt),0); if(LOG) cout << "dY that will be applied by NewtonIntegrator :" << dY << endl; Real Ysup_mod = Ysup + dY; Real Ylat_mod = Ylat + dY; computeAlpha(); // Then computation of the angle of the rotation,dalpha, to be done : if (alpha == Mathr::PI/2.0) // Case of the very beginning { dalpha = - atan( dX / (Ysup_mod -Ylat_mod) ); } else { Real A = (Ysup_mod - Ylat_mod) * 2.0*tan(alpha) / (2.0*(Ysup - Ylat) + dX*tan(alpha) ); dalpha = atan( (A - tan(alpha))/(1.0 + A * tan(alpha))); } Quaternionr qcorr(AngleAxisr(dalpha,Vector3r::UnitZ())); // Rotation is applied through velocities (and NewtonIntegrator) leftbox->state->angVel = Vector3r(0,0,1)*dalpha/dt; rightbox->state->angVel = Vector3r(0,0,1)*dalpha/dt; } void KinemSimpleShearBox::stopMovement() { // upper plate's speed is zeroed topbox->state->vel = Vector3r(0,0,0); // same for left box leftbox->state->vel = Vector3r(0,0,0); leftbox->state->angVel = Vector3r(0,0,0); // and for rightbox rightbox->state->vel = Vector3r(0,0,0); rightbox->state->angVel = Vector3r(0,0,0); } void KinemSimpleShearBox::computeStiffness() { int nbre_contacts = 0; stiffness=0.0; InteractionContainer::iterator ii = scene->interactions->begin(); InteractionContainer::iterator iiEnd = scene->interactions->end(); for( ; ii!=iiEnd ; ++ii ) { if ((*ii)->isReal()) { const shared_ptr& contact = *ii; Real fn = (static_cast(contact->phys.get()))->normalForce.norm(); if (fn!=0) { int id1 = contact->getId1(), id2 = contact->getId2(); if ( id_topbox==id1 || id_topbox==id2 ) { FrictPhys* currentContactPhysics = static_cast ( contact->phys.get() ); stiffness += currentContactPhysics->kn; nbre_contacts += 1; } } } } if(LOG) cout << "nbre billes en contacts : " << nbre_contacts << endl; if(LOG) cout << "rigidite echantillon calculee : " << stiffness << endl; } void KinemSimpleShearBox::getBoxes_Dt() { leftbox = Body::byId(id_boxleft); rightbox = Body::byId(id_boxright); frontbox = Body::byId(id_boxfront); backbox = Body::byId(id_boxback); topbox = Body::byId(id_topbox); boxbas = Body::byId(id_boxbas); dt = scene->dt; } void KinemSimpleShearBox::computeDY(Real KnC) { scene->forces.sync(); Vector3r fSup=scene->forces.getForce(id_topbox); if(firstRun) { alpha=Mathr::PI/2.0;; y0 = topbox->state->pos.y(); f0 = fSup.y(); firstRun=false; } computeStiffness(); Real hCurrent = topbox->state->pos.y(); computeScontact(); Real fDesired = f0 + KnC * 1.0e9 * Scontact * (hCurrent-y0); // The value of the force desired, with the fact that KnC is in MPa/mm if( stiffness==0 ) { deltaH=0; cerr << "Stiffness(sample) = 0 => DNC in fact : not CNL or CNS..." << endl; } else { deltaH = ( fSup.y() - fDesired )/(stiffness); } if(LOG) cout << "Alors q je veux KnC = " << KnC << " depuis f0 = " << f0 << " et y0 = " << y0 << endl; if(LOG) cout << "deltaH a permettre normalement :" << deltaH << endl; deltaH = (1-wallDamping)*deltaH; if(LOG) cout << "deltaH apres amortissement :" << deltaH << endl; if(std::abs(deltaH) > max_vel*scene->dt) { deltaH=deltaH/std::abs(deltaH)*max_vel*scene->dt; if(LOG) cout << "Correction appliquee pour ne pas depasser vmax(comp)" << endl; } } trunk-2018.02b/pkg/dem/KinemSimpleShearBox.hpp000066400000000000000000000114161324306050200211010ustar00rootroot00000000000000/************************************************************************* * Copyright (C) 2010 by Jerome Duriez * * jerome.duriez@hmg.inpg.fr * * * * This program is free software; it is licensed under the terms of the * * GNU General Public License v2 or later. See file LICENSE for details. * *************************************************************************/ #pragma once #include #include #include #include class KinemSimpleShearBox : public BoundaryController { protected : Real Scontact // computed by computeScontact() ,dt // value of the time step, obtained in getBoxes_Dt ,stiffness // the normal stifness on the sample acting below the upper plate. Computed by computeStiffness ,dalpha // the increment over alpha, due to vertical displacement of upper box ,deltaH // the vertical increment of displacement to allow on the upper plate on this time step to verify either the constant normal stress or the constant normal stifness. Computed here by computeDY(..) ; shared_ptr leftbox; shared_ptr rightbox; shared_ptr frontbox; shared_ptr backbox; shared_ptr topbox; shared_ptr boxbas; void computeAlpha() ,computeScontact() ,stopMovement() // to cancel all velocities when end of the loading is reached ,letMove(Real dgamma,Real dH) //dgamma and dH being computed by different ways in the different Kinem... Engines ,computeStiffness() // computes the stiffness of the contact sample - upper side. Useful for CNL and CNS loads ,computeDY(Real KnC) // computes vertical displacement to perform to obey to the stiffness of the shear wanted : KnC. Useful for CNL (for which KnC = 0) and CNS loads ,getBoxes_Dt() ; YADE_CLASS_BASE_DOC_ATTRS_CTOR(KinemSimpleShearBox,BoundaryController, "This class is supposed to be a mother class for all Engines performing loadings on the simple shear box of :yref:`SimpleShear`. It is not intended to be used by itself, but its declaration and implentation will thus contain all what is useful for all these Engines. The script simpleShear.py illustrates the use of the various corresponding Engines.", ((Real,alpha,Mathr::PI/2.0,,"the angle from the lower box to the left box (trigo wise). Measured by this Engine. Has to be saved, but not to be changed by the user.")) ((std::vector,temoin_save,,,"vector (same length as 'gamma_save' for ex), with 0 or 1 depending whether the save for the corresponding value of gamma has been done (1) or not (0). Has to be saved, but not to be changed by the user.")) ((Body::id_t,id_topbox,3,,"the id of the upper wall")) ((Body::id_t,id_boxbas,1,,"the id of the lower wall")) ((Body::id_t,id_boxleft,0,,"the id of the left wall")) ((Body::id_t,id_boxright,2,,"the id of the right wall")) ((Body::id_t,id_boxfront,5,,"the id of the wall in front of the sample")) ((Body::id_t,id_boxback,4,,"the id of the wall at the back of the sample")) ((Real,max_vel,1.0,,"to limit the speed of the vertical displacements done to control $\\sigma$ (CNL or CNS cases) [$m/s$]")) ((Real,wallDamping,0.2,,"the vertical displacements done to to control $\\sigma$ (CNL or CNS cases) are in fact damped, through this wallDamping")) ((bool,firstRun,true,,"boolean set to false as soon as the engine has done its job one time : useful to know if initial height of, and normal force sustained by, the upper box are known or not (and thus if they have to be initialized). Has to be saved, but not to be changed by the user.")) ((Real,f0,0.0,,"the (vertical) force acting on the upper plate on the very first time step (determined by the Engine). Controls of the loadings in case of :yref:`KinemCNSEngine` or :yref:`KinemCNLEngine` will be done according to this initial value [$N$]. Has to be saved, but not to be changed by the user.")) ((Real,y0,0.0,,"the height of the upper plate at the very first time step : the engine finds its value [$m$]. Has to be saved, but not to be changed by the user.")) ((bool,LOG,false,,"boolean controling the output of messages on the screen")) //FIXME : surely something better to use here ((string,Key,"",,"string to add at the names of the saved files")), /* leftbox = Body::byId(id_boxleft,scene); rightbox = Body::byId(id_boxright,scene); frontbox = Body::byId(id_boxfront,scene); backbox = Body::byId(id_boxback,scene); topbox = Body::byId(id_topbox,scene); boxbas = Body::byId(id_boxbas,scene);*/ Scontact = 0.0; dt = 0.0; stiffness=0.0; dalpha=0.0; deltaH=0.0; ); }; REGISTER_SERIALIZABLE(KinemSimpleShearBox); trunk-2018.02b/pkg/dem/KnKsLaw.cpp000066400000000000000000000266441324306050200165460ustar00rootroot00000000000000#ifdef YADE_POTENTIAL_PARTICLES #include "KnKsLaw.hpp" #include #include #include #include YADE_PLUGIN((Law2_SCG_KnKsPhys_KnKsLaw)(Ip2_FrictMat_FrictMat_KnKsPhys)(KnKsPhys) ); /********************** Law2_Dem3DofGeom_RockPMPhys_Rpm ****************************/ CREATE_LOGGER(Law2_SCG_KnKsPhys_KnKsLaw); bool Law2_SCG_KnKsPhys_KnKsLaw::go(shared_ptr& ig, shared_ptr& ip, Interaction* contact) { //const Real& dt = scene->dt; int id1 = contact->getId1(); int id2 = contact->getId2(); ScGeom* geom= static_cast(ig.get()); KnKsPhys* phys = static_cast(ip.get()); State* de1 = Body::byId(id1,scene)->state.get(); State* de2 = Body::byId(id2,scene)->state.get(); Shape* shape1 = Body::byId(id1,scene)->shape.get(); Shape* shape2 = Body::byId(id2,scene)->shape.get(); PotentialParticle *s1=static_cast(shape1); PotentialParticle *s2=static_cast(shape2); Vector3r& shearForce = phys->shearForce; Real un=geom->penetrationDepth; TRVAR3(geom->penetrationDepth,de1->se3.position,de2->se3.position); /* Need to initialise in python. In the 1st time step. All the particles in contact (controlled by initialOverlap) are identified. The interactions are set to tensile and cohesive (tensionBroken = false and cohesionBroken = false). If there is no initial tension or cohesion, the contact law is run in a tensionless or cohesionless mode */ if(geom->penetrationDepth <0.0 ) { if (neverErase) { phys->shearForce = Vector3r::Zero(); phys->normalForce = Vector3r::Zero(); phys->normalViscous = Vector3r::Zero(); geom->normal = Vector3r::Zero(); phys->tensionBroken = true; } else { scene->interactions->requestErase(id1,id2); return false; } return true; } //Vector3r shearForceBeforeRotate = shearForce; Vector3r shiftVel = Vector3r(0,0,0); //scene->isPeriodic ? (Vector3r)((scene->cell->velGrad*scene->cell->Hsize)*Vector3r((Real) contact->cellDist[0],(Real) contact->cellDist[1],(Real) contact->cellDist[2])) : Vector3r::Zero(); geom->rotate(shearForce); //AndGetShear(shearForce,phys->prevNormal,de1,de2,dt,shiftVel,/*avoid ratcheting*/false); //Vector3r shearForceAfterRotate = shearForce; //Linear elasticity giving "trial" shear force Vector3r shift2(0,0,0); Vector3r incidentV = geom->getIncidentVel(de1, de2, scene->dt, shift2, shiftVel, /*preventGranularRatcheting*/false ); Vector3r incidentVn = geom->normal.dot(incidentV)*geom->normal; // contact normal velocity Vector3r incidentVs = incidentV-incidentVn; // contact shear velocity Vector3r shearIncrement=incidentVs*scene->dt; phys->shearDir = shearIncrement; phys->shearIncrementForCD += shearIncrement.norm(); double du = 0.0; //double debugFn = 0.0; //double u_prev = fabs(phys->u_cumulative); if(phys->shearDir.norm() > pow(10,-15)) { phys->shearDir.normalize(); } double degradeLength = phys->brittleLength; /*jointLength = 100u_peak */ /* Elastic and plastic displacement can have negative signs but must be consistent throughout the simulation */ if(phys->initialShearDir.norm() < pow(10,-11)) { phys->initialShearDir = phys->shearDir; du = shearIncrement.norm(); if(fabs(phys->mobilizedShear)>0.99999) { phys->u_cumulative += du; phys->cumulative_us += du; } else { phys->u_elastic +=du; } } else { du = Mathr::Sign(phys->initialShearDir.dot(phys->shearDir))*shearIncrement.norm(); //check cumulative shear displacement if(fabs(phys->mobilizedShear) > 0.99999) { if(du>0.0) { //if negative it means it is unloading phys->u_cumulative += du; phys->cumulative_us += du; } else { phys->u_elastic +=du; } } else { phys->u_elastic +=du; } } /* Original */ if(phys->twoDimension) { phys->contactArea = phys->unitWidth2D*phys->jointLength; } if(s1->isBoundary == true || s2->isBoundary==true) { phys->tensionBroken = true; phys->cohesionBroken = true; } if(!Talesnick) { un = un-initialOverlapDistance; if (phys->jointType==3) { phys->prevSigma = un*phys->kn_i/(1.0-un/phys->maxClosure); } else { phys->prevSigma = phys->kn*un; } //} phys->normalForce = phys->prevSigma*std::max(pow(10,-15),phys->contactArea)*geom->normal; } phys->Knormal_area = phys->kn*std::max(pow(10,-8),phys->contactArea); if((un <0.0 && fabs(phys->prevSigma)>phys->tension && phys->tensionBroken == false /* first time tension is broken */) || (un<0.0 && phys->tensionBroken==true)) { if (neverErase) { phys->shearForce = Vector3r::Zero(); phys->normalForce = Vector3r::Zero(); phys->normalViscous = Vector3r::Zero(); geom->normal = Vector3r::Zero(); phys->tensionBroken = true; } else { return false; } return true; } /*ORIGINAL */ Vector3r c1x = geom->contactPoint - de1->pos; Vector3r c2x = geom->contactPoint - de2->pos; incidentV = (de2->vel+de2->angVel.cross(c2x)) - (de1->vel+de1->angVel.cross(c1x)); incidentVn = geom->normal.dot(incidentV)*geom->normal; // contact normal velocity incidentVs = incidentV-incidentVn; // contact shear velocity shearIncrement=incidentVs*scene->dt; if(!Talesnick) { double Ks=0.0; if(phys->jointType == 3) { Ks = phys->ks_i*pow(phys->prevSigma,0.6); } else { Ks = phys->ks; } shearForce -= Ks*shearIncrement*std::max(pow(10,-11),phys->contactArea); } phys->Kshear_area = phys->ks*std::max(pow(10,-11),phys->contactArea); const shared_ptr& b1=Body::byId(id1,scene); const shared_ptr& b2=Body::byId(id2,scene); Real mbar = (!b1->isDynamic() && b2->isDynamic()) ? de2->mass : ((!b2->isDynamic() && b1->isDynamic()) ? de1->mass : (de1->mass*de2->mass / (de1->mass + de2->mass))); // get equivalent mass if both bodies are dynamic, if not set it equal to the one of the dynamic body Real Cn_crit = 2.*sqrt(mbar*phys->Knormal_area); // Knormal_area Critical damping coefficient (normal direction) Real Cs_crit = 2.*sqrt(mbar*phys->Kshear_area); // Kshear_area Critical damping coefficient (shear direction) // Note: to compare with the analytical solution you provide cn and cs directly (since here we used a different method to define c_crit) double cn = Cn_crit*phys->viscousDamping; // Damping normal coefficient double cs = Cs_crit*phys->viscousDamping; // Damping tangential coefficient // add normal viscous component if damping is included //double maxFnViscous = phys->normalForce.norm(); phys->normalViscous = cn*incidentVn; //if(phys->normalViscous.norm() > maxFnViscous){ // phys->normalViscous = phys->normalViscous * maxFnViscous/phys->normalViscous.norm(); //} phys->normalForce -= phys->normalViscous; //double baseElevation = geom->contactPoint.z(); /* Water pressure, heat effect */ /* strength degradation */ const double PI = std::atan(1.0)*4; double tan_effective_phi = 0.0; if(s1->isBoundary==true || s2->isBoundary == true || phys->jointType==2 ) { // clay layer at boundary; phys->effective_phi = phys->phi_b; // - 3.25*(1.0-exp(-fabs(phys->cumulative_us)/0.4)); tan_effective_phi = tan(phys->effective_phi/180.0*PI); } else if(phys->intactRock == true) { phys->effective_phi = phys->phi_r + (phys->phi_b-phys->phi_r)*(exp(-fabs(phys->u_cumulative)/degradeLength)); tan_effective_phi = tan(phys->effective_phi/180.0*PI); } else { phys->effective_phi = phys->phi_b; tan_effective_phi = tan(phys->effective_phi/180.0*PI); } /* shear loss */ Vector3r dampedShearForce = shearForce; double cohesiveForce = phys->cohesion*std::max(pow(10,-11),phys->contactArea); Real maxFs = cohesiveForce; if (un>0.0 /*compression*/) { double fN = phys->normalForce.norm(); if(phys->intactRock == true) { if (phys->cohesionBroken == true && allowBreakage == true) { maxFs = std::max( fN,0.0)*tan_effective_phi; } else { maxFs = cohesiveForce+std::max( fN,0.0)*tan_effective_phi; } } else { maxFs = std::max( fN,0.0)*tan_effective_phi; } } if( shearForce.norm() > maxFs ) { Real ratio = maxFs / shearForce.norm(); shearForce *= ratio; dampedShearForce = shearForce; if(allowBreakage == true) { phys->cohesionBroken = true; } phys->shearViscous = Vector3r(0,0,0); } else { /* no damping when it slides */ phys->shearViscous = cs*incidentVs; dampedShearForce = shearForce - phys->shearViscous; } if(shearForce.norm() < pow(10,-11) ) { phys->mobilizedShear = 1.0; } else { phys->mobilizedShear = shearForce.norm()/maxFs; } //we need to use correct branches in the periodic case, the following apply for spheres only Vector3r force = -phys->normalForce-dampedShearForce; if(std::isnan(force.norm())) { //std::cout<<"shearForce: "<normalForce<<", debugFn: "<normalViscous<<", normal: "<normal<<", geom normal: "<normal<<", effective_phi: "<effective_phi<<", shearIncrement: "<forces.addForce(id1,force); scene->forces.addForce(id2,-force); //Vector3r normal = geom->normal; scene->forces.addTorque(id1,c1x.cross(force)); scene->forces.addTorque(id2,-(c2x).cross(force)); phys->prevNormal = geom->normal; return true; } CREATE_LOGGER(Ip2_FrictMat_FrictMat_KnKsPhys); void Ip2_FrictMat_FrictMat_KnKsPhys::go(const shared_ptr& b1, const shared_ptr& b2, const shared_ptr& interaction) { const double PI = 3.14159265358979323846; if(interaction->phys) return; ScGeom* scg=YADE_CAST(interaction->geom.get()); assert(scg); const shared_ptr& sdec1 = YADE_PTR_CAST(b1); const shared_ptr& sdec2 = YADE_PTR_CAST(b2); shared_ptr contactPhysics(new KnKsPhys()); //interaction->interactionPhysics = shared_ptr(new MomentPhys()); //const shared_ptr& contactPhysics = YADE_PTR_CAST(interaction->interactionPhysics); /* From interaction physics */ Real fa = sdec1->frictionAngle; Real fb = sdec2->frictionAngle; /* calculate stiffness */ Real Kn= Knormal; Real Ks= Kshear; /* Pass values calculated from above to CSPhys */ contactPhysics->viscousDamping = viscousDamping; contactPhysics->useOverlapVol = useOverlapVol; contactPhysics->kn = Kn; contactPhysics->ks = Ks; contactPhysics->kn_i = Kn; contactPhysics->ks_i = Ks; contactPhysics->u_peak = u_peak; contactPhysics->maxClosure = maxClosure; contactPhysics->cohesionBroken = cohesionBroken; contactPhysics->tensionBroken = tensionBroken; contactPhysics->unitWidth2D = unitWidth2D; contactPhysics->frictionAngle = std::min(fa,fb); if(!useFaceProperties) { contactPhysics->phi_r = std::min(fa,fb)/PI*180.0; contactPhysics->phi_b = contactPhysics->phi_r; } contactPhysics->tanFrictionAngle = std::tan(contactPhysics->frictionAngle); //contactPhysics->initialOrientation1 = Body::byId(interaction->getId1())->state->ori; //contactPhysics->initialOrientation2 = Body::byId(interaction->getId2())->state->ori; contactPhysics->prevNormal = scg->normal; //This is also done in the Contact Law. It is not redundant because this class is only called ONCE! contactPhysics->calJointLength = calJointLength; contactPhysics->twoDimension = twoDimension; contactPhysics->useFaceProperties = useFaceProperties; contactPhysics->brittleLength = brittleLength; interaction->phys = contactPhysics; } CREATE_LOGGER(KnKsPhys); /* KnKsPhys */ KnKsPhys::~KnKsPhys() {} #endif // YADE_POTENTIAL_PARTICLES trunk-2018.02b/pkg/dem/KnKsLaw.hpp000066400000000000000000000235421324306050200165450ustar00rootroot00000000000000#pragma once #ifdef YADE_POTENTIAL_PARTICLES #include #include #include #include #include #include #include class KnKsPhys: public FrictPhys { public: virtual ~KnKsPhys(); YADE_CLASS_BASE_DOC_ATTRS_CTOR(KnKsPhys,FrictPhys,"EXPERIMENTAL. IPhys originally for potential particles", ((vector,lambdaIPOPT,0.0,,"Lagrange multiplier for equality constraints")) ((vector,cstatCPLEX,,,"Lagrange multiplier for equality constraints")) ((vector,rstatCPLEX,,,"Lagrange multiplier for equality constraints")) ((Real,frictionAngle,0.0,,"fric angle")) ((Real,tanFrictionAngle,0.0,,"tangent of fric angle")) ((Vector3r,contactDetectionPt,Vector3r(0,0,0),,"contact detection result")) ((Real, viscousDamping, 0.8, ,"viscousDamping")) ((Real, unitWidth2D, 1.0, ,"viscousDamping")) ((Real, maxClosure, 0.0002, ,"vmi")) ((Real, u_peak, 0.05, ,"peak shear displacement")) ((Real, u_elastic, 0.0, ,"elastic shear displacement")) ((double, brittleLength, 5.0, ,"brittle rock")) ((double, kn_i, 5.0, ,"initial normal stiffness")) ((double, ks_i, 5.0, ,"initial shear stiffness")) ((Vector3r, normalViscous, Vector3r(0,0,0), ,"viscousDamping")) ((Vector3r, shearViscous, Vector3r(0,0,0), ,"viscousDamping")) ((double, hwater, 0.0, ,"height of pore water")) ((bool, rockJointContact, false, ,"brittle rock")) ((bool, intactRock, false, ,"brittle rock")) ((int, jointType, 0, ,"jointType")) ((Real,Kshear_area,0.0,,"kshear area")) ((Real,Knormal_area, 0.0,,"knormal area")) ((Vector3r, shearDir, Vector3r(0,0,0), ,"shear direction")) ((vector, shearForces, , ,"shear direction")) ((Vector3r, shear, Vector3r(0,0,0),, "shear displacement")) ((Vector3r, prevNormal, Vector3r(0.0,0.0,0.0),, "previous Normal")) ((Vector3r, normal, Vector3r(0.0,0.0,0.0),, " normalVector")) ((vector, pointsArea, ,, "intermediate contact points")) ((vector, pointsShear, ,, "points to calculate shear")) ((vector, areaShear, ,, "area to attribute shear")) ((vector, overlapDistances, ,, "area to attribute shear")) ((Real, finalSize, 0.0,, "finalgridsize")) ((int, finalGridNo, 0,, "final number of grids")) ((vector, dualityGap, , ,"duality gap for SOCP")) ((bool, warmstart, false, ,"warmstart for SOCP")) ((int, generation, 0,, "number of subdivisions")) ((int, triNoMain, 24,, "number of subdivisions")) ((int, triNoSub, 6,, "number of subdivisions")) ((Vector3r, initial1, Vector3r(0.0,0.0,0.0),, "midpoint")) ((Vector3r, ptOnP1, Vector3r(0.0,0.0,0.0),, "pt on particle")) ((Vector3r, ptOnP2, Vector3r(0.0,0.0,0.0),, " pt on particle 2")) ((vector, redundantA, , ,"activePlanes for interaction.id1")) ((vector, redundantB, , ,"activePlanes for interaction.id1")) ((vector, activePlanes1, , ,"activePlanes for interaction.id1")) ((vector, activePlanes2, , ,"activePlanes for interaction.id2")) ((vector, activeA1, , ,"activePlanes for interaction.id2")) ((vector, activeB1, , ,"activePlanes for interaction.id2")) ((vector, activeC1, , ,"activePlanes for interaction.id2")) ((vector, activeD1, , ,"activePlanes for interaction.id2")) ((vector, activeA2, , ,"activePlanes for interaction.id2")) ((vector, activeB2, , ,"activePlanes for interaction.id2")) ((vector, activeC2, , ,"activePlanes for interaction.id2")) ((vector, activeD2, , ,"activePlanes for interaction.id2")) ((int, noActive1, 0, ,"activePlanes for interaction.id1")) ((int, noActive2,0 , ,"activePlanes for interaction.id2")) ((int, smallerID,1 , ,"id of particle with smaller plane")) ((Real, cumulative_us, 0.0,, "cumulative translation")) ((Real, cumulativeRotation, 0.0,, "cumulative rotation")) ((Real, mobilizedShear, , ,"mobilizedShear")) ((Real, contactArea, 0.0, ,"contactArea")) ((Real, radCurvFace, , ,"face")) ((double, prevJointLength, 0.0, ,"previous joint length")) ((Real, radCurvCorner, , ,"corners")) ((Real, prevSigma,0.0 , ,"previous normal stress")) ((vector, prevSigmaList,0.0 , ,"previous normal stress")) ((bool, calJointLength, false,, "calculate joint length")) ((bool, useOverlapVol, false,, "calculate overlap volume")) ((bool, calContactArea, false,, "calculate contact area")) ((double, jointLength, 0.0,, "approximatedJointLength")) ((double, shearIncrementForCD, 0.0,, "toSeeWhether it is necessary to update contactArea")) ((Real, overlappingVol,0.0 , ,"overlapping vol")) ((Real, overlappingVolMulti,0.0 , ,"overlapping vol")) ((double, gap_normalized, 0.0,, "distance between particles normalized by particle size. Estimated using Taubin Distance ")) ((double, gap, 0.0,, "distance between particles normalized by particle size. Estimated using Taubin Distance ")) ((bool, findCurv, false,, "to get radius of curvature")) ((bool, useFaceProperties, false,,"boolean to get face properites")) ((Real, cohesion, 0.0, ,"cohesion")) ((Real, tension, 0.0, ,"tension")) ((bool, cohesionBroken, true, ,"cohesion already broken")) ((bool, tensionBroken, true, ,"tension already broken")) ((bool, twoDimension, false, ,"tension already broken")) ((Real, phi_b, 0.0, ,"basic friction angle (degrees)")) ((Real, phi_r, 0.0, ,"residual friction angle (degrees)")) ((Real, asperity, 3.0, ,"asperity height")) ((Real, JRC, 0.0, ,"Joint Roughness Coefficient")) ((Real, JRCmobilized, 0.0, ,"Joint Roughness Coefficient")) ((Real, JCS, 0.0, ,"Joint Roughness Coefficient")) ((Real, sigmaC,0.0 , ,"Joint Roughness Coefficient")) ((Real, u_dilate,0.0 , ,"dilation distance")) ((Real, dilation_angle,0.0 , ,"dilation distance")) /* pore water pressure */ ((Real, lambda0,0.0 , ,"initial pore water pressure to stress ratio")) ((Real, lambda_present,0.0 , ,"Voight&Faust (1992) Sitar et al. (2005)")) ((Real, u_cumulative, 0.0,, "cumulative translation")) ((Vector3r, prevShearDir, Vector3r(0.0,0.0,0.0),, "previous shear direction")) ((Vector3r, initialShearDir, Vector3r(0.0,0.0,0.0),, "previous shear direction")) ((double, delta_porePressure, 0.0,, "change in pore water pressure")) ((double, porePressure, 0.0,, "pore water pressure")) ((double, bandThickness, 0.1,, "clay layer thickness")) ((double, heatCapacities, 0.0,, "clay layer thickness")) ((double, effective_phi, 0.0,, "friction angle in clay after displacement")) ((double, prevOverlap, 0.0,, "friction angle in clay after displacement")) ((Real, h, 0.0,,"cd")), //((Real, cumulativeRotation, 0.0,, "cumulative rotation")) //((Quaternionr, initialOrientation1, Quaternionr(1.0,0.0,0.0,0.0),, "orientation1")) //((Quaternionr, initialOrientation2, Quaternionr(1.0,0.0,0.0,0.0),, "orientation2")), createIndex(); ); REGISTER_CLASS_INDEX(KnKsPhys,FrictPhys); DECLARE_LOGGER; }; REGISTER_SERIALIZABLE(KnKsPhys); class Ip2_FrictMat_FrictMat_KnKsPhys: public IPhysFunctor { public: virtual void go(const shared_ptr& pp1, const shared_ptr& pp2, const shared_ptr& interaction); YADE_CLASS_BASE_DOC_ATTRS(Ip2_FrictMat_FrictMat_KnKsPhys,IPhysFunctor,"EXPERIMENTAL. Ip2 functor for :yref:`KnKsPhys`", ((Real,Knormal,0.0,,"allows user to input values directly from python scripts")) ((Real,Kshear,0.0,,"allows user to input values directly from python scripts")) ((Real, unitWidth2D, 1.0, ,"viscousDamping")) ((double, brittleLength, 3.0, ,"brittle rock")) ((double, kn_i, 0.002, ,"brittle rock")) ((double, ks_i, 0.002, ,"brittle rock")) ((double, u_peak, -1.0, ,"brittle rock")) ((double, maxClosure, 0.002, ,"brittle rock")) ((Real, viscousDamping, 0.8, ,"viscousDamping")) ((Real, cohesion, 0.0, ,"viscousDamping")) ((Real, tension, 0.0, ,"viscousDamping")) ((bool, cohesionBroken, true, ,"cohesion")) ((bool, tensionBroken, true, ,"tension")) ((Real, phi_b, 0.0, ,"viscousDamping")) ((bool, useOverlapVol, false,, "calculate overlap volume")) ((bool, useFaceProperties, false,,"boolean to get face properites")) ((bool, calJointLength, false,, "calculate joint length")) ((bool, twoDimension, false, ,"tension already broken")) ); FUNCTOR2D(FrictMat,FrictMat); DECLARE_LOGGER; }; REGISTER_SERIALIZABLE(Ip2_FrictMat_FrictMat_KnKsPhys); class Law2_SCG_KnKsPhys_KnKsLaw: public LawFunctor { public: static Real Real0; //OpenMPAccumulator plasticDissipation; virtual bool go(shared_ptr& _geom, shared_ptr& _phys, Interaction* I); FUNCTOR2D(ScGeom,KnKsPhys); YADE_CLASS_BASE_DOC_ATTRS_CTOR_PY(Law2_SCG_KnKsPhys_KnKsLaw,LawFunctor,"Law for linear compression, without cohesion and Mohr-Coulomb plasticity surface.\n\n.. note::\n This law uses :yref:`ScGeom`; there is also functionally equivalent :yref:`Law2_Dem3DofGeom_FrictPhys_Basic`, which uses :yref:`Dem3DofGeom` (sphere-box interactions are not implemented for the latest).", ((bool,neverErase,false,,"Keep interactions even if particles go away from each other (only in case another constitutive law is in the scene, e.g. :yref:`Law2_ScGeom_CapillaryPhys_Capillarity`)")) ((bool,preventGranularRatcheting,false,,"bool to avoid granular ratcheting")) ((bool,traceEnergy,false,,"Define the total energy dissipated in plastic slips at all contacts.")) ((bool,Talesnick,false,,"Define the total energy dissipated in plastic slips at all contacts.")) ((double,waterLevel,0.0,,"Define the total energy dissipated in plastic slips at all contacts.")) ((bool, allowBreakage, false, ,"cohesion = 0, once broken")) ((double,initialOverlapDistance,0.0,,"initial overlap distance")) ,, ); DECLARE_LOGGER; }; REGISTER_SERIALIZABLE(Law2_SCG_KnKsPhys_KnKsLaw); #endif // YADE_POTENTIAL_PARTICLES trunk-2018.02b/pkg/dem/KnKsPBLaw.cpp000077500000000000000000000672771324306050200170020ustar00rootroot00000000000000#ifdef YADE_POTENTIAL_BLOCKS #include"KnKsPBLaw.hpp" #include #include #include #include YADE_PLUGIN((Law2_SCG_KnKsPBPhys_KnKsPBLaw)(Ip2_FrictMat_FrictMat_KnKsPBPhys)(KnKsPBPhys) ); /********************** Law2_Dem3DofGeom_RockPMPhys_Rpm ****************************/ CREATE_LOGGER(Law2_SCG_KnKsPBPhys_KnKsPBLaw); bool Law2_SCG_KnKsPBPhys_KnKsPBLaw::go(shared_ptr& ig /* contact geometry */, shared_ptr& ip /* contact physics */, Interaction* contact){ const Real& dt = scene->dt; /* size of time step */ int id1 = contact->getId1(); /* id of Body1 */ int id2 = contact->getId2(); /* id of Body2 */ ScGeom* geom= static_cast(ig.get()); /* contact geometry */ KnKsPBPhys* phys = static_cast(ip.get()); /* contact physics */ State* de1 = Body::byId(id1,scene)->state.get(); /* pointer to Body1 */ State* de2 = Body::byId(id2,scene)->state.get(); /* pointer to Body2 */ Shape* shape1 = Body::byId(id1,scene)->shape.get(); /* pointer to Shape1 */ Shape* shape2 = Body::byId(id2,scene)->shape.get(); /* pointer to Shape2 */ PotentialBlock *s1=static_cast(shape1); PotentialBlock *s2=static_cast(shape2); Vector3r& shearForce = phys->shearForce; /* shear force at previous timestep */ Real un=geom->penetrationDepth; /* overlap distance */ TRVAR3(geom->penetrationDepth,de1->se3.position,de2->se3.position); /* ERASE CONTACT OR RESET PARAMETERS IF NO OVERLAP */ if(geom->penetrationDepth <0.0 ){ if (neverErase) { phys->shearForce = Vector3r::Zero(); phys->normalForce = Vector3r::Zero(); phys->normalViscous = Vector3r::Zero(); geom->normal = Vector3r::Zero(); phys->tensionBroken = true; }else{ scene->interactions->requestErase(id1,id2); return false; } return true; } Vector3r shiftVel = Vector3r(0,0,0); //scene->isPeriodic ? (Vector3r)((scene->cell->velGrad*scene->cell->Hsize)*Vector3r((Real) contact->cellDist[0],(Real) contact->cellDist[1],(Real) contact->cellDist[2])) : Vector3r::Zero(); geom->rotateNonSpherical(shearForce); /*rotate shear force according to new contact plane (normal) */ Vector3r oriShear = shearForce; Vector3r oriNormalF = phys->normalForce; /* CALCULATE SHEAR INCREMENT */ Vector3r shift2(0,0,0); Vector3r incidentV = geom->getIncidentVel(de1, de2, scene->dt, shift2, shiftVel, /*preventGranularRatcheting*/false ); /* get relative velocity */ Vector3r incidentVn = geom->normal.dot(incidentV)*geom->normal; /* get normal relative velocity */ Vector3r incidentVs = incidentV-incidentVn; /* get shear relative velocity */ Vector3r shearIncrement=incidentVs*scene->dt; /* calculate shear increment from shear velocity */ double du = shearIncrement.norm(); /* magnitude of shear increment */ phys->shearDir = shearIncrement; /* get shear direction */ if(phys->shearDir.norm() > pow(10,-15)){ phys->shearDir.normalize(); } if(phys->twoDimension) { phys->contactArea = phys->unitWidth2D*phys->jointLength;} /* contact area in 2D */ if(s1->isBoundary == true || s2->isBoundary==true){phys->tensionBroken = true; phys->cohesionBroken = true;} /* no cohesion and tension if the contact is a boundary*/ /* NORMAL CONTACT FORCE */ double normalStiffness = phys->kn; /* use default stiffness values */ if(Talesnick){ //phys->prevSigma = phys->kn*std::max(un,(Real) 0.0); //phys->normalForce=phys->prevSigma*phys->normal*std::max(pow(10,-15),phys->contactArea); //#if 0 //#if 0 /* linear */ //un = un - 8.0*pow(10.0,-5); double A = 0.5*4.0*pow(10.0,9);//*pow(10.0,9); double B = 0.5*7.4*pow(10.0,4); double expTerm = B*(std::max(un,0.0)) + std::log(A) ; phys->prevSigma = std::max(( (std::exp(expTerm)-A)) / B, 0.0); double Fn = phys->prevSigma*std::max(pow(10,-15),phys->contactArea); phys->normalForce = Fn*geom->normal; phys->kn = (A+ B*phys->prevSigma); //#endif #if 0 /* power */phys->h double Fn = pow(525000*std::max(un,(Real) 0),1.0/0.25)*std::max(pow(10,-11),phys->contactArea); phys->kn = 2.1*pow(10.0,6)*pow(phys->prevSigma,0.75); #endif #if 0 double A = 7.0*pow(10.0,13);//*pow(10.0,9); double B = 1.0*pow(10.0,6); phys->prevSigma = (A*un)*un - B*un; double Fn = phys->prevSigma*std::max(pow(10,-15),phys->contactArea); phys->normalForce = Fn*geom->normal; phys->kn = A*2.0*un - pow(10,6); #endif #if 0 //power double A = 2.1*pow(10.0,6); //4.2*pow(10.0,5); double B = 0.75; //0.88; phys->prevSigma = pow( (1.0-B)*A*std::max(un, 0.0),1.0/(1.0-B) ); double Fn = phys->prevSigma*std::max(pow(10,-14),phys->contactArea); phys->kn = A*pow(phys->prevSigma,B); //1.0/(1.0-B)*pow( (1.0-B)*A,1.0/(1.0-B) ) * pow(std::max(un,0.0), B/(1.0-B)); //A*pow(phys->prevSigma,B); // phys->normalForce = Fn*geom->normal; #endif if (phys->prevSigma > pow(10.0,15) /* || Fn < 0.0 */){ std::cout<<"prevSigma: "<prevSigma/* <<", Fn: "<Knormal_area = phys->kn*std::max(pow(10,-15),phys->contactArea); }else{ if(s1->isBoundary == true || s2->isBoundary==true|| s1->isEastBoundary == true || s2->isEastBoundary==true ){ normalStiffness = phys->kn_i; /* use special stiffness for boundaries */ phys->prevSigma = normalStiffness*un; phys->normalForce = phys->prevSigma*std::max(pow(10,-15),phys->contactArea)*geom->normal; //std::max(pow(10,-15),phys->contactArea)* phys->Knormal_area = normalStiffness*std::max(pow(10,-15),phys->contactArea); }else{ if(s1->isLining==true){normalStiffness=s1->liningStiffness; un = un-s1->liningTensionGap; phys->prevSigma = normalStiffness*un;} else if(s2->isLining==true){normalStiffness=s2->liningStiffness; un = un-s2->liningTensionGap; phys->prevSigma = normalStiffness*un;} else{ un = un-initialOverlapDistance; if(phys->tensionBroken == true){ //if(allowBreakage == false && un > 0.0){phys->tensionBroken = false;} phys->prevSigma = normalStiffness*std::max(un,0.0); }else{ phys->prevSigma = normalStiffness*un; } } /* GENERAL CASE - initialOverlapDistance is the offset distance for tension overlap, i.e. negative overlap */ phys->normalForce = phys->prevSigma*std::max(pow(10,-15),phys->contactArea)*geom->normal; phys->Knormal_area = normalStiffness*std::max(pow(10,-15),phys->contactArea); } } /* ERASE CONTACT IF TENSION IS BROKEN */ if((un <0.0 && fabs(phys->prevSigma)>phys->tension && phys->tensionBroken == false /* first time tension is broken */) || (un<0.0 && phys->tensionBroken==true)){ if (neverErase) { phys->shearForce = Vector3r::Zero(); phys->normalForce = Vector3r::Zero(); phys->normalViscous = Vector3r::Zero(); //geom->normal = Vector3r::Zero(); phys->tensionBroken = true; }else { return false; } return true; } /* SHEAR CONTACT FORCE */ double Ks = 0.0; if(Talesnick){ //shearForce -= phys->ks*shearIncrement*std::max(pow(10,-15),phys->contactArea); #if 0 /* TALESNICK */ /* linear law */ double shearStiffness = 1.0*pow(10.0,8) + 9.7*pow(10.0,4)*phys->prevSigma; /* current sigmaN from above */ /* power law */ //double shearStiffness = 0.95*pow(10.0,6)*pow(phys->prevSigma,0.7); /* current sigmaN from above */ //double shearStiffness = 3.3*pow(10.0,5)*pow(phys->prevSigma,0.88); /* current sigmaN from above */ phys->ks = shearStiffness; Ks = shearStiffness; shearForce -= shearStiffness*shearIncrement*std::max(pow(10,-15),phys->contactArea); #endif //#if 0 phys->ks = 1.9*pow(10.0,6)*pow(phys->prevSigma,0.7); shearForce -= phys->ks*shearIncrement*std::max(pow(10,-14),phys->contactArea); //#endif phys->Kshear_area = phys->ks*std::max(pow(10,-15),phys->contactArea); }else{ Ks= phys->ks; /* use default values */ if(s1->isBoundary == true || s2->isBoundary==true || s1->isEastBoundary == true || s2->isEastBoundary==true){ Ks = phys->ks_i; shearForce -= Ks*shearIncrement*std::max(pow(10,-15),phys->contactArea); phys->Kshear_area = Ks*std::max(pow(10,-15),phys->contactArea); }else{ if(s1->isLining==true){Ks=s1->liningStiffness;} if(s2->isLining==true){Ks=s2->liningStiffness;} shearForce -= Ks*shearIncrement*std::max(pow(10,-15),phys->contactArea); /* GENERAL CASE */ phys->Kshear_area = Ks*std::max(pow(10,-15),phys->contactArea); } } /* CONTACT DAMPING */ Real mass1 = 0.0; Real mass2 = 0.0; const shared_ptr& b1=Body::byId(id1,scene); const shared_ptr& b2=Body::byId(id2,scene); if (b1->isClumpMember() == true){ State* stateClump = Body::byId(b1->clumpId,scene)->state.get(); mass1 = stateClump->mass; } if (b2->isClumpMember() == true){ State* stateClump = Body::byId(b2->clumpId,scene)->state.get(); mass2 = stateClump->mass; } if (b1->isClumpMember() == false && b2->isClumpMember() == false){ mass1 = de1->mass; mass2 = de2->mass; } Real mbar = (!b1->isDynamic() && b2->isDynamic()) ? mass2 : ((!b2->isDynamic() && b1->isDynamic()) ? mass1 : (mass1*mass2 / (mass1 + mass2))); // get equivalent mass Real Cn_crit = 2.*sqrt(mbar*phys->Knormal_area); // Knormal_area Critical damping coefficient (normal direction) 2.*sqrt(mbar*std::min(phys->Knormal_area,phys->Kshear_area)) Real Cs_crit = Cn_crit ; //2.*sqrt(mbar*phys->Kshear_area); // Kshear_area Critical damping coefficient (shear direction) // Note: to compare with the analytical solution you provide cn and cs directly (since here we used a different method to define c_crit) double cn = Cn_crit*phys->viscousDamping; // Damping normal coefficient double cs = Cs_crit*phys->viscousDamping; // Damping tangential coefficient /* Add normal viscous component if damping is included */ phys->normalViscous = cn*incidentVn; phys->normalForce -= phys->normalViscous; /* FRICTION LIMIT */ const double PI = std::atan(1.0)*4; double tan_effective_phi = 0.0; bool useIterativeMethod = false; if(Talesnick){ phys->cumulative_us = phys->cumulative_us + fabs(du); phys->effective_phi = phys->phi_b; tan_effective_phi = tan(phys->effective_phi/180.0*PI); #if 0 double upeak = 2.0*pow(10.0,-6)*pow(phys->prevSigma,0.213); double delta_miu = phys->ks/phys->prevSigma*(1.0 - std::min(fabs(phys->cumulative_us)/upeak, 1.0) ); if (isnan(delta_miu)){delta_miu = phys->ks/phys->prevSigma;} if (shearForce.norm() > phys->normalForce.norm()*phys->effective_phi){ phys->effective_phi = phys->effective_phi + delta_miu*fabs(du); } tan_effective_phi = phys->effective_phi; #endif }else{ if(s1->isBoundary==true || s2->isBoundary == true || phys->jointType==2 ){ // clay layer at boundary; useIterativeMethod=false; //fixme //if (allowBreakage == true) {useIterativeMethod = true;}else{ // useIterativeMethod = false; // phys->effective_phi = phys->phi_b; // - 3.25*(1.0-exp(-fabs(phys->cumulative_us)/0.4)); // tan_effective_phi = tan(phys->effective_phi/180.0*PI); //} }else if(s1->isLining==true){ phys->effective_phi = s1->liningFriction; tan_effective_phi = tan(phys->effective_phi/180.0*PI); }else if(s2->isLining==true){ phys->effective_phi = s2->liningFriction; tan_effective_phi = tan(phys->effective_phi/180.0*PI); }else{ phys->effective_phi = phys->phi_b; //if(s1->isEastBoundary==true || s2->isEastBoundary==true){phys->effective_phi = 0.0;} tan_effective_phi = tan(phys->effective_phi/180.0*PI); } } /* SHEAR CORRECTION */ Vector3r dampedShearForce = shearForce; double cohesiveForce = phys->cohesion*std::max(pow(10,-15),phys->contactArea); Real maxFs = 0.0; double maxShear = 0.0; if (useIterativeMethod == false){ double fN = phys->normalForce.dot(geom->normal); if(std::isnan(fN)){fN=0.0;} if(phys->intactRock == true){ if(allowBreakage == false || phys->cohesionBroken == false){ maxFs = cohesiveForce+ fN*tan_effective_phi; }else{ maxFs = std::max( fN,0.0)*tan_effective_phi; } }else{ maxFs = std::max( fN,0.0)*tan_effective_phi; } if( shearForce.norm() > maxFs ){ Real ratio = maxFs / shearForce.norm(); shearForce *= ratio; shearForce = shearForce; if(allowBreakage == true){phys->cohesionBroken = true;} dampedShearForce = shearForce; phys->shearViscous = Vector3r(0,0,0); }else{ /* no damping when it slides */ phys->shearViscous = Vector3r(0,0,0); //cs*incidentVs; // dampedShearForce = shearForce - phys->shearViscous; } }else{ Vector3r Fs_prev = oriShear; /* shear force before stress update */ Vector3r delta_us = -shearIncrement; /* increment of shear displacement */ double beta = 0.0; /* rate of plastic multiplier */ double beta_prev = phys->cumulative_us; /* accumulated plastic mutliplier before stress update */ double fN = phys->normalForce.norm(); //std::max(pow(10,-15),phys->contactArea); if(std::isnan(fN)){fN=0.0;} double phi =phys->phi_b; Vector3r newFs (0,0,0); double plasticDisp = 0.0; if(!Talesnick){ plasticDisp = stressUpdateVec(ip /*contact physics */, Fs_prev, delta_us, beta_prev, phys->Kshear_area /*shear stiffness */,fN, phi, newFs); }else{ double upeak = 2.0*pow(10.0,-6)*pow(phys->prevSigma,0.213); plasticDisp = stressUpdateVecTalesnick(ip /*contact physics */, Fs_prev, delta_us, beta_prev, phys->Kshear_area /*shear stiffness */,fN, phi, newFs, upeak); } shearForce = newFs; dampedShearForce = newFs; phys->cumulative_us = phys->cumulative_us + plasticDisp; //beta*shearIncrement; /* add plastic displacements */ double miu_peak = tan(phys->phi_b/180.0*PI); double delta_miu = 0.059266; tan_effective_phi = miu_peak - delta_miu*(1.0-exp(-phys->cumulative_us/0.35)); phys->effective_phi = atan(tan_effective_phi)/PI*180.0; maxFs = fN*tan_effective_phi; //if(shearForce.norm()/maxFs > 1.02) {std::cout<<"shearForce.norm()/maxFs: "<shearViscous = Vector3r(0,0,0); //cs*incidentVs; // dampedShearForce = shearForce - phys->shearViscous; } } /* APPLY FORCES */ Vector3r c1x = geom->contactPoint - de1->pos; Vector3r c2x = geom->contactPoint - de2->pos; Vector3r force = -phys->normalForce-dampedShearForce; scene->forces.addForce(id1,force); scene->forces.addForce(id2,-force); Vector3r normal = geom->normal; scene->forces.addTorque(id1,c1x.cross(force)); scene->forces.addTorque(id2,-(c2x).cross(force)); phys->prevNormal = geom->normal; /* RECORDING VALUES AND DEBUGGING */ if(shearForce.norm() < pow(10,-11) ){phys->mobilizedShear = 1.0;}else{phys->mobilizedShear = shearForce.norm()/maxFs;} if(s1->isLining==true){s1->liningTotalPressure = 1.0/s1->liningLength*force; s1->liningNormalPressure=-1.0/s1->liningLength*phys->normalForce; } else if(s2->isLining==true){s2->liningTotalPressure = -1.0/s2->liningLength*force; s2->liningNormalPressure=1.0/s2->liningLength*phys->normalForce; } if(std::isnan(force.norm())){ std::cout<<"shearForce: "<normalForce<<", viscousNormal: "<normalViscous<<", viscousShear: "<shearViscous<<", normal: "<normal<<", geom normal: "<normal<<", effective_phi: "<effective_phi<<", shearIncrement: "<mobilizedShear: "<mobilizedShear<& ip, const Vector3r Fs_prev /*prev shear force*/ , const Vector3r du /*shear displacement increment*/, const double beta_prev /* prev plastic displacements*/, const double Ks /*shear stiffness */,const double fN /*normal force*/, const double phi_b /*peak friction angle*/, Vector3r & newFs /*new shear force*/){ // FUNCTION RETURNS PLASTIC MULTIPLIER RATE (beta) AND CURRENT SHEAR FORCE newFs = Vector3r(0,0,0); double maxFs = 0.0; const double PI = std::atan(1.0)*4; // Define beta_prev as the cumulated plastic multiplier // Define beta as the rate of plastic multiplier at the current time step // Fs_new = Fs_prev + dF // dF = Ks*du_elastic = Ks*(du - du_plastic) = Ks*(du - beta*du_p) double beta = 0.0; //beta is the plastic multiplier double effective_phi = phi_b; double tan_effective_phi = tan(effective_phi/180.0*PI); double miu_peak = tan_effective_phi ; double delta_miu = 0.059266; double function = 0.0; double lambda = 0.0; newFs = Fs_prev + Ks*du; maxFs = fN*(miu_peak-delta_miu*(1.0-exp(-1.0*beta_prev/0.35))); //If new stress after elastic update is outside the previous yield surface if (newFs.norm() - maxFs >pow(10,-11) && fN>pow(10,-11) && (Ks*du).norm()>pow(10,-11) ){ // Fs_new = Fs_prev + dF // dF = Ks*du_elastic = Ks*(du - du_plastic) = Ks*(du - beta*du_p) // where du_p is a unit vector whose sign is Sign((Fs_prev + Ks*du).dot(du))*Sign(du); /* EQUATION TO SOLVE */ /* Solve for beta */ // Fs + Ks*(du - beta*du_p) = N*( miu_peak-delta_miu*(1-exp(beta_prev + beta) ) ) beta = 0.0; //////* ESTABLISH LOWER AND UPPER BOUNDS (positive and negative) FOR BRACKETING beta (plastic multiplier) *////// /* Establish lower bound for lambda*/ /* Lower bound = 0.0, i.e. fully elastic */ /* f_lower_bound <0.0, because it is outside the yield surface */ double lowerBound = 0.0; Vector3r termA = (Fs_prev + Ks*du)/(Ks*lowerBound + 1.0); double beta = beta_prev + lowerBound*termA.norm(); double f_lower_bound = termA.norm() - fN*(miu_peak - delta_miu*(1.0 - exp(-1.0*beta/0.35))); /* Establish upper bound for lambda*/ double upperBound = du.norm()/Fs_prev.norm(); if(std::isnan(upperBound)==true){upperBound=1.0;} if(std::isinf(upperBound)==true || upperBound>pow(10.0,12) ){upperBound=pow(10.0,12);} termA = (Fs_prev + Ks*du)/(Ks*upperBound + 1.0); beta = beta_prev + upperBound*termA.norm(); double f_upper_bound= termA.norm() - fN*(miu_peak - delta_miu*(1.0 - exp(-1.0*beta/0.35))); int iterUpper = 0; while(Mathr::Sign(f_upper_bound)*Mathr::Sign(f_lower_bound)>0.0){ upperBound=5.0*upperBound; termA = (Fs_prev + Ks*du)/(Ks*upperBound + 1.0); beta = beta_prev + upperBound*termA.norm(); f_upper_bound= termA.norm() - fN*(miu_peak - delta_miu*(1.0 - exp(-1.0*beta/0.35))); iterUpper++; if(iterUpper>1000){ std::cout<<"iterUpper: "< pow(10,-14) && fabs(lowerBound-upperBound)> pow(10,-14) ) { midTrial = 0.5*(lowerBound+upperBound); lambda = midTrial; termA = (Fs_prev + Ks*du)/(Ks*lambda + 1.0); beta = beta_prev + lambda*termA.norm(); function = termA.norm() - fN*(miu_peak - delta_miu*(1.0 - exp(-1.0*beta/0.35))); double Fmid = function; if (Mathr::Sign(Fmid) == Mathr::Sign(f_lower_bound)){ lowerBound = midTrial; f_lower_bound=function;}else{upperBound = midTrial;f_upper_bound=function;} iter++; if(iter > 98){ if ( fabs(function) > pow(10,-6) && fabs(lowerBound-upperBound)> pow(10,-6) ){ std::cout<<"iter: "< 1.05){ std::cout<<"newFs.norm()/maxFs: "<& ip, const Vector3r Fs_prev /*prev shear force*/ , const Vector3r du /*shear displacement increment*/, const double beta_prev /* prev plastic displacements*/, const double Ks /*shear stiffness */,const double fN /*normal force*/, const double phi_b /*peak friction angle*/, Vector3r & newFs /*new shear force*/, const double upeak){ // FUNCTION RETURNS PLASTIC MULTIPLIER RATE (beta) AND CURRENT SHEAR FORCE newFs = Vector3r(0,0,0); double maxFs = 0.0; const double PI = std::atan(1.0)*4; // Define beta_prev as the cumulated plastic multiplier // Define beta as the rate of plastic multiplier at the current time step // Fs_new = Fs_prev + dF // dF = Ks*du_elastic = Ks*(du - du_plastic) = Ks*(du - beta*du_p) double beta = 0.0; //beta is the plastic multiplier double effective_phi = phi_b; double tan_effective_phi = tan(effective_phi/180.0*PI); double miu_peak = tan_effective_phi ; double function = 0.0; double lambda = 0.0; newFs = Fs_prev + Ks*du; maxFs = fN*(miu_peak*(1.0-exp(-1.0*beta_prev/upeak))); //If new stress after elastic update is outside the previous yield surface if (newFs.norm() - maxFs >pow(10,-11) && fN>pow(10,-11) && (Ks*du).norm()>pow(10,-11) ){ // Fs_new = Fs_prev + dF // dF = Ks*du_elastic = Ks*(du - du_plastic) = Ks*(du - beta*du_p) // where du_p is a unit vector whose sign is Sign((Fs_prev + Ks*du).dot(du))*Sign(du); /* EQUATION TO SOLVE */ /* Solve for beta */ // Fs + Ks*(du - beta*du_p) = N*( miu_peak-delta_miu*(1-exp(beta_prev + beta) ) ) beta = 0.0; //////* ESTABLISH LOWER AND UPPER BOUNDS (positive and negative) FOR BRACKETING beta (plastic multiplier) *////// /* Establish lower bound for lambda*/ /* Lower bound = 0.0, i.e. fully elastic */ /* f_lower_bound <0.0, because it is outside the yield surface */ double lowerBound = 0.0; Vector3r termA = (Fs_prev + Ks*du)/(Ks*lowerBound + 1.0); double beta = beta_prev + lowerBound*termA.norm(); double f_lower_bound = termA.norm() - fN*(miu_peak*(1.0-exp(-1.0*beta_prev/upeak))); /* Establish upper bound for lambda*/ double upperBound = du.norm()/Fs_prev.norm(); if(std::isnan(upperBound)==true){upperBound=1.0;} if(std::isinf(upperBound)==true || upperBound>pow(10.0,12) ){upperBound=pow(10.0,12);} termA = (Fs_prev + Ks*du)/(Ks*upperBound + 1.0); beta = beta_prev + upperBound*termA.norm(); double f_upper_bound= termA.norm() - fN*(miu_peak*(1.0-exp(-1.0*beta_prev/upeak))); int iterUpper = 0; while(Mathr::Sign(f_upper_bound)*Mathr::Sign(f_lower_bound)>0.0){ upperBound=5.0*upperBound; termA = (Fs_prev + Ks*du)/(Ks*upperBound + 1.0); beta = beta_prev + upperBound*termA.norm(); f_upper_bound= termA.norm() - fN*(miu_peak*(1.0-exp(-1.0*beta_prev/upeak))); iterUpper++; if(iterUpper>1000){ std::cout<<"iterUpper: "< pow(10,-14) && fabs(lowerBound-upperBound)> pow(10,-14) ) { midTrial = 0.5*(lowerBound+upperBound); lambda = midTrial; termA = (Fs_prev + Ks*du)/(Ks*lambda + 1.0); beta = beta_prev + lambda*termA.norm(); function = termA.norm() - fN*(miu_peak*(1.0-exp(-1.0*beta_prev/upeak))); double Fmid = function; if (Mathr::Sign(Fmid) == Mathr::Sign(f_lower_bound)){ lowerBound = midTrial; f_lower_bound=function;}else{upperBound = midTrial;f_upper_bound=function;} iter++; if(iter > 98){ if ( fabs(function) > pow(10,-6) && fabs(lowerBound-upperBound)> pow(10,-6) ){ std::cout<<"iter: "< 1.05){ std::cout<<"newFs.norm()/maxFs: "<& b1, const shared_ptr& b2, const shared_ptr& interaction){ const double PI = 3.14159265358979323846; if(interaction->phys) return; ScGeom* scg=YADE_CAST(interaction->geom.get()); assert(scg); const shared_ptr& sdec1 = YADE_PTR_CAST(b1); const shared_ptr& sdec2 = YADE_PTR_CAST(b2); shared_ptr contactPhysics(new KnKsPBPhys()); //interaction->interactionPhysics = shared_ptr(new MomentPhys()); //const shared_ptr& contactPhysics = YADE_PTR_CAST(interaction->interactionPhysics); /* From interaction physics */ Real fa = sdec1->frictionAngle; Real fb = sdec2->frictionAngle; /* calculate stiffness */ Real Kn= Knormal; Real Ks= Kshear; /* Pass values calculated from above to CSPhys */ contactPhysics->viscousDamping = viscousDamping; contactPhysics->useOverlapVol = useOverlapVol; contactPhysics->kn = Kn; contactPhysics->ks = Ks; contactPhysics->kn_i = kn_i; contactPhysics->ks_i = ks_i; contactPhysics->u_peak = u_peak; contactPhysics->maxClosure = maxClosure; contactPhysics->cohesionBroken = cohesionBroken; contactPhysics->tensionBroken = tensionBroken; contactPhysics->unitWidth2D = unitWidth2D; contactPhysics->frictionAngle = std::min(fa,fb); if(!useFaceProperties){ contactPhysics->phi_r = std::min(fa,fb)/PI*180.0; contactPhysics->phi_b = contactPhysics->phi_r; } contactPhysics->tanFrictionAngle = std::tan(contactPhysics->frictionAngle); //contactPhysics->initialOrientation1 = Body::byId(interaction->getId1())->state->ori; //contactPhysics->initialOrientation2 = Body::byId(interaction->getId2())->state->ori; contactPhysics->prevNormal = scg->normal; //This is also done in the Contact Law. It is not redundant because this class is only called ONCE! contactPhysics->calJointLength = calJointLength; contactPhysics->twoDimension = twoDimension; contactPhysics->useFaceProperties = useFaceProperties; contactPhysics->brittleLength = brittleLength; interaction->phys = contactPhysics; } CREATE_LOGGER(KnKsPBPhys); /* KnKsPBPhys */ KnKsPBPhys::~KnKsPBPhys(){} #endif // YADE_POTENTIAL_BLOCKS trunk-2018.02b/pkg/dem/KnKsPBLaw.hpp000077500000000000000000000247211324306050200167720ustar00rootroot00000000000000#ifdef YADE_POTENTIAL_BLOCKS #pragma once #include #include #include #include #include #include #include class KnKsPBPhys: public FrictPhys { public: virtual ~KnKsPBPhys(); YADE_CLASS_BASE_DOC_ATTRS_CTOR(KnKsPBPhys,FrictPhys,"Simple phys", ((vector,lambdaIPOPT,0.0,,"lagrane multiplier for equality constraints")) ((vector,cstatCPLEX,,,"lagrane multiplier for equality constraints")) ((vector,rstatCPLEX,,,"lagrane multiplier for equality constraints")) ((Real,frictionAngle,0.0,,"fric angle")) ((Real,tanFrictionAngle,0.0,,"tangent of fric angle")) ((Vector3r,contactDetectionPt,Vector3r(0,0,0),,"contact detection result")) ((Real, viscousDamping, 0.8, ,"viscousDamping")) ((Real, unitWidth2D, 1.0, ,"viscousDamping")) ((Real, maxClosure, 0.0002, ,"vmi")) ((Real, u_peak, 0.05, ,"peak shear displacement")) ((Real, u_elastic, 0.0, ,"elastic shear displacement")) ((double, brittleLength, 5.0, ,"brittle rock")) ((double, kn_i, 5.0, ,"initial normal stiffness")) ((double, ks_i, 5.0, ,"initial shear stiffness")) ((Vector3r, normalViscous, Vector3r(0,0,0), ,"viscousDamping")) ((Vector3r, shearViscous, Vector3r(0,0,0), ,"viscousDamping")) ((double, hwater, 0.0, ,"height of pore water")) ((bool, rockJointContact, false, ,"brittle rock")) ((bool, intactRock, false, ,"brittle rock")) ((int, jointType, 0, ,"jointType")) ((Real,Kshear_area,0.0,,"kshear area")) ((Real,Knormal_area, 0.0,,"knormal area")) ((Vector3r, shearDir, Vector3r(0,0,0), ,"shear direction")) ((vector, shearForces, , ,"shear direction")) ((Vector3r, shear, Vector3r(0,0,0),, "shear displacement")) ((Vector3r, prevNormal, Vector3r(0.0,0.0,0.0),, "previous Normal")) ((Vector3r, normal, Vector3r(0.0,0.0,0.0),, " normalVector")) ((vector, pointsArea, ,, "intermediate contact points")) ((vector, pointsShear, ,, "points to calculate shear")) ((vector, areaShear, ,, "area to attribute shear")) ((vector, overlapDistances, ,, "area to attribute shear")) ((Real, finalSize, 0.0,, "finalgridsize")) ((int, finalGridNo, 0,, "final number of grids")) ((vector, dualityGap, , ,"duality gap for SOCP")) ((bool, warmstart, false, ,"warmstart for SOCP")) ((int, generation, 0,, "number of subdivisions")) ((int, triNoMain, 24,, "number of subdivisions")) ((int, triNoSub, 6,, "number of subdivisions")) ((Vector3r, initial1, Vector3r(0.0,0.0,0.0),, "midpoint")) ((Vector3r, ptOnP1, Vector3r(0.0,0.0,0.0),, "pt on particle")) ((Vector3r, ptOnP2, Vector3r(0.0,0.0,0.0),, " pt on particle 2")) ((vector, redundantA, , ,"activePlanes for interaction.id1")) ((vector, redundantB, , ,"activePlanes for interaction.id1")) ((vector, activePlanes1, , ,"activePlanes for interaction.id1")) ((vector, activePlanes2, , ,"activePlanes for interaction.id2")) ((vector, activeA1, , ,"activePlanes for interaction.id2")) ((vector, activeB1, , ,"activePlanes for interaction.id2")) ((vector, activeC1, , ,"activePlanes for interaction.id2")) ((vector, activeD1, , ,"activePlanes for interaction.id2")) ((vector, activeA2, , ,"activePlanes for interaction.id2")) ((vector, activeB2, , ,"activePlanes for interaction.id2")) ((vector, activeC2, , ,"activePlanes for interaction.id2")) ((vector, activeD2, , ,"activePlanes for interaction.id2")) ((int, noActive1, 0, ,"activePlanes for interaction.id1")) ((int, noActive2,0 , ,"activePlanes for interaction.id2")) ((int, smallerID,1 , ,"id of particle with smaller plane")) ((Real, cumulative_us, 0.0,, "cumulative translation")) ((Real, cumulativeRotation, 0.0,, "cumulative rotation")) ((Real, mobilizedShear, , ,"mobilizedShear")) ((Real, contactArea, 0.0, ,"contactArea")) ((Real, radCurvFace, , ,"face")) ((double, prevJointLength, 0.0, ,"previous joint length")) ((Real, radCurvCorner, , ,"corners")) ((Real, prevSigma,0.0 , ,"previous normal stress")) ((vector, prevSigmaList,0.0 , ,"previous normal stress")) ((bool, calJointLength, false,, "calculate joint length")) ((bool, useOverlapVol, false,, "calculate overlap volume")) ((bool, calContactArea, false,, "calculate contact area")) ((double, jointLength, 0.0,, "approximatedJointLength")) ((double, shearIncrementForCD, 0.0,, "toSeeWhether it is necessary to update contactArea")) ((Real, overlappingVol,0.0 , ,"overlapping vol")) ((Real, overlappingVolMulti,0.0 , ,"overlapping vol")) ((double, gap_normalized, 0.0,, "distance between particles normalized by particle size. Estimated using Taubin Distance ")) ((double, gap, 0.0,, "distance between particles normalized by particle size. Estimated using Taubin Distance ")) ((bool, findCurv, false,, "to get radius of curvature")) ((bool, useFaceProperties, false,,"boolean to get face properites")) ((Real, cohesion, 0.0, ,"cohesion")) ((Real, tension, 0.0, ,"tension")) ((bool, cohesionBroken, true, ,"cohesion already broken")) ((bool, tensionBroken, true, ,"tension already broken")) ((bool, twoDimension, false, ,"tension already broken")) ((Real, phi_b, 0.0, ,"basic friction angle (degrees)")) ((Real, phi_r, 0.0, ,"residual friction angle (degrees)")) ((Real, asperity, 3.0, ,"asperity height")) ((Real, JRC, 0.0, ,"Joint Roughness Coefficient")) ((Real, JRCmobilized, 0.0, ,"Joint Roughness Coefficient")) ((Real, JCS, 0.0, ,"Joint Roughness Coefficient")) ((Real, sigmaC,0.0 , ,"Joint Roughness Coefficient")) ((Real, u_dilate,0.0 , ,"dilation distance")) ((Real, dilation_angle,0.0 , ,"dilation distance")) /* pore water pressure */ ((Real, lambda0,0.0 , ,"initial pore water pressure to stress ratio")) ((Real, lambda_present,0.0 , ,"Voight&Faust (1992) Sitar et al. (2005)")) ((Real, u_cumulative, 0.0,, "cumulative translation")) ((Vector3r, prevShearDir, Vector3r(0.0,0.0,0.0),, "previous shear direction")) ((Vector3r, initialShearDir, Vector3r(0.0,0.0,0.0),, "previous shear direction")) ((double, delta_porePressure, 0.0,, "change in pore water pressure")) ((double, porePressure, 0.0,, "pore water pressure")) ((double, bandThickness, 0.1,, "clay layer thickness")) ((double, heatCapacities, 0.0,, "clay layer thickness")) ((double, effective_phi, 0.0,, "friction angle in clay after displacement")) ((double, prevOverlap, 0.0,, "friction angle in clay after displacement")) ((Real, h, 0.0,,"cd")), //((Real, cumulativeRotation, 0.0,, "cumulative rotation")) //((Quaternionr, initialOrientation1, Quaternionr(1.0,0.0,0.0,0.0),, "orientation1")) //((Quaternionr, initialOrientation2, Quaternionr(1.0,0.0,0.0,0.0),, "orientation2")), createIndex(); ); REGISTER_CLASS_INDEX(KnKsPBPhys,FrictPhys); DECLARE_LOGGER; }; REGISTER_SERIALIZABLE(KnKsPBPhys); class Ip2_FrictMat_FrictMat_KnKsPBPhys: public IPhysFunctor{ public: virtual void go(const shared_ptr& pp1, const shared_ptr& pp2, const shared_ptr& interaction); YADE_CLASS_BASE_DOC_ATTRS(Ip2_FrictMat_FrictMat_KnKsPBPhys,IPhysFunctor,"Calculation", ((Real,Knormal,0.0,,"allows user to input values directly from python scripts")) ((Real,Kshear,0.0,,"allows user to input values directly from python scripts")) ((Real, unitWidth2D, 1.0, ,"viscousDamping")) ((double, brittleLength, 3.0, ,"brittle rock")) ((double, kn_i, 0.002, ,"brittle rock")) ((double, ks_i, 0.002, ,"brittle rock")) ((double, u_peak, -1.0, ,"brittle rock")) ((double, maxClosure, 0.002, ,"brittle rock")) ((Real, viscousDamping, 0.8, ,"viscousDamping")) ((Real, cohesion, 0.0, ,"viscousDamping")) ((Real, tension, 0.0, ,"viscousDamping")) ((bool, cohesionBroken, true, ,"cohesion")) ((bool, tensionBroken, true, ,"tension")) ((Real, phi_b, 0.0, ,"viscousDamping")) ((bool, useOverlapVol, false,, "calculate overlap volume")) ((bool, useFaceProperties, false,,"boolean to get face properites")) ((bool, calJointLength, false,, "calculate joint length")) ((bool, twoDimension, false, ,"tension already broken")) ); FUNCTOR2D(FrictMat,FrictMat); DECLARE_LOGGER; }; REGISTER_SERIALIZABLE(Ip2_FrictMat_FrictMat_KnKsPBPhys); class Law2_SCG_KnKsPBPhys_KnKsPBLaw: public LawFunctor{ public: double stressUpdate(shared_ptr& ip, const Vector3r Fs_prev, const Vector3r du, const Vector3r prev_us, const double Kshear_area /*shear stiffness */,const double fN, const double dFn, const double phi_b, Vector3r & newFs); double stressUpdateVec(shared_ptr& ip, const Vector3r Fs_prev, const Vector3r du, const double prev_us, const double Kshear_area /*shear stiffness */,const double fN, const double phi_b, Vector3r & newFs); double stressUpdateVecTalesnick(shared_ptr& ip, const Vector3r Fs_prev, const Vector3r du, const double prev_us, const double Kshear_area /*shear stiffness */,const double fN, const double phi_b, Vector3r & newFs, const double upeak); static Real Real0; //OpenMPAccumulator plasticDissipation; virtual bool go(shared_ptr& _geom, shared_ptr& _phys, Interaction* I); FUNCTOR2D(ScGeom,KnKsPBPhys); YADE_CLASS_BASE_DOC_ATTRS_CTOR_PY(Law2_SCG_KnKsPBPhys_KnKsPBLaw,LawFunctor,"Law for linear compression, without cohesion and Mohr-Coulomb plasticity surface.\n\n.. note::\n This law uses :yref:`ScGeom`; there is also functionally equivalent :yref:`Law2_Dem3DofGeom_FrictPhys_Basic`, which uses :yref:`Dem3DofGeom` (sphere-box interactions are not implemented for the latest).", ((bool,neverErase,false,,"Keep interactions even if particles go away from each other (only in case another constitutive law is in the scene, e.g. :yref:`Law2_ScGeom_CapillaryPhys_Capillarity`)")) ((bool,preventGranularRatcheting,false,,"bool to avoid granular ratcheting")) ((bool,traceEnergy,false,,"Define the total energy dissipated in plastic slips at all contacts.")) ((bool,Talesnick,false,,"Define the total energy dissipated in plastic slips at all contacts.")) ((double,waterLevel,0.0,,"Define the total energy dissipated in plastic slips at all contacts.")) ((bool, allowBreakage, false, ,"cohesion = 0, once broken")) ((double,initialOverlapDistance,0.0,,"initial overlap distance")) ,, ); DECLARE_LOGGER; }; REGISTER_SERIALIZABLE(Law2_SCG_KnKsPBPhys_KnKsPBLaw); #endif // YADE_POTENTIAL_BLOCKS trunk-2018.02b/pkg/dem/L3Geom.cpp000066400000000000000000000415021324306050200163100ustar00rootroot00000000000000 #include #include #include #include #ifdef YADE_OPENGL #include #include #include #endif YADE_PLUGIN((L3Geom)(L6Geom)(Ig2_Sphere_Sphere_L3Geom)(Ig2_Wall_Sphere_L3Geom)(Ig2_Facet_Sphere_L3Geom)(Ig2_Sphere_Sphere_L6Geom)(Law2_L3Geom_FrictPhys_ElPerfPl)(Law2_L6Geom_FrictPhys_Linear) #ifdef YADE_OPENGL (Gl1_L3Geom)(Gl1_L6Geom) #endif ); L3Geom::~L3Geom(){} void L3Geom::applyLocalForceTorque(const Vector3r& localF, const Vector3r& localT, const Interaction* I, Scene* scene, NormShearPhys* nsp) const { Vector2r foo; // avoid undefined ~Vector2r with clang? Vector3r globF=trsf.transpose()*localF; // trsf is orthonormal, therefore inverse==transpose Vector3r x1c(normal*(refR1+.5*u[0])), x2c(-normal*(refR2+.5*u[0])); if(nsp){ nsp->normalForce=normal*globF.dot(normal); nsp->shearForce=globF-nsp->normalForce; } Vector3r globT=Vector3r::Zero(); if(localT!=Vector3r::Zero()){ globT=trsf.transpose()*localT; } // apply force and torque scene->forces.addForce(I->getId1(), globF); scene->forces.addTorque(I->getId1(),x1c.cross( globF)+globT); scene->forces.addForce(I->getId2(),-globF); scene->forces.addTorque(I->getId2(),x2c.cross(-globF)-globT); } void L3Geom::applyLocalForce(const Vector3r& localF, const Interaction* I, Scene* scene, NormShearPhys* nsp) const { applyLocalForceTorque(localF,Vector3r::Zero(),I,scene,nsp); } L6Geom::~L6Geom(){} bool Ig2_Sphere_Sphere_L3Geom::go(const shared_ptr& s1, const shared_ptr& s2, const State& state1, const State& state2, const Vector3r& shift2, const bool& force, const shared_ptr& I){ return genericGo(/*is6Dof*/false,s1,s2,state1,state2,shift2,force,I); }; bool Ig2_Sphere_Sphere_L6Geom::go(const shared_ptr& s1, const shared_ptr& s2, const State& state1, const State& state2, const Vector3r& shift2, const bool& force, const shared_ptr& I){ return genericGo(/*is6Dof*/true,s1,s2,state1,state2,shift2,force,I); }; CREATE_LOGGER(Ig2_Sphere_Sphere_L3Geom); bool Ig2_Sphere_Sphere_L3Geom::genericGo(bool is6Dof, const shared_ptr& s1, const shared_ptr& s2, const State& state1, const State& state2, const Vector3r& shift2, const bool& force, const shared_ptr& I){ // temporary hack only, to not have elastic potential energy in rigid packings with overlapping spheres //if(state1.blockedDOFs==State::DOF_ALL && state2.blockedDOFs==State::DOF_ALL) return false; const Real& r1=s1->cast().radius; const Real& r2=s2->cast().radius; Vector3r relPos=state2.pos+shift2-state1.pos; Real unDistSq=relPos.squaredNorm()-pow(std::abs(distFactor)*(r1+r2),2); if (unDistSq>0 && !I->isReal() && !force) return false; // contact exists, go ahead Real dist=relPos.norm(); Real uN=dist-(r1+r2); Vector3r normal=relPos/dist; Vector3r contPt=state1.pos+(r1+0.5*uN)*normal; handleSpheresLikeContact(I,state1,state2,shift2,is6Dof,normal,contPt,uN,r1,r2); return true; }; /* Generic function to compute L3Geom (with colinear points), used for {sphere,facet,wall}+sphere contacts now */ void Ig2_Sphere_Sphere_L3Geom::handleSpheresLikeContact(const shared_ptr& I, const State& state1, const State& state2, const Vector3r& shift2, bool is6Dof, const Vector3r& normal, const Vector3r& contPt, Real uN, Real r1, Real r2){ // create geometry if(!I->geom){ if(is6Dof) I->geom=shared_ptr(new L6Geom); else I->geom=shared_ptr(new L3Geom); L3Geom& g(I->geom->cast()); g.contactPoint=contPt; g.refR1=r1; g.refR2=r2; g.normal=normal; // g.trsf.setFromTwoVectors(Vector3r::UnitX(),g.normal); // quaternion just from the X-axis; does not seem to work for some reason?! const Vector3r& locX(g.normal); // initial local y-axis orientation, in the xz or xy plane, depending on which component is larger to avoid singularities Vector3r locY=normal.cross(std::abs(normal[1])isPeriodic?scene->cell->intrShiftVel(I->cellDist):Vector3r::Zero())<isPeriodic) relShearVel+=scene->cell->intrShiftVel(I->cellDist); relShearVel-=avgNormal.dot(relShearVel)*avgNormal; Vector3r relShearDu=relShearVel*scene->dt; /* Update of quantities in global coords consists in adding 3 increments we have computed; in global coords (a is any vector) 1. +relShearVel*scene->dt; // mutual motion of the contact 2. -a.cross(normRotVec); // rigid rotation perpendicular to the normal 3. -a.cross(normTwistVec); // rigid rotation parallel to the normal */ // compute current transformation, by updating previous axes // the X axis can be prescribed directly (copy of normal) // the mutual motion on the contact does not change transformation const Matrix3r prevTrsf(g.trsf); // could be reference perhaps, but we need it to compute midTrsf (if applicable) Matrix3r currTrsf; currTrsf.row(0)=currNormal; for(int i=1; i<3; i++){ currTrsf.row(i)=prevTrsf.row(i)-prevTrsf.row(i).cross(normRotVec)-prevTrsf.row(i).cross(normTwistVec); } if((scene->iter % trsfRenorm)==0 && trsfRenorm>0){ currTrsf.row(0).normalize(); currTrsf.row(1)-=currTrsf.row(0)*currTrsf.row(1).dot(currTrsf.row(0)); // take away y projected on x, to stabilize numerically currTrsf.row(1).normalize(); currTrsf.row(2)=currTrsf.row(0).cross(currTrsf.row(1)); currTrsf.row(2).normalize(); #ifdef YADE_DEBUG if(std::abs(currTrsf.determinant()-1)>.05){ LOG_ERROR("##"<getId1()<<"+"<getId2()<<", |trsf|="<geom->cast().phi+=midTrsf*(scene->dt*(state2.angVel-state1.angVel)); } }; bool Ig2_Wall_Sphere_L3Geom::go(const shared_ptr& s1, const shared_ptr& s2, const State& state1, const State& state2, const Vector3r& shift2, const bool& force, const shared_ptr& I){ if(scene->isPeriodic) throw std::logic_error("Ig2_Wall_Sphere_L3Geom does not handle periodic boundary conditions."); const Real& radius=s2->cast().radius; const int& ax(s1->cast().axis); const int& sense(s1->cast().sense); Real dist=state2.pos[ax]+shift2[ax]-state1.pos[ax]; // signed "distance" between centers if(!I->isReal() && std::abs(dist)>radius && !force) { return false; }// wall and sphere too far from each other // contact point is sphere center projected onto the wall Vector3r contPt=state2.pos+shift2; contPt[ax]=state1.pos[ax]; Vector3r normal=Vector3r::Zero(); // wall interacting from both sides: normal depends on sphere's position assert(sense==-1 || sense==0 || sense==1); if(sense==0) normal[ax]=dist>0?1.:-1.; else normal[ax]=(sense==1?1.:-1); Real uN=normal[ax]*dist-radius; // takes in account sense, radius and distance // check that the normal did not change orientation (would be abrup here) if(I->geom && I->geom->cast().normal!=normal){ ostringstream oss; oss<<"Ig2_Wall_Sphere_L3Geom: normal changed from ("<geom->cast().normal<<" to "<getId1()<<"+"<getId2()<<" (with Wall.sense=0, a particle might cross the Wall plane, if Δt is too high)"; throw std::logic_error(oss.str().c_str()); } handleSpheresLikeContact(I,state1,state2,shift2,/*is6Dof*/false,normal,contPt,uN,/*r1*/0,/*r2*/radius); return true; }; bool Ig2_Facet_Sphere_L3Geom::go(const shared_ptr& s1, const shared_ptr& s2, const State& state1, const State& state2, const Vector3r& shift2, const bool& force, const shared_ptr& I){ const Facet& facet(s1->cast()); Real radius=s2->cast().radius; // begin facet-local coordinates Vector3r cogLine=state1.ori.conjugate()*(state2.pos+shift2-state1.pos); // connect centers of gravity Vector3r normal=facet.normal; // trial contact normal Real planeDist=normal.dot(cogLine); if(std::abs(planeDist)>radius && !I->isReal() && !force) return false; // sphere too far if(planeDist<0){normal*=-1; planeDist*=-1; } Vector3r planarPt=cogLine-planeDist*normal; // project sphere center to the facet plane Vector3r contactPt; // facet's point closes to the sphere Real normDotPt[3]; // edge outer normals dot products for(int i=0; i<3; i++) normDotPt[i]=facet.ne[i].dot(planarPt-facet.vertices[i]); short w=(normDotPt[0]>0?1:0)+(normDotPt[1]>0?2:0)+(normDotPt[2]>0?4:0); // bitmask whether the closest point is outside (1,2,4 for respective edges) switch(w){ case 0: contactPt=planarPt; break; // ---: inside triangle case 1: contactPt=getClosestSegmentPt(planarPt,facet.vertices[0],facet.vertices[1]); break; // +-- (n1) case 2: contactPt=getClosestSegmentPt(planarPt,facet.vertices[1],facet.vertices[2]); break; // -+- (n2) case 4: contactPt=getClosestSegmentPt(planarPt,facet.vertices[2],facet.vertices[0]); break; // --+ (n3) case 3: contactPt=facet.vertices[1]; break; // ++- (v1) case 5: contactPt=facet.vertices[0]; break; // +-+ (v0) case 6: contactPt=facet.vertices[2]; break; // -++ (v2) case 7: throw logic_error("Ig2_Facet_Sphere_L3Geom: Impossible sphere-facet intersection (all points are outside the edges). (please report bug)"); // +++ (impossible) default: throw logic_error("Ig2_Facet_Sphere_L3Geom: Nonsense intersection value. (please report bug)"); } normal=cogLine-contactPt; // normal is now the contact normal, still in local coords if(!I->isReal() && normal.squaredNorm()>radius*radius && !force) { return false; } // fast test before sqrt Real dist=normal.norm(); normal/=dist; // normal is unit vector now // end facet-local coordinates normal=state1.ori*normal; // normal is in global coords now handleSpheresLikeContact(I,state1,state2,shift2,/*is6Dof*/false,normal,/*contact pt*/state2.pos+shift2-normal*dist,dist-radius,0,radius); return true; } bool Law2_L3Geom_FrictPhys_ElPerfPl::go(shared_ptr& ig, shared_ptr& ip, Interaction* I){ L3Geom* geom=static_cast(ig.get()); FrictPhys* phys=static_cast(ip.get()); // compute force Vector3r& localF(geom->F); localF=geom->relU().cwiseProduct(Vector3r(phys->kn,phys->ks,phys->ks)); // break if necessary if(localF[0]>0 && !noBreak) return false; if(!noSlip){ // plastic slip, if necessary; non-zero elastic limit only for compression Real maxFs=-min((Real)0.,localF[0]*phys->tangensOfFrictionAngle); Eigen::Map Fs(&localF[1]); //cerr<<"u="<relU()<<", maxFs="<trackEnergy) { scene->energy->add(0.5*(pow(geom->relU()[0],2)*phys->kn+(pow(geom->relU()[1],2)+pow(geom->relU()[2],2))*phys->ks),"elastPotential",elastPotentialIx,/*reset at every timestep*/true); } // apply force: this converts the force to global space, updates NormShearPhys::{normal,shear}Force, applies to particles geom->applyLocalForce(localF,I,scene,phys); return true; } bool Law2_L6Geom_FrictPhys_Linear::go(shared_ptr& ig, shared_ptr& ip, Interaction* I){ L6Geom& geom=ig->cast(); FrictPhys& phys=ip->cast(); // simple linear relationships Vector3r localF=geom.relU().cwiseProduct(Vector3r(phys.kn,phys.ks,phys.ks)); Vector3r localT=charLen*(geom.relPhi().cwiseProduct(Vector3r(phys.kn,phys.ks,phys.ks))); geom.applyLocalForceTorque(localF,localT,I,scene,static_cast(ip.get())); return true; } #ifdef YADE_OPENGL bool Gl1_L3Geom::axesLabels; Real Gl1_L3Geom::axesWd; Real Gl1_L3Geom::axesScale; Real Gl1_L3Geom::uPhiWd; Real Gl1_L3Geom::uScale; Real Gl1_L6Geom::phiScale; void Gl1_L3Geom::go(const shared_ptr& ig, const shared_ptr&, const shared_ptr&, const shared_ptr&, bool){ draw(ig); } void Gl1_L6Geom::go(const shared_ptr& ig, const shared_ptr&, const shared_ptr&, const shared_ptr&, bool){ draw(ig,true,phiScale); } void Gl1_L3Geom::draw(const shared_ptr& ig, bool isL6Geom, const Real& phiScale){ const L3Geom& g(ig->cast()); glTranslatev(g.contactPoint); glMultMatrix(Eigen::Transform(g.trsf.transpose()).data()); Real rMin=g.refR1<=0?g.refR2:(g.refR2<=0?g.refR1:min(g.refR1,g.refR2)); if(axesWd>0){ glLineWidth(axesWd); for(int i=0; i<3; i++){ Vector3r pt=Vector3r::Zero(); pt[i]=.5*rMin*axesScale; Vector3r color=.3*Vector3r::Ones(); color[i]=1; GLUtils::GLDrawLine(Vector3r::Zero(),pt,color); if(axesLabels) GLUtils::GLDrawText(string(i==0?"x":(i==1?"y":"z")),pt,color); } } if(uPhiWd>0){ glLineWidth(uPhiWd); if(uScale!=0) GLUtils::GLDrawLine(Vector3r::Zero(),uScale*g.relU(),Vector3r(0,1,.5)); if(isL6Geom && phiScale>0) GLUtils::GLDrawLine(Vector3r::Zero(),ig->cast().relPhi()/Mathr::PI*rMin*phiScale,Vector3r(.8,0,1)); } glLineWidth(1.); }; #endif trunk-2018.02b/pkg/dem/L3Geom.hpp000066400000000000000000000305361324306050200163220ustar00rootroot00000000000000 #pragma once #include #include #include #include #include #include #include #include #include #include #ifdef YADE_OPENGL #include #endif /* L3Geom Ig2 functor cooperation: 1. functors define genericGo function, which take ``bool is6Dof`` as the first arg; they are called from functors returning both L3Geom and L6Geom (this only applies to sphere+sphere now, since Ig2_{Facet,Wall}_Sphere_L6Geom has not been written yet for lack of interest, though would be trivial) 2. genericGo function computes several parameter specific to shape types; then it calls (if L3GEOM_SPHERESLIKE is defined) handleSpheresLikeContact which contains the common code, given all data necessary. Note that: (a) although L3GEOM_SPHERESLIKE is enabled by default, its performance impact has not been measured yet (the compiler should be smart enough, since it is just factoring out common code). (b) L3Geom only contains contPt and normal, which supposes (in L3Geom::applyLocalForce) that particles' centroids and the contact point are colinear; while this is true for spheres and mostly OK for facets&walls (since they are non-dynamic), it might be adjusted in the future -- L3Geom_Something deriving from L3Geom will be created, and exact branch vectors contained in it will be gotten via virtual method from L3Geom::applyLocalForce. This would be controlled via some approxMask (in the Law2 functor perhaps) so that it is only optional (c) Ig2_Facet_Sphere_L3Geom is only enabled with L3GEOM_SPHERESLIKE */ #define L3GEOM_SPHERESLIKE struct L3Geom: public GenericSpheresContact{ const Real& uN; const Vector2r uT; virtual ~L3Geom(); // utility function // TODO: currently supposes body's centroids are conencted with distance*normal // that will not be true for sphere+facet and others, watch out! // the force is oriented as applied to particle #1 void applyLocalForce(const Vector3r& f, const Interaction* I, Scene* scene, NormShearPhys* nsp=NULL) const; void applyLocalForceTorque(const Vector3r& f, const Vector3r& t, const Interaction* I, Scene* scene, NormShearPhys* nsp=NULL) const; Vector3r relU() const{ return u-u0; } YADE_CLASS_BASE_DOC_ATTRS_INIT_CTOR_PY(L3Geom,GenericSpheresContact,"Geometry of contact given in local coordinates with 3 degress of freedom: normal and two in shear plane. [experimental]", ((Vector3r,u,Vector3r::Zero(),,"Displacement components, in local coordinates. |yupdate|")) ((Vector3r,u0,Vector3r::Zero(),,"Zero displacement value; u0 should be always subtracted from the *geometrical* displacement *u* computed by appropriate :yref:`IGeomFunctor`, resulting in *u*. This value can be changed for instance\n\n#. by :yref:`IGeomFunctor`, e.g. to take in account large shear displacement value unrepresentable by underlying geomeric algorithm based on quaternions)\n#. by :yref:`LawFunctor`, to account for normal equilibrium position different from zero geometric overlap (set once, just after the interaction is created)\n#. by :yref:`LawFunctor` to account for plastic slip.\n\n.. note:: Never set an absolute value of *u0*, only increment, since both :yref:`IGeomFunctor` and :yref:`LawFunctor` use it. If you need to keep track of plastic deformation, store it in :yref:`IPhys` isntead (this might be changed: have *u0* for :yref:`LawFunctor` exclusively, and a separate value stored (when that is needed) inside classes deriving from :yref:`L3Geom`.")) /* Is it better to store trsf as Matrix3 or Quaternion? * Quaternions are much easier to re-normalize, which we should do to avoid numerical drift. * Multiplication of vector with quaternion is internally done by converting to matrix first, anyway * We need to extract local axes, and that is easier to be done from Matrix3r (columns) */ ((Matrix3r,trsf,Matrix3r::Identity(),,"Transformation (rotation) from global to local coordinates. (the translation part is in :yref:`GenericSpheresContact.contactPoint`)")) ((Vector3r,F,Vector3r::Zero(),,"Applied force in local coordinates [debugging only, will be removed]")) , /*init*/ ((uN,u[0])) ((uT,Vector2r::Map(&u[1]))) , /*ctor*/ createIndex(); , /*py*/ ); REGISTER_CLASS_INDEX(L3Geom,GenericSpheresContact); }; REGISTER_SERIALIZABLE(L3Geom); struct L6Geom: public L3Geom{ virtual ~L6Geom(); Vector3r relPhi() const{ return phi-phi0; } YADE_CLASS_BASE_DOC_ATTRS_CTOR(L6Geom,L3Geom,"Geometric of contact in local coordinates with 6 degrees of freedom. [experimental]", ((Vector3r,phi,Vector3r::Zero(),,"Rotation components, in local coordinates. |yupdate|")) ((Vector3r,phi0,Vector3r::Zero(),,"Zero rotation, should be always subtracted from *phi* to get the value. See :yref:`L3Geom.u0`.")) , /* ctor */ createIndex(); ); REGISTER_CLASS_INDEX(L6Geom,L3Geom); }; REGISTER_SERIALIZABLE(L6Geom); #ifdef YADE_OPENGL struct Gl1_L3Geom: public GlIGeomFunctor{ RENDERS(L3Geom); void go(const shared_ptr&, const shared_ptr&, const shared_ptr&, const shared_ptr&, bool); void draw(const shared_ptr&, bool isL6Geom=false, const Real& phiScale=0); YADE_CLASS_BASE_DOC_STATICATTRS(Gl1_L3Geom,GlIGeomFunctor,"Render :yref:`L3Geom` geometry.", ((bool,axesLabels,false,,"Whether to display labels for local axes (x,y,z)")) ((Real,axesScale,1.,,"Scale local axes, their reference length being half of the minimum radius.")) ((Real,axesWd,1.,,"Width of axes lines, in pixels; not drawn if non-positive")) ((Real,uPhiWd,2.,,"Width of lines for drawing displacements (and rotations for :yref:`L6Geom`); not drawn if non-positive.")) ((Real,uScale,1.,,"Scale local displacements (:yref:`u` - :yref:`u0`); 1 means the true scale, 0 disables drawing local displacements; negative values are permissible.")) ); }; REGISTER_SERIALIZABLE(Gl1_L3Geom); struct Gl1_L6Geom: public Gl1_L3Geom{ RENDERS(L6Geom); void go(const shared_ptr&, const shared_ptr&, const shared_ptr&, const shared_ptr&, bool); YADE_CLASS_BASE_DOC_STATICATTRS(Gl1_L6Geom,Gl1_L3Geom,"Render :yref:`L6Geom` geometry.", ((Real,phiScale,1.,,"Scale local rotations (:yref:`phi` - :yref:`phi0`). The default scale is to draw $\\pi$ rotation with length equal to minimum radius.")) ); }; REGISTER_SERIALIZABLE(Gl1_L6Geom); #endif struct Ig2_Sphere_Sphere_L3Geom: public IGeomFunctor{ virtual bool go(const shared_ptr& s1, const shared_ptr& s2, const State& state1, const State& state2, const Vector3r& shift2, const bool& force, const shared_ptr& I); virtual bool genericGo(bool is6Dof, const shared_ptr& s1, const shared_ptr& s2, const State& state1, const State& state2, const Vector3r& shift2, const bool& force, const shared_ptr& I); // common code for {sphere,facet,wall}+sphere contacts // facet&wall will get separated if L3Geom subclass with exact branch vector is created void handleSpheresLikeContact(const shared_ptr& I, const State& state1, const State& state2, const Vector3r& shift2, bool is6Dof, const Vector3r& normal, const Vector3r& contPt, Real uN, Real r1, Real r2); enum { APPROX_NO_MID_TRSF=1, APPROX_NO_MID_NORMAL=2, APPROX_NO_RENORM_MID_NORMAL=4 }; YADE_CLASS_BASE_DOC_ATTRS(Ig2_Sphere_Sphere_L3Geom,IGeomFunctor,"Incrementally compute :yref:`L3Geom` for contact of 2 spheres. Detailed documentation in py/\\_extraDocs.py", ((bool,noRatch,true,,"See :yref:`Ig2_Sphere_Sphere_ScGeom.avoidGranularRatcheting`.")) ((Real,distFactor,1,,"Create interaction if spheres are not futher than *distFactor* \\*(r1+r2). If negative, zero normal deformation will be set to be the initial value (otherwise, the geometrical distance is the \'\'zero'' one).")) ((int,trsfRenorm,100,,"How often to renormalize :yref:`trsf`; if non-positive, never renormalized (simulation might be unstable)")) ((int,approxMask,0,,"Selectively enable geometrical approximations (bitmask); add the values for approximations to be enabled.\n\n" "== ===============================================================\n" "1 use previous transformation to transform velocities (which are known at mid-steps), instead of mid-step transformation computed as quaternion slerp at t=0.5.\n" "2 do not take average (mid-step) normal when computing relative shear displacement, use previous value instead\n" "4 do not re-normalize average (mid-step) normal, if used.…\n" "== ===============================================================\n\n" "By default, the mask is zero, wherefore none of these approximations is used.\n" )) ); FUNCTOR2D(Sphere,Sphere); DEFINE_FUNCTOR_ORDER_2D(Sphere,Sphere); DECLARE_LOGGER; }; REGISTER_SERIALIZABLE(Ig2_Sphere_Sphere_L3Geom); struct Ig2_Wall_Sphere_L3Geom: public Ig2_Sphere_Sphere_L3Geom{ virtual bool go(const shared_ptr& s1, const shared_ptr& s2, const State& state1, const State& state2, const Vector3r& shift2, const bool& force, const shared_ptr& I); //virtual bool genericGo(bool is6Dof, const shared_ptr& s1, const shared_ptr& s2, const State& state1, const State& state2, const Vector3r& shift2, const bool& force, const shared_ptr& I); YADE_CLASS_BASE_DOC(Ig2_Wall_Sphere_L3Geom,Ig2_Sphere_Sphere_L3Geom,"Incrementally compute :yref:`L3Geom` for contact between :yref:`Wall` and :yref:`Sphere`. Uses attributes of :yref:`Ig2_Sphere_Sphere_L3Geom`."); FUNCTOR2D(Wall,Sphere); DEFINE_FUNCTOR_ORDER_2D(Wall,Sphere); DECLARE_LOGGER; }; REGISTER_SERIALIZABLE(Ig2_Wall_Sphere_L3Geom); #ifdef L3GEOM_SPHERESLIKE struct Ig2_Facet_Sphere_L3Geom: public Ig2_Sphere_Sphere_L3Geom{ // get point on segment A..B closest to P; algo: http://local.wasp.uwa.edu.au/~pbourke/geometry/pointline/ static Vector3r getClosestSegmentPt(const Vector3r& P, const Vector3r& A, const Vector3r& B){ Vector3r BA=B-A; Real u=(P.dot(BA)-A.dot(BA))/(BA.squaredNorm()); return A+min((Real)1.,max((Real)0.,u))*BA; } virtual bool go(const shared_ptr& s1, const shared_ptr& s2, const State& state1, const State& state2, const Vector3r& shift2, const bool& force, const shared_ptr& I); YADE_CLASS_BASE_DOC(Ig2_Facet_Sphere_L3Geom,Ig2_Sphere_Sphere_L3Geom,"Incrementally compute :yref:`L3Geom` for contact between :yref:`Facet` and :yref:`Sphere`. Uses attributes of :yref:`Ig2_Sphere_Sphere_L3Geom`."); FUNCTOR2D(Facet,Sphere); DEFINE_FUNCTOR_ORDER_2D(Facet,Sphere); DECLARE_LOGGER; }; REGISTER_SERIALIZABLE(Ig2_Facet_Sphere_L3Geom); #endif struct Ig2_Sphere_Sphere_L6Geom: public Ig2_Sphere_Sphere_L3Geom{ virtual bool go(const shared_ptr& s1, const shared_ptr& s2, const State& state1, const State& state2, const Vector3r& shift2, const bool& force, const shared_ptr& I); YADE_CLASS_BASE_DOC(Ig2_Sphere_Sphere_L6Geom,Ig2_Sphere_Sphere_L3Geom,"Incrementally compute :yref:`L6Geom` for contact of 2 spheres."); FUNCTOR2D(Sphere,Sphere); DEFINE_FUNCTOR_ORDER_2D(Sphere,Sphere); }; REGISTER_SERIALIZABLE(Ig2_Sphere_Sphere_L6Geom); struct Law2_L3Geom_FrictPhys_ElPerfPl: public LawFunctor{ virtual bool go(shared_ptr&, shared_ptr&, Interaction*); FUNCTOR2D(L3Geom,FrictPhys); YADE_CLASS_BASE_DOC_ATTRS(Law2_L3Geom_FrictPhys_ElPerfPl,LawFunctor,"Basic law for testing :yref:`L3Geom`; it bears no cohesion (unless *noBreak* is ``True``), and plastic slip obeys the Mohr-Coulomb criterion (unless *noSlip* is ``True``).", ((bool,noBreak,false,,"Do not break contacts when particles separate.")) ((bool,noSlip,false,,"No plastic slipping.")) ((int,plastDissipIx,-1,(Attr::noSave|Attr::hidden),"Index of plastically dissipated energy")) ((int,elastPotentialIx,-1,(Attr::hidden|Attr::noSave),"Index for elastic potential energy (with O.trackEnergy)")) ); }; REGISTER_SERIALIZABLE(Law2_L3Geom_FrictPhys_ElPerfPl); struct Law2_L6Geom_FrictPhys_Linear: public Law2_L3Geom_FrictPhys_ElPerfPl{ virtual bool go(shared_ptr&, shared_ptr&, Interaction*); FUNCTOR2D(L6Geom,FrictPhys); YADE_CLASS_BASE_DOC_ATTRS(Law2_L6Geom_FrictPhys_Linear,Law2_L3Geom_FrictPhys_ElPerfPl,"Basic law for testing :yref:`L6Geom` -- linear in both normal and shear sense, without slip or breakage.", ((Real,charLen,1,,"Characteristic length with the meaning of the stiffness ratios bending/shear and torsion/normal.")) ); }; REGISTER_SERIALIZABLE(Law2_L6Geom_FrictPhys_Linear); trunk-2018.02b/pkg/dem/Law2_ScGeom_CapillaryPhys_Capillarity.cpp000066400000000000000000000642631324306050200244760ustar00rootroot00000000000000/************************************************************************* * Copyright (C) 2006 by luc Scholtes * * luc.scholtes@hmg.inpg.fr * * * * This program is free software; it is licensed under the terms of the * * GNU General Public License v2 or later. See file LICENSE for details. * *************************************************************************/ #include "Law2_ScGeom_CapillaryPhys_Capillarity.hpp" #include #include #include #include #include #include #include #include YADE_PLUGIN((Law2_ScGeom_CapillaryPhys_Capillarity)); void Law2_ScGeom_CapillaryPhys_Capillarity::postLoad(Law2_ScGeom_CapillaryPhys_Capillarity&){ capillary = shared_ptr(new capillarylaw); capillary->fill(("M(r=1)"+suffCapFiles).c_str()); capillary->fill(("M(r=1.1)"+suffCapFiles).c_str()); capillary->fill(("M(r=1.25)"+suffCapFiles).c_str()); capillary->fill(("M(r=1.5)"+suffCapFiles).c_str()); capillary->fill(("M(r=1.75)"+suffCapFiles).c_str()); capillary->fill(("M(r=2)"+suffCapFiles).c_str()); capillary->fill(("M(r=3)"+suffCapFiles).c_str()); capillary->fill(("M(r=4)"+suffCapFiles).c_str()); capillary->fill(("M(r=5)"+suffCapFiles).c_str()); capillary->fill(("M(r=10)"+suffCapFiles).c_str()); } MeniscusParameters::MeniscusParameters() { V = 0; F = 0; delta1 = 0; delta2 = 0; nn11 = 0; nn33 = 0; } MeniscusParameters::MeniscusParameters(const MeniscusParameters &source) { V = source.V; F = source.F; delta1 = source.delta1; delta2 = source.delta2; nn11 = source.nn11; nn33 = source.nn33; } MeniscusParameters::~MeniscusParameters() {} void Law2_ScGeom_CapillaryPhys_Capillarity::action() { if (!scene) cerr << "scene not defined!"; if (!capillary) postLoad(*this);//when the script does not define arguments, postLoad is never called shared_ptr& bodies = scene->bodies; int sphereIndex = Sphere::getClassIndexStatic(); //check for contact model once (assuming that contact model does not change) if (!hertzInitialized){//NOTE: We are assuming that only one type is used in one simulation here FOREACH(const shared_ptr& I, *scene->interactions){ if (I->isReal()) { if (CapillaryPhys::getClassIndexStatic()==I->phys->getClassIndex()) hertzOn=false; else if (MindlinCapillaryPhys::getClassIndexStatic()==I->phys->getClassIndex()) hertzOn=true; else LOG_ERROR("The capillary law is not implemented for interactions using "<phys->getClassName()); bodiesMenisciiList.initialized = false;//must be re-initialized after creation of first real contact in the model hertzInitialized = true; break; } } } if (fusionDetection && !bodiesMenisciiList.initialized) bodiesMenisciiList.prepare(scene,hertzOn); FOREACH(const shared_ptr& interaction, *scene->interactions){ // could be done in parallel as soon as OpenMPVector class (lib/base/openmp-accu.hpp) is extended See http://www.mail-archive.com/yade-dev@lists.launchpad.net/msg10842.html and msg11238.html /// interaction is real if (interaction->isReal()) { CapillaryPhys* cundallContactPhysics=NULL; MindlinCapillaryPhys* mindlinContactPhysics=NULL; /// contact physics depends on the contact law, that is used (either linear model or hertz model) if (!hertzOn) cundallContactPhysics = static_cast(interaction->phys.get());//use CapillaryPhys for linear model else mindlinContactPhysics = static_cast(interaction->phys.get());//use MindlinCapillaryPhys for hertz model unsigned int id1 = interaction->getId1(); unsigned int id2 = interaction->getId2(); /// definition of interacting objects (not necessarily in contact) ScGeom* currentContactGeometry = static_cast(interaction->geom.get()); /// test of interacting bodies geometries since capillarity will be computed between spheres only int geometryIndex1 = (*bodies)[id1]->shape->getClassIndex(); int geometryIndex2 = (*bodies)[id2]->shape->getClassIndex(); if ( !(geometryIndex1 == sphereIndex && geometryIndex2 == sphereIndex) ) { // such interactions won't have a meniscus // thus we will ask for the interaction to be erased in this distant case, as we do w. sphere-sphere interactions below: if(currentContactGeometry->penetrationDepth < 0) scene->interactions->requestErase(interaction); continue;} /// Interacting Grains: // If you want to define a ratio between YADE sphere size and real sphere size Real alpha=1; Real R1 = 0; R1=alpha*std::min(currentContactGeometry->radius2,currentContactGeometry->radius1) ; Real R2 = 0; R2=alpha*std::max(currentContactGeometry->radius2,currentContactGeometry->radius1) ; /// intergranular distance Real D = - alpha * currentContactGeometry->penetrationDepth; if ( D<0 || createDistantMeniscii) { //||(scene->iter < 1) ) // a simplified way to define meniscii everywhere D=max(0.,D); // defines fCap when spheres interpenetrate. D<0 leads to wrong interpolation as D<0 has no solution in the interpolation : this is not physically interpretable!! even if, interpenetration << grain radius. if (!hertzOn) { if (fusionDetection && !cundallContactPhysics->meniscus) bodiesMenisciiList.insert(interaction); cundallContactPhysics->meniscus=true; } else { if (fusionDetection && !mindlinContactPhysics->meniscus) bodiesMenisciiList.insert(interaction); mindlinContactPhysics->meniscus=true; } } Real Dinterpol = D/R2; /// Suction (Capillary pressure): Real Pinterpol = 0; if (!hertzOn) Pinterpol = cundallContactPhysics->isBroken ? 0 : capillaryPressure*(R2/surfaceTension); else Pinterpol = mindlinContactPhysics->isBroken ? 0 : capillaryPressure*(R2/surfaceTension); if (!hertzOn) cundallContactPhysics->capillaryPressure = capillaryPressure; else mindlinContactPhysics->capillaryPressure = capillaryPressure; /// Capillary solution finder: if ((Pinterpol>=0) && (hertzOn? mindlinContactPhysics->meniscus : cundallContactPhysics->meniscus)) { int* currentIndexes = hertzOn? mindlinContactPhysics->currentIndexes : cundallContactPhysics->currentIndexes; //If P=0, we use null solution MeniscusParameters solution(Pinterpol? capillary->interpolate(R1,R2,Dinterpol, Pinterpol, currentIndexes) : MeniscusParameters()); /// If no bridge, delete the interaction if necessary and escape if (!solution.V) { if ((fusionDetection) || (hertzOn ? mindlinContactPhysics->isBroken : cundallContactPhysics->isBroken)) bodiesMenisciiList.remove(interaction); if (D>0) {scene->interactions->requestErase(interaction); continue;} else if ((Pinterpol > 0) && (showError)) { LOG_ERROR("No meniscus found at a contact. capillaryPressure may be too large wrt. the loaded data files."); // V=0 at a contact reveals a problem if and only if uc* > 0 showError = false;}//show error message once / avoid console spam } /// capillary adhesion force Real Finterpol = solution.F; Vector3r fCap = - Finterpol*(2*Mathr::PI*(R2/alpha)*surfaceTension)*currentContactGeometry->normal; if (!hertzOn) cundallContactPhysics->fCap = fCap; else mindlinContactPhysics->fCap = fCap; /// meniscus volume Real Vinterpol = solution.V * pow(R2/alpha,3); if (!hertzOn) { cundallContactPhysics->vMeniscus = Vinterpol; if (Vinterpol != 0) cundallContactPhysics->meniscus = true; else cundallContactPhysics->meniscus = false; } else { mindlinContactPhysics->vMeniscus = Vinterpol; if (Vinterpol != 0) mindlinContactPhysics->meniscus = true; else mindlinContactPhysics->meniscus = false; } /// wetting angles if (!hertzOn) { cundallContactPhysics->Delta1 = min(max(solution.delta1,solution.delta2),90.0); // undesired values greater than 90 degrees (by few degrees) may be present in the capillary files for high r (~ 10) and very low suction and distance cundallContactPhysics->Delta2 = min(solution.delta1,solution.delta2); } else { mindlinContactPhysics->Delta1 = min(max(solution.delta1,solution.delta2),90.0); mindlinContactPhysics->Delta2 = min(solution.delta1,solution.delta2); } // nn11 and nn33 if (!hertzOn) { cundallContactPhysics->nn11 = pow(R2/alpha,2) * solution.nn11; cundallContactPhysics->nn33 = pow(R2/alpha,2) * solution.nn33; } } ///interaction is not real //If the interaction is not real, it should not be in the list } else if (fusionDetection) bodiesMenisciiList.remove(interaction); } if (fusionDetection) checkFusion(); #ifdef YADE_OPENMP const long size=scene->interactions->size(); #pragma omp parallel for schedule(guided) num_threads(ompThreads>0 ? min(ompThreads,omp_get_max_threads()) : omp_get_max_threads()) for(long i=0; i& interaction=(*scene->interactions)[i]; #else FOREACH(const shared_ptr& interaction, *scene->interactions){ #endif if (interaction->isReal()) { CapillaryPhys* cundallContactPhysics=NULL; MindlinCapillaryPhys* mindlinContactPhysics=NULL; if (!hertzOn) cundallContactPhysics = static_cast(interaction->phys.get());//use CapillaryPhys for linear model else mindlinContactPhysics = static_cast(interaction->phys.get());//use MindlinCapillaryPhys for hertz model if ((hertzOn && mindlinContactPhysics->meniscus) || (!hertzOn && cundallContactPhysics->meniscus)) { if (fusionDetection) {//version with effect of fusion //BINARY VERSION : if fusionNumber!=0 then no capillary force short int& fusionNumber = hertzOn?mindlinContactPhysics->fusionNumber:cundallContactPhysics->fusionNumber; if (binaryFusion) { if (fusionNumber!=0) { //cerr << "fusion" << endl; hertzOn?mindlinContactPhysics->fCap:cundallContactPhysics->fCap = Vector3r::Zero(); continue; } } //LINEAR VERSION : capillary force is divided by (fusionNumber + 1) - NOTE : any decreasing function of fusionNumber can be considered in fact else if (fusionNumber !=0) hertzOn?mindlinContactPhysics->fCap:cundallContactPhysics->fCap /= (fusionNumber+1.); } scene->forces.addForce(interaction->getId1(),-(hertzOn?mindlinContactPhysics->fCap:cundallContactPhysics->fCap)); scene->forces.addForce(interaction->getId2(), hertzOn?mindlinContactPhysics->fCap:cundallContactPhysics->fCap ); } } } } capillarylaw::capillarylaw() {} void capillarylaw::fill(const char* filename) { data_complete.push_back(Tableau(filename)); } void Law2_ScGeom_CapillaryPhys_Capillarity::checkFusion() { //Reset fusion numbers FOREACH(const shared_ptr& interaction, *scene->interactions){ // same remark for parallel loops, the most problematic part ? if ( interaction->isReal()) { if (!hertzOn) static_cast(interaction->phys.get())->fusionNumber=0; else static_cast(interaction->phys.get())->fusionNumber=0; } } list< shared_ptr >::iterator firstMeniscus, lastMeniscus, currentMeniscus; Real angle1 = -1.0; Real angle2 = -1.0; for ( int i=0; i< bodiesMenisciiList.size(); ++i ) { // i is the index (or id) of the body being tested CapillaryPhys* cundallInteractionPhysics1=NULL; MindlinCapillaryPhys* mindlinInteractionPhysics1=NULL; CapillaryPhys* cundallInteractionPhysics2=NULL; MindlinCapillaryPhys* mindlinInteractionPhysics2=NULL; if ( !bodiesMenisciiList[i].empty() ) { lastMeniscus = bodiesMenisciiList[i].end(); for ( firstMeniscus=bodiesMenisciiList[i].begin(); firstMeniscus!=lastMeniscus; ++firstMeniscus ) { //FOR EACH MENISCUS ON THIS BODY... currentMeniscus = firstMeniscus; ++currentMeniscus; if (!hertzOn) { cundallInteractionPhysics1 = YADE_CAST((*firstMeniscus)->phys.get()); if (i == (*firstMeniscus)->getId1()) angle1=cundallInteractionPhysics1->Delta1;//get angle of meniscus1 on body i else angle1=cundallInteractionPhysics1->Delta2; } else { mindlinInteractionPhysics1 = YADE_CAST((*firstMeniscus)->phys.get()); if (i == (*firstMeniscus)->getId1()) angle1=mindlinInteractionPhysics1->Delta1;//get angle of meniscus1 on body i else angle1=mindlinInteractionPhysics1->Delta2; } for ( ;currentMeniscus!= lastMeniscus; ++currentMeniscus) {//... CHECK FUSION WITH ALL OTHER MENISCII ON THE BODY if (!hertzOn) { cundallInteractionPhysics2 = YADE_CAST((*currentMeniscus)->phys.get()); if (i == (*currentMeniscus)->getId1()) angle2=cundallInteractionPhysics2->Delta1;//get angle of meniscus2 on body i else angle2=cundallInteractionPhysics2->Delta2; } else { mindlinInteractionPhysics2 = YADE_CAST((*currentMeniscus)->phys.get()); if (i == (*currentMeniscus)->getId1()) angle2=mindlinInteractionPhysics2->Delta1;//get angle of meniscus2 on body i else angle2=mindlinInteractionPhysics2->Delta2; } if (angle1==0 || angle2==0) cerr << "THIS SHOULD NOT HAPPEN!!"<< endl; //cerr << "angle1 = " << angle1 << " | angle2 = " << angle2 << endl; Vector3r normalFirstMeniscus = YADE_CAST((*firstMeniscus)->geom.get())->normal; Vector3r normalCurrentMeniscus = YADE_CAST((*currentMeniscus)->geom.get())->normal; Real normalDot = 0; if ((*firstMeniscus)->getId1() == (*currentMeniscus)->getId1() || (*firstMeniscus)->getId2() == (*currentMeniscus)->getId2()) normalDot = normalFirstMeniscus.dot(normalCurrentMeniscus); else normalDot = - (normalFirstMeniscus.dot(normalCurrentMeniscus)); Real normalAngle = 0; if (normalDot >= 0 ) normalAngle = Mathr::FastInvCos0(normalDot); else normalAngle = ((Mathr::PI) - Mathr::FastInvCos0(-(normalDot))); if ((angle1+angle2)*Mathr::DEG_TO_RAD > normalAngle) { if (!hertzOn) {++(cundallInteractionPhysics1->fusionNumber); ++(cundallInteractionPhysics2->fusionNumber);}//count +1 if 2 meniscii are overlaping else {++(mindlinInteractionPhysics1->fusionNumber); ++(mindlinInteractionPhysics2->fusionNumber);} } } } } } } MeniscusParameters capillarylaw::interpolate(Real R1, Real R2, Real D, Real P, int* index) { if (R1 > R2) { Real R3 = R1; R1 = R2; R2 = R3; } Real R = R2/R1; //cerr << "R = " << R << endl; MeniscusParameters result_inf; MeniscusParameters result_sup; MeniscusParameters result; int i = 0; for ( ; i < (NB_R_VALUES); i++) { Real data_R = data_complete[i].R; //cerr << "i = " << i << endl; if (data_R > R) // Attention a l'ordre ds lequel vont etre ranges les tableau R (croissant) { Tableau& tab_inf=data_complete[i-1]; Tableau& tab_sup=data_complete[i]; Real r=(R-tab_inf.R)/(tab_sup.R-tab_inf.R); result_inf = tab_inf.Interpolate2(D,P,index[0], index[1]); result_sup = tab_sup.Interpolate2(D,P,index[2], index[3]); result.V = result_inf.V*(1-r) + r*result_sup.V; result.F = result_inf.F*(1-r) + r*result_sup.F; result.delta1 = result_inf.delta1*(1-r) + r*result_sup.delta1; result.delta2 = result_inf.delta2*(1-r) + r*result_sup.delta2; result.nn11 = result_inf.nn11*(1-r) + r*result_sup.nn11; result.nn33 = result_inf.nn33*(1-r) + r*result_sup.nn33; i=NB_R_VALUES; //cerr << "i = " << i << endl; } else if (data_complete[i].R == R) { result = data_complete[i].Interpolate2(D,P, index[0], index[1]); i=NB_R_VALUES; } } return result; } Tableau::Tableau() {} Tableau::Tableau(const char* filename) { ifstream file (filename); file >> R; int n_D; //number of D values file >> n_D; if (!file.is_open()) { static bool first=true; if(first) { cout << "WARNING: cannot open files used for capillary law, all forces will be null. Instructions on how to download and install them is found here : https://yade-dem.org/wiki/CapillaryTriaxialTest." << endl; first=false; } return; } for (int i=0; i D ) // ok si D rang�s ds l'ordre croissant { Real rD = (D-full_data[i-1].D)/(full_data[i].D-full_data[i-1].D); result_inf = full_data[i-1].Interpolate3(P, index1); result_sup = full_data[i].Interpolate3(P, index2); result.V = result_inf.V*(1-rD) + rD*result_sup.V; result.F = result_inf.F*(1-rD) + rD*result_sup.F; result.delta1 = result_inf.delta1*(1-rD) + rD*result_sup.delta1; result.delta2 = result_inf.delta2*(1-rD) + rD*result_sup.delta2; result.nn11 = result_inf.nn11*(1-rD) + rD*result_sup.nn11; result.nn33 = result_inf.nn33*(1-rD) + rD*result_sup.nn33; i = full_data.size(); } else if (full_data[i].D == D) { result=full_data[i].Interpolate3(P, index1); i=full_data.size(); } } return result; } TableauD::TableauD() {} TableauD::TableauD(ifstream& file) { int i=0; Real x; int n_lines; //pb: n_lines is real!!! file >> n_lines; file.ignore(200, '\n'); // saute les caract�res (200 au maximum) jusque au caract�re \n (fin de ligne)*_ if (n_lines!=0) for (; i ()); for (int j=0; j < 8; ++j) // [D,P,V,F,delta1,delta2,nn11,nn33] { file >> x; data[i].push_back(x); } } else LOG_ERROR("Problem regarding the capillary file structure (e.g. n_D is not consistent with the actual data), and/or mismatch with the expected structure by the code ! Will segfault"); D = data[i-1][0]; } MeniscusParameters TableauD::Interpolate3(Real P, int& index) { //cerr << "interpolate3" << endl; MeniscusParameters result; int dataSize = data.size(); if (index < dataSize && index>0) { if (data[index][1] >= P && data[index-1][1] < P) { //compteur1+=1; Real Pinf=data[index-1][1]; Real Finf=data[index-1][3]; Real Vinf=data[index-1][2]; Real Delta1inf=data[index-1][4]; Real Delta2inf=data[index-1][5]; Real nn11inf = data[index-1][6]; Real nn33inf = data[index-1][7]; Real Psup=data[index][1]; Real Fsup=data[index][3]; Real Vsup=data[index][2]; Real Delta1sup=data[index][4]; Real Delta2sup=data[index][5]; Real nn11sup = data[index][6]; Real nn33sup = data[index][7]; result.V = Vinf+((Vsup-Vinf)/(Psup-Pinf))*(P-Pinf); result.F = Finf+((Fsup-Finf)/(Psup-Pinf))*(P-Pinf); result.delta1 = Delta1inf+((Delta1sup-Delta1inf)/(Psup-Pinf))*(P-Pinf); result.delta2 = Delta2inf+((Delta2sup-Delta2inf)/(Psup-Pinf))*(P-Pinf); result.nn11 = nn11inf + (nn11sup - nn11inf) / (Psup-Pinf) * (P-Pinf); result.nn33 = nn33inf + (nn33sup - nn33inf) / (Psup-Pinf) * (P-Pinf); return result; } } //compteur2+=1; for (int k=1; k < dataSize; ++k) // Length(data) ?? { if ( data[k][1] > P) // OK si P rangés ds l'ordre croissant { Real Pinf=data[k-1][1]; Real Finf=data[k-1][3]; Real Vinf=data[k-1][2]; Real Delta1inf=data[k-1][4]; Real Delta2inf=data[k-1][5]; Real nn11inf = data[k-1][6]; Real nn33inf = data[k-1][7]; Real Psup=data[k][1]; Real Fsup=data[k][3]; Real Vsup=data[k][2]; Real Delta1sup=data[k][4]; Real Delta2sup=data[k][5]; Real nn11sup = data[k][6]; Real nn33sup = data[k][7]; result.V = Vinf+((Vsup-Vinf)/(Psup-Pinf))*(P-Pinf); result.F = Finf+((Fsup-Finf)/(Psup-Pinf))*(P-Pinf); result.delta1 = Delta1inf+((Delta1sup-Delta1inf)/(Psup-Pinf))*(P-Pinf); result.delta2 = Delta2inf+((Delta2sup-Delta2inf)/(Psup-Pinf))*(P-Pinf); result.nn11 = nn11inf + (nn11sup - nn11inf) / (Psup-Pinf) * (P-Pinf); result.nn33 = nn33inf + (nn33sup - nn33inf) / (Psup-Pinf) * (P-Pinf); index = k; k=dataSize; } else if (data[k][1] == P) { result.V = data[k][2]; result.F = data[k][3]; result.delta1 = data[k][4]; result.delta2 = data[k][5]; result.nn11= data[k][6]; result.nn33= data[k][7]; index = k; k=dataSize; } } return result; } TableauD::~TableauD() {} std::ostream& operator<<(std::ostream& os, Tableau& T) { os << "Tableau : R=" << T.R << endl; for (unsigned int i=0; i& bodies = scene->bodies; Body::id_t MaxId = -1; BodyContainer::iterator bi = bodies->begin(); BodyContainer::iterator biEnd = bodies->end(); for( ; bi!=biEnd ; ++bi ) { MaxId=max(MaxId, (*bi)->getId()); } interactionsOnBody.resize(MaxId+1); for ( unsigned int i=0; i& I, *scene->interactions){ // parallel version using Engine::ompThreads variable not accessible, this function is not one of the Engine.. if (I->isReal()) { if (!hertzOn) {if (static_cast(I->phys.get())->meniscus) insert(I);} else {if (static_cast(I->phys.get())->meniscus) insert(I);} } } return initialized=true; } bool BodiesMenisciiList::insert(const shared_ptr< Interaction >& interaction) { checkLengthBuffer(interaction); interactionsOnBody[interaction->getId1()].push_back(interaction); interactionsOnBody[interaction->getId2()].push_back(interaction); return true; } bool BodiesMenisciiList::remove(const shared_ptr< Interaction >& interaction) { checkLengthBuffer(interaction); interactionsOnBody[interaction->getId1()].remove(interaction); interactionsOnBody[interaction->getId2()].remove(interaction); return true; } void BodiesMenisciiList::checkLengthBuffer(const shared_ptr& interaction) { Body::id_t maxBodyId = std::max(interaction->getId1(), interaction->getId2()); if (unsigned(maxBodyId) >= interactionsOnBody.size()) { interactionsOnBody.resize(maxBodyId+1); } } list< shared_ptr >& BodiesMenisciiList::operator[] (int index) { return interactionsOnBody[index]; } int BodiesMenisciiList::size() { return interactionsOnBody.size(); } void BodiesMenisciiList::display() { list< shared_ptr >::iterator firstMeniscus; list< shared_ptr >::iterator lastMeniscus; for ( unsigned int i=0; igetId1() << ", " << ( *firstMeniscus )->getId2() <<") "; else cerr << "(void)"; } } cerr << endl; } else cerr << "empty" << endl; } } BodiesMenisciiList::BodiesMenisciiList() { initialized=false; } trunk-2018.02b/pkg/dem/Law2_ScGeom_CapillaryPhys_Capillarity.hpp000066400000000000000000000215331324306050200244740ustar00rootroot00000000000000// // C++ Interface: Law2_ScGeom_CapillaryPhys_Capillarity /************************************************************************* * Copyright (C) 2006 by luc Scholtes * * luc.scholtes@hmg.inpg.fr * * * * This program is free software; it is licensed under the terms of the * * GNU General Public License v2 or later. See file LICENSE for details. * *************************************************************************/ #pragma once #include /** This law allows one to take into account capillary forces/effects between spheres coming from the presence of interparticular liquid bridges (menisci). refs: - (french, lot of documentation) L. Scholtes, PhD thesis -> http://tel.archives-ouvertes.fr/tel-00363961/en/ - (english, less...) L. Scholtes et al. Micromechanics of granular materials with capillary effects. International Journal of Engineering Science 2009,(47)1, 64-75 The law needs ascii files M(r=i) with i=R1/R2 to work (downloaded from https://yade-dem.org/wiki/CapillaryTriaxialTest). They contain a set of results from the resolution of the Laplace-Young equation for different configurations of the interacting geometry (assuming a null wetting angle) and must be placed in the bin directory (where yade exec file is situated) to be taken into account. The control parameter is the capillary pressure (or suction) Delta_u, defined as the difference between gas and liquid pressure: Delta_u = u_gas - u_liquid Liquid bridges properties (volume V, extent over interacting grains delta1 and delta2) are computed as a result of Delta_u and the interacting geometry (spheres radii and interparticular distance) Rk: - the formulation is valid only for pendular menisci involving two grains (pendular regime). - an algorithm was developed by B. Chareyre to identify menisci overlaps on each spheres (menisci fusion). - some assumptions can be made to reduce capillary forces when menisci overlap (binary->F_cap=0 if at least 1 overlap, linear->F_cap=F_cap/numberOfOverlaps) */ /// !!! This version is deprecated. It should be updated to the new formalism -> ToDo !!! /// a class to store meniscus parameters -> Rk: is it really needed since CapillaryPhys exist? class MeniscusParameters { public : Real V; // adimentionnal volume of the meniscus : true volume / Rmax^3, see Annexe 1 of Scholtes2009d Real F; // adimentionnal capillary force for this meniscus : true force / ( 2 * pi * Rmax * superficial tension), (30) of Annexe1 of Scholtes2009d Real delta1; // angle defined Fig 2.5 Scholtes2009d Real delta2; // angle defined Fig 2.5 Scholtes2009d Real nn11; // CapillaryPhys.nn11 / R2^2 Real nn33; // CapillaryPhys.nn33 / R2^2 int index1; int index2; MeniscusParameters(); MeniscusParameters(const MeniscusParameters &source); ~MeniscusParameters(); }; /// R = ratio(RadiusParticle1 on RadiusParticle2). Here, 10 R values from interpolation files (yade/extra/capillaryFiles), R = 1, 1.1, 1.25, 1.5, 1.75, 2, 3, 4, 5, 10 const int NB_R_VALUES = 10; class capillarylaw; // the class defined below (end of file) class Interaction; ///This container class is used to check if meniscii overlap. Wet interactions are put in a series of lists, with one list per body. class BodiesMenisciiList { private: vector< list< shared_ptr > > interactionsOnBody; // would require use of OpenMPVector (lib/base/openmp-accu.hpp) for parallel coding, see http://www.mail-archive.com/yade-dev@lists.launchpad.net/msg10842.html and msg11238.html //shared_ptr empty; public: BodiesMenisciiList(); BodiesMenisciiList(Scene*,bool);//TODO: remove? bool prepare(Scene*,bool); bool insert(const shared_ptr&); bool remove(const shared_ptr&); list< shared_ptr >& operator[] (int); int size(); void display(); void checkLengthBuffer(const shared_ptr&); bool initialized; }; /// This is the constitutive law class Law2_ScGeom_CapillaryPhys_Capillarity : public GlobalEngine { public : void checkFusion(); shared_ptr capillary; BodiesMenisciiList bodiesMenisciiList; void action(); void postLoad(Law2_ScGeom_CapillaryPhys_Capillarity&); bool hertzInitialized; bool hertzOn; bool showError; YADE_CLASS_BASE_DOC_ATTRS_INIT_CTOR_PY(Law2_ScGeom_CapillaryPhys_Capillarity,GlobalEngine,"This law allows one to take into account capillary forces/effects between spheres coming from the presence of interparticular liquid bridges (menisci).\n\nThe control parameter is the :yref:`capillary pressure` (or suction) Uc = Ugas - Uliquid. Liquid bridges properties (volume V, extent over interacting grains delta1 and delta2) are computed as a result of the defined capillary pressure and of the interacting geometry (spheres radii and interparticular distance).\n\nReferences: in english [Scholtes2009b]_; more detailed, but in french [Scholtes2009d]_.\n\nThe law needs ascii files M(r=i) with i=R1/R2 to work (see https://yade-dem.org/wiki/CapillaryTriaxialTest). These ASCII files contain a set of results from the resolution of the Laplace-Young equation for different configurations of the interacting geometry, assuming a null wetting angle.\n\nIn order to allow capillary forces between distant spheres, it is necessary to enlarge the bounding boxes using :yref:`Bo1_Sphere_Aabb::aabbEnlargeFactor` and make the Ig2 define define distant interactions via :yref:`interactionDetectionFactor`. It is also necessary to disable interactions removal by the constitutive law (:yref:`Law2=True`). The only combinations of laws supported are currently capillary law + :yref:`Law2_ScGeom_FrictPhys_CundallStrack` and capillary law + :yref:`Law2_ScGeom_MindlinPhys_Mindlin` (and the other variants of Hertz-Mindlin).\n\nSee CapillaryPhys-example.py for an example script.", ((Real,capillaryPressure,0.,,"Value of the capillary pressure Uc defined as Uc=Ugas-Uliquid")) ((bool,fusionDetection,false,,"If true potential menisci overlaps are checked, computing :yref:`fusionNumber` for each capillary interaction, and reducing :yref:`fCap` according to :yref:`binaryFusion`")) ((bool,binaryFusion,true,,"If true, capillary forces are set to zero as soon as, at least, 1 overlap (menisci fusion) is detected. Otherwise :yref:`fCap` = :yref:`fCap` / (:yref:`fusionNumber` + 1 )")) ((bool,createDistantMeniscii,false,,"Generate meniscii between distant spheres? Else only maintain the existing ones. For modeling a wetting path this flag should always be false. For a drying path it should be true for one step (initialization) then false, as in the logic of [Scholtes2009c]_")) ((Real,surfaceTension,0.073,,"Value of considered surface tension")) // (0.073 N/m is water tension at 20 Celsius degrees) ((string,suffCapFiles,"",,"Capillary files suffix: M(r=X)suffCapFiles")) ,,/*constructor*/ hertzInitialized = false; hertzOn = false; showError = true; , ); }; class TableauD // Laplace solutions for a given r, and a given D (and a given contact angle): pieces of one capillary file { public: Real D; // one cst D value in each TableauD (the one appearing last line of corresponding group D=cst in the capillary file) std::vector > data; MeniscusParameters Interpolate3(Real P, int& index); // does the interpolation on uc* TableauD(); TableauD(std::ifstream& file); ~TableauD(); }; // Fonction d'ecriture de tableau, utilisee dans le constructeur pour test //TODO: translate this in english class Tableau; std::ostream& operator<<(std::ostream& os, Tableau& T); class Tableau // Laplace solutions for a given r (and a given contact angle): one capillary file { public: Real R; std::vector full_data; // members of full_data are the different TableauD, for different D. MeniscusParameters Interpolate2(Real D, Real P, int& index1, int& index2); // does the interpolation on d* (returning no meniscus when d* > the greatest D of the file) std::ifstream& operator<< (std::ifstream& file); Tableau(); Tableau(const char* filename); ~Tableau(); }; class capillarylaw // class for a whole set of capillary files M(r=..) { public: capillarylaw(); std::vector data_complete; // data_complete includes, in ascending order of r, all capillary files: each Tableau of data_complete is one capillary file MeniscusParameters interpolate(Real R1, Real R2, Real D, Real P, int* index); void fill (const char* filename); }; REGISTER_SERIALIZABLE(Law2_ScGeom_CapillaryPhys_Capillarity); trunk-2018.02b/pkg/dem/Law2_ScGeom_CapillaryPhys_Capillarity1.cpp000066400000000000000000000672311324306050200245550ustar00rootroot00000000000000/************************************************************************* * Copyright (C) 2006 by luc Scholtes * * luc.scholtes@hmg.inpg.fr * * * * This program is free software; it is licensed under the terms of the * * GNU General Public License v2 or later. See file LICENSE for details. * *************************************************************************/ //Modifs : Parameters renamed as MeniscusParameters //id1/id2 as id1 is the smallest grain, FIXME : wetting angle? //FIXME : in triaxialStressController, change test about null force in updateStiffnessccc //keep this #ifdef as long as you don't really want to realize a final version publicly, it will save compilation time for everyone else //when you want it compiled, you can just uncomment the following line //#define LAW2_SCGEOM_CAPILLARYPHYS_Capillarity1 #ifdef LAW2_SCGEOM_CAPILLARYPHYS_Capillarity1 #include #include #include #include #include #include #include #include #include DT Law2_ScGeom_CapillaryPhys_Capillarity1::dtVbased; DT Law2_ScGeom_CapillaryPhys_Capillarity1::dtPbased; Real Law2_ScGeom_CapillaryPhys_Capillarity1::intEnergy() { Real energy=0; FOREACH(const shared_ptr& I, *scene->interactions){ if(!I->isReal()) continue; ScGeom* currentGeometry = static_cast(I->geom.get()); CapillaryPhys1* phys = dynamic_cast(I->phys.get()); if(phys) { if (phys->SInterface!=0){ energy += liquidTension*(phys->SInterface-4*3.141592653589793238462643383279502884*(pow(currentGeometry->radius1,2))-4*3.141592653589793238462643383279502884*(pow(currentGeometry->radius2,2)));} } } return energy; } Real Law2_ScGeom_CapillaryPhys_Capillarity1::wnInterface() { Real wn=0; FOREACH(const shared_ptr& I, *scene->interactions){ if(!I->isReal()) continue; ScGeom* currentGeometry = static_cast(I->geom.get()); CapillaryPhys1* phys = dynamic_cast(I->phys.get()); if(phys) { if (phys->SInterface!=0){ wn += (phys->SInterface-2*3.141592653589793238462643383279502884*(pow(currentGeometry->radius1,2)*(1+cos(phys->Delta1))+pow(currentGeometry->radius2,2)*(1+cos(phys->Delta2)))); } } } return wn; } Real Law2_ScGeom_CapillaryPhys_Capillarity1::swInterface() { Real sw=0; FOREACH(const shared_ptr& I, *scene->interactions){ if(!I->isReal()) continue; ScGeom* currentGeometry = static_cast(I->geom.get()); CapillaryPhys1* phys = dynamic_cast(I->phys.get()); if(phys) { sw += (2*3.141592653589793238462643383279502884*(pow(currentGeometry->radius1,2)*(1-cos(phys->Delta1))+pow(currentGeometry->radius2,2)*(1-cos(phys->Delta2)))); } } return sw; } Real Law2_ScGeom_CapillaryPhys_Capillarity1::waterVolume() { Real volume=0; FOREACH(const shared_ptr& I, *scene->interactions){ if(!I->isReal()) continue; CapillaryPhys1* phys = dynamic_cast(I->phys.get()); if(phys) { volume += phys->vMeniscus;} } return volume; } void Law2_ScGeom_CapillaryPhys_Capillarity1::triangulateData() { /// We get data from a file and input them in triangulations if (solutions.size()>0) {LOG_WARN("Law2_ScGeom_CapillaryPhys_Capillarity1 asking triangulation for the second time. Ignored."); return;} ifstream file (inputFilename.c_str()); if (!file.is_open()) { LOG_ERROR("No data file found for capillary law. Check path and inputFilename."); return;} // convention R,v,d,s,e,f,p,a1,a2,dummy (just for the example, define your own, // dummy is because has too much values per line - with one useless extra colum,) MeniscusPhysicalData dat; double ending; while ( file.good() ) { file >>dat.succion>>dat.force>>dat.distance>>dat.volume>>dat.surface>>dat.arcLength>>dat.delta1>>dat.delta2>>dat.R>>ending; dat.ending=(bool) ending; solutions.push_back(dat); } file.close(); // Make lists of points with index, so we can use range insertion, more efficient // see http://doc.cgal.org/latest/Triangulation_3/index.html#Triangulation_3SettingInformationWhileInserting std::vector< std::pair > pointsP, pointsV; for (unsigned int k=0; kinteractions->begin(); InteractionContainer::iterator iiEnd = scene->interactions->end(); if (imposePressure) { solver(capillaryPressure,switched); } else{ if (((totalVolumeConstant || (!totalVolumeConstant && firstIteration==1)) && totalVolumeofWater!=-1) || (totalVolumeConstant && totalVolumeofWater==-1)) { if (!totalVolumeConstant) x=1; totalVolumeConstant=1; Real p0=capillaryPressure; Real slope; Real eps=0.0000000001; solver(p0,switched); Real V0=waterVolume(); if (totalVolumeConstant && totalVolumeofWater==-1 && firstIteration==1){ totalVolumeofWater=V0; firstIteration+=1; } Real p1=capillaryPressure+0.1; solver(p1,switched); Real V1=waterVolume(); while (abs((totalVolumeofWater-V1)/totalVolumeofWater)>eps){ slope= (p1-p0)/(V1-V0); p0=p1; V0=V1; p1=p1-slope*(V1-totalVolumeofWater); if (p1<0) { cout<< "The requested volume of water is quite big, the simulation will continue at constant suction.:"<< p0 <interactions->begin(); ii!=iiEnd ; ++ii) { CapillaryPhys1* phys = dynamic_cast((*ii)->phys.get()); if ((*ii)->isReal() && phys-> computeBridge==true) { CapillaryPhys1* cundallContactPhysics=NULL; MindlinCapillaryPhys* mindlinContactPhysics=NULL; if (!hertzOn) cundallContactPhysics = static_cast((*ii)->phys.get());//use CapillaryPhys for linear model else mindlinContactPhysics = static_cast((*ii)->phys.get());//use MindlinCapillaryPhys for hertz model if ((hertzOn && mindlinContactPhysics->meniscus) || (!hertzOn && cundallContactPhysics->meniscus)) { if (fusionDetection) {//version with effect of fusion //BINARY VERSION : if fusionNumber!=0 then no capillary force short int& fusionNumber = hertzOn?mindlinContactPhysics->fusionNumber:cundallContactPhysics->fusionNumber; if (binaryFusion) { if (fusionNumber!=0) { //cerr << "fusion" << endl; hertzOn?mindlinContactPhysics->fCap:cundallContactPhysics->fCap = Vector3r::Zero(); continue; } } //LINEAR VERSION : capillary force is divided by (fusionNumber + 1) - NOTE : any decreasing function of fusionNumber can be considered in fact else if (fusionNumber !=0) hertzOn?mindlinContactPhysics->fCap:cundallContactPhysics->fCap /= (fusionNumber+1.); } scene->forces.addForce((*ii)->getId1(), hertzOn?mindlinContactPhysics->fCap:cundallContactPhysics->fCap); scene->forces.addForce((*ii)->getId2(),-(hertzOn?mindlinContactPhysics->fCap:cundallContactPhysics->fCap)); } } } } void Law2_ScGeom_CapillaryPhys_Capillarity1::checkFusion() { //Reset fusion numbers InteractionContainer::iterator ii = scene->interactions->begin(); InteractionContainer::iterator iiEnd = scene->interactions->end(); for( ; ii!=iiEnd ; ++ii ) { if ((*ii)->isReal()) { if (!hertzOn) static_cast((*ii)->phys.get())->fusionNumber=0; else static_cast((*ii)->phys.get())->fusionNumber=0; } } std::list< shared_ptr >::iterator firstMeniscus, lastMeniscus, currentMeniscus; Real angle1 = -1.0; Real angle2 = -1.0; for ( int i=0; i< bodiesMenisciiList.size(); ++i ) { // i is the index (or id) of the body being tested CapillaryPhys1* cundallInteractionPhysics1=NULL; MindlinCapillaryPhys* mindlinInteractionPhysics1=NULL; CapillaryPhys1* cundallInteractionPhysics2=NULL; MindlinCapillaryPhys* mindlinInteractionPhysics2=NULL; if ( !bodiesMenisciiList[i].empty() ) { lastMeniscus = bodiesMenisciiList[i].end(); for ( firstMeniscus=bodiesMenisciiList[i].begin(); firstMeniscus!=lastMeniscus; ++firstMeniscus ) { //FOR EACH MENISCUS ON THIS BODY... currentMeniscus = firstMeniscus; ++currentMeniscus; if (!hertzOn) { cundallInteractionPhysics1 = YADE_CAST((*firstMeniscus)->phys.get()); if (i == (*firstMeniscus)->getId1()) angle1=cundallInteractionPhysics1->Delta1;//get angle of meniscus1 on body i else angle1=cundallInteractionPhysics1->Delta2; } else { mindlinInteractionPhysics1 = YADE_CAST((*firstMeniscus)->phys.get()); if (i == (*firstMeniscus)->getId1()) angle1=mindlinInteractionPhysics1->Delta1;//get angle of meniscus1 on body i else angle1=mindlinInteractionPhysics1->Delta2; } for ( ; currentMeniscus!= lastMeniscus; ++currentMeniscus) { //... CHECK FUSION WITH ALL OTHER MENISCII ON THE BODY if (!hertzOn) { cundallInteractionPhysics2 = YADE_CAST((*currentMeniscus)->phys.get()); if (i == (*currentMeniscus)->getId1()) angle2=cundallInteractionPhysics2->Delta1;//get angle of meniscus2 on body i else angle2=cundallInteractionPhysics2->Delta2; } else { mindlinInteractionPhysics2 = YADE_CAST((*currentMeniscus)->phys.get()); if (i == (*currentMeniscus)->getId1()) angle2=mindlinInteractionPhysics2->Delta1;//get angle of meniscus2 on body i else angle2=mindlinInteractionPhysics2->Delta2; } if (angle1==0 || angle2==0) cerr << "THIS SHOULD NOT HAPPEN!!"<< endl; Vector3r normalFirstMeniscus = YADE_CAST((*firstMeniscus)->geom.get())->normal; Vector3r normalCurrentMeniscus = YADE_CAST((*currentMeniscus)->geom.get())->normal; Real normalDot = 0; if ((*firstMeniscus)->getId1() == (*currentMeniscus)->getId1() || (*firstMeniscus)->getId2() == (*currentMeniscus)->getId2()) normalDot = normalFirstMeniscus.dot(normalCurrentMeniscus); else normalDot = - (normalFirstMeniscus.dot(normalCurrentMeniscus)); Real normalAngle = 0; if (normalDot >= 0 ) normalAngle = Mathr::FastInvCos0(normalDot); else normalAngle = ((Mathr::PI) - Mathr::FastInvCos0(-(normalDot))); if ((angle1+angle2)*Mathr::DEG_TO_RAD > normalAngle) { if (!hertzOn) { ++(cundallInteractionPhysics1->fusionNumber); //count +1 if 2 meniscii are overlaping ++(cundallInteractionPhysics2->fusionNumber); } else { ++(mindlinInteractionPhysics1->fusionNumber); ++(mindlinInteractionPhysics2->fusionNumber); } }; } } } } } BodiesMenisciiList1::BodiesMenisciiList1(Scene * scene) { initialized=false; prepare(scene); } bool BodiesMenisciiList1::prepare(Scene * scene) { interactionsOnBody.clear(); shared_ptr& bodies = scene->bodies; Body::id_t MaxId = -1; BodyContainer::iterator bi = bodies->begin(); BodyContainer::iterator biEnd = bodies->end(); for( ; bi!=biEnd ; ++bi ) { MaxId=max(MaxId, (*bi)->getId()); } interactionsOnBody.resize(MaxId+1); for ( unsigned int i=0; iinteractions->begin(); InteractionContainer::iterator iiEnd = scene->interactions->end(); for( ; ii!=iiEnd ; ++ii ) { if ((*ii)->isReal()) { if (static_cast((*ii)->phys.get())->meniscus) insert(*ii); } } return initialized=true; } bool BodiesMenisciiList1::insert(const shared_ptr< Interaction >& interaction) { interactionsOnBody[interaction->getId1()].push_back(interaction); interactionsOnBody[interaction->getId2()].push_back(interaction); return true; } bool BodiesMenisciiList1::remove(const shared_ptr< Interaction >& interaction) { interactionsOnBody[interaction->getId1()].remove(interaction); interactionsOnBody[interaction->getId2()].remove(interaction); return true; } list< shared_ptr >& BodiesMenisciiList1::operator[] (int index) { return interactionsOnBody[index]; } int BodiesMenisciiList1::size() { return interactionsOnBody.size(); } void BodiesMenisciiList1::display() { list< shared_ptr >::iterator firstMeniscus; list< shared_ptr >::iterator lastMeniscus; for ( unsigned int i=0; iget() ) cerr << "(" << ( *firstMeniscus )->getId1() << ", " << ( *firstMeniscus )->getId2() <<") "; else cerr << "(void)"; } } cerr << endl; } else cerr << "empty" << endl; } } BodiesMenisciiList1::BodiesMenisciiList1() { initialized=false; } void Law2_ScGeom_CapillaryPhys_Capillarity1::solver(Real suction,bool reset) { if (!scene) cerr << "scene not defined!"; shared_ptr& bodies = scene->bodies; if (dtPbased.number_of_vertices ()<1 ) triangulateData(); if (fusionDetection && !bodiesMenisciiList.initialized) bodiesMenisciiList.prepare(scene); InteractionContainer::iterator ii = scene->interactions->begin(); InteractionContainer::iterator iiEnd = scene->interactions->end(); bool hertzInitialized = false; Real i=0; for (; ii!=iiEnd ; ++ii) { i+=1; CapillaryPhys1* phys = dynamic_cast((*ii)->phys.get());//////////////////////////////////////////////////////////////////////////////////////////// if ((*ii)->isReal() && phys-> computeBridge==true) { const shared_ptr& interaction = *ii; if (!hertzInitialized) {//NOTE: We are assuming that only one type is used in one simulation here if (CapillaryPhys1::getClassIndexStatic()==interaction->phys->getClassIndex()) hertzOn=false; else if (MindlinCapillaryPhys::getClassIndexStatic()==interaction->phys->getClassIndex()) hertzOn=true; else LOG_ERROR("The capillary law is not implemented for interactions using"<phys->getClassName()); } hertzInitialized = true; CapillaryPhys1* cundallContactPhysics=NULL; MindlinCapillaryPhys* mindlinContactPhysics=NULL; /// contact physics depends on the contact law, that is used (either linear model or hertz model) if (!hertzOn) cundallContactPhysics = static_cast(interaction->phys.get());//use CapillaryPhys for linear model else mindlinContactPhysics = static_cast(interaction->phys.get());//use MindlinCapillaryPhys for hertz model unsigned int id1 = interaction->getId1(); unsigned int id2 = interaction->getId2(); /// interaction geometry search (this test is to compute capillarity only between spheres (probably a better way to do that) int geometryIndex1 = (*bodies)[id1]->shape->getClassIndex(); // !!! int geometryIndex2 = (*bodies)[id2]->shape->getClassIndex(); if (!(geometryIndex1 == geometryIndex2)) continue; /// definition of interacting objects (not necessarily in contact) ScGeom* currentContactGeometry = static_cast(interaction->geom.get()); /// Interacting Grains: // If you want to define a ratio between YADE sphere size and real sphere size Real alpha=1; Real R1 = alpha*std::max(currentContactGeometry->radius2,currentContactGeometry->radius1); Real R2 =alpha*std::min(currentContactGeometry->radius2,currentContactGeometry->radius1); shared_ptr& bodies = scene->bodies; Real N=bodies->size(); Real epsilon1,epsilon2; Real ran1= (N-id1+0.5)/(N+1); Real ran2= (N-id2+0.5)/(N+1); epsilon1 = epsilonMean*(2*(ran1-0.5)* disp +1);//addednow epsilon2 = epsilonMean*(2*(ran2-0.5)* disp +1); // cout << epsilon1 << "separate" <penetrationDepth))+epsilon1*R1+epsilon2*R2; if ((currentContactGeometry->penetrationDepth>=0)|| D<=0 || createDistantMeniscii) { //||(scene->iter < 1) ) // a simplified way to define meniscii everywhere if (!hertzOn) { if (fusionDetection && !cundallContactPhysics->meniscus) bodiesMenisciiList.insert((*ii)); cundallContactPhysics->meniscus=true; } else { if (fusionDetection && !mindlinContactPhysics->meniscus) bodiesMenisciiList.insert((*ii)); mindlinContactPhysics->meniscus=true; } } Real Dinterpol = D/R1; /// Suction (Capillary pressure): if (imposePressure || (!imposePressure && totalVolumeConstant)){ Real Pinterpol = 0; if (!hertzOn) Pinterpol = cundallContactPhysics->isBroken ? 0 : suction*R1/liquidTension; else Pinterpol = mindlinContactPhysics->isBroken ? 0 : suction*R1/liquidTension; if (!hertzOn) cundallContactPhysics->capillaryPressure = suction; else mindlinContactPhysics->capillaryPressure = suction; /// Capillary solution finder: if ((Pinterpol>=0) && (hertzOn? mindlinContactPhysics->meniscus : cundallContactPhysics->meniscus)) {//FIXME: need an "else {delete}" MeniscusPhysicalData solution = interpolate1(dtPbased,K::Point_3(R2/R1, Pinterpol, Dinterpol), cundallContactPhysics->m, solutions, reset); /// capillary adhesion force Real Finterpol = solution.force; Vector3r fCap = Finterpol*R1*liquidTension*currentContactGeometry->normal; if (!hertzOn) cundallContactPhysics->fCap = fCap; else mindlinContactPhysics->fCap = fCap; /// meniscus volume //FIXME: hardcoding numerical constants is bad practice generaly, and it probably reveals a flaw in that case (Bruno) Real Vinterpol = solution.volume*pow(R1,3); Real SInterface = solution.surface*pow(R1,2); if (!hertzOn) { cundallContactPhysics->vMeniscus = Vinterpol; cundallContactPhysics->SInterface = SInterface; if (Vinterpol > 0) cundallContactPhysics->meniscus = true; else cundallContactPhysics->meniscus = false; } else { mindlinContactPhysics->vMeniscus = Vinterpol; if (Vinterpol > 0) mindlinContactPhysics->meniscus = true; else mindlinContactPhysics->meniscus = false; } if (cundallContactPhysics->meniscus== false) cundallContactPhysics->SInterface=4*3.141592653589793238462643383279502884*(pow(R1,2))+4*3.141592653589793238462643383279502884*(pow(R2,2)); if (!Vinterpol) { if ((fusionDetection) || (hertzOn ? mindlinContactPhysics->isBroken : cundallContactPhysics->isBroken)) bodiesMenisciiList.remove((*ii)); /// FIXME: the following D>(...) test is wrong, should be based on penetrationDepth, and should "continue" after erasing if (D>((interactionDetectionFactor-1)*(currentContactGeometry->radius2+currentContactGeometry->radius1))) scene->interactions->requestErase(interaction); } /// wetting angles if (!hertzOn) { cundallContactPhysics->Delta1 = max(solution.delta1,solution.delta2); cundallContactPhysics->Delta2 = min(solution.delta1,solution.delta2); } else { mindlinContactPhysics->Delta1 = max(solution.delta1,solution.delta2); mindlinContactPhysics->Delta2 = min(solution.delta1,solution.delta2); } } } else{ if (cundallContactPhysics->vMeniscus==0 and cundallContactPhysics->capillaryPressure!=0){//FIXME: test capillaryPressure consistently (!0 or >0 or >=0?!) Real Pinterpol = 0; if (!hertzOn) Pinterpol = cundallContactPhysics->isBroken ? 0 : cundallContactPhysics->capillaryPressure*R1/liquidTension; else Pinterpol = mindlinContactPhysics->isBroken ? 0 : cundallContactPhysics->capillaryPressure*R1/liquidTension; // if (!hertzOn) cundallContactPhysics->capillaryPressure = suction; // else mindlinContactPhysics->capillaryPressure = suction; /// Capillary solution finder: if ((Pinterpol>=0) && (hertzOn? mindlinContactPhysics->meniscus : cundallContactPhysics->meniscus)) { MeniscusPhysicalData solution = interpolate1(dtPbased,K::Point_3(R2/R1, Pinterpol, Dinterpol), cundallContactPhysics->m, solutions, reset); /// capillary adhesion force Real Finterpol = solution.force; Vector3r fCap = Finterpol*R1*liquidTension*currentContactGeometry->normal; if (!hertzOn) cundallContactPhysics->fCap = fCap; else mindlinContactPhysics->fCap = fCap; /// meniscus volume //FIXME: hardcoding numerical constants is bad practice generaly, and it probably reveals a flaw in that case (Bruno) Real Vinterpol = solution.volume*pow(R1,3); Real SInterface = solution.surface*pow(R1,2); if (!hertzOn) { cundallContactPhysics->vMeniscus = Vinterpol; cundallContactPhysics->SInterface = SInterface; if (Vinterpol > 0) cundallContactPhysics->meniscus = true; else cundallContactPhysics->meniscus = false; } else { mindlinContactPhysics->vMeniscus = Vinterpol; if (Vinterpol > 0) mindlinContactPhysics->meniscus = true; else mindlinContactPhysics->meniscus = false; } if (cundallContactPhysics->meniscus== false) cundallContactPhysics->SInterface=4*3.141592653589793238462643383279502884*(pow(R1,2))+4*3.141592653589793238462643383279502884*(pow(R2,2)); if (!Vinterpol) { if ((fusionDetection) || (hertzOn ? mindlinContactPhysics->isBroken : cundallContactPhysics->isBroken)) bodiesMenisciiList.remove((*ii)); if (D>((interactionDetectionFactor-1)*(currentContactGeometry->radius2+currentContactGeometry->radius1))) scene->interactions->requestErase(interaction); } /// wetting angles if (!hertzOn) { cundallContactPhysics->Delta1 = max(solution.delta1,solution.delta2); cundallContactPhysics->Delta2 = min(solution.delta1,solution.delta2); } else { mindlinContactPhysics->Delta1 = max(solution.delta1,solution.delta2); mindlinContactPhysics->Delta2 = min(solution.delta1,solution.delta2); } } } else{ Real Vinterpol = 0; if (!hertzOn) { Vinterpol = cundallContactPhysics->vMeniscus/pow(R1,3); } else Vinterpol = mindlinContactPhysics->vMeniscus; /// Capillary solution finder: if ((hertzOn? mindlinContactPhysics->meniscus : cundallContactPhysics->meniscus)) { MeniscusPhysicalData solution = interpolate2(dtVbased,K::Point_3(R2/R1, Vinterpol, Dinterpol), cundallContactPhysics->m, solutions,reset); /// capillary adhesion force Real Finterpol = solution.force; Vector3r fCap = Finterpol*R1*liquidTension*currentContactGeometry->normal; if (!hertzOn) cundallContactPhysics->fCap = fCap; else mindlinContactPhysics->fCap = fCap; /// suction and interfacial area Real Pinterpol = solution.succion*liquidTension/R1; Real SInterface = solution.surface*pow(R1,2); if (!hertzOn) { cundallContactPhysics->capillaryPressure = Pinterpol; cundallContactPhysics->SInterface = SInterface; if (Finterpol > 0) cundallContactPhysics->meniscus = true; else{ cundallContactPhysics->vMeniscus=0; cundallContactPhysics->meniscus = false; } } else { mindlinContactPhysics->capillaryPressure = Pinterpol; if (Finterpol > 0) mindlinContactPhysics->meniscus = true; else { cundallContactPhysics->vMeniscus=0; mindlinContactPhysics->meniscus = false; } } if (!Vinterpol) { if ((fusionDetection) || (hertzOn ? mindlinContactPhysics->isBroken : cundallContactPhysics->isBroken)) bodiesMenisciiList.remove((*ii)); if (D>((interactionDetectionFactor-1)*(currentContactGeometry->radius2+currentContactGeometry->radius1))) scene->interactions->requestErase(interaction); } /// wetting angles if (!hertzOn) { cundallContactPhysics->Delta1 = max(solution.delta1,solution.delta2); cundallContactPhysics->Delta2 = min(solution.delta1,solution.delta2); } else { mindlinContactPhysics->Delta1 = max(solution.delta1,solution.delta2); mindlinContactPhysics->Delta2 = min(solution.delta1,solution.delta2); } } } } ///interaction is not real //If the interaction is not real, it should not be in the list } else if (fusionDetection) bodiesMenisciiList.remove((*ii)); } if (fusionDetection) checkFusion(); } #endif //LAW2_SCGEOM_CAPILLARYPHYS_Capillarity1 trunk-2018.02b/pkg/dem/Law2_ScGeom_CapillaryPhys_Capillarity1.hpp000066400000000000000000000176431324306050200245640ustar00rootroot00000000000000// // C++ Interface: Law2_ScGeom_CapillaryPhys_Capillarity /************************************************************************* * Copyright (C) 2006 by luc Scholtes * * luc.scholtes@hmg.inpg.fr * * * * This program is free software; it is licensed under the terms of the * * GNU General Public License v2 or later. See file LICENSE for details. * *************************************************************************/ #pragma once #include #include #include #include #include #include #include #include #include #include #include /** This law allows one to take into account capillary forces/effects between spheres coming from the presence of interparticular liquid bridges (menisci). refs: - (french, lot of documentation) L. Scholtes, PhD thesis -> http://tel.archives-ouvertes.fr/tel-00363961/en/ - (english, less...) L. Scholtes et al. Micromechanics of granular materials with capillary effects. International Journal of Engineering Science 2009,(47)1, 64-75 The law needs ascii files M(r=i) with i=R1/R2 to work (downloaded from https://yade-dem.org/wiki/CapillaryTriaxialTest). They contain a set of results from the resolution of the Laplace-Young equation for different configurations of the interacting geometry and must be placed in the bin directory (where yade exec file is situated) to be taken into account. The control parameter is the capillary pressure (or suction) Delta_u, defined as the difference between gas and liquid pressure: Delta_u = u_gas - u_liquid Liquid bridges properties (volume V, extent over interacting grains delta1 and delta2) are computed as a result of Delta_u and the interacting geometry (spheres radii and interparticular distance) Rk: - the formulation is valid only for pendular menisci involving two grains (pendular regime). - an algorithm was developed by B. Chareyre to identify menisci overlaps on each spheres (menisci fusion). - some assumptions can be made to reduce capillary forces when menisci overlap (binary->F_cap=0 if at least 1 overlap, linear->F_cap=F_cap/numberOfOverlaps) */ /// !!! This version is deprecated. It should be updated to the new formalism -> ToDo !!! /// R = ratio(RadiusParticle1 on RadiusParticle2). Here, 10 R values from interpolation files (yade/extra/capillaryFiles), R = 1, 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9 //const int NB_R_VALUES = 10; // class capillarylaw1; // fait appel a la classe def plus bas class Interaction; ///This container class is used to check if meniscii overlap. Wet interactions are put in a series of lists, with one list per body. class BodiesMenisciiList1 { private: vector< list< shared_ptr > > interactionsOnBody; //shared_ptr empty; public: BodiesMenisciiList1(); BodiesMenisciiList1(Scene* body); bool prepare(Scene* scene); bool insert(const shared_ptr& interaction); bool remove(const shared_ptr& interaction); list< shared_ptr >& operator[] (int index); int size(); void display(); bool initialized; }; /// This is the constitutive law class Law2_ScGeom_CapillaryPhys_Capillarity1 : public GlobalEngine { public : void checkFusion(); static DT dtVbased; static DT dtPbased; std::vector solutions; int switchTriangulation;//to detect switches between P-based and V-based data BodiesMenisciiList1 bodiesMenisciiList; void action(); Real intEnergy(); Real swInterface(); Real wnInterface(); Real waterVolume(); void solver(Real suction, bool reset); void triangulateData(); YADE_CLASS_BASE_DOC_ATTRS_CTOR_PY(Law2_ScGeom_CapillaryPhys_Capillarity1,GlobalEngine,"This law allows one to take into account capillary forces/effects between spheres coming from the presence of interparticular liquid bridges (menisci).\n\nThe control parameter is the capillary pressure (or suction) Uc = ugas - Uliquid. Liquid bridges properties (volume V, extent over interacting grains delta1 and delta2) are computed as a result of the defined capillary pressure and of the interacting geometry (spheres radii and interparticular distance).\n\nReferences: in english [Scholtes2009b]_; more detailed, but in french [Scholtes2009d]_.\n\nThe law needs ascii files M(r=i) with i=R1/R2 to work (see https://yade-dem.org/index.php/CapillaryTriaxialTest). These ASCII files contain a set of results from the resolution of the Laplace-Young equation for different configurations of the interacting geometry." "\n\nIn order to allow capillary forces between distant spheres, it is necessary to enlarge the bounding boxes using :yref:`Bo1_Sphere_Aabb::aabbEnlargeFactor` and make the Ig2 define define distant interactions via:yref:`interactionDetectionFactor`. It is also necessary to disable interactions removal by the constitutive law (:yref:`Law2=True`). The only combinations of laws supported are currently capillary law + :yref:`Law2_ScGeom_FrictPhys_CundallStrack` and capillary law + :yref:`Law2_ScGeom_MindlinPhys_Mindlin` (and the other variants of Hertz-Mindlin).\n\nSee CapillaryPhys-example.py for an example script.", ((Real,capillaryPressure,0.,,"Value of the capillary pressure Uc defines as Uc=Ugas-Uliquid")) ((Real,totalVolumeofWater,-1.,,"Value of imposed water volume")) ((Real,liquidTension,0.073,,"Value of the superficial water tension in N/m")) ((Real,epsilonMean,0.,," Mean Value of the roughness"))//old value was epsilon ((Real,disp,0.,," Dispersion from the mean Value of the roughness"))//added now ((Real,interactionDetectionFactor,1.5,,"defines critical distance for deleting interactions. Must be consistent with the Ig2 value.")) ((bool,fusionDetection,false,,"If true potential menisci overlaps are checked")) ((bool,initialized,false,," ")) ((bool,binaryFusion,true,,"If true, capillary forces are set to zero as soon as, at least, 1 overlap (menisci fusion) is detected")) ((bool,hertzOn,false,,"|yupdate| true if hertz model is used")) ((string,inputFilename,string("capillaryfile.txt"),,"the file with meniscus solutions, used for interpolation.")) ((bool,createDistantMeniscii,false,,"Generate meniscii between distant spheres? Else only maintain the existing one. For modeling a wetting path this flag should always be false. For a drying path it should be true for one step (initialization) then false, as in the logic of [Scholtes2009c]_")) ((bool,imposePressure,true,," If True, suction is imposed and is constant if not Volume is imposed-Undrained test")) ((bool,totalVolumeConstant,true,," in undrained test there are 2 options, If True, the total volume of water is imposed,if false the volume of each meniscus is kept constant: in this case capillary pressure can be imposed for initial distribution of meniscus or it is the total volume that can be imposed initially")) ,switchTriangulation=-1, .def("intEnergy",&Law2_ScGeom_CapillaryPhys_Capillarity1::intEnergy,"define the energy of interfaces in unsaturated pendular state") // .def("sninterface",&Law2_ScGeom_CapillaryPhys_Capillarity1::sninterface,"define the amount of solid-non-wetting interfaces in unsaturated pendular state") .def("swInterface",&Law2_ScGeom_CapillaryPhys_Capillarity1::swInterface,"define the amount of solid-wetting interfaces in unsaturated pendular state") .def("wnInterface",&Law2_ScGeom_CapillaryPhys_Capillarity1::wnInterface,"define the amount of wetting-non-wetiing interfaces in unsaturated pendular state") .def("waterVolume",&Law2_ScGeom_CapillaryPhys_Capillarity1::waterVolume,"return the total value of water in the sample") ); }; REGISTER_SERIALIZABLE(Law2_ScGeom_CapillaryPhys_Capillarity1); trunk-2018.02b/pkg/dem/Lubrication.cpp000066400000000000000000000304761324306050200175050ustar00rootroot00000000000000 #include "Lubrication.hpp" YADE_PLUGIN((Ip2_ElastMat_ElastMat_LubricationPhys)(LubricationPhys)(Law2_ScGeom_LubricationPhys)(Law2_ScGeom_ImplicitLubricationPhys)) LubricationPhys::LubricationPhys(const ViscElPhys &obj) : ViscElPhys(obj), eta(1.), eps(0.001), kno(0.), kso(0.), nun(0.), phic(0.), ue(0.), contact(false), slip(false), NormalForce(Vector3r::Zero()), TangentForce(Vector3r::Zero()) { } LubricationPhys::~LubricationPhys() { } CREATE_LOGGER(LubricationPhys); void Ip2_ElastMat_ElastMat_LubricationPhys::go(const shared_ptr &material1, const shared_ptr &material2, const shared_ptr &interaction) { if (interaction->phys) return; // Cast to Lubrication shared_ptr phys(new LubricationPhys()); FrictMat* mat1 = YADE_CAST(material1.get()); FrictMat* mat2 = YADE_CAST(material2.get()); if(otherPhysFunctor) { otherPhysFunctor->go(material1,material2,interaction); phys->otherPhys = interaction->phys; } // Copy from HertzMindlin /* from interaction physics */ Real Ea = mat1->young; Real Eb = mat2->young; Real Va = mat1->poisson; Real Vb = mat2->poisson; Real fa = mat1->frictionAngle; Real fb = mat2->frictionAngle; /* from interaction geometry */ GenericSpheresContact* scg = YADE_CAST(interaction->geom.get()); Real Da = scg->refR1>0 ? scg->refR1 : scg->refR2; Real Db = scg->refR2; //Vector3r normal=scg->normal; //The variable set but not used /* calculate stiffness coefficients */ Real Ga = Ea/(2*(1+Va)); Real Gb = Eb/(2*(1+Vb)); Real G = (Ga+Gb)/2; // average of shear modulus Real V = (Va+Vb)/2; // average of poisson's ratio Real E = Ea*Eb/((1.-std::pow(Va,2))*Eb+(1.-std::pow(Vb,2))*Ea); // Young modulus Real R = Da*Db/(Da+Db); // equivalent radius Real a = (Da+Db)/2.; Real Kno = 4./3.*E*sqrt(R); // coefficient for normal stiffness Real Kso = 2*sqrt(4*R)*G/(2-V); // coefficient for shear stiffness Real frictionAngle = std::min(fa,fb); const Real pi(3.141596); phys->kno = Kno; phys->kso = Kso; phys->phic = frictionAngle; phys->nun = pi*eta*3./2.*a*a; // Get bodies properties Body::id_t id1 = interaction->getId1(); Body::id_t id2 = interaction->getId2(); const shared_ptr b1 = Body::byId(id1,scene); const shared_ptr b2 = Body::byId(id2,scene); State* s1 = b1->state.get(); State* s2 = b2->state.get(); phys->delta = std::log((s1->se3.position-s2->se3.position).norm()-2.*a); phys->eta = eta; phys->eps = eps; interaction->phys = phys; } CREATE_LOGGER(Ip2_ElastMat_ElastMat_LubricationPhys); bool Law2_ScGeom_LubricationPhys::go(shared_ptr &iGeom, shared_ptr &iPhys, Interaction *interaction) { // Physic LubricationPhys* phys=static_cast(iPhys.get()); otherLawFunctor->scene = scene; // If not activated, only compute other law /*if(!activateLubrication) return (otherLawFunctor) ? otherLawFunctor->go(iGeom,phys->otherPhys,interaction) : false;//*/ // Geometry ScGeom* geom=static_cast(iGeom.get()); Vector3r norm = geom->normal/geom->normal.norm(); // Get bodies properties Body::id_t id1 = interaction->getId1(); Body::id_t id2 = interaction->getId2(); const shared_ptr b1 = Body::byId(id1,scene); const shared_ptr b2 = Body::byId(id2,scene); State* s1 = b1->state.get(); State* s2 = b2->state.get(); // geometric parameters Real a((geom->radius1+geom->radius2)/2.); Real h((s1->se3.position-s2->se3.position).norm()-2.*a); const Real pi(3.141596); // Speeds Vector3r normVel((s2->vel-s1->vel).dot(norm)*norm); Vector3r normRot((s2->vel-s1->vel).cross(norm)/(2.*a+h)); Vector3r shearVel((geom->radius1*(s1->angVel-normRot)+geom->radius2*(s2->angVel-normRot)).cross(norm)); // Forces and torques Vector3r Fln = Vector3r::Zero(); if(activateNormalLubrication) Fln = pi*phys->eta*3./2.*a*a/h*normVel; Vector3r FLs = Vector3r::Zero(); if(activateTangencialLubrication) FLs = pi*phys->eta/2.*(-2.*a+(2.*a+h)*std::log((2.*a+h)/h))*shearVel; Vector3r Cr = Vector3r::Zero(); if(activateRollLubrication) Cr = pi*phys->eta*a*a*a*(3./2.*std::log(a/h)+63./500.*h/a*std::log(a/h))*((s1->angVel-s2->angVel)-((s1->angVel-s2->angVel).dot(norm))*norm); Vector3r Ct = Vector3r::Zero(); if(activateTwistLubrication) Ct = pi*phys->eta*a*h*std::log(a/h)*(s1->angVel-s2->angVel).dot(norm)*norm; // total torque Vector3r C1 = (geom->radius1+h/2.)*FLs.cross(norm)+Cr+Ct; Vector3r C2 = (geom->radius2+h/2.)*FLs.cross(norm)-Cr-Ct; //cout << "FL (" << Fln << ") FLs (" << FLs << ") Cr ("<< Cr << ") Ct (" << Ct << ")\n"; // Apply! scene->forces.addForce(id1,Fln+FLs); scene->forces.addTorque(id1,C1); scene->forces.addForce(id2,-Fln-FLs); scene->forces.addTorque(id2,C2); // Compute other law if(otherLawFunctor) otherLawFunctor->go(iGeom, phys->otherPhys,interaction); NormShearPhys* ophys=YADE_CAST(phys->otherPhys.get()); if(ophys) { phys->kn = ophys->kn; phys->ks = ophys->ks; } ViscElPhys* ophysel=YADE_CAST(phys->otherPhys.get()); if(ophysel) { phys->cn += ophysel->cn; phys->cs += ophysel->cs; } return true; } CREATE_LOGGER(Law2_ScGeom_LubricationPhys); bool Law2_ScGeom_ImplicitLubricationPhys::go(shared_ptr &iGeom, shared_ptr &iPhys, Interaction *interaction) { // Physic LubricationPhys* phys=static_cast(iPhys.get()); // Geometry ScGeom* geom=static_cast(iGeom.get()); Vector3r norm = geom->normal/geom->normal.norm(); // Get bodies properties Body::id_t id1 = interaction->getId1(); Body::id_t id2 = interaction->getId2(); const shared_ptr b1 = Body::byId(id1,scene); const shared_ptr b2 = Body::byId(id2,scene); State* s1 = b1->state.get(); State* s2 = b2->state.get(); // geometric parameters Real a((geom->radius1+geom->radius2)/2.); Real u((s1->se3.position-s2->se3.position).norm()-2.*a); const Real pi(3.141596); if(u > a) return false; // Speeds Vector3r shiftVel=scene->isPeriodic ? Vector3r(scene->cell->velGrad*scene->cell->hSize*interaction->cellDist.cast()) : Vector3r::Zero(); Vector3r shift2 = scene->isPeriodic ? Vector3r(scene->cell->hSize*interaction->cellDist.cast()): Vector3r::Zero(); Vector3r relV = geom->getIncidentVel(s1, s2, scene->dt, shift2, shiftVel, false ); Vector3r relVN = relV.dot(norm)*norm; // Normal velocity Vector3r relVT = relV - relVN; // Tangeancial velocity Real udot = relV.dot(norm); // Normal velocity norm // Normal force Vector3r Fn(Vector3r::Zero()); Real ue(0.),D(0.),un(0.); Real delt = max(std::abs(phys->ue),a/100.); //Real delt = std::abs(phys->ue); //delt = a/100.; Real g = 3./2.*phys->kno*std::pow(delt,0.5); // Stiffness for normal surface deflection bool contact = (u - phys->ue) < phys->eps*a; if(solution == 2) contact = (u-(1+phys->eps)*phys->ue) < phys->eps*a; // Log resolution if(solution == 4) contact = std::exp(phys->delta) < phys->eps*a; Real kn = (contact) ? g : 0.; if(contact && !phys->contact) LOG_INFO("CONTACT"); if(!contact && phys->contact) LOG_INFO("END OF CONTACT"); if(activateNormalLubrication) { Real G = g/phys->nun; Real K = kn/phys->nun; Real EPS = phys->eps*a; // Full implicit resolution if(solution == 1 || solution == 2) { Real A = G + K; Real B = -1./scene->dt - G*u - 2.*K*u + K*EPS; Real C = phys->ue/scene->dt + udot + K*u*u - K*u*EPS; if(solution == 2) { A = -G-(1+phys->eps)*K; B = G*u-K*(EPS-u-u*(1+phys->eps)) - 1/scene->dt; C = phys->ue/scene->dt - udot - K*u*(u-EPS); } Real rho = B*B-4.*A*C; Real u1 = (-B+std::sqrt(rho))/(2.*A); Real u2 = (-B-std::sqrt(rho))/(2.*A); // FIXME: Very ugly... if(rho >= 0) { ue = (std::abs(phys->ue - u1) < std::abs(phys->ue - u2)) ? u1 : u2; //ue2 = (ue == u1) ? u2 : u1; //phys->due = ue - phys->ue; } else { //LOG_DEBUG("Unable to find a solution: rho < 0"); ue = phys->ue;// + phys->due; } } // Semi-implicit resolution if(solution == 3) { ue = (K*(u-phys->ue)*(u-EPS)+udot+phys->ue/scene->dt)/((G+K)*(u-phys->ue)+1/scene->dt); // ue2 = 0.; } un = u - ue; //Implicit log-resolution if(solution == 4) { // Function to be zero #define fx(d,d_,u,g,k,eps,dt) (std::exp(2.*d)*(g+k)+std::exp(d)*(1./dt-g*u-k*eps)-std::exp(d_)/dt) #define F(d) fx((d),phys->delta,u,G,K,EPS,scene->dt) // Expression of F(d)/(dF/dd)(d) #define fdf(d,d_,u,g,k,eps,dt) ((std::exp(d)*(g+k)+(1.-std::exp(d_-d))/dt-g*u-k*eps)/(2.*std::exp(d)*(g+k)+1./dt-g*u-k*eps)) #define FdF(d) fdf((d),phys->delta,u,G,K,EPS,scene->dt) if(debug) LOG_DEBUG("G K EPS dt u d_ " << G << " " << K << " " << EPS << " " << scene->dt << " " << u << " " << phys->delta); Real d = phys->delta; for(int i(0);i<20;i++) { //Real dd = 0.005; //Real df = (F(d+dd) - F(d-dd))/(2.*dd); //d = d - F(d)/df; d = d - FdF(d); if(std::abs(F(d)) < 1e-10) break; if(debug) LOG_DEBUG("d F(d) F/(dF/dd) " << d << " " << F(d) << " " << FdF(d)); if(i == 19) LOG_DEBUG("Max Newton-Rafson steps reach"); } ue = u - std::exp(d); un = std::exp(d); D = d; } // calc dtm //phys->dtm = (-2.*phys->ue)/(-3.*(G+K)*phys->ue*phys->ue+udot+K*u*(u-EPS)-(-G*u+K*(EPS-2.*u))*phys->ue); //calculate the force Fn = ue*g*norm; } // Tangencial force Vector3r Ft(Vector3r::Zero()); if(activateTangencialLubrication) { Vector3r Ft_ = geom->rotate(phys->TangentForce); Real kt = phys->kso*std::pow(delt,0.5); Real nut = pi*phys->eta/2.*(-2.*a+(2.*a+un)*std::log((2.*a+un)/un)); if(solution == 4) { nut = pi*phys->eta/2.*(-2.*a+(2.*a+un)*(std::log(2.*a+un)-D)); } phys->slip = false; //LOG_INFO("nut: " << nut); if(contact) { Ft = Ft_ - scene->dt*kt*relVT; Real Feps = std::abs(-kn*(u-ue-phys->eps*a)); if(Ft.norm() > Feps*std::tan(phys->phic)) // If slip { //LOG_INFO("SLIP"); Ft = (Ft_ - scene->dt*kt*relVT*(1.+Feps*std::tan(phys->phic)/relVT.norm()))/(1.+kt/nut*scene->dt); phys->slip = true; } } else { Ft = (Ft_ - scene->dt*kt*relVT)/(1.+kt/nut*scene->dt); } } // Update Physics memory phys->NormalForce = Fn; phys->ue = ue; phys->delta = D; phys->TangentForce = Ft; phys->contact = contact; // Rolling and twist torques Vector3r relAngularVelocity = geom->getRelAngVel(s1,s2,scene->dt); Vector3r relTwistVelocity = relAngularVelocity.dot(norm)*norm; Vector3r relRollVelocity = relAngularVelocity - relTwistVelocity; Vector3r Cr = Vector3r::Zero(); if(activateRollLubrication) Cr = pi*phys->eta*a*a*a*(3./2.*std::log(a/u)+63./500.*u/a*std::log(a/u))*relRollVelocity; Vector3r Ct = Vector3r::Zero(); if (activateTwistLubrication) Ct = pi*phys->eta*a*u*std::log(a/u)*relTwistVelocity; // total torque Vector3r C1 = (geom->radius1+u/2.)*Ft.cross(norm)+Cr+Ct; Vector3r C2 = (geom->radius2+u/2.)*Ft.cross(norm)-Cr-Ct; // Apply! scene->forces.addForce(id1,Fn+Ft); scene->forces.addTorque(id1,C1); scene->forces.addForce(id2,-Fn-Ft); scene->forces.addTorque(id2,C2); return true; } CREATE_LOGGER(Law2_ScGeom_ImplicitLubricationPhys); trunk-2018.02b/pkg/dem/Lubrication.hpp000066400000000000000000000205521324306050200175040ustar00rootroot00000000000000// 2017 © William Chèvremont #pragma once #include #include #include #include #include #include #include #include #include #include namespace py=boost::python; class LubricationPhys: public ViscElPhys { public: LubricationPhys(ViscElPhys const& ); // "copy" constructor virtual ~LubricationPhys(); YADE_CLASS_BASE_DOC_ATTRS_CTOR_PY(LubricationPhys,ViscElPhys,"IPhys class for Lubrication w/o FlowEngine. Used by Law2_ScGeom_LubricationPhys.", ((Real,eta,1,,"Fluid viscosity [Pa.s]")) ((Real,eps,0.001,,"Rugosity: fraction of radius used as rugosity [-]")) ((Real,kno,0.0,,"Coeficient for normal stiffness")) ((Real,kso,0.0,,"Coeficient for tangeancial stiffness")) ((Real,nun,0.0,,"Normal viscosity coefficient")) ((Real,phic,0.0,,"Critical friction angle [-]")) ((Real,ue,0.,,"Surface deflection at t-dt [m]")) // ((Real,ue2,0.,,"Rejected solution of ue")) // ((Real,due,0.,,"Last increment of ue")) // ((Real,dtm,0.,,"dt optim.")) ((Real,delta,0,,"exponantial solution")) ((bool,contact,false,,"Spheres in contact")) ((bool,slip,false,,"Slip condition")) ((Vector3r,NormalForce,Vector3r::Zero(),,"Normal force computed at t-dt (Used only by Law2_ScGeom_ImplicitLubricationPhys) [N]")) ((Vector3r,TangentForce,Vector3r::Zero(),,"Tangeancial force computed at t-dt (Used only by Law2_ScGeom_ImplicitLubricationPhys) [N]")) ((shared_ptr,otherPhys,0,,"Other physics combined (used only by Law2_ScGeom_LubricationPhys)")) , // ctors createIndex();, .def_readonly("eta",&LubricationPhys::eta,"Fluid viscosity [Pa.s]") .def_readonly("eps",&LubricationPhys::eps,"Rugosity [-]") .def_readonly("NormalForce",&LubricationPhys::NormalForce,"Normal componant of the force [N]") .def_readonly("TangentForce",&LubricationPhys::TangentForce,"Shear compoannt of the force [N]") .def_readonly("ue",&LubricationPhys::ue,"Surface deflection [m]") // .def_readonly("ue2",&LubricationPhys::ue2,"Rejected solution of ue [m]") .def_readonly("contact",&LubricationPhys::contact,"Spheres in contact") .def_readonly("slip",&LubricationPhys::slip,"Slip contact") // .def_readonly("dtm",&LubricationPhys::dtm,"Dtm") ); DECLARE_LOGGER; REGISTER_CLASS_INDEX(LubricationPhys,ViscElPhys); }; REGISTER_SERIALIZABLE(LubricationPhys); class Ip2_ElastMat_ElastMat_LubricationPhys: public IPhysFunctor{ public: virtual void go(const shared_ptr& material1, const shared_ptr& material2, const shared_ptr& interaction); FUNCTOR2D(ElastMat,ElastMat); DECLARE_LOGGER; YADE_CLASS_BASE_DOC_ATTRS_CTOR_PY(Ip2_ElastMat_ElastMat_LubricationPhys,IPhysFunctor,"Ip2 creating LubricationPhys from two Material instances.", ((Real,eta,1,,"Fluid viscosity [Pa.s]")) ((Real,eps,0.001,,"Rugosity: fraction of radius used as rugosity")) ((shared_ptr, otherPhysFunctor,0,,"Other physics to combine with lubrication. (used only by Law2_ScGeom_LubricationPhys)")) ,, .def_readwrite("otherPhys",&Ip2_ElastMat_ElastMat_LubricationPhys::otherPhysFunctor,"Other physics to combine with lubrication (used only by Law2_ScGeom_LubricationPhys)") ); }; REGISTER_SERIALIZABLE(Ip2_ElastMat_ElastMat_LubricationPhys); class Law2_ScGeom_LubricationPhys: public LawFunctor{ public: bool go(shared_ptr& iGeom, shared_ptr& iPhys, Interaction* interaction); FUNCTOR2D(GenericSpheresContact,LubricationPhys); YADE_CLASS_BASE_DOC_ATTRS_CTOR_PY(Law2_ScGeom_LubricationPhys,LawFunctor,"Material law for lubrication between two spheres.", //((bool,activateLubrication,false,,"Activate lubrication (default: false)")) ((shared_ptr, otherLawFunctor,0,,"Other interaction law to combine with lubrication (used only by Law2_ScGeom_LubricationPhys)")) ((bool,activateNormalLubrication,true,,"Activate normal lubrication (default: true)")) ((bool,activateTangencialLubrication,true,,"Activate tangencial lubrication (default: true)")) ((bool,activateTwistLubrication,true,,"Activate twist lubrication (default: true)")) ((bool,activateRollLubrication,true,,"Activate roll lubrication (default: true)")) ,, //.def_readwrite("activateLubrication",&Law2_ScGeom_LubricationPhys::activateLubrication,"Activate lubrication (default: false)") .def_readwrite("otherLaw",&Law2_ScGeom_LubricationPhys::otherLawFunctor,"Other interaction law to combine with lubrication (used only by Law2_ScGeom_LubricationPhys)") .def_readwrite("activateNormalLubrication",&Law2_ScGeom_LubricationPhys::activateNormalLubrication,"Activate normal lubrication (default: true)") .def_readwrite("activateTangeancialLubrication",&Law2_ScGeom_LubricationPhys::activateTangencialLubrication,"Activate tangencial lubrication (default: true)") .def_readwrite("activateTwistLubrication",&Law2_ScGeom_LubricationPhys::activateTwistLubrication,"Activate twist lubrication (default: true)") .def_readwrite("activateRollLubrication",&Law2_ScGeom_LubricationPhys::activateRollLubrication,"Activate roll lubrication (default: true)") ); DECLARE_LOGGER; }; REGISTER_SERIALIZABLE(Law2_ScGeom_LubricationPhys); class Law2_ScGeom_ImplicitLubricationPhys: public LawFunctor{ public: bool go(shared_ptr& iGeom, shared_ptr& iPhys, Interaction* interaction); FUNCTOR2D(GenericSpheresContact,LubricationPhys); YADE_CLASS_BASE_DOC_ATTRS_CTOR_PY(Law2_ScGeom_ImplicitLubricationPhys, LawFunctor, "Material law for lubrication and contact between two spheres, resolved implicitly.", // ATTR ((bool,activateNormalLubrication,true,,"Activate normal lubrication (default: true)")) ((bool,activateTangencialLubrication,true,,"Activate tangencial lubrication (default: true)")) ((bool,activateTwistLubrication,true,,"Activate twist lubrication (default: true)")) ((bool,activateRollLubrication,true,,"Activate roll lubrication (default: true)")) ((int,solution,4,,"Resolution method (default: 4)")) ((bool,debug,false,,"Write debug informations")) ,// CTOR ,// PY .def_readwrite("activateNormalLubrication",&Law2_ScGeom_ImplicitLubricationPhys::activateNormalLubrication,"Activate normal lubrication (default: true)") .def_readwrite("activateTangencialLubrication",&Law2_ScGeom_ImplicitLubricationPhys::activateTangencialLubrication,"Activate tangencial lubrication (default: true)") .def_readwrite("activateTwistLubrication",&Law2_ScGeom_ImplicitLubricationPhys::activateTwistLubrication,"Activate twist lubrication (default: true)") .def_readwrite("activateRollLubrication",&Law2_ScGeom_ImplicitLubricationPhys::activateRollLubrication,"Activate roll lubrication (default: true)") .def_readwrite("solution",&Law2_ScGeom_ImplicitLubricationPhys::solution,"Choose resolution method") ); DECLARE_LOGGER; }; REGISTER_SERIALIZABLE(Law2_ScGeom_ImplicitLubricationPhys); trunk-2018.02b/pkg/dem/LudingPM.cpp000066400000000000000000000171571324306050200167120ustar00rootroot00000000000000#include"LudingPM.hpp" #include #include #include #include #include YADE_PLUGIN((LudingMat)(LudingPhys)(Ip2_LudingMat_LudingMat_LudingPhys)(Law2_ScGeom_LudingPhys_Basic)); LudingMat::~LudingMat(){} LudingPhys::~LudingPhys(){} void Ip2_LudingMat_LudingMat_LudingPhys::go(const shared_ptr& b1, const shared_ptr& b2, const shared_ptr& interaction) { if(interaction->phys) return; LudingMat* mat1 = static_cast(b1.get()); LudingMat* mat2 = static_cast(b2.get()); const Real k11 = mat1->k1; const Real k12 = mat2->k1; const Real kp1 = mat1->kp; const Real kp2 = mat2->kp; const Real kc1 = mat1->kc; const Real kc2 = mat2->kc; const Real ks1 = mat1->ks; const Real ks2 = mat2->ks; const Real G01 = mat1->G0; const Real G02 = mat2->G0; const Real PhiF1 = mat1->PhiF; const Real PhiF2 = mat2->PhiF; LudingPhys* phys = new LudingPhys(); phys->k1 = this->reduced(k11, k12); phys->kp = this->reduced(kp1, kp2); phys->kc = this->reduced(kc1, kc2); phys->ks = this->reduced(ks1, ks2); phys->PhiF = this->reduced(PhiF1, PhiF2); phys->k2 = 0.0; phys->G0 = this->reduced(G01, G02); Real a1 = 0.0; Real a2 = 0.0; Sphere* s1=dynamic_cast(Body::byId(interaction->getId1())->shape.get()); Sphere* s2=dynamic_cast(Body::byId(interaction->getId2())->shape.get()); Real a1dR = 0.; Real a2dR = 0.; #ifdef YADE_DEFORM State* de1 = dynamic_cast(Body::byId(interaction->getId1())->state.get()); State* de2 = dynamic_cast(Body::byId(interaction->getId2())->state.get()); if (de1 and de2) { a1dR = de1->dR ; a2dR = de2->dR ; } else if (de1 and not(de2)) { a1dR = de1->dR ; } else { a2dR = de2->dR ; } #endif if (s1 and s2) { a1 = s1->radius + a1dR; a2 = s2->radius + a2dR; } else if (s1 and not(s2)) { a1 = s1->radius + a1dR; } else { a2 = s2->radius + a2dR; } if (phys->k1 >= phys->kp) { throw runtime_error("k1 have to be less as kp!"); // [Luding2008], sentence after equation (6); kp = k2^ // [Singh2013], sentence after equation (6) } phys->tangensOfFrictionAngle = std::tan(std::min(mat1->frictionAngle, mat2->frictionAngle)); phys->shearForce = Vector3r(0,0,0); phys->DeltMax = 0.0; phys->DeltNull = 0.0; phys->DeltPMax = phys->kp/(phys->kp-phys->k1)*phys->PhiF*2*a1*a2/(a1+a2); // [Luding2008], equation (7) // [Singh2013], equation (11) phys->DeltPNull = phys->PhiF*2*a1*a2/(a1+a2); // [Singh2013], equation (12) phys->DeltPrev = 0.0; phys->DeltMin = 0.0; interaction->phys = shared_ptr(phys); } Real Ip2_LudingMat_LudingMat_LudingPhys::reduced(Real a1, Real a2){ Real a = (a1?1/a1:0) + (a2?1/a2:0); a = a?1/a:0; return 2.0*a; } bool Law2_ScGeom_LudingPhys_Basic::go(shared_ptr& _geom, shared_ptr& _phys, Interaction* I){ const ScGeom& geom=*static_cast(_geom.get()); LudingPhys& phys=*static_cast(_phys.get()); const int id1 = I->getId1(); const int id2 = I->getId2(); const BodyContainer& bodies = *scene->bodies; const State& de1 = *static_cast(bodies[id1]->state.get()); const State& de2 = *static_cast(bodies[id2]->state.get()); Real addDR = 0. ; #ifdef YADE_DEFORM addDR = de1.dR + de2.dR; #endif const Real Delt = geom.penetrationDepth + addDR; if (Delt < 0 ) return false; Real forceHys = 0.0; if (phys.DeltMax/phys.DeltPMax >= 1.0) { // [Luding2008], equation (8) phys.k2 = phys.kp; // [Singh2013], equation (10) } else { phys.k2 = phys.k1 + (phys.kp - phys.k1)*phys.DeltMax/phys.DeltPMax; } if (phys.k2>phys.kp) { phys.k2 = phys.kp; } if (phys.k1>phys.k2) { phys.k1 = phys.k2; } phys.DeltMin = (phys.k2- phys.k1)/(phys.k2 + phys.kc); if (Delt > phys.DeltMax) { phys.DeltMax = Delt; phys.DeltNull = std::min((1.0 - phys.k1/phys.k2)*phys.DeltMax, phys.DeltPNull); // [Luding2008], equation over Fig 1 // [Singh2013], equation (8) } Real k2DeltTtmp = phys.k2*(Delt - phys.DeltNull); // [Luding2008], equation (6) // [Singh2013], equation (6) if ( k2DeltTtmp >= phys.k1*Delt) { if (Delt -phys.kc*Delt and k2DeltTtmp < phys.k1*Delt) { forceHys = k2DeltTtmp; } else if (k2DeltTtmp<=-phys.kc*Delt) { if ((Delt - phys.DeltPrev) < 0) { forceHys = -phys.kc*Delt; phys.DeltMax = Delt*(phys.k2 + phys.kc)/(phys.k2 - phys.k1); // [Singh2013], equation (9) phys.DeltNull = std::min((1.0 - phys.k1/phys.k2)*phys.DeltMax, phys.DeltPNull); // [Luding2008], equation over Fig 1 // [Singh2013], equation (8) } else { forceHys = k2DeltTtmp; } } phys.DeltPrev = Delt; //=================================================================== //=================================================================== //=================================================================== // Copy-paste from ViscoElasticPM Vector3r& shearForce = phys.shearForce; if (I->isFresh(scene)) shearForce=Vector3r(0,0,0); const Real& dt = scene->dt; shearForce = geom.rotate(shearForce); // Handle periodicity. const Vector3r shift2 = scene->isPeriodic ? scene->cell->intrShiftPos(I->cellDist): Vector3r::Zero(); const Vector3r shiftVel = scene->isPeriodic ? scene->cell->intrShiftVel(I->cellDist): Vector3r::Zero(); const Vector3r c1x = (geom.contactPoint - de1.pos); const Vector3r c2x = (geom.contactPoint - de2.pos - shift2); const Vector3r relativeVelocity = (de1.vel+de1.angVel.cross(c1x)) - (de2.vel+de2.angVel.cross(c2x)) + shiftVel; const Real normalVelocity = geom.normal.dot(relativeVelocity); const Vector3r shearVelocity = relativeVelocity-normalVelocity*geom.normal; shearForce += phys.ks*dt*shearVelocity; // the elastic shear force have a history, but Vector3r shearForceVisc = Vector3r::Zero(); // the viscous shear damping haven't a history because it is a function of the instant velocity phys.normalForce = (forceHys + phys.G0 * normalVelocity)*geom.normal; const Real maxFs = phys.normalForce.squaredNorm() * std::pow(phys.tangensOfFrictionAngle,2); if( shearForce.squaredNorm() > maxFs ) { const Real ratio = sqrt(maxFs) / shearForce.norm(); shearForce *= ratio; } else { // shearForceVisc = phys.cs*shearVelocity; //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ shearForceVisc = phys.G0*shearVelocity; } //=================================================================== //=================================================================== //=================================================================== if (I->isActive) { const Vector3r f = phys.normalForce + shearForce + shearForceVisc; addForce (id1,-f,scene); addForce (id2, f,scene); addTorque(id1,-c1x.cross(f),scene); addTorque(id2, c2x.cross(f),scene); } return true; } trunk-2018.02b/pkg/dem/LudingPM.hpp000066400000000000000000000056071324306050200167140ustar00rootroot00000000000000#pragma once #include #include #include #include class LudingMat : public Material { public: virtual ~LudingMat(); YADE_CLASS_BASE_DOC_ATTRS_CTOR(LudingMat,Material,"Material for simple Luding`s model of contact [Luding2008]_ ,[Singh2013]_ .\n", ((Real,k1,NaN,,"Slope of loading plastic branch")) ((Real,kp,NaN,,"Slope of unloading and reloading limit elastic branch")) ((Real,kc,NaN,,"Slope of irreversible, tensile adhesive branch")) ((Real,ks,NaN,,"Shear stiffness")) ((Real,PhiF,NaN,,"Dimensionless plasticity depth")) ((Real,G0,NaN,,"Viscous damping")) ((Real,frictionAngle,NaN,,"Friction angle [rad]")), createIndex(); ); REGISTER_CLASS_INDEX(LudingMat,Material); }; REGISTER_SERIALIZABLE(LudingMat); class LudingPhys : public FrictPhys{ public: virtual ~LudingPhys(); Real R; YADE_CLASS_BASE_DOC_ATTRS_CTOR(LudingPhys,FrictPhys,"IPhys created from :yref:`LudingMat`, for use with :yref:`Law2_ScGeom_LudingPhys_Basic`.", ((Real,k1,NaN,,"Slope of loading plastic branch")) ((Real,k2,NaN,,"Slope of unloading and reloading elastic branch")) ((Real,kp,NaN,,"Slope of unloading and reloading limit elastic branch")) ((Real,kc,NaN,,"Slope of irreversible, tensile adhesive branch")) ((Real,PhiF,NaN,,"Dimensionless plasticity depth")) ((Real,DeltMin,NaN,,"MinimalDelta value of delta")) ((Real,DeltMax,NaN,,"Maximum overlap between particles for a collision")) ((Real,DeltPMax,NaN,,"Maximum overlap between particles for the limit case")) ((Real,DeltNull,NaN,,"Force free overlap, plastic contact deformation")) ((Real,DeltPNull,NaN,,"Max force free overlap, plastic contact deformation")) ((Real,DeltPrev,NaN,,"Previous value of delta")) ((Real,G0,NaN,,"Viscous damping")), createIndex(); ) }; REGISTER_SERIALIZABLE(LudingPhys); class Ip2_LudingMat_LudingMat_LudingPhys: public IPhysFunctor { public : virtual void go(const shared_ptr& b1, const shared_ptr& b2, const shared_ptr& interaction); YADE_CLASS_BASE_DOC(Ip2_LudingMat_LudingMat_LudingPhys,IPhysFunctor,"Convert 2 instances of :yref:`LudingMat` to :yref:`LudingPhys` using the rule of consecutive connection."); FUNCTOR2D(LudingMat,LudingMat); private: Real reduced(Real, Real); }; REGISTER_SERIALIZABLE(Ip2_LudingMat_LudingMat_LudingPhys); class Law2_ScGeom_LudingPhys_Basic: public LawFunctor { public : virtual bool go(shared_ptr&, shared_ptr&, Interaction*); private: Real calculateCapillarForce(const ScGeom& geom, LudingPhys& phys); FUNCTOR2D(ScGeom,LudingPhys); YADE_CLASS_BASE_DOC(Law2_ScGeom_LudingPhys_Basic,LawFunctor,"Linear viscoelastic model operating on :yref:`ScGeom` and :yref:`LudingPhys`. See [Luding2008]_ ,[Singh2013]_ for more details."); }; REGISTER_SERIALIZABLE(Law2_ScGeom_LudingPhys_Basic); trunk-2018.02b/pkg/dem/MeasureCapStress.cpp000066400000000000000000000135711324306050200204600ustar00rootroot00000000000000#include #include #include #include #include #include // for direct use of aabbExtrema YADE_PLUGIN((MeasureCapStress)); void MeasureCapStress::action() { shared_ptr& bodies = scene->bodies; Matrix3r matAglob = Matrix3r::Zero(); Matrix3r matBglob = Matrix3r::Zero(); Matrix3r matLGInt = Matrix3r::Zero(); vLiq= 0; // was the case at the creation of the Engine, but has to be reset at each execution... FOREACH(const shared_ptr& interaction, *scene->interactions){ // not possible or meaningfull to use parallel loops here.. (http://www.mail-archive.com/yade-dev@lists.launchpad.net/msg11018.html) if ( !interaction->isReal()) continue; const shared_ptr phys = YADE_PTR_CAST(interaction->phys); const shared_ptr geom = YADE_PTR_CAST(interaction->geom); if (phys->meniscus) { vLiq += phys->vMeniscus; Body* b1 = (*bodies)[interaction->getId1()].get(); Body* b2 = (*bodies)[interaction->getId2()].get(); Real rB1 = YADE_PTR_DYN_CAST( b1->shape )->radius; Real rB2 = YADE_PTR_DYN_CAST( b2->shape )->radius; Real deltaB1,deltaB2; Vector3r vecN = geom->normal; // from body1 to body2 Vector3r vecSmallToBig = Vector3r::Zero(); // is this one useful ??? if(rB1 > rB2){ // body1 is the biggest, i.e. the one with Delta2 deltaB1 = phys->Delta2; deltaB2 = phys->Delta1; vecSmallToBig = - vecN; } else { deltaB1 = phys->Delta1; deltaB2 = phys->Delta2; vecSmallToBig = vecN; } // Body 1 consideration: vecN = z axis with respect to Fig 3.18 [Khosravani2014] matAglob += matA_BodyGlob( deltaB1 * Mathr::DEG_TO_RAD,rB1,vecN); matBglob += matBp_BodyGlob( deltaB1 * Mathr::DEG_TO_RAD,wettAngle,surfaceTension,rB1,vecN); // Body 2 consideration: vecN = - z axis with respect to Fig 3.18 [Khosravani2014] matAglob += matA_BodyGlob( deltaB2 * Mathr::DEG_TO_RAD , rB2 , -vecN); matBglob += matBp_BodyGlob(deltaB2 * Mathr::DEG_TO_RAD,wettAngle,surfaceTension,rB2,-vecN); // liq-gas interface term matLGInt += matLG_bridgeGlob(phys->nn11,phys->nn33,surfaceTension,vecSmallToBig); } } Real volume = 0; if (scene->isPeriodic) volume = scene->cell->hSize.determinant(); else { boost::python::tuple extrema = Shop::aabbExtrema(); volume = boost::python::extract( (extrema[1][0] - extrema[0][0])*(extrema[1][1] - extrema[0][1])*(extrema[1][2] - extrema[0][2]) ); } if (debug) cout << "c++ : volume = " << volume << endl; // if (volume ==0) LOG_ERROR("Could not get a non-zero volume value"); // else { // error: ‘else’ without a previous ‘if’ ?????????!!!!!########## capStrTens1 = vLiq * Matrix3r::Identity() * capillaryPressure / volume ; capStrTens2 = matAglob * capillaryPressure / volume; capStrTens3 = matLGInt / volume; capStrTens4 = matBglob / volume; // } capStrTens = capStrTens1 + capStrTens2 + capStrTens3 + capStrTens4; } Matrix3r MeasureCapStress::matA_BodyGlob(Real alpha,Real radius,Vector3r vecN){ Matrix3r A_BodyGlob ; A_BodyGlob << pow( 1 - cos(alpha),2) * (2 + cos(alpha)) , 0 , 0 , 0 , pow( 1 - cos(alpha),2) * (2 + cos(alpha)) , 0 , 0 , 0 , 2 * ( 1-pow(cos(alpha),3) ); A_BodyGlob *= Mathr::PI * pow(radius,3.0)/3.0; Matrix3r globToLocRet = matGlobToLoc(vecN); return globToLocRet * A_BodyGlob * globToLocRet.transpose() ; } Matrix3r MeasureCapStress::matLG_bridgeGlob(Real nn11,Real nn33,Real surfTens, Vector3r vecN){ Matrix3r LG_bridgeGlob ; LG_bridgeGlob << nn11 + nn33 , 0 , 0 , // useless to write lgArea - nn11 = 2*nn11 + nn33 - nn11 0 , nn11 + nn33 , 0 , 0 , 0 , 2*nn11; // trace = 2*(2*nn11 + nn33) = 2*lgArea LG_bridgeGlob *= surfTens; Matrix3r globToLocRet = matGlobToLoc(vecN); return globToLocRet * LG_bridgeGlob * globToLocRet.transpose() ; } Matrix3r MeasureCapStress::matBp_BodyGlob(Real alpha,Real wettAngle, Real surfTens, Real radius,Vector3r vecN){ // matrix B prime, defined at body scale (see (3.49) p.65), expressed in global framework Matrix3r Bp_BodyGlob ; Bp_BodyGlob << - pow(sin(alpha),2) * cos(alpha+wettAngle) , 0 , 0 , 0 , - pow(sin(alpha),2) * cos(alpha+wettAngle) , 0 , 0 , 0 , sin(2*alpha) * sin(alpha+wettAngle); Bp_BodyGlob *= Mathr::PI * pow(radius,2.0) * surfTens ; Matrix3r globToLocRet = matGlobToLoc(vecN); return globToLocRet * Bp_BodyGlob * globToLocRet.transpose() ; } Matrix3r MeasureCapStress::matGlobToLoc(Vector3r vecN){ Real phi; // the angle between x-axis and vecN Real theta = acos(vecN[2]); // in [0,pi] according to http://www.cplusplus.com/reference/cmath/acos/. The same in std according to http://en.cppreference.com/w/cpp/numeric/math/acos ? if (fabs(vecN[2]) == 1) // hence vecN = +/- Z, sin(theta = 0), and phi value does not matter phi = 0; else { Real cosPhi; cosPhi = vecN[0]/sin(theta); if (cosPhi > 1) cosPhi = 1; // might occur. Because of numeric precision ? else if (cosPhi < -1) cosPhi = -1; if (vecN[1] > 0) // <=> here sinPhi > 0 <=> phi in ]0,pi[ { phi = acos(cosPhi); } else { phi = 2*Mathr::PI - acos(cosPhi); } } // Change of basis matrix from global (X,Y,Z), to local (x=eTheta,y=ePhi,vecN=eR) Matrix3r globToLoc = Matrix3r::Zero(); globToLoc << cos(theta)*cos(phi), -sin(phi), sin(theta)*cos(phi), cos(theta)*sin(phi), cos(phi) , sin(theta)*sin(phi), - sin(theta) , 0 , cos(theta); return globToLoc; } trunk-2018.02b/pkg/dem/MeasureCapStress.hpp000066400000000000000000000075131324306050200204640ustar00rootroot00000000000000// jerome.duriez@irstea.fr #pragma once #include class MeasureCapStress : public PeriodicEngine { protected: Matrix3r matA_BodyGlob(Real, Real, Vector3r); // particle-scale matrix A (see (3.48) p.65 [Khosravani2014]) describing the orientation of wetted surface, expressed in global axis Matrix3r matBp_BodyGlob(Real, Real, Real, Real, Vector3r); // particle-scale matrix B' (from (3.49) p.65 [Khosravani2014]) relative to the contact line, expressed in global axis Matrix3r matLG_bridgeGlob(Real, Real, Real, Vector3r); // = surface tension * integral over Snw (deltaIJ - nI nJ) dS, expressed in global axis Matrix3r matGlobToLoc(Vector3r vecN); // Change of basis matrix, from local basis with vecN = z, see Fig 3.18 p.65 [Khosravani2014]; to global basis X,Y,Z, where vecN is identified by (theta,phi) angles, see Fig 3.8 p.54 [Khosravani2014]. Upon execution of this function, vec N is basically the meniscus' axis of symmetry. public: void action(); YADE_CLASS_BASE_DOC_ATTRS(MeasureCapStress,PeriodicEngine,"Post-processing engine giving :yref:`the capillary stress tensor` (the fluids mixture contribution to the total stress in unsaturated, i.e. triphasic, conditions) according to the expression detailled in [Duriez2016b]_, [Duriez2017c]_. Although this expression differs in nature from the one of utils.getCapillaryStress (consideration of distributed integrals herein, vs resultant capillary force therein), both are equivalent [Duriez2016b]_, [Duriez2017]_, [Duriez2017c]_.\n The REV volume $V$ entering the expression is automatically measured, from the :yref:`Cell` for periodic conditions, or from :yref:`aabbExtrema` function otherwise.", ((Matrix3r,capStrTens,Matrix3r::Zero(),Attr::readonly,"The capillary stress tensor itself, equal to :yref:`capStrTens1` + :yref:`capStrTens2` + :yref:`capStrTens3` + :yref:`capStrTens4`. |yupdate|")) ((Matrix3r,capStrTens1,Matrix3r::Zero(),Attr::readonly,"Wetting fluid volume spherical stress contribution to :yref:`capStrTens`: $u_c / V \\boldsymbol{\\mu_{Vw}}$ in [Duriez2017c]_. |yupdate|")) ((Matrix3r,capStrTens2,Matrix3r::Zero(),Attr::readonly,"Wetted solid surfaces stress contribution to :yref:`capStrTens`: $u_c / V \\boldsymbol{\\mu_{Ssw}}$ in [Duriez2017c]_. |yupdate|")) ((Matrix3r,capStrTens3,Matrix3r::Zero(),Attr::readonly,"Wetting/non-wetting interface (e.g. liquid-gas) stress contribution to :yref:`capStrTens`: $\\gamma_{nw} / V \\boldsymbol{\\mu_{Snw}}$ in [Duriez2017c]_. |yupdate|")) ((Matrix3r,capStrTens4,Matrix3r::Zero(),Attr::readonly,"Contact lines stress contribution to :yref:`capStrTens`: $\\gamma_{nw} / V \\boldsymbol{\\mu_{\\Gamma}}$ in [Duriez2017c]_. |yupdate|")) ((Real,wettAngle,0,,"Wetting, i.e. contact, angle value (radians). To be defined consistenly with the value upon which the capillary files (used by :yref:`Law2_ScGeom_CapillaryPhys_Capillarity`) are founded.")) ((Real,capillaryPressure,0,,"Capillary pressure $u_c$, to be defined equal to :yref:`Law2_ScGeom_CapillaryPhys_Capillarity.capillaryPressure` (Pa).")) ((Real,surfaceTension,0.073,,"Fluid-fluid surface tension $\\gamma_{nw}$, to be defined equal to :yref:`Law2_ScGeom_CapillaryPhys_Capillarity.surfaceTension` (N/m).")) ((bool,debug,0,,"To output some debugging messages.")) ((Real,vLiq,0,Attr::readonly,"Wetting fluid volume (m3), summing :yref:`menisci volumes` (faster here than through python loops). |yupdate|")) ); }; REGISTER_SERIALIZABLE(MeasureCapStress);trunk-2018.02b/pkg/dem/MicroMacroAnalyser.cpp000066400000000000000000000257671324306050200207730ustar00rootroot00000000000000/************************************************************************* * Copyright (C) 2008 by Bruno Chareyre * * bruno.chareyre@hmg.inpg.fr * * * * This program is free software; it is licensed under the terms of the * * GNU General Public License v2 or later. See file LICENSE for details. * *************************************************************************/ #ifdef YADE_CGAL #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "MicroMacroAnalyser.hpp" YADE_PLUGIN((MicroMacroAnalyser)); CREATE_LOGGER(MicroMacroAnalyser); MicroMacroAnalyser::~MicroMacroAnalyser(){ /*delete analyser;*/} //no need, its a shared_ptr now... void MicroMacroAnalyser::postLoad(MicroMacroAnalyser&) { ofile.open(outputFile.c_str(), std::ios::app); if (!boost::filesystem::exists( outputFile.c_str() )) ofile<<"iteration eps1w eps2w eps3w eps11g eps22g eps33g eps12g eps13g eps23g"<< endl; } void MicroMacroAnalyser::action() { //cerr << "MicroMacroAnalyser::action() (interval="<< interval <<", iteration="<< scene->iter<<")" << endl; if (!triaxialCompressionEngine) { vector >::iterator itFirst = scene->engines.begin(); vector >::iterator itLast = scene->engines.end(); for (;itFirst!=itLast; ++itFirst) { if ((*itFirst)->getClassName() == "TriaxialCompressionEngine") { LOG_DEBUG("stress controller engine found"); triaxialCompressionEngine = YADE_PTR_CAST (*itFirst);} } if (!triaxialCompressionEngine) LOG_ERROR("stress controller engine not found"); } if (triaxialCompressionEngine->strain[0]==0) return;// no deformation yet if (!initialized) { setState(1,true,false); //Check file here again, to make sure we write to the correct file when filename is modified after the scene is loaded ofile.open(outputFile.c_str(), std::ios::app); if (!boost::filesystem::exists( outputFile.c_str() )) ofile<<"iteration eps1w eps2w eps3w eps11g eps22g eps33g eps12g eps13g eps23g"<< endl; initialized=true; } else if (scene->iter % interval == 0) { setState(2, true, compIncrt); if (compDeformation) { analyser->computeParticlesDeformation(); //for (int i=0; iParticleDeformation.size();i++) cerr<< analyser->ParticleDeformation[i]<DefToFile(oss.str().c_str()); } CGT::Tenseur_sym3 epsg(analyser->grad_u_total); ofile << scene->iter << analyser->Delta_epsilon(1,1)<<" "<Delta_epsilon(2,2)<<" "<Delta_epsilon(3,3)<<" "<SwitchStates(); } //cerr << "ENDOF MicroMacro::action" << endl; } void MicroMacroAnalyser::setState(unsigned int state, bool save_states, bool computeIncrement) { LOG_INFO("MicroMacroAnalyser::setState"); CGT::TriaxialState& TS = makeState(state); if (state == 2) { analyser->Delta_epsilon(3,3) = analyser->TS1->eps3 - analyser->TS0->eps3; analyser->Delta_epsilon(1,1) = analyser->TS1->eps1 - analyser->TS0->eps1; analyser->Delta_epsilon(2,2) = analyser->TS1->eps2 - analyser->TS0->eps2; if (computeIncrement) { analyser->SetForceIncrements(); analyser->SetDisplacementIncrements(); } } if (save_states) { ostringstream oss; //oss<iter; oss<& bodies = scene->bodies; CGT::TriaxialState* ts=0; if (state==1) ts = analyser->TS0; else if (state==2) ts = analyser->TS1; else LOG_ERROR("state must be 1 or 2, instead of " << state); CGT::TriaxialState& TS = *ts; TS.reset(); long Ng = bodies->size(); TS.mean_radius=0; TS.grains.resize(Ng); BodyContainer::iterator biBegin = bodies->begin(); BodyContainer::iterator biEnd = bodies->end(); BodyContainer::iterator bi = biBegin; Ng = 0; vector fictiousVtx; for (; bi!=biEnd ; ++bi) { const Body::id_t Idg = (*bi)->getId(); TS.grains[Idg].id = Idg; if (!(*bi)->isDynamic()) { if (!nonSphereAsFictious) continue; TS.grains[Idg].isSphere = false; fictiousVtx.push_back(Idg);} else {//then it is a sphere (not a wall) ++Ng; const Sphere* s = YADE_CAST ((*bi)->shape.get()); //const GranularMat* p = YADE_CAST ( ( *bi )->material.get() ); const Vector3r& pos = (*bi)->state->pos; Real rad = s->radius; TS.grains[Idg].sphere = CGT::Sphere(CGT::Point(pos[0],pos[1],pos[2]),rad); // TS.grains[Idg].translation = trans; AngleAxisr aa((*bi)->state->ori); Vector3r rotVec=aa.axis()*aa.angle(); TS.grains[Idg].rotation = CGT::CVector(rotVec[0],rotVec[1],rotVec[2]); TS.box.base = CGT::Point(min(TS.box.base.x(), pos.x()-rad), min(TS.box.base.y(), pos.y()-rad), min(TS.box.base.z(), pos.z()-rad)); TS.box.sommet = CGT::Point(max(TS.box.sommet.x(), pos.x() +rad), max(TS.box.sommet.y(), pos.y() +rad), max(TS.box.sommet.z(), pos.z() +rad)); TS.mean_radius += TS.grains[Idg].sphere.weight(); } } TS.mean_radius /= Ng;//rayon moyen LOG_INFO(" loaded : " << Ng << " grains with mean radius = " << TS.mean_radius); Real FAR = 1e4; if (fictiousVtx.size()==0) { TS.grains.resize(Ng+6); for (int fv=Ng;fvinteractions->begin(); InteractionContainer::iterator iiEnd = scene->interactions->end(); for (; ii!=iiEnd ; ++ii) { if ((*ii)->isReal()) { CGT::TriaxialState::Contact *c = new CGT::TriaxialState::Contact; TS.contacts.push_back(c); CGT::TriaxialState::VectorGrain& grains = TS.grains; Body::id_t id1 = (*ii)->getId1(); Body::id_t id2 = (*ii)->getId2(); c->grain1 = & (TS.grains[id1]); c->grain2 = & (TS.grains[id2]); grains[id1].contacts.push_back(c); grains[id2].contacts.push_back(c); c->normal = CGT::CVector((YADE_CAST ((*ii)->geom.get()))->normal.x(), (YADE_CAST ((*ii)->geom.get()))->normal.y(), (YADE_CAST ((*ii)->geom.get()))->normal.z()); // c->normal = ( grains[id2].sphere.point()-grains[id1].sphere.point() ); // c->normal = c->normal/sqrt ( pow ( c->normal.x(),2 ) +pow ( c->normal.y(),2 ) +pow ( c->normal.z(),2 ) ); c->position = CGT::CVector((YADE_CAST ((*ii)->geom.get()))->contactPoint.x(), (YADE_CAST ((*ii)->geom.get()))->contactPoint.y(), (YADE_CAST ((*ii)->geom.get()))->contactPoint.z()); // c->position = 0.5* ( ( grains[id1].sphere.point()-CGAL::ORIGIN ) + // ( grains[id1].sphere.weight() *c->normal ) + // ( grains[id2].sphere.point()-CGAL::ORIGIN ) - // ( grains[id2].sphere.weight() *c->normal ) ); c->fn = YADE_CAST (((*ii)->phys.get()))->normalForce.dot((YADE_CAST ((*ii)->geom.get()))->normal); Vector3r fs = YADE_CAST ((*ii)->phys.get())->shearForce; c->fs = CGT::CVector(fs.x(),fs.y(),fs.z()); c->old_fn = c->fn; c->old_fs = c->fs; c->frictional_work = 0; } } //Save various parameters if triaxialCompressionEngine is defined if (!triaxialCompressionEngine) { vector >::iterator itFirst = scene->engines.begin(); vector >::iterator itLast = scene->engines.end(); for (;itFirst!=itLast; ++itFirst) { if ((*itFirst)->getClassName() == "TriaxialCompressionEngine") { LOG_DEBUG("stress controller engine found"); triaxialCompressionEngine = YADE_PTR_CAST (*itFirst);} } if (!triaxialCompressionEngine) LOG_INFO("stress controller engine not found");} if (triaxialCompressionEngine) { TS.wszzh = triaxialCompressionEngine->stress[triaxialCompressionEngine->wall_top][1]; TS.wsxxd = triaxialCompressionEngine->stress[triaxialCompressionEngine->wall_right][0]; TS.wsyyfa = triaxialCompressionEngine->stress[triaxialCompressionEngine->wall_front][2]; TS.eps3 = triaxialCompressionEngine->strain[2];//find_parameter("eps3=", Statefile); TS.eps1 = triaxialCompressionEngine->strain[0];//find_parameter("eps1=", Statefile); TS.eps2 = triaxialCompressionEngine->strain[1];//find_parameter("eps2=", Statefile); TS.haut = triaxialCompressionEngine->height;//find_parameter("haut=", Statefile); TS.larg = triaxialCompressionEngine->width;//find_parameter("larg=", Statefile); TS.prof = triaxialCompressionEngine->depth;//find_parameter("prof=", Statefile); TS.porom = 0/*analyser->computeMacroPorosity() crasher?*/;//find_parameter("porom=", Statefile); TS.ratio_f = triaxialCompressionEngine->ComputeUnbalancedForce(scene); //find_parameter("ratio_f=", Statefile); } else TS.wszzh=TS.wsxxd=TS.wsyyfa=TS.eps3=TS.eps1=TS.eps2=TS.haut=TS.larg=TS.prof=TS.porom=TS.ratio_f=0; if (filename!=NULL) TS.to_file(filename); return TS; } // const vector& MicroMacroAnalyser::makeDeformationArray(const char* state_file1, const char* state_file0) // { // return analyser->computeParticlesDeformation(state_file1, state_file0); // } #endif /* YADE_CGAL */ trunk-2018.02b/pkg/dem/MicroMacroAnalyser.hpp000066400000000000000000000066451324306050200207720ustar00rootroot00000000000000/************************************************************************* * Copyright (C) 2008 by Bruno Chareyre * * bruno.chareyre@hmg.inpg.fr * * * * This program is free software; it is licensed under the terms of the * * GNU General Public License v2 or later. See file LICENSE for details. * *************************************************************************/ #ifdef YADE_CGAL #pragma once #include #include /*! \brief compute fabric tensor, local porosity, local deformation, and other micromechanicaly defined quantities based on triangulation/tesselation of the packing. */ namespace CGT { class TriaxialState; class Tenseur3; } class TriaxialCompressionEngine; class MicroMacroAnalyser : public GlobalEngine { /// Attributes private : std::ofstream ofile; shared_ptr triaxialCompressionEngine; bool initialized; public : ~MicroMacroAnalyser(); void action(); /// Set current state as initial (state=1) or final (state=2) congiguration for later kinematic analysis on the increment; if requested : save snapshots (with specific format) - possibly including contact forces increments on the state1->state2 interval void setState(unsigned int state, bool save_states = false, bool computeIncrement = false); /// Copy the current simulation in a TriaxialState structure. If filename!=NULL, save it to a file that can be reloaded later for computing strain increments, state must be 1 or 2. CGT::TriaxialState& makeState(unsigned int state, const char* filename = NULL); //const vector& makeDeformationArray(const char* state_file1, const char* state_file0); shared_ptr analyser; void postLoad(MicroMacroAnalyser&); YADE_CLASS_BASE_DOC_ATTRS_INIT_CTOR_PY(MicroMacroAnalyser,GlobalEngine,"compute fabric tensor, local porosity, local deformation, and other micromechanicaly defined quantities based on triangulation/tesselation of the packing.", ((unsigned int,stateNumber,0,,"A number incremented and appended at the end of output files to reflect increment number.")) ((unsigned int,incrtNumber,1,,"")) ((std::string,outputFile,"MicroMacroAnalysis",,"Base name for increment analysis output file.")) ((std::string,stateFileName,"state",,"Base name of state files.")) ((int,interval,100,,"Number of timesteps between analyzed states.")) ((bool,compDeformation,false,,"Is the engine just saving states or also computing and outputing deformations for each increment?")) ((bool,compIncrt,false,,"Should increments of force and displacements be defined on [n,n+1]? If not, states will be saved with only positions and forces (no displacements).")) ((bool,nonSphereAsFictious,true,,"bodies that are not spheres will be used to defines bounds (else just skipped).")) ,/*init*/ ,/*ctor*/ analyser = shared_ptr (new CGT::KinematicLocalisationAnalyser); analyser->SetConsecutive(true); analyser->SetNO_ZERO_ID(false); initialized = false; ,/*py*/ ); DECLARE_LOGGER; //REGISTER_ATTRIBUTES(GlobalEngine,(stateNumber)(incrtNumber)(outputFile)(stateFileName)(interval)(compDeformation)(compIncrt)); }; REGISTER_SERIALIZABLE(MicroMacroAnalyser); #endif /* YADE_CGAL */ trunk-2018.02b/pkg/dem/MortarMat.cpp000066400000000000000000000076771324306050200171470ustar00rootroot00000000000000// 2016 © Jan Stránský #include"MortarMat.hpp" YADE_PLUGIN((MortarMat)(Ip2_MortarMat_MortarMat_MortarPhys)(MortarPhys)(Law2_ScGeom_MortarPhys_Lourenco)) CREATE_LOGGER(Ip2_MortarMat_MortarMat_MortarPhys); void Ip2_MortarMat_MortarMat_MortarPhys::go(const shared_ptr& material1, const shared_ptr& material2, const shared_ptr& interaction){ if (interaction->phys) return; if (scene->iter >= cohesiveThresholdIter) { LOG_ERROR("MortarMat not implemented for non-cohesive contacts"); } shared_ptr phys(new MortarPhys()); interaction->phys = phys; MortarMat* mat1 = YADE_CAST(material1.get()); MortarMat* mat2 = YADE_CAST(material2.get()); GenericSpheresContact* geom = YADE_CAST(interaction->geom.get()); if (mat1->id>=0 && mat1->id == mat2->id) { #define _CPATTR(a) phys->a=mat1->a _CPATTR(tensileStrength); _CPATTR(compressiveStrength); _CPATTR(cohesion); _CPATTR(ellAspect); _CPATTR(neverDamage); #undef _CPATTR phys->tangensOfFrictionAngle = std::tan(mat1->frictionAngle); } else { // averaging over both materials #define _MINATTR(a) phys->a=std::min(mat1->a,mat2->a) #define _AVGATTR(a) phys->a=.5*(mat1->a+mat2->a) _MINATTR(tensileStrength); _MINATTR(compressiveStrength); _MINATTR(cohesion); _AVGATTR(ellAspect); #undef _AVGATTR #undef _MINATTR phys->neverDamage = mat1->neverDamage || mat2->neverDamage; phys->tangensOfFrictionAngle = std::tan(.5*(mat1->frictionAngle+mat2->frictionAngle)); } // const Real& r1 = geom->refR1; const Real& r2 = geom->refR2; Real minRad = r1 <= 0 ? r2 : r2 <= 0 ? r1 : std::min(r1,r2); phys->crossSection = std::pow(minRad,2); const Real& E1 = mat1->young; const Real& E2 = mat2->young; const Real& n1 = mat1->poisson; const Real& n2 = mat2->poisson; phys->kn = 2*E1*r1*E2*r2/(E1*r1+E2*r2); phys->ks = 2*E1*r1*n1*E2*r2*n2/(E1*r1*n1+E2*r2*n2); } CREATE_LOGGER(MortarPhys); MortarPhys::~MortarPhys(){}; bool MortarPhys::failureCondition(Real sigmaN, Real sigmaT) { bool cond1 = sigmaN - tensileStrength > 0; bool cond2 = sigmaT + sigmaN*tangensOfFrictionAngle - cohesion > 0; bool cond3 = std::pow(sigmaN,2) + std::pow(ellAspect*sigmaT,2) - std::pow(compressiveStrength,2) > 0; return cond1 || cond2 || cond3; } /********************** Law2_ScGeom_MortarPhys_Lourenco ****************************/ CREATE_LOGGER(Law2_ScGeom_MortarPhys_Lourenco); bool Law2_ScGeom_MortarPhys_Lourenco::go(shared_ptr& iGeom, shared_ptr& iPhys, Interaction* interaction){ ScGeom* geom=static_cast(iGeom.get()); MortarPhys* phys=static_cast(iPhys.get()); Body::id_t id1 = interaction->getId1(); Body::id_t id2 = interaction->getId2(); const shared_ptr b1 = Body::byId(id1,scene); const shared_ptr b2 = Body::byId(id2,scene); /* shorthands */ const Real& crossSection(phys->crossSection); Real& sigmaN(phys->sigmaN); Vector3r& sigmaT(phys->sigmaT); Real& kn(phys->kn); Real& ks(phys->ks); Vector3r& normalForce(phys->normalForce); Vector3r& shearForce(phys->shearForce); /* constitutive law */ normalForce = kn*geom->penetrationDepth*geom->normal; geom->rotate(shearForce); shearForce -= ks*geom->shearIncrement(); sigmaN = - normalForce.dot(geom->normal) / crossSection; sigmaT = - shearForce / crossSection; if (!phys->neverDamage && phys->failureCondition(sigmaN,sigmaT.norm())) { return false; } State* s1 = b1->state.get(); State* s2 = b2->state.get(); Vector3r f = - normalForce - shearForce; if (!scene->isPeriodic) { applyForceAtContactPoint(f, geom->contactPoint , id1, s1->se3.position, id2, s2->se3.position); } else { scene->forces.addForce(id1,f); scene->forces.addForce(id2,-f); scene->forces.addTorque(id1,(geom->radius1-.5*(geom->penetrationDepth))*geom->normal.cross(f)); scene->forces.addTorque(id2,(geom->radius2-.5*(geom->penetrationDepth))*geom->normal.cross(f)); } return true; } trunk-2018.02b/pkg/dem/MortarMat.hpp000066400000000000000000000076351324306050200171460ustar00rootroot00000000000000// 2016 © Jan Stránský #pragma once #include #include #include #include #include #include #include #include namespace py=boost::python; class MortarMat: public FrictMat { public: YADE_CLASS_BASE_DOC_ATTRS_CTOR(MortarMat,FrictMat,"Material for mortar interface, used in Ip2_MortarMat_MortarMat_MortarPhys and Law2_ScGeom_MortarPhys_Lourenco. Default values according to ", ((Real,young,1e9,,"Normal elastic modulus [Pa]")) ((Real,poisson,1,,"Shear to normal modulus ratio")) ((Real,frictionAngle,.25,,"Friction angle")) // ((Real,tensileStrength,1e6,,"tensileStrength [Pa]")) ((Real,compressiveStrength,10e6,,"compressiveStrength [Pa]")) ((Real,cohesion,1e6,,"cohesion [Pa]")) ((Real,ellAspect,3,,"aspect ratio of elliptical 'cap'. Value >1 means the ellipse is longer along normal stress axis.")) ((bool,neverDamage,false,,"If true, interactions remain elastic regardless stresses")) , createIndex(); ); REGISTER_CLASS_INDEX(MortarMat,FrictMat); }; REGISTER_SERIALIZABLE(MortarMat); class MortarPhys: public FrictPhys { public: Real sigmaN; Vector3r sigmaT; virtual ~MortarPhys(); bool failureCondition(Real sigmaN, Real sigmaT); YADE_CLASS_BASE_DOC_ATTRS_CTOR_PY(MortarPhys,FrictPhys,"IPhys class containing parameters of MortarMat. Used by Law2_ScGeom_MortarPhys_Lourenco.", ((Real,tensileStrength,NaN,,"tensileStrength [Pa]")) ((Real,compressiveStrength,NaN,,"compressiveStrength [Pa]")) ((Real,cohesion,NaN,,"cohesion [Pa]")) ((Real,ellAspect,NaN,,"aspect ratio of elliptical 'cap'. Value >1 means the ellipse is longer along normal stress axis.")) ((Real,crossSection,NaN,,"Crosssection of interaction")) ((bool,neverDamage,false,,"If true, interactions remain elastic regardless stresses")) , // ctors createIndex(); , .def_readonly("sigmaN",&MortarPhys::sigmaN,"Current normal stress |yupdate|") .def_readonly("sigmaT",&MortarPhys::sigmaT,"Current shear stress |yupdate|") .def("failureCondition",&MortarPhys::failureCondition,"Failure condition from normal stress and norm of shear stress (false=elastic, true=damaged)") ); DECLARE_LOGGER; REGISTER_CLASS_INDEX(MortarPhys,NormShearPhys); }; REGISTER_SERIALIZABLE(MortarPhys); class Ip2_MortarMat_MortarMat_MortarPhys: public IPhysFunctor{ public: virtual void go(const shared_ptr& material1, const shared_ptr& material2, const shared_ptr& interaction); FUNCTOR2D(MortarMat,MortarMat); DECLARE_LOGGER; YADE_CLASS_BASE_DOC_ATTRS(Ip2_MortarMat_MortarMat_MortarPhys,IPhysFunctor,"Ip2 creating MortarPhys from two MortarMat instances.", ((long,cohesiveThresholdIter,2,,"Should new contacts be cohesive? They will before this iter#, they will not be afterwards. If <=0, they will never be.")) ); }; REGISTER_SERIALIZABLE(Ip2_MortarMat_MortarMat_MortarPhys); class Law2_ScGeom_MortarPhys_Lourenco: public LawFunctor{ public: virtual bool go(shared_ptr& iGeom, shared_ptr& iPhys, Interaction* interaction); FUNCTOR2D(GenericSpheresContact,MortarPhys); YADE_CLASS_BASE_DOC(Law2_ScGeom_MortarPhys_Lourenco,LawFunctor,"Material law for mortar layer according to [Lourenco1994]_. The contact behaves elastic until brittle failure when reaching strength envelope. The envelope has three parts.\n\nTensile with condition $\\sigma_N-f_t$.\n\nShear part with Mohr-Coulomb condition $|\\sigma_T|+\\sigma_N\\tan\\varphi-c$.\n\nCompressive part with condition $\\sigma_N^2+A^2\\sigma_T^2-f_c^2$\n\nThe main idea is to begin simulation with this model and when the contact is broken, to use standard non-cohesive Law2_PolyhedraGeom_PolyhedraPhys_Volumetric." ); DECLARE_LOGGER; }; REGISTER_SERIALIZABLE(Law2_ScGeom_MortarPhys_Lourenco); trunk-2018.02b/pkg/dem/NewtonIntegrator.cpp000066400000000000000000000405031324306050200205330ustar00rootroot00000000000000/************************************************************************* Copyright (C) 2008 by Bruno Chareyre * * bruno.chareyre@hmg.inpg.fr * * * * This program is free software; it is licensed under the terms of the * * GNU General Public License v2 or later. See file LICENSE for details. * *************************************************************************/ #include #include #include #include YADE_PLUGIN((NewtonIntegrator)); CREATE_LOGGER(NewtonIntegrator); // 1st order numerical damping void NewtonIntegrator::cundallDamp1st(Vector3r& force, const Vector3r& vel){ for(int i=0; i<3; i++) force[i]*=1-damping*Mathr::Sign(force[i]*vel[i]); } // 2nd order numerical damping void NewtonIntegrator::cundallDamp2nd(const Real& dt, const Vector3r& vel, Vector3r& accel){ for(int i=0; i<3; i++) accel[i]*= 1 - damping*Mathr::Sign ( accel[i]*(vel[i] + 0.5*dt*accel[i]) ); } Vector3r NewtonIntegrator::computeAccel(const Vector3r& force, const Real& mass, int blockedDOFs){ if(blockedDOFs==0) return (force/mass + gravity); Vector3r ret(Vector3r::Zero()); for(int i=0; i<3; i++) if(!(blockedDOFs & State::axisDOF(i,false))) ret[i]+=force[i]/mass+gravity[i]; return ret; } Vector3r NewtonIntegrator::computeAngAccel(const Vector3r& torque, const Vector3r& inertia, int blockedDOFs){ if(blockedDOFs==0) return torque.cwiseQuotient(inertia); Vector3r ret(Vector3r::Zero()); for(int i=0; i<3; i++) if(!(blockedDOFs & State::axisDOF(i,true))) ret[i]+=torque[i]/inertia[i]; return ret; } void NewtonIntegrator::updateEnergy(const shared_ptr& b, const State* state, const Vector3r& fluctVel, const Vector3r& f, const Vector3r& m){ assert(b->isStandalone() || b->isClump()); // always positive dissipation, by-component: |F_i|*|v_i|*damping*dt (|T_i|*|ω_i|*damping*dt for rotations) if(damping!=0. && state->isDamped){ scene->energy->add(fluctVel.cwiseAbs().dot(f.cwiseAbs())*damping*scene->dt,"nonviscDamp",nonviscDampIx,/*non-incremental*/false); // when the aspherical integrator is used, torque is damped instead of ang acceleration; this code is only approximate scene->energy->add(state->angVel.cwiseAbs().dot(m.cwiseAbs())*damping*scene->dt,"nonviscDamp",nonviscDampIx,false); } // kinetic energy Real Etrans=.5*state->mass*fluctVel.squaredNorm(); Real Erot; // rotational terms if(b->isAspherical()){ Matrix3r mI; mI<inertia[0],0,0, 0,state->inertia[1],0, 0,0,state->inertia[2]; Matrix3r T(state->ori); Erot=.5*b->state->angVel.transpose().dot((T.transpose()*mI*T)*b->state->angVel); } else { Erot=0.5*state->angVel.dot(state->inertia.cwiseProduct(state->angVel)); } if(!kinSplit) scene->energy->add(Etrans+Erot,"kinetic",kinEnergyIx,/*non-incremental*/true); else{ scene->energy->add(Etrans,"kinTrans",kinEnergyTransIx,true); scene->energy->add(Erot,"kinRot",kinEnergyRotIx,true); } // gravitational work (work done by gravity is "negative", since the energy appears in the system from outside) scene->energy->add(-gravity.dot(b->state->vel)*b->state->mass*scene->dt,"gravWork",fieldWorkIx,/*non-incremental*/false); } void NewtonIntegrator::saveMaximaVelocity(const Body::id_t& id, State* state){ #ifdef YADE_OPENMP Real& thrMaxVSq=threadMaxVelocitySq[omp_get_thread_num()]; thrMaxVSq=max(thrMaxVSq,state->vel.squaredNorm()); #else maxVelocitySq=max(maxVelocitySq,state->vel.squaredNorm()); #endif } void NewtonIntegrator::saveMaximaDisplacement(const shared_ptr& b){ if (!b->bound) return;//clumps for instance, have no bounds, hence not saved Vector3r disp=b->state->pos-b->bound->refPos; Real maxDisp=max(std::abs(disp[0]),max(std::abs(disp[1]),std::abs(disp[2]))); if (!maxDisp || maxDispbound->sweepLength) {/*b->bound->isBounding = (updatingDispFactor>0 && (updatingDispFactor*maxDisp)bound->sweepLength);*/ maxDisp=0.5;//not 0, else it will be seen as "not updated" by the collider, but less than 1 means no colliding } else {/*b->bound->isBounding = false;*/ maxDisp=2;/*2 is more than 1, enough to trigger collider*/} #ifdef YADE_OPENMP Real& thrMaxVSq=threadMaxVelocitySq[omp_get_thread_num()]; thrMaxVSq=max(thrMaxVSq,maxDisp); #else maxVelocitySq=max(maxVelocitySq,maxDisp); #endif } void NewtonIntegrator::action() { scene->forces.sync(); bodySelected=(scene->selectedBody>=0); if(warnNoForceReset && scene->forces.lastResetiter) LOG_WARN("O.forces last reset in step "<forces.lastReset<<", while the current step is "<iter<<". Did you forget to include ForceResetter in O.engines?"); const Real& dt=scene->dt; //Take care of user's request to change velGrad. Safe to change it here after the interaction loop. if (scene->cell->velGradChanged || scene->cell->nextVelGrad!=Matrix3r::Zero()) { scene->cell->velGrad=scene->cell->nextVelGrad; scene->cell->velGradChanged=0; scene->cell->nextVelGrad=Matrix3r::Zero();} homoDeform=scene->cell->homoDeform; dVelGrad=scene->cell->velGrad-prevVelGrad; Matrix3r R=.5*(dVelGrad-dVelGrad.transpose()); dSpin=Vector3r(-R(1,2),R(0,2),-R(0,1)); // account for motion of the periodic boundary, if we remember its last position // its velocity will count as max velocity of bodies // otherwise the collider might not run if only the cell were changing without any particle motion // FIXME: will not work for pure shear transformation, which does not change Cell::getSize() if(scene->isPeriodic && ((prevCellSize!=scene->cell->getSize())) && /* initial value */!std::isnan(prevCellSize[0]) ){ cellChanged=true; maxVelocitySq=(prevCellSize-scene->cell->getSize()).squaredNorm()/pow(dt,2); } else { maxVelocitySq=0; cellChanged=false; } #ifdef YADE_BODY_CALLBACK // setup callbacks vector callbackPtrs; FOREACH(const shared_ptr& cb, callbacks){ cerr<<"scene="<"; cb->scene=scene; callbackPtrs.push_back(cb->stepInit()); } assert(callbackPtrs.size()==callbacks.size()); size_t callbacksSize=callbacks.size(); #endif const bool trackEnergy(scene->trackEnergy); const bool isPeriodic(scene->isPeriodic); #ifdef YADE_OPENMP FOREACH(Real& thrMaxVSq, threadMaxVelocitySq) { thrMaxVSq=0; } #endif YADE_PARALLEL_FOREACH_BODY_BEGIN(const shared_ptr& b, scene->bodies){ // clump members are handled inside clumps if(b->isClumpMember()) continue; State* state=b->state.get(); const Body::id_t& id=b->getId(); Vector3r f=Vector3r::Zero(); Vector3r m=Vector3r::Zero(); // clumps forces if(b->isClump()) { b->shape->cast().addForceTorqueFromMembers(state,scene,f,m); #ifdef YADE_OPENMP //it is safe here, since only one thread is adding forces/torques scene->forces.addTorqueUnsynced(id,m); scene->forces.addForceUnsynced(id,f); #else scene->forces.addTorque(id,m); scene->forces.addForce(id,f); #endif } //in most cases, the initial force on clumps will be zero and next line is not changing f and m, but make sure we don't miss something (e.g. user defined forces on clumps) f=scene->forces.getForce(id); m=scene->forces.getTorque(id); #ifdef YADE_DEBUG if(std::isnan(f[0])||std::isnan(f[1])||std::isnan(f[2])) throw runtime_error(("NewtonIntegrator: NaN force acting on #"+boost::lexical_cast(id)+".").c_str()); if(std::isnan(m[0])||std::isnan(m[1])||std::isnan(m[2])) throw runtime_error(("NewtonIntegrator: NaN torque acting on #"+boost::lexical_cast(id)+".").c_str()); if(state->mass<=0 && ((state->blockedDOFs & State::DOF_XYZ) != State::DOF_XYZ)) throw runtime_error(("NewtonIntegrator: #"+boost::lexical_cast(id)+" has some linear accelerations enabled, but State::mass is non-positive.")); if(state->inertia.minCoeff()<=0 && ((state->blockedDOFs & State::DOF_RXRYRZ) != State::DOF_RXRYRZ)) throw runtime_error(("NewtonIntegrator: #"+boost::lexical_cast(id)+" has some angular accelerations enabled, but State::inertia contains non-positive terms.")); #endif // fluctuation velocity does not contain meanfield velocity in periodic boundaries // in aperiodic boundaries, it is equal to absolute velocity Vector3r fluctVel=isPeriodic?scene->cell->bodyFluctuationVel(b->state->pos,b->state->vel,prevVelGrad):state->vel; // numerical damping & kinetic energy if(trackEnergy) updateEnergy(b,state,fluctVel,f,m); // whether to use aspherical rotation integration for this body; for no accelerations, spherical integrator is "exact" (and faster) bool useAspherical=(exactAsphericalRot && b->isAspherical() && state->blockedDOFs!=State::DOF_ALL); // for particles not totally blocked, compute accelerations; otherwise, the computations would be useless if (state->blockedDOFs!=State::DOF_ALL) { // linear acceleration Vector3r linAccel=computeAccel(f,state->mass,state->blockedDOFs); if (densityScaling) linAccel*=state->densityScaling; if(state->isDamped) cundallDamp2nd(dt,fluctVel,linAccel); //This is the convective term, appearing in the time derivation of Cundall/Thornton expression (dx/dt=velGrad*pos -> d²x/dt²=dvelGrad/dt*pos+velGrad*vel), negligible in many cases but not for high speed large deformations (gaz or turbulent flow). if (isPeriodic && homoDeform>1) linAccel+=prevVelGrad*state->vel; //finally update velocity state->vel+=dt*linAccel; // angular acceleration if(!useAspherical){ // uses angular velocity Vector3r angAccel=computeAngAccel(m,state->inertia,state->blockedDOFs); if (densityScaling) angAccel*=state->densityScaling; if(state->isDamped) cundallDamp2nd(dt,state->angVel,angAccel); state->angVel+=dt*angAccel; } else { // uses torque for(int i=0; i<3; i++) if(state->blockedDOFs & State::axisDOF(i,true)) m[i]=0; // block DOFs here if(state->isDamped) cundallDamp1st(m,state->angVel); } // reflect macro-deformation even for non-dynamic bodies } else if (isPeriodic && homoDeform>1) state->vel+=dt*prevVelGrad*state->vel; // update positions from velocities (or torque, for the aspherical integrator) leapfrogTranslate(state,id,dt); if(!useAspherical) leapfrogSphericalRotate(state,id,dt); else leapfrogAsphericalRotate(state,id,dt,m); saveMaximaDisplacement(b); // move individual members of the clump, save maxima velocity (for collider stride) if(b->isClump()) Clump::moveMembers(b,scene,this); #ifdef YADE_BODY_CALLBACK // process callbacks for(size_t i=0; iid<<",cb="<scene<<">"; // <<",force="<scene->forces.getForce(b->id)<<">"; if(callbackPtrs[i]!=NULL) (*(callbackPtrs[i]))(callbacks[i].get(),b.get()); } #endif } YADE_PARALLEL_FOREACH_BODY_END(); #ifdef YADE_OPENMP FOREACH(const Real& thrMaxVSq, threadMaxVelocitySq) { maxVelocitySq=max(maxVelocitySq,thrMaxVSq); } #endif if(scene->isPeriodic) { prevCellSize=scene->cell->getSize(); prevVelGrad=scene->cell->prevVelGrad=scene->cell->velGrad; } } void NewtonIntegrator::leapfrogTranslate(State* state, const Body::id_t& id, const Real& dt){ if (scene->forces.getMoveRotUsed()) state->pos+=scene->forces.getMove(id); // update velocity reflecting changes in the macroscopic velocity field, making the problem homothetic. //NOTE : if the velocity is updated before moving the body, it means the current velGrad (i.e. before integration in cell->integrateAndUpdate) will be effective for the current time-step. Is it correct? If not, this velocity update can be moved just after "state->pos += state->vel*dt", meaning the current velocity impulse will be applied at next iteration, after the contact law. (All this assuming the ordering is resetForces->integrateAndUpdate->contactLaw->PeriCompressor->NewtonsLaw. Any other might fool us.) //NOTE : dVel defined without wraping the coordinates means bodies out of the (0,0,0) period can move realy fast. It has to be compensated properly in the definition of relative velocities (see Ig2 functors and contact laws). //Reflect mean-field (periodic cell) acceleration in the velocity if(scene->isPeriodic && homoDeform) {Vector3r dVel=dVelGrad*state->pos; state->vel+=dVel;} if ( (mask<=0) or ((mask>0) and (Body::byId(id)->maskCompatible(mask))) ) { state->pos+=state->vel*dt; } } void NewtonIntegrator::leapfrogSphericalRotate(State* state, const Body::id_t& id, const Real& dt ) { if(scene->isPeriodic && homoDeform) {state->angVel+=dSpin;} Real angle2=state->angVel.squaredNorm(); if (angle2!=0 and ( (mask<=0) or ((mask>0) and (Body::byId(id)->maskCompatible(mask))) )) {//If we have an angular velocity, we make a rotation Real angle=sqrt(angle2); Quaternionr q(AngleAxisr(angle*dt,state->angVel/angle)); state->ori = q*state->ori; } if(scene->forces.getMoveRotUsed() && scene->forces.getRot(id)!=Vector3r::Zero() and ( (mask<=0) or ((mask>0) and (Body::byId(id)->maskCompatible(mask))) )) { Vector3r r(scene->forces.getRot(id)); Real norm=r.norm(); r/=norm; Quaternionr q(AngleAxisr(norm,r)); state->ori=q*state->ori; } state->ori.normalize(); } void NewtonIntegrator::leapfrogAsphericalRotate(State* state, const Body::id_t& id, const Real& dt, const Vector3r& M){ //FIXME: where to increment angular velocity like this? Only done for spherical rotations at the moment //if(scene->isPeriodic && homoDeform) {state->angVel+=dSpin;} Matrix3r A=state->ori.conjugate().toRotationMatrix(); // rotation matrix from global to local r.f. const Vector3r l_n = state->angMom + dt/2. * M; // global angular momentum at time n const Vector3r l_b_n = A*l_n; // local angular momentum at time n Vector3r angVel_b_n = l_b_n.cwiseQuotient(state->inertia); // local angular velocity at time n if (densityScaling) angVel_b_n*=state->densityScaling; const Quaternionr dotQ_n=DotQ(angVel_b_n,state->ori); // dQ/dt at time n const Quaternionr Q_half = Quaternionr(state->ori.coeffs() + dt/2. * dotQ_n.coeffs()); // Q at time n+1/2 state->angMom+=dt*M; // global angular momentum at time n+1/2 const Vector3r l_b_half = A*state->angMom; // local angular momentum at time n+1/2 Vector3r angVel_b_half = l_b_half.cwiseQuotient(state->inertia); // local angular velocity at time n+1/2 if (densityScaling) angVel_b_half*=state->densityScaling; const Quaternionr dotQ_half=DotQ(angVel_b_half,Q_half); // dQ/dt at time n+1/2 state->ori=Quaternionr(state->ori.coeffs()+dt*dotQ_half.coeffs()); // Q at time n+1 state->angVel=state->ori*angVel_b_half; // global angular velocity at time n+1/2 if(scene->forces.getMoveRotUsed() && scene->forces.getRot(id)!=Vector3r::Zero()) { Vector3r r(scene->forces.getRot(id)); Real norm=r.norm(); r/=norm; Quaternionr q(AngleAxisr(norm,r)); state->ori=q*state->ori; } state->ori.normalize(); } bool NewtonIntegrator::get_densityScaling() { FOREACH(const shared_ptr e, Omega::instance().getScene()->engines) { GlobalStiffnessTimeStepper* ts=dynamic_cast(e.get()); if (ts && densityScaling != ts->densityScaling) LOG_WARN("density scaling is not active in the timeStepper, it will have no effect unless a scaling is specified manually for some bodies");} LOG_WARN("GlobalStiffnessTimeStepper not present in O.engines, density scaling will have no effect unless a scaling is specified manually for some bodies"); return densityScaling;; } void NewtonIntegrator::set_densityScaling(bool dsc) { FOREACH(const shared_ptr e, Omega::instance().getScene()->engines) { GlobalStiffnessTimeStepper* ts=dynamic_cast(e.get()); if (ts) { ts->densityScaling=dsc; densityScaling=dsc; LOG_WARN("GlobalStiffnessTimeStepper found in O.engines and adjusted to match this setting. Revert in the timestepper if you don't want the scaling adjusted automatically."); return; } } LOG_WARN("GlobalStiffnessTimeStepper not found in O.engines. Density scaling will have no effect unless a scaling is specified manually for some bodies"); } // http://www.euclideanspace.com/physics/kinematics/angularvelocity/QuaternionDifferentiation2.pdf Quaternionr NewtonIntegrator::DotQ(const Vector3r& angVel, const Quaternionr& Q){ Quaternionr dotQ; dotQ.w() = (-Q.x()*angVel[0]-Q.y()*angVel[1]-Q.z()*angVel[2])/2; dotQ.x() = ( Q.w()*angVel[0]-Q.z()*angVel[1]+Q.y()*angVel[2])/2; dotQ.y() = ( Q.z()*angVel[0]+Q.w()*angVel[1]-Q.x()*angVel[2])/2; dotQ.z() = (-Q.y()*angVel[0]+Q.x()*angVel[1]+Q.w()*angVel[2])/2; return dotQ; } trunk-2018.02b/pkg/dem/NewtonIntegrator.hpp000066400000000000000000000133511324306050200205410ustar00rootroot00000000000000/************************************************************************* Copyright (C) 2008 by Bruno Chareyre * * bruno.chareyre@hmg.inpg.fr * * * * This program is free software; it is licensed under the terms of the * * GNU General Public License v2 or later. See file LICENSE for details. * *************************************************************************/ #pragma once #include #include #include #include #include #ifdef YADE_OPENMP #include #endif /*! An engine that can replace the usual series of engines used for integrating the laws of motion. */ class State; class NewtonIntegrator : public FieldApplier{ inline void cundallDamp1st(Vector3r& force, const Vector3r& vel); inline void cundallDamp2nd(const Real& dt, const Vector3r& vel, Vector3r& accel); inline void leapfrogTranslate(State*, const Body::id_t& id, const Real& dt); // leap-frog translate inline void leapfrogSphericalRotate(State*, const Body::id_t& id, const Real& dt); // leap-frog rotate of spherical body inline void leapfrogAsphericalRotate(State*, const Body::id_t& id, const Real& dt, const Vector3r& M); // leap-frog rotate of aspherical body Quaternionr DotQ(const Vector3r& angVel, const Quaternionr& Q); // compute linear and angular acceleration, respecting State::blockedDOFs Vector3r computeAccel(const Vector3r& force, const Real& mass, int blockedDOFs); Vector3r computeAngAccel(const Vector3r& torque, const Vector3r& inertia, int blockedDOFs); void updateEnergy(const shared_ptr&b, const State* state, const Vector3r& fluctVel, const Vector3r& f, const Vector3r& m); #ifdef YADE_OPENMP void ensureSync(); bool syncEnsured; #endif // whether the cell has changed from the previous step bool cellChanged; int homoDeform; // wether a body has been selected in Qt view bool bodySelected; Matrix3r dVelGrad; Vector3r dSpin; public: bool densityScaling;// internal for density scaling Real updatingDispFactor;//(experimental) Displacement factor used to trigger bound update: the bound is updated only if updatingDispFactor*disp>sweepDist when >0, else all bounds are updated. // function to save maximum velocity, for the verlet-distance optimization void saveMaximaVelocity(const Body::id_t& id, State* state); void saveMaximaDisplacement(const shared_ptr& b); bool get_densityScaling (); void set_densityScaling (bool dsc); #ifdef YADE_OPENMP vector threadMaxVelocitySq; #endif virtual void action(); YADE_CLASS_BASE_DOC_ATTRS_CTOR_PY(NewtonIntegrator,GlobalEngine,"Engine integrating newtonian motion equations.", ((Real,damping,0.2,,"damping coefficient for Cundall's non viscous damping (see `numerical damping `_ and [Chareyre2005]_)")) ((Vector3r,gravity,Vector3r::Zero(),,"Gravitational acceleration (effectively replaces GravityEngine).")) ((Real,maxVelocitySq,NaN,,"store square of max. velocity, for informative purposes; computed again at every step. |yupdate|")) ((bool,exactAsphericalRot,true,,"Enable more exact body rotation integrator for :yref:`aspherical bodies` *only*, using formulation from [Allen1989]_, pg. 89.")) ((Matrix3r,prevVelGrad,Matrix3r::Zero(),,"Store previous velocity gradient (:yref:`Cell::velGrad`) to track acceleration. |yupdate|")) #ifdef YADE_BODY_CALLBACK ((vector >,callbacks,,,"List (std::vector in c++) of :yref:`BodyCallbacks` which will be called for each body as it is being processed.")) #endif ((Vector3r,prevCellSize,Vector3r(NaN,NaN,NaN),Attr::hidden,"cell size from previous step, used to detect change and find max velocity")) ((bool,warnNoForceReset,true,,"Warn when forces were not resetted in this step by :yref:`ForceResetter`; this mostly points to :yref:`ForceResetter` being forgotten incidentally and should be disabled only with a good reason.")) // energy tracking ((int,nonviscDampIx,-1,(Attr::hidden|Attr::noSave),"Index of the energy dissipated using the non-viscous damping (:yref:`damping`).")) ((bool,kinSplit,false,,"Whether to separately track translational and rotational kinetic energy.")) ((int,kinEnergyIx,-1,(Attr::hidden|Attr::noSave),"Index for kinetic energy in scene->energies.")) ((int,kinEnergyTransIx,-1,(Attr::hidden|Attr::noSave),"Index for translational kinetic energy in scene->energies.")) ((int,kinEnergyRotIx,-1,(Attr::hidden|Attr::noSave),"Index for rotational kinetic energy in scene->energies.")) ((int,mask,-1,,"If mask defined and the bitwise AND between mask and body`s groupMask gives 0, the body will not move/rotate. Velocities and accelerations will be calculated not paying attention to this parameter.")) , /*ctor*/ densityScaling=false; #ifdef YADE_OPENMP threadMaxVelocitySq.resize(omp_get_max_threads()); syncEnsured=false; #endif ,/*py*/ .add_property("densityScaling",&NewtonIntegrator::get_densityScaling,&NewtonIntegrator::set_densityScaling,"if True, then density scaling [Pfc3dManual30]_ will be applied in order to have a critical timestep equal to :yref:`GlobalStiffnessTimeStepper::targetDt` for all bodies. This option makes the simulation unrealistic from a dynamic point of view, but may speedup quasistatic simulations. In rare situations, it could be useful to not set the scalling factor automatically for each body (which the time-stepper does). In such case revert :yref:`GlobalStiffnessTimeStepper.densityScaling` to False.") ); DECLARE_LOGGER; }; REGISTER_SERIALIZABLE(NewtonIntegrator); trunk-2018.02b/pkg/dem/PeriIsoCompressor.cpp000066400000000000000000000550601324306050200206550ustar00rootroot00000000000000 // 2009 © Václav Šmilauer #include #include #include #include #include #include YADE_PLUGIN((PeriIsoCompressor)(PeriTriaxController)(Peri3dController)) CREATE_LOGGER(PeriIsoCompressor); void PeriIsoCompressor::action(){ if(!scene->isPeriodic){ LOG_FATAL("Being used on non-periodic simulation!"); throw; } if(state>=stresses.size()) return; // initialize values if(charLen<=0){ Bound* bv=Body::byId(0,scene)->bound.get(); if(!bv){ LOG_FATAL("No charLen defined and body #0 has no bound"); throw; } const Vector3r sz=bv->max-bv->min; charLen=(sz[0]+sz[1]+sz[2])/3.; LOG_INFO("No charLen defined, taking avg bbox size of body #0 = "<& b, *scene->bodies){ if(!b || !b->bound) continue; for(int i=0; i<3; i++) maxSpan=max(maxSpan,b->bound->max[i]-b->bound->min[i]); } } if(maxDisplPerStep<0) maxDisplPerStep=1e-2*charLen; // this should be tuned somehow… const long& step=scene->iter; Vector3r cellSize=scene->cell->getSize(); //unused: Real cellVolume=cellSize[0]*cellSize[1]*cellSize[2]; Vector3r cellArea=Vector3r(cellSize[1]*cellSize[2],cellSize[0]*cellSize[2],cellSize[0]*cellSize[1]); Real minSize=min(cellSize[0],min(cellSize[1],cellSize[2])), maxSize=max(cellSize[0],max(cellSize[1],cellSize[2])); if(minSize<2.1*maxSpan){ throw runtime_error("Minimum cell size is smaller than 2.1*span_of_the_biggest_body! (periodic collider requirement)"); } if(((step%globalUpdateInt)==0) || avgStiffness<0 || sigma[0]<0 || sigma[1]<0 || sigma[2]<0){ Vector3r sumForces=Shop::totalForceInVolume(avgStiffness,scene); sigma=-Vector3r(sumForces[0]/cellArea[0],sumForces[1]/cellArea[1],sumForces[2]/cellArea[2]); LOG_TRACE("Updated sigma="<0) { sigma+=(avgGrow*avgStiffness)*Vector3r::Ones(); sigAvg+=avgGrow*avgStiffness; } if(std::abs((sigAvg-sigmaGoal)/sigmaGoal)>5e-3) allStressesOK=false; cellGrow=(avgGrow/avgSize)*cellSize; } else{ // handle each dimension separately for(int axis=0; axis<3; axis++){ // Δσ=ΔεE=(Δl/l)×(l×K/A) ↔ Δl=Δσ×A/K // FIXME: either NormShearPhys::{kn,ks} is computed wrong or we have dimensionality problem here // FIXME: that is why the fixup 1e-4 is needed here // FIXME: or perhaps maxDisplaPerStep=1e-2*charLen is too big?? cellGrow[axis]=1e-4*(sigmaGoal-sigma[axis])*cellArea[axis]/(avgStiffness>0?avgStiffness:1); // FIXME: wrong dimensions? See PeriTriaxController if(std::abs(cellGrow[axis])>maxDisplPerStep) cellGrow[axis]=Mathr::Sign(cellGrow[axis])*maxDisplPerStep; cellGrow[axis]=max(cellGrow[axis],-(cellSize[axis]-2.1*maxSpan)); // crude way of predicting sigma, for steps when it is not computed from intrs if(avgStiffness>0) sigma[axis]+=cellGrow[axis]*avgStiffness; // FIXME: dimensions if(std::abs((sigma[axis]-sigmaGoal)/sigmaGoal)>5e-3) allStressesOK=false; } } TRVAR4(cellGrow,sigma,sigmaGoal,avgStiffness); assert(scene->dt>0); for(int axis=0; axis<3; axis++){ scene->cell->velGrad(axis,axis)=cellGrow[axis]/(scene->dt*scene->cell->getSize()[axis]); } // handle state transitions if(allStressesOK){ if((step%globalUpdateInt)==0) currUnbalanced=Shop::unbalancedForce(/*useMaxForce=*/false,scene); if(currUnbalancedcell->trsf(i,i)); //Compute volume of the deformed cell Real volume=scene->cell->hSize.determinant(); //Compute sum(fi*lj) and stiffness stressTensor = Matrix3r::Zero(); Vector3r sumStiff(Vector3r::Zero()); int n=0; // NOTE : This sort of loops on interactions could be removed if we had callbacks in e.g. constitutive laws // → very likely performance hit; do you have some concrete design in mind? // → a vector with functors so we can law->functs->pushback(myThing), and access to the fundamental members (forces, stiffness, normal, etc.). Implementing the second part is less clear in my mind. Inheriting from law::funct(force&, stiffness&, ...)? FOREACH(const shared_ptr&I, *scene->interactions){ if ( !I->isReal() ) continue; NormShearPhys* nsi=YADE_CAST ( I->phys.get() ); GenericSpheresContact* gsc=YADE_CAST ( I->geom.get() ); //Contact force Vector3r f= (-1.)*( nsi->normalForce+nsi->shearForce ); Vector3r branch=Body::byId(I->getId2(),scene)->state->pos + scene->cell->hSize*I->cellDist.cast() -Body::byId(I->getId1(),scene)->state->pos; stressTensor+=f*branch.transpose(); if( !dynCell ){ for ( int i=0; i<3; i++ ) sumStiff[i]+=std::abs ( gsc->normal[i] ) *nsi->kn+ ( 1-std::abs ( gsc->normal[i] ) ) *nsi->ks; n++;} } // Divide by volume as in stressTensor=sum(fi*lj)/Volume (Love equation) stressTensor /= volume; for(int axis=0; axis<3; axis++) stress[axis]=stressTensor(axis,axis); LOG_DEBUG ( "stressTensor : "<cell->getSize(); //FIXME : this is wrong I think (almost sure, B.) Vector3r cellArea=Vector3r(cellSize[1]*cellSize[2],cellSize[0]*cellSize[2],cellSize[0]*cellSize[1]); // initial updates if (maxBodySpan[0]<=0){ FOREACH(const shared_ptr& b,*scene->bodies){ if(!b || !b->bound) continue; for(int i=0; i<3; i++) if ((b->bound->max[i]-b->bound->min[i])bound->max[i]-b->bound->min[i]);} } // check current size if(2.1*maxBodySpan[0]>cellSize[0] || 2.1*maxBodySpan[1]>cellSize[1] || 2.1*maxBodySpan[2]>cellSize[2]){ LOG_DEBUG("cellSize="<iter%globUpdate)==0); if(doUpdate || min(stiff[0],min(stiff[1],stiff[2])) <=0 || dynCell){ strainStressStiffUpdate(); } // set mass to be sum of masses, if not set by the user if(dynCell && std::isnan(mass)){ mass=0; FOREACH(const shared_ptr& b, *scene->bodies){ if(b && b->state) mass+=b->state->mass; } LOG_INFO("Setting cell mass to "<dt>0.); for(int axis=0; axis<3; axis++){ Real& strain_rate = scene->cell->velGrad(axis,axis);//strain rate on axis if(stressMask & (1<dt*(goal[axis]-stress[axis])*cellArea[axis]/(stiff[axis]>0?stiff[axis]:1.); LOG_TRACE(axis<<": stress="<dt; LOG_TRACE ( axis<<": strain="<1e-6");} } } // update stress and strain if (!dynCell) for ( int axis=0; axis<3; axis++ ){ // take in account something like poisson's effect here… //Real bogusPoisson=0.25; int ax1=(axis+1)%3,ax2=(axis+2)%3; //don't modify stress if dynCell, testing only stiff[axis]>0 would not allow switching the control mode in simulations, if (stiff[axis]>0) stress[axis]+=(scene->cell->velGrad(axis,axis)*scene->dt/cellSize[axis])*(stiff[axis]/cellArea[axis]); //-bogusPoisson*(cellGrow[ax1]/refSize[ax1])*(stiff[ax1]/cellArea[ax1])-bogusPoisson*(cellGrow[ax2]/refSize[ax2])*(stiff[ax2]/cellArea[ax2]); } for (int k=0;k<3;k++) strainRate[k]=scene->cell->velGrad(k,k); //Update energy input FIXME: replace trace by norm, so it works for any kind of deformation Real dW=(0.5*(scene->cell->prevVelGrad + scene->cell->velGrad)*stressTensor).trace()*scene->dt*(scene->cell->hSize.determinant()); externalWork+=dW; if(scene->trackEnergy) scene->energy->add(-dW,"velGradWork",velGradWorkIx,/*non-incremental*/false); prevGrow = strainRate; if(allOk){ if(doUpdate || currUnbalanced<0){ currUnbalanced=Shop::unbalancedForce(/*useMaxForce=*/false,scene); LOG_DEBUG("Stress/strain="<< (stressMask&1?stress[0]:strain[0]) <<"," <<(stressMask&2?stress[1]:strain[1])<<"," <<(stressMask&4?stress[2]:strain[2]) <<", goal="<isPeriodic){ LOG_FATAL("Being used on non-periodic simulation!"); throw; } const Real& dt=scene->dt; assert(dt>0); /* "Constructor" (if (step==0) ) ps is the vector of indices, where stress is prescribed pe is the vector of indices, where strain is prescribed example: goal = 0b000110 : ps=(1,2,0,0,0,0), pe=(0,3,4,5,0,0) lenPs (lenPe) is the meaningful length of ps (pe) (the zeros at the end of ps and pe has no meaning), i.e. the number of indices with prescribed stress (strain) */ bool stressBasedSimulation=false; // true when all stresses are prescribed or if all prescribed strains equal zero if (progress==0) { lenPs=0; lenPe=0; ps=Vector6i::Zero(); pe=Vector6i::Zero(); stressGoal = Vector6r::Zero(); strainGoal = Vector6r::Zero(); for (int i=0; i<6; i++){ if (stressMask&(1<operator[](j).operator(k) is k-th element of j-th Vector2r of xxPath //#define PATH_OP_OP(pi,op1i,op2i) paths[pi]->operator[](op1i).operator()(op2i) //#define PATH_OP_OP(pi,op1i,op2i) (pi==0?xxPath:pi==1?yyPath:pi==2?zzPath:pi==3?yzPath:pi==4?zxPath:pi==5?xyPath:NULL)->operator[](op1i).operator()(op2i) #define PATH_OP_OP(pi,op1i,op2i) (pi==0?xxPath:pi==1?yyPath:pi==2?zzPath:pi==3?yzPath:pi==4?zxPath:xyPath)[op1i].operator()(op2i) for (int i=0; i<6; i++) { for (int j=1; j= PATH_OP_OP(i,j,0) ) { throw runtime_error("Peri3dCoontroller.##Path: Time in ##Path must be monothonically increasing"); }} } for (int j=0; j= 1e-9) { // the final value is not 0 (otherwise always absolute values are considered) PATH_OP_OP(i,j,1) *= goal(i)/PATH_OP_OP(i,pathSizes[i]-1,1); } } } // set weather the simulation is "stress based" (all stress components prescribed or all prescribed strains equal zero) if (lenPe == 0) { stressBasedSimulation = true; } else { stressBasedSimulation = true; for (int i=0; i= PATH_OP_OP(i,pathsCounter[i],0)) { pathsCounter[i]++; } } /* values of prescribed stress (strain) rate in respect to prescribed path. The strain indices where stress is prescribed will be overwritten by predictor */ for (int i=0; i 0){ // if at least 1 stress component is prescribed (otherwise prescribed strain is applied in all 6 directions if (progress == 0 && stressBasedSimulation) { // the very first step, use compliance estimation (compliance matrix for elastic isotropic material) Real complianceEstimation[6][6] = { {1/youngEstimation, -poissonEstimation/youngEstimation, -poissonEstimation/youngEstimation, 0,0,0}, {-poissonEstimation/youngEstimation, 1/youngEstimation, -poissonEstimation/youngEstimation, 0,0,0}, {-poissonEstimation/youngEstimation, -poissonEstimation/youngEstimation, 1/youngEstimation, 0,0,0}, {0,0,0, (1+poissonEstimation)/youngEstimation,0,0}, {0,0,0, 0,(1+poissonEstimation)/youngEstimation,0}, {0,0,0, 0,0,(1+poissonEstimation)/youngEstimation}}; for (int i=0; i0){strainRate(j) -= sr*mod;} else {strainRate(j) += sr*mod;} } } } // correction coefficient ix strainRate.maxstd::abs() > maxStrainRate Real srCorr = (strainRate.cwiseAbs().maxCoeff() > maxStrainRate)? (maxStrainRate/strainRate.cwiseAbs().maxCoeff()):1.; strainRate *= srCorr; // Actual action (see the documentation for more info) const Matrix3r& trsf=scene->cell->trsf; // compute rotational and nonrotational (strain in local coordinates) part of trsf Matrix_computeUnitaryPositive(trsf,&rot,&nonrot); // prescribed velocity gradient (strain tensor rate) in global coordinates epsilonRate = voigt_toSymmTensor(strainRate,/*strain=*/true); /* transformation of prescribed strain rate (computed by predictor) into local cell coordinates, multiplying by time to obtain strain increment and adding it to nonrot (current strain in local coordinates)*/ nonrot += rot.transpose()*(epsilonRate*dt)*rot; Matrix3r& velGrad=scene->cell->velGrad; // compute velGrad: // trsf = rot*nonrot // dTrsf = dt*velGrad // trsfNew = trsf + dTrsf*trsf = (I+dTrsf)*trsf = (I+dt*velGrad)*trsf -> velGrad velGrad = ((rot*nonrot)*trsf.inverse()- Matrix3r::Identity()) / dt ; progress += srCorr/nSteps; if (progress >= 1. || strain.cwiseAbs().maxCoeff() > maxStrain) { if(doneHook.empty()){ LOG_INFO("No doneHook set, dying."); dead=true; Omega::instance().pause(); } else{ LOG_INFO("Running doneHook: "< #pragma once #include class PeriIsoCompressor: public BoundaryController{ Real avgStiffness; Real maxDisplPerStep; Vector3r sumForces, sigma; Real currUnbalanced; public: void action(); YADE_CLASS_BASE_DOC_ATTRS_CTOR_PY(PeriIsoCompressor,BoundaryController,"Compress/decompress cloud of spheres by controlling periodic cell size until it reaches prescribed average stress, then moving to next stress value in given stress series.", ((vector,stresses,,,"Stresses that should be reached, one after another")) ((Real,charLen,-1.,,"Characteristic length, should be something like mean particle diameter (default -1=invalid value))")) ((Real,maxSpan,-1.,,"Maximum body span in terms of bbox, to prevent periodic cell getting too small. |ycomp|")) ((Real,maxUnbalanced,1e-4,,"if actual unbalanced force is smaller than this number, the packing is considered stable,")) ((int,globalUpdateInt,20,,"how often to recompute average stress, stiffness and unbalanced force")) ((size_t,state,0,,"Where are we at in the stress series")) ((string,doneHook,"",,"Python command to be run when reaching the last specified stress")) ((bool,keepProportions,true,,"Exactly keep proportions of the cell (stress is controlled based on average, not its components")), /*ctor*/ currUnbalanced=-1; avgStiffness=-1; maxDisplPerStep=-1; sumForces=Vector3r::Zero(); sigma=Vector3r::Zero(); , /*py*/ .def_readonly("currUnbalanced",&PeriIsoCompressor::currUnbalanced,"Current value of unbalanced force") .def_readonly("sigma",&PeriIsoCompressor::sigma,"Current stress value") ); DECLARE_LOGGER; }; REGISTER_SERIALIZABLE(PeriIsoCompressor); /* Engine for independently controlling stress or strain in periodic simulations. strainStress contains absolute values for the controlled quantity, and stressMask determines meaning of those values (0 for strain, 1 for stress): e.g. ( 1<<0 | 1<<2 ) = 1 | 4 = 5 means that strainStress[0] and strainStress[2] are stress values, and strainStress[1] is strain. See scripts/test/periodic-triax.py for a simple example. */ class PeriTriaxController: public BoundaryController{ public: virtual void action(); void strainStressStiffUpdate(); YADE_CLASS_BASE_DOC_ATTRS_INIT_CTOR_PY(PeriTriaxController,BoundaryController,"Engine for independently controlling stress or strain in periodic simulations.\n\n``strainStress`` contains absolute values for the controlled quantity, and ``stressMask`` determines meaning of those values (0 for strain, 1 for stress): e.g. ``( 1<<0 | 1<<2 ) = 1 | 4 = 5`` means that ``strainStress[0]`` and ``strainStress[2]`` are stress values, and ``strainStress[1]`` is strain. \n\nSee scripts/test/periodic-triax.py for a simple example.", ((bool,dynCell,false,,"Imposed stress can be controlled using the packing stiffness or by applying the laws of dynamic (dynCell=true). Don't forget to assign a :yref:`mass` to the cell.")) ((Vector3r,goal,Vector3r::Zero(),,"Desired stress or strain values (depending on stressMask), strains defined as ``strain(i)=log(Fii)``.\n\n.. warning:: Strains are relative to the :yref:`O.cell.refSize` (reference cell size), not the current one (e.g. at the moment when the new strain value is set).")) ((int,stressMask,((void)"all strains",0),,"mask determining strain/stress (0/1) meaning for goal components")) ((Vector3r,maxStrainRate,Vector3r(1,1,1),,"Maximum strain rate of the periodic cell.")) ((Real,maxUnbalanced,1e-4,,"maximum unbalanced force.")) ((Real,absStressTol,1e3,,"Absolute stress tolerance")) ((Real,relStressTol,3e-5,,"Relative stress tolerance")) ((Real,growDamping,.25,,"Damping of cell resizing (0=perfect control, 1=no control at all); see also ``wallDamping`` in :yref:`TriaxialStressController`.")) ((int,globUpdate,5,,"How often to recompute average stress, stiffness and unbalaced force.")) ((string,doneHook,,,"python command to be run when the desired state is reached")) ((Vector3r,maxBodySpan,Vector3r::Zero(),,"maximum body dimension |ycomp|")) ((Matrix3r,stressTensor,Matrix3r::Zero(),,"average stresses, updated at every step (only every globUpdate steps recomputed from interactions if !dynCell)")) ((Vector3r,stress,Vector3r::Zero(),,"diagonal terms of the stress tensor")) ((Vector3r,strain,Vector3r::Zero(),,"cell strain |yupdate|")) ((Vector3r,strainRate,Vector3r::Zero(),,"cell strain rate |yupdate|")) ((Vector3r,stiff,Vector3r::Zero(),,"average stiffness (only every globUpdate steps recomputed from interactions) |yupdate|")) ((Real,currUnbalanced,NaN,,"current unbalanced force (updated every globUpdate) |yupdate|")) ((Vector3r,prevGrow,Vector3r::Zero(),,"previous cell grow")) ((Real,mass,NaN,,"mass of the cell (user set); if not set and :yref:`dynCell` is used, it will be computed as sum of masses of all particles.")) ((Real,externalWork,0,,"Work input from boundary controller.")) ((int,velGradWorkIx,-1,(Attr::hidden|Attr::noSave),"Index for work done by velocity gradient, if tracking energy")) ,,, ); DECLARE_LOGGER; }; REGISTER_SERIALIZABLE(PeriTriaxController); class Peri3dController: public BoundaryController{ public: Vector6r stressOld; Matrix3r sigma, epsilon, epsilonRate, rot, nonrot; virtual void action(); YADE_CLASS_BASE_DOC_ATTRS_CTOR_PY(Peri3dController,BoundaryController,"Experimental controller of full strain/stress tensors on periodic cell. Detailed documentation is in py/_extraDocs.py.", ((Vector6r,stress,Vector6r::Zero(),,"Current stress vector ($\\sigma_x$,$\\sigma_y$,$\\sigma_z$,$\\tau_{yz}$,$\\tau_{zx}$,$\\tau_{xy}$)|yupdate|.")) ((Vector6r,strain,Vector6r::Zero(),,"Current strain (deformation) vector ($\\varepsilon_x$,$\\varepsilon_y$,$\\varepsilon_z$,$\\gamma_{yz}$,$\\gamma_{zx}$,$\\gamma_{xy}$) |yupdate|.")) ((Vector6r,strainRate,Vector6r::Zero(),,"Current strain rate vector.")) ((Vector6r,stressRate,Vector6r::Zero(),,"Current stress rate vector (that is prescribed, the actual one slightly differ).")) ((Vector6r,stressIdeal,Vector6r::Zero(),,"Ideal stress vector at current time step.")) ((Vector6r,goal,Vector6r::Zero(),,"Goal state; only the upper triangular matrix is considered; each component is either prescribed stress or strain, depending on :yref:`stressMask`.")) ((int,stressMask,((void)"all strains",0),,"mask determining whether components of :yref:`goal` are strain (0) or stress (1). The order is 00,11,22,12,02,01 from the least significant bit. (e.g. 0b000011 is stress 00 and stress 11).")) ((int,nSteps,1000,,"Number of steps of the simulation.")) ((Real,progress,0.,,"Actual progress of the simulation with Controller.")) ((Real,mod,.1,,"Predictor modificator, by trail-and-error analysis the value 0.1 was found as the best.")) ((string,doneHook,,,"Python command (as string) to run when :yref:`nSteps` is achieved. If empty, the engine will be set :yref:`dead`.")) ((vector,xxPath,vector(1,Vector2r::Ones()),,"\"Time function\" (piecewise linear) for xx direction. Sequence of couples of numbers. First number is time, second number desired value of respective quantity (stress or strain). The last couple is considered as final state (equal to (:yref:`nSteps`, :yref:`goal`)), other values are relative to this state.\n\nExample: nSteps=1000, goal[0]=300, xxPath=((2,3),(4,1),(5,2))\n\nat step 400 (=5*1000/2) the value is 450 (=3*300/2),\n\nat step 800 (=4*1000/5) the value is 150 (=1*300/2),\n\nat step 1000 (=5*1000/5=nSteps) the value is 300 (=2*300/2=goal[0]).\n\nSee example :ysrc:`scripts/test/peri3dController_example1` for illusration.")) ((vector,yyPath,vector(1,Vector2r::Ones()),,"Time function for yy direction, see :yref:`xxPath`")) ((vector,zzPath,vector(1,Vector2r::Ones()),,"Time function for zz direction, see :yref:`xxPath`")) ((vector,yzPath,vector(1,Vector2r::Ones()),,"Time function for yz direction, see :yref:`xxPath`")) ((vector,zxPath,vector(1,Vector2r::Ones()),,"Time function for zx direction, see :yref:`xxPath`")) ((vector,xyPath,vector(1,Vector2r::Ones()),,"Time function for xy direction, see :yref:`xxPath`")) ((Real,maxStrainRate,1e3,,"Maximal absolute value of strain rate (both normal and shear components of :yref:`strain`)")) ((Real,maxStrain,1e6,,"Maximal asolute value of :yref:`strain` allowed in the simulation. If reached, the simulation is considered as finished")) ((Real,youngEstimation,1e20,,"Estimation of macroscopic Young's modulus, used for the first simulation step")) ((Real,poissonEstimation,.25,,"Estimation of macroscopic Poisson's ratio, used used for the first simulation step")) // ((Vector6r,stressGoal,Vector6r::Zero(),Attr::readonly,"Peri3dController internal variable")) ((Vector6r,strainGoal,Vector6r::Zero(),Attr::readonly,"Peri3dController internal variable")) ((Vector6i,pe,Vector6i::Zero(),Attr::readonly,"Peri3dController internal variable")) ((Vector6i,ps,Vector6i::Zero(),Attr::readonly,"Peri3dController internal variable")) ((Vector6i,pathSizes,Vector6i::Zero(),Attr::readonly,"Peri3dController internal variable")) ((Vector6i,pathsCounter,Vector6i::Zero(),Attr::readonly,"Peri3dController internal variable")) ((int,lenPe,0,Attr::readonly,"Peri3dController internal variable")) ((int,lenPs,0,Attr::readonly,"Peri3dController internal variable")) , /*ctor*/ , /*py*/ ); }; REGISTER_SERIALIZABLE(Peri3dController); trunk-2018.02b/pkg/dem/Polyhedra.cpp000066400000000000000000000551301324306050200171530ustar00rootroot00000000000000// © 2013 Jan Elias, http://www.fce.vutbr.cz/STM/elias.j/, elias.j@fce.vutbr.cz // https://www.vutbr.cz/www_base/gigadisk.php?i=95194aa9a #ifdef YADE_CGAL // NDEBUG causes crashes in CGAL sometimes. Anton #ifdef NDEBUG #undef NDEBUG #endif #include "Polyhedra.hpp" YADE_PLUGIN(/* self-contained in hpp: */ (Polyhedra) (PolyhedraGeom) (Bo1_Polyhedra_Aabb) (PolyhedraPhys) (PolyhedraMat) (Ip2_PolyhedraMat_PolyhedraMat_PolyhedraPhys) (Ip2_FrictMat_PolyhedraMat_FrictPhys) (Law2_PolyhedraGeom_PolyhedraPhys_Volumetric) /* some code in cpp (this file): */ #ifdef YADE_OPENGL (Gl1_Polyhedra) (Gl1_PolyhedraGeom) (Gl1_PolyhedraPhys) #endif ); //********************************************************************************* /* Polyhedra Constructor */ Polyhedra::Polyhedra(const std::vector && V) { createIndex(); v = V; Initialize(); } Polyhedra::Polyhedra (const Vector3r && xsize, const int && xseed) { createIndex(); seed=xseed; size=xsize; v.clear(); Initialize(); } void Polyhedra::Initialize(){ if (init) return; bool isRandom = false; //get vertices int N = (int) v.size(); if (N==0) { //generate randomly while ((int) v.size()<4) GenerateRandomGeometry(); N = (int) v.size(); isRandom = true; } //compute convex hull of vertices std::vector points; points.resize(v.size()); for(int i=0;ipoint().x(),vIter->point().y(),vIter->point().z())); } //list surface triangles for plotting faceTri.clear(); std::transform(P.facets_begin(), P.facets_end(), P.planes_begin(),Plane_equation()); for (Polyhedron::Facet_iterator fIter = P.facets_begin(); fIter != P.facets_end(); fIter++){ Polyhedron::Halfedge_around_facet_circulator hfc0; int n = fIter->facet_degree(); hfc0 = fIter->facet_begin(); int a = std::distance(P.vertices_begin(), hfc0->vertex()); for (int i=2; ivertex())); faceTri.push_back(std::distance(P.vertices_begin(), hfc0->next()->vertex())); } } //compute centroid and volume P_volume_centroid(P, &volume, ¢roid); //check vierd behavior of CGAL in tessalation if(isRandom && volume*1.75<4./3.*3.14*size[0]/2.*size[1]/2.*size[2]/2.) { v.clear(); seed = rand(); Initialize(); } Vector3r translation((-1)*centroid); //set centroid to be [0,0,0] for(int i=0;i& v) { init = false; this->v = v; Initialize(); } void Polyhedra::setVertices4(const Vector3r& v0, const Vector3r& v1,const Vector3r& v2,const Vector3r& v3) { init = false; v.resize(4); v[0] = v0; v[1] = v1; v[2] = v2; v[3] = v3; Initialize(); } //************************************************************************** /* Generator of randomly shaped polyhedron based on Voronoi tessellation*/ void Polyhedra::GenerateRandomGeometry(){ srand(seed); vector nuclei; nuclei.push_back(CGALpoint(5.,5.,5.)); CGALpoint trial; unsigned int iter = 0; bool isOK; //fill box 5x5x5 with randomly located nuclei with restricted minimal mutual distance 0.75 which gives approximate mean mutual distance 1; Real dist_min2 = 0.75*0.75; while(iter<500){ isOK = true; iter++; trial = CGALpoint(Real(rand())/RAND_MAX*5.+2.5,Real(rand())/RAND_MAX*5.+2.5,Real(rand())/RAND_MAX*5.+2.5); for(int i=0;i< (int) nuclei.size();i++) { isOK = pow(nuclei[i].x()-trial.x(),2)+pow(nuclei[i].y()-trial.y(),2)+pow(nuclei[i].z()-trial.z(),2) > dist_min2; if (!isOK) break; } if(isOK){ iter = 0; nuclei.push_back(trial); } } //perform Voronoi tessellation nuclei.erase(nuclei.begin()); Triangulation dt(nuclei.begin(), nuclei.end()); Triangulation::Vertex_handle zero_point = dt.insert(CGALpoint(5.,5.,5.)); v.clear(); std::vector ch_cells; dt.incident_cells(zero_point,std::back_inserter(ch_cells)); for(auto ci = ch_cells.begin(); ci !=ch_cells.end(); ++ci){ v.push_back(FromCGALPoint(dt.dual(*ci))-Vector3r(5.,5.,5.)); } //resize and rotate the voronoi cell Quaternionr Rot(Real(rand())/RAND_MAX,Real(rand())/RAND_MAX,Real(rand())/RAND_MAX,Real(rand())/RAND_MAX); Rot.normalize(); for(int i=0; i< (int) v.size();i++) { v[i] = Rot*(Vector3r(v[i][0]*size[0],v[i][1]*size[1],v[i][2]*size[2])); } //to avoid patological cases (that should not be present, but CGAL works somehow unpredicable) if (v.size() < 8) { cout << "wrong " << v.size() << endl; v.clear(); seed = rand(); GenerateRandomGeometry(); } } //************************************************************************** /* Get polyhdral surfaces */ vector> Polyhedra::GetSurfaces() const { vector> ret(P.size_of_facets()); int i=0; for (Polyhedron::Facet_const_iterator f = P.facets_begin(); f != P.facets_end(); f++, i++){ Polyhedron::Halfedge_around_facet_const_circulator h=f->facet_begin(); do { ret[i].push_back(std::distance(P.vertices_begin(), h->vertex())); } while (++h != f->facet_begin()); } return ret; } Vector3r Polyhedra::GetCentroid() { Initialize(); return centroid; } Vector3r Polyhedra::GetInertia() { Initialize(); return inertia; } vector Polyhedra::GetSurfaceTriangulation() { Initialize(); return faceTri; } Real Polyhedra::GetVolume() { Initialize(); return volume; } Quaternionr Polyhedra::GetOri() { Initialize(); return orientation; } void Polyhedra::Clear() { v.clear(); P.clear(); init = 0; size = Vector3r(1.,1.,1.); faceTri.clear(); } bool Polyhedra::IsInitialized() const { return init; } Polyhedron Polyhedra::GetPolyhedron() const { return P; } //**************************************************************************************** /* Destructor */ Polyhedra::~Polyhedra(){} //**************************************************************************************** Real PolyhedraMat::GetStrength() const {return strength;}; Real PolyhedraMat::GetStrengthTau() const {return strengthTau;}; Real PolyhedraMat::GetStrengthSigmaCZ() const {return sigmaCZ;}; Real PolyhedraMat::GetStrengthSigmaCD() const {return sigmaCD;}; int PolyhedraMat::GetWeiM() const {return Wei_m;}; Real PolyhedraMat::GetWeiS0() const {return Wei_S0;}; Real PolyhedraMat::GetWeiV0() const {return Wei_V0;}; Real PolyhedraMat::GetP() const {return Wei_P;}; //**************************************************************************************** /* Destructor */ PolyhedraGeom::~PolyhedraGeom(){} //**************************************************************************************** /* AaBb overlap checker */ void Bo1_Polyhedra_Aabb::go(const shared_ptr& ig, shared_ptr& bv, const Se3r& se3, const Body*){ Polyhedra* t=static_cast(ig.get()); if (!t->IsInitialized()) t->Initialize(); if(!bv){ bv=shared_ptr(new Aabb); } Aabb* aabb=static_cast(bv.get()); //Quaternionr invRot=se3.orientation.conjugate(); int N = (int) t->v.size(); Vector3r v_g, mincoords(0.,0.,0.), maxcoords(0.,0.,0.); for(int i=0; iv[i]; // vertices in global coordinates mincoords = Vector3r(min(mincoords[0],v_g[0]),min(mincoords[1],v_g[1]),min(mincoords[2],v_g[2])); maxcoords = Vector3r(max(maxcoords[0],v_g[0]),max(maxcoords[1],v_g[1]),max(maxcoords[2],v_g[2])); } if (aabbEnlargeFactor>0) { mincoords *= aabbEnlargeFactor; maxcoords *= aabbEnlargeFactor; } aabb->min=se3.position+mincoords; aabb->max=se3.position+maxcoords; } //********************************************************************************** /* Plotting */ #ifdef YADE_OPENGL #include bool Gl1_Polyhedra::wire; void Gl1_Polyhedra::go(const shared_ptr& cm, const shared_ptr&,bool wire2,const GLViewInfo&) { glMaterialv(GL_FRONT,GL_AMBIENT_AND_DIFFUSE,Vector3r(cm->color[0],cm->color[1],cm->color[2])); glColor3v(cm->color); Polyhedra* t=static_cast(cm.get()); vector faceTri = t->GetSurfaceTriangulation(); if (wire || wire2) { glDisable(GL_LIGHTING); glBegin(GL_LINES); for(int tri=0; tri < (int) faceTri.size(); tri+=3) { glOneWire(t, faceTri[tri], faceTri[tri+1]); glOneWire(t, faceTri[tri], faceTri[tri+2]); glOneWire(t, faceTri[tri+1], faceTri[tri+2]); } glEnd(); } else { Vector3r centroid = t->GetCentroid(); glDisable(GL_CULL_FACE); glEnable(GL_LIGHTING); glBegin(GL_TRIANGLES); for(int tri=0; tri < (int) faceTri.size(); tri+=3) { const auto a = faceTri[tri+0]; const auto b = faceTri[tri+1]; const auto c = faceTri[tri+2]; Vector3r n=(t->v[b]-t->v[a]).cross(t->v[c]-t->v[a]); n.normalize(); Vector3r faceCenter=(t->v[a]+t->v[b]+t->v[c])/3.; if((faceCenter-centroid).dot(n)<0) n=-n; glNormal3v(n); glVertex3v(t->v[a]); glVertex3v(t->v[b]); glVertex3v(t->v[c]); } glEnd(); } } void Gl1_PolyhedraGeom::go(const shared_ptr& ig, const shared_ptr&, const shared_ptr&, const shared_ptr&, bool) {draw(ig);} void Gl1_PolyhedraGeom::draw(const shared_ptr& ig){}; GLUquadric* Gl1_PolyhedraPhys::gluQuadric=NULL; Real Gl1_PolyhedraPhys::maxFn; Real Gl1_PolyhedraPhys::refRadius; Real Gl1_PolyhedraPhys::maxRadius; int Gl1_PolyhedraPhys::signFilter; int Gl1_PolyhedraPhys::slices; int Gl1_PolyhedraPhys::stacks; void Gl1_PolyhedraPhys::go(const shared_ptr& ip, const shared_ptr& i, const shared_ptr& b1, const shared_ptr& b2, bool wireFrame){ if(!gluQuadric){ gluQuadric=gluNewQuadric(); if(!gluQuadric) throw runtime_error("Gl1_PolyhedraPhys::go unable to allocate new GLUquadric object (out of memory?)."); } PolyhedraPhys* np=static_cast(ip.get()); shared_ptr ig(i->geom); if(!ig) return; // changed meanwhile? PolyhedraGeom* geom=YADE_CAST(ig.get()); Real fnNorm=np->normalForce.dot(geom->normal); if((signFilter>0 && fnNorm<0) || (signFilter<0 && fnNorm>0)) return; int fnSign=fnNorm>0?1:-1; fnNorm=std::abs(fnNorm); Real radiusScale=1.; maxFn=max(fnNorm,maxFn); Real realMaxRadius; if(maxRadius<0){ refRadius=min(0.03,refRadius); realMaxRadius=refRadius; } else realMaxRadius=maxRadius; Real radius=radiusScale*realMaxRadius*(fnNorm/maxFn); if (radius<=0.) radius = 1E-8; Vector3r color=Shop::scalarOnColorScale(fnNorm*fnSign,-maxFn,maxFn); Vector3r p1=b1->state->pos, p2=b2->state->pos; Vector3r relPos; relPos=p2-p1; Real dist=relPos.norm(); glDisable(GL_CULL_FACE); glPushMatrix(); glTranslatef(p1[0],p1[1],p1[2]); Quaternionr q(Quaternionr().setFromTwoVectors(Vector3r(0,0,1),relPos/dist /* normalized */)); // using Transform with OpenGL: http://eigen.tuxfamily.org/dox/TutorialGeometry.html //glMultMatrixd(Eigen::Affine3d(q).data()); glMultMatrix(Eigen::Transform(q).data()); glColor3v(color); gluCylinder(gluQuadric,radius,radius,dist,slices,stacks); glPopMatrix(); } #endif //********************************************************************************** //!Precompute data needed for rotating tangent vectors attached to the interaction void PolyhedraGeom::precompute(const State& rbp1, const State& rbp2, const Scene* scene, const shared_ptr& c, const Vector3r& currentNormal, bool isNew, const Vector3r& shift2) { if(!isNew) { orthonormal_axis = normal.cross(currentNormal); Real angle = scene->dt*0.5*normal.dot(rbp1.angVel + rbp2.angVel); twist_axis = angle*normal;} else twist_axis=orthonormal_axis=Vector3r::Zero(); //Update contact normal normal=currentNormal; //Precompute shear increment Vector3r c1x = (contactPoint - rbp1.pos); Vector3r c2x = (contactPoint - rbp2.pos + shift2); Vector3r relativeVelocity = (rbp2.vel+rbp2.angVel.cross(c2x)) - (rbp1.vel+rbp1.angVel.cross(c1x)); //keep the shear part only relativeVelocity = relativeVelocity-normal.dot(relativeVelocity)*normal; shearInc = relativeVelocity*scene->dt; } //********************************************************************************** Vector3r& PolyhedraGeom::rotate(Vector3r& shearForce) const { // approximated rotations shearForce -= shearForce.cross(orthonormal_axis); shearForce -= shearForce.cross(twist_axis); //NOTE : make sure it is in the tangent plane? It's never been done before. Is it not adding rounding errors at the same time in fact?... shearForce -= normal.dot(shearForce)*normal; return shearForce; } //********************************************************************************** /* Material law, physics */ void Ip2_PolyhedraMat_PolyhedraMat_PolyhedraPhys::go( const shared_ptr& b1 , const shared_ptr& b2 , const shared_ptr& interaction) { if(interaction->phys) return; const shared_ptr& mat1 = YADE_PTR_CAST(b1); const shared_ptr& mat2 = YADE_PTR_CAST(b2); interaction->phys = shared_ptr(new PolyhedraPhys()); const shared_ptr& contactPhysics = YADE_PTR_CAST(interaction->phys); Real Kna = mat1->young; Real Knb = mat2->young; Real Ksa = mat1->young*mat1->poisson; Real Ksb = mat2->young*mat2->poisson; Real frictionAngle = std::min(mat1->frictionAngle,mat2->frictionAngle); contactPhysics->tangensOfFrictionAngle = std::tan(frictionAngle); contactPhysics->kn = Kna*Knb/(Kna+Knb); contactPhysics->ks = Ksa*Ksb/(Ksa+Ksb); }; void Ip2_FrictMat_PolyhedraMat_FrictPhys::go(const shared_ptr& pp1, const shared_ptr& pp2, const shared_ptr& interaction){ const shared_ptr& mat1 = YADE_PTR_CAST(pp1); const shared_ptr& mat2 = YADE_PTR_CAST(pp2); Ip2_FrictMat_FrictMat_FrictPhys().go(mat1,mat2,interaction); } //************************************************************************************** Real Law2_PolyhedraGeom_PolyhedraPhys_Volumetric::getPlasticDissipation() {return (Real) plasticDissipation;} void Law2_PolyhedraGeom_PolyhedraPhys_Volumetric::initPlasticDissipation(Real initVal) {plasticDissipation.reset(); plasticDissipation+=initVal;} Real Law2_PolyhedraGeom_PolyhedraPhys_Volumetric::elasticEnergy() { Real energy=0; FOREACH(const shared_ptr& I, *scene->interactions){ if(!I->isReal()) continue; FrictPhys* phys = dynamic_cast(I->phys.get()); if(phys) { energy += 0.5*(phys->normalForce.squaredNorm()/phys->kn + phys->shearForce.squaredNorm()/phys->ks);} } return energy; } //************************************************************************************** // Apply forces on polyhedrons in collision based on geometric configuration bool Law2_PolyhedraGeom_PolyhedraPhys_Volumetric::go(shared_ptr& ig, shared_ptr& ip, Interaction* I){ if (!I->geom) {return true;} const shared_ptr& contactGeom(YADE_PTR_DYN_CAST(I->geom)); if(!contactGeom) {return true;} const Body::id_t idA=I->getId1(), idB=I->getId2(); const shared_ptr& A=Body::byId(idA), B=Body::byId(idB); PolyhedraPhys* phys = dynamic_cast(I->phys.get()); //erase the interaction when aAbB shows separation, otherwise keep it to be able to store previous separating plane for fast detection of separation Vector3r shift2=scene->cell->hSize*I->cellDist.cast(); if (A->bound->min[0] >= B->bound->max[0]+shift2[0] || B->bound->min[0]+shift2[0] >= A->bound->max[0] || A->bound->min[1] >= B->bound->max[1]+shift2[1] || B->bound->min[1]+shift2[1] >= A->bound->max[1] || A->bound->min[2] >= B->bound->max[2]+shift2[2] || B->bound->min[2]+shift2[2] >= A->bound->max[2]) { return false; } //zero penetration depth means no interaction force if(!(contactGeom->equivalentPenetrationDepth > 1E-18) || !(contactGeom->penetrationVolume > 0)) { phys->normalForce = Vector3r(0.,0.,0.); phys->shearForce = Vector3r(0.,0.,0.); return true; } Real prop = std::pow(contactGeom->penetrationVolume,volumePower); Vector3r normalForce=contactGeom->normal*prop*phys->kn; //shear force: in case the polyhdras are separated and come to contact again, one //should not use the previous shear force if (contactGeom->isShearNew) shearForce = Vector3r::Zero(); else shearForce = contactGeom->rotate(shearForce); const Vector3r& shearDisp = contactGeom->shearInc; shearForce -= phys->ks*shearDisp; const Real maxFs = normalForce.squaredNorm()*std::pow(phys->tangensOfFrictionAngle,2); if(shearForce.squaredNorm() > maxFs && maxFs){ //PFC3d SlipModel, is using friction angle. CoulombCriterion Real ratio = sqrt(maxFs) / shearForce.norm(); if (std::isinf(ratio)) { LOG_DEBUG("shearForce.squaredNorm() > maxFs && maxFs: " << (shearForce.squaredNorm() > maxFs && maxFs)); // the condition should be 1 (we are in this branch), but is actually 0 LOG_DEBUG("shearForce: "<trackEnergy && traceEnergy) { const Real dissip=((1/phys->ks)*(trialForce-shearForce)).dot(shearForce); if (traceEnergy) plasticDissipation += dissip; else if(dissip>0) scene->energy->add(dissip,"plastDissip",plastDissipIx,false); // compute elastic energy as well scene->energy->add(0.5*(normalForce.squaredNorm()/phys->kn+shearForce.squaredNorm()/phys->ks), "elastPotential",elastPotentialIx,true); } } else { if (maxFs==0) shearForce = Vector3r::Zero(); scene->energy->add(0.5*(normalForce.squaredNorm()/phys->kn+shearForce.squaredNorm()/phys->ks), "elastPotential",elastPotentialIx,true); } Vector3r F = -normalForce-shearForce; if (contactGeom->equivalentPenetrationDepth != contactGeom->equivalentPenetrationDepth) exit(1); scene->forces.addForce (idA,F); scene->forces.addForce (idB, -F); scene->forces.addTorque(idA, -(A->state->pos-contactGeom->contactPoint).cross(F)); scene->forces.addTorque(idB, (B->state->pos-contactGeom->contactPoint).cross(F)); /* FILE * fin = fopen("Forces.dat","a"); fprintf(fin,"************** IDS %d %d **************\n",idA, idB); Vector3r T = (B->state->pos-contactGeom->contactPoint).cross(F); fprintf(fin,"volume\t%e\n",contactGeom->penetrationVolume); fprintf(fin,"normal_force\t%e\t%e\t%e\n",normalForce[0],normalForce[1],normalForce[2]); fprintf(fin,"shear_force\t%e\t%e\t%e\n",shearForce[0],shearForce[1],shearForce[2]); fprintf(fin,"total_force\t%e\t%e\t%e\n",F[0],F[1],F[2]); fprintf(fin,"torsion\t%e\t%e\t%e\n",T[0],T[1],T[2]); fprintf(fin,"A\t%e\t%e\t%e\n",A->state->pos[0],A->state->pos[1],A->state->pos[2]); fprintf(fin,"B\t%e\t%e\t%e\n",B->state->pos[0],B->state->pos[1],B->state->pos[2]); fprintf(fin,"centroid\t%e\t%e\t%e\n",contactGeom->contactPoint[0],contactGeom->contactPoint[1],contactGeom->contactPoint[2]); fclose(fin); */ //needed to be able to acces interaction forces in other parts of yade phys->normalForce = normalForce; phys->shearForce = shearForce; return true; } #endif // YADE_CGAL trunk-2018.02b/pkg/dem/Polyhedra.hpp000066400000000000000000000406571324306050200171700ustar00rootroot00000000000000// © 2013 Jan Elias, http://www.fce.vutbr.cz/STM/elias.j/, elias.j@fce.vutbr.cz // https://www.vutbr.cz/www_base/gigadisk.php?i=95194aa9a #pragma once #ifdef YADE_CGAL // NDEBUG causes crashes in CGAL sometimes. Anton #ifdef NDEBUG #undef NDEBUG #endif #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define likely(x) __builtin_expect((x),1) #define unlikely(x) __builtin_expect((x),0) //CGAL definitions - does not work with another kernel!! Why??? using K = CGAL::Exact_predicates_inexact_constructions_kernel; using Polyhedron = CGAL::Polyhedron_3; using Triangulation = CGAL::Delaunay_triangulation_3; using CGALpoint = K::Point_3; using CGALtriangle = K::Triangle_3; using CGALvector = K::Vector_3; using Transformation = CGAL::Aff_transformation_3; using Segment = K::Segment_3; using Triangle = CGAL::Triangle_3; using Plane = CGAL::Plane_3; using Line = CGAL::Line_3; using CGAL_ORIGIN = CGAL::Origin; using CGAL_AABB_tree = CGAL::AABB_tree::iterator>>>; //********************************************************************************** class Polyhedra: public Shape{ public: //constructor from Vertices Polyhedra(const std::vector && V); Polyhedra(const Vector3r && xsize, const int && xseed); //contructor of "random" polyhedra virtual ~Polyhedra(); Vector3r GetCentroid(); Vector3r GetInertia(); vector GetSurfaceTriangulation(); vector> GetSurfaces() const; void Initialize(); bool IsInitialized() const; Real GetVolume(); Quaternionr GetOri(); Polyhedron GetPolyhedron() const; void Clear(); void setVertices(const std::vector& v); void setVertices4(const Vector3r& v0, const Vector3r& v1,const Vector3r& v2,const Vector3r& v3); protected: //triangulation of facets for plotting vector faceTri; //centroid = (0,0,0) for random Polyhedra Vector3r centroid; //CGAL structure Polyhedron Polyhedron P; //sign of performed initialization bool init; //centroid Volume Real volume; //centroid inerta - diagonal of the tensor Vector3r inertia; //orientation, that provides diagonal inertia tensor Quaternionr orientation; void GenerateRandomGeometry(); YADE_CLASS_BASE_DOC_ATTRS_INIT_CTOR_PY(Polyhedra,Shape,"Polyhedral (convex) geometry.", ((std::vector,v,,,"Tetrahedron vertices in global coordinate system.")) ((int,seed, time(NULL),,"Seed for random generator.")) ((Vector3r, size, Vector3r(1.,1.,1.),,"Size of the grain in meters - x,y,z - before random rotation")), /*init*/, /*ctor*/ createIndex(); init = 0, .def("Initialize",&Polyhedra::Initialize,"Initialization") .def("GetVolume",&Polyhedra::GetVolume,"return polyhedra's volume") .def("GetInertia",&Polyhedra::GetInertia,"return polyhedra's inertia tensor") .def("GetOri",&Polyhedra::GetOri,"return polyhedra's orientation") .def("GetCentroid",&Polyhedra::GetCentroid,"return polyhedra's centroid") .def("GetSurfaceTriangulation",&Polyhedra::GetSurfaceTriangulation,"triangulation of facets (for plotting)") .def("GetSurfaces",&Polyhedra::GetSurfaces,"get indices of surfaces' vertices (for postprocessing)") .def("setVertices",&Polyhedra::setVertices,"set vertices and update receiver. Takes a list/tuple of vertices as argument.\n\n.. note:: Causes memory leaks, so if you want to use it maaaany times, use one of setVertices mentioned lower, passing each vertex as individual argument (currently only setVertices(v1,v2,v3,v4) for tetrahedron is implemented, on request it is easy to implement more vertices).") .def("setVertices4",&Polyhedra::setVertices4,"set 4 vertices and update receiver. Each vertex is single argument.") ); REGISTER_CLASS_INDEX(Polyhedra,Shape); }; REGISTER_SERIALIZABLE(Polyhedra); //*************************************************************************** /*! Collision configuration for Polyhedra and something. * This is expressed as penetration volume properties: centroid, volume, depth ... * * Self-contained. */ class PolyhedraGeom: public IGeom{ public: virtual ~PolyhedraGeom(); //precompute data for shear evaluation void precompute(const State& rbp1, const State& rbp2, const Scene* scene, const shared_ptr& c, const Vector3r& currentNormal, bool isNew, const Vector3r& shift2); Vector3r& rotate(Vector3r& shearForce) const; //sep_plane is a code storing plane, that previously separated two polyhedras. It is used for faster detection of non-overlap. std::vector sep_plane; bool isShearNew; protected: YADE_CLASS_BASE_DOC_ATTRS_CTOR(PolyhedraGeom,IGeom,"Geometry of interaction between 2 :yref:`vector`, including volumetric characteristics", ((Real,penetrationVolume,NaN,,"Volume of overlap [m³]")) ((Real,equivalentCrossSection,NaN,,"Cross-section area of the overlap (perpendicular to the normal) - not used")) ((Real,equivalentPenetrationDepth,NaN,,"volume / equivalentCrossSection - not used")) ((Vector3r,contactPoint,Vector3r::Zero(),,"Contact point (global coords), centriod of the overlapping polyhedron")) ((Vector3r,shearInc,Vector3r::Zero(),,"Shear displacement increment in the last step")) ((Vector3r,normal,Vector3r::Zero(),,"Normal direction of the interaction")) ((Vector3r,twist_axis,Vector3r::Zero(),,"")) ((Vector3r,orthonormal_axis,Vector3r::Zero(),,"")), createIndex(); sep_plane.assign(3,0); ); //FUNCTOR2D(Tetra,Tetra); REGISTER_CLASS_INDEX(PolyhedraGeom,IGeom); }; REGISTER_SERIALIZABLE(PolyhedraGeom); //*************************************************************************** /*! Creates Aabb from Polyhedra. * * Self-contained. */ class Bo1_Polyhedra_Aabb: public BoundFunctor{ public: void go(const shared_ptr& ig, shared_ptr& bv, const Se3r& se3, const Body*); FUNCTOR1D(Polyhedra); YADE_CLASS_BASE_DOC_ATTRS(Bo1_Polyhedra_Aabb,BoundFunctor,"Create/update :yref:`Aabb` of a :yref:`Polyhedra`", ((Real,aabbEnlargeFactor,((void)"deactivated",-1),,"see :yref:`Bo1_Sphere_Aabb.aabbEnlargeFactor`")) ); }; REGISTER_SERIALIZABLE(Bo1_Polyhedra_Aabb); //*************************************************************************** /*! Elastic material */ class PolyhedraMat: public FrictMat{ public: Real GetStrength() const; Real GetStrengthTau() const; Real GetStrengthSigmaCZ() const; Real GetStrengthSigmaCD() const; int GetWeiM() const; Real GetWeiS0() const; Real GetWeiV0() const; Real GetP() const; virtual ~PolyhedraMat(){}; YADE_CLASS_BASE_DOC_ATTRS_CTOR(PolyhedraMat,FrictMat,"Elastic material with Coulomb friction.", ((bool,IsSplitable,0,,"To be splitted ... or not")) ((Real,strength,100,,"Stress at which polyhedra of volume 4/3*pi [mm] breaks.")) ((Real,strengthTau,-1,,"Tangential stress at which polyhedra of volume 4/3*pi [mm] breaks.")) ((Real,sigmaCZ,-1,,"Mohr-Coulomb failure criterium SigmaCZ, Pa, maximal tensile strength (if negative - disabled), [Gladky2017]_")) ((Real,sigmaCD,-1,,"Mohr-Coulomb failure criterium SigmaCD, Pa, maximal compressive strength (if negative - disabled), [Gladky2017]_")) ((int,Wei_m,-1,,"Weibull Formulation, Weibull modulus, m, (if negative - disabled), [Gladky2017]_")) ((Real,Wei_S0,-1,,"Weibull Formulation, Sigma0, Pa, (if negative - disabled), [Gladky2017]_")) ((Real,Wei_V0,1e-9,,"Weibull Formulation, V0, m^3, representative volume, [Gladky2017]_.")) ((Real,Wei_P,-1,,"Weibull Formulation, failure probability, P, [Gladky2017]_.")) ((Real,young,1e8,,"Young modulus")), /*ctor*/ createIndex(); ); REGISTER_CLASS_INDEX(PolyhedraMat,FrictMat); }; REGISTER_SERIALIZABLE(PolyhedraMat); //*************************************************************************** class PolyhedraPhys: public FrictPhys{ public: virtual ~PolyhedraPhys(){}; YADE_CLASS_BASE_DOC_ATTRS_CTOR(PolyhedraPhys,FrictPhys,"Simple elastic material with friction for volumetric constitutive laws", /*attrs*/ , /*ctor*/ createIndex(); ); REGISTER_CLASS_INDEX(PolyhedraPhys,FrictPhys); }; REGISTER_SERIALIZABLE(PolyhedraPhys); //*************************************************************************** #ifdef YADE_OPENGL #include #include #include #include #include /*! Draw Polyhedra using OpenGL */ class Gl1_Polyhedra: public GlShapeFunctor{ public: virtual void go(const shared_ptr&, const shared_ptr&,bool,const GLViewInfo&); YADE_CLASS_BASE_DOC_STATICATTRS(Gl1_Polyhedra,GlShapeFunctor,"Renders :yref:`Polyhedra` object", ((bool,wire,false,,"Only show wireframe")) ); RENDERS(Polyhedra); }; REGISTER_SERIALIZABLE(Gl1_Polyhedra); struct Gl1_PolyhedraGeom: public GlIGeomFunctor{ RENDERS(PolyhedraGeom); void go(const shared_ptr&, const shared_ptr&, const shared_ptr&, const shared_ptr&, bool); void draw(const shared_ptr&); YADE_CLASS_BASE_DOC_STATICATTRS(Gl1_PolyhedraGeom,GlIGeomFunctor,"Render :yref:`PolyhedraGeom` geometry.", ); }; REGISTER_SERIALIZABLE(Gl1_PolyhedraGeom); class Gl1_PolyhedraPhys: public GlIPhysFunctor{ static GLUquadric* gluQuadric; // needed for gluCylinder, initialized by ::go if no initialized yet public: virtual void go(const shared_ptr&,const shared_ptr&,const shared_ptr&,const shared_ptr&,bool wireFrame); YADE_CLASS_BASE_DOC_STATICATTRS(Gl1_PolyhedraPhys,GlIPhysFunctor,"Renders :yref:`PolyhedraPhys` objects as cylinders of which diameter and color depends on :yref:`PolyhedraPhys::normForce` magnitude.", ((Real,maxFn,0,,"Value of :yref:`NormPhys.normalForce` corresponding to :yref:`maxDiameter`. This value will be increased (but *not decreased* ) automatically.")) ((Real,refRadius,std::numeric_limits::infinity(),,"Reference (minimum) particle radius")) ((int,signFilter,0,,"If non-zero, only display contacts with negative (-1) or positive (+1) normal forces; if zero, all contacts will be displayed.")) ((Real,maxRadius,-1,,"Cylinder radius corresponding to the maximum normal force.")) ((int,slices,6,,"Number of sphere slices; (see `glutCylinder reference `__)")) ( (int,stacks,1,,"Number of sphere stacks; (see `glutCylinder reference `__)")) ); RENDERS(PolyhedraPhys); }; REGISTER_SERIALIZABLE(Gl1_PolyhedraPhys); #endif //*************************************************************************** class Ip2_PolyhedraMat_PolyhedraMat_PolyhedraPhys: public IPhysFunctor{ public: virtual void go(const shared_ptr& b1, const shared_ptr& b2, const shared_ptr& interaction); FUNCTOR2D(PolyhedraMat,PolyhedraMat); YADE_CLASS_BASE_DOC_ATTRS(Ip2_PolyhedraMat_PolyhedraMat_PolyhedraPhys,IPhysFunctor,"", ); }; REGISTER_SERIALIZABLE(Ip2_PolyhedraMat_PolyhedraMat_PolyhedraPhys); class Ip2_FrictMat_PolyhedraMat_FrictPhys: public IPhysFunctor{ public: virtual void go(const shared_ptr& b1, const shared_ptr& b2, const shared_ptr& interaction); FUNCTOR2D(FrictMat,PolyhedraMat); YADE_CLASS_BASE_DOC_ATTRS(Ip2_FrictMat_PolyhedraMat_FrictPhys,IPhysFunctor,"", ); }; REGISTER_SERIALIZABLE(Ip2_FrictMat_PolyhedraMat_FrictPhys); //*************************************************************************** /*! Calculate physical response based on penetration configuration given by PolyhedraGeom. */ class Law2_PolyhedraGeom_PolyhedraPhys_Volumetric: public LawFunctor{ OpenMPAccumulator plasticDissipation; virtual bool go(shared_ptr&, shared_ptr&, Interaction*); Real elasticEnergy (); Real getPlasticDissipation(); void initPlasticDissipation(Real initVal=0); YADE_CLASS_BASE_DOC_ATTRS_CTOR_PY(Law2_PolyhedraGeom_PolyhedraPhys_Volumetric,LawFunctor,"Calculate physical response of 2 :yref:`vector` in interaction, based on penetration configuration given by :yref:`PolyhedraGeom`. Normal force is proportional to the volume of intersection", ((Real,volumePower,1.,,"Power of volume used in evaluation of normal force. Default is 1.0 - normal force is linearly proportional to volume. 1.0/3.0 would mean that normal force is proportional to the cube root of volume, approximation of penetration depth.")) ((Vector3r,shearForce,Vector3r::Zero(),,"Shear force from last step")) ((bool,traceEnergy,false,,"Define the total energy dissipated in plastic slips at all contacts. This will trace only plastic energy in this law, see O.trackEnergy for a more complete energies tracing")) ((int,plastDissipIx,-1,(Attr::hidden|Attr::noSave),"Index for plastic dissipation (with O.trackEnergy)")) ((int,elastPotentialIx,-1,(Attr::hidden|Attr::noSave),"Index for elastic potential energy (with O.trackEnergy)")) ,, .def("elasticEnergy",&Law2_PolyhedraGeom_PolyhedraPhys_Volumetric::elasticEnergy,"Compute and return the total elastic energy in all \"FrictPhys\" contacts") .def("plasticDissipation",&Law2_PolyhedraGeom_PolyhedraPhys_Volumetric::getPlasticDissipation,"Total energy dissipated in plastic slips at all FrictPhys contacts. Computed only if :yref:`Law2_PolyhedraGeom_PolyhedraPhys_Volumetric::traceEnergy` is true.") .def("initPlasticDissipation",&Law2_PolyhedraGeom_PolyhedraPhys_Volumetric::initPlasticDissipation,"Initialize cummulated plastic dissipation to a value (0 by default).") ); FUNCTOR2D(PolyhedraGeom,PolyhedraPhys); DECLARE_LOGGER; }; REGISTER_SERIALIZABLE(Law2_PolyhedraGeom_PolyhedraPhys_Volumetric); //*************************************************************************** //compute plane equation from three points on the facet struct Plane_equation { template typename Facet::Plane_3 operator()( Facet& f) { typename Facet::Halfedge_handle h = f.halfedge(); typedef typename Facet::Plane_3 Plane; return Plane( h->vertex()->point(), h->next()->vertex()->point(), h->next()->next()->vertex()->point()); } }; //get Tetrahedron inertia Matrix3r TetraInertiaTensor(Vector3r av,Vector3r bv,Vector3r cv,Vector3r dv); //return intersection of two polyhedrons Polyhedron Polyhedron_Polyhedron_intersection(Polyhedron A, Polyhedron B, CGALpoint X, CGALpoint centroidA, CGALpoint centroidB, std::vector &code); //return intersection of plane & polyhedron Polyhedron Polyhedron_Plane_intersection(Polyhedron A, Plane B, CGALpoint centroid, CGALpoint X); //Test if point is inside Polyhedron bool Is_inside_Polyhedron(Polyhedron P, CGALpoint inside); //return approximate intersection of sphere & polyhedron bool Sphere_Polyhedron_intersection(Polyhedron A, Real r, CGALpoint C, CGALpoint centroid, Real volume, CGALvector normal, Real area); //return volume and centroid of polyhedra bool P_volume_centroid(Polyhedron P, Real * volume, Vector3r * centroid); //CGAL - miniEigen communication Vector3r FromCGALPoint(CGALpoint A); Vector3r FromCGALVector(CGALvector A); CGALpoint ToCGALPoint(Vector3r A); CGALvector ToCGALVector(Vector3r A); //determination of intersection of two polyhedras bool do_intersect(Polyhedron A, Polyhedron B); bool do_intersect(Polyhedron A, Polyhedron B, std::vector &sep_plane); //connect triagular facets if possible Polyhedron Simplify(Polyhedron P, Real lim); //list of facets and edges void PrintPolyhedron(Polyhedron P); void PrintPolyhedron2File(Polyhedron P,FILE* X); //normal by least square fitting of separating segments Vector3r FindNormal(Polyhedron Int, Polyhedron PA, Polyhedron PB); //split polyhedron shared_ptr SplitPolyhedra(const shared_ptr& body, Vector3r direction, Vector3r point); //new polyhedra shared_ptr NewPolyhedra(vector v, shared_ptr mat); #endif // YADE_CGAL trunk-2018.02b/pkg/dem/Polyhedra_Ig2.cpp000066400000000000000000000465631324306050200176660ustar00rootroot00000000000000// © 2013 Jan Elias, http://www.fce.vutbr.cz/STM/elias.j/, elias.j@fce.vutbr.cz // https://www.vutbr.cz/www_base/gigadisk.php?i=95194aa9a #ifdef YADE_CGAL // NDEBUG causes crashes in CGAL sometimes. Anton #ifdef NDEBUG #undef NDEBUG #endif #include "Polyhedra_Ig2.hpp" YADE_PLUGIN(/* self-contained in hpp: */ (Ig2_Polyhedra_Polyhedra_PolyhedraGeom) (Ig2_Wall_Polyhedra_PolyhedraGeom) (Ig2_Facet_Polyhedra_PolyhedraGeom) (Ig2_Sphere_Polyhedra_ScGeom) (Ig2_Polyhedra_Polyhedra_ScGeom) (Ig2_Polyhedra_Polyhedra_PolyhedraGeomOrScGeom) ); //********************************************************************************** /*! Create Polyhedra (collision geometry) from colliding Polyhedras. */ bool Ig2_Polyhedra_Polyhedra_PolyhedraGeom::go( const shared_ptr& shape1, const shared_ptr& shape2, const State& state1, const State& state2, const Vector3r& shift2, const bool& force, const shared_ptr& interaction) { //get polyhedras const Se3r& se31=state1.se3; const Se3r& se32=state2.se3; Polyhedra* A = static_cast(shape1.get()); Polyhedra* B = static_cast(shape2.get()); bool isNew = !interaction->geom; //move and rotate 1st the CGAL structure Polyhedron Matrix3r rot_mat = (se31.orientation).toRotationMatrix(); Vector3r trans_vec = se31.position; const Real& s = interactionDetectionFactor; Transformation t_rot_trans( s*rot_mat(0,0), s*rot_mat(0,1), s*rot_mat(0,2), trans_vec[0], s*rot_mat(1,0), s*rot_mat(1,1), s*rot_mat(1,2), trans_vec[1], s*rot_mat(2,0), s*rot_mat(2,1), s*rot_mat(2,2), trans_vec[2], 1. ); Polyhedron PA = A->GetPolyhedron(); std::transform( PA.points_begin(), PA.points_end(), PA.points_begin(), t_rot_trans); std::transform( PA.facets_begin(), PA.facets_end(), PA.planes_begin(),Plane_equation()); //move and rotate 2nd the CGAL structure Polyhedron rot_mat = (se32.orientation).toRotationMatrix(); trans_vec = se32.position + shift2; t_rot_trans = Transformation( s*rot_mat(0,0), s*rot_mat(0,1), s*rot_mat(0,2), trans_vec[0], s*rot_mat(1,0), s*rot_mat(1,1), s*rot_mat(1,2), trans_vec[1], s*rot_mat(2,0), s*rot_mat(2,1), s*rot_mat(2,2), trans_vec[2], 1. ); Polyhedron PB = B->GetPolyhedron(); std::transform( PB.points_begin(), PB.points_end(), PB.points_begin(), t_rot_trans); std::transform( PB.facets_begin(), PB.facets_end(), PB.planes_begin(),Plane_equation()); shared_ptr bang; if (isNew) { // new interaction bang=shared_ptr(new PolyhedraGeom()); bang->sep_plane.assign(3,0); bang->contactPoint = Vector3r(0,0,0); bang->isShearNew = true; }else{ // use data from old interaction bang=YADE_PTR_CAST(interaction->geom); bang->isShearNew = bang->equivalentPenetrationDepth<=0; } //find intersection Polyhedra Polyhedron Int; Int = Polyhedron_Polyhedron_intersection(PA,PB,ToCGALPoint(bang->contactPoint),ToCGALPoint(se31.position),ToCGALPoint(se32.position+shift2), bang->sep_plane); //volume and centroid of intersection Real volume; Vector3r centroid; P_volume_centroid(Int, &volume, ¢roid); if(std::isnan(volume) || volume<=1E-25 || volume > min(A->GetVolume(),B->GetVolume())) { bang->equivalentPenetrationDepth=0; bang->penetrationVolume=min(A->GetVolume(),B->GetVolume()); bang->normal = (A->GetVolume()>B->GetVolume() ? 1 : -1)*(se32.position+shift2-se31.position); return !isNew; } if ((!Is_inside_Polyhedron(PA, ToCGALPoint(centroid))) or (!Is_inside_Polyhedron(PB, ToCGALPoint(centroid)))) { bang->equivalentPenetrationDepth=0; return !isNew; } if (isNew) interaction->geom = bang; //find normal direction Vector3r normal = FindNormal(Int, PA, PB); if((se32.position+shift2-centroid).dot(normal)<0) normal*=-1; Real area = std::pow(volume,2./3.); // store calculated stuff in bang; some is redundant bang->equivalentCrossSection=area; bang->contactPoint=centroid; bang->penetrationVolume=volume; bang->equivalentPenetrationDepth=volume/area; scene = Omega::instance().getScene().get(); bang->precompute(state1,state2,scene,interaction,normal,bang->isShearNew,shift2); bang->normal=normal; /* FILE * fin = fopen("Interactions.dat","a"); fprintf(fin,"************** IDS %d %d **************\n",interaction->id1, interaction->id2); fprintf(fin,"volume\t%e\n",volume); fprintf(fin,"centroid\t%e\t%e\t%e\n",centroid[0],centroid[1],centroid[2]); PrintPolyhedron2File(PA,fin); PrintPolyhedron2File(PB,fin); PrintPolyhedron2File(Int,fin); fclose(fin); */ return true; } //********************************************************************************** bool Ig2_Polyhedra_Polyhedra_PolyhedraGeom::goReverse( const shared_ptr& shape1, const shared_ptr& shape2, const State& state1, const State& state2, const Vector3r& shift2, const bool& force, const shared_ptr& c) { return go(shape1,shape2,state2,state1,-shift2,force,c); } //********************************************************************************** /*! Create Polyhedra (collision geometry) from colliding Polyhedron and Wall. */ bool Ig2_Wall_Polyhedra_PolyhedraGeom::go(const shared_ptr& shape1,const shared_ptr& shape2, const State& state1,const State& state2, const Vector3r& shift2, const bool& force, const shared_ptr& interaction) { const int& PA(shape1->cast().axis); const int& sense(shape1->cast().sense); const Se3r& se31=state1.se3; const Se3r& se32=state2.se3; Polyhedra* B = static_cast(shape2.get()); bool isNew = !interaction->geom; //move and rotate also the CGAL structure Polyhedron Matrix3r rot_mat = (se32.orientation).toRotationMatrix(); Vector3r trans_vec = se32.position; Transformation t_rot_trans( rot_mat(0,0),rot_mat(0,1),rot_mat(0,2),trans_vec[0], rot_mat(1,0),rot_mat(1,1),rot_mat(1,2),trans_vec[1], rot_mat(2,0),rot_mat(2,1),rot_mat(2,2),trans_vec[2],1.); Polyhedron PB = B->GetPolyhedron(); std::transform( PB.points_begin(), PB.points_end(), PB.points_begin(), t_rot_trans); std::transform( PB.facets_begin(), PB.facets_end(), PB.planes_begin(),Plane_equation()); //move wall Vector3r normal = Vector3r(0,0,0); if (sense != 0) { normal[PA] = sense; } else { normal[PA] = 1; Real dot = normal.dot(se32.position-se31.position); if (dot<1) { normal[PA] = -1; } } CGALvector CGALnormal = CGALvector(normal[0],normal[1],normal[2]); Plane A = Plane(CGALpoint(se31.position[0],se31.position[1],se31.position[2]),CGALvector(normal[0],normal[1],normal[2])); shared_ptr bang; if (isNew) { // new interaction bang=shared_ptr(new PolyhedraGeom()); bang->contactPoint = Vector3r(0,0,0); bang->isShearNew = true; interaction->geom = bang; }else{ // use data from old interaction bang=YADE_PTR_CAST(interaction->geom); bang->isShearNew = bang->equivalentPenetrationDepth<=0; } //find intersection Polyhedra Polyhedron Int; Int = Polyhedron_Plane_intersection(PB,A,ToCGALPoint(se32.position),ToCGALPoint(bang->contactPoint)); //volume and centroid of intersection Real volume; Vector3r centroid; P_volume_centroid(Int, &volume, ¢roid); if(std::isnan(volume) || volume<=1E-25 || volume > B->GetVolume()) {bang->equivalentPenetrationDepth=0; return true;} if (!Is_inside_Polyhedron(PB, ToCGALPoint(centroid))) {bang->equivalentPenetrationDepth=0; return true;} //calculate area of projection of Intersection into the normal plane Real area = volume/1E-8; // store calculated stuff in bang; some is redundant bang->equivalentCrossSection=area; bang->contactPoint=centroid; bang->penetrationVolume=volume; bang->equivalentPenetrationDepth=volume/area; bang->precompute(state1,state2,scene,interaction,normal,isNew,shift2); bang->normal=FromCGALVector(CGALnormal); return true; } //********************************************************************************** /*! Create Polyhedra (collision geometry) from colliding Polyhedron and Facet. */ bool Ig2_Facet_Polyhedra_PolyhedraGeom::go(const shared_ptr& shape1,const shared_ptr& shape2, const State& state1,const State& state2, const Vector3r& shift2, const bool& force, const shared_ptr& interaction){ const Se3r& se31=state1.se3; const Se3r& se32=state2.se3; Facet* A = static_cast(shape1.get()); Polyhedra* B = static_cast(shape2.get()); bool isNew = !interaction->geom; //move and rotate 1st the CGAL structure Polyhedron Matrix3r rot_mat = (se32.orientation).toRotationMatrix(); Vector3r trans_vec = se32.position; Transformation t_rot_trans( rot_mat(0,0),rot_mat(0,1),rot_mat(0,2),trans_vec[0], rot_mat(1,0),rot_mat(1,1),rot_mat(1,2),trans_vec[1], rot_mat(2,0),rot_mat(2,1),rot_mat(2,2),trans_vec[2],1.); Polyhedron PB = B->GetPolyhedron(); std::transform( PB.points_begin(), PB.points_end(), PB.points_begin(), t_rot_trans); std::transform( PB.facets_begin(), PB.facets_end(), PB.planes_begin(),Plane_equation()); //move and rotate facet vector v; v.resize(6); for (int i=0; i<3; i++) v[i] = ToCGALPoint(se31.orientation*A->vertices[i] + se31.position); // vertices in global coordinates //determine CGALpoint f_center((v[0].x()+v[1].x()+v[2].x())/3.,(v[0].y()+v[1].y()+v[2].y())/3.,(v[0].z()+v[1].z()+v[2].z())/3.); CGALvector f_normal = CGAL::cross_product((v[1]-v[0]),(v[2]-v[0])); //chage normal + change order of vertices to get outwarding facet normal for initial tetrahedron if ((ToCGALPoint(se32.position)-f_center)*(f_normal) < 0) { f_normal = (-1)*f_normal; CGALpoint help; help = v[0]; v[0]=v[1]; v[1]=help; } Real f_area = sqrt(f_normal.squared_length()); for (int i=3; i<6; i++) v[i] = v[i-3]-f_normal/f_area*0.05*sqrt(f_area); // vertices in global coordinates Polyhedron PA; //construct polyhedron directly Polyhedron::Halfedge_iterator hei = PA.make_tetrahedron(v[4],v[1],v[0],v[2]); hei = PA.split_vertex(hei, hei->next()->opposite()); hei->vertex()->point() = v[3]; hei = PA.split_facet(hei->next()->next()->next(),hei->next()); hei = PA.split_vertex(hei, hei->next_on_vertex()->next_on_vertex()); hei->vertex()->point() = v[5]; shared_ptr bang; if (isNew) { // new interaction bang=shared_ptr(new PolyhedraGeom()); bang->sep_plane.assign(3,0); bang->contactPoint = Vector3r(0,0,0); bang->isShearNew = true; interaction->geom = bang; } else { // use data from old interaction bang=YADE_PTR_CAST(interaction->geom); bang->isShearNew = bang->equivalentPenetrationDepth<=0; } //find intersection Polyhedra Polyhedron Int; Int = Polyhedron_Polyhedron_intersection(PA,PB,ToCGALPoint(bang->contactPoint),ToCGALPoint(se31.position),ToCGALPoint(se32.position), bang->sep_plane); //volume and centroid of intersection Real volume; Vector3r centroid; P_volume_centroid(Int, &volume, ¢roid); if(std::isnan(volume) || volume<=1E-25 || volume > B->GetVolume()) {bang->equivalentPenetrationDepth=0; return true;} if (!Is_inside_Polyhedron(PB, ToCGALPoint(centroid))) {bang->equivalentPenetrationDepth=0; return true;} //find normal direction Vector3r normal = FindNormal(Int, PA, PB); if((se32.position-centroid).dot(normal)<0) normal*=-1; //calculate area of projection of Intersection into the normal plane Real area = volume/1E-8; // store calculated stuff in bang; some is redundant bang->equivalentCrossSection=area; bang->contactPoint=centroid; bang->penetrationVolume=volume; bang->equivalentPenetrationDepth=volume/area; bang->precompute(state1,state2,scene,interaction,normal,bang->isShearNew,shift2); bang->normal=normal; return true; } //********************************************************************************** /*! Create Polyhedra (collision geometry) from colliding Polyhedron and Sphere. */ bool Ig2_Sphere_Polyhedra_ScGeom::go(const shared_ptr& shape1,const shared_ptr& shape2,const State& state1,const State& state2, const Vector3r& shift2, const bool& force, const shared_ptr& interaction){ const Se3r& se31=state1.se3; const Se3r& se32=state2.se3; Sphere* A = static_cast(shape1.get()); Real radius = A->radius; Real r2 = radius*radius; Polyhedra* B = static_cast(shape2.get()); bool isNew = !interaction->geom; shared_ptr geom; // use polyhedron surface triangulation const std::vector faceTri = B->GetSurfaceTriangulation(); // get vertices in global coordinate system std::vector pts(B->v); for (unsigned int i=0; i0) isInside = false; // p0 lies in positive halfspace of triangle, cannot be inside p0a = p0 - dst*n; // p0 projected to triangle plane p10 = p0a-p1; p20 = p0a-p2; p30 = p0a-p3; // inside/outside with respect to individual edges o1 = (p10.cross(p20)).dot(n); o2 = (p20.cross(p30)).dot(n); o3 = (p30.cross(p10)).dot(n); if (o1>0 && o2>0 && o3>0) { // p0a is inside triangle pp = p0a; relTemp = inside; } else { edst2min = DBL_MAX; for (int j=0; j<3; j++) { // iterate over 3 edges end find the closest point of them pa = j==0? p1 : (j==1? p2 : p3); pb = j==0? p2 : (j==1? p3 : p1); r = (((pb-p0a).cross(pa-p0a)).cross(pb-pa)).normalized(); p0aa = p0a + (pa-p0a).dot(r)*r; // projection of p0a on the edge e = pb-pa; en = e.norm(); e /= en; t = (p0aa-pa).dot(e)/en; // parameter of edge line if (t<0.) { ppp = pa; relTemp2=vertex; } else if (t>1.) { ppp = pb; relTemp2=vertex; } else { ppp = p0aa; relTemp2=edge; } edst2 = (ppp-p0).squaredNorm(); if (edst2 < edst2min) { edst2min = edst2; pp = ppp; relTemp = relTemp2; } } } dst2 = (pp-p0).squaredNorm(); if (dst2 < dst2min) { // dst2 is now the minimal squared distance and pp is now the closest found point so far dst2min = dst2; closest = pp; rel = relTemp; } } //****************************************************************************** if (isNew && !(isInside || dst2min<=r2) ) return false; if (isNew) { geom = shared_ptr(new ScGeom()); geom->radius1 = geom->radius2 = radius; interaction->geom = geom; } else { geom = YADE_PTR_CAST(interaction->geom); } Real penetrationDepth; Vector3r normal,contactPoint; if (isInside) { // some artificial tricks for unlikely case normal = se32.position - center; dst = normal.norm(); penetrationDepth = radius; normal /= dst; contactPoint = center + normal*(.5*radius); } else { if (rel==none) { LOG_FATAL("TODO"); } Real coeff = rel==edge? edgeCoeff : (rel==vertex? vertexCoeff : 1.0); normal = closest - center; dst = normal.norm(); normal /= dst; penetrationDepth = radius - dst; contactPoint = center + normal*(radius-.5*penetrationDepth); penetrationDepth *= coeff; } geom->normal = normal; geom->penetrationDepth = penetrationDepth; geom->contactPoint = contactPoint; geom->precompute(state1,state2,scene,interaction,normal,isNew,shift2,false/*avoidGranularRatcheting only for sphere-sphere*/); return true; } //********************************************************************************** /*! Plyhedra -> ScGeom. */ bool Ig2_Polyhedra_Polyhedra_ScGeom::go(const shared_ptr& shape1, const shared_ptr& shape2, const State& state1, const State& state2, const Vector3r& shift2, const bool& force, const shared_ptr& interaction) { const Se3r& se31=state1.se3; const Se3r& se32=state2.se3; shared_ptr geom; bool isNew = !interaction->geom; if (isNew) { Ig2_Polyhedra_Polyhedra_PolyhedraGeom ppGeom = Ig2_Polyhedra_Polyhedra_PolyhedraGeom(); ppGeom.interactionDetectionFactor = interactionDetectionFactor; bool pp = ppGeom.go(shape1,shape2,state2,state1,shift2,force,interaction); if (!pp) { return false; } shared_ptr pGeom = YADE_PTR_CAST(interaction->geom); geom = shared_ptr(new ScGeom()); geom->radius1 = (pGeom->contactPoint-se31.position).norm(); geom->radius2 = (pGeom->contactPoint-se32.position+shift2).norm(); interaction->geom=geom; } else { geom = YADE_PTR_CAST(interaction->geom); } const Real& radius1 = geom->radius1; const Real& radius2 = geom->radius2; Vector3r normal=(se32.position+shift2)-se31.position; Real norm=normal.norm(); normal/=norm; // normal is unit vector now Real penetrationDepth=radius1+radius2-norm; geom->contactPoint=se31.position+(radius1-0.5*penetrationDepth)*normal;//0.5*(pt1+pt2); geom->penetrationDepth=penetrationDepth; scene = Omega::instance().getScene().get(); geom->precompute(state1,state2,scene,interaction,normal,isNew,shift2,false); return true; } bool Ig2_Polyhedra_Polyhedra_ScGeom::goReverse(const shared_ptr& shape1, const shared_ptr& shape2, const State& state1, const State& state2, const Vector3r& shift2, const bool& force, const shared_ptr& interaction) { return go(shape1,shape2,state2,state1,-shift2,force,interaction); } bool Ig2_Polyhedra_Polyhedra_PolyhedraGeomOrScGeom::go(const shared_ptr& shape1, const shared_ptr& shape2, const State& state1, const State& state2, const Vector3r& shift2, const bool& force, const shared_ptr& interaction) { bool isNew = !interaction->geom; if (isNew) { if (createScGeom) { return ig2scGeom->go(shape1,shape2,state1,state2,shift2,force,interaction); } else { return ig2polyhedraGeom->go(shape1,shape2,state1,state2,shift2,force,interaction); } } // ScGeom* scGeom = dynamic_cast(interaction->geom.get()); if (scGeom) { return ig2scGeom->go(shape1,shape2,state1,state2,shift2,force,interaction); } // PolyhedraGeom* pGeom = dynamic_cast(interaction->geom.get()); if (pGeom) { return ig2polyhedraGeom->go(shape1,shape2,state1,state2,shift2,force,interaction); } // LOG_ERROR("TODO, should not happen"); return false; } bool Ig2_Polyhedra_Polyhedra_PolyhedraGeomOrScGeom::goReverse(const shared_ptr& shape1, const shared_ptr& shape2, const State& state1, const State& state2, const Vector3r& shift2, const bool& force, const shared_ptr& interaction) { return go(shape1,shape2,state2,state1,-shift2,force,interaction); } #endif // YADE_CGAL trunk-2018.02b/pkg/dem/Polyhedra_Ig2.hpp000066400000000000000000000154641324306050200176670ustar00rootroot00000000000000// © 2013 Jan Elias, http://www.fce.vutbr.cz/STM/elias.j/, elias.j@fce.vutbr.cz // https://www.vutbr.cz/www_base/gigadisk.php?i=95194aa9a #ifdef YADE_CGAL #include "Polyhedra.hpp" //*************************************************************************** /*! Create Polyhedra (collision geometry) from colliding Polyhedras. */ class Ig2_Polyhedra_Polyhedra_PolyhedraGeom: public IGeomFunctor { public: virtual ~Ig2_Polyhedra_Polyhedra_PolyhedraGeom(){}; virtual bool go(const shared_ptr& shape1, const shared_ptr& shape2, const State& state1, const State& state2, const Vector3r& shift2, const bool& force, const shared_ptr& c); virtual bool goReverse(const shared_ptr& shape1, const shared_ptr& shape2, const State& state1, const State& state2, const Vector3r& shift2, const bool& force, const shared_ptr& c); FUNCTOR2D(Polyhedra,Polyhedra); DEFINE_FUNCTOR_ORDER_2D(Polyhedra,Polyhedra); YADE_CLASS_BASE_DOC_ATTRS(Ig2_Polyhedra_Polyhedra_PolyhedraGeom,IGeomFunctor,"Create/update geometry of collision between 2 Polyhedras", ((Real,interactionDetectionFactor,1,,"see :yref:`Ig2_Sphere_Sphere_ScGeom.interactionDetectionFactor`")) ); DECLARE_LOGGER; }; REGISTER_SERIALIZABLE(Ig2_Polyhedra_Polyhedra_PolyhedraGeom); //*************************************************************************** /*! Create Polyhedra (collision geometry) from colliding Wall & Polyhedra. */ class Ig2_Wall_Polyhedra_PolyhedraGeom: public IGeomFunctor { public: virtual ~Ig2_Wall_Polyhedra_PolyhedraGeom(){}; virtual bool go(const shared_ptr& shape1, const shared_ptr& shape2, const State& state1, const State& state2, const Vector3r& shift2, const bool& force, const shared_ptr& c); FUNCTOR2D(Wall,Polyhedra); DEFINE_FUNCTOR_ORDER_2D(Wall,Polyhedra); YADE_CLASS_BASE_DOC(Ig2_Wall_Polyhedra_PolyhedraGeom,IGeomFunctor,"Create/update geometry of collision between Wall and Polyhedra"); DECLARE_LOGGER; }; REGISTER_SERIALIZABLE(Ig2_Wall_Polyhedra_PolyhedraGeom); //*************************************************************************** /*! Create Polyhedra (collision geometry) from colliding Facet & Polyhedra. */ class Ig2_Facet_Polyhedra_PolyhedraGeom: public IGeomFunctor { public: virtual ~Ig2_Facet_Polyhedra_PolyhedraGeom(){}; virtual bool go(const shared_ptr& shape1, const shared_ptr& shape2, const State& state1, const State& state2, const Vector3r& shift2, const bool& force, const shared_ptr& c); FUNCTOR2D(Facet,Polyhedra); DEFINE_FUNCTOR_ORDER_2D(Facet,Polyhedra); YADE_CLASS_BASE_DOC(Ig2_Facet_Polyhedra_PolyhedraGeom,IGeomFunctor,"Create/update geometry of collision between Facet and Polyhedra"); DECLARE_LOGGER; }; REGISTER_SERIALIZABLE(Ig2_Facet_Polyhedra_PolyhedraGeom); //*************************************************************************** /*! Create Polyhedra (collision geometry) from colliding Sphere & Polyhedra. */ class Ig2_Sphere_Polyhedra_ScGeom: public IGeomFunctor { public: enum PointTriangleRelation { inside, edge, vertex, none }; virtual ~Ig2_Sphere_Polyhedra_ScGeom(){}; virtual bool go(const shared_ptr& shape1, const shared_ptr& shape2, const State& state1, const State& state2, const Vector3r& shift2, const bool& force, const shared_ptr& c); FUNCTOR2D(Sphere,Polyhedra); DEFINE_FUNCTOR_ORDER_2D(Sphere,Polyhedra); YADE_CLASS_BASE_DOC_ATTRS(Ig2_Sphere_Polyhedra_ScGeom,IGeomFunctor,"Create/update geometry of collision between Sphere and Polyhedra", ((Real,edgeCoeff,1.0,,"multiplier of penetrationDepth when sphere contacts edge (simulating smaller volume of actual intersection or when several polyhedrons has common edge)")) ((Real,vertexCoeff,1.0,,"multiplier of penetrationDepth when sphere contacts vertex (simulating smaller volume of actual intersection or when several polyhedrons has common vertex)")) ); DECLARE_LOGGER; }; REGISTER_SERIALIZABLE(Ig2_Sphere_Polyhedra_ScGeom); //*************************************************************************** /*! Plyhedra -> ScGeom. */ class Ig2_Polyhedra_Polyhedra_ScGeom: public IGeomFunctor { public: virtual ~Ig2_Polyhedra_Polyhedra_ScGeom(){}; virtual bool go(const shared_ptr& shape1, const shared_ptr& shape2, const State& state1, const State& state2, const Vector3r& shift2, const bool& force, const shared_ptr& c); virtual bool goReverse(const shared_ptr& shape1, const shared_ptr& shape2, const State& state1, const State& state2, const Vector3r& shift2, const bool& force, const shared_ptr& c); FUNCTOR2D(Polyhedra,Polyhedra); DEFINE_FUNCTOR_ORDER_2D(Polyhedra,Polyhedra); YADE_CLASS_BASE_DOC_ATTRS(Ig2_Polyhedra_Polyhedra_ScGeom,IGeomFunctor,"EXPERIMENTAL. Ig2 functor creating ScGeom from two Polyhedra shapes. The radii are computed as a distance of contact point (computed using Ig2_Polyhedra_Polyhedra_PolyhedraGeom) and center of particle. Tested only for face-face contacts (like brick wall).", ((Real,interactionDetectionFactor,1,,"see Ig2_Sphere_Sphere_ScGeom.interactionDetectionFactor")) ); DECLARE_LOGGER; }; REGISTER_SERIALIZABLE(Ig2_Polyhedra_Polyhedra_ScGeom); class Ig2_Polyhedra_Polyhedra_PolyhedraGeomOrScGeom: public IGeomFunctor { public: virtual ~Ig2_Polyhedra_Polyhedra_PolyhedraGeomOrScGeom(){}; virtual bool go(const shared_ptr& shape1, const shared_ptr& shape2, const State& state1, const State& state2, const Vector3r& shift2, const bool& force, const shared_ptr& c); virtual bool goReverse(const shared_ptr& shape1, const shared_ptr& shape2, const State& state1, const State& state2, const Vector3r& shift2, const bool& force, const shared_ptr& c); FUNCTOR2D(Polyhedra,Polyhedra); DEFINE_FUNCTOR_ORDER_2D(Polyhedra,Polyhedra); YADE_CLASS_BASE_DOC_ATTRS(Ig2_Polyhedra_Polyhedra_PolyhedraGeomOrScGeom,IGeomFunctor,"EXPERIMENTAL. A hacky helper Ig2 functor combining two Polyhedra shapes and creating, according to the settings, either ScGeom or PolyhedraGeom.", ((bool,createScGeom,true,,"If true, creates ScGeom on new contacts. Creates PolyhedraGeom otherwise. On existing contacts Ig2_Polyhedra_Polyhedra_PolyhedraGeom or Ig2_Polyhedra_Polyhedra_ScGeom is used according to present IGeom isntance.")) ((shared_ptr,ig2polyhedraGeom,new Ig2_Polyhedra_Polyhedra_PolyhedraGeom,,"Helper Ig2 functor for PolyhedraGeom (to be able to modify its settings)")) ((shared_ptr,ig2scGeom,new Ig2_Polyhedra_Polyhedra_ScGeom,,"Helper Ig2 functor for ScGeom (to be able to modify its settings)")) ); DECLARE_LOGGER; }; REGISTER_SERIALIZABLE(Ig2_Polyhedra_Polyhedra_PolyhedraGeomOrScGeom); //*************************************************************************** #endif // YADE_CGAL trunk-2018.02b/pkg/dem/Polyhedra_splitter.cpp000066400000000000000000000265261324306050200211100ustar00rootroot00000000000000// © 2013 Jan Elias, http://www.fce.vutbr.cz/STM/elias.j/, elias.j@fce.vutbr.cz // https://www.vutbr.cz/www_base/gigadisk.php?i=95194aa9a #ifdef YADE_CGAL // NDEBUG causes crashes in CGAL sometimes. Anton #ifdef NDEBUG #undef NDEBUG #endif #include #include YADE_PLUGIN((PolyhedraSplitter)(SplitPolyTauMax)(SplitPolyMohrCoulomb)); CREATE_LOGGER(PolyhedraSplitter); using PSplitTwo = std::tuple, Vector3r, Vector3r>; using PSplitOne = std::tuple, Vector3r>; //********************************************************************************* /* Evaluate tensorial stress estimation in polyhedras */ void getStressForEachBody(vector& bStresses){ const shared_ptr& scene=Omega::instance().getScene(); FOREACH(const shared_ptr& I, *scene->interactions){ if(!I->isReal()) continue; PolyhedraGeom* geom=YADE_CAST(I->geom.get()); PolyhedraPhys* phys=YADE_CAST(I->phys.get()); if(!geom || !phys) continue; Vector3r f=phys->normalForce+phys->shearForce; //Sum f_i*l_j for each contact of each particle const auto cP = geom->contactPoint; const auto posB1 = Body::byId(I->getId1(),scene)->state->pos; const auto posB2 = Body::byId(I->getId2(),scene)->state->pos; bStresses[I->getId1()] -=f*((cP-posB1).transpose()); bStresses[I->getId2()] +=f*((cP-posB2).transpose()); } } //********************************************************************************* /* Size dependent strength */ Real PolyhedraSplitter::getStrength(const Real & volume, const Real & strength) const { //equvalent radius const Real r_eq = pow(volume*3./4./Mathr::PI,1./3.); //r should be in milimeters return strength/(r_eq/1000.); } //********************************************************************************* /* Symmetrization of stress tensor */ void PolyhedraSplitter::Symmetrize(Matrix3r & bStress){ bStress(0,1) = (bStress(0,1) + bStress(1,0))/2.; bStress(0,2) = (bStress(0,2) + bStress(2,0))/2.; bStress(1,2) = (bStress(1,2) + bStress(2,1))/2.; bStress(1,0) = bStress(0,1); bStress(2,0) = bStress(0,2); bStress(2,1) = bStress(1,2); } //********************************************************************************** //split polyhedra void SplitPolyhedraDouble(const PSplitTwo & split){ const auto & b =(get<0>(split)); const auto & d1=(get<1>(split)); const auto & d2=(get<2>(split)); const Se3r& se3=b->state->se3; const Vector3r pnt = se3.position; shared_ptr B2 = SplitPolyhedra(b, d1, pnt); shared_ptr B3 = SplitPolyhedra(B2, d2, pnt); shared_ptr B4 = SplitPolyhedra(b, d2, pnt); } //********************************************************************************* /* Split if stress exceed strength */ void PolyhedraSplitter::action() { const shared_ptr _rb=shared_ptr(); shared_ptr rb=(_rb?_rb:Omega::instance().getScene()); vector splitsV; vector bStresses (scene->bodies->size(), Matrix3r::Zero()); getStressForEachBody(bStresses); for(const auto b : *(rb->bodies)) { if(!b || !b->material || !b->shape) continue; shared_ptr p=YADE_PTR_DYN_CAST(b->shape); shared_ptr m=YADE_PTR_DYN_CAST(b->material); if(p && m->IsSplitable){ //not real strees, to get real one, it has to be divided by body volume Matrix3r stress = bStresses[b->id]; //get eigenstresses Symmetrize(stress); Matrix3r I_vect(Matrix3r::Zero()), I_valu(Matrix3r::Zero()); matrixEigenDecomposition(stress,I_vect,I_valu); Eigen::Matrix3f::Index min_i, max_i; I_valu.diagonal().minCoeff(&min_i); I_valu.diagonal().maxCoeff(&max_i); //division of stress by volume const Vector3r dirC = I_vect.col(max_i); const Vector3r dirT = I_vect.col(min_i); const Vector3r dir1 = dirC.normalized() + dirT.normalized(); const Vector3r dir2 = dirC.normalized() - dirT.normalized(); //double sigma_t = -comp_stress/2.+ tens_stress; const Real sigma_t = pow(( pow(I_valu(0,0)-I_valu(1,1),2)+ pow(I_valu(0,0)-I_valu(2,2),2)+ pow(I_valu(1,1)-I_valu(2,2),2)) /2.,0.5)/p->GetVolume(); if (sigma_t > getStrength(p->GetVolume(),m->GetStrength())) { splitsV.push_back(std::make_tuple(b, dir1.normalized(), dir2.normalized())); } } } std::for_each(splitsV.begin(), splitsV.end(), &SplitPolyhedraDouble); } //********************************************************************************* /* Split if stress exceed strength */ void SplitPolyTauMax::action() { const shared_ptr& scene=Omega::instance().getScene(); vector splitsV; vector bStresses (scene->bodies->size(), Matrix3r::Zero()); getStressForEachBody(bStresses); FOREACH(const shared_ptr& b, *scene->bodies){ if(!b || !b->material || !b->shape) continue; shared_ptr p=YADE_PTR_DYN_CAST(b->shape); shared_ptr m=YADE_PTR_DYN_CAST(b->material); if(p && m->IsSplitable){ //not real strees, to get real one, it has to be divided by body volume Matrix3r Sigma = bStresses[b->id]; //get eigenstresses Symmetrize(Sigma); Eigen::SelfAdjointEigenSolver es(Sigma); const Matrix3r eVect = es.eigenvectors(); const Vector3r eVals = es.eigenvalues()/p->GetVolume(); unsigned int S1_i, /*S2_i,*/ S3_i; // commented out to remove warning about unused variable - Janek Real S1, /*S2, */ S3; // commented out to remove warning about unused variable - Janek std::set inds {1,2,3}; // Sigmas: indices and values S1 = eVals.maxCoeff(&S1_i); inds.erase(S1_i); S3 = eVals.minCoeff(&S3_i); inds.erase(S3_i); // S2_i = *inds.begin(); // commented out to remove warning about unused variable - Janek // S2 = eVals[S2_i]; // commented out to remove warning about unused variable - Janek // Taus // const Real T1 = 0.5 * std::abs(S2 - S3); // commented out to remove warning about unused variable - Janek const Real T2 = 0.5 * std::abs(S1 - S3); // Max // const Real T3 = 0.5 * std::abs(S1 - S2); // commented out to remove warning about unused variable - Janek if ((m->GetStrengthTau() > 0) && (T2 > (getStrength(p->GetVolume(),m->GetStrengthTau())*1000))) { //Split direction, tangential LOG_DEBUG("Split in tangential direction") const Vector3r SplitVector = (eVect.col(S1_i) + eVect.col(S3_i)); splitsV.push_back(std::make_tuple(b, SplitVector.normalized())); } else { // Set absolute values for normal stresses Vector3r eValsAbs = Vector3r::Zero(); for (unsigned short i=0; i < 3; i++) eValsAbs[i]=std::abs(eVals[i]); S1 = eValsAbs.maxCoeff(&S1_i); if (S1 > getStrength(p->GetVolume(),m->GetStrength()*1000)) { //Split direction, normal LOG_DEBUG("Split in normal direction") const Vector3r SplitVector = (eVect.col(S1_i)); splitsV.push_back(std::make_tuple(b, SplitVector.normalized())); } } } } for (auto s : splitsV) { const auto b = get<0>(s); const auto vec = get<1>(s); // Polyhedra* A = static_cast(b->shape.get()); // commented out to remove warning about unused variable - Janek //PrintPolyhedron(A->GetPolyhedron()); shared_ptr B2 = SplitPolyhedra(b, vec, b->state->pos); // A = static_cast(b->shape.get()); // commented out to remove warning about unused variable - Janek //PrintPolyhedron(A->GetPolyhedron()); // A = static_cast(B2->shape.get()); // commented out to remove warning about unused variable - Janek //PrintPolyhedron(A->GetPolyhedron()); } } //********************************************************************************* /* Split if stress exceed strength */ inline bool isPolyhedraBroken (const Real & Sigma0, const Real & Sigma, const Real & V0, const Real & V, unsigned int m, const Real & P) { // [Gladky2017], eq. (6) const Real failureProbability = 1 - exp(-V/V0 * (std::pow((Sigma/Sigma0),m))); if (failureProbability > P) { return true; } else { return false; } } void SplitPolyMohrCoulomb::action() { const shared_ptr& scene=Omega::instance().getScene(); vector splitsV; vector bStresses (scene->bodies->size(), Matrix3r::Zero()); getStressForEachBody(bStresses); ofstream fileS; struct stat buffer; if (stat (fileName.c_str(), &buffer) != 0) { fileS.open (fileName, ios::out); fileS << "id\ttime\titer\tV\tmass\tS1\tS3\tSigmaCD\tSigmaCZ\tSigmaT\tSigmaV\tSigma0\tWei_m\tWei_V0\tP\tpGen\tVersagen\tS2"<bodies)) { if(!b || !b->material || !b->shape) continue; shared_ptr p=YADE_PTR_DYN_CAST(b->shape); shared_ptr m=YADE_PTR_DYN_CAST(b->material); if(p && m->IsSplitable){ //not real strees, to get real one, it has to be divided by body volume Matrix3r Sigma = bStresses[b->id]; //get eigenstresses Symmetrize(Sigma); Eigen::SelfAdjointEigenSolver es(Sigma); const Matrix3r eVect = es.eigenvectors(); const Vector3r eVals = es.eigenvalues()/p->GetVolume(); const auto V0 = m->GetWeiV0(); const auto V = p->GetVolume(); const auto M = m->GetWeiM(); const auto P = m->GetP(); unsigned int S1_i = 0, S2_i = 0, S3_i = 0; Real S1 = 0., S2 = 0., S3 = 0.; std::set inds {0,1,2}; // Sigmas: indices and values S1 = eVals.maxCoeff(&S1_i); inds.erase(S1_i); S3 = eVals.minCoeff(&S3_i); inds.erase(S3_i); S2_i = *inds.begin(); S2 = eVals[S2_i]; if ((m->GetStrengthSigmaCZ() > 0) && (m->GetStrengthSigmaCD() > 0) ) { //Split direction, tangential const Real SigmaCZ = m->GetStrengthSigmaCZ(); const Real SigmaCD = m->GetStrengthSigmaCD(); bool BodyBroken = false; Real SigmaV = 0.; //================================== if (S1 <= 0 and S3 <= 0) { if (P>0) { BodyBroken = isPolyhedraBroken(SigmaCD, -S3, V0, V, M, P); } else { if (S3 < -SigmaCD) { BodyBroken = true; } } SigmaV = -S3; } else if (S1 > 0 and S3 > 0) { if (P>0) { BodyBroken = isPolyhedraBroken(std::abs(SigmaCZ), S1, V0, V, M, P); } else { if (S1 > SigmaCZ) { BodyBroken = true; } } SigmaV = S1; } else { const Real SigmaT = S1 - SigmaCZ/SigmaCD*S3; if (P>0) { BodyBroken = isPolyhedraBroken(std::abs(SigmaCZ), std::abs(SigmaT), V0, V, M, P); } else { if (SigmaT >= SigmaCZ) { BodyBroken = true; } } SigmaV = std::abs(SigmaT); } //================================== fileS <id<<"\t" <time<<"\t" <iter<<"\t" <state->mass<<"\t" <GetWeiS0()<<"\t" <GetP()<<"\t" ; fileS <<"-1\t"; fileS <(s); const auto vec = get<1>(s); shared_ptr B2 = SplitPolyhedra(b, vec, b->state->pos); } } #endif // YADE_CGAL trunk-2018.02b/pkg/dem/Polyhedra_splitter.hpp000066400000000000000000000025771324306050200211150ustar00rootroot00000000000000// © 2013 Jan Elias, http://www.fce.vutbr.cz/STM/elias.j/, elias.j@fce.vutbr.cz // https://www.vutbr.cz/www_base/gigadisk.php?i=95194aa9a #pragma once #ifdef YADE_CGAL #include "Polyhedra.hpp" #include //********************************************************************************* /* Polyhedra Splitter */ class PolyhedraSplitter : public PeriodicEngine{ public: virtual void action(); double getStrength(const double & volume, const double & strength) const; void Symmetrize(Matrix3r & bStress); YADE_CLASS_BASE_DOC_ATTRS_CTOR_PY( PolyhedraSplitter,PeriodicEngine,"Engine that splits polyhedras." , , /*ctor*/ ,/*py*/ ); DECLARE_LOGGER; }; REGISTER_SERIALIZABLE(PolyhedraSplitter); class SplitPolyTauMax : public PolyhedraSplitter{ public: virtual void action(); YADE_CLASS_BASE_DOC_ATTRS_CTOR_PY( SplitPolyTauMax,PolyhedraSplitter,"Split polyhedra along TauMax." , , /*ctor*/ ,/*py*/ ); DECLARE_LOGGER; }; REGISTER_SERIALIZABLE(SplitPolyTauMax); class SplitPolyMohrCoulomb : public PolyhedraSplitter{ public: virtual void action(); YADE_CLASS_BASE_DOC_ATTRS_CTOR_PY( SplitPolyMohrCoulomb,PolyhedraSplitter,"Split polyhedra according to Mohr-Coulomb criterion." , ((string,fileName,"",,"Base.")) , /*ctor*/ ,/*py*/ ); DECLARE_LOGGER; }; REGISTER_SERIALIZABLE(SplitPolyMohrCoulomb); #endif // YADE_CGAL trunk-2018.02b/pkg/dem/Polyhedra_support.cpp000066400000000000000000001000721324306050200207430ustar00rootroot00000000000000// © 2013 Jan Elias, http://www.fce.vutbr.cz/STM/elias.j/, elias.j@fce.vutbr.cz // https://www.vutbr.cz/www_base/gigadisk.php?i=95194aa9a #ifdef YADE_CGAL // NDEBUG causes crashes in CGAL sometimes. Anton #ifdef NDEBUG #undef NDEBUG #endif #include "Polyhedra.hpp" //EMPRIRICAL CONSTANTS - ADJUST IF SEGMENTATION FAULT OCCUR, IT IS A PROBLEM OF CGAL. THESE ARE USED TO CHECK CGAL IMPUTS //DISTANCE_LIMIT controls numerical issues in calculating intersection. It should be small enough to neglect only extremely //small overlaps, but large enough to prevent errors during computation of convex hull const Real DISTANCE_LIMIT = 2E-11; //MERGE_PLANES_LIMIT - if two facets of two intersecting polyhedron differ less, then they are treated ose one only const Real MERGE_PLANES_LIMIT = 1E-18; //18 //SIMPLIFY_LIMIT - if two facets of one polyhedron differ less, then they are joint into one facet const Real SIMPLIFY_LIMIT = 1E-19; //19 //FIND_NORMAL_LIMIT - to determine which facet of intersection belongs to which polyhedron const Real FIND_NORMAL_LIMIT = 1E-40; //SPLITTER_GAP - creates gap between splitted polyhedrons const Real SPLITTER_GAP = 1E-8; //********************************************************************************** //return volume and centroid of polyhedron bool P_volume_centroid(Polyhedron P, Real * volume, Vector3r * centroid){ Vector3r basepoint = FromCGALPoint(P.vertices_begin()->point()); Vector3r A,B,C,D; (*volume) = 0; Real vtet; (*centroid) = Vector3r(0.,0.,0.); //compute centroid and volume for (Polyhedron::Facet_iterator fIter = P.facets_begin(); fIter != P.facets_end(); fIter++){ Polyhedron::Halfedge_around_facet_circulator hfc0; hfc0 = fIter->facet_begin(); int n = fIter->facet_degree(); A = FromCGALPoint(hfc0->vertex()->point()); C = FromCGALPoint(hfc0->next()->vertex()->point()); for (int i=2; inext()->vertex()->point()); vtet = std::abs((basepoint-C).dot((A-C).cross(B-C)))/6.; *volume += vtet; *centroid = *centroid + (basepoint+A+B+C) / 4. * vtet; } } *centroid = *centroid/(*volume); return true; } //********************************************************************************** //STOLEN FROM TETRA body of Vaclav Smilauer /*! Calculates tetrahedron inertia relative to the origin (0,0,0), with unit density (scales linearly). * See article F. Tonon, "Explicit Exact Formulas for the 3-D Tetrahedron Inertia Tensor in Terms of its Vertex Coordinates", * http://docsdrive.com/pdfs/sciencepublications/jmssp/2005/8-11.pdf * [Tonon2005] */ //Centroid MUST be [0,0,0] Matrix3r TetraInertiaTensor(Vector3r av,Vector3r bv,Vector3r cv,Vector3r dv){ const auto x1 = av[0]; const auto y1 = av[1]; const auto z1 = av[2]; const auto x2 = bv[0]; const auto y2 = bv[1]; const auto z2 = bv[2]; const auto x3 = cv[0]; const auto y3 = cv[1]; const auto z3 = cv[2]; const auto x4 = dv[0]; const auto y4 = dv[1]; const auto z4 = dv[2]; // Jacobian of transformation to the reference 4hedron Real detJ=(x2-x1)*(y3-y1)*(z4-z1)+ (x3-x1)*(y4-y1)*(z2-z1)+ (x4-x1)*(y2-y1)*(z3-z1) -(x2-x1)*(y4-y1)*(z3-z1) -(x3-x1)*(y2-y1)*(z4-z1) -(x4-x1)*(y3-y1)*(z2-z1); detJ=std::abs(detJ); const Real a=detJ*(y1*y1+y1*y2+y2*y2+y1*y3+y2*y3+ y3*y3+y1*y4+y2*y4+y3*y4+y4*y4+z1*z1+z1*z2+ z2*z2+z1*z3+z2*z3+z3*z3+z1*z4+z2*z4+z3*z4+z4*z4)/60.; const Real b=detJ*(x1*x1+x1*x2+x2*x2+x1*x3+x2*x3+x3*x3+ x1*x4+x2*x4+x3*x4+x4*x4+z1*z1+z1*z2+z2*z2+z1*z3+ z2*z3+z3*z3+z1*z4+z2*z4+z3*z4+z4*z4)/60.; const Real c=detJ*(x1*x1+x1*x2+x2*x2+x1*x3+x2*x3+x3*x3+x1*x4+ x2*x4+x3*x4+x4*x4+y1*y1+y1*y2+y2*y2+y1*y3+ y2*y3+y3*y3+y1*y4+y2*y4+y3*y4+y4*y4)/60.; // a' in the article etc. const Real a__=detJ*(2*y1*z1+y2*z1+y3*z1+y4*z1+y1*z2+ 2*y2*z2+y3*z2+y4*z2+y1*z3+y2*z3+2*y3*z3+ y4*z3+y1*z4+y2*z4+y3*z4+2*y4*z4)/120.; const Real b__=detJ*(2*x1*z1+x2*z1+x3*z1+x4*z1+x1*z2+ 2*x2*z2+x3*z2+x4*z2+x1*z3+x2*z3+2*x3*z3+ x4*z3+x1*z4+x2*z4+x3*z4+2*x4*z4)/120.; const Real c__=detJ*(2*x1*y1+x2*y1+x3*y1+x4*y1+x1*y2+ 2*x2*y2+x3*y2+x4*y2+x1*y3+x2*y3+2*x3*y3+ x4*y3+x1*y4+x2*y4+x3*y4+2*x4*y4)/120.; Matrix3r ret; ret<< a , -c__, -b__, -c__, b , -a__, -b__, -a__, c ; return ret; } //********************************************************************************** //distace of point from a plane (squared) with sign Real Oriented_squared_distance(Plane P, CGALpoint x){ Real h = P.a()*x.x()+P.b()*x.y()+P.c()*x.z()+P.d(); return ((h>0.)-(h<0.))*pow(h,2)/(CGALvector(P.a(),P.b(),P.c())).squared_length(); } //********************************************************************************** // test if point is inside polyhedra in strong sence, i.e. boundary location is not enough bool Is_inside_Polyhedron(Polyhedron P, CGALpoint inside){ Polyhedron::Plane_iterator pi; for(pi=P.planes_begin(); pi!=P.planes_end(); ++pi){ if( ! pi->has_on_negative_side(inside)) return false; } return true; } //********************************************************************************** // test if point is inside polyhedra not closer than lim to its boundary bool Is_inside_Polyhedron(Polyhedron P, CGALpoint inside, Real lim){ Polyhedron::Plane_iterator pi; lim = pow(lim,2); for(pi=P.planes_begin(); pi!=P.planes_end(); ++pi){ if(Oriented_squared_distance(*pi, inside)>-lim) return false; } return true; } //********************************************************************************** //test if two polyhedron intersect bool do_intersect(Polyhedron A, Polyhedron B){ std::vector sep_plane; sep_plane.assign(3,0); return do_intersect(A, B, sep_plane); } //********************************************************************************** //test if two polyhedron intersect based on previous data bool do_intersect(Polyhedron A, Polyhedron B, std::vector &sep_plane){ bool found; //check previous separation plane switch (sep_plane[0]){ case 1://separation plane was previously determined as sep_plane[2]-th plane of A polyhedron { if(unlikely((unsigned) sep_plane[2]>=A.size_of_facets())) break; Polyhedron::Facet_iterator fIter = A.facets_begin(); for (int i=0; iplane().has_on_positive_side(vIter->point())) {found = false; break;}; } if(found) return false; } break; case 2://separation plane was previously determined as sep_plane[2]-th plane of B polyhedron { if(unlikely((unsigned) sep_plane[2]>=B.size_of_facets())) break; Polyhedron::Facet_iterator fIter = B.facets_begin(); for (int i=0; iplane().has_on_positive_side(vIter->point())) {found = false; break;}; } if(found) return false; } break; case 3://separation plane was previously given by sep_plane[1]-th and sep_plane[2]-th edge of A & B polyhedrons { if(unlikely((unsigned) sep_plane[1]>=A.size_of_halfedges()/2)) break; if(unlikely((unsigned) sep_plane[2]>=B.size_of_halfedges()/2)) break; Polyhedron::Edge_iterator eIter1 = A.edges_begin(); Polyhedron::Edge_iterator eIter2 = B.edges_begin(); for (int i=0; ivertex()->point(),CGAL::cross_product( (eIter1->vertex()->point() - eIter1->opposite()->vertex()->point()), (eIter2->vertex()->point() - eIter2->opposite()->vertex()->point()))); if (!X.has_on_positive_side(B.vertices_begin()->point())) X = X.opposite(); Real lim = pow(DISTANCE_LIMIT,2); for (Polyhedron::Vertex_iterator vIter = A.vertices_begin(); vIter != A.vertices_end(); ++vIter){ if (Oriented_squared_distance(X, vIter->point())>lim) {found = false; break;}; } for (Polyhedron::Vertex_iterator vIter = B.vertices_begin(); vIter != B.vertices_end(); ++vIter){ if (! X.has_on_positive_side(vIter->point())) {found = false; break;}; } if(found) return false; } break; } //regular test with no previous information about separating plane //test all planes from A int i = 0; for (Polyhedron::Facet_iterator fIter = A.facets_begin(); fIter != A.facets_end(); fIter++, i++){ found = true; for (Polyhedron::Vertex_iterator vIter = B.vertices_begin(); vIter != B.vertices_end(); ++vIter){ if (! fIter->plane().has_on_positive_side(vIter->point())) {found = false; break;}; } if(found) {sep_plane[0] = 1; sep_plane[1] = 1; sep_plane[2] = i; return false;} } //test all planes from B i = 0; for (Polyhedron::Facet_iterator fIter = B.facets_begin(); fIter != B.facets_end(); fIter++, i++){ found = true; for (Polyhedron::Vertex_iterator vIter = A.vertices_begin(); vIter != A.vertices_end(); ++vIter){ if (! fIter->plane().has_on_positive_side(vIter->point())) {found = false; break;}; } if(found) {sep_plane[0] = 2; sep_plane[1] = 2; sep_plane[2] = i; return false;} } //test all pairs of edges from A & B Plane X; CGALvector vA; Real lim = pow(DISTANCE_LIMIT,2); i = 0; for (Polyhedron::Edge_iterator eIter1 = A.edges_begin(); eIter1 != A.edges_end(); ++eIter1, i++){ vA = eIter1->vertex()->point() - eIter1->opposite()->vertex()->point(); int j = 0; for (Polyhedron::Edge_iterator eIter2 = B.edges_begin(); eIter2 != B.edges_end(); ++eIter2, j++){ found = true; X = Plane(eIter1->vertex()->point(),CGAL::cross_product(vA, (eIter2->vertex()->point() - eIter2->opposite()->vertex()->point()))); if (!X.has_on_positive_side(B.vertices_begin()->point())) X = X.opposite(); for (Polyhedron::Vertex_iterator vIter = A.vertices_begin(); vIter != A.vertices_end(); ++vIter){ if (Oriented_squared_distance(X, vIter->point())>lim) {found = false; break;}; } for (Polyhedron::Vertex_iterator vIter = B.vertices_begin(); vIter != B.vertices_end(); ++vIter){ if (! X.has_on_positive_side(vIter->point())) {found = false; break;}; } if(found) { sep_plane[0] = 3; sep_plane[1] = i; sep_plane[2] = j; return false; } } } sep_plane[0] = 0; return true; } //********************************************************************************** //norm of difference between two planes Real PlaneDifference(const Plane &a, const Plane &b){ Real la = sqrt(pow(a.a(),2) + pow(a.b(),2) + pow(a.c(),2) + pow(a.d(),2)); Real lb = sqrt(pow(b.a(),2) + pow(b.b(),2) + pow(b.c(),2) + pow(b.d(),2)); return pow(a.a()/la-b.a()/lb,2) + pow(a.b()/la-b.b()/lb,2) + pow(a.c()/la-b.c()/lb,2) + pow(a.d()/la-b.d()/lb,2); //in case we do not care of the orientation //return min(pow(a.a()/la-b.a()/lb,2) + pow(a.b()/la-b.b()/lb,2) + pow(a.c()/la-b.c()/lb,2) + pow(a.d()/la-b.d()/lb,2),pow(a.a()/la+b.a()/lb,2) + pow(a.b()/la+b.b()/lb,2) + pow(a.c()/la+b.c()/lb,2) + pow(a.d()/la+b.d()/lb,2)); } //********************************************************************************** //connect triagular facets if possible Polyhedron Simplify(Polyhedron P, Real limit){ bool elimination = true; while(elimination){ elimination = false; for (Polyhedron::Edge_iterator hei = P.edges_begin(); hei!=P.edges_end(); ++hei){ if (PlaneDifference(hei->facet()->plane(),hei->opposite()->facet()->plane()) < limit){ if (hei->vertex()->vertex_degree() < 3) hei = P.erase_center_vertex(hei); else if(hei->opposite()->vertex()->vertex_degree() < 3) hei = P.erase_center_vertex(hei->opposite()); else hei = P.join_facet(hei); elimination = true; break; } } } if (P.size_of_facets() < 4) P.clear(); return P; } //********************************************************************************** //list of facets + edges void PrintPolyhedron2File(Polyhedron P,FILE* X){ Vector3r A,B,C; fprintf(X,"*** faces ***\n"); for (Polyhedron::Facet_iterator fIter = P.facets_begin(); fIter!=P.facets_end(); ++fIter){ Polyhedron::Halfedge_around_facet_circulator hfc0; hfc0 = fIter->facet_begin(); int n = fIter->facet_degree(); A = FromCGALPoint(hfc0->vertex()->point()); C = FromCGALPoint(hfc0->next()->vertex()->point()); for (int i=2; inext()->vertex()->point()); fprintf(X,"%e\t%e\t%e\t%e\t%e\t%e\t%e\t%e\t%e\n",A[0],A[1],A[2],B[0],B[1],B[2],C[0],C[1],C[2]); } } fprintf(X,"*** edges ***\n"); for (Polyhedron::Edge_iterator hei = P.edges_begin(); hei!=P.edges_end(); ++hei){ fprintf(X,"%e\t%e\t%e\t%e\t%e\t%e\n", hei->vertex()->point()[0], hei->vertex()->point()[1], hei->vertex()->point()[2], hei->opposite()->vertex()->point()[0], hei->opposite()->vertex()->point()[1], hei->opposite()->vertex()->point()[2]); } } //********************************************************************************** //list of facets + edges void PrintPolyhedron(Polyhedron P){ Vector3r A,B,C; cout << "*** faces ***" << endl; for (Polyhedron::Facet_iterator fIter = P.facets_begin(); fIter!=P.facets_end(); ++fIter){ Polyhedron::Halfedge_around_facet_circulator hfc0; hfc0 = fIter->facet_begin(); int n = fIter->facet_degree(); A = FromCGALPoint(hfc0->vertex()->point()); C = FromCGALPoint(hfc0->next()->vertex()->point()); for (int i=2; inext()->vertex()->point()); cout << A << " "<< B << " "<< C << endl; } } cout << "*** edges ***" << endl; for (Polyhedron::Edge_iterator hei = P.edges_begin(); hei!=P.edges_end(); ++hei){ cout << hei->vertex()->point() << " " << hei->opposite()->vertex()->point() << endl; } } //********************************************************************************** //list of facets void PrintPolyhedronFacets(Polyhedron P){ Vector3r A,B,C; for (Polyhedron::Facet_iterator fIter = P.facets_begin(); fIter!=P.facets_end(); ++fIter){ cout << "***" << endl; Polyhedron::Halfedge_around_facet_circulator hfc0; hfc0 = fIter->facet_begin(); int n = fIter->facet_degree(); for (int i = 0; ivertex()->point() << endl; } } } //********************************************************************************** //solve system of 3x3 by Cramers rule Vector3r SolveLinSys3x3(Matrix3r A, Vector3r y){ //only system 3x3 by Cramers rule Real det = A(0,0)*A(1,1)*A(2,2)+ A(0,1)*A(1,2)*A(2,0)+ A(0,2)*A(1,0)*A(2,1)- A(0,2)*A(1,1)*A(2,0)- A(0,1)*A(1,0)*A(2,2)- A(0,0)*A(1,2)*A(2,1); if (det == 0) { LOG_WARN("error in linear solver"); return Vector3r(0,0,0); } return Vector3r( ( y(0)*A(1,1)*A(2,2)+ A(0,1)*A(1,2)*y(2)+ A(0,2)*y(1)*A(2,1)- A(0,2)*A(1,1)*y(2)- A(0,1)*y(1)*A(2,2)- y(0)*A(1,2)*A(2,1) )/det, ( A(0,0)*y(1)*A(2,2)+ y(0)*A(1,2)*A(2,0)+ A(0,2)*A(1,0)*y(2)- A(0,2)*y(1)*A(2,0)- y(0)*A(1,0)*A(2,2)- A(0,0)*A(1,2)*y(2) )/det, ( A(0,0)*A(1,1)*y(2)+ A(0,1)*y(1)*A(2,0)+ y(0)*A(1,0)*A(2,1)- y(0)*A(1,1)*A(2,0)- A(0,1)*A(1,0)*y(2)- A(0,0)*y(1)*A(2,1) )/det ); } //********************************************************************************** /* * Return convex hull of points * critical point, because CGAL often returnes segmentation fault. * The planes must be sufficiently "different". This is, however, * checked elswhere by DISTANCE_LIMIT variable. */ Polyhedron ConvexHull(vector &planes){ Polyhedron Int; for (const auto p : planes) { if (std::isnan(p.x()) || std::isnan(p.y()) || std::isnan(p.z())) return Int; } if (planes.size()>3) CGAL::convex_hull_3(planes.begin(), planes.end(), Int); return Int; } //********************************************************************************** //determination of normal direction of intersection Vector3r FindNormal(Polyhedron Int, Polyhedron PA, Polyhedron PB){ //determine which plane is from which polyhedra Polyhedron::Plane_iterator pi, pj; std::transform( Int.facets_begin(), Int.facets_end(), Int.planes_begin(),Plane_equation()); std::transform( PA.facets_begin(), PA.facets_end(), PA.planes_begin(), Plane_equation()); std::transform( PB.facets_begin(), PB.facets_end(), PB.planes_begin(), Plane_equation()); vector from_A(Int.size_of_facets()); vector minsA(Int.size_of_facets()); vector minsB(Int.size_of_facets()); int i=0; Real minA, minB, k; for(pi = Int.planes_begin(); pi!=Int.planes_end(); ++pi,i++){ minA = 1.; minB = 1.; for(pj=PA.planes_begin(); pj!=PA.planes_end(); ++pj){ k = PlaneDifference(*pi, *pj); if (k segments; int a,b; for (Polyhedron::Edge_iterator hei = Int.edges_begin(); hei!=Int.edges_end(); ++hei){ a = std::distance(Int.facets_begin(), hei->facet()); b = std::distance(Int.facets_begin(), hei->opposite()->facet()); if ((from_A[a] && !from_A[b]) || (from_A[b] && !from_A[a])) { segments.push_back(Segment(hei->vertex()->point(),hei->opposite()->vertex()->point())); } } //find normal direction Plane fit; linear_least_squares_fitting_3(segments.begin(),segments.end(),fit,CGAL::Dimension_tag<1>()); CGALvector CGALnormal=fit.orthogonal_vector(); CGALnormal = CGALnormal/sqrt(CGALnormal.squared_length()); // reverse direction if projection of the (contact_point-centroid_of_B) vector onto the normal is negative (i.e. the normal points more towards B) return FromCGALVector(CGALnormal); } //********************************************************************************** //prepare data for CGAL convex hull vector MergePlanes(vector planes1, vector planes2, Real limit){ vector P = planes1; bool add; for(vector::iterator i = planes2.begin(); i!=planes2.end(); ++i){ add = true; for(vector::iterator j = planes1.begin(); j!=planes1.end(); ++j){ if (PlaneDifference(*i,*j) < limit){ add = false; break; } } if (add) P.push_back(*i); } return P; } //********************************************************************************** //returnes intersecting polyhedron of polyhedron & plane (possibly empty) Polyhedron Polyhedron_Plane_intersection(Polyhedron A, Plane B, CGALpoint centroid, CGALpoint X){ Polyhedron Intersection; CGALpoint inside; vector planes1, planes2; vector dual_planes; // test if do intersect, find some intersecting point bool intersection_found = false; Real lim = pow(DISTANCE_LIMIT,2); std::transform( A.facets_begin(), A.facets_end(), A.planes_begin(),Plane_equation()); // test centroid of previous intersection if(Is_inside_Polyhedron(A,X,DISTANCE_LIMIT) && Oriented_squared_distance(B,X)<=-lim) { intersection_found = true; inside = X; // find new point by checking polyhedron vertices that lies on negative side of the plane } else { for(Polyhedron::Vertex_iterator vIter = A.vertices_begin(); vIter != A.vertices_end() && !intersection_found ; vIter++){ if(Oriented_squared_distance(B,vIter->point())<=-lim) { if (Oriented_squared_distance(B,centroid)point() + 0.5*CGALvector(vIter->point(),centroid); }else{ CGAL::Object result = CGAL::intersection(Line(vIter->point(),centroid), B); if (const CGALpoint *ipoint = CGAL::object_cast(&result)) { inside = vIter->point() + 0.5*CGALvector(vIter->point(),*ipoint); }else{ LOG_WARN("Error in line-plane intersection"); } } if(Is_inside_Polyhedron(A,inside,DISTANCE_LIMIT) && Oriented_squared_distance(B,inside)<=-lim) intersection_found = true; } } } //no intersectiong point => no intersection polyhedron if(!intersection_found) return Intersection; //set the intersection point to origin Transformation transl_back(CGAL::TRANSLATION, inside - CGALpoint(0.,0.,0.)); Transformation transl(CGAL::TRANSLATION, CGALpoint(0.,0.,0.)-inside); std::transform( A.points_begin(), A.points_end(), A.points_begin(), transl); B = transl.transform(B); //dualize plane planes1.push_back(B); //dualize polyhedron std::transform( A.facets_begin(), A.facets_end(), A.planes_begin(),Plane_equation()); for(Polyhedron::Plane_iterator pi =A.planes_begin(); pi!=A.planes_end(); ++pi) planes2.push_back(*pi);; //merge planes planes1 = MergePlanes(planes1,planes2, MERGE_PLANES_LIMIT);//MERGE_PLANES_LIMIT); for(vector::iterator pi =planes1.begin(); pi!= planes1.end(); ++pi) dual_planes.push_back(CGALpoint(-pi->a()/pi->d(),-pi->b()/pi->d(),-pi->c()/pi->d())); //compute convex hull of it Intersection = ConvexHull(dual_planes); if (Intersection.empty()) return Intersection; //simplify std::transform( Intersection.facets_begin(), Intersection.facets_end(), Intersection.planes_begin(),Plane_equation()); Intersection = Simplify(Intersection, SIMPLIFY_LIMIT); std::transform( Intersection.facets_begin(), Intersection.facets_end(), Intersection.planes_begin(),Plane_equation()); //dualize again dual_planes.clear(); for(Polyhedron::Plane_iterator pi =Intersection.planes_begin(); pi!=Intersection.planes_end(); ++pi) dual_planes.push_back(CGALpoint(-pi->a()/pi->d(),-pi->b()/pi->d(),-pi->c()/pi->d())); //compute convex hull of it Intersection = ConvexHull(dual_planes); if (Intersection.empty()) return Intersection; //return to original position std::transform( Intersection.points_begin(), Intersection.points_end(), Intersection.points_begin(), transl_back); if (Intersection.size_of_facets() < 4) Intersection.clear(); return Intersection; } //********************************************************************************** //returnes intersecting polyhedron of polyhedron & plane defined by direction and point Polyhedron Polyhedron_Plane_intersection(Polyhedron A, Vector3r direction, Vector3r plane_point){ Plane B(ToCGALPoint(plane_point), ToCGALVector(direction)); CGALpoint X = ToCGALPoint(plane_point) - 1E-8*ToCGALVector(direction); return Polyhedron_Plane_intersection(A, B, ToCGALPoint(plane_point), X); } //********************************************************************************** //returnes intersecting polyhedron of two polyhedrons (possibly empty) Polyhedron Polyhedron_Polyhedron_intersection(Polyhedron A, Polyhedron B, CGALpoint X, CGALpoint centroidA, CGALpoint centroidB, std::vector &sep_plane){ Polyhedron Intersection; vector planes1, planes2; vector dual_planes; Polyhedron::Plane_iterator pi; CGALpoint inside(0,0,0); bool intersection_found = false; std::transform( A.facets_begin(), A.facets_end(), A.planes_begin(),Plane_equation()); std::transform( B.facets_begin(), B.facets_end(), B.planes_begin(),Plane_equation()); Matrix3r Amatrix; Vector3r y; // test that X is really inside if(Is_inside_Polyhedron(A,X,DISTANCE_LIMIT) && Is_inside_Polyhedron(B,X,DISTANCE_LIMIT)) { intersection_found = true; inside = X; } else { if (!do_intersect(A, B, sep_plane)) return Intersection; //some intersection point Real dist_S, dist_T; Real lim2 = pow(DISTANCE_LIMIT,2); CGALvector d1; Real factor = sqrt(DISTANCE_LIMIT * 1.5); //test vertices A - not needed, edges are enough for (Polyhedron::Vertex_iterator vIter = A.vertices_begin(); vIter != A.vertices_end() && !intersection_found; vIter++){ d1 = centroidA-vIter->point(); inside = vIter->point() + d1/sqrt(d1.squared_length())*DISTANCE_LIMIT*20.; intersection_found = (Is_inside_Polyhedron(A,inside,DISTANCE_LIMIT) && Is_inside_Polyhedron(B,inside,DISTANCE_LIMIT)); } //test vertices B - necessary for (Polyhedron::Vertex_iterator vIter = B.vertices_begin(); vIter != B.vertices_end() && !intersection_found; vIter++){ d1 = centroidB-vIter->point(); inside = vIter->point() + d1/sqrt(d1.squared_length())*DISTANCE_LIMIT*20.; intersection_found = (Is_inside_Polyhedron(A,inside,DISTANCE_LIMIT) && Is_inside_Polyhedron(B,inside,DISTANCE_LIMIT)); } //test edges for (Polyhedron::Edge_iterator eIter = A.edges_begin(); eIter != A.edges_end() && !intersection_found; eIter++){ for (Polyhedron::Facet_iterator fIter = B.facets_begin(); fIter != B.facets_end() && !intersection_found; fIter++){ dist_S = Oriented_squared_distance(fIter->plane(), eIter->vertex()->point()); dist_T = Oriented_squared_distance(fIter->plane(), eIter->opposite()->vertex()->point()); if (dist_S*dist_T >= 0 || std::abs(dist_S)vertex()->point() + (eIter->opposite()->vertex()->point()-eIter->vertex()->point())*sqrt(std::abs(dist_S))/(sqrt(std::abs(dist_S))+sqrt(std::abs(dist_T))); // the fact that edge intersects the facet (not only its plane) is not explicitely checked, it sufices to check that the resulting point is inside both polyhedras Plane p1 = fIter->plane(); Plane p2 = eIter->facet()->plane(); Plane p3 = eIter->opposite()->facet()->plane(); Amatrix << p1.a(),p1.b(),p1.c(), p2.a(),p2.b(),p2.c(), p3.a(),p3.b(),p3.c(); y = Vector3r(-p1.d()-factor*sqrt(pow(p1.a(),2)+pow(p1.b(),2)+pow(p1.c(),2)),-p2.d()-factor*sqrt(pow(p2.a(),2)+pow(p2.b(),2)+pow(p2.c(),2)),-p3.d()-factor*sqrt(pow(p3.a(),2)+pow(p3.b(),2)+pow(p3.c(),2))); inside = ToCGALPoint(SolveLinSys3x3(Amatrix,y)); intersection_found = (Is_inside_Polyhedron(A,inside,DISTANCE_LIMIT) && Is_inside_Polyhedron (B,inside,DISTANCE_LIMIT)); } } } //Polyhedrons do not intersect if (!intersection_found) return Intersection; //set the intersection point to origin Transformation transl_back(CGAL::TRANSLATION, inside - CGALpoint(0.,0.,0.)); Transformation transl(CGAL::TRANSLATION, CGALpoint(0.,0.,0.)-inside); std::transform( A.points_begin(), A.points_end(), A.points_begin(), transl); std::transform( B.points_begin(), B.points_end(), B.points_begin(), transl); //dualize polyhedrons std::transform( A.facets_begin(), A.facets_end(), A.planes_begin(),Plane_equation()); std::transform( B.facets_begin(), B.facets_end(), B.planes_begin(),Plane_equation()); for(Polyhedron::Plane_iterator pi =A.planes_begin(); pi!=A.planes_end(); ++pi) planes1.push_back(*pi); for(Polyhedron::Plane_iterator pi =B.planes_begin(); pi!=B.planes_end(); ++pi) planes2.push_back(*pi); //merge planes planes1 = MergePlanes(planes1,planes2, MERGE_PLANES_LIMIT);//MERGE_PLANES_LIMIT); for(vector::iterator pi =planes1.begin(); pi!= planes1.end(); ++pi) dual_planes.push_back(CGALpoint(-pi->a()/pi->d(),-pi->b()/pi->d(),-pi->c()/pi->d())); //compute convex hull of it Intersection = ConvexHull(dual_planes); if (Intersection.empty()) return Intersection; //simplify std::transform( Intersection.facets_begin(), Intersection.facets_end(), Intersection.planes_begin(),Plane_equation()); Intersection = Simplify(Intersection, SIMPLIFY_LIMIT); std::transform( Intersection.facets_begin(), Intersection.facets_end(), Intersection.planes_begin(),Plane_equation()); //dualize again dual_planes.clear(); for(Polyhedron::Plane_iterator pi =Intersection.planes_begin(); pi!=Intersection.planes_end(); ++pi) { const auto pX = -pi->a()/pi->d(); const auto pY = -pi->b()/pi->d(); const auto pZ = -pi->c()/pi->d(); if (std::isnan(pX) || std::isnan(pY) || std::isnan(pZ)) { Polyhedron IntersectionEmpty; return IntersectionEmpty; } else { dual_planes.push_back(CGALpoint(-pi->a()/pi->d(),-pi->b()/pi->d(),-pi->c()/pi->d())); } } //compute convex hull of it Intersection = ConvexHull(dual_planes); if (Intersection.empty()) return Intersection; //return to original position std::transform( Intersection.points_begin(), Intersection.points_end(), Intersection.points_begin(), transl_back); if (Intersection.size_of_facets() < 4) Intersection.clear(); return Intersection; } //********************************************************************************** Vector3r FromCGALPoint(CGALpoint A){ return Vector3r(A.x(), A.y(), A.z()); } //********************************************************************************** Vector3r FromCGALVector(CGALvector A){ return Vector3r(A.x(), A.y(), A.z()); } //********************************************************************************** CGALpoint ToCGALPoint(Vector3r A){ return CGALpoint(A[0], A[1], A[2]); } //********************************************************************************** CGALvector ToCGALVector(Vector3r A){ return CGALvector(A[0], A[1], A[2]); } //********************************************************************************** //new polyhedra shared_ptr NewPolyhedra(vector v, shared_ptr mat){ shared_ptr body(new Body); body->material=mat; body->shape=shared_ptr(new Polyhedra()); Polyhedra* A = static_cast(body->shape.get()); A->v = v; A->Initialize(); body->state->pos= A->GetCentroid(); body->state->mass=body->material->density*A->GetVolume(); body->state->inertia =A->GetInertia()*body->material->density; body->state->ori = A->GetOri(); body->bound=shared_ptr(new Aabb); body->setAspherical(true); return body; } Real maxDistancePoints (const std::vector & v) { Real maxDistance = 0.; for (unsigned int i = 0; i < v.size(); ++i) { for (unsigned int j = i+1; j < v.size(); ++j) { maxDistance = std::max(maxDistance, (v[i] - v[j]).norm()); } } return maxDistance; } //********************************************************************************** //split polyhedra shared_ptr SplitPolyhedra(const shared_ptr& body, const Vector3r direction, const Vector3r point){ Scene* scene=Omega::instance().getScene().get(); const Se3r& se3=body->state->se3; Polyhedra* A = static_cast(body->shape.get()); State* X = static_cast(body->state.get()); const Vector3r OrigPos = X->pos; const Vector3r OrigVel = X->vel; const Vector3r OrigAngVel = X->angVel; //move and rotate CGAL structure Polyhedron const Matrix3r rot_mat = (se3.orientation).toRotationMatrix(); const Vector3r trans_vec = se3.position; Transformation t_rot_trans( rot_mat(0,0),rot_mat(0,1),rot_mat(0,2),trans_vec[0], rot_mat(1,0),rot_mat(1,1),rot_mat(1,2),trans_vec[1], rot_mat(2,0),rot_mat(2,1),rot_mat(2,2),trans_vec[2],1.); Polyhedron PA = A->GetPolyhedron(); std::transform( PA.points_begin(), PA.points_end(), PA.points_begin(), t_rot_trans); //calculate splitted polyhedrons Plane B(ToCGALPoint(point-direction*SPLITTER_GAP), ToCGALVector(direction)); Polyhedron S1 = Polyhedron_Plane_intersection(PA, B, ToCGALPoint(se3.position), B.projection(ToCGALPoint(OrigPos)) - 1E-6*ToCGALVector(direction)); B = Plane(ToCGALPoint(point+direction*SPLITTER_GAP), ToCGALVector((-1.)*direction)); Polyhedron S2 = Polyhedron_Plane_intersection(PA, B, ToCGALPoint(se3.position), B.projection(ToCGALPoint(OrigPos)) + 1E-6*ToCGALVector(direction)); //replace original polyhedron A->Clear(); for(Polyhedron::Vertex_iterator vi = S1.vertices_begin(); vi != S1.vertices_end(); vi++) A->v.push_back(FromCGALPoint(vi->point())); A->Initialize(); X->pos = A->GetCentroid(); X->ori = A->GetOri(); X->mass=body->material->density*A->GetVolume(); X->refPos[0] = X->refPos[0]+1.; X->inertia =A->GetInertia()*body->material->density; X->vel = OrigVel + OrigAngVel.cross(X->pos-OrigPos); X->angVel = OrigAngVel; //second polyhedron vector v2; for(Polyhedron::Vertex_iterator vi = S2.vertices_begin(); vi != S2.vertices_end(); vi++) v2.push_back(FromCGALPoint(vi->point())); shared_ptr BP = NewPolyhedra(v2, body->material); BP->shape->color = Vector3r(Real(rand())/RAND_MAX,Real(rand())/RAND_MAX,Real(rand())/RAND_MAX); scene->bodies->insert(BP); //set proper state variables BP->state->vel = OrigVel + OrigAngVel.cross(BP->state->pos-OrigPos); BP->state->angVel = OrigAngVel; return BP; } #endif // YADE_CGAL trunk-2018.02b/pkg/dem/PotentialBlock.cpp000077500000000000000000000072241324306050200201420ustar00rootroot00000000000000/*CWBoon 2015 */ #ifdef YADE_POTENTIAL_BLOCKS //! To implement potential particles (Houlsby 2009) using sphere #include "PotentialBlock.hpp" YADE_PLUGIN((PotentialBlock)); PotentialBlock::~PotentialBlock() { } void PotentialBlock::addPlaneStruct() { planeStruct.push_back(Planes()); } void PotentialBlock::addVertexStruct() { vertexStruct.push_back(Vertices()); } void PotentialBlock::addEdgeStruct() { edgeStruct.push_back(Edges()); } //#if 0 void PotentialBlock::postLoad(PotentialBlock&) { int planeNo = a.size(); for (int i=0; i0.9998){parallel = true;} if (fabs(plane1.dot(plane3))<1.0002 && fabs(plane1.dot(plane3))>0.9998){parallel = true;} if (fabs(plane2.dot(plane3))<1.0002 && fabs(plane2.dot(plane3))>0.9998){parallel = true;} double det = Aplanes.determinant(); if(fabs(det)>pow(10,-15) ){ //if (parallel == false){ int ipiv[3]; int bColNo=1; int info=0; /* LU */ int three =3; dgesv_( &three, &bColNo, Ax, &three, ipiv, D, &three, &info); if (info!=0){ //std::cout<<"linear algebra error"<pow(10,-3)){inside = false;} } #if 0 double planeV1 = d1 - (plane1.x()*vertex[0] + plane1.y()*vertex[1] + plane1.z()*vertex[2]); double planeV2 = d2 - (plane2.x()*vertex[0] + plane2.y()*vertex[1] + plane2.z()*vertex[2]); double planeV3 = d3 - (plane3.x()*vertex[0] + plane3.y()*vertex[1] + plane3.z()*vertex[2]); if (planeV1 <-pow(10,-3) ){ inside = false;} if (planeV2 <-pow(10,-3) ){ inside = false;} if (planeV3 <-pow(10,-3) ){ inside = false;} #endif if (inside == true){ //std::cout<<"vertex: "< #include #include #include #include #include namespace yade{ class PotentialBlock : public Shape { public: struct Planes{ vector vertexID; }; struct Vertices{ vector edgeID; vector planeID; }; struct Edges{ vector vertexID; }; void addPlaneStruct(); void addVertexStruct(); void addEdgeStruct(); vector planeStruct; vector vertexStruct; vector edgeStruct; Eigen::MatrixXd Amatrix; Eigen::MatrixXd Dmatrix; virtual ~PotentialBlock (); void postLoad(PotentialBlock&); YADE_CLASS_BASE_DOC_ATTRS_CTOR(PotentialBlock,Shape,"Geometry of PotentialBlock.", ((bool, isLining, false,, "false")) ((double, liningStiffness, pow(10.0,8),, "lining stiffness")) ((double, liningFriction, 20.0,, "lining stiffness")) ((double, liningLength, 0.0,, "lining stiffness")) ((double, liningTensionGap, 0.0,, "lining stiffness")) ((Vector3r, liningNormalPressure, Vector3r(0,0,0),, "lining stiffness")) ((Vector3r, liningTotalPressure, Vector3r(0,0,0),, "lining stiffness")) ((bool, isBoundary, false,, "boundary")) ((bool, isEastBoundary, false,, "boundary")) ((bool, isBolt, false,, "boundary")) ((bool, fixedNormal, false,, "boundary")) ((Vector3r, boundaryNormal, Vector3r::Zero(),,"normal direction of boundary")) ((bool, AabbMinMax, false,, "aabb")) ((Vector3r, minAabb, Vector3r::Zero(),,"min from box centre")) ((Vector3r, maxAabb, Vector3r::Zero(),,"max frin box centre")) ((Vector3r, minAabbRotated, Vector3r::Zero(),,"min from box centre")) ((Vector3r, maxAabbRotated, Vector3r::Zero(),,"max frin box centre")) ((Vector3r, halfSize, Vector3r::Zero(),,"max frin box centre")) ((vector , node, ,, "nodes ")) ((Real, gridVol, ,, "nodes ")) ((Quaternionr , oriAabb, Quaternionr::Identity(),, "r ")) ((Real , r, 0.1,, "r ")) ((Real , R, 1.0,, "R ")) ((Real , k, 0.1,, "k ")) ((Real , volume, 0.1,, "k ")) ((int, id, -1,, " for graphics")) ((bool, erase, false,, " for graphics")) ((vector, intactRock, false,, " for graphics")) ((vector, isBoundaryPlane, ,, "whether it is a boundary")) ((vector, hwater, ,, "height of pore water")) ((vector, JRC, ,, "rock joint")) ((vector, JCS, ,, "rock joint")) ((vector, asperity, ,, "rock joint")) ((vector, sigmaC, ,,"rock joint")) ((vector, phi_b, ,, "rock joint")) ((vector, phi_r, ,, "rock joint")) ((vector, cohesion, ,, "rock joint")) ((vector, tension, ,, "rock joint")) ((vector, lambda0, ,, "rock joint")) ((vector, heatCapacity, ,, "rock joint")) ((vector, rFactor, ,, "individual factors for r")) ((vector, vertices,,,"vertices")) //((Eigen::MatrixXd , Amatrix, ,, "a ")) //((Eigen::MatrixXd , Dmatrix, ,, "b ")) ((double, waterVolume, ,, "water")) ((vector ,verticesCD, ,, "vertices ")) ((vector , a, ,, "a ")) ((vector , b, ,, "b ")) ((vector , c, ,, "c ")) ((vector , d, ,, "d ")) ((vector , jointType, ,, "jointType")) , createIndex(); /*ctor*/ #if 0 for (int i=0; i #include void PotentialBlock2AABB::go(const shared_ptr& cm, shared_ptr& bv, const Se3r& se3, const Body*){ PotentialBlock* pp = static_cast(cm.get()); if(!bv){ bv=shared_ptr(new Aabb); } Aabb* aabb = static_cast(bv.get()); Matrix3r r = se3.orientation.toRotationMatrix(); if(pp->AabbMinMax == false){ Real distFromCentre = 1.05*pp->R; // std::max(maxD, pp->R); halfSize = Vector3r(distFromCentre,distFromCentre,distFromCentre); aabb->min = se3.position-halfSize; aabb->max = se3.position+halfSize; return; }else{ Matrix3r r=se3.orientation.toRotationMatrix(); Vector3r halfSizeMin(Vector3r::Zero());Vector3r halfSizeMax(Vector3r::Zero()); if(pp->vertices.size() ==0){ //pp->vertices.clear(); pp->vertices.push_back(Vector3r(pp->maxAabbRotated[0],pp->maxAabbRotated[1],pp->maxAabbRotated[2])); pp->vertices.push_back(Vector3r(pp->maxAabbRotated[0],pp->maxAabbRotated[1],-pp->minAabbRotated[2])); pp->vertices.push_back(Vector3r(-pp->minAabbRotated[0],-pp->minAabbRotated[1],pp->maxAabbRotated[2])); pp->vertices.push_back(Vector3r(-pp->minAabbRotated[0],-pp->minAabbRotated[1],-pp->minAabbRotated[2])); pp->vertices.push_back(Vector3r(-pp->minAabbRotated[0],pp->maxAabbRotated[1],pp->maxAabbRotated[2])); pp->vertices.push_back(Vector3r(-pp->minAabbRotated[0],pp->maxAabbRotated[1],-pp->minAabbRotated[2])); pp->vertices.push_back(Vector3r(pp->maxAabbRotated[0],-pp->minAabbRotated[1],pp->maxAabbRotated[2])); pp->vertices.push_back(Vector3r(pp->maxAabbRotated[0],-pp->minAabbRotated[1],-pp->minAabbRotated[2])); } Vector3r aabbMin(0,0,0); Vector3r aabbMax(0,0,0); for (int i=0; i<8; i++){ Vector3r vertex = r*(pp->oriAabb.conjugate()*pp->vertices[i]); if(vertex.x() < aabbMin.x()){ aabbMin.x() = vertex.x(); } if(vertex.y() < aabbMin.y()){ aabbMin.y() = vertex.y(); } if(vertex.z() < aabbMin.z()){ aabbMin.z() = vertex.z(); } if(vertex.x() > aabbMax.x()){ aabbMax.x() = vertex.x(); } if(vertex.y() > aabbMax.y()){ aabbMax.y() = vertex.y(); } if(vertex.z() > aabbMax.z()){ aabbMax.z() = vertex.z(); } } aabb->min= se3.position + 1.05*aabbMin; aabb->max = se3.position + 1.05*aabbMax; halfSizeMin = -1.0*aabbMin; halfSizeMax = 1.0*aabbMax; pp->halfSize = 1.1*Vector3r(std::max(halfSizeMin.x(),halfSizeMax.x()),std::max(halfSizeMin.y(),halfSizeMax.y()),std::max(halfSizeMin.z(),halfSizeMax.z())); return; } } YADE_PLUGIN((PotentialBlock2AABB)); #endif // YADE_POTENTIAL_BLOCKS trunk-2018.02b/pkg/dem/PotentialBlock2AABB.hpp000077500000000000000000000021731324306050200206350ustar00rootroot00000000000000/*CWBoon 2015 */ #ifdef YADE_POTENTIAL_BLOCKS #pragma once #include #include class PotentialBlock2AABB : public BoundFunctor { public : void go(const shared_ptr& cm, shared_ptr& bv, const Se3r&, const Body*); FUNCTOR1D(PotentialBlock); //REGISTER_ATTRIBUTES(BoundFunctor,(aabbEnlargeFactor)); YADE_CLASS_BASE_DOC_ATTRS(PotentialBlock2AABB,BoundFunctor,"Functor creating :yref:`Aabb` from :yref:`Sphere`.", ((Real,aabbEnlargeFactor,((void)"deactivated",-1),,"Relative enlargement of the bounding box; deactivated if negative.\n\n.. note::\n\tThis attribute is used to create distant interaction, but is only meaningful with an :yref:`InteractionGeometryFunctor` which will not simply discard such interactions: :yref:`Ig2_Sphere_Sphere_Dem3DofGeom::distFactor` / :yref:`Ig2_Sphere_Sphere_ScGeom::interactionDetectionFactor` should have the same value as :yref:`aabbEnlargeFactor`.")) ((Vector3r, halfSize, Vector3r::Zero(),,"halfSize")) ); }; REGISTER_SERIALIZABLE(PotentialBlock2AABB); #endif // YADE_POTENTIAL_BLOCKS trunk-2018.02b/pkg/dem/PotentialParticle.cpp000066400000000000000000000004111324306050200206370ustar00rootroot00000000000000/*CWBoon 2015 */ #ifdef YADE_POTENTIAL_PARTICLES //! To implement potential particles (Houlsby 2009) using sphere #include "PotentialParticle.hpp" YADE_PLUGIN((PotentialParticle)); PotentialParticle::~PotentialParticle() { } #endif // YADE_POTENTIAL_PARTICLES trunk-2018.02b/pkg/dem/PotentialParticle.hpp000066400000000000000000000040461324306050200206540ustar00rootroot00000000000000/*CWBoon 2015 */ #pragma once #ifdef YADE_POTENTIAL_PARTICLES #include #include #include #include #include #include namespace yade { class PotentialParticle : public Shape { public: virtual ~PotentialParticle (); YADE_CLASS_BASE_DOC_ATTRS_CTOR(PotentialParticle,Shape,"EXPERIMENTAL. Geometry of PotentialParticle.", ((int, id, 1,, "idNo")) ((bool, isBoundary, false,, "boundary")) ((bool, fixedNormal, false,, "use fixed normal")) ((Vector3r, boundaryNormal, Vector3r::Zero(),,"normal direction of boundary")) ((bool, AabbMinMax, false,, "aabb")) ((Vector3r, minAabb, Vector3r::Zero(),,"min from box centre")) ((Vector3r, maxAabb, Vector3r::Zero(),,"max from box centre")) ((Vector3r, minAabbRotated, Vector3r::Zero(),,"min from box centre")) ((Vector3r, maxAabbRotated, Vector3r::Zero(),,"max from box centre")) ((Vector3r, halfSize, Vector3r::Zero(),,"max from box centre")) ((Quaternionr , oriAabb, Quaternionr::Identity(),, "r ")) ((Real , r, 0.1,, "r ")) ((Real , R, 1.0,, "R ")) ((Real , k, 0.1,, "k ")) ((vector, vertices,,,"vertices")) ((vector , isBoundaryPlane, ,, "whether it is a boundaryPlane ")) ((vector , a, ,, "a ")) ((vector , b, ,, "b ")) ((vector , c, ,, "c ")) ((vector , d, ,, "d ")) , createIndex(); /*ctor*/ #if 0 for (int i=0; i #include void PotentialParticle2AABB::go(const shared_ptr& cm, shared_ptr& bv, const Se3r& se3, const Body*) { PotentialParticle* pp = static_cast(cm.get()); if(!bv) { bv=shared_ptr(new Aabb); } Aabb* aabb = static_cast(bv.get()); if(pp->AabbMinMax == false) { Real distFromCentre = 1.05*pp->R; // std::max(maxD, pp->R); halfSize = Vector3r(distFromCentre,distFromCentre,distFromCentre); aabb->min = se3.position-halfSize; aabb->max = se3.position+halfSize; return; } else { Matrix3r r=se3.orientation.toRotationMatrix(); Vector3r halfSizeMin(Vector3r::Zero()); Vector3r halfSizeMax(Vector3r::Zero()); if(pp->vertices.size() ==0) { //pp->vertices.clear(); pp->vertices.push_back(Vector3r(pp->maxAabbRotated[0],pp->maxAabbRotated[1],pp->maxAabbRotated[2])); pp->vertices.push_back(Vector3r(pp->maxAabbRotated[0],pp->maxAabbRotated[1],-pp->minAabbRotated[2])); pp->vertices.push_back(Vector3r(-pp->minAabbRotated[0],-pp->minAabbRotated[1],pp->maxAabbRotated[2])); pp->vertices.push_back(Vector3r(-pp->minAabbRotated[0],-pp->minAabbRotated[1],-pp->minAabbRotated[2])); pp->vertices.push_back(Vector3r(-pp->minAabbRotated[0],pp->maxAabbRotated[1],pp->maxAabbRotated[2])); pp->vertices.push_back(Vector3r(-pp->minAabbRotated[0],pp->maxAabbRotated[1],-pp->minAabbRotated[2])); pp->vertices.push_back(Vector3r(pp->maxAabbRotated[0],-pp->minAabbRotated[1],pp->maxAabbRotated[2])); pp->vertices.push_back(Vector3r(pp->maxAabbRotated[0],-pp->minAabbRotated[1],-pp->minAabbRotated[2])); } Vector3r aabbMin(0,0,0); Vector3r aabbMax(0,0,0); for (int i=0; i<8; i++) { Vector3r vertex = r*(pp->oriAabb.conjugate()*pp->vertices[i]); if(vertex.x() < aabbMin.x()) { aabbMin.x() = vertex.x(); } if(vertex.y() < aabbMin.y()) { aabbMin.y() = vertex.y(); } if(vertex.z() < aabbMin.z()) { aabbMin.z() = vertex.z(); } if(vertex.x() > aabbMax.x()) { aabbMax.x() = vertex.x(); } if(vertex.y() > aabbMax.y()) { aabbMax.y() = vertex.y(); } if(vertex.z() > aabbMax.z()) { aabbMax.z() = vertex.z(); } } aabb->min= se3.position + 1.05*aabbMin; aabb->max = se3.position + 1.05*aabbMax; halfSizeMin = -1.0*aabbMin; halfSizeMax = 1.0*aabbMax; return; } } YADE_PLUGIN((PotentialParticle2AABB)); #endif // YADE_POTENTIAL_PARTICLES trunk-2018.02b/pkg/dem/PotentialParticle2AABB.hpp000066400000000000000000000013741324306050200213450ustar00rootroot00000000000000/*CWBoon 2015 */ #pragma once #ifdef YADE_POTENTIAL_PARTICLES #include #include class PotentialParticle2AABB : public BoundFunctor { public : void go(const shared_ptr& cm, shared_ptr& bv, const Se3r&, const Body*); FUNCTOR1D(PotentialParticle); //REGISTER_ATTRIBUTES(BoundFunctor,(aabbEnlargeFactor)); YADE_CLASS_BASE_DOC_ATTRS(PotentialParticle2AABB,BoundFunctor,"EXPERIMENTAL. Functor creating :yref:`Aabb` from :yref:`PotentialParticle`.", ((Real,aabbEnlargeFactor,((void)"deactivated",-1),,"see :yref:`Sphere2AABB`.")) ((Vector3r, halfSize, Vector3r::Zero(),,"halfSize")) ); }; REGISTER_SERIALIZABLE(PotentialParticle2AABB); #endif // YADE_POTENTIAL_PARTICLES trunk-2018.02b/pkg/dem/RockBolt.cpp000077500000000000000000000532271324306050200167530ustar00rootroot00000000000000/*CWBoon 2016 */ /* Please cite: */ /* CW Boon, GT Houlsby, S Utili (2015). Designing Tunnel Support in Jointed Rock Masses Via the DEM. Rock Mechanics and Rock Engineering, 48 (2), 603-632. */ #ifdef YADE_POTENTIAL_BLOCKS #include "RockBolt.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 #include #include #include #include #include #include void RockBolt::action(){ if (openingCreated == true && installed == false){ vector distanceFrOpening; FOREACH(const shared_ptr& b, *scene->bodies){ if (!b) continue; if (b->isClump() == true) continue; PotentialBlock* pb=static_cast(b->shape.get()); if(!pb) continue; if(pb->isBoundary == true || pb->erase== true || pb->isLining==true){continue;} State* state1 = b->state.get(); Vector3r intersectionPt(0,0,0); if ( installBolts(pb, state1, startingPoint, boltDirection, boltLength, intersectionPt )){ blockIDs.push_back(b->id); pb->isBolt = true; distanceFrOpening.push_back((intersectionPt-startingPoint).norm()); } } /* sort blocks according to distance from the centre */ int totalBlocks = blockIDs.size(); for (int i=0; i 0 && distanceFrOpening[ihole-1] > distance){ distanceFrOpening[ihole] = distanceFrOpening[ihole-1]; blockIDs[ihole] = blockIDs[ihole-1]; ihole = ihole-1; } distanceFrOpening[ihole] = distance; blockIDs[ihole] = blockID; } Vector3r jointIntersection (0,0,0); for (int j=0; jstate.get(); Shape* shape1 = Body::byId(blockIDs[j],scene)->shape.get(); PotentialBlock *pb=static_cast(shape1); int totalPlanes = pb->a.size(); int intersectNo = 0; vector tempCoord; vector distance; for (int i=0; iori*Vector3r(pb->a[i], pb->b[i], pb->c[i]); double planeD = plane.dot(state1->pos) + pb->d[i] +pb->r; if ( intersectPlane(pb, state1,startingPoint,boltDirection, boltLength, jointIntersection, plane, planeD)){ double sign = plane.dot(boltDirection); jointIntersection = jointIntersection - Mathr::Sign(sign)*halfActiveLength*boltDirection; distance.push_back(jointIntersection.norm() ); jointIntersection = state1->ori.conjugate()*(jointIntersection-state1->pos); intersectNo++; if(intersectNo >2){std::cout<<"intersectNo > 2: "<state.get(); endPoint = state1 ->ori.conjugate()*(endPoint-state1->pos); if(useMidPoint == false){ localCoordinates.push_back(tempCoord[0]); }else{ localCoordinates.push_back(0.5*(endPoint+tempCoord[0])); } localCoordinates.push_back(endPoint); }else{ if(useMidPoint == false){ if(distance[0] < distance[1] ){ localCoordinates.push_back(tempCoord[0]);localCoordinates.push_back(tempCoord[1]); }else{ localCoordinates.push_back(tempCoord[1]);localCoordinates.push_back(tempCoord[0]); } }else{ Vector3r midPoint = 0.5*(tempCoord[0] + tempCoord[1]); if(j!=0){ localCoordinates.push_back(midPoint); localCoordinates.push_back(midPoint); }else{ if(distance[0] < distance[1] ){ localCoordinates.push_back(tempCoord[0]);localCoordinates.push_back(midPoint); }else{ localCoordinates.push_back(tempCoord[1]);localCoordinates.push_back(midPoint); } } } } tempCoord.clear(); distance.clear(); //std::cout<<"j: "< tempCoord = localCoordinates; localCoordinates.clear(); State* stateB = Body::byId(blockIDs[0],scene)->state.get(); Vector3r startPt = stateB->ori.conjugate()*(startingPoint-stateB->pos); localCoordinates.push_back(startPt); for (int i=0; istate.get(); endPoint = stateA->ori.conjugate()*(endPoint-stateA->pos); localCoordinates.push_back(endPoint); } #endif installed = true; distanceFrOpening.clear(); } if (installed == true && blockIDs.size()>=2){ averageForce = 0.0; maxForce = 0.0; int blockNo = blockIDs.size(); for (int j=1; jstate.get(); State* state2 = Body::byId(blockIDs[j],scene)->state.get(); Shape* shape1 = Body::byId(blockIDs[j-1],scene)->shape.get(); Shape* shape2 = Body::byId(blockIDs[j],scene)->shape.get(); PotentialBlock *s1=static_cast(shape1); PotentialBlock *s2=static_cast(shape2); Vector3r nodeDistance = getNodeDistance(s1,state1,s2,state2,localCoordinates[2*j-1],localCoordinates[2*j]); /* 2 minus 1, from 1 to 2 */ if (initialLength.size() < blockNo-1 ){ /*not initialized */ initialLength.push_back(nodeDistance.norm()*Mathr::Sign(nodeDistance.dot(boltDirection) )); /* negative if there is overlap */ initialDirection.push_back(nodeDistance); forces.push_back(0.0); axialForces.push_back(0.0); shearForces.push_back(0.0);ruptured.push_back(false); nodeDistanceVec.push_back(nodeDistance); nodePosition.push_back(Vector3r(0,0,0)); distanceFrCentre.push_back(0.0); }else{ if (resetLengthInit == true){ initialLength[j-1] =nodeDistance.norm()*Mathr::Sign(nodeDistance.dot(boltDirection) ); resetLengthInit = false; } Vector3r direction = nodeDistance; direction.normalize(); double dirSign = 1.0; nodeDistanceVec[j-1] =nodeDistance; //if (initialDirection[j-1].norm()>pow(10,-11) ){ // dirSign = direction.dot(initialDirection[j-1]); //}else{ dirSign = direction.dot(boltDirection); //FIXME assume special case does not happen, i.e., activeLength is long enough //} Vector3r axialForce = (normalStiffness*(Mathr::Sign(dirSign)*nodeDistance.norm() - initialLength[j-1])+preTension)*(Mathr::Sign(dirSign)*direction);/* the last term makes sure tension is always pointing in the direction of boltdirection */ //Vector3r axialForce = (axialStiffness/initialLength[j-1]*(Mathr::Sign(dirSign)*nodeDistance.norm() - initialLength[j-1])+preTension)*(Mathr::Sign(dirSign)*direction);/* the last term makes sure tension is always pointing in the direction of boltdirection */ Vector3r shearDir = boltDirection.cross(Vector3r(0,1,0)); shearDir.normalize(); Vector3r shearForce = shearStiffness*(nodeDistance.dot(shearDir))*shearDir; if(axialForce.norm()>axialMax || shearForce.norm()>shearMax || ruptured[j-1] == true){ axialForce = Vector3r(0,0,0); shearForce = Vector3r(0,0,0); ruptured[j-1]= true; } axialForces[j-1] = axialForce.norm(); shearForces[j-1] = shearForce.norm(); forces[j-1] = (axialForce +shearForce).norm(); //*Mathr::Sign(dirSign); averageForce += forces[j-1]; maxForce = std::max(maxForce,forces[j-1]); Vector3r totalForce = axialForce + shearForce; Vector3r c1x = state1->ori*localCoordinates[2*j-1]+0.5*nodeDistance; nodePosition[j-1] = state1->pos + c1x; distanceFrCentre[j-1]=nodePosition[j-1].dot(boltDirection); if(j==1){ displacements=(state1->pos+state1->ori*localCoordinates[0]).dot(boltDirection); } Vector3r c2x = state2->ori*localCoordinates[2*j]-0.5*nodeDistance; scene->forces.addTorque(blockIDs[j-1],c1x.cross(totalForce)); scene->forces.addTorque(blockIDs[j],-c2x.cross(totalForce)); scene->forces.addForce(blockIDs[j-1],totalForce ); scene->forces.addForce(blockIDs[j],-totalForce ); } } averageForce = averageForce/static_cast(blockNo-1); } if ((scene->iter-vtkRefTimeStep)%vtkIteratorInterval == 0 && installed == true && blockIDs.size()>=2){ vtkRefTimeStep = scene->iter; vtkSmartPointer appendFilter = vtkSmartPointer::New(); int blockNo = blockIDs.size(); /// BOLT FORCE // vtkSmartPointer boltNodalPoints = vtkSmartPointer::New(); vtkSmartPointer boltNodalPointsCells = vtkSmartPointer::New(); vtkSmartPointer boltNode = vtkSmartPointer::New(); vtkSmartPointer boltNodeCells = vtkSmartPointer::New(); vtkSmartPointer boltNodalForce = vtkSmartPointer::New(); boltNodalForce->SetNumberOfComponents(3); boltNodalForce->SetName("Bolt Force"); //Linear velocity in Vector3 form vtkSmartPointer boltAxialForce = vtkSmartPointer::New(); boltAxialForce->SetNumberOfComponents(3); boltAxialForce->SetName("AxialForce"); //Linear velocity in Vector3 form vtkSmartPointer boltShearForce = vtkSmartPointer::New(); boltShearForce->SetNumberOfComponents(3); boltShearForce->SetName("Shear Force"); //Linear velocity in Vector3 form //#if 0; for (int i=0; i state.get(); Vector3r globalPoint1 = state1->ori*localCoordinates[2*i]+state1->pos; Vector3r globalPoint2 = state1->ori*localCoordinates[2*i+1]+state1->pos; vtkSmartPointer lineSource = vtkSmartPointer::New(); double p0[3] = {globalPoint1[0], globalPoint1[1], globalPoint1[2]}; double p1[3] = {globalPoint2[0], globalPoint2[1], globalPoint2[2]}; lineSource->SetPoint1(p0); lineSource->SetPoint2(p1); appendFilter->AddInputConnection(lineSource-> GetOutputPort()); vtkIdType pid2[1]; pid2[0]= boltNodalPoints->InsertNextPoint(globalPoint1[0], globalPoint1[1], globalPoint1[2]); boltNodalPointsCells->InsertNextCell(1,pid2); pid2[0]= boltNodalPoints->InsertNextPoint(globalPoint2[0], globalPoint2[1], globalPoint2[2]); boltNodalPointsCells->InsertNextCell(1,pid2); if(i < blockNo-1){ /* draw a line between joints*/ State* state2 = Body::byId(blockIDs[i+1],scene)->state.get(); Vector3r globalPoint3 = state2->ori*localCoordinates[2*i+2]+state2->pos; vtkSmartPointer lineSourceJoint = vtkSmartPointer::New(); double p2[3] = {globalPoint2[0], globalPoint2[1], globalPoint2[2]}; double p3[3] = {globalPoint3[0], globalPoint3[1], globalPoint3[2]}; lineSourceJoint->SetPoint1(p2); lineSourceJoint->SetPoint2(p3); appendFilter->AddInputConnection(lineSourceJoint-> GetOutputPort()); /* try to draw forces */ vtkIdType pid[1]; Vector3r midPoint = 0.5*(globalPoint2+globalPoint3); pid[0] = boltNode->InsertNextPoint( midPoint[0], midPoint[1], midPoint[2]); boltNodeCells->InsertNextCell(1,pid); Vector3r plotDirection = boltDirection.cross(Vector3r(0,1,0)); if(plotDirection.dot(Vector3r(1,0,0)) <0.0){ plotDirection = -plotDirection; } plotDirection.normalize(); Vector3r nodalForce = forces[i]*plotDirection; float f[3]={nodalForce[0],nodalForce[1],nodalForce[2]}; boltNodalForce->InsertNextTupleValue(f); Vector3r axialForce = axialForces[i]*plotDirection; float fa[3]={axialForce[0],axialForce[1],axialForce[2]}; boltAxialForce->InsertNextTupleValue(fa); Vector3r shearForce = shearForces[i]*plotDirection; float fs[3]={shearForce[0],shearForce[1],shearForce[2]}; boltShearForce->InsertNextTupleValue(fs); } //lineSource->Update(); } //#endif vtkSmartPointer pbUgCP2 = vtkSmartPointer::New(); pbUgCP2->SetPoints(boltNodalPoints); pbUgCP2->SetCells(VTK_VERTEX, boltNodalPointsCells); vtkSmartPointer writerC = vtkSmartPointer::New(); writerC->SetDataModeToAscii(); string fileBoltC=fileName+"boltNodalPoints"+name+"."+std::to_string(scene->iter)+".vtu"; writerC->SetFileName(fileBoltC.c_str()); writerC->SetInputData(pbUgCP2); writerC->Write(); vtkSmartPointer pbUgCP = vtkSmartPointer::New(); pbUgCP->SetPoints(boltNode); pbUgCP->SetCells(VTK_VERTEX, boltNodeCells); pbUgCP->GetPointData()->AddArray(boltNodalForce); pbUgCP->GetPointData()->AddArray(boltAxialForce); pbUgCP->GetPointData()->AddArray(boltShearForce); vtkSmartPointer writerB = vtkSmartPointer::New(); writerB->SetDataModeToAscii(); string fileBolt=fileName+"boltNodeForce"+name+"."+std::to_string(scene->iter)+".vtu"; writerB->SetFileName(fileBolt.c_str()); writerB->SetInputData(pbUgCP); writerB->Write(); vtkSmartPointer writer = vtkXMLPolyDataWriter::New(); writer->SetDataModeToAscii(); string fn=fileName+"-bolt"+name+"."+std::to_string(scene->iter)+".vtp"; writer->SetFileName(fn.c_str()); writer->SetInputConnection(appendFilter->GetOutputPort()); writer->Write(); } } Vector3r RockBolt::getNodeDistance(const PotentialBlock* cm1,const State* state1,const PotentialBlock* cm2,const State* state2, const Vector3r localPt1, const Vector3r localPt2){ Vector3r nodeDist = Vector3r(0,0,0.0); Vector3r global1 = state1->ori*localPt1 + state1->pos; Vector3r global2 = state2->ori*localPt2 + state2->pos; return (global2-global1); } double RockBolt::evaluateFNoSphereVol(const PotentialBlock* s1,const State* state1, const Vector3r newTrial){ Vector3r tempP1 = newTrial - state1->pos; /* Direction cosines */ //state1.ori.normalize(); Vector3r localP1 = state1->ori.conjugate()*tempP1; Real x = localP1.x(); Real y = localP1.y(); Real z = localP1.z(); int planeNo = s1->a.size(); Real r = s1->r; int insideCount = 0; for (int i=0; ia[i]*x + s1->b[i]*y + s1->c[i]*z - s1->d[i]-1.0002*r; //-pow(10,-10); if (Mathr::Sign(plane)*1.0<0.0){ insideCount++; } } /* Complete potential particle */ Real f = 1.0; if (insideCount == planeNo){ f = -1.0;} return f; } bool RockBolt::installBolts(const PotentialBlock* s1,const State* state1,const Vector3r startingPt,const Vector3r direction, const double length, Vector3r& intersectionPt){ Vector3r endPt = startingPt + length*direction; // PotentialBlock *s1=static_cast(cm1.get()); int planeNoA = s1->a.size(); /* line equality */ // x = x0 + t*dirX // y = y0 + t*dirY // z = z0 + t*dirZ /* linear inequality for blocks */ // Ax - d < 0 /* Variables to keep things neat */ int NUMCON = 3 /* equality */ + planeNoA /*block inequality */; int NUMVAR = 3/*3D */ + 1 /*t */+ 1 /* s */; double s = 0.0; bool converge = true; Matrix3r Q1 = (state1->ori.conjugate()).toRotationMatrix(); Eigen::MatrixXd A1 = Eigen::MatrixXd::Zero(planeNoA,3); for (int i=0; i < planeNoA; i++){ A1(i,0) = s1->a[i]; A1(i,1) = s1->b[i]; A1(i,2) = s1->c[i]; } Eigen::MatrixXd AQ1 = A1*Q1; Eigen::MatrixXd pos1(3,1); pos1(0,0) = state1->pos.x(); pos1(1,0) = state1->pos.y(); pos1(2,0) = state1->pos.z(); Eigen::MatrixXd Q1pos1 = AQ1*pos1; ClpSimplex model2; model2.setOptimizationDirection(1); // Create space for 3 columns and 10000 rows int numberRows = NUMCON; int numberColumns = NUMVAR; // This is fully dense - but would not normally be so // Arrays will be set to default values model2.resize(0, numberColumns); model2.setObjectiveCoefficient(0,0.0); model2.setObjectiveCoefficient(1,0.0); model2.setObjectiveCoefficient(2,0.0); model2.setObjectiveCoefficient(3,0.0); model2.setObjectiveCoefficient(4,1.0); for (int k = 0; k < 3; k++){ model2.setColumnLower(k,-COIN_DBL_MAX); model2.setColumnUpper(k,COIN_DBL_MAX); } model2.setColumnLower(3,openingRad); model2.setColumnUpper(3,length); model2.setColumnLower(4,-COIN_DBL_MAX); model2.setColumnUpper(4,COIN_DBL_MAX); // Rows double rowLower[numberRows]; double rowUpper[numberRows]; rowLower[0] = startingPt.x(); rowLower[1] = startingPt.y(); rowLower[2] = startingPt.z(); rowUpper[0] = startingPt.x(); rowUpper[1] = startingPt.y(); rowUpper[2] = startingPt.z(); for (int k = 0; k < planeNoA; k++){ rowLower[3+k]= -COIN_DBL_MAX; rowUpper[3+k] = s1->d[k] + s1->r + Q1pos1(k,0); } int row1Index[] = {0,3}; double row1Value[] = {1.0, -1.0*direction.x()}; model2.addRow(2,row1Index,row1Value,rowLower[0],rowUpper[0]); int row2Index[] = {1,3}; double row2Value[] = {1.0, -1.0*direction.y()}; model2.addRow(2,row2Index,row2Value,rowLower[1],rowUpper[1]); int row3Index[] = {2,3}; double row3Value[] = {1.0, -1.0*direction.z()}; model2.addRow(2,row3Index,row3Value,rowLower[2],rowUpper[2]); for (int i=0; iori.conjugate()*(temp-state1->pos); s = columnPrimal[4]; int convergeSuccess = model2.status(); if(s>-pow(10,-8) || convergeSuccess !=0){ return false; }else{ return true; } } bool RockBolt::intersectPlane(const PotentialBlock* s1,const State* state1,const Vector3r startingPt,const Vector3r direction, const double length, Vector3r& intersectionPt, const Vector3r plane, const double planeD){ bool feasible = true; Vector3r endPt = startingPt + length*direction; // PotentialBlock *s1=static_cast(cm1.get()); int planeNoA = s1->a.size(); /* Variables to keep things neat */ int NUMCON = 3 /* equality */ + 1 /*planeEquality */; int NUMVAR = 3/*3D */ + 1 /*t */; double t = 0.0; bool converge = true; /* line equality */ // x = x0 + t*dirX // y = y0 + t*dirY // z = z0 + t*dirZ /* linear equality for blocks */ // Ax - d = 0 /* LINEAR CONSTRAINTS */ ClpSimplex model2; model2.setOptimizationDirection(1); // Create space for 3 columns and 10000 rows int numberRows = NUMCON; int numberColumns = NUMVAR; // This is fully dense - but would not normally be so // Arrays will be set to default values model2.resize(0, numberColumns); model2.setObjectiveCoefficient(0,0.0); model2.setObjectiveCoefficient(1,0.0); model2.setObjectiveCoefficient(2,0.0); model2.setObjectiveCoefficient(3,1.0); for (int k = 0; k < 4; k++){ model2.setColumnLower(k,-COIN_DBL_MAX); model2.setColumnUpper(k,COIN_DBL_MAX); } // Rows double rowLower[numberRows]; double rowUpper[numberRows]; rowLower[0] = startingPt.x(); rowLower[1] = startingPt.y(); rowLower[2] = startingPt.z(); rowLower[3] = planeD; rowUpper[0] = startingPt.x(); rowUpper[1] = startingPt.y(); rowUpper[2] = startingPt.z(); rowUpper[3] = planeD; int row1Index[] = {0,3}; double row1Value[] = {1.0, -1.0*direction.x()}; model2.addRow(2,row1Index,row1Value,rowLower[0],rowUpper[0]); int row2Index[] = {1,3}; double row2Value[] = {1.0, -1.0*direction.y()}; model2.addRow(2,row2Index,row2Value,rowLower[1],rowUpper[1]); int row3Index[] = {2,3}; double row3Value[] = {1.0, -1.0*direction.z()}; model2.addRow(2,row3Index,row3Value,rowLower[2],rowUpper[2]); int row4Index[] = {0,1,2}; double row4Value[] = {plane.x(),plane.y(),plane.z()}; model2.addRow(3,row4Index,row4Value,rowLower[3],rowUpper[3]); model2.scaling(0); model2.setLogLevel(0); model2.primal(); double * columnPrimal = model2.primalColumnSolution(); Vector3r temp = Vector3r(columnPrimal[0],columnPrimal[1],columnPrimal[2]); intersectionPt = temp; //state1->ori.conjugate()*(temp-state1->pos); t = columnPrimal[3]; double f = evaluateFNoSphereVol(s1,state1,intersectionPt); //std::cout<<"t: "<1.001*length || t< 0.0 || f> 0.0|| convergeSuccess !=0){ return false; }else{ return true; } } YADE_PLUGIN((RockBolt)); #endif // YADE_POTENTIAL_BLOCKS trunk-2018.02b/pkg/dem/RockBolt.hpp000077500000000000000000000105651324306050200167560ustar00rootroot00000000000000/*CWBoon 2016 */ /* Please cite: */ /* CW Boon, GT Houlsby, S Utili (2015). Designing Tunnel Support in Jointed Rock Masses Via the DEM. Rock Mechanics and Rock Engineering, 48 (2), 603-632. */ #ifdef YADE_POTENTIAL_BLOCKS #pragma once #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 "/home/boon/coin-Clp/Clp/src/ClpSimplex.hpp" //#include "/home/boon/coin-Clp/CoinUtils/src/CoinHelperFunctions.hpp" //#include "/home/boon/coin-Clp/CoinUtils/src/CoinTime.hpp" //#include "/home/boon/coin-Clp/CoinUtils/src/CoinBuild.hpp" //#include "/home/boon/coin-Clp/CoinUtils/src/CoinModel.hpp" class RockBolt: public PeriodicEngine{ public: #if 0 struct Bolts{ Bolts(Vector3r pt1, Vector3r pt2){startingPoint = pt1; endPoint=pt2; } Vector3r startingPoint; Vector3r endPoint; /* variables stored in sequence starting from the block closest to the opening */ vector blockIDs; /*blocks intersected */ vector localCoordinates; /*local coordinates inside blocks */ vector initialLength; }; vector bolt; #endif Vector3r getNodeDistance(const PotentialBlock* cm1,const State* state1,const PotentialBlock* cm2,const State* state2, const Vector3r localPt1, const Vector3r localPt2); bool installBolts(const PotentialBlock* cm1,const State* state1,const Vector3r startingPt,const Vector3r direction, const double length, Vector3r& intersectionPt); double evaluateFNoSphereVol(const PotentialBlock* s1,const State* state1, const Vector3r newTrial); bool intersectPlane(const PotentialBlock* s1,const State* state1,const Vector3r startingPt,const Vector3r direction, const double length, Vector3r& intersectionPt, const Vector3r plane, const double planeD); virtual void action(void); YADE_CLASS_BASE_DOC_ATTRS_CTOR_PY(RockBolt,PeriodicEngine,"Engine recording potential blocks as surfaces into files with given periodicity.", ((double,normalStiffness,0.0 ,,"EA/L")) ((double,shearStiffness,0.0 ,,"stiffness")) ((double,axialStiffness,0.0 ,,"EA")) ((bool,useMidPoint,false ,,"large length")) ((double,halfActiveLength,0.02 ,,"stiffness")) ((bool,resetLengthInit,false ,,"reset length for pretension")) ((Vector3r,startingPoint,Vector3r(0,0,0) ,,"startingPt")) ((double,boltLength,0.0 ,,"startingPt")) ((Vector3r,boltDirection,Vector3r(0,0,0) ,,"direction")) ((vector,blockIDs, ,,"ids")) ((double,displacements, ,,"ids")) ((vector,localCoordinates, ,,"local coordinates of intersection")) ((vector,initialLength, ,,"initial length")) ((vector,initialDirection, ,,"initial length")) ((vector,nodeDistanceVec, ,,"nodeDistance")) ((vector,nodePosition, ,,"nodePosition")) ((vector,distanceFrCentre, ,,"nodePosition")) ((vector,forces, ,,"force")) ((vector,axialForces, ,,"force")) ((vector,shearForces, ,,"force")) ((double,openingRad,5.0 ,,"estimated opening radius")) ((double,preTension,0.0 ,,"prestressed tension")) ((double,averageForce,0.0 ,,"averageForce")) ((double,maxForce,0.0 ,,"maxForce")) ((bool,installed,false ,,"installed?")) ((bool,openingCreated,false ,,"opening created?")) ((vector,ruptured, ,,"ruptured")) ((Real,axialMax,1000000000 ,,"maximum axial force")) ((Real,shearMax,1000000000 ,,"maximum shear force")) ((int,vtkIteratorInterval,10000 ,,"how often to print vtk")) ((int,vtkRefTimeStep,1 ,,"first timestep to print vtk")) ((string,fileName,,,"File prefix to save to")) ((string,name,,,"File prefix to save to")) , , ); }; REGISTER_SERIALIZABLE(RockBolt); #endif // YADE_POTENTIAL_BLOCKS trunk-2018.02b/pkg/dem/RockLiningGlobal.cpp000077500000000000000000000662571324306050200204230ustar00rootroot00000000000000/*CWBoon 2016 */ /* Please cite: */ /* CW Boon, GT Houlsby, S Utili (2015). Designing Tunnel Support in Jointed Rock Masses Via the DEM. Rock Mechanics and Rock Engineering, 48 (2), 603-632. */ #ifdef YADE_POTENTIAL_BLOCKS #include "RockLiningGlobal.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 #include #include #include #include #include #include #include #include void RockLiningGlobal::action(){ const double PI = 3.14159; if (openingCreated == true && installed == false){ double angleInterval = 2.0*PI/static_cast(totalNodes); for (int n=0; n distanceFrOpening; vector IDs; double outerRadius = openingRad + 1.0; FOREACH(const shared_ptr& b, *scene->bodies){ if (!b) continue; if (b->isClump() == true) continue; PotentialBlock* pb=static_cast(b->shape.get()); if(!pb) continue; if(pb->isBoundary == true || pb->erase== true || pb->isLining==true){continue;} State* state1 = b->state.get(); Vector3r intersectionPt(0,0,0); if ( installLining(pb, state1, startingPoint, searchDir, outerRadius, intersectionPt )){ IDs.push_back(b->id); distanceFrOpening.push_back((intersectionPt-startingPoint).norm()); //std::cout<<"currentAngle: "<id: "<id<<", dist: "<<(intersectionPt-startingPoint).norm()<state.get(); Shape* shape1 = Body::byId(closestID,scene)->shape.get(); PotentialBlock *pb=static_cast(shape1); int totalPlanes = pb->a.size(); int intersectNo = 0; Vector3r nodeLocalPos(0,0,0); Vector3r nodeGlobalPos(0,0,0); //std::cout<<"totalPlanes: "<ori*Vector3r(pb->a[i], pb->b[i], pb->c[i]); double planeD = plane.dot(state1->pos) + pb->d[i] +pb->r; if ( intersectPlane(pb, state1,startingPoint,searchDir, outerRadius, jointIntersection, plane, planeD)){ double distance = jointIntersection.norm(); if (distance < closestPlaneDist){ closestPlaneDist = distance; nodeLocalPos = state1->ori.conjugate()*(jointIntersection-state1->pos); nodeGlobalPos=jointIntersection; } } } if(nodeGlobalPos.norm() > 1.03*openingRad){ nodeGlobalPos=1.03*openingRad*searchDir;} //if(nodeGlobalPos.norm() < 0.98*openingRad){ continue;} //initOverlap = interfaceTension/interfaceStiffness; nodeGlobalPos = nodeGlobalPos + searchDir*initOverlap; localCoordinates.push_back(nodeLocalPos); refPos.push_back(nodeGlobalPos); int nodeID = insertNode(nodeGlobalPos, lumpedMass, contactLength); blockIDs.push_back(nodeID); //(nodeID); //(closestID); refOri.push_back(Quaternionr::Identity()); //(state1->ori); installed = true; axialForces.push_back(0.0); shearForces.push_back(0.0); moment.push_back(0.0); sigmaMax.push_back(0.0); sigmaMin.push_back(0.0); displacement.push_back(0.0); radialDisplacement.push_back(0.0); } totalNodes=blockIDs.size(); /* Assembling global stiffness matrix */ for (int n=0; n=2){ double displacementMatrix[totalNodes*3]; memset(displacementMatrix,0.0,sizeof(displacementMatrix)); //averageForce = 0.0; maxForce = 0.0; int blockNo = blockIDs.size(); for (int j=0; jstate.get(); State* state2 = Body::byId(blockIDs[nextNode],scene)->state.get(); Quaternionr qA = (state1->ori); double thetaA = 2.0 *acos(qA.w()); if(qA.y() < 0.0){ thetaA = -thetaA ; } Quaternionr qB = (state2->ori); double thetaB = 2.0 *acos(qB.w()); if(qB.y() < 0.0){ thetaB = -thetaB ; } double deformedAngle = refAngle[j];// - thetaA; double temperatureForce = expansionFactor*lengthNode[j]*EA; Vector3r tempForceGlobal = Vector3r(temperatureForce*cos(refAngle[j] ) , 0.0,-temperatureForce*sin(refAngle[j] ) ) ; //Ttranspose Vector3r globalDispA = state1->pos - refPos[j]; double localXa = globalDispA.x()*cos(deformedAngle) + globalDispA.z()*sin(deformedAngle); double localYa = -globalDispA.x()*sin(deformedAngle) + globalDispA.z()*cos(deformedAngle); Vector3r globalDispB = state2->pos - refPos[nextNode]; double localXb = globalDispB.x()*cos(deformedAngle) + globalDispB.z()*sin(deformedAngle); double localYb = -globalDispB.x()*sin(deformedAngle) + globalDispB.z()*cos(deformedAngle); axialForces[j] = EA/lengthNode[j] * (localXa-localXb); shearForces[j] = 12.0*EI/(pow(lengthNode[j],3))*(localYa-localYb) - 6.0*EI/(pow(lengthNode[j],2))*(thetaA+thetaB); moment[j] = -6.0*EI/(pow(lengthNode[j],2))*(localYa-localYb) + 2.0*EI/lengthNode[j]*(2.0*thetaA+thetaB); double globalForceX = axialForces[j]*cos(deformedAngle) - shearForces[j]*sin(deformedAngle); double globalForceZ = axialForces[j]*sin(deformedAngle) + shearForces[j]*cos(deformedAngle); Vector3r totalForceA = Vector3r(globalForceX, 0.0, globalForceZ); Vector3r torqueA = moment[j]*Vector3r(0,1,0); double axialForcesB = EA/lengthNode[j] * (localXb-localXa); double shearForcesB = -12.0*EI/(pow(lengthNode[j],3))*(localYa-localYb) + 6.0*EI/(pow(lengthNode[j],2))*(thetaA+thetaB); double momentB = -6.0*EI/(pow(lengthNode[j],2))*(localYa-localYb) + 2.0*EI/lengthNode[j]*(thetaA+2.0*thetaB); double globalForceXb = axialForcesB*cos(deformedAngle) - shearForcesB*sin(deformedAngle); double globalForceZb = axialForcesB*sin(deformedAngle) + shearForcesB*cos(deformedAngle); Vector3r totalForceB = Vector3r(globalForceXb, 0.0, globalForceZb); Vector3r torqueB = momentB*Vector3r(0,1,0); double area = liningThickness*1.0; sigmaMax[j] = axialForces[j]/area + fabs(moment[j])*(2.0*liningThickness)/Inertia; sigmaMin[j] = axialForces[j]/area - fabs(moment[j])*(2.0*liningThickness)/Inertia; double displacementSign = (state1->pos - refPos[j]).dot(refPos[j]); displacement[j] = Mathr::Sign(displacementSign)*(state1->pos - refPos[j]).norm(); Vector3r dir = refPos[j]; dir.normalize(); radialDisplacement[j] = (state1->pos-refPos[j]).dot(dir); scene->forces.addTorque(blockIDs[j],-torqueA); scene->forces.addForce(blockIDs[j],-totalForceA); scene->forces.addTorque(blockIDs[nextNode],-torqueB); scene->forces.addForce(blockIDs[nextNode],-totalForceB); } //std::cout<<"end of rock llining global"<iter-vtkRefTimeStep)%vtkIteratorInterval == 0 && installed == true && blockIDs.size()>=2){ vtkRefTimeStep = scene->iter; vtkSmartPointer appendFilter = vtkSmartPointer::New(); int blockNo = blockIDs.size(); /// lining FORCE // vtkSmartPointer liningNode = vtkSmartPointer::New(); vtkSmartPointer liningNodeCells = vtkSmartPointer::New(); vtkSmartPointer liningNodalMoment = vtkSmartPointer::New(); liningNodalMoment->SetNumberOfComponents(3); liningNodalMoment->SetName("lining Moment"); //Linear velocity in Vector3 form vtkSmartPointer liningAxialForce = vtkSmartPointer::New(); liningAxialForce->SetNumberOfComponents(3); liningAxialForce->SetName("AxialForce"); //Linear velocity in Vector3 form vtkSmartPointer liningShearForce = vtkSmartPointer::New(); liningShearForce->SetNumberOfComponents(3); liningShearForce->SetName("Shear Force"); //Linear velocity in Vector3 form //#if 0 for (int i=0; i state.get(); State* state2 = Body::byId(blockIDs[nextID],scene)->state.get(); Vector3r globalPoint1 = state1->pos+state1->ori*localCoordinates[i]; Vector3r globalPoint2 = state2->pos+state2->ori*localCoordinates[nextID]; vtkSmartPointer lineSource = vtkSmartPointer::New(); double p0[3] = {globalPoint1[0], globalPoint1[1], globalPoint1[2]}; double p1[3] = {globalPoint2[0], globalPoint2[1], globalPoint2[2]}; lineSource->SetPoint1(p0); lineSource->SetPoint2(p1); appendFilter->AddInputConnection(lineSource-> GetOutputPort()); //#if 0 /* try to draw forces */ vtkIdType pid[1]; Vector3r midPoint = 0.5*(globalPoint1+globalPoint2); pid[0] = liningNode->InsertNextPoint( midPoint[0], midPoint[1], midPoint[2]); liningNodeCells->InsertNextCell(1,pid); Vector3r plotDirection = -midPoint; //local z-direction is pointing into the tunnel (positive), and clockwise moment is positive (outer lining is subject to compression) plotDirection.normalize(); Vector3r nodalMoment = moment[i]*plotDirection; float m[3]={nodalMoment[0],nodalMoment[1],nodalMoment[2]}; Vector3r axialForce = axialForces[i]*plotDirection; //axialForce tension is negative (plotdirection is pointing inwards), tension is pointing outwards float fa[3]={axialForce[0],axialForce[1],axialForce[2]}; Vector3r shearForce = shearForces[i]*plotDirection; float fs[3]={shearForce[0],shearForce[1],shearForce[2]}; //#endif if(blockIDs[i] == blockIDs[nextID] ){ m={0,0,0}; fa = {0,0,0}; fs = {0,0,0}; } if (Body::byId(blockIDs[i],scene)->isClumpMember()==true && Body::byId(blockIDs[nextID],scene)->isClumpMember()==true ){ if (Body::byId(blockIDs[i],scene)->clumpId == Body::byId(blockIDs[nextID],scene)->clumpId){ m={0,0,0}; fa = {0,0,0}; fs = {0,0,0}; } } liningNodalMoment->InsertNextTupleValue(m); liningAxialForce->InsertNextTupleValue(fa); liningShearForce->InsertNextTupleValue(fs); //lineSource->Update(); } //#endif //#if 0 vtkSmartPointer pbUgCP = vtkSmartPointer::New(); pbUgCP->SetPoints(liningNode); pbUgCP->SetCells(VTK_VERTEX, liningNodeCells); pbUgCP->GetPointData()->AddArray(liningNodalMoment); pbUgCP->GetPointData()->AddArray(liningAxialForce); pbUgCP->GetPointData()->AddArray(liningShearForce); vtkSmartPointer writerB = vtkSmartPointer::New(); writerB->SetDataModeToAscii(); string filelining=fileName+"liningNodeForce"+name+"."+std::to_string(scene->iter)+".vtu"; writerB->SetFileName(filelining.c_str()); writerB->SetInput(pbUgCP); writerB->Write(); vtkSmartPointer writer = vtkXMLPolyDataWriter::New(); writer->SetDataModeToAscii(); string fn=fileName+"-lining"+name+"."+std::to_string(scene->iter)+".vtp"; writer->SetFileName(fn.c_str()); writer->SetInputConnection(appendFilter->GetOutputPort()); writer->Write(); //#endif } #endif //#if 0 if ((scene->iter-vtkRefTimeStep)%vtkIteratorInterval == 0 && installed == true && blockIDs.size()>=2){ vtkRefTimeStep = scene->iter; vtkSmartPointer appendFilter = vtkSmartPointer::New(); int blockNo = blockIDs.size(); /// lining FORCE // vtkSmartPointer liningNode = vtkSmartPointer::New(); vtkSmartPointer liningNodeCells = vtkSmartPointer::New(); vtkSmartPointer liningNodalMoment = vtkSmartPointer::New(); liningNodalMoment->SetNumberOfComponents(3); liningNodalMoment->SetName("lining Moment"); //Linear velocity in Vector3 form vtkSmartPointer liningAxialForce = vtkSmartPointer::New(); liningAxialForce->SetNumberOfComponents(3); liningAxialForce->SetName("AxialForce"); //Linear velocity in Vector3 form vtkSmartPointer liningShearForce = vtkSmartPointer::New(); liningShearForce->SetNumberOfComponents(3); liningShearForce->SetName("Shear Force"); //Linear velocity in Vector3 form vtkSmartPointer liningNormalPressure = vtkSmartPointer::New(); liningNormalPressure->SetNumberOfComponents(3); liningNormalPressure->SetName("Normal Pressure"); //Linear velocity in Vector3 form vtkSmartPointer liningNormalPressureIdeal = vtkSmartPointer::New(); liningNormalPressureIdeal->SetNumberOfComponents(3); liningNormalPressureIdeal->SetName("Normal Pressure Magnitude"); vtkSmartPointer liningTotalPressure = vtkSmartPointer::New(); liningTotalPressure->SetNumberOfComponents(3); liningTotalPressure->SetName("Total Pressure"); //Linear velocity in Vector3 form //#if 0 for (int i=0; i state.get(); State* state2 = Body::byId(blockIDs[nextID],scene)->state.get(); PotentialBlock* pb=static_cast(Body::byId(blockIDs[i],scene)->shape.get()); Vector3r globalPoint1 = state1->pos; Vector3r globalPoint2 = state2->pos; vtkSmartPointer lineSource = vtkSmartPointer::New(); double p0[3] = {globalPoint1[0], globalPoint1[1], globalPoint1[2]}; double p1[3] = {globalPoint2[0], globalPoint2[1], globalPoint2[2]}; lineSource->SetPoint1(p0); lineSource->SetPoint2(p1); appendFilter->AddInputConnection(lineSource-> GetOutputPort()); //#if 0 /* try to draw forces */ vtkIdType pid[1]; Vector3r midPoint = globalPoint1; // 0.5*(globalPoint1+globalPoint2); pid[0] = liningNode->InsertNextPoint( midPoint[0], midPoint[1], midPoint[2]); liningNodeCells->InsertNextCell(1,pid); Vector3r plotDirection = midPoint; plotDirection.normalize(); Vector3r nodalMoment = moment[i]*plotDirection; float m[3]={nodalMoment[0],nodalMoment[1],nodalMoment[2]}; liningNodalMoment->InsertNextTupleValue(m); Vector3r axialForce = -axialForces[i]*plotDirection; float fa[3]={axialForce[0],axialForce[1],axialForce[2]}; liningAxialForce->InsertNextTupleValue(fa); Vector3r shearForce = shearForces[i]*plotDirection; float fs[3]={shearForce[0],shearForce[1],shearForce[2]}; liningShearForce->InsertNextTupleValue(fs); Vector3r normalP = pb->liningNormalPressure; Vector3r totalP = pb->liningTotalPressure; float pN[3] = {normalP[0], normalP[1],normalP[2]}; float pT[3] = {totalP[0], totalP[1], totalP[2]}; liningNormalPressure->InsertNextTupleValue(pN); liningTotalPressure->InsertNextTupleValue(pT); Vector3r normalPideal=-1.0*(normalP.norm())*plotDirection; float pNi[3] = {normalPideal[0], normalPideal[1],normalPideal[2]}; liningNormalPressureIdeal->InsertNextTupleValue(pNi); //#endif //lineSource->Update(); } //#endif //#if 0 vtkSmartPointer pbUgCP = vtkSmartPointer::New(); pbUgCP->SetPoints(liningNode); pbUgCP->SetCells(VTK_VERTEX, liningNodeCells); pbUgCP->GetPointData()->AddArray(liningNodalMoment); pbUgCP->GetPointData()->AddArray(liningAxialForce); pbUgCP->GetPointData()->AddArray(liningShearForce); pbUgCP->GetPointData()->AddArray(liningNormalPressure); pbUgCP->GetPointData()->AddArray(liningNormalPressureIdeal); pbUgCP->GetPointData()->AddArray(liningTotalPressure); vtkSmartPointer writerB = vtkSmartPointer::New(); writerB->SetDataModeToAscii(); string filelining=fileName+"liningNodeForce"+name+"."+std::to_string(scene->iter)+".vtu"; writerB->SetFileName(filelining.c_str()); writerB->SetInputData(pbUgCP); writerB->Write(); vtkSmartPointer writer = vtkXMLPolyDataWriter::New(); writer->SetDataModeToAscii(); string fn=fileName+"-lining"+name+"."+std::to_string(scene->iter)+".vtp"; writer->SetFileName(fn.c_str()); writer->SetInputConnection(appendFilter->GetOutputPort()); writer->Write(); //#endif } //#endif } int RockLiningGlobal::insertNode(Vector3r pos, double mass, double intervalLength){ shared_ptr& bodies = scene->bodies; //std::cout<<"pos: "< body(new Body()); /* new body */ shared_ptr pBlock(new PotentialBlock); shared_ptr aabb(new Aabb); shared_ptr mat(new FrictMat); pBlock->isLining = true; /* Shape object, variables not added: node, gridVol,vertices */ pBlock->AabbMinMax = false; pBlock->R = 1.0; pBlock->liningLength = intervalLength; pBlock->liningStiffness = interfaceStiffness; pBlock->liningFriction = interfaceFriction; pBlock->cohesion.push_back(interfaceCohesion); pBlock->tension.push_back(interfaceTension); pBlock->liningTensionGap = interfaceTension/interfaceStiffness; const int body_size= bodies->size(); int newID = body_size; pBlock->id = newID; aabb->color = Vector3r(0,1,0); body->state->ori = Quaternionr::Identity(); body->state->pos = pos; mat->frictionAngle = interfaceFriction/180.0*3.14159; body->setDynamic(true); body->state->mass = mass; body->state->inertia = 1.0/12.0*body->state->mass*(liningThickness*liningThickness + intervalLength*intervalLength)*Vector3r(1,1,1); //std::cout<<"mass: "<state->inertia<shape = pBlock; body->bound = aabb; body->material = mat; // std::cout<<"pos: "<insert(body); //std::cout<<"after insert"<ori*localPt1 + state1->pos; Vector3r global2 = state2->ori*localPt2 + state2->pos; return (global2-global1); } double RockLiningGlobal::evaluateFNoSphereVol(const PotentialBlock* s1,const State* state1, const Vector3r newTrial){ Vector3r tempP1 = newTrial - state1->pos; /* Direction cosines */ //state1.ori.normalize(); Vector3r localP1 = state1->ori.conjugate()*tempP1; Real x = localP1.x(); Real y = localP1.y(); Real z = localP1.z(); int planeNo = s1->a.size(); Real r = s1->r; int insideCount = 0; for (int i=0; ia[i]*x + s1->b[i]*y + s1->c[i]*z - s1->d[i]-1.0002*r; //-pow(10,-10); if (Mathr::Sign(plane)*1.0<0.0){ insideCount++; } } /* Complete potential particle */ Real f = 1.0; if (insideCount == planeNo){ f = -1.0;} return f; } bool RockLiningGlobal::installLining(const PotentialBlock* s1,const State* state1,const Vector3r startingPt,const Vector3r direction, const double length, Vector3r& intersectionPt){ Vector3r endPt = startingPt + length*direction; // PotentialBlock *s1=static_cast(cm1.get()); int planeNoA = s1->a.size(); /* line equality */ // x = x0 + t*dirX // y = y0 + t*dirY // z = z0 + t*dirZ /* linear inequality for blocks */ // Ax - d < 0 /* Variables to keep things neat */ int NUMCON = 3 /* equality */ + planeNoA /*block inequality */; int NUMVAR = 3/*3D */ + 1 /*t */+ 1 /* s */; double s = 0.0; bool converge = true; Matrix3r Q1 = (state1->ori.conjugate()).toRotationMatrix(); Eigen::MatrixXd A1 = Eigen::MatrixXd::Zero(planeNoA,3); for (int i=0; i < planeNoA; i++){ A1(i,0) = s1->a[i]; A1(i,1) = s1->b[i]; A1(i,2) = s1->c[i]; } Eigen::MatrixXd AQ1 = A1*Q1; Eigen::MatrixXd pos1(3,1); pos1(0,0) = state1->pos.x(); pos1(1,0) = state1->pos.y(); pos1(2,0) = state1->pos.z(); Eigen::MatrixXd Q1pos1 = AQ1*pos1; ClpSimplex model2; model2.setOptimizationDirection(1); // Create space for 3 columns and 10000 rows int numberRows = NUMCON; int numberColumns = NUMVAR; // This is fully dense - but would not normally be so // Arrays will be set to default values model2.resize(0, numberColumns); model2.setObjectiveCoefficient(0,0.0); model2.setObjectiveCoefficient(1,0.0); model2.setObjectiveCoefficient(2,0.0); model2.setObjectiveCoefficient(3,0.0); model2.setObjectiveCoefficient(4,1.0); for (int k = 0; k < 3; k++){ model2.setColumnLower(k,-COIN_DBL_MAX); model2.setColumnUpper(k,COIN_DBL_MAX); } model2.setColumnLower(3,openingRad); model2.setColumnUpper(3,length); model2.setColumnLower(4,-COIN_DBL_MAX); model2.setColumnUpper(4,COIN_DBL_MAX); // Rows double rowLower[numberRows]; double rowUpper[numberRows]; rowLower[0] = startingPt.x(); rowLower[1] = startingPt.y(); rowLower[2] = startingPt.z(); rowUpper[0] = startingPt.x(); rowUpper[1] = startingPt.y(); rowUpper[2] = startingPt.z(); for (int k = 0; k < planeNoA; k++){ rowLower[3+k]= -COIN_DBL_MAX; rowUpper[3+k] = s1->d[k] + s1->r + Q1pos1(k,0); } int row1Index[] = {0,3}; double row1Value[] = {1.0, -1.0*direction.x()}; model2.addRow(2,row1Index,row1Value,rowLower[0],rowUpper[0]); int row2Index[] = {1,3}; double row2Value[] = {1.0, -1.0*direction.y()}; model2.addRow(2,row2Index,row2Value,rowLower[1],rowUpper[1]); int row3Index[] = {2,3}; double row3Value[] = {1.0, -1.0*direction.z()}; model2.addRow(2,row3Index,row3Value,rowLower[2],rowUpper[2]); for (int i=0; iori.conjugate()*(temp-state1->pos); s = columnPrimal[4]; int convergeSuccess = model2.status(); if(s>-pow(10,-8) || convergeSuccess !=0){ return false; }else{ return true; } } bool RockLiningGlobal::intersectPlane(const PotentialBlock* s1,const State* state1,const Vector3r startingPt,const Vector3r direction, const double length, Vector3r& intersectionPt, const Vector3r plane, const double planeD){ bool feasible = true; Vector3r endPt = startingPt + length*direction; // PotentialBlock *s1=static_cast(cm1.get()); int planeNoA = s1->a.size(); /* Variables to keep things neat */ int NUMCON = 3 /* equality */ + 1 /*planeEquality */; int NUMVAR = 3/*3D */ + 1 /*t */; double t = 0.0; bool converge = true; /* line equality */ // x = x0 + t*dirX // y = y0 + t*dirY // z = z0 + t*dirZ /* linear equality for blocks */ // Ax - d = 0 /* LINEAR CONSTRAINTS */ ClpSimplex model2; model2.setOptimizationDirection(1); // Create space for 3 columns and 10000 rows int numberRows = NUMCON; int numberColumns = NUMVAR; // This is fully dense - but would not normally be so // Arrays will be set to default values model2.resize(0, numberColumns); model2.setObjectiveCoefficient(0,0.0); model2.setObjectiveCoefficient(1,0.0); model2.setObjectiveCoefficient(2,0.0); model2.setObjectiveCoefficient(3,1.0); for (int k = 0; k < 4; k++){ model2.setColumnLower(k,-COIN_DBL_MAX); model2.setColumnUpper(k,COIN_DBL_MAX); } // Rows double rowLower[numberRows]; double rowUpper[numberRows]; rowLower[0] = startingPt.x(); rowLower[1] = startingPt.y(); rowLower[2] = startingPt.z(); rowLower[3] = planeD; rowUpper[0] = startingPt.x(); rowUpper[1] = startingPt.y(); rowUpper[2] = startingPt.z(); rowUpper[3] = planeD; int row1Index[] = {0,3}; double row1Value[] = {1.0, -1.0*direction.x()}; model2.addRow(2,row1Index,row1Value,rowLower[0],rowUpper[0]); int row2Index[] = {1,3}; double row2Value[] = {1.0, -1.0*direction.y()}; model2.addRow(2,row2Index,row2Value,rowLower[1],rowUpper[1]); int row3Index[] = {2,3}; double row3Value[] = {1.0, -1.0*direction.z()}; model2.addRow(2,row3Index,row3Value,rowLower[2],rowUpper[2]); int row4Index[] = {0,1,2}; double row4Value[] = {plane.x(),plane.y(),plane.z()}; model2.addRow(3,row4Index,row4Value,rowLower[3],rowUpper[3]); model2.scaling(0); model2.setLogLevel(0); model2.primal(); double * columnPrimal = model2.primalColumnSolution(); Vector3r temp = Vector3r(columnPrimal[0],columnPrimal[1],columnPrimal[2]); intersectionPt = temp; //state1->ori.conjugate()*(temp-state1->pos); t = columnPrimal[3]; int convergeSuccess = model2.status(); double f = evaluateFNoSphereVol(s1,state1,intersectionPt); //std::cout<<"t: "<1.001*length || t< 0.0 || f> 0.0|| convergeSuccess !=0){ return false; }else{ return true; } } YADE_PLUGIN((RockLiningGlobal)); #endif // YADE_POTENTIAL_BLOCKS trunk-2018.02b/pkg/dem/RockLiningGlobal.hpp000077500000000000000000000153201324306050200204110ustar00rootroot00000000000000/*CWBoon 2016 */ /* Please cite: */ /* CW Boon, GT Houlsby, S Utili (2015). Designing Tunnel Support in Jointed Rock Masses Via the DEM. Rock Mechanics and Rock Engineering, 48 (2), 603-632. */ #ifdef YADE_POTENTIAL_BLOCKS #pragma once #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 "/home/boon/coin-Clp/Clp/src/ClpSimplex.hpp" //#include "/home/boon/coin-Clp/CoinUtils/src/CoinHelperFunctions.hpp" //#include "/home/boon/coin-Clp/CoinUtils/src/CoinTime.hpp" //#include "/home/boon/coin-Clp/CoinUtils/src/CoinBuild.hpp" //#include "/home/boon/coin-Clp/CoinUtils/src/CoinModel.hpp" class RockLiningGlobal: public PeriodicEngine{ protected: double stiffnessMatrix[36]; //double * globalStiffnessMatrix; double globalStiffnessMatrix[3*3*200*200]; public: #if 0 struct Bolts{ Bolts(Vector3r pt1, Vector3r pt2){startingPoint = pt1; endPoint=pt2; } Vector3r startingPoint; Vector3r endPoint; /* variables stored in sequence starting from the block closest to the opening */ vector blockIDs; /*blocks intersected */ vector localCoordinates; /*local coordinates inside blocks */ vector initialLength; }; vector bolt; #endif Vector3r getNodeDistance(const PotentialBlock* cm1,const State* state1,const PotentialBlock* cm2,const State* state2, const Vector3r localPt1, const Vector3r localPt2); bool installLining(const PotentialBlock* cm1,const State* state1,const Vector3r startingPt,const Vector3r direction, const double length, Vector3r& intersectionPt); int insertNode(Vector3r pos, double mass, double intervalLength); double evaluateFNoSphereVol(const PotentialBlock* s1,const State* state1, const Vector3r newTrial); bool intersectPlane(const PotentialBlock* s1,const State* state1,const Vector3r startingPt,const Vector3r direction, const double length, Vector3r& intersectionPt, const Vector3r plane, const double planeD); virtual void action(void); YADE_CLASS_BASE_DOC_ATTRS_CTOR_PY(RockLiningGlobal,PeriodicEngine,"Engine recording potential blocks as surfaces into files with given periodicity.", ((bool,assembledKglobal,false ,,"global stiffness matrix")) ((double,density,0.0 ,,"density")) ((double,lumpedMass,0.0 ,,"lumpedMass")) ((double,EA,0.0 ,,"EA")) ((double,EI,0.0 ,,"EI")) ((double,initOverlap,pow(10,-5),,"initialOverlap")) ((double,expansionFactor,pow(10,-5),,"alpha deltaT")) ((double,contactLength,1.0 ,,"contactLength")) ((vector, sigmaMax, ,,"sigma max")) ((vector, sigmaMin, ,,"sigma min")) ((double,ElasticModulus,0.0 ,,"E")) ((double,liningThickness,0.1 ,,"liningThickness")) ((double,Inertia,0.0 ,,"I")) ((vector,lengthNode, ,,"L")) ((vector,stickIDs, ,,"L")) ((double,Area,0.02 ,,"A")) ((double,interfaceStiffness,pow(10,8) ,,"L")) ((double,interfaceFriction,30.0 ,,"L")) ((double,interfaceCohesion,0.5*pow(10,6) ,,"L")) ((double,interfaceTension,0.8*pow(10,6) ,,"L")) ((int,totalNodes,0 ,,"L")) ((Vector3r,startingPoint,Vector3r(0,0,0) ,,"startingPt")) ((vector,blockIDs, ,,"ids")) ((vector,localCoordinates, ,,"local coordinates of intersection")) ((vector,refPos, ,,"initial u")) ((vector,refDir, ,,"initial v")) ((vector,refOri, ,,"initial theta")) ((vector,refAngle, ,,"initial theta")) ((vector,moment, ,,"moment")) ((vector,axialForces, ,,"force")) ((vector,shearForces, ,,"force")) ((vector,displacement, ,,"force")) ((vector,radialDisplacement, ,,"force")) ((double,openingRad,5.0 ,,"estimated opening radius")) ((bool,installed,false ,,"installed?")) ((bool,openingCreated,false ,,"opening created?")) ((vector,ruptured, ,,"ruptured")) ((Real,axialMax,1000000000 ,,"maximum axial force")) ((Real,shearMax,1000000000 ,,"maximum shear force")) ((int,vtkIteratorInterval,10000 ,,"how often to print vtk")) ((int,vtkRefTimeStep,1 ,,"first timestep to print vtk")) ((string,fileName,,,"File prefix to save to")) ((string,name,,,"File prefix to save to")) , //globalStiffnessMatrix = new double[totalNodes*3*totalNodes*3]; , ); }; REGISTER_SERIALIZABLE(RockLiningGlobal); #ifdef __cplusplus extern "C" { #endif /* LAPACK LU */ //int dgesv(int varNo, int varNo2, double *H, int varNo3, int *pivot, double* g, int varNo4, int info){ extern void dgesv_(const int *N, const int *nrhs, double *Hessian, const int *lda, int *ipiv, double *gradient, const int *ldb, int *info); // int ans; // dgesv_(&varNo, &varNo2, H, &varNo3, pivot,g, &varNo4, &ans); // return ans; //} /* LAPACK Cholesky */ extern void dpbsv_(const char *uplo, const int *n, const int *kd, const int *nrhs, double *AB, const int *ldab, double *B, const int *ldb, int *info); /* LAPACK QR */ extern void dgels_(const char *Trans, const int *m, const int *n, const int *nrhs, double *A, const int *lda, double *B, const int *ldb, const double *work, const int *lwork, int *info); /*BLAS */ extern void dgemm_(const char *transA, const char *transB, const int *m, const int *n, const int *k, const double *alpha, double *A, const int *lda, double *B, const int *ldb, const double *beta, double *C, const int *ldc); extern void dgemv_(const char *trans, const int *m, const int *n, const double *alpha, double *A, const int *lda, double *x, const int *incx, const double *beta, double *y, const int *incy); extern void dcopy_(const int *N, double *x, const int *incx, double *y, const int *incy); extern double ddot_(const int *N, double *x, const int *incx, double *y, const int *incy); extern void daxpy_(const int *N, const double *da, double *dx, const int *incx, double *dy, const int *incy); extern void dscal_(const int *N, const double *alpha, double *x, const int *incx); void dsyev_(const char *jobz, const char *uplo, const int *N, double *A, const int *lda, double *W, double *work, int *lwork, int *info); #ifdef __cplusplus }; #endif #endif // YADE_POTENTIAL_BLOCKS trunk-2018.02b/pkg/dem/RungeKuttaCashKarp54Integrator.cpp000066400000000000000000000024221324306050200231360ustar00rootroot00000000000000#ifdef YADE_ODEINT #include #include YADE_PLUGIN((RungeKuttaCashKarp54Integrator)); shared_ptr RungeKuttaCashKarp54Integrator_ctor_list(const boost::python::list& slaves){ shared_ptr instance(new RungeKuttaCashKarp54Integrator); instance->slaves_set(slaves); return instance; } void RungeKuttaCashKarp54Integrator::action() { Real dt=scene->dt; Real time=scene->time; error_checker_type rungekuttaerrorcontroller=error_checker_type(abs_err,rel_err,a_x,a_dxdt); controlled_stepper_type rungekuttastepper=controlled_stepper_type(rungekuttaerrorcontroller); stateVector currentstates=getCurrentStates(); resetstate.reserve(currentstates.size()); copy(currentstates.begin(),currentstates.end(),back_inserter(resetstate));//copy current state to resetstate this->timeresetvalue=time; //set reset time to the time just before the integration /*Try an adaptive integration*/ integrationsteps+=integrate_adaptive(rungekuttastepper,make_ode_wrapper( *((Integrator*)this) , &Integrator::system ),currentstates,time,time+dt, stepsize, observer(this)); scene->time=scene->time-dt;//Scene move next time step function already increments the time so we have to decrement it just before it. } #endif trunk-2018.02b/pkg/dem/RungeKuttaCashKarp54Integrator.hpp000066400000000000000000000034711324306050200231500ustar00rootroot00000000000000#ifdef YADE_ODEINT #pragma once #include #include #include typedef boost::numeric::odeint::runge_kutta_cash_karp54< stateVector > error_stepper_type; //Runge-Kutta 54 error stepper other steppers can also be used typedef boost::numeric::odeint::controlled_runge_kutta< error_stepper_type > controlled_stepper_type;//Controlled Runge Kutta stepper typedef boost::numeric::odeint::default_error_checker< error_stepper_type::value_type,error_stepper_type::algebra_type ,error_stepper_type::operations_type > error_checker_type; //Error checker type that is redefined for initialization using different tolerance values shared_ptr RungeKuttaCashKarp54Integrator_ctor_list(const boost::python::list& slaves); class RungeKuttaCashKarp54Integrator: public Integrator { public: error_checker_type rungekuttaerrorcontroller; controlled_stepper_type rungekuttastepper; void init() { rungekuttaerrorcontroller=error_checker_type(abs_err,rel_err,a_x,a_dxdt); rungekuttastepper=controlled_stepper_type(rungekuttaerrorcontroller); } virtual void action(); YADE_CLASS_BASE_DOC_ATTRS_CTOR_PY(RungeKuttaCashKarp54Integrator,Integrator,"RungeKuttaCashKarp54Integrator engine.", ((Real,abs_err,1e-6,,"Relative integration tolerance")) ((Real,rel_err,1e-6,,"Absolute integration tolerance")) ((Real,a_x,1.0,,"")) ((Real,a_dxdt,1.0,,"")) ((Real,stepsize,1e-6,,"It is not important for an adaptive integration but important for the observer for setting the found states after integration")) , /*ctor*/ init(); , .def("__init__",boost::python::make_constructor(RungeKuttaCashKarp54Integrator_ctor_list),"Construct from (possibly nested) list of slaves.") /*py*/ ); }; REGISTER_SERIALIZABLE(RungeKuttaCashKarp54Integrator); #endif trunk-2018.02b/pkg/dem/STLImporter.cpp000066400000000000000000000032561324306050200174120ustar00rootroot00000000000000/************************************************************************* * Copyright (C) 2008 by Sergei Dorofeenko * * sega@users.berlios.de * * * * This program is free software; it is licensed under the terms of the * * GNU General Public License v2 or later. See file LICENSE for details. * *************************************************************************/ #include"STLImporter.hpp" #include CREATE_LOGGER(STLImporter); vector > STLImporter::import(const char* filename) { vector tr; vector > imported; // Load geometry vector vtmp, ntmp; vector etmp, ftmp; STLReader reader; reader.tolerance=Mathr::ZERO_TOLERANCE; if(!reader.open(filename, back_inserter(vtmp), back_inserter(etmp), back_inserter(ftmp), back_inserter(ntmp))) { LOG_ERROR("Can't open file: " << filename); return imported; // zero size } for(int i=0,e=ftmp.size(); i iFacet(new Facet); iFacet->color = Vector3r(0.8,0.3,0.3); for (int j=0; j<3; ++j) { iFacet->vertices[j]=v[j]-icc; } iFacet->postLoad(*iFacet); shared_ptr b(new Body); b->state->pos=b->state->refPos=icc; b->state->ori=b->state->refOri=Quaternionr::Identity(); b->shape = iFacet; imported.push_back(b); } return imported; } trunk-2018.02b/pkg/dem/STLImporter.hpp000066400000000000000000000156541324306050200174240ustar00rootroot00000000000000/************************************************************************* * Copyright (C) 2008 by Sergei Dorofeenko * * sega@users.berlios.de * * * * This program is free software; it is licensed under the terms of the * * GNU General Public License v2 or later. See file LICENSE for details. * *************************************************************************/ #pragma once #include #include #include class STLImporter { public: vector > import(const char*); DECLARE_LOGGER; }; template std::pair minmax(const T &a, const T &b) { return (a(a,b) : std::pair(b,a); } class STLReader { public: // if it is binary there are 80 char of comment, the number fn of faces and then exactly fn*4*3 bytes. enum {STL_LABEL_SIZE=80}; template bool open(const char* filename, OutV vertices, OutE edges, OutF facets, OutN normals); float tolerance; protected: template bool open_binary(const char* filename, OutV vertices, OutE edges, OutF facets, OutN normals); template bool open_ascii(const char* filename, OutV vertices, OutE edges, OutF facets, OutN normals); struct Vrtx { float pos[3]; Vrtx(){} Vrtx(float x, float y, float z) {pos[0]=x; pos[1]=y; pos[2]=z;} bool operator< (const Vrtx& v) const { return memcmp(pos, v.pos,3*sizeof(float)) < 0; } float operator[](int id) const { return pos[id]; } float& operator[](int id) { return pos[id]; } }; class IsDifferent { float tolerance; public: IsDifferent(float _tolerance): tolerance(_tolerance){} bool operator() (const Vrtx& v1, const Vrtx& v2) { if ( std::abs(v1[0]-v2[0]) bool STLReader::open(const char* filename, OutV vertices, OutE edges, OutF facets, OutN normals) { FILE *fp; bool binary=false; fp = fopen(filename, "r"); if(fp == NULL) return false; /* Find size of file */ fseek(fp, 0, SEEK_END); int file_size = ftell(fp); int facenum; /* Check for binary or ASCII file */ fseek(fp, STL_LABEL_SIZE, SEEK_SET); size_t res=fread(&facenum, sizeof(int), 1, fp); int expected_file_size=STL_LABEL_SIZE + 4 + (sizeof(short)+4*sizeof(float) )*facenum ; if(file_size == expected_file_size) binary = true; unsigned char tmpbuf[128]; res=fread(tmpbuf,sizeof(tmpbuf),1,fp); if (res) { for(size_t i = 0; i < sizeof(tmpbuf); i++) { if(tmpbuf[i] > 127) { binary=true; break; } } } // Now we know if the stl file is ascii or binary. fclose(fp); if(binary) return open_binary(filename,vertices,edges,facets,normals); else return open_ascii(filename,vertices,edges,facets,normals); } template bool STLReader::open_ascii(const char* filename, OutV vertices, OutE edges, OutF facets, OutN normals) { FILE *fp; fp = fopen(filename, "r"); if(fp == NULL) return false; /* Skip the first line of the file */ while(getc(fp) != '\n'); vector vcs; set > egs; /* Read a single facet from an ASCII .STL file */ int r=0; //just to escape "warning: ignoring return value of 'int fscanf(FILE*, const char*, ...)'," while(!feof(fp)) { float n[3]; Vrtx v[3]; r=fscanf(fp, "%*s %*s %f %f %f\n", &n[0], &n[1], &n[2]); r+=fscanf(fp, "%*s %*s"); r+=fscanf(fp, "%*s %f %f %f\n", &v[0][0], &v[0][1], &v[0][2]); r+=fscanf(fp, "%*s %f %f %f\n", &v[1][0], &v[1][1], &v[1][2]); r+=fscanf(fp, "%*s %f %f %f\n", &v[2][0], &v[2][1], &v[2][2]); r+=fscanf(fp, "%*s"); // end loop r+=fscanf(fp, "%*s"); // end facet if(feof(fp)) break; int vid[3]; for(int i=0;i<3;++i) { (normals++) = n[i]; bool is_different=true; IsDifferent isd(tolerance); int j=0; for(int ej=vcs.size(); j::iterator it=vcs.begin(),end=vcs.end(); it!=end; ++it) { (vertices++) = (*it)[0]; (vertices++) = (*it)[1]; (vertices++) = (*it)[2]; } for(set >::iterator it=egs.begin(),end=egs.end(); it!=end; ++it) { (edges++) = it->first; (edges++) = it->second; } return (r>0); } template bool STLReader::open_binary(const char* filename, OutV vertices, OutE edges, OutF facets, OutN normals) { FILE *fp; fp = fopen(filename, "rb"); if (fp == NULL) { return false; } int facenum; fseek(fp, STL_LABEL_SIZE, SEEK_SET); int res=fread(&facenum, sizeof(int), 1, fp); vector vcs; set > egs; if (res) { // For each triangle read the normal, the three coords and a short set to zero for(int i=0;i::iterator it=vcs.begin(),end=vcs.end(); it!=end; ++it) { (vertices++) = (*it)[0]; (vertices++) = (*it)[1]; (vertices++) = (*it)[2]; } for(set >::iterator it=egs.begin(),end=egs.end(); it!=end; ++it) { (edges++) = it->first; (edges++) = it->second; } return true; } trunk-2018.02b/pkg/dem/SampleCapillaryPressureEngine.cpp000066400000000000000000000050621324306050200231640ustar00rootroot00000000000000#ifdef DEPREC_CODE /************************************************************************* * Copyright (C) 2006 by Luc Scholtes * * luc.scholtes@hmg.inpg.fr * * * * This program is free software; it is licensed under the terms of the * * GNU General Public License v2 or later. See file LICENSE for details. * *************************************************************************/ #include "SampleCapillaryPressureEngine.hpp" #include #include #include #include #include YADE_PLUGIN((SampleCapillaryPressureEngine)); CREATE_LOGGER(SampleCapillaryPressureEngine); SampleCapillaryPressureEngine::~SampleCapillaryPressureEngine() { } void SampleCapillaryPressureEngine::updateParameters() { UnbalancedForce = ComputeUnbalancedForce(scene); if (!Phase1 && UnbalancedForce<=StabilityCriterion && !pressureVariationActivated) { internalCompaction = false; Phase1 = true; } if ( Phase1 && UnbalancedForce<=StabilityCriterion && !pressureVariationActivated) { Real S = meanStress; cerr << "Smoy = " << meanStress << endl; if ((S > (sigma_iso - (sigma_iso*SigmaPrecision))) && (S < (sigma_iso + (sigma_iso*SigmaPrecision)))) { string fileName = "../data/" + Phase1End + "_" + boost::lexical_cast(scene->iter) + ".xml"; cerr << "saving snapshot: " << fileName << " ..."; Omega::instance().saveSimulation(fileName); pressureVariationActivated = true; } } } void SampleCapillaryPressureEngine::action() { updateParameters(); TriaxialStressController::action(); if (pressureVariationActivated) { if (scene->iter % 100 == 0) cerr << "pressure variation!!" << endl; if ((Pressure>=0) && (Pressure<=1000000000)) Pressure += PressureVariation; capillaryCohesiveLaw->capillaryPressure = Pressure; capillaryCohesiveLaw->fusionDetection = fusionDetection; capillaryCohesiveLaw->binaryFusion = binaryFusion; } else { capillaryCohesiveLaw->capillaryPressure = Pressure; capillaryCohesiveLaw->fusionDetection = fusionDetection; capillaryCohesiveLaw->binaryFusion = binaryFusion;} if (scene->iter % 100 == 0) cerr << "capillary pressure = " << Pressure << endl; capillaryCohesiveLaw->scene=scene;; capillaryCohesiveLaw->action(); UnbalancedForce = ComputeUnbalancedForce(scene); } #endif //DEPREC CODE trunk-2018.02b/pkg/dem/SampleCapillaryPressureEngine.hpp000066400000000000000000000052631324306050200231740ustar00rootroot00000000000000#ifdef DEPREC_CODE /************************************************************************* * Copyright (C) 2006 by Luc Scholtes * * luc.scholtes@hmg.inpg.fr * * * * This program is free software; it is licensed under the terms of the * * GNU General Public License v2 or later. See file LICENSE for details. * *************************************************************************/ #ifndef SAMPLE_CAPILLARY_PRESSURE_ENGINE_HPP #define SAMPLE_CAPILLARY_PRESSURE_ENGINE_HPP #include #include #include #include /*! \brief Isotropic compression + suction variation test */ class Law2_ScGeom_CapillaryPhys_Capillarity; class SampleCapillaryPressureEngine : public TriaxialStressController { public : //! is isotropicInternalCompactionFinished? bool Phase1; std::string Phase1End; //! is this the beginning of the simulation, after reading the scene? bool firstRun; shared_ptr capillaryCohesiveLaw; //Law2_ScGeom_CapillaryPhys_Capillarity* capillaryCohesiveLaw; // which one is right? virtual ~SampleCapillaryPressureEngine(); void updateParameters(); virtual void action(); YADE_CLASS_BASE_DOC_ATTRS_CTOR_PY(SampleCapillaryPressureEngine,TriaxialStressController,"It produces the isotropic compaction of an assembly and allows one to controlled the capillary pressure inside (uses Law2_ScGeom_CapillaryPhys_Capillarity).", ((Real,Pressure,0,,"Value of the capillary pressure Uc=Ugas-Uliquid (see Law2_ScGeom_CapillaryPhys_Capillarity). [Pa]")) ((bool,pressureVariationActivated,1,,"Is the capillary pressure varying?")) ((bool,fusionDetection,1,,"Is the detection of menisci overlapping activated?")) ((bool,binaryFusion,1,,"If yes, capillary force are set to 0 when, at least, 1 overlap is detected for a meniscus. If no, capillary force is divided by the number of overlaps.")) ((Real,PressureVariation,0,,"Variation of the capillary pressure (each iteration). [Pa]")) ((Real,UnbalancedForce,1,,"mean resultant forces divided by mean contact force")) ((Real,StabilityCriterion,0.01,,"tolerance in terms of :yref:`TriaxialCompressionEngine::UnbalancedForce` to consider the packing as stable")) ((Real,SigmaPrecision,0.001,,"tolerance in terms of mean stress to consider the packing as stable")), Phase1=false; Phase1End = "Compacted"; firstRun=true; ,) DECLARE_LOGGER; }; REGISTER_SERIALIZABLE(SampleCapillaryPressureEngine); #endif // SAMPLECAPILLARYPRESSUREENGINE_HPP #endif //DEPREC CODE trunk-2018.02b/pkg/dem/ScGeom.cpp000066400000000000000000000176721324306050200164120ustar00rootroot00000000000000// © 2004 Olivier Galizzi // © 2004 Janek Kozicki // © 2008 Václav Šmilauer // © 2006 Bruno Chareyre #include #include #include YADE_PLUGIN((ScGeom)(ScGeom6D)(ChCylGeom6D)); ScGeom::~ScGeom(){}; ScGeom6D::~ScGeom6D(){}; ChCylGeom6D::~ChCylGeom6D(){}; Vector3r& ScGeom::rotate(Vector3r& shearForce) const { // approximated rotations shearForce -= shearForce.cross(orthonormal_axis); shearForce -= shearForce.cross(twist_axis); //NOTE : make sure it is in the tangent plane? It's never been done before. Is it not adding rounding errors at the same time in fact?... //shearForce -= normal.dot(shearForce)*normal; return shearForce; } Vector3r& ScGeom::rotateNonSpherical(Vector3r& shearForce) const { //FIXME // approximated rotations shearForce -= shearForce.cross(orthonormal_axis); //shearForce -= shearForce.cross(twist_axis); //NOTE : make sure it is in the tangent plane? It's never been done before. Is it not adding rounding errors at the same time in fact?... //shearForce -= normal.dot(shearForce)*normal; if(std::isnan(shearForce.norm())){ std::cout<<"orthonormal_axis: "<& c, const Vector3r& currentNormal, bool isNew, const Vector3r& shift2, bool avoidGranularRatcheting){ if(!isNew) { orthonormal_axis = normal.cross(currentNormal); Real angle = scene->dt*0.5*normal.dot(rbp1.angVel + rbp2.angVel); twist_axis = angle*normal;} else twist_axis=orthonormal_axis=Vector3r::Zero(); //Update contact normal normal=currentNormal; //Precompute shear increment Vector3r relativeVelocity=getIncidentVel(&rbp1,&rbp2,scene->dt,shift2,scene->isPeriodic ? scene->cell->intrShiftVel(c->cellDist) : Vector3r::Zero(),avoidGranularRatcheting); //keep the shear part only relativeVelocity = relativeVelocity-normal.dot(relativeVelocity)*normal; shearInc = relativeVelocity*scene->dt; } Vector3r ScGeom::getIncidentVel(const State* rbp1, const State* rbp2, Real dt, const Vector3r& shift2, const Vector3r& shiftVel, bool avoidGranularRatcheting){ if(avoidGranularRatcheting){ /* B.C. Comment : Short explanation of what we want to avoid : Numerical ratcheting is best understood considering a small elastic cycle at a contact between two grains : assuming b1 is fixed, impose this displacement to b2 : 1. translation "dx" in the normal direction 2. rotation "a" 3. translation "-dx" (back to initial position) 4. rotation "-a" (back to initial orientation) If the branch vector used to define the relative shear in rotation×branch is not constant (typically if it is defined from the vector center→contactPoint), then the shear displacement at the end of this cycle is not zero: rotations *a* and *-a* are multiplied by branches of different lengths. It results in a finite contact force at the end of the cycle even though the positions and orientations are unchanged, in total contradiction with the elastic nature of the problem. It could also be seen as an *inconsistent energy creation or loss*. Given that DEM simulations tend to generate oscillations around equilibrium (damped mass-spring), it can have a significant impact on the evolution of the packings, resulting for instance in slow creep in iterations under constant load. The solution adopted here to avoid ratcheting is as proposed by McNamara and co-workers. They analyzed the ratcheting problem in detail - even though they comment on the basis of a cycle that differs from the one shown above. One will find interesting discussions in e.g. DOI 10.1103/PhysRevE.77.031304, even though solution it suggests is not fully applied here (equations of motion are not incorporating alpha, in contradiction with what is suggested by McNamara et al.). */ // For sphere-facet contact this will give an erroneous value of relative velocity... Real alpha = (radius1+radius2)/(radius1+radius2-penetrationDepth); Vector3r relativeVelocity = (rbp2->vel-rbp1->vel)*alpha + rbp2->angVel.cross(-radius2*normal) - rbp1->angVel.cross(radius1*normal); relativeVelocity+=alpha*shiftVel; return relativeVelocity; } else { // It is correct for sphere-sphere and sphere-facet contact Vector3r c1x = (contactPoint - rbp1->pos); Vector3r c2x = (contactPoint - (rbp2->pos + shift2)); Vector3r relativeVelocity = (rbp2->vel+rbp2->angVel.cross(c2x)) - (rbp1->vel+rbp1->angVel.cross(c1x)); relativeVelocity+=shiftVel; return relativeVelocity;} } Vector3r ScGeom::getIncidentVel(const State* rbp1, const State* rbp2, Real dt, bool avoidGranularRatcheting){ //Just pass null shift to the periodic version return getIncidentVel(rbp1,rbp2,dt,Vector3r::Zero(),Vector3r::Zero(),avoidGranularRatcheting); } Vector3r ScGeom::getIncidentVel_py(shared_ptr i, bool avoidGranularRatcheting){ if(i->geom.get()!=this) throw invalid_argument("ScGeom object is not the same as Interaction.geom."); Scene* scene=Omega::instance().getScene().get(); return getIncidentVel(Body::byId(i->getId1(),scene)->state.get(),Body::byId(i->getId2(),scene)->state.get(), scene->dt, scene->isPeriodic ? scene->cell->intrShiftPos(i->cellDist): Vector3r::Zero(), // shift2 scene->isPeriodic ? scene->cell->intrShiftVel(i->cellDist): Vector3r::Zero(), // shiftVel avoidGranularRatcheting); } Vector3r ScGeom::getRelAngVel(const State* rbp1, const State* rbp2, Real dt){ Vector3r relAngVel = (rbp2->angVel-rbp1->angVel); return relAngVel; } Vector3r ScGeom::getRelAngVel_py(shared_ptr i){ if(i->geom.get()!=this) throw invalid_argument("ScGeom object is not the same as Interaction.geom."); Scene* scene=Omega::instance().getScene().get(); return getRelAngVel(Body::byId(i->getId1(),scene)->state.get(),Body::byId(i->getId2(),scene)->state.get(),scene->dt); } //!Precompute relative rotations (and precompute ScGeom3D) void ScGeom6D::precomputeRotations(const State& rbp1, const State& rbp2, bool isNew, bool creep){ if (isNew) { initRotations(rbp1,rbp2); } else { Quaternionr delta((rbp1.ori * (initialOrientation1.conjugate())) * (initialOrientation2 * (rbp2.ori.conjugate()))); delta.normalize(); if (creep) delta = delta * twistCreep; AngleAxisr aa(delta); // axis of rotation - this is the Moment direction UNIT vector; // angle represents the power of resistant ELASTIC moment //Eigen::AngleAxisr(q) returns nan's when q close to identity, next tline fixes the pb. // add -DYADE_SCGEOM_DEBUG to CXXFLAGS to enable this piece or just do // #define YADE_SCGEOM_DEBUG //(but do not commit with that enabled in the code) #ifdef YADE_SCGEOM_DEBUG if (std::isnan(aa.angle())) { cerr<<"NaN angle found in angleAxisr(q), for quaternion "< Mathr::PI) aa.angle() -= Mathr::TWO_PI; // angle is between 0 and 2*pi, but should be between -pi and pi twist = (aa.angle() * aa.axis().dot(normal)); bending = Vector3r(aa.angle()*aa.axis() - twist*normal); } } void ScGeom6D::initRotations(const State& state1, const State& state2) { initialOrientation1 = state1.ori; initialOrientation2 = state2.ori; twist=0; bending=Vector3r::Zero(); twistCreep=Quaternionr(1.0,0.0,0.0,0.0); } trunk-2018.02b/pkg/dem/ScGeom.hpp000066400000000000000000000160541324306050200164100ustar00rootroot00000000000000// © 2004 Olivier Galizzi // © 2008 Václav Šmilauer // © 2006 Bruno Chareyre #pragma once #include #include #include #include #include /*! Class representing geometry of two bodies in contact. * * The code under SCG_SHEAR is experimental and is used only if ElasticContactLaw::useShear is explicitly true */ #define SCG_SHEAR class ScGeom: public GenericSpheresContact { private: //cached values Vector3r twist_axis;//rotation vector around normal Vector3r orthonormal_axis;//rotation vector in contact plane public: // inherited from GenericSpheresContact: Vector3r& normal; Real &radius1, &radius2; virtual ~ScGeom(); inline ScGeom& operator= (const ScGeom& source){ normal=source.normal; contactPoint=source.contactPoint; twist_axis=source.twist_axis; orthonormal_axis=source.orthonormal_axis; radius1=source.radius1; radius2=source.radius2; penetrationDepth=source.penetrationDepth; shearInc=source.shearInc; return *this;} //!precompute values of shear increment and interaction rotation data. Update contact normal to the currentNormal value. Precondition : the value of normal is not updated outside (and before) this function. void precompute(const State& rbp1, const State& rbp2, const Scene* scene, const shared_ptr& c, const Vector3r& currentNormal, bool isNew, const Vector3r& shift2, bool avoidGranularRatcheting=true); //! Rotates a "shear" vector to keep track of contact orientation. Returns reference of the updated vector. Vector3r& rotate(Vector3r& tangentVector) const; Vector3r& rotateNonSpherical(Vector3r& tangentVector) const; //FIXME const Vector3r& shearIncrement() const {return shearInc;} // Add method which returns the relative velocity (then, inside the contact law, this can be split into shear and normal component). Handle periodicity. Vector3r getIncidentVel(const State* rbp1, const State* rbp2, Real dt, const Vector3r& shiftVel, const Vector3r& shift2, bool avoidGranularRatcheting=true); // Implement another version of getIncidentVel which does not handle periodicity. Vector3r getIncidentVel(const State* rbp1, const State* rbp2, Real dt, bool avoidGranularRatcheting=true); // Add function to get the relative angular velocity (useful to determine bending moment at the contact level) Vector3r getRelAngVel(const State* rbp1, const State* rbp2, Real dt); // convenience version to be called from python Vector3r getIncidentVel_py(shared_ptr i, bool avoidGranularRatcheting); Vector3r getRelAngVel_py(shared_ptr i); YADE_CLASS_BASE_DOC_ATTRS_INIT_CTOR_PY(ScGeom,GenericSpheresContact,"Class representing :yref:`geometry` of a contact point between two :yref:`bodies`. It is more general than sphere-sphere contact even though it is primarily focused on spheres interactions (reason for the 'Sc' namming); it is also used for representing contacts of a :yref:`Sphere` with non-spherical bodies (:yref:`Facet`, :yref:`Plane`, :yref:`Box`, :yref:`ChainedCylinder`), or between two non-spherical bodies (:yref:`ChainedCylinder`). The contact has 3 DOFs (normal and 2×shear) and uses incremental algorithm for updating shear.\n\nWe use symbols $\\vec{x}$, $\\vec{v}$, $\\vec{\\omega}$ respectively for position, linear and angular velocities (all in global coordinates) and $r$ for particles radii; subscripted with 1 or 2 to distinguish 2 spheres in contact. Then we define branch length and unit contact normal\n\n.. math::\n\n\tl=||\\vec{x}_2-\\vec{x}_1||, \\vec{n}=\\frac{\\vec{x}_2-\\vec{x}_1}{||\\vec{x}_2-\\vec{x}_1||}\n\nThe relative velocity of the spheres is then\n\n.. math::\n\n\t\\vec{v}_{12}=\\frac{r_1+r_2}{l}(\\vec{v}_2-\\vec{v}_1) -(r_2 \\vec{\\omega}_2 + r_1\\vec{\\omega}_1)\\times\\vec{n}\n\nwhere the fraction multiplying translational velocities is to make the definition objective and avoid ratcheting effects (see :yref:`Ig2_Sphere_Sphere_ScGeom.avoidGranularRatcheting`). The shear component is\n\n.. math::\n\n\t\\vec{v}_{12}^s=\\vec{v}_{12}-(\\vec{n}\\cdot\\vec{v}_{12})\\vec{n}.\n\nTangential displacement increment over last step then reads\n\n.. math::\n\n\t\\Delta\\vec{x}_{12}^s=\\Delta t \\vec{v}_{12}^s.", ((Real,penetrationDepth,NaN,(Attr::noSave|Attr::readonly),"Penetration distance of spheres (positive if overlapping)")) ((Vector3r,shearInc,Vector3r::Zero(),(Attr::noSave|Attr::readonly),"Shear displacement increment in the last step")) , /* extra initializers */ ((radius1,GenericSpheresContact::refR1)) ((radius2,GenericSpheresContact::refR2)), /* ctor */ createIndex(); twist_axis=orthonormal_axis=Vector3r::Zero();, /* py */ .def("incidentVel",&ScGeom::getIncidentVel_py,(boost::python::arg("i"),boost::python::arg("avoidGranularRatcheting")=true),"Return incident velocity of the interaction (see also :yref:`Ig2_Sphere_Sphere_ScGeom.avoidGranularRatcheting` for explanation of the ratcheting argument).") .def("relAngVel",&ScGeom::getRelAngVel_py,(boost::python::arg("i")),"Return relative angular velocity of the interaction.") ); REGISTER_CLASS_INDEX(ScGeom,GenericSpheresContact); }; REGISTER_SERIALIZABLE(ScGeom); class ScGeom6D: public ScGeom { public: virtual ~ScGeom6D(); const Real& getTwist() const {return twist;} const Vector3r& getBending() const {return bending;} void precomputeRotations(const State& rbp1, const State& rbp2, bool isNew, bool creep=false); void initRotations(const State& rbp1, const State& rbp2); YADE_CLASS_BASE_DOC_ATTRS_INIT_CTOR_PY(ScGeom6D,ScGeom,"Class representing :yref:`geometry` of two :yref:`bodies` in contact. The contact has 6 DOFs (normal, 2×shear, twist, 2xbending) and uses :yref:`ScGeom` incremental algorithm for updating shear.", ((Quaternionr,initialOrientation1,Quaternionr(1.0,0.0,0.0,0.0),(Attr::readonly),"Orientation of body 1 one at initialisation time |yupdate|")) ((Quaternionr,initialOrientation2,Quaternionr(1.0,0.0,0.0,0.0),(Attr::readonly),"Orientation of body 2 one at initialisation time |yupdate|")) ((Quaternionr,twistCreep,Quaternionr(1.0,0.0,0.0,0.0),(Attr::readonly),"Stored creep, substracted from total relative rotation for computation of elastic moment |yupdate|")) ((Real,twist,0,(Attr::readonly),"Elastic twist angle (around :yref:`normal axis`) of the contact.")) ((Vector3r,bending,Vector3r::Zero(),(Attr::readonly),"Bending at contact as a vector defining axis of rotation and angle (angle=norm).")) , /* extra initializers */, /* ctor */ createIndex();, /* py */ ); REGISTER_CLASS_INDEX(ScGeom6D,ScGeom); }; REGISTER_SERIALIZABLE(ScGeom6D); class ChCylGeom6D: public ScGeom6D { public: virtual ~ChCylGeom6D(); State fictiousState1; State fictiousState2; Real relPos1, relPos2; YADE_CLASS_BASE_DOC_ATTRS_INIT_CTOR_PY(ChCylGeom6D,ScGeom6D,"Test", /*attributes*/ , /* extra initializers */, /* ctor */ createIndex();, /* py */ ); REGISTER_CLASS_INDEX(ChCylGeom6D,ScGeom6D); }; REGISTER_SERIALIZABLE(ChCylGeom6D); trunk-2018.02b/pkg/dem/Shop.hpp000066400000000000000000000201761324306050200161440ustar00rootroot00000000000000// 2007 © Václav Šmilauer #pragma once #include #include "lib/base/Math.hpp" #include "lib/base/Logging.hpp" #include "core/Body.hpp" #include class Scene; class Body; class SimpleViscoelasticBodyParameters; class ViscElMat; class FrictMat; class Interaction; using boost::shared_ptr; namespace py = boost::python; /*! Miscillaneous utility functions which are believed to be generally useful. * * All data members are methods are static, no instance of Shop is created. It is not serializable either. */ class Shop{ public: DECLARE_LOGGER; //! create default sphere, along with its bound etc. static shared_ptr sphere(Vector3r center, Real radius, shared_ptr mat); //! create default box with everything needed static shared_ptr box(Vector3r center, Vector3r extents, shared_ptr mat); //! create default tetrahedron static shared_ptr tetra(Vector3r v[4], shared_ptr mat); //! return instance of default FrictMat static shared_ptr defaultGranularMat(); //! Return vector of pairs (center,radius) loaded from a file with numbers inside static vector > loadSpheresFromFile(const string& fname,Vector3r& minXYZ, Vector3r& maxXYZ, Vector3r* cellSize=NULL); //! Save spheres in the current simulation into a text file static void saveSpheresToFile(string fileName); //! Compute the total volume of spheres static Real getSpheresVolume(const shared_ptr& rb=shared_ptr(), int mask=-1); //! Compute the total mass of spheres static Real getSpheresMass(const shared_ptr& rb=shared_ptr(), int mask=-1); //! Compute porosity; volume must be given for aperiodic simulations static Real getPorosity(const shared_ptr& rb=shared_ptr(),Real volume=-1); //! Compute porosity by dividing given volume into a grid of voxels; static Real getVoxelPorosity(const shared_ptr& rb=shared_ptr(),int resolution=500,Vector3r start=Vector3r(0,0,0),Vector3r end=Vector3r(0,0,0)); //! Estimate timestep based on P-wave propagation speed static Real PWaveTimeStep(const shared_ptr rb=shared_ptr()); //! Estimate timestep based on Rayleigh-wave propagation speed static Real RayleighWaveTimeStep(const shared_ptr rb=shared_ptr()); //! return 2d coordinates of a 3d point within plane defined by rotation axis and inclination of spiral, wrapped to the 0th period static boost::tuple spiralProject(const Vector3r& pt, Real dH_dTheta, int axis=2, Real periodStart=std::numeric_limits::quiet_NaN(), Real theta0=0); //! Calculate inscribed circle center of trianlge static Vector3r inscribedCircleCenter(const Vector3r& v0, const Vector3r& v1, const Vector3r& v2); /// Get viscoelastic parameters kn,cn,ks,cs from analytical solution of /// a problem of interaction of pair spheres with mass 1, collision /// time tc and restitution coefficients en,es. static void getViscoelasticFromSpheresInteraction(Real tc, Real en, Real es, shared_ptr b); //! Get unbalanced force of the whole simulation static Real unbalancedForce(bool useMaxForce=false, Scene* _rb=NULL); static Real kineticEnergy(Scene* _rb=NULL, Body::id_t* maxId=NULL); //! get total momentum of current simulation static Vector3r momentum(); //! get total angular momentum of current simulation static Vector3r angularMomentum(Vector3r origin = Vector3r::Zero()); static Vector3r totalForceInVolume(Real& avgIsoStiffness, Scene *_rb=NULL); //! create transientInteraction between 2 bodies, using existing Dispatcher in Omega static shared_ptr createExplicitInteraction(Body::id_t id1, Body::id_t id2, bool force); //! apply force on contact point on both bodies (reversed on body 2) static void applyForceAtContactPoint(const Vector3r& force, const Vector3r& contPt, Body::id_t id1, const Vector3r& pos1, Body::id_t id2, const Vector3r& pos2, Scene* scene); //! map scalar variable to 1d colorscale static Vector3r scalarOnColorScale(Real x, Real xmin=0., Real xmax=1.); //! wrap floating number periodically to the given range static Real periodicWrap(Real x, Real x0, Real x1, long* period=NULL); //! Flip cell shear without affecting interactions; if flip is zeros, it will be computed such that abs of shear strain is minimal for each shear component //! Diagonal terms of flip are meaningless and ignored. static Matrix3r flipCell(const Matrix3r& flip=Matrix3r::Zero()); //! Class for storing stresses, affected on bodies, obtained from Interactions struct bodyState{ Vector3r normStress, shearStress; bodyState (){ normStress = Vector3r(0.0,0.0,0.0); shearStress = Vector3r(0.0,0.0,0.0); } }; //! Function of getting stresses for each body static void getStressForEachBody(vector&); //! Define the exact average stress in each particle from contour integral ("LW" stands for Love-Weber, since this is what the contour integral gives). static void getStressLWForEachBody(vector& bStresses); static py::list getStressLWForEachBody(); //! Function to compute overall ("macroscopic") stress. static Matrix3r getStress(Real volume=0); static Matrix3r getCapillaryStress(Real volume=0, bool mindlin=false); static Matrix3r stressTensorOfPeriodicCell() { LOG_WARN("Shop::stressTensorOfPeriodicCelli is DEPRECATED: use getStress instead"); return Shop::getStress(); } //! Compute the stress tensor in each defined cell, return a stress tensor depth profile static py::tuple getStressProfile(Real volume, int nCell, Real dz, Real zRef, vector vPartAverageX, vector vPartAverageY, vector vPartAverageZ); static py::tuple getStressProfile_contact(Real volume, int nCell, Real dz, Real zRef); //same, only contact contribution //! Compute average depth profile of particle velocity (x,y,z) and solid volume fraction. The direction may be specified by direction argument (default is z). static py::tuple getDepthProfiles(Real vCell, int nCell, Real dz, Real zRef,bool activateCond=false, Real radiusPy=0,int direction=2); //! Same, but taking into account point particles static py::tuple getDepthProfiles_center(Real vCell, int nCell, Real dz, Real zRef,bool activateCond=false, Real radiusPy=0); //! Compute overall ("macroscopic") stress of periodic cell, returning 2 tensors //! (contribution of normal and shear forces) static py::tuple normalShearStressTensors(bool compressionPositive=false, bool splitNormalTensor=false, Real thresholdForce=NaN); //! Function to compute fabric tensor static void fabricTensor(Real& Fmean, Matrix3r& fabric, Matrix3r& fabricStrong, Matrix3r& fabricWeak, Real cutoff=0.0, bool splitTensor=false, Real thresholdForce=NaN); static py::tuple fabricTensor(Real cutoff=0.0, bool splitTensor=false, Real thresholdForce=NaN); //! Function to set translational and rotational velocities of all bodies to zero static void calm(const shared_ptr& rb=shared_ptr(), int mask=-1); //! Get a list of body-ids, which contacts the given body; static py::list getBodyIdsContacts(Body::id_t bodyID=-1); //! Set material and contact friction to the given value, non-dynamic bodies are not affected static void setContactFriction(Real angleRad); //! Homothetic change of sizes of spheres and clumps static void growParticles(Real multiplier, bool updateMass, bool dynamicOnly); //! Change of size of a single sphere or a clump // DEPREC, update wrt growParticles() static void growParticle(Body::id_t bodyID, Real multiplier, bool updateMass); /* \todo implement groupMask */ static py::tuple aabbExtrema(Real cutoff=0.0, bool centers=false); //! evaluation of 2D quantities static Real getSpheresVolume2D(const shared_ptr& rb=shared_ptr(), int mask=-1); static Real getVoidRatio2D(const shared_ptr& rb=shared_ptr(),Real zlen=1); //! get stress tensor and tangent operator tensor for FEMxDEM coupling. By Ning Guo static py::tuple getStressAndTangent(Real volume=0, bool symmetry=true); }; trunk-2018.02b/pkg/dem/Shop_01.cpp000066400000000000000000000415221324306050200164350ustar00rootroot00000000000000// 2007 © Václav Šmilauer #include"Shop.hpp" #include #include"core/Scene.hpp" #include"core/Body.hpp" #include"core/Interaction.hpp" #include"pkg/common/Aabb.hpp" #include"core/Clump.hpp" #include"pkg/common/InsertionSortCollider.hpp" #include"pkg/common/Box.hpp" #include"pkg/common/Sphere.hpp" #include"pkg/common/ElastMat.hpp" #include"pkg/dem/ViscoelasticPM.hpp" #include"pkg/dem/CapillaryPhys.hpp" #include"pkg/common/Bo1_Aabb.hpp" #include"pkg/dem/NewtonIntegrator.hpp" #include"pkg/dem/Ig2_Sphere_Sphere_ScGeom.hpp" #include"pkg/dem/Ig2_Box_Sphere_ScGeom.hpp" #include"pkg/dem/FrictPhys.hpp" #include"pkg/common/ForceResetter.hpp" #include"pkg/common/Dispatching.hpp" #include"pkg/common/InteractionLoop.hpp" #include"pkg/common/GravityEngines.hpp" #include"pkg/dem/GlobalStiffnessTimeStepper.hpp" #include"pkg/dem/ElasticContactLaw.hpp" #include"pkg/dem/ScGeom.hpp" #include"pkg/dem/FrictPhys.hpp" #include"pkg/common/Grid.hpp" #include"pkg/dem/Tetra.hpp" #ifdef YADE_OPENGL #include"pkg/common/Gl1_NormPhys.hpp" #endif #include #include "py/_utils.hpp" CREATE_LOGGER(Shop); /*! Flip periodic cell for shearing indefinitely.*/ Matrix3r Shop::flipCell(const Matrix3r& _flip){ Scene* scene=Omega::instance().getScene().get(); const shared_ptr& cell(scene->cell); Matrix3r& hSize = cell->hSize; Matrix3i flip; if(_flip==Matrix3r::Zero()){ bool hasNonzero=false; for(int i=0; i<3; i++) for(int j=0; j<3; j++) { if(i==j){ flip(i,j)=0; continue; } flip(i,j)=-floor(hSize.col(j).dot(hSize.col(i))/hSize.col(i).dot(hSize.col(i))); if(flip(i,j)!=0) hasNonzero=true; } if(!hasNonzero) {LOG_TRACE("No flip necessary."); return Matrix3r::Zero();} } else { flip=_flip.cast(); } Matrix3r flipFloat = flip.cast(); cell->hSize+=cell->hSize*flipFloat; cell->postLoad(*cell); // adjust Interaction::cellDist for interactions; Matrix3r invFlip = (Matrix3r::Identity() + flipFloat).inverse(); // FIXME: is Matrix3i.cast().cast() really preserving the integer numbers?? problem: there is no inverse() for Matrix3i, even though in this case the inverse _seems_ to be always an integer matrix FOREACH(const shared_ptr& i, *scene->interactions) i->cellDist = invFlip.cast()*i->cellDist; // force reinitialization of the collider bool colliderFound=false; FOREACH(const shared_ptr& e, scene->engines){ Collider* c=dynamic_cast(e.get()); if(c){ colliderFound=true; c->invalidatePersistentData(); } } if(!colliderFound) LOG_WARN("No collider found while flipping cell; continuing simulation might give garbage results."); return flipFloat; } /* Apply force on contact point to 2 bodies; the force is oriented as it applies on the first body and is reversed on the second. */ void Shop::applyForceAtContactPoint(const Vector3r& force, const Vector3r& contPt, Body::id_t id1, const Vector3r& pos1, Body::id_t id2, const Vector3r& pos2, Scene* scene){ scene->forces.addForce(id1,force); scene->forces.addForce(id2,-force); scene->forces.addTorque(id1,(contPt-pos1).cross(force)); scene->forces.addTorque(id2,-(contPt-pos2).cross(force)); } /*! Compute sum of forces in the whole simulation and averages stiffness. Designed for being used with periodic cell, where diving the resulting components by areas of the cell will give average stress in that direction. Requires all .isReal() interaction to have phys deriving from NormShearPhys. */ Vector3r Shop::totalForceInVolume(Real& avgIsoStiffness, Scene* _rb){ Scene* rb=_rb ? _rb : Omega::instance().getScene().get(); Vector3r force(Vector3r::Zero()); Real stiff=0; long n=0; FOREACH(const shared_ptr&I, *rb->interactions){ if(!I->isReal()) continue; NormShearPhys* nsi=YADE_CAST(I->phys.get()); force+=Vector3r(std::abs(nsi->normalForce[0]+nsi->shearForce[0]),std::abs(nsi->normalForce[1]+nsi->shearForce[1]),std::abs(nsi->normalForce[2]+nsi->shearForce[2])); stiff+=(1/3.)*nsi->kn+(2/3.)*nsi->ks; // count kn in one direction and ks in the other two n++; } avgIsoStiffness= n>0 ? (1./n)*stiff : -1; return force; } Real Shop::unbalancedForce(bool useMaxForce, Scene* _rb){ Scene* rb=_rb ? _rb : Omega::instance().getScene().get(); rb->forces.sync(); shared_ptr newton; Vector3r gravity = Vector3r::Zero(); FOREACH(shared_ptr& e, rb->engines){ newton=YADE_PTR_DYN_CAST(e); if(newton) {gravity=newton->gravity; break;} } // get maximum force on a body and sum of all forces (for averaging) Real sumF=0,maxF=0,currF; int nb=0; FOREACH(const shared_ptr& b, *rb->bodies){ if(!b || b->isClumpMember() || !b->isDynamic()) continue; currF=(rb->forces.getForce(b->id)+b->state->mass*gravity).norm(); if(b->isClump() && currF==0){ // this should not happen unless the function is called by an engine whose position in the loop is before Newton (with the exception of bodies which really have null force), because clumps forces are updated in Newton. Typical triaxial loops are using such ordering unfortunately (triaxEngine before Newton). So, here we make sure that they will get correct unbalance. In the future, it is better for optimality to check unbalancedF inside scripts at the end of loops, so that this "if" is never active. Vector3r f(rb->forces.getForce(b->id)),m(Vector3r::Zero()); b->shape->cast().addForceTorqueFromMembers(b->state.get(),rb,f,m); currF=(f+b->state->mass*gravity).norm(); } maxF=max(currF,maxF); sumF+=currF; nb++; } Real meanF=sumF/nb; // get mean force on interactions sumF=0; nb=0; FOREACH(const shared_ptr& I, *rb->interactions){ if(!I->isReal()) continue; shared_ptr nsi=YADE_PTR_CAST(I->phys); assert(nsi); sumF+=(nsi->normalForce+nsi->shearForce).norm(); nb++; } sumF/=nb; return (useMaxForce?maxF:meanF)/(sumF); } Real Shop::kineticEnergy(Scene* _scene, Body::id_t* maxId){ Scene* scene=_scene ? _scene : Omega::instance().getScene().get(); Real ret=0.; Real maxE=0; if(maxId) *maxId=Body::ID_NONE; Vector3r spin = scene->cell->getSpin(); FOREACH(const shared_ptr& b, *scene->bodies){ if(!b || !b->isDynamic() || b->isClumpMember()) continue; const State* state(b->state.get()); // ½(mv²+ωIω) Real E=0; if(scene->isPeriodic){ /* Only take in account the fluctuation velocity, not the mean velocity of homothetic resize. */ E=.5*state->mass*scene->cell->bodyFluctuationVel(state->pos-state->vel*scene->dt,state->vel,scene->cell->velGrad).squaredNorm(); } else { E=.5*(state->mass*state->vel.squaredNorm()); } Vector3r angVel = state->angVel; if (scene->isPeriodic) angVel=angVel-spin; if(b->isAspherical()){ Matrix3r T(state->ori); // the tensorial expression http://en.wikipedia.org/wiki/Moment_of_inertia#Moment_of_inertia_tensor // inertia tensor rotation from http://www.kwon3d.com/theory/moi/triten.html Matrix3r mI; mI<inertia[0],0,0, 0,state->inertia[1],0, 0,0,state->inertia[2]; //E+=.5*state->angVel.transpose().dot((T.transpose()*mI*T)*state->angVel); E+=.5*angVel.transpose().dot((T*mI*T.transpose())*angVel); } else { E+=0.5*angVel.dot(state->inertia.cwiseProduct(angVel)); } if(maxId && E>maxE) { *maxId=b->getId(); maxE=E; } ret+=E; } return ret; } Vector3r Shop::momentum() { Vector3r ret = Vector3r::Zero(); Scene* scene=Omega::instance().getScene().get(); FOREACH(const shared_ptr b, *scene->bodies){ ret += b->state->mass * b->state->vel; } return ret; } Vector3r Shop::angularMomentum(Vector3r origin) { Vector3r ret = Vector3r::Zero(); Scene* scene=Omega::instance().getScene().get(); Matrix3r T,Iloc; FOREACH(const shared_ptr b, *scene->bodies){ ret += (b->state->pos-origin).cross(b->state->mass * b->state->vel); ret += b->state->angMom; } return ret; } shared_ptr Shop::defaultGranularMat(){ shared_ptr mat(new FrictMat); mat->density=2e3; mat->young=30e9; mat->poisson=.3; mat->frictionAngle=.5236; //30˚ return mat; } /*! Create body - sphere. */ shared_ptr Shop::sphere(Vector3r center, Real radius, shared_ptr mat){ shared_ptr body(new Body); body->material=mat ? mat : boost::static_pointer_cast(defaultGranularMat()); body->state->pos=center; body->state->mass=4.0/3.0*Mathr::PI*radius*radius*radius*body->material->density; body->state->inertia=Vector3r(2.0/5.0*body->state->mass*radius*radius,2.0/5.0*body->state->mass*radius*radius,2.0/5.0*body->state->mass*radius*radius); body->bound=shared_ptr(new Aabb); body->shape=shared_ptr(new Sphere(radius)); return body; } /*! Create body - box. */ shared_ptr Shop::box(Vector3r center, Vector3r extents, shared_ptr mat){ shared_ptr body(new Body); body->material=mat ? mat : boost::static_pointer_cast(defaultGranularMat()); body->state->pos=center; Real mass=8.0*extents[0]*extents[1]*extents[2]*body->material->density; body->state->mass=mass; body->state->inertia=Vector3r(mass*(4*extents[1]*extents[1]+4*extents[2]*extents[2])/12.,mass*(4*extents[0]*extents[0]+4*extents[2]*extents[2])/12.,mass*(4*extents[0]*extents[0]+4*extents[1]*extents[1])/12.); body->bound=shared_ptr(new Aabb); body->shape=shared_ptr(new Box(extents)); return body; } /*! Create body - tetrahedron. */ shared_ptr Shop::tetra(Vector3r v_global[4], shared_ptr mat){ shared_ptr body(new Body); body->material=mat ? mat : boost::static_pointer_cast(defaultGranularMat()); Vector3r centroid=(v_global[0]+v_global[1]+v_global[2]+v_global[3])*.25; Vector3r v[4]; for(int i=0; i<4; i++) v[i]=v_global[i]-centroid; body->state->pos=centroid; body->state->mass=body->material->density*TetrahedronVolume(v); // inertia will be calculated below, by TetrahedronWithLocalAxesPrincipal body->bound=shared_ptr(new Aabb); body->shape=shared_ptr(new Tetra(v[0],v[1],v[2],v[3])); // make local axes coincident with principal axes TetrahedronWithLocalAxesPrincipal(body); return body; } void Shop::saveSpheresToFile(string fname){ const shared_ptr& scene=Omega::instance().getScene(); ofstream f(fname.c_str()); if(!f.good()) throw runtime_error("Unable to open file `"+fname+"'"); FOREACH(shared_ptr b, *scene->bodies){ if (!b->isDynamic()) continue; shared_ptr intSph=YADE_PTR_DYN_CAST(b->shape); if(!intSph) continue; const Vector3r& pos=b->state->pos; f<radius<& _scene, int mask){ const shared_ptr scene=(_scene?_scene:Omega::instance().getScene()); Real vol=0; FOREACH(shared_ptr b, *scene->bodies){ if (!b) continue; Sphere* s=dynamic_cast(b->shape.get()); if((!s) or ((mask>0) and ((b->groupMask & mask)==0))) continue; vol += (4/3.)*Mathr::PI*pow(s->radius,3); } return vol; } Real Shop::getSpheresMass(const shared_ptr& _scene, int mask){ const shared_ptr scene=(_scene?_scene:Omega::instance().getScene()); Real mass=0; FOREACH(shared_ptr b, *scene->bodies){ if (!b) continue; Sphere* s=dynamic_cast(b->shape.get()); if((!s) or ((mask>0) and ((b->groupMask & mask)==0))) continue; mass += b->state->mass; } return mass; } Real Shop::getPorosity(const shared_ptr& _scene, Real _volume){ const shared_ptr scene=(_scene?_scene:Omega::instance().getScene()); Real V; if(!scene->isPeriodic){ if(_volume<=0){// throw std::invalid_argument("utils.porosity must be given (positive) *volume* for aperiodic simulations."); py::tuple extrema = aabbExtrema(); V = py::extract( (extrema[1][0] - extrema[0][0])*(extrema[1][1] - extrema[0][1])*(extrema[1][2] - extrema[0][2]) ); } else V=_volume; } else { V=scene->cell->getVolume(); } Real Vs=Shop::getSpheresVolume(); return (V-Vs)/V; } Real Shop::getVoxelPorosity(const shared_ptr& _scene, int _resolution, Vector3r _start,Vector3r _end){ const shared_ptr scene=(_scene?_scene:Omega::instance().getScene()); if(_start==_end) throw std::invalid_argument("utils.voxelPorosity: cannot calculate porosity when start==end of the volume box."); if(_resolution<50) throw std::invalid_argument("utils.voxelPorosity: it doesn't make sense to calculate porosity with voxel resolution below 50."); // prepare the gird, it eats a lot of memory. // I am not optimizing for using bits. A single byte for each cell is used. std::vector > > grid; int S(_resolution); grid.resize(S); for(int i=0 ; i bi, *scene->bodies){ if((bi)->isClump()) continue; const shared_ptr& b = bi; if ( b->isDynamic() || b->isClumpMember() ) { const shared_ptr& sphere = YADE_PTR_CAST ( b->shape ); Real r = sphere->radius; Real rr = r*r; Vector3r pos = b->state->se3.position; // we got sphere with radius r, at position pos. // and a box of size S, scaled to 'size' // mark cells that are iniside a sphere int ii(0),II(S),jj(0),JJ(S),kk(0),KK(S); // make sure to loop only in AABB of that sphere. No need to waste cycles outside of it. ii = std::max((int)((Real)(pos[0]-start[0]-r)*(Real)(S)/(Real)(size[0]))-1,0); II = std::min(ii+(int)((Real)(2.0*r)*(Real)(S)/(Real)(size[0]))+3,S); jj = std::max((int)((Real)(pos[1]-start[1]-r)*(Real)(S)/(Real)(size[1]))-1,0); JJ = std::min(jj+(int)((Real)(2.0*r)*(Real)(S)/(Real)(size[1]))+3,S); kk = std::max((int)((Real)(pos[2]-start[2]-r)*(Real)(S)/(Real)(size[2]))-1,0); KK = std::min(kk+(int)((Real)(2.0*r)*(Real)(S)/(Real)(size[2]))+3,S); for(int i=ii ; i > Shop::loadSpheresFromFile(const string& fname, Vector3r& minXYZ, Vector3r& maxXYZ, Vector3r* cellSize){ if(!boost::filesystem::exists(fname)) { throw std::invalid_argument(string("File with spheres `")+fname+"' doesn't exist."); } vector > spheres; ifstream sphereFile(fname.c_str()); if(!sphereFile.good()) throw std::runtime_error("File with spheres `"+fname+"' couldn't be opened."); Vector3r C; Real r=0; int clumpId=-1; string line; size_t lineNo=0; while(std::getline(sphereFile, line, '\n')){ lineNo++; boost::tokenizer > toks(line,boost::char_separator(" \t")); vector tokens; FOREACH(const string& s, toks) tokens.push_back(s); if(tokens.empty()) continue; if(tokens[0]=="##PERIODIC::"){ if(tokens.size()!=4) throw std::invalid_argument(("Spheres file "+fname+":"+boost::lexical_cast(lineNo)+" contains ##PERIODIC::, but the line is malformed.").c_str()); if(cellSize){ *cellSize=Vector3r(boost::lexical_cast(tokens[1]),boost::lexical_cast(tokens[2]),boost::lexical_cast(tokens[3])); } continue; } if(tokens.size()!=5 and tokens.size()!=4) throw std::invalid_argument(("Line "+boost::lexical_cast(lineNo)+" in the spheres file "+fname+" has "+boost::lexical_cast(tokens.size())+" columns (must be 4 or 5).").c_str()); C=Vector3r(boost::lexical_cast(tokens[0]),boost::lexical_cast(tokens[1]),boost::lexical_cast(tokens[2])); r=boost::lexical_cast(tokens[3]); for(int j=0; j<3; j++) { minXYZ[j]=(spheres.size()>0?min(C[j]-r,minXYZ[j]):C[j]-r); maxXYZ[j]=(spheres.size()>0?max(C[j]+r,maxXYZ[j]):C[j]+r);} if(tokens.size()==5)clumpId=boost::lexical_cast(tokens[4]); spheres.push_back(boost::tuple(C,r,clumpId)); } return spheres; } Real Shop::PWaveTimeStep(const shared_ptr _rb){ shared_ptr rb=(_rb?_rb:Omega::instance().getScene()); Real dt=std::numeric_limits::infinity(); FOREACH(const shared_ptr& b, *rb->bodies){ if(!b || !b->material || !b->shape) continue; shared_ptr ebp=YADE_PTR_DYN_CAST(b->material); shared_ptr s=YADE_PTR_DYN_CAST(b->shape); if(!ebp || !s) continue; Real density=b->state->mass/((4/3.)*Mathr::PI*pow(s->radius,3)); dt=min(dt,s->radius/sqrt(ebp->young/density)); } if (dt==std::numeric_limits::infinity()) { dt = 1.0; LOG_WARN("PWaveTimeStep has not found any suitable spherical body to calculate dt. dt is set to 1.0"); } return dt; } trunk-2018.02b/pkg/dem/Shop_02.cpp000066400000000000000000001216201324306050200164340ustar00rootroot00000000000000// 2007 © Václav Šmilauer #include"Shop.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 #ifdef YADE_OPENGL #include #endif CREATE_LOGGER(Shop); /*! Flip periodic cell by given number of cells. Still broken, some interactions are missed. Should be checked. */ /* Detremination of time step as according to Rayleigh wave speed of force propagation (see Thornton 2000, ref. MillerPursey_1955) */ Real Shop::RayleighWaveTimeStep(const shared_ptr _rb){ shared_ptr rb=(_rb?_rb:Omega::instance().getScene()); Real dt=std::numeric_limits::infinity(); FOREACH(const shared_ptr& b, *rb->bodies){ if(!b || !b->material || !b->shape) continue; shared_ptr ebp=YADE_PTR_DYN_CAST(b->material); shared_ptr s=YADE_PTR_DYN_CAST(b->shape); if(!ebp || !s) continue; Real density=b->state->mass/((4/3.)*Mathr::PI*pow(s->radius,3)); Real ShearModulus=ebp->young/(2.*(1+ebp->poisson)); Real lambda=0.1631*ebp->poisson+0.876605; dt=min(dt,Mathr::PI*s->radius/lambda*sqrt(density/ShearModulus)); } return dt; } /* Project 3d point into 2d using spiral projection along given axis; * the returned tuple is * * (height relative to the spiral, distance from axis, theta ) * * dH_dTheta is the inclination of the spiral (height increase per radian), * theta0 is the angle for zero height (by given axis). */ boost::tuple Shop::spiralProject(const Vector3r& pt, Real dH_dTheta, int axis, Real periodStart, Real theta0){ int ax1=(axis+1)%3,ax2=(axis+2)%3; Real r=sqrt(pow(pt[ax1],2)+pow(pt[ax2],2)); Real theta; if(r>Mathr::ZERO_TOLERANCE){ theta=acos(pt[ax1]/r); if(pt[ax2]<0) theta=Mathr::TWO_PI-theta; } else theta=0; Real hRef=dH_dTheta*(theta-theta0); long period; if(std::isnan(periodStart)){ Real h=Shop::periodicWrap(pt[axis]-hRef,hRef-Mathr::PI*dH_dTheta,hRef+Mathr::PI*dH_dTheta,&period); return boost::make_tuple(r,h,theta); } else{ // Real hPeriodStart=(periodStart-theta0)*dH_dTheta; //Real h=Shop::periodicWrap(pt[axis]-hRef,hPeriodStart,hPeriodStart+2*Mathr::PI*dH_dTheta,&period); theta=Shop::periodicWrap(theta,periodStart,periodStart+2*Mathr::PI,&period); Real h=pt[axis]-hRef+period*2*Mathr::PI*dH_dTheta; return boost::make_tuple(r,h,theta); } } shared_ptr Shop::createExplicitInteraction(Body::id_t id1, Body::id_t id2, bool force){ IGeomDispatcher* geomMeta=NULL; IPhysDispatcher* physMeta=NULL; shared_ptr rb=Omega::instance().getScene(); if(rb->interactions->find(Body::id_t(id1),Body::id_t(id2))!=0) throw runtime_error(string("Interaction #")+boost::lexical_cast(id1)+"+#"+boost::lexical_cast(id2)+" already exists."); FOREACH(const shared_ptr& e, rb->engines){ if(!geomMeta) { geomMeta=dynamic_cast(e.get()); if(geomMeta) continue; } if(!physMeta) { physMeta=dynamic_cast(e.get()); if(physMeta) continue; } InteractionLoop* id(dynamic_cast(e.get())); if(id){ geomMeta=id->geomDispatcher.get(); physMeta=id->physDispatcher.get(); } if(geomMeta&&physMeta){break;} } if(!geomMeta) throw runtime_error("No IGeomDispatcher in engines or inside InteractionLoop."); if(!physMeta) throw runtime_error("No IPhysDispatcher in engines or inside InteractionLoop."); shared_ptr b1=Body::byId(id1,rb), b2=Body::byId(id2,rb); if(!b1) throw runtime_error(("No body #"+boost::lexical_cast(id1)).c_str()); if(!b2) throw runtime_error(("No body #"+boost::lexical_cast(id2)).c_str()); shared_ptr i=geomMeta->explicitAction(b1,b2,/*force*/force); assert(force && i); if(!i) return i; physMeta->explicitAction(b1->material,b2->material,i); i->iterMadeReal=rb->iter; rb->interactions->insert(i); return i; } Vector3r Shop::inscribedCircleCenter(const Vector3r& v0, const Vector3r& v1, const Vector3r& v2) { return v0+((v2-v0)*(v1-v0).norm()+(v1-v0)*(v2-v0).norm())/((v1-v0).norm()+(v2-v1).norm()+(v0-v2).norm()); } void Shop::getViscoelasticFromSpheresInteraction( Real tc, Real en, Real es, shared_ptr b) { throw runtime_error("Setting parameters in ViscoElastic model is changed. You do not need to use getViscoelasticFromSpheresInteraction function any more, because this functino is deprecated. You need to set the parameters tc, en and es directly in material properties. Please, update your scripts. How to do it you can see in the following commit https://github.com/yade/trunk/commit/1987c2febdb8a6ce2d27f2dc1bb29df0dc5f686e"); } /* This function is copied almost verbatim from scientific python, module Visualization, class ColorScale * */ Vector3r Shop::scalarOnColorScale(Real x, Real xmin, Real xmax){ Real xnorm=min((Real)1.,max((x-xmin)/(xmax-xmin),(Real)0.)); if(xnorm<.25) return Vector3r(0,4.*xnorm,1); if(xnorm<.5) return Vector3r(0,1,1.-4.*(xnorm-.25)); if(xnorm<.75) return Vector3r(4*(xnorm-.5),1.,0); return Vector3r(1,1-4*(xnorm-.75),0); } /* Wrap floating point number into interval (x0,x1〉such that it is shifted * by integral number of the interval range. If given, *period will hold * this number. The wrapped value is returned. */ Real Shop::periodicWrap(Real x, Real x0, Real x1, long* period){ Real xNorm=(x-x0)/(x1-x0); Real xxNorm=xNorm-floor(xNorm); if(period) *period=(long)floor(xNorm); return x0+xxNorm*(x1-x0); } void Shop::getStressForEachBody(vector& bodyStates){ const shared_ptr& scene=Omega::instance().getScene(); bodyStates.resize(scene->bodies->size()); FOREACH(const shared_ptr& I, *scene->interactions){ Vector3r normalStress,shearStress; if(!I->isReal()) continue; const FrictPhys* physFP = YADE_CAST(I->phys.get()); ScGeom* geomScG=YADE_CAST(I->geom.get()); const Body::id_t id1=I->getId1(), id2=I->getId2(); if((physFP) and (geomScG)){ Real minRad=(geomScG->radius1<=0?geomScG->radius2:(geomScG->radius2<=0?geomScG->radius1:min(geomScG->radius1,geomScG->radius2))); Real crossSection=Mathr::PI*pow(minRad,2); normalStress=((1./crossSection)*geomScG->normal.dot(physFP->normalForce))*geomScG->normal; for(int i=0; i<3; i++){ int ix1=(i+1)%3,ix2=(i+2)%3; shearStress[i]=geomScG->normal[ix1]*physFP->shearForce[ix1]+geomScG->normal[ix2]*physFP->shearForce[ix2]; shearStress[i]/=crossSection; } bodyStates[id1].normStress+=normalStress; bodyStates[id2].normStress+=normalStress; bodyStates[id1].shearStress+=shearStress; bodyStates[id2].shearStress+=shearStress; } } } /* Return the stress tensor decomposed in 2 contributions, from normal and shear forces. The formulation follows the [Thornton2000]_ article "Numerical simulations of deviatoric shear deformation of granular media", eq (3) and (4) */ py::tuple Shop::normalShearStressTensors(bool compressionPositive, bool splitNormalTensor, Real thresholdForce){ //*** Stress tensor split into shear and normal contribution ***/ Scene* scene=Omega::instance().getScene().get(); if (!scene->isPeriodic){ throw runtime_error("Can't compute stress of periodic cell in aperiodic simulation."); } Matrix3r sigN(Matrix3r::Zero()),sigT(Matrix3r::Zero()); //const Matrix3r& cellHsize(scene->cell->Hsize); //Disabled because of warning. FOREACH(const shared_ptr& I, *scene->interactions){ if(!I->isReal()) continue; GenericSpheresContact* geom=YADE_CAST(I->geom.get()); NormShearPhys* phys=YADE_CAST(I->phys.get()); const Vector3r& n=geom->normal; // if compression has positive sign, we need to change sign for both normal and shear force // make copy to Fs since shearForce is used at multiple places (less efficient, but avoids confusion) Vector3r Fs=(compressionPositive?-1:1)*phys->shearForce; Real N=(compressionPositive?-1:1)*phys->normalForce.dot(n), T=Fs.norm(); bool hasShear=(T>0); Vector3r t; if(hasShear) t=Fs/T; // Real R=(Body::byId(I->getId2(),scene)->state->pos+cellHsize*I->cellDist.cast()-Body::byId(I->getId1(),scene)->state->pos).norm(); Real R=.5*(geom->refR1+geom->refR2); for(int i=0; i<3; i++) for(int j=i; j<3; j++){ sigN(i,j)+=R*N*n[i]*n[j]; if(hasShear) sigT(i,j)+=R*T*n[i]*t[j]; } } Real vol=scene->cell->getVolume(); sigN*=2/vol; sigT*=2/vol; // fill terms under the diagonal sigN(1,0)=sigN(0,1); sigN(2,0)=sigN(0,2); sigN(2,1)=sigN(1,2); sigT(1,0)=sigT(0,1); sigT(2,0)=sigT(0,2); sigT(2,1)=sigT(1,2); // *** Normal stress tensor split into two parts according to subnetworks of strong and weak forces (or other distinction if a threshold value for the force is assigned) ***/ Real Fmean(0); Matrix3r f, fs, fw; fabricTensor(Fmean,f,fs,fw); // 0,false,NaN for cutoff, split and thresholdForce as default arguments Matrix3r sigNStrong(Matrix3r::Zero()), sigNWeak(Matrix3r::Zero()); FOREACH(const shared_ptr& I, *scene->interactions){ if(!I->isReal()) continue; GenericSpheresContact* geom=YADE_CAST(I->geom.get()); NormShearPhys* phys=YADE_CAST(I->phys.get()); const Vector3r& n=geom->normal; Real N=(compressionPositive?-1:1)*phys->normalForce.dot(n); // Real R=(Body::byId(I->getId2(),scene)->state->pos+cellHsize*I->cellDist.cast()-Body::byId(I->getId1(),scene)->state->pos).norm(); Real R=.5*(geom->refR1+geom->refR2); Real Fsplit=(!std::isnan(thresholdForce))?thresholdForce:Fmean; if (compressionPositive?(NFsplit)){ for(int i=0; i<3; i++) for(int j=i; j<3; j++){ sigNStrong(i,j)+=R*N*n[i]*n[j];} } else{ for(int i=0; i<3; i++) for(int j=i; j<3; j++){ sigNWeak(i,j)+=R*N*n[i]*n[j];} } } sigNStrong*=2/vol; sigNWeak*=2/vol; // fill terms under the diagonal sigNStrong(1,0)=sigNStrong(0,1); sigNStrong(2,0)=sigNStrong(0,2); sigNStrong(2,1)=sigNStrong(1,2); sigNWeak(1,0)=sigNWeak(0,1); sigNWeak(2,0)=sigNWeak(0,2); sigNWeak(2,1)=sigNWeak(1,2); /// tensile forces are taken as positive! if (splitNormalTensor){return py::make_tuple(sigNStrong,sigNWeak);} // return strong-weak or tensile-compressive parts of the stress tensor (only normal part) return py::make_tuple(sigN,sigT); // return normal and shear components } /* Return the fabric tensor as according to [Satake1982]. */ /* as side-effect, set Gl1_NormShear::strongWeakThresholdForce */ void Shop::fabricTensor(Real& Fmean, Matrix3r& fabric, Matrix3r& fabricStrong, Matrix3r& fabricWeak, Real cutoff, bool splitTensor, Real thresholdForce){ Scene* scene=Omega::instance().getScene().get(); // *** Fabric tensor ***/ fabric=Matrix3r::Zero(); int count=0; // number of interactions py::tuple aabb = Shop::aabbExtrema(cutoff); Vector3r bbMin = py::extract(aabb[0]), bbMax = py::extract(aabb[1]); Vector3r cp; Fmean=0; // initialize average contact force for split = 1 fabric measurements // interactions loop to compute the fabric tensor returned when split = 0, and also measures average force for subsequent computations for split = 1: FOREACH(const shared_ptr& I, *scene->interactions){ if(!I->isReal()) continue; GenericSpheresContact* geom=YADE_CAST(I->geom.get()); cp = geom->contactPoint; if( !(cp[0]>=bbMin[0] && cp[0]<=bbMax[0] && cp[1]>=bbMin[1] && cp[1]<=bbMax[1] && cp[2]>=bbMin[2] && cp[2]<=bbMax[2]) ) continue; // possible to use isInBB() from _utils.cpp ? (NB: would exclude the contact points exactly along the BB) const Vector3r& n=geom->normal; for(int i=0; i<3; i++) for(int j=i; j<3; j++){ fabric(i,j)+=n[i]*n[j]; } NormShearPhys* phys=YADE_CAST(I->phys.get()); Real f=-phys->normalForce.dot(n); // will be < 0 in compression Fmean+=f; count++; } Fmean/=count; // fill terms under the diagonal fabric(1,0)=fabric(0,1); fabric(2,0)=fabric(0,2); fabric(2,1)=fabric(1,2); fabric/=count; #ifdef YADE_OPENGL Gl1_NormPhys::maxWeakFn=Fmean; #endif // *** Weak and strong fabric tensors ***/ // evaluate two different parts of the fabric tensor // making distinction between strong and weak network of contact forces fabricStrong=Matrix3r::Zero(); fabricWeak=Matrix3r::Zero(); int nStrong(0), nWeak(0); // number of strong and weak contacts respectively if (!splitTensor & !std::isnan(thresholdForce)) {LOG_WARN("The bool splitTensor should be set to True if you specified a threshold value for the contact force, otherwise the function will return only the fabric tensor and not the two separate contributions.");} FOREACH(const shared_ptr& I, *scene->interactions){ if(!I->isReal()) continue; GenericSpheresContact* geom=YADE_CAST(I->geom.get()); cp = geom->contactPoint; if( !(cp[0]>=bbMin[0] && cp[0]<=bbMax[0] && cp[1]>=bbMin[1] && cp[1]<=bbMax[1] && cp[2]>=bbMin[2] && cp[2]<=bbMax[2]) ) continue; // see above for idea about isInBB() from _utils.cpp NormShearPhys* phys=YADE_CAST(I->phys.get()); const Vector3r& n=geom->normal; Real f=-phys->normalForce.dot(n); // slipt the tensor according to the mean contact force or a threshold value if this is given Real Fsplit=(!std::isnan(thresholdForce))?thresholdForce:Fmean; if (fisPeriodic ) { py::tuple extrema = Shop::aabbExtrema(); volumeNonPeri = py::extract( (extrema[1][0] - extrema[0][0])*(extrema[1][1] - extrema[0][1])*(extrema[1][2] - extrema[0][2]) ); } if (volume==0) volume = scene->isPeriodic?scene->cell->hSize.determinant():volumeNonPeri; Matrix3r stressTensor = Matrix3r::Zero(); const bool isPeriodic = scene->isPeriodic; FOREACH(const shared_ptr&I, *scene->interactions){ if (!I->isReal()) continue; shared_ptr b1 = Body::byId(I->getId1(),scene); shared_ptr b2 = Body::byId(I->getId2(),scene); if (b1->shape->getClassIndex()==GridNode::getClassIndexStatic()) continue; //no need to check b2 because a GridNode can only be in interaction with an oher GridNode. NormShearPhys* nsi=YADE_CAST ( I->phys.get() ); Vector3r branch=b1->state->pos -b2->state->pos; if (isPeriodic) branch-= scene->cell->hSize*I->cellDist.cast(); stressTensor += (nsi->normalForce+nsi->shearForce)*branch.transpose(); } return stressTensor/volume; } py::tuple Shop::getStressProfile(Real volume, int nCell, Real dz, Real zRef, vector vPartAverageX, vector vPartAverageY, vector vPartAverageZ){ int minZ=0; int maxZ=0; Real minPosZ=0.; Real maxPosZ=0.; Scene* scene=Omega::instance().getScene().get(); vector stressTensorProfile(nCell,Matrix3r::Zero()); vector kineticStressTensorProfile(nCell,Matrix3r::Zero()); vector granularTemperatureProfile(nCell,0.0); vector numPart(nCell,0.0); const bool isPeriodic = scene->isPeriodic; // //Dynamic contribution to the stress tensor // FOREACH(const shared_ptr& b,*Omega::instance().getScene()->bodies){ int Np = floor((b->state->pos[2]-zRef)/dz); //Define the layer number with 0 corresponding to zRef if ((Np>=0)&&(Npstate->vel - Vector3r(vPartAverageX[Np],vPartAverageY[Np],vPartAverageZ[Np]); //Classical dynamical expression of the stress tensor stressTensorProfile[Np]+= -1/volume*b->state->mass*vFluct*vFluct.transpose(); kineticStressTensorProfile[Np]+= -1/volume*b->state->mass*vFluct*vFluct.transpose(); granularTemperatureProfile[Np] += 1/3.*(pow(vFluct[0],2) + pow(vFluct[1],2) + pow(vFluct[2],2)); numPart[Np]+=1.; } } for(int n=0;n0) granularTemperatureProfile[n]/=numPart[n]; } // //Love Weber contribution (same as getStress(), but with different layers) // FOREACH(const shared_ptr&I, *scene->interactions){ // loop over the interactions if (!I->isReal()) continue; shared_ptr b1 = Body::byId(I->getId1(),scene); shared_ptr b2 = Body::byId(I->getId2(),scene); if ((b1->state->blockedDOFs!=State::DOF_ALL)||(b2->state->blockedDOFs!=State::DOF_ALL)){// to remove annoying contribution from the fixed particles //Layers in which the particle center is contained int Np1 = floor((b1->state->pos[2] - zRef)/dz); int Np2 = floor((b2->state->pos[2] - zRef)/dz); //Vector between the two centers, from 2 to 1 Vector3r branch = b1->state->pos -b2->state->pos; if (isPeriodic) branch -= scene->cell->hSize*I->cellDist.cast();//to handle periodicity //Contact vector (from 1 to 2) NormShearPhys* nsi=YADE_CAST ( I->phys.get() ); Vector3r fContact = nsi->normalForce + nsi->shearForce; //The contribution to the stress tensor is taken such that only the part of the branch vector //inside the cell is taken into account //If the whole vector is in the cell, add the whole contribution to the cell if (Np1==Np2){ if ((Np1>=0) && (Np1Np2){ minZ = Np2; minPosZ = b2->state->pos[2]-zRef; maxZ = Np1; maxPosZ = b1->state->pos[2]-zRef; } else if (Np2>Np1) { minZ = Np1; minPosZ = b1->state->pos[2]-zRef; maxZ = Np2; maxPosZ = b2->state->pos[2]-zRef; } Real branchS_x = pow(branch[0],2.0); Real branchS_y = pow(branch[1],2.0); Real branchS_z = pow(branch[2],2.0); //Normalize the branch vector branch/=sqrt(branchS_x + branchS_y+branchS_z); //Loop over the cell containing the branch vector int numLayer = minZ; while (numLayer<=maxZ){ if ((numLayer>= 0)&&(numLayer stressTensorProfile(nCell,Matrix3r::Zero()); vector numPart(nCell,0.0); const bool isPeriodic = scene->isPeriodic; // //Love Weber contribution (same as getStress(), but with different layers) // FOREACH(const shared_ptr&I, *scene->interactions){ // loop over the interactions if (!I->isReal()) continue; shared_ptr b1 = Body::byId(I->getId1(),scene); shared_ptr b2 = Body::byId(I->getId2(),scene); if ((b1->state->blockedDOFs!=State::DOF_ALL)||(b2->state->blockedDOFs!=State::DOF_ALL)){// to remove annoying contribution from the fixed particles //Layers in which the particle center is contained int Np1 = floor((b1->state->pos[2] - zRef)/dz); int Np2 = floor((b2->state->pos[2] - zRef)/dz); //Vector between the two centers, from 2 to 1 Vector3r branch = b1->state->pos -b2->state->pos; if (isPeriodic) branch -= scene->cell->hSize*I->cellDist.cast();//to handle periodicity //Contact vector (from 1 to 2) NormShearPhys* nsi=YADE_CAST ( I->phys.get() ); Vector3r fContact = nsi->normalForce + nsi->shearForce; //The contribution to the stress tensor is taken such that only the part of the branch vector //inside the cell is taken into account //If the whole vector is in the cell, add the whole contribution to the cell if (Np1==Np2){ if ((Np1>=0) && (Np1Np2){ minZ = Np2; minPosZ = b2->state->pos[2]-zRef; maxZ = Np1; maxPosZ = b1->state->pos[2]-zRef; } else if (Np2>Np1) { minZ = Np1; minPosZ = b1->state->pos[2]-zRef; maxZ = Np2; maxPosZ = b2->state->pos[2]-zRef; } Real branchS_x = pow(branch[0],2.0); Real branchS_y = pow(branch[1],2.0); Real branchS_z = pow(branch[2],2.0); //Normalize the branch vector branch/=sqrt(branchS_x + branchS_y+branchS_z); //Loop over the cell containing the branch vector int numLayer = minZ; while (numLayer<=maxZ){ if ((numLayer>= 0)&&(numLayer velAverageX(nCell,0.0); vector velAverageY(nCell,0.0); vector velAverageZ(nCell,0.0); vector phiAverage(nCell,0.0); //Loop over the particles FOREACH(const shared_ptr& b, *Omega::instance().getScene()->bodies){ shared_ptr s=YADE_PTR_DYN_CAST(b->shape); if(!s) continue; if (activateCond==true){ const Sphere* sphere = dynamic_cast(b->shape.get()); if (sphere->radius!=radiusPy) continue; } //select diameters asked const Real zPos = b->state->pos[dir]-zRef; int Np = floor(zPos/dz); //Define the layer number with 0 corresponding to zRef. Let the z position wrt to zero, that way all z altitude are positive. (otherwise problem with volPart evaluation) minZ= floor((zPos-s->radius)/dz); maxZ= floor((zPos+s->radius)/dz); deltaCenter = zPos - Np*dz; // Loop over the cell in which the particle is contained numLayer = minZ; while (numLayer<=maxZ){ if ((numLayer>=0)&&(numLayerradius) zInf = -s->radius; if (zSup>s->radius) zSup = s->radius; //Analytical formulation of the volume of a slice of sphere volPart = Mathr::PI*pow(s->radius,2)*(zSup - zInf +(pow(zInf,3)-pow(zSup,3))/(3*pow(s->radius,2))); phiAverage[numLayer]+=volPart; velAverageX[numLayer]+=volPart*b->state->vel[0]; velAverageY[numLayer]+=volPart*b->state->vel[1]; velAverageZ[numLayer]+=volPart*b->state->vel[2]; } numLayer+=1; } } //Normalized the weighted velocity by the volume of particles contained inside the cell for(int n=0;n velAverageX(nCell,0.0); vector velAverageY(nCell,0.0); vector velAverageZ(nCell,0.0); vector phiAverage(nCell,0.0); vector Npart(nCell,0.0); //Loop over the particles FOREACH(const shared_ptr& b, *Omega::instance().getScene()->bodies){ shared_ptr s=YADE_PTR_DYN_CAST(b->shape); if(!s) continue; if (activateCond==true){ const Sphere* sphere = dynamic_cast(b->shape.get()); if (sphere->radius!=radiusPy) continue; } //select diameters asked const Real zPos = b->state->pos[2]-zRef; int Np = floor(zPos/dz); //Define the layer number with 0 corresponding to zRef. Let the z position wrt to zero, that way all z altitude are positive. (otherwise problem with volPart evaluation) if ((Np>=0)&&(Npradius,3); phiAverage[Np]+=volPart/vCell; Npart[Np]+=1.; velAverageX[Np]+=b->state->vel[0]; velAverageY[Np]+=b->state->vel[1]; velAverageZ[Np]+=b->state->vel[2]; } } //Normalized the weighted velocity by the volume of particles contained inside the cell for(int n=0;nisPeriodic?scene->cell->hSize.determinant():1; Matrix3r stressTensor = Matrix3r::Zero(); const bool isPeriodic = scene->isPeriodic; FOREACH(const shared_ptr&I, *scene->interactions){ if (!I->isReal()) continue; shared_ptr b1 = Body::byId(I->getId1(),scene); shared_ptr b2 = Body::byId(I->getId2(),scene); Vector3r fCap = mindlin? YADE_CAST (I->phys.get())->fCap : YADE_CAST (I->phys.get())->fCap; Vector3r branch=b1->state->pos -b2->state->pos; if (isPeriodic) branch-= scene->cell->hSize*I->cellDist.cast(); stressTensor += fCap*branch.transpose(); } return stressTensor/volume; } void Shop::getStressLWForEachBody(vector& bStresses){ const shared_ptr& scene=Omega::instance().getScene(); bStresses.resize(scene->bodies->size()); for (size_t k=0;kbodies->size();k++) bStresses[k]=Matrix3r::Zero(); FOREACH(const shared_ptr& I, *scene->interactions){ if(!I->isReal()) continue; GenericSpheresContact* geom=YADE_CAST(I->geom.get()); NormShearPhys* phys=YADE_CAST(I->phys.get()); Vector3r f=phys->normalForce+phys->shearForce; //Sum f_i*l_j/V for each contact of each particle bStresses[I->getId1()]-=(3.0/(4.0*Mathr::PI*pow(geom->refR1,3)))*f*((geom->contactPoint-Body::byId(I->getId1(),scene)->state->pos).transpose()); if (!scene->isPeriodic) bStresses[I->getId2()]+=(3.0/(4.0*Mathr::PI*pow(geom->refR2,3)))*f*((geom->contactPoint- (Body::byId(I->getId2(),scene)->state->pos)).transpose()); else bStresses[I->getId2()]+=(3.0/(4.0*Mathr::PI*pow(geom->refR2,3)))*f* ((geom->contactPoint- (Body::byId(I->getId2(),scene)->state->pos + (scene->cell->hSize*I->cellDist.cast()))).transpose()); } } py::list Shop::getStressLWForEachBody(){ py::list ret; vector bStresses; getStressLWForEachBody(bStresses); FOREACH(const Matrix3r& m, bStresses) ret.append(m); return ret; // return py::make_tuple(bStresses); } void Shop::calm(const shared_ptr& _scene, int mask){ const shared_ptr scene=(_scene?_scene:Omega::instance().getScene()); FOREACH(shared_ptr b, *scene->bodies){ if (!b || !b->isDynamic()) continue; if(((mask>0) and ((b->groupMask & mask)==0))) continue; b->state->vel=Vector3r::Zero(); b->state->angVel=Vector3r::Zero(); b->state->angMom=Vector3r::Zero(); } } py::list Shop::getBodyIdsContacts(Body::id_t bodyID) { py::list ret; if (bodyID < 0) { throw std::logic_error("BodyID should be a positive value!"); } const shared_ptr& scene=Omega::instance().getScene(); const shared_ptr& b = Body::byId(bodyID,scene); for(Body::MapId2IntrT::iterator it=b->intrs.begin(),end=b->intrs.end(); it!=end; ++it) { ret.append((*it).first); } return ret; } void Shop::setContactFriction(Real angleRad){ Scene* scene = Omega::instance().getScene().get(); shared_ptr& bodies = scene->bodies; FOREACH(const shared_ptr& b,*scene->bodies){ if(b->isClump()) continue; if (b->isDynamic()) YADE_PTR_CAST (b->material)->frictionAngle = angleRad; } FOREACH(const shared_ptr& ii, *scene->interactions){ if (!ii->isReal()) continue; const shared_ptr& sdec1 = YADE_PTR_CAST((*bodies)[(Body::id_t) ((ii)->getId1())]->material); const shared_ptr& sdec2 = YADE_PTR_CAST((*bodies)[(Body::id_t) ((ii)->getId2())]->material); //FIXME - why dynamic_cast fails here? FrictPhys* contactPhysics = YADE_CAST((ii)->phys.get()); const Real& fa = sdec1->frictionAngle; const Real& fb = sdec2->frictionAngle; contactPhysics->tangensOfFrictionAngle = std::tan(std::min(fa,fb)); } } void Shop::growParticles(Real multiplier, bool updateMass, bool dynamicOnly) { Scene* scene = Omega::instance().getScene().get(); const int sphereIdx = Sphere::getClassIndexStatic(); FOREACH(const shared_ptr& b,*scene->bodies){ if (dynamicOnly && !b->isDynamic()) continue; //We grow only spheres and clumps if(b->isClump() or sphereIdx == b->shape->getClassIndex()){ if (updateMass) {b->state->mass*=pow(multiplier,3); b->state->inertia*=pow(multiplier,5);} if (b->isClump()) { //update local Se3 of each clump member //FIXME: the velocity of clump members is invalid after a growth since the growth is not reflected (see Clump::moveMembers) // for clumps we updated inertia, nothing else to do FOREACH(Clump::MemberMap::value_type& B, YADE_PTR_CAST(b->shape)->members){ // B.first is Body::id_t, B.second is local Se3r of that body in the clump B.second.position *= multiplier;} // for clumps we are done continue; } // for spheres, we update radius (YADE_CAST (b->shape.get()))->radius *= multiplier; // and if they are clump members,clump volume variation with homothetic displacement of all members if (b->isClumpMember()) b->state->pos += (multiplier-1) * (b->state->pos - Body::byId(b->clumpId, scene)->state->pos); } } FOREACH(const shared_ptr& ii, *scene->interactions){ int ci1=(*(scene->bodies))[ii->getId1()]->shape->getClassIndex(); int ci2=(*(scene->bodies))[ii->getId2()]->shape->getClassIndex(); if (ii->isReal()) { GenericSpheresContact* contact = YADE_CAST(ii->geom.get()); if ((!dynamicOnly || (*(scene->bodies))[ii->getId1()]->isDynamic()) && ci1==sphereIdx) contact->refR1 = YADE_CAST((* (scene->bodies))[ii->getId1()]->shape.get())->radius; if ((!dynamicOnly || (*(scene->bodies))[ii->getId2()]->isDynamic()) && ci2==sphereIdx) contact->refR2 = YADE_CAST((* (scene->bodies))[ii->getId2()]->shape.get())->radius; const shared_ptr& contactPhysics = YADE_PTR_CAST(ii->phys); contactPhysics->kn*=multiplier; contactPhysics->ks*=multiplier; } } } void Shop::growParticle(Body::id_t bodyID, Real multiplier, bool updateMass) { const shared_ptr& b = Body::byId(bodyID); Real& rad = YADE_CAST(b->shape.get())->radius; rad *= multiplier; if (updateMass) {b->state->mass*=pow(multiplier,3); b->state->inertia*=pow(multiplier,5);} for(Body::MapId2IntrT::iterator it=b->intrs.begin(),end=b->intrs.end(); it!=end; ++it) { //Iterate over all bodie's interactions if(!(*it).second->isReal()) continue; GenericSpheresContact* contact = YADE_CAST((*it).second->geom.get()); if (bodyID==it->second->getId1()) contact->refR1 = rad; else contact->refR2 = rad; } } py::tuple Shop::aabbExtrema(Real cutoff, bool centers){ if(cutoff<0. || cutoff>1.) throw invalid_argument("Cutoff must be >=0 and <=1."); Real inf=std::numeric_limits::infinity(); Vector3r minimum(inf,inf,inf),maximum(-inf,-inf,-inf); FOREACH(const shared_ptr& b, *Omega::instance().getScene()->bodies){ shared_ptr s=YADE_PTR_DYN_CAST(b->shape); if(!s) continue; Vector3r rrr(s->radius,s->radius,s->radius); minimum=minimum.cwiseMin(b->state->pos-(centers?Vector3r::Zero():rrr)); maximum=maximum.cwiseMax(b->state->pos+(centers?Vector3r::Zero():rrr)); } Vector3r dim=maximum-minimum; return py::make_tuple(Vector3r(minimum+.5*cutoff*dim),Vector3r(maximum-.5*cutoff*dim)); } /*! Added function for 2D calculation: sphere volume. Optional. By Ning Guo */ Real Shop::getSpheresVolume2D(const shared_ptr& _scene, int mask){ const shared_ptr scene=(_scene?_scene:Omega::instance().getScene()); Real vol=0; FOREACH(shared_ptr b, *scene->bodies){ if (!b) continue; Sphere* s=dynamic_cast(b->shape.get()); if((!s) or ((mask>0) and ((b->groupMask & mask)==0))) continue; vol += Mathr::PI*pow(s->radius,2); } return vol; } /*! Added function for 2D calculation: void ratio. Optional. By Ning Guo */ Real Shop::getVoidRatio2D(const shared_ptr& _scene, Real _zlen){ const shared_ptr scene=(_scene?_scene:Omega::instance().getScene()); Real V; if(!scene->isPeriodic){ throw std::invalid_argument("utils.voidratio2D applies only to aperiodic simulations."); } else { V=scene->cell->getVolume()/_zlen; } Real Vs=Shop::getSpheresVolume2D(); return (V-Vs)/Vs; } /*! Added function to get stress tensor and tangent operator tensor. By Ning Guo */ py::tuple Shop::getStressAndTangent(Real volume, bool symmetry){ Scene* scene=Omega::instance().getScene().get(); if (volume==0) volume = scene->isPeriodic?scene->cell->hSize.determinant():1; Matrix3r stress = Matrix3r::Zero(); Matrix6r tangent = Matrix6r::Zero(); const bool isPeriodic = scene->isPeriodic; FOREACH(const shared_ptr& I, *scene->interactions){ if(!I->isReal()) continue; shared_ptr b1 = Body::byId(I->getId1(),scene); shared_ptr b2 = Body::byId(I->getId2(),scene); Vector3r pos1 = Vector3r::Zero(); Vector3r pos2 = Vector3r::Zero(); if(b1->isClumpMember()) { pos1 = Body::byId(b1->clumpId,scene)->state->pos; } else { pos1 = b1->state->pos; } if(b2->isClumpMember()) { pos2 = Body::byId(b2->clumpId,scene)->state->pos; } else { pos2 = b2->state->pos; } Vector3r branch=pos1 - pos2; if (isPeriodic) branch -= scene->cell->hSize*I->cellDist.cast(); NormShearPhys* nsi=YADE_CAST(I->phys.get()); Real kN=nsi->kn; Real kT=nsi->ks; GenericSpheresContact* geom=YADE_CAST(I->geom.get()); const Vector3r& n=geom->normal; const Vector3r& fT=nsi->shearForce; const Vector3r& fTotal=nsi->normalForce+nsi->shearForce; Real T=fT.norm(); bool hasShear=(T>0); Vector3r t(Vector3r::Zero()); if(hasShear) t=fT/T; stress += fTotal*branch.transpose(); tangent(0,0) += kN*n[0]*branch[0]*n[0]*branch[0]+kT*t[0]*branch[0]*t[0]*branch[0]; tangent(0,1) += kN*n[0]*branch[0]*n[1]*branch[1]+kT*t[0]*branch[0]*t[1]*branch[1]; tangent(0,2) += kN*n[0]*branch[0]*n[2]*branch[2]+kT*t[0]*branch[0]*t[2]*branch[2]; tangent(0,3) += kN*n[0]*branch[0]*(n[1]*branch[2]+n[2]*branch[1])*0.5+kT*t[0]*branch[0]*(t[1]*branch[2]+t[2]*branch[1])*0.5; tangent(0,4) += kN*n[0]*branch[0]*(n[0]*branch[2]+n[2]*branch[0])*0.5+kT*t[0]*branch[0]*(t[0]*branch[2]+t[2]*branch[0])*0.5; tangent(0,5) += kN*n[0]*branch[0]*(n[0]*branch[1]+n[1]*branch[0])*0.5+kT*t[0]*branch[0]*(t[0]*branch[1]+t[1]*branch[0])*0.5; tangent(1,1) += kN*n[1]*branch[1]*n[1]*branch[1]+kT*t[1]*branch[1]*t[1]*branch[1]; tangent(1,2) += kN*n[1]*branch[1]*n[2]*branch[2]+kT*t[1]*branch[1]*t[2]*branch[2]; tangent(1,3) += kN*n[1]*branch[1]*(n[1]*branch[2]+n[2]*branch[1])*0.5+kT*t[1]*branch[1]*(t[1]*branch[2]+t[2]*branch[1])*0.5; tangent(1,4) += kN*n[1]*branch[1]*(n[0]*branch[2]+n[2]*branch[0])*0.5+kT*t[1]*branch[1]*(t[0]*branch[2]+t[2]*branch[0])*0.5; tangent(1,5) += kN*n[1]*branch[1]*(n[0]*branch[1]+n[1]*branch[0])*0.5+kT*t[1]*branch[1]*(t[0]*branch[1]+t[1]*branch[0])*0.5; tangent(2,2) += kN*n[2]*branch[2]*n[2]*branch[2]+kT*t[2]*branch[2]*t[2]*branch[2]; tangent(2,3) += kN*n[2]*branch[2]*(n[1]*branch[2]+n[2]*branch[1])*0.5+kT*t[2]*branch[2]*(t[1]*branch[2]+t[2]*branch[1])*0.5; tangent(2,4) += kN*n[2]*branch[2]*(n[0]*branch[2]+n[2]*branch[0])*0.5+kT*t[2]*branch[2]*(t[0]*branch[2]+t[2]*branch[0])*0.5; tangent(2,5) += kN*n[2]*branch[2]*(n[0]*branch[1]+n[1]*branch[0])*0.5+kT*t[2]*branch[2]*(t[0]*branch[1]+t[1]*branch[0])*0.5; tangent(3,3) += kN*(n[1]*branch[2]*n[1]*branch[2]+n[1]*branch[2]*n[2]*branch[1]*2+n[2]*branch[1]*n[2]*branch[1])*0.25+kT*(t[1]*branch[2]*t[1]*branch[2]+t[1]*branch[2]*t[2]*branch[1]*2+t[2]*branch[1]*t[2]*branch[1])*0.25; tangent(3,4) += kN*(n[1]*branch[2]*n[0]*branch[2]+n[1]*branch[2]*n[2]*branch[0]+n[2]*branch[1]*n[0]*branch[2]+n[2]*branch[1]*n[2]*branch[0])*0.25+kT*(t[1]*branch[2]*t[0]*branch[2]+t[1]*branch[2]*t[2]*branch[0]+t[2]*branch[1]*t[0]*branch[2]+t[2]*branch[1]*t[2]*branch[0])*0.25; tangent(3,5) += kN*(n[1]*branch[2]*n[0]*branch[1]+n[1]*branch[2]*n[1]*branch[0]+n[2]*branch[1]*n[0]*branch[1]+n[2]*branch[1]*n[1]*branch[0])*0.25+kT*(t[1]*branch[2]*t[0]*branch[1]+t[1]*branch[2]*t[1]*branch[0]+t[2]*branch[1]*t[0]*branch[1]+t[2]*branch[1]*t[1]*branch[0])*0.25; tangent(4,4) += kN*(n[0]*branch[2]*n[0]*branch[2]+n[0]*branch[2]*n[2]*branch[0]*2+n[2]*branch[0]*n[2]*branch[0])*0.25+kT*(t[0]*branch[2]*t[0]*branch[2]+t[0]*branch[2]*t[2]*branch[0]*2+t[2]*branch[0]*t[2]*branch[0])*0.25; tangent(4,5) += kN*(n[0]*branch[2]*n[0]*branch[1]+n[0]*branch[2]*n[1]*branch[0]+n[2]*branch[0]*n[0]*branch[1]+n[2]*branch[0]*n[1]*branch[0])*0.25+kT*(t[0]*branch[2]*t[0]*branch[1]+t[0]*branch[2]*t[1]*branch[0]+t[2]*branch[0]*t[0]*branch[1]+t[2]*branch[0]*t[1]*branch[0])*0.25; tangent(5,5) += kN*(n[0]*branch[1]*n[0]*branch[1]+n[0]*branch[1]*n[1]*branch[0]*2+n[1]*branch[0]*n[1]*branch[0])*0.25+kT*(t[0]*branch[1]*t[0]*branch[1]+t[0]*branch[1]*t[1]*branch[0]*2+t[1]*branch[0]*t[1]*branch[0])*0.25; } stress/=volume; tangent/=volume; return py::make_tuple(stress,tangent); } trunk-2018.02b/pkg/dem/SimpleShear.cpp000066400000000000000000000262271324306050200174450ustar00rootroot00000000000000/************************************************************************* * Copyright (C) 2008 by Jerome Duriez * * duriez@geo.hmg.inpg.fr * * * * This program is free software; it is licensed under the terms of the * * GNU General Public License v2 or later. See file LICENSE for details. * *************************************************************************/ #include "SimpleShear.hpp" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include YADE_PLUGIN((SimpleShear)) CREATE_LOGGER(SimpleShear); SimpleShear::~SimpleShear () { } bool SimpleShear::generate(std::string& message) { scene = shared_ptr(new Scene); createActors(scene); // Box walls shared_ptr w1; // The left one : createBox(w1,Vector3r(-thickness/2.0,(height)/2.0,0),Vector3r(thickness/2.0,5*(height/2.0+thickness),width/2.0)); scene->bodies->insert(w1); shared_ptr w2; // The lower one : createBox(w2,Vector3r(length/2.0,-thickness/2.0,0),Vector3r(length/2.0,thickness/2.0,width/2.0)); YADE_PTR_CAST (w2->material)->frictionAngle = matFrictionDeg * Mathr::PI/180.0; // so that we have phi(spheres-inferior wall)=phi(sphere-sphere) scene->bodies->insert(w2); shared_ptr w3; // The right one createBox(w3,Vector3r(length+thickness/2.0,height/2.0,0),Vector3r(thickness/2.0,5*(height/2.0+thickness),width/2.0)); scene->bodies->insert(w3); shared_ptr w4; // The upper one createBox(w4,Vector3r(length/2.0,height+thickness/2.0,0),Vector3r(length/2.0,thickness/2.0,width/2.0)); YADE_PTR_CAST (w4->material)->frictionAngle = matFrictionDeg * Mathr::PI/180.0; // so that we have phi(spheres-superior wall)=phi(sphere-sphere) scene->bodies->insert(w4); // To close the front and the back of the box shared_ptr w5; // behind createBox(w5,Vector3r(length/2.0,height/2.0,-width/2.0-thickness/2.0), Vector3r(2.5*length/2.0,height/2.0+thickness,thickness/2.0)); scene->bodies->insert(w5); shared_ptr w6; // the front createBox(w6,Vector3r(length/2.0,height/2.0,width/2.0+thickness/2.0), Vector3r(2.5*length/2.0,height/2.0+thickness,thickness/2.0)); scene->bodies->insert(w6); // the list which will contain the positions of centers and the radii of the created spheres vector sphere_list; // to use the TriaxialTest method : string out=GenerateCloud(sphere_list,Vector3r(0,0,-width/2.0),Vector3r(length,height,width/2.0),1000,0.3,0.7);// tries to generate a sample of 1000 spheres, with a required porosity of 0.7 cout << out << endl; // to use a text file : // std::pair res=ImportCloud(sphere_list,filename); vector::iterator it = sphere_list.begin(); vector::iterator it_end = sphere_list.end(); shared_ptr body; for (;it!=it_end; ++it) { createSphere(body,it->first,it->second); scene->bodies->insert(body); } return true; } void SimpleShear::createSphere(shared_ptr& body, Vector3r position, Real radius) { body = shared_ptr(new Body); body->groupMask=1; shared_ptr mat(new FrictMat); shared_ptr aabb(new Aabb); shared_ptr iSphere(new Sphere); body->state->pos =position; body->state->ori =Quaternionr::Identity(); body->state->vel =Vector3r(0,0,0); body->state->angVel =Vector3r(0,0,0); Real masse =4.0/3.0*Mathr::PI*radius*radius*radius*density; body->state->mass =masse; body->state->inertia = Vector3r(2.0/5.0*masse*radius*radius,2.0/5.0*masse*radius*radius,2.0/5.0*masse*radius*radius); mat->young = matYoungModulus; mat->poisson = matPoissonRatio; mat->frictionAngle = matFrictionDeg * Mathr::PI/180.0; body->material = mat; aabb->color = Vector3r(0,1,0); iSphere->radius = radius; iSphere->color = ((int)(floor(8*position.x()/length)))%2?Vector3r(0.7,0.7,0.7):Vector3r(0.45,0.45,0.45);// so that we have eight different colour bands body->shape = iSphere; body->bound = aabb; } void SimpleShear::createBox(shared_ptr& body, Vector3r position, Vector3r extents) { body = shared_ptr(new Body); body->groupMask=1; shared_ptr mat(new FrictMat); shared_ptr aabb(new Aabb); // shared_ptr gBox(new BoxModel); shared_ptr iBox(new Box); iBox->wire = true; body->setDynamic(false); body->state->angVel = Vector3r(0,0,0); body->state->vel = Vector3r(0,0,0); // NB : mass and inertia not defined because not used, since Box are not dynamics body->state->pos = position; body->state->ori = Quaternionr::Identity(); mat->young = matYoungModulus; mat->poisson = matPoissonRatio; mat->frictionAngle = 0.0; //default value, modified after for w2 and w4 to have good values of phi(sphere-walls) body->material = mat; aabb->color = Vector3r(1,0,0); /* gBox->extents = extents; gBox->color = Vector3r(1,0,0); gBox->wire = true; gBox->shadowCaster = false;*/ iBox->extents = extents; iBox->color = Vector3r(1,0,0); body->bound = aabb; body->shape = iBox; // body->geometricalModel = gBox; } void SimpleShear::createActors(shared_ptr& scene) { shared_ptr interactionGeometryDispatcher(new IGeomDispatcher); interactionGeometryDispatcher->add(new Ig2_Sphere_Sphere_ScGeom); interactionGeometryDispatcher->add(new Ig2_Box_Sphere_ScGeom); shared_ptr interactionPhysicsDispatcher(new IPhysDispatcher); shared_ptr CL1Rel(new Ip2_FrictMat_FrictMat_FrictPhys); interactionPhysicsDispatcher->add(CL1Rel); shared_ptr collider(new InsertionSortCollider); collider->boundDispatcher->add(new Bo1_Sphere_Aabb); collider->boundDispatcher->add(new Bo1_Box_Aabb); shared_ptr gravityCondition(new GravityEngine); gravityCondition->gravity = gravity; shared_ptr globalStiffnessTimeStepper(new GlobalStiffnessTimeStepper); globalStiffnessTimeStepper->timeStepUpdateInterval = timeStepUpdateInterval; globalStiffnessTimeStepper->defaultDt=3e-6; shared_ptr kinemEngine (new KinemCTDEngine); kinemEngine->compSpeed = 10.0; kinemEngine->targetSigma=2000.0; shared_ptr ids(new InteractionLoop); ids->geomDispatcher=interactionGeometryDispatcher; ids->physDispatcher=interactionPhysicsDispatcher; ids->lawDispatcher=shared_ptr(new LawDispatcher); shared_ptr ldc(new Law2_ScGeom_FrictPhys_CundallStrack); ids->lawDispatcher->add(ldc); scene->engines.clear(); scene->engines.push_back(shared_ptr(new ForceResetter)); scene->engines.push_back(globalStiffnessTimeStepper); scene->engines.push_back(collider); scene->engines.push_back(ids); if(gravApplied) scene->engines.push_back(gravityCondition); scene->engines.push_back(shared_ptr (new NewtonIntegrator)); scene->engines.push_back(kinemEngine); } string SimpleShear::GenerateCloud(vector& sphere_list,Vector3r lowerCorner,Vector3r upperCorner,long number,Real rad_std_dev, Real porosity) { sphere_list.clear(); long tries = 1000; //nb max of tries for positionning the next sphere Vector3r dimensions = upperCorner - lowerCorner; Real mean_radius = pow(dimensions.x()*dimensions.y()*dimensions.z()*(1-porosity)/(4.0/3.0*Mathr::PI*number),1.0/3.0); cerr << " mean radius " << mean_radius << endl;; // std::cerr << "generating aggregates ... "; long t, i; for (i=0; i (sphere_list[j].first-s.first).squaredNorm()) overlap=true; if (!overlap) { sphere_list.push_back(s); // cout << "j'ai bien rajoute une sphere dans la liste" << endl; break; } } if (t==tries) { string str1="Generated a sample with " + boost::lexical_cast(i) + " spheres inside box of dimensions: (" + boost::lexical_cast(dimensions[0]) + "," + boost::lexical_cast(dimensions[1]) + "," + boost::lexical_cast(dimensions[2]) + ").\n"; return str1 + "More than " + boost::lexical_cast(tries) + " tries while generating sphere number " + boost::lexical_cast(i+1) + "/" + boost::lexical_cast(number) + "."; } } return "Generated a sample with " + boost::lexical_cast(number) + " spheres inside box of dimensions: (" + boost::lexical_cast(dimensions[0]) + "," + boost::lexical_cast(dimensions[1]) + "," + boost::lexical_cast(dimensions[2]) + ")."; } std::pair SimpleShear::ImportCloud(vector& sphere_list,string importFilename) { sphere_list.clear(); int nombre=0; if(importFilename.size() != 0 && boost::filesystem::exists(importFilename) ) { ifstream loadFile(importFilename.c_str()); // cree l'objet loadFile de la classe ifstream qui va permettre de lire ce qu'il y a dans importFilename // Real zJF; // while( !loadFile.eof() ) // tant qu'on n'est pas a la fin du fichier // { // BasicSphere s; // l'elt de la liste sphere_list (= la sphere) que l'on va lire maintenant // loadFile >> s.first.X();// le X de la position de son centre // loadFile >> zJF; // s.first.Z()=zJF - width/2.0;// le Z de la position de son centre // loadFile >> s.first.Y();// le Y de la position de son centre // loadFile >> s.second; // son rayon // sphere_list.push_back(s); // nombre++; // } Real it; while( !loadFile.eof() ) // tant qu'on n'est pas a la fin du fichier { BasicSphere s; // l'elt de la liste sphere_list (= la sphere) que l'on va lire maintenant loadFile >> it; loadFile >> s.second; // son rayon loadFile >> s.first.x(); loadFile >> s.first.y();// le y de la position de son centre loadFile >> s.first.z();// le z de la position de son centre sphere_list.push_back(s); nombre++; } return std::make_pair(std::string("Echantillon correctement genere : " + boost::lexical_cast(nombre) + " billes"),true); } else { cerr << "Cannot find input file" << endl; return std::make_pair("Cannot find input file",false); } } trunk-2018.02b/pkg/dem/SimpleShear.hpp000066400000000000000000000071071324306050200174460ustar00rootroot00000000000000/************************************************************************* * Copyright (C) 2008 by Jerome Duriez * * duriez@geo.hmg.inpg.fr * * * * This program is free software; it is licensed under the terms of the * * GNU General Public License v2 or later. See file LICENSE for details. * *************************************************************************/ #pragma once #include typedef pair BasicSphere; //! make a list of spheres non-overlapping sphere class SimpleShear : public FileGenerator { void createBox(shared_ptr& body, Vector3r position, Vector3r extents); void createSphere(shared_ptr& body, Vector3r position, Real radius); void createActors(shared_ptr& scene); //method to create a list (containing the positions of centers and radii) of n non interpenetrating spheres, occupying a rectangle with a given (rather high) porosity (issued from TriaxialTest) : string GenerateCloud(vector& sphere_list,Vector3r lowerCorner,Vector3r upperCorner,long number,Real rad_std_dev, Real porosity); // to create the same list but by reading a text file containing the information : std::pair ImportCloud(vector& sphere_list,string importFilename); public : ~SimpleShear (); bool generate(std::string& message); YADE_CLASS_BASE_DOC_ATTRS(SimpleShear,FileGenerator,"Preprocessor for a simple shear box model. The packing initially conforms a gas-like, very loose, state (see utils.makeCloud function), but importing some existing packing from a text file can be also performed after little change in the source code. In its current state, the preprocessor carries out an oedometric compression, until a value of normal stress equal to 2 MPa (and a stable mechanical state). Others Engines such as :yref:`KinemCNDEngine`, :yref:`KinemCNSEngine` and :yref:`KinemCNLEngine`, could be used to apply resp. constant normal displacement, constant normal rigidity and constant normal stress paths using such a simple shear box.", /* ((string,filename,"../porosite0_44.txt",,"file with the list of spheres centers and radii"))*/ ((Real,thickness,0.001,,"thickness of the boxes constituting the shear box [$m$]")) ((Real,length,0.1,,"initial length (along x-axis) of the shear box [$m$]")) ((Real,height,0.02,,"initial height (along y-axis) of the shear box [$m$]")) ((Real,width,0.04,,"initial width (along z-axis) of the shear box [$m$]")) ((Real,density,2600,,"density of the spheres [$kg/m^3$]")) ((Real,matYoungModulus,4.0e9,,"value of :yref:`FrictMat.young` for the bodies [$Pa$]")) ((Real,matPoissonRatio,0.04,,"value of :yref:`FrictMat.poisson` for the bodies [-]")) ((Real,matFrictionDeg,37,,"value of :yref:`FrictMat.frictionAngle` within the packing and for the two horizontal boundaries (friction is zero along other boundaries) [$^\\circ$] (the necessary conversion in [$rad$] is done automatically)")) ((bool,gravApplied,false,,"depending on this, :yref:`GravityEngine` is added or not to the scene to take into account the weight of particles")) ((Vector3r,gravity,Vector3r(0,-9.81,0),,"vector corresponding to used gravity (if :yref:`gravApplied`) [$m/s^2$]")) ((int,timeStepUpdateInterval,50,,"value of :yref:`TimeStepper::timeStepUpdateInterval` for the :yref:`TimeStepper` used here")) ); DECLARE_LOGGER; }; REGISTER_SERIALIZABLE(SimpleShear); trunk-2018.02b/pkg/dem/SnapshotEngine.cpp000066400000000000000000000032351324306050200201500ustar00rootroot00000000000000#ifdef YADE_OPENGL #include"SnapshotEngine.hpp" YADE_PLUGIN((SnapshotEngine)); CREATE_LOGGER(SnapshotEngine); void SnapshotEngine::action(){ if(!OpenGLManager::self) throw logic_error("No OpenGLManager instance?!"); if(OpenGLManager::self->views.size()==0){ int viewNo=OpenGLManager::self->waitForNewView(deadTimeout); if(viewNo<0){ if(!ignoreErrors) throw runtime_error("SnapshotEngine: Timeout waiting for new 3d view."); else { LOG_WARN("Making myself Engine::dead, as I can not live without a 3d view (timeout)."); dead=true; return; } } } const shared_ptr& glv=OpenGLManager::self->views[0]; ostringstream fss; fss<setSnapshotFormat(QString(format.c_str())); glv->nextFrameSnapshotFilename=fss.str(); // wait for the renderer to save the frame (will happen at next postDraw) timespec t1,t2; t1.tv_sec=0; t1.tv_nsec=10000000; /* 10 ms */ long waiting=0; while(!glv->nextFrameSnapshotFilename.empty()){ nanosleep(&t1,&t2); waiting++; if(((waiting) % 1000)==0) LOG_WARN("Already waiting "<deadTimeout){ if(ignoreErrors){ LOG_WARN("Timeout waiting for snapshot to be saved, making byself Engine::dead"); dead=true; return; } else throw runtime_error("SnapshotEngine: Timeout waiting for snapshot to be saved."); } } snapshots.push_back(fss.str()); usleep((long)(msecSleep*1000)); //if(!plot.empty()){ pyRunString("import yade.plot; yade.plot.addImgData("+plot+"='"+fss.str()+"')"); } } #endif /* YADE_OPENGL */ trunk-2018.02b/pkg/dem/SnapshotEngine.hpp000066400000000000000000000032071324306050200201540ustar00rootroot00000000000000#pragma once #include #include #include #ifdef YADE_QT5 #include #elif YADE_QT4 #include #endif class SnapshotEngine: public PeriodicEngine { public: virtual void action(); YADE_CLASS_BASE_DOC_ATTRS(SnapshotEngine,PeriodicEngine,"Periodically save snapshots of GLView(s) as .png files. Files are named *fileBase* + *counter* + '.png' (counter is left-padded by 0s, i.e. snap00004.png).", ((string,format,"PNG",,"Format of snapshots (one of JPEG, PNG, EPS, PS, PPM, BMP) `QGLViewer documentation `_. File extension will be lowercased *format*. Validity of format is not checked.")) ((string,fileBase,"",,"Basename for snapshots")) ((int,counter,0,,"Number that will be appended to fileBase when the next snapshot is saved (incremented at every save). |yupdate|")) ((bool,ignoreErrors,true,,"Only report errors instead of throwing exceptions, in case of timeouts.")) ((vector,snapshots,,,"Files that have been created so far")) ((int,msecSleep,0,,"number of msec to sleep after snapshot (to prevent 3d hw problems) [ms]")) ((Real,deadTimeout,3,,"Timeout for 3d operations (opening new view, saving snapshot); after timing out, throw exception (or only report error if *ignoreErrors*) and make myself :yref:`dead`. [s]")) ((string,plot,,,"Name of field in :yref:`yade.plot.imgData` to which taken snapshots will be appended automatically.")) ); DECLARE_LOGGER; }; REGISTER_SERIALIZABLE(SnapshotEngine); trunk-2018.02b/pkg/dem/SpherePack.cpp000066400000000000000000000533061324306050200172540ustar00rootroot00000000000000// © 2009 Václav Šmilauer #include #include #include #include #include #include #include #include #include // not a serializable in the sense of YADE_PLUGIN CREATE_LOGGER(SpherePack); namespace py=boost::python; void SpherePack::fromList(const py::list& l){ pack.clear(); size_t len=py::len(l); for(size_t i=0; i(l[i]); py::extract vec(t[0]); if(vec.check()) { pack.push_back(Sph(vec(),py::extract(t[1]),(py::len(t)>2?py::extract(t[2]):-1))); continue; } PyErr_SetString(PyExc_TypeError, "List elements must be (Vector3, float) or (Vector3, float, int)!"); py::throw_error_already_set(); } }; void SpherePack::fromLists(const vector& centers, const vector& radii){ pack.clear(); if(centers.size()!=radii.size()) throw std::invalid_argument(("The same number of centers and radii must be given (is "+boost::lexical_cast(centers.size())+", "+boost::lexical_cast(radii.size())+")").c_str()); size_t l=centers.size(); for(size_t i=0; i tupleVector3rRealInt; vector ss; Vector3r mn,mx; ss=Shop::loadSpheresFromFile(file,mn,mx,&cellSize); pack.clear(); FOREACH(const tupleVector3rRealInt& s, ss) pack.push_back(Sph(boost::get<0>(s),boost::get<1>(s),boost::get<2>(s))); } void SpherePack::toFile(const string fname) const { ofstream f(fname.c_str()); if(!f.good()) throw runtime_error("Unable to open file `"+fname+"'"); if(cellSize!=Vector3r::Zero()){ f<<"##PERIODIC:: "<=0) throw std::invalid_argument("SpherePack with clumps cannot be (currently) exported to a text file."); f<& b, *scene->bodies){ if(!b) continue; shared_ptr intSph=YADE_PTR_DYN_CAST(b->shape); if(!intSph) continue; pack.push_back(Sph(b->state->pos,intSph->radius,(b->isClumpMember()?b->clumpId:-1))); } if(scene->isPeriodic) { cellSize=scene->cell->getSize(); isPeriodic = true; } } long SpherePack::makeCloud(Vector3r mn, Vector3r mx, Real rMean, Real rRelFuzz, int num, bool periodic, Real porosity, const vector& psdSizes, const vector& psdCumm, bool distributeMass, int seed, Matrix3r hSize){ isPeriodic = periodic; static boost::minstd_rand randGen(seed!=0?seed:(int)TimingInfo::getNow(/* get the number even if timing is disabled globally */ true)); static boost::variate_generator > rnd(randGen, boost::uniform_real(0,1)); vector psdRadii; // holds plain radii (rather than diameters), scaled down in some situations to get the target number vector psdCumm2; // psdCumm but dimensionally transformed to match mass distribution Vector3r size=mx-mn; bool hSizeFound =(hSize!=Matrix3r::Zero());//is hSize passed to the function? if (!hSizeFound) {hSize=size.asDiagonal();} if (hSizeFound && !periodic) LOG_WARN("hSize can be defined only for periodic cells."); Real volume=hSize.determinant(); Matrix3r invHsize =hSize.inverse(); Real area=std::abs(size[0]*size[2]+size[0]*size[1]+size[1]*size[2]);//2 terms will be null if one coordinate is 0, the other is the area if (!volume) { if (hSizeFound) throw invalid_argument("The period defined by hSize has null length in at least one direction, this is not supported. Define flat boxes via min-max and keep hSize undefined if you want a 2D packing."); else LOG_WARN("The volume of the min-max box is null, we will assume that the packing is 2D. If it is not what you want then you defined wrong input values; check that min and max corners are defined correctly.");} int mode=-1; bool err=false; // determine the way we generate radii if(porosity<=0) {LOG_WARN("porosity must be >0, changing it for you. It will be ineffective if rMean>0."); porosity=0.5;} //If rMean is not defined, then in will be defined in RDIST_NUM if(rMean>0) mode=RDIST_RMEAN; else if(num>0 && psdSizes.size()==0) { mode=RDIST_NUM; // the term (1+rRelFuzz²) comes from the mean volume for uniform distribution : Vmean = 4/3*pi*Rmean*(1+rRelFuzz²) if (volume) rMean=pow(volume*(1-porosity)/(Mathr::PI*(4/3.)*(1+rRelFuzz*rRelFuzz)*num),1/3.); else {//The volume is null, we will generate a 2D packing with the following rMean if (!area) throw invalid_argument("The box defined has null volume AND null surface. Define at least maxCorner of the box, or hSize if periodic."); rMean=pow(area*(1-porosity)/(Mathr::PI*(1+rRelFuzz*rRelFuzz)*num),0.5);} } // transform sizes and cummulated fractions values in something convenient for the generation process if(psdSizes.size()>0){ err=(mode>=0); mode=RDIST_PSD; if(psdSizes.size()!=psdCumm.size()) throw invalid_argument(("SpherePack.makeCloud: psdSizes and psdCumm must have same dimensions ("+boost::lexical_cast(psdSizes.size())+"!="+boost::lexical_cast(psdCumm.size())).c_str()); if(psdSizes.size()<=1) throw invalid_argument("SpherePack.makeCloud: psdSizes must have at least 2 items"); if((*psdCumm.begin())!=0. && (*psdCumm.rbegin())!=1.) throw invalid_argument("SpherePack.makeCloud: first and last items of psdCumm *must* be exactly 0 and 1."); psdRadii.reserve(psdSizes.size()); for(size_t i=0; i0 && (psdSizes[i-1]>psdSizes[i] || psdCumm[i-1]>psdCumm[i])) throw invalid_argument("SpherePack:makeCloud: psdSizes and psdCumm must be both non-decreasing."); } // check the consistency between sizes, num, and poro if all three are imposed. If target number will not fit in (1-poro)*volume, scale down particles sizes if (num>1){ appliedPsdScaling=1; if(distributeMass) { if (psdCumm2[psdSizes.size()-1]1) appliedPsdScaling=pow(volumeRatio,-1./3.); } if (appliedPsdScaling<1) for(size_t i=0; i0), generate radii the deterministic way, in decreasing order, else radii are stochastic since we don't know what the final number will be if (num>0) rand = ((Real)num-(Real)i+0.5)/((Real)num+1.); else rand = rnd(); int t; switch(mode){ case RDIST_RMEAN: //FIXME : r is never defined, it will be zero at first iteration, but it will have values in the next ones. //I don't understand why it apparently works. Some magic? case RDIST_NUM: if(distributeMass) r=pow3Interp(rand,rMean*(1-rRelFuzz),rMean*(1+rRelFuzz)); else r=rMean*(2*(rand-.5)*rRelFuzz+1); // uniform distribution in rMean*(1±rRelFuzz) break; case RDIST_PSD: if(distributeMass){ int piece=psdGetPiece(rand,psdCumm2,norm); r=pow3Interp(norm,psdRadii[piece],psdRadii[piece+1]); } else { int piece=psdGetPiece(rand,psdCumm,norm); r=psdRadii[piece]+norm*(psdRadii[piece+1]-psdRadii[piece]);} } // try to put the sphere into a free spot for(t=0; t=(pack[j].c-c).squaredNorm()) { overlap=true; break; } } } else { for(size_t j=0; j= dr.squaredNorm()) { overlap=true; break; } } } if(!overlap) { pack.push_back(Sph(c,r)); break; } } if (t==maxTry) { if(num>0) { if (mode!=RDIST_RMEAN) { //if rMean is not imposed, then we call makeCloud recursively, //scaling the PSD down until the target num is obtained Real nextPoro = porosity+(1-porosity)/10.; LOG_WARN("Exceeded "<& cumm, Real& norm){ int sz=cumm.size(); int i=0; while(i& radii, const vector& passing, int numSph, bool periodic, Real cloudPorosity, int seed){ //deprecated (https://bugs.launchpad.net/yade/+bug/1024443) LOG_ERROR("particleSD2() has been removed. Please use makeCloud() instead."); return 1; }; // Discrete particle size distribution long SpherePack::particleSD(Vector3r mn, Vector3r mx, Real rMean, bool periodic, string name, int numSph, const vector& radii, const vector& passing, bool passingIsNotPercentageButCount, int seed){ //deprecated (https://bugs.launchpad.net/yade/+bug/1024443) LOG_ERROR("particleSD() has been removed. Please use makeCloud() instead."); return 1; } long SpherePack::particleSD_2d(Vector2r mn, Vector2r mx, Real rMean, bool periodic, string name, int numSph, const vector& radii, const vector& passing, bool passingIsNotPercentageButCount, int seed){ //deprecated (https://bugs.launchpad.net/yade/+bug/1024443) LOG_ERROR("particleSD_2d() has been removed. Please use makeCloud() instead."); return 1; } long SpherePack::makeClumpCloud(const Vector3r& mn, const Vector3r& mx, const vector >& _clumps, bool periodic, int num, int seed){ // recenter given clumps and compute their margins vector clumps; /* vector margins; */ Vector3r boxMargins(Vector3r::Zero()); Real maxR=0; vector boundRad; // squared radii of bounding sphere for each clump FOREACH(const shared_ptr& c, _clumps){ SpherePack c2(*c); c2.translate(c2.midPt()); //recenter clumps.push_back(c2); Real r=0; FOREACH(const Sph& s, c2.pack) r=max(r,s.c.norm()+s.r); boundRad.push_back(r); Vector3r cMn,cMx; c2.aabb(cMn,cMx); // centered at zero now, this gives margin //margins.push_back(periodic?cMx:Vector3r::Zero()); //boxMargins=boxMargins.cwise().max(cMx); FOREACH(const Sph& s, c2.pack) maxR=max(maxR,s.r); // keep track of maximum sphere radius } std::list clumpInfos; Vector3r size=mx-mn; if(periodic)(cellSize=size); const int maxTry=200; int nGen=0; // number of clumps generated // random point coordinate generator, with non-zero margins if aperiodic static boost::minstd_rand randGen(seed!=0?seed:(int)TimingInfo::getNow(/* get the number even if timing is disabled globally */ true)); static boost::variate_generator > rnd(randGen, boost::uniform_real(0,1)); while(nGen(s.c-cInfo.center).squaredNorm()){ detailedCheck=true; break; }} // check sphere-by-sphere, since bounding spheres did overlap if(detailedCheck){ FOREACH(const Sph& s, C.pack) for(int id=cInfo.minId; id<=cInfo.maxId; id++) if((s.c-pack[id].c).squaredNorm()periPtDistSq(s.c,cInfo.center)){ detailedCheck=true; break; } // check sphere-by-sphere if(detailedCheck){ FOREACH(const Sph& s, C.pack) for(int id=cInfo.minId; id<=cInfo.maxId; id++) if(periPtDistSq(s.c,pack[id].c)=(c-pack[j].c).squaredNorm()) goto overlap; // check that we are not over the box boundary // this could be handled by adjusting the position random interval (by taking off the smallest radius in the clump) // but usually the margin band is relatively small and this does not make the code as hairy if((c+r*Vector3r::Ones()).cwise().max(mx)!=mx || (c-r*Vector3r::Ones()).cwise().min(mn)!=mn) goto overlap; } } }else{ for(size_t i=0; i= dr.squaredNorm()) goto overlap; } } } #endif // add the clump, if no collisions /*number clumps consecutively*/ ci.clumpId=nGen; ci.center=pos; ci.rad=rad; ci.minId=pack.size(); ci.maxId=pack.size()+C.pack.size(); FOREACH(const Sph& s, C.pack){ pack.push_back(Sph(s.c,s.r,ci.clumpId)); } clumpInfos.push_back(ci); nGen++; //cerr<<"O"; break; // break away from the try-loop overlap: //cerr<<"."; if(tries++==maxTry){ // last loop if(num>0) LOG_WARN("Exceeded "<=0) return true; } return false; } py::tuple SpherePack::getClumps() const{ std::map clumps; py::list standalone; size_t packSize=pack.size(); for(size_t i=0; i intListPair; FOREACH(const intListPair& c, clumps) clumpList.append(c.second); return py::make_tuple(standalone,clumpList); } trunk-2018.02b/pkg/dem/SpherePack.hpp000066400000000000000000000143671324306050200172650ustar00rootroot00000000000000// © 2009 Václav Šmilauer #pragma once #include #include /*! Class representing geometry of spherical packing, with some utility functions. */ class SpherePack{ // return coordinate wrapped to x0…x1, relative to x0; don't care about period // copied from PeriodicInsertionSortCollider Real cellWrapRel(const Real x, const Real x0, const Real x1){ Real xNorm=(x-x0)/(x1-x0); return (xNorm-floor(xNorm))*(x1-x0); } Real periPtDistSq(const Vector3r& p1, const Vector3r& p2){ Vector3r dr; for(int ax=0; ax<3; ax++) dr[ax]=min(cellWrapRel(p1[ax],p2[ax],p2[ax]+cellSize[ax]),cellWrapRel(p2[ax],p1[ax],p1[ax]+cellSize[ax])); return dr.squaredNorm(); } struct ClumpInfo{ int clumpId; Vector3r center; Real rad; int minId, maxId; }; public: enum {RDIST_RMEAN, RDIST_NUM, RDIST_PSD}; struct Sph{ Vector3r c; Real r; int clumpId; Sph(const Vector3r& _c, Real _r, int _clumpId=-1): c(_c), r(_r), clumpId(_clumpId) {}; boost::python::tuple asTuple() const { if(clumpId<0) return boost::python::make_tuple(c,r); return boost::python::make_tuple(c,r,clumpId); } boost::python::tuple asTupleNoClump() const { return boost::python::make_tuple(c,r); } }; std::vector pack; Vector3r cellSize; Real appliedPsdScaling;//a scaling factor that can be applied on size distribution bool isPeriodic; SpherePack(): cellSize(Vector3r::Zero()), appliedPsdScaling(1.), isPeriodic(0) {}; SpherePack(const boost::python::list& l):cellSize(Vector3r::Zero()){ fromList(l); } // add single sphere void add(const Vector3r& c, Real r){ pack.push_back(Sph(c,r)); } // I/O void fromList(const boost::python::list& l); void fromLists(const vector& centers, const vector& radii); // used as ctor in python boost::python::list toList() const; void fromFile(const string file); void toFile(const string file) const; void fromSimulation(); // random generation; if num<0, insert as many spheres as possible; if porosity>0, recompute meanRadius (porosity>0.65 recommended) and try generating this porosity with num spheres. long makeCloud(Vector3r min, Vector3r max, Real rMean=-1, Real rFuzz=0, int num=-1, bool periodic=false, Real porosity=-1, const vector& psdSizes=vector(), const vector& psdCumm=vector(), bool distributeMass=false, int seed=0, Matrix3r hSize=Matrix3r::Zero()); // return number of piece for x in piecewise function defined by cumm with non-decreasing elements ∈(0,1) // norm holds normalized coordinate withing the piece int psdGetPiece(Real x, const vector& cumm, Real& norm); // function to generate the packing based on a given psd long particleSD(Vector3r min, Vector3r max, Real rMean, bool periodic=false, string name="", int numSph=400, const vector& radii=vector(), const vector& passing=vector(),bool passingIsNotPercentageButCount=false, int seed=0); long particleSD_2d(Vector2r min, Vector2r max, Real rMean, bool periodic=false, string name="", int numSph=400, const vector& radii=vector(), const vector& passing=vector(),bool passingIsNotPercentageButCount=false, int seed=0); long particleSD2(const vector& radii, const vector& passing, int numSph, bool periodic=false, Real cloudPorosity=.8, int seed=0); // interpolate a variable with power distribution (exponent -3) between two margin values, given uniformly distributed x∈(0,1) Real pow3Interp(Real x,Real a,Real b){ return pow(x*(pow(b,-2)-pow(a,-2))+pow(a,-2),-1./2); } // generate packing of clumps, selected with equal probability // periodic boundary is supported long makeClumpCloud(const Vector3r& mn, const Vector3r& mx, const vector >& clumps, bool periodic=false, int num=-1, int seed=0); // periodic repetition void cellRepeat(Vector3i count); void cellFill(Vector3r volume); // spatial characteristics Vector3r dim() const {Vector3r mn,mx; aabb(mn,mx); return mx-mn;} boost::python::tuple aabb_py() const { Vector3r mn,mx; aabb(mn,mx); return boost::python::make_tuple(mn,mx); } void aabb(Vector3r& mn, Vector3r& mx) const { Real inf=std::numeric_limits::infinity(); mn=Vector3r(inf,inf,inf); mx=Vector3r(-inf,-inf,-inf); FOREACH(const Sph& s, pack){ Vector3r r(s.r,s.r,s.r); mn=mn.cwiseMin(s.c-r); mx=mx.cwiseMax(s.c+r); } } Vector3r midPt() const {Vector3r mn,mx; aabb(mn,mx); return .5*(mn+mx);} Real relDensity() const { Real sphVol=0; Vector3r dd=dim(); FOREACH(const Sph& s, pack) sphVol+=pow(s.r,3); sphVol*=(4/3.)*Mathr::PI; return sphVol/(dd[0]*dd[1]*dd[2]); } boost::python::tuple psd(int bins=10, bool mass=false) const; bool hasClumps() const; boost::python::tuple getClumps() const; // transformations void translate(const Vector3r& shift){ FOREACH(Sph& s, pack) s.c+=shift; } void rotate(const Vector3r& axis, Real angle){ if(cellSize!=Vector3r::Zero()) { LOG_WARN("Periodicity reset when rotating periodic packing (non-zero cellSize="<=pack.size()) throw runtime_error("Index "+boost::lexical_cast(idx)+" out of range 0.."+boost::lexical_cast(pack.size()-1)); return pack[idx].asTuple(); } struct _iterator{ const SpherePack& sPack; size_t pos; _iterator(const SpherePack& _sPack): sPack(_sPack), pos(0){} _iterator iter(){ return *this;} boost::python::tuple next(){ if(pos==sPack.pack.size()){ PyErr_SetNone(PyExc_StopIteration); boost::python::throw_error_already_set(); } return sPack.pack[pos++].asTupleNoClump(); } }; SpherePack::_iterator getIterator() const{ return SpherePack::_iterator(*this);}; DECLARE_LOGGER; }; trunk-2018.02b/pkg/dem/SpheresFactory.cpp000066400000000000000000000210611324306050200201610ustar00rootroot00000000000000 #include #include #include #include #include YADE_PLUGIN((SpheresFactory)(CircularFactory)(BoxFactory)); CREATE_LOGGER(SpheresFactory); CREATE_LOGGER(CircularFactory); CREATE_LOGGER(BoxFactory); // initialize random number generator with time seed static boost::minstd_rand randGen(TimingInfo::getNow(/* get the number even if timing is disabled globally */ true)); static boost::variate_generator > randomUnit(randGen, boost::uniform_real(0,1)); void SpheresFactory::pickRandomPosition(Vector3r&,Real){ LOG_FATAL("Engine "<& e, scene->engines){ collider=YADE_PTR_DYN_CAST(e); if(collider) break; } if(!collider) throw runtime_error("SpheresFactory: No Collider instance found in engines (needed for collision detection)."); } goalMass+=massFlowRate*scene->dt; // totalMass that we want to attain in the current step if ((PSDcum.size()>0) and (!PSDuse)) { //Defined, that we will use PSD if ((PSDcum.size() != PSDsizes.size()) and (exactDiam)) { //The number of elements in both arrays should be the same LOG_ERROR("PSDcum and PSDsizes should have an equal number of elements, if exactDiam=True."); throw std::logic_error("PSDcum and PSDsizes should have an equal number of elements, if exactDiam=True."); } else if ((PSDcum.size() != (PSDsizes.size()-1)) and (not(exactDiam))) {//The number of elements in PSDsizes should be on 1 more, than in PSDcum LOG_ERROR("PSDsizes should have a number of elements on 1 more, than PSDcum, if exactDiam=False"); throw std::logic_error("PSDsizes should have a number of elements on 1 more, than PSDcum, if exactDiam=False"); } //Check the correctness of inputted data PSDcum for (unsigned int i=1; i 0; Setting maxMass=-1)"); maxMass = -1; } vector< SpherCoord > justCreatedBodies; // pick random initial velocity Vector3r initVel; if (normalVel.norm()>0) { normalVel.normalize(); initVel = normalVel; } else { initVel = normal; } initVel*=(vMin+randomUnit()*(vMax-vMin)); // TODO: compute from vMin, vMax, vAngle, normal; while(totalMass collidingParticles=collider->probeBoundingVolume(b); //Check, whether newly created sphere collides with existing bodies bool collideWithNewBodies = false; if (justCreatedBodies.size()>0) { //Check, whether newly created sphere collides with bodies from this scope for (unsigned int ii = 0; ii < justCreatedBodies.size(); ii++) { if ((justCreatedBodies.at(ii).c-c).norm() < (justCreatedBodies.at(ii).r+r)) { //Bodies intersect collideWithNewBodies = true; break; } } } if(collidingParticles.size()==0 and not(collideWithNewBodies)) break; #ifdef YADE_DEBUG FOREACH(const Body::id_t& id, collidingParticles) LOG_TRACE(scene->iter<<":"<=0 ? materialId : scene->materials.size()+materialId); if(mId<0 || (size_t) mId>=scene->materials.size()) throw std::invalid_argument(("SpheresFactory: invalid material id "+boost::lexical_cast(materialId)).c_str()); const shared_ptr& material=scene->materials[mId]; shared_ptr b(new Body); shared_ptr sphere(new Sphere); shared_ptr state(material->newAssocState()); sphere->radius=r; state->pos=state->refPos=c; if (color[0]>=0 and color[1]>=0 and color[2]>=0){ sphere->color = color; } state->vel=initVel; Real vol=(4/3.)*Mathr::PI*pow(r,3); state->mass=vol*material->density; state->inertia=(2./5.)*vol*r*r*material->density*Vector3r::Ones(); state->blockedDOFs_vec_set(blockedDOFs); b->shape=sphere; b->state=state; b->material=material; if (mask>0) {b->groupMask=mask;} // insert particle in the simulation scene->bodies->insert(b); ids.push_back(b->getId()); // increment total mass, volume and numparticles we've spit out totalMass+=state->mass; totalVolume+= vol; numParticles++; justCreatedBodies.push_back(SpherCoord(c, r)); if (PSDuse) { //Add newly created "material" into the bin Real summMaterial = 0.0; if (PSDcalculateMass) { PSDCurMean[maxdiffID]=PSDCurMean[maxdiffID]+state->mass; summMaterial = totalMass;} else { PSDCurMean[maxdiffID]=PSDCurMean[maxdiffID]+1; summMaterial = numParticles;} for (unsigned int k=0; k #include #include class SpheresFactory: public GlobalEngine { shared_ptr collider; protected: // Pick random position of a sphere. Should be override in derived engine. virtual void pickRandomPosition(Vector3r&/*picked position*/, Real/*sphere's radius*/); vector PSDCurMean; //Current value of material in each bin vector PSDCurProc; //Current value of material in each bin, in procents vector PSDNeedProc; //Need value of procent in each bin bool PSDuse; //PSD or not public: virtual void action(); struct SpherCoord{ Vector3r c; Real r; SpherCoord(const Vector3r& _c, Real _r){ c=_c; r=_r;} }; DECLARE_LOGGER; YADE_CLASS_BASE_DOC_ATTRS_CTOR(SpheresFactory,GlobalEngine,"Engine for spitting spheres based on mass flow rate, particle size distribution etc. Initial velocity of particles is given by *vMin*, *vMax*, the *massFlowRate* determines how many particles to generate at each step. When *goalMass* is attained or positive *maxParticles* is reached, the engine does not produce particles anymore. Geometry of the region should be defined in a derived engine by overridden SpheresFactory::pickRandomPosition(). \n\nA sample script for this engine is in :ysrc:`scripts/spheresFactory.py`.", ((Real,massFlowRate,NaN,,"Mass flow rate [kg/s]")) ((Real,rMin,NaN,,"Minimum radius of generated spheres (uniform distribution)")) ((Real,rMax,NaN,,"Maximum radius of generated spheres (uniform distribution)")) ((Real,vMin,NaN,,"Minimum velocity norm of generated spheres (uniform distribution)")) ((Real,vMax,NaN,,"Maximum velocity norm of generated spheres (uniform distribution)")) ((Real,vAngle,NaN,,"Maximum angle by which the initial sphere velocity deviates from the normal.")) ((Vector3r,normal,Vector3r(NaN,NaN,NaN),,"Orientation of the region's geometry, direction of particle's velocites if normalVel is not set.")) ((Vector3r,normalVel,Vector3r(NaN,NaN,NaN),,"Direction of particle's velocites.")) ((int,materialId,-1,,"Shared material id to use for newly created spheres (can be negative to count from the end)")) ((int,mask,-1,,"groupMask to apply for newly created spheres ")) ((Vector3r,color,Vector3r(-1,-1,-1),,"Use the color for newly created particles, if specified")) ((vector,ids,,,"ids of created bodies")) ((Real,totalMass,0,,"Mass of spheres that was produced so far. |yupdate|")) ((Real,totalVolume,0,,"Volume of spheres that was produced so far. |yupdate|")) ((Real,goalMass,0,,"Total mass that should be attained at the end of the current step. |yupdate|")) ((int,maxParticles,100,,"The number of particles at which to stop generating new ones regardless of massFlowRate. if maxParticles=-1 - this parameter is ignored .")) ((Real,maxMass,-1,,"Maximal mass at which to stop generating new particles regardless of massFlowRate. if maxMass=-1 - this parameter is ignored.")) ((int,numParticles,0,,"Cummulative number of particles produces so far |yupdate|")) ((int,maxAttempt,5000 ,,"Maximum number of attempts to position a new sphere randomly.")) ((bool,silent,false ,,"If true no complain about excessing maxAttempt but disable the factory (by set massFlowRate=0).")) ((std::string,blockedDOFs,"" ,,"Blocked degress of freedom")) ((vector,PSDsizes,,,"PSD-dispersion, sizes of cells, Diameter [m]")) ((vector,PSDcum,,,"PSD-dispersion, cumulative procent meanings [-]")) ((bool,PSDcalculateMass,true,,"PSD-Input is in mass (true), otherwise the number of particles will be considered.")) ((bool,stopIfFailed,true,,"If true, the SpheresFactory stops (sets massFlowRate=0), when maximal number of attempts to insert particle exceed.")) ((bool,exactDiam,true,,"If true, the particles only with the defined in PSDsizes diameters will be created. Otherwise the diameter will be randomly chosen in the range [PSDsizes[i-1]:PSDsizes[i]], in this case the length of PSDsizes should be more on 1, than the length of PSDcum.")), PSDuse=false; ); }; REGISTER_SERIALIZABLE(SpheresFactory); class CircularFactory: public SpheresFactory { protected: virtual void pickRandomPosition(Vector3r&, Real); public: virtual ~CircularFactory(){}; DECLARE_LOGGER; YADE_CLASS_BASE_DOC_ATTRS(CircularFactory,SpheresFactory,"Circular geometry of the SpheresFactory region. It can be disk (given by radius and center), or cylinder (given by radius, length and center).", ((Real,radius,NaN,,"Radius of the region")) ((Real,length,0,,"Length of the cylindrical region (0 by default)")) ((Vector3r,center,Vector3r(NaN,NaN,NaN),,"Center of the region")) ); }; REGISTER_SERIALIZABLE(CircularFactory); class BoxFactory: public SpheresFactory { protected: virtual void pickRandomPosition(Vector3r&, Real); public: virtual ~BoxFactory(){}; DECLARE_LOGGER; YADE_CLASS_BASE_DOC_ATTRS(BoxFactory,SpheresFactory,"Box geometry of the SpheresFactory region, given by extents and center", ((Vector3r,extents,Vector3r(NaN,NaN,NaN),,"Extents of the region")) ((Vector3r,center,Vector3r(NaN,NaN,NaN),,"Center of the region")) ); }; REGISTER_SERIALIZABLE(BoxFactory); trunk-2018.02b/pkg/dem/TesselationWrapper.cpp000066400000000000000000000403211324306050200210530ustar00rootroot00000000000000/************************************************************************* * Copyright (C) 2008 by Bruno Chareyre * * bruno.chareyre@hmg.inpg.fr * * * * This program is free software; it is licensed under the terms of the * * GNU General Public License v2 or later. See file LICENSE for details. * *************************************************************************/ #ifdef YADE_CGAL #include #include "TesselationWrapper.hpp" #include #include #include YADE_PLUGIN((TesselationWrapper)); CREATE_LOGGER(TesselationWrapper); // helper macro do assign Matrix3r values to subarrays #define MATRIX3R_TO_NUMPY(mat,arr) arr[0]=mat(0,0);arr[1]=mat(0,1);arr[2]=mat(0,2);arr[3]=mat(1,0);arr[4]=mat(1,1);arr[5]=mat(1,2);arr[6]=mat(2,0);arr[7]=mat(2,1);arr[8]=mat(2,2); //spatial sort traits to use with a pair of CGAL::sphere pointers and integer. //template struct RTraits_for_spatial_sort : public CGT::SimpleTriangulationTypes::RTriangulation::Geom_traits { //typedef typename _Triangulation::Geom_traits Gt; typedef CGT::SimpleTriangulationTypes::RTriangulation::Geom_traits Gt; typedef std::pair Point_3; struct Less_x_3 { bool operator()(const Point_3& p,const Point_3& q) const { return Gt::Less_x_3()( p.first->point() , q.first->point() ); } }; struct Less_y_3 { bool operator()(const Point_3& p,const Point_3& q) const { return Gt::Less_y_3()( p.first->point(), q.first->point()); } }; struct Less_z_3 { bool operator()(const Point_3& p,const Point_3& q) const { return Gt::Less_z_3()( p.first->point(), q.first->point()); } }; Less_x_3 less_x_3_object() const {return Less_x_3();} Less_y_3 less_y_3_object() const {return Less_y_3();} Less_z_3 less_z_3_object() const {return Less_z_3();} }; //function inserting points into a triangulation (where YADE::Sphere is converted to CGT::Sphere) //and setting the info field to the bodies id. //Possible improvements : use bodies pointers to avoid one copy, use aabb's lists to replace the shuffle/sort part // template void build_triangulation_with_ids(const shared_ptr& bodies, TesselationWrapper &TW, bool reset=true) { if (reset) TW.clear(); typedef SimpleTesselation::RTriangulation RTriangulation; SimpleTesselation& Tes = *(TW.Tes); RTriangulation& T = Tes.Triangulation(); std::vector spheres; std::vector > pointsPtrs; spheres.reserve(bodies->size()); pointsPtrs.reserve(bodies->size()); BodyContainer::iterator biBegin = bodies->begin(); BodyContainer::iterator biEnd = bodies->end(); BodyContainer::iterator bi = biBegin; Body::id_t Ng = 0; Body::id_t& MaxId=Tes.maxId; TW.mean_radius = 0; int nonSpheres =0; shared_ptr sph (new Sphere); int Sph_Index = sph->getClassIndexStatic(); Scene* scene = Omega::instance().getScene().get(); for (; bi!=biEnd ; ++bi) { if ( (*bi)->shape->getClassIndex() == Sph_Index ) { const Sphere* s = YADE_CAST ((*bi)->shape.get()); //FIXME: is the scene periodicity verification useful in the next line ? Tesselation seems to work in both periodic and non-periodic conditions with "scene->cell->wrapShearedPt((*bi)->state->pos)". I keep the verification to be consistent with all other uses of "wrapShearedPt" function. const Vector3r& pos = scene->isPeriodic ? scene->cell->wrapShearedPt((*bi)->state->pos) : (*bi)->state->pos; const Real rad = s->radius; CGT::Sphere sp(CGT::Point(pos[0],pos[1],pos[2]),rad*rad); spheres.push_back(sp); pointsPtrs.push_back(std::make_pair(&(spheres[Ng]/*.point()*/),(*bi)->getId())); TW.Pmin = CGT::Point(min(TW.Pmin.x(),pos.x()-rad),min(TW.Pmin.y(), pos.y()-rad),min(TW.Pmin.z(), pos.z()-rad)); TW.Pmax = CGT::Point(max(TW.Pmax.x(),pos.x()+rad),max(TW.Pmax.y(),pos.y()+rad),max(TW.Pmax.z(),pos.z()+rad)); Ng++; TW.mean_radius += rad; MaxId = max(MaxId,(*bi)->getId()); } else ++nonSpheres; } TW.mean_radius /= Ng; TW.rad_divided = true; spheres.resize(Ng); pointsPtrs.resize(Ng); Tes.vertexHandles.resize(MaxId+1+max(0,6-nonSpheres),NULL);//+6 extra slots max for adding boundaries latter Tes.redirected = 1; std::random_shuffle(pointsPtrs.begin(), pointsPtrs.end()); spatial_sort(pointsPtrs.begin(),pointsPtrs.end(), RTraits_for_spatial_sort()/*, CGT::RTriangulation::Weighted_point*/); RTriangulation::Cell_handle hint; TW.n_spheres = 0; for (std::vector >::const_iterator p = pointsPtrs.begin();p != pointsPtrs.end(); ++p) { RTriangulation::Locate_type lt; RTriangulation::Cell_handle c; int li, lj; c = T.locate(* (p->first), lt, li, lj, hint); RTriangulation::Vertex_handle v = T.insert(*(p->first),lt,c,li,lj); if (v==RTriangulation::Vertex_handle()) hint=c; else { v->info().setId((const unsigned int) p->second); //Vh->info().isFictious = false;//false is the default Tes.maxId = std::max(Tes.maxId,(int) p->second); Tes.vertexHandles[p->second]=v; hint=v->cell(); ++TW.n_spheres; } } //cerr << " loaded : " << Ng<<", triangulated : "<Max_id() >= id) ? Tes->Volume(id) : -1;} bool TesselationWrapper::insert(double x, double y, double z, double rad, unsigned int id) { checkMinMax(x,y,z,rad); mean_radius += rad; ++n_spheres; return (Tes->insert(x,y,z,rad,id)!=NULL); } void TesselationWrapper::checkMinMax(double x, double y, double z, double rad) { using std::min; using std::max; Pmin = CGT::Point(min(Pmin.x(), x-rad), min(Pmin.y(), y-rad), min(Pmin.z(), z-rad)); Pmax = CGT::Point(max(Pmax.x(), x+rad), max(Pmax.y(), y+rad), max(Pmax.z(), z+rad)); } bool TesselationWrapper::move(double x, double y, double z, double rad, unsigned int id) { checkMinMax(x,y,z,rad); if (Tes->move(x,y,z,rad,id)!=NULL) return true; else { std::cerr << "Tes->move(x,y,z,rad,id)==NULL" << std::endl; return false; } } void TesselationWrapper::computeTesselation(void) { if (!rad_divided) { mean_radius /= n_spheres; rad_divided = true; } Tes->compute(); } void TesselationWrapper::computeTesselation(double pminx, double pmaxx, double pminy, double pmaxy, double pminz, double pmaxz) { addBoundingPlanes(pminx, pmaxx, pminy, pmaxy, pminz, pmaxz); computeTesselation(); } void TesselationWrapper::computeVolumes(void) { if (!bounded) addBoundingPlanes(); computeTesselation(); Tes->computeVolumes(); } unsigned int TesselationWrapper::NumberOfFacets(bool initIters) { if (initIters) InitIter(); return Tes->Triangulation().number_of_finite_edges(); } void TesselationWrapper::InitIter(void) { facet_begin = Tes->Triangulation().finite_edges_begin(); facet_end = Tes->Triangulation().finite_edges_end(); facet_it = facet_begin; } bool TesselationWrapper::nextFacet(std::pair& facet) { if (facet_end==facet_it) return false; facet.first = facet_it->first->vertex(facet_it->second)->info().id(); facet.second = facet_it->first->vertex((facet_it)->third)->info().id(); ++facet_it; return true; } void TesselationWrapper::addBoundingPlanes(double pminx, double pmaxx, double pminy, double pmaxy, double pminz, double pmaxz) { if (!bounded) { if (!rad_divided) { mean_radius /= n_spheres; rad_divided = true; } // Insert the 6 additional vertices in the right place (usually they will be ids 0 to 5 when walls/facets/boxes are used, but not always) // append them at the end if the initial list is full int freeIds [6]; int i=0; for (int k=0; k<6; k++) { while (Tes->vertexHandles[i]!=NULL) ++i; freeIds[k] = i;} // now insert Tes->vertexHandles[freeIds[0]]=Tes->insert(0.5*(pminx+pmaxx),pminy-far*(pmaxx-pminx),0.5*(pmaxz+pminz),far*(pmaxx-pminx)+thickness,freeIds[0],true); Tes->vertexHandles[freeIds[1]]=Tes->insert(0.5*(pminx+pmaxx), pmaxy+far*(pmaxx-pminx),0.5*(pmaxz+pminz),far*(pmaxx-pminx)+thickness, freeIds[1], true); Tes->vertexHandles[freeIds[2]]=Tes->insert(pminx-far*(pmaxy-pminy), 0.5*(pmaxy+pminy), 0.5*(pmaxz+pminz), far*(pmaxy-pminy)+thickness, freeIds[2], true); Tes->vertexHandles[freeIds[3]]=Tes->insert(pmaxx+far*(pmaxy-pminy), 0.5*(pmaxy+pminy), 0.5*(pmaxz+pminz), far*(pmaxy-pminy)+thickness, freeIds[3], true); Tes->vertexHandles[freeIds[4]]=Tes->insert(0.5*(pminx+pmaxx), 0.5*(pmaxy+pminy), pminz-far*(pmaxy-pminy), far*(pmaxy-pminy)+thickness, freeIds[4], true); Tes->vertexHandles[freeIds[5]]=Tes->insert(0.5*(pminx+pmaxx), 0.5*(pmaxy+pminy), pmaxz+far*(pmaxy-pminy), far*(pmaxy-pminy)+thickness, freeIds[5], true); bounded = true; } } void TesselationWrapper::addBoundingPlanes(void) {addBoundingPlanes(Pmin.x(),Pmax.x(),Pmin.y(),Pmax.y(),Pmin.z(),Pmax.z());} // { // if (!bounded) { // if (!rad_divided) { // mean_radius /= n_spheres; // rad_divided = true; // } // double FAR = 10000; // //Add big bounding spheres with isFictious=true // Tes->vertexHandles[0]=Tes->insert(0.5*(Pmin.x()+Pmax.x()),Pmin.y()-FAR*(Pmax.x()-Pmin.x()),0.5*(Pmax.z()+Pmin.z()),FAR*(Pmax.x()-Pmin.x()), 0, true); // Tes->vertexHandles[1]=Tes->insert(0.5*(Pmin.x()+Pmax.x()),Pmax.y()+FAR*(Pmax.x()-Pmin.x()),0.5*(Pmax.z()+Pmin.z()),FAR*(Pmax.x()-Pmin.x()), 1, true); // Tes->vertexHandles[2]=Tes->insert(Pmin.x()-FAR*(Pmax.y()-Pmin.y()),0.5*(Pmax.y()+Pmin.y()),0.5*(Pmax.z()+Pmin.z()),FAR*(Pmax.y()-Pmin.y()),2, true); // Tes->vertexHandles[3]=Tes->insert(Pmax.x()+FAR*(Pmax.y()-Pmin.y()),0.5*(Pmax.y()+Pmin.y()),0.5*(Pmax.z()+Pmin.z()),FAR*(Pmax.y()-Pmin.y()),3,true); // Tes->vertexHandles[4]=Tes->insert(0.5*(Pmin.x()+Pmax.x()),0.5*(Pmax.y()+Pmin.y()),Pmin.z()-FAR*(Pmax.y()-Pmin.y()),FAR*(Pmax.y()-Pmin.y()),4,true); // Tes->vertexHandles[5]=Tes->insert(0.5*(Pmin.x()+Pmax.x()),0.5*(Pmax.y()+Pmin.y()),Pmax.z()+FAR*(Pmax.y()-Pmin.y()),FAR*(Pmax.y()-Pmin.y()),5, true); // bounded = true; // } // } void TesselationWrapper::RemoveBoundingPlanes(void) { Tes->remove(0); Tes->remove(1); Tes->remove(2); Tes->remove(3); Tes->remove(4); Tes->remove(5); Pmin = CGT::Point(inf, inf, inf); Pmax = CGT::Point(-inf, -inf, -inf); mean_radius = 0; rad_divided = false; bounded = false; } void TesselationWrapper::setState (bool state){ mma.setState(state ? 2 : 1);} void TesselationWrapper::loadState (string filename, bool stateNumber, bool bz2){ CGT::TriaxialState& TS = stateNumber? *(mma.analyser->TS1) :*( mma.analyser->TS0); TS.from_file(filename.c_str(),bz2); } void TesselationWrapper::saveState (string filename, bool stateNumber, bool bz2){ CGT::TriaxialState& TS = stateNumber? *(mma.analyser->TS1) :*( mma.analyser->TS0); TS.to_file(filename.c_str(),bz2); } void TesselationWrapper::defToVtkFromStates (string inputFile1, string inputFile2, string outputFile, bool bz2){ mma.analyser->DefToFile(inputFile1.c_str(),inputFile2.c_str(),outputFile.c_str(),bz2); } void createSphere(shared_ptr& body, Vector3r position, Real radius, bool big, bool dynamic ) { body = shared_ptr(new Body); body->groupMask=2; shared_ptr iSphere(new Sphere); body->state->blockedDOFs=State::DOF_NONE; body->state->pos=position; iSphere->radius = radius; body->shape = iSphere; } void TesselationWrapper::defToVtkFromPositions (string inputFile1, string inputFile2, string outputFile, bool bz2){ SpherePack sp1, sp2; sp1.fromFile(inputFile1); sp2.fromFile(inputFile2); size_t imax=sp1.pack.size(); if (imax!=sp2.pack.size()) LOG_ERROR("The files have different numbers of spheres"); shared_ptr body; scene->bodies->clear(); for(size_t i=0; ibodies->insert(body); } mma.setState(1); scene->bodies->clear(); for(size_t i=0; ibodies->insert(body); } mma.setState(2); mma.analyser->DefToFile(outputFile.c_str()); } void TesselationWrapper::defToVtk (string outputFile){ mma.analyser->DefToFile(outputFile.c_str()); } boost::python::dict TesselationWrapper::getVolPoroDef(bool deformation) { delete Tes; CGT::TriaxialState* ts; if (deformation){//use the final state to compute volumes /*const vector& def =*/ mma.analyser->computeParticlesDeformation(); Tes = &mma.analyser->TS1->tesselation(); ts = mma.analyser->TS1; } else { Tes = &mma.analyser->TS0->tesselation();//no reason to use the final state if we don't want to compute deformations, keep using the initial ts = mma.analyser->TS0;} RTriangulation& Tri = Tes->Triangulation(); Pmin=ts->box.base; Pmax=ts->box.sommet; //if (!scene->isPeriodic) addBoundingPlanes(); computeVolumes(); int bodiesDim = Tes->Max_id() + 1; //=scene->bodies->size(); cerr<<"bodiesDim="< id(dim1); numpy_boost vol(dim1); numpy_boost poro(dim1); numpy_boost def(dim2); //FOREACH(const shared_ptr& b, *scene->bodies){ for (RTriangulation::Finite_vertices_iterator V_it = Tri.finite_vertices_begin(); V_it != Tri.finite_vertices_end(); V_it++) { //id[]=V_it->info().id() //if(!b) continue; const Body::id_t id = V_it->info().id(); Real sphereVol = 4.188790 * std::pow ( ( V_it->point().weight() ),1.5 );// 4/3*PI*R³ = 4.188...*R³ vol[id]=V_it->info().v(); poro[id]=(V_it->info().v() - sphereVol)/V_it->info().v(); if (deformation) MATRIX3R_TO_NUMPY(mma.analyser->ParticleDeformation[id],def[id]); //cerr << V_it->info().v()<<" "< faces; Tes->setAlphaFaces(faces,alpha); boost::python::list ret; for (auto f=faces.begin();f!=faces.end();f++) ret.append(boost::python::make_tuple(Vector3i(f->ids[0],f->ids[1],f->ids[2]),makeVector3r(f->normal))); return ret; } boost::python::list TesselationWrapper::getAlphaCaps(double alpha, double shrinkedAlpha, bool fixedAlpha) { vector caps; Tes->setExtendedAlphaCaps(caps,alpha,shrinkedAlpha,fixedAlpha); boost::python::list ret; for (auto f=caps.begin();f!=caps.end();f++) ret.append(boost::python::make_tuple(f->id,makeVector3r(f->normal))); // cerr<<"number of caps="< segments=Tes->getExtendedAlphaGraph(alpha,shrinkedAlpha,fixedAlpha); boost::python::list ret; for (auto f=segments.begin();f!=segments.end();f++) ret.append(*f); return ret; } boost::python::list TesselationWrapper::getAlphaVertices(double alpha) { vector vertices=Tes->getAlphaVertices(alpha); boost::python::list ret; for (auto f=vertices.begin();f!=vertices.end();f++) ret.append(*f); return ret; } #endif /* YADE_CGAL */ trunk-2018.02b/pkg/dem/TesselationWrapper.hpp000066400000000000000000000267171324306050200210750ustar00rootroot00000000000000/************************************************************************* * Copyright (C) 2008 by Bruno Chareyre * * bruno.chareyre@hmg.inpg.fr * * * * This program is free software; it is licensed under the terms of the * * GNU General Public License v2 or later. See file LICENSE for details. * *************************************************************************/ #ifdef YADE_CGAL #pragma once #include #include #include #include #include #include /*! \class TesselationWrapper * \brief Handle the triangulation of spheres in a scene, build tesselation on request, and give access to computed quantities : currently volume and porosity of each Voronoï sphere. * More accessors in course of implementation. Feel free to suggest new ones. * * Example usage script : * *tt=TriaxialTest() *tt.generate("test.yade") *O.load("test.yade") *O.run(100,True) *TW=TesselationWrapper() *TW.triangulate() #compute regular Delaunay triangulation, don't construct tesselation *TW.computeVolumes() #will silently tesselate the packing *TW.volume(10) #get volume associated to sphere of id 10 * */ class TesselationWrapper : public GlobalEngine{ public: typedef CGT::_Tesselation Tesselation; typedef Tesselation::RTriangulation RTriangulation; typedef Tesselation::VertexInfo VertexInfo; typedef Tesselation::CellInfo CellInfo; typedef RTriangulation::Finite_edges_iterator FiniteEdgesIterator; typedef Tesselation::AlphaFace AlphaFace; typedef Tesselation::AlphaCap AlphaCap; Tesselation* Tes; double mean_radius, inf; bool rad_divided; bool bounded; CGT::Point Pmin; CGT::Point Pmax; ~TesselationWrapper(); /// Insert a sphere, "id" will be used by some getters to retrieve spheres bool insert(double x, double y, double z, double rad, unsigned int id); /// A faster version of insert, inserting all spheres in scene (first erasing current triangulation if reset=true) void insertSceneSpheres(bool reset=true); /// Move one sphere to the new position (x,y,z) and maintain triangulation (invalidates the tesselation) bool move (double x, double y, double z, double rad, unsigned int id); void checkMinMax(double x, double y, double z, double rad);//for experimentation purpose /// Reset the triangulation void clear(void); void clear2(void); /// Add axis aligned bounding planes (modelised as spheres with (almost) infinite radius) void addBoundingPlanes (void); /// Force boudaries at positions not equal to precomputed ones void addBoundingPlanes(double pminx, double pmaxx, double pminy, double pmaxy, double pminz, double pmaxz); void RemoveBoundingPlanes (void); ///compute voronoi centers then stop (don't compute anything else) void computeTesselation (void); void computeTesselation( double pminx, double pmaxx, double pminy, double pmaxy, double pminz, double pmaxz); void testAlphaShape(double alpha) {Tes->testAlphaShape(alpha);} boost::python::list getAlphaFaces(double alpha); boost::python::list getAlphaCaps(double alpha, double shrinkedAlpha, bool fixedAlpha); boost::python::list getAlphaVertices(double alpha); boost::python::list getAlphaGraph(double alpha, double shrinkedAlpha, bool fixedAlpha); ///compute Voronoi vertices + volumes of all cells ///use computeTesselation to force update, e.g. after spheres positions have been updated void computeVolumes (void); void computeDeformations (void) {mma.analyser->computeParticlesDeformation();} ///Get volume of the sphere inserted with indentifier "id"" double Volume (unsigned int id); double deformation (unsigned int id,unsigned int i,unsigned int j) { if (!mma.analyser->ParticleDeformation.size()) {LOG_ERROR("compute deformations first"); return 0;} if (mma.analyser->ParticleDeformation.size()3 || j<1 || j>3) {LOG_ERROR("tensor index must be between 1 and 3"); return 0;} return mma.analyser->ParticleDeformation[id](i,j);} /// number of facets in the tesselation (finite branches of the triangulation) unsigned int NumberOfFacets(bool initIters=false); /// set first and last facets, set facet_it = facet_begin void InitIter(void); /// set facet = next pair (body1->id,body2->id), returns facet_it==facet_end bool nextFacet (std::pair& facet); /// make the current state the initial (0) or final (1) configuration for the definition of displacement increments, use only state=0 if you just want to get only volmumes and porosity void setState (bool state=0); void loadState (string fileName, bool stateNumber=0, bool bz2=false); void saveState (string fileName, bool stateNumber=0, bool bz2=false); /// read two state files and write per-particle deformation to a vtk file. The second variant uses existing states. void defToVtkFromStates (string inputFile1, string inputFile2, string outputFile="def.vtk", bool bz2=false); void defToVtkFromPositions (string inputFile1, string inputFile2, string outputFile="def.vtk", bool bz2=false); void defToVtk (string outputFile="def.vtk"); /// return python array containing voronoi volumes, per-particle porosity, and optionaly per-particle deformation, if states 0 and 1 have been assigned boost::python::dict getVolPoroDef(bool deformation);//FIXME ; unexplained crash for now public: /// edge iterators are used for returning tesselation "facets", i.e. spheres with a common branch in the triangulation, convert CGAL::edge to int pair (b1->id, b2->id) FiniteEdgesIterator facet_begin; FiniteEdgesIterator facet_end; FiniteEdgesIterator facet_it; MicroMacroAnalyser mma; YADE_CLASS_BASE_DOC_ATTRS_CTOR_PY(TesselationWrapper,GlobalEngine,"Handle the triangulation of spheres in a scene, build tesselation on request, and give access to computed quantities (see also the `dedicated section in user manual `_). The calculation of microstrain is explained in [Catalano2014a]_ \n\nSee example usage in script example/tesselationWrapper/tesselationWrapper.py.\n\nBelow is an output of the :yref:`defToVtk` function visualized with paraview (in this case Yade's TesselationWrapper was used to process experimental data obtained on sand by Edward Ando at Grenoble University, 3SR lab.)\n\n.. figure:: fig/localstrain.*", ((unsigned int,n_spheres,0,,"|ycomp|")) ((Real,far,10000.,,"Defines the radius of the large virtual spheres used to define nearly flat boundaries around the assembly. The radius will be the (scene's) bounding box size multiplied by 'far'. Higher values will minimize the error theoretically (since the infinite sphere really defines a plane), but it may increase numerical errors at some point. The default should give a resonable compromize.")) ,/*ctor*/ Tes = new Tesselation; clear(); facet_begin = Tes->Triangulation().finite_edges_begin(); facet_end = Tes->Triangulation().finite_edges_end(); facet_it = Tes->Triangulation().finite_edges_begin(); inf=1e10; mma.analyser->SetConsecutive(false); ,/*py*/ .def("triangulate",&TesselationWrapper::insertSceneSpheres,(boost::python::arg("reset")=true),"triangulate spheres of the packing") .def("setState",&TesselationWrapper::setState,(boost::python::arg("state")=0),"Make the current state of the simulation the initial (0) or final (1) configuration for the definition of displacement increments, use only state=0 if you just want to get volmumes and porosity.") .def("loadState",&TesselationWrapper::loadState,(boost::python::arg("inputFile")="state",boost::python::arg("state")=0,boost::python::arg("bz2")=true),"Load a file with positions to define state 0 or 1.") .def("saveState",&TesselationWrapper::saveState,(boost::python::arg("outputFile")="state",boost::python::arg("state")=0,boost::python::arg("bz2")=true),"Save a file with positions, can be later reloaded in order to define state 0 or 1.") .def("volume",&TesselationWrapper::Volume,(boost::python::arg("id")=0),"Returns the volume of Voronoi's cell of a sphere.") .def("defToVtk",&TesselationWrapper::defToVtk,(boost::python::arg("outputFile")="def.vtk"),"Write local deformations in vtk format from states 0 and 1.") .def("defToVtkFromStates",&TesselationWrapper::defToVtkFromStates,(boost::python::arg("input1")="state1",boost::python::arg("input2")="state2",boost::python::arg("outputFile")="def.vtk",boost::python::arg("bz2")=true),"Write local deformations in vtk format from state files (since the file format is very special, consider using defToVtkFromPositions if the input files were not generated by TesselationWrapper).") .def("defToVtkFromPositions",&TesselationWrapper::defToVtkFromPositions,(boost::python::arg("input1")="pos1",boost::python::arg("input2")="pos2",boost::python::arg("outputFile")="def.vtk",boost::python::arg("bz2")=false),"Write local deformations in vtk format from positions files (one sphere per line, with x,y,z,rad separated by spaces).") .def("computeVolumes",&TesselationWrapper::computeVolumes,"compute volumes of all Voronoi's cells.") .def("getVolPoroDef",&TesselationWrapper::getVolPoroDef,(boost::python::arg("deformation")=false),"Return a table with per-sphere computed quantities. Include deformations on the increment defined by states 0 and 1 if deformation=True (make sure to define states 0 and 1 consistently).") .def("computeDeformations",&TesselationWrapper::computeDeformations,"compute per-particle deformation. Get it with :yref:`TesselationWrapper::deformation` (id,i,j).") .def("deformation",&TesselationWrapper::deformation,(boost::python::arg("id"),boost::python::arg("i"),boost::python::arg("j")),"Get particle deformation") .def("testAlphaShape",&TesselationWrapper::testAlphaShape,(boost::python::arg("alpha")=0),"transitory function, testing AlphaShape feature") .def("getAlphaFaces",&TesselationWrapper::getAlphaFaces,(boost::python::arg("alpha")=0),"Get the list of alpha faces for a given alpha. If alpha is not specified or null the minimum alpha resulting in a unique connected domain is used") .def("getAlphaCaps",&TesselationWrapper::getAlphaCaps,(boost::python::arg("alpha")=0,boost::python::arg("shrinkedAlpha")=0,boost::python::arg("fixedAlpha")=false),"Get the list of area vectors for the polyhedral caps associated to boundary particles ('extended' alpha-contour). If alpha is not specified or null the minimum alpha resulting in a unique connected domain is used") .def("getAlphaGraph",&TesselationWrapper::getAlphaGraph,(boost::python::arg("alpha")=0,boost::python::arg("shrinkedAlpha")=0,boost::python::arg("fixedAlpha")=false),"Get the list of area vectors for the polyhedral caps associated to boundary particles ('extended' alpha-contour). If alpha is not specified or null the minimum alpha resulting in a unique connected domain is used") .def("getAlphaVertices",&TesselationWrapper::getAlphaVertices,(boost::python::arg("alpha")=0),"Get the list of 'alpha' bounding spheres for a given alpha. If alpha is not specified or null the minimum alpha resulting in a unique connected domain is used. This function is generating a new alpha shape for each call, not to be used intensively.") ); DECLARE_LOGGER; }; REGISTER_SERIALIZABLE(TesselationWrapper); //} // namespace CGT #endif /* YADE_CGAL */ trunk-2018.02b/pkg/dem/Tetra.cpp000066400000000000000000001306351324306050200163070ustar00rootroot00000000000000// © 2007 Václav Šmilauer // © 2013 Jan Stránský #include"Tetra.hpp" #include #include #include #include #include #include #ifdef YADE_CGAL #include #endif YADE_PLUGIN(/* self-contained in hpp: */ (Tetra) (TTetraGeom) (TTetraSimpleGeom) (Bo1_Tetra_Aabb) /* some code in cpp (this file): */ (TetraVolumetricLaw) (Ig2_Tetra_Tetra_TTetraGeom) #ifdef YADE_CGAL (Ig2_Tetra_Tetra_TTetraSimpleGeom) (Law2_TTetraSimpleGeom_NormPhys_Simple) #endif #ifdef YADE_OPENGL (Gl1_Tetra) #endif ); Tetra::~Tetra(){} TTetraGeom::~TTetraGeom(){} TTetraSimpleGeom::~TTetraSimpleGeom(){} void Bo1_Tetra_Aabb::go(const shared_ptr& ig, shared_ptr& bv, const Se3r& se3, const Body*){ Tetra* t=static_cast(ig.get()); if(!bv){ bv=shared_ptr(new Aabb); } Aabb* aabb=static_cast(bv.get()); //Quaternionr invRot=se3.orientation.conjugate(); //The variable set but not used Vector3r v_g[4]; for(int i=0; i<4; i++) v_g[i]=se3.orientation*t->v[i]; // vertices in global coordinates #define __VOP(op,ix) op(v_g[0][ix],op(v_g[1][ix],op(v_g[2][ix],v_g[3][ix]))) aabb->min=se3.position+Vector3r(__VOP(std::min,0),__VOP(std::min,1),__VOP(std::min,2)); aabb->max=se3.position+Vector3r(__VOP(std::max,0),__VOP(std::max,1),__VOP(std::max,2)); #undef __VOP } #ifdef YADE_CGAL const int Ig2_Tetra_Tetra_TTetraSimpleGeom::psMap[4][3] = { // segments of point {0,2,3}, {0,1,4}, {1,2,5}, {3,4,5} }; const int Ig2_Tetra_Tetra_TTetraSimpleGeom::ptMap[4][3] = { // triangles of point {0,1,3}, {0,1,2}, {1,2,3}, {0,2,3} }; const int Ig2_Tetra_Tetra_TTetraSimpleGeom::stMap[6][2] = { // triangles of segments {0,1}, {1,2}, {1,3}, {0,3}, {0,2}, {2,3} }; const int Ig2_Tetra_Tetra_TTetraSimpleGeom::ppsMap[4][4] = { // point-point pair to segment {-1, 0, 2, 3}, { 0,-1, 1, 4}, { 2, 1,-1, 5}, { 3, 4, 5,-1} }; const int Ig2_Tetra_Tetra_TTetraSimpleGeom::tsMap[4][3] = { // segmnts of triangle {0,3,4}, {0,1,2}, {1,4,5}, {2,3,5} }; const int Ig2_Tetra_Tetra_TTetraSimpleGeom::sstMap[6][6] = { // segment-segment pair to triangle {-1, 1, 1, 0, 0,-1}, { 1,-1, 1,-1, 2, 2}, { 1, 1,-1, 3,-1, 3}, { 0,-1, 3,-1, 0, 3}, { 0, 2,-1, 0,-1, 2}, {-1, 2, 3, 3, 2,-1} }; bool Ig2_Tetra_Tetra_TTetraSimpleGeom::checkVertexToTriangleCase( const Triangle tA[4], const Point pB[4], const Segment sB[6], Vector3r& normal, Vector3r& contactPoint, Real& penetrationVolume) { for (int i=0; i<4; i++) { // loop over triangles 1 const Triangle& t = tA[i]; // choose triangle 1 for (int j=0; j<4; j++) { // loop over vertices 2 const Point& p = pB[j]; // choose vertex 2 // choose edges posessing p const Segment& sa = sB[psMap[j][0]]; const Segment& sb = sB[psMap[j][1]]; const Segment& sc = sB[psMap[j][2]]; if ( !(do_intersect(t,sa) && do_intersect(t,sb) && do_intersect(t,sc)) ) { continue; }// if all edges intersect with t // evaluate the points CGAL::Object oa = intersection(t,sa); const Point* pa = CGAL::object_cast(&oa); CGAL::Object ob = intersection(t,sb); const Point* pb = CGAL::object_cast(&ob); CGAL::Object oc = intersection(t,sc); const Point* pc = CGAL::object_cast(&oc); if ( !(pa && pb && pc) ) { continue; } // check that the intrsection really exists Vector_3 n = CGAL::normal(t[0],t[1],t[2]); // normal of triangle for (int k=0; k<3; k++) { normal[k] = n[k]; // sets normal of contact = nornal of triangle // contact point is center of mass of overlaping tetrahedron contactPoint[k] = .25*(p[k]+pa->operator[](k)+pb->operator[](k)+pc->operator[](k)); } normal.normalize(); const Point* v[4] = {&p,pa,pb,pc}; penetrationVolume = TetrahedronVolume(v); Real vol = TetrahedronVolume(pB); if (penetrationVolume > .5*vol) { penetrationVolume = vol-penetrationVolume; } return true; } } return false; } bool Ig2_Tetra_Tetra_TTetraSimpleGeom::checkEdgeToEdgeCase( const Segment sA[6], const Segment sB[6], const Triangle tA[4], const Triangle tB[4], Vector3r& normal, Vector3r& contactPoint, Real& penetrationVolume) { for (int i=0; i<6; i++) { const Segment& sa = sA[i]; const Triangle& ta0 = tA[stMap[i][0]]; const Triangle& ta1 = tA[stMap[i][1]]; for (int j=0; j<6; j++) { const Segment sb = sB[j]; if ( !(do_intersect(sb,ta0) && do_intersect(sb,ta1) ) ) { continue; } const Triangle tb0 = tB[stMap[j][0]]; const Triangle tb1 = tB[stMap[j][1]]; if ( !(do_intersect(sa,tb0) && do_intersect(sa,tb1) ) ) { continue; } CGAL::Object osb1 = intersection(sb,ta0); CGAL::Object osb2 = intersection(sb,ta1); CGAL::Object osa1 = intersection(sa,tb0); CGAL::Object osa2 = intersection(sa,tb1); const Point* psb1 = CGAL::object_cast(&osb1); const Point* psb2 = CGAL::object_cast(&osb2); const Point* psa1 = CGAL::object_cast(&osa1); const Point* psa2 = CGAL::object_cast(&osa2); if ( !(psb1 && psb2 && psa1 && psa2) ) { continue; } Vector_3 n = CGAL::cross_product(sa.to_vector(),sb.to_vector()); Vector3r nApprox; for (int k=0; k<3; k++) { normal[k] = n[k]; #define OP(p) p->operator[](k) nApprox[k] = .5*(OP(psa1)+OP(psa2)) - .5*(OP(psb1)+OP(psb2)); contactPoint[k] = .25*(OP(psa1)+OP(psa2)+OP(psb1)+OP(psb2)); #undef OP } if ( nApprox.dot(normal) < 0 ) { normal *= (Real)-1.; } normal.normalize(); const Point* p[4] = {psb1,psb2,psa1,psa2}; penetrationVolume = TetrahedronVolume(p); return true; } } return false; } bool Ig2_Tetra_Tetra_TTetraSimpleGeom::checkEdgeToTriangleCase1( // edge smaller than triangle const Triangle tA[4], const Segment sB[6], const Point pB[6], Vector3r& normal, Vector3r& contactPoint, Real& penetrationVolume) { for (int i=0; i<4; i++) { const Triangle& ta = tA[i]; int ni = 0; for (int j=0; j<6; j++) { const Segment& s = sB[j]; if ( do_intersect(ta,s) ) { ni++; } } if ( ni != 4 ) { continue; } Vector_3 n = CGAL::normal(ta[0],ta[1],ta[2]); for (int j=0; j<3; j++) { const Point& p1 = pB[j]; if ( Vector_3(ta[0],p1)*n > 0.) { continue; } const Segment& s10 = sB[psMap[j][0]]; const Segment& s11 = sB[psMap[j][1]]; const Segment& s12 = sB[psMap[j][2]]; bool b10 = do_intersect(ta,s10); bool b11 = do_intersect(ta,s11); bool b12 = do_intersect(ta,s12); if ( !((b10 && b11) || (b11 && b12) || (b12 && b10) ) ) { continue; } for (int k=j+1; k<4; k++) { const Point& p2 = pB[k]; if ( Vector_3(ta[0],p2)*n > 0. ) { continue; } const Segment& s20 = sB[psMap[k][0]]; const Segment& s21 = sB[psMap[k][1]]; const Segment& s22 = sB[psMap[k][2]]; bool b20 = do_intersect(ta,s20); bool b21 = do_intersect(ta,s21); bool b22 = do_intersect(ta,s22); if ( !((b20 && b21) || (b21 && b22) || (b22 && b20) ) ) { continue; } int l,m; for (l=0; l<3; l++) { if (l!=j && l!=k) { break; } } for (m=l+1; m<4; m++) { if (m!=j && m!=k) { break; } } const Segment& s13 = sB[ppsMap[j][l]]; const Segment& s14 = sB[ppsMap[j][m]]; const Segment& s23 = sB[ppsMap[k][l]]; const Segment& s24 = sB[ppsMap[k][m]]; CGAL::Object o13 = intersection(ta,s13); CGAL::Object o14 = intersection(ta,s14); CGAL::Object o23 = intersection(ta,s23); CGAL::Object o24 = intersection(ta,s24); const Point* ps13 = CGAL::object_cast(&o13); const Point* ps14 = CGAL::object_cast(&o14); const Point* ps23 = CGAL::object_cast(&o23); const Point* ps24 = CGAL::object_cast(&o24); if ( !(ps13 && ps14 && ps23 && ps24) ) { continue; } const Point* pp1[4] = {&p1,&p2,ps13,ps14}; const Point* pp2[4] = {&p2,ps23,ps24,ps14}; const Point* pp3[4] = {&p2,ps23,ps13,ps14}; Real v1 = TetrahedronVolume(pp1); Real v2 = TetrahedronVolume(pp2); Real v3 = TetrahedronVolume(pp3); Vector3r cg1,cg2,cg3; for (l=0; l<3; l++) { normal[l] = n[l]; #define OP(p) p->operator[](l) cg1[l] = .25*(p1[l]+p2[l]+OP(ps13)+OP(ps14)); cg2[l] = .25*(p2[l]+OP(ps23)+OP(ps24)+OP(ps14)); cg3[l] = .25*(p2[l]+OP(ps23)+OP(ps13)+OP(ps14)); #undef OP } penetrationVolume = v1 + v2 + v3; contactPoint = (v1*cg1 + v2*cg2 + v3*cg3) / penetrationVolume; normal.normalize(); return true; } } } return false; } bool Ig2_Tetra_Tetra_TTetraSimpleGeom::checkEdgeToTriangleCase2( // edge larger than triangle const Triangle tA[4], const Triangle tB[4], const Segment sA[6], const Segment sB[6], Vector3r& normal, Vector3r& contactPoint, Real& penetrationVolume) { for (int i=0; i<6; i++) { const Segment& sb = sB[i]; int ni = 0; for (int j=0; j<4; j++) { const Triangle& t = tA[j]; if ( do_intersect(t,sb) ) { ni++; } } if ( ni != 2 ) { continue; } for (int j=0; j<3; j++) { const Triangle& ta1 = tA[j]; if ( !(do_intersect(ta1,sb) ) ) { continue; } for (int k=j+1; k<4; k++) { const Triangle& ta2 = tA[k]; if ( !(do_intersect(ta2,sb) ) ) { continue; } const Triangle& tb1 = tB[stMap[i][0]]; const Triangle& tb2 = tB[stMap[i][1]]; const Segment& sa1a = sA[tsMap[j][0]]; const Segment& sa1b = sA[tsMap[j][1]]; const Segment& sa1c = sA[tsMap[j][2]]; bool b1a = do_intersect(sa1a,tb1) && do_intersect(sa1a,tb2); bool b1b = do_intersect(sa1b,tb1) && do_intersect(sa1b,tb2); bool b1c = do_intersect(sa1c,tb1) && do_intersect(sa1c,tb2); if ( !(b1a || b1b || b1c) ) { continue; } const Segment& sa2a = sA[tsMap[k][0]]; const Segment& sa2b = sA[tsMap[k][1]]; const Segment& sa2c = sA[tsMap[k][2]]; bool b2a = do_intersect(sa2a,tb1) && do_intersect(sa2a,tb2); bool b2b = do_intersect(sa2b,tb1) && do_intersect(sa2b,tb2); bool b2c = do_intersect(sa2c,tb1) && do_intersect(sa2c,tb2); if ( !(b2a || b2b || b2c) ) { continue; } int l = b1a? tsMap[j][0] : b1b? tsMap[j][1] : tsMap[j][2]; int m = b2a? tsMap[k][0] : b2b? tsMap[k][1] : tsMap[k][2]; if (sstMap[l][m] == -1) { continue; } const Segment& sa1 = sA[l]; const Segment& sa2 = sA[m]; const Triangle& taN = tA[sstMap[l][m]]; CGAL::Object o1 = intersection(sb,ta1); CGAL::Object o2 = intersection(sb,ta2); CGAL::Object o11 = intersection(sa1,tb1); CGAL::Object o12 = intersection(sa1,tb2); CGAL::Object o21 = intersection(sa2,tb1); CGAL::Object o22 = intersection(sa2,tb2); const Point* p1 = CGAL::object_cast(&o1); const Point* p2 = CGAL::object_cast(&o2); const Point* p11 = CGAL::object_cast(&o11); const Point* p12 = CGAL::object_cast(&o12); const Point* p21 = CGAL::object_cast(&o21); const Point* p22 = CGAL::object_cast(&o22); if ( !(p1 && p2 && p11 && p12 && p21 && p22) ) { continue; } const Point* pp1[4] = {p1,p2,p11,p12}; const Point* pp2[4] = {p2,p21,p22,p12}; const Point* pp3[4] = {p2,p21,p11,p12}; Real v1 = TetrahedronVolume(pp1); Real v2 = TetrahedronVolume(pp2); Real v3 = TetrahedronVolume(pp3); Vector3r cg1,cg2,cg3; Vector_3 n = CGAL::normal(taN[0],taN[1],taN[2]); for (int l=0; l<3; l++) { normal[l] = n[l]; #define OP(p) p->operator[](l) cg1[l] = .25*(OP(p1)+OP(p2)+OP(p11)+OP(p12)); cg2[l] = .25*(OP(p2)+OP(p21)+OP(p22)+OP(p12)); cg3[l] = .25*(OP(p2)+OP(p21)+OP(p11)+OP(p12)); #undef OP } penetrationVolume = v1 + v2 + v3; contactPoint = (v1*cg1 + v2*cg2 + v3*cg3) / penetrationVolume; normal.normalize(); return true; } } } return false; } bool Ig2_Tetra_Tetra_TTetraSimpleGeom::checkVertexToEdgeCase( const Point pA[4], const Segment sA[6], const Triangle tA[4], const Segment sB[6], const Triangle tB[4], Vector3r& normal, Vector3r& contactPoint, Real& penetrationVolume) { for (int i=0; i<4; i++) { const Point& pa = pA[i]; if ( Vector_3(tB[0][0],pa)*CGAL::normal(tB[0][0],tB[0][1],tB[0][2]) > 0. ) { continue; } if ( Vector_3(tB[1][0],pa)*CGAL::normal(tB[1][0],tB[1][1],tB[1][2]) > 0. ) { continue; } if ( Vector_3(tB[2][0],pa)*CGAL::normal(tB[2][0],tB[2][1],tB[2][2]) > 0. ) { continue; } if ( Vector_3(tB[3][0],pa)*CGAL::normal(tB[3][0],tB[3][1],tB[3][2]) > 0. ) { continue; } const Segment& sa1 = sA[psMap[i][0]]; const Segment& sa2 = sA[psMap[i][1]]; const Segment& sa3 = sA[psMap[i][2]]; for (int j=0; j<6; j++) { const Segment& sb = sB[j]; const Triangle& tb1 = tB[stMap[j][0]]; const Triangle& tb2 = tB[stMap[j][1]]; const Triangle& ta1 = tA[ptMap[i][0]]; const Triangle& ta2 = tA[ptMap[i][1]]; const Triangle& ta3 = tA[ptMap[i][2]]; bool bsa1tb1 = do_intersect(sa1,tb1); bool bsa1tb2 = do_intersect(sa1,tb2); bool bsa2tb1 = do_intersect(sa2,tb1); bool bsa2tb2 = do_intersect(sa2,tb2); bool bsa3tb1 = do_intersect(sa3,tb1); bool bsa3tb2 = do_intersect(sa3,tb2); bool bsbta1 = do_intersect(sb,ta1); bool bsbta2 = do_intersect(sb,ta2); bool bsbta3 = do_intersect(sb,ta3); if ( !( (bsa1tb1 || bsa1tb2) && (bsa2tb1 || bsa2tb2) && (bsa3tb1 || bsa3tb2) && ((bsbta1 && bsbta2) || (bsbta2 && bsbta3) || (bsbta3 && bsbta1)) ) ) { continue; } CGAL::Object oa1 = intersection(sa1,bsa1tb1? tb1 : tb2); CGAL::Object oa2 = intersection(sa2,bsa2tb1? tb1 : tb2); CGAL::Object oa3 = intersection(sa3,bsa3tb1? tb1 : tb2); CGAL::Object ob1 = intersection(sb, (bsbta1 && bsbta2)? ta1 : (bsbta2 && bsbta3)? ta2 : ta3); CGAL::Object ob2 = intersection(sb, (bsbta1 && bsbta2)? ta2 : (bsbta2 && bsbta3)? ta3 : ta1); const Point* pa1 = CGAL::object_cast(&oa1); const Point* pa2 = CGAL::object_cast(&oa2); const Point* pa3 = CGAL::object_cast(&oa3); const Point* pb1 = CGAL::object_cast(&ob1); const Point* pb2 = CGAL::object_cast(&ob2); if ( !(pa1 && pa2 && pa3 && pb1 && pb2) ) { continue; } Segment sa(*pa1,*pa2); Real d1 = sqrt(CGAL::squared_distance(sa,*pb1)); Real d2 = sqrt(CGAL::squared_distance(sa,*pb2)); const Point* ppb1 = d1operator[](l) cg1[l] = .25*(pa[l]+OP(pa1)+OP(pa2)+OP(pa3)); cg2[l] = .25*(OP(pa1)+OP(pa2)+OP(pa3)+OP(ppb2)); cg3[l] = .25*(OP(pa1)+OP(pa2)+OP(ppb1)+OP(ppb2)); #undef OP } penetrationVolume = v1 + v2 + v3; contactPoint = (v1*cg1 + v2*cg2 + v3*cg3) / penetrationVolume; normal.normalize(); return true; } } return false; } bool Ig2_Tetra_Tetra_TTetraSimpleGeom::checkVertexToVertexCase( const Point pA[4], const Point pB[4], const Segment sA[6], const Triangle tA[4], const Triangle tB[4], Vector3r& normal, Vector3r& contactPoint, Real& penetrationVolume) { for (int i=0; i<4; i++) { const Point& pa = pA[i]; if ( Vector_3(tB[0][0],pa)*CGAL::normal(tB[0][0],tB[0][1],tB[0][2]) > 0. ) { continue; } if ( Vector_3(tB[1][0],pa)*CGAL::normal(tB[1][0],tB[1][1],tB[1][2]) > 0. ) { continue; } if ( Vector_3(tB[2][0],pa)*CGAL::normal(tB[2][0],tB[2][1],tB[2][2]) > 0. ) { continue; } if ( Vector_3(tB[3][0],pa)*CGAL::normal(tB[3][0],tB[3][1],tB[3][2]) > 0. ) { continue; } const Segment& sa1 = sA[psMap[i][0]]; const Segment& sa2 = sA[psMap[i][1]]; const Segment& sa3 = sA[psMap[i][2]]; for (int j=0; j<4; j++) { const Point& pb = pB[j]; if ( Vector_3(tA[0][0],pb)*CGAL::normal(tA[0][0],tA[0][1],tA[0][2]) > 0. ) { continue; } if ( Vector_3(tA[1][0],pb)*CGAL::normal(tA[1][0],tA[1][1],tA[1][2]) > 0. ) { continue; } if ( Vector_3(tA[2][0],pb)*CGAL::normal(tA[2][0],tA[2][1],tA[2][2]) > 0. ) { continue; } if ( Vector_3(tA[3][0],pb)*CGAL::normal(tA[3][0],tB[3][1],tB[3][2]) > 0. ) { continue; } const Triangle& tb1 = tB[ptMap[j][0]]; const Triangle& tb2 = tB[ptMap[j][1]]; const Triangle& tb3 = tB[ptMap[j][2]]; bool b11 = do_intersect(sa1,tb1); bool b12 = do_intersect(sa1,tb2); bool b13 = do_intersect(sa1,tb3); bool b21 = do_intersect(sa2,tb1); bool b22 = do_intersect(sa2,tb2); bool b23 = do_intersect(sa2,tb3); bool b31 = do_intersect(sa3,tb1); bool b32 = do_intersect(sa3,tb2); bool b33 = do_intersect(sa3,tb3); if ( !(b11 || b12 || b13) && (b21 || b22 || b23) && (b31 || b32 || b33) ) { continue; } CGAL::Object o1 = intersection(sa1, b11? tb1: b12? tb2 : tb3); CGAL::Object o2 = intersection(sa2, b21? tb1: b22? tb2 : tb3); CGAL::Object o3 = intersection(sa3, b31? tb1: b32? tb2 : tb3); const Point* p1 = CGAL::object_cast(&o1); const Point* p2 = CGAL::object_cast(&o2); const Point* p3 = CGAL::object_cast(&o3); if ( !(p1 && p2 && p3) ) { continue; } const Point* pp1[4] = {&pa,p1,p2,p3}; const Point* pp2[4] = {&pb,p2,p3,p3}; Real v1 = TetrahedronVolume(pp1); Real v2 = TetrahedronVolume(pp2); Vector3r cg1,cg2; Vector_3 n(pa,pb); for (int l=0; l<3; l++) { normal[l] = n[l]; #define OP(p) p->operator[](l) cg1[l] = .25*(pa[l]+OP(p1)+OP(p2)+OP(p3)); cg2[l] = .25*(pb[l]+OP(p1)+OP(p2)+OP(p3)); #undef OP } penetrationVolume = v1 + v2; contactPoint = (v1*cg1 + v2*cg2) / penetrationVolume; normal.normalize(); return true; } } return false; } bool Ig2_Tetra_Tetra_TTetraSimpleGeom::go( const shared_ptr& cm1, const shared_ptr& cm2, const State& state1, const State& state2, const Vector3r& shift2, const bool& force, const shared_ptr& interaction) { const Se3r& se31=state1.se3; const Se3r& se32=state2.se3; Tetra* shape1 = static_cast(cm1.get()); Tetra* shape2 = static_cast(cm2.get()); Point p1[4], p2[4]; Vector3r vTemp; // vertices in global coordinates for (int i=0; i<4; i++) { vTemp = se31.position + se31.orientation*shape1->v[i]; p1[i] = Point(vTemp[0],vTemp[1],vTemp[2]); vTemp = se32.position + se32.orientation*shape2->v[i] + shift2; p2[i] = Point(vTemp[0],vTemp[1],vTemp[2]); } // Faces (CGAL triangles) of each tetra #define T(p,i,j,k) Triangle(p[i],p[j],p[k]) const Triangle t1[4] = {T(p1,0,1,3),T(p1,0,2,1),T(p1,1,2,3),T(p1,0,3,2)}; const Triangle t2[4] = {T(p2,0,1,3),T(p2,0,2,1),T(p2,1,2,3),T(p2,0,3,2)}; #undef T // Edges (CGAL segments) of each tetra #define S(p,i,j) Segment(p[i],p[j]) const Segment s1[6] = {S(p1,0,1),S(p1,1,2),S(p1,0,2),S(p1,0,3),S(p1,1,3),S(p1,2,3)}; const Segment s2[6] = {S(p2,0,1),S(p2,1,2),S(p2,0,2),S(p2,0,3),S(p2,1,3),S(p2,2,3)}; #undef S Vector3r n; Vector3r cp; Real V; int flag; # define SET_GEOM_AND_RETURN_TRUE \ shared_ptr geom; \ if (!interaction->geom) geom=shared_ptr(new TTetraSimpleGeom()); \ else geom=YADE_PTR_CAST(interaction->geom); \ interaction->geom=geom; \ geom->normal = n; \ geom->contactPoint = cp; \ geom->penetrationVolume = V; \ geom->flag = flag; \ return true; if (checkVertexToTriangleCase(t1,p2,s2,n,cp,V)) { flag = 1; SET_GEOM_AND_RETURN_TRUE } if (checkVertexToTriangleCase(t2,p1,s1,n,cp,V)) { n *= -1.; flag = 2; SET_GEOM_AND_RETURN_TRUE } if (checkEdgeToEdgeCase(s1,s2,t1,t2,n,cp,V)) { flag = 3; SET_GEOM_AND_RETURN_TRUE } if (checkEdgeToTriangleCase1(t1,s2,p2,n,cp,V)) { flag = 4; SET_GEOM_AND_RETURN_TRUE } if (checkEdgeToTriangleCase1(t2,s1,p1,n,cp,V)) { n *= -1.; flag = 5; SET_GEOM_AND_RETURN_TRUE } if (checkEdgeToTriangleCase2(t1,t2,s1,s2,n,cp,V)) { flag = 6; SET_GEOM_AND_RETURN_TRUE } if (checkEdgeToTriangleCase2(t2,t1,s2,s1,n,cp,V)) { n *= -1.; flag = 7; SET_GEOM_AND_RETURN_TRUE } if (checkVertexToEdgeCase(p1,s1,t1,s2,t2,n,cp,V)) { n *= -1.; flag = 8; SET_GEOM_AND_RETURN_TRUE } if (checkVertexToEdgeCase(p2,s2,t2,s1,t1,n,cp,V)) { flag = 9; SET_GEOM_AND_RETURN_TRUE } #undef SET_GEOM_AND_RETURN_TRUE if (interaction->geom) { TTetraSimpleGeom* geom = static_cast(interaction->geom.get()); geom->penetrationVolume = (Real)-1.; geom->flag = 0; return true; } return false; } #endif CREATE_LOGGER(Ig2_Tetra_Tetra_TTetraGeom); /*! Calculate configuration of Tetra - Tetra intersection. * * Wildmagick's functions are used here: intersection is returned as a set of tetrahedra (may be empty, inwhich case there is no real intersection). * Then we calcualte volumetric proeprties of this intersection volume: inertia, centroid, volume. * * Contact normal (the direction in which repulsive force will act) coincides with the direction of least inertia, * since that is the gradient that maximizes the drop of elastic deformation energy and will reach minimum fastest. * * Equivalent cross section of the penetrating volume (as if it were a cuboid with the same inertia) and equivalent penetration depth are calculated; * Equivalent solid size in the dimension of normal serves as reference for strain calculation and is different for solids A and B. * * Strain will be then approximated by equivalentPenetrationDepth/.5*(maxPenetrationDepthA+maxPenetrationDepthB) (the average of A and B) * * All the relevant results are fed into TTetraGeom which is passed to TetraVolumetricLaw later that makes actual use of all this. * * @todo thoroughly test this for numerical correctness. * */ bool Ig2_Tetra_Tetra_TTetraGeom::go(const shared_ptr& cm1,const shared_ptr& cm2,const State& state1,const State& state2, const Vector3r& shift2, const bool& force, const shared_ptr& interaction){ const Se3r& se31=state1.se3; const Se3r& se32=state2.se3; Tetra* A = static_cast(cm1.get()); Tetra* B = static_cast(cm2.get()); //return false; shared_ptr bang; // depending whether it's a new interaction: create new one, or use the existing one. if (!interaction->geom) bang=shared_ptr(new TTetraGeom()); else bang=YADE_PTR_CAST(interaction->geom); interaction->geom=bang; // use wildmagick's intersection routine? #if 0 // transform to global coordinates, build Tetrahedron3r objects to make wm3 happy Tetrahedron3r tA(se31.orientation*A->v[0]+se31.position,se31.orientation*A->v[1]+se31.position,se31.orientation*A->v[2]+se31.position,se31.orientation*A->v[3]+se31.position); Tetrahedron3r tB(se32.orientation*B->v[0]+se32.position,se32.orientation*B->v[1]+se32.position,se32.orientation*B->v[2]+se32.position,se32.orientation*B->v[3]+se32.position); IntrTetrahedron3Tetrahedron3r iAB(tA,tB); bool found=iAB.Find(); //calculates the intersection volume as a composition of 0 or more tetrahedra if(!found) return false; // no intersecting volume Real V(0); // volume of intersection (cummulative) Vector3r Sg(0,0,0); // static moment of intersection vector > tAB; Wm3::TArray iABinfo(iAB.GetIntersection()); // retrieve the array of 4hedra for(int i=0; i t; t.push_back(v0); t.push_back(v1); t.push_back(v2); t.push_back(v3); tAB.push_back(t); #undef v0 #undef v1 #undef v2 #undef v3 } #endif // transform to global coordinates, build Tetra objects Tetra tA(se31.orientation*A->v[0]+se31.position,se31.orientation*A->v[1]+se31.position,se31.orientation*A->v[2]+se31.position,se31.orientation*A->v[3]+se31.position); Tetra tB(se32.orientation*B->v[0]+se32.position+shift2,se32.orientation*B->v[1]+se32.position+shift2,se32.orientation*B->v[2]+se32.position+shift2,se32.orientation*B->v[3]+se32.position+shift2); // calculate intersection #if 0 tB=Tetra(Vector3r(0,0,0),Vector3r(1.5,1,1),Vector3r(0.5,1,1),Vector3r(1,1,.5)); tA=Tetra(Vector3r(0,0,0),Vector3r(1,0,0),Vector3r(0,1,0),Vector3r(0,0,1)); #endif list tAB=Tetra2TetraIntersection(tA,tB); if (!interaction->isReal() && !force) { if(tAB.size()==0) { /* LOG_DEBUG("No intersection."); */ return false;} //no intersecting volume } Real V(0); // volume of intersection (cummulative) Vector3r Sg(0,0,0); // static moment of intersection Vector3r tt[4]; for(int i=0; i<4; i++) tt[i]=tA.v[i]; for(list::iterator II=tAB.begin(); II!=tAB.end(); II++){ Real dV=TetrahedronVolume(II->v); V+=dV; Sg+=dV*(II->v[0]+II->v[1]+II->v[2]+II->v[3])*.25; } Vector3r centroid=Sg/V; Matrix3r I(Matrix3r::Zero()); // inertia tensor for the composition; zero matrix initially // I is purely geometrical (as if with unit density) // get total Vector3r dist; for(list::iterator II=tAB.begin(); II!=tAB.end(); II++){ II->v[0]-=centroid; II->v[1]-=centroid; II->v[2]-=centroid; II->v[3]-=centroid; dist=(II->v[0]+II->v[1]+II->v[2]+II->v[3])*.25-centroid; /* use parallel axis theorem */ Matrix3r distSq(Matrix3r::Zero()); distSq(0,0)=dist[0]*dist[0]; distSq(1,1)=dist[1]*dist[1]; distSq(2,2)=dist[2]*dist[2]; // could be done more intelligently with eigen I+=TetrahedronInertiaTensor(II->v)+TetrahedronVolume(II->v)*distSq; } /* Now, we have the collision volumetrically described by intersection volume (V), its inertia tensor (I) and centroid (centroid; contact point). * The inertia tensor is in global coordinates; by eigendecomposition, we find principal axes, which will give us * 1. normal, the direction of the lest inertia; this is the gradient of penetration energy * it may have either direction mathematically, but since 4hedra are convex, * normal will be always the direction pointing more towards the centroid of the other 4hedron * 2. tangent?! hopefully not needed at all. */ Matrix3r Ip, R; // principal moments of inertia, rotation matrix /* should check convergence*/ matrixEigenDecomposition(I,R,Ip); // according to the documentation in Wm3 header, diagonal entries are in ascending order: d0<=d1<=d2; // but keep it algorithmic for now and just assert that. int ix=(Ip(0,0)v[0]+B->v[1]+B->v[2]+B->v[3])*.25)+se31.position; // reverse direction if projection of the (contact_point-centroid_of_B) vector onto the normal is negative (i.e. the normal points more towards A) if((Bcent-centroid).dot(normal)<0) normal*=-1; /* now estimate the area of the solid that is perpendicular to the normal. This will be needed to estimate elastic force based on Young's modulus. * Suppose we have cuboid, with edges of lengths x,y,z in the direction of respective axes. * It's inertia are Ix=(V/12)*(y^2+z^2), Iy=(V/12)*(x^2+z^2), Iz=(V/12)*(x^2+y^2) and suppose Iz is maximal; Ix, Iy and Iz are known (from decomposition above). * Then the area perpendicular to z (normal direction) is given by x*y=V/z, where V is known. * Ix+Iy-Iz=(V/12)*(y^2+z^2+x^2+z^2-x^2-y^2)=(V*z^2)/6, z=√(6*(Ix+Iy-Iz)/V) * Az=V/z=√(V^3/(6*(Ix+Iy-Iz))). * * In our case, the greatest inertia is along ixxx, the other coordinates are ixx and ix. equivalentPenetrationDepth means what was z. */ Real equivalentPenetrationDepth=sqrt(6.*(Ip(ix,ix)+Ip(ixx,ixx)-Ip(ixxx,ixxx))/V); Real equivalentCrossSection=V/equivalentPenetrationDepth; TRVAR3(V,equivalentPenetrationDepth,equivalentCrossSection); /* Now rotate the whole inertia tensors of A and B and estimate maxPenetrationDepth -- the length of the body in the direction of the contact normal. * This will be used to calculate relative deformation, which is needed for elastic response. */ const State* physA=Body::byId(interaction->getId1())->state.get(); const State* physB=Body::byId(interaction->getId2())->state.get(); // WARNING: Matrix3r(Vector3r(...)) is compiled, but gives zero matrix??!! Use explicitly constructor from diagonal entries //Matrix3r IA(physA->inertia[0],physA->inertia[1],physA->inertia[2]); Matrix3r IB(physB->inertia[0],physB->inertia[1],physB->inertia[2]); Matrix3r IA=Matrix3r::Zero(), IB=Matrix3r::Zero(); for(int i=0; i<3; i++){ IA(i,i)=physA->inertia[i]; IB(i,i)=physB->inertia[i]; } // see Clump::inertiaTensorRotate for references IA=R.transpose()*IA*R; IB=R.transpose()*IB*R; Real maxPenetrationDepthA=sqrt(6*(IA(ix,ix)+IA(ixx,ixx)-IA(ixxx,ixxx))/V); Real maxPenetrationDepthB=sqrt(6*(IB(ix,ix)+IB(ixx,ixx)-IB(ixxx,ixxx))/V); TRVAR2(maxPenetrationDepthA,maxPenetrationDepthB); //normal = se32.position - se31.position; normal.normalize(); /* store calculated stuff in bang; some is redundant */ bang->normal=normal; bang->equivalentCrossSection=equivalentCrossSection; bang->contactPoint=centroid; bang->penetrationVolume=V; bang->equivalentPenetrationDepth=equivalentPenetrationDepth; bang->maxPenetrationDepthA=maxPenetrationDepthA; bang->maxPenetrationDepthB=maxPenetrationDepthB; return true; } /*! Calculate intersection o Tetrahedron A and B as union of set (std::list) of 4hedra. * * intersecting tetrahedra A and B * S=intersection set (4hedra) * S={A} * for face in B_faces: * for t in S: [ S is mutable, but if list, iterators remain valid? ] * tmp = clip t by face // may return multiple 4hedra or none * replace t by tmp (possibly none) in S * return S * */ list Ig2_Tetra_Tetra_TTetraGeom::Tetra2TetraIntersection(const Tetra& A, const Tetra& B){ // list of 4hedra to split; initially A list ret; ret.push_back(A); /* I is vertex index at B; * clipping face is [i i1 i2], normal points away from i3 */ int i,i1,i2,i3; Vector3r normal; /* LOG_TRACE("===========================================================================================") LOG_TRACE("===========================================================================================") LOG_TRACE(ret.size()); LOG_TRACE("DUMP A and B:"); A.dump(); B.dump(); */ for(i=0; i<4; i++){ i1=(i+1)%4; i2=(i+2)%4; i3=(i+3)%4; const Vector3r& P(B.v[i]); // reference point on the plane normal=(B.v[i1]-P).cross(B.v[i2]-P); normal.normalize(); // normal if((B.v[i3]-P).dot(normal)>0) normal*=-1; // outer normal for(list::iterator I=ret.begin(); I!=ret.end(); /* I++ */ ){ list splitDecomposition=TetraClipByPlane(*I,P,normal); // replace current list element by the result of decomposition; // I points after the erased one, so decomposed 4hedra will not be touched in this iteration, just as we want. // Since it will be incremented by I++ at the end of the cycle, compensate for that by I--; I=ret.erase(I); ret.insert(I,splitDecomposition.begin(),splitDecomposition.end()); /* I--; */ /* LOG_TRACE("DUMP current tetrahedron list:"); for(list::iterator I=ret.begin(); I!=ret.end(); I++) (*I).dump();*/ } } //exit(0); return ret; } /*! Clip Tetra T by plane give by point P and outer normal n. * * Algorithm: * * clip t by face * sort points of t into positive, negative, zero (face normal n points outside) * -: inside; +: outside; 0: on face * homogeneous cases (no split): * ++++, +++0, ++00, +000 : * 0Δ full clip (everything outside), nothing left; return ∅ * ----, ---0, --00, -000 : * 1Δ all inside, return identity * split (at least one - and one +) * -+++ * 1Δ [A AB AC AD] * -++0 * 1Δ [A AB AC D] * -+00: * 1Δ [A AB C D] * --++: * 3Δ [A AC AD B BC BD] ⇒ (e.g.) [A AC AD B] [B BC BD AD] [B AD AC BC] * --+0: * 2Δ [A B AC BC D] ⇒ (e.g.) [A AC BC D] [B BC A D] * ---+: * 3Δ tetrahedrize [A B C AD BD CD] * * http://members.tripod.com/~Paul_Kirby/vector/Vplanelineint.html */ list Ig2_Tetra_Tetra_TTetraGeom::TetraClipByPlane(const Tetra& T, const Vector3r& P, const Vector3r& normal){ list ret; // scaling factor for Mathr::EPSILON: average edge length Real scaledEPSILON=Mathr::EPSILON*(1/6.)*((T.v[1]-T.v[0])+(T.v[2]-T.v[0])+(T.v[3]-T.v[0])+(T.v[2]-T.v[1])+(T.v[3]-T.v[1])+(T.v[3]-T.v[2])).norm(); vector pos, neg, zer; Real dist[4]; for(size_t i=0; i<4; i++){ dist[i]=(T.v[i]-P).dot(normal); if(dist[i]>scaledEPSILON) pos.push_back(i); else if(dist[i]<-scaledEPSILON) neg.push_back(i); else zer.push_back(i); } /* LOG_TRACE("dist[i]=["<& I, *scene->interactions){ // normally, we would test isReal(), but TetraVolumetricLaw doesn't use phys at all if (!I->geom) continue; // Ig2_Tetra_Tetra_TTetraGeom::go returned false for this interaction, skip it const shared_ptr& contactGeom(YADE_PTR_DYN_CAST(I->geom)); if(!contactGeom) continue; const Body::id_t idA=I->getId1(), idB=I->getId2(); const shared_ptr& A=Body::byId(idA), B=Body::byId(idB); const shared_ptr& physA(YADE_PTR_DYN_CAST(A->material)); const shared_ptr& physB(YADE_PTR_DYN_CAST(B->material)); /* Cross-section is volumetrically equivalent to the penetration configuration */ Real averageStrain=contactGeom->equivalentPenetrationDepth/(.5*(contactGeom->maxPenetrationDepthA+contactGeom->maxPenetrationDepthB)); /* Do not use NormPhys::kn (as calculated by ElasticBodySimpleRelationship). * NormPhys::kn is not Young's modulus, it is calculated by MacroMicroElasticRelationships. So perhaps * a new IPhysFunctor will be needed that will just pass the average Young's modulus here? * For now, just go back to Young's moduli directly here. */ Real young=.5*(physA->young+physB->young); TRVAR3(young,averageStrain,contactGeom->equivalentCrossSection); // F=σA=εEA // this is unused; should it?: contactPhys->kn Vector3r F=contactGeom->normal*averageStrain*young*contactGeom->equivalentCrossSection; scene->forces.addForce (idA,-F); scene->forces.addForce (idB, F); scene->forces.addTorque(idA,-(A->state->pos-contactGeom->contactPoint).cross(F)); scene->forces.addTorque(idB, (B->state->pos-contactGeom->contactPoint).cross(F)); } } #ifdef YADE_OPENGL #include bool Gl1_Tetra::wire; void Gl1_Tetra::go(const shared_ptr& cm, const shared_ptr&,bool wire2,const GLViewInfo&) { glMaterialv(GL_FRONT,GL_AMBIENT_AND_DIFFUSE,Vector3r(cm->color[0],cm->color[1],cm->color[2])); glColor3v(cm->color); Tetra* t=static_cast(cm.get()); if (wire && wire2) { // wireframe, as for Tetrahedron glDisable(GL_LIGHTING); glBegin(GL_LINES); glOneWire(t, 0, 1); glOneWire(t, 0, 2); glOneWire(t, 0, 3); glOneWire(t, 1, 2); glOneWire(t, 1, 3); glOneWire(t, 2, 3); glEnd(); } else { glDisable(GL_CULL_FACE); glEnable(GL_LIGHTING); glBegin(GL_TRIANGLES); glOneFace (t, 0,2,1); glOneFace (t, 0,1,3); glOneFace (t, 1,2,3); glOneFace (t, 0,3,2); glEnd(); } } #endif /*! Calculates tetrahedron inertia relative to the origin (0,0,0), with unit density (scales linearly). See article F. Tonon, "Explicit Exact Formulas for the 3-D Tetrahedron Inertia Tensor in Terms of its Vertex Coordinates", http://www.scipub.org/fulltext/jms2/jms2118-11.pdf Numerical example to check: vertices: (8.33220, 11.86875, 0.93355) (0.75523 ,5.00000, 16.37072) (52.61236, 5.00000, 5.38580) (2.00000, 5.00000, 3.00000) centroid: (15.92492, 0.78281, 3.72962) intertia/density WRT centroid: a/μ = 43520.33257 m⁵ b/μ = 194711.28938 m⁵ c/μ = 191168.76173 m⁵ a’/μ= 4417.66150 m⁵ b’/μ=-46343.16662 m⁵ c’/μ= 11996.20119 m⁵ The numerical testcase (in TetraTestGen::generate) is exact as in the article for inertia (as well as centroid): 43520.3 194711 191169 4417.66 -46343.2 11996.2 */ //Matrix3r TetrahedronInertiaTensor(const Vector3r v[4]){ Matrix3r TetrahedronInertiaTensor(const vector& v){ #define x1 v[0][0] #define y1 v[0][1] #define z1 v[0][2] #define x2 v[1][0] #define y2 v[1][1] #define z2 v[1][2] #define x3 v[2][0] #define y3 v[2][1] #define z3 v[2][2] #define x4 v[3][0] #define y4 v[3][1] #define z4 v[3][2] // FIXME - C array assert(v.size()==4); // Jacobian of transformation to the reference 4hedron double detJ=(x2-x1)*(y3-y1)*(z4-z1)+(x3-x1)*(y4-y1)*(z2-z1)+(x4-x1)*(y2-y1)*(z3-z1) -(x2-x1)*(y4-y1)*(z3-z1)-(x3-x1)*(y2-y1)*(z4-z1)-(x4-x1)*(y3-y1)*(z2-z1); detJ=std::abs(detJ); double a=detJ*(y1*y1+y1*y2+y2*y2+y1*y3+y2*y3+ y3*y3+y1*y4+y2*y4+y3*y4+y4*y4+z1*z1+z1*z2+ z2*z2+z1*z3+z2*z3+z3*z3+z1*z4+z2*z4+z3*z4+z4*z4)/60.; double b=detJ*(x1*x1+x1*x2+x2*x2+x1*x3+x2*x3+x3*x3+ x1*x4+x2*x4+x3*x4+x4*x4+z1*z1+z1*z2+z2*z2+z1*z3+ z2*z3+z3*z3+z1*z4+z2*z4+z3*z4+z4*z4)/60.; double c=detJ*(x1*x1+x1*x2+x2*x2+x1*x3+x2*x3+x3*x3+x1*x4+ x2*x4+x3*x4+x4*x4+y1*y1+y1*y2+y2*y2+y1*y3+ y2*y3+y3*y3+y1*y4+y2*y4+y3*y4+y4*y4)/60.; // a' in the article etc. double a__=detJ*(2*y1*z1+y2*z1+y3*z1+y4*z1+y1*z2+ 2*y2*z2+y3*z2+y4*z2+y1*z3+y2*z3+2*y3*z3+ y4*z3+y1*z4+y2*z4+y3*z4+2*y4*z4)/120.; double b__=detJ*(2*x1*z1+x2*z1+x3*z1+x4*z1+x1*z2+ 2*x2*z2+x3*z2+x4*z2+x1*z3+x2*z3+2*x3*z3+ x4*z3+x1*z4+x2*z4+x3*z4+2*x4*z4)/120.; double c__=detJ*(2*x1*y1+x2*y1+x3*y1+x4*y1+x1*y2+ 2*x2*y2+x3*y2+x4*y2+x1*y3+x2*y3+2*x3*y3+ x4*y3+x1*y4+x2*y4+x3*y4+2*x4*y4)/120.; Matrix3r ret; ret<< a , -b__, -c__, -b__, b , -a__, -c__, -a__, c ; return ret; #undef x1 #undef y1 #undef z1 #undef x2 #undef y2 #undef z2 #undef x3 #undef y3 #undef z3 #undef x4 #undef y4 #undef z4 } /*! Caluclate tetrahedron's central inertia tensor */ //Matrix3r TetrahedronCentralInertiaTensor(const Vector3r v[4]){ Matrix3r TetrahedronCentralInertiaTensor(const vector& v){ assert(v.size()==4); vector vv; // Vector3r vv[4]; Vector3r cg=(v[0]+v[1]+v[2]+v[3])*.25; // vv[0]=v[0]-cg; // vv[1]=v[1]-cg; // vv[2]=v[2]-cg; // vv[3]=v[3]-cg; vv.push_back(v[0]-cg); vv.push_back(v[1]-cg); vv.push_back(v[2]-cg); vv.push_back(v[3]-cg); return TetrahedronInertiaTensor(vv); } /*! Rotate and translate terahedron body so that its local axes are principal, keeping global position by updating vertex positions as well. * Updates all body parameters as need. * * @returns rotation that was done as Wm3::Quaternionr. * @todo check for geometrical correctness... * */ Quaternionr TetrahedronWithLocalAxesPrincipal(shared_ptr& tetraBody){ //const shared_ptr& rbp(YADE_PTR_CAST(tetraBody->physicalParameters)); State* rbp=tetraBody->state.get(); const shared_ptr& tMold(YADE_PTR_DYN_CAST(tetraBody->shape)); #define v0 tMold->v[0] #define v1 tMold->v[1] #define v2 tMold->v[2] #define v3 tMold->v[3] // adjust position (origin to centroid) Vector3r cg=(v0+v1+v2+v3)*.25; v0-=cg; v1-=cg; v2-=cg; v3-=cg; //tMold->v[0]=v0; tMold->v[1]=v1; tMold->v[2]=v2; tMold->v[3]=v3; rbp->se3.position+=cg; // adjust orientation (local axes to principal axes) Matrix3r I_old=TetrahedronInertiaTensor(tMold->v); //≡TetrahedronCentralInertiaTensor Matrix3r I_rot(Matrix3r::Zero()), I_new(Matrix3r::Zero()); matrixEigenDecomposition(I_old,I_rot,I_new); Quaternionr I_Qrot(I_rot); //! @fixme from right to left: rotate by I_rot, then add original rotation (?!!) rbp->se3.orientation=rbp->se3.orientation*I_Qrot; for(size_t i=0; i<4; i++){ tMold->v[i]=I_Qrot.conjugate()*tMold->v[i]; } // set inertia rbp->inertia=Vector3r(I_new(0,0),I_new(1,1),I_new(2,2)); return I_Qrot; #undef v0 #undef v1 #undef v2 #undef v3 } Real TetrahedronSignedVolume(const Vector3r v[4]) { return (Vector3r(v[3])-Vector3r(v[0])).dot((Vector3r(v[3])-Vector3r(v[1])).cross(Vector3r(v[3])-Vector3r(v[2])))/6.; } Real TetrahedronVolume(const Vector3r v[4]) { return std::abs(TetrahedronSignedVolume(v)); } Real TetrahedronSignedVolume(const vector& v) { return Vector3r(v[1]-v[0]).dot(Vector3r(v[2]-v[0]).cross(v[3]-v[0]))/6.; } Real TetrahedronVolume(const vector& v) { return std::abs(TetrahedronSignedVolume(v)); } #ifdef YADE_CGAL Real TetrahedronVolume(const CGAL::Point_3 >* v[4]) { Vector3r vv[4]; for (int i=0; i<4; i++) { for (int j=0; j<3; j++) { vv[i][j] = v[i]->operator[](j); } } return TetrahedronVolume(vv); } Real TetrahedronVolume(const CGAL::Point_3 > v[4]) { Vector3r vv[4]; for (int i=0; i<4; i++) { for (int j=0; j<3; j++) { vv[i][j] = v[i][j]; } } return TetrahedronVolume(vv); } #endif #ifdef YADE_CGAL bool Law2_TTetraSimpleGeom_NormPhys_Simple::go(shared_ptr& ig, shared_ptr& ip, Interaction* contact){ int id1 = contact->getId1(), id2 = contact->getId2(); TTetraSimpleGeom* geom= static_cast(ig.get()); NormPhys* phys = static_cast(ip.get()); if ( geom->flag == 0 || geom->penetrationVolume <= 0. ) { return false; } Real& un=geom->penetrationVolume; phys->normalForce=phys->kn*std::max(un,(Real) 0)*geom->normal; State* de1 = Body::byId(id1,scene)->state.get(); State* de2 = Body::byId(id2,scene)->state.get(); applyForceAtContactPoint(-phys->normalForce, geom->contactPoint, id1, de1->se3.position, id2, de2->se3.position); // TODO periodic return true; } #endif trunk-2018.02b/pkg/dem/Tetra.hpp000066400000000000000000000211731324306050200163100ustar00rootroot00000000000000// © 2007 Václav Šmilauer // © 2013 Jan Stránský #pragma once #include #include #include #include #include #include #ifdef YADE_CGAL #include //#include #endif /* Our mold of tetrahedron: just 4 vertices. * * Self-contained. */ class Tetra: public Shape{ public: Tetra(Vector3r v0, Vector3r v1, Vector3r v2, Vector3r v3) { createIndex(); v.resize(4); v[0]=v0; v[1]=v1; v[2]=v2; v[3]=v3; } virtual ~Tetra(); YADE_CLASS_BASE_DOC_ATTRS_CTOR(Tetra,Shape,"Tetrahedron geometry.", ((std::vector,v,std::vector(4),,"Tetrahedron vertices (in local coordinate system).")), /*ctor*/createIndex(); ); REGISTER_CLASS_INDEX(Tetra,Shape); }; REGISTER_SERIALIZABLE(Tetra); /*! Collision configuration for Tetra and something. * This is expressed as penetration volume properties: centroid, volume, orientation of principal axes, inertia. * * Self-contained. */ class TTetraGeom: public IGeom{ public: virtual ~TTetraGeom(); YADE_CLASS_BASE_DOC_ATTRS_CTOR(TTetraGeom,IGeom,"Geometry of interaction between 2 :yref:`tetrahedra`, including volumetric characteristics", ((Real,penetrationVolume,NaN,,"Volume of overlap [m³]")) ((Real,equivalentCrossSection,NaN,,"Cross-section of the overlap (perpendicular to the axis of least inertia")) ((Real,maxPenetrationDepthA,NaN,,"??")) ((Real,maxPenetrationDepthB,NaN,,"??")) ((Real,equivalentPenetrationDepth,NaN,,"??")) ((Vector3r,contactPoint,,,"Contact point (global coords)")) ((Vector3r,normal,,,"Normal of the interaction, directed in the sense of least inertia of the overlap volume")), createIndex(); ); REGISTER_CLASS_INDEX(TTetraGeom,IGeom); }; REGISTER_SERIALIZABLE(TTetraGeom); class TTetraSimpleGeom: public IGeom{ public: virtual ~TTetraSimpleGeom(); YADE_CLASS_BASE_DOC_ATTRS_CTOR(TTetraSimpleGeom,IGeom,"EXPERIMENTAL. Geometry of interaction between 2 :yref:`tetrahedra`", ((Real,penetrationVolume,NaN,,"Volume of overlap [m³]")) ((Vector3r,contactPoint,,,"Contact point (global coords)")) ((Vector3r,normal,,,"Normal of the interaction TODO")) ((int,flag,0,,"TODO")), createIndex(); ); REGISTER_CLASS_INDEX(TTetraSimpleGeom,IGeom); }; REGISTER_SERIALIZABLE(TTetraSimpleGeom); /*! Creates Aabb from Tetra. * * Self-contained. */ class Bo1_Tetra_Aabb: public BoundFunctor{ public: void go(const shared_ptr& ig, shared_ptr& bv, const Se3r& se3, const Body*); FUNCTOR1D(Tetra); YADE_CLASS_BASE_DOC(Bo1_Tetra_Aabb,BoundFunctor,"Create/update :yref:`Aabb` of a :yref:`Tetra`"); }; REGISTER_SERIALIZABLE(Bo1_Tetra_Aabb); #ifdef YADE_OPENGL #include /*! Draw Tetra using OpenGL */ class Gl1_Tetra: public GlShapeFunctor{ public: virtual void go(const shared_ptr&, const shared_ptr&,bool,const GLViewInfo&); YADE_CLASS_BASE_DOC_STATICATTRS(Gl1_Tetra,GlShapeFunctor,"Renders :yref:`Tetra` object", ((bool,wire,true,,"TODO")) ); RENDERS(Tetra); }; REGISTER_SERIALIZABLE(Gl1_Tetra); #endif /*! Calculate physical response based on penetration configuration given by TTetraGeom. */ class TetraVolumetricLaw: public GlobalEngine { public: void action(); DECLARE_LOGGER; YADE_CLASS_BASE_DOC(TetraVolumetricLaw,GlobalEngine,"Calculate physical response of 2 :yref:`tetrahedra` in interaction, based on penetration configuration given by :yref:`TTetraGeom`."); }; REGISTER_SERIALIZABLE(TetraVolumetricLaw); /*! @fixme implement Tetra2BoxBang by representing box as 6 tetrahedra. */ /*! Create TTetraGeom (collision geometry) from colliding Tetra's. */ class Ig2_Tetra_Tetra_TTetraGeom: public IGeomFunctor { public: virtual bool go(const shared_ptr& cm1, const shared_ptr& cm2, const State& state1, const State& state2, const Vector3r& shift2, const bool& force, const shared_ptr& c); virtual bool goReverse( const shared_ptr& cm1, const shared_ptr& cm2, const State& state1, const State& state2, const Vector3r& shift2, const bool& force, const shared_ptr& c){ throw std::logic_error("Ig2_Tetra_Tetra_TTetraGeom::goReverse called, but the functor is symmetric."); } FUNCTOR2D(Tetra,Tetra); DEFINE_FUNCTOR_ORDER_2D(Tetra,Tetra); YADE_CLASS_BASE_DOC(Ig2_Tetra_Tetra_TTetraGeom,IGeomFunctor,"Create/update geometry of collision between 2 :yref:`tetrahedra` (:yref:`TTetraGeom` instance)"); DECLARE_LOGGER; private: std::list Tetra2TetraIntersection(const Tetra& A, const Tetra& B); std::list TetraClipByPlane(const Tetra& T, const Vector3r& P, const Vector3r& n); //! Intersection of line given by points A, B and plane given by P and its normal. Vector3r PtPtPlaneIntr(const Vector3r& A, const Vector3r& B, const Vector3r& P, const Vector3r& normal){const double t=(P-A).dot(normal) / (B-A).dot(normal); return A+t*(B-A); } }; REGISTER_SERIALIZABLE(Ig2_Tetra_Tetra_TTetraGeom); #ifdef YADE_CGAL class Ig2_Tetra_Tetra_TTetraSimpleGeom: public IGeomFunctor { protected: typedef CGAL::Cartesian K; typedef K::Point_3 Point; typedef CGAL::Tetrahedron_3 CGALTetra; typedef CGAL::Segment_3 Segment; typedef CGAL::Triangle_3 Triangle; typedef CGAL::Vector_3 Vector_3; bool checkVertexToTriangleCase(const Triangle tA[4], const Point pB[4], const Segment sB[6], Vector3r& normal, Vector3r& contactPoint, Real& penetrationVolume); bool checkEdgeToEdgeCase(const Segment sA[6], const Segment sB[6], const Triangle tA[4], const Triangle tB[4], Vector3r& normal, Vector3r& contactPoint, Real& penetrationVolume); bool checkEdgeToTriangleCase1(const Triangle tA[4], const Segment sB[6], const Point pB[4], Vector3r& normal, Vector3r& contactPoint, Real& penetrationVolume); bool checkEdgeToTriangleCase2(const Triangle tA[4], const Triangle tB[4], const Segment sA[6], const Segment sB[6], Vector3r& normal, Vector3r& contactPoint, Real& penetrationVolume); bool checkVertexToEdgeCase(const Point pA[4], const Segment sA[6], const Triangle tA[4], const Segment sB[6], const Triangle tB[4], Vector3r& normal, Vector3r& contactPoint, Real& penetrationVolume); bool checkVertexToVertexCase(const Point pA[4], const Point pB[4], const Segment sA[6], const Triangle tA[4], const Triangle tB[4], Vector3r& normal, Vector3r& contactPoint, Real& penetrationVolume); static const int psMap[4][3]; static const int ptMap[4][3]; static const int stMap[6][2]; static const int tsMap[4][3]; static const int ppsMap[4][4]; static const int sstMap[6][6]; public: virtual bool go(const shared_ptr& cm1, const shared_ptr& cm2, const State& state1, const State& state2, const Vector3r& shift2, const bool& force, const shared_ptr& c); virtual bool goReverse( const shared_ptr& cm1, const shared_ptr& cm2, const State& state1, const State& state2, const Vector3r& shift2, const bool& force, const shared_ptr& c){ throw std::logic_error("Ig2_Tetra_Tetra_TTetraSimpleGeom::goReverse called, but the functor is symmetric."); } FUNCTOR2D(Tetra,Tetra); DEFINE_FUNCTOR_ORDER_2D(Tetra,Tetra); YADE_CLASS_BASE_DOC(Ig2_Tetra_Tetra_TTetraSimpleGeom,IGeomFunctor,"EXPERIMANTAL. Create/update geometry of collision between 2 :yref:`tetrahedra` (:yref:`TTetraSimpleGeom` instance)"); DECLARE_LOGGER; }; REGISTER_SERIALIZABLE(Ig2_Tetra_Tetra_TTetraSimpleGeom); class Law2_TTetraSimpleGeom_NormPhys_Simple: public LawFunctor{ public: virtual bool go(shared_ptr& _geom, shared_ptr& _phys, Interaction* I); YADE_CLASS_BASE_DOC(Law2_TTetraSimpleGeom_NormPhys_Simple,LawFunctor,"EXPERIMENTAL. TODO"); FUNCTOR2D(TTetraSimpleGeom,NormPhys); DECLARE_LOGGER; }; REGISTER_SERIALIZABLE(Law2_TTetraSimpleGeom_NormPhys_Simple); #endif // Miscillaneous functions //! Tetrahedron's volume. /// http://en.wikipedia.org/wiki/Tetrahedron#Surface_area_and_volume Real TetrahedronSignedVolume(const Vector3r v[4]); Real TetrahedronVolume(const Vector3r v[4]); Real TetrahedronSignedVolume(const vector& v); Real TetrahedronVolume(const vector& v); #ifdef YADE_CGAL Real TetrahedronVolume(const CGAL::Point_3 >* v[4]); Real TetrahedronVolume(const CGAL::Point_3 > v[4]); #endif Matrix3r TetrahedronInertiaTensor(const vector& v); //Matrix3r TetrahedronInertiaTensor(const Vector3r v[4]); Matrix3r TetrahedronCentralInertiaTensor(const vector& v); //Matrix3r TetrahedronCentralInertiaTensor(const Vector3r v[4]); Quaternionr TetrahedronWithLocalAxesPrincipal(shared_ptr& tetraBody); trunk-2018.02b/pkg/dem/ThreeDTriaxialEngine.cpp000066400000000000000000000120441324306050200212200ustar00rootroot00000000000000/************************************************************************* * Copyright (C) 2009 by Luc Sibille * * luc.sibille@univ-nantes.fr * * * * This program is free software; it is licensed under the terms of the * * GNU General Public License v2 or later. See file LICENSE for details. * *************************************************************************/ #include "ThreeDTriaxialEngine.hpp" #include #include #include #include #include #include #include #include #include class Ip2_CohFrictMat_CohFrictMat_CohFrictPhys; CREATE_LOGGER(ThreeDTriaxialEngine); YADE_PLUGIN((ThreeDTriaxialEngine)); ThreeDTriaxialEngine::~ThreeDTriaxialEngine() { } void ThreeDTriaxialEngine::action() { static int warn = 0; if (!warn++) LOG_WARN ("This engine is deprecated, please switch to TriaxialStressController if you expect long term support.") if ( firstRun ) { LOG_INFO ( "First run, will initialize!" ); if (updateFrictionAngle) setContactProperties(frictionAngleDegree); height0 = height; depth0 = depth; width0 = width; if (stressControl_1){ wall_right_activated=true; wall_left_activated=true; //are the right walls for direction 1? } else { wall_right_activated=false; wall_left_activated=false; } if (stressControl_2){ wall_bottom_activated=true; wall_top_activated=true; } else { wall_bottom_activated=false; wall_top_activated=false; } if (stressControl_3){ wall_front_activated=true; wall_back_activated=true; //are the right walls for direction 3? } else { wall_front_activated=false; wall_back_activated=false; } //internalCompaction=false; //is needed to avoid a control for internal compaction by the TriaxialStressController engine // isAxisymetric=false; //is needed to avoid a stress control according the parameter sigma_iso (but according to sigma1, sigma2 and sigma3) firstRun=false; } const Real& dt = scene->dt; if(!stressControl_1) // control in strain if wanted { if ( currentStrainRate1 != strainRate1 ) currentStrainRate1 += ( strainRate1-currentStrainRate1 ) *(1-strainDamping); State* p_left=Body::byId(wall_left_id,scene)->state.get(); p_left->pos += 0.5*currentStrainRate1*width*translationAxisx*dt; State* p_right=Body::byId(wall_right_id,scene)->state.get(); p_right->pos -= 0.5*currentStrainRate1*width*translationAxisx*dt; } else { if ( currentStrainRate1 != strainRate1 ) currentStrainRate1 += ( strainRate1-currentStrainRate1 ) *(1-strainDamping); max_vel1 = 0.5*currentStrainRate1*width; } if(!stressControl_2) // control in strain if wanted { if ( currentStrainRate2 != strainRate2 ) currentStrainRate2 += ( strainRate2-currentStrainRate2 ) *(1-strainDamping); State* p_bottom=Body::byId(wall_bottom_id,scene)->state.get(); p_bottom->pos += 0.5*currentStrainRate2*height*translationAxisy*dt; State* p_top=Body::byId(wall_top_id,scene)->state.get(); p_top->pos -= 0.5*currentStrainRate2*height*translationAxisy*dt; } else { if ( currentStrainRate2 != strainRate2 ) currentStrainRate2 += ( strainRate2-currentStrainRate2 ) *(1-strainDamping); max_vel2 = 0.5*currentStrainRate2*height; } if(!stressControl_3) // control in strain if wanted { if ( currentStrainRate3 != strainRate3 ) currentStrainRate3 += ( strainRate3-currentStrainRate3 ) *(1-strainDamping); State* p_back=Body::byId(wall_back_id,scene)->state.get(); p_back->pos += 0.5*currentStrainRate3*depth*translationAxisz*dt; State* p_front=Body::byId(wall_front_id,scene)->state.get(); p_front->pos -= 0.5*currentStrainRate3*depth*translationAxisz*dt; } else { if ( currentStrainRate3 != strainRate3 ) currentStrainRate3 += ( strainRate3-currentStrainRate3 ) *(1-strainDamping); max_vel3 = 0.5*currentStrainRate3*depth; } TriaxialStressController::action(); // this function is called to perform the external stress control or the internal compaction } void ThreeDTriaxialEngine::setContactProperties(Real frictionDegree) { scene = Omega::instance().getScene().get(); shared_ptr& bodies = scene->bodies; FOREACH(const shared_ptr& b,*scene->bodies){ if (b->isDynamic()) YADE_PTR_CAST (b->material)->frictionAngle = frictionDegree * Mathr::PI/180.0; } FOREACH(const shared_ptr& ii, *scene->interactions){ if (!ii->isReal()) continue; const shared_ptr& sdec1 = YADE_PTR_CAST((*bodies)[(Body::id_t) ((ii)->getId1())]->material); const shared_ptr& sdec2 = YADE_PTR_CAST((*bodies)[(Body::id_t) ((ii)->getId2())]->material); //FIXME - why dynamic_cast fails here? FrictPhys* contactPhysics = YADE_CAST((ii)->phys.get()); Real fa = sdec1->frictionAngle; Real fb = sdec2->frictionAngle; contactPhysics->tangensOfFrictionAngle = std::tan(std::min(fa,fb)); } } trunk-2018.02b/pkg/dem/ThreeDTriaxialEngine.hpp000066400000000000000000000121301324306050200212210ustar00rootroot00000000000000/************************************************************************* * Copyright (C) 2009 by Luc Sibille * * luc.sibille@univ-nantes.fr * * * * This program is free software; it is licensed under the terms of the * * GNU General Public License v2 or later. See file LICENSE for details. * *************************************************************************/ #pragma once #include #include #include /** \brief Class for controlling in stress or in strain with respect to each spatial direction a cubical assembly of particles. * * The engine perform a triaxial compression with a control in direction "i" in stress "if (stressControl_i)" else in strain. * For a stress control the imposed stress is specified by "sigma_i" with a "max_veli" depending on "strainRatei". To obtain the same strain rate in stress control than in strain control you need to set "wallDamping = 0.8". * For a strain control the imposed strain is specified by "strainRatei". * With this engine you can perform internal compaction by growing the size of particles by using TriaxialStressController::controlInternalStress . For that, just switch on 'internalCompaction=1' and fix sigma_iso=value of mean pressure that you want at the end of the internal compaction. * */ class ThreeDTriaxialEngine : public TriaxialStressController { public : // ThreeDTriaxialEngine(); virtual ~ThreeDTriaxialEngine(); Vector3r translationAxisy; Vector3r translationAxisx; Vector3r translationAxisz; //! is this the beginning of the simulation, after reading the scene? -> it is the first time that Yade passes trought the engine ThreeDTriaxialEngine bool firstRun; virtual void action(); ///Change physical properties of interactions and/or bodies in the middle of a simulation (change only friction for the moment, complete this function to set cohesion and others before compression test) void setContactProperties(Real frictionDegree); YADE_CLASS_BASE_DOC_ATTRS_CTOR_PY( ThreeDTriaxialEngine,TriaxialStressController, "The engine perform a triaxial compression with a control in direction 'i' in stress (if stressControl_i) else in strain.\n\n" "For a stress control the imposed stress is specified by 'sigma_i' with a 'max_veli' depending on 'strainRatei'. To obtain the same strain rate in stress control than in strain control you need to set 'wallDamping = 0.8'.\n" "For a strain control the imposed strain is specified by 'strainRatei'.\n" "With this engine you can also perform internal compaction by growing the size of particles by using ``TriaxialStressController::controlInternalStress``. For that, just switch on 'internalCompaction=1' and fix sigma_iso=value of mean pressure that you want at the end of the internal compaction.\n" "\n\n.. warning::\n\t This engine is deprecated, please switch to TriaxialStressController if you expect long term support." , ((Real, strainRate1,0,,"target strain rate in direction 1 (./s, >0 for compression)")) ((Real, currentStrainRate1,0,,"current strain rate in direction 1 - converging to :yref:`ThreeDTriaxialEngine::strainRate1` (./s)")) ((Real, strainRate2,0,,"target strain rate in direction 2 (./s, >0 for compression)")) ((Real, currentStrainRate2,0,,"current strain rate in direction 2 - converging to :yref:`ThreeDTriaxialEngine::strainRate2` (./s)")) ((Real, strainRate3,0,,"target strain rate in direction 3 (./s, >0 for compression)")) ((Real, currentStrainRate3,0,,"current strain rate in direction 3 - converging to :yref:`ThreeDTriaxialEngine::strainRate3` (./s)")) ((Real, UnbalancedForce,1,,"mean resultant forces divided by mean contact force")) ((Real, frictionAngleDegree,-1,,"Value of friction used in the simulation if (updateFrictionAngle)")) ((bool, updateFrictionAngle,false,,"Switch to activate the update of the intergranular frictionto the value :yref:`ThreeDTriaxialEngine::frictionAngleDegree`.")) ((bool, stressControl_1,true,,"Switch to choose a stress or a strain control in directions 1")) ((bool, stressControl_2,true,,"Switch to choose a stress or a strain control in directions 2")) ((bool, stressControl_3,true,,"Switch to choose a stress or a strain control in directions 3")) ((Real, strainDamping,0.9997,,"factor used for smoothing changes in effective strain rate. If target rate is TR, then (1-damping)*(TR-currentRate) will be added at each iteration. With damping=0, rate=target all the time. With damping=1, it doesn't change.")) ((std::string,Key,"",,"A string appended at the end of all files, use it to name simulations.")) , translationAxisy=Vector3r(0,1,0); translationAxisx=Vector3r(1,0,0); translationAxisz=Vector3r(0,0,1); firstRun=true; boxVolume=0; , .def("setContactProperties",&ThreeDTriaxialEngine::setContactProperties,"Assign a new friction angle (degrees) to dynamic bodies and relative interactions") ) DECLARE_LOGGER; }; REGISTER_SERIALIZABLE(ThreeDTriaxialEngine); trunk-2018.02b/pkg/dem/TriaxialCompressionEngine.cpp000066400000000000000000000210551324306050200223500ustar00rootroot00000000000000/************************************************************************* * Copyright (C) 2006 by Bruno Chareyre * * bruno.chareyre@hmg.inpg.fr * * * * This program is free software; it is licensed under the terms of the * * GNU General Public License v2 or later. See file LICENSE for details. * *************************************************************************/ #include "TriaxialCompressionEngine.hpp" #include #include #include #include #include #include #include #include #include class Ip2_CohFrictMat_CohFrictMat_CohFrictPhys; CREATE_LOGGER(TriaxialCompressionEngine); YADE_PLUGIN((TriaxialCompressionEngine)); TriaxialCompressionEngine::~TriaxialCompressionEngine() { } void TriaxialCompressionEngine::doStateTransition(stateNum nextState){ if ( /* currentState==STATE_UNINITIALIZED && */ nextState==STATE_ISO_COMPACTION){ sigma_iso=sigmaIsoCompaction; previousSigmaIso=sigma_iso; } else if(nextState==STATE_TRIAX_LOADING){ sigma_iso=sigmaLateralConfinement; previousSigmaIso=sigma_iso; internalCompaction = false; if (frictionAngleDegree>0) setContactProperties(frictionAngleDegree); height0 = height; depth0 = depth; width0 = width; //compressionActivated = true; wall_bottom_activated=false; wall_top_activated=false; if(currentState==STATE_ISO_UNLOADING && !noFiles){ LOG_INFO("Speres -> /tmp/unloaded.spheres"); Shop::saveSpheresToFile("/tmp/unloaded.spheres"); } if(!firstRun && !noFiles) saveSimulation=true; // saving snapshot .xml will actually be done in ::action Phase1End = "Unloaded"; } else if(currentState==STATE_ISO_COMPACTION && nextState==STATE_ISO_UNLOADING){ sigma_iso=sigmaLateralConfinement; sigmaIsoCompaction = sigmaLateralConfinement; previousSigmaIso=sigma_iso; internalCompaction=false; // unloading will not change grain sizes if (frictionAngleDegree>0) setContactProperties(frictionAngleDegree); if(!firstRun && !noFiles) saveSimulation=true; Phase1End = "Compacted"; } else if ((currentState==STATE_ISO_COMPACTION || currentState==STATE_ISO_UNLOADING) && nextState==STATE_LIMBO) { //urrentState==STATE_DIE_COMPACTION internalCompaction = false; if (frictionAngleDegree>0) setContactProperties(frictionAngleDegree); height0 = height; depth0 = depth; width0 = width; if(!noFiles) saveSimulation=true; // saving snapshot .xml will actually be done in ::action // stop simulation here, since nothing will happen from now on Phase1End = (currentState==STATE_ISO_COMPACTION ? "compacted" : "unloaded"); if(!noFiles) Shop::saveSpheresToFile("/tmp/limbo.spheres"); // Please keep this saving process intact, I'm tired of running 3 days simulations and getting nothing at the end! if(!firstRun && !noFiles) saveSimulation=true; // saving snapshot .xml will actually be done in ::action } else if( nextState==STATE_FIXED_POROSITY_COMPACTION){ internalCompaction = false; wall_bottom_activated=false; wall_top_activated=false; wall_front_activated=false; wall_back_activated=false; wall_right_activated=false; wall_left_activated=false; } else { LOG_ERROR("Undefined transition from "<iter % testEquilibriumInterval == 0 ) { updateParameters (); maxStress = max(maxStress,-stress[wall_top][1]); LOG_INFO("UnbalancedForce="<< UnbalancedForce<<", rel stress "<< std::abs ( ( meanStress-sigma_iso ) /sigma_iso )); } if ( saveSimulation ) { if(!noFiles){ string fileName = "./"+ Key + "_" + Phase1End + "_" + boost::lexical_cast ( scene->iter ) + "_" + boost::lexical_cast ( currentState ) + ".xml"; LOG_INFO ( "saving snapshot: "< ( scene->iter ) + "_" + boost::lexical_cast ( currentState ) +".spheres"; LOG_INFO ( "saving spheres: "<iter % 100 == 0 ) { LOG_INFO ("Triax Compression started"); } if (scene->iter % 100 == 0) LOG_DEBUG("Compression active."); const Real& dt = scene->dt; if (std::abs(epsilonMax) > std::abs(strain[1])) { if ( currentStrainRate != strainRate ) currentStrainRate += ( strainRate-currentStrainRate ) *0.0003; /* Move top and bottom wall according to strain rate */ State* p_bottom=Body::byId(wall_bottom_id,scene)->state.get(); p_bottom->pos += 0.5*currentStrainRate*height*translationAxis*dt; State* p_top=Body::byId(wall_top_id,scene)->state.get(); p_top->pos -= 0.5*currentStrainRate*height*translationAxis*dt; } } if ( currentState==STATE_FIXED_POROSITY_COMPACTION ) { if ( scene->iter % 100 == 0 ) LOG_INFO ("Compression started"); const Real& dt = scene->dt; State* p_bottom=Body::byId(wall_bottom_id,scene)->state.get(); State* p_top=Body::byId(wall_top_id,scene)->state.get(); State* p_left=Body::byId(wall_left_id,scene)->state.get(); State* p_right=Body::byId(wall_right_id,scene)->state.get(); State* p_front=Body::byId(wall_front_id,scene)->state.get(); State* p_back=Body::byId(wall_back_id,scene)->state.get(); /* Move top and bottom wall according to strain rate */ p_bottom->pos += 0.5*strainRate*height*translationAxis*dt; p_top->pos -= 0.5*strainRate*height*translationAxis*dt; p_back->pos += 0.5*strainRate*depth*translationAxisz*dt; p_front->pos -= 0.5*strainRate*depth*translationAxisz*dt; p_left->pos += 0.5*strainRate*width*translationAxisx*dt; p_right->pos -= 0.5*strainRate*width*translationAxisx*dt; } } void TriaxialCompressionEngine::setContactProperties(Real frictionDegree) { Shop::setContactFriction(frictionDegree*Mathr::PI/180.0); } trunk-2018.02b/pkg/dem/TriaxialCompressionEngine.hpp000066400000000000000000000247061324306050200223630ustar00rootroot00000000000000/************************************************************************* * Copyright (C) 2006 by Bruno Chareyre * * bruno.chareyre@hmg.inpg.fr * * * * This program is free software; it is licensed under the terms of the * * GNU General Public License v2 or later. See file LICENSE for details. * *************************************************************************/ #pragma once #include #include #include /** \brief Class for controlling optional initial isotropic compaction and subsequent triaxial test with constant lateral stress and constant axial strain rate. The algorithms used have been developed initialy for simulations reported in [Chareyre2002a] and [Chareyre2005]. They have been ported to Yade in a second step and used in e.g. [Kozicki2008],[Scholtes2009b],[Jerier2010b]. * * The engine is a state machine with the following states; transitions may be automatic, see below. The algorithms used have been developed initialy for simulations reported in [Chareyre2002a]_ and [Chareyre2005]_. They have been ported to Yade in a second step and used in e.g. [Kozicki2008]_,[Scholtes2009b]_,[Jerier2010b]. * * 1. STATE_ISO_COMPACTION: isotropic compaction (compression) until * the prescribed mean pressue sigmaIsoCompaction is reached and the packing is stable. * The compaction happens either by straining the walls (!internalCompaction) * or by growing size of grains (internalCompaction). * 2. STATE_ISO_UNLOADING: isotropic unloading from the previously reached state, until * the mean pressure sigmaLateralConfinement is reached (and stabilizes). * NOTE: this state will be skipped if sigmaLateralConfinement == sigmaIsoCompaction. * 3. STATE_TRIAX_LOADING: confined uniaxial compression: * constant sigmaLateralConfinement is kept at lateral walls (left, right, front, back), while * top and bottom walls load the packing in their axis (by straining), until the value of epsilonMax * (deformation along the loading axis) is reached. At this point, the simulation is stopped. * 4. STATE_FIXED_POROSITY_COMPACTION: isotropic compaction (compression) until * a chosen porosity value (parameter:fixedPorosity). The six walls move with a chosen translation speed * (parameter StrainRate). * 5. STATE_TRIAX_LIMBO: currently unused, since simulation is hard-stopped in the previous state. * * Transition from COMPACTION to UNLOADING is done automatically if autoUnload==true; * Transition from (UNLOADING to LOADING) or from (COMPACTION to LOADING: if UNLOADING is skipped) is * done automatically if autoCompressionActivation=true; * Both autoUnload and autoCompressionActivation are true by default. * * NOTE: This engine handles many different manipulations, including some save/reload with attributes modified manually in between. Please don't modify the algorithms, even if they look strange (especially test sequences) without notifying me and getting explicit approval. A typical situation is somebody generates a sample with !autoCompressionActivation and run : he wants a saved simulation at the end. He then reload the saved state, modify some parameters, set autoCompressionActivation=true, and run. He should get the compression test done. * */ class TriaxialCompressionEngine : public TriaxialStressController { private : std::string Phase1End;//used to name output files based on current state public : //TriaxialCompressionEngine(); virtual ~TriaxialCompressionEngine(); // FIXME: current serializer doesn't handle named enum types, this is workaround. #define stateNum int // should be "enum stateNum {...}" once this is fixed enum {STATE_UNINITIALIZED, STATE_ISO_COMPACTION, STATE_ISO_UNLOADING, STATE_TRIAX_LOADING, STATE_FIXED_POROSITY_COMPACTION, STATE_LIMBO}; void doStateTransition(stateNum nextState); #define _STATE_CASE(ST) case ST: return #ST string stateName(stateNum st){switch(st){ _STATE_CASE(STATE_UNINITIALIZED);_STATE_CASE(STATE_ISO_COMPACTION);_STATE_CASE(STATE_ISO_UNLOADING);_STATE_CASE(STATE_TRIAX_LOADING);_STATE_CASE(STATE_FIXED_POROSITY_COMPACTION);_STATE_CASE(STATE_LIMBO); default: return ""; } } #undef _STATE_CASE Vector3r translationAxisx; Vector3r translationAxisz; //! is isotropicInternalCompactionFinished? bool Phase1, saveSimulation, DieCompaction;//FIXME : document DieCompaction //! is this the beginning of the simulation, after reading the scene? bool firstRun; int FinalIterationPhase1, Iteration/*, testEquilibriumInterval*/;//FIXME : what is that? virtual void action(); void updateParameters(); ///Change physical properties of interactions and/or bodies in the middle of a simulation (change only friction for the moment, complete this function to set cohesion and others before compression test) void setContactProperties(Real frictionDegree); YADE_CLASS_BASE_DOC_ATTRS_CTOR_PY( TriaxialCompressionEngine,TriaxialStressController, "The engine is a state machine with the following states; transitions my be automatic, see below.\n\n" "#. STATE_ISO_COMPACTION: isotropic compaction (compression) until the prescribed mean pressue sigmaIsoCompaction is reached and the packing is stable. The compaction happens either by straining the walls (!internalCompaction) or by growing size of grains (internalCompaction).\n" "#. STATE_ISO_UNLOADING: isotropic unloading from the previously reached state, until the mean pressure sigmaLateralConfinement is reached (and stabilizes).\n\n\t.. note::\n\t\tthis state will be skipped if sigmaLateralConfinement == sigmaIsoCompaction.\n" "#. STATE_TRIAX_LOADING: confined uniaxial compression: constant sigmaLateralConfinement is kept at lateral walls (left, right, front, back), while top and bottom walls load the packing in their axis (by straining), until the value of epsilonMax (deformation along the loading axis) is reached. At this point, the simulation is stopped.\n" "#. STATE_FIXED_POROSITY_COMPACTION: isotropic compaction (compression) until a chosen porosity value (parameter:fixedPorosity). The six walls move with a chosen translation speed (parameter StrainRate).\n" "#. STATE_TRIAX_LIMBO: currently unused, since simulation is hard-stopped in the previous state.\n\n" "Transition from COMPACTION to UNLOADING is done automatically if autoUnload==true;\n\n Transition from (UNLOADING to LOADING) or from (COMPACTION to LOADING: if UNLOADING is skipped) is done automatically if autoCompressionActivation=true; Both autoUnload and autoCompressionActivation are true by default.\n\n" "\n\n.. note::\n\t Most of the algorithms used have been developed initialy for simulations reported in [Chareyre2002a]_ and [Chareyre2005]_. They have been ported to Yade in a second step and used in e.g. [Kozicki2008]_,[Scholtes2009b]_,[Jerier2010b]." "\n\n.. warning::\n\t This engine is deprecated, please switch to TriaxialStressController if you expect long term support." , ((int, warn, 0,,"counter used for sending a deprecation warning once")) ((Real,strainRate,0,,"target strain rate (./s, >0 for compression)")) ((Real,currentStrainRate,0,,"current strain rate - converging to :yref:`TriaxialCompressionEngine::strainRate` (./s)")) ((Real,UnbalancedForce,1,,"mean resultant forces divided by mean contact force")) ((Real,StabilityCriterion,0.001,,"tolerance in terms of :yref:`TriaxialCompressionEngine::UnbalancedForce` to consider the packing is stable")) ((Vector3r,translationAxis,TriaxialStressController::normal[wall_bottom],,"compression axis")) ((bool,autoCompressionActivation,true,,"Auto-switch from isotropic compaction (or unloading state if sigmaLateralConfinement #include #include #include #include #include #include #include CREATE_LOGGER(TriaxialStateRecorder); TriaxialStateRecorder::~TriaxialStateRecorder() {}; void TriaxialStateRecorder::action () { // at the beginning of the file; write column titles if(out.tellp()==0) out<<"iteration s11 s22 s33 e11 e22 e33 unb_force porosity kineticE"< >::iterator itFirst = scene->engines.begin(); vector >::iterator itLast = scene->engines.end(); for ( ;itFirst!=itLast; ++itFirst ){ if ( ( *itFirst )->getClassName() == "TriaxialCompressionEngine" || ( *itFirst )->getClassName() == "ThreeDTriaxialEngine" || ( *itFirst )->getClassName() == "TriaxialStressController"){ LOG_DEBUG ( "stress controller engine found" ); triaxialStressController = YADE_PTR_CAST ( *itFirst ); //triaxialCompressionEngine = shared_ptr (static_cast ( (*itFirst).get())); } } if ( !triaxialStressController ) LOG_ERROR ( "stress controller engine NOT found" ); } if ( ! ( scene->iter % triaxialStressController->computeStressStrainInterval == 0 ) ) triaxialStressController->computeStressStrain (); /// Compute porosity : Real Vs=0; Real V = ( triaxialStressController->height ) * ( triaxialStressController->width ) * ( triaxialStressController->depth ); BodyContainer::iterator bi = scene->bodies->begin(); BodyContainer::iterator biEnd = scene->bodies->end(); for ( ; bi!=biEnd; ++bi ){ if(!(*bi) || (*bi)->isClump()) continue; const shared_ptr& b = *bi; if ( b->isDynamic() ){ //Sorry, the next string was commented, because it gave a Warning "unused variable v". Anton Gladky //const Vector3r& v = b->state->vel; Vs += 1.3333333*Mathr::PI*pow ( YADE_PTR_CAST( b->shape)->radius, 3 );} } porosity = ( V - Vs ) /V; out << boost::lexical_cast ( scene->iter ) << " " << boost::lexical_cast ( triaxialStressController->stress[triaxialStressController->wall_right][0] ) << " " << boost::lexical_cast ( triaxialStressController->stress[triaxialStressController->wall_top][1] ) << " " << boost::lexical_cast ( triaxialStressController->stress[triaxialStressController->wall_front][2] ) << " " << boost::lexical_cast ( triaxialStressController->strain[0] ) << " " << boost::lexical_cast ( triaxialStressController->strain[1] ) << " " << boost::lexical_cast ( triaxialStressController->strain[2] ) << " " << boost::lexical_cast ( triaxialStressController->ComputeUnbalancedForce () ) << " " << boost::lexical_cast ( porosity ) << " " << boost::lexical_cast ( Shop::kineticEnergy() ) << endl; } YADE_PLUGIN((TriaxialStateRecorder)); trunk-2018.02b/pkg/dem/TriaxialStateRecorder.hpp000066400000000000000000000032341324306050200214730ustar00rootroot00000000000000/************************************************************************* * Copyright (C) 2006 by luc Scholtes * * luc.scholtes@hmg.inpg.fr * * Copyright (C) 2008 by Bruno Chareyre * * bruno.chareyre@hmg.inpg.fr * * * * This program is free software; it is licensed under the terms of the * * GNU General Public License v2 or later. See file LICENSE for details. * *************************************************************************/ #pragma once #include /*! \brief Record the stress-strain state of a sample in simulations using TriaxialCompressionEngine The output is a text file where each line is a record, with the format IterationNumber sigma11 sigma22 sigma33 epsilon11 epsilon22 epsilon33 */ class TriaxialStressController; class TriaxialStateRecorder : public Recorder { private : shared_ptr triaxialStressController; public : virtual ~TriaxialStateRecorder (); virtual void action(); YADE_CLASS_BASE_DOC_ATTRS_CTOR(TriaxialStateRecorder,Recorder,"Engine recording triaxial variables (see the variables list in the first line of the output file). This recorder needs :yref:`TriaxialCompressionEngine` or :yref:`ThreeDTriaxialEngine` present in the simulation).", ((Real,porosity,1,,"porosity of the packing [-]")), //Is it really needed to have this value as a serializable? initRun=true; ); DECLARE_LOGGER; }; REGISTER_SERIALIZABLE(TriaxialStateRecorder); trunk-2018.02b/pkg/dem/TriaxialStressController.cpp000066400000000000000000000322441324306050200222520ustar00rootroot00000000000000/************************************************************************* * Copyright (C) 2006 by Bruno Chareyre * * bruno.chareyre@hmg.inpg.fr * * * * This program is free software; it is licensed under the terms of the * * GNU General Public License v2 or later. See file LICENSE for details. * *************************************************************************/ #include"TriaxialStressController.hpp" #include #include #include #include #include #include #include #include #include #ifdef FLOW_ENGINE //#include #include "FlowEngine_FlowEngineT.hpp" #endif CREATE_LOGGER(TriaxialStressController); YADE_PLUGIN((TriaxialStressController)); TriaxialStressController::~TriaxialStressController(){} Vector3r TriaxialStressController::getStress(int boundId) {assert (boundId>=0 && boundId<=5); return stress[boundId];} Vector3r TriaxialStressController::getStrainRate() { return Vector3r ( (Body::byId(wall_right_id,scene)->state->vel[0]-Body::byId(wall_left_id,scene)->state->vel[0])/width, (Body::byId(wall_top_id,scene)->state->vel[1]-Body::byId(wall_bottom_id,scene)->state->vel[1])/height, (Body::byId(wall_front_id,scene)->state->vel[2]-Body::byId(wall_back_id,scene)->state->vel[2])/depth ); } void TriaxialStressController::updateStiffness() { Real fluidStiffness = 0.; #ifdef FLOW_ENGINE FOREACH(const shared_ptr e, Omega::instance().getScene()->engines) { if (e->getClassName() == "FlowEngine") { TemplateFlowEngine_FlowEngineT* flow = dynamic_cast*>(e.get()); if ( (flow->fluidBulkModulus > 0) && (!(flow->dead)) ) fluidStiffness = flow->fluidBulkModulus/porosity; } } #endif for (int i=0; i<6; ++i) stiffness[i] = 0; InteractionContainer::iterator ii = scene->interactions->begin(); InteractionContainer::iterator iiEnd = scene->interactions->end(); for( ; ii!=iiEnd ; ++ii ) if ((*ii)->isReal()) { const shared_ptr& contact = *ii; Real fn = (static_cast (contact->phys.get()))->normalForce.norm(); if (fn!=0) { int id1 = contact->getId1(), id2 = contact->getId2(); for (int index=0; index<6; ++index) if ( wall_id[index]==id1 || wall_id[index]==id2 ) { FrictPhys* currentContactPhysics = static_cast ( contact->phys.get() ); stiffness[index] += currentContactPhysics->kn; } } } if (fluidStiffness > 0) { stiffness[0] += fluidStiffness*width*depth/height; stiffness[1] += fluidStiffness*width*depth/height; stiffness[2] += fluidStiffness*height*depth/width; stiffness[3] += fluidStiffness*height*depth/width; stiffness[4] += fluidStiffness*width*height/depth; stiffness[5] += fluidStiffness*width*height/depth; } } void TriaxialStressController::controlExternalStress(int wall, Vector3r resultantForce, State* p, Real wall_max_vel) // controls walls such that Sum Forces from Sample on Wall = resultantForce { scene->forces.sync(); Real translation=normal[wall].dot(getForce(scene,wall_id[wall])-resultantForce); const bool log=false; if(log) LOG_DEBUG("wall="<se3.position += previousTranslation[wall]; externalWork += previousTranslation[wall].dot(getForce(scene,wall_id[wall])); // this is important is using VelocityBins. Otherwise the motion is never detected. Related to https://bugs.launchpad.net/yade/+bug/398089 p->vel=previousTranslation[wall]/scene->dt; //if(log)TRVAR2(previousTranslation,p->se3.position); } void TriaxialStressController::action() { // sync thread storage of ForceContainer scene->forces.sync(); if (first) {// sync boundaries ids in the table wall_id[wall_bottom] = wall_bottom_id; wall_id[wall_top] = wall_top_id; wall_id[wall_left] = wall_left_id; wall_id[wall_right] = wall_right_id; wall_id[wall_front] = wall_front_id; wall_id[wall_back] = wall_back_id;} if(thickness<0) thickness=2.0*YADE_PTR_CAST(Body::byId(wall_bottom_id,scene)->shape)->extents.y(); State* p_bottom=Body::byId(wall_bottom_id,scene)->state.get(); State* p_top=Body::byId(wall_top_id,scene)->state.get(); State* p_left=Body::byId(wall_left_id,scene)->state.get(); State* p_right=Body::byId(wall_right_id,scene)->state.get(); State* p_front=Body::byId(wall_front_id,scene)->state.get(); State* p_back=Body::byId(wall_back_id,scene)->state.get(); height = p_top->se3.position.y() - p_bottom->se3.position.y() - thickness; width = p_right->se3.position.x() - p_left->se3.position.x() - thickness; depth = p_front->se3.position.z() - p_back->se3.position.z() - thickness; boxVolume = height * width * depth; if ( (first) || (updatePorosity) ) { BodyContainer::iterator bi = scene->bodies->begin(); BodyContainer::iterator biEnd = scene->bodies->end(); particlesVolume = 0; for ( ; bi!=biEnd; ++bi ) { const shared_ptr& b = *bi; if (b->isClump()) { const shared_ptr& clump = YADE_PTR_CAST(b->shape); const shared_ptr& member = Body::byId(clump->members.begin()->first,scene); particlesVolume += b->state->mass / member->material->density; } else if (b->isDynamic() && !b->isClumpMember()) { const shared_ptr& sphere = YADE_PTR_CAST ( b->shape ); particlesVolume += 1.3333333*Mathr::PI*pow ( sphere->radius, 3 ); } } first = false; updatePorosity = false; } max_vel1=3 * width /(height+width+depth)*max_vel; max_vel2=3 * height /(height+width+depth)*max_vel; max_vel3 =3 * depth /(height+width+depth)*max_vel; porosity = ( boxVolume - particlesVolume ) /boxVolume; position_top = p_top->se3.position.y(); position_bottom = p_bottom->se3.position.y(); position_right = p_right->se3.position.x(); position_left = p_left->se3.position.x(); position_front = p_front->se3.position.z(); position_back = p_back->se3.position.z(); // must be done _after_ height, width, depth have been calculated //Update stiffness only if it has been computed by StiffnessCounter (see "stiffnessUpdateInterval") if (scene->iter % stiffnessUpdateInterval == 0 || scene->iter<100) updateStiffness(); bool isARadiusControlIteration = (scene->iter % radiusControlInterval == 0); if (scene->iter % computeStressStrainInterval == 0 || (internalCompaction && isARadiusControlIteration) ) computeStressStrain(); if (!internalCompaction) { Vector3r wallForce (0, goal2*width*depth, 0); if (wall_bottom_activated) { if (stressMask & 2) controlExternalStress(wall_bottom, wallForce, p_bottom, max_vel2); else { p_bottom->vel[1] += (-normal[wall_bottom][1]*0.5*goal2*height -p_bottom->vel[1])*(1-strainDamping); externalWork += p_bottom->vel.dot(getForce(scene,wall_bottom_id))*scene->dt;} } else p_bottom->vel=Vector3r::Zero(); if (wall_top_activated) { if (stressMask & 2) controlExternalStress(wall_top, -wallForce, p_top, max_vel2); else { p_top->vel[1] += (-normal[wall_top][1]*0.5*goal2*height -p_top->vel[1])*(1-strainDamping); externalWork += p_top->vel.dot(getForce(scene,wall_top_id))*scene->dt;} } else p_top->vel=Vector3r::Zero(); wallForce = Vector3r(goal1*height*depth, 0, 0); if (wall_left_activated) { if (stressMask & 1) controlExternalStress(wall_left, wallForce, p_left, max_vel1); else { p_left->vel[0] += (-normal[wall_left][0]*0.5*goal1*width -p_left->vel[0])*(1-strainDamping); externalWork += p_left->vel.dot(getForce(scene,wall_left_id))*scene->dt;} } else p_left->vel=Vector3r::Zero(); if (wall_right_activated) { if (stressMask & 1) controlExternalStress(wall_right, -wallForce, p_right, max_vel1); else { p_right->vel[0] += (-normal[wall_right][0]*0.5*goal1*width -p_right->vel[0])*(1-strainDamping); externalWork += p_right->vel.dot(getForce(scene,wall_right_id))*scene->dt;} } else p_right->vel=Vector3r::Zero(); wallForce = Vector3r(0, 0, goal3*height*width); if (wall_back_activated) { if (stressMask & 4) controlExternalStress(wall_back, wallForce, p_back, max_vel3); else { p_back->vel[2] += (-normal[wall_back][2]*0.5*goal3*depth -p_back->vel[2])*(1-strainDamping); externalWork += p_back->vel.dot(getForce(scene,wall_back_id))*scene->dt;} } else p_back->vel=Vector3r::Zero(); if (wall_front_activated) { if (stressMask & 4) controlExternalStress(wall_front, -wallForce, p_front, max_vel3); else { p_front->vel[2] += (-normal[wall_front][2]*0.5*goal3*depth -p_front->vel[2])*(1-strainDamping); externalWork += p_front->vel.dot(getForce(scene,wall_front_id))*scene->dt;} } else p_front->vel=Vector3r::Zero(); } else //if internal compaction { p_bottom->vel=Vector3r::Zero(); p_top->vel=Vector3r::Zero(); p_left->vel=Vector3r::Zero(); p_right->vel=Vector3r::Zero(); p_back->vel=Vector3r::Zero(); p_front->vel=Vector3r::Zero(); if (isARadiusControlIteration) { Real sigma_iso_ = bool(stressMask & 1)*goal1 + bool(stressMask & 2)*goal2 + bool(stressMask & 4)*goal3; sigma_iso_ /= bool(stressMask & 1) + bool(stressMask & 2) + bool(stressMask & 4); if (std::abs(sigma_iso_)<=std::abs(meanStress)) maxMultiplier = finalMaxMultiplier; if (meanStress==0) previousMultiplier = maxMultiplier; else { // previousMultiplier = 1+0.7*(sigma_iso-s)*(previousMultiplier-1.f)/(s-previousStress); // = (Dsigma/apparentModulus)*0.7 // previousMultiplier = std::max(2-maxMultiplier, std::min(previousMultiplier, maxMultiplier)); if (sigma_iso_ < 0) // compressive case: we have to increase radii if meanStress > sigma_iso_, considering that sigma_iso_ < 0. We end with the same expression as before sign change previousMultiplier = 1.+(sigma_iso_-meanStress)/sigma_iso_*(maxMultiplier-1.); // = (Dsigma/apparentModulus)*0.7 else // tensile case: we have to increase radii if meanStress > sigma_iso_ too. But here sigma_iso_ > 0 => another expression previousMultiplier = 1.+(meanStress-sigma_iso_)/sigma_iso_*(maxMultiplier-1.); // = (Dsigma/apparentModulus)*0.7 } previousStress = meanStress; //Real apparentModulus = (s-previousStress)/(previousMultiplier-1.f); controlInternalStress(previousMultiplier); } } } void TriaxialStressController::computeStressStrain() { scene->forces.sync(); State* p_bottom=Body::byId(wall_bottom_id,scene)->state.get(); State* p_top=Body::byId(wall_top_id,scene)->state.get(); State* p_left=Body::byId(wall_left_id,scene)->state.get(); State* p_right=Body::byId(wall_right_id,scene)->state.get(); State* p_front=Body::byId(wall_front_id,scene)->state.get(); State* p_back=Body::byId(wall_back_id,scene)->state.get(); height = p_top->se3.position.y() - p_bottom->se3.position.y() - thickness; width = p_right->se3.position.x() - p_left->se3.position.x() - thickness; depth = p_front->se3.position.z() - p_back->se3.position.z() - thickness; meanStress = 0; if (height0 == 0) height0 = height; if (width0 == 0) width0 = width; if (depth0 == 0) depth0 = depth; strain[0] = log(width/width0); // all strain values are positiv for extension strain[1] = log(height/height0); strain[2] = log(depth/depth0); volumetricStrain=strain[0]+strain[1]+strain[2]; Real invXSurface = 1.f/(height*depth); Real invYSurface = 1.f/(width*depth); Real invZSurface = 1.f/(width*height); force[wall_bottom]=getForce(scene,wall_id[wall_bottom]); stress[wall_bottom]=force[wall_bottom]*invYSurface; // all stress values are positiv for tension force[wall_top]= getForce(scene,wall_id[wall_top]); stress[wall_top]=-force[wall_top]*invYSurface; force[wall_left]= getForce(scene,wall_id[wall_left]); stress[wall_left]=force[wall_left]*invXSurface; force[wall_right]= getForce(scene,wall_id[wall_right]); stress[wall_right]= -force[wall_right]*invXSurface; force[wall_front]= getForce(scene,wall_id[wall_front]); stress[wall_front]=-force[wall_front]*invZSurface; force[wall_back]= getForce(scene,wall_id[wall_back]); stress[wall_back]= force[wall_back]*invZSurface; for (int i=0; i<6; i++) meanStress+=stress[i].dot(pow(-1.0,i)*normal[i]); // normal[i] is always inwards meanStress/=6.; // ( sXX(xLeft) + sXX(xRight) + sYY(yBottom) + sYY(yTop) + sZZ(zBack) + sZZ(zFront) ) / 6 } void TriaxialStressController::controlInternalStress ( Real multiplier ) { particlesVolume *= pow ( multiplier,3 ); Shop::growParticles(multiplier,true,true); } /*! \fn TriaxialStressController::ComputeUnbalancedForce( bool maxUnbalanced) */ Real TriaxialStressController::ComputeUnbalancedForce( bool maxUnbalanced) {return Shop::unbalancedForce(maxUnbalanced,scene);} trunk-2018.02b/pkg/dem/TriaxialStressController.hpp000066400000000000000000000254671324306050200222700ustar00rootroot00000000000000/************************************************************************* * Copyright (C) 2006 by Bruno Chareyre * * bruno.chareyre@hmg.inpg.fr * * * * This program is free software; it is licensed under the terms of the * * GNU General Public License v2 or later. See file LICENSE for details. * *************************************************************************/ #pragma once #include #include #include #include class Scene; class State; /*! \brief Controls the stress on the boundaries of a box and compute strain-like and stress-like quantities for the packing. The algorithms used have been developed initialy for simulations reported in [Chareyre2002a] and [Chareyre2005]. They have been ported to Yade in a second step and used in e.g. [Kozicki2008],[Scholtes2009b],[Jerier2010b]. */ class TriaxialStressController : public BoundaryController { private : bool first; inline const Vector3r getForce(Scene* rb, Body::id_t id){ return rb->forces.getForce(id); /* needs sync, which is done at the beginning of action */ } public : //! internal index values for retrieving walls enum {wall_left=0, wall_right, wall_bottom, wall_top, wall_back, wall_front}; //! real index values of walls in the Scene int wall_id [6]; //! Stores the value of the translation at the previous time step, stiffness, and normal boost::array previousTranslation; //! The value of stiffness (updated according to stiffnessUpdateInterval) vector stiffness; Vector3r strain; Vector3r normal [6]; //! The values of stresses Vector3r stress [6]; Vector3r force [6]; //! Value of particles volume (solid volume of clumps and spheres) Real particlesVolume; //! Value of box volume Real boxVolume; //! Sample porosity Real porosity; Real max_vel1; Real max_vel2; Real max_vel3; Real position_top; Real position_bottom; Real position_right; Real position_left; Real position_front; Real position_back; virtual ~TriaxialStressController(); virtual void action(); //! Regulate the stress applied on walls with flag wall_XXX_activated = true void controlExternalStress(int wall, Vector3r resultantForce, State* p, Real wall_max_vel); //! Regulate the mean stress by changing spheres size, WARNING : this function assumes that all dynamic bodies in the problem are spheres void controlInternalStress(Real multiplier); //! update the stiffness of boundary-packing interaction (sum of contacts stiffness on the boundary) void updateStiffness(); //! Compute stresses on walls as "Vector3r stress[6]", compute meanStress, strain[3] and mean strain void computeStressStrain(); //! Compute the mean/max unbalanced force in the assembly (normalized by mean contact force) Real ComputeUnbalancedForce(bool maxUnbalanced=false); ///! Getter for stress and rates in python Vector3r getStress(int boundId); Vector3r getStrainRate(); YADE_CLASS_BASE_DOC_ATTRS_INIT_CTOR_PY(TriaxialStressController,BoundaryController, "An engine maintaining constant stresses or constant strain rates on some boundaries of a parallepipedic packing. The stress/strain control is defined for each axis using :yref:`TriaxialStressController::stressMask` (a bitMask) and target values are defined by goal1,goal2, and goal3. The sign conventions of continuum mechanics are used for strains and stresses (positive traction)." "\n\n.. note::\n\t The algorithms used have been developed initialy for simulations reported in [Chareyre2002a]_ and [Chareyre2005]_. They have been ported to Yade in a second step and used in e.g. [Kozicki2008]_,[Scholtes2009b]_,[Jerier2010b]." , ((unsigned int,stiffnessUpdateInterval,10,,"target strain rate (./s)")) ((unsigned int,radiusControlInterval,10,,"")) ((unsigned int,computeStressStrainInterval,10,,"")) ((Real,stressDamping,0.25,,"wall damping coefficient for the stress control - wallDamping=0 implies a (theoretical) perfect control, wallDamping=1 means no movement")) ((Real,strainDamping,0.99,,"coefficient used for smoother transitions in the strain rate. The rate reaches the target value like $d^n$ reaches 0, where $d$ is the damping coefficient and $n$ is the number of steps")) ((Real,thickness,-1,,"thickness of boxes (needed by some functions)")) ((int,wall_bottom_id,2,,"id of boundary ; coordinate 1- (default value is ok if aabbWalls are appended BEFORE spheres.)")) ((int,wall_top_id,3,,"id of boundary ; coordinate 1+ (default value is ok if aabbWalls are appended BEFORE spheres.)")) ((int,wall_left_id,0,,"id of boundary ; coordinate 0- (default value is ok if aabbWalls are appended BEFORE spheres.)")) ((int,wall_right_id,1,,"id of boundary ; coordinate 0+ (default value is ok if aabbWalls are appended BEFORE spheres.)")) ((int,wall_front_id,5,,"id of boundary ; coordinate 2+ (default value is ok if aabbWalls are appended BEFORE spheres.)")) ((int,wall_back_id,4,,"id of boundary ; coordinate 2- (default value is ok if aabbWalls are appended BEFORE spheres.)")) ((bool,wall_bottom_activated,true,,"if true, this wall moves according to the target value (stress or strain rate).")) ((bool,wall_top_activated,true,,"if true, this wall moves according to the target value (stress or strain rate).")) ((bool,wall_left_activated,true,,"if true, this wall moves according to the target value (stress or strain rate).")) ((bool,wall_right_activated,true,,"if true, this wall moves according to the target value (stress or strain rate).")) ((bool,wall_front_activated,true,,"if true, this wall moves according to the target value (stress or strain rate).")) ((bool,wall_back_activated,true,,"if true, this wall moves according to the target value (stress or strain rate).")) ((Real,height,0,Attr::readonly,"size of the box (1-axis) |yupdate|")) ((Real,width,0,Attr::readonly,"size of the box (0-axis) |yupdate|")) ((Real,depth,0,Attr::readonly,"size of the box (2-axis) |yupdate|")) ((Real,height0,0,,"Reference size for strain definition. See :yref:`TriaxialStressController::height`")) ((Real,width0,0,,"Reference size for strain definition. See :yref:`TriaxialStressController::width`")) ((Real,depth0,0,,"Reference size for strain definition. See :yref:`TriaxialStressController::depth`")) ((Real,goal1,0,,"prescribed stress/strain rate on axis 1, as defined by :yref:`TriaxialStressController::stressMask`")) ((Real,goal2,0,,"prescribed stress/strain rate on axis 2, as defined by :yref:`TriaxialStressController::stressMask`")) ((Real,goal3,0,,"prescribed stress/strain rate on axis 3, as defined by :yref:`TriaxialStressController::stressMask`")) ((unsigned int,stressMask,7,,"Bitmask determining wether the imposed :yref:`goal` values are stresses (0 for none, 7 for all, 1 for direction 1, 5 for directions 1 and 3, etc.) or strain rates")) ((Real,maxMultiplier,1.001,,"max multiplier of diameters during internal compaction (initial fast increase - :yref:`TriaxialStressController::finalMaxMultiplier` is used in a second stage)")) ((Real,finalMaxMultiplier,1.00001,,"max multiplier of diameters during internal compaction (secondary precise adjustment - :yref:`TriaxialStressController::maxMultiplier` is used in the initial stage)")) ((Real,max_vel,1,,"Maximum allowed walls velocity [m/s]. This value superseeds the one assigned by the stress controller if the later is higher. max_vel can be set to infinity in many cases, but sometimes helps stabilizing packings. Based on this value, different maxima are computed for each axis based on the dimensions of the sample, so that if each boundary moves at its maximum velocity, the strain rate will be isotropic (see e.g. :yref:`TriaxialStressController::max_vel1`).")) ((Real,previousStress,0,Attr::readonly,"|yupdate|")) ((Real,previousMultiplier,1,Attr::readonly,"|yupdate|")) ((bool,internalCompaction,true,,"Switch between 'external' (walls) and 'internal' (growth of particles) compaction.")) ((Real,meanStress,0,Attr::readonly,"Mean stress in the packing. |yupdate|")) ((Real,volumetricStrain,0,Attr::readonly,"Volumetric strain (see :yref:`TriaxialStressController::strain`). |yupdate|")) ((Real,externalWork,0,Attr::readonly,"Mechanical work associated to the boundary conditions, i.e. $\\int_{\\partial \\Omega} \\mat{T} \\cdot \\mat{u} ds$ with $\\mat{T}$ the surface traction and $\\mat{u}$ the displacement at the boundary. |yupdate|")) ((bool,updatePorosity,false,,"If true, :yref:`solid volume` will be updated once (will automatically reset to false after one calculation step) e.g. for porosity calculation purpose. Can be used when volume of particles changes during the simulation (e.g. when particles are erased or when clumps are created).")) , /* extra initializers */ , /* constructor */ strain = Vector3r::Zero(); first = true; stiffness.resize(6); previousTranslation.assign(Vector3r::Zero()); for (int i=0; i<6; ++i){normal[i]=stress[i]=force[i]=Vector3r::Zero();stiffness[i]=0;} normal[wall_bottom].y()=1; normal[wall_top].y()=-1; normal[wall_left].x()=1; normal[wall_right].x()=-1; normal[wall_front].z()=-1; normal[wall_back].z()=1; porosity=1; , .def_readonly("strain",&TriaxialStressController::strain,"Current strain in a vector (exx,eyy,ezz). The values reflect true (logarithmic) strain.") .def_readonly("strainRate",&TriaxialStressController::getStrainRate,"Current strain rate in a vector d/dt(exx,eyy,ezz).") .def_readonly("porosity",&TriaxialStressController::porosity,"Porosity of the packing, computed from :yref:`particlesVolume` and :yref:`boxVolume`. |yupdate|") .def_readonly("boxVolume",&TriaxialStressController::boxVolume,"Total packing volume.") .def_readonly("particlesVolume",&TriaxialStressController::particlesVolume,"Total volume of particles (clumps and :yref:`dynamic` spheres). |ycomp|") .def_readonly("spheresVolume",&TriaxialStressController::particlesVolume,"Shorthand for :yref:`TriaxialStressController::particlesVolume`") .def_readonly("max_vel1",&TriaxialStressController::max_vel1,"see :yref:`TriaxialStressController::max_vel` |ycomp|") .def_readonly("max_vel2",&TriaxialStressController::max_vel2,"see :yref:`TriaxialStressController::max_vel` |ycomp|") .def_readonly("max_vel3",&TriaxialStressController::max_vel3,"see :yref:`TriaxialStressController::max_vel` |ycomp|") .def("stress",&TriaxialStressController::getStress,(boost::python::arg("id")),"Returns the average stress on boundary 'id'. Here, 'id' refers to the internal numbering of boundaries, between 0 and 5.") ) DECLARE_LOGGER; }; REGISTER_SERIALIZABLE(TriaxialStressController); trunk-2018.02b/pkg/dem/TriaxialTest.cpp000066400000000000000000000344701324306050200176450ustar00rootroot00000000000000/************************************************************************* * Copyright (C) 2006 by Bruno Chareyre * * bruno.chareyre@hmg.inpg.fr * * * * This program is free software; it is licensed under the terms of the * * GNU General Public License v2 or later. See file LICENSE for details. * *************************************************************************/ #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 // random #include #include #include #include #include //#include #include "TriaxialTest.hpp" CREATE_LOGGER(TriaxialTest); YADE_PLUGIN((TriaxialTest)); TriaxialTest::~TriaxialTest () {} bool TriaxialTest::generate(string& message) { message=""; if(biaxial2dTest && (8.0*(upperCorner[2]-lowerCorner[2]))>(upperCorner[0]-lowerCorner[0])){ message="Biaxial test can be generated only if Z size is more than 8 times smaller than X size"; return false;} if(facetWalls&&wallWalls){ LOG_WARN("Turning TriaxialTest::facetWalls off, since wallWalls were selected as well."); } shared_ptr body; /* if _mean_radius is not given (i.e. <=0), then calculate it from box size; * OTOH, if it is specified, scale the box preserving its ratio and lowerCorner so that the radius can be as requested */ Real porosity=.8; SpherePack sphere_pack; if(importFilename==""){ Vector3r dimensions=upperCorner-lowerCorner; Real volume=dimensions.x()*dimensions.y()*dimensions.z(); long num; if(radiusMean<=0) num=sphere_pack.makeCloud(lowerCorner,upperCorner,-1,radiusStdDev,numberOfGrains,false /*periodic?*/,porosity); else { bool fixedDims[3]; fixedDims[0]=fixedBoxDims.find('x')!=string::npos; fixedDims[1]=fixedBoxDims.find('y')!=string::npos; fixedDims[2]=fixedBoxDims.find('z')!=string::npos; int nScaled=(3-(int)fixedDims[0]+(int)fixedDims[1]+(int)fixedDims[2]); if(nScaled==0) throw std::invalid_argument("At most 2 (not 3) axes can have fixed dimensions in fixedBoxDims if scaling for given radiusMean."); Real boxScaleFactor=radiusMean*pow((4/3.)*Mathr::PI*numberOfGrains/(volume*(1-porosity)),1./nScaled); LOG_INFO("Mean radius value of "<(num) + " spheres inside box of dimensions: (" + boost::lexical_cast(upperCorner[0]-lowerCorner[0]) + "," + boost::lexical_cast(upperCorner[1]-lowerCorner[1]) + "," + boost::lexical_cast(upperCorner[2]-lowerCorner[2]) + ").";} else { if(radiusMean>0) LOG_WARN("radiusMean ignored, since importFilename specified."); sphere_pack.fromFile(importFilename); sphere_pack.aabb(lowerCorner,upperCorner);} // setup scene here, since radiusMean is now at its true value (if it was negative) scene = shared_ptr(new Scene); positionRootBody(scene); createActors(scene); if(thickness<0) thickness=radiusMean; if(facetWalls || wallWalls) thickness=0; if(!facetWalls && !wallWalls){ // bottom box Vector3r center = Vector3r((lowerCorner[0]+upperCorner[0])/2, lowerCorner[1]-thickness/2.0, (lowerCorner[2]+upperCorner[2])/2); Vector3r halfSize = Vector3r(wallOversizeFactor*std::abs(lowerCorner[0]-upperCorner[0])/2+thickness, thickness/2.0, wallOversizeFactor*std::abs(lowerCorner[2]-upperCorner[2])/2+thickness); createBox(body,center,halfSize,true); scene->bodies->insert(body); triaxialcompressionEngine->wall_bottom_id = body->getId(); // top box center = Vector3r((lowerCorner[0]+upperCorner[0])/2, upperCorner[1]+thickness/2.0, (lowerCorner[2]+upperCorner[2])/2); halfSize = Vector3r(wallOversizeFactor*std::abs(lowerCorner[0]-upperCorner[0])/2+thickness, thickness/2.0, wallOversizeFactor*std::abs(lowerCorner[2]-upperCorner[2])/2+thickness); createBox(body,center,halfSize,true); scene->bodies->insert(body); triaxialcompressionEngine->wall_top_id = body->getId(); // box 1 center = Vector3r(lowerCorner[0]-thickness/2.0, (lowerCorner[1]+upperCorner[1])/2, (lowerCorner[2]+upperCorner[2])/2); halfSize = Vector3r(thickness/2.0, wallOversizeFactor*std::abs(lowerCorner[1]-upperCorner[1])/2+thickness, wallOversizeFactor*std::abs(lowerCorner[2]-upperCorner[2])/2+thickness); createBox(body,center,halfSize,true); scene->bodies->insert(body); triaxialcompressionEngine->wall_left_id = body->getId(); // box 2 center = Vector3r(upperCorner[0]+thickness/2.0, (lowerCorner[1]+upperCorner[1])/2, (lowerCorner[2]+upperCorner[2])/2); halfSize = Vector3r(thickness/2.0, wallOversizeFactor*std::abs(lowerCorner[1]-upperCorner[1])/2+thickness, wallOversizeFactor*std::abs(lowerCorner[2]-upperCorner[2])/2+thickness); createBox(body,center,halfSize,true); scene->bodies->insert(body); triaxialcompressionEngine->wall_right_id = body->getId(); // box 3 center = Vector3r((lowerCorner[0]+upperCorner[0])/2, (lowerCorner[1]+upperCorner[1])/2, lowerCorner[2]-thickness/2.0); halfSize = Vector3r(wallOversizeFactor*std::abs(lowerCorner[0]-upperCorner[0])/2+thickness, wallOversizeFactor*std::abs(lowerCorner[1]-upperCorner[1])/2+thickness, thickness/2.0); createBox(body,center,halfSize,true); scene->bodies->insert(body); triaxialcompressionEngine->wall_back_id = body->getId(); // box 4 center = Vector3r((lowerCorner[0]+upperCorner[0])/2, (lowerCorner[1]+upperCorner[1])/2, upperCorner[2]+thickness/2.0); halfSize = Vector3r(wallOversizeFactor*std::abs(lowerCorner[0]-upperCorner[0])/2+thickness, wallOversizeFactor*std::abs(lowerCorner[1]-upperCorner[1])/2+thickness, thickness/2.0); createBox(body,center,halfSize,true); scene->bodies->insert(body); triaxialcompressionEngine->wall_front_id = body->getId(); } size_t imax=sphere_pack.pack.size(); for(size_t i=0; istate->blockedDOFs=State::DOF_Z; } scene->bodies->insert(body); } if(defaultDt<0){ defaultDt=Shop::PWaveTimeStep(scene); scene->dt=defaultDt; globalStiffnessTimeStepper->defaultDt=defaultDt; LOG_INFO("Computed default (PWave) timestep "<& body, Vector3r position, Real radius, bool big, bool dynamic ) { body = shared_ptr(new Body); body->groupMask=2; shared_ptr aabb(new Aabb); shared_ptr iSphere(new Sphere); body->state->blockedDOFs=State::DOF_NONE; body->state->mass = 4.0/3.0*Mathr::PI*radius*radius*radius*density; body->state->inertia = Vector3r(2.0/5.0*body->state->mass*radius*radius, 2.0/5.0*body->state->mass*radius*radius, 2.0/5.0*body->state->mass*radius*radius); body->state->pos=position; shared_ptr mat(new FrictMat); mat->young = sphereYoungModulus; mat->poisson = sphereKsDivKn; mat->frictionAngle = compactionFrictionDeg * Mathr::PI/180.0; aabb->color = Vector3r(0,1,0); iSphere->radius = radius; //iSphere->color = Vector3r(0.4,0.1,0.1); iSphere->color = Vector3r(Mathr::UnitRandom(),Mathr::UnitRandom(),Mathr::UnitRandom()); iSphere->color.normalize(); body->shape = iSphere; body->bound = aabb; body->material = mat; } void TriaxialTest::createBox(shared_ptr& body, Vector3r position, Vector3r extents, bool wire) { body = shared_ptr(new Body); body->groupMask=2; body->state->blockedDOFs=State::DOF_ALL; shared_ptr aabb(new Aabb); aabb->color = Vector3r(1,0,0); body->bound = aabb; body->state->pos=position; shared_ptr mat(new FrictMat); mat->young = sphereYoungModulus; mat->poisson = sphereKsDivKn; mat->frictionAngle = boxFrictionDeg * Mathr::PI/180.0; body->material=mat; if(!facetWalls && !wallWalls){ shared_ptr iBox(new Box); iBox->extents = extents; iBox->wire = wire; iBox->color = Vector3r(1,1,1); body->shape = iBox; } // guess the orientation int ax0 = extents[0]==0 ? 0 : (extents[1]==0 ? 1 : 2); int ax1=(ax0+1)%3, ax2=(ax0+2)%3; if(facetWalls){ Vector3r corner=position-extents; // "lower right" corner, with 90 degrees Vector3r side1(Vector3r::Zero()); side1[ax1]=4*extents[ax1]; Vector3r side2(Vector3r::Zero()); side2[ax2]=4*extents[ax2]; Vector3r v[3]; v[0]=corner; v[1]=corner+side1; v[2]=corner+side2; Vector3r cog=Shop::inscribedCircleCenter(v[0],v[1],v[2]); shared_ptr iFacet(new Facet); for(int i=0; i<3; i++){ iFacet->vertices[i]=v[i]-cog;} iFacet->color=Vector3r(1,1,1); body->shape=iFacet; } if(wallWalls){ shared_ptr wall(new Wall); wall->sense=0; // interact from both sides, since unspecified here wall->axis=ax0; body->shape=wall; } } void TriaxialTest::createActors(shared_ptr& scene) { shared_ptr interactionGeometryDispatcher(new IGeomDispatcher); interactionGeometryDispatcher->add(new Ig2_Sphere_Sphere_ScGeom); interactionGeometryDispatcher->add(new Ig2_Facet_Sphere_ScGeom); interactionGeometryDispatcher->add(new Ig2_Box_Sphere_ScGeom); shared_ptr interactionPhysicsDispatcher(new IPhysDispatcher); shared_ptr ss(new Ip2_FrictMat_FrictMat_FrictPhys); interactionPhysicsDispatcher->add(ss); shared_ptr gravityCondition(new GravityEngine); gravityCondition->gravity = gravity; globalStiffnessTimeStepper=shared_ptr(new GlobalStiffnessTimeStepper); globalStiffnessTimeStepper->timeStepUpdateInterval = timeStepUpdateInterval; globalStiffnessTimeStepper->defaultDt = defaultDt; // moving walls to regulate the stress applied + compress when the packing is dense an stable //cerr << "triaxialcompressionEngine = shared_ptr (new TriaxialCompressionEngine);" << std::endl; triaxialcompressionEngine = shared_ptr (new TriaxialCompressionEngine); //This prevent the deprecation warning. In fact this preprocessor in itself is becoming deprecated triaxialcompressionEngine->warn = 1; triaxialcompressionEngine-> stiffnessUpdateInterval = wallStiffnessUpdateInterval;// = stiffness update interval triaxialcompressionEngine-> radiusControlInterval = radiusControlInterval;// = stiffness update interval triaxialcompressionEngine-> sigmaIsoCompaction = sigmaIsoCompaction; triaxialcompressionEngine-> sigmaLateralConfinement = sigmaLateralConfinement; triaxialcompressionEngine->max_vel = maxWallVelocity; triaxialcompressionEngine-> thickness = thickness; triaxialcompressionEngine->strainRate = strainRate; triaxialcompressionEngine->StabilityCriterion = StabilityCriterion; triaxialcompressionEngine->autoCompressionActivation = autoCompressionActivation; triaxialcompressionEngine->autoUnload = autoUnload; triaxialcompressionEngine->autoStopSimulation = autoStopSimulation; triaxialcompressionEngine->internalCompaction = internalCompaction; triaxialcompressionEngine->maxMultiplier = maxMultiplier; triaxialcompressionEngine->finalMaxMultiplier = finalMaxMultiplier; triaxialcompressionEngine->Key = Key; triaxialcompressionEngine->noFiles=noFiles; triaxialcompressionEngine->frictionAngleDegree = sphereFrictionDeg; triaxialcompressionEngine->fixedPoroCompaction = false; triaxialcompressionEngine->fixedPorosity=1; // recording global stress if(recordIntervalIter>0 && !noFiles){ triaxialStateRecorder = shared_ptr(new TriaxialStateRecorder); triaxialStateRecorder-> file = WallStressRecordFile + Key; triaxialStateRecorder-> iterPeriod = recordIntervalIter; } scene->engines.clear(); scene->engines.push_back(shared_ptr(new ForceResetter)); shared_ptr collider(new InsertionSortCollider); scene->engines.push_back(collider); collider->verletDist=.5*radiusMean; collider->boundDispatcher->add(new Bo1_Sphere_Aabb); collider->boundDispatcher->add(new Bo1_Box_Aabb); collider->boundDispatcher->add(new Bo1_Facet_Aabb); collider->boundDispatcher->add(new Bo1_Wall_Aabb); shared_ptr ids(new InteractionLoop); ids->geomDispatcher=interactionGeometryDispatcher; ids->physDispatcher=interactionPhysicsDispatcher; ids->lawDispatcher=shared_ptr(new LawDispatcher); shared_ptr see(new Law2_ScGeom_FrictPhys_CundallStrack); ids->lawDispatcher->add(see); scene->engines.push_back(ids); scene->engines.push_back(globalStiffnessTimeStepper); scene->engines.push_back(triaxialcompressionEngine); if(recordIntervalIter>0 && !noFiles) scene->engines.push_back(triaxialStateRecorder); shared_ptr newton(new NewtonIntegrator); newton->damping=dampingForce; scene->engines.push_back(newton); } void TriaxialTest::positionRootBody(shared_ptr& scene) { } trunk-2018.02b/pkg/dem/TriaxialTest.hpp000066400000000000000000000224651324306050200176530ustar00rootroot00000000000000/************************************************************************* * Copyright (C) 2006 by Bruno Chareyre * * bruno.chareyre@hmg.inpg.fr * * * * This program is free software; it is licensed under the terms of the * * GNU General Public License v2 or later. See file LICENSE for details. * *************************************************************************/ #pragma once #include class ForceRecorder; class AveragePositionRecorder; class VelocityRecorder; class TriaxialStressController; class TriaxialCompressionEngine; class TriaxialStateRecorder; class GlobalStiffnessTimeStepper; /*! \brief Isotropic compression + triaxial compression test TriaxialTest full documentation is here : http://yade-dem.org/wiki/TriaxialTest This preprocessor shows how to simulate a triaxial test in Yade. It is using the elastic-frictional contact law defined in ElasticContactLaw (similar procedures can be used for other contact laws). It is designed to : 1/ generate random loose packings and compress them under isotropic confining stress, either squeezing the packing between moving rigid boxes or expanding the particles while boxes are fixed (depending on flag "InternalCompaction"). 2/ simulate all sorts triaxial loading path (there is however a default loading path corresponding to constant lateral stress in 2 directions and constant strain rate on the third direction - this loading path is used when the flag AutoCompressionActivation = true, otherwise the simulation stops after isotropic compression) Essential engines : 1/ The TriaxialCompressionEngine is used for controlling the state of the sample and simulating loading paths. TriaxialCompressionEngine inherits from TriaxialStressController, which can compute stress- strain-like quantities in the packing and maintain a constant level of stress at each boundary. TriaxialCompressionEngine has few more members in order to impose constant strain rate and control the transition between isotropic compression and triaxial test. 2/ The class TriaxialStateRecorder is used to write to a file the history of stresses and strains. 3/ TriaxialTest is currently using GlobalStiffnessTimeStepper to compute an appropriate dt for the numerical scheme. The TriaxialTest is the only preprocessor using these classes in Yade because they have been developed AFTER most of preprocessor examples, BUT they can be used in principle in any situation and they have nothing specifically related to the triaxial test. @note TriaxialStressController::ComputeUnbalancedForce(...) returns a value that can be useful for evaluating the stability of the packing. It is defined as (mean force on particles)/(mean contact force), so that it tends to 0 in a stable packing. This parameter is checked by TriaxialCompressionEngine to switch from one stage of the simulation to the next one (e.g. stop isotropic confinment and start axial loading) @note Compaction is done (1) by moving rigid boxes or (2) by increasing the sizes of the particles (decided using the option "internalCompaction" : true => size increase). Both algorithm needs numerical parameters to prevent instabilities. For instance, with method (1) maxWallVelocity is the maximum wall velocity, with method (2) finalMaxMultiplier is the max value of the multiplier applied on sizes at each iteration (always something like 1.00001). */ class TriaxialTest : public FileGenerator { private : Vector3r gravity; Vector3r spheresColor; bool spheresRandomColor; shared_ptr triaxialcompressionEngine; shared_ptr triaxialstressController; shared_ptr triaxialStateRecorder; shared_ptr globalStiffnessTimeStepper; void createBox(shared_ptr& body, Vector3r position, Vector3r extents,bool wire); void createSphere(shared_ptr& body, Vector3r position, Real radius,bool big,bool dynamic); void createActors(shared_ptr& scene); void positionRootBody(shared_ptr& scene); typedef pair BasicSphere; public : ~TriaxialTest (); bool generate(string& message); YADE_CLASS_BASE_DOC_ATTRS_INIT_CTOR_PY( TriaxialTest,FileGenerator,"Prepare a scene for triaxial tests. Full documentation in py/_extraDocs.py." , ((Vector3r,lowerCorner,Vector3r(0,0,0),,"Lower corner of the box.")) ((Vector3r,upperCorner,Vector3r(1,1,1),,"Upper corner of the box.")) ((string,importFilename,"",,"File with positions and sizes of spheres.")) ((string,Key,"",,"A code that is added to output filenames.")) ((string,fixedBoxDims,"",,"string that contains some subset (max. 2) of {'x','y','z'} ; contains axes will have box dimension hardcoded, even if box is scaled as mean_radius is prescribed: scaling will be applied on the rest.")) ((string,WallStressRecordFile,"./WallStresses"+Key,,"")) ((bool,internalCompaction,false,,"flag for choosing between moving boundaries or increasing particles sizes during the compaction stage.")) ((bool,biaxial2dTest,false,,"FIXME : what is that?")) ((bool,autoCompressionActivation,true,,"Do we just want to generate a stable packing under isotropic pressure (false) or do we want the triaxial loading to start automatically right after compaction stage (true)?")) ((bool,autoUnload,true,,"auto adjust the isotropic stress state from :yref:`TriaxialTest::sigmaIsoCompaction` to :yref:`TriaxialTest::sigmaLateralConfinement` if they have different values. See docs for :yref:`TriaxialCompressionEngine::autoUnload`")) ((bool,autoStopSimulation,false,,"freeze the simulation when conditions are reached (don't activate this if you want to be able to run/stop from Qt GUI)")) ((bool,noFiles,false,,"Do not create any files during run (.xml, .spheres, wall stress records)")) ((bool,facetWalls,false,,"Use facets for boundaries (not tested)")) ((bool,wallWalls,false,,"Use walls for boundaries (not tested)")) ((Real,thickness,0.001,,"thickness of boundaries. It is arbitrary and should have no effect")) ((Real,maxMultiplier,1.01,,"max multiplier of diameters during internal compaction (initial fast increase)")) ((Real,finalMaxMultiplier,1.001,,"max multiplier of diameters during internal compaction (secondary precise adjustment)")) ((Real,radiusStdDev,0.3,,"Normalized standard deviation of generated sizes.")) ((Real,radiusMean,-1,,"Mean radius. If negative (default), autocomputed to as a function of box size and :yref:`TriaxialTest::numberOfGrains`")) ((Real,sphereYoungModulus,15000000.0,,"Stiffness of spheres.")) ((Real,sphereKsDivKn,0.5,,"Ratio of shear vs. normal contact stiffness for spheres.")) ((Real,sphereFrictionDeg,18.0,,"Friction angle [°] of spheres assigned just before triaxial testing.")) ((Real,compactionFrictionDeg,sphereFrictionDeg,,"Friction angle [°] of spheres during compaction (different values result in different porosities)]. This value is overridden by :yref:`TriaxialTest::sphereFrictionDeg` before triaxial testing.")) ((Real,boxYoungModulus,15000000.0,,"Stiffness of boxes.")) ((Real,maxWallVelocity,10,,"max velocity of boundaries. Usually useless, but can help stabilizing the system in some cases.")) ((Real,boxKsDivKn,0.5,,"Ratio of shear vs. normal contact stiffness for boxes.")) ((Real,boxFrictionDeg,0.0,,"Friction angle [°] of boundaries contacts.")) ((Real,density,2600,,"density of spheres")) ((Real,strainRate,0.1,,"Strain rate in triaxial loading.")) ((Real,defaultDt,-1,,"Max time-step. Used as initial value if defined. Latter adjusted by the time stepper.")) ((Real,dampingForce,0.2,,"Coefficient of Cundal-Non-Viscous damping (applied on on the 3 components of forces)")) ((Real,dampingMomentum,0.2,,"Coefficient of Cundal-Non-Viscous damping (applied on on the 3 components of torques)")) ((Real,StabilityCriterion,0.01,,"Value of unbalanced force for which the system is considered stable. Used in conditionals to switch between loading stages.")) ((Real,wallOversizeFactor,1.3,,"Make boundaries larger than the packing to make sure spheres don't go out during deformation.")) ((Real,sigmaIsoCompaction,-50000,,"Confining stress during isotropic compaction (< 0 for real - compressive - compaction).")) ((Real,sigmaLateralConfinement,-50000,,"Lateral stress during triaxial loading (< 0 for classical compressive cases). An isotropic unloading is performed if the value is not equal to :yref:`TriaxialTest::sigmaIsoCompaction`.")) ((int,timeStepUpdateInterval,50,,"interval for :yref:`GlobalStiffnessTimeStepper`")) ((int,wallStiffnessUpdateInterval,10,,"interval for updating the stiffness of sample/boundaries contacts")) ((int,radiusControlInterval,10,,"interval between size changes when growing spheres.")) ((int,numberOfGrains,400,,"Number of generated spheres.")) ((int,recordIntervalIter,20,,"interval between file outputs")) , /* init */ , /* constructor */ spheresColor = Vector3r(0.8,0.3,0.3); spheresRandomColor = false; WallStressRecordFile = "./WallStresses"+Key; gravity = Vector3r(0,-9.81,0); , //.def("setContactProperties",&TriaxialCompressionEngine::setContactProperties,"Assign a new friction angle (degrees) to dynamic bodies and relative interactions") ); DECLARE_LOGGER; }; REGISTER_SERIALIZABLE(TriaxialTest); trunk-2018.02b/pkg/dem/UnbalancedForceCallbacks.cpp000066400000000000000000000032131324306050200220320ustar00rootroot00000000000000#include #include #include #include #include YADE_PLUGIN((SumIntrForcesCb) #ifdef YADE_BODY_CALLBACK (SumBodyForcesCb) #endif ); IntrCallback::FuncPtr SumIntrForcesCb::stepInit(){ // if(scene->iter%100 != 0) return NULL; cerr<<"("<<(Real)force<<","<<(int)numIntr<<")"; // reset accumulators force.reset(); numIntr.reset(); // return function pointer return &SumIntrForcesCb::go; } void SumIntrForcesCb::go(IntrCallback* _self, Interaction* i){ SumIntrForcesCb* self=static_cast(_self); NormShearPhys* nsp=YADE_CAST(i->phys.get()); assert(nsp!=NULL); // only effective in debug mode Vector3r f=nsp->normalForce+nsp->shearForce; if(f==Vector3r::Zero()) return; self->numIntr+=1; self->force+=f.norm(); //cerr<<"[cb#"<getId1()<<"+"<getId2()<<"]"; } #ifdef YADE_BODY_CALLBACK BodyCallback::FuncPtr SumBodyForcesCb::stepInit(){ cerr<<"{"<<(Real)force<<","<<(int)numBodies<<",this="<forces)<<"}"; force.reset(); numBodies.reset(); // reset accumulators return &SumBodyForcesCb::go; } void SumBodyForcesCb::go(BodyCallback* _self, Body* b){ if(b->state->blockedDOFs==State::DOF_ALL) return; SumBodyForcesCb* self=static_cast(_self); #ifdef YADE_OPENMP cerr<<"["<id<<",scene="<scene<<"]"; #endif cerr<<"[force="<scene->forces.getForce(b->id)<<"]"; self->numBodies+=1; //self->scene->forces.sync(); self->force+=self->scene->forces.getForce(b->id).norm(); } #endif trunk-2018.02b/pkg/dem/UnbalancedForceCallbacks.hpp000066400000000000000000000020221324306050200220340ustar00rootroot00000000000000// 2010 © Václav Šmilauer #pragma once #include #include class SumIntrForcesCb: public IntrCallback{ public: OpenMPAccumulator numIntr; OpenMPAccumulator force; static void go(IntrCallback*,Interaction*); virtual IntrCallback::FuncPtr stepInit(); YADE_CLASS_BASE_DOC(SumIntrForcesCb,IntrCallback,"Callback summing magnitudes of forces over all interactions. :yref:`IPhys` of interactions must derive from :yref:`NormShearPhys` (responsability fo the user)."); }; REGISTER_SERIALIZABLE(SumIntrForcesCb); #ifdef YADE_BODY_CALLBACK class SumBodyForcesCb: public BodyCallback{ Scene* scene; public: OpenMPAccumulator numBodies; OpenMPAccumulator force; static void go(BodyCallback*,Body*); virtual BodyCallback::FuncPtr stepInit(); YADE_CLASS_BASE_DOC(SumBodyForcesCb,BodyCallback,"Callback summing magnitudes of resultant forces over :yref:`dynamic` bodies."); }; REGISTER_SERIALIZABLE(SumBodyForcesCb); #endif trunk-2018.02b/pkg/dem/UniaxialStrainer.cpp000066400000000000000000000155621324306050200205130ustar00rootroot00000000000000// 2008 © Václav Šmilauer #include"UniaxialStrainer.hpp" #include #include #include YADE_PLUGIN((UniaxialStrainer)); /************************ UniaxialStrainer **********************/ CREATE_LOGGER(UniaxialStrainer); void UniaxialStrainer::init(){ needsInit=false; assert(posIds.size()>0); assert(negIds.size()>0); posCoords.clear(); negCoords.clear(); FOREACH(Body::id_t id,posIds){ const shared_ptr& b=Body::byId(id,scene); posCoords.push_back(b->state->pos[axis]); if(blockDisplacements && blockRotations) b->state->blockedDOFs=State::DOF_ALL; else{ if(!blockDisplacements) b->state->blockedDOFs=State::axisDOF(axis); else b->state->blockedDOFs=State::DOF_XYZ; if(blockRotations) b->state->blockedDOFs|=State::DOF_RXRYRZ; } } FOREACH(Body::id_t id,negIds){ const shared_ptr& b=Body::byId(id,scene); negCoords.push_back(b->state->pos[axis]); if(blockDisplacements && blockRotations) b->state->blockedDOFs=State::DOF_ALL; else{ if(!blockDisplacements) b->state->blockedDOFs=State::axisDOF(axis); else b->state->blockedDOFs=State::DOF_XYZ; if(blockRotations) b->state->blockedDOFs|=State::DOF_RXRYRZ; } } assert(posIds.size()==posCoords.size() && negIds.size()==negCoords.size()); originalLength=axisCoord(posIds[0])-axisCoord(negIds[0]); LOG_DEBUG("Reference particles: positive #"<(originalLength)).c_str()); /* this happens is nan propagates from e.g. brefcom consitutive law in case 2 bodies have _exactly_ the same position * (the the normal strain is 0./0.=nan). That is an user's error, however and should not happen. */ if(std::isnan(originalLength)) throw logic_error("UniaxialStrainer: Initial length is NaN!"); assert(originalLength>0 && !std::isnan(originalLength)); assert(!std::isnan(strainRate) || !std::isnan(absSpeed)); if(!std::isnan(std::numeric_limits::quiet_NaN())){ throw runtime_error("UniaxialStrainer: NaN's are not properly supported (compiled with -ffast-math?), which is required."); } if(std::isnan(strainRate)){ strainRate=absSpeed/originalLength; LOG_INFO("Computed new strainRate "<=0 ? initAccelTime : scene->dt*(-initAccelTime); LOG_INFO("Strain speed will be "<dt<<" steps)."); } else { /* set speed such that it is linear on the strained axis; transversal speed is not set, which can perhaps create some problems. Note: all bodies in the simulation will have their speed set, since there is no way to tell which ones are part of the specimen and which are not. Speeds will be linearly interpolated beween axis positions p0,p1 and velocities v0,v1. */ initAccelTime_s=0; LOG_INFO("Strain speed will be "<(asymmetry)+" (should be -1,0,1)").c_str()); } assert(p1>p0); // set speeds for particles on the boundary FOREACH(const shared_ptr& b, *scene->bodies){ // skip bodies on the boundary, since those will have their positions updated directly if(std::find(posIds.begin(),posIds.end(),b->id)!=posIds.end() || std::find(negIds.begin(),negIds.end(),b->id)!=negIds.end()) { continue; } Real p=axisCoord(b->id); Real pNormalized=(p-p0)/(p1-p0); b->state->vel[axis]=pNormalized*(v1-v0)+v0; } } if(std::isnan(crossSectionArea)){ throw std::invalid_argument("UniaxialStrain.crossSectionArea must be specified."); } } void UniaxialStrainer::action(){ if(needsInit) init(); // postconditions for initParams assert(posIds.size()==posCoords.size() && negIds.size()==negCoords.size() && originalLength>0 && crossSectionArea>0); //nothing to do if(posIds.size()==0 || negIds.size()==0) return; // linearly increase strain to the desired value if(std::abs(currentStrainRate)time/initAccelTime_s)*strainRate; else currentStrainRate=strainRate; } else currentStrainRate=strainRate; // how much do we move (in total, symmetry handled below) Real dAX=currentStrainRate*originalLength*scene->dt; if(!std::isnan(stopStrain)){ Real axialLength=axisCoord(posIds[0])-axisCoord(negIds[0]); Real newStrain=(axialLength+dAX)/originalLength-1; if((newStrain*stopStrain>0) && std::abs(newStrain)>=stopStrain){ // same sign of newStrain and stopStrain && over the limit from below in abs values dAX=originalLength*(stopStrain+1)-axialLength; LOG_INFO("Reached stopStrain "<active=false; scene->stopAtIter=scene->iter+1+idleIterations; } } if(asymmetry==0) dAX*=.5; // apply half on both sides if straining symetrically if(asymmetry!=1){ for(size_t i=0; ipos is useless with curent velocity defined below, and use of NewtonIntegrator axisVel(negIds[i]) = -dAX/scene->dt; // update current position } } if(asymmetry!=-1){ for(size_t i=0; idt; } } Real axialLength=axisCoord(posIds[0])-axisCoord(negIds[0]); strain=axialLength/originalLength-1; // reverse if we're over the limit strain if(notYetReversed && limitStrain!=0 && ((currentStrainRate>0 && strain>limitStrain) || (currentStrainRate<0 && strainiter%stressUpdateInterval==0) { computeAxialForce(); avgStress=(sumPosForces+sumNegForces)/(2*crossSectionArea); // average nominal stress } } void UniaxialStrainer::computeAxialForce(){ sumPosForces=sumNegForces=0; scene->forces.sync(); FOREACH(Body::id_t id, negIds) sumNegForces+=scene->forces.getForce(id)[axis]; FOREACH(Body::id_t id, posIds) sumPosForces-=scene->forces.getForce(id)[axis]; } trunk-2018.02b/pkg/dem/UniaxialStrainer.hpp000066400000000000000000000075661324306050200205250ustar00rootroot00000000000000// 2008 © Václav Šmilauer #pragma once #include #include #include /*! Axial displacing two groups of bodies in the opposite direction with given strain rate. * * Takes two groups of body IDs (in posIds and negIds) and displaces them at each timestep in the direction given by axis∈{0,1,2} (for axes x,y,z respectively). These bodies automatically have Body::isDynamic==false. * * This engine should be run once forces on particles have been computed. */ class UniaxialStrainer: public BoundaryController { private: bool needsInit; void computeAxialForce(); Real axisCoord(Body::id_t id){ return Body::byId(id,scene)->state->pos[axis]; }; Real& axisVel(Body::id_t id){ return Body::byId(id,scene)->state->vel[axis]; }; void init(); public: virtual bool isActivated(){ return active; } Real sumPosForces,sumNegForces; Real initAccelTime_s /* value always in s, computed from initAccelTime */; /** coordinates of pos/neg bodies in the direction of axis */ vector posCoords,negCoords; virtual void action(); YADE_CLASS_BASE_DOC_ATTRS_CTOR(UniaxialStrainer,BoundaryController,"Axial displacing two groups of bodies in the opposite direction with given strain rate.", ((Real,strainRate,NaN,,"Rate of strain, starting at 0, linearly raising to strainRate. [-]")) ((Real,absSpeed,NaN,,"alternatively, absolute speed of boundary motion can be specified; this is effective only at the beginning and if strainRate is not set; changing absSpeed directly during simulation wil have no effect. [ms⁻¹]")) ((Real,initAccelTime,-200,,"Time for strain reaching the requested value (linear interpolation). If negative, the time is dt*(-initAccelTime), where dt is the timestep at the first iteration. [s]")) ((Real,stopStrain,NaN,,"Strain at which we will pause simulation; inactive (nan) by default; must be reached from below (in absolute value)")) ((bool,active,true,,"Whether this engine is activated")) ((long,idleIterations,0,,"Number of iterations that will pass without straining activity after stopStrain has been reached")) ((Real,currentStrainRate,NaN,,"Current strain rate (update automatically). |yupdate|")) ((int,axis,2,,"The axis which is strained (0,1,2 for x,y,z)")) ((int,asymmetry,((void)"symmetric",0),,"If 0, straining is symmetric for negIds and posIds; for 1 (or -1), only posIds are strained and negIds don't move (or vice versa)")) ((vector,posIds,,,"Bodies on which strain will be applied (on the positive end along the axis)")) ((vector,negIds,,,"Bodies on which strain will be applied (on the negative end along the axis)")) ((Real,originalLength,NaN,,"Distance of reference bodies in the direction of axis before straining started (computed automatically) [m]")) ((Real,limitStrain,((void)"disabled",0),,"Invert the sense of straining (sharply, without transition) one this value of strain is reached. Not effective if 0.")) ((bool,notYetReversed,true,,"Flag whether the sense of straining has already been reversed (only used internally).")) ((Real,crossSectionArea,NaN,,"crossSection perpendicular to he strained axis; must be given explicitly [m²]")) ((Real,strain,0,,"Current strain value, elongation/originalLength |yupdate| [-]")) ((Real,avgStress,0,,"Current average stress |yupdate| [Pa]")) ((bool,blockDisplacements,false,,"Whether displacement of boundary bodies perpendicular to the strained axis are blocked or are free")) ((bool,blockRotations,false,,"Whether rotations of boundary bodies are blocked.")) ((bool,setSpeeds,false,,"should we set speeds at the beginning directly, instead of increasing strain rate progressively?")) ((int,stressUpdateInterval,10,,"How often to recompute stress on supports.")), /*ctor*/ needsInit=true; ); DECLARE_LOGGER; }; REGISTER_SERIALIZABLE(UniaxialStrainer); trunk-2018.02b/pkg/dem/VTKRecorder.cpp000066400000000000000000001325721324306050200173640ustar00rootroot00000000000000#ifdef YADE_VTK #include"VTKRecorder.hpp" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef YADE_VTK_MULTIBLOCK #include #include #endif #include #include #include #include #include #include #include #include #ifdef YADE_LIQMIGRATION #include #endif YADE_PLUGIN((VTKRecorder)); CREATE_LOGGER(VTKRecorder); #ifdef YADE_MASK_ARBITRARY #define GET_MASK(b) b->groupMask.to_ulong() #else #define GET_MASK(b) b->groupMask #endif #include #include #include void VTKRecorder::action(){ vector recActive(REC_SENTINEL,false); FOREACH(string& rec, recorders){ if(rec=="all"){ recActive[REC_SPHERES]=true; recActive[REC_VELOCITY]=true; recActive[REC_FACETS]=true; recActive[REC_BOXES]=true; recActive[REC_COLORS]=true; recActive[REC_MASS]=true; recActive[REC_INTR]=true; recActive[REC_ID]=true; recActive[REC_MASK]=true; recActive[REC_CLUMPID]=true; recActive[REC_MATERIALID]=true; recActive[REC_STRESS]=true; recActive[REC_FORCE]=true; recActive[REC_COORDNUMBER]=true; if (scene->isPeriodic) { recActive[REC_PERICELL]=true; } } else if(rec=="spheres") recActive[REC_SPHERES]=true; else if(rec=="velocity") recActive[REC_VELOCITY]=true; else if(rec=="facets") recActive[REC_FACETS]=true; else if(rec=="boxes") recActive[REC_BOXES]=true; else if(rec=="mass") recActive[REC_MASS]=true; else if((rec=="colors") || (rec=="color"))recActive[REC_COLORS]=true; else if(rec=="cpm") recActive[REC_CPM]=true; else if(rec=="wpm") recActive[REC_WPM]=true; else if(rec=="intr") recActive[REC_INTR]=true; else if((rec=="ids") || (rec=="id")) recActive[REC_ID]=true; else if(rec=="mask") recActive[REC_MASK]=true; else if((rec=="clumpids") || (rec=="clumpId")) recActive[REC_CLUMPID]=true; else if(rec=="materialId") recActive[REC_MATERIALID]=true; else if(rec=="stress") recActive[REC_STRESS]=true; else if(rec=="force") recActive[REC_FORCE]=true; else if(rec=="jcfpm") recActive[REC_JCFPM]=true; else if(rec=="cracks") recActive[REC_CRACKS]=true; else if(rec=="pericell" && scene->isPeriodic) recActive[REC_PERICELL]=true; else if(rec=="liquidcontrol") recActive[REC_LIQ]=true; else if(rec=="bstresses") recActive[REC_BSTRESS]=true; else if(rec=="coordNumber") recActive[REC_COORDNUMBER]=true; else LOG_ERROR("Unknown recorder named `"< spheresPos = vtkSmartPointer::New(); vtkSmartPointer spheresCells = vtkSmartPointer::New(); vtkSmartPointer radii = vtkSmartPointer::New(); radii->SetNumberOfComponents(1); radii->SetName("radii"); vtkSmartPointer spheresSigI = vtkSmartPointer::New(); spheresSigI->SetNumberOfComponents(1); spheresSigI->SetName("sigI"); vtkSmartPointer spheresSigII = vtkSmartPointer::New(); spheresSigII->SetNumberOfComponents(1); spheresSigII->SetName("sigII"); vtkSmartPointer spheresSigIII = vtkSmartPointer::New(); spheresSigIII->SetNumberOfComponents(1); spheresSigIII->SetName("sigIII"); vtkSmartPointer spheresDirI = vtkSmartPointer::New(); spheresDirI->SetNumberOfComponents(3); spheresDirI->SetName("dirI"); vtkSmartPointer spheresDirII = vtkSmartPointer::New(); spheresDirII->SetNumberOfComponents(3); spheresDirII->SetName("dirII"); vtkSmartPointer spheresDirIII = vtkSmartPointer::New(); spheresDirIII->SetNumberOfComponents(3); spheresDirIII->SetName("dirIII"); vtkSmartPointer spheresMass = vtkSmartPointer::New(); spheresMass->SetNumberOfComponents(1); spheresMass->SetName("mass"); vtkSmartPointer spheresId = vtkSmartPointer::New(); spheresId->SetNumberOfComponents(1); spheresId->SetName("id"); #ifdef YADE_SPH vtkSmartPointer spheresRhoSPH = vtkSmartPointer::New(); spheresRhoSPH->SetNumberOfComponents(1); spheresRhoSPH->SetName("SPH_Rho"); vtkSmartPointer spheresPressSPH = vtkSmartPointer::New(); spheresPressSPH->SetNumberOfComponents(1); spheresPressSPH->SetName("SPH_Press"); vtkSmartPointer spheresCoordNumbSPH = vtkSmartPointer::New(); spheresCoordNumbSPH->SetNumberOfComponents(1); spheresCoordNumbSPH->SetName("SPH_Neigh"); #endif #ifdef YADE_DEFORM vtkSmartPointer spheresRealRad = vtkSmartPointer::New(); spheresRealRad->SetNumberOfComponents(1); spheresRealRad->SetName("RealRad"); #endif #ifdef YADE_LIQMIGRATION vtkSmartPointer spheresLiqVol = vtkSmartPointer::New(); spheresLiqVol->SetNumberOfComponents(1); spheresLiqVol->SetName("Liq_Vol"); vtkSmartPointer spheresLiqVolIter = vtkSmartPointer::New(); spheresLiqVolIter->SetNumberOfComponents(1); spheresLiqVolIter->SetName("Liq_VolIter"); vtkSmartPointer spheresLiqVolTotal = vtkSmartPointer::New(); spheresLiqVolTotal->SetNumberOfComponents(1); spheresLiqVolTotal->SetName("Liq_VolTotal"); #endif vtkSmartPointer spheresMask = vtkSmartPointer::New(); spheresMask->SetNumberOfComponents(1); spheresMask->SetName("mask"); vtkSmartPointer spheresCoordNumb = vtkSmartPointer::New(); spheresCoordNumb->SetNumberOfComponents(1); spheresCoordNumb->SetName("coordNumber"); vtkSmartPointer clumpId = vtkSmartPointer::New(); clumpId->SetNumberOfComponents(1); clumpId->SetName("clumpId"); vtkSmartPointer spheresColors = vtkSmartPointer::New(); spheresColors->SetNumberOfComponents(3); spheresColors->SetName("color"); vtkSmartPointer spheresLinVelVec = vtkSmartPointer::New(); spheresLinVelVec->SetNumberOfComponents(3); spheresLinVelVec->SetName("linVelVec"); //Linear velocity in Vector3 form vtkSmartPointer spheresLinVelLen = vtkSmartPointer::New(); spheresLinVelLen->SetNumberOfComponents(1); spheresLinVelLen->SetName("linVelLen"); //Length (magnitude) of linear velocity vtkSmartPointer spheresAngVelVec = vtkSmartPointer::New(); spheresAngVelVec->SetNumberOfComponents(3); spheresAngVelVec->SetName("angVelVec"); //Angular velocity in Vector3 form vtkSmartPointer spheresAngVelLen = vtkSmartPointer::New(); spheresAngVelLen->SetNumberOfComponents(1); spheresAngVelLen->SetName("angVelLen"); //Length (magnitude) of angular velocity vtkSmartPointer spheresNormalStressVec = vtkSmartPointer::New(); spheresNormalStressVec->SetNumberOfComponents(3); spheresNormalStressVec->SetName("normalStress"); vtkSmartPointer spheresShearStressVec = vtkSmartPointer::New(); spheresShearStressVec->SetNumberOfComponents(3); spheresShearStressVec->SetName("shearStress"); vtkSmartPointer spheresNormalStressNorm = vtkSmartPointer::New(); spheresNormalStressNorm->SetNumberOfComponents(1); spheresNormalStressNorm->SetName("normalStressNorm"); vtkSmartPointer spheresMaterialId = vtkSmartPointer::New(); spheresMaterialId->SetNumberOfComponents(1); spheresMaterialId->SetName("materialId"); vtkSmartPointer spheresForceVec = vtkSmartPointer::New(); spheresForceVec->SetNumberOfComponents(3); spheresForceVec->SetName("forceVec"); vtkSmartPointer spheresForceLen = vtkSmartPointer::New(); spheresForceLen->SetNumberOfComponents(1); spheresForceLen->SetName("forceLen"); vtkSmartPointer spheresTorqueVec = vtkSmartPointer::New(); spheresTorqueVec->SetNumberOfComponents(3); spheresTorqueVec->SetName("torqueVec"); vtkSmartPointer spheresTorqueLen = vtkSmartPointer::New(); spheresTorqueLen->SetNumberOfComponents(1); spheresTorqueLen->SetName("torqueLen"); // facets vtkSmartPointer facetsPos = vtkSmartPointer::New(); vtkSmartPointer facetsCells = vtkSmartPointer::New(); vtkSmartPointer facetsColors = vtkSmartPointer::New(); facetsColors->SetNumberOfComponents(3); facetsColors->SetName("color"); vtkSmartPointer facetsStressVec = vtkSmartPointer::New(); facetsStressVec->SetNumberOfComponents(3); facetsStressVec->SetName("stressVec"); vtkSmartPointer facetsStressLen = vtkSmartPointer::New(); facetsStressLen->SetNumberOfComponents(1); facetsStressLen->SetName("stressLen"); vtkSmartPointer facetsMaterialId = vtkSmartPointer::New(); facetsMaterialId->SetNumberOfComponents(1); facetsMaterialId->SetName("materialId"); vtkSmartPointer facetsMask = vtkSmartPointer::New(); facetsMask->SetNumberOfComponents(1); facetsMask->SetName("mask"); vtkSmartPointer facetsForceVec = vtkSmartPointer::New(); facetsForceVec->SetNumberOfComponents(3); facetsForceVec->SetName("forceVec"); vtkSmartPointer facetsForceLen = vtkSmartPointer::New(); facetsForceLen->SetNumberOfComponents(1); facetsForceLen->SetName("forceLen"); vtkSmartPointer facetsTorqueVec = vtkSmartPointer::New(); facetsTorqueVec->SetNumberOfComponents(3); facetsTorqueVec->SetName("torqueVec"); vtkSmartPointer facetsTorqueLen = vtkSmartPointer::New(); facetsTorqueLen->SetNumberOfComponents(1); facetsTorqueLen->SetName("torqueLen"); vtkSmartPointer facetsCoordNumb = vtkSmartPointer::New(); facetsCoordNumb->SetNumberOfComponents(1); facetsCoordNumb->SetName("coordNumber"); // boxes vtkSmartPointer boxesPos = vtkSmartPointer::New(); vtkSmartPointer boxesCells = vtkSmartPointer::New(); vtkSmartPointer boxesColors = vtkSmartPointer::New(); boxesColors->SetNumberOfComponents(3); boxesColors->SetName("color"); vtkSmartPointer boxesStressVec = vtkSmartPointer::New(); boxesStressVec->SetNumberOfComponents(3); boxesStressVec->SetName("stressVec"); vtkSmartPointer boxesStressLen = vtkSmartPointer::New(); boxesStressLen->SetNumberOfComponents(1); boxesStressLen->SetName("stressLen"); vtkSmartPointer boxesMaterialId = vtkSmartPointer::New(); boxesMaterialId->SetNumberOfComponents(1); boxesMaterialId->SetName("materialId"); vtkSmartPointer boxesMask = vtkSmartPointer::New(); boxesMask->SetNumberOfComponents(1); boxesMask->SetName("mask"); vtkSmartPointer boxesForceVec = vtkSmartPointer::New(); boxesForceVec->SetNumberOfComponents(3); boxesForceVec->SetName("forceVec"); vtkSmartPointer boxesForceLen = vtkSmartPointer::New(); boxesForceLen->SetNumberOfComponents(1); boxesForceLen->SetName("forceLen"); vtkSmartPointer boxesTorqueVec = vtkSmartPointer::New(); boxesTorqueVec->SetNumberOfComponents(3); boxesTorqueVec->SetName("torqueVec"); vtkSmartPointer boxesTorqueLen = vtkSmartPointer::New(); boxesTorqueLen->SetNumberOfComponents(1); boxesTorqueLen->SetName("torqueLen"); // interactions vtkSmartPointer intrBodyPos = vtkSmartPointer::New(); vtkSmartPointer intrCells = vtkSmartPointer::New(); vtkSmartPointer intrForceN = vtkSmartPointer::New(); intrForceN->SetNumberOfComponents(1); intrForceN->SetName("forceN"); vtkSmartPointer intrAbsForceT = vtkSmartPointer::New(); intrAbsForceT->SetNumberOfComponents(3); intrAbsForceT->SetName("absForceT"); // pericell vtkSmartPointer pericellPoints = vtkSmartPointer::New(); vtkSmartPointer pericellHexa = vtkSmartPointer::New(); // extras for CPM if(recActive[REC_CPM]){ CpmStateUpdater csu; csu.update(scene); } vtkSmartPointer cpmDamage = vtkSmartPointer::New(); cpmDamage->SetNumberOfComponents(1); cpmDamage->SetName("cpmDamage"); vtkSmartPointer cpmStress = vtkSmartPointer::New(); cpmStress->SetNumberOfComponents(9); cpmStress->SetName("cpmStress"); // extras for JCFpm vtkSmartPointer nbCracks = vtkSmartPointer::New(); nbCracks->SetNumberOfComponents(1); nbCracks->SetName("nbCracks"); vtkSmartPointer jcfpmDamage = vtkSmartPointer::New(); jcfpmDamage->SetNumberOfComponents(1); jcfpmDamage->SetName("damage"); vtkSmartPointer intrIsCohesive = vtkSmartPointer::New(); intrIsCohesive->SetNumberOfComponents(1); intrIsCohesive->SetName("isCohesive"); vtkSmartPointer intrIsOnJoint = vtkSmartPointer::New(); intrIsOnJoint->SetNumberOfComponents(1); intrIsOnJoint->SetName("isOnJoint"); // extras for cracks vtkSmartPointer crackPos = vtkSmartPointer::New(); vtkSmartPointer crackCells = vtkSmartPointer::New(); vtkSmartPointer crackIter = vtkSmartPointer::New(); crackIter->SetNumberOfComponents(1); crackIter->SetName("iter"); vtkSmartPointer crackTime = vtkSmartPointer::New(); crackTime->SetNumberOfComponents(1); crackTime->SetName("time"); vtkSmartPointer crackType = vtkSmartPointer::New(); crackType->SetNumberOfComponents(1); crackType->SetName("type"); vtkSmartPointer crackSize = vtkSmartPointer::New(); crackSize->SetNumberOfComponents(1); crackSize->SetName("size"); vtkSmartPointer crackNorm = vtkSmartPointer::New(); crackNorm->SetNumberOfComponents(3); crackNorm->SetName("norm"); vtkSmartPointer crackNrg = vtkSmartPointer::New(); crackNrg->SetNumberOfComponents(1); crackNrg->SetName("nrg"); #ifdef YADE_LIQMIGRATION vtkSmartPointer liqVol = vtkSmartPointer::New(); liqVol->SetNumberOfComponents(1); liqVol->SetName("liqVol"); vtkSmartPointer liqVolNorm = vtkSmartPointer::New(); liqVolNorm->SetNumberOfComponents(1); liqVolNorm->SetName("liqVolNorm"); #endif // extras for WireMatPM vtkSmartPointer wpmNormalForce = vtkSmartPointer::New(); wpmNormalForce->SetNumberOfComponents(1); wpmNormalForce->SetName("wpmNormalForce"); vtkSmartPointer wpmLimitFactor = vtkSmartPointer::New(); wpmLimitFactor->SetNumberOfComponents(1); wpmLimitFactor->SetName("wpmLimitFactor"); if(recActive[REC_INTR]){ // holds information about cell distance between spatial and displayed position of each particle vector wrapCellDist; if (scene->isPeriodic){ wrapCellDist.resize(scene->bodies->size()); } // save body positions, referenced by ids by vtkLine // map to keep real body ids and their number in a vector (intrBodyPos) boost::unordered_map bIdVector; Body::id_t curId = 0; FOREACH(const shared_ptr& b, *scene->bodies){ if (b) { if(!scene->isPeriodic) { intrBodyPos->InsertNextPoint(b->state->pos[0],b->state->pos[1],b->state->pos[2]); } else { Vector3r pos=scene->cell->wrapShearedPt(b->state->pos,wrapCellDist[b->id]); intrBodyPos->InsertNextPoint(pos[0],pos[1],pos[2]); } bIdVector.insert (std::pair(b->id,curId)); curId++; } } FOREACH(const shared_ptr& I, *scene->interactions){ if(!I->isReal()) continue; if(skipFacetIntr){ if(!(Body::byId(I->getId1()))) continue; if(!(Body::byId(I->getId2()))) continue; if(!(dynamic_cast(Body::byId(I->getId1())->shape.get()))) continue; if(!(dynamic_cast(Body::byId(I->getId2())->shape.get()))) continue; } const auto iterId1 = bIdVector.find (I->getId1()); const auto iterId2 = bIdVector.find (I->getId2()); if (iterId2 == bIdVector.end() || iterId2 == bIdVector.end()) continue; const auto setId1Line = iterId1->second; const auto setId2Line = iterId2->second; /* For the periodic boundary conditions, find out whether the interaction crosses the boundary of the periodic cell; if it does, display the interaction on both sides of the cell, with one of the points sticking out in each case. Since vtkLines must connect points with an ID assigned, we will create a new additional point for each point outside the cell. It might create some data redundancy, but let us suppose that the number of interactions crossing the cell boundary is low compared to total numer of interactions */ // how many times to add values defined on interactions, depending on how many times the interaction is saved int numAddValues=1; // aperiodic boundary, or interaction is inside the cell if(!scene->isPeriodic || (scene->isPeriodic && (I->cellDist==wrapCellDist[I->getId2()]-wrapCellDist[I->getId1()]))){ vtkSmartPointer line = vtkSmartPointer::New(); line->GetPointIds()->SetId(0,setId1Line); line->GetPointIds()->SetId(1,setId2Line); intrCells->InsertNextCell(line); } else { assert(scene->isPeriodic); // spatial positions of particles const Vector3r& p01(Body::byId(I->getId1())->state->pos); const Vector3r& p02(Body::byId(I->getId2())->state->pos); // create two line objects; each of them has one endpoint inside the cell and the other one sticks outside // A,B are the "fake" bodies outside the cell for id1 and id2 respectively, p1,p2 are the displayed points // distance in cell units for shifting A away from p1; negated value is shift of B away from p2 Vector3r ptA(p01+scene->cell->hSize*(wrapCellDist[I->getId2()]-I->cellDist).cast()); const vtkIdType idPtA=intrBodyPos->InsertNextPoint(ptA[0],ptA[1],ptA[2]); Vector3r ptB(p02+scene->cell->hSize*(wrapCellDist[I->getId1()]-I->cellDist).cast()); const vtkIdType idPtB=intrBodyPos->InsertNextPoint(ptB[0],ptB[1],ptB[2]); vtkSmartPointer line1B(vtkSmartPointer::New()); line1B->GetPointIds()->SetId(0,setId2Line); line1B->GetPointIds()->SetId(1,idPtB); vtkSmartPointer lineA2(vtkSmartPointer::New()); lineA2->GetPointIds()->SetId(0,idPtA); lineA2->GetPointIds()->SetId(1,setId2Line); numAddValues=2; } const NormShearPhys* phys = YADE_CAST(I->phys.get()); const GenericSpheresContact* geom = YADE_CAST(I->geom.get()); // gives _signed_ scalar of normal force, following the convention used in the respective constitutive law Real fn=phys->normalForce.dot(geom->normal); Real fs[3]={ (Real) std::abs(phys->shearForce[0]), (Real) std::abs(phys->shearForce[1]), (Real) std::abs(phys->shearForce[2])}; // add the value once for each interaction object that we created (might be 2 for the periodic boundary) for(int i=0; iInsertNextTupleValue(fs); if(recActive[REC_WPM]) { const WirePhys* wirephys = dynamic_cast(I->phys.get()); if (wirephys!=NULL && wirephys->isLinked) { wpmLimitFactor->InsertNextValue(wirephys->limitFactor); wpmNormalForce->InsertNextValue(fn); intrForceN->InsertNextValue(NaN); } else { intrForceN->InsertNextValue(fn); wpmNormalForce->InsertNextValue(NaN); wpmLimitFactor->InsertNextValue(NaN); } } else if (recActive[REC_JCFPM]){ const JCFpmPhys* jcfpmphys = YADE_CAST(I->phys.get()); intrIsCohesive->InsertNextValue(jcfpmphys->isCohesive); intrIsOnJoint->InsertNextValue(jcfpmphys->isOnJoint); intrForceN->InsertNextValue(fn); } else { intrForceN->InsertNextValue(fn); } #ifdef YADE_LIQMIGRATION if (recActive[REC_LIQ]) { const ViscElCapPhys* capphys = YADE_CAST(I->phys.get()); liqVol->InsertNextValue(capphys->Vb); liqVolNorm->InsertNextValue(capphys->Vb/capphys->Vmax); } #endif } } } //Additional Vector for storing forces vector bodyStates; if(recActive[REC_STRESS]) Shop::getStressForEachBody(bodyStates); vector bStresses; if (recActive[REC_BSTRESS]) { Shop::getStressLWForEachBody(bStresses); } FOREACH(const shared_ptr& b, *scene->bodies){ if (!b) continue; if(mask!=0 && !b->maskCompatible(mask)) continue; if (recActive[REC_SPHERES]){ const Sphere* sphere = dynamic_cast(b->shape.get()); if (sphere){ if(skipNondynamic && b->state->blockedDOFs==State::DOF_ALL) continue; vtkIdType pid[1]; Vector3r pos(scene->isPeriodic ? scene->cell->wrapShearedPt(b->state->pos) : b->state->pos); pid[0] = spheresPos->InsertNextPoint(pos[0], pos[1], pos[2]); spheresCells->InsertNextCell(1,pid); radii->InsertNextValue(sphere->radius); if (recActive[REC_BSTRESS]) { const Matrix3r& bStress = bStresses[b->getId()]; Eigen::SelfAdjointEigenSolver solver(bStress); // bStress is probably not symmetric (= self-adjoint for real matrices), but the solver hopefully works (considering only one half of bStress). And, moreover, existence of (real) eigenvalues is not sure for not symmetric bStress.. Matrix3r dirAll = solver.eigenvectors(); Vector3r eigenVal = solver.eigenvalues(); // cf http://eigen.tuxfamily.org/dox/classEigen_1_1SelfAdjointEigenSolver.html#a30caf3c3884a7f4a46b8ec94efd23c5e to be sure that eigenVal[i] * dirAll.col(i) = bStress * dirAll.col(i) int whereSigI(-1), whereSigII(-1), whereSigIII(-1); // all whereSig_i are in [0;2] : whereSigI = 2 => sigI=eigenVal[2] if ( eigenVal[0] > std::max(eigenVal[1],eigenVal[2]) ) { whereSigI = 0; if (eigenVal[1]>eigenVal[2]) { whereSigII=1; whereSigIII=2; } else { //eigenVal[0] > eigenVal[2] >= eigenVal[1] whereSigII = 2; whereSigIII=1; } } else { // max(lambda1,lambda2) >= lambda0 // lambda = eigenVal in the comments if (eigenVal[1]>=eigenVal[2]) {//max(lambda1,lambda2) = lambda1 : lambda 1 >= lambda2 whereSigI = 1; if (eigenVal[2]>=eigenVal[0]) {//lambda1 >= lambda2 >= lambda0 whereSigII=2; whereSigIII=0; } else { //lambda1 >= lambda0 > lambda2 whereSigII=0; whereSigIII=2; } } else { //max(lambda1,lambda2) = lambda2 : lambda2 > lambda1 whereSigI = 2; if (eigenVal[1] > eigenVal[0]) { whereSigII = 1; whereSigIII = 0; } else { whereSigIII = 1; whereSigII = 0; } } } spheresSigI->InsertNextValue(eigenVal[whereSigI]); spheresSigII->InsertNextValue(eigenVal[whereSigII]); spheresSigIII->InsertNextValue(eigenVal[whereSigIII]); Real dirI[3] { (Real) dirAll(0,whereSigI), (Real) dirAll(1,whereSigI), (Real) dirAll(2,whereSigI) }; spheresDirI->InsertNextTupleValue(dirI); Real dirII[3] { (Real) dirAll(0,whereSigII), (Real) dirAll(1,whereSigII), (Real) dirAll(2,whereSigII) }; spheresDirII->InsertNextTupleValue(dirII); Real dirIII[3] { (Real) dirAll(0,whereSigIII), (Real) dirAll(1,whereSigIII), (Real) dirAll(2,whereSigIII) }; spheresDirIII->InsertNextTupleValue(dirIII); } if (recActive[REC_ID]) spheresId->InsertNextValue(b->getId()); if (recActive[REC_MASK]) spheresMask->InsertNextValue(GET_MASK(b)); if (recActive[REC_MASS]) spheresMass->InsertNextValue(b->state->mass); if (recActive[REC_CLUMPID]) clumpId->InsertNextValue(b->clumpId); if (recActive[REC_COLORS]){ const Vector3r& color = sphere->color; Real c[3] = { (Real) color[0], (Real) color[1], (Real) color[2]}; spheresColors->InsertNextTupleValue(c); } if(recActive[REC_VELOCITY]){ const Vector3r& vel = b->state->vel; Real v[3] = { (Real) vel[0], (Real) vel[1], (Real) vel[2] }; spheresLinVelVec->InsertNextTupleValue(v); spheresLinVelLen->InsertNextValue(vel.norm()); const Vector3r& angVel = b->state->angVel; Real av[3] = { (Real) angVel[0], (Real) angVel[1], (Real) angVel[2] }; spheresAngVelVec->InsertNextTupleValue(av); spheresAngVelLen->InsertNextValue(angVel.norm()); } if(recActive[REC_STRESS]){ const Vector3r& stress = bodyStates[b->getId()].normStress; const Vector3r& shear = bodyStates[b->getId()].shearStress; Real n[3] = { (Real) stress[0], (Real) stress[1], (Real) stress[2] }; Real s[3] = { (Real) shear [0], (Real) shear [1], (Real) shear [2] }; spheresNormalStressVec->InsertNextTupleValue(n); spheresShearStressVec->InsertNextTupleValue(s); spheresNormalStressNorm->InsertNextValue(stress.norm()); } if(recActive[REC_FORCE]){ scene->forces.sync(); const Vector3r& f = scene->forces.getForce(b->getId()); const Vector3r& t = scene->forces.getTorque(b->getId()); Real ff[3] = { (Real) f[0], (Real) f[1], (Real) f[2] }; Real tt[3] = { (Real) t[0], (Real) t[1], (Real) t[2] }; Real fn = f.norm(); Real tn = t.norm(); spheresForceVec->InsertNextTupleValue(ff); spheresForceLen->InsertNextValue(fn); spheresTorqueVec->InsertNextTupleValue(tt); spheresTorqueLen->InsertNextValue(tn); } if (recActive[REC_CPM]){ cpmDamage->InsertNextValue(YADE_PTR_CAST(b->state)->normDmg); const Matrix3r& ss=YADE_PTR_CAST(b->state)->stress; //Real s[3]={ss[0],ss[1],ss[2]}; Real s[9]={ (Real) ss(0,0), (Real) ss(0,1), (Real) ss(0,2), (Real) ss(1,0), (Real) ss(1,1), (Real) ss(1,2), (Real) ss(2,0), (Real) ss(2,1), (Real) ss(2,2)}; cpmStress->InsertNextTupleValue(s); } if (recActive[REC_JCFPM]){ nbCracks->InsertNextValue(YADE_PTR_CAST(b->state)->nbBrokenBonds); jcfpmDamage->InsertNextValue(YADE_PTR_CAST(b->state)->damageIndex); } if (recActive[REC_COORDNUMBER]){ spheresCoordNumb->InsertNextValue(b->coordNumber()); } #ifdef YADE_SPH spheresRhoSPH->InsertNextValue(b->state->rho); spheresPressSPH->InsertNextValue(b->state->press); spheresCoordNumbSPH->InsertNextValue(b->coordNumber()); #endif #ifdef YADE_DEFORM const Sphere* sphere = dynamic_cast(b->shape.get()); spheresRealRad->InsertNextValue(b->state->dR + sphere->radius); #endif #ifdef YADE_LIQMIGRATION if (recActive[REC_LIQ]) { spheresLiqVol->InsertNextValue(b->state->Vf); const Real tmpVolIter = liqVolIterBody(b); spheresLiqVolIter->InsertNextValue(tmpVolIter); spheresLiqVolTotal->InsertNextValue(tmpVolIter + b->state->Vf); } #endif if (recActive[REC_MATERIALID]) spheresMaterialId->InsertNextValue(b->material->id); continue; } } if (recActive[REC_FACETS]){ const Facet* facet = dynamic_cast(b->shape.get()); if (facet){ Vector3r pos(scene->isPeriodic ? scene->cell->wrapShearedPt(b->state->pos) : b->state->pos); const vector& localPos = facet->vertices; Matrix3r facetAxisT=b->state->ori.toRotationMatrix(); vtkSmartPointer tri = vtkSmartPointer::New(); vtkIdType nbPoints=facetsPos->GetNumberOfPoints(); for (int i=0;i<3;++i){ Vector3r globalPos = pos + facetAxisT * localPos[i]; facetsPos->InsertNextPoint(globalPos[0], globalPos[1], globalPos[2]); tri->GetPointIds()->SetId(i,nbPoints+i); } facetsCells->InsertNextCell(tri); if (recActive[REC_COLORS]){ const Vector3r& color = facet->color; Real c[3] = { (Real) color[0], (Real) color[1], (Real) color[2]}; facetsColors->InsertNextTupleValue(c); } if(recActive[REC_STRESS]){ const Vector3r& stress = bodyStates[b->getId()].normStress+bodyStates[b->getId()].shearStress; Real s[3] = { (Real) stress[0], (Real) stress[1], (Real) stress[2] }; facetsStressVec->InsertNextTupleValue(s); facetsStressLen->InsertNextValue(stress.norm()); } if(recActive[REC_FORCE]){ scene->forces.sync(); const Vector3r& f = scene->forces.getForce(b->getId()); const Vector3r& t = scene->forces.getTorque(b->getId()); Real ff[3] = { (Real) f[0], (Real) f[1], (Real) f[2] }; Real tt[3] = { (Real) t[0], (Real) t[1], (Real) t[2] }; Real fn = f.norm(); Real tn = t.norm(); facetsForceVec->InsertNextTupleValue(ff); facetsForceLen->InsertNextValue(fn); facetsTorqueVec->InsertNextTupleValue(tt); facetsTorqueLen->InsertNextValue(tn); } if (recActive[REC_MATERIALID]) facetsMaterialId->InsertNextValue(b->material->id); if (recActive[REC_MASK]) facetsMask->InsertNextValue(GET_MASK(b)); if (recActive[REC_COORDNUMBER]){ facetsCoordNumb->InsertNextValue(b->coordNumber()); } continue; } } if (recActive[REC_BOXES]){ const Box* box = dynamic_cast(b->shape.get()); if (box){ Vector3r pos(scene->isPeriodic ? scene->cell->wrapShearedPt(b->state->pos) : b->state->pos); Quaternionr ori(b->state->ori); Vector3r ext(box->extents); vtkSmartPointer boxes = vtkSmartPointer::New(); Vector3r A = Vector3r(-ext[0], -ext[1], -ext[2]); Vector3r B = Vector3r(-ext[0], +ext[1], -ext[2]); Vector3r C = Vector3r(+ext[0], +ext[1], -ext[2]); Vector3r D = Vector3r(+ext[0], -ext[1], -ext[2]); Vector3r E = Vector3r(-ext[0], -ext[1], +ext[2]); Vector3r F = Vector3r(-ext[0], +ext[1], +ext[2]); Vector3r G = Vector3r(+ext[0], +ext[1], +ext[2]); Vector3r H = Vector3r(+ext[0], -ext[1], +ext[2]); A = pos + ori*A; B = pos + ori*B; C = pos + ori*C; D = pos + ori*D; E = pos + ori*E; F = pos + ori*F; G = pos + ori*G; H = pos + ori*H; addWallVTK(boxes, boxesPos, A, B, C, D); boxesCells->InsertNextCell(boxes); addWallVTK(boxes, boxesPos, E, H, G, F); boxesCells->InsertNextCell(boxes); addWallVTK(boxes, boxesPos, A, E, F, B); boxesCells->InsertNextCell(boxes); addWallVTK(boxes, boxesPos, G, H, D, C); boxesCells->InsertNextCell(boxes); addWallVTK(boxes, boxesPos, F, G, C, B); boxesCells->InsertNextCell(boxes); addWallVTK(boxes, boxesPos, D, H, E, A); boxesCells->InsertNextCell(boxes); for(int i=0; i<6; i++){ if (recActive[REC_COLORS]){ const Vector3r& color = box->color; Real c[3] = { (Real) color[0], (Real) color[1], (Real) color[2]}; boxesColors->InsertNextTupleValue(c); } if(recActive[REC_STRESS]){ const Vector3r& stress = bodyStates[b->getId()].normStress+bodyStates[b->getId()].shearStress; Real s[3] = { (Real) stress[0], (Real) stress[1], (Real) stress[2] }; boxesStressVec->InsertNextTupleValue(s); boxesStressLen->InsertNextValue(stress.norm()); } if(recActive[REC_FORCE]){ scene->forces.sync(); const Vector3r& f = scene->forces.getForce(b->getId()); const Vector3r& t = scene->forces.getTorque(b->getId()); Real ff[3] = { (Real) f[0], (Real) f[1], (Real) f[2] }; Real tt[3] = { (Real) t[0], (Real) t[1], (Real) t[2] }; Real fn = f.norm(); Real tn = t.norm(); boxesForceVec->InsertNextTupleValue(ff); boxesForceLen->InsertNextValue(fn); boxesTorqueVec->InsertNextTupleValue(tt); boxesTorqueLen->InsertNextValue(tn); } if (recActive[REC_MATERIALID]) boxesMaterialId->InsertNextValue(b->material->id); if (recActive[REC_MASK]) boxesMask->InsertNextValue(GET_MASK(b)); } continue; } } } if (recActive[REC_PERICELL]) { const Matrix3r& hSize = scene->cell->hSize; Vector3r v0 = hSize*Vector3r(0,0,1); Vector3r v1 = hSize*Vector3r(0,1,1); Vector3r v2 = hSize*Vector3r(1,1,1); Vector3r v3 = hSize*Vector3r(1,0,1); Vector3r v4 = hSize*Vector3r(0,0,0); Vector3r v5 = hSize*Vector3r(0,1,0); Vector3r v6 = hSize*Vector3r(1,1,0); Vector3r v7 = hSize*Vector3r(1,0,0); pericellPoints->InsertNextPoint(v0[0],v0[1],v0[2]); pericellPoints->InsertNextPoint(v1[0],v1[1],v1[2]); pericellPoints->InsertNextPoint(v2[0],v2[1],v2[2]); pericellPoints->InsertNextPoint(v3[0],v3[1],v3[2]); pericellPoints->InsertNextPoint(v4[0],v4[1],v4[2]); pericellPoints->InsertNextPoint(v5[0],v5[1],v5[2]); pericellPoints->InsertNextPoint(v6[0],v6[1],v6[2]); pericellPoints->InsertNextPoint(v7[0],v7[1],v7[2]); vtkSmartPointer h = vtkSmartPointer::New(); vtkIdList* l = h->GetPointIds(); for (int i=0; i<8; i++) { l->SetId(i,i); } pericellHexa->InsertNextCell(h); } vtkSmartPointer compressor; if(compress) compressor=vtkSmartPointer::New(); vtkSmartPointer spheresUg = vtkSmartPointer::New(); if (recActive[REC_SPHERES]){ spheresUg->SetPoints(spheresPos); spheresUg->SetCells(VTK_VERTEX, spheresCells); spheresUg->GetPointData()->AddArray(radii); if (recActive[REC_ID]) spheresUg->GetPointData()->AddArray(spheresId); if (recActive[REC_MASK]) spheresUg->GetPointData()->AddArray(spheresMask); if (recActive[REC_MASS]) spheresUg->GetPointData()->AddArray(spheresMass); if (recActive[REC_CLUMPID]) spheresUg->GetPointData()->AddArray(clumpId); if (recActive[REC_COLORS]) spheresUg->GetPointData()->AddArray(spheresColors); if (recActive[REC_VELOCITY]){ spheresUg->GetPointData()->AddArray(spheresLinVelVec); spheresUg->GetPointData()->AddArray(spheresAngVelVec); spheresUg->GetPointData()->AddArray(spheresLinVelLen); spheresUg->GetPointData()->AddArray(spheresAngVelLen); } #ifdef YADE_SPH spheresUg->GetPointData()->AddArray(spheresRhoSPH); spheresUg->GetPointData()->AddArray(spheresPressSPH); spheresUg->GetPointData()->AddArray(spheresCoordNumbSPH); #endif #ifdef YADE_DEFORM spheresUg->GetPointData()->AddArray(spheresRealRad); #endif #ifdef YADE_LIQMIGRATION if (recActive[REC_LIQ]) { spheresUg->GetPointData()->AddArray(spheresLiqVol); spheresUg->GetPointData()->AddArray(spheresLiqVolIter); spheresUg->GetPointData()->AddArray(spheresLiqVolTotal); } #endif if (recActive[REC_STRESS]){ spheresUg->GetPointData()->AddArray(spheresNormalStressVec); spheresUg->GetPointData()->AddArray(spheresShearStressVec); spheresUg->GetPointData()->AddArray(spheresNormalStressNorm); } if (recActive[REC_FORCE]){ spheresUg->GetPointData()->AddArray(spheresForceVec); spheresUg->GetPointData()->AddArray(spheresForceLen); spheresUg->GetPointData()->AddArray(spheresTorqueVec); spheresUg->GetPointData()->AddArray(spheresTorqueLen); } if (recActive[REC_CPM]){ spheresUg->GetPointData()->AddArray(cpmDamage); spheresUg->GetPointData()->AddArray(cpmStress); } if (recActive[REC_JCFPM]) { spheresUg->GetPointData()->AddArray(nbCracks); spheresUg->GetPointData()->AddArray(jcfpmDamage); } if (recActive[REC_BSTRESS]) { spheresUg->GetPointData()->AddArray(spheresSigI); spheresUg->GetPointData()->AddArray(spheresSigII); spheresUg->GetPointData()->AddArray(spheresSigIII); spheresUg->GetPointData()->AddArray(spheresDirI); spheresUg->GetPointData()->AddArray(spheresDirII); spheresUg->GetPointData()->AddArray(spheresDirIII); } if (recActive[REC_MATERIALID]) spheresUg->GetPointData()->AddArray(spheresMaterialId); if (recActive[REC_COORDNUMBER]) spheresUg->GetCellData()->AddArray(spheresCoordNumb); #ifdef YADE_VTK_MULTIBLOCK if(!multiblock) #endif { vtkSmartPointer writer = vtkSmartPointer::New(); if(compress) writer->SetCompressor(compressor); if(ascii) writer->SetDataModeToAscii(); string fn=fileName+"spheres."+boost::lexical_cast(scene->iter)+".vtu"; writer->SetFileName(fn.c_str()); #ifdef YADE_VTK6 writer->SetInputData(spheresUg); #else writer->SetInput(spheresUg); #endif writer->Write(); } } vtkSmartPointer facetsUg = vtkSmartPointer::New(); if (recActive[REC_FACETS]){ facetsUg->SetPoints(facetsPos); facetsUg->SetCells(VTK_TRIANGLE, facetsCells); if (recActive[REC_COLORS]) facetsUg->GetCellData()->AddArray(facetsColors); if (recActive[REC_STRESS]){ facetsUg->GetCellData()->AddArray(facetsStressVec); facetsUg->GetCellData()->AddArray(facetsStressLen); } if (recActive[REC_FORCE]){ facetsUg->GetCellData()->AddArray(facetsForceVec); facetsUg->GetCellData()->AddArray(facetsForceLen); facetsUg->GetCellData()->AddArray(facetsTorqueVec); facetsUg->GetCellData()->AddArray(facetsTorqueLen); } if (recActive[REC_MATERIALID]) facetsUg->GetCellData()->AddArray(facetsMaterialId); if (recActive[REC_MASK]) facetsUg->GetCellData()->AddArray(facetsMask); if (recActive[REC_COORDNUMBER]) facetsUg->GetCellData()->AddArray(facetsCoordNumb); #ifdef YADE_VTK_MULTIBLOCK if(!multiblock) #endif { vtkSmartPointer writer = vtkSmartPointer::New(); if(compress) writer->SetCompressor(compressor); if(ascii) writer->SetDataModeToAscii(); string fn=fileName+"facets."+boost::lexical_cast(scene->iter)+".vtu"; writer->SetFileName(fn.c_str()); #ifdef YADE_VTK6 writer->SetInputData(facetsUg); #else writer->SetInput(facetsUg); #endif writer->Write(); } } vtkSmartPointer boxesUg = vtkSmartPointer::New(); if (recActive[REC_BOXES]){ boxesUg->SetPoints(boxesPos); boxesUg->SetCells(VTK_QUAD, boxesCells); if (recActive[REC_COLORS]) boxesUg->GetCellData()->AddArray(boxesColors); if (recActive[REC_STRESS]){ boxesUg->GetCellData()->AddArray(boxesStressVec); boxesUg->GetCellData()->AddArray(boxesStressLen); } if (recActive[REC_FORCE]){ boxesUg->GetCellData()->AddArray(boxesForceVec); boxesUg->GetCellData()->AddArray(boxesForceLen); boxesUg->GetCellData()->AddArray(boxesTorqueVec); boxesUg->GetCellData()->AddArray(boxesTorqueLen); } if (recActive[REC_MATERIALID]) boxesUg->GetCellData()->AddArray(boxesMaterialId); if (recActive[REC_MASK]) boxesUg->GetCellData()->AddArray(boxesMask); #ifdef YADE_VTK_MULTIBLOCK if(!multiblock) #endif { vtkSmartPointer writer = vtkSmartPointer::New(); if(compress) writer->SetCompressor(compressor); if(ascii) writer->SetDataModeToAscii(); string fn=fileName+"boxes."+boost::lexical_cast(scene->iter)+".vtu"; writer->SetFileName(fn.c_str()); #ifdef YADE_VTK6 writer->SetInputData(boxesUg); #else writer->SetInput(boxesUg); #endif writer->Write(); } } vtkSmartPointer intrPd = vtkSmartPointer::New(); if (recActive[REC_INTR]){ intrPd->SetPoints(intrBodyPos); intrPd->SetLines(intrCells); intrPd->GetCellData()->AddArray(intrForceN); intrPd->GetCellData()->AddArray(intrAbsForceT); #ifdef YADE_LIQMIGRATION if (recActive[REC_LIQ]) { intrPd->GetCellData()->AddArray(liqVol); intrPd->GetCellData()->AddArray(liqVolNorm); } #endif if (recActive[REC_JCFPM]) { intrPd->GetCellData()->AddArray(intrIsCohesive); intrPd->GetCellData()->AddArray(intrIsOnJoint); } if (recActive[REC_WPM]){ intrPd->GetCellData()->AddArray(wpmNormalForce); intrPd->GetCellData()->AddArray(wpmLimitFactor); } #ifdef YADE_VTK_MULTIBLOCK if(!multiblock) #endif { vtkSmartPointer writer = vtkSmartPointer::New(); if(compress) writer->SetCompressor(compressor); if(ascii) writer->SetDataModeToAscii(); string fn=fileName+"intrs."+boost::lexical_cast(scene->iter)+".vtp"; writer->SetFileName(fn.c_str()); #ifdef YADE_VTK6 writer->SetInputData(intrPd); #else writer->SetInput(intrPd); #endif writer->Write(); } } vtkSmartPointer pericellUg = vtkSmartPointer::New(); if (recActive[REC_PERICELL]){ pericellUg->SetPoints(pericellPoints); pericellUg->SetCells(12,pericellHexa); #ifdef YADE_VTK_MULTIBLOCK if(!multiblock) #endif { vtkSmartPointer writer = vtkSmartPointer::New(); if(compress) writer->SetCompressor(compressor); if(ascii) writer->SetDataModeToAscii(); string fn=fileName+"pericell."+boost::lexical_cast(scene->iter)+".vtu"; writer->SetFileName(fn.c_str()); #ifdef YADE_VTK6 writer->SetInputData(pericellUg); #else writer->SetInput(pericellUg); #endif writer->Write(); } } if (recActive[REC_CRACKS]) { string fileCracks = "cracks_"+Key+".txt"; std::ifstream file (fileCracks.c_str(),std::ios::in); vtkSmartPointer crackUg = vtkSmartPointer::New(); if(file){ while ( !file.eof() ){ std::string line; Real iter,time,p0,p1,p2,type,size,n0,n1,n2,nrg; while ( std::getline(file, line)) {/* writes into string "line", a line of file "file". To go along diff. lines*/ file >> iter >> time >> p0 >> p1 >> p2 >> type >> size >> n0 >> n1 >> n2 >> nrg; vtkIdType pid[1]; pid[0] = crackPos->InsertNextPoint(p0, p1, p2); crackCells->InsertNextCell(1,pid); crackIter->InsertNextValue(iter); crackTime->InsertNextValue(time); crackType->InsertNextValue(type); crackSize->InsertNextValue(size); Real n[3] = { n0, n1, n2 }; crackNorm->InsertNextTupleValue(n); crackNrg->InsertNextValue(nrg); } } file.close(); } // crackUg->SetPoints(crackPos); crackUg->SetCells(VTK_VERTEX, crackCells); crackUg->GetPointData()->AddArray(crackIter); crackUg->GetPointData()->AddArray(crackTime); crackUg->GetPointData()->AddArray(crackType); crackUg->GetPointData()->AddArray(crackSize); crackUg->GetPointData()->AddArray(crackNorm); //see https://www.mail-archive.com/paraview@paraview.org/msg08166.html to obtain Paraview 2D glyphs conforming to this normal crackUg->GetPointData()->AddArray(crackNrg); vtkSmartPointer writer = vtkSmartPointer::New(); if(compress) writer->SetCompressor(compressor); if(ascii) writer->SetDataModeToAscii(); string fn=fileName+"cracks."+boost::lexical_cast(scene->iter)+".vtu"; writer->SetFileName(fn.c_str()); #ifdef YADE_VTK6 writer->SetInputData(crackUg); #else writer->SetInput(crackUg); #endif writer->Write(); } #ifdef YADE_VTK_MULTIBLOCK if(multiblock){ vtkSmartPointer multiblockDataset = vtkSmartPointer::New(); int i=0; if(recActive[REC_SPHERES]) multiblockDataset->SetBlock(i++,spheresUg); if(recActive[REC_FACETS]) multiblockDataset->SetBlock(i++,facetsUg); if(recActive[REC_INTR]) multiblockDataset->SetBlock(i++,intrPd); if(recActive[REC_PERICELL]) multiblockDataset->SetBlock(i++,pericellUg); vtkSmartPointer writer = vtkSmartPointer::New(); if(ascii) writer->SetDataModeToAscii(); string fn=fileName+boost::lexical_cast(scene->iter)+".vtm"; writer->SetFileName(fn.c_str()); #ifdef YADE_VTK6 writer->SetInputData(multiblockDataset); #else writer->SetInput(multiblockDataset); #endif writer->Write(); } #endif }; void VTKRecorder::addWallVTK (vtkSmartPointer& boxes, vtkSmartPointer& boxesPos, Vector3r& W1, Vector3r& W2, Vector3r& W3, Vector3r& W4) { //Function for exporting walls of boxes vtkIdType nbPoints=boxesPos->GetNumberOfPoints(); boxesPos->InsertNextPoint(W1[0], W1[1], W1[2]); boxes->GetPointIds()->SetId(0,nbPoints+0); boxesPos->InsertNextPoint(W2[0], W2[1], W2[2]); boxes->GetPointIds()->SetId(1,nbPoints+1); boxesPos->InsertNextPoint(W3[0], W3[1], W3[2]); boxes->GetPointIds()->SetId(2,nbPoints+2); boxesPos->InsertNextPoint(W4[0], W4[1], W4[2]); boxes->GetPointIds()->SetId(3,nbPoints+3); }; #endif /* YADE_VTK */ #undef GET_MASK trunk-2018.02b/pkg/dem/VTKRecorder.hpp000066400000000000000000000157511324306050200173700ustar00rootroot00000000000000#pragma once #include #include #include // multiblock features don't seem to exist prioor to 5.2 #if (VTK_MAJOR_VERSION==5 && VTK_MINOR_VERSION>=2) || (VTK_MAJOR_VERSION > 5) #define YADE_VTK_MULTIBLOCK #endif class VTKRecorder: public PeriodicEngine { public: enum {REC_SPHERES=0,REC_FACETS,REC_BOXES,REC_COLORS,REC_MASS,REC_CPM,REC_INTR,REC_VELOCITY,REC_ID,REC_CLUMPID,REC_SENTINEL,REC_MATERIALID,REC_STRESS,REC_MASK,REC_RPM,REC_JCFPM,REC_CRACKS,REC_WPM,REC_PERICELL,REC_LIQ,REC_BSTRESS,REC_FORCE,REC_COORDNUMBER}; virtual void action(); void addWallVTK (vtkSmartPointer& boxes, vtkSmartPointer& boxesPos, Vector3r& W1, Vector3r& W2, Vector3r& W3, Vector3r& W4); YADE_CLASS_BASE_DOC_ATTRS_CTOR(VTKRecorder,PeriodicEngine,"Engine recording snapshots of simulation into series of \\*.vtu files, readable by VTK-based postprocessing programs such as Paraview. Both bodies (spheres and facets) and interactions can be recorded, with various vector/scalar quantities that are defined on them.\n\n:yref:`PeriodicEngine.initRun` is initialized to ``True`` automatically.", ((bool,compress,false,,"Compress output XML files [experimental].")) ((bool,ascii,false,,"Store data as readable text in the XML file (sets `vtkXMLWriter `__ data mode to ``vtkXMLWriter::Ascii``, while the default is ``Appended``")) ((bool,skipFacetIntr,true,,"Skip interactions that are not of sphere-sphere type (e.g. sphere-facet, sphere-box...), when saving interactions")) ((bool,skipNondynamic,false,,"Skip non-dynamic spheres (but not facets).")) #ifdef YADE_VTK_MULTIBLOCK ((bool,multiblock,false,,"Use multi-block (``.vtm``) files to store data, rather than separate ``.vtu`` files.")) #endif ((string,fileName,"",,"Base file name; it will be appended with {spheres,intrs,facets}-243100.vtu (unless *multiblock* is ``True``) depending on active recorders and step number (243100 in this case). It can contain slashes, but the directory must exist already.")) ((vector,recorders,vector(1,string("all")),,"List of active recorders (as strings). ``all`` (the default value) enables all base and generic recorders.\n\n.. admonition:: Base recorders\n\n\tBase recorders save the geometry (unstructured grids) on which other data is defined. They are implicitly activated by many of the other recorders. Each of them creates a new file (or a block, if :yref:`multiblock ` is set).\n\n\t``spheres``\n\t\tSaves positions and radii (``radii``) of :yref:`spherical` particles.\n\t``facets``\n\t\tSave :yref:`facets` positions (vertices).\n\t``boxes``\n\t\tSave :yref:`boxes` positions (edges).\n\t``intr``\n\t\tStore interactions as lines between nodes at respective particles positions. Additionally stores magnitude of normal (``forceN``) and shear (``absForceT``) forces on interactions (the :yref:`geom must be of type :yref:`NormShearPhys`). \n\n.. admonition:: Generic recorders\n\n\tGeneric recorders do not depend on specific model being used and save commonly useful data.\n\n\t``id``\n\t\tSaves id's (field ``id``) of spheres; active only if ``spheres`` is active.\n\t``mass``\n\t\tSaves masses (field ``mass``) of spheres; active only if ``spheres`` is active.\n\t``clumpId``\n\t\tSaves id's of clumps to which each sphere belongs (field ``clumpId``); active only if ``spheres`` is active.\n\t``colors``\n\t\tSaves colors of :yref:`spheres` and of :yref:`facets` (field ``color``); only active if ``spheres`` or ``facets`` are activated.\n\t``mask``\n\t\tSaves groupMasks of :yref:`spheres` and of :yref:`facets` (field ``mask``); only active if ``spheres`` or ``facets`` are activated.\n\t``materialId``\n\t\tSaves materialID of :yref:`spheres` and of :yref:`facets`; only active if ``spheres`` or ``facets`` are activated.\n\t``coordNumber``\n\t\tSaves coordination number (number of neighbours) of :yref:`spheres` and of :yref:`facets`; only active if ``spheres`` or ``facets`` are activated.\n\t``velocity``\n\t\tSaves linear and angular velocities of spherical particles as Vector3 and length(fields ``linVelVec``, ``linVelLen`` and ``angVelVec``, ``angVelLen`` respectively``); only effective with ``spheres``.\n\t``stress``\n\t\tSaves stresses of :yref:`spheres` and of :yref:`facets` as Vector3 and length; only active if ``spheres`` or ``facets`` are activated.\n\t``force``\n\t\tSaves force and torque of :yref:`spheres`, :yref:`facets` and :yref:`boxes` as Vector3 and length (norm); only active if ``spheres``, ``facets`` or ``boxes`` are activated.\n\t``pericell``\n\t\tSaves the shape of the cell (simulation has to be periodic).\n\t``bstresses``\n\t\tSaves per-particle principal stresses (sigI >= sigII >= sigIII) and associated principal directions (dirI/II/III). Per-particle stress tensors are given by :yref:`bodyStressTensors` (positive values for tensile states).\n\n.. admonition:: Specific recorders\n\n\tThe following should only be activated in appropriate cases, otherwise crashes can occur due to violation of type presuppositions.\n\n\t``cpm``\n\t\tSaves data pertaining to the :yref:`concrete model`: ``cpmDamage`` (normalized residual strength averaged on particle), ``cpmStress`` (stress on particle); ``intr`` is activated automatically by ``cpm``\n\t``wpm``\n\t\tSaves data pertaining to the :yref:`wire particle model`: ``wpmForceNFactor`` shows the loading factor for the wire, e.g. normal force divided by threshold normal force.\n\t``jcfpm``\n\t\tSaves data pertaining to the :yref:`rock (smooth)-jointed model`: ``damage`` is defined by :yref:`JCFpmState.tensBreak` + :yref:`JCFpmState.shearBreak`; ``intr`` is activated automatically by ``jcfpm``, and :yref:`on joint` or :yref:`cohesive` interactions can be vizualized.\n\t``cracks``\n\t\tSaves other data pertaining to the :yref:`rock model`: ``cracks`` shows locations where cohesive bonds failed during the simulation, with their types (0/1 for tensile/shear breakages), their sizes (0.5*(R1+R2)), and their normal directions. The :yref:`corresponding attribute` has to be activated, and Key attributes have to be consistent.\n\n")) ((string,Key,"",,"Necessary if :yref:`recorders` contains 'cracks'. A string specifying the name of file 'cracks___.txt' that is considered in this case (see :yref:`corresponding attribute`).")) ((int,mask,0,,"If mask defined, only bodies with corresponding groupMask will be exported. If 0, all bodies will be exported.")), /*ctor*/ initRun=true; ); DECLARE_LOGGER; }; REGISTER_SERIALIZABLE(VTKRecorder); trunk-2018.02b/pkg/dem/ViscoelasticCapillarPM.cpp000066400000000000000000000560161324306050200215650ustar00rootroot00000000000000#include "ViscoelasticCapillarPM.hpp" #include #include #include #include #include YADE_PLUGIN((ViscElCapMat)(ViscElCapPhys)(Ip2_ViscElCapMat_ViscElCapMat_ViscElCapPhys)(Law2_ScGeom_ViscElCapPhys_Basic)); /* ViscElCapMat */ ViscElCapMat::~ViscElCapMat(){} /* ViscElCapPhys */ ViscElCapPhys::~ViscElCapPhys(){} /* Ip2_ViscElCapMat_ViscElCapMat_ViscElCapPhys */ void Ip2_ViscElCapMat_ViscElCapMat_ViscElCapPhys::go(const shared_ptr& b1, const shared_ptr& b2, const shared_ptr& interaction) { // no updates of an existing contact if(interaction->phys) return; TIMING_DELTAS_START(); TIMING_DELTAS_CHECKPOINT("setup"); shared_ptr phys (new ViscElCapPhys()); Calculate_ViscElMat_ViscElMat_ViscElPhys(b1, b2, interaction, phys); ViscElCapMat* mat1 = static_cast(b1.get()); ViscElCapMat* mat2 = static_cast(b2.get()); TIMING_DELTAS_CHECKPOINT("collide_materials"); if (mat1->Capillar and mat2->Capillar) { if (mat1->Vb == mat2->Vb) { phys->Vb = mat1->Vb; } else { throw runtime_error("Vb should be equal for both particles!."); } if (mat1->gamma == mat2->gamma) { phys->gamma = mat1->gamma; } else { throw runtime_error("Gamma should be equal for both particles!."); } if (mat1->theta == mat2->theta) { phys->theta = (mat1->theta*M_PI/180.0); } else { throw runtime_error("Theta should be equal for both particles!."); } if (mat1->dcap == mat2->dcap) { phys->dcap = mat1->dcap; } else { throw runtime_error("Theta should be equal for both particles!."); } if (mat1->CapillarType == mat2->CapillarType and mat2->CapillarType != ""){ if (mat1->CapillarType == "Willett_numeric") {phys->CapillarType = Willett_numeric;} else if (mat1->CapillarType == "Willett_analytic") {phys->CapillarType = Willett_analytic;} else if (mat1->CapillarType == "Weigert") {phys->CapillarType = Weigert;} else if (mat1->CapillarType == "Rabinovich") {phys->CapillarType = Rabinovich;} else if (mat1->CapillarType == "Lambert") {phys->CapillarType = Lambert;} else if (mat1->CapillarType == "Soulie") {phys->CapillarType = Soulie;} else {phys->CapillarType = None_Capillar;} } else { throw runtime_error("CapillarType should be equal for both particles!."); } phys->Capillar=true; } #ifdef YADE_LIQMIGRATION phys->LiqMigrEnabled = mat1->LiqMigrEnabled && mat2->LiqMigrEnabled; #endif interaction->phys = phys; } /* Law2_ScGeom_ViscElCapPhys_Basic */ bool Law2_ScGeom_ViscElCapPhys_Basic::go(shared_ptr& _geom, shared_ptr& _phys, Interaction* I) { TIMING_DELTAS_START(); TIMING_DELTAS_CHECKPOINT("setup"); Vector3r force = Vector3r::Zero(); const id_t id1 = I->getId1(); const id_t id2 = I->getId2(); const ScGeom& geom=*static_cast(_geom.get()); Scene* scene=Omega::instance().getScene().get(); ViscElCapPhys& phys=*static_cast(_phys.get()); const BodyContainer& bodies = *scene->bodies; /* * This part for implementation of the capillar model. * All main equations are in calculateCapillarForce function. * There is only the determination of critical distance between spheres, * after that the liquid bridge will be broken. */ TIMING_DELTAS_CHECKPOINT("create_liq_bridge"); if (phys.Capillar and not(phys.liqBridgeCreated) and geom.penetrationDepth>=0) { phys.liqBridgeCreated = true; phys.liqBridgeActive = false; #ifdef YADE_LIQMIGRATION if (phys.LiqMigrEnabled) { scene->addIntrs.push_back(I); } #endif Sphere* s1=dynamic_cast(bodies[id1]->shape.get()); Sphere* s2=dynamic_cast(bodies[id2]->shape.get()); if (s1 and s2) { phys.R = 2 * s1->radius * s2->radius / (s1->radius + s2->radius); } else if (s1 and not(s2)) { phys.R = s1->radius; } else { phys.R = s2->radius; } } TIMING_DELTAS_CHECKPOINT("calculate_scrit"); phys.sCrit = this->critDist(phys.Vb, phys.R, phys.theta); TIMING_DELTAS_CHECKPOINT("force_calculation_liquid"); if (geom.penetrationDepth<0) { if (phys.liqBridgeCreated and -geom.penetrationDepth(bodies[id1]->state.get()); const State& de2 = *static_cast(bodies[id2]->state.get()); Vector3r& shearForce = phys.shearForce; if (I->isFresh(scene)) shearForce=Vector3r(0,0,0); shearForce = geom.rotate(shearForce); const Vector3r shift2 = scene->isPeriodic ? scene->cell->intrShiftPos(I->cellDist): Vector3r::Zero(); const Vector3r shiftVel = scene->isPeriodic ? scene->cell->intrShiftVel(I->cellDist): Vector3r::Zero(); const Vector3r c1x = (geom.contactPoint - de1.pos); const Vector3r c2x = (geom.contactPoint - de2.pos - shift2); const Vector3r relativeVelocity = (de1.vel+de1.angVel.cross(c1x)) - (de2.vel+de2.angVel.cross(c2x)) + shiftVel; const auto normalVelocity = geom.normal.dot(relativeVelocity); dampCapForceScalar = -phys.dcap * normalVelocity; } phys.normalForce = -(normalCapForceScalar + dampCapForceScalar)*geom.normal; if (I->isActive) { addForce (id1,-phys.normalForce,scene); addForce (id2, phys.normalForce,scene); }; return true; } else { if (phys.liqBridgeActive) { VLiqBridg -= phys.Vb; NLiqBridg -= 1; } #ifdef YADE_LIQMIGRATION if (phys.Vb > 0.0 and ((phys.Vf1+phys.Vf2) == 0.0)) { phys.Vf1 = phys.Vb/2.0; phys.Vf2 = phys.Vb/2.0; } const std::pair B1 = {id1, phys.Vf1}; const std::pair B2 = {id2, phys.Vf2}; scene->delIntrs.push_back(B1); scene->delIntrs.push_back(B2); #endif return false; }; }; if (phys.liqBridgeActive) { phys.liqBridgeActive=false; VLiqBridg -= phys.Vb; NLiqBridg -= 1; } if (I->isActive) { Vector3r torque1 = Vector3r::Zero(); Vector3r torque2 = Vector3r::Zero(); TIMING_DELTAS_CHECKPOINT("force_calculation_penetr"); if (computeForceTorqueViscEl(_geom, _phys, I, force, torque1, torque2)) { addForce (id1,-force,scene); addForce (id2, force,scene); addTorque(id1, torque1,scene); addTorque(id2, torque2,scene); return true; } else { return false; } } return true; } Real Law2_ScGeom_ViscElCapPhys_Basic::critDist(const Real& Vb, const Real& R, const Real& Theta) { const Real Vstar = Vb/(R*R*R); const Real Sstar = (1+0.5*Theta)*(pow(Vstar,1/3.0) + 0.1*pow(Vstar,2.0/3.0)); // [Willett2000], equation (15), use the full-length e.g 2*Sc const Real critDist = Sstar*R; return critDist; } //========================================================================================= //======================Capillary bridge models============================================ //========================================================================================= Real Law2_ScGeom_ViscElCapPhys_Basic::Willett_numeric_f(const ScGeom& geom, ViscElCapPhys& phys) { /* * Capillar model from [Willett2000] */ const Real R = phys.R; const Real s = -geom.penetrationDepth; const Real Vb = phys.Vb; const Real VbS = Vb/(R*R*R); const Real Th1 = phys.theta; const Real Th2 = phys.theta*phys.theta; const Real Gamma = phys.gamma; /* * [Willett2000], equations in Attachment */ const Real f1 = (-0.44507 + 0.050832*Th1 - 1.1466*Th2) + (-0.1119 - 0.000411*Th1 - 0.1490*Th2) * log(VbS) + (-0.012101 - 0.0036456*Th1 - 0.01255*Th2) *log(VbS)*log(VbS) + (-0.0005 - 0.0003505*Th1 - 0.00029076*Th2) *log(VbS)*log(VbS)*log(VbS); const Real f2 = (1.9222 - 0.57473*Th1 - 1.2918*Th2) + (-0.0668 - 0.1201*Th1 - 0.22574*Th2) * log(VbS) + (-0.0013375 - 0.0068988*Th1 - 0.01137*Th2) *log(VbS)*log(VbS); const Real f3 = (1.268 - 0.01396*Th1 - 0.23566*Th2) + (0.198 + 0.092*Th1 - 0.06418*Th2) * log(VbS) + (0.02232 + 0.02238*Th1 - 0.009853*Th2) *log(VbS)*log(VbS) + (0.0008585 + 0.001318*Th1 - 0.00053*Th2) *log(VbS)*log(VbS)*log(VbS); const Real f4 = (-0.010703 + 0.073776*Th1 - 0.34742*Th2) + (0.03345 + 0.04543*Th1 - 0.09056*Th2) * log(VbS) + (0.0018574 + 0.004456*Th1 - 0.006257*Th2) *log(VbS)*log(VbS); const Real sPl = (s/2.0)/sqrt(Vb/R); const Real lnFS = f1 - f2*exp(f3*log(sPl) + f4*log(sPl)*log(sPl)); const Real FS = exp(lnFS); const Real fC = FS * 2.0 * M_PI* R * Gamma; return fC; } Real Law2_ScGeom_ViscElCapPhys_Basic::Willett_analytic_f(const ScGeom& geom, ViscElCapPhys& phys) { /* * Capillar model from Willet [Willett2000] (analytical solution), but * used also in the work of Herminghaus [Herminghaus2005] */ const Real R = phys.R; const Real Gamma = phys.gamma; const Real s = -geom.penetrationDepth; const Real Vb = phys.Vb; /* Real sPl = s/sqrt(Vb/R); // [Herminghaus2005], equation (sentence between (7) and (8)) fC = 2.0 * M_PI* R * Gamma * cos(phys.theta)/(1 + 1.05*sPl + 2.5 *sPl * sPl); // [Herminghaus2005], equation (7) */ const Real sPl = (s/2.0)/sqrt(Vb/R); // [Willett2000], equation (sentence after (11)), s - half-separation, so s*2.0 const Real f_star = cos(phys.theta)/(1 + 2.1*sPl + 10.0 * pow(sPl, 2.0)); // [Willett2000], equation (12) const Real fC = f_star * (2*M_PI*R*Gamma); // [Willett2000], equation (13), against F return fC; } Real Law2_ScGeom_ViscElCapPhys_Basic::Weigert_f(const ScGeom& geom, ViscElCapPhys& phys) { /* * Capillar model from [Weigert1999] */ const Real R = phys.R; const Real a = -geom.penetrationDepth; const Real Ca = (1.0 + 6.0*a/(R*2.0)); // [Weigert1999], equation (16) const Real Ct = (1.0 + 1.1*sin(phys.theta)); // [Weigert1999], equation (17) /* Real Eps = 0.36; // Porosity Real fi = phys.Vb/(2.0*M_PI/6.0*pow(R*2.0,3.)); // [Weigert1999], equation (13) Real S = M_PI*(1-Eps)/(pow(Eps, 2.0))*fi; // [Weigert1999], equation (14) Real beta = asin(pow(((S/0.36)*(pow(Eps, 2.0)/(1-Eps))*(1.0/Ca)*(1.0/Ct)), 1.0/4.0)); // [Weigert1999], equation (19) */ const Real beta = asin(pow(phys.Vb/(0.12*Ca*Ct*pow(2.0*R, 3.0)), 1.0/4.0)); // [Weigert1999], equation (15), against Vb const Real r1 = (2.0*R*(1-cos(beta)) + a)/(2.0*cos(beta+phys.theta)); // [Weigert1999], equation (5) const Real r2 = R*sin(beta) + r1*(sin(beta+phys.theta)-1); // [Weigert1999], equation (6) const Real Pk = phys.gamma*(1/r1 - 1/r2); // [Weigert1999], equation (22), // see also a sentence over the equation // "R1 was taken as positive and R2 was taken as negative" //fC = M_PI*2.0*R*phys.gamma/(1+tan(0.5*beta)); // [Weigert1999], equation (23), [Fisher] const Real fC = M_PI/4.0*pow((2.0*R),2.0)*pow(sin(beta),2.0)*Pk + phys.gamma*M_PI*2.0*R*sin(beta)*sin(beta+phys.theta); // [Weigert1999], equation (21) return fC; } Real Law2_ScGeom_ViscElCapPhys_Basic::Rabinovich_f(const ScGeom& geom, ViscElCapPhys& phys) { /* * Capillar model from Rabinovich [Rabinov2005] * * This formulation from Rabinovich has been later verified and corrected * by Lambert [Lambert2008]. So we can calculate both formulations * */ const Real R = phys.R; const Real Gamma = phys.gamma; const Real H = -geom.penetrationDepth; const Real V = phys.Vb; Real fC = 0.0; Real dsp = 0.0; if (H!=0.0) { dsp = H/2.0*(-1.0 + sqrt(1.0 + 2.0*V/(M_PI*R*H*H))); // [Rabinov2005], equation (20) fC = -(2*M_PI*R*Gamma*cos(phys.theta))/(1+(H/(2*dsp))); // [Lambert2008], equation (65), taken from [Rabinov2005] const Real alpha = sqrt(H/R*(-1+ sqrt(1 + 2.0*V/(M_PI*R*H*H)))); // [Rabinov2005], equation (A3) fC -= 2*M_PI*R*Gamma*sin(alpha)*sin(phys.theta + alpha); // [Rabinov2005], equation (19) } else { fC = -(2*M_PI*R*Gamma*cos(phys.theta)); const Real alpha = 0.0; fC -= 2*M_PI*R*Gamma*sin(alpha)*sin(phys.theta + alpha); // [Rabinov2005], equation (19) } fC *=-1; return fC; } Real Law2_ScGeom_ViscElCapPhys_Basic::Lambert_f(const ScGeom& geom, ViscElCapPhys& phys) { /* * Capillar model from Rabinovich [Rabinov2005] * * This formulation from Rabinovich has been later verified and corrected * by Lambert [Lambert2008]. So we can calculate both formulations * */ const Real R = phys.R; const Real Gamma = phys.gamma; const Real H = -geom.penetrationDepth; const Real V = phys.Vb; Real fC = 0.0; Real dsp = 0.0; if (H!=0.0) { dsp = H/2.0*(-1.0 + sqrt(1.0 + 2.0*V/(M_PI*R*H*H))); // [Rabinov2005], equation (20) fC = -(2*M_PI*R*Gamma*cos(phys.theta))/(1+(H/(2*dsp))); // [Lambert2008], equation (65), taken from [Rabinov2005] } else { fC = -(2*M_PI*R*Gamma*cos(phys.theta)); } fC *=-1; return fC; } Real Law2_ScGeom_ViscElCapPhys_Basic::Soulie_f(const ScGeom& geom, ViscElCapPhys& phys) { /* * Capillar model from Soulie [Soulie2006] * * !!! In this implementation the radiis of particles are taken equal * to get the symmetric forces. * * Please, use this model only for testing purposes. * */ const Real R = phys.R; const Real Gamma = phys.gamma; const Real D = -geom.penetrationDepth; const Real V = phys.Vb; const Real Theta = phys.theta; const Real a = -1.1*pow((V/(R*R*R)), -0.53); const Real b = (-0.148*log(V/(R*R*R)) - 0.96)*Theta*Theta -0.0082*log(V/(R*R*R)) + 0.48; const Real c = 0.0018*log(V/(R*R*R)) + 0.078; const Real fC = Mathr::PI*Gamma*sqrt(R*R)*(c+exp(a*D/R+b)); return fC; } Real Law2_ScGeom_ViscElCapPhys_Basic::None_f(const ScGeom& geom, ViscElCapPhys& phys) { return 0; } #ifdef YADE_LIQMIGRATION YADE_PLUGIN((LiqControl)); void LiqControl::action(){ // This function implements liquid migration model, introduced here [Mani2013] mapBodyInt bI; mapBodyInt bodyNeedUpdate; // Calculate, how much new contacts will be at each body for (unsigned int i=0; iaddIntrs.size(); i++) { shared_ptr b1 = Body::byId(scene->addIntrs[i]->getId1(),scene); shared_ptr b2 = Body::byId(scene->addIntrs[i]->getId2(),scene); if(not(mask!=0 && ((b1->groupMask & b2->groupMask & mask)==0))) { addBodyMapInt( bI, scene->addIntrs[i]->getId1() ); addBodyMapInt( bI, scene->addIntrs[i]->getId2() ); } } // Update volume water at each deleted interaction for each body for (unsigned int i=0; idelIntrs.size(); i++) { shared_ptr b = Body::byId(scene->delIntrs[i].first,scene); b->state->Vf += scene->delIntrs[i].second; addBodyMapInt(bodyNeedUpdate, scene->delIntrs[i].first); liqVolRup += scene->delIntrs[i].second; } scene->delIntrs.clear(); // Update volume bridge at each new added interaction mapBodyReal bodyUpdateLiquid; for (unsigned int i=0; iaddIntrs.size(); i++) { shared_ptr b1 = Body::byId(scene->addIntrs[i]->getId1(),scene); shared_ptr b2 = Body::byId(scene->addIntrs[i]->getId2(),scene); const id_t id1 = b1->id; const id_t id2 = b2->id; ViscElCapPhys* Vb=dynamic_cast(scene->addIntrs[i]->phys.get()); if(mask!=0 && ((b1->groupMask & b2->groupMask & mask)==0)) { Vb->Vb = 0.0; Vb->Vf1 = 0.0; Vb->Vf2 = 0.0; Vb->Vmax = 0.0; } else { const Real Vmax = vMax(b1, b2); Vb->Vmax = Vmax; Real Vf1 = 0.0; Real Vf2 = 0.0; if ((b1->state->Vmin)state->Vf) { Vf1 = (b1->state->Vf - b1->state->Vmin)/bI[id1]; } if ((b2->state->Vmin)state->Vf) { Vf2 = (b2->state->Vf - b2->state->Vmin)/bI[id2]; } Real Vrup = Vf1+Vf2; if (Vrup > Vmax) { Vf1 *= Vmax/Vrup; Vf2 *= Vmax/Vrup; Vrup = Vf1 + Vf2; } liqVolShr += Vrup; addBodyMapReal(bodyUpdateLiquid, id1, -Vf1); addBodyMapReal(bodyUpdateLiquid, id2, -Vf2); Vb->Vb = Vrup; if (particleconserve) { Vb->Vf1 = Vf1; Vb->Vf2 = Vf2; } else { Vb->Vf1 = Vrup/2.0; Vb->Vf2 = Vrup/2.0; } } } scene->addIntrs.clear(); // Update water volume in body for (mapBodyReal::const_iterator it = bodyUpdateLiquid.begin(); it != bodyUpdateLiquid.end(); ++it) { Body::byId(it->first)->state->Vf += it->second; } // Update contacts around body for (mapBodyInt::const_iterator it = bodyNeedUpdate.begin(); it != bodyNeedUpdate.end(); ++it) { updateLiquid(Body::byId(it->first)); } } void LiqControl::updateLiquid(shared_ptr b){ if (b->state->Vf<=b->state->Vmin) { return; } else { // How much liquid can body share const Real LiqCanBeShared = b->state->Vf - b->state->Vmin; // Check how much liquid can accept contacts Real LiqContactsAccept = 0.0; unsigned int contactN = 0; for(Body::MapId2IntrT::iterator it=b->intrs.begin(),end=b->intrs.end(); it!=end; ++it) { if(!((*it).second) or !(((*it).second)->isReal())) continue; ViscElCapPhys* physT=dynamic_cast(((*it).second)->phys.get()); if ((physT->Vb < physT->Vmax) and (physT->Vmax > 0)) { LiqContactsAccept+=physT->Vmax-physT->Vb; contactN++; } } if (contactN>0) { //There are some contacts, which can be filled Real FillLevel = 0.0; if (LiqContactsAccept > LiqCanBeShared) { // Share all available liquid from body to contacts const Real LiquidWillBeShared = b->state->Vf - b->state->Vmin; b->state->Vf = b->state->Vmin; FillLevel = LiquidWillBeShared/LiqContactsAccept; } else { // Not all available liquid from body can be shared b->state->Vf -= LiqContactsAccept; FillLevel = 1.0; } for(Body::MapId2IntrT::iterator it=b->intrs.begin(),end=b->intrs.end(); it!=end; ++it) { if(!((*it).second) or !(((*it).second)->isReal())) continue; ViscElCapPhys* physT=dynamic_cast(((*it).second)->phys.get()); if ((physT->Vb < physT->Vmax) and (physT->Vmax > 0)) { const Real addVolLiq = (physT->Vmax - physT->Vb)*FillLevel; liqVolShr += addVolLiq; physT->Vb += addVolLiq; if (particleconserve) { if (((*it).second)->getId1() == (*it).first) { physT->Vf2+=addVolLiq; } else if (((*it).second)->getId2() == (*it).first) { physT->Vf1+=addVolLiq; } } else { physT->Vf1+=addVolLiq/2.0; physT->Vf2+=addVolLiq/2.0; } } } return; } else { return; } } } void LiqControl::addBodyMapInt( mapBodyInt & m, Body::id_t b ){ mapBodyInt::const_iterator got; got = m.find (b); if ( got == m.end() ) { m.insert (mapBodyInt::value_type(b,1)); } else { m[b] += 1; } } void LiqControl::addBodyMapReal( mapBodyReal & m, Body::id_t b, Real addV ) { mapBodyReal::const_iterator got; got = m.find (b); if ( got == m.end() ) { m.insert (mapBodyReal::value_type(b, addV)); } else { m[b] += addV; } } Real LiqControl::vMax(shared_ptr const b1, shared_ptr const b2) { Sphere* s1=dynamic_cast(b1->shape.get()); Sphere* s2=dynamic_cast(b2->shape.get()); Real minR = 0.0; if (s1 and s2) { minR = std::min (s1->radius, s2->radius); } else if (s1 and not(s2)) { minR = s1->radius; } else { minR = s2->radius; } return vMaxCoef*minR*minR*minR; } Real liqVolIterBody (shared_ptr b) { Real LiqVol = 0.0; if (!b) { return LiqVol; } else { for(Body::MapId2IntrT::iterator it=b->intrs.begin(),end=b->intrs.end(); it!=end; ++it) { if(!((*it).second) or !(((*it).second)->isReal())) continue; ViscElCapPhys* physT=dynamic_cast(((*it).second)->phys.get()); if (physT and physT->Vb and physT->Vb>0) { if (((*it).second)->id1 == b->id) { if (physT->Vf1 > 0 or physT->Vf2 > 0) { LiqVol += physT->Vf1; } else { LiqVol += physT->Vb/2.0; } } else { if (physT->Vf1 > 0 or physT->Vf2 > 0) { LiqVol += physT->Vf2; } else { LiqVol += physT->Vb/2.0; } } } } return LiqVol; } } Real LiqControl::liqVolBody (id_t id) const { Scene* scene=Omega::instance().getScene().get(); const BodyContainer& bodies = *scene->bodies; if (bodies[id]) { if (bodies[id]->state->Vf > 0) { return bodies[id]->state->Vf + liqVolIterBody(bodies[id]); } else { return liqVolIterBody(bodies[id]); } } else return -1; } Real LiqControl::totalLiqVol(int mask=0) const{ Scene* scene=Omega::instance().getScene().get(); Real totalLiqVol = 0.0; FOREACH(const shared_ptr& b, *scene->bodies){ if((mask>0 && (b->groupMask & mask)==0) or (!b)) continue; totalLiqVol += liqVolIterBody(b); if (b->state->Vf > 0) {totalLiqVol +=b->state->Vf;} } return totalLiqVol; } bool LiqControl::addLiqInter(id_t id1, id_t id2, Real liq) { if (id1==id2 or liq<=0) return false; Scene* scene=Omega::instance().getScene().get(); shared_ptr& intrs=scene->interactions; const shared_ptr& I=intrs->find(id1,id2); if (I->isReal()) { ViscElCapPhys* physT=dynamic_cast(I->phys.get()); if (physT and physT->Vb <= physT->Vmax and liq <= (physT->Vmax - physT->Vb)) { physT->Vb += liq; physT->Vf1 += liq/2.0; physT->Vf2 += liq/2.0; return true; } } return false; } #endif trunk-2018.02b/pkg/dem/ViscoelasticCapillarPM.hpp000066400000000000000000000152771324306050200215760ustar00rootroot00000000000000#pragma once #include "ViscoelasticPM.hpp" #include #include #include class ViscElCapMat : public ViscElMat { public: virtual ~ViscElCapMat(); YADE_CLASS_BASE_DOC_ATTRS_CTOR(ViscElCapMat,ViscElMat,"Material for extended viscoelastic model of contact with capillary parameters.", ((bool,Capillar,false,,"True, if capillar forces need to be added.")) #ifdef YADE_LIQMIGRATION ((bool,LiqMigrEnabled,true,,"True, if liquid migration mechanism is needed. On by default.")) #endif ((Real,Vb,0.0,,"Liquid bridge volume [m^3]")) ((Real,gamma,0.0,,"Surface tension [N/m]")) ((Real,theta,0.0,,"Contact angle [°]")) ((Real,dcap,0.0,,"Damping coefficient for the capillary phase [-]")) ((std::string,CapillarType,"",,"Different types of capillar interaction: Willett_numeric, Willett_analytic [Willett2000]_ , Weigert [Weigert1999]_ , Rabinovich [Rabinov2005]_ , Lambert (simplified, corrected Rabinovich model) [Lambert2008]_ ")), createIndex(); ); REGISTER_CLASS_INDEX(ViscElCapMat,ViscElMat); }; REGISTER_SERIALIZABLE(ViscElCapMat); /// Interaction physics enum CapType {None_Capillar, Willett_numeric, Willett_analytic, Weigert, Rabinovich, Lambert, Soulie}; class ViscElCapPhys : public ViscElPhys{ typedef Real (* CapillarFunction)(const ScGeom& geom, ViscElCapPhys& phys); public: virtual ~ViscElCapPhys(); Real R; YADE_CLASS_BASE_DOC_ATTRS_CTOR(ViscElCapPhys,ViscElPhys,"IPhys created from :yref:`ViscElCapMat`, for use with :yref:`Law2_ScGeom_ViscElCapPhys_Basic`.", ((bool,Capillar,false,,"True, if capillar forces need to be added.")) ((bool,liqBridgeCreated,false,,"Whether liquid bridge was created, only after a normal contact of spheres")) ((bool,liqBridgeActive,false,, "Whether liquid bridge is active at the moment")) ((Real,sCrit,false,,"Critical bridge length [m]")) ((Real,Vb,0.0,,"Liquid bridge volume [m^3]")) ((Real,gamma,0.0,,"Surface tension [N/m]")) ((Real,theta,0.0,,"Contact angle [rad]")) ((CapType,CapillarType,None_Capillar,,"Different types of capillar interaction: Willett_numeric, Willett_analytic, Weigert, Rabinovich, Lambert, Soulie")) ((Real,dcap,0.0,,"Damping coefficient for the capillary phase [-]")) #ifdef YADE_LIQMIGRATION ((bool,LiqMigrEnabled,,,"True, if liquid migration mechanism is needed.")) ((Real,Vmax,0.0,,"Maximal liquid bridge volume [m^3]")) ((Real,Vf1,0.0,, "Liquid which will be returned to the 1st body after rupture [m^3]")) ((Real,Vf2,0.0,, "Liquid which will be returned to the 2nd body after rupture [m^3]")) #endif , createIndex(); ) REGISTER_CLASS_INDEX(ViscElCapPhys,ViscElPhys); }; REGISTER_SERIALIZABLE(ViscElCapPhys); /// Convert material to interaction physics. class Ip2_ViscElCapMat_ViscElCapMat_ViscElCapPhys: public Ip2_ViscElMat_ViscElMat_ViscElPhys { public : virtual void go(const shared_ptr& b1, const shared_ptr& b2, const shared_ptr& interaction); YADE_CLASS_BASE_DOC(Ip2_ViscElCapMat_ViscElCapMat_ViscElCapPhys,Ip2_ViscElMat_ViscElMat_ViscElPhys,"Convert 2 instances of :yref:`ViscElCapMat` to :yref:`ViscElCapPhys` using the rule of consecutive connection."); FUNCTOR2D(ViscElCapMat,ViscElCapMat); }; REGISTER_SERIALIZABLE(Ip2_ViscElCapMat_ViscElCapMat_ViscElCapPhys); /// Constitutive law class Law2_ScGeom_ViscElCapPhys_Basic: public LawFunctor { public : virtual bool go(shared_ptr&, shared_ptr&, Interaction*); static Real Willett_numeric_f (const ScGeom& geom, ViscElCapPhys& phys); static Real Willett_analytic_f (const ScGeom& geom, ViscElCapPhys& phys); static Real Weigert_f (const ScGeom& geom, ViscElCapPhys& phys); static Real Rabinovich_f (const ScGeom& geom, ViscElCapPhys& phys); static Real Lambert_f (const ScGeom& geom, ViscElCapPhys& phys); static Real Soulie_f (const ScGeom& geom, ViscElCapPhys& phys); static Real None_f (const ScGeom& geom, ViscElCapPhys& phys); std::deque > CapFunctionsPool; Real critDist(const Real& Vb, const Real& R, const Real& Theta); FUNCTOR2D(ScGeom,ViscElCapPhys); YADE_CLASS_BASE_DOC_ATTRS_CTOR_PY(Law2_ScGeom_ViscElCapPhys_Basic,LawFunctor,"Extended version of Linear viscoelastic model with capillary parameters.", ((OpenMPAccumulator,VLiqBridg,,Attr::noSave,"The total volume of liquid bridges")) ((OpenMPAccumulator, NLiqBridg,,Attr::noSave,"The total number of liquid bridges")) ,{ //enum CapType {None_Capillar, Willett_numeric, Willett_analytic, Weigert, Rabinovich, Lambert, Soulie}; CapFunctionsPool.resize(20, nullptr); CapFunctionsPool[None_Capillar] = None_f; CapFunctionsPool[Willett_numeric] = Willett_numeric_f; CapFunctionsPool[Willett_analytic] = Willett_analytic_f; CapFunctionsPool[Weigert] = Weigert_f; CapFunctionsPool[Rabinovich] = Rabinovich_f; CapFunctionsPool[Lambert] = Lambert_f; CapFunctionsPool[Soulie] = Soulie_f; } ,/* py */ ; ) DECLARE_LOGGER; }; REGISTER_SERIALIZABLE(Law2_ScGeom_ViscElCapPhys_Basic); #ifdef YADE_LIQMIGRATION typedef boost::unordered_map mapBodyInt; typedef boost::unordered_map mapBodyReal; class LiqControl: public PartialEngine{ public: virtual void action(); void addBodyMapInt( mapBodyInt & m, Body::id_t b ); void addBodyMapReal( mapBodyReal & m, Body::id_t b, Real addV ); Real vMax(shared_ptr b1, shared_ptr b2); Real totalLiqVol(int mask) const; Real liqVolBody(id_t id) const; bool addLiqInter(id_t id1, id_t id2, Real liq); void updateLiquid(shared_ptr b); YADE_CLASS_BASE_DOC_ATTRS_CTOR_PY(LiqControl,PartialEngine,"This engine implements liquid migration model, introduced here [Mani2013]_ . ", ((int,mask,0,, "Bitmask for liquid creation.")) ((Real,liqVolRup,0.,, "Liquid volume (integral value), which has been freed after rupture occured, [m^3].")) ((Real,liqVolShr,0.,, "Liquid volume (integral value), which has been shared among of contacts, [m^3].")) ((Real,vMaxCoef,0.03,, "Coefficient for vMax, [-].")) ((bool,particleconserve,false,, "If True, the particle will have the same liquid volume during simulation e.g. liquid will not migrate [false].")) ,/* ctor */ ,/* py */ .def("totalLiq",&LiqControl::totalLiqVol,(boost::python::arg("mask")=0),"Return total volume of water in simulation.") .def("liqBody",&LiqControl::liqVolBody,(boost::python::arg("id")=-1),"Return total volume of water in body.") .def("addLiqInter",&LiqControl::addLiqInter,(boost::python::arg("id1")=-1, boost::python::arg("id2")=-1, boost::python::arg("liq")=-1),"Add liquid into the interaction.") ); }; Real liqVolIterBody (shared_ptr b); REGISTER_SERIALIZABLE(LiqControl); #endif trunk-2018.02b/pkg/dem/ViscoelasticPM.cpp000066400000000000000000000404331324306050200201110ustar00rootroot00000000000000// 2009 © Sergei Dorofeenko #include"ViscoelasticPM.hpp" #include #include #include #include #include #ifdef YADE_SPH #include #endif #ifdef YADE_DEFORM #include #endif using std::isfinite; YADE_PLUGIN((ViscElMat)(ViscElPhys)(Ip2_ViscElMat_ViscElMat_ViscElPhys)(Law2_ScGeom_ViscElPhys_Basic)); /* ViscElMat */ ViscElMat::~ViscElMat(){} /* ViscElPhys */ ViscElPhys::~ViscElPhys(){} Real Ip2_ViscElMat_ViscElMat_ViscElPhys::epsilon = 1.0e-8; /* Ip2_ViscElMat_ViscElMat_ViscElPhys */ void Ip2_ViscElMat_ViscElMat_ViscElPhys::go(const shared_ptr& b1, const shared_ptr& b2, const shared_ptr& interaction) { // no updates of an existing contact if(interaction->phys) return; shared_ptr phys (new ViscElPhys()); Calculate_ViscElMat_ViscElMat_ViscElPhys(b1, b2, interaction, phys); #ifdef YADE_DEFORM const ViscElMat* mat1 = static_cast(b1.get()); const ViscElMat* mat2 = static_cast(b2.get()); phys->DeformEnabled = mat1->DeformEnabled && mat2->DeformEnabled; #endif interaction->phys = phys; } /* Law2_ScGeom_ViscElPhys_Basic */ bool Law2_ScGeom_ViscElPhys_Basic::go(shared_ptr& _geom, shared_ptr& _phys, Interaction* I) { Vector3r force = Vector3r::Zero(); Vector3r torque1 = Vector3r::Zero(); Vector3r torque2 = Vector3r::Zero(); if (computeForceTorqueViscEl(_geom, _phys, I, force, torque1, torque2) and (I->isActive)) { const int id1 = I->getId1(); const int id2 = I->getId2(); addForce (id1,-force,scene); addForce (id2, force,scene); addTorque(id1, torque1,scene); addTorque(id2, torque2,scene); return true; } else return false; } bool computeForceTorqueViscEl(shared_ptr& _geom, shared_ptr& _phys, Interaction* I, Vector3r & force, Vector3r & torque1, Vector3r & torque2) { ViscElPhys& phys=*static_cast(_phys.get()); const ScGeom& geom=*static_cast(_geom.get()); Scene* scene=Omega::instance().getScene().get(); #ifdef YADE_SPH //======================================================================================================= if (phys.SPHmode) { if (computeForceSPH(_geom, _phys, I, force)) { return true; } else { return false; } } //======================================================================================================= #endif const int id1 = I->getId1(); const int id2 = I->getId2(); Real addDR = 0.; #ifdef YADE_DEFORM const BodyContainer& bodies = *scene->bodies; const State& de1 = *static_cast(bodies[id1]->state.get()); const State& de2 = *static_cast(bodies[id2]->state.get()); addDR = de1.dR + de2.dR; #endif if ((geom.penetrationDepth + addDR)<0) { return false; } else { #ifndef YADE_DEFORM // These 3 lines were duplicated (see above) not to loose // runtime performance, if YADE_DEFORM is disabled and no // contact detected const BodyContainer& bodies = *scene->bodies; const State& de1 = *static_cast(bodies[id1]->state.get()); const State& de2 = *static_cast(bodies[id2]->state.get()); #endif Vector3r& shearForce = phys.shearForce; if (I->isFresh(scene)) shearForce=Vector3r(0,0,0); const Real& dt = scene->dt; shearForce = geom.rotate(shearForce); // Handle periodicity. const Vector3r shift2 = scene->isPeriodic ? scene->cell->intrShiftPos(I->cellDist): Vector3r::Zero(); const Vector3r shiftVel = scene->isPeriodic ? scene->cell->intrShiftVel(I->cellDist): Vector3r::Zero(); const Vector3r c1x = (geom.contactPoint - de1.pos); const Vector3r c2x = (geom.contactPoint - de2.pos - shift2); const Vector3r relativeVelocity = (de1.vel+de1.angVel.cross(c1x)) - (de2.vel+de2.angVel.cross(c2x)) + shiftVel; const Real normalVelocity = geom.normal.dot(relativeVelocity); const Vector3r shearVelocity = relativeVelocity-normalVelocity*geom.normal; // As Chiara Modenese suggest, we store the elastic part // and then add the viscous part if we pass the Mohr-Coulomb criterion. // See http://www.mail-archive.com/yade-users@lists.launchpad.net/msg01391.html shearForce += phys.ks*dt*shearVelocity; // the elastic shear force have a history, but Vector3r shearForceVisc = Vector3r::Zero(); // the viscous shear damping haven't a history because it is a function of the instant velocity // Prevent appearing of attraction forces due to a viscous component // [Radjai2011], page 3, equation [1.7] // [Schwager2007] phys.Fn = phys.kn * (geom.penetrationDepth + addDR); phys.Fv = phys.cn * normalVelocity; const Real normForceReal = phys.Fn + phys.Fv; if (normForceReal < 0) { phys.normalForce = Vector3r::Zero(); } else { phys.normalForce = normForceReal * geom.normal; } Vector3r momentResistance = Vector3r::Zero(); if (phys.mR>0.0) { const Vector3r relAngVel = de1.angVel - de2.angVel; relAngVel.normalized(); if (phys.mRtype == 1) { momentResistance = -phys.mR*phys.normalForce.norm()*relAngVel; // [Zhou1999536], equation (3) } else if (phys.mRtype == 2) { momentResistance = -phys.mR*(c1x.cross(de1.angVel) - c2x.cross(de2.angVel)).norm()*phys.normalForce.norm()*relAngVel; // [Zhou1999536], equation (4) } } const Real maxFs = phys.normalForce.squaredNorm() * std::pow(phys.tangensOfFrictionAngle,2); if( shearForce.squaredNorm() > maxFs ) { // Then Mohr-Coulomb is violated (so, we slip), // we have the max value of the shear force, so // we consider only friction damping. const Real ratio = sqrt(maxFs) / shearForce.norm(); shearForce *= ratio; } else { // Then no slip occurs we consider friction damping + viscous damping. shearForceVisc = phys.cs*shearVelocity; } force = phys.normalForce + shearForce + shearForceVisc; torque1 = -c1x.cross(force)+momentResistance; torque2 = c2x.cross(force)-momentResistance; return true; } } void Ip2_ViscElMat_ViscElMat_ViscElPhys::Calculate_ViscElMat_ViscElMat_ViscElPhys(const shared_ptr& b1, const shared_ptr& b2, const shared_ptr& interaction, shared_ptr phys) { ViscElMat* mat1 = static_cast(b1.get()); ViscElMat* mat2 = static_cast(b2.get()); Real mass1 = 1.0; Real mass2 = 1.0; if ((isfinite(mat1->kn) and not (isfinite(mat2->kn))) or (isfinite(mat2->kn) and not (isfinite(mat1->kn))) or (isfinite(mat1->ks) and not (isfinite(mat2->ks))) or (isfinite(mat2->ks) and not (isfinite(mat1->ks))) or (isfinite(mat1->cn) and not (isfinite(mat2->cn))) or (isfinite(mat2->cn) and not (isfinite(mat1->cn))) or (isfinite(mat1->cs) and not (isfinite(mat2->cs))) or (isfinite(mat2->cs) and not (isfinite(mat1->cs))) or (isfinite(mat1->tc) and not (isfinite(mat2->tc))) or (isfinite(mat2->tc) and not (isfinite(mat1->tc))) or (isfinite(mat1->en) and not (isfinite(mat2->en))) or (isfinite(mat2->en) and not (isfinite(mat1->en))) or (isfinite(mat1->et) and not (isfinite(mat2->et))) or (isfinite(mat2->et) and not (isfinite(mat1->et)))) { throw runtime_error("Both materials should have the same defined set of variables e.g. tc, ks etc.!"); } mass1 = Body::byId(interaction->getId1())->state->mass; mass2 = Body::byId(interaction->getId2())->state->mass; if (mass1 == 0.0 and mass2 > 0.0) { mass1 = mass2; } else if (mass2 == 0.0 and mass1 > 0.0) { mass2 = mass1; } // See [Pournin2001, just below equation (19)] const Real massR = mass1*mass2/(mass1+mass2); GenericSpheresContact* sphCont=YADE_CAST(interaction->geom.get()); Real R1=sphCont->refR1>0?sphCont->refR1:sphCont->refR2; Real R2=sphCont->refR2>0?sphCont->refR2:sphCont->refR1; Real kn1 = 0.0; Real kn2 = 0.0; Real cn1 = 0.0; Real cn2 = 0.0; Real ks1 = 0.0; Real ks2 = 0.0; Real cs1 = 0.0; Real cs2 = 0.0; if (((isfinite(mat1->tc)) and (isfinite(mat1->en)) and (isfinite(mat1->et))) or ((tc) and (en) and (et))) { //Set parameters according to [Pournin2001] const Real Tc = (tc) ? (*tc)(mat1->id,mat2->id) : (mat1->tc+mat2->tc)/2.0; const Real En = (en) ? (*en)(mat1->id,mat2->id) : (mat1->en+mat2->en)/2.0; const Real Et = (et) ? (*et)(mat1->id,mat2->id) : (mat1->et+mat2->et)/2.0; // Factor 2 at the end of each expression is necessary, because we calculate // individual kn1, kn2, ks1, ks2 etc., because kn1 = 2*kn, ks1 = 2*ks // http://www.mail-archive.com/yade-users@lists.launchpad.net/msg08778.html kn1 = kn2 = 1/Tc/Tc * ( Mathr::PI*Mathr::PI + pow(log(En),2) )*massR*2; cn1 = cn2 = -2.0 /Tc * log(En)*massR*2; ks1 = ks2 = 2.0/7.0 /Tc/Tc * ( Mathr::PI*Mathr::PI + pow(log(Et),2) )*massR*2; cs1 = cs2 = -4.0/7.0 /Tc * log(Et)*massR*2; // ^^^ // It seems to be an error in [Pournin2001] (22) Eq.4, missing factor 2 // Thanks to Dominik Boemer for pointing this out // http://www.mail-archive.com/yade-users@lists.launchpad.net/msg08741.html if (std::abs(cn1) <= Mathr::ZERO_TOLERANCE ) cn1=0; if (std::abs(cn2) <= Mathr::ZERO_TOLERANCE ) cn2=0; if (std::abs(cs1) <= Mathr::ZERO_TOLERANCE ) cs1=0; if (std::abs(cs2) <= Mathr::ZERO_TOLERANCE ) cs2=0; } else if ((isfinite(mat1->kn)) and (isfinite(mat1->ks)) and (isfinite(mat1->cn)) and (isfinite(mat1->cs))) { //Set parameters explicitly kn1 = mat1->kn; kn2 = mat2->kn; ks1 = mat1->ks; ks2 = mat2->ks; cn1 = mat1->cn; cn2 = mat2->cn; cs1 = mat1->cs; cs2 = mat2->cs; } else { //Set parameters on the base of young modulus kn1 = 2*mat1->young*R1; kn2 = 2*mat2->young*R2; ks1 = kn1*mat1->poisson; ks2 = kn2*mat2->poisson; if ((isfinite(mat1->cn)) and (isfinite(mat1->cs))) { cn1 = mat1->cn; cn2 = mat2->cn; cs1 = mat1->cs; cs2 = mat2->cs; } else if( isfinite(mat1->en) and isfinite(mat1->et)) { const Real En = (en) ? (*en)(mat1->id,mat2->id) : (mat1->en+mat2->en)/2.0; cn1 = cn2 = 2.0*find_cn_from_en(En, massR,contactParameterCalculation(kn1,kn2),interaction); cs1 = cs2 = 0; } } const Real mR1 = mat1->mR; const Real mR2 = mat2->mR; const int mRtype1 = mat1->mRtype; const int mRtype2 = mat2->mRtype; phys->kn = contactParameterCalculation(kn1,kn2); phys->ks = contactParameterCalculation(ks1,ks2); phys->cn = contactParameterCalculation(cn1,cn2); phys->cs = contactParameterCalculation(cs1,cs2); if ((mR1>0) or (mR2>0)) { phys->mR = 2.0/( ((mR1>0)?1/mR1:0) + ((mR2>0)?1/mR2:0) ); } else { phys->mR = 0; } if (frictAngle) { phys->tangensOfFrictionAngle = std::tan((*frictAngle)(mat1->id,mat2->id)); } else { phys->tangensOfFrictionAngle = std::tan(std::min(mat1->frictionAngle, mat2->frictionAngle)); } phys->shearForce = Vector3r(0,0,0); if ((mRtype1 != mRtype2) or (mRtype1>2) or (mRtype2>2) or (mRtype1<1) or (mRtype2<1) ) { throw runtime_error("mRtype should be equal for both materials and have the values 1 or 2!"); } else { phys->mRtype = mRtype1; } #ifdef YADE_SPH if (mat1->SPHmode and mat2->SPHmode) { phys->SPHmode=true; phys->mu=(mat1->mu+mat2->mu); phys->h=(mat1->h+mat2->h)/2.0; } phys->kernelFunctionCurrentPressure = returnKernelFunction (mat1->KernFunctionPressure, mat2->KernFunctionPressure, Grad); phys->kernelFunctionCurrentVisco = returnKernelFunction (mat1->KernFunctionVisco, mat2->KernFunctionVisco, Lapl); #endif } /* Contact parameter calculation function */ Real contactParameterCalculation(const Real& l1, const Real& l2){ // If one of paramaters > 0. we DO NOT return 0 Real a = (l1?1/l1:0) + (l2?1/l2:0); if (a) return 1/a; else return 0; } Real find_cn_from_en(const Real& en, const Real& m, const Real& kn, const shared_ptr& interaction){ Real eps = Ip2_ViscElMat_ViscElMat_ViscElPhys::epsilon; Real cn = eps ; //initial small value Real en_temp=get_en_from_cn(cn,m ,kn); int i =0; Real error = 1.0/eps; while (error > 1.0e-2 or error!=error){ if(i>15){ cn = 0.; en_temp = 1.; cerr<<"Warning in ViscoelasticPM.cpp : Newton-Raphson algorithm did not converged within 15 iterations for contact between "<id1<<" and "<id2<<". Continue with values : cn="<& b1, const shared_ptr& b2, const shared_ptr& interaction); YADE_CLASS_BASE_DOC_ATTRS(Ip2_ViscElMat_ViscElMat_ViscElPhys,IPhysFunctor,"Convert 2 instances of :yref:`ViscElMat` to :yref:`ViscElPhys` using the rule of consecutive connection.", ((shared_ptr,tc,,,"Instance of :yref:`MatchMaker` determining contact time")) ((shared_ptr,en,,,"Instance of :yref:`MatchMaker` determining restitution coefficient in normal direction")) ((shared_ptr,et,,,"Instance of :yref:`MatchMaker` determining restitution coefficient in tangential direction")) ((shared_ptr,frictAngle,,,"Instance of :yref:`MatchMaker` determining how to compute interaction's friction angle. If ``None``, minimum value is used.")) ); virtual void Calculate_ViscElMat_ViscElMat_ViscElPhys(const shared_ptr& b1, const shared_ptr& b2, const shared_ptr& interaction, shared_ptr phys); FUNCTOR2D(ViscElMat,ViscElMat); }; REGISTER_SERIALIZABLE(Ip2_ViscElMat_ViscElMat_ViscElPhys); /// Constitutive law /// This class provides linear viscoelastic contact model class Law2_ScGeom_ViscElPhys_Basic: public LawFunctor { public : virtual bool go(shared_ptr&, shared_ptr&, Interaction*); public : FUNCTOR2D(ScGeom,ViscElPhys); YADE_CLASS_BASE_DOC(Law2_ScGeom_ViscElPhys_Basic,LawFunctor,"Linear viscoelastic model operating on ScGeom and ViscElPhys. " "The contact law is visco-elastic in the normal direction, and visco-elastic frictional in the tangential direction. " "The normal contact is modelled as a spring of equivalent stiffness $k_n$, placed in parallel with a viscous damper " "of equivalent viscosity $c_n$. As for the tangential contact, it is made of a spring-dashpot system (in parallel " "with equivalent stiffness $k_s$ and viscosity $c_s$) in serie with a slider of friction coefficient " "$\\mu = \\tan \\phi$.\n\nThe friction coefficient $\\mu = \\tan \\phi$ is always evaluated as " "$\\tan(\\min(\\phi_1,\\phi_2))$, where $\\phi_1$ and $\\phi_2$ are respectively the friction angle of particle 1 " "and 2. For the other parameters, depending on the material input, the equivalent parameters of the contact " "($K_n$,$C_n$,$K_s$,$C_s$,$\\phi$) are evaluated differently. In the following, the quantities in parenthesis are " "the material constant which are precised for each particle. They are then associated to particle 1 and 2 (e.g. " "$kn_1$,$kn_2$,$cn_1$...), and should not be confused with the equivalent parameters of the contact " "($K_n$,$C_n$,$K_s$,$C_s$,$\\phi$). \n\n - If contact time (tc), normal and tangential restitution coefficient " "(en,et) are precised, the equivalent parameters are evaluated following the formulation of " "Pournin [Pournin2001]_.\n\n - If normal and tangential stiffnesses (kn, ks) and damping constant (cn,cs) " "of each particle are precised, the equivalent stiffnesses and damping constants of each contact made of " "two particles 1 and 2 is made $A = 2\\frac{a_1 a_2}{a_1 + a_2}$, where A is $K_n$, $K_s$, $C_n$ and $C_s$, " "and 1 and 2 refer to the value associated to particle 1 and 2.\n\n - Alternatively it is possible to precise " "the Young modulus (young) and poisson's ratio (poisson) instead of the normal and spring constant (kn and ks). " "In this case, the equivalent parameters are evaluated the same way as the previous case with $kn_x = E_x d_x$, " "$ks_x = v_x kn_x$, where $E_x$, $v_x$ and $d_x$ are Young modulus, poisson's ratio and diameter of particle x. " "\n\n - If Yound modulus (young), poisson's ratio (poisson), normal and tangential restitution coefficient (en,et) " "are precised, the equivalent stiffnesses are evaluated as previously: $K_n = 2\\frac{kn_1 kn_2}{kn_1 + kn_2}$, " "$kn_x = E_x d_x$, $K_s = 2(ks_1 ks_2)/(ks_1 + ks_2)$, $ks_x = v kn_x$. The damping constant is computed at each " "contact in order to fulfill the normal restitution coefficient $e_n = (en_1 en_2)/(en_1 + en_2)$. This is " "achieved resolving numerically equation 21 of [Schwager2007]_ (There is in fact a mistake in the article from " "equation 18 to 19, so that there is a change in sign). Be careful in this configuration the tangential " "restitution coefficient is set to 1 (no tangential damping). This formulation imposes directly the normal " "restitution coefficient of the collisions instead of the damping constant."); DECLARE_LOGGER; }; REGISTER_SERIALIZABLE(Law2_ScGeom_ViscElPhys_Basic); Real contactParameterCalculation(const Real& l1,const Real& l2); bool computeForceTorqueViscEl(shared_ptr& _geom, shared_ptr& _phys, Interaction* I, Vector3r & force, Vector3r & torque1, Vector3r & torque2); Real get_en_from_cn(const Real& cn, const Real& m, const Real& kn); Real find_cn_from_en(const Real& en, const Real& m, const Real& kn, const shared_ptr& interaction); #ifdef YADE_DEFORM class DeformControl: public PartialEngine{ public: virtual void action(); YADE_CLASS_BASE_DOC_ATTRS_CTOR_PY(DeformControl,PartialEngine,"This engine implements particle deformation with const. volume, see [Haustein2017]_ . ", // Attrs ,/* ctor */ ,/* py */ ); }; REGISTER_SERIALIZABLE(DeformControl); #endif trunk-2018.02b/pkg/dem/WirePM.cpp000066400000000000000000000305261324306050200163710ustar00rootroot00000000000000/************************************************************************* * Copyright (C) 2010 by Klaus Thoeni * * klaus.thoeni@newcastle.edu.au * * * * This program is free software; it is licensed under the terms of the * * GNU General Public License v2 or later. See file LICENSE for details. * *************************************************************************/ #include"WirePM.hpp" #include #include #include #include "../../lib/base/Math.hpp" #include #include #include #include YADE_PLUGIN((WireMat)(WireState)(WirePhys)(Ip2_WireMat_WireMat_WirePhys)(Law2_ScGeom_WirePhys_WirePM)); /********************** WireMat ****************************/ CREATE_LOGGER(WireMat); void WireMat::postLoad(WireMat&){ //BUG: ????? postLoad is called twice, LOG_TRACE( "WireMat::postLoad - update material parameters" ); // compute cross-section area for single wire as = pow(diameter*0.5,2)*Mathr::PI; // check for stress strain curve for single wire if(strainStressValues.empty()) return; // uninitialized object, don't do nothing at all if(strainStressValues.size() < 2) throw std::invalid_argument("WireMat.strainStressValues: at least two points must be given."); if(strainStressValues[0](0) == 0. && strainStressValues[0](1) == 0.) throw std::invalid_argument("WireMat.strainStressValues: Definition must start with values greater than zero (strain>0,stress>0)"); switch(type) { case 0: LOG_DEBUG("WireMat - Bertrand's approach"); if(!strainStressValuesDT.empty()) throw std::invalid_argument("Use of WireMat.strainStressValuesDT has no effect!"); break; case 1: // check stress strain curve four double twist if type=1 LOG_DEBUG("WireMat - New approach with two curves"); if(isDoubleTwist) { if(strainStressValuesDT.empty()) throw runtime_error("WireMat.strainStressValuesDT not defined"); if(strainStressValuesDT.size() < 2) throw std::invalid_argument("WireMat.strainStressValuesDT: at least two points must be given."); if(strainStressValuesDT[0](0) == 0. && strainStressValuesDT[0](1)) throw std::invalid_argument("WireMat.strainStressValuesDT: Definition must start with values greater than zero (strain>0,stress>0)"); } break; case 2: // check stress strain curve four double twist if type=2 LOG_DEBUG("WireMat - New approach with two curves and initial shift"); if(isDoubleTwist) { if(strainStressValuesDT.empty()) throw runtime_error("WireMat.strainStressValuesDT not defined"); if(strainStressValuesDT.size() < 2) throw std::invalid_argument("WireMat.strainStressValuesDT: at least two points must be given."); if(strainStressValuesDT[0](0) == 0. && strainStressValuesDT[0](1)) throw std::invalid_argument("WireMat.strainStressValuesDT: Definition must start with values greater than zero (strain>0,stress>0)"); } break; default: throw std::invalid_argument("WireMat.type: Type must be 0, 1 or 2."); break; } } /********************** Law2_ScGeom_WirePhys_WirePM ****************************/ CREATE_LOGGER(Law2_ScGeom_WirePhys_WirePM); bool Law2_ScGeom_WirePhys_WirePM::go(shared_ptr& ig, shared_ptr& ip, Interaction* contact){ LOG_TRACE( "Law2_ScGeom_WirePhys_WirePM::go - contact law" ); ScGeom* geom = static_cast(ig.get()); WirePhys* phys = static_cast(ip.get()); const int &id1 = contact->getId1(); const int &id2 = contact->getId2(); Body* b1 = Body::byId(id1,scene).get(); Body* b2 = Body::byId(id2,scene).get(); Real displN = geom->penetrationDepth; // NOTE: ScGeom -> penetrationDepth>0 when spheres interpenetrate, and therefore, for wire always negative /* get reference to values since values are updated/changed in order to take unloading into account */ vector &DFValues = phys->displForceValues; vector &kValues = phys->stiffnessValues; Real kn = phys->kn; Real D = displN - phys->initD; // interparticular distance is computed depending on the equilibrium distance /* check whether the particles are linked or not */ if ( !phys->isLinked ) { // destroy the interaction before calculation return false; } if ( (phys->isLinked) && (D < DFValues.back()(0)) ) { // spheres are linked but failure because of reaching maximal admissible displacement phys->isLinked=false; // update body state with the number of broken links WireState* st1=dynamic_cast(b1->state.get()); WireState* st2=dynamic_cast(b2->state.get()); st1->numBrokenLinks+=1; st2->numBrokenLinks+=1; return false; } /* compute normal force Fn */ Real Fn = 0.; if ( D > DFValues[0](0) ) { // unloading LOG_TRACE("WirePM: Unloading"); Fn = kn * (D-phys->plastD); } else { // loading LOG_TRACE("WirePM: Loading"); for (unsigned int i=1; i DFValues[i](0) ) { Fn = DFValues[i-1](1) + (D-DFValues[i-1](0))*kValues[i-1]; phys->plastD = D - Fn/kn; // update values for unloading DFValues[0](0) = D; DFValues[0](1) = Fn; break; } } } /* compression forces cannot be applied to wires */ if (Fn > 0.) Fn = 0.; TRVAR3( displN, D, Fn ); phys->normalForce = Fn*geom->normal; // NOTE: normal is position2-position1 - It is directed from particle1 to particle2 /* compute a limit value to check how far the interaction is from failing */ Real limitFactor = 0.; if (Fn < 0.) limitFactor = std::abs(D/(DFValues.back()(0))); phys->limitFactor = limitFactor; State* st1 = Body::byId(id1,scene)->state.get(); State* st2 = Body::byId(id2,scene)->state.get(); /* apply forces */ Vector3r f = phys->normalForce; // these lines to adapt to periodic boundary conditions if ( !scene->isPeriodic ) applyForceAtContactPoint(f , geom->contactPoint , id2, st2->se3.position, id1, st1->se3.position); else { // in scg we do not wrap particles positions, hence "applyForceAtContactPoint" cannot be used when scene is periodic scene->forces.addForce(id1,-f); scene->forces.addForce(id2,f); } /* set shear force to zero */ phys->shearForce = Vector3r::Zero(); return true; } /********************** Ip2_WireMat_WireMat_WirePhys ****************************/ CREATE_LOGGER(Ip2_WireMat_WireMat_WirePhys); void Ip2_WireMat_WireMat_WirePhys::go(const shared_ptr& b1, const shared_ptr& b2, const shared_ptr& interaction){ /* avoid any updates if interactions which already exist */ if(interaction->phys) return; //TODO: make boolean to make sure physics are never updated, optimisation of contact detection mesh (no contact detection after link is created) LOG_TRACE( "Ip2_WireMat_WireMat_WirePhys::go - create interaction physics" ); ScGeom* geom=dynamic_cast(interaction->geom.get()); assert(geom); /* set equilibrium distance, e.g. initial distance between particle (stress free state) */ shared_ptr contactPhysics(new WirePhys()); Real initD = geom->penetrationDepth; contactPhysics->normalForce = Vector3r::Zero(); /* get values from material */ const shared_ptr& mat1 = YADE_PTR_CAST(b1); const shared_ptr& mat2 = YADE_PTR_CAST(b2); Real crossSection; vector SSValues; /* check properties of interaction */ if ( mat1->id == mat2->id ) { // interaction of two bodies of the same material crossSection = mat1->as; SSValues = mat1->strainStressValues; if ( (mat1->isDoubleTwist) && (std::abs(interaction->getId1()-interaction->getId2())==1) ) {// bodies which id differs by 1 are double twisted contactPhysics->isDoubleTwist = true; if ( mat1->type==1 || mat1->type==2 ) { SSValues = mat1->strainStressValuesDT; crossSection *= 2.; } } else { contactPhysics->isDoubleTwist = false; } } else { // interaction of two bodies of two different materials, take weaker material and no double-twist contactPhysics->isDoubleTwist = false; if ( mat1->diameter <= mat2->diameter){ crossSection = mat1->as; SSValues = mat1->strainStressValues; } else { crossSection = mat2->as; SSValues = mat2->strainStressValues; } } Real R1 = geom->radius1; Real R2 = geom->radius2; Real l0 = R1 + R2 - initD; // initial length of the wire (can be single or double twisted) /* compute displacement-force values */ vector DFValues; vector kValues; Real dl = 0.; bool isShifted = false; /* account for random distortion if type=2 */ if ( mat1->type==2 ) { isShifted = true; if (mat1->seed==-1) dl = l0*mat1->lambdau; else { // initialize random number generator static boost::minstd_rand randGen(mat1->seed!=0?mat1->seed:(int)TimingInfo::getNow(true)); static boost::variate_generator > rnd(randGen, boost::triangle_distribution(0,0.5,1)); Real rndu = rnd(); TRVAR1( rndu ); dl = l0*mat1->lambdau*rndu; isShifted = true; } } else if ( mat2->type==2 ) { isShifted = true; if (mat2->seed==-1) dl = l0*mat2->lambdau; else { // initialize random number generator static boost::minstd_rand randGen(mat2->seed!=0?mat2->seed:(int)TimingInfo::getNow(true)); static boost::variate_generator > rnd(randGen, boost::triangle_distribution(0,0.5,1)); Real rndu = rnd(); TRVAR1( rndu ); dl = l0*mat2->lambdau*rndu; } } contactPhysics->dL=dl; contactPhysics->isShifted=isShifted; // update geometry values l0 += dl; contactPhysics->initD = initD; /* compute threshold displacement-force values (tension negative since ScGem is used!) */ for ( vector::iterator it = SSValues.begin(); it != SSValues.end(); it++ ) { Vector2r values = Vector2r::Zero(); // values(0) = -(*it)(0)*l0; values(0) = -(*it)(0)*l0-dl; values(1) = -(*it)(1)*crossSection; DFValues.push_back(values); } /* compute elastic stiffness for unloading*/ Real k = DFValues[0](1) / (DFValues[0](0)+dl); /* update values if the interaction is a double twist and type=0 */ if ( contactPhysics->isDoubleTwist && mat1->type==0 ) { // type=0 (force displacement values are computed by manipulating the values of the single wire by using the parameters lambdak and lambdaEps) Real alpha = atan( l0 / (3.*Mathr::PI*mat1->diameter) ); Real kh = k * ( l0*mat1->diameter/crossSection ) / ( 48.*cos(alpha) * ( 41./9.*(1.+mat1->poisson) + 17./4.*pow(tan(alpha),2) ) ); k = 2. * ( mat1->lambdak*kh + (1-mat1->lambdak)*k ); Real F = k * DFValues[0](0); Real mappingF = F/DFValues[0](1); DFValues[0](1) = F; for (unsigned int i = 1; ilambdaEps; DFValues[i](1) *= mappingF; } } else { // type=1 and type=2 (force displacement values have already been computed by given stress-strain curve) } /* store elastic/unloading stiffness as kn in physics */ contactPhysics->kn = k; contactPhysics->ks = 0.; TRVAR1( k ); /* consider an additional point for the initial shift if type==2 */ if ( mat1->type==2 ) { Vector2r values = Vector2r::Zero(); values(0) = -dl+mat1->lambdaF*(DFValues[0](0)+dl); values(1) = DFValues[0](1)*mat1->lambdaF; k = values(1) / values(0); if ( mat1->lambdaF<1. ) DFValues.insert( DFValues.begin(), values ); } else if ( mat2->type==2 ) { Vector2r values = Vector2r::Zero(); values(0) = -dl+mat2->lambdaF*(DFValues[0](0)+dl); values(1) = DFValues[0](1)*mat2->lambdaF; k = values(1) / values(0); if ( mat2->lambdaF<1. ) DFValues.insert( DFValues.begin(), values ); } /* compute stiffness-values of wire */ kValues.push_back(k); for( unsigned int i = 1 ; i < DFValues.size(); i++ ) { Real deltau = -DFValues[i](0) + DFValues[i-1](0); Real deltaF = -DFValues[i](1) + DFValues[i-1](1); k = deltaF/deltau; kValues.push_back(k); } /* add zero values for first point */ DFValues.insert( DFValues.begin(), Vector2r::Zero() ); /* store values in physics */ contactPhysics->displForceValues = DFValues; contactPhysics->stiffnessValues = kValues; /* set particles as linked */ if ( (scene->iter < linkThresholdIteration)) contactPhysics->isLinked=true; else contactPhysics->isLinked=false; interaction->phys = contactPhysics; } WirePhys::~WirePhys(){} trunk-2018.02b/pkg/dem/WirePM.hpp000066400000000000000000000211261324306050200163720ustar00rootroot00000000000000/************************************************************************* * Copyright (C) 2010 by Klaus Thoeni * * klaus.thoeni@newcastle.edu.au * * * * This program is free software; it is licensed under the terms of the * * GNU General Public License v2 or later. See file LICENSE for details. * *************************************************************************/ /** === OVERVIEW OF WirePM === A particle model to simulate single wires and rockfall meshes (see Bertrad et al. 2005, Bertrad et al. 2008, Thoeni et al. 2013). Features of the interaction law: 1. The law is designed for particles which do not touch. A link will be created by defining an interaction radius and running the threshold iteration. 2. The contact law is for tension only. Compressive forces never exist between the particles. However, elastic unloading is considered. 3. The force displacement curve which defines the interaction forces is piecewise linear and defined by the stress-strain curve of the wire material. Any piecewise linear curve can be used. 4. Three different types of wire models are available. */ #pragma once #include #include #include #include /** This class holds information associated with each body state*/ class WireState: public State { YADE_CLASS_BASE_DOC_ATTRS_CTOR(WireState,State,"Wire state information of each body.\n\nNone of that is used for computation (at least not now), only for post-processing.", ((int,numBrokenLinks,0,,"Number of broken links (e.g. number of wires connected to the body which are broken). [-]")) , createIndex(); ); REGISTER_CLASS_INDEX(WireState,State); }; REGISTER_SERIALIZABLE(WireState); /** This class holds information associated with each body */ class WireMat: public FrictMat { public: virtual shared_ptr newAssocState() const { return shared_ptr(new WireState); } virtual bool stateTypeOk(State* s) const { return (bool)dynamic_cast(s); } void postLoad(WireMat&); YADE_CLASS_BASE_DOC_ATTRS_CTOR(WireMat,FrictMat,"Material for use with the Wire classes. In conjunction with the corresponding functors it can be used to model steel wire meshes [Thoeni2014]_, geotextiles [Cheng2016]_ and more.", ((Real,diameter,0.0027,,"Diameter of the single wire in [m] (the diameter is used to compute the cross-section area of the wire).")) ((unsigned int,type,0,,"Three different types are considered:\n\n" "== ===============================================================\n" "0 Corresponds to Bertrand's approach (see [Bertrand2008]_): only one stress-strain curve is used\n" "1 New approach: two separate stress-strain curves can be used (see [Thoeni2013]_)\n" "2 New approach with stochastically distorted contact model: two separate stress-strain curves with changed initial stiffness and horizontal shift (shift is random if $\\text{seed}\\geq0$, for more details see [Thoeni2013]_)\n" "== ===============================================================\n\n" "By default the type is 0." )) ((vector,strainStressValues,,Attr::triggerPostLoad,"Piecewise linear definition of the stress-strain curve by set of points (strain[-]>0,stress[Pa]>0) for one single wire. Tension only is considered and the point (0,0) is not needed! NOTE: Vector needs to be initialized!")) ((vector,strainStressValuesDT,,Attr::triggerPostLoad,"Piecewise linear definition of the stress-strain curve by set of points (strain[-]>0,stress[Pa]>0) for the double twist. Tension only is considered and the point (0,0) is not needed! If this value is given the calculation will be based on two different stress-strain curves without considering the parameter introduced by [Bertrand2008]_ (see [Thoeni2013]_).")) ((bool,isDoubleTwist,false,,"Type of the mesh. If true two particles of the same material which body ids differ by one will be considered as double-twisted interaction.")) ((Real,lambdaEps,0.47,,"Parameter between 0 and 1 to reduce strain at failure of a double-twisted wire (as used by [Bertrand2008]_). [-]")) ((Real,lambdak,0.73,,"Parameter between 0 and 1 to compute the elastic stiffness of a double-twisted wire (as used by [Bertrand2008]_): $k^D=2(\\lambda_k k_h + (1-\\lambda_k)k^S)$. [-]")) ((int,seed,12345,,"Integer used to initialize the random number generator for the calculation of the distortion. If the integer is equal to 0 a internal seed number based on the time is computed. [-]")) ((Real,lambdau,0.2,,"Parameter between 0 and 1 introduced by [Thoeni2013]_ which defines the maximum shift of the force-displacement curve in order to take an additional initial elongation (e.g. wire distortion/imperfections, slipping, system flexibility) into account: $\\Delta l^*=\\lambda_u l_0 \\text{rnd(seed)}$. [-]")) ((Real,lambdaF,1.0,,"Parameter between 0 and 1 introduced by [Thoeni2013]_ which defines where the shifted force-displacement curve intersects with the new initial stiffness: $F^*=\\lambda_F F_{\\text{elastic}}$. [-]")) ((Real,as,0.,Attr::readonly,"Cross-section area of a single wire used to transform stress into force. [m²]")) , createIndex(); ); DECLARE_LOGGER; REGISTER_CLASS_INDEX(WireMat,FrictMat); }; REGISTER_SERIALIZABLE(WireMat); /** This class holds information associated with each interaction */ // NOTE: even if WirePhys has no shear force it is derived from FrictPhys since all implemented functions (e.g. unbalancedForce) work properly for FrictPhys only class WirePhys: public FrictPhys { public: virtual ~WirePhys(); YADE_CLASS_BASE_DOC_ATTRS_CTOR_PY(WirePhys,FrictPhys,"Representation of a single interaction of the WirePM type, storage for relevant parameters", ((Real,initD,0.,,"Equilibrium distance for particles. Computed as the initial inter-particular distance when particle are linked.")) ((bool,isLinked,false,,"If true particles are linked and will interact. Interactions are linked automatically by the definition of the corresponding interaction radius. The value is false if the wire breaks (no more interaction).")) ((bool,isDoubleTwist,false,,"If true the properties of the interaction will be defined as a double-twisted wire.")) ((vector,displForceValues,,Attr::readonly,"Defines the values for force-displacement curve.")) ((vector,stiffnessValues,,Attr::readonly,"Defines the values for the various stiffnesses (the elastic stiffness is stored as kn).")) ((Real,plastD,0.,Attr::readonly,"Plastic part of the inter-particular distance of the previous step. \n\n.. note::\n\t Only elastic displacements are reversible (the elastic stiffness is used for unloading) and compressive forces are inadmissible. The compressive stiffness is assumed to be equal to zero.\n\n..")) ((Real,limitFactor,0.,Attr::readonly,"This value indicates on how far from failing the wire is, e.g. actual normal displacement divided by admissible normal displacement.")) ((bool,isShifted,false,Attr::readonly,"If true :yref:`WireMat` type=2 and the force-displacement curve will be shifted.")) ((Real,dL,0.,Attr::readonly,"Additional wire length for considering the distortion for :yref:`WireMat` type=2 (see [Thoeni2013]_).")) , createIndex(); , ); DECLARE_LOGGER; REGISTER_CLASS_INDEX(WirePhys,FrictPhys); }; REGISTER_SERIALIZABLE(WirePhys); /** 2d functor creating IPhys (Ip2) taking WireMat and WireMat of 2 bodies, returning type WirePhys */ class Ip2_WireMat_WireMat_WirePhys: public IPhysFunctor{ public: virtual void go(const shared_ptr& pp1, const shared_ptr& pp2, const shared_ptr& interaction); FUNCTOR2D(WireMat,WireMat); YADE_CLASS_BASE_DOC_ATTRS(Ip2_WireMat_WireMat_WirePhys,IPhysFunctor,"Converts 2 :yref:`WireMat` instances to :yref:`WirePhys` with corresponding parameters.", ((int,linkThresholdIteration,1,,"Iteration to create the link.")) ); DECLARE_LOGGER; }; REGISTER_SERIALIZABLE(Ip2_WireMat_WireMat_WirePhys); /** 2d functor creating the interaction law (Law2) based on SphereContactGeometry (ScGeom) and WirePhys of 2 bodies, returning type WirePM */ class Law2_ScGeom_WirePhys_WirePM: public LawFunctor{ public: virtual bool go(shared_ptr& _geom, shared_ptr& _phys, Interaction* I); FUNCTOR2D(ScGeom,WirePhys); YADE_CLASS_BASE_DOC_ATTRS(Law2_ScGeom_WirePhys_WirePM,LawFunctor,"Constitutive law for the wire model.", ((int,linkThresholdIteration,1,,"Iteration to create the link.")) ); DECLARE_LOGGER; }; REGISTER_SERIALIZABLE(Law2_ScGeom_WirePhys_WirePM); trunk-2018.02b/pkg/dem/deformablecohesive/000077500000000000000000000000001324306050200203425ustar00rootroot00000000000000trunk-2018.02b/pkg/dem/deformablecohesive/Bo1_DeformableElement_Aabb.cpp000066400000000000000000000050251324306050200260300ustar00rootroot00000000000000/************************************************************************* * Copyright (C) 2013 by Burak ER * * * * * * This program is free software; it is licensed under the terms of the * * GNU General Public License v2 or later. See file LICENSE for details. * *************************************************************************/ #include #include #include void Bo1_DeformableElement_Aabb::go(const shared_ptr& cm, shared_ptr& bv, const Se3r& se3, const Body* b){ DeformableElement* deformableElement = static_cast(cm.get()); if(!bv){ bv=shared_ptr(new Aabb); } Aabb* aabb=static_cast(bv.get()); Real inf=std::numeric_limits::infinity(); Vector3r min,max; min=Vector3r(inf,inf,inf); max=Vector3r(-inf,-inf,-inf); //Not sure this is the best of all possible algorithms, therefore, I think it can be improved for (DeformableElement::NodeMap::iterator it = deformableElement->localmap.begin(); it != deformableElement->localmap.end(); it++) { if(it->first->state->pos(0)first->state->pos(0); } if(it->first->state->pos(1)first->state->pos(1); } if(it->first->state->pos(0)first->state->pos(2); } if(it->first->state->pos(0)>max(0)) { max(0)=it->first->state->pos(0); } if(it->first->state->pos(1)>max(1)) { max(1)=it->first->state->pos(1); } if(it->first->state->pos(2)>max(2)) { max(2)=it->first->state->pos(2); } } aabb->min=min; aabb->max=max; // if(!scene->isPeriodic){ // aabb->min=se3.position-halfSize; aabb->max=se3.position+halfSize; // return; // } // // adjust box size along axes so that DeformableElement doesn't stick out of the box even if sheared (i.e. parallelepiped) // if(scene->cell->hasShear()) { // Vector3r refHalfSize(halfSize); // const Vector3r& cos=scene->cell->getCos(); // for(int i=0; i<3; i++){ // //cerr<<"cos["<min = scene->cell->unshearPt(se3.position)-halfSize; // aabb->max = scene->cell->unshearPt(se3.position)+halfSize; } YADE_PLUGIN((Bo1_DeformableElement_Aabb)); trunk-2018.02b/pkg/dem/deformablecohesive/Bo1_DeformableElement_Aabb.hpp000066400000000000000000000025721324306050200260410ustar00rootroot00000000000000/************************************************************************* * Copyright (C) 2013 by Burak ER * * * * * * This program is free software; it is licensed under the terms of the * * GNU General Public License v2 or later. See file LICENSE for details. * *************************************************************************/ #pragma once #include #include class Bo1_DeformableElement_Aabb : public BoundFunctor { public : void go(const shared_ptr& cm, shared_ptr& bv, const Se3r&, const Body*); FUNCTOR1D(DeformableElement); YADE_CLASS_BASE_DOC_ATTRS(Bo1_DeformableElement_Aabb,BoundFunctor,"Functor creating :yref:`Aabb` from :yref:`DeformableElement`.", ((Real,aabbEnlargeFactor,((void)"deactivated",-1),,"Relative enlargement of the bounding box; deactivated if negative.\n\n.. note::\n\tThis attribute is used to create distant interaction, but is only meaningful with an :yref:`IGeomFunctor` which will not simply discard such interactions: :yref:`Ig2_Sphere_Sphere_ScGeom::interactionDetectionFactor` should have the same value as :yref:`aabbEnlargeFactor`.")) ); }; REGISTER_SERIALIZABLE(Bo1_DeformableElement_Aabb); trunk-2018.02b/pkg/dem/deformablecohesive/Bo1_Node_Aabb.cpp000066400000000000000000000032001324306050200233340ustar00rootroot00000000000000/************************************************************************* * Copyright (C) 2013 by Burak ER * * * * * * This program is free software; it is licensed under the terms of the * * GNU General Public License v2 or later. See file LICENSE for details. * *************************************************************************/ #include #include #include void Bo1_Node_Aabb::go(const shared_ptr& cm, shared_ptr& bv, const Se3r& se3, const Body* b){ Node* node = static_cast(cm.get()); if(!bv){ bv=shared_ptr(new Aabb); } Aabb* aabb=static_cast(bv.get()); Vector3r halfSize = (aabbEnlargeFactor>0?aabbEnlargeFactor:1.)*Vector3r(node->radius,node->radius,node->radius); if(!scene->isPeriodic){ aabb->min=se3.position-halfSize; aabb->max=se3.position+halfSize; return; } // adjust box size along axes so that Node doesn't stick out of the box even if sheared (i.e. parallelepiped) if(scene->cell->hasShear()) { Vector3r refHalfSize(halfSize); const Vector3r& cos=scene->cell->getCos(); for(int i=0; i<3; i++){ //cerr<<"cos["<min = scene->cell->unshearPt(se3.position)-halfSize; aabb->max = scene->cell->unshearPt(se3.position)+halfSize; } YADE_PLUGIN((Bo1_Node_Aabb)); trunk-2018.02b/pkg/dem/deformablecohesive/Bo1_Node_Aabb.hpp000066400000000000000000000024541324306050200233530ustar00rootroot00000000000000/************************************************************************* * Copyright (C) 2013 by Burak ER * * * * * * This program is free software; it is licensed under the terms of the * * GNU General Public License v2 or later. See file LICENSE for details. * *************************************************************************/ #pragma once #include #include class Bo1_Node_Aabb : public BoundFunctor { public : void go(const shared_ptr& cm, shared_ptr& bv, const Se3r&, const Body*); FUNCTOR1D(Node); YADE_CLASS_BASE_DOC_ATTRS(Bo1_Node_Aabb,BoundFunctor,"Functor creating :yref:`Aabb` from :yref:`Node`.", ((Real,aabbEnlargeFactor,((void)"deactivated",-1),,"Relative enlargement of the bounding box; deactivated if negative.\n\n.. note::\n\tThis attribute is used to create distant interaction, but is only meaningful with an :yref:`IGeomFunctor` which will not simply discard such interactions: :yref:`Ig2_Sphere_Sphere_ScGeom::interactionDetectionFactor` should have the same value as :yref:`aabbEnlargeFactor`.")) ); }; REGISTER_SERIALIZABLE(Bo1_Node_Aabb); trunk-2018.02b/pkg/dem/deformablecohesive/CohesiveMat.cpp000066400000000000000000000015141324306050200232560ustar00rootroot00000000000000/************************************************************************* * Copyright (C) 2013 by Burak ER * * * * * * This program is free software; it is licensed under the terms of the * * GNU General Public License v2 or later. See file LICENSE for details. * *************************************************************************/ #include YADE_PLUGIN((CohesiveDeformableElementMaterial)(LinCohesiveElasticMaterial)(LinCohesiveStiffPropDampElastMat)); CohesiveDeformableElementMaterial::~CohesiveDeformableElementMaterial(void){} LinCohesiveElasticMaterial::~LinCohesiveElasticMaterial(void){} LinCohesiveStiffPropDampElastMat::~LinCohesiveStiffPropDampElastMat(void){} trunk-2018.02b/pkg/dem/deformablecohesive/CohesiveMat.hpp000066400000000000000000000041751324306050200232710ustar00rootroot00000000000000/************************************************************************* * Copyright (C) 2013 by Burak ER * * * * * * This program is free software; it is licensed under the terms of the * * GNU General Public License v2 or later. See file LICENSE for details. * *************************************************************************/ #pragma once #include #include /*! Elastic material */ class CohesiveDeformableElementMaterial: public Material{ public: virtual ~CohesiveDeformableElementMaterial(); YADE_CLASS_BASE_DOC_ATTRS_CTOR(CohesiveDeformableElementMaterial,Material,"Deformable Element Material.", // ((Real,density,1,,"Density of the material.")) , /*ctor*/ createIndex(); ); REGISTER_CLASS_INDEX(CohesiveDeformableElementMaterial,Material); }; REGISTER_SERIALIZABLE(CohesiveDeformableElementMaterial); class LinCohesiveElasticMaterial: public CohesiveDeformableElementMaterial{ public: virtual ~LinCohesiveElasticMaterial(); YADE_CLASS_BASE_DOC_ATTRS_CTOR(LinCohesiveElasticMaterial,CohesiveDeformableElementMaterial,"Linear Isotropic Elastic material", ((Real,youngmodulus,.78e5,,"Young's modulus. Initially aluminium.")) ((Real,poissonratio,.33,,"Poisson ratio. Initially aluminium.")), createIndex(); ); REGISTER_CLASS_INDEX(LinCohesiveElasticMaterial,CohesiveDeformableElementMaterial); }; REGISTER_SERIALIZABLE(LinCohesiveElasticMaterial); /*Stiffness proportional damping material*/ class LinCohesiveStiffPropDampElastMat: public LinCohesiveElasticMaterial{ public: virtual ~LinCohesiveStiffPropDampElastMat(); YADE_CLASS_BASE_DOC_ATTRS_CTOR(LinCohesiveStiffPropDampElastMat,LinCohesiveElasticMaterial,"Elastic material with Rayleigh Damping.", ((Real,alpha,0,,"Mass propotional damping constant of Rayleigh Damping.")) ((Real,beta,0,,"Stiffness propotional damping constant of Rayleigh Damping.")), createIndex(); ); REGISTER_CLASS_INDEX(LinCohesiveStiffPropDampElastMat,LinCohesiveElasticMaterial); }; REGISTER_SERIALIZABLE(LinCohesiveStiffPropDampElastMat); trunk-2018.02b/pkg/dem/deformablecohesive/DeformableCohesiveElement.cpp000066400000000000000000000053421324306050200261120ustar00rootroot00000000000000/************************************************************************* * Copyright (C) 2013 by Burak ER * * * * * * This program is free software; it is licensed under the terms of the * * GNU General Public License v2 or later. See file LICENSE for details. * *************************************************************************/ #include #include #include #include #include #include #include #include DeformableCohesiveElement::~DeformableCohesiveElement(){ } YADE_PLUGIN((DeformableCohesiveElement)); CREATE_LOGGER(DeformableCohesiveElement); void DeformableCohesiveElement::addPair(const shared_ptr& nodeBody1,const shared_ptr& nodeBody2) { const shared_ptr node1=YADE_PTR_CAST(nodeBody1->shape);//Should be checked dynamically otherwise it will always cast: every shape is castable to Node shape const shared_ptr node2=YADE_PTR_CAST(nodeBody2->shape);//Should be checked dynamically otherwise it will always cast: every shape is castable to Node shape Body::id_t subId1=nodeBody1->getId(); Body::id_t subId2=nodeBody2->getId(); if(node1||node2){} else{ throw std::invalid_argument(("One of the nodes that is given is not a Node therefore cannot be added to the cohesive deformable element "));} if(subId1<0||subId2<0){throw std::invalid_argument(("One of the node that is given is not a member of the scene therefore it has no state, not adding exiting"));} if(this->localmap.count(nodeBody1)!=0) throw std::invalid_argument(("Node that has Body id #"+boost::lexical_cast(subId1)+" is already part of this cohesive deformable element")); if(this->localmap.count(nodeBody2)!=0) throw std::invalid_argument(("Node that has Body id #"+boost::lexical_cast(subId2)+" is already part of this cohesive deformable element")); nodepair pair; pair.node1=nodeBody1; pair.node2=nodeBody2; this->nodepairs[pair]=Se3r(); // Add body to localmap this->nodepairs[pair].position=nodeBody1->state->pos-nodeBody2->state->pos;// Initial difference on positions return; } void DeformableCohesiveElement::delPair(const shared_ptr& node1,const shared_ptr& node2) { // erase the subBody; removing body that is not part of the element throws // if(this->localmap.erase(subBody)!=1) throw std::invalid_argument(("Node #"+boost::lexical_cast(subBody->id)+" not a part of the deformable element, not removing...").c_str()); // LOG_DEBUG("Removed node #"<id); } trunk-2018.02b/pkg/dem/deformablecohesive/DeformableCohesiveElement.hpp000066400000000000000000000062571324306050200261250ustar00rootroot00000000000000/************************************************************************* * Copyright (C) 2013 by Burak ER * * * * * * This program is free software; it is licensed under the terms of the * * GNU General Public License v2 or later. See file LICENSE for details. * *************************************************************************/ #pragma once #include #include #include #include #include //#include //Node shape /* Before starting the implementation of the deformable element, I am really dissappointed that deformableelement's algorithm relies on its shape. Shape means "shape" and I think it should not contain any other physical meaning or anything else. With respect to this view; The deformable element class is derived from the body and knows the information of its members that are node shaped bodies. */ //class NewtonIntegrator; class DeformableCohesiveElement: public DeformableElement { public: struct nodepair:public Serializable{ public: YADE_CLASS_BASE_DOC_ATTRS_CTOR_PY(nodepair,Serializable,"Geometry of a body", ((shared_ptr,node1,,,"Node1 of node pair")) ((shared_ptr,node2,,,"Node2 of node pair")), /*ctor*/, /*py*/ ); // Comparison operator for table sorting. bool operator<(const nodepair& param) const { if (node1.get() < param.node1.get()) return true; if (node1.get() > param.node1.get()) return false; if (node2.get() < param.node2.get()) return true; if (node2.get() > param.node2.get()) return false; LOG_ERROR("Incomplete 'if' sequence"); return false; } }; typedef std::map NodePairsMap;//Initial node differences typedef std::map localTriad;//Updated on every step unsigned int max_pair; Matrix calculateStiffness(Real, Real ,Vector3r,Vector3r,Vector3r,Vector3r); Matrix calculateMassMatrix(Real, Real); virtual ~DeformableCohesiveElement(); void initialize(void){max_pair=3;} void addPair(const shared_ptr& node1,const shared_ptr& node2); void delPair(const shared_ptr& node1,const shared_ptr& node2); YADE_CLASS_BASE_DOC_ATTRS_INIT_CTOR_PY(DeformableCohesiveElement,DeformableElement,"Tetrahedral Deformable Element Composed of Nodes", ((NodePairsMap,nodepairs,,,"Ids and relative position+orientation difference of members of the cohesive deformable element in the inital condition (should not be accessed directly)")) , , createIndex(); /*ctor*/ initialize(); , /*py*/ .def("addPair",&DeformableCohesiveElement::addPair,"Add a node shared_pt<:yref:'Body'>& as into the element") .def("removePair",&DeformableCohesiveElement::delPair,"Add a node shared_pt<:yref:'Body'>& as into the element") ); DECLARE_LOGGER; REGISTER_CLASS_INDEX(DeformableCohesiveElement,DeformableElement); }; // necessary using namespace yade; REGISTER_SERIALIZABLE(DeformableCohesiveElement); trunk-2018.02b/pkg/dem/deformablecohesive/DeformableElement.cpp000066400000000000000000000070721324306050200244260ustar00rootroot00000000000000/************************************************************************* * Copyright (C) 2013 by Burak ER * * * * * * This program is free software; it is licensed under the terms of the * * GNU General Public License v2 or later. See file LICENSE for details. * *************************************************************************/ #include #include #include #include #include #include #include #include #include DeformableElement::~DeformableElement(){ } YADE_PLUGIN((DeformableElement)); boost::python::dict DeformableElement::localmap_get(){ boost::python::dict ret; FOREACH(NodeMap::value_type& b, localmap){ ret[b.first]=boost::python::make_tuple(b.second.position,b.second.orientation); } return ret; } void DeformableElement::addFace(Vector3r& indexes){ faces.push_back(indexes); } //std::vector DeformableElement::getDisplacements(void) //{ // //} shared_ptr DeformableElement::getNode(int id){ NodeMap::iterator i1(this->localmap.begin()); std::advance(i1,id); return i1->first; } void DeformableElement::removeLastFace(void){ faces.pop_back(); } void DeformableElement::addNode(const shared_ptr& nodeBody){ if(this->localmap.size()==maxNodeCount) { std::string errormessage ="This element cannot hold more than"+ boost::lexical_cast(maxNodeCount); throw std::out_of_range(errormessage); return; } const shared_ptr node=YADE_PTR_CAST(nodeBody->shape);//Should be checked dynamically otherwise it will always cast: every shape is castable to Node shape Body::id_t subId=nodeBody->getId(); if(node){} else{ throw std::invalid_argument(("The body that is given #"+boost::lexical_cast(subId)+" is not a Node therefore cannot be added to the deformable element "));} if(subId<0){throw std::invalid_argument(("The Node that is given is not a member of the scene therefore it has no state, not adding exiting"));} if(this->localmap.count(nodeBody)!=0) throw std::invalid_argument(("Node that has Body id #"+boost::lexical_cast(subId)+" is already part of this deformable element")); // Add body to localmap this->localmap[nodeBody]=Se3r();// meaningful values will be put in by DeformableElement::updateProperties // Get first node //Scene* scene(Omega::instance().getScene().get()); // get scene //const shared_ptr& member=localmap.begin()->first; //Substract from the current node, therefore find the local value, localmap[nodeBody].position=nodeBody->state->pos; //cout<<"Local map of "<(subBody->id)+" not a part of the deformable element, not removing...").c_str()); LOG_DEBUG("Removed node #"<id); } trunk-2018.02b/pkg/dem/deformablecohesive/DeformableElement.hpp000066400000000000000000000065111324306050200244300ustar00rootroot00000000000000/************************************************************************* * Copyright (C) 2013 by Burak ER * * * * * * This program is free software; it is licensed under the terms of the * * GNU General Public License v2 or later. See file LICENSE for details. * *************************************************************************/ #pragma once #include #include #include #include #include //#include //Node shape /* Before starting the implementation of the deformable element, I am really dissappointed that deformableelement's algorithm relies on its shape. Shape means "shape" and I think it should not contain any other physical meaning or anything else. With respect to this view; The deformable element class is derived from the body and knows the information of its members that are node shaped bodies. */ class NewtonIntegrator; class InternalForceFunctor; namespace yade{ typedef Eigen::MatrixXd Matrix; class DeformableElement: public Shape { public: typedef std::map,Se3r> NodeMap;//Node id's with initial positions first node is selected as the reference node typedef std::vector Triangles; // Used for drawing the element unsigned int maxNodeCount;//Maximum number of nodes of this element Se3r referenceCoord;//Reference node position in global coordinates virtual ~DeformableElement(); void addNode(const shared_ptr& subBody); shared_ptr getNode(int id); void delNode(const shared_ptr& subBody); std::vector getDisplacements(void); void addFace(Vector3r&); void removeLastFace(void); //! Recalculate physical properties of DeformableElement. //virtual void getMassMatrix()=0; Se3r frame_get() const { const shared_ptr& member=localmap.begin()->first; return member->state->se3; } void frame_set(Se3r) const { return; } boost::python::dict localmap_get(); virtual Real getVolume(){return -1;} YADE_CLASS_BASE_DOC_ATTRS_INIT_CTOR_PY(DeformableElement,Shape,"Deformable aggregate of nodes", ((NodeMap,localmap,,,"Ids and relative positions+orientations of members of the deformable element (should not be accessed directly)")) ((Se3r,elementframe,,,"Position and orientation of the element frame")) ((Triangles,faces,,,"Faces of the element for drawing")) , , createIndex(); /*ctor*/ //init(); , /*py*/ .add_property("elementframe",&DeformableElement::frame_get) .def("addNode",&DeformableElement::addNode,"Add a node shared_pt<:yref:'Body'>& as into the element") .def("getNode",&DeformableElement::getNode,"Get a node shared_pt<:yref:'Body'>& as into the element") .def("delNode",&DeformableElement::delNode,"Remove a node shared_pt<:yref:'Body'>& from the element") .def("addFace",&DeformableElement::addFace,"Add a face into the element") .def("removeLastFace",&DeformableElement::removeLastFace,"Remove a face from the element") .def("getVolume",&DeformableElement::getVolume,"Get volume of the element") ); REGISTER_CLASS_INDEX(DeformableElement,Shape); }; } // necessary using namespace yade; REGISTER_SERIALIZABLE(DeformableElement); trunk-2018.02b/pkg/dem/deformablecohesive/FEInternalForceDispatchers.cpp000066400000000000000000000032431324306050200262100ustar00rootroot00000000000000/************************************************************************* * Copyright (C) 2013 by Burak ER * * * * * * This program is free software; it is licensed under the terms of the * * GNU General Public License v2 or later. See file LICENSE for details. * *************************************************************************/ #include #include YADE_PLUGIN((InternalForceFunctor)(InternalForceDispatcher)); InternalForceFunctor::~InternalForceFunctor(){}; /******************************************************************** InternalForceDispatcher *********************************************************************/ CREATE_LOGGER(InternalForceDispatcher); void InternalForceDispatcher::action() { //There is no need to make an action for this dispatcher its main misson is to return functors for the shape that is given. } //void InternalForceDispatcher::explicitAction(const shared_ptr deformableelement,const Body* bdy){ // // updateScenePtr(); // shared_ptr deformableElement=deformableelement; // if(!deformableElement->functorCache.iff){ // deformableElement->functorCache.iff=getFunctor1D(deformableElement); // if(!deformableElement->functorCache.iff) throw invalid_argument("InternalForceDispatcher::explicitAction did not find a suitable dispatch for type"+deformableElement->getClassName()); // deformableElement->functorCache.iff->go(deformableElement,bdy); // } // // //} trunk-2018.02b/pkg/dem/deformablecohesive/FEInternalForceDispatchers.hpp000066400000000000000000000034631324306050200262210ustar00rootroot00000000000000/************************************************************************* * Copyright (C) 2013 by Burak ER burak.er@btu.edu.tr * * * * * * This program is free software; it is licensed under the terms of the * * GNU General Public License v2 or later. See file LICENSE for details. * *************************************************************************/ #pragma once #include #include #include #include #include #include #include #include #include #include #include #include /*Functor of Internal Force Calculation*/ class InternalForceFunctor: public Functor2D< /*dispatch types*/ Shape, Material, /*return type*/ void , /*argument types*/ TYPELIST_3(const shared_ptr&,const shared_ptr&,const shared_ptr&) >{ public: virtual ~InternalForceFunctor(); YADE_CLASS_BASE_DOC(InternalForceFunctor,Functor,"Functor for creating/updating :yref:`Body::bound`."); }; REGISTER_SERIALIZABLE(InternalForceFunctor); /*Dispatcher of Internal Force Functors*/ class InternalForceDispatcher: public Dispatcher2D< /* functor type*/ InternalForceFunctor >{ public: virtual void action(); // void explicitAction(const shared_ptr& deformableelement,const Body* bdy); //virtual bool isActivated(){ return activated; } DECLARE_LOGGER; YADE_DISPATCHER2D_FUNCTOR_DOC_ATTRS_CTOR_PY(InternalForceDispatcher,InternalForceFunctor,/*doc is optional*/,/*attrs*/,/*ctor*/,/*py*/); }; REGISTER_SERIALIZABLE(InternalForceDispatcher); trunk-2018.02b/pkg/dem/deformablecohesive/FEInternalForceEngine.cpp000066400000000000000000000070531324306050200251470ustar00rootroot00000000000000/************************************************************************* * Copyright (C) 2013 by Burak ER * * * * * * This program is free software; it is licensed under the terms of the * * GNU General Public License v2 or later. See file LICENSE for details. * *************************************************************************/ #include #include #include YADE_PLUGIN((FEInternalForceEngine)); CREATE_LOGGER(FEInternalForceEngine); void FEInternalForceEngine::pyHandleCustomCtorArgs(boost::python::tuple& t, boost::python::dict& d){ cout<<"Entered the initialization function"; if(boost::python::len(t)==0) return; // nothing to do if(boost::python::len(t)!=1) throw invalid_argument("At least 1 functors must be given"); boost::python::list vv=boost::python::extract(t[0])(); // FOREACH(shared_ptr iff, vv) this->internalforcedispatcher->add(iff); for( int i=0;iinternalforcedispatcher->add(boost::python::extract>(vv[i])); t=boost::python::tuple(); // empty the args; not sure if this is OK, as there is some refcounting in raw_constructor code cout<<"Added to the list"; } void FEInternalForceEngine::action(){ // update Scene* of the dispatcher internalforcedispatcher->scene=scene; // ask dispatcher to update Scene* of their functors internalforcedispatcher->updateScenePtr(); // call InternalForceFunctor::preStep //FOREACH(const shared_ptr& iff, internalforcedispatcher->functors) iff->preStep(); /* initialize callbacks; they return pointer (used only in this timestep) to the function to be called returning NULL deactivates the callback in this timestep */ // // pair of callback object and pointer to the function to be called // vector callbackPtrs; // FOREACH(const shared_ptr cb, callbacks){ // cb->scene=scene; // callbackPtrs.push_back(cb->stepInit()); // } // assert(callbackPtrs.size()==callbacks.size()); // size_t callbacksSize=callbacks.size(); // cache transformed cell size // Matrix3r cellHsize; if(scene->isPeriodic) cellHsize=scene->cell->hSize; #ifdef YADE_OPENMP const long size=scene->bodies->size(); #pragma omp parallel for schedule(guided) num_threads(ompThreads>0 ? ompThreads : omp_get_max_threads()) for(long i=0; i& bdy=(*scene->bodies)[i]; #else FOREACH(const shared_ptr& bdy, *scene->bodies){ #endif //Try to get internal functor if(bdy->shape->internalforcefunctor.get()==0) { //If there isn't any internalforcefunctor; try to get one bdy->shape->internalforcefunctor=internalforcedispatcher->getFunctor(bdy->shape,bdy->material); //İf we found any of them that is suitable to this element??? if(bdy->shape->internalforcefunctor!=0){ //If yes try to apply internal force functor shared_ptr deformableElement=YADE_PTR_CAST(bdy->shape); bdy->shape->internalforcefunctor->go(deformableElement,bdy->material,bdy); } } else{ //Take functor from cache and call it's go functor shared_ptr deformableElement=YADE_PTR_CAST(bdy->shape); bdy->shape->internalforcefunctor->go(deformableElement,bdy->material,bdy); } } } trunk-2018.02b/pkg/dem/deformablecohesive/FEInternalForceEngine.hpp000066400000000000000000000041031324306050200251450ustar00rootroot00000000000000/************************************************************************* * Copyright (C) 2013 by Burak ER burak.er@btu.edu.tr * * * * * * This program is free software; it is licensed under the terms of the * * GNU General Public License v2 or later. See file LICENSE for details. * *************************************************************************/ #pragma once #include #include #include #ifdef USE_TIMING_DELTAS #define TIMING_DELTAS_CHECKPOINT(cpt) timingDeltas->checkpoint(cpt) #define TIMING_DELTAS_START() timingDeltas->start() #else #define TIMING_DELTAS_CHECKPOINT(cpt) #define TIMING_DELTAS_START() #endif class FEInternalForceEngine: public GlobalEngine { // #ifdef YADE_OPENMP // std::vector > eraseAfterLoopIds; // void eraseAfterLoop(Body::id_t id1,Body::id_t id2){ eraseAfterLoopIds[omp_get_thread_num()].push_back(idPair(id1,id2)); } // #else // list eraseAfterLoopIds; // void eraseAfterLoop(Body::id_t id1,Body::id_t id2){ eraseAfterLoopIds.push_back(idPair(id1,id2)); } // #endif public: virtual void pyHandleCustomCtorArgs(boost::python::tuple& t, boost::python::dict& d); virtual void action(); YADE_CLASS_BASE_DOC_ATTRS_CTOR_PY(FEInternalForceEngine,GlobalEngine,"Unified dispatcher for handling Finite Element internal force loop at every step, for parallel performance reasons.\n\n.. admonition:: Special constructor\n\n\tConstructs from 3 lists of :yref:`Ig2`, :yref:`Ip2`, :yref:`Law` functors respectively; they will be passed to interal dispatchers, which you might retrieve.", ((shared_ptr,internalforcedispatcher,new InternalForceDispatcher,Attr::readonly,":yref:`InternalForceDispatcher` object that is used for dispatching of element types.")) , /*ctor*/ , /*py*/ ); DECLARE_LOGGER; }; REGISTER_SERIALIZABLE(FEInternalForceEngine); trunk-2018.02b/pkg/dem/deformablecohesive/Gl1_DeformableElement.cpp000066400000000000000000000066611324306050200251340ustar00rootroot00000000000000/************************************************************************* * Copyright (C) 2013 by Burak ER * * * * * * This program is free software; it is licensed under the terms of the * * GNU General Public License v2 or later. See file LICENSE for details. * *************************************************************************/ #ifdef YADE_OPENGL #include #include #include typedef DeformableElement::NodeMap NodeMap; void Gl1_DeformableElement::go(const shared_ptr& cm, const shared_ptr& ,bool wire,const GLViewInfo&) { DeformableElement* element = static_cast(cm.get()); std::vector triangles = element->faces; if(!(cm->wire || wire)){ glDisable(GL_CULL_FACE); //Vector3r normal=(facet->vertices[1]-facet->vertices[0]).cross(facet->vertices[2]-facet->vertices[1]); normal.normalize(); glColor3v(cm->color); glBegin(GL_TRIANGLES); FOREACH(Vector3r vertices,triangles){ NodeMap::iterator i1(element->localmap.begin()); NodeMap::iterator i2(i1); NodeMap::iterator i3(i1); NodeMap::iterator i4(i1); //Find the opposite vertex total indice sum equals to six therefore the last one is going to be 6-sum of indices // I accept it is very uncommon int oppositevertexindex=6-vertices.sum(); std::advance(i1,vertices[0]); std::advance(i2,vertices[1]); std::advance(i3,vertices[2]); std::advance(i4,oppositevertexindex); const shared_ptr& member1=i1->first; const shared_ptr& member2=i2->first; const shared_ptr& member3=i3->first; Vector3r& vertex0=i1->first->state->pos; Vector3r& vertex1=i2->first->state->pos; Vector3r& vertex2=i3->first->state->pos; Vector3r& vertex3=i4->first->state->pos; Vector3r normal=(vertex1-vertex0).cross(vertex2-vertex0); normal.normalize(); if((vertex3-vertex0).dot(normal)>0) normal=-normal; glNormal3v(normal); // this makes every triangle different WRT the light glVertex3v(member1->state->pos); glVertex3v(member2->state->pos); glVertex3v(member3->state->pos); } } glEnd(); glBegin(GL_LINE_LOOP); glColor3v(Vector3r(0,0,0)); FOREACH(Vector3r vertices,triangles){ NodeMap::iterator i1(element->localmap.begin()); NodeMap::iterator i2(i1); NodeMap::iterator i3(i1); std::advance(i1,vertices[0]); std::advance(i2,vertices[1]); std::advance(i3,vertices[2]); const shared_ptr& member1=i1->first; const shared_ptr& member2=i2->first; const shared_ptr& member3=i3->first; glVertex3v(member1->state->pos); glVertex3v(member2->state->pos); glVertex3v(member3->state->pos); } glEnd(); } YADE_PLUGIN((Gl1_DeformableElement)); #endif /* YADE_OPENGL */ trunk-2018.02b/pkg/dem/deformablecohesive/Gl1_DeformableElement.hpp000066400000000000000000000016131324306050200251310ustar00rootroot00000000000000/************************************************************************* * Copyright (C) 2013 by Burak ER * * * * * * This program is free software; it is licensed under the terms of the * * GNU General Public License v2 or later. See file LICENSE for details. * *************************************************************************/ #pragma once #include #include class Gl1_DeformableElement : public GlShapeFunctor { public: virtual void go(const shared_ptr&, const shared_ptr&,bool,const GLViewInfo&); YADE_CLASS_BASE_DOC(Gl1_DeformableElement,GlShapeFunctor,"Renders :yref:`Node` object" ); RENDERS(DeformableElement); }; REGISTER_SERIALIZABLE(Gl1_DeformableElement); trunk-2018.02b/pkg/dem/deformablecohesive/Gl1_Node.cpp000066400000000000000000000142071324306050200224420ustar00rootroot00000000000000/************************************************************************* * Copyright (C) 2013 by Burak ER burak.er@btu.edu.tr * * * * * * This program is free software; it is licensed under the terms of the * * GNU General Public License v2 or later. See file LICENSE for details. * *************************************************************************/ #ifdef YADE_OPENGL #include #include #include bool Gl1_Node::wire; bool Gl1_Node::stripes; int Gl1_Node::glutSlices; int Gl1_Node::glutStacks; Real Gl1_Node::quality; bool Gl1_Node::localSpecView; vector Gl1_Node::vertices, Gl1_Node::faces; int Gl1_Node::glStripedSphereList=-1; int Gl1_Node::glGlutSphereList=-1; Real Gl1_Node::prevQuality=0; void Gl1_Node::go(const shared_ptr& cm, const shared_ptr& ,bool wire2, const GLViewInfo&) { glClearDepth(1.0f); glEnable(GL_NORMALIZE); Real r=(static_cast(cm.get()))->radius; glColor3v(cm->color); if (wire || wire2) glutWireSphere(r,quality*glutSlices,quality*glutStacks); else { //Check if quality has been modified or if previous lists are invalidated (e.g. by creating a new qt view), then regenerate lists bool somethingChanged = (abs(quality-prevQuality)>0.001 || glIsList(glStripedSphereList)!=GL_TRUE); if (somethingChanged) {initStripedGlList(); initGlutGlList(); prevQuality=quality;} glScalef(r,r,r); if(stripes) glCallList(glStripedSphereList); else glCallList(glGlutSphereList); } return; } YADE_PLUGIN((Gl1_Node)); void Gl1_Node::subdivideTriangle(Vector3r& v1,Vector3r& v2,Vector3r& v3, int depth){ Vector3r v; //Change color only at the appropriate level, i.e. 8 times in total, since we draw 8 mono-color sectors one after another if (depth==int(quality) || quality<=0){ v = (v1+v2+v3)/3.0; GLfloat matEmit[4]; if (v[1]*v[0]*v[2]>0){ matEmit[0] = 0.3; matEmit[1] = 0.3; matEmit[2] = 0.3; matEmit[3] = 1.f; }else{ matEmit[0] = 0.15; matEmit[1] = 0.15; matEmit[2] = 0.15; matEmit[3] = 0.2; } glMaterialfv(GL_FRONT, GL_EMISSION, matEmit); } if (depth==1){//Then display 4 triangles Vector3r v12 = v1+v2; Vector3r v23 = v2+v3; Vector3r v31 = v3+v1; v12.normalize(); v23.normalize(); v31.normalize(); //Use TRIANGLE_STRIP for faster display of adjacent facets glBegin(GL_TRIANGLE_STRIP); glNormal3v(v1); glVertex3v(v1); glNormal3v(v31); glVertex3v(v31); glNormal3v(v12); glVertex3v(v12); glNormal3v(v23); glVertex3v(v23); glNormal3v(v2); glVertex3v(v2); glEnd(); //terminate with this triangle left behind glBegin(GL_TRIANGLES); glNormal3v(v3); glVertex3v(v3); glNormal3v(v23); glVertex3v(v23); glNormal3v(v31); glVertex3v(v31); glEnd(); return; } Vector3r v12 = v1+v2; Vector3r v23 = v2+v3; Vector3r v31 = v3+v1; v12.normalize(); v23.normalize(); v31.normalize(); subdivideTriangle(v1,v12,v31,depth-1); subdivideTriangle(v2,v23,v12,depth-1); subdivideTriangle(v3,v31,v23,depth-1); subdivideTriangle(v12,v23,v31,depth-1); } void Gl1_Node::initStripedGlList() { if (!vertices.size()){//Fill vectors with vertices and facets //Define 6 points for +/- coordinates vertices.push_back(Vector3r(-1,0,0));//0 vertices.push_back(Vector3r(1,0,0));//1 vertices.push_back(Vector3r(0,-1,0));//2 vertices.push_back(Vector3r(0,1,0));//3 vertices.push_back(Vector3r(0,0,-1));//4 vertices.push_back(Vector3r(0,0,1));//5 //Define 8 sectors of the sphere faces.push_back(Vector3r(3,4,1)); faces.push_back(Vector3r(3,0,4)); faces.push_back(Vector3r(3,5,0)); faces.push_back(Vector3r(3,1,5)); faces.push_back(Vector3r(2,1,4)); faces.push_back(Vector3r(2,4,0)); faces.push_back(Vector3r(2,0,5)); faces.push_back(Vector3r(2,5,1)); } //Generate the list. Only once for each qtView, or more if quality is modified. glDeleteLists(glStripedSphereList,1); glStripedSphereList = glGenLists(1); glNewList(glStripedSphereList,GL_COMPILE); glEnable(GL_LIGHTING); glShadeModel(GL_SMOOTH); // render the sphere now for (int i=0;i<8;i++) subdivideTriangle(vertices[(unsigned int)faces[i][0]],vertices[(unsigned int)faces[i][1]],vertices[(unsigned int)faces[i][2]],1+ (int) quality); glEndList(); } void Gl1_Node::initGlutGlList(){ //Generate the "no-stripes" display list, each time quality is modified glDeleteLists(glGlutSphereList,1); glGlutSphereList = glGenLists(1); glNewList(glGlutSphereList,GL_COMPILE); glEnable(GL_LIGHTING); glShadeModel(GL_SMOOTH); glutSolidSphere(1.0,max(quality*glutSlices,(Real)2.),max(quality*glutStacks,(Real)3.)); glEndList(); } #endif /* YADE_OPENGL */ // ///The old Galizzi's lists // if (!vertices.size()) { // Real X = 0.525731112119133606; // Real Z = 0.850650808352039932; // vertices.push_back(Vector3r(-X,0,Z));//0 // vertices.push_back(Vector3r(X,0,Z));//1 // vertices.push_back(Vector3r(-X,0,-Z));//2 // vertices.push_back(Vector3r(X,0,-Z));//3 // vertices.push_back(Vector3r(0,Z,X));//4 // vertices.push_back(Vector3r(0,Z,-X));//5 // vertices.push_back(Vector3r(0,-Z,X));//6 // vertices.push_back(Vector3r(0,-Z,-X));//7 // vertices.push_back(Vector3r(Z,X,0));//8 // vertices.push_back(Vector3r(-Z,X,0));//9 // vertices.push_back(Vector3r(Z,-X,0));//10 // vertices.push_back(Vector3r(-Z,-X,0));//11 // faces.push_back(Vector3r(0,4,1)); // faces.push_back(Vector3r(0,9,4)); // faces.push_back(Vector3r(9,5,4)); // faces.push_back(Vector3r(4,5,8)); // faces.push_back(Vector3r(4,8,1)); // faces.push_back(Vector3r(8,10,1)); // faces.push_back(Vector3r(8,3,10)); // faces.push_back(Vector3r(5,3,8)); // faces.push_back(Vector3r(5,2,3)); // faces.push_back(Vector3r(2,7,3)); // faces.push_back(Vector3r(7,10,3)); // faces.push_back(Vector3r(7,6,10)); // faces.push_back(Vector3r(7,11,6)); // faces.push_back(Vector3r(11,0,6)); // faces.push_back(Vector3r(0,1,6)); // faces.push_back(Vector3r(6,1,10)); // faces.push_back(Vector3r(9,0,11)); // faces.push_back(Vector3r(9,11,2)); // faces.push_back(Vector3r(9,2,5)); // faces.push_back(Vector3r(7,2,11));} trunk-2018.02b/pkg/dem/deformablecohesive/Gl1_Node.hpp000066400000000000000000000051671324306050200224540ustar00rootroot00000000000000/************************************************************************* * Copyright (C) 2013 by Burak ER burak.er@btu.edu.tr * * * * * * This program is free software; it is licensed under the terms of the * * GNU General Public License v2 or later. See file LICENSE for details. * *************************************************************************/ #pragma once #include #include class Gl1_Node : public GlShapeFunctor{ private: // for stripes static vector vertices, faces; static int glStripedSphereList; static int glGlutSphereList; void subdivideTriangle(Vector3r& v1,Vector3r& v2,Vector3r& v3, int depth); // void drawSphere(const Vector3r& color); //Generate GlList for GLUT sphere void initGlutGlList(); //Generate GlList for sliced spheres void initStripedGlList(); //for regenerating glutSphere list if needed static Real prevQuality; public: virtual void go(const shared_ptr&, const shared_ptr&,bool,const GLViewInfo&); YADE_CLASS_BASE_DOC_STATICATTRS(Gl1_Node,GlShapeFunctor,"Renders :yref:`Node` object", ((Real,quality,1.0,,"Change discretization level of spheres. quality>1 for better image quality, at the price of more cpu/gpu usage, 0`_)")) ((int,glutStacks,6,(Attr::noSave | Attr::readonly),"Base number of sphere stacks, multiplied by :yref:`Gl1_Node::quality` before use; not used with ``stripes`` (see `glut{Solid,Wire}Sphere reference `_)")) ); RENDERS(Node); }; REGISTER_SERIALIZABLE(Gl1_Node); trunk-2018.02b/pkg/dem/deformablecohesive/If2_2xLin4NodeTetra_LinCohesiveStiffPropDampElastMat.cpp000066400000000000000000000056541324306050200330300ustar00rootroot00000000000000/************************************************************************* * Copyright (C) 2013 by Burak ER burak.er@btu.edu.tr * * * * * * This program is free software; it is licensed under the terms of the * * GNU General Public License v2 or later. See file LICENSE for details. * *************************************************************************/ #include #include YADE_PLUGIN((If2_2xLin4NodeTetra_LinCohesiveStiffPropDampElastMat)); If2_2xLin4NodeTetra_LinCohesiveStiffPropDampElastMat::~If2_2xLin4NodeTetra_LinCohesiveStiffPropDampElastMat(){}; /******************************************************************** InternalForceDispatcher *********************************************************************/ typedef std::map NodePairsMap;//Initial node differences CREATE_LOGGER(If2_2xLin4NodeTetra_LinCohesiveStiffPropDampElastMat); void If2_2xLin4NodeTetra_LinCohesiveStiffPropDampElastMat::go(const shared_ptr& element,const shared_ptr& material,const shared_ptr& bdy) { shared_ptr interactionelement=YADE_PTR_CAST(element); shared_ptr mat=YADE_PTR_CAST(material); NodePairsMap::iterator i1(interactionelement->nodepairs.begin()); shared_ptr node11=i1->first.node1;shared_ptr node12=i1->first.node2; Vector3r initial1=i1->second.position; std::advance(i1,1); shared_ptr node21= i1->first.node1;shared_ptr node22=i1->first.node2; Vector3r initial2=i1->second.position; std::advance(i1,1); shared_ptr node31= i1->first.node1;shared_ptr node32=i1->first.node2; Vector3r initial3=i1->second.position; Vector3r disp1=(node11->state->pos-node12->state->pos)-initial1; Vector3r disp2=(node21->state->pos-node22->state->pos)-initial2; Vector3r disp3=(node31->state->pos-node32->state->pos)-initial3; Vector3r l1=node11->state->pos-node21->state->pos; Vector3r l2=node31->state->pos-node21->state->pos; Real A=0.5*fabs(l1.cross(l2).norm()); Vector3r f1=(0.083333333)*A*mat->youngmodulus*(2*disp1+disp2+disp3); Vector3r f2=(0.083333333)*A*mat->youngmodulus*(disp1+2*disp2+disp3); Vector3r f3=(0.083333333)*A*mat->youngmodulus*(disp1+disp2+2*disp3); //cout<<"forces are \n"<forces.addForce(node11->getId(),-f1); scene->forces.addForce(node12->getId(),f1); scene->forces.addForce(node21->getId(),-f2); scene->forces.addForce(node22->getId(),f2); scene->forces.addForce(node31->getId(),-f3); scene->forces.addForce(node32->getId(),f3); return; } trunk-2018.02b/pkg/dem/deformablecohesive/If2_2xLin4NodeTetra_LinCohesiveStiffPropDampElastMat.hpp000066400000000000000000000031311324306050200330210ustar00rootroot00000000000000/************************************************************************* * Copyright (C) 2013 by Burak ER burak.er@btu.edu.tr * * * * * * This program is free software; it is licensed under the terms of the * * GNU General Public License v2 or later. See file LICENSE for details. * *************************************************************************/ #pragma once #include #include #include #include #include #include #include #include #include #include #include #include #include #include class If2_2xLin4NodeTetra_LinCohesiveStiffPropDampElastMat : public InternalForceFunctor { public : virtual void go(const shared_ptr&,const shared_ptr&,const shared_ptr&); virtual ~If2_2xLin4NodeTetra_LinCohesiveStiffPropDampElastMat(); FUNCTOR2D(Lin4NodeTetra_Lin4NodeTetra_InteractionElement,LinCohesiveStiffPropDampElastMat); YADE_CLASS_BASE_DOC(If2_2xLin4NodeTetra_LinCohesiveStiffPropDampElastMat,InternalForceFunctor,"Apply internal forces of the tetrahedral element using lumped mass theory") }; REGISTER_SERIALIZABLE(If2_2xLin4NodeTetra_LinCohesiveStiffPropDampElastMat); trunk-2018.02b/pkg/dem/deformablecohesive/If2_Lin4NodeTetra_LinIsoRayleighDampElast.cpp000066400000000000000000000066021324306050200307230ustar00rootroot00000000000000/************************************************************************* * Copyright (C) 2013 by Burak ER burak.er@btu.edu.tr * * * * * * This program is free software; it is licensed under the terms of the * * GNU General Public License v2 or later. See file LICENSE for details. * *************************************************************************/ #include #include YADE_PLUGIN((If2_Lin4NodeTetra_LinIsoRayleighDampElast)); If2_Lin4NodeTetra_LinIsoRayleighDampElast::~If2_Lin4NodeTetra_LinIsoRayleighDampElast(){}; /******************************************************************** InternalForceDispatcher *********************************************************************/ typedef DeformableElement::NodeMap NodeMap; #define VECTOR12_TEMPLATE(Scalar) Eigen::Matrix typedef VECTOR12_TEMPLATE(Real) Vector12r; CREATE_LOGGER(If2_Lin4NodeTetra_LinIsoRayleighDampElast); void If2_Lin4NodeTetra_LinIsoRayleighDampElast::go(const shared_ptr& element,const shared_ptr& material,const shared_ptr& bdy) { shared_ptr tetel=YADE_PTR_CAST(element); shared_ptr mat=YADE_PTR_CAST(material); DeformableElement::NodeMap::iterator i0(tetel->localmap.begin()); DeformableElement::NodeMap::iterator i1(i0); DeformableElement::NodeMap::iterator i2(i0); DeformableElement::NodeMap::iterator i3(i0); std::advance(i1,1); std::advance(i2,2); std::advance(i3,3); if(tetel->massMatrixInvProductstiffnessMatrix==0) { Vector3r node0relpos=Vector3r(0,0,0); Vector3r node1relpos=i1->second.position-i0->second.position; Vector3r node2relpos=i2->second.position-i0->second.position; Vector3r node3relpos=i3->second.position-i0->second.position; // I dont know wheter this is optimum or not tetel->massMatrixInvProductstiffnessMatrix=shared_ptr ( new Matrix( tetel->calculateMassMatrix(mat->density,mat->poissonratio).inverse()* tetel->calculateStiffness(mat->youngmodulus,mat->poissonratio,node0relpos,node1relpos,node2relpos,node3relpos) ) ); } //apply internal forces to the tetrahedron //Calculate displacements Vector12r displacements; Vector12r displacementvelocity; displacements<first->state->pos - i0->second.position, i1->first->state->pos - i1->second.position, i2->first->state->pos - i2->second.position, i3->first->state->pos - i3->second.position; displacementvelocity<first->state->vel, i1->first->state->vel, i2->first->state->vel, i3->first->state->vel; //Now calculate the forces Vector12r forces=-(*tetel->massMatrixInvProductstiffnessMatrix)*displacements-(mat->alpha*Matrix::Identity(12,12)+(*tetel->massMatrixInvProductstiffnessMatrix)*(mat->beta))*displacementvelocity; scene->forces.addForce(i0->first->getId(),forces.segment(0,3)); scene->forces.addForce(i1->first->getId(),forces.segment(3,3)); scene->forces.addForce(i2->first->getId(),forces.segment(6,3)); scene->forces.addForce(i3->first->getId(),forces.segment(9,3)); return; } trunk-2018.02b/pkg/dem/deformablecohesive/If2_Lin4NodeTetra_LinIsoRayleighDampElast.hpp000066400000000000000000000027451324306050200307340ustar00rootroot00000000000000/************************************************************************* * Copyright (C) 2013 by Burak ER burak.er@btu.edu.tr * * * * * * This program is free software; it is licensed under the terms of the * * GNU General Public License v2 or later. See file LICENSE for details. * *************************************************************************/ #pragma once #include #include #include #include #include #include #include #include #include #include #include #include #include #include class If2_Lin4NodeTetra_LinIsoRayleighDampElast : public InternalForceFunctor { public : virtual void go(const shared_ptr&,const shared_ptr&,const shared_ptr&); virtual ~If2_Lin4NodeTetra_LinIsoRayleighDampElast(); FUNCTOR2D(Lin4NodeTetra,LinIsoRayleighDampElastMat); YADE_CLASS_BASE_DOC(If2_Lin4NodeTetra_LinIsoRayleighDampElast,InternalForceFunctor,"Apply internal forces of the tetrahedral element using lumped mass theory") }; REGISTER_SERIALIZABLE(If2_Lin4NodeTetra_LinIsoRayleighDampElast); trunk-2018.02b/pkg/dem/deformablecohesive/Lin4NodeTetra.cpp000066400000000000000000000070751324306050200234730ustar00rootroot00000000000000/************************************************************************* * Copyright (C) 2013 by Burak ER * * * * * * This program is free software; it is licensed under the terms of the * * GNU General Public License v2 or later. See file LICENSE for details. * *************************************************************************/ #include #include #include #include #include #include #include #include YADE_PLUGIN((Lin4NodeTetra)); CREATE_LOGGER(Lin4NodeTetra); Lin4NodeTetra::~Lin4NodeTetra(){ } void Lin4NodeTetra::initialize(void){ maxNodeCount=4; } Matrix Lin4NodeTetra::calculateMassMatrix(Real density,Real v) { Matrix mass(12,12); mass<<2,0,0,1,0,0,1,0,0,1,0,0, 0,2,0,0,1,0,0,1,0,0,1,0, 0,0,2,0,0,1,0,0,1,0,0,1, 1,0,0,2,0,0,1,0,0,1,0,0, 0,1,0,0,2,0,0,1,0,0,1,0, 0,0,1,0,0,2,0,0,1,0,0,1, 1,0,0,1,0,0,2,0,0,1,0,0, 0,1,0,0,1,0,0,2,0,0,1,0, 0,0,1,0,0,1,0,0,2,0,0,1, 1,0,0,1,0,0,1,0,0,2,0,0, 0,1,0,0,1,0,0,1,0,0,2,0, 0,0,1,0,0,1,0,0,1,0,0,2; mass=0.05*(density*v)*mass; return mass; } Matrix Lin4NodeTetra::calculateStiffness(Real E, Real v,Vector3r pos0,Vector3r pos1,Vector3r pos2,Vector3r pos3) { /* * The calculation is relative, therefore, we do not need position of the first node. * All other nodes are calculated with respect to first node which is at zero on local coordinates. * The local coordinates are the coordinates that is fixed at node0 and parallel to global axes. * * */ Vector3r pos01=-pos1; Vector3r pos02=-pos2; Vector3r pos03=-pos3; Vector3r pos12= pos1-pos2; Vector3r pos13= pos1-pos3; Vector3r pos23= pos2-pos3; Matrix J(4,4); J.col(0)<<1,pos0; J.col(1)<<1,pos1; J.col(2)<<1,pos2; J.col(3)<<1,pos3; Real x12=pos01(0), x13=pos02(0), x14=pos03(0), x23= pos12(0), x24=pos13(0), x34= pos23(0); Real x21=-x12, x31=-x13, /*x41=-x14,*/ x32=-x23, x42=-x24, x43=-x34; Real y12=pos01(1), y13=pos02(1), y14=pos03(1), y23= pos12(1), y24=pos13(1), y34= pos23(1); Real y21=-y12, y31=-y13, /*y41=-y14,*/ y32=-y23, y42=-y24, y43=-y34; Real z12=pos01(2), z13=pos02(2), z14=pos03(2), z23= pos12(2), z24=pos13(2), z34= pos23(2); Real z21=-z12, z31=-z13, /*z41=-z14,*/ z32=-z23, z42=-z24, z43=-z34; Real V=fabs(((0.166666667)*J.determinant())); Real a1=y42*z32-y32*z42, b1=x32*z42-x42*z32, c1=x42*y32-x32*y42; Real a2=y31*z43-y34*z13, b2=x43*z31-x13*z34, c2=x31*y43-x34*y13; Real a3=y24*z14-y14*z24, b3=x14*z24-x24*z14, c3=x24*y14-x14*y24; Real a4=y13*z21-y12*z31, b4=x21*z13-x31*z12, c4=x13*y21-x12*y31; //std::cout<<"Volume of the element is "< #include #include #include #include class NewtonIntegrator; namespace yade{ #define VECTOR4_TEMPLATE(Scalar) Eigen::Matrix typedef VECTOR4_TEMPLATE(Real) Vector4r; class Lin4NodeTetra: public DeformableElement { public: friend class If2_Lin4NodeTetra_LinIsoRayleighDampElast; shared_ptr massMatrixInvProductstiffnessMatrix; Matrix calculateStiffness(Real, Real ,Vector3r,Vector3r,Vector3r,Vector3r); Matrix calculateMassMatrix(Real, Real); virtual ~Lin4NodeTetra(); void initialize(void); Real getVolume(void){ NodeMap::iterator i0(localmap.begin()); NodeMap::iterator i1(i0); NodeMap::iterator i2(i0); NodeMap::iterator i3(i0); std::advance(i1,1); std::advance(i2,2); std::advance(i3,3); Matrix J(4,4); Vector3r pos0=Vector3r(0,0,0); Vector3r pos1=i1->second.position-i0->second.position; Vector3r pos2=i2->second.position-i0->second.position; Vector3r pos3=i3->second.position-i0->second.position; /* Vector3r pos01=-pos1; Vector3r pos02=-pos2; Vector3r pos03=-pos3; Vector3r pos12= pos1-pos2; Vector3r pos13= pos1-pos3; Vector3r pos23= pos2-pos3; */ J.col(0)<<1,pos0; J.col(1)<<1,pos1; J.col(2)<<1,pos2; J.col(3)<<1,pos3; /* Real x12=pos01(0), x13=pos02(0), x14=pos03(0), x23= pos12(0), x24=pos13(0), x34= pos23(0); Real x21=-x12, x31=-x13, x41=-x14, x32=-x23, x42=-x24, x43=-x34; Real y12=pos01(1), y13=pos02(1), y14=pos03(1), y23= pos12(1), y24=pos13(1), y34= pos23(1); Real y21=-y12, y31=-y13, y41=-y14, y32=-y23, y42=-y24, y43=-y34; Real z12=pos01(2), z13=pos02(2), z14=pos03(2), z23= pos12(2), z24=pos13(2), z34= pos23(2); Real z21=-z12, z31=-z13, z41=-z14, z32=-z23, z42=-z24, z43=-z34; */ Real V=fabs(((0.166666667)*J.determinant())); return V; } YADE_CLASS_BASE_DOC_ATTRS_INIT_CTOR_PY(Lin4NodeTetra,DeformableElement,"Tetrahedral Deformable Element Composed of Nodes", , , createIndex(); /*ctor*/ initialize(); , /*py*/ ); DECLARE_LOGGER; REGISTER_CLASS_INDEX(Lin4NodeTetra,DeformableElement); }; } // necessary using namespace yade; REGISTER_SERIALIZABLE(Lin4NodeTetra); trunk-2018.02b/pkg/dem/deformablecohesive/Lin4NodeTetra_Lin4NodeTetra_InteractionElement.cpp000066400000000000000000000020601324306050200320250ustar00rootroot00000000000000/************************************************************************* * Copyright (C) 2013 by Burak ER * * * * * * This program is free software; it is licensed under the terms of the * * GNU General Public License v2 or later. See file LICENSE for details. * *************************************************************************/ #include #include #include #include #include #include #include #include YADE_PLUGIN((Lin4NodeTetra_Lin4NodeTetra_InteractionElement)); CREATE_LOGGER(Lin4NodeTetra_Lin4NodeTetra_InteractionElement); Lin4NodeTetra_Lin4NodeTetra_InteractionElement::~Lin4NodeTetra_Lin4NodeTetra_InteractionElement(){ } void Lin4NodeTetra_Lin4NodeTetra_InteractionElement::initialize(void){ } trunk-2018.02b/pkg/dem/deformablecohesive/Lin4NodeTetra_Lin4NodeTetra_InteractionElement.hpp000066400000000000000000000026121324306050200320350ustar00rootroot00000000000000/************************************************************************* * Copyright (C) 2013 by Burak ER * * * * * * This program is free software; it is licensed under the terms of the * * GNU General Public License v2 or later. See file LICENSE for details. * *************************************************************************/ #pragma once #include #include #include #include #include #include #include class Lin4NodeTetra_Lin4NodeTetra_InteractionElement: public DeformableCohesiveElement { public: friend class If2_2xLin4NodeTetra_LinCohesiveStiffPropDampElastMat; virtual ~Lin4NodeTetra_Lin4NodeTetra_InteractionElement(); void initialize(void); YADE_CLASS_BASE_DOC_ATTRS_INIT_CTOR_PY(Lin4NodeTetra_Lin4NodeTetra_InteractionElement,DeformableCohesiveElement,"Tetrahedral Deformable Element Composed of Nodes", , , createIndex(); /*ctor*/ initialize(); , /*py*/ ); DECLARE_LOGGER; REGISTER_CLASS_INDEX(Lin4NodeTetra_Lin4NodeTetra_InteractionElement,DeformableCohesiveElement); }; // necessary using namespace yade; REGISTER_SERIALIZABLE(Lin4NodeTetra_Lin4NodeTetra_InteractionElement); trunk-2018.02b/pkg/dem/deformablecohesive/LinElastMat.cpp000066400000000000000000000013761324306050200232320ustar00rootroot00000000000000/************************************************************************* * Copyright (C) 2013 by Burak ER * * * * * * This program is free software; it is licensed under the terms of the * * GNU General Public License v2 or later. See file LICENSE for details. * *************************************************************************/ #include YADE_PLUGIN((DeformableElementMaterial)(LinIsoElastMat)(LinIsoRayleighDampElastMat)); DeformableElementMaterial::~DeformableElementMaterial(void){} LinIsoElastMat::~LinIsoElastMat(void){} LinIsoRayleighDampElastMat::~LinIsoRayleighDampElastMat(void){} trunk-2018.02b/pkg/dem/deformablecohesive/LinElastMat.hpp000066400000000000000000000036711324306050200232370ustar00rootroot00000000000000/************************************************************************* * Copyright (C) 2013 by Burak ER * * * * * * This program is free software; it is licensed under the terms of the * * GNU General Public License v2 or later. See file LICENSE for details. * *************************************************************************/ #pragma once #include #include /*! Elastic material */ class DeformableElementMaterial: public Material{ public: virtual ~DeformableElementMaterial(); YADE_CLASS_BASE_DOC_ATTRS_CTOR(DeformableElementMaterial,Material,"Deformable Element Material.", ((Real,density,1,,"Density of the material.")), /*ctor*/ createIndex(); ); REGISTER_CLASS_INDEX(DeformableElementMaterial,Material); }; REGISTER_SERIALIZABLE(DeformableElementMaterial); class LinIsoElastMat: public DeformableElementMaterial{ public: virtual ~LinIsoElastMat(); YADE_CLASS_BASE_DOC_ATTRS_CTOR(LinIsoElastMat,DeformableElementMaterial,"Linear Isotropic Elastic material", ((Real,youngmodulus,.78e5,,"Young's modulus. Initially aluminium.")) ((Real,poissonratio,.33,,"Poisson ratio. Initially aluminium.")), createIndex(); ); REGISTER_CLASS_INDEX(LinIsoElastMat,DeformableElementMaterial); }; REGISTER_SERIALIZABLE(LinIsoElastMat); /*Standard damped linear elastic material*/ class LinIsoRayleighDampElastMat: public LinIsoElastMat{ public: virtual ~LinIsoRayleighDampElastMat(); YADE_CLASS_BASE_DOC_ATTRS_CTOR(LinIsoRayleighDampElastMat,LinIsoElastMat,"Elastic material with Rayleigh Damping.", ((Real,alpha,0,,"Mass propotional damping constant of Rayleigh Damping.")) ((Real,beta,0,,"Stiffness propotional damping constant of Rayleigh Damping.")), createIndex(); ); REGISTER_CLASS_INDEX(LinIsoRayleighDampElastMat,LinIsoElastMat); }; REGISTER_SERIALIZABLE(LinIsoRayleighDampElastMat); trunk-2018.02b/pkg/dem/deformablecohesive/Node.cpp000066400000000000000000000001241324306050200217300ustar00rootroot00000000000000#include Node::~Node(){} YADE_PLUGIN((Node)); trunk-2018.02b/pkg/dem/deformablecohesive/Node.hpp000066400000000000000000000021051324306050200217360ustar00rootroot00000000000000/************************************************************************* * Copyright (C) 2013 by Burak ER * * * * * * This program is free software; it is licensed under the terms of the * * GNU General Public License v2 or later. See file LICENSE for details. * *************************************************************************/ #ifndef NODE_HPP_ #define NODE_HPP_ #include //TODO: Look at Sphere hack to work around problem // HACK to work around https://bugs.launchpad.net/yade/+bug/528509 // see comments there for explanation namespace yade{ class Node: public Shape{ public: Node(Real _radius): radius(_radius){ createIndex();} virtual ~Node (); YADE_CLASS_BASE_DOC_ATTRS_CTOR(Node,Shape,"Geometry of node particle.", ((Real,radius,0.1,,"Radius [m]")), createIndex(); /*ctor*/ ); REGISTER_CLASS_INDEX(Node,Shape); }; } // necessary using namespace yade; // must be outside yade namespace REGISTER_SERIALIZABLE(Node); #endif trunk-2018.02b/pkg/lbm/000077500000000000000000000000001324306050200145215ustar00rootroot00000000000000trunk-2018.02b/pkg/lbm/HydrodynamicsLawLBM.cpp000066400000000000000000002062271324306050200210520ustar00rootroot00000000000000/************************************************************************* * Copyright (C) 2009-2012 by Franck Lominé * * franck.lomine@insa-rennes.fr * * * * This program is free software; it is licensed under the terms of the * * GNU General Public License v2 or later. See file LICENSE for details. * * * * Luc Scholtès luc.scholtes@univ-lorraine.fr * * and Luc Sibille luc.sibille@3sr-grenoble.fr also contributed to this * * code. * * * * Lominé F., Scholtès L., Sibille L., Poullain P. (2013) * * Modelling of fluid-solid interaction in granular media with coupled * * LB/DE methods: application to piping erosion. International Journal * * for Numerical and Analytical Methods in Geomechanics, 37(6):577-596 * * doi: 10.1002/nag.1109 * * * * Sibille L., Lominé F., Marot D. (2012) Investigation In Modelling * * Piping Erosion With a Coupled «Lattice Boltzmann – Discrete Element» * * Numerical Method. in Proc. 6th Int. Conference on Scour and Erosion * * (ICSE-6), pp. 93-100. * * * *************************************************************************/ #ifdef LBM_ENGINE #include"HydrodynamicsLawLBM.hpp" #include #include #include #include #include #include namespace bfs=boost::filesystem; inline Vector3i vect3rToVect3i(Vector3r vect){Vector3i newvect((int)vect[0],(int)vect[1],(int)vect[2]);return(newvect);} HydrodynamicsLawLBM::~HydrodynamicsLawLBM() {}; bool HydrodynamicsLawLBM::isActivated(){ DEM_ITER=scene->iter;//+1; if(EngineIsActivated){ if((DEM_ITER % DemIterLbmIterRatio==0)&&(DEM_ITER!=DemIterLbmIterRatio)) { if(DEM_ITER==0){DEMdt0 = scene->dt;scene->dt=1.e-50;} return(true);} else{ if(applyForcesAndTorques) CalculateAndApplyForcesAndTorquesOnBodies(false,true); return(false); } }else return(false); } void HydrodynamicsLawLBM::action() { timingDeltas->start(); NB_BODIES= scene->bodies->size(); int I, J, step=0; NbFluidNodes=0; NbSolidNodes=0; Real CurMinVelOfPtc=1000000.; Real CurMaxVelOfPtc=-1000000.; int ErrorCriterion=2; /*------------------------------------------------------------------*/ /* AT FIRST ITERATION */ /*------------------------------------------------------------------*/ if(firstRun){ // createNewFiles(); //this line is move further to create files only when the recording configuration chosen by the operator is known bool initVbCutOff=false; if(VbCutOff==-1) initVbCutOff=true; halfWallthickness=1000000.; NB_WALLS=0; //NB_DYNWALLS=0; NB_DYNGRAINS=0; LBMbody tmpbody; FOREACH(const shared_ptr& b, *scene->bodies){ if(!b) continue; // deleted bodies if (b->shape->getClassName()=="Box"){ Vector3r ext(YADE_PTR_CAST ( b->shape )->extents); if (ext[0]isDynamic()) NB_DYNWALLS++; } if (b->shape->getClassName()=="Sphere"){ const shared_ptr& sph = YADE_PTR_CAST ( b->shape ); if(IdFirstSphere==-1) IdFirstSphere=b->getId(); tmpbody.setAsPtc(); Real r=sph->radius; if(b->isDynamic()){ NB_DYNGRAINS++; /*--- computation of the initial volume ---*/ if(!strcmp(model.c_str(), "d2q9" )) Vo += Mathr::PI*(r*r); else Vo += 4./3.*Mathr::PI*(r*r*r); if(initVbCutOff) VbCutOff=max(VbCutOff,b->state->vel.norm()+r*b->state->angVel.norm()); tmpbody.saveProperties=true; }else{ if(b->state->pos[1]>0.) tmpbody.saveProperties=true; else tmpbody.saveProperties=false; } /*--- computation of Rmean Rmax Rmin --*/ MaxBodyRadius=max(r,MaxBodyRadius); MinBodyRadius=min(r,MinBodyRadius); MeanBodyRadius+=r; } LBbodies.push_back(tmpbody); } Wallthickness=2.0*halfWallthickness; NB_GRAINS=NB_BODIES-NB_WALLS; //Luc: is it right to count the walls as dynamic bodies? //Franck why not ? Enhancement (see next line is coming ;-)) NB_DYNBODIES=NB_WALLS+NB_DYNGRAINS; //NB_DYNBODIES=NB_DYNWALLS+NB_DYNGRAINS; MeanBodyRadius=MeanBodyRadius/NB_GRAINS; InitialNumberOfDynamicParticles=NB_DYNGRAINS; /*-------------------------------------------------------------------------*/ /* D2Q9 model configuration */ /*-------------------------------------------------------------------------*/ if(!strcmp(model.c_str(), "d2q9" )){ dim=2; /*--------------------------------------*/ /* D2Q9 model: 6 2 5 */ /* 3 _\|/__1 */ /* /|\ */ /* 7 4 8 */ /*--------------------------------------*/ /*----------- D2Q9 constants ---------*/ w.push_back(4.0/9.0); for(int aa=1;aa<=4;aa++) w.push_back(1.0/9.0); for(int aa=5;aa<=8;aa++) w.push_back(1.0/36.0); /*--------- node position vectors ----*/ eib.push_back(Vector3r( 0., 0., 0.)); //0 eib.push_back(Vector3r( 1., 0., 0.)); //1 eib.push_back(Vector3r( 0., 1., 0.)); //2 eib.push_back(Vector3r(-1., 0., 0.)); //3 eib.push_back(Vector3r( 0.,-1., 0.)); //4 eib.push_back(Vector3r( 1., 1., 0.)); //5 eib.push_back(Vector3r(-1., 1., 0.)); //6 eib.push_back(Vector3r(-1.,-1., 0.)); //7 eib.push_back(Vector3r( 1.,-1., 0.)); //8 NbDir=(int) eib.size(); /*-------------- opposite nodes --------*/ opp.push_back(0); opp.push_back(3); opp.push_back(4); opp.push_back(1); opp.push_back(2); opp.push_back(7); opp.push_back(8); opp.push_back(5); opp.push_back(6); }else {cerr<<"This model is not implemented yet: "<=0&&res=0&&res=0&&res=0&&res=0&&res=0&&res=0&&res=0&&res=0&&res=0&&res=0&&res=0&&res=0&&res=0&&res=0&&res=0&&res=0&&res=0&&res=0&&res=0&&res=0&&res=0&&res=0&&res=0&&res=0&&res0)&&(SAVE_OBSERVEDPTC)) ObservedPtc=IdFirstSphere; //Condition If(SAVE_OBSERVEDPTC) is added to save observedPtc only if it is required by the operator if ((SAVE_OBSERVEDPTC)&&((unsigned)ObservedPtc>=LBbodies.size())){cerr<<"Error: ObservedPtc>bodies.size()"<=0&&res=0&&res=0&&resstate.get(); State* sWallYp=Body::byId(WallYp_id,scene)->state.get(); State* sWallXm=Body::byId(WallXm_id,scene)->state.get(); State* sWallXp=Body::byId(WallXp_id,scene)->state.get(); State* sWallZp=Body::byId(WallZp_id,scene)->state.get(); State* sWallZm=Body::byId(WallZm_id,scene)->state.get(); height = sWallYp->se3.position.y() - sWallYm->se3.position.y(); width = sWallXp->se3.position.x() - sWallXm->se3.position.x(); depth = sWallZp->se3.position.z() - sWallZm->se3.position.z(); Lx0 = width-Wallthickness; Ly0 = height-Wallthickness; Lz0 = depth-Wallthickness; Real Lx1 = width+Wallthickness; Real Ly1 = height+Wallthickness; Real Lz1 = depth+Wallthickness; /*--------------------------------------------------------------*/ /* Computation of number of lattice nodes in each direction */ /*--------------------------------------------------------------*/ dx = Lx0 / (Real) (Nx-1); invdx=1./dx; //Number of nodes (after-correction) Ny = ceil(invdx*Ly1)+1; dx = Ly1 / (Real) (Ny-1); invdx=1./dx; dx2=dx*dx; Nx = ceil(invdx*Lx1)+1; Ny = ceil(invdx*Ly1)+1; Nz=1; cerr <<"LXYZ0= "<se3.position.x()- halfWallthickness<0.){cerr <<"Wall position error: please avoid negatives region (X)"<se3.position.z()- halfWallthickness<0.)){cerr <<"Wall position error: please avoid negatives region (Z)"<=Nx*Ny)){ cerr <<"Warning ObservedNode is >= Nx*Ny ... exit"<state.get(); State* sWallYp=Body::byId(WallYp_id,scene)->state.get(); State* sWallXm=Body::byId(WallXm_id,scene)->state.get(); State* sWallXp=Body::byId(WallXp_id,scene)->state.get(); State* sWallZp=Body::byId(WallZp_id,scene)->state.get(); State* sWallZm=Body::byId(WallZm_id,scene)->state.get(); timingDeltas->checkpoint("Reinit:Nodes0"); #pragma omp parallel for for (int nidx=0; nidx=invdx*(sWallXp->se3.position.x() - halfWallthickness))){ nodes[nidx].setAsObstacle(); nodes[nidx].isObstacleBoundary=true; nodes[nidx].body_id=WallXp_id; NbSolidNodes++;} /*--- according to X- ---*/ else if (useWallXm&&(nodes[nidx].i<=invdx*(sWallXm->se3.position.x() + halfWallthickness))){ nodes[nidx].setAsObstacle(); nodes[nidx].isObstacleBoundary=true; nodes[nidx].body_id=WallXm_id; NbSolidNodes++;} /*--- according to Y+ ---*/ else if (useWallYp&&(nodes[nidx].j>=invdx*(sWallYp->se3.position.y() - halfWallthickness))){ nodes[nidx].setAsObstacle(); nodes[nidx].isObstacleBoundary=true; nodes[nidx].body_id=WallYp_id; NbSolidNodes++;} /*--- according to Y- ---*/ else if (useWallYm&&(nodes[nidx].j<=invdx*(sWallYm->se3.position.y() + halfWallthickness))){ nodes[nidx].setAsObstacle(); nodes[nidx].isObstacleBoundary=true; nodes[nidx].body_id=WallYm_id; NbSolidNodes++;} /*--- according to Z+ ---*/ else if (useWallZp&&(nodes[nidx].k>=invdx*(sWallZp->se3.position.z() - halfWallthickness))){ nodes[nidx].setAsObstacle(); nodes[nidx].isObstacleBoundary=true; nodes[nidx].body_id=WallZp_id; NbSolidNodes++;} /*--- according to Z- ---*/ else if (useWallZm&&(nodes[nidx].k<=invdx*(sWallZm->se3.position.z() + halfWallthickness))){ nodes[nidx].setAsObstacle(); nodes[nidx].isObstacleBoundary=true; nodes[nidx].body_id=WallZm_id; NbSolidNodes++;} if(firstRun){nodes[nidx].wasObstacle=nodes[nidx].isObstacle;} } /*---------------------------------------------------------------*/ /*- Solid particle detection and recording of their properties --*/ /*---------------------------------------------------------------*/ NumberOfDynamicParticles=0; if(removingCriterion!=0) IdOfNextErodedPtc.clear(); FOREACH(const shared_ptr& b, *scene->bodies){ if(!b) continue; // deleted bodies State* state=b->state.get(); const int id=b->getId(); //if ((b->shape->getClassName()=="Sphere")&&(b->isDynamic())){ //ModLuc: removing of b->isDynamic() in order that non dynamic particle can be seen by the LBM if (b->shape->getClassName()=="Sphere"){ const shared_ptr& sphere = YADE_PTR_CAST ( b->shape ); LBbodies[id].pos=invdx*state->pos; LBbodies[id].vel=(state->vel)/c; LBbodies[id].AVel= (state->angVel)*dt; LBbodies[id].radius=invdx*RadFactor*(sphere->radius); CurMinVelOfPtc=min(CurMinVelOfPtc,state->vel.norm()); CurMaxVelOfPtc=max(CurMaxVelOfPtc,state->vel.norm()); Vector3r posMax=LBbodies[id].pos+ Vector3r(LBbodies[id].radius,LBbodies[id].radius,LBbodies[id].radius); Vector3r posMin=LBbodies[id].pos- Vector3r(LBbodies[id].radius,LBbodies[id].radius,LBbodies[id].radius); Vector3r dist=Vector3r::Zero(); for(int ii=posMin[0]-1;ii<=posMax[0]+1;ii++) for(int jj=posMin[1]-1;jj<=posMax[1]+1;jj++){ if((ii==-1)||(ii==Nx)||(jj==-1)||(jj==Ny)) continue; if((ii<-1)||(ii>Nx)||(jj<-1)||(jj>Ny)) continue; if (LBbodies[id].radius < Rmin) Rmin = LBbodies[id].radius; int nidx=ii+jj*Nx; dist=nodes[nidx].posb-LBbodies[id].pos; if(dist.norm()isDynamic() to remove only dynamic particles and not already removed particles changed into non dynamic if((removingCriterion!=0)&&(b->isDynamic())){ switch(removingCriterion){ case 1: /* criterion with respect to the particle postion in x direction */ if(LBbodies[id].pos.x()>(invdx*(sWallXp->se3.position.x())-1.05*MaxBodyRadius/RadFactor)){ IdOfNextErodedPtc.push_back(id); } break; case 2: /* criterion on particle velocity */ if((LBbodies[id].vel.norm()>VelocityThreshold)||(LBbodies[id].pos.x()>(invdx*(sWallXp->se3.position.x())-2.*LBbodies[id].radius))) {IdOfNextErodedPtc.push_back(id);} break; default: exit(-1); break; } } //NumberOfDynamicParticles++; //ModLuc: to still count only dynamic particles and not all particles if(b->isDynamic()) NumberOfDynamicParticles++; } LBbodies[id].force=Vector3r::Zero(); LBbodies[id].momentum=Vector3r::Zero(); } /*------------------------------------------------------------------*/ /*------------------ detection of boundary nodes -------------------*/ /*------------------------------------------------------------------*/ #pragma omp parallel for for (int nidx=0; nidx1) ) MODE = 1; if((IterSubCyclingStart<=0) && (IterMax==1) ) MODE = 2; if(IterSubCyclingStart>0) MODE = 3; /*------------------------------------------------------------------*/ /*---------------------------- SUBCYCLING --------------------------*/ /*------------------------------------------------------------------*/ if(MODE==3&&IterMaxdt=newDEMdt; if(SaveMode==2){ IterSave=TimeSave/(dt); if(TimeSavedt; DEM_TIME=DEM_ITER*DEMdt; for (iter=0; iter& b, *scene->bodies){ if(!b) continue; const int id=b->getId(); LBbodies[id].force=Vector3r::Zero(); LBbodies[id].momentum=Vector3r::Zero();} } /*------------------------------------------------------------------*/ /* GENERAL REINITIALIZATION */ /*------------------------------------------------------------------*/ // Vector3r WallBottomVel=Vector3r::Zero();//m s-1 FmoyCur=0.; VmeanFluidC=0.; VmaxC=-1000000.; VminC=1000000.; RhomaxC=-1000000.; RhominC=1000000.;RhoTot=0.; /*------------------------------------------------------------------*/ /* Loop on nodes */ /*------------------------------------------------------------------*/ #pragma omp parallel for for (int nidx=0; nidxc*nodes[nidx].velb.norm()) VminC=c*nodes[nidx].velb.norm(); if(RhomaxCRho*nodes[nidx].rhob) RhominC=Rho*nodes[nidx].rhob; if(!nodes[nidx].isObstacle) VmeanFluidC+=c*nodes[nidx].velb.norm(); } #pragma omp parallel for for(unsigned int lid=0;lid=5) {links[lid].VbMid=Vector3r::Zero();links[lid].ct=0.;} nodes[fid].f[opp[idx_sigma_i]] = nodes[fid].fpostcol[idx_sigma_i] - 2.0*links[lid].ct; nodes[sid].f[idx_sigma_i] = nodes[sid].fpostcol[opp[idx_sigma_i]]+ 2.0*links[lid].ct; if( (MODE==2)||((MODE==3)&&(IterMax==1)) ) {links[lid].ReinitDynamicalProperties();} } VmeanFluidC=VmeanFluidC/NbFluidNodes; /*---------------------------------------------*/ /* Stop criteria */ /*---------------------------------------------*/ // if(use_ConvergenceCriterion){ // switch(ErrorCriterion){ // case 1: // /*--------------------------------------------------------*/ // /* Criterion based on the mean force */ // /*--------------------------------------------------------*/ // if((LBM_ITER > 1000) & (LBM_ITER % 10 == 0)){ // for (int s=NB_WALLS ; s 100) & (LBM_ITER % 10 == 0)){ // if (VmeanFluidC!=0.){ // Real ErrorA = std::abs(VmeanFluidC-PrevVmeanFluidC)/std::abs(VmeanFluidC); // //Real ErrorB = std::abs(VmeanFluidC-PrevPrevVmeanFluidC)/std::abs(VmeanFluidC); // //Error=max(ErrorA,ErrorB); // Error= ErrorA; // PrevPrevVmeanFluidC=PrevVmeanFluidC; // PrevVmeanFluidC=VmeanFluidC; // } // } // break; // default: // cerr <<"Unknow ErrorCriterion value ! "< (m/s)= "<0)){ cerr <<"| VminPtcC (m/s)\t: "<0)&&(iter+1>=IterSubCyclingStart)) {modeTransition();} if ((Error < ConvergenceThreshold)&&(use_ConvergenceCriterion)) { if(MODE==1) break; if(MODE==3) modeTransition(); } if((EndTime>0)&&(LBM_TIME>EndTime)) LbmEnd(); //if((InitialNumberOfDynamicParticles!=0)&&(NumberPtcEroded==InitialNumberOfDynamicParticles)) LbmEnd(); } /*------------------- End of the LBM loop (iterations) --------------------*/ if(MODE==1) { cerr << "LBM ended after " << step << " iterations"; cerr <<" | LBM_ITER = "<< LBM_ITER<<" | Error = " << Error << endl; } /*--------------------------------------------------------------------------------*/ /*-------------- APPLICATION OF HYDRODYNAMIC FORCES ON SPHERES -------------------*/ /*--------------------------------------------------------------------------------*/ if(applyForcesAndTorques) CalculateAndApplyForcesAndTorquesOnBodies(true,true); /*----------------------------------------------------------------------------*/ /*----------------- SPHERES ARE MOVED OUTSIDE THE SIMULATION DOMAIN --------- */ /*----------------------------------------------------------------------------*/ //if(removingCriterion!=0){ // for(unsigned int aa=0; aa b=Body::byId(IdRemoved); // const shared_ptr& sphere = YADE_PTR_CAST ( b->shape ); // Real r=sphere->radius; // outside_limit=outside_limit+1.1*r; // b->state->pos=Vector3r(outside_limit,0.,0.); // b->setDynamic(false); // NB_DYNGRAINS--; // NB_DYNBODIES--; // outside_limit=outside_limit+1.1*r; // cerr <<"Eroded Ptc: "<=0){ FxLBMfile<< 2.*Rho*c2*dx*(LBbodies[nodes[nidx].body_id].force.x()) << " "; FyLBMfile<< 2.*Rho*c2*dx*(LBbodies[nodes[nidx].body_id].force.y()) << " "; MzLBMfile<< 2.*Rho*c2*dx2*LBbodies[nodes[nidx].body_id].momentum.z() << " "; } else{FxLBMfile<< 0. << " ";FyLBMfile<< 0. << " ";MzLBMfile<< 0. << " ";}} if(SAVE_RHO) Rhofile << Rho*nodes[nidx].rhob << " "; if(SAVE_NODEBD) { int tmp=0; if(nodes[nidx].isObstacleBoundary) tmp=nodes[nidx].isObstacleBoundary; if(nodes[nidx].isFluidBoundary) tmp= -1; NodeBoundaryfile << tmp << " ";} if(SAVE_NODEISNEW) { int tmp=0; if(nodes[nidx].isNewObstacle) tmp=nodes[nidx].isNewObstacle; if(nodes[nidx].isNewFluid) tmp=-1; NewNodefile << tmp << " ";} if(nodes[nidx].i==Nx-1){ if(SAVE_BODIES) Bodiesfile<< endl; if(SAVE_VELOCITY) Vfile<< endl; if(SAVE_VELOCITYCOMP){Vxfile<< endl;Vyfile<< endl;} if(SAVE_FORCES){FxLBMfile << endl;FyLBMfile << endl;MzLBMfile << endl;} if(SAVE_RHO) Rhofile << endl; if(SAVE_NODEBD) NodeBoundaryfile << endl; if(SAVE_NODEISNEW) NewNodefile << endl; } } } std::stringstream cmd; cmd<<"bzip2"; if(SAVE_BODIES) {Bodiesfile.close();cmd<<" "< 0 is done earlier when recording configuration is done spherefile_name.str(""); spherefile_name<0) {ofstream file3(spherefile_name.str().c_str());file3.close();} //For what this line is used for? It seems to work without if(SAVE_OBSERVEDPTC){ //Condition to create observedPtc file only if the recording is required by the operator ofstream file4(ObservedPtcFile.c_str()); file4 <<"#iter t x y z r Vx Vy Vz Wx Wy Wz Fx Fy Fz Mx My Mz"<0) bfs::create_directory(bfs::path(dem_dir)); //ModLuc: to create only necessary directory if(dirDem) bfs::create_directory(bfs::path(dem_dir)); //if(SAVE_CONTACTINFO) bfs::create_directory(bfs::path(cntct_dir)); //ModLuc: to create only necessary directory if(dirCntct) bfs::create_directory(bfs::path(cntct_dir)); return; } void HydrodynamicsLawLBM::writelogfile() { ofstream file(LBMlogFile.c_str()); file <<"File format: 1"<0) file <<"\t NbNodePerPtc= "<& b, *scene->bodies){ if(!b) continue; const int id=b->getId(); //if ( ((b->isDynamic())&&(b->shape->getClassName()=="Sphere")) || (b->shape->getClassName()=="Box") ){ //ModLuc: remove the condition (b->isDynamic()) to be able to apply force and torque on non dynamic bodies, by this way hydrodynamic force and torque on bodies can be read through python even if bodies are non dynamic. if ( (b->shape->getClassName()=="Sphere") || (b->shape->getClassName()=="Box") ){ if(mean){ LBbodies[id].fp=LBbodies[id].force; LBbodies[id].force=0.5*(LBbodies[id].fp+LBbodies[id].fm); LBbodies[id].fm=LBbodies[id].fp; LBbodies[id].mp=LBbodies[id].momentum; LBbodies[id].momentum=0.5*(LBbodies[id].mp+LBbodies[id].mm); LBbodies[id].mm=LBbodies[id].mp; LBbodies[id].Fh=2.*Rho*c2*dx*LBbodies[id].force; LBbodies[id].Mh=2.*Rho*c2*dx2*LBbodies[id].momentum; FhTotale=FhTotale+LBbodies[id].Fh; } if(apply){ scene->forces.addForce(id, LBbodies[id].Fh); scene->forces.addTorque(id, LBbodies[id].Mh); } } } return; } YADE_PLUGIN((HydrodynamicsLawLBM)); #endif //LBM_ENGINE trunk-2018.02b/pkg/lbm/HydrodynamicsLawLBM.hpp000066400000000000000000000472171324306050200210610ustar00rootroot00000000000000/************************************************************************* * Copyright (C) 2009-2012 by Franck Lominé * * franck.lomine@insa-rennes.fr * * * * This program is free software; it is licensed under the terms of the * * GNU General Public License v2 or later. See file LICENSE for details. * * * * Luc Scholtès luc.scholtes@univ-lorraine.fr * * and Luc Sibille luc.sibille@3sr-grenoble.fr also contributed to this * * code. * * * * Lominé F., Scholtès L., Sibille L., Poullain P. (2013) * * Modelling of fluid-solid interaction in granular media with coupled * * LB/DE methods: application to piping erosion. International Journal * * for Numerical and Analytical Methods in Geomechanics, 37(6):577-596 * * doi: 10.1002/nag.1109 * * * * Sibille L., Lominé F., Marot D. (2012) Investigation In Modelling * * Piping Erosion With a Coupled «Lattice Boltzmann – Discrete Element» * * Numerical Method. in Proc. 6th Int. Conference on Scour and Erosion * * (ICSE-6), pp. 93-100. * * * *************************************************************************/ #ifdef LBM_ENGINE #pragma once #include #include #include #include class HydrodynamicsLawLBM : public GlobalEngine { private : std::ofstream ofile; public : bool firstRun, /*! = 1 if it is the first iteration during 1 YADE simulation*/ use_ConvergenceCriterion, /*! use stop condition based on the convergence criterion*/ SAVE_VELOCITY, /*! Switch to save node velocities*/ SAVE_VELOCITYCOMP, /*! Switch to save node velocities in each directions*/ SAVE_RHO, /*! Switch to save node densities*/ SAVE_FORCES, /*! Switch to save node force and momentum on grid*/ SAVE_BODIES, /*! Switch to save particle nodes*/ SAVE_NODEBD, /*! Switch to save fluid or solid boundary nodes*/ SAVE_NODEISNEW, /*! Switch to save new fluid/solid nodes*/ SAVE_DEBUGFILES, /*! Switch to save some debug data*/ SAVE_OBSERVEDPTC, /*! Switch to save properties of the observed particle*/ SAVE_OBSERVEDNODE, /*! Switch to save properties of the observed node*/ SAVE_CONTACTINFO, /*! Switch to save contact properties*/ SAVE_SPHERES, /*! Switch to save spheres properties*/ COMPRESS_DATA, /*! Switch to enable file compression*/ Xperiodicity, /*! Switch to activate lattice periodicity in x direction*/ Yperiodicity, /*! Switch to activate lattice periodicity in y direction*/ Zperiodicity; /*! Switch to activate lattice periodicity in z direction*/ int NB_BODIES, /*! Number of bodies*/ NB_GRAINS, /*! number of grains*/ NB_DYNGRAINS, /*! number of dynamic grains*/ NB_DYNBODIES, /*! number of dynamic bodies*/ NB_WALLS, /*! Number of walls*/ DEM_ITER, /*! Number of iteration of the DEM loop*/ LBM_ITER, /*! Number of iteration of the LBM loop*/ MODE, /*! 1->only a LBM loop, 2->lbm subcycling, 3->lbm subcycling after lbm loop*/ dim, /*! dimension*/ NbDir, /*! number of directions of the lattice model*/ NbNodes, /*! Total number of nodes*/ NbFluidNodes, /*! Number of fluid nodes*/ NbSolidNodes, /*! Number of solid nodes*/ NbParticleNodes, /*! Number of particle nodes*/ NbContacts, /*! Number of Contact*/ InitialNumberOfDynamicParticles,/*! Initial number of dynamic particles*/ NumberOfDynamicParticles, /*! Number of dynamic particles*/ Ny, /*! Number of grid divisions in y direction */ Nz, /*! Number of grid divisions in z direction */ NumberPtcEroded, /*! The bumber of eroded/removed particles*/ iter, /*! LBM Iteration number in current DEM loop (=1 in mode=2)*/ IdFirstSphere; /*! Id of the first sphere*/ Real height, /*! System height */ width, /*! System width */ depth, /*! System depth */ halfWallthickness, /*! Half Wall thickness */ Wallthickness, /*! Wall thickness */ cub, /*! A temporary variable to calculate equilibrium distribution function */ c, /*! Lattice speed */ c2, /*! The squared lattice speed*/ dx, /*! The lattice size*/ invdx, /*! 1 / lattice size*/ dx2, /*! The squared lattice size*/ uMax, /// TODO: PLEASE EXPLAIN uMax cs, /*! c/sqrt(3) */ dt, /*! LBM timestep */ invdt, /*! one over LBM timestep */ nu, /*! LBM kinematic viscosity */ feqb, /*! Equilibrium distribution function*/ omega, /*! 1/tau */ Lx0, /*! LBM grid size in x direction*/ Ly0, /*! LBM grid size in y direction*/ Lz0, /*! LBM grid size in z direction*/ outside_limit, /*! the x coordinate of a point outside the system*/ DEMdt, /*! timestep for the DEM iteration*/ DEMdt0, /*! original timestep for the DEM iteration*/ newDEMdt, /*! the new timestep for the DEM iteration*/ Vr, /*! Volume of the removed particles*/ Vo, /*! Initial volume of dynamic particles */ VmeanFluidC, /*! Current mean fluid velocity */ PrevVmeanFluidC, /*! Previous mean fluid velocity */ PrevPrevVmeanFluidC, /*! Previous previous mean fluid velocity */ VmaxC, /*! Maximum velocity during the current time step*/ VminC, /*! Minimum velocity during the current time step*/ RhomaxC, /*! Maximum density during the current time step*/ RhominC, /*! Minimum density during the current time step*/ LBM_TIME, /*! The time ellapsed in the LB method*/ DEM_TIME, /*! The time ellapsed in the DE method*/ RhoTot, /*! Cumulative density*/ FmoyCur, /*! Mean force at the current LB iteration*/ FmoyPrev, /*! Mean force at the previous LB iteration*/ FmoyPrevPrev, /*! Mean force at 2 previous LB iteration*/ UMaxtheo, /// TODO: PLEASE EXPLAIN UMaxtheo MaxBodyRadius, /*! Max radius of spheres*/ MinBodyRadius, /*! Min radius of spheres*/ MeanBodyRadius; /*! Mean radius of spheres*/ std::string LBMlogFile, /*! Name of the logfile */ LBMmachFile, /*! Name of the stat file */ LBMcontactsFile, /*! Name of the contact file */ RemovedPtcFile, /*! Name of the file to store removed particle informations*/ ObservedPtcFile, /*! Name of the file to store observed particle informations*/ ObservedNodeFile, /*! Name of the file to store observed particle informations*/ lbm_dir, /*! Directory name to save LBM files */ dem_dir, /*! Directory name to save DEM files */ cntct_dir; /*! Directory name to save contact properties */ std::stringstream spherefile_name; /*! Name of the file where sphere data are saved*/ vector IdOfNextErodedPtc, /*! List of particles which will be eroded*/ opp; /*! opposite nodes */ vector w; /*! Weighting factor */ vector nodes; /*! the LBM nodes*/ vector links; /*! the LBM links*/ vector LBbodies; /*! the LBM bodies*/ vector eib; /*! node velocity directions*/ Vector3r FhTotale; ///Total hydrodynamic force virtual ~HydrodynamicsLawLBM (); virtual bool isActivated(); virtual void action(); void save(int iter_number, Real timestep); void saveStats(int iter_number, Real timestep); void saveEroded(int iter_number, Real timestep); void saveContacts(int iter_number, Real timestep); void saveObservedNode(int iter_number, Real timestep); void saveObservedPtc(int iter_number, Real timestep); void createNewFiles(); //void createDirectories(); // ModLuc: to create directories only if necessary void createDirectories(bool dirLBM, bool dirDem, bool dirCntct); void writelogfile(); void modeTransition(); void LbmEnd(); void CalculateAndApplyForcesAndTorquesOnBodies(bool mean,bool apply); YADE_CLASS_BASE_DOC_ATTRS_CTOR(HydrodynamicsLawLBM,GlobalEngine,"Engine to simulate fluid flow (with the lattice Boltzmann method) with a coupling with the discrete element method.\n If you use this Engine, please cite and refer to F. Lominé et al. International Journal For Numerical and Analytical Method in Geomechanics, 2012, doi: 10.1002/nag.1109", ((int,WallYm_id,0,,"Identifier of the Y- wall")) ((bool,useWallYm,true,,"Set true if you want that the LBM see the wall in Ym")) ((int,YmBCType,2,,"Boundary condition for the wall in Ym (-1: unused, 1: pressure condition, 2: velocity condition).")) ((Vector3r,YmBcVel,Vector3r::Zero(),,"(!!! not fully implemented !!) The velocity imposed at the boundary")) ((Real,YmBcRho,-1,,"(!!! not fully implemented !!) The density imposed at the boundary")) ((int,WallYp_id,1,,"Identifier of the Y+ wall")) ((bool,useWallYp,true,,"Set true if you want that the LBM see the wall in Yp")) ((int,YpBCType,2,,"Boundary condition for the wall in Yp (-1: unused, 1: pressure condition, 2: velocity condition).")) ((Vector3r,YpBcVel,Vector3r::Zero(),,"(!!! not fully implemented !!) The velocity imposed at the boundary")) ((Real,YpBcRho,-1,,"(!!! not fully implemented !!) The density imposed at the boundary")) ((int,WallXm_id,2,,"Identifier of the X- wall")) ((bool,useWallXm,false,,"Set true if you want that the LBM see the wall in Xm")) ((int,XmBCType,1,,"Boundary condition for the wall in Xm (-1: unused, 1: pressure condition, 2: velocity condition).")) ((Vector3r,XmBcVel,Vector3r::Zero(),,"(!!! not fully implemented !!) The velocity imposed at the boundary")) ((Real,XmBcRho,-1,,"(!!! not fully implemented !!) The density imposed at the boundary")) ((int,WallXp_id,3,,"Identifier of the X+ wall")) ((bool,useWallXp,false,,"Set true if you want that the LBM see the wall in Xp")) ((int,XpBCType,1,,"Boundary condition for the wall in Xp (-1: unused, 1: pressure condition, 2: velocity condition).")) ((Vector3r,XpBcVel,Vector3r::Zero(),,"(!!! not fully implemented !!) The velocity imposed at the boundary")) ((Real,XpBcRho,-1,,"(!!! not fully implemented !!) The density imposed at the boundary")) ((int,WallZp_id,5,,"Identifier of the Z+ wall")) ((bool,useWallZp,false,,"Set true if you want that the LBM see the wall in Zp")) ((int,ZpBCType,-1,,"Boundary condition for the wall in Zp (-1: unused, 1: pressure condition, 2: velocity condition).")) ((Vector3r,ZpBcVel,Vector3r::Zero(),,"(!!! not fully implemented !!) The velocity imposed at the boundary")) ((Real,zpBcRho,-1,,"(!!! not fully implemented !!) The density imposed at the boundary")) ((int,WallZm_id,4,,"Identifier of the Z- wall")) ((bool,useWallZm,false,,"Set true if you want that the LBM see the wall in Zm")) ((int,ZmBCType,-1,,"Boundary condition for the wall in Zm (-1: unused, 1: pressure condition, 2: velocity condition).")) ((Vector3r,ZmBcVel,Vector3r::Zero(),,"(!!! not fully implemented !!) The velocity imposed at the boundary")) ((Real,ZmBcRho,-1,,"(!!! not fully implemented !!) The density imposed at the boundary")) ((int,XmYmZpBCType,2,,"Boundary condition for the corner node XmYmZp (-1: unused, 1: pressure condition, 2: velocity condition).")) ((int,XmYpZpBCType,2,,"Boundary condition for the corner node XmYpZp (-1: unused, 1: pressure condition, 2: velocity condition).")) ((int,XpYmZpBCType,2,,"Boundary condition for the corner node XpYmZp (-1: unused, 1: pressure condition, 2: velocity condition).")) ((int,XpYpZpBCType,2,,"Boundary condition for the corner node XpYpZp (-1: unused, 1: pressure condition, 2: velocity condition).")) ((int,XmYmZmBCType,-1,,"Boundary condition for the corner node XmYmZm (not used with d2q9, -1: unused, 1: pressure condition, 2: velocity condition).")) ((int,XmYpZmBCType,-1,,"Boundary condition for the corner node XmYpZm (not used with d2q9, -1: unused, 1: pressure condition, 2: velocity condition).")) ((int,XpYmZmBCType,-1,,"Boundary condition for the corner node XpYmZm (not used with d2q9, -1: unused, 1: pressure condition, 2: velocity condition).")) ((int,XpYpZmBCType,-1,,"Boundary condition for the corner node XpYpZm (not used with d2q9, -1: unused, 1: pressure condition, 2: velocity condition).")) ((int,defaultLbmInitMode,0,,"Switch between the two initialisation methods")) ((Vector3r,dP,Vector3r(0.,0.,0.),,"Pressure difference between input and output")) ((Real,Rho,1000.,,"Fluid density")) ((Real,Nu,0.000001,,"Fluid kinematic viscosity")) ((Real,tau,0.6,,"Relaxation time")) ((int,Nx,1000,,"The number of grid division in x direction")) ((int,IterMax,1,,"This variable can be used to do several LBM iterations during one DEM iteration. ")) ((int,IterPrint,1,,"Print info on screen every IterPrint iterations")) ((int,SaveMode,1,,"Save Mode (1-> default, 2-> in time (not yet implemented)")) ((int,IterSave,100,,"Data are saved every IterSave LBM iteration (or see TimeSave)")) ((Real,TimeSave,-1,,"Data are saved at constant time interval (or see IterSave)")) ((int,SaveGridRatio,1,,"Grid data are saved every SaveGridRatio * IterSave LBM iteration (with SaveMode=1)")) ((int,IterSubCyclingStart,-1,,"Iteration number when the subcycling process starts")) ((int,DemIterLbmIterRatio,-1,,"Ratio between DEM and LBM iterations for subcycling")) ((bool,EngineIsActivated,true,,"To activate (or not) the engine")) ((bool,applyForcesAndTorques,true,,"Switch to apply forces and torques")) ((int,ObservedNode,-1,,"The identifier of the node that will be observed (-1 means none)")) ((int,ObservedPtc,-1,,"The identifier of the particle that will be observed (-1 means the first one)")) ((Real,RadFactor,1.0,,"The radius of DEM particules seen by the LBM is the real radius of particules*RadFactor")) ((Real,ConvergenceThreshold,0.000001,,"")) ((std::string,LBMSavedData," ",,"a list of data that will be saved. Can use velocity,velXY,forces,rho,bodies,nodeBD,newNode,observedptc,observednode,contacts,spheres,bz2")) ((std::string,periodicity," ",,"periodicity")) ((std::string,bc," ",,"Boundary condition")) ((std::string,model,"d2q9",,"The LB model. Until now only d2q9 is implemented")) ((int,removingCriterion ,0,,"Criterion to remove a sphere (1->based on particle position, 2->based on particle velocity")) ((Real,VelocityThreshold,-1.,,"Velocity threshold when removingCriterion=2")) ((Real,EndTime,-1,,"the time to stop the simulation")) ((Vector3r,CstBodyForce,Vector3r::Zero(),,"A constant body force (=that does not vary in time or space, otherwise the implementation introduces errors)")) ((Real,VbCutOff,-1,,"the minimum boundary velocity that is taken into account")) , firstRun = true; omega = 1.0/tau; DEM_TIME = 0.; LBM_TIME = 0.; iter = 0; use_ConvergenceCriterion = true; MODE=0; dim=0; NbDir=0; NbNodes=0; NbFluidNodes=0; NbSolidNodes=0; NbParticleNodes=0; NbContacts=0; InitialNumberOfDynamicParticles=0; NumberOfDynamicParticles=0; NumberPtcEroded=0; Vr=0.; Vo=0.; MaxBodyRadius=-1000000.; MinBodyRadius=1000000.; MeanBodyRadius=0.; lbm_dir="lbm-nodes"; dem_dir="dem-bodies"; cntct_dir="contacts"; LBMlogFile ="LBM.log"; LBMmachFile ="LBM.mach"; LBMcontactsFile ="LBM.cntct"; RemovedPtcFile="eroded.dat"; ObservedPtcFile="observedPtc.dat"; ObservedNodeFile="observedNode.dat"; COMPRESS_DATA = false; SAVE_VELOCITY = false; SAVE_VELOCITYCOMP = false; SAVE_RHO = false; SAVE_FORCES = false; SAVE_BODIES = false; SAVE_NODEBD = false; SAVE_NODEISNEW = false; SAVE_DEBUGFILES = false; SAVE_OBSERVEDPTC= false; SAVE_OBSERVEDNODE=false; SAVE_CONTACTINFO =false; SAVE_SPHERES = false; //to save spheres_* files only if it is required by the operator Xperiodicity = false; Yperiodicity = false; Zperiodicity = false; Ny = 0;Nz = 0; FmoyCur=0.;FmoyPrev=0.; FmoyPrevPrev=0.;VmeanFluidC=0.;PrevVmeanFluidC=0.;PrevPrevVmeanFluidC=0.; LBM_ITER=0; DEM_ITER=0; IdFirstSphere=-1; timingDeltas=shared_ptr(new TimingDeltas); ); DECLARE_LOGGER; }; REGISTER_SERIALIZABLE(HydrodynamicsLawLBM); #endif //LBM_ENGINE trunk-2018.02b/pkg/lbm/LBMbody.hpp000066400000000000000000000044541324306050200165310ustar00rootroot00000000000000/************************************************************************* * Copyright (C) 2009-2012 by Franck Lominé * * franck.lomine@insa-rennes.fr * * * * This program is free software; it is licensed under the terms of the * * GNU General Public License v2 or later. See file LICENSE for details. * * * *************************************************************************/ #ifdef LBM_ENGINE #pragma once #include #include class LBMbody: public Serializable{ public: virtual ~LBMbody() {}; //Real radius(){return ext[0];} bool isBox(){if(type==1)return true; else return false;} bool isPtc(){if(type==2)return true; else return false;} void setAsPtc(){type=2;} void setAsBox(){type=1;} YADE_CLASS_BASE_DOC_ATTRS_CTOR(LBMbody,Serializable, "Body class for Lattice Boltzmann Method ", ((Vector3r,force,Vector3r::Zero(),,"Hydrodynamic force, need to be reinitialized (LB unit)")) ((Vector3r,fm,Vector3r::Zero(),,"Hydrodynamic force (LB unit) at t-0.5dt")) ((Vector3r,fp,Vector3r::Zero(),,"Hydrodynamic force (LB unit) at t+0.5dt")) ((Vector3r,momentum,Vector3r::Zero(),,"Hydrodynamic momentum,need to be reinitialized (LB unit)")) ((Vector3r,mm,Vector3r::Zero(),,"Hydrodynamic momentum (LB unit) at t-0.5dt")) ((Vector3r,mp,Vector3r::Zero(),,"Hydrodynamic momentum (LB unit) at t+0.5dt")) ((Vector3r,pos,Vector3r::Zero(),,"Position of body")) ((Vector3r,vel,Vector3r::Zero(),,"Velocity of body")) ((Vector3r,AVel,Vector3r::Zero(),,"Angular velocity of body")) ((Vector3r,Fh,Vector3r::Zero(),,"Hydrodynamical force on body")) ((Vector3r,Mh,Vector3r::Zero(),,"Hydrodynamical momentum on body")) ((Real,radius,-1000.,,"Radius of body (for sphere)")) ((bool,isEroded,false,,"Hydrodynamical force on body")) ((bool,saveProperties,false,,"To save properties of the body")) ((short int,type,-1,," ")) , ); }; REGISTER_SERIALIZABLE(LBMbody); #endif //LBM_ENGINE trunk-2018.02b/pkg/lbm/LBMlink.hpp000066400000000000000000000037761324306050200165370ustar00rootroot00000000000000/************************************************************************* * Copyright (C) 2009-2012 by Franck Lominé * * franck.lomine@insa-rennes.fr * * * * This program is free software; it is licensed under the terms of the * * GNU General Public License v2 or later. See file LICENSE for details. * * * *************************************************************************/ #ifdef LBM_ENGINE #pragma once #include #include class LBMlink: public Serializable{ public: void ReinitDynamicalProperties() { sid=-1; fid=-1; idx_sigma_i=-1; isBd=false; VbMid=Vector3r::Zero(); DistMid=Vector3r::Zero(); ct=0.; return; }; virtual ~LBMlink() {}; YADE_CLASS_BASE_DOC_ATTRS_CTOR(LBMlink,Serializable, "Link class for Lattice Boltzmann Method ", ((int,sid,-1,,"Solid node identifier ")) ((int,fid,-1,,"Fluid node identifier ")) ((short int,i,-1,,"direction index of the link")) ((int,nid1,-1,,"fixed node identifier")) ((int,nid2,-1,,"fixed node identifier or -1 if node points outside")) ((short int,idx_sigma_i,-1,,"sigma_i direction index (Fluid->Solid)")) ((bool,isBd,false,,"True if it is a boundary link")) ((bool,PointingOutside,false,,"True if it is a link pointing outside to the system (from a fluid or solid node)")) ((Vector3r,VbMid,Vector3r::Zero(),,"Velocity of boundary at midpoint")) ((Vector3r,DistMid,Vector3r::Zero(),,"Distance between middle of the link and mass center of body")) ((Real,ct,0.,,"Coupling term in modified bounce back rule")), ); }; REGISTER_SERIALIZABLE(LBMlink); #endif //LBM_ENGINE trunk-2018.02b/pkg/lbm/LBMnode.cpp000066400000000000000000000116351324306050200165130ustar00rootroot00000000000000/************************************************************************* * Copyright (C) 2009-2012 by Franck Lominé * * franck.lomine@insa-rennes.fr * * * * This program is free software; it is licensed under the terms of the * * GNU General Public License v2 or later. See file LICENSE for details. * * * *************************************************************************/ #ifdef LBM_ENGINE #include "LBMnode.hpp" #include "LBMlink.hpp" #include "LBMbody.hpp" YADE_PLUGIN((LBMnode)(LBMlink)(LBMbody)); LBMnode::~LBMnode(){}; void LBMnode::MixteBC(string lbmodel,Real density, Vector3r U, string where){ Real rhoVx=density*U.x(); Real rhoVy=density*U.y(); if(!strcmp(lbmodel.c_str(), "d2q9" )){ if(!strcmp(where.c_str(), "Xm" )){ f[1]=f[3]+(2./3.)*rhoVx; f[5]=f[7]-0.5*(f[2]-f[4])+(1./6.)*rhoVx+ 0.5*rhoVy; f[8]=f[6]+0.5*(f[2]-f[4])+(1./6.)*rhoVx- 0.5*rhoVy; } else if(!strcmp(where.c_str(), "Xp" )){ f[3]=f[1]-(2./3.)*rhoVx; f[7]=f[5]+0.5*(f[2]-f[4])-(1./6.)*rhoVx - 0.5*rhoVy; f[6]=f[8]-0.5*(f[2]-f[4])-(1./6.)*rhoVx + 0.5*rhoVy; } else if(!strcmp(where.c_str(), "Ym" )){ f[2]=f[4]+(2./3.)*rhoVy; f[5]=f[7]-0.5*(f[1]-f[3])+0.5*rhoVx + (1./6.)*rhoVy; f[6]=f[8]+0.5*(f[1]-f[3])-0.5*rhoVx + (1./6.)*rhoVy; } else if(!strcmp(where.c_str(), "Yp" )){ f[4]=f[2]-(2./3.)*rhoVy; f[7]=f[5]+0.5*(f[1]-f[3])-0.5*rhoVx- (1./6.)*rhoVy; f[8]=f[6]-0.5*(f[1]-f[3])+0.5*rhoVx -(1./6.)*rhoVy; } else if(!strcmp(where.c_str(), "XmYmZp" )){ f[1]=f[3]+(2./3.)*rhoVx; f[2]=f[4]+(2./3.)*rhoVy; f[5]=f[7] + (1./6.)*density*(U.x()+U.y()); f[6]=0.5*(density*(1.-U.x() -(2./3.)*U.y())-f[0]-2.*(f[3]+f[4]+f[7])); f[8]=0.5*(density*(1.-(2./3.)*U.x() -U.y())-f[0]-2.*(f[3]+f[4]+f[7])); } else if(!strcmp(where.c_str(), "XmYpZp" )){ f[1]=f[3]+(2./3.)*rhoVx; f[4]=f[2]-(2./3.)*rhoVy; f[5]=0.5*(density*(1.-(2./3.)*U.x()+U.y())-f[0]-2.*(f[2]+f[3]+f[6])); f[7]=0.5*(density*(1.-U.x()+(2./3.)*U.y())-f[0]-2.*(f[2]+f[3]+f[6])); f[8]=f[6]+(1./6.)*density*(U.x()-U.y()); } else if(!strcmp(where.c_str(), "XpYmZp" )){ f[2]=f[4]+(2./3.)*rhoVy; f[3]=f[1]-(2./3.)*rhoVx; f[5]=0.5*(density*(1.+U.x()-(2./3.)*U.y())-f[0]-2.*(f[1]+f[4]+f[8])); f[6]=f[8]-(1./6.)*density*(U.x()-U.y()); f[7]=0.5*(density*(1.+(2./3.)*U.x()-U.y())-f[0]-2.*(f[1]+f[4]+f[8])); } else if(!strcmp(where.c_str(), "XpYpZp" )){ f[3]=f[1]-(2./3.)*rhoVx; f[4]=f[2]-(2./3.)*rhoVy; f[6]=0.5*(density*(1.+(2./3.)*U.x()+U.y())-f[0]-2.*(f[1]+f[2]+f[5])); f[7]=f[5]-(1./6.)*density*(U.x()+U.y()); f[8]=0.5*(density*(1.+U.x()+(2./3.)*U.y())-f[0]-2.*(f[1]+f[2]+f[5])); } else {exit(-1);} }else {exit(-1);} return; } bool LBMnode::checkIsNewObstacle(){ if(isObstacle){ if(!wasObstacle) {isNewObstacle=true;wasObstacle=true;} else {isNewObstacle=false;wasObstacle=true;} return(isNewObstacle); } else return(false); } bool LBMnode::checkIsNewFluid(){ if(!isObstacle){ if(wasObstacle) {isNewFluid=true;wasObstacle=false;} else {isNewFluid=false;wasObstacle=false;} return(isNewFluid); } else return(false); } void LBMnode::DispatchBoundaryConditions(int SizeNx,int SizeNy,int SizeNz){ applyBC =false; applyXmBC =false; applyYmXmBC =false; applyYpXmBC =false; applyXpBC =false; applyYmXpBC =false; applyYpXpBC =false; applyYpBC =false; applyYmBC =false; if((i==0)&&(j>0)&&(j0)&&(j0)&&(i0)&&(i #include class LBMnode: public Serializable{ public: int i, /*! node index in x direction */ j, /*! node index in y direction */ k, /*! node index in z direction */ body_id; /*! the node belongs to the body indexed with body_id*/ short int IsolNb; /*! number of boundary links of a fluid boundary nodes*/ bool isObstacle, /*! the node belongs to an obstacle */ isObstacleBoundary, /*! the node is an obstacle boundary */ isFluidBoundary, /*! the node is a fluid boundary */ wasObstacle, /*! the node was an obstacle */ isNewObstacle, /*! the node is an new obstacle node */ isNewFluid, /*! the node is an new fluid node */ applyBC, /*! the node is subject to the one boundary condition */ applyXmBC, /*! the node is subject to the left boundary condition */ applyXpBC, /*! the node is subject to the right boundary condition */ applyYpBC, /*! the node is subject to the top boundary condition */ applyYmBC, /*! the node is subject to the bottom boundary condition */ applyZpBC, /*! the node is subject to the front boundary condition NOT USED NOW*/ applyZmBC, /*! the node is subject to the back boundary condition NOT USED NOW*/ applyYmXmBC, /*! the node is subject to the bottom-left boundary condition */ applyYpXmBC, /*! the node is subject to the top-left boundary condition */ applyYmXpBC, /*! the node is subject to the bottom-right boundary condition */ applyYpXpBC; /*! the node is subject to the top-right boundary condition */ Vector3r posb, /*! the node position */ velb; /*! the node velocity */ Real rhob; /*! the node density */ /*! the node density */ vector neighbour_id; /*! list of adjacent nodes */ vector links_id; /*! list of links */ vector f; vector fprecol; vector fpostcol; void DispatchBoundaryConditions(int SizeNx,int SizeNy,int SizeNz); bool checkIsNewFluid(); bool checkIsNewObstacle(); void MixteBC(string lbmodel,Real density, Vector3r U, string where); void SetCellIndexesAndPosition(int indI, int indJ, int indK); void setAsObstacle(){isObstacle=true;} void setAsFluid(){isObstacle=false;} virtual ~LBMnode(); YADE_CLASS_BASE_DOC(LBMnode,Serializable,"Node class for Lattice Boltzmann Method "); }; REGISTER_SERIALIZABLE(LBMnode); #endif //LBM_ENGINE trunk-2018.02b/pkg/pfv/000077500000000000000000000000001324306050200145425ustar00rootroot00000000000000trunk-2018.02b/pkg/pfv/DFNFlow.cpp000066400000000000000000000435241324306050200165150ustar00rootroot00000000000000 /************************************************************************* * Copyright (C) 2014 by Bruno Chareyre * * * * This program is free software; it is licensed under the terms of the * * GNU General Public License v2 or later. See file LICENSE for details. * *************************************************************************/ #ifdef FLOW_ENGINE #include // #include //keep this #ifdef for commited versions unless you really have stable version that should be compiled by default //it will save compilation time for everyone else //when you want it compiled, you can pass -DDFNFLOW to cmake, or just uncomment the following line //#define DFNFLOW #ifdef DFNFLOW #include "FlowEngine_DFNFlowEngineT.hpp" class DFNCellInfo : public FlowCellInfo_DFNFlowEngineT { public: Real anotherVariable; bool crack; Real crackArea; // DFNCellInfo() : crack(false) {} /// enable to visualize cracked cells in Paraview // DFNCellInfo() : crack(false), crackArea(0) {} // void anotherFunction() {}; std::vector faceBreakCount; std::vector facetAperture; DFNCellInfo (void) { faceBreakCount.resize(4,0); facetAperture.resize(4,0); } inline std::vector& count(void) {return faceBreakCount;} inline std::vector& aperture(void) {return facetAperture;} }; class DFNVertexInfo : public FlowVertexInfo_DFNFlowEngineT { public: //same here if needed }; typedef CGT::_Tesselation > DFNTesselation; #ifdef LINSOLV class DFNBoundingSphere : public CGT::FlowBoundingSphereLinSolv #else class DFNBoundingSphere : public CGT::FlowBoundingSphere #endif { public: void saveVtk(const char* folder) { RTriangulation& Tri = T[noCache?(!currentTes):currentTes].Triangulation(); static unsigned int number = 0; char filename[250]; mkdir(folder, S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH); sprintf(filename,"%s/out_%d.vtk",folder,number++); int firstReal=-1; /// count fictious vertices and cells vtkInfiniteVertices=vtkInfiniteCells=0; FiniteCellsIterator cellEnd = Tri.finite_cells_end(); for (FiniteCellsIterator cell = Tri.finite_cells_begin(); cell != cellEnd; cell++) { bool isDrawable = cell->info().isReal() && cell->vertex(0)->info().isReal() && cell->vertex(1)->info().isReal() && cell->vertex(2)->info().isReal() && cell->vertex(3)->info().isReal(); if (!isDrawable) vtkInfiniteCells+=1; } for (FiniteVerticesIterator v = Tri.finite_vertices_begin(); v != Tri.finite_vertices_end(); ++v) { if (!v->info().isReal()) vtkInfiniteVertices+=1; else if (firstReal==-1) firstReal=vtkInfiniteVertices; } basicVTKwritter vtkfile((unsigned int) Tri.number_of_vertices()-vtkInfiniteVertices, (unsigned int) Tri.number_of_finite_cells()-vtkInfiniteCells); vtkfile.open(filename,"test"); vtkfile.begin_vertices(); double x,y,z; for (FiniteVerticesIterator v = Tri.finite_vertices_begin(); v != Tri.finite_vertices_end(); ++v) { if (v->info().isReal()){ x = (double)(v->point().point()[0]); y = (double)(v->point().point()[1]); z = (double)(v->point().point()[2]); vtkfile.write_point(x,y,z);} } vtkfile.end_vertices(); vtkfile.begin_cells(); for (FiniteCellsIterator cell = Tri.finite_cells_begin(); cell != Tri.finite_cells_end(); ++cell) { bool isDrawable = cell->info().isReal() && cell->vertex(0)->info().isReal() && cell->vertex(1)->info().isReal() && cell->vertex(2)->info().isReal() && cell->vertex(3)->info().isReal(); if (isDrawable){vtkfile.write_cell(cell->vertex(0)->info().id()-firstReal, cell->vertex(1)->info().id()-firstReal, cell->vertex(2)->info().id()-firstReal, cell->vertex(3)->info().id()-firstReal);} } vtkfile.end_cells(); if (permeabilityMap){ vtkfile.begin_data("Permeability",CELL_DATA,SCALARS,FLOAT); for (FiniteCellsIterator cell = Tri.finite_cells_begin(); cell != Tri.finite_cells_end(); ++cell) { bool isDrawable = cell->info().isReal() && cell->vertex(0)->info().isReal() && cell->vertex(1)->info().isReal() && cell->vertex(2)->info().isReal() && cell->vertex(3)->info().isReal(); if (isDrawable){vtkfile.write_data(cell->info().s);} } vtkfile.end_data();} else{ vtkfile.begin_data("Pressure",CELL_DATA,SCALARS,FLOAT); for (FiniteCellsIterator cell = Tri.finite_cells_begin(); cell != Tri.finite_cells_end(); ++cell) { bool isDrawable = cell->info().isReal() && cell->vertex(0)->info().isReal() && cell->vertex(1)->info().isReal() && cell->vertex(2)->info().isReal() && cell->vertex(3)->info().isReal(); if (isDrawable){vtkfile.write_data(cell->info().p());} } vtkfile.end_data();} if (1){ averageRelativeCellVelocity(); vtkfile.begin_data("Velocity",CELL_DATA,VECTORS,FLOAT); for (FiniteCellsIterator cell = Tri.finite_cells_begin(); cell != Tri.finite_cells_end(); ++cell) { bool isDrawable = cell->info().isReal() && cell->vertex(0)->info().isReal() && cell->vertex(1)->info().isReal() && cell->vertex(2)->info().isReal() && cell->vertex(3)->info().isReal(); if (isDrawable){vtkfile.write_data(cell->info().averageVelocity()[0],cell->info().averageVelocity()[1],cell->info().averageVelocity()[2]);} } vtkfile.end_data();} if(1){ vtkfile.begin_data("fracturedCells",CELL_DATA,SCALARS,FLOAT); for (FiniteCellsIterator cell = Tri.finite_cells_begin(); cell != Tri.finite_cells_end(); ++cell) { bool isDrawable = cell->info().isReal() && cell->vertex(0)->info().isReal() && cell->vertex(1)->info().isReal() && cell->vertex(2)->info().isReal() && cell->vertex(3)->info().isReal(); if (isDrawable){vtkfile.write_data(cell->info().crack);} } vtkfile.end_data();} } }; typedef TemplateFlowEngine_DFNFlowEngineT DFNFlowEngineT; REGISTER_SERIALIZABLE(DFNFlowEngineT); YADE_PLUGIN((DFNFlowEngineT)); class DFNFlowEngine : public DFNFlowEngineT { public : void trickPermeability(Solver* flow); void interpolateCrack(Tesselation& Tes, Tesselation& NewTes); void trickPermeability (RTriangulation::Facet_circulator& facet,Real aperture, RTriangulation::Finite_edges_iterator& edge); void trickPermeability (RTriangulation::Finite_edges_iterator& edge,Real aperture); void setPositionsBuffer(bool current); Real leakOffRate; Real averageAperture; Real averageFracturePermeability; Real maxAperture; Real crackArea; bool edgeOnJoint; Real getCrackArea() {return crackArea;} Real getLeakOffRate() {return leakOffRate;} Real getAverageAperture() {return averageAperture;} Real getMaxAperture() {return maxAperture;} // void computeTotalFractureArea(Real totalFracureArea,bool printFractureTotalArea); /// Trying to get fracture's surface Real totalFracureArea; /// Trying to get fracture's surface // CELL_SCALAR_GETTER(double,.crackArea,crackArea) YADE_CLASS_BASE_DOC_ATTRS_INIT_CTOR_PY(DFNFlowEngine,DFNFlowEngineT,"This is an enhancement of the FlowEngine for intact and fractured rocks that takes into acount pre-existing discontinuities and bond breakage between particles. The local conductivity around the broken link is calculated according to parallel plates model", ((Real,jointResidualAperture,1.e-6,,"residual aperture of joints")) ((Real, facetEdgeBreakThreshold, 1,,"minimum number of edges that need to be broken for a facet to be tricked")) ((Real,slotInitialAperture,1.e-6,,"initial aperture of injection slots")) ((Real,inducedCracksResidualAperture,0.,,"residual aperture of induced cracks")) ((Real,apertureFactor,1.,,"calibration parameter for fracture conductance to deal with tortuosity")) ((bool,updatePositions,false,,"update particles positions when rebuilding the mesh (experimental)")) ((bool,printFractureTotalArea,0.,,"The final fracture area computed through the network")) /// Trying to get fracture's surface ((bool,calcCrackArea,true,,"The amount of crack per pore () is updated if calcCrackArea=True")) /// Trying to get fracture's surface ,,, .def("getCrackArea",&DFNFlowEngine::getCrackArea,(boost::python::arg("id")),"get the cracked area within cell 'id'.") .def("getLeakOffRate",&DFNFlowEngine::getLeakOffRate, "report leak-off rate") .def("getAverageAperture",&DFNFlowEngine::getAverageAperture, "report the current average aperture") .def("getMaxAperture",&DFNFlowEngine::getMaxAperture, "report the max aperture") // .def("computeTotalFractureArea",&DFNFlowEngineT::computeTotalFractureArea," Compute and print the total fracture area of the network") /// Trying to get fracture's surface // .def("trickPermeability",&DFNFlowEngineT::trickPermeability,"measure the mean trickPermeability in the period") ) DECLARE_LOGGER; }; REGISTER_SERIALIZABLE(DFNFlowEngine); YADE_PLUGIN((DFNFlowEngine)); /// In this version, we never update positions when !updatePositions, i.e. keep triangulating the same positions void DFNFlowEngine::setPositionsBuffer(bool current) { vector& buffer = current? positionBufferCurrent : positionBufferParallel; if (!updatePositions && buffer.size()>0) return; buffer.clear(); buffer.resize(scene->bodies->size()); shared_ptr sph ( new Sphere ); const int Sph_Index = sph->getClassIndexStatic(); FOREACH ( const shared_ptr& b, *scene->bodies ) { if (!b || ignoredBody==b->getId()) continue; posData& dat = buffer[b->getId()]; dat.id=b->getId(); dat.pos=b->state->pos; dat.isSphere= (b->shape->getClassIndex() == Sph_Index); if (dat.isSphere) dat.radius = YADE_CAST(b->shape.get())->radius; dat.exists=true; } } /// function allows us to interpolate information about fractured/non fractured cells so we can identify newly fractured cells, monitor half width, and identify fracture tip. We also use the loop to compute leakoff rate void DFNFlowEngine::interpolateCrack(Tesselation& Tes, Tesselation& NewTes){ RTriangulation& Tri = Tes.Triangulation(); RTriangulation& newTri = NewTes.Triangulation(); FiniteCellsIterator cellEnd = newTri.finite_cells_end(); #ifdef YADE_OPENMP const long size = NewTes.cellHandles.size(); #pragma omp parallel for num_threads(ompThreads>0 ? ompThreads : 1) for (long i=0; iinfo().fictious()==0) for ( int k=0;k<4;k++ ) center= center + 0.25* (Tes.vertex(newCell->vertex(k)->info().id())->point()-CGAL::ORIGIN); CellHandle oldCell = Tri.locate(Point(center[0],center[1],center[2])); newCell->info().crack = oldCell->info().crack; // For later commit newCell->info().fractureTip = oldCell->info().fractureTip; // For later commit newCell->info().cellHalfWidth = oldCell->info().cellHalfWidth; /// compute leakoff rate by summing the flow through facets abutting non-cracked neighbors if (oldCell->info().crack && !oldCell->info().fictious()){ Real facetFlowRate = 0; facetFlowRate -= oldCell->info().dv(); for (int k=0; k<4;k++) { if (!oldCell->neighbor(k)->info().crack){ facetFlowRate= oldCell->info().kNorm()[k]*(oldCell->info().shiftedP()-oldCell->neighbor(k)->info().shiftedP()); leakOffRate += facetFlowRate; } } } } } void DFNFlowEngine::trickPermeability(RTriangulation::Facet_circulator& facet, Real aperture, RTriangulation::Finite_edges_iterator& ed_it) { const RTriangulation::Facet& currentFacet = *facet; /// seems verbose but facet->first was declaring a junk cell and crashing program (https://bugs.launchpad.net/yade/+bug/1666339) const RTriangulation& Tri = solver->T[solver->currentTes].Triangulation(); const CellHandle& cell1 = currentFacet.first; const CellHandle& cell2 = currentFacet.first->neighbor(facet->second); if ( Tri.is_infinite(cell1) || Tri.is_infinite(cell2)) cerr<<"Infinite cell found in trickPermeability, should be handled somehow, maybe"<info().count()[currentFacet.second] < 3){ cell1->info().count()[currentFacet.second] += 1; //cell1->info().aperture()[currentFacet.second] += aperture // used with avgAperture below if desired } if (!edgeOnJoint && cell1->info().count()[currentFacet.second] < facetEdgeBreakThreshold) return; // only allow facets with 2 or 3 broken edges to be tricked // Need to decide aperture criteria! Otherwise it selects the most recent broken edge aperture for facet's perm calc. Here is one possibility: //Real avgAperture = cell1->info().aperture()[currentFacet.second]/Real(cell1->info().count()[currentFacet.second]); cell1->info().kNorm()[currentFacet.second]=cell2->info().kNorm()[Tri.mirror_index(cell1,currentFacet.second)] = apertureFactor*pow((aperture),3)/(12*viscosity); /// For vtk recorder: cell1->info().crack= 1; cell2->info().crack= 1; cell2->info().blocked = cell1->info().blocked = cell2->info().Pcondition = cell1->info().Pcondition = false; /// those ones will be included in the flow problem Point& CellCentre1 = cell1->info(); /// Trying to get fracture's surface Point& CellCentre2 = cell2->info(); /// Trying to get fracture's surface CVector networkFractureLength = CellCentre1 - CellCentre2; /// Trying to get fracture's surface double networkFractureDistance = sqrt(networkFractureLength.squared_length()); /// Trying to get fracture's surface Real networkFractureArea = pow(networkFractureDistance,2); /// Trying to get fracture's surface totalFracureArea += networkFractureArea; /// Trying to get fracture's surface // cout <<" ------------------ The total surface area up to here is --------------------" << totalFracureArea << endl; // printFractureTotalArea = totalFracureArea; /// Trying to get fracture's surface if (calcCrackArea) { CVector edge = ed_it->first->vertex(ed_it->second)->point().point() - ed_it->first->vertex(ed_it->third)->point().point(); CVector unitV = edge*(1./sqrt(edge.squared_length())); Point p3 = ed_it->first->vertex(ed_it->third)->point().point() + unitV*(cell1->info() - ed_it->first->vertex(ed_it->third)->point().point())*unitV; Real halfCrackArea = 0.25*sqrt(std::abs(cross_product(CellCentre1-p3,CellCentre2-p3).squared_length()));// cell1->info().crackArea += halfCrackArea; cell2->info().crackArea += halfCrackArea; crackArea += 2*halfCrackArea; } } void DFNFlowEngine::trickPermeability(RTriangulation::Finite_edges_iterator& edge, Real aperture) { const RTriangulation& Tri = solver->T[solver->currentTes].Triangulation(); RTriangulation::Facet_circulator facet1 = Tri.incident_facets(*edge); RTriangulation::Facet_circulator facet0=facet1++; trickPermeability(facet0, aperture, edge); while ( facet1!=facet0 ) {trickPermeability(facet1, aperture, edge); facet1++;} /// Needs the fracture surface for this edge? // double edgeArea = solver->T[solver->currentTes].computeVFacetArea(edge); cout<<"edge area="<T[solver->currentTes].Triangulation(); if (!first) interpolateCrack(solver->T[solver->currentTes], flow->T[flow->currentTes]); const JCFpmPhys* jcfpmphys; const shared_ptr interactions = scene->interactions; int numberOfCrackedOrJoinedInteractions = 0; Real SumOfApertures = 0.; averageAperture =0; maxAperture= 0; crackArea = 0; //Real totalFracureArea=0; /// Trying to get fracture's surface // const shared_ptr& ig; // const ScGeom* geom; // = static_cast(ig.get()); FiniteEdgesIterator edge = Tri.finite_edges_begin(); for( ; edge!= Tri.finite_edges_end(); ++edge) { const VertexInfo& vi1=(edge->first)->vertex(edge->second)->info(); const VertexInfo& vi2=(edge->first)->vertex(edge->third)->info(); const shared_ptr& interaction=interactions->find( vi1.id(),vi2.id() ); if (interaction && interaction->isReal()) { if (edge->first->info().isFictious) continue; /// avoid trick permeability for fictitious jcfpmphys = YADE_CAST(interaction->phys.get()); if ( jcfpmphys->isOnJoint || jcfpmphys->isBroken ) { numberOfCrackedOrJoinedInteractions +=1; /// here are some workarounds //Real residualAperture = jcfpmphys->isOnJoint? jointResidualAperture : inducedCracksResidualAperture; /// inducedCracksResidualAperture=0. by default Real residualAperture; if (jcfpmphys->isOnJoint){ residualAperture = jointResidualAperture; edgeOnJoint = true; }else{ residualAperture = inducedCracksResidualAperture; edgeOnJoint = false; } /// for injection slot (different from pre-existing fractures if needed) if (jcfpmphys->isOnSlot) {residualAperture = slotInitialAperture;} if (!jcfpmphys->isOnJoint && residualAperture<=0) continue; /// avoid trick permeability for closed induced crack (permeability=matrix permeability) /// which one of these two lines is the more accurate? Real aperture = jcfpmphys->crackJointAperture; // Real aperture = jcfpmphys->crackJointAperture + residualAperture; // cout<<"aperture = " << aperture < valueToDefine) aperture = valueToDefine; /// to avoid problem for large deformations? if (aperture <= residualAperture) aperture = residualAperture; if (aperture > maxAperture) maxAperture = aperture; SumOfApertures += aperture; trickPermeability(edge, aperture); }; } } averageAperture = SumOfApertures/numberOfCrackedOrJoinedInteractions; /// DEBUG // cout << " Average aperture in joint ( -D ) = " << AverageAperture << endl; /// DEBUG } // Real DFNFlowEngine::computeTotalFractureArea(totalFracureArea,printFractureTotalArea) /// Trying to get fracture's surface // { // if (printFractureTotalArea >0) { // cout<< " The total fracture area computed from the Network is: " << totalFracureArea < * * * * This program is free software; it is licensed under the terms of the * * GNU General Public License v2 or later. See file LICENSE for details. * *************************************************************************/ // This is an example of how to derive a new FlowEngine with additional data and possibly completely new behaviour. // Every functions of the base engine can be overloaded, and new functions can be added //keep this #ifdef as long as you don't really want to release a final version publicly, it will save compilation time for everyone else //when you want it compiled, you can pass -DDUMMYFLOW to cmake, or just uncomment the following line // #define DUMMYFLOW #ifdef DUMMYFLOW #include "FlowEngine_DummyFlowEngineT.hpp" /// We can add data to the Info types by inheritance class DummyCellInfo : public FlowCellInfo_DummyFlowEngineT { public: Real anotherVariable; void anotherFunction() {}; }; class DummyVertexInfo : public FlowVertexInfo_DummyFlowEngineT { public: //same here if needed }; typedef TemplateFlowEngine_DummyFlowEngineT DummyFlowEngineT; REGISTER_SERIALIZABLE(DummyFlowEngineT); YADE_PLUGIN((DummyFlowEngineT)); class DummyFlowEngine : public DummyFlowEngineT { public : //We can overload every functions of the base engine to make it behave differently //if we overload action() like this, this engine is doing nothing in a standard timestep, it can still have useful functions virtual void action() {}; //If a new function is specific to the derived engine, put it here, else go to the base TemplateFlowEngine //if it is useful for everyone void fancyFunction(Real what); YADE_CLASS_BASE_DOC_ATTRS_INIT_CTOR_PY(DummyFlowEngine,DummyFlowEngineT,"documentation here", ((Real, myNewAttribute, 0,,"useless example")) ,/*DummyFlowEngineT()*/, , .def("fancyFunction",&DummyFlowEngine::fancyFunction,(boost::python::arg("what")=0),"test function") ) DECLARE_LOGGER; }; REGISTER_SERIALIZABLE(DummyFlowEngine); YADE_PLUGIN((DummyFlowEngine)); void DummyFlowEngine::fancyFunction(Real what) {std::cerr<<"yes, I'm a new function"< * * Copyright (C) 2009 by Bruno Chareyre * * * * This program is free software; it is licensed under the terms of the * * GNU General Public License v2 or later. See file LICENSE for details. * *************************************************************************/ #ifdef YADE_CGAL #ifdef FLOW_ENGINE #include "FlowEngine_FlowEngineT.hpp" // To register properly, we need to first instantiate an intermediate class, then inherit from it with correct class names in YADE_CLASS macro // The intermediate one would be seen with the name "TemplateFlowEngine" by python, thus it would not work when more than one class are derived, they would all // be named "TemplateFlowEngine" ... typedef TemplateFlowEngine_FlowEngineT FlowEngineT; REGISTER_SERIALIZABLE(FlowEngineT); class FlowEngine : public FlowEngineT { public : YADE_CLASS_BASE_DOC_ATTRS_INIT_CTOR_PY(FlowEngine,FlowEngineT,"An engine to solve flow problem in saturated granular media. Model description can be found in [Chareyre2012a]_ and [Catalano2014a]_. See the example script FluidCouplingPFV/oedometer.py. More documentation to come.\n\n.. note::Multi-threading seems to work fine for Cholesky decomposition, but it fails for the solve phase in which -j1 is the fastest, here we specify thread numbers independently using :yref:`FlowEngine::numFactorizeThreads` and :yref:`FlowEngine::numSolveThreads`. These multhreading settings are only impacting the behaviour of openblas library and are relatively independant of :yref:`FlowEngine::multithread`. However, the settings have to be globally consistent. For instance, :yref:`multithread` =True with yref:`numFactorizeThreads` = yref:`numSolveThreads` = 4 implies that openblas will mobilize 8 processors at some point. If the system does not have so many procs. it will hurt performance.", ,, , //nothing special to define here, we simply re-use FlowEngine methods //.def("meanVelocity",&PeriodicFlowEngine::meanVelocity,"measure the mean velocity in the period") ) DECLARE_LOGGER; }; REGISTER_SERIALIZABLE(FlowEngine); YADE_PLUGIN((FlowEngineT)); CREATE_LOGGER(FlowEngine ); YADE_PLUGIN((FlowEngine)); #endif //FLOW_ENGINE #endif /* YADE_CGAL */ trunk-2018.02b/pkg/pfv/FlowEngine.hpp000066400000000000000000000037471324306050200173230ustar00rootroot00000000000000#pragma once /// Frequently used: // typedef CGT::CVector CVector; // typedef CGT::Point Point; /* /// Converters for Eigen and CGAL vectors inline CVector makeCgVect ( const Vector3r& yv ) {return CVector ( yv[0],yv[1],yv[2] );} inline Point makeCgPoint ( const Vector3r& yv ) {return Point ( yv[0],yv[1],yv[2] );} inline Vector3r makeVector3r ( const Point& yv ) {return Vector3r ( yv[0],yv[1],yv[2] );} inline Vector3r makeVector3r ( const CVector& yv ) {return Vector3r ( yv[0],yv[1],yv[2] );}*/ /// The following macros can be used to expose CellInfo members. /// The syntax is CELL_SCALAR_GETTER(double,.p(),pressure), note the "." before member name, data members would be without the "()" #define CELL_SCALAR_GETTER(type, param, getterName) \ type getterName(unsigned int id){\ if (id>=solver->T[solver->currentTes].cellHandles.size()) {LOG_ERROR("id out of range, max value is "<T[solver->currentTes].cellHandles.size()); return 0;}\ return solver->T[solver->currentTes].cellHandles[id]->info()param;} #define CELL_SCALAR_SETTER_EXTRA(type, param, setterName, extra) \ void setterName(unsigned int id, type value){\ if (id>=solver->T[solver->currentTes].cellHandles.size()) {LOG_ERROR("id out of range, max value is "<T[solver->currentTes].cellHandles.size()); return;}\ solver->T[solver->currentTes].cellHandles[id]->info()param=value;\ extra;} #define CELL_SCALAR_SETTER(type, param, setterName) CELL_SCALAR_SETTER_EXTRA(type, param, setterName,return) #define CELL_VECTOR_GETTER(param, getterName) \ Vector3r getterName(unsigned int id){\ if (id>=solver->T[solver->currentTes].cellHandles.size()) {LOG_ERROR("id out of range, max value is "<T[solver->currentTes].cellHandles.size()); return Vector3r(0,0,0);}\ return makeVector3r(solver->T[solver->currentTes].cellHandles[id]->info()param);} #ifdef LINSOLV #define DEFAULTSOLVER CGT::FlowBoundingSphereLinSolv<_Tesselation> #else #define DEFAULTSOLVER CGT::FlowBoundingSphere<_Tesselation> #endif trunk-2018.02b/pkg/pfv/FlowEngine.hpp.in000066400000000000000000001114531324306050200177220ustar00rootroot00000000000000/************************************************************************* * Copyright (C) 2009 by Emanuele Catalano * * Copyright (C) 2009 by Bruno Chareyre * * * * This program is free software; it is licensed under the terms of the * * GNU General Public License v2 or later. See file LICENSE for details. * *************************************************************************/ /*! FlowEngine is an interface between Yade and the fluid solvers defined in lib/triangulation and using the PFV scheme. There are also a few non-trivial functions defined here, such has building triangulation and computating elements volumes. The code strongly relies on CGAL library for building triangulations on the top of sphere packings. CGAL's trangulations introduce the data associated to each element of the triangulation through template parameters (Cell_info and Vertex_info). FlowEngine is thus defined via the template class TemplateFlowEngine(Cellinfo,VertexInfo), for easier addition of variables in various couplings. PeriodicFlowEngine is a variant for periodic boundary conditions. Which solver will be actually used internally to obtain pore pressure will depend partly on compile time flags (libcholmod available and #define LINSOLV), and on runtime settings (useSolver=0: Gauss-Seidel (iterative), useSolver=1: CHOLMOD if available (direct sparse cholesky)). The files defining lower level classes are in yade/lib. The code uses CGAL::Triangulation3 for managing the mesh and storing data. Eigen3::Sparse, suitesparse::cholmod, and metis are used for solving the linear systems with a direct method (Cholesky). An iterative method (Gauss-Seidel) is implemented directly in Yade and can be used as a fallback (see FlowEngine::useSolver). Most classes in lib/triangulation are templates, and are therefore completely defined in header files. A pseudo hpp/cpp split is reproduced for clarity, with hpp/ipp extensions (but again, in the end they are all include files). The files are - RegularTriangulation.h : declaration of info types embeded in the mesh (template parameters of CGAL::Triangulation3), and instanciation of RTriangulation classes - Tesselation.h/ipp : encapsulate RegularTriangulation and adds functions to manipulate the dual (Voronoi) graph of the triangulation - Network.hpp/ipp : specialized for PFV model (the two former are used independently by TesselationWrapper), a set of functions to determine volumes and surfaces of intersections between spheres and various subdomains. Contains two triangulations for smooth transitions while remeshing - e.g. interpolating values in the new mesh using the previous one. - FlowBoundingSphere.hpp/ipp and PeriodicFlow.hpp/ipp + LinSolv variants: implement the solver in itself (mesh, boundary conditions, solving, defining fluid-particles interactions) - FlowEngine.hpp/hpp.in/ipp.in (this file) define a generic flow engine, instantiations of which are FlowEngine and variants (FlowEngine.cpp, PeriodicFlowEngine.cpp,...) */ #pragma once #include #include #include #include #include #include #include template >, class solverT=DEFAULTSOLVER > class TemplateFlowEngine_@TEMPLATE_FLOW_NAME@ : public PartialEngine { public : typedef solverT FlowSolver; typedef FlowSolver Solver;//FIXME: useless alias, search/replace then remove typedef typename FlowSolver::Tesselation Tesselation; typedef typename FlowSolver::RTriangulation RTriangulation; typedef typename FlowSolver::FiniteVerticesIterator FiniteVerticesIterator; typedef typename FlowSolver::FiniteCellsIterator FiniteCellsIterator; typedef typename FlowSolver::CellHandle CellHandle; typedef typename FlowSolver::FiniteEdgesIterator FiniteEdgesIterator; typedef typename FlowSolver::VertexHandle VertexHandle; typedef typename RTriangulation::Triangulation_data_structure::Cell::Info CellInfo; typedef typename RTriangulation::Triangulation_data_structure::Vertex::Info VertexInfo; // protected: shared_ptr solver; shared_ptr backgroundSolver; volatile bool backgroundCompleted; Cell cachedCell; struct posData {Body::id_t id; Vector3r pos; Real radius; bool isSphere; bool isClump; bool exists; posData(){exists=0; isClump=0;}}; vector positionBufferCurrent;//reflect last known positions before we start computations vector positionBufferParallel;//keep the positions from a given step for multithread factorization //copy positions in a buffer for faster and/or parallel access virtual void setPositionsBuffer(bool current); virtual void trickPermeability(Solver* flow) {}; public : int retriangulationLastIter; enum {wall_xmin, wall_xmax, wall_ymin, wall_ymax, wall_zmin, wall_zmax}; Vector3r normal [6]; bool fluxChanged; // flag that keeps multithreading working when a flux is imposed bool metisForced; double epsVolCumulative; int ReTrg; int ellapsedIter; void initSolver (FlowSolver& flow); #ifdef LINSOLV void setForceMetis (bool force); bool getForceMetis (); #endif void triangulate (Solver& flow); void addBoundary (Solver& flow); void buildTriangulation (double pZero, Solver& flow); void buildTriangulation (Solver& flow); void updateVolumes (Solver& flow); void initializeVolumes (Solver& flow); void boundaryConditions(Solver& flow); void updateBCs () { if (solver->tesselation().maxId>0) boundaryConditions(*solver);//avoids crash at iteration 0, when the packing is not bounded yet else LOG_ERROR("updateBCs not applied"); solver->pressureChanged=true;} void imposeFlux(Vector3r pos, Real flux); unsigned int imposePressure(Vector3r pos, Real p); unsigned int imposePressureFromId(unsigned long id, Real p); void setImposedPressure(unsigned int cond, Real p); void clearImposedPressure(); void clearImposedFlux(); void computeViscousForces(Solver& flow); Real getCellFlux(unsigned int cond); Real getBoundaryFlux(unsigned int boundary) {return solver->boundaryFlux(boundary);} Vector3r fluidForce(unsigned int idSph) { const CGT::CVector& f=solver->T[solver->currentTes].vertex(idSph)->info().forces; return Vector3r(f[0],f[1],f[2]);} Vector3r averageVelocity(); Vector3r shearLubForce(unsigned int id_sph) { return (solver->shearLubricationForces.size()>id_sph)?solver->shearLubricationForces[id_sph]:Vector3r::Zero();} Vector3r shearLubTorque(unsigned int id_sph) { return (solver->shearLubricationTorques.size()>id_sph)?solver->shearLubricationTorques[id_sph]:Vector3r::Zero();} Vector3r pumpLubTorque(unsigned int id_sph) { return (solver->pumpLubricationTorques.size()>id_sph)?solver->pumpLubricationTorques[id_sph]:Vector3r::Zero();} Vector3r twistLubTorque(unsigned int id_sph) { return (solver->twistLubricationTorques.size()>id_sph)?solver->twistLubricationTorques[id_sph]:Vector3r::Zero();} Vector3r normalLubForce(unsigned int id_sph) { return (solver->normalLubricationForce.size()>id_sph)?solver->normalLubricationForce[id_sph]:Vector3r::Zero();} Matrix3r bodyShearLubStress(unsigned int id_sph) { return (solver->shearLubricationBodyStress.size()>id_sph)?solver->shearLubricationBodyStress[id_sph]:Matrix3r::Zero();} Matrix3r bodyNormalLubStress(unsigned int id_sph) { return (solver->normalLubricationBodyStress.size()>id_sph)?solver->normalLubricationBodyStress[id_sph]:Matrix3r::Zero();} Vector3r shearVelocity(unsigned int interaction) { return (solver->deltaShearVel[interaction]);} Vector3r normalVelocity(unsigned int interaction) { return (solver->deltaNormVel[interaction]);} Matrix3r normalStressInteraction(unsigned int interaction) { return (solver->normalStressInteraction[interaction]);} Matrix3r shearStressInteraction(unsigned int interaction) { return (solver->shearStressInteraction[interaction]);} Vector3r normalVect(unsigned int interaction) { return (solver->normalV[interaction]);} Real surfaceDistanceParticle(unsigned int interaction) { return (solver->surfaceDistance[interaction]);} Real edgeSize() { return (solver->edgeIds.size());} Real OSI() { return (solver->onlySpheresInteractions.size());} int onlySpheresInteractions(unsigned int interaction) { return (solver->onlySpheresInteractions[interaction]);} boost::python::list getConstrictions(bool all) { vector csd=solver->getConstrictions(); boost::python::list pycsd; for (unsigned int k=0;k0) pycsd.append(csd[k]); return pycsd;} boost::python::list getConstrictionsFull(bool all) { vector csd=solver->getConstrictionsFull(); boost::python::list pycsd; for (unsigned int k=0;k0) { boost::python::list cons; cons.append(csd[k].first.first); cons.append(csd[k].first.second); cons.append(csd[k].second[0]); cons.append(csd[k].second[1]); cons.append(csd[k].second[2]); cons.append(csd[k].second[3]); pycsd.append(cons);} return pycsd;} template Real volumeCellSingleFictious (Cellhandle cell); template Real volumeCellDoubleFictious (Cellhandle cell); template Real volumeCellTripleFictious (Cellhandle cell); template Real volumeCell (Cellhandle cell); Real surfaceSolidThroatInPore(int cellId, int throatIndex) {return solver->surfaceSolidThroatInPore(solver->T[solver->currentTes].cellHandles[cellId], throatIndex, false, false);} template Vector3r cellBarycenterFromHandle (Cellhandle cell) {return makeVector3r(solver->cellBarycenter(cell));} Vector3r cellBarycenterFromId (unsigned long id) { if (id>=solver->T[solver->currentTes].cellHandles.size()) {LOG_ERROR("id out of range, max value is "<T[solver->currentTes].cellHandles.size()); return Vector3r(0,0,0);} else return cellBarycenterFromHandle(solver->T[solver->currentTes].cellHandles[id]);} void Oedometer_Boundary_Conditions(); void averageRealCellVelocity(); void saveVtk(const char* folder) {solver->saveVtk(folder);} vector avFlVelOnSph(unsigned int idSph) {return solver->averageFluidVelocityOnSphere(idSph);} // void setBoundaryVel(Vector3r vel) {topBoundaryVelocity=vel; updateTriangulation=true;} void pressureProfile(double wallUpY, double wallDownY) {return solver->measurePressureProfile(wallUpY,wallDownY);} double getPorePressure(Vector3r pos){return solver->getPorePressure(pos[0], pos[1], pos[2]);} int getCell(double posX, double posY, double posZ){return solver->getCell(posX, posY, posZ);} unsigned int nCells(){return solver->T[solver->currentTes].cellHandles.size();} CELL_VECTOR_GETTER(,cellCenter) CELL_VECTOR_GETTER(.averageCellVelocity,cellVelocity) CELL_SCALAR_GETTER(double,.p(),cellPressure) CELL_SCALAR_SETTER_EXTRA(double,.p(),setCellPressure,solver->resetRHS()) CELL_SCALAR_GETTER(bool,.Pcondition,cellPImposed) CELL_SCALAR_SETTER_EXTRA(bool,.Pcondition,setCellPImposed,solver->resetLinearSystem()) boost::python::list getVertices(unsigned int id){ boost::python::list ids; if (id>=solver->T[solver->currentTes].cellHandles.size()) {LOG_ERROR("id out of range, max value is "<T[solver->currentTes].cellHandles.size()); return ids;} for (unsigned int i=0;i<4;i++) ids.append(solver->T[solver->currentTes].cellHandles[id]->vertex(i)->info().id()); return ids; } void blockCell(unsigned int id, bool blockPressure){ if (id>=solver->T[solver->currentTes].cellHandles.size()) {LOG_ERROR("id out of range, max value is "<T[solver->currentTes].cellHandles.size()); return;} solver->T[solver->currentTes].cellHandles[id]->info().blocked=true; solver->T[solver->currentTes].cellHandles[id]->info().Pcondition=blockPressure; } Vector3r getBoundaryNormal(int index) { if (index<0 || index>5) LOG_ERROR("index out of range (0-5)"); return normal[max(0,min(5,index))];} void setBoundaryNormal(int index, Vector3r v) { if (index<0 || index>5) LOG_ERROR("index out of range (0-5)"); normal[max(0,min(5,index))]=v;} double averageSlicePressure(double posY){return solver->averageSlicePressure(posY);} double averagePressure(){return solver->averagePressure();} void emulateAction(){ scene = Omega::instance().getScene().get(); action();} #ifdef LINSOLV void exportMatrix(string filename) {if (useSolver==3) solver->exportMatrix(filename.c_str()); else cerr<<"available for Cholmod solver (useSolver==3)"<exportTriplets(filename.c_str()); else cerr<<"available for Cholmod solver (useSolver==3)"<eSolver.cholmod()))<eSolver.cholmod().selected<eSolver.cholmod().called_nd<eSolver.cholmod().called_nd);} #endif virtual ~TemplateFlowEngine_@TEMPLATE_FLOW_NAME@(); virtual void action(); virtual void backgroundAction(); //commodities void compTessVolumes() { solver->T[solver->currentTes].compute(); solver->T[solver->currentTes].computeVolumes(); } Real getVolume (Body::id_t id) { if (solver->T[solver->currentTes].Max_id() <= 0) {emulateAction(); /*LOG_WARN("Not triangulated yet, emulating action");*/} if (solver->T[solver->currentTes].Volume(id) == -1) {compTessVolumes();/* LOG_WARN("Computing all volumes now, as you did not request it explicitely.");*/} return (solver->T[solver->currentTes].Max_id() >= id) ? solver->T[solver->currentTes].Volume(id) : -1;} YADE_CLASS_PYCLASS_BASE_DOC_ATTRS_DEPREC_INIT_CTOR_PY(TemplateFlowEngine_@TEMPLATE_FLOW_NAME@,@TEMPLATE_FLOW_NAME@,PartialEngine,"A generic engine from wich more specialized engines can inherit. It is defined for the sole purpose of inserting the right data classes CellInfo and VertexInfo in the triangulation, and it should not be used directly. Instead, look for specialized engines, e.g. :yref:`FlowEngine`, :yref:`PeriodicFlowEngine`, or :yref:`DFNFlowEngine`.", ((bool,isActivated,true,,"Activates Flow Engine")) ((bool,first,true,,"Controls the initialization/update phases")) ((int,idOffset,0,,"If the bounding walls of the fluid mesh are not walls of the scene (i.e. are not elements of O.bodies), the offset should be set equal to the size of O.bodies. If the bounding walls are bodies of the scene but are not numbered as 0-5 then offset should be the number of bodies comming before the walls. Set offset<0 to get it set equal to O.bodies.size(), it will also update :yref:`FlowEngine::wallIds`.")) ((bool,convertClumps,true,,"If true the clumps will be temptatively converted into equivalent spheres in the triangulation, and clump members are skipped. Else clumps are ignored and spherical clump members are triangulated as independent bodies.")) ((bool,doInterpolate,false,,"Force the interpolation of cell's info while remeshing. By default, interpolation would be done only for compressible fluids. It can be forced with this flag.")) ((double, fluidBulkModulus, 0.,,"Bulk modulus of fluid (inverse of compressibility) K=-dP*V/dV [Pa]. Flow is compressible if fluidBulkModulus > 0, else incompressible.")) ((Real, dt, 0,,"timestep [s]")) ((bool,permeabilityMap,false,,"Enable/disable stocking of average permeability scalar in cell infos.")) ((bool, slipBoundary, true,, "Controls friction condition on lateral walls")) ((bool,waveAction, false,, "Allow sinusoidal pressure condition to simulate ocean waves")) ((double, sineMagnitude, 0,, "Pressure value (amplitude) when sinusoidal pressure is applied (p )")) ((double, sineAverage, 0,,"Pressure value (average) when sinusoidal pressure is applied")) ((bool, debug, false,,"Activate debug messages")) ((double, wallThickness,0,,"Walls thickness")) ((double,pZero,0,,"The value used for initializing pore pressure. It is useless for incompressible fluid, but important for compressible model.")) ((double,tolerance,1e-06,,"Gauss-Seidel tolerance")) ((double,relax,1.9,,"Gauss-Seidel relaxation")) ((bool, updateTriangulation, 0,,"If true the medium is retriangulated. Can be switched on to force retriangulation after some events (else it will be true periodicaly based on :yref:`FlowEngine::defTolerance` and :yref:`FlowEngine::meshUpdateInterval`. Of course, it costs CPU time. Note that the new triangulation will start to be effectively used only after one iteration (i.e. O.run(2) gives a result with the new one, O.run(1) does not).")) ((int,meshUpdateInterval,1000,,"Maximum number of timesteps between re-triangulation events (a negative value will never re-triangulate). See also :yref:`FlowEngine::defTolerance`.")) ((int,breakControlledRemesh,0,,"If true, remesh will occur everytime a break occurs in JCFpmPhys. Designed to increase accuracy and efficiency in hydraulic fracture simulations.")) ((double, epsVolMax, 0,(Attr::readonly),"Maximal absolute volumetric strain computed at each iteration. |yupdate|")) ((double, defTolerance,0.05,,"Cumulated deformation threshold for which retriangulation of pore space is performed. If negative, the triangulation update will occure with a fixed frequency on the basis of :yref:`FlowEngine::meshUpdateInterval`")) ((double, porosity, 0,(Attr::readonly),"Porosity computed at each retriangulation |yupdate|")) ((bool,meanKStat,false,,"report the local permeabilities' correction")) ((bool,clampKValues,true,,"If true, clamp local permeabilities in [minKdivKmean,maxKdivKmean]*globalK. This clamping can avoid singular values in the permeability matrix and may reduce numerical errors in the solve phase. It will also hide junk values if they exist, or bias all values in very heterogeneous problems. So, use this with care.")) ((Real,minKdivKmean,0.0001,,"define the min K value (see :yref:`FlowEngine::clampKValues`)")) ((Real,maxKdivKmean,100,,"define the max K value (see :yref:`FlowEngine::clampKValues`)")) ((double,permeabilityFactor,1.0,,"Permability multiplier ($m$): $m=1$ (default) attempts to predicty the actual hydraulic conductivity using a Poiseuille equation; $m>0$ multiplies the default values by $m$; $m<0$ defines the conductivity independently of particle size and viscosity as if the material was a homogeneous continuum of conductivity $-m$")) ((double,viscosity,1.0,,"viscosity of the fluid")) ((double,stiffness, 10000,,"equivalent contact stiffness used in the lubrication model")) ((int, useSolver, 0,, "Solver to use. 0:Gauss-Seidel, >0: Cholesky factorization (sparse CHOLMOD), 4: GPU accelerated CHOLMOD (must be combined with :yref:`FlowEngine::multithread`=True)")) ((int, xmin,0,(Attr::readonly),"Index of the boundary $x_{min}$. This index is not equal the the id of the corresponding body in general, it may be used to access the corresponding attributes (e.g. flow.bndCondValue[flow.xmin], flow.wallId[flow.xmin],...).")) ((int, xmax,1,(Attr::readonly),"See :yref:`FlowEngine::xmin`.")) ((int, ymin,2,(Attr::readonly),"See :yref:`FlowEngine::xmin`.")) ((int, ymax,3,(Attr::readonly),"See :yref:`FlowEngine::xmin`.")) ((int, zmin,4,(Attr::readonly),"See :yref:`FlowEngine::xmin`.")) ((int, zmax,5,(Attr::readonly),"See :yref:`FlowEngine::xmin`.")) ((vector, bndCondIsPressure, vector(6,false),,"defines the type of boundary condition for each side. True if pressure is imposed, False for no-flux. Indexes can be retrieved with :yref:`FlowEngine::xmin` and friends.")) ((vector, bndCondValue, vector(6,0),,"Imposed value of a boundary condition. Only applies if the boundary condition is imposed pressure, else the imposed flux is always zero presently (may be generalized to non-zero imposed fluxes in the future).")) ((vector, boundaryVelocity, vector(6,Vector3r::Zero()),, "velocity on top boundary, only change it using :yref:`FlowEngine::setBoundaryVel`")) ((int, ignoredBody,-1,,"DEPRECATED, USE MASK - Id of a sphere to exclude from the triangulation.)")) ((int,mask,0,,"If mask defined, only bodies with corresponding groupMask will be affected by this engine. If 0, all bodies will be affected.")) ((vector, wallIds,vector(6),,"body ids of the boundaries (default values are ok only if aabbWalls are appended before spheres, i.e. numbered 0,...,5)")) ((vector, boundaryUseMaxMin, vector(6,true),,"If true (default value) bounding sphere is added as function of max/min sphere coord, if false as function of yade wall position")) ((bool, viscousShear, false,,"compute viscous shear terms as developped by Donia Marzougui (FIXME: ref.)")) ((bool, shearLubrication, false,,"compute shear lubrication force as developped by Brule (FIXME: ref.) ")) ((bool, pumpTorque, false,,"Compute pump torque applied on particles ")) ((bool, twistTorque, false,,"Compute twist torque applied on particles ")) ((double, eps, 0.00001,,"roughness defined as a fraction of particles size, giving the minimum distance between particles in the lubrication model.")) ((bool, pressureForce, true,,"compute the pressure field and associated fluid forces. WARNING: turning off means fluid flow is not computed at all.")) ((bool, normalLubrication, false,,"compute normal lubrication force as developped by Brule")) ((bool, viscousNormalBodyStress, false,,"compute normal viscous stress applied on each body")) ((bool, viscousShearBodyStress, false,,"compute shear viscous stress applied on each body")) ((bool, multithread, false,,"Build triangulation and factorize in the background (multi-thread mode)")) #ifdef CHOLMOD_LIBS ((int, numSolveThreads, 1,,"number of openblas threads in the solve phase.")) ((int, numFactorizeThreads, 1,,"number of openblas threads in the factorization phase")) #endif ((vector, boundaryPressure,vector(),,"values defining pressure along x-axis for the top surface. See also :yref:`@TEMPLATE_FLOW_NAME@::boundaryXPos`")) ((vector, boundaryXPos,vector(),,"values of the x-coordinate for which pressure is defined. See also :yref:`@TEMPLATE_FLOW_NAME@::boundaryPressure`")) ((string,blockHook,"",,"Python command to be run when remeshing. Anticipated usage: define blocked cells (see also :yref:`TemplateFlowEngine_@TEMPLATE_FLOW_NAME@.blockCell`), or apply exotic types of boundary conditions which need to visit the newly built mesh")) , /*deprec*/ ((meanK_opt,clampKValues,"the name changed")) ,, timingDeltas=shared_ptr(new TimingDeltas); for (int i=0; i<6; ++i){normal[i]=Vector3r::Zero(); wallIds[i]=i;} normal[wall_ymin].y()=normal[wall_xmin].x()=normal[wall_zmin].z()=1; normal[wall_ymax].y()=normal[wall_xmax].x()=normal[wall_zmax].z()=-1; solver = shared_ptr (new FlowSolver); first=true; epsVolMax=epsVolCumulative=retriangulationLastIter=0; ReTrg=1; backgroundCompleted=true; ellapsedIter=0; metisForced=false; , .def("imposeFlux",&TemplateFlowEngine_@TEMPLATE_FLOW_NAME@::imposeFlux,(boost::python::arg("pos"),boost::python::arg("p")),"Impose a flux in cell located at 'pos' (i.e. add a source term in the flow problem). Outflux positive, influx negative.") .def("imposePressureFromId",&TemplateFlowEngine_@TEMPLATE_FLOW_NAME@::imposePressureFromId,(boost::python::arg("id"),boost::python::arg("p")),"Impose pressure in cell of index 'id' (after remeshing the same condition will apply for the same location, regardless of what the new cell index is at this location). The index of the condition itself is returned (for multiple imposed pressures at different points).") .def("imposePressure",&TemplateFlowEngine_@TEMPLATE_FLOW_NAME@::imposePressure,(boost::python::arg("pos"),boost::python::arg("p")),"Impose pressure in cell of location 'pos'. The index of the condition is returned (for multiple imposed pressures at different points).") .def("setImposedPressure",&TemplateFlowEngine_@TEMPLATE_FLOW_NAME@::setImposedPressure,(boost::python::arg("cond"),boost::python::arg("p")),"Set pressure value at the point indexed 'cond'.") .def("clearImposedPressure",&TemplateFlowEngine_@TEMPLATE_FLOW_NAME@::clearImposedPressure,"Clear the list of points with pressure imposed.") .def("clearImposedFlux",&TemplateFlowEngine_@TEMPLATE_FLOW_NAME@::clearImposedFlux,"Clear the list of points with flux imposed.") .def("getCellFlux",&TemplateFlowEngine_@TEMPLATE_FLOW_NAME@::getCellFlux,(boost::python::arg("cond")),"Get influx in cell associated to an imposed P (indexed using 'cond').") .def("getBoundaryFlux",&TemplateFlowEngine_@TEMPLATE_FLOW_NAME@::getBoundaryFlux,(boost::python::arg("boundary")),"Get total flux through boundary defined by its body id.\n\n.. note:: The flux may be not zero even for no-flow condition. This artifact comes from cells which are incident to two or more boundaries (along the edges of the sample, typically). Such flux evaluation on impermeable boundary is just irrelevant, it does not imply that the boundary condition is not applied properly.") .def("getConstrictions",&TemplateFlowEngine_@TEMPLATE_FLOW_NAME@::getConstrictions,(boost::python::arg("all")=true),"Get the list of constriction radii (inscribed circle) for all finite facets (if all==True) or all facets not incident to a virtual bounding sphere (if all==False). When all facets are returned, negative radii denote facet incident to one or more fictious spheres.") .def("getConstrictionsFull",&TemplateFlowEngine_@TEMPLATE_FLOW_NAME@::getConstrictionsFull,(boost::python::arg("all")=true),"Get the list of constrictions (inscribed circle) for all finite facets (if all==True), or all facets not incident to a fictious bounding sphere (if all==False). When all facets are returned, negative radii denote facet incident to one or more fictious spheres. The constrictions are returned in the format {{cell1,cell2}{rad,nx,ny,nz}}") .def("edgeSize",&TemplateFlowEngine_@TEMPLATE_FLOW_NAME@::edgeSize,"Return the number of interactions.") .def("surfaceSolidThroatInPore",&TemplateFlowEngine_@TEMPLATE_FLOW_NAME@::surfaceSolidThroatInPore,(boost::python::arg("cellId"),boost::python::arg("throatIndex")),"returns solid area in the throat (index 0-3), keeping only that part of the throat in cell.") .def("OSI",&TemplateFlowEngine_@TEMPLATE_FLOW_NAME@::OSI,"Return the number of interactions only between spheres.") .def("saveVtk",&TemplateFlowEngine_@TEMPLATE_FLOW_NAME@::saveVtk,(boost::python::arg("folder")="./VTK"),"Save pressure field in vtk format. Specify a folder name for output.") .def("avFlVelOnSph",&TemplateFlowEngine_@TEMPLATE_FLOW_NAME@::avFlVelOnSph,(boost::python::arg("idSph")),"compute a sphere-centered average fluid velocity") .def("fluidForce",&TemplateFlowEngine_@TEMPLATE_FLOW_NAME@::fluidForce,(boost::python::arg("idSph")),"Return the fluid force on sphere idSph.") .def("shearLubForce",&TemplateFlowEngine_@TEMPLATE_FLOW_NAME@::shearLubForce,(boost::python::arg("idSph")),"Return the shear lubrication force on sphere idSph.") .def("shearLubTorque",&TemplateFlowEngine_@TEMPLATE_FLOW_NAME@::shearLubTorque,(boost::python::arg("idSph")),"Return the shear lubrication torque on sphere idSph.") .def("normalLubForce",&TemplateFlowEngine_@TEMPLATE_FLOW_NAME@::normalLubForce,(boost::python::arg("idSph")),"Return the normal lubrication force on sphere idSph.") .def("bodyShearLubStress",&TemplateFlowEngine_@TEMPLATE_FLOW_NAME@::bodyShearLubStress,(boost::python::arg("idSph")),"Return the shear lubrication stress on sphere idSph.") .def("bodyNormalLubStress",&TemplateFlowEngine_@TEMPLATE_FLOW_NAME@::bodyNormalLubStress,(boost::python::arg("idSph")),"Return the normal lubrication stress on sphere idSph.") .def("shearVelocity",&TemplateFlowEngine_@TEMPLATE_FLOW_NAME@::shearVelocity,(boost::python::arg("idSph")),"Return the shear velocity of the interaction.") .def("normalVelocity",&TemplateFlowEngine_@TEMPLATE_FLOW_NAME@::normalVelocity,(boost::python::arg("idSph")),"Return the normal velocity of the interaction.") .def("normalVect",&TemplateFlowEngine_@TEMPLATE_FLOW_NAME@::normalVect,(boost::python::arg("idSph")),"Return the normal vector between particles.") .def("surfaceDistanceParticle",&TemplateFlowEngine_@TEMPLATE_FLOW_NAME@::surfaceDistanceParticle,(boost::python::arg("interaction")),"Return the distance between particles.") .def("onlySpheresInteractions",&TemplateFlowEngine_@TEMPLATE_FLOW_NAME@::onlySpheresInteractions,(boost::python::arg("interaction")),"Return the id of the interaction only between spheres.") .def("pressureProfile",&TemplateFlowEngine_@TEMPLATE_FLOW_NAME@::pressureProfile,(boost::python::arg("wallUpY"),boost::python::arg("wallDownY")),"Measure pore pressure in 6 equally-spaced points along the height of the sample") .def("getPorePressure",&TemplateFlowEngine_@TEMPLATE_FLOW_NAME@::getPorePressure,(boost::python::arg("pos")),"Measure pore pressure in position pos[0],pos[1],pos[2]") .def("averageSlicePressure",&TemplateFlowEngine_@TEMPLATE_FLOW_NAME@::averageSlicePressure,(boost::python::arg("posY")),"Measure slice-averaged pore pressure at height posY") .def("averagePressure",&TemplateFlowEngine_@TEMPLATE_FLOW_NAME@::averagePressure,"Measure averaged pore pressure in the entire volume, the cells adjacent to the boundaries are ignored if includeBoundaries=False") .def("updateBCs",&TemplateFlowEngine_@TEMPLATE_FLOW_NAME@::updateBCs,"Update the boundary condition to reflect changes of boundary pressure (needed typically after changing :yref:`FlowEngine::bndCondValue`). It is not sufficient to reflect changes of the type of boundary condition (:yref:`FlowEngine::bndCondIsPressure`), in such case re-triangulation is needed (see :yref:`FlowEngine::updateTriangulation`). Conversely, the update is not necessary for point-wise imposed pressure (:yref:`FlowEngine::imposePressure`)") .def("emulateAction",&TemplateFlowEngine_@TEMPLATE_FLOW_NAME@::emulateAction,"get scene and run action (may be used to manipulate an engine outside the timestepping loop).") .def("getCell",&TemplateFlowEngine_@TEMPLATE_FLOW_NAME@::getCell,(boost::python::arg("pos")),"get id of the cell containing (X,Y,Z).") .def("getCellBarycenter",&TemplateFlowEngine_@TEMPLATE_FLOW_NAME@::cellBarycenterFromId,(boost::python::arg("id")),"get barycenter of cell 'id'.") .def("getCellCenter",&TemplateFlowEngine_@TEMPLATE_FLOW_NAME@::cellCenter,(boost::python::arg("id")),"get voronoi center of cell 'id'.") .def("getCellPressure",&TemplateFlowEngine_@TEMPLATE_FLOW_NAME@::cellPressure,(boost::python::arg("id")),"get pressure in cell 'id'.") .def("setCellPressure",&TemplateFlowEngine_@TEMPLATE_FLOW_NAME@::setCellPressure,(boost::python::arg("id"),boost::python::arg("pressure")),"set pressure in cell 'id'.") .def("getCellPImposed",&TemplateFlowEngine_@TEMPLATE_FLOW_NAME@::cellPImposed,(boost::python::arg("id")),"get the status of cell 'id' wrt imposed pressure.") .def("setCellPImposed",&TemplateFlowEngine_@TEMPLATE_FLOW_NAME@::setCellPImposed,(boost::python::arg("id"),boost::python::arg("pImposed")),"make cell 'id' assignable with imposed pressure.") .def("nCells",&TemplateFlowEngine_@TEMPLATE_FLOW_NAME@::nCells,"get the total number of finite cells in the triangulation.") .def("blockCell",&TemplateFlowEngine_@TEMPLATE_FLOW_NAME@::blockCell,(boost::python::arg("id"),boost::python::arg("blockPressure")),"block cell 'id'. The cell will be excluded from the fluid flow problem and the conductivity of all incident facets will be null. If blockPressure=False, deformation is reflected in the pressure, else it is constantly 0.") .def("getVertices",&TemplateFlowEngine_@TEMPLATE_FLOW_NAME@::getVertices,(boost::python::arg("id")),"get the vertices of a cell") #ifdef LINSOLV .def("exportMatrix",&TemplateFlowEngine_@TEMPLATE_FLOW_NAME@::exportMatrix,(boost::python::arg("filename")="matrix"),"Export system matrix to a file with all entries (even zeros will displayed).") .def("exportTriplets",&TemplateFlowEngine_@TEMPLATE_FLOW_NAME@::exportTriplets,(boost::python::arg("filename")="triplets"),"Export system matrix to a file with only non-zero entries.") .def("cholmodStats",&TemplateFlowEngine_@TEMPLATE_FLOW_NAME@::cholmodStats,"get statistics of cholmod solver activity") .def("metisUsed",&TemplateFlowEngine_@TEMPLATE_FLOW_NAME@::metisUsed,"check wether metis lib is effectively used") .add_property("forceMetis",&TemplateFlowEngine_@TEMPLATE_FLOW_NAME@::getForceMetis,&TemplateFlowEngine_@TEMPLATE_FLOW_NAME@::setForceMetis,"If true, METIS is used for matrix preconditioning, else Cholmod is free to choose the best method (which may be METIS to, depending on the matrix). See ``nmethods`` in Cholmod documentation") #endif .def("compTessVolumes",&TemplateFlowEngine_@TEMPLATE_FLOW_NAME@::compTessVolumes,"Like TesselationWrapper::computeVolumes()") .def("volume",&TemplateFlowEngine_@TEMPLATE_FLOW_NAME@::getVolume,(boost::python::arg("id")=0),"Returns the volume of Voronoi's cell of a sphere.") .def("averageVelocity",&TemplateFlowEngine_@TEMPLATE_FLOW_NAME@::averageVelocity,"measure the mean velocity in the period") .def("setBoundaryNormal",&TemplateFlowEngine_@TEMPLATE_FLOW_NAME@::setBoundaryNormal,"define the unit outward-pointing normal of a boundary (0<=index<=5).") ) }; class FlowCellInfo_@TEMPLATE_FLOW_NAME@ : public CGT::SimpleCellInfo { public: //For vector storage of all cells unsigned int index; int volumeSign; bool Pcondition; bool blocked;//exclude cell from the fluid domain Real invVoidV; Real t; int fict; Real volumeVariation; double pression; //average relative (fluid - facet translation) velocity defined for a single cell as 1/Volume * SUM_ON_FACETS(x_average_facet*average_facet_flow_rate) CVector averageCellVelocity; // Surface vectors of facets, pointing from outside toward inside the cell std::vector facetSurfaces; //Ratio between fluid surface and facet surface std::vector facetFluidSurfacesRatio; // Reflects the geometrical property of the cell, so that the force by cell fluid on grain "i" is pressure*unitForceVectors[i] std::vector unitForceVectors; // Store the area of triangle-sphere intersections for each facet (used in forces definition) std::vector facetSphereCrossSections; std::vector cellForce; std::vector rayHydr; std::vector modulePermeability; // Partial surfaces of spheres in the double-tetrahedron linking two voronoi centers. [i][j] is for sphere facet "i" and sphere facetVertices[i][j]. Last component for 1/sum_surfaces in the facet.doInterpolate double solidSurfaces [4][4]; FlowCellInfo_@TEMPLATE_FLOW_NAME@ (void) { modulePermeability.resize(4, 0); cellForce.resize(4,CGAL::NULL_VECTOR); facetSurfaces.resize(4,CGAL::NULL_VECTOR); facetFluidSurfacesRatio.resize(4,0); facetSphereCrossSections.resize(4,CGAL::NULL_VECTOR); unitForceVectors.resize(4,CGAL::NULL_VECTOR); for (int k=0; k<4;k++) for (int l=0; l<3;l++) solidSurfaces[k][l]=0; rayHydr.resize(4, 0); invSumK=index=volumeSign=s=volumeVariation=pression=invVoidV=fict=0; isFictious=false; Pcondition = false; isGhost = false; isvisited = false; isGhost=false; blocked=false; } bool isGhost; double invSumK; bool isvisited; inline Real& volume (void) {return t;} inline const Real& invVoidVolume (void) const {return invVoidV;} inline Real& invVoidVolume (void) {return invVoidV;} inline Real& dv (void) {return volumeVariation;} inline int& fictious (void) {return fict;} inline double& p (void) {return pression;} inline const double shiftedP (void) const {return pression;} //For compatibility with the periodic case inline const std::vector& kNorm (void) const {return modulePermeability;} inline std::vector& kNorm (void) {return modulePermeability;} inline std::vector< CVector >& facetSurf (void) {return facetSurfaces;} inline std::vector& force (void) {return cellForce;} inline std::vector& Rh (void) {return rayHydr;} inline CVector& averageVelocity (void) {return averageCellVelocity;} //used for transfering values between two triangulations, overload with more variables in derived classes (see e.g. SoluteFlow) inline void getInfo(const FlowCellInfo_@TEMPLATE_FLOW_NAME@& otherCellInfo) {p()=otherCellInfo.shiftedP();} }; class FlowVertexInfo_@TEMPLATE_FLOW_NAME@ : public CGT::SimpleVertexInfo { CVector grainVelocity; Real volumeIncidentCells; public: CVector forces; bool isGhost; FlowVertexInfo_@TEMPLATE_FLOW_NAME@ (void) {isGhost=false;} inline CVector& force (void) {return forces;} inline CVector& vel (void) {return grainVelocity;} inline Real& volCells (void) {return volumeIncidentCells;} inline const CVector ghostShift (void) const {return CGAL::NULL_VECTOR;} }; // Implementation of functions in this separate file for clarity #include "FlowEngine_@TEMPLATE_FLOW_NAME@.ipp" trunk-2018.02b/pkg/pfv/FlowEngine.ipp.in000066400000000000000000001210121324306050200177130ustar00rootroot00000000000000/************************************************************************* * Copyright (C) 2009 by Emanuele Catalano * * Copyright (C) 2009 by Bruno Chareyre * * * * This program is free software; it is licensed under the terms of the * * GNU General Public License v2 or later. See file LICENSE for details. * *************************************************************************/ #ifdef YADE_CGAL #ifdef FLOW_ENGINE #ifdef LINSOLV #include #endif //For pyRunString #include #include template< class _CellInfo, class _VertexInfo, class _Tesselation, class solverT > TemplateFlowEngine_@TEMPLATE_FLOW_NAME@<_CellInfo,_VertexInfo,_Tesselation,solverT>::~TemplateFlowEngine_@TEMPLATE_FLOW_NAME@() {} // YADE_PLUGIN((TFlowEng)); template< class _CellInfo, class _VertexInfo, class _Tesselation, class solverT > unsigned int TemplateFlowEngine_@TEMPLATE_FLOW_NAME@<_CellInfo,_VertexInfo,_Tesselation,solverT>::imposePressure(Vector3r pos, Real p) { // if (!flow) LOG_ERROR("no flow defined yet, run at least one iter"); solver->imposedP.push_back( pair(CGT::Point(pos[0],pos[1],pos[2]),p) ); //force immediate update of boundary conditions updateTriangulation=true; return solver->imposedP.size()-1; } template< class _CellInfo, class _VertexInfo, class _Tesselation, class solverT > unsigned int TemplateFlowEngine_@TEMPLATE_FLOW_NAME@<_CellInfo,_VertexInfo,_Tesselation,solverT>::imposePressureFromId(unsigned long id, Real p) { return imposePressure(cellBarycenterFromId(id),p); } template< class _CellInfo, class _VertexInfo, class _Tesselation, class solverT > void TemplateFlowEngine_@TEMPLATE_FLOW_NAME@<_CellInfo,_VertexInfo,_Tesselation,solverT>::action() { if ( !isActivated ) return; timingDeltas->start(); setPositionsBuffer(true); timingDeltas->checkpoint ( "Position buffer" ); if (first) { if (multithread) setPositionsBuffer(false); buildTriangulation(pZero,*solver); initializeVolumes(*solver); backgroundSolver=solver; backgroundCompleted=true; } #ifdef YADE_OPENMP solver->ompThreads = ompThreads>0? ompThreads : omp_get_max_threads(); #endif timingDeltas->checkpoint ( "Triangulating" ); updateVolumes ( *solver ); timingDeltas->checkpoint ( "Update_Volumes" ); epsVolCumulative += epsVolMax; retriangulationLastIter++; if (!updateTriangulation) updateTriangulation = // If not already set true by another function of by the user, check conditions (defTolerance>0 && epsVolCumulative > defTolerance) || (meshUpdateInterval>0 && retriangulationLastIter>meshUpdateInterval); // remesh everytime a bond break occurs (for DFNFlow-JCFPM coupling) if (breakControlledRemesh){ bool foundBreak = false; FOREACH(const shared_ptr& I, *scene->interactions){ if (!I || !I->phys.get()) continue; if (I->isReal() && JCFpmPhys::getClassIndexStatic()==I->phys->getClassIndex()) { JCFpmPhys* jcfpmphys = YADE_CAST(I->phys.get()); if (!jcfpmphys) continue; if (jcfpmphys->breakOccurred && !foundBreak){ updateTriangulation = true; jcfpmphys->breakOccurred = false; foundBreak = true; } else if (jcfpmphys->breakOccurred && foundBreak) jcfpmphys->breakOccurred = false; } } } ///compute flow and and forces here if (pressureForce){ solver->gaussSeidel(scene->dt); timingDeltas->checkpoint ( "Gauss-Seidel (includes matrix construct and factorization in single-thread mode)" ); solver->computeFacetForcesWithCache();} timingDeltas->checkpoint ( "compute_Forces" ); ///Application of vicscous forces scene->forces.sync(); timingDeltas->checkpoint ( "forces.sync()" ); computeViscousForces ( *solver ); timingDeltas->checkpoint ( "viscous forces" ); Vector3r force; Vector3r torque; FiniteVerticesIterator verticesEnd = solver->T[solver->currentTes].Triangulation().finite_vertices_end(); size_t bodiesLength = scene->bodies->size(); for ( FiniteVerticesIterator vIt = solver->T[solver->currentTes].Triangulation().finite_vertices_begin(); vIt != verticesEnd; vIt++ ) { int vId = vIt->info().id(); force = pressureForce ? Vector3r ( vIt->info().forces[0],vIt->info().forces[1],vIt->info().forces[2] ): Vector3r(0,0,0); torque = Vector3r(0,0,0); if (shearLubrication || viscousShear){ force = force + solver->shearLubricationForces[vId]; torque = torque + solver->shearLubricationTorques[vId]; if (pumpTorque) torque = torque + solver->pumpLubricationTorques[vId]; } if (twistTorque) torque = torque + solver->twistLubricationTorques[vId]; if (normalLubrication) force = force + solver-> normalLubricationForce[vId]; if (vIt->info().id() < bodiesLength) { scene->forces.addForce ( vId, force); scene->forces.addTorque ( vId, torque);} } ///End compute flow and forces timingDeltas->checkpoint ( "Applying Forces" ); #ifdef LINSOLV int sleeping = 0; if (multithread && !first) { while (updateTriangulation && !backgroundCompleted) { /*cout<<"sleeping..."<0 && ellapsedIter>(0.5*meshUpdateInterval)) && backgroundCompleted)) { if (debug) cerr<<"switch flow solver"<0 || doInterpolate) solver->interpolate (solver->T[solver->currentTes], backgroundSolver->T[backgroundSolver->currentTes]); //Copy imposed pressures/flow from the old solver backgroundSolver->imposedP = vector >(solver->imposedP); backgroundSolver->imposedF = vector >(solver->imposedF); solver=backgroundSolver; } else { fluxChanged=false; } backgroundSolver = shared_ptr (new FlowSolver); if (metisForced) {backgroundSolver->eSolver.cholmod().nmethods=1; backgroundSolver->eSolver.cholmod().method[0].ordering=CHOLMOD_METIS;} backgroundSolver->imposedP = vector >(solver->imposedP); backgroundSolver->imposedF = vector >(solver->imposedF); if (debug) cerr<<"switched"< void TemplateFlowEngine_@TEMPLATE_FLOW_NAME@<_CellInfo,_VertexInfo,_Tesselation,solverT>::backgroundAction() { if (useSolver<1) {LOG_ERROR("background calculations not available for Gauss-Seidel"); return;} buildTriangulation ( pZero,*backgroundSolver ); backgroundSolver->factorizeOnly = true; backgroundSolver->gaussSeidel(scene->dt); backgroundSolver->factorizeOnly = false; //FIXME(2): and here we need only cached variables, not forces <- this appears to be fixed already inside computeFacetForcesWithCache backgroundSolver->computeFacetForcesWithCache(/*onlyCache?*/ true); // boost::this_thread::sleep(boost::posix_time::seconds(5)); backgroundCompleted = true; } template< class _CellInfo, class _VertexInfo, class _Tesselation, class solverT > void TemplateFlowEngine_@TEMPLATE_FLOW_NAME@<_CellInfo,_VertexInfo,_Tesselation,solverT>::boundaryConditions ( Solver& flow ) { for (int k=0;k<6;k++) { flow.boundary (wallIds[k]).flowCondition=!bndCondIsPressure[k]; flow.boundary (wallIds[k]).value=bndCondValue[k]; flow.boundary (wallIds[k]).velocity = boundaryVelocity[k];//FIXME: needs correct implementation, maybe update the cached pos/vel? } } template< class _CellInfo, class _VertexInfo, class _Tesselation, class solverT > void TemplateFlowEngine_@TEMPLATE_FLOW_NAME@<_CellInfo,_VertexInfo,_Tesselation,solverT>::setImposedPressure ( unsigned int cond, Real p) { if ( cond>=solver->imposedP.size() ) LOG_ERROR ( "Setting p with cond higher than imposedP size." ); solver->imposedP[cond].second=p; //force immediate update of boundary conditions solver->pressureChanged=true; } template< class _CellInfo, class _VertexInfo, class _Tesselation, class solverT > void TemplateFlowEngine_@TEMPLATE_FLOW_NAME@<_CellInfo,_VertexInfo,_Tesselation,solverT>::imposeFlux ( Vector3r pos, Real flux){ solver->imposedF.push_back ( pair ( CGT::Point ( pos[0],pos[1],pos[2] ), flux ) ); CellHandle cell=solver->T[solver->currentTes].Triangulation().locate(CGT::Sphere(pos[0],pos[1],pos[2])); if (cell->info().isGhost) cerr<<"Imposing pressure in a ghost cell."<IPCells.size();kk++){ if (cell==solver->IPCells[kk]) cerr<<"Both flux and pressure are imposed in the same cell."<info().Pcondition) cerr<<"Imposed flux fall in a pressure boundary condition."<IFCells.push_back(cell); fluxChanged=true; } template< class _CellInfo, class _VertexInfo, class _Tesselation, class solverT > void TemplateFlowEngine_@TEMPLATE_FLOW_NAME@<_CellInfo,_VertexInfo,_Tesselation,solverT>::clearImposedPressure () { solver->imposedP.clear(); solver->IPCells.clear();} template< class _CellInfo, class _VertexInfo, class _Tesselation, class solverT > void TemplateFlowEngine_@TEMPLATE_FLOW_NAME@<_CellInfo,_VertexInfo,_Tesselation,solverT>::clearImposedFlux () { solver->imposedF.clear(); solver->IFCells.clear();} template< class _CellInfo, class _VertexInfo, class _Tesselation, class solverT > Real TemplateFlowEngine_@TEMPLATE_FLOW_NAME@<_CellInfo,_VertexInfo,_Tesselation,solverT>::getCellFlux ( unsigned int cond) { if ( cond>=solver->imposedP.size() ) {LOG_ERROR ( "Getting flux with cond higher than imposedP size." ); return 0;} double flux=0; typename Solver::CellHandle& cell= solver->IPCells[cond]; for ( int ngb=0;ngb<4;ngb++ ) { /*if (!cell->neighbor(ngb)->info().Pcondition)*/ flux+= cell->info().kNorm() [ngb]* ( cell->info().p()-cell->neighbor ( ngb )->info().p() ); } return flux+cell->info().dv(); } template< class _CellInfo, class _VertexInfo, class _Tesselation, class solverT > void TemplateFlowEngine_@TEMPLATE_FLOW_NAME@<_CellInfo,_VertexInfo,_Tesselation,solverT>::initSolver ( FlowSolver& flow ) { flow.Vtotalissimo=0; flow.VSolidTot=0; flow.vPoral=0; flow.sSolidTot=0; flow.slipBoundary=slipBoundary; flow.kFactor = permeabilityFactor; flow.debugOut = debug; flow.useSolver = useSolver; #ifdef CHOLMOD_LIBS flow.numSolveThreads = numSolveThreads; flow.numFactorizeThreads = numFactorizeThreads; #endif flow.factorizeOnly = false; flow.meanKStat = meanKStat; flow.viscosity = viscosity; flow.tolerance=tolerance; flow.relax=relax; flow.clampKValues = clampKValues; flow.maxKdivKmean = maxKdivKmean; flow.minKdivKmean = minKdivKmean; flow.meanKStat = meanKStat; flow.permeabilityMap = permeabilityMap; flow.fluidBulkModulus = fluidBulkModulus; // flow.tesselation().Clear(); flow.tesselation().maxId=-1; flow.blockedCells.clear(); flow.xMin = 1000.0, flow.xMax = -10000.0, flow.yMin = 1000.0, flow.yMax = -10000.0, flow.zMin = 1000.0, flow.zMax = -10000.0; } #ifdef LINSOLV template< class _CellInfo, class _VertexInfo, class _Tesselation, class solverT > void TemplateFlowEngine_@TEMPLATE_FLOW_NAME@<_CellInfo,_VertexInfo,_Tesselation,solverT>::setForceMetis ( bool force ) { if (force) { metisForced=true; solver->eSolver.cholmod().nmethods=1; solver->eSolver.cholmod().method[0].ordering=CHOLMOD_METIS; } else {cholmod_defaults(&(solver->eSolver.cholmod())); metisForced=false;} } template< class _CellInfo, class _VertexInfo, class _Tesselation, class solverT > bool TemplateFlowEngine_@TEMPLATE_FLOW_NAME@<_CellInfo,_VertexInfo,_Tesselation,solverT>::getForceMetis () {return (solver->eSolver.cholmod().nmethods==1);} #endif template< class _CellInfo, class _VertexInfo, class _Tesselation, class solverT > void TemplateFlowEngine_@TEMPLATE_FLOW_NAME@<_CellInfo,_VertexInfo,_Tesselation,solverT>::buildTriangulation ( Solver& flow ) { buildTriangulation ( 0.f,flow ); } template< class _CellInfo, class _VertexInfo, class _Tesselation, class solverT > void TemplateFlowEngine_@TEMPLATE_FLOW_NAME@<_CellInfo,_VertexInfo,_Tesselation,solverT>::buildTriangulation ( double pZero, Solver& flow ) { if (first) flow.currentTes=0; else { flow.currentTes=!flow.currentTes; if (debug) cout << "--------RETRIANGULATION-----------" << endl;} flow.resetNetwork(); initSolver(flow); addBoundary ( flow ); triangulate ( flow ); if ( debug ) cout << endl << "Tesselating------" << endl << endl; flow.tesselation().compute(); flow.defineFictiousCells(); // For faster loops on cells define this vector flow.tesselation().cellHandles.clear(); flow.tesselation().cellHandles.reserve(flow.tesselation().Triangulation().number_of_finite_cells()); FiniteCellsIterator cell_end = flow.tesselation().Triangulation().finite_cells_end(); int k=0; for ( FiniteCellsIterator cell = flow.tesselation().Triangulation().finite_cells_begin(); cell != cell_end; cell++ ){ flow.tesselation().cellHandles.push_back(cell); cell->info().id=k++;}//define unique numbering now, corresponds to position in cellHandles flow.displayStatistics (); if(!blockHook.empty()){ LOG_INFO("Running blockHook: "<0) initializeVolumes(flow); // needed for multithreaded compressible flow (https://bugs.launchpad.net/yade/+bug/1687355) trickPermeability(&flow); if ( !first && !multithread && (useSolver==0 || fluidBulkModulus>0 || doInterpolate)) flow.interpolate ( flow.T[!flow.currentTes], flow.tesselation() ); if ( waveAction ) flow.applySinusoidalPressure ( flow.tesselation().Triangulation(), sineMagnitude, sineAverage, 30 ); else if (boundaryPressure.size()!=0) flow.applyUserDefinedPressure ( flow.tesselation().Triangulation(), boundaryXPos , boundaryPressure); if (normalLubrication || shearLubrication || viscousShear) flow.computeEdgesSurfaces(); } template< class _CellInfo, class _VertexInfo, class _Tesselation, class solverT > void TemplateFlowEngine_@TEMPLATE_FLOW_NAME@<_CellInfo,_VertexInfo,_Tesselation,solverT>::setPositionsBuffer(bool current) { vector& buffer = current? positionBufferCurrent : positionBufferParallel; buffer.clear(); buffer.resize(scene->bodies->size()); shared_ptr sph ( new Sphere ); const int Sph_Index = sph->getClassIndexStatic(); FOREACH ( const shared_ptr& b, *scene->bodies ) { if (!b || (mask>0 and !b->maskCompatible(mask)) || (convertClumps && b->isClumpMember()) || (b->isClump() && !convertClumps)) continue; posData& dat = buffer[b->getId()]; dat.id=b->getId(); dat.pos=b->state->pos; dat.isSphere= (b->shape->getClassIndex() == Sph_Index); dat.isClump = b->isClump(); if (dat.isSphere) dat.radius = YADE_CAST(b->shape.get())->radius; if (dat.isClump) { const shared_ptr& clump = YADE_PTR_CAST(b->shape); const shared_ptr& member = Body::byId(clump->members.begin()->first,scene); dat.radius = pow( (3*b->state->mass)/(4*Mathr::PI*member->material->density) , 1.0/3.0); //use equivalent radius of clump (just valid for nearly spherical clumps) } dat.exists=true; } } template< class _CellInfo, class _VertexInfo, class _Tesselation, class solverT > void TemplateFlowEngine_@TEMPLATE_FLOW_NAME@<_CellInfo,_VertexInfo,_Tesselation,solverT>::addBoundary ( Solver& flow ) { vector& buffer = multithread ? positionBufferParallel : positionBufferCurrent; solver->xMin = Mathr::MAX_REAL, solver->xMax = -Mathr::MAX_REAL, solver->yMin = Mathr::MAX_REAL, solver->yMax = -Mathr::MAX_REAL, solver->zMin = Mathr::MAX_REAL, solver->zMax = -Mathr::MAX_REAL; FOREACH ( const posData& b, buffer ) { if ( !b.exists ) continue; if ( b.isSphere || b.isClump ) { const Real& rad = b.radius; const Real& x = b.pos[0]; const Real& y = b.pos[1]; const Real& z = b.pos[2]; flow.xMin = min ( flow.xMin, x-rad ); flow.xMax = max ( flow.xMax, x+rad ); flow.yMin = min ( flow.yMin, y-rad ); flow.yMax = max ( flow.yMax, y+rad ); flow.zMin = min ( flow.zMin, z-rad ); flow.zMax = max ( flow.zMax, z+rad ); } } //set idOffset if<0, in that case overwrite wallIds and assign values out of range scene->bodies if (idOffset<0) { idOffset = scene->bodies->size(); for (int i=0; i<6; ++i){wallIds[i]=i+idOffset;} } flow.idOffset = idOffset; flow.sectionArea = ( flow.xMax - flow.xMin ) * ( flow.zMax-flow.zMin ); flow.vTotal = ( flow.xMax-flow.xMin ) * ( flow.yMax-flow.yMin ) * ( flow.zMax-flow.zMin ); flow.yMinId=wallIds[ymin]; flow.yMaxId=wallIds[ymax]; flow.xMaxId=wallIds[xmax]; flow.xMinId=wallIds[xmin]; flow.zMinId=wallIds[zmin]; flow.zMaxId=wallIds[zmax]; //FIXME: Id's order in boundsIds is done according to the enumeration of boundaries from TXStressController.hpp, line 31. DON'T CHANGE IT! flow.boundsIds[0]= &flow.xMinId; flow.boundsIds[1]= &flow.xMaxId; flow.boundsIds[2]= &flow.yMinId; flow.boundsIds[3]= &flow.yMaxId; flow.boundsIds[4]= &flow.zMinId; flow.boundsIds[5]= &flow.zMaxId; for (int k=0;k<6;k++) flow.boundary ( *flow.boundsIds[k] ).useMaxMin = boundaryUseMaxMin[k]; flow.cornerMin = CGT::Point ( flow.xMin, flow.yMin, flow.zMin ); flow.cornerMax = CGT::Point ( flow.xMax, flow.yMax, flow.zMax ); //assign BCs types and values boundaryConditions ( flow ); double center[3]; for ( int i=0; i<6; i++ ) { if ( *flow.boundsIds[i]<0 ) continue; CGT::CVector Normal ( normal[i].x(), normal[i].y(), normal[i].z() ); if ( flow.boundary ( *flow.boundsIds[i] ).useMaxMin ) flow.addBoundingPlane(Normal, *flow.boundsIds[i] ); else { for ( int h=0;h<3;h++ ) center[h] = buffer[*flow.boundsIds[i]].pos[h]; // cerr << "id="<<*flow.boundsIds[i] <<" center="< void TemplateFlowEngine_@TEMPLATE_FLOW_NAME@<_CellInfo,_VertexInfo,_Tesselation,solverT>::triangulate ( Solver& flow ) { ///Using Tesselation wrapper (faster) // TesselationWrapper TW; // if (TW.Tes) delete TW.Tes; // TW.Tes = &(flow.tesselation());//point to the current Tes we have in Flowengine // TW.insertSceneSpheres();//TW is now really inserting in TemplateFlowEngine_@TEMPLATE_FLOW_NAME@, using the faster insert(begin,end) // TW.Tes = NULL;//otherwise, Tes would be deleted by ~TesselationWrapper() at the end of the function. ///Using one-by-one insertion vector& buffer = multithread ? positionBufferParallel : positionBufferCurrent; FOREACH ( const posData& b, buffer ) { if ( !b.exists || b.id==ignoredBody ) continue; if ( b.isSphere || b.isClump ) flow.tesselation().insert ( b.pos[0], b.pos[1], b.pos[2], b.radius, b.id ); } flow.tesselation().redirected=true;//By inserting one-by-one, we already redirected flow.shearLubricationForces.resize ( flow.tesselation().maxId+1 ); flow.shearLubricationTorques.resize ( flow.tesselation().maxId+1 ); flow.pumpLubricationTorques.resize ( flow.tesselation().maxId+1 ); flow.twistLubricationTorques.resize ( flow.tesselation().maxId+1 ); flow.shearLubricationBodyStress.resize ( flow.tesselation().maxId+1 ); flow.normalLubricationForce.resize ( flow.tesselation().maxId+1 ); flow.normalLubricationBodyStress.resize ( flow.tesselation().maxId+1 ); } template< class _CellInfo, class _VertexInfo, class _Tesselation, class solverT > void TemplateFlowEngine_@TEMPLATE_FLOW_NAME@<_CellInfo,_VertexInfo,_Tesselation,solverT>::initializeVolumes ( Solver& flow ) { typedef typename Solver::FiniteVerticesIterator FiniteVerticesIterator; FiniteVerticesIterator vertices_end = flow.tesselation().Triangulation().finite_vertices_end(); CGT::CVector Zero(0,0,0); for (FiniteVerticesIterator V_it = flow.tesselation().Triangulation().finite_vertices_begin(); V_it!= vertices_end; V_it++) V_it->info().forces=Zero; FOREACH(CellHandle& cell, flow.tesselation().cellHandles) { switch ( cell->info().fictious() ) { case ( 0 ) : cell->info().volume() = volumeCell ( cell ); break; case ( 1 ) : cell->info().volume() = volumeCellSingleFictious ( cell ); break; case ( 2 ) : cell->info().volume() = volumeCellDoubleFictious ( cell ); break; case ( 3 ) : cell->info().volume() = volumeCellTripleFictious ( cell ); break; default: break; } if (flow.fluidBulkModulus>0) { cell->info().invVoidVolume() = 1 / ( std::abs(cell->info().volume()) - flow.volumeSolidPore(cell) ); } } if (debug) cout << "Volumes initialised." << endl; } template< class _CellInfo, class _VertexInfo, class _Tesselation, class solverT > void TemplateFlowEngine_@TEMPLATE_FLOW_NAME@<_CellInfo,_VertexInfo,_Tesselation,solverT>::averageRealCellVelocity() { solver->averageRelativeCellVelocity(); Vector3r Vel ( 0,0,0 ); //AVERAGE CELL VELOCITY FiniteCellsIterator cell_end = solver->T[solver->currentTes].Triangulation().finite_cells_end(); for ( FiniteCellsIterator cell = solver->T[solver->currentTes].Triangulation().finite_cells_begin(); cell != cell_end; cell++ ) { for ( int g=0;g<4;g++ ) { if ( !cell->vertex ( g )->info().isFictious ) { const shared_ptr& sph = Body::byId ( cell->vertex ( g )->info().id(), scene ); for ( int i=0;i<3;i++ ) Vel[i] = Vel[i] + sph->state->vel[i]/4; } } RTriangulation& Tri = solver->T[solver->currentTes].Triangulation(); CGT::Point pos_av_facet; double volume_facet_translation = 0; CGT::CVector Vel_av ( Vel[0], Vel[1], Vel[2] ); for ( int i=0; i<4; i++ ) { volume_facet_translation = 0; if ( !Tri.is_infinite ( cell->neighbor ( i ) ) ) { CGT::CVector Surfk = cell->info()-cell->neighbor ( i )->info(); Real area = sqrt ( Surfk.squared_length() ); Surfk = Surfk/area; CGT::CVector branch = cell->vertex ( facetVertices[i][0] )->point() - cell->info(); pos_av_facet = ( CGT::Point ) cell->info() + ( branch*Surfk ) *Surfk; volume_facet_translation += Vel_av*cell->info().facetSurfaces[i]; cell->info().averageVelocity() = cell->info().averageVelocity() - volume_facet_translation/std::abs(cell->info().volume()) * ( pos_av_facet-CGAL::ORIGIN ); } } } } template< class _CellInfo, class _VertexInfo, class _Tesselation, class solverT > void TemplateFlowEngine_@TEMPLATE_FLOW_NAME@<_CellInfo,_VertexInfo,_Tesselation,solverT>::updateVolumes ( Solver& flow ) { if ( debug ) cout << "Updating volumes.............." << endl; Real invDeltaT = 1/scene->dt; epsVolMax=0; Real totVol=0; Real totDVol=0; #ifdef YADE_OPENMP const long size=flow.tesselation().cellHandles.size(); #pragma omp parallel for num_threads(ompThreads>0 ? ompThreads : 1) for(long i=0; iinfo().fictious() ) { case ( 3 ) : newVol = volumeCellTripleFictious ( cell ); break; case ( 2 ) : newVol = volumeCellDoubleFictious ( cell ); break; case ( 1 ) : newVol = volumeCellSingleFictious ( cell ); break; case ( 0 ) : newVol = volumeCell (cell ); break; default: newVol = 0; break;} dVol=cell->info().volumeSign* ( newVol - cell->info().volume() ); cell->info().dv() = dVol*invDeltaT; cell->info().volume() = newVol; if (defTolerance>0) { //if the criterion is not used, then we skip these updates a save a LOT of time when Nthreads > 1 #pragma omp atomic totVol+=cell->info().volumeSign*newVol; #pragma omp atomic totDVol+=dVol;} } if (defTolerance>0) epsVolMax = totDVol/totVol; //FIXME: move this loop to FlowBoundingSphere for (unsigned int n=0; ninfo().dv()+=flow.imposedF[n].second; flow.IFCells[n]->info().Pcondition=false;} if ( debug ) cout << "Updated volumes, total =" <info().volumeSign ) ) cell->info().volumeSign= ( volume>0 ) ?1:-1; return volume; } template< class _CellInfo, class _VertexInfo, class _Tesselation, class solverT > void TemplateFlowEngine_@TEMPLATE_FLOW_NAME@<_CellInfo,_VertexInfo,_Tesselation,solverT>::computeViscousForces ( Solver& flow ) { if (normalLubrication || shearLubrication || viscousShear){ if ( debug ) cout << "Application of viscous forces" << endl; if ( debug ) cout << "Number of edges = " << flow.edgeIds.size() << endl; for ( unsigned int k=0; kinfo().isFictious + Tes.vertex ( id2 )->info().isFictious; if (hasFictious>0 or id1==id2) continue; const shared_ptr& sph1 = Body::byId ( id1, scene ); const shared_ptr& sph2 = Body::byId ( id2, scene ); Sphere* s1=YADE_CAST ( sph1->shape.get() ); Sphere* s2=YADE_CAST ( sph2->shape.get() ); const Real& r1 = s1->radius; const Real& r2 = s2->radius; Vector3r deltaV; Real deltaNormV; Vector3r deltaShearV; Vector3r O1O2Vector; Real O1O2; Vector3r normal; Real surfaceDist; Vector3r O1CVector; Vector3r O2CVector;Real meanRad ;Real Rh; Vector3r deltaAngVel; Vector3r deltaShearAngVel; Vector3r shearLubF; Vector3r normaLubF; Vector3r pumpT; Vector3r deltaAngNormVel; Vector3r twistT; Vector3r angVel1; Vector3r angVel2; //FIXME: if periodic and velGrad!=0, then deltaV should account for velGrad, not the case currently if ( !hasFictious ){ O1O2Vector = sph2->state->pos + makeVector3r(vi2.ghostShift()) - sph1->state->pos - makeVector3r(vi1.ghostShift()); O1O2 = O1O2Vector.norm(); normal= (O1O2Vector/O1O2); surfaceDist = O1O2 - r2 - r1; //FIXME: what is that? O1CVector = (O1O2/2. + (pow(r1,2) - pow(r2,2)) / (2.*O1O2))*normal; O2CVector = -(O1O2Vector - O1CVector); meanRad = (r2 + r1)/2.; Rh = (r1 < r2)? surfaceDist + 0.45 * r1 : surfaceDist + 0.45 * r2; deltaV = (sph2->state->vel + sph2->state->angVel.cross(-r2 * normal)) - (sph1->state->vel+ sph1->state->angVel.cross(r1 * normal)); angVel1 = sph1->state->angVel; angVel2 = sph2->state->angVel; deltaAngVel = sph2->state->angVel - sph1->state->angVel; } else { if ( hasFictious==1 ) {//for the fictious sphere, use velocity of the boundary, not of the body bool v1fictious = Tes.vertex ( id1 )->info().isFictious; int bnd = v1fictious? id1 : id2; int coord = flow.boundary(bnd).coordinate; O1O2 = v1fictious ? std::abs((sph2->state->pos + makeVector3r(Tes.vertex(id2)->info().ghostShift()))[coord] - flow.boundary(bnd).p[coord]) : std::abs((sph1->state->pos + makeVector3r(Tes.vertex(id1)->info().ghostShift()))[coord] - flow.boundary(bnd).p[coord]); if(v1fictious) normal = makeVector3r(flow.boundary(id1).normal); else normal = -makeVector3r(flow.boundary(id2).normal); O1O2Vector = O1O2 * normal; meanRad = v1fictious ? r2:r1; surfaceDist = O1O2 - meanRad; if (v1fictious){ O1CVector = Vector3r::Zero(); O2CVector = - O1O2Vector;} else{ O1CVector = O1O2Vector; O2CVector = Vector3r::Zero();} Rh = surfaceDist + 0.45 * meanRad; Vector3r v1 = ( Tes.vertex ( id1 )->info().isFictious ) ? flow.boundary ( id1 ).velocity:sph1->state->vel + sph1->state->angVel.cross(r1 * normal); Vector3r v2 = ( Tes.vertex ( id2 )->info().isFictious ) ? flow.boundary ( id2 ).velocity:sph2->state->vel + sph2->state->angVel.cross(-r2 * (normal)); deltaV = v2-v1; angVel1 = ( Tes.vertex ( id1 )->info().isFictious ) ? Vector3r::Zero() : sph1->state->angVel; angVel2 = ( Tes.vertex ( id2 )->info().isFictious ) ? Vector3r::Zero() : sph2->state->angVel; deltaAngVel = angVel2 - angVel1; } } deltaShearV = deltaV - ( normal.dot ( deltaV ) ) *normal; deltaShearAngVel = deltaAngVel - ( normal.dot ( deltaAngVel ) ) *normal; flow.deltaShearVel.push_back(deltaShearV); flow.normalV.push_back(normal); flow.surfaceDistance.push_back(max(surfaceDist, 0.) + eps*meanRad); /// Compute the shear Lubrication force and torque on each particle if (shearLubrication) shearLubF = flow.computeShearLubricationForce(deltaShearV,surfaceDist,i,eps,O1O2,meanRad); else if (viscousShear) shearLubF = flow.computeViscousShearForce ( deltaShearV, i , Rh); if (viscousShear || shearLubrication){ flow.shearLubricationForces[id1]+=shearLubF; flow.shearLubricationForces[id2]+=(-shearLubF); flow.shearLubricationTorques[id1]+=O1CVector.cross(shearLubF); flow.shearLubricationTorques[id2]+=O2CVector.cross(-shearLubF); /// Compute the pump Lubrication torque on each particle if (pumpTorque){ pumpT = flow.computePumpTorque(deltaShearAngVel, surfaceDist, i, eps, meanRad ); flow.pumpLubricationTorques[id1]+=(-pumpT); flow.pumpLubricationTorques[id2]+=pumpT;} /// Compute the twist Lubrication torque on each particle if (twistTorque){ deltaAngNormVel = (normal.dot(deltaAngVel))*normal ; twistT = flow.computeTwistTorque(deltaAngNormVel, surfaceDist, i, eps, meanRad ); flow.twistLubricationTorques[id1]+=(-twistT); flow.twistLubricationTorques[id2]+=twistT; } } /// Compute the viscous shear stress on each particle if (viscousShearBodyStress){ flow.shearLubricationBodyStress[id1] += shearLubF * O1CVector.transpose()/ (4.0/3.0 *3.14* pow(r1,3)); flow.shearLubricationBodyStress[id2] += (-shearLubF) * O2CVector.transpose()/ (4.0/3.0 *3.14* pow(r2,3)); flow.shearStressInteraction.push_back(shearLubF * O1O2Vector.transpose()/(4.0/3.0 *3.14* pow(r1,3))); } /// Compute the normal lubrication force applied on each particle if (normalLubrication){ deltaNormV = normal.dot(deltaV); flow.deltaNormVel.push_back(deltaNormV * normal); normaLubF = flow.computeNormalLubricationForce (deltaNormV, surfaceDist, i,eps,stiffness,scene->dt,meanRad)*normal; flow.normalLubricationForce[id1]+=normaLubF; flow.normalLubricationForce[id2]+=(-normaLubF); /// Compute the normal lubrication stress on each particle if (viscousNormalBodyStress){ flow.normalLubricationBodyStress[id1] += normaLubF * O1CVector.transpose()/ (4.0/3.0 *3.14* pow(r1,3)); flow.normalLubricationBodyStress[id2] += (-normaLubF) *O2CVector.transpose() / (4.0/3.0 *3.14* pow(r2,3)); flow.normalStressInteraction.push_back(normaLubF * O1O2Vector.transpose()/(4.0/3.0 *3.14* pow(r1,3))); } } if (!hasFictious) flow.onlySpheresInteractions.push_back(i); } } } template< class _CellInfo, class _VertexInfo, class _Tesselation, class solverT > Vector3r TemplateFlowEngine_@TEMPLATE_FLOW_NAME@<_CellInfo,_VertexInfo,_Tesselation,solverT>::averageVelocity() { solver->averageRelativeCellVelocity(); Vector3r meanVel ( 0,0,0 ); Real volume=0; FiniteCellsIterator cell_end = solver->T[solver->currentTes].Triangulation().finite_cells_end(); for ( FiniteCellsIterator cell = solver->T[solver->currentTes].Triangulation().finite_cells_begin(); cell != cell_end; cell++ ) { //We could also define velocity using cell's center // if ( !cell->info().isReal() ) continue; if ( cell->info().isGhost ) continue; for ( int i=0;i<3;i++ ) meanVel[i]=meanVel[i]+ ( ( cell->info().averageVelocity() ) [i] * std::abs ( cell->info().volume() ) ); volume+=std::abs ( cell->info().volume() ); } return ( meanVel/volume ); } #endif //FLOW_ENGINE #endif /* YADE_CGAL */ trunk-2018.02b/pkg/pfv/PeriodicFlowEngine.cpp000066400000000000000000000603631324306050200207720ustar00rootroot00000000000000/************************************************************************* * Copyright (C) 2009 by Bruno Chareyre * * Copyright (C) 2012 by Donia Marzougui * * * * This program is free software; it is licensed under the terms of the * * GNU General Public License v2 or later. See file LICENSE for details. * *************************************************************************/ #ifdef YADE_CGAL #ifdef FLOW_ENGINE /// The periodic variant of FlowEngine is defined here. It should become a template class for more flexibility. /// It is a bit more complicated as for FlowEngine, though, because we need template inheriting from template, which breaks YADE_CLASS_XXX logic_error /// See below the commented exemple, for a possible solution #include "FlowEngine_FlowEngine_PeriodicInfo.hpp" class PeriodicCellInfo : public FlowCellInfo_FlowEngine_PeriodicInfo { public: static CVector gradP; //for real cell, baseIndex is the rank of the cell in cellHandles. For ghost cells, it is the baseIndex of the corresponding real cell. //Unlike ordinary index, baseIndex is also indexing cells with imposed pressures int baseIndex; int period[3]; static CVector hSize[3]; static CVector deltaP; int ghost; Real* _pression; PeriodicCellInfo (void){ _pression=&pression; period[0]=period[1]=period[2]=0; baseIndex=-1; volumeSign=0;} ~PeriodicCellInfo (void) {} inline const double shiftedP (void) const {return isGhost? (*_pression)+pShift() :(*_pression) ;} inline const double pShift (void) const {return deltaP[0]*period[0] + deltaP[1]*period[1] +deltaP[2]*period[2];} // inline const double p (void) {return shiftedP();} inline void setP (const Real& p) {pression=p;} bool isReal (void) {return !(isFictious || isGhost);} }; class PeriodicVertexInfo : public FlowVertexInfo_FlowEngine_PeriodicInfo { public: PeriodicVertexInfo& operator= (const CVector &u) { CVector::operator= (u); return *this; } PeriodicVertexInfo& operator= (const float &scalar) { s=scalar; return *this; } PeriodicVertexInfo& operator= (const unsigned int &id) { i= id; return *this; } int period[3]; //FIXME: the name is misleading, even non-ghost can be out of the period and therefore they need to be shifted as well inline const CVector ghostShift (void) const { return period[0]*PeriodicCellInfo::hSize[0]+period[1]*PeriodicCellInfo::hSize[1]+period[2]*PeriodicCellInfo::hSize[2];} PeriodicVertexInfo (void) {isFictious=false; s=0; i=0; period[0]=period[1]=period[2]=0; isGhost=false;} bool isReal (void) {return !(isFictious || isGhost);} }; typedef CGT::TriangulationTypes PeriFlowTriangulationTypes; typedef CGT::PeriodicTesselation > PeriFlowTesselation; #ifdef LINSOLV #define _PeriFlowSolver CGT::PeriodicFlowLinSolv #else #define _PeriFlowSolver CGT::PeriodicFlow #endif typedef TemplateFlowEngine_FlowEngine_PeriodicInfo< PeriodicCellInfo, PeriodicVertexInfo, CGT::PeriodicTesselation > >, _PeriFlowSolver> FlowEngine_PeriodicInfo; REGISTER_SERIALIZABLE(FlowEngine_PeriodicInfo); YADE_PLUGIN((FlowEngine_PeriodicInfo)); class PeriodicFlowEngine : public FlowEngine_PeriodicInfo { public : void triangulate (FlowSolver& flow); void buildTriangulation (Real pzero, FlowSolver& flow); void initializeVolumes (FlowSolver& flow); void updateVolumes (FlowSolver& flow); Real volumeCell (CellHandle cell); Real volumeCellSingleFictious (CellHandle cell); inline void locateCell(CellHandle baseCell, unsigned int& index, int& baseIndex, FlowSolver& flow, unsigned int count=0); virtual ~PeriodicFlowEngine(); virtual void action(); //Cache precomputed values for pressure shifts, based on current hSize and pGrad void preparePShifts(); YADE_CLASS_BASE_DOC_ATTRS_INIT_CTOR_PY(PeriodicFlowEngine,FlowEngine_PeriodicInfo,"A variant of :yref:`FlowEngine` implementing periodic boundary conditions. The API is very similar.", ((Real,duplicateThreshold, 0.06,,"distance from cell borders that will triger periodic duplication in the triangulation |yupdate|")) ((Vector3r, gradP, Vector3r::Zero(),,"Macroscopic pressure gradient")) ,, wallIds=vector(6,-1); solver = shared_ptr (new FlowSolver); epsVolMax=epsVolCumulative=retriangulationLastIter=0; ReTrg=1; first=true; , //nothing special to define, we re-use FlowEngine methods //.def("meanVelocity",&PeriodicFlowEngine::meanVelocity,"measure the mean velocity in the period") ) DECLARE_LOGGER; }; REGISTER_SERIALIZABLE(PeriodicFlowEngine); CVector PeriodicCellInfo::hSize[]={CVector(),CVector(),CVector()}; CVector PeriodicCellInfo::deltaP=CVector(); CVector PeriodicCellInfo::gradP=CVector(); CREATE_LOGGER ( PeriodicFlowEngine ); PeriodicFlowEngine::~PeriodicFlowEngine(){} void PeriodicFlowEngine:: action() { if ( !isActivated ) return; timingDeltas->start(); preparePShifts(); setPositionsBuffer(true); if (first) { if (multithread) setPositionsBuffer(false); cachedCell= Cell(*(scene->cell)); buildTriangulation(pZero,*solver); if (solver->errorCode>0) {LOG_INFO("triangulation error, pausing"); Omega::instance().pause(); return;} initializeVolumes(*solver); backgroundSolver=solver; backgroundCompleted=true;} // if ( first ) {buildTriangulation ( pZero ); updateTriangulation = false; initializeVolumes();} timingDeltas->checkpoint("Triangulating"); updateVolumes (*solver); epsVolCumulative += epsVolMax; retriangulationLastIter++; if (!updateTriangulation) updateTriangulation = // If not already set true by another function of by the user, check conditions (defTolerance>0 && epsVolCumulative > defTolerance) || (meshUpdateInterval>0 && retriangulationLastIter>meshUpdateInterval); timingDeltas->checkpoint("Update_Volumes"); ///compute flow and and forces here if (pressureForce){ solver->gaussSeidel(scene->dt); timingDeltas->checkpoint("Gauss-Seidel"); solver->computeFacetForcesWithCache();} timingDeltas->checkpoint("compute_Pressure_Forces"); ///compute vicscous forces scene->forces.sync(); computeViscousForces(*solver); timingDeltas->checkpoint("compute_Viscous_Forces"); Vector3r force; Vector3r torque; const Tesselation& Tes = solver->T[solver->currentTes]; for (int id=0; id<=Tes.maxId; id++){ assert (Tes.vertexHandles[id] != NULL); const Tesselation::VertexInfo& v_info = Tes.vertexHandles[id]->info(); force =(pressureForce) ? Vector3r ( ( v_info.forces ) [0],v_info.forces[1],v_info.forces[2] ) : Vector3r(0,0,0); torque = Vector3r(0,0,0); if (shearLubrication || viscousShear){ force = force +solver->shearLubricationForces[v_info.id()]; torque = torque +solver->shearLubricationTorques[v_info.id()]; if (pumpTorque) torque = torque +solver->pumpLubricationTorques[v_info.id()]; if (twistTorque) torque = torque +solver->twistLubricationTorques[v_info.id()]; } if (normalLubrication) force = force + solver->normalLubricationForce[v_info.id()]; scene->forces.addForce ( v_info.id(), force); scene->forces.addTorque ( v_info.id(), torque); } ///End Compute flow and forces timingDeltas->checkpoint("Applying Forces"); if (multithread && !first) { while (updateTriangulation && !backgroundCompleted) { /*cout<<"sleeping..."<0 && ellapsedIter>(0.5*meshUpdateInterval))) { if (useSolver==0) LOG_ERROR("background calculations not available for Gauss-Seidel"); if (fluidBulkModulus>0 || doInterpolate) solver->interpolate (solver->T[solver->currentTes], backgroundSolver->T[backgroundSolver->currentTes]); solver=backgroundSolver; backgroundSolver = shared_ptr (new FlowSolver); //Copy imposed pressures/flow from the old solver backgroundSolver->imposedP = vector >(solver->imposedP); backgroundSolver->imposedF = vector >(solver->imposedF); setPositionsBuffer(false); cachedCell= Cell(*(scene->cell)); backgroundCompleted=false; retriangulationLastIter=ellapsedIter; ellapsedIter=0; epsVolCumulative=0; boost::thread workerThread(&PeriodicFlowEngine::backgroundAction,this); workerThread.detach(); initializeVolumes(*solver); computeViscousForces(*solver); } else if (debug && !first) { if (debug && !backgroundCompleted) cerr<<"still computing solver in the background"<cell)); buildTriangulation (pZero, *solver); initializeVolumes(*solver); computeViscousForces(*solver); updateTriangulation = false; epsVolCumulative=0; retriangulationLastIter=0; ReTrg++;} } first=false; timingDeltas->checkpoint("Ending"); } // void PeriodicFlowEngine::backgroundAction() // { // if (useSolver<1) {LOG_ERROR("background calculations not available for Gauss-Seidel"); return;} // buildTriangulation (pZero,*backgroundSolver); // //FIXME: GS is computing too much, we need only matrix factorization in fact // backgroundSolver->gaussSeidel(scene->dt); // backgroundSolver->computeFacetForcesWithCache(/*onlyCache?*/ true); // // boost::this_thread::sleep(boost::posix_time::seconds(10)); // backgroundCompleted = true; // } void PeriodicFlowEngine::triangulate( FlowSolver& flow ) { Tesselation& Tes = flow.tesselation(); vector& buffer = multithread ? positionBufferParallel : positionBufferCurrent; FOREACH ( const posData& b, buffer ) { if ( !b.exists || !b.isSphere || b.id==ignoredBody) continue; Vector3i period; Vector3r wpos; // FIXME: use "sheared" variant if the cell is sheared wpos=cachedCell.wrapPt ( b.pos,period ); const Body::id_t& id = b.id; const Real& rad = b.radius; const Real& x = wpos[0]; const Real& y = wpos[1]; const Real& z = wpos[2]; VertexHandle vh0=Tes.insert ( x, y, z, rad, id ); // VertexHandle vh0=Tes.insert ( b.pos[0], b.pos[1], b.pos[2], b.radius, b.id ); if (vh0==NULL) { flow.errorCode = 2; LOG_ERROR("Vh NULL in PeriodicFlowEngine::triangulate(), check input data"); continue;} for ( int k=0;k<3;k++ ) vh0->info().period[k]=-period[k]; const Vector3r cellSize ( cachedCell.getSize() ); //FIXME: if hasShear, comment in // wpos=scene->cell->unshearPt ( wpos ); // traverse all periodic cells around the body, to see if any of them touches Vector3r halfSize= ( rad+duplicateThreshold ) *Vector3r ( 1,1,1 ); Vector3r pmin,pmax; Vector3i i; for ( i[0]=-1; i[0]<=1; i[0]++ ) for ( i[1]=-1;i[1]<=1; i[1]++ ) for ( i[2]=-1; i[2]<=1; i[2]++ ) { if ( i[0]!=0 || i[1]!=0 || i[2]!=0 ) { // middle; already rendered above Vector3r pos2=wpos+Vector3r ( cellSize[0]*i[0],cellSize[1]*i[1],cellSize[2]*i[2] ); // shift, but without shear! pmin=pos2-halfSize; pmax=pos2+halfSize; if ( (pmin[0]<=cellSize[0]) && (pmax[0]>=0) && (pmin[1]<=cellSize[1]) && (pmax[1]>=0) && (pmin[2]<=cellSize[2]) && (pmax[2]>=0) ) { //with shear: //Vector3r pt=scene->cell->shearPt ( pos2 ); //without shear: const Vector3r& pt= pos2; VertexHandle vh=Tes.insert ( pt[0],pt[1],pt[2],rad,id,false,id ); for ( int k=0;k<3;k++ ) vh->info().period[k]=i[k]-period[k];}} } //re-assign the original vertex pointer since duplicates may have overwrite it Tes.vertexHandles[id]=vh0; } Tes.redirected=true;//By inserting one-by-one, we already redirected flow.shearLubricationForces.resize ( Tes.maxId+1 ); flow.shearLubricationTorques.resize ( Tes.maxId+1 ); flow.pumpLubricationTorques.resize ( Tes.maxId+1 ); flow.twistLubricationTorques.resize ( Tes.maxId+1 ); flow.shearLubricationBodyStress.resize ( Tes.maxId+1 ); flow.normalLubricationForce.resize ( Tes.maxId+1 ); flow.normalLubricationBodyStress.resize ( Tes.maxId+1 ); } Real PeriodicFlowEngine::volumeCell ( CellHandle cell ) { static const Real inv6 = 1/6.; const Vector3r p0 = positionBufferCurrent[cell->vertex(0)->info().id()].pos + makeVector3r(cell->vertex(0)->info().ghostShift()); const Vector3r p1 = positionBufferCurrent[cell->vertex(1)->info().id()].pos + makeVector3r(cell->vertex(1)->info().ghostShift()); const Vector3r p2 = positionBufferCurrent[cell->vertex(2)->info().id()].pos + makeVector3r(cell->vertex(2)->info().ghostShift()); const Vector3r p3 = positionBufferCurrent[cell->vertex(3)->info().id()].pos + makeVector3r(cell->vertex(3)->info().ghostShift()); Real volume = inv6*((p0-p1).cross(p0-p2)).dot(p0-p3); if ( ! ( cell->info().volumeSign ) ) cell->info().volumeSign= ( volume>0 ) ?1:-1; return volume; } Real PeriodicFlowEngine::volumeCellSingleFictious ( CellHandle cell ) { Vector3r V[3]; int b=0; int w=0; cell->info().volumeSign=1; Real Wall_coordinate=0; for ( int y=0;y<4;y++ ) { if ( ! ( cell->vertex ( y )->info().isFictious ) ) { const shared_ptr& sph = Body::byId ( cell->vertex ( y )->info().id(), scene ); V[w]=sph->state->pos+ makeVector3r ( cell->vertex ( y )->info().ghostShift() ); w++; } else { b = cell->vertex ( y )->info().id(); const shared_ptr& wll = Body::byId ( b,scene ); if ( !solver->boundary ( b ).useMaxMin ) Wall_coordinate = wll->state->pos[solver->boundary ( b ).coordinate]+ ( solver->boundary ( b ).normal[solver->boundary ( b ).coordinate] ) *wallThickness/2.; else Wall_coordinate = solver->boundary ( b ).p[solver->boundary ( b ).coordinate]; } } Real Volume = 0.5* ( ( V[0]-V[1] ).cross ( V[0]-V[2] ) ) [solver->boundary ( b ).coordinate] * ( 0.33333333333* ( V[0][solver->boundary ( b ).coordinate]+ V[1][solver->boundary ( b ).coordinate]+ V[2][solver->boundary ( b ).coordinate] ) - Wall_coordinate ); return std::abs ( Volume ); } void PeriodicFlowEngine::locateCell ( CellHandle baseCell, unsigned int& index, int& baseIndex, FlowSolver& flow, unsigned int count) { if (count>10) { LOG_ERROR("More than 10 attempts to locate a cell, duplicateThreshold may be too small, resulting in periodicity inconsistencies."); flow.errorCode=1; return; } PeriFlowTesselation::CellInfo& baseInfo = baseCell->info(); //already located, return FIXME: is inline working correctly? else move this test outside the function, just before the calls if ( baseInfo.index>0 || baseInfo.isGhost ) return; RTriangulation& Tri = flow.tesselation().Triangulation(); Vector3r center ( 0,0,0 ); Vector3i period; if (baseCell->info().fictious()==0) for ( int k=0;k<4;k++ ) center+= 0.25*makeVector3r (baseCell->vertex(k)->point().point()); else { Real boundPos=0; int coord=0; for ( int k=0;k<4;k++ ) { if ( !baseCell->vertex ( k )->info().isFictious ) center+= 0.3333333333*makeVector3r ( baseCell->vertex ( k )->point().point() ); else { coord=flow.boundary ( baseCell->vertex ( k )->info().id() ).coordinate; boundPos=flow.boundary ( baseCell->vertex ( k )->info().id() ).p[coord];} } center[coord]=boundPos; } Vector3r wdCenter= cachedCell.wrapPt ( center,period ); if ( period[0]!=0 || period[1]!=0 || period[2]!=0 ) { if ( baseCell->info().index>0 ) { cout<<"indexed cell is found ghost!"<vertex(kk)->info().isGhost) && ((!baseCell->vertex(kk)->info().isFictious))) checkC = true; if (checkC) { bool checkV=true; for (int kk=0; kk<4;kk++) { checkV=false; for (int jj=0; jj<4;jj++) if (baseCell->vertex(kk)->info().id() == ch->vertex(jj)->info().id()) checkV = true; if (!checkV) {cerr <<"periodicity is broken"<vertex(jj)->info().id()<<" "; cerr<<" vs. "; for (int jj=0; jj<4;jj++) cerr<vertex(jj)->info().id()<<" "; cerr<vertex(kk)->info().id() == ch->vertex(jj)->info().id()) checkV = true; // if (!checkV) {cerr <<"periodicity is broken (that's ok probably)"<vertex(jj)->info().id()<<" "; // cerr<<" vs. "; // for (int jj=0; jj<4;jj++) cerr<vertex(jj)->info().id()<<" "; // cerr<info().p() ); baseInfo.index=ch->info().index; baseInfo.baseIndex=ch->info().baseIndex; baseInfo.Pcondition=ch->info().Pcondition; } else { baseInfo.isGhost=false; //index is 1-based, if it is zero it is not initialized, we define it here if ( baseInfo.baseIndex<0 ){ baseInfo.baseIndex=++baseIndex; if (!baseInfo.Pcondition) baseInfo.index=++index;} } } void PeriodicFlowEngine::updateVolumes (FlowSolver& flow) { //FIXME: replace by the non-periodic version if ( debug ) cout << "Updating volumes.............." << endl; Real invDeltaT = 1/scene->dt; double newVol, dVol; epsVolMax=0; Real totVol=0; Real totDVol=0; Real totVol0=0; Real totVol1=0; FOREACH(CellHandle& cell, flow.tesselation().cellHandles){ switch ( cell->info().fictious() ) { case ( 1 ) : newVol = volumeCellSingleFictious ( cell ); totVol1+=newVol; break; case ( 0 ) : newVol = volumeCell ( cell ); totVol0+=newVol; break; default: newVol = 0; break; } totVol+=newVol; dVol=cell->info().volumeSign * ( newVol - cell->info().volume() ); totDVol+=dVol; if (newVol != 0) { epsVolMax = max ( epsVolMax, std::abs ( dVol/newVol ) ); } cell->info().dv() = dVol * invDeltaT; cell->info().volume() = newVol; } for (unsigned int n=0; ninfo().dv()+=flow.imposedF[n].second; flow.IFCells[n]->info().Pcondition=false;} if ( debug ) cout << "Updated volumes, total =" <info().forces=Zero; FOREACH(CellHandle& cell, flow.tesselation().cellHandles){ switch ( cell->info().fictious() ) { case ( 0 ) : cell->info().volume() = volumeCell ( cell ); break; case ( 1 ) : cell->info().volume() = volumeCellSingleFictious ( cell ); break; default: cell->info().volume() = 0; break; } //FIXME: the void volume is negative sometimes, hence crashing... if (flow.fluidBulkModulus>0) { cell->info().invVoidVolume() = 1. / (max(0.1*cell->info().volume(),std::abs(cell->info().volume()) - flow.volumeSolidPore(cell)) ); } } if ( debug ) cout << "Volumes initialised." << endl; } void PeriodicFlowEngine::buildTriangulation ( double pZero, FlowSolver& flow) { if (first) flow.currentTes=0; else { flow.currentTes=!flow.currentTes; if ( debug ) cout << "--------RETRIANGULATION-----------" << endl;} flow.resetNetwork(); initSolver(flow); addBoundary ( flow ); if ( debug ) cout << endl << "Added boundaries------" << endl << endl; triangulate (flow); if ( debug ) cout << endl << "Tesselating------" << endl << endl; flow.tesselation().compute(); flow.defineFictiousCells(); //FIXME: this is already done in addBoundary(?) boundaryConditions ( flow ); if ( debug ) cout << endl << "boundaryConditions------" << endl << endl; flow.initializePressure ( pZero ); if ( debug ) cout << endl << "initializePressure------" << endl << endl; // Define the ghost cells and add indexes to the cells inside the period (the ones that will contain the pressure unknowns) //This must be done after boundary conditions and initialize pressure, else the indexes are not good (not accounting imposedP): FIXME unsigned int index=0; int baseIndex=-1; FlowSolver::Tesselation& Tes = flow.tesselation(); Tes.cellHandles.resize(Tes.Triangulation().number_of_finite_cells()); const FiniteCellsIterator cellend=Tes.Triangulation().finite_cells_end(); for ( FiniteCellsIterator cell=Tes.Triangulation().finite_cells_begin(); cell!=cellend; cell++ ){ locateCell ( cell,index,baseIndex,flow ); if (flow.errorCode>0) {LOG_ERROR("problem here, flow.errorCode>0"); return;} //Fill this vector than can be later used to speedup loops if (!cell->info().isGhost) Tes.cellHandles[cell->info().baseIndex]=cell; cell->info().id=cell->info().baseIndex; } Tes.cellHandles.resize(baseIndex+1); /// Call blockHook here if defined, valid indices and ghost status are available if(!blockHook.empty()){ LOG_INFO("Running blockHook: "<0 || doInterpolate)) flow.interpolate ( flow.T[!flow.currentTes], Tes ); // if ( !first && (useSolver==0 || fluidBulkModulus>0)) flow.interpolate ( flow.T[!flow.currentTes], flow.tesselation() ); if ( waveAction ) flow.applySinusoidalPressure ( Tes.Triangulation(), sineMagnitude, sineAverage, 30 ); if (normalLubrication || shearLubrication || viscousShear) flow.computeEdgesSurfaces(); if ( debug ) cout << endl << "end buildTri------" << endl << endl; } void PeriodicFlowEngine::preparePShifts() { CellInfo::gradP = makeCgVect ( gradP ); CellInfo::hSize[0] = makeCgVect ( scene->cell->hSize.col ( 0 ) ); CellInfo::hSize[1] = makeCgVect ( scene->cell->hSize.col ( 1 ) ); CellInfo::hSize[2] = makeCgVect ( scene->cell->hSize.col ( 2 ) ); CellInfo::deltaP=CGT::CVector ( CellInfo::hSize[0]*CellInfo::gradP, CellInfo::hSize[1]*CellInfo::gradP, CellInfo::hSize[2]*CellInfo::gradP ); } YADE_PLUGIN((PeriodicFlowEngine)); #endif //FLOW_ENGINE #endif /* YADE_CGAL */ trunk-2018.02b/pkg/pfv/SoluteFlowEngine.cpp000066400000000000000000000243441324306050200205060ustar00rootroot00000000000000/************************************************************************* * Copyright (C) 2013 by T. Sweijen (T.sweijen@uu.nl) * * * * This program is free software; it is licensed under the terms of the * * GNU General Public License v2 or later. See file LICENSE for details. * *************************************************************************/ #ifdef YADE_CGAL #ifdef FLOW_ENGINE // #define SOLUTE_FLOW #ifdef SOLUTE_FLOW #include "FlowEngine_SoluteFlowEngineT.hpp" #include class SoluteCellInfo : public FlowCellInfo_SoluteFlowEngineT { public: Real solute_concentration; SoluteCellInfo (void) : FlowCellInfo_SoluteFlowEngineT() {solute_concentration=0;} inline Real& solute (void) {return solute_concentration;} inline const Real& solute (void) const {return solute_concentration;} inline void getInfo (const SoluteCellInfo& otherCellInfo) {FlowCellInfo_SoluteFlowEngineT::getInfo(otherCellInfo); solute()=otherCellInfo.solute();} }; typedef TemplateFlowEngine_SoluteFlowEngineT SoluteFlowEngineT; REGISTER_SERIALIZABLE(SoluteFlowEngineT); YADE_PLUGIN((SoluteFlowEngineT)); class SoluteFlowEngine : public SoluteFlowEngineT { public : void initializeSoluteTransport(); void soluteTransport (); double getConcentration(unsigned int id){return solver->T[solver->currentTes].cellHandles[id]->info().solute(); } double insertConcentration(unsigned int id,double conc){ solver->T[solver->currentTes].cellHandles[id]->info().solute() = conc; return conc;} void soluteBC(unsigned int bc_id1, unsigned int bc_id2, double bc_concentration1, double bc_concentration2,unsigned int s); double getConcentrationPlane (double Yobs,double Yr, int xyz); double getAverageConcentration(); ///Elaborate the description as you wish YADE_CLASS_BASE_DOC_ATTRS_INIT_CTOR_PY(SoluteFlowEngine,SoluteFlowEngineT,"A variant of :yref:`FlowEngine` with solute transport).", ///No additional variable yet, else input here // ((Vector3r, gradP, Vector3r::Zero(),,"Macroscopic pressure gradient")) ((double,DiffusionCoefficient,0,,"Diffusion coefficient for molecular diffusion")) ,,, .def("soluteTransport",&SoluteFlowEngine::soluteTransport,"Solute transport (advection and diffusion) engine for diffusion use a diffusion coefficient (D) other than 0.") .def("getConcentration",&SoluteFlowEngine::getConcentration,(boost::python::arg("id")),"get concentration of pore with ID") .def("insertConcentration",&SoluteFlowEngine::insertConcentration,(boost::python::arg("id"),boost::python::arg("conc")),"Insert Concentration (ID, Concentration)") .def("solute_BC",&SoluteFlowEngine::soluteBC,(boost::python::arg("bc_id1"),boost::python::arg("bc_id2"),boost::python::arg("bc_concentration1"),boost::python::arg("bc_concentration2"),boost::python::arg("s")),"Enter X,Y,Z for concentration observation'.") .def("initializeSoluteTransport",&SoluteFlowEngine::initializeSoluteTransport,"Initialize Solute Transport") .def("getConcentrationPlane",&SoluteFlowEngine::getConcentrationPlane,(boost::python::arg("Yobs"),boost::python::arg("Yr"),boost::python::arg("xyz")),"get concentration of pore with ID") .def("getAverageConcentration",&SoluteFlowEngine::getAverageConcentration,"The the volume averaged concentration") ) }; REGISTER_SERIALIZABLE(SoluteFlowEngine); // PeriodicFlowEngine::~PeriodicFlowEngine(){} void SoluteFlowEngine::initializeSoluteTransport () { FOREACH(CellHandle& cell, solver->T[solver->currentTes].cellHandles) { cell->info().solute() = 0.0; } } void SoluteFlowEngine::soluteTransport () { double deltatime = scene->dt; //soluteTransport is a function to solve transport of solutes for advection (+diffusion). //Call this function with a pyRunner in the python script, implement a diffusion coefficient and a dt. //Optimalization has to be done such that the coefficient matrix is not solved for each time step,only after triangulation. //Extensive testing has to be done to check its ability to simulate a deforming porous media. double coeff = 0.00; //Ratio of dt and void volume double coeff1 = 0.00; //Coefficient for off-diagonal element double coeff2 = 0.00; //Coefficient for diagonal element double qin = 0.00; //Flux into the pore per pore throat double Qout=0.0; //Total Flux out of the pore double dt = 1e9; double invdistance = 0.0; //Fluid facet area divided by pore throat length for each pore throat double invdistancelocal = 0.0; //Sum of invdistance //Set vectors & matrix int ncells=solver->T[solver->currentTes].cellHandles.size(); Eigen::SparseMatrix Aconc(ncells,ncells); typedef Eigen::Triplet ETriplet2; std::vector tripletList2; Eigen::SparseLU,Eigen::COLAMDOrdering > eSolver2; // Prepare (copy) concentration vector Eigen::VectorXd eb2(ncells); Eigen::VectorXd ex2(ncells); FOREACH(CellHandle& cell, solver->T[solver->currentTes].cellHandles){ eb2[cell->info().id]=cell->info().solute(); } // Fill coefficient matrix FOREACH(CellHandle& cell, solver->T[solver->currentTes].cellHandles){ cell->info().invVoidVolume() = 1.0 / ( std::abs(cell->info().volume()) - std::abs(solver->volumeSolidPore(cell) ) ); for (unsigned int ngb=0;ngb<4;ngb++){ CGT::Point& p2 = cell ->neighbor(ngb)->info(); CGT::Point& p1 = cell->info(); CGT::CVector l = p1-p2; CGT::Real fluidSurf = sqrt(cell->info().facetSurfaces[ngb].squared_length())*cell->info().facetFluidSurfacesRatio[ngb]; invdistancelocal = (fluidSurf/sqrt(l.squared_length())); invdistance+=(fluidSurf/sqrt(l.squared_length())); coeff = deltatime*cell->info().invVoidVolume(); qin=std::abs(cell->info().kNorm() [ngb])* ( cell->neighbor ( ngb )->info().p()-cell->info().p()); dt = std::min(( std::abs(cell->info().volume()) - std::abs(solver->volumeSolidPore(cell) ) )/ std::abs(qin), dt); Qout=Qout+qin; //max(qin,0.0); coeff1=-1*coeff*(qin+DiffusionCoefficient*invdistancelocal); //(std::abs(max(qin,0.0))-(DiffusionCoefficient*invdistancelocal)); //off-diagonal if (coeff1 != 0.0){ tripletList2.push_back(ETriplet2(cell->info().id, cell->neighbor(ngb)->info().id,coeff1)); } } coeff2=1.0+(coeff*Qout)+(coeff*DiffusionCoefficient*invdistance); //diagonal tripletList2.push_back(ETriplet2(cell->info().id,cell->info().id,coeff2)); Qout=0.0; invdistancelocal = 0.0; invdistance = 0.0; } //Solve Matrix Aconc.setFromTriplets(tripletList2.begin(), tripletList2.end()); //if (eSolver2.signDeterminant() < 1){cerr << "determinant is negative!!!!!!! " << eSolver2.signDeterminant()<T[solver->currentTes].cellHandles){ cell->info().solute()= ex2[cell->info().id]; } tripletList2.clear(); if(dt != 1e9){scene->dt = dt;} } void SoluteFlowEngine::soluteBC(unsigned int bcid1, unsigned int bcid2, double bcconcentration1, double bcconcentration2, unsigned int s) { //Boundary conditions according to soluteTransport. //It simply assigns boundary concentrations to cells with a common vertices (e.g. infinite large sphere which makes up the boundary condition in flowEngine) //s is a switch, if 0 only bc_id1 is used (advection only). If >0 than both bc_id1 and bc_id2 are used. //NOTE (bruno): cell cirulators can be use to get all cells having bcid2 has a vertex more efficiently (see e.g. FlowBoundingSphere.ipp:721) FOREACH(CellHandle& cell, solver->T[solver->currentTes].cellHandles) { for (unsigned int ngb=0;ngb<4;ngb++){ if (cell->vertex(ngb)->info().id() == bcid1){cell->info().solute() = bcconcentration1;} if (s > 0){if (cell->vertex(ngb)->info().id() == bcid2){cell->info().solute() = bcconcentration2;}} } } } double SoluteFlowEngine::getConcentrationPlane (double Yobs,double Yr, int xyz) { //Get the concentration within a certain plane (Y_obs), whilst the cells are located in a small volume around this plane //The concentration in cells are weighed for their distance to the observation point //for a point on the x-axis (xyz=0), point on the y-axis (xyz=1), point on the z-axis (xyz=2) double sumConcentration = 0.0; double sumFraction=0.0; double concentration=0.0; //Find cells within designated volume FOREACH(CellHandle& cell, solver->T[solver->currentTes].cellHandles) { CGT::Point& p1 = cell->info(); if (std::abs(p1[xyz]) < std::abs(std::abs(Yobs) + std::abs(Yr))){ if(std::abs(p1[xyz]) > std::abs(std::abs(Yobs) - std::abs(Yr))){ sumConcentration += cell->info().solute()*(1-(std::abs(p1[xyz])-std::abs(Yobs))/std::abs(Yr)); sumFraction += (1-(std::abs(p1[xyz])-std::abs(Yobs))/std::abs(Yr)); } } } concentration = sumConcentration / sumFraction; return concentration; } double SoluteFlowEngine::getAverageConcentration() { //Get volume-averaged concentration double summConc = 0.0, summVol = 0.0; FOREACH(CellHandle& cell, solver->T[solver->currentTes].cellHandles) { if(!cell->info().isFictious){ summConc += cell->info().solute() * (std::abs(cell->info().volume()) - std::abs(solver->volumeSolidPore(cell) )); summVol += (std::abs(cell->info().volume()) - std::abs(solver->volumeSolidPore(cell) )); } } return(summConc / summVol); } YADE_PLUGIN ( ( SoluteFlowEngine ) ); #endif //SOLUTE_FLOW #endif //FLOW_ENGINE #endif /* YADE_CGAL */ trunk-2018.02b/pkg/pfv/TwoPhaseFlowEngine.cpp000066400000000000000000004515221324306050200207670ustar00rootroot00000000000000/************************************************************************* * Copyright (C) 2014 by Bruno Chareyre * * Copyright (C) 2013 by T. Sweijen (T.sweijen@uu.nl) * * Copyright (C) 2012 by Chao Yuan * * * * This program is free software; it is licensed under the terms of the * * GNU General Public License v2 or later. See file LICENSE for details. * *************************************************************************/ // This is an example of how to derive a new FlowEngine with additional data and possibly completely new behaviour. // Every functions of the base engine can be overloaded, and new functions can be added //keep this #ifdef as long as you don't really want to realize a final version publicly, it will save compilation time for everyone else //when you want it compiled, you can pass -DTWOPHASEFLOW to cmake, or just uncomment the following line #include "TwoPhaseFlowEngine.hpp" #ifdef TWOPHASEFLOW #include YADE_PLUGIN((TwoPhaseFlowEngineT)); YADE_PLUGIN((TwoPhaseFlowEngine)); YADE_PLUGIN((PhaseCluster)); PhaseCluster::~PhaseCluster(){} void TwoPhaseFlowEngine::initialization() { // scene = Omega::instance().getScene().get();//here define the pointer to Yade's scene setPositionsBuffer(true);//copy sphere positions in a buffer... if(!keepTriangulation){buildTriangulation(0.0,*solver);}//create a triangulation and initialize pressure in the elements (connecting with W-reservoir), everything will be contained in "solver" // initializeCellIndex();//initialize cell index // if(isInvadeBoundary) {computePoreThroatRadius();} // // else {computePoreThroatRadiusTrickyMethod1();}//save pore throat radius before drainage. Thomas, here you can also revert this to computePoreThroatCircleRadius(). // Determine the entry-pressure if(entryPressureMethod == 1 && isInvadeBoundary){computePoreThroatRadiusMethod1();} //MS-P method else if(entryPressureMethod == 1 && isInvadeBoundary == false){computePoreThroatRadiusTrickyMethod1();} //MS-P method else if(entryPressureMethod == 2){computePoreThroatRadiusMethod2();} //Inscribed circle} else if(entryPressureMethod == 3){computePoreThroatRadiusMethod3();} //Area equivalent circle} else if(entryPressureMethod > 3){cout << endl << "ERROR - Method for determining the entry pressure does not exist";} computePoreBodyRadius();//save pore body radius before imbibition computePoreBodyVolume();//save capillary volume of all cells, for fast calculating saturation. Also save the porosity of each cell. computeSolidLine();//save cell->info().solidLine[j][y] initializeReservoirs();//initial pressure, reservoir flags and local pore saturation // if(isCellLabelActivated) updateReservoirLabel(); solver->noCache = true; } void TwoPhaseFlowEngine::computePoreBodyVolume() { initializeVolumes(*solver); RTriangulation& tri = solver->T[solver->currentTes].Triangulation(); FiniteCellsIterator cellEnd = tri.finite_cells_end(); for (FiniteCellsIterator cell = tri.finite_cells_begin(); cell != cellEnd; cell++) { cell->info().poreBodyVolume = std::abs( cell->info().volume() ) - std::abs(solver->volumeSolidPore(cell)); cell->info().porosity = cell->info().poreBodyVolume/std::abs( cell->info().volume() ); } } void TwoPhaseFlowEngine::computePoreThroatRadiusMethod2() { //Calculate the porethroat radii of the inscribed sphere in each pore-body. RTriangulation& tri = solver->T[solver->currentTes].Triangulation(); FiniteCellsIterator cellEnd = tri.finite_cells_end(); for (FiniteCellsIterator cell = tri.finite_cells_begin(); cell != cellEnd; cell++) { for(unsigned int i = 0; i<4;i++){ cell->info().poreThroatRadius[i] = std::abs(solver->computeEffectiveRadius(cell,i)); } } } void TwoPhaseFlowEngine::computePoreThroatRadiusMethod3() { //Calculate the porethroat radii of the surface equal circle of a throat RTriangulation& tri = solver->T[solver->currentTes].Triangulation(); FiniteCellsIterator cellEnd = tri.finite_cells_end(); for (FiniteCellsIterator cell = tri.finite_cells_begin(); cell != cellEnd; cell++) { for(unsigned int i = 0; i<4;i++){ cell->info().poreThroatRadius[i] = solver->computeEquivalentRadius(cell,i); } } } void TwoPhaseFlowEngine::computePoreBodyRadius() { // This routine finds the radius of the inscribed sphere within each pore-body // Following Mackay et al., 1972. double d01 = 0.0, d02 = 0.0, d03 = 0.0, d12 = 0.0, d13 = 0.0, d23 = 0.0, Rin = 0.0, r0 = 0.0, r1 = 0.0, r2 =0.0, r3 = 0.0; bool check = false; unsigned int i = 0; double dR=0.0, tempR = 0.0; bool initialSign = false; //False = negative, true is positive bool first = true; Eigen::MatrixXd M(6,6); FOREACH(CellHandle& cell, solver->T[solver->currentTes].cellHandles){ //Distance between multiple particles, can be done more efficient d01 = d02 = d03 = d12 = d13 = d23 = r0 = r1 = r2= r3 = 0.0; d01 = pow((cell->vertex(0)->point().x()-cell->vertex(1)->point().x()),2)+ pow((cell->vertex(0)->point().y()-cell->vertex(1)->point().y()),2)+ pow((cell->vertex(0)->point().z()-cell->vertex(1)->point().z()),2); d02 = pow((cell->vertex(0)->point().x()-cell->vertex(2)->point().x()),2)+ pow((cell->vertex(0)->point().y()-cell->vertex(2)->point().y()),2)+ pow((cell->vertex(0)->point().z()-cell->vertex(2)->point().z()),2); d03 = pow((cell->vertex(0)->point().x()-cell->vertex(3)->point().x()),2)+ pow((cell->vertex(0)->point().y()-cell->vertex(3)->point().y()),2)+ pow((cell->vertex(0)->point().z()-cell->vertex(3)->point().z()),2); d12 =pow((cell->vertex(1)->point().x()-cell->vertex(2)->point().x()),2)+ pow((cell->vertex(1)->point().y()-cell->vertex(2)->point().y()),2)+ pow((cell->vertex(1)->point().z()-cell->vertex(2)->point().z()),2); d13 = pow((cell->vertex(1)->point().x()-cell->vertex(3)->point().x()),2)+ pow((cell->vertex(1)->point().y()-cell->vertex(3)->point().y()),2)+ pow((cell->vertex(1)->point().z()-cell->vertex(3)->point().z()),2); d23 = pow((cell->vertex(2)->point().x()-cell->vertex(3)->point().x()),2)+ pow((cell->vertex(2)->point().y()-cell->vertex(3)->point().y()),2)+ pow((cell->vertex(2)->point().z()-cell->vertex(3)->point().z()),2); //Radii of the particles r0 = sqrt(cell -> vertex(0) -> point().weight()); r1 = sqrt(cell -> vertex(1) -> point().weight()); r2 = sqrt(cell -> vertex(2) -> point().weight()); r3 = sqrt(cell -> vertex(3) -> point().weight()); //Fill coefficient matrix M(0,0) = 0.0; M(1,0) = d01; M(2,0) = d02; M(3,0) = d03; M(4,0) = pow((r0+Rin),2); M(5,0) = 1.0; M(0,1) = d01; M(1,1) = 0.0; M(2,1) = d12; M(3,1) = d13; M(4,1) = pow((r1+Rin),2); M(5,1) = 1.0; M(0,2) = d02; M(1,2) = d12; M(2,2) = 0.0; M(3,2) = d23; M(4,2) = pow((r2+Rin),2); M(5,2) = 1.0; M(0,3) = d03; M(1,3) = d13; M(2,3) = d23; M(3,3) = 0.0; M(4,3) = pow((r3+Rin),2); M(5,3) = 1.0; M(0,4) = pow((r0+Rin),2); M(1,4) = pow((r1+Rin),2); M(2,4) = pow((r2+Rin),2); M(3,4) = pow((r3+Rin),2); M(4,4) = 0.0; M(5,4) = 1.0; M(0,5) = 1.0; M(1,5) = 1.0; M(2,5) = 1.0; M(3,5) = 1.0; M(4,5) = 1.0; M(5,5) = 0.0; i = 0; check = false; dR = Rin = 0.0 + (min(r0,min(r1,min(r2,r3))) / 50.0); //Estimate an initial dR first = true; //Iterate untill check = true, such that an accurate answer as been found while (check == false){ i = i + 1; tempR = Rin; Rin = Rin + dR; M(4,0) = pow((r0+Rin),2); M(4,1) = pow((r1+Rin),2); M(4,2) = pow((r2+Rin),2); M(4,3) = pow((r3+Rin),2); M(0,4) = pow((r0+Rin),2); M(1,4) = pow((r1+Rin),2); M(2,4) = pow((r2+Rin),2); M(3,4) = pow((r3+Rin),2); if (first){ first = false; if(M.determinant() < 0.0){initialSign = false;} //Initial D is negative if(M.determinant() > 0.0){initialSign = true;} // Initial D is positive } if(std::abs(M.determinant()) < 1E-30){check = true;} if((initialSign==true) && (check ==false)){ if(M.determinant() < 0.0){ Rin = Rin -dR; dR = dR / 2.0; } } if((initialSign==false) && (check ==false)){ if(M.determinant() > 0.0){ Rin = Rin -dR; dR = dR / 2.0; } } if(solver->debugOut) {cout << endl << i << " "< 4000){ cout << endl << "error, finding solution takes too long cell:" << cell->info().id; check = true; } if ( std::abs(tempR - Rin)/Rin < 0.001){check = true;} } cell -> info().poreBodyRadius = Rin; } } void TwoPhaseFlowEngine::computePoreThroatRadiusMethod1() { RTriangulation& tri = solver->T[solver->currentTes].Triangulation(); FiniteCellsIterator cellEnd = tri.finite_cells_end(); CellHandle neighbourCell; for (FiniteCellsIterator cell = tri.finite_cells_begin(); cell != cellEnd; cell++) { for (int j=0; j<4; j++) { neighbourCell = cell->neighbor(j); if (!tri.is_infinite(neighbourCell)) { cell->info().poreThroatRadius[j]=computeEffPoreThroatRadius(cell, j); neighbourCell->info().poreThroatRadius[tri.mirror_index(cell, j)]= cell->info().poreThroatRadius[j];}}} } double TwoPhaseFlowEngine::computeEffPoreThroatRadius(CellHandle cell, int j) { double rInscribe = std::abs(solver->computeEffectiveRadius(cell, j)); CellHandle cellh = CellHandle(cell); int facetNFictious = solver->detectFacetFictiousVertices (cellh,j); double r; if(facetNFictious==0) {r=computeEffPoreThroatRadiusFine(cell,j);} else r=rInscribe; return r; } double TwoPhaseFlowEngine::computeEffPoreThroatRadiusFine(CellHandle cell, int j) { RTriangulation& tri = solver->T[solver->currentTes].Triangulation(); if (tri.is_infinite(cell->neighbor(j))) return 0; Vector3r pos[3]; //solid pos double r[3]; //solid radius for (int i=0; i<3; i++) { pos[i] = makeVector3r(cell->vertex(facetVertices[j][i])->point().point()); r[i] = sqrt(cell->vertex(facetVertices[j][i])->point().weight()); } return computeMSPRcByPosRadius(pos[0],r[0],pos[1],r[1],pos[2],r[2]); } double TwoPhaseFlowEngine::computeMSPRcByPosRadius(const Vector3r& posA, const double& rA, const Vector3r& posB, const double& rB, const Vector3r& posC, const double& rC) { double e[3]; //edges of triangulation double g[3]; //gap radius between solid e[0] = (posB-posC).norm(); e[1] = (posC-posA).norm(); e[2] = (posB-posA).norm(); g[0] = ((e[0]-rB-rC)>0) ? 0.5*(e[0]-rB-rC):0 ; g[1] = ((e[1]-rC-rA)>0) ? 0.5*(e[1]-rC-rA):0 ; g[2] = ((e[2]-rA-rB)>0) ? 0.5*(e[2]-rA-rB):0 ; double rmin= (std::max(g[0],std::max(g[1],g[2]))==0) ? 1.0e-11:std::max(g[0],std::max(g[1],g[2])) ; double rmax= computeEffRcByPosRadius(posA, rA, posB, rB, posC, rC); if(rmin>rmax) { cerr<<"WARNING! rmin>rmax. rmin="<vtkInfiniteVertices=solver->vtkInfiniteCells=0; FiniteCellsIterator cellEnd = Tri.finite_cells_end(); for (FiniteCellsIterator cell = Tri.finite_cells_begin(); cell != cellEnd; cell++) { bool isDrawable = cell->info().isReal() && cell->vertex(0)->info().isReal() && cell->vertex(1)->info().isReal() && cell->vertex(2)->info().isReal() && cell->vertex(3)->info().isReal(); if (!isDrawable) solver->vtkInfiniteCells+=1; } for (FiniteVerticesIterator v = Tri.finite_vertices_begin(); v != Tri.finite_vertices_end(); ++v) { if (!v->info().isReal()) solver->vtkInfiniteVertices+=1; else if (firstReal==-1) firstReal=solver->vtkInfiniteVertices;} basicVTKwritter vtkfile((unsigned int) Tri.number_of_vertices()-solver->vtkInfiniteVertices, (unsigned int) Tri.number_of_finite_cells()-solver->vtkInfiniteCells); vtkfile.open(filename,"test"); vtkfile.begin_vertices(); double x,y,z; for (FiniteVerticesIterator v = Tri.finite_vertices_begin(); v != Tri.finite_vertices_end(); ++v) { if (v->info().isReal()){ x = (double)(v->point().point()[0]); y = (double)(v->point().point()[1]); z = (double)(v->point().point()[2]); vtkfile.write_point(x,y,z);} } vtkfile.end_vertices(); vtkfile.begin_cells(); for (FiniteCellsIterator cell = Tri.finite_cells_begin(); cell != Tri.finite_cells_end(); ++cell) { bool isDrawable = cell->info().isReal() && cell->vertex(0)->info().isReal() && cell->vertex(1)->info().isReal() && cell->vertex(2)->info().isReal() && cell->vertex(3)->info().isReal(); if (isDrawable){vtkfile.write_cell(cell->vertex(0)->info().id()-firstReal, cell->vertex(1)->info().id()-firstReal, cell->vertex(2)->info().id()-firstReal, cell->vertex(3)->info().id()-firstReal);} } vtkfile.end_cells(); vtkfile.begin_data("Saturation",CELL_DATA,SCALARS,FLOAT); for (FiniteCellsIterator cell = Tri.finite_cells_begin(); cell != Tri.finite_cells_end(); ++cell) { bool isDrawable = cell->info().isReal() && cell->vertex(0)->info().isReal() && cell->vertex(1)->info().isReal() && cell->vertex(2)->info().isReal() && cell->vertex(3)->info().isReal(); if (isDrawable){vtkfile.write_data(cell->info().saturation);} } vtkfile.end_data(); vtkfile.begin_data("HasInterface",CELL_DATA,SCALARS,FLOAT); for (FiniteCellsIterator cell = Tri.finite_cells_begin(); cell != Tri.finite_cells_end(); ++cell) { bool isDrawable = cell->info().isReal() && cell->vertex(0)->info().isReal() && cell->vertex(1)->info().isReal() && cell->vertex(2)->info().isReal() && cell->vertex(3)->info().isReal(); if (isDrawable){vtkfile.write_data(cell->info().hasInterface);} } vtkfile.end_data(); vtkfile.begin_data("Pcondition",CELL_DATA,SCALARS,FLOAT); for (FiniteCellsIterator cell = Tri.finite_cells_begin(); cell != Tri.finite_cells_end(); ++cell) { bool isDrawable = cell->info().isReal() && cell->vertex(0)->info().isReal() && cell->vertex(1)->info().isReal() && cell->vertex(2)->info().isReal() && cell->vertex(3)->info().isReal(); if (isDrawable){vtkfile.write_data(cell->info().Pcondition);} } vtkfile.end_data(); vtkfile.begin_data("flux",CELL_DATA,SCALARS,FLOAT); for (FiniteCellsIterator cell = Tri.finite_cells_begin(); cell != Tri.finite_cells_end(); ++cell) { bool isDrawable = cell->info().isReal() && cell->vertex(0)->info().isReal() && cell->vertex(1)->info().isReal() && cell->vertex(2)->info().isReal() && cell->vertex(3)->info().isReal(); if (isDrawable){vtkfile.write_data(cell->info().flux);} } vtkfile.end_data(); vtkfile.begin_data("mergedID",CELL_DATA,SCALARS,FLOAT); for (FiniteCellsIterator cell = Tri.finite_cells_begin(); cell != Tri.finite_cells_end(); ++cell) { bool isDrawable = cell->info().isReal() && cell->vertex(0)->info().isReal() && cell->vertex(1)->info().isReal() && cell->vertex(2)->info().isReal() && cell->vertex(3)->info().isReal(); if (isDrawable){vtkfile.write_data(cell->info().mergedID);} } vtkfile.end_data(); vtkfile.begin_data("deltaVolume",CELL_DATA,SCALARS,FLOAT); for (FiniteCellsIterator cell = Tri.finite_cells_begin(); cell != Tri.finite_cells_end(); ++cell) { bool isDrawable = cell->info().isReal() && cell->vertex(0)->info().isReal() && cell->vertex(1)->info().isReal() && cell->vertex(2)->info().isReal() && cell->vertex(3)->info().isReal(); if (isDrawable){vtkfile.write_data(cell->info().accumulativeDV);} } vtkfile.end_data(); vtkfile.begin_data("Porosity",CELL_DATA,SCALARS,FLOAT); for (FiniteCellsIterator cell = Tri.finite_cells_begin(); cell != Tri.finite_cells_end(); ++cell) { bool isDrawable = cell->info().isReal() && cell->vertex(0)->info().isReal() && cell->vertex(1)->info().isReal() && cell->vertex(2)->info().isReal() && cell->vertex(3)->info().isReal(); if (isDrawable){vtkfile.write_data(cell->info().porosity);} } vtkfile.end_data(); vtkfile.begin_data("Label",CELL_DATA,SCALARS,FLOAT); for (FiniteCellsIterator cell = Tri.finite_cells_begin(); cell != Tri.finite_cells_end(); ++cell) { bool isDrawable = cell->info().isReal() && cell->vertex(0)->info().isReal() && cell->vertex(1)->info().isReal() && cell->vertex(2)->info().isReal() && cell->vertex(3)->info().isReal(); if (isDrawable){vtkfile.write_data(cell->info().label);} } vtkfile.end_data(); } void TwoPhaseFlowEngine::computePoreThroatRadiusTrickyMethod1() { computePoreThroatRadiusMethod1(); RTriangulation& tri = solver->T[solver->currentTes].Triangulation(); FiniteCellsIterator cellEnd = tri.finite_cells_end(); CellHandle neighbourCell; for (FiniteCellsIterator cell = tri.finite_cells_begin(); cell != cellEnd; cell++) { for (int j=0; j<4; j++) { neighbourCell = cell->neighbor(j); if(cell->info().isFictious && neighbourCell->info().isFictious) {cell->info().poreThroatRadius[j]=-1.0; neighbourCell->info().poreThroatRadius[tri.mirror_index(cell, j)]= cell->info().poreThroatRadius[j];} } } } void TwoPhaseFlowEngine::computeSolidLine() { RTriangulation& Tri = solver->T[solver->currentTes].Triangulation(); FiniteCellsIterator cellEnd = Tri.finite_cells_end(); for (FiniteCellsIterator cell = Tri.finite_cells_begin(); cell != cellEnd; cell++) { for(int j=0; j<4; j++) { solver->lineSolidPore(cell, j); } } if(solver->debugOut) {cout<<"----computeSolidLine-----."<pressureChanged=true; solver->reApplyBoundaryConditions(); ///keep boundingCells[2] as W-reservoir. for (FlowSolver::VCellIterator it = solver->boundingCells[2].begin(); it != solver->boundingCells[2].end(); it++) { (*it)->info().isWRes = true; (*it)->info().isNWRes = false; (*it)->info().saturation=1.0; } ///keep boundingCells[3] as NW-reservoir. for (FlowSolver::VCellIterator it = solver->boundingCells[3].begin(); it != solver->boundingCells[3].end(); it++) { (*it)->info().isNWRes = true; (*it)->info().isWRes = false; (*it)->info().saturation=0.0; } RTriangulation& tri = solver->T[solver->currentTes].Triangulation(); FiniteCellsIterator cellEnd = tri.finite_cells_end(); ///if we start from drainage if(drainageFirst) { for ( FiniteCellsIterator cell = tri.finite_cells_begin(); cell != cellEnd; cell++ ) { if (cell->info().Pcondition) continue; cell->info().p()=bndCondValue[2]; cell->info().isWRes = true; cell->info().isNWRes= false; cell->info().saturation=1.0; } } ///if we start from imbibition if(!drainageFirst) { for ( FiniteCellsIterator cell = tri.finite_cells_begin(); cell != cellEnd; cell++ ) { if (cell->info().Pcondition) continue; cell->info().p()=bndCondValue[3]; cell->info().isWRes = false; cell->info().isNWRes= true; cell->info().saturation=0.0; } } if(solver->debugOut) {cout<<"----initializeReservoirs----"<T[solver->currentTes].Triangulation(); FiniteCellsIterator cellEnd = tri.finite_cells_end(); for (FiniteCellsIterator cell = tri.finite_cells_begin(); cell != cellEnd; cell++) { if(cell->info().isGhost == false && cell->info().id < solver->T[solver->currentTes].cellHandles.size()){ filePoreBodyRadius << cell->info().poreBodyRadius << '\n'; filePoreBodyVolume << cell->info().poreBodyRadius << '\n'; CVector center ( 0,0,0 ); double count = 0.0; for ( int k=0;k<4;k++ ){ if(cell->vertex(k)->info().id() > 5){ center= center + (cell->vertex(k)->point().point()-CGAL::ORIGIN); count = count + 1.0; } } if(count != 0.0){center = center * (1./count);} fileLocation << center<< '\n'; for (unsigned int i=0;i<4;i++){ if(cell->neighbor(i)->info().isGhost == false && cell->neighbor(i)->info().id < solver->T[solver->currentTes].cellHandles.size() && (cell->info().id < cell->neighbor(i)->info().id)){ fileNeighbor << cell->neighbor(i)->info().id << '\n'; fileThroatRadius << cell->info().poreThroatRadius[i] << '\n'; fileThroats << cell->info().id << " " << cell->neighbor(i)->info().id << '\n'; const CVector& Surfk = cell->info().facetSurfaces[i]; Real area = sqrt(Surfk.squared_length()); fileThroatFluidArea << cell->info().facetFluidSurfacesRatio[i] * area << '\n'; fileHydraulicRadius << 2.0 * solver->computeHydraulicRadius(cell,i) << '\n'; fileConductivity << cell->info().kNorm()[i] << '\n'; } } if(cell->info().isFictious == 1 && cell->info().isGhost == false && cell->info().id < solver->T[solver->currentTes].cellHandles.size()){ //add boundary condition if(cell->info().isFictious == 1 &&(cell->vertex(0)->info().id() == 3 || cell->vertex(1)->info().id() == 3 || cell->vertex(2)->info().id() == 3 || cell->vertex(3)->info().id() == 3)){ filePoreBoundary << "3" << '\n'; } else if(cell->info().isFictious == 1 &&(cell->vertex(0)->info().id() == 2 || cell->vertex(1)->info().id() == 2 || cell->vertex(2)->info().id() == 2 || cell->vertex(3)->info().id() == 2)){ filePoreBoundary << "2" << '\n'; } else{filePoreBoundary << "2" << '\n';} } if(cell->info().isFictious == 0 && cell->info().isGhost == false && cell->info().id < solver->T[solver->currentTes].cellHandles.size()){ filePoreBoundary << "0" << '\n'; } } } fileThroatFluidArea.close(); fileHydraulicRadius.close(); fileConductivity.close(); filePoreBodyRadius.close(); filePoreBoundary.close(); filePoreBodyVolume.close(); fileLocation.close(); fileNeighbor.close(); fileThroatRadius.close(); } ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// //..............................................................Library............................................................// ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// double TwoPhaseFlowEngine::getKappa(int numberFacets) { if(numberFacets == 0){return 0; cout << endl << "Pore with zero throats? Check your data";} else{ double kappa = 0.0; if(numberFacets == 4){kappa = 3.8716;}//Tetrahedra else if(numberFacets == 6){kappa = 8.7067;}//Octahedra else if(numberFacets == 8){kappa = 6.7419;}//Cube else if(numberFacets == 10){kappa = 5.150;}//Octahedron + hexahedron else if(numberFacets == 12){kappa = 24.105;} //Icosahedra else if(numberFacets == 20){kappa = 22.866;} // Dodecahedron else{ kappa = 1.2591*float(numberFacets) - 1.1041;} //Other pore shapes return kappa; } } double TwoPhaseFlowEngine::getChi(int numberFacets) { if(numberFacets == 0){return 0; cout << endl << "Pore with zero throats? Check your data";} else{ double chi = 0.0; if(numberFacets == 4){chi = 0.416;}//Tetrahedra else if(numberFacets == 6){chi = 0.525;}//Octahedra else if(numberFacets == 8){chi = 0.500;}//Cube else if(numberFacets == 10){chi = 0.4396;}//Octahedron + hexahedron else if(numberFacets == 12){chi =0.583; } //Icosahedra else if(numberFacets == 20){chi = 0.565;} // Dodecahedron else{ chi = 0.0893*std::log(float(numberFacets))+0.326;}//Other pore shapes return chi; } } double TwoPhaseFlowEngine::getLambda(int numberFacets) { if(numberFacets == 0){return 0; cout << endl << "Pore with zero throats? Check your data";} else{ double lambda = 0.0; if(numberFacets == 4){lambda = 2.0396;}//Tetrahedra else if(numberFacets == 6){lambda = 1.2849;}//Octahedra else if(numberFacets == 8){lambda = 1;}//Cube else if(numberFacets == 10){lambda = 0.77102;}//Octahedron + hexahedron else if(numberFacets == 12){lambda =0.771025; } //Icosahedra else if(numberFacets == 20){lambda = 0.50722;} // Dodecahedron else{ lambda = 7.12*std::pow(numberFacets,-0.89);} //Other pore shapes return lambda; } } double TwoPhaseFlowEngine::getN(int numberFacets) { if(numberFacets == 0){return 0; cout << endl << "Pore with zero throats? Check your data";} else{ double n = 0.0; if(numberFacets == 4){n = 6.0;}//Tetrahedra else if(numberFacets == 6){n = 12.0;}//Octahedra else if(numberFacets == 8){n = 8.0;}//Cube else if(numberFacets == 10){n = 12.0; /*cout << endl << "number of edges requested for octa + hexahedron!";*/ }//Octahedron + hexahedron NOTE this should not be requested for calculations! else if(numberFacets == 12){n = 30.0; } //Icosahedra else if(numberFacets == 20){n = 30.0;} // Dodecahedron else{ n = 1.63 * double(numberFacets);} //Other pore shapes return n; } } double TwoPhaseFlowEngine::getDihedralAngle(int numberFacets) { //given in radians which is reported as tetha in manuscript Sweijen et al., if(numberFacets == 0){return 0; cout << endl << "Pore with zero throats? Check your data";} else{ double DihedralAngle = 0.0; if(numberFacets == 4){DihedralAngle = 1.0*std::atan(2.0*std::sqrt(2.0));}//Tetrahedra else if(numberFacets == 6){DihedralAngle = 1.0*std::acos(-1.0 / 3.0);}//Octahedra else if(numberFacets == 8){DihedralAngle = 0.5*3.1415926535;}//Cube else if(numberFacets == 10){DihedralAngle = (1./4.)*3.1415926535; /*cout << endl << "dihedral angle requested for octa + hexahedron!";*/ }//Octahedron + hexahedron NOTE this should not be requested for calculations! else if(numberFacets == 12){DihedralAngle = std::acos((-1.0 / 3.0)*std::sqrt(5.0)); } //Icosahedra else if(numberFacets == 20){DihedralAngle = std::acos((-1.0 / 5.0)*std::sqrt(5.0));} // Dodecahedron else{ DihedralAngle = (1./4.)*3.1415926535;} //Other pore shapes return DihedralAngle; } } double TwoPhaseFlowEngine::getConstantC3(CellHandle cell) { double c1 = 54.92*std::pow(double(cell->info().numberFacets),-1.14); if(cell->info().numberFacets == 4){c1 = 8.291;} if(cell->info().numberFacets == 6){c1 = 2.524;} if(cell->info().numberFacets == 8){c1 = 2.524;} if(cell->info().numberFacets == 10){c1 = 6.532;} if(cell->info().numberFacets == 12){c1 = 6.087;} if(cell->info().numberFacets == 20){c1 = 0.394;} double c3 = c1 * std::pow(2.0 * surfaceTension,3) / cell->info().mergedVolume; return c3; } double TwoPhaseFlowEngine::getConstantC4(CellHandle cell) { double c2 = 4.85*std::pow(double(cell->info().numberFacets),-1.19); if(cell->info().numberFacets == 4){c2 = 1.409;} if(cell->info().numberFacets == 6){c2 = 0.353;} if(cell->info().numberFacets == 8){c2 = 0.644;} if(cell->info().numberFacets == 10){c2 = 0.462 ;} if(cell->info().numberFacets == 12){c2 = 0.0989;} if(cell->info().numberFacets == 20){c2 = 0.245;} double c4 = c2 * std::pow(2.0 * surfaceTension,3) / std::pow( double (cell->info().mergedVolume),2./3.); return c4; } double TwoPhaseFlowEngine::dsdp(CellHandle cell, double pw) { if(pw == 0){std::cout << endl << "Error! water pressure is zero, while computing capillary pressure ... cellId= "<< cell->info().id;} double exp = std::exp(-1*getKappa(cell->info().numberFacets) * cell->info().saturation); double dsdp2 = (1.0 / cell->info().thresholdPressure) * std::pow((1.0 - exp),2.0) / ( getKappa(cell->info().numberFacets) * exp); // if(std::abs(dsdp2) > 1e10){ std::cerr << "Huge dsdp! : "<< dsdp2 << " " << exp << " "<< cell->info().thresholdPressure << " " << getKappa(cell->info().numberFacets);} // double dsdp2 = (3.0 * getConstantC3(cell) - 2.0 * getConstantC4(cell) * pw) / std::pow(pw,4); if(dsdp2 != dsdp2){std::cerr<info().saturation << " kappa:" <info().numberFacets) << " exp: " << exp << " mergedVolume=" << cell->info().mergedVolume << " pthreshold=" << cell->info().thresholdPressure;} if(dsdp2 < 0.0){std::cerr<1e6){std::cerr< cell->info().thresholdPressure){ s = std::log(1.0 + cell->info().thresholdPressure / pw) / (-1.0 * getKappa(cell->info().numberFacets)); } if(-1*pw == cell->info().thresholdPressure){ s = cell->info().thresholdSaturation; } if(-1*pw < cell->info().thresholdPressure){ if(!remesh && !firstDynTPF){std::cerr<info().thresholdPressure;} s = cell->info().thresholdSaturation; } if(s > 1.0 || s < 0.0){std::cout << "Error, saturation from Pc(S) curve is not correct: "<< s << " "<< cell->info().poreId << " log:" << std::log(1.0 + cell->info().thresholdPressure / pw)<< " " << (-1.0 * getKappa(cell->info().numberFacets)) << " pw=" << pw << " " << cell->info().thresholdPressure; s = 1.0;} if(s != s){std::cerr<info().saturation;} if(pw > 0){ std::cout << "Pw is above 0! - error: "<< pw << " id=" << cell->info().poreId << " pthr=" << cell->info().thresholdPressure << " sat:" << cell->info().saturation << " kappa: " << getKappa(cell->info().numberFacets) << " " << (1.0 - std::exp(-1*getKappa(cell->info().numberFacets) * cell->info().saturation)); pw = -1 * cell->info().thresholdPressure; } if(pw != pw){std::cout << "Non existing capillary pressure!";} // if(pw < 100 * waterBoundaryPressure){std::cout << "huge PC!" << pw << " saturation=" << cell->info().saturation << " hasIFace=" << cell->info().hasInterface << " NWRES=" << cell->info().isNWRes; pw = 100 * waterBoundaryPressure;} return pw; } ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// //....................................Merging of tetrahedra to find PUA............... ............................................// ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// void TwoPhaseFlowEngine::actionMergingAlgorithm() { //(1) merge tetrahedra together mergeCells(); //(2) count the facets and update the mergedVolume countFacets(); computeMergedVolumes(); //(3) resolve unsolved pore throats that are too big adjustUnresolvedPoreThroatsAfterMerging(); //(4) export statistics on the merging algorithm getMergedCellStats(); //(5) check for volume conservation checkVolumeConservationAfterMergingAlgorithm(); } void TwoPhaseFlowEngine::mergeCells() { //This function finds the tetrahedra that belong together (i.e. pore throat is too big compared to pore body) //Start with worst case scenario, Rij / Ri > 200 (defined as criterion)towards maximumRatioPoreThroatoverPoreBody. //If Rij/Ri is too large, than tetrahedra are merged. Then the merged volume and nrFacets is updated. //When checkign a subsequent criterion, the updated values of merged volume and nr of facets is used. //The remaining unsolved Rij/Ri ratios are fixed later in the program. //A limitation of max 20 merged tetrahedra is used to prevent huge pores. int number = 1, ID = 0; double dC = 0.0, criterion = 200.0; bool check = false; RTriangulation& tri = solver->T[solver->currentTes].Triangulation(); FiniteCellsIterator cellEnd = tri.finite_cells_end(); maxIDMergedCells = 0; //Initialize Merged Volumes for (FiniteCellsIterator cell = tri.finite_cells_begin(); cell != cellEnd; cell++){ cell->info().mergedVolume = cell->info().poreBodyVolume; cell->info().mergednr = 1; cell->info().mergedID = 0; cell->info().numberFacets = 4; } for (unsigned int i=0; i< 110; i++){ if(i < 10){dC = (200.0 - 50.0) / 9.0;} if(i >=10){dC = (50.0 - maximumRatioPoreThroatoverPoreBody) / 100.0;} if(i == 0){dC = 0.0;} //Decrease the criteria for throat over body radius, so first merge the worst case scenarios. criterion = criterion - dC; if(debugTPF){cout << endl << "criterion=" << criterion;} for (unsigned int j = 0;j<5;j++){ for (FiniteCellsIterator cell = tri.finite_cells_begin(); cell != cellEnd; cell++){ if(cell->info().isGhost == false && cell->info().mergedID T[solver->currentTes].cellHandles.size() && cell->info().isFictious == false && cell->info().mergednr < 20) { for(unsigned int ngb=0;ngb<4;ngb++) { if(cell->neighbor(ngb)->info().mergednr < 20){ if(cell->neighbor(ngb)->info().isGhost == false && cell->neighbor(ngb)->info().mergedID T[solver->currentTes].cellHandles.size() && cell->neighbor(ngb)->info().isFictious == false && ((cell->info().mergedID == cell->neighbor(ngb)->info().mergedID && cell->info().mergedID != 0)==false)){ if((cell->info().poreThroatRadius[ngb] / ( getChi(cell->info().numberFacets) * std::pow(cell->info().mergedVolume,(1./3.)))) > criterion) { if(cell->info().mergedID == 0 && cell->neighbor(ngb)->info().mergedID == 0) { cell->info().mergedID = number; cell->neighbor(ngb)->info().mergedID = number; number = number + 1; countFacets(); computeMergedVolumes(); } else if(cell->info().mergedID == 0 && cell->neighbor(ngb)->info().mergedID != 0) { cell->info().mergedID = cell->neighbor(ngb)->info().mergedID; countFacets(); computeMergedVolumes(); } else if(cell->info().mergedID != 0 && cell->neighbor(ngb)->info().mergedID == 0) { cell->neighbor(ngb)->info().mergedID = cell->info().mergedID; countFacets(); computeMergedVolumes(); } } } } } } } countFacets(); computeMergedVolumes(); } } maxIDMergedCells = number; // RENUMBER THE MERGED CELLS for(unsigned int k=1;kinfo().mergedID == k){check = true;} } if(check){ ID = ID + 1; for (FiniteCellsIterator cell = tri.finite_cells_begin(); cell != cellEnd; cell++){ if(cell->info().mergedID == k){ cell->info().mergedID = ID; } } } } maxIDMergedCells = ID + 1; if(debugTPF){cout << endl << "EFFICIENT - RENUMBER MERGEDCELLS -- FROM: " << number << " TO: " << ID + 1;} for (FiniteCellsIterator cell = tri.finite_cells_begin(); cell != cellEnd; cell++){ cell->info().mergedVolume = cell->info().poreBodyVolume; } } void TwoPhaseFlowEngine::computeMergedVolumes() { RTriangulation& tri = solver->T[solver->currentTes].Triangulation(); FiniteCellsIterator cellEnd = tri.finite_cells_end(); double volume = 0.0, summ = 0.0; for (unsigned int mergeID = 1; mergeID < maxIDMergedCells; mergeID++){ volume = 0.0; summ = 0.0; for (FiniteCellsIterator Mergecell = tri.finite_cells_begin(); Mergecell != cellEnd; Mergecell++){ if(Mergecell->info().mergedID == mergeID && Mergecell->info().isFictious == false && Mergecell->info().isGhost == false && Mergecell->info().id < solver->T[solver->currentTes].cellHandles.size()){ volume = volume + Mergecell->info().poreBodyVolume; summ = summ + 1.0; } } if(summ > 1.0){ for (FiniteCellsIterator Mergecell = tri.finite_cells_begin(); Mergecell != cellEnd; Mergecell++){ if(Mergecell->info().mergedID == mergeID && Mergecell->info().isFictious == false && Mergecell->info().isGhost == false && Mergecell->info().id < solver->T[solver->currentTes].cellHandles.size()){ Mergecell->info().poreBodyRadius = getChi(Mergecell->info().numberFacets)*std::pow(volume,(1./3.)); Mergecell->info().mergedVolume = volume; Mergecell->info().mergednr = summ; } } } if(summ <= 1.0){ for (FiniteCellsIterator Mergecell = tri.finite_cells_begin(); Mergecell != cellEnd; Mergecell++){ if(Mergecell->info().mergedID == mergeID && Mergecell->info().isFictious == false && Mergecell->info().isGhost == false && Mergecell->info().id < solver->T[solver->currentTes].cellHandles.size()){ cout << endl << "isMerged set to -1: " << Mergecell->info().id << " "<< Mergecell->info().poreBodyRadius << " " << Mergecell->info().poreThroatRadius[0] << " " << Mergecell->info().poreThroatRadius[1]<<" " << Mergecell->info().poreThroatRadius[2]<<" " << Mergecell->info().poreThroatRadius[3]; Mergecell->info().mergednr = 1; Mergecell->info().mergedID = 0; } } } } } void TwoPhaseFlowEngine::countFacets() { RTriangulation& tri = solver->T[solver->currentTes].Triangulation(); FiniteCellsIterator cellEnd = tri.finite_cells_end(); int summngb = 0; for(unsigned int k=1;kinfo().mergedID == k && cell->info().isGhost == false && (cell->info().isFictious == false) && (cell->info().id < solver->T[solver->currentTes].cellHandles.size())){ for(unsigned int i=0;i<4;i++){ if(cell->neighbor(i)->info().mergedID != cell->info().mergedID && cell->neighbor(i)->info().isGhost == false && (cell->neighbor(i)->info().isFictious == false) && (cell->neighbor(i)->info().id < solver->T[solver->currentTes].cellHandles.size())){ summngb = summngb + 1; } } } } for (FiniteCellsIterator cell = tri.finite_cells_begin(); cell != cellEnd; cell++){ if(cell->info().mergedID == k){ if(summngb < 4){summngb = 4;} //Less than 4 throats is not supported -> boundary problems. cell->info().numberFacets = summngb; } } } } void TwoPhaseFlowEngine::getMergedCellStats() { std::array countFacets = {0}; std::array countMergedNR = {0}; int count = 0, countTot = 0; RTriangulation& tri = solver->T[solver->currentTes].Triangulation(); FiniteCellsIterator cellEnd = tri.finite_cells_end(); //In this function the amount of tetrahedra per pore is counted and reported in the terminal. std::string NameDistributionInFacets = modelRunName; std::string NamedistibutionInMergedPoreUnits = modelRunName; NameDistributionInFacets.append("-distributionInFacets.txt"); NamedistibutionInMergedPoreUnits.append("-distributionInMergedPoreUnits.txt"); std::ofstream distributionInFacets; distributionInFacets.open(NameDistributionInFacets, std::ios::trunc); if(!distributionInFacets.is_open()){ std::cerr<< "Error opening file [" << "PoreBodyRadius" << ']' << std::endl; return; } std::ofstream distibutionInMergedPoreUnits; distibutionInMergedPoreUnits.open(NamedistibutionInMergedPoreUnits , std::ios::trunc); if(!distibutionInMergedPoreUnits.is_open()){ std::cerr<< "Error opening file [" << "PoreBoundary" << ']' << std::endl; return; } distributionInFacets << "The distribution in the number of pore throats per pore unit - table shows in the first column the number of pore throats and in the second column the total count"<<'\n'; distibutionInMergedPoreUnits << "The distribution in the number of tetrahedra per merged pore unit - table shows in the first column the number of merged tetrahedra and in the second column the total count"<<'\n'; countTot = 0; count = 0; for (FiniteCellsIterator cell = tri.finite_cells_begin(); cell != cellEnd; cell++){ if(cell->info().isFictious == false && cell->info().isGhost == false && cell->info().id < solver->T[solver->currentTes].cellHandles.size()){ if(cell->info().numberFacets == 4){count = count + 1;} countTot = countTot + 1; } } if(debugTPF){cout << endl << "Number of merged cells is:"<< count << "of the total number" << countTot <<" which is: "<< (float(count) * 100.0 / float(countTot));} for (FiniteCellsIterator cell = tri.finite_cells_begin(); cell != cellEnd; cell++){ if(cell->info().isFictious == false && cell->info().isGhost == false && cell->info().id < solver->T[solver->currentTes].cellHandles.size()) { if(cell->info().numberFacets < 30){ countFacets[cell->info().numberFacets - 4] = countFacets[cell->info().numberFacets - 4] + (1.0 / float(cell->info().mergednr)); } } } for (FiniteCellsIterator cell = tri.finite_cells_begin(); cell != cellEnd; cell++){ if(cell->info().isFictious == false && cell->info().isGhost == false && cell->info().id < solver->T[solver->currentTes].cellHandles.size()) { if(cell->info().mergednr < 30){ countMergedNR[cell->info().mergednr - 1] = countMergedNR[cell->info().mergednr - 1] + (1.0 / float(cell->info().mergednr)); } } } for (unsigned int i = 0; i< countFacets.size(); i++){ if(debugTPF){cout << endl << "nrFacets: "<< (i + 4) << "-count:" << countFacets[i];} distributionInFacets << (i+4) << " " << countFacets[i] << '\n'; } for (unsigned int i = 0; i< countMergedNR.size(); i++){ if(debugTPF){cout << endl << "nrMergedUnits: "<< i+1 << "-count:" << countMergedNR[i];} distibutionInMergedPoreUnits << (i+1) << " " << countMergedNR[i] << '\n'; } distributionInFacets.close(); distibutionInMergedPoreUnits.close(); } void TwoPhaseFlowEngine::adjustUnresolvedPoreThroatsAfterMerging() { //Adjust the remaining pore throats, such that all throats are smaller than the pore units. RTriangulation& tri = solver->T[solver->currentTes].Triangulation(); FiniteCellsIterator cellEnd = tri.finite_cells_end(); int count = 0, countTot = 0; for (unsigned int p = 0; p < 5; p++){ countTot = 0; count = 0; for (FiniteCellsIterator cell = tri.finite_cells_begin(); cell != cellEnd; cell++){ if(cell->info().isGhost == false && cell->info().isFictious == false){ for(unsigned int i = 0; i < 4; i++){ if((cell->info().mergedID != cell->neighbor(i)->info().mergedID || (cell->info().mergedID == 0 && cell->neighbor(i)->info().mergedID == 0)) && cell->neighbor(i)->info().isGhost == false /*&& cell->neighbor(i)->info().mergedID < solver->T[solver->currentTes].cellHandles.size()*/){ countTot = countTot + 1; if(cell->info().poreThroatRadius[i] >= maximumRatioPoreThroatoverPoreBody * (getChi(cell->info().numberFacets) * std::pow(cell->info().mergedVolume,(1./3.)))){ // if throat is larger than maximumRatioPoreThroatoverPoreBody time the pore body volume, then adjust pore throat radii count = count + 1; cell->info().poreThroatRadius[i] = std::min((maximumRatioPoreThroatoverPoreBody * getChi(cell->info().numberFacets) * std::pow(cell->info().mergedVolume,(1./3.))), cell->neighbor(i)->info().poreThroatRadius[i]); } } } } } if(debugTPF){cout << endl << "Total nr Throats = " << countTot << "total throats that are too large: "<< count << "that is : "<< (float(count) * 100.0 / float(countTot)) << "%";} if((float(count) / float(countTot)) > 0.1){cout << endl << "Error! Too many pore throats have been adjusted, more than 10%. Simulation is stopped" << count << " tot:" << countTot; /*stopSimulation = true;*/} } } void TwoPhaseFlowEngine::checkVolumeConservationAfterMergingAlgorithm() { //Check volume of the merging of pores, especially required for truncated pore shapes. RTriangulation& tri = solver->T[solver->currentTes].Triangulation(); FiniteCellsIterator cellEnd = tri.finite_cells_end(); double volumeSingleCells = 0.0, volumeTotal = 0.0, volumeMergedCells = 0.0; bool check = false; for (FiniteCellsIterator cell = tri.finite_cells_begin(); cell != cellEnd; cell++){ if(cell->info().isFictious == 0){ volumeTotal = volumeTotal + cell->info().poreBodyVolume; if(cell->info().mergedID == 0){volumeSingleCells = volumeSingleCells + cell->info().poreBodyVolume;} } } for(unsigned int k=1;kinfo().mergedID == k)&&(check == false)){ volumeMergedCells = volumeMergedCells + cell->info().mergedVolume; check = true; } } } //if volume is not conserved give error message if( std::abs((volumeTotal - volumeMergedCells - volumeSingleCells) / volumeTotal) > 1e-6){ std::cerr << endl << "Error! Volume of pores is not conserved between merged pores and total pores: "<< "Total pore volume = " << volumeTotal<< "Volume of merged cells = "<info().entrySaturation[ngb] << " " << cell->info().poreThroatRadius[ngb] << " and " <info().poreBodyRadius << " " << getKappa(cell->info().numberFacets) << " " <info().poreThroatRadius[ngb] / (cell->info().poreBodyRadius * entryMethodCorrection))) << " CellID=" << cell->info().id << " MergedID ="<< cell->info().mergedID << " Facets=" << cell->info().numberFacets; cout << endl << "Simulation is terminated because of an error in entry saturation"; stopSimulation = true; } } //Option (2): Throat radii bigger than pore body radii (this is an error). if((cell->info().poreThroatRadius[ngb] >= cell->info().poreBodyRadius)&&(cell->neighbor(ngb)->info().isFictious == 0)&&(cell->info().mergedID != cell->neighbor(ngb)->info().mergedID)){ cout << endl << "Error, throat radius is larger than the pore body radius for a merged pores: " << cell->info().id << " MergedID" << cell->info().mergedID << " ThroatRadius: "<< cell->info().poreThroatRadius[ngb] << " BodyRadius: "<info().poreBodyRadius << " nr facets = "<< cell->info().numberFacets; cout << endl << "Simulation is terminated because of an pore throat is larger than pore body!"; stopSimulation = true; cell->info().entrySaturation[ngb] = 1.0; cell->info().entryPressure[ngb] = 0.0; } //Option (3): Two tetrahedra from the same pore body, thus an artificial pore throat that is deactivated here. if(cell->info().mergedID == cell->neighbor(ngb)->info().mergedID){ cell->info().entrySaturation[ngb] = 1.0; cell->info().entryPressure[ngb] = 0.0; } //Option (4): Neighboring Tetrahedron is a boundary cell. if(cell->neighbor(ngb)->info().isFictious == true){ cell->info().entrySaturation[ngb] = 1.0; cell->info().entryPressure[ngb] = 0.0; } } } //check all the non-merged pores if(cell->info().mergedID == 0){ for(unsigned vert = 0; vert < 4; vert++){ //Deactive all boundary cells - which are infact individual tetrahedra. if(cell->neighbor(vert)->info().isFictious == 1){cell->info().entrySaturation[vert] = 1.0;cell->info().entryPressure[vert] = 0.0;} if(cell->neighbor(vert)->info().isFictious == 0){ //calculate the different entry pressures and so on. cell->info().entrySaturation[vert] = std::log(1.0-(2.0 * cell->info().poreThroatRadius[vert] / (cell->info().poreBodyRadius * entryMethodCorrection))) / (-1.0*getKappa(cell->info().numberFacets)); cell->info().entryPressure[vert] = entryMethodCorrection*surfaceTension / cell->info().poreThroatRadius[vert]; if((cell->info().entrySaturation[vert] > 1.0 && !cell->info().isFictious) || ((cell->info().entrySaturation[vert] < 0.0))){ cout << endl << "entry saturation error!" << cell->info().entrySaturation[vert] << " "<< cell->info().id << " " << cell->info().poreBodyRadius << " "<< cell->info().poreThroatRadius[vert] ;cell->info().entrySaturation[vert] = 1.0; cout << endl << "Simulation is terminated because of an error in entry saturation!"; stopSimulation = true; } } } } } } ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// //....................................Triangulation while maintaining saturation field ............................................// ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// void TwoPhaseFlowEngine::reTriangulate() { //Governing function to apply triangulation while maintaining saturation distribution. if(debugTPF){std::cerr << endl << "Apply retriangulation";} initializationTriangulation(); readTriangulation(); keepTriangulation = false; initialization(); assignWaterVolumesTriangulation(); actionMergingAlgorithm(); equalizeSaturationOverMergedCells(); } void TwoPhaseFlowEngine::initializationTriangulation() { //Resize all relevant functions //per sphere leftOverVolumePerSphere.resize(scene->bodies->size(),0); untreatedAreaPerSphere.resize(scene->bodies->size(),0); leftOverDVPerSphere.resize(scene->bodies->size(),0); //per tetrahedra finishedUpdating.resize(solver->T[solver->currentTes].cellHandles.size(),0); waterVolume.resize(solver->T[solver->currentTes].cellHandles.size(),0); deltaVoidVolume.resize(solver->T[solver->currentTes].cellHandles.size(),0); tetrahedra.resize(solver->T[solver->currentTes].cellHandles.size()); solidFractionSpPerTet.resize(solver->T[solver->currentTes].cellHandles.size()); for(unsigned int i =0; iT[solver->currentTes].cellHandles.size(); i++){ tetrahedra[i].resize(4,0); solidFractionSpPerTet[i].resize(4,0); } } void TwoPhaseFlowEngine::readTriangulation() { //Read all relevant information from old assembly of tetrahedra for (unsigned int i=0;ibodies->size();i++){ untreatedAreaPerSphere[i] = 0.0; leftOverVolumePerSphere[i] = 0.0; leftOverDVPerSphere[i] = 0.0; } for (unsigned int i=0;iT[solver->currentTes].cellHandles.size();i++){ tetrahedra[i][0] = tetrahedra[i][1] = tetrahedra[i][2] = tetrahedra[i][3] = 1e6; solidFractionSpPerTet[i][0] = solidFractionSpPerTet[i][1] = solidFractionSpPerTet[i][2] = solidFractionSpPerTet[i][3] = 0.0; waterVolume[i] = 0.0; deltaVoidVolume[i] = 0.0; finishedUpdating[i] = 0; } RTriangulation& tri = solver->T[solver->currentTes].Triangulation(); FiniteCellsIterator cellEnd = tri.finite_cells_end(); for (FiniteCellsIterator cell = tri.finite_cells_begin(); cell != cellEnd; cell++) { waterVolume[cell->info().id] = cell->info().saturation * cell->info().poreBodyVolume; deltaVoidVolume[cell->info().id] = cell->info().dv(); if(cell->info().isFictious){finishedUpdating[cell->info().id] = -1;} if(!cell->info().isFictious){ std::pair pairs[4]; for (unsigned int i = 0;i<4;i++){ pairs[i] = std::make_pair(cell->vertex(i)->info().id(),std::abs(solver->fractionalSolidArea(cell,i))); } sort(std::begin(pairs),std::end(pairs)); for(unsigned int j=0;j<4;j++){ tetrahedra[cell->info().id][j] = pairs[j].first; solidFractionSpPerTet[cell->info().id][j] = pairs[j].second; } } } } void TwoPhaseFlowEngine::assignWaterVolumesTriangulation() { //Assign saturation to new assembly of tetrahedra RTriangulation& tri = solver->T[solver->currentTes].Triangulation(); FiniteCellsIterator cellEnd = tri.finite_cells_end(); unsigned int saveID = 1e6; static unsigned int index = waterVolume.size(); for (FiniteCellsIterator cell = tri.finite_cells_begin(); cell != cellEnd; cell++) { if(!cell->info().isFictious){ saveID = 1e6; unsigned int vert[4] = {cell->vertex(0)->info().id(),cell->vertex(1)->info().id(),cell->vertex(2)->info().id(),cell->vertex(3)->info().id()}; std::sort(std::begin(vert),std::end(vert)); for(unsigned int i=0;iinfo().saturation = waterVolume[saveID] / cell->info().poreBodyVolume; cell->info().dv() = deltaVoidVolume[saveID]; if(cell->info().saturation < 0.0){std::cout << endl << "Negative Sat in subFunction1 :" << cell->info().saturation << " " << waterVolume[saveID] << " " << cell->info().poreBodyVolume;} finishedUpdating[saveID] = 1; } if(saveID == 1e6){ cell->info().saturation = -1; for(unsigned int i=0;i<4;i++){ untreatedAreaPerSphere[cell->vertex(i)->info().id()] += std::abs(solver->fractionalSolidArea(cell,i)); } } } } for(unsigned int i=0;iinfo().saturation == -1){ double vol = 0.0, dv = 0.0; for(unsigned int j=0;j<4;j++){ vol += leftOverVolumePerSphere[cell->vertex(j)->info().id()] * (std::abs(solver->fractionalSolidArea(cell,j)) / untreatedAreaPerSphere[cell->vertex(j)->info().id()]); dv += leftOverDVPerSphere[cell->vertex(j)->info().id()] * (std::abs(solver->fractionalSolidArea(cell,j)) / untreatedAreaPerSphere[cell->vertex(j)->info().id()]); } cell->info().saturation = vol / cell->info().poreBodyVolume; cell->info().dv() = dv; if(cell->info().saturation < 0.0){std::cout << endl << "Error! Negative Sat in sphere allocation: " << cell->info().saturation << " " << vol << " " << cell->info().poreBodyVolume;} } } } void TwoPhaseFlowEngine::equalizeSaturationOverMergedCells() { double waterVolume = 0.0, volume = 0.0, leftOverVolume = 0.0, workVolume = 0.0; RTriangulation& tri = solver->T[solver->currentTes].Triangulation(); FiniteCellsIterator cellEnd = tri.finite_cells_end(); for (FiniteCellsIterator cell = tri.finite_cells_begin(); cell != cellEnd; cell++) { if(cell->info().saturation < 0.0){std::cout << endl << "Error! Before Starting! Negative sat! ... " << cell->info().saturation;} } for(unsigned int k=1;kinfo().mergedID == k){ waterVolume += cell->info().saturation * cell->info().poreBodyVolume; volume = cell->info().mergedVolume; } } if(waterVolume > volume){ leftOverVolume = waterVolume - volume; waterVolume = volume; } if(leftOverVolume > 0.0){ for (FiniteCellsIterator cell = tri.finite_cells_begin(); cell != cellEnd; cell++){ if(cell->info().mergedID == k && leftOverVolume > 0.0){ for(unsigned int j = 0; j<4; j++){ if(cell->info().mergedID != cell->neighbor(j)->info().mergedID && cell->neighbor(j)->info().saturation < 1.0 && !cell->neighbor(j)->info().isFictious && leftOverVolume > 0.0){ workVolume = (1.0 - cell->neighbor(j)->info().saturation) * cell->neighbor(j)->info().poreBodyVolume; std::cout << endl << workVolume << " " << leftOverVolume << " " << cell->neighbor(j)->info().saturation; if(workVolume <= leftOverVolume){ leftOverVolume -= workVolume; cell->neighbor(j)->info().saturation = 1.0; std::cout << "inOne"; } std::cout << endl << workVolume << " " << leftOverVolume << " " << cell->neighbor(j)->info().saturation ; if(workVolume > leftOverVolume && leftOverVolume > 0.0){ cell->neighbor(j)->info().saturation = (leftOverVolume + cell->neighbor(j)->info().saturation * cell->neighbor(j)->info().poreBodyVolume) / cell->neighbor(j)->info().poreBodyVolume; leftOverVolume = 0.0; std::cout << "inOne"; } std::cout << endl << workVolume << " " << leftOverVolume << " " << cell->neighbor(j)->info().saturation ; } } } } if(leftOverVolume > 0.0){std::cout << endl << "Error! Left over water volume: " << leftOverVolume;} } for (FiniteCellsIterator cell = tri.finite_cells_begin(); cell != cellEnd; cell++) { if(cell->info().mergedID == k){ cell->info().saturation = waterVolume / cell->info().mergedVolume; } } } for (FiniteCellsIterator cell = tri.finite_cells_begin(); cell != cellEnd; cell++){ if(!cell->info().isFictious && cell->info().saturation > 1.0 && cell->info().mergedID == 0){ leftOverVolume = (cell->info().saturation - 1.0) * cell->info().poreBodyVolume; cell->info().saturation = 1.0; for(unsigned int j=0;j<4;j++){ if(cell->neighbor(j)->info().saturation < 1.0 && !cell->neighbor(j)->info().isFictious && leftOverVolume > 0.0){ workVolume = (1.0 - cell->neighbor(j)->info().saturation) * cell->neighbor(j)->info().poreBodyVolume; if(workVolume <= leftOverVolume){ leftOverVolume -= workVolume; cell->neighbor(j)->info().saturation = 1.0; std::cout << "inOne-sec"; } std::cout << endl << " sec-" << workVolume << " " << leftOverVolume << " " << cell->neighbor(j)->info().saturation ; if(workVolume > leftOverVolume && leftOverVolume > 0.0){ cell->neighbor(j)->info().saturation = (leftOverVolume + cell->neighbor(j)->info().saturation * cell->neighbor(j)->info().poreBodyVolume) / cell->neighbor(j)->info().poreBodyVolume; leftOverVolume = 0.0; std::cout << "inOne-sec"; } } } if(leftOverVolume > 0.0){std::cout << "Mass left during remeshing" << leftOverVolume;} } } bool redo = false; for (FiniteCellsIterator cell = tri.finite_cells_begin(); cell != cellEnd; cell++){ if(cell->info().saturation > 1.0){ redo = true; } } if(redo){std::cout<< "redo calculation"; equalizeSaturationOverMergedCells();} } ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// //....................................Set pore network from PUA....................... ............................................// ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// void TwoPhaseFlowEngine::setInitialConditions() { if(debugTPF){std::cerr<T[solver->currentTes].Triangulation(); FiniteCellsIterator cellEnd = tri.finite_cells_end(); for (FiniteCellsIterator cell = tri.finite_cells_begin(); cell != cellEnd; cell++) { //make backup of saturated hydraulic conductivity for(unsigned int ngb = 0; ngb<4; ngb++){ cell->info().kNorm2[ngb] = cell->info().kNorm()[ngb]; } cell->info().isFictiousId = -1; cell->info().isWRes = false; cell->info().isNWRes = false; if(cell->info().isFictious){ //boundary cells are not used here cell->info().p() = 0.0; cell->info().saturation = 1.0; cell->info().hasInterface = false; } if(!cell->info().isFictious){ //Primary drainage if(drainageFirst && primaryTPF){ cell->info().p() = -1 * initialPC; cell->info().saturation = 1.0; cell->info().hasInterface = false; } //Secondary drainage (using saturation field as input parameter) if(drainageFirst && !primaryTPF){ cell->info().p() = -1 * initialPC; if(cell->info().saturation <= cell->info().thresholdSaturation){ cell->info().p() = porePressureFromPcS(cell,cell->info().saturation); cell->info().hasInterface = true; } if(cell->info().saturation > cell->info().thresholdSaturation){ cell->info().p() = -1*initialPC; cell->info().saturation = 1.0; cell->info().hasInterface = false; std::cerr << "Warning: local saturation changed for compatibility of local Pc(S)"; } } //Primary imbibition if(!drainageFirst && primaryTPF){ cell->info().p() = -1 * initialPC; cell->info().saturation = poreSaturationFromPcS(cell,-1*initialPC); cell->info().hasInterface = true; //FIXME: hasInterface should be false, but until an imbibition criteria is implementend into solvePressure, this should remain true for testing purposes } //Secondary imbibition if(!drainageFirst && !primaryTPF){ cell->info().p() = -1 * initialPC; if(cell->info().saturation <= cell->info().thresholdSaturation){ cell->info().p() = porePressureFromPcS(cell,cell->info().saturation); cell->info().hasInterface = true; } if(cell->info().saturation > cell->info().thresholdSaturation){ cell->info().p() = -1*initialPC; cell->info().saturation = 1.0; cell->info().hasInterface = false; std::cerr << "Warning: local saturation changed for compatibility of local Pc(S)"; } } } } } void TwoPhaseFlowEngine::transferConditions() { RTriangulation& tri = solver->T[solver->currentTes].Triangulation(); FiniteCellsIterator cellEnd = tri.finite_cells_end(); for (FiniteCellsIterator cell = tri.finite_cells_begin(); cell != cellEnd; cell++) { for(unsigned int ngb = 0; ngb<4; ngb++){ cell->info().kNorm2[ngb] = cell->info().kNorm()[ngb]; } if(cell->info().saturation == 1.0){ cell->info().hasInterface = false; } if(cell->info().saturation < 1.0){ cell->info().hasInterface = true; cell->info().p() = porePressureFromPcS(cell,cell->info().saturation); } } } void TwoPhaseFlowEngine::setBoundaryConditions() { RTriangulation& tri = solver->T[solver->currentTes].Triangulation(); FiniteCellsIterator cellEnd = tri.finite_cells_end(); for (FiniteCellsIterator cell = tri.finite_cells_begin(); cell != cellEnd; cell++) { if(cell->info().isFictious){ for(unsigned int j=0;j<4;j++){ for(unsigned int i=0;i<6;i++){ if(cell->vertex(j)->info().id() == i) { cell->info().isFictiousId = i; if(bndCondIsPressure[cell->info().isFictiousId] && bndCondIsWaterReservoir[cell->info().isFictiousId] ){ cell->info().p() = bndCondValue[cell->info().isFictiousId]; cell->info().isWRes = true; waterBoundaryPressure = bndCondValue[cell->info().isFictiousId]; } if(bndCondIsPressure[cell->info().isFictiousId] && !bndCondIsWaterReservoir[cell->info().isFictiousId]){ cell->info().p() = bndCondValue[cell->info().isFictiousId]; cell->info().isNWRes = true; airBoundaryPressure = bndCondValue[cell->info().isFictiousId]; } } } } } } for (FiniteCellsIterator cell = tri.finite_cells_begin(); cell != cellEnd; cell++) { //set initial interface in simulations //Drainage if(drainageFirst && cell->info().isNWRes){ for (unsigned int i = 0; i<4; i++){ if(!cell->neighbor(i)->info().isFictious){ if(!deformation){ cell->neighbor(i)->info().hasInterface = true; cell->neighbor(i)->info().saturation = poreSaturationFromPcS(cell->neighbor(i),-1*initialPC); cell->neighbor(i)->info().p() = -1*initialPC; cell->neighbor(i)->info().airBC = true; if(cell->neighbor(i)->info().saturation != cell->neighbor(i)->info().saturation || cell->neighbor(i)->info().saturation > 1.0 || cell->neighbor(i)->info().saturation < 0.0){ std::cout << "Error with initial BC saturation: " << cell->neighbor(i)->info().saturation; } } if(deformation){ cell->neighbor(i)->info().hasInterface = true; cell->neighbor(i)->info().p() = -1*initialPC; cell->neighbor(i)->info().saturation = poreSaturationFromPcS(cell->neighbor(i),-1 * initialPC); } //Update properties of other cells in pore unit if(cell->neighbor(i)->info().mergedID > 0){ for (FiniteCellsIterator Mcell = tri.finite_cells_begin(); Mcell != cellEnd; Mcell++) { if(Mcell->info().mergedID == cell->neighbor(i)->info().mergedID){ Mcell->info().hasInterface = cell->neighbor(i)->info().hasInterface; Mcell->info().saturation = cell->neighbor(i)->info().saturation; Mcell->info().p() = cell->neighbor(i)->info().p(); Mcell->info().isNWRes = cell->neighbor(i)->info().isNWRes; } } } } } } //Imbibition FIXME(thomas): Needs to be tested for both rigid packings and deforming packings if(!drainageFirst){ for (unsigned int i = 0; i<4; i++){ if(cell->info().isNWRes){ cell->neighbor(i)->info().airBC = true; } if(cell->info().isWRes){ cell->neighbor(i)->info().hasInterface = true; cell->neighbor(i)->info().saturation = poreSaturationFromPcS(cell->neighbor(i),-1*initialPC); cell->neighbor(i)->info().p() = -1*initialPC; if(cell->neighbor(i)->info().saturation != cell->neighbor(i)->info().saturation || cell->neighbor(i)->info().saturation > 1.0 || cell->neighbor(i)->info().saturation < 0.0){ std::cout << "Error with initial BC saturation: " << cell->neighbor(i)->info().saturation; } if(cell->neighbor(i)->info().mergedID > 0){ for (FiniteCellsIterator Mcell = tri.finite_cells_begin(); Mcell != cellEnd; Mcell++) { if(Mcell->info().mergedID == cell->neighbor(i)->info().mergedID){ Mcell->info().hasInterface = cell->neighbor(i)->info().hasInterface; Mcell->info().saturation = cell->neighbor(i)->info().saturation; Mcell->info().p() = cell->neighbor(i)->info().p(); Mcell->info().isNWRes = cell->neighbor(i)->info().isNWRes; } } } } } } } if(waterBoundaryPressure == 0.0){waterBoundaryPressure = -1 * initialPC;} } void TwoPhaseFlowEngine::verifyCompatibilityBC() { //This is merely a function to double check boundary conditions to avoid ill-posed B.C. std::cerr << endl << "Boundary and initial conditions are set for: "; if(drainageFirst && primaryTPF){ std::cerr<< "Primary Drainage"; if(initialPC > -1 * waterBoundaryPressure){ std::cerr << endl << "Warning, initial capillary pressure larger than imposed capillary pressure, this may cause imbibition";} } if(drainageFirst && !primaryTPF){ std::cerr<< "Secondary Drainage"; if(initialPC > -1 * waterBoundaryPressure){ std::cerr << endl << "Warning, initial capillary pressure larger than imposed capillary pressure, this may cause imbibition";} } if(!drainageFirst && primaryTPF){ std::cerr<< "Primary Imbibition"; if(initialPC < -1 * waterBoundaryPressure){ std::cerr << endl << "Warning, initial capillary pressure smaller than imposed capillary pressure, this may cause drainage";} } if(!drainageFirst && !primaryTPF){ std::cerr<< "Secondary Imbibition"; if(initialPC < -1 * waterBoundaryPressure){ std::cerr << endl << "Warning, initial capillary pressure smaller than imposed capillary pressure, this may cause drainage";} } std::cout << endl << "Water pressure at: " << waterBoundaryPressure << " and air pressure at: " << airBoundaryPressure << " InitialPC: "<< initialPC; } void TwoPhaseFlowEngine::setPoreNetwork() { //Reorder cell id's RTriangulation& tri = solver->T[solver->currentTes].Triangulation(); FiniteCellsIterator cellEnd = tri.finite_cells_end(); unsigned int i = 0; for (FiniteCellsIterator cell = tri.finite_cells_begin(); cell != cellEnd; cell++) { if(!cell->info().isFictious){ if(cell->info().poreId == -1){ cell->info().poreId = i; if(cell->info().mergedID > 0){ for (FiniteCellsIterator Mcell = tri.finite_cells_begin(); Mcell != cellEnd; Mcell++) { if(Mcell->info().mergedID == cell->info().mergedID){ Mcell->info().poreId = i; } } } i = i + 1; } } } for (FiniteCellsIterator cell = tri.finite_cells_begin(); cell != cellEnd; cell++) { if(!cell->info().isFictious){ if(cell->info().poreId == -1){std::cout << " cell -1 " << cell->info().id;} } } numberOfPores = i; for (FiniteCellsIterator cell = tri.finite_cells_begin(); cell != cellEnd; cell++) { if(!cell->info().isFictious){ for(unsigned int k = 0; k<4; k++){ if(!cell->neighbor(k)->info().isFictious){ if(cell->info().mergedID == 0 || (cell->neighbor(k)->info().mergedID != cell->info().mergedID && cell->info().mergedID != 0)){ cell->info().poreIdConnectivity[k] = cell->neighbor(k)->info().poreId; //FIXME: REDUNDANT } else cell->info().poreIdConnectivity[k] = -1; } } } } makeListOfPoresInCells(false); } void TwoPhaseFlowEngine::setListOfPores() { RTriangulation& tri = solver->T[solver->currentTes].Triangulation(); FiniteCellsIterator cellEnd = tri.finite_cells_end(); bool stop = false; //set list of pores if((deformation && remesh) || firstDynTPF){ listOfPores.clear(); for (unsigned int j = 0 ; j < numberOfPores; j++){ for (FiniteCellsIterator cell = tri.finite_cells_begin(); cell != cellEnd; cell++) { if(cell->info().poreId == (int) j && !cell->info().isGhost && !stop){ listOfPores.push_back(cell); stop = true; } } stop = false; } for (unsigned int i = 0 ; i < numberOfPores; i++){ for (FiniteCellsIterator cell = tri.finite_cells_begin(); cell != cellEnd; cell++) { if(cell->info().poreId == (int) i && !listOfPores[i]->info().isNWRes){ for(unsigned int j = 0; j<4; j++){ if(cell->neighbor(j)->info().isWRes){ listOfPores[i]->info().isWResInternal = true; listOfPores[i]->info().conductivityWRes = cell->info().kNorm()[j]; } } } } } for (unsigned int i = 0 ; i < numberOfPores; i++){ if(listOfPores[i]->info().isWResInternal){ //Important to track for centroidAverage water pressure for(unsigned int j = 0; j < listOfPores[i]->info().poreNeighbors.size(); j++){ if(!listOfPores[listOfPores[i]->info().poreNeighbors[j]]->info().isWResInternal){ listOfPores[listOfPores[i]->info().poreNeighbors[j]]->info().waterBC = true; } } } for(unsigned int j = 0; j < listOfPores[i]->info().poreNeighbors.size(); j++){ for(unsigned int k = 0; k < listOfPores[listOfPores[i]->info().poreNeighbors[j]]->info().poreNeighbors.size(); k++){ if(listOfPores[listOfPores[i]->info().poreNeighbors[j]]->info().poreNeighbors[k] == (int) i){ listOfPores[listOfPores[i]->info().poreNeighbors[j]]->info().listOfEntryPressure[k] = listOfPores[i]->info().listOfEntryPressure[j]; listOfPores[listOfPores[i]->info().poreNeighbors[j]]->info().listOfThroatArea[k] = listOfPores[i]->info().listOfThroatArea[j]; listOfPores[listOfPores[i]->info().poreNeighbors[j]]->info().listOfkNorm[k] = listOfPores[i]->info().listOfkNorm[j]; listOfPores[listOfPores[i]->info().poreNeighbors[j]]->info().listOfkNorm2[k] = listOfPores[i]->info().listOfkNorm2[j]; } } } } for (unsigned int i = 0 ; i < numberOfPores; i++){ listOfPores[i]->info().minSaturation = 1e-3; } } //update for deformation //reset knorm for(unsigned int i = 0; i < numberOfPores; i++){ listOfPores[i]->info().listOfkNorm.clear(); listOfPores[i]->info().listOfkNorm = listOfPores[i]->info().listOfkNorm2; if(listOfPores[i]->info().saturation < 1.0){ for(unsigned int j = 0; j < listOfPores[i]->info().poreNeighbors.size(); j++){ if(listOfPores[listOfPores[i]->info().poreNeighbors[j]]->info().saturation < 1.0){ if(-1.0*listOfPores[i]->info().p() > listOfPores[i]->info().listOfEntryPressure[j]){ double radiusCurvature = 0.0; if(listOfPores[i]->info().saturation < 1.0 || listOfPores[listOfPores[i]->info().poreNeighbors[j]]->info().saturation == 1.0){ radiusCurvature = 2.0 * surfaceTension / (-1.0 * listOfPores[i]->info().p()); } if(listOfPores[i]->info().saturation == 1.0 || listOfPores[listOfPores[i]->info().poreNeighbors[j]]->info().saturation < 1.0){ radiusCurvature = 2.0 * surfaceTension / (-1.0 * listOfPores[listOfPores[i]->info().poreNeighbors[j]]->info().p()); } if(listOfPores[i]->info().saturation < 1.0 || listOfPores[listOfPores[i]->info().poreNeighbors[j]]->info().saturation < 1.0){ radiusCurvature = 4.0*surfaceTension / (-1.0 * (listOfPores[listOfPores[i]->info().poreNeighbors[j]]->info().p() + listOfPores[i]->info().p() )); } double areaWater = listOfPores[i]->info().listOfThroatArea[j] - 3.1415926535 * radiusCurvature * radiusCurvature; if(areaWater < 0.0){areaWater = listOfPores[i]->info().listOfThroatArea[j];} double hydraulicRad = 4.0*surfaceTension / (-1.0 * (listOfPores[listOfPores[i]->info().poreNeighbors[j]]->info().p() + listOfPores[i]->info().p() )); Point& p1 = listOfPores[i]->info(); Point& p2 = listOfPores[listOfPores[i]->info().poreNeighbors[j]]->info(); CVector l = p1 - p2; double distance = sqrt(l.squared_length()); listOfPores[i]->info().listOfkNorm[j] = areaWater * hydraulicRad * hydraulicRad / (viscosity * distance); if(listOfPores[i]->info().listOfkNorm[j] < 1e-15){listOfPores[i]->info().listOfkNorm[j] = 1e-15;} if(listOfPores[i]->info().listOfkNorm[j] < 0.0 || listOfPores[i]->info().listOfkNorm[j] != listOfPores[i]->info().listOfkNorm[j]){std::cerr << " Error! " << listOfPores[i]->info().listOfkNorm[j];} for(unsigned int k = 0; k < listOfPores[listOfPores[i]->info().poreNeighbors[j]]->info().poreNeighbors.size(); k++){ if(listOfPores[listOfPores[i]->info().poreNeighbors[j]]->info().poreNeighbors[k] == (int) i){ listOfPores[listOfPores[i]->info().poreNeighbors[j]]->info().listOfkNorm[k] = listOfPores[i]->info().listOfkNorm[j]; } } } } } } } for(unsigned int i = 0; i < numberOfPores; i++){ for(unsigned int j = 0; j < listOfPores[i]->info().poreNeighbors.size(); j++){ for(unsigned int k = 0; k < listOfPores[listOfPores[i]->info().poreNeighbors[j]]->info().poreNeighbors.size(); k++){ if(listOfPores[listOfPores[i]->info().poreNeighbors[j]]->info().poreNeighbors[k] == (int) i){ listOfPores[listOfPores[i]->info().poreNeighbors[j]]->info().listOfkNorm[k] = listOfPores[i]->info().listOfkNorm[j]; } } } } } void TwoPhaseFlowEngine::solvePressure() { RTriangulation& tri = solver->T[solver->currentTes].Triangulation(); FiniteCellsIterator cellEnd = tri.finite_cells_end(); double oldDT = 0.0; //Define matrix, triplet list, and linear solver tripletList.clear(); // tripletList.resize(T_nnz); Eigen::VectorXd residualsList(numberOfPores); Eigen::VectorXd pressuresList(numberOfPores); //Solve aMatrix * pressuresList = residualsList //define lists if((deformation && remesh) || firstDynTPF){ aMatrix.resize(numberOfPores,numberOfPores); saturationList.assign(numberOfPores,0.0); hasInterfaceList.assign(numberOfPores,false); listOfFlux.assign(numberOfPores,0.0); listOfMergedVolume.assign(numberOfPores,0.0); //NOTE CHANGED AFTER PUSH ON GIT } //reset various lists for(unsigned int i = 0; i < numberOfPores; i++){ residualsList[i] = 0.0; pressuresList[i] = 0.0; saturationList[i] = listOfPores[i]->info().saturation; hasInterfaceList[i] = listOfPores[i]->info().hasInterface; listOfFlux[i] = 0.0; } //Fill matrix for(unsigned int i = 0; i < numberOfPores; i++){ //Get diagonal coeff double dsdp2 = 0.0; double coeffA = 0.0, coeffA2 = 0.0; if(hasInterfaceList[i] && !firstDynTPF){ if(listOfPores[i]->info().p() == 0){ std::cout << endl << "Error, pressure = 0 "<< listOfPores[i]->info().p() << listOfPores[i]->info().id; listOfPores[i]->info().p() = -1.0 * listOfPores[i]->info().thresholdPressure; } dsdp2 = dsdp(listOfPores[i],listOfPores[i]->info().p()); coeffA = dsdp2 * ((listOfPores[i]->info().mergedVolume / scene->dt) + (listOfPores[i]->info().accumulativeDV - listOfPores[i]->info().accumulativeDVSwelling)); //Only consider the change in porosity due to particle movement } //fill matrix off-diagonals if(!listOfPores[i]->info().isWResInternal ){ for(unsigned int j = 0; j < listOfPores[i]->info().poreNeighbors.size(); j++){ tripletList.push_back(ETriplet(i,listOfPores[i]->info().poreNeighbors[j],-1.0 * listOfPores[i]->info().listOfkNorm[j])); coeffA2 += listOfPores[i]->info().listOfkNorm[j]; } } //Set boundary conditions if(listOfPores[i]->info().isWResInternal){ tripletList.push_back(ETriplet(i,i, 1.0)); residualsList[i] = waterBoundaryPressure; } //Fill matrix diagonal if(!listOfPores[i]->info().isWResInternal ){ if(hasInterfaceList[i]){residualsList[i] += -1.0*listOfPores[i]->info().saturation * (listOfPores[i]->info().accumulativeDV - listOfPores[i]->info().accumulativeDVSwelling) + coeffA * listOfPores[i]->info().p();} if(!hasInterfaceList[i] && deformation && listOfPores[i]->info().saturation > listOfPores[i]->info().minSaturation){residualsList[i] += -1.0*(listOfPores[i]->info().accumulativeDV - listOfPores[i]->info().accumulativeDVSwelling);} tripletList.push_back(ETriplet(i,i, coeffA + coeffA2)); } } //Solve Matrix aMatrix.setFromTriplets(tripletList.begin(),tripletList.end()); eSolver.analyzePattern(aMatrix); eSolver.factorize(aMatrix); eSolver.compute(aMatrix); //Solve for pressure: FIXME: add check for quality of matrix, if problematic, skip all below. pressuresList = eSolver.solve(residualsList); //Compute flux double flux = 0.0; double accumulativeDefFlux = 0.0; double waterBefore = 0.0, waterAfter = 0.0; double boundaryFlux = 0.0, lostVolume = 0.0; oldDT = scene->dt; //check water balance for(unsigned int i = 0; i < numberOfPores; i++){ waterBefore += listOfPores[i]->info().saturation * listOfPores[i]->info().mergedVolume; } //compute flux for(unsigned int i = 0; i < numberOfPores; i++){ flux = 0.0; for(unsigned int j = 0; j < listOfPores[i]->info().poreNeighbors.size(); j++){ flux += listOfPores[i]->info().listOfkNorm[j] * (pressuresList[i] - pressuresList[ listOfPores[i]->info().poreNeighbors[j] ]) ; } listOfFlux[i] = flux; if(listOfPores[i]->info().saturation > listOfPores[i]->info().minSaturation){accumulativeDefFlux += listOfPores[i]->info().accumulativeDV;} if(listOfPores[i]->info().isWResInternal){ boundaryFlux += flux; } if(!listOfPores[i]->info().isWResInternal && !hasInterfaceList[i] && std::abs(listOfFlux[i]) > 1e-15 && !deformation){ std::cerr << " | Flux not 0.0" << listOfFlux[i] << " isNWRES: "<< listOfPores[i]->info().isNWRes << " saturation: "<< listOfPores[i]->info().saturation << " P:"<< listOfPores[i]->info().p() << " isNWef:" << listOfPores[i]->info().isNWResDef << "|"; lostVolume += listOfFlux[i] * scene->dt; } } double summFluxList = 0.0; double summFluxUnsat = 0.0; for(unsigned int i = 0; i < numberOfPores; i++){ if(hasInterfaceList[i]){summFluxUnsat += listOfFlux[i];} summFluxList += listOfFlux[i]; } //update saturation for(unsigned int i = 0; i < numberOfPores; i++){ if(!deformation && hasInterfaceList[i] && listOfFlux[i] != 0.0 && !listOfPores[i]->info().isWResInternal){ double ds = -1.0 * scene->dt * (listOfFlux[i]) / (listOfPores[i]->info().mergedVolume + listOfPores[i]->info().accumulativeDV * scene->dt); saturationList[i] = ds + (saturationList[i] * listOfPores[i]->info().mergedVolume / (listOfPores[i]->info().mergedVolume + listOfPores[i]->info().accumulativeDV * scene->dt )); } if(deformation && hasInterfaceList[i] && !listOfPores[i]->info().isWResInternal){ saturationList[i] = saturationList[i] + (pressuresList[i] - listOfPores[i]->info().p())*dsdp(listOfPores[i],listOfPores[i]->info().p()) + (scene->dt / (listOfPores[i]->info().mergedVolume + (listOfPores[i]->info().accumulativeDV - listOfPores[i]->info().accumulativeDVSwelling) * scene->dt )) * listOfPores[i]->info().accumulativeDVSwelling; } } for(unsigned int i = 0; i < numberOfPores; i++){ waterAfter += saturationList[i] * (listOfPores[i]->info().mergedVolume + listOfPores[i]->info().accumulativeDV * scene->dt); } accumulativeFlux += (summFluxList) * scene->dt; accumulativeDeformationFlux += accumulativeDefFlux * scene->dt; fluxInViaWBC += boundaryFlux * scene->dt; if(!deformation && std::abs(boundaryFlux * scene->dt + (waterBefore - waterAfter)) / std::abs(boundaryFlux * scene->dt) > 1e-3 && std::abs(boundaryFlux) > 1e-18){ //FIXME test has to optimized for deforming pore units std::cerr << endl << "No volume balance! Flux balance: WBFlux:" << std::abs(boundaryFlux * scene->dt + (waterBefore - waterAfter)) / std::abs(boundaryFlux * scene->dt) << " " <dt << "Flux: " << summFluxList*scene->dt << "deltaVolume: " << waterBefore - waterAfter << "Flux in IFACE: "<< summFluxUnsat * scene->dt << " lostVolume: " << lostVolume * scene->dt; // stopSimulation = true; } // --------------------------------------find new dt ----------------------------------------------------- double dt = 0.0, finalDT = 1e6; int saveID = -1; for(unsigned int i = 0; i < numberOfPores; i++){ //Time step for deforming pore units if(deformation){ dt = -1.0 * listOfPores[i]->info().mergedVolume / (listOfPores[i]->info().accumulativeDV + listOfPores[i]->info().accumulativeDVSwelling); //Residence time total pore volume if(dt > deltaTimeTruncation && dt < finalDT){finalDT = dt;saveID = -1;} if(listOfPores[i]->info().accumulativeDVSwelling > 0.0 || listOfPores[i]->info().accumulativeDV > 0.0){ // Residence time during increase in pore size if(listOfPores[i]->info().accumulativeDVSwelling > listOfPores[i]->info().accumulativeDV){ dt = listOfPores[i]->info().mergedVolume * (1.0 - saturationList[i]) / listOfPores[i]->info().accumulativeDVSwelling; } if(listOfPores[i]->info().accumulativeDVSwelling <= listOfPores[i]->info().accumulativeDV){ dt = listOfPores[i]->info().mergedVolume * (1.0 - saturationList[i]) / listOfPores[i]->info().accumulativeDV; } if(dt > deltaTimeTruncation && dt < finalDT){finalDT = dt;saveID = -2;} } if(listOfPores[i]->info().accumulativeDVSwelling < 0.0 || listOfPores[i]->info().accumulativeDV < 0.0){ if(listOfPores[i]->info().accumulativeDVSwelling < listOfPores[i]->info().accumulativeDV){ dt = -1.0 * listOfPores[i]->info().mergedVolume * saturationList[i] / listOfPores[i]->info().accumulativeDVSwelling; } if(listOfPores[i]->info().accumulativeDVSwelling >= listOfPores[i]->info().accumulativeDV){ dt = -1.0 * listOfPores[i]->info().mergedVolume * saturationList[i] / listOfPores[i]->info().accumulativeDV; } if(dt > deltaTimeTruncation && dt < finalDT){finalDT = dt;saveID = -2;} } } //Time step for dynamic flow if(hasInterfaceList[i]){ //thresholdSaturation if(std::abs(listOfPores[i]->info().thresholdSaturation - saturationList[i]) > truncationPrecision){ dt = -1.0 * (listOfPores[i]->info().thresholdSaturation - saturationList[i]) * listOfPores[i]->info().mergedVolume / listOfFlux[i]; if(dt > deltaTimeTruncation && dt < finalDT){finalDT = dt;/*saveID = 1;*/} } //Empty pore if(std::abs(0.0 - saturationList[i]) > truncationPrecision && listOfFlux[i] > 0.0){ //only for drainage dt = -1.0 * (0.0 - saturationList[i]) * listOfPores[i]->info().mergedVolume / listOfFlux[i]; if(dt > deltaTimeTruncation && dt < finalDT){finalDT = dt;/*saveID = 2;*/} } //Saturated pore if(std::abs(1.0 - saturationList[i]) > truncationPrecision && listOfFlux[i] < 0.0){ //only for imbibition dt = -1.0 * (1.0 - saturationList[i]) * listOfPores[i]->info().mergedVolume / listOfFlux[i]; if(dt > deltaTimeTruncation && dt < finalDT){finalDT = dt;/*saveID = 3;*/} } } } if(finalDT == 1e6){ finalDT = deltaTimeTruncation; saveID = 5; if(!firstDynTPF && !remesh){ std::cout << endl << "NO dt found!"; stopSimulation = true;} } scene->dt = finalDT * safetyFactorTimeStep; if(debugTPF){std::cerr<info().isWResInternal && (!deformation || listOfPores[i]->info().saturation != 0.0)){ pressuresList[i] = porePressureFromPcS(listOfPores[i],saturationList[i]);} } // --------------------------------------Find invasion events----------------------------------------------------- for(unsigned int i = 0; i < numberOfPores; i++){ if(saturationList[i] > 1.0 - truncationPrecision && (listOfFlux[i] < 0.0 || deformation) && saturationList[i] != 1.0){ if(saturationList[i] > 1.0){ if(saturationList[listOfPores[i]->info().invadedFrom] >= 1.0){ waterVolumeTruncatedLost += (saturationList[i] - 1.0) * listOfPores[i]->info().mergedVolume; saturationList[i] = 1.0; } if(saturationList[listOfPores[i]->info().invadedFrom] < 1.0){ saturationList[listOfPores[i]->info().invadedFrom] += (saturationList[i] - 1.0) * listOfPores[i]->info().mergedVolume / listOfPores[listOfPores[i]->info().invadedFrom]->info().mergedVolume; saturationList[i] = 1.0; if(saturationList[listOfPores[i]->info().invadedFrom] > 1.0){ waterVolumeTruncatedLost += (saturationList[listOfPores[i]->info().invadedFrom] - 1.0) * listOfPores[listOfPores[i]->info().invadedFrom]->info().mergedVolume; saturationList[listOfPores[i]->info().invadedFrom] = 1.0; } } } saturationList[i] = 1.0; hasInterfaceList[i] = false; listOfPores[i]->info().isNWResDef = true; } } //Check for drainage for(unsigned int i = 0; i < numberOfPores; i++){ if((fractionMinSaturationInvasion == -1 && hasInterfaceList[i] && saturationList[i] < listOfPores[i]->info().thresholdSaturation) || (fractionMinSaturationInvasion > 0.0 && saturationList[i] < fractionMinSaturationInvasion)){ for(unsigned int j = 0; j < listOfPores[i]->info().poreNeighbors.size(); j++){ if(airBoundaryPressure - pressuresList[listOfPores[i]->info().poreNeighbors[j]] > listOfPores[i]->info().listOfEntryPressure[j] && !hasInterfaceList[listOfPores[i]->info().poreNeighbors[j]] && !listOfPores[listOfPores[i]->info().poreNeighbors[j]]->info().isWResInternal && saturationList[listOfPores[i]->info().poreNeighbors[j]] > listOfPores[listOfPores[i]->info().poreNeighbors[j]]->info().minSaturation && saturationList[listOfPores[i]->info().poreNeighbors[j]] <= 1.0 ){ hasInterfaceList[listOfPores[i]->info().poreNeighbors[j]] = true; saturationList[listOfPores[i]->info().poreNeighbors[j]] = 1.0 - truncationPrecision; pressuresList[listOfPores[i]->info().poreNeighbors[j]] = porePressureFromPcS(listOfPores[i], 1.0 - truncationPrecision); listOfPores[listOfPores[i]->info().poreNeighbors[j]]->info().invadedFrom = i; } } } } //truncate saturation for(unsigned int i = 0; i < numberOfPores; i++){ if((saturationList[i] < truncationPrecision || saturationList[i] <= listOfPores[i]->info().minSaturation)){ waterVolumeTruncatedLost -= (listOfPores[i]->info().minSaturation - saturationList[i]) * listOfPores[i]->info().mergedVolume; saturationList[i] = listOfPores[i]->info().minSaturation; // hasInterfaceList[i] = false; // NOTE: in case of deactivation of empty cell, set hasInterfaceList[i] to false pressuresList[i] = porePressureFromPcS(listOfPores[i], listOfPores[i]->info().minSaturation); //waterBoundaryPressure; listOfPores[i]->info().isNWRes = true; for(unsigned int j = 0; j < listOfPores[i]->info().poreNeighbors.size(); j++){ if(!hasInterfaceList[listOfPores[i]->info().poreNeighbors[j]] && !listOfPores[listOfPores[i]->info().poreNeighbors[j]]->info().isNWRes && listOfFlux[listOfPores[i]->info().poreNeighbors[j]] >= 0.0 && !listOfPores[listOfPores[i]->info().poreNeighbors[j]]->info().isWResInternal){ hasInterfaceList[listOfPores[i]->info().poreNeighbors[j]] = true; saturationList[listOfPores[i]->info().poreNeighbors[j]] = 1.0 - truncationPrecision; pressuresList[listOfPores[i]->info().poreNeighbors[j]] = porePressureFromPcS(listOfPores[listOfPores[i]->info().poreNeighbors[j]], 1.0 - truncationPrecision); } } } if(listOfPores[i]->info().isNWResDef && saturationList[i] < listOfPores[i]->info().thresholdSaturation){ listOfPores[i]->info().isNWResDef = false; } } if(deformation){ for (FiniteCellsIterator cell = tri.finite_cells_begin(); cell != cellEnd; cell++){ cell->info().poreBodyVolume += cell->info().dv() * oldDT; } //copyPoreDataToCells(); //NOTE: For two-way coupling this function should be activated, but it is a bit costly for computations. } for(unsigned int i = 0; i < numberOfPores; i++){ if(deformation){ listOfPores[i]->info().mergedVolume += listOfPores[i]->info().accumulativeDV * oldDT; listOfPores[i]->info().poreBodyRadius = getChi(listOfPores[i]->info().numberFacets)*std::pow(listOfPores[i]->info().mergedVolume,(1./3.)); } if(saturationList[i] > 1.0){ std::cerr << endl << "Error!, saturation larger than 1? "; saturationList[i] = 1.0; //NOTE ADDED AFTER TRUNK UPDATE should be 0.0? // stopSimulation = true; } listOfPores[i]->info().saturation = saturationList[i]; listOfPores[i]->info().p() = pressuresList[i]; listOfPores[i]->info().hasInterface = bool(hasInterfaceList[i]); listOfPores[i]->info().flux = listOfFlux[i]; listOfPores[i]->info().dv() = 0.0; //NOTE ADDED AFTER TRUNK UPDATE listOfPores[i]->info().accumulativeDV = 0.0; } } void TwoPhaseFlowEngine::getQuantities() { double waterVolume = 0.0, pressureWaterVolume = 0.0, waterVolume_NHJ = 0.0, pressureWaterVolume_NHJ = 0.0, waterVolumeP = 0.0, YDimension = 0.0, simplePressureAverage = 0.0; voidVolume = 0.0; for(unsigned int i = 0; i < numberOfPores; i++){ voidVolume += listOfPores[i]->info().mergedVolume; waterVolume += listOfPores[i]->info().mergedVolume * listOfPores[i]->info().saturation; YDimension += solver->cellBarycenter(listOfPores[i])[1] * listOfPores[i]->info().mergedVolume * listOfPores[i]->info().saturation; simplePressureAverage += listOfPores[i]->info().mergedVolume * listOfPores[i]->info().p(); if(std::abs(listOfPores[i]->info().p()) < 1e10){ pressureWaterVolume += listOfPores[i]->info().mergedVolume * listOfPores[i]->info().saturation * listOfPores[i]->info().p(); waterVolumeP += listOfPores[i]->info().mergedVolume * listOfPores[i]->info().saturation; } if(listOfPores[i]->info().saturation < 1.0){ waterVolume_NHJ += listOfPores[i]->info().mergedVolume * listOfPores[i]->info().saturation; pressureWaterVolume_NHJ += listOfPores[i]->info().mergedVolume * listOfPores[i]->info().saturation * listOfPores[i]->info().p(); } } double areaAveragedPressureAcc = 0.0, areaSphere = 0.0; airWaterInterfacialArea = 0.0; for(unsigned int i = 0; i < numberOfPores; i++){ if(listOfPores[i]->info().hasInterface){ if(listOfPores[i]->info().saturation < 1.0 && listOfPores[i]->info().saturation >= listOfPores[i]->info().thresholdSaturation){ areaSphere = 4.0 * 3.14159265359 * std::pow(getChi(listOfPores[i]->info().numberFacets) * std::pow(listOfPores[i]->info().mergedVolume * (1.0 - listOfPores[i]->info().saturation),0.3333),2); } if(listOfPores[i]->info().saturation < listOfPores[i]->info().thresholdSaturation && listOfPores[i]->info().saturation > 0.0 && listOfPores[i]->info().saturation > listOfPores[i]->info().minSaturation){ //FIXME FIXME FIXME 1 june 2016 areaSphere = 4.0 * 3.14159265359 * std::pow((2.0 * surfaceTension / (-1.0*listOfPores[i]->info().p())),2.0) + 2.0 * getN(listOfPores[i]->info().numberFacets) * (listOfPores[i]->info().poreBodyRadius - (2.0 * surfaceTension / (-1.0*listOfPores[i]->info().p()))) * (2.0 * surfaceTension / (-1.0 * listOfPores[i]->info().p())) * (2.0 * 3.14159265359 - getDihedralAngle(listOfPores[i]->info().numberFacets)); } areaAveragedPressureAcc += areaSphere * listOfPores[i]->info().p(); airWaterInterfacialArea += areaSphere; } } areaAveragedPressure = areaAveragedPressureAcc / airWaterInterfacialArea; waterSaturation = waterVolume / voidVolume; waterPressure = pressureWaterVolume / waterVolumeP; waterPressurePartiallySatPores = pressureWaterVolume_NHJ / waterVolume_NHJ; simpleWaterPressure = simplePressureAverage / voidVolume; totalWaterVolume = waterVolume; if(!deformation){ double volumeWaterVBC = 0.0, volumeWaterPressureBC = 0.0, volumeWaterBC = 0.0, volumeWaterAirBC = 0.0, volumeWaterPressureAirBC = 0.0, volumeAirBC = 0.0, Ybottom = 0.0, Ytop = 0.0; for(unsigned int i = 0; i < numberOfPores; i++){ if(listOfPores[i]->info().waterBC){ volumeWaterVBC += listOfPores[i]->info().saturation * listOfPores[i]->info().mergedVolume; volumeWaterPressureBC += listOfPores[i]->info().saturation * listOfPores[i]->info().mergedVolume * listOfPores[i]->info().p(); volumeWaterBC += listOfPores[i]->info().mergedVolume; Ybottom += solver->cellBarycenter(listOfPores[i])[1] * listOfPores[i]->info().mergedVolume; } if(listOfPores[i]->info().airBC){ volumeWaterAirBC += listOfPores[i]->info().saturation * listOfPores[i]->info().mergedVolume; volumeWaterPressureAirBC += listOfPores[i]->info().saturation * listOfPores[i]->info().mergedVolume * listOfPores[i]->info().p(); volumeAirBC += listOfPores[i]->info().mergedVolume; Ytop += solver->cellBarycenter(listOfPores[i])[1] * listOfPores[i]->info().mergedVolume; } } double Stop = volumeWaterAirBC / volumeAirBC; //air BC double Sbottom = volumeWaterVBC / volumeWaterBC;//Water BC. double Ptop = volumeWaterPressureAirBC / volumeWaterAirBC ; double Pbottom = volumeWaterPressureBC / volumeWaterVBC ; double z = (((Ytop / volumeAirBC) - (Ybottom / volumeWaterBC)) / 2.0) + (Ybottom / volumeWaterBC); double gradP = -1.0 * (Stop - Sbottom) + (Stop * Ptop - Sbottom * Pbottom); double gradZ = -1.0 * (YDimension / waterVolume)*(Stop - Sbottom) + ((Stop * Ytop / volumeAirBC) - (Sbottom * Ybottom / volumeWaterBC)); centroidAverageWaterPressure = waterPressure + (1.0 / gradZ) * (z - (YDimension / waterVolume)) * gradP; } } void TwoPhaseFlowEngine::imposeDeformationFluxTPF() { RTriangulation& tri = solver->T[solver->currentTes].Triangulation(); FiniteCellsIterator cellEnd = tri.finite_cells_end(); for (FiniteCellsIterator cell = tri.finite_cells_begin(); cell != cellEnd; cell++) { cell->info().dv() = cell->info().dvTPF; //Only relevant for imposed deformation from python-shell } imposeDeformationFluxTPFSwitch = true; } void TwoPhaseFlowEngine::updateDeformationFluxTPF() { RTriangulation& tri = solver->T[solver->currentTes].Triangulation(); FiniteCellsIterator cellEnd = tri.finite_cells_end(); double dv = 0.0, summ = 0.0, summBC = 0.0, summMC = 0.0, SolidVolume = 0.0, dvSwelling = 0.0; if(!imposeDeformationFluxTPFSwitch){ setPositionsBuffer(true); updateVolumes(*solver); if(swelling){ double volume = 0.0, invTime = (1.0 / scene->dt); if(scene->dt == 0.0){std::cerr<<" No dt found!";} for (FiniteCellsIterator cell = tri.finite_cells_begin(); cell != cellEnd; cell++){ cell->info().dv() = 0.0; if(!cell->info().isFictious){ double solidVol = getSolidVolumeInCell(cell); if(solidVol < 0.0){std::cerr<<"Error! negative pore body volume! " << solidVol; solidVol = 0.0;} volume = cell->info().volume()*cell->info().volumeSign - solidVol; if(volume < 0.0){ volume = cell->info().poreBodyVolume; listOfPores[cell->info().poreId]->info().isNWRes = true; listOfPores[cell->info().poreId]->info().saturation = truncationPrecision; } if(cell->info().apparentSolidVolume <= 0.0){cell->info().apparentSolidVolume = solidVol;} cell->info().dvSwelling = (volume - cell->info().poreBodyVolume + solidVol - cell->info().apparentSolidVolume)*invTime - cell->info().dv(); if(cell->info().isNWRes || listOfPores[cell->info().poreId]->info().isNWRes){cell->info().dvSwelling = 0.0;} cell->info().dv() = (volume - cell->info().poreBodyVolume)*invTime; SolidVolume += solidVol; summ += cell->info().dv(); summBC += cell->info().dv(); } } } } for(unsigned int i = 0; i < numberOfPores; i++){ dv = 0.0, dvSwelling =0.0; for (FiniteCellsIterator cell = tri.finite_cells_begin(); cell != cellEnd; cell++){ if(cell->info().poreId == (int) i){ dv += cell->info().dv(); dvSwelling += cell->info().dvSwelling; } } listOfPores[i]->info().accumulativeDV = dv; listOfPores[i]->info().accumulativeDVSwelling = dvSwelling; summMC += dv; } if(swelling){ //Account for swelling of particles into a non-existing pore (i.e. boundary pores). for(unsigned int i = 0; i < numberOfPores; i++){ if(listOfPores[i]->info().isNWRes){ double count = 0.0; for(unsigned int j = 0; j < listOfPores[i]->info().poreNeighbors.size(); j++){ if(!listOfPores[listOfPores[i]->info().poreNeighbors[j]]->info().isNWRes){ count += 1.0; } } for(unsigned int j = 0; j < listOfPores[i]->info().poreNeighbors.size(); j++){ if(!listOfPores[listOfPores[i]->info().poreNeighbors[j]]->info().isNWRes){ if(count != 0.0){ listOfPores[listOfPores[i]->info().poreNeighbors[j]]->info().accumulativeDVSwelling += listOfPores[i]->info().accumulativeDVSwelling / count; } } } listOfPores[i]->info().accumulativeDVSwelling = 0.0; } } } } double TwoPhaseFlowEngine::getSolidVolumeInCell(CellHandle cell) { //Dublicate function that depends on position buffer of particles //FIXME this function be replaced if function of void volume can be made dependent on updated location of particles double Vsolid=0; cell->info().apparentSolidVolume = 0.0; for (int i=0;i<4;i++) { const Vector3r& p0v = positionBufferCurrent[cell->vertex (solver->permut4[i][0])->info().id()].pos; const Vector3r& p1v = positionBufferCurrent[cell->vertex (solver->permut4[i][1])->info().id()].pos; const Vector3r& p2v = positionBufferCurrent[cell->vertex (solver->permut4[i][2])->info().id()].pos; const Vector3r& p3v = positionBufferCurrent[cell->vertex (solver->permut4[i][3])->info().id()].pos; Point p0(p0v[0],p0v[1],p0v[2]); Point p1(p1v[0],p1v[1],p1v[2]); Point p2(p2v[0],p2v[1],p2v[2]); Point p3(p3v[0],p3v[1],p3v[2]); double rad = positionBufferCurrent[cell->vertex (solver->permut4[i][0])->info().id()].radius; double angle = solver->fastSolidAngle(p0,p1,p2,p3); cell->info().particleSurfaceArea[i] = rad*rad*angle; if(setFractionParticles[cell->vertex(i)->info().id()] > 0){ //should be moved cell->info().apparentSolidVolume += rad*rad*angle / (setFractionParticles[cell->vertex(i)->info().id()] * setFractionParticles[cell->vertex(i)->info().id()]) ; //Coupling to account for swelling in dry pores } Vsolid += (1./3.) * std::pow(rad,3) * std::abs(angle); } return Vsolid; } void TwoPhaseFlowEngine::updatePoreUnitProperties() { //FIXME clean-up this function (computePoreThroatRadiusMethod2() does not include update of particle location, thus this is a quick fix RTriangulation& tri = solver->T[solver->currentTes].Triangulation(); FiniteCellsIterator cellEnd = tri.finite_cells_end(); for (FiniteCellsIterator cell = tri.finite_cells_begin(); cell != cellEnd; cell++){ if(!cell->info().isFictious){ for(unsigned int j = 0; j<4; j++){ if(cell->info().poreId != cell->neighbor(j)->info().poreId && cell->info().id > cell->neighbor(j)->info().id){ double rA = positionBufferCurrent[cell->vertex(facetVertices[j][0])->info().id()].radius; double rB = positionBufferCurrent[cell->vertex(facetVertices[j][1])->info().id()].radius; double rC = positionBufferCurrent[cell->vertex(facetVertices[j][2])->info().id()].radius; CVector posA(positionBufferCurrent[cell->vertex(facetVertices[j][0])->info().id()].pos[0],positionBufferCurrent[cell->vertex(facetVertices[j][0])->info().id()].pos[1],positionBufferCurrent[cell->vertex(facetVertices[j][0])->info().id()].pos[2] ); CVector posB(positionBufferCurrent[cell->vertex(facetVertices[j][1])->info().id()].pos[0],positionBufferCurrent[cell->vertex(facetVertices[j][1])->info().id()].pos[1],positionBufferCurrent[cell->vertex(facetVertices[j][1])->info().id()].pos[2] ); CVector posC(positionBufferCurrent[cell->vertex(facetVertices[j][2])->info().id()].pos[0],positionBufferCurrent[cell->vertex(facetVertices[j][2])->info().id()].pos[1],positionBufferCurrent[cell->vertex(facetVertices[j][2])->info().id()].pos[2] ); CVector B = posB - posA; //positionBufferCurrent[cell->vertex(facetVertices[j][1])->info().id()].pos - positionBufferCurrent[cell->vertex(facetVertices[j][0])->info().id()].pos; CVector x = B/sqrt(B.squared_length()); CVector C = posC - posA; //positionBufferCurrent[cell->vertex(facetVertices[j][2])->info().id()].pos - positionBufferCurrent[cell->vertex(facetVertices[j][0])->info().id()].pos; CVector z = CGAL::cross_product(x,C); CVector y = CGAL::cross_product(x,z); y = y/std::sqrt(y.squared_length()); double b1[2]; b1[0] = B*x; b1[1] = B*y; double c1[2]; c1[0] = C*x; c1[1] = C*y; double A = ((std::pow(rA,2))*(1-c1[0]/b1[0])+((std::pow(rB,2)*c1[0])/b1[0])-std::pow(rC,2)+pow(c1[0],2)+std::pow(c1[1],2)-((std::pow(b1[0],2)+std::pow(b1[1],2))*c1[0]/b1[0]))/(2*c1[1]-2*b1[1]*c1[0]/b1[0]); double BB = (rA-rC-((rA-rB)*c1[0]/b1[0]))/(c1[1]-b1[1]*c1[0]/b1[0]); double CC = (std::pow(rA,2)-std::pow(rB,2)+std::pow(b1[0],2)+std::pow(b1[1],2))/(2*b1[0]); double D = (rA-rB)/b1[0]; double E = b1[1]/b1[0]; double F = std::pow(CC,2)+std::pow(E,2)*std::pow(A,2)-2*CC*E*A; double c = -F-std::pow(A,2)+pow(rA,2); double b = 2*rA-2*(D-BB*E)*(CC-E*A)-2*A*BB; double a = 1-std::pow((D-BB*E),2)-std::pow(BB,2); if ((std::pow(b,2)-4*a*c)<0){std::cout << "NEGATIVE DETERMINANT" << endl; } double reff = (-b+std::sqrt(pow(b,2)-4*a*c))/(2*a); if (cell->vertex(facetVertices[j][2])->info().isFictious || cell->vertex(facetVertices[j][1])->info().isFictious || cell->vertex(facetVertices[j][2])->info().isFictious){ reff = -1 * reff; } cell->info().poreThroatRadius[j] = reff; cell->neighbor(j)->info().poreThroatRadius[tri.mirror_index(cell, j)] = reff; } } } } makeListOfPoresInCells(true); } void TwoPhaseFlowEngine::makeListOfPoresInCells(bool fast) { RTriangulation& tri = solver->T[solver->currentTes].Triangulation(); FiniteCellsIterator cellEnd = tri.finite_cells_end(); bool cancel = false, firstCheck = true;; for (unsigned int j = 0 ; j < numberOfPores; j++){ firstCheck = true; std::vector poreNeighbors; std::vector listOfkNorm; std::vector listOfEntrySaturation; std::vector listOfEntryPressure; std::vector listOfThroatArea; for (FiniteCellsIterator cell = tri.finite_cells_begin(); cell != cellEnd; cell++){ if(cell->info().poreId == (int) j){ for(unsigned int ngb = 0; ngb <4 ; ngb++){ if(cell->neighbor(ngb)->info().poreId != (int) j && cell->neighbor(ngb)->info().poreId != -1 ){ cancel = false; for(unsigned int checkID = 0; checkID < poreNeighbors.size(); checkID++){ if( poreNeighbors[checkID] == cell->neighbor(ngb)->info().poreId){ cancel = true; // std::cerr<<"skipCell"; } } if((firstCheck || !cancel) || poreNeighbors.size() == 0){ if(!fast){poreNeighbors.push_back(cell->neighbor(ngb)->info().poreId);} if(!fast){listOfkNorm.push_back(cell->info().kNorm()[ngb]);} listOfEntryPressure.push_back(entryMethodCorrection * surfaceTension / cell->info().poreThroatRadius[ngb]); double saturation = poreSaturationFromPcS(cell,-1.0 * cell->info().entryPressure[ngb]); listOfEntrySaturation.push_back(saturation); if(saturation > 1.0 || saturation < 0.0 || saturation != saturation){ std::cerr<info().facetSurfaces[ngb]; Real area = sqrt(Surfk.squared_length()); listOfThroatArea.push_back(area * cell->info().facetFluidSurfacesRatio[ngb]); } if(firstCheck){firstCheck = false;} } } } } } if(fast){ // listOfPores[j]->info().poreNeighbors = poreNeighbors; listOfPores[j]->info().listOfEntrySaturation = listOfEntrySaturation; listOfPores[j]->info().listOfEntryPressure = listOfEntryPressure; // listOfPores[j]->info().listOfThroatArea = listOfThroatArea; // listOfPores[j]->info().listOfkNorm = listOfkNorm; // listOfPores[j]->info().listOfkNorm2 = listOfkNorm; } if(!fast){ for (FiniteCellsIterator cell = tri.finite_cells_begin(); cell != cellEnd; cell++) { if(cell->info().poreId == (int) j){ cell->info().poreNeighbors = poreNeighbors; cell->info().listOfEntrySaturation = listOfEntrySaturation; cell->info().listOfEntryPressure = listOfEntryPressure; cell->info().listOfThroatArea = listOfThroatArea; cell->info().listOfkNorm = listOfkNorm; cell->info().listOfkNorm2 = listOfkNorm; } } } } } void TwoPhaseFlowEngine::copyPoreDataToCells() { //NOTE: Don't apply this function via python directly after applying reTriangulation() via Python, this will give segment fault. RTriangulation& tri = solver->T[solver->currentTes].Triangulation(); FiniteCellsIterator cellEnd = tri.finite_cells_end(); for (FiniteCellsIterator cell = tri.finite_cells_begin(); cell != cellEnd; cell++) { if(!cell->info().isFictious){ cell->info().saturation = listOfPores[cell->info().poreId]->info().saturation; cell->info().p() = listOfPores[cell->info().poreId]->info().p(); cell->info().hasInterface = bool(hasInterfaceList[cell->info().poreId]); cell->info().flux = listOfFlux[cell->info().poreId]; cell->info().isNWRes = listOfPores[cell->info().poreId]->info().isNWRes; cell->info().airWaterArea = listOfPores[cell->info().poreId]->info().airWaterArea; if(deformation){ cell->info().mergedVolume = listOfPores[cell->info().poreId]->info().mergedVolume; //NOTE ADDED AFTER TRUNK UPDATE cell->info().poreBodyRadius = getChi(cell->info().numberFacets)*std::pow(listOfPores[cell->info().poreId]->info().mergedVolume,(1./3.)); }//NOTE ADDED AFTER TRUNK UPDATE //NOTE ADDED AFTER TRUNK UPDATE } } } void TwoPhaseFlowEngine::actionTPF() { iterationTPF += 1; if(firstDynTPF){ std::cout << endl << "Welcome to the two-phase flow Engine" << endl << "by T.Sweijen, B.Chareyre and S.M.Hassanizadeh" << endl << "For contact: T.Sweijen@uu.nl"; solver->computePermeability(); scene->time = 0.0; initialization(); actionMergingAlgorithm(); calculateResidualSaturation(); setInitialConditions(); setBoundaryConditions(); verifyCompatibilityBC(); setPoreNetwork(); scene->dt = 1e-20; setListOfPores(); solvePressure(); getQuantities(); firstDynTPF = false; } if(!firstDynTPF && !stopSimulation){ // bool remesh = false; //Time steps + deformation, but no remeshing scene->time = scene->time + scene->dt; if(deformation && !remesh){ updateDeformationFluxTPF(); if(int(float(iterationTPF)/10.0) == float(iterationTPF)/10.0){ updatePoreUnitProperties(); } } //Update pore throat radii etc. if(deformation && remesh){ reTriangulate(); //retriangulation + merging calculateResidualSaturation(); transferConditions(); //get saturation, hasInterface from previous network setBoundaryConditions(); setPoreNetwork(); } setListOfPores(); if(solvePressureSwitch){solvePressure();} if(deformation){if(int(float(iterationTPF)/50.0) == float(iterationTPF)/50.0){getQuantities();}} //FIXME update of quantities has to be made more appropiate // getQuantities();//NOTE FIX if(!deformation ){ if(!getQuantitiesUpdateCont){ if(int(float(iterationTPF)/100.0) == float(iterationTPF)/100.0){getQuantities();} } if(getQuantitiesUpdateCont){ getQuantities(); } } if(remesh){remesh = false;} //Remesh bool is also used in solvePressure(); } } void TwoPhaseFlowEngine:: updateReservoirLabel() { clusters[0]->reset(); clusters[0]->label=0; clusters[1]->reset(); clusters[1]->label=1; RTriangulation& tri = solver->T[solver->currentTes].Triangulation(); FiniteCellsIterator cellEnd = tri.finite_cells_end(); for ( FiniteCellsIterator cell = tri.finite_cells_begin(); cell != cellEnd; cell++ ) { if (cell->info().isNWRes) clusterGetPore(clusters[0].get(),cell); else if (cell->info().isWRes) { clusterGetPore(clusters[1].get(),cell); for (int facet = 0; facet < 4; facet ++) if (!cell->neighbor(facet)->info().isWRes) clusterGetFacet(clusters[1].get(),cell,facet);} else if (cell->info().label>1) continue; else cell->info().label=-1; } } void TwoPhaseFlowEngine::clusterGetFacet(PhaseCluster* cluster, CellHandle cell, int facet) { cell->info().hasInterface = true; double interfArea = sqrt((cell->info().facetSurfaces[facet]*cell->info().facetFluidSurfacesRatio[facet]).squared_length()); cluster->interfaces.push_back(std::pair,double>( std::pair(cell->info().id,cell->neighbor(facet)->info().id),interfArea)); cluster->interfacialArea += interfArea; if (cluster->entryRadius < cell->info().poreThroatRadius[facet]){ cluster->entryRadius = cell->info().poreThroatRadius[facet]; cluster->entryPore = cell->info().id;} } void TwoPhaseFlowEngine::clusterGetPore(PhaseCluster* cluster, CellHandle cell) { cell->info().label=cluster->label; cluster->volume+=cell->info().poreBodyVolume; cluster->pores.push_back(cell); } vector TwoPhaseFlowEngine::clusterInvadePore(PhaseCluster* cluster, CellHandle cell) { //invade the pore and attach to NW reservoir, label is assigned after reset int label = cell->info().label; cell->info().saturation=0; cell->info().isNWRes=true; clusterGetPore(clusters[0].get(),cell); //update the cluster(s) unsigned nPores = cluster->pores.size(); vector newClusters; //for returning the list of possible sub-clusters, empty if we are removing the last pore of the base cluster if (nPores==0) {LOG_WARN("Invading the empty cluster id="<reset(); cluster->label = label; return newClusters;} FOREACH(CellHandle& cell, cluster->pores) {cell->info().label=-1;} //mark all pores, and get them back in again below cell->info().label=0;//mark the invaded one //find a remaining pore unsigned neighborStart=0; while ( (cell->neighbor(neighborStart)->info().label != -1 or solver->T[solver->currentTes].Triangulation().is_infinite(cell->neighbor(neighborStart))) and neighborStart<3 ) ++neighborStart; if (neighborStart==3 and cell->neighbor(neighborStart)->info().label != -1) cerr<<"This is not supposed to happen (line "<<__LINE__<<")"<neighbor(neighborStart); //use the remaining pore to start reconstruction of the cluster nCell->info().label = label; //assign the label of the original cluster cluster->reset(); //reset pores, volume, entryRadius, area... but restore label again after that cluster->label = label; updateSingleCellLabelRecursion(nCell,cluster); //rebuild newClusters.push_back(cluster->label);//we will return the original cluster itself if not empty // gen new clusters on the fly from the other neighbors of the invaded pore (for disconnected subclusters) for (int neighborId=neighborStart+1 ; neighborId<=3; neighborId++) {//should be =1 if the cluster remain the same -1 removed pore const CellHandle& nCell = cell->neighbor(neighborId); if (nCell->info().label != -1 or solver->T[solver->currentTes].Triangulation().is_infinite(nCell)) continue; //already reached from another neighbour (connected domain): skip, else this is a new cluster shared_ptr clst (new PhaseCluster()); clst->label=clusters.size(); newClusters.push_back(clst->label); clusters.push_back(clst); updateSingleCellLabelRecursion(nCell,clusters.back().get()); } return newClusters;// return list of created clusters } // int TwoPhaseFlowEngine:: getMaxCellLabel() // { // int maxLabel=-1; // RTriangulation& tri = solver->T[solver->currentTes].Triangulation(); // FiniteCellsIterator cellEnd = tri.finite_cells_end(); // for ( FiniteCellsIterator cell = tri.finite_cells_begin(); cell != cellEnd; cell++ ) { // if (cell->info().label>maxLabel) maxLabel=cell->info().label; // } // return maxLabel; // } void TwoPhaseFlowEngine::updateCellLabel() { // int currentLabel = getMaxCellLabel();//FIXME: A loop on cells for each new label?? is it serious?? updateReservoirLabel(); int currentLabel = clusters.size(); RTriangulation& tri = solver->T[solver->currentTes].Triangulation(); FiniteCellsIterator cellEnd = tri.finite_cells_end(); for ( FiniteCellsIterator cell = tri.finite_cells_begin(); cell != cellEnd; cell++ ) { if (cell->info().label==-1) { shared_ptr clst (new PhaseCluster()); clst->label=currentLabel; clusters.push_back(clst); updateSingleCellLabelRecursion(cell,clusters.back().get()); currentLabel++; } } } void TwoPhaseFlowEngine::updateSingleCellLabelRecursion(CellHandle cell, PhaseCluster* cluster) { clusterGetPore(cluster,cell); // cell->info().label=label; // cluster->volume+=cell->info(). // cluster->pores.push_back(cell); for (int facet = 0; facet < 4; facet ++) { CellHandle nCell = cell->neighbor(facet); if (solver->T[solver->currentTes].Triangulation().is_infinite(nCell)) continue; // if (nCell->info().Pcondition) continue; // if ( (nCell->info().isFictious) && (!isInvadeBoundary) ) continue; //TODO:the following condition may relax to relate to nCell->info().hasInterface if ( (nCell->info().saturation==cell->info().saturation) && (nCell->info().label!=cell->info().label) ) updateSingleCellLabelRecursion(nCell,cluster); else if (nCell->info().isNWRes) clusterGetFacet(cluster,cell,facet); } } boost::python::list TwoPhaseFlowEngine::pyClusters() { boost::python::list ret; for(vector >::iterator it=clusters.begin(); it!=clusters.end(); ++it) ret.append(*it); return ret;} void TwoPhaseFlowEngine::updatePressure() { boundaryConditions(*solver); solver->pressureChanged=true; solver->reApplyBoundaryConditions(); RTriangulation& tri = solver->T[solver->currentTes].Triangulation(); FiniteCellsIterator cellEnd = tri.finite_cells_end(); for ( FiniteCellsIterator cell = tri.finite_cells_begin(); cell != cellEnd; cell++ ) { if (cell->info().isWRes==true) {cell->info().p()=bndCondValue[2];} if (cell->info().isNWRes==true) {cell->info().p()=bndCondValue[3];} if (isPhaseTrapped) { if ( cell->info().isTrapW ) {cell->info().p()=bndCondValue[3]-cell->info().trapCapP;} if ( cell->info().isTrapNW) {cell->info().p()=bndCondValue[2]+cell->info().trapCapP;} //check cell reservoir info. if ( !cell->info().isWRes && !cell->info().isNWRes && !cell->info().isTrapW && !cell->info().isTrapNW ) {cerr<<"ERROR! NOT FIND Cell Info!";} // {cell->info().p()=bndCondValue[2]; if (isInvadeBoundary) cerr<<"Something wrong in updatePressure.(isInvadeBoundary)";} } } } void TwoPhaseFlowEngine::invasion() { if (isPhaseTrapped) invasion1(); else invasion2(); } ///mode1 and mode2 can share the same invasionSingleCell(), invasionSingleCell() ONLY change neighbor pressure and neighbor saturation, independent of reservoirInfo. void TwoPhaseFlowEngine::invasionSingleCell(CellHandle cell) { double localPressure=cell->info().p(); double localSaturation=cell->info().saturation; for (int facet = 0; facet < 4; facet ++) { CellHandle nCell = cell->neighbor(facet); if (solver->T[solver->currentTes].Triangulation().is_infinite(nCell)) continue; if (nCell->info().Pcondition) continue;//FIXME:defensive // if ( (nCell->info().isFictious) && (!isInvadeBoundary) ) continue; if (cell->info().poreThroatRadius[facet]<0) continue; if ( (nCell->info().saturation==localSaturation) && (nCell->info().p() != localPressure) && ((nCell->info().isTrapNW)||(nCell->info().isTrapW)) ) { nCell->info().p() = localPressure; if(solver->debugOut) {cerr<<"merge trapped phase"<info().saturation>localSaturation) ) { double nPcThroat=surfaceTension/cell->info().poreThroatRadius[facet]; double nPcBody=surfaceTension/nCell->info().poreBodyRadius; if( (localPressure-nCell->info().p()>nPcThroat) && (localPressure-nCell->info().p()>nPcBody) ) { nCell->info().p() = localPressure; nCell->info().saturation=localSaturation; nCell->info().hasInterface=false; if(solver->debugOut) {cerr<<"drainage"<info().p()>nPcThroat) && (localPressure-nCell->info().p()info().hasInterface==false) && (nCell->info().hasInterface==false) ) { // if(solver->debugOut) {cerr<<"invasion paused into pore interface "<info().hasInterface=true; // } // else continue; } else if ( (nCell->info().saturationinfo().poreThroatRadius[facet]; double nPcBody=surfaceTension/nCell->info().poreBodyRadius; if( (nCell->info().p()-localPressureinfo().p()-localPressureinfo().p() = localPressure; nCell->info().saturation=localSaturation; if(solver->debugOut) {cerr<<"imbibition"<info().p()-localPressureinfo().p()-localPressure>nPcThroat) /*&& (cell->info().hasInterface==false) && (nCell->info().hasInterface==false)*/ ) { // nCell->info().p() = localPressure; // nCell->info().saturation=localSaturation; // if(solver->debugOut) {cerr<<"imbibition paused pore interface"<info().hasInterface=true; // } // else continue; } else continue; } } ///invasion mode 1: withTrap void TwoPhaseFlowEngine::invasion1() { if(solver->debugOut) {cout<<"----start invasion1----"<debugOut) {cout<<"----invasion1.updatePressure----"<T[solver->currentTes].Triangulation(); FiniteCellsIterator cellEnd = tri.finite_cells_end(); if(isDrainageActivated) { for ( FiniteCellsIterator cell = tri.finite_cells_begin(); cell != cellEnd; cell++ ) { if(cell->info().isNWRes) invasionSingleCell(cell); } } if(isImbibitionActivated) { for ( FiniteCellsIterator cell = tri.finite_cells_begin(); cell != cellEnd; cell++ ) { if(cell->info().isWRes) invasionSingleCell(cell); } } if(solver->debugOut) {cout<<"----invasion1.invasionSingleCell----"<info().saturation updateReservoirs1(); if(solver->debugOut) {cout<<"----invasion1.update W, NW reservoirInfo----"<debugOut) {cout<<"----invasion1.checkWTrap----"<info().isTrapW ) {cell->info().p()=bndCondValue[3]-cell->info().trapCapP;} if ( cell->info().isTrapNW) {cell->info().p()=bndCondValue[2]+cell->info().trapCapP;} } if(solver->debugOut) {cout<<"----invasion1.update trapped W-phase/NW-phase Pressure----"<debugOut) {cout<<"----update cell labels----"<T[solver->currentTes].Triangulation(); FiniteCellsIterator cellEnd = tri.finite_cells_end(); for ( FiniteCellsIterator cell = tri.finite_cells_begin(); cell != cellEnd; cell++ ) { // if( (cell->info().isFictious) && (!cell->info().Pcondition) && (!isInvadeBoundary) ) continue; if( (cell->info().isWRes) || (cell->info().isNWRes) || (cell->info().isTrapW) || (cell->info().isTrapNW) ) continue; cell->info().trapCapP=pressure; if(cell->info().saturation==1.0) cell->info().isTrapW=true; if(cell->info().saturation==0.0) cell->info().isTrapNW=true; } } void TwoPhaseFlowEngine::updateReservoirs1() { RTriangulation& tri = solver->T[solver->currentTes].Triangulation(); FiniteCellsIterator cellEnd = tri.finite_cells_end(); for ( FiniteCellsIterator cell = tri.finite_cells_begin(); cell != cellEnd; cell++ ) { if(cell->info().Pcondition) continue; cell->info().isWRes = false; cell->info().isNWRes = false; } for (FlowSolver::VCellIterator it = solver->boundingCells[2].begin(); it != solver->boundingCells[2].end(); it++) { if ((*it)==NULL) continue; WResRecursion(*it); } for (FlowSolver::VCellIterator it = solver->boundingCells[3].begin(); it != solver->boundingCells[3].end(); it++) { if ((*it)==NULL) continue; NWResRecursion(*it); } } void TwoPhaseFlowEngine::WResRecursion(CellHandle cell) { for (int facet = 0; facet < 4; facet ++) { CellHandle nCell = cell->neighbor(facet); if (solver->T[solver->currentTes].Triangulation().is_infinite(nCell)) continue; if (nCell->info().Pcondition) continue; // if ( (nCell->info().isFictious) && (!isInvadeBoundary) ) continue; if (nCell->info().saturation != 1.0) continue; if (nCell->info().isWRes==true) continue; nCell->info().isWRes = true; nCell->info().isNWRes = false; nCell->info().isTrapW = false; nCell->info().trapCapP=0.0; WResRecursion(nCell); } } void TwoPhaseFlowEngine::NWResRecursion(CellHandle cell) { for (int facet = 0; facet < 4; facet ++) { CellHandle nCell = cell->neighbor(facet); if (solver->T[solver->currentTes].Triangulation().is_infinite(nCell)) continue; if (nCell->info().Pcondition) continue; // if ( (nCell->info().isFictious) && (!isInvadeBoundary) ) continue; if (nCell->info().saturation != 0.0) continue; if (nCell->info().isNWRes==true) continue; nCell->info().isNWRes = true; nCell->info().isWRes = false; nCell->info().isTrapNW = false; nCell->info().trapCapP=0.0; NWResRecursion(nCell); } } ///invasion mode 2: withoutTrap void TwoPhaseFlowEngine::invasion2() { if(solver->debugOut) {cout<<"----start invasion2----"<debugOut) {cout<<"----invasion2.updatePressure----"<T[solver->currentTes].Triangulation(); FiniteCellsIterator cellEnd = tri.finite_cells_end(); if(isDrainageActivated) { for ( FiniteCellsIterator cell = tri.finite_cells_begin(); cell != cellEnd; cell++ ) { if(cell->info().isNWRes) invasionSingleCell(cell); } } ///drainageSingleCell by Pressure difference, change Pressure and Saturation. if(isImbibitionActivated) { for ( FiniteCellsIterator cell = tri.finite_cells_begin(); cell != cellEnd; cell++ ) { if(cell->info().isWRes) invasionSingleCell(cell); } } if(solver->debugOut) {cout<<"----invasion2.invasionSingleCell----"<debugOut) {cout<<"----drainage2.update W, NW reservoirInfo----"<T[solver->currentTes].Triangulation(); FiniteCellsIterator cellEnd = tri.finite_cells_end(); for ( FiniteCellsIterator cell = tri.finite_cells_begin(); cell != cellEnd; cell++ ) { if (cell->info().p()==bndCondValue[2]) {cell->info().isWRes=true; cell->info().isNWRes=false;} else if (cell->info().p()==bndCondValue[3]) {cell->info().isNWRes=true; cell->info().isWRes=false;} else {cerr<<"drainage mode2: updateReservoir Error!"<T[solver->currentTes].Triangulation(); FiniteCellsIterator cellEnd = tri.finite_cells_end(); for ( FiniteCellsIterator cell = tri.finite_cells_begin(); cell != cellEnd; cell++ ) { if (cell->info().isNWRes == true) { for (int facet=0; facet<4; facet ++) { CellHandle nCell = cell->neighbor(facet); if (tri.is_infinite(nCell)) continue; if (nCell->info().Pcondition) continue; // if ( (nCell->info().isFictious) && (!isInvadeBoundary) ) continue; if ( nCell->info().isWRes == true && cell->info().poreThroatRadius[facet]>0) { double nCellP = std::max( (surfaceTension/cell->info().poreThroatRadius[facet]),(surfaceTension/nCell->info().poreBodyRadius) ); // double nCellP = surfaceTension/cell->info().poreThroatRadius[facet]; nextEntry = std::min(nextEntry,nCellP);}}}} if (nextEntry==1e50) { cout << "End drainage !" << endl; return nextEntry=0; } else return nextEntry; } double TwoPhaseFlowEngine::getMaxImbibitionPc() { double nextEntry = -1e50; RTriangulation& tri = solver->T[solver->currentTes].Triangulation(); FiniteCellsIterator cellEnd = tri.finite_cells_end(); for ( FiniteCellsIterator cell = tri.finite_cells_begin(); cell != cellEnd; cell++ ) { if (cell->info().isWRes == true) { for (int facet=0; facet<4; facet ++) { CellHandle nCell = cell->neighbor(facet); if (tri.is_infinite(nCell)) continue; if (nCell->info().Pcondition) continue; // if ( (nCell->info().isFictious) && (!isInvadeBoundary) ) continue; if ( nCell->info().isNWRes == true && cell->info().poreThroatRadius[facet]>0) { double nCellP = std::min( (surfaceTension/nCell->info().poreBodyRadius), (surfaceTension/cell->info().poreThroatRadius[facet])); nextEntry = std::max(nextEntry,nCellP);}}}} if (nextEntry==-1e50) { cout << "End imbibition !" << endl; return nextEntry=0; } else return nextEntry; } double TwoPhaseFlowEngine::getSaturation(bool isSideBoundaryIncluded) { if( (!isInvadeBoundary) && (isSideBoundaryIncluded)) cerr<<"In isInvadeBoundary=false drainage, isSideBoundaryIncluded can't set true."<T[solver->currentTes].Triangulation(); double poresVolume = 0.0; //total pores volume double wVolume = 0.0; //NW-phase volume FiniteCellsIterator cellEnd = tri.finite_cells_end(); for ( FiniteCellsIterator cell = tri.finite_cells_begin(); cell != cellEnd; cell++ ) { if (cell->info().Pcondition) continue; if ( (cell->info().isFictious) && (!isSideBoundaryIncluded) ) continue; poresVolume = poresVolume + cell->info().poreBodyVolume; if (cell->info().saturation>0.0) { wVolume = wVolume + cell->info().poreBodyVolume * cell->info().saturation; } } return wVolume/poresVolume; } ///compute forces void TwoPhaseFlowEngine::computeFacetPoreForcesWithCache(bool onlyCache) { RTriangulation& Tri = solver->T[solver->currentTes].Triangulation(); CVector nullVect(0,0,0); //reset forces if (!onlyCache) for (FiniteVerticesIterator v = Tri.finite_vertices_begin(); v != Tri.finite_vertices_end(); ++v) v->info().forces=nullVect; // #ifdef parallel_forces // if (solver->noCache) { // solver->perVertexUnitForce.clear(); solver->perVertexPressure.clear(); // solver->perVertexUnitForce.resize(solver->T[solver->currentTes].maxId+1); // solver->perVertexPressure.resize(solver->T[solver->currentTes].maxId+1);} // #endif // CellHandle neighbourCell; // VertexHandle mirrorVertex; CVector tempVect; //FIXME : Ema, be carefull with this (noCache), it needs to be turned true after retriangulation if (solver->noCache) {//WARNING:all currentTes must be solver->T[solver->currentTes], should NOT be solver->T[currentTes] for (FlowSolver::VCellIterator cellIt=solver->T[solver->currentTes].cellHandles.begin(); cellIt!=solver->T[solver->currentTes].cellHandles.end(); cellIt++) { CellHandle& cell = *cellIt; //reset cache for (int k=0; k<4; k++) cell->info().unitForceVectors[k]=nullVect; for (int j=0; j<4; j++) if (!Tri.is_infinite(cell->neighbor(j))) { const CVector& Surfk = cell->info().facetSurfaces[j]; //FIXME : later compute that fluidSurf only once in hydraulicRadius, for now keep full surface not modified in cell->info for comparison with other forces schemes //The ratio void surface / facet surface Real area = sqrt(Surfk.squared_length()); if (area<=0) cerr <<"AREA <= 0!! AREA="<& crossSections = cell->info().facetSphereCrossSections; CVector fluidSurfk = cell->info().facetSurfaces[j]*cell->info().facetFluidSurfacesRatio[j]; /// handle fictious vertex since we can get the projected surface easily here if (cell->vertex(j)->info().isFictious) { Real projSurf=std::abs(Surfk[solver->boundary(cell->vertex(j)->info().id()).coordinate]); tempVect=-projSurf*solver->boundary(cell->vertex(j)->info().id()).normal; cell->vertex(j)->info().forces = cell->vertex(j)->info().forces+tempVect*cell->info().p(); //define the cached value for later use with cache*p cell->info().unitForceVectors[j]=cell->info().unitForceVectors[j]+ tempVect; } /// Apply weighted forces f_k=sqRad_k/sumSqRad*f CVector facetUnitForce = -fluidSurfk*cell->info().solidLine[j][3]; CVector facetForce = cell->info().p()*facetUnitForce; for (int y=0; y<3; y++) { cell->vertex(facetVertices[j][y])->info().forces = cell->vertex(facetVertices[j][y])->info().forces + facetForce*cell->info().solidLine[j][y]; //add to cached value cell->info().unitForceVectors[facetVertices[j][y]]=cell->info().unitForceVectors[facetVertices[j][y]]+facetUnitForce*cell->info().solidLine[j][y]; //uncomment to get total force / comment to get only pore tension forces if (!cell->vertex(facetVertices[j][y])->info().isFictious) { cell->vertex(facetVertices[j][y])->info().forces = cell->vertex(facetVertices[j][y])->info().forces -facetNormal*cell->info().p()*crossSections[j][y]; //add to cached value cell->info().unitForceVectors[facetVertices[j][y]]=cell->info().unitForceVectors[facetVertices[j][y]]-facetNormal*crossSections[j][y]; } } // #ifdef parallel_forces // solver->perVertexUnitForce[cell->vertex(j)->info().id()].push_back(&(cell->info().unitForceVectors[j])); // solver->perVertexPressure[cell->vertex(j)->info().id()].push_back(&(cell->info().p())); // #endif } } solver->noCache=false;//cache should always be defined after execution of this function } if (onlyCache) return; // else {//use cached values when triangulation doesn't change // #ifndef parallel_forces for (FiniteCellsIterator cell = Tri.finite_cells_begin(); cell != Tri.finite_cells_end(); cell++) { for (int yy=0; yy<4; yy++) cell->vertex(yy)->info().forces = cell->vertex(yy)->info().forces + cell->info().unitForceVectors[yy]*cell->info().p(); } // #else // #pragma omp parallel for num_threads(ompThreads) // for (int vn=0; vn<= solver->T[solver->currentTes].maxId; vn++) { // VertexHandle& v = solver->T[solver->currentTes].vertexHandles[vn]; // const int& id = v->info().id(); // CVector tf (0,0,0); // int k=0; // for (vector::iterator c = solver->perVertexPressure[id].begin(); c != solver->perVertexPressure[id].end(); c++) // tf = tf + (*(solver->perVertexUnitForce[id][k++]))*(**c); // v->info().forces = tf; // } // #endif // } if (solver->debugOut) { CVector totalForce = nullVect; for (FiniteVerticesIterator v = Tri.finite_vertices_begin(); v != Tri.finite_vertices_end(); ++v) { if (!v->info().isFictious) totalForce = totalForce + v->info().forces; else if (solver->boundary(v->info().id()).flowCondition==1) totalForce = totalForce + v->info().forces; } cout << "totalForce = "<< totalForce << endl; } } bool TwoPhaseFlowEngine::detectBridge(RTriangulation::Finite_edges_iterator& edge) { bool dryBridgeExist=true; const RTriangulation& Tri = solver->T[solver->currentTes].Triangulation(); RTriangulation::Cell_circulator cell1 = Tri.incident_cells(*edge); RTriangulation::Cell_circulator cell0 = cell1++; if(cell0->info().saturation==1) {dryBridgeExist=false; return dryBridgeExist;} else { while (cell1!=cell0) { if(cell1->info().saturation==1) {dryBridgeExist=false;break;} else cell1++;} return dryBridgeExist; } } bool TwoPhaseFlowEngine::isCellNeighbor(unsigned int cell1, unsigned int cell2) { bool neighbor=false; for (unsigned int i=0;i<4;i++) { if (solver->T[solver->currentTes].cellHandles[cell1]->neighbor(i)->info().id==cell2) {neighbor=true;break;} } return neighbor; } void TwoPhaseFlowEngine::setPoreThroatRadius(unsigned int cell1, unsigned int cell2, double radius) { if (isCellNeighbor(cell1,cell2)==false) { cout<<"cell1 and cell2 are not neighbors."<T[solver->currentTes].cellHandles[cell1]->neighbor(i)->info().id==cell2) solver->T[solver->currentTes].cellHandles[cell1]->info().poreThroatRadius[i]=radius; if (solver->T[solver->currentTes].cellHandles[cell2]->neighbor(i)->info().id==cell1) solver->T[solver->currentTes].cellHandles[cell2]->info().poreThroatRadius[i]=radius;}} } double TwoPhaseFlowEngine::getPoreThroatRadius(unsigned int cell1, unsigned int cell2) { double r =-1.; if (isCellNeighbor(cell1,cell2)==false) { cout<<"cell1 and cell2 are not neighbors."<T[solver->currentTes].cellHandles[cell1]->neighbor(i)->info().id==cell2) r = solver->T[solver->currentTes].cellHandles[cell1]->info().poreThroatRadius[i];}} return r; } #endif //TwoPhaseFLOW trunk-2018.02b/pkg/pfv/TwoPhaseFlowEngine.hpp000066400000000000000000001033411324306050200207650ustar00rootroot00000000000000/************************************************************************* * Copyright (C) 2014 by Bruno Chareyre * * Copyright (C) 2013 by Thomas. Sweijen * * Copyright (C) 2012 by Chao Yuan * * * * This program is free software; it is licensed under the terms of the * * GNU General Public License v2 or later. See file LICENSE for details. * *************************************************************************/ // This is an example of how to derive a new FlowEngine with additional data and possibly completely new behaviour. // Every functions of the base engine can be overloaded, and new functions can be added //keep this #ifdef as long as you don't really want to realize a final version publicly, it will save compilation time for everyone else //when you want it compiled, you can pass -DTWOPHASEFLOW to cmake, or just uncomment the following line // #define TWOPHASEFLOW #ifdef TWOPHASEFLOW #include "FlowEngine_TwoPhaseFlowEngineT.hpp" // #include // #include // #include // #include // #include #include /// We can add data to the Info types by inheritance class TwoPhaseCellInfo : public FlowCellInfo_TwoPhaseFlowEngineT { public: bool isWRes;//Flags for marking cell(pore unit) state: isWettingReservoirCell, isNonWettingReservoirCell, isTrappedWettingCell, isTrappedNonWettingCell bool isNWRes; bool isTrapW; bool isTrapNW; double saturation;//the saturation of single pore (will be used in quasi-static imbibition and dynamic flow) double trapCapP;//for calculating the pressure of trapped pore, cell->info().p() = pressureNW- trapCapP. OR cell->info().p() = pressureW + trapCapP bool hasInterface; //Indicated whether a NW-W interface is present within the pore body std::vector poreThroatRadius; double poreBodyRadius; double poreBodyVolume; double porosity; int windowsID;//a temp cell info for experiment comparison(used by chao) double solidLine [4][4];//the length of intersecting line between sphere and facet. [i][j] is for facet "i" and sphere (facetVertices)"[i][j]". Last component [i][3] for 1/sumLines in the facet "i" (used by chao). //DynamicTwoPhaseFlow std::vector entryPressure; std::vector entrySaturation; std::vector poreNeighbors; std::vector poreIdConnectivity; std::vector listOfkNorm; std::vector listOfkNorm2; std::vector listOfEntrySaturation; std::vector listOfEntryPressure; std::vector kNorm2; std::vector listOfThroatArea; std::vector particleSurfaceArea; //Surface area of four particles enclosing one grain-based tetrahedra double accumulativeDVSwelling; double saturation2; int numberFacets; int isFictiousId; double mergedVolume; int mergednr; unsigned int mergedID; double thresholdSaturation; double flux; double accumulativeDV; double airWaterArea; bool isWResInternal; double conductivityWRes; double minSaturation; int poreId; bool airBC; bool waterBC; double thresholdPressure; double apparentSolidVolume; double dvSwelling; double dvTPF; bool isNWResDef; int invadedFrom; int label;//for marking disconnected clusters. NW-res: 0; W-res: 1; W-clusters by 2,3,4... TwoPhaseCellInfo (void) { saturation2 = 0.0; isFictiousId = 0; isWRes = true; isNWRes = false; isTrapW = false; isTrapNW = false; saturation = 1.0; hasInterface = false; trapCapP = 0; poreThroatRadius.resize(4, 0); kNorm2.resize(4,0); poreBodyRadius = 0; poreBodyVolume = 0; windowsID = 0; for (int k=0; k<4;k++) for (int l=0; l<4;l++) solidLine[k][l]=0; //dynamic TwoPhaseFlow airBC = false; waterBC = false; numberFacets = 4; mergedVolume = 0; mergednr = 0; mergedID = 0; apparentSolidVolume = 0.0; dvSwelling = 0.0; entryPressure.resize(4,0); entrySaturation.resize(4,0); poreIdConnectivity.resize(4,-1); particleSurfaceArea.resize(4,0); thresholdSaturation = 0.0; flux = 0.0; //NOTE can potentially be removed, currently not used but might be handy in future work accumulativeDV = 0.0; thresholdPressure = 0.0; airWaterArea = 0.0; accumulativeDV = 0.0; minSaturation = 0.0; poreId = -1; isWResInternal = false; dvTPF = 0.0; //FIXME dvTPF is currently only used to impose pressure as dv() cannot be imposed from FlowEngine currently isNWResDef = false; conductivityWRes = 0.0; invadedFrom = 0; label=-1; porosity=0.; } }; class TwoPhaseVertexInfo : public FlowVertexInfo_TwoPhaseFlowEngineT { public: //same here if needed }; typedef TemplateFlowEngine_TwoPhaseFlowEngineT TwoPhaseFlowEngineT; REGISTER_SERIALIZABLE(TwoPhaseFlowEngineT); // A class to represent isolated single-phase cluster (main application in convective drying at the moment) class PhaseCluster : public Serializable { double totalCellVolume; // CellHandle entryPoreHandle; public : virtual ~PhaseCluster(); vector pores; vector,double> > interfaces; TwoPhaseFlowEngineT::RTriangulation* tri; void reset() {label=entryPore=-1;volume=entryRadius=interfacialArea=0; pores.clear(); interfaces.clear();} vector getPores() { vector res; for (vector::iterator it = pores.begin(); it!=pores.end(); it++) res.push_back((*it)->info().id); return res;} boost::python::list getInterfaces(){ boost::python::list ints; for (vector,double> >::iterator it = interfaces.begin(); it!=interfaces.end(); it++) ints.append(boost::python::make_tuple(it->first.first,it->first.second,it->second)); return ints; } YADE_CLASS_BASE_DOC_ATTRS_INIT_CTOR_PY(PhaseCluster,Serializable,"Preliminary.", ((int,label,-1,,"Unique label of this cluster, should be reflected in pores of this cluster.")) ((double,volume,0,,"cumulated volume of all pores.")) ((double,entryRadius,0,,"smallest entry capillary pressure.")) ((int,entryPore,-1,,"the pore of the cluster incident to the throat with smallest entry Pc.")) ((double,interfacialArea,0,,"interfacial area of the cluster")) ,,, .def("getPores",&PhaseCluster::getPores,"get the list of pores by index") .def("getInterfaces",&PhaseCluster::getInterfaces,"get the list of interfacial pore-throats associated to a cluster, listed as [id1,id2,area] where id2 is the neighbor pore outside the cluster.") ) }; REGISTER_SERIALIZABLE(PhaseCluster); class TwoPhaseFlowEngine : public TwoPhaseFlowEngineT { public : double airBoundaryPressure = 0.0; std::vector listOfPores; bool imposeDeformationFluxTPFSwitch =false; double totalCellVolume; vector > clusters; // the list of clusters //We can overload every functions of the base engine to make it behave differently //if we overload action() like this, this engine is doing nothing in a standard timestep, it can still have useful functions virtual void action() {}; //If a new function is specific to the derived engine, put it here, else go to the base TemplateFlowEngine if it is useful for everyone void computePoreBodyVolume(); void computePoreBodyRadius(); void computeSolidLine(); void savePhaseVtk(const char* folder); //compute entry pore throat radius (drainage) void computePoreThroatRadiusMethod1();//MS-P method void computePoreThroatRadiusTrickyMethod1();//set the radius of pore throat between side pores negative. double computeEffPoreThroatRadius(CellHandle cell, int j); double computeEffPoreThroatRadiusFine(CellHandle cell, int j); double computeMSPRcByPosRadius(const Vector3r& posA, const double& rA, const Vector3r& posB, const double& rB, const Vector3r& posC, const double& rC); double computeTriRadian(double a, double b, double c); double computeEffRcByPosRadius(const Vector3r& posA, const double& rA, const Vector3r& posB, const double& rB, const Vector3r& posC, const double& rC){double reff=solver->computeEffectiveRadiusByPosRadius(makeCgPoint(posA),rA,makeCgPoint(posB),rB,makeCgPoint(posC),rC); return reff<0?1.0e-10:reff;}; double bisection(const Vector3r& posA, const double& rA, const Vector3r& posB, const double& rB, const Vector3r& posC, const double& rC, double a, double b); double computeDeltaForce(const Vector3r& posA, const double& rA, const Vector3r& posB, const double& rB, const Vector3r& posC, const double& rC, double r); void computePoreThroatRadiusMethod2();//radius of the inscribed circle void computePoreThroatRadiusMethod3();//radius of area-equivalent circle ///begin of invasion (mainly drainage) model void initialization(); void initializeReservoirs(); void invasion();//functions can be shared by two modes void invasionSingleCell(CellHandle cell); void updatePressure(); double getMinDrainagePc(); double getMaxImbibitionPc(); double getSaturation(bool isSideBoundaryIncluded=false); void invasion1();//with-trap void updateReservoirs1(); void WResRecursion(CellHandle cell); void NWResRecursion(CellHandle cell); void checkTrap(double pressure); void updateReservoirLabel(); void invasion2();//without-trap void updateReservoirs2(); ///end of invasion model //## Clusters ## void updateCellLabel(); void updateSingleCellLabelRecursion(CellHandle cell, PhaseCluster* cluster); void clusterGetFacet(PhaseCluster* cluster, CellHandle cell, int facet);//update cluster inetrfacial area and max entry radius wrt to a facet void clusterGetPore(PhaseCluster* cluster, CellHandle cell);//add pore to cluster, updating flags and cluster volume vector clusterInvadePore(PhaseCluster* cluster, CellHandle cell);//remove pore from cluster, if it splits the cluster in many pieces introduce new one(s) vector pyClusterInvadePore(int cellId) { int label = solver->T[solver->currentTes].cellHandles[cellId]->info().label; if (label<=1) {LOG_WARN("the pore is not in a cluster, label="<();} return clusterInvadePore(clusters[label].get(), solver->T[solver->currentTes].cellHandles[cellId]);} boost::python::list pyClusters(); // int getMaxCellLabel(); //compute forces void computeFacetPoreForcesWithCache(bool onlyCache=false); void computeCapillaryForce() {computeFacetPoreForcesWithCache(false);} //combine with pendular model boost::python::list getPotentialPendularSpheresPair() { RTriangulation& Tri = solver->T[solver->currentTes].Triangulation(); boost::python::list bridgeIds; FiniteEdgesIterator ed_it = Tri.finite_edges_begin(); for ( ; ed_it!=Tri.finite_edges_end(); ed_it++ ) { if (detectBridge(ed_it)==true) { const VertexInfo& vi1=(ed_it->first)->vertex(ed_it->second)->info(); const VertexInfo& vi2=(ed_it->first)->vertex(ed_it->third)->info(); const int& id1 = vi1.id(); const int& id2 = vi2.id(); bridgeIds.append(boost::python::make_tuple(id1,id2));}} return bridgeIds; } bool detectBridge(RTriangulation::Finite_edges_iterator& edge); //Library TwoPhaseFlow double getKappa(int numberFacets); double getChi(int numberFacets); double getLambda(int numberFacets); double getN(int numberFacets); double getDihedralAngle(int numberFacets); //Merging Library void mergeCells(); void countFacets(); void computeMergedVolumes(); void getMergedCellStats(); void calculateResidualSaturation(); void adjustUnresolvedPoreThroatsAfterMerging(); void actionMergingAlgorithm(); void checkVolumeConservationAfterMergingAlgorithm(); //Dynamic Engine void actionTPF(); void solvePressure(); void setBoundaryConditions(); void setInitialConditions(); void setPoreNetwork(); void setListOfPores(); void getQuantities(); double porePressureFromPcS(CellHandle cell, double saturation); double getSolidVolumeInCell(CellHandle cell); double getConstantC4(CellHandle cell); double getConstantC3(CellHandle cell); double dsdp(CellHandle cell, double pw); double poreSaturationFromPcS(CellHandle cell,double pw); void reTriangulate(); void readTriangulation(); void initializationTriangulation(); void assignWaterVolumesTriangulation(); void equalizeSaturationOverMergedCells(); void updatePoreUnitProperties(); void transferConditions(); void imposeDeformationFluxTPF(); void updateDeformationFluxTPF(); void copyPoreDataToCells(); void verifyCompatibilityBC(); void makeListOfPoresInCells(bool fast); std::vector leftOverVolumePerSphere; std::vector untreatedAreaPerSphere; std::vector finishedUpdating; std::vector waterVolume; std::vector< std::vector > tetrahedra; std::vector< std::vector > solidFractionSpPerTet; std::vector deltaVoidVolume; std::vector leftOverDVPerSphere; std::vector saturationList; std::vector hasInterfaceList; std::vector listOfFlux; std::vector listOfMergedVolume; Eigen::SparseMatrix aMatrix; typedef Eigen::Triplet ETriplet; std::vector tripletList; Eigen::SparseLU,Eigen::COLAMDOrdering > eSolver; int getCell2(double posX, double posY, double posZ){ //Should be fixed properly RTriangulation& tri = solver->T[solver->currentTes].Triangulation(); CellHandle cell = tri.locate(CGT::Sphere(posX,posY,posZ)); return cell->info().id; } boost::python::list cellporeThroatConductivity(unsigned int id){ // Temporary function to allow for simulations in Python, can be easily accessed in c++ boost::python::list ids; if (id>=solver->T[solver->currentTes].cellHandles.size()) {LOG_ERROR("id out of range, max value is "<T[solver->currentTes].cellHandles.size()); return ids;} for (unsigned int i=0;i<4;i++) ids.append(solver->T[solver->currentTes].cellHandles[id]->info().kNorm() [i]); return ids; } boost::python::list solidSurfaceAreaPerParticle(unsigned int id){ // Temporary function to allow for simulations in Python, can be easily accessed in c++ boost::python::list ids; if (id>=solver->T[solver->currentTes].cellHandles.size()) {LOG_ERROR("id out of range, max value is "<T[solver->currentTes].cellHandles.size()); return ids;} for (unsigned int i=0;i<4;i++) ids.append(solver->T[solver->currentTes].cellHandles[id]->info().particleSurfaceArea[i]); return ids; } //post-processing void savePoreNetwork(const char* folder); void saveVtk(const char* folder) {bool initT=solver->noCache; solver->noCache=false; solver->saveVtk(folder); solver->noCache=initT;} boost::python::list cellporeThroatRadius(unsigned int id){ // Temporary function to allow for simulations in Python, can be easily accessed in c++ boost::python::list ids; if (id>=solver->T[solver->currentTes].cellHandles.size()) {LOG_ERROR("id out of range, max value is "<T[solver->currentTes].cellHandles.size()); return ids;} for (unsigned int i=0;i<4;i++) ids.append(solver->T[solver->currentTes].cellHandles[id]->info().poreThroatRadius[i]); return ids; } boost::python::list getNeighbors(unsigned int id){ // Temporary function to allow for simulations in Python, can be easily accessed in c++ boost::python::list ids; const RTriangulation& Tri = solver->tesselation().Triangulation(); if (id>=solver->tesselation().cellHandles.size()) {LOG_ERROR("id out of range, max value is "<T[solver->currentTes].cellHandles.size()); return ids;} for (unsigned int i=0;i<4;i++) { const CellHandle& neighbourCell = solver->tesselation().cellHandles[id]->neighbor(i); if (!Tri.is_infinite(neighbourCell)) ids.append(neighbourCell->info().id);} return ids;} //TODO //Dynamic code boost::python::list cellEntrySaturation(unsigned int id){ // Temporary function to allow for simulations in Python, can be easily accessed in c++ boost::python::list ids; if (id>=solver->T[solver->currentTes].cellHandles.size()) {LOG_ERROR("id out of range, max value is "<T[solver->currentTes].cellHandles.size()); return ids;} for (unsigned int i=0;i<4;i++) ids.append(solver->T[solver->currentTes].cellHandles[id]->info().entrySaturation[i]); return ids; } //FIXME, needs to trigger initSolver() Somewhere, else changing flow.debug or other similar things after first calculation has no effect //FIXME, I removed indexing cells from inside UnsatEngine (SoluteEngine shouldl be ok (?)) in order to get pressure computed, problem is they are not indexed at all if flow is not calculated void computeOnePhaseFlow() {scene = Omega::instance().getScene().get(); if (!solver) cerr<<"no solver!"<gaussSeidel(scene->dt);initSolver(*solver);} ///manipulate/get/set on pore geometry bool isCellNeighbor(unsigned int cell1, unsigned int cell2); void setPoreThroatRadius(unsigned int cell1, unsigned int cell2, double radius); double getPoreThroatRadius(unsigned int cell1, unsigned int cell2); CELL_SCALAR_GETTER(bool,.isWRes,cellIsWRes) CELL_SCALAR_GETTER(bool,.isNWRes,cellIsNWRes) CELL_SCALAR_GETTER(bool,.isTrapW,cellIsTrapW) CELL_SCALAR_GETTER(bool,.isTrapNW,cellIsTrapNW) CELL_SCALAR_SETTER(bool,.isNWRes,setCellIsNWRes) CELL_SCALAR_SETTER(bool,.isWRes,setCellIsWRes) CELL_SCALAR_GETTER(Real,.saturation,cellSaturation) CELL_SCALAR_SETTER(Real,.saturation,setCellSaturation) CELL_SCALAR_GETTER(bool,.isFictious,cellIsFictious) //Temporary function to allow for simulations in Python CELL_SCALAR_GETTER(bool,.hasInterface,cellHasInterface) //Temporary function to allow for simulations in Python CELL_SCALAR_GETTER(Real,.poreBodyRadius,cellInSphereRadius) //Temporary function to allow for simulations in Python CELL_SCALAR_SETTER(Real,.poreBodyRadius,setPoreBodyRadius) //Temporary function to allow for simulations in Python, for lbm coupling. CELL_SCALAR_GETTER(Real,.poreBodyVolume,cellVoidVolume) //Temporary function to allow for simulations in Python CELL_SCALAR_GETTER(Real,.mergedVolume,cellMergedVolume) //Temporary function to allow for simulations in Python CELL_SCALAR_SETTER(Real,.dvTPF,setCellDV) //Temporary function to allow for simulations in Python CELL_SCALAR_GETTER(Real,.porosity,cellPorosity) CELL_SCALAR_SETTER(bool,.hasInterface,setCellHasInterface) //Temporary function to allow for simulations in Python CELL_SCALAR_GETTER(int,.label,cellLabel) CELL_SCALAR_GETTER(Real,.volume(),cellVolume) //Temporary function to allow for simulations in Python //Dynamic Code CELL_SCALAR_GETTER(Real,.thresholdSaturation,cellThresholdSaturation) //Temporary function to allow for simulations in Python CELL_SCALAR_GETTER(Real,.mergedID,cellMergedID) //Temporary function to allow for simulations in Python YADE_CLASS_BASE_DOC_ATTRS_INIT_CTOR_PY(TwoPhaseFlowEngine,TwoPhaseFlowEngineT,"documentation here", ((double,surfaceTension,0.0728,,"Water Surface Tension in contact with air at 20 Degrees Celsius is: 0.0728(N/m)")) ((bool,recursiveInvasion,true,,"If true the invasion stops only when no entry pc is less than current capillary pressure, implying simultaneous invasion of many pores. Else only one pore invasion per invasion step.")) ((bool,initialWetting,true,,"Initial wetting saturated (=true) or non-wetting saturated (=false)")) ((bool, isPhaseTrapped,true,,"If True, both phases can be entrapped by the other, which would correspond to snap-off. If false, both phases are always connected to their reservoirs, thus no snap-off.")) ((bool, isInvadeBoundary, true,,"Invasion side boundary condition. If True, pores of side boundary can be invaded; if False, the pore throats connecting side boundary are closed, those pores are excluded in saturation calculation.")) ((bool, drainageFirst, true,,"If true, activate drainage first (initial saturated), then imbibition; if false, activate imbibition first (initial unsaturated), then drainage.")) ((double,dtDynTPF,0.0,,"Parameter which stores the smallest time step, based on the residence time")) ((int,entryPressureMethod,1,,"integer to define the method used to determine the pore throat radii and the according entry pressures. 1)radius of entry pore throat based on MS-P method; 2) radius of the inscribed circle; 3) radius of the circle with equivalent surface area of the pore throat.")) // ((double,partiallySaturatedPores,false,,"Include partially saturated pores or not?")) //BEGIN Latest dynamic/pore merging things (to clean maybe) ((double,entryMethodCorrection,float(entryPressureMethod),,"Parameter that is used in computing entry pressure of a pore throat: P_ij = entryMethodCorrection * surfaceTension / radius_porethroat ")) //Dynamic TwoPhaseFlow ((vector, bndCondIsWaterReservoir, vector(6,false),,"Boundary conditions, if bndCondIsPressure[] = True, is it air or water boundary condition? True is water reservoir")) ((unsigned int,maxIDMergedCells,0,,"maximum number of merged ID, this is computed in mergeCells()")) ((double,waterPressurePartiallySatPores,0.0,,"water pressure based on the volume-averaged water pressure in partially-saturated pore units (i.e. pore units having an interface)")) ((double,waterPressure,0.0,,"Volume-averaged water pressure")) ((double,waterSaturation,0.0,,"Water saturation, excluding the boundary cells")) ((double,voidVolume,0.0,,"total void volume, excluding boundary cells")) ((bool,stopSimulation, false,,"Boolean to indicate that dynamic flow simulations cannot find a solution (or next time step). If True, stop simulations")) ((bool,debugTPF, false,,"Print debuging messages two phase flow engine ")) ((double,airWaterInterfacialArea,0.0,,"Air-water interfacial area, based on the pore-unit assembly and regular-shaped pore units")) ((double,areaAveragedPressure,0.0,,"Air-water interfacial area averaged water pressure ")) ((double,maximumRatioPoreThroatoverPoreBody,0.90,,"maximum ratio of pore throat radius over pore body radius, this is used during merging of tetrahedra.")) ((double,totalWaterVolume,0.0,,"total watervolume")) ((string,modelRunName,"dynamicDrainage",,"Name of simulation, to be implemented into output files")) ((double,safetyFactorTimeStep,1.0,,"Safey coefficient for time step")) ((double,fluxInViaWBC,0.0,,"Total water flux over water boundary conditions")) ((double, accumulativeFlux,0.0,,"accumulative influx of water")) ((double, truncationPrecision,1e-6,,"threshold at which a saturation is truncated")) ((unsigned int, numberOfPores, 0,,"Number of pores (i.e. number of tetrahedra, but compensated for merged tetrahedra")) ((bool, firstDynTPF, true,,"this bool activated the initialization of the dynamic flow engine, such as merging and defining initial values")) ((bool, keepTriangulation, false,,"this bool activated triangulation or not during initialization")) ((bool, remesh, false,,"update triangulation? -- YET TO BE IMPLEMENTED")) //FIXME - trinagulation of unsaturated pore units still to be implemented properly ((bool, deformation, false,,"Boolean to indicate whether simulations of dynamic flow are withing a deformating packing or not. If true, change of void volume due to deformation is considered in flow computations.")) ((int, iterationTPF, -1,,"Iteration number")) ((double, initialPC, 2000.0,,"Initial capillary pressure of the water-air inside the packing")) ((double, accumulativeDeformationFlux, 0.0,,"accumulative internal flux caused by deformation")) ((bool, solvePressureSwitch, true,,"solve for pressure during actionTPF()")) ((double, deltaTimeTruncation, 0.0,,"truncation of time step, to avoid very small time steps during local imbibition, NOTE it does affect the mass conservation not set to 0")) ((double, waterBoundaryPressure, 0.0,,"Water pressure at boundary used in computations, is set automaticaly, but this value can be used to change water pressure during simulations")) ((double, waterVolumeTruncatedLost, 0.0,,"Water volume that has been truncated.")) ((bool, getQuantitiesUpdateCont, false,,"Continuous update of various macro-scale quantities or not. Note that the updating quantities is computationally expensive")) ((double, simpleWaterPressure, 0.0,,"Water pressure based on averaging over pore volume")) ((double, centroidAverageWaterPressure, 0.0,,"Water pressure based on centroid-corrected averaging, see Korteland et al. (2010) - what is the correct definition of average pressure?")) ((double, fractionMinSaturationInvasion, -1.0,,"Set the threshold saturation at which drainage can occur (Sthr = fractionMinSaturationInvasion), note that -1 implied the conventional definition of Sthr")) ((vector, setFractionParticles, vector(scene->bodies->size(),0.0),,"Correction fraction for swelling of particles by mismatch of surface area of particles with those from actual surface area in pore units")) ((bool,primaryTPF, true,,"Boolean to indicate whether the initial conditions are for primary drainage of imbibition (dictated by drainageFirst) or secondary drainage or imbibition. Note that during simulations, a switch from drainage to imbibition or vise versa can easily be made by changing waterBoundaryPressure")) ((bool,swelling, false,,"If true, include swelling of particles during TPF computations")) //END Latest dynamic/pore merging ((bool, isCellLabelActivated, false,, "Activate cell labels for marking disconnected wetting clusters. NW-reservoir label 0; W-reservoir label 1; disconnected W-clusters label from 2. ")) ((bool, computeForceActivated, true,,"Activate capillary force computation. WARNING: turning off means capillary force is not computed at all, but the drainage can still work.")) ((bool, isDrainageActivated, true,, "Activates drainage.")) ((bool, isImbibitionActivated, false,, "Activates imbibition.")) ,/*TwoPhaseFlowEngineT()*/, clusters.resize(2); clusters[0]=shared_ptr(new PhaseCluster); clusters[1]=shared_ptr(new PhaseCluster); , .def("getCellIsFictious",&TwoPhaseFlowEngine::cellIsFictious,"Check the connection between pore and boundary. If true, pore throat connects the boundary.") .def("setCellIsNWRes",&TwoPhaseFlowEngine::setCellIsNWRes,"set status whether 'wetting reservoir' state") .def("setCellIsWRes",&TwoPhaseFlowEngine::setCellIsWRes,"set status whether 'wetting reservoir' state") .def("savePhaseVtk",&TwoPhaseFlowEngine::savePhaseVtk,(boost::python::arg("folder")="./phaseVtk"),"Save the saturation of local pores in vtk format. Sw(NW-pore)=0, Sw(W-pore)=1. Specify a folder name for output.") .def("getCellIsWRes",&TwoPhaseFlowEngine::cellIsWRes,"get status wrt 'wetting reservoir' state") .def("getCellIsNWRes",&TwoPhaseFlowEngine::cellIsNWRes,"get status wrt 'non-wetting reservoir' state") .def("getCellIsTrapW",&TwoPhaseFlowEngine::cellIsTrapW,"get status wrt 'trapped wetting phase' state") .def("getCellIsTrapNW",&TwoPhaseFlowEngine::cellIsTrapNW,"get status wrt 'trapped non-wetting phase' state") .def("getCellSaturation",&TwoPhaseFlowEngine::cellSaturation,"get saturation of one pore") .def("setCellSaturation",&TwoPhaseFlowEngine::setCellSaturation,"change saturation of one pore") .def("computeOnePhaseFlow",&TwoPhaseFlowEngine::computeOnePhaseFlow,"compute pressure and fluxes in the W-phase") .def("initialization",&TwoPhaseFlowEngine::initialization,"Initialize invasion setup. Build network, compute pore geometry info and initialize reservoir boundary conditions. ") .def("getPoreThroatRadiusList",&TwoPhaseFlowEngine::cellporeThroatRadius,(boost::python::arg("cell_ID")),"get 4 pore throat radii of a cell.") .def("getNeighbors",&TwoPhaseFlowEngine::getNeighbors,"get 4 neigboring cells") .def("getCellHasInterface",&TwoPhaseFlowEngine::cellHasInterface,"indicates whether a NW-W interface is present within the cell") .def("getCellInSphereRadius",&TwoPhaseFlowEngine::cellInSphereRadius,"get the radius of the inscribed sphere in a pore unit") .def("setPoreBodyRadius",&TwoPhaseFlowEngine::setPoreBodyRadius,"set the entry pore body radius.") .def("getCellVoidVolume",&TwoPhaseFlowEngine::cellVoidVolume,"get the volume of pore space in each pore unit") //Pore merging .def("getCellMergedVolume",&TwoPhaseFlowEngine::cellMergedVolume,"get the merged volume of pore space in each pore unit") .def("setCellHasInterface",&TwoPhaseFlowEngine::setCellHasInterface,"change wheter a cell has a NW-W interface") .def("savePoreNetwork",&TwoPhaseFlowEngine::savePoreNetwork,(boost::python::arg("folder")="./poreNetwork"),"Extract the pore network of the granular material (i.e. based on triangulation of the pore space") .def("reTriangulateSpheres",&TwoPhaseFlowEngine::reTriangulate,"apply triangulation, while maintaining saturation") .def("actionMergingAlgorithm",&TwoPhaseFlowEngine::actionMergingAlgorithm,"apply triangulation, while maintaining saturation") .def("getCell2",&TwoPhaseFlowEngine::getCell2,(boost::python::arg("pos")),"get id of the cell containing (X,Y,Z).") //should be removed finally, duplicate function .def("setCellDeltaVolume",&TwoPhaseFlowEngine::setCellDV,(boost::python::arg("id"),boost::python::arg("value")),"get id of the cell containing (X,Y,Z).") .def("mergeCells",&TwoPhaseFlowEngine::mergeCells,"Extract the pore network of the granular material") //Dynamic flow .def("calculateResidualSaturation",&TwoPhaseFlowEngine::calculateResidualSaturation,"Calculate the residual saturation for each pore body") .def("copyPoreDataToCells",&TwoPhaseFlowEngine::copyPoreDataToCells,"copy data from merged pore units back to grain-based tetrahedra, this should be done before exporting VTK files") .def("getCellEntrySaturation",&TwoPhaseFlowEngine::cellEntrySaturation,"get the entry saturation of each pore throat") .def("getCellThresholdSaturation",&TwoPhaseFlowEngine::cellThresholdSaturation,"get the saturation of imbibition") .def("getCellMergedID",&TwoPhaseFlowEngine::cellMergedID,"get the saturation of imbibition") .def("actionTPF",&TwoPhaseFlowEngine::actionTPF,"run 1 time step flow Engine") .def("getSolidSurfaceAreaPerParticle",&TwoPhaseFlowEngine::solidSurfaceAreaPerParticle,(boost::python::arg("cell_ID")),"get solid area inside a packing of particles") // .def("readTriangulation",&TwoPhaseFlowEngine::readTriangulation,"get the solid area of various solids in a pore") .def("imposeDeformationFluxTPF",&TwoPhaseFlowEngine::imposeDeformationFluxTPF,"Impose fluxes defined in dvTPF") .def("getCellPorosity",&TwoPhaseFlowEngine::cellPorosity,"get the porosity of individual cells.") .def("setCellHasInterface",&TwoPhaseFlowEngine::setCellHasInterface,"change wheter a cell has a NW-W interface") .def("getCellLabel",&TwoPhaseFlowEngine::cellLabel,"get cell label. 0 for NW-reservoir; 1 for W-reservoir; others for disconnected W-clusters.") .def("getMinDrainagePc",&TwoPhaseFlowEngine::getMinDrainagePc,"Get the minimum entry capillary pressure for the next drainage step.") .def("getMaxImbibitionPc",&TwoPhaseFlowEngine::getMaxImbibitionPc,"Get the maximum entry capillary pressure for the next imbibition step.") .def("getSaturation",&TwoPhaseFlowEngine::getSaturation,(boost::python::arg("isSideBoundaryIncluded")),"Get saturation of entire packing. If isSideBoundaryIncluded=false (default), the pores of side boundary are excluded in saturation calculating; if isSideBoundaryIncluded=true (only in isInvadeBoundary=true drainage mode), the pores of side boundary are included in saturation calculating.") .def("invasion",&TwoPhaseFlowEngine::invasion,"Run the drainage invasion.") .def("computeCapillaryForce",&TwoPhaseFlowEngine::computeCapillaryForce,"Compute capillary force. ") .def("saveVtk",&TwoPhaseFlowEngine::saveVtk,(boost::python::arg("folder")="./VTK"),"Save pressure field in vtk format. Specify a folder name for output.") .def("getPotentialPendularSpheresPair",&TwoPhaseFlowEngine::getPotentialPendularSpheresPair,"Get the list of sphere ID pairs of potential pendular liquid bridge.") // Clusters .def("getClusters",&TwoPhaseFlowEngine::pyClusters/*,(boost::python::arg("folder")="./VTK")*/,"Get the list of clusters.") .def("clusterInvadePore",&TwoPhaseFlowEngine::pyClusterInvadePore,boost::python::arg("cellId"),"drain the pore identified by cellId and update the clusters accordingly.") // others .def("getCellVolume",&TwoPhaseFlowEngine::cellVolume,"get the volume of each cell") .def("isCellNeighbor",&TwoPhaseFlowEngine::isCellNeighbor,(boost::python::arg("cell1_ID"), boost::python::arg("cell2_ID")),"check if cell1 and cell2 are neigbors.") .def("setPoreThroatRadius",&TwoPhaseFlowEngine::setPoreThroatRadius, (boost::python::arg("cell1_ID"), boost::python::arg("cell2_ID"), boost::python::arg("radius")), "set the pore throat radius between cell1 and cell2.") .def("getPoreThroatRadius",&TwoPhaseFlowEngine::getPoreThroatRadius, (boost::python::arg("cell1_ID"), boost::python::arg("cell2_ID")), "get the pore throat radius between cell1 and cell2.") .def("getEffRcByPosRadius",&TwoPhaseFlowEngine::computeEffRcByPosRadius, (boost::python::arg("position1"),boost::python::arg("radius1"),boost::python::arg("position2"),boost::python::arg("radius2"),boost::python::arg("position3"),boost::python::arg("radius3")), "get effective radius by three spheres position and radius.(inscribed sphere)") .def("getMSPRcByPosRadius",&TwoPhaseFlowEngine::computeMSPRcByPosRadius, (boost::python::arg("position1"),boost::python::arg("radius1"),boost::python::arg("position2"),boost::python::arg("radius2"),boost::python::arg("position3"),boost::python::arg("radius3")), "get entry radius wrt MSP method by three spheres position and radius.") ) DECLARE_LOGGER; }; REGISTER_SERIALIZABLE(TwoPhaseFlowEngine); #endif //TwoPhaseFLOW trunk-2018.02b/pkg/pfv/UnsaturatedEngine.cpp000066400000000000000000000445531324306050200207060ustar00rootroot00000000000000/************************************************************************* * Copyright (C) 2012 by Chao Yuan * * Copyright (C) 2012 by Bruno Chareyre * * * * This program is free software; it is licensed under the terms of the * * GNU General Public License v2 or later. See file LICENSE for details. * *************************************************************************/ #ifdef FLOW_ENGINE #include "TwoPhaseFlowEngine.hpp" //keep this #ifdef for commited versions unless you really have stable version that should be compiled by default //it will save compilation time for everyone else #ifdef TWOPHASEFLOW class UnsaturatedEngine : public TwoPhaseFlowEngine { public : double totalCellVolume; double computeCellInterfacialArea(CellHandle cell, int j, double rC); // void computeSolidLine(); //record and test functions void checkLatticeNodeY(double y); //temporary functions void initializeCellWindowsID(); double getWindowsSaturation(int i, bool isSideBoundaryIncluded=false); bool checknoCache() {return solver->noCache;} double getInvadeDepth(); double getSphericalSubdomainSaturation(Vector3r pos, double radius); double getCuboidSubdomainSaturation(Vector3r pos1, Vector3r pos2, bool isSideBoundaryIncluded); double getCuboidSubdomainPorosity(Vector3r pos1, Vector3r pos2, bool isSideBoundaryIncluded); double getSpecificInterfacialArea(); void printSomething(); virtual ~UnsaturatedEngine(); virtual void action(); YADE_CLASS_BASE_DOC_ATTRS_INIT_CTOR_PY(UnsaturatedEngine,TwoPhaseFlowEngine,"Preliminary version engine of a drainage model for unsaturated soils. Note:Air reservoir is on the top; water reservoir is on the bottom.(deprecated engine, use TwoPhaseFlowEngine instead)", ((int, windowsNo, 10,, "Number of genrated windows(or zoomed samples).")) ,,, .def("getSpecificInterfacialArea",&UnsaturatedEngine::getSpecificInterfacialArea,"get specific interfacial area (defined as the amount of fluid-fluid interfacial area per unit volume pf the porous medium).") .def("checkLatticeNodeY",&UnsaturatedEngine::checkLatticeNodeY,(boost::python::arg("y")),"Check the slice of lattice nodes for yNormal(y). 0: out of sphere; 1: inside of sphere.") .def("getInvadeDepth",&UnsaturatedEngine::getInvadeDepth,"Get NW-phase invasion depth. (the distance from NW-reservoir to front of NW-W interface.)") .def("getSphericalSubdomainSaturation",&UnsaturatedEngine::getSphericalSubdomainSaturation,(boost::python::arg("pos"),boost::python::arg("radius")),"Get saturation of spherical subdomain defined by (pos, radius). The subdomain exclude boundary pores.") .def("getCuboidSubdomainSaturation",&UnsaturatedEngine::getCuboidSubdomainSaturation,(boost::python::arg("pos1"),boost::python::arg("pos2"),boost::python::arg("isSideBoundaryIncluded")),"Get saturation of cuboid subdomain defined by (pos1,pos2). If isSideBoundaryIncluded=false, the pores of side boundary are excluded in saturation calculating; if isSideBoundaryIncluded=true (only in isInvadeBoundary=true drainage mode), the pores of side boundary are included in saturation calculating.") .def("getCuboidSubdomainPorosity",&UnsaturatedEngine::getCuboidSubdomainPorosity,(boost::python::arg("pos1"),boost::python::arg("pos2"),boost::python::arg("isSideBoundaryIncluded")),"Get the porosity of cuboid subdomain defined by (pos1,pos2). If isSideBoundaryIncluded=false, the pores of side boundary are excluded in porosity calculating; if isSideBoundaryIncluded=true (only in isInvadeBoundary=true drainage mode), the pores of side boundary are included in porosity calculating.") .def("checknoCache",&UnsaturatedEngine::checknoCache,"check noCache. (temporary function.)") .def("getWindowsSaturation",&UnsaturatedEngine::getWindowsSaturation,(boost::python::arg("windowsID"),boost::python::arg("isSideBoundaryIncluded")), "get saturation of subdomain with windowsID. If isSideBoundaryIncluded=false (default), the pores of side boundary are excluded in saturation calculating; if isSideBoundaryIncluded=true (only in isInvadeBoundary=true drainage mode), the pores of side boundary are included in saturation calculating.") .def("initializeCellWindowsID",&UnsaturatedEngine::initializeCellWindowsID,"Initialize cell windows index. A temporary function for comparison with experiments, will delete soon") .def("printSomething",&UnsaturatedEngine::printSomething,"print debug.") ) DECLARE_LOGGER; }; REGISTER_SERIALIZABLE(UnsaturatedEngine); YADE_PLUGIN((UnsaturatedEngine)); UnsaturatedEngine::~UnsaturatedEngine(){} /*void UnsaturatedEngine::initialDrainage() { cout<<"This is UnsaturatedEngine test program"<T[solver->currentTes].Triangulation(); if (tri.number_of_vertices()==0) { cout<< "triangulation is empty: building a new one" << endl; scene = Omega::instance().getScene().get();//here define the pointer to Yade's scene setPositionsBuffer(true);//copy sphere positions in a buffer... buildTriangulation(bndCondValue[2],*solver);//create a triangulation and initialize pressure in the elements, everything will be contained in "solver" initialReservoirs(); initializeCellIndex();//initialize cell index computePoreThroatRadius();//save all pore radii before drainage computeTotalPoresVolume();//save total volume of porous medium, considering different invading boundaries condition (isInvadeBoundary==True or False), aiming to calculate specific interfacial area. computePoreBodyVolume();//save capillary volume of all cells, for fast calculating saturation computeSolidLine();//save cell->info().solidLine[j][y] } solver->noCache = true; }*/ void UnsaturatedEngine::action() { /* //the drainage is in quasi-static regime, so it can work outside Omega. if ( !isActivated ) return; RTriangulation& tri = solver->T[solver->currentTes].Triangulation(); if ( (tri.number_of_vertices()==0) || (updateTriangulation) ) { cout<< "triangulation is empty: building a new one" << endl; scene = Omega::instance().getScene().get();//here define the pointer to Yade's scene setPositionsBuffer(true);//copy sphere positions in a buffer... buildTriangulation(bndCondValue[2],*solver);//create a triangulation and initialize pressure in the elements, everything will be contained in "solver" initialReservoirs(); initializeCellIndex();//initialize cell index computePoreThroatRadius();//save all pore radii before drainage computeTotalPoresVolume();//save total volume of porous medium, considering different invading boundaries condition (isInvadeBoundary==True or False), aiming to calculate specific interfacial area. computePoreBodyVolume();//save capillary volume of all cells, for calculating saturation computeSolidLine();//save cell->info().solidLine[j][y] solver->noCache = true; } ///compute drainage if (pressureForce) { drainage();} ///compute force if(computeForceActivated){ computeCapillaryForce(); Vector3r force; FiniteVerticesIterator vertices_end = solver->T[solver->currentTes].Triangulation().finite_vertices_end(); for ( FiniteVerticesIterator V_it = solver->T[solver->currentTes].Triangulation().finite_vertices_begin(); V_it != vertices_end; V_it++ ) { force = pressureForce ? Vector3r ( V_it->info().forces[0],V_it->info().forces[1],V_it->info().forces[2] ): Vector3r(0,0,0); scene->forces.addForce ( V_it->info().id(), force); }} */} double UnsaturatedEngine::getSpecificInterfacialArea() { RTriangulation& tri = solver->T[solver->currentTes].Triangulation(); FiniteCellsIterator cellEnd = tri.finite_cells_end(); double interfacialArea=0; for ( FiniteCellsIterator cell = tri.finite_cells_begin(); cell != cellEnd; cell++ ) { // if (cell->info().Pcondition==true) continue;//NOTE:reservoirs cells interfacialArea should not be included. if(cell->info().isFictious) continue; if (cell->info().isNWRes==true) { for (int facet = 0; facet < 4; facet ++) { if (tri.is_infinite(cell->neighbor(facet))) continue; if (cell->neighbor(facet)->info().Pcondition==true) continue; if ( (cell->neighbor(facet)->info().isFictious) && (!isInvadeBoundary) ) continue; if (cell->neighbor(facet)->info().isNWRes==false) interfacialArea = interfacialArea + computeCellInterfacialArea(cell, facet, cell->info().poreThroatRadius[facet]);}}} // cerr<<"InterArea:"<computeEffectiveRadius(cell, j)); CellHandle cellh = CellHandle(cell); int facetNFictious = solver->detectFacetFictiousVertices (cellh,j); if(facetNFictious==0) { RTriangulation& tri = solver->T[solver->currentTes].Triangulation(); if (tri.is_infinite(cell->neighbor(j))) return 0; Vector3r pos[3]; //solid pos double r[3]; //solid radius double rRc[3]; //r[i] + rC (rC: capillary radius) double e[3]; //edges of triangulation double rad[4][3]; //angle in radian for (int i=0; i<3; i++) { pos[i] = makeVector3r(cell->vertex(facetVertices[j][i])->point().point()); r[i] = sqrt(cell->vertex(facetVertices[j][i])->point().weight()); rRc[i] = r[i]+rC; } e[0] = (pos[1]-pos[2]).norm(); e[1] = (pos[2]-pos[0]).norm(); e[2] = (pos[1]-pos[0]).norm(); rad[3][0]=acos(((pos[1]-pos[0]).dot(pos[2]-pos[0]))/(e[2]*e[1])); rad[3][1]=acos(((pos[2]-pos[1]).dot(pos[0]-pos[1]))/(e[0]*e[2])); rad[3][2]=acos(((pos[0]-pos[2]).dot(pos[1]-pos[2]))/(e[1]*e[0])); rad[0][0]=computeTriRadian(e[0],rRc[1],rRc[2]); rad[0][1]=computeTriRadian(rRc[2],e[0],rRc[1]); rad[0][2]=computeTriRadian(rRc[1],rRc[2],e[0]); rad[1][0]=computeTriRadian(rRc[2],e[1],rRc[0]); rad[1][1]=computeTriRadian(e[1],rRc[0],rRc[2]); rad[1][2]=computeTriRadian(rRc[0],rRc[2],e[1]); rad[2][0]=computeTriRadian(rRc[1],e[2],rRc[0]); rad[2][1]=computeTriRadian(rRc[0],rRc[1],e[2]); rad[2][2]=computeTriRadian(e[2],rRc[0],rRc[1]); double sW0=0.5*rRc[1]*rRc[2]*sin(rad[0][0])-0.5*rad[0][0]*pow(rC,2)-0.5*rad[0][1]*pow(r[1],2)-0.5*rad[0][2]*pow(r[2],2) ; double sW1=0.5*rRc[2]*rRc[0]*sin(rad[1][1])-0.5*rad[1][1]*pow(rC,2)-0.5*rad[1][2]*pow(r[2],2)-0.5*rad[1][0]*pow(r[0],2) ; double sW2=0.5*rRc[0]*rRc[1]*sin(rad[2][2])-0.5*rad[2][2]*pow(rC,2)-0.5*rad[2][0]*pow(r[0],2)-0.5*rad[2][1]*pow(r[1],2) ; double sW=sW0+sW1+sW2; double sVoid=sqrt(cell->info().facetSurfaces[j].squared_length()) * cell->info().facetFluidSurfacesRatio[j]; double sInterface=sVoid-sW; return sInterface; } else { return Mathr::PI*pow(rInscribe,2); } } // ------------------for checking---------- void UnsaturatedEngine::checkLatticeNodeY(double y) { RTriangulation& tri = solver->T[solver->currentTes].Triangulation(); if((yyMin)||(y>solver->yMax)) { cerr<<"y is out of range! "<<"pleas set y between "<yMin<<" and "<yMax<xMax-solver->xMin)/N; double deltaZ = (solver->zMax-solver->zMin)/N; for (int j=0; jxMin+j*deltaX; double z=solver->zMin+k*deltaZ; int M=0; Vector3r LatticeNode = Vector3r(x,y,z); for (FiniteVerticesIterator V_it = tri.finite_vertices_begin(); V_it != tri.finite_vertices_end(); V_it++) { if(V_it->info().isFictious) continue; Vector3r SphereCenter = makeVector3r(V_it->point().point()); if ((LatticeNode-SphereCenter).squaredNorm() < V_it->point().weight()) { M=1; break;}} file << M;} file << "\n";} file.close();} } void UnsaturatedEngine::printSomething() { RTriangulation& Tri = solver->T[solver->currentTes].Triangulation(); FiniteEdgesIterator ed_it; for ( FiniteEdgesIterator ed_it = Tri.finite_edges_begin(); ed_it!=Tri.finite_edges_end();ed_it++ ) { const VertexInfo& vi1=(ed_it->first)->vertex(ed_it->second)->info(); const VertexInfo& vi2=(ed_it->first)->vertex(ed_it->third)->info(); const int& id1 = vi1.id(); const int& id2 = vi2.id(); cerr<T[solver->currentTes].Triangulation(); FiniteCellsIterator cellEnd = tri.finite_cells_end(); for ( FiniteCellsIterator cell = tri.finite_cells_begin(); cell != cellEnd; cell++ ) { for (int i=1; i<(windowsNo+1); i++) { if ( (cell->info()[1]>(solver->yMin+(i-1)*(solver->yMax-solver->yMin)/windowsNo) ) && (cell->info()[1] < (solver->yMin+i*(solver->yMax-solver->yMin)/windowsNo)) ) {cell->info().windowsID=i; break;} } } } double UnsaturatedEngine::getWindowsSaturation(int i, bool isSideBoundaryIncluded) { if( (!isInvadeBoundary) && (isSideBoundaryIncluded)) cerr<<"In isInvadeBoundary=false drainage, isSideBoundaryIncluded can't set true."<T[solver->currentTes].Triangulation(); FiniteCellsIterator cellEnd = tri.finite_cells_end(); for ( FiniteCellsIterator cell = tri.finite_cells_begin(); cell != cellEnd; cell++ ) { if (cell->info().Pcondition) continue; if ( (cell->info().isFictious) && (!isSideBoundaryIncluded) ) continue; if (cell->info().windowsID != i) continue; poresVolume = poresVolume + cell->info().poreBodyVolume; if (cell->info().saturation>0.0) { wVolume = wVolume + cell->info().poreBodyVolume * cell->info().saturation; } } return wVolume/poresVolume; } double UnsaturatedEngine::getCuboidSubdomainSaturation(Vector3r pos1, Vector3r pos2, bool isSideBoundaryIncluded) { if( (!isInvadeBoundary) && (isSideBoundaryIncluded)) cerr<<"In isInvadeBoundary=false drainage, isSideBoundaryIncluded can't set true."<T[solver->currentTes].Triangulation(); FiniteCellsIterator cellEnd = tri.finite_cells_end(); for ( FiniteCellsIterator cell = tri.finite_cells_begin(); cell != cellEnd; cell++ ) { if (cell->info().Pcondition) continue; if ( (cell->info().isFictious) && (!isSideBoundaryIncluded) ) continue; if ( ((pos1[0]-cell->info()[0])*(pos2[0]-cell->info()[0])<0) && ((pos1[1]-cell->info()[1])*(pos2[1]-cell->info()[1])<0) && ((pos1[2]-cell->info()[2])*(pos2[2]-cell->info()[2])<0) ) { poresVolume = poresVolume + cell->info().poreBodyVolume; if (cell->info().saturation>0.0) { wVolume = wVolume + cell->info().poreBodyVolume * cell->info().saturation; } } } return wVolume/poresVolume; } double UnsaturatedEngine::getCuboidSubdomainPorosity(Vector3r pos1, Vector3r pos2, bool isSideBoundaryIncluded) { if( (!isInvadeBoundary) && (isSideBoundaryIncluded)) cerr<<"In isInvadeBoundary=false drainage, isSideBoundaryIncluded can't set true."<T[solver->currentTes].Triangulation(); FiniteCellsIterator cellEnd = tri.finite_cells_end(); for ( FiniteCellsIterator cell = tri.finite_cells_begin(); cell != cellEnd; cell++ ) { if (cell->info().Pcondition) continue; if ( (cell->info().isFictious) && (!isSideBoundaryIncluded) ) continue; if ( ((pos1[0]-cell->info()[0])*(pos2[0]-cell->info()[0])<0) && ((pos1[1]-cell->info()[1])*(pos2[1]-cell->info()[1])<0) && ((pos1[2]-cell->info()[2])*(pos2[2]-cell->info()[2])<0) ) { totalCellVolume = totalCellVolume + std::abs( cell->info().volume() ); totalVoidVolume = totalVoidVolume + cell->info().poreBodyVolume; } } if(totalVoidVolume==0 || totalCellVolume==0) cerr<<"subdomain too small!"<T[solver->currentTes].Triangulation(); FiniteCellsIterator cellEnd = tri.finite_cells_end(); for ( FiniteCellsIterator cell = tri.finite_cells_begin(); cell != cellEnd; cell++ ) { if (cell->info().isNWRes) { yPosMax=std::max(yPosMax,cell->info()[1]); yPosMin=std::min(yPosMin,cell->info()[1]); } } return std::abs(yPosMax-yPosMin); } double UnsaturatedEngine::getSphericalSubdomainSaturation(Vector3r pos, double radius) { double poresVolume=0.0; double wVolume=0.0; RTriangulation& Tri = solver->T[solver->currentTes].Triangulation(); FiniteCellsIterator cellEnd = Tri.finite_cells_end(); for (FiniteCellsIterator cell = Tri.finite_cells_begin(); cell != cellEnd; cell++) { Vector3r cellPos = makeVector3r(cell->info()); double dist=(pos-cellPos).norm(); if(dist>radius) continue; if(cell->info().isFictious) { cerr<<"The radius of subdomain is too large, or the center of subdomain is out of packing. Please reset subdomain again."<info().poreBodyVolume; if(cell->info().saturation>0.0) { wVolume=wVolume+cell->info().poreBodyVolume * cell->info().saturation; } } return wVolume/poresVolume; } //--------------end of comparison with experiment---------------------------- #endif //TWOPHASEFLOW #endif //FLOW_ENGINE trunk-2018.02b/py/000077500000000000000000000000001324306050200136165ustar00rootroot00000000000000trunk-2018.02b/py/3rd-party/000077500000000000000000000000001324306050200154435ustar00rootroot00000000000000trunk-2018.02b/py/3rd-party/README000066400000000000000000000025261324306050200163300ustar00rootroot00000000000000pygts-0.3.1: homepage: http://pygts.sourceforge.net/ documentation: http://pygts.svn.sourceforge.net/viewvc/pygts/doc/gts.html license: GNU GPL v2 note: the local version is only the 'gts' directory of the source archive (doc, examples, test ommited). Distutils are not used for building either. patch: pygts.py has the following lines added at the beginning: # added by eudoxos 29.1.2011, fixes https://bugs.launchpad.net/yade/+bug/668329 ## force decimal separator to be always . (decimal point), not , (decimal comma) -- unless LC_ALL is set, then we are stuck ## this was reason for bogus gts imports ## adding to yade main does not solve the problem for some reason import locale locale.setlocale(locale.LC_NUMERIC,'C') boost-python-indexing-suite-v2-noSymlinkHeaders: homepage: http://www.language-binding.net/pyplusplus/pyplusplus.html (part of Py++-1.0.0) documentation: http://www.language-binding.net/pyplusplus/documentation/indexing_suite_v2.html.html source: https://pygccxml.svn.sourceforge.net/svnroot/pygccxml/pyplusplus_dev/indexing_suite_v2 revision 1755 license: Boost software license 1.0 note: only headers are copied here mtTkinter-0.4: homepage: http://tkinter.unpythonic.net/wiki/mtTkinter source: http://tkinter.unpythonic.net/wiki/mtTkinter?action=AttachFile&do=get&target=mtTkinter-0.4.tar.gz license: gpl lgpl trunk-2018.02b/py/3rd-party/mtTkinter-0.4/000077500000000000000000000000001324306050200177235ustar00rootroot00000000000000trunk-2018.02b/py/3rd-party/mtTkinter-0.4/gpl.txt000066400000000000000000001045131324306050200212520ustar00rootroot00000000000000 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 . trunk-2018.02b/py/3rd-party/mtTkinter-0.4/lgpl.txt000066400000000000000000000167431324306050200214350ustar00rootroot00000000000000 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. trunk-2018.02b/py/3rd-party/mtTkinter-0.4/mtTkinter.py000066400000000000000000000207531324306050200222650ustar00rootroot00000000000000'''Thread-safe version of Tkinter. Copyright (c) 2009, Allen B. Taylor This module is free software: you can redistribute it and/or modify it under the terms of the GNU Lesser 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 Lesser Public License for more details. You should have received a copy of the GNU Lesser Public License along with this program. If not, see . Usage: import mtTkinter as Tkinter # Use "Tkinter." as usual. or from mtTkinter import * # Use Tkinter module definitions as usual. This module modifies the original Tkinter module in memory, making all functionality thread-safe. It does this by wrapping the Tk class' tk instance with an object that diverts calls through an event queue when the call is issued from a thread other than the thread in which the Tk instance was created. The events are processed in the creation thread via an 'after' event. The modified Tk class accepts two additional keyword parameters on its __init__ method: mtDebug: 0 = No debug output (default) 1 = Minimal debug output ... 9 = Full debug output mtCheckPeriod: Amount of time in milliseconds (default 100) between checks for out-of-thread events when things are otherwise idle. Decreasing this value can improve GUI responsiveness, but at the expense of consuming more CPU cycles. Note that, because it modifies the original Tkinter module (in memory), other modules that use Tkinter (e.g., Pmw) reap the benefits automagically as long as mtTkinter is imported at some point before extra threads are created. Author: Allen B. Taylor, a.b.taylor@gmail.com ''' from Tkinter import * import threading import Queue class _Tk(object): """ Wrapper for underlying attribute tk of class Tk. """ def __init__(self, tk, mtDebug = 0, mtCheckPeriod = 10): self._tk = tk # Create the incoming event queue. self._eventQueue = Queue.Queue(1) # Identify the thread from which this object is being created so we can # tell later whether an event is coming from another thread. self._creationThread = threading.currentThread() # Store remaining values. self._debug = mtDebug self._checkPeriod = mtCheckPeriod def __getattr__(self, name): # Divert attribute accesses to a wrapper around the underlying tk # object. return _TkAttr(self, getattr(self._tk, name)) class _TkAttr(object): """ Thread-safe callable attribute wrapper. """ def __init__(self, tk, attr): self._tk = tk self._attr = attr def __call__(self, *args, **kwargs): """ Thread-safe method invocation. Diverts out-of-thread calls through the event queue. Forwards all other method calls to the underlying tk object directly. """ # Check if we're in the creation thread. if threading.currentThread() == self._tk._creationThread: # We're in the creation thread; just call the event directly. if self._tk._debug >= 8 or \ self._tk._debug >= 3 and self._attr.__name__ == 'call' and \ len(args) >= 1 and args[0] == 'after': print 'Calling event directly:', \ self._attr.__name__, args, kwargs return self._attr(*args, **kwargs) else: # We're in a different thread than the creation thread; enqueue # the event, and then wait for the response. responseQueue = Queue.Queue(1) if self._tk._debug >= 1: print 'Marshalling event:', self._attr.__name__, args, kwargs self._tk._eventQueue.put((self._attr, args, kwargs, responseQueue)) isException, response = responseQueue.get() # Handle the response, whether it's a normal return value or # an exception. if isException: exType, exValue, exTb = response raise exType, exValue, exTb else: return response # Define a hook for class Tk's __init__ method. def _Tk__init__(self, *args, **kwargs): # We support some new keyword arguments that the original __init__ method # doesn't expect, so separate those out before doing anything else. new_kwnames = ('mtCheckPeriod', 'mtDebug') new_kwargs = {} for name, value in kwargs.items(): if name in new_kwnames: new_kwargs[name] = value del kwargs[name] # Call the original __init__ method, creating the internal tk member. self.__original__init__mtTkinter(*args, **kwargs) # Replace the internal tk member with a wrapper that handles calls from # other threads. self.tk = _Tk(self.tk, **new_kwargs) # Set up the first event to check for out-of-thread events. self.after_idle(_CheckEvents, self) # Replace Tk's original __init__ with the hook. Tk.__original__init__mtTkinter = Tk.__init__ Tk.__init__ = _Tk__init__ def _CheckEvents(tk): "Event checker event." used = False try: # Process all enqueued events, then exit. while True: try: # Get an event request from the queue. method, args, kwargs, responseQueue = \ tk.tk._eventQueue.get_nowait() except: # No more events to process. break else: # Call the event with the given arguments, and then return # the result back to the caller via the response queue. used = True if tk.tk._debug >= 2: print 'Calling event from main thread:', \ method.__name__, args, kwargs try: responseQueue.put((False, method(*args, **kwargs))) except SystemExit, ex: raise SystemExit, ex except Exception, ex: # Calling the event caused an exception; return the # exception back to the caller so that it can be raised # in the caller's thread. from sys import exc_info exType, exValue, exTb = exc_info() responseQueue.put((True, (exType, exValue, exTb))) finally: # Schedule to check again. If we just processed an event, check # immediately; if we didn't, check later. if used: tk.after_idle(_CheckEvents, tk) else: tk.after(tk.tk._checkPeriod, _CheckEvents, tk) # Test thread entry point. def _testThread(root): text = "This is Tcl/Tk version %s" % TclVersion if TclVersion >= 8.1: try: text = text + unicode("\nThis should be a cedilla: \347", "iso-8859-1") except NameError: pass # no unicode support try: if root.globalgetvar('tcl_platform(threaded)'): text = text + "\nTcl is built with thread support" else: raise RuntimeError except: text = text + "\nTcl is NOT built with thread support" text = text + "\nmtTkinter works with or without Tcl thread support" label = Label(root, text=text) label.pack() button = Button(root, text="Click me!", command=lambda root=root: root.button.configure( text="[%s]" % root.button['text'])) button.pack() root.button = button quit = Button(root, text="QUIT", command=root.destroy) quit.pack() # The following three commands are needed so the window pops # up on top on Windows... root.iconify() root.update() root.deiconify() # Simulate button presses... button.invoke() root.after(1000, _pressOk, root, button) # Test button continuous press event. def _pressOk(root, button): button.invoke() try: root.after(1000, _pressOk, root, button) except: pass # Likely we're exiting # Test. Mostly borrowed from the Tkinter module, but the important bits moved # into a separate thread. if __name__ == '__main__': import threading root = Tk(mtDebug = 1) thread = threading.Thread(target = _testThread, args=(root,)) thread.start() root.mainloop() thread.join() trunk-2018.02b/py/3rd-party/pygts-0.3.1/000077500000000000000000000000001324306050200172465ustar00rootroot00000000000000trunk-2018.02b/py/3rd-party/pygts-0.3.1/__init__.py000066400000000000000000000067271324306050200213730ustar00rootroot00000000000000# pygts - python package for the manipulation of triangulated surfaces # # Copyright (C) 2009 Thomas J. Duck # All rights reserved. # # Thomas J. Duck # Department of Physics and Atmospheric Science, # Dalhousie University, Halifax, Nova Scotia, Canada, B3H 3J5 # # NOTICE # # This library is free software; you can redistribute it and/or # modify it under the terms of the GNU Library General Public # License as published by the Free Software Foundation; either # version 2 of the License, or (at your option) any later version. # # This library 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 # Library General Public License for more details. # # You should have received a copy of the GNU Library General Public # License along with this library; if not, write to the # Free Software Foundation, Inc., 59 Temple Place - Suite 330, # Boston, MA 02111-1307, USA. """A package for constructing and manipulating triangulated surfaces. PyGTS is a python binding for the GNU Triangulated Surface (GTS) Library, which may be used to build, manipulate, and perform computations on triangulated surfaces. The following geometric primitives are provided: * Point - a point in 3D space * Vertex - a Point in 3D space that may be used to define a Segment * Segment - a line defined by two Vertex end-points * Edge - a Segment that may be used to define the edge of a Triangle * Triangle - a triangle defined by three Edges * Face - a Triangle that may be used to define a face on a Surface * Surface - a surface composed of Faces A tetrahedron is assembled from these primitives as follows. First, create Vertices for each of the tetrahedron's points: .. code-block:: python import gts v1 = gts.Vertex(1,1,1) v2 = gts.Vertex(-1,-1,1) v3 = gts.Vertex(-1,1,-1) v4 = gts.Vertex(1,-1,-1) Next, connect the four vertices to create six unique Edges: .. code-block:: python e1 = gts.Edge(v1,v2) e2 = gts.Edge(v2,v3) e3 = gts.Edge(v3,v1) e4 = gts.Edge(v1,v4) e5 = gts.Edge(v4,v2) e6 = gts.Edge(v4,v3) The four triangular faces are composed using three edges each: .. code-block:: python f1 = gts.Face(e1,e2,e3) f2 = gts.Face(e1,e4,e5) f3 = gts.Face(e2,e5,e6) f4 = gts.Face(e3,e4,e6) Finally, the surface is assembled from the faces: .. code-block:: python s = gts.Surface() for face in [f1,f2,f3,f4]: s.add(face) Some care must be taken in the orientation of the faces. In the above example, the surface normals are pointing inward, and so the surface technically defines a void, rather than a solid. To create a tetrahedron with surface normals pointing outward, use the following instead: .. code-block:: python f1.revert() s = Surface() for face in [f1,f2,f3,f4]: if not face.is_compatible(s): face.revert() s.add(face) Once the Surface is constructed, there are many different operations that can be performed. For example, the volume can be calculated using: .. code-block:: python s.volume() The difference between two Surfaces s1 and s2 is given by: .. code-block:: python s3 = s2.difference(s1) Etc. It is also possible to read in GTS data files and plot surfaces to the screen. See the example programs packaged with PyGTS for more information. """ from pygts import * trunk-2018.02b/py/3rd-party/pygts-0.3.1/cleanup.c000066400000000000000000000346611324306050200210530ustar00rootroot00000000000000/* pygts - python package for the manipulation of triangulated surfaces * * Copyright (C) 1999 Stphane Popinet * Copyright (C) 2009 Thomas J. Duck * All rights reserved. * * Thomas J. Duck * Department of Physics and Atmospheric Science, * Dalhousie University, Halifax, Nova Scotia, Canada, B3H 3J5 * * NOTICE * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library 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 * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public * License along with this library; if not, write to the * Free Software Foundation, Inc., 59 Temple Place - Suite 330, * Boston, MA 02111-1307, USA. */ /* * Below are functions for cleaning up duplicated edges and faces on * a surface. This file contains modified functions from the GTS * distribution. */ #include "pygts.h" /** * Original documentation from GTS's vertex.c: * * gts_vertices_merge: * @vertices: a list of #GtsVertex. * @epsilon: half the size of the bounding box to consider for each vertex. * @check: function called for each pair of vertices about to be merged * or %NULL. * * For each vertex v in @vertices look if there are any vertex of * @vertices contained in a box centered on v of size 2*@epsilon. If * there are and if @check is not %NULL and returns %TRUE, replace * them with v (using gts_vertex_replace()), destroy them and remove * them from list. This is done efficiently using Kd-Trees. * * Returns: the updated list of vertices. */ /* This function is modified from the original in GTS in order to avoid * deallocating any objects referenced by the live-objects table. The * approach is similar to what is used for replace() in vertex.c. */ GList* pygts_vertices_merge(GList* vertices, gdouble epsilon, gboolean (* check) (GtsVertex *, GtsVertex *)) { GPtrArray *array; GList *i, *next; GNode *kdtree; GtsVertex *v; GtsBBox *bbox; GSList *selected, *j; GtsVertex *sv; PygtsObject *obj; PygtsVertex *vertex=NULL; GSList *parents=NULL, *ii,*cur; g_return_val_if_fail(vertices != NULL, 0); array = g_ptr_array_new(); i = vertices; while (i) { g_ptr_array_add(array, i->data); i = g_list_next(i); } kdtree = gts_kdtree_new(array, NULL); g_ptr_array_free(array, TRUE); i = vertices; while(i) { v = i->data; if (!GTS_OBJECT(v)->reserved) { /* Do something only if v is active */ /* build bounding box */ bbox = gts_bbox_new(gts_bbox_class(), v, GTS_POINT(v)->x - epsilon, GTS_POINT(v)->y - epsilon, GTS_POINT(v)->z - epsilon, GTS_POINT(v)->x + epsilon, GTS_POINT(v)->y + epsilon, GTS_POINT(v)->z + epsilon); /* select vertices which are inside bbox using kdtree */ j = selected = gts_kdtree_range(kdtree, bbox, NULL); while(j) { sv = j->data; if( sv!=v && !GTS_OBJECT(sv)->reserved && (!check||(*check)(sv, v)) ) { /* sv is not v and is active */ if( (obj = g_hash_table_lookup(obj_table,GTS_OBJECT(sv))) !=NULL ) { vertex = PYGTS_VERTEX(obj); /* Detach and save any parent segments */ ii = sv->segments; while(ii!=NULL) { cur = ii; ii = g_slist_next(ii); if(PYGTS_IS_PARENT_SEGMENT(cur->data)) { sv->segments = g_slist_remove_link(sv->segments, cur); parents = g_slist_prepend(parents,cur->data); g_slist_free_1(cur); } } } gts_vertex_replace(sv, v); GTS_OBJECT(sv)->reserved = sv; /* mark sv as inactive */ /* Reattach the parent segments */ if( vertex != NULL ) { ii = parents; while(ii!=NULL) { sv->segments = g_slist_prepend(sv->segments, ii->data); ii = g_slist_next(ii); } g_slist_free(parents); parents = NULL; } vertex = NULL; } j = g_slist_next(j); } g_slist_free(selected); gts_object_destroy(GTS_OBJECT(bbox)); } i = g_list_next(i); } gts_kdtree_destroy(kdtree); /* destroy inactive vertices and removes them from list */ /* we want to control vertex destruction */ gts_allow_floating_vertices = TRUE; i = vertices; while (i) { v = i->data; next = g_list_next(i); if(GTS_OBJECT(v)->reserved) { /* v is inactive */ if( g_hash_table_lookup(obj_table,GTS_OBJECT(v))==NULL ) { gts_object_destroy(GTS_OBJECT(v)); } else { GTS_OBJECT(v)->reserved = 0; } vertices = g_list_remove_link(vertices, i); g_list_free_1(i); } i = next; } gts_allow_floating_vertices = FALSE; return vertices; } static void build_list(gpointer data, GSList ** list) { *list = g_slist_prepend(*list, data); } static void build_list1(gpointer data, GList ** list) { *list = g_list_prepend(*list, data); } void pygts_vertex_cleanup(GtsSurface *s, gdouble threshold) { GList * vertices = NULL; /* merge vertices which are close enough */ /* build list of vertices */ gts_surface_foreach_vertex(s, (GtsFunc) build_list1, &vertices); /* merge vertices: we MUST update the variable vertices because this function modifies the list (i.e. removes the merged vertices). */ vertices = pygts_vertices_merge(vertices, threshold, NULL); /* free the list */ g_list_free(vertices); } void pygts_edge_cleanup(GtsSurface *s) { GSList *edges = NULL; GSList *i, *ii, *cur, *parents=NULL; PygtsEdge *edge; GtsEdge *e, *duplicate; g_return_if_fail(s != NULL); /* build list of edges */ gts_surface_foreach_edge(s, (GtsFunc)build_list, &edges); /* remove degenerate and duplicate edges. Note: we could use gts_edges_merge() to remove the duplicates and then remove the degenerate edges but it is more efficient to do everything at once (and it's more pedagogical too ...) */ /* We want to control manually the destruction of edges */ gts_allow_floating_edges = TRUE; i = edges; while(i) { e = i->data; if(GTS_SEGMENT(e)->v1 == GTS_SEGMENT(e)->v2) { /* edge is degenerate */ if( !g_hash_table_lookup(obj_table,GTS_OBJECT(e)) ) { /* destroy e */ gts_object_destroy(GTS_OBJECT(e)); } } else { if((duplicate = gts_edge_is_duplicate(e))) { /* Detach and save any parent triangles */ if( (edge = PYGTS_EDGE(g_hash_table_lookup(obj_table,GTS_OBJECT(e)))) !=NULL ) { ii = e->triangles; while(ii!=NULL) { cur = ii; ii = g_slist_next(ii); if(PYGTS_IS_PARENT_TRIANGLE(cur->data)) { e->triangles = g_slist_remove_link(e->triangles, cur); parents = g_slist_prepend(parents,cur->data); g_slist_free_1(cur); } } } /* replace e with its duplicate */ gts_edge_replace(e, duplicate); /* Reattach the parent segments */ if( edge != NULL ) { ii = parents; while(ii!=NULL) { e->triangles = g_slist_prepend(e->triangles, ii->data); ii = g_slist_next(ii); } g_slist_free(parents); parents = NULL; } if( !g_hash_table_lookup(obj_table,GTS_OBJECT(e)) ) { /* destroy e */ gts_object_destroy(GTS_OBJECT (e)); } } } i = g_slist_next(i); } /* don't forget to reset to default */ gts_allow_floating_edges = FALSE; /* free list of edges */ g_slist_free (edges); } void pygts_face_cleanup(GtsSurface * s) { GSList *triangles = NULL; GSList * i; g_return_if_fail(s != NULL); /* build list of triangles */ gts_surface_foreach_face(s, (GtsFunc) build_list, &triangles); /* remove duplicate and degenerate triangles */ i = triangles; while(i) { GtsTriangle * t = i->data; if (!gts_triangle_is_ok(t)) { /* destroy t, its edges (if not used by any other triangle) and its corners (if not used by any other edge) */ if( g_hash_table_lookup(obj_table,GTS_OBJECT(t))==NULL ) { gts_object_destroy(GTS_OBJECT(t)); } else { gts_surface_remove_face(PYGTS_SURFACE_AS_GTS_SURFACE(s),GTS_FACE(t)); } } i = g_slist_next(i); } /* free list of triangles */ g_slist_free(triangles); } /* old main program (below) - retained as an example of how to use the * functions above. */ /* cleanup - using a given threshold merge vertices which are too close. Eliminate degenerate and duplicate edges. Eliminate duplicate triangles . */ /* static int */ /* main (int argc, char * argv[]) */ /* { */ /* GtsSurface * s, * m; */ /* GList * vertices = NULL; */ /* gboolean verbose = FALSE, sever = FALSE, boundary = FALSE; */ /* gdouble threshold; */ /* int c = 0; */ /* GtsFile * fp; */ /* gboolean (* check) (GtsVertex *, GtsVertex *) = NULL; */ /* if (!setlocale (LC_ALL, "POSIX")) */ /* g_warning ("cannot set locale to POSIX"); */ /* s = gts_surface_new (gts_surface_class (), */ /* gts_face_class (), */ /* gts_edge_class (), */ /* gts_vertex_class ()); */ /* /\* parse options using getopt *\/ */ /* while (c != EOF) { */ /* #ifdef HAVE_GETOPT_LONG */ /* static struct option long_options[] = { */ /* {"2D", no_argument, NULL, 'c'}, */ /* {"boundary", no_argument, NULL, 'b'}, */ /* {"merge", required_argument, NULL, 'm'}, */ /* {"sever", no_argument, NULL, 's'}, */ /* {"help", no_argument, NULL, 'h'}, */ /* {"verbose", no_argument, NULL, 'v'}, */ /* { NULL } */ /* }; */ /* int option_index = 0; */ /* switch ((c = getopt_long (argc, argv, "hvsm:bc", */ /* long_options, &option_index))) { */ /* #else /\* not HAVE_GETOPT_LONG *\/ */ /* switch ((c = getopt (argc, argv, "hvsm:bc"))) { */ /* #endif /\* not HAVE_GETOPT_LONG *\/ */ /* case 'c': /\* 2D *\/ */ /* check = check_boundaries; */ /* break; */ /* case 'b': /\* boundary *\/ */ /* boundary = TRUE; */ /* break; */ /* case 's': /\* sever *\/ */ /* sever = TRUE; */ /* break; */ /* case 'm': { /\* merge *\/ */ /* FILE * fptr = fopen (optarg, "rt"); */ /* GtsFile * fp; */ /* if (fptr == NULL) { */ /* fprintf (stderr, "cleanup: cannot open file `%s' for merging\n", */ /* optarg); */ /* return 1; /\* failure *\/ */ /* } */ /* m = gts_surface_new (gts_surface_class (), */ /* gts_face_class (), */ /* gts_edge_class (), */ /* s->vertex_class); */ /* fp = gts_file_new (fptr); */ /* if (gts_surface_read (m, fp)) { */ /* fprintf (stderr, "cleanup: file `%s' is not a valid GTS file\n", */ /* optarg); */ /* fprintf (stderr, "%s:%d:%d: %s\n", */ /* optarg, fp->line, fp->pos, fp->error); */ /* return 1; /\* failure *\/ */ /* } */ /* gts_file_destroy (fp); */ /* fclose (fptr); */ /* gts_surface_merge (s, m); */ /* gts_object_destroy (GTS_OBJECT (m)); */ /* break; */ /* } */ /* case 'v': /\* verbose *\/ */ /* verbose = TRUE; */ /* break; */ /* case 'h': /\* help *\/ */ /* fprintf (stderr, */ /* "Usage: cleanup [OPTION] THRESHOLD < FILE\n" */ /* "Merge vertices of the GTS surface FILE if they are closer than THRESHOLD,\n" */ /* "eliminate degenerate, duplicate edges and duplicate triangles.\n" */ /* "\n" */ /* " -c --2D 2D boundary merging\n" */ /* " -b --boundary only consider boundary vertices for merging\n" */ /* " -s --sever sever \"contact\" vertices\n" */ /* " -m FILE --merge merge surface FILE\n" */ /* " -v --verbose print statistics about the surface\n" */ /* " -h --help display this help and exit\n" */ /* "\n" */ /* "Report bugs to %s\n", */ /* GTS_MAINTAINER); */ /* return 0; /\* success *\/ */ /* break; */ /* case '?': /\* wrong options *\/ */ /* fprintf (stderr, "Try `cleanup --help' for more information.\n"); */ /* return 1; /\* failure *\/ */ /* } */ /* } */ /* if (optind >= argc) { /\* missing threshold *\/ */ /* fprintf (stderr, */ /* "cleanup: missing THRESHOLD\n" */ /* "Try `cleanup --help' for more information.\n"); */ /* return 1; /\* failure *\/ */ /* } */ /* threshold = atof (argv[optind]); */ /* if (threshold < 0.0) { /\* threshold must be positive *\/ */ /* fprintf (stderr, */ /* "cleanup: THRESHOLD must be >= 0.0\n" */ /* "Try `cleanup --help' for more information.\n"); */ /* return 1; /\* failure *\/ */ /* } */ /* /\* read surface in *\/ */ /* m = gts_surface_new (gts_surface_class (), */ /* gts_face_class (), */ /* gts_edge_class (), */ /* s->vertex_class); */ /* fp = gts_file_new (stdin); */ /* if (gts_surface_read (m, fp)) { */ /* fputs ("cleanup: file on standard input is not a valid GTS file\n", */ /* stderr); */ /* fprintf (stderr, "stdin:%d:%d: %s\n", fp->line, fp->pos, fp->error); */ /* return 1; /\* failure *\/ */ /* } */ /* gts_surface_merge (s, m); */ /* gts_object_destroy (GTS_OBJECT (m)); */ /* /\* if verbose on print stats *\/ */ /* if (verbose) */ /* gts_surface_print_stats (s, stderr); */ /* /\* merge vertices which are close enough *\/ */ /* /\* build list of vertices *\/ */ /* gts_surface_foreach_vertex (s, boundary ? (GtsFunc) build_list2 : (GtsFunc) build_list1, */ /* &vertices); */ /* /\* merge vertices: we MUST update the variable vertices because this function */ /* modifies the list (i.e. removes the merged vertices). *\/ */ /* vertices = gts_vertices_merge (vertices, threshold, check); */ /* /\* free the list *\/ */ /* g_list_free (vertices); */ /* /\* eliminate degenerate and duplicate edges *\/ */ /* edge_cleanup (s); */ /* /\* eliminate duplicate triangles *\/ */ /* triangle_cleanup (s); */ /* if (sever) */ /* gts_surface_foreach_vertex (s, (GtsFunc) vertex_cleanup, NULL); */ /* /\* if verbose on print stats *\/ */ /* if (verbose) { */ /* GtsBBox * bb = gts_bbox_surface (gts_bbox_class (), s); */ /* gts_surface_print_stats (s, stderr); */ /* fprintf (stderr, "# Bounding box: [%g,%g,%g] [%g,%g,%g]\n", */ /* bb->x1, bb->y1, bb->z1, */ /* bb->x2, bb->y2, bb->z2); */ /* } */ /* /\* write surface *\/ */ /* gts_surface_write (s, stdout); */ /* return 0; /\* success *\/ */ /* } */ trunk-2018.02b/py/3rd-party/pygts-0.3.1/cleanup.h000066400000000000000000000031231324306050200210450ustar00rootroot00000000000000/* pygts - python package for the manipulation of triangulated surfaces * * Copyright (C) 2009 Thomas J. Duck * All rights reserved. * * Thomas J. Duck * Department of Physics and Atmospheric Science, * Dalhousie University, Halifax, Nova Scotia, Canada, B3H 3J5 * * NOTICE * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library 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 * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public * License along with this library; if not, write to the * Free Software Foundation, Inc., 59 Temple Place - Suite 330, * Boston, MA 02111-1307, USA. */ /* * Below are functions for cleaning up duplicated edges and faces on * a surface. This file was adapted from the example file of the same * name in the GTS distribution. */ #ifndef __PYGTS_CLEANUP_H__ #define __PYGTS_CLEANUP_H__ GList* pygts_vertices_merge(GList* vertices, gdouble epsilon, gboolean (* check) (GtsVertex *, GtsVertex *)); void pygts_vertex_cleanup(GtsSurface *s, gdouble threhold); void pygts_edge_cleanup(GtsSurface * s); void pygts_face_cleanup(GtsSurface * s); #endif /* __PYGTS_CLEANUP_H__ */ trunk-2018.02b/py/3rd-party/pygts-0.3.1/edge.c000066400000000000000000000377111324306050200203270ustar00rootroot00000000000000/* pygts - python package for the manipulation of triangulated surfaces * * Copyright (C) 2009 Thomas J. Duck * All rights reserved. * * Thomas J. Duck * Department of Physics and Atmospheric Science, * Dalhousie University, Halifax, Nova Scotia, Canada, B3H 3J5 * * NOTICE * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library 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 * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public * License along with this library; if not, write to the * Free Software Foundation, Inc., 59 Temple Place - Suite 330, * Boston, MA 02111-1307, USA. */ #include "pygts.h" #if PYGTS_DEBUG #define SELF_CHECK if(!pygts_edge_check((PyObject*)self)) { \ PyErr_SetString(PyExc_RuntimeError, \ "problem with self object (internal error)"); \ return NULL; \ } #else #define SELF_CHECK #endif /*-------------------------------------------------------------------------*/ /* Methods exported to python */ static PyObject* is_ok(PygtsEdge *self, PyObject *args) { if(pygts_edge_is_ok(self)) { Py_INCREF(Py_True); return Py_True; } else { Py_INCREF(Py_False); return Py_False; } } static PyObject* is_unattached(PygtsEdge *self, PyObject *args) { guint n; SELF_CHECK /* Check for attachments other than to the gtsobj_parent */ n = g_slist_length(PYGTS_EDGE_AS_GTS_EDGE(self)->triangles); if( n > 1 ) { Py_INCREF(Py_False); return Py_False; } else if( n == 1 ){ Py_INCREF(Py_True); return Py_True; } else { PyErr_SetString(PyExc_RuntimeError,"Edge lost parent (internal error)"); return NULL; } } /* replace() works, but can break Triangles and so is disabled */ /* static PyObject* */ /* replace(PygtsEdge *self, PyObject *args) */ /* { */ /* PyObject *e2_; */ /* PygtsEdge *e2; */ /* GSList *parents=NULL, *i, *cur; */ /* #if PYGTS_DEBUG */ /* if(!pygts_edge_check((PyObject*)self)) { */ /* PyErr_SetString(PyExc_TypeError, */ /* "problem with self object (internal error)"); */ /* return NULL; */ /* } */ /* #endif */ /* /\* Parse the args *\/ */ /* if(! PyArg_ParseTuple(args, "O", &e2_) ) { */ /* return NULL; */ /* } */ /* /\* Convert to PygtsObjects *\/ */ /* if(!pygts_edge_check(e2_)) { */ /* PyErr_SetString(PyExc_TypeError,"expected an Edge"); */ /* return NULL; */ /* } */ /* e2 = PYGTS_EDGE(e2_); */ /* if(PYGTS_OBJECT(self)->gtsobj!=PYGTS_OBJECT(e2)->gtsobj) { */ /* /\* (Ignore self-replacement) *\/ */ /* /\* Detach and save any parent triangles *\/ */ /* i = GTS_EDGE(PYGTS_OBJECT(self)->gtsobj)->triangles; */ /* while(i!=NULL) { */ /* cur = i; */ /* i = i->next; */ /* if(PYGTS_IS_PARENT_TRIANGLE(cur->data)) { */ /* GTS_EDGE(PYGTS_OBJECT(self)->gtsobj)->triangles = */ /* g_slist_remove_link(GTS_EDGE(PYGTS_OBJECT(self)->gtsobj)->triangles, */ /* cur); */ /* parents = g_slist_prepend(parents,cur->data); */ /* g_slist_free_1(cur); */ /* } */ /* } */ /* /\* Perform the replace operation *\/ */ /* gts_edge_replace(GTS_EDGE(PYGTS_OBJECT(self)->gtsobj), */ /* GTS_EDGE(PYGTS_OBJECT(e2)->gtsobj)); */ /* /\* Reattach the parent segments *\/ */ /* i = parents; */ /* while(i!=NULL) { */ /* GTS_EDGE(PYGTS_OBJECT(self)->gtsobj)->triangles = */ /* g_slist_prepend(GTS_EDGE(PYGTS_OBJECT(self)->gtsobj)->triangles, */ /* i->data); */ /* i = i->next; */ /* } */ /* g_slist_free(parents); */ /* } */ /* #if PYGTS_DEBUG */ /* if(!pygts_edge_check((PyObject*)self)) { */ /* PyErr_SetString(PyExc_TypeError, */ /* "problem with self object (internal error)"); */ /* return NULL; */ /* } */ /* #endif */ /* Py_INCREF(Py_None); */ /* return Py_None; */ /* } */ static PyObject* face_number(PygtsEdge *self, PyObject *args) { PyObject *s_; GtsSurface *s; SELF_CHECK /* Parse the args */ if(! PyArg_ParseTuple(args, "O", &s_) ) { return NULL; } /* Convert to PygtsObjects */ if(!pygts_surface_check(s_)) { PyErr_SetString(PyExc_TypeError,"expected a Surface"); return NULL; } s = PYGTS_SURFACE_AS_GTS_SURFACE(s_); return Py_BuildValue("i", gts_edge_face_number(PYGTS_EDGE_AS_GTS_EDGE(self),s)); } static PyObject* belongs_to_tetrahedron(PygtsEdge *self, PyObject *args) { SELF_CHECK if(gts_edge_belongs_to_tetrahedron(PYGTS_EDGE_AS_GTS_EDGE(self))) { Py_INCREF(Py_True); return Py_True; } else { Py_INCREF(Py_False); return Py_False; } } static PyObject* is_boundary(PygtsEdge *self, PyObject *args) { PyObject *s_; GtsSurface *s; SELF_CHECK /* Parse the args */ if(! PyArg_ParseTuple(args, "O", &s_) ) { return NULL; } /* Convert to PygtsObjects */ if(!pygts_surface_check(s_)) { PyErr_SetString(PyExc_TypeError,"expected a Surface"); return NULL; } s = PYGTS_SURFACE_AS_GTS_SURFACE(s_); /* Make the call and return */ if(gts_edge_is_boundary(PYGTS_EDGE_AS_GTS_EDGE(self),s)!=NULL) { Py_INCREF(Py_True); return Py_True; } else { Py_INCREF(Py_False); return Py_False; } } static PyObject* contacts(PygtsEdge *self, PyObject *args) { SELF_CHECK return Py_BuildValue("i", gts_edge_is_contact(PYGTS_EDGE_AS_GTS_EDGE(self))); } /* Methods table */ static PyMethodDef methods[] = { {"is_ok", (PyCFunction)is_ok, METH_NOARGS, "True if this Edge e is not degenerate or duplicate.\n" "False otherwise. Degeneracy implies e.v1.id == e.v2.id.\n" "\n" "Signature: e.is_ok()\n" }, {"is_unattached", (PyCFunction)is_unattached, METH_NOARGS, "True if this Edge e is not part of any Triangle.\n" "\n" "Signature: e.is_unattached()\n" }, /* Edge replace() method works but results in Triangles that are "not ok"; * i.e., they have edges that don't connect. We don't want that problem * and so the method has been disabled. */ /* {"replace", (PyCFunction)replace, METH_VARARGS, "Replaces this Edge e1 with Edge e2 in all Triangles that have e1.\n" "Edge e1 itself is left unchanged.\n" "\n" "Signature: e1.replace(e2).\n" }, */ {"face_number", (PyCFunction)face_number, METH_VARARGS, "Returns number of faces using this Edge e on Surface s.\n" "\n" "Signature: e.face_number(s)\n" }, {"is_boundary", (PyCFunction)is_boundary, METH_VARARGS, "Returns True if this Edge e is a boundary on Surface s.\n" "Otherwise False.\n" "\n" "Signature: e.is_boundary(s)\n" }, {"belongs_to_tetrahedron", (PyCFunction)belongs_to_tetrahedron, METH_NOARGS, "Returns True if this Edge e belongs to a tetrahedron.\n" "Otherwise False.\n" "\n" "Signature: e.belongs_to_tetrahedron()\n" }, {"contacts", (PyCFunction)contacts, METH_NOARGS, "Returns number of sets of connected triangles share this Edge e\n" "as a contact Edge.\n" "\n" "Signature: e.contacts()\n" }, {NULL} /* Sentinel */ }; /*-------------------------------------------------------------------------*/ /* Python type methods */ static GtsObject* parent(GtsEdge *e1); static PyObject * new(PyTypeObject *type, PyObject *args, PyObject *kwds) { PyObject *o; PygtsObject *obj; GtsEdge *tmp; GtsObject *edge=NULL; PyObject *v1_,*v2_; PygtsVertex *v1,*v2; guint alloc_gtsobj = TRUE; guint N; /* Parse the args */ if(kwds) { o = PyDict_GetItemString(kwds,"alloc_gtsobj"); if(o==Py_False) { alloc_gtsobj = FALSE; } if(o!=NULL) { PyDict_DelItemString(kwds, "alloc_gtsobj"); } } if(kwds) { Py_INCREF(Py_False); PyDict_SetItemString(kwds,"alloc_gtsobj", Py_False); } /* Allocate the gtsobj (if needed) */ if( alloc_gtsobj ) { /* Parse the args */ if( (N = PyTuple_Size(args)) < 2 ) { PyErr_SetString(PyExc_TypeError,"expected two Vertices"); return NULL; } v1_ = PyTuple_GET_ITEM(args,0); v2_ = PyTuple_GET_ITEM(args,1); /* Convert to PygtsObjects */ if(!pygts_vertex_check(v1_)) { PyErr_SetString(PyExc_TypeError,"expected two Vertices"); return NULL; } if(!pygts_vertex_check(v2_)) { PyErr_SetString(PyExc_TypeError,"expected two Vertices"); return NULL; } v1 = PYGTS_VERTEX(v1_); v2 = PYGTS_VERTEX(v2_); /* Error check */ if(PYGTS_OBJECT(v1)->gtsobj == PYGTS_OBJECT(v2)->gtsobj) { PyErr_SetString(PyExc_ValueError,"Vertices given are the same"); return NULL; } /* Create the GtsEdge */ edge = GTS_OBJECT(gts_edge_new(gts_edge_class(), GTS_VERTEX(v1->gtsobj), GTS_VERTEX(v2->gtsobj))); if( edge == NULL ) { PyErr_SetString(PyExc_MemoryError, "could not create Edge"); return NULL; } /* Check for duplicate */ tmp = gts_edge_is_duplicate(GTS_EDGE(edge)); if( tmp != NULL ) { gts_object_destroy(edge); edge = GTS_OBJECT(tmp); } /* If corresponding PyObject found in object table, we are done */ if( (obj=g_hash_table_lookup(obj_table,edge)) != NULL ) { Py_INCREF(obj); return (PyObject*)obj; } } /* Chain up */ obj = PYGTS_OBJECT(PygtsSegmentType.tp_new(type,args,kwds)); if( alloc_gtsobj ) { obj->gtsobj = edge; /* Create a parent GtsTriangle */ if( (obj->gtsobj_parent = parent(GTS_EDGE(obj->gtsobj))) == NULL ) { gts_object_destroy(obj->gtsobj); obj->gtsobj = NULL; return NULL; } pygts_object_register(PYGTS_OBJECT(obj)); } return (PyObject*)obj; } static int init(PygtsEdge *self, PyObject *args, PyObject *kwds) { gint ret; /* Chain up */ if( (ret=PygtsSegmentType.tp_init((PyObject*)self,args,kwds)) != 0 ){ return ret; } return 0; } /* Methods table */ PyTypeObject PygtsEdgeType = { PyObject_HEAD_INIT(NULL) 0, /* ob_size */ "gts.Edge", /* tp_name */ sizeof(PygtsEdge), /* tp_basicsize */ 0, /* tp_itemsize */ 0, /* tp_dealloc */ 0, /* tp_print */ 0, /* tp_getattr */ 0, /* tp_setattr */ 0, /* tp_compare */ 0, /* tp_repr */ 0, /* tp_as_number */ 0, /* tp_as_sequence */ 0, /* tp_as_mapping */ 0, /* tp_hash */ 0, /* tp_call */ 0, /* tp_str */ 0, /* tp_getattro */ 0, /* tp_setattro */ 0, /* tp_as_buffer */ Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, /* tp_flags */ "Edge object", /* tp_doc */ 0, /* tp_traverse */ 0, /* tp_clear */ 0, /* tp_richcompare */ 0, /* tp_weaklistoffset */ 0, /* tp_iter */ 0, /* tp_iternext */ methods, /* tp_methods */ 0, /* tp_members */ 0, /* tp_getset */ 0, /* tp_base */ 0, /* tp_dict */ 0, /* tp_descr_get */ 0, /* tp_descr_set */ 0, /* tp_dictoffset */ (initproc)init, /* tp_init */ 0, /* tp_alloc */ (newfunc)new /* tp_new */ }; /*-------------------------------------------------------------------------*/ /* Pygts functions */ gboolean pygts_edge_check(PyObject* o) { if(! PyObject_TypeCheck(o, &PygtsEdgeType)) { return FALSE; } else { #if PYGTS_DEBUG return pygts_edge_is_ok(PYGTS_EDGE(o)); #else return TRUE; #endif } } gboolean pygts_edge_is_ok(PygtsEdge *e) { GSList *parent; PygtsObject *obj; obj = PYGTS_OBJECT(e); if(!pygts_segment_is_ok(PYGTS_SEGMENT(e))) return FALSE; /* Check for a valid parent */ g_return_val_if_fail(obj->gtsobj_parent!=NULL,FALSE); g_return_val_if_fail(PYGTS_IS_PARENT_TRIANGLE(obj->gtsobj_parent),FALSE); parent = g_slist_find(GTS_EDGE(obj->gtsobj)->triangles, obj->gtsobj_parent); g_return_val_if_fail(parent!=NULL,FALSE); return TRUE; } static GtsObject* parent(GtsEdge *e1) { GtsVertex *v1,*v2,*v3; GtsPoint *p1,*p2; GtsEdge *e2, *e3; GtsTriangle *p; /* Create a third vertex for the triangle */ v1 = GTS_SEGMENT(e1)->v1; v2 = GTS_SEGMENT(e1)->v2; p1 = GTS_POINT(v1); p2 = GTS_POINT(v2); v3 = gts_vertex_new(pygts_parent_vertex_class(), p1->x+p2->x,p1->y+p2->y,p1->z+p2->z); if( v3 == NULL ) { PyErr_SetString(PyExc_MemoryError, "could not create Vertex"); return NULL; } /* Create another two edges */ if( (e2 = gts_edge_new(pygts_parent_edge_class(),v2,v3)) == NULL ) { PyErr_SetString(PyExc_MemoryError, "could not create Edge"); return NULL; } if( (e3 = gts_edge_new(pygts_parent_edge_class(),v3,v1)) == NULL ) { PyErr_SetString(PyExc_MemoryError, "could not create Edge"); gts_object_destroy(GTS_OBJECT(e2)); return NULL; } /* Create and return the parent */ if( (p = gts_triangle_new(pygts_parent_triangle_class(),e1,e2,e3)) == NULL ) { gts_object_destroy(GTS_OBJECT(e2)); gts_object_destroy(GTS_OBJECT(e3)); PyErr_SetString(PyExc_MemoryError, "could not create Triangle"); return NULL; } return GTS_OBJECT(p); } PygtsEdge * pygts_edge_new(GtsEdge *e) { PyObject *args, *kwds; PygtsObject *edge; /* Check for Edge in the object table */ if( (edge = PYGTS_OBJECT(g_hash_table_lookup(obj_table,GTS_OBJECT(e)))) !=NULL ) { Py_INCREF(edge); return PYGTS_EDGE(edge); } /* Build a new Edge */ args = Py_BuildValue("OO",Py_None,Py_None); kwds = Py_BuildValue("{s:O}","alloc_gtsobj",Py_False); edge = PYGTS_EDGE(PygtsEdgeType.tp_new(&PygtsEdgeType, args, kwds)); Py_DECREF(args); Py_DECREF(kwds); if( edge == NULL ) { PyErr_SetString(PyExc_MemoryError,"could not create Edge"); return NULL; } edge->gtsobj = GTS_OBJECT(e); /* Attach the parent */ if( (edge->gtsobj_parent = parent(e)) == NULL ) { Py_DECREF(edge); return NULL; } /* Register and return */ pygts_object_register(edge); return PYGTS_EDGE(edge); } GtsTriangleClass* pygts_parent_triangle_class(void) { static GtsTriangleClass *klass = NULL; GtsObjectClass *super = NULL; if (klass == NULL) { super = GTS_OBJECT_CLASS(gts_triangle_class()); GtsObjectClassInfo pygts_parent_triangle_info = { "PygtsParentTriangle", sizeof(PygtsParentTriangle), sizeof(GtsTriangleClass), (GtsObjectClassInitFunc)(super->info.class_init_func), (GtsObjectInitFunc)(super->info.object_init_func), (GtsArgSetFunc) NULL, (GtsArgGetFunc) NULL }; klass = gts_object_class_new(gts_object_class(), &pygts_parent_triangle_info); } return klass; } GtsEdgeClass* pygts_parent_edge_class(void) { static GtsEdgeClass *klass = NULL; GtsObjectClass *super = NULL; if (klass == NULL) { super = GTS_OBJECT_CLASS(pygts_parent_segment_class()); GtsObjectClassInfo pygts_parent_edge_info = { "PygtsParentEdge", sizeof(PygtsParentEdge), sizeof(GtsEdgeClass), (GtsObjectClassInitFunc)(super->info.class_init_func), (GtsObjectInitFunc)(super->info.object_init_func), (GtsArgSetFunc) NULL, (GtsArgGetFunc) NULL }; klass = gts_object_class_new(gts_object_class(), &pygts_parent_edge_info); } return klass; } trunk-2018.02b/py/3rd-party/pygts-0.3.1/edge.h000066400000000000000000000051331324306050200203250ustar00rootroot00000000000000/* pygts - python package for the manipulation of triangulated surfaces * * Copyright (C) 2009 Thomas J. Duck * All rights reserved. * * Thomas J. Duck * Department of Physics and Atmospheric Science, * Dalhousie University, Halifax, Nova Scotia, Canada, B3H 3J5 * * NOTICE * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library 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 * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public * License along with this library; if not, write to the * Free Software Foundation, Inc., 59 Temple Place - Suite 330, * Boston, MA 02111-1307, USA. */ #ifndef __PYGTS_EDGE_H__ #define __PYGTS_EDGE_H__ typedef struct _PygtsObject PygtsEdge; #define PYGTS_EDGE(obj) ((PygtsEdge*)obj) #define PYGTS_EDGE_AS_GTS_EDGE(o) (GTS_EDGE(PYGTS_OBJECT(o)->gtsobj)) extern PyTypeObject PygtsEdgeType; gboolean pygts_edge_check(PyObject* o); gboolean pygts_edge_is_ok(PygtsEdge *e); PygtsEdge* pygts_edge_new(GtsEdge *e); /*-------------------------------------------------------------------------*/ /* Parent GTS triangle for GTS edges */ /* Define a GtsTriangle subclass that can be readily identified as the parent * of an encapsulated GtsEdge. The pygts_parent_triangle_class() function * is defined at the bottom, and is what ultimately allows the distinction * to be made. This capability is used for edge replacement operations. */ typedef struct _GtsTriangle PygtsParentTriangle; #define PYGTS_PARENT_TRIANGLE(obj) GTS_OBJECT_CAST(obj,\ GtsTriangle,\ pygts_parent_triangle_class()) #define PYGTS_IS_PARENT_TRIANGLE(obj)(gts_object_is_from_class(obj,\ pygts_parent_triangle_class())) GtsTriangleClass* pygts_parent_triangle_class(void); /* GTS edges in parent triangles */ typedef struct _GtsEdge PygtsParentEdge; #define PYGTS_PARENT_EDGE(obj) GTS_OBJECT_CAST(obj,\ GtsEdge,\ pygts_parent_edge_class()) #define PYGTS_IS_PARENT_EDGE(obj)(gts_object_is_from_class(obj,\ pygts_parent_edge_class())) GtsEdgeClass* pygts_parent_edge_class(void); #endif /* __PYGTS_EDGE_H__ */ trunk-2018.02b/py/3rd-party/pygts-0.3.1/face.c000066400000000000000000000400471324306050200203150ustar00rootroot00000000000000/* pygts - python package for the manipulation of triangulated surfaces * * Copyright (C) 2009 Thomas J. Duck * All rights reserved. * * Thomas J. Duck * Department of Physics and Atmospheric Science, * Dalhousie University, Halifax, Nova Scotia, Canada, B3H 3J5 * * NOTICE * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library 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 * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public * License along with this library; if not, write to the * Free Software Foundation, Inc., 59 Temple Place - Suite 330, * Boston, MA 02111-1307, USA. */ #include "pygts.h" #if PYGTS_DEBUG #define SELF_CHECK if(!pygts_face_check((PyObject*)self)) { \ PyErr_SetString(PyExc_RuntimeError, \ "problem with self object (internal error)"); \ return NULL; \ } #else #define SELF_CHECK #endif /*-------------------------------------------------------------------------*/ /* Methods exported to python */ static PyObject* is_ok(PygtsFace *self, PyObject *args) { if(pygts_face_is_ok(self)) { Py_INCREF(Py_True); return Py_True; } else { Py_INCREF(Py_False); return Py_False; } } static PyObject* is_unattached(PygtsFace *self, PyObject *args) { guint n; /* Check for attachments other than to the gtsobj_parent */ n = g_slist_length(PYGTS_FACE_AS_GTS_FACE(self)->surfaces); if( n > 1 ) { Py_INCREF(Py_False); return Py_False; } else if( n == 1 ){ Py_INCREF(Py_True); return Py_True; } else { PyErr_SetString(PyExc_RuntimeError, "Face lost parent (internal error)"); return NULL; } } static PyObject* neighbor_number(PygtsFace *self, PyObject *args) { PyObject *s_=NULL; PygtsSurface *s=NULL; SELF_CHECK /* Parse the args */ if(! PyArg_ParseTuple(args, "O", &s_) ) return NULL; /* Convert to PygtsObjects */ if( pygts_surface_check(s_) ) { s = PYGTS_SURFACE(s_); } else { PyErr_SetString(PyExc_TypeError, "expected a Surface"); return NULL; } return Py_BuildValue("i", gts_face_neighbor_number(PYGTS_FACE_AS_GTS_FACE(self), PYGTS_SURFACE_AS_GTS_SURFACE(s))); } static PyObject* neighbors(PygtsFace *self, PyObject *args) { PyObject *s_=NULL; PygtsSurface *s=NULL; guint i,N; PyObject *tuple; GSList *faces,*f; PygtsFace *face; SELF_CHECK /* Parse the args */ if(! PyArg_ParseTuple(args, "O", &s_) ) return NULL; /* Convert to PygtsObjects */ if( pygts_surface_check(s_) ) { s = PYGTS_SURFACE(s_); } else { PyErr_SetString(PyExc_TypeError, "expected a Surface"); return NULL; } N = gts_face_neighbor_number(PYGTS_FACE_AS_GTS_FACE(self), PYGTS_SURFACE_AS_GTS_SURFACE(s)); if( (tuple=PyTuple_New(N)) == NULL) { PyErr_SetString(PyExc_MemoryError, "Could not create tuple"); return NULL; } /* Get the neighbors */ faces = gts_face_neighbors(PYGTS_FACE_AS_GTS_FACE(self), PYGTS_SURFACE_AS_GTS_SURFACE(s)); f = faces; for(i=0;idata))) == NULL ) { Py_DECREF(tuple); return NULL; } PyTuple_SET_ITEM(tuple, i, (PyObject*)face); f = g_slist_next(f); } return (PyObject*)tuple; } static PyObject* is_compatible(PygtsFace *self, PyObject *args) { PyObject *o1_=NULL; GtsEdge *e=NULL; PygtsTriangle *t=NULL; PygtsSurface *s=NULL; SELF_CHECK /* Parse the args */ if(! PyArg_ParseTuple(args, "O", &o1_) ) return NULL; /* Convert to PygtsObjects */ if( pygts_triangle_check(o1_) ) { t = PYGTS_TRIANGLE(o1_); } else { if( pygts_surface_check(o1_) ) { s = PYGTS_SURFACE(o1_); } else { PyErr_SetString(PyExc_TypeError, "expected a Triangle or Surface"); return NULL; } } if(t!=NULL) { if( (e = gts_triangles_common_edge(PYGTS_TRIANGLE_AS_GTS_TRIANGLE(self), PYGTS_TRIANGLE_AS_GTS_TRIANGLE(t))) == NULL ) { PyErr_SetString(PyExc_RuntimeError, "Faces do not share common edge"); return NULL; } if(gts_triangles_are_compatible(PYGTS_TRIANGLE_AS_GTS_TRIANGLE(self), PYGTS_TRIANGLE_AS_GTS_TRIANGLE(t), e)==TRUE) { Py_INCREF(Py_True); return Py_True; } } else { if(gts_face_is_compatible(PYGTS_FACE_AS_GTS_FACE(self), PYGTS_SURFACE_AS_GTS_SURFACE(s))==TRUE) { Py_INCREF(Py_True); return Py_True; } } Py_INCREF(Py_False); return Py_False; } static PyObject* is_on(PygtsFace *self, PyObject *args) { PyObject *s_=NULL; PygtsSurface *s=NULL; SELF_CHECK /* Parse the args */ if(! PyArg_ParseTuple(args, "O", &s_) ) return NULL; /* Convert to PygtsObjects */ if( pygts_surface_check(s_) ) { s = PYGTS_SURFACE(s_); } else { PyErr_SetString(PyExc_TypeError, "expected a Surface"); return NULL; } if( gts_face_has_parent_surface(PYGTS_FACE_AS_GTS_FACE(self), PYGTS_SURFACE_AS_GTS_SURFACE(s)) ) { Py_INCREF(Py_True); return Py_True; } else { Py_INCREF(Py_False); return Py_False; } } /* Methods table */ static PyMethodDef methods[] = { {"is_ok", (PyCFunction)is_ok, METH_NOARGS, "True if this Face f is non-degenerate and non-duplicate.\n" "False otherwise.\n" "\n" "Signature: f.is_ok()\n" }, {"is_unattached", (PyCFunction)is_unattached, METH_NOARGS, "True if this Face f is not part of any Surface.\n" "\n" "Signature: f.is_unattached().\n" }, {"neighbor_number", (PyCFunction)neighbor_number, METH_VARARGS, "Returns the number of neighbors of Face f belonging to Surface s.\n" "\n" "Signature: f.neighbor_number(s).\n" }, {"neighbors", (PyCFunction)neighbors, METH_VARARGS, "Returns a tuple of neighbors of this Face f belonging to Surface s.\n" "\n" "Signature: f.neighbors(s).\n" }, {"is_compatible", (PyCFunction)is_compatible, METH_VARARGS, "True if Face f is compatible with all neighbors in Surface s.\n" "False otherwise.\n" "\n" "Signature: f.is_compatible(s).\n" }, {"is_on", (PyCFunction)is_on, METH_VARARGS, "True if this Face f is on Surface s. False otherwise.\n" "\n" "Signature: f.is_on(s).\n" }, {NULL} /* Sentinel */ }; /*-------------------------------------------------------------------------*/ /* Python type methods */ static GtsObject * parent(GtsFace *face); static PyObject * new(PyTypeObject *type, PyObject *args, PyObject *kwds) { PyObject *o; PygtsObject *obj; guint alloc_gtsobj = TRUE; PyObject *o1_,*o2_,*o3_; GtsVertex *v1=NULL, *v2=NULL, *v3=NULL; GtsEdge *e1=NULL,*e2=NULL,*e3=NULL,*e; GtsSegment *s1,*s2,*s3; gboolean flag=FALSE; /* Flag when the args are gts.Point objects */ GtsFace *f; GtsTriangle *t; guint N; /* Parse the args */ if(kwds) { o = PyDict_GetItemString(kwds,"alloc_gtsobj"); if(o==Py_False) { alloc_gtsobj = FALSE; } if(o!=NULL) { PyDict_DelItemString(kwds, "alloc_gtsobj"); } } if(kwds) { Py_INCREF(Py_False); PyDict_SetItemString(kwds,"alloc_gtsobj", Py_False); } /* Allocate the gtsobj (if needed) */ if( alloc_gtsobj ) { /* Parse the args */ if( (N = PyTuple_Size(args)) < 3 ) { PyErr_SetString(PyExc_TypeError,"expected three Edges or three Vertices"); return NULL; } o1_ = PyTuple_GET_ITEM(args,0); o2_ = PyTuple_GET_ITEM(args,1); o3_ = PyTuple_GET_ITEM(args,2); /* Convert to PygtsObjects */ if( pygts_edge_check(o1_) ) { e1 = PYGTS_EDGE_AS_GTS_EDGE(o1_); } else { if( pygts_vertex_check(o1_) ) { v1 = PYGTS_VERTEX_AS_GTS_VERTEX(o1_); flag = TRUE; } } if( pygts_edge_check(o2_) ) { e2 = PYGTS_EDGE_AS_GTS_EDGE(o2_); } else { if( pygts_vertex_check(o2_) ) { v2 = PYGTS_VERTEX_AS_GTS_VERTEX(o2_); flag = TRUE; } } if( pygts_edge_check(o3_) ) { e3 = PYGTS_EDGE_AS_GTS_EDGE(o3_); } else { if(pygts_vertex_check(o3_)) { v3 = PYGTS_VERTEX_AS_GTS_VERTEX(o3_); flag = TRUE; } } /* Check for three edges or three vertices */ if( !((e1!=NULL && e2!=NULL && e3!=NULL) || (v1!=NULL && v2!=NULL && v3!=NULL)) ) { PyErr_SetString(PyExc_TypeError, "three Edge or three Vertex objects expected"); return NULL; } if(flag) { /* Create gts edges */ if( (e1 = gts_edge_new(gts_edge_class(),v1,v2)) == NULL ) { PyErr_SetString(PyExc_MemoryError, "could not create Edge"); return NULL; } if( (e2 = gts_edge_new(gts_edge_class(),v2,v3)) == NULL ) { PyErr_SetString(PyExc_MemoryError, "could not create Edge"); gts_object_destroy(GTS_OBJECT(e1)); return NULL; } if( (e3 = gts_edge_new(gts_edge_class(),v3,v1)) == NULL ) { PyErr_SetString(PyExc_MemoryError, "could not create Edge"); gts_object_destroy(GTS_OBJECT(e1)); gts_object_destroy(GTS_OBJECT(e2)); return NULL; } /* Check for duplicates */ if( (e = gts_edge_is_duplicate(e1)) != NULL ) { gts_object_destroy(GTS_OBJECT(e1)); e1 = e; } if( (e = gts_edge_is_duplicate(e2)) != NULL ) { gts_object_destroy(GTS_OBJECT(e2)); e2 = e; } if( (e = gts_edge_is_duplicate(e3)) != NULL ) { gts_object_destroy(GTS_OBJECT(e3)); e3 = e; } } /* Check that edges connect */ s1 = GTS_SEGMENT(e1); s2 = GTS_SEGMENT(e2); s3 = GTS_SEGMENT(e3); if( !((s1->v1==s3->v2 && s1->v2==s2->v1 && s2->v2==s3->v1) || (s1->v1==s3->v2 && s1->v2==s2->v2 && s2->v1==s3->v1) || (s1->v1==s3->v1 && s1->v2==s2->v1 && s2->v2==s3->v2) || (s1->v2==s3->v2 && s1->v1==s2->v1 && s2->v2==s3->v1) || (s1->v1==s3->v1 && s1->v2==s2->v2 && s2->v1==s3->v2) || (s1->v2==s3->v2 && s1->v1==s2->v2 && s2->v1==s3->v1) || (s1->v2==s3->v1 && s1->v1==s2->v1 && s2->v2==s3->v2) || (s1->v2==s3->v1 && s1->v1==s2->v2 && s2->v1==s3->v2)) ) { PyErr_SetString(PyExc_RuntimeError, "Edges in face must connect"); if(!g_hash_table_lookup(obj_table,GTS_OBJECT(e1))) { gts_object_destroy(GTS_OBJECT(e1)); } if(!g_hash_table_lookup(obj_table,GTS_OBJECT(e1))) { gts_object_destroy(GTS_OBJECT(e2)); } if(!g_hash_table_lookup(obj_table,GTS_OBJECT(e1))) { gts_object_destroy(GTS_OBJECT(e3)); } return NULL; } /* Create the GtsFace */ if( (f = gts_face_new(gts_face_class(),e1,e2,e3)) == NULL ) { PyErr_SetString(PyExc_MemoryError, "could not create Face"); if(!g_hash_table_lookup(obj_table,GTS_OBJECT(e1))) { gts_object_destroy(GTS_OBJECT(e1)); } if(!g_hash_table_lookup(obj_table,GTS_OBJECT(e1))) { gts_object_destroy(GTS_OBJECT(e2)); } if(!g_hash_table_lookup(obj_table,GTS_OBJECT(e1))) { gts_object_destroy(GTS_OBJECT(e3)); } return NULL; } /* Check for duplicate */ t = gts_triangle_is_duplicate(GTS_TRIANGLE(f)); if( t != NULL ) { gts_object_destroy(GTS_OBJECT(f)); if(!GTS_IS_FACE(t)) { PyErr_SetString(PyExc_TypeError, "expected a Face (internal error)"); } f = GTS_FACE(t); } /* If corresponding PyObject found in object table, we are done */ if( (obj=g_hash_table_lookup(obj_table,GTS_OBJECT(f))) != NULL ) { Py_INCREF(obj); return (PyObject*)obj; } } /* Chain up */ obj = PYGTS_OBJECT(PygtsTriangleType.tp_new(type,args,kwds)); if( alloc_gtsobj ) { obj->gtsobj = GTS_OBJECT(f); /* Create the parent GtsSurface */ if( (obj->gtsobj_parent = parent(GTS_FACE(obj->gtsobj))) == NULL ) { gts_object_destroy(obj->gtsobj); obj->gtsobj = NULL; return NULL; } pygts_object_register(PYGTS_OBJECT(obj)); } return (PyObject*)obj; } static int init(PygtsFace *self, PyObject *args, PyObject *kwds) { gint ret; /* Chain up */ if( (ret=PygtsTriangleType.tp_init((PyObject*)self,args,kwds)) != 0 ){ return ret; } return 0; } /* Methods table */ PyTypeObject PygtsFaceType = { PyObject_HEAD_INIT(NULL) 0, /* ob_size */ "gts.Face", /* tp_name */ sizeof(PygtsFace), /* tp_basicsize */ 0, /* tp_itemsize */ 0, /* tp_dealloc */ 0, /* tp_print */ 0, /* tp_getattr */ 0, /* tp_setattr */ 0, /* tp_compare */ 0, /* tp_repr */ 0, /* tp_as_number */ 0, /* tp_as_sequence */ 0, /* tp_as_mapping */ 0, /* tp_hash */ 0, /* tp_call */ 0, /* tp_str */ 0, /* tp_getattro */ 0, /* tp_setattro */ 0, /* tp_as_buffer */ Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, /* tp_flags */ "Face object", /* tp_doc */ 0, /* tp_traverse */ 0, /* tp_clear */ 0, /* tp_richcompare */ 0, /* tp_weaklistoffset */ 0, /* tp_iter */ 0, /* tp_iternext */ methods, /* tp_methods */ 0, /* tp_members */ 0, /* tp_getset */ 0, /* tp_base */ 0, /* tp_dict */ 0, /* tp_descr_get */ 0, /* tp_descr_set */ 0, /* tp_dictoffset */ (initproc)init, /* tp_init */ 0, /* tp_alloc */ (newfunc)new /* tp_new */ }; /*-------------------------------------------------------------------------*/ /* Pygts functions */ gboolean pygts_face_check(PyObject* o) { if(! PyObject_TypeCheck(o, &PygtsFaceType)) { return FALSE; } else { #if PYGTS_DEBUG return pygts_face_is_ok(PYGTS_FACE(o)); #else return TRUE; #endif } } gboolean pygts_face_is_ok(PygtsFace *f) { GSList *parent; PygtsObject *obj; obj = PYGTS_OBJECT(f); if(!pygts_triangle_is_ok(PYGTS_TRIANGLE(f))) return FALSE; /* Check for a valid parent */ g_return_val_if_fail(obj->gtsobj_parent!=NULL,FALSE); g_return_val_if_fail(GTS_IS_SURFACE(obj->gtsobj_parent),FALSE); parent = g_slist_find(GTS_FACE(obj->gtsobj)->surfaces, obj->gtsobj_parent); g_return_val_if_fail(parent!=NULL,FALSE); return TRUE; } static GtsObject * parent(GtsFace *face) { GtsSurface *p; p = gts_surface_new(gts_surface_class(), gts_face_class(), gts_edge_class(), gts_vertex_class()); if( p == NULL ) { PyErr_SetString(PyExc_MemoryError, "could not create parent"); return NULL; } gts_surface_add_face(p,face); return GTS_OBJECT(p); } PygtsFace * pygts_face_new(GtsFace *f) { PyObject *args, *kwds; PygtsObject *face; /* Check for Face in the object table */ if( (face=PYGTS_OBJECT(g_hash_table_lookup(obj_table,GTS_OBJECT(f)))) != NULL ) { Py_INCREF(face); return PYGTS_FACE(face); } /* Build a new Face */ args = Py_BuildValue("OOO",Py_None,Py_None,Py_None); kwds = Py_BuildValue("{s:O}","alloc_gtsobj",Py_False); face = PYGTS_OBJECT(PygtsFaceType.tp_new(&PygtsFaceType, args, kwds)); Py_DECREF(args); Py_DECREF(kwds); if( face == NULL ) { PyErr_SetString(PyExc_MemoryError, "could not create Face"); return NULL; } face->gtsobj = GTS_OBJECT(f); /* Attach the parent */ if( (face->gtsobj_parent = parent(f)) == NULL ) { Py_DECREF(face); return NULL; } /* Register and return */ pygts_object_register(face); return PYGTS_FACE(face); } trunk-2018.02b/py/3rd-party/pygts-0.3.1/face.h000066400000000000000000000030651324306050200203210ustar00rootroot00000000000000/* pygts - python package for the manipulation of triangulated surfaces * * Copyright (C) 2009 Thomas J. Duck * All rights reserved. * * Thomas J. Duck * Department of Physics and Atmospheric Science, * Dalhousie University, Halifax, Nova Scotia, Canada, B3H 3J5 * * NOTICE * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library 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 * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public * License along with this library; if not, write to the * Free Software Foundation, Inc., 59 Temple Place - Suite 330, * Boston, MA 02111-1307, USA. */ #ifndef __PYGTS_FACE_H__ #define __PYGTS_FACE_H__ #ifndef gts_face_is_unattached #define gts_face_is_unattached(f) ((f)->surfaces == NULL ? TRUE : FALSE) #endif typedef struct _PygtsObject PygtsFace; #define PYGTS_FACE(obj) ((PygtsFace*)obj) #define PYGTS_FACE_AS_GTS_FACE(o) (GTS_FACE(PYGTS_OBJECT(o)->gtsobj)) extern PyTypeObject PygtsFaceType; gboolean pygts_face_check(PyObject* o); gboolean pygts_face_is_ok(PygtsFace *f); PygtsFace* pygts_face_new(GtsFace *f); #endif /* __PYGTS_FACE_H__ */ trunk-2018.02b/py/3rd-party/pygts-0.3.1/object.c000066400000000000000000000145061324306050200206660ustar00rootroot00000000000000/* pygts - python package for the manipulation of triangulated surfaces * * Copyright (C) 2009 Thomas J. Duck * All rights reserved. * * Thomas J. Duck * Department of Physics and Atmospheric Science, * Dalhousie University, Halifax, Nova Scotia, Canada, B3H 3J5 * * NOTICE * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library 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 * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public * License along with this library; if not, write to the * Free Software Foundation, Inc., 59 Temple Place - Suite 330, * Boston, MA 02111-1307, USA. */ #include "pygts.h" /*-------------------------------------------------------------------------*/ /* Methods exported to python */ static PyObject* is_unattached(PygtsObject *self, PyObject *args, PyObject *kwds) { /* Objects are unattached by default */ Py_INCREF(Py_False); return Py_False; } /* Methods table */ static PyMethodDef methods[] = { {"is_unattached", (PyCFunction)is_unattached, METH_NOARGS, "True if this Object o is not attached to another Object.\n" "Otherwise False.\n" "\n" "Trace: o.is_unattached().\n" }, {NULL} /* Sentinel */ }; /*-------------------------------------------------------------------------*/ /* Attributes exported to python */ static PyObject * id(PygtsObject *self, void *closure) { if( self->gtsobj == NULL) { PyErr_SetString(PyExc_RuntimeError, "GTS object does not exist!"); return NULL; } /* Use the pointer of the gtsobj */ return Py_BuildValue("i",(long)(self->gtsobj)); } /* Methods table */ static PyGetSetDef getset[] = { {"id", (getter)id, NULL, "GTS object id", NULL}, {NULL} /* Sentinel */ }; /*-------------------------------------------------------------------------*/ /* Python type methods */ static void dealloc(PygtsObject* self) { /* De-register entry from the object table */ pygts_object_deregister(self); if(self->gtsobj_parent!=NULL) { /* Free the parent; GTS will free the child unless it is attached * to something else. */ gts_object_destroy(self->gtsobj_parent); self->gtsobj_parent=NULL; } else { /* We have the only reference, and so it is safe to destroy the gtsobj * (unless it was never created in the first place). */ if(self->gtsobj!=NULL) { gts_object_destroy(self->gtsobj); self->gtsobj=NULL; } } self->ob_type->tp_free((PyObject*)self); } static PyObject * new(PyTypeObject *type, PyObject *args, PyObject *kwds) { PygtsObject *self; /* Chain up object allocation */ self = PYGTS_OBJECT(type->tp_alloc(type, 0)); if( self == NULL ) return NULL; /* Object initialization */ self->gtsobj = NULL; self->gtsobj_parent = NULL; return (PyObject *)self; } static int init(PygtsObject *self, PyObject *args, PyObject *kwds) { if( self->gtsobj == NULL ) { PyErr_SetString(PyExc_RuntimeError, "Cannot create abstract Object"); return -1; } return 0; } static int compare(PygtsObject *o1, PygtsObject *o2) { if(o1->gtsobj==o2->gtsobj) { return 0; } else { return -1; } } /* Methods table */ PyTypeObject PygtsObjectType = { PyObject_HEAD_INIT(NULL) 0, /* ob_size */ "gts.Object" , /* tp_name */ sizeof(PygtsObject), /* tp_basicsize */ 0, /* tp_itemsize */ (destructor)dealloc, /* tp_dealloc */ 0, /* tp_print */ 0, /* tp_getattr */ 0, /* tp_setattr */ (cmpfunc)compare, /* tp_compare */ 0, /* tp_repr */ 0, /* tp_as_number */ 0, /* tp_as_sequence */ 0, /* tp_as_mapping */ 0, /* tp_hash */ 0, /* tp_call */ 0, /* tp_str */ 0, /* tp_getattro */ 0, /* tp_setattro */ 0, /* tp_as_buffer */ Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, /* tp_flags */ "Base object", /* tp_doc */ 0, /* tp_traverse */ 0, /* tp_clear */ 0, /* tp_richcompare */ 0, /* tp_weaklistoffset */ 0, /* tp_iter */ 0, /* tp_iternext */ methods, /* tp_methods */ 0, /* tp_members */ getset, /* tp_getset */ 0, /* tp_base: attached in pygts.c */ 0, /* tp_dict */ 0, /* tp_descr_get */ 0, /* tp_descr_set */ 0, /* tp_dictoffset */ (initproc)init, /* tp_init */ 0, /* tp_alloc */ (newfunc)new /* tp_new */ }; /*-------------------------------------------------------------------------*/ /* Pygts functions */ gboolean pygts_object_check(PyObject* o) { if(! PyObject_TypeCheck(o, &PygtsObjectType)) { return FALSE; } else { #if PYGTS_DEBUG return pygts_object_is_ok(PYGTS_OBJECT(o)); #else return TRUE; #endif } } gboolean pygts_object_is_ok(PygtsObject *o) { g_return_val_if_fail(o->gtsobj!=NULL,FALSE); g_return_val_if_fail(g_hash_table_lookup(obj_table,o->gtsobj)!=NULL,FALSE); return TRUE; } /*-------------------------------------------------------------------------*/ /* Object table functions */ GHashTable *obj_table; /* GtsObject key, associated PyObject value */ void pygts_object_register(PygtsObject *o) { if( g_hash_table_lookup(obj_table,o->gtsobj) == NULL ) { g_hash_table_insert(obj_table,o->gtsobj,o); } } void pygts_object_deregister(PygtsObject *o) { if(o->gtsobj!=NULL) { if(g_hash_table_lookup(obj_table,o->gtsobj)==o) { g_hash_table_remove(obj_table,o->gtsobj); } } } trunk-2018.02b/py/3rd-party/pygts-0.3.1/object.h000066400000000000000000000034231324306050200206670ustar00rootroot00000000000000/* pygts - python package for the manipulation of triangulated surfaces * * Copyright (C) 2009 Thomas J. Duck * All rights reserved. * * Thomas J. Duck * Department of Physics and Atmospheric Science, * Dalhousie University, Halifax, Nova Scotia, Canada, B3H 3J5 * * NOTICE * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library 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 * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public * License along with this library; if not, write to the * Free Software Foundation, Inc., 59 Temple Place - Suite 330, * Boston, MA 02111-1307, USA. */ #ifndef __PYGTS_OBJECT_H__ #define __PYGTS_OBJECT_H__ typedef struct _PygtsObject PygtsObject; typedef struct _PygtsMethods PygtsMethods; #define PYGTS_OBJECT(obj) ((PygtsObject*)obj) struct _PygtsObject { PyObject_HEAD GtsObject *gtsobj; /* Encapsulated GtsObject */ GtsObject *gtsobj_parent; /* A parent object to ensure persistence */ }; extern PyTypeObject PygtsObjectType; extern PygtsMethods PygtsObjectMethods; gboolean pygts_object_check(PyObject* o); gboolean pygts_object_is_ok(PygtsObject *o); extern GHashTable *obj_table; /* GtsObject key, associated PyObject value */ void pygts_object_register(PygtsObject *o); void pygts_object_deregister(PygtsObject *o); #endif /* __PYGTS_OBJECT_H__ */ trunk-2018.02b/py/3rd-party/pygts-0.3.1/point.c000066400000000000000000000706351324306050200205560ustar00rootroot00000000000000/* pygts - python point for the manipulation of triangulated surfaces * * Copyright (C) 2009 Thomas J. Duck * All rights reserved. * * Thomas J. Duck * Department of Physics and Atmospheric Science, * Dalhousie University, Halifax, Nova Scotia, Canada, B3H 3J5 * * NOTICE * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library 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 * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public * License along with this library; if not, write to the * Free Software Foundation, Inc., 59 Temple Place - Suite 330, * Boston, MA 02111-1307, USA. */ #include "pygts.h" #if PYGTS_DEBUG #define SELF_CHECK if(!pygts_point_check((PyObject*)self)) { \ PyErr_SetString(PyExc_RuntimeError, \ "problem with self object (internal error)"); \ return NULL; \ } #else #define SELF_CHECK #endif /*-------------------------------------------------------------------------*/ /* Methods exported to python */ static PyObject* is_ok(PygtsPoint *self, PyObject *args) { if(pygts_point_is_ok(self)) { Py_INCREF(Py_True); return Py_True; } else { Py_INCREF(Py_False); return Py_False; } } static PyObject* set(PygtsPoint *self, PyObject *args) { gdouble x=0,y=0,z=0; SELF_CHECK /* Parse the args */ if(! PyArg_ParseTuple(args, "|ddd", &x,&y,&z)) { return NULL; } gts_point_set(PYGTS_POINT_AS_GTS_POINT(self),x,y,z); Py_INCREF(Py_None); return Py_None; } static PyObject* coords(PygtsPoint *self, PyObject *args) { SELF_CHECK return Py_BuildValue("ddd",PYGTS_POINT_AS_GTS_POINT(self)->x, PYGTS_POINT_AS_GTS_POINT(self)->y, PYGTS_POINT_AS_GTS_POINT(self)->z); } static PyObject* is_in_rectangle(PygtsPoint* self, PyObject *args) { PyObject *o1_,*o2_; PygtsPoint *p1, *p2; gboolean flag = FALSE; gdouble x,y; SELF_CHECK /* Parse the args */ if(! PyArg_ParseTuple(args, "OO", &o1_, &o2_) ) { return NULL; } /* Convert to PygtsObjects */ if(!(pygts_point_check(o1_) && pygts_point_check(o2_))) { PyErr_SetString(PyExc_TypeError,"expected two Points"); return NULL; } p1 = PYGTS_POINT(o1_); p2 = PYGTS_POINT(o2_); /* Test if point *may* be on rectangle perimeter */ x = PYGTS_POINT_AS_GTS_POINT(self)->x; y = PYGTS_POINT_AS_GTS_POINT(self)->y; if( PYGTS_POINT_AS_GTS_POINT(p1)->x == x || PYGTS_POINT_AS_GTS_POINT(p1)->y == y || PYGTS_POINT_AS_GTS_POINT(p2)->x == x || PYGTS_POINT_AS_GTS_POINT(p2)->y == y ) { flag = TRUE; } if( gts_point_is_in_rectangle(PYGTS_POINT_AS_GTS_POINT(self), PYGTS_POINT_AS_GTS_POINT(p1), PYGTS_POINT_AS_GTS_POINT(p2)) ) { if(flag) { return Py_BuildValue("i",0); } else { return Py_BuildValue("i",1); } } else { if( flag && gts_point_is_in_rectangle(PYGTS_POINT_AS_GTS_POINT(self), PYGTS_POINT_AS_GTS_POINT(p2), PYGTS_POINT_AS_GTS_POINT(p1))) { return Py_BuildValue("i",0); } else { return Py_BuildValue("i",-1); } } } static PyObject* distance(PygtsPoint* self, PyObject *args) { PyObject *o_; PygtsPoint *p=NULL; PygtsSegment *s=NULL; PygtsTriangle *t=NULL; SELF_CHECK /* Parse the args */ if(! PyArg_ParseTuple(args, "O", &o_) ) { return NULL; } /* Convert to PygtsObjects */ if(pygts_point_check(o_)) { p = PYGTS_POINT(o_); } else { if(pygts_segment_check(o_)) { s = PYGTS_SEGMENT(o_); } else { if(pygts_triangle_check(o_)) { t = PYGTS_TRIANGLE(o_); } else { PyErr_SetString(PyExc_TypeError, "expected a Point, Segment or Triangle"); return NULL; } } } if(p!=NULL) { return Py_BuildValue("d", gts_point_distance(PYGTS_POINT_AS_GTS_POINT(self), PYGTS_POINT_AS_GTS_POINT(p))); } else { if(s!=NULL) { return Py_BuildValue("d", gts_point_segment_distance(PYGTS_POINT_AS_GTS_POINT(self), PYGTS_SEGMENT_AS_GTS_SEGMENT(s) ) ); } else { return Py_BuildValue("d", gts_point_triangle_distance(PYGTS_POINT_AS_GTS_POINT(self), PYGTS_TRIANGLE_AS_GTS_TRIANGLE(t) ) ); } } } static PyObject* distance2(PygtsPoint* self, PyObject *args) { PyObject *o_; PygtsPoint *p=NULL; PygtsSegment *s=NULL; PygtsTriangle *t=NULL; SELF_CHECK /* Parse the args */ if(! PyArg_ParseTuple(args, "O", &o_) ) { return NULL; } /* Convert to PygtsObjects */ if(pygts_point_check(o_)) { p = PYGTS_POINT(o_); } else { if(pygts_segment_check(o_)) { s = PYGTS_SEGMENT(o_); } else { if(pygts_triangle_check(o_)) { t = PYGTS_TRIANGLE(o_); } else { PyErr_SetString(PyExc_TypeError, "expected a Point, Segment or Triangle"); return NULL; } } } if(p!=NULL) { return Py_BuildValue("d", gts_point_distance2(PYGTS_POINT_AS_GTS_POINT(self), PYGTS_POINT_AS_GTS_POINT(p))); } else { if(s!=NULL) { return Py_BuildValue("d", gts_point_segment_distance2(PYGTS_POINT_AS_GTS_POINT(self), PYGTS_SEGMENT_AS_GTS_SEGMENT(s) ) ); } else { return Py_BuildValue("d", gts_point_triangle_distance2(PYGTS_POINT_AS_GTS_POINT(self), PYGTS_TRIANGLE_AS_GTS_TRIANGLE(t) ) ); } } } static PyObject* orientation_3d(PygtsPoint* self, PyObject *args) { PyObject *p1_,*p2_,*p3_; PygtsPoint *p1,*p2,*p3; SELF_CHECK /* Parse the args */ if(! PyArg_ParseTuple(args, "OOO", &p1_, &p2_, &p3_) ) { return NULL; } /* Convert to PygtsObjects */ if(!pygts_point_check(p1_)) { PyErr_SetString(PyExc_TypeError,"expected three Points"); return NULL; } if(!pygts_point_check(p2_)) { PyErr_SetString(PyExc_TypeError,"expected three Points"); return NULL; } if(!pygts_point_check(p3_)) { PyErr_SetString(PyExc_TypeError,"expected three Points"); return NULL; } p1 = PYGTS_POINT(p1_); p2 = PYGTS_POINT(p2_); p3 = PYGTS_POINT(p3_); return Py_BuildValue("d", gts_point_orientation_3d(PYGTS_POINT_AS_GTS_POINT(p1), PYGTS_POINT_AS_GTS_POINT(p2), PYGTS_POINT_AS_GTS_POINT(p3), PYGTS_POINT_AS_GTS_POINT(self))); } static PyObject* orientation_3d_sos(PygtsPoint* self, PyObject *args) { PyObject *p1_,*p2_,*p3_; PygtsPoint *p1,*p2,*p3; SELF_CHECK /* Parse the args */ if(! PyArg_ParseTuple(args, "OOO", &p1_, &p2_, &p3_) ) { return NULL; } /* Convert to PygtsObjects */ if(!pygts_point_check(p1_)) { PyErr_SetString(PyExc_TypeError,"expected three Points"); return NULL; } if(!pygts_point_check(p2_)) { PyErr_SetString(PyExc_TypeError,"expected three Points"); return NULL; } if(!pygts_point_check(p3_)) { PyErr_SetString(PyExc_TypeError,"expected three Points"); return NULL; } p1 = PYGTS_POINT(p1_); p2 = PYGTS_POINT(p2_); p3 = PYGTS_POINT(p3_); return Py_BuildValue("i",gts_point_orientation_3d_sos( PYGTS_POINT_AS_GTS_POINT(p1), PYGTS_POINT_AS_GTS_POINT(p2), PYGTS_POINT_AS_GTS_POINT(p3), PYGTS_POINT_AS_GTS_POINT(self))); } static PyObject* is_in_circle(PygtsPoint* self, PyObject *args) { PyObject *o1_=NULL,*o2_=NULL,*o3_=NULL; PygtsPoint *p1=NULL, *p2=NULL, *p3=NULL; PygtsTriangle *t=NULL; gdouble result; SELF_CHECK /* Parse the args */ if(! PyArg_ParseTuple(args, "O|OO", &o1_, &o2_, &o3_) ) { return NULL; } if( (o2_==NULL && o3_!=NULL) || (o2_!=NULL && o3_==NULL) ) { PyErr_SetString(PyExc_TypeError,"expected three Points or one Triangle"); return NULL; } /* Convert to PygtsObjects */ if(o2_==NULL && o3_==NULL) { if(!pygts_triangle_check(o1_)) { PyErr_SetString(PyExc_TypeError,"expected three Points or one Triangle"); return NULL; } t = PYGTS_TRIANGLE(o1_); } else { if(!pygts_point_check(o1_)) { PyErr_SetString(PyExc_TypeError,"expected three Points or one Triangle"); return NULL; } if(!pygts_point_check(o2_)) { PyErr_SetString(PyExc_TypeError,"expected three Points or one Triangle"); return NULL; } if(!pygts_point_check(o3_)) { PyErr_SetString(PyExc_TypeError,"expected three Points or one Triangle"); return NULL; } p1 = PYGTS_POINT(o1_); p2 = PYGTS_POINT(o2_); p3 = PYGTS_POINT(o3_); } if(t!=NULL){ result=gts_point_in_triangle_circle(PYGTS_POINT_AS_GTS_POINT(self), PYGTS_TRIANGLE_AS_GTS_TRIANGLE(t)); } else { result = gts_point_in_circle(PYGTS_POINT_AS_GTS_POINT(self), PYGTS_POINT_AS_GTS_POINT(p1), PYGTS_POINT_AS_GTS_POINT(p2), PYGTS_POINT_AS_GTS_POINT(p3)); } if(result>0) return Py_BuildValue("i",1); if(result==0) return Py_BuildValue("i",0); return Py_BuildValue("i",-1); } static PyObject* is_in(PygtsPoint* self, PyObject *args) { PyObject *t_; PygtsTriangle *t; SELF_CHECK /* Parse the args */ if(! PyArg_ParseTuple(args, "O", &t_) ) { return NULL; } /* Convert to PygtsObjects */ if(!pygts_triangle_check(t_)) { PyErr_SetString(PyExc_TypeError,"expected a Triangle"); return NULL; } t = PYGTS_TRIANGLE(t_); return Py_BuildValue("i", gts_point_is_in_triangle(PYGTS_POINT_AS_GTS_POINT(self), PYGTS_TRIANGLE_AS_GTS_TRIANGLE(t))); } static PyObject* is_inside(PygtsPoint* self, PyObject *args) { PyObject *s_; PygtsSurface *s; GNode *tree; gboolean is_open=FALSE, ret; SELF_CHECK /* Parse the args */ if(! PyArg_ParseTuple(args, "O", &s_) ) { return NULL; } /* Convert to PygtsObjects */ if(!pygts_surface_check(s_)) { PyErr_SetString(PyExc_TypeError,"expected a Surface"); return NULL; } s = PYGTS_SURFACE(s_); /* Error check */ if(!gts_surface_is_closed(PYGTS_SURFACE_AS_GTS_SURFACE(s))) { PyErr_SetString(PyExc_RuntimeError,"Surface is not closed"); return NULL; } /* Determing is_open parameter; note the meaning is different from the * error check above. */ if( gts_surface_volume(PYGTS_SURFACE_AS_GTS_SURFACE(s))<0. ) { is_open = TRUE; } /* Construct the tree */ if((tree=gts_bb_tree_surface(PYGTS_SURFACE_AS_GTS_SURFACE(s))) == NULL) { PyErr_SetString(PyExc_MemoryError,"could not create GTree"); return NULL; } /* Make the call */ ret = gts_point_is_inside_surface(PYGTS_POINT_AS_GTS_POINT(self), tree, is_open); g_node_destroy(tree); if(ret) { Py_INCREF(Py_True); return Py_True; } else { Py_INCREF(Py_False); return Py_False; } } static PyObject* closest(PygtsPoint* self, PyObject *args) { PyObject *o1_,*o2_; PygtsPoint *p=NULL; PygtsSegment *s=NULL; PygtsTriangle *t=NULL; SELF_CHECK /* Parse the args */ if(! PyArg_ParseTuple(args, "OO", &o1_, &o2_) ) { return NULL; } /* Convert to PygtsObjects */ if(pygts_segment_check(o1_)) { s = PYGTS_SEGMENT(o1_); } else { if(pygts_triangle_check(o1_)) { t = PYGTS_TRIANGLE(o1_); } else { PyErr_SetString(PyExc_TypeError, "expected a Segment or Triangle, and a Point"); return NULL; } } if(pygts_point_check(o2_)) { p = PYGTS_POINT(o2_); } else { PyErr_SetString(PyExc_TypeError, "expected a Segment or Triangle, and a Point"); return NULL; } if(s!=NULL) { gts_point_segment_closest(PYGTS_POINT_AS_GTS_POINT(p), PYGTS_SEGMENT_AS_GTS_SEGMENT(s), PYGTS_POINT_AS_GTS_POINT(self)); } else { gts_point_triangle_closest(PYGTS_POINT_AS_GTS_POINT(p), PYGTS_TRIANGLE_AS_GTS_TRIANGLE(t), PYGTS_POINT_AS_GTS_POINT(self)); } Py_INCREF(self); return (PyObject*)self; } /* Helper for rotate() */ gint pygts_point_rotate(GtsPoint* p, gdouble dx, gdouble dy, gdouble dz, gdouble a) { GtsMatrix *m; GtsVector v; v[0] = dx; v[1] = dy; v[2] = dz; if( (m = gts_matrix_rotate(NULL,v,a)) == NULL ) { PyErr_SetString(PyExc_MemoryError,"could not create matrix"); return -1; } gts_point_transform(p,m); gts_matrix_destroy(m); return 0; } static PyObject* rotate(PygtsPoint* self, PyObject *args, PyObject *keywds) { static char *kwlist[] = {"dx", "dy", "dz", "a", NULL}; gdouble dx=0,dy=0,dz=0,a=0; SELF_CHECK /* Parse the args */ if(! PyArg_ParseTupleAndKeywords(args, keywds,"|dddd", kwlist, &dx, &dy, &dz, &a) ) { return NULL; } if(pygts_point_rotate(PYGTS_POINT_AS_GTS_POINT(self),dx,dy,dz,a)==-1) return NULL; Py_INCREF(Py_None); return Py_None; } /* Helper for scale() */ gint pygts_point_scale(GtsPoint* p, gdouble dx, gdouble dy, gdouble dz) { GtsMatrix *m; GtsVector v; v[0] = dx; v[1] = dy; v[2] = dz; if( (m = gts_matrix_scale(NULL,v)) == NULL ) { PyErr_SetString(PyExc_MemoryError,"could not create matrix"); return -1; } gts_point_transform(p,m); gts_matrix_destroy(m); return 0; } static PyObject* scale(PygtsPoint* self, PyObject *args, PyObject *keywds) { static char *kwlist[] = {"dx", "dy", "dz", NULL}; gdouble dx=1,dy=1,dz=1; SELF_CHECK /* Parse the args */ if(! PyArg_ParseTupleAndKeywords(args, keywds,"|ddd", kwlist, &dx, &dy, &dz) ) { return NULL; } if(pygts_point_scale(PYGTS_POINT_AS_GTS_POINT(self),dx,dy,dz)==-1) return NULL; Py_INCREF(Py_None); return Py_None; } /* Helper for translate() */ gint pygts_point_translate(GtsPoint* p, gdouble dx, gdouble dy, gdouble dz) { GtsMatrix *m; GtsVector v; v[0] = dx; v[1] = dy; v[2] = dz; if( (m = gts_matrix_translate(NULL,v)) == NULL ) { PyErr_SetString(PyExc_MemoryError,"could not create matrix"); return -1; } gts_point_transform(p,m); gts_matrix_destroy(m); return 0; } static PyObject* translate(PygtsPoint* self, PyObject *args, PyObject *keywds) { static char *kwlist[] = {"dx", "dy", "dz", NULL}; gdouble dx=0,dy=0,dz=0; SELF_CHECK /* Parse the args */ if(! PyArg_ParseTupleAndKeywords(args, keywds,"|ddd", kwlist, &dx, &dy, &dz) ) { return NULL; } if(pygts_point_translate(PYGTS_POINT_AS_GTS_POINT(self),dx,dy,dz)==-1) return NULL; Py_INCREF(Py_None); return Py_None; } /* Methods table */ static PyMethodDef methods[] = { {"is_ok", (PyCFunction)is_ok, METH_NOARGS, "True if this Point p is OK. False otherwise.\n" "This method is useful for unit testing and debugging.\n" "\n" "Signature: p.is_ok().\n" }, {"set", (PyCFunction)set, METH_VARARGS, "Sets x, y, and z coordinates of this Point p.\n" "\n" "Signature: p.set(x,y,z)\n" }, {"coords", (PyCFunction)coords, METH_VARARGS, "Returns a tuple of the x, y, and z coordinates for this Point p.\n" "\n" "Signature: p.coords(x,y,z)\n" }, {"is_in_rectangle", (PyCFunction)is_in_rectangle, METH_VARARGS, "True if this Point p is in box with bottom-left and upper-right\n" "Points p1 and p2.\n" "\n" "Signature: p.is_in_rectange(p1,p2)\n" }, {"distance", (PyCFunction)distance, METH_VARARGS, "Returns Euclidean distance between this Point p and other Point p2,\n" "Segment s, or Triangle t." "\n" "Signature: p.distance(p2), p.distance(s) or p.distance(t)\n" }, {"distance2", (PyCFunction)distance2, METH_VARARGS, "Returns squared Euclidean distance between Point p and Point p2,\n" "Segment s, or Triangle t.\n" "\n" "Signature: p.distance2(p2), p.distance2(s), or p.distance2(t)\n" }, {"orientation_3d", (PyCFunction)orientation_3d, METH_VARARGS, "Determines if this Point p is above, below or on plane of 3 Points\n" "p1, p2 and p3.\n" "\n" "Signature: p.orientation_3d(p1,p2,p3)\n" "\n" "Below is defined so that p1, p2 and p3 appear in counterclockwise\n" "order when viewed from above the plane.\n" "\n" "The return value is positive if p4 lies below the plane, negative\n" "if p4 lies above the plane, and zero if the four points are\n" "coplanar. The value is an approximation of six times the signed\n" "volume of the tetrahedron defined by the four points.\n" }, {"orientation_3d_sos", (PyCFunction)orientation_3d_sos, METH_VARARGS, "Determines if this Point p is above, below or on plane of 3 Points\n" "p1, p2 and p3.\n" "\n" "Signature: p.orientation_3d_sos(p1,p2,p3)\n" "\n" "Below is defined so that p1, p2 and p3 appear in counterclockwise\n" "order when viewed from above the plane.\n" "\n" "The return value is +1 if p4 lies below the plane, and -1 if p4\n" "lies above the plane. Simulation of Simplicity (SoS) is used to\n" "break ties when the orientation is degenerate (i.e. the point lies\n" "on the plane definedby p1, p2 and p3)." }, {"is_in_circle", (PyCFunction)is_in_circle, METH_VARARGS, "Tests if this Point p is inside or outside circumcircle.\n" "The planar projection (x,y) of Point p is tested against the\n" "circumcircle defined by the planar projection of p1, p2 and p3,\n" "or alternatively the Triangle t\n" "\n" "Signature: p.in_circle(p1,p2,p3) or p.in_circle(t) \n" "\n" "Returns +1 if p lies inside, -1 if p lies outside, and 0 if p lies\n" "on the circle. The Points p1, p2, and p3 must be in\n" "counterclockwise order, or the sign of the result will be reversed.\n" }, {"is_in", (PyCFunction)is_in, METH_VARARGS, "Tests if this Point p is inside or outside Triangle t.\n" "The planar projection (x,y) of Point p is tested against the\n" "planar projection of Triangle t.\n" "\n" "Signature: p.in_circle(p1,p2,p3) or p.in_circle(t) \n" "\n" "Returns a +1 if p lies inside, -1 if p lies outside, and 0\n" "if p lies on the triangle.\n" }, {"is_inside", (PyCFunction)is_inside, METH_VARARGS, "True if this Point p is inside or outside Surface s.\n" "False otherwise.\n" "\n" "Signature: p.in_inside(s)\n" }, {"closest", (PyCFunction)closest, METH_VARARGS, "Set the coordinates of Point p to the Point on Segment s\n" "or Triangle t closest to the Point p2\n" "\n" "Signature: p.closest(s,p2) or p.closest(t,p2)\n" "\n" "Returns the (modified) Point p.\n" }, {"rotate", (PyCFunction)rotate, METH_VARARGS | METH_KEYWORDS, "Rotates Point p around vector dx,dy,dz by angle a.\n" "The sense of the rotation is given by the right-hand-rule.\n" "\n" "Signature: p.rotate(dx=0,dy=0,dz=0,a=0)\n" }, {"scale", (PyCFunction)scale, METH_VARARGS | METH_KEYWORDS, "Scales Point p by vector dx,dy,dz.\n" "\n" "Signature: p.scale(dx=1,dy=1,dz=1)\n" }, {"translate", (PyCFunction)translate, METH_VARARGS | METH_KEYWORDS, "Translates Point p by vector dx,dy,dz.\n" "\n" "Signature: p.translate(dx=0,dy=0,dz=0)\n" }, {NULL} /* Sentinel */ }; /*-------------------------------------------------------------------------*/ /* Attributes exported to python */ static PyObject * getx(PygtsPoint *self, void *closure) { SELF_CHECK return Py_BuildValue("d",PYGTS_POINT_AS_GTS_POINT(self)->x); } static int setx(PygtsPoint *self, PyObject *value, void *closure) { if(PyFloat_Check(value)) { PYGTS_POINT_AS_GTS_POINT(self)->x = PyFloat_AsDouble(value); } else if(PyInt_Check(value)) { PYGTS_POINT_AS_GTS_POINT(self)->x = (gdouble)PyInt_AsLong(value); } else { PyErr_SetString(PyExc_TypeError,"expected a float"); return -1; } return 0; } static PyObject * gety(PygtsPoint *self, void *closure) { SELF_CHECK return Py_BuildValue("d",PYGTS_POINT_AS_GTS_POINT(self)->y); } static int sety(PygtsPoint *self, PyObject *value, void *closure) { if(PyFloat_Check(value)) { PYGTS_POINT_AS_GTS_POINT(self)->y = PyFloat_AsDouble(value); } else if(PyInt_Check(value)) { PYGTS_POINT_AS_GTS_POINT(self)->y = (gdouble)PyInt_AsLong(value); } else { PyErr_SetString(PyExc_TypeError,"expected a float"); return -1; } return 0; } static PyObject * getz(PygtsPoint *self, void *closure) { SELF_CHECK return Py_BuildValue("d",PYGTS_POINT_AS_GTS_POINT(self)->z); } static int setz(PygtsPoint *self, PyObject *value, void *closure) { if(PyFloat_Check(value)) { PYGTS_POINT_AS_GTS_POINT(self)->z = PyFloat_AsDouble(value); } else if(PyInt_Check(value)) { PYGTS_POINT_AS_GTS_POINT(self)->z = (gdouble)PyInt_AsLong(value); } else { PyErr_SetString(PyExc_TypeError,"expected a float"); return -1; } return 0; } /* Methods table */ static PyGetSetDef getset[] = { {"x", (getter)getx, (setter)setx, "x value", NULL}, {"y", (getter)gety, (setter)sety, "y value", NULL}, {"z", (getter)getz, (setter)setz, "z value", NULL}, {NULL} /* Sentinel */ }; /*-------------------------------------------------------------------------*/ /* Python type methods */ static PyObject * new(PyTypeObject *type, PyObject *args, PyObject *kwds) { PyObject *o; PygtsObject *obj; guint alloc_gtsobj = TRUE; /* Parse the args */ if(kwds) { o = PyDict_GetItemString(kwds,"alloc_gtsobj"); if(o==Py_False) { alloc_gtsobj = FALSE; } if(o!=NULL) { PyDict_DelItemString(kwds, "alloc_gtsobj"); } } if(kwds) { Py_INCREF(Py_False); PyDict_SetItemString(kwds,"alloc_gtsobj", Py_False); } /* Chain up */ obj = PYGTS_OBJECT(PygtsObjectType.tp_new(type,args,kwds)); /* Allocate the gtsobj (if needed) */ if( alloc_gtsobj ) { obj->gtsobj = GTS_OBJECT(gts_point_new(gts_point_class(),0,0,0)); if( obj->gtsobj == NULL ) { PyErr_SetString(PyExc_MemoryError, "could not create Point"); return NULL; } pygts_object_register(obj); } return (PyObject*)obj; } static int init(PygtsPoint *self, PyObject *args, PyObject *kwds) { PygtsObject *obj; gdouble x=0,y=0,z=0; guint a; gint ret; static char *kwlist[] = {"x", "y", "z", "alloc_gtsobj", NULL}; obj = PYGTS_OBJECT(self); /* Parse the args */ if(! PyArg_ParseTupleAndKeywords(args, kwds, "|dddi", kwlist, &x,&y,&z,&a)) { return -1; } /* Initialize */ gts_point_set(GTS_POINT(obj->gtsobj),x,y,z); /* Chain up */ if( (ret=PygtsObjectType.tp_init((PyObject*)self,args,kwds)) != 0 ) { return ret; } return 0; } static int compare(PygtsPoint *p1_, PygtsPoint *p2_) { GtsPoint *p1, *p2; #if PYGTS_DEBUG pygts_point_check((PyObject*)p1_); pygts_point_check((PyObject*)p2_); #endif p1 = PYGTS_POINT_AS_GTS_POINT(p1_); p2 = PYGTS_POINT_AS_GTS_POINT(p2_); return pygts_point_compare(p1,p2); } /* Methods table */ PyTypeObject PygtsPointType = { PyObject_HEAD_INIT(NULL) 0, /* ob_size */ "gts.Point" , /* tp_name */ sizeof(PygtsPoint), /* tp_basicsize */ 0, /* tp_itemsize */ 0, /* tp_dealloc */ 0, /* tp_print */ 0, /* tp_getattr */ 0, /* tp_setattr */ (cmpfunc)compare, /* tp_compare */ 0, /* tp_repr */ 0, /* tp_as_number */ 0, /* tp_as_sequence */ 0, /* tp_as_mapping */ 0, /* tp_hash */ 0, /* tp_call */ 0, /* tp_str */ 0, /* tp_getattro */ 0, /* tp_setattro */ 0, /* tp_as_buffer */ Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, /* tp_flags */ "Point object", /* tp_doc */ 0, /* tp_traverse */ 0, /* tp_clear */ 0, /* tp_richcompare */ 0, /* tp_weaklistoffset */ 0, /* tp_iter */ 0, /* tp_iternext */ methods, /* tp_methods */ 0, /* tp_members */ getset, /* tp_getset */ 0, /* tp_base: attached in pygts.c */ 0, /* tp_dict */ 0, /* tp_descr_get */ 0, /* tp_descr_set */ 0, /* tp_dictoffset */ (initproc)init, /* tp_init */ 0, /* tp_alloc */ (newfunc)new /* tp_new */ }; /*-------------------------------------------------------------------------*/ /* Pygts functions */ gboolean pygts_point_check(PyObject* o) { gboolean check = FALSE; guint i,N; PyObject *obj; /* Check for a Point */ if( PyObject_TypeCheck(o, &PygtsPointType) ) { check = TRUE; } /* Convert list into tuple */ if(PyList_Check(o)) { o = PyList_AsTuple(o); } else { Py_INCREF(o); } /* Check for a tuple of floats */ if( PyTuple_Check(o) ) { if( (N = PyTuple_Size(o)) <= 3 ) { check = TRUE; for(i=0;igtsobj = GTS_OBJECT(p); /* Register and return */ pygts_object_register(point); return PYGTS_POINT(point); } PygtsPoint * pygts_point_from_sequence(PyObject *tuple) { guint i,N; gdouble x=0,y=0,z=0; PyObject *obj; GtsPoint *p; PygtsPoint *point; /* Convert list into tuple */ if(PyList_Check(tuple)) { tuple = PyList_AsTuple(tuple); } else { Py_INCREF(tuple); } if(!PyTuple_Check(tuple)) { Py_DECREF(tuple); PyErr_SetString(PyExc_TypeError,"expected a list or tuple of vertices"); return NULL; } /* Get the tuple size */ if( (N = PyTuple_Size(tuple)) > 3 ) { PyErr_SetString(PyExc_RuntimeError, "expected a list or tuple of up to three floats"); Py_DECREF(tuple); return NULL; } /* Get the coordinates */ for(i=0;ix==p2->x) && (p1->y==p2->y) && (p1->z==p2->z) ) { return 0; } /* Compare distances from origin */ r1 = sqrt(pow(p1->x,2) + pow(p1->y,2) + pow(p1->z,2)); r2 = sqrt(pow(p2->x,2) + pow(p2->y,2) + pow(p2->z,2)); if(r1r2) return 1; /* Compare horizontal distances from origin */ r1 = sqrt(pow(p1->x,2) + pow(p1->y,2)); r2 = sqrt(pow(p2->x,2) + pow(p2->y,2)); if(r1r2) return 1; /* Compare x */ r1 = p1->x; r2 = p2->x; if(r1r2) return 1; /* Compare y */ r1 = p1->y; r2 = p2->y; if(r1r2) return 1; /* Compare z */ r1 = p1->z; r2 = p2->z; if(r1 * Department of Physics and Atmospheric Science, * Dalhousie University, Halifax, Nova Scotia, Canada, B3H 3J5 * * NOTICE * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library 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 * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public * License along with this library; if not, write to the * Free Software Foundation, Inc., 59 Temple Place - Suite 330, * Boston, MA 02111-1307, USA. */ #ifndef __PYGTS_POINT_H__ #define __PYGTS_POINT_H__ typedef struct _PygtsObject PygtsPoint; #define PYGTS_POINT(o) ( PyObject_TypeCheck((PyObject*)o, &PygtsPointType) ? \ (PygtsPoint*)o : \ pygts_point_from_sequence((PyObject*)o) ) #define PYGTS_POINT_AS_GTS_POINT(o) (GTS_POINT(PYGTS_OBJECT(o)->gtsobj)) extern PyTypeObject PygtsPointType; gboolean pygts_point_check(PyObject* o); gboolean pygts_point_is_ok(PygtsPoint *o); PygtsPoint* pygts_point_from_sequence(PyObject *tuple); int pygts_point_compare(GtsPoint* p1,GtsPoint* p2); gint pygts_point_rotate(GtsPoint* p,gdouble dx,gdouble dy,gdouble dz,gdouble a); gint pygts_point_scale(GtsPoint* p, gdouble dx, gdouble dy, gdouble dz); gint pygts_point_translate(GtsPoint* p, gdouble dx, gdouble dy, gdouble dz); #endif /* __PYGTS_POINT_H__ */ trunk-2018.02b/py/3rd-party/pygts-0.3.1/pygts.c000066400000000000000000000505301324306050200205630ustar00rootroot00000000000000/* pygts - python package for the manipulation of triangulated surfaces * * Copyright (C) 2009 Thomas J. Duck * All rights reserved. * * Thomas J. Duck * Department of Physics and Atmospheric Science, * Dalhousie University, Halifax, Nova Scotia, Canada, B3H 3J5 * * NOTICE * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library 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 * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public * License along with this library; if not, write to the * Free Software Foundation, Inc., 59 Temple Place - Suite 330, * Boston, MA 02111-1307, USA. */ #include "pygts.h" #if PYGTS_HAS_NUMPY #include "numpy/arrayobject.h" #endif static PyObject* merge(PyObject *self, PyObject *args) { PyObject *tuple, *obj; guint i,N; GList *vertices=NULL,*v; gdouble epsilon; PygtsVertex *vertex; /* Parse the args */ if(! PyArg_ParseTuple(args, "Od", &tuple, &epsilon) ) { return NULL; } if(PyList_Check(tuple)) { tuple = PyList_AsTuple(tuple); } else { Py_INCREF(tuple); } if(!PyTuple_Check(tuple)) { Py_DECREF(tuple); PyErr_SetString(PyExc_TypeError,"expected a list or tuple of vertices"); return NULL; } /* Assemble the GList */ N = PyTuple_Size(tuple); for(i=N-1;i>0;i--) { obj = PyTuple_GET_ITEM(tuple,i); if(!pygts_vertex_check(obj)) { Py_DECREF(tuple); g_list_free(vertices); PyErr_SetString(PyExc_TypeError,"expected a list or tuple of vertices"); return NULL; } vertices = g_list_prepend(vertices,PYGTS_VERTEX_AS_GTS_VERTEX(obj)); } Py_DECREF(tuple); /* Make the call */ vertices = pygts_vertices_merge(vertices,epsilon,NULL); /* Assemble the return tuple */ N = g_list_length(vertices); if( (tuple=PyTuple_New(N)) == NULL) { PyErr_SetString(PyExc_MemoryError,"could not create tuple"); return NULL; } v = vertices; for(i=0;idata)) )) ==NULL ) { PyErr_SetString(PyExc_RuntimeError, "could not get object from table (internal error)"); g_list_free(vertices); return NULL; } Py_INCREF(vertex); PyTuple_SET_ITEM(tuple,i,(PyObject*)vertex); v = g_list_next(v); } g_list_free(vertices); return tuple; } static PyObject* vertices(PyObject *self, PyObject *args) { PyObject *tuple, *obj; guint i,N; GSList *segments=NULL,*vertices=NULL,*v; PygtsVertex *vertex; /* Parse the args */ if(! PyArg_ParseTuple(args, "O", &tuple) ) { return NULL; } if(PyList_Check(tuple)) { tuple = PyList_AsTuple(tuple); } else { Py_INCREF(tuple); } if(!PyTuple_Check(tuple)) { Py_DECREF(tuple); PyErr_SetString(PyExc_TypeError,"expected a list or tuple of Segments"); return NULL; } /* Assemble the GSList */ N = PyTuple_Size(tuple); for(i=0;idata))) == NULL ) { Py_DECREF(tuple); g_slist_free(vertices); return NULL; } PyTuple_SET_ITEM(tuple,i,(PyObject*)vertex); v = g_slist_next(v); } g_slist_free(vertices); return tuple; } static PyObject* segments(PyObject *self, PyObject *args) { PyObject *tuple, *obj; guint i,n,N; GSList *segments=NULL,*vertices=NULL,*s; PygtsSegment *segment; /* Parse the args */ if(! PyArg_ParseTuple(args, "O", &tuple) ) { return NULL; } if(PyList_Check(tuple)) { tuple = PyList_AsTuple(tuple); } else { Py_INCREF(tuple); } if(!PyTuple_Check(tuple)) { Py_DECREF(tuple); PyErr_SetString(PyExc_TypeError,"expected a list or tuple of vertices"); return NULL; } /* Assemble the GSList */ N = PyTuple_Size(tuple); for(i=0;idata) || PYGTS_IS_PARENT_EDGE(s->data)) { s = g_slist_next(s); segment = NULL; continue; } /* Fill in the tuple */ if(GTS_IS_EDGE(s->data)) { segment = PYGTS_SEGMENT(pygts_edge_new(GTS_EDGE(s->data))); } else { segment = pygts_segment_new(GTS_SEGMENT(s->data)); } if( segment == NULL ) { Py_DECREF(tuple); g_slist_free(segments); return NULL; } PyTuple_SET_ITEM(tuple,n,(PyObject*)segment); s = g_slist_next(s); n += 1; } g_slist_free(segments); if(_PyTuple_Resize(&tuple,n)!=0) { Py_DECREF(tuple); return NULL; } return tuple; } static PyObject* triangles(PyObject *self, PyObject *args) { PyObject *tuple, *obj; guint i,n,N; GSList *edges=NULL,*triangles=NULL,*t; PygtsTriangle *triangle; /* Parse the args */ if(! PyArg_ParseTuple(args, "O", &tuple) ) { return NULL; } if(PyList_Check(tuple)) { tuple = PyList_AsTuple(tuple); } else { Py_INCREF(tuple); } if(!PyTuple_Check(tuple)) { Py_DECREF(tuple); PyErr_SetString(PyExc_TypeError,"expected a list or tuple of edges"); return NULL; } /* Assemble the GSList */ N = PyTuple_Size(tuple); for(i=0;idata)) { t = g_slist_next(t); triangle = NULL; continue; } /* Fill in the tuple */ if(GTS_IS_FACE(t->data)) { triangle = PYGTS_TRIANGLE(pygts_face_new(GTS_FACE(t->data))); } else { triangle = pygts_triangle_new(GTS_TRIANGLE(t->data)); } if( triangle == NULL ) { Py_DECREF(tuple); g_slist_free(triangles); return NULL; } PyTuple_SET_ITEM(tuple,n,(PyObject*)triangle); t = g_slist_next(t); n += 1; } g_slist_free(triangles); if(_PyTuple_Resize(&tuple,n)!=0) { Py_DECREF(tuple); return NULL; } return tuple; } static PyObject* triangle_enclosing(PyObject *self, PyObject *args) { PyObject *tuple, *obj; guint i,N; GSList *points=NULL; GtsTriangle *t; PygtsTriangle *triangle; /* Parse the args */ if(! PyArg_ParseTuple(args, "O", &tuple) ) { return NULL; } if(PyList_Check(tuple)) { tuple = PyList_AsTuple(tuple); } else { Py_INCREF(tuple); } if(!PyTuple_Check(tuple)) { Py_DECREF(tuple); PyErr_SetString(PyExc_TypeError,"expected a list or tuple of points"); return NULL; } /* Assemble the GSList */ N = PyTuple_Size(tuple); for(i=0;ierror); gts_file_destroy(fp); return NULL; } gts_file_destroy(fp); if( (surface = pygts_surface_new(s)) == NULL ) { gts_object_destroy(GTS_OBJECT(s)); return NULL; } /* Clean up the surface */ pygts_edge_cleanup(PYGTS_SURFACE_AS_GTS_SURFACE(surface)); pygts_face_cleanup(PYGTS_SURFACE_AS_GTS_SURFACE(surface)); return (PyObject*)surface; } static PyObject* sphere(PyObject *self, PyObject *args) { PyObject *kwds; PygtsSurface *surface; guint geodesation_order; /* Parse the args */ if(! PyArg_ParseTuple(args, "i", &geodesation_order) ) return NULL; /* Chain up object allocation */ args = Py_BuildValue("()"); kwds = Py_BuildValue("{s:O}","alloc_gtsobj",Py_True); surface = PYGTS_SURFACE(PygtsSurfaceType.tp_new(&PygtsSurfaceType, args, kwds)); Py_DECREF(args); Py_DECREF(kwds); if( surface == NULL ) { PyErr_SetString(PyExc_MemoryError, "could not create Surface"); return NULL; } gts_surface_generate_sphere(PYGTS_SURFACE_AS_GTS_SURFACE(surface), geodesation_order); pygts_object_register(PYGTS_OBJECT(surface)); return (PyObject*)surface; } #if PYGTS_HAS_NUMPY /* Helper for pygts_iso to fill f with a layer of data from scalar */ static void isofunc(gdouble **f, GtsCartesianGrid g, guint k, gpointer data) { PyArrayObject *scalars = (PyArrayObject *)data; int i, j; for (i = 0; i < scalars->dimensions[0]; i++) { for (j = 0; j < scalars->dimensions[1]; j++) { f[i][j] = *(gdouble *)(scalars->data + i*scalars->strides[0] + \ j*scalars->strides[1] + k*scalars->strides[2]); } } } #define ISO_CLEANUP \ if (scalars) { Py_DECREF(scalars); } \ if (extents) { Py_DECREF(extents); } static PyObject* isosurface(PyObject *self, PyObject *args, PyObject *kwds) { double isoval[1]; PyObject *Oscalars = NULL, *Oextents = NULL; PyArrayObject *scalars = NULL, *extents = NULL; GtsCartesianGrid g; GtsSurface *s; PygtsSurface *surface; char *method = "cubes"; static char *kwlist[] = {"scalars", "isoval", "method", "extents", NULL}; if(!PyArg_ParseTupleAndKeywords(args, kwds, "Od|sO", kwlist, &Oscalars, isoval, &method, &Oextents)) { return NULL; } if(!(scalars = (PyArrayObject *) PyArray_ContiguousFromObject(Oscalars, PyArray_DOUBLE, 3, 3))) { ISO_CLEANUP; return NULL; } if(Oextents && (!(extents = (PyArrayObject *) PyArray_ContiguousFromObject(Oextents, PyArray_DOUBLE, 1, 1)))) { ISO_CLEANUP; return NULL; } if(extents && extents->dimensions[0] < 6) { PyErr_SetString(PyExc_ValueError, "extents must have at least 6 elements"); ISO_CLEANUP; return NULL; } if(extents) { int s = extents->strides[0]; g.x = *(gdouble*)(extents->data + 0*s); g.nx = scalars->dimensions[0]; g.dx = (*(gdouble*)(extents->data + 1*s) - \ *(gdouble*)(extents->data + 0* s))/(g.nx-1); g.y = *(gdouble*)(extents->data + 2*s); g.ny = scalars->dimensions[1]; g.dy = (*(gdouble*)(extents->data + 3*s) - \ *(gdouble*)(extents->data + 2*s))/(g.ny-1); g.z = *(gdouble*)(extents->data + 4*s); g.nz = scalars->dimensions[2]; g.dz = (*(gdouble*)(extents->data + 5*s) - \ *(gdouble*)(extents->data + 4*s))/(g.nz-1); } else { g.x = -1.0; g.nx = scalars->dimensions[0]; g.dx = 2.0/(scalars->dimensions[0]-1); g.y = -1.0; g.ny = scalars->dimensions[1]; g.dy = 2.0/(scalars->dimensions[1]-1); g.z = -1.0; g.nz = scalars->dimensions[2]; g.dz = 2.0/(scalars->dimensions[2]-1); } /* Create the surface */ if((s = gts_surface_new(gts_surface_class(), gts_face_class(), gts_edge_class(), gts_vertex_class())) == NULL ) { PyErr_SetString(PyExc_MemoryError,"could not create Surface"); return NULL; } /* Make the call */ switch(method[0]) { case 'c': /* cubes */ gts_isosurface_cartesian(s, g, isofunc, scalars, isoval[0]); break; case 't': /* tetra */ gts_isosurface_tetra(s, g, isofunc, scalars, isoval[0]); /* *** ATTENTION *** * Isosurface produced is "inside-out", and so we must revert it. * This is a bug in GTS. */ gts_surface_foreach_face(s, (GtsFunc)gts_triangle_revert, NULL); /* *** ATTENTION *** */ break; case 'b': /* tetra bounded */ gts_isosurface_tetra_bounded(s, g, isofunc, scalars, isoval[0]); /* *** ATTENTION *** * Isosurface produced is "inside-out", and so we must revert it. * This is a bug in GTS. */ gts_surface_foreach_face(s, (GtsFunc)gts_triangle_revert, NULL); /* *** ATTENTION *** */ break; case 'd': /* tetra bcl*/ gts_isosurface_tetra_bcl(s, g, isofunc, scalars, isoval[0]); /* *** ATTENTION *** * Isosurface produced is "inside-out", and so we must revert it. * This is a bug in GTS. */ gts_surface_foreach_face(s, (GtsFunc)gts_triangle_revert, NULL); /* *** ATTENTION *** */ break; default: PyErr_SetString(PyExc_ValueError, "unknown method"); ISO_CLEANUP; return NULL; } ISO_CLEANUP; if( (surface = pygts_surface_new(s)) == NULL ) { gts_object_destroy(GTS_OBJECT(s)); return NULL; } return (PyObject*)surface; } #endif /* PYGTS_HAS_NUMPY */ static PyMethodDef gts_methods[] = { {"read", (PyCFunction)pygts_read, METH_VARARGS, "Returns the data read from File f as a Surface.\n" "The File data must be in GTS format (e.g., as written using\n" "Surface.write())\n" "\n" "Signature: read(f)\n" }, { "sphere", sphere, METH_VARARGS, "Returns a unit sphere generated by recursive subdivision.\n" "First approximation is an isocahedron; each level of refinement\n" "(geodesation_order) increases the number of triangles by a factor\n" "of 4.\n" "\n" "Signature: sphere(geodesation_order)\n" }, #if PYGTS_HAS_NUMPY {"isosurface", (PyCFunction)isosurface, METH_VARARGS|METH_KEYWORDS, "Adds to surface new faces defining the isosurface data[x,y,z] = c\n" "\n" "Signature: isosurface(data, c, ...)\n" "\n" "data is a 3D numpy array.\n" "c is the isovalue defining the surface\n" "\n" "Keyword arguments:\n" "extents= [xmin, xmax, ymin, ymax, zmin, zmax]\n" " A numpy array defining the extent of the data cube.\n" " Default is the cube with corners at (-1,-1,-1) and (1,1,1)\n" " Data is assumed to be regularly sampled in the cube.\n" "method= ['cube'|'tetra'|'dual'|'bounded']\n" " String (only the first character counts) specifying the\n" " method.\n" " cube -- marching cubes (default)\n" " tetra -- marching tetrahedra\n" " dual -- maching tetrahedra producing dual 'body-centred'\n" " faces relative to 'tetra'\n" " bounded -- marching tetrahedra ensuring the surface is\n" " bounded by adding a border of large negative\n" " values around the domain.\n" "\n" "By convention, the normals to the surface are pointing towards\n" "positive values of data[x,y,z] - c.\n" }, #endif /* PYGTS_HAS_NUMPY */ { "merge", merge, METH_VARARGS, "Merges list of Vertices that are within a box of side-length\n" "epsilon of each other.\n" "\n" "Signature: merge(list,epsilon)\n" }, { "vertices", vertices, METH_VARARGS, "Returns tuple of Vertices from a list or tuple of Segments.\n" "\n" "Signature: vertices(list)\n" }, { "segments", segments, METH_VARARGS, "Returns tuple of Segments from a list or tuple of Vertices.\n" "\n" "Signature: segments(list)\n" }, { "triangles", triangles, METH_VARARGS, "Returns tuple of Triangles from a list or tuple of Edges.\n" "\n" "Signature: triangles(list)\n" }, { "triangle_enclosing", triangle_enclosing, METH_VARARGS, "Returns a Triangle that encloses the plane projection of a list\n" "or tuple of Points. The Triangle is equilateral and encloses a\n" "rectangle defined by the maximum and minimum x and y coordinates\n" "of the points.\n" "\n" "Signature: triangles(list)\n" }, {NULL} /* Sentinel */ }; #ifndef PyMODINIT_FUNC /* declarations for DLL import/export */ #define PyMODINIT_FUNC void #endif PyMODINIT_FUNC init_gts(void) { PyObject* m; /* Allocate the object table */ if( (obj_table=g_hash_table_new(NULL,NULL)) == NULL ) return; /* Set class base types and make ready (i.e., inherit methods) */ if (PyType_Ready(&PygtsObjectType) < 0) return; PygtsPointType.tp_base = &PygtsObjectType; if (PyType_Ready(&PygtsPointType) < 0) return; PygtsVertexType.tp_base = &PygtsPointType; if (PyType_Ready(&PygtsVertexType) < 0) return; PygtsSegmentType.tp_base = &PygtsObjectType; if (PyType_Ready(&PygtsSegmentType) < 0) return; PygtsEdgeType.tp_base = &PygtsSegmentType; if (PyType_Ready(&PygtsEdgeType) < 0) return; PygtsTriangleType.tp_base = &PygtsObjectType; if (PyType_Ready(&PygtsTriangleType) < 0) return; PygtsFaceType.tp_base = &PygtsTriangleType; if (PyType_Ready(&PygtsFaceType) < 0) return; PygtsSurfaceType.tp_base = &PygtsObjectType; if (PyType_Ready(&PygtsSurfaceType) < 0) return; /* Initialize the module */ m = Py_InitModule3("_gts", gts_methods,"Gnu Triangulated Surface Library"); if (m == NULL) return; /* Add new types to python */ Py_INCREF(&PygtsObjectType); PyModule_AddObject(m, "Object", (PyObject *)&PygtsObjectType); Py_INCREF(&PygtsPointType); PyModule_AddObject(m, "Point", (PyObject *)&PygtsPointType); Py_INCREF(&PygtsVertexType); PyModule_AddObject(m, "Vertex", (PyObject *)&PygtsVertexType); Py_INCREF(&PygtsSegmentType); PyModule_AddObject(m, "Segment", (PyObject *)&PygtsSegmentType); Py_INCREF(&PygtsEdgeType); PyModule_AddObject(m, "Edge", (PyObject *)&PygtsEdgeType); Py_INCREF(&PygtsTriangleType); PyModule_AddObject(m, "Triangle", (PyObject *)&PygtsTriangleType); Py_INCREF(&PygtsFaceType); PyModule_AddObject(m, "Face", (PyObject *)&PygtsFaceType); Py_INCREF(&PygtsSurfaceType); PyModule_AddObject(m, "Surface", (PyObject *)&PygtsSurfaceType); } trunk-2018.02b/py/3rd-party/pygts-0.3.1/pygts.h000066400000000000000000000031601324306050200205650ustar00rootroot00000000000000/* pygts - python package for the manipulation of triangulated surfaces * * Copyright (C) 2009 Thomas J. Duck * All rights reserved. * * Thomas J. Duck * Department of Physics and Atmospheric Science, * Dalhousie University, Halifax, Nova Scotia, Canada, B3H 3J5 * * NOTICE * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library 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 * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public * License along with this library; if not, write to the * Free Software Foundation, Inc., 59 Temple Place - Suite 330, * Boston, MA 02111-1307, USA. */ #ifndef __PYGTS_H__ #define __PYGTS_H__ #ifndef PYGTS_DEBUG #define PYGTS_DEBUG 1 #endif /* PYGTS_DEBUG */ #include #include #include #include #include /* Defined for arrayobject.h which is only included where needed */ #define PY_ARRAY_UNIQUE_SYMBOL PYGTS #include #include #include "object.h" #include "point.h" #include "vertex.h" #include "segment.h" #include "edge.h" #include "triangle.h" #include "face.h" #include "surface.h" #include "cleanup.h" #endif /* __PYGTS_H__ */ trunk-2018.02b/py/3rd-party/pygts-0.3.1/pygts.py000066400000000000000000000106271324306050200207740ustar00rootroot00000000000000# pygts - python package for the manipulation of triangulated surfaces # # Copyright (C) 2009 Thomas J. Duck # All rights reserved. # # Thomas J. Duck # Department of Physics and Atmospheric Science, # Dalhousie University, Halifax, Nova Scotia, Canada, B3H 3J5 # # NOTICE # # This library is free software; you can redistribute it and/or # modify it under the terms of the GNU Library General Public # License as published by the Free Software Foundation; either # version 2 of the License, or (at your option) any later version. # # This library 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 # Library General Public License for more details. # # You should have received a copy of the GNU Library General Public # License along with this library; if not, write to the # Free Software Foundation, Inc., 59 Temple Place - Suite 330, # Boston, MA 02111-1307, USA. # added by eudoxos 29.1.2011, fixes https://bugs.launchpad.net/yade/+bug/668329 ## force decimal separator to be always . (decimal point), not , (decimal comma) -- unless LC_ALL is set, then we are stuck ## this was reason for bogus gts imports ## adding to yade main does not solve the problem for some reason import locale locale.setlocale(locale.LC_NUMERIC,'C') from _gts import * def get_coords_and_face_indices(s,unzip=False): """Returns the coordinates and face indices of Surface s. If unzip is True then four tuples are returned. The first three are the x, y, and z coordinates for each Vertex on the Surface. The last is a list of tuples, one for each Face on the Surface, containing 3 indices linking the Face Vertices to the coordinate lists. If unzip is False then the coordinates are given in a single list of 3-tuples. """ vertices = s.vertices() coords = [v.coords() for v in vertices] face_indices = s.face_indices(vertices) if unzip: x,y,z = zip(*coords) return x,y,z,face_indices else: return vertices, coords def cube(): """Returns a cube of side length 2 centered at the origin.""" # # v8 +------+ v5 # / /| # / v1/ | # v4 +------+ | # | | + v6 # |(v7) | / # | |/ # v3 +------+ v2 # v1,v2,v3,v4=Vertex(1,1,1),Vertex(1,1,-1),Vertex(1,-1,-1),Vertex(1,-1,1) v5,v6,v7,v8=Vertex(-1,1,1),Vertex(-1,1,-1),Vertex(-1,-1,-1),Vertex(-1,-1,1) e12,e23,e34,e14 = Edge(v1,v2), Edge(v2,v3), Edge(v3,v4), Edge(v4,v1) e56,e67,e78,e58 = Edge(v5,v6), Edge(v6,v7), Edge(v7,v8), Edge(v8,v5) e15,e26,e37,e48 = Edge(v1,v5), Edge(v2,v6), Edge(v3,v7), Edge(v4,v8) e13,e16,e18 = Edge(v1,v3), Edge(v1,v6), Edge(v1,v8) e27,e47,e57 = Edge(v7,v2), Edge(v7,v4), Edge(v7,v5) faces = [ Face(e12,e23,e13), Face(e13,e34,e14), Face(e12,e26,e16), Face(e15,e56,e16), Face(e15,e58,e18), Face(e14,e48,e18), Face(e58,e78,e57), Face(e56,e67,e57), Face(e26,e67,e27), Face(e37,e23,e27), Face(e37,e47,e34), Face(e78,e48,e47) ] faces[0].revert() # Set the orientation of the first face s = Surface() for face in faces: if not face.is_compatible(s): face.revert() s.add(face) return s def tetrahedron(): """Returns a tetrahedron of side length 2*sqrt(2) centered at origin. The edges of the tetrahedron are perpendicular to the cardinal directions. """ # v4 # + # | \ e6 # e5 '|e4 \ # v1 . +-e3-+ v3 # / . # ./e1. e2 # / . # + # v2 # Create vertices v1 = Vertex(1,1,1) v2 = Vertex(-1,-1,1) v3 = Vertex(-1,1,-1) v4 = Vertex(1,-1,-1) # Create edges e1 = Edge(v1,v2) e2 = Edge(v2,v3) e3 = Edge(v3,v1) e4 = Edge(v1,v4) e5 = Edge(v4,v2) e6 = Edge(v4,v3) # Create faces f1 = Face(e1,e2,e3) # Bottom face f2 = Face(e1,e4,e5) # Left face f3 = Face(e2,e5,e6) # Right face f4 = Face(e3,e4,e6) # Back face # Set orientation of first face f1.revert() # Assemble surface s = Surface() for face in [f1,f2,f3,f4]: if not face.is_compatible(s): face.revert() s.add(face) return s trunk-2018.02b/py/3rd-party/pygts-0.3.1/segment.c000066400000000000000000000335441324306050200210650ustar00rootroot00000000000000/* pygts - python segment for the manipulation of triangulated surfaces * * Copyright (C) 2009 Thomas J. Duck * All rights reserved. * * Thomas J. Duck * Department of Physics and Atmospheric Science, * Dalhousie University, Halifax, Nova Scotia, Canada, B3H 3J5 * * NOTICE * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library 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 * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public * License along with this library; if not, write to the * Free Software Foundation, Inc., 59 Temple Place - Suite 330, * Boston, MA 02111-1307, USA. */ #include "pygts.h" #if PYGTS_DEBUG #define SELF_CHECK if(!pygts_segment_check((PyObject*)self)) { \ PyErr_SetString(PyExc_RuntimeError, \ "problem with self object (internal error)"); \ return NULL; \ } #else #define SELF_CHECK #endif /*-------------------------------------------------------------------------*/ /* Methods exported to python */ static PyObject* is_ok(PygtsSegment *self, PyObject *args) { if(pygts_segment_is_ok(self)) { Py_INCREF(Py_True); return Py_True; } else { Py_INCREF(Py_False); return Py_False; } } static PyObject* intersects(PygtsSegment *self, PyObject *args) { PyObject *s_; GtsSegment *s; SELF_CHECK /* Parse the args */ if(! PyArg_ParseTuple(args, "O", &s_) ) { return NULL; } /* Convert to PygtsObjects */ if(!pygts_segment_check(s_)) { PyErr_SetString(PyExc_TypeError,"expected a Segment"); return NULL; } s = PYGTS_SEGMENT_AS_GTS_SEGMENT(s_); return Py_BuildValue("i", gts_segments_are_intersecting(PYGTS_SEGMENT_AS_GTS_SEGMENT(self),s)); } static PyObject* connects(PygtsSegment *self, PyObject *args) { PyObject *v1_,*v2_; GtsVertex *v1,*v2; SELF_CHECK /* Parse the args */ if(! PyArg_ParseTuple(args, "OO", &v1_, &v2_) ) { return NULL; } /* Convert to PygtsObjects */ if(!pygts_vertex_check(v1_)) { PyErr_SetString(PyExc_TypeError,"expected a Vertex"); return NULL; } v1 = PYGTS_VERTEX_AS_GTS_VERTEX(v1_); if(!pygts_vertex_check(v2_)) { PyErr_SetString(PyExc_TypeError,"expected a Vertex"); return NULL; } v2 = PYGTS_VERTEX_AS_GTS_VERTEX(v2_); if(gts_segment_connect(PYGTS_SEGMENT_AS_GTS_SEGMENT(self),v1,v2)) { Py_INCREF(Py_True); return Py_True; } else { Py_INCREF(Py_False); return Py_False; } } static PyObject* touches(PygtsSegment *self, PyObject *args) { PyObject *s_; GtsSegment *s; SELF_CHECK /* Parse the args */ if(! PyArg_ParseTuple(args, "O", &s_) ) { return NULL; } /* Convert to PygtsObjects */ if(!pygts_segment_check(s_)) { PyErr_SetString(PyExc_TypeError,"expected a Segment"); return NULL; } s = PYGTS_SEGMENT_AS_GTS_SEGMENT(s_); if(gts_segments_touch(PYGTS_SEGMENT_AS_GTS_SEGMENT(self),s)) { Py_INCREF(Py_True); return Py_True; } else { Py_INCREF(Py_False); return Py_False; } } static PyObject* midvertex(PygtsSegment *self, PyObject *args) { PygtsVertex *vertex; GtsVertex *v; SELF_CHECK v = gts_segment_midvertex(PYGTS_SEGMENT_AS_GTS_SEGMENT(self), gts_vertex_class()); if( (vertex = pygts_vertex_new(v)) == NULL ) { return NULL; } return (PyObject*)vertex; } static PyObject* intersection(PygtsSegment *self, PyObject *args) { PyObject *t_,*boundary_=NULL; PygtsTriangle *t; gboolean boundary=TRUE; GtsVertex *v; PygtsObject *vertex; SELF_CHECK /* Parse the args */ if(! PyArg_ParseTuple(args, "O|O", &t_, &boundary_) ) { return NULL; } /* Convert to PygtsObjects */ if(!pygts_triangle_check(t_)) { PyErr_SetString(PyExc_TypeError,"expected a Triangle and boolean"); return NULL; } t = PYGTS_TRIANGLE(t_); if( boundary_ != NULL ) { if(PyBool_Check(boundary_)==FALSE) { PyErr_SetString(PyExc_TypeError,"expected a Triangle and boolean"); return NULL; } if( boundary_ == Py_False ){ /* Default TRUE */ boundary = FALSE; } } v = GTS_VERTEX( gts_segment_triangle_intersection( PYGTS_SEGMENT_AS_GTS_SEGMENT(self), PYGTS_TRIANGLE_AS_GTS_TRIANGLE(t), boundary, GTS_POINT_CLASS(gts_vertex_class())) ); if( v == NULL ) { Py_INCREF(Py_None); return Py_None; } if( (vertex = pygts_vertex_new(v)) == NULL ) { return NULL; } return (PyObject *)vertex; } /* Methods table */ static PyMethodDef methods[] = { {"is_ok", (PyCFunction)is_ok, METH_NOARGS, "True if this Segment s is not degenerate or duplicate.\n" "False otherwise. Degeneracy implies s.v1.id == s.v2.id.\n" "\n" "Signature: s.is_ok().\n" }, {"intersects", (PyCFunction)intersects, METH_VARARGS, "Checks if this Segment s1 intersects with Segment s2.\n" "Returns 1 if they intersect, 0 if an endpoint of one Segment lies\n" "on the other Segment, -1 otherwise\n" "\n" "Signature: s1.intersects(s2).\n" }, {"connects", (PyCFunction)connects, METH_VARARGS, "Returns True if this Segment s1 connects Vertices v1 and v2.\n" "False otherwise.\n" "\n" "Signature: s1.connects(v1,v2).\n" }, {"touches", (PyCFunction)touches, METH_VARARGS, "Returns True if this Segment s1 touches Segment s2\n" "(i.e., they share a common Vertex). False otherwise.\n" "\n" "Signature: s1.touches(s2).\n" }, {"midvertex", (PyCFunction)midvertex, METH_NOARGS, "Returns a new Vertex at the mid-point of this Segment s.\n" "\n" "Signature: s.midvertex().\n" }, {"intersection", (PyCFunction)intersection, METH_VARARGS, "Returns the intersection of Segment s with Triangle t\n" "\n" "This function is geometrically robust in the sense that it will\n" "return None if s and t do not intersect and will return a\n" "Vertex if they do. However, the point coordinates are subject\n" "to round-off errors. None will be returned if s is contained\n" "in the plane defined by t.\n" "\n" "Signature: s.intersection(t) or s.intersection(t,boundary).\n" "\n" "If boundary is True (default), the boundary of s is taken into\n" "account.\n" "\n" "Returns a summit of t (if boundary is True), one of the endpoints\n" "of s, a new Vertex at the intersection of s with t, or None if\n" "s and t don't intersect.\n" }, {NULL} /* Sentinel */ }; /*-------------------------------------------------------------------------*/ /* Attributes exported to python */ static PyObject * get_v1(PygtsSegment *self, void *closure) { PygtsObject *v1; SELF_CHECK if( (v1=pygts_vertex_new(PYGTS_SEGMENT_AS_GTS_SEGMENT(self)->v1)) == NULL ) { return NULL; } return (PyObject *)v1; } static PyObject * get_v2(PygtsSegment *self, void *closure) { PygtsObject *v2; SELF_CHECK if( (v2=pygts_vertex_new(PYGTS_SEGMENT_AS_GTS_SEGMENT(self)->v2)) == NULL ) { return NULL; } return (PyObject *)v2; } /* Methods table */ static PyGetSetDef getset[] = { {"v1", (getter)get_v1, NULL, "Vertex 1", NULL}, {"v2", (getter)get_v2, NULL, "Vertex 2", NULL}, {NULL} /* Sentinel */ }; /*-------------------------------------------------------------------------*/ /* Python type methods */ static PyObject * new(PyTypeObject *type, PyObject *args, PyObject *kwds) { PyObject *o; PygtsObject *obj; GtsSegment *tmp; GtsObject *segment=NULL; PyObject *v1_=NULL,*v2_=NULL; PygtsVertex *v1,*v2; guint alloc_gtsobj = TRUE; guint N; /* Parse the args */ if(kwds) { o = PyDict_GetItemString(kwds,"alloc_gtsobj"); if(o==Py_False) { alloc_gtsobj = FALSE; } if(o!=NULL) { PyDict_DelItemString(kwds, "alloc_gtsobj"); } } if(kwds) { Py_INCREF(Py_False); PyDict_SetItemString(kwds,"alloc_gtsobj", Py_False); } /* Allocate the gtsobj (if needed) */ if( alloc_gtsobj ) { /* Parse the args */ if( (N = PyTuple_Size(args)) < 2 ) { PyErr_SetString(PyExc_TypeError,"expected two Vertices"); return NULL; } v1_ = PyTuple_GET_ITEM(args,0); v2_ = PyTuple_GET_ITEM(args,1); /* Convert to PygtsObjects */ if(!pygts_vertex_check(v1_)) { PyErr_SetString(PyExc_TypeError,"expected two Vertices"); return NULL; } if(!pygts_vertex_check(v2_)) { PyErr_SetString(PyExc_TypeError,"expected two Vertices"); return NULL; } v1 = PYGTS_VERTEX(v1_); v2 = PYGTS_VERTEX(v2_); /* Error check */ if(PYGTS_OBJECT(v1)->gtsobj == PYGTS_OBJECT(v2)->gtsobj) { PyErr_SetString(PyExc_ValueError,"Vertices are identical"); return NULL; } /* Create the GtsSegment */ segment = GTS_OBJECT(gts_segment_new(gts_segment_class(), GTS_VERTEX(v1->gtsobj), GTS_VERTEX(v2->gtsobj))); if( segment == NULL ) { PyErr_SetString(PyExc_MemoryError, "could not create Segment"); return NULL; } /* Check for duplicate */ tmp = gts_segment_is_duplicate(GTS_SEGMENT(segment)); if( tmp != NULL ) { gts_object_destroy(segment); segment = GTS_OBJECT(tmp); } /* If corresponding PyObject found in object table, we are done */ if( (obj=g_hash_table_lookup(obj_table,segment)) != NULL ) { Py_INCREF(obj); return (PyObject*)obj; } } /* Chain up */ obj = PYGTS_OBJECT(PygtsObjectType.tp_new(type,args,kwds)); if( alloc_gtsobj ) { obj->gtsobj = segment; pygts_object_register(PYGTS_OBJECT(obj)); } return (PyObject*)obj; } static int init(PygtsSegment *self, PyObject *args, PyObject *kwds) { gint ret; /* Chain up */ if( (ret=PygtsObjectType.tp_init((PyObject*)self,args,kwds)) != 0 ){ return ret; } return 0; } static int compare(PygtsSegment *s1_, PygtsSegment *s2_) { GtsSegment *s1, *s2; #if PYGTS_DEBUG pygts_segment_check((PyObject*)s1_); pygts_segment_check((PyObject*)s2_); #endif s1 = PYGTS_SEGMENT_AS_GTS_SEGMENT(s1_); s2 = PYGTS_SEGMENT_AS_GTS_SEGMENT(s2_); return pygts_segment_compare(s1,s2); } /* Methods table */ PyTypeObject PygtsSegmentType = { PyObject_HEAD_INIT(NULL) 0, /* ob_size */ "gts.Segment", /* tp_name */ sizeof(PygtsSegment), /* tp_basicsize */ 0, /* tp_itemsize */ 0, /* tp_dealloc */ 0, /* tp_print */ 0, /* tp_getattr */ 0, /* tp_setattr */ (cmpfunc)compare, /* tp_compare */ 0, /* tp_repr */ 0, /* tp_as_number */ 0, /* tp_as_sequence */ 0, /* tp_as_mapping */ 0, /* tp_hash */ 0, /* tp_call */ 0, /* tp_str */ 0, /* tp_getattro */ 0, /* tp_setattro */ 0, /* tp_as_buffer */ Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, /* tp_flags */ "Segment object", /* tp_doc */ 0, /* tp_traverse */ 0, /* tp_clear */ 0, /* tp_richcompare */ 0, /* tp_weaklistoffset */ 0, /* tp_iter */ 0, /* tp_iternext */ methods, /* tp_methods */ 0, /* tp_members */ getset, /* tp_getset */ 0, /* tp_base */ 0, /* tp_dict */ 0, /* tp_descr_get */ 0, /* tp_descr_set */ 0, /* tp_dictoffset */ (initproc)init, /* tp_init */ 0, /* tp_alloc */ (newfunc)new /* tp_new */ }; /*-------------------------------------------------------------------------*/ /* Pygts functions */ gboolean pygts_segment_check(PyObject* o) { if(! PyObject_TypeCheck(o, &PygtsSegmentType)) { return FALSE; } else { #if PYGTS_DEBUG return pygts_segment_is_ok(PYGTS_SEGMENT(o)); #else return TRUE; #endif } } gboolean pygts_segment_is_ok(PygtsSegment *s) { if(!pygts_object_is_ok(PYGTS_OBJECT(s))) return FALSE; return gts_segment_is_ok(PYGTS_SEGMENT_AS_GTS_SEGMENT(s)); } PygtsSegment * pygts_segment_new(GtsSegment *s) { PyObject *args, *kwds; PygtsObject *segment; /* Check for Segment in the object table */ if( (segment=PYGTS_OBJECT(g_hash_table_lookup(obj_table,GTS_OBJECT(s)))) != NULL ) { Py_INCREF(segment); return PYGTS_FACE(segment); } /* Build a new Segment */ args = Py_BuildValue("OO",Py_None,Py_None); kwds = Py_BuildValue("{s:O}","alloc_gtsobj",Py_False); segment = PYGTS_SEGMENT(PygtsSegmentType.tp_new(&PygtsSegmentType, args, kwds)); Py_DECREF(args); Py_DECREF(kwds); if( segment == NULL ) { PyErr_SetString(PyExc_MemoryError,"could not create Segment"); return NULL; } segment->gtsobj = GTS_OBJECT(s); /* Register and return */ pygts_object_register(segment); return PYGTS_SEGMENT(segment); } int pygts_segment_compare(GtsSegment* s1,GtsSegment* s2) { if( (pygts_point_compare(GTS_POINT(s1->v1),GTS_POINT(s2->v1))==0 && pygts_point_compare(GTS_POINT(s1->v2),GTS_POINT(s2->v2))==0) || (pygts_point_compare(GTS_POINT(s1->v1),GTS_POINT(s2->v2))==0 && pygts_point_compare(GTS_POINT(s1->v2),GTS_POINT(s2->v1))==0) ) { return 0; } return -1; } trunk-2018.02b/py/3rd-party/pygts-0.3.1/segment.h000066400000000000000000000030601324306050200210600ustar00rootroot00000000000000/* pygts - python package for the manipulation of triangulated surfaces * * Copyright (C) 2009 Thomas J. Duck * All rights reserved. * * Thomas J. Duck * Department of Physics and Atmospheric Science, * Dalhousie University, Halifax, Nova Scotia, Canada, B3H 3J5 * * NOTICE * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library 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 * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public * License along with this library; if not, write to the * Free Software Foundation, Inc., 59 Temple Place - Suite 330, * Boston, MA 02111-1307, USA. */ #ifndef __PYGTS_SEGMENT_H__ #define __PYGTS_SEGMENT_H__ typedef struct _PygtsObject PygtsSegment; #define PYGTS_SEGMENT(obj) ((PygtsSegment*)obj) #define PYGTS_SEGMENT_AS_GTS_SEGMENT(o) (GTS_SEGMENT(PYGTS_OBJECT(o)->gtsobj)) extern PyTypeObject PygtsSegmentType; gboolean pygts_segment_check(PyObject* o); gboolean pygts_segment_is_ok(PygtsSegment *t); PygtsSegment* pygts_segment_new(GtsSegment *f); int pygts_segment_compare(GtsSegment* s1,GtsSegment* s2); #endif /* __PYGTS_SEGMENT_H__ */ trunk-2018.02b/py/3rd-party/pygts-0.3.1/surface.c000066400000000000000000001565711324306050200210610ustar00rootroot00000000000000/* pygts - python package for the manipulation of triangulated surfaces * * Copyright (C) 2009 Thomas J. Duck * All rights reserved. * * Thomas J. Duck * Department of Physics and Atmospheric Science, * Dalhousie University, Halifax, Nova Scotia, Canada, B3H 3J5 * * NOTICE * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library 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 * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public * License along with this library; if not, write to the * Free Software Foundation, Inc., 59 Temple Place - Suite 330, * Boston, MA 02111-1307, USA. */ #include "pygts.h" #if PYGTS_DEBUG #define SELF_CHECK if(!pygts_surface_check((PyObject*)self)) { \ PyErr_SetString(PyExc_RuntimeError, \ "problem with self object (internal error)"); \ return NULL; \ } #else #define SELF_CHECK #endif /*-------------------------------------------------------------------------*/ /* Methods exported to python */ static PyObject* is_ok(PygtsSurface *self, PyObject *args) { if(pygts_surface_is_ok(self)) { Py_INCREF(Py_True); return Py_True; } else { Py_INCREF(Py_False); return Py_False; } } static PyObject* add(PygtsSurface *self, PyObject *args) { PyObject *o_; PygtsFace *f; PygtsSurface *s; SELF_CHECK /* Parse the args */ if(! PyArg_ParseTuple(args, "O", &o_) ) return NULL; /* Convert to PygtsObjects */ if(pygts_face_check(o_)) { f = PYGTS_FACE(o_); gts_surface_add_face(PYGTS_SURFACE_AS_GTS_SURFACE(self), PYGTS_FACE_AS_GTS_FACE(f)); } else if(pygts_surface_check(o_)) { s = PYGTS_SURFACE(o_); /* Make the call */ gts_surface_merge(PYGTS_SURFACE_AS_GTS_SURFACE(self), PYGTS_SURFACE_AS_GTS_SURFACE(s)); } else { PyErr_SetString(PyExc_TypeError,"expected a Face or a Surface"); return NULL; } Py_INCREF(Py_None); return Py_None; } static PyObject* pygts_remove(PygtsSurface *self, PyObject *args) { PyObject *f_; PygtsFace *f; SELF_CHECK /* Parse the args */ if(! PyArg_ParseTuple(args, "O", &f_) ) return NULL; /* Convert to PygtsObjects */ if(!pygts_face_check(f_)) { PyErr_SetString(PyExc_TypeError,"expected a Face"); return NULL; } f = PYGTS_FACE(f_); /* Make the call */ gts_surface_remove_face(PYGTS_SURFACE_AS_GTS_SURFACE(self), PYGTS_FACE_AS_GTS_FACE(f)); Py_INCREF(Py_None); return Py_None; } static PyObject* copy(PygtsSurface *self, PyObject *args) { PyObject *s_; PygtsSurface *s; SELF_CHECK /* Parse the args */ if(! PyArg_ParseTuple(args, "O", &s_) ) return NULL; /* Convert to PygtsObjects */ if(!pygts_surface_check(s_)) { PyErr_SetString(PyExc_TypeError,"expected a Surface"); return NULL; } s = PYGTS_SURFACE(s_); /* Make the call */ gts_surface_copy(PYGTS_SURFACE_AS_GTS_SURFACE(self), PYGTS_SURFACE_AS_GTS_SURFACE(s)); Py_INCREF((PyObject*)self); return (PyObject*)self; } static PyObject* is_manifold(PygtsSurface *self, PyObject *args) { SELF_CHECK if( gts_surface_is_manifold(PYGTS_SURFACE_AS_GTS_SURFACE(self)) ) { Py_INCREF(Py_True); return Py_True; } else { Py_INCREF(Py_False); return Py_False; } } static PyObject* manifold_faces(PygtsSurface *self, PyObject *args) { PyObject *e_; PygtsEdge *e; GtsFace *f1,*f2; PygtsFace *face1,*face2; SELF_CHECK /* Parse the args */ if(! PyArg_ParseTuple(args, "O", &e_) ) return NULL; /* Convert to PygtsObjects */ if(!pygts_edge_check(e_)) { PyErr_SetString(PyExc_TypeError,"expected an Edge"); return NULL; } e = PYGTS_EDGE(e_); /* Make the call */ if(!gts_edge_manifold_faces(PYGTS_EDGE_AS_GTS_EDGE(e), PYGTS_SURFACE_AS_GTS_SURFACE(self), &f1, &f2)) { Py_INCREF(Py_None); return Py_None; } if( (face1 = pygts_face_new(f1)) == NULL ) { return NULL; } if( (face2 = pygts_face_new(f2)) == NULL ) { Py_DECREF(face1); return NULL; } return Py_BuildValue("OO",face1,face2); } static PyObject* is_orientable(PygtsSurface *self, PyObject *args) { SELF_CHECK if(gts_surface_is_orientable(PYGTS_SURFACE_AS_GTS_SURFACE(self))) { Py_INCREF(Py_True); return Py_True; } else { Py_INCREF(Py_False); return Py_False; } } static PyObject* is_closed(PygtsSurface *self, PyObject *args) { SELF_CHECK if(gts_surface_is_closed(PYGTS_SURFACE_AS_GTS_SURFACE(self))) { Py_INCREF(Py_True); return Py_True; } else { Py_INCREF(Py_False); return Py_False; } } static PyObject* boundary(PyObject *self, PyObject *args) { PyObject *tuple; guint i,N; GSList *edges=NULL,*e; PygtsEdge *edge; SELF_CHECK /* Make the call */ if( (edges = gts_surface_boundary(PYGTS_SURFACE_AS_GTS_SURFACE(self))) == NULL ) { PyErr_SetString(PyExc_RuntimeError,"could not retrieve edges"); return NULL; } /* Assemble the return tuple */ N = g_slist_length(edges); if( (tuple=PyTuple_New(N)) == NULL) { PyErr_SetString(PyExc_MemoryError,"could not create tuple"); return NULL; } e = edges; for(i=0;idata))) == NULL ) { Py_DECREF(tuple); g_slist_free(edges); } PyTuple_SET_ITEM(tuple,i,(PyObject*)edge); e = g_slist_next(e); } g_slist_free(edges); return tuple; } static PyObject* area(PygtsSurface *self, PyObject *args) { GtsSurface *s; SELF_CHECK s = PYGTS_SURFACE_AS_GTS_SURFACE(self); return Py_BuildValue("d",gts_surface_area(s)); } static PyObject* volume(PygtsSurface *self, PyObject *args) { GtsSurface *s; SELF_CHECK s = PYGTS_SURFACE_AS_GTS_SURFACE(self); if(!gts_surface_is_closed(s)) { PyErr_SetString(PyExc_RuntimeError,"Surface is not closed"); return NULL; } if(!gts_surface_is_orientable(s)) { PyErr_SetString(PyExc_RuntimeError,"Surface is not orientable"); return NULL; } return Py_BuildValue("d",gts_surface_volume(s)); } static PyObject* center_of_mass(PygtsSurface *self, PyObject *args) { GtsSurface *s; GtsVector cm; SELF_CHECK s = PYGTS_SURFACE_AS_GTS_SURFACE(self); gts_surface_center_of_mass(s,cm); return Py_BuildValue("ddd",cm[0],cm[1],cm[2]); } static PyObject* center_of_area(PygtsSurface *self, PyObject *args) { GtsSurface *s; GtsVector cm; SELF_CHECK s = PYGTS_SURFACE_AS_GTS_SURFACE(self); gts_surface_center_of_area(s,cm); return Py_BuildValue("ddd",cm[0],cm[1],cm[2]); } static PyObject* pygts_write(PygtsSurface *self, PyObject *args) { PyObject *f_; FILE *f; SELF_CHECK /* Parse the args */ if(! PyArg_ParseTuple(args, "O", &f_) ) return NULL; /* Convert to PygtsObjects */ if(!PyFile_Check(f_)) { PyErr_SetString(PyExc_TypeError,"expected a File"); return NULL; } f = PyFile_AsFile(f_); /* Write to the file */ gts_surface_write(PYGTS_SURFACE_AS_GTS_SURFACE(self),f); Py_INCREF(Py_None); return Py_None; } static PyObject* pygts_write_oogl(PygtsSurface *self, PyObject *args) { PyObject *f_; FILE *f; SELF_CHECK /* Parse the args */ if(! PyArg_ParseTuple(args, "O", &f_) ) return NULL; /* Convert to PygtsObjects */ if(!PyFile_Check(f_)) { PyErr_SetString(PyExc_TypeError,"expected a File"); return NULL; } f = PyFile_AsFile(f_); /* Write to the file */ gts_surface_write_oogl(PYGTS_SURFACE_AS_GTS_SURFACE(self),f); Py_INCREF(Py_None); return Py_None; } static PyObject* pygts_write_oogl_boundary(PygtsSurface *self, PyObject *args) { PyObject *f_; FILE *f; SELF_CHECK /* Parse the args */ if(! PyArg_ParseTuple(args, "O", &f_) ) return NULL; /* Convert to PygtsObjects */ if(!PyFile_Check(f_)) { PyErr_SetString(PyExc_TypeError,"expected a File"); return NULL; } f = PyFile_AsFile(f_); /* Write to the file */ gts_surface_write_oogl_boundary(PYGTS_SURFACE_AS_GTS_SURFACE(self),f); Py_INCREF(Py_None); return Py_None; } static PyObject* pygts_write_vtk(PygtsSurface *self, PyObject *args) { PyObject *f_; FILE *f; SELF_CHECK /* Parse the args */ if(! PyArg_ParseTuple(args, "O", &f_) ) return NULL; /* Convert to PygtsObjects */ if(!PyFile_Check(f_)) { PyErr_SetString(PyExc_TypeError,"expected a File"); return NULL; } f = PyFile_AsFile(f_); /* Write to the file */ gts_surface_write_vtk(PYGTS_SURFACE_AS_GTS_SURFACE(self),f); Py_INCREF(Py_None); return Py_None; } static PyObject* fan_oriented(PygtsSurface *self, PyObject *args) { PyObject *v_; PygtsVertex *v; GSList *edges=NULL, *e; guint i,N; PyObject *tuple; PygtsEdge *edge; SELF_CHECK /* Parse the args */ if(! PyArg_ParseTuple(args, "O", &v_) ) return NULL; /* Convert to PygtsObjects */ if(!pygts_vertex_check(v_)) { PyErr_SetString(PyExc_TypeError,"expected a Vertex"); return NULL; } v = PYGTS_VERTEX(v_); /* Check that the Surface is orientable; the calculation will * fail otherwise. */ if(!gts_surface_is_orientable(PYGTS_SURFACE_AS_GTS_SURFACE(self))) { PyErr_SetString(PyExc_RuntimeError,"Surface must be orientable"); return NULL; } /* Make the call */ edges = gts_vertex_fan_oriented(PYGTS_VERTEX_AS_GTS_VERTEX(v), PYGTS_SURFACE_AS_GTS_SURFACE(self)); /* Build the return tuple */ N = g_slist_length(edges); if( (tuple=PyTuple_New(N)) == NULL) { PyErr_SetString(PyExc_MemoryError,"Could not create tuple"); return NULL; } e = edges; for(i=0;idata))) == NULL ) { Py_DECREF(tuple); g_slist_free(edges); return NULL; } PyTuple_SET_ITEM(tuple,i,(PyObject*)edge); e = g_slist_next(e); } return tuple; } static PyObject* split(PygtsSurface *self, PyObject *args) { GSList *surfaces, *s; PyObject *tuple; PygtsSurface *surface; guint n,N; SELF_CHECK surfaces = gts_surface_split(PYGTS_SURFACE_AS_GTS_SURFACE(self)); /* Create a tuple to put the Surfaces into */ N = g_slist_length(surfaces); if( (tuple=PyTuple_New(N)) == NULL) { PyErr_SetString(PyExc_MemoryError,"could not create tuple"); return NULL; } /* Put PygtsSurface objects into the tuple */ s = surfaces; for(n=0;ndata))) == NULL ) { Py_DECREF(tuple); return NULL; } surface->traverse = NULL; PyTuple_SET_ITEM(tuple, n, (PyObject*)surface); s = g_slist_next(s); } return tuple; } /* Helper function for vertices() */ static void get_vertex(GtsVertex *vertex, GtsVertex ***v) { **v = vertex; *v += 1; } static PyObject* vertices(PygtsSurface *self, PyObject *args) { PyObject *tuple; PygtsVertex *vertex; PygtsVertex **vertices,**v; guint i,N=0; SELF_CHECK /* Get the number of vertices */ N = gts_surface_vertex_number(PYGTS_SURFACE_AS_GTS_SURFACE(self)); /* Retrieve all of the vertex pointers into a temporary array */ if( (vertices = (PygtsVertex**)malloc(N*sizeof(PygtsVertex*))) == NULL ) { PyErr_SetString(PyExc_MemoryError,"could not create array"); return NULL; } v = vertices; gts_surface_foreach_vertex(PYGTS_SURFACE_AS_GTS_SURFACE(self), (GtsFunc)get_vertex,&v); /* Create a tuple to put the vertices into */ if( (tuple=PyTuple_New(N)) == NULL) { PyErr_SetString(PyExc_MemoryError,"could not create tuple"); return NULL; } /* Put PygtsVertex objects into the tuple */ v = vertices; for(i=0;idata))) == NULL ) { Py_DECREF(tuple); g_slist_free(edges); return NULL; } PyTuple_SET_ITEM(tuple,i,(PyObject*)edge); e = g_slist_next(e); } g_slist_free(edges); return tuple; } /* Helper function for edges() */ static void get_face(GtsFace *face, GSList **faces) { *faces = g_slist_prepend(*faces,face); } static PyObject* faces(PyObject *self, PyObject *args) { PyObject *tuple=NULL, *obj; guint i,N; GSList *edges=NULL,*faces=NULL,*f; PygtsFace *face; SELF_CHECK /* Parse the args */ if(! PyArg_ParseTuple(args, "|O", &tuple) ) { return NULL; } if(tuple) { if(PyList_Check(tuple)) { tuple = PyList_AsTuple(tuple); } else { Py_INCREF(tuple); } if(!PyTuple_Check(tuple)) { Py_DECREF(tuple); PyErr_SetString(PyExc_TypeError,"expected a list or tuple of edges"); return NULL; } /* Assemble the GSList */ N = PyTuple_Size(tuple); for(i=0;idata))) == NULL ) { Py_DECREF(tuple); g_slist_free(faces); return NULL; } PyTuple_SET_ITEM(tuple,i,(PyObject*)face); f = g_slist_next(f); } g_slist_free(faces); return tuple; } /* Helper for face_indices() */ typedef struct { PyObject *vertices,*indices; /* Vertex and indices tuples */ guint Nv,Ni; /* Number of vertices and indices */ guint n; /* Current face index */ gboolean errflag; } IndicesData; /* Helper for face_indices() */ static void get_indices(GtsFace *face, IndicesData *data) { PyObject *t; GtsVertex *v[3]; guint i,j; gboolean flag; if(data->errflag) return; /* Put the vertex pointers in an array */ gts_triangle_vertices( GTS_TRIANGLE(face), &(v[0]), &(v[1]), &(v[2]) ); /* Create a tuple to put the indices into */ if( (t=PyTuple_New(3)) == NULL) { PyErr_SetString(PyExc_MemoryError,"could not create tuple"); data->errflag = TRUE; return; } PyTuple_SET_ITEM(data->indices, data->n, t); /* Determine the indices */ for(i=0;i<3;i++) { flag = FALSE; for(j=0;jNv;j++) { if( PYGTS_VERTEX_AS_GTS_VERTEX(PyTuple_GET_ITEM(data->vertices,j)) ==v[i] ) { PyTuple_SET_ITEM(t, i, PyInt_FromLong(j)); flag = TRUE; break; } } if(!flag) { PyErr_SetString(PyExc_RuntimeError, "Could not initialize tuple (internal error)"); data->errflag = TRUE; return; } } data->n += 1; } static PyObject* face_indices(PygtsSurface *self, PyObject *args) { PyObject *vertices,*indices; IndicesData data; guint Nv,Nf; guint i; SELF_CHECK /* Parse the args */ if(! PyArg_ParseTuple(args, "O", &vertices) ) return NULL; /* Make sure that the tuple contains only vertices */ Nv = PyTuple_Size(vertices); for(i=0;idata); n = g_slist_length(f); if( (tuples[i]=PyTuple_New(n)) == NULL) { PyErr_SetString(PyExc_MemoryError,"could not create tuple"); Py_DECREF(tuple); free(tuples); return NULL; } PyTuple_SET_ITEM(tuple, i, tuples[i]); s = g_slist_next(s); } /* Put PygtsFace objects into the tuple */ s = strips; for(i=0;idata); n = g_slist_length(f); for(j=0;jdata))) == NULL ) { } PyTuple_SET_ITEM(tuples[i], j, (PyObject*)face); f = g_slist_next(f); } s = g_slist_next(s); } free(tuples); return tuple; } static PyObject* stats(PygtsSurface *self, PyObject *args) { GtsSurfaceStats stats; PyObject *dict, *edges_per_vertex, *faces_per_edge; SELF_CHECK /* Make the call */ gts_surface_stats(PYGTS_SURFACE_AS_GTS_SURFACE(self),&stats); /* Create the dictionaries */ if( (dict = PyDict_New()) == NULL ) { PyErr_SetString(PyExc_MemoryError,"cannot create dict"); return NULL; } if( (edges_per_vertex = PyDict_New()) == NULL ) { PyErr_SetString(PyExc_MemoryError,"cannot create dict"); Py_DECREF(dict); return NULL; } if( (faces_per_edge = PyDict_New()) == NULL ) { PyErr_SetString(PyExc_MemoryError,"cannot create dict"); Py_DECREF(dict); Py_DECREF(edges_per_vertex); return NULL; } /* Populate the edges_per_vertex dict */ PyDict_SetItemString(edges_per_vertex,"min", Py_BuildValue("d",stats.edges_per_vertex.min)); PyDict_SetItemString(edges_per_vertex,"max", Py_BuildValue("d",stats.edges_per_vertex.max)); PyDict_SetItemString(edges_per_vertex,"sum", Py_BuildValue("d",stats.edges_per_vertex.sum)); PyDict_SetItemString(edges_per_vertex,"sum2", Py_BuildValue("d",stats.edges_per_vertex.sum2)); PyDict_SetItemString(edges_per_vertex,"mean", Py_BuildValue("d",stats.edges_per_vertex.mean)); PyDict_SetItemString(edges_per_vertex,"stddev", Py_BuildValue("d",stats.edges_per_vertex.stddev)); PyDict_SetItemString(edges_per_vertex,"n", Py_BuildValue("i",stats.edges_per_vertex.n)); /* Populate the faces_per_edge dict */ PyDict_SetItemString(faces_per_edge,"min", Py_BuildValue("d",stats.faces_per_edge.min)); PyDict_SetItemString(faces_per_edge,"max", Py_BuildValue("d",stats.faces_per_edge.max)); PyDict_SetItemString(faces_per_edge,"sum", Py_BuildValue("d",stats.faces_per_edge.sum)); PyDict_SetItemString(faces_per_edge,"sum2", Py_BuildValue("d",stats.faces_per_edge.sum2)); PyDict_SetItemString(faces_per_edge,"mean", Py_BuildValue("d",stats.faces_per_edge.mean)); PyDict_SetItemString(faces_per_edge,"stddev", Py_BuildValue("d",stats.faces_per_edge.stddev)); PyDict_SetItemString(faces_per_edge,"n", Py_BuildValue("i",stats.faces_per_edge.n)); /* Populate the main dict */ PyDict_SetItemString(dict,"n_faces", Py_BuildValue("i",stats.n_faces)); PyDict_SetItemString(dict,"n_incompatible_faces", Py_BuildValue("i",stats.n_incompatible_faces)); PyDict_SetItemString(dict,"n_boundary_edges", Py_BuildValue("i",stats.n_boundary_edges)); PyDict_SetItemString(dict,"n_non_manifold_edges", Py_BuildValue("i",stats.n_non_manifold_edges)); PyDict_SetItemString(dict,"edges_per_vertex", edges_per_vertex); PyDict_SetItemString(dict,"faces_per_edge", faces_per_edge); return dict; } static PyObject* quality_stats(PygtsSurface *self, PyObject *args) { GtsSurfaceQualityStats stats; PyObject *dict, *face_quality, *face_area, *edge_length, *edge_angle; SELF_CHECK /* Make the call */ gts_surface_quality_stats(PYGTS_SURFACE_AS_GTS_SURFACE(self),&stats); /* Create the dictionaries */ if( (dict = PyDict_New()) == NULL ) { PyErr_SetString(PyExc_MemoryError,"cannot create dict"); return NULL; } if( (face_quality = PyDict_New()) == NULL ) { PyErr_SetString(PyExc_MemoryError,"cannot create dict"); Py_DECREF(dict); return NULL; } if( (face_area = PyDict_New()) == NULL ) { PyErr_SetString(PyExc_MemoryError,"cannot create dict"); Py_DECREF(dict); Py_DECREF(face_quality); return NULL; } if( (edge_length = PyDict_New()) == NULL ) { PyErr_SetString(PyExc_MemoryError,"cannot create dict"); Py_DECREF(dict); Py_DECREF(face_quality); Py_DECREF(face_area); return NULL; } if( (edge_angle = PyDict_New()) == NULL ) { PyErr_SetString(PyExc_MemoryError,"cannot create dict"); Py_DECREF(dict); Py_DECREF(face_quality); Py_DECREF(face_area); Py_DECREF(edge_length); return NULL; } /* Populate the face_quality dict */ PyDict_SetItemString(face_quality,"min", Py_BuildValue("d",stats.face_quality.min)); PyDict_SetItemString(face_quality,"max", Py_BuildValue("d",stats.face_quality.max)); PyDict_SetItemString(face_quality,"sum", Py_BuildValue("d",stats.face_quality.sum)); PyDict_SetItemString(face_quality,"sum2", Py_BuildValue("d",stats.face_quality.sum2)); PyDict_SetItemString(face_quality,"mean", Py_BuildValue("d",stats.face_quality.mean)); PyDict_SetItemString(face_quality,"stddev", Py_BuildValue("d",stats.face_quality.stddev)); PyDict_SetItemString(face_quality,"n", Py_BuildValue("i",stats.face_quality.n)); /* Populate the face_area dict */ PyDict_SetItemString(face_area,"min", Py_BuildValue("d",stats.face_area.min)); PyDict_SetItemString(face_area,"max", Py_BuildValue("d",stats.face_area.max)); PyDict_SetItemString(face_area,"sum", Py_BuildValue("d",stats.face_area.sum)); PyDict_SetItemString(face_area,"sum2", Py_BuildValue("d",stats.face_area.sum2)); PyDict_SetItemString(face_area,"mean", Py_BuildValue("d",stats.face_area.mean)); PyDict_SetItemString(face_area,"stddev", Py_BuildValue("d",stats.face_area.stddev)); PyDict_SetItemString(face_area,"n", Py_BuildValue("i",stats.face_area.n)); /* Populate the edge_length dict */ PyDict_SetItemString(edge_length,"min", Py_BuildValue("d",stats.edge_length.min)); PyDict_SetItemString(edge_length,"max", Py_BuildValue("d",stats.edge_length.max)); PyDict_SetItemString(edge_length,"sum", Py_BuildValue("d",stats.edge_length.sum)); PyDict_SetItemString(edge_length,"sum2", Py_BuildValue("d",stats.edge_length.sum2)); PyDict_SetItemString(edge_length,"mean", Py_BuildValue("d",stats.edge_length.mean)); PyDict_SetItemString(edge_length,"stddev", Py_BuildValue("d",stats.edge_length.stddev)); PyDict_SetItemString(edge_length,"n", Py_BuildValue("i",stats.edge_length.n)); /* Populate the edge_angle dict */ PyDict_SetItemString(edge_angle,"min", Py_BuildValue("d",stats.edge_angle.min)); PyDict_SetItemString(edge_angle,"max", Py_BuildValue("d",stats.edge_angle.max)); PyDict_SetItemString(edge_angle,"sum", Py_BuildValue("d",stats.edge_angle.sum)); PyDict_SetItemString(edge_angle,"sum2", Py_BuildValue("d",stats.edge_angle.sum2)); PyDict_SetItemString(edge_angle,"mean", Py_BuildValue("d",stats.edge_angle.mean)); PyDict_SetItemString(edge_angle,"stddev", Py_BuildValue("d",stats.edge_angle.stddev)); PyDict_SetItemString(edge_angle,"n", Py_BuildValue("i",stats.edge_angle.n)); /* Populate the main dict */ PyDict_SetItemString(dict,"face_quality", face_quality); PyDict_SetItemString(dict,"face_area", face_area); PyDict_SetItemString(dict,"edge_length", edge_length); PyDict_SetItemString(dict,"edge_angle", edge_angle); return dict; } static PyObject* tessellate(PygtsSurface *self, PyObject *args) { SELF_CHECK gts_surface_tessellate(PYGTS_SURFACE_AS_GTS_SURFACE(self),NULL,NULL); Py_INCREF(Py_None); return Py_None; } /* Helper function for inter() */ void get_largest_coord(GtsVertex *v,gdouble *val) { if( fabs(GTS_POINT(v)->x) > *val ) *val = fabs(GTS_POINT(v)->x); if( fabs(GTS_POINT(v)->y) > *val ) *val = fabs(GTS_POINT(v)->y); if( fabs(GTS_POINT(v)->z) > *val ) *val = fabs(GTS_POINT(v)->z); } /* Helper function for intersection operations */ static PyObject* inter(PygtsSurface *self, PyObject *args, GtsBooleanOperation op1, GtsBooleanOperation op2) { PyObject *obj; PyObject *s_; PygtsSurface *s; GtsSurface *surface; GtsVector cm1, cm2; gdouble area1, area2; GtsSurfaceInter *si; GNode *tree1, *tree2; gboolean is_open1, is_open2, closed; gdouble eps=0.; /* Parse the args */ if(! PyArg_ParseTuple(args, "O", &s_) ) return NULL; /* Convert to PygtsObjects */ if(!pygts_surface_check(s_)) { PyErr_SetString(PyExc_TypeError,"expected a Surface"); return NULL; } s = PYGTS_SURFACE(s_); /* Make sure that we don't have two pointers to the same surface */ if( self == s ) { PyErr_SetString(PyExc_RuntimeError, "can't determine intersection with self"); return NULL; } /* *** ATTENTION *** * Eliminate any active gts traverse objects. They appear to interfere * with the intersection calculation. I would guess that this is due * to the use of the "reserved" field (i.e., it doesn't get properly * reset until the traverse is destroyed). * * I don't expect this to cause problems here, but a bug report should be * filed. */ if(self->traverse!=NULL) { gts_surface_traverse_destroy(self->traverse); self->traverse = NULL; } if(s->traverse!=NULL) { gts_surface_traverse_destroy(s->traverse); s->traverse = NULL; } /* *** ATTENTION *** */ /* Check for self-intersections in either surface */ if( gts_surface_is_self_intersecting(PYGTS_SURFACE_AS_GTS_SURFACE(self)) != NULL ) { PyErr_SetString(PyExc_RuntimeError,"Surface is self-intersecting"); return NULL; } if( gts_surface_is_self_intersecting(PYGTS_SURFACE_AS_GTS_SURFACE(s)) != NULL ) { PyErr_SetString(PyExc_RuntimeError,"Surface is self-intersecting"); return NULL; } /* Avoid complete self-intersection of two surfaces*/ if( (gts_surface_face_number(PYGTS_SURFACE_AS_GTS_SURFACE(self)) == gts_surface_face_number(PYGTS_SURFACE_AS_GTS_SURFACE(s))) && (gts_surface_edge_number(PYGTS_SURFACE_AS_GTS_SURFACE(self)) == gts_surface_edge_number(PYGTS_SURFACE_AS_GTS_SURFACE(s))) && (gts_surface_vertex_number(PYGTS_SURFACE_AS_GTS_SURFACE(self)) == gts_surface_vertex_number(PYGTS_SURFACE_AS_GTS_SURFACE(s))) && (gts_surface_area(PYGTS_SURFACE_AS_GTS_SURFACE(self)) == gts_surface_area(PYGTS_SURFACE_AS_GTS_SURFACE(s))) ) { area1 = \ gts_surface_center_of_area(PYGTS_SURFACE_AS_GTS_SURFACE(self),cm1); area2 = \ gts_surface_center_of_area(PYGTS_SURFACE_AS_GTS_SURFACE(s),cm2); if( (area1==area2) && (cm1[0]==cm2[0]) && (cm1[1]==cm2[1]) && (cm1[2]==cm2[2]) ) { PyErr_SetString(PyExc_RuntimeError,"Surfaces mutually intersect"); return NULL; } } /* Get bounding boxes */ if( (tree1=gts_bb_tree_surface(PYGTS_SURFACE_AS_GTS_SURFACE(self))) ==NULL ) { PyErr_SetString(PyExc_MemoryError,"could not create tree"); return NULL; } is_open1 = !gts_surface_is_closed(PYGTS_SURFACE_AS_GTS_SURFACE(self)); if( (tree2=gts_bb_tree_surface(PYGTS_SURFACE_AS_GTS_SURFACE(s))) ==NULL ) { gts_bb_tree_destroy(tree1, TRUE); PyErr_SetString(PyExc_MemoryError,"could not create tree"); return NULL; } is_open2 = !gts_surface_is_closed(PYGTS_SURFACE_AS_GTS_SURFACE(s)); /* Get the surface intersection object */ if( (si = gts_surface_inter_new(gts_surface_inter_class(), PYGTS_SURFACE_AS_GTS_SURFACE(self), PYGTS_SURFACE_AS_GTS_SURFACE(s), tree1, tree2, is_open1, is_open2))==NULL) { gts_bb_tree_destroy(tree1, TRUE); gts_bb_tree_destroy(tree2, TRUE); PyErr_SetString(PyExc_RuntimeError,"could not create GtsSurfaceInter"); return NULL; } gts_bb_tree_destroy(tree1, TRUE); gts_bb_tree_destroy(tree2, TRUE); /* Check that the surface intersection object is closed */ gts_surface_inter_check(si,&closed); if( closed == FALSE ) { gts_object_destroy(GTS_OBJECT(si)); PyErr_SetString(PyExc_RuntimeError,"result is not closed"); return NULL; } /* Create the surface */ if( (surface = gts_surface_new(gts_surface_class(), gts_face_class(), gts_edge_class(), gts_vertex_class())) == NULL ) { PyErr_SetString(PyExc_MemoryError, "could not create Surface"); return NULL; } /* Calculate the new surface */ gts_surface_inter_boolean(si, surface ,op1); gts_surface_inter_boolean(si, surface ,op2); gts_object_destroy(GTS_OBJECT(si)); /* Clean up the result */ gts_surface_foreach_vertex(surface, (GtsFunc)get_largest_coord, &eps); eps *= pow(2.,-50); pygts_vertex_cleanup(surface,1.e-9); pygts_edge_cleanup(surface); pygts_face_cleanup(surface); /* Check for self-intersection */ if( gts_surface_is_self_intersecting(surface) != NULL ) { gts_object_destroy(GTS_OBJECT(surface)); PyErr_SetString(PyExc_RuntimeError,"result is self-intersecting surface"); return NULL; } /* Create the return Surface */ if( (obj = (PyObject*)pygts_surface_new(surface)) == NULL ) { gts_object_destroy(GTS_OBJECT(surface)); return NULL; } return obj; } static PyObject* intersection(PygtsSurface *self, PyObject *args, GtsBooleanOperation op1, GtsBooleanOperation op2) { SELF_CHECK return inter(self,args,GTS_1_IN_2,GTS_2_IN_1); } static PyObject* pygts_union(PygtsSurface *self, PyObject *args) { SELF_CHECK return inter(self,args,GTS_1_OUT_2,GTS_2_OUT_1); } static PyObject* difference(PygtsSurface *self, PyObject *args) { SELF_CHECK return inter(self,args,GTS_1_OUT_2,GTS_2_IN_1); } /* Helper for rotate(), scale() and translate() transforms */ typedef struct { double dx, dy, dz, a; gboolean errflag; } TransformData; /* Helper for rotate() */ static void rotate_point(GtsPoint *p, TransformData *data) { if(data->errflag) return; if(pygts_point_rotate(p,data->dx,data->dy,data->dz,data->a)==-1) data->errflag=TRUE; } static PyObject* rotate(PygtsSurface* self, PyObject *args, PyObject *keywds) { TransformData data; static char *kwlist[] = {"dx", "dy", "dz", "a", NULL}; SELF_CHECK data.dx=0; data.dy=0; data.dz=0; data.a=0; data.errflag = FALSE; /* Parse the args */ if(! PyArg_ParseTupleAndKeywords(args, keywds,"|dddd", kwlist, &(data.dx), &(data.dy), &(data.dz), &(data.a)) ) { return NULL; } gts_surface_foreach_vertex(PYGTS_SURFACE_AS_GTS_SURFACE(self), (GtsFunc)rotate_point,&data); if(data.errflag) return NULL; Py_INCREF(Py_None); return Py_None; } /* Helper for scale() */ static void scale_point(GtsPoint *p, TransformData *data) { if(data->errflag) return; if(pygts_point_scale(p,data->dx,data->dy,data->dz)==-1) data->errflag=TRUE; } static PyObject* scale(PygtsSurface* self, PyObject *args, PyObject *keywds) { TransformData data; static char *kwlist[] = {"dx", "dy", "dz", NULL}; SELF_CHECK data.dx=1; data.dy=1; data.dz=1; data.a=0; data.errflag = FALSE; /* Parse the args */ if(! PyArg_ParseTupleAndKeywords(args, keywds,"|ddd", kwlist, &(data.dx), &(data.dy), &(data.dz)) ) { return NULL; } gts_surface_foreach_vertex(PYGTS_SURFACE_AS_GTS_SURFACE(self), (GtsFunc)scale_point,&data); if(data.errflag) return NULL; Py_INCREF(Py_None); return Py_None; } /* Helper for translate() */ static void translate_point(GtsPoint *p, TransformData *data) { if(data->errflag) return; if(pygts_point_translate(p,data->dx,data->dy,data->dz)==-1) data->errflag=TRUE; } static PyObject* translate(PygtsSurface* self, PyObject *args, PyObject *keywds) { TransformData data; static char *kwlist[] = {"dx", "dy", "dz", NULL}; SELF_CHECK data.dx=0; data.dy=0; data.dz=0; data.a=0; data.errflag = FALSE; /* Parse the args */ if(! PyArg_ParseTupleAndKeywords(args, keywds,"|ddd", kwlist, &(data.dx), &(data.dy), &(data.dz)) ) { return NULL; } /* Make the call */ gts_surface_foreach_vertex(PYGTS_SURFACE_AS_GTS_SURFACE(self), (GtsFunc)translate_point,&data); if(data.errflag) return NULL; Py_INCREF(Py_None); return Py_None; } static PyObject* is_self_intersecting(PygtsSurface *self, PyObject *args) { GtsSurface *s; gboolean ret = FALSE; SELF_CHECK if( (s=gts_surface_is_self_intersecting(PYGTS_SURFACE_AS_GTS_SURFACE(self))) != NULL) { gts_object_destroy(GTS_OBJECT(s)); ret = TRUE; } if(ret) { Py_INCREF(Py_True); return Py_True; } else { Py_INCREF(Py_False); return Py_False; } } static PyObject* cleanup(PygtsSurface *self, PyObject *args) { GtsSurface *s; gdouble threshold = 0.; SELF_CHECK /* Parse the args */ if(! PyArg_ParseTuple(args,"|d", &threshold) ) { return NULL; } s = PYGTS_SURFACE_AS_GTS_SURFACE(self); /* Do the cleanup */ if( threshold != 0. ) { pygts_vertex_cleanup(s,threshold); } pygts_edge_cleanup(s); pygts_face_cleanup(s); Py_INCREF(Py_None); return Py_None; } static PyObject* coarsen(PygtsSurface *self, PyObject *args) { guint n; gdouble amin=0.; GtsVolumeOptimizedParams params = {0.5,0.5,1.e-10}; SELF_CHECK /* Parse the args */ if(! PyArg_ParseTuple(args,"i|d", &n, &amin) ) { return NULL; } /* Make the call */ gts_surface_coarsen(PYGTS_SURFACE_AS_GTS_SURFACE(self), (GtsKeyFunc)gts_volume_optimized_cost, ¶ms, (GtsCoarsenFunc)gts_volume_optimized_vertex, ¶ms, (GtsStopFunc)gts_coarsen_stop_number, &n, amin); Py_INCREF(Py_None); return Py_None; } /* Methods table */ static PyMethodDef methods[] = { {"is_ok", (PyCFunction)is_ok, METH_NOARGS, "True if this Surface s is OK. False otherwise.\n" "\n" "Signature: s.is_ok()\n" }, {"add", (PyCFunction)add, METH_VARARGS, "Adds a Face f or Surface s2 to Surface s1.\n" "\n" "Signature: s1.add(f) or s2.add(f)\n" }, {"remove", (PyCFunction)pygts_remove, METH_VARARGS, "Removes Face f from this Surface s.\n" "\n" "Signature: s.remove(f)\n" }, {"copy", (PyCFunction)copy, METH_VARARGS, "Copys all Faces, Edges and Vertices of Surface s2 to Surface s1.\n" "\n" "Signature: s1.copy(s2)\n" "\n" "Returns s1.\n" }, {"is_manifold", (PyCFunction)is_manifold, METH_NOARGS, "True if Surface s is a manifold, False otherwise.\n" "\n" "Signature: s.is_manifold()\n" }, {"manifold_faces", (PyCFunction)manifold_faces, METH_VARARGS, "Returns the 2 manifold Faces of Edge e on this Surface s\n" "if they exist, or None.\n" "\n" "Signature: s.manifold_faces(e)\n" }, {"is_orientable", (PyCFunction)is_orientable, METH_NOARGS, "True if Faces in Surface s have compatible orientation,\n" "False otherwise.\n" "Note that a closed surface is also a manifold. Note that an\n" "orientable surface is also a manifold.\n" "\n" "Signature: s.is_orientable()\n" }, {"is_closed", (PyCFunction)is_closed, METH_NOARGS, "True if Surface s is closed, False otherwise.\n" "Note that a closed Surface is also a manifold.\n" "\n" "Signature: s.is_closed()\n" }, {"boundary", (PyCFunction)boundary, METH_NOARGS, "Returns a tuple of boundary Edges of Surface s.\n" "\n" "Signature: s.boundary()\n" }, {"area", (PyCFunction)area, METH_NOARGS, "Returns the area of Surface s.\n" "The area is taken as the sum of the signed areas of the Faces of s.\n" "\n" "Signature: s.area()\n" }, {"volume", (PyCFunction)volume, METH_NOARGS, "Returns the signed volume of the domain bounded by the Surface s.\n" "\n" "Signature: s.volume()\n" }, {"center_of_mass", (PyCFunction)center_of_mass, METH_NOARGS, "Returns the coordinates of the center of mass of Surface s.\n" "\n" "Signature: s.center_of_mass()\n" }, {"center_of_area", (PyCFunction)center_of_area, METH_NOARGS, "Returns the coordinates of the center of area of Surface s.\n" "\n" "Signature: s.center_of_area()\n" }, {"write", (PyCFunction)pygts_write, METH_VARARGS, "Saves Surface s to File f in GTS ascii format.\n" "All the lines beginning with #! are ignored.\n" "\n" "Signature: s.write(f)\n" }, {"write_oogl", (PyCFunction)pygts_write_oogl, METH_VARARGS, "Saves Surface s to File f in OOGL (Geomview) format.\n" "\n" "Signature: s.write_oogl(f)\n" }, {"write_oogl_boundary", (PyCFunction)pygts_write_oogl_boundary, METH_VARARGS, "Saves boundary of Surface s to File f in OOGL (Geomview) format.\n" "\n" "Signature: s.write_oogl_boundary(f)\n" }, {"write_vtk", (PyCFunction)pygts_write_vtk, METH_VARARGS, "Saves Surface s to File f in VTK format.\n" "\n" "Signature: s.write_vtk(f)\n" }, {"fan_oriented", (PyCFunction)fan_oriented, METH_VARARGS, "Returns a tuple of outside Edges of the Faces fanning from\n" "Vertex v on this Surface s. The Edges are given in \n" "counter-clockwise order.\n" "\n" "Signature: s.fan_oriented(v)\n" }, {"split", (PyCFunction)split, METH_NOARGS, "Splits a surface into a tuple of connected and manifold components.\n" "\n" "Signature: s.split()\n" }, {"distance", (PyCFunction)distance, METH_VARARGS, "Calculates the distance between the faces of this Surface s1 and\n" "the nearest Faces of other s2, and (if applicable) the distance\n" "between the boundary of this Surface s1 and the nearest boundary\n" "Edges of other s2.\n" "\n" "One or two dictionaries are returned (where applicable), the first\n" "for the face range and the second for the boundary range. The\n" "fields in each dictionary describe statistical results for each\n" "population: {min,max,sum,sum2,mean,stddev,n}.\n" "\n" "Signature: s1.distance(s2) or s1.distance(s2,delta)\n" "\n" "The value delta is a spatial increment defined as the percentage\n" "of the diagonal of the bounding box of s2 (default 0.1).\n" }, {"strip", (PyCFunction)strip, METH_NOARGS, "Returns a tuple of strips, where each strip is a tuple of Faces\n" "that are successive and have one edge in common.\n" "\n" "Signature: s.split()\n" }, {"stats", (PyCFunction)stats, METH_NOARGS, "Returns statistics for this Surface f in a dict.\n" "The stats include n_faces, n_incompatible_faces,, n_boundary_edges,\n" "n_non_manifold_edges, and the statisics {min, max, sum, sum2, mean,\n" "stddev, and n} for populations of edges_per_vertex and\n" "faces_per_edge. Each of these names are dictionary keys.\n" "\n" "Signature: s.stats()\n" }, {"quality_stats", (PyCFunction)quality_stats, METH_NOARGS, "Returns quality statistics for this Surface f in a dict.\n" "The statistics include the {min, max, sum, sum2, mean, stddev,\n" "and n} for populations of face_quality, face_area, edge_length,\n" "and edge_angle. Each of these names are dictionary keys.\n" "See Triangle.quality() for an explanation of the face_quality.\n" "\n" "Signature: s.quality_stats()\n" }, {"tessellate", (PyCFunction)tessellate, METH_NOARGS, "Tessellate each face of this Surface s with 4 triangles.\n" "The number of triangles is increased by a factor of 4.\n" "\n" "Signature: s.tessellate()\n" }, {"vertices", (PyCFunction)vertices, METH_NOARGS, "Returns a tuple containing the vertices of Surface s.\n" "\n" "Signature: s.vertices()\n" }, {"parent", (PyCFunction)parent, METH_VARARGS, "Returns Face on this Surface s that has Edge e, or None\n" "if the Edge is not on this Surface.\n" "\n" "Signature: s.parent(e)\n" }, {"edges", (PyCFunction)edges, METH_VARARGS, "Returns tuple of Edges on Surface s that have Vertex in list.\n" "If a list is not given then all of the Edges are returned.\n" "\n" "Signature: s.edges(list) or s.edges()\n" }, {"faces", (PyCFunction)faces, METH_VARARGS, "Returns tuple of Faces on Surface s that have Edge in list.\n" "If a list is not given then all of the Faces are returned.\n" "\n" "Signature: s.faces(list) s.faces()\n" }, {"face_indices", (PyCFunction)face_indices, METH_VARARGS, "Returns a tuple of 3-tuples containing Vertex indices for each Face\n" "in Surface s. The index for each Vertex in a face corresponds to\n" "where it is found in the Vertex tuple vs.\n" "\n" "Signature: s.face_indices(vs)\n" }, {"intersection", (PyCFunction)intersection, METH_VARARGS, "Returns the intersection of this Surface s1 with Surface s2.\n" "\n" "Signature: s1.intersection(s2)\n" }, {"union", (PyCFunction)pygts_union, METH_VARARGS, "Returns the union of this Surface s1 with Surface s2.\n" "\n" "Signature: s1.union(s2)\n" }, {"difference", (PyCFunction)difference, METH_VARARGS, "Returns the difference of this Surface s1 with Surface s2.\n" "\n" "Signature: s1.difference(s2)\n" }, {"rotate", (PyCFunction)rotate, METH_VARARGS | METH_KEYWORDS, "Rotates Surface s about vector dx,dy,dz and angle a.\n" "The sense of the rotation is given by the right-hand-rule.\n" "\n" "Signature: s.rotate(dx,dy,dz,a)\n" }, {"scale", (PyCFunction)scale, METH_VARARGS | METH_KEYWORDS, "Scales Surface s by vector dx,dy,dz.\n" "\n" "Signature: s.scale(dx=1,dy=1,dz=1)\n" }, {"translate", (PyCFunction)translate, METH_VARARGS | METH_KEYWORDS, "Translates Surface s by vector dx,dy,dz.\n" "\n" "Signature: s.translate(dx=0,dy=0,dz=0)\n" }, {"is_self_intersecting", (PyCFunction)is_self_intersecting, METH_NOARGS, "Returns True if this Surface s is self-intersecting.\n" "False otherwise.\n" "\n" "Signature: s.is_self_intersecting()\n" }, {"cleanup", (PyCFunction)cleanup, METH_VARARGS, "Cleans up the Vertices, Edges, and Faces on a Surface s.\n" "\n" "Signature: s.cleanup() or s.cleanup(threhold)\n" "\n" "If threhold is given, then Vertices that are spaced less than\n" "the threshold are merged. Degenerate Edges and Faces are also\n" "removed.\n" }, {"coarsen", (PyCFunction)coarsen, METH_VARARGS, "Reduces the number of vertices on Surface s.\n" "\n" "Signature: s.coarsen(n) and s.coarsen(amin)\n" "\n" "n is the smallest number of desired edges (but you may get fewer).\n" "amin is the smallest angle between Faces.\n" }, {NULL} /* Sentinel */ }; /*-------------------------------------------------------------------------*/ /* Attributes exported to python */ static PyObject * get_Nvertices(PygtsSurface *self, void *closure) { SELF_CHECK return Py_BuildValue("i", gts_surface_vertex_number(PYGTS_SURFACE_AS_GTS_SURFACE(self))); } static PyObject * get_Nedges(PygtsSurface *self, void *closure) { SELF_CHECK return Py_BuildValue("i", gts_surface_edge_number(PYGTS_SURFACE_AS_GTS_SURFACE(self))); } static PyObject * get_Nfaces(PygtsSurface *self, void *closure) { SELF_CHECK return Py_BuildValue("i", gts_surface_face_number(PYGTS_SURFACE_AS_GTS_SURFACE(self))); } /* Methods table */ static PyGetSetDef getset[] = { { "Nvertices", (getter)get_Nvertices, NULL, "The number of unique vertices", NULL }, { "Nedges", (getter)get_Nedges, NULL, "The number of unique edges", NULL }, { "Nfaces", (getter)get_Nfaces, NULL, "The number of unique faces", NULL }, {NULL} /* Sentinel */ }; /*-------------------------------------------------------------------------*/ /* Python type methods */ static void dealloc(PygtsSurface* self) { if(self->traverse!=NULL) { gts_surface_traverse_destroy(self->traverse); } self->traverse = NULL; /* Chain up */ PygtsObjectType.tp_dealloc((PyObject*)self); } static PyObject * new(PyTypeObject *type, PyObject *args, PyObject *kwds) { PyObject *o; PygtsObject *obj; guint alloc_gtsobj = TRUE; /* Parse the args */ if(kwds) { o = PyDict_GetItemString(kwds,"alloc_gtsobj"); if(o==Py_False) { alloc_gtsobj = FALSE; } if(o!=NULL) { PyDict_DelItemString(kwds, "alloc_gtsobj"); } } if(kwds) { Py_INCREF(Py_False); PyDict_SetItemString(kwds,"alloc_gtsobj", Py_False); } /* Chain up */ obj = PYGTS_OBJECT(PygtsObjectType.tp_new(type,args,kwds)); PYGTS_SURFACE(obj)->traverse = NULL; /* Allocate the gtsobj (if needed) */ if( alloc_gtsobj ) { obj->gtsobj = GTS_OBJECT(gts_surface_new(gts_surface_class(), gts_face_class(), gts_edge_class(), gts_vertex_class())); if( obj->gtsobj == NULL ) { PyErr_SetString(PyExc_MemoryError, "could not create Surface"); return NULL; } pygts_object_register(obj); } return (PyObject*)obj; } static int init(PygtsSurface *self, PyObject *args, PyObject *kwds) { gint ret; if( (ret = PygtsObjectType.tp_init((PyObject*)self,args,kwds)) != 0 ) { return ret; } return 0; } /* Helper function for iter */ static void get_f0(GtsFace *f,GtsFace **f0) { if(*f0==NULL) *f0 = f; } PyObject* iter(PygtsSurface *self) { GtsFace* f0=NULL; SELF_CHECK if(self->traverse!=NULL) { gts_surface_traverse_destroy(self->traverse); self->traverse = NULL; } /* Assign a "first" face */ gts_surface_foreach_face(PYGTS_SURFACE_AS_GTS_SURFACE(self), (GtsFunc)get_f0,&f0); if(f0==NULL) { PyErr_SetString(PyExc_RuntimeError, "No faces to traverse"); return NULL; } if( (self->traverse=gts_surface_traverse_new( PYGTS_SURFACE_AS_GTS_SURFACE(self),f0)) == NULL ) { PyErr_SetString(PyExc_MemoryError, "could not create Traverse"); return NULL; } Py_INCREF((PyObject*)self); return (PyObject*)self; } PyObject* iternext(PygtsSurface *self) { PygtsFace *face; GtsFace *f; SELF_CHECK if( self->traverse == NULL ) { PyErr_SetString(PyExc_RuntimeError, "iterator not initialized"); return NULL; } /* Get the next face */ if( (f = gts_surface_traverse_next(self->traverse,NULL)) == NULL ) { gts_surface_traverse_destroy(self->traverse); self->traverse = NULL; PyErr_SetString(PyExc_StopIteration, "No more faces"); return NULL; } if( (face = pygts_face_new(f)) == NULL ) { return NULL; } return (PyObject*)face; } /* Methods table */ PyTypeObject PygtsSurfaceType = { PyObject_HEAD_INIT(NULL) 0, /* ob_size */ "gts.Surface", /* tp_name */ sizeof(PygtsSurface), /* tp_basicsize */ 0, /* tp_itemsize */ (destructor)dealloc, /* tp_dealloc */ 0, /* tp_print */ 0, /* tp_getattr */ 0, /* tp_setattr */ 0, /* tp_compare */ 0, /* tp_repr */ 0, /* tp_as_number */ 0, /* tp_as_sequence */ 0, /* tp_as_mapping */ 0, /* tp_hash */ 0, /* tp_call */ 0, /* tp_str */ 0, /* tp_getattro */ 0, /* tp_setattro */ 0, /* tp_as_buffer */ Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE | Py_TPFLAGS_HAVE_ITER, /* tp_flags */ "Surface object", /* tp_doc */ 0, /* tp_traverse */ 0, /* tp_clear */ 0, /* tp_richcompare */ 0, /* tp_weaklistoffset */ (getiterfunc)iter, /* tp_iter */ (iternextfunc)iternext, /* tp_iternext */ methods, /* tp_methods */ 0, /* tp_members */ getset, /* tp_getset */ 0, /* tp_base */ 0, /* tp_dict */ 0, /* tp_descr_get */ 0, /* tp_descr_set */ 0, /* tp_dictoffset */ (initproc)init, /* tp_init */ 0, /* tp_alloc */ (newfunc)new /* tp_new */ }; /*-------------------------------------------------------------------------*/ /* Pygts functions */ gboolean pygts_surface_check(PyObject* o) { if(! PyObject_TypeCheck(o, &PygtsSurfaceType)) { return FALSE; } else { #if PYGTS_DEBUG return pygts_surface_is_ok(PYGTS_SURFACE(o)); #else return TRUE; #endif } } /* Helper function */ static void face_is_ok(GtsFace *f,gboolean *ret) { if( !pygts_gts_triangle_is_ok(GTS_TRIANGLE(f)) ) { *ret = FALSE; } } gboolean pygts_surface_is_ok(PygtsSurface *s) { PygtsObject *obj; gboolean ret=TRUE; obj = PYGTS_OBJECT(s); if(!pygts_object_is_ok(PYGTS_OBJECT(s))) return FALSE; g_return_val_if_fail(obj->gtsobj_parent==NULL,FALSE); /* Check all of the faces this surface contains */ gts_surface_foreach_face(GTS_SURFACE(obj->gtsobj),(GtsFunc)face_is_ok,&ret); if( ret == FALSE ) return FALSE; return TRUE; } PygtsSurface * pygts_surface_new(GtsSurface *s) { PyObject *args, *kwds; PygtsObject *surface; /* Check for Surface in the object table */ if( (surface = PYGTS_OBJECT(g_hash_table_lookup(obj_table,GTS_OBJECT(s)))) !=NULL ) { Py_INCREF(surface); return PYGTS_SURFACE(surface); } /* Build a new Surface */ args = Py_BuildValue("()"); kwds = Py_BuildValue("{s:O}","alloc_gtsobj",Py_False); surface = PYGTS_OBJECT(PygtsSurfaceType.tp_new(&PygtsSurfaceType,args,kwds)); Py_DECREF(args); Py_DECREF(kwds); if( surface == NULL ) { PyErr_SetString(PyExc_MemoryError, "could not create Surface"); return NULL; } surface->gtsobj = GTS_OBJECT(s); /* Register and return */ pygts_object_register(surface); return PYGTS_SURFACE(surface); } trunk-2018.02b/py/3rd-party/pygts-0.3.1/surface.h000066400000000000000000000030751324306050200210540ustar00rootroot00000000000000/* pygts - python package for the manipulation of triangulated surfaces * * Copyright (C) 2009 Thomas J. Duck * All rights reserved. * * Thomas J. Duck * Department of Physics and Atmospheric Science, * Dalhousie University, Halifax, Nova Scotia, Canada, B3H 3J5 * * NOTICE * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library 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 * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public * License along with this library; if not, write to the * Free Software Foundation, Inc., 59 Temple Place - Suite 330, * Boston, MA 02111-1307, USA. */ #ifndef __PYGTS_SURFACE_H__ #define __PYGTS_SURFACE_H__ typedef struct _PygtsSurface PygtsSurface; #define PYGTS_SURFACE(o) ((PygtsSurface*)o) #define PYGTS_SURFACE_AS_GTS_SURFACE(o) (GTS_SURFACE(PYGTS_OBJECT(o)->gtsobj)) struct _PygtsSurface { PygtsObject o; GtsSurfaceTraverse* traverse; }; extern PyTypeObject PygtsSurfaceType; gboolean pygts_surface_check(PyObject* o); gboolean pygts_surface_is_ok(PygtsSurface *s); PygtsSurface* pygts_surface_new(GtsSurface *s); #endif /* __PYGTS_SURFACE_H__ */ trunk-2018.02b/py/3rd-party/pygts-0.3.1/triangle.c000066400000000000000000000644401324306050200212270ustar00rootroot00000000000000/* pygts - python package for the manipulation of triangulated surfaces * * Copyright (C) 2009 Thomas J. Duck * All rights reserved. * * Thomas J. Duck * Department of Physics and Atmospheric Science, * Dalhousie University, Halifax, Nova Scotia, Canada, B3H 3J5 * * NOTICE * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library 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 * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public * License along with this library; if not, write to the * Free Software Foundation, Inc., 59 Temple Place - Suite 330, * Boston, MA 02111-1307, USA. */ #include "pygts.h" #if PYGTS_DEBUG #define SELF_CHECK if(!pygts_triangle_check((PyObject*)self)) { \ PyErr_SetString(PyExc_RuntimeError, \ "problem with self object (internal error)"); \ return NULL; \ } #else #define SELF_CHECK #endif /*-------------------------------------------------------------------------*/ /* Methods exported to python */ static PyObject* is_ok(PygtsTriangle *self, PyObject *args) { if(pygts_triangle_is_ok(self)) { Py_INCREF(Py_True); return Py_True; } else { Py_INCREF(Py_False); return Py_False; } } static PyObject* area(PygtsTriangle *self, PyObject *args) { SELF_CHECK return Py_BuildValue("d", gts_triangle_area(PYGTS_TRIANGLE_AS_GTS_TRIANGLE(self))); } static PyObject* perimeter(PygtsTriangle *self, PyObject *args) { SELF_CHECK return Py_BuildValue("d", gts_triangle_perimeter(PYGTS_TRIANGLE_AS_GTS_TRIANGLE(self))); } static PyObject* quality(PygtsTriangle *self, PyObject *args) { SELF_CHECK return Py_BuildValue("d", gts_triangle_quality(PYGTS_TRIANGLE_AS_GTS_TRIANGLE(self))); } static PyObject* normal(PygtsTriangle *self, PyObject *args) { gdouble x,y,z; SELF_CHECK gts_triangle_normal(PYGTS_TRIANGLE_AS_GTS_TRIANGLE(self),&x,&y,&z); return Py_BuildValue("ddd",x,y,z); } static PyObject* revert(PygtsTriangle *self, PyObject *args) { SELF_CHECK gts_triangle_revert(PYGTS_TRIANGLE_AS_GTS_TRIANGLE(self)); Py_INCREF(Py_None); return Py_None; } static PyObject* orientation(PygtsTriangle *self, PyObject *args) { SELF_CHECK return Py_BuildValue("d", gts_triangle_orientation(PYGTS_TRIANGLE_AS_GTS_TRIANGLE(self))); } static PyObject* angle(PygtsTriangle* self, PyObject *args) { PyObject *t_; PygtsTriangle *t; SELF_CHECK /* Parse the args */ if(! PyArg_ParseTuple(args, "O", &t_) ) { return NULL; } /* Convert to PygtsObjects */ if(!pygts_triangle_check(t_)) { PyErr_SetString(PyExc_TypeError,"expected a Triangle"); return NULL; } t = PYGTS_TRIANGLE(t_); return Py_BuildValue("d", gts_triangles_angle(PYGTS_TRIANGLE_AS_GTS_TRIANGLE(self), PYGTS_TRIANGLE_AS_GTS_TRIANGLE(t))); } static PyObject* is_compatible(PygtsTriangle *self, PyObject *args) { PyObject *t2_; PygtsTriangle *t2; GtsEdge *e; SELF_CHECK /* Parse the args */ if(! PyArg_ParseTuple(args, "O", &t2_) ) { return NULL; } /* Convert to PygtsObjects */ if(!pygts_triangle_check(t2_)) { PyErr_SetString(PyExc_TypeError,"expected a Triangle"); return NULL; } t2 = PYGTS_TRIANGLE(t2_); /* Get the common edge */ if( (e = gts_triangles_common_edge(PYGTS_TRIANGLE_AS_GTS_TRIANGLE(self), PYGTS_TRIANGLE_AS_GTS_TRIANGLE(t2))) == NULL ) { PyErr_SetString(PyExc_RuntimeError,"Triangles do not share common edge"); return NULL; } if( gts_triangles_are_compatible(PYGTS_TRIANGLE_AS_GTS_TRIANGLE(self), PYGTS_TRIANGLE_AS_GTS_TRIANGLE(t2),e) ) { Py_INCREF(Py_True); return Py_True; } else { Py_INCREF(Py_False); return Py_False; } } static PyObject* common_edge(PygtsTriangle *self, PyObject *args) { PyObject *t2_; PygtsTriangle *t2; GtsEdge *e; PygtsEdge *edge; SELF_CHECK /* Parse the args */ if(! PyArg_ParseTuple(args, "O", &t2_) ) { return NULL; } /* Convert to PygtsObjects */ if(!pygts_triangle_check(t2_)) { PyErr_SetString(PyExc_TypeError,"expected a Triangle"); return NULL; } t2 = PYGTS_TRIANGLE(t2_); /* Get the common edge */ if( (e = gts_triangles_common_edge(PYGTS_TRIANGLE_AS_GTS_TRIANGLE(self), PYGTS_TRIANGLE_AS_GTS_TRIANGLE(t2))) == NULL ) { Py_INCREF(Py_None); return Py_None; } if( (edge = pygts_edge_new(GTS_EDGE(e))) == NULL ) { return NULL; } return (PyObject*)edge; } static PyObject* opposite(PygtsTriangle *self, PyObject *args) { PyObject *o_; PygtsEdge *e=NULL; PygtsVertex *v=NULL; GtsVertex *vertex=NULL,*v1,*v2,*v3; GtsEdge *edge=NULL; GtsTriangle *triangle; SELF_CHECK /* Parse the args */ if(! PyArg_ParseTuple(args, "O", &o_) ) { return NULL; } /* Convert to PygtsObjects */ if(pygts_edge_check(o_)) { e = PYGTS_TRIANGLE(o_); } else { if(pygts_vertex_check(o_)) { v = PYGTS_TRIANGLE(o_); } else { PyErr_SetString(PyExc_TypeError,"expected an Edge or a Vertex"); return NULL; } } /* Error check */ triangle = PYGTS_TRIANGLE_AS_GTS_TRIANGLE(self); if( e!=NULL ) { edge = PYGTS_EDGE_AS_GTS_EDGE(e); if(! ((triangle->e1==edge)||(triangle->e2==edge)||(triangle->e3==edge)) ) { PyErr_SetString(PyExc_RuntimeError,"Edge not in Triangle"); return NULL; } } else { vertex = PYGTS_VERTEX_AS_GTS_VERTEX(v); gts_triangle_vertices(triangle,&v1,&v2,&v3); if(! ((vertex==v1)||(vertex==v2)||(vertex==v3)) ) { PyErr_SetString(PyExc_RuntimeError,"Vertex not in Triangle"); return NULL; } } /* Get the opposite and return */ if( e!=NULL) { vertex = gts_triangle_vertex_opposite(triangle, edge); if( (v = pygts_vertex_new(vertex)) == NULL ) { return NULL; } return (PyObject*)v; } else{ edge = gts_triangle_edge_opposite(triangle, vertex); if( (e = pygts_edge_new(edge)) == NULL ) { return NULL; } return (PyObject*)e; } } static PyObject * vertices(PygtsTriangle *self,PyObject *args) { GtsVertex *v1_,*v2_,*v3_; PygtsObject *v1,*v2,*v3; SELF_CHECK /* Get the vertices */ gts_triangle_vertices(PYGTS_TRIANGLE_AS_GTS_TRIANGLE(self), &v1_, &v2_, &v3_); if( (v1 = pygts_vertex_new(v1_)) == NULL ) { return NULL; } if( (v2 = pygts_vertex_new(v2_)) == NULL ) { Py_DECREF(v1); return NULL; } if( (v3 = pygts_vertex_new(v3_)) == NULL ) { Py_DECREF(v1); Py_DECREF(v2); return NULL; } return Py_BuildValue("OOO",v1,v2,v3); } static PyObject * vertex(PygtsTriangle *self,PyObject *args) { GtsVertex *v1_; PygtsObject *v1; SELF_CHECK /* Get the vertices */ v1_ = gts_triangle_vertex(PYGTS_TRIANGLE_AS_GTS_TRIANGLE(self)); if( (v1 = pygts_vertex_new(v1_)) == NULL ) { return NULL; } return (PyObject*)v1; } static PyObject * circumcenter(PygtsTriangle *self,PyObject *args) { PygtsVertex *v; GtsVertex *vertex; SELF_CHECK /* Get the Vertex */ vertex = GTS_VERTEX( gts_triangle_circumcircle_center( PYGTS_TRIANGLE_AS_GTS_TRIANGLE(self), GTS_POINT_CLASS(gts_vertex_class()))); if( vertex == NULL ) { Py_INCREF(Py_None); return Py_None; } if( (v = pygts_vertex_new(vertex)) == NULL ) { return NULL; } return (PyObject*)v; } static PyObject * is_stabbed(PygtsTriangle *self,PyObject *args) { PyObject *p_; PygtsVertex *p; GtsObject *obj; PygtsVertex *vertex; PygtsEdge *edge; SELF_CHECK /* Parse the args */ if(! PyArg_ParseTuple(args, "O", &p_) ) { return NULL; } /* Convert to PygtsObjects */ if(!pygts_point_check(p_)) { PyErr_SetString(PyExc_TypeError,"expected a Point"); return NULL; } p = PYGTS_POINT(p_); obj = gts_triangle_is_stabbed(PYGTS_TRIANGLE_AS_GTS_TRIANGLE(self), GTS_POINT(PYGTS_OBJECT(p)->gtsobj), NULL); if( obj == NULL ) { Py_INCREF(Py_None); return Py_None; } if(GTS_IS_VERTEX(obj)) { if( (vertex = pygts_vertex_new(GTS_VERTEX(obj))) == NULL ) { return NULL; } return (PyObject*)vertex; } if(GTS_IS_EDGE(obj)) { if( (edge = pygts_edge_new(GTS_EDGE(obj))) == NULL ) { return NULL; } return (PyObject*)edge; } Py_INCREF(self); return (PyObject*)self; } static PyObject * interpolate_height(PygtsTriangle *self,PyObject *args) { PyObject *p_; PygtsPoint *p; GtsPoint point; SELF_CHECK /* Parse the args */ if(! PyArg_ParseTuple(args, "O", &p_) ) { return NULL; } /* Convert to PygtsObjects */ if(!pygts_point_check(p_)) { PyErr_SetString(PyExc_TypeError,"expected a Point"); return NULL; } p = PYGTS_POINT(p_); point.x = PYGTS_POINT_AS_GTS_POINT(p)->x; point.y = PYGTS_POINT_AS_GTS_POINT(p)->y; gts_triangle_interpolate_height(PYGTS_TRIANGLE_AS_GTS_TRIANGLE(self), &point); return Py_BuildValue("d",point.z); } /* Methods table */ static PyMethodDef methods[] = { {"is_ok", (PyCFunction)is_ok, METH_NOARGS, "True if this Triangle t is non-degenerate and non-duplicate.\n" "False otherwise.\n" "\n" "Signature: t.is_ok()\n" }, {"area", (PyCFunction)area, METH_NOARGS, "Returns the area of Triangle t.\n" "\n" "Signature: t.area()\n" }, {"perimeter", (PyCFunction)perimeter, METH_NOARGS, "Returns the perimeter of Triangle t.\n" "\n" "Signature: t.perimeter()\n" }, {"quality", (PyCFunction)quality, METH_NOARGS, "Returns the quality of Triangle t.\n" "\n" "The quality of a triangle is defined as the ratio of the square\n" "root of its surface area to its perimeter relative to this same\n" "ratio for an equilateral triangle with the same area. The quality\n" "is then one for an equilateral triangle and tends to zero for a\n" "very stretched triangle." "\n" "Signature: t.quality()\n" }, {"normal", (PyCFunction)normal, METH_NOARGS, "Returns a tuple of coordinates of the oriented normal of Triangle t\n" "as the cross-product of two edges, using the left-hand rule. The\n" "normal is not normalized. If this triangle is part of a closed and\n" "oriented surface, the normal points to the outside of the surface.\n" "\n" "Signature: t.normal()\n" }, {"revert", (PyCFunction)revert, METH_NOARGS, "Changes the orientation of triangle t, turning it inside out.\n" "\n" "Signature: t.revert()\n" }, {"orientation", (PyCFunction)orientation, METH_NOARGS, "Determines orientation of the plane (x,y) projection of Triangle t\n" "\n" "Signature: t.orientation()\n" "\n" "Returns a positive value if Points p1, p2 and p3 in Triangle t\n" "appear in counterclockwise order, a negative value if they appear\n" "in clockwise order and zero if they are colinear.\n" }, {"angle", (PyCFunction)angle, METH_VARARGS, "Returns the angle (radians) between Triangles t1 and t2\n" "\n" "Signature: t1.angle(t2)\n" }, {"is_compatible", (PyCFunction)is_compatible, METH_VARARGS, "True if this triangle t1 and other t2 are compatible;\n" "otherwise False.\n" "\n" "Checks if this triangle t1 and other t2, which share a common\n" "Edge, can be part of the same surface without conflict in the\n" "surface normal orientation.\n" "\n" "Signature: t1.is_compatible(t2)\n" }, {"common_edge", (PyCFunction)common_edge, METH_VARARGS, "Returns Edge common to both this Triangle t1 and other t2.\n" "Returns None if the triangles do not share an Edge.\n" "\n" "Signature: t1.common_edge(t2)\n" }, {"opposite", (PyCFunction)opposite, METH_VARARGS, "Returns Vertex opposite to Edge e or Edge opposite to Vertex v\n" "for this Triangle t.\n" "\n" "Signature: t.opposite(e) or t.opposite(v)\n" }, {"vertices", (PyCFunction)vertices, METH_NOARGS, "Returns the three oriented set of vertices in Triangle t.\n" "\n" "Signature: t.vertices()\n" }, {"vertex", (PyCFunction)vertex, METH_NOARGS, "Returns the Vertex of this Triangle t not in t.e1.\n" "\n" "Signature: t.vertex()\n" }, {"circumcenter", (PyCFunction)circumcenter, METH_NOARGS, "Returns a Vertex at the center of the circumscribing circle of\n" "this Triangle t, or None if the circumscribing circle is not\n" "defined.\n" "\n" "Signature: t.circumcircle_center()\n" }, {"is_stabbed", (PyCFunction)is_stabbed, METH_VARARGS, "Returns the component of this Triangle t that is stabbed by a\n" "ray projecting from Point p to z=infinity. The result\n" "can be this Triangle t, one of its Edges or Vertices, or None.\n" "If the ray is contained in the plan of this Triangle then None is\n" "also returned.\n" "\n" "Signature: t.is_stabbed(p)\n" }, {"interpolate_height", (PyCFunction)interpolate_height, METH_VARARGS, "Returns the height of the plane defined by Triangle t at Point p.\n" "Only the x- and y-coordinates of p are considered.\n" "\n" "Signature: t.interpolate_height(p)\n" }, {NULL} /* Sentinel */ }; /*-------------------------------------------------------------------------*/ /* Attributes exported to python */ static PyObject * get_e1(PygtsTriangle *self, void *closure) { PygtsEdge *e1; SELF_CHECK if( (e1=pygts_edge_new(PYGTS_TRIANGLE_AS_GTS_TRIANGLE(self)->e1)) == NULL ) { return NULL; } return (PyObject *)e1; } static PyObject * get_e2(PygtsTriangle *self, void *closure) { PygtsEdge *e2; SELF_CHECK if( (e2=pygts_edge_new(PYGTS_TRIANGLE_AS_GTS_TRIANGLE(self)->e2)) == NULL ) { return NULL; } return (PyObject *)e2; } static PyObject * get_e3(PygtsTriangle *self, void *closure) { PygtsEdge *e3; SELF_CHECK if( (e3=pygts_edge_new(PYGTS_TRIANGLE_AS_GTS_TRIANGLE(self)->e3)) == NULL ) { return NULL; } return (PyObject *)e3; } /* Methods table */ static PyGetSetDef getset[] = { {"e1", (getter)get_e1, NULL, "Edge 1", NULL}, {"e2", (getter)get_e2, NULL, "Edge 2", NULL}, {"e3", (getter)get_e3, NULL, "Edge 3", NULL}, {NULL} /* Sentinel */ }; /*-------------------------------------------------------------------------*/ /* Python type methods */ static PyObject * new(PyTypeObject *type, PyObject *args, PyObject *kwds) { PyObject *o; PygtsObject *obj; guint alloc_gtsobj = TRUE; PyObject *o1_,*o2_,*o3_; GtsVertex *v1=NULL, *v2=NULL, *v3=NULL; GtsEdge *e1=NULL,*e2=NULL,*e3=NULL,*e; GtsSegment *s1,*s2,*s3; gboolean flag=FALSE; /* Flag when the args are gts.Point objects */ GtsTriangle *t,*t_; guint N; /* Parse the args */ if(kwds) { o = PyDict_GetItemString(kwds,"alloc_gtsobj"); if(o==Py_False) { alloc_gtsobj = FALSE; } if(o!=NULL) { PyDict_DelItemString(kwds, "alloc_gtsobj"); } } if(kwds) { Py_INCREF(Py_False); PyDict_SetItemString(kwds,"alloc_gtsobj", Py_False); } /* Allocate the gtsobj (if needed) */ if( alloc_gtsobj ) { /* Parse the args */ if( (N = PyTuple_Size(args)) < 3 ) { PyErr_SetString(PyExc_TypeError,"expected three Edges or three Vertices"); return NULL; } o1_ = PyTuple_GET_ITEM(args,0); o2_ = PyTuple_GET_ITEM(args,1); o3_ = PyTuple_GET_ITEM(args,2); /* Convert to PygtsObjects */ if( pygts_edge_check(o1_) ) { e1 = PYGTS_EDGE_AS_GTS_EDGE(o1_); } else { if( pygts_vertex_check(o1_) ) { v1 = PYGTS_VERTEX_AS_GTS_VERTEX(o1_); flag = TRUE; } } if( pygts_edge_check(o2_) ) { e2 = PYGTS_EDGE_AS_GTS_EDGE(o2_); } else { if( pygts_vertex_check(o2_) ) { v2 = PYGTS_VERTEX_AS_GTS_VERTEX(o2_); flag = TRUE; } } if( pygts_edge_check(o3_) ) { e3 = PYGTS_EDGE_AS_GTS_EDGE(o3_); } else { if(pygts_vertex_check(o3_)) { v3 = PYGTS_VERTEX_AS_GTS_VERTEX(o3_); flag = TRUE; } } /* Check for three edges or three vertices */ if( !((e1!=NULL && e2!=NULL && e3!=NULL) || (v1!=NULL && v2!=NULL && v3!=NULL)) ) { PyErr_SetString(PyExc_TypeError,"expected three Edges or three Vertices"); return NULL; } if( (v1==v2 || v2==v3 || v1==v3) && v1!=NULL ) { PyErr_SetString(PyExc_ValueError,"three Vertices must be different"); return NULL; } /* Get gts edges */ if(flag) { /* Create gts edges */ if( (e1 = gts_edge_new(gts_edge_class(),v1,v2)) == NULL ) { PyErr_SetString(PyExc_MemoryError, "could not create Edge"); return NULL; } if( (e2 = gts_edge_new(gts_edge_class(),v2,v3)) == NULL ) { PyErr_SetString(PyExc_MemoryError, "could not create Edge"); gts_object_destroy(GTS_OBJECT(e1)); return NULL; } if( (e3 = gts_edge_new(gts_edge_class(),v3,v1)) == NULL ) { PyErr_SetString(PyExc_MemoryError, "could not create Edge"); gts_object_destroy(GTS_OBJECT(e1)); gts_object_destroy(GTS_OBJECT(e2)); return NULL; } /* Check for duplicates */ if( (e = gts_edge_is_duplicate(e1)) != NULL ) { gts_object_destroy(GTS_OBJECT(e1)); e1 = e; } if( (e = gts_edge_is_duplicate(e2)) != NULL ) { gts_object_destroy(GTS_OBJECT(e2)); e2 = e; } if( (e = gts_edge_is_duplicate(e3)) != NULL ) { gts_object_destroy(GTS_OBJECT(e3)); e3 = e; } } /* Check that edges connect with common vertices */ s1 = GTS_SEGMENT(e1); s2 = GTS_SEGMENT(e2); s3 = GTS_SEGMENT(e3); if( !((s1->v1==s3->v2 && s1->v2==s2->v1 && s2->v2==s3->v1) || (s1->v1==s3->v2 && s1->v2==s2->v2 && s2->v1==s3->v1) || (s1->v1==s3->v1 && s1->v2==s2->v1 && s2->v2==s3->v2) || (s1->v2==s3->v2 && s1->v1==s2->v1 && s2->v2==s3->v1) || (s1->v1==s3->v1 && s1->v2==s2->v2 && s2->v1==s3->v2) || (s1->v2==s3->v2 && s1->v1==s2->v2 && s2->v1==s3->v1) || (s1->v2==s3->v1 && s1->v1==s2->v1 && s2->v2==s3->v2) || (s1->v2==s3->v1 && s1->v1==s2->v2 && s2->v1==s3->v2)) ) { PyErr_SetString(PyExc_RuntimeError, "Edges in triangle must connect"); if(!g_hash_table_lookup(obj_table,GTS_OBJECT(e1))) { gts_object_destroy(GTS_OBJECT(e1)); } if(!g_hash_table_lookup(obj_table,GTS_OBJECT(e1))) { gts_object_destroy(GTS_OBJECT(e2)); } if(!g_hash_table_lookup(obj_table,GTS_OBJECT(e1))) { gts_object_destroy(GTS_OBJECT(e3)); } return NULL; } /* Create the GtsTriangle */ if( (t = gts_triangle_new(gts_triangle_class(),e1,e2,e3)) == NULL ) { PyErr_SetString(PyExc_MemoryError, "could not create Face"); if(!g_hash_table_lookup(obj_table,GTS_OBJECT(e1))) { gts_object_destroy(GTS_OBJECT(e1)); } if(!g_hash_table_lookup(obj_table,GTS_OBJECT(e1))) { gts_object_destroy(GTS_OBJECT(e2)); } if(!g_hash_table_lookup(obj_table,GTS_OBJECT(e1))) { gts_object_destroy(GTS_OBJECT(e3)); } return NULL; } /* Check for duplicate */ t_ = gts_triangle_is_duplicate(GTS_TRIANGLE(t)); if( t_ != NULL ) { gts_object_destroy(GTS_OBJECT(t)); t = t_; } /* If corresponding PyObject found in object table, we are done */ if( (obj=g_hash_table_lookup(obj_table,GTS_OBJECT(t))) != NULL ) { Py_INCREF(obj); return (PyObject*)obj; } } /* Chain up */ obj = PYGTS_OBJECT(PygtsObjectType.tp_new(type,args,kwds)); if( alloc_gtsobj ) { obj->gtsobj = GTS_OBJECT(t); pygts_object_register(PYGTS_OBJECT(obj)); } return (PyObject*)obj; } static int init(PygtsTriangle *self, PyObject *args, PyObject *kwds) { gint ret; /* Chain up */ if( (ret=PygtsObjectType.tp_init((PyObject*)self,args,kwds)) != 0 ){ return ret; } #if PYGTS_DEBUG if(!pygts_triangle_check((PyObject*)self)) { PyErr_SetString(PyExc_RuntimeError, "problem with self object (internal error)"); return -1; } #endif return 0; } static int compare(PyObject *o1, PyObject *o2) { GtsTriangle *t1, *t2; if( !(pygts_triangle_check(o1) && pygts_triangle_check(o2)) ) { return -1; } t1 = PYGTS_TRIANGLE_AS_GTS_TRIANGLE(o1); t2 = PYGTS_TRIANGLE_AS_GTS_TRIANGLE(o2); return pygts_triangle_compare(t1,t2); } /* Methods table */ PyTypeObject PygtsTriangleType = { PyObject_HEAD_INIT(NULL) 0, /* ob_size */ "gts.Triangle", /* tp_name */ sizeof(PygtsTriangle), /* tp_basicsize */ 0, /* tp_itemsize */ 0, /* tp_dealloc */ 0, /* tp_print */ 0, /* tp_getattr */ 0, /* tp_setattr */ (cmpfunc)compare, /* tp_compare */ 0, /* tp_repr */ 0, /* tp_as_number */ 0, /* tp_as_sequence */ 0, /* tp_as_mapping */ 0, /* tp_hash */ 0, /* tp_call */ 0, /* tp_str */ 0, /* tp_getattro */ 0, /* tp_setattro */ 0, /* tp_as_buffer */ Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, /* tp_flags */ "Triangle object", /* tp_doc */ 0, /* tp_traverse */ 0, /* tp_clear */ 0, /* tp_richcompare */ 0, /* tp_weaklistoffset */ 0, /* tp_iter */ 0, /* tp_iternext */ methods, /* tp_methods */ 0, /* tp_members */ getset, /* tp_getset */ 0, /* tp_base */ 0, /* tp_dict */ 0, /* tp_descr_get */ 0, /* tp_descr_set */ 0, /* tp_dictoffset */ (initproc)init, /* tp_init */ 0, /* tp_alloc */ (newfunc)new /* tp_new */ }; /*-------------------------------------------------------------------------*/ /* Pygts functions */ gboolean pygts_triangle_check(PyObject* o) { if(! PyObject_TypeCheck(o, &PygtsTriangleType)) { return FALSE; } else { #if PYGTS_DEBUG return pygts_triangle_is_ok(PYGTS_TRIANGLE(o)); #else return TRUE; #endif } } gboolean pygts_triangle_is_ok(PygtsTriangle *t) { if(!pygts_object_is_ok(PYGTS_OBJECT(t))) return FALSE; return pygts_gts_triangle_is_ok(PYGTS_TRIANGLE_AS_GTS_TRIANGLE(t)); } PygtsTriangle * pygts_triangle_new(GtsTriangle *t) { PyObject *args, *kwds; PygtsObject *triangle; /* Check for Triangle in the object table */ if( (triangle = PYGTS_OBJECT(g_hash_table_lookup(obj_table,GTS_OBJECT(t)))) !=NULL ) { Py_INCREF(triangle); return PYGTS_TRIANGLE(triangle); } /* Build a new Triangle */ args = Py_BuildValue("OOO",Py_None,Py_None,Py_None); kwds = Py_BuildValue("{s:O}","alloc_gtsobj",Py_False); triangle = PYGTS_OBJECT(PygtsTriangleType.tp_new(&PygtsTriangleType, args, kwds)); Py_DECREF(args); Py_DECREF(kwds); if( triangle == NULL ) { PyErr_SetString(PyExc_MemoryError,"could not create Triangle"); return NULL; } triangle->gtsobj = GTS_OBJECT(t); /* Register and return */ pygts_object_register(triangle); return PYGTS_TRIANGLE(triangle); } int pygts_triangle_compare(GtsTriangle* t1,GtsTriangle* t2) { if( (pygts_segment_compare(GTS_SEGMENT(t1->e1),GTS_SEGMENT(t2->e1))==0 && pygts_segment_compare(GTS_SEGMENT(t1->e2),GTS_SEGMENT(t2->e2))==0 && pygts_segment_compare(GTS_SEGMENT(t1->e3),GTS_SEGMENT(t2->e3))==0) || (pygts_segment_compare(GTS_SEGMENT(t1->e1),GTS_SEGMENT(t2->e3))==0 && pygts_segment_compare(GTS_SEGMENT(t1->e2),GTS_SEGMENT(t2->e1))==0 && pygts_segment_compare(GTS_SEGMENT(t1->e3),GTS_SEGMENT(t2->e2))==0) || (pygts_segment_compare(GTS_SEGMENT(t1->e1),GTS_SEGMENT(t2->e2))==0 && pygts_segment_compare(GTS_SEGMENT(t1->e2),GTS_SEGMENT(t2->e3))==0 && pygts_segment_compare(GTS_SEGMENT(t1->e3),GTS_SEGMENT(t2->e1))==0) || (pygts_segment_compare(GTS_SEGMENT(t1->e1),GTS_SEGMENT(t2->e3))==0 && pygts_segment_compare(GTS_SEGMENT(t1->e2),GTS_SEGMENT(t2->e2))==0 && pygts_segment_compare(GTS_SEGMENT(t1->e3),GTS_SEGMENT(t2->e1))==0) || (pygts_segment_compare(GTS_SEGMENT(t1->e1),GTS_SEGMENT(t2->e2))==0 && pygts_segment_compare(GTS_SEGMENT(t1->e2),GTS_SEGMENT(t2->e1))==0 && pygts_segment_compare(GTS_SEGMENT(t1->e3),GTS_SEGMENT(t2->e3))==0) || (pygts_segment_compare(GTS_SEGMENT(t1->e1),GTS_SEGMENT(t2->e1))==0 && pygts_segment_compare(GTS_SEGMENT(t1->e2),GTS_SEGMENT(t2->e3))==0 && pygts_segment_compare(GTS_SEGMENT(t1->e3),GTS_SEGMENT(t2->e2))==0) ) { return 0; } return -1; } /** * gts_triangle_is_ok: * @t: a #GtsTriangle. * * Returns: %TRUE if @t is a non-degenerate, non-duplicate triangle, * %FALSE otherwise. */ gboolean pygts_gts_triangle_is_ok (GtsTriangle * t) { g_return_val_if_fail (t != NULL, FALSE); g_return_val_if_fail (t->e1 != NULL, FALSE); g_return_val_if_fail (t->e2 != NULL, FALSE); g_return_val_if_fail (t->e3 != NULL, FALSE); g_return_val_if_fail (t->e1 != t->e2 && t->e1 != t->e3 && t->e2 != t->e3, FALSE); g_return_val_if_fail (gts_segments_touch (GTS_SEGMENT (t->e1), GTS_SEGMENT (t->e2)), FALSE); g_return_val_if_fail (gts_segments_touch (GTS_SEGMENT (t->e1), GTS_SEGMENT (t->e3)), FALSE); g_return_val_if_fail (gts_segments_touch (GTS_SEGMENT (t->e2), GTS_SEGMENT (t->e3)), FALSE); g_return_val_if_fail (GTS_SEGMENT (t->e1)->v1 != GTS_SEGMENT (t->e1)->v2, FALSE); g_return_val_if_fail (GTS_SEGMENT (t->e2)->v1 != GTS_SEGMENT (t->e2)->v2, FALSE); g_return_val_if_fail (GTS_SEGMENT (t->e3)->v1 != GTS_SEGMENT (t->e3)->v2, FALSE); /* g_return_val_if_fail (GTS_OBJECT (t)->reserved == NULL, FALSE); */ g_return_val_if_fail (!gts_triangle_is_duplicate (t), FALSE); return TRUE; } trunk-2018.02b/py/3rd-party/pygts-0.3.1/triangle.h000066400000000000000000000040111324306050200212200ustar00rootroot00000000000000/* pygts - python package for the manipulation of triangulated surfaces * * Copyright (C) 2009 Thomas J. Duck * All rights reserved. * * Thomas J. Duck * Department of Physics and Atmospheric Science, * Dalhousie University, Halifax, Nova Scotia, Canada, B3H 3J5 * * NOTICE * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library 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 * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public * License along with this library; if not, write to the * Free Software Foundation, Inc., 59 Temple Place - Suite 330, * Boston, MA 02111-1307, USA. */ #ifndef __PYGTS_TRIANGLE_H__ #define __PYGTS_TRIANGLE_H__ typedef struct _PygtsObject PygtsTriangle; #define PYGTS_TRIANGLE(obj) ((PygtsTriangle*)obj) #define PYGTS_TRIANGLE_AS_GTS_TRIANGLE(o) \ (GTS_TRIANGLE(PYGTS_OBJECT(o)->gtsobj)) extern PyTypeObject PygtsTriangleType; gboolean pygts_triangle_check(PyObject* o); gboolean pygts_triangle_is_ok(PygtsTriangle *t); PygtsTriangle* pygts_triangle_new(GtsTriangle *t); int pygts_triangle_compare(GtsTriangle* t1,GtsTriangle* t2); /* Replacement for gts_triangle_is_ok(). The problem is that sometimes the * "reserved" variable is set in a face by gts, and so this function fails. * e.g., The error occurs when gts_triangle_is_ok() is called during * iteration over faces in a surface. This function ignores that check so * that there is no failure when PYGTS_DEBUG is set. A bug report should be * submitted. */ gboolean pygts_gts_triangle_is_ok(GtsTriangle *t); #endif /* __PYGTS_TRIANGLE_H__ */ trunk-2018.02b/py/3rd-party/pygts-0.3.1/vertex.c000066400000000000000000000476231324306050200207430ustar00rootroot00000000000000/* pygts - python package for the manipulation of triangulated surfaces * * Copyright (C) 2009 Thomas J. Duck * All rights reserved. * * Thomas J. Duck * Department of Physics and Atmospheric Science, * Dalhousie University, Halifax, Nova Scotia, Canada, B3H 3J5 * * NOTICE * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library 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 * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public * License along with this library; if not, write to the * Free Software Foundation, Inc., 59 Temple Place - Suite 330, * Boston, MA 02111-1307, USA. */ #include "pygts.h" #if PYGTS_DEBUG #define SELF_CHECK if(!pygts_vertex_check((PyObject*)self)) { \ PyErr_SetString(PyExc_RuntimeError, \ "problem with self object (internal error)"); \ return NULL; \ } #else #define SELF_CHECK #endif /*-------------------------------------------------------------------------*/ /* Methods exported to python */ static PyObject* is_ok(PygtsVertex *self, PyObject *args) { if(pygts_vertex_is_ok(self)) { Py_INCREF(Py_True); return Py_True; } else { Py_INCREF(Py_False); return Py_False; } } static PyObject* is_unattached(PygtsVertex *self, PyObject *args) { guint n; SELF_CHECK /* Check for attachments other than to the gtsobj_parent */ n = g_slist_length(PYGTS_VERTEX_AS_GTS_VERTEX(self)->segments); if( n > 1 ) { Py_INCREF(Py_False); return Py_False; } else { Py_INCREF(Py_True); return Py_True; } } static PyObject* is_boundary(PygtsVertex* self, PyObject *args) { PyObject *s_; PygtsObject *s; SELF_CHECK /* Parse the args */ if(! PyArg_ParseTuple(args, "O", &s_) ) { return NULL; } /* Convert to PygtsObjects */ if(!pygts_surface_check(s_)) { PyErr_SetString(PyExc_TypeError,"expected a Surface"); return NULL; } s = PYGTS_OBJECT(s_); if( gts_vertex_is_boundary(PYGTS_VERTEX_AS_GTS_VERTEX(self), PYGTS_SURFACE_AS_GTS_SURFACE(s)) ) { Py_INCREF(Py_True); return Py_True; } else { Py_INCREF(Py_False); return Py_False; } } static PyObject* contacts(PygtsVertex* self, PyObject *args) { PyObject *sever_=NULL; gboolean sever=FALSE; guint n; SELF_CHECK /* Parse the args */ if(! PyArg_ParseTuple(args, "|O", &sever_) ) { return NULL; } /* Convert to PygtsObjects */ if( sever_ != NULL ) { if(!PyBool_Check(sever_)) { PyErr_SetString(PyExc_TypeError,"expected a Boolean"); return NULL; } if( sever_ == Py_True ) { sever = TRUE; } } n = gts_vertex_is_contact(PYGTS_VERTEX_AS_GTS_VERTEX(self),sever); return Py_BuildValue("i",n); } static PyObject* is_connected(PygtsVertex *self, PyObject *args) { PyObject *v_; PygtsVertex *v; SELF_CHECK /* Parse the args */ if(! PyArg_ParseTuple(args, "O", &v_) ) { return NULL; } /* Convert to PygtsObjects */ if(!pygts_vertex_check(v_)) { PyErr_SetString(PyExc_TypeError,"expected a Vertex"); return NULL; } v = PYGTS_VERTEX(v_); if( gts_vertices_are_connected(PYGTS_VERTEX_AS_GTS_VERTEX(self), PYGTS_VERTEX_AS_GTS_VERTEX(v)) != NULL ) { Py_INCREF(Py_True); return Py_True; } else { Py_INCREF(Py_False); return Py_False; } } static PyObject* replace(PygtsVertex *self, PyObject *args) { PyObject *p2_; PygtsVertex *p2; GSList *parents=NULL, *i, *cur; SELF_CHECK /* Parse the args */ if(! PyArg_ParseTuple(args, "O", &p2_) ) { return NULL; } /* Convert to PygtsObjects */ if(!pygts_vertex_check(p2_)) { PyErr_SetString(PyExc_TypeError,"expected a Vertex"); return NULL; } p2 = PYGTS_VERTEX(p2_); if( self != p2 ) { /* (Ignore self-replacement) */ /* Detach and save any parent segments */ i = PYGTS_VERTEX_AS_GTS_VERTEX(self)->segments; while(i!=NULL) { cur = i; i = g_slist_next(i); if(PYGTS_IS_PARENT_SEGMENT(cur->data)) { PYGTS_VERTEX_AS_GTS_VERTEX(self)->segments = g_slist_remove_link(PYGTS_VERTEX_AS_GTS_VERTEX(self)->segments, cur); parents = g_slist_prepend(parents,cur->data); g_slist_free_1(cur); } } /* Perform the replace operation */ gts_vertex_replace(PYGTS_VERTEX_AS_GTS_VERTEX(self), PYGTS_VERTEX_AS_GTS_VERTEX(p2)); /* Reattach the parent segments */ i = parents; while(i!=NULL) { PYGTS_VERTEX_AS_GTS_VERTEX(self)->segments = g_slist_prepend(PYGTS_VERTEX_AS_GTS_VERTEX(self)->segments,i->data); i = g_slist_next(i); } g_slist_free(parents); } Py_INCREF(Py_None); return Py_None; } static PyObject* neighbors(PygtsVertex* self, PyObject *args) { PyObject *s_=NULL; GtsSurface *s=NULL; GSList *vertices,*v; PygtsVertex *vertex; PyObject *tuple; guint n,N; SELF_CHECK /* Parse the args */ if(! PyArg_ParseTuple(args, "|O", &s_) ) { return NULL; } /* Convert */ if( s_ != NULL ) { if(!pygts_surface_check(s_)) { PyErr_SetString(PyExc_TypeError,"expected a Surface"); return NULL; } s = PYGTS_SURFACE_AS_GTS_SURFACE(s_); } /* Get the neighbors */ vertices = gts_vertex_neighbors(PYGTS_VERTEX_AS_GTS_VERTEX(self), NULL,s); N = g_slist_length(vertices); /* Create the tuple */ if( (tuple=PyTuple_New(N)) == NULL) { PyErr_SetString(PyExc_MemoryError,"could not create tuple"); return NULL; } /* Put PygtsVertex objects into the tuple */ v = vertices; for(n=0;ndata)) ) { v = g_slist_next(v); } if( v==NULL ) break; if( (vertex = pygts_vertex_new(GTS_VERTEX(v->data))) == NULL ) { Py_DECREF((PyObject*)tuple); return NULL; } PyTuple_SET_ITEM(tuple, n, (PyObject*)vertex); v = g_slist_next(v); } if(_PyTuple_Resize(&tuple,n)!=0) { Py_DECREF(tuple); return NULL; } return tuple; } static PyObject* faces(PygtsVertex* self, PyObject *args) { PyObject *s_=NULL; GtsSurface *s=NULL; GSList *faces,*f; PygtsFace *face; PyObject *tuple; guint n,N; SELF_CHECK /* Parse the args */ if(! PyArg_ParseTuple(args, "|O", &s_) ) { return NULL; } /* Convert */ if( s_ != NULL ) { if(!pygts_surface_check(s_)) { PyErr_SetString(PyExc_TypeError,"expected a Surface"); return NULL; } s = PYGTS_SURFACE_AS_GTS_SURFACE(s_); } /* Get the faces */ faces = gts_vertex_faces(PYGTS_VERTEX_AS_GTS_VERTEX(self),s,NULL); N = g_slist_length(faces); /* Create the tuple */ if( (tuple=PyTuple_New(N)) == NULL) { PyErr_SetString(PyExc_MemoryError,"expected a tuple"); return NULL; } /* Put PygtsVertex objects into the tuple */ f = faces; for(n=0;ndata))) == NULL ) { Py_DECREF(tuple); return NULL; } PyTuple_SET_ITEM(tuple, n, (PyObject*)face); f = g_slist_next(f); } return tuple; } static PyObject* encroaches(PygtsVertex *self, PyObject *args) { PyObject *e_; PygtsEdge *e; SELF_CHECK /* Parse the args */ if(! PyArg_ParseTuple(args, "O", &e_) ) { return NULL; } /* Convert to PygtsObjects */ if(!pygts_edge_check(e_)) { PyErr_SetString(PyExc_TypeError,"expected an Edge"); return NULL; } e = PYGTS_EDGE(e_); if(gts_vertex_encroaches_edge(PYGTS_VERTEX_AS_GTS_VERTEX(self), PYGTS_EDGE_AS_GTS_EDGE(e))) { Py_INCREF(Py_True); return Py_True; } else { Py_INCREF(Py_False); return Py_False; } } static PyObject* triangles(PygtsVertex *self, PyObject *args) { GSList *triangles, *t; PygtsTriangle *triangle; guint i,N; PyObject *tuple; SELF_CHECK triangles = gts_vertex_triangles(PYGTS_VERTEX_AS_GTS_VERTEX(self),NULL); N = g_slist_length(triangles); /* Create the tuple */ if( (tuple=PyTuple_New(N)) == NULL) { PyErr_SetString(PyExc_MemoryError,"could not create tuple"); return NULL; } /* Put PygtsVertex objects into the tuple */ t = triangles; for(i=0;idata))) == NULL ) { Py_DECREF(tuple); return NULL; } PyTuple_SET_ITEM(tuple, i, (PyObject*)triangle); t = g_slist_next(t); } return tuple; } /* Methods table */ static PyMethodDef methods[] = { {"is_ok", (PyCFunction)is_ok, METH_NOARGS, "True if this Vertex v is OK. False otherwise.\n" "This method is useful for unit testing and debugging.\n" "\n" "Signature: v.is_ok().\n" }, {"is_unattached", (PyCFunction)is_unattached, METH_NOARGS, "True if this Vertex v is not the endpoint of any Segment.\n" "\n" "Signature: v.is_unattached().\n" }, {"is_boundary", (PyCFunction)is_boundary, METH_VARARGS, "True if this Vertex v is used by a boundary Edge of Surface s.\n" "\n" "Signature: v.is_boundary().\n" }, {"contacts", (PyCFunction)contacts, METH_VARARGS, "Returns the number of sets of connected Triangles sharing this\n" "Vertex v.\n" "\n" "Signature: v.contacts().\n" "\n" "If sever is True (default: False) and v is a contact vertex then\n" "the vertex is replaced in each Triangle with clones.\n" }, {"is_connected", (PyCFunction)is_connected, METH_VARARGS, "Return True if this Vertex v1 is connected to Vertex v2\n" "by a Segment.\n" "\n" "Signature: v1.is_connected().\n" }, {"replace", (PyCFunction)replace, METH_VARARGS, "Replaces this Vertex v1 with Vertex v2 in all Segments that have v1.\n" "Vertex v1 itself is left unchanged.\n" "\n" "Signature: v1.replace(v2).\n" }, {"neighbors", (PyCFunction)neighbors, METH_VARARGS, "Returns a tuple of Vertices attached to this Vertex v\n" "by a Segment.\n" "\n" "If a Surface s is given, only Vertices on s are considered.\n" "\n" "Signature: v.neighbors() or v.neighbors(s).\n" }, {"faces", (PyCFunction)faces, METH_VARARGS, "Returns a tuple of Faces that have this Vertex v.\n" "\n" "If a Surface s is given, only Vertices on s are considered.\n" "\n" "Signature: v.faces() or v.faces(s).\n" }, {"encroaches", (PyCFunction)encroaches, METH_VARARGS, "Returns True if this Vertex v is strictly contained in the\n" "diametral circle of Edge e. False otherwise.\n" "\n" "Only the projection onto the x-y plane is considered.\n" "\n" "Signature: v.encroaches(e)\n" }, {"triangles", (PyCFunction)triangles, METH_NOARGS, "Returns a list of Triangles that have this Vertex v.\n" "\n" "Signature: v.triangles()\n" }, {NULL} /* Sentinel */ }; /*-------------------------------------------------------------------------*/ /* Python type methods */ static GtsObject * parent(GtsVertex *v1); static PyObject * new(PyTypeObject *type, PyObject *args, PyObject *kwds) { PyObject *o; PygtsObject *obj; guint alloc_gtsobj = TRUE; /* Parse the args */ if(kwds) { o = PyDict_GetItemString(kwds,"alloc_gtsobj"); if(o==Py_False) { alloc_gtsobj = FALSE; } if(o!=NULL) { PyDict_DelItemString(kwds, "alloc_gtsobj"); } } if(kwds) { Py_INCREF(Py_False); PyDict_SetItemString(kwds,"alloc_gtsobj", Py_False); } /* Chain up */ obj = PYGTS_OBJECT(PygtsPointType.tp_new(type,args,kwds)); /* Allocate the gtsobj (if needed) */ if( alloc_gtsobj ) { obj->gtsobj = GTS_OBJECT(gts_vertex_new(gts_vertex_class(),0,0,0)); if( obj->gtsobj == NULL ) { PyErr_SetString(PyExc_MemoryError, "could not create Vertex"); return NULL; } /* Create the parent GtsSegment */ if( (obj->gtsobj_parent=parent(GTS_VERTEX(obj->gtsobj))) == NULL ) { gts_object_destroy(obj->gtsobj); obj->gtsobj = NULL; return NULL; } pygts_object_register(obj); } return (PyObject*)obj; } static int init(PygtsVertex *self, PyObject *args, PyObject *kwds) { gint ret; /* Chain up */ if( (ret=PygtsPointType.tp_init((PyObject*)self,args,kwds)) != 0 ) { return ret; } return 0; } /* Methods table */ PyTypeObject PygtsVertexType = { PyObject_HEAD_INIT(NULL) 0, /* ob_size */ "gts.Vertex", /* tp_name */ sizeof(PygtsVertex), /* tp_basicsize */ 0, /* tp_itemsize */ 0, /* tp_dealloc */ 0, /* tp_print */ 0, /* tp_getattr */ 0, /* tp_setattr */ 0, /* tp_compare */ 0, /* tp_repr */ 0, /* tp_as_number */ 0, /* tp_as_sequence */ 0, /* tp_as_mapping */ 0, /* tp_hash */ 0, /* tp_call */ 0, /* tp_str */ 0, /* tp_getattro */ 0, /* tp_setattro */ 0, /* tp_as_buffer */ Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, /* tp_flags */ "Vertex object", /* tp_doc */ 0, /* tp_traverse */ 0, /* tp_clear */ 0, /* tp_richcompare */ 0, /* tp_weaklistoffset */ 0, /* tp_iter */ 0, /* tp_iternext */ methods, /* tp_methods */ 0, /* tp_members */ 0, /* tp_getset */ 0, /* tp_base: attached in pygts.c */ 0, /* tp_dict */ 0, /* tp_descr_get */ 0, /* tp_descr_set */ 0, /* tp_dictoffset */ (initproc)init, /* tp_init */ 0, /* tp_alloc */ (newfunc)new /* tp_new */ }; /*-------------------------------------------------------------------------*/ /* Pygts functions */ gboolean pygts_vertex_check(PyObject* o) { gboolean check = FALSE; guint i,N; PyObject *obj; /* Check for a Vertex */ if( PyObject_TypeCheck(o, &PygtsVertexType) ) { check = TRUE; } /* Convert list into tuple */ if(PyList_Check(o)) { o = PyList_AsTuple(o); } else { Py_INCREF(o); } /* Check for a tuple of floats */ if( PyTuple_Check(o) ) { if( (N = PyTuple_Size(o)) <= 3 ) { check = TRUE; for(i=0;igtsobj_parent!=NULL,FALSE); g_return_val_if_fail(PYGTS_IS_PARENT_SEGMENT(obj->gtsobj_parent),FALSE); parent = g_slist_find(GTS_VERTEX(obj->gtsobj)->segments, obj->gtsobj_parent); g_return_val_if_fail(parent!=NULL,FALSE); return TRUE; } static GtsObject * parent(GtsVertex *v1) { GtsPoint *p1; GtsVertex *v2; GtsSegment *p; /* Create another Vertex */ p1 = GTS_POINT(v1); if( (v2 = gts_vertex_new(pygts_parent_vertex_class(),p1->x,p1->y,p1->z+1)) == NULL ) { PyErr_SetString(PyExc_MemoryError, "could not create parent"); return NULL; } /* Create and return the parent */ if( (p = gts_segment_new(pygts_parent_segment_class(),v1,v2)) == NULL ) { PyErr_SetString(PyExc_MemoryError, "could not create parent"); gts_object_destroy(GTS_OBJECT(v2)); return NULL; } return GTS_OBJECT(p); } PygtsVertex * pygts_vertex_new(GtsVertex *v) { PyObject *args, *kwds; PygtsObject *vertex; /* Check for Vertex in the object table */ if( (vertex = PYGTS_OBJECT(g_hash_table_lookup(obj_table,GTS_OBJECT(v)))) !=NULL ) { Py_INCREF(vertex); return PYGTS_VERTEX(vertex); } /* Build a new Vertex */ args = Py_BuildValue("ddd",0,0,0); kwds = Py_BuildValue("{s:O}","alloc_gtsobj",Py_False); vertex = PYGTS_VERTEX(PygtsVertexType.tp_new(&PygtsVertexType, args, kwds)); Py_DECREF(args); Py_DECREF(kwds); if( vertex == NULL ) { PyErr_SetString(PyExc_MemoryError,"could not create Vertex"); return NULL; } vertex->gtsobj = GTS_OBJECT(v); /* Attach the parent */ if( (vertex->gtsobj_parent=parent(v)) == NULL ) { Py_DECREF(vertex); return NULL; } /* Register and return */ pygts_object_register(vertex); return PYGTS_VERTEX(vertex); } PygtsVertex * pygts_vertex_from_sequence(PyObject *tuple) { guint i,N; gdouble x=0,y=0,z=0; PyObject *obj; GtsVertex *v; PygtsVertex *vertex; /* Convert list into tuple */ if(PyList_Check(tuple)) { tuple = PyList_AsTuple(tuple); } else { Py_INCREF(tuple); } if(!PyTuple_Check(tuple)) { Py_DECREF(tuple); PyErr_SetString(PyExc_TypeError,"expected a list or tuple of vertices"); return NULL; } /* Get the tuple size */ if( (N = PyTuple_Size(tuple)) > 3 ) { PyErr_SetString(PyExc_RuntimeError, "expected a list or tuple of up to three floats"); Py_DECREF(tuple); return NULL; } /* Get the coordinates */ for(i=0;iinfo.class_init_func), (GtsObjectInitFunc)(super->info.object_init_func), (GtsArgSetFunc) NULL, (GtsArgGetFunc) NULL }; klass = gts_object_class_new(gts_object_class(), &pygts_parent_segment_info); } return klass; } GtsVertexClass* pygts_parent_vertex_class(void) { static GtsVertexClass *klass = NULL; GtsObjectClass *super = NULL; if (klass == NULL) { super = GTS_OBJECT_CLASS(gts_vertex_class()); GtsObjectClassInfo pygts_parent_vertex_info = { "PygtsParentVertex", sizeof(PygtsParentVertex), sizeof(GtsVertexClass), (GtsObjectClassInitFunc)(super->info.class_init_func), (GtsObjectInitFunc)(super->info.object_init_func), (GtsArgSetFunc) NULL, (GtsArgGetFunc) NULL }; klass = gts_object_class_new(gts_object_class(), &pygts_parent_vertex_info); } return klass; } trunk-2018.02b/py/3rd-party/pygts-0.3.1/vertex.h000066400000000000000000000057011324306050200207370ustar00rootroot00000000000000/* pygts - python package for the manipulation of triangulated surfaces * * Copyright (C) 2009 Thomas J. Duck * All rights reserved. * * Thomas J. Duck * Department of Physics and Atmospheric Science, * Dalhousie University, Halifax, Nova Scotia, Canada, B3H 3J5 * * NOTICE * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library 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 * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public * License along with this library; if not, write to the * Free Software Foundation, Inc., 59 Temple Place - Suite 330, * Boston, MA 02111-1307, USA. */ #ifndef __PYGTS_VERTEX_H__ #define __PYGTS_VERTEX_H__ typedef struct _PygtsObject PygtsVertex; #define PYGTS_VERTEX(o) \ ( PyObject_TypeCheck((PyObject*)o, &PygtsVertexType) ? \ (PygtsVertex*)o : \ pygts_vertex_from_sequence((PyObject*)o) ) #define PYGTS_VERTEX_AS_GTS_VERTEX(o) \ ( PyObject_TypeCheck((PyObject*)o, &PygtsVertexType) ? \ GTS_VERTEX(PYGTS_OBJECT(o)->gtsobj) : \ GTS_VERTEX(PYGTS_OBJECT(PYGTS_VERTEX(o))->gtsobj) ) extern PyTypeObject PygtsVertexType; gboolean pygts_vertex_check(PyObject* o); gboolean pygts_vertex_is_ok(PygtsVertex *v); PygtsVertex* pygts_vertex_new(GtsVertex *f); PygtsVertex* pygts_vertex_from_sequence(PyObject *tuple); /*-------------------------------------------------------------------------*/ /* Parent GTS segment for GTS vertices */ /* Define a GtsSegment subclass that can be readily identified as the parent * of an encapsulated GtsVertex. The pygts_parent_segment_class() function * is defined at the bottom, and is what ultimately allows the distinction * to be made. This capability is used for vertex replacement operations. */ typedef struct _GtsSegment PygtsParentSegment; #define PYGTS_PARENT_SEGMENT(obj) GTS_OBJECT_CAST(obj,\ GtsSegment,\ pygts_parent_segment_class()) #define PYGTS_IS_PARENT_SEGMENT(obj)(gts_object_is_from_class(obj,\ pygts_parent_segment_class())) GtsSegmentClass* pygts_parent_segment_class(void); /* GTS vertices in parent segments */ typedef struct _GtsVertex PygtsParentVertex; #define PYGTS_PARENT_VERTEX(obj) GTS_OBJECT_CAST(obj,\ GtsVertex,\ pygts_parent_vertex_class()) #define PYGTS_IS_PARENT_VERTEX(obj)(gts_object_is_from_class(obj,\ pygts_parent_vertex_class())) GtsVertexClass *pygts_parent_vertex_class(void); #endif /* __PYGTS_VERTEX_H__ */ trunk-2018.02b/py/CMakeLists.txt000066400000000000000000000071011324306050200163550ustar00rootroot00000000000000#==================pygts========================================= IF(ENABLE_GTS) find_python_module(gts) IF (PY_gts) MESSAGE(STATUS "Use system gts version") ELSE (PY_gts) MESSAGE(STATUS "Use embedded version of gts. Please, consider installing the corresponding package") FILE(GLOB SRC_PYGTS "${CMAKE_CURRENT_SOURCE_DIR}/3rd-party/pygts-0.3.1/*.c") ADD_LIBRARY(_gts SHARED ${SRC_PYGTS}) SET_TARGET_PROPERTIES(_gts PROPERTIES PREFIX "") TARGET_LINK_LIBRARIES(_gts gts) INSTALL(FILES 3rd-party/pygts-0.3.1/__init__.py DESTINATION ${YADE_LIB_PATH}/py/gts) INSTALL(FILES 3rd-party/pygts-0.3.1/pygts.py DESTINATION ${YADE_LIB_PATH}/py/gts) INSTALL(TARGETS _gts DESTINATION ${YADE_LIB_PATH}/py/gts) ENDIF (PY_gts) ENDIF(ENABLE_GTS) #==================pygts========================================= #==================pyModules========================================= FILE(GLOB filesPY "${CMAKE_CURRENT_SOURCE_DIR}/*.py") SET(filesPY "${filesPY};${CMAKE_CURRENT_SOURCE_DIR}/pack/pack.py") INSTALL(FILES ${filesPY} DESTINATION ${YADE_PY_PATH}/yade) INSTALL(FILES 3rd-party/mtTkinter-0.4/mtTkinter.py DESTINATION ${YADE_LIB_PATH}/py) FILE(GLOB filesPYTests "${CMAKE_CURRENT_SOURCE_DIR}/tests/*.py") INSTALL(FILES ${filesPYTests} DESTINATION ${YADE_PY_PATH}/yade/tests) FILE(GLOB filesPYPerf "${CMAKE_CURRENT_SOURCE_DIR}/../examples/test/performance/*") INSTALL(FILES ${filesPYPerf} DESTINATION ${YADE_PY_PATH}/yade/tests/checks/performance) FILE(GLOB filesFEMxDEM "${CMAKE_CURRENT_SOURCE_DIR}/FEMxDEM/*.py") INSTALL(FILES ${filesFEMxDEM} DESTINATION ${YADE_PY_PATH}/yade/FEMxDEM) ADD_LIBRARY(WeightedAverage2d SHARED "${CMAKE_CURRENT_SOURCE_DIR}/WeightedAverage2d.cpp") SET_TARGET_PROPERTIES(WeightedAverage2d PROPERTIES PREFIX "") INSTALL(TARGETS WeightedAverage2d DESTINATION "${YADE_PY_PATH}/yade/") ADD_LIBRARY(_utils SHARED "${CMAKE_CURRENT_SOURCE_DIR}/_utils.cpp") SET_TARGET_PROPERTIES(_utils PROPERTIES PREFIX "") TARGET_LINK_LIBRARIES(_utils yade) INSTALL(TARGETS _utils DESTINATION "${YADE_PY_PATH}/yade/") ADD_LIBRARY(_polyhedra_utils SHARED "${CMAKE_CURRENT_SOURCE_DIR}/_polyhedra_utils.cpp") SET_TARGET_PROPERTIES(_polyhedra_utils PROPERTIES PREFIX "") TARGET_LINK_LIBRARIES(_polyhedra_utils) INSTALL(TARGETS _polyhedra_utils DESTINATION "${YADE_PY_PATH}/yade/") ADD_LIBRARY(_packPredicates SHARED "${CMAKE_CURRENT_SOURCE_DIR}/pack/_packPredicates.cpp") SET_TARGET_PROPERTIES(_packPredicates PROPERTIES PREFIX "") TARGET_LINK_LIBRARIES(_packPredicates yade) IF(ENABLE_GTS AND NOT(PY_gts)) TARGET_LINK_LIBRARIES(_packPredicates _gts) ENDIF(ENABLE_GTS AND NOT(PY_gts)) INSTALL(TARGETS _packPredicates DESTINATION "${YADE_PY_PATH}/yade/") ADD_LIBRARY(_packSpheres SHARED "${CMAKE_CURRENT_SOURCE_DIR}/pack/_packSpheres.cpp") SET_TARGET_PROPERTIES(_packSpheres PROPERTIES PREFIX "") TARGET_LINK_LIBRARIES(_packSpheres yade) INSTALL(TARGETS _packSpheres DESTINATION "${YADE_PY_PATH}/yade/") ADD_LIBRARY(_packObb SHARED "${CMAKE_CURRENT_SOURCE_DIR}/pack/_packObb.cpp") SET_TARGET_PROPERTIES(_packObb PROPERTIES PREFIX "") INSTALL(TARGETS _packObb DESTINATION "${YADE_PY_PATH}/yade/") ADD_LIBRARY(wrapper SHARED "${CMAKE_CURRENT_SOURCE_DIR}/wrapper/yadeWrapper.cpp") SET_TARGET_PROPERTIES(wrapper PROPERTIES PREFIX "") TARGET_LINK_LIBRARIES(wrapper yade) INSTALL(TARGETS wrapper DESTINATION "${YADE_PY_PATH}/yade/") ADD_LIBRARY(_customConverters SHARED "${CMAKE_CURRENT_SOURCE_DIR}/wrapper/customConverters.cpp") SET_TARGET_PROPERTIES(_customConverters PROPERTIES PREFIX "") TARGET_LINK_LIBRARIES(_customConverters yade) INSTALL(TARGETS _customConverters DESTINATION "${YADE_PY_PATH}/yade/") trunk-2018.02b/py/FEMxDEM/000077500000000000000000000000001324306050200147435ustar00rootroot00000000000000trunk-2018.02b/py/FEMxDEM/mpipool.py000066400000000000000000000041031324306050200167720ustar00rootroot00000000000000__contributor__="Lisandro Dalcín" """ MPIPool wrapped using mpi4py """ #import mpi4py #mpi4py.rc.threaded = False from mpi4py import MPI class MPIPool(object): def __init__(self, comm=None, master=0): self.comm = MPI.COMM_WORLD if comm is None else comm self.master = master self.workers = set(range(self.comm.size)) self.workers.discard(self.master) def is_master(self): return self.master == self.comm.rank def is_worker(self): return self.comm.rank in self.workers def map(self, function, iterable): assert self.is_master() comm = self.comm workerset = self.workers.copy() tasklist = [(tid, (function, arg)) for tid, arg in enumerate(iterable)] resultlist = [None] * len(tasklist) pending = len(tasklist) while pending: if workerset and tasklist: worker = workerset.pop() taskid, task = tasklist.pop() comm.send(task, dest=worker, tag=taskid) if tasklist: flag = comm.Iprobe(source=MPI.ANY_SOURCE, tag=MPI.ANY_TAG) if not flag: continue else: comm.Probe(source=MPI.ANY_SOURCE, tag=MPI.ANY_TAG) status = MPI.Status() result = comm.recv(source=MPI.ANY_SOURCE, tag=MPI.ANY_TAG, status=status) worker = status.source workerset.add(worker) taskid = status.tag resultlist[taskid] = result pending -= 1 return resultlist def start(self): if not self.is_worker(): return comm = self.comm master = self.master status = MPI.Status() while True: task = comm.recv(source=master, tag=MPI.ANY_TAG, status=status) if task is None: break function, arg = task result = function(arg) comm.ssend(result, master, status.tag) def close(self): if not self.is_master(): return for worker in self.workers: self.comm.send(None, worker, 0) trunk-2018.02b/py/FEMxDEM/msFEM2D.py000066400000000000000000000240141324306050200164530ustar00rootroot00000000000000__author__="Ning Guo, ceguo@connect.ust.hk" __supervisor__="Jidong Zhao, jzhao@ust.hk" __institution__="The Hong Kong University of Science and Technology" """ 2D model for multiscale simulation which implements a Newton-Raphson scheme into FEM framework to solve the nonlinear problem where the tangent operator is obtained from DEM simulation by calling simDEM modules""" # import Escript modules import esys.escript as escript from esys.escript import util from esys.escript.linearPDEs import LinearPDE,SolverOptions # import YADE modules from simDEM import * # other python modules from itertools import repeat """ function to return pool for parallelization supporting both MPI (experimental) on distributed memory and multiprocessing on shared memory. """ def get_pool(mpi=False,threads=1): if mpi: # using MPI from mpipool import MPIPool pool = MPIPool() pool.start() if not pool.is_master(): sys.exit(0) elif threads>1: # using multiprocessing from multiprocessing import Pool pool = Pool(processes=threads) else: raise RuntimeError,"Wrong arguments: either mpi=True or threads>1." return pool class MultiScale(object): """ problem description: -(A_{ijkl} u_{k,l})_{,j} = -X_{ij,j} + Y_i Neumann boundary: n_j A_{ijkl} u_{k,l} = n_j X_{ij} + y_i Dirichlet boundary: u_i = r_i where q_i > 0 :var u: unknown vector, displacement :var A: elastic tensor / tangent operator :var X: old/current stress tensor :var Y: vector, body force :var y: vector, Neumann bc traction :var q: vector, Dirichlet bc mask :var r: vector, Dirichlet bc value """ def __init__(self,domain,ng=1,useMPI=False,np=1,random=False,rtol=1.e-2,usePert=False,pert=-2.e-6,verbose=False): """ initialization of the problem, i.e. model constructor :param domain: type Domain, domain of the problem :param ng: type integer, number of Gauss points :param useMPI: type boolean, use MPI or not :param np: type integer, number of processors :param random: type boolean, if or not use random density field :param rtol: type float, relevative tolerance for global convergence :param usePert: type boolean, if or not use perturbation method :param pert: type float, perturbated strain applied to DEM to obtain tangent operator :param verbose: type boolean, if or not print messages during calculation """ self.__domain=domain self.__pde=LinearPDE(domain,numEquations=self.__domain.getDim(),numSolutions=self.__domain.getDim()) self.__pde.getSolverOptions().setSolverMethod(SolverOptions.DIRECT) self.__pde.setSymmetryOn() #self.__pde.getSolverOptions().setTolerance(rtol**2) #self.__pde.getSolverOptions().setPackage(SolverOptions.UMFPACK) self.__numGaussPoints=ng self.__rtol=rtol self.__usepert=usePert self.__pert=pert self.__verbose=verbose self.__pool=get_pool(mpi=useMPI,threads=np) self.__scenes=self.__pool.map(initLoad,range(ng)) self.__strain=escript.Tensor(0,escript.Function(self.__domain)) self.__stress=escript.Tensor(0,escript.Function(self.__domain)) self.__S=escript.Tensor4(0,escript.Function(self.__domain)) if self.__usepert: s = self.__pool.map(getStressTensor,self.__scenes) t = self.__pool.map(getTangentOperator,zip(self.__scenes,repeat(pert))) for i in xrange(ng): self.__stress.setValueOfDataPoint(i,s[i]) self.__S.setValueOfDataPoint(i,t[i]) else: st = self.__pool.map(getStressAndTangent2D,self.__scenes) for i in xrange(ng): self.__stress.setValueOfDataPoint(i,st[i][0]) self.__S.setValueOfDataPoint(i,st[i][1]) def initialize(self, b=escript.Data(), f=escript.Data(), specified_u_mask=escript.Data(), specified_u_val=escript.Data()): """ initialize the model for each time step, e.g. assign parameters :param b: type vector, body force on FunctionSpace, e.g. gravity :param f: type vector, boundary traction on FunctionSpace (FunctionOnBoundary) :param specified_u_mask: type vector, mask of location for Dirichlet boundary :param specified_u_val: type vector, specified displacement for Dirichlet boundary """ self.__pde.setValue(Y=b,y=f,q=specified_u_mask,r=specified_u_val) def getDomain(self): """ return model domain """ return self.__domain def getRelTolerance(self): """ return relative tolerance for convergence type float """ return self.__rtol def getCurrentPacking(self,pos=(),time=0,prefix=''): if len(pos) == 0: # output all Gauss points packings self.__pool.map(outputPack,zip(self.__scenes,repeat(time),repeat(prefix))) else: # output selected Gauss points packings scene = [self.__scenes[i] for i in pos] self.__pool.map(outputPack,zip(scene,repeat(time),repeat(prefix))) def getLocalVoidRatio(self): void=escript.Scalar(0,escript.Function(self.__domain)) e = self.__pool.map(getVoidRatio2D,self.__scenes) for i in xrange(self.__numGaussPoints): void.setValueOfDataPoint(i,e[i]) return void def getLocalAvgRotation(self): rot=escript.Scalar(0,escript.Function(self.__domain)) r = self.__pool.map(avgRotation2D,self.__scenes) for i in xrange(self.__numGaussPoints): rot.setValueOfDataPoint(i,r[i]) return rot def getLocalFabric(self): fabric=escript.Tensor(0,escript.Function(self.__domain)) f = self.__pool.map(getFabric2D,self.__scenes) for i in xrange(self.__numGaussPoints): fabric.setValueOfDataPoint(i,f[i]) return fabric """ used for clumped particle model only def getLocalParOriFab(self): fabric=escript.Tensor(0,escript.Function(self.__domain)) f = self.__pool.map(getParOriFabric,self.__scenes) for i in xrange(self.__numGaussPoints): fabric.setValueOfDataPoint(i,f[i]) return fabric """ """ used for cohesive particle model only def getLocalBondBreakage(self,oriIntr=[]): debond = escript.Scalar(0,escript.Function(self.__domain)) num = self.__pool.map(getDebondingNumber,zip(self.__scenes,repeat(oriIntr))) for i in xrange(self.__numGaussPoints): debond.setValueOfDataPoint(i,num[i]) return debond """ def getCurrentTangent(self): """ return current tangent operator type Tensor4 on FunctionSpace """ return self.__S def getCurrentStress(self): """ return current stress type: Tensor on FunctionSpace """ return self.__stress def getCurrentStrain(self): """ return current strain type: Tensor on FunctionSpace """ return self.__strain def exitSimulation(self): """finish the whole simulation, exit""" self.__pool.close() def solve(self, iter_max=100): """ solve the equation using Newton-Ralphson scheme """ iterate=0 rtol=self.getRelTolerance() stress=self.getCurrentStress() s=self.getCurrentTangent() x_safe=self.__domain.getX() self.__pde.setValue(A=s, X=-stress) #residual0=util.L2(self.__pde.getRightHandSide()) # using force error u=self.__pde.getSolution() # trial solution, displacement D=util.grad(u) # trial strain tensor # !!!!!! obtain stress and tangent operator from DEM part update_stress,update_s,update_scenes=self.applyStrain_getStressTangentDEM(st=D) err=1.0 # initial error before iteration converged=(errrtol*0.001: # only update DEM parts when error is large enough self.__domain.setX(x_safe) D=util.grad(u) update_stress,update_s,update_scenes=self.applyStrain_getStressTangentDEM(st=D) #if err>err_safe: # to ensure consistent convergence, however this may not be achieved due to fluctuation! # raise RuntimeError,"No improvement of convergence with iterations! Relative error: %e"%err """ update 'domain geometry', 'stress', 'tangent operator', 'accumulated strain' and 'simulation scenes'. """ self.__domain.setX(x_safe+u) self.__stress=update_stress self.__S=update_s self.__strain+=D self.__scenes=update_scenes if self.__verbose: print "Convergence reached after %d iteration(s)! Relative error: %e"%(iterate,err) return u """ apply strain to DEM packing, get stress and tangent operator (including two methods) """ def applyStrain_getStressTangentDEM(self,st=escript.Data()): st = st.toListOfTuples() st = numpy.array(st).reshape(-1,4) stress = escript.Tensor(0,escript.Function(self.__domain)) S = escript.Tensor4(0,escript.Function(self.__domain)) scenes = self.__pool.map(shear2D,zip(self.__scenes,st)) if self.__usepert: s = self.__pool.map(getStressTensor,scenes) t = self.__pool.map(getTangentOperator,zip(scenes,repeat(self.__pert))) for i in xrange(self.__numGaussPoints): stress.setValueOfDataPoint(i,s[i]) S.setValueOfDataPoint(i,t[i]) else: ST = self.__pool.map(getStressAndTangent2D,scenes) for i in xrange(self.__numGaussPoints): stress.setValueOfDataPoint(i,ST[i][0]) S.setValueOfDataPoint(i,ST[i][1]) return stress,S,scenes trunk-2018.02b/py/FEMxDEM/msFEM3D.py000066400000000000000000000206721324306050200164620ustar00rootroot00000000000000__author__="Ning Guo, ceguo@connect.ust.hk" __supervisor__="Jidong Zhao, jzhao@ust.hk" __institution__="The Hong Kong University of Science and Technology" """ 3D model for multiscale simulation which implements a Newton-Raphson scheme into FEM framework to solve the nonlinear problem where the tangent operator is obtained from DEM simulation by calling simDEM modules""" """ import Escript modules """ import esys.escript as escript from esys.escript import util from esys.escript.linearPDEs import LinearPDE,SolverOptions from simDEM import * from itertools import repeat """ function to return pool for parallelization supporting both MPI (experimental) on distributed memory and multiprocessing on shared memory. """ def get_pool(mpi=False,threads=1): if mpi: # using MPI from mpipool import MPIPool pool = MPIPool() pool.start() if not pool.is_master(): sys.exit(0) elif threads>1: # using multiprocessing from multiprocessing import Pool pool = Pool(processes=threads) else: raise RuntimeError,"Wrong arguments: either mpi=True or threads>1." return pool class MultiScale(object): """ problem description: -(A_{ijkl} u_{k,l})_{,j} = -X_{ij,j} + Y_i Neumann boundary: n_j A_{ijkl} u_{k,l} = n_j X_{ij} + y_i Dirichlet boundary: u_i = r_i where q_i > 0 :var u: unknown vector, displacement :var A: elastic tensor / tangent operator :var X: old/current stress tensor :var Y: vector, body force :var y: vector, Neumann bc traction :var q: vector, Dirichlet bc mask :var r: vector, Dirichlet bc value """ def __init__(self,domain,ng=1,useMPI=False,np=1,random=False,rtol=1.e-2,verbose=False): """ initialization of the problem, i.e. model constructor :param domain: type Domain, domain of the problem :param ng: type integer, number of Gauss points :param useMPI: type boolean, use MPI or not :param np: type integer, number of processors :param random: type boolean, if or not use random density field :param rtol: type float, relevant tolerance for global convergence :param verbose: type boolean, if or not print messages during calculation """ self.__domain=domain self.__pde=LinearPDE(domain,numEquations=self.__domain.getDim(),numSolutions=self.__domain.getDim()) self.__pde.getSolverOptions().setSolverMethod(SolverOptions.DIRECT) self.__pde.setSymmetryOn() #self.__pde.getSolverOptions().setTolerance(rtol**2) #self.__pde.getSolverOptions().setPackage(SolverOptions.UMFPACK) self.__numGaussPoints=ng self.__rtol=rtol self.__verbose=verbose self.__pool=get_pool(mpi=useMPI,threads=np) self.__scenes=self.__pool.map(initLoad,range(ng)) self.__strain=escript.Tensor(0,escript.Function(self.__domain)) self.__stress=escript.Tensor(0,escript.Function(self.__domain)) self.__S=escript.Tensor4(0,escript.Function(self.__domain)) st = self.__pool.map(getStressAndTangent,self.__scenes) for i in xrange(ng): self.__stress.setValueOfDataPoint(i,st[i][0]) self.__S.setValueOfDataPoint(i,st[i][1]) def initialize(self, b=escript.Data(), f=escript.Data(), specified_u_mask=escript.Data(), specified_u_val=escript.Data()): """ initialize the model for each time step, e.g. assign parameters :param b: type vector, body force on FunctionSpace, e.g. gravity :param f: type vector, boundary traction on FunctionSpace (FunctionOnBoundary) :param specified_u_mask: type vector, mask of location for Dirichlet boundary :param specified_u_val: type vector, specified displacement for Dirichlet boundary """ self.__pde.setValue(Y=b,y=f,q=specified_u_mask,r=specified_u_val) def getDomain(self): """ return model domain """ return self.__domain def getRelTolerance(self): """ return relative tolerance for convergence type float """ return self.__rtol def getCurrentPacking(self,pos=(),time=0,prefix=''): if len(pos) == 0: # output all Gauss points packings self.__pool.map(outputPack,zip(self.__scenes,repeat(time),repeat(prefix))) else: # output selected Gauss points packings scene = [self.__scenes[i] for i in pos] self.__pool.map(outputPack,zip(scene,repeat(time),repeat(prefix))) def getLocalVoidRatio(self): void=escript.Scalar(0,escript.Function(self.__domain)) e = self.__pool.map(getVoidRatio,self.__scenes) for i in xrange(self.__numGaussPoints): void.setValueOfDataPoint(i,e[i]) return void def getLocalAvgRotation(self): rot=escript.Vector(0,escript.Function(self.__domain)) r = self.__pool.map(avgRotation,self.__scenes) for i in xrange(self.__numGaussPoints): rot.setValueOfDataPoint(i,r[i]) return rot def getLocalFabric(self): fabric=escript.Tensor(0,escript.Function(self.__domain)) f = self.__pool.map(getFabric,self.__scenes) for i in xrange(self.__numGaussPoints): fabric.setValueOfDataPoint(i,f[i]) return fabric def getCurrentTangent(self): """ return current tangent operator type Tensor4 on FunctionSpace """ return self.__S def getCurrentStress(self): """ return current stress type: Tensor on FunctionSpace """ return self.__stress def getCurrentStrain(self): """ return current strain type: Tensor on FunctionSpace """ return self.__strain def exitSimulation(self): """finish the whole simulation, exit""" self.__pool.close() def solve(self, iter_max=100): """ solve the equation using Newton-Ralphson scheme """ iterate=0 rtol=self.getRelTolerance() stress=self.getCurrentStress() s=self.getCurrentTangent() x_safe=self.__domain.getX() self.__pde.setValue(A=s, X=-stress) #residual0=util.L2(self.__pde.getRightHandSide()) # using force error u=self.__pde.getSolution() # trial solution, displacement D=util.grad(u) # trial strain tensor # !!!!!! obtain stress and tangent operator from DEM part update_stress,update_s,update_scenes=self.applyStrain_getStressTangentDEM(st=D) err=1.0 # initial error before iteration converged=(errrtol*0.001: # only update DEM parts when error is large enough self.__domain.setX(x_safe) D=util.grad(u) update_stress,update_s,update_scenes=self.applyStrain_getStressTangentDEM(st=D) #if err>err_safe: # to ensure consistent convergence, however this may not be achieved due to fluctuation! # raise RuntimeError,"No improvement of convergence with iterations! Relative error: %e"%err """ update 'domain geometry', 'stress', 'tangent operator', 'accumulated strain' and 'simulation scenes'. """ self.__domain.setX(x_safe+u) self.__stress=update_stress self.__S=update_s self.__strain+=D self.__scenes=update_scenes if self.__verbose: print "Convergence reached after %d iteration(s)! Relative error: %e"%(iterate,err) return u """ apply strain to DEM packing, get stress and tangent operator (including two methods) """ def applyStrain_getStressTangentDEM(self,st=escript.Data()): st = st.toListOfTuples() st = numpy.array(st).reshape(-1,9) stress = escript.Tensor(0,escript.Function(self.__domain)) S = escript.Tensor4(0,escript.Function(self.__domain)) scenes = self.__pool.map(shear,zip(self.__scenes,st)) st = self.__pool.map(getStressAndTangent,scenes) for i in xrange(self.__numGaussPoints): stress.setValueOfDataPoint(i,st[i][0]) S.setValueOfDataPoint(i,st[i][1]) return stress,S,scenes trunk-2018.02b/py/FEMxDEM/msFEMup.py000066400000000000000000000263441324306050200166420ustar00rootroot00000000000000__author__="Ning Guo, ceguo@connect.ust.hk" __supervisor__="Jidong Zhao, jzhao@ust.hk" __institution__="The Hong Kong University of Science and Technology" """ 2D model for multiscale simulation of a hydromechanical system based on u-p formulation; iterative scheme (fixed-stress split) is used for the coupled PDEs; Newton-Raphson scheme is used to solve the nonlinear PDE for displacement, pore pressure is solved from the mass conservation equation; total stress is a superposition of the effective stress and pore pressure.""" # import Escript modules import esys.escript as escript from esys.escript import util from esys.escript.linearPDEs import LinearPDE,SolverOptions from simDEM import * from itertools import repeat """ function to return pool for parallelization supporting both MPI (experimental) on distributed memory and multiprocessing on shared memory. """ def get_pool(mpi=False,threads=1): if mpi: # using MPI from mpipool import MPIPool pool = MPIPool() pool.start() if not pool.is_master(): sys.exit(0) elif threads>1: # using multiprocessing from multiprocessing import Pool pool = Pool(processes=threads) else: raise RuntimeError,"Wrong arguments: either mpi=True or threads>1." return pool class MultiScale(object): """ problem description 1. displacement: -(A_{ijkl} u_{k,l})_{,j} = -X_{ij,j} + Y_i Neumann boundary: n_j A_{ijkl} u_{k,l} = n_j X_{ij} + y_i Dirichlet boundary: u_i = r_i where q_i > 0 :var u: unknown vector, displacement :var A: elastic tensor / tangent operator :var X: tensor, minus old stress :var Y: vector, gamma - grad(p) :var y: vector, Neumann bc traction :var q: vector, Dirichlet bc mask :var r: vector, Dirichlet bc value 2. pore pressure: -(A_{ij} p_{,j})_{,i} + D p = Y Neumann boundary: n_j A_{jl} p_{,l} = y Dirichlet boundary: p = r where q > 0 :var p: unknown scalar, pore pressure :var A: permeability tensor :var D: scalar, n / (K_f dt) :var Y: scalar, -dot(u)_{i,i} + n p_pre / (K_f dt) :var y: scalar, Neumann bc flux :var q: scalar, Dirichlet bc mask :var r: scalar, Dirichlet bc value """ def __init__(self,domain,pore0=0.,perm=1.e-5,kf=2.2e9,dt=0.001,ng=1,useMPI=False,np=1,rtol=1.e-2): """ initialization of the problem, i.e. model constructor :param domain: type Domain, domain of the problem :param pore0: type float, initial pore pressure :param perm: type float, d^2/(150 mu_f) in KC equation :param kf: type float, bulk modulus of the fluid :param dt: type float, time step for calculation :param ng: type integer, number of Gauss points :param useMPI: type boolean, use MPI or not :param np: type integer, number of processors :param rtol: type float, relevative tolerance for global convergence """ self.__domain=domain self.__upde=LinearPDE(domain,numEquations=domain.getDim(),numSolutions=domain.getDim()) self.__ppde=LinearPDE(domain,numEquations=1,numSolutions=1) # use reduced interpolation for pore pressure self.__ppde.setReducedOrderOn() self.__upde.getSolverOptions().setSolverMethod(SolverOptions.DIRECT) self.__ppde.getSolverOptions().setSolverMethod(SolverOptions.DIRECT) self.__upde.setSymmetryOn() self.__ppde.setSymmetryOn() self.__dt=dt self.__bulkFluid=kf self.__numGaussPoints=ng self.__rtol=rtol self.__stress=escript.Tensor(0,escript.Function(domain)) self.__S=escript.Tensor4(0,escript.Function(domain)) self.__pool=get_pool(mpi=useMPI,threads=np) self.__scenes=self.__pool.map(initLoad,range(ng)) st = self.__pool.map(getStressAndTangent2D,self.__scenes) for i in xrange(ng): self.__stress.setValueOfDataPoint(i,st[i][0]) self.__S.setValueOfDataPoint(i,st[i][1]) self.__strain=escript.Tensor(0,escript.Function(domain)) self.__pore=escript.Scalar(pore0,escript.ReducedSolution(domain)) self.__pgauss=util.interpolate(pore0,escript.Function(domain)) self.__permeability=perm self.__meanStressRate=escript.Scalar(0,escript.Function(domain)) self.__r=escript.Vector(0,escript.Solution(domain)) #Dirichlet BC for u def initialize(self, b=escript.Data(), f=escript.Data(), umsk=escript.Data(), uvalue=escript.Data(), flux=escript.Data(), pmsk=escript.Data(), pvalue=escript.Data()): """ initialize the model for each time step, e.g. assign parameters :param b: type vector, body force on FunctionSpace, e.g. gravity :param f: type vector, boundary traction on FunctionSpace (FunctionOnBoundary) :param umsk: type vector, mask of location for Dirichlet boundary :param uvalue: type vector, specified displacement for Dirichlet boundary """ self.__upde.setValue(Y=b,y=f,q=umsk,r=uvalue) self.__ppde.setValue(y=flux,q=pmsk,r=pvalue) self.__r=uvalue def getDomain(self): """ return model domain """ return self.__domain def setTimeStep(self,dt=1.0): self.__dt = dt def getCurrentPacking(self,pos=(),time=0,prefix=''): if len(pos) == 0: # output all Gauss points packings self.__pool.map(outputPack,zip(self.__scenes,repeat(time),repeat(prefix))) else: # output selected Gauss points packings scene = [self.__scenes[i] for i in pos] self.__pool.map(outputPack,zip(scene,repeat(time),repeat(prefix))) def getEquivalentPorosity(self): porosity=escript.Scalar(0,escript.Function(self.__domain)) p = self.__pool.map(getEquivalentPorosity,self.__scenes) for i in xrange(self.__numGaussPoints): porosity.setValueOfDataPoint(i,p[i]) return porosity def getLocalAvgRotation(self): rot=escript.Scalar(0,escript.Function(self.__domain)) r = self.__pool.map(avgRotation2D,self.__scenes) for i in xrange(self.__numGaussPoints): rot.setValueOfDataPoint(i,r[i]) return rot def getLocalFabric(self): fabric=escript.Tensor(0,escript.Function(self.__domain)) f = self.__pool.map(getFabric2D,self.__scenes) for i in xrange(self.__numGaussPoints): fabric.setValueOfDataPoint(i,f[i]) return fabric def getCurrentTangent(self): """ return current tangent operator type Tensor4 on FunctionSpace """ return self.__S def getCurrentStress(self): """ return current stress (effective) type: Tensor on FunctionSpace """ return self.__stress def getCurrentPore(self): """ return current pore pressure type: Scalar on ReducedSolution """ return self.__pore def getCurrentStrain(self): """ return current strain type: Tensor on FunctionSpace """ return self.__strain def getCurrentFlux(self): """ return current Darcy flux type: Vector on FunctionSpace """ n=self.getEquivalentPorosity() perm=self.__permeability*n**3/(1.-n)**2 flux=-perm*util.grad(self.__pore) return flux def exitSimulation(self): """finish the whole simulation, exit""" self.__pool.close() def solveSolid(self, p_iter_gauss=escript.Data(), iter_max=50): """ solve the pde for displacement using Newton-Ralphson scheme """ k=util.kronecker(self.__domain) p=p_iter_gauss*k iterate=0 rtol=self.__rtol stress_safe=self.__stress s_safe=self.__S x_safe=self.__domain.getX() self.__upde.setValue(A=s_safe, X=-stress_safe+p, r=self.__r) #residual0=util.L2(self.__pde.getRightHandSide()) # using force error u=self.__upde.getSolution() # trial solution, displacement D=util.grad(u) # trial strain tensor # !!!!!! obtain stress and tangent operator from DEM part update_stress,update_s,update_scenes=self.applyStrain_getStressTangentDEM(st=D) err=util.Lsup(u) # initial error before iteration converged=(err<1.e-12) while (not converged) and (iterateiter_max: # raise RuntimeError,"Convergence for Newton-Raphson failed after %s steps."%(iter_max) iterate+=1 self.__domain.setX(x_safe+u) self.__upde.setValue(A=update_s,X=-update_stress+p,r=escript.Data()) #residual=util.L2(self.__pde.getRightHandSide()) du=self.__upde.getSolution() u+=du l,d=util.L2(u),util.L2(du) err=d/l # displacement error, alternatively using force error 'residual' converged=(errrtol**3: # only update DEM parts when error is large enough self.__domain.setX(x_safe) D=util.grad(u) update_stress,update_s,update_scenes=self.applyStrain_getStressTangentDEM(st=D) """reset domain geometry to original until global convergence""" self.__domain.setX(x_safe) return u,D,update_stress,update_s,update_scenes def solve(self, globalIter=10, solidIter=50): """ solve the coupled PDE using fixed-stress split method, call solveSolid to get displacement """ rtol=self.__rtol x_safe=self.__domain.getX() k=util.kronecker(self.__domain) kdr=util.inner(k,util.tensor_mult(self.__S,k))/4. n=self.getEquivalentPorosity() perm=self.__permeability*n**3/(1.-n)**2*k kf=self.__bulkFluid dt=self.__dt self.__ppde.setValue(A=perm,D=(n/kf+1./kdr)/dt,Y=(n/kf+1./kdr)/dt*self.__pgauss-self.__meanStressRate/kdr) p_iter_old=self.__ppde.getSolution() p_iter_gauss=util.interpolate(p_iter_old,escript.Function(self.__domain)) u_old,D,sig,s,scene=self.solveSolid(p_iter_gauss=p_iter_gauss,iter_max=solidIter) converge=False iterate=0 while (not converge) and (iterate """ def saveGauss2D(name='',pos=(),**kwargs): fout = file(name,'w') for key in kwargs: data = kwargs[key].toListOfTuples() if len(pos)==0: fout.write('%s '%key+str(len(data))+'\n') for i in xrange(len(data)): fout.write(' '.join('%s %s'%x for x in data[i])+'\n') else: fout.write('%s '%key+str(len(pos))+'\n') for i in pos: fout.write(' '.join('%s %s'%x for x in data[i])+'\n') fout.close() def saveGauss3D(name='',pos=(),**kwargs): fout = file(name,'w') for key in kwargs: data = kwargs[key].toListOfTuples() if len(pos)==0: fout.write('%s '%key+str(len(data))+'\n') for i in xrange(len(data)): fout.write(' '.join('%s %s %s'%x for x in data[i])+'\n') else: fout.write('%s '%key+str(len(pos))+'\n') for i in pos: fout.write(' '.join('%s %s %s'%x for x in data[i])+'\n') fout.close() trunk-2018.02b/py/FEMxDEM/simDEM.py000066400000000000000000000233541324306050200164420ustar00rootroot00000000000000__author__="Ning Guo, ceguo@connect.ust.hk" __supervisor__="Jidong Zhao, jzhao@ust.hk" __institution__="The Hong Kong University of Science and Technology" """ DEM part for multiscale simulation which sets up a packing representing a material point (RVE) at the Gauss point of the FEM domain and returns constitutive responses (updated stress and tangent operator) at this point. """ # import yade modules import sys from os.path import expanduser sys.path.append(expanduser('~')+'/yade/bin') # path where you have yadeimport.py # yadeimport.py is generated by `ln yade-versionNo yadeimport.py` from yadeimport import * import numpy def initLoad(ID=0): # where ID identifies the Gauss point location if 1: # All Gauss points import 0.yade.gz resulting in a uniform sample (default) Omega().load('0.yade.gz') else: # Otherwise load different packings to generate random field # resulting in an inherently heterogeneous sample Omega().load(str(ID)+'.yade.gz') Omega().tags['id']=str(ID) return Omega().sceneToString() def outputPack(param): if len(param) != 3: raise RuntimeError,"No. of param. should be exactly 3. 0: RVE scene; 1: step count; 2: name prefix" Omega().stringToScene(param[0]) pos = Omega().tags['id'] Omega().save(param[2]+'packing_'+pos+'_'+str(param[1])+'.yade.gz') def numOfParticles(scene): Omega().stringToScene(scene) return len(Omega().bodies) # !!! for spherical particle packing only # Apply deformation on 2D DEM packing def shear2D(param): if len(param) != 2: raise RuntimeError,"No. of param. should be exactly 2. 0: RVE scene; 1: strain." Omega().stringToScene(param[0]) ns=int(max(1e5*numpy.max(numpy.abs(param[1])),2)) dstrain = utils.Matrix3(param[1][0],param[1][1],0, param[1][2],param[1][3],0, 0,0,0) Omega().cell.velGrad=dstrain/(ns*Omega().dt) Omega().run(ns,True) Omega().cell.velGrad=utils.Matrix3.Zero return Omega().sceneToString() # Apply deformation on 3D DEM packing def shear3D(param): if len(param) != 2: raise RuntimeError,"No. of param. should be exactly 2. 0: RVE scene; 1: strain." Omega().stringToScene(param[0]) ns=int(max(1e5*numpy.max(numpy.abs(param[1])),2)) dstrain = utils.Matrix3(param[1][0],param[1][1],param[1][2], param[1][3],param[1][4],param[1][5], param[1][6],param[1][7],param[1][8]) Omega().cell.velGrad=dstrain/(ns*Omega().dt) Omega().run(ns,True) Omega().cell.velGrad=utils.Matrix3.Zero return Omega().sceneToString() """ Used for perturbation method only (2D), deprecated def getStressTensor(scene): Omega().stringToScene(scene) stress = utils.getStress() stress = .5*(stress+stress.transpose()) return [[stress[0],stress[1]],[stress[3],stress[4]]] """ # get contact normal based fabric tensor def getFabric2D(scene): Omega().stringToScene(scene) f = utils.fabricTensor(splitTensor=False)[0] return [[f[0,0],f[0,1]],[f[1,0],f[1,1]]] def getFabric3D(scene): Omega().stringToScene(scene) f = utils.fabricTensor(splitTensor=False)[0] return [[f[0,0],f[0,1],f[0,2]],[f[1,0],f[1,1],f[1,2]],[f[2,0],f[2,1],f[2,2]]] """ # Used for clumped particle model only # get particle orientation based fabric tensor def getParOriFabric(scene): Omega().stringToScene(scene) fab = utils.Matrix3.Zero numPar = 0 for b in Omega().bodies: if b.isClump: numPar += 1 keys = b.shape.members.keys() pos1 = Omega().bodies[keys[0]].state.pos pos2 = Omega().bodies[keys[1]].state.pos ori = (pos1-pos2).normalized() fab += ori.outer(ori) fab /= numPar return [[fab[0],fab[1]],[fab[3],fab[4]]] """ """ # Used for cohesive particle model only # get the bond breakage number within a packing def getDebondingNumber(param): Omega().stringToScene(param[0]) num = 0 for id1,id2 in param[1]: try: i = Omega().interactions[id1,id2] if not i.isReal(): num += 1 elif i.phys.cohesionBroken: num += 1 except IndexError: num += 1 return num """ # get updated stress tensor and tangent operator as a tuple # utils.getStressAndTangent() is implemented in Shop.cpp def getStressAndTangent2D(scene): Omega().stringToScene(scene) st = utils.getStressAndTangent(symmetry=True) s = st[0] s = .5*(s+s.transpose()) t=[[[[0,0],[0,0]],[[0,0],[0,0]]],[[[0,0],[0,0]],[[0,0],[0,0]]]] t[0][0][0][0]=st[1][0,0] t[1][1][0][0]=t[0][0][1][1]=st[1][0,1] t[0][1][0][0]=t[0][0][0][1]=t[1][0][0][0]=t[0][0][1][0]=st[1][0,5] t[1][1][1][1]=st[1][1,1] t[1][1][0][1]=t[0][1][1][1]=t[1][1][1][0]=t[1][0][1][1]=st[1][1,5] t[0][1][0][1]=t[0][1][1][0]=t[1][0][0][1]=t[1][0][1][0]=st[1][5,5] return [[s[0,0],s[0,1]],[s[1,0],s[1,1]]],t def getStressAndTangent3D(scene): Omega().stringToScene(scene) st = utils.getStressAndTangent(symmetry=True) s = st[0] s = .5*(s+s.transpose()) t = numpy.zeros((3,3,3,3)) t[0][0][0][0]=st[1][0,0] t[0][0][1][1]=t[1][1][0][0]=st[1][0,1] t[0][0][2][2]=t[2][2][0][0]=st[1][0,2] t[0][0][1][2]=t[0][0][2][1]=t[1][2][0][0]=t[2][1][0][0]=st[1][0,3] t[0][0][0][2]=t[0][0][2][0]=t[0][2][0][0]=t[2][0][0][0]=st[1][0,4] t[0][0][0][1]=t[0][0][1][0]=t[0][1][0][0]=t[1][0][0][0]=st[1][0,5] t[1][1][1][1]=st[1][1,1] t[1][1][2][2]=t[2][2][1][1]=st[1][1,2] t[1][1][1][2]=t[1][1][2][1]=t[1][2][1][1]=t[2][1][1][1]=st[1][1,3] t[1][1][0][2]=t[1][1][2][0]=t[0][2][1][1]=t[2][0][1][1]=st[1][1,4] t[1][1][0][1]=t[1][1][1][0]=t[0][1][1][1]=t[1][0][1][1]=st[1][1,5] t[2][2][2][2]=st[1][2,2] t[2][2][1][2]=t[2][2][2][1]=t[1][2][2][2]=t[2][1][2][2]=st[1][2,3] t[2][2][0][2]=t[2][2][2][0]=t[0][2][2][2]=t[2][0][2][2]=st[1][2,4] t[2][2][0][1]=t[2][2][1][0]=t[0][1][2][2]=t[1][0][2][2]=st[1][2,5] t[1][2][1][2]=t[1][2][2][1]=t[2][1][1][2]=t[2][1][2][1]=st[1][3,3] t[1][2][0][2]=t[1][2][2][0]=t[2][1][0][2]=t[2][1][2][0]=t[0][2][1][2]=t[2][0][1][2]=t[0][2][2][1]=t[2][0][2][1]=st[1][3,4] t[1][2][0][1]=t[1][2][1][0]=t[2][1][0][1]=t[2][1][1][0]=t[0][1][1][2]=t[1][0][1][2]=t[0][1][2][1]=t[1][0][2][1]=st[1][3,5] t[0][2][0][2]=t[2][0][0][2]=t[0][2][2][0]=t[2][0][2][0]=st[1][4,4] t[0][2][0][1]=t[0][2][1][0]=t[2][0][0][1]=t[2][0][1][0]=t[0][1][0][2]=t[1][0][0][2]=t[0][1][2][0]=t[1][0][2][0]=st[1][4,5] t[0][1][0][1]=t[0][1][1][0]=t[1][0][0][1]=t[1][0][1][0]=st[1][5,5] return [[s[0,0],s[0,1],s[0,2]],[s[1,0],s[1,1],s[1,2]],[s[2,0],s[2,1],s[2,2]]],t # utils.voidratio2D() is implemented in Shop.cpp # to get plane void ratio (for 2D test only) def getVoidRatio2D(scene): Omega().stringToScene(scene) zSize = Omega().cell.hSize[2,2] return utils.voidratio2D(zlen=zSize) def getEquivalentPorosity(scene): Omega().stringToScene(scene) zSize = Omega().cell.hSize[2,2] e = utils.voidratio2D(zlen=zSize) return e/(1.+e) def getVoidRatio3D(scene): Omega().stringToScene(scene) p = utils.porosity() return p/(1.0-p) # get average rotation of the particles within a packing def avgRotation2D(scene): Omega().stringToScene(scene) rot = 0.0 for b in Omega().bodies: rot += b.state.rot()[2] rot /= len(Omega().bodies) return rot def avgRotation3D(scene): Omega().stringToScene(scene) rot = utils.Vector3.Zero for b in Omega().bodies: rot += b.state.rot() rot /= len(Omega().bodies) return [rot[0],rot[1],rot[2]] """ Used for perturbation method only (2D), deprecated def getTangentOperator(param): if len(param)!=2: raise RuntimeError,"No. of param. should be exactly 2. 0: RVE scene; 1: perturbation." Omega().stringToScene(param[0]) pos = Omega().tags['id'] perturbation = param[1] t=[[[[0,0],[0,0]],[[0,0],[0,0]]],[[[0,0],[0,0]],[[0,0],[0,0]]]] ns=int(max(1e5*abs(perturbation),2)) stress0 = utils.getStress() stress0 = .5*(stress0+stress0.transpose()) #00 strain=utils.Matrix3(perturbation,0,0, 0,0,0, 0,0,0) Omega().cell.velGrad=strain/(ns*Omega().dt) Omega().run(ns,True) stress1 = utils.getStress() stress1 = .5*(stress1+stress1.transpose()) t[0][0][0][0]=(stress1[0]-stress0[0])/perturbation t[1][1][0][0]=(stress1[4]-stress0[4])/perturbation t[0][1][0][0]=t[1][0][0][0]=(stress1[1]-stress0[1])/perturbation Omega().stringToScene(param[0]) #11 strain=utils.Matrix3(0,0,0, 0,perturbation,0, 0,0,0) Omega().cell.velGrad=strain/(ns*Omega().dt) Omega().run(ns,True) stress1=utils.getStress() stress1=.5*(stress1+stress1.transpose()) t[0][0][1][1]=(stress1[0]-stress0[0])/perturbation t[1][1][1][1]=(stress1[4]-stress0[4])/perturbation t[0][1][1][1]=t[1][0][1][1]=(stress1[1]-stress0[1])/perturbation Omega().stringToScene(param[0]) #01 strain=utils.Matrix3(0,perturbation,0, 0,0,0, 0,0,0) Omega().cell.velGrad=strain/(ns*Omega().dt) Omega().run(ns,True) stress1=utils.getStress() stress1=.5*(stress1+stress1.transpose()) t[0][0][0][1]=(stress1[0]-stress0[0])/perturbation t[1][1][0][1]=(stress1[4]-stress0[4])/perturbation t[0][1][0][1]=t[1][0][0][1]=(stress1[1]-stress0[1])/perturbation Omega().stringToScene(param[0]) #10 strain=utils.Matrix3(0,0,0, perturbation,0,0, 0,0,0) Omega().cell.velGrad=strain/(ns*Omega().dt) Omega().run(ns,True) stress1=utils.getStress() stress1=.5*(stress1+stress1.transpose()) t[0][0][1][0]=(stress1[0]-stress0[0])/perturbation t[1][1][1][0]=(stress1[4]-stress0[4])/perturbation t[0][1][1][0]=t[1][0][1][0]=(stress1[1]-stress0[1])/perturbation #symmetrize t[0][0][1][1]=t[1][1][0][0]=(t[0][0][1][1]+t[1][1][0][0])*0.5 t[0][1][0][0]=t[1][0][0][0]=t[0][0][1][0]=t[0][0][0][1]=(t[0][1][0][0]*2+t[0][0][0][1]+t[0][0][1][0])*0.25 t[0][1][0][1]=t[0][1][1][0]=t[1][0][0][1]=t[1][0][1][0]=(t[0][1][1][0]+t[0][1][0][1])*0.5 t[0][1][1][1]=t[1][0][1][1]=t[1][1][0][1]=t[1][1][1][0]=(t[0][1][1][1]*2+t[1][1][0][1]+t[1][1][1][0])*0.25 return t """ trunk-2018.02b/py/WeightedAverage2d.cpp000066400000000000000000000036061324306050200176100ustar00rootroot00000000000000#include /* Tell whether point is inside polygon * * See _utils.cpp: pointInsidePolygon for docs and license. */ bool pyGaussAverage::pointInsidePolygon(const Vector2r& pt, const vector& vertices){ int i /*current node*/, j/*previous node*/; bool inside=false; int rows=(int)vertices.size(); const Real& testx=pt[0],testy=pt[1]; for(i=0,j=rows-1; itesty)!=(vy_j>testy)) && (testx < (vx_j-vx_i) * (testy-vy_i) / (vy_j-vy_i) + vx_i) ) inside=!inside; } return inside; } BOOST_PYTHON_MODULE(WeightedAverage2d) { boost::python::scope().attr("__doc__")="Smoothing (2d gauss-weighted average) for postprocessing scalars in 2d."; boost::python::class_("GaussAverage",boost::python::init >(boost::python::args("min","max","nCells","stDev","relThreshold"),"Create empty container for data, which can be added using add and later retrieved using avg.")) .def("add",&pyGaussAverage::addPt) .def("avg",&pyGaussAverage::avg) .def("avgPerUnitArea",&pyGaussAverage::avgPerUnitArea) .def("cellNum",&pyGaussAverage::cellNum) .def("cellSum",&pyGaussAverage::cellSum) .def("cellAvg",&pyGaussAverage::cellAvg) .add_property("stDev",&pyGaussAverage::stDev_get,&pyGaussAverage::stDev_set) .add_property("relThreshold",&pyGaussAverage::relThreshold_get,&pyGaussAverage::relThreshold_set) .add_property("clips",&pyGaussAverage::clips_get,&pyGaussAverage::clips_set) .add_property("data",&pyGaussAverage::data_get) .add_property("aabb",&pyGaussAverage::aabb_get) .add_property("nCells",&pyGaussAverage::nCells_get) .add_property("cellArea",&pyGaussAverage::cellArea) .add_property("cellDim",&pyGaussAverage::cellDim) ; }; trunk-2018.02b/py/__init__.py.in000066400000000000000000000065401324306050200163410ustar00rootroot00000000000000# 1. it tells python that yade (this directory) is package of python modules # see http://http://www.python.org/doc/2.1.3/tut/node8.html#SECTION008400000000000000000 # # 2. import the runtime namespace (will be populated from within c++) # """Common initialization core for yade. This file is executed when anything is imported from yade for the first time. It loads yade plugins and injects c++ class constructors to the __builtins__ (that might change in the future, though) namespace, making them available everywhere. """ import ctypes,sys try: import dl except ImportError: import DLFCN as dl if True: # not eval('${mono}'): # see file:///usr/share/doc/python2.6/html/library/sys.html#sys.setdlopenflags # and various web posts on the topic, e.g. # * http://gcc.gnu.org/faq.html#dso # * http://www.code-muse.com/blog/?p=58 # * http://wiki.python.org/moin/boost.python/CrossExtensionModuleDependencies sys.setdlopenflags(dl.RTLD_NOW | dl.RTLD_GLOBAL) else: sys.setdlopenflags(dl.RTLD_NOW) # important: initialize c++ by importing libstdc++ directly # see http://www.abclinuxu.cz/poradna/programovani/show/286322 # https://bugs.launchpad.net/bugs/490744 # # This is already fixed in systems, newer, than Ubuntu 10.04. anyway for # back-compatibility we will keep it. libstdcxx='${libstdcxx}' # substituted by scons try: ctypes.cdll.LoadLibrary(libstdcxx) except: pass # # now ready for c++ imports # find plugin directory import os,os.path import config # find plugins recursively plugins=[] # might add followlinks=True to os.walk, for python>=2.6 for root,dirs,files in os.walk(config.libDir): # skip any directory named 'dbg' in the non-debug build if 'dbg' in dirs: dirs.remove('dbg') for f in files: # ouch, ugly! #if not config.debug and '/dbg/' in root: continue if not (f.startswith('lib') and f.endswith('.so')): continue plugin=os.path.join(config.libDir,root,f) plugins.append(plugin) if 'YADE_DEBUG' in os.environ: print 'The following plugins will be loaded:' for p in plugins: print '\t'+p # c++ initialization code import boot boot.initialize(plugins,config.confDir) import system # create proxies for deprecated classes deprecatedTypes=system.cxxCtorsDict() # insert those in the module namespace globals().update(deprecatedTypes) # declare what should be imported for "from yade import *" __all__=deprecatedTypes.keys()+['O'] from minieigen import * from wrapper import * # import to separate namespace to be able to call dir(miniEigen) and dir(wrapper) below import wrapper # out-of-class docstrings for some classes import _extraDocs # import a few "important" modules along with * import utils # some others? __all__+=['utils',]+dir(wrapper) # make O and wrapper.Omega part of __builtin__ (equvialent to global) import __builtin__ __builtin__.__dict__['O'] = O __builtin__.__dict__['Omega'] = wrapper.Omega # define a default list of engine O.engines=[ ForceResetter(), InsertionSortCollider([Bo1_Sphere_Aabb(),Bo1_Facet_Aabb(),Bo1_Box_Aabb()],label="collider"), InteractionLoop( [Ig2_Sphere_Sphere_ScGeom(),Ig2_Facet_Sphere_ScGeom(),Ig2_Box_Sphere_ScGeom()], [Ip2_FrictMat_FrictMat_FrictPhys()], #for linear model only [Law2_ScGeom_FrictPhys_CundallStrack(label="law")], #for linear model only label="interactionLoop" ), GlobalStiffnessTimeStepper(timeStepUpdateInterval=10,label="timeStepper"), NewtonIntegrator(label="newton") ] trunk-2018.02b/py/_extraDocs.py000066400000000000000000000553751324306050200163020ustar00rootroot00000000000000# encoding: utf-8 # 2010 © Václav Šmilauer # # This module is imported at startup. It is meant to update # docstrings of wrapper classes, which are not practical to document # in the c++ source itself, due to the necessity of writing # \n for newlines and having everything as "string". # # PLEASE: # # 1. provide at least brief description of the class # in the c++ code (for those who read it) and # # 2. Add something like # # "Full documentation of this class is in py/_extraDocs.py." # # to the c++ documentation. import wrapper # Update docstring of your class/function like this: # # wrapper.YourClass.__doc__=""" # This class is documented from _extraDocs.py. Yay! # # .. note:: # The c++ documentation will be overwritten by this string. # """ wrapper.TriaxialTest.__doc__=''' Create a scene for triaxal test. **Introduction** Yade includes tools to simulate triaxial tests on particles assemblies. This pre-processor (and variants like e.g. :yref:`CapillaryTriaxialTest`) illustrate how to use them. It generates a scene which will - by default - go through the following steps : * generate random loose packings in a parallelepiped. * compress the packing isotropicaly, either squeezing the packing between moving rigid boxes or expanding the particles while boxes are fixed (depending on flag :yref:`internalCompaction`). The confining pressure in this stage is defined via :yref:`sigmaIsoCompaction`. * when the packing is dense and stable, simulate a loading path and get the mechanical response as a result. The default loading path corresponds to a constant lateral stress (:yref:`sigmaLateralConfinement`) in 2 directions and constant strain rate on the third direction. This default loading path is performed when the flag :yref:`autoCompressionActivation` it ``True``, otherwise the simulation stops after isotropic compression. Different loading paths might be performed. In order to define them, the user can modify the flags found in engine :yref:`TriaxialStressController` at any point in the simulation (in c++). If ``TriaxialStressController.wall_X_activated`` is ``true`` boundary X is moved automatically to maintain the defined stress level *sigmaN* (see axis conventions below). If ``false`` the boundary is not controlled by the engine at all. In that case the user is free to prescribe fixed position, constant velocity, or more complex conditions. .. note:: *Axis conventions.* Boundaries perpendicular to the *x* axis are called "left" and "right", *y* corresponds to "top" and "bottom", and axis *z* to "front" and "back". In the default loading path, strain rate is assigned along *y*, and constant stresses are assigned on *x* and *z*. **Essential engines** #. The :yref:`TriaxialCompressionEngine` is used for controlling the state of the sample and simulating loading paths. :yref:`TriaxialCompressionEngine` inherits from :yref:`TriaxialStressController`, which computes stress- and strain-like quantities in the packing and maintain a constant level of stress at each boundary. :yref:`TriaxialCompressionEngine` has few more members in order to impose constant strain rate and control the transition between isotropic compression and triaxial test. Transitions are defined by changing some flags of the :yref:`TriaxialStressController`, switching from/to imposed strain rate to/from imposed stress. #. The class :yref:`TriaxialStateRecorder` is used to write to a file the history of stresses and strains. #. :yref:`TriaxialTest` is using :yref:`GlobalStiffnessTimeStepper` to compute an appropriate $\Dt$ for the numerical scheme. .. note:: ``TriaxialStressController::ComputeUnbalancedForce`` returns a value that can be useful for evaluating the stability of the packing. It is defined as (mean force on particles)/(mean contact force), so that it tends to 0 in a stable packing. This parameter is checked by :yref:`TriaxialCompressionEngine` to switch from one stage of the simulation to the next one (e.g. stop isotropic confinment and start axial loading) .. admonition:: Frequently Asked Questions #. How is generated the packing? How to change particles sizes distribution? Why do I have a message "Exceeded 3000 tries to insert non-overlapping sphere? The initial positioning of spheres is done by generating random (x,y,z) in a box and checking if a sphere of radius R (R also randomly generated with respect to a uniform distribution between mean*(1-std_dev) and mean*(1+std_dev) can be inserted at this location without overlaping with others. If the sphere overlaps, new (x,y,z)'s are generated until a free position for the new sphere is found. This explains the message you have: after 3000 trial-and-error, the sphere couldn't be placed, and the algorithm stops. You get the message above if you try to generate an initialy dense packing, which is not possible with this algorithm. It can only generate clouds. You should keep the default value of porosity (n~0.7), or even increase if it is still to low in some cases. The dense state will be obtained in the second step (compaction, see below). #. How is the compaction done, what are the parameters :yref:`maxWallVelocity` and :yref:`finalMaxMultiplier`? Compaction is done #. by moving rigid boxes or #. by increasing the sizes of the particles (decided using the option :yref:`internalCompaction` ⇒ size increase). Both algorithm needs numerical parameters to prevent instabilities. For instance, with the method (1) :yref:`maxWallVelocity` is the maximum wall velocity, with method (2) :yref:`finalMaxMultiplier` is the max value of the multiplier applied on sizes at each iteration (always something like 1.00001). #. During the simulation of triaxial compression test, the wall in one direction moves with an increment of strain while the stresses in other two directions are adjusted to :yref:`sigma_iso`. How the stresses in other directions are maintained constant to :yref:`sigma_iso`? What is the mechanism? Where is it implemented in Yade? The control of stress on a boundary is based on the total stiffness *K* of all contacts between the packing and this boundary. In short, at each step, displacement=stress_error/K. This algorithm is implemented in :yref:`TriaxialStressController`, and the control itself is in ``TriaxialStressController::ControlExternalStress``. The control can be turned off independently for each boundary, using the flags ``wall_XXX_activated``, with *XXX*\ ∈{*top*, *bottom*, *left*, *right*, *back*, *front*}. The imposed sress is a unique value (:yref:`sigma_iso`) for all directions if :yref:`TriaxialStressController.isAxisymetric`, or 3 independent values :yref:`sigma1`, :yref:`sigma2`, :yref:`sigma3`. #. Which value of friction angle do you use during the compaction phase of the Triaxial Test? The friction during the compaction (whether you are using the expansion method or the compression one for the specimen generation) can be anything between 0 and the final value used during the Triaxial phase. Note that higher friction than the final one would result in volumetric collapse at the beginning of the test. The purpose of using a different value of friction during this phase is related to the fact that the final porosity you get at the end of the sample generation essentially depends on it as well as on the assumed Particle Size Distribution. Changing the initial value of friction will get to a different value of the final porosity. #. Which is the aim of the ``bool isRadiusControlIteration``? This internal variable (updated automatically) is true each *N* timesteps (with *N*\ =\ :yref:`radiusControlInterval`). For other timesteps, there is no expansion. Cycling without expanding is just a way to speed up the simulation, based on the idea that 1% increase each 10 iterations needs less operations than 0.1% at each iteration, but will give similar results. #. How comes the unbalanced force reaches a low value only after many timesteps in the compaction phase? The value of unbalanced force (dimensionless) is expected to reach low value (i.e. identifying a static-equilibrium condition for the specimen) only at the end of the compaction phase. The code is not aiming at simulating a quasistatic isotropic compaction process, it is only giving a stable packing at the end of it. ''' wrapper.Peri3dController.__doc__=r''' Class for controlling independently all 6 components of "engineering" :yref:`stress` and :yref:`strain` of periodic :yref:`Cell`. :yref:`goal` are the goal values, while :yref:`stressMask` determines which components prescribe stress and which prescribe strain. If the strain is prescribed, appropriate strain rate is directly applied. If the stress is prescribed, the strain predictor is used: from stress values in two previous steps the value of strain rate is prescribed so as the value of stress in the next step is as close as possible to the ideal one. Current algorithm is extremly simple and probably will be changed in future, but is roboust enough and mostly works fine. Stress error (difference between actual and ideal stress) is evaluated in current and previous steps ($\mathrm{d}\sigma_i,\mathrm{d}\sigma_{i-1}$). Linear extrapolation is used to estimate error in the next step .. math:: \mathrm{d}\sigma_{i+1}=2\mathrm{d}\sigma_i - \mathrm{d}\sigma_{i-1} According to this error, the strain rate is modified by :yref:`mod` parameter .. math:: \mathrm{d}\sigma_{i+1}\left\{\begin{array}{c} >0 \rightarrow \dot{\varepsilon}_{i+1} = \dot{\varepsilon}_i - \max(\mathrm{abs}(\dot{\boldsymbol{\varepsilon}}_i))\cdot\mathrm{mod} \\ <0 \rightarrow \dot{\varepsilon}_{i+1} = \dot{\varepsilon}_i + \max(\mathrm{abs}(\dot{\boldsymbol{\varepsilon}}_i))\cdot\mathrm{mod} \end{array}\right. According to this fact, the prescribed stress will (almost) never have exact prescribed value, but the difference would be very small (and decreasing for increasing :yref:`nSteps`. This approach works good if one of the dominant strain rates is prescribed. If all stresses are prescribed or if all goal strains is prescribed as zero, a good estimation is needed for the first step, therefore the compliance matrix is estimated (from user defined estimations of macroscopic material parameters :yref:`youngEstimation` and :yref:`poissonEstimation`) and respective strain rates is computed form prescribed stress rates and compliance matrix (the estimation of compliance matrix could be computed autamatically avoiding user inputs of this kind). The simulation on rotated periodic cell is also supported. Firstly, the `polar decomposition `_ is performed on cell's transformation matrix :yref:`trsf` $\mathcal{T}=\mat{U}\mat{P}$, where $\mat{U}$ is orthogonal (unitary) matrix representing rotation and $\mat{P}$ is a positive semi-definite Hermitian matrix representing strain. A logarithm of $\mat{P}$ should be used to obtain realistic values at higher strain values (not implemented yet). A prescribed strain increment in global coordinates $\mathrm{d}t\cdot\dot{\boldsymbol{\varepsilon}}$ is properly rotated to cell's local coordinates and added to $\mat{P}$ .. math:: \mat{P}_{i+1}=\mat{P}+\mat{U}^{\mathsf{T}}\mathrm{d}t\cdot\dot{\boldsymbol{\varepsilon}}\mat{U} The new value of :yref:`trsf` is computed at $\mat{T}_{i+1}=\mat{UP}_{i+1}$. From current and next :yref:`trsf` the cell's velocity gradient :yref:`velGrad` is computed (according to its definition) as .. math:: \mat{V} = (\mat{T}_{i+1}\mat{T}^{-1}-\mat{I})/\mathrm{d}t Current implementation allow user to define independent loading "path" for each prescribed component. i.e. define the prescribed value as a function of time (or :yref:`progress` or steps). See :yref:`Paths`. Examples :ysrc:`examples/test/peri3dController_example1.py` and :ysrc:`examples/test/peri3dController_triaxialCompression.py` explain usage and inputs of Peri3dController, :ysrc:`examples/test/peri3dController_shear.py` is an example of using shear components and also simulation on rotated cell. ''' wrapper.Ig2_Sphere_Sphere_L3Geom.__doc__=r'''Functor for computing incrementally configuration of 2 :yref:`Spheres` stored in :yref:`L3Geom`; the configuration is positioned in global space by local origin $\vec{c}$ (contact point) and rotation matrix $\mat{T}$ (orthonormal transformation matrix), and its degrees of freedom are local displacement $\vec{u}$ (in one normal and two shear directions); with :yref:`Ig2_Sphere_Sphere_L6Geom` and :yref:`L6Geom`, there is additionally $\vec{\phi}$. The first row of $\mat{T}$, i.e. local $x$-axis, is the contact normal noted $\vec{n}$ for brevity. Additionally, quasi-constant values of $\vec{u}_0$ (and $\vec{\phi}_0$) are stored as shifted origins of $\vec{u}$ (and $\vec{\phi}$); therefore, current value of displacement is always $\curr{\vec{u}}-\vec{u}_0$. Suppose two spheres with radii $r_i$, positions $\vec{x}_i$, velocities $\vec{v}_i$, angular velocities $\vec{\omega}_i$. When there is not yet contact, it will be created if $u_N=|\curr{\vec{x}}_2-\curr{\vec{x}}_1|-|f_d|(r_1+r2)<0$, where $f_d$ is :yref:`distFactor` (sometimes also called \`\`interaction radius''). If $f_d>0$, then $\vec{u}_{0x}$ will be initalized to $u_N$, otherwise to 0. In another words, contact will be created if spheres enlarged by $|f_d|$ touch, and the \`\`equilibrium distance'' (where $\vec{u}_x-\vec{u}-{0x}$ is zero) will be set to the current distance if $f_d$ is positive, and to the geometrically-touching distance if negative. Local axes (rows of $\mat{T}$) are initially defined as follows: * local $x$-axis is $\vec{n}=\vec{x}_l=\normalized{\vec{x}_2-\vec{x}_1}$; * local $y$-axis positioned arbitrarily, but in a deterministic manner: aligned with the $xz$ plane (if $\vec{n}_y<\vec{n}_z$) or $xy$ plane (otherwise); * local $z$-axis $\vec{z}_l=\vec{x}_l\times\vec{y}_l$. If there has already been contact between the two spheres, it is updated to keep track of rigid motion of the contact (one that does not change mutual configuration of spheres) and mutual configuration changes. Rigid motion transforms local coordinate system and can be decomposed in rigid translation (affecting $\vec{c}$), and rigid rotation (affecting $\mat{T}$), which can be split in rotation $\vec{o}_r$ perpendicular to the normal and rotation $\vec{o}_t$ (\`\`twist'') parallel with the normal: .. math:: \pprev{\vec{o}_r}=\prev{\vec{n}}\times\curr{\vec{n}}. Since velocities are known at previous midstep ($t-\Dt/2$), we consider mid-step normal .. math:: \pprev{\vec{n}}=\frac{\prev{\vec{n}}+\curr{\vec{n}}}{2}. For the sake of numerical stability, $\pprev{\vec{n}}$ is re-normalized after being computed, unless prohibited by :yref:`approxMask`. If :yref:`approxMask` has the appropriate bit set, the mid-normal is not compute, and we simply use $\pprev{\vec{n}}\approx\prev{\vec{n}}$. Rigid rotation parallel with the normal is .. math:: \pprev{\vec{o}_t}=\pprev{\vec{n}}\left(\pprev{\vec{n}}\cdot\frac{\pprev{\vec{\omega}}_1+\pprev{\vec{\omega}}_2}{2}\right)\Dt. *Branch vectors* $\vec{b}_1$, $\vec{b}_2$ (connecting $\curr{\vec{x}}_1$, $\curr{\vec{x}}_2$ with $\curr{\vec{c}}$ are computed depending on :yref:`noRatch` (see :yref:`here`). .. math:: :nowrap: \begin{align*} \vec{b}_1&=\begin{cases} r_1 \curr{\vec{n}} & \mbox{with \texttt{noRatch}} \\ \curr{\vec{c}}-\curr{\vec{x}}_1 & \mbox{otherwise} \end{cases} \\ \vec{b}_2&=\begin{cases} -r_2\curr{\vec{n}} & \mbox{with \texttt{noRatch}} \\ \curr{\vec{c}}-\curr{\vec{x}}_2 & \mbox{otherwise} \end{cases} \\ \end{align*} Relative velocity at $\curr{\vec{c}}$ can be computed as .. math:: \pprev{\vec{v}_r}=(\pprev{\vec{\tilde{v}}_2}+\vec{\omega}_2\times\vec{b}_2)-(\vec{v}_1+\vec{\omega}_1\times\vec{b}_1) where $\vec{\tilde{v}}_2$ is $\vec{v}_2$ without mean-field velocity gradient in periodic boundary conditions (see :yref:`Cell.homoDeform`). In the numerial implementation, the normal part of incident velocity is removed (since it is computed directly) with $\pprev{\vec{v}_{r2}}=\pprev{\vec{v}_r}-(\pprev{\vec{n}}\cdot\pprev{\vec{v}_r})\pprev{\vec{n}}$. Any vector $\vec{a}$ expressed in global coordinates transforms during one timestep as .. math:: \curr{\vec{a}}=\prev{\vec{a}}+\pprev{\vec{v}_r}\Dt-\prev{\vec{a}}\times\pprev{\vec{o}_r}-\prev{\vec{a}}\times{\pprev{\vec{t}_r}} where the increments have the meaning of relative shear, rigid rotation normal to $\vec{n}$ and rigid rotation parallel with $\vec{n}$. Local coordinate system orientation, rotation matrix $\mat{T}$, is updated by rows, i.e. .. math:: \curr{\mat{T}}=\begin{pmatrix} \curr{\vec{n}_x} & \curr{\vec{n}_y} & \curr{\vec{n}_z} \\ \multicolumn{3}{c}{\prev{\mat{T}_{1,\bullet}}-\prev{\mat{T}_{1,\bullet}}\times\pprev{\vec{o}_r}-\prev{\mat{T}_{1,\bullet}}\times\pprev{\vec{o}_t}} \\ \multicolumn{3}{c}{\prev{\mat{T}_{2,\bullet}}-\prev{\mat{T}_{2,\bullet}}\times\pprev{\vec{o}_r}-\prev{\mat{T}_{,\bullet}}\times\pprev{\vec{o}_t}} \end{pmatrix} This matrix is re-normalized (unless prevented by :yref:`approxMask`) and mid-step transformation is computed using quaternion spherical interpolation as .. math:: \pprev{\mat{T}}=\mathrm{Slerp}\,\left(\prev{\mat{T}};\curr{\mat{T}};t=1/2\right). Depending on :yref:`approxMask`, this computation can be avoided by approximating $\pprev{\mat{T}}=\prev{\mat{T}}$. Finally, current displacement is evaluated as .. math:: \curr{\vec{u}}=\prev{u}+\pprev{\mat{T}}\pprev{\vec{v}_r}\Dt. For the normal component, non-incremental evaluation is preferred, giving .. math:: \curr{\vec{u}_x}=|\curr{\vec{x}_2}-\curr{\vec{x}_1}|-(r_1+r_2) If this functor is called for :yref:`L6Geom`, local rotation is updated as .. math:: \curr{\vec{\phi}}=\prev{\vec{\phi}}+\pprev{\mat{T}}\Dt(\vec{\omega}_2-\vec{\omega}_1) .. note: TODO: ``distFactor`` is not yet implemented as described above; some formulas mix values at different times, should be checked carefully. ''' wrapper.LawTester.__doc__='''Prescribe and apply deformations of an interaction in terms of local mutual displacements and rotations. The loading path is specified either using :yref:`path` (as sequence of 6-vectors containing generalized displacements $u_x$, $u_y$, $u_z$, $\phi_x$, $\phi_y$, $\phi_z$) or :yref:`disPath` ($u_x$, $u_y$, $u_z$) and :yref:`rotPath` ($\phi_x$, $\phi_y$, $\phi_z$). Time function with time values (step numbers) corresponding to points on loading path is given by :yref:`pathSteps`. Loading values are linearly interpolated between given loading path points, and starting zero-value (the initial configuration) is assumed for both :yref:`path` and :yref:`pathSteps`. :yref:`hooks` can specify python code to run when respective point on the path is reached; when the path is finished, :yref:`doneHook` will be run. LawTester should be placed between :yref:`InteractionLoop` and :yref:`NewtonIntegrator` in the simulation loop, since it controls motion via setting linear/angular velocities on particles; those velocities are integrated by :yref:`NewtonIntegrator` to yield an actual position change, which in turn causes :yref:`IGeom` to be updated (and :yref:`contact law` applied) when :yref:`InteractionLoop` is executed. Constitutive law generating forces on particles will not affect prescribed particle motion, since both particles have all :yref:`DoFs blocked` when first used with LawTester. LawTester uses, as much as possible, :yref:`IGeom` to provide useful data (such as local coordinate system), but is able to compute those independently if absent in the respective :yref:`IGeom`: =================== ===== ================================== :yref:`IGeom` #DoFs LawTester support level =================== ===== ================================== :yref:`L3Geom` 3 full :yref:`L6Geom` 6 full :yref:`ScGeom` 3 emulate local coordinate system :yref:`ScGeom6D` 6 emulate local coordinate system =================== ===== ================================== Depending on :yref:`IGeom`, 3 ($u_x$, $u_y$, $u_z$) or 6 ($u_x$, $u_y$, $u_z$, $\phi_x$, $\phi_y$, $\phi_z$) degrees of freedom (DoFs) are controlled with LawTester, by prescribing linear and angular velocities of both particles in contact. All DoFs controlled with LawTester are orthogonal (fully decoupled) and are controlled independently. When 3 DoFs are controlled, :yref:`rotWeight` controls whether local shear is applied by moving particle on arc around the other one, or by rotating without changing position; although such rotation induces mutual rotation on the interaction, it is ignored with :yref:`IGeom` with only 3 DoFs. When 6 DoFs are controlled, only arc-displacement is applied for shear, since otherwise mutual rotation would occur. :yref:`idWeight` distributes prescribed motion between both particles (resulting local deformation is the same if ``id1`` is moved towards ``id2`` or ``id2`` towards ``id1``). This is true only for $u_x$, $u_y$, $u_z$, $\phi_x$ however ; bending rotations $\phi_y$, $\phi_z$ are nevertheless always distributed regardless of ``idWeight`` to both spheres in inverse proportion to their radii, so that there is no shear induced. LawTester knows current contact deformation from 2 sources: from its own internal data (which are used for prescribing the displacement at every step), which can be accessed in :yref:`uTest`, and from :yref:`IGeom` itself (depending on which data it provides), which is stored in :yref:`uGeom`. These two values should be identical (disregarding numerical percision), and it is a way to test whether :yref:`IGeom` and related functors compute what they are supposed to compute. LawTester-operated interactions can be rendered with :yref:`GlExtra_LawTester` renderer. See :ysrc:`scripts/test/law-test.py` for an example. ''' trunk-2018.02b/py/_polyhedra_utils.cpp000066400000000000000000000522451324306050200177000ustar00rootroot00000000000000// © 2013 Jan Elias, http://www.fce.vutbr.cz/STM/elias.j/, elias.j@fce.vutbr.cz // https://www.vutbr.cz/www_base/gigadisk.php?i=95194aa9a #ifdef YADE_CGAL #include "pkg/dem/Polyhedra.hpp" #include #include #include #include #include #include #include namespace py = boost::python; //********************************************************************************** //print polyhedron in basic position void PrintPolyhedra(const shared_ptr& shape){ Polyhedra* A = static_cast(shape.get()); Polyhedron PA = A->GetPolyhedron(); A->Initialize(); PrintPolyhedron(PA); } //********************************************************************************** //print polyhedron in actual position void PrintPolyhedraActualPos(const shared_ptr& cm1,const State& state1){ const Se3r& se3=state1.se3; Polyhedra* A = static_cast(cm1.get()); A->Initialize(); //move and rotate CGAL structure Polyhedron Matrix3r rot_mat = (se3.orientation).toRotationMatrix(); Vector3r trans_vec = se3.position; Transformation t_rot_trans(rot_mat(0,0),rot_mat(0,1),rot_mat(0,2), trans_vec[0],rot_mat(1,0),rot_mat(1,1),rot_mat(1,2),trans_vec[1],rot_mat(2,0),rot_mat(2,1),rot_mat(2,2),trans_vec[2],1.); Polyhedron PA = A->GetPolyhedron(); std::transform( PA.points_begin(), PA.points_end(), PA.points_begin(), t_rot_trans); PrintPolyhedron(PA); } //********************************************************************************** //test of polyhedron intersection callable from python shell bool do_Polyhedras_Intersect(const shared_ptr& cm1,const shared_ptr& cm2,const State& state1,const State& state2){ const Se3r& se31=state1.se3; const Se3r& se32=state2.se3; Polyhedra* A = static_cast(cm1.get()); Polyhedra* B = static_cast(cm2.get()); //move and rotate 1st the CGAL structure Polyhedron Matrix3r rot_mat = (se31.orientation).toRotationMatrix(); Vector3r trans_vec = se31.position; Transformation t_rot_trans(rot_mat(0,0),rot_mat(0,1),rot_mat(0,2), trans_vec[0],rot_mat(1,0),rot_mat(1,1),rot_mat(1,2),trans_vec[1],rot_mat(2,0),rot_mat(2,1),rot_mat(2,2),trans_vec[2],1.); Polyhedron PA = A->GetPolyhedron(); std::transform( PA.points_begin(), PA.points_end(), PA.points_begin(), t_rot_trans); //move and rotate 2st the CGAL structure Polyhedron rot_mat = (se32.orientation).toRotationMatrix(); trans_vec = se32.position; t_rot_trans = Transformation(rot_mat(0,0),rot_mat(0,1),rot_mat(0,2), trans_vec[0],rot_mat(1,0),rot_mat(1,1),rot_mat(1,2),trans_vec[1],rot_mat(2,0),rot_mat(2,1),rot_mat(2,2),trans_vec[2],1.); Polyhedron PB = B->GetPolyhedron(); std::transform( PB.points_begin(), PB.points_end(), PB.points_begin(), t_rot_trans); //calculate plane equations std::transform( PA.facets_begin(), PA.facets_end(), PA.planes_begin(),Plane_equation()); std::transform( PB.facets_begin(), PB.facets_end(), PB.planes_begin(),Plane_equation()); //call test return do_intersect(PA,PB); } //********************************************************************************** //determination of critical time step for polyhedrons & spheres (just rough estimation) Real PWaveTimeStep(){ const shared_ptr _rb=shared_ptr(); shared_ptr rb=(_rb?_rb:Omega::instance().getScene()); Real dt=std::numeric_limits::infinity(); FOREACH(const shared_ptr& b, *rb->bodies){ if(!b || !b->material || !b->shape) continue; shared_ptr s=YADE_PTR_DYN_CAST(b->shape); shared_ptr p=YADE_PTR_DYN_CAST(b->shape); if(!s && !p) continue; if(!p){ //spheres shared_ptr ebp=YADE_PTR_DYN_CAST(b->material); if(!ebp) continue; Real density=b->state->mass/((4./3.)*Mathr::PI*pow(s->radius,3)); dt=min(dt,s->radius/sqrt(ebp->young/density)); }else{ //polyhedrons shared_ptr ebp=YADE_PTR_DYN_CAST(b->material); if(!ebp) continue; Real density=b->state->mass/p->GetVolume(); //get equivalent radius and use same equation as for sphere Real equi_radius=pow(p->GetVolume()/((4./3.)*Mathr::PI),1./3.); dt=min(dt,equi_radius/sqrt(ebp->young*equi_radius/density)); } } if (dt==std::numeric_limits::infinity()) { dt = 1.0; LOG_WARN("PWaveTimeStep has not found any suitable spherical or polyhedral body to calculate dt. dt is set to 1.0"); } return dt; } //********************************************************************************** //returns approximate sieve size of polyhedron Real SieveSize(const shared_ptr& cm1){ Polyhedra* A = static_cast(cm1.get()); double phi = M_PI/4.; double x,y; double minx = 0, maxx = 0, miny = 0, maxy = 0; for (vector::iterator i=A->v.begin(); i!=A->v.end(); ++i){ x = cos(phi)*(*i)[1] + sin(phi)*(*i)[2]; y = -sin(phi)*(*i)[1] + cos(phi)*(*i)[2]; minx = min(minx,x); maxx = max(maxx,x); miny = min(miny,y); maxy = max(maxy,y); } return max(maxx-minx, maxy-miny); } //********************************************************************************** //returns approximate size of polyhedron Vector3r SizeOfPolyhedra(const shared_ptr& cm1){ Polyhedra* A = static_cast(cm1.get()); double minx = 0, maxx = 0, miny = 0, maxy = 0, minz = 0, maxz = 0; for (vector::iterator i=A->v.begin(); i!=A->v.end(); ++i){ minx = min(minx,(*i)[0]); maxx = max(maxx,(*i)[0]); miny = min(miny,(*i)[1]); maxy = max(maxy,(*i)[1]); minz = min(minz,(*i)[2]); maxz = max(maxz,(*i)[2]); } return Vector3r(maxx-minx, maxy-miny, maxz-minz); } //********************************************************************************** //save sieve curve points into a file void SieveCurve(){ const shared_ptr _rb=shared_ptr(); shared_ptr rb=(_rb?_rb:Omega::instance().getScene()); std::vector< std::pair > sieve_volume; double total_volume = 0; FOREACH(const shared_ptr& b, *rb->bodies){ if(!b || !b->shape) continue; shared_ptr p=YADE_PTR_DYN_CAST(b->shape); if(p){ sieve_volume.push_back(std::pair(SieveSize(p),p->GetVolume())); total_volume += p->GetVolume(); } } std::sort(sieve_volume.begin(), sieve_volume.end()) ; double cumul_vol = 0; ofstream myfile; myfile.open ("sieve_curve.dat"); for(std::vector< std::pair > :: iterator i = sieve_volume.begin(); i != sieve_volume.end(); ++i) { cumul_vol += i->second/total_volume; myfile << i->first << "\t" << cumul_vol << endl; } myfile.close(); } //********************************************************************************** //save size of polyhedrons into a file void SizeRatio(){ const shared_ptr _rb=shared_ptr(); shared_ptr rb=(_rb?_rb:Omega::instance().getScene()); ofstream myfile; myfile.open ("sizes.dat"); FOREACH(const shared_ptr& b, *rb->bodies){ if(!b || !b->shape) continue; shared_ptr p=YADE_PTR_DYN_CAST(b->shape); if(p){ myfile << SizeOfPolyhedra(p) << endl; } } myfile.close(); } //********************************************************************************** //returns max coordinates Vector3r MaxCoord(const shared_ptr& cm1,const State& state1){ const Se3r& se3=state1.se3; Polyhedra* A = static_cast(cm1.get()); //move and rotate CGAL structure Polyhedron Matrix3r rot_mat = (se3.orientation).toRotationMatrix(); Vector3r trans_vec = se3.position; Transformation t_rot_trans(rot_mat(0,0),rot_mat(0,1),rot_mat(0,2), trans_vec[0],rot_mat(1,0),rot_mat(1,1),rot_mat(1,2),trans_vec[1],rot_mat(2,0),rot_mat(2,1),rot_mat(2,2),trans_vec[2],1.); Polyhedron PA = A->GetPolyhedron(); std::transform( PA.points_begin(), PA.points_end(), PA.points_begin(), t_rot_trans); Vector3r maxccord = trans_vec; for(Polyhedron::Vertex_iterator vi = PA.vertices_begin(); vi != PA.vertices_end(); ++vi){ if (vi->point()[0]>maxccord[0]) maxccord[0]=vi->point()[0]; if (vi->point()[1]>maxccord[1]) maxccord[1]=vi->point()[1]; if (vi->point()[2]>maxccord[2]) maxccord[2]=vi->point()[2]; } return maxccord; } //********************************************************************************** //returns min coordinates Vector3r MinCoord(const shared_ptr& cm1,const State& state1){ const Se3r& se3=state1.se3; Polyhedra* A = static_cast(cm1.get()); //move and rotate CGAL structure Polyhedron Matrix3r rot_mat = (se3.orientation).toRotationMatrix(); Vector3r trans_vec = se3.position; Transformation t_rot_trans(rot_mat(0,0),rot_mat(0,1),rot_mat(0,2), trans_vec[0],rot_mat(1,0),rot_mat(1,1),rot_mat(1,2),trans_vec[1],rot_mat(2,0),rot_mat(2,1),rot_mat(2,2),trans_vec[2],1.); Polyhedron PA = A->GetPolyhedron(); std::transform( PA.points_begin(), PA.points_end(), PA.points_begin(), t_rot_trans); Vector3r minccord = trans_vec; for(Polyhedron::Vertex_iterator vi = PA.vertices_begin(); vi != PA.vertices_end(); ++vi){ if (vi->point()[0]point()[0]; if (vi->point()[1]point()[1]; if (vi->point()[2]point()[2]; } return minccord; } //********************************************************************************** //generate "packing" of non-overlapping polyhedrons vector fillBox_cpp(Vector3r minCoord, Vector3r maxCoord, Vector3r sizemin, Vector3r sizemax, Vector3r ratio, int seed, shared_ptr mat){ vector v; Polyhedra trialP; Polyhedron trial, trial_moved; srand(seed); int it = 0; vector polyhedrons; vector > vv; Vector3r position; bool intersection; int count = 0; bool fixed_ratio = 0; if (ratio[0] > 0 && ratio[1] > 0 && ratio[2]>0){ fixed_ratio = 1; sizemax[0] = min(min(sizemax[0]/ratio[0], sizemax[1]/ratio[1]), sizemax[2]/ratio[2]); sizemin[0] = max(max(sizemin[0]/ratio[0], sizemin[1]/ratio[1]), sizemin[2]/ratio[2]); } //it - number of trials to make packing possibly more/less dense Vector3r random_size; while (it<1000){ it = it+1; if (it == 1){ trialP.Clear(); trialP.seed = rand(); if(fixed_ratio) trialP.size = (rand()*(sizemax[0]-sizemin[0])/RAND_MAX + sizemin[0])*ratio; else trialP.size = Vector3r(rand()*(sizemax[0]-sizemin[0]),rand()*(sizemax[1]-sizemin[1]),rand()*(sizemax[2]-sizemin[2]))/RAND_MAX + sizemin; trialP.Initialize(); trial = trialP.GetPolyhedron(); Matrix3r rot_mat = (trialP.GetOri()).toRotationMatrix(); Transformation t_rot(rot_mat(0,0),rot_mat(0,1),rot_mat(0,2),rot_mat(1,0),rot_mat(1,1),rot_mat(1,2),rot_mat(2,0),rot_mat(2,1),rot_mat(2,2),1.); std::transform( trial.points_begin(), trial.points_end(), trial.points_begin(), t_rot); } position = Vector3r(rand()*(maxCoord[0]-minCoord[0]),rand()*(maxCoord[1]-minCoord[1]),rand()*(maxCoord[2]-minCoord[2]))/RAND_MAX + minCoord; //move CGAL structure Polyhedron Transformation transl(CGAL::TRANSLATION, ToCGALVector(position)); trial_moved = trial; std::transform( trial_moved.points_begin(), trial_moved.points_end(), trial_moved.points_begin(), transl); //calculate plane equations std::transform( trial_moved.facets_begin(), trial_moved.facets_end(), trial_moved.planes_begin(),Plane_equation()); intersection = false; //call test with boundary for(Polyhedron::Vertex_iterator vi = trial_moved.vertices_begin(); (vi != trial_moved.vertices_end()) && (!intersection); vi++){ intersection = (vi->point().x()point().x()>maxCoord[0]) || (vi->point().y()point().y()>maxCoord[1]) || (vi->point().z()point().z()>maxCoord[2]); } //call test with other polyhedrons for(vector::iterator a = polyhedrons.begin(); (a != polyhedrons.end()) && (!intersection); a++){ intersection = do_intersect(*a,trial_moved); if (intersection) break; } if (!intersection){ polyhedrons.push_back(trial_moved); v.clear(); for(Polyhedron::Vertex_iterator vi = trial_moved.vertices_begin(); vi != trial_moved.vertices_end(); vi++){ v.push_back(FromCGALPoint(vi->point())); } vv.push_back(v); it = 0; count ++; } } cout << "generated " << count << " polyhedrons"<< endl; //can't be used - no information about material Scene* scene=Omega::instance().getScene().get(); for(vector >::iterator p=vv.begin(); p!=vv.end(); ++p){ shared_ptr BP = NewPolyhedra(*p, mat); BP->shape->color = Vector3r(double(rand())/RAND_MAX,double(rand())/RAND_MAX,double(rand())/RAND_MAX); scene->bodies->insert(BP); } return v; } //************************************************************************** /* Generate truncated icosahedron*/ vector TruncIcosaHedPoints(Vector3r radii){ vector v; double p = (1.+sqrt(5.))/2.; Vector3r f,c,b; f = radii/sqrt(9.*p + 1.); vector A,B; A.push_back(Vector3r(0.,1.,3.*p)); A.push_back(Vector3r(2.,1.+2.*p,p)); A.push_back(Vector3r(1.,2.+p,2.*p)); for(int i=0;i<(int) A.size() ;i++){ B.clear(); c = Vector3r(A[i][0]*f[0],A[i][1]*f[1],A[i][2]*f[2]); B.push_back(c); B.push_back(Vector3r(c[1],c[2],c[0])); B.push_back(Vector3r(c[2],c[0],c[1])); for(int j=0;j<(int) B.size() ;j++){ b = B[j]; v.push_back(b); if (b[0] != 0.){ v.push_back(Vector3r(-b[0], b[1], b[2])); if (b[1] != 0.){ v.push_back(Vector3r(-b[0],-b[1], b[2])); if (b[2] != 0.) v.push_back(Vector3r(-b[0],-b[1],-b[2])); } if (b[2] != 0.) v.push_back(Vector3r(-b[0], b[1],-b[2])); } if (b[1] != 0.){ v.push_back(Vector3r( b[0],-b[1], b[2])); if (b[2] != 0.) v.push_back(Vector3r( b[0],-b[1],-b[2])); } if (b[2] != 0.) v.push_back(Vector3r( b[0], b[1],-b[2])); } } return v; } //************************************************************************** /* Generate SnubCube*/ vector SnubCubePoints(Vector3r radii){ vector v; double c1 = 0.337754; double c2 = 1.14261; double c3 = 0.621226; Vector3r f,b; f = radii/1.3437133737446; vector A; A.push_back(Vector3r(c2,c1,c3)); A.push_back(Vector3r(c1,c3,c2)); A.push_back(Vector3r(c3,c2,c1)); A.push_back(Vector3r(-c1,-c2,-c3)); A.push_back(Vector3r(-c2,-c3,-c1)); A.push_back(Vector3r(-c3,-c1,-c2)); for(int i=0;i<(int) A.size();i++){ b = Vector3r(A[i][0]*f[0],A[i][1]*f[1],A[i][2]*f[2]); v.push_back(b); v.push_back(Vector3r(-b[0],-b[1], b[2])); v.push_back(Vector3r(-b[0], b[1],-b[2])); v.push_back(Vector3r( b[0],-b[1],-b[2])); } return v; } //************************************************************************** /* Generate ball*/ vector BallPoints(Vector3r radii, int NumFacets,int seed){ vector v; if (NumFacets == 60) v = TruncIcosaHedPoints(radii); if (NumFacets == 24) v = SnubCubePoints(radii); else{ double inc = Mathr::PI * (3. - pow(5.,0.5)); double off = 2. / double(NumFacets); double y,r,phi; for(int k=0; k fillBoxByBalls_cpp(Vector3r minCoord, Vector3r maxCoord, Vector3r sizemin, Vector3r sizemax, Vector3r ratio, int seed, shared_ptr mat, int NumPoints){ vector v; Polyhedra trialP; Polyhedron trial, trial_moved; srand(seed); int it = 0; vector polyhedrons; vector > vv; Vector3r position; bool intersection; int count = 0; Vector3r radii; bool fixed_ratio = 0; if (ratio[0] > 0 && ratio[1] > 0 && ratio[2]>0){ fixed_ratio = 1; sizemax[0] = min(min(sizemax[0]/ratio[0], sizemax[1]/ratio[1]), sizemax[2]/ratio[2]); sizemin[0] = max(max(sizemin[0]/ratio[0], sizemin[1]/ratio[1]), sizemin[2]/ratio[2]); } fixed_ratio = 1; //force spherical //it - number of trials to make packing possibly more/less dense Vector3r random_size; while (it<1000){ it = it+1; if (it == 1){ if (fixed_ratio) { double rrr = (rand()*(sizemax[0]-sizemin[0])/RAND_MAX + sizemin[0])/2.; radii = Vector3r(rrr,rrr,rrr); }else { radii = Vector3r(rand()*(sizemax[0]-sizemin[0])/2.,rand()*(sizemax[1]-sizemin[1])/2.,rand()*(sizemax[2]-sizemin[2])/2.)/RAND_MAX + sizemin/2.; } trialP.v = BallPoints(radii,NumPoints,rand()); trialP.Initialize(); trial = trialP.GetPolyhedron(); Matrix3r rot_mat = (trialP.GetOri()).toRotationMatrix(); Transformation t_rot(rot_mat(0,0),rot_mat(0,1),rot_mat(0,2),rot_mat(1,0),rot_mat(1,1),rot_mat(1,2),rot_mat(2,0),rot_mat(2,1),rot_mat(2,2),1.); std::transform( trial.points_begin(), trial.points_end(), trial.points_begin(), t_rot); } position = Vector3r(rand()*(maxCoord[0]-minCoord[0]),rand()*(maxCoord[1]-minCoord[1]),rand()*(maxCoord[2]-minCoord[2]))/RAND_MAX + minCoord; //move CGAL structure Polyhedron Transformation transl(CGAL::TRANSLATION, ToCGALVector(position)); trial_moved = trial; std::transform( trial_moved.points_begin(), trial_moved.points_end(), trial_moved.points_begin(), transl); //calculate plane equations std::transform( trial_moved.facets_begin(), trial_moved.facets_end(), trial_moved.planes_begin(),Plane_equation()); intersection = false; //call test with boundary for(Polyhedron::Vertex_iterator vi = trial_moved.vertices_begin(); (vi != trial_moved.vertices_end()) && (!intersection); vi++){ intersection = (vi->point().x()point().x()>maxCoord[0]) || (vi->point().y()point().y()>maxCoord[1]) || (vi->point().z()point().z()>maxCoord[2]); } //call test with other polyhedrons for(vector::iterator a = polyhedrons.begin(); (a != polyhedrons.end()) && (!intersection); a++){ intersection = do_intersect(*a,trial_moved); if (intersection) break; } if (!intersection){ polyhedrons.push_back(trial_moved); v.clear(); for(Polyhedron::Vertex_iterator vi = trial_moved.vertices_begin(); vi != trial_moved.vertices_end(); vi++){ v.push_back(FromCGALPoint(vi->point())); } vv.push_back(v); it = 0; count ++; } } cout << "generated " << count << " polyhedrons"<< endl; //can't be used - no information about material Scene* scene=Omega::instance().getScene().get(); for(vector >::iterator p=vv.begin(); p!=vv.end(); ++p){ shared_ptr BP = NewPolyhedra(*p, mat); BP->shape->color = Vector3r(double(rand())/RAND_MAX,double(rand())/RAND_MAX,double(rand())/RAND_MAX); scene->bodies->insert(BP); } return v; } //********************************************************************************** //split polyhedra void Split(const shared_ptr body, Vector3r direction, Vector3r point){ SplitPolyhedra(body, direction, point); } //********************************************************************************** //distace of point from a plane (squared) with sign double Oriented_squared_distance2(Plane P, CGALpoint x){ double h = P.a()*x.x()+P.b()*x.y()+P.c()*x.z()+P.d(); return ((h>0.)-(h<0.))*pow(h,2)/(CGALvector(P.a(),P.b(),P.c())).squared_length(); } //********************************************************************************** bool convexHull(vector points){ vector pointsCGAL; for(int i=0;i<(int) points.size() ;i++) { pointsCGAL.push_back(ToCGALPoint(points[i])); } Polyhedron P; CGAL::convex_hull_3(pointsCGAL.begin(), pointsCGAL.end(), P); return true; } BOOST_PYTHON_MODULE(_polyhedra_utils){ YADE_SET_DOCSTRING_OPTS; py::def("PrintPolyhedra",PrintPolyhedra,"Print list of vertices sorted according to polyhedrons facets."); py::def("PrintPolyhedraActualPos",PrintPolyhedraActualPos,"Print list of vertices sorted according to polyhedrons facets."); py::def("PWaveTimeStep",PWaveTimeStep,"Get timestep accoring to the velocity of P-Wave propagation; computed from sphere radii, rigidities and masses."); py::def("do_Polyhedras_Intersect",do_Polyhedras_Intersect,"check polyhedras intersection"); py::def("fillBox_cpp",fillBox_cpp,"Generate non-overlaping polyhedrons in box"); py::def("fillBoxByBalls_cpp",fillBoxByBalls_cpp,"Generate non-overlaping 'spherical' polyhedrons in box"); py::def("MinCoord",MinCoord,"returns min coordinates"); py::def("MaxCoord",MaxCoord,"returns max coordinates"); py::def("SieveSize",SieveSize,"returns approximate sieve size of polyhedron"); py::def("SieveCurve",SieveCurve,"save sieve curve coordinates into file"); py::def("SizeOfPolyhedra",SizeOfPolyhedra,"returns max, middle an min size in perpendicular directions"); py::def("SizeRatio",SizeRatio,"save sizes of polyhedra into file"); py::def("convexHull",convexHull,"TODO"); py::def("Split",Split,"split polyhedron perpendicularly to given direction through given point"); } #endif // YADE_CGAL trunk-2018.02b/py/_utils.cpp000066400000000000000000001271771324306050200156400ustar00rootroot00000000000000#include #include bool isInBB(Vector3r p, Vector3r bbMin, Vector3r bbMax){return p[0]>bbMin[0] && p[0]bbMin[1] && p[1]bbMin[2] && p[2](extrema[0][axis])(),maxCoord=py::extract(extrema[1][axis])(); py::list minIds,maxIds; FOREACH(const shared_ptr& b, *Omega::instance().getScene()->bodies){ shared_ptr s=YADE_PTR_DYN_CAST(b->shape); if(!s) continue; if(b->state->pos[axis]-s->radius*distFactor<=minCoord) minIds.append(b->getId()); if(b->state->pos[axis]+s->radius*distFactor>=maxCoord) maxIds.append(b->getId()); } return py::make_tuple(minIds,maxIds); } py::tuple coordsAndDisplacements(int axis,py::tuple Aabb){ Vector3r bbMin(Vector3r::Zero()), bbMax(Vector3r::Zero()); bool useBB=py::len(Aabb)>0; if(useBB){bbMin=py::extract(Aabb[0])();bbMax=py::extract(Aabb[1])();} py::list retCoord,retDispl; FOREACH(const shared_ptr&b, *Omega::instance().getScene()->bodies){ if(useBB && !isInBB(b->state->pos,bbMin,bbMax)) continue; retCoord.append(b->state->pos[axis]); retDispl.append(b->state->pos[axis]-b->state->refPos[axis]); } return py::make_tuple(retCoord,retDispl); } void setRefSe3(){ Scene* scene=Omega::instance().getScene().get(); FOREACH(const shared_ptr& b, *scene->bodies){ b->state->refPos=b->state->pos; b->state->refOri=b->state->ori; } if(scene->isPeriodic){ scene->cell->refHSize=scene->cell->hSize; } } Real PWaveTimeStep(){return Shop::PWaveTimeStep();}; Real RayleighWaveTimeStep(){return Shop::RayleighWaveTimeStep();}; py::tuple interactionAnglesHistogram(int axis, int mask, size_t bins, py::tuple aabb, bool sphSph, Real minProjLen){ if(axis<0||axis>2) throw invalid_argument("Axis must be from {0,1,2}=x,y,z."); Vector3r bbMin(Vector3r::Zero()), bbMax(Vector3r::Zero()); bool useBB=py::len(aabb)>0; if(useBB){bbMin=py::extract(aabb[0])();bbMax=py::extract(aabb[1])();} Real binStep=Mathr::PI/bins; int axis2=(axis+1)%3, axis3=(axis+2)%3; vector cummProj(bins,0.); shared_ptr rb=Omega::instance().getScene(); FOREACH(const shared_ptr& i, *rb->interactions){ if(!i->isReal()) continue; const shared_ptr& b1=Body::byId(i->getId1(),rb), b2=Body::byId(i->getId2(),rb); if(!b1->maskOk(mask) || !b2->maskOk(mask)) continue; if(useBB && !isInBB(b1->state->pos,bbMin,bbMax) && !isInBB(b2->state->pos,bbMin,bbMax)) continue; if (sphSph && ( !dynamic_cast(b1->shape.get()) || !dynamic_cast(b2->shape.get()) ) ) continue; GenericSpheresContact* geom=dynamic_cast(i->geom.get()); if(!geom) continue; Vector3r n(geom->normal); n[axis]=0.; Real nLen=n.norm(); if(nLen0?1:-1); if(theta<0) theta+=Mathr::PI; int binNo=theta/binStep; cummProj[binNo]+=nLen; } py::list val,binMid; for(size_t i=0; i<(size_t)bins; i++){ val.append(cummProj[i]); binMid.append(i*binStep);} return py::make_tuple(binMid,val); } py::tuple bodyNumInteractionsHistogram(py::tuple aabb){ Vector3r bbMin(Vector3r::Zero()), bbMax(Vector3r::Zero()); bool useBB=py::len(aabb)>0; if(useBB){bbMin=py::extract(aabb[0])();bbMax=py::extract(aabb[1])();} const shared_ptr& rb=Omega::instance().getScene(); vector bodyNumIntr; bodyNumIntr.resize(rb->bodies->size(),0); int maxIntr=0; FOREACH(const shared_ptr& i, *rb->interactions){ if(!i->isReal()) continue; const Body::id_t id1=i->getId1(), id2=i->getId2(); const shared_ptr& b1=Body::byId(id1,rb), b2=Body::byId(id2,rb); if((useBB && isInBB(b1->state->pos,bbMin,bbMax)) || !useBB) { if (b1->isClumpMember()) bodyNumIntr[b1->clumpId]+=1; //count bodyNumIntr for the clump, not for the member else bodyNumIntr[id1]+=1; } if((useBB && isInBB(b2->state->pos,bbMin,bbMax)) || !useBB) { if (b2->isClumpMember()) bodyNumIntr[b2->clumpId]+=1; //count bodyNumIntr for the clump, not for the member else bodyNumIntr[id2]+=1; } maxIntr=max(max(maxIntr,bodyNumIntr[b1->getId()]),bodyNumIntr[b2->getId()]); if (b1->isClumpMember()) maxIntr=max(maxIntr,bodyNumIntr[b1->clumpId]); if (b2->isClumpMember()) maxIntr=max(maxIntr,bodyNumIntr[b2->clumpId]); } vector bins; bins.resize(maxIntr+1,0); for(size_t id=0; id& b=Body::byId(id,rb); if (b) { if(bodyNumIntr[id]>0) bins[bodyNumIntr[id]]+=1; // 0 is handled specially: add body to the 0 bin only if it is inside the bb requested (if applicable) // otherwise don't do anything, since it is outside the volume of interest else if(((useBB && isInBB(b->state->pos,bbMin,bbMax)) || !useBB) && !(b->isClumpMember())) bins[0]+=1; } } py::list count,num; for(size_t n=0; n b = shared_ptr(new ViscElMat()); Shop::getViscoelasticFromSpheresInteraction(tc,en,es,b); py::dict d; d["kn"]=b->kn; d["cn"]=b->cn; d["ks"]=b->ks; d["cs"]=b->cs; return d; } /* reset highlight of all bodies */ void highlightNone(){ FOREACH(const shared_ptr& b, *Omega::instance().getScene()->bodies){ if(!b->shape) continue; b->shape->highlight=false; } } /*!Sum moments acting on given bodies * * @param ids is the calculated bodies ids * @param axis is the direction of axis with respect to which the moment is calculated. * @param axisPt is a point on the axis. * * The computation is trivial: moment from force is is by definition r×F, where r * is position relative to axisPt; moment from moment is m; such moment per body is * projected onto axis. */ Real sumTorques(py::list ids, const Vector3r& axis, const Vector3r& axisPt){ shared_ptr rb=Omega::instance().getScene(); rb->forces.sync(); Real ret=0; size_t len=py::len(ids); for(size_t i=0; ibodies)[py::extract(ids[i])].get(); const Vector3r& m=rb->forces.getTorque(b->getId()); const Vector3r& f=rb->forces.getForce(b->getId()); Vector3r r=b->state->pos-axisPt; ret+=axis.dot(m+r.cross(f)); } return ret; } /* Sum forces acting on bodies within mask. * * @param ids list of ids * @param direction direction in which forces are summed * */ Real sumForces(py::list ids, const Vector3r& direction){ shared_ptr rb=Omega::instance().getScene(); rb->forces.sync(); Real ret=0; size_t len=py::len(ids); for(size_t i=0; i(ids[i]); const Vector3r& f=rb->forces.getForce(id); ret+=direction.dot(f); } return ret; } /* Sum force acting on facets given by their ids in the sense of their respective normals. If axis is given, it will sum forces perpendicular to given axis only (not the the facet normals). */ Real sumFacetNormalForces(vector ids, int axis){ shared_ptr rb=Omega::instance().getScene(); rb->forces.sync(); Real ret=0; FOREACH(const Body::id_t id, ids){ Facet* f=YADE_CAST(Body::byId(id,rb)->shape.get()); if(axis<0) ret+=rb->forces.getForce(id).dot(f->normal); else { Vector3r ff=rb->forces.getForce(id); ff[axis]=0; ret+=ff.dot(f->normal); } } return ret; } /* Set wire display of all/some/none bodies depending on the filter. */ void wireSome(string filter){ enum{none,all,noSpheres,unknown}; int mode=(filter=="none"?none:(filter=="all"?all:(filter=="noSpheres"?noSpheres:unknown))); if(mode==unknown) { LOG_WARN("Unknown wire filter `"<& b, *Omega::instance().getScene()->bodies){ if(!b->shape) return; bool wire; switch(mode){ case none: wire=false; break; case all: wire=true; break; case noSpheres: wire=!(bool)(YADE_PTR_DYN_CAST(b->shape)); break; default: throw logic_error("No such case possible"); } b->shape->wire=wire; } } void wireAll(){wireSome("all");} void wireNone(){wireSome("none");} void wireNoSpheres(){wireSome("noSpheres");} /* Tell us whether a point lies in polygon given by array of points. * @param xy is the point that is being tested * @param vertices is Numeric.array (or list or tuple) of vertices of the polygon. * Every row of the array is x and y coordinate, numer of rows is >= 3 (triangle). * * Copying the algorithm from http://www.ecse.rpi.edu/Homepages/wrf/Research/Short_Notes/pnpoly.html * is gratefully acknowledged: * * License to Use: * Copyright (c) 1970-2003, Wm. Randolph Franklin * 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: * 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimers. * 2. Redistributions in binary form must reproduce the above copyright notice in the documentation and/or other materials provided with the distribution. * 3. The name of W. Randolph Franklin may not be used to endorse or promote products derived from this Software without specific prior written permission. * 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. * * http://numpy.scipy.org/numpydoc/numpy-13.html told me how to use Numeric.array from c */ bool pointInsidePolygon(py::tuple xy, py::object vertices){ Real testx=py::extract(xy[0])(),testy=py::extract(xy[1])(); char** vertData; int rows, cols; PyArrayObject* vert=(PyArrayObject*)vertices.ptr(); int result=PyArray_As2D((PyObject**)&vert /* is replaced */ ,&vertData,&rows,&cols,PyArray_DOUBLE); if(result!=0) throw invalid_argument("Unable to cast vertices to 2d array"); if(cols!=2 || rows<3) throw invalid_argument("Vertices must have 2 columns (x and y) and at least 3 rows."); int i /*current node*/, j/*previous node*/; bool inside=false; for(i=0,j=rows-1; idata+i*vert->strides[0]), vy_i=*(double*)(vert->data+i*vert->strides[0]+vert->strides[1]), vx_j=*(double*)(vert->data+j*vert->strides[0]), vy_j=*(double*)(vert->data+j*vert->strides[0]+vert->strides[1]); if (((vy_i>testy)!=(vy_j>testy)) && (testx < (vx_j-vx_i) * (testy-vy_i) / (vy_j-vy_i) + vx_i) ) inside=!inside; } Py_DECREF(vert); return inside; } /* Compute area of convex hull when when taking (swept) spheres crossing the plane at coord, perpendicular to axis. All spheres that touch the plane are projected as hexagons on their circumference to the plane. Convex hull from this cloud is computed. The area of the hull is returned. */ Real approxSectionArea(Real coord, int axis){ std::list cloud; if(axis<0 || axis>2) throw invalid_argument("Axis must be ∈ {0,1,2}"); const int ax1=(axis+1)%3, ax2=(axis+2)%3; const Real sqrt3=sqrt(3); Vector2r mm,mx; int i=0; FOREACH(const shared_ptr& b, *Omega::instance().getScene()->bodies){ Sphere* s=dynamic_cast(b->shape.get()); if(!s) continue; const Vector3r& pos(b->state->pos); const Real r(s->radius); if((pos[axis]>coord && (pos[axis]-r)>coord) || (pos[axis] hull=ch2d(); return simplePolygonArea2d(hull); } /* Find all interactions deriving from NormShearPhys that cross plane given by a point and normal (the normal may not be normalized in this case, though) and sum forces (both normal and shear) on them. Returns a 3-tuple with the components along global x,y,z axes, which can be viewed as "action from lower part, towards upper part" (lower and upper parts with respect to the plane's normal). (This could be easily extended to return sum of only normal forces or only of shear forces.) */ Vector3r forcesOnPlane(const Vector3r& planePt, const Vector3r& normal){ Vector3r ret(Vector3r::Zero()); Scene* scene=Omega::instance().getScene().get(); FOREACH(const shared_ptr&I, *scene->interactions){ if(!I->isReal()) continue; NormShearPhys* nsi=dynamic_cast(I->phys.get()); if(!nsi) continue; Vector3r pos1,pos2; pos1=Body::byId(I->getId1(),scene)->state->pos; pos2=Body::byId(I->getId2(),scene)->state->pos; Real dot1=(pos1-planePt).dot(normal), dot2=(pos2-planePt).dot(normal); if(dot1*dot2>0) continue; // both (centers of) bodies are on the same side of the plane=> this interaction has to be disregarded // if pt1 is on the negative plane side, d3dg->normal.Dot(normal)>0, the force is well oriented; // otherwise, reverse its contribution. So that we return finally // Sum [ ( normal(plane) dot normal(interaction= from 1 to 2) ) "nsi->force" ] ret+=(dot1<0.?1.:-1.)*(nsi->normalForce+nsi->shearForce); } return ret; } /* Less general than forcesOnPlane, computes force on plane perpendicular to axis, passing through coordinate coord. */ Vector3r forcesOnCoordPlane(Real coord, int axis){ Vector3r planePt(Vector3r::Zero()); planePt[axis]=coord; Vector3r normal(Vector3r::Zero()); normal[axis]=1; return forcesOnPlane(planePt,normal); } py::tuple spiralProject(const Vector3r& pt, Real dH_dTheta, int axis, Real periodStart, Real theta0){ Real r,h,theta; boost::tie(r,h,theta)=Shop::spiralProject(pt,dH_dTheta,axis,periodStart,theta0); return py::make_tuple(py::make_tuple(r,h),theta); } shared_ptr Shop__createExplicitInteraction(Body::id_t id1, Body::id_t id2){ return Shop::createExplicitInteraction(id1,id2,/*force*/true);} Real Shop__unbalancedForce(bool useMaxForce /*false by default*/){return Shop::unbalancedForce(useMaxForce);} py::tuple Shop__totalForceInVolume(){Real stiff; Vector3r ret=Shop::totalForceInVolume(stiff); return py::make_tuple(ret,stiff); } Real Shop__getSpheresVolume(int mask){ return Shop::getSpheresVolume(Omega::instance().getScene(), mask);} Real Shop__getSpheresMass(int mask){ return Shop::getSpheresMass(Omega::instance().getScene(), mask);} py::object Shop__kineticEnergy(bool findMaxId){ if(!findMaxId) return py::object(Shop::kineticEnergy()); Body::id_t maxId; Real E=Shop::kineticEnergy(NULL,&maxId); return py::make_tuple(E,maxId); } Real maxOverlapRatio(){ Scene* scene=Omega::instance().getScene().get(); Real ret=-1; FOREACH(const shared_ptr I, *scene->interactions){ if(!I->isReal()) continue; Sphere *s1(dynamic_cast(Body::byId(I->getId1(),scene)->shape.get())), *s2(dynamic_cast(Body::byId(I->getId2(),scene)->shape.get())); if((!s1) || (!s2)) continue; ScGeom* geom=dynamic_cast(I->geom.get()); if(!geom) continue; Real rEq=2*s1->radius*s2->radius/(s1->radius+s2->radius); ret=max(ret,geom->penetrationDepth/rEq); } return ret; } Real Shop__getPorosity(Real volume){ return Shop::getPorosity(Omega::instance().getScene(),volume); } Real Shop__getVoxelPorosity(int resolution, Vector3r start,Vector3r end){ return Shop::getVoxelPorosity(Omega::instance().getScene(),resolution,start,end); } //Matrix3r Shop__stressTensorOfPeriodicCell(bool smallStrains=false){return Shop::stressTensorOfPeriodicCell(smallStrains);} py::tuple Shop__fabricTensor(Real cutoff, bool splitTensor, Real thresholdForce){return Shop::fabricTensor(cutoff, splitTensor,thresholdForce);} py::tuple Shop__normalShearStressTensors(bool compressionPositive, bool splitNormalTensor, Real thresholdForce){return Shop::normalShearStressTensors(compressionPositive,splitNormalTensor,thresholdForce);} py::list Shop__getStressLWForEachBody(){return Shop::getStressLWForEachBody();} py::list Shop__getBodyIdsContacts(Body::id_t bodyID){return Shop::getBodyIdsContacts(bodyID);} Real shiftBodies(py::list ids, const Vector3r& shift){ shared_ptr rb=Omega::instance().getScene(); size_t len=py::len(ids); for(size_t i=0; ibodies)[py::extract(ids[i])].get(); if(!b) continue; b->state->pos+=shift; } return 1; } void Shop__calm(int mask){ return Shop::calm(Omega::instance().getScene(), mask);} void setNewVerticesOfFacet(const shared_ptr& b, const Vector3r& v1, const Vector3r& v2, const Vector3r& v3) { Vector3r center = inscribedCircleCenter(v1,v2,v3); Facet* facet = YADE_CAST(b->shape.get()); facet->vertices[0] = v1 - center; facet->vertices[1] = v2 - center; facet->vertices[2] = v3 - center; b->state->pos = center; } py::list intrsOfEachBody() { py::list ret, temp; shared_ptr rb=Omega::instance().getScene(); size_t n = rb->bodies->size(); // create list of size len(O.bodies) full of zeros for (size_t i=0; i& i, *rb->interactions) { if (!i->isReal()) { continue; } temp = py::extract(ret[i->getId1()]); temp.append( i ); temp = py::extract(ret[i->getId2()]); temp.append( i ); } return ret; } py::list numIntrsOfEachBody() { py::list ret; shared_ptr rb=Omega::instance().getScene(); size_t n = rb->bodies->size(); // create list of size len(O.bodies) full of zeros for (size_t i=0; i& i, *rb->interactions) { if (!i->isReal()) continue; ret[i->getId1()] += 1; ret[i->getId2()] += 1; } return ret; } Real Shop__getSpheresVolume2D(int mask=-1){ return Shop::getSpheresVolume2D(Omega::instance().getScene(), mask);} Real Shop__getVoidRatio2D(Real zlen=1){ return Shop::getVoidRatio2D(Omega::instance().getScene(),zlen);} py::tuple Shop__getStressAndTangent(Real volume=0, bool symmetry=true){return Shop::getStressAndTangent(volume,symmetry);} void setBodyPosition(int id, Vector3r newPos, string axis){ shared_ptr rb=Omega::instance().getScene(); const Body* b=(*rb->bodies)[id].get(); for (char c : axis){ if(c=='x') {b->state->pos[0] = newPos[0];continue;} if(c=='y') {b->state->pos[1] = newPos[1];continue;} if(c=='z') {b->state->pos[2] = newPos[2];continue;} } } void setBodyVelocity(int id, Vector3r newVel, string axis){ shared_ptr rb=Omega::instance().getScene(); const Body* b=(*rb->bodies)[id].get(); for (char c : axis){ if(c=='x') {b->state->vel[0] = newVel[0];continue;} if(c=='y') {b->state->vel[1] = newVel[1];continue;} if(c=='z') {b->state->vel[2] = newVel[2];continue;} } } void setBodyOrientation(int id, Quaternionr newOri){ shared_ptr rb=Omega::instance().getScene(); const Body* b=(*rb->bodies)[id].get(); b->state->ori=newOri; } void setBodyAngularVelocity(int id, Vector3r newAngVel){ shared_ptr rb=Omega::instance().getScene(); const Body* b=(*rb->bodies)[id].get(); b->state->angVel=newAngVel; } void setBodyColor(int id, Vector3r newColor){ shared_ptr rb=Omega::instance().getScene(); const Body* b=(*rb->bodies)[id].get(); b->shape->color=newColor; } BOOST_PYTHON_MODULE(_utils){ YADE_SET_DOCSTRING_OPTS; py::def("PWaveTimeStep",PWaveTimeStep,"Get timestep accoring to the velocity of P-Wave propagation; computed from sphere radii, rigidities and masses."); py::def("RayleighWaveTimeStep",RayleighWaveTimeStep,"Determination of time step according to Rayleigh wave speed of force propagation."); py::def("getSpheresVolume",Shop__getSpheresVolume,(py::arg("mask")=-1),"Compute the total volume of spheres in the simulation, mask parameter is considered"); py::def("getSpheresMass",Shop__getSpheresMass,(py::arg("mask")=-1),"Compute the total mass of spheres in the simulation, mask parameter is considered"); py::def("porosity",Shop__getPorosity,(py::arg("volume")=-1),"Compute packing porosity $\\frac{V-V_s}{V}$ where $V$ is overall volume and $V_s$ is volume of spheres.\n\n:param float volume: overall volume $V$. For periodic simulations, current volume of the :yref:`Cell` is used. For aperiodic simulations, the value deduced from utils.aabbDim() is used. For compatibility reasons, positive values passed by the user are also accepted in this case.\n"); py::def("voxelPorosity",Shop__getVoxelPorosity,(py::arg("resolution")=200,py::arg("start")=Vector3r(0,0,0),py::arg("end")=Vector3r(0,0,0)),"Compute packing porosity $\\frac{V-V_v}{V}$ where $V$ is a specified volume (from start to end) and $V_v$ is volume of voxels that fall inside any sphere. The calculation method is to divide whole volume into a dense grid of voxels (at given resolution), and count the voxels that fall inside any of the spheres. This method allows one to calculate porosity in any given sub-volume of a whole sample. It is properly excluding part of a sphere that does not fall inside a specified volume.\n\n:param int resolution: voxel grid resolution, values bigger than resolution=1600 require a 64 bit operating system, because more than 4GB of RAM is used, a resolution=800 will use 500MB of RAM.\n:param Vector3 start: start corner of the volume.\n:param Vector3 end: end corner of the volume.\n"); py::def("aabbExtrema",Shop::aabbExtrema,(py::arg("cutoff")=0.0,py::arg("centers")=false),"Return coordinates of box enclosing all spherical bodies\n\n:param bool centers: do not take sphere radii in account, only their centroids\n:param float∈〈0…1〉 cutoff: relative dimension by which the box will be cut away at its boundaries.\n\n\n:return: (lower corner, upper corner) as (Vector3,Vector3)\n\n"); py::def("ptInAABB",isInBB,"Return True/False whether the point p is within box given by its min and max corners"); py::def("negPosExtremeIds",negPosExtremeIds,(py::arg("axis"),py::arg("distFactor")),"Return list of ids for spheres (only) that are on extremal ends of the specimen along given axis; distFactor multiplies their radius so that sphere that do not touch the boundary coordinate can also be returned."); py::def("approxSectionArea",approxSectionArea,"Compute area of convex hull when when taking (swept) spheres crossing the plane at coord, perpendicular to axis."); py::def("coordsAndDisplacements",coordsAndDisplacements,(py::arg("axis"),py::arg("Aabb")=py::tuple()),"Return tuple of 2 same-length lists for coordinates and displacements (coordinate minus reference coordinate) along given axis (1st arg); if the Aabb=((x_min,y_min,z_min),(x_max,y_max,z_max)) box is given, only bodies within this box will be considered."); py::def("setRefSe3",setRefSe3,"Set reference :yref:`positions` and :yref:`orientations` of all :yref:`bodies` equal to their current :yref:`positions` and :yref:`orientations`."); py::def("interactionAnglesHistogram",interactionAnglesHistogram,(py::arg("axis"),py::arg("mask")=0,py::arg("bins")=20,py::arg("aabb")=py::tuple(),py::arg("sphSph")=0,py::arg("minProjLen")=1e-6)); py::def("bodyNumInteractionsHistogram",bodyNumInteractionsHistogram,(py::arg("aabb"))); py::def("inscribedCircleCenter",inscribedCircleCenter,(py::arg("v1"),py::arg("v2"),py::arg("v3")),"Return center of inscribed circle for triangle given by its vertices *v1*, *v2*, *v3*."); py::def("unbalancedForce",&Shop__unbalancedForce,(py::args("useMaxForce")=false),"Compute the ratio of mean (or maximum, if *useMaxForce*) summary force on bodies and mean force magnitude on interactions. For perfectly static equilibrium, summary force on all bodies is zero (since forces from interactions cancel out and induce no acceleration of particles); this ratio will tend to zero as simulation stabilizes, though zero is never reached because of finite precision computation. Sufficiently small value can be e.g. 1e-2 or smaller, depending on how much equilibrium it should be."); py::def("kineticEnergy",Shop__kineticEnergy,(py::args("findMaxId")=false),"Compute overall kinetic energy of the simulation as\n\n.. math:: \\sum\\frac{1}{2}\\left(m_i\\vec{v}_i^2+\\vec{\\omega}(\\mat{I}\\vec{\\omega}^T)\\right).\n\nFor :yref:`aspherical` bodies, the inertia tensor $\\mat{I}$ is transformed to global frame, before multiplied by $\\vec{\\omega}$, therefore the value should be accurate.\n"); py::def("sumForces",sumForces,(py::arg("ids"),py::arg("direction")),"Return summary force on bodies with given *ids*, projected on the *direction* vector."); py::def("sumTorques",sumTorques,(py::arg("ids"),py::arg("axis"),py::arg("axisPt")),"Sum forces and torques on bodies given in *ids* with respect to axis specified by a point *axisPt* and its direction *axis*."); py::def("sumFacetNormalForces",sumFacetNormalForces,(py::arg("ids"),py::arg("axis")=-1),"Sum force magnitudes on given bodies (must have :yref:`shape` of the :yref:`Facet` type), considering only part of forces perpendicular to each :yref:`facet's` face; if *axis* has positive value, then the specified axis (0=x, 1=y, 2=z) will be used instead of facet's normals."); py::def("forcesOnPlane",forcesOnPlane,(py::arg("planePt"),py::arg("normal")),"Find all interactions deriving from :yref:`NormShearPhys` that cross given plane and sum forces (both normal and shear) on them.\n\n:param Vector3 planePt: a point on the plane\n:param Vector3 normal: plane normal (will be normalized).\n"); py::def("forcesOnCoordPlane",forcesOnCoordPlane); py::def("totalForceInVolume",Shop__totalForceInVolume,"Return summed forces on all interactions and average isotropic stiffness, as tuple (Vector3,float)"); py::def("createInteraction",Shop__createExplicitInteraction,(py::arg("id1"),py::arg("id2")),"Create interaction between given bodies by hand.\n\nCurrent engines are searched for :yref:`IGeomDispatcher` and :yref:`IPhysDispatcher` (might be both hidden in :yref:`InteractionLoop`). Geometry is created using ``force`` parameter of the :yref:`geometry dispatcher`, wherefore the interaction will exist even if bodies do not spatially overlap and the functor would return ``false`` under normal circumstances. \n\n.. warning:: This function will very likely behave incorrectly for periodic simulations (though it could be extended it to handle it farily easily)."); py::def("spiralProject",spiralProject,(py::arg("pt"),py::arg("dH_dTheta"),py::arg("axis")=2,py::arg("periodStart")=std::numeric_limits::quiet_NaN(),py::arg("theta0")=0)); py::def("pointInsidePolygon",pointInsidePolygon); py::def("scalarOnColorScale",Shop::scalarOnColorScale); py::def("highlightNone",highlightNone,"Reset :yref:`highlight` on all bodies."); py::def("wireAll",wireAll,"Set :yref:`Shape::wire` on all bodies to True, rendering them with wireframe only."); py::def("wireNone",wireNone,"Set :yref:`Shape::wire` on all bodies to False, rendering them as solids."); py::def("wireNoSpheres",wireNoSpheres,"Set :yref:`Shape::wire` to True on non-spherical bodies (:yref:`Facets`, :yref:`Walls`)."); py::def("flipCell",&Shop::flipCell,(py::arg("flip")=Matrix3r(Matrix3r::Zero())),"Flip periodic cell so that angles between $R^3$ axes and transformed axes are as small as possible. This function relies on the fact that periodic cell defines by repetition or its corners regular grid of points in $R^3$; however, all cells generating identical grid are equivalent and can be flipped one over another. This necessiatates adjustment of :yref:`Interaction.cellDist` for interactions that cross boundary and didn't before (or vice versa), and re-initialization of collider. The *flip* argument can be used to specify desired flip: integers, each column for one axis; if zero matrix, best fit (minimizing the angles) is computed automatically.\n\nIn c++, this function is accessible as ``Shop::flipCell``."); py::def("getViscoelasticFromSpheresInteraction",getViscoelasticFromSpheresInteraction,(py::arg("tc"),py::arg("en"),py::arg("es")),"Attention! The function is deprecated! Compute viscoelastic interaction parameters from analytical solution of a pair spheres collision problem:\n\n.. math:: k_n=\\frac{m}{t_c^2}\\left(\\pi^2+(\\ln e_n)^2\\right) \\\\ c_n=-\\frac{2m}{t_c}\\ln e_n \\\\ k_t=\\frac{2}{7}\\frac{m}{t_c^2}\\left(\\pi^2+(\\ln e_t)^2\\right) \\\\ c_t=-\\frac{2}{7}\\frac{m}{t_c}\\ln e_t \n\n\nwhere $k_n$, $c_n$ are normal elastic and viscous coefficients and $k_t$, $c_t$ shear elastic and viscous coefficients. For details see [Pournin2001]_.\n\n:param float m: sphere mass $m$\n:param float tc: collision time $t_c$\n:param float en: normal restitution coefficient $e_n$\n:param float es: tangential restitution coefficient $e_s$\n:return: dictionary with keys ``kn`` (the value of $k_n$), ``cn`` ($c_n$), ``kt`` ($k_t$), ``ct`` ($c_t$)."); py::def("stressTensorOfPeriodicCell",Shop::getStress,(py::args("volume")=0),"Deprecated, use utils.getStress instead |ydeprecated|"); py::def("normalShearStressTensors",Shop__normalShearStressTensors,(py::args("compressionPositive")=false,py::args("splitNormalTensor")=false,py::args("thresholdForce")=NaN),"Compute overall stress tensor of the periodic cell decomposed in 2 parts, one contributed by normal forces, the other by shear forces. The formulation can be found in [Thornton2000]_, eq. (3):\n\n.. math:: \\tens{\\sigma}_{ij}=\\frac{2}{V}\\sum R N \\vec{n}_i \\vec{n}_j+\\frac{2}{V}\\sum R T \\vec{n}_i\\vec{t}_j\n\nwhere $V$ is the cell volume, $R$ is \"contact radius\" (in our implementation, current distance between particle centroids), $\\vec{n}$ is the normal vector, $\\vec{t}$ is a vector perpendicular to $\\vec{n}$, $N$ and $T$ are norms of normal and shear forces.\n\n:param bool splitNormalTensor: if true the function returns normal stress tensor split into two parts according to the two subnetworks of strong an weak forces.\n\n:param Real thresholdForce: threshold value according to which the normal stress tensor can be split (e.g. a zero value would make distinction between tensile and compressive forces)."); py::def("fabricTensor",Shop__fabricTensor,(py::args("cutoff")=0.0,py::args("splitTensor")=false,py::args("thresholdForce")=NaN),"Computes the fabric tensor $F_{ij}=\\frac{1}{n_c}\\sum_c n_i n_j$ [Satake1982]_, for all interactions $c$.\n\n:param Real cutoff: intended to disregard boundary effects: to define in [0;1] to focus on the interactions located in the centered inner (1-cutoff)^3*$V$ part of the spherical packing $V$.\n\n:param bool splitTensor: split the fabric tensor into two parts related to the strong (greatest compressive normal forces) and weak contact forces respectively.\n\n:param Real thresholdForce: if the fabric tensor is split into two parts, a threshold value can be specified otherwise the mean contact force is considered by default. Use negative signed values for compressive states. To note that this value could be set to zero if one wanted to make distinction between compressive and tensile forces."); py::def("bodyStressTensors",Shop__getStressLWForEachBody,"Compute and return a table with per-particle stress tensors. Each tensor represents the average stress in one particle, obtained from the contour integral of applied load as detailed below. This definition is considering each sphere as a continuum. It can be considered exact in the context of spheres at static equilibrium, interacting at contact points with negligible volume changes of the solid phase (this last assumption is not restricting possible deformations and volume changes at the packing scale).\n\nProof: \n\nFirst, we remark the identity: $\\sigma_{ij}=\\delta_{ik}\\sigma_{kj}=x_{i,k}\\sigma_{kj}=(x_{i}\\sigma_{kj})_{,k}-x_{i}\\sigma_{kj,k}$.\n\nAt equilibrium, the divergence of stress is null: $\\sigma_{kj,k}=\\vec{0}$. Consequently, after divergence theorem: $\\frac{1}{V}\\int_V \\sigma_{ij}dV = \\frac{1}{V}\\int_V (x_{i}\\sigma_{kj})_{,k}dV = \\frac{1}{V}\\int_{\\partial V}x_i\\sigma_{kj}n_kdS = \\frac{1}{V}\\sum_bx_i^bf_j^b$.\n\nThe last equality is implicitely based on the representation of external loads as Dirac distributions whose zeros are the so-called *contact points*: 0-sized surfaces on which the *contact forces* are applied, located at $x_i$ in the deformed configuration.\n\nA weighted average of per-body stresses will give the average stress inside the solid phase. There is a simple relation between the stress inside the solid phase and the stress in an equivalent continuum in the absence of fluid pressure. For porosity $n$, the relation reads: $\\sigma_{ij}^{equ.}=(1-n)\\sigma_{ij}^{solid}$.\n\nThis last relation may not be very useful if porosity is not homogeneous. If it happens, one can define the equivalent bulk stress a the particles scale by assigning a volume to each particle. This volume can be obtained from :yref:`TesselationWrapper` (see e.g. [Catalano2014a]_)"); py::def("getStress",Shop::getStress,(py::args("volume")=0),"Compute and return Love-Weber stress tensor:\n\n $\\sigma_{ij}=\\frac{1}{V}\\sum_b f_i^b l_j^b$, where the sum is over all interactions, with $f$ the contact force and $l$ the branch vector (joining centers of the bodies). Stress is negativ for repulsive contact forces, i.e. compression. $V$ can be passed to the function. If it is not, it will be equal to the volume of the cell in periodic cases, or to the one deduced from utils.aabbDim() in non-periodic cases."); py::def("getStressProfile",Shop::getStressProfile,(py::args("volume"),py::args("nCell"),py::args("dz"),py::args("zRef"),py::args("vPartAverageX"),py::args("vPartAverageY"),py::args("vPartAverageZ")),"Compute and return the stress tensor depth profile, including the contribution from Love-Weber stress tensor and the dynamic stress tensor taking into account the effect of particles inertia. For each defined cell z, the stress tensor reads: \n\n $\\sigma_{ij}^z= \\frac{1}{V}\\sum_c f_i^c l_j^{c,z} - \\frac{1}{V}\\sum_p m^p u'^p_i u'^p_j$,\n\n where the first sum is made over the contacts which are contained or cross the cell z, f^c is the contact force from particle 1 to particle 2, and l^{c,z} is the part of the branch vector from particle 2 to particle 1, contained in the cell. The second sum is made over the particles, and u'^p is the velocity fluctuations of the particle p with respect to the spatial averaged particle velocity at this point (given as input parameters). The expression of the stress tensor is the same as the one given in getStress plus the inertial contribution. Apart from that, the main difference with getStress stands in the fact that it gives a depth profile of stress tensor, i.e. from the reference horizontal plane at elevation zRef (input parameter) until the plane of elevation zRef+nCell*dz (input parameters), it is computing the stress tensor for each cell of height dz. For the love-Weber stress contribution, the branch vector taken into account in the calculations is only the part of the branch vector contained in the cell considered.\n To validate the formulation, it has been checked that activating only the Love-Weber stress tensor, and suming all the contributions at the different altitude, we recover the same stress tensor as when using getStress. For my own use, I have troubles with strong overlap between fixed object, so that I made a condition to exclude the contribution to the stress tensor of the fixed objects, this can be desactivated easily if needed (and should be desactivated for the comparison with getStress)." ); py::def("getStressProfile_contact",Shop::getStressProfile_contact,(py::args("volume"),py::args("nCell"),py::args("dz"),py::args("zRef")),"same as getStressProfile, only contact contribution." ); py::def("getDepthProfiles",Shop::getDepthProfiles,(py::args("volume"),py::args("nCell"),py::args("dz"),py::args("zRef"),py::args("activateCond"),py::args("radiusPy"),py::args("direction")),"Compute and return the particle velocity and solid volume fraction (porosity) depth profile along the direction specified (default is z; 0=>x,1=>y,2=>z). For each defined cell z, the k component of the average particle velocity reads: \n\n $^z= \\sum_p V^p v_k^p/\\sum_p V^p$,\n\n where the sum is made over the particles contained in the cell, $v_k^p$ is the k component of the velocity associated to particle p, and $V^p$ is the part of the volume of the particle p contained inside the cell. This definition allows to smooth the averaging, and is equivalent to taking into account the center of the particles only when there is a lot of particles in each cell. As for the solid volume fraction, it is evaluated in the same way: for each defined cell z, it reads: \n\n $<\\phi>^z= \\frac{1}{V_{cell}}\\sum_p V^p$, where $V_{cell}$ is the volume of the cell considered, and $V^p$ is the volume of particle p contained in cell z.\n This function gives depth profiles of average velocity and solid volume fraction, returning the average quantities in each cell of height dz, from the reference horizontal plane at elevation zRef (input parameter) until the plane of elevation zRef+nCell*dz (input parameters). If the argument activateCond is set to true, do the average only on particles of radius equal to radiusPy (input parameter)" ); py::def("getDepthProfiles_center",Shop::getDepthProfiles_center,(py::args("volume"),py::args("nCell"),py::args("dz"),py::args("zRef"),py::args("activateCond"),py::args("radiusPy")),"Same as getDepthProfiles but taking into account particles as points located at the particle center." ); py::def("getCapillaryStress",Shop::getCapillaryStress,(py::args("volume")=0,py::args("mindlin")=false),"Compute and return Love-Weber capillary stress tensor:\n\n $\\sigma^{cap}_{ij}=\\frac{1}{V}\\sum_b l_i^b f^{cap,b}_j$, where the sum is over all interactions, with $l$ the branch vector (joining centers of the bodies) and $f^{cap}$ is the capillary force. $V$ can be passed to the function. If it is not, it will be equal to one in non-periodic cases, or equal to the volume of the cell in periodic cases. Only the CapillaryPhys interaction type is supported presently. Using this function with physics MindlinCapillaryPhys needs to pass True as second argument."); py::def("getBodyIdsContacts",Shop__getBodyIdsContacts,(py::args("bodyID")=0),"Get a list of body-ids, which contacts the given body."); py::def("maxOverlapRatio",maxOverlapRatio,"Return maximum overlap ration in interactions (with :yref:`ScGeom`) of two :yref:`spheres`. The ratio is computed as $\\frac{u_N}{2(r_1 r_2)/r_1+r_2}$, where $u_N$ is the current overlap distance and $r_1$, $r_2$ are radii of the two spheres in contact."); py::def("shiftBodies",shiftBodies,(py::arg("ids"),py::arg("shift")),"Shifts bodies listed in ids without updating their velocities."); py::def("calm",Shop__calm,(py::arg("mask")=-1),"Set translational and rotational velocities of bodies to zero. Applied to all bodies by default. To calm only some bodies, use mask parameter, it will calm only bodies with groupMask compatible to given value"); py::def("setNewVerticesOfFacet",setNewVerticesOfFacet,(py::arg("b"),py::arg("v1"),py::arg("v2"),py::arg("v3")),"Sets new vertices (in global coordinates) to given facet."); py::def("setContactFriction",Shop::setContactFriction,py::arg("angleRad"),"Modify the friction angle (in radians) inside the material classes and existing contacts. The friction for non-dynamic bodies is not modified."); py::def("growParticles",Shop::growParticles,(py::args("multiplier"), py::args("updateMass")=true, py::args("dynamicOnly")=true), "Change the size of spheres and clumps of spheres by the multiplier. If updateMass=True, then the mass and inertia are updated. dynamicOnly=True will select dynamic bodies."); py::def("growParticle",Shop::growParticle,(py::args("bodyID"),py::args("multiplier"), py::args("updateMass")=true), "Change the size of a single sphere (to be implemented: single clump). If updateMass=True, then the mass is updated."); py::def("intrsOfEachBody",intrsOfEachBody,"returns list of lists of interactions of each body"); py::def("numIntrsOfEachBody",numIntrsOfEachBody,"returns list of number of interactions of each body"); py::def("TetrahedronSignedVolume",static_cast&)>(&TetrahedronSignedVolume),"TODO"); py::def("TetrahedronVolume",static_cast&)>(&TetrahedronVolume),"TODO"); py::def("TetrahedronInertiaTensor",TetrahedronInertiaTensor,"TODO"); py::def("TetrahedronCentralInertiaTensor",TetrahedronCentralInertiaTensor,"TODO"); py::def("TetrahedronWithLocalAxesPrincipal",TetrahedronWithLocalAxesPrincipal,"TODO"); py::def("momentum",Shop::momentum,"TODO"); py::def("angularMomentum",Shop::angularMomentum,(py::args("origin")=Vector3r(Vector3r::Zero())),"TODO"); py::def("getSpheresVolume2D",Shop__getSpheresVolume2D,(py::arg("mask")=-1),"Compute the total volume of discs in the simulation, mask parameter is considered"); py::def("voidratio2D",Shop__getVoidRatio2D,(py::arg("zlen")=1),"Compute 2D packing void ratio $\\frac{V-V_s}{V_s}$ where $V$ is overall volume and $V_s$ is volume of disks.\n\n:param float zlen: length in the third direction.\n"); py::def("getStressAndTangent",Shop__getStressAndTangent,(py::args("volume")=0,py::args("symmetry")=true),"Compute overall stress of periodic cell using the same equation as function getStress. In addition, the tangent operator is calculated using the equation published in [Kruyt and Rothenburg1998]_:\n\n.. math:: S_{ijkl}=\\frac{1}{V}\\sum_{c}(k_n n_i l_j n_k l_l + k_t t_i l_j t_k l_l)\n\n:param float volume: same as in function getStress\n:param bool symmetry: make the tensors symmetric.\n\n:return: macroscopic stress tensor and tangent operator as py::tuple"); py::def("setBodyPosition",setBodyPosition,(py::args("id"),py::args("pos"),py::args("axis")="xyz"),"Set a body position from its id and a new vector3r.\n\n:param int id: the body id.\n:param Vector3 pos: the desired updated position.\n:param str axis: the axis along which the position has to be updated (ex: if axis==\"xy\" and pos==Vector3r(r0,r1,r2), r2 will be ignored and the position along z will not be updated)."); py::def("setBodyVelocity",setBodyVelocity,(py::args("id"),py::args("vel"),py::args("axis")="xyz"),"Set a body velocity from its id and a new vector3r.\n\n:param int id: the body id.\n:param Vector3 vel: the desired updated velocity.\n:param str axis: the axis along which the velocity has to be updated (ex: if axis==\"xy\" and vel==Vector3r(r0,r1,r2), r2 will be ignored and the velocity along z will not be updated)."); py::def("setBodyOrientation",setBodyOrientation,(py::args("id"),py::args("ori")),"Set a body orientation from its id and a new Quaternionr.\n\n:param int id: the body id.\n:param Quaternion ori: the desired updated orientation."); py::def("setBodyAngularVelocity",setBodyAngularVelocity,(py::args("id"),py::args("angVel")),"Set a body angular velocity from its id and a new Vector3r.\n\n:param int id: the body id.\n:param Vector3 angVel: the desired updated angular velocity."); py::def("setBodyColor",setBodyColor,(py::args("id"),py::args("color")),"Set a body color from its id and a new Vector3r.\n\n:param int id: the body id.\n:param Vector3 color: the desired updated color."); } trunk-2018.02b/py/_utils.hpp000066400000000000000000000204341324306050200156310ustar00rootroot00000000000000#include #include #include #include #include #include #include #include #include #include #include #include namespace py = boost::python; bool isInBB(Vector3r p, Vector3r bbMin, Vector3r bbMax); py::tuple negPosExtremeIds(int axis, Real distFactor=1.1); py::tuple coordsAndDisplacements(int axis,py::tuple Aabb=py::tuple()); void setRefSe3(); Real PWaveTimeStep(); Real RayleighWaveTimeStep(); py::tuple interactionAnglesHistogram(int axis, int mask=0, size_t bins=20, py::tuple aabb=py::tuple(), bool sphSph=0, Real minProjLen=1e-6); py::tuple bodyNumInteractionsHistogram(py::tuple aabb=py::tuple()); Vector3r inscribedCircleCenter(const Vector3r& v0, const Vector3r& v1, const Vector3r& v2); py::dict getViscoelasticFromSpheresInteraction(Real tc, Real en, Real es); /* reset highlight of all bodies */ void highlightNone(); /*!Sum moments acting on given bodies * * @param ids is the calculated bodies ids * @param axis is the direction of axis with respect to which the moment is calculated. * @param axisPt is a point on the axis. * * The computation is trivial: moment from force is is by definition r×F, where r * is position relative to axisPt; moment from moment is m; such moment per body is * projected onto axis. */ Real sumTorques(py::list ids, const Vector3r& axis, const Vector3r& axisPt); /* Sum forces acting on bodies within mask. * * @param ids list of ids * @param direction direction in which forces are summed * */ Real sumForces(py::list ids, const Vector3r& direction); /* Sum force acting on facets given by their ids in the sense of their respective normals. If axis is given, it will sum forces perpendicular to given axis only (not the the facet normals). */ Real sumFacetNormalForces(vector ids, int axis=-1); /* Set wire display of all/some/none bodies depending on the filter. */ void wireSome(string filter); void wireAll(); void wireNone(); void wireNoSpheres(); /* Tell us whether a point lies in polygon given by array of points. * @param xy is the point that is being tested * @param vertices is Numeric.array (or list or tuple) of vertices of the polygon. * Every row of the array is x and y coordinate, numer of rows is >= 3 (triangle). * * Copying the algorithm from http://www.ecse.rpi.edu/Homepages/wrf/Research/Short_Notes/pnpoly.html * is gratefully acknowledged: * * License to Use: * Copyright (c) 1970-2003, Wm. Randolph Franklin * 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: * 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimers. * 2. Redistributions in binary form must reproduce the above copyright notice in the documentation and/or other materials provided with the distribution. * 3. The name of W. Randolph Franklin may not be used to endorse or promote products derived from this Software without specific prior written permission. * 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. * * http://numpy.scipy.org/numpydoc/numpy-13.html told me how to use Numeric.array from c */ bool pointInsidePolygon(py::tuple xy, py::object vertices); /* Compute area of convex hull when when taking (swept) spheres crossing the plane at coord, perpendicular to axis. All spheres that touch the plane are projected as hexagons on their circumference to the plane. Convex hull from this cloud is computed. The area of the hull is returned. */ Real approxSectionArea(Real coord, int axis); /* Find all interactions deriving from NormShearPhys that cross plane given by a point and normal (the normal may not be normalized in this case, though) and sum forces (both normal and shear) on them. Returns a 3-tuple with the components along global x,y,z axes, which can be viewed as "action from lower part, towards upper part" (lower and upper parts with respect to the plane's normal). (This could be easily extended to return sum of only normal forces or only of shear forces.) */ Vector3r forcesOnPlane(const Vector3r& planePt, const Vector3r& normal); /* Less general than forcesOnPlane, computes force on plane perpendicular to axis, passing through coordinate coord. */ Vector3r forcesOnCoordPlane(Real coord, int axis); py::tuple spiralProject(const Vector3r& pt, Real dH_dTheta, int axis=2, Real periodStart=std::numeric_limits::quiet_NaN(), Real theta0=0); shared_ptr Shop__createExplicitInteraction(Body::id_t id1, Body::id_t id2); Real Shop__unbalancedForce(bool useMaxForce /*false by default*/); py::tuple Shop__totalForceInVolume(); Real Shop__getSpheresVolume(int mask=-1); Real Shop__getSpheresMass(int mask=-1); py::object Shop__kineticEnergy(bool findMaxId=false); Real maxOverlapRatio(); Real Shop__getPorosity(Real volume=-1); Real Shop__getVoxelPorosity(int resolution=200, Vector3r start=Vector3r(0,0,0),Vector3r end=Vector3r(0,0,0)); //Matrix3r Shop__stressTensorOfPeriodicCell(bool smallStrains=false){return Shop::stressTensorOfPeriodicCell(smallStrains);} py::tuple Shop__fabricTensor(Real cutoff=0.0,bool splitTensor=false, Real thresholdForce=NaN); py::tuple Shop__normalShearStressTensors(bool compressionPositive=false, bool splitNormalTensor=false, Real thresholdForce=NaN); py::list Shop__getStressLWForEachBody(); py::list Shop__getBodyIdsContacts(Body::id_t bodyID=-1); Real shiftBodies(py::list ids, const Vector3r& shift); void Shop__calm(int mask=-1); void setNewVerticesOfFacet(const shared_ptr& b, const Vector3r& v1, const Vector3r& v2, const Vector3r& v3); py::list intrsOfEachBody(); py::list numIntrsOfEachBody(); /* The 5 following setters are used to workaround a long-standing bug in the c++/python binding which produces a memory leak (see two links below). * https://bugs.launchpad.net/yade/+bug/1041084 * https://answers.launchpad.net/yade/+question/253112 * It is not in the spirit of Yade Python binding but you can use them if you massively update bodies attributes. * TODO : remove them as soon as the bug is solved. */ /* Set a body position from its id and a new vector3r. * @param id is the body id * @param newPos is the desired updated position * @param axis is the axis along which the position has to be updated (ex: if axis=="xy" and newPos==Vector3r(r0,r1,r2), r2 will be ignored and the position along z will not be updated). */ void setBodyPosition(int id, Vector3r newPos, string axis="xyz"); /* Set a body velocity from its id and a new vector3r. * @param id is the body id * @param newPos is the desired updated velocity * @param axis is the axis along which the velocity has to be updated (ex: if axis=="xy" and newVel==Vector3r(r0,r1,r2), r2 will be ignored and the velocity along z will not be updated). */ void setBodyVelocity(int id, Vector3r newVel, string axis="xyz"); /* Set a body orientation from its id and a new Quaternionr. * @param id is the body id * @param newOri is the desired updated orientation */ void setBodyOrientation(int id, Quaternionr newOri); /* Set a body angular velocity from its id and a new Vector3r. * @param id is the body id * @param newAngVel is the desired updated angular velocity */ void setBodyAngularVelocity(int id, Vector3r newAngVel); /* Set a body color from its id and a new Vector3r. * @param id is the body id * @param newColor is the desired rgb color */ void setBodyColor(int id, Vector3r newColor);trunk-2018.02b/py/bodiesHandling.py000066400000000000000000000201751324306050200171070ustar00rootroot00000000000000# encoding: utf-8 """ Miscellaneous functions, which are useful for handling bodies. """ from yade.wrapper import * import utils,math,numpy from minieigen import * #spheresPackDimensions================================================== def spheresPackDimensions(idSpheres=[],mask=-1): """The function accepts the list of spheres id's or list of bodies and calculates max and min dimensions, geometrical center. :param list idSpheres: list of spheres :param int mask: :yref:`Body.mask` for the checked bodies :return: dictionary with keys ``min`` (minimal dimension, Vector3), ``max`` (maximal dimension, Vector3), ``minId`` (minimal dimension sphere Id, Vector3), ``maxId`` (maximal dimension sphere Id, Vector3), ``center`` (central point of bounding box, Vector3), ``extends`` (sizes of bounding box, Vector3), ``volume`` (volume of spheres, Real), ``mass`` (mass of spheres, Real), ``number`` (number of spheres, int), """ idSpheresIter=[] if (len(idSpheres)<1): #check mask ifSpherMask=[] if (mask>-1): #The case, when only the mask was given, without list of ids for i in O.bodies: if ((i.mask&mask)<>0): ifSpherMask.append(i.id) if (len(ifSpherMask)<2): raise RuntimeWarning("Not enough bodies to analyze with given mask") else: idSpheresIter=ifSpherMask else: raise RuntimeWarning("Only a list of particles with length > 1 can be analyzed") else: idSpheresIter=idSpheres minVal = Vector3.Zero maxVal = Vector3.Zero minId = Vector3.Zero maxId = Vector3.Zero counter = 0 volume = 0.0 mass = 0.0 for i in idSpheresIter: if (type(i).__name__=='int'): b = O.bodies[i] #We have received a list of ID's elif (type(i).__name__=='Body'): b = i #We have recevied a list of bodies else: raise TypeError("Unknow type of data, should be list of int's or bodies's") if (b): spherePosition=b.state.pos #skip non-existent spheres try: sphereRadius=b.shape.radius #skip non-spheres except AttributeError: continue if (mask>-1) and ((mask&b.mask)==0): continue #skip bodies with wrong mask sphereRadiusVec3 = Vector3(sphereRadius,sphereRadius,sphereRadius) sphereMax = spherePosition + sphereRadiusVec3 sphereMin = spherePosition - sphereRadiusVec3 for dim in range(0,3): if ((sphereMax[dim]>maxVal[dim]) or (counter==0)): maxVal[dim]=sphereMax[dim] maxId[dim] = b.id if ((sphereMin[dim]-1): #The case, when only the mask was given, without list of ids for i in O.bodies: if ((i.mask&mask)<>0): ifFacetMask.append(i.id) if (len(ifFacetMask)<2): raise RuntimeWarning("Not enough bodies to analyze with given mask") else: idFacetsIter=ifFacetMask else: raise RuntimeWarning("Only a list of particles with length > 1 can be analyzed") else: idFacetsIter=idFacets minVal = Vector3.Zero maxVal = Vector3.Zero minId = Vector3.Zero maxId = Vector3.Zero counter = 0 for i in idFacetsIter: if (type(i).__name__=='int'): b = O.bodies[i] #We have received a list of ID's elif (type(i).__name__=='Body'): b = i #We have recevied a list of bodies else: raise TypeError("Unknow type of data, should be list of int's or bodies's") if (b): p = b.state.pos o = b.state.ori s = b.shape pt1 = p + o*s.vertices[0] pt2 = p + o*s.vertices[1] pt3 = p + o*s.vertices[2] if (mask>-1) and ((mask&b.mask)==0): continue #skip bodies with wrong mask facetMax = Vector3(max(pt1[0], pt2[0], pt3[0]), max(pt1[1], pt2[1], pt3[1]), max(pt1[2], pt2[2], pt3[2])) facetMin = Vector3(min(pt1[0], pt2[0], pt3[0]), min(pt1[1], pt2[1], pt3[1]), min(pt1[2], pt2[2], pt3[2])) for dim in range(0,3): if ((facetMax[dim]>maxVal[dim]) or (counter==0)): maxVal[dim]=facetMax[dim] maxId[dim] = b.id if ((facetMin[dim]-1): #The case, when only the mask was given, without list of ids for i in O.bodies: if ((i.mask&mask)<>0): ifSpherMask.append(i.id) if (len(ifSpherMask)==0): raise RuntimeWarning("No bodies to modify with given mask") else: idSpheresIter=ifSpherMask else: raise RuntimeWarning("No bodies to modify") else: idSpheresIter=idSpheres dims = spheresPackDimensions(idSpheresIter) ret=[] for i in idSpheresIter: if (type(i).__name__=='int'): b = O.bodies[i] #We have received a list of ID's elif (type(i).__name__=='Body'): b = i #We have recevied a list of bodies else: raise TypeError("Unknown type of data, should be list of int's or bodies") try: sphereRadius=b.shape.radius #skip non-spheres except AttributeError: continue if (mask>-1) and ((mask&b.mask)==0): continue #skip bodies with wrong mask if (copy): b=sphereDuplicate(b) b.state.pos=orientation*(b.state.pos-dims['center'])+dims['center'] b.shape.radius*=scale b.state.mass*=pow(scale, 3) b.state.inertia*=pow(scale, 5) b.state.pos=(b.state.pos-dims['center'])*scale + dims['center'] b.state.pos+=shift if (copy): ret.append(b) if (copy): return ret else: return True #spheresDublicate======================================================= def sphereDuplicate(idSphere): """The functions makes a copy of sphere""" i=idSphere if (type(i).__name__=='int'): b = O.bodies[i] #We have received a list of ID's elif (type(i).__name__=='Body'): b = i #We have recevied a list of bodies else: raise TypeError("Unknown type of data, should be list of int's or bodies") try: sphereRadius=b.shape.radius #skip non-spheres except AttributeError: return False addedBody = utils.sphere(center=b.state.pos,radius=b.shape.radius,fixed=not(b.dynamic),wire=b.shape.wire,color=b.shape.color,highlight=b.shape.highlight,material=b.material,mask=b.mask) return addedBody trunk-2018.02b/py/config.py.in000066400000000000000000000043631324306050200160500ustar00rootroot00000000000000# encoding: utf-8 """ Compile-time configuration for yade. Template file is processed by cmake to create the actual configuration at build-time. """ import os,datetime,os.path prefix='${runtimePREFIX}' if not os.environ.has_key('YADE_PREFIX') else os.environ['YADE_PREFIX'] suffix='${SUFFIX}' libPATH='${LIBRARY_OUTPUT_PATH}' if (libPATH[1:] == '{LIBRARY_OUTPUT_PATH}'): libPATH='lib' libDir=os.path.abspath(prefix+'/'+libPATH+'/yade${SUFFIX}') confDir=os.environ['HOME']+'/.yade${SUFFIX}' libstdcxx='${libstdcxx}' features='${CONFIGURED_FEATS}'.split(' ') if (features[0]==''): features=features[1:] revision='${realVersion}' version='${version}' sourceRoot='${sourceRoot}' # project metadata metadata=dict( short_desc='open-source platform for dynamic computations', long_desc='Extensible open-source framework for discrete numerical models, focused on Discrete Element Method. The computation parts are written in c++ using flexible object model, allowing independent implementation of new alogrithms and interfaces. Python is used for rapid and concise scene construction, simulation control, postprocessing and debugging.\n\n\n\nSee http://www.yade-dem.org/ for documentation and http://www.launchpad.net/yade for the project itself.\n\nThis is version %s with features %s.'%(version,','.join(features)), author='Yade Developers Team', website='http://www.yade-dem.org', author_contact='http://www.launchpad.net/~yade-dev', mailinglist='yade-users@lists.launchpad.net', bugtracker='http://bugs.launchpad.net/yade', copyright='© 2003--%s'%(datetime.date.today().year), license='''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 2 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, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. ''', ) trunk-2018.02b/py/deformableelementsutils.py000066400000000000000000000301741324306050200211130ustar00rootroot00000000000000# encoding: utf-8 # # utility functions for deformable cohesive elements # # burak er buraker88@yandex.com import math,random,doctest,geom,numpy from yade import * from yade.wrapper import * from yade.utils import * try: # use psyco if available import psyco psyco.full() except ImportError: pass from minieigen import * # c++ implementations for performance reasons from yade._utils import * from yade.utils import _commonBodySetup #Deformable Element Node def node(center,radius,dynamic=None,fixed=True,wire=False,color=None,highlight=False,material=-1,mask=1): """Create sphere with given parameters; mass and inertia computed automatically. Last assigned material is used by default (*material* = -1), and utils.defaultMaterial() will be used if no material is defined at all. :param Vector3 center: center :param float radius: radius :param float dynamic: deprecated, see "fixed" :param float fixed: generate the body with all DOFs blocked? :param material: specify :yref:`Body.material`; different types are accepted: * int: O.materials[material] will be used; as a special case, if material==-1 and there is no shared materials defined, utils.defaultMaterial() will be assigned to O.materials[0] * string: label of an existing material that will be used * :yref:`Material` instance: this instance will be used * callable: will be called without arguments; returned Material value will be used (Material factory object, if you like) :param int mask: :yref:`Body.mask` for the body :param wire: display as wire sphere? :param highlight: highlight this body in the viewer? :param Vector3-or-None: body's color, as normalized RGB; random color will be assigned if ``None``. :return: A Body instance with desired characteristics. Creating default shared material if none exists neither is given:: >>> O.reset() >>> from yade import utils >>> len(O.materials) 0 >>> s0=utils.sphere([2,0,0],1) >>> len(O.materials) 1 Instance of material can be given:: >>> s1=utils.sphere([0,0,0],1,wire=False,color=(0,1,0),material=ElastMat(young=30e9,density=2e3)) >>> s1.shape.wire False >>> s1.shape.color Vector3(0,1,0) >>> s1.mat.density 2000.0 Material can be given by label:: >>> O.materials.append(FrictMat(young=10e9,poisson=.11,label='myMaterial')) 1 >>> s2=utils.sphere([0,0,2],1,material='myMaterial') >>> s2.mat.label 'myMaterial' >>> s2.mat.poisson 0.11 Finally, material can be a callable object (taking no arguments), which returns a Material instance. Use this if you don't call this function directly (for instance, through yade.pack.randomDensePack), passing only 1 *material* parameter, but you don't want material to be shared. For instance, randomized material properties can be created like this: >>> import random >>> def matFactory(): return ElastMat(young=1e10*random.random(),density=1e3+1e3*random.random()) ... >>> s3=utils.sphere([0,2,0],1,material=matFactory) >>> s4=utils.sphere([1,2,0],1,material=matFactory) """ b=Body() b.shape=Node(radius=radius,color=color if color else randomColor(),wire=wire,highlight=highlight) V=(4./3)*math.pi*radius**3 geomInert=(2./5.)*V*radius**2 _commonBodySetup(b,V,Vector3(geomInert,geomInert,geomInert),material,pos=center,dynamic=dynamic,fixed=fixed,blockedDOFs='XYZ') b.aspherical=False b.mask=mask b.bounded=True return b def randomColor(): """Return random Vector3 with each component in interval 0…1 (uniform distribution)""" return Vector3(random.random(),random.random(),random.random()) # Meshing def clear_mesh(gmshmeshhandle_): sceneelements=gmshmeshhandle_[1]; #first remove elements and nodes from the scene for elem in sceneelements: bdy=elem[0]; nodes=elem[1]; O.bodies.erase(bdy.id); for node in nodes: O.bodies.erase(node.id); def tetrahedronvolume(nodes): V =0.166666666*numpy.linalg.det([[nodes[0][0], nodes[1][0] ,nodes[2][0] ,nodes[3][0]],[nodes[0][1], nodes[1][1], nodes[2][1] ,nodes[3][1]], [nodes[0][2] ,nodes[1][2], nodes[2][2], nodes[3][2]], [ 1,1,1,1]]); return V def tetrahedral_mesh_generator(filename,tetrahedralelementshape,elementmat,interfaceelementshape,interfacemat): [nodeList,elementList,interactionList]=mshreader(filename) nodes=nodeList meshelements=elementList interfacepairs=interactionList scenebodies=[] for elem in elementList: #Create the corresponding mesh element nodesofelement=[nodes[elem[0]],nodes[elem[1]],nodes[elem[2]],nodes[elem[3]]] noderadius=0.1*(0.75*tetrahedronvolume(nodesofelement))**0.33333333; [elbody, nodebodies]=tetrahedral_element(elementmat,nodesofelement,tetrahedralelementshape,radius=noderadius) scenebodies.append([elbody,nodebodies]) #Create the interfacing element pairs for interpair in interfacepairs: #get element pair elementpair=interpair[0]; #get node pairs pairofnodes=interpair[1] interfaceelementpairs=[] for pair in pairofnodes: firstid=pair[0]; secondid=pair[1]; el1nodebodies=scenebodies[elementpair[0]][1]; el2nodebodies=scenebodies[elementpair[1]][1]; node1=el1nodebodies[firstid] node2=el2nodebodies[secondid] interfaceelementpairs.append([node1, node2]) [elbody, nodebodies]=interaction_element(interfacemat,interfaceelementpairs,interfaceelementshape); scenebodies.append([elbody,nodebodies]) return scenebodies #Read mesh file and get elements nodes and interactions def mshreader(meshfile="file.mesh",shift=Vector3.Zero,scale=1.0,orientation=Quaternion.Identity,**kw): """ Imports volume mesh from gmsh2 file :Parameters: `shift`: [float,float,float] [X,Y,Z] parameter moves the specimen. `scale`: float factor scales the given data. `orientation`: quaternion orientation of the imported mesh `**kw`: (unused keyword arguments) is passed to :yref:`yade.utils.facet` :Returns: list of nodes and elements for the specimen. """ infile = open(meshfile,"r") lines = infile.readlines() infile.close() nodelistVector3=[] elementList=[] interactionList=[] findVerticesString=0 while (lines[findVerticesString].split()[0]<>'$Nodes'): #Find the string with the number of Vertices findVerticesString+=1 findVerticesString+=1 numNodes = int(lines[findVerticesString].split()[0]) for line in lines[findVerticesString+1:numNodes+findVerticesString+1]: data = line.split() nodelistVector3.append(orientation*Vector3(float(data[1])*scale,float(data[2])*scale,float(data[3])*scale)+shift) findElementString=findVerticesString+numNodes while (lines[findElementString].split()[0]<>'$Elements'): #Find the string with the number of Elements findElementString+=1 findElementString+=1 numElements = int(lines[findElementString].split()[0]) for line in lines[findElementString+1:findElementString+numElements+1]: data = line.split() numberofnodes=data[1] if(int(numberofnodes)==4):#tetrahedral element id1 = int(data[5])-1 id2 = int(data[6])-1 id3 = int(data[7])-1 id4 = int(data[8])-1 elementList.append([id1,id2,id3,id4]) #Create the interaction list i=0 numVolElements=elementList.__len__() for elem in elementList: j=i+1 while j0): interactionList.append([Vector2i(i,j),pair]) j=j+1 i=i+1 return [nodelistVector3,elementList,interactionList] def check_coinciding_nodes(element1,element2): pairstemp=[] #for each node of the element1 i=0 j=0 for node1 in element1: for node2 in element2: if(node1==node2): pairstemp.append([i,j]) j=j+1 i=i+1 j=0 if(3<=pairstemp.__len__()): pairs=pairstemp else: pairs=[] return pairs def interaction_element(material,nodepairs,elementshape,radius=0.0015,dynamic=None,fixed=True,wire=False,color=Vector3(1,0,0),highlight=False,mask=1): # triangles for drawing(node indices) #faces=[Vector3(0,1,3),Vector3(1,2,3),Vector3(2,0,3),Vector3(0,1,2)] faces=[]; shape=elementshape() shape.color=color if color else randomColor(); [body_,nodes_]=finite_element(material,shape,nodepairs,faces,radius,interface=True) return [body_,nodes_] def tetrahedral_element(material,nodes,elementshape,radius=0.0015,dynamic=None,fixed=True,wire=False,color=Vector3(1,0,0),highlight=False,mask=1): # triangles for drawing(node indices) #faces are in the format [face_vertex0,face_vertex1,face_vertex2,opposite_vertex] faces=[Vector3(1,2,0),Vector3(2,1,3),Vector3(2,0,3),Vector3(3,1,0)] shape=elementshape() shape.color=color if color else randomColor(); [body_,nodes_]=finite_element(material,shape,nodes,faces,radius) return [body_,nodes_] def finite_element(material,shape,nodes,faces,radius,dynamic=None,fixed=True,wire=False,Color=None,highlight=False,mask=1,interface=False): """Create sphere with given parameters; mass and inertia computed automatically. Last assigned material is used by default (*material* = -1), and utils.defaultMaterial() will be used if no material is defined at all. :param Vector3 center: center :param float radius: radius :param float dynamic: deprecated, see "fixed" :param float fixed: generate the body with all DOFs blocked? :param material: specify :yref:`Body.material`; different types are accepted: * int: O.materials[material] will be used; as a special case, if material==-1 and there is no shared materials defined, utils.defaultMaterial() will be assigned to O.materials[0] * string: label of an existing material that will be used * :yref:`Material` instance: this instance will be used * callable: will be called without arguments; returned Material value will be used (Material factory object, if you like) :param int mask: :yref:`Body.mask` for the body :param wire: display as wire sphere? :param highlight: highlight this body in the viewer? :param Vector3-or-None: body's color, as normalized RGB; random color will be assigned if ``None``. :return: A Body instance with desired characteristics. Creating default shared material if none exists neither is given:: >>> O.reset() >>> from yade import utils >>> len(O.materials) 0 >>> s0=utils.sphere([2,0,0],1) >>> len(O.materials) 1 Instance of material can be given:: >>> s1=utils.sphere([0,0,0],1,wire=False,color=(0,1,0),material=ElastMat(young=30e9,density=2e3)) >>> s1.shape.wire False >>> s1.shape.color Vector3(0,1,0) >>> s1.mat.density 2000.0 Material can be given by label:: >>> O.materials.append(FrictMat(young=10e9,poisson=.11,label='myMaterial')) 1 >>> s2=utils.sphere([0,0,2],1,material='myMaterial') >>> s2.mat.label 'myMaterial' >>> s2.mat.poisson 0.11 Finally, material can be a callable object (taking no arguments), which returns a Material instance. Use this if you don't call this function directly (for instance, through yade.pack.randomDensePack), passing only 1 *material* parameter, but you don't want material to be shared. For instance, randomized material properties can be created like this: >>> import random >>> def matFactory(): return ElastMat(young=1e10*random.random(),density=1e3+1e3*random.random()) ... >>> s3=utils.sphere([0,2,0],1,material=matFactory) >>> s4=utils.sphere([1,2,0],1,material=matFactory) """ b=Body(); b.shape=shape; O.bodies.append(b) nodes_ret=[]; #b.shape.color=Color; #add nodes to the scene and keep them in the nodes list if(interface==False): #Deformable Element for vec in nodes: # Second Example nod=node(vec,radius) O.bodies.append(nod) b.shape.addNode(nod) nodes_ret.append(nod) #add faces for drawing purposes for face in faces: b.shape.addFace(face) else: #Deformable Cohesive Element for nodepair in nodes: # Second Example b.shape.addPair(nodepair[0],nodepair[1]) #add faces for drawing purposes for face in faces: b.shape.addFace(face) V=1;#For symbolical geomInert=Vector3(1,1,1)#For symbolical #Only add body to draw it not for integration, therefore block all dof _commonBodySetup(b,V,geomInert,material,pos=Vector3(0,0,0),dynamic=False,fixed=True,blockedDOFs='xyzXYZ') b.aspherical=False b.mask=mask b.bounded=False return [b,nodes_ret] ##**********************End of Deformable Elements********************************* trunk-2018.02b/py/export.py000066400000000000000000001263371324306050200155250ustar00rootroot00000000000000# encoding: utf-8 """ Export (not only) geometry to various formats. """ from yade.wrapper import * from yade import utils,Matrix3,Vector3 #textExt=============================================================== def textExt(filename, format='x_y_z_r', comment='',mask=-1,attrs=[]): """Save sphere coordinates and other parameters into a text file in specific format. Non-spherical bodies are silently skipped. Users can add here their own specific format, giving meaningful names. The first file row will contain the format name. Be sure to add the same format specification in ymport.textExt. :param string filename: the name of the file, where sphere coordinates will be exported. :param string format: the name of output format. Supported 'x_y_z_r'(default), 'x_y_z_r_matId', 'x_y_z_r_attrs' (use proper comment) :param string comment: the text, which will be added as a comment at the top of file. If you want to create several lines of text, please use '\\\\n#' for next lines. With 'x_y_z_r_attrs' format, the last (or only) line should consist of column headers of quantities passed as attrs (1 comment word for scalars, 3 comment words for vectors and 9 comment words for matrices) :param int mask: export only spheres with the corresponding mask export only spheres with the corresponding mask :param [str] attrs: attributes to be exported with 'x_y_z_r_attrs' format. Each str in the list is evaluated for every body exported with body=b (i.e. 'b.state.pos.norm()' would stand for distance of body from coordinate system origin) :return: number of spheres which were written. :rtype: int """ O=Omega() try: out=open(filename,'w') except: raise RuntimeError("Problem to write into the file") count=0 # TODO use output=[] instrad of ''??? output = '' outputVel='' if (format<>'liggghts_in'): output = '#format ' + format + '\n' if (comment): if format=='x_y_z_r_attrs': cmts = comment.split('\n') for cmt in cmts[:-1]: output += cmt output += '# x y z r ' + cmts[-1] + '\n' else: output += '# ' + comment + '\n' minCoord= Vector3.Zero maxCoord= Vector3.Zero maskNumber = [] for b in O.bodies: try: if (isinstance(b.shape,Sphere) and ((mask<0) or ((mask&b.mask)>0))): if (format=='x_y_z_r'): output+=('%g\t%g\t%g\t%g\n'%(b.state.pos[0],b.state.pos[1],b.state.pos[2],b.shape.radius)) elif (format=='x_y_z_r_matId'): output+=('%g\t%g\t%g\t%g\t%d\n'%(b.state.pos[0],b.state.pos[1],b.state.pos[2],b.shape.radius,b.material.id)) elif (format=='x_y_z_r_attrs'): output+=('%g\t%g\t%g\t%g'%(b.state.pos[0],b.state.pos[1],b.state.pos[2],b.shape.radius)) for cmd in attrs: v = eval(cmd) if isinstance(v,(int,float)): output+='\t%g'%v elif isinstance(v,Vector3): output+='\t%g\t%g\t%g'%tuple(v[i] for i in xrange(3)) elif isinstance(v,Matrix3): output+='\t%g'%tuple(v[i] for i in xrange(9)) output += '\n' elif (format=='id_x_y_z_r_matId'): output+=('%d\t%g\t%g\t%g\t%g\t%d\n'%(b.id,b.state.pos[0],b.state.pos[1],b.state.pos[2],b.shape.radius,b.material.id)) elif (format=='jointedPM'): output+=('%g\t%g\t%g\t%g\t%g\t%g\t%g\t%g\t%g\t%g\t%g\t%g\n'%(b.id,b.state.onJoint,b.state.joint,b.state.jointNormal1[0],b.state.jointNormal1[1],b.state.jointNormal1[2],b.state.jointNormal2[0],b.state.jointNormal2[1],b.state.jointNormal2[2],b.state.jointNormal3[0],b.state.jointNormal3[1],b.state.jointNormal3[2])) elif (format=='liggghts_in'): output+=('%g %g %g %g %g %g %g\n'%(count+1,b.mask,b.shape.radius,b.material.density,b.state.pos[0],b.state.pos[1],b.state.pos[2])) outputVel+=('%g %g %g %g %g %g %g\n'%(count+1,b.state.vel[0],b.state.vel[1],b.state.vel[2],b.state.angVel[0],b.state.angVel[1],b.state.angVel[2])) else: raise RuntimeError("Please, specify a correct format output!"); count+=1 if (count==1): minCoord = b.state.pos - Vector3(b.shape.radius,b.shape.radius,b.shape.radius) maxCoord = b.state.pos + Vector3(b.shape.radius,b.shape.radius,b.shape.radius) else: minCoord = Vector3(min(minCoord[0], b.state.pos[0]-b.shape.radius),min(minCoord[1], b.state.pos[1]-b.shape.radius),min(minCoord[2], b.state.pos[2]-b.shape.radius)) maxCoord = Vector3(max(maxCoord[0], b.state.pos[0]+b.shape.radius),max(maxCoord[1], b.state.pos[1]+b.shape.radius),max(minCoord[2], b.state.pos[2]+b.shape.radius)) if b.mask not in maskNumber: maskNumber.append(b.mask) except AttributeError: pass if (format=='liggghts_in'): outputHeader = 'LIGGGHTS Description\n\n' outputHeader += '%d atoms\n%d atom types\n\n'%(count,len(maskNumber)) outputHeader += '%g %g xlo xhi\n%g %g ylo yhi\n%g %g zlo zhi\n\n'%(minCoord[0],maxCoord[0],minCoord[1],maxCoord[1],minCoord[2],maxCoord[2]) output=outputHeader + 'Atoms\n\n' + output + '\nVelocities\n\n' + outputVel out.write(output) out.close() return count bodies = [b for b in O.bodies if isinstance(b.shape,Sphere) and (True if mask==-1 else b.msak==mask)] data = [] for b in bodies: pos = b.state.pos d = [pos[i] for i in (0,1,2)] for name,command in what: val = eval(command) if isinstance(val,Matrix3): d.extend((val[0,0],val[0,1],val[0,2],val[1,0],val[1,1],val[1,2],val[2,0],val[2,1],val[2,2])) elif isinstance(val,Vector3): d.extend((v[0],v[1],v[2])) elif isinstance(val,(int,float)): d.append(val) else: print "WARNING: export.text: wrong 'what' parameter, output might be corrupted" return 0 data.append(d) dataw = [' '.join('%e'%v for v in d) for d in data] outFile = open(filename,'w') outFile.writelines(dataw) outFile.close() return len(bodies) #textClumps=============================================================== def textClumps(filename, format='x_y_z_r_clumpId', comment='',mask=-1): """Save clumps-members into a text file. Non-clumps members are bodies are silently skipped. :param string filename: the name of the file, where sphere coordinates will be exported. :param string comment: the text, which will be added as a comment at the top of file. If you want to create several lines of text, please use '\\\\n#' for next lines. :param int mask: export only spheres with the corresponding mask export only spheres with the corresponding mask :return: number of clumps, number of spheres which were written. :rtype: int """ O=Omega() try: out=open(filename,'w') except: raise RuntimeError("Problem to write into the file") count=0 countClumps=0 output = '' output = '#format x_y_z_r_clumpId\n' if (comment): output += '# ' + comment + '\n' minCoord= Vector3.Zero maxCoord= Vector3.Zero maskNumber = [] for bC in O.bodies: if bC.isClump: keys = bC.shape.members.keys() countClumps+=1 for ii in keys: try: b = O.bodies[ii] if (isinstance(b.shape,Sphere) and ((mask<0) or ((mask&b.mask)>0))): output+=('%g\t%g\t%g\t%g\t%g\n'%(b.state.pos[0],b.state.pos[1],b.state.pos[2],b.shape.radius,bC.id)) count+=1 except AttributeError: pass out.write(output) out.close() return countClumps,count #textPolyhedra=============================================================== def textPolyhedra(fileName, comment='',mask=-1, explanationComment=True,attrs=[]): """Save polyhedra into a text file. Non-polyhedra bodies are silently skipped. :param string filename: the name of the output file :param string comment: the text, which will be added as a comment at the top of file. If you want to create several lines of text, please use '\\\\n#' for next lines. :param int mask: export only polyhedra with the corresponding mask :param str explanationComment: inclde explanation of format to the beginning of file :return: number of polyhedra which were written. :rtype: int """ count = 0 f = open(fileName,'w') f.writelines('# %s\n'%l for l in [ 'YADE export of polyhedra.', 'Each polyhedron export contains first line with id, nuber of vertices and number of surfaces.', 'x,y,z coordinates of each vertex follows (each vertex on separate line).', 'ids if vertices of individual surfaces follows (numbering from 0, each surface on separate line).', '', 'Example of tetrahedron and cube with random ids:', '23 4 4', '0.1 0.2 0.3','1.3 0.1 -0.1','-0.2 1.2 0','0 -0.1 1.5', '0 2 1','0 3 2','0 1 3','1 2 3', '65 8 6', '4 0 0','5 0 0','4 1 0','5 1 0','4 0 1','5 0 1','4 1 1','5 1 1', '0 2 3 1','0 1 5 4','1 3 7 5','3 2 6 7','2 0 4 6','4 5 7 6', '', ]) if comment: f.write('#\n# %s\n'%comment) for b in O.bodies: if not isinstance(b.shape,Polyhedra) or not mask & b.mask: continue count += 1 vertices = [b.state.pos + b.state.ori*v for v in b.shape.v] surfaces = b.shape.GetSurfaces() strAttrs = '' if attrs: for cmd in attrs: v = eval(cmd) if isinstance(v,(int,float)): strAttrs+=' %g'%v elif isinstance(v,Vector3): strAttrs+=' %g %g %g'%tuple(v[i] for i in xrange(3)) elif isinstance(v,Matrix3): strAttrs+=' %g'%tuple(v[i] for i in xrange(9)) f.write('%d %d %d%s\n'%(b.id,len(vertices),len(surfaces),strAttrs)) f.writelines('%.8e %.8e %.8e\n'%(v[0],v[1],v[2]) for v in vertices) f.writelines(' '.join(str(i) for i in surface)+'\n' for surface in surfaces) f.close() return count #VTKWriter=============================================================== class VTKWriter: """ USAGE: create object vtk_writer = VTKWriter('base_file_name'), add to engines PyRunner with command='vtk_writer.snapshot()' """ def __init__(self,baseName='snapshot',startSnap=0): self.snapCount = startSnap self.baseName=baseName def snapshot(self): import xml.dom.minidom #import xml.dom.ext # python 2.5 and later positions=[]; radii=[] for b in Omega().bodies: if b.mold.name=='Sphere': positions.append(b.phys['se3'][0]) radii.append(b.mold['radius']) # Document and root element doc = xml.dom.minidom.Document() root_element = doc.createElementNS("VTK", "VTKFile") root_element.setAttribute("type", "UnstructuredGrid") root_element.setAttribute("version", "0.1") root_element.setAttribute("byte_order", "LittleEndian") doc.appendChild(root_element) # Unstructured grid element unstructuredGrid = doc.createElementNS("VTK", "UnstructuredGrid") root_element.appendChild(unstructuredGrid) # Piece 0 (only one) piece = doc.createElementNS("VTK", "Piece") piece.setAttribute("NumberOfPoints", str(len(positions))) piece.setAttribute("NumberOfCells", "0") unstructuredGrid.appendChild(piece) ### Points #### points = doc.createElementNS("VTK", "Points") piece.appendChild(points) # Point location data point_coords = doc.createElementNS("VTK", "DataArray") point_coords.setAttribute("type", "Float32") point_coords.setAttribute("format", "ascii") point_coords.setAttribute("NumberOfComponents", "3") points.appendChild(point_coords) string = str() for x,y,z in positions: string += repr(x) + ' ' + repr(y) + ' ' + repr(z) + ' ' point_coords_data = doc.createTextNode(string) point_coords.appendChild(point_coords_data) #### Cells #### cells = doc.createElementNS("VTK", "Cells") piece.appendChild(cells) # Cell locations cell_connectivity = doc.createElementNS("VTK", "DataArray") cell_connectivity.setAttribute("type", "Int32") cell_connectivity.setAttribute("Name", "connectivity") cell_connectivity.setAttribute("format", "ascii") cells.appendChild(cell_connectivity) # Cell location data connectivity = doc.createTextNode("0") cell_connectivity.appendChild(connectivity) cell_offsets = doc.createElementNS("VTK", "DataArray") cell_offsets.setAttribute("type", "Int32") cell_offsets.setAttribute("Name", "offsets") cell_offsets.setAttribute("format", "ascii") cells.appendChild(cell_offsets) offsets = doc.createTextNode("0") cell_offsets.appendChild(offsets) cell_types = doc.createElementNS("VTK", "DataArray") cell_types.setAttribute("type", "UInt8") cell_types.setAttribute("Name", "types") cell_types.setAttribute("format", "ascii") cells.appendChild(cell_types) types = doc.createTextNode("1") cell_types.appendChild(types) #### Data at Points #### point_data = doc.createElementNS("VTK", "PointData") piece.appendChild(point_data) # Particle radii if len(radii) > 0: radiiNode = doc.createElementNS("VTK", "DataArray") radiiNode.setAttribute("Name", "radii") radiiNode.setAttribute("type", "Float32") radiiNode.setAttribute("format", "ascii") point_data.appendChild(radiiNode) string = str() for r in radii: string += repr(r) + ' ' radiiData = doc.createTextNode(string) radiiNode.appendChild(radiiData) #### Cell data (dummy) #### cell_data = doc.createElementNS("VTK", "CellData") piece.appendChild(cell_data) # Write to file and exit outFile = open(self.baseName+'%08d'%self.snapCount+'.vtu', 'w') # xml.dom.ext.PrettyPrint(doc, file) doc.writexml(outFile, newl='\n') outFile.close() self.snapCount+=1 #text=============================================================== def text(filename,mask=-1): """Save sphere coordinates into a text file; the format of the line is: x y z r. Non-spherical bodies are silently skipped. Example added to examples/regular-sphere-pack/regular-sphere-pack.py :param string filename: the name of the file, where sphere coordinates will be exported. :param int mask: export only spheres with the corresponding mask :return: number of spheres which were written. :rtype: int """ return (textExt(filename=filename, format='x_y_z_r',mask=mask)) #VTKExporter=============================================================== class VTKExporter: """Class for exporting data to VTK Simple Legacy File (for example if, for some reason, you are not able to use VTKRecorder). Export of spheres, facets, interactions and polyhedra is supported. USAGE: create object vtkExporter = VTKExporter('baseFileName'), add to engines PyRunner with command='vtkExporter.exportSomething(params)' alternatively just use vtkExporter.exportSomething(...) at the end of the script for instance Example: :ysrc:`examples/test/vtk-exporter/vtkExporter.py`, :ysrc:`examples/test/unv-read/unvReadVTKExport.py`. :param string baseName: name of the exported files. The files would be named baseName-spheres-snapNb.vtk or baseName-facets-snapNb.vtk :param int startSnap: the numbering of files will start form startSnap """ # TODO comments def __init__(self,baseName,startSnap=0): self.spheresSnapCount = startSnap self.facetsSnapCount = startSnap self.intrsSnapCount = startSnap self.polyhedraSnapCount = startSnap self.contactPointsSnapCount = startSnap self.baseName = baseName # auxiliary functions def _warn(self,msg): print "Warning (yade.export.VTKExporter): " + msg def _error(self,msg): print "ERROR (yade.export.VTKExporter): " + msg def _getBodies(self,ids,type): allIds = False if isinstance(ids,str) and ids.lower()=='all': ids=xrange(len(O.bodies)) allIds = True bodies = [] for i in ids: b = O.bodies[i] if not b: continue if not isinstance(b.shape,type): if not allIds: self._warn("body %d is not of type %s"%(i,type)) continue bodies.append(b) if not bodies: self._warn("no bodies...") return bodies def _getInteractions(self,ids): if isinstance(ids,str) and ids.lower()=='all': ids = [(i.id1,i.id2) for i in O.interactions] intrs = [(i,j) for i,j in ids] if not intrs: self._warn("no interactions ...") return intrs def exportSpheres(self,ids='all',what=[],comment="comment",numLabel=None,useRef=False): """exports spheres (positions and radius) and defined properties. :param [int]|"all" ids: if "all", then export all spheres, otherwise only spheres from integer list :param [tuple(2)] what: which additional quantities (other than the position and the radius) to export. parameter is list of couple (name,command). Name is string under which it is save to vtk, command is string to evaluate. Note that the bodies are labeled as b in this function. Scalar, vector and tensor variables are supported. For example, to export velocity (with name particleVelocity) and the distance form point (0,0,0) (named as dist) you should write: ... what=[('particleVelocity','b.state.vel'),('dist','b.state.pos.norm()', ... :param string comment: comment to add to vtk file :param int numLabel: number of file (e.g. time step), if unspecified, the last used value + 1 will be used :param bool useRef: if False (default), use current position of the spheres for export, use reference position otherwise """ # get list of bodies to export bodies = self._getBodies(ids,Sphere) if not bodies: return nBodies = len(bodies) # output file fName = self.baseName+'-spheres-%08d'%(numLabel if numLabel else self.spheresSnapCount)+'.vtk' outFile = open(fName, 'w') # head outFile.write("# vtk DataFile Version 3.0.\n%s\nASCII\n\nDATASET POLYDATA\nPOINTS %d double\n"%(comment,nBodies)) # write position of spheres for b in bodies: pos = b.state.refPos if useRef else b.state.pos if not O.periodic else O.cell.wrap(b.state.pos) outFile.write("%g %g %g\n"%(pos[0],pos[1],pos[2])) # write radius outFile.write("\nPOINT_DATA %d\nSCALARS radius double 1\nLOOKUP_TABLE default\n"%(nBodies)) for b in bodies: outFile.write("%g\n"%(b.shape.radius)) # write additional data from 'what' param for name,command in what: # for each name... test = eval(command) # ... eval one example to see what type (float, Vector3, Matrix3) the result is ... # ... and write appropriate header line and loop over all bodies and write appropriate vtk line(s) if isinstance(test,Matrix3): outFile.write("\nTENSORS %s double\n"%(name)) for b in bodies: t = eval(command) outFile.write("%g %g %g\n%g %g %g\n%g %g %g\n\n"%(t[0,0],t[0,1],t[0,2],t[1,0],t[1,1],t[1,2],t[2,0],t[2,1],t[2,2])) elif isinstance(test,Vector3): outFile.write("\nVECTORS %s double\n"%(name)) for b in bodies: v = eval(command) outFile.write("%g %g %g\n"%(v[0],v[1],v[2])) elif isinstance(test,(int,float)): outFile.write("\nSCALARS %s double 1\nLOOKUP_TABLE default\n"%(name)) for b in bodies: outFile.write("%g\n"%(eval(command))) else: self._warn("exportSpheres: wrong 'what' parameter, vtk output might be corrupted'") outFile.close() self.spheresSnapCount += 1 def exportFacets(self,ids='all',what=[],comment="comment",numLabel=None): """ exports facets (positions) and defined properties. Facets are exported with multiplicated nodes :param [int]|"all" ids: if "all", then export all facets, otherwise only facets from integer list :param [tuple(2)] what: see exportSpheres :param string comment: comment to add to vtk file :param int numLabel: number of file (e.g. time step), if unspecified, the last used value + 1 will be used """ # get list of bodies to export bodies = self._getBodies(ids,Facet) if not bodies: return nBodies = len(bodies) # output file fName = self.baseName+'-facets-%08d'%(numLabel if numLabel else self.facetsSnapCount)+'.vtk' outFile = open(fName, 'w') # head outFile.write("# vtk DataFile Version 3.0.\n%s\nASCII\n\nDATASET POLYDATA\nPOINTS %d double\n"%(comment,3*nBodies)) # write vertices for b in bodies: p = b.state.pos o = b.state.ori s = b.shape pt1 = p + o*s.vertices[0] pt2 = p + o*s.vertices[1] pt3 = p + o*s.vertices[2] outFile.write("%g %g %g\n"%(pt1[0],pt1[1],pt1[2])) outFile.write("%g %g %g\n"%(pt2[0],pt2[1],pt2[2])) outFile.write("%g %g %g\n"%(pt3[0],pt3[1],pt3[2])) # write facets outFile.write("\nPOLYGONS %d %d\n"%(nBodies,4*nBodies)) i = 0 for b in bodies: outFile.write("3 %d %d %d\n"%(i,i+1,i+2)) i += 3 # write additional data from 'what' param if what: outFile.write("\nCELL_DATA %d"%(nBodies)) # see exportSpheres for explanation of this code block for name,command in what: test = eval(command) if isinstance(test,Matrix3): outFile.write("\nTENSORS %s double\n"%(name)) for b in bodies: t = eval(command) outFile.write("%g %g %g\n%g %g %g\n%g %g %g\n\n"%(t[0,0],t[0,1],t[0,2],t[1,0],t[1,1],t[1,2],t[2,0],t[2,1],t[2,2])) if isinstance(test,Vector3): outFile.write("\nVECTORS %s double\n"%(name)) for b in bodies: v = eval(command) outFile.write("%g %g %g\n"%(v[0],v[1],v[2])) else: outFile.write("\nSCALARS %s double 1\nLOOKUP_TABLE default\n"%(name)) for b in bodies: outFile.write("%g\n"%(eval(command))) outFile.close() self.facetsSnapCount += 1 def exportFacetsAsMesh(self,ids='all',connectivityTable=None,what=[],comment="comment",numLabel=None): """ exports facets (positions) and defined properties. Facets are exported as mesh (not with multiplicated nodes). Therefore additional parameters connectivityTable is needed :param [int]|"all" ids: if "all", then export all facets, otherwise only facets from integer list :param [tuple(2)] what: see exportSpheres :param string comment: comment to add to vtk file :param int numLabel: number of file (e.g. time step), if unspecified, the last used value + 1 will be used :param [(float,float,float)|Vector3] nodes: list of coordinates of nodes :param [(int,int,int)] connectivityTable: list of node ids of individual elements (facets) """ # get list of bodies to export bodies = self._getBodies(ids,Facet) ids = [b.id for b in bodies] if not bodies: return nBodies = len(bodies) if connectivityTable is None: self._error("'connectivityTable' not specified") return if nBodies != len(connectivityTable): self._error("length of 'connectivityTable' does not match length of 'ids', no export") return # nodes nodes = [Vector3.Zero for i in xrange(max(max(e) for e in connectivityTable)+1)] for id,e in zip(ids,connectivityTable): b = bodies[id] p = b.state.pos o = b.state.ori s = b.shape pt1 = p + o*s.vertices[0] pt2 = p + o*s.vertices[1] pt3 = p + o*s.vertices[2] nodes[e[0]] = pt1 nodes[e[1]] = pt2 nodes[e[2]] = pt3 # output file fName = self.baseName+'-facets-%08d'%(numLabel if numLabel else self.facetsSnapCount)+'.vtk' outFile = open(fName, 'w') # head outFile.write("# vtk DataFile Version 3.0.\n%s\nASCII\n\nDATASET POLYDATA\nPOINTS %d double\n"%(comment,len(nodes))) # write vertices for node in nodes: outFile.write("%g %g %g\n"%(node[0],node[1],node[2])) # write facets outFile.write("\nPOLYGONS %d %d\n"%(len(connectivityTable),4*len(connectivityTable))) for e in connectivityTable: outFile.write("3 %d %d %d\n"%e) # write additional data from 'what' param if what: outFile.write("\nCELL_DATA %d"%(nBodies)) # see exportSpheres for explanation of this code block for name,command in what: test = eval(command) if isinstance(test,Matrix3): outFile.write("\nTENSORS %s double\n"%(name)) for b in bodies: t = eval(command) outFile.write("%g %g %g\n%g %g %g\n%g %g %g\n\n"%(t[0,0],t[0,1],t[0,2],t[1,0],t[1,1],t[1,2],t[2,0],t[2,1],t[2,2])) if isinstance(test,Vector3): outFile.write("\nVECTORS %s double\n"%(name)) for b in bodies: v = eval(command) outFile.write("%g %g %g\n"%(v[0],v[1],v[2])) else: outFile.write("\nSCALARS %s double 1\nLOOKUP_TABLE default\n"%(name)) for b in bodies: outFile.write("%g\n"%(eval(command))) outFile.close() self.facetsSnapCount += 1 def exportInteractions(self,ids='all',what=[],verticesWhat=[],comment="comment",numLabel=None,useRef=False): """exports interactions and defined properties. :param [(int,int)]|"all" ids: if "all", then export all interactions, otherwise only interactions from (int,int) list :param [tuple(2)] what: what to export. parameter is a list of (name,command) pair. Name is string under which it is saved to vtk, command is string to evaluate. Note that the interactions are labeled as i in this function. Scalar, vector and tensor variables are supported. For example, to export the stiffness difference (named as dStiff) from a certain value (1e9) you should write: ... what=[('dStiff','i.phys.kn-1e9'), ... :param [tuple(2|3)] verticesWhat: what to export on connected bodies. Bodies are labeled as 'b' (or 'b1' and 'b2' if you need to treat both bodies differently) :param string comment: comment to add to vtk file :param int numLabel: number of file (e.g. time step), if unspecified, the last used value + 1 will be used """ # get list of interactions to export intrs = self._getInteractions(ids) if not intrs: return nIntrs = len(intrs) # output file fName = self.baseName+'-intrs-%08d'%(numLabel if numLabel else self.intrsSnapCount)+'.vtk' outFile = open(fName, 'w') # head outFile.write("# vtk DataFile Version 3.0.\n%s\nASCII\n\nDATASET POLYDATA\nPOINTS %d double\n"%(comment,2*nIntrs)) # write coords of intrs bodies (also taking into account possible periodicity for ii,jj in intrs: i = O.interactions[ii,jj] pos = O.bodies[ii].state.refPos if useRef else O.bodies[ii].state.pos outFile.write("%g %g %g\n"%(pos[0],pos[1],pos[2])) pos = (O.bodies[jj].state.refPos if useRef else O.bodies[jj].state.pos) + (O.cell.hSize*i.cellDist if O.periodic else Vector3.Zero) outFile.write("%g %g %g\n"%(pos[0],pos[1],pos[2])) # write interactions as lines outFile.write("LINES %d %d\n"%(nIntrs,3*nIntrs)) for j,i in enumerate(intrs): outFile.write("2 %d %d\n"%(2*j,2*j+1)) # write additional data from 'what' param if what: outFile.write("\nCELL_DATA %d\n"%(nIntrs)) for i in O.interactions: if i.isReal: break # see exportSpheres for explanation of this code block for name,command in what: test = eval(command) if isinstance(test,Matrix3): outFile.write("\nTENSORS %s double\n"%(name)) for ii,jj in intrs: i = O.interactions[ii,jj] t = eval(command) outFile.write("%g %g %g\n%g %g %g\n%g %g %g\n\n"%(t[0,0],t[0,1],t[0,2],t[1,0],t[1,1],t[1,2],t[2,0],t[2,1],t[2,2])) elif isinstance(test,Vector3): outFile.write("\nVECTORS %s double\n"%(name)) for ii,jj in intrs: i = O.interactions[ii,jj] v = eval(command) outFile.write("%g %g %g\n"%(v[0],v[1],v[2])) elif isinstance(test,(int,float)): outFile.write("\nSCALARS %s double 1\nLOOKUP_TABLE default\n"%(name)) for ii,jj in intrs: i = O.interactions[ii,jj] outFile.write("%g\n"%(eval(command))) else: self._warn("exportInteractions: wrong 'what' parameter, vtk output might be corrupted") # write additional data of bodies if verticesWhat: outFile.write("\nPOINT_DATA %d\n"%(2*nIntrs)) b = b1 = b2 = O.bodies[0] # see exportSpheres for explanation of this code block for vWhat in verticesWhat: lw = len(vWhat) if lw == 2: name,command = vWhat test = eval(command) elif lw == 3: name,command1,command2 = vWhat test = eval(command1) if isinstance(test,Matrix3): outFile.write("\nTENSORS %s double\n"%(name)) for ii,jj in intrs: i = O.interactions[ii,jj] b1 = O.bodies[ii] b2 = O.bodies[jj] if lw==2: for b in (b1,b2): t = eval(command) outFile.write("%g %g %g\n%g %g %g\n%g %g %g\n\n"%(t[0,0],t[0,1],t[0,2],t[1,0],t[1,1],t[1,2],t[2,0],t[2,1],t[2,2])) elif lw==3: t1 = eval(command1) t2 = eval(command2) outFile.write("%g %g %g\n%g %g %g\n%g %g %g\n\n"%(t1[0,0],t1[0,1],t1[0,2],t1[1,0],t1[1,1],t1[1,2],t1[2,0],t1[2,1],t1[2,2])) outFile.write("%g %g %g\n%g %g %g\n%g %g %g\n\n"%(t2[0,0],t2[0,1],t2[0,2],t2[1,0],t2[1,1],t2[1,2],t2[2,0],t2[2,1],t2[2,2])) elif isinstance(test,Vector3): outFile.write("\nVECTORS %s double\n"%(name)) for ii,jj in intrs: i = O.interactions[ii,jj] b1 = O.bodies[ii] b2 = O.bodies[jj] if lw==2: for b in (b1,b2): v = eval(command) outFile.write("%g %g %g\n"%(v[0],v[1],v[2])) elif lw==3: v1 = eval(command1) v2 = eval(command2) outFile.write("%g %g %g\n"%(v1[0],v1[1],v1[2])) outFile.write("%g %g %g\n"%(v2[0],v2[1],v2[2])) elif isinstance(test,(int,float)): outFile.write("\nSCALARS %s double 1\nLOOKUP_TABLE default\n"%(name)) for ii,jj in intrs: i = O.interactions[ii,jj] b1 = O.bodies[ii] b2 = O.bodies[jj] if lw==2: for b in (b1,b2): outFile.write("%g\n"%(eval(command))) elif lw==3: outFile.write("%g\n"%(eval(command1))) outFile.write("%g\n"%(eval(command2))) else: self._warn("exportInteractions: wrong 'what' parameter, vtk output might be corrupted") outFile.close() self.intrsSnapCount += 1 def exportContactPoints(self,ids='all',what=[],useRef={},comment="comment",numLabel=None): """exports contact points (CPs) and defined properties. :param [(int,int)] ids: see exportInteractions :param [tuple(2)] what: what to export. parameter is list of couple (name,command). Name is string under which it is saved to vtk, command is string to evaluate. Note that the CPs are labeled as i in this function (according to their interaction). Scalar, vector and tensor variables are supported. For example, to export the stiffness difference (named as dStiff) from a certain value (1e9) you should write: ... what=[('dStiff','i.phys.kn-1e9'), ... :param {Interaction:Vector3} useRef: if not specified, current position used. Otherwise use position from dict using interactions as keys. Interactions not in dict are not exported :param string comment: comment to add to vtk file :param int numLabel: number of file (e.g. time step), if unspecified, the last used value + 1 will be used """ # get list of interactions to export if useRef: useRef = dict(((i.id1,i.id2),v) for i,v in useRef.iteritems()) intrs = useRef.keys() else: intrs = self._getInteractions(ids) if not intrs: return nIntrs = len(intrs) # output file fName = self.baseName+'-cps-%08d'%(numLabel if numLabel else self.contactPointsSnapCount)+'.vtk' outFile = open(fName, 'w') # head outFile.write("# vtk DataFile Version 3.0.\n%s\nASCII\n\nDATASET POLYDATA\nPOINTS %d double\n"%(comment,nIntrs)) # write coords of contact points for ii,jj in intrs: if useRef: pos = useRef[(ii,jj)] else: i = O.interactions[ii,jj] pos = i.geom.contactPoint outFile.write("%g %g %g\n"%(pos[0],pos[1],pos[2])) # see exportSpheres for explanation of this code block if what: outFile.write("\nPOINT_DATA %d\n"%(nIntrs)) for i in O.interactions: if i.isReal: break for name,command in what: test = eval(command) if isinstance(test,Matrix3): outFile.write("\nTENSORS %s double\n"%(name)) for ii,jj in intrs: try: i = O.interactions[ii,jj] t = eval(command) except IndexError: t = Matrix3.Zero # TODO? outFile.write("%g %g %g\n%g %g %g\n%g %g %g\n\n"%(t[0,0],t[0,1],t[0,2],t[1,0],t[1,1],t[1,2],t[2,0],t[2,1],t[2,2])) elif isinstance(test,Vector3): outFile.write("\nVECTORS %s double\n"%(name)) for ii,jj in intrs: try: i = O.interactions[ii,jj] v = eval(command) except IndexError: v = Vector3.Zero # TODO? outFile.write("%g %g %g\n"%(v[0],v[1],v[2])) elif isinstance(test,(int,float)): outFile.write("\nSCALARS %s double 1\nLOOKUP_TABLE default\n"%(name)) for ii,jj in intrs: try: i = O.interactions[ii,jj] f = eval(command) except IndexError: f = 0. # TODO? outFile.write("%g\n"%(f)) else: self._warn("exportContacPoints: wrong 'what' parameter, vtk output might be corrupted'") outFile.close() self.contactPointsSnapCount += 1 def exportPeriodicCell(self,comment="comment",numLabel=None): """exports the Cell geometry for periodic simulations. :param string comment: comment to add to vtk file :param int numLabel: number of file (e.g. time step), if unspecified, the last used value + 1 will be used """ if not O.periodic: self._warn("exportPeriodicCell: scene is not periodic, no export...") return hSize = O.cell.hSize fName = self.baseName+'-periCell-%08d'%(numLabel if numLabel else self.intrsSnapCount)+'.vtk' outFile = open(fName, 'w') outFile.write("# vtk DataFile Version 3.0.\n%s\nASCII\n\nDATASET UNSTRUCTURED_GRID\nPOINTS 8 double\n"%(comment)) vertices = [ hSize*Vector3(0,0,1), hSize*Vector3(0,1,1), hSize*Vector3(1,1,1), hSize*Vector3(1,0,1), hSize*Vector3(0,0,0), hSize*Vector3(0,1,0), hSize*Vector3(1,1,0), hSize*Vector3(1,0,0), ] for v in vertices: outFile.write('%g %g %g\n'%(v[0],v[1],v[2])) outFile.write('\nCELLS 1 9\n') outFile.write('8 0 1 2 3 4 5 6 7\n') outFile.write('\nCELL_TYPES 1\n12\n') outFile.close() def exportPolyhedra(self,ids='all',what=[],comment="comment",numLabel=None,useRef=False): """Exports polyhedrons and defined properties. :param ids: if "all", then export all polyhedrons, otherwise only polyhedrons from integer list :type ids: [int] | "all" :param what: which additional quantities (in addition to the positions) to export. parameter is list of couple (name,command). Name is string under which it is saved to vtk, command is string to evaluate. Note that the bodies are labeled as b in this function. Scalar, vector and tensor variables are supported. For example, to export velocity (named as particleVelocity) and the distance from point (0,0,0) (named as dist) you should write: ... what=[('particleVelocity','b.state.vel'),('dist','b.state.pos.norm()', ... :type what: [tuple(2)] :param string comment: comment to add to vtk file :param int numLabel: number of file (e.g. time step), if unspecified, the last used value + 1 will be used """ # TODO useRef? # get list of bodies to export bodies = self._getBodies(ids,Polyhedra) # TODO if not bodies: return # number of vertices nVertices = sum(len(b.shape.v) for b in bodies) # export polyherda as a set of triangle faces bodyFaces = [] for b in bodies: ff = [] f = b.shape.GetSurfaceTriangulation() for i in xrange(len(f)/3): ff.append([f[3*i+j] for j in (0,1,2)]) bodyFaces.append(ff) # output file nFaces = sum(len(f) for f in bodyFaces) fName = self.baseName+'-polyhedra-%08d'%(numLabel if numLabel else self.polyhedraSnapCount)+'.vtk' outFile = open(fName, 'w') # head outFile.write("# vtk DataFile Version 3.0.\n%s\nASCII\n\nDATASET POLYDATA\nPOINTS %d double\n"%(comment,nVertices)) # write position of vertices if useRef: dspls = [] for b in bodies: bPos = b.state.pos bOri = b.state.ori brPos = b.state.refPos brOri = b.state.refOri for v in b.shape.v: rPos = brPos + brOri*v pos = bPos + bOri*v outFile.write("%g %g %g\n"%(rPos[0],rPos[1],rPos[2])) dspls.append(pos-rPos) else: for b in bodies: bPos = b.state.pos bOri = b.state.ori for v in b.shape.v: pos = bPos + bOri*v outFile.write("%g %g %g\n"%(pos[0],pos[1],pos[2])) # write triangle faces outFile.write("\nPOLYGONS %d %d\n"%(nFaces,4*nFaces)) j = 0 for i,b in enumerate(bodies): faces = bodyFaces[i] for face in faces: t = tuple([j+ii for ii in face]) outFile.write("3 %d %d %d\n"%t) j += len(b.shape.v) # write additional data from 'what' param if useRef: outFile.write("\nPOINT_DATA %d\n"%(len(dspls))) outFile.write("\nVECTORS displacement double\n") for v in dspls: outFile.write("%g %g %g\n"%(v[0],v[1],v[2])) if what: outFile.write("\nCELL_DATA %d"%(nFaces)) # see exportSpheres for explanation of this code block for name,command in what: test = eval(command) if isinstance(test,Matrix3): outFile.write("\nTENSORS %s double\n"%(name)) for i,b in enumerate(bodies): t = eval(command) for f in bodyFaces[i]: outFile.write("%g %g %g\n%g %g %g\n%g %g %g\n\n"%(t[0,0],t[0,1],t[0,2],t[1,0],t[1,1],t[1,2],t[2,0],t[2,1],t[2,2])) elif isinstance(test,Vector3): outFile.write("\nVECTORS %s double\n"%(name)) for i,b in enumerate(bodies): v = eval(command) for f in bodyFaces[i]: outFile.write("%g %g %g\n"%(v[0],v[1],v[2])) elif isinstance(test,(int,float)): outFile.write("\nSCALARS %s double 1\nLOOKUP_TABLE default\n"%(name)) for i,b in enumerate(bodies): e = eval(command) for f in bodyFaces[i]: outFile.write("%g\n"%e) else: self._warn("exportPolyhedra: wrong 'what' parameter, vtk output might be corrupted") outFile.close() self.polyhedraSnapCount += 1 #gmshGeoExport=============================================================== def gmshGeo(filename, comment='',mask=-1,accuracy=-1): """Save spheres in geo-file for the following using in GMSH (http://www.geuz.org/gmsh/doc/texinfo/) program. The spheres can be there meshed. :param string filename: the name of the file, where sphere coordinates will be exported. :param int mask: export only spheres with the corresponding mask export only spheres with the corresponding mask :param float accuracy: the accuracy parameter, which will be set for the poinst in geo-file. By default: 1./10. of the minimal sphere diameter. :return: number of spheres which were exported. :rtype: int """ O=Omega() try: out=open(filename,'w') except: raise RuntimeError("Problem to write into the file") count=0 #out.write('#format \n') # Find the minimal diameter if (accuracy<0.0): dMin = -1.0 for b in O.bodies: try: if (isinstance(b.shape,Sphere) and ((mask<0) or ((mask&b.mask)>0))): if (((dMin>0.0) and (dMin>b.shape.radius*2.0)) or (dMin<0.0)): dMin = b.shape.radius*2.0 except AttributeError: pass accuracy = dMin/10.0 # Export bodies PTS = 0 CRS = 0 out.write('Acc = %g;\n'%(accuracy)) for b in O.bodies: try: if (isinstance(b.shape,Sphere) and ((mask<0) or ((mask&b.mask)>0))): r = b.shape.radius x = b.state.pos[0] y = b.state.pos[1] z = b.state.pos[2] out.write('Rad = %g;\n'%(r)) out.write('Point(%d) = {%g, %g, %g, Acc};\n\ Point(%d) = {%g, %g, %g, Acc};\n\ Point(%d) = {%g, %g, %g, Acc};\n\ Point(%d) = {%g, %g, %g, Acc};\n\ Point(%d) = {%g, %g, %g, Acc};\n\ Point(%d) = {%g, %g, %g, Acc};\n\ Point(%d) = {%g, %g, %g, Acc};\n\n'%( PTS+1, x, y, z, PTS+2, r+x, y, z, PTS+3, -r+x, y, z, PTS+4, x, y, r+z, PTS+5, x, y, -r+z, PTS+6, x, r+y, z, PTS+7, x, -r+y, z )) out.write('\n\ Circle(%d) = {%d, %d, %d};\n\ Circle(%d) = {%d, %d, %d};\n\ Circle(%d) = {%d, %d, %d};\n\ Circle(%d) = {%d, %d, %d};\n\ Circle(%d) = {%d, %d, %d};\n\ Circle(%d) = {%d, %d, %d};\n\ Circle(%d) = {%d, %d, %d};\n\ Circle(%d) = {%d, %d, %d};\n\ Circle(%d) = {%d, %d, %d};\n\ Circle(%d) = {%d, %d, %d};\n\ Circle(%d) = {%d, %d, %d};\n\ Circle(%d) = {%d, %d, %d};\n'%( CRS+1, PTS+4, PTS+1, PTS+6, CRS+2, PTS+6, PTS+1, PTS+5, CRS+3, PTS+6, PTS+1, PTS+3, CRS+4, PTS+3, PTS+1, PTS+7, CRS+5, PTS+7, PTS+1, PTS+5, CRS+6, PTS+7, PTS+1, PTS+2, CRS+7, PTS+2, PTS+1, PTS+6, CRS+8, PTS+7, PTS+1, PTS+4, CRS+9, PTS+2, PTS+1, PTS+5, CRS+10, PTS+5, PTS+1, PTS+3, CRS+11, PTS+3, PTS+1, PTS+4, CRS+12, PTS+4, PTS+1, PTS+2, )) out.write('\n\ Line Loop(%d) = {%d, %d, %d}; Ruled Surface(%d) = {%d};\n\ Line Loop(%d) = {%d, %d, %d}; Ruled Surface(%d) = {%d};\n\ Line Loop(%d) = {%d, %d, %d}; Ruled Surface(%d) = {%d};\n\ Line Loop(%d) = {%d, %d, %d}; Ruled Surface(%d) = {%d};\n\ Line Loop(%d) = {%d, %d, %d}; Ruled Surface(%d) = {%d};\n\ Line Loop(%d) = {%d, %d, %d}; Ruled Surface(%d) = {%d};\n\ Line Loop(%d) = {%d, %d, %d}; Ruled Surface(%d) = {%d};\n\ Line Loop(%d) = {%d, %d, %d}; Ruled Surface(%d) = {%d};\n\n\ '%( (CRS+13), +(CRS+1), -(CRS+7), -(CRS+12), (CRS+14), (CRS+13), (CRS+15), +(CRS+7), +(CRS+2), -(CRS+9), (CRS+16), (CRS+15), (CRS+17), +(CRS+2), +(CRS+10), -(CRS+3), (CRS+18), (CRS+17), (CRS+19), +(CRS+3), +(CRS+11), +(CRS+1), (CRS+20), (CRS+19), (CRS+21), +(CRS+8), +(CRS+12), -(CRS+6), (CRS+22), (CRS+21), (CRS+23), +(CRS+4), +(CRS+8), -(CRS+11), (CRS+24), (CRS+23), (CRS+25), +(CRS+5), +(CRS+10), (CRS+4), (CRS+26), (CRS+25), (CRS+27), +(CRS+6), +(CRS+9), -(CRS+5), (CRS+28), (CRS+27), )) PTS+=7 CRS+=28 count+=1 except AttributeError: pass out.close() return count # external vtk manipulation =============================================================== def text2vtk(inFileName,outFileName): """Converts text file (created by :yref:`yade.export.textExt` function) into vtk file. See :ysrc:`examples/test/paraview-spheres-solid-section/export_text.py` example :param str inFileName: name of input text file :param str outFileName: name of output vtk file """ fin = open(inFileName) fout = open(outFileName,'w') lastLine = None line = '#' while line.startswith('#'): lastLine = line line = fin.readline() columns = lastLine.split()[5:] data = [line.split() for line in fin] fin.close() n = len(data) fout.write('# vtk DataFile Version 3.0.\ncomment\nASCII\n\nDATASET POLYDATA\nPOINTS %d double\n'%(n)) fout.writelines('%s %s %s\n'%(d[0],d[1],d[2]) for d in data) fout.write("\nPOINT_DATA %d\nSCALARS radius double 1\nLOOKUP_TABLE default\n"%(n)) fout.writelines('%s\n'%(d[3]) for d in data) for i,c in enumerate(columns): fout.write("\nSCALARS %s double 1\nLOOKUP_TABLE default\n"%(c)) fout.writelines('%s\n'%(d[4+i]) for d in data) fout.close() def text2vtkSection(inFileName,outFileName,point,normal=(1,0,0)): """Converts section through spheres from text file (created by :yref:`yade.export.textExt` function) into vtk file. See :ysrc:`examples/test/paraview-spheres-solid-section/export_text.py` example :param str inFileName: name of input text file :param str outFileName: name of output vtk file :param Vector3|(float,float,float) point: coordinates of a point lying on the section plane :param Vector3|(float,float,float) normal: normal vector of the section plane """ from math import sqrt norm = sqrt(pow(normal[0],2)+pow(normal[1],2)+pow(normal[2],2)) normal = (normal[0]/norm,normal[1]/norm,normal[2]/norm) # def computeD(point,normal): # from point and normal computes parameter d in plane equation ax+by+cz+d=0 return -normal[0]*point[0] - normal[1]*point[1] - normal[2]*point[2] def computeDistanceFromPlane(dat,point,normal,d=None): # computes distance of sphere dat from plane (point,normal) x,y,z = computeProjectionOnPlane(dat,point,normal,d) cx,cy,cz = dat[0],dat[1],dat[2] return sqrt(pow(x-cx,2)+pow(y-cy,2)+pow(z-cz,2)) def computeProjectionOnPlane(self,point,normal,d=None): # computes projection of sphere dat on plane (point,normal) if d is None: d = computeD(point,normal) nx,ny,nz = normal[0],normal[1],normal[2] cx,cy,cz = dat[0],dat[1],dat[2] t = (-d-nx*cx-ny*cy-nz*cz) / (nx*nx+ny*ny+nz*nz) x,y,z = cx+t*nx, cy+t*ny, cz+t*nz return x,y,z # fin = open(inFileName) lastLine = None line = '#' while line.startswith('#'): lastLine = line line = fin.readline() columns = lastLine.split()[4:] data = [[float(w) for w in line.split()] for line in fin] fin.close() # d = computeD(point,normal) circs = [] for dat in data: r = dat[3] dst = computeDistanceFromPlane(dat,point,normal,d) if dst > r: continue x,y,z = computeProjectionOnPlane(dat,point,normal,d) rNew = sqrt(r*r-dst*dst) dNew = [x,y,z,rNew,r] dNew.extend(dat[4:]) circs.append(dNew) n = len(circs) fout = open(outFileName,'w') fout.write('# vtk DataFile Version 3.0.\ncomment\nASCII\n\nDATASET POLYDATA\nPOINTS %d double\n'%(n)) fout.writelines('%g %g %g\n'%(c[0],c[1],c[2]) for c in circs) fout.write("\nPOINT_DATA %d\nSCALARS radius double 1\nLOOKUP_TABLE default\n"%(n)) fout.writelines('%g\n'%(c[3]) for c in circs) fout.write("\nSCALARS radiusOrig double 1\nLOOKUP_TABLE default\n") fout.writelines('%g\n'%(c[4]) for c in circs) fout.write("\nVECTORS normal double\n") fout.writelines("%g %g %g\n"%normal for i in circs) for i,c in enumerate(columns): fout.write("\nSCALARS %s double 1\nLOOKUP_TABLE default\n"%(c)) fout.writelines('%s\n'%(c[4+i]) for c in circs) fout.close() trunk-2018.02b/py/geom.py000066400000000000000000000571321324306050200151270ustar00rootroot00000000000000# encoding: utf-8 """ Creates geometry objects from facets. """ from yade.wrapper import * import utils,math,numpy from minieigen import * #facetBox=============================================================== def facetBox(center,extents,orientation=Quaternion((0,1,0),0.0),wallMask=63,**kw): """ Create arbitrarily-aligned box composed of facets, with given center, extents and orientation. If any of the box dimensions is zero, corresponding facets will not be created. The facets are oriented outwards from the box. :param Vector3 center: center of the box :param Vector3 extents: lengths of the box sides :param Quaternion orientation: orientation of the box :param bitmask wallMask: determines which walls will be created, in the order -x (1), +x (2), -y (4), +y (8), -z (16), +z (32). The numbers are ANDed; the default 63 means to create all walls :param \*\*kw: (unused keyword arguments) passed to :yref:`yade.utils.facet` :returns: list of facets forming the box """ return facetParallelepiped(center=center, extents=extents, height=extents[2], orientation=orientation, wallMask=wallMask, **kw) #facetParallelepiped=============================================================== def facetParallelepiped(center,extents,height,orientation=Quaternion((0,1,0),0.0),wallMask=63,**kw): """ Create arbitrarily-aligned Parallelepiped composed of facets, with given center, extents, height and orientation. If any of the parallelepiped dimensions is zero, corresponding facets will not be created. The facets are oriented outwards from the parallelepiped. :param Vector3 center: center of the parallelepiped :param Vector3 extents: lengths of the parallelepiped sides :param Real height: height of the parallelepiped (along axis z) :param Quaternion orientation: orientation of the parallelepiped :param bitmask wallMask: determines which walls will be created, in the order -x (1), +x (2), -y (4), +y (8), -z (16), +z (32). The numbers are ANDed; the default 63 means to create all walls :param \*\*kw: (unused keyword arguments) passed to :yref:`yade.utils.facet` :returns: list of facets forming the parallelepiped """ if (height<0): raise RuntimeError("The height should have the positive value"); if (height>extents[2]): raise RuntimeError("The height should be smaller or equal as extents[2]"); #Defense from zero dimensions if (wallMask>63): print "wallMask must be 63 or less" wallMask=63 if (extents[0]==0): wallMask=1 elif (extents[1]==0): wallMask=4 elif (extents[2]==0 or height==0): wallMask=16 if (((extents[0]==0) and (extents[1]==0)) or ((extents[0]==0) and (extents[2]==0)) or ((extents[1]==0) and (extents[2]==0))): raise RuntimeError("Please, specify at least 2 none-zero dimensions in extents!"); # ___________________________ #inclination angle beta = 0; dx = 0 if (height>0): beta = math.asin(height/extents[2]) dx = math.cos(beta)*extents[2] mn,mx=[-extents[i] for i in 0,1,2],[extents[i] for i in 0,1,2] def doWall(a,b,c,d): return [utils.facet((a,b,c),**kw),utils.facet((a,c,d),**kw)] ret=[] mn[2] = -height mx[2] = +height A=orientation*Vector3(mn[0],mn[1],mn[2])+center B=orientation*Vector3(mx[0],mn[1],mn[2])+center C=orientation*Vector3(mx[0],mx[1],mn[2])+center D=orientation*Vector3(mn[0],mx[1],mn[2])+center E=orientation*Vector3(mn[0]+dx,mn[1],mx[2])+center F=orientation*Vector3(mx[0]+dx,mn[1],mx[2])+center G=orientation*Vector3(mx[0]+dx,mx[1],mx[2])+center H=orientation*Vector3(mn[0]+dx,mx[1],mx[2])+center if wallMask&1: ret+=doWall(A,D,H,E) if wallMask&2: ret+=doWall(B,F,G,C) if wallMask&4: ret+=doWall(A,E,F,B) if wallMask&8: ret+=doWall(D,C,G,H) if wallMask&16: ret+=doWall(A,B,C,D) if wallMask&32: ret+=doWall(E,H,G,F) return ret #facetCylinder========================================================== def facetCylinder(center,radius,height,orientation=Quaternion((0,1,0),0.0), segmentsNumber=10,wallMask=7,angleRange=None,closeGap=False, radiusTopInner=-1, radiusBottomInner=-1, **kw): """ Create arbitrarily-aligned cylinder composed of facets, with given center, radius, height and orientation. Return List of facets forming the cylinder; :param Vector3 center: center of the created cylinder :param float radius: cylinder radius :param float height: cylinder height :param float radiusTopInner: inner radius of cylinders top, -1 by default :param float radiusBottomInner: inner radius of cylinders bottom, -1 by default :param Quaternion orientation: orientation of the cylinder; the reference orientation has axis along the $+x$ axis. :param int segmentsNumber: number of edges on the cylinder surface (>=5) :param bitmask wallMask: determines which walls will be created, in the order up (1), down (2), side (4). The numbers are ANDed; the default 7 means to create all walls :param (θmin,Θmax) angleRange: allows one to create only part of bunker by specifying range of angles; if ``None``, (0,2*pi) is assumed. :param bool closeGap: close range skipped in angleRange with triangular facets at cylinder bases. :param \*\*kw: (unused keyword arguments) passed to utils.facet; """ # check zero dimentions if (radius<=0): raise RuntimeError("The radius should have the positive value"); if (height<=0): wallMask = 1; return facetCylinderConeGenerator(center=center,radiusTop=radius,height=height, orientation=orientation,segmentsNumber=segmentsNumber,wallMask=wallMask, angleRange=angleRange,closeGap=closeGap, radiusTopInner=radiusTopInner, radiusBottomInner=radiusBottomInner, **kw) #facetSphere========================================================== def facetSphere(center,radius,thetaResolution=8,phiResolution=8,returnElementMap=False,**kw): """ Create arbitrarily-aligned sphere composed of facets, with given center, radius and orientation. Return List of facets forming the sphere. Parameters inspired by ParaView sphere glyph :param Vector3 center: center of the created sphere :param float radius: sphere radius :param int thetaResolution: number of facets around "equator" :param int phiResolution: number of facets between "poles" + 1 :param bool returnElementMap: returns also tuple of nodes ((x1,y1,z1),(x2,y2,z2),...) and elements ((id01,id02,id03),(id11,id12,id13),...) if true, only facets otherwise :param \*\*kw: (unused keyword arguments) passed to utils.facet; """ # check zero dimentions if (radius<=0): raise RuntimeError("The radius should have the positive value"); if (thetaResolution<3): raise RuntimeError("thetaResolution must be > 3"); if (phiResolution<3): raise RuntimeError("phiResolution must be > 3"); r,c0,c1,c2 = radius,center[0],center[1],center[2] nodes = [Vector3(c0,c1,c2+radius)] phis = numpy.linspace(math.pi/(phiResolution-1),math.pi,phiResolution-2,endpoint=False) thetas = numpy.linspace(0,2*math.pi,thetaResolution,endpoint=False) nodes.extend((Vector3(c0+r*math.cos(theta)*math.sin(phi),c1+r*math.sin(theta)*math.sin(phi),c2+r*math.cos(phi)) for phi in phis for theta in thetas)) nodes.append(Vector3(c0,c1,c2-radius)) n = len(nodes)-1 elements = [(0,i+1,i+2) for i in xrange(thetaResolution-1)] elements.append((0,1,thetaResolution)) for j in xrange(0,phiResolution-3): k = j*thetaResolution + 1 elements.extend((k+i,k+i+1,k+i+thetaResolution) for i in xrange(thetaResolution-1)) elements.append((k,k+thetaResolution-1,k+2*thetaResolution-1)) elements.extend((k+i+thetaResolution,k+i+1+thetaResolution,k+i+1) for i in xrange(thetaResolution-1)) elements.append((k+2*thetaResolution-1,k+thetaResolution,k)) elements.extend((n,n-i-1,n-i-2) for i in xrange(thetaResolution-1)) elements.append((n,n-1,n-thetaResolution)) facets = [utils.facet(tuple(nodes[node] for node in elem),**kw) for elem in elements] if returnElementMap: return facets,nodes,elements return facets #facetCone============================================================== def facetCone(center,radiusTop,radiusBottom,height,orientation=Quaternion((0,1,0),0.0), segmentsNumber=10,wallMask=7,angleRange=None,closeGap=False, radiusTopInner=-1, radiusBottomInner=-1, **kw): """ Create arbitrarily-aligned cone composed of facets, with given center, radius, height and orientation. Return List of facets forming the cone; :param Vector3 center: center of the created cylinder :param float radiusTop: cone top radius :param float radiusBottom: cone bottom radius :param float radiusTopInner: inner radius of cones top, -1 by default :param float radiusBottomInner: inner radius of cones bottom, -1 by default :param float height: cone height :param Quaternion orientation: orientation of the cone; the reference orientation has axis along the $+x$ axis. :param int segmentsNumber: number of edges on the cone surface (>=5) :param bitmask wallMask: determines which walls will be created, in the order up (1), down (2), side (4). The numbers are ANDed; the default 7 means to create all walls :param (θmin,Θmax) angleRange: allows one to create only part of cone by specifying range of angles; if ``None``, (0,2*pi) is assumed. :param bool closeGap: close range skipped in angleRange with triangular facets at cylinder bases. :param \*\*kw: (unused keyword arguments) passed to utils.facet; """ # check zero dimentions if ((radiusBottom<=0) and (radiusTop<=0)): raise RuntimeError("The radiusBottom or radiusTop should have the positive value"); return facetCylinderConeGenerator(center=center,radiusTop=radiusTop, radiusBottom=radiusBottom,height=height,orientation=orientation,segmentsNumber=segmentsNumber, wallMask=wallMask,angleRange=angleRange,closeGap=closeGap, radiusTopInner=radiusTopInner, radiusBottomInner=radiusBottomInner, **kw) #facetPolygon=========================================================== def facetPolygon(center,radiusOuter,orientation=Quaternion((0,1,0),0.0),segmentsNumber=10,angleRange=None,radiusInner=0,**kw): """ Create arbitrarily-aligned polygon composed of facets, with given center, radius (outer and inner) and orientation. Return List of facets forming the polygon; :param Vector3 center: center of the created cylinder :param float radiusOuter: outer radius :param float radiusInner: inner height (can be 0) :param Quaternion orientation: orientation of the polygon; the reference orientation has axis along the $+x$ axis. :param int segmentsNumber: number of edges on the polygon surface (>=3) :param (θmin,Θmax) angleRange: allows one to create only part of polygon by specifying range of angles; if ``None``, (0,2*pi) is assumed. :param \*\*kw: (unused keyword arguments) passed to utils.facet; """ # check zero dimentions if (abs(angleRange[1]-angleRange[0])>2.0*math.pi): raise RuntimeError("The |angleRange| cannot be larger 2.0*math.pi"); return facetPolygonHelixGenerator(center=center,radiusOuter=radiusOuter,orientation=orientation,segmentsNumber=segmentsNumber,angleRange=angleRange,radiusInner=radiusInner,**kw) #facetHelix=========================================================== def facetHelix(center,radiusOuter,pitch,orientation=Quaternion((0,1,0),0.0),segmentsNumber=10,angleRange=None,radiusInner=0,**kw): """ Create arbitrarily-aligned helix composed of facets, with given center, radius (outer and inner), pitch and orientation. Return List of facets forming the helix; :param Vector3 center: center of the created cylinder :param float radiusOuter: outer radius :param float radiusInner: inner height (can be 0) :param Quaternion orientation: orientation of the helix; the reference orientation has axis along the $+x$ axis. :param int segmentsNumber: number of edges on the helix surface (>=3) :param (θmin,Θmax) angleRange: range of angles; if ``None``, (0,2*pi) is assumed. :param \*\*kw: (unused keyword arguments) passed to utils.facet; """ # check zero dimentions if (pitch<=0): raise RuntimeError("The pitch should have the positive value"); return facetPolygonHelixGenerator(center=center,radiusOuter=radiusOuter,orientation=orientation,segmentsNumber=segmentsNumber,angleRange=angleRange,radiusInner=radiusInner,pitch=pitch,**kw) #facetBunker============================================================ def facetBunker(center,dBunker,dOutput,hBunker,hOutput,hPipe=0.0,orientation=Quaternion((0,1,0),0.0),segmentsNumber=10,wallMask=4,angleRange=None,closeGap=False,**kw): """ Create arbitrarily-aligned bunker, composed of facets, with given center, radii, heights and orientation. Return List of facets forming the bunker; .. code-block:: none dBunker ______________ | | | | | | hBunker | | | | | | |____________| \ / \ / \ / hOutput \ / \____/ | | |____| hPipe dOutput :param Vector3 center: center of the created bunker :param float dBunker: bunker diameter, top :param float dOutput: bunker output diameter :param float hBunker: bunker height :param float hOutput: bunker output height :param float hPipe: bunker pipe height :param Quaternion orientation: orientation of the bunker; the reference orientation has axis along the $+x$ axis. :param int segmentsNumber: number of edges on the bunker surface (>=5) :param bitmask wallMask: determines which walls will be created, in the order up (1), down (2), side (4). The numbers are ANDed; the default 7 means to create all walls :param (θmin,Θmax) angleRange: allows one to create only part of bunker by specifying range of angles; if ``None``, (0,2*pi) is assumed. :param bool closeGap: close range skipped in angleRange with triangular facets at cylinder bases. :param \*\*kw: (unused keyword arguments) passed to utils.facet; """ # check zero dimentions if (dBunker<=0): raise RuntimeError("The diameter dBunker should have the positive value"); if (dOutput<=0): raise RuntimeError("The diameter dOutput should have the positive value"); if (hBunker<0): raise RuntimeError("The height hBunker should have the positive or or zero"); if (hOutput<=0): raise RuntimeError("The height hOutput should have the positive value"); if (hPipe<0): raise RuntimeError("The height hPipe should have the positive value or zero"); ret=[] if ((hPipe>0) or (wallMask&2)): centerPipe = Vector3(0,0,hPipe/2.0) ret+=facetCylinder(center=centerPipe,radius=dOutput/2.0,height=hPipe,segmentsNumber=segmentsNumber,wallMask=wallMask&6,angleRange=angleRange,closeGap=closeGap,**kw) centerOutput = Vector3(0.0,0.0,hPipe+hOutput/2.0) ret+=facetCone(center=centerOutput,radiusTop=dBunker/2.0,radiusBottom=dOutput/2.0,height=hOutput,segmentsNumber=segmentsNumber,wallMask=wallMask&4,angleRange=angleRange,closeGap=closeGap,**kw) if (hBunker>0): centerBunker = Vector3(0.0,0.0,hPipe+hOutput+hBunker/2.0) ret+=facetCylinder(center=centerBunker,radius=dBunker/2.0,height=hBunker,segmentsNumber=segmentsNumber,wallMask=wallMask&5,angleRange=angleRange,closeGap=closeGap,**kw) for i in ret: i.state.pos=orientation*(i.state.pos)+Vector3(center) i.state.ori=orientation return ret #facetPolygonHelixGenerator================================================== def facetPolygonHelixGenerator(center,radiusOuter,pitch=0,orientation=Quaternion((0,1,0),0.0),segmentsNumber=10,angleRange=None,radiusInner=0,**kw): """ Please, do not use this function directly! Use geom.facetPloygon and geom.facetHelix instead. This is the base function for generating polygons and helixes from facets. """ # check zero dimentions if (segmentsNumber<3): raise RuntimeError("The segmentsNumber should be at least 3"); if (radiusOuter<=0): raise RuntimeError("The radiusOuter should have the positive value"); if (radiusInner<0): raise RuntimeError("The radiusInner should have the positive value or 0"); if angleRange==None: angleRange=(0,2*math.pi) anglesInRad = numpy.linspace(angleRange[0], angleRange[1], segmentsNumber+1, endpoint=True) heightsInRad = numpy.linspace(0, pitch*(abs(angleRange[1]-angleRange[0])/(2.0*math.pi)), segmentsNumber+1, endpoint=True) POuter=[]; PInner=[]; PCenter=[]; z=0; for i in anglesInRad: XOuter=radiusOuter*math.cos(i); YOuter=radiusOuter*math.sin(i); POuter.append(Vector3(XOuter,YOuter,heightsInRad[z])) PCenter.append(Vector3(0,0,heightsInRad[z])) if (radiusInner<>0): XInner=radiusInner*math.cos(i); YInner=radiusInner*math.sin(i); PInner.append(Vector3(XInner,YInner,heightsInRad[z])) z+=1 for i in range(0,len(POuter)): POuter[i]=orientation*POuter[i]+center PCenter[i]=orientation*PCenter[i]+center if (radiusInner<>0): PInner[i]=orientation*PInner[i]+center ret=[] for i in range(1,len(POuter)): if (radiusInner==0): ret.append(utils.facet((PCenter[i],POuter[i],POuter[i-1]),**kw)) else: ret.append(utils.facet((PInner[i-1],POuter[i-1],POuter[i]),**kw)) ret.append(utils.facet((PInner[i],PInner[i-1],POuter[i]),**kw)) return ret #facetCylinderConeGenerator============================================= def facetCylinderConeGenerator(center,radiusTop,height,orientation=Quaternion((0,1,0),0.0), segmentsNumber=10,wallMask=7,angleRange=None,closeGap=False, radiusBottom=-1, radiusTopInner=-1, radiusBottomInner=-1, **kw): """ Please, do not use this function directly! Use geom.facetCylinder and geom.facetCone instead. This is the base function for generating cylinders and cones from facets. :param float radiusTop: top radius :param float radiusBottom: bottom radius :param \*\*kw: (unused keyword arguments) passed to utils.facet; """ #For cylinders top and bottom radii are equal if (radiusBottom == -1): radiusBottom = radiusTop if ((radiusTopInner > 0 and radiusTopInner > radiusTop) or (radiusBottomInner > 0 and radiusBottomInner > radiusBottom)): raise RuntimeError("The internal radius cannot be larger than outer"); # check zero dimentions if (segmentsNumber<3): raise RuntimeError("The segmentsNumber should be at least 3"); if (height<0): raise RuntimeError("The height should have the positive value"); if angleRange==None: angleRange=(0,2*math.pi) if (abs(angleRange[1]-angleRange[0])>2.0*math.pi): raise RuntimeError("The |angleRange| cannot be larger 2.0*math.pi"); if (angleRange[1] 0): XTopIn=radiusTopInner*math.cos(i); YTopIn=radiusTopInner*math.sin(i); PTopIn.append(Vector3(XTopIn,YTopIn,+height/2)) XBottom=radiusBottom*math.cos(i); YBottom=radiusBottom*math.sin(i); PBottom.append(Vector3(XBottom,YBottom,-height/2)) if (radiusBottomInner > 0): XBottomIn=radiusBottomInner*math.cos(i); YBottomIn=radiusBottomInner*math.sin(i); PBottomIn.append(Vector3(XBottomIn,YBottomIn,-height/2)) for i in range(0,len(PTop)): PTop[i]=orientation*PTop[i]+center PBottom[i]=orientation*PBottom[i]+center if (len(PTopIn)>1): PTopIn[i]=orientation*PTopIn[i]+center if (len(PBottomIn)>1): PBottomIn[i]=orientation*PBottomIn[i]+center ret=[] for i in range(2,len(PTop)): if (wallMask&1)and(radiusTop!=0): if (len(PTopIn)>1): ret.append(utils.facet((PTop[i-1],PTopIn[i],PTopIn[i-1]),**kw)) ret.append(utils.facet((PTop[i-1],PTop[i],PTopIn[i]),**kw)) else: ret.append(utils.facet((PTop[0],PTop[i],PTop[i-1]),**kw)) if (wallMask&2)and(radiusBottom!=0): if (len(PBottomIn)>1): ret.append(utils.facet((PBottom[i-1],PBottomIn[i],PBottomIn[i-1]),**kw)) ret.append(utils.facet((PBottom[i-1],PBottom[i],PBottomIn[i]),**kw)) else: ret.append(utils.facet((PBottom[0],PBottom[i-1],PBottom[i]),**kw)) if wallMask&4: if (radiusBottom!=0): ret.append(utils.facet((PTop[i],PBottom[i],PBottom[i-1]),**kw)) if (radiusTop!=0): ret.append(utils.facet((PBottom[i-1],PTop[i-1],PTop[i]),**kw)) if (closeGap): if (wallMask&1)and(radiusTop!=0)and(abs(((angleRange[1]-angleRange[0])) > math.pi)): pts=[(radiusTop*math.cos(angleRange[i]),radiusTop*math.sin(angleRange[i])) for i in (0,1)] pp=[(pts[0][0],pts[0][1],+height/2.0), (pts[1][0],pts[1][1],+height/2.0), (0,0,+height/2.0)] pp=[orientation*p+center for p in pp] ret.append(utils.facet(pp,**kw)) if (wallMask&2)and(radiusBottom!=0)and(abs(((angleRange[1]-angleRange[0])) > math.pi)): pts=[(radiusBottom*math.cos(angleRange[i]),radiusBottom*math.sin(angleRange[i])) for i in (0,1)] pp=[(0,0,-height/2.0), (pts[1][0],pts[1][1],-height/2.0), (pts[0][0],pts[0][1],-height/2.0)] pp=[orientation*p+center for p in pp] ret.append(utils.facet(pp,**kw)) if (wallMask&4): ptsBottom=[(radiusBottom*math.cos(angleRange[i]),radiusBottom*math.sin(angleRange[i])) for i in (0,1)] ptsTop=[(radiusTop*math.cos(angleRange[i]),radiusTop*math.sin(angleRange[i])) for i in (0,1)] if (abs(((angleRange[1]-angleRange[0])) >= math.pi)): if (radiusBottom!=0)and(radiusTop!=0): #Cylinder pp=[(ptsBottom[0][0],ptsBottom[0][1],-height/2.0),(ptsBottom[1][0],ptsBottom[1][1],-height/2.0),(ptsTop[0][0],ptsTop[0][1],height/2.0)] pp=[orientation*p+center for p in pp] ret.append(utils.facet(pp,**kw)) pp=[(ptsBottom[1][0],ptsBottom[1][1],-height/2.0), (ptsTop[1][0],ptsTop[1][1],height/2.0), (ptsTop[0][0],ptsTop[0][1],height/2.0)] pp=[orientation*p+center for p in pp] ret.append(utils.facet(pp,**kw)) elif (radiusBottom==0)and(radiusTop!=0): #ConeTop pp=[(ptsTop[1][0],ptsTop[1][1],height/2.0), (ptsTop[0][0],ptsTop[0][1],height/2.0), (0,0,-height/2.0)] pp=[orientation*p+center for p in pp] ret.append(utils.facet(pp,**kw)) elif (radiusTop==0)and(radiusBottom!=0): #ConeBottom pp=[(0,0,height/2.0),(ptsBottom[0][0],ptsBottom[0][1],-height/2.0),(ptsBottom[1][0],ptsBottom[1][1],-height/2.0)] pp=[orientation*p+center for p in pp] ret.append(utils.facet(pp,**kw)) else: if (radiusBottom!=0)and(radiusTop!=0): #Cylinder pp=[(ptsBottom[0][0],ptsBottom[0][1],-height/2.0),(0,0,-height/2.0),(ptsTop[0][0],ptsTop[0][1],height/2.0)] pp=[orientation*p+center for p in pp] ret.append(utils.facet(pp,**kw)) pp=[(0,0,-height/2.0), (0,0,height/2.0), (ptsTop[0][0],ptsTop[0][1],height/2.0)] pp=[orientation*p+center for p in pp] ret.append(utils.facet(pp,**kw)) pp=[(0,0,-height/2.0),(ptsBottom[1][0],ptsBottom[1][1],-height/2.0),(0,0,height/2.0)] pp=[orientation*p+center for p in pp] ret.append(utils.facet(pp,**kw)) pp=[(ptsBottom[1][0],ptsBottom[1][1],-height/2.0), (ptsTop[1][0],ptsTop[1][1],height/2.0), (0,0,height/2.0)] pp=[orientation*p+center for p in pp] ret.append(utils.facet(pp,**kw)) elif (radiusBottom==0)and(radiusTop!=0): #ConeTop pp=[(0,0,height/2.0), (ptsTop[0][0],ptsTop[0][1],height/2.0), (0,0,-height/2.0)] pp=[orientation*p+center for p in pp] ret.append(utils.facet(pp,**kw)) pp=[(ptsTop[1][0],ptsTop[1][1],height/2.0), (0,0,height/2.0), (0,0,-height/2.0)] pp=[orientation*p+center for p in pp] ret.append(utils.facet(pp,**kw)) elif (radiusTop==0)and(radiusBottom!=0): #ConeBottom pp=[(0,0,height/2.0),(ptsBottom[0][0],ptsBottom[0][1],-height/2.0),(0,0,-height/2.0)] pp=[orientation*p+center for p in pp] ret.append(utils.facet(pp,**kw)) pp=[(0,0,height/2.0),(0,0,-height/2.0),(ptsBottom[1][0],ptsBottom[1][1],-height/2.0)] pp=[orientation*p+center for p in pp] ret.append(utils.facet(pp,**kw)) return ret trunk-2018.02b/py/gridpfacet.py000066400000000000000000000541031324306050200163030ustar00rootroot00000000000000# encoding: utf-8 # # 2015 © Bruno Chareyre # 2015 © Anna Effeindzourou # 2015 © François Kneib # 2015 © Klaus Thoeni """ Helper functions for creating cylinders, grids and membranes. For more details on this type of elements see [Effeindzourou2016]_, [Effeindzourou2015a]_, [Bourrier2013]_,. For examples using :yref:`GridConnections`, see * :ysrc:`examples/grids/CohesiveGridConnectionSphere.py` * :ysrc:`examples/grids/GridConnection_Spring.py` * :ysrc:`examples/grids/Simple_Grid_Falling.py` * :ysrc:`examples/grids/Simple_GridConnection_Falling.py` For examples using :yref:`PFacets`, see * :ysrc:`examples/pfacet/gts-pfacet.py` * :ysrc:`examples/pfacet/mesh-pfacet.py` * :ysrc:`examples/pfacet/pfacetcreators.py` """ import math,random,doctest,geom,numpy from yade.wrapper import * try: # use psyco if available import psyco psyco.full() except ImportError: pass from yade import utils from yade._utils import createInteraction from minieigen import * def chainedCylinder(begin=Vector3(0,0,0),end=Vector3(1.,0.,0.),radius=0.2,dynamic=None,fixed=False,wire=False,color=None,highlight=False,material=-1,mask=1): """ Create and connect a chainedCylinder with given parameters. The shape generated by repeted calls of this function is the Minkowski sum of polyline and sphere. :param Real radius: radius of sphere in the Minkowski sum. :param Vector3 begin: first point positioning the line in the Minkowski sum :param Vector3 last: last point positioning the line in the Minkowski sum In order to build a correct chain, last point of element of rank N must correspond to first point of element of rank N+1 in the same chain (with some tolerance, since bounding boxes will be used to create connections. :return: Body object with the :yref:`ChainedCylinder` :yref:`shape`. .. note:: :yref:`ChainedCylinder` is deprecated and will be removed in the future, use :yref:`GridConnection` instead. See :yref:`yade.gridpfacet.cylinder` and :yref:`yade.gridpfacet.cylinderConnection`. """ import warnings warnings.warn('\033[1;31mchainedCylinder is deprecated and will be removed in the future, use GridConnection instead.\033[1;0m',category=UserWarning) segment=end-begin b=Body() b.shape=ChainedCylinder(radius=radius,length=segment.norm(),color=color if color else utils.randomColor(),wire=wire,highlight=highlight) b.shape.segment=segment V=2*(4./3)*math.pi*radius**3 geomInert=(2./5.)*V*radius**2+b.shape.length*b.shape.length*2*(4./3)*math.pi*radius**3 b.state=ChainedState() b.state.addToChain(O.bodies.append(b)) utils._commonBodySetup(b,V,Vector3(geomInert,geomInert,geomInert),material,pos=begin,resetState=False,dynamic=dynamic,fixed=fixed) b.mask=mask b.bound=Aabb(color=[0,1,0]) b.state.ori.setFromTwoVectors(Vector3(0.,0.,1.),segment) if (end == begin): b.state.ori = Quaternion((1,0,0),0) return b def gridNode(center,radius,dynamic=None,fixed=False,wire=False,color=None,highlight=False,material=-1): """ Create a :yref:`GridNode` which is needed to set up :yref:`GridConnections`. See documentation of :yref:`yade.utils.sphere` for meaning of parameters. :return: Body object with the :yref:`gridNode` :yref:`shape`. """ b=Body() b.shape=GridNode(radius=radius,color=color if color else utils.randomColor(),wire=wire,highlight=highlight) #V=(4./3)*math.pi*radius**3 # will be overwritten by the connection V=0. geomInert=(2./5.)*V*radius**2 # will be overwritten by the connection utils._commonBodySetup(b,V,Vector3(geomInert,geomInert,geomInert),material,pos=center,dynamic=dynamic,fixed=fixed) b.aspherical=False b.bounded=False b.mask=0 # avoid contact detection with the nodes. Manual interaction will be set for them in "gridConnection" below. return b def gridConnection(id1,id2,radius,wire=False,color=None,highlight=False,material=-1,mask=1,cellDist=None): """ Create a :yref:`GridConnection` by connecting two :yref:`GridNodes`. :param id1,id2: the two :yref:`GridNodes` forming the cylinder. :param float radius: radius of the cylinder. Note that the radius needs to be the same as the one for the :yref:`GridNodes`. :param Vector3 cellDist: for periodic boundary conditions, see :yref:`Interaction.cellDist`. Note: periodic boundary conditions for gridConnections are not yet implemented! See documentation of :yref:`yade.utils.sphere` for meaning of other parameters. :return: Body object with the :yref:`GridConnection` :yref:`shape`. .. note:: The material of the :yref:`GridNodes` will be used to set the constitutive behaviour of the internal connection, i.e., the constitutive behaviour of the cylinder. The material of the :yref:`GridConnection` is used for interactions with other (external) bodies. """ b=Body() b.shape=GridConnection(radius=radius,color=color if color else utils.randomColor(),wire=wire,highlight=highlight) sph1=O.bodies[id1] ; sph2=O.bodies[id2] i=createInteraction(id1,id2) nodeMat=sph1.material b.shape.node1=sph1 ; b.shape.node2=sph2 sph1.shape.addConnection(b) ; sph2.shape.addConnection(b) if(O.periodic): if(cellDist!=None): i.cellDist=cellDist segt=sph2.state.pos + O.cell.hSize*i.cellDist - sph1.state.pos else: segt=sph2.state.pos - sph1.state.pos L=segt.norm() V=0.5*L*math.pi*radius**2 geomInert=(2./5.)*V*radius**2 utils._commonBodySetup(b,V,Vector3(geomInert,geomInert,geomInert),material,pos=sph1.state.pos,dynamic=False,fixed=True) sph1.state.mass = sph1.state.mass + V*nodeMat.density sph2.state.mass = sph2.state.mass + V*nodeMat.density for k in [0,1,2]: sph1.state.inertia[k] = sph1.state.inertia[k] + geomInert*nodeMat.density sph2.state.inertia[k] = sph2.state.inertia[k] + geomInert*nodeMat.density b.aspherical=False if O.periodic: i.phys.unp= -(sph2.state.pos + O.cell.hSize*i.cellDist - sph1.state.pos).norm() + sph1.shape.radius + sph2.shape.radius b.shape.periodic=True b.shape.cellDist=i.cellDist else: i.phys.unp= -(sph2.state.pos - sph1.state.pos).norm() + sph1.shape.radius + sph2.shape.radius i.geom.connectionBody=b I=math.pi*(2.*radius)**4/64. E=nodeMat.young i.phys.kn=E*math.pi*(radius**2)/L i.phys.kr=E*I/L i.phys.ks=12.*E*I/(L**3) G=E/(2.*(1+nodeMat.poisson)) i.phys.ktw=2.*I*G/L b.mask=mask return b #TODO: find a better way of handling the Id lists for checking duplicated gridNodes or gridConnections with the same coordinates etc. It would be better to handle this globally, maybe implement something like O.bodies.getGridNodes def cylinder(begin=Vector3(0,0,0),end=Vector3(1.,0.,0.),radius=0.2,nodesIds=[],cylIds=[],dynamic=None,fixed=False,wire=False,color=None,highlight=False,intMaterial=-1,extMaterial=-1,mask=1): """ Create a cylinder with given parameters. The shape corresponds to the Minkowski sum of line-segment and sphere, hence, the cylinder has rounded vertices. The cylinder (:yref:`GridConnection`) and its corresponding nodes (yref:`GridNodes`) are automatically added to the simulation. The lists with nodes and cylinder ids will be updated automatically. :param Vector3 begin: first point of the Minkowski sum in the global coordinate system. :param Vector3 end: last point of the Minkowski sum in the global coordinate system. :param Real radius: radius of sphere in the Minkowski sum. :param list nodesIds: list with ids of already existing :yref:`GridNodes`. New ids will be added. :param list cylIds: list with ids of already existing :yref:`GridConnections`. New id will be added. :param intMaterial: :yref:`Body.material` used to create the interaction physics between the two GridNodes :param extMaterial: :yref:`Body.material` used to create the interaction physics between the Cylinder (GridConnection) and other bodies (e.g., spheres interaction with the cylinder) See :yref:`yade.utils.sphere`'s documentation for meaning of other parameters. """ id1 = O.bodies.append( gridNode(begin,radius,dynamic=dynamic,fixed=fixed,wire=wire,color=color,highlight=highlight,material=intMaterial) ) nodesIds.append(id1) id2 = O.bodies.append( gridNode(end,radius,dynamic=dynamic,fixed=fixed,wire=wire,color=color,highlight=highlight,material=intMaterial) ) nodesIds.append(id2) cylIds.append(O.bodies.append( gridConnection(id1,id2,radius=radius,wire=wire,color=color,highlight=highlight,material=extMaterial,mask=mask,cellDist=None) )) def cylinderConnection(vertices,radius=0.2,nodesIds=[],cylIds=[],dynamic=None,fixed=False,wire=False,color=None,highlight=False,intMaterial=-1,extMaterial=-1,mask=1): """ Create a chain of cylinders with given parameters. The cylinders (:yref:`GridConnection`) and its corresponding nodes (yref:`GridNodes`) are automatically added to the simulation. The lists with nodes and cylinder ids will be updated automatically. :param [[Vector3]] vertices: coordinates of vertices to connect in the global coordinate system. See :yref:`yade.gridpfacet.cylinder` documentation for meaning of other parameters. """ # create all gridNodes first nodesIdsCC=[] for i in vertices: nodesIdsCC.append( O.bodies.append(gridNode(i,radius=radius, dynamic=dynamic,fixed=fixed,wire=wire,color=color,highlight=highlight,material=intMaterial)) ) nodesIds.extend(nodesIdsCC) # now create connection between the gridNodes for i,j in zip( nodesIdsCC[:-1], nodesIdsCC[1:]): cylIds.append( O.bodies.append( gridConnection(i,j,radius=radius, wire=wire,color=color,highlight=highlight,material=intMaterial,mask=mask,cellDist=None)) ) def pfacet(id1,id2,id3,wire=True,color=None,highlight=False,material=-1,mask=1,cellDist=None): """ Create a :yref:`PFacet` element from 3 :yref:`GridNodes` which are already connected via 3 :yref:`GridConnections`: :param id1,id2,id3: already with :yref:`GridConnections` connected :yref:`GridNodes` :param bool wire: if ``True``, top and bottom facet are shown as skeleton; otherwise facets are filled. :param Vector3-or-None color: color of the PFacet; random color will be assigned if ``None``. :param Vector3 cellDist: for periodic boundary conditions, see :yref:`Interaction.cellDist`. Note: periodic boundary conditions are not yet implemented for PFacets! See documentation of :yref:`yade.utils.sphere` for meaning of other parameters. :return: Body object with the :yref:`PFacet` :yref:`shape`. .. note:: :yref:`GridNodes` and :yref:`GridConnections` need to have the same radius. This is also the radius used to create the :yref:`PFacet` """ b=Body() GridN1=O.bodies[id1]; GridN2=O.bodies[id2]; GridN3=O.bodies[id3] b.shape=PFacet(color=color if color else randomColor(),wire=wire,highlight=highlight,node1=GridN1,node2=GridN2,node3=GridN3) GridN1.bounded=False; GridN2.bounded=False; GridN3.bounded=False GridC1=O.bodies[O.interactions[id1,id2].geom.connectionBody.id] GridC2=O.bodies[O.interactions[id2,id3].geom.connectionBody.id] GridC3=O.bodies[O.interactions[id1,id3].geom.connectionBody.id] GridC1.bounded=False GridC2.bounded=False GridC3.bounded=False b.shape.conn1=GridC1 b.shape.conn2=GridC2 b.shape.conn3=GridC3 b.shape.radius=GridN1.shape.radius GridC1.shape.addPFacet(b) GridC2.shape.addPFacet(b) GridC3.shape.addPFacet(b) GridN1.shape.addPFacet(b); GridN2.shape.addPFacet(b); GridN3.shape.addPFacet(b) V=0 utils._commonBodySetup(b,V,Vector3(0,0,0),material,pos=GridN1.state.pos,dynamic=False,fixed=True) b.aspherical=False # mass and inertia are lumped into the GridNodes b.mask=mask return b #TODO: find a better way of handling the Id lists for checking duplicated gridNodes or gridConnections with the same coordinates etc. It would be better to handle this globally, maybe implement something like O.bodies.getGridNodes def pfacetCreator1(vertices,radius,nodesIds=[],cylIds=[],pfIds=[],wire=False,fixed=True,materialNodes=-1,material=-1,color=None): """ Create a :yref:`PFacet` element from 3 vertices and automatically append to simulation. The function uses the vertices to create :yref:`GridNodes` and automatically checks for existing nodes. :param [Vector3,Vector3,Vector3] vertices: coordinates of vertices in the global coordinate system. :param float radius: radius used to create the :yref:`PFacets`. :param list nodesIds: list with ids of already existing :yref:`GridNodes`. New ids will be added. :param list cylIds: list with ids of already existing :yref:`GridConnections`. New ids will be added. :param list pfIds: list with ids of already existing :yref:`PFacets`. New ids will be added. :param materialNodes: specify :yref:`Body.material` of :yref:`GridNodes`. This material is used to make the internal connections. :param material: specify :yref:`Body.material` of :yref:`PFacets`. This material is used for interactions with external bodies. See documentation of :yref:`yade.utils.sphere` for meaning of other parameters. """ n=len(nodesIds) k=[0,0,0] f=[0,0,0] u=0 nod=0 for i in vertices: u=0 for j in nodesIds: if(i==O.bodies[j].state.pos): f[nod]=j k[nod]=1 u+=1 nod+=1 test=True #if(u==0): for GN in nodesIds: if(i==O.bodies[GN].state.pos): u=1 if(u==0): nodesIds.append( O.bodies.append(gridNode(i,radius,wire=wire,fixed=fixed,material=materialNodes,color=color)) ) if(k==[0,0,0]): pfacetCreator3(nodesIds[n],nodesIds[n+1],nodesIds[n+2],cylIds=cylIds,pfIds=pfIds,wire=wire,material=material,color=color,fixed=fixed ) if(k==[1,0,0]): pfacetCreator3(f[0],nodesIds[n],nodesIds[n+1],cylIds=cylIds,pfIds=pfIds,wire=wire,material=material,color=color,fixed=fixed ) if(k==[0,1,0]): pfacetCreator3(nodesIds[n],f[1],nodesIds[n+1],cylIds=cylIds,pfIds=pfIds,wire=wire,material=material,color=color,fixed=fixed ) if(k==[0,0,1]): pfacetCreator3(nodesIds[n],nodesIds[n+1],f[2],cylIds=cylIds,pfIds=pfIds,wire=wire,material=material,color=color,fixed=fixed ) if(k==[1,1,0]): pfacetCreator3(f[0],f[1],nodesIds[n],cylIds=cylIds,pfIds=pfIds,wire=wire,material=material,color=color,fixed=fixed ) if(k==[0,1,1]): pfacetCreator3(nodesIds[n],f[1],f[2],cylIds=cylIds,pfIds=pfIds,wire=wire,material=material,color=color,fixed=fixed ) if(k==[1,0,1]): pfacetCreator3(f[0],nodesIds[n],f[2],cylIds=cylIds,pfIds=pfIds,wire=wire,material=material,color=color,fixed=fixed ) if(k==[1,1,1]): pfacetCreator3(f[0],f[1],f[2],cylIds=cylIds,pfIds=pfIds,wire=wire,material=material,color=color,fixed=fixed ) def pfacetCreator2(id1,id2,vertex,radius,nodesIds=[],wire=True,materialNodes=-1,material=-1,color=None,fixed=True): """ Create a :yref:`PFacet` element from 2 already existing and connected :yref:`GridNodes` and one vertex. The element is automatically appended to the simulation. :param int id1,id2: ids of already with :yref:`GridConnection` connected :yref:`GridNodes`. :param Vector3 vertex: coordinates of the vertex in the global coordinate system. See documentation of :yref:`yade.gridpfacet.pfacetCreator1` for meaning of other parameters. """ n=len(nodesIds) nodesIds.append( O.bodies.append(gridNode(vertex,radius,wire=wire,fixed=fixed,material=materialNodes,color=color)) ) O.bodies.append(gridConnection(id1,nodesIds[n],radius=radius,material=materialNodes,color=color,wire=wire)) O.bodies.append(gridConnection(id2,nodesIds[n],radius=radius,material=materialNodes,color=color,wire=wire)) O.bodies.append(pfacet(id1,id2,nodesIds[n],wire=wire,material=material,color=color)) def pfacetCreator3(id1,id2,id3,cylIds=[],pfIds=[],wire=True,material=-1,color=None,fixed=True,mask=-1): """ Create a :yref:`PFacet` element from 3 already existing :yref:`GridNodes` which are not yet connected. The element is automatically appended to the simulation. :param int id1,id2,id3: id of the 3 :yref:`GridNodes` forming the :yref:`PFacet`. See documentation of :yref:`yade.gridpfacet.pfacetCreator1` for meaning of other parameters. """ radius=O.bodies[id1].shape.radius try: cylIds.append(O.bodies.append(gridConnection(id1,id2,radius=radius,material=material,color=color,wire=wire,mask=mask))) except: pass try: cylIds.append(O.bodies.append(gridConnection(id2,id3,radius=radius,material=material,color=color,wire=wire,mask=mask))) except: pass try: cylIds.append(O.bodies.append(gridConnection(id3,id1,radius=radius,material=material,color=color,wire=wire,mask=mask))) except: pass pfIds.append(O.bodies.append(pfacet(id1,id2,id3,wire=wire,material=material,color=color,mask=mask))) def pfacetCreator4(id1,id2,id3,pfIds=[],wire=True,material=-1,color=None,fixed=True,mask=-1): """ Create a :yref:`PFacet` element from 3 already existing :yref:`GridConnections`. The element is automatically appended to the simulation. :param int id1,id2,id3: id of the 3 :yref:`GridConnections` forming the :yref:`PFacet`. See documentation of :yref:`yade.gridpfacet.pfacetCreator1` for meaning of other parameters. """ radius=O.bodies[id1].shape.radius GridN=[] GridN1=O.bodies[id1].shape.node1.id GridN2=O.bodies[id1].shape.node2.id GridN.append(GridN1) GridN.append(GridN2) GridN1=O.bodies[id2].shape.node1.id if(GridN1 not in GridN): GridN.append(GridN1) GridN2=O.bodies[id2].shape.node2.id if(GridN2 not in GridN): GridN.append(GridN2) GridN1=O.bodies[id3].shape.node1.id if(GridN1 not in GridN): GridN.append(GridN1) GridN2=O.bodies[id3].shape.node2.id if(GridN2 not in GridN): GridN.append(GridN2) pfIds.append(O.bodies.append(pfacet(GridN[0],GridN[1],GridN[2],wire=wire,material=material,color=color,mask=mask))) def gtsPFacet(meshfile,shift=Vector3.Zero,scale=1.0, radius=1,wire=True,fixed=True,materialNodes=-1,material=-1,color=None): """ Imports mesh geometry from .gts file and automatically creates connected :yref:`PFacet3` elements. For an example see :ysrc:`examples/pfacet/gts-pfacet.py`. :param string filename: .gts file to read. :param [float,float,float] shift: [X,Y,Z] parameter shifts the mesh. :param float scale: factor scales the mesh. :param float radius: radius used to create the :yref:`PFacets`. :param materialNodes: specify :yref:`Body.material` of :yref:`GridNodes`. This material is used to make the internal connections. :param material: specify :yref:`Body.material` of :yref:`PFacets`. This material is used for interactions with external bodies. See documentation of :yref:`yade.utils.sphere` for meaning of other parameters. :returns: lists of :yref:`GridNode` ids `nodesIds`, :yref:`GridConnection` ids `cylIds`, and :yref:`PFacet` ids `pfIds` """ import gts,yade.pack surf=gts.read(open(meshfile)) surf.scale(scale,scale,scale) surf.translate(shift[0],shift[1],shift[2]) nodesIds=[]; cylIds=[]; pfIds=[] for face in surf.faces(): a=face.vertices()[0].coords() b=face.vertices()[1].coords() c=face.vertices()[2].coords() pfacetCreator1([a,b,c],radius=radius,nodesIds=nodesIds,cylIds=cylIds,pfIds=pfIds,wire=wire,fixed=fixed,materialNodes=materialNodes,material=material,color=color) #print a,b,c return nodesIds,cylIds,pfIds def gmshPFacet(meshfile="file.mesh",shift=Vector3.Zero,scale=1.0,orientation=Quaternion.Identity, radius=1.0,wire=True,fixed=True,materialNodes=-1,material=-1,color=None): """ Imports mesh geometry from .mesh file and automatically creates connected PFacet elements. For an example see :ysrc:`examples/pfacet/mesh-pfacet.py`. :param string filename: .gts file to read. :param [float,float,float] shift: [X,Y,Z] parameter shifts the mesh. :param float scale: factor scales the mesh. :param quaternion orientation: orientation of the imported geometry. :param float radius: radius used to create the :yref:`PFacets`. :param materialNodes: specify :yref:`Body.material` of :yref:`GridNodes`. This material is used to make the internal connections. :param material: specify :yref:`Body.material` of :yref:`PFacets`. This material is used for interactions with external bodies. See documentation of :yref:`yade.utils.sphere` for meaning of other parameters. :returns: lists of :yref:`GridNode` ids `nodesIds`, :yref:`GridConnection` ids `cylIds`, and :yref:`PFacet` ids `pfIds` mesh files can easily be created with `GMSH `_. Additional examples of mesh-files can be downloaded from http://www-roc.inria.fr/gamma/download/download.php """ infile = open(meshfile,"r") lines = infile.readlines() infile.close() nodelistVector3=[] findVerticesString=0 while (lines[findVerticesString].split()[0]<>'Vertices'): # find the string with the number of Vertices findVerticesString+=1 findVerticesString+=1 numNodes = int(lines[findVerticesString].split()[0]) for i in range(numNodes): nodelistVector3.append(Vector3(0.0,0.0,0.0)) id = 0 for line in lines[findVerticesString+1:numNodes+findVerticesString+1]: data = line.split() nodelistVector3[id] = orientation*Vector3(float(data[0])*scale,float(data[1])*scale,float(data[2])*scale)+shift id += 1 findTriangleString=findVerticesString+numNodes while (lines[findTriangleString].split()[0]<>'Triangles'): # find the string with the number of Triangles findTriangleString+=1 findTriangleString+=1 numTriangles = int(lines[findTriangleString].split()[0]) triList = [] for i in range(numTriangles): triList.append([0,0,0,0]) tid = 0 for line in lines[findTriangleString+1:findTriangleString+numTriangles+1]: data = line.split() id1 = int(data[0])-1 id2 = int(data[1])-1 id3 = int(data[2])-1 triList[tid][0] = tid triList[tid][1] = id1 triList[tid][2] = id2 triList[tid][3] = id3 tid += 1 nodesIds=[]; cylIds=[]; pfIds=[] for i in triList: a=nodelistVector3[i[1]] b=nodelistVector3[i[2]] c=nodelistVector3[i[3]] #print 'i',i #print 'a',a #print 'b',b #print 'c',c try: pfacetCreator1([a,b,c],radius=radius,nodesIds=nodesIds,cylIds=cylIds,pfIds=pfIds, wire=wire,fixed=fixed,materialNodes=materialNodes,material=material,color=color) except: pass return nodesIds,cylIds,pfIds trunk-2018.02b/py/linterpolation.py000066400000000000000000000070001324306050200172300ustar00rootroot00000000000000# encoding: utf-8 # # © 2009 Václav Šmilauer # """ Module for rudimentary support of manipulation with piecewise-linear functions (which are usually interpolations of higher-order functions, whence the module name). Interpolation is always given as two lists of the same length, where the x-list must be increasing. Periodicity is supported by supposing that the interpolation can wrap from the last x-value to the first x-value (which should be 0 for meaningful results). Non-periodic interpolation can be converted to periodic one by padding the interpolation with constant head and tail using the sanitizeInterpolation function. There is a c++ template function for interpolating on such sequences in pkg/common/Engine/PartialEngine/LinearInterpolate.hpp (stateful, therefore fast for sequential reads). TODO: Interpolating from within python is not (yet) supported. """ def revIntegrateLinear(I,x0,y0,x1,y1): """Helper function, returns value of integral variable x for linear function f passing through (x0,y0),(x1,y1) such that 1. x∈[x0,x1] 2. ∫_x0^x f dx=I and raise exception if such number doesn't exist or the solution is not unique (possible?) """ from math import sqrt dx,dy=x1-x0,y1-y0 if dy==0: # special case, degenerates to linear equation return (x0*y0+I)/y0 a=dy/dx; b=2*(y0-x0*dy/dx); c=x0**2*dy/dx-2*x0*y0-2*I det=b**2-4*a*c; assert(det>0) p,q=(-b+sqrt(det))/(2*a),(-b-sqrt(det))/(2*a) pOK,qOK=x0<=p<=x1,x0<=q<=x1 if pOK and qOK: raise ValueError("Both radices within interval!?") if not pOK and not qOK: raise ValueError("No radix in given interval!") return p if pOK else q def integral(x,y): """Return integral of piecewise-linear function given by points x0,x1,… and y0,y1,… """ assert(len(x)==len(y)) sum=0 for i in range(1,len(x)): sum+=(x[i]-x[i-1])*.5*(y[i]+y[i-1]) return sum def xFractionalFromIntegral(integral,x,y): """Return x within range x0…xn such that ∫_x0^x f dx==integral. Raises error if the integral value is not reached within the x-range. """ sum=0 for i in range(1,len(x)): diff=(x[i]-x[i-1])*.5*(y[i]+y[i-1]) if sum+diff>integral: return revIntegrateLinear(integral-sum,x[i-1],y[i-1],x[i],y[i]) else: sum+=diff raise "Integral not reached within the interpolation range!" def xFromIntegral(integralValue,x,y): """Return x such that ∫_x0^x f dx==integral. x wraps around at xn. For meaningful results, therefore, x0 should == 0 """ from math import floor period=x[-1]-x[0] periodIntegral=integral(x,y) numPeriods=floor(integralValue/periodIntegral) xFrac=xFractionalFromIntegral(integralValue-numPeriods*periodIntegral,x,y) #print '### wanted _%g; period=%g; periodIntegral=_%g (numPeriods=%g); rests _%g (xFrac=%g)'%(integralValue,period,periodIntegral,numPeriods,integralValue-numPeriods*periodIntegral,xFrac) #print '### returning %g*%g+%g=%g'%(period,numPeriods,xFrac,period*numPeriods+xFrac) return period*numPeriods+xFrac def sanitizeInterpolation(x,y,x0,x1): """Extends piecewise-linear function in such way that it spans at least the x0…x1 interval, by adding constant padding at the beginning (using y0) and/or at the end (using y1) or not at all.""" xx,yy=[],[] if x0x[-1]: xx+=[x1]; yy+=[y[-1]] return xx,yy if __name__=="main": xx,yy=sanitizeInterpolation([1,2,3],[1,1,2],0,4) print xx,yy print integral(xx,yy) # 5.5 print revIntegrateLinear(.625,1,1,2,2) # 1.5 print xFractionalFromIntegral(1.625,xx,yy) # 1.625 print xFractionalFromIntegral(2.625,xx,yy) # 2.5 trunk-2018.02b/py/pack/000077500000000000000000000000001324306050200145345ustar00rootroot00000000000000trunk-2018.02b/py/pack/_packObb.cpp000066400000000000000000000050721324306050200167440ustar00rootroot00000000000000// many thanks to http://codesuppository.blogspot.com/2006_06_01_archive.html // the code written after http://www.amillionpixels.us/bestfitobb.cpp // which is MIT-licensed #include #include #include // compute minimum bounding for a cloud of points // returns volume Real computeOBB(const std::vector& pts, const Matrix3r& rot, Vector3r& center, Vector3r& halfSize){ const Real inf=std::numeric_limits::infinity(); Vector3r mn(inf,inf,inf), mx(-inf,-inf,-inf); FOREACH(const Vector3r& pt, pts){ Vector3r ptT=rot*pt; mn=mn.cwiseMin(ptT); mx=mx.cwiseMax(ptT); } halfSize=.5*(mx-mn); center=.5*(mn+mx); return 8*halfSize[0]*halfSize[1]*halfSize[2]; } void bestFitOBB(const std::vector& pts, Vector3r& center, Vector3r& halfSize, Quaternionr& rot){ Vector3r angle0(Vector3r::Zero()), angle(Vector3r::Zero()); Vector3r center0; Vector3r halfSize0; Real bestVolume=std::numeric_limits::infinity(); Real sweep=Mathr::PI/4; Real steps=7.; while(sweep>=Mathr::PI/180.){ bool found=false; Real stepSize=sweep/steps; for(Real x=angle0[0]-sweep; x<=angle0[0]+sweep; x+=stepSize){ for(Real y=angle0[1]-sweep; y pts; pts.resize(l); for(int i=0; i(_pts[i]); Quaternionr rot; Vector3r halfSize, center; bestFitOBB(pts,center,halfSize,rot); return boost::python::make_tuple(center,halfSize,rot); } BOOST_PYTHON_MODULE(_packObb){ YADE_SET_DOCSTRING_OPTS; boost::python::scope().attr("__doc__")="Computation of oriented bounding box for cloud of points."; boost::python::def("cloudBestFitOBB",bestFitOBB_py,"Return (Vector3 center, Vector3 halfSize, Quaternion orientation) of\nbest-fit oriented bounding-box for given tuple of points\n(uses brute-force velome minimization, do not use for very large clouds)."); }; trunk-2018.02b/py/pack/_packPredicates.cpp000066400000000000000000000446611324306050200203340ustar00rootroot00000000000000// 2009 © Václav Šmilauer #include #include #include namespace py=boost::python; /* This file contains various predicates that say whether a given point is within the solid, or, not closer than "pad" to its boundary, if pad is nonzero Besides the (point,pad) operator, each predicate defines aabb() method that returns (min,max) tuple defining minimum and maximum point of axis-aligned bounding box for the predicate. These classes are primarily used for yade.pack.* functions creating packings. See examples/regular-sphere-pack/regular-sphere-pack.py for an example. */ // aux functions void ttuple2vvec(const py::tuple& t, Vector3r& v1, Vector3r& v2){ v1=py::extract(t[0])(); v2=py::extract(t[1])(); } // do not use make_tuple directly on vector ops, since their type can be something like Eigen::CwiseBinaryOp<...> py::tuple vvec2tuple(const Vector3r& a, const Vector3r& b){ return py::make_tuple(a,b); } struct Predicate{ public: virtual bool operator() (const Vector3r& pt,Real pad=0.) const = 0; virtual py::tuple aabb() const = 0; Vector3r dim() const { Vector3r mn,mx; ttuple2vvec(aabb(),mn,mx); return (mx-mn).eval(); } Vector3r center() const { Vector3r mn,mx; ttuple2vvec(aabb(),mn,mx); return .5*(mn+mx); } }; struct PredicateWrap: Predicate, py::wrapper{ bool operator()(const Vector3r& pt, Real pad=0.) const { return this->get_override("__call__")(pt,pad);} py::tuple aabb() const { return this->get_override("aabb")(); } }; /********************************************************************************* ****************** Boolean operations on predicates ****************************** *********************************************************************************/ const Predicate& obj2pred(py::object obj){ return py::extract(obj)();} class PredicateBoolean: public Predicate{ protected: const py::object A,B; public: PredicateBoolean(const py::object _A, const py::object _B): A(_A), B(_B){} const py::object getA(){ return A;} const py::object getB(){ return B;} }; // http://www.linuxtopia.org/online_books/programming_books/python_programming/python_ch16s03.html class PredicateUnion: public PredicateBoolean{ public: PredicateUnion(const py::object _A, const py::object _B): PredicateBoolean(_A,_B){} virtual bool operator()(const Vector3r& pt,Real pad) const {return obj2pred(A)(pt,pad)||obj2pred(B)(pt,pad);} virtual py::tuple aabb() const { Vector3r minA,maxA,minB,maxB; ttuple2vvec(obj2pred(A).aabb(),minA,maxA); ttuple2vvec(obj2pred(B).aabb(),minB,maxB); return vvec2tuple(minA.cwiseMin(minB),maxA.cwiseMax(maxB)); } }; PredicateUnion makeUnion(const py::object& A, const py::object& B){ return PredicateUnion(A,B);} class PredicateIntersection: public PredicateBoolean{ public: PredicateIntersection(const py::object _A, const py::object _B): PredicateBoolean(_A,_B){} virtual bool operator()(const Vector3r& pt,Real pad) const {return obj2pred(A)(pt,pad) && obj2pred(B)(pt,pad);} virtual py::tuple aabb() const { Vector3r minA,maxA,minB,maxB; ttuple2vvec(obj2pred(A).aabb(),minA,maxA); ttuple2vvec(obj2pred(B).aabb(),minB,maxB); return vvec2tuple(minA.cwiseMax(minB),maxA.cwiseMin(maxB)); } }; PredicateIntersection makeIntersection(const py::object& A, const py::object& B){ return PredicateIntersection(A,B);} class PredicateDifference: public PredicateBoolean{ public: PredicateDifference(const py::object _A, const py::object _B): PredicateBoolean(_A,_B){} virtual bool operator()(const Vector3r& pt,Real pad) const {return obj2pred(A)(pt,pad) && !obj2pred(B)(pt,-pad);} virtual py::tuple aabb() const { return obj2pred(A).aabb(); } }; PredicateDifference makeDifference(const py::object& A, const py::object& B){ return PredicateDifference(A,B);} class PredicateSymmetricDifference: public PredicateBoolean{ public: PredicateSymmetricDifference(const py::object _A, const py::object _B): PredicateBoolean(_A,_B){} virtual bool operator()(const Vector3r& pt,Real pad) const {bool inA=obj2pred(A)(pt,pad), inB=obj2pred(B)(pt,pad); return (inA && !inB) || (!inA && inB);} virtual py::tuple aabb() const { Vector3r minA,maxA,minB,maxB; ttuple2vvec(obj2pred(A).aabb(),minA,maxA); ttuple2vvec(obj2pred(B).aabb(),minB,maxB); return vvec2tuple(minA.cwiseMin(minB),maxA.cwiseMax(maxB)); } }; PredicateSymmetricDifference makeSymmetricDifference(const py::object& A, const py::object& B){ return PredicateSymmetricDifference(A,B);} /********************************************************************************* ****************************** Primitive predicates ****************************** *********************************************************************************/ /*! Sphere predicate */ class inSphere: public Predicate { Vector3r center; Real radius; public: inSphere(const Vector3r& _center, Real _radius){center=_center; radius=_radius;} virtual bool operator()(const Vector3r& pt, Real pad=0.) const { return ((pt-center).norm()<=radius-pad); } virtual py::tuple aabb() const {return vvec2tuple(Vector3r(center[0]-radius,center[1]-radius,center[2]-radius),Vector3r(center[0]+radius,center[1]+radius,center[2]+radius));} }; /*! Axis-aligned box predicate */ class inAlignedBox: public Predicate{ Vector3r mn, mx; public: inAlignedBox(const Vector3r& _mn, const Vector3r& _mx): mn(_mn), mx(_mx) {} virtual bool operator()(const Vector3r& pt, Real pad=0.) const { return mn[0]+pad<=pt[0] && mx[0]-pad>=pt[0] && mn[1]+pad<=pt[1] && mx[1]-pad>=pt[1] && mn[2]+pad<=pt[2] && mx[2]-pad>=pt[2]; } virtual py::tuple aabb() const { return vvec2tuple(mn,mx); } }; class inParallelepiped: public Predicate{ Vector3r n[6]; // outer normals, for -x, +x, -y, +y, -z, +z Vector3r pts[6]; // points on planes Vector3r mn,mx; public: inParallelepiped(const Vector3r& o, const Vector3r& a, const Vector3r& b, const Vector3r& c){ Vector3r A(o), B(a), C(a+(b-o)), D(b), E(c), F(c+(a-o)), G(c+(a-o)+(b-o)), H(c+(b-o)); Vector3r x(B-A), y(D-A), z(E-A); n[0]=-y.cross(z).normalized(); n[1]=-n[0]; pts[0]=A; pts[1]=B; n[2]=-z.cross(x).normalized(); n[3]=-n[2]; pts[2]=A; pts[3]=D; n[4]=-x.cross(y).normalized(); n[5]=-n[4]; pts[4]=A; pts[5]=E; // bounding box Vector3r vertices[8]={A,B,C,D,E,F,G,H}; mn=mx=vertices[0]; for(int i=1; i<8; i++){ mn=mn.cwiseMin(vertices[i]); mx=mx.cwiseMax(vertices[i]); } } virtual bool operator()(const Vector3r& pt, Real pad=0.) const { for(int i=0; i<6; i++) if((pt-pts[i]).dot(n[i])>-pad) return false; return true; } virtual py::tuple aabb() const { return vvec2tuple(mn,mx); } }; /*! Arbitrarily oriented cylinder predicate */ class inCylinder: public Predicate{ Vector3r c1,c2,c12; Real radius,ht; public: inCylinder(const Vector3r& _c1, const Vector3r& _c2, Real _radius){c1=_c1; c2=_c2; c12=c2-c1; radius=_radius; ht=c12.norm(); } bool operator()(const Vector3r& pt, Real pad=0.) const { Real u=(pt.dot(c12)-c1.dot(c12))/(ht*ht); // normalized coordinate along the c1--c2 axis if((u*ht<0+pad) || (u*ht>ht-pad)) return false; // out of cylinder along the axis Real axisDist=((pt-c1).cross(pt-c2)).norm()/ht; if(axisDist>radius-pad) return false; return true; } py::tuple aabb() const { // see http://www.gamedev.net/community/forums/topic.asp?topic_id=338522&forum_id=20&gforum_id=0 for the algorithm const Vector3r& A(c1); const Vector3r& B(c2); Vector3r k( sqrt((pow(A[1]-B[1],2)+pow(A[2]-B[2],2)))/ht, sqrt((pow(A[0]-B[0],2)+pow(A[2]-B[2],2)))/ht, sqrt((pow(A[0]-B[0],2)+pow(A[1]-B[1],2)))/ht); Vector3r mn=A.cwiseMin(B), mx=A.cwiseMax(B); return vvec2tuple((mn-radius*k).eval(),(mx+radius*k).eval()); } }; /*! Oriented hyperboloid predicate (cylinder as special case). See http://mathworld.wolfram.com/Hyperboloid.html for the parametrization and meaning of symbols */ class inHyperboloid: public Predicate{ Vector3r c1,c2,c12; Real R,a,ht,c; public: inHyperboloid(const Vector3r& _c1, const Vector3r& _c2, Real _R, Real _r){ c1=_c1; c2=_c2; R=_R; a=_r; c12=c2-c1; ht=c12.norm(); Real uMax=sqrt(pow(R/a,2)-1); c=ht/(2*uMax); } // WARN: this is not accurate, since padding is taken as perpendicular to the axis, not the the surface bool operator()(const Vector3r& pt, Real pad=0.) const { Real v=(pt.dot(c12)-c1.dot(c12))/(ht*ht); // normalized coordinate along the c1--c2 axis if((v*ht<0+pad) || (v*ht>ht-pad)) return false; // out of cylinder along the axis Real u=(v-.5)*ht/c; // u from the wolfram parametrization; u is 0 in the center Real rHere=a*sqrt(1+u*u); // pad is taken perpendicular to the axis, not to the surface (inaccurate) Real axisDist=((pt-c1).cross(pt-c2)).norm()/ht; if(axisDist>rHere-pad) return false; return true; } py::tuple aabb() const { // the lazy way return inCylinder(c1,c2,R).aabb(); } }; /*! Axis-aligned ellipsoid predicate */ class inEllipsoid: public Predicate{ Vector3r c, abc; public: inEllipsoid(const Vector3r& _c, const Vector3r& _abc) {c=_c; abc=_abc;} bool operator()(const Vector3r& pt, Real pad=0.) const { //Define the ellipsoid X-coordinate of given Y and Z Real x = sqrt((1-pow((pt[1]-c[1]),2)/((abc[1]-pad)*(abc[1]-pad))-pow((pt[2]-c[2]),2)/((abc[2]-pad)*(abc[2]-pad)))*((abc[0]-pad)*(abc[0]-pad)))+c[0]; Vector3r edgeEllipsoid(x,pt[1],pt[2]); // create a vector of these 3 coordinates //check whether given coordinates lie inside ellipsoid or not if ((pt-c).norm()<=(edgeEllipsoid-c).norm()) return true; else return false; } py::tuple aabb() const { const Vector3r& center(c); const Vector3r& ABC(abc); return vvec2tuple(Vector3r(center[0]-ABC[0],center[1]-ABC[1],center[2]-ABC[2]),Vector3r(center[0]+ABC[0],center[1]+ABC[1],center[2]+ABC[2])); } }; /*! Negative notch predicate. Use intersection (& operator) of another predicate with notInNotch to create notched solid. geometry explanation: c: the center normalHalfHt (in constructor): A-C inside: perpendicular to notch edge, points inside the notch (unit vector) normal: perpendicular to inside, perpendicular to both notch planes edge: unit vector in the direction of the edge ↑ distUp A ------------------------- | C inside(unit) ← * → distInPlane | ------------------------- ↓ distDown B */ class notInNotch: public Predicate{ Vector3r c, edge, normal, inside; Real aperture; public: notInNotch(const Vector3r& _c, const Vector3r& _edge, const Vector3r& _normal, Real _aperture){ c=_c; edge=_edge; edge.normalize(); normal=_normal; normal-=edge*edge.dot(normal); normal.normalize(); inside=edge.cross(normal); aperture=_aperture; // LOG_DEBUG("edge="< *bb) { GtsPoint *_p=GTS_POINT(vertex); Vector3r p(_p->x,_p->y,_p->z); bb->first=bb->first.cwiseMin(p); bb->second=bb->second.cwiseMax(p); } /* This class plays tricks getting around pyGTS to get GTS objects and cache bb tree to speed up point inclusion tests. For this reason, we have to link with _gts.so (see corresponding SConscript file), which is at the same time the python module. */ class inGtsSurface: public Predicate{ py::object pySurf; // to hold the reference so that surf is valid GtsSurface *surf; bool is_open, noPad, noPadWarned; GNode* tree; public: inGtsSurface(py::object _surf, bool _noPad=false): pySurf(_surf), noPad(_noPad), noPadWarned(false) { if(!pygts_surface_check(_surf.ptr())) throw std::invalid_argument("Ctor must receive a gts.Surface() instance."); surf=PYGTS_SURFACE_AS_GTS_SURFACE(PYGTS_SURFACE(_surf.ptr())); if(!gts_surface_is_closed(surf)) throw std::invalid_argument("Surface is not closed."); is_open=gts_surface_volume(surf)<0.; if((tree=gts_bb_tree_surface(surf))==NULL) throw std::runtime_error("Could not create GTree."); } ~inGtsSurface(){g_node_destroy(tree);} py::tuple aabb() const { Real inf=std::numeric_limits::infinity(); std::pair bb; bb.first=Vector3r(inf,inf,inf); bb.second=Vector3r(-inf,-inf,-inf); gts_surface_foreach_vertex(surf,(GtsFunc)vertex_aabb,&bb); return vvec2tuple(bb.first,bb.second); } bool ptCheck(const Vector3r& pt) const{ GtsPoint gp; gp.x=pt[0]; gp.y=pt[1]; gp.z=pt[2]; return (bool)gts_point_is_inside_surface(&gp,tree,is_open); } bool operator()(const Vector3r& pt, Real pad=0.) const { if(noPad){ if(pad!=0. && noPadWarned) LOG_WARN("inGtsSurface constructed with noPad; requested non-zero pad set to zero."); return ptCheck(pt); } return ptCheck(pt) && ptCheck(pt-Vector3r(pad,0,0)) && ptCheck(pt+Vector3r(pad,0,0)) && ptCheck(pt-Vector3r(0,pad,0))&& ptCheck(pt+Vector3r(0,pad,0)) && ptCheck(pt-Vector3r(0,0,pad))&& ptCheck(pt+Vector3r(0,0,pad)); } py::object surface() const {return pySurf;} }; #endif BOOST_PYTHON_MODULE(_packPredicates){ py::scope().attr("__doc__")="Spatial predicates for volumes (defined analytically or by triangulation)."; YADE_SET_DOCSTRING_OPTS; // base predicate class py::class_("Predicate") .def("__call__",py::pure_virtual(&Predicate::operator())) .def("aabb",py::pure_virtual(&Predicate::aabb)) .def("dim",&Predicate::dim) .def("center",&Predicate::center) .def("__or__",makeUnion).def("__and__",makeIntersection).def("__sub__",makeDifference).def("__xor__",makeSymmetricDifference); // boolean operations py::class_,boost::noncopyable>("PredicateBoolean","Boolean operation on 2 predicates (abstract class)",py::no_init) .add_property("A",&PredicateBoolean::getA).add_property("B",&PredicateBoolean::getB); py::class_ >("PredicateUnion","Union (non-exclusive disjunction) of 2 predicates. A point has to be inside any of the two predicates to be inside. Can be constructed using the ``|`` operator on predicates: ``pred1 | pred2``.",py::init()); py::class_ >("PredicateIntersection","Intersection (conjunction) of 2 predicates. A point has to be inside both predicates. Can be constructed using the ``&`` operator on predicates: ``pred1 & pred2``.",py::init()); py::class_ >("PredicateDifference","Difference (conjunction with negative predicate) of 2 predicates. A point has to be inside the first and outside the second predicate. Can be constructed using the ``-`` operator on predicates: ``pred1 - pred2``.",py::init()); py::class_ >("PredicateSymmetricDifference","SymmetricDifference (exclusive disjunction) of 2 predicates. A point has to be in exactly one predicate of the two. Can be constructed using the ``^`` operator on predicates: ``pred1 ^ pred2``.",py::init()); // primitive predicates py::class_ >("inSphere","Sphere predicate.",py::init(py::args("center","radius"),"Ctor taking center (as a 3-tuple) and radius")); py::class_ >("inAlignedBox","Axis-aligned box predicate",py::init(py::args("minAABB","maxAABB"),"Ctor taking minumum and maximum points of the box (as 3-tuples).")); py::class_ >("inParallelepiped","Parallelepiped predicate",py::init(py::args("o","a","b","c"),"Ctor taking four points: ``o`` (for origin) and then ``a``, ``b``, ``c`` which define endpoints of 3 respective edges from ``o``.")); py::class_ >("inCylinder","Cylinder predicate",py::init(py::args("centerBottom","centerTop","radius"),"Ctor taking centers of the lateral walls (as 3-tuples) and radius.")); py::class_ >("inHyperboloid","Hyperboloid predicate",py::init(py::args("centerBottom","centerTop","radius","skirt"),"Ctor taking centers of the lateral walls (as 3-tuples), radius at bases and skirt (middle radius).")); py::class_ >("inEllipsoid","Ellipsoid predicate",py::init(py::args("centerPoint","abc"),"Ctor taking center of the ellipsoid (3-tuple) and its 3 radii (3-tuple).")); py::class_ >("notInNotch","Outside of infinite, rectangle-shaped notch predicate",py::init(py::args("centerPoint","edge","normal","aperture"),"Ctor taking point in the symmetry plane, vector pointing along the edge, plane normal and aperture size.\nThe side inside the notch is edge×normal.\nNormal is made perpendicular to the edge.\nAll vectors are normalized at construction time.")); #ifdef YADE_GTS py::class_ >("inGtsSurface","GTS surface predicate",py::init >(py::args("surface","noPad"),"Ctor taking a gts.Surface() instance, which must not be modified during instance lifetime.\nThe optional noPad can disable padding (if set to True), which speeds up calls several times.\nNote: padding checks inclusion of 6 points along +- cardinal directions in the pad distance from given point, which is not exact.")) .add_property("surf",&inGtsSurface::surface,"The associated gts.Surface object."); #endif } trunk-2018.02b/py/pack/_packSpheres.cpp000066400000000000000000000265041324306050200176560ustar00rootroot00000000000000// 2009 © Václav Šmilauer #include #include #include BOOST_PYTHON_MODULE(_packSpheres){ boost::python::scope().attr("__doc__")="Creation, manipulation, IO for generic sphere packings."; YADE_SET_DOCSTRING_OPTS; boost::python::class_("SpherePack","Set of spheres represented as centers and radii. This class is returned by :yref:`yade.pack.randomDensePack`, :yref:`yade.pack.randomPeriPack` and others. The object supports iteration over spheres, as in \n\n\t>>> sp=SpherePack()\n\t>>> for center,radius in sp: print center,radius\n\n\t>>> for sphere in sp: print sphere[0],sphere[1] ## same, but without unpacking the tuple automatically\n\n\t>>> for i in range(0,len(sp)): print sp[i][0], sp[i][1] ## same, but accessing spheres by index\n\n\n.. admonition:: Special constructors\n\n\tConstruct from list of ``[(c1,r1),(c2,r2),…]``. To convert two same-length lists of ``centers`` and ``radii``, construct with ``zip(centers,radii)``.\n",boost::python::init >(boost::python::args("list"),"Empty constructor, optionally taking list [ ((cx,cy,cz),r), … ] for initial data." )) .def("add",&SpherePack::add,"Add single sphere to packing, given center as 3-tuple and radius") .def("toList",&SpherePack::toList,"Return packing data as python list.") .def("fromList",&SpherePack::fromList,"Make packing from given list, same format as for constructor. Discards current data.") .def("fromList",&SpherePack::fromLists,(boost::python::arg("centers"),boost::python::arg("radii")),"Make packing from given list, same format as for constructor. Discards current data.") .def("load",&SpherePack::fromFile,(boost::python::arg("fileName")),"Load packing from external text file (current data will be discarded).") .def("save",&SpherePack::toFile,(boost::python::arg("fileName")),"Save packing to external text file (will be overwritten).") .def("fromSimulation",&SpherePack::fromSimulation,"Make packing corresponding to the current simulation. Discards current data.") //The basic sphere generator .def("makeCloud",&SpherePack::makeCloud,(boost::python::arg("minCorner")=Vector3r(Vector3r::Zero()),boost::python::arg("maxCorner")=Vector3r(Vector3r::Zero()),boost::python::arg("rMean")=-1,boost::python::arg("rRelFuzz")=0,boost::python::arg("num")=-1,boost::python::arg("periodic")=false,boost::python::arg("porosity")=0.65,boost::python::arg("psdSizes")=vector(),boost::python::arg("psdCumm")=vector(),boost::python::arg("distributeMass")=false,boost::python::arg("seed")=0,boost::python::arg("hSize")=Matrix3r(Matrix3r::Zero())),"Create random very loose packing enclosed in a parallelepiped (also works in 2D if minCorner[k]=maxCorner[k] for one coordinate). The resulting packing conforms in fact a gas-like state (see Cloud denomination) with no contacts between particles. Usually used as a first step for packing generation, before subsequent mechanical loading that will confer a solid-like nature to the packing." "\nSphere radius distribution can be specified using one of the following ways:\n\n#. *rMean*, *rRelFuzz* and *num* gives uniform radius distribution in *rMean×(1 ± rRelFuzz)*. Less than *num* spheres can be generated if it is too high.\n#. *rRelFuzz*, *num* and (optional) *porosity*, which estimates mean radius so that *porosity* is attained at the end. *rMean* must be less than 0 (default). *porosity* is only an initial guess for the generation algorithm, which will retry with higher porosity until the prescibed *num* is obtained.\n#. *psdSizes* and *psdCumm*, two arrays specifying points of the `particle size distribution `__ function. As many spheres as possible are generated.\n#. *psdSizes*, *psdCumm*, *num*, and (optional) *porosity*, like above but if *num* is not obtained, *psdSizes* will be scaled down uniformly, until *num* is obtained (see :yref:`appliedPsdScaling`).\n\nBy default (with ``distributeMass==False``), the distribution is applied to particle radii. The usual sense of \"particle size distribution\" is the distribution of *mass fraction* (rather than particle count); this can be achieved with ``distributeMass=True``." "\n\nIf *num* is defined, then sizes generation is deterministic, giving the best fit of target distribution. It enables spheres placement in descending size order, thus giving lower porosity than the random generation." "\n\n:param Vector3 minCorner: lower corner of an axis-aligned box\n:param Vector3 maxCorner: upper corner of an axis-aligned box\n:param Matrix3 hSize: base vectors of a generalized box (arbitrary parallelepiped, typically :yref:`Cell::hSize`), superseeds minCorner and maxCorner if defined. For periodic boundaries only.\n:param float rMean: mean radius or spheres\n:param float rRelFuzz: dispersion of radius relative to rMean\n:param int num: number of spheres to be generated. If negavite (default), generate as many as possible with stochastic sizes, ending after a fixed number of tries to place the sphere in space, else generate exactly *num* spheres with deterministic size distribution.\n:param bool periodic: whether the packing to be generated should be periodic\n:param float porosity: initial guess for the iterative generation procedure (if *num*>1). The algorithm will be retrying until the number of generated spheres is *num*. The first iteration tries with the provided porosity, but next iterations increase it if necessary (hence an initialy high porosity can speed-up the algorithm). If *psdSizes* is not defined, *rRelFuzz* ($z$) and *num* ($N$) are used so that the porosity given ($\\rho$) is approximately achieved at the end of generation, $r_m=\\sqrt[3]{\\frac{V(1-\\rho)}{\\frac{4}{3}\\pi(1+z^2)N}}$. The default is $\\rho$=0.5. The optimal value depends on *rRelFuzz* or *psdSizes*.\n:param psdSizes: sieve sizes (particle diameters) when particle size distribution (PSD) is specified\n:param psdCumm: cummulative fractions of particle sizes given by *psdSizes*; must be the same length as *psdSizes* and should be non-decreasing\n:param bool distributeMass: if ``True``, given distribution will be used to distribute sphere's mass rather than radius of them.\n:param seed: number used to initialize the random number generator.\n:returns: number of created spheres, which can be lower than *num* depending on the method used.\n") .def("psd",&SpherePack::psd,(boost::python::arg("bins")=50,boost::python::arg("mass")=true),"Return `particle size distribution `__ of the packing.\n:param int bins: number of bins between minimum and maximum diameter\n:param mass: Compute relative mass rather than relative particle count for each bin. Corresponds to :yref:`distributeMass parameter for makeCloud`.\n:returns: tuple of ``(cumm,edges)``, where ``cumm`` are cummulative fractions for respective diameters and ``edges`` are those diameter values. Dimension of both arrays is equal to ``bins+1``.") //The variant for clumps .def("makeClumpCloud",&SpherePack::makeClumpCloud,(boost::python::arg("minCorner"),boost::python::arg("maxCorner"),boost::python::arg("clumps"),boost::python::arg("periodic")=false,boost::python::arg("num")=-1,boost::python::arg("seed")=0),"Create random loose packing of clumps within box given by *minCorner* and *maxCorner*. Clumps are selected with equal probability. At most *num* clumps will be positioned if *num* is positive; otherwise, as many clumps as possible will be put in space, until maximum number of attempts to place a new clump randomly is attained.\n:param seed: number used to initialize the random number generator.") // old unmaintained stuff, kept for the moment so it at least returns an error message .def("particleSD",&SpherePack::particleSD,(boost::python::arg("minCorner"),boost::python::arg("maxCorner"),boost::python::arg("rMean"),boost::python::arg("periodic")=false,boost::python::arg("name"),boost::python::arg("numSph"),boost::python::arg("radii")=vector(),boost::python::arg("passing")=vector(),boost::python::arg("passingIsNotPercentageButCount")=false, boost::python::arg("seed")=0),"Not working. Use makeCloud instead.") .def("particleSD2",&SpherePack::particleSD2,(boost::python::arg("radii"),boost::python::arg("passing"),boost::python::arg("numSph"),boost::python::arg("periodic")=false,boost::python::arg("cloudPorosity")=.8,boost::python::arg("seed")=0),"Not working. Use makeCloud instead.") .def("particleSD_2d",&SpherePack::particleSD_2d,(boost::python::arg("minCorner"),boost::python::arg("maxCorner"),boost::python::arg("rMean"),boost::python::arg("periodic")=false,boost::python::arg("name"),boost::python::arg("numSph"),boost::python::arg("radii")=vector(),boost::python::arg("passing")=vector(),boost::python::arg("passingIsNotPercentageButCount")=false,boost::python::arg("seed")=0),"Not working. Use makeCloud instead.") // .def("aabb",&SpherePack::aabb_py,"Get axis-aligned bounding box coordinates, as 2 3-tuples.") .def("dim",&SpherePack::dim,"Return dimensions of the packing in terms of aabb(), as a 3-tuple.") .def("center",&SpherePack::midPt,"Return coordinates of the bounding box center.") .def_readwrite("cellSize",&SpherePack::cellSize,"Size of periodic cell; is Vector3(0,0,0) if not periodic. (Change this property only if you know what you're doing).") .def_readwrite("isPeriodic",&SpherePack::isPeriodic,"was the packing generated in periodic boundaries?") .def("cellFill",&SpherePack::cellFill,"Repeat the packing (if periodic) so that the results has dim() >= given size. The packing retains periodicity, but changes cellSize. Raises exception for non-periodic packing.") .def("cellRepeat",&SpherePack::cellRepeat,"Repeat the packing given number of times in each dimension. Periodicity is retained, cellSize changes. Raises exception for non-periodic packing.") .def("relDensity",&SpherePack::relDensity,"Relative packing density, measured as sum of spheres' volumes / aabb volume.\n(Sphere overlaps are ignored.)") .def("translate",&SpherePack::translate,"Translate all spheres by given vector.") .def("rotate",&SpherePack::rotate,(boost::python::arg("axis"),boost::python::arg("angle")),"Rotate all spheres around packing center (in terms of aabb()), given axis and angle of the rotation.") .def("scale",&SpherePack::scale,"Scale the packing around its center (in terms of aabb()) by given factor (may be negative).") .def("hasClumps",&SpherePack::hasClumps,"Whether this object contains clumps.") .def("getClumps",&SpherePack::getClumps,"Return lists of sphere ids sorted by clumps they belong to. The return value is (standalones,[clump1,clump2,…]), where each item is list of id's of spheres.") .def("__len__",&SpherePack::len,"Get number of spheres in the packing") .def("__getitem__",&SpherePack::getitem,"Get entry at given index, as tuple of center and radius.") .def("__iter__",&SpherePack::getIterator,"Return iterator over spheres.") .def_readonly("appliedPsdScaling",&SpherePack::appliedPsdScaling,"A factor between 0 and 1, uniformly applied on all sizes of of the PSD.") ; boost::python::class_("SpherePackIterator",boost::python::init()) .def("__iter__",&SpherePack::_iterator::iter) .def("next",&SpherePack::_iterator::next) ; } trunk-2018.02b/py/pack/pack.py000066400000000000000000001007721324306050200160330ustar00rootroot00000000000000# encoding: utf-8 # 2009 © Václav Šmilauer """ Creating packings and filling volumes defined by boundary representation or constructive solid geometry. For examples, see * :ysrc:`scripts/test/gts-operators.py` * :ysrc:`scripts/test/gts-random-pack-obb.py` * :ysrc:`scripts/test/gts-random-pack.py` * :ysrc:`scripts/test/pack-cloud.py` * :ysrc:`scripts/test/pack-predicates.py` * :ysrc:`examples/packs/packs.py` * :ysrc:`examples/gts-horse/gts-horse.py` * :ysrc:`examples/WireMatPM/wirepackings.py` """ import itertools,warnings from numpy import arange from math import sqrt from yade import utils from yade.wrapper import * from minieigen import * ## compatibility hack for python 2.5 (21/8/2009) ## can be safely removed at some point if 'product' not in dir(itertools): def product(*args, **kwds): "http://docs.python.org/library/itertools.html#itertools.product" pools = map(tuple, args) * kwds.get('repeat', 1); result = [[]] for pool in pools: result = [x+[y] for x in result for y in pool] for prod in result: yield tuple(prod) itertools.product=product # for now skip the import, but try in inGtsSurface constructor again, to give error if we really use it try: import gts except ImportError: pass # make c++ predicates available in this module noPredicate = False try: from _packPredicates import * ## imported in randomDensePack as well except ImportError: pass; noPredicate = True # import SpherePack from _packSpheres import * from _packObb import * ## # extend _packSphere.SpherePack c++ class by this method ## if not (noPredicate): def SpherePack_toSimulation(self,rot=Matrix3.Identity,**kw): """Append spheres directly to the simulation. In addition calling :yref:`O.bodies.append`, this method also appropriately sets periodic cell information of the simulation. >>> from yade import pack; from math import * >>> sp=pack.SpherePack() Create random periodic packing with 20 spheres: >>> sp.makeCloud((0,0,0),(5,5,5),rMean=.5,rRelFuzz=.5,periodic=True,num=20) 20 Virgin simulation is aperiodic: >>> O.reset() >>> O.periodic False Add generated packing to the simulation, rotated by 45° along +z >>> sp.toSimulation(rot=Quaternion((0,0,1),pi/4),color=(0,0,1)) [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19] Periodic properties are transferred to the simulation correctly, including rotation (this could be avoided by explicitly passing "hSize=O.cell.hSize" as an argument): >>> O.periodic True >>> O.cell.refSize Vector3(5,5,5) """ #The following 2 lines do not work, because of accuaracy #>>> O.cell.hSize #Matrix3(3.53553,-3.53553,0, 3.53553,3.53553,0, 0,0,5) """ The current state (even if rotated) is taken as mechanically undeformed, i.e. with identity transformation: >>> O.cell.trsf Matrix3(1,0,0, 0,1,0, 0,0,1) :param Quaternion/Matrix3 rot: rotation of the packing, which will be applied on spheres and will be used to set :yref:`Cell.trsf` as well. :param \*\*kw: passed to :yref:`yade.utils.sphere` :return: list of body ids added (like :yref:`O.bodies.append`) """ if isinstance(rot,Quaternion): rot=rot.toRotationMatrix() assert(isinstance(rot,Matrix3)) if self.isPeriodic: O.periodic=True if self.cellSize!=Vector3.Zero and self.isPeriodic: O.cell.hSize=rot*Matrix3(self.cellSize[0],0,0, 0,self.cellSize[1],0, 0,0,self.cellSize[2]) O.cell.trsf=Matrix3.Identity if not self.hasClumps(): return O.bodies.append([utils.sphere(rot*c,r,**kw) for c,r in self]) else: standalone,clumps=self.getClumps() ids=O.bodies.append([utils.sphere(rot*c,r,**kw) for c,r in self]) # append all spheres first clumpIds=[] userColor='color' in kw for clump in clumps: clumpIds.append(O.bodies.clump([ids[i] for i in clump])) # clump spheres with given ids together, creating the clump object as well # make all spheres within one clump a single color, unless color was specified by the user if not userColor: for i in clump[1:]: O.bodies[ids[i]].shape.color=O.bodies[ids[clump[0]]].shape.color return ids+clumpIds SpherePack.toSimulation=SpherePack_toSimulation class inGtsSurface_py(Predicate): """This class was re-implemented in c++, but should stay here to serve as reference for implementing Predicates in pure python code. C++ allows us to play dirty tricks in GTS which are not accessible through pygts itself; the performance penalty of pygts comes from fact that if constructs and destructs bb tree for the surface at every invocation of gts.Point().is_inside(). That is cached in the c++ code, provided that the surface is not manipulated with during lifetime of the object (user's responsibility). --- Predicate for GTS surfaces. Constructed using an already existing surfaces, which must be closed. import gts surf=gts.read(open('horse.gts')) inGtsSurface(surf) .. note:: Padding is optionally supported by testing 6 points along the axes in the pad distance. This must be enabled in the ctor by saying doSlowPad=True. If it is not enabled and pad is not zero, warning is issued. """ def __init__(self,surf,noPad=False): # call base class ctor; necessary for virtual methods to work as expected. # see comments in _packPredicates.cpp for struct PredicateWrap. super(inGtsSurface,self).__init__() if not surf.is_closed(): raise RuntimeError("Surface for inGtsSurface predicate must be closed.") self.surf=surf self.noPad=noPad inf=float('inf') mn,mx=[inf,inf,inf],[-inf,-inf,-inf] for v in surf.vertices(): c=v.coords() mn,mx=[min(mn[i],c[i]) for i in 0,1,2],[max(mx[i],c[i]) for i in 0,1,2] self.mn,self.mx=tuple(mn),tuple(mx) import gts def aabb(self): return self.mn,self.mx def __call__(self,_pt,pad=0.): p=gts.Point(*_pt) if self.noPad: if pad!=0: warnings.warn("Padding disabled in ctor, using 0 instead.") return p.is_inside(self.surf) pp=[gts.Point(_pt[0]-pad,_pt[1],_pt[2]),gts.Point(_pt[0]+pad,_pt[1],_pt[2]),gts.Point(_pt[0],_pt[1]-pad,_pt[2]),gts.Point(_pt[0],_pt[1]+pad,_pt[2]),gts.Point(_pt[0],_pt[1],_pt[2]-pad),gts.Point(_pt[0],_pt[1],_pt[2]+pad)] return p.is_inside(self.surf) and pp[0].is_inside(self.surf) and pp[1].is_inside(self.surf) and pp[2].is_inside(self.surf) and pp[3].is_inside(self.surf) and pp[4].is_inside(self.surf) and pp[5].is_inside(self.surf) class inSpace(Predicate): """Predicate returning True for any points, with infinite bounding box.""" def __init__(self, _center=Vector3().Zero): self._center=_center def aabb(self): inf=float('inf'); return Vector3(-inf,-inf,-inf),Vector3(inf,inf,inf) def center(self): return self._center def dim(self): inf=float('inf'); return Vector3(inf,inf,inf) def __call__(self,pt,pad): return True class inHalfSpace(Predicate): """Predicate returning True any points, with infinite bounding box.""" def __init__(self, _center=Vector3().Zero, _dir=Vector3(1,0,0)): self._center = Vector3(_center) self._dir = Vector3(_dir) assert self._dir.norm() > 0., "Direction has to be nonzero vector" self._dir.normalize() self._d = -self._dir.dot(self._center) def aabb(self): d,c = self._dir, self._center inf=float('inf') min = Vector3(-inf,-inf,-inf) max = Vector3(+inf,+inf,+inf) for i in xrange(3): j = (i+1)%3 k = (i+2)%3 if d[i]==0 and d[j]==0: if d[k] > 0: min[k] = c[k] else: max[k] = c[k] return min,max def center(self): return self._center def dim(self): inf=float('inf') return Vector3(inf,inf,inf) def __call__(self,pt,pad): v = self._dir.dot(pt) + self._d return v > pad class inConvexPolyhedron(Predicate): def __init__(self,planes): self._inHalfSpaces = [inHalfSpace(c,d) for c,d in planes] self._min, self._max = self._computeAabb() def _computeAabb(self): try: import scipy.optimize except ImportError: raise ImportError, "scipy (package python-scipy) needed for pack.inConvexPolyhedron" min,max = Vector3.Zero, Vector3.Zero A,b = [],[] for h in self._inHalfSpaces: A.append(tuple(-h._dir)) b.append(h._d) inf = float('inf') for i in xrange(3): c = Vector3.Zero # c[i] = 1 opt = scipy.optimize.linprog(c,A_ub=A,b_ub=b,bounds=(-inf,inf)) errmsg = "Something wrong in pack.inConvexPolyhedron defining planes.\nThe scipy.optimize.linprog output:\n{}\n" if not opt.success: raise ValueError, errmsg.format(opt) min[i] = opt.x[i] # c[i] = -1 opt = scipy.optimize.linprog(c,A_ub=A,b_ub=b,bounds=(-inf,inf)) if not opt.success: raise ValueError, errmsg.format(opt) max[i] = opt.x[i] return min,max def aabb(self): return Vector3(self._min), Vector3(self._max) def center(self): return .5*(self._min + self._max) def dim(self): return self._max - self._min def __call__(self,pt,pad): for p in self._inHalfSpaces: if not p(pt,pad): return False return True ##### ## surface construction and manipulation ##### def gtsSurface2Facets(surf,**kw): """Construct facets from given GTS surface. \*\*kw is passed to utils.facet.""" import gts return [utils.facet([v.coords() for v in face.vertices()],**kw) for face in surf.faces()] def sweptPolylines2gtsSurface(pts,threshold=0,capStart=False,capEnd=False): """Create swept suface (as GTS triangulation) given same-length sequences of points (as 3-tuples). If threshold is given (>0), then * degenerate faces (with edges shorter than threshold) will not be created * gts.Surface().cleanup(threshold) will be called before returning, which merges vertices mutually closer than threshold. In case your pts are closed (last point concident with the first one) this will the surface strip of triangles. If you additionally have capStart==True and capEnd==True, the surface will be closed. .. note:: capStart and capEnd make the most naive polygon triangulation (diagonals) and will perhaps fail for non-convex sections. .. warning:: the algorithm connects points sequentially; if two polylines are mutually rotated or have inverse sense, the algorithm will not detect it and connect them regardless in their given order. """ import gts # will raise an exception in gts-less builds if not len(set([len(pts1) for pts1 in pts]))==1: raise RuntimeError("Polylines must be all of the same length!") vtxs=[[gts.Vertex(x,y,z) for x,y,z in pts1] for pts1 in pts] sectEdges=[[gts.Edge(vtx[i],vtx[i+1]) for i in xrange(0,len(vtx)-1)] for vtx in vtxs] interSectEdges=[[] for i in range(0,len(vtxs)-1)] for i in range(0,len(vtxs)-1): for j in range(0,len(vtxs[i])): interSectEdges[i].append(gts.Edge(vtxs[i][j],vtxs[i+1][j])) if j0: # replace edges of zero length with None; their faces will be skipped def fixEdges(edges): for i,e in enumerate(edges): if (Vector3(e.v1.x,e.v1.y,e.v1.z)-Vector3(e.v2.x,e.v2.y,e.v2.z)).norm()0: surf.cleanup(threshold) return surf def gtsSurfaceBestFitOBB(surf): """Return (Vector3 center, Vector3 halfSize, Quaternion orientation) describing best-fit oriented bounding box (OBB) for the given surface. See cloudBestFitOBB for details.""" import gts pts=[Vector3(v.x,v.y,v.z) for v in surf.vertices()] return cloudBestFitOBB(tuple(pts)) def revolutionSurfaceMeridians(sects,angles,origin=Vector3().Zero,orientation=Quaternion().Identity): """Revolution surface given sequences of 2d points and sequence of corresponding angles, returning sequences of 3d points representing meridian sections of the revolution surface. The 2d sections are turned around z-axis, but they can be transformed using the origin and orientation arguments to give arbitrary orientation.""" import math def toGlobal(x,y,z): return tuple(origin+orientation*(Vector3(x,y,z))) return [[toGlobal(x2d*math.cos(angles[i]),x2d*math.sin(angles[i]),y2d) for x2d,y2d in sects[i]] for i in range(0,len(sects))] ######## ## packing generators ######## def regularOrtho(predicate,radius,gap,**kw): """Return set of spheres in regular orthogonal grid, clipped inside solid given by predicate. Created spheres will have given radius and will be separated by gap space.""" ret=[] mn,mx=predicate.aabb() if(max([mx[i]-mn[i] for i in 0,1,2])==float('inf')): raise ValueError("Aabb of the predicate must not be infinite (didn't you use union | instead of intersection & for unbounded predicate such as notInNotch?"); xx,yy,zz=[arange(mn[i]+radius,mx[i]-radius,2*radius+gap) for i in 0,1,2] for xyz in itertools.product(xx,yy,zz): if predicate(xyz,radius): ret+=[utils.sphere(xyz,radius=radius,**kw)] if (len(ret)==0): warnings.warn('No spheres are produced by regularOrtho-function',category=RuntimeWarning) return ret def regularHexa(predicate,radius,gap,**kw): """Return set of spheres in regular hexagonal grid, clipped inside solid given by predicate. Created spheres will have given radius and will be separated by gap space.""" ret=[] a=2*radius+gap hy,hz=a*sqrt(3)/2.,a*sqrt(6)/3. mn,mx=predicate.aabb() dim=[mx[i]-mn[i] for i in 0,1,2] if(max(dim)==float('inf')): raise ValueError("Aabb of the predicate must not be infinite (didn't you use union | instead of intersection & for unbounded predicate such as notInNotch?"); ii,jj,kk=[range(0,int(dim[0]/a)+1),range(0,int(dim[1]/hy)+1),range(0,int(dim[2]/hz)+1)] for i,j,k in itertools.product(ii,jj,kk): #Simple HCP-lattice packing #http://en.wikipedia.org/wiki/Close-packing_of_equal_spheres#Simple_hcp_lattice coordSph = Vector3((2*i + ((j + k) % 2 ) ), (sqrt(3.)*(j + 1./3.*(k % 2))), (2.*sqrt(6.)/3.*k))*(a/2.0) + mn if predicate(coordSph,radius): ret+=[utils.sphere(coordSph,radius=radius,**kw)] if (len(ret)==0): warnings.warn('No spheres are produced by regularHexa-function',category=RuntimeWarning) return ret def filterSpherePack(predicate,spherePack,returnSpherePack=None,**kw): """Using given SpherePack instance, return spheres that satisfy predicate. It returns either a :yref:`yade._packSpheres.SpherePack` (if returnSpherePack) or a list. The packing will be recentered to match the predicate and warning is given if the predicate is larger than the packing.""" if returnSpherePack==None: warnings.warn('The default behavior will change; specify returnSpherePack=True for the new behavior, and False to get rid of this warning (your code will break in the future, however). The returned SpherePack object can be added to the simulation using SpherePack.toSimulation()',category=FutureWarning) returnSpherePack=False mn,mx=predicate.aabb() dimP,centP=predicate.dim(),predicate.center() dimS,centS=spherePack.dim(),spherePack.center() if dimP[0]>dimS[0] or dimP[1]>dimS[1] or dimP[2]>dimS[2]: warnings.warn("Packing's dimension (%s) doesn't fully contain dimension of the predicate (%s)."%(dimS,dimP)) spherePack.translate(centP-centS) if returnSpherePack: ret=SpherePack() for c,r in spherePack: if predicate(c,r): ret.add(c,r) return ret else: # return particles to be added to O.bodies ret=[] for s in spherePack: if predicate(s[0],s[1]): ret+=[utils.sphere(s[0],radius=s[1],**kw)] return ret def _memoizePacking(memoizeDb,sp,radius,rRelFuzz,wantPeri,fullDim,noPrint=False): if not memoizeDb: return import cPickle,sqlite3,time,os if os.path.exists(memoizeDb): conn=sqlite3.connect(memoizeDb) else: conn=sqlite3.connect(memoizeDb) c=conn.cursor() c.execute('create table packings (radius real, rRelFuzz real, dimx real, dimy real, dimz real, N integer, timestamp real, periodic integer, pack blob)') c=conn.cursor() packBlob=buffer(cPickle.dumps(sp.toList(),cPickle.HIGHEST_PROTOCOL)) packDim=sp.cellSize if wantPeri else fullDim c.execute('insert into packings values (?,?,?,?,?,?,?,?,?)',(radius,rRelFuzz,packDim[0],packDim[1],packDim[2],len(sp),time.time(),wantPeri,packBlob,)) c.close() conn.commit() if not noPrint: print "Packing saved to the database",memoizeDb def _getMemoizedPacking(memoizeDb,radius,rRelFuzz,x1,y1,z1,fullDim,wantPeri,fillPeriodic,spheresInCell,memoDbg=False,noPrint=False): """Return suitable SpherePack read from *memoizeDb* if found, None otherwise. :param fillPeriodic: whether to fill fullDim by repeating periodic packing :param wantPeri: only consider periodic packings """ import os,os.path,sqlite3,time,cPickle,sys if memoDbg and not noPrint: def memoDbgMsg(s): print s else: def memoDbgMsg(s): pass if not memoizeDb or not os.path.exists(memoizeDb): if memoizeDb: memoDbgMsg("Database %s does not exist."%memoizeDb) return None # find suitable packing and return it directly conn=sqlite3.connect(memoizeDb); c=conn.cursor(); try: c.execute('select radius,rRelFuzz,dimx,dimy,dimz,N,timestamp,periodic from packings order by N') except sqlite3.OperationalError: raise RuntimeError("ERROR: database `"+memoizeDb+"' not compatible with randomDensePack (corrupt, deprecated format or not a db created by randomDensePack)") for row in c: R,rDev,X,Y,Z,NN,timestamp,isPeri=row[0:8]; scale=radius/R rDev*=scale; X*=scale; Y*=scale; Z*=scale memoDbgMsg("Considering packing (radius=%g±%g,N=%g,dim=%g×%g×%g,%s,scale=%g), created %s"%(R,.5*rDev,NN,X,Y,Z,"periodic" if isPeri else "non-periodic",scale,time.asctime(time.gmtime(timestamp)))) if not isPeri and wantPeri: memoDbgMsg("REJECT: is not periodic, which is requested."); continue if wantPeri and (X/x1>0.9 or X/x1<0.6): memoDbgMsg("REJECT: initSize differs too much from scaled packing size."); continue if (rRelFuzz==0 and rDev!=0) or (rRelFuzz!=0 and rDev==0) or (rRelFuzz!=0 and abs((rDev-rRelFuzz)/rRelFuzz)>1e-2): memoDbgMsg("REJECT: radius fuzz differs too much (%g, %g desired)"%(rDev,rRelFuzz)); continue # radius fuzz differs too much if isPeri and wantPeri: if spheresInCell>NN and spheresInCell>0: memoDbgMsg("REJECT: Number of spheres in the packing too small"); continue if abs((y1/x1)/(Y/X)-1)>0.3 or abs((z1/x1)/(Z/X)-1)>0.3: memoDbgMsg("REJECT: proportions (y/x=%g, z/x=%g) differ too much from what is desired (%g, %g)."%(Y/X,Z/X,y1/x1,z1/x1)); continue else: if (X0) if 'inGtsSurface' in dir(_packPredicates) and type(predicate)==inGtsSurface and useOBB: center,dim,orientation=gtsSurfaceBestFitOBB(predicate.surf) print "Best-fit oriented-bounding-box computed for GTS surface, orientation is",orientation dim*=2 # gtsSurfaceBestFitOBB returns halfSize else: if not dim: dim=predicate.dim() if max(dim)==float('inf'): raise RuntimeError("Infinite predicate and no dimension of packing requested.") center=predicate.center() orientation=None if not wantPeri: fullDim=tuple([dim[i]+4*cropLayers*radius for i in 0,1,2]) else: # compute cell dimensions now, as they will be compared to ones stored in the db # they have to be adjusted to not make the cell to small WRT particle radius fullDim=dim cloudPorosity=0.25 # assume this number for the initial cloud (can be underestimated) beta,gamma=fullDim[1]/fullDim[0],fullDim[2]/fullDim[0] # ratios β=y₀/x₀, γ=z₀/x₀ N100=spheresInCell/cloudPorosity # number of spheres for cell being filled by spheres without porosity x1=radius*(1/(beta*gamma)*N100*(4/3.)*pi)**(1/3.) y1,z1=beta*x1,gamma*x1; vol0=x1*y1*z1 maxR=radius*(1+rRelFuzz) x1=max(x1,8*maxR); y1=max(y1,8*maxR); z1=max(z1,8*maxR); vol1=x1*y1*z1 N100*=vol1/vol0 # volume might have been increased, increase number of spheres to keep porosity the same sp=_getMemoizedPacking(memoizeDb,radius,rRelFuzz,x1,y1,z1,fullDim,wantPeri,fillPeriodic=True,spheresInCell=spheresInCell,memoDbg=False) if sp: if orientation: sp.cellSize=(0,0,0) # resetting cellSize avoids warning when rotating sp.rotate(*orientation.toAxisAngle()) return filterSpherePack(predicate,sp,material=material,returnSpherePack=returnSpherePack) else: print "No suitable packing in database found, running",'PERIODIC compression' if wantPeri else 'triaxial' sys.stdout.flush() O.switchScene(); O.resetThisScene() ### !! if wantPeri: # x1,y1,z1 already computed above sp=SpherePack() O.periodic=True #O.cell.refSize=(x1,y1,z1) O.cell.setBox((x1,y1,z1)) #print cloudPorosity,beta,gamma,N100,x1,y1,z1,O.cell.refSize #print x1,y1,z1,radius,rRelFuzz O.materials.append(FrictMat(young=3e10,density=2400)) num=sp.makeCloud(Vector3().Zero,O.cell.refSize,radius,rRelFuzz,spheresInCell,True) O.engines=[ForceResetter(),InsertionSortCollider([Bo1_Sphere_Aabb()],verletDist=.05*radius),InteractionLoop([Ig2_Sphere_Sphere_ScGeom()],[Ip2_FrictMat_FrictMat_FrictPhys()],[Law2_ScGeom_FrictPhys_CundallStrack()]),PeriIsoCompressor(charLen=2*radius,stresses=[-100e9,-1e8],maxUnbalanced=1e-2,doneHook='O.pause();',globalUpdateInt=5,keepProportions=True),NewtonIntegrator(damping=.6)] O.materials.append(FrictMat(young=30e9,frictionAngle=.5,poisson=.3,density=1e3)) for s in sp: O.bodies.append(utils.sphere(s[0],s[1])) O.dt=utils.PWaveTimeStep() O.run(); O.wait() sp=SpherePack(); sp.fromSimulation() #print 'Resulting cellSize',sp.cellSize,'proportions',sp.cellSize[1]/sp.cellSize[0],sp.cellSize[2]/sp.cellSize[0] # repetition to the required cell size will be done below, after memoizing the result else: assumedFinalDensity=0.6 V=(4.0/3.0)*pi*radius**3.0; N=assumedFinalDensity*fullDim[0]*fullDim[1]*fullDim[2]/V; TriaxialTest( numberOfGrains=int(N),radiusMean=radius,radiusStdDev=rRelFuzz, # upperCorner is just size ratio, if radiusMean is specified upperCorner=fullDim, ## no need to touch any the following noFiles=True,lowerCorner=[0,0,0],sigmaIsoCompaction=-1e7,sigmaLateralConfinement=-1e5,compactionFrictionDeg=1,StabilityCriterion=.05,strainRate=.2,thickness=-1,maxWallVelocity=.1,wallOversizeFactor=1.5,autoUnload=True,autoCompressionActivation=False).load() while ( numpy.isnan(utils.unbalancedForce()) or utils.unbalancedForce()>0.005 ) : O.run(100,True) sp=SpherePack(); sp.fromSimulation() O.switchScene() ### !! _memoizePacking(memoizeDb,sp,radius,rRelFuzz,wantPeri,fullDim) if wantPeri: sp.cellFill(Vector3(fullDim[0],fullDim[1],fullDim[2])) if orientation: sp.cellSize=(0,0,0); # reset periodicity to avoid warning when rotating periodic packing sp.rotate(*orientation.toAxisAngle()) return filterSpherePack(predicate,sp,material=material,color=color,returnSpherePack=returnSpherePack) def randomPeriPack(radius,initSize,rRelFuzz=0.0,memoizeDb=None,noPrint=False): """Generate periodic dense packing. A cell of initSize is stuffed with as many spheres as possible, then we run periodic compression with PeriIsoCompressor, just like with randomDensePack. :param radius: mean sphere radius :param rRelFuzz: relative fuzz of sphere radius (equal distribution); see the same param for randomDensePack. :param initSize: initial size of the periodic cell. :return: SpherePack object, which also contains periodicity information. """ from math import pi sp=_getMemoizedPacking(memoizeDb,radius,rRelFuzz,initSize[0],initSize[1],initSize[2],fullDim=Vector3(0,0,0),wantPeri=True,fillPeriodic=False,spheresInCell=-1,memoDbg=True,noPrint=noPrint) if sp: return sp O.switchScene(); O.resetThisScene() sp=SpherePack() O.periodic=True #O.cell.refSize=initSize O.cell.setBox(initSize) sp.makeCloud(Vector3().Zero,O.cell.refSize,radius,rRelFuzz,-1,True) O.engines=[ForceResetter(),InsertionSortCollider([Bo1_Sphere_Aabb()],verletDist=.05*radius),InteractionLoop([Ig2_Sphere_Sphere_ScGeom()],[Ip2_FrictMat_FrictMat_FrictPhys()],[Law2_ScGeom_FrictPhys_CundallStrack()]),PeriIsoCompressor(charLen=2*radius,stresses=[-100e9,-1e8],maxUnbalanced=1e-2,doneHook='O.pause();',globalUpdateInt=20,keepProportions=True),NewtonIntegrator(damping=.8)] O.materials.append(FrictMat(young=30e9,frictionAngle=.1,poisson=.3,density=1e3)) for s in sp: O.bodies.append(utils.sphere(s[0],s[1])) O.dt=utils.PWaveTimeStep() O.timingEnabled=True O.run(); O.wait() ret=SpherePack() ret.fromSimulation() _memoizePacking(memoizeDb,ret,radius,rRelFuzz,wantPeri=True,fullDim=Vector3(0,0,0),noPrint=noPrint) # fullDim unused O.switchScene() return ret def hexaNet( radius, cornerCoord=[0,0,0], xLength=1., yLength=0.5, mos=0.08, a=0.04, b=0.04, startAtCorner=True, isSymmetric=False, **kw ): """Definition of the particles for a hexagonal wire net in the x-y-plane for the WireMatPM. :param radius: radius of the particle :param cornerCoord: coordinates of the lower left corner of the net :param xLenght: net length in x-direction :param yLenght: net length in y-direction :param mos: mesh opening size (horizontal distance between the double twists) :param a: length of double-twist :param b: height of single wire section :param startAtCorner: if true the generation starts with a double-twist at the lower left corner :param isSymmetric: defines if the net is symmetric with respect to the y-axis :return: set of spheres which defines the net (net) and exact dimensions of the net (lx,ly). .. note:: This packing works for the WireMatPM only. The particles at the corner are always generated first. For examples on how to use this packing see examples/WireMatPM. In order to create the proper interactions for the net the interaction radius has to be adapted in the simulation. """ # check input dimension if(xLength """ Module containing utility functions for plotting inside yade. See :ysrc:`examples/simple-scene/simple-scene-plot.py` or :ysrc:`examples/concrete/uniax.py` for example of usage. """ ## all exported names __all__=['data','plots','labels','live','liveInterval','autozoom','plot','reset','resetData','splitData','reverseData','addData','addAutoData','saveGnuplot','saveDataTxt','savePlotSequence'] # multi-threaded support for Tk # safe to import even if Tk will not be used import mtTkinter as Tkinter try: import Image except: try: import PIL.Image except: import warnings warnings.warn("PIL (python-imaging package) must be installed to use yade.plot") import matplotlib,os,time,math,itertools # running in batch # # If GtkAgg is the default, X must be working, which is not the case # with batches (DISPLAY is unset in such case) and importing pylab fails then. # # Agg does not require the GUI part and works without any DISPLAY active # just fine. # # see http://www.mail-archive.com/yade-dev@lists.launchpad.net/msg04320.html # and https://lists.launchpad.net/yade-users/msg03289.html # import yade.runtime if not yade.runtime.hasDisplay: matplotlib.use('Agg') from minieigen import * #matplotlib.use('TkAgg') #matplotlib.use('GTKAgg') ##matplotlib.use('QtAgg') matplotlib.rc('axes',grid=True) # put grid in all figures import pylab data={} "Global dictionary containing all data values, common for all plots, in the form {'name':[value,...],...}. Data should be added using plot.addData function. All [value,...] columns have the same length, they are padded with NaN if unspecified." imgData={} "Dictionary containing lists of strings, which have the meaning of images corresponding to respective :yref:`yade.plot.data` rows. See :yref:`yade.plot.plots` on how to plot images." plots={} # dictionary x-name -> (yspec,...), where yspec is either y-name or (y-name,'line-specification') "dictionary x-name -> (yspec,...), where yspec is either y-name or (y-name,'line-specification'). If ``(yspec,...)`` is ``None``, then the plot has meaning of image, which will be taken from respective field of :yref:`yade.plot.imgData`." labels={} "Dictionary converting names in data to human-readable names (TeX names, for instance); if a variable is not specified, it is left untranslated." xylabels={} "Dictionary of 2-tuples specifying (xlabel,ylabel) for respective plots; if either of them is None, the default auto-generated title is used." legendLoc=('upper left','upper right') "Location of the y1 and y2 legends on the plot, if y2 is active." live=True if yade.runtime.hasDisplay else False "Enable/disable live plot updating. Disabled by default for now, since it has a few rough edges." liveInterval=1 "Interval for the live plot updating, in seconds." autozoom=True "Enable/disable automatic plot rezooming after data update." scientific=True if hasattr(pylab,'ticklabel_format') else False ## safe default for older matplotlib versions "Use scientific notation for axes ticks." axesWd=0 "Linewidth (in points) to make *x* and *y* axes better visible; not activated if non-positive." current=-1 "Point that is being tracked with a scatter point. -1 is for the last point, set to *nan* to disable." afterCurrentAlpha=.2 "Color alpha value for part of lines after :yref:`yade.plot.current`, between 0 (invisible) to 1 (full color)" scatterMarkerKw=dict(verts=[(0.,0.),(-30.,10.),(-25,0),(-30.,-10.)],marker=None) "Parameters for the current position marker" componentSeparator='_' componentSuffixes={Vector2:{0:'x',1:'y'},Vector3:{0:'x',1:'y',2:'z'},Matrix3:{(0,0):'xx',(1,1):'yy',(2,2):'zz',(0,1):'xy',(0,2):'xz',(1,2):'yz',(1,0):'yx',(2,0):'zx',(2,1):'zy'}} # if a type with entry in componentSuffixes is given in addData, columns for individual components are synthesized using indices and suffixes given for each type, e.g. foo=Vector3r(1,2,3) will result in columns foox=1,fooy=2,fooz=3 def reset(): "Reset all plot-related variables (data, plots, labels)" global data, plots, labels # plotLines data={}; plots={}; imgData={} # plotLines={}; pylab.close('all') def resetData(): "Reset all plot data; keep plots and labels intact." global data data={} from yade.wrapper import * def splitData(): "Make all plots discontinuous at this point (adds nan's to all data fields)" addData({}) def reverseData(): """Reverse yade.plot.data order. Useful for tension-compression test, where the initial (zero) state is loaded and, to make data continuous, last part must *end* in the zero state. """ for k in data: data[k].reverse() def addDataColumns(dd): '''Add new columns with NaN data, without adding anything to other columns. Does nothing for columns that already exist''' numSamples=len(data[data.keys()[0]]) if len(data)>0 else 0 for d in dd: if d in data.keys(): continue data[d]=[nan for i in range(numSamples)] def addAutoData(): """Add data by evaluating contents of :yref:`yade.plot.plots`. Expressions rasing exceptions will be handled gracefully, but warning is printed for each. >>> from yade import plot >>> from pprint import pprint >>> O.reset() >>> plot.resetData() >>> plot.plots={'O.iter':('O.time',None,'numParticles=len(O.bodies)')} >>> plot.addAutoData() >>> pprint(plot.data) {'O.iter': [0], 'O.time': [0.0], 'numParticles': [0]} Note that each item in :yref:`yade.plot.plots` can be * an expression to be evaluated (using the ``eval`` builtin); * ``name=expression`` string, where ``name`` will appear as label in plots, and expression will be evaluated each time; * a dictionary-like object -- current keys are labels of plots and current values are added to :yref:`yade.plot.data`. The contents of the dictionary can change over time, in which case new lines will be created as necessary. A simple simulation with plot can be written in the following way; note how the energy plot is specified. >>> from yade import plot, utils >>> plot.plots={'i=O.iter':(O.energy,None,'total energy=O.energy.total()')} >>> # we create a simple simulation with one ball falling down >>> plot.resetData() >>> O.bodies.append(utils.sphere((0,0,0),1)) 0 >>> O.dt=utils.PWaveTimeStep() >>> O.engines=[ ... ForceResetter(), ... GravityEngine(gravity=(0,0,-10),warnOnce=False), ... NewtonIntegrator(damping=.4,kinSplit=True), ... # get data required by plots at every step ... PyRunner(command='yade.plot.addAutoData()',iterPeriod=1,initRun=True) ... ] >>> O.trackEnergy=True >>> O.run(2,True) >>> pprint(plot.data) #doctest: +ELLIPSIS {'gravWork': [0.0, -25.13274...], 'i': [0, 1], 'kinRot': [0.0, 0.0], 'kinTrans': [0.0, 7.5398...], 'nonviscDamp': [0.0, 10.0530...], 'total energy': [0.0, -7.5398...]} """ # this part of docstring does not work with Sphinx """ .. plot:: from yade import * from yade import plot,utils O.reset() O.engines=[ForceResetter(),GravityEngine(gravity=(0,0,-10),warnOnce=False),NewtonIntegrator(damping=.4,kinSplit=True),PyRunner(command='yade.plot.addAutoData()',iterPeriod=1,initRun=True)] O.bodies.append(utils.sphere((0,0,0),1)); O.dt=utils.PWaveTimeStep() plot.resetData() plot.plots={'i=O.iter':(O.energy,None,'total energy=O.energy.total()')} O.trackEnergy=True O.run(50,True) import pylab; pylab.grid(True) plot.legendLoc=('lower left','upper right') plot.plot(noShow=True) """ def colDictUpdate(col,dic): 'update *dic* with the value from col, which is a "expr" or "name=expr" string; all exceptions from ``eval`` are caught and warning is printed without adding any data.' name,expr=col.split('=',1) if '=' in col else (col,col) try: val=eval(expr) dic.update({name:val}) except: print 'WARN: ignoring exception raised while evaluating auto-column `'+expr+"'%s."%('' if name==expr else ' ('+name+')') cols={} for p in plots: pp=plots[p] colDictUpdate(p.strip(),cols) for y in tuplifyYAxis(plots[p]): # imgplot specifier if y==None: continue yy=addPointTypeSpecifier(y,noSplit=True)[0] # dict-like object if hasattr(yy,'keys'): cols.update(dict(yy)) # callable returning list sequence of expressions to evaluate #elif callable(yy): # for yyy in yy(): colDictUpdate(yyy,cols) # plain value else: colDictUpdate(yy,cols) addData(cols) def addData(*d_in,**kw): """Add data from arguments name1=value1,name2=value2 to yade.plot.data. (the old {'name1':value1,'name2':value2} is deprecated, but still supported) New data will be padded with nan's, unspecified data will be nan (nan's don't appear in graphs). This way, equal length of all data is assured so that they can be plotted one against any other. >>> from yade import plot >>> from pprint import pprint >>> plot.resetData() >>> plot.addData(a=1) >>> plot.addData(b=2) >>> plot.addData(a=3,b=4) >>> pprint(plot.data) {'a': [1, nan, 3], 'b': [nan, 2, 4]} Some sequence types can be given to addData; they will be saved in synthesized columns for individual components. >>> plot.resetData() >>> plot.addData(c=Vector3(5,6,7),d=Matrix3(8,9,10, 11,12,13, 14,15,16)) >>> pprint(plot.data) {'c_x': [5.0], 'c_y': [6.0], 'c_z': [7.0], 'd_xx': [8.0], 'd_xy': [9.0], 'd_xz': [10.0], 'd_yx': [11.0], 'd_yy': [12.0], 'd_yz': [13.0], 'd_zx': [14.0], 'd_zy': [15.0], 'd_zz': [16.0]} """ import numpy if len(data)>0: numSamples=len(data[data.keys()[0]]) else: numSamples=0 # align with imgData, if there is more of them than data if len(imgData)>0 and numSamples==0: numSamples=max(numSamples,len(imgData[imgData.keys()[0]])) d=(d_in[0] if len(d_in)>0 else {}) d.update(**kw) # handle types composed of multiple values (vectors, matrices) dNames=d.keys()[:] # make copy, since dict cannot change size if iterated over directly for name in dNames: if type(d[name]) in componentSuffixes: val=d[name] suffixes=componentSuffixes[type(d[name])] for ix in suffixes: d[name+componentSeparator+suffixes[ix]]=d[name][ix] del d[name] elif hasattr(d[name],'__len__'): raise ValueError('plot.addData given unhandled sequence type (is a '+type(d[name]).__name__+', must be number or '+'/'.join([k.__name__ for k in componentSuffixes])+')') for name in d: if not name in data.keys(): data[name]=[] for name in data: data[name]+=(numSamples-len(data[name]))*[nan] data[name].append(d[name] if name in d else nan) #print [(k,len(data[k])) for k in data.keys()] #numpy.array([nan for i in range(numSamples)]) #numpy.append(data[name],[d[name]],1) def addImgData(**kw): for k in kw: if k not in imgData: imgData[k]=[] # align imgData with data if len(data.keys())>0 and len(imgData.keys())>0: nData,nImgData=len(data[data.keys()[0]]),len(imgData[imgData.keys()[0]]) #if nImgData>nData-1: raise RuntimeError("imgData is already the same length as data?") if nImgData0 else None imgData[k]+=(nData-len(imgData[k])-1)*[lastValue] elif nData0 else nan data[k]+=(nImgData-nData)*[lastValue] # add one more, because we will append to imgData below # add values from kw newLen=(len(imgData[imgData.keys()[0]]) if imgData else 0)+1 # current length plus 1 for k in kw: if k in imgData and len(imgData[k])>0: imgData[k]+=(newLen-len(imgData[k])-1)*[imgData[k][-1]]+[kw[k]] # repeat last element as necessary else: imgData[k]=(newLen-1)*[None]+[kw[k]] # repeat None if no previous value # align values which were not in kw by repeating the last value for k in imgData: if len(imgData[k])current else 0) self.line.set_xdata(self.xdata[:preCurrEnd]); self.line.set_ydata(self.ydata[:preCurrEnd]) self.line2.set_xdata(self.xdata[current:]); self.line2.set_ydata(self.ydata[current:]) try: x,y=self.xdata[current],self.ydata[current] except IndexError: x,y=0,0 # this could be written in a nicer way, very likely try: pt=numpy.ndarray((2,),buffer=numpy.array([float(x),float(y)])) if self.scatter: self.scatter.set_offsets(pt) # change rotation of the marker (possibly incorrect) try: dx,dy=self.xdata[current]-self.xdata[current-1],self.ydata[current]-self.ydata[current-1] # smoothing from last n values, if possible # FIXME: does not show arrow at all if less than window values #try: # window=10 # dx,dy=[numpy.average(numpy.diff(dta[current-window:current])) for dta in self.xdata,self.ydata] #except IndexError: pass # there must be an easier way to find on-screen derivative angle, ask on the matplotlib mailing list axes=self.line.axes() p=axes.patch; xx,yy=p.get_verts()[:,0],p.get_verts()[:,1]; size=max(xx)-min(xx),max(yy)-min(yy) aspect=(size[1]/size[0])*(1./axes.get_data_ratio()) angle=math.atan(aspect*dy/dx) if dx<0: angle-=math.pi self.scatter.set_transform(matplotlib.transforms.Affine2D().rotate(angle)) except IndexError: pass except TypeError: pass # this happens at i386 with empty data, saying TypeError: buffer is too small for requested array currLineRefs=[] liveTimeStamp=0 # timestamp when live update was started, so that the old thread knows to stop if that changes nan=float('nan') def createPlots(subPlots=True,scatterSize=60,wider=False): global currLineRefs figs=set([l.line.get_axes().get_figure() for l in currLineRefs]) # get all current figures for f in figs: pylab.close(f) # close those currLineRefs=[] # remove older plots (breaks live updates of windows that are still open) if len(plots)==0: return # nothing to plot if subPlots: # compute number of rows and colums for plots we have subCols=int(round(math.sqrt(len(plots)))); subRows=int(math.ceil(len(plots)*1./subCols)) if wider: subRows,subCols=subCols,subRows for nPlot,p in enumerate(plots.keys()): pStrip=p.strip().split('=',1)[0] if not subPlots: pylab.figure() else: pylab.subplot(subRows,subCols,nPlot+1) if plots[p]==None: # image plot if not pStrip in imgData.keys(): imgData[pStrip]=[] # fake (empty) image if no data yet if len(imgData[pStrip])==0 or imgData[pStrip][-1]==None: img=Image.new('RGBA',(1,1),(0,0,0,0)) else: img=Image.open(imgData[pStrip][-1]) img=pylab.imshow(img,origin='lower') currLineRefs.append(LineRef(img,None,None,imgData[pStrip],None,pStrip)) pylab.gca().set_axis_off() continue plots_p=[addPointTypeSpecifier(o) for o in tuplifyYAxis(plots[p])] plots_p_y1,plots_p_y2=[],[]; y1=True missing=set() # missing data columns if pStrip not in data.keys(): missing.add(pStrip) for d in plots_p: if d[0]==None: y1=False; continue if y1: plots_p_y1.append(d) else: plots_p_y2.append(d) if d[0] not in data.keys() and not callable(d[0]) and not hasattr(d[0],'keys'): missing.add(d[0]) if missing: if len(data.keys())==0 or len(data[data.keys()[0]])==0: # no data at all yet, do not add garbage NaNs for m in missing: data[m]=[] else: print 'Missing columns in plot.data, adding NaN: ',','.join(list(missing)) addDataColumns(missing) def createLines(pStrip,ySpecs,isY1=True,y2Exists=False): '''Create data lines from specifications; this code is common for y1 and y2 axes; it handles y-data specified as callables, which might create additional lines when updated with liveUpdate. ''' # save the original specifications; they will be smuggled into the axes object # the live updated will run yNameFuncs to see if there are new lines to be added # and will add them if necessary yNameFuncs=set([d[0] for d in ySpecs if callable(d[0])]) | set([d[0].keys for d in ySpecs if hasattr(d[0],'keys')]) yNames=set() ySpecs2=[] for ys in ySpecs: # ys[0]() must return list of strings, which are added to ySpecs2; line specifier is synthesized by tuplifyYAxis and cannot be specified by the user if callable(ys[0]): ySpecs2+=[(ret,ys[1]) for ret in ys[0]()] elif hasattr(ys[0],'keys'): ySpecs2+=[(yy,'') for yy in ys[0].keys()] else: ySpecs2.append(ys) if len(ySpecs2)==0: print 'yade.plot: creating fake plot, since there are no y-data yet' line,=pylab.plot([nan],[nan]) line2,=pylab.plot([nan],[nan]) currLineRefs.append(LineRef(line,None,line2,[nan],[nan])) # set different color series for y1 and y2 so that they are recognizable if pylab.rcParams.has_key('axes.color_cycle'): pylab.rcParams['axes.color_cycle']='b,g,r,c,m,y,k' if not isY1 else 'm,y,k,b,g,r,c' for d in ySpecs2: yNames.add(d) line,=pylab.plot(data[pStrip],data[d[0]],d[1],label=xlateLabel(d[0])) line2,=pylab.plot([],[],d[1],color=line.get_color(),alpha=afterCurrentAlpha) # use (0,0) if there are no data yet scatterPt=[0,0] if len(data[pStrip])==0 else (data[pStrip][current],data[d[0]][current]) # if current value is NaN, use zero instead scatter=pylab.scatter(scatterPt[0] if not math.isnan(scatterPt[0]) else 0,scatterPt[1] if not math.isnan(scatterPt[1]) else 0,s=scatterSize,color=line.get_color(),**scatterMarkerKw) currLineRefs.append(LineRef(line,scatter,line2,data[pStrip],data[d[0]])) axes=line.axes labelLoc=(legendLoc[0 if isY1 else 1] if y2Exists>0 else 'best') l=pylab.legend(loc=labelLoc) if hasattr(l,'draggable'): l.draggable(True) if scientific: pylab.ticklabel_format(style='sci',scilimits=(0,0),axis='both') # fixes scientific exponent placement for y2: https://sourceforge.net/mailarchive/forum.php?thread_name=20101223174750.GD28779%40ykcyc&forum_name=matplotlib-users if not isY1: axes.yaxis.set_offset_position('right') if isY1: pylab.ylabel((', '.join([xlateLabel(_p[0]) for _p in ySpecs2])) if p not in xylabels or not xylabels[p][1] else xylabels[p][1]) pylab.xlabel(xlateLabel(pStrip) if (p not in xylabels or not xylabels[p][0]) else xylabels[p][0]) else: pylab.ylabel((', '.join([xlateLabel(_p[0]) for _p in ySpecs2])) if (p not in xylabels or len(xylabels[p])<3 or not xylabels[p][2]) else xylabels[p][2]) # if there are callable/dict ySpecs, save them inside the axes object, so that the live updater can use those if yNameFuncs: axes.yadeYNames,axes.yadeYFuncs,axes.yadeXName,axes.yadeLabelLoc=yNames,yNameFuncs,pStrip,labelLoc # prepend yade to avoid clashes createLines(pStrip,plots_p_y1,isY1=True,y2Exists=len(plots_p_y2)>0) if axesWd>0: pylab.axhline(linewidth=axesWd,color='k') pylab.axvline(linewidth=axesWd,color='k') # create y2 lines, if any if len(plots_p_y2)>0: pylab.twinx() # create the y2 axis createLines(pStrip,plots_p_y2,isY1=False,y2Exists=True) if 'title' in O.tags.keys(): pylab.title(O.tags['title']) def liveUpdate(timestamp): global liveTimeStamp liveTimeStamp=timestamp while True: if not live or liveTimeStamp!=timestamp: return figs,axes,linesData=set(),set(),set() for l in currLineRefs: l.update() figs.add(l.line.get_figure()) axes.add(l.line.axes) linesData.add(id(l.ydata)) # find callables in y specifiers, create new lines if necessary for ax in axes: if not hasattr(ax,'yadeYFuncs') or not ax.yadeYFuncs: continue # not defined of empty yy=set(); for f in ax.yadeYFuncs: if callable(f): yy.update(f()) elif hasattr(f,'keys'): yy.update(f.keys()) else: raise ValueError("Internal error: ax.yadeYFuncs items must be callables or dictionary-like objects and nothing else.") #print 'callables y names:',yy news=yy-ax.yadeYNames if not news: continue for new in news: ax.yadeYNames.add(new) if new in data.keys() and id(data[new]) in linesData: continue # do not add when reloaded and the old lines are already there print 'yade.plot: creating new line for',new if not new in data.keys(): data[new]=len(data[ax.yadeXName])*[nan] # create data entry if necessary #print 'data',len(data[ax.yadeXName]),len(data[new]),data[ax.yadeXName],data[new] line,=ax.plot(data[ax.yadeXName],data[new],label=xlateLabel(new)) # no line specifier line2,=ax.plot([],[],color=line.get_color(),alpha=afterCurrentAlpha) scatterPt=(0 if len(data[ax.yadeXName])==0 or math.isnan(data[ax.yadeXName][current]) else data[ax.yadeXName][current]),(0 if len(data[new])==0 or math.isnan(data[new][current]) else data[new][current]) scatter=ax.scatter(scatterPt[0],scatterPt[1],s=60,color=line.get_color(),**scatterMarkerKw) currLineRefs.append(LineRef(line,scatter,line2,data[ax.yadeXName],data[new])) ax.set_ylabel(ax.get_ylabel()+(', ' if ax.get_ylabel() else '')+xlateLabel(new)) # it is possible that the legend has not yet been created l=ax.legend(loc=ax.yadeLabelLoc) if hasattr(l,'draggable'): l.draggable(True) if autozoom: for ax in axes: try: ax.relim() # recompute axes limits ax.autoscale_view() except RuntimeError: pass # happens if data are being updated and have not the same dimension at the very moment for fig in figs: try: fig.canvas.draw() except RuntimeError: pass # happens here too time.sleep(liveInterval) def savePlotSequence(fileBase,stride=1,imgRatio=(5,7),title=None,titleFrames=20,lastFrames=30): '''Save sequence of plots, each plot corresponding to one line in history. It is especially meant to be used for :yref:`yade.utils.makeVideo`. :param stride: only consider every stride-th line of history (default creates one frame per each line) :param title: Create title frame, where lines of title are separated with newlines (``\\n``) and optional subtitle is separated from title by double newline. :param int titleFrames: Create this number of frames with title (by repeating its filename), determines how long the title will stand in the movie. :param int lastFrames: Repeat the last frame this number of times, so that the movie does not end abruptly. :return: List of filenames with consecutive frames. ''' createPlots(subPlots=True,scatterSize=60,wider=True) sqrtFigs=math.sqrt(len(plots)) pylab.gcf().set_size_inches(8*sqrtFigs,5*sqrtFigs) # better readable pylab.subplots_adjust(left=.05,right=.95,bottom=.05,top=.95) # make it more compact if len(plots)==1 and plots[plots.keys()[0]]==None: # only pure snapshot is there pylab.gcf().set_size_inches(5,5) pylab.subplots_adjust(left=0,right=1,bottom=0,top=1) #if not data.keys(): raise ValueError("plot.data is empty.") pltLen=max(len(data[data.keys()[0]]) if data else 0,len(imgData[imgData.keys()[0]]) if imgData else 0) if pltLen==0: raise ValueError("Both plot.data and plot.imgData are empty.") global current, currLineRefs ret=[] print 'Saving %d plot frames, it can take a while...'%(pltLen) for i,n in enumerate(range(0,pltLen,stride)): current=n for l in currLineRefs: l.update() out=fileBase+'-%03d.png'%i pylab.gcf().savefig(out) ret.append(out) if len(ret)==0: raise RuntimeError("No images created?!") if title: titleImgName=fileBase+'-title.png' createTitleFrame(titleImgName,Image.open(ret[-1]).size,title) ret=titleFrames*[titleImgName]+ret if lastFrames>1: ret+=(lastFrames-1)*[ret[-1]] return ret def createTitleFrame(out,size,title): 'create figure with title and save to file; a figure object must be opened to get the right size' pylab.clf(); fig=pylab.gcf() #insize=fig.get_size_inches(); size=insize[1]*fig.get_dpi(),insize[0]*fig.get_dpi() # this gives wrong dimensions... #fig.set_facecolor('blue'); fig.patch.set_color('blue'); fig.patch.set_facecolor('blue'); fig.patch.set_alpha(None) title,subtitle=title.split('\n\n') lines=[(t,True) for t in title.split('\n')]+([(t,False) for t in subtitle.split('\n')] if subtitle else []) nLines=len(lines); fontSizes=size[1]/10.,size[1]/16. import matplotlib.mathtext def writeLine(text,vertPos,fontsize): rgba,depth=matplotlib.mathtext.MathTextParser('Bitmap').to_rgba(text,fontsize=fontsize,dpi=fig.get_dpi(),color='blue') textsize=rgba.shape[1],rgba.shape[0] if textsize[0]>size[0]: rgba,depth=matplotlib.mathtext.MathTextParser('Bitmap').to_rgba(text,fontsize=fontsize*size[0]/textsize[0],dpi=fig.get_dpi(),color='blue') textsize=rgba.shape[1],rgba.shape[0] fig.figimage(rgba.astype(float)/255.,xo=(size[0]-textsize[0])/2.,yo=vertPos-depth) ht=size[1]; y0=ht-2*fontSizes[0]; yStep=(ht-2.5*fontSizes[0])/len(lines) for i,(l,isTitle) in enumerate(lines): writeLine(l,y0-i*yStep,fontSizes[0 if isTitle else 1]) fig.savefig(out) def plot(noShow=False,subPlots=True): """Do the actual plot, which is either shown on screen (and nothing is returned: if *noShow* is ``False`` - note that your yade compilation should present qt4 feature so that figures can be displayed) or, if *noShow* is ``True``, returned as matplotlib's Figure object or list of them. You can use >>> from yade import plot >>> plot.resetData() >>> plot.plots={'foo':('bar',)} >>> plot.plot(noShow=True).savefig('someFile.pdf') >>> import os >>> os.path.exists('someFile.pdf') True >>> os.remove('someFile.pdf') to save the figure to file automatically. .. note:: For backwards compatibility reasons, *noShow* option will return list of figures for multiple figures but a single figure (rather than list with 1 element) if there is only 1 figure. """ createPlots(subPlots=subPlots) global currLineRefs figs=set([l.line.axes.get_figure() for l in currLineRefs]) if not hasattr(list(figs)[0],'show') and not noShow: import warnings warnings.warn('plot.plot not showing figure (matplotlib using headless backend?)') noShow=True if not noShow: if not yade.runtime.hasDisplay: return # would error out with some backends, such as Agg used in batches if live: import thread thread.start_new_thread(liveUpdate,(time.time(),)) # pylab.show() # this blocks for some reason; call show on figures directly for f in figs: f.show() else: figs=list(set([l.line.get_figure() for l in currLineRefs])) if len(figs)==1: return figs[0] else: return figs def saveDataTxt(fileName,vars=None): """Save plot data into a (optionally compressed) text file. The first line contains a comment (starting with ``#``) giving variable name for each of the columns. This format is suitable for being loaded for further processing (outside yade) with ``numpy.genfromtxt`` function, which recognizes those variable names (creating numpy array with named entries) and handles decompression transparently. >>> from yade import plot >>> from pprint import pprint >>> plot.reset() >>> plot.addData(a=1,b=11,c=21,d=31) # add some data here >>> plot.addData(a=2,b=12,c=22,d=32) >>> pprint(plot.data) {'a': [1, 2], 'b': [11, 12], 'c': [21, 22], 'd': [31, 32]} >>> plot.saveDataTxt('/tmp/dataFile.txt.bz2',vars=('a','b','c')) >>> import numpy >>> d=numpy.genfromtxt('/tmp/dataFile.txt.bz2',dtype=None,names=True) >>> d['a'] array([1, 2]) >>> d['b'] array([11, 12]) :param fileName: file to save data to; if it ends with ``.bz2`` / ``.gz``, the file will be compressed using bzip2 / gzip. :param vars: Sequence (tuple/list/set) of variable names to be saved. If ``None`` (default), all variables in :yref:`yade.plot.plot` are saved. """ import bz2,gzip if not vars: vars=data.keys(); vars.sort() if fileName.endswith('.bz2'): f=bz2.BZ2File(fileName,'w') elif fileName.endswith('.gz'): f=gzip.GzipFile(fileName,'w') else: f=open(fileName,'w') f.write("# "+"\t\t".join(vars)+"\n") for i in range(len(data[vars[0]])): f.write("\t".join([str(data[var][i]) for var in vars])+"\n") f.close() def savePylab(baseName,timestamp=False,title=None): '''This function is not finished, do not use it.''' import time if len(data.keys())==0: raise RuntimeError("No data for plotting were saved.") if timestamp: baseName+=_mkTimestamp() baseNameNoPath=baseName.split('/')[-1] saveDataTxt(fileName=baseName+'.data.bz2') if len(plots)==0: raise RuntimeError("No plots to save, only data saved.") py=file(baseName+'.py','w') py.write('#!/usr/bin/env python\n# encoding: utf-8\n# created '+time.asctime()+' ('+time.strftime('%Y%m%d_%H:%M')+')\n#\nimport pylab, numpy\n') py.write("data=numpy.genfromtxt('%s.data.bz2',dtype=None,names=True)\n"%baseName) subCols=int(round(math.sqrt(len(plots)))); subRows=int(math.ceil(len(plots)*1./subCols)) for nPlot,p in enumerate(plots.keys()): pStrip=p.strip().split('=',1)[0] if plots[p]==None: continue # image plots, which is not exported if len(plots)==1: py.write('pylab.figure()\n') else: py.write('pylab.subplot(%d,%d,%d)\n'%(subRows,subCols,nPlots+1)) def _mkTimestamp(): import time return time.strftime('_%Y%m%d_%H:%M') def saveGnuplot(baseName,term='wxt',extension=None,timestamp=False,comment=None,title=None,varData=False): """Save data added with :yref:`yade.plot.addData` into (compressed) file and create .gnuplot file that attempts to mimick plots specified with :yref:`yade.plot.plots`. :param baseName: used for creating baseName.gnuplot (command file for gnuplot), associated ``baseName.data.bz2`` (data) and output files (if applicable) in the form ``baseName.[plot number].extension`` :param term: specify the gnuplot terminal; defaults to ``x11``, in which case gnuplot will draw persistent windows to screen and terminate; other useful terminals are ``png``, ``cairopdf`` and so on :param extension: extension for ``baseName`` defaults to terminal name; fine for png for example; if you use ``cairopdf``, you should also say ``extension='pdf'`` however :param bool timestamp: append numeric time to the basename :param bool varData: whether file to plot will be declared as variable or be in-place in the plot expression :param comment: a user comment (may be multiline) that will be embedded in the control file :return: name of the gnuplot file created. """ if len(data.keys())==0: raise RuntimeError("No data for plotting were saved.") if timestamp: baseName+=_mkTimestamp() baseNameNoPath=baseName.split('/')[-1] vars=data.keys(); vars.sort() saveDataTxt(fileName=baseName+'.data.bz2',vars=vars) fPlot=file(baseName+".gnuplot",'w') fPlot.write('#!/usr/bin/env gnuplot\n#\n# created '+time.asctime()+' ('+time.strftime('%Y%m%d_%H:%M')+')\n#\n') if comment: fPlot.write('# '+comment.replace('\n','\n# ')+'#\n') dataFile='"< bzcat %s.data.bz2"'%(baseNameNoPath) if varData: fPlot.write('dataFile=%s'%dataFile); dataFile='dataFile' if not extension: extension=term i=0 for p in plots: pStrip=p.strip().split('=',1)[0] if plots[p]==None: continue ## this plot is image plot, which is not applicable to gnuplot plots_p=[addPointTypeSpecifier(o) for o in tuplifyYAxis(plots[p])] if term in ['wxt','x11']: fPlot.write("set term %s %d persist\n"%(term,i)) else: fPlot.write("set term %s; set output '%s.%d.%s'\n"%(term,baseNameNoPath,i,extension)) fPlot.write("set xlabel '%s'\n"%xlateLabel(p)) fPlot.write("set grid\n") fPlot.write("set datafile missing 'nan'\n") if title: fPlot.write("set title '%s'\n"%title) y1=True; plots_y1,plots_y2=[],[] # replace callable/dict-like data specifiers by the results, it that particular data exists plots_p2=[] for pp in plots_p: if callable(pp[0]): plots_p2+=[(ppp,'') for ppp in pp[0]() if ppp in data.keys()] elif hasattr(pp[0],'keys'): plots_p2+=[(name,val) for name,val in pp[0].items() if name in data.keys()] else: plots_p2.append((pp[0],pp[1])) plots_p=plots_p2 #plots_p=sum([([(pp,'') for pp in p[0]() if pp in data.keys()] if callable(p[0]) else [(p[0],p[1])] ) for p in plots_p],[]) for d in plots_p: if d[0]==None: y1=False; continue if y1: plots_y1.append(d) else: plots_y2.append(d) fPlot.write("set ylabel '%s'\n"%(','.join([xlateLabel(_p[0]) for _p in plots_y1]))) if len(plots_y2)>0: fPlot.write("set y2label '%s'\n"%(','.join([xlateLabel(_p[0]) for _p in plots_y2]))) fPlot.write("set y2tics\n") ppp=[] for pp in plots_y1: ppp.append(" %s using %d:%d title '← %s(%s)' with lines"%(dataFile,vars.index(pStrip)+1,vars.index(pp[0])+1,xlateLabel(pp[0]),xlateLabel(pStrip),)) for pp in plots_y2: ppp.append(" %s using %d:%d title '%s(%s) →' with lines axes x1y2"%(dataFile,vars.index(pStrip)+1,vars.index(pp[0])+1,xlateLabel(pp[0]),xlateLabel(pStrip),)) fPlot.write("plot "+",".join(ppp)+"\n") i+=1 fPlot.close() return baseName+'.gnuplot' trunk-2018.02b/py/polyhedra_utils.py000066400000000000000000000131561324306050200174050ustar00rootroot00000000000000# 2013 Jan Elias, http://www.fce.vutbr.cz/STM/elias.j/, elias.j@fce.vutbr.cz # https://www.vutbr.cz/www_base/gigadisk.php?i=95194aa9a """ Auxiliary functions for polyhedra """ import math,random,doctest,geom,numpy from yade import Vector3 from yade.wrapper import * try: # use psyco if available import psyco psyco.full() except ImportError: pass # c++ implementations for performance reasons from yade._polyhedra_utils import * #********************************************************************************** def randomColor(seed=None): random.seed(seed); #Return random Vector3 with each component in interval 0...1 (uniform distribution) return Vector3(random.random(),random.random(),random.random()) #********************************************************************************** #create polyhedra, one can specify vertices directly, or leave it empty for random shape def polyhedra(material,size=Vector3(1,1,1),seed=None,v=[],mask=1,fixed=False, color=[-1,-1,-1]): """create polyhedra, one can specify vertices directly, or leave it empty for random shape. :param Material material: material of new body :param Vector3 size: size of new body (see Polyhedra docs) :param float seed: seed for random operations :param [Vector3] v: list of body vertices (see Polyhedra docs) """ b=Body() random.seed(seed); b.aspherical = True if len(v)>0: b.shape = Polyhedra(v=v) else: b.shape = Polyhedra(size = size, seed=random.randint(0,1E6)) if color[0] == -1: b.shape.color = randomColor(seed=random.randint(0,1E6)) else: b.shape.color = color b.mat = material b.state.mass = b.mat.density*b.shape.GetVolume() b.state.inertia = b.shape.GetInertia()*b.mat.density b.state.ori = b.shape.GetOri() b.state.pos = b.shape.GetCentroid() b.mask=mask if fixed: b.state.blockedDOFs = 'xyzXYZ' return b #********************************************************************************** #creates polyhedra having N vertices and resembling sphere def polyhedralBall(radius, N, material, center,mask=1): """creates polyhedra having N vertices and resembling sphere :param float radius: ball radius :param int N: number of vertices :param Material material: material of new body :param Vector3 center: center of the new body """ pts = [] inc = math.pi * (3. - math.sqrt(5.)) off = 2. / float(N) for k in range(0, N): y = k * off - 1. + (off / 2.) r = math.sqrt(1. - y*y) phi = k * inc pts.append([math.cos(phi)*r*radius, y*radius, math.sin(phi)*r*radius]) ball = polyhedra(material,v=pts) ball.state.pos = center return ball #********************************************************************************** def polyhedraTruncIcosaHed(radius, material, centre,mask=1): pts = [] p = (1.+math.sqrt(5.))/2. f = radius/math.sqrt(9.*p + 1.) A = [[0.,1.,3.*p],[2.,1.+2.*p,p],[1.,2.+p,2.*p]] for a in A: a = [a[0]*f,a[1]*f,a[2]*f] B = [a,[a[1],a[2],a[0]],[a[2],a[0],a[1]]] for b in B: pts.append(b) if not b[0] == 0: pts.append([-b[0], b[1], b[2]]) if not b[1] == 0: pts.append([-b[0],-b[1], b[2]]) if not b[2] == 0: pts.append([-b[0],-b[1],-b[2]]) if not b[2] == 0: pts.append([-b[0], b[1],-b[2]]) if not b[1] == 0: pts.append([ b[0],-b[1], b[2]]) if not b[2] == 0: pts.append([ b[0],-b[1],-b[2]]) if not b[2] == 0: pts.append([ b[0], b[1],-b[2]]) ball = polyhedra(material,v=pts) ball.state.pos = centre return ball #********************************************************************************** def polyhedraSnubCube(radius, material, centre, mask=1): pts = [] f = radius/1.3437133737446 c1 = 0.337754 c2 = 1.14261 c3 = 0.621226 A = [[c2,c1,c3],[c1,c3,c2],[c3,c2,c1],[-c1,-c2,-c3],[-c2,-c3,-c1],[-c3,-c1,-c2]] for a in A: a = [a[0]*f,a[1]*f,a[2]*f] pts.append([-a[0],-a[1], a[2]]) pts.append([ a[0],-a[1],-a[2]]) pts.append([-a[0], a[1],-a[2]]) pts.append([ a[0], a[1], a[2]]) ball = polyhedra(material,v=pts) ball.state.pos = centre return ball #********************************************************************************** #fill box [mincoord, maxcoord] by non-overlaping polyhedrons with random geometry and sizes within the range (uniformly distributed) def fillBox(mincoord, maxcoord,material,sizemin=[1,1,1],sizemax=[1,1,1],ratio=[0,0,0],seed=None,mask=1): """fill box [mincoord, maxcoord] by non-overlaping polyhedrons with random geometry and sizes within the range (uniformly distributed) :param Vector3 mincoord: first corner :param Vector3 maxcoord: second corner :param Vector3 sizemin: minimal size of bodies :param Vector3 sizemax: maximal size of bodies :param Vector3 ratio: scaling ratio :param float seed: random seed """ random.seed(seed); v = fillBox_cpp(mincoord, maxcoord, sizemin,sizemax, ratio, random.randint(0,1E6), material) #lastnan = -1 #for i in range(0,len(v)): # if(math.isnan(v[i][0])): # O.bodies.append(polyhedra(material,seed=random.randint(0,1E6),v=v[lastnan+1:i],mask=1,fixed=False)) # lastnan = i #********************************************************************************** #fill box [mincoord, maxcoord] by non-overlaping polyhedrons with random geometry and sizes within the range (uniformly distributed) def fillBoxByBalls(mincoord, maxcoord,material,sizemin=[1,1,1],sizemax=[1,1,1],ratio=[0,0,0],seed=None,mask=1,numpoints=60): random.seed(seed); v = fillBoxByBalls_cpp(mincoord, maxcoord, sizemin,sizemax, ratio, random.randint(0,1E6), material,numpoints) #lastnan = -1 #for i in range(0,len(v)): # if(math.isnan(v[i][0])): # O.bodies.append(polyhedra(material,seed=random.randint(0,1E6),v=v[lastnan+1:i],mask=1,fixed=False)) # lastnan = i trunk-2018.02b/py/post2d.py000066400000000000000000000327531324306050200154150ustar00rootroot00000000000000# encoding: utf-8 # 2009 © Václav Šmilauer """ Module for 2d postprocessing, containing classes to project points from 3d to 2d in various ways, providing basic but flexible framework for extracting arbitrary scalar values from bodies/interactions and plotting the results. There are 2 basic components: flatteners and extractors. The algorithms operate on bodies (default) or interactions, depending on the ``intr`` parameter of post2d.data. Flatteners ========== Instance of classes that convert 3d (model) coordinates to 2d (plot) coordinates. Their interface is defined by the :yref:`yade.post2d.Flatten` class (``__call__``, ``planar``, ``normal``). Extractors ========== Callable objects returning scalar or vector value, given a body/interaction object. If a 3d vector is returned, Flattener.planar is called, which should return only in-plane components of the vector. Example ======= This example can be found in examples/concrete/uniax-post.py :: from yade import post2d import pylab # the matlab-like interface of matplotlib O.load('/tmp/uniax-tension.xml.bz2') # flattener that project to the xz plane flattener=post2d.AxisFlatten(useRef=False,axis=1) # return scalar given a Body instance extractDmg=lambda b: b.state.normDmg # will call flattener.planar implicitly # the same as: extractVelocity=lambda b: flattener.planar(b,b.state.vel) extractVelocity=lambda b: b.state.vel # create new figure pylab.figure() # plot raw damage post2d.plot(post2d.data(extractDmg,flattener)) # plot smooth damage into new figure pylab.figure(); ax,map=post2d.plot(post2d.data(extractDmg,flattener,stDev=2e-3)) # show color scale pylab.colorbar(map,orientation='horizontal') # raw velocity (vector field) plot pylab.figure(); post2d.plot(post2d.data(extractVelocity,flattener)) # smooth velocity plot; data are sampled at regular grid pylab.figure(); ax,map=post2d.plot(post2d.data(extractVelocity,flattener,stDev=1e-3)) # save last (current) figure to file pylab.gcf().savefig('/tmp/foo.png') # show the figures pylab.show() """ from yade.wrapper import * from minieigen import * class Flatten: """Abstract class for converting 3d point into 2d. Used by post2d.data2d.""" def __init__(self): pass def __call__(self,b): "Given a :yref:`Body` / :yref:`Interaction` instance, should return either 2d coordinates as a 2-tuple, or None if the Body should be discarded." pass def planar(self,pos,vec): "Given position and vector value, project the vector value to the flat plane and return its 2 in-plane components." def normal(self,pos,vec): "Given position and vector value, return lenght of the vector normal to the flat plane." class HelixFlatten(Flatten): """Class converting 3d point to 2d based on projection from helix. The y-axis in the projection corresponds to the rotation axis""" def __init__(self,useRef,thetaRange,dH_dTheta,axis=2,periodStart=0): """ :param bool useRef: use reference positions rather than actual positions :param (θmin,θmax) thetaRange: bodies outside this range will be discarded :param float dH_dTheta: inclination of the spiral (per radian) :param {0,1,2} axis: axis of rotation of the spiral :param float periodStart: height of the spiral for zero angle """ self.useRef,self.thetaRange,self.dH_dTheta,self.axis,self.periodStart=useRef,thetaRange,dH_dTheta,axis,periodStart self.ax1,self.ax2=(axis+1)%3,(axis+2)%3 def _getPos(self,b): return b.state.refPos if self.useRef else b.state.pos def __call__(self,b): import yade.utils xy,theta=yade.utils.spiralProject(_getPos(b),self.dH_dTheta,self.axis,self.periodStart) if thetathetaRange[1]: return None return xy def planar(self,b,vec): from math import sqrt pos=_getPos(b) pos[self.axis]=0; pos.Normalize() return pos.Dot(vec),vec[axis] def normal(self,pos,vec): ax=Vector3(0,0,0); ax[axis]=1; pos=_getPos(b) circum=ax.Cross(pos); circum.Normalize() return circum.Dot(vec) class CylinderFlatten(Flatten): """Class for converting 3d point to 2d based on projection onto plane from circle. The y-axis in the projection corresponds to the rotation axis; the x-axis is distance form the axis. """ def __init__(self,useRef,axis=2): """ :param useRef: (bool) use reference positions rather than actual positions :param axis: axis of the cylinder, ∈{0,1,2} """ if axis not in (0,1,2): raise IndexError("axis must be one of 0,1,2 (not %d)"%axis) self.useRef,self.axis=useRef,axis def _getPos(self,b): return b.state.refPos if self.useRef else b.state.pos def __call__(self,b): p=_getPos(b) pp=(p[(self.axis+1)%3],p[(self.axis+2)%3]) import math.sqrt return math.sqrt(pp[0]**2+pp[2]**2),p[self.axis] def planar(self,b,vec): pos=_getPos(b) from math import sqrt pos[self.axis]=0; pos.Normalize() return pos.Dot(vec),vec[axis] def normal(self,b,vec): pos=_getPos(b) ax=Vector3(0,0,0); ax[axis]=1 circum=ax.Cross(pos); circum.Normalize() return circum.Dot(vec) class AxisFlatten(Flatten): def __init__(self,useRef=False,axis=2): """ :param bool useRef: use reference positions rather than actual positions (only meaningful when operating on Bodies) :param {0,1,2} axis: axis normal to the plane; the return value will be simply position with this component dropped. """ if axis not in (0,1,2): raise IndexError("axis must be one of 0,1,2 (not %d)"%axis) self.useRef,self.axis=useRef,axis self.ax1,self.ax2=(self.axis+1)%3,(self.axis+2)%3 def __call__(self,b): p=((b.state.refPos if self.useRef else b.state.pos) if isinstance(b,Body) else b.geom.contactPoint) return (p[self.ax1],p[self.ax2]) def planar(self,pos,vec): return vec[self.ax1],vec[self.ax2] def normal(self,pos,vec): return vec[self.axis] def data(extractor,flattener,intr=False,onlyDynamic=True,stDev=None,relThreshold=3.,perArea=0,div=(50,50),margin=(0,0),radius=1): """Filter all bodies/interactions, project them to 2d and extract required scalar value; return either discrete array of positions and values, or smoothed data, depending on whether the stDev value is specified. The ``intr`` parameter determines whether we operate on bodies or interactions; the extractor provided should expect to receive body/interaction. :param callable extractor: receives :yref:`Body` (or :yref:`Interaction`, if ``intr`` is ``True``) instance, should return scalar, a 2-tuple (vector fields) or None (to skip that body/interaction) :param callable flattener: :yref:`yade.post2d.Flatten` instance, receiving body/interaction, returns its 2d coordinates or ``None`` (to skip that body/interaction) :param bool intr: operate on interactions rather than bodies :param bool onlyDynamic: skip all non-dynamic bodies :param float/None stDev: standard deviation for averaging, enables smoothing; ``None`` (default) means raw mode, where discrete points are returned :param float relThreshold: threshold for the gaussian weight function relative to stDev (smooth mode only) :param int perArea: if 1, compute weightedSum/weightedArea rather than weighted average (weightedSum/sumWeights); the first is useful to compute average stress; if 2, compute averages on subdivision elements, not using weight function :param (int,int) div: number of cells for the gaussian grid (smooth mode only) :param (float,float) margin: x,y margins around bounding box for data (smooth mode only) :param float/callable radius: Fallback value for radius (for raw plotting) for non-spherical bodies or interactions; if a callable, receives body/interaction and returns radius :return: dictionary Returned dictionary always containing keys 'type' (one of 'rawScalar','rawVector','smoothScalar','smoothVector', depending on value of smooth and on return value from extractor), 'x', 'y', 'bbox'. Raw data further contains 'radii'. Scalar fields contain 'val' (value from *extractor*), vector fields have 'valX' and 'valY' (2 components returned by the *extractor*). """ from minieigen import Vector3 xx,yy,dd1,dd2,rr=[],[],[],[],[] nDim=0 objects=O.interactions if intr else O.bodies for b in objects: if not intr and onlyDynamic and not b.dynamic: continue xy,d=flattener(b),extractor(b) if xy==None or d==None: continue if nDim==0: nDim=1 if isinstance(d,float) else 2 if nDim==1: dd1.append(d); elif len(d)==2: dd1.append(d[0]); dd2.append(d[1]) elif len(d)==3: d1,d2=flattener.planar(b,Vector3(d)) dd1.append(d1); dd2.append(d2) else: raise RuntimeError("Extractor must return float or 2 or 3 (not %d) floats"%nDim) if stDev==None: # radii are needed in the raw mode exclusively if not intr and isinstance(b.shape,Sphere): r=b.shape.radius else: r=(radius(b) if callable(radius) else radius) rr.append(r) xx.append(xy[0]); yy.append(xy[1]); if stDev==None: bbox=(min(xx),min(yy)),(max(xx),max(yy)) if nDim==1: return {'type':'rawScalar','x':xx,'y':yy,'val':dd1,'radii':rr,'bbox':bbox} else: return {'type':'rawVector','x':xx,'y':yy,'valX':dd1,'valY':dd2,'radii':rr,'bbox':bbox} from yade.WeightedAverage2d import GaussAverage import numpy lo,hi=(min(xx),min(yy)),(max(xx),max(yy)) llo=lo[0]-margin[0],lo[1]-margin[1]; hhi=hi[0]+margin[0],hi[1]+margin[1] ga=GaussAverage(llo,hhi,div,stDev,relThreshold) ga2=GaussAverage(llo,hhi,div,stDev,relThreshold) for i in range(0,len(xx)): ga.add(dd1[i],(xx[i],yy[i])) if nDim>1: ga2.add(dd2[i],(xx[i],yy[i])) step=[(hhi[i]-llo[i])/float(div[i]) for i in [0,1]] xxx,yyy=[numpy.arange(llo[i]+.5*step[i],hhi[i],step[i]) for i in [0,1]] ddd=numpy.zeros((len(yyy),len(xxx)),float) ddd2=numpy.zeros((len(yyy),len(xxx)),float) # set the type of average we are going to use if perArea==0: def compAvg(gauss,coord,cellCoord): return float(gauss.avg(coord)) elif perArea==1: def compAvg(gauss,coord,cellCoord): return gauss.avgPerUnitArea(coord) elif perArea==2: def compAvg(gauss,coord,cellCoord): s=gauss.cellSum(cellCoord); return (s/gauss.cellArea) if s>0 else float('nan') elif perArea==3: def compAvg(gauss,coord,cellCoord): s=gauss.cellSum(cellCoord); return s if s>0 else float('nan') else: raise RuntimeError('Invalid value of *perArea*, must be one of 0,1,2,3.') # for cx in range(0,div[0]): for cy in range(0,div[1]): ddd[cy,cx]=compAvg(ga,(xxx[cx],yyy[cy]),(cx,cy)) if nDim>1: ddd2[cy,cx]=compAvg(ga2,(xxx[cx],yyy[cy]),(cx,cy)) if nDim==1: return {'type':'smoothScalar','x':xxx,'y':yyy,'val':ddd,'bbox':(llo,hhi),'perArea':perArea,'grid':ga} else: return {'type':'smoothVector','x':xxx,'y':yyy,'valX':ddd,'valY':ddd2,'bbox':(llo,hhi),'grid':ga,'grid2':ga2} def plot(data,axes=None,alpha=.5,clabel=True,cbar=False,aspect='equal',**kw): """Given output from post2d.data, plot the scalar as discrete or smooth plot. For raw discrete data, plot filled circles with radii of particles, colored by the scalar value. For smooth discrete data, plot image with optional contours and contour labels. For vector data (raw or smooth), plot quiver (vector field), with arrows colored by the magnitude. :param axes: matplotlib.axes\ instance where the figure will be plotted; if None, will be created from scratch. :param data: value returned by :yref:`yade.post2d.data` :param bool clabel: show contour labels (smooth mode only), or annotate cells with numbers inside (with perArea==2) :param bool cbar: show colorbar (equivalent to calling pylab.colorbar(mappable) on the returned mappable) :return: tuple of ``(axes,mappable)``; mappable can be used in further calls to pylab.colorbar. """ import pylab,math if not axes: axes=pylab.gca() if data['type']=='rawScalar': from matplotlib.patches import Circle import matplotlib.collections,numpy patches=[] for x,y,d,r in zip(data['x'],data['y'],data['val'],data['radii']): patches.append(Circle(xy=(x,y),radius=r)) coll=matplotlib.collections.PatchCollection(patches,linewidths=0.,**kw) coll.set_array(numpy.array(data['val'])) bb=coll.get_datalim(coll.get_transform()) axes.add_collection(coll) axes.set_xlim(bb.xmin,bb.xmax); axes.set_ylim(bb.ymin,bb.ymax) if cbar: axes.get_figure().colorbar(coll) axes.grid(True); axes.set_aspect(aspect) return axes,coll elif data['type']=='smoothScalar': loHi=data['bbox'] if data['perArea'] in (0,1): img=axes.imshow(data['val'],extent=(loHi[0][0],loHi[1][0],loHi[0][1],loHi[1][1]),origin='lower',aspect=aspect,**kw) ct=axes.contour(data['x'],data['y'],data['val'],colors='k',origin='lower',extend='both') if clabel: axes.clabel(ct,inline=1,fontsize=10) else: img=axes.imshow(data['val'],extent=(loHi[0][0],loHi[1][0],loHi[0][1],loHi[1][1]),origin='lower',aspect=aspect,interpolation='nearest',**kw) xStep=(data['x'][1]-data['x'][0]) if len(data['x'])>1 else 0 for y,valLine in zip(data['y'],data['val']): for x,val in zip(data['x'],valLine): axes.text(x-.4*xStep,y,('-' if math.isnan(val) else '%5g'%val),size=4) axes.update_datalim(loHi) axes.set_xlim(loHi[0][0],loHi[1][0]); axes.set_ylim(loHi[0][1],loHi[1][1]) if cbar: axes.get_figure().colorbar(img) axes.grid(True if data['perArea'] in (0,1) else False); axes.set_aspect(aspect) return axes,img elif data['type'] in ('rawVector','smoothVector'): import numpy loHi=data['bbox'] valX,valY=numpy.array(data['valX']),numpy.array(data['valY']) # rawVector data are plain python lists scalars=numpy.sqrt(valX**2+valY**2) # numpy.sqrt computes element-wise sqrt quiv=axes.quiver(data['x'],data['y'],data['valX'],data['valY'],scalars,**kw) #axes.update_datalim(loHi) axes.set_xlim(loHi[0][0],loHi[1][0]); axes.set_ylim(loHi[0][1],loHi[1][1]) if cbar: axes.get_figure().colorbar(coll) axes.grid(True); axes.set_aspect(aspect) return axes,quiv trunk-2018.02b/py/remote.py000066400000000000000000000216721324306050200154730ustar00rootroot00000000000000# encoding: utf-8 # 2008-2009 © Václav Šmilauer """ Remote connections to yade: authenticated python command-line over telnet and anonymous socket for getting some read-only information about current simulation. These classes are used internally in gui/py/PythonUI_rc.py and are not intended for direct use. """ import SocketServer,xmlrpclib,socket import sys,time,os,math from yade import * import yade.runtime useQThread=False gui='' "Set before using any of our classes to use QThread for background execution instead of the standard thread module. Mixing the two (in case the qt4 UI is running, for instance) does not work well." plotImgFormat,plotImgMimetype='png','image/png' #plotImgFormat,plotImgMimetype='svg','image/svg+xml' # yade 16x16 favicon, encoded with base64 b64favicon='AAABAAEAEBAAAAAAAABoBQAAFgAAACgAAAAQAAAAIAAAAAEACAAAAAAAQAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAA////AAiD7wCjjY8AAGhoAAwMwwBuVO0AY1RWAJG+4QAHpKMAHiRTAD88vQDW0t0AQp7XAHBwtAAMvf8ANWx0AIKK5QABycgAqZ/AADc1iwA6Oe0AM09QABskJQA+dNoAXWt/AAhAPwAAhYMApp3nAMm+/ABYWM0Avr7IAG+v5gBdS6wAint9AAdFXwApKdoAgGvnAIGBwwCjoaMANSssAEpERQByZGYAiaTmAJiQ1AAhPDkASkjfAAa0tgAGrO4APE2OABNdXgBlZdwAuqvVAA8qPAAAt9YAcmrKAF5etwAvL8YAUUm+ADNiYQAAl5QAWlrqAMPF2wCCd60ATTzNAGdPwQATc+sABjJbABKS9QCsruEAfn7VADFAQACWleUAAnV0ADea5ACIgPIAc3PfAGZk8QAVFLgAjITZAFllxAAMjpAAkpPGAFI6uwAZQ0QAZ1fXABcuSACnotkACFRaAFNT2wAIuusACI39AFpbqwAAqqwAZmbMAFFMTAB6d8YArq3SAAsuMQAApJkA0cfeAAqxqwC6t9YAN2FwAJV6gQBdXdgAhHThAFJSxQBDNucAV1FUAEQzuQB6e+AADaGrAFpOzwBkZL0AAI6PAD8+xwBIR8IAXl7iAMa88wAMMz0AAZyeAJGO2gAFvbQAcnDDAF9ezQCenesASj/FAFFR4wAJqKoAz8/bAAJ6egAWJ0UAg36wAFdX1gC7u9AAAKKiAGdrxwCDg9UAqKjcAAanmgAdQEAAYWLYAF1SUwBPTboAzcH+AAOBgQAQde8AV1fIAGNjuAAEt7kAAHFzAAGamgAFoKMAxbv3AJCQ1wCCbuYABrG0AAJqagCnpdoA0NDZAAajpAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAATpA/IRNTQR5FoAAAAAAAADl0f25AVWpQGAoGnHcAAABrfDo3cSwMK0KEkSWaAAAABQtgZldPPkpEVjRoAAAAAHJ1fWF6bEMCkzUAAwAAAAA4JopcLksjWw94AAAAAAAAlR+CTE0cWDBaYiIAAAAAAA5SRiQVFDyMZUkoAAAAAACUXoZZSBEAewChMgcAAAAAi5+OaWQIAACSAIMWAAAAAG+AM4kNcGMtjZgvl20AAACbdh0gNl0AjyqeAAkaAAAAh4VnlhIEXycAKXOZgRcAAH4ZAIgbRwAAAABUeZ1RAAA9MRA7AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA/AAAABwAAAAcAAAAPAAAALwAAAD8AAAAfAAAAHwAAAo8AAANPAAAABwAAAicAAACDAAAjwwAAD/8AAP//AAA=' bgThreads=[] # needed to keep background threads alive class InfoProvider: def basicInfo(self): ret=dict(iter=O.iter,dt=O.dt,stopAtIter=O.stopAtIter,speed=O.speed,realtime=O.realtime,time=O.time,id=O.tags['id'] if O.tags.has_key('id') else None,threads=os.environ['OMP_NUM_THREADS'] if os.environ.has_key('OMP_NUM_THREADS') else '0',numBodies=len(O.bodies),numIntrs=len(O.interactions)) sys.stdout.flush(); sys.stderr.flush() return ret def plot(self): try: from yade import plot if len(plot.plots)==0: return None fig=plot.plot(subPlots=True,noShow=True) img=O.tmpFilename()+'.'+plotImgFormat sqrtFigs=math.sqrt(len(plot.plots)) fig.set_size_inches(5*sqrtFigs,7*sqrtFigs) fig.savefig(img) f=open(img,'rb'); data=f.read(); f.close(); os.remove(img) #print 'returning '+plotImgFormat return xmlrpclib.Binary(data) except: print 'Error updating plots:' import traceback traceback.print_exc() return None def stop(self): O.pause() return True class PythonConsoleSocketEmulator(SocketServer.BaseRequestHandler): """Class emulating python command-line over a socket connection. The connection is authenticated by requiring a cookie. Only connections from localhost (127.0.0.*) are allowed. """ def setup(self): if not self.client_address[0].startswith('127.0.0'): print "TCP Connection from non-127.0.0.* address %s rejected"%self.client_address[0] return print self.client_address, 'connected!' self.request.send('Enter auth cookie: ') def tryLogin(self): if self.request.recv(1024).rstrip()==self.server.cookie: self.server.authenticated+=[self.client_address] self.request.send("""__ __ ____ __ _____ ____ ____ \ \ / /_ _| _ \ ___ ___ / / |_ _/ ___| _ \ \ V / _` | | | |/ _ \ / _ \ / / | || | | |_) | | | (_| | |_| | __/ | (_) / / | || |___| __/ |_|\__,_|____/ \___| \___/_/ |_| \____|_| (connected from %s:%d) >>> """%(str(self.client_address[0]),self.client_address[1])) return True else: import time time.sleep(5) print "invalid cookie" return False def displayhook(self,s): import pprint self.request.send(pprint.pformat(s)) def handle(self): if self.client_address not in self.server.authenticated and not self.tryLogin(): return import code,cStringIO,traceback buf=[] while True: data = self.request.recv(1024).rstrip() if data=='\x04' or data=='exit' or data=='quit': # \x04 == ^D return buf.append(data) orig_displayhook,orig_stdout=sys.displayhook,sys.stdout sio=cStringIO.StringIO() continuation=False #print "buffer:",buf try: comp=code.compile_command('\n'.join(buf)) if comp: sys.displayhook=self.displayhook sys.stdout=sio exec comp self.request.send(sio.getvalue()) buf=[] else: self.request.send('... '); continuation=True except: self.request.send(traceback.format_exc()) buf=[] finally: sys.displayhook,sys.stdout=orig_displayhook,orig_stdout if not continuation: self.request.send('\n>>> ') def finish(self): print self.client_address, 'disconnected!' self.request.send('\nBye ' + str(self.client_address) + '\n') def _runInBackground(func): if useQThread: if (gui=='qt4'): from PyQt4.QtCore import QThread elif (gui=='qt5'): from PyQt5.QtCore import QThread class WorkerThread(QThread): def __init__(self,func_): QThread.__init__(self); self.func=func_ def run(self): self.func() wt=WorkerThread(func) wt.start() global bgThreads; bgThreads.append(wt) else: import thread; thread.start_new_thread(func,()) class GenericTCPServer: "Base class for socket server, handling port allocation, initial logging and thead backgrounding." def __init__(self,handler,title,cookie=True,minPort=9000,host='',maxPort=65536,background=True): import socket, random, sys self.port=-1 self.host=host tryPort=minPort if maxPort==None: maxPort=minPort while self.port==-1 and tryPort<=maxPort: try: self.server=SocketServer.ThreadingTCPServer((host,tryPort),handler) self.port=tryPort if cookie: self.server.cookie=''.join([i for i in random.sample('yadesucks',6)]) self.server.authenticated=[] sys.stderr.write(title+" on %s:%d, auth cookie `%s'\n"%(host if host else 'localhost',self.port,self.server.cookie)) else: sys.stderr.write(title+" on %s:%d\n"%(host if host else 'localhost',self.port)) if background: _runInBackground(self.server.serve_forever) else: self.server.serve_forever() except socket.error: tryPort+=1 if self.port==-1: raise RuntimeError("No free port to listen on in range %d-%d"%(minPort,maxPort)) def runServers(): """Run python telnet server and info socket. They will be run at localhost on ports 9000 (or higher if used) and 21000 (or higer if used) respectively. The python telnet server accepts only connection from localhost, after authentication by random cookie, which is printed on stdout at server startup. The info socket provides read-only access to several simulation parameters at runtime. Each connection receives pickled dictionary with those values. This socket is primarily used by yade-multi batch scheduler. """ srv=GenericTCPServer(handler=yade.remote.PythonConsoleSocketEmulator,title='TCP python prompt',cookie=True,minPort=9000) yade.runtime.cookie=srv.server.cookie #info=GenericTCPServer(handler=yade.remote.InfoSocketProvider,title='TCP info provider',cookie=False,minPort=21000) ## XMPRPC server for general information: from SimpleXMLRPCServer import SimpleXMLRPCServer port,maxPort=21000,65535 # minimum port number while port """ Functions for accessing yade's internals; only used internally. """ import sys from yade import wrapper from yade._customConverters import * from yade import runtime from yade import config O=wrapper.Omega() def childClasses(base,recurse=True,includeBase=False): """Enumerate classes deriving from given base (as string), recursively by default. Returns set.""" ret=set(O.childClassesNonrecursive(base)); ret2=set(); if includeBase: ret|=set([base]) if not recurse: return ret for bb in ret: ret2|=childClasses(bb) return ret | ret2 _allSerializables=childClasses('Serializable') ## set of classes for which the proxies were created _proxiedClasses=set() ## deprecated classes # if old class name is used, the new object is constructed and a warning is issued about old name being used # keep chronologically ordered, oldest first; script/rename-class.py appends at the end _deprecated={ 'GranularMat':'FrictMat', # Sun Jan 10 09:26:45 2010, vaclav@flux 'SimpleElasticRelationships':'Ip2_FrictMat_FrictMat_NormShearPhys', # Sun Jan 10 09:28:17 2010, vaclav@flux 'NormalInteraction':'NormPhys', # Sun Jan 10 09:28:56 2010, vaclav@flux 'NormalShearInteraction':'NormShearPhys', # Sun Jan 10 09:29:22 2010, vaclav@flux 'ElasticMat':'ElastMat', # Sun Jan 10 09:53:15 2010, vaclav@flux 'ElasticContactInteraction':'FrictPhys', # Sun Jan 10 09:57:59 2010, vaclav@flux 'ef2_Spheres_Elastic_ElasticLaw':'Law2_ScGeom_FrictPhys_Basic', # Sun Jan 10 09:59:42 2010, vaclav@flux 'Ip2_FrictMat_FrictMat_NormShearPhys':'Ip2_FrictMat_FrictMat_FrictPhys', # Sun Jan 10 10:07:40 2010, vaclav@flux 'GLDrawCpmPhys':'Gl1_CpmPhys', # Sat Feb 6 14:46:08 2010, vaclav@flux 'RockJointLawRelationships':'Ip2_2xCohFrictMat_NormalInelasticityPhys', # Mon Feb 8 11:17:59 2010, jduriez@c1solimara-l 'TetraBang':'TTetraGeom', # Tue Feb 9 10:21:24 2010, vaclav@flux 'TetraMold':'Tetra', # Tue Feb 9 10:22:15 2010, vaclav@flux 'TetraAABB':'Bo1_Tetra_Aabb', # Tue Feb 9 10:22:33 2010, vaclav@flux 'Tetra2TetraBang':'Ig2_Tetra_Tetra_TTetraGeom', # Tue Feb 9 10:23:19 2010, vaclav@flux 'TetraLaw':'TetraVolumetricLaw', # Tue Feb 9 10:24:10 2010, vaclav@flux 'DirecResearchEngine':'Disp2DPropLoadEngine', # Wed Mar 10 12:23:42 2010, jduriez@c1solimara-l 'Ip2_BMP_BMP_CSPhys':'Ip2_2xFrictMat_CSPhys', # Wed Mar 10 15:08:56 2010, eudoxos@frigo 'NormalInelasticityLaw':'Law2_ScGeom_NormalInelasticityPhys_NormalInelasticity', # Wed Mar 17 15:50:59 2010, jduriez@c1solimara-l 'CapillaryCohesiveLaw':'CapillaryLaw', # Tue Mar 30 14:11:36 2010, sch50p@fluent-ph 'SimpleElasticRelationshipsWater':'Ip2_Frictmat_FrictMat_CapillaryLawPhys', # Tue Mar 30 14:20:36 2010, sch50p@fluent-ph 'CapillaryLaw':'Law2_ScGeom_CapillaryPhys_Capillarity', # Wed Mar 31 09:23:36 2010, sch50p@fluent-ph 'CapillaryParameters':'CapillaryPhys', # Wed Mar 31 09:25:03 2010, sch50p@fluent-ph 'Ip2_FrictMat_FrictMat_CapillaryLawPhys':'Ip2_FrictMat_FrictMat_CapillaryPhys', # Wed Mar 31 09:26:04 2010, sch50p@fluent-ph 'SimpleViscoelasticMat':'ViscElMat', # Fri Apr 9 19:25:38 2010, vaclav@flux 'SimpleViscoelasticPhys':'ViscElPhys', # Fri Apr 9 19:26:34 2010, vaclav@flux 'Law2_Spheres_Viscoelastic_SimpleViscoelastic':'Law2_ScGeom_ViscElPhys_Basic', # Fri Apr 9 19:28:02 2010, vaclav@flux 'Ip2_SimleViscoelasticMat_SimpleViscoelasticMat_SimpleViscoelasticPhys':'Ip2_ViscElMat_ViscElMat_ViscElPhys', # Fri Apr 9 19:28:48 2010, vaclav@flux 'MomentEngine':'TorqueEngine', # Sun May 2 16:09:34 2010, vaclav@flux 'JumpChangeSe3':'StepDisplacer', # Sun May 2 16:14:21 2010, vaclav@flux 'Ip2_2xCohFrictMat_NormalInelasticityPhys':'Ip2_2xNormalInelasticMat_NormalInelasticityPhys', # Fri Jun 4 15:36:41 2010, jduriez@c1solimara-l 'Ip2_2xCohFrictMat_NormalInelasticityPhys':'Ip2_2xNormalInelasticMat_NormalInelasticityPhys', # Fri Jun 4 15:37:01 2010, jduriez@c1solimara-l 'Ip2_2xCohFrictMat_NormalInelasticityPhys':'Ip2_2xNormalInelasticMat_NormalInelasticityPhys', # Fri Jun 4 15:37:16 2010, jduriez@c1solimara-l 'OpenGLRenderingEngine':'OpenGLRenderer', # Sat Jul 24 06:04:13 2010, vaclav@flux 'PeriodicPythonRunner':'PyRunner', # Wed Sep 1 16:41:50 2010, chia@engs-018373 'InteractionDispatchers':'InteractionLoop', # Mon Sep 27 13:44:54 2010, chia@engs-018373 'InteractionGeometry':'IGeom', # Thu Sep 30 10:39:24 2010, chia@engs-018373 'InteractionPhysics':'IPhys', # Thu Sep 30 10:39:43 2010, chia@engs-018373 'InteractionGeometryFunctor':'IGeomFunctor', # Thu Sep 30 14:27:43 2010, chia@engs-018373 'InteractionPhysicsFunctor':'IPhysFunctor', # Thu Sep 30 14:27:53 2010, chia@engs-018373 'InteractionPhysicsDispatcher':'IPhysDispatcher', # Thu Sep 30 14:28:12 2010, chia@engs-018373 'InteractionGeometryDispatcher':'IGeomDispatcher', # Thu Sep 30 14:28:22 2010, chia@engs-018373 'Law2_ScGeom_FrictPhys_Basic':'Law2_ScGeom_FrictPhys_CundallStrack', # Wed Oct 13 17:40:42 2010, bchareyre@dt-rv020 'Law2_ScGeom_CohFrictPhys_ElasticPlastic':'Law2_ScGeom_CohFrictPhys_CohesionMoment', # Wed Oct 13 17:47:09 2010, bchareyre@dt-rv020 'Law2_ScGeom_NormalInelasticityPhys_NormalInelasticity':'Law2_ScGeom6D_NormalInelasticityPhys_NormalInelasticity', # Thu Oct 28 18:09:16 2010, jduriez@c1solimara-l 'SpiralEngine':'HelixEngine', # Sat Oct 30 05:52:06 2010, vaclav@flux 'InterpolatingSpiralEngine':'InterpolatingHelixEngine', # Sat Oct 30 05:52:21 2010, vaclav@flux 'SpiralInteractionLocator2d':'HelixInteractionLocator2d', # Sat Oct 30 05:53:04 2010, vaclav@flux 'Law2_ScGeom_CohFrictPhys_CohesionMoment':'Law2_ScGeom6D_CohFrictPhys_CohesionMoment', # Fri Nov 12 18:45:23 2010, bchareyre@dt-rv020 'Ig2_ChainedCylinder_ChainedCylinder_ScGeom':'Ig2_ChainedCylinder_ChainedCylinder_ScGeom6D', # Fri Nov 12 18:47:44 2010, bchareyre@dt-rv020 'Ip2_2xCohFrictMat_CohFrictPhys':'Ip2_CohFrictMat_CohFrictMat_CohFrictPhys', # Tue Dec 21 16:11:28 2010, bchareyre@dt-rv020 'Ig2_Sphere_Sphere_L3Geom_Inc':'Ig2_Sphere_Sphere_L3Geom', # Sun Dec 26 11:11:05 2010, vaclav@flux 'Ig2_Wall_Sphere_L3Geom_Inc':'Ig2_Wall_Sphere_L3Geom', # Sun Dec 26 11:11:30 2010, vaclav@flux 'Ig2_Sphere_Sphere_L6Geom_Inc':'Ig2_Sphere_Sphere_L6Geom', # Sun Dec 26 11:11:53 2010, vaclav@flux ### END_RENAMED_CLASSES_LIST ### (do not delete this line; scripts/rename-class.py uses it } def updateScripts(scripts): ## Thanks goes to http://code.activestate.com/recipes/81330-single-pass-multiple-replace/ from UserDict import UserDict import re,os class Xlator(UserDict): "An all-in-one multiple string substitution class; adapted to match only whole words" def _make_regex(self): "Build a regular expression object based on the keys of the current dictionary" return re.compile(r"(\b%s\b)" % "|".join(self.keys())) ## adapted here def __call__(self, mo): "This handler will be invoked for each regex match" # Count substitutions self.count += 1 # Look-up string return self[mo.string[mo.start():mo.end()]] def xlat(self, text): "Translate text, returns the modified text." # Reset substitution counter self.count = 0 # Process text return self._make_regex().sub(self, text) # use the _deprecated dictionary for translation, but only when matching on words boundary xlator=Xlator(_deprecated) if len(scripts)==0: print "No scripts given to --update. Nothing to do." for s in scripts: if not s.endswith('.py'): raise RuntimeError("Refusing to do --update on file '"+s+"' (not *.py)") txt=open(s).read() txt2=xlator.xlat(txt) if xlator.count==0: print "%s: already up-to-date."%s else: os.rename(s,s+'~') out=open(s,'w'); out.write(txt2); out.close() print "%s: %d subtitution%s made, backup in %s~"%(s,xlator.count,'s' if xlator.count>1 else '',s) def cxxCtorsDict(proxyNamespace=__builtins__): """Return dictionary of class constructors for yade's c++ types, which should be used to update a namespace. Root classes are those that are directly wrapped by boost::python. These are only put to the dict. Derived classes (from these root classes) are faked by creating a callable which invokes appropriate root class constructor with the derived class parameter and passes remaining arguments to it. Classes that are neither root nor derived are exposed via callable object that constructs a Serializable of given type and passes the parameters. """ proxyNamespace={} import yade.wrapper for c in _allSerializables: try: proxyNamespace[c]=yade.wrapper.__dict__[c] except KeyError: pass # not registered properly # deprecated names for oldName in _deprecated.keys(): class warnWrap: def __init__(self,_old,_new): # assert(proxyNamespace.has_key(_new)) self.old,self.new=_old,_new def __call__(self,*args,**kw): import warnings; warnings.warn("Class `%s' was renamed to (or replaced by) `%s', update your code! (you can run 'yade --update script.py' to do that automatically)"%(self.old,self.new),DeprecationWarning,stacklevel=2); return yade.wrapper.__dict__[self.new](*args,**kw) proxyNamespace[oldName]=warnWrap(oldName,_deprecated[oldName]) return proxyNamespace # consistency check # if there are no serializables, then plugins were not loaded yet, probably if(len(_allSerializables)==0): raise ImportError("No classes deriving from Serializable found; you must call yade.boot.initialize to load plugins before importing yade.system.") trunk-2018.02b/py/tests/000077500000000000000000000000001324306050200147605ustar00rootroot00000000000000trunk-2018.02b/py/tests/__init__.py000066400000000000000000000027461324306050200171020ustar00rootroot00000000000000# encoding: utf-8 # 2009 © Václav Šmilauer """All defined functionality tests for yade.""" import unittest,inspect,sys # add any new test suites to the list here, so that they are picked up by testAll allTests=['wrapper','core','pbc','clump','cohesive-chain','engines'] # all yade modules (ugly...) import yade.export,yade.linterpolation,yade.pack,yade.plot,yade.post2d,yade.timing,yade.utils,yade.ymport,yade.geom,yade.gridpfacet allModules=(yade.export,yade.linterpolation,yade.pack,yade.plot,yade.post2d,yade.timing,yade.utils,yade.ymport,yade.geom,yade.gridpfacet) try: import yade.qt allModules+=(yade.qt,) except ImportError: pass # fully qualified module names allTestsFQ=['yade.tests.'+test for test in allTests] def testModule(module): """Run all tests defined in the module specified, return TestResult object (http://docs.python.org/library/unittest.html#unittest.TextTestResult) for further processing. @param module: fully-qualified module name, e.g. yade.tests.wrapper """ suite=unittest.defaultTestLoader.loadTestsFromName(module) return unittest.TextTestRunner(stream=sys.stdout,verbosity=2).run(suite) def testAll(): """Run all tests defined in all yade.tests.* modules and return TestResult object for further examination.""" suite=unittest.defaultTestLoader.loadTestsFromNames(allTestsFQ) import doctest for mod in allModules: suite.addTest(doctest.DocTestSuite(mod)) return unittest.TextTestRunner(stream=sys.stdout,verbosity=2).run(suite) trunk-2018.02b/py/tests/clump.py000066400000000000000000000046351324306050200164620ustar00rootroot00000000000000 ''' Various computations affected by the periodic boundary conditions. ''' import unittest import random from yade.wrapper import * from yade._customConverters import * from yade import utils from yade import * from math import * from minieigen import * class TestSimpleClump(unittest.TestCase): "Test things on a simple clump composed of 2 spheres." def setUp(self): O.reset() r1,r2,p0,p1=1,.5,Vector3.Zero,Vector3(0,0,3) self.idC,(self.id1,self.id2)=O.bodies.appendClumped([ utils.sphere(p0,r1), utils.sphere(p1,r2) ]) def testConsistency(self): "Clump: ids and flags consistency" b1,b2,bC=[O.bodies[id] for id in (self.id1,self.id2,self.idC)] self.assertEqual(b1.clumpId,bC.id) self.assertEqual(b2.clumpId,bC.id) self.assertEqual(bC.clumpId,bC.id) self.assert_(bC.isClump) self.assert_(b1.isClumpMember) self.assert_(b2.isClumpMember) self.assert_(not bC.bounded) def testStaticProperties(self): "Clump: mass, centroid, intertia" b1,b2,bC=[O.bodies[id] for id in (self.id1,self.id2,self.idC)] # mass self.assertEqual(bC.state.mass,b1.state.mass+b2.state.mass) # centroid S=b1.state.mass*b1.state.pos+b2.state.mass*b2.state.pos c=S/bC.state.mass self.assertAlmostEqual(bC.state.pos[0],c[0]); self.assertAlmostEqual(bC.state.pos[1],c[1]); self.assertAlmostEqual(bC.state.pos[2],c[2]); # inertia i1,i2=(8./15)*pi*b1.material.density*b1.shape.radius**5, (8./15)*pi*b2.material.density*b2.shape.radius**5 # inertia of spheres iMax=i1+i2+b1.state.mass*(b1.state.pos-c).norm()**2+b2.state.mass*(b2.state.pos-c).norm()**2 # minimum principal inertia iMin=i1+i2 # perpendicular to the # the order of bC.state.inertia is arbitrary (though must match the orientation) iC=list(bC.state.inertia); iC.sort() self.assertAlmostEqual(iC[0],iMin) self.assertAlmostEqual(iC[1],iMax) self.assertAlmostEqual(iC[2],iMax) # check orientation...? #self.assertAlmostEqual def testVelocity(self): "Clump: velocities of member assigned by NewtonIntegrator" s1,s2,sC=[O.bodies[id].state for id in (self.id1,self.id2,self.idC)] O.dt=0 sC.vel=(1.,.2,.4) sC.angVel=(0,.4,.1) O.engines=[NewtonIntegrator()]; O.step() # update velocities # linear velocities self.assertEqual(s1.vel,sC.vel+sC.angVel.cross(s1.pos-sC.pos)) self.assertEqual(s2.vel,sC.vel+sC.angVel.cross(s2.pos-sC.pos)) # angular velocities self.assertEqual(s1.angVel,sC.angVel); self.assertEqual(s2.angVel,sC.angVel); trunk-2018.02b/py/tests/cohesive-chain.py000066400000000000000000000051461324306050200202250ustar00rootroot00000000000000# encoding: utf-8 # 2010 © Bruno Chareyre ''' Motion of a "sinusoidal" beam made of cylinders. The test checks the position and velocity of the free end of the bending beam subjected to gravitational load. It is similar to scripts/test/chained-cylinder-spring.py but with less elements. positions and velocity are compared during the transient oscillations, only positions are compared for the larger time since residual velocity is compiler-dependent (see https://lists.launchpad.net/yade-dev/msg06178.html). ''' import unittest import random from yade.wrapper import * from yade._customConverters import * from yade.gridpfacet import * from math import * from minieigen import * class TestCohesiveChain(unittest.TestCase): # prefix test names with PBC: def setUp(self): O.reset(); young=1.0e3 poisson=5 density=2.60e3 frictionAngle=radians(30) O.materials.append(CohFrictMat(young=young,poisson=poisson,density=density,frictionAngle=frictionAngle,normalCohesion=1e13,shearCohesion=1e13,momentRotationLaw=True)) O.dt=1e-3 O.engines=[ ForceResetter(), InsertionSortCollider([ Bo1_ChainedCylinder_Aabb(), Bo1_Sphere_Aabb()]), InteractionLoop( [Ig2_ChainedCylinder_ChainedCylinder_ScGeom6D(),Ig2_Sphere_ChainedCylinder_CylScGeom()], [Ip2_CohFrictMat_CohFrictMat_CohFrictPhys(setCohesionNow=True,setCohesionOnNewContacts=True)], [Law2_ScGeom6D_CohFrictPhys_CohesionMoment()]), ## Apply gravity ## Motion equation NewtonIntegrator(damping=0.15,gravity=[0,-9.81,0]) ] #Generate a spiral Ne=10 for i in range(0, Ne): omeg=95.0/float(Ne); hy=0.05; hz=0.07; px=float(i)*(omeg/60.0); py=sin(float(i)*omeg)*hy; pz=cos(float(i)*omeg)*hz; px2=float(i+1.)*(omeg/60.0); py2=sin(float(i+1.)*omeg)*hy; pz2=cos(float(i+1.)*omeg)*hz; chainedCylinder(begin=Vector3(pz,py,px), radius=0.005,end=Vector3(pz2,py2,px2),color=Vector3(0.6,0.5,0.5)) O.bodies[Ne-1].state.blockedDOFs='xyzXYZ' def testMotion(self): "CohesiveChain: velocity/positions tested in transient dynamics and equilibrium state" #target values tv1=-0.790576599652;tp1=-0.0483370740415;tv2=-0.000494271551993;tp2=-0.0611987315415; tolerance = 10e-3 O.run(100,True) v1=O.bodies[0].state.vel[1];p1=O.bodies[0].state.pos[1] #print v1,p1 O.run(10000,True) v2=O.bodies[0].state.vel[1];p2=O.bodies[0].state.pos[1] #print v2,p2 self.assertTrue(abs(tv1-v1) """ Core functionality (Scene in c++), such as accessing bodies, materials, interactions. Specific functionality tests should go to engines.py or elsewhere, not here. """ import unittest import random from yade.wrapper import * from yade._customConverters import * from yade import utils from yade import * from math import * from minieigen import * ## TODO tests class TestForce(unittest.TestCase): pass class TestTags(unittest.TestCase): pass class TestInteractions(unittest.TestCase): def setUp(self): O.reset() def testEraseBodiesInInteraction(self): O.reset() id1 = O.bodies.append(utils.sphere([0.5,0.5,0.0+0.095],.1)) id2 = O.bodies.append(utils.sphere([0.5,0.5,0.0+0.250],.1)) O.engines=[ ForceResetter(), InsertionSortCollider([Bo1_Sphere_Aabb()]), InteractionLoop( [Ig2_Sphere_Sphere_L3Geom()], [Ip2_FrictMat_FrictMat_FrictPhys()], [Law2_L3Geom_FrictPhys_ElPerfPl()] ), NewtonIntegrator(damping=0.1,gravity=(0,0,-9.81)) ] O.dt=.5e-4*utils.PWaveTimeStep() O.step() O.bodies.erase(id1) O.step() class TestLoop(unittest.TestCase): def setUp(self): O.reset() def testSubstepping(self): 'Loop: substepping' O.engines=[ForceResetter(),PyRunner(initRun=True,iterPeriod=1,command='pass')] # value outside the loop self.assert_(O.subStep==-1) # O.subStep is meaningful when substepping O.subStepping=True O.step(); self.assert_(O.subStep==0) O.step(); self.assert_(O.subStep==1) # when substepping is turned off in the middle of the loop, the next step finishes the loop O.subStepping=False O.step(); self.assert_(O.subStep==-1) # subStep==0 inside the loop without substepping O.engines=[PyRunner(initRun=True,iterPeriod=1,command='if O.subStep!=0: raise RuntimeError("O.subStep!=0 inside the loop with O.subStepping==False!")')] O.step() def testEnginesModificationInsideLoop(self): 'Loop: O.engines can be modified inside the loop transparently.' O.engines=[ PyRunner(initRun=True,iterPeriod=1,command='from yade import *; O.engines=[ForceResetter(),GravityEngine(),NewtonIntegrator()]'), # change engines here ForceResetter() # useless engine ] O.subStepping=True # run prologue and the first engine, which modifies O.engines O.step(); O.step(); self.assert_(O.subStep==1) self.assert_(len(O.engines)==3) # gives modified engine sequence transparently self.assert_(len(O._nextEngines)==3) self.assert_(len(O._currEngines)==2) O.step(); O.step(); # run the 2nd ForceResetter, and epilogue self.assert_(O.subStep==-1) # start the next step, nextEngines should replace engines automatically O.step() self.assert_(O.subStep==0) self.assert_(len(O._nextEngines)==0) self.assert_(len(O.engines)==3) self.assert_(len(O._currEngines)==3) def testDead(self): 'Loop: dead engines are not run' O.engines=[PyRunner(dead=True,initRun=True,iterPeriod=1,command='pass')] O.step(); self.assert_(O.engines[0].nDone==0) class TestIO(unittest.TestCase): def testSaveAllClasses(self): 'I/O: All classes can be saved and loaded with boost::serialization' import yade.system failed=set() for c in yade.system.childClasses('Serializable'): O.reset() try: O.miscParams=[eval(c)()] O.saveTmp(quiet=True) O.loadTmp(quiet=True) except (RuntimeError,ValueError): failed.add(c) failed=list(failed); failed.sort() self.assert_(len(failed)==0,'Failed classes were: '+' '.join(failed)) class TestMaterialStateAssociativity(unittest.TestCase): def setUp(self): O.reset() def testThrowsAtBadCombination(self): "Material+State: throws when body has material and state that don't work together." b=Body() b.mat=CpmMat() b.state=State() #should be CpmState() O.bodies.append(b) self.assertRaises(RuntimeError,lambda: O.step()) # throws runtime_error def testThrowsAtNullState(self): "Material+State: throws when body has material but NULL state." b=Body() b.mat=Material() b.state=None # → shared_ptr() by boost::python O.bodies.append(b) self.assertRaises(RuntimeError,lambda: O.step()) def testMaterialReturnsState(self): "Material+State: CpmMat returns CpmState when asked for newAssocState" self.assert_(CpmMat().newAssocState().__class__==CpmState) class TestBodies(unittest.TestCase): def setUp(self): O.reset() self.count=100 O.bodies.append([utils.sphere([random.random(),random.random(),random.random()],random.random()) for i in range(0,self.count)]) random.seed() def testIterate(self): "Bodies: Iteration" counted=0 for b in O.bodies: counted+=1 self.assert_(counted==self.count) def testLen(self): "Bodies: len(O.bodies)" self.assert_(len(O.bodies)==self.count) def testErase(self): "Bodies: erased bodies are None in python" O.bodies.erase(0) self.assert_(O.bodies[0]==None) def testNegativeIndex(self): "Bodies: Negative index counts backwards (like python sequences)." self.assert_(O.bodies[-1]==O.bodies[self.count-1]) def testErasedIterate(self): "Bodies: Iterator silently skips erased ones" removed,counted=0,0 for i in range(0,10): id=random.randint(0,self.count-1) if O.bodies[id]: O.bodies.erase(id);removed+=1 for b in O.bodies: counted+=1 self.assert_(counted==self.count-removed) def testErasedAndNewlyCreatedSphere(self): "Bodies: The bug is described in LP:1001194. If the new body was created after deletion of previous, it has no bounding box" O.reset() id1 = O.bodies.append(utils.sphere([0.0, 0.0, 0.0],0.5)) id2 = O.bodies.append(utils.sphere([0.0, 2.0, 0.0],0.5)) O.engines=[ ForceResetter(), InsertionSortCollider([Bo1_Sphere_Aabb()]), InteractionLoop( [Ig2_Sphere_Sphere_L3Geom()], [Ip2_FrictMat_FrictMat_FrictPhys()], [Law2_L3Geom_FrictPhys_ElPerfPl()] ), NewtonIntegrator(damping=0.1,gravity=(0,0,-9.81)) ] O.dt=.5e-4*utils.PWaveTimeStep() #Before first step the bodies should not have bounds self.assert_(O.bodies[id1].bound==None and O.bodies[id2].bound==None) O.run(1, True) #After first step the bodies should have bounds self.assert_(O.bodies[id1].bound!=None and O.bodies[id2].bound!=None) #Add 3rd body id3 = O.bodies.append(utils.sphere([0.0, 4.0, 0.0],0.5)) O.run(1, True) self.assert_(O.bodies[id1].bound!=None and O.bodies[id2].bound!=None and O.bodies[id3].bound!=None) #Remove 3rd body O.bodies.erase(id3) O.run(1, True) #Add 4th body id4 = O.bodies.append(utils.sphere([0.0, 6.0, 0.0],0.5)) O.run(1, True) self.assert_(O.bodies[id1].bound!=None and O.bodies[id2].bound!=None and O.bodies[id4].bound!=None) class TestMaterials(unittest.TestCase): def setUp(self): # common setup for all tests in this class O.reset() O.materials.append([ FrictMat(young=1,label='materialZero'), ElastMat(young=100,label='materialOne') ]) O.bodies.append([ utils.sphere([0,0,0],.5,material=0), utils.sphere([1,1,1],.5,material=0), utils.sphere([1,1,1],.5,material=1) ]) def testShared(self): "Material: shared_ptr's makes change in material immediate everywhere" O.bodies[0].mat.young=23423333 self.assert_(O.bodies[0].mat.young==O.bodies[1].mat.young) def testSharedAfterReload(self): "Material: shared_ptr's are preserved when saving/loading" O.saveTmp(quiet=True); O.loadTmp(quiet=True) O.bodies[0].mat.young=9087438484 self.assert_(O.bodies[0].mat.young==O.bodies[1].mat.young) def testLen(self): "Material: len(O.materials)" self.assert_(len(O.materials)==2) def testNegativeIndex(self): "Material: negative index counts backwards." self.assert_(O.materials[-1]==O.materials[1]) def testIterate(self): "Material: iteration over O.materials" counted=0 for m in O.materials: counted+=1 self.assert_(counted==len(O.materials)) def testAccess(self): "Material: find by index or label; KeyError raised for invalid label." self.assertRaises(KeyError,lambda: O.materials['nonexistent label']) self.assert_(O.materials['materialZero']==O.materials[0]) class TestMatchMaker(unittest.TestCase): def setUp(self): O.reset(); def testMatchMakerCollisions(self): fr = 0.5;rho=2000 tc = 0.001; en = 0.5; et = 0.5; mat1 = O.materials.append(ViscElMat(frictionAngle=fr,tc=tc,en=en,et=et,density=rho)) mat2 = O.materials.append(ViscElMat(frictionAngle=fr,tc=tc,en=en,et=et,density=rho)) mat3 = O.materials.append(ViscElMat(frictionAngle=fr,tc=tc,en=en,et=et,density=rho)) r1 = 0.002381 r2 = 0.002381 id11 = O.bodies.append(utils.sphere(center=[0,0,0],radius=r1,material=mat1,fixed=True,color=[0,0,1])) id12 = O.bodies.append(utils.sphere(center=[0,0,(r1+r2)],radius=r2,material=mat2,fixed=False,color=[0,0,1])) id21 = O.bodies.append(utils.sphere(center=[3*r1,0,0],radius=r1,material=mat1,fixed=True,color=[0,1,0])) id22 = O.bodies.append(utils.sphere(center=[3*r1,0,(r1+r2)],radius=r2,material=mat3,fixed=False,color=[0,1,0])) id31 = O.bodies.append(utils.sphere(center=[6*r1,0,0],radius=r1,material=mat2,fixed=True,color=[1,0,0])) id32 = O.bodies.append(utils.sphere(center=[6*r1,0,(r1+r2)],radius=r2,material=mat3,fixed=False,color=[1,0,0])) O.engines = [ ForceResetter(), InsertionSortCollider([Bo1_Sphere_Aabb()],verletDist=r1*10.0), InteractionLoop( [Ig2_Sphere_Sphere_ScGeom()], [Ip2_ViscElMat_ViscElMat_ViscElPhys( en=MatchMaker(matches=((mat1,mat2,.1),(mat1,mat3,.2),(mat2,mat3,.4))), et=MatchMaker(matches=((mat1,mat2,.7),(mat1,mat3,.8),(mat2,mat3,.9))), frictAngle=MatchMaker(matches=((mat1,mat2,.1),(mat1,mat3,.2),(mat2,mat3,.3))) )], [Law2_ScGeom_ViscElPhys_Basic()], ), NewtonIntegrator(damping=0,gravity=[0,0,-9.81]), ] O.step() self.assertTrue((atan(O.interactions[id11,id12].phys.tangensOfFrictionAngle)-0.1)==0) self.assertTrue((atan(O.interactions[id21,id22].phys.tangensOfFrictionAngle)-0.2)==0) self.assertTrue((atan(O.interactions[id31,id32].phys.tangensOfFrictionAngle)-0.3)==0) self.assertTrue(round(O.interactions[id11,id12].phys.cn, 3) - 0.26 == 0) self.assertTrue(round(O.interactions[id21,id22].phys.cn, 3) - 0.182 == 0) self.assertTrue(round(O.interactions[id31,id32].phys.cn, 3) - 0.104 == 0) self.assertTrue(round(O.interactions[id11,id12].phys.cs, 3) - 0.012== 0) self.assertTrue(round(O.interactions[id21,id22].phys.cs, 3) - 0.007 == 0) self.assertTrue(round(O.interactions[id31,id32].phys.cs, 3) - 0.003 == 0) trunk-2018.02b/py/tests/engines.py000066400000000000000000000121451324306050200167650ustar00rootroot00000000000000'test functionality of individual engines' import unittest import random from yade.wrapper import * from yade._customConverters import * from yade import utils from yade import * from math import * from minieigen import * class TestKinematicEngines(unittest.TestCase): def testKinematicEngines(self): 'Engines: kinematic engines' tolerance = 1e-5 rotIndex=1.0 angVelTemp = pi/rotIndex O.reset() id_fixed_transl = O.bodies.append(utils.sphere((0.0,0.0,0.0),1.0,fixed=True)) id_nonfixed_transl = O.bodies.append(utils.sphere((0.0,5.0,0.0),1.0,fixed=False)) id_fixed_rot = O.bodies.append(utils.sphere((0.0,10.0,10.0),1.0,fixed=True)) id_nonfixed_rot = O.bodies.append(utils.sphere((0.0,15.0,10.0),1.0,fixed=False)) id_fixed_helix = O.bodies.append(utils.sphere((0.0,20.0,10.0),1.0,fixed=True)) id_nonfixed_helix = O.bodies.append(utils.sphere((0.0,25.0,10.0),1.0,fixed=False)) O.engines=[ TranslationEngine(velocity = 1.0, translationAxis = [1.0,0,0], ids = [id_fixed_transl]), TranslationEngine(velocity = 1.0, translationAxis = [1.0,0,0], ids = [id_nonfixed_transl]), RotationEngine(angularVelocity = pi/angVelTemp, rotationAxis = [0.0,1.0,0.0], rotateAroundZero = True, zeroPoint = [0.0,0.0,0.0], ids = [id_fixed_rot]), RotationEngine(angularVelocity = pi/angVelTemp, rotationAxis = [0.0,1.0,0.0], rotateAroundZero = True, zeroPoint = [0.0,5.0,0.0], ids = [id_nonfixed_rot]), HelixEngine(angularVelocity = pi/angVelTemp, rotationAxis = [0.0,1.0,0.0], linearVelocity = 1.0, zeroPoint = [0.0,0.0,0.0], ids = [id_fixed_helix]), HelixEngine(angularVelocity = pi/angVelTemp, rotationAxis = [0.0,1.0,0.0], linearVelocity = 1.0, zeroPoint = [0.0,5.0,0.0], ids = [id_nonfixed_helix]), ForceResetter(), NewtonIntegrator() ] O.dt = 1.0 for i in range(0,2): O.step() self.assertTrue(abs(O.bodies[id_fixed_transl].state.pos[0] - O.iter) < tolerance) #Check translation of fixed bodies self.assertTrue(abs(O.bodies[id_nonfixed_transl].state.pos[0] - O.iter) < tolerance) #Check translation of nonfixed bodies self.assertTrue(abs(O.bodies[id_fixed_rot].state.pos[0]-10.0*sin(pi/angVelTemp*O.iter)) ''' Various computations affected by the periodic boundary conditions. ''' import unittest import random,math from yade.wrapper import * from yade._customConverters import * from yade import utils from yade import * from minieigen import * class TestPBC(unittest.TestCase): # prefix test names with PBC: def setUp(self): O.reset(); O.periodic=True; O.cell.setBox(2.5,2.5,3) self.cellDist=Vector3i(0,0,10) # how many cells away we go self.relDist=Vector3(0,.999999999999999999,0) # rel position of the 2nd ball within the cell self.initVel=Vector3(0,0,5) O.bodies.append(utils.sphere((1,1,1),.5)) self.initPos=Vector3([O.bodies[0].state.pos[i]+self.relDist[i]+self.cellDist[i]*O.cell.refSize[i] for i in (0,1,2)]) O.bodies.append(utils.sphere(self.initPos,.5)) #print O.bodies[1].state.pos O.bodies[1].state.vel=self.initVel O.engines=[NewtonIntegrator(warnNoForceReset=False)] O.cell.velGrad=Matrix3(0,0,0, 0,0,0, 0,0,-1) O.dt=0 # do not change positions with dt=0 in NewtonIntegrator, but still update velocities from velGrad def testVelGrad(self): 'PBC: velGrad changes hSize, accumulates in trsf' O.dt=1e-3 hSize,trsf=O.cell.hSize,Matrix3.Identity hSize0=hSize O.step() for i in range(0,10): O.step(); hSize+=O.dt*O.cell.velGrad*hSize; trsf+=O.dt*O.cell.velGrad*trsf for i in range(0,len(O.cell.hSize)): self.assertAlmostEqual(hSize[i],O.cell.hSize[i]) self.assertAlmostEqual(trsf[i],O.cell.trsf[i]) def testTrsfChange(self): 'PBC: chaing trsf changes hSize0, but does not modify hSize' O.dt=1e-2 O.step() O.cell.trsf=Matrix3.Identity for i in range(0,len(O.cell.hSize)): self.assertAlmostEqual(O.cell.hSize0[i],O.cell.hSize[i]) def testDegenerate(self): "PBC: degenerate cell raises exception" self.assertRaises(RuntimeError,lambda: setattr(O.cell,'hSize',Matrix3(1,0,0, 0,0,0, 0,0,1))) def testSetBox(self): "PBC: setBox modifies hSize correctly" O.cell.setBox(2.55,11,45) self.assert_(O.cell.hSize==Matrix3(2.55,0,0, 0,11,0, 0,0,45)); def testHomotheticResizeVel(self): "PBC: homothetic cell deformation adjusts particle velocity " O.dt=1e-5 O.step() s1=O.bodies[1].state self.assertAlmostEqual(s1.vel[2],self.initVel[2]+self.initPos[2]*O.cell.velGrad[2,2]) def testScGeomIncidentVelocity(self): "PBC: ScGeom computes incident velocity correctly" O.run(2,1) O.engines=[InteractionLoop([Ig2_Sphere_Sphere_ScGeom()],[Ip2_FrictMat_FrictMat_FrictPhys()],[])] i=utils.createInteraction(0,1) self.assertEqual(self.initVel,i.geom.incidentVel(i,avoidGranularRatcheting=True)) self.assertEqual(self.initVel,i.geom.incidentVel(i,avoidGranularRatcheting=False)) self.assertAlmostEqual(self.relDist[1],1-i.geom.penetrationDepth) def testL3GeomIncidentVelocity(self): "PBC: L3Geom computes incident velocity correctly" O.step() O.engines=[ForceResetter(),InteractionLoop([Ig2_Sphere_Sphere_L3Geom()],[Ip2_FrictMat_FrictMat_FrictPhys()],[Law2_L3Geom_FrictPhys_ElPerfPl(noBreak=True)]),NewtonIntegrator()] i=utils.createInteraction(0,1) O.dt=1e-10; O.step() # tiny timestep, to not move the normal too much self.assertAlmostEqual(self.initVel.norm(),(i.geom.u/O.dt).norm()) def testKineticEnergy(self): "PBC: utils.kineticEnergy considers only fluctuation velocity, not the velocity gradient" O.step() # updates velocity with homotheticCellResize # ½(mv²+ωIω) # #0 is still, no need to add it; #1 has zero angular velocity # we must take self.initVel since O.bodies[1].state.vel now contains the homothetic resize which utils.kineticEnergy is supposed to compensate back Ek=.5*O.bodies[1].state.mass*self.initVel.squaredNorm() self.assertAlmostEqual(Ek,utils.kineticEnergy()) trunk-2018.02b/py/tests/wrapper.py000066400000000000000000000152761324306050200170250ustar00rootroot00000000000000# encoding: utf-8 # 2009 © Václav Šmilauer """ This test module covers python/c++ transitions, for both classes deriving from Serializable, but also for other classes that we wrap (like miniEigen). """ import unittest from yade.wrapper import * from yade._customConverters import * from math import * from yade import system from yade import * from minieigen import * allClasses=system.childClasses('Serializable') class TestObjectInstantiation(unittest.TestCase): def setUp(self): pass # no setup needed for tests here def testClassCtors(self): "Core: correct types are instantiated" # correct instances created with Foo() syntax for r in allClasses: obj=eval(r)(); self.assert_(obj.__class__.__name__==r,'Failed for '+r) def testRootDerivedCtors_attrs_few(self): "Core: class ctor's attributes" # attributes passed when using the Foo(attr1=value1,attr2=value2) syntax gm=Shape(wire=True); self.assert_(gm.wire==True) def testDispatcherCtor(self): "Core: dispatcher ctors with functors" # dispatchers take list of their functors in the ctor # same functors are collapsed in one cld1=LawDispatcher([Law2_ScGeom_FrictPhys_CundallStrack(),Law2_ScGeom_FrictPhys_CundallStrack()]); self.assert_(len(cld1.functors)==1) # two different make two different, right? cld2=LawDispatcher([Law2_ScGeom_FrictPhys_CundallStrack(),Law2_ScGeom6D_CohFrictPhys_CohesionMoment()]); self.assert_(len(cld2.functors)==2) def testInteractionLoopCtor(self): "Core: InteractionLoop special ctor" # InteractionLoop takes 3 lists id=InteractionLoop([Ig2_Facet_Sphere_ScGeom(),Ig2_Sphere_Sphere_ScGeom()],[Ip2_FrictMat_FrictMat_FrictPhys()],[Law2_ScGeom_FrictPhys_CundallStrack()],) self.assert_(len(id.geomDispatcher.functors)==2) self.assert_(id.geomDispatcher.__class__==IGeomDispatcher().__class__) self.assert_(id.physDispatcher.functors[0].__class__==Ip2_FrictMat_FrictMat_FrictPhys().__class__) self.assert_(id.lawDispatcher.functors[0].__class__==Law2_ScGeom_FrictPhys_CundallStrack().__class__) def testParallelEngineCtor(self): "Core: ParallelEngine special ctor" pe=ParallelEngine([InsertionSortCollider(),[BoundDispatcher(),ForceResetter()]]) self.assert_(pe.slaves[0].__class__==InsertionSortCollider().__class__) self.assert_(len(pe.slaves[1])==2) pe.slaves=[] self.assert_(len(pe.slaves)==0) ## ## testing incorrect operations that should raise exceptions ## def testWrongFunctorType(self): "Core: dispatcher and functor type mismatch is detected" # dispatchers accept only correct functors self.assertRaises(TypeError,lambda: LawDispatcher([Bo1_Sphere_Aabb()])) def testInvalidAttr(self): 'Core: invalid attribute access raises AttributeError' # accessing invalid attributes raises AttributeError self.assertRaises(AttributeError,lambda: Sphere(attributeThatDoesntExist=42)) self.assertRaises(AttributeError,lambda: Sphere().attributeThatDoesntExist) ## ## attribute flags ## def testTriggerPostLoad(self): 'Core: Attr::triggerPostLoad' # TranslationEngine normalizes translationAxis automatically # anything else could be tested te=TranslationEngine(); te.translationAxis=(0,2,0) self.assert_(te.translationAxis==(0,1,0)) def testHidden(self): 'Core: Attr::hidden' # hidden attributes are not wrapped in python at all self.assert_(not hasattr(Interaction(),'iterLastSeen')) def testNoSave(self): 'Core: Attr::noSave' # update bound of the particle O.bodies.append(utils.sphere((0,0,0),1)) O.engines=[InsertionSortCollider([Bo1_Sphere_Aabb()]),NewtonIntegrator()] O.step() O.saveTmp(quiet=True) mn0=Vector3(O.bodies[0].bound.min) O.reload(quiet=True) mn1=Vector3(O.bodies[0].bound.min) # check that the minimum is not saved self.assert_(not isnan(mn0[0])) self.assert_(isnan(mn1[0])) def _testReadonly(self): 'Core: Attr::readonly' self.assertRaises(AttributeError,lambda: setattr(Body(),'id',3)) class TestEigenWrapper(unittest.TestCase): def assertSeqAlmostEqual(self,v1,v2): "floating-point comparison of vectors/quaterions" self.assertEqual(len(v1),len(v2)); for i in range(len(v1)): self.assertAlmostEqual(v1[i],v2[i],msg='Component '+str(i)+' of '+str(v1)+' and '+str(v2)) def testVector2(self): "Math: Vector2 operations" v=Vector2(1,2); v2=Vector2(3,4) self.assert_(v+v2==Vector2(4,6)) self.assert_(Vector2().UnitX.dot(Vector2().UnitY)==0) self.assert_(Vector2().Zero.norm()==0) def testVector3(self): "Math: Vector3 operations" v=Vector3(3,4,5); v2=Vector3(3,4,5) self.assert_(v[0]==3 and v[1]==4 and v[2]==5) self.assert_(v.squaredNorm()==50) self.assert_(v==(3,4,5)) # comparison with list/tuple self.assert_(v==[3,4,5]) self.assert_(v==v2) x,y,z,one=Vector3().UnitX,Vector3().UnitY,Vector3().UnitZ,Vector3().Ones self.assert_(x+y+z==one) self.assert_(x.dot(y)==0) self.assert_(x.cross(y)==z) def testQuaternion(self): "Math: Quaternion operations" # construction q1=Quaternion((0,0,1),pi/2) q2=Quaternion(Vector3(0,0,1),pi/2) q1==q2 x,y,z,one=Vector3().UnitX,Vector3().UnitY,Vector3().UnitZ,Vector3().Ones self.assertSeqAlmostEqual(q1*x,y) self.assertSeqAlmostEqual(q1*q1*x,-x) self.assertSeqAlmostEqual(q1*q1.conjugate(),Quaternion().Identity) self.assertSeqAlmostEqual(q1.toAxisAngle()[0],(0,0,1)) self.assertAlmostEqual(q1.toAxisAngle()[1],pi/2) def testMatrix3(self): "Math: Matrix3 operations" #construction m1=Matrix3(1,0,0,0,1,0,0,0,1) # comparison self.assert_(m1==Matrix3().Identity) # rotation matrix from quaternion m1=Quaternion(Vector3(0,0,1),pi/2).toRotationMatrix() # multiplication with vectors self.assertSeqAlmostEqual(m1*Vector3().UnitX,Vector3().UnitY) # determinant m2=Matrix3(-2,2,-3,-1,1,3,2,0,-1) self.assertEqual(m2.determinant(),18) # inverse inv=Matrix3(-0.055555555555556,0.111111111111111,0.5,0.277777777777778,0.444444444444444,0.5,-0.111111111111111,0.222222222222222,0.0) m2inv=m2.inverse() self.assertSeqAlmostEqual(m2inv,inv) # matrix-matrix multiplication self.assertSeqAlmostEqual(Matrix3().Identity*Matrix3().Identity,Matrix3().Identity) m3=Matrix3(1,2,3,4,5,6,-1,0,3) m33=m3*m3 self.assertSeqAlmostEqual(m33,Matrix3(6,12,24,18,33,60,-4,-2,6)) # not really wm3 thing, but closely related # no way to test this currently, as State::se3 is not serialized (State::pos and State::ori are serialized instead...) #def testSe3Conversion(self): # return # pp=State() # pp.se3=(Vector3().Zero,Quaternion().Identity) # self.assert_(pp['se3'][0]==Vector3().Zero) # self.assert_(pp['se3'][1]==Quaternion().Identity) # pp.se3=((1,2,3),Quaternion((1,1,1),pi/4)) # self.assert_(pp['se3'][0]==(1,2,3)) # self.assert_(pp['se3'][0]==pp.pos) # self.assert_(pp['se3'][1]==Quaternion((1,1,1),pi/4)) # self.assert_(pp['se3'][1]==pp.ori) trunk-2018.02b/py/timing.py000066400000000000000000000155111324306050200154620ustar00rootroot00000000000000# encoding: utf-8 # 2008 © Václav Šmilauer """Functions for accessing timing information stored in engines and functors. See :ref:`timing` section of the programmer's manual, `wiki page `_ for some examples. """ from yade.wrapper import * def _resetEngine(e): if e.timingDeltas: e.timingDeltas.reset() if isinstance(e,Functor): return if isinstance(e,Dispatcher): for f in e.functors: _resetEngine(f) elif isinstance(e,ParallelEngine): for s in e.slaves: _resetEngine(s) e.execTime,e.execCount=0,0 def reset(): "Zero all timing data." for e in O.engines: _resetEngine(e) _statCols={'label':40,'count':20,'time':20,'relTime':20} _maxLev=3 def _formatLine(label,time,count,totalTime,level): sp,negSp=' '*level*2,' '*(_maxLev-level)*2 raw=[] raw.append(label) raw.append(str(count) if count>=0 else '') raw.append((str(time/1000)+u'us') if time>=0 else '') raw.append(('%6.2f%%'%(time*100./totalTime)) if totalTime>0 else '') return u' '.join([ (sp+raw[0]).ljust(_statCols['label']), (raw[1]+negSp).rjust(_statCols['count']), (raw[2]+negSp).rjust(_statCols['time']), (raw[3]+negSp).rjust(_statCols['relTime']), ]) def _delta_stats(deltas,totalTime,level): ret=0 deltaTime=sum([d[1] for d in deltas.data]) for d in deltas.data: print _formatLine(d[0],d[1],d[2],totalTime,level); ret+=1 if len(deltas.data)>1: print _formatLine('TOTAL',deltaTime,sum(d[2] for d in deltas.data),totalTime,level); ret+=1 return ret def _engines_stats(engines,totalTime,level): lines=0; hereLines=0 for e in engines: if not isinstance(e,Functor): print _formatLine(u'"'+e.label+'"' if e.label else e.__class__.__name__,e.execTime,e.execCount,totalTime,level); lines+=1; hereLines+=1 if e.timingDeltas: if isinstance(e,Functor): print _formatLine(e.__class__.__name__,sum(d[1] for d in e.timingDeltas.data),sum(d[2] for d in e.timingDeltas.data),totalTime,level); lines+=1; hereLines+=1 execTime=sum([d[1] for d in e.timingDeltas.data]) else: execTime=e.execTime lines+=_delta_stats(e.timingDeltas,execTime,level+1) if isinstance(e,Dispatcher): lines+=_engines_stats(e.functors,e.execTime,level+1) if isinstance(e,InteractionLoop): lines+=_engines_stats(e.geomDispatcher.functors,e.execTime,level+1) lines+=_engines_stats(e.physDispatcher.functors,e.execTime,level+1) lines+=_engines_stats(e.lawDispatcher.functors,e.execTime,level+1) elif isinstance(e,ParallelEngine): lines+=_engines_stats(e.slave,e.execTime,level+1) if hereLines>1 and not isinstance(e,Functor): print _formatLine('TOTAL',totalTime,-1,totalTime,level); lines+=1 return lines def stats(): """Print summary table of timing information from engines and functors. Absolute times as well as percentages are given. Sample output: .. code-block:: none Name Count Time Rel. time ------------------------------------------------------------------------------------------------------- ForceResetter 102 2150us 0.02% "collider" 5 64200us 0.60% InteractionLoop 102 10571887us 98.49% "combEngine" 102 8362us 0.08% "newton" 102 73166us 0.68% "cpmStateUpdater" 1 9605us 0.09% PyRunner 1 136us 0.00% "plotDataCollector" 1 291us 0.00% TOTAL 10733564us 100.00% sample output (compiled with -DENABLE_PROFILING=1 option): .. code-block:: none Name Count Time Rel. time ------------------------------------------------------------------------------------------------------- ForceResetter 102 2150us 0.02% "collider" 5 64200us 0.60% InteractionLoop 102 10571887us 98.49% Ig2_Sphere_Sphere_ScGeom 1222186 1723168us 16.30% Ig2_Sphere_Sphere_ScGeom 1222186 1723168us 100.00% Ig2_Facet_Sphere_ScGeom 753 1157us 0.01% Ig2_Facet_Sphere_ScGeom 753 1157us 100.00% Ip2_CpmMat_CpmMat_CpmPhys 11712 26015us 0.25% end of Ip2_CpmPhys 11712 26015us 100.00% Ip2_FrictMat_CpmMat_FrictPhys 0 0us 0.00% Law2_ScGeom_CpmPhys_Cpm 3583872 4819289us 45.59% GO A 1194624 1423738us 29.54% GO B 1194624 1801250us 37.38% rest 1194624 1594300us 33.08% TOTAL 3583872 4819289us 100.00% Law2_ScGeom_FrictPhys_CundallStrack 0 0us 0.00% "combEngine" 102 8362us 0.08% "newton" 102 73166us 0.68% "cpmStateUpdater" 1 9605us 0.09% PyRunner 1 136us 0.00% "plotDataCollector" 1 291us 0.00% TOTAL 10733564us 100.00% """ print 'Name'.ljust(_statCols['label'])+' '+'Count'.rjust(_statCols['count'])+' '+'Time'.rjust(_statCols['time'])+' '+'Rel. time'.rjust(_statCols['relTime']) print '-'*(sum([_statCols[k] for k in _statCols])+len(_statCols)-1) _engines_stats(O.engines,sum([e.execTime for e in O.engines]),0) print trunk-2018.02b/py/utils.py000066400000000000000000001275241324306050200153430ustar00rootroot00000000000000# encoding: utf-8 # # utility functions for yade # # 2008-2009 © Václav Šmilauer """Heap of functions that don't (yet) fit anywhere else. Devs: please DO NOT ADD more functions here, it is getting too crowded! """ import math,random,doctest,geom,numpy from yade import * from yade.wrapper import * try: # use psyco if available import psyco psyco.full() except ImportError: pass from minieigen import * # c++ implementations for performance reasons from yade._utils import * def saveVars(mark='',loadNow=True,**kw): """Save passed variables into the simulation so that it can be recovered when the simulation is loaded again. For example, variables *a*, *b* and *c* are defined. To save them, use:: >>> saveVars('something',a=1,b=2,c=3) >>> from yade.params.something import * >>> a,b,c (1, 2, 3) those variables will be save in the .xml file, when the simulation itself is saved. To recover those variables once the .xml is loaded again, use ``loadVars('something')`` and they will be defined in the yade.params.\ *mark* module. The *loadNow* parameter calls :yref:`yade.utils.loadVars` after saving automatically. If 'something' already exists, given variables will be inserted. """ import cPickle try: d=cPickle.loads(Omega().tags['pickledPythonVariablesDictionary'+mark]) #load dictionary d for key in kw.keys(): d[key]=kw[key] #insert new variables into d except KeyError: d = kw Omega().tags['pickledPythonVariablesDictionary'+mark]=cPickle.dumps(d) if loadNow: loadVars(mark) def loadVars(mark=None): """Load variables from :yref:`yade.utils.saveVars`, which are saved inside the simulation. If ``mark==None``, all save variables are loaded. Otherwise only those with the mark passed.""" import cPickle, types, sys, warnings def loadOne(d,mark=None): """Load given dictionary into a synthesized module yade.params.name (or yade.params if *name* is not given). Update yade.params.__all__ as well.""" import yade.params if mark: if mark in yade.params.__dict__: warnings.warn('Overwriting yade.params.%s which already exists.'%mark) modName='yade.params.'+mark mod=types.ModuleType(modName) mod.__dict__.update(d) mod.__all__=list(d.keys()) # otherwise params starting with underscore would not be imported sys.modules[modName]=mod yade.params.__all__.append(mark) yade.params.__dict__[mark]=mod else: yade.params.__all__+=list(d.keys()) yade.params.__dict__.update(d) if mark!=None: d=cPickle.loads(Omega().tags['pickledPythonVariablesDictionary'+mark]) loadOne(d,mark) else: # load everything one by one for m in Omega().tags.keys(): if m.startswith('pickledPythonVariablesDictionary'): loadVars(m[len('pickledPythonVariableDictionary')+1:]) def SpherePWaveTimeStep(radius,density,young): r"""Compute P-wave critical timestep for a single (presumably representative) sphere, using formula for P-Wave propagation speed $\Delta t_{c}=\frac{r}{\sqrt{E/\rho}}$. If you want to compute minimum critical timestep for all spheres in the simulation, use :yref:`yade.utils.PWaveTimeStep` instead. >>> SpherePWaveTimeStep(1e-3,2400,30e9) 2.8284271247461903e-07 """ from math import sqrt return radius/sqrt(young/density) def randomColor(): """Return random Vector3 with each component in interval 0…1 (uniform distribution)""" return Vector3(random.random(),random.random(),random.random()) def typedEngine(name): """Return first engine from current O.engines, identified by its type (as string). For example: >>> from yade import utils >>> O.engines=[InsertionSortCollider(),NewtonIntegrator(),GravityEngine()] >>> utils.typedEngine("NewtonIntegrator") == O.engines[1] True """ return [e for e in Omega().engines if e.__class__.__name__==name][0] def defaultMaterial(): """Return default material, when creating bodies with :yref:`yade.utils.sphere` and friends, material is unspecified and there is no shared material defined yet. By default, this function returns:: .. code-block:: python FrictMat(density=1e3,young=1e7,poisson=.3,frictionAngle=.5,label='defaultMat') """ return FrictMat(density=1e3,young=1e7,poisson=.3,frictionAngle=.5,label='defaultMat') def _commonBodySetup(b,volume,geomInertia,material,pos,noBound=False,resetState=True,dynamic=None,fixed=False,blockedDOFs='xyzXYZ'): """Assign common body parameters.""" if isinstance(material,int): if material<0 and len(O.materials)==0: O.materials.append(defaultMaterial()); b.mat=O.materials[material] elif isinstance(material,str): b.mat=O.materials[material] elif isinstance(material,Material): b.mat=material elif callable(material): b.mat=material() else: raise TypeError("The 'material' argument must be None (for defaultMaterial), string (for shared material label), int (for shared material id) or Material instance."); ## resets state (!!) if resetState: b.state=b.mat.newAssocState() mass=volume*b.mat.density b.state.mass,b.state.inertia=mass,geomInertia*b.mat.density b.state.pos=b.state.refPos=pos b.bounded=(not noBound) if dynamic!=None: import warnings warnings.warn('dynamic=%s is deprecated, use fixed=%s instead'%(str(dynamic),str(not dynamic)),category=DeprecationWarning,stacklevel=2) fixed=not dynamic b.state.blockedDOFs=(blockedDOFs if fixed else '') def sphere(center,radius,dynamic=None,fixed=False,wire=False,color=None,highlight=False,material=-1,mask=1): """Create sphere with given parameters; mass and inertia computed automatically. Last assigned material is used by default (*material* = -1), and utils.defaultMaterial() will be used if no material is defined at all. :param Vector3 center: center :param float radius: radius :param float dynamic: deprecated, see "fixed" :param float fixed: generate the body with all DOFs blocked? :param material: specify :yref:`Body.material`; different types are accepted: * int: O.materials[material] will be used; as a special case, if material==-1 and there is no shared materials defined, utils.defaultMaterial() will be assigned to O.materials[0] * string: label of an existing material that will be used * :yref:`Material` instance: this instance will be used * callable: will be called without arguments; returned Material value will be used (Material factory object, if you like) :param int mask: :yref:`Body.mask` for the body :param wire: display as wire sphere? :param highlight: highlight this body in the viewer? :param Vector3-or-None: body's color, as normalized RGB; random color will be assigned if ``None``. :return: A Body instance with desired characteristics. Creating default shared material if none exists neither is given:: >>> O.reset() >>> from yade import utils >>> len(O.materials) 0 >>> s0=utils.sphere([2,0,0],1) >>> len(O.materials) 1 Instance of material can be given:: >>> s1=utils.sphere([0,0,0],1,wire=False,color=(0,1,0),material=ElastMat(young=30e9,density=2e3)) >>> s1.shape.wire False >>> s1.shape.color Vector3(0,1,0) >>> s1.mat.density 2000.0 Material can be given by label:: >>> O.materials.append(FrictMat(young=10e9,poisson=.11,label='myMaterial')) 1 >>> s2=utils.sphere([0,0,2],1,material='myMaterial') >>> s2.mat.label 'myMaterial' >>> s2.mat.poisson 0.11 Finally, material can be a callable object (taking no arguments), which returns a Material instance. Use this if you don't call this function directly (for instance, through yade.pack.randomDensePack), passing only 1 *material* parameter, but you don't want material to be shared. For instance, randomized material properties can be created like this: >>> import random >>> def matFactory(): return ElastMat(young=1e10*random.random(),density=1e3+1e3*random.random()) ... >>> s3=utils.sphere([0,2,0],1,material=matFactory) >>> s4=utils.sphere([1,2,0],1,material=matFactory) """ b=Body() b.shape=Sphere(radius=radius,color=color if color else randomColor(),wire=wire,highlight=highlight) V=(4./3)*math.pi*radius**3 geomInert=(2./5.)*V*radius**2 _commonBodySetup(b,V,Vector3(geomInert,geomInert,geomInert),material,pos=center,dynamic=dynamic,fixed=fixed) b.aspherical=False b.mask=mask return b def box(center,extents,orientation=Quaternion(1,0,0,0),dynamic=None,fixed=False,wire=False,color=None,highlight=False,material=-1,mask=1): """Create box (cuboid) with given parameters. :param Vector3 extents: half-sizes along x,y,z axes See :yref:`yade.utils.sphere`'s documentation for meaning of other parameters.""" b=Body() b.shape=Box(extents=extents,color=color if color else randomColor(),wire=wire,highlight=highlight) V=8*extents[0]*extents[1]*extents[2] # I = m*dim**2/12. = (m=V) = V*(2*extent)**2/12. = V*extent**2*4/12. = V*extent**2/3. geomInert=(V/3.)*Vector3(extents[1]**2+extents[2]**2,extents[0]**2+extents[2]**2,extents[0]**2+extents[1]**2) _commonBodySetup(b,V,geomInert,material,pos=center,dynamic=dynamic,fixed=fixed) b.state.ori=orientation b.mask=mask b.aspherical=True return b def wall(position,axis,sense=0,color=None,material=-1,mask=1): """Return ready-made wall body. :param float-or-Vector3 position: center of the wall. If float, it is the position along given axis, the other 2 components being zero :param ∈{0,1,2} axis: orientation of the wall normal (0,1,2) for x,y,z (sc. planes yz, xz, xy) :param ∈{-1,0,1} sense: sense in which to interact (0: both, -1: negative, +1: positive; see :yref:`Wall`) See :yref:`yade.utils.sphere`'s documentation for meaning of other parameters.""" b=Body() b.shape=Wall(sense=sense,axis=axis,color=color if color else randomColor()) if isinstance(position,(int,long,float)): pos2=Vector3(0,0,0); pos2[axis]=position else: pos2=position _commonBodySetup(b,0,Vector3(0,0,0),material,pos=pos2,fixed=True) b.aspherical=False # wall never moves dynamically b.mask=mask return b def facet(vertices,dynamic=None,fixed=True,wire=True,color=None,highlight=False,noBound=False,material=-1,mask=1,chain=-1): """Create facet with given parameters. :param [Vector3,Vector3,Vector3] vertices: coordinates of vertices in the global coordinate system. :param bool wire: if ``True``, facets are shown as skeleton; otherwise facets are filled :param bool noBound: set :yref:`Body.bounded` :param Vector3-or-None color: color of the facet; random color will be assigned if ``None``. See :yref:`yade.utils.sphere`'s documentation for meaning of other parameters.""" b=Body() center=inscribedCircleCenter(vertices[0],vertices[1],vertices[2]) vertices=Vector3(vertices[0])-center,Vector3(vertices[1])-center,Vector3(vertices[2])-center b.shape=Facet(color=color if color else randomColor(),wire=wire,highlight=highlight,vertices=vertices) _commonBodySetup(b,0,Vector3(0,0,0),material,noBound=noBound,pos=center,fixed=fixed) b.aspherical=False # mass and inertia are 0 anyway; fell free to change to ``True`` if needed b.mask=mask b.chain=chain return b def tetraPoly(vertices,dynamic=True,fixed=False,wire=True,color=None,highlight=False,noBound=False,material=-1,mask=1,chain=-1): """Create tetrahedron (actually simple Polyhedra) with given parameters. :param [Vector3,Vector3,Vector3,Vector3] vertices: coordinates of vertices in the global coordinate system. See :yref:`yade.utils.sphere`'s documentation for meaning of other parameters.""" b=Body() b.shape = Polyhedra(v=vertices,color=color if color else randomColor(),wire=wire,highlight=highlight) volume = b.shape.GetVolume() inertia = b.shape.GetInertia() center = b.shape.GetCentroid() _commonBodySetup(b,volume,inertia,material,noBound=noBound,pos=center,fixed=fixed) b.aspherical=True b.state.ori = b.shape.GetOri() b.mask=mask b.chain=chain return b def tetra(vertices,strictCheck=True,dynamic=True,fixed=False,wire=True,color=None,highlight=False,noBound=False,material=-1,mask=1,chain=-1): """Create tetrahedron with given parameters. :param [Vector3,Vector3,Vector3,Vector3] vertices: coordinates of vertices in the global coordinate system. :param bool strictCheck: checks vertices order, raise RuntimeError for negative volume See :yref:`yade.utils.sphere`'s documentation for meaning of other parameters.""" b=Body() center = .25*sum(vertices,Vector3.Zero) volume = TetrahedronSignedVolume(vertices) if volume < 0: if strictCheck: raise RuntimeError, "tetra: wrong order of vertices" temp = vertices[3] vertices[3] = vertices[2] vertices[2] = temp volume = TetrahedronSignedVolume(vertices) assert(volume>0) b.shape = Tetra(v=vertices,color=color if color else randomColor(),wire=wire,highlight=highlight) # modifies pos, ori and inertia ori = TetrahedronWithLocalAxesPrincipal(b) _commonBodySetup(b,volume,b.state.inertia,material,noBound=noBound,pos=center,fixed=fixed) b.state.ori = b.state.refOri = ori b.aspherical = True b.mask = mask b.chain = chain return b def polyhedron(vertices,dynamic=True,fixed=False,wire=True,color=None,highlight=False,noBound=False,material=-1,mask=1,chain=-1): """Create polyhedron with given parameters. :param [[Vector3]] vertices: coordinates of vertices in the global coordinate system. See :yref:`yade.utils.sphere`'s documentation for meaning of other parameters.""" b=Body() b.shape = Polyhedra(v=vertices,color=color if color else randomColor(),wire=wire,highlight=highlight) volume = b.shape.GetVolume() inertia = b.shape.GetInertia() center = b.shape.GetCentroid() _commonBodySetup(b,volume,inertia,material,noBound=noBound,pos=center,fixed=fixed) b.aspherical=True b.state.ori = b.shape.GetOri() b.mask=mask b.chain=chain return b #def setNewVerticesOfFacet(b,vertices): # center = inscribedCircleCenter(vertices[0],vertices[1],vertices[2]) # vertices = Vector3(vertices[0])-center,Vector3(vertices[1])-center,Vector3(vertices[2])-center # b.shape.vertices = vertices # b.state.pos = center def facetBox(*args,**kw): "|ydeprecated|" _deprecatedUtilsFunction('facetBox','geom.facetBox') return geom.facetBox(*args,**kw) def facetCylinder(*args,**kw): "|ydeprecated|" _deprecatedUtilsFunction('facetCylinder','geom.facetCylinder') return geom.facetCylinder(*args,**kw) def aabbWalls(extrema=None,thickness=0,oversizeFactor=1.5,**kw): """Return 6 boxes that will wrap existing packing as walls from all sides; extrema are extremal points of the Aabb of the packing (will be calculated if not specified) thickness is wall thickness (will be 1/10 of the X-dimension if not specified) Walls will be enlarged in their plane by oversizeFactor. returns list of 6 wall Bodies enclosing the packing, in the order minX,maxX,minY,maxY,minZ,maxZ. """ walls=[] if not extrema: extrema=aabbExtrema() #if not thickness: thickness=(extrema[1][0]-extrema[0][0])/10. for axis in [0,1,2]: mi,ma=extrema center=[(mi[i]+ma[i])/2. for i in range(3)] extents=[.5*oversizeFactor*(ma[i]-mi[i]) for i in range(3)] extents[axis]=thickness/2. for j in [0,1]: center[axis]=extrema[j][axis]+(j-.5)*thickness walls.append(box(center=center,extents=extents,fixed=True,**kw)) walls[-1].shape.wire=True return walls def aabbDim(cutoff=0.,centers=False): """Return dimensions of the axis-aligned bounding box, optionally with relative part *cutoff* cut away.""" a=aabbExtrema(cutoff,centers) return (a[1][0]-a[0][0],a[1][1]-a[0][1],a[1][2]-a[0][2]) def aabbExtrema2d(pts): """Return 2d bounding box for a sequence of 2-tuples.""" inf=float('inf') min,max=[inf,inf],[-inf,-inf] for pt in pts: if pt[0]max[0]: max[0]=pt[0] if pt[1]max[1]: max[1]=pt[1] return tuple(min),tuple(max) def perpendicularArea(axis): """Return area perpendicular to given axis (0=x,1=y,2=z) generated by bodies for which the function consider returns True (defaults to returning True always) and which is of the type :yref:`Sphere`. """ ext=aabbExtrema() other=((axis+1)%3,(axis+2)%3) return (ext[1][other[0]]-ext[0][other[0]])*(ext[1][other[1]]-ext[0][other[1]]) def fractionalBox(fraction=1.,minMax=None): """retrurn (min,max) that is the original minMax box (or aabb of the whole simulation if not specified) linearly scaled around its center to the fraction factor""" if not minMax: minMax=aabbExtrema() half=[.5*(minMax[1][i]-minMax[0][i]) for i in [0,1,2]] return (tuple([minMax[0][i]+(1-fraction)*half[i] for i in [0,1,2]]),tuple([minMax[1][i]-(1-fraction)*half[i] for i in [0,1,2]])) def randomizeColors(onlyDynamic=False): """Assign random colors to :yref:`Shape::color`. If onlyDynamic is true, only dynamic bodies will have the color changed. """ for b in O.bodies: color=(random.random(),random.random(),random.random()) if b.dynamic or not onlyDynamic: b.shape.color=color def avgNumInteractions(cutoff=0.,skipFree=False,considerClumps=False): r"""Return average numer of interactions per particle, also known as *coordination number* $Z$. This number is defined as .. math:: Z=2C/N where $C$ is number of contacts and $N$ is number of particles. When clumps are present, number of particles is the sum of standalone spheres plus the sum of clumps. Clumps are considered in the calculation if cutoff != 0 or skipFree = True. If cutoff=0 (default) and skipFree=False (default) one needs to set considerClumps=True to consider clumps in the calculation. With *skipFree*, particles not contributing to stable state of the packing are skipped, following equation (8) given in [Thornton2000]_: .. math:: Z_m=\frac{2C-N_1}{N-N_0-N_1} :param cutoff: cut some relative part of the sample's bounding box away. :param skipFree: see above. :param considerClumps: also consider clumps if cutoff=0 and skipFree=False; for further explanation see above. """ if cutoff==0 and not skipFree and not considerClumps: return 2*O.interactions.countReal()*1./len(O.bodies) else: nums,counts=bodyNumInteractionsHistogram(aabbExtrema(cutoff)) ## CC is 2*C CC=sum([nums[i]*counts[i] for i in range(len(nums))]); N=sum(counts) if not skipFree: return CC*1./N if N>0 else float('nan') ## find bins with 0 and 1 spheres N0=0 if (0 not in nums) else counts[nums.index(0)] N1=0 if (1 not in nums) else counts[nums.index(1)] NN=N-N0-N1 return (CC-N1)*1./NN if NN>0 else float('nan') def plotNumInteractionsHistogram(cutoff=0.): "Plot histogram with number of interactions per body, optionally cutting away *cutoff* relative axis-aligned box from specimen margin." nums,counts=bodyNumInteractionsHistogram(aabbExtrema(cutoff)) import pylab pylab.bar(nums,counts) pylab.title('Number of interactions histogram, average %g (cutoff=%g)'%(avgNumInteractions(cutoff),cutoff)) pylab.xlabel('Number of interactions') pylab.ylabel('Body count') pylab.ion() pylab.show() def plotDirections(aabb=(),mask=0,bins=20,numHist=True,noShow=False,sphSph=False): """Plot 3 histograms for distribution of interaction directions, in yz,xz and xy planes and (optional but default) histogram of number of interactions per body. If sphSph only sphere-sphere interactions are considered for the 3 directions histograms. :returns: If *noShow* is ``False``, displays the figure and returns nothing. If *noShow*, the figure object is returned without being displayed (works the same way as :yref:`yade.plot.plot`). """ import pylab,math from yade import utils for axis in [0,1,2]: d=utils.interactionAnglesHistogram(axis,mask=mask,bins=bins,aabb=aabb,sphSph=sphSph) fc=[0,0,0]; fc[axis]=1. subp=pylab.subplot(220+axis+1,polar=True); # 1.1 makes small gaps between values (but the column is a bit decentered) pylab.bar(d[0],d[1],width=math.pi/(1.1*bins),fc=fc,alpha=.7,label=['yz','xz','xy'][axis]) #pylab.title(['yz','xz','xy'][axis]+' plane') pylab.text(.5,.25,['yz','xz','xy'][axis],horizontalalignment='center',verticalalignment='center',transform=subp.transAxes,fontsize='xx-large') if numHist: pylab.subplot(224,polar=False) nums,counts=utils.bodyNumInteractionsHistogram(aabb if len(aabb)>0 else utils.aabbExtrema()) avg=sum([nums[i]*counts[i] for i in range(len(nums))])/(1.*sum(counts)) pylab.bar(nums,counts,fc=[1,1,0],alpha=.7,align='center') pylab.xlabel('Interactions per body (avg. %g)'%avg) pylab.axvline(x=avg,linewidth=3,color='r') pylab.ylabel('Body count') if noShow: return pylab.gcf() else: pylab.ion() pylab.show() def encodeVideoFromFrames(*args,**kw): "|ydeprecated|" _deprecatedUtilsFunction('utils.encodeVideoFromFrames','utils.makeVideo') return makeVideo(*args,**kw) def makeVideo(frameSpec,out,renameNotOverwrite=True,fps=24,kbps=6000,bps=None): """Create a video from external image files using `mencoder `__. Two-pass encoding using the default mencoder codec (mpeg4) is performed, running multi-threaded with number of threads equal to number of OpenMP threads allocated for Yade. :param frameSpec: wildcard | sequence of filenames. If list or tuple, filenames to be encoded in given order; otherwise wildcard understood by mencoder's mf:// URI option (shell wildcards such as ``/tmp/snap-*.png`` or and printf-style pattern like ``/tmp/snap-%05d.png``) :param str out: file to save video into :param bool renameNotOverwrite: if True, existing same-named video file will have -*number* appended; will be overwritten otherwise. :param int fps: Frames per second (``-mf fps=…``) :param int kbps: Bitrate (``-lavcopts vbitrate=…``) in kb/s """ import os,os.path,subprocess,warnings if bps!=None: warnings.warn('plot.makeVideo: bps is deprecated, use kbps instead (the significance is the same, but the name is more precise)',stacklevel=2,category=DeprecationWarning) kbps=bps if renameNotOverwrite and os.path.exists(out): i=0 while(os.path.exists(out+"~%d"%i)): i+=1 os.rename(out,out+"~%d"%i); print "Output file `%s' already existed, old file renamed to `%s'"%(out,out+"~%d"%i) if isinstance(frameSpec,list) or isinstance(frameSpec,tuple): frameSpec=','.join(frameSpec) for passNo in (1,2): cmd=['mencoder','mf://%s'%frameSpec,'-mf','fps=%d'%int(fps),'-ovc','lavc','-lavcopts','vbitrate=%d:vpass=%d:threads=%d:%s'%(int(kbps),passNo,O.numThreads,'turbo' if passNo==1 else ''),'-o',('/dev/null' if passNo==1 else out)] print 'Pass %d:'%passNo,' '.join(cmd) ret=subprocess.call(cmd) if ret!=0: raise RuntimeError("Error when running mencoder.") def replaceCollider(colliderEngine): """Replaces collider (Collider) engine with the engine supplied. Raises error if no collider is in engines.""" colliderIdx=-1 for i,e in enumerate(O.engines): if O.isChildClassOf(e.__class__.__name__,"Collider"): colliderIdx=i break if colliderIdx<0: raise RuntimeError("No Collider found within O.engines.") O.engines=O.engines[:colliderIdx]+[colliderEngine]+O.engines[colliderIdx+1:] def _procStatus(name): import os for l in open('/proc/%d/status'%os.getpid()): if l.split(':')[0]==name: return l raise "No such line in /proc/[pid]/status: "+name def vmData(): "Return memory usage data from Linux's /proc/[pid]/status, line VmData." l=_procStatus('VmData'); ll=l.split(); assert(ll[2]=='kB') return int(ll[1]) def uniaxialTestFeatures(filename=None,areaSections=10,axis=-1,distFactor=2.2,**kw): """Get some data about the current packing useful for uniaxial test: #. Find the dimensions that is the longest (uniaxial loading axis) #. Find the minimum cross-section area of the specimen by examining several (areaSections) sections perpendicular to axis, computing area of the convex hull for each one. This will work also for non-prismatic specimen. #. Find the bodies that are on the negative/positive boundary, to which the straining condition should be applied. :param filename: if given, spheres will be loaded from this file (ASCII format); if not, current simulation will be used. :param float areaSection: number of section that will be used to estimate cross-section :param ∈{0,1,2} axis: if given, force strained axis, rather than computing it from predominant length :return: dictionary with keys ``negIds``, ``posIds``, ``axis``, ``area``. .. warning:: The function :yref:`yade.utils.approxSectionArea` uses convex hull algorithm to find the area, but the implementation is reported to be *buggy* (bot works in some cases). Always check this number, or fix the convex hull algorithm (it is documented in the source, see :ysrc:`py/_utils.cpp`). """ if filename: ids=spheresFromFile(filename,**kw) else: ids=[b.id for b in O.bodies] mm,mx=aabbExtrema() dim=aabbDim(); if axis<0: axis=list(dim).index(max(dim)) # list(dim) for compat with python 2.5 which didn't have index defined for tuples yet (appeared in 2.6 first) assert(axis in (0,1,2)) import numpy areas=[approxSectionArea(coord,axis) for coord in numpy.linspace(mm[axis],mx[axis],num=10)[1:-1]] negIds,posIds=negPosExtremeIds(axis=axis,distFactor=distFactor) return {'negIds':negIds,'posIds':posIds,'axis':axis,'area':min(areas)} def voxelPorosityTriaxial(triax,resolution=200,offset=0): """ Calculate the porosity of a sample, given the TriaxialCompressionEngine. A function :yref:`yade.utils.voxelPorosity` is invoked, with the volume of a box enclosed by TriaxialCompressionEngine walls. The additional parameter offset allows using a smaller volume inside the box, where each side of the volume is at offset distance from the walls. By this way it is possible to find a more precise porosity of the sample, since at walls' contact the porosity is usually reduced. A recommended value of offset is bigger or equal to the average radius of spheres inside. The value of resolution depends on size of spheres used. It can be calibrated by invoking voxelPorosityTriaxial with offset=0 and comparing the result with TriaxialCompressionEngine.porosity. After calibration, the offset can be set to radius, or a bigger value, to get the result. :param triax: the TriaxialCompressionEngine handle :param resolution: voxel grid resolution :param offset: offset distance :return: the porosity of the sample inside given volume Example invocation:: from yade import utils rAvg=0.03 TriaxialTest(numberOfGrains=200,radiusMean=rAvg).load() O.dt=-1 O.run(1000) O.engines[4].porosity 0.44007807740143889 utils.voxelPorosityTriaxial(O.engines[4],200,0) 0.44055412500000002 utils.voxelPorosityTriaxial(O.engines[4],200,rAvg) 0.36798199999999998 """ p_bottom = O.bodies[triax.wall_bottom_id].state.se3[0] p_top = O.bodies[triax.wall_top_id ].state.se3[0] p_left = O.bodies[triax.wall_left_id ].state.se3[0] p_right = O.bodies[triax.wall_right_id ].state.se3[0] p_front = O.bodies[triax.wall_front_id ].state.se3[0] p_back = O.bodies[triax.wall_back_id ].state.se3[0] th = (triax.thickness)*0.5+offset x_0 = p_left [0] + th x_1 = p_right [0] - th y_0 = p_bottom[1] + th y_1 = p_top [1] - th z_0 = p_back [2] + th z_1 = p_front [2] - th a=Vector3(x_0,y_0,z_0) b=Vector3(x_1,y_1,z_1) return voxelPorosity(resolution,a,b) def trackPerfomance(updateTime=5): """ Track perfomance of a simulation. (Experimental) Will create new thread to produce some plots. Useful for track perfomance of long run simulations (in bath mode for example). """ def __track_perfomance(updateTime): pid=os.getpid() threadsCpu={} lastTime,lastIter=-1,-1 while 1: time.sleep(updateTime) if not O.running: lastTime,lastIter=-1,-1 continue if lastTime==-1: lastTime=time.time();lastIter=O.iter plot.plots.update({'Iteration':('Perfomance',None,'Bodies','Interactions')}) continue curTime=time.time();curIter=O.iter perf=(curIter-lastIter)/(curTime-lastTime) out=subprocess.Popen(['top','-bH','-n1', ''.join(['-p',str(pid)])],stdout=subprocess.PIPE).communicate()[0].splitlines() for s in out[7:-1]: w=s.split() threadsCpu[w[0]]=float(w[8]) plot.addData(Iteration=curIter,Iter=curIter,Perfomance=perf,Bodies=len(O.bodies),Interactions=len(O.interactions),**threadsCpu) plot.plots.update({'Iter':threadsCpu.keys()}) lastTime=time.time();lastIter=O.iter thread.start_new_thread(__track_perfomance,(updateTime)) def NormalRestitution2DampingRate(en): r"""Compute the normal damping rate as a function of the normal coefficient of restitution $e_n$. For $e_n\in\langle0,1\rangle$ damping rate equals .. math:: -\frac{\log e_n}{\sqrt{e_n^2+\pi^2}} """ if en == 0.0: return 0.999999999 if en == 1.0: return 0.0 from math import sqrt,log,pi ln_en = math.log(en) return (-ln_en/math.sqrt((math.pow(ln_en,2) + math.pi*math.pi))) def xMirror(half): """Mirror a sequence of 2d points around the x axis (changing sign on the y coord). The sequence should start up and then it will wrap from y downwards (or vice versa). If the last point's x coord is zero, it will not be duplicated.""" return list(half)+[(x,-y) for x,y in reversed(half[:-1] if half[-1][1]==0 else half)] ############################# ##### deprecated functions def _deprecatedUtilsFunction(old,new): "Wrapper for deprecated functions, example below." import warnings warnings.warn('Function utils.%s is deprecated, use %s instead.'%(old,new),stacklevel=2,category=UserWarning) # example of _deprecatedUtilsFunction usage: # # def import_mesh_geometry(*args,**kw): # "|ydeprecated|" # _deprecatedUtilsFunction('import_mesh_geometry','yade.import.gmsh') # import yade.ymport # return yade.ymport.stl(*args,**kw) class TableParamReader(): """Class for reading simulation parameters from text file. Each parameter is represented by one column, each parameter set by one line. Colums are separated by blanks (no quoting). First non-empty line contains column titles (without quotes). You may use special column named 'description' to describe this parameter set; if such colum is absent, description will be built by concatenating column names and corresponding values (``param1=34,param2=12.22,param4=foo``) * from columns ending in ``!`` (the ``!`` is not included in the column name) * from all columns, if no columns end in ``!``. Empty lines within the file are ignored (although counted); ``#`` starts comment till the end of line. Number of blank-separated columns must be the same for all non-empty lines. A special value ``=`` can be used instead of parameter value; value from the previous non-empty line will be used instead (works recursively). This class is used by :yref:`yade.utils.readParamsFromTable`. """ def __init__(self,file): "Setup the reader class, read data into memory." import re # read file in memory, remove newlines and comments; the [''] makes lines 1-indexed ll=[re.sub('\s*#.*','',l[:-1]) for l in ['']+open(file,'r').readlines()] # usable lines are those that contain something else than just spaces usableLines=[i for i in range(len(ll)) if not re.match(r'^\s*(#.*)?$',ll[i])] headings=ll[usableLines[0]].split() # use all values of which heading has ! after its name to build up the description string # if there are none, use all columns if not 'description' in headings: bangHeads=[h[:-1] for h in headings if h[-1]=='!'] or headings headings=[(h[:-1] if h[-1]=='!' else h) for h in headings] usableLines=usableLines[1:] # and remove headinds from usableLines values={} for l in usableLines: val={} for i in range(len(headings)): val[headings[i]]=ll[l].split()[i] values[l]=val lines=values.keys(); lines.sort() # replace '=' by previous value of the parameter for i,l in enumerate(lines): for j in values[l].keys(): if values[l][j]=='=': try: values[l][j]=values[lines[i-1]][j] except IndexError,KeyError: raise RuntimeError("The = specifier on line %d refers to nonexistent value on previous line?"%l) #import pprint; pprint.pprint(headings); pprint.pprint(values) # add descriptions, but if they repeat, append line number as well if not 'description' in headings: descs=set() for l in lines: dd=','.join(head.replace('!','')+'='+('%g'%values[head] if isinstance(values[l][head],float) else str(values[l][head])) for head in bangHeads).replace("'",'').replace('"','') if dd in descs: dd+='__line=%d__'%l values[l]['description']=dd descs.add(dd) self.values=values def paramDict(self): """Return dictionary containing data from file given to constructor. Keys are line numbers (which might be non-contiguous and refer to real line numbers that one can see in text editors), values are dictionaries mapping parameter names to their values given in the file. The special value '=' has already been interpreted, ``!`` (bangs) (if any) were already removed from column titles, ``description`` column has already been added (if absent).""" return self.values if __name__=="__main__": tryTable="""head1 important2! !OMP_NUM_THREADS! abcd 1 1.1 1.2 1.3 'a' 'b' 'c' 'd' ### comment # empty line 1 = = g """ file='/tmp/try-tbl.txt' f=open(file,'w') f.write(tryTable) f.close() from pprint import * pprint(TableParamReader(file).paramDict()) def runningInBatch(): 'Tell whether we are running inside the batch or separately.' import os return 'YADE_BATCH' in os.environ def waitIfBatch(): 'Block the simulation if running inside a batch. Typically used at the end of script so that it does not finish prematurely in batch mode (the execution would be ended in such a case).' if runningInBatch(): O.wait() def readParamsFromTable(tableFileLine=None,noTableOk=True,unknownOk=False,**kw): """ Read parameters from a file and assign them to __builtin__ variables. The format of the file is as follows (commens starting with # and empty lines allowed):: # commented lines allowed anywhere name1 name2 … # first non-blank line are column headings # empty line is OK, with or without comment val1 val2 … # 1st parameter set val2 val2 … # 2nd … Assigned tags (the ``description`` column is synthesized if absent,see :yref:`yade.utils.TableParamReader`); O.tags['description']=… # assigns the description column; might be synthesized O.tags['params']="name1=val1,name2=val2,…" # all explicitly assigned parameters O.tags['defaultParams']="unassignedName1=defaultValue1,…" # parameters that were left at their defaults O.tags['d.id']=O.tags['id']+'.'+O.tags['description'] O.tags['id.d']=O.tags['description']+'.'+O.tags['id'] All parameters (default as well as settable) are saved using :yref:`yade.utils.saveVars`\ ``('table')``. :param tableFileLine: string attribute to define which line number (as seen in a text editor) from wich text file (with one value per blank-separated columns) to get the values from. A ':' should appear between the two informations, e.g. 'file.table:4' to read the 4th line from file.table file :param bool noTableOk: if False, raise exception if the file cannot be open; use default values otherwise :param bool unknownOk: do not raise exception if unknown column name is found in the file, and assign it as well :return: number of assigned parameters """ tagsParams=[] # dictParams is what eventually ends up in yade.params.table (default+specified values) dictDefaults,dictParams,dictAssign={},{},{} import os, __builtin__,re,math if not tableFileLine and ('YADE_BATCH' not in os.environ or os.environ['YADE_BATCH']==''): if not noTableOk: raise EnvironmentError("YADE_BATCH is not defined in the environment") O.tags['line']='l!' else: if not tableFileLine: tableFileLine=os.environ['YADE_BATCH'] env=tableFileLine.split(':') tableFile,tableLine=env[0],int(env[1]) allTab=TableParamReader(tableFile).paramDict() if not allTab.has_key(tableLine): raise RuntimeError("Table %s doesn't contain valid line number %d"%(tableFile,tableLine)) vv=allTab[tableLine] O.tags['line']='l%d'%tableLine O.tags['description']=vv['description'] O.tags['id.d']=O.tags['id']+'.'+O.tags['description']; O.tags['d.id']=O.tags['description']+'.'+O.tags['id'] # assign values specified in the table to python vars # !something cols are skipped, those are env vars we don't treat at all (they are contained in description, though) for col in vv.keys(): if col=='description' or col[0]=='!': continue if col not in kw.keys() and (not unknownOk): raise NameError("Parameter `%s' has no default value assigned"%col) if vv[col]=='*': vv[col]=kw[col] # use default value for * in the table elif col in kw.keys(): kw.pop(col) # remove the var from kw, so that it contains only those that were default at the end of this loop #print 'ASSIGN',col,vv[col] tagsParams+=['%s=%s'%(col,vv[col])]; dictParams[col]=eval(vv[col],math.__dict__) # assign remaining (default) keys to python vars defaults=[] for k in kw.keys(): dictDefaults[k]=kw[k] defaults+=["%s=%s"%(k,kw[k])]; O.tags['defaultParams']=",".join(defaults) O.tags['params']=",".join(tagsParams) dictParams.update(dictDefaults) saveVars('table',loadNow=True,**dictParams) return len(tagsParams) def psd(bins=5, mass=True, mask=-1): """Calculates particle size distribution. :param int bins: number of bins :param bool mass: if true, the mass-PSD will be calculated :param int mask: :yref:`Body.mask` for the body :return: * binsSizes: list of bin's sizes * binsProc: how much material (in percents) are in the bin, cumulative * binsSumCum: how much material (in units) are in the bin, cumulative binsSizes, binsProc, binsSumCum """ maxD = 0.0 minD = 0.0 for b in O.bodies: if (isinstance(b.shape,Sphere) and ((mask<0) or ((b.mask&mask)<>0))): if ((2*b.shape.radius) > maxD) : maxD = 2*b.shape.radius if (((2*b.shape.radius) < minD) or (minD==0.0)): minD = 2*b.shape.radius if (minD==maxD): print 'Monodisperse packing with diameter =', minD,'. Not computing psd' return False #All particles are having the same size binsSizes = numpy.linspace(minD, maxD, bins+1) deltaBinD = (maxD-minD)/bins binsMass = numpy.zeros(bins) binsNumbers = numpy.zeros(bins) for b in O.bodies: if (isinstance(b.shape,Sphere) and ((mask<0) or ((b.mask&mask)<>0))): d=2*b.shape.radius basketId = int(math.floor( (d-minD) / deltaBinD ) ) if (d == maxD): basketId = bins-1 #If the diameter equals the maximal diameter, put the particle into the last bin binsMass[basketId] = binsMass[basketId] + b.state.mass #Put masses into the bin binsNumbers[basketId] = binsNumbers[basketId] + 1 #Put numbers into the bin binsProc = numpy.zeros(bins+1) binsSumCum = numpy.zeros(bins+1) i=1 for size in binsSizes[:-1]: if (mass): binsSumCum[i]+=binsSumCum[i-1]+binsMass[i-1] #Calculate mass else: binsSumCum[i]+=binsSumCum[i-1]+binsNumbers[i-1] #Calculate number of particles i+=1 if (binsSumCum[len(binsSumCum)-1] > 0): i=0 for l in binsSumCum: binsProc[i] = binsSumCum[i]/binsSumCum[len(binsSumCum)-1] i+=1 return binsSizes, binsProc, binsSumCum class clumpTemplate: """Create a clump template by a list of relative radii and a list of relative positions. Both lists must have the same length. :param [float,float,...] relRadii: list of relative radii (minimum length = 2) :param [Vector3,Vector3,...] relPositions: list of relative positions (minimum length = 2) """ def __init__(self,relRadii=[],relPositions=[[],[]]): if (len(relRadii) != len(relPositions)): raise ValueError("Given lists must have same length! Given lists does not match clump template structure."); if (len(relRadii) < 2): raise ValueError("One or more of given lists for relative radii have length < 2! Given lists does not match clump template structure."); for ii in range(0,len(relPositions)): if len(relPositions[ii]) != 3: raise ValueError("One or more of given lists for relative positions do not have length of 3! Given lists does not match clump template structure."); for jj in range(ii,len(relPositions)): if ii != jj: if (relPositions[ii] == relPositions[jj]): raise ValueError("Two or more of given lists for relative positions are equal! Given lists does not match clump template structure."); self.numCM = len(relRadii) self.relRadii = relRadii self.relPositions = relPositions class UnstructuredGrid: """EXPERIMENTAL. Class representing triangulated FEM-like unstructured grid. It is used for transfereing data from ad to YADE and external FEM program. The main purpose of this class is to store information about individual grid vertices/nodes coords (since facets stores only coordinates of vertices in local coords) and to avaluate and/or apply nodal forces from contact forces (from actual contact force and contact point the force is distributed to nodes using linear approximation). TODO rewrite to C++ TODO better docs :param dict vertices: dict of {internal vertex label:vertex}, e.g. {5:(0,0,0),22:(0,1,0),23:(1,0,0)} :param dict connectivityTable: dict of {internal element label:[indices of vertices]}, e.g. {88:[5,22,23]} """ def __init__(self,vertices={},connectivityTable={},**kw): self.vertices = vertices self.connectivityTable = connectivityTable self.forces = dict( (i,Vector3(0,0,0)) for i in vertices) self.build(**kw) def setup(self,vertices,connectivityTable,toSimulation=False,bodies=None,**kw): """Sets new information to receiver :param dict vertices: see constructor for explanation :param dict connectivityTable: see constructor for explanation :param bool toSimulation: if new information should be inserted to Yade simulation (create new bodies or not) :param [[int]]|None bodies: list of list of bodies indices to be appended as clumps (thus no contact detection is done within one body) """ self.vertices = dict( (i,v) for i,v in vertices.iteritems()) self.connectivityTable = dict( (i,e) for i,e in connectivityTable.iteritems()) self.build(**kw) if toSimulation: self.toSimulation(bodies) def build(self,**kw): self.elements = {} for i,c in self.connectivityTable.iteritems(): if len(c) == 3: b = facet([self.vertices[j] for j in c],**kw) elif len(c) == 4: b = tetra([self.vertices[j] for j in c],**kw) #b = polyhedron([self.vertices[j] for j in c],**kw) else: raise RuntimeError, "Unsupported cell shape (should be triangle or tetrahedron)" self.elements[i] = b def resetForces(self): for i in self.vertices: self.forces[i] = Vector3(0,0,0) def getForcesOfNodes(self): """Computes forces for each vertex/node. The nodal force is computed from contact force and contact point using linear approximation """ self.resetForces() for i,e in self.elements.iteritems(): ie = self.connectivityTable[i] for i in e.intrs(): fn = i.phys.normalForce fs = i.phys.shearForce if hasattr(i.phys,"shearForce") else Vector3.Zero f = (fn+fs) * (-1 if e.id == i.id1 else +1 if e.id == i.id2 else 'ERROR') cp = i.geom.contactPoint if isinstance(e.shape,Facet): v0,v1,v2 = [Vector3(self.vertices[j]) for j in ie] w0 = ((cp-v1).cross(v2-v1)).norm() w1 = ((cp-v2).cross(v0-v2)).norm() w2 = ((cp-v0).cross(v1-v0)).norm() ww = w0+w1+w2 self.forces[ie[0]] += f*w0/ww self.forces[ie[1]] += f*w1/ww self.forces[ie[2]] += f*w2/ww elif isinstance(e.shape,Tetra): v0,v1,v2,v3 = [Vector3(self.vertices[j]) for j in ie] w0 = TetrahedronVolume((cp,v1,v2,v3)) w1 = TetrahedronVolume((cp,v2,v3,v0)) w2 = TetrahedronVolume((cp,v3,v0,v1)) w3 = TetrahedronVolume((cp,v0,v1,v2)) ww = w0+w1+w2+w3 self.forces[ie[0]] += f*w0/ww self.forces[ie[1]] += f*w1/ww self.forces[ie[2]] += f*w2/ww self.forces[ie[3]] += f*w3/ww else: raise RuntimeError, "TODO" return self.forces def setPositionsOfNodes(self,newPoss): """Sets new position of nodes and also updates all elements in the simulation :param [Vector3] newPoss: list of new positions """ for i in self.vertices: self.vertices[i] = newPoss[i] self.updateElements() def updateElements(self): """Updates positions of all elements in the simulation """ for i,c in self.connectivityTable.iteritems(): e = self.elements[i] e.state.pos = Vector3(0,0,0) e.state.ori = Quaternion((1,0,0),0) if isinstance(e.shape,Facet): #e.shape.vertices = [self.vertices[j] for j in c] vs = [Vector3(self.vertices[j]) for j in c] cc = inscribedCircleCenter(*vs) for v in vs: v -= cc e.state.pos = cc e.shape.setVertices(*vs) elif isinstance(e.shape,Tetra): e.shape.v = [self.vertices[j] for j in c] else: raise RuntimeError, "TODO" def toSimulation(self,bodies=None): """Insert all elements to Yade simulation """ if bodies: e = self.elements.values() for b in bodies: O.bodies.appendClumped([e[i] for i in b]) else: O.bodies.append(self.elements.values()) trunk-2018.02b/py/wrapper/000077500000000000000000000000001324306050200152765ustar00rootroot00000000000000trunk-2018.02b/py/wrapper/customConverters.cpp000066400000000000000000000251601324306050200213730ustar00rootroot00000000000000// 2009 © Václav Šmilauer #include #include #include #include #include #include #include #ifdef YADE_OPENGL #include #include #endif #include // move this to the miniEigen wrapper later /* two-way se3 handling */ struct custom_se3_to_tuple{ static PyObject* convert(const Se3r& se3){ boost::python::tuple ret=boost::python::make_tuple(se3.position,se3.orientation); return boost::python::incref(ret.ptr()); } }; struct custom_Se3r_from_seq{ custom_Se3r_from_seq(){ boost::python::converter::registry::push_back(&convertible,&construct,boost::python::type_id()); } static void* convertible(PyObject* obj_ptr){ if(!PySequence_Check(obj_ptr)) return 0; if(PySequence_Size(obj_ptr)!=2 && PySequence_Size(obj_ptr)!=7) return 0; return obj_ptr; } static void construct(PyObject* obj_ptr, boost::python::converter::rvalue_from_python_stage1_data* data){ void* storage=((boost::python::converter::rvalue_from_python_storage*)(data))->storage.bytes; new (storage) Se3r; Se3r* se3=(Se3r*)storage; if(PySequence_Size(obj_ptr)==2){ // from vector and quaternion se3->position=boost::python::extract(PySequence_GetItem(obj_ptr,0)); se3->orientation=boost::python::extract(PySequence_GetItem(obj_ptr,1)); } else if(PySequence_Size(obj_ptr)==7){ // 3 vector components, 3 axis components, angle se3->position=Vector3r(boost::python::extract(PySequence_GetItem(obj_ptr,0)),boost::python::extract(PySequence_GetItem(obj_ptr,1)),boost::python::extract(PySequence_GetItem(obj_ptr,2))); Vector3r axis=Vector3r(boost::python::extract(PySequence_GetItem(obj_ptr,3)),boost::python::extract(PySequence_GetItem(obj_ptr,4)),boost::python::extract(PySequence_GetItem(obj_ptr,5))); Real angle=boost::python::extract(PySequence_GetItem(obj_ptr,6)); se3->orientation=Quaternionr(AngleAxisr(angle,axis)); } else throw std::logic_error(__FILE__ ": First, the sequence size for Se3r object was 2 or 7, but now is not? (programming error, please report!"); data->convertible=storage; } }; struct custom_OpenMPAccumulator_to_float{ static PyObject* convert(const OpenMPAccumulator& acc){ return boost::python::incref(PyFloat_FromDouble(acc.get())); } }; struct custom_OpenMPAccumulator_from_float{ custom_OpenMPAccumulator_from_float(){ boost::python::converter::registry::push_back(&convertible,&construct,boost::python::type_id >()); } static void* convertible(PyObject* obj_ptr){ return PyFloat_Check(obj_ptr) ? obj_ptr : 0; } static void construct(PyObject* obj_ptr, boost::python::converter::rvalue_from_python_stage1_data* data){ void* storage=((boost::python::converter::rvalue_from_python_storage >*)(data))->storage.bytes; new (storage) OpenMPAccumulator; ((OpenMPAccumulator*)storage)->set(boost::python::extract(obj_ptr)); data->convertible=storage; } }; struct custom_OpenMPAccumulator_to_int { static PyObject* convert(const OpenMPAccumulator& acc){ return boost::python::incref(PyInt_FromLong((long)acc.get())); } }; struct custom_OpenMPAccumulator_from_int{ custom_OpenMPAccumulator_from_int(){ boost::python::converter::registry::push_back(&convertible,&construct,boost::python::type_id >()); } static void* convertible(PyObject* obj_ptr){ return PyInt_Check(obj_ptr) ? obj_ptr : 0; } static void construct(PyObject* obj_ptr, boost::python::converter::rvalue_from_python_stage1_data* data){ void* storage=((boost::python::converter::rvalue_from_python_storage >*)(data))->storage.bytes; new (storage) OpenMPAccumulator; ((OpenMPAccumulator*)storage)->set(boost::python::extract(obj_ptr)); data->convertible=storage; } }; template struct custom_vvector_to_list{ static PyObject* convert(const std::vector >& vv){ boost::python::list ret; FOREACH(const std::vector& v, vv){ boost::python::list ret2; FOREACH(const T& e, v) ret2.append(e); ret.append(ret2); } return boost::python::incref(ret.ptr()); } }; template struct custom_list_to_list{ static PyObject* convert(const std::list& v){ boost::python::list ret; FOREACH(const containedType& e, v) ret.append(e); return boost::python::incref(ret.ptr()); } }; /*** c++-list to python-list */ template struct custom_vector_to_list{ static PyObject* convert(const std::vector& v){ boost::python::list ret; FOREACH(const containedType& e, v) ret.append(e); return boost::python::incref(ret.ptr()); } }; template struct custom_vector_from_seq{ custom_vector_from_seq(){ boost::python::converter::registry::push_back(&convertible,&construct,boost::python::type_id >()); } static void* convertible(PyObject* obj_ptr){ // the second condition is important, for some reason otherwise there were attempted conversions of Body to list which failed afterwards. if(!PySequence_Check(obj_ptr) || !PyObject_HasAttrString(obj_ptr,"__len__")) return 0; return obj_ptr; } static void construct(PyObject* obj_ptr, boost::python::converter::rvalue_from_python_stage1_data* data){ void* storage=((boost::python::converter::rvalue_from_python_storage >*)(data))->storage.bytes; new (storage) std::vector(); std::vector* v=(std::vector*)(storage); int l=PySequence_Size(obj_ptr); if(l<0) abort(); /*std::cerr<<"l="<reserve(l); for(int i=0; ipush_back(boost::python::extract(PySequence_GetItem(obj_ptr,i))); } data->convertible=storage; } }; struct custom_ptrMatchMaker_from_float{ custom_ptrMatchMaker_from_float(){ boost::python::converter::registry::push_back(&convertible,&construct,boost::python::type_id >()); } static void* convertible(PyObject* obj_ptr){ if(!PyNumber_Check(obj_ptr)) { cerr<<"Not convertible to MatchMaker"< >*)(data))->storage.bytes; new (storage) shared_ptr(new MatchMaker); // allocate the object at given address shared_ptr* mm=(shared_ptr*)(storage); // convert that address to our type (*mm)->algo="val"; (*mm)->val=PyFloat_AsDouble(obj_ptr); (*mm)->postLoad(**mm); data->convertible=storage; } }; #ifdef YADE_MASK_ARBITRARY struct custom_mask_to_long{ static PyObject* convert(const mask_t& mask){ return PyLong_FromString(const_cast(mask.to_string().c_str()),NULL,2); } }; struct custom_mask_from_long{ custom_mask_from_long(){ boost::python::converter::registry::push_back(&convertible,&construct,boost::python::type_id()); } static void* convertible(PyObject* obj_ptr){ return (PyLong_Check(obj_ptr) || PyInt_Check(obj_ptr))? obj_ptr : 0; } static void construct(PyObject* obj_ptr, boost::python::converter::rvalue_from_python_stage1_data* data){ void* storage=((boost::python::converter::rvalue_from_python_storage*)(data))->storage.bytes; new (storage) mask_t; mask_t* mask=(mask_t*)storage; if (PyInt_Check(obj_ptr)) obj_ptr = PyLong_FromLong(PyInt_AsLong(obj_ptr)); obj_ptr = _PyLong_Format(obj_ptr,2,0,0); std::string s(PyString_AsString(obj_ptr)); // if (s.substr(0,2).compare("0b")==0) s = s.substr(2); if (s[s.length()-1]=='L') s = s.substr(0,s.length()-1); // TODO? *mask = mask_t(s); data->convertible=storage; } }; #endif BOOST_PYTHON_MODULE(_customConverters){ custom_Se3r_from_seq(); boost::python::to_python_converter(); custom_OpenMPAccumulator_from_float(); boost::python::to_python_converter, custom_OpenMPAccumulator_to_float>(); custom_OpenMPAccumulator_from_int(); boost::python::to_python_converter, custom_OpenMPAccumulator_to_int>(); // todo: OpenMPAccumulator custom_ptrMatchMaker_from_float(); // StrArrayMap (typedef for std::map) → python dictionary //custom_StrArrayMap_to_dict(); // register from-python converter and to-python converter boost::python::to_python_converter >,custom_vvector_to_list >(); boost::python::to_python_converter >,custom_vvector_to_list >(); //boost::python::to_python_converter >, custom_list_to_list > >(); //boost::python::to_python_converter >, custom_list_to_list > >(); #ifdef YADE_MASK_ARBITRARY custom_mask_from_long(); boost::python::to_python_converter(); #endif // register 2-way conversion between c++ vector and python homogeneous sequence (list/tuple) of corresponding type #define VECTOR_SEQ_CONV(Type) custom_vector_from_seq(); boost::python::to_python_converter, custom_vector_to_list >(); VECTOR_SEQ_CONV(int); VECTOR_SEQ_CONV(bool); VECTOR_SEQ_CONV(Real); VECTOR_SEQ_CONV(Se3r); VECTOR_SEQ_CONV(Vector2r); VECTOR_SEQ_CONV(Vector2i); VECTOR_SEQ_CONV(Vector3r); VECTOR_SEQ_CONV(Vector3i); VECTOR_SEQ_CONV(Vector6r); VECTOR_SEQ_CONV(Vector6i); VECTOR_SEQ_CONV(Matrix3r); VECTOR_SEQ_CONV(Matrix6r); VECTOR_SEQ_CONV(std::string); VECTOR_SEQ_CONV(shared_ptr); VECTOR_SEQ_CONV(shared_ptr); VECTOR_SEQ_CONV(shared_ptr); VECTOR_SEQ_CONV(shared_ptr); VECTOR_SEQ_CONV(shared_ptr); VECTOR_SEQ_CONV(shared_ptr); VECTOR_SEQ_CONV(shared_ptr); VECTOR_SEQ_CONV(shared_ptr); VECTOR_SEQ_CONV(shared_ptr); #ifdef YADE_BODY_CALLBACK VECTOR_SEQ_CONV(shared_ptr); #endif VECTOR_SEQ_CONV(shared_ptr); VECTOR_SEQ_CONV(shared_ptr); #ifdef YADE_OPENGL VECTOR_SEQ_CONV(shared_ptr); VECTOR_SEQ_CONV(shared_ptr); VECTOR_SEQ_CONV(shared_ptr); VECTOR_SEQ_CONV(shared_ptr); VECTOR_SEQ_CONV(shared_ptr); VECTOR_SEQ_CONV(shared_ptr); #endif #undef VECTOR_SEQ_CONV } trunk-2018.02b/py/wrapper/yadeWrapper.cpp000066400000000000000000001731241324306050200202750ustar00rootroot00000000000000// 2007,2008 © Václav Šmilauer #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 namespace py = boost::python; /* Python normally iterates over object it is has __getitem__ and __len__, which BodyContainer does. However, it will not skip removed bodies automatically, hence this iterator which does just that. */ class pyBodyIterator{ BodyContainer::iterator I, Iend; public: pyBodyIterator(const shared_ptr& bc){ I=bc->begin(); Iend=bc->end(); } pyBodyIterator pyIter(){return *this;} shared_ptr pyNext(){ BodyContainer::iterator ret; while(I!=Iend){ ret=I; ++I; if(*ret) return *ret; } PyErr_SetNone(PyExc_StopIteration); py::throw_error_already_set(); /* never reached, but makes the compiler happier */ throw; } }; class pyBodyContainer{ private: void checkClump(shared_ptr b){ if (!(b->isClump())){ PyErr_SetString(PyExc_TypeError,("Error: Body"+boost::lexical_cast(b->getId())+" is not a clump.").c_str()); py::throw_error_already_set(); } } typedef std::map MemberMap; public: const shared_ptr proxee; pyBodyIterator pyIter(){return pyBodyIterator(proxee);} pyBodyContainer(const shared_ptr& _proxee): proxee(_proxee){} shared_ptr pyGetitem(Body::id_t _id){ int id=(_id>=0 ? _id : proxee->size()+_id); if(id<0 || (size_t)id>=proxee->size()){ PyErr_SetString(PyExc_IndexError, "Body id out of range."); py::throw_error_already_set(); /* make compiler happy; never reached */ return shared_ptr(); } else return (*proxee)[id]; } Body::id_t append(shared_ptr b){ // shoud be >=0, but Body is by default created with id 0... :-| if(b->getId()>=0){ PyErr_SetString(PyExc_IndexError,("Body already has id "+boost::lexical_cast(b->getId())+" set; appending such body (for the second time) is not allowed.").c_str()); py::throw_error_already_set(); } return proxee->insert(b); } vector appendList(vector > bb){ boost::mutex::scoped_lock lock(Omega::instance().renderMutex); vector ret; FOREACH(shared_ptr& b, bb){ret.push_back(append(b));} return ret; } Body::id_t clump(vector ids, unsigned int discretization){ // create and add clump itself Scene* scene(Omega::instance().getScene().get()); shared_ptr clumpBody=shared_ptr(new Body()); shared_ptr clump=shared_ptr(new Clump()); clumpBody->shape=clump; clumpBody->setBounded(false); proxee->insert(clumpBody); // add clump members to the clump FOREACH(Body::id_t id, ids) { if (Body::byId(id,scene)->isClumpMember()){ //Check, whether the body is clumpMember Clump::del(Body::byId(Body::byId(id,scene)->clumpId,scene),Body::byId(id,scene)); //If so, remove it from there } }; FOREACH(Body::id_t id, ids) Clump::add(clumpBody,Body::byId(id,scene)); Clump::updateProperties(clumpBody, discretization); return clumpBody->getId(); } py::tuple appendClump(vector > bb, unsigned int discretization){ // append constituent particles vector ids(appendList(bb)); // clump them together (the clump fcn) and return return py::make_tuple(clump(ids, discretization),ids); } void updateClumpProperties(py::list excludeList,unsigned int discretization){ //convert excludeList to a c++ list vector excludeListC; for (int ii = 0; ii < py::len(excludeList); ii++) excludeListC.push_back(py::extract(excludeList[ii])()); FOREACH(const shared_ptr& b, *proxee){ if ( !(std::find(excludeListC.begin(), excludeListC.end(), b->getId()) != excludeListC.end()) ) { if (b->isClump()) Clump::updateProperties(b, discretization); } } } void deleteClumpMember(shared_ptr clumpBody, shared_ptr memberBody){ //FIXME const shared_ptr clump(YADE_PTR_CAST(clumpBody->shape)); if (clump->members.size()==1 ){ Clump::del(clumpBody,memberBody); //phD was not commented out for (unsigned i=0; iids.size(); i++){ if (clump->ids[i] == memberBody->getId()){ clump->ids.erase(clump->ids.begin()+i); } } proxee->erase(memberBody->getId(),false); proxee->erase(clumpBody->getId(),false); }else{ Clump::del(clumpBody,memberBody); //pHD was not commented out for (unsigned i=0; iids.size(); i++){ if (clump->ids[i] == memberBody->getId()){ clump->ids.erase(clump->ids.begin()+i); } } Clump::updatePropertiesNonSpherical(clumpBody,/*intersecting*/ false); proxee->erase(memberBody->getId(),false); } } void deleteClumpBody(shared_ptr clumpBody){ //FIXME const shared_ptr clump(YADE_PTR_CAST(clumpBody->shape)); //if (clump->members.size()==0 ){ // proxee->erase(clumpBody->getId()); //}else{ Scene* scene(Omega::instance().getScene().get()); int totalNumber = clump->ids.size(); int count = 0; while(countids.size(); i++){ shared_ptr memberBody (YADE_PTR_CAST( Body::byId(clump->ids[/*i */0],scene) )); deleteClumpMember(clumpBody,memberBody); //clump->ids.erase(clump->ids.begin()+i); //proxee->erase(memberBody->getId()); count++; } proxee->erase(clumpBody->getId(),true); //} } void addToClump(vector bids, Body::id_t cid, unsigned int discretization){ Scene* scene(Omega::instance().getScene().get()); // get scene shared_ptr clp = Body::byId(cid,scene); // get clump pointer checkClump(clp); vector eraseList; FOREACH(Body::id_t bid, bids) { shared_ptr bp = Body::byId(bid,scene); // get body pointer if (bp->isClump()){ if (bp == clp) {PyErr_Warn(PyExc_UserWarning,("Warning: Body "+boost::lexical_cast(bid)+" and clump "+boost::lexical_cast(cid)+" are the same bodies. Body was not added.").c_str()); return;} Clump::add(clp,bp);//add clump bid to clump cid eraseList.push_back(bid); } else if (bp->isClumpMember()){ Body::id_t bpClumpId = bp->clumpId; shared_ptr bpClumpPointer = Body::byId(bpClumpId,scene); if (bpClumpPointer == clp) {PyErr_Warn(PyExc_UserWarning,("Warning: Body "+boost::lexical_cast(bid)+" is already a clump member of clump "+boost::lexical_cast(cid)+". Body was not added.").c_str()); return;} Clump::add(clp,bpClumpPointer);//add clump bpClumpId to clump cid eraseList.push_back(bpClumpId); } else Clump::add(clp,bp);// bp must be a standalone! } Clump::updateProperties(clp, discretization); FOREACH(Body::id_t bid, eraseList) proxee->erase(bid,false);//erase old clumps } void releaseFromClump(Body::id_t bid, Body::id_t cid, unsigned int discretization){ Scene* scene(Omega::instance().getScene().get()); // get scene shared_ptr bp = Body::byId(bid,scene); // get body pointer shared_ptr clp = Body::byId(cid,scene); // get clump pointer checkClump(clp); if (bp->isClumpMember()){ Body::id_t bpClumpId = bp->clumpId; if (cid == bpClumpId){ const shared_ptr& clump=YADE_PTR_CAST(clp->shape); std::map& members = clump->members; if (members.size() == 2) {PyErr_Warn(PyExc_UserWarning,("Warning: Body "+boost::lexical_cast(bid)+" not released from clump "+boost::lexical_cast(cid)+", because number of clump members would get < 2!").c_str()); return;} Clump::del(clp,bp);//release bid from cid Clump::updateProperties(clp, discretization); } else { PyErr_Warn(PyExc_UserWarning,("Warning: Body "+boost::lexical_cast(bid)+" must be a clump member of clump "+boost::lexical_cast(cid)+". Body was not released.").c_str()); return;} } else { PyErr_Warn(PyExc_UserWarning,("Warning: Body "+boost::lexical_cast(bid)+" is not a clump member. Body was not released.").c_str()); return;} } py::list replaceByClumps(py::list ctList, vector amounts, unsigned int discretization){ py::list ret; Real checkSum = 0.0; FOREACH(Real amount, amounts) { if (amount < 0.0) { PyErr_SetString(PyExc_ValueError,("Error: One or more of given amounts are negative!")); py::throw_error_already_set(); } else checkSum += amount; } if (checkSum > 1.0){ PyErr_SetString(PyExc_ValueError,("Error: Sum of amounts "+boost::lexical_cast(checkSum)+" should not be bigger than 1.0!").c_str()); py::throw_error_already_set(); } if (py::len(ctList) != (unsigned) amounts.size()) {//avoid unsigned comparison warning PyErr_SetString(PyExc_ValueError,("Error: Length of amounts list ("+boost::lexical_cast(amounts.size())+") differs from length of template list ("+boost::lexical_cast(py::len(ctList))+").").c_str()); py::throw_error_already_set(); } //set a random generator (code copied from pkg/dem/SpherePack.cpp): static boost::minstd_rand randGen((int)TimingInfo::getNow(/* get the number even if timing is disabled globally */ true)); typedef boost::variate_generator > UniRandGen; static UniRandGen rndUnit(randGen,boost::uniform_real<>(-1,1)); //get number of spherical particles and a list of all spheres: vector > sphereList; shared_ptr sph (new Sphere); int Sph_Index = sph->getClassIndexStatic(); FOREACH(const shared_ptr& b, *proxee) if ( (b->shape->getClassIndex() == Sph_Index) && (b->isStandalone()) ) sphereList.push_back(b); int num = sphereList.size(); //loop over templates: int numSphereList = num, numTemplates = amounts.size(); for (int ii = 0; ii < numTemplates; ii++) { //ctList: [,, ...] = [,, ...] //ct: = (python objects) //relRadList: [relRad1,relRad2, ...] (list of doubles) //relPosList: [relPos1,relPos2, ...] (list of vectors) //extract attributes from python objects: py::object ctTmp = ctList[ii]; int numCM = py::extract(ctTmp.attr("numCM"))();// number of clump members py::list relRadListTmp = py::extract(ctTmp.attr("relRadii"))(); py::list relPosListTmp = py::extract(ctTmp.attr("relPositions"))(); //get relative radii and positions; calculate volumes; get balance point: get axis aligned bounding box; get minimum radius; vector relRadTmp(numCM), relVolTmp(numCM); vector relPosTmp(numCM); Vector3r relPosTmpMean = Vector3r::Zero(); Real rMin=1./0.; AlignedBox3r aabb; for (int jj = 0; jj < numCM; jj++) { relRadTmp[jj] = py::extract(relRadListTmp[jj])(); relVolTmp[jj] = (4./3.)*Mathr::PI*pow(relRadTmp[jj],3.); relPosTmp[jj] = py::extract(relPosListTmp[jj])(); relPosTmpMean += relPosTmp[jj]; aabb.extend(relPosTmp[jj] + Vector3r::Constant(relRadTmp[jj])); aabb.extend(relPosTmp[jj] - Vector3r::Constant(relRadTmp[jj])); rMin=min(rMin,relRadTmp[jj]); } relPosTmpMean /= numCM;//balance point //get volume of the clump template using regular cubic cell array inside axis aligned bounding box of the clump: //(some parts are duplicated from intergration algorithm in Clump::updateProperties) Real dx = rMin/5.; //edge length of cell Real dv = pow(dx,3); //volume of a single cell Vector3r x; //position vector (center) of cell Real relVolSumTmp = 0.0; //volume of clump template for(x.x()=aabb.min().x()+dx/2.; x.x() > bpListTmp(numReplaceTmp); int a = 0, c = 0;//counters vector posTmp; FOREACH (const shared_ptr& b, sphereList) { if (c == a*numSphereList/numReplaceTmp) { bpListTmp[a] = b; a++; posTmp.push_back(c);//remember position in sphereList } c++; } for (int jj = 0; jj < a; jj++) { sphereList.erase(sphereList.begin()+posTmp[jj]-jj);//remove bodies from sphereList, that were already found numSphereList--; } //adapt position- and radii-informations and replace spheres from bpListTmp by clumps: FOREACH (const shared_ptr& b, bpListTmp) { //get sphere, that should be replaced: const Sphere* sphere = YADE_CAST (b->shape.get()); shared_ptr matTmp = b->material; //get a random rotation quaternion: Quaternionr randAxisTmp = (Quaternionr) AngleAxisr(2*Mathr::PI*rndUnit(),Vector3r(rndUnit(),rndUnit(),rndUnit())); randAxisTmp.normalize(); //convert geometries in global coordinates (scaling): Real scalingFactorVolume = ((4./3.)*Mathr::PI*pow(sphere->radius,3.))/relVolSumTmp; Real scalingFactor1D = pow(scalingFactorVolume,1./3.);//=((vol. sphere)/(relative clump volume))^(1/3) vector newPosTmp(numCM); vector newRadTmp(numCM); vector idsTmp(numCM); for (int jj = 0; jj < numCM; jj++) { newPosTmp[jj] = relPosTmp[jj] - relPosTmpMean; //shift position, to get balance point at (0,0,0) newPosTmp[jj] = randAxisTmp*newPosTmp[jj]; //rotate around balance point newRadTmp[jj] = relRadTmp[jj] * scalingFactor1D;//scale radii newPosTmp[jj] = newPosTmp[jj] * scalingFactor1D;//scale position newPosTmp[jj] += b->state->pos; //translate new position to spheres center //create spheres: shared_ptr newSphere = shared_ptr(new Body()); newSphere->state->blockedDOFs = State::DOF_NONE; newSphere->state->mass = scalingFactorVolume*relVolTmp[jj]*matTmp->density;//vol. corrected mass for clump members Real inertiaTmp = 2.0/5.0*newSphere->state->mass*newRadTmp[jj]*newRadTmp[jj]; newSphere->state->inertia = Vector3r(inertiaTmp,inertiaTmp,inertiaTmp); newSphere->state->pos = newPosTmp[jj]; newSphere->material = matTmp; shared_ptr sphereTmp = shared_ptr(new Sphere()); sphereTmp->radius = newRadTmp[jj]; sphereTmp->color = Vector3r(Mathr::UnitRandom(),Mathr::UnitRandom(),Mathr::UnitRandom()); sphereTmp->color.normalize(); newSphere->shape = sphereTmp; shared_ptr aabbTmp = shared_ptr(new Aabb()); aabbTmp->color = Vector3r(0,1,0); newSphere->bound = aabbTmp; proxee->insert(newSphere); LOG_DEBUG("New body (sphere) "<id<<" added."); idsTmp[jj] = newSphere->id; } Body::id_t newClumpId = clump(idsTmp, discretization); ret.append(py::make_tuple(newClumpId,idsTmp)); erase(b->id,false); } } return ret; } Real getRoundness(py::list excludeList){ Scene* scene(Omega::instance().getScene().get()); // get scene shared_ptr sph (new Sphere); int Sph_Index = sph->getClassIndexStatic(); // get sphere index for checking if bodies are spheres //convert excludeList to a c++ list vector excludeListC; for (int ii = 0; ii < py::len(excludeList); ii++) excludeListC.push_back(py::extract(excludeList[ii])()); Real RC_sum = 0.0; //sum of local roundnesses Real R1, R2, vol, dens; int c = 0; //counter FOREACH(const shared_ptr& b, *proxee){ if ( !(std::find(excludeListC.begin(), excludeListC.end(), b->getId()) != excludeListC.end()) ) { if ((b->shape->getClassIndex() == Sph_Index) && (b->isStandalone())) { RC_sum += 1.0; c += 1; } if (b->isClump()){ R2 = 0.0; dens = 0.0; vol = 0.0; const shared_ptr& clump=YADE_PTR_CAST(b->shape); std::map& members = clump->members; for(MemberMap::value_type& mm : members){ const Body::id_t& memberId=mm.first; const shared_ptr& member=Body::byId(memberId,scene); assert(member->isClumpMember()); if (member->shape->getClassIndex() == Sph_Index){//clump member should be a sphere const Sphere* sphere = YADE_CAST (member->shape.get()); R2 = max((member->state->pos - b->state->pos).norm() + sphere->radius, R2); //get minimum radius of a sphere, that imbeds clump dens = member->material->density; } } if (dens > 0.) vol = b->state->mass/dens; R1 = pow((3.*vol)/(4.*Mathr::PI),1./3.); //get theoretical radius of a sphere, with same volume as clump if (R2 < R1) {PyErr_Warn(PyExc_UserWarning,("Something went wrong in getRoundness method (R2 < R1 detected).")); return 0;} RC_sum += R1/R2; c += 1; } } } if (c == 0) c = 1; //in case no spheres and no clumps are present in the scene: RC = 0 return RC_sum/c; //return roundness coefficient RC } vector replace(vector > bb){proxee->clear(); return appendList(bb);} long length(){return proxee->size();} void clear(){proxee->clear();} bool erase(Body::id_t id, bool eraseClumpMembers){ return proxee->erase(id,eraseClumpMembers); } }; class pyTags{ public: pyTags(const shared_ptr _mb): mb(_mb){} const shared_ptr mb; bool hasKey(const string& key){ FOREACH(string val, mb->tags){ if(boost::algorithm::starts_with(val,key+"=")){ return true;} } return false; } string getItem(const string& key){ FOREACH(string& val, mb->tags){ if(boost::algorithm::starts_with(val,key+"=")){ string val1(val); boost::algorithm::erase_head(val1,key.size()+1); return val1;} } PyErr_SetString(PyExc_KeyError,("Invalid key: "+key+".").c_str()); py::throw_error_already_set(); /* make compiler happy; never reached */ return string(); } void setItem(const string& key,const string& item){ if(key.find("=")!=string::npos) { PyErr_SetString(PyExc_KeyError, "Key must not contain the '=' character (implementation limitation; sorry)."); py::throw_error_already_set(); } FOREACH(string& val, mb->tags){if(boost::algorithm::starts_with(val,key+"=")){ val=key+"="+item; return; } } mb->tags.push_back(key+"="+item); } py::list keys(){ py::list ret; FOREACH(string val, mb->tags){ size_t i=val.find("="); if(i==string::npos) throw runtime_error("Tags must be in the key=value format (internal error?)"); boost::algorithm::erase_tail(val,val.size()-i); ret.append(val); } return ret; } }; class pyInteractionIterator{ InteractionContainer::iterator I, Iend; public: pyInteractionIterator(const shared_ptr& ic){ I=ic->begin(); Iend=ic->end(); } pyInteractionIterator pyIter(){return *this;} shared_ptr pyNext(){ InteractionContainer::iterator ret; while(I!=Iend){ ret=I; ++I; if((*ret)->isReal()) return *ret; } PyErr_SetNone(PyExc_StopIteration); py::throw_error_already_set(); throw; // to avoid compiler warning; never reached //InteractionContainer::iterator ret=I; ++I; return *ret; } }; class pyInteractionContainer{ public: const shared_ptr proxee; pyInteractionContainer(const shared_ptr& _proxee): proxee(_proxee){} pyInteractionIterator pyIter(){return pyInteractionIterator(proxee);} bool has(Body::id_t id1, Body::id_t id2){return proxee->found(id1,id2);} shared_ptr pyGetitem(vector id12){ //if(!PySequence_Check(id12.ptr())) throw invalid_argument("Key must be a tuple"); //if(py::len(id12)!=2) throw invalid_argument("Key must be a 2-tuple: id1,id2."); if(id12.size()==2){ //if(max(id12[0],id12[1])> shared_ptr i=proxee->find(id12[0],id12[1]); if(i) return i; else { PyErr_SetString(PyExc_IndexError,"No such interaction"); py::throw_error_already_set(); /* make compiler happy; never reached */ return shared_ptr(); } } else if(id12.size()==1){ return (*proxee)[id12[0]];} else throw invalid_argument("2 integers (id1,id2) or 1 integer (nth) required."); } /* return nth _real_ iteration from the container (0-based index); this is to facilitate picking random interaction */ shared_ptr pyNth(long n){ long i=0; FOREACH(shared_ptr I, *proxee){ if(!I->isReal()) continue; if(i++==n) return I; } PyErr_SetString(PyExc_IndexError,(string("Interaction number out of range (")+boost::lexical_cast(n)+">="+boost::lexical_cast(i)+").").c_str()); py::throw_error_already_set(); /* make compiler happy; never reached */ return shared_ptr(); } long len(){return proxee->size();} void clear(){proxee->clear();} py::list withBody(long id){ py::list ret; FOREACH(const shared_ptr& I, *proxee){ if(I->isReal() && (I->getId1()==id || I->getId2()==id)) ret.append(I);} return ret;} py::list withBodyAll(long id){ py::list ret; FOREACH(const shared_ptr& I, *proxee){ if(I->getId1()==id || I->getId2()==id) ret.append(I);} return ret; } py::list getAll(bool onlyReal){ py::list ret; FOREACH(const shared_ptr& I, *proxee){ if(onlyReal && !I->isReal()) continue; ret.append(I);} return ret;} long countReal(){ long ret=0; FOREACH(const shared_ptr& I, *proxee){ if(I->isReal()) ret++; } return ret; } bool serializeSorted_get(){return proxee->serializeSorted;} void serializeSorted_set(bool ss){proxee->serializeSorted=ss;} void eraseNonReal(){ proxee->eraseNonReal(); } void erase(Body::id_t id1, Body::id_t id2){ proxee->requestErase(id1,id2); } }; class pyForceContainer{ shared_ptr scene; public: pyForceContainer(shared_ptr _scene): scene(_scene) { } void checkId(long id){ if(id<0 || (size_t)id>=scene->bodies->size()){ PyErr_SetString(PyExc_IndexError, "Body id out of range."); py::throw_error_already_set(); /* never reached */ throw; } } Vector3r force_get(long id, bool sync){ checkId(id); if (!sync) return scene->forces.getForceSingle(id); scene->forces.sync(); return scene->forces.getForce(id);} Vector3r torque_get(long id, bool sync){ checkId(id); if (!sync) return scene->forces.getTorqueSingle(id); scene->forces.sync(); return scene->forces.getTorque(id);} Vector3r move_get(long id){ checkId(id); return scene->forces.getMoveSingle(id); } Vector3r rot_get(long id){ checkId(id); return scene->forces.getRotSingle(id); } void force_add(long id, const Vector3r& f, bool permanent){ checkId(id); if (!permanent) scene->forces.addForce (id,f); else { LOG_WARN("O.forces.addF(...,permanent=True) is deprecated, use O.forces.setPermF(...) instead"); scene->forces.setPermForce (id,f); } } void torque_add(long id, const Vector3r& t, bool permanent){ checkId(id); if (!permanent) scene->forces.addTorque(id,t); else { LOG_WARN("O.forces.addT(...,permanent=True) is deprecated, use O.forces.setPermT(...) instead"); scene->forces.setPermTorque(id,t); } } void move_add(long id, const Vector3r& t){ checkId(id); scene->forces.addMove(id,t);} void rot_add(long id, const Vector3r& t){ checkId(id); scene->forces.addRot(id,t);} Vector3r permForce_get(long id){ checkId(id); return scene->forces.getPermForce(id);} Vector3r permTorque_get(long id){ checkId(id); return scene->forces.getPermTorque(id);} void permForce_set (long id, const Vector3r& f){ checkId(id); scene->forces.setPermForce (id,f); } void permTorque_set(long id, const Vector3r& t){ checkId(id); scene->forces.setPermTorque(id,t); } void reset(bool resetAll) {scene->forces.reset(scene->iter,resetAll);} long syncCount_get(){ return scene->forces.syncCount;} void syncCount_set(long count){ scene->forces.syncCount=count;} bool getPermForceUsed() {return scene->forces.getPermForceUsed();} }; class pyMaterialContainer{ shared_ptr scene; public: pyMaterialContainer(shared_ptr _scene): scene(_scene) { } shared_ptr getitem_id(int _id){ int id=(_id>=0 ? _id : scene->materials.size()+_id); if(id<0 || (size_t)id>=scene->materials.size()){ PyErr_SetString(PyExc_IndexError, "Material id out of range."); py::throw_error_already_set(); /* never reached */ throw; } return Material::byId(id,scene); } shared_ptr getitem_label(string label){ // translate runtime_error to KeyError (instead of RuntimeError) if the material doesn't exist try { return Material::byLabel(label,scene); } catch (std::runtime_error& e){ PyErr_SetString(PyExc_KeyError,e.what()); py::throw_error_already_set(); /* never reached; avoids warning */ throw; } } int append(shared_ptr m){ scene->materials.push_back(m); m->id=scene->materials.size()-1; return m->id; } vector appendList(vector > mm){ vector ret; FOREACH(shared_ptr& m, mm) ret.push_back(append(m)); return ret; } int len(){ return (int)scene->materials.size(); } int index(const std::string& label){ return Material::byLabelIndex(label,scene.get()); } }; void termHandlerNormal(int sig){cerr<<"Yade: normal exit."< rb=OMEGA.getScene(); if(!rb){ OMEGA.init(); rb=OMEGA.getScene(); } assert(rb); if(!OMEGA.hasSimulationLoop()){ OMEGA.createSimulationLoop(); } }; /* Create variables in python's __builtin__ namespace that correspond to labeled objects. At this moment, only engines and functors can be labeled (not bodies etc). */ void mapLabeledEntitiesToVariables(){ // not sure if we should map materials to variables by default... // a call to this functions would have to be added to pyMaterialContainer::append #if 0 FOREACH(const shared_ptr& m, OMEGA.getScene()->materials){ if(!m->label.empty()) { PyGILState_STATE gstate; gstate = PyGILState_Ensure(); PyRun_SimpleString(("__builtins__."+m->label+"=Omega().materials["+boost::lexical_cast(m->id)+"]").c_str()); PyGILState_Release(gstate); } } #endif FOREACH(const shared_ptr& e, OMEGA.getScene()->engines){ if(!e->label.empty()){ pyRunString("__builtins__."+e->label+"=Omega().labeledEngine('"+e->label+"')"); } #define _DO_FUNCTORS(functors,FunctorT){ FOREACH(const shared_ptr& f, functors){ if(!f->label.empty()){ pyRunString("__builtins__."+f->label+"=Omega().labeledEngine('"+f->label+"')");}} } #define _TRY_DISPATCHER(DispatcherT) { DispatcherT* d=dynamic_cast(e.get()); if(d){ _DO_FUNCTORS(d->functors,DispatcherT::FunctorType); } } _TRY_DISPATCHER(BoundDispatcher); _TRY_DISPATCHER(IGeomDispatcher); _TRY_DISPATCHER(IPhysDispatcher); _TRY_DISPATCHER(LawDispatcher); InteractionLoop* id=dynamic_cast(e.get()); if(id){ _DO_FUNCTORS(id->geomDispatcher->functors,IGeomFunctor); _DO_FUNCTORS(id->physDispatcher->functors,IPhysFunctor); _DO_FUNCTORS(id->lawDispatcher->functors,LawFunctor); } Collider* coll=dynamic_cast(e.get()); if(coll){ _DO_FUNCTORS(coll->boundDispatcher->functors,BoundFunctor); } #undef _DO_FUNCTORS #undef _TRY_DISPATCHER CombinedKinematicEngine* cke=dynamic_cast(e.get()); if (cke) { FOREACH(const shared_ptr& ke, cke->comb){ if(!ke->label.empty()){ pyRunString("__builtins__."+ke->label+"=Omega().labeledEngine('"+ke->label+"')"); } } } } } py::object labeled_engine_get(string label){ FOREACH(const shared_ptr& e, OMEGA.getScene()->engines){ #define _DO_FUNCTORS(functors,FunctorT){ FOREACH(const shared_ptr& f, functors){ if(f->label==label) return py::object(f); }} #define _TRY_DISPATCHER(DispatcherT) { DispatcherT* d=dynamic_cast(e.get()); if(d){ _DO_FUNCTORS(d->functors,DispatcherT::FunctorType); } } if(e->label==label){ return py::object(e); } _TRY_DISPATCHER(BoundDispatcher); _TRY_DISPATCHER(IGeomDispatcher); _TRY_DISPATCHER(IPhysDispatcher); _TRY_DISPATCHER(LawDispatcher); InteractionLoop* id=dynamic_cast(e.get()); if(id){ _DO_FUNCTORS(id->geomDispatcher->functors,IGeomFunctor); _DO_FUNCTORS(id->physDispatcher->functors,IPhysFunctor); _DO_FUNCTORS(id->lawDispatcher->functors,LawFunctor); } Collider* coll=dynamic_cast(e.get()); if(coll){ _DO_FUNCTORS(coll->boundDispatcher->functors,BoundFunctor); } #undef _DO_FUNCTORS #undef _TRY_DISPATCHER CombinedKinematicEngine* cke=dynamic_cast(e.get()); if (cke) { FOREACH(const shared_ptr& ke, cke->comb){ if(ke->label==label) return py::object(ke); } } } throw std::invalid_argument(string("No engine labeled `")+label+"'"); } long iter(){ return OMEGA.getScene()->iter;} int subStep(){ return OMEGA.getScene()->subStep; } bool subStepping_get(){ return OMEGA.getScene()->subStepping; } void subStepping_set(bool val){ OMEGA.getScene()->subStepping=val; } double time(){return OMEGA.getScene()->time;} double realTime(){ return OMEGA.getRealTime(); } double speed(){ return OMEGA.getScene()->speed; } double dt_get(){return OMEGA.getScene()->dt;} void dt_set(double dt){ Scene* scene=OMEGA.getScene().get(); // activate timestepper, if possible (throw exception if there is none) if(dt<0){ if(!scene->timeStepperActivate(true)) /* not activated*/ throw runtime_error("No TimeStepper found in O.engines."); } else { scene->dt=dt; } } bool dynDt_get(){return OMEGA.getScene()->timeStepperActive();} bool dynDt_set(bool activate){if(!OMEGA.getScene()->timeStepperActivate(activate)) /* not activated*/ throw runtime_error("No TimeStepper found in O.engines."); return true;} bool dynDtAvailable_get(){ return OMEGA.getScene()->timeStepperPresent(); } long stopAtIter_get(){return OMEGA.getScene()->stopAtIter; } void stopAtIter_set(long s){OMEGA.getScene()->stopAtIter=s; } Real stopAtTime_get(){return OMEGA.getScene()->stopAtTime; } void stopAtTime_set(long s){OMEGA.getScene()->stopAtTime=s; } bool timingEnabled_get(){return TimingInfo::enabled;} void timingEnabled_set(bool enabled){TimingInfo::enabled=enabled;} // deprecated: unsigned long forceSyncCount_get(){ return OMEGA.getScene()->forces.syncCount;} void forceSyncCount_set(unsigned long count){ OMEGA.getScene()->forces.syncCount=count;} void run(long int numIter=-1,bool doWait=false){ Scene* scene=OMEGA.getScene().get(); if(numIter>0) scene->stopAtIter=scene->iter+numIter; OMEGA.run(); // timespec t1,t2; t1.tv_sec=0; t1.tv_nsec=40000000; /* 40 ms */ // while(!OMEGA.isRunning()) nanosleep(&t1,&t2); // wait till we start, so that calling wait() immediately afterwards doesn't return immediately LOG_DEBUG("RUN"<<((scene->stopAtIter-scene->iter)>0?string(" ("+boost::lexical_cast(scene->stopAtIter-scene->iter)+" to go)"):string(""))<<"!"); if(doWait) wait(); } void pause(){Py_BEGIN_ALLOW_THREADS; OMEGA.pause(); Py_END_ALLOW_THREADS; LOG_DEBUG("PAUSE!");} void step() { if(OMEGA.isRunning()) throw runtime_error("Called O.step() while simulation is running."); OMEGA.getScene()->moveToNextTimeStep(); /* LOG_DEBUG("STEP!"); run(1); wait(); */ } void wait(){ if(OMEGA.isRunning()){LOG_DEBUG("WAIT!");} else return; timespec t1,t2; t1.tv_sec=0; t1.tv_nsec=40000000; /* 40 ms */ Py_BEGIN_ALLOW_THREADS; while(OMEGA.isRunning()) nanosleep(&t1,&t2); Py_END_ALLOW_THREADS; if(!OMEGA.simulationLoop->workerThrew) return; LOG_ERROR("Simulation error encountered."); OMEGA.simulationLoop->workerThrew=false; throw OMEGA.simulationLoop->workerException; } bool isRunning(){ return OMEGA.isRunning(); } py::object get_filename(){ string f=OMEGA.sceneFile; if(f.size()>0) return py::object(f); return py::object();} void load(std::string fileName,bool quiet=false) { Py_BEGIN_ALLOW_THREADS; OMEGA.stop(); Py_END_ALLOW_THREADS; OMEGA.loadSimulation(fileName,quiet); OMEGA.createSimulationLoop(); mapLabeledEntitiesToVariables(); } void reload(bool quiet=false){ load(OMEGA.sceneFile,quiet);} void saveTmp(string mark="", bool quiet=false){ save(":memory:"+mark,quiet);} void loadTmp(string mark="", bool quiet=false){ load(":memory:"+mark,quiet);} py::list lsTmp(){ py::list ret; typedef pair strstr; FOREACH(const strstr& sim,OMEGA.memSavedSimulations){ string mark=sim.first; boost::algorithm::replace_first(mark,":memory:",""); ret.append(mark); } return ret; } void tmpToFile(string mark, string filename){ if(OMEGA.memSavedSimulations.count(":memory:"+mark)==0) throw runtime_error("No memory-saved simulation named "+mark); boost::iostreams::filtering_ostream out; if(boost::algorithm::ends_with(filename,".bz2")) out.push(boost::iostreams::bzip2_compressor()); out.push(boost::iostreams::file_sink(filename)); if(!out.good()) throw runtime_error("Error while opening file `"+filename+"' for writing."); LOG_INFO("Saving :memory:"<iter=0; OMEGA.getScene()->time=0; OMEGA.timeInit(); } void switchScene(){ std::swap(OMEGA.scenes[OMEGA.currentSceneNb],OMEGA.sceneAnother); } void resetAllScenes(){Py_BEGIN_ALLOW_THREADS; OMEGA.stop(); Py_END_ALLOW_THREADS; OMEGA.resetAllScenes(); OMEGA.createSimulationLoop();} shared_ptr scene_get(){ return OMEGA.getScene(); } int addScene(){return OMEGA.addScene();} void switchToScene(int i){OMEGA.switchToScene(i);} string sceneToString(){ ostringstream oss; yade::ObjectIO::save(oss,"scene",OMEGA.getScene()); oss.flush(); return oss.str(); } void stringToScene(const string &sstring, string mark=""){ Py_BEGIN_ALLOW_THREADS; OMEGA.stop(); Py_END_ALLOW_THREADS; assertScene(); OMEGA.memSavedSimulations[":memory:"+mark]=sstring; OMEGA.sceneFile=":memory:"+mark; load(OMEGA.sceneFile,true); } int thisScene(){return OMEGA.currentSceneNb;} void save(std::string fileName,bool quiet=false){ assertScene(); OMEGA.saveSimulation(fileName,quiet); // OMEGA.sceneFile=fileName; // done in Omega::saveSimulation; } py::list miscParams_get(){ py::list ret; FOREACH(shared_ptr& s, OMEGA.getScene()->miscParams){ ret.append(s); } return ret; } void miscParams_set(vector > ss){ vector >& miscParams=OMEGA.getScene()->miscParams; miscParams.clear(); FOREACH(shared_ptr s, ss){ miscParams.push_back(s); } } vector > engines_get(void){assertScene(); Scene* scene=OMEGA.getScene().get(); return scene->_nextEngines.empty()?scene->engines:scene->_nextEngines;} void engines_set(const vector >& egs){ assertScene(); Scene* scene=OMEGA.getScene().get(); if(scene->subStep<0) scene->engines=egs; // not inside the engine loop right now, ok to update directly else scene->_nextEngines=egs; // inside the engine loop, update _nextEngines; O.engines picks that up automatically, and Scene::moveToNextTimestep will put them in place of engines at the start of the next loop mapLabeledEntitiesToVariables(); } // raw access to engines/_nextEngines, for debugging vector > currEngines_get(){ return OMEGA.getScene()->engines; } vector > nextEngines_get(){ return OMEGA.getScene()->_nextEngines; } pyBodyContainer bodies_get(void){assertScene(); return pyBodyContainer(OMEGA.getScene()->bodies); } pyInteractionContainer interactions_get(void){assertScene(); return pyInteractionContainer(OMEGA.getScene()->interactions); } pyForceContainer forces_get(void){return pyForceContainer(OMEGA.getScene());} pyMaterialContainer materials_get(void){return pyMaterialContainer(OMEGA.getScene());} py::list listChildClassesNonrecursive(const string& base){ py::list ret; for(map::const_iterator di=Omega::instance().getDynlibsDescriptor().begin();di!=Omega::instance().getDynlibsDescriptor().end();++di) if (Omega::instance().isInheritingFrom((*di).first,base)) ret.append(di->first); return ret; } bool isChildClassOf(const string& child, const string& base){ return (Omega::instance().isInheritingFrom_recursive(child,base)); } py::list plugins_get(){ const map& plugins=Omega::instance().getDynlibsDescriptor(); std::pair p; py::list ret; FOREACH(p, plugins) ret.append(p.first); return ret; } pyTags tags_get(void){assertScene(); return pyTags(OMEGA.getScene());} void interactionContainer_set(string clss){ Scene* rb=OMEGA.getScene().get(); if(rb->interactions->size()>0) throw std::runtime_error("Interaction container not empty, will not change its class."); shared_ptr ic=YADE_PTR_DYN_CAST(ClassFactory::instance().createShared(clss)); rb->interactions=ic; } string interactionContainer_get(string clss){ return OMEGA.getScene()->interactions->getClassName(); } void bodyContainer_set(string clss){ Scene* rb=OMEGA.getScene().get(); if(rb->bodies->size()>0) throw std::runtime_error("Body container not empty, will not change its class."); shared_ptr bc=YADE_PTR_DYN_CAST(ClassFactory::instance().createShared(clss)); rb->bodies=bc; } string bodyContainer_get(string clss){ return OMEGA.getScene()->bodies->getClassName(); } #ifdef YADE_OPENMP int numThreads_get(){ return omp_get_max_threads();} void numThreads_set(int n){ int bcn=OMEGA.getScene()->forces.getNumAllocatedThreads(); if(bcn cell_get(){ if(OMEGA.getScene()->isPeriodic) return OMEGA.getScene()->cell; return shared_ptr(); } bool periodic_get(void){ return OMEGA.getScene()->isPeriodic; } void periodic_set(bool v){ OMEGA.getScene()->isPeriodic=v; } shared_ptr energy_get(){ return OMEGA.getScene()->energy; } bool trackEnergy_get(void){ return OMEGA.getScene()->trackEnergy; } void trackEnergy_set(bool e){ OMEGA.getScene()->trackEnergy=e; } void disableGdb(){ signal(SIGSEGV,SIG_DFL); signal(SIGABRT,SIG_DFL); } void exitNoBacktrace(int status=0){ if(status==0) signal(SIGSEGV,termHandlerNormal); /* unset the handler that runs gdb and prints backtrace */ else signal(SIGSEGV,termHandlerError); // try to clean our mess Omega::instance().cleanupTemps(); // flush all streams (so that in case we crash at exit, unflushed buffers are not lost) fflush(NULL); // attempt exit exit(status); } void runEngine(const shared_ptr& e){ LOG_WARN("Omega().runEngine(): deprecated, use __call__ method of the engine instance directly instead; will be removed in the future."); e->scene=OMEGA.getScene().get(); e->action(); } std::string tmpFilename(){ return OMEGA.tmpFilename(); } }; BOOST_PYTHON_MODULE(wrapper) { py::scope().attr("__doc__")="Wrapper for c++ internals of yade."; YADE_SET_DOCSTRING_OPTS; py::enum_("AttrFlags") .value("noSave",yade::Attr::noSave) .value("readonly",yade::Attr::readonly) .value("triggerPostLoad",yade::Attr::triggerPostLoad) .value("noResize",yade::Attr::noResize) ; py::class_("Omega") .add_property("iter",&pyOmega::iter,"Get current step number") .add_property("subStep",&pyOmega::subStep,"Get the current subStep number (only meaningful if O.subStepping==True); -1 when outside the loop, otherwise either 0 (O.subStepping==False) or number of engine to be run (O.subStepping==True)") .add_property("subStepping",&pyOmega::subStepping_get,&pyOmega::subStepping_set,"Get/set whether subStepping is active.") .add_property("stopAtIter",&pyOmega::stopAtIter_get,&pyOmega::stopAtIter_set,"Get/set number of iteration after which the simulation will stop.") .add_property("stopAtTime",&pyOmega::stopAtTime_get,&pyOmega::stopAtTime_set,"Get/set time after which the simulation will stop.") .add_property("time",&pyOmega::time,"Return virtual (model world) time of the simulation.") .add_property("realtime",&pyOmega::realTime,"Return clock (human world) time the simulation has been running.") .add_property("speed",&pyOmega::speed,"Return current calculation speed [iter/sec].") .add_property("dt",&pyOmega::dt_get,&pyOmega::dt_set,"Current timestep (Δt) value.") .add_property("dynDt",&pyOmega::dynDt_get,&pyOmega::dynDt_set,"Whether a :yref:`TimeStepper` is used for dynamic Δt control. See :yref:`dt` on how to enable/disable :yref:`TimeStepper`.") .add_property("dynDtAvailable",&pyOmega::dynDtAvailable_get,"Whether a :yref:`TimeStepper` is amongst :yref:`O.engines`, activated or not.") .def("load",&pyOmega::load,(py::arg("file"),py::arg("quiet")=false),"Load simulation from file. The file should be :yref:`saved` in the same version of Yade, otherwise compatibility is not guaranteed.") .def("reload",&pyOmega::reload,(py::arg("quiet")=false),"Reload current simulation") .def("save",&pyOmega::save,(py::arg("file"),py::arg("quiet")=false),"Save current simulation to file (should be .xml or .xml.bz2 or .yade or .yade.gz). .xml files are bigger than .yade, but can be more or less easily (due to their size) opened and edited, e.g. with text editors. .bz2 and .gz correspond both to compressed versions. All saved files should be :yref:`loaded` in the same version of Yade, otherwise compatibility is not guaranteed.") .def("loadTmp",&pyOmega::loadTmp,(py::arg("mark")="",py::arg("quiet")=false),"Load simulation previously stored in memory by saveTmp. *mark* optionally distinguishes multiple saved simulations") .def("saveTmp",&pyOmega::saveTmp,(py::arg("mark")="",py::arg("quiet")=false),"Save simulation to memory (disappears at shutdown), can be loaded later with loadTmp. *mark* optionally distinguishes different memory-saved simulations.") .def("lsTmp",&pyOmega::lsTmp,"Return list of all memory-saved simulations.") .def("tmpToFile",&pyOmega::tmpToFile,(py::arg("fileName"),py::arg("mark")=""),"Save XML of :yref:`saveTmp`'d simulation into *fileName*.") .def("tmpToString",&pyOmega::tmpToString,(py::arg("mark")=""),"Return XML of :yref:`saveTmp`'d simulation as string.") .def("run",&pyOmega::run,(py::arg("nSteps")=-1,py::arg("wait")=false),"Run the simulation. *nSteps* how many steps to run, then stop (if positive); *wait* will cause not returning to python until simulation will have stopped.") .def("pause",&pyOmega::pause,"Stop simulation execution. (May be called from within the loop, and it will stop after the current step).") .def("step",&pyOmega::step,"Advance the simulation by one step. Returns after the step will have finished.") .def("wait",&pyOmega::wait,"Don't return until the simulation will have been paused. (Returns immediately if not running).") .add_property("running",&pyOmega::isRunning,"Whether background thread is currently running a simulation.") .add_property("filename",&pyOmega::get_filename,"Filename under which the current simulation was saved (None if never saved).") .def("reset",&pyOmega::reset,"Reset simulations completely (including another scenes!).") .def("resetThisScene",&pyOmega::resetThisScene,"Reset current scene.") .def("resetCurrentScene",&pyOmega::resetCurrentScene,"Reset current scene.") .def("resetAllScenes",&pyOmega::resetAllScenes,"Reset all scenes.") .def("addScene",&pyOmega::addScene,"Add new scene to Omega, returns its number") .def("switchToScene",&pyOmega::switchToScene,"Switch to defined scene. Default scene has number 0, other scenes have to be created by addScene method.") .def("switchScene",&pyOmega::switchScene,"Switch to alternative simulation (while keeping the old one). Calling the function again switches back to the first one. Note that most variables from the first simulation will still refer to the first simulation even after the switch\n(e.g. b=O.bodies[4]; O.switchScene(); [b still refers to the body in the first simulation here])") .add_property("thisScene",&pyOmega::thisScene,"Return current scene's id.") .def("sceneToString",&pyOmega::sceneToString,"Return the entire scene as a string. Equivalent to using O.save(...) except that the scene goes to a string instead of a file. (see also stringToScene())") .def("stringToScene",&pyOmega::stringToScene,(py::arg("mark")=""),"Load simulation from a string passed as argument (see also sceneToString).") .def("labeledEngine",&pyOmega::labeled_engine_get,"Return instance of engine/functor with the given label. This function shouldn't be called by the user directly; every ehange in O.engines will assign respective global python variables according to labels.\n\nFor example:\n\n\t *O.engines=[InsertionSortCollider(label='collider')]*\n\n\t *collider.nBins=5 # collider has become a variable after assignment to O.engines automatically*") .def("resetTime",&pyOmega::resetTime,"Reset simulation time: step number, virtual and real time. (Doesn't touch anything else, including timings).") .def("plugins",&pyOmega::plugins_get,"Return list of all plugins registered in the class factory.") .def("_sceneObj",&pyOmega::scene_get,"Return the :yref:`scene ` object. Debugging only, all (or most) :yref:`Scene` functionality is proxies through :yref:`Omega`.") .add_property("engines",&pyOmega::engines_get,&pyOmega::engines_set,"List of engines in the simulation (corresponds to Scene::engines in C++ source code).") .add_property("_currEngines",&pyOmega::currEngines_get,"Currently running engines; debugging only!") .add_property("_nextEngines",&pyOmega::nextEngines_get,"Engines for the next step, if different from the current ones, otherwise empty; debugging only!") .add_property("miscParams",&pyOmega::miscParams_get,&pyOmega::miscParams_set,"MiscParams in the simulation (Scene::mistParams), usually used to save serializables that don't fit anywhere else, like GL functors") .add_property("bodies",&pyOmega::bodies_get,"Bodies in the current simulation (container supporting index access by id and iteration)") .add_property("interactions",&pyOmega::interactions_get,"Access to :yref:`interactions` of simulation, by using \n\n#. id's of both :yref:`Bodies` of the interactions, e.g. ``O.interactions[23,65]``\n#. iteraction over the whole container::\n\n\tfor i in O.interactions: print i.id1,i.id2\n\n.. note::\n\tIteration silently skips interactions that are not :yref:`real`.") .add_property("materials",&pyOmega::materials_get,"Shared materials; they can be accessed by id or by label") .add_property("forces",&pyOmega::forces_get,":yref:`ForceContainer` (forces, torques, displacements) in the current simulation.") .add_property("energy",&pyOmega::energy_get,":yref:`EnergyTracker` of the current simulation. (meaningful only with :yref:`O.trackEnergy`)") .add_property("trackEnergy",&pyOmega::trackEnergy_get,&pyOmega::trackEnergy_set,"When energy tracking is enabled or disabled in this simulation.") .add_property("tags",&pyOmega::tags_get,"Tags (string=string dictionary) of the current simulation (container supporting string-index access/assignment)") .def("childClassesNonrecursive",&pyOmega::listChildClassesNonrecursive,"Return list of all classes deriving from given class, as registered in the class factory") .def("isChildClassOf",&pyOmega::isChildClassOf,"Tells whether the first class derives from the second one (both given as strings).") .add_property("timingEnabled",&pyOmega::timingEnabled_get,&pyOmega::timingEnabled_set,"Globally enable/disable timing services (see documentation of the :yref:`timing module`).") .add_property("forceSyncCount",&pyOmega::forceSyncCount_get,&pyOmega::forceSyncCount_set,"Counter for number of syncs in ForceContainer, for profiling purposes.") .add_property("numThreads",&pyOmega::numThreads_get /* ,&pyOmega::numThreads_set*/ ,"Get maximum number of threads openMP can use.") .add_property("cell",&pyOmega::cell_get,"Periodic cell of the current scene (None if the scene is aperiodic).") .add_property("periodic",&pyOmega::periodic_get,&pyOmega::periodic_set,"Get/set whether the scene is periodic or not (True/False).") .def("exitNoBacktrace",&pyOmega::exitNoBacktrace,(py::arg("status")=0),"Disable SEGV handler and exit, optionally with given status number.") .def("disableGdb",&pyOmega::disableGdb,"Revert SEGV and ABRT handlers to system defaults.") .def("runEngine",&pyOmega::runEngine,"Run given engine exactly once; simulation time, step number etc. will not be incremented (use only if you know what you do).") .def("tmpFilename",&pyOmega::tmpFilename,"Return unique name of file in temporary directory which will be deleted when yade exits.") ; py::class_("TagsWrapper","Container emulating dictionary semantics for accessing tags associated with simulation. Tags are accesed by strings.",py::init()) .def("__getitem__",&pyTags::getItem) .def("__setitem__",&pyTags::setItem) .def("keys",&pyTags::keys) .def("has_key",&pyTags::hasKey); py::class_("BodyContainer",py::init()) .def("__getitem__",&pyBodyContainer::pyGetitem) .def("__len__",&pyBodyContainer::length) .def("__iter__",&pyBodyContainer::pyIter) .def("append",&pyBodyContainer::append,"Append one Body instance, return its id.") .def("append",&pyBodyContainer::appendList,"Append list of Body instance, return list of ids") .def("appendClumped",&pyBodyContainer::appendClump,(py::arg("discretization")=0),"Append given list of bodies as a clump (rigid aggregate); returns a tuple of ``(clumpId,[memberId1,memberId2,...])``. Clump masses and inertia are adapted automatically (for details see :yref:`clump()`).") .def("clump",&pyBodyContainer::clump,(py::arg("discretization")=0),"Clump given bodies together (creating a rigid aggregate); returns ``clumpId``. Clump masses and inertia are adapted automatically when discretization>0. If clump members are overlapping this is done by integration/summation over mass points using a regular grid of cells (grid cells length is defined as $R_{min}/discretization$, where $R_{min}$ is minimum clump member radius). For non-overlapping members inertia of the clump is the sum of inertias from members. If discretization<=0 sum of inertias from members is used (faster, but inaccurate).") .def("updateClumpProperties",&pyBodyContainer::updateClumpProperties,(py::arg("excludeList")=py::list(),py::arg("discretization")=5),"Manually force Yade to update clump properties mass, volume and inertia (for details of 'discretization' value see :yref:`clump()`). Can be used, when clumps are modified or erased during a simulation. Clumps can be excluded from the calculation by giving a list of ids: *O.bodies.updateProperties([ids])*.") .def("deleteClumpMember", &pyBodyContainer::deleteClumpMember,"Erase clump member.") //FIXME .def("deleteClumpBody", &pyBodyContainer::deleteClumpBody,"Erase clump member.")//FIXME .def("addToClump",&pyBodyContainer::addToClump,(py::arg("discretization")=0),"Add body b (or a list of bodies) to an existing clump c. c must be clump and b may not be a clump member of c. Clump masses and inertia are adapted automatically (for details see :yref:`clump()`).\n\nSee :ysrc:`examples/clumps/addToClump-example.py` for an example script.\n\n.. note:: If b is a clump itself, then all members will be added to c and b will be deleted. If b is a clump member of clump d, then all members from d will be added to c and d will be deleted. If you need to add just clump member b, :yref:`release` this member from d first.") .def("releaseFromClump",&pyBodyContainer::releaseFromClump,(py::arg("discretization")=0),"Release body b from clump c. b must be a clump member of c. Clump masses and inertia are adapted automatically (for details see :yref:`clump()`).\n\nSee :ysrc:`examples/clumps/releaseFromClump-example.py` for an example script.\n\n.. note:: If c contains only 2 members b will not be released and a warning will appear. In this case clump c should be :yref:`erased`.") .def("replaceByClumps",&pyBodyContainer::replaceByClumps,(py::arg("discretization")=0),"Replace spheres by clumps using a list of clump templates and a list of amounts; returns a list of tuples: ``[(clumpId1,[memberId1,memberId2,...]),(clumpId2,[memberId1,memberId2,...]),...]``. A new clump will have the same volume as the sphere, that was replaced. Clump masses and inertia are adapted automatically (for details see :yref:`clump()`). \n\n\t *O.bodies.replaceByClumps( [utils.clumpTemplate([1,1],[.5,.5])] , [.9] ) #will replace 90 % of all standalone spheres by 'dyads'*\n\nSee :ysrc:`examples/clumps/replaceByClumps-example.py` for an example script.") .def("getRoundness",&pyBodyContainer::getRoundness,(py::arg("excludeList")=py::list()),"Returns roundness coefficient RC = R2/R1. R1 is the equivalent sphere radius of a clump. R2 is the minimum radius of a sphere, that imbeds the clump. If just spheres are present RC = 1. If clumps are present 0 < RC < 1. Bodies can be excluded from the calculation by giving a list of ids: *O.bodies.getRoundness([ids])*.\n\nSee :ysrc:`examples/clumps/replaceByClumps-example.py` for an example script.") .def("clear", &pyBodyContainer::clear,"Remove all bodies (interactions not checked)") .def("erase", &pyBodyContainer::erase,(py::arg("eraseClumpMembers")=0),"Erase body with the given id; all interaction will be deleted by InteractionLoop in the next step. If a clump is erased use *O.bodies.erase(clumpId,True)* to erase the clump AND its members.") .def("replace",&pyBodyContainer::replace); py::class_("BodyIterator",py::init()) .def("__iter__",&pyBodyIterator::pyIter) .def("next",&pyBodyIterator::pyNext); py::class_("InteractionContainer","Access to :yref:`interactions` of simulation, by using \n\n#. id's of both :yref:`Bodies` of the interactions, e.g. ``O.interactions[23,65]``\n#. iteraction over the whole container::\n\n\tfor i in O.interactions: print i.id1,i.id2\n\n.. note::\n\tIteration silently skips interactions that are not :yref:`real`.",py::init()) .def("__iter__",&pyInteractionContainer::pyIter) .def("__getitem__",&pyInteractionContainer::pyGetitem) .def("__len__",&pyInteractionContainer::len) .def("has",&pyInteractionContainer::has,"Tell if a pair of ids corresponds to an existing interaction (real or not)") .def("countReal",&pyInteractionContainer::countReal,"Return number of interactions that are \"real\", i.e. they have phys and geom.") .def("nth",&pyInteractionContainer::pyNth,"Return n-th interaction from the container (usable for picking random interaction).") .def("withBody",&pyInteractionContainer::withBody,"Return list of real interactions of given body.") .def("withBodyAll",&pyInteractionContainer::withBodyAll,"Return list of all (real as well as non-real) interactions of given body.") .def("all",&pyInteractionContainer::getAll,(py::arg("onlyReal")=false),"Return list of all interactions. Virtual interaction are filtered out if onlyReal=True, else (default) it dumps the full content.") .def("eraseNonReal",&pyInteractionContainer::eraseNonReal,"Erase all interactions that are not :yref:`real `.") .def("erase",&pyInteractionContainer::erase,"Erase one interaction, given by id1, id2 (internally, ``requestErase`` is called -- the interaction might still exist as potential, if the :yref:`Collider` decides so).") .add_property("serializeSorted",&pyInteractionContainer::serializeSorted_get,&pyInteractionContainer::serializeSorted_set) .def("clear",&pyInteractionContainer::clear,"Remove all interactions, and invalidate persistent collider data (if the collider supports it)."); py::class_("InteractionIterator",py::init()) .def("__iter__",&pyInteractionIterator::pyIter) .def("next",&pyInteractionIterator::pyNext); py::class_("ForceContainer",py::init()) .def("f",&pyForceContainer::force_get,(py::arg("id"),py::arg("sync")=false),"Force applied on body. For clumps in openMP, synchronize the force container with sync=True, else the value will be wrong.") .def("t",&pyForceContainer::torque_get,(py::arg("id"),py::arg("sync")=false),"Torque applied on body. For clumps in openMP, synchronize the force container with sync=True, else the value will be wrong.") .def("m",&pyForceContainer::torque_get,(py::arg("id"),py::arg("sync")=false),"Deprecated alias for t (torque).") .def("move",&pyForceContainer::move_get,(py::arg("id")),"Displacement applied on body.") .def("rot",&pyForceContainer::rot_get,(py::arg("id")),"Rotation applied on body.") .def("addF",&pyForceContainer::force_add,(py::arg("id"),py::arg("f"),py::arg("permanent")=false),"Apply force on body (accumulates). The force applies for one iteration, then it is reset by ForceResetter. \n # permanent parameter is deprecated, instead of addF(...,permanent=True) use setPermF(...).") .def("addT",&pyForceContainer::torque_add,(py::arg("id"),py::arg("t"),py::arg("permanent")=false),"Apply torque on body (accumulates). The torque applies for one iteration, then it is reset by ForceResetter. \n # permanent parameter is deprecated, instead of addT(...,permanent=True) use setPermT(...).") .def("permF",&pyForceContainer::permForce_get,(py::arg("id")),"read the value of permanent force on body (set with setPermF()).") .def("permT",&pyForceContainer::permTorque_get,(py::arg("id")),"read the value of permanent torque on body (set with setPermT()).") .def("setPermF",&pyForceContainer::permForce_set, "set the value of permanent force on body.") .def("setPermT",&pyForceContainer::permTorque_set,"set the value of permanent torque on body.") .def("addMove",&pyForceContainer::move_add,(py::arg("id"),py::arg("m")),"Apply displacement on body (accumulates).") .def("addRot",&pyForceContainer::rot_add,(py::arg("id"),py::arg("r")),"Apply rotation on body (accumulates).") .def("reset",&pyForceContainer::reset,(py::arg("resetAll")=true),"Reset the force container, including user defined permanent forces/torques. resetAll=False will keep permanent forces/torques unchanged.") .def("getPermForceUsed",&pyForceContainer::getPermForceUsed,"Check wether permanent forces are present.") .add_property("syncCount",&pyForceContainer::syncCount_get,&pyForceContainer::syncCount_set,"Number of synchronizations of ForceContainer (cummulative); if significantly higher than number of steps, there might be unnecessary syncs hurting performance.") ; py::class_("MaterialContainer","Container for :yref:`Materials`. A material can be accessed using \n\n #. numerical index in range(0,len(cont)), like cont[2]; \n #. textual label that was given to the material, like cont['steel']. This etails traversing all materials and should not be used frequently.",py::init()) .def("append",&pyMaterialContainer::append,"Add new shared :yref:`Material`; changes its id and return it.") .def("append",&pyMaterialContainer::appendList,"Append list of :yref:`Material` instances, return list of ids.") .def("index",&pyMaterialContainer::index,"Return id of material, given its label.") .def("__getitem__",&pyMaterialContainer::getitem_id) .def("__getitem__",&pyMaterialContainer::getitem_label) .def("__len__",&pyMaterialContainer::len); py::class_("STLImporter").def("ymport",&STLImporter::import); ////////////////////////////////////////////////////////////// ///////////// proxyless wrappers Serializable().pyRegisterClass(py::scope()); py::class_, boost::noncopyable >("TimingDeltas").add_property("data",&TimingDeltas::pyData,"Get timing data as list of tuples (label, execTime[nsec], execCount) (one tuple per checkpoint)").def("reset",&TimingDeltas::reset,"Reset timing information"); py::scope().attr("O")=pyOmega(); } trunk-2018.02b/py/ymport.py000066400000000000000000000427351324306050200155350ustar00rootroot00000000000000""" Import geometry from various formats ('import' is python keyword, hence the name 'ymport'). """ from yade.wrapper import * from yade import utils from minieigen import * def textExt(fileName,format='x_y_z_r',shift=Vector3.Zero,scale=1.0,attrs=[],**kw): """Load sphere coordinates from file in specific format, returns a list of corresponding bodies; that may be inserted to the simulation with O.bodies.append(). :param str filename: file name :param str format: the name of output format. Supported `x_y_z_r`(default), `x_y_z_r_matId`, 'x_y_z_r_attrs' :param [float,float,float] shift: [X,Y,Z] parameter moves the specimen. :param float scale: factor scales the given data. :param list attrs: attrs read from file if export.textExt(format='x_y_z_r_attrs') were used ('passed by refernece' style) :param \*\*kw: (unused keyword arguments) is passed to :yref:`yade.utils.sphere` :returns: list of spheres. Lines starting with # are skipped """ infile = open(fileName,"r") lines = infile.readlines() infile.close() ret=[] for line in lines: data = line.split() if (data[0] == "#format"): format=data[1] continue elif (data[0][0] == "#"): continue if (format=='x_y_z_r'): pos = Vector3(float(data[0]),float(data[1]),float(data[2])) ret.append(utils.sphere(shift+scale*pos,scale*float(data[3]),**kw)) elif (format=='x_y_z_r_matId'): pos = Vector3(float(data[0]),float(data[1]),float(data[2])) ret.append(utils.sphere(shift+scale*pos,scale*float(data[3]),material=int(data[4]),**kw)) elif (format=='id_x_y_z_r_matId'): pos = Vector3(float(data[1]),float(data[2]),float(data[3])) ret.append(utils.sphere(shift+scale*pos,scale*float(data[4]),material=int(data[5]),**kw)) elif (format=='x_y_z_r_attrs'): pos = Vector3(float(data[0]),float(data[1]),float(data[2])) s = utils.sphere(shift+scale*pos,scale*float(data[3]),**kw) ret.append(s) attrs.append(data[4:]) else: raise RuntimeError("Please, specify a correct format output!"); return ret def textClumps(fileName,shift=Vector3.Zero,discretization=0,orientation=Quaternion((0,1,0),0.0),scale=1.0,**kw): """Load clumps-members from file, insert them to the simulation. :param str filename: file name :param str format: the name of output format. Supported `x_y_z_r`(default), `x_y_z_r_clumpId` :param [float,float,float] shift: [X,Y,Z] parameter moves the specimen. :param float scale: factor scales the given data. :param \*\*kw: (unused keyword arguments) is passed to :yref:`yade.utils.sphere` :returns: list of spheres. Lines starting with # are skipped """ infile = open(fileName,"r") lines = infile.readlines() infile.close() ret=[] curClump=[] newClumpId = -1 for line in lines: data = line.split() if (data[0][0] == "#"): continue pos = orientation*Vector3(float(data[0]),float(data[1]),float(data[2])) if (newClumpId<0 or newClumpId==int(data[4])): idD = curClump.append(utils.sphere(shift+scale*pos,scale*float(data[3]),**kw)) newClumpId = int(data[4]) else: newClumpId = int(data[4]) ret.append(O.bodies.appendClumped(curClump,discretization=discretization)) curClump=[] idD = curClump.append(utils.sphere(shift+scale*pos,scale*float(data[3]),**kw)) if (len(curClump)<>0): ret.append(O.bodies.appendClumped(curClump,discretization=discretization)) # Set the mask to a clump the same as the first member of it for i in range(len(ret)): O.bodies[ret[i][0]].mask = O.bodies[ret[i][1][0]].mask return ret def text(fileName,shift=Vector3.Zero,scale=1.0,**kw): """Load sphere coordinates from file, returns a list of corresponding bodies; that may be inserted to the simulation with O.bodies.append(). :param string filename: file which has 4 colums [x, y, z, radius]. :param [float,float,float] shift: [X,Y,Z] parameter moves the specimen. :param float scale: factor scales the given data. :param \*\*kw: (unused keyword arguments) is passed to :yref:`yade.utils.sphere` :returns: list of spheres. Lines starting with # are skipped """ return textExt(fileName=fileName,format='x_y_z_r',shift=shift,scale=scale,**kw) def stl(file, dynamic=None,fixed=True,wire=True,color=None,highlight=False,noBound=False,material=-1): """ Import geometry from stl file, return list of created facets.""" imp = STLImporter() facets=imp.ymport(file) for b in facets: b.shape.color=color if color else utils.randomColor() b.shape.wire=wire b.shape.highlight=highlight pos=b.state.pos utils._commonBodySetup(b,0,Vector3(0,0,0),material=material,pos=pos,noBound=noBound,dynamic=dynamic,fixed=fixed) b.aspherical=False return facets def gts(meshfile,shift=Vector3.Zero,scale=1.0,**kw): """ Read given meshfile in gts format. :Parameters: `meshfile`: string name of the input file. `shift`: [float,float,float] [X,Y,Z] parameter moves the specimen. `scale`: float factor scales the given data. `**kw`: (unused keyword arguments) is passed to :yref:`yade.utils.facet` :Returns: list of facets. """ import gts,yade.pack surf=gts.read(open(meshfile)) surf.scale(scale,scale,scale) surf.translate(shift[0],shift[1],shift[2]) yade.pack.gtsSurface2Facets(surf,**kw) def gmsh(meshfile="file.mesh",shift=Vector3.Zero,scale=1.0,orientation=Quaternion((0,1,0),0.0),**kw): """ Imports geometry from .mesh file and creates facets. :Parameters: `shift`: [float,float,float] [X,Y,Z] parameter moves the specimen. `scale`: float factor scales the given data. `orientation`: quaternion orientation of the imported mesh `**kw`: (unused keyword arguments) is passed to :yref:`yade.utils.facet` :Returns: list of facets forming the specimen. mesh files can easily be created with `GMSH `_. Example added to :ysrc:`examples/packs/packs.py` Additional examples of mesh-files can be downloaded from http://www-roc.inria.fr/gamma/download/download.php """ infile = open(meshfile,"r") lines = infile.readlines() infile.close() nodelistVector3=[] elementlistVector3=[] # for deformable elements findVerticesString=0 while (lines[findVerticesString].split()[0]<>'Vertices'): #Find the string with the number of Vertices findVerticesString+=1 findVerticesString+=1 numNodes = int(lines[findVerticesString].split()[0]) for i in range(numNodes): nodelistVector3.append(Vector3(0.0,0.0,0.0)) id = 0 for line in lines[findVerticesString+1:numNodes+findVerticesString+1]: data = line.split() nodelistVector3[id] = orientation*Vector3(float(data[0])*scale,float(data[1])*scale,float(data[2])*scale)+shift id += 1 findTriangleString=findVerticesString+numNodes while (lines[findTriangleString].split()[0]<>'Triangles'): #Find the string with the number of Triangles findTriangleString+=1 findTriangleString+=1 numTriangles = int(lines[findTriangleString].split()[0]) triList = [] for i in range(numTriangles): triList.append([0,0,0,0]) tid = 0 for line in lines[findTriangleString+1:findTriangleString+numTriangles+1]: data = line.split() id1 = int(data[0])-1 id2 = int(data[1])-1 id3 = int(data[2])-1 triList[tid][0] = tid triList[tid][1] = id1 triList[tid][2] = id2 triList[tid][3] = id3 tid += 1 ret=[] for i in triList: a=nodelistVector3[i[1]] b=nodelistVector3[i[2]] c=nodelistVector3[i[3]] ret.append(utils.facet((nodelistVector3[i[1]],nodelistVector3[i[2]],nodelistVector3[i[3]]),**kw)) return ret def gengeoFile(fileName="file.geo",shift=Vector3.Zero,scale=1.0,orientation=Quaternion((0,1,0),0.0),**kw): """ Imports geometry from LSMGenGeo .geo file and creates spheres. Since 2012 the package is available in Debian/Ubuntu and known as python-demgengeo http://packages.qa.debian.org/p/python-demgengeo.html :Parameters: `filename`: string file which has 4 colums [x, y, z, radius]. `shift`: Vector3 Vector3(X,Y,Z) parameter moves the specimen. `scale`: float factor scales the given data. `orientation`: quaternion orientation of the imported geometry `**kw`: (unused keyword arguments) is passed to :yref:`yade.utils.sphere` :Returns: list of spheres. LSMGenGeo library allows one to create pack of spheres with given [Rmin:Rmax] with null stress inside the specimen. Can be useful for Mining Rock simulation. Example: :ysrc:`examples/packs/packs.py`, usage of LSMGenGeo library in :ysrc:`examples/test/genCylLSM.py`. * https://answers.launchpad.net/esys-particle/+faq/877 * http://www.access.edu.au/lsmgengeo_python_doc/current/pythonapi/html/GenGeo-module.html * https://svn.esscc.uq.edu.au/svn/esys3/lsm/contrib/LSMGenGeo/""" from yade.utils import sphere infile = open(fileName,"r") lines = infile.readlines() infile.close() numSpheres = int(lines[6].split()[0]) ret=[] for line in lines[7:numSpheres+7]: data = line.split() pos = orientation*Vector3(float(data[0]),float(data[1]),float(data[2])) ret.append(utils.sphere(shift+scale*pos,scale*float(data[3]),**kw)) return ret def gengeo(mntable,shift=Vector3.Zero,scale=1.0,**kw): """ Imports geometry from LSMGenGeo library and creates spheres. Since 2012 the package is available in Debian/Ubuntu and known as python-demgengeo http://packages.qa.debian.org/p/python-demgengeo.html :Parameters: `mntable`: mntable object, which creates by LSMGenGeo library, see example `shift`: [float,float,float] [X,Y,Z] parameter moves the specimen. `scale`: float factor scales the given data. `**kw`: (unused keyword arguments) is passed to :yref:`yade.utils.sphere` LSMGenGeo library allows one to create pack of spheres with given [Rmin:Rmax] with null stress inside the specimen. Can be useful for Mining Rock simulation. Example: :ysrc:`examples/packs/packs.py`, usage of LSMGenGeo library in :ysrc:`examples/test/genCylLSM.py`. * https://answers.launchpad.net/esys-particle/+faq/877 * http://www.access.edu.au/lsmgengeo_python_doc/current/pythonapi/html/GenGeo-module.html * https://svn.esscc.uq.edu.au/svn/esys3/lsm/contrib/LSMGenGeo/""" try: from GenGeo import MNTable3D,Sphere except ImportError: from gengeo import MNTable3D,Sphere ret=[] sphereList=mntable.getSphereListFromGroup(0) for i in range(0, len(sphereList)): r=sphereList[i].Radius() c=sphereList[i].Centre() ret.append(utils.sphere([shift[0]+scale*float(c.X()),shift[1]+scale*float(c.Y()),shift[2]+scale*float(c.Z())],scale*float(r),**kw)) return ret def unv(fileName,shift=(0,0,0),scale=1.0,returnConnectivityTable=False,**kw): """ Import geometry from unv file, return list of created facets. :param string fileName: name of unv file :param (float,float,float)|Vector3 shift: (X,Y,Z) parameter moves the specimen. :param float scale: factor scales the given data. :param \*\*kw: (unused keyword arguments) is passed to :yref:`yade.utils.facet` :param bool returnConnectivityTable: if True, apart from facets returns also nodes (list of (x,y,z) nodes coordinates) and elements (list of (id1,id2,id3) element nodes ids). If False (default), returns only facets unv files are mainly used for FEM analyses (are used by `OOFEM `_ and `Abaqus `_), but triangular elements can be imported as facets. These files cen be created e.g. with open-source free software `Salome `_. Example: :ysrc:`examples/test/unv-read/unvRead.py`.""" class UNVReader: # class used in ymport.unv function # reads and evaluate given unv file and extracts all triangles # can be extended to read tetrahedrons as well def __init__(self,fileName,shift=(0,0,0),scale=1.0,returnConnectivityTable=False,**kw): self.shift = shift self.scale = scale self.unvFile = open(fileName,'r') self.flag = 0 self.line = self.unvFile.readline() self.lineSplit = self.line.split() self.nodes = [] self.elements = [] self.read(**kw) def readLine(self): self.line = self.unvFile.readline() self.lineSplit = self.line.split() def read(self,**kw): while self.line: self.evalLine() self.line = self.unvFile.readline() self.unvFile.close() self.createFacets(**kw) def evalLine(self): self.lineSplit = self.line.split() if len(self.lineSplit) <= 1: # eval special unv format if self.lineSplit[0] == '-1': pass elif self.lineSplit[0] == '2411': self.flag = 1; # nodes elif self.lineSplit[0] == '2412': self.flag = 2; # edges (lines) else: self.flag = 4; # volume elements or other, not interesting for us (at least yet) elif self.flag == 1: self.evalNodes() elif self.flag == 2: self.evalEdge() elif self.flag == 3: self.evalFacet() #elif self.flag == 4: self.evalGroup() def evalNodes(self): self.readLine() self.nodes.append(( self.shift[0]+self.scale*float(self.lineSplit[0]), self.shift[1]+self.scale*float(self.lineSplit[1]), self.shift[2]+self.scale*float(self.lineSplit[2]))) def evalEdge(self): if self.lineSplit[1]=='41': self.flag = 3 self.evalFacet() else: self.readLine() self.readLine() def evalFacet(self): if self.lineSplit[1]=='41': # triangle self.readLine() self.elements.append(( int(self.lineSplit[0])-1, int(self.lineSplit[1])-1, int(self.lineSplit[2])-1)) else: # is not triangle self.readLine() self.flag = 4 # can be added function to handle tetrahedrons def createFacets(self,**kw): self.facets = [utils.facet(tuple(self.nodes[i] for i in e),**kw) for e in self.elements] # unvReader = UNVReader(fileName,shift,scale,returnConnectivityTable,**kw) if returnConnectivityTable: return unvReader.facets, unvReader.nodes, unvReader.elements return unvReader.facets def iges(fileName,shift=(0,0,0),scale=1.0,returnConnectivityTable=False,**kw): """ Import triangular mesh from .igs file, return list of created facets. :param string fileName: name of iges file :param (float,float,float)|Vector3 shift: (X,Y,Z) parameter moves the specimen. :param float scale: factor scales the given data. :param \*\*kw: (unused keyword arguments) is passed to :yref:`yade.utils.facet` :param bool returnConnectivityTable: if True, apart from facets returns also nodes (list of (x,y,z) nodes coordinates) and elements (list of (id1,id2,id3) element nodes ids). If False (default), returns only facets """ nodes,elems = [],[] f = open(fileName) for line in f: if line.startswith('134,'): # read nodes coordinates ls = line.split(',') v = Vector3( float(ls[1])*scale + shift[0], float(ls[2])*scale + shift[1], float(ls[3])*scale + shift[2] ) nodes.append(v) if line.startswith('136,'): # read elements ls = line.split(',') i1,i2,i3 = int(ls[3])/2, int(ls[4])/2, int(ls[5])/2 # the numbering of nodes is 1,3,5,7,..., hence this int(ls[*])/2 elems.append( (i1,i2,i3) ) facets = [utils.facet( ( nodes[e[0]], nodes[e[1]], nodes[e[2]] ), **kw) for e in elems] if returnConnectivityTable: return facets, nodes, elems return facets def ele(nodeFileName,eleFileName,shift=(0,0,0),scale=1.0,**kw): """ Import tetrahedral mesh from .ele file, return list of created tetrahedrons. :param string nodeFileName: name of .node file :param string eleFileName: name of .ele file :param (float,float,float)|Vector3 shift: (X,Y,Z) parameter moves the specimen. :param float scale: factor scales the given data. :param \*\*kw: (unused keyword arguments) is passed to :yref:`yade.utils.polyhedron` """ f = open(nodeFileName) line = f.readline() while line.startswith('#'): line = f.readline() ls = line.split() nVertices = int(ls[0]) if int(ls[1])!=3: raise RuntimeError, "wrong .node file, number of dimensions should be 3" vertices = [None for i in xrange(nVertices)] shift = Vector3(shift) for i in xrange(nVertices): line = f.readline() while line.startswith('#'): line = f.readline() ls = line.split() if not ls: continue v = shift + scale*Vector3(tuple(float(ls[j]) for j in (1,2,3))) vertices[int(ls[0])-1] = v f.close() # f = open(eleFileName) line = f.readline() while line.startswith('#'): line = f.readline() ls = line.split() if int(ls[1])!=4: raise RuntimeError, "wrong .ele file, unsupported tetrahedra's number of nodes" nTetras = int(ls[0]) tetras = [None for i in xrange(nTetras)] for i in xrange(nTetras): ls = f.readline().split() tetras[int(ls[0])-1] = utils.polyhedron([vertices[int(ls[j])-1] for j in (1,2,3,4)],**kw) f.close() return tetras def textPolyhedra(fileName,material,shift=Vector3.Zero,scale=1.0,orientation=Quaternion((0,1,0),0.0),**kw): from yade import polyhedra_utils """Load polyhedra from a text file. :param str filename: file name :param [float,float,float] shift: [X,Y,Z] parameter moves the specimen. :param float scale: factor scales the given data. :param quaternion orientation: orientation of the imported polyhedra :param \*\*kw: (unused keyword arguments) is passed to :yref:`yade.polyhedra_utils.polyhedra` :returns: list of polyhedras. Lines starting with # are skipped """ infile = open(fileName,"r") lines = infile.readlines() infile.close() ret=[] i=-1 while (i < (len(lines)-1)): i+=1 line = lines[i] data = line.split() if (data[0][0] == "#"): continue if (len(data)!=3): raise RuntimeError("Check polyhedra input file! Number of parameters in the first line is not 3!"); else: vertLoad = [] ids = int(data[0]) verts = int(data[1]) surfs = int(data[2]) i+=1 for d in range(verts): dataV = lines[i].split() pos = orientation*Vector3(float(dataV[0])*scale,float(dataV[1])*scale,float(dataV[2])*scale)+shift vertLoad.append(pos) i+=1 polR = polyhedra_utils.polyhedra(material=material,v=vertLoad,**kw) if (polR != -1): ret.append(polR) i= i + surfs - 1 return ret trunk-2018.02b/scripts/000077500000000000000000000000001324306050200146555ustar00rootroot00000000000000trunk-2018.02b/scripts/README000066400000000000000000000001071324306050200155330ustar00rootroot00000000000000In this directory are placed scripts that help managing yade project. trunk-2018.02b/scripts/RPM_packaging/000077500000000000000000000000001324306050200173175ustar00rootroot00000000000000trunk-2018.02b/scripts/RPM_packaging/exclude-rpath-patch000066400000000000000000000056501324306050200231120ustar00rootroot00000000000000=== modified file 'SConstruct' --- SConstruct 2011-02-02 10:39:58 +0000 +++ SConstruct 2011-02-12 15:20:51 +0000 @@ -436,12 +436,12 @@ ### LINKER ## libs for all plugins env.Append(LIBS=[],SHLINKFLAGS=['-rdynamic']) -env.Append(LINKFLAGS=['-rdynamic','-Wl,-z,origin']) +env.Append(LINKFLAGS=['-rdynamic']) if not env['debug']: env.Append(SHLINKFLAGS=['-W,--strip-all']) # makes dynamic library loading easier (no LD_LIBRARY_PATH) and perhaps faster -env.Append(RPATH=runtimeLibDirs) +#env.Append(RPATH=runtimeLibDirs) # find already compiled but not yet installed libraries for linking env.Append(LIBPATH=instLibDirs) # this is if we link to libs that are installed, which is the case now === modified file 'core/main/main.py.in' --- core/main/main.py.in 2011-01-29 23:02:30 +0000 +++ core/main/main.py.in 2011-02-12 20:56:17 +0000 @@ -11,6 +11,16 @@ ## find available builds nonDebugLibDir=prefix+'/lib/yade'+suffix debugLibDir=nonDebugLibDir+'/dbg' + +try: + os.environ["LD_LIBRARY_PATH"] +except KeyError: + #UGLY!!! Need to change it. + os.environ["LD_LIBRARY_PATH"] = nonDebugLibDir + "/lib:" + nonDebugLibDir + "/plugins:" + nonDebugLibDir + "/py:" + nonDebugLibDir + "/py/gts:" + nonDebugLibDir + "/py/yade/:" + nonDebugLibDir + "/py/yade/qt" + debugLibDir + "/lib:" + debugLibDir + "/plugins:" + debugLibDir + "/py:" + debugLibDir + "/py/gts:" + debugLibDir + "/py/yade/:" + debugLibDir + "/py/yade/qt" + os.execve(sys.argv[0], sys.argv, os.environ) + + + hasDebug,hasNonDebug=os.path.exists(debugLibDir+'/py/yade/__init__.py'),os.path.exists(nonDebugLibDir+'/py/yade/__init__.py') if hasDebug and hasNonDebug: buildsAvailable='both non-debug and debug build' elif hasDebug and not hasNonDebug: buildsAvailable='debug build only' === modified file 'gui/SConscript' --- gui/SConscript 2010-10-12 12:28:40 +0000 +++ gui/SConscript 2011-02-12 15:09:00 +0000 @@ -11,7 +11,7 @@ env.File('qt4/SerializableEditor.py'), env.File('qt4/Inspector.py'), env.File('qt4/__init__.py'), - env.SharedLibrary('_GLViewer',['qt4/GLViewer.cpp','qt4/_GLViewer.cpp','qt4/OpenGLManager.cpp'],SHLIBPREFIX='',LIBS=env['LIBS']+[env['QGLVIEWER_LIB']]+linkPlugins(['PeriodicEngines']),RPATH=env['RPATH']+[env.Literal('\\$$ORIGIN/../../../gui')],CXX=env['QT4CXX'] if haveQt4Cxx else env['CXX'],CXXFLAGS=[f for f in env['CXXFLAGS'] if not f.startswith('-Q')] if haveQt4Cxx else env['CXXFLAGS']) + env.SharedLibrary('_GLViewer',['qt4/GLViewer.cpp','qt4/_GLViewer.cpp','qt4/OpenGLManager.cpp'],SHLIBPREFIX='',LIBS=env['LIBS']+[env['QGLVIEWER_LIB']]+linkPlugins(['PeriodicEngines']),CXX=env['QT4CXX'] if haveQt4Cxx else env['CXX'],CXXFLAGS=[f for f in env['CXXFLAGS'] if not f.startswith('-Q')] if haveQt4Cxx else env['CXXFLAGS']) ]) env.Command('qt4/img_rc.py','qt4/img.qrc','pyrcc4 -o $buildDir/gui/qt4/img_rc.py gui/qt4/img.qrc') env.Command('qt4/ui_controller.py','qt4/controller.ui','pyuic4 -o $buildDir/gui/qt4/ui_controller.py gui/qt4/controller.ui') trunk-2018.02b/scripts/RPM_packaging/exclude-rpath-patch-060000066400000000000000000000056641324306050200234220ustar00rootroot00000000000000=== modified file 'SConstruct' --- SConstruct 2011-02-02 13:41:00 +0000 +++ SConstruct 2011-02-12 21:12:28 +0000 @@ -480,12 +480,12 @@ ## libs for all plugins if not env['mono']: env.Append(LIBS=[],SHLINKFLAGS=['-rdynamic']) - env.Append(LINKFLAGS=['-rdynamic','-Wl,-z,origin']) + env.Append(LINKFLAGS=['-rdynamic']) if not env['debug']: env.Append(SHLINKFLAGS=['-W,--strip-all']) # makes dynamic library loading easier (no LD_LIBRARY_PATH) and perhaps faster -env.Append(RPATH=runtimeLibDirs) +#env.Append(RPATH=runtimeLibDirs) # find already compiled but not yet installed libraries for linking env.Append(LIBPATH=instLibDirs) # this is if we link to libs that are installed, which is the case now === modified file 'core/main/main.py.in' --- core/main/main.py.in 2011-01-22 21:30:51 +0000 +++ core/main/main.py.in 2011-02-12 21:11:07 +0000 @@ -11,6 +11,16 @@ ## find available builds nonDebugLibDir=prefix+'/lib/yade'+suffix debugLibDir=nonDebugLibDir+'/dbg' + +try: + os.environ["LD_LIBRARY_PATH"] +except KeyError: + #UGLY!!! Need to change it. + os.environ["LD_LIBRARY_PATH"] = nonDebugLibDir + "/lib:" + nonDebugLibDir + "/plugins:" + nonDebugLibDir + "/py:" + nonDebugLibDir + "/py/gts:" + nonDebugLibDir + "/py/yade/:" + nonDebugLibDir + "/py/yade/qt" + debugLibDir + "/lib:" + debugLibDir + "/plugins:" + debugLibDir + "/py:" + debugLibDir + "/py/gts:" + debugLibDir + "/py/yade/:" + debugLibDir + "/py/yade/qt" + os.execve(sys.argv[0], sys.argv, os.environ) + + + hasDebug,hasNonDebug=os.path.exists(debugLibDir+'/py/yade/__init__.py'),os.path.exists(nonDebugLibDir+'/py/yade/__init__.py') if hasDebug and hasNonDebug: buildsAvailable='both non-debug and debug build' elif hasDebug and not hasNonDebug: buildsAvailable='debug build only' === modified file 'gui/SConscript' --- gui/SConscript 2010-10-12 12:28:40 +0000 +++ gui/SConscript 2011-02-12 21:11:07 +0000 @@ -11,7 +11,7 @@ env.File('qt4/SerializableEditor.py'), env.File('qt4/Inspector.py'), env.File('qt4/__init__.py'), - env.SharedLibrary('_GLViewer',['qt4/GLViewer.cpp','qt4/_GLViewer.cpp','qt4/OpenGLManager.cpp'],SHLIBPREFIX='',LIBS=env['LIBS']+[env['QGLVIEWER_LIB']]+linkPlugins(['PeriodicEngines']),RPATH=env['RPATH']+[env.Literal('\\$$ORIGIN/../../../gui')],CXX=env['QT4CXX'] if haveQt4Cxx else env['CXX'],CXXFLAGS=[f for f in env['CXXFLAGS'] if not f.startswith('-Q')] if haveQt4Cxx else env['CXXFLAGS']) + env.SharedLibrary('_GLViewer',['qt4/GLViewer.cpp','qt4/_GLViewer.cpp','qt4/OpenGLManager.cpp'],SHLIBPREFIX='',LIBS=env['LIBS']+[env['QGLVIEWER_LIB']]+linkPlugins(['PeriodicEngines']),CXX=env['QT4CXX'] if haveQt4Cxx else env['CXX'],CXXFLAGS=[f for f in env['CXXFLAGS'] if not f.startswith('-Q')] if haveQt4Cxx else env['CXXFLAGS']) ]) env.Command('qt4/img_rc.py','qt4/img.qrc','pyrcc4 -o $buildDir/gui/qt4/img_rc.py gui/qt4/img.qrc') env.Command('qt4/ui_controller.py','qt4/controller.ui','pyuic4 -o $buildDir/gui/qt4/ui_controller.py gui/qt4/controller.ui') trunk-2018.02b/scripts/RPM_packaging/yade.spec000066400000000000000000000077031324306050200211240ustar00rootroot00000000000000Name: yade-0.60 Version: 0.60 Release: 1%{?dist} Summary: Platform for discrete element modeling Group: Applications/Engineering License: GPLv2 URL: https://launchpad.net/yade Source0: http://launchpad.net/yade/trunk/0.60/+download/yade-0.60.1.tar.bz2 patch0: exclude-rpath-patch-060 patch1: disable-info.patch BuildRoot: %{_tmppath}/%{name}-%{version} BuildRequires: scons, freeglut-devel, boost-devel >= 1.35, boost-date-time >= 1.35, boost-filesystem >= 1.35, boost-thread >= 1.35, boost-regex >= 1.35, fakeroot, gcc, gcc-c++ > 4.0, boost-iostreams >= 1.35, log4cxx, log4cxx-devel, python-devel, boost-python >= 1.35, ipython, python-matplotlib, sqlite-devel, python-numeric, graphviz-python, vtk-devel, ScientificPython, bzr, eigen2-devel, libQGLViewer-devel, loki-lib-devel, python-xlib, PyQt4, PyQt4-devel, ScientificPython-tk, gnuplot, doxygen, gts-devel, texlive-xetex, texlive-latex Requires: ScientificPython, python-numeric, ipython, ScientificPython-tk, PyQt4, gnuplot Requires(post): info Requires(preun): info %description Platform for discrete element modeling. Yet Another Dynamic Engine. Extensible open-source framework for discrete numerical models, focused on Discrete Element Method. The computation parts are written in c++ using flexible object model, allowing independent implementation of new algorithms and interfaces. Python is used for rapid and concise scene construction, simulation control, postprocessing and debugging. %package dbg Summary: Debug Yade version %description dbg This package contains debug-libraries for yade %package doc Summary: Documentation for Yade %description doc This package contains examples, test scripts and documentation. %prep %setup -q %patch1 %build %install rm -f scons.profile-rpm rm -rf %{buildroot}/* #Compile and install optimized version scons profile=rpm PREFIX=%{buildroot}/usr runtimePREFIX=/usr buildPrefix=rpm march= brief=0 chunkSize=5 jobs=1 version=0.60 features=vtk,gts,opengl,openmp,qt4 optimize=1 debug=0 variant='' #Compile and install debug version scons profile=rpm brief=0 chunkSize=5 jobs=1 optimize=0 debug=1 variant='' %{buildroot}%_libdir #Copy a library "by hand" (not sure, why the symling is not working in RPMs) rm %{buildroot}%_libdir/%{name}/lib/lib_gts__python-module.so cp %{buildroot}%_libdir/%{name}/py/gts/_gts.so %{buildroot}%_libdir/%{name}/lib/lib_gts__python-module.so #Generate documentation cd %_builddir/%{name}-%{version}/doc/sphinx PYTHONPATH=. YADE_PREFIX=%{buildroot}/usr %{buildroot}/usr/bin/%{name} yadeSphinx.py cd _build/latex; xelatex Yade.tex; xelatex Yade.tex; xelatex Yade.tex; YADE_PREFIX=%{buildroot}/usr %{buildroot}/usr/bin/%{name} --generate-manpage yade-%{version}.1 YADE_PREFIX=%{buildroot}/usr %{buildroot}/usr/bin/%{name}-batch --generate-manpage yade-%{version}-batch.1 cd ../..; mkdir -p %{buildroot}%{_docdir}/%{name} mkdir -p %{buildroot}%{_mandir}/man1 mv %_builddir/%{name}-%{version}/doc/sphinx/_build/html %{buildroot}%{_docdir}/%{name} mv %_builddir/%{name}-%{version}/doc/sphinx/_build/latex/Yade.pdf %{buildroot}%{_docdir}/%{name} mv %_builddir/%{name}-%{version}/examples %{buildroot}%{_docdir}/%{name} mv %_builddir/%{name}-%{version}/doc/sphinx/_build/latex/yade-%{version}.1 %{buildroot}%{_mandir}/man1/ mv %_builddir/%{name}-%{version}/doc/sphinx/_build/latex/yade-%{version}-batch.1 %{buildroot}%{_mandir}/man1/ %clean rm -rf %{buildroot} rm -rf %{_builddir}/* %files %defattr(-,root,root,-) %{_bindir}/* %{_libdir}/%{name}/lib/* %{_libdir}/%{name}/plugins/* %{_libdir}/%{name}/py/* %doc %{_mandir}/* %files dbg %defattr(-,root,root,-) %{_libdir}/%{name}/dbg/* %files doc %defattr(-,root,root,-) %doc %{_docdir}/%{name}/* %check YADE_PREFIX=%{buildroot}/usr %{buildroot}/usr/bin/%{name} --test YADE_PREFIX=%{buildroot}/usr %{buildroot}/usr/bin/%{name} --debug --test %changelog * Wed Feb 23 2011 Anton Gladky 0.60-1 - Initial packaging trunk-2018.02b/scripts/RPM_packaging/yade_old.spec000066400000000000000000000046671324306050200217700ustar00rootroot00000000000000# # This needs to be tuned by hand # %define REVISION 0.11 %define _REVISION -0.11 %define SNAPSHOT -stable # do not strip files after compilation %define __spec_install_post /usr/lib/rpm/brp-compress # do not generate debug package %define debug_package %{nil} Name: yade%{_REVISION} Provides: yade%{SNAPSHOT} Version: 1 Release: a Summary: Platform for discrete numerical modeling License: GPL Requires: boost python libQGLViewer BuildRequires: qt-devel boost-devel scons libXmu libXmu-devel glut-devel ScientificPython python-devel Url: http://yade.wikia.com Source: http://download.berlios.de/yade/yade-0.11.0.tar.gz Group: Applications/Engineering BuildRoot: %{_tmppath}/%{name}-buildroot %package dbg Summary: Platform for discrete numerical modeling, debugging version. Group: Applications/Engineering Provides: yade%{SNAPSHOT}-dbg %package devel Summary: Platform for discrete numerical modeling, development files. Group: Applications/Engineering Provides: yade%{SNAPSHOT}-devel Requires: boost-devel python-devel qt-devel libQGLViewer-devel glut-devel %description YADE is an Open Source GNU/GPL Software framework designed with dynamic libraries in a way that it is easy to add new numerical models for simulation. It aims to provide versatile means to perform discrete modelling. %description dbg YADE is an Open Source GNU/GPL Software framework designed with dynamic libraries in a way that it is easy to add new numerical models for simulation. It aims to provide versatile means to perform discrete modelling. This packages is built with debugging support. %description devel YADE is an Open Source GNU/GPL Software framework designed with dynamic libraries in a way that it is easy to add new numerical models for simulation. It aims to provide versatile means to perform discrete modelling. This package contains development files. %prep %setup -n yade-0.11.0 %build %install #optimized build (main package) scons buildPrefix=${RPM_BUILD_DIR} PREFIX=%{RPM_BUILD_ROOT}/usr runtimePREFIX=/usr version=%{REVISION} useMiniWm3=1 exclude=realtime-rigidbody variant='' optimize=1 debug=0 #debug build (-dbg) scons variant=-dbg optimize=0 debug=1 # install headers (-devel) scons %{RPM_BUILD_ROOT}/usr/include %{RPM_BUILD_ROOT}/usr/lib/pkgconfig %files %defattr(-,root,root) /usr/lib/%name /usr/bin/%name %files dbg %defattr(-,root,root) /usr/lib/%name-dbg /usr/bin/%name-dbg %files devel %defattr(-,root,root) /usr/include/%name /usr/lib/pkgconfig/%{name}*.pc trunk-2018.02b/scripts/build-infrastructure/000077500000000000000000000000001324306050200210325ustar00rootroot00000000000000trunk-2018.02b/scripts/build-infrastructure/build-farm000077500000000000000000000053051324306050200230050ustar00rootroot00000000000000#!/usr/bin/python # encoding: utf-8 #Initial script to create build-bot import sys,os,shutil,glob,argparse revNo = 0 parser = argparse.ArgumentParser(description='Build packages for different distributions') parser.add_argument('--buildpath', metavar=os.path.normpath(sys.path[0]), default = os.path.normpath(sys.path[0]) , help ='path, where all files for building will be stored ) [default: current directory]') parser.add_argument('--distributions', metavar="lucid maverick natty sid", default = "lucid maverick natty sid" , help ='distributions for building [default: lucid maverick]') parser.add_argument('--architectures', metavar="i386 amd64", default = "i386 amd64" , help ='architectures to building [default: i386 amd64]') args = parser.parse_args() defaultPath = args.buildpath distributionsNames = args.distributions.split() distributionsArch = args.architectures.split() # Check, whether base files for building exist for distrName in distributionsNames: for archName in distributionsArch: distr_plus_arch = distrName+'_'+archName if not(os.path.exists('/var/cache/pbuilder/'+distr_plus_arch+'.tgz')): os.system('sudo pbuilder --create --basetgz /var/cache/pbuilder/'+distr_plus_arch+'.tgz --aptcache /var/cache/pbuilder/aptcache_'+archName+'/ --distribution '+distrName+' --architecture '+archName+' --components "main universe" --debootstrapopts --variant=buildd') os.system('sudo pbuilder --update --basetgz /var/cache/pbuilder/'+distr_plus_arch+'.tgz') #Update to the latest revision or checkout if os.path.exists(defaultPath+'/cleanBzr'): print "CleanBZR folder exists" print "Updating to the latest bzr version" os.chdir(defaultPath+'/cleanBzr/yade') os.system('bzr up') revNo=os.popen("LC_ALL=C bzr revno 2>/dev/null").readlines()[0][:-1] else: os.mkdir(defaultPath+'/cleanBzr') print "CleanBZR created" print "Checking out the latest bzr version" os.chdir(defaultPath+'/cleanBzr/') os.system('bzr checkout lp:yade --lightweight') os.chdir('yade') revNo=os.popen("LC_ALL=C bzr revno 2>/dev/null").readlines()[0][:-1] os.chdir(defaultPath) VERSION = 'bzr'+str(revNo) # Prepare .dsc files for pbuilder for distrName in distributionsNames: if os.path.exists(distrName) and (os.path.isdir(distrName)): shutil.rmtree(distrName) os.mkdir(distrName) os.chdir(distrName) shutil.copytree('../cleanBzr/yade','yade') os.chdir('yade') os.system('scripts/debian-prep ' + distrName) os.chdir('..') os.system('dpkg-source -b -I yade-'+VERSION) dscFile = glob.glob('*.dsc')[0] for archName in distributionsArch: os.mkdir(archName) os.system('sudo pbuilder --build --basetgz /var/cache/pbuilder/'+distr_plus_arch+'.tgz --buildresult '+defaultPath+'/'+distrName+'/'+archName+' '+dscFile) os.chdir('..') trunk-2018.02b/scripts/build-infrastructure/master.cfg000066400000000000000000000302061324306050200230070ustar00rootroot00000000000000# -*- python -*- # ex: set syntax=python: # This is a sample buildmaster config file. It must be installed as # 'master.cfg' in your buildmaster's base directory (although the filename # can be changed with the --basedir option to 'mktap buildbot master'). # It has one job: define a dictionary named BuildmasterConfig. This # dictionary has a variety of keys to control different aspects of the # buildmaster. They are documented in docs/config.xhtml . # This is the dictionary that the buildmaster pays attention to. We also use # a shorter alias to save typing. c = BuildmasterConfig = {} ####### BUILDSLAVES # the 'slaves' list defines the set of allowable buildslaves. Each element is # a BuildSlave object, which is created with bot-name, bot-password. These # correspond to values given to the buildslave's mktap invocation. from buildbot.buildslave import BuildSlave c['slaves'] = [BuildSlave("r0calcul5", "1MotDePasse!"), BuildSlave("r0calcul6", "1MotDePasse!"), BuildSlave("r0calcul7", "1MotDePasse!"), BuildSlave("r0calcul8", "1MotDePasse!"), BuildSlave("r0calcul9", "1MotDePasse!"), BuildSlave("r0calcul10", "1MotDePasse!")] c['slavePortnum'] = 9989 ####### CHANGESOURCES #bm = {'lp:yade' : 'defaultBranch' } from buildbot.changes.mail import BzrLaunchpadEmailMaildirSource c['change_source'] = BzrLaunchpadEmailMaildirSource("/var/lib/buildbot/Maildir", defaultBranch = None) ####### SCHEDULERS ## configure the Schedulers from buildbot.scheduler import Scheduler, Nightly c['schedulers'] = [] c['schedulers'].append(Scheduler(name="full", branch=None, treeStableTimer=5*60, builderNames=["yade-full"])) c['schedulers'].append(Nightly(name="nightly", branch=None, hour=01,minute=23, builderNames=["yade-full"])) #c['schedulers'].append(Nightly(name="nightly", branch=None, # hour=16,minute=55, # builderNames=["yade-full-test"])) # #c['schedulers'].append(Nightly(name="lucid64", branch=None, # hour=01,minute=23, # builderNames=["yade-full-lucid64"])) # ####### BUILDERS # the 'builders' list defines the Builders. Each one is configured with a # dictionary, using the following keys: # name (required): the name used to describe this builder # slavename (required): which slave to use (must appear in c['bots']) # builddir (required): which subdirectory to run the builder in # factory (required): a BuildFactory to define how the build is run # periodicBuildTime (optional): if set, force a build every N seconds from buildbot.process import factory from buildbot.process.properties import WithProperties from buildbot.steps.source import Bzr from buildbot.steps.python_twisted import Trial from buildbot.steps.shell import Compile from buildbot.steps.shell import ShellCommand, RemoteCommand from buildbot.steps.shell import Test from buildbot.steps.transfer import DirectoryUpload, FileUpload from buildbot.steps.master import MasterShellCommand ## nextSlave : choose the best buildslave ## we use nagios probe do determine which hosts is the bets. import os import subprocess def nextSlave(builder, slaves): best = 0 for slave in slaves: print slave mem = float(subprocess.Popen(["/usr/local/bin/check_nrpe", "-H", slave.slave.slavename, "-c", "check_freemem"], stdout=subprocess.PIPE).communicate()[0].split("(")[1].split(")")[0].strip("M")) load = float(subprocess.Popen(["/usr/local/bin/check_nrpe", "-H", slave.slave.slavename, "-c", "check_load_buildbot"], stdout=subprocess.PIPE).communicate()[0].split()[1].rstrip(',')) + 0.01 score = mem/load if score > best : best = score selected = slave return selected bzrurl = "lp:yade" f1 = factory.BuildFactory() # EIGEN3 prefix = "/home/buildbot/yade/yade-full" f1.addStep(Bzr(repourl=bzrurl, mode="update")) f1.addStep(Compile(description="Compiling with eigen3", command=["scons", "features=opengl,gts,openmp,vtk,qt4,cgal", "chunkSize=20", "jobs=4", "PREFIX=" + prefix])) f1.addStep(Test(command=[WithProperties(prefix + "/bin/yade-bzr%(got_revision)s"), "--test"])) f1.addStep(Test(command=[WithProperties(prefix + "/bin/yade-bzr%(got_revision)s"), "--checks"], description="checking eigen3 build", descriptionDone="checking eigen3 build done")) # EIGEN3 - DEBUG #f1.addStep(Compile(description="Compiling debug with eigen3", command=["scons", "features=opengl,gts,openmp,vtk,qt4,cgal", # "chunkSize=20", "jobs=4","debug=true", "PREFIX=" + prefix])) #f1.addStep(Test(command=[WithProperties(prefix + "/bin/yade-bzr%(got_revision)s"), "--test"])) #f1.addStep(Test(command=[WithProperties(prefix + "/bin/yade-bzr%(got_revision)s"), "--checks"], # description="checking eigen3 debug build", descriptionDone="checking eigen3 debug build done")) # # EIGEN2 f1.addStep(Compile(description="Compiling with eigen2", command=["scons", "features=opengl,gts,openmp,vtk,qt4,cgal", "chunkSize=20", "jobs=4", "PREFIX=" + prefix, "eigen2=true"])) f1.addStep(Test(command=[WithProperties(prefix + "/bin/yade-bzr%(got_revision)s"), "--test"])) f1.addStep(Test(command=[WithProperties(prefix + "/bin/yade-bzr%(got_revision)s"), "--checks"], description="checking eigen2 build", descriptionDone="checks eigen2 build done")) # doc wdir = prefix + "/build/doc/sphinx/" texdir = wdir+"_build/latex/" f1.addStep(ShellCommand(workdir=wdir, env={'PYTHONPATH':wdir}, command=["xvfb-run", WithProperties(prefix + "/bin/yade-bzr%(got_revision)s"), "yadeSphinx.py"], description="compiling html doc", descriptionDone="html doc", haltOnFailure=True)) f1.addStep(ShellCommand(workdir= texdir, command=["xelatex", "Yade.tex"], description="compiling pdf doc 1st pass", descriptionDone="pdf doc", haltOnFailure=True)) f1.addStep(ShellCommand(workdir= texdir, command=["xelatex", "Yade.tex"], description="compiling pdf doc 2nd pass", descriptionDone="pdf doc (2)", haltOnFailure=True)) f1.addStep(DirectoryUpload(slavesrc="doc/sphinx/_build/html", masterdest=WithProperties("/var/www/doc"))) f1.addStep(DirectoryUpload(slavesrc="doc/sphinx/_build/html", masterdest=WithProperties("/var/www/doc/%(got_revision)s"))) f1.addStep(FileUpload(slavesrc=texdir+"Yade.pdf", masterdest="/var/www/doc/Yade.pdf")) f1.addStep(MasterShellCommand(command="chmod -R a-x+rX /var/www/doc")) # tarball f1.addStep(ShellCommand(workdir= prefix + "/build", command="/usr/bin/bzr export /tmp/yade")) f1.addStep(ShellCommand(workdir="/tmp",command=["tar", "cvzf", "yade-src.tgz", "yade"])) f1.addStep(FileUpload(slavesrc="/tmp/yade-src.tgz", masterdest=WithProperties("/var/www/source/yade_r%(got_revision)s.tgz"))) f1.addStep(MasterShellCommand(command=["ln", "-sf", WithProperties("/var/www/source/yade_r%(got_revision)s.tgz") ,"/var/www/source/yade_latest_trunk.tgz"])) # clean f1.addStep(ShellCommand(command=["rm","-rf", WithProperties(prefix + "/build-bzr%(got_revision)s"), WithProperties(prefix + "/bin/yade-bzr%(got_revision)s"), "/tmp/yade", WithProperties(prefix + "/bin/yade-bzr%(got_revision)s-batch"), WithProperties(prefix + "/lib/yade-bzr%(got_revision)s")], alwaysRun=True, description="cleaning", descriptionDone="clean")) b1 = {'name': "yade-full", 'slavenames': ["r0calcul5", "r0calcul8", "r0calcul9", "r0calcul10"], 'builddir': "yade-full", 'factory': f1, 'nextSlave': nextSlave, } #b1-test = {'name': "yade-full-test", # 'slavenames': ["r0calcul5", "r0calcul8", "r0calcul9", "r0calcul10"], # 'builddir': "yade-full-test", # 'factory': f2, # 'nextSlave': nextSlave, # } # c['builders'] = [b1] ####### STATUS TARGETS # 'status' is a list of Status Targets. The results of each build will be # pushed to these targets. buildbot/status/*.py has a variety to choose from, # including web pages, email senders, and IRC bots. c['status'] = [] # Use allowForce=True (boolean, not a string. ie: not 'True') to allow # Forcing Builds in the Web User Interface. The default is False. # from buildbot.status import html # c['status'].append(html.WebStatus(http_port=8010,allowForce=True)) from buildbot.status import html c['status'].append(html.WebStatus(http_port=8010,allowForce=True)) from zope.interface import implements from buildbot.interfaces import IEmailLookup import re class MailLookup(): implements(IEmailLookup) def getAddress(self,user): found = set() mailsrch = re.compile("[a-zA-Z0-9\.\_\%\-\+]+@[a-zA-Z0-9\.\_\%\-]+.[a-zA-Z]{2,6}") found.update(mailsrch.findall(user)) if len(found) : return found.pop() else : return None email_lookup = MailLookup() from buildbot.status import mail c['status'].append(mail.MailNotifier(fromaddr="buildbot@yade-dem.org", extraRecipients=["remi.cailletaud@hmg.inpg.fr"], sendToInterestedUsers=True, lookup=email_lookup)) c['status'].append(mail.MailNotifier(fromaddr="buildbot@yade-dem.org", mode="failing", extraRecipients=["yade-dev@lists.launchpad.net"], sendToInterestedUsers=False)) #from buildbot.status import words #irc = words.IRC("chat.eu.freenode.net", "ybb", # channels=["#yade-dev"], # port=8000, # password="1MotDePasse!", # notify_events={ # 'started': 1, # 'finished': 1, # 'exception': 1, # 'successToFailure': 1, # 'failureToSuccess': 1, # }) #c['status'].append(irc) # # #c['status'].append(mail.MailNotifier(fromaddr="buildbot@yade-dem.org",builders=["yade-full-test"], # extraRecipients=["remi.cailletaud@hmg.inpg.fr"], # sendToInterestedUsers=True)) # from buildbot.status import client # c['status'].append(client.PBListener(9988)) ####### DEBUGGING OPTIONS # if you set 'debugPassword', then you can connect to the buildmaster with # the diagnostic tool in contrib/debugclient.py . From this tool, you can # manually force builds and inject changes, which may be useful for testing # your buildmaster without actually committing changes to your repository (or # before you have a functioning 'sources' set up). The debug tool uses the # same port number as the slaves do: 'slavePortnum'. #c['debugPassword'] = "debugpassword" # if you set 'manhole', you can ssh into the buildmaster and get an # interactive python shell, which may be useful for debugging buildbot # internals. It is probably only useful for buildbot developers. You can also # use an authorized_keys file, or plain telnet. #from buildbot import manhole #c['manhole'] = manhole.PasswordManhole("tcp:9999:interface=127.0.0.1", # "admin", "password") ####### PROJECT IDENTITY # the 'projectName' string will be used to describe the project that this # buildbot is working on. For example, it is used as the title of the # waterfall HTML page. The 'projectURL' string will be used to provide a link # from buildbot HTML pages to your project's home page. c['projectName'] = "Yade" c['projectURL'] = "http://yade-dem.org" # the 'buildbotURL' string should point to the location where the buildbot's # internal web server (usually the html.Waterfall page) is visible. This # typically uses the port number set in the Waterfall 'status' entry, but # with an externally-visible host name which the buildbot cannot figure out # without some help. c['buildbotURL'] = "http://yade-dem.org/buildbot/" trunk-2018.02b/scripts/checks-and-tests/000077500000000000000000000000001324306050200200155ustar00rootroot00000000000000trunk-2018.02b/scripts/checks-and-tests/checks/000077500000000000000000000000001324306050200212555ustar00rootroot00000000000000trunk-2018.02b/scripts/checks-and-tests/checks/DEM-PFV-check.py000066400000000000000000000125161324306050200237450ustar00rootroot00000000000000# -*- coding: utf-8 -*- # Here, we are testing bulk modulus, then permeability, then the consolidation of a specimen. # the test is based on examples/FluidCouplingPFV/oedometer.py, only slightly simplified and using less particles if ('PFVFLOW' in features): errors=0 toleranceWarning =0.01 toleranceCritical=0.10 from yade import pack num_spheres=100# number of spheres young=1e6 compFricDegree = 3 # initial contact friction during the confining phase finalFricDegree = 30 # contact friction during the deviatoric loading mn,mx=Vector3(0,0,0),Vector3(1,1,1) # corners of the initial packing O.materials.append(FrictMat(young=young,poisson=0.5,frictionAngle=radians(compFricDegree),density=2600,label='spheres')) O.materials.append(FrictMat(young=young,poisson=0.5,frictionAngle=0,density=0,label='walls')) walls=aabbWalls([mn,mx],thickness=0,material='walls') wallIds=O.bodies.append(walls) sp=pack.SpherePack() sp.makeCloud(mn,mx,-1,0.3333,num_spheres,False, 0.95,seed=0) #"seed" is not enough for portable determinism it seems, let us use a data file sp.load(checksPath+'/data/100spheres') sp.toSimulation(material='spheres') triax=TriaxialStressController( maxMultiplier=1.+2e4/young, # spheres growing factor (fast growth) finalMaxMultiplier=1.+2e3/young, # spheres growing factor (slow growth) thickness = 0, stressMask = 7, max_vel = 0.005, internalCompaction=True, # If true the confining pressure is generated by growing particles ) newton=NewtonIntegrator(damping=0.2) O.engines=[ ForceResetter(), InsertionSortCollider([Bo1_Sphere_Aabb(),Bo1_Box_Aabb()]), InteractionLoop( [Ig2_Sphere_Sphere_ScGeom(),Ig2_Box_Sphere_ScGeom()], [Ip2_FrictMat_FrictMat_FrictPhys()], [Law2_ScGeom_FrictPhys_CundallStrack()],label="iloop" ), FlowEngine(dead=1,label="flow"),#introduced as a dead engine for the moment, see 2nd section GlobalStiffnessTimeStepper(active=1,timeStepUpdateInterval=100,timestepSafetyCoefficient=0.8), triax, newton ] triax.goal1=triax.goal2=triax.goal3=-10000 while 1: O.run(200, True) unb=unbalancedForce() if unb<0.01 and abs(-10000-triax.meanStress)/10000<0.01: break setContactFriction(radians(finalFricDegree)) ## ______________ Oedometer section _________________ #A. Check bulk modulus of the dry material from load/unload cycles triax.stressMask=2 triax.goal1=triax.goal3=0 triax.internalCompaction=False triax.wall_bottom_activated=False triax.goal2=-11000; O.run(2000,1) triax.goal2=-10000; O.run(2000,1) triax.goal2=-11000; O.run(2000,1) e22=triax.strain[1] triax.goal2=-10000; O.run(2000,1) e22=e22-triax.strain[1] modulus = 1000./abs(e22) target=252759.905803 if abs((modulus-target)/target)>toleranceWarning: print "DEM-PFV: difference in bulk modulus:", modulus, "vs. target ",target if (abs((modulus-target)/target)>toleranceCritical): errors+=1 print "The difference is more, than the critical tolerance!" #B. Activate flow engine and set boundary conditions in order to get permeability flow.dead=0 flow.defTolerance=0.3 flow.meshUpdateInterval=200 flow.useSolver=3 flow.viscosity=10 flow.bndCondIsPressure=[0,0,1,1,0,0] flow.bndCondValue=[0,0,1,0,0,0] flow.boundaryUseMaxMin=[0,0,0,0,0,0] O.dt=0.1e-3 O.dynDt=False O.run(1,1) Qin = flow.getBoundaryFlux(2) Qout = flow.getBoundaryFlux(3) permeability = abs(Qin)/1.e-4 #size is one, we compute K=V/∇H if abs(Qin+Qout)>1e-10 : print "DEM-PFV: unbalanced Qin vs. Qout (",Qin," vs. ",Qout,")" errors+=1 target=0.040399916554 if abs((permeability-target)/target)>toleranceWarning: print "DEM-PFV: difference in permeability:",permeability," vs. target ",target if (abs((permeability-target)/target)>toleranceCritical): errors+=1 print "The difference is more, than the critical tolerance!" #C. now the oedometer test, drained at the top, impermeable at the bottom plate flow.bndCondIsPressure=[0,0,0,1,0,0] flow.bndCondValue=[0,0,0,0,0,0] newton.damping=0 zeroTime=O.time zeroe22 = triax.strain[1] triax.goal2=-11000 O.timingEnabled=1 from yade import timing O.run(3000,1) target=628.314160434 if abs((flow.getPorePressure((0.5,0.1,0.5))-target)/target)>toleranceWarning: print "DEM-PFV: difference in final pressure:",flow.getPorePressure((0.5,0.1,0.5))," vs. target ",target if (abs((flow.getPorePressure((0.5,0.1,0.5))-target)/target)>toleranceCritical): errors+=1 print "The difference is more, than the critical tolerance!" target=-0.00258113045083 if abs((triax.strain[1]-zeroe22-target)/target)>toleranceWarning: print "DEM-PFV: difference in final deformation",triax.strain[1]-zeroe22," vs. target ",target if (abs((triax.strain[1]-zeroe22-target)/target)>toleranceCritical): errors+=1 print "The difference is more, than the critical tolerance!" if (float(flow.execTime)/float(sum([e.execTime for e in O.engines])))>0.6 : print "(INFO) DEM-PFV: More than 60\% of cpu time in FlowEngine (",100.*(float(flow.execTime)/float(sum([e.execTime for e in O.engines]))) ,"%). Should not happen with efficient libraries (check blas/lapack/cholmod implementations)" flow.forceMetis=True O.run(201,1) if not flow.metisUsed(): print "DEM-PFV: Metis is not used during cholmod's reordering although explicitly enabled, something wrong with libraries" #errors+=1 if (errors): resultStatus +=1 #Test is failed else: print "skip DEM-PFV check, FlowEngine not available" trunk-2018.02b/scripts/checks-and-tests/checks/README000066400000000000000000000041571324306050200221440ustar00rootroot00000000000000=== Checktesting === 1. Check tests perform comparisons of simulation results between different versions of yade, as discussed in http://www.mail-archive.com/yade-dev@lists.launchpad.net/msg05784.html and the whole thread. They differ with regression tests in the sense that they simulate more complex situations and combinations of different engines, and usually don't have a mathematical proof (though there is no restriction on the latest). 2. They compare the values obtained in version N with values obtained in a previous version or any other "expected" results. The reference values must be hardcoded in the script itself or in data files provided with the script. 3. Check tests are based on regular yade scripts, so that users can easily commit their own scripts to trunk in order to get some automatized testing after commits. 4. Since the check tests history will be mostly based on standard output generated by "yade --checks", a meaningfull checkTest should include some "print" command telling if something went wrong. If the script itself fails for some reason and can't generate an output, the log will contain "scriptName failure". 5. If the script defines differences on obtained and awaited data, it should print some useful information about the problem and increase the value of global variable resultStatus. After this occurs, the automatic test will stop the execution with error message. 6. An example check test can be found in checkTestTriax.py. It shows results comparison, output, and how to define the path to data files using "checksPath". 7. Users are encouraged to add their own scripts into the scripts/test/checks/ folder. Discussion of some specific checktests design in users question is welcome. 8. A check test should never need more than a few seconds to run. If your typical script needs more, try and reduce the number of element or the number of steps. 9. Failures are reported via a global variable "resultStatus", which should be ONLY INCREMENTED in a checkTests, never assigned to (resultStatus+=1 is ok, resultStatus=1 is bad), else the script would erase the history of the checkTests coming before it. trunk-2018.02b/scripts/checks-and-tests/checks/checkCapillaryModels.py000066400000000000000000000054171324306050200257200ustar00rootroot00000000000000#!/usr/bin/env python # encoding: utf-8 # 3 pairs of spheres with different diameters, # different capillary bridge models and different # bridge volume are pulled out and the capillary forces # are messed after 250 iterations and compared to a # verified values (TODO: values check one more time). # Links to papers: # Comparison of different capillary bridge models for application in the discrete element method # (A. Gladkyy, R. Schwarze) # http://arxiv.org/pdf/1403.7926.pdf # Capillary forces between surfaces with nanoscale roughness (Yakov I. Rabinovich ...) # http://diyhpl.us/~bryan/papers2/Capillary%20forces%20between%20surfaces%20with%20nanoscale%20roughness.pdf from yade import utils, plot o = Omega() fr = 0.5;rho=2000 tc = 0.001; en = 0.7; et = 0.7; o.dt = 0.001*tc tolerance = 0.0001 r0 = 19.0e-6 r1 = 35.0e-6 r2 = 32.5e-6 r3 = 27.5e-6 Gamma1 = 27.0e-3 Gamma2 = 24.0e-3 Gamma3 = 28.0e-3 Theta = 10.0 VB1 = 2.0e-19 VB2 = 12.0e-19 VB3 = 36.0e-19 CapillarType1 = "Willett_numeric" CapillarType2 = "Willett_analytic" CapillarType3 = "Rabinovich" mat1 = O.materials.append(ViscElCapMat(frictionAngle=fr,density=rho,Vb=VB1,gamma=Gamma1,theta=Theta,Capillar=True,CapillarType=CapillarType1,tc=tc,en=en,et=et)) mat2 = O.materials.append(ViscElCapMat(frictionAngle=fr,density=rho,Vb=VB2,gamma=Gamma2,theta=Theta,Capillar=True,CapillarType=CapillarType2,tc=tc,en=en,et=et)) mat3 = O.materials.append(ViscElCapMat(frictionAngle=fr,density=rho,Vb=VB3,gamma=Gamma3,theta=Theta,Capillar=True,CapillarType=CapillarType3,tc=tc,en=en,et=et)) id1 = O.bodies.append(sphere(center=[0,0,0],radius=r0,material=mat1,fixed=True)) id2 = O.bodies.append(sphere(center=[0,0,(r0+r1)*0.99999],radius=r1,material=mat1,fixed=True)) id3 = O.bodies.append(sphere(center=[3.0*r1,0,0],radius=r0,material=mat2,fixed=True)) id4 = O.bodies.append(sphere(center=[3.0*r1,0,(r0+r2)*0.99999],radius=r2,material=mat2,fixed=True)) id5 = O.bodies.append(sphere(center=[6.0*r1,0,0],radius=r0,material=mat3,fixed=True)) id6 = O.bodies.append(sphere(center=[6.0*r1,0,(r0+r3)*0.99999],radius=r3,material=mat3,fixed=True)) o.engines = [ ForceResetter(), InsertionSortCollider([Bo1_Sphere_Aabb()],verletDist=(r0+r1)*5.0), InteractionLoop( [Ig2_Sphere_Sphere_ScGeom()], [Ip2_ViscElCapMat_ViscElCapMat_ViscElCapPhys()], [Law2_ScGeom_ViscElCapPhys_Basic()], ), NewtonIntegrator(damping=0,gravity=[0,0,0]), ] O.bodies[id2].state.vel=[0,0,0.001] O.bodies[id4].state.vel=[0,0,0.001] O.bodies[id6].state.vel=[0,0,0.001] O.run(250,True) f1 = -O.forces.f(id2)[2] f2 = -O.forces.f(id4)[2] f3 = -O.forces.f(id6)[2] O.wait() if ((abs(2.07328148666e-07-f1)/f1)>tolerance): resultStatus += 1 if ((abs(6.78192074e-07-f2)/f2)>tolerance): resultStatus += 1 if ((abs(1.58617796928e-06-f3)/f3)>tolerance): resultStatus += 1 trunk-2018.02b/scripts/checks-and-tests/checks/checkClumpHopper.py000066400000000000000000000053111324306050200250630ustar00rootroot00000000000000# -*- coding: utf-8 # This script is used in --check to keep the functionality of adding/removal # of particles and clumps functional import itertools from numpy import * kinEnergyMax = 100000 # Parameters tc=0.001# collision time en=.3 # normal restitution coefficient es=.3 # tangential restitution coefficient frictionAngle=radians(35)# density=2700 # facets material facetMat=O.materials.append(ViscElMat(frictionAngle=frictionAngle,tc=tc,en=en,et=es)) # default spheres material dfltSpheresMat=O.materials.append(ViscElMat(density=density,frictionAngle=frictionAngle,tc=tc,en=en,et=es)) O.dt=.05*tc # time step Rs=0.05 # particle radius # Create geometry x0=0.; y0=0.; z0=0.; ab=.7; at=2.; h=1.; hl=h; al=at*3 zb=z0; x0b=x0-ab/2.; y0b=y0-ab/2.; x1b=x0+ab/2.; y1b=y0+ab/2. zt=z0+h; x0t=x0-at/2.; y0t=y0-at/2.; x1t=x0+at/2.; y1t=y0+at/2. zl=z0-hl;x0l=x0-al/2.; y0l=y0-al/2.; x1l=x0+al/2.; y1l=y0+al/2. vibrationPlate = O.bodies.append(geom.facetBunker((x0,y0,z0), dBunker=ab*4.0,dOutput=ab*1.7,hBunker=ab*3,hOutput=ab,hPipe=ab/3.0, wallMask=4,segmentsNumber=10,material=facetMat)) # Create engines O.engines=[ ForceResetter(), InsertionSortCollider([Bo1_Sphere_Aabb(),Bo1_Facet_Aabb()]), InteractionLoop( [Ig2_Sphere_Sphere_ScGeom(), Ig2_Facet_Sphere_ScGeom()], [Ip2_ViscElMat_ViscElMat_ViscElPhys()], [Law2_ScGeom_ViscElPhys_Basic()], ), DomainLimiter(lo=(-4,-4,-1),hi=(4,4,4),iterPeriod=5000,label='domLim'), NewtonIntegrator(damping=0, gravity=[0,0,-9.81]), PyRunner(iterPeriod=6500,command='addBodies()',nDo=7,label='addb'), PyRunner(iterPeriod=10000,command='state()',label='state'), ] numSphereGen = 0 def addBodies(): global numSphereGen # Create clumps... clumpColor=(0.0, 0.5, 0.5) for k,l in itertools.product(arange(0,10),arange(0,10)): clpId,sphId=O.bodies.appendClumped([sphere(Vector3(x0t+Rs*(k*4+2),y0t+Rs*(l*4+2),i*Rs*2+zt+ab*3),Rs,color=clumpColor,material=dfltSpheresMat) for i in xrange(4)]) numSphereGen += len(sphId) # ... and spheres spheresColor=(0.4, 0.4, 0.4) for k,l in itertools.product(arange(0,9),arange(0,9)): sphAloneId=O.bodies.append( [sphere( Vector3(x0t+Rs*(k*4+4),y0t+Rs*(l*4+4),i*Rs*2.3+zt+ab*3),Rs,color=spheresColor,material=dfltSpheresMat) for i in xrange(4) ] ) numSphereGen += len(sphAloneId) def state(): global numSphereGen global resultStatus print "Iter %d: Total number of generated spheres %d, removed particles %d, current particles %d, kinEnergy %g"%(O.iter, numSphereGen, domLim.nDeleted, numSphereGen-domLim.nDeleted, utils.kineticEnergy()) if (utils.kineticEnergy() > kinEnergyMax): print "Kinetic energy is over a threshold value! Error!" resultStatus += 1 O.pause() addBodies() O.run(10, True) O.bodies.erase(64, True) O.run(20002, True) trunk-2018.02b/scripts/checks-and-tests/checks/checkGravity.py000066400000000000000000000057341324306050200242630ustar00rootroot00000000000000# -*- coding: utf-8 # 3 Spheres have an initial velociries: 0, +5, -5 # Their positions and velocities are checking during free fall # Checks the correctness of NewtonIntegrator and GravityEngine ## Omega o=Omega() ## PhysicalParameters Density=2400 frictionAngle=radians(35) sphereRadius=0.05 tc = 0.001 en = 0.3 et = 0.3 sphereMat=O.materials.append(ViscElMat(density=Density,frictionAngle=frictionAngle,tc=tc,en=en,et=et)) v_down = -5.0 v_up = 5.0 g = -9.81 tolerance = 1e-3 id_0=o.bodies.append(utils.sphere((0.0,0,0),0.2,material=sphereMat)) #The body has no initial vertical Velocity id_down=o.bodies.append(utils.sphere((1.0,0,0),0.2,material=sphereMat)) #The body has an initial vertical Velocity -5 id_up=o.bodies.append(utils.sphere((2.0,0,0),0.2,material=sphereMat)) #The body has an initial vertical Velocity +5 O.bodies[id_down].state.vel[1] = v_down O.bodies[id_up].state.vel[1] = v_up ## Engines o.engines=[ ForceResetter(), InsertionSortCollider([ Bo1_Sphere_Aabb(), Bo1_Facet_Aabb(), ]), InteractionLoop( [Ig2_Sphere_Sphere_ScGeom(), Ig2_Facet_Sphere_ScGeom()], [Ip2_ViscElMat_ViscElMat_ViscElPhys()], [Law2_ScGeom_ViscElPhys_Basic()], ), NewtonIntegrator(damping=0,gravity=[0,g,0]), PyRunner(command='checkPos()',iterPeriod=10000), ] def checkPos(): if ((O.bodies[id_0].state.pos[1] - getCurrentPos(0))/O.bodies[id_0].state.pos[1] > tolerance): warningMessagePos (0, O.bodies[id_0].state.pos[1], getCurrentPos(0)) if ((O.bodies[id_down].state.pos[1] - getCurrentPos(v_down))/O.bodies[id_down].state.pos[1] > tolerance): warningMessagePos (v_down, O.bodies[id_down].state.pos[1], getCurrentPos(0)) if ((O.bodies[id_up].state.pos[1] - getCurrentPos(v_up))/O.bodies[id_up].state.pos[1] > tolerance): warningMessagePos (v_up, O.bodies[id_up].state.pos[1], getCurrentPos(0)) if ((O.bodies[id_0].state.vel[1] - getCurrentVel(0))/O.bodies[id_0].state.vel[1] > tolerance): warningMessageVel (0, O.bodies[id_0].state.vel[1], getCurrentPos(0)) if ((O.bodies[id_down].state.vel[1] - getCurrentVel(v_down))/O.bodies[id_down].state.vel[1] > tolerance): warningMessageVel (v_down, O.bodies[id_down].state.vel[1], getCurrentPos(0)) if ((O.bodies[id_up].state.vel[1] - getCurrentVel(v_up))/O.bodies[id_up].state.vel[1] > tolerance): warningMessageVel (v_up, O.bodies[id_up].state.vel[1], getCurrentPos(0)) def getCurrentPos(inVel=0): t = O.time+O.dt return inVel*t + g*t*t/2 def getCurrentVel(inVel=0): t = O.time+O.dt return inVel + g*t def warningMessagePos(inVel, y_pos, y_pos_need): print "The body with the initial velocity %.3f, has an y-position %.3f, but it should be %.3f" % (inVel, y_pos, y_pos_need) global resultStatus resultStatus+=1 def warningMessageVel(inVel, y_vel, y_pos_vel): print "The body with the initial velocity %.3f, has an y-velocity %.3f, but it should be %.3f" % (inVel, y_vel, y_pos_vel) global resultStatus resultStatus+=1 O.dt=0.02*tc O.saveTmp('init'); O.run(1000000) O.wait() trunk-2018.02b/scripts/checks-and-tests/checks/checkLiquidMigration.py000066400000000000000000000064071324306050200257350ustar00rootroot00000000000000#!/usr/bin/env python # encoding: utf-8 # The model checks liquid migration model if it is enabled during compilation from yade import utils, plot if ('LIQMIGRATION' in features): o = Omega() fr = 0.5;rho=2000 tc = 0.001; en = 0.7; et = 0.7; o.dt = 1.0 r1 = 1.0 r2 = 1.0 Gamma = 20.6*1e-3 Theta = 0 VB = 74.2*1e-12 tolerance = 1e-6 CapillarType = "Lambert" mat1 = O.materials.append(ViscElCapMat(frictionAngle=fr,density=rho,Vb=VB,gamma=Gamma,theta=Theta,Capillar=True,CapillarType=CapillarType,tc=tc,en=en,et=et)) d = 1.1 id0 = O.bodies.append(sphere(center=[0,0,0],radius=r1,material=mat1,fixed=True, color=[1,0,0])) id1 = O.bodies.append(sphere(center=[0,0,(r1+r2)*d],radius=r2,material=mat1,fixed=True, color=[0,1,0])) id2 = O.bodies.append(sphere(center=[0,0,-(r1+r2)*d],radius=r2,material=mat1,fixed=True, color=[0,1,0])) O.bodies[id0].state.Vf = 0.3e-1 O.bodies[id0].state.Vmin = 0.1e-1 O.bodies[id1].state.Vf = 0.4e-1 O.bodies[id1].state.Vmin = 0.1e-1 O.bodies[id2].state.Vf = 0.5e-1 O.bodies[id2].state.Vmin = 0.1e-1 vel = -0.15 O.bodies[id1].state.vel=[0,0,vel] O.bodies[id2].state.vel=[0,0,-vel] o.engines = [ ForceResetter(), InsertionSortCollider([Bo1_Sphere_Aabb()],verletDist=(r1+r2)*5.0), InteractionLoop( [Ig2_Sphere_Sphere_ScGeom()], [Ip2_ViscElCapMat_ViscElCapMat_ViscElCapPhys()], [Law2_ScGeom_ViscElCapPhys_Basic()], ), LiqControl(particleconserve=True,label='liqcontrol'), NewtonIntegrator(damping=0,gravity=[0,0,0]), PyRunner(command='showData()',iterPeriod=1,dead=True), ] def showData(): print "Step %d"%O.iter print "idB=%d, Vf=%s, Vmin=%s;"%(id0, O.bodies[id0].state.Vf, O.bodies[id0].state.Vmin) print "idB=%d, Vf=%s, Vmin=%s;"%(id1, O.bodies[id1].state.Vf, O.bodies[id1].state.Vmin) print "idB=%d, Vf=%s, Vmin=%s;"%(id2, O.bodies[id2].state.Vf, O.bodies[id2].state.Vmin) try: print "Interaction[%d, %d].Vb=%s"%(id0, id1, O.interactions[id0,id1].phys.Vb) except: pass try: print "Interaction[%d, %d].Vb=%s"%(id0, id2, O.interactions[id0,id2].phys.Vb) except: pass print def switchVel(): O.bodies[id1].state.vel=-O.bodies[id1].state.vel O.bodies[id2].state.vel=-O.bodies[id2].state.vel resultStatus = 0 O.run(3, True) if ((abs((O.interactions[id0,id1].phys.Vb - 0.03)/0.03) > tolerance) or (abs((O.interactions[id0,id1].phys.Vb - 0.03)/0.03) > tolerance)): resultStatus += 1 switchVel() O.run(5, True) if ((abs((O.bodies[id0].state.Vf - 0.03)/0.03) > tolerance) or (abs((O.bodies[id1].state.Vf - 0.04)/0.04) > tolerance) or (abs((O.bodies[id2].state.Vf - 0.05)/0.05) > tolerance)): resultStatus += 1 liqcontrol.particleconserve=False switchVel() O.run(5, True) switchVel() O.run(5, True) if ((abs((O.bodies[id0].state.Vf - 0.0465)/0.0465) > tolerance) or (abs((O.bodies[id1].state.Vf - 0.0325)/0.0325) > tolerance) or (abs((O.bodies[id2].state.Vf - 0.041)/0.041) > tolerance)): resultStatus += 1 else: print "This checkLiquidMigration.py cannot be executed because LIQMIGRATION is disabled" trunk-2018.02b/scripts/checks-and-tests/checks/checkList.py000066400000000000000000000015331324306050200235420ustar00rootroot00000000000000# encoding: utf-8 # 2011 © Bruno Chareyre import yade,math,os,sys scriptsToRun=os.listdir(checksPath) resultStatus = 0 nFailed=0 skipScripts = ['checkList.py'] for script in scriptsToRun: if (script[len(script)-3:]==".py" and script not in skipScripts): try: print "###################################" print "running: ",script execfile(checksPath+"/"+script) if (resultStatus>nFailed): print "Status: FAILURE!!!" nFailed=resultStatus else: print "Status: success" print "___________________________________" except: print script," failure" O.reset() elif (script in skipScripts): print "###################################" print "Skipping %s, because it is in SkipScripts"%script if (resultStatus>0): print resultStatus, " tests are failed" sys.exit(1) else: sys.exit(0) trunk-2018.02b/scripts/checks-and-tests/checks/checkPolyhedraCrush.py000066400000000000000000000042411324306050200255620ustar00rootroot00000000000000from yade import plot, polyhedra_utils, ymport, export frictionIn = 0.0 densityIn = 3000.0 youngIn = 1E10 poissonIn = 0.3 sizeB = 0.020 odt = 0.00001 vel=0.01 tolerance = 0.05 startPos = sizeB def printWarning (f_awaited, f_real, n_awaited, n_real): print ("The awaited force is %.4f, but obtained force is %.4f; number of bodies: %d vs %d! Iteration %d"%(f_awaited, f_real, n_awaited, n_real, O.iter)) def printSuccess (): print ("Checkpoint: force values and number of bodies are OK! Iteration %d"%(O.iter)) def checkForcesBodies(fR, bodNum): if (abs(f[2] - fR)/f[2] > tolerance or len(O.bodies) <> bodNum): printWarning (fR, f[2], 4, len(O.bodies)) resultStatus += 1 else: printSuccess() mat1 = PolyhedraMat(density=densityIn, young=youngIn,poisson=poissonIn, frictionAngle=frictionIn,IsSplitable=True,strength=1) O.bodies.append(utils.wall(0,axis=2,sense=1, material = mat1)) vertices = [[0,0,0],[sizeB,0,0],[sizeB,sizeB,0],[sizeB,sizeB,sizeB],[0,sizeB,0],[0,sizeB,sizeB],[0,0,sizeB],[sizeB,0,sizeB]] t = polyhedra_utils.polyhedra(mat1,v=vertices) t.state.pos = (0,0,sizeB/2) O.bodies.append(t) topmesh=O.bodies.append(geom.facetBox((0.,0.,startPos),(sizeB,sizeB,0.), material=mat1)) O.engines=[ ForceResetter(), InsertionSortCollider([Bo1_Polyhedra_Aabb(), Bo1_Wall_Aabb(), Bo1_Facet_Aabb()], verletDist=.05*sizeB), InteractionLoop( [Ig2_Facet_Polyhedra_PolyhedraGeom(), Ig2_Wall_Polyhedra_PolyhedraGeom(), Ig2_Polyhedra_Polyhedra_PolyhedraGeom()], [Ip2_PolyhedraMat_PolyhedraMat_PolyhedraPhys()], [Law2_PolyhedraGeom_PolyhedraPhys_Volumetric()] ), NewtonIntegrator(damping=0.8,gravity=(0,0,-9.81)), PolyhedraSplitter(iterPeriod=1,label='Splitter'), TranslationEngine(translationAxis=[0,0,-1],velocity=vel,ids=topmesh, label='Tr'), PyRunner(command='addPlotData()',iterPeriod=10), ] O.dt=odt f=Vector3.Zero def addPlotData(): global f f=Vector3.Zero for i in topmesh: if (O.forces.f(i)): f+=O.forces.f(i) #from yade import qt #qt.Controller() #V = qt.View() O.run(166, True); checkForcesBodies(-21.7037, 7) trunk-2018.02b/scripts/checks-and-tests/checks/checkSaveLoadClumps.py000066400000000000000000000036531324306050200255160ustar00rootroot00000000000000#!/usr/bin/env python # encoding: utf-8 # Script generates clumps and saves them # Then it tries to load the saved simulation # See https://bugs.launchpad.net/bugs/1560171 # Thanks to Bettina Suhr for providing the # minimal test script. from yade import pack import tempfile, shutil import time #define material for all bodies: id_Mat=O.materials.append(FrictMat(young=1e6,poisson=0.3,density=1000,frictionAngle=1)) partType='clumps' #define engines: O.engines=[ ForceResetter(), InsertionSortCollider([Bo1_Sphere_Aabb(),Bo1_Facet_Aabb()]), InteractionLoop( [Ig2_Sphere_Sphere_ScGeom(),Ig2_Facet_Sphere_ScGeom()], [Ip2_FrictMat_FrictMat_FrictPhys()], [Law2_ScGeom_FrictPhys_CundallStrack()] ), NewtonIntegrator(damping=0.7,gravity=[0,0,-9.81]) ] #create a box: O.bodies.append(geom.facetBox(center=(0.0,0.0,0.25),extents = (0.125,0.125,0.25), wallMask=31)) #add particles eiter spheres or clumps if partType=='spheres': sp=pack.SpherePack() sp.makeCloud( (-0.125,-0.125,0), ( 0.125,0.125,0.5), rMean=37.6e-3/2.,rRelFuzz=0.0,num=100) sp.toSimulation() O.bodies.updateClumpProperties(discretization=10)# correct mass, volume, inertia!! elif partType=='clumps': sp=pack.SpherePack() c1=pack.SpherePack([ ((0.,0.,0.),37.6e-3/2.), ((37.6e-3/2.,0.,0.),25e-3/2.) ])# overlap between both spheres sp.makeClumpCloud( (-0.125,-0.125,0), ( 0.125,0.125,0.5), [c1],num=80) sp.toSimulation() O.bodies.updateClumpProperties(discretization=10)# correct mass, volume, inertia!! else: print "ERROR! choose either spheres or clumps for partType!" O.dt=1e-6 #write some restart files tmp_dir = tempfile.mkdtemp() O.save(tmp_dir + '/restartMinWorkEx_'+partType+'_Initial') O.run(100000,True) O.save(tmp_dir + '/restartMinWorkEx_'+partType+str(O.iter)) time.sleep(1) O.reset() time.sleep(1) O.load(tmp_dir + '/restartMinWorkEx_'+partType +'100000') O.run(1000, True) trunk-2018.02b/scripts/checks-and-tests/checks/checkTestDummy.py000066400000000000000000000007331324306050200245630ustar00rootroot00000000000000# encoding: utf-8 # 2011 © Bruno Chareyre # A dummy test, just to give an example, and detect possible path problems from yade import pack,utils,export,plot import math,os,sys print 'checkTest mechanism' #Typical structure of a checkTest: #do something and get a result... if 0: #put a condition on the result here, is it the expected result? else: print "Dummy failed (we know it will not happen here, you get the idea)." resultStatus+=1 trunk-2018.02b/scripts/checks-and-tests/checks/checkTestTriax.py000066400000000000000000000034441324306050200245610ustar00rootroot00000000000000# encoding: utf-8 # 2011 © Bruno Chareyre # Check the result of a triaxial test on a dense 100 spheres packing with position defined, # comparing stresses at the "peak" state. The initial positions have been generated by # internal compaction without friction, so that it is initially at equilibrium without shear forces. # Positions and reference results are in data folder. from yade import pack,utils,export,plot import math,os,sys,shutil,tempfile tolerance=0.01 interactive=False errors=0 tt=TriaxialTest(internalCompaction=True,numberOfGrains=100,compactionFrictionDeg=0,sphereFrictionDeg=30,importFilename=checksPath+'/data/checkTestTriax.spheres') tt.generate("checkTest.yade") O.load("checkTest.yade") O.run(2020,True) if interactive: print O.engines[4].stress(0)[0],O.engines[4].stress(1)[0], O.engines[4].stress(2)[1], O.engines[4].stress(3)[1], O.engines[4].stress(4)[2], O.engines[4].stress(5)[2] os.system('gnuplot -e "plot \'./WallStresses\' using 1:3; replot'+checksPath+'\'/data/WallStressesCheckTest\' using 1:3; replot '+checksPath+'\'/data/WallStresses\' using 1:4; replot '+checksPath+'\'/data/WallStressesCheckTest\' using 1:4; pause -1"') if abs((O.engines[4].stress(3)[1]+107157.2)/107157.2)>tolerance : print "Triaxial checkTest: difference on peak stress" errors+=1 if abs((O.engines[4].stress(1)[0]+50058.7)/50058.7)>tolerance : print "Triaxial checkTest: difference on confining stress" errors+=1 if (errors): resultStatus +=1 #Test is failed if (errors): resultStatus +=1 #Test is failed dirpath = tempfile.mkdtemp() for fileName in ['./checkTest.yade', './_Unloaded_380_3.spheres', './_Unloaded_380_3.xml', './WallStresses', ]: if (os.path.exists(fileName)): shutil.move(fileName,dirpath) print "File %s moved into %s/ directory"%(fileName,dirpath) trunk-2018.02b/scripts/checks-and-tests/checks/checkViscElEng.py000066400000000000000000000026171324306050200244520ustar00rootroot00000000000000#!/usr/bin/env python # encoding: utf-8 # 2 spheres. One is fixed, another one failing from up. # Calculate en (coefficient of restitution) and compare with precalculated value # Coefficient of restitution is the ratio of speeds after and before an impact # This check-simulation checks the correctness of ViscoElasticEngine from yade import utils, plot o = Omega() fr = 0.5;rho=2000 tc = 0.001; en = 0.7; et = 0.7; o.dt = 0.0002*tc r1 = 0.003 r2 = 0.002 mat1 = O.materials.append(ViscElMat(frictionAngle=fr,density=rho,tc=tc,en=en,et=et)) id1 = O.bodies.append(sphere(center=[0,0,0],radius=r1,material=mat1,fixed=True)) id2 = O.bodies.append(sphere(center=[0,0,(r1+r2*2.0)],radius=r2,material=mat1,fixed=False)) o.engines = [ ForceResetter(), InsertionSortCollider([Bo1_Sphere_Aabb()],verletDist=(r1+r2)*5.0), InteractionLoop( [Ig2_Sphere_Sphere_ScGeom()], [Ip2_ViscElMat_ViscElMat_ViscElPhys()], [Law2_ScGeom_ViscElPhys_Basic()], ), NewtonIntegrator(damping=0,gravity=[0,0,-9.81]), PyRunner(command='addPlotData()',iterPeriod=100), ] v0 = 0 en = 0 tolerance = 0.0001 def addPlotData(): global v0, en v = O.bodies[id2].state.vel[2] if v0<=0 and v>0: en=-v/v0 print ("Precalculated en value %.12f" % 0.736356797441) print ("Obtained en value %.12f" % en) O.pause() v0=v O.run(1000000) O.wait() if ((abs(0.736356797441-en)/en)>tolerance): resultStatus += 1 trunk-2018.02b/scripts/checks-and-tests/checks/checkViscElPM.py000066400000000000000000000066651324306050200242640ustar00rootroot00000000000000#!/usr/bin/env python # encoding: utf-8 ################################################################################ # CONSTITUTIVE LAW TESTING: Law2_ScGeom_ViscElPhys_Basic() # # Two spheres with velocities (v,0,0) and (-v,0,0) collide. # This script checks if: # - the numerical displacement equals the analytical displacement at a certain # time before the spheres separate, for instance in t = 0.05 s # This also implies the consistency of the results in terms of velocity # (and acceleration). # - the normal stiffness and normal damping coefficients have been calculated # correctly in function of the normal coefficient (en) of restitution and the # impact time (tc); this is a consequence of the previous condition. # # Notice that: # - this script only checks the displacement before the separation because the # analytical solution (given below) supposes that the damping term is still # present, when the spheres separate. This is however not true in the # numerical model (see source code, prevent appearing of attraction forces # due to a viscous component). # - this script does not check the tangential (or shear) behaviour of # the constitutive law. # from yade import plot,ymport import math ################################################################################ # MATERIAL rho = 2000 # [kg/m^3] density mu = 0.75 # [-] friction coefficient tc = 0.2 # [s] contact time en = 0.3 # [-] normal restitution coefficient et = 0.2 # [-] tangential restitution coefficient frictionAngle = math.atan(mu) mat = O.materials.append(ViscElMat(tc=tc,en=en,et=et, frictionAngle=frictionAngle,density=rho)) ################################################################################ # GEOMETRY r = 0.1 # [m] sphere radius b1 = O.bodies.append(utils.sphere(center=(2*r,0,0),radius=r,material=mat)) b2 = O.bodies.append(utils.sphere(center=(0,0,0),radius=r,material=mat)) ################################################################################ # SIMULATION O.dt = 1e-5 # [s] fixed time step v = 2 # [m/s] velocity along x-axis O.bodies[b1].state.vel[0] = -v O.bodies[b2].state.vel[0] = v O.engines=[ ForceResetter(), InsertionSortCollider([Bo1_Sphere_Aabb(),Bo1_Facet_Aabb(),Bo1_Wall_Aabb()]), InteractionLoop( [Ig2_Sphere_Sphere_ScGeom(), Ig2_Facet_Sphere_ScGeom(), Ig2_Wall_Sphere_ScGeom()], [Ip2_ViscElMat_ViscElMat_ViscElPhys()], [Law2_ScGeom_ViscElPhys_Basic()] ), NewtonIntegrator(damping=0) ] ################################################################################ # RUN O.run(5001,True) ################################################################################ # COMPARE ANALYTICAL AND NUMERICAL SOLUTIONS m = 4./3. * math.pi * r**3 * rho # [kg] mass of the sphere # Normal stiffness and damping coefficients according to [Pournin2001] meff = m/2 kn = 2.0 * meff/tc**2 * (math.pi**2 + math.log(en)**2) cn = -4.0 * meff/tc * math.log(en) # Analytical solution of a linear spring damper system omega0 = math.sqrt(kn/m) zeta = cn / (2 * math.sqrt(kn * m)) omegad = omega0 * math.sqrt(1 - zeta**2) xAnalytical = v/omegad * math.exp(-zeta*omega0*O.time) * math.sin(omegad*O.time) # Comparison (if ok, resultStatus is not incremented) tolerance = 0.0001 xNumerical = O.bodies[b2].state.pos[0] if ((abs(xNumerical-xAnalytical)/xAnalytical)>tolerance): resultStatus += 1 trunk-2018.02b/scripts/checks-and-tests/checks/checkViscElPM2.py000066400000000000000000000056071324306050200243410ustar00rootroot00000000000000#!/usr/bin/env python # encoding: utf-8 ################################################################################ # CONSTITUTIVE LAW TESTING: Law2_ScGeom_ViscElPhys_Basic() # # Two spheres of different diameter and young modulus, with velocities # (v,0,0) and (-v,0,0) collide. # The script checks if the restitution coefficient applied is the one obtained # Law2_ScGeom_ViscElPhys_Basic() compute the stiffness and damping coefficient # differently depending on the input. This test is then different from # checkViscElPM.py and checkViscElEng.py # from yade import plot,ymport import math ################################################################################ # MATERIAL diameter1 = 1e-2 #[m] diameter of the first particle diameter2 = 1e-3 #[m] diameter of the second particle rho1 = 1500 # [kg/m^3] density of the first particle rho2 = 2500 # [kg/m^3] density of the second particle youngMod = 1e5 # [kg/m/s^2] young modulus of the particles poissonRatio = 0.5 # [-] poisson's ratio of the particles mu = 0.4 # [-] friction coefficient of both particle en = 0.5 # [-] normal restitution coefficient of both particle et = 1. # [-] tangential restitution coefficient of both particle, no damping frictionAngle = math.atan(mu) O.materials.append(ViscElMat(en=en,et=1.,young = youngMod,poisson = poissonRatio,frictionAngle=frictionAngle,density=rho1, label='Mat1')) O.materials.append(ViscElMat(en=en,et=1.,young = youngMod,poisson = poissonRatio,frictionAngle=frictionAngle,density=rho2, label='Mat2')) ################################################################################ v = 1e-1 # GEOMETRY O.bodies.append(sphere((0,0,0),diameter1/2.0, material = 'Mat1')) O.bodies[0].state.vel[2] = v O.bodies.append(sphere((0,0,(diameter1+diameter2)/2.+ 0.01*diameter1),diameter2/2.0, material = 'Mat2')) O.bodies[-1].state.vel[2] = -v ################################################################################ # SIMULATION O.dt = 1e-6 # [s] fixed time step O.engines=[ ForceResetter(), InsertionSortCollider([Bo1_Sphere_Aabb(),Bo1_Facet_Aabb(),Bo1_Wall_Aabb()]), InteractionLoop( [Ig2_Sphere_Sphere_ScGeom(), Ig2_Facet_Sphere_ScGeom(), Ig2_Wall_Sphere_ScGeom()], [Ip2_ViscElMat_ViscElMat_ViscElPhys()], [Law2_ScGeom_ViscElPhys_Basic()] ), NewtonIntegrator(damping=0.), PyRunner(command = 'check()',iterPeriod = int(0.05/O.dt)-1) ] ################################################################################ def check(): global resultStatus # Compare imposed restitution coefficient and obtained one enMeasured = (O.bodies[1].state.vel[2]-O.bodies[0].state.vel[2])/(2*v) tolerance = 1e-2 if (abs(enMeasured -en)/en)>tolerance: resultStatus+=1 ################################################################################ # RUN O.run(int(0.05/O.dt)) O.wait() ################################################################################ trunk-2018.02b/scripts/checks-and-tests/checks/checkWeight.py000066400000000000000000000024261324306050200240600ustar00rootroot00000000000000# -*- coding: utf-8 # Several spheres falling down into the box. # Their weight is measured and compares with real mass particles from yade import utils,pack,export,geom tc=0.001 en=.003 es=.003 frictionAngle=radians(35) density=2300 defMat=O.materials.append(ViscElMat(density=density,frictionAngle=frictionAngle,tc=tc,en=en,et=es)) O.dt=.1*tc # time step rad=0.2 # particle radius tolerance = 0.0001 SpheresID=[] SpheresID+=O.bodies.append(pack.regularHexa(pack.inSphere((Vector3(0.0,0.0,0.0)),0.5),radius=rad,gap=rad*0.5,material=defMat)) floorId=[] floorId+=O.bodies.append(geom.facetBox((0,0,0),(0.6,0.6,0.6),material=defMat)) #Floor #Calculate the weight of spheres sphMass = utils.getSpheresVolume()*density*9.81 # Create engines O.engines=[ ForceResetter(), InsertionSortCollider([Bo1_Sphere_Aabb(),Bo1_Facet_Aabb()]), InteractionLoop( [Ig2_Sphere_Sphere_ScGeom(), Ig2_Facet_Sphere_ScGeom()], [Ip2_ViscElMat_ViscElMat_ViscElPhys()], [Law2_ScGeom_ViscElPhys_Basic()], ), NewtonIntegrator(damping=0.0,gravity=[0.0,0.0,-9.81]) ] O.run(30000) O.wait() curForce = utils.sumForces(ids=floorId,direction=Vector3(0,0,1))*(-1) print ("Precalculated weight %f" % sphMass) print ("Obtained weight %f" % curForce) if (((sphMass-curForce)/curForce)>tolerance): resultStatus += 1 trunk-2018.02b/scripts/checks-and-tests/checks/checkWirePM.py000066400000000000000000000114061324306050200237720ustar00rootroot00000000000000# -*- coding: utf-8 -*- # 2011 © Klaus Thoeni # Check test version for WirePM tensile test tolerance=0.01 errors=0 #### define parameters for the net # wire diameter d = 2.7/1000. # particle radius radius = d*4. # define piecewise lineare stress-strain curve strainStressValues=[(0.0019230769,2.5e8),(0.0192,3.2195e8),(0.05,3.8292e8),(0.15,5.1219e8),(0.25,5.5854e8),(0.3,5.6585e8),(0.35,5.6585e8)] # elastic material properties particleVolume = 4./3.*pow(radius,3)*pi particleMass = 3.9/1000. density = particleMass/particleVolume young = strainStressValues[0][1] / strainStressValues[0][0] poisson = 0.3 #### material definition netMat = O.materials.append( WireMat( young=young,poisson=poisson,frictionAngle=radians(30),density=density,isDoubleTwist=True,diameter=d,strainStressValues=strainStressValues,lambdaEps=0.4,lambdak=0.66) ) wireMat = O.materials.append( WireMat( young=young,poisson=poisson,frictionAngle=radians(30),density=density,isDoubleTwist=False,diameter=3.4/1000,strainStressValues=strainStressValues ) ) #### get net packing kw = {'color':[1,1,0],'wire':True,'highlight':False,'fixed':False,'material':netMat} [netpack,lx,ly] = hexaNet( radius=radius, cornerCoord=[0,0,0], xLength=1.0, yLength=0.55, mos=0.08, a=0.04, b=0.04, startAtCorner=False, isSymmetric=True, **kw ) O.bodies.append(netpack) if abs((lx-0.96)/0.96)>tolerance: print "WirePM checkTest: difference on net dimension lx" errors+=1 if abs((ly-0.52)/0.52)>tolerance: print "WirePM checkTest: difference on net dimension ly" errors+=1 #### get bodies for single wire at the boundary in y-direction and change properties bb = uniaxialTestFeatures(axis=0) negIds,posIds=bb['negIds'],bb['posIds'] for id in negIds: O.bodies[id].material = O.materials[wireMat] O.bodies[id].shape.color = [0,0,1] for id in posIds: O.bodies[id].material = O.materials[wireMat] O.bodies[id].shape.color = [0,0,1] #### define engines to create link interactionRadius=2.8 O.engines=[ ForceResetter(), InsertionSortCollider([Bo1_Sphere_Aabb(aabbEnlargeFactor=interactionRadius,label='aabb')]), InteractionLoop( [Ig2_Sphere_Sphere_ScGeom(interactionDetectionFactor=interactionRadius,label='Ig2ssGeom')], [Ip2_WireMat_WireMat_WirePhys(linkThresholdIteration=1,label='interactionPhys')], [Law2_ScGeom_WirePhys_WirePM(linkThresholdIteration=1,label='interactionLaw')] ), NewtonIntegrator(damping=0.), ] #### define additional vertical interactions at the boundary createInteraction(negIds[0],negIds[2]) createInteraction(negIds[3],negIds[4]) createInteraction(negIds[5],negIds[6]) createInteraction(negIds[7],negIds[1]) createInteraction(posIds[0],posIds[2]) createInteraction(posIds[3],posIds[4]) createInteraction(posIds[5],posIds[6]) createInteraction(posIds[7],posIds[1]) #### time step definition for first time step to create links O.step() #### initialize values for UniaxialStrainer bb = uniaxialTestFeatures(axis=1) negIds,posIds,axis,crossSectionArea=bb['negIds'],bb['posIds'],bb['axis'],bb['area'] strainRateTension = 0.1 setSpeeds = True ##### delete horizontal interactions for corner particles bb = uniaxialTestFeatures(axis=1) negIds,posIds,axis,crossSectionArea=bb['negIds'],bb['posIds'],bb['axis'],bb['area'] ##### delete some interactions O.interactions.erase(0,4) O.interactions.erase(0,5) O.interactions.erase(1,154) O.interactions.erase(1,155) O.interactions.erase(2,26) O.interactions.erase(2,27) O.interactions.erase(3,176) O.interactions.erase(3,177) #### time step definition for deleting some links which have been created by the Ig2 functor O.step() #### initializes now the interaction detection factor aabb.aabbEnlargeFactor=-1. Ig2ssGeom.interactionDetectionFactor=-1. #### define engines for simulation with UniaxialStrainer O.engines = O.engines[:3] + [ UniaxialStrainer(strainRate=strainRateTension,axis=axis,asymmetry=1,posIds=posIds,negIds=negIds,crossSectionArea=crossSectionArea,blockDisplacements=True,blockRotations=False,setSpeeds=setSpeeds,label='strainer'), NewtonIntegrator(damping=0.5), ] #### time step definition for simulation ## critical time step proposed by Bertrand kn = 16115042 # stiffness of single wire from code O.dt = 0.2*sqrt(particleMass/(2.*kn)) O.run(30000,True) Fn = 0. for i in posIds: try: inter=O.interactions.withBody(i)[0] F = abs(inter.phys.normalForce[1]) except: F = 0 Fn += F un = O.bodies[O.bodies[posIds[0]].id].state.pos[1] - O.bodies[O.bodies[posIds[0]].id].state.refPos[1] if abs((un-0.0034)/0.034)>tolerance : print "WirePM checkTest: difference on peak displacement" print "Reference value:",0.034 print "Calculated value:",un errors+=1 if abs((Fn-6458.9)/6458.9)>tolerance : print "WirePM checkTest: difference on peak Force" print "Reference value:",6458.9 print "Calculated value:",Fn errors+=1 if (errors): resultStatus +=1 #Test is failed trunk-2018.02b/scripts/checks-and-tests/checks/data/000077500000000000000000000000001324306050200221665ustar00rootroot00000000000000trunk-2018.02b/scripts/checks-and-tests/checks/data/100spheres000066400000000000000000000075641324306050200240170ustar00rootroot000000000000000.0632457 0.137506 0.588536 0.063226 -1 0.842337 0.909075 0.228734 0.0629123 -1 0.513101 0.410777 0.29259 0.0625985 -1 0.713178 0.140678 0.552867 0.0622848 -1 0.572038 0.771198 0.580526 0.061971 -1 0.510268 0.830189 0.934033 0.0616573 -1 0.698458 0.909364 0.321995 0.0613435 -1 0.435077 0.850735 0.634324 0.0610297 -1 0.88327 0.425009 0.331716 0.060716 -1 0.513094 0.408062 0.754777 0.0604022 -1 0.666411 0.538952 0.581302 0.0600885 -1 0.142211 0.596396 0.440358 0.0597747 -1 0.740399 0.122601 0.228504 0.059461 -1 0.256304 0.747201 0.133182 0.0591472 -1 0.218839 0.602966 0.569829 0.0588335 -1 0.711303 0.335885 0.446001 0.0585197 -1 0.568087 0.203046 0.675411 0.058206 -1 0.35397 0.460902 0.125286 0.0578922 -1 0.171544 0.212005 0.448482 0.0575785 -1 0.569966 0.654172 0.139978 0.0572647 -1 0.116057 0.794807 0.402336 0.056951 -1 0.225881 0.2033 0.906605 0.0566372 -1 0.935517 0.157095 0.828888 0.0563234 -1 0.608556 0.653312 0.60127 0.0560097 -1 0.577936 0.18979 0.282304 0.0556959 -1 0.765827 0.582739 0.852717 0.0553822 -1 0.296862 0.231217 0.29576 0.0550684 -1 0.407717 0.14018 0.631867 0.0547547 -1 0.133618 0.899989 0.536212 0.0544409 -1 0.0921193 0.544563 0.70964 0.0541272 -1 0.486208 0.46921 0.0602879 0.0538134 -1 0.252228 0.23762 0.563835 0.0534997 -1 0.155961 0.593446 0.205225 0.0531859 -1 0.677064 0.271099 0.647271 0.0528722 -1 0.725058 0.404587 0.790763 0.0525584 -1 0.337957 0.79412 0.52915 0.0522447 -1 0.762235 0.905443 0.869685 0.0519309 -1 0.754353 0.763991 0.564623 0.0516172 -1 0.869227 0.32951 0.763365 0.0513034 -1 0.250542 0.453809 0.602187 0.0509896 -1 0.323428 0.875137 0.101187 0.0506759 -1 0.262607 0.771509 0.44276 0.0503621 -1 0.935384 0.602415 0.0959998 0.0500484 -1 0.807628 0.265685 0.547061 0.0497346 -1 0.210523 0.519291 0.802076 0.0494209 -1 0.346678 0.445988 0.335549 0.0491071 -1 0.712145 0.375483 0.90935 0.0487934 -1 0.079367 0.0931514 0.842839 0.0484796 -1 0.56555 0.93538 0.10315 0.0481659 -1 0.109913 0.781804 0.155589 0.0478521 -1 0.933627 0.325613 0.251893 0.0475384 -1 0.607802 0.937669 0.730514 0.0472246 -1 0.21714 0.876959 0.650905 0.0469109 -1 0.713226 0.866026 0.720087 0.0465971 -1 0.205985 0.336471 0.560601 0.0462833 -1 0.404262 0.27439 0.467811 0.0459696 -1 0.399282 0.21219 0.5422 0.0456558 -1 0.279491 0.743819 0.600524 0.0453421 -1 0.790314 0.223836 0.440482 0.0450283 -1 0.173372 0.349905 0.6542 0.0447146 -1 0.872236 0.77503 0.319853 0.0444008 -1 0.950422 0.316509 0.681236 0.0440871 -1 0.875169 0.816942 0.480635 0.0437733 -1 0.10681 0.139044 0.218576 0.0434596 -1 0.44486 0.492578 0.399139 0.0431458 -1 0.0584047 0.166868 0.316999 0.0428321 -1 0.270556 0.626276 0.464675 0.0425183 -1 0.814508 0.681643 0.904557 0.0422046 -1 0.231417 0.205926 0.208882 0.0418908 -1 0.919379 0.403174 0.713806 0.041577 -1 0.171969 0.774404 0.697855 0.0412633 -1 0.23728 0.475321 0.91767 0.0409495 -1 0.372958 0.577225 0.0409206 0.0406358 -1 0.926633 0.934036 0.646285 0.040322 -1 0.716763 0.904977 0.424815 0.0400083 -1 0.588615 0.886954 0.871796 0.0396945 -1 0.146662 0.3448 0.369151 0.0393808 -1 0.319354 0.476513 0.661693 0.039067 -1 0.156809 0.453677 0.546265 0.0387533 -1 0.416026 0.40884 0.60921 0.0384395 -1 0.0818604 0.387212 0.662613 0.0381258 -1 0.92477 0.0546337 0.437419 0.037812 -1 0.532074 0.304338 0.905312 0.0374983 -1 0.581168 0.387114 0.578045 0.0371845 -1 0.492878 0.335594 0.662791 0.0368708 -1 0.192548 0.76341 0.544556 0.036557 -1 0.865145 0.877785 0.710656 0.0362432 -1 0.17073 0.719191 0.256069 0.0359295 -1 0.0826056 0.232443 0.719743 0.0356157 -1 0.769743 0.431122 0.159319 0.035302 -1 0.218126 0.38882 0.909338 0.0349882 -1 0.382319 0.603201 0.318078 0.0346745 -1 0.800503 0.477491 0.763354 0.0343607 -1 0.909841 0.522721 0.399759 0.034047 -1 0.722825 0.660194 0.668187 0.0337332 -1 0.394776 0.40613 0.713393 0.0334195 -1 0.0397222 0.0600967 0.278554 0.0331057 -1 0.583692 0.935074 0.93591 0.032792 -1 0.201732 0.607963 0.0696597 0.0324782 -1 0.165494 0.421522 0.84153 0.0321645 -1 trunk-2018.02b/scripts/checks-and-tests/checks/data/WallStressesCheckTest000066400000000000000000001552301324306050200263500ustar00rootroot00000000000000iteration s11 s22 s33 e11 e22 e33 unb_force porosity kineticE 400 49566.037998387372 48955.252378055688 50830.41890644082 0 0 0 0.0053680811782467674 0.44488693137992941 0.15903777482138973 420 49977.096201642387 48864.614561700706 49899.23302581007 0.00021267293086211031 5.8964253128234604e-06 -0.00035627549183499062 0.0068712278837924348 0.44496336859240598 0.19015952577734965 440 50036.058942961281 48968.561929892297 49972.202706320968 0.00024631280266909053 2.2978202122293951e-05 -0.00032962028724921116 0.0046803160246437908 0.44492041994286163 0.15828015151073432 460 49987.064491349811 49100.818028878886 49996.869812418969 0.00016482477500420631 5.1178199623532612e-05 -0.00028965271763504167 0.0037816477919528054 0.44492781380926466 0.13205742692258149 480 49994.801281714193 49190.913320016458 50012.447463006683 0.00017935166373592845 9.0429787702956814e-05 -0.0003017210425847727 0.0024870837542071843 0.44490466118123817 0.12684051084280809 500 50001.058302638965 49374.565212195215 50007.448078951697 0.00018012080645627497 0.00014066702764890021 -0.00032441043230233674 0.0022224589053528895 0.44488894232788867 0.12098178842261725 520 50001.972762399855 49587.995913963183 49999.900082141794 0.00016944808710761875 0.00020182366019785475 -0.0003336510480161043 0.0017217812852814381 0.44486604724526796 0.11684035515158114 540 50004.546643027519 49829.569522149781 50002.063374404788 0.00015293821005952561 0.00027383480127565057 -0.00033552983965115383 0.0014274035215790576 0.44483627879208898 0.093197709146506172 560 50014.752514428495 50128.491462389895 49999.362303495283 0.00013745665316053127 0.0003566341897850414 -0.00035477664315635164 0.001810806332081626 0.44480959085979099 0.090567782649043263 580 49998.209499657431 50451.165301212961 50004.929553092625 0.00012292085491632948 0.00045015702910980806 -0.00037052798672515167 0.0032703829593221777 0.44477448189695884 0.074286413598736653 600 50005.359621062576 50841.94276532701 49998.114355201702 0.00011148388275992605 0.00055434040164428322 -0.00038827295699470272 0.00324864200831451 0.44473283762739801 0.067260565073482936 620 50005.62808209275 51223.817503700709 50002.297374122194 9.3364590503689941e-05 0.00066911667837191028 -0.00040761356222081399 0.0027744067202789061 0.44468990472120251 0.060230488047957942 640 50002.963006223261 51593.510830551415 50002.522355566296 6.4891961117484562e-05 0.00079442596497925619 -0.00042728310164930858 0.0024974079932179433 0.44464705138825594 0.056768052647139473 660 50004.47502109543 52064.593028300187 50060.399737517502 4.509840815728011e-05 0.00093020215871885813 -0.00045563276377594145 0.0023348230672856829 0.44459838202267599 0.055823130576791349 680 50003.922870151539 52611.469521519139 50002.386481644186 2.1435372258399064e-05 0.0010763822755367532 -0.00047974978058954181 0.0019602328835925582 0.44454372777891621 0.052284334705629293 700 50005.94855032242 53107.720732221416 50002.9976237222 -1.1067175470784068e-05 0.0012329070304957727 -0.00050266090888447087 0.001700832071693125 0.44448756215649532 0.054150188884905824 720 50006.389210929665 53640.921536506714 50005.863747930838 -3.8972971035134305e-05 0.001399704940235198 -0.0005314267240745 0.0015983052595450757 0.4444263822589794 0.053756805581121142 740 50005.156937890941 54222.543643777644 50005.178795685075 -6.9183933860726208e-05 0.0015767234983194154 -0.00056735460610184097 0.0011985073828479787 0.44436477700024168 0.054175464615800119 760 50008.210969832144 54850.728756560879 50006.081722901501 -0.00010235457649716767 0.0017638953467352973 -0.00059744222312149199 0.0012058904190005151 0.44429592197977025 0.054427738954411171 780 50005.52909633646 55495.565329390651 50003.648296804757 -0.0001431571692497864 0.0019611592113214539 -0.00063525408506367109 0.00097245312354638273 0.44422998410717152 0.047408125629938444 800 50007.241971481664 56201.644209122518 50003.733952172523 -0.00017732527726025245 0.0021684607289895862 -0.00067409328957867567 0.00095470297046397631 0.44415534240278287 0.047710030227362768 820 50009.764236139432 56938.971703308722 50003.033155897574 -0.00021925042124761637 0.0023857234018523979 -0.0007115680821681747 0.0014709716248607935 0.44407870685486495 0.046978302688125716 840 50008.137044221599 57671.239697471647 50004.816766807031 -0.00026076512789549914 0.0026129032370259117 -0.00075293276112191787 0.0015706428060102838 0.4439984813741904 0.048442529104732533 860 50008.892317611229 58461.365835084231 50007.302284468009 -0.00030509791027016095 0.0028499306693101785 -0.00079423647157349816 0.0015806260960581043 0.44391430141006438 0.043304491893954772 880 50012.162714007216 59279.119880352977 50004.862174897949 -0.00035749338808990723 0.0030967460971833608 -0.00083516851320289453 0.0024587185446566092 0.44382894242866483 0.037493682209173484 900 50009.327535467215 60080.367665305683 50006.633557880443 -0.00040197115804317755 0.0033533009308541646 -0.00087852547254407658 0.002224651641544069 0.44373509727322086 0.037691364159763999 920 50014.793095669549 60938.44179850287 50005.405045302323 -0.00045479936801977886 0.0036195120495683326 -0.0009325240434291199 0.002174824547336886 0.44364643029415307 0.042658442863613033 940 50010.70559567857 61815.820562935856 50006.634810485935 -0.00051784859990702001 0.0038953454932306826 -0.00098269073668170938 0.0022004164942143159 0.44355595010059334 0.040888328551092396 960 50004.363902876139 62695.940881212788 50014.064795553524 -0.00055973177700115173 0.0041807280182940528 -0.0010570079897317844 0.0018586161521949879 0.44346180176664457 0.042691464267911455 980 50017.397488818235 63679.019521287853 50009.460488662917 -0.00060770180912511987 0.0044756016278084693 -0.0011288055169523987 0.0018421705180351447 0.44336434002816233 0.046858128898113138 1000 50008.491219792108 64594.160565640654 50005.533808335676 -0.0006794766661242824 0.0047799248681691664 -0.0011968827217597241 0.0016959790704666143 0.44327278197601738 0.047474298047119522 1020 50011.49623842779 65600.190316090346 50012.64758585854 -0.00073440428810715795 0.0050936037380713431 -0.0012706431296587101 0.0016224446932724182 0.44316978301365434 0.045992318862660933 1040 50011.121662242003 66583.505970307335 50012.57961763152 -0.00079797857310266143 0.0054166183976931273 -0.0013495043768270028 0.0014876894783416557 0.44306922201959376 0.051068537806104125 1060 50012.81036957405 67598.623818575463 50014.84438847912 -0.00085578899933674022 0.0057488918948768583 -0.0014371918555643056 0.0016629589752940118 0.44296519122881128 0.050374157731787418 1080 50015.114556055763 68640.092420288682 50015.315531424734 -0.00092311339800707612 0.0060903678409575512 -0.0015247805057382116 0.0015852012972787978 0.44286125950642508 0.053366554192070939 1100 50013.728691172386 69699.192829950349 50011.160910325729 -0.0009853195067314306 0.0064410118673955217 -0.0016138055181873032 0.0015831076714283051 0.44275014777335953 0.057455759726266861 1120 50010.810116044704 70733.000214090513 50013.705776152812 -0.0010661662778355483 0.0068007085840794792 -0.0017122437475882511 0.0015518975725231741 0.44264960430184136 0.058558365860792959 1140 50009.026204664653 71776.587492003702 50015.942860265808 -0.0011316904216435591 0.0071694624890985973 -0.0018158156483994625 0.0017149908396863901 0.44253831380470171 0.05415957558237023 1160 50018.667089896604 72875.493201932492 50013.333715999965 -0.0012106696219497306 0.0075471776080603143 -0.0019274405061449923 0.0015990832450179305 0.44243399679805245 0.061192039283979698 1180 50016.15212889963 73973.362972415722 50013.680999619311 -0.0013088042885596801 0.0079337986058462696 -0.0020285809571168392 0.0018347593174341743 0.44232952931894032 0.067540670648322962 1200 50022.069512445865 75124.810209945164 50016.229737059344 -0.0014051875576485216 0.008329313252456479 -0.0021253896633859899 0.0019068210163981707 0.4422166885248931 0.0697953189638576 1220 50013.678711623026 76245.324262376322 50015.00639442441 -0.0015046130828248352 0.0087335482107491948 -0.0022373949305522899 0.0020025259530435115 0.44210913521071282 0.069127260601857754 1240 50025.896094259748 77409.032316124285 50014.988539847582 -0.0016064410272227038 0.0091465681130706925 -0.0023366018980840145 0.0020658102884513743 0.44199085818506667 0.07198189757756443 1260 50015.954368970517 78587.044802919801 50018.478542287499 -0.0017218227024756235 0.0095682475078417515 -0.00245040311198283 0.002292345262936305 0.44188343303601246 0.08454884024886547 1280 50018.095476946932 79738.124150619638 50014.534301501139 -0.0018256445487115692 0.0099985319169222618 -0.0025806780458952068 0.0022874601342012619 0.44177392672853194 0.09764042476589764 1300 50030.65797566816 80920.354866014313 50014.490254409859 -0.0019458213554588793 0.010437441576343994 -0.002694645312997488 0.0027198129974564502 0.44165960953604277 0.10271063008236009 1320 50012.038252986713 82078.499978884211 50015.776798700048 -0.0020640863280565508 0.010884742932449567 -0.0028349506515926888 0.002758351795918186 0.44155422342623107 0.12517899118391776 1340 50022.072309239811 83264.808937098205 50020.224676926104 -0.0021633253101561159 0.011340563181045945 -0.0029819228578914681 0.003150579192822038 0.44143715586304955 0.10928972384026862 1360 50023.351005747929 84557.013150444851 50017.158685400595 -0.0022943790430305856 0.011804741573059318 -0.0031179011818638683 0.0034009226467739666 0.44132702638991356 0.096016691629119993 1380 50016.077590483656 85800.993930786994 50024.889794607101 -0.0024204195304700616 0.012277224479966689 -0.0032613361323940515 0.0035398513254209668 0.44121360009077365 0.10358518398584944 1400 50027.253927804952 86929.933574173658 50028.103706971029 -0.0025700347505772979 0.012758070360127082 -0.0034147417082690634 0.0031638394270315211 0.44111422501674358 0.13614685479436714 1420 50034.342183972702 88253.692664038696 50030.00224640769 -0.0027244532683565409 0.013266205328087678 -0.0035946403000395666 0.0037375973664536787 0.44101707224504094 0.124680339248309 1440 50032.604734863715 89548.542985064094 50023.47663399476 -0.0028862285573388042 0.013782924067679756 -0.0037477966483777324 0.0037266036366262719 0.44090426531872134 0.12181755201776356 1460 50027.868231740504 90982.364975616991 50018.835985097794 -0.0030578272615698382 0.014308175226444052 -0.0039119014147398334 0.0034038006966997181 0.44079827997042459 0.13958962733005151 1480 50018.237616149301 92122.048315490509 50032.194445367641 -0.0032039731089447063 0.014841907759114101 -0.0041347644652315737 0.0033076684025706671 0.44070615864344137 0.14318717953766491 1500 50036.647941486532 93362.499424031586 50036.682400874764 -0.0033632834611440282 0.015384070925782211 -0.0043612461423623763 0.0042297346497067005 0.44061869439109813 0.15591968890200064 1520 50023.829950315892 94621.134132044463 50037.635641959707 -0.003528648099858932 0.015934614290071189 -0.0045974500905229835 0.0041834949284867825 0.44053535447798753 0.17341662819570747 1540 50044.955182856953 95632.448123411363 50028.16857467041 -0.0036972314610840503 0.016493487717318473 -0.0048586893052997276 0.0049062023435101545 0.44046315043034445 0.20289514060600553 1560 50031.882272567214 96943.174409213563 50021.830731646798 -0.0039311629251763394 0.017060641372773154 -0.0050772681238670679 0.0053216926406885086 0.4403989995615411 0.23577512401476572 1580 50027.069411980367 97692.654457625293 50031.784404510938 -0.0041654289916403702 0.017636025719800514 -0.0053074060663011414 0.005679718332583154 0.44033689140696669 0.28062806002856971 1600 50028.654889161313 98670.955135255121 50033.593865106508 -0.0044012943659611998 0.018219591518100212 -0.0055963618870513761 0.0056094864891141783 0.44030401325401303 0.37337449145173135 1620 50049.073577087132 99437.989876198582 50026.641615990004 -0.0046413361183579331 0.018811289821932031 -0.0059021965446735044 0.0059322856423293458 0.44027836633635009 0.42209722652235232 1640 50032.54229274745 100272.81887449691 50046.986158055552 -0.0049025537768449751 0.01941107197835662 -0.0062068218898119311 0.0069338145825944483 0.44025936953593442 0.41991866397734395 1660 50026.392708482643 101264.87297387703 50032.466465935511 -0.0051346251856785805 0.020018889625481616 -0.0065491121362623046 0.0077344711694811432 0.4402406425447053 0.51309872026835979 1680 50035.060743615613 101753.16189470836 50062.64703046513 -0.0053243353370055133 0.020634694690722079 -0.0069747838457121193 0.0077266903094270322 0.44024040565199635 0.6763880993571274 1700 50046.751198339334 101944.63477102852 50037.409165908437 -0.0055837883527159607 0.021258439389070589 -0.007465914851316903 0.0070602226907480418 0.44031140067739194 0.78130877791305786 1720 50052.944974450365 102385.48903940769 50018.862197936527 -0.0058731706395913231 0.021890076221375256 -0.0078920676471882483 0.0074845135298195614 0.44035835560192188 0.66625826110489905 1740 50031.023114860465 103006.77414740455 50061.914731141827 -0.0061213217307384653 0.022529557972632272 -0.0083507062766330145 0.0074331354195302634 0.44039602267690026 0.67975996468300759 1760 50036.049657946511 103205.89696227368 50057.30369610303 -0.0063377682139140494 0.023176837710283629 -0.0088669943885356182 0.0073982335761506798 0.44044384151179294 0.7932185593293769 1780 50045.484456738035 103669.88030901441 50031.664492022232 -0.0065841108425803967 0.023831868782530713 -0.0093568390403409949 0.0073309283088975512 0.44048925112524356 0.7479722541667897 1800 50071.599267962119 104473.99172675019 50045.34515670751 -0.0069270324096878996 0.024494604816652641 -0.0097025278419885855 0.0087415221584656397 0.44050372790630155 0.76256362248519249 1820 50045.408019729715 104996.86529545336 50051.352552736171 -0.0072564626248444869 0.025164999717337426 -0.010200501497453978 0.0095591188572324796 0.44059156694372786 0.80802620388907109 1840 50020.114563618445 105207.50162796638 50024.337561191722 -0.0075787225880916496 0.025843007665023905 -0.010705352954276132 0.0090764810838837237 0.44067497046554488 0.95932340124958504 1860 50098.158083603521 105624.19390702808 50044.239418358993 -0.0079941029724468482 0.026528583114251044 -0.011108331507657958 0.009942143149529523 0.4407492346635386 1.4646274041283049 1880 50077.137742226427 104721.95031854876 50025.937907908148 -0.0085829224358547722 0.027221680792019164 -0.011731998034881293 0.01184217701278502 0.44103962755514897 2.5955395784500794 1900 50042.447903319226 104460.21774512067 50101.767889549934 -0.0090268146408970362 0.027922255696159335 -0.012498178311617444 0.014894793951447154 0.4413243439735835 3.3486606532104912 1920 50117.407538366439 103244.20147336698 50096.54741719483 -0.0093818567000053966 0.028630263093715188 -0.013285419304981338 0.020454474581742647 0.44156691053580166 2.4123285848201577 1940 50023.561152537506 103546.78717835245 50017.851949860633 -0.0099107955681736089 0.029345658519329684 -0.013788625820808705 0.016995045165877208 0.44174376618265998 1.2695587930518211 1960 50066.478515282237 105506.71997235318 50070.854822389796 -0.0099953697847823837 0.030068397773648038 -0.013989481507105227 0.016502560770350935 0.44149958212303231 0.74959335603261867 1980 50055.218364083776 106470.56285684057 50047.006079563776 -0.010354999347766783 0.030798436921723842 -0.01447592328798549 0.014036593309540097 0.44156438239333579 0.76524589180791291 2000 50037.957411792617 107086.89763233457 50049.945423267403 -0.010743823498068909 0.031535732291439841 -0.014956074297929958 0.013610013084600726 0.44163791223718374 0.98565290964516661 2020 50023.930373970928 107576.50003335536 50080.855182619103 -0.011036606124594242 0.032280240471932065 -0.015583193505635416 0.013633915385663437 0.44173583681593959 1.3919572483807254 2040 50034.116870266895 107277.53048704931 50129.685434873376 -0.011560469220533314 0.033031918312032227 -0.016207965210168752 0.013342577144751632 0.44195739968287151 1.5796899148821604 2060 50002.967084709766 107431.93092879855 50092.9438935215 -0.011841731384045092 0.033790722918710409 -0.016758514791115169 0.012915235245496585 0.44199813928872089 1.8043486495279693 2080 50050.024285545456 107256.68858094858 50062.800115990001 -0.012102320417867135 0.034556611655532816 -0.017517493627318578 0.014525080963021691 0.4421396747640145 1.8556825031626687 2100 50089.36577645842 107159.25534421443 50034.740771251221 -0.012408896767204391 0.035329542141125797 -0.018305339650249323 0.015344576428232464 0.44231899350617088 1.7992420984197748 2120 50021.904694440091 107023.21612544126 50098.553316720987 -0.012868372794837879 0.036109472247651112 -0.018788975564288796 0.014261382977159674 0.44240998949158955 2.0003416593989103 2140 50048.8999784798 107600.00426911762 50100.986919461793 -0.013289248940402015 0.036896360099290058 -0.019459324511684282 0.01415147850394308 0.44257965907747915 2.4503120981604263 2160 50057.767742983822 106966.76034140858 50112.383555349516 -0.013666881635198937 0.037690164070733806 -0.020114669222973159 0.015953850421035132 0.44271296327291049 3.4046325506377215 2180 50031.738209726689 105601.22070001217 50193.094145471383 -0.014072835404353166 0.038490842785686583 -0.021094819648400129 0.017813782221918373 0.44303911782476946 4.2747939037051434 2200 50045.61944168734 104193.65340021162 50182.852503755872 -0.014367439894810418 0.039298355115376611 -0.022286232987822147 0.020598240256925741 0.44341689067174545 4.2275158765216849 2220 50089.45611808687 104176.63884105172 50061.984928810991 -0.01472314314007845 0.040112660177074135 -0.023164995112455157 0.024892860429468491 0.44365069568126964 4.2520680739529979 2240 50058.038475687339 103524.16968275803 50023.880178354986 -0.015084067191479377 0.040933717332620051 -0.023793284660347233 0.025286809654837063 0.44374424153637548 3.7203408670045639 2260 50168.016998530387 103757.9367315932 50114.728108466697 -0.015508642796365086 0.041761486186963986 -0.024434205733519281 0.022585493758371194 0.44387646329024577 5.2376278048141591 2280 50066.154376504091 102625.14071024147 50072.673666803814 -0.016193369468327425 0.042595926586708847 -0.024896239729927369 0.028815796538963979 0.44405012482220013 6.5958716570176588 2300 50095.475598185956 102205.30697734408 50313.236137830187 -0.01668323198515587 0.043436998618666192 -0.025739737373656875 0.032613819900294383 0.44432374499034094 6.6460624074859203 2320 50107.048845382888 101229.42600318159 50077.748662481186 -0.017270088055392804 0.044284662608419276 -0.026554883687968572 0.034510516109688652 0.44463169231217131 5.7284702403524621 2340 50032.115903799116 100443.18238693199 50157.605449350005 -0.017779949007206093 0.045138879118893742 -0.027130916217440203 0.033159040323244682 0.44476034345605142 7.6486509588168472 2360 49959.159154566361 99213.133640599481 50194.947806572891 -0.018090608974819324 0.04599960894894016 -0.027988821272631324 0.031236792847686969 0.44493123945749918 10.483627182652954 2380 50080.352213525141 97741.072645143984 49976.085101021468 -0.018431835024295781 0.046866813131920669 -0.028971479745674784 0.031635255279680585 0.44518467057462263 14.97863187336957 2400 49925.527914324819 96377.450424635565 50209.959220344441 -0.01925980856497712 0.047740452934306794 -0.029233590449011738 0.038769332267959492 0.44530474427369782 13.277202251514449 2420 50094.811035027662 95417.876121049965 50101.414513769618 -0.020178839023873237 0.048620489854286117 -0.030068872815746626 0.037248584490521264 0.44578948903954752 10.417692391758223 2440 50063.032538551008 94233.689671771877 50182.312622786711 -0.020598234472496382 0.049506885620374821 -0.030847564146181713 0.033188257617429467 0.44596220455677149 9.8896105198212574 2460 49886.264282782875 94411.736135222702 50197.467998668282 -0.020904141360022558 0.050399602190040749 -0.031466093231908347 0.037283087693346477 0.44597977802649624 9.3713745957858663 2480 49948.127003231864 94964.146259022469 50259.337910260059 -0.020783064607431314 0.051298601748333193 -0.032489708572608376 0.037845660503414015 0.44598173871711116 9.3859134973463796 2500 49973.018299094125 93230.590834916133 50207.454742429072 -0.020989199537296426 0.052203846706523171 -0.033722007442186536 0.035496090384491252 0.44627705633485221 10.486170649541606 2520 49887.196631465886 94351.635225728125 50141.531234835209 -0.021074396130979139 0.053115299700748013 -0.034767255324970658 0.035226422397011924 0.44639830366648586 8.9197518356478014 2540 49971.837131304666 96089.700825341934 50242.05156054665 -0.020894553301753944 0.054032923590667498 -0.036045817183876158 0.033241268113566737 0.44649854916579479 6.3403542859992292 2560 49975.654157802019 96862.15909324598 50090.332018407527 -0.020692568160111266 0.05495668145812755 -0.037005480971010737 0.033082265822431914 0.4464066164416356 3.1852292772837139 2580 49978.40854682459 98362.411479333838 50057.161388500761 -0.02089513084847093 0.055886536605830095 -0.037757785025495691 0.026642157817290828 0.44642046252206158 2.6472451182011061 2600 50039.197202076968 99958.539650526829 50099.444270105669 -0.020967135077546144 0.056822452556011351 -0.038426166366049425 0.022655983130501572 0.4463122103214805 2.2735305198713167 2620 49993.486374968423 101661.18510429813 50058.075242620551 -0.021152514129196155 0.057764393049130246 -0.039234259826504948 0.020345189526586264 0.44634074223601095 2.3512367896239708 2640 50016.835142643722 102411.41004167231 50110.154579567134 -0.021339300900518315 0.05871232204256243 -0.040075023281989128 0.020123640306739084 0.44638482351405701 2.5739207512390188 2660 50014.350907291933 102846.81304554416 50140.186984754451 -0.021575706762253205 0.059666203709301611 -0.040901293480644314 0.018261363021210991 0.44644505046526206 2.8475097874510209 2680 49987.10940005567 103308.14569299581 50031.87200509385 -0.021774505011672194 0.060626002436671968 -0.041677203458255735 0.020763015186438308 0.44645330363106861 2.2974695280820705 2700 50070.451157061194 104639.42468698665 50082.089918223297 -0.022227049663096264 0.061591682825044548 -0.042383218352177954 0.018945727614739501 0.44656006095524731 2.5812079586303494 2720 50084.922680131342 103371.79324011739 50142.451981733866 -0.022872598758877746 0.062563209686563551 -0.043235314516086372 0.01945002841859567 0.44685115930664748 2.7457491767051785 2740 50025.565005148688 104112.26416941165 50118.4560607276 -0.023284894645899022 0.06354054804387832 -0.04412798943404414 0.019158453389198696 0.44703235913015488 2.7409633149239934 2760 49971.016248619162 103916.80794354696 50186.984416762483 -0.023485688356067624 0.064523663128885009 -0.045118860066417819 0.01922199590478528 0.44714766809688189 3.7097346052809193 2780 49999.969443384172 103180.75405786106 50261.83853791048 -0.023717227361326716 0.065512520381476064 -0.046377249901182978 0.020118780146944539 0.44742461730096139 4.696749129684135 2800 50035.723813618017 103072.41622448927 50074.525162355763 -0.023805255035777632 0.066507085448294895 -0.047568467418295214 0.021805942336582573 0.4475819021408427 4.360998764904215 2820 50019.843741155892 103982.08339529298 50158.579120367292 -0.024068179798318809 0.067507324181498268 -0.048440675710371708 0.02160950436705002 0.44765641517948451 4.4766008049520432 2840 50001.524219838633 102459.97945496994 50225.678655654738 -0.024515054908881256 0.068513202637528248 -0.049619737496751075 0.024941828806950526 0.44799879432302742 7.5178911465442715 2860 50041.194938008419 103037.13274896091 50198.959057707711 -0.024827560963909416 0.069524687075889768 -0.050982996659213811 0.026420906509421107 0.44836535635048091 6.2469935720330572 2880 50009.744496722647 103753.36825687817 50031.474385202964 -0.02513614965142633 0.070541743957935818 -0.051750016922886499 0.027721202968505814 0.44839765475469939 4.1191490648186715 2900 50043.599387483751 104345.77897997176 50108.349703247921 -0.025439873067357271 0.071564339945661276 -0.052189949077907041 0.026382584864587836 0.44824376910515745 4.1948209180742575 2920 50006.413599562642 105948.81714641505 50171.779912372825 -0.025755982611194255 0.072592441900500002 -0.053036327593584141 0.025759216047203409 0.44831791249372549 4.2055406962694057 2940 49961.082571042265 105686.58016338191 50184.671873486484 -0.025933788512487337 0.073626016882135048 -0.054086040308276892 0.023330932046146415 0.44842489734793178 4.9223377895914924 2960 50042.93095380054 105815.28557547773 50050.633582908813 -0.026241807150902925 0.074665032147309712 -0.055043664885986991 0.022213029506276877 0.44854988551979968 4.5767756046106385 2980 50092.590142105117 106150.1008047896 50108.250828197743 -0.026677436440213986 0.075709455148651489 -0.055919598659081866 0.023854929902847793 0.44869718026201666 3.4353172344026177 3000 50080.063064569644 105713.9271752639 50166.145024836616 -0.02729853464759488 0.076759253533498148 -0.056702630667593006 0.024521462222008523 0.44889248979535906 3.2050855716545223 3020 50078.460676936273 106641.13410127394 50208.061639882217 -0.02781691186880389 0.077814395142734541 -0.057422189352730281 0.024005082258192072 0.44899321989859425 4.1626591907721506 3040 50065.761249103365 105761.6981661762 50162.58257071409 -0.028186553430785275 0.078874848009633361 -0.058360093494423178 0.023528386553481596 0.4491293529075337 4.5720948743942902 3060 49982.939733245497 105327.9562790679 50201.004077775789 -0.028579592483649566 0.079940580358706381 -0.059421638975927615 0.022953367366782832 0.44934351852038901 5.4996289278798143 3080 50004.340353718064 104829.78285165525 50112.119544108187 -0.028886393074946615 0.081011560604558427 -0.06039504653894627 0.026324192112294025 0.44945871917227498 4.3360343391694318 3100 50029.202034622809 106609.71955525738 50050.975998698661 -0.029141616176483118 0.082087757350751356 -0.06093996002022066 0.026410471653239918 0.44930671567625141 3.6507124860417952 3120 50098.002028462317 107523.25595239746 50248.009168478791 -0.02968491390181309 0.083169139388673111 -0.06161092752467285 0.023193437842164474 0.44937988869576856 4.1840165630219728 3140 50010.747347021083 107776.81045679565 50047.985865229253 -0.030310729762126516 0.08425567569641515 -0.062488479887060022 0.021152140698969292 0.44960935690301967 4.1770408282792868 3160 50097.673050351666 109192.67776438837 49939.123407607527 -0.030902389890003799 0.08534733543765359 -0.062931570626002825 0.024231729281372599 0.44957803389976064 4.9526473412740524 3180 50115.385723937725 108547.24543990393 50080.450971371458 -0.031834880252995558 0.086444087960542274 -0.063400758215858694 0.026236905939744374 0.44974584596804873 5.450471411747972 3200 50088.717743795394 108863.05213060133 50150.818909591551 -0.032620038598678389 0.087545902796606248 -0.064152080641781545 0.027105323490528809 0.44998497073123744 7.9305742564750528 3220 50157.049710513536 107282.68147491053 50077.307117619181 -0.033772737927852695 0.088652749659646368 -0.064647550473535736 0.032939475376113289 0.45028262555988791 11.167022499808787 3240 50064.988669244616 105811.94015112838 50190.170525255031 -0.03518506014348307 0.089764598444648194 -0.065377072342140491 0.037798414249486129 0.45084854037663646 12.373811140482278 3260 50107.130155878629 104311.05335370034 50201.995770446585 -0.035941954418917128 0.090881419226699053 -0.066437978928885338 0.040153548015007576 0.45123334972320545 15.021284202158963 3280 49989.752607053313 104284.06620587679 50102.351299536327 -0.036390727746359604 0.092003182259909652 -0.067308926454696549 0.041495753681634259 0.45134197162137474 18.137698365690301 3300 50066.095359296945 104760.53647509939 50116.568134288005 -0.036902042836601236 0.093129857976344294 -0.068015880227324063 0.042623220709715942 0.45139222263515111 18.896187625441534 3320 50229.307438467469 104883.50217199062 49991.945291625954 -0.037353472631559033 0.094261416984955188 -0.068558672205064147 0.043270747558399304 0.45131687318538322 22.56450569388408 3340 49946.215351722334 106693.58196373694 50014.805005598842 -0.037810391786075814 0.09539783007052563 -0.069113138394192253 0.03981826268714616 0.45124826828433645 26.048253780980687 3360 50014.012198671226 107096.296347782 50060.907712115433 -0.03804933952342883 0.096539068192616512 -0.069898405146830905 0.039618289620963525 0.45118404760597508 27.990983106183002 3380 50140.211318633737 107323.37491003894 50229.983741574244 -0.038591858177275555 0.09768510248452196 -0.070594573528073634 0.035923697574768022 0.45123489455387666 32.791023398658133 3400 50006.99338297655 105616.84307817421 50469.840544477018 -0.039403421621185111 0.098835904252230058 -0.071500058155977819 0.042558304040042218 0.45154554280664466 41.752075200785256 3420 50032.384136116882 105142.04937690096 50182.449711930291 -0.039690016446773578 0.099991444973389867 -0.072982373151776053 0.06463216270228038 0.45188184467443809 26.959341779903738 3440 50117.440376004888 105865.24987449023 50137.005965484888 -0.039838414690099237 0.10115169629628361 -0.073979702752167281 0.053920999498254671 0.45187388403401485 13.427869805189703 3460 50160.954608884989 107281.85068603099 50105.630393850006 -0.040691117353725936 0.1022936238573396 -0.073866903372134862 0.049182209125206981 0.45165347972731307 10.455135363297217 3480 50151.319024224416 108646.73698319003 50133.304154756501 -0.041617464863323995 0.10341706203581209 -0.073991306339179955 0.040467204388432564 0.45161362023031842 7.2611780367118621 3500 49996.608518417765 109104.26555026969 50219.725887001674 -0.04231339054541812 0.10454494421310405 -0.074648991007418078 0.035135673428702788 0.4517373925159725 6.7599905770569997 3520 49982.039372811487 109288.51913299966 50133.290519047638 -0.043074020633809294 0.10570674681868725 -0.075500455907136887 0.033062017094569615 0.45198421541634032 6.5968737417552683 3540 50046.720078935949 109551.2658747245 50175.829523562024 -0.043710385832956639 0.10687305482192992 -0.076541823955784391 0.032179924825819974 0.45226441286685726 6.3844465707510194 3560 50075.5500781921 109413.11157502643 50171.527284730866 -0.044318397000346253 0.1080438638269028 -0.077467362073006654 0.030122247887216614 0.45246306259559499 6.7809142091671992 3580 50102.866215361522 110011.50545598569 50161.687381724681 -0.044894189365662439 0.10921914716879653 -0.078357742064052771 0.029982432387913278 0.4526223119126403 7.5057333508876347 3600 50099.102323033541 108783.4588877551 50229.1221681971 -0.045710301441362282 0.11039885569808457 -0.079530601693966127 0.032054000431332566 0.45306510532678329 8.2382880759676524 3620 50048.809037688115 109188.80257988737 50188.367662612371 -0.046235510094064902 0.11158310270784072 -0.080705172731520447 0.032248691891543217 0.45334699547105956 9.0855950222970776 3640 50066.200426433803 107617.60632739859 50274.662026584454 -0.046673281914391032 0.11277172263025365 -0.08213539565058689 0.032137398848930528 0.45371825163064061 10.86399057097996 3660 50069.054128667536 107915.87019969609 50136.374728049326 -0.047432716382091865 0.11397333607291479 -0.083459226304009856 0.03245138638192531 0.45419966959834013 13.671071122720528 3680 50058.407158414135 107541.80128372999 50355.791272336741 -0.047784151598714852 0.11518796391822056 -0.085038334194850712 0.032810206833902358 0.45459027654754391 11.038107312435823 3700 50101.962176965979 106030.01417435883 50204.931926564655 -0.048236796443595985 0.11640694886440379 -0.086609810870417636 0.031605526672540588 0.45502922913175292 12.453537504531631 3720 49915.553815778308 105147.45452916318 50187.267482823569 -0.048646964332208727 0.1176290361947832 -0.088349175107700975 0.032781568561340395 0.45553442512999159 13.202129561180008 3740 49917.108844730428 103151.96109644348 50311.003440130415 -0.048930657078957537 0.11885542432337838 -0.090187499112608066 0.045754716646720546 0.45602184577659294 16.202006871029894 3760 49984.070209555306 101352.02986806011 50067.154537130991 -0.048922626228139525 0.12007814383799592 -0.091664586117940816 0.049125782161862162 0.45615583102386648 23.978864319930072 3780 50044.019557379412 100683.07586660807 50136.83380120212 -0.049257907990675399 0.12129712809318488 -0.092504177956158465 0.055265539971470351 0.45613184117256184 17.716131726346745 3800 49908.525796084316 101247.22208335517 50220.631759272626 -0.049456438709295586 0.12252028193058412 -0.093467759545974666 0.046733351493804927 0.45609864161532726 17.148672587635936 3820 49932.803005265567 101608.76742115419 50420.133903259288 -0.049028380920548148 0.12376486749452843 -0.094820843295798901 0.048493959153649992 0.45592480493913257 18.92904352833968 3840 50043.232236240794 100543.19254691181 49999.054803670275 -0.049149792835097648 0.12501363093074752 -0.09654913295311035 0.04222713730385981 0.4562516622521528 23.890720761862802 3860 49955.891984834168 100582.04830035544 50297.30166260089 -0.049179257288293046 0.12626654724418657 -0.097809750717597288 0.0414880081823195 0.45627187077526959 21.040805058606871 3880 50165.859168838222 98306.38907650164 50076.614621252847 -0.049647571344596381 0.12752359158932369 -0.099333318076888555 0.038484095291572748 0.45667127559416826 16.704842049914898 3900 50054.252964016778 99859.557528804828 50261.314120502335 -0.050047957902431613 0.12878473926927489 -0.10061664356628815 0.042197795593859617 0.45690081845109209 8.3091189233139957 3920 49968.684176808136 99431.936190941939 50115.512666453797 -0.050376270357491919 0.13004848618480014 -0.10170640653728889 0.03604152077434547 0.45698462767161141 4.8485548942135592 3940 50046.339853133795 101993.49052179091 50158.702962222102 -0.050662743502080616 0.13131628274240992 -0.10260140283944816 0.031908023892442398 0.45693774869993076 3.896997738139834 3960 50087.528417672896 103321.41356447572 50142.607432661338 -0.051030967951850602 0.13258211579181323 -0.1034415286939812 0.025426194923723701 0.4569065310937373 4.066639875781366 3980 49936.345297718086 104132.00448335746 50012.415733090369 -0.051567566959470429 0.13384593303669939 -0.10403609003168079 0.023821817739031245 0.45683448121912645 4.0836335236958439 4000 50065.031047777877 104907.34299680174 50119.736700025016 -0.051940510389694675 0.13511369024369366 -0.10482702352365925 0.022461953152791123 0.45677805409956468 5.3030434334586216 4020 49975.064370617569 105843.59153950163 50277.300266946651 -0.052263056136346409 0.13638570893439006 -0.10589368814805258 0.024740992782993604 0.45684171146207764 6.3127462650024748 4040 49985.144562282119 105847.37067266399 50139.885879787093 -0.052406954140328511 0.13766162164117474 -0.10709369411009029 0.024404531356895114 0.45687864022264857 7.8747958729707142 4060 50081.752165865604 106517.50942146609 50197.27933939897 -0.052367382230904244 0.13894187520193954 -0.10821101890263561 0.027223657820573798 0.45676864664273947 8.4167060441658137 4080 50027.173396693819 107871.16492367948 50097.967067121201 -0.052831109829811464 0.14022644857566274 -0.1092748620697948 0.028535389318297393 0.45690063440805351 7.1948945169089038 4100 50127.877998879172 108504.13866269839 50071.443861507287 -0.053398959873801596 0.14151484930003685 -0.11021215800635908 0.026374249379486691 0.45701833586310325 6.4111204617006248 4120 50062.966820070586 109531.65431686211 49933.662335976471 -0.054001908635182783 0.14280742911990565 -0.11085095001954641 0.026260011241324651 0.45699073049197364 5.3154481386637444 4140 50151.8052324936 111195.72641807907 50115.401931275715 -0.054624048022236443 0.14410379172776086 -0.11131447567100718 0.025968401789382856 0.45687630770488058 3.3961402345512743 4160 50022.629601738969 112375.01607827688 50091.290723700156 -0.055256638792716126 0.14540407982021972 -0.11209136961920871 0.022990358869079099 0.45693561174174124 3.1608324989923107 4180 50063.496233278696 113492.75662687025 50053.352549518364 -0.055702363661354108 0.14670827161296349 -0.11294757608486435 0.021560082360057266 0.45693438416586835 3.7215118102865521 4200 50096.054371988939 113723.3623721496 50114.976049108722 -0.056210093337546778 0.14801617964896813 -0.11390036482871352 0.020030253716520879 0.45701725529440007 3.7288750987403634 4220 49997.019463741941 114843.5579979657 50112.165855272862 -0.056927861389379407 0.14932807520787295 -0.11482091716269306 0.020260818434585409 0.45719446941773589 4.4539753524562196 4240 50113.4992876834 114535.21485934216 50122.467254872412 -0.057577874117838053 0.15064364349810166 -0.11585844931298575 0.02433944724147737 0.45739634281881603 5.7837580416346119 4260 49993.844328834857 114437.29643008651 50194.225376606933 -0.058274716378180623 0.15196728931870065 -0.11708333769631452 0.022445846230932674 0.45772076880431967 6.2597911689776504 4280 50019.832865606506 114042.02596587484 50203.017905636414 -0.058543515524935627 0.15329900906363705 -0.11835301642090161 0.025097986235671249 0.45783287785202281 7.5451566548671867 4300 50097.830866687633 114102.6235406663 50329.118748849425 -0.058855850718830933 0.15463436020719565 -0.11989706299357185 0.029108712904984513 0.45811528994638584 9.1444746172120137 4320 49996.597625415823 112358.52221614646 50202.474580366077 -0.059235884949447287 0.15597254514148212 -0.12158874938524095 0.034625682912450703 0.45851263596843034 11.11719724108638 4340 50043.168452397629 111330.52189324077 50291.207168384062 -0.059542800028093215 0.15731431607362306 -0.1232672133743669 0.036924241058453433 0.45886102951477564 14.993791357208346 4360 50016.953393826167 109296.90540105513 50271.997750906739 -0.059573108439986616 0.15866058054108817 -0.12540967256805899 0.040577675835451771 0.45930809779492315 24.6325097643611 4380 50022.265955858129 106036.82516859449 50270.337056291377 -0.059408788527178139 0.16001132088215511 -0.12828269492421768 0.045921317605408594 0.46004183859827191 38.718831278184076 4400 49877.932423939703 100597.99307198297 50326.932334801524 -0.058726536204622856 0.1613655881040032 -0.13184396632349027 0.068469771248281311 0.46086451345952634 48.686911854034463 4420 49774.642113080088 98845.071478089492 50331.647166054172 -0.057382658721411753 0.16272522032191972 -0.13566647379520513 0.070684598989034397 0.46146746745200701 53.965811768381947 4440 49679.817739771956 93481.161525257543 50375.462875569545 -0.055816106935967377 0.16408834211787027 -0.13895472898077632 0.082450657758686097 0.46166054069310403 79.61104597099272 4460 49516.346747954391 86063.604317317891 50358.436744992563 -0.053540721501191997 0.16545493261455382 -0.14343166370826974 0.11016469497166581 0.462109844358778 113.20496217731309 4480 49720.736123895374 79558.092056797366 50203.173893496452 -0.050406664169759752 0.16682497105957086 -0.14727089435553137 0.12868553073373781 0.46175210103860759 118.52476071217555 4500 49601.585370174726 81691.251110346551 50280.861050515727 -0.04887047342146545 0.16819843682467447 -0.15020412487328041 0.15090731263105123 0.46176478954735972 102.60889207754309 4520 50226.314616639211 77465.791097861249 49716.764573662462 -0.047904632171657822 0.1695753094050273 -0.15106251535443219 0.14842079512420572 0.46096528123959252 96.514847461722795 4540 49875.470494483139 76585.975453013991 50059.157328399742 -0.046923370371444485 0.17095556841846582 -0.15119830569169493 0.14218212601939423 0.4597641990914792 68.124210681239205 4560 50077.952260577018 78310.061520268893 50265.451287527256 -0.045866652502776634 0.17233919360476327 -0.15206348032007391 0.11744602924754501 0.45891256615840903 40.52709622010979 4580 49458.31282475865 75501.121682674944 50238.633617849213 -0.04601284422251619 0.17372616482490177 -0.1538251214544176 0.099354705546009808 0.45919432445839242 27.62250110350001 4600 50080.108164893471 77501.698347374098 49962.259997643167 -0.045135851600273821 0.17511646206034734 -0.15541023522361519 0.076936641079237922 0.45882527389359473 18.151211644891433 4620 49958.69935335761 78068.314297038625 50239.952190268261 -0.044975917370583013 0.17651006541232925 -0.15646670911679214 0.060960371220529982 0.45855620872239222 18.098706098101783 4640 50143.678439349816 79226.457874099549 49881.407121311247 -0.044664603572848578 0.17790695510112267 -0.15771184572058322 0.05867031020583241 0.45830542597367657 17.015878223579399 4660 49942.767814386505 80584.187508241012 50171.41077527681 -0.044553596433202558 0.17930711146533754 -0.15881840549845658 0.047210899410796071 0.45808620998586608 17.268731648191654 4680 50058.707566855868 80187.729548399948 50334.391313456115 -0.044477530673944624 0.18071051496120932 -0.16034205943102384 0.04746940684909446 0.45811015374289937 19.519934593494316 4700 49862.260878603047 79381.47886088799 50221.554496547971 -0.044382583514860199 0.18211714616189734 -0.16181812206884899 0.047606866229180186 0.45809632685585544 19.96974250556335 4720 49911.732217098557 80347.854994283713 50187.079968084705 -0.044120469961141766 0.18352698575678295 -0.16310701840090042 0.046128324306539424 0.4578887071974187 22.127664348474536 4740 50002.725493710008 81170.306018101517 50201.79166146159 -0.04396723073429306 0.18494001455077588 -0.1640011592252465 0.044422878319686131 0.4575242169758249 24.334197662548853 4760 50060.46876872993 81843.559553035579 50222.117343747334 -0.043887986767730147 0.1863562134636233 -0.16544092113813147 0.040620710228767745 0.45749401055895617 26.045895028018947 4780 49871.452093909786 80517.396507587066 50078.164749991374 -0.043781441588194955 0.18777556352922134 -0.16698258926924209 0.045416128010247601 0.45750256737658668 23.978210645280182 4800 49938.755272726368 82634.599720455531 50208.418254989978 -0.043312626866157326 0.18919804589493358 -0.16828334297222666 0.041285571955429236 0.45718210449219365 23.744586070255068 4820 49966.35487406289 82900.551306741239 50346.826849833691 -0.042796630561429017 0.19062364182091113 -0.16981726157871699 0.042677291696567719 0.45696076683974701 25.848289627900169 4840 50104.286328129834 84581.974496848561 50119.148673412783 -0.04273429302152746 0.19205233267941882 -0.17102480531545655 0.05048050915041951 0.45680680172411786 22.68754723247995 4860 49938.256320359753 85421.810267059976 50411.033893085711 -0.042905072340591842 0.19348409995416502 -0.1723071808956072 0.045480360390724971 0.45681841921202104 15.73704948548249 4880 49910.506685438973 87119.036787519173 50208.90992532875 -0.042860480659941864 0.19491892523963264 -0.17340735004492411 0.044198981566475157 0.45661237971583579 11.357645590347255 4900 50052.470780176271 88764.883080018612 50137.783712641191 -0.042934551163291264 0.19635679024041949 -0.17461167835850935 0.042933941936539655 0.4565257208562814 10.577046469320955 4920 50046.466977929005 90093.724738881254 50078.228581737472 -0.043154776706508691 0.19779767677057686 -0.17568443911093912 0.043320874383460092 0.45644533493854433 10.816105247921067 4940 49906.712097000047 92963.717594310248 50053.783598713329 -0.043171463410771278 0.19924156675295615 -0.17647279352699208 0.042230190920897089 0.45609797471577279 8.8494738449905714 4960 50079.088234278293 94314.453519806193 50184.522746003815 -0.043319602891221574 0.20068844221855764 -0.17733531019220555 0.037849188960455041 0.45586066238784251 6.9573796869322555 4980 50086.766526533662 95761.594170945289 50280.077230119532 -0.043533277134199314 0.20213828530588354 -0.17838478742250427 0.039428358975385359 0.45575906665339511 8.3203772937366036 5000 50089.926352662849 95890.668450550947 50005.618237564595 -0.043826179685603386 0.20359107826029513 -0.17962575821443658 0.036022683809750523 0.45580319213106002 6.8203112258991716 5020 50043.099731357222 97443.774988948251 50073.737295633589 -0.044113224131224781 0.20504680343337359 -0.1803518899541478 0.032647047413979995 0.45556230508637569 7.3693897977000953 5040 50066.219640153846 98452.154507104162 50125.747599132061 -0.044604215914705425 0.20650544328228324 -0.1811762444344093 0.033853868711567169 0.45548428506742061 7.8633490755703308 5060 50000.084027219476 99479.647498175065 50063.378283852246 -0.045078295626489601 0.20796698036914193 -0.18191250555677904 0.032258506680413683 0.45534748757950178 7.4126256004895934 5080 50134.503812210176 101177.21205481704 50133.617811225216 -0.045481650155644524 0.20943139736039107 -0.18248571753982268 0.034322269107629637 0.45508171376673157 10.024791054895354 5100 50031.784073063653 101618.84274120234 50097.560802058397 -0.046091512917836995 0.21089867702617338 -0.18317629512464423 0.03734115758381535 0.45499079238665241 9.7863047774799128 5120 50089.928571843819 101765.56635586376 50080.929639226975 -0.046408429321742557 0.21236449713659059 -0.18381153924569929 0.039451468671509345 0.45471077127139153 16.935484499586956 5140 49942.785046661695 101000.50672861833 50410.782039059894 -0.046734108788430657 0.2138331374871196 -0.18489763636630863 0.040270322566015801 0.45467976319218678 18.15901795925555 5160 49989.077937772243 100110.75111740707 50368.782657233503 -0.046517852692243497 0.21530385086112036 -0.18644481420763984 0.045989170862804366 0.45460352665954434 22.236375906139411 5180 50177.164338726332 97744.983509655634 50423.734394344305 -0.046656711825008634 0.21677661841494522 -0.18796305434888225 0.047064371811545873 0.45470405126443553 23.865659253790454 5200 49934.648774237896 98786.859529773443 50450.872178780315 -0.047260740281720878 0.2182521531439193 -0.18904641729590851 0.045972581252131287 0.45481956361443399 24.12219914183077 5220 49875.968392470371 97182.978023608681 50314.981438100484 -0.047554154882865955 0.21973625275941949 -0.19053055550626288 0.05098500320827308 0.45497952508413736 18.259196753422959 5240 50111.4522779279 99389.331762427319 50291.268811217102 -0.047653054764541472 0.22122309729241849 -0.19163910926511948 0.042549480381630499 0.45482723005330639 18.11099388222296 5260 50161.131117961857 99798.868587595163 50323.886762850227 -0.047654412514577887 0.22268727429972621 -0.19290337442128208 0.043530509153383877 0.45471897301693692 22.432163341880269 5280 50091.075716961408 99836.834232639041 50077.658196943317 -0.048404078841313279 0.22412869819535811 -0.19382605056406629 0.052089935869705714 0.45484487399705914 21.387769272009578 5300 50066.645540680089 98770.600445443255 50083.60894320051 -0.049556478061274181 0.22557272614706506 -0.1944417021139937 0.054306773210310876 0.45502148808229048 24.835307140941744 5320 50306.79070633329 97722.721411464852 50162.450174499587 -0.050603714535659872 0.22701927555876655 -0.19509010408420163 0.062371005408256235 0.45515721934748449 23.85487128790793 5340 50238.519358283069 97762.906061296482 50228.68220438814 -0.051967897033152563 0.22846839784144679 -0.19584303411265588 0.066040969734574531 0.45552104747872507 23.55209385779834 5360 50144.815615828185 96154.189806644819 50172.595374746532 -0.053057334060618147 0.22991924324218638 -0.19666448984331378 0.05866180624864175 0.45577147597308332 28.466931612052683 5380 50227.459458196223 96246.695203007359 50337.970054853431 -0.054082575401376773 0.23137179427147714 -0.19750805881248673 0.061043301237320116 0.45599796900381556 32.590986545779785 5400 50424.630605617313 93712.861959500224 50474.565011019135 -0.054991973720694316 0.2328268693649222 -0.19848906981243691 0.063455097509371561 0.45623474016579069 39.674771268600807 5420 50074.590989479453 91276.931190353323 50279.240377391201 -0.056151386181305767 0.23428647417431442 -0.20017282183471835 0.064244118941324813 0.45698655164319807 42.464729032148888 5440 50186.431847232358 88493.388780441339 50260.460751363411 -0.056731602843707568 0.2357485763939054 -0.20108283104717981 0.073213805625835043 0.45700182295148423 48.273706153253855 5460 50044.964906859415 85875.467231153045 50100.45692252944 -0.05679203801734603 0.23721380541317999 -0.20219238614110627 0.076353956475388812 0.45684148517971479 59.686382279609703 5480 49883.645542836268 82683.377196242887 50224.689956283859 -0.057579046778529368 0.23868214800615706 -0.2033845222488693 0.091532687154442752 0.4571188609371819 54.49050989945777 5500 50443.112173553811 81850.959077907173 50132.968714702241 -0.058508995253680592 0.24015294560978931 -0.20314539784682514 0.099317965717742512 0.45669526284231565 40.451056080614265 5520 50284.342633961642 82591.776042534912 49706.215635494234 -0.059889699956110903 0.24162600836633916 -0.20263539583873688 0.094346198206426118 0.45636789918154436 27.073915066873059 5540 50347.529257885071 83467.583610925125 49976.838205884233 -0.061390899015480532 0.24310149655726485 -0.20260095992437038 0.082165430650326179 0.45636315594580729 19.547132668173987 5560 49932.66778396954 83803.975837114514 49947.914241356993 -0.062595089634444656 0.24457949985127372 -0.20297892268457207 0.071964379831277347 0.45641977282103269 13.000636655537352 5580 50056.579797838902 83854.226415748766 50106.282708177801 -0.063389087764268645 0.24606000407731815 -0.20345736730923386 0.060680039668454214 0.45630666295256073 10.318174024534395 5600 49986.106565669761 86474.734020396529 50060.206286787776 -0.064231247380007989 0.24754289080064076 -0.2040682682201353 0.046739331288687222 0.45629044640631766 9.3013301118499783 5620 50098.389358528264 87164.68251376359 50071.858179458919 -0.064627816707984495 0.24902816625643209 -0.20450776766106907 0.045517262033833522 0.455937351925156 11.530944401960133 5640 50204.385715703465 87735.734611559077 50087.141597438509 -0.065253783627758633 0.25051579581918476 -0.2053871881833757 0.054076767275284036 0.4559470132382249 7.8437649803667266 5660 50178.514325315853 89279.568858602215 50106.634003742773 -0.066196009632882399 0.25200542525080882 -0.20572393902158917 0.04808759440679311 0.45583239499650746 7.3486292870041927 5680 50249.331265635032 89858.283755270138 50037.616963319844 -0.067094599982423203 0.25349703975592858 -0.20594286862040309 0.043409616537400657 0.45562878677551882 8.8514485107813101 5700 50388.971803519424 91453.430217310772 50111.459229516033 -0.068218449181192381 0.25499096531242155 -0.2061494725652035 0.040538607707359231 0.45553978982569221 8.3361161904529393 5720 50039.248039384329 92715.839505999145 50133.003289857726 -0.069276299211763925 0.25653897990239488 -0.20660750028394684 0.041197623919065007 0.45552229231351699 8.0310051688724098 5740 49967.409684746402 94233.204748135366 50205.442134553632 -0.069870187992676719 0.25808935701911329 -0.20741684707955144 0.038727755657781471 0.45544217113057034 6.0425087518825098 5760 50200.347707279565 95165.46190551357 49938.75496024188 -0.070438010616140523 0.25964208252801907 -0.20843318764089572 0.035510517650381643 0.45545929049400652 4.446925471537746 5780 50052.374124449678 98722.439857639911 50094.100109211467 -0.070989165361242459 0.26119714237911801 -0.20903219785646765 0.03096085741732572 0.45523876410110686 3.8551010978335158 5800 50063.211864438032 100619.21029748341 50221.13994418639 -0.071599822149364917 0.26275452260647253 -0.20976334722916004 0.031996195453287114 0.4551213150471608 4.4962267379250891 5820 50235.212042287014 102313.78233939828 50127.648007565214 -0.07235441704272548 0.26431420932769883 -0.21061618594568829 0.031196036670794784 0.45514733068797786 5.78219253205984 5840 50181.363969300517 103358.42387103706 50179.002090312497 -0.073132631756195957 0.2658761887434673 -0.21146722043242849 0.034277495513036653 0.45518398157683276 7.6641959925476559 5860 50135.300638648849 104879.63347689452 50147.623538860054 -0.073584807446232289 0.26744012316004034 -0.2124452029808799 0.031935301419332179 0.45511109329687949 7.5695697157980115 5880 50217.92359901443 106349.96020711458 50103.910780160812 -0.074406173368757489 0.26900599823987126 -0.21317349375500586 0.031355952311551909 0.45510225600813703 7.9989767781527892 5900 50048.724998787431 107654.65335644185 50198.988937297312 -0.075015571202733652 0.2705741241771828 -0.21401076947687819 0.030174192162723525 0.45503607286050635 6.6071407257112318 5920 50015.532521099783 108613.85006899349 50210.824260680885 -0.075539468114404859 0.27214362237071277 -0.21486236662976668 0.031462670955788065 0.45493033735227051 6.8227273499004388 5940 50158.538626418864 109104.61372799512 50125.329660554999 -0.076242227944850374 0.27371534334405978 -0.21583656268260865 0.032643004902589194 0.45498769469108025 7.2952988434395785 5960 50072.585968079002 109226.00163481837 50207.263840624531 -0.076995633524997484 0.27528830027817863 -0.21687726278599342 0.031714532884444147 0.45510821015440095 9.2558724614297692 5980 50073.716660544356 109377.79961791448 50190.831663251338 -0.077431588111033733 0.27686247791913193 -0.21811499991420205 0.035637806848631326 0.45516243185795552 10.694902427029412 6000 50341.330714257878 109167.41560217475 50239.105090538353 -0.078376624854949922 0.27843883598282276 -0.21908828119232202 0.035270680328133228 0.45534871263796334 10.565428857249609 6020 50051.113091763917 109253.53899871861 50272.67313741368 -0.079715785115832563 0.28002084213996897 -0.22013244802524121 0.039530964540065848 0.45578497829995401 11.544646964079959 6040 50013.142955518539 108523.34215979189 50124.419437487784 -0.080419816964169499 0.28160500745848732 -0.22147468949998644 0.038948314334578141 0.45603640631976111 9.7450160812313733 6060 49981.526987019097 110534.89886593015 50319.381573747683 -0.080670135289276523 0.28319131902049566 -0.2226938198831025 0.033061211320049418 0.45597283346726802 8.6847877814875751 6080 50064.204179728462 111636.37361389346 50152.510969598312 -0.080936948334405054 0.28477976398539684 -0.22404543082138842 0.036450770338825426 0.45598914262329787 11.587232382531374 6100 50219.320539341046 110652.8146127238 50201.067440101331 -0.082366368329940279 0.28637032958941433 -0.22538867775250568 0.036480130668230783 0.45663183863658696 13.919379754874397 6120 50241.393643196716 109070.98606390026 50207.265283638801 -0.083818253693085543 0.28796300314513384 -0.2265382209653054 0.037308604977066527 0.4571796876280258 15.690617928962597 6140 50176.115519917534 108494.77354106931 50124.847547793506 -0.084663139091175871 0.28955777204104649 -0.22718123560158832 0.045523011031923036 0.45712167393934894 23.802773900957106 6160 50065.054042239062 107786.64196089511 50230.302972863865 -0.085755584982377384 0.29115462374109441 -0.22840711997710486 0.050499766273761175 0.45751320775729881 24.782910397944015 6180 50050.819075917134 105745.92187955641 49671.366165277541 -0.086712502367581948 0.29275354578422025 -0.22980885330282438 0.060320545347439573 0.45792519400656928 17.691013695960805 6200 50260.635140706945 105686.51121844171 50682.329406498109 -0.087296174593213954 0.2943545257839168 -0.23079041103230563 0.063589253493493605 0.45790581446167977 16.446959711942942 6220 50097.602689844498 105714.00493883864 50213.916698934736 -0.087959900121024642 0.29595755142778246 -0.23211408690846638 0.062838741615840493 0.45811414228564723 19.070829974172554 6240 50263.792618080974 103133.00234232913 50398.417057285253 -0.087928561435135555 0.29756261047707722 -0.23373332027418692 0.057287819056729812 0.45810484107675375 25.65576992725796 6260 50268.710599935497 100871.31950140072 50516.926544817594 -0.088518083220486002 0.29916969076628191 -0.2355197881535698 0.052991721379500097 0.45852134919598381 33.841679898625863 6280 49702.988750047429 96263.987118667748 50720.303911513336 -0.088655849217965946 0.3007787802026603 -0.23752574209359434 0.068141701380696362 0.45881076282684147 48.115022366467386 6300 50118.170558062673 92860.744241536813 50600.657090663291 -0.087291661525518843 0.30238986676582452 -0.24043955231844788 0.084071505568453608 0.45877749813264163 45.476841611366517 6320 49974.119894195763 91955.357264575738 50157.418451469173 -0.087026784539028998 0.30398811774950796 -0.24277278367387797 0.083297770291876183 0.45903186886798997 39.699416358522484 6340 49804.873288495517 92332.193190603823 50038.163766612139 -0.086999134561215982 0.30558832390327401 -0.24379722823272462 0.07486212551041789 0.45870534393263057 19.998715792035725 6360 50240.820470158869 95875.387197981108 50095.9797735916 -0.087352120361838814 0.30718876421462815 -0.24475554789781537 0.067515627842390374 0.45854881415778687 13.976986902612555 6380 50198.516408282921 97687.863790415737 49943.825520712904 -0.087755366825108136 0.30878942396840425 -0.24531433520418988 0.05668544710546259 0.45820291892428272 8.2862771378297992 trunk-2018.02b/scripts/checks-and-tests/checks/data/checkTestTriax.spheres000066400000000000000000000070451324306050200265140ustar00rootroot000000000000000.889007 0.370253 0.49249 0.112051 0.106231 0.613934 0.902817 0.0962812 0.12452 0.873184 0.875371 0.127055 0.832523 0.191905 0.869978 0.130982 0.427274 0.712482 0.491121 0.129754 0.130636 0.121106 0.121567 0.122252 0.924603 0.40166 0.288205 0.0760697 0.664008 0.704862 0.117063 0.122249 0.723763 0.417214 0.676983 0.0903237 0.580382 0.878832 0.68611 0.124262 0.575315 0.555035 0.254806 0.100855 0.74337 0.486397 0.280619 0.0851146 0.39043 0.2715 0.899719 0.100437 0.629798 0.754126 0.885567 0.116237 0.450424 0.114867 0.114739 0.118933 0.519912 0.328938 0.717354 0.130634 0.477927 0.129573 0.55581 0.130896 0.927541 0.716063 0.789473 0.074717 0.460968 0.74796 0.241157 0.12545 0.462977 0.631696 0.697762 0.0873663 0.272396 0.309091 0.472434 0.108493 0.116645 0.116327 0.691447 0.118586 0.878025 0.8233 0.121058 0.124751 0.739467 0.469218 0.439503 0.0745774 0.333154 0.91401 0.91469 0.0891591 0.917424 0.919932 0.298377 0.0844085 0.273897 0.663729 0.647263 0.0957049 0.333073 0.891726 0.108271 0.108659 0.581354 0.500781 0.548516 0.119384 0.86913 0.578421 0.619917 0.135086 0.733968 0.679969 0.323001 0.0979915 0.273468 0.103312 0.895351 0.104989 0.587308 0.127969 0.32062 0.129824 0.106708 0.694978 0.538144 0.108407 0.126469 0.531662 0.169561 0.130534 0.773902 0.769791 0.721813 0.103444 0.905888 0.906728 0.669521 0.096856 0.769344 0.291209 0.315762 0.116945 0.700505 0.0913456 0.61564 0.0936637 0.297977 0.130503 0.313111 0.132732 0.665138 0.483651 0.0951909 0.0948744 0.0848007 0.0844155 0.319672 0.0850136 0.872557 0.568489 0.130253 0.130598 0.0903266 0.708086 0.737566 0.0901761 0.36138 0.478965 0.612469 0.112567 0.0801136 0.0785539 0.919915 0.0805341 0.690681 0.904312 0.203504 0.0973209 0.721138 0.41539 0.883633 0.118038 0.0965956 0.182296 0.471412 0.0967464 0.0831669 0.297167 0.608682 0.0836104 0.335886 0.896917 0.35925 0.110526 0.256366 0.0949048 0.533258 0.0958868 0.869386 0.129365 0.472819 0.132068 0.447891 0.317073 0.235048 0.120905 0.121732 0.274307 0.875431 0.126186 0.57658 0.275729 0.917692 0.0871043 0.595802 0.396661 0.365697 0.0955921 0.604975 0.874928 0.406538 0.12675 0.115718 0.881873 0.409916 0.11954 0.662571 0.708538 0.556382 0.106038 0.129734 0.492715 0.687081 0.133176 0.640484 0.297388 0.155114 0.0863378 0.662068 0.594781 0.735305 0.106973 0.0740249 0.704755 0.0740086 0.0740423 0.288438 0.914122 0.746346 0.0883019 0.608424 0.114666 0.800904 0.11637 0.885311 0.112878 0.226228 0.115618 0.865802 0.800774 0.470278 0.134602 0.897753 0.384666 0.754854 0.103426 0.280255 0.282575 0.694192 0.115552 0.881949 0.319611 0.114871 0.120729 0.922221 0.250453 0.635867 0.0779785 0.127178 0.489092 0.42654 0.127629 0.242809 0.709746 0.0948951 0.0950931 0.407839 0.0878376 0.758298 0.0879989 0.277809 0.285493 0.099211 0.0990407 0.317299 0.699551 0.868143 0.133576 0.909094 0.0904488 0.688579 0.0913639 0.467612 0.337789 0.485116 0.0892909 0.504256 0.523261 0.879854 0.124714 0.892311 0.559626 0.37862 0.109296 0.117733 0.864499 0.172626 0.11874 0.684607 0.10613 0.105716 0.105557 0.113043 0.290449 0.29119 0.114165 0.6737 0.281479 0.528252 0.119101 0.880302 0.880578 0.882143 0.120219 0.890397 0.566897 0.890321 0.110156 0.455847 0.0808486 0.920019 0.0811885 0.3068 0.899905 0.563592 0.100155 0.236787 0.690509 0.328985 0.121908 0.594301 0.632676 0.408954 0.0737832 0.110501 0.889304 0.639394 0.111008 0.372094 0.494768 0.379451 0.122105 0.534502 0.905335 0.0939354 0.0947423 0.393152 0.791555 0.686637 0.0856638 0.508811 0.913869 0.912628 0.087609 0.276046 0.455607 0.884703 0.117275 0.382944 0.533427 0.127997 0.132751 0.923481 0.716801 0.285144 0.0772079 0.0915387 0.328191 0.0903334 0.0922539 trunk-2018.02b/scripts/checks-and-tests/collider-perf/000077500000000000000000000000001324306050200225445ustar00rootroot00000000000000trunk-2018.02b/scripts/checks-and-tests/collider-perf/README000066400000000000000000000023031324306050200234220ustar00rootroot00000000000000This example tests performance of InsertionSortCollider (with and without stride) and SpatialQuickSortCollider and was basis for http://yade.wikia.com/wiki/Colliders_performace, which features now-removed PersistentSAPCollider as well. To run the test, say: yade-trunk-multi perf.table perf.py It will take time to finish. To collet results and get graphs from the generated log files, say python mkGraph.py *.log To see other results, log files are named like perf.64k.q.log (64k spheres with SpatialQuickSortCollider), perf.32k.i.log (32k spheres, InsertionSortCollider), perf.40k.is.log (InsertionSortCollider with stride) etc. 1. File with nSpheres spheres (loose packing) is generated first, it if doesn't exist yet. This can take a few minutes, but is done only the first time for the particular sphere number. 2. First iteration on the scene (TriaxialTest and the selected collider) with timings is done and timing.stats() printed (appears in the log file). 3. Another 100 iterations are measured with timing.stats(), after which the test exits. (Note: InsertionSortCollider with stride is currently commented out in the table, as striding is not effective until later during the simulation.) trunk-2018.02b/scripts/checks-and-tests/collider-perf/mkGraph.py000066400000000000000000000026111324306050200245070ustar00rootroot00000000000000#encoding: utf-8 dta={'QS':{},'IS':{},'ISS':{}} import sys for f in sys.argv[1:]: print f,'', N=f.split('.')[1]; assert(N[-1]=='k'); N=1000*int(N[:-1]) if '.q.' in f: collider='QS' elif '.i.' in f: collider='IS' elif '.is.' in f: collider='ISS' else: raise RuntimeError("Unknown collider type for file "+f) for l in open(f): if 'Collider' in l: t=l.split()[2]; assert(t[-2:]=='us'); t=float(t[:-2])/1e6 if not dta[collider].has_key(N): dta[collider][N]=[t] else: dta[collider][N]+=[t*0.01] # the second time is per 100 iterations print ISS_N=dta['ISS'].keys(); ISS_N.sort() QS_N=dta['QS'].keys(); QS_N.sort() IS_N=dta['IS'].keys(); IS_N.sort() ISSinit=[dta['ISS'][N][0] for N in ISS_N]; ISSstep=[dta['ISS'][N][1] for N in ISS_N] QSinit=[dta['QS'][N][0] for N in QS_N]; QSstep=[dta['QS'][N][1] for N in QS_N] ISinit=[dta['IS'][N][0] for N in IS_N]; ISstep=[dta['IS'][N][1] for N in IS_N] from pylab import * plot(IS_N,ISinit,'y',ISS_N,ISSinit) gca().set_yscale('log') xlabel("Number of spheres") ylabel(u"Log time for the 1st collider step [s]") title("Colliders performance (QS=QuickSoft, IS=InsertionSort, IS/s=IS+stride)") legend(('IS init','IS/s init',),'upper left') ax2=twinx() plot(IS_N,ISstep,'k-',ISS_N,ISSstep,'r-',QS_N,QSstep,'g-',QS_N,QSinit,'b-') ylabel(u"Linear time per 1 step [s]") legend(('IS step','IS/s step','QS step','QS init'),'right') grid() savefig('colliders.svg') show() trunk-2018.02b/scripts/checks-and-tests/collider-perf/perf.py000066400000000000000000000015641324306050200240600ustar00rootroot00000000000000 utils.readParamsFromTable(nSpheres=8000,collider='InsertionSortCollider',noTableOk=True) # name of file containing sphere packing with given number of spheres spheresFile="packing-%dk.spheres"%(nSpheres/1000) fast='@stride' in collider import os if not os.path.exists(spheresFile): print "Generating packing" p=TriaxialTest(numberOfGrains=nSpheres,radiusMean=1e-3,lowerCorner=[0,0,0],upperCorner=[1,1,1],noFiles=True) p.load() utils.spheresToFile(spheresFile) O.reset() print "Packing %s done"%spheresFile else: print "Packing found (%s), using it."%spheresFile from yade import timing O.timingEnabled=True TriaxialTest(importFilename=spheresFile,fast=fast,noFiles=True).load() O.dt=utils.PWaveTimeStep() isc=O.engines[2] isc.sweepLength=1e-1 if not fast: utils.replaceCollider(eval(collider)) O.step() timing.stats() timing.reset() O.run(200,True) timing.stats() quit() trunk-2018.02b/scripts/checks-and-tests/collider-perf/perf.table000066400000000000000000000060611324306050200245140ustar00rootroot00000000000000!OMP_NUM_THREADS description nSpheres collider 4 128k.q 128000 'SpatialQuickSortCollider' 4 96k.q 96000 'SpatialQuickSortCollider' 4 64k.q 64000 'SpatialQuickSortCollider' 4 56k.q 56000 'SpatialQuickSortCollider' 4 48k.q 48000 'SpatialQuickSortCollider' 4 40k.q 40000 'SpatialQuickSortCollider' 4 36k.q 36000 'SpatialQuickSortCollider' 4 32k.q 32000 'SpatialQuickSortCollider' 4 28k.q 28000 'SpatialQuickSortCollider' 4 24k.q 24000 'SpatialQuickSortCollider' 4 20k.q 20000 'SpatialQuickSortCollider' 4 18k.q 18000 'SpatialQuickSortCollider' 4 16k.q 16000 'SpatialQuickSortCollider' 4 14k.q 14000 'SpatialQuickSortCollider' 4 12k.q 12000 'SpatialQuickSortCollider' 4 10k.q 10000 'SpatialQuickSortCollider' 4 9k.q 9000 'SpatialQuickSortCollider' 4 8k.q 8000 'SpatialQuickSortCollider' 4 7k.q 7000 'SpatialQuickSortCollider' 4 6k.q 6000 'SpatialQuickSortCollider' 4 5k.q 5000 'SpatialQuickSortCollider' 4 4k.q 4000 'SpatialQuickSortCollider' 4 3k.q 3000 'SpatialQuickSortCollider' 4 2k.q 2000 'SpatialQuickSortCollider' 4 1k.q 1000 'SpatialQuickSortCollider' 4 128k.i 128000 'InsertionSortCollider' 4 96k.i 96000 'InsertionSortCollider' 4 64k.i 64000 'InsertionSortCollider' 4 56k.i 56000 'InsertionSortCollider' 4 48k.i 48000 'InsertionSortCollider' 4 40k.i 40000 'InsertionSortCollider' 4 36k.i 36000 'InsertionSortCollider' 4 32k.i 32000 'InsertionSortCollider' 4 28k.i 28000 'InsertionSortCollider' 4 24k.i 24000 'InsertionSortCollider' 4 20k.i 20000 'InsertionSortCollider' 4 18k.i 18000 'InsertionSortCollider' 4 16k.i 16000 'InsertionSortCollider' 4 14k.i 14000 'InsertionSortCollider' 4 12k.i 12000 'InsertionSortCollider' 4 10k.i 10000 'InsertionSortCollider' 4 9k.i 9000 'InsertionSortCollider' 4 8k.i 8000 'InsertionSortCollider' 4 7k.i 7000 'InsertionSortCollider' 4 6k.i 6000 'InsertionSortCollider' 4 5k.i 5000 'InsertionSortCollider' 4 4k.i 4000 'InsertionSortCollider' 4 3k.i 3000 'InsertionSortCollider' 4 2k.i 2000 'InsertionSortCollider' 4 1k.i 1000 'InsertionSortCollider' #4 128k.is 128000 'InsertionSortCollider' #4 96k.is 96000 'InsertionSortCollider' #4 64k.is 64000 'InsertionSortCollider' #4 56k.is 56000 'InsertionSortCollider' #4 48k.is 48000 'InsertionSortCollider@stride' #4 40k.is 40000 'InsertionSortCollider@stride' #4 36k.is 36000 'InsertionSortCollider@stride' #4 32k.is 32000 'InsertionSortCollider@stride' #4 28k.is 28000 'InsertionSortCollider@stride' #4 24k.is 24000 'InsertionSortCollider@stride' #4 20k.is 20000 'InsertionSortCollider@stride' #4 18k.is 18000 'InsertionSortCollider@stride' #4 16k.is 16000 'InsertionSortCollider@stride' #4 14k.is 14000 'InsertionSortCollider@stride' #4 12k.is 12000 'InsertionSortCollider@stride' #4 10k.is 10000 'InsertionSortCollider@stride' #4 9k.is 9000 'InsertionSortCollider@stride' #4 8k.is 8000 'InsertionSortCollider@stride' #4 7k.is 7000 'InsertionSortCollider@stride' #4 6k.is 6000 'InsertionSortCollider@stride' #4 5k.is 5000 'InsertionSortCollider@stride' #4 4k.is 4000 'InsertionSortCollider@stride' #4 3k.is 3000 'InsertionSortCollider@stride' #4 2k.is 2000 'InsertionSortCollider@stride' #4 1k.is 1000 'InsertionSortCollider@stride' trunk-2018.02b/scripts/checks-and-tests/compare-identical.py000066400000000000000000000035011324306050200237460ustar00rootroot00000000000000""" This files tests simulation correctness by loading a simulation two times, and periodically comparing the results, showing diffs in the simulation XML, if any. It stops after the differece is first spotted. On openMP-enabled installs, it should be run with OMP_NUM_THREADS=1, otherwise there will be noise differences coming from non-deterministic order of interactions in the container (although they are the same). You can use OMP_NUM_THREADS=2 to see what happens if the simulations get different. """ # this is to provide some default simulation to test on # comment it out and provide your own simulation XML in init TriaxialTest().generate('/tmp/TriaxialTest.xml') # # what is the initial file to load initFile='/tmp/TriaxialTest.xml' # that is the prefix for HTML diffs, if any outPrefix='/tmp/scene_' # at which step to stop stopIter=2000 # how may steps to run between comparisons nSteps=100 # quiet annoying messages if O.numThreads>1: print "WARNING: You should run single-threaded with OMP_NUM_THREADS=1; interaction order will be probably different otherwise!" for scene in 0,1: O.load(initFile); O.interactions.serializeSorted=True; O.switchScene(); from hashlib import md5; import difflib,sys print "Identical at steps ", for i in xrange(0,stopIter/nSteps): sys.stdout.flush() for scene in 'A','B': O.run(nSteps,True); O.saveTmp(scene); O.switchScene() A,B=O.tmpToString('A'),O.tmpToString('B') # fast compare first using hash digest Ahash,Bhash=md5(A),md5(B) if Ahash.digest()==Bhash.digest(): print O.iter,; continue print "\nComputing differences..." diff=difflib.HtmlDiff(tabsize=3,wrapcolumn=80) outName=outPrefix+'%05d_diff.html'%O.iter out=open(outName,'w') out.write(diff.make_file(A.split('\n'),B.split('\n'),context=True,numlines=2)) print 'file://%s'%outName break # stop at the first different value trunk-2018.02b/scripts/checks-and-tests/dispatcher-torture.py000066400000000000000000000037461324306050200242310ustar00rootroot00000000000000""" Script that shows dispatch matrices when all available functors are loaded. Later ones will overwrite earlier ones, this is not what you will get in reality. Pipe the output to file and open it in browser: $ yade-trunk dispatcher-torture.py > /tmp/aa.html $ firefox /tmp/aa.html """ import collections Dispatch=collections.namedtuple('Dispatch',['basename','types']) dispatches=[ Dispatch('Law',('IGeom','IPhys')), Dispatch('IGeom',('Shape','Shape')), Dispatch('IPhys',('Material','Material')), Dispatch('Bound',('Shape',)), Dispatch('GlBound',('Bound',)), Dispatch('GlIGeom',('IGeom',)), Dispatch('GlIPhys',('IPhys',)), Dispatch('GlShape',('Shape',)), #Dispatch('GlState',('State',)) # broken for now ] sys.path.append('.') import HTML outStr='' for D in dispatches: functors=yade.system.childClasses(D.basename+'Functor') # create dispatcher with all available functors dispatcher=eval(D.basename+'Dispatcher([%s])'%(','.join(['%s()'%f for f in functors]))) if len(D.types)==1: allDim0=list(yade.system.childClasses(D.types[0])) table=HTML.Table(header_row=allDim0) row=[] for d0 in allDim0: dd0=eval(d0+'()') try: f=dispatcher.dispFunctor(dd0) row.append(f.name if f else '-') except RuntimeError as strerror: row.append('ambiguous (%s)'%(strerror)) table.rows.append(row) elif len(D.types)==2: # lists of types the dispatcher accepts allDim0=list(yade.system.childClasses(D.types[0])) allDim1=list(yade.system.childClasses(D.types[1])) table=HTML.Table(header_row=['']+allDim1) for d0 in allDim0: row=[''+d0+''] for d1 in allDim1: dd0,dd1=eval(d0+'()'),eval(d1+'()') try: f=dispatcher.dispFunctor(dd0,dd1) row.append(f.__class__.__name__ if f else '-') except RuntimeError: # ambiguous row.append('ambiguous') table.rows.append(row) else: raise ValueError("Dispatcher must be 1D or 2D, not %dD"%len(D.types)) outStr+='\n